社区开发3

过滤敏感词

  • 前缀树
    名称:Trie、字典树、查找树
    特点:查找效率高,消耗内存大
    应用:字符串检索、词频统计、字符串排序等
  • 敏感词过滤器
    定义前缀树
    根据敏感词,初始化前缀树
    编写过滤敏感词的方法

前缀树的特点:1.前缀树的根节点不包含字符,除了根节点以外的节点只包含一个字符 2.从根节点到叶子节点途径的字符连起来就是字符串;3.每个节点的所有子节点包含的字符串不相同
在这里插入图片描述

    // 前缀树
    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);
        }
    }

发布帖子

异步请求:当前网页不刷新,访问服务器,服务器返回一些不是网页的结果,从结果中提炼的数据对网页进行局部刷新

  • AJAX
    异步的JavaScript与XML,网页能够将增量更新呈现在页面上,而不需要刷新整个页面
    X代表XML,但是目前JSON字符串使用更加的方便普遍

DiscussPost实体类封装帖子信息
index页面上点击我要发布会调用jindex.js中方法,把数据传到服务器中;DiscussPostController创建实体类将从浏览器传过来的标题和内容封装进去,然后调用DiscussPostService处理:转义标签,过滤敏感词,添加帖子;添加帖子是通过DiscussPostMapper将帖子放入数据库

//index.js
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},//向服务器提交的数据,json字符串
	    function(data) {				  //回调函数,服务器响应的数据会传到data里
	        data = $.parseJSON(data);
	        // 在提示框中显示返回消息
	        $("#hintBody").text(data.msg);
	        // 显示提示框
            $("#hintModal").modal("show");
            // 2秒后,自动隐藏提示框
            setTimeout(function(){
                $("#hintModal").modal("hide");
                // ==0成功,刷新页面
                if(data.code == 0) {
                    window.location.reload(); //重新加载页面
                }
            }, 2000);
	    }
	);
//数据访问层,Mapper
// 增加帖子的方法,返回的是增加的行数,传入的是实体
int insertDiscussPost(DiscussPost discussPost);
//Mapper.xml
<!--id:对应的方法名,parameterType:方法的参数-->
<insert id="insertDiscussPost" parameterType="DiscussPost">
    insert into discuss_post(<include refid="insertFields"></include>)
    values(#{userId},#{title},#{content},#{type},#{status},#{createTime},#{commentCount},#{score})
</insert>
//service层
// 添加帖子,转义标签,过滤敏感词
    public int addDiscussPost(DiscussPost post) {
        if (post == null) {
            throw new IllegalArgumentException("参数不能为空!");
        }
        // 转义HTML标记  :<script>asd</script>,把标签去掉
        // HtmlUtils.htmlEscape(String) 方法会把其中的标签转义成转义字符
        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);
    }
	//controller层
	@RequestMapping(path = "/add", method = RequestMethod.POST)
    @ResponseBody
    public String addDiscussPost(String title, String content) {
        User user = hostHolder.getUser();
        if (user == null) {
            //返回json字符串
            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, "发布成功!");
    }

帖子详情

首页点击帖子(包含帖子信息和对应的user信息)会通过帖子id访问帖子详情,访问DiscussPostController的方法

<!--点帖子会从这里访问/discuss/detail/{discussPostId}-->
<a th:href="@{|/discuss/detail/${map.post.id}|}" th:utext="${map.post.title}"></a>
//controller层
//访问时是/discuss/detail/{discussPostId} ,discussPostId为帖子id而不是userid,这个类路径/discuss
    @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);
        // 作者,从帖子中userID获得username
        User user = userService.findUserById(post.getUserId());
        model.addAttribute("user", user);
        return "/site/discuss-detail";
         }
//DiscussPostService
// 根据ID查询帖子
    public DiscussPost findDiscussPostById(int id) {
        return discussPostMapper.selectDiscussPostById(id);
    }
