• 仿牛客网项目---社区首页的开发实现


    从今天开始我们来写一个新项目,这个项目是一个完整的校园论坛的项目。主要功能模块:用户登录注册,帖子发布和热帖排行,点赞关注,发送私信,消息通知,社区搜索等。这篇文章我们先试着写一下用户的登录注册功能。

    我们做web项目,一般web项目是主要解决浏览器和服务器之间交互的问题。而浏览器和服务器是由一次一次的请求交互的。因此,任何功能都可拆解成若干次请求,其实只要掌握好每次请求的执行过程,按照步骤开发每一次请求,基本上项目就可以逐步完善起来。

    一次请求的执行过程:

    其实最好是可以把功能做拆解,第一步先实现什么效果,第二步再完善这个效果。因为你不可能一下子就一步到位写出完整的代码的,是吧?

    所以这就是我的思路,我是想着一个功能一个功能的搞定。这篇文章来搞定开发社区首页。

    首页有什么?不就是帖子,还有用户头像,还有分页嘛,因此接下来我们就实现这三个功能。

    社区首页模块大致结构

    entity文件夹:Page.java

    DAO层:DiscussionMapper

    Service层:DiscussionPostMapper

    Controller层:HomeController

    Dao层---DiscussPostMapper

    1. @Mapper
    2. public interface DiscussPostMapper {
    3. List selectDiscussPosts(int userId, int offset, int limit, int orderMode);
    4. // @Param注解用于给参数取别名,
    5. // 如果只有一个参数,并且在里使用,则必须加别名.
    6. int selectDiscussPostRows(@Param("userId") int userId);
    7. int insertDiscussPost(DiscussPost discussPost);
    8. DiscussPost selectDiscussPostById(int id);
    9. int updateCommentCount(int id, int commentCount);
    10. int updateType(int id, int type);
    11. int updateStatus(int id, int status);
    12. int updateScore(int id, double score);
    13. }

    这是一个名为DiscussPostMapper的接口,用于定义帖子相关的数据库操作方法 :

    1. selectDiscussPosts(int userId, int offset, int limit, int orderMode):根据用户ID、偏移量、限制数量和排序方式从数据库中查询帖子列表。

    2. selectDiscussPostRows(int userId):查询帖子总数。根据用户ID统计数据库中的帖子数量。

    3. insertDiscussPost(DiscussPost discussPost):将帖子信息插入数据库。

    4. selectDiscussPostById(int id):根据帖子ID从数据库中查询帖子信息。

    5. updateCommentCount(int id, int commentCount):更新帖子的评论数量。根据帖子ID更新帖子的评论数量信息。

    6. updateType(int id, int type):更新帖子的类型。根据帖子ID更新帖子的类型信息。

    7. updateStatus(int id, int status):更新帖子的状态。根据帖子ID更新帖子的状态信息。

    8. updateScore(int id, double score):更新帖子的分数。根据帖子ID更新帖子的分数信息。

    Service层---DiscussPostService

    1. @Service
    2. public class DiscussPostService {
    3. @PostConstruct
    4. public void init() {
    5. // 初始化帖子列表缓存
    6. postListCache = Caffeine.newBuilder()
    7. .maximumSize(maxSize)
    8. .expireAfterWrite(expireSeconds, TimeUnit.SECONDS)
    9. .build(new CacheLoader>() {
    10. @Nullable
    11. @Override
    12. public List load(@NonNull String key) throws Exception {
    13. if (key == null || key.length() == 0) {
    14. throw new IllegalArgumentException("参数错误!");
    15. }
    16. String[] params = key.split(":");
    17. if (params == null || params.length != 2) {
    18. throw new IllegalArgumentException("参数错误!");
    19. }
    20. int offset = Integer.valueOf(params[0]);
    21. int limit = Integer.valueOf(params[1]);
    22. // 二级缓存: Redis -> mysql
    23. logger.debug("load post list from DB.");
    24. return discussPostMapper.selectDiscussPosts(0, offset, limit, 1);
    25. }
    26. });
    27. // 初始化帖子总数缓存
    28. postRowsCache = Caffeine.newBuilder()
    29. .maximumSize(maxSize)
    30. .expireAfterWrite(expireSeconds, TimeUnit.SECONDS)
    31. .build(new CacheLoader() {
    32. @Nullable
    33. @Override
    34. public Integer load(@NonNull Integer key) throws Exception {
    35. logger.debug("load post rows from DB.");
    36. return discussPostMapper.selectDiscussPostRows(key);
    37. }
    38. });
    39. }
    40. public List findDiscussPosts(int userId, int offset, int limit, int orderMode) {
    41. if (userId == 0 && orderMode == 1) {
    42. return postListCache.get(offset + ":" + limit);
    43. }
    44. logger.debug("load post list from DB.");
    45. return discussPostMapper.selectDiscussPosts(userId, offset, limit, orderMode);
    46. }
    47. public int findDiscussPostRows(int userId) {
    48. if (userId == 0) {
    49. return postRowsCache.get(userId);
    50. }
    51. logger.debug("load post rows from DB.");
    52. return discussPostMapper.selectDiscussPostRows(userId);
    53. }
    54. public int addDiscussPost(DiscussPost post) {
    55. if (post == null) {
    56. throw new IllegalArgumentException("参数不能为空!");
    57. }
    58. // 转义HTML标记
    59. post.setTitle(HtmlUtils.htmlEscape(post.getTitle()));
    60. post.setContent(HtmlUtils.htmlEscape(post.getContent()));
    61. // 过滤敏感词
    62. post.setTitle(sensitiveFilter.filter(post.getTitle()));
    63. post.setContent(sensitiveFilter.filter(post.getContent()));
    64. return discussPostMapper.insertDiscussPost(post);
    65. }
    66. public DiscussPost findDiscussPostById(int id) {
    67. return discussPostMapper.selectDiscussPostById(id);
    68. }
    69. public int updateCommentCount(int id, int commentCount) {
    70. return discussPostMapper.updateCommentCount(id, commentCount);
    71. }
    72. public int updateType(int id, int type) {
    73. return discussPostMapper.updateType(id, type);
    74. }
    75. public int updateStatus(int id, int status) {
    76. return discussPostMapper.updateStatus(id, status);
    77. }
    78. public int updateScore(int id, double score) {
    79. return discussPostMapper.updateScore(id, score);
    80. }
    81. }

    这是一个名为DiscussPostService的服务类,用于处理帖子相关的业务逻辑。这段代码是真的烦,一大堆东西,不过我总结了一下,大概这段代码的功能差不多是这样的:

    1. 通过DiscussPostMapper进行数据库操作,包括查询帖子列表、帖子总数,插入帖子,根据帖子ID查询帖子信息,以及更新帖子的评论数量、类型、状态和分数。

    2. 使用SensitiveFilter进行敏感词过滤,对帖子的标题和内容进行转义和过滤。

    3. 使用Caffeine缓存库对帖子列表和帖子总数进行缓存,提高访问性能。缓存设置了最大容量和过期时间。

    4. 在初始化方法init()中,配置了帖子列表缓存和帖子总数缓存的加载方式,当缓存中不存在数据时,会从数据库中加载数据。

    5. 提供了方法如findDiscussPosts()findDiscussPostRows()来获取帖子列表和帖子总数,如果缓存中有数据,则直接从缓存中获取,否则从数据库中获取。

    6. 提供了方法如addDiscussPost()来添加帖子,对帖子的标题和内容进行处理后插入数据库。

    7. 该服务类使用了注解@Service,表示该类是一个Spring的服务组件。

    Controller层---HomeController

    1. @Controller
    2. public class HomeController implements CommunityConstant {
    3. @RequestMapping(path = "/", method = RequestMethod.GET)
    4. public String root() {
    5. return "forward:/index";
    6. }
    7. @RequestMapping(path = "/index", method = RequestMethod.GET)
    8. public String getIndexPage(Model model, Page page,
    9. @RequestParam(name = "orderMode", defaultValue = "0") int orderMode) {
    10. // 方法调用前,SpringMVC会自动实例化Model和Page,并将Page注入Model.
    11. // 所以,在thymeleaf中可以直接访问Page对象中的数据.
    12. page.setRows(discussPostService.findDiscussPostRows(0));
    13. page.setPath("/index?orderMode=" + orderMode);
    14. List list = discussPostService
    15. .findDiscussPosts(0, page.getOffset(), page.getLimit(), orderMode);
    16. List> discussPosts = new ArrayList<>();
    17. if (list != null) {
    18. for (DiscussPost post : list) {
    19. Map map = new HashMap<>();
    20. map.put("post", post);
    21. User user = userService.findUserById(post.getUserId());
    22. map.put("user", user);
    23. long likeCount = likeService.findEntityLikeCount(ENTITY_TYPE_POST, post.getId());
    24. map.put("likeCount", likeCount);
    25. discussPosts.add(map);
    26. }
    27. }
    28. model.addAttribute("discussPosts", discussPosts);
    29. model.addAttribute("orderMode", orderMode);
    30. return "/index";
    31. }
    32. @RequestMapping(path = "/error", method = RequestMethod.GET)
    33. public String getErrorPage() {
    34. return "/error/500";
    35. }
    36. @RequestMapping(path = "/denied", method = RequestMethod.GET)
    37. public String getDeniedPage() {
    38. return "/error/404";
    39. }
    40. }

    这是一个名为HomeController的控制器类,用于处理主页相关的请求。

    1. 通过DiscussPostService获取帖子相关的信息,包括帖子列表和帖子总数。

    2. 通过UserService获取用户相关的信息,包括发帖用户的信息。

    3. 通过LikeService获取帖子的点赞数量。

    4. 提供了root()方法,将根路径的请求转发到"/index"路径。

    5. 提供了getIndexPage()方法,处理主页的GET请求,根据页面参数orderMode获取帖子列表,并将数据添加到Model中供前端页面渲染。

    entity文件夹---Page

    1. /**
    2. * 封装分页相关的信息.
    3. */
    4. public class Page {
    5. // 当前页码
    6. private int current = 1;
    7. // 显示上限
    8. private int limit = 10;
    9. // 数据总数(用于计算总页数)
    10. private int rows;
    11. // 查询路径(用于复用分页链接)
    12. private String path;
    13. public int getCurrent() {
    14. return current;
    15. }
    16. public void setCurrent(int current) {
    17. if (current >= 1) {
    18. this.current = current;
    19. }
    20. }
    21. public int getLimit() {
    22. return limit;
    23. }
    24. public void setLimit(int limit) {
    25. if (limit >= 1 && limit <= 100) {
    26. this.limit = limit;
    27. }
    28. }
    29. public int getRows() {
    30. return rows;
    31. }
    32. public void setRows(int rows) {
    33. if (rows >= 0) {
    34. this.rows = rows;
    35. }
    36. }
    37. public String getPath() {
    38. return path;
    39. }
    40. public void setPath(String path) {
    41. this.path = path;
    42. }
    43. /**
    44. * 获取当前页的起始行
    45. *
    46. * @return
    47. */
    48. public int getOffset() {
    49. // current * limit - limit
    50. return (current - 1) * limit;
    51. }
    52. /**
    53. * 获取总页数
    54. *
    55. * @return
    56. */
    57. public int getTotal() {
    58. // rows / limit [+1]
    59. if (rows % limit == 0) {
    60. return rows / limit;
    61. } else {
    62. return rows / limit + 1;
    63. }
    64. }
    65. /**
    66. * 获取起始页码
    67. *
    68. * @return
    69. */
    70. public int getFrom() {
    71. int from = current - 2;
    72. return from < 1 ? 1 : from;
    73. }
    74. /**
    75. * 获取结束页码
    76. *
    77. * @return
    78. */
    79. public int getTo() {
    80. int to = current + 2;
    81. int total = getTotal();
    82. return to > total ? total : to;
    83. }
    84. }

    这个实体类用于在分页查询中保存分页相关的信息,例如当前页码、每页显示的数量、数据总数等,以便在前端页面进行分页展示和生成分页链接。

    至此,社区首页的一些简单的功能就实现了。其实这并不复杂,就是代码有点多而已。

  • 相关阅读:
    c++对接CAT1400
    matlab写shpfile文件(字段名改为中文)
    MySQL (6)
    12-1- GAN -简单网络-线性网络
    单目标应用:基于螳螂搜索算法(Mantis Search Algorithm,MSA)的微电网优化调度MATLAB
    【光学】Matlab实现色散曲线拟合
    vue 概述
    第十四届蓝桥杯省赛C++B组D题【飞机降落】题解(AC)
    蓝牙协议栈基础学习笔记
    iOS自定义滚动条
  • 原文地址:https://blog.csdn.net/qq_54432917/article/details/136321163