论坛项目进展03

第3章 Spring Boot进阶,开发社区核心功能
3.1过滤敏感词

image-20220614132235370

image-20220614133045135

首先在resources目录下建一个文件sensitive-words.txt用来记录若干敏感词。然后在util包下建一个SensitiveFilter类

@Component
public class SensitiveFilter {
   private static final Logger logger = LoggerFactory.getLogger(SensitiveFilter.class);
   // 替换符
   private static final String REPLACEMENT = "***";
   // 根节点
   private TrieNode rootNode = new TrieNode();
   @PostConstruct
   public void init() {//构造器调用后执行该方法,根据敏感词初始化前缀树
       try (
               InputStream is = this.getClass().getClassLoader().getResourceAsStream("sensitive-words.txt");
               BufferedReader reader = new BufferedReader(new InputStreamReader(is));
      ) {
           String keyword;
           while ((keyword = reader.readLine()) != null) {
               // 添加到前缀树
               this.addKeyword(keyword);
          }
      } catch (IOException e) {
           logger.error("加载敏感词文件失败: " + e.getMessage());
      }
  }
   // 将一个敏感词添加到前缀树中
   private void addKeyword(String keyword) {
       TrieNode tempNode = rootNode;
       for (int i = 0; i < keyword.length(); i++) {
           char c = keyword.charAt(i);
           TrieNode subNode = tempNode.getSubNode(c);
           if (subNode == null) {
               // 初始化子节点
               subNode = new TrieNode();
               tempNode.addSubNode(c, subNode);
          }
           // 指向子节点,进入下一轮循环
           tempNode = subNode;
           // 设置结束标识
           if (i == keyword.length() - 1) {
               tempNode.setKeywordEnd(true);
          }
      }
  }
   /**
    * 过滤敏感词
    * @param text 待过滤的文本
    * @return 过滤后的文本
    */
   public String filter(String text) {
       if (StringUtils.isBlank(text)) {
           return null;
      }
       // 指针1
       TrieNode tempNode = rootNode;
       // 指针2
       int begin = 0;
       // 指针3
       int position = 0;
       // 结果
       StringBuilder sb = new StringBuilder();
       while (position < text.length()) {
           char c = text.charAt(position);

           // 跳过符号
           if (isSymbol(c)) {
               // 若指针1处于根节点,将此符号计入结果,让指针2向下走一步
               if (tempNode == rootNode) {
                   sb.append(c);
                   begin++;
              }
               // 无论符号在开头或中间,指针3都向下走一步
               position++;
               continue;
          }
           // 检查下级节点
           tempNode = tempNode.getSubNode(c);
           if (tempNode == null) {
               // 以begin开头的字符串不是敏感词
               sb.append(text.charAt(begin));
               // 进入下一个位置
               position = ++begin;
               // 重新指向根节点
               tempNode = rootNode;
          } else if (tempNode.isKeywordEnd()) {
               // 发现敏感词,将begin~position字符串替换掉
               sb.append(REPLACEMENT);
               // 进入下一个位置
               begin = ++position;
               // 重新指向根节点
               tempNode = rootNode;
          } else {
               // 检查下一个字符
               position++;
          }
      }
       // 将最后一批字符计入结果
       sb.append(text.substring(begin));

       return sb.toString();
  }
   // 判断是否为符号
   private boolean isSymbol(Character c) {
       // 0x2E80~0x9FFF 是东亚文字范围
       return !CharUtils.isAsciiAlphanumeric(c) && (c < 0x2E80 || c > 0x9FFF);
  }
   // 前缀树
   private class TrieNode {
       // 关键词结束标识
       private boolean isKeywordEnd = false;
       // 子节点(key是下级节点的字符,value是下级节点)
       private Map<Character, TrieNode> subNodes = new HashMap<>();
       public boolean isKeywordEnd() {
           return isKeywordEnd;
      }
       public void setKeywordEnd(boolean keywordEnd) {
           isKeywordEnd = keywordEnd;
      }
       // 添加子节点
       public void addSubNode(Character c, TrieNode node) {
           subNodes.put(c, node);
      }
       // 获取子节点
       public TrieNode getSubNode(Character c) {
           return subNodes.get(c);
      }
  }
}
3.2发布帖子