// Mapper
DiscussPost selectDiscussPostById(int id);
// Mapper.xml
<!--id:对应的方法名,resultType:方法返回的参数-->
    <select id="selectDiscussPostById" resultType="DiscussPost">
        select <include refid="selectFields"></include>
        from discuss_post
        where id = #{id}
    </select>

事务管理

  • 事务:事务由N步数据库操作序列组成的逻辑执行单元,这系列操作要么全执行,要么全放弃执行
  • 事务的特性(ACID):原子性、一致性、隔离性、持久性
  • 事物的隔离性
    常见的并发异常:第一类丢失更新、第二类丢失更新;脏读、不可重复读、幻读
    常见的隔离级别:读取未提交的数据、读取已提交的数据、可重复读、串行化

第一类丢失更新:某一个事务的回滚,导致另外一个事务已更新的数据丢失了。
第二类丢失更新:某一个事务的提交,导致另外一个事务已更新的数据丢失了。
脏读:某一个事务,读取了另外一个事务未提交的数据。
不可重复读:某一个事务,对同一个数据前后读取结果不一样。
幻读:某一个事务,对同一个表前后查询的行数不一致。

实现机制

  • 悲观锁(数据库)
    共享锁(S锁):事务A对某数据加了共享锁之后,其他事务只能对该数据加共享锁,但不能加排它锁。(可读,不可写)
    排他锁(X锁):事务A对某数据加了排他锁之后,其他事务对该数据既不能加共享锁,也不能加排它锁。(其他事务不可读,不可写)
  • 乐观锁(自定义)
    版本号、时间戳等;在更新数据前,检查版本号是否发生变化。若变化则取消本次更新,否则就更新数据(版本号+1);

Spring事务管理

  • 声明式事务(整个方法在一个事务之内,不能是局部进行事务管理)
    通过XML配置,声明某方法的事务特征
    通过注解,声明某方法的事务特征 @Transactional
  • 编程式事务(整个方法在一个事务之内,也可以是方法局部进行事务管理)
    通过Transaction Template类管理事务,并通过它执行数据库操作。
//Service层进行事务管理
// isolation = Isolation.READ_COMMITTED :隔离级别
    // propagation传播机制:业务方法A可能调用业务方法B,这两个方法可能都加上这个注解(管理事务),B的事务应该以谁为准;解决交叉的问题
    // REQUIRED: 支持当前事务(外部事务,对于B来说A就是当前事务),如果不存在则创建新事务.
    // REQUIRES_NEW: 创建一个新事务,并且暂停当前事务(外部事务).
    // NESTED: 如果当前存在事务(外部事务),则嵌套在该事务中执行(独立的提交和回滚),否则就会REQUIRED一样.
    @Transactional(isolation = Isolation.READ_COMMITTED, propagation = Propagation.REQUIRED)
    public Object save1() {
        // 新增用户
        User user = new User();
        user.setUsername("alpha");
        user.setSalt(CommunityUtil.generateUUID().substring(0, 5));
        user.setPassword(CommunityUtil.md5("123" + user.getSalt()));
        user.setEmail("alpha@qq.com");
        user.setHeaderUrl("http://image.nowcoder.com/head/99t.png");
        user.setCreateTime(new Date());
        userMapper.insertUser(user);
        // 新增帖子
        DiscussPost post = new DiscussPost();
        post.setUserId(user.getId());
        post.setTitle("Hello");
        post.setContent("新人报道!");
        post.setCreateTime(new Date());
        discussPostMapper.insertDiscussPost(post);
        Integer.valueOf("abc"); //这是个错误,演示回滚

        return "ok";
    }

    public Object save2() {
        // 声明隔离级别
        transactionTemplate.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED);
        // 声明传播机制
        transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
        
        // 这个方法实现事务管理
        // TransactionCallback:回调接口
        return transactionTemplate.execute(new TransactionCallback<Object>() {
            @Override
            //回调方法,由Template底层自动调的,调用的时候会对方法进行事务的管理
            public Object doInTransaction(TransactionStatus status) {
                // 新增用户
                User user = new User();
                user.setUsername("beta");
                user.setSalt(CommunityUtil.generateUUID().substring(0, 5));
                user.setPassword(CommunityUtil.md5("123" + user.getSalt()));
                user.setEmail("beta@qq.com");
                user.setHeaderUrl("http://image.nowcoder.com/head/999t.png");
                user.setCreateTime(new Date());
                userMapper.insertUser(user);

                // 新增帖子
                DiscussPost post = new DiscussPost();
                post.setUserId(user.getId());
                post.setTitle("你好");
                post.setContent("我是新人!");
                post.setCreateTime(new Date());
                discussPostMapper.insertDiscussPost(post);
                Integer.valueOf("abc");
                return "ok";
            }
        });
    }

