栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

仿牛客网社区开发--核心功能模块

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

仿牛客网社区开发--核心功能模块

1.过滤敏感词

我们想在某个网站上发布一些内容的时候,网站会对我们所发布的内容进行过滤,如果发现我的内容里包含一些色情、暴力等非法词汇,会把这些词汇隐去,即不显示或者打码,那么这种行为就叫过滤敏感词。从技术上的角度就是对输入的字符串进行过滤,调用某些api判断这些字符串是否合法,如jdk里面的replace方法,但是如果发布的是一篇文章,那么字符串的数量就会非常多,这个时候再使用原生的api会导致效率非常低,这个时候推荐使用前缀树这种数据结构,自己实现过滤敏感词的一种算法。

前缀树的根节点不包含任何字符,除了根节点以外的节点只包含一个字符,从根节点到某一个节点的路径上就是当前这个节点对应的字符串,每个节点所有的子节点包含的字符串不同。

如图所示,从根节点到叶子节点走过的路径,即为一个敏感词。
首先,需要定义好项目中的敏感词有哪些,根据这些敏感词构造一棵前缀树,检测用户输入的字符串是否有敏感词。
具体实现需要三个指针,一个指针指向前缀树,两个指针指向字符串(一前一后),同时需要一个新的空间存储结果。
resource下新建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 //表示这是一个初始化方法,当容器实例化这个bean以后,在调用其构造器之后,这个方法就会被自动调用,即在服务器启动就会被初始化,用于筛选敏感词
    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);
            }
        }
    }

    
    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 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);
        }
    }
}
2.发布帖子

maven搜索fastjson,把相关的依赖添加进pom.xml

		
			com.alibaba
			fastjson
			1.2.58
		

在CommunityUtil类中实现获取JSON字符串的三个构造方法

    public static String getJSONString(int code, String msg, Map 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);
    }

有关ajax的小示例

    //ajax示例
    @RequestMapping(path = "/ajax", method = RequestMethod.POST)
    @ResponseBody
    public String testAjax(String name, int age) {
        System.out.println(name);
        System.out.println(age);
        return CommunityUtil.getJSONString(0, "操作成功!");
    }



    
    AJAX


