1、敏感词过滤器

1、编写敏感词过滤器SensitiveFilter

  1. @Component
  2. public class SensitiveFilter {
  3. private static final Logger logger = LoggerFactory.getLogger(SensitiveFilter.class);
  4. // 替换符
  5. private static final String REPLACEMENT = "***";
  6. // 根节点
  7. private TrieNode rootNode = new TrieNode();
  8. @PostConstruct
  9. public void init() {
  10. try (
  11. InputStream is = this.getClass().getClassLoader().getResourceAsStream("sensitive-words.txt");
  12. BufferedReader reader = new BufferedReader(new InputStreamReader(is));
  13. ) {
  14. String keyword;
  15. while ((keyword = reader.readLine()) != null) {
  16. // 添加到前缀树
  17. this.addKeyword(keyword);
  18. }
  19. } catch (IOException e) {
  20. logger.error("加载敏感词文件失败: " + e.getMessage());
  21. }
  22. }
  23. // 将一个敏感词添加到前缀树中
  24. private void addKeyword(String keyword) {
  25. TrieNode tempNode = rootNode;
  26. for (int i = 0; i < keyword.length(); i++) {
  27. char c = keyword.charAt(i);
  28. TrieNode subNode = tempNode.getSubNode(c);
  29. if (subNode == null) {
  30. // 初始化子节点
  31. subNode = new TrieNode();
  32. tempNode.addSubNode(c, subNode);
  33. }
  34. // 指向子节点,进入下一轮循环
  35. tempNode = subNode;
  36. // 设置结束标识
  37. if (i == keyword.length() - 1) {
  38. tempNode.setKeywordEnd(true);
  39. }
  40. }
  41. }
  42. /**
  43. * 过滤敏感词
  44. *
  45. * @param text 待过滤的文本
  46. * @return 过滤后的文本
  47. */
  48. public String filter(String text) {
  49. if (StringUtils.isBlank(text)) {
  50. return null;
  51. }
  52. // 指针1
  53. TrieNode tempNode = rootNode;
  54. // 指针2
  55. int begin = 0;
  56. // 指针3
  57. int position = 0;
  58. // 结果
  59. StringBuilder sb = new StringBuilder();
  60. while (position < text.length()) {
  61. char c = text.charAt(position);
  62. // 跳过符号
  63. if (isSymbol(c)) {
  64. // 若指针1处于根节点,将此符号计入结果,让指针2向下走一步
  65. if (tempNode == rootNode) {
  66. sb.append(c);
  67. begin++;
  68. }
  69. // 无论符号在开头或中间,指针3都向下走一步
  70. position++;
  71. continue;
  72. }
  73. // 检查下级节点
  74. tempNode = tempNode.getSubNode(c);
  75. if (tempNode == null) {
  76. // 以begin开头的字符串不是敏感词
  77. sb.append(text.charAt(begin));
  78. // 进入下一个位置
  79. position = ++begin;
  80. // 重新指向根节点
  81. tempNode = rootNode;
  82. } else if (tempNode.isKeywordEnd()) {
  83. // 发现敏感词,将begin~position字符串替换掉
  84. sb.append(REPLACEMENT);
  85. // 进入下一个位置
  86. begin = ++position;
  87. // 重新指向根节点
  88. tempNode = rootNode;
  89. } else {
  90. // 检查下一个字符
  91. position++;
  92. }
  93. }
  94. // 将最后一批字符计入结果
  95. sb.append(text.substring(begin));
  96. return sb.toString();
  97. }
  98. // 判断是否为符号
  99. private boolean isSymbol(Character c) {
  100. // 0x2E80~0x9FFF 是东亚文字范围
  101. return !CharUtils.isAsciiAlphanumeric(c) && (c < 0x2E80 || c > 0x9FFF);
  102. }
  103. // 前缀树
  104. private class TrieNode {
  105. // 关键词结束标识
  106. private boolean isKeywordEnd = false;
  107. // 子节点(key是下级字符,value是下级节点)
  108. private Map<Character, TrieNode> subNodes = new HashMap<>();
  109. public boolean isKeywordEnd() {
  110. return isKeywordEnd;
  111. }
  112. public void setKeywordEnd(boolean keywordEnd) {
  113. isKeywordEnd = keywordEnd;
  114. }
  115. // 添加子节点
  116. public void addSubNode(Character c, TrieNode node) {
  117. subNodes.put(c, node);
  118. }
  119. // 获取子节点
  120. public TrieNode getSubNode(Character c) {
  121. return subNodes.get(c);
  122. }
  123. }
  124. }