显示评论

  • 数据层
    根据实体查询一页评论数据。
    根据实体查询评论的数量。
  • 业务层
    处理查询评论的业务,处理查询评论数量的业务。
  • 表现层
    显示帖子详情数据时,同时显示该帖子所有的评论数据。
    在这里插入图片描述
    在这里插入图片描述
//访问时是/discuss/detail/{discussPostId} ,这个类路径/discuss
 @RequestMapping(path = "/detail/{discussPostId}", method = RequestMethod.GET)
 public String getDiscussPost(@PathVariable("discussPostId") int discussPostId, Model model, Page page) {//Page接收、整理分页条件
     // 帖子
     DiscussPost post = discussPostService.findDiscussPostById(discussPostId);
     model.addAttribute("post", post);
     // 作者,从帖子中userID获得username
     User user = userService.findUserById(post.getUserId());
     model.addAttribute("user", user);

     // 评论分页信息
     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列表,把评论对应的user找到和评论一起放入一个map中,以及帖子评论的评论
	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()));
	
	        // 评论的回复列表
	        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()));
	                // 回复目标,==0
	                User target = reply.getTargetId() == 0 ? null : userService.findUserById(reply.getTargetId());
	                replyVo.put("target", target);
	
	                replyVoList.add(replyVo);
	            }
	        }
	        commentVo.put("replys", replyVoList);	
	        // 回复数量
	        int replyCount = commentService.findCommentCount(ENTITY_TYPE_COMMENT, comment.getId());
	        commentVo.put("replyCount", replyCount);	
	        commentVoList.add(commentVo);
	    }
	}
	
	model.addAttribute("comments", commentVoList);
	
	return "/site/discuss-detail";
//CommentService
public List<Comment> findCommentsByEntity(int entityType, int entityId, int offset, int limit) {
        return commentMapper.selectCommentsByEntity(entityType, entityId, offset, limit);
    }

    public int findCommentCount(int entityType, int entityId) {
        return commentMapper.selectCountByEntity(entityType, entityId);
    }
@Mapper
public interface CommentMapper {
    // 根据entityType实体(帖子的评论、课程的评论等)查询,offset每页的起始行,limit每页显示的行数的限制
    // 查询某一个帖子(entityType类型中id为entityId的帖子)的评论中从offset起的limit条评论
    List<Comment> selectCommentsByEntity(int entityType, int entityId, int offset, int limit);
    // 查询评论数据的条目数
    int selectCountByEntity(int entityType, int entityId);
}

	<!--Mapper.xml-->
    <select id="selectCommentsByEntity" resultType="Comment">
        select <include refid="selectFields"></include>
        from comment
        where status = 0
        and entity_type = #{entityType}
        and entity_id = #{entityId}
        order by create_time asc
        limit #{offset}, #{limit}
    </select>

    <select id="selectCountByEntity" resultType="int">
        select count(id)
        from comment
        where status = 0
        and entity_type = #{entityType}
        and entity_id = #{entityId}
    </select>

