티스토리 뷰
[ URL설계 ]
- forumSlug를 이용해 게시판을 구분한다.
- 처음엔 post 등록, 삭제, 변경은 {forumSlug}를 빼고 구현할까 했지만 등록의 경우 어느 게시판에 게시글을 작성하는지에 대한 정보가 필요하다. 조회의 경우 조회가 끝난 뒤 목록으로 돌아가거나 게시판 내의 다른 게시물 리스트를 보여줄 때 게시판에 대한 정보가 필요하다. 삭제, 변경 등은 각 요청을 처리한 뒤 게시판으로 돌아가야 되는데, 이때 게시판에 대한 정보가 필요하다. 그래서 모두 forumSlug를 URI에 추가하게 되었다.
GET /posts/{forumSlug}/{postNumber} : post 조회
GET /posts/{forumSlug} : post list 조회
GET /posts/{forumSlug}/registration : post 등록 양식
POST /posts/{forumSlug} : post 등록 요청
PATCH /posts/{forumSlug}/{postNumber} : post 내용 변경
DELETE /posts/{forumSlug}/{postNumber} : post 삭제
[ 고민 ]
- 게시글을 등록할 때 writer를 사용자 아이디로 하는 게 좋을지, 사용자 닉네임으로 하는 게 좋을지 모르겠다. 이렇게 생각해보니 애초에 writer란 말도 애매하다. writer가 userNumber를 의미하는지, userId를 의미하는지, userNickname을 의미하는지 알 수가 없다. 그런데 이미 nickname이라 간주하고 구현했다. 일단은 이렇게 해두고 나중에 다시 손봐야겠다.
- 아이디나 닉네임을 바꾸는 기능을 추가했을 때 생각해봤다. 처음엔 이런 기능을 추가했을 때 어떤 문제가 생기지 않을까 걱정했는데, DB엔 posts 필드가 userNumber로 되어 있어서 바뀌어도 상관없다. 프런트 관점, DB 관점, Service 관점 등을 돌아가면서 생각하다 보니 조금 헷갈린다.
- post 조회 화면에서 변경, 삭제 요청 버튼을 이용해서 이동을 한다. 그럼 조회할 때 PostVO를 보내주니까 조회 화면 상에 있을 땐 PostVO에 대한 정보를 가지고 있고 따라서 postNumber 정보도 가지고 있다. 첫 구현이라 그런지 이 사실을 깜빡한다.
[ DB Table and Sequence ]
create table posts (
post_number number(20) primary key,
forum_number number(20) references forums(forum_number) on delete set null,
post_title varchar2(100) not null,
post_content varchar2(2000) not null,
post_writer number(20) references users(user_number) on delete cascade,
date_posted date default sysdate not null,
date_post_modified date,
post_views_count number(20) default 0 not null
);
create sequence seq_post;
[ PostVO ]
@Data
public class PostVO {
private Long number;
private Long forumNumber;
private String title;
private String content;
private String writer;
private Date datePosted;
private Date dateModified;
private Long views;
}
[ PostMapper Interface and Impl ]
public interface PostMapper {
public int insertPost(PostVO post);
public PostVO readPostByPostNumber(Long postNumber);
public List<PostVO> readPostsByForumNumber(@Param("forumNumber") Long forumNumber, @Param("from") Long from, @Param("to") Long to);
public long readTotalPostsCountByForumNumber(Long forumNumber);
public int updatePost(PostVO post);
public int increasePostViewsByPostNumber(Long postNumber);
public int deletePostByPostNumber(Long postNumber);
}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.bibidi.mapper.PostMapper">
<resultMap type="com.bibidi.domain.PostVO" id="postMap">
<id property="number" column="forum_number"/>
<result property="number" column="post_number"/>
<result property="forumNumber" column="forum_number" />
<result property="title" column="post_title" />
<result property="content" column="post_content" />
<result property="writer" column="user_nickname" />
<result property="datePosted" column="date_posted" />
<result property="dateModified" column="date_post_modified" />
<result property="views" column="post_views_count" />
</resultMap>
<insert id="insertPost">
INSERT INTO posts
(post_number, forum_number, post_title, post_content, post_writer)
VALUES
(seq_post.nextval, #{forumNumber}, #{title}, #{content},
(SELECT user_number FROM users WHERE user_nickname = #{writer}))
</insert>
<select id="readPostByPostNumber" resultMap="postMap">
SELECT post_number, forum_number, post_title, post_content, user_nickname, date_posted, date_post_modified, post_views_count
FROM posts
INNER JOIN users ON user_number = post_writer
WHERE post_number = #{postNumber}
</select>
<select id="readPostsByForumNumber" resultMap="postMap">
<![CDATA[
SELECT post_number, post_title, user_nickname, date_posted, post_views_count
FROM
(SELECT ROWNUM rn, post_number, post_title, user_nickname, date_posted, post_views_count
FROM
(
SELECT post_number, post_title, user_nickname, date_posted, post_views_count
FROM posts
INNER JOIN users ON post_writer = user_number
WHERE forum_number = #{forumNumber}
ORDER BY post_number DESC
)
WHERE ROWNUM <= #{to}
)
WHERE rn >= #{from}
]]>
</select>
<select id="readTotalPostsCountByForumNumber" resultType="long">
SELECT count(*) FROM posts
WHERE forum_number = #{forumNumber}
</select>
<update id="updatePost">
UPDATE posts SET
post_title = #{title},
post_content = #{content},
date_post_modified = sysdate
WHERE post_number = #{number}
</update>
<update id="increasePostViewsByPostNumber">
UPDATE posts SET
post_views_count = post_views_count + 1
WHERE post_number = #{postNumber}
</update>
<delete id="deletePostByPostNumber">
DELETE FROM posts
WHERE post_number = #{postNumber}
</delete>
</mapper>
[ PostMapper Tests ]
@RunWith(SpringRunner.class)
@ContextConfiguration("file:src/main/webapp/WEB-INF/spring/root-context.xml")
@Log4j
public class PostMapperTests {
@Setter(onMethod_ = @Autowired)
private PostMapper postMapper;
@Test
public void testInsertPost() {
PostVO post = new PostVO();
post.setForumNumber(1L);
post.setTitle("new title");
post.setContent("new content");
post.setWriter("bibidi");
log.info("THE NUMBER OF INSERTED POSTS : " + postMapper.insertPost(post));
}
@Test
public void testReadPostByPostNumber() {
log.info(postMapper.readPostByPostNumber(1L));
}
@Test
public void testReadPostsByForumNumber() {
postMapper
.readPostsByForumNumber(1L, 1L, 10L)
.forEach(post -> log.info(post));
}
@Test
public void testReadTotalPostsCountByForumNumber() {
log.info("THE TOTAL COUNT OF POSTS : " + postMapper.readTotalPostsCountByForumNumber(1L));
}
@Test
public void testUpdatePost() {
PostVO post = new PostVO();
post.setNumber(1L);
post.setTitle("updated title");
post.setContent("updated content");
log.info("THE NUMBER OF UPDATED POSTS : " + postMapper.updatePost(post));
}
@Test
public void testIncreasePostViewsByPostNumber() {
Long postNumber = 1L;
log.info("THE NUMBER OF POSTS WHOSE VIEWS INCREASED : " + postMapper.increasePostViewsByPostNumber(postNumber));
}
@Test
public void testDeletePostByPostNumber() {
log.info("THE NUMBER OF DELETED POSTS : " + postMapper.deletePostByPostNumber(2L));
}
}
[ PostService Interface and Impl ]
public interface PostService {
public int registerPost(PostVO post);
public PostVO getPostByPostNumber(Long postNumber);
public List<PostVO> getPostsByForumSlug(String forumSlug, SearchCriteria searchCriteria);
public long getTotalPostsCountByForumNumber(Long forumNumber);
public int modifyPost(PostVO post);
public int deletePostByPostNumber(Long postNumber);
}
@Service
@Log4j
public class PostServiceImpl implements PostService{
@Setter(onMethod_ = @Autowired)
private PostMapper postMapper;
@Setter(onMethod_ = @Autowired)
private ForumMapper ForumMapper;
@Setter(onMethod_ = @Autowired)
private UserMapper userMapper;
private static final Long SCORE = 30L;
@Override
public int registerPost(PostVO post) {
log.info("register Post.............");
int result = postMapper.insertPost(post);
if (result > 0) {
result = userMapper.increaseUserActivityScoreByUserNickname(post.getWriter(), SCORE);
}
return result;
}
@Override
public PostVO getPostByPostNumber(Long postNumber) {
log.info("get post by post number .............");
postMapper.increasePostViewsByPostNumber(postNumber);
return postMapper.readPostByPostNumber(postNumber);
}
@Override
public List<PostVO> getPostsByForumSlug(String forumSlug, SearchCriteria searchCriteria) {
log.info("get posts by forum slug..............");
Long forumNumber = ForumMapper.readForumByForumSlug(forumSlug).getNumber();
Long to = searchCriteria.getPageNumber() * searchCriteria.getContentQuantity();
Long from = to - searchCriteria.getContentQuantity() + 1;
log.info(from);
log.info(to);
return postMapper.readPostsByForumNumber(forumNumber, from, to);
}
@Override
public long getTotalPostsCountByForumNumber(Long forumNumber) {
log.info("get total posts count by forum number..................");
return postMapper.readTotalPostsCountByForumNumber(forumNumber);
}
@Override
public int modifyPost(PostVO post) {
log.info("modify post.........................");
return postMapper.updatePost(post);
}
@Override
public int deletePostByPostNumber(Long postNumber) {
log.info("delete post by post number..................");
return postMapper.deletePostByPostNumber(postNumber);
}
}
- 나중에 try catch 구문을 이용하도록 고쳐야함. transaction 기능을 이용해 원자성을 가지도록 해야함.
[ PostService Tests ]
@RunWith(SpringRunner.class)
@ContextConfiguration("file:src/main/webapp/WEB-INF/spring/root-context.xml")
@Log4j
public class PostServiceTests {
@Setter(onMethod_ = @Autowired)
private PostService postService;
@Test
public void testNotNull() {
assertNotNull(postService);
log.info(postService);
}
@Test
public void testRegisterPost() {
PostVO post = new PostVO();
post.setForumNumber(1L);
post.setTitle("new title");
post.setContent("new content");
post.setWriter("bibidi");
log.info("THE NUMBER OF REGISTERED POSTS : " + postService.registerPost(post));
}
@Test
public void testGetPostByPostNumber() {
log.info(postService.getPostByPostNumber(3L));
}
@Test
public void testGetPostsByForumSlug() {
String forumSlug = "notice";
SearchCriteria searchCriteria = new SearchCriteria(2L, 20L);
log.info("searchCriteria : " + searchCriteria);
postService
.getPostsByForumSlug(forumSlug, searchCriteria)
.forEach(post -> log.info(post));
}
@Test
public void testModifyPost() {
PostVO post = new PostVO();
post.setNumber(2L);
post.setTitle("updated title");
post.setContent("updated content");
log.info("THE NUMBER OF MODIFIED POSTS : " + postService.modifyPost(post));
}
@Test
public void testDeletePostByPostNumber() {
log.info("THE NUMBER OF DELETED POSTS : " + postService.deletePostByPostNumber(4L));
}
}
[ PostController ]
@Controller
@Log4j
@RequestMapping("/posts/*")
public class PostController {
@Setter(onMethod_ = @Autowired)
private PostService postService;
@Setter(onMethod_ = @Autowired)
private ForumService forumService;
@RequestMapping(value="/{forumSlug}", method=RequestMethod.GET)
public String getPosts(@PathVariable(name = "forumSlug") String forumSlug, SearchCriteria searchCriteria, Model model) {
log.info("PostController get posts.............");
ForumVO forum = forumService.getForumByForumSlug(forumSlug);
model.addAttribute("posts", postService.getPostsByForumSlug(forumSlug, searchCriteria));
model.addAttribute("forum", forum);
model.addAttribute("pageMaker", new Page(searchCriteria, postService.getTotalPostsCountByForumNumber(forum.getNumber())));
return "posts/postList";
}
@RequestMapping(value="/{forumSlug}/{postNumber}", method=RequestMethod.GET)
public String getPost(@PathVariable(name="forumSlug") String forumSlug, @PathVariable(name="postNumber") Long postNumber, SearchCriteria searchCriteria, Model model) {
log.info("PostController get post.............");
ForumVO forum = forumService.getForumByForumSlug(forumSlug);
model.addAttribute("selectedPost", postService.getPostByPostNumber(postNumber));
model.addAttribute("forum", forum);
model.addAttribute("posts", postService.getPostsByForumSlug(forumSlug, searchCriteria));
model.addAttribute("pageMaker", new Page(searchCriteria, postService.getTotalPostsCountByForumNumber(forum.getNumber())));
return "posts/postPage";
}
@RequestMapping(value="/{forumSlug}/{postNumber}/modification", method=RequestMethod.GET)
public String getPostModificationForm(@PathVariable("forumSlug") String forumSlug, @PathVariable("postNumber") Long postNumber, Model model) {
log.info("PostController get post modification form");
model.addAttribute("forum", forumService.getForumByForumSlug(forumSlug));
model.addAttribute("post", postService.getPostByPostNumber(postNumber));
return "posts/postModificationForm";
}
@RequestMapping(value="/{forumSlug}/registration", method=RequestMethod.GET)
public String getPostRegistrationForm(@PathVariable("forumSlug") String forumSlug, Model model) {
log.info("PostController get register form.............");
model.addAttribute("forum", forumService.getForumByForumSlug(forumSlug));
return "posts/postRegistrationForm";
}
@RequestMapping(value="/{forumSlug}", method=RequestMethod.POST)
public String postPostRegistrationForm(@PathVariable("forumSlug") String forumSlug, PostVO post, RedirectAttributes rttr) {
log.info("PostController post post register form.............");
post.setForumNumber(forumService.getForumByForumSlug(forumSlug).getNumber());
postService.registerPost(post);
rttr.addFlashAttribute("resultMessage", "새 글이 등록되었습니다.");
StringBuilder redirectUri = new StringBuilder();
redirectUri
.append("redirect:/posts/")
.append(forumSlug);
return redirectUri.toString();
}
@RequestMapping(value="/{forumSlug}/{postNumber}", method=RequestMethod.PATCH)
public String patchPost(@PathVariable("forumSlug") String forumSlug, PostVO post) {
log.info("PostController patch post.............");
postService.modifyPost(post);
StringBuilder redirectUri = new StringBuilder();
redirectUri
.append("redirect:/posts/")
.append(forumSlug)
.append("/")
.append(post.getNumber());
return redirectUri.toString();
}
@RequestMapping(value="/{forumSlug}/{postNumber}", method=RequestMethod.DELETE)
public String deletePost(@PathVariable("forumSlug") String forumSlug, @PathVariable("postNumber") Long postNumber, RedirectAttributes rttr) {
log.info("PostController delete post.............");
postService.deletePostByPostNumber(postNumber);
StringBuilder redirectUri = new StringBuilder();
redirectUri
.append("redirect:/posts/")
.append(forumSlug);
return redirectUri.toString();
}
}
- 여기도 try catch 구문을 이용해서 에러가 발생하면 어떤 처리를 하도록 해야하는데, 어떤 처리를 해야되는지 감을 못 잡았다. REST API 관련 문서들 보면 3XX, 4XX 등에 대한 처리를 하던데, 이게 이 부분인가?
[ PostController Tests ]
@RunWith(SpringRunner.class)
@WebAppConfiguration
@ContextConfiguration({
"file:src/main/webapp/WEB-INF/spring/root-context.xml",
"file:src/main/webapp/WEB-INF/spring/security-context.xml",
"file:src/main/webapp/WEB-INF/spring/appServlet/servlet-context.xml"})
@Log4j
public class PostControllerTests {
@Setter(onMethod_ = @Autowired)
private WebApplicationContext ctx;
@Setter(onMethod_ = @Autowired)
private PostService postService;
private MockMvc mockMvc;
@Before
public void setup() {
this.mockMvc = MockMvcBuilders.webAppContextSetup(ctx).build();
}
@Test
public void testGetPostsByForumSlug() throws Exception {
String forumSlug = "notice";
URI uri = new URI("/posts/" + forumSlug);
log.info(
mockMvc.perform(MockMvcRequestBuilders.request("GET", uri))
.andReturn()
.getModelAndView()
.getModelMap());
}
@Test
public void testGetPostByPostNumber() throws Exception {
String forumSlug = "notice";
Long postNumber = 1L;
URI uri = new URI("/posts/" + forumSlug + "/" + postNumber);
log.info(
mockMvc.perform(MockMvcRequestBuilders.request("GET", uri))
.andReturn()
.getModelAndView()
.getModelMap());
}
@Test
public void testGetPostRegistrationForm() throws Exception {
String forumSlug = "notice";
URI uri = new URI("/posts/" + forumSlug + "/registration");
log.info(
mockMvc.perform(MockMvcRequestBuilders.request("GET", uri))
.andReturn()
.getModelAndView()
.getViewName());
}
@Test
public void testPostPostRegistrationForm() throws Exception {
String forumSlug = "notice";
URI uri = new URI("/posts/" + forumSlug);
log.info(
mockMvc.perform(MockMvcRequestBuilders.request("POST", uri)
.param("title", "mock test title")
.param("content", "mock test content")
.param("writer", "bibidi"))
.andReturn()
.getModelAndView()
.getViewName());
}
@Test
public void testPatchPost() throws Exception {
String forumSlug = "notice";
Long postNumber = 3L;
URI uri = new URI("/posts/" + forumSlug + "/" + postNumber);
PostVO postBeforePatch = postService.getPostByPostNumber(postNumber);
log.info(
mockMvc.perform(MockMvcRequestBuilders.request("PATCH", uri)
.param("number", postNumber.toString())
.param("title", "test patch post")
.param("content", "patch post content"))
.andReturn()
.getModelAndView()
.getViewName());
PostVO postAfterPatch = postService.getPostByPostNumber(postNumber);
log.info(postBeforePatch);
log.info(postAfterPatch);
}
@Test
public void testDeletePost() throws Exception {
String forumSlug = "notice";
Long postNumber = 5L;
URI uri = new URI("/posts/" + forumSlug + "/" + postNumber);
log.info(
mockMvc.perform(MockMvcRequestBuilders.request("DELETE", uri))
.andReturn()
.getModelAndView()
.getViewName());
}
}
- 다른 테스트와 다르게 WebAppConfiguration을 해줘야 한다. 또 MockMvc는 Setter를 이용해 주입하면 오류가 난다. 지금 생각해보니Dependency Injection에 대한 개념이 부족하다. 관련 자료를 찾아서 보충해야 한다.
- 지금 다 작성하고 보니 모두 한 게시판에 대해 테스트를 한다. forumSlug를 상수로 선언하는 게 더 좋은가?
[ 화면 작성 ]
- 부트스트랩 SB Admin2을 사용해서 만들어져 있는 컴포넌트를 이용할 땐 편하다. 그런데 내가 원하는 모양으로 만들려면 또 새로운 Div을 만들고 class를 지정하고 css를 만들어야하니 이거 또 자료를 찾아서 공부해야 되나 싶다.
- 라이브러리 테이블 내에 thead, tbody 부분으로 나눠서 작성하던데 thead reference를 찾아보면 HTML5부터 지원되지 않는 속성들이 있고 하던데, 사용하는 것이 좋은지 안 좋은지 모르겠다. 의미 상으론 있는 게 나은 것 같은데.
- 화면은 9 : 3으로 나눠서 9는 본문으로 사용하고 3은 광고를 넣거나 인기 게시물을 나열하도록 만들려고 남겨놨다. 본문은 크게 게시판 정보, 게시글로 나누고 게시글이 또 게시글 리스트, 버튼, 페이지네이션으로 구분된다.
- 조회 페이지이다. 게시글 정보를 나열하고 게시글 리스트를 나열하는데, 조회 중인 게시글에 해당하는 포스트를 '현재'로 나타내도록 만들었다. pagination처럼 active 클래스를 만들어서 시각적인 효과를 주면 더 좋을 것 같다.
- 로그인 후 본인이 작성한 게시글에 들어왔을 때 보이는 화면이다. 글쓰기는 로그인을 한 사용자만 나오도록 되어있고 수정, 삭제는 본인이 작성한 게시글일 경우에만 나온다.
- 아직 수정, 삭제 기능은 구현되어 있지 않다. form의 경우 지원하는 method가 get, post밖에 없다. patch, delete 요청을 보내려면 javascript로 작업해야 한다. 먼저 댓글 부분을 처리하고 나서 작업해야겠다.
- 좋아요, 싫어요도 댓글 부분 처리하고 작업.
* Referecne
1. tcpschool.com/html-tags/thead : sdfsdf
[ JSTL Libraries ]
- Core : https://docs.oracle.com/javaee/5/jstl/1.1/docs/tlddocs/c/tld-summary.html
- Fmt : https://docs.oracle.com/javaee/5/jstl/1.1/docs/tlddocs/fmt/tld-summary.html
-------------------------------------------------------
21.06.01 update
[ Front - postService 작성 ]
console.log("Post Module .......");
const postService = (function() {
const csrfHeaderName = document.querySelector("meta[name=csrfHeaderName]").getAttribute("content");
const csrfToken = document.querySelector("meta[name=csrfTokenValue]").getAttribute("content");
const fakeForumSlug = "temp";
function addPost(post, callback, error) {
}
function getPost(postNumber, callback, error) {
}
function getPosts(param, callback, error) {
}
function updatePost(post, callback, error) {
$.ajax({
type : "PATCH",
url : '/posts/' + fakeForumSlug + '/' + post.number,
beforeSend : function(xhr) {
xhr.setRequestHeader(csrfHeaderName, csrfToken);
},
data : JSON.stringify(post),
contentType : 'application/json',
success : function(data, status, xhr) {
if (callback) {
callback(data);
}
},
error : function(xhr, status, errorThrown) {
if (error) {
error(errorThrown);
}
}
});
}
function deletePost(postNumber, callback, error) {
$.ajax({
type : 'DELETE',
url : '/posts/' + fakeForumSlug + '/' + postNumber,
beforeSend : function(xhr) {
xhr.setRequestHeader(csrfHeaderName, csrfToken);
},
success : function(data, status, xhr) {
if (callback) {
callback(data);
}
},
error : function(xhr, status, errorThrown) {
if (error) {
error(errorThrown);
}
}
});
}
return {
addPost : addPost,
getPost : getPost,
getPosts : getPosts,
updatePost : updatePost,
deletePost : deletePost
};
})();
- JSTL로도 구현할 수 있지만 페이지 조작에 한계가 있어서 자바스크립트로 처리했다. 자바스크립트에도 http 통신은 있지만 사용하는 브라우저를 고려하는 등 구현 난이도가 높아 jquery ajax를 이용했다.
[ 게시물 삭제 이벤트 등록 ]
function registerBtnEvent() {
const isLogin = userNickname == '' ? false : true;
if (isLogin) {
...
// 게시물 삭제 이벤트 등록
const postDeleteBtn = document.querySelector('.post-delete-link');
if (postDeleteBtn !== null) {
const result = location.pathname.split('/');
postDeleteBtn.addEventListener('click', function(event) {
event.preventDefault();
postService.deletePost(
postNumber,
function(msg) {
alert("msg");
location.href = '/posts/' + forumSlug;
}
);
});
}
...
}
}
[ 게시물 수정 이벤트 등록 ]
const urlTokens = location.pathname.split('/');
const forumSlug = urlTokens[2];
const postNumber = urlTokens[3];
const metaNickname = document.querySelector("meta[name='userNickname']");
const userNickname = !metaNickname ? "" : metaNickname.getAttribute("content");
registerBtnEvent();
function registerBtnEvent() {
const isLogin = userNickname == '' ? false : true;
if (isLogin) {
const submitBtn = document.querySelector('#post-submit-btn');
submitBtn.addEventListener('click', function() {
const title = document.querySelector('#post-title').value;
const content = document.querySelector('#post-content').value;
post = {
number : postNumber,
title : title,
content : content
};
postService.updatePost(
post,
function(msg) {
alert(msg);
location.href = '/posts/' + forumSlug + '/' + postNumber;
});
});
}
}
- cookie, session에 대한 개념이 부족해서 head의 meta에 userNickname을 만든 뒤 그걸 자바스크립트를 이용해 읽어와 처리하도록 작성했다. 그리고 nickname이 아니라 id를 이용해 구현하는 것이 좀 더 일반적인 구현인데, 처음 설계를 실수해서 지금 고치기엔 시간이 조금 많이 걸린다 ㅎㅎ; 처음에 이거 만들 즈음엔 프런트 쪽에서 어떻게 데이터 처리를 하는지 몰라서 사용자 관점에서 작성하자고 결정했고, 사용자가 볼 때 서로를 구별하는 기준이 userNickname이니 이걸 이용해서 데이터를 처리하자는 식으로 생각했는데, 지금 보니 좀 그렇다.
[ ]
'(구)게시판 프로젝트' 카테고리의 다른 글
댓글, 대댓글 (1) | 2021.05.11 |
---|---|
권한 관련 작업 (0) | 2021.05.05 |
(멀티)게시판 (0) | 2021.05.04 |
이름 짓는 법 총 정리 (1) | 2021.04.28 |
메인 화면 (0) | 2021.04.27 |
- Total
- Today
- Yesterday
- 백준 1106
- boj 3006
- boj 2243
- 백준 16562
- boj 10775
- 제로베이스 스쿨
- boj 2336
- 터보소트
- 제로베이스 백엔드 스쿨
- 인간 대포
- 백준 1280
- 백준 14868
- boj 1106
- boj 1280
- boj 16562
- 백준 3006
- 백준 12713
- boj 10473
- 백준 10473
- boj 12713
- 백준 9345
- 디지털 비디오 디스크
- 부트 캠프
- 사탕상자
- 백준 2243
- boj 14868
- 백준 2336
- Ugly Numbers
- boj 9345
- 백준 10775
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 |