2、敏感词记录表sensitive-words.txt

  1. 赌博
  2. 嫖娼
  3. 吸毒
  4. 开票

2、发布帖子

1、引入fastjson依赖

  1. <dependency>
  2. <groupId>com.alibaba</groupId>
  3. <artifactId>fastjson</artifactId>
  4. <version>1.2.79</version>
  5. </dependency>

2、编写统一返回结果处理CommunityUtil

  1. /**
  2. * 统一返回结果处理
  3. * @param code
  4. * @param msg
  5. * @param map
  6. * @return
  7. */
  8. public static String getJSONString(int code, String msg, Map<String, Object> map) {
  9. JSONObject json = new JSONObject();
  10. json.put("code", code);
  11. json.put("msg", msg);
  12. if (map != null) {
  13. for (String key : map.keySet()) {
  14. json.put(key, map.get(key));
  15. }
  16. }
  17. return json.toJSONString();
  18. }
  19. public static String getJSONString(int code, String msg) {
  20. return getJSONString(code, msg, null);
  21. }
  22. public static String getJSONString(int code) {
  23. return getJSONString(code, null, null);
  24. }
  25. }

3、DiscussPostMapper

  1. /**
  2. * 增加帖子的方法
  3. * @param discussPost
  4. * @return
  5. */
  6. int insertDiscussPost(DiscussPost discussPost);