添加评论

  • 数据层
    增加评论数据
    修改帖子的评论数量
  • 业务层
    处理添加评论的业务:先增加评论、再更新帖子的评论数量(进行事务管理,要同步)
  • 表现层
    处理添加评论数据的请求
    设置添加评论的表单
    在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
共有三个回复的情况:1.直接在最下面对当前帖子回复(只有这一种会增加帖子回复的数量);2.对某一个评论回复;3.对某一个评论的评论回复(显示谁回复了谁)


@Controller
@RequestMapping("/comment")
public class CommentController {
    @Autowired
    private CommentService commentService;
    @Autowired
    private HostHolder hostHolder;
    // 帖子详情路径(/discuss/detail/{discussPostId})中有帖子的id,{discussPostId}把id传过来,
    // 一会添加完评论后重新回到帖子详情时需要用到这个id
    @RequestMapping(path = "/add/{discussPostId}", method = RequestMethod.POST)
    // comment中存当前用户的userid,评论创建的时间
    // comment会从表单中自动接收content、entityType、entityId
    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;
    }
//Service层
	// 进行事务管理,先增加评论、再更新帖子的评论数量这两件事要么一块完成,要么都不完成
    // 如果判断不是帖子回复的类型,则不会增加帖子评论数量
    @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. 帖子详情页面("/discuss/detail/{discussPostId}")的三种回复的提交方式(评论类型entityType不同,各自的entityId,content,以及是否有targetId)—>
  2. 都会访问DocummentController.addComment()方法,Comment实体接收表单传过来的信息—>
  3. 调用 commentService.addComment()方法【这是一个事务管理方法,先增加评论、再更新帖子的评论数量这两件事要么一块完成,要么都不完成。如果判断不是帖子回复的类型,则不会增加帖子评论数量】—>
  4. commentMapper.insertComment()把评论增加到相应的数据库中【如果是帖子回复更新帖子的评论数量,discussPostService.updateCommentCount(comment.getEntityId(), count);—>discussPostMapper.updateCommentCount(id, commentCount);】

显示评论

  1. 从首页点击帖子打开帖子详情页面("/discuss/detail/{discussPostId}")—>
  2. 访问DiscussPostController.getDiscussPost ()方法:
    ① post=discussPostService.findDiscussPostById(discussPostId) —> discussPostMapper.selectDiscussPostById(discussPostid); 找到帖子
    ② userService.findUserById(post.getUserId()); 找到帖子作者
    ③ List<Map<String, Object>> 评论VO列表,把评论对应的user找到和评论以及帖子评论的回复列表一起放入一个map中。

私信列表

  • 私信列表
    查询当前用户的会话列表,每个会话只显示一条最新的私信;
    支持分页显示
    未读消息数量用小红框显示在会话的上面,显示和某一个人总共有多少条私信往来
  • 私信详情
    查询某个会话所包含的私信
    支持分页显示
  • 设置已读
    访问私信详情时,将显示的私信状态设置为已读状态。
  • 发送私信
    采用异步的方式发送私信AJAX
    发送成功后刷新私信列表列表,或者会话列表(看是否点进去和某个用户的会话页面发送)
    在这里插入图片描述
 // 私信列表
    @RequestMapping(path = "/letter/list", method = RequestMethod.GET)
    // 虽然get请求,current会传过来装到page里
    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()); //从浏览器点的第几页获取page.getOffset()
        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));// targetID对应的用户信息

                conversations.add(map);
            }
        }
        model.addAttribute("conversations", conversations);
        // 查询未读消息数量,总体未读数量
        int letterUnreadCount = messageService.findLetterUnreadCount(user.getId(), null);
        model.addAttribute("letterUnreadCount", letterUnreadCount);
        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);
        }
    }
    // 找到未读信息
    private List<Integer> getLetterIds(List<Message> letterList) {
        List<Integer> ids = new ArrayList<>();

        if (letterList != null) {
            for (Message message : letterList) {
                // 这条私信中我是接收方,并且私信状态为未读,把未读信息加到ids中
               if (hostHolder.getUser().getId() == message.getToId() && message.getStatus() == 0) {
                    ids.add(message.getId());
                }
            }
        }

        return ids;
    }
    //发送私信
 	@RequestMapping(path = "/letter/send", method = RequestMethod.POST)
    @ResponseBody
    public String sendLetter(String toName, String content) {
        User target = userService.findUserByName(toName); // 根据名字找到我发送给的这个用户user
        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);
    }