DiscussPostMapper中新增一个发布帖子的方法
discusspost-mapper.xml中补充sql语句

    int insertDiscussPost(DiscussPost discussPost);
    
        user_id, title, content, type, status, create_time, comment_count, score
    

    
        insert into discuss_post()
        values(#{userId},#{title},#{content},#{type},#{status},#{createTime},#{commentCount},#{score})
    

DiscussPostService增加addDiscussPost()方法,用于转义HTML标记和过滤敏感词,并且调用insertDiscussPost()插入数据。

    public int addDiscussPost(DiscussPost post) {
        if (post == null) {
            throw new IllegalArgumentException("参数不能为空!");
        }

        // 转义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,添加add访问请求

@Controller
@RequestMapping("/discuss")
public class DiscussPostController {
	@Autowired
    private DiscussPostService discussPostService;

    @Autowired
    private HostHolder hostHolder; //获取当前用户

	    @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

$(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.帖子详情

DiscussPostMapper中新增一个根据帖子id查询到帖子详情的方法

    DiscussPost selectDiscussPostById(int id);

在discusspost-mapper.xml中书写sql语句

    
						
					
				

仿牛客网社区开发--核心功能模块
备战春招,面试刷题跟他复习,一个月全搞定!
用户头像
寒江雪
发布于 2019-04-15 15:32:18
金三银四的金三已经到了,你还沉浸在过年的喜悦中吗? 如果是,那我要让你清醒一下了:目前大部分公司已经开启了内推,正式网申也将在3月份陆续开始,金三银四,春招的求职黄金时期已经来啦!!! 再不准备,作为19应届生的你可能就找不到工作了。。。作为20届实习生的你可能就找不到实习了。。。 现阶段时间紧,任务重,能做到短时间内快速提升的也就只有算法了, 那么算法要怎么复习?重点在哪里?常见笔试面试算法题型和解题思路以及最优代码是怎样的? 跟左程云老师学算法,不仅能解决以上所有问题,还能在短时间内得到最大程度的提升!!!
30条回帖
  • 用户头像
    掉脑袋切切 1#
    这开课时间是不是有点晚啊。。。
    发布于 2019-04-15 15:32:18
    • 寒江雪:   Sissi 回复 寒江雪:   这个是直播时间哈,觉得晚的话可以直接看之前的完整录播的~
      2019-04-15 15:32:18

4.事务管理

事务是由N布数据库操作序列组成的逻辑执行单元,这些序列操作要么全执行,要么全放弃执行。
事务的特性(ACID):
原子性(Atomicity):事务是应用中不可再分的最小执行体。
一致性(Consistency):事务执行的结果,须使数据从一个一致性状态,变为另一个一致性状态。
隔离性(Isolation):各个事务的执行互不干扰,任何事务的内部操作对其他的事务都是隔离的。
持久性(Durability):事务一旦提交,对数据所做的任何改变都要记录到永久存储器中。

事务的隔离性是针对并发而言的,如果在多线程的环境下没有做事务隔离,每一个浏览器在访问服务器的时候,服务器就会创建一个线程去处理这个请求,在这个请求中如果要访问数据库,就可能会产生事务的操作,如果多个事务访问同一条数据,如果不做隔离性,就会出现一些问题。

常见的并发异常:
第一类丢失更新、第二类丢失更新
脏读、不可重复读、幻读

常见的隔离级别:
Read Uncommitted:读取未提交的数据
Read Committed:读取已提交的数据
Repeatable Read:可重复读
Serializable:串行化







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


有关事务的示例:

@Service
//@Scope("prototype") //被Spring容器管理的bean默认是单例的,只会实例化一次,如果想多个实例,需要用scope注解,加上prototype
public class AlphaService {

    @Autowired
    private AlphaDao alphaDao;

    @Autowired
    private UserMapper userMapper;

    @Autowired
    private DiscussPostMapper discussPostMapper;

    public AlphaService() {
        System.out.println("实例化AlphaService");
    }

    @PostConstruct //方法会在构造器之后调用
    public void init() {
        System.out.println("初始化AlphaService");
    }

    @PreDestroy //在销毁对象之前调用,如果对象被销毁就没法调用了
    public void destroy() {
        System.out.println("销毁AlphaService");
    }

    public String find() {
        return alphaDao.select();
    }

    // REQUIRED: 支持当前事务(外部事务),如果不存在则创建新事务.
    // REQUIRES_NEW: 创建一个新事务,并且暂停当前事务(外部事务).
    // NESTED: 如果当前存在事务(外部事务),则嵌套在该事务中执行(独立的提交和回滚),否则就会REQUIRED一样
    // 第二个参数为事务的传播机制,业务方法A可能会调用业务方法B,业务方法A和B可能都会加上@Transactional注解,当A调用B的时候,解决B的事务以哪个为基准的问题
    @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);
        return transactionTemplate.execute(new TransactionCallback() {

            @Override
            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/999.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";
            }
        });
    }

    // 让该方法在多线程的环境下,被异步的调用
    @Async
    public void execute1() {
        logger.debug("execute1");
    }


    public void execute2() {
        logger.debug("execute2");
    }
}
 
5.显示评论 

可以针对帖子评论,也可以回复帖子的评论,所以表中有entity_type和entity_id两个字段

entity包下新建Comment实体类

package com.nowcoder.community.entity;

import java.util.Date;

public class Comment {
    private int id;
    private int userId;
    private int entityType;
    private int entityId;
    private int targetId;
    private String content;
    private int status;
    private Date createTime;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public int getUserId() {
        return userId;
    }

    public void setUserId(int userId) {
        this.userId = userId;
    }

    public int getEntityType() {
        return entityType;
    }

    public void setEntityType(int entityType) {
        this.entityType = entityType;
    }

    public int getEntityId() {
        return entityId;
    }

    public void setEntityId(int entityId) {
        this.entityId = entityId;
    }

    public int getTargetId() {
        return targetId;
    }

    public void setTargetId(int targetId) {
        this.targetId = targetId;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }

    public int getStatus() {
        return status;
    }

    public void setStatus(int status) {
        this.status = status;
    }

    public Date getCreateTime() {
        return createTime;
    }

    public void setCreateTime(Date createTime) {
        this.createTime = createTime;
    }

    @Override
    public String toString() {
        return "Comment{" +
                "id=" + id +
                ", userId=" + userId +
                ", entityType=" + entityType +
                ", entityId=" + entityId +
                ", targetId=" + targetId +
                ", content='" + content + ''' +
                ", status=" + status +
                ", createTime=" + createTime +
                '}';
    }
}

dao层下新建CommentMapper,添加显示评论及数量的方法,使用@Mapper注解,并修改index.html

@Controller
@RequestMapping("/discuss")
public class DiscussPostController implements CommunityConstant {

    @Autowired
    private DiscussPostService discussPostService;

    @Autowired
    private HostHolder hostHolder; //获取当前用户

    @Autowired
    private UserService userService;

    @Autowired
    private CommentService commentService;

	    @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);
        // 点赞数量
        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 commentList = commentService.findCommentsByEntity(
                ENTITY_TYPE_POST, post.getId(), page.getOffset(), page.getLimit());
        // 评论VO列表
        List> commentVoList = new ArrayList<>();
        if (commentList != null) {
            for (Comment comment : commentList) {
                // 评论VO
                Map 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 replyList = commentService.findCommentsByEntity(
                        ENTITY_TYPE_COMMENT, comment.getId(), 0, Integer.MAX_VALUE);
                // 回复VO列表
                List> replyVoList = new ArrayList<>();
                if (replyList != null) {
                    for (Comment reply : replyList) {
                        Map replyVo = new HashMap<>();
                        // 回复
                        replyVo.put("reply", reply);
                        // 作者
                        replyVo.put("user", userService.findUserById(reply.getUserId()));
                        // 回复目标
                        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);

                // 回复数量
                int replyCount = commentService.findCommentCount(ENTITY_TYPE_COMMENT, comment.getId());
                commentVo.put("replyCount", replyCount);

                commentVoList.add(commentVo);
            }
        }

        model.addAttribute("comments", commentVoList);

        return "/site/discuss-detail";
    }

6.添加评论(事务管理)

Dao层:增加评论数据,CommentMapper下新增一个添加评论的方法
更新评论数量,DiscussPostMapper中新增一个更新数量的方法
在对应的mapper.xml文件中书写sql语句

@Mapper
public interface CommentMapper {
    List selectCommentsByEntity(int entityType, int entityId, int offset, int limit);

    int selectCountByEntity(int entityType, int entityId);

    int insertComment(Comment comment);
}

comment-mapper中书写sql语句

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

    
        insert into comment()
        values(#{userId},#{entityType},#{entityId},#{targetId},#{content},#{status},#{createTime})
    

DiscussPostMapper中新增一个更新数量的方法

@Mapper
public interface DiscussPostMapper {

    List selectDiscussPosts(int userId, int offset, int limit, int orderMode); //orderMode为排序模式,如果为0,就按照原先的顺序来排,如果为1 ,就按照热度的顺序来排

    // @Param注解用于给参数取别名,
    // 如果只有一个参数,并且在里使用,则必须加别名.
    int selectDiscussPostRows(@Param("userId") int userId);

    int insertDiscussPost(DiscussPost discussPost);

    DiscussPost selectDiscussPostById(int id);

    int updateCommentCount(int id, int commentCount);
}

discusspost-mapper.xml中书写sql语句

    
        update discuss_post set comment_count = #{commentCount} where id = #{id}
    

Service层:
1)处理添加评论的功能:
CommentService中新增addComment方法,此方法包含两次DML操作,对其进行事务管理,保持其在一个事务范围之内,要么全成功,要么全不成功,这里使用声明式事务

在DiscussPostService先书写更新帖子数量,然后再CommentService里面书写帖子个数。

    public int updateCommentCount(int id, int commentCount) {
        return discussPostMapper.updateCommentCount(id, commentCount);
    }
@Service
public class CommentService implements CommunityConstant {

    @Autowired
    private CommentMapper commentMapper;

    @Autowired
    private SensitiveFilter sensitiveFilter;

    @Autowired
    private DiscussPostService discussPostService;

    public List 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);
    }

    @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;
    }
}

Controller层:
处理添加评论数据的请求:书写方法:addComment

@Controller
@RequestMapping("/comment")
public class CommentController implements CommunityConstant {

    @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;
    }
}

更新discuss-detail.html

7.私信列表


私信指的是我和某些人之间的会话,我和任何人之间的多次会话为一个会话,所以能看到的一定是不同的人,针对某个会话点进去,才能看到详细的私信,对应community.message表,表中包含的字段有from_id消息的发送人,to_id消息的接收者,content内容,状态status,0表示未读,1表示已读,2表示删除,create_time表示创建时间,conversation_id表示会话id

Entity包下新建一个实体类Message

public class Message {
    private int id;
    private int fromId;
    private int toId;
    private String conversationId;
    private String content;
    private int status;
    private Date createTime;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public int getFromId() {
        return fromId;
    }

    public void setFromId(int fromId) {
        this.fromId = fromId;
    }

    public int getToId() {
        return toId;
    }

    public void setToId(int toId) {
        this.toId = toId;
    }

    public String getConversationId() {
        return conversationId;
    }

    public void setConversationId(String conversationId) {
        this.conversationId = conversationId;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }

    public int getStatus() {
        return status;
    }

    public void setStatus(int status) {
        this.status = status;
    }

    public Date getCreateTime() {
        return createTime;
    }

    public void setCreateTime(Date createTime) {
        this.createTime = createTime;
    }

    @Override
    public String toString() {
        return "Message{" +
                "id=" + id +
                ", fromId=" + fromId +
                ", toId=" + toId +
                ", conversationId='" + conversationId + ''' +
                ", content='" + content + ''' +
                ", status=" + status +
                ", createTime=" + createTime +
                '}';
    }
}

dao层下添加MessageMapper类。

@Mapper
public interface MessageMapper {

    //查询当前用户的会话列表,针对每个会话只返回一条最新的私信
    List selectConversations(int userId, int offset, int limit);

    //查询当前用户的会话数量
    int selectConversationCount(int userId);

    //查询某个会话所包含的私信列表
    List selectLetters(String conversationId, int offset, int limit);

    //查询某个会话所包含的私信数量
    int selectLetterCount(String conversationId);

    //查询未读私信的数量
    int selectLetterUnreadCount(int userId, String converationId);
}

message-mapper.xml中书写sql语句





    
        id
        , from_id, to_id, conversation_id, content, status, create_time
    

    
        select
        
        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
        
        from message
        where status != 2
        and from_id != 1
        and conversation_id = #{conversationId}
        order by id desc
        limit #{offset}, #{limit}
    

    
        select count(id)
        from message
        where status = 0
        and from_id != 1
        and to_id = #{userId}
        
            and conversation_id = #{conversationId}
        
    

service层,添加MessageService类

@Service
public class MessageService {

    @Autowired
    private MessageMapper messageMapper;

    public List findConversations(int userId, int offset, int limit) {
        return messageMapper.selectConversations(userId, offset, limit);
    }

    public int findConversationCount(int userId) {
        return messageMapper.selectConversationCount(userId);
    }

    public List 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);
    }
}

Controller层,添加MessageController类,并修改https://blog.csdn.net/qq_42044278/article/details/letter.html

```java
@Controller
public class MessageController {
	@Autowired
    private MessageService messageService;

    @Autowired
    private HostHolder hostHolder;

	    // 私信列表
    @RequestMapping(path = "/letter/list", method = RequestMethod.GET)
    public String getLetterList(Model model, Page page) {
//        Integer.valueOf("abc");
        User user = hostHolder.getUser();
        // 分页信息
        page.setLimit(5);
        page.setPath("/letter/list");
        page.setRows(messageService.findConversationCount(user.getId()));
        //会话列表
        List conversationList = messageService.findConversations(
                user.getId(), page.getOffset(), page.getLimit());
        List> conversations = new ArrayList<>();
        if (conversations != null) {
            for (Message message : conversationList) {
                Map 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 letterList = messageService.findLetters(conversationId, page.getOffset(), page.getLimit());
        List> letters = new ArrayList<>();
        if (letterList != null) {
            for (Message message : letterList) {
                Map 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 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);
        }
    }
}

修改letter-detail.html。

8.发送私信


发私信是指在消息页面,点击发送私信,在弹出框里填写发给谁,以及发送的内容,用异步请求的发生发送给服务器,服务器返回结果以后,判断成功还是失败,最终给一个提示就好了,除此之外,我们在进入详情页面的时候,点击给Ta私信,要自动带上当前对话对方的名字,填写内容,点击发送就可以了。

数据访问层:MessageMapper中新增发送私信及修改消息状态的方法(设置为已读)。

    //新增消息
    int insertMessage(Message message);

    //修改消息的状态
    int updateStatus(List ids, int status);

message-mapper.xml中实现sql语句

    
         from_id, to_id, conversation_id, content, status, create_time
    
    
    
        insert into message()
        values (#{fromId},#{toId},#{conversationId},#{content},#{status},#{createTime})
    

    
        update message set status = #{status}
        where id in
        
            #{id}
        
    

业务层:MessageService中新增addMessage方法和readMessage方法

@Service
public class MessageService {

    @Autowired
    private MessageMapper messageMapper;

    @Autowired
    private SensitiveFilter sensitiveFilter;

    public List findConversations(int userId, int offset, int limit) {
        return messageMapper.selectConversations(userId, offset, limit);
    }

    public int findConversationCount(int userId) {
        return messageMapper.selectConversationCount(userId);
    }

    public List 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);
    }

    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 ids) {
        return messageMapper.updateStatus(ids, 1);
    }
}

userService层增加一个根据用户名查找的方法

    public User findUserByName(String username) {
        return userMapper.selectByName(username);
    }

表现层,messageController新增sendLetter方法,处理用户点击发送私信的请求
接收前端传来的用户名和内容,拼接conversationId的时候把小的拼在前面

@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.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);
    }

然后实现把未读消息改成已读。
在之前写的加载消息列表的方法里补充一下。首先写一个方法获取所有未读消息的id。

private List getLetterIds(List letterList){
        List 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;
    }

然后在getLetterDetail方法里加上

        //设置已读
        List ids =getLetterIds(letterList);
        if (!ids.isEmpty()){
            messageService.readMessage(ids);
        }

修改https://blog.csdn.net/qq_42044278/article/details/letter.html、letter-detail.html及letter.js

9.统一处理异常


在controller层中添加advice包,添加ExceptionAdvice类 HomeController中添加getErrorPage方法

@ControllerAdvice(annotations = Controller.class)
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)) {
            response.setContentType("application/plain;charset=utf-8");
            PrintWriter writer = response.getWriter();
            writer.write(CommunityUtil.getJSONString(1, "服务器异常"));
        } else {
            response.sendRedirect(request.getContextPath() + "/error");
        }
    }
}
10.统一记录日志


现在已经开发了几个模块,将来系统可能越来越完善,模块会越来越多,就会有越来越多的Service,就比如说现在会有一个需求,我想对所有的Service记录日志,既然通知和拦截器解决不了问题,那么使用传统的方式也可以,就是把记录日志的代码封装到组件里,然后在不同的Service方法里去调用就可以了,如果将来有一天系统需求发生了变化,比如说我想在后面记录日志,而不是在前面记录日志,那么这种情况下,有多少Service就要改多少次系统需求,那么这个时候就可以使用AOP来代替传统的OOP。




在controller层中添加aspect包,创建ServiceLogAspect类

@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],在[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));
    }
}
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/1035339.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号