image-20220614151718459

在CommunityUtil里增加几个方法:

//调用者把code,msg,map给这个方法,服务器根据这些生成json并返回给客户端
public static String getJSONString(int code, String msg, Map<String, Object> map) {
       JSONObject json = new JSONObject();
       json.put("code", code);
       json.put("msg", msg);
       if (map != null) {
           for (String key : map.keySet()) {
               json.put(key, map.get(key));
          }
      }
       return json.toJSONString();
  }
   public static String getJSONString(int code, String msg) {
       return getJSONString(code, msg, null);
  }
   public static String getJSONString(int code) {
       return getJSONString(code, null, null);
  }

在DiscussPostService增加方法addDiscussPost用来插入帖子

 public int addDiscussPost(DiscussPost post) {
       if (post == null) {
           throw new IllegalArgumentException("参数不能为空!");
      }
       // 转义HTML标记,防止帖子内容里有html标签,对系统有影响
       post.setTitle(HtmlUtils.htmlEscape(post.getTitle()));
       post.setContent(HtmlUtils.htmlEscape(post.getContent()));
       // 过滤敏感词
       post.setTitle(sensitiveFilter.filter(post.getTitle()));
       post.setContent(sensitiveFilter.filter(post.getContent()));
       return discussPostMapper.insertDiscussPost(post);
  }

新建一个DiscussPostController

@Controller
@RequestMapping("/discuss")
public class DiscussPostController implements CommunityConstant {
   @Autowired
   private DiscussPostService discussPostService;
   @Autowired
   private HostHolder hostHolder;
   @Autowired
   private RedisTemplate redisTemplate;
   @RequestMapping(path = "/add", method = RequestMethod.POST)
   @ResponseBody
   public String addDiscussPost(String title, String content) {
       User user = hostHolder.getUser();
       if (user == null) {
           return CommunityUtil.getJSONString(403, "你还没有登录哦!");
      }
       DiscussPost post = new DiscussPost();
       post.setUserId(user.getId());
       post.setTitle(title);
       post.setContent(content);
       post.setCreateTime(new Date());
       discussPostService.addDiscussPost(post);
       // 报错的情况,将来统一处理.
       return CommunityUtil.getJSONString(0, "发布成功!");
  }
}

index.js,发帖时异步刷新,采用ajax请求,实现发布帖子,点发布按钮调用下面js函数

$(function(){
$("#publishBtn").click(publish);
});
function publish() {
$("#publishModal").modal("hide");
// 获取标题和内容
var title = $("#recipient-name").val();
var content = $("#message-text").val();
// 发送异步请求(POST)
$.post(
   CONTEXT_PATH + "/discuss/add",
  {"title":title,"content":content},
   function(data) {
       data = $.parseJSON(data);
       // 在提示框中显示返回消息
       $("#hintBody").text(data.msg);
       // 显示提示框
           $("#hintModal").modal("show");
           // 2秒后,自动隐藏提示框
           setTimeout(function(){
               $("#hintModal").modal("hide");
               // 刷新页面
               if(data.code == 0) {
                   window.location.reload();
              }
          }, 2000);
  }
);
}
3.3帖子详情

image-20220615141416800

在DiscussPostController里增加getDiscussPost方法,DiscussPostMapper和DiscussPostService增加的方法略。

@RequestMapping(path = "/detail/{discussPostId}", method = RequestMethod.GET)
   public String getDiscussPost(@PathVariable("discussPostId") int discussPostId, Model model, Page page) {
       // 帖子
       DiscussPost post = discussPostService.findDiscussPostById(discussPostId);
       model.addAttribute("post", post);
       // 作者
       User user = userService.findUserById(post.getUserId());
       //帖子获赞,帖子评论等以后再补充
       model.addAttribute("user", user);
       return "/site/discuss-detail";
  }
