这个项目的整体结构来源于牛客网,主要使用了Springboot、Mybatis、MySQL、Redis、Kafka、等工具。主要实现了用户的注册、登录、发帖、点赞、系统通知、按热度排序、搜索等功能。另外引入了redis数据库来提升网站的整体性能,实现了用户凭证的存取、点赞关注的功能。基于 Kafka 实现了系统通知:当用户获得点赞、评论后得到通知。利用定时任务定期计算帖子的分数,并在页面上展现热帖排行榜。
1、完成软件系统代码的实现,编写代码注释和开发文档; 2、辅助进行系统的功能定义,程序设计; 3、根据设计文档或需求说明完成代码编写,调试,测试和维护;
服务器分为表现层/业务层/数据层,其中Spring MVC是工作在表现层,作用是接收/解析用户发送的请求,调用对应的业务类,根据业务类返回的结果(ModelAndView),调用view进行视图渲染,并将渲染后的View返回给请求者。具体分为以下8步:
客户端(浏览器)发送请求给前端处理器(DispatcherServlet)(发送请求,响应结果);
DispatcherServlet根据请求信息调用HandlerMapping,查找到对应的Handler;
查找到对应的Handler(也就是Controller)后,由HandlerAdapter适配器处理;
HandlerAdapter根据Handler来调用真正的Controller;
Controller进行业务处理,返回ModelAndView对象,Model是数据对象,View是逻辑上的View;
ViewResolver根据逻辑view找到实际view;
DispatcherServlet把Model传给view进行视图渲染,然后返回给请求者。
mvc三层架构:
C - Controller:控制器。接受用户请求,调用 Model 处理,然后选择合适的View给客户。
M - Model:模型。业务处理模型,接受Controller的调遣,处理业务,处理数据。
V - View:视图。返回给客户看的结果。
对Spring IoC的理解:
IoC的意思是控制反转,是一种设计思想,把需要在程序中手动创建对象的控制权交给了Spring框架。IoC的载体是IoC容器,本质是一个工厂,数据结构上来看是一个Map,用来存放着各种对象。当我们创建一个对象时,只需要配置好配置文件/注解,而不用担心对象是怎么被创建出来的。
IoC的优点:降低耦合,对象被容器管理需要两份数据:你的对象定义 + 配置文件,对象间的关系体现在配置文件,不会直接产生耦合。
user_id对应的是发评论的用户,entity_type是指评论的类型,论坛部分,有两种类型,对帖子的评论和对评论的评论,为了方便区分,对评论的评论我们成为回复,entity_id对应回复的实体的id,target_id也就是回复的对象,这个主要是在回复的时候需要显示回复的谁。
private int id;
private int userId;
private int entityType;
private int entityId;
private int targetId;
private String content;
private int status;
private Date createTime;
一个是查询评论,一个是查询评论数量,这里也需要用到分页查询,用了offset和limit
之前这个方法返回了帖子和作者的数据,因为评论需要分页,所以传入page对象。
对page对象进行配置,一页显示5条,page的路径和总的评论数
首先用上面写的方法查询评论,放到list里,然后还需要进行一些处理。查询到的评论里只有user_id,没有用户名,同时评论下还有回复。
VO代表显示对象,用来显示在页面上的对象。
整个逻辑就是查出当前帖子下的所有评论,遍历所有评论,处理用户名等信息,查询评论下的回复,遍历每个回复,处理用户名等信息。
@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);
//评论的分页信息
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
根据请求来拆解功能 1,打开注册网页 2,把注册的信息发送给服务器(点注册) 3,把激活邮件发送给邮箱 4,利用激活链接打开网页
每一次请求都是先开发数据访问层,在开发业务层,最后开发视图层(三层架构),但是每一次请求不一定要用到这三层
redis的使用
概念:redis是一个非关系型数据库,数据存储在内存中,读写速度快。可以存储键和五种不同类型值的映射。只能以字符串为键,值支持:字符串,列表,无序集合,有序集合,hash散列表。
使用redis存储验证码:
因为验证码需要频繁的进行访问与刷新,因此对性能的要求较高; 验证码不需要永久保存,通常在很短的时间后就会失效; 分布式部署的时候,存在session共享的问题。
使用redis存储登录凭证:因为后台在每次处理请求的时候都要查询用户的登录凭证,访问的频率非常高,因此需要使用redis存储。
使用redis缓存用户信息: 因为后台在每次处理请求的时候都要根据用户的凭证查询用户信息,访问的频率非常高。
Redis可以使用zset对需要排序的数据进行自定义的排序。
怎样存储的点赞/关注/缓存用户数据:
点赞:点赞key是实体类型实体id,vlaue存的是登录者的用户id,使用的数据类型是set无序集合
存储
关注:使用zSet类型存储,key为被关注者,value保存关注者以及关注时间为score
缓存用户数据:使用Value类型,key为用userID得到的key,value为user对象(设置过期时间,且数据修改时需要清除缓存)
一般我们来使用redis做缓存,那么redis如何与数据库配合,使得我们的项目质量更高呢。我们一般将用户访问频繁,且修改频度低的数据放在缓存中,以提高响应速度。在前端发来访问请求时,我们一般进行以下逻辑操作:
查询操作:
前端发来请求时,先进行缓存的查询,如果缓存存在要查询的数据,则返回。否则去数据库中查询,并添加到缓存中,再返回数据,这样在下次查询时,便可直接从缓存中取。
添加操作:
添加操作我们直接添加到数据库即可,也可以在添加到缓存的同时添加到数据库。但在数据量较大时,推荐的做法是先将数据添加到缓存,在另一个线程中将数据同步到数据库。
修改操作:
修改操作先修改数据库,再将缓存的数据删除即可,不要直接更新缓存,效率太低。
注意:本文仅仅适用于普通web项目,对于高并发项目,需考虑数据同步问题。
kafka的使用
Kafka简介:Kafka是一种消息队列,主要用来处理大量数据状态下的消息队列,一般用来做日志的处理,是一个分布式、支持分区的(partition)、多副本的(replica),基于zookeeper协调的分布式消息系统,它的最大的特性就是可以实时的处理大量数据以满足各种需求场景。
当有点赞,评论,关注请求时,会发送系统通知点赞,评论,关注的对象。在处理系统信息时,使用到了Kafka。
具体来说,先定义了生产者类和消费者类,其中生产者被点赞/评论/关注功能对应的Controller使用,产生消息。而消费者负责消息(message)到来时,把消息存到数据库内。
ES的使用
项目扩展在进行帖子搜索时,使用到了ES。可用Repository和Template两种方式,由于Repository搜索到的结果(直接返回的post类,方便)没有高亮标签(why),所以使用了template方式重写了mapResults函数,获得了带有高亮标签的post。 搜索:定义SearchQuery,确定搜素内容,排序方式,高亮等。接着使用elasticTemplate.queryForPage方法,需要重写mapResults函数,得到高亮数据。
是怎样实现统一捕获异常的? 在SpringBoot的项目某一路径下,加上对应的错误页面,发生错误时自动会跳转。服务器的三层结构中,错误会层层向上传递,所以只需要在表现层(controller)统一处理错误即可。 方法:在controller中加上advice包,并通过注解@ControllerAdvice和@ExceptionHandler,统一捕获异常。
是怎样实现统一记录日志的? 使用了AOP技术(面向切面编程),这里使用到的是SpringAOP。 AOP技术能够将哪些与业务,但是为业务模块共同调用的逻辑或责任(比如事务处理,日志记录,权限控制等),封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的扩展性和维护性。
SpringAOP本质上基于动态代理,当要代理的对象实现了某接口,会使用JDK动态代理,在运行时通过创建接口的代理实例,织入代码。当要代理的对象没有实现接口,则使用Cglib技术(编译时增强),通过子类代理织入代码。
1、使用索引 尽量避免全表扫描,首先应考虑在where及order by,group by涉及的列上建立索引。
2、优化SQL语句 通过explain来查看SQL语句的执行效果,可以帮助选择更好的索引和优化查询语句。例如:
explain select * from news;
不要返回用不到的字段 ,不在索引列做运算或者使用函数 ,查询尽可能使用limit减少返回的行数,减少数据传输时间和带宽浪费。
3、优化数据库对象 优化表的数据类型
select * from 表名 procedure analyse();
对表进行拆分: 可以提高表的访问效率。有两种拆分方法: 垂直拆分:把主键和一些列放在一个表中,然后把主键和另外的列放在另一个表中。 使用场景:如果一个表中某些列常用,另外一些不常用,就可以垂直拆分。 水平拆分:根据一列或者多列数据的值把数据行放到两个独立的表中。 使用中间表来提高查询速度: 创建中间表,表结构和源表结构完全相同,转移要统计的数据到中间表,然后在中间表上进行统计,得出想要的结果。
点赞、关注、缓存用户的信息,通过redis这一个非关系型数据库,将数据存储在内存中,读写速度快,满足高性能的要求。
spring有很多模块组成,利用这些模块可以方便开发工作。
这些模块是:核心容器(spring core)/数据访问和集成(Spring JDBC)/Web(Spring Web/MVC)/AOP(Spring Aop)/消息模块/测试模块(Spring Test)等。
QPS(TPS):每秒钟request/事务 数量
并发数: 系统同时处理的request/事务数
响应时间: 一般取平均响应时间
由公式:QPS(TPS)= 并发数/平均响应时间 可以看出,要提高qps,我们必须做2个方面努力
增加并发数:
1.比如增加tomcat并发的线程数,开喝服务器性能匹配的线程数,可以更多满足服务请求。
2.增加数据库的连接数,预建立合适数量的TCP连接数。
3.后端服务尽量无状态话,可以更好支持横向扩容,满足更大流量要求。
4.调用链路上的各个系统和服务尽量不要单点,要从头到尾都是能力对等的,不能让其中某一点成为瓶颈。
5.RPC调用的尽量使用线程池,预先建立合适的连接数。
减少平均响应时间:
1.请求尽量越前结束,越好,这样压力就不要穿透到后面的系统上,可以在各个层上加上缓存
2.流量消峰。放行适当的流量,处理不了的请求直接返回错误或者其他提示。和水坝道理很类似
3.减少调用链
4.优化程序
5.减少网络开销,适当使用长连接
6.优化数据库,建立索引