4、Discusspost-mapper.xml

  1. <sql id="insertFields">
  2. user_id,title,content,type,status,create_time,comment_count,score
  3. </sql>
  4. <insert id="insertDiscussPost">
  5. insert into discuss_post(<include refid="insertFields"></include>)
  6. values(#{userId},#{title},#{content},#{type},#{status},#{createTime},#{commentCount},#{score})
  7. </insert>

5、DiscussService

  1. public int addDiscussPost(DiscussPost discussPost){
  2. if(null==discussPost){
  3. throw new IllegalArgumentException("参数不能为空");
  4. }
  5. //转义HTML标记
  6. discussPost.setTitle(HtmlUtils.htmlEscape(discussPost.getTitle()));
  7. discussPost.setTitle(HtmlUtils.htmlEscape(discussPost.getContent()));
  8. //过滤敏感词
  9. discussPost.setTitle(sensitiveFilter.filter(discussPost.getTitle()));
  10. discussPost.setTitle(sensitiveFilter.filter(discussPost.getContent()));
  11. return discussPostMapper.insertDiscussPost(discussPost);
  12. }

6、DiscussController

  1. @PostMapping("/discuss/add")
  2. @ResponseBody
  3. public String addDiscussPost(String title,String content){
  4. User user = hostHolder.getUser();
  5. if(null==user){
  6. return CommunityUtil.getJSONString(300,"您还没有登录哦!");
  7. }
  8. DiscussPost post = new DiscussPost();
  9. post.setUserId(user.getId());
  10. post.setTitle(title);
  11. post.setContent(content);
  12. post.setCreateTime(new Date());
  13. discussPostService.addDiscussPost(post);
  14. return CommunityUtil.getJSONString(200,"发布成功");
  15. }

7、页面处理略

3、帖子详情功能

1、DiscussPostMapper

  1. /**
  2. * 根据id查询帖子
  3. * @param id
  4. * @return
  5. */
  6. DiscussPost selectDiscussPostById(int id);

2、discuss-post.mapper

  1. <select id="selectDiscussPostById" resultType="DiscussPost">
  2. select <include refid="selectFields"></include>
  3. from discuss_post
  4. where id=#{id}
  5. </select>

3、DiscussPostService

  1. public DiscussPost selectDiscussPostById(int id){
  2. return discussPostMapper.selectDiscussPostById(id);
  3. }

4、DiscussPostController

  1. /**
  2. * 查询帖子详情接口
  3. * @param discussPostId
  4. * @param model
  5. * @return
  6. */
  7. @GetMapping("/discuss/detail/{discussPostId}")
  8. public String selectDiscussPostById(@PathVariable int discussPostId,Model model){
  9. //帖子
  10. DiscussPost post = discussPostService.selectDiscussPostById(discussPostId);
  11. model.addAttribute("post",post);
  12. //作者
  13. User user = userService.selectUserById(post.getUserId());
  14. model.addAttribute("user",user);
  15. return "/site/discuss-detail";
  16. }

5、模板处理略
index.html
discuss-detail.html

4、事务处理

• 什么是事务

  • 事务是由N步数据库操作序列组成的逻辑执行单元,这系列操作要么全执行,要么全放弃执行。

• 事务的特性(ACID)

  • 原子性(Atomicity):事务是应用中不可再分的最小执行体。
  • 一致性(Consistency):事务执行的结果,须使数据从一个一致性状态,变为另一个一致性状态。
  • 隔离性(Isolation):各个事务的执行互不干扰,任何事务的内部操作对其他的事务都是隔离的。
  • 持久性(Durability):事务一旦提交,对数据所做的任何改变都要记录到永久存储器中。

事务的隔离性
• 常见的并发异常

  • 第一类丢失更新、第二类丢失更新。
  • 脏读、不可重复读、幻读。

• 常见的隔离级别

  • Read Uncommitted:读取未提交的数据。
  • Read Committed:读取已提交的数据。
  • Repeatable Read:可重复读。
  • Serializable:串行化。

image.png
image.png
image.png
image.png
image.png
image.png
实现机制
• 悲观锁(数据库)

  • 共享锁(S锁)
    事务A对某数据加了共享锁后,其他事务只能对该数据加共享锁,但不能加排他锁。
  • 排他锁(X锁)
    事务A对某数据加了排他锁后,其他事务对该数据既不能加共享锁,也不能加排他锁。

• 乐观锁(自定义)

  • 版本号、时间戳等
    在更新数据前,检查版本号是否发生变化。若变化则取消本次更新,否则就更新数据(版本号+1)。

Spring事务管理
• 声明式事务

  • 通过XML配置,声明某方法的事务特征。
  • 通过注解,声明某方法的事务特征。

• 编程式事务

  • 通过 TransactionTemplate 管理事务,
    并通过它执行数据库的操作。

    声明式事务举例

    1. // REQUIRED: 支持当前事务(外部事务),如果不存在则创建新事务.
    2. // REQUIRES_NEW: 创建一个新事务,并且暂停当前事务(外部事务).
    3. // NESTED: 如果当前存在事务(外部事务),则嵌套在该事务中执行(独立的提交和回滚),否则就会REQUIRED一样.
    4. @Transactional(isolation = Isolation.READ_COMMITTED, propagation = Propagation.REQUIRED)
    5. public Object save1() {
    6. // 新增用户
    7. User user = new User();
    8. user.setUsername("alpha");
    9. user.setSalt(CommunityUtil.generateUUID().substring(0, 5));
    10. user.setPassword(CommunityUtil.md5("123" + user.getSalt()));
    11. user.setEmail("alpha@qq.com");
    12. user.setHeaderUrl("http://image.nowcoder.com/head/99t.png");
    13. user.setCreateTime(new Date());
    14. userMapper.insertUser(user);
    15. // 新增帖子
    16. DiscussPost post = new DiscussPost();
    17. post.setUserId(user.getId());
    18. post.setTitle("Hello");
    19. post.setContent("新人报道!");
    20. post.setCreateTime(new Date());
    21. discussPostMapper.insertDiscussPost(post);
    22. Integer.valueOf("abc");
    23. return "ok";
    24. }

    编程式事务举例

    1. public Object save2() {
    2. transactionTemplate.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED);
    3. transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
    4. return transactionTemplate.execute(new TransactionCallback<Object>() {
    5. @Override
    6. public Object doInTransaction(TransactionStatus status) {
    7. // 新增用户
    8. User user = new User();
    9. user.setUsername("beta");
    10. user.setSalt(CommunityUtil.generateUUID().substring(0, 5));
    11. user.setPassword(CommunityUtil.md5("123" + user.getSalt()));
    12. user.setEmail("beta@qq.com");
    13. user.setHeaderUrl("http://image.nowcoder.com/head/999t.png");
    14. user.setCreateTime(new Date());
    15. userMapper.insertUser(user);
    16. // 新增帖子
    17. DiscussPost post = new DiscussPost();
    18. post.setUserId(user.getId());
    19. post.setTitle("你好");
    20. post.setContent("我是新人!");
    21. post.setCreateTime(new Date());
    22. discussPostMapper.insertDiscussPost(post);
    23. Integer.valueOf("abc");
    24. return "ok";
    25. }
    26. });
    27. }

    5、显示评论功能

    1、CommentMapper

    1. @Mapper
    2. public interface CommentMapper {
    3. List<Comment> selectCommentsByEntity(int entityType, int entityId, int offset, int limit);
    4. int selectCountByEntity(int entityType, int entityId);
    5. }

    2、comment-mapper.xml ```xml

    id, user_id, entity_type, entity_id, target_id, content, status, create_time

  1. 3CommentService
  2. ```java
  3. @Service
  4. public class CommentService {
  5. @Autowired
  6. private CommentMapper commentMapper;
  7. public List<Comment> findCommentsByEntity(int entityType, int entityId, int offset, int limit) {
  8. return commentMapper.selectCommentsByEntity(entityType, entityId, offset, limit);
  9. }
  10. public int findCommentCount(int entityType, int entityId) {
  11. return commentMapper.selectCountByEntity(entityType, entityId);
  12. }
  13. }