3.4事务管理

image-20220615153956137

image-20220615154040672

image-20220615155051541

image-20220615155246611

 

image-20220615231333107

spring对任何数据库做事务管理时都是同一套api,

3.5显示评论

image-20220615232501042

CREATE TABLE `comment` (//评论表
 `id` int NOT NULL AUTO_INCREMENT,//主键
 `user_id` int DEFAULT NULL,//发布评论的用户id
 `entity_type` int DEFAULT NULL,//评论的目标类型,1代表帖子 2代表评论,3代表用户等等
 `entity_id` int DEFAULT NULL,//评论的目标的id
 `target_id` int DEFAULT NULL,//若评论有指向性,比如针对某个用户进行评论,则针target_id记录所针对的人
 `content` text,//评论内容
 `status` int DEFAULT NULL,//状态 若是1代表不可用,被禁了
 `create_time` timestamp NULL DEFAULT NULL,//
 PRIMARY KEY (`id`),
 KEY `index_user_id` (`user_id`) /*!80000 INVISIBLE */,
 KEY `index_entity_id` (`entity_id`)
) ENGINE=InnoDB AUTO_INCREMENT=242 DEFAULT CHARSET=utf8mb3;

在entity包下建一实体类Comment。dao下建一CommentMapper。在mapper文件下建xml文件把CommentMapper具体实现。新建CommentService同样实现下面几个方法。

@Mapper
public interface CommentMapper {
   List<Comment> selectCommentsByEntity(int entityType, int entityId, int offset, int limit);//根据实体来查询,是帖子评论,还是评论的评论
   int selectCountByEntity(int entityType, int entityId);//查询评论的条目数
   int insertComment(Comment comment);
   Comment selectCommentById(int id);
}

在DiscussPostController的getDiscussPost补方法继续补充,之前只显示了帖子和作者,没有显示评论等信息:

@RequestMapping(path = "/detail/{discussPostId}", method = RequestMethod.GET)
   public String getDiscussPost(@PathVariable("discussPostId") int discussPostId, Model model, Page page) {//Page参数为了分页,Page参数需要接收和整理分页条件,最终SpirngMVC会把这个page存到model里,所以在前端页面上从model中就可以获得page。(只要是一个bean,放到参数中,model都会存)
       // 帖子
       DiscussPost post = discussPostService.findDiscussPostById(discussPostId);
       model.addAttribute("post", post);
       // 作者
       User user = userService.findUserById(post.getUserId());
       model.addAttribute("user", user);
       // 点赞数量
       long likeCount = likeService.findEntityLikeCount(ENTITY_TYPE_POST, discussPostId);
       model.addAttribute("likeCount", likeCount);
       // 点赞状态
       int likeStatus = hostHolder.getUser() == null ? 0 :
               likeService.findEntityLikeStatus(hostHolder.getUser().getId(), ENTITY_TYPE_POST, discussPostId);
       model.addAttribute("likeStatus", likeStatus);
       // 评论分页信息
       page.setLimit(5);
       page.setPath("/discuss/detail/" + discussPostId);
       page.setRows(post.getCommentCount());
       // 评论: 给帖子的评论
       // 回复: 给评论的评论
       
       // 评论列表
       List<Comment> commentList = commentService.findCommentsByEntity(
               ENTITY_TYPE_POST, post.getId(), page.getOffset(), page.getLimit());
       // 评论VO列表(view object) 保存在model里,传给前端页面,用来前端展示
       List<Map<String, Object>> commentVoList = new ArrayList<>();
       if (commentList != null) {
           for (Comment comment : commentList) {
               // 一个评论的VO
               Map<String, Object> commentVo = new HashMap<>();
               // 评论
               commentVo.put("comment", comment);
               // 作者
               commentVo.put("user", userService.findUserById(comment.getUserId()));
               // 点赞数量
               likeCount = likeService.findEntityLikeCount(ENTITY_TYPE_COMMENT, comment.getId());
               commentVo.put("likeCount", likeCount);
               // 点赞状态
               likeStatus = hostHolder.getUser() == null ? 0 :
                       likeService.findEntityLikeStatus(hostHolder.getUser().getId(), ENTITY_TYPE_COMMENT, comment.getId());
               commentVo.put("likeStatus", likeStatus);
               // 回复列表(回复: 给评论的评论)
               List<Comment> replyList = commentService.findCommentsByEntity(
                       ENTITY_TYPE_COMMENT, comment.getId(), 0, Integer.MAX_VALUE);
               // 回复VO列表
               List<Map<String, Object>> replyVoList = new ArrayList<>();
               if (replyList != null) {
                   for (Comment reply : replyList) {
                       Map<String, Object> replyVo = new HashMap<>();
                       // 回复
                       replyVo.put("reply", reply);
                       // 作者
                       replyVo.put("user", userService.findUserById(reply.getUserId()));
                       // 回复目标,如果针对某个用户的回复,则target_id不为0,则保存target,如果只是对帖子的回复,没有针对性,则没有target。
                       User target = reply.getTargetId() == 0 ? null : userService.findUserById(reply.getTargetId());
                       replyVo.put("target", target);
                       // 点赞数量
                       likeCount = likeService.findEntityLikeCount(ENTITY_TYPE_COMMENT, reply.getId());
                       replyVo.put("likeCount", likeCount);
                       // 点赞状态
                       likeStatus = hostHolder.getUser() == null ? 0 :
                               likeService.findEntityLikeStatus(hostHolder.getUser().getId(), ENTITY_TYPE_COMMENT, reply.getId());
                       replyVo.put("likeStatus", likeStatus);
                       replyVoList.add(replyVo);
                  }
              }
               commentVo.put("replys", replyVoList);//把回复的list也装进commentVo
               // 回复数量
               int replyCount = commentService.findCommentCount(ENTITY_TYPE_COMMENT, comment.getId());
               commentVo.put("replyCount", replyCount);
               commentVoList.add(commentVo);
          }
      }
       model.addAttribute("comments", commentVoList);
       return "/site/discuss-detail";
  }
3.6添加评论

image-20220616145433745

注意,增加评论和更新帖子的评论数量是一个事务,必须都要成功。在CommentService加上addComment方法

 @Transactional(isolation = Isolation.READ_COMMITTED, propagation = Propagation.REQUIRED)//表示该方法需要事务管理,添加评论和更新帖子评论数量是一个事务
   public int addComment(Comment comment) {
       if (comment == null) {
           throw new IllegalArgumentException("参数不能为空!");
      }
       // 添加评论
       comment.setContent(HtmlUtils.htmlEscape(comment.getContent()));
       comment.setContent(sensitiveFilter.filter(comment.getContent()));
       int rows = commentMapper.insertComment(comment);
       // 更新帖子评论数量
       if (comment.getEntityType() == ENTITY_TYPE_POST) {
           int count = commentMapper.selectCountByEntity(comment.getEntityType(), comment.getEntityId());
           discussPostService.updateCommentCount(comment.getEntityId(), count);
      }
       return rows;
  }

新建一个CommentController

