用Spring Boot + MongoDB,做一个作品评论的功能,用户可以发表评论;其他用户可以对作品现有的评论进行回复;最顶级的评论者,可以对回复评论的人再回复评论。其中,评论的数据结构如下:
t_resource_comment { _id:, resource_id: 123, comment_user_id: "A", comment_user_name:, comment_content: "A的评论", ctime:, status:, comment_responses: [ { response_user_id: "B", response_user_name:, response_content:[ {ctime:, content:"B给A的第一个评论"}, {ctime:, content:"B给A的第二个评论"}, {ctime:, content:"B给A的第三个评论"} ] get_reply:[ {ctime:, content:"这是A给B的某一个评论的回复如果有就对应插入index对应的元素没有就是空串"}, {ctime:, content:"A没有回复B这一条就是空串"}, {ctime:, content:"A个神经病跳着回复了这一条评论,这数组的第三个元素就是A回复的内容"} ] }, { response_user_id: "C", response_user_name:, response_content:[ {ctime:, content:"C给A的第一个评论"}, {ctime:, content:"C给A的第二个评论"}, {ctime:, content:"C给A的第三个评论"} ] get_reply:[ {ctime:, content:"这是A给C的某一个评论的回复如果有就对应插入index对应的元素没有就是空串"}, {ctime:, content:"A没有回复C这一条就是空串"}, {ctime:, content:"A个神经病跳着回复了这一条评论,这数组的第三个元素就是A回复的内容"} ] }, ... ] }
比如,A对文章或作品123发表了一个评论;B看到发表的评论后,给A的评论回复了一个评论;A看到B的回复后,又对B的回复评论做了一个回复。一共三级。
创建一个Spring Boot项目,pom.xml如下:
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.fhbean.springboot</groupId> <artifactId>springboot-mongodb</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>springboot-mongodb</name> <description>Demo project for Spring Boot</description> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.1.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-mongodb</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
评论相关的实体类:
package com.fhbean.springboot.mongodb.entity; import java.util.Date; import org.springframework.data.annotation.Id; import org.springframework.data.mongodb.core.index.Indexed; import org.springframework.data.mongodb.core.mapping.Document; import org.springframework.data.mongodb.core.mapping.Field; @Document(collection = "resource_comment") public class ResourceComment { @Id private String id; /** * 接收评论的资源ID或作品ID */ @Indexed @Field(value = "resource_id") private Long resourceId; /** * 发表评论的用户ID */ @Indexed @Field(value = "comment_user_id") private Long commentUserId; /** * 发表评论的用户名 */ @Field(value = "comment_user_name") private String commentUserName; /** * 发表的评论内容 */ @Field(value = "comment_content") private String commentContent; /** * 发表评论的时间 */ private Date ctime; /** * 此条评论的状态:0为正常.... */ private String status; /** * 对此评论的回复 */ @Field(value = "comment_responses") private CommentResponse[] commentResponses; public String getId() { return id; } public void setId(String id) { this.id = id; } public Long getResourceId() { return resourceId; } public void setResourceId(Long resourceId) { this.resourceId = resourceId; } public Long getCommentUserId() { return commentUserId; } public void setCommentUserId(Long commentUserId) { this.commentUserId = commentUserId; } public String getCommentUserName() { return commentUserName; } public void setCommentUserName(String commentUserName) { this.commentUserName = commentUserName; } public String getCommentContent() { return commentContent; } public void setCommentContent(String commentContent) { this.commentContent = commentContent; } public Date getCtime() { return ctime; } public void setCtime(Date ctime) { this.ctime = ctime; } public String getStatus() { return status; } public void setStatus(String status) { this.status = status; } public CommentResponse[] getCommentResponses() { return commentResponses; } public void setCommentResponses(CommentResponse[] commentResponses) { this.commentResponses = commentResponses; } }
package com.fhbean.springboot.mongodb.entity; import org.springframework.data.mongodb.core.mapping.Field; public class CommentResponse { /** * 回复评论的用户ID */ @Field(value = "response_user_id") private Long responseUserId; /** * 回复评论的用户名 */ @Field(value = "response_user_name") private String responseUserName; /** * 回复的评论内容 */ @Field(value = "response_contents") private ResponseContent[] responseContents; /** * 一楼评论作者 对 回复评论者 的回复 */ @Field(value = "get_replys") private ResponseContent[] getReplys; public Long getResponseUserId() { return responseUserId; } public void setResponseUserId(Long responseUserId) { this.responseUserId = responseUserId; } public String getResponseUserName() { return responseUserName; } public void setResponseUserName(String responseUserName) { this.responseUserName = responseUserName; } public ResponseContent[] getResponseContents() { return responseContents; } public void setResponseContents(ResponseContent[] responseContents) { this.responseContents = responseContents; } public ResponseContent[] getGetReplys() { return getReplys; } public void setGetReplys(ResponseContent[] getReplys) { this.getReplys = getReplys; } }
package com.fhbean.springboot.mongodb.entity; import java.util.Date; public class ResponseContent { private Date ctime; private String content; public ResponseContent() { super(); } public ResponseContent(Date ctime, String content) { super(); this.ctime = ctime; this.content = content; } public Date getCtime() { return ctime; } public void setCtime(Date ctime) { this.ctime = ctime; } public String getContent() { return content; } public void setContent(String content) { this.content = content; } }
创建Repository接口
package com.fhbean.springboot.mongodb.reposistory; import org.springframework.data.mongodb.repository.MongoRepository; import com.fhbean.springboot.mongodb.entity.ResourceComment; public interface ResourceCommentRepository extends MongoRepository<ResourceComment, String> { }
创建Service
package com.fhbean.springboot.mongodb.service; import java.util.List; import com.fhbean.springboot.mongodb.entity.ResourceComment; public interface ResourceCommentService { /** * 查询某资源的所有评论 * * @param resourceId * @return */ public List<ResourceComment> findResourceComment(Long resourceId); /** * 对指定资源发表评论 * * @param resourceId 资源ID * @param commentUserId 用户ID * @param commentUserName 用户名 * @param commentContent 评论内容 * @return 1成功,0失败 */ public int saveComment(Long resourceId, Long commentUserId, String commentUserName, String commentContent); /** * 回复评论 * * @param id 原评论的ID * @param responseUserId 回复评论的用户ID * @param responseUserName 回复评论的用户名 * @param responseContent 回复的内容 * @return 1成功,0失败 */ public int saveResponse(String id, Long responseUserId, String responseUserName, String responseContent); /** * 一楼评论者 回复二楼评论者 的评论 * * @param id 原评论的ID * @param responseUserId 回复评论的用户ID * @param index 要回复的评论索引值,从0开始 * @param replyContent 一楼评论者 回复的内容 * @return 1成功,0失败 */ public int saveReply(String id, Long responseUserId, int index, String replyContent); }
package com.fhbean.springboot.mongodb.service.impl; import java.util.Date; import java.util.List; import java.util.Optional; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Example; import org.springframework.stereotype.Service; import org.springframework.util.StringUtils; import com.fhbean.springboot.mongodb.entity.CommentResponse; import com.fhbean.springboot.mongodb.entity.ResourceComment; import com.fhbean.springboot.mongodb.entity.ResponseContent; import com.fhbean.springboot.mongodb.reposistory.ResourceCommentRepository; import com.fhbean.springboot.mongodb.service.ResourceCommentService; @Service(value = "resourceCommentService") public class ResourceCommentServiceImpl implements ResourceCommentService { @Autowired private ResourceCommentRepository resourceCommentRepository; /** * 查询某个资源的所有评论 */ @Override public List<ResourceComment> findResourceComment(Long resourceId) { if (null == resourceId) { return null; } ResourceComment rc = new ResourceComment(); rc.setResourceId(resourceId); return resourceCommentRepository.findAll(Example.of(rc)); } /** * 发表评论 */ @Override public int saveComment(Long resourceId, Long commentUserId, String commentUserName, String commentContent) { //数据合法性校验 begin if (null == resourceId || null == commentUserId) { return 0; } if (StringUtils.isEmpty(commentUserName) || StringUtils.isEmpty(commentContent)) { return 0; } //数据合法性校验 end //保存评论 begin ResourceComment rc = new ResourceComment(); rc.setResourceId(resourceId); rc.setCommentUserId(commentUserId); rc.setCommentUserName(commentUserName); rc.setCommentContent(commentContent); rc.setCtime(new Date()); rc.setStatus("0"); //此处可采用常量或枚举类型 resourceCommentRepository.save(rc); //保存评论 end return 0; } /** * 回复评论 */ @Override public int saveResponse(String id, Long responseUserId, String responseUserName, String responseContent) { //数据合法性校验 begin if (StringUtils.isEmpty(id)) { return 0; } if (null == responseUserId) { return 0; } if (StringUtils.isEmpty(responseUserName) || StringUtils.isEmpty(responseContent)) { return 0; } //数据合法性校验 end //查找指定评论 begin ResourceComment rc = new ResourceComment(); rc.setId(id); Optional<ResourceComment> optional = resourceCommentRepository.findOne(Example.of(rc)); if (!optional.isPresent()) { //未找到指定的评论 return 0; } rc = optional.get(); //查找指定评论 end //得到巳有回复 CommentResponse[] crs = rc.getCommentResponses(); if (null == crs) { //此评论之前没有任何人回复,当前回复作为第一条回复 CommentResponse cr = getNewResponse(responseUserId, responseUserName, responseContent); rc.setCommentResponses(new CommentResponse[]{cr}); resourceCommentRepository.save(rc); return 1; } //己有人回复过评论,判断responseUserId是否回复过评论 begin //responseUserId之前是否回复过此评论 begin boolean responsed = false; for (int i = 0; i < crs.length; i++) { if (crs[i].getResponseUserId().equals(responseUserId)) { responsed = true; //之前回复过 ResponseContent[] rcsNew = getNewResponseContent(responseContent, crs, crs[i].getResponseContents()); crs[i].setResponseContents(rcsNew); break; //已经回复,不再继续循环 } } //己有人回复过评论,判断responseUserId是否回复过评论 end if (responsed) { //之前,responseUserId已经回复过此评论,在现有回复后追加一个回复即可 resourceCommentRepository.save(rc); return 1; } //之前别人回复过此评论,但responseUserId没有回复过此评论 begin //将之前所有人的回复转储到新的数组 CommentResponse[] crsNew = new CommentResponse[crs.length + 1]; CommentResponse cr = getNewResponse(responseUserId, responseUserName, responseContent); for (int i = 0; i < crs.length; i++) { crsNew[i] = crs[i]; } crsNew[crsNew.length - 1] = cr; //之前别人回复过此评论,但responseUserId没有回复过此评论 end rc.setCommentResponses(crsNew); resourceCommentRepository.save(rc); return 1; } /** * 回复评论的评论 */ @Override public int saveReply(String id, Long responseUserId, int index, String replyContent) { //数据合法性校验 begin if (StringUtils.isEmpty(id)) { return 0; } if (null == responseUserId) { return 0; } if (index < 0) { //索引值不能小于零 return 0; } if (StringUtils.isEmpty(replyContent)) { return 0; } //数据合法性校验 end //查找指定评论 begin ResourceComment rc = new ResourceComment(); rc.setId(id); Optional<ResourceComment> optional = resourceCommentRepository.findOne(Example.of(rc)); if (!optional.isPresent()) { //未找到指定的评论 return 0; } rc = optional.get(); //查找指定评论 end CommentResponse[] crs = rc.getCommentResponses(); if (null == crs || crs.length < 1) { //没有人回复过评论 return 0; } for (int i = 0; i < crs.length; i++) { if (responseUserId.equals(crs[i].getResponseUserId())) { //找到responseUserId回复的评论 ResponseContent[] rcs = crs[i].getResponseContents();//回复的评论数组 if (null == rcs || index >= rcs.length) { //responseUserId没有对评论回复任何内容或index越界,指定的回复不存在,不再继续 return 0; } ResponseContent[] grs = crs[i].getGetReplys(); if (null == grs) { //之前没有回复过评论的评论 grs = new ResponseContent[index+1]; ResponseContent reply = new ResponseContent(new Date(), replyContent); grs[index] = reply; crs[i].setGetReplys(grs); } else { //之前回复过评论的评论 if (index >= grs.length) { //索引值超过现有回复数组的长度,给数组增长 ResponseContent[] grsNew = new ResponseContent[index + 1]; ResponseContent reply = new ResponseContent(new Date(), replyContent); grsNew[index] = reply; crs[i].setGetReplys(grsNew); } else { //索引值未超过回复数组的长度,reply内容直接覆盖索引值的reply ResponseContent reply = new ResponseContent(new Date(), replyContent); grs[index] = reply; crs[i].setGetReplys(grs); } } resourceCommentRepository.save(rc); return 1; } } return 0; } private ResponseContent[] getNewResponseContent(String responseContent, CommentResponse[] crs, ResponseContent[] rcs) { ResponseContent[] rcsNew = null; int rcsSize = 0; if (null == rcs) { rcsSize++; rcsNew = new ResponseContent[rcsSize]; //防止程序异常,没有回复 } else { rcsSize = rcs.length + 1; rcsNew = new ResponseContent[rcsSize]; for (int i = 0; i < rcs.length; i++) { //原先的回复转储到新的数组 rcsNew[i] = rcs[i]; } } ResponseContent rcon = new ResponseContent(); rcon.setCtime(new Date()); rcon.setContent(responseContent); rcsNew[rcsSize - 1] = rcon; return rcsNew; } private CommentResponse getNewResponse(Long responseUserId, String responseUserName, String responseContent) { CommentResponse cr = new CommentResponse(); cr.setResponseUserId(responseUserId); cr.setResponseUserName(responseUserName); ResponseContent rct = new ResponseContent(); rct.setCtime(new Date()); rct.setContent(responseContent); cr.setResponseContents(new ResponseContent[] {rct}); return cr; } }
创建Controller
package com.fhbean.springboot.mongodb.controller; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.RestController; import com.fhbean.springboot.mongodb.service.ResourceCommentService; @RestController @RequestMapping(value = "/rc") public class ResourceCommentController { @Autowired private ResourceCommentService resourceCommentService; /** * 列出某个资源的所有评论 * * 客户端页面要做到这些权限限制 * 三级按钮:发表评论btn1,回复评论btn2,回复评论的评论btn3 * 当前登录用户是本条评论的第一级评论者, * 可以看到发表评论按钮btn1 * 不可以看到第二级的回复按钮btn2,即不能给自己的评论回复 * 可以看到第三级的按钮btn3,这个按钮可以给回复评论的人回复评论 * * 当前登录用户是本条评论的第二级评论者,即回复评论者 * 可以看到发表评论按钮btn1 * 可以看到第二级的回复按钮btn2,即可以给他人的评论回复 * 不可以看到第三级的按钮btn3,即不可以给自己的回复发表回复评论 * * 当前登录用户不是本条评论的第一级和第二级用户 * 可以看到发表评论按钮btn1 * 可以看到第二级的回复按钮btn2,即可以给他人的评论回复 * 不可以看到第三级的按钮btn3,只有版主才可以回复评论的评论 * * @param resourceId * @return * @throws Exception * * example http://localhost:8080/rc/listComments/1 */ @ResponseBody @RequestMapping(value = "/listComments/{resourceId}") public Object listComments(@PathVariable(value = "resourceId", required = true)Long resourceId) throws Exception { return resourceCommentService.findResourceComment(resourceId); } /** * 发表评论 * @param resourceId * @param commentUserId * @param commentUserName * @param commentContent * @return * @throws Exception */ @ResponseBody @RequestMapping(value = "/saveComment") public Object saveComment( @RequestParam(value = "resourceId", required = true)Long resourceId, @RequestParam(value = "commentUserId", required = true)Long commentUserId, @RequestParam(value = "commentUserName", required = true)String commentUserName, @RequestParam(value = "commentContent", required = true)String commentContent) throws Exception { return resourceCommentService.saveComment(resourceId, commentUserId, commentUserName, commentContent); } /** * 回复评论 * @param id * @param responseUserId * @param responseUserName * @param responseContent * @return * @throws Exception */ @ResponseBody @RequestMapping(value = "/saveResponse") public Object saveResponse( @RequestParam(value = "id", required = true)String id, @RequestParam(value = "responseUserId", required = true)Long responseUserId, @RequestParam(value = "responseUserName", required = true)String responseUserName, @RequestParam(value = "responseContent", required = true)String responseContent) throws Exception { return resourceCommentService.saveResponse(id, responseUserId, responseUserName, responseContent); } /** * 回复评论的评论 * * @param id * @param responseUserId * @param index * @param replyContent * @return * @throws Exception */ @ResponseBody @RequestMapping(value = "/saveReply") public Object saveReply( @RequestParam(value = "id", required = true)String id, @RequestParam(value = "responseUserId", required = true)Long responseUserId, @RequestParam(value = "index", required = true)Integer index, @RequestParam(value = "replyContent", required = true)String replyContent) throws Exception { if (null == index) { index = 0; } return resourceCommentService.saveReply(id, responseUserId, index, replyContent); } }
配置application.yml
spring: data: mongodb: host: 127.0.0.1 port: 27017 database: foobar
编写testcase
package com.fhbean.springboot.mongodb; import java.util.Optional; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.data.domain.Example; import org.springframework.test.context.junit4.SpringRunner; import com.fhbean.springboot.mongodb.entity.ResourceComment; import com.fhbean.springboot.mongodb.reposistory.ResourceCommentRepository; import com.fhbean.springboot.mongodb.service.ResourceCommentService; @RunWith(SpringRunner.class) @SpringBootTest public class ResourceCommentTests { @Autowired private ResourceCommentRepository resourceCommentRepository; @Autowired private ResourceCommentService resourceCommentService; @Before public void setUp() { } /** * 测试评论 */ @Test public void testSaveComment() { Long resourceId = 1L; Long commentUserId = 1L; String commentUserName = "user" + commentUserId; String commentContent = commentUserName + "'s comment on " + System.currentTimeMillis(); resourceCommentService.saveComment(resourceId, commentUserId, commentUserName, commentContent); } /** * 测试评论回复 */ @Test public void testSaveResponse() { String id = "5acdae25c986d7518c1a7081"; Long responseUserId = 2L; String responseUserName = "user" + responseUserId; String responseContent = responseUserName + "'s response on " + System.currentTimeMillis(); resourceCommentService.saveResponse(id, responseUserId, responseUserName, responseContent); } /** * 测试回复评论的评论 */ @Test public void testSaveReply() { String id = "5acdae25c986d7518c1a7081"; Long responseUserId = 2L; int index = 1; String replyContent = "user"+responseUserId+" reply index["+index+"] on " + System.currentTimeMillis(); resourceCommentService.saveReply(id, responseUserId, index, replyContent); } @Test public void testFind() { ResourceComment rc = new ResourceComment(); rc.setResourceId(1L); rc.setCommentUserId(1L); Example<ResourceComment> example = Example.of(rc); Optional<ResourceComment> optional = resourceCommentRepository.findOne(example); Assert.assertTrue(optional.isPresent()); Assert.assertEquals("user1", optional.get().getCommentUserName()); } }
未编写页面ajax脚本
项目结构:
依次跑一下ResourceCommentTests的三个方法:testSaveComment()、testSaveResponse()、testSaveReply()
然后启动Spring Boot项目,在浏览器地址栏录入http://localhost:8080/rc/listComments/1
项目在github上的源码 https://github.com/cjrjc/springboot-mongodb.git
参考 https://segmentfault.com/a/1190000010391079