4、CommentController

  1. /**
  2. * 查询帖子详情接口
  3. * @param discussPostId
  4. * @param model
  5. * @return
  6. */
  7. @GetMapping("/discuss/detail/{discussPostId}")
  8. public String selectDiscussPostById(@PathVariable int discussPostId,Model model,Page page){
  9. //帖子
  10. DiscussPost post = discussPostService.selectDiscussPostById(discussPostId);
  11. model.addAttribute("post",post);
  12. //作者
  13. User user = userService.selectUserById(post.getUserId());
  14. model.addAttribute("user",user);
  15. // 评论分页信息
  16. page.setLimit(5);
  17. page.setPath("/discuss/detail/" + discussPostId);
  18. page.setRows(post.getCommentCount());
  19. // 评论: 给帖子的评论
  20. // 回复: 给评论的评论
  21. // 评论列表
  22. List<Comment> commentList = commentService.findCommentsByEntity(
  23. ENTITY_TYPE_POST, post.getId(), page.getOffset(), page.getLimit());
  24. // 评论VO列表
  25. List<Map<String, Object>> commentVoList = new ArrayList<>();
  26. if (commentList != null) {
  27. for (Comment comment : commentList) {
  28. // 评论VO
  29. Map<String, Object> commentVo = new HashMap<>();
  30. // 评论
  31. commentVo.put("comment", comment);
  32. // 作者
  33. commentVo.put("user", userService.selectUserById(comment.getUserId()));
  34. // 回复列表
  35. List<Comment> replyList = commentService.findCommentsByEntity(
  36. ENTITY_TYPE_COMMENT, comment.getId(), 0, Integer.MAX_VALUE);
  37. // 回复VO列表
  38. List<Map<String, Object>> replyVoList = new ArrayList<>();
  39. if (replyList != null) {
  40. for (Comment reply : replyList) {
  41. Map<String, Object> replyVo = new HashMap<>();
  42. // 回复
  43. replyVo.put("reply", reply);
  44. // 作者
  45. replyVo.put("user", userService.selectUserById(reply.getUserId()));
  46. // 回复目标
  47. User target = reply.getTargetId() == 0 ? null : userService.selectUserById(reply.getTargetId());
  48. replyVo.put("target", target);
  49. replyVoList.add(replyVo);
  50. }
  51. }
  52. commentVo.put("replys", replyVoList);
  53. // 回复数量
  54. int replyCount = commentService.findCommentCount(ENTITY_TYPE_COMMENT, comment.getId());
  55. commentVo.put("replyCount", replyCount);
  56. commentVoList.add(commentVo);
  57. }
  58. }
  59. model.addAttribute("comments", commentVoList);
  60. return "/site/discuss-detail";
  61. }
  62. }

5、页面略
index.html
discuss-detail.html

6、添加评论功能

1、CommentMapper

  1. int insertComment(Comment comment);

2、DiscussMapper

  1. /**
  2. * 更新评论数量
  3. * @param id
  4. * @param commentCount
  5. * @return
  6. */
  7. int updateCommentCount(int id, int commentCount);