@Controller
@RequestMapping("/comment")
public class CommentController implements CommunityConstant {
   @Autowired
   private CommentService commentService;
   @Autowired
   private HostHolder hostHolder;
   @Autowired
   private EventProducer eventProducer;
   @Autowired
   private DiscussPostService discussPostService;
   @Autowired
   private RedisTemplate redisTemplate;
   @RequestMapping(path = "/add/{discussPostId}", method = RequestMethod.POST)//从前端页面传一个帖子id,就是给哪个帖子增加评论的
   public String addComment(@PathVariable("discussPostId") int discussPostId, Comment comment) {
       comment.setUserId(hostHolder.getUser().getId());
       comment.setStatus(0);
       comment.setCreateTime(new Date());
       commentService.addComment(comment);
       // 触发评论事件
       Event event = new Event()
              .setTopic(TOPIC_COMMENT)
              .setUserId(hostHolder.getUser().getId())
              .setEntityType(comment.getEntityType())
              .setEntityId(comment.getEntityId())
              .setData("postId", discussPostId);
       if (comment.getEntityType() == ENTITY_TYPE_POST) {
           DiscussPost target = discussPostService.findDiscussPostById(comment.getEntityId());
           event.setEntityUserId(target.getUserId());
      } else if (comment.getEntityType() == ENTITY_TYPE_COMMENT) {
           Comment target = commentService.findCommentById(comment.getEntityId());
           event.setEntityUserId(target.getUserId());
      }
       eventProducer.fireEvent(event);
       if (comment.getEntityType() == ENTITY_TYPE_POST) {
           // 触发发帖事件
           event = new Event()
                  .setTopic(TOPIC_PUBLISH)
                  .setUserId(comment.getUserId())
                  .setEntityType(ENTITY_TYPE_POST)
                  .setEntityId(discussPostId);
           eventProducer.fireEvent(event);
           // 计算帖子分数
           String redisKey = RedisKeyUtil.getPostScoreKey();
           redisTemplate.opsForSet().add(redisKey, discussPostId);
      }
       return "redirect:/discuss/detail/" + discussPostId;
  }
}
3.7私信列表

 

image-20220616153151768

mysql的message表(会话表):

CREATE TABLE `message` (
 `id` int NOT NULL AUTO_INCREMENT,
 `from_id` int DEFAULT NULL,//消息的发送人id,若是为1则为系统发送给用户的消息
 `to_id` int DEFAULT NULL,//接收人的id
 `conversation_id` varchar(45) NOT NULL,//会话id,就是把from_id和to_id拼在一起组成的(小的在前)
 `content` text,//内容
 `status` int DEFAULT NULL COMMENT '0-未读;1-已读;2-删除;',
 `create_time` timestamp NULL DEFAULT NULL,
 PRIMARY KEY (`id`),
 KEY `index_from_id` (`from_id`),
 KEY `index_to_id` (`to_id`),
 KEY `index_conversation_id` (`conversation_id`)
) ENGINE=InnoDB AUTO_INCREMENT=364 DEFAULT CHARSET=utf8mb3;

在entity下写一个实体类Message,在dao包下新建MessageMapper,然后在xml文件里实现下面的方法

@Mapper
public interface  MessageMapper {
   // 查询当前用户的会话列表,针对每个会话只返回一条最新的私信.
   List<Message> selectConversations(int userId, int offset, int limit);
   // 查询当前用户的会话数量.
   int selectConversationCount(int userId);
   // 查询某个会话所包含的私信列表.
   List<Message> selectLetters(String conversationId, int offset, int limit);
   // 查询某个会话所包含的私信数量.
   int selectLetterCount(String conversationId);
   // 查询未读私信的数量
   int selectLetterUnreadCount(int userId, String conversationId);
   // 新增消息
   int insertMessage(Message message);
   // 修改消息的状态
   int updateStatus(List<Integer> ids, int status);
   // 查询某个主题下最新的通知
   Message selectLatestNotice(int userId, String topic);
   // 查询某个主题所包含的通知数量
   int selectNoticeCount(int userId, String topic);
   // 查询未读的通知的数量
   int selectNoticeUnreadCount(int userId, String topic);
   // 查询某个主题所包含的通知列表
   List<Message> selectNotices(int userId, String topic, int offset, int limit);
}

再写一个业务层的MessageService。再写一个表现层的MessageController

