• 第3章 Spring Boot进阶,开发社区核心功能(下)


    3.6 添加评论

    image-20220717081307260

    数据访问层(dao)

    首先是在 CommentMapper 接口中增加两个方法添加评论

    int insertComment(Comment comment);
    /**
     * 插入一条评论
     */
    
    • 1
    • 2
    • 3
    • 4

    image-20220717103805158

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

    image-20220717103854104

    然后我们需要在 DiscussPostMapper 接口中增加一个更新帖子数量的方法:

    int updateCommentCount(int id, int commentCount);
    /**
     * 根据帖子id更新评论数量
     */
    
    • 1
    • 2
    • 3
    • 4

    image-20220717103920650

    然后是对应的mapper配置文件

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

    image-20220717103947994

    业务层(Service)

    我们先在 DiscussPostService 中加一个更新评论数量的方法,然后在 CommentService 中开发时可以依赖于 DiscussPostService 组件

    DiscussPostService :

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

    image-20220717104139627

    CommentService:

    为了方便使用常量,CommentService要实现常量接口 CommunityConstant

    @Autowired
    private SensitiveFilter sensitiveFilter;
    
    @Autowired
    private DiscussPostService discussPostService;
    
    
        // 增加评论数量,另外,因为是两个DML操作:增加评论、更新评论数量,所以交给事务管理
        @Transactional(isolation = Isolation.READ_COMMITTED, propagation = Propagation.REQUIRED)  // 事务级别、传播机制
        public int addComment(Comment comment){
            if(comment == null){
                throw new IllegalArgumentException("参数不能为空!");
            }
            // 添加评论        转义html标记     过滤敏感词
            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;
        }
    
    • 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

    image-20220717104502482

    image-20220717104527639

    表现层(controller 和 thymeleaf 模板)

    新建一个 CommentController:

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

    image-20220717104740012

    然后就是处理 discuss-detail.html 模板了

    回复有三种类型:

    • 一种是直接在帖子最下面回复

    image-20220717104935438

    • 一种是回复别人对于帖子的评论的评论

    image-20220717105117324

    • 最后一种是回复别人评论别人的评论

    image-20220717105405129

    测试

    image-20220717105433421

    经过自己测试,三种方式的评论都测试成功!

    3.7 私信列表 和 私信详情

    image-20220717145914509

    这次我们开发的是私信列表私信详情两个功能,这里我们一起开发。

    首先是私信对应的实体类 Message

    public class Message {
    
        private int id;											// 主键
        private int fromId;									// 发信方id
        private int toId;										// 首先方id
        private String conversationId;      // 会话id,有fromId和toId组成
        private String content;							// 绘画内容
        private int status;									// 状态 0-未读   1-已读  2-删除
        private Date createTime;						// 日期
    		// 以免影响阅读体验,get。set。toString方法 没有粘,但开发时这些方法是需要的
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    1. 数据访问层(dao)

    因为是一个新业务,所以我们创建一个新的dao接口

    @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);
    
        // 查询未读私信的数量, 这里conversationId作为动态的条件去拼,不是一定会有,传了就拼上去,这样这个方法能够实现两种业务
        int selectLetterUnreadCount(int userId, String conversationId);
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    image-20220718104614902

    然后是对应的mapper配置文件message-mapper.xml

    
    DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <mapper namespace="com.nowcoder.community.dao.MessageMapper">
    
        <sql id="selectFields">
            id, from_id, to_id, conversation_id, content, status, create_time
        sql>
    
        
        <select id="selectConversations" resultType="com.nowcoder.community.entity.Message">
            select <include refid="selectFields">include>
            from message
            where id in (
                select max(id) from message
                where status != 2
                and from_id != 1
                and (from_id = #{userId} or to_id = #{userId})
                group by conversation_id
            )
            order by id desc
            limit #{offset}, #{limit}
        select>
        
        <select id="selectConversationCount" resultType="Integer">
            select count(m.maxid) from (
               select max(id) as maxid from message
               where status != 2
                and from_id != 1
                and (from_id = #{userId} or to_id = #{userId})
               group by conversation_id
           ) as m
        select>
        
        <select id="selectLetters" resultType="Message">
            select <include refid="selectFields">include>
            from message
            where status != 2
            and from_id != 1
            and conversation_id = #{conversationId}
            order by id desc
            limit #{offset}, #{limit}
        select>
        
        <select id="selectLetterCount" resultType="Integer">
            select count(id)
            from message
            where status != 2
            and from_id != 1
            and conversation_id = #{conversationId}
        select>
        
        <select id="selectLetterUnreadCount" resultType="Integer">
            select count(id)
            from message
            where status = 0
            and from_id != 1
            and to_id = #{userId}
            <if test="conversationId!=null">
                and conversation_id = #{conversationId}
            if>
        select>
    
    mapper>
    
    • 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
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63

    image-20220718104705094

    1. 业务层

    新建一个Service:MessageService

    @Service
    public class MessageService {
    
    
        @Autowired
        private MessageMapper messageMapper;
    
    
        public List<Message> findConversations(int userId, int offset, int limit) {
            return messageMapper.selectConversations(userId, offset, limit);
        }
    
        public int findConversationCount(int userId) {
            return messageMapper.selectConversationCount(userId);
        }
    
        public List<Message> findLetters(String conversationId, int offset, int limit) {
            return messageMapper.selectLetters(conversationId, offset, limit);
        }
    
        public int findLetterCount(String conversationId) {
            return messageMapper.selectLetterCount(conversationId);
        }
    
        public int findLetterUnreadCount(int userId, String conversationId) {
            return messageMapper.selectLetterUnreadCount(userId, conversationId);
        }
    }
    
    • 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

    image-20220718104913944

    1. 表现层

    表现层我们需要两个功能,一个是私信列表,一个是私信详情。这两个功能写在一个controller里就可以。

    先处理私信列表

    新建一个Controller叫 MessageController

    @Controller
    public class MessageController {
    
        @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);               //设置一页显示5条
            page.setPath("/letter/llist");  //设置路径
            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<>();
                    // 将 message 封装进去
                    map.put("conversation", message);
                    // 私信数量
                    map.put("letterCount", messageService.findLetterCount(message.getConversationId()));
                    // 将 未读消息数量 封装进去
                    map.put("unreadCount", messageService.findLetterUnreadCount(user.getId(), message.getConversationId()));
                    // 找到谁给“自己”传了消息的id,然后传入,因为我们在页面上还要显示这个人的用户名和头像
                    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);
    
            return "/site/letter";
        }
      
    }
    
    • 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
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49

    image-20220718105055527

    首先要将首页 index.html“消息” 链到 controller 上

    image-20220718091742873

    然后是处理私信列表的模板 letter.html

    image-20220718091948246

    image-20220718092101195

    image-20220718092655941

    到这里私信列表功能就已经实现了,到这里可以启动一下项目测试一下


    接下来开发 私信详情 功能

    MessageController 中添加下面方法

    // 私信详情
    @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));
    
    
        return "/site/letter-detail";
    }
    
    // 根据conversationId查询目标用户
    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);
        }
    }
    
    • 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
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40

    image-20220718105144995

    然后我们就可以处理模板了

    首先我们在会话列表里 letter.html 做一个处理,把那个链接(可以链到详情页面)做好

    image-20220718101751168

    然后处理私信详情页面 letter-detail.html

    image-20220718104108032

    image-20220718104207393

    image-20220718104343612

    image-20220718104424136

    image-20220718104452073

    到这里私信详情就开发完了

    测试

    image-20220718105307160

    image-20220718105238305

    经测试,功能开发成功!

    3.8 发送私信

    image-20220718145742909

    这次开发发送私信设置已读功能

    首先开发发送私信的功能

    1. 数据访问层(dao)

    在之前创建好的dao组件MessageMapper中增加 发送私信设置已读 方法

    // 新增消息(私信)
    int insertMessage(Message message);
    
    // 修改私信的状态(未读-->已读或添加私信后首先直接设置为未读货值设置为删除)
    int updateStatus(List<Integer> ids, int status);
    
    • 1
    • 2
    • 3
    • 4
    • 5

    image-20220718153914495

    然后是对应的mapper配置文件

    image-20220718154113387

    1. 业务层(Service)

    在之前创建的MessageService中添加 发送私信设置已读 方法

    @Autowired
    private SensitiveFilter sensitiveFilter;
    // 新增私信
    public int addMessage(Message message){
      // 过滤一下消息(过滤标签和敏感词)
      message.setContent(HtmlUtils.htmlEscape(message.getContent()));
      message.setContent(sensitiveFilter.filter(message.getContent()));
      return messageMapper.insertMessage(message);
    }
    // 更新私信的状态
    public int readMessage(List<Integer> ids) {
      return messageMapper.updateStatus(ids, 1);  // 1 表示已读
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    image-20220718154743154

    1. 表现层

    UserService中增加一个根据用户名查询用户的方法(dao接口之前已经写过这个方法),方便MessageController使用这个方法

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

    image-20220718160351905

    然后在MessageController中增加添加私信方法

    // 发送私信
    @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, "目标用户不存在!"); // 返回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);
        // 异步方式返回JSON字符串
        return CommunityUtil.getJSONString(0);  // 新增私信默认状态为0,表示未读
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    然后我们需要修改 私信列表 letter.html 处的发私信按钮,这个功能是在 letter.js 中实现的,所以我们需要修改一下letter.js

    image-20220718163015040

    image-20220718163032329

    image-20220718163148096

    然后我们需要处理一下私信详情页面 letter-detail.html的私信时会默认带上对方的用户名,我们要处理一下

    image-20220718163538245

    到这里发送私信的功能就开发完了。

    然后我们接下来需要开发当用户读私信时设置已读功能

    我们只需要在 MessageController 的 getLetterDetail 访问私信详情方法里,把私信列表里面的未读的消息提取出来,然后自动的设置为已读就可以了

    image-20220718165013138

    这样,设置已读功能就开发完成了,开发完成之后可以测试一下。

    3.9 统一异常处理

    SpringBoot中异常的自动处理

    浏览器发送请求的时候一律发送给表现层,然后 表现层 调用 业务层,业务层 调用 数据层,假如 数据层 出异常了,它会抛给 业务层,然后 业务层 会抛给 表现层。所以整个三层架构,无论 数据层还是业务层哪个层次的异常,最终都会汇集到表现层,因此只要我们统一的对表现层捕获异常就能处理系统的所有异常

    SpringBoot中异常处理

    SpringBoot会自动处理异常,跳转到相应的 404.html 、500.html 对应的错误状态页面。

    我们先将 404.html、500.html页面处理一下:

    文件夹名字一定要叫 error,且在 templates 目录下,错误文件的名字一定要叫错误状态。

    image-20220719085500452

    image-20220719085644094

    image-20220719090041068

    运行项目测试:

    image-20220719090059262

    image-20220719090137416

    SpringBoot会自动给异常进行一个处理,但是这个处理还不是一定符合我们的预期,

    比如 404 这个就ok,找不到资源没有更好的办法,500 就不一样,500 意味着服务端报错了,报错的时候我们最好记个日志,好以后分析,所以直接跳转页面这只是表面上的一个处理,我们内在需要记个日志这个还没有处理。

    SpringBoot中异常的详细处理

    接下来我们来看一下如何统一记录日志:

    image-20220719081530683

    • @ModelAttribute

    绑定数据的意思是:

    比如 controller 有很多请求,这些请求当中要用到同一个数据给模板,我们需要往 model

    里装同一个数据,我们可以利用 @ModelAttribute 加一个方法给 model 统一绑定参数,给所有的 controller 用。

    • @DataBinder

      这个注解的作用是,页面向服务器传参,会被自动的做转换,是因为内部调了很多的参数转换器,万一程序当中默认的参数转换器不够用,有一个特殊的类型需要处理,可以自定义一个转换器,然后用 @DataBinder 把它注册上。

    上面的 @ModelAttribute 和 @DataBinder 目前没有需求要用,所以就不演示了。接下来我们演示利用 @ExceptionHandler 统一处理所有 controller 可能发生的异常。

    演示

    首先在 HomeController 里加上一个请求,为什么呢?因为controller发生异常之后,我们统一处理,记了日志,处理完以后我们得去到 500那个页面,这个时候因为是我们人为处理的,我们需要手动的重定向过去,所以我们需要提前把 500 页面这个请求的访问给它配一下,增加一下这个请求的处理。

    @RequestMapping(path = "/error", method = RequestMethod.GET)
    public String getErrorPage() {
        return "/error/500";
    }
    
    • 1
    • 2
    • 3
    • 4

    image-20220719101404514

    接下来我们需要用@ControllerAdvice 声明一个 controller 全局配置类,然后对所有 controller 的异常做统一的处理

    @ControllerAdvice 不加参数的话会扫描所有的bean,范围太大了,我们通常需要一个限制,让它范围小一点,

    @ControllerAdvice(annotations = Controller.class) 的意思是 这个注解只扫描带有 @Controller 注解的那些组件,

    我们需要在这个类里面加一个方法处理所有的错误的情况,方法之前加一个注解@ExceptionHandler 表示这个方法是处理所有异常的方法

    @ControllerAdvice(annotations = Controller.class)
    public class ExceptionAdvice {
    
        // 为了记日志
        private static Logger logger = LoggerFactory.getLogger(ExceptionAdvice.class);
    
        @ExceptionHandler({Exception.class})   // 里面的参数表示处理所有异常
        public void handlerException(Exception e, HttpServletRequest request, HttpServletResponse response) throws IOException {        // 方法名无所谓
            // 记录一下异常的概括
            logger.error("服务器发生异常: " + e.getMessage());
            // 详细记录异常的栈的信息
            for (StackTraceElement element : e.getStackTrace()) {
                logger.error(element.toString());
            }
            /*
                普通的请求重定向到刚刚500的controller
                异步请求的话返回json,json字符串里面写错误信息
             */
            // 判断请求是普通请求还是异步请求
            String xRequestedWith = request.getHeader("x-requested-with");
            if ("XMLHttpRequest".equals(xRequestedWith)) {
                // 异步请求
                response.setContentType("application/plain;charset=utf-8"); // plain表示向浏览器返回的是一个普通的字符串
                PrintWriter writer = response.getWriter();
                writer.write(CommunityUtil.getJSONString(1, "服务器异常!"));
            } else {
                // 普通请求重定向到错误页面
                response.sendRedirect(request.getContextPath() + "/error");
            }
        }
    }
    
    • 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
    • 29
    • 30
    • 31

    image-20220719093924630

    image-20220719101522812

    上面的处理的话如果是 500 错误,表示服务器错误,会记个日志,然后跳到 500 那个controller,然后这个 controller会跳转到 500.html 界面

    如果是 404 错误,也就是找不到资源,会直接跳转到 404.html 页面

    测试

    image-20220719101736530

    普通请求的 500 异常

    image-20220719101914295

    异步发私信的 500 异常

    image-20220719101759548

    到这里,统一异常处理就完成了。

    3.10 统一记录日志

    image-20220719154250916

    这里日志的意思是平时不发生异常的话也记录日志。

    image-20220719154720536

    image-20220719155449555

    image-20220719162102990

    image-20220719162535840

    切入点表达式:

    # 切入点表达式
        切入点表达式
        execution(返回值 包.类名.方法名(参数类型))
        execution(返回值 包.类名.*(..))
            *代表类中所有方法  ..代表参数任意
            aop.*.*(..) 代表aop包下的所有类的所有方法在执行时都要加前置通知
        execution:
            第一个代表方法的返回值,*代表不关心方法的返回值
            之后空格写包名(包.类.方法名),切类中所有方法且不关心方法的参数写*(..)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    以前写的关于AOP的博客:

    SpringBoot中AOP切面编程和文件上传

    Spring中AOP编程本质(动态代理机制)以及前置通知的开发

    SpringAOP使用示例

    Spring AOP 的示例

    下面写一个示例体会一下 Spring Aop 的语法

    写完这个示例之后将类的两个注解注释掉,防止影响记录日志时的数据观看

    
    @Component
    @Aspect
    public class AlphaAspect {
    
        @Pointcut("execution(* com.nowcoder.community.service.*.*(..))")
        public void pointcut() {
    
        }
        // 前置通知
        @Before("pointcut()")
        public void before() {
            System.out.println("before");
        }
        // 后置通知
        @After("pointcut()")
        public void after() {
            System.out.println("after");
        }
        // 如果想在有了返回值以后处理一下逻辑可以使用这个注解(相当于后置通知的增强版)
        @AfterReturning("pointcut()")
        public void afterRetuning() {
            System.out.println("afterRetuning");
        }
        // 在抛异常之后置入代码
        @AfterThrowing("pointcut()")
        public void afterThrowing() {
            System.out.println("afterThrowing");
        }
        // 环绕通知
        @Around("pointcut()")
        public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
            System.out.println("around before");
            Object obj = joinPoint.proceed();    // 调目标组件的方法
            System.out.println("around after");
            return obj;                         // 目标组件方法的返回值
        }
    }
    
    /*
    around before
    before
    2022-07-19 17:00:43,555 DEBUG [http-nio-8080-exec-1] c.n.c.d.L.selectByTicket [BaseJdbcLogger.java:143] ==>  Preparing: select id, user_id, ticket, status, expired from login_ticket where ticket = ? 
    2022-07-19 17:00:43,555 DEBUG [http-nio-8080-exec-1] c.n.c.d.L.selectByTicket [BaseJdbcLogger.java:143] ==> Parameters: e626e90074e84641993380de204f177e(String)
    2022-07-19 17:00:43,557 DEBUG [http-nio-8080-exec-1] c.n.c.d.L.selectByTicket [BaseJdbcLogger.java:143] <==      Total: 1
    afterRetuning
    after
    around after
    */
    
    • 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
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49

    image-20220719170818303

    对所有业务组件记录日志

    @Component
    @Aspect
    public class ServiceLogAspect {
    
        // 日志
        private static final Logger logger = LoggerFactory.getLogger(ServiceLogAspect.class);
        // 切点
        @Pointcut("execution(* com.nowcoder.community.service.*.*(..))")
        public void pointcut() {
        }
        // 使用前置通知记录日志
        @Before("pointcut()")
        public void before(JoinPoint joinPoint) {    // 这个参数是为了知道访问了哪个类的哪个方法
            // 用户[1.2.3.4,ip地址],在[xx时间],访问了[com.nowcoder.community.service.xxx()].
            ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            // 得到request对象
            HttpServletRequest request = attributes.getRequest();
            String ip = request.getRemoteHost();        // 利用request得到ip地址
            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));
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    访问首页:

    image-20220719173449969

    经测试,统一记录日志功能完成

  • 相关阅读:
    NOIP2023模拟19联测40 诡异键盘
    橘子学linux03之Linux文件管理(上)
    springboot配置https
    STM32H7 Azure RTOS
    openstack在计算节点安装配置 Nova
    水质测定仪的优势特点有哪些?
    行业沙龙第一期丨汽配供应链业务的解痛之道与数字化未来
    深度学习 - RNN训练过程推演
    k8s 拉取镜像报错 no basic auth credentials
    计算机算法的设计与分析——排序和顺序统计
  • 原文地址:https://blog.csdn.net/qq_50313418/article/details/126327836