3、comment-mapper.xml

  1. <sql id="insertFields">
  2. user_id, entity_type, entity_id, target_id, content, status, create_time
  3. </sql>
  4. <insert id="insertComment" parameterType="Comment">
  5. insert into comment(<include refid="insertFields"></include>)
  6. values(#{userId},#{entityType},#{entityId},#{targetId},#{content},#{status},#{createTime})
  7. </insert>

4、discusspost-mapper.xml

  1. <update id="updateCommentCount">
  2. update discuss_post set comment_count = #{commentCount} where id = #{id}
  3. </update>

5、CommentService

  1. @Transactional(isolation = Isolation.READ_COMMITTED, propagation = Propagation.REQUIRED)
  2. public int addComment(Comment comment) {
  3. if (comment == null) {
  4. throw new IllegalArgumentException("参数不能为空!");
  5. }
  6. // 添加评论
  7. comment.setContent(HtmlUtils.htmlEscape(comment.getContent()));
  8. comment.setContent(sensitiveFilter.filter(comment.getContent()));
  9. int rows = commentMapper.insertComment(comment);
  10. // 更新帖子评论数量
  11. if (comment.getEntityType() == ENTITY_TYPE_POST) {
  12. int count = commentMapper.selectCountByEntity(comment.getEntityType(), comment.getEntityId());
  13. discussPostService.updateCommentCount(comment.getEntityId(), count);
  14. }
  15. return rows;
  16. }

6、DiscussPostService

  1. public int updateCommentCount(int id, int commentCount) {
  2. return discussPostMapper.updateCommentCount(id, commentCount);
  3. }

7、CommentController

  1. @Controller
  2. @RequestMapping("/comment")
  3. public class CommentController {
  4. @Autowired
  5. private CommentService commentService;
  6. @Autowired
  7. private HostHolder hostHolder;
  8. @RequestMapping(path = "/add/{discussPostId}", method = RequestMethod.POST)
  9. public String addComment(@PathVariable("discussPostId") int discussPostId, Comment comment) {
  10. comment.setUserId(hostHolder.getUser().getId());
  11. comment.setStatus(0);
  12. comment.setCreateTime(new Date());
  13. commentService.addComment(comment);
  14. return "redirect:/discuss/detail/" + discussPostId;
  15. }
  16. }

7、私信列表功能

1、Message

  1. @Data
  2. @AllArgsConstructor
  3. @NoArgsConstructor
  4. @ToString
  5. public class Message {
  6. private int id;
  7. private int fromId;
  8. private int toId;
  9. private String conversationId;
  10. private String content;
  11. private int status;
  12. private Date createTime;
  13. }

2、MessageMapper

  1. @Mapper
  2. public interface MessageMapper {
  3. /**
  4. * 查询当前用户的会话列表,针对每个会话只返回一条最新的私信
  5. * @param userId
  6. * @param offset
  7. * @param limit
  8. * @return
  9. */
  10. List<Message> selectConversations(int userId, int offset, int limit);
  11. /**
  12. * 查询当前用户的会话数量
  13. * @param userId
  14. * @return
  15. */
  16. int selectConversationCount(int userId);
  17. /**
  18. * 查询某个会话所包含的私信列表
  19. * @param conversationId
  20. * @param offset
  21. * @param limit
  22. * @return
  23. */
  24. List<Message> selectLetters(String conversationId, int offset, int limit);
  25. /**
  26. * 查询某个会话所包含的私信数量
  27. * @param conversationId
  28. * @return
  29. */
  30. int selectLetterCount(String conversationId);
  31. /**
  32. * 查询未读私信的数量
  33. * @param userId
  34. * @param conversationId
  35. * @return
  36. */
  37. int selectLetterUnreadCount(int userId, String conversationId);
  38. }

3、message-mapper.xml

  1. <mapper namespace="com.guang.community.dao.MessageMapper">
  2. <sql id="selectFields">
  3. id, from_id, to_id, conversation_id, content, status, create_time
  4. </sql>
  5. <select id="selectConversations" resultType="Message">
  6. select <include refid="selectFields"></include>
  7. from message
  8. where id in (
  9. select max(id) from message
  10. where status != 2
  11. and from_id != 1
  12. and (from_id = #{userId} or to_id = #{userId})
  13. group by conversation_id
  14. )
  15. order by id desc
  16. limit #{offset}, #{limit}
  17. </select>
  18. <select id="selectConversationCount" resultType="int">
  19. select count(m.maxid) from (
  20. select max(id) as maxid from message
  21. where status != 2
  22. and from_id != 1
  23. and (from_id = #{userId} or to_id = #{userId})
  24. group by conversation_id
  25. ) as m
  26. </select>
  27. <select id="selectLetters" resultType="Message">
  28. select <include refid="selectFields"></include>
  29. from message
  30. where status != 2
  31. and from_id != 1
  32. and conversation_id = #{conversationId}
  33. order by id desc
  34. limit #{offset}, #{limit}
  35. </select>
  36. <select id="selectLetterCount" resultType="int">
  37. select count(id)
  38. from message
  39. where status != 2
  40. and from_id != 1
  41. and conversation_id = #{conversationId}
  42. </select>
  43. <select id="selectLetterUnreadCount" resultType="int">
  44. select count(id)
  45. from message
  46. where status = 0
  47. and from_id != 1
  48. and to_id = #{userId}
  49. <if test="conversationId!=null">
  50. and conversation_id = #{conversationId}
  51. </if>
  52. </select>
  53. </mapper>

4、MessageService

  1. @Service
  2. public class MessageService {
  3. @Autowired
  4. private MessageMapper messageMapper;
  5. public List<Message> findConversations(int userId, int offset, int limit) {
  6. return messageMapper.selectConversations(userId, offset, limit);
  7. }
  8. public int findConversationCount(int userId) {
  9. return messageMapper.selectConversationCount(userId);
  10. }
  11. public List<Message> findLetters(String conversationId, int offset, int limit) {
  12. return messageMapper.selectLetters(conversationId, offset, limit);
  13. }
  14. public int findLetterCount(String conversationId) {
  15. return messageMapper.selectLetterCount(conversationId);
  16. }
  17. public int findLetterUnreadCount(int userId, String conversationId) {
  18. return messageMapper.selectLetterUnreadCount(userId, conversationId);
  19. }
  20. }

5、MessageController

  1. @Controller
  2. public class MessageController {
  3. @Autowired
  4. private MessageService messageService;
  5. @Autowired
  6. private HostHolder hostHolder;
  7. @Autowired
  8. private UserService userService;
  9. /**
  10. * 私信列表接口
  11. * @param model
  12. * @param page
  13. * @return
  14. */
  15. @GetMapping("/letter/list")
  16. public String getLetterList(Model model, Page page) {
  17. User user = hostHolder.getUser();
  18. // 分页信息
  19. page.setLimit(5);
  20. page.setPath("/letter/list");
  21. page.setRows(messageService.findConversationCount(user.getId()));
  22. // 会话列表
  23. List<Message> conversationList = messageService.findConversations(
  24. user.getId(), page.getOffset(), page.getLimit());
  25. List<Map<String, Object>> conversations = new ArrayList<>();
  26. if (conversationList != null) {
  27. for (Message message : conversationList) {
  28. Map<String, Object> map = new HashMap<>();
  29. map.put("conversation", message);
  30. map.put("letterCount", messageService.findLetterCount(message.getConversationId()));
  31. map.put("unreadCount", messageService.findLetterUnreadCount(user.getId(), message.getConversationId()));
  32. int targetId = user.getId() == message.getFromId() ? message.getToId() : message.getFromId();
  33. map.put("target", userService.selectUserById(targetId));
  34. conversations.add(map);
  35. }
  36. }
  37. model.addAttribute("conversations", conversations);
  38. // 查询未读消息数量
  39. int letterUnreadCount = messageService.findLetterUnreadCount(user.getId(), null);
  40. model.addAttribute("letterUnreadCount", letterUnreadCount);
  41. return "/site/letter";
  42. }
  43. /**
  44. * 私信详情接口
  45. * @param conversationId
  46. * @param page
  47. * @param model
  48. * @return
  49. */
  50. @GetMapping("/letter/detail/{conversationId}")
  51. public String getLetterDetail(@PathVariable("conversationId") String conversationId, Page page, Model model) {
  52. // 分页信息
  53. page.setLimit(5);
  54. page.setPath("/letter/detail/" + conversationId);
  55. page.setRows(messageService.findLetterCount(conversationId));
  56. // 私信列表
  57. List<Message> letterList = messageService.findLetters(conversationId, page.getOffset(), page.getLimit());
  58. List<Map<String, Object>> letters = new ArrayList<>();
  59. if (letterList != null) {
  60. for (Message message : letterList) {
  61. Map<String, Object> map = new HashMap<>();
  62. map.put("letter", message);
  63. map.put("fromUser", userService.selectUserById(message.getFromId()));
  64. letters.add(map);
  65. }
  66. }
  67. model.addAttribute("letters", letters);
  68. // 私信目标
  69. model.addAttribute("target", getLetterTarget(conversationId));
  70. return "/site/letter-detail";
  71. }
  72. private User getLetterTarget(String conversationId) {
  73. String[] ids = conversationId.split("_");
  74. int id0 = Integer.parseInt(ids[0]);
  75. int id1 = Integer.parseInt(ids[1]);
  76. if (hostHolder.getUser().getId() == id0) {
  77. return userService.selectUserById(id1);
  78. } else {
  79. return userService.selectUserById(id0);
  80. }
  81. }
  82. }

6、页面略
index.html
letter.html
letter-detail.html

8、发送私信功能

1、MessageMapper

  1. /**
  2. * 新增消息
  3. * @param message
  4. * @return
  5. */
  6. int insertMessage(Message message);
  7. /**
  8. * 修改消息的状态
  9. * @param ids
  10. * @param status
  11. * @return
  12. */
  13. int updateStatus(List<Integer> ids, int status);

2、message-mapper.xml

  1. <sql id="insertFields">
  2. from_id, to_id, conversation_id, content, status, create_time
  3. </sql>
  4. <insert id="insertMessage" parameterType="Message" keyProperty="id">
  5. insert into message(<include refid="insertFields"></include>)
  6. values(#{fromId},#{toId},#{conversationId},#{content},#{status},#{createTime})
  7. </insert>
  8. <update id="updateStatus">
  9. update message set status = #{status}
  10. where id in
  11. <foreach collection="ids" item="id" open="(" separator="," close=")">
  12. #{id}
  13. </foreach>
  14. </update>

3、UserService

  1. public User findUserByName(String username){
  2. return userMapper.selectUserByName(username);
  3. }

4、MessageService

  1. public int addMessage(Message message) {
  2. message.setContent(HtmlUtils.htmlEscape(message.getContent()));
  3. message.setContent(sensitiveFilter.filter(message.getContent()));
  4. return messageMapper.insertMessage(message);
  5. }
  6. //修改已读消息的状态
  7. public int readMessage(List<Integer> ids) {
  8. return messageMapper.updateStatus(ids, 1);
  9. }

5、MessageController

  1. //私信详情接口中添加 设置已读
  2. List<Integer> ids = getLetterIds(letterList);
  3. if (!ids.isEmpty()) {
  4. messageService.readMessage(ids);
  5. }
  6. /**
  7. * 获取未读消息的id集合
  8. * @param letterList
  9. * @return
  10. */
  11. private List<Integer> getLetterIds(List<Message> letterList) {
  12. List<Integer> ids = new ArrayList<>();
  13. if (letterList != null) {
  14. for (Message message : letterList) {
  15. if (hostHolder.getUser().getId() == message.getToId() && message.getStatus() == 0) {
  16. ids.add(message.getId());
  17. }
  18. }
  19. }
  20. return ids;
  21. }
  22. /**
  23. * 发送私信接口
  24. * @param toName
  25. * @param content
  26. * @return
  27. */
  28. @PostMapping("/letter/send")
  29. @ResponseBody
  30. public String sendLetter(String toName, String content) {
  31. User target = userService.findUserByName(toName);
  32. if (target == null) {
  33. return CommunityUtil.getJSONString(1, "目标用户不存在!");
  34. }
  35. Message message = new Message();
  36. message.setFromId(hostHolder.getUser().getId());
  37. message.setToId(target.getId());
  38. if (message.getFromId() < message.getToId()) {
  39. message.setConversationId(message.getFromId() + "_" + message.getToId());
  40. } else {
  41. message.setConversationId(message.getToId() + "_" + message.getFromId());
  42. }
  43. message.setContent(content);
  44. message.setCreateTime(new Date());
  45. messageService.addMessage(message);
  46. return CommunityUtil.getJSONString(0);
  47. }

6、页面(略)
letter.js
letter.html
letter-detail.html

9、统一异常处理

@ControllerAdvice

  • 用于修饰类,表示该类是Controller的全局配置类。
  • 在此类中,可以对Controller进行如下三种全局配置:

异常处理方案、绑定数据方案、绑定参数方案。
@ExceptionHandler - 用于修饰方法,该方法会在Controller出现异常后被调用,用于处理捕获到的异常。
@ModelAttribute
用于修饰方法,该方法会在Controller方法执行前被调用,用于为Model对象绑定参数。
@DataBinder - 用于修饰方法,该方法会在Controller方法执行前被调用,用于绑定参数的转换器。
1、在template下error目录4xx.html与5xx.html
2、DiscussPostController

  1. /**
  2. * 500错误页面
  3. * @return
  4. */
  5. @GetMapping("/error")
  6. public String getErrorPage() {
  7. return "/error/500";
  8. }

3、ExceptionAdvice

  1. @ControllerAdvice(annotations = Controller.class)
  2. public class ExceptionAdvice {
  3. private static final Logger logger = LoggerFactory.getLogger(ExceptionAdvice.class);
  4. @ExceptionHandler({Exception.class})
  5. public void handleException(Exception e, HttpServletRequest request, HttpServletResponse response) throws IOException {
  6. logger.error("服务器发生异常: " + e.getMessage());
  7. for (StackTraceElement element : e.getStackTrace()) {
  8. logger.error(element.toString());
  9. }
  10. String xRequestedWith = request.getHeader("x-requested-with");
  11. if ("XMLHttpRequest".equals(xRequestedWith)) {
  12. response.setContentType("application/plain;charset=utf-8");
  13. PrintWriter writer = response.getWriter();
  14. writer.write(CommunityUtil.getJSONString(1, "服务器异常!"));
  15. } else {
  16. response.sendRedirect(request.getContextPath() + "/error");
  17. }
  18. }
  19. }

10、统一记录日志

AOP的实现
AspectJ
AspectJ是语言级的实现,它扩展了Java语言,定义了AOP语法。

  • AspectJ在编译期织入代码,它有一个专门的编译器,用来生成遵守Java字节码规范的class文件。

Spring AOP

  • Spring AOP使用纯Java实现,它不需要专门的编译过程,也不需要特殊的类装载器。
  • Spring AOP在运行时通过代理的方式织入代码,只支持方法类型的连接点。
  • Spring支持对AspectJ的集成。

    1. @Component
    2. @Aspect
    3. public class AlphaAspect {
    4. @Pointcut("execution(* com.nowcoder.community.service.*.*(..))")
    5. public void pointcut() {
    6. }
    7. @Before("pointcut()")
    8. public void before() {
    9. System.out.println("before");
    10. }
    11. @After("pointcut()")
    12. public void after() {
    13. System.out.println("after");
    14. }
    15. @AfterReturning("pointcut()")
    16. public void afterRetuning() {
    17. System.out.println("afterRetuning");
    18. }
    19. @AfterThrowing("pointcut()")
    20. public void afterThrowing() {
    21. System.out.println("afterThrowing");
    22. }
    23. @Around("pointcut()")
    24. public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
    25. System.out.println("around before");
    26. Object obj = joinPoint.proceed();
    27. System.out.println("around after");
    28. return obj;
    29. }
    30. }

    1、ServiceLogAspect ```java @Component @Aspect public class ServiceLogAspect {

    private static final Logger logger = LoggerFactory.getLogger(ServiceLogAspect.class);

    @Pointcut(“execution( com.guang.community.service..*(..))”) public void pointcut() {

    }

    @Before(“pointcut()”) public void before(JoinPoint joinPoint) {

    1. // 用户[1.2.3.4],在[xxx],访问了[com.guang.community.service.xxx()].
    2. ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
    3. HttpServletRequest request = attributes.getRequest();
    4. String ip = request.getRemoteHost();
    5. String now = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
    6. String target = joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName();
    7. logger.info(String.format("用户[%s],在[%s],访问了[%s].", ip, now, target));

    }

}

```