@Controller
public class MessageController implements CommunityConstant {
   @Autowired
   private MessageService messageService;
   @Autowired
   private HostHolder hostHolder;
   @Autowired
   private UserService userService;
   // 私信列表
   @RequestMapping(path = "/letter/list", method = RequestMethod.GET)
   public String getLetterList(Model model, Page page) {
       User user = hostHolder.getUser();
       // 分页信息
       page.setLimit(5);
       page.setPath("/letter/list");
       page.setRows(messageService.findConversationCount(user.getId()));
       // 会话列表
       List<Message> conversationList = messageService.findConversations(
               user.getId(), page.getOffset(), page.getLimit());
       List<Map<String, Object>> conversations = new ArrayList<>();
       if (conversationList != null) {
           for (Message message : conversationList) {
               Map<String, Object> map = new HashMap<>();
               map.put("conversation", message);
               map.put("letterCount", messageService.findLetterCount(message.getConversationId()));
               map.put("unreadCount", messageService.findLetterUnreadCount(user.getId(), message.getConversationId()));
               int targetId = user.getId() == message.getFromId() ? message.getToId() : message.getFromId();
               map.put("target", userService.findUserById(targetId));

               conversations.add(map);
          }
      }
       model.addAttribute("conversations", conversations);
       // 查询未读消息数量
       int letterUnreadCount = messageService.findLetterUnreadCount(user.getId(), null);
       model.addAttribute("letterUnreadCount", letterUnreadCount);
       int noticeUnreadCount = messageService.findNoticeUnreadCount(user.getId(), null);
       model.addAttribute("noticeUnreadCount", noticeUnreadCount);

       return "/site/letter";
  }
   @RequestMapping(path = "/letter/detail/{conversationId}", method = RequestMethod.GET)
   public String getLetterDetail(@PathVariable("conversationId") String conversationId, Page page, Model model) {
       // 分页信息
       page.setLimit(5);
       page.setPath("/letter/detail/" + conversationId);
       page.setRows(messageService.findLetterCount(conversationId));
       // 私信列表
       List<Message> letterList = messageService.findLetters(conversationId, page.getOffset(), page.getLimit());
       List<Map<String, Object>> letters = new ArrayList<>();
       if (letterList != null) {
           for (Message message : letterList) {
               Map<String, Object> map = new HashMap<>();
               map.put("letter", message);
               map.put("fromUser", userService.findUserById(message.getFromId()));
               letters.add(map);
          }
      }
       model.addAttribute("letters", letters);
       // 私信目标
       model.addAttribute("target", getLetterTarget(conversationId));
       // 设置已读
       List<Integer> ids = getLetterIds(letterList);
       if (!ids.isEmpty()) {
           messageService.readMessage(ids);
      }
       return "/site/letter-detail";
  }
   private User getLetterTarget(String conversationId) {
       String[] ids = conversationId.split("_");
       int id0 = Integer.parseInt(ids[0]);
       int id1 = Integer.parseInt(ids[1]);

       if (hostHolder.getUser().getId() == id0) {
           return userService.findUserById(id1);
      } else {
           return userService.findUserById(id0);
      }
  }
}
3.8发送私信

image-20220616171429568

在dao,service,controller层都增加方法,在MessageController里增加:

private List<Integer> getLetterIds(List<Message> letterList) {
       List<Integer> ids = new ArrayList<>();
       if (letterList != null) {
           for (Message message : letterList) {
               if (hostHolder.getUser().getId() == message.getToId() && message.getStatus() == 0) {//如果当前用户是消息发送的接收方且消息是未读的
                   ids.add(message.getId());
              }
          }
      }
       return ids;//返回所有消息中未读的消息id
  }
   @RequestMapping(path = "/letter/send", method = RequestMethod.POST)
   @ResponseBody//因为是异步请求,所以加上该注解
   public String sendLetter(String toName, String content) {
       User target = userService.findUserByName(toName);
       if (target == null) {
           return CommunityUtil.getJSONString(1, "目标用户不存在!");
      }
       Message message = new Message();
       message.setFromId(hostHolder.getUser().getId());
       message.setToId(target.getId());
       if (message.getFromId() < message.getToId()) {
           message.setConversationId(message.getFromId() + "_" + message.getToId());
      } else {
           message.setConversationId(message.getToId() + "_" + message.getFromId());
      }
       message.setContent(content);
       message.setCreateTime(new Date());
       messageService.addMessage(message);
       return CommunityUtil.getJSONString(0);
  }
3.9统一异常处理

image-20220616163226293

如果业务层或者数据层出异常,都会把异常向上抛到表现层,所以在表现层来统一处理异常。注意,提供好的错误页面(404.html和500.html)必须放到templates下的error包下才行。这样如果程序有问题,springboot会自动的让页面跳到404.html,不用程序员配置或写代码。但是如果想在发生500错误时要记录日志,则需要使用其他的注解。

在HomeController里增加方法:

   @RequestMapping(path = "/error", method = RequestMethod.GET)
   public String getErrorPage() {
       return "/error/500";
  }
   @RequestMapping(path = "/denied", method = RequestMethod.GET)
   public String getDeniedPage() {
       return "/error/404";
  }

在controller下建一个advice包,在其下建一个ExceptionAdvice通知类:

@ControllerAdvice(annotations = Controller.class)//表示ControllerAdvice注解只去扫描带有Controller注解的类,
public class ExceptionAdvice {
   private static final Logger logger = LoggerFactory.getLogger(ExceptionAdvice.class);
   @ExceptionHandler({Exception.class})//表示该方法是异常处理的方法
   public void handleException(Exception e, HttpServletRequest request, HttpServletResponse response) throws IOException {
       logger.error("服务器发生异常: " + e.getMessage());//记录日志
       for (StackTraceElement element : e.getStackTrace()) {
           logger.error(element.toString());
      }
       String xRequestedWith = request.getHeader("x-requested-with");
       if ("XMLHttpRequest".equals(xRequestedWith)) {//如果该请求是异步请求,则返回一个json格式的字符串
           response.setContentType("application/plain;charset=utf-8");
           PrintWriter writer = response.getWriter();
           writer.write(CommunityUtil.getJSONString(1, "服务器异常!"));
      } else {//如果是普通请求,则直接重定向到错误页面
           response.sendRedirect(request.getContextPath() + "/error");
      }
  }
}

这样统一处理异常的好处是不用在任何controller里加代码,只在这一个类里写就可以。

3.10统一记录日志

image-20220616172736492

如果在每个service的每个方法都手动记录日志,这样会复杂且方法耦合了和业务不相关的代码。

image-20220616173035446image-20220616173053524

image-20220616173227512

image-20220616173447202

image-20220616173646802

新建aspects包,在其下建ServiceLogAspect:

@Component
@Aspect
public class ServiceLogAspect {
   private static final Logger logger = LoggerFactory.getLogger(ServiceLogAspect.class);
   @Pointcut("execution(* com.nowcoder.community.service.*.*(..))")//用户访问每个service的每个方法都会用到该切面
   public void pointcut() {

  }
   @Before("pointcut()")
   public void before(JoinPoint joinPoint) {
       // 用户[1.2.3.4],在[xxx],访问了[com.nowcoder.community.service.xxx()].
       ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
       if (attributes == null) {
           return;
      }
       HttpServletRequest request = attributes.getRequest();
       String ip = request.getRemoteHost();
       String now = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
       String target = joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName();
       logger.info(String.format("用户[%s],在[%s],访问了[%s].", ip, now, target));
  }
}
第4章 Redis,一站式高性能存储方案
posted @   zhangshuai2496689659  阅读(9)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
点击右上角即可分享
微信分享提示