//对于Mapper
// 查询当前用户的会话列表,针对每个会话只返回一条最新的私信.
    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);

统一处理异常

针对表现层统一处理异常,业务层和数据层抛出的异常最终都会到表现层上。

  • @ControllerAdvice
    用于修饰类,表示该类是Controller的全局配置类。(对所有的controller错误进行统一处理)
    在此类中,可以对controller进行三种全局配置:异常处理方案、绑定数据方案、绑定参数方案。
  • @ExceptionHandler
    用于修饰方法,该方法会在Controller出现异常后调用,用于处理捕获到的异常
  • @ModelAttribute
    用于修饰方法,该方法会在Controller执行前被调用,用于为Model对象绑定参数
  • @DataBinder
    用于修饰方法,该方法会在Controller执行前被调用,用于绑定参数的转换器

把error文件夹放在Template文件夹下,同时文件夹下的文件名字要为错误状态码名字

// controller全局配置类
// 这个组件只去扫描带有controller注解的bean
@ControllerAdvice(annotations = Controller.class)
public class ExceptionAdvice {
    private static final Logger logger = LoggerFactory.getLogger(ExceptionAdvice.class);
    @ExceptionHandler({Exception.class})
    // Exception:controller中发生的异常
    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)) { // 这是异步请求
            // application/plain:向浏览器返回的是一个普通的字符串(可以是json格式),浏览器得到后要人为的将字符串转化为js对象
            // application/json:向浏览器返回一个字符串,浏览器自动把其转化为json对象
            // charset=utf-8:声明字符集格式
            response.setContentType("application/plain;charset=utf-8");
            // 输出一个输出流
            PrintWriter writer = response.getWriter();
            writer.write(CommunityUtil.getJSONString(1, "服务器异常!"));
        } else {
            // <img th:src="@{/img/404.png}" >  传回去的是图片,不是网页
            response.sendRedirect(request.getContextPath() + "/error");
        }
    }

统一记录日志

AOP:面向切面编程,是一种编程思想,是对面向对象(OOP)的补充。

AOP(Aspect-Oriented Programming:面向切面编程)能够将那些与业务无关,却为业务模块所共同调用的逻辑或责任(例如事务处理、日志管理、权限控制等)封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可拓展性和可维护性。

AOP实现:

  • AspectJ :语言级别的实现,扩展了java语言,定义了AOP语法;在编译器织如代码,它有一个专门的编译器,用来生成遵循java字节码规范的class文件
  • Spring AOP:Spring AOP使用纯java实现,它不需要专门的编译过程,也不需要特殊的类加载器;在运行时织入代码,只支持方法类型的连接点;Spring支持AspectJ的集成。

Spring AOP实现

  • JDK动态代理:java提供的动态的代理方式,可以在运行时创建接口的代理实例;Spring AOP默认采用这种方式,在接口的代理实例中织入代码。
  • CGLib动态代理:采用底层的字节码技术,在运行时创建子类代理对象;当目标对象不存在接口时,Apring AOP会采用这种方式,在子类实例中织入代码。
    在这里插入图片描述
@Component
@Aspect  // AOP切面 注解
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) {//JoinPoint指代程序织入的目标组件调用的那个方法
        // 用户[1.2.3.4],在[xxx],访问了[com.nowcoder.community.service.xxx()].
        // 获得request对象
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        // 从request中获取IP
        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));
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值