本文复习做过的项目
1. 基于SpringBoot的博客系统
1.1 利用线程池实现了文章阅读次数的更新以及登录日志的记录
在点击文章详情的时候,从整体上会发送两次请求,一次是获取文章的详细内容,一次是获取该文章的评论内容。
获取评论内容,无需过多解释,就是查询ms_comments数据表即可。
获取文章详细内容,除了获取文章体之外,还需要获取文章的附加信息(比如创建时间,阅读次数,评论等等),另外还需要获取所属类别、包含标签等信息。
因为是文章详情,也就是阅读文章,所以阅读次数需要后台增加1。但是这种操作和前端没什么太大的关系,而前端只需要尽可能快的看到文章内容和评论,因此只需要尽快返回这些结果即可,而增加阅读次数可以交给后台开启多线程默默运行。
因此,这里采用线程池进行阅读次数的更新。首先要在SpringBoot容器中创建线程对象。采用Bean注解创建对象。主要有以下步骤:
添加@EnableAsync注解,使得SpringBoot开启异步
创建线程池对象,这里以Configuration配置文件中的方法形式创建线程池对象,并以@Bean(“taskExecutor”)形式创建对象,并命名(如果不命名,那么就是方法名是线程池对象名)。如下所示:
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
// 开启线程池
public class ThreadPoolConfig {
// 创建对象,注入到SpringBoot容器中
public Executor asyncServiceExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
// 设置核心线程数
executor.setCorePoolSize(5);
// 设置最大线程数
// 对于CPU密集型,可设置为CPU核数加1
// Runtime.getRuntime().availableProcessors()
executor.setMaxPoolSize(20);
// 配置队列大小
executor.setQueueCapacity(Integer.MAX_VALUE);
// 设置线程活跃时间(秒)
executor.setKeepAliveSeconds(60);
// 设置默认线程名称
executor.setThreadNamePrefix("浮云笔记");
// 等待所有任务结束后再关闭线程池
executor.setWaitForTasksToCompleteOnShutdown(true);
// 执行初始化
executor.initialize();
return executor;
}
}定义service方法【也可先定义接口】,本质上和service层没什么区别,只不过这里的service不再是此时请求线程负责处理;而是交给线程池来处理。因此,需要在service方法上面加入@Async(“taskExecutor”)注解,注解的值就是要交给的线程池对象。代码如下所示:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class ThreadService {
// 声明本任务采用的线程池
public void updateArticleViewCount(ArticleMapper articleMapper, Article article) {
// 在线程池中更新操作,不会影响原有的主线程。
Integer viewCounts = article.getViewCounts();
// 注意,因为多线程的缘故,为了线程安全,防止读脏数据
// 即当前更新操作,必须用子查询的方式来更新。
int i = articleMapper.updateByPrimaryKeyAndMul(article.getId());
}
}之后,就像调用其他service一样,只需要调用方法即可。
总结,其实开启多线程核心就是创建线程池对象并将其注入到容器中,而如果想要将某个任务(方法)放到线程池去执行,只需要添加@Async(“taskExecutor”)注解以及指明线程池对象即可。
因为存在多线程,所以对于阅读次数更新,采用子查询,在现有次数的基础上进行加一。
创建线程池,SpringBoot框架也封装了一个线程池类ThreadPoolTaskExecutor,这个类和Java中原生的ThreadPoolExecutor类有什么区别呢?
ThreadPoolTaskExecutor继承了ExecutorConfigurationSupport抽象类,而这个抽象类中有一个initialize方法,这个方法调用了initializeExecutor抽象方法,参数为线程工厂和拒绝策略。
而ThreadPoolTaskExecutor类实现了initializeExecutor方法。并且有一个ThreadPoolExecutor属性,在initializeExecutor方法里面实例化ThreadPoolExecutor属性。
也就是说,ExecutorConfigurationSupport提供了线程池创建的一些必备参数,这些参数,调用者无需给出,只需要给出一些基本的参数即可。调用者只需调用ThreadPoolTaskExecutor创建对象,然后调用ExecutorConfigurationSupport的init方法进行初始化线程池即可。
补充:上面只是其中一种方式,SpringBoot实现线程池还有另一种方式,继承AsyncConfigurer接口,直接在方法上面使用@Async,而无需再指明线程池对象。
其实这两种都是可以的,默认情况下,只要使用了@Configuration和@EnableAsync注解,即开启了异步,那么SpringBoot就是开始搜索TaskExecutor类对象或者名为“taskExecutor”的Executor对象。
所以说,上面实现了AsyncConfigurer接口的对象,必须返回值属于TaskExecutor及其子类对象,比如ThreadPoolTaskExecutor。而第一种方式,则可以返回Executor子类,但是必须对象名为taskExecutor,或者和上面方法类似,不起名字,但是返回值类型为ThreadPoolTaskExecutor。
而@Async注解则是将方法的执行加入到线程池中。另外,还有第三种方式。
@EnableAsync 详解,@Async 如何生效_Oxye的博客-CSDN博客_@enableasync
SpringBoot:详解@EnableAsync + @Async 实现共享线程池_Oxye的博客-CSDN博客_enableasync 线程池
除了上面两种,其实SpringBoot默认注册了线程池,可直接在配置文件中打开默认配置。总体上说有三种方式实现线程池:【SpringBoot】@Async、AsyncConfigurer源码解析_qq_三哥啊的博客-CSDN博客,springboot2异步线程池配置_MiaoO的博客-CSDN博客
1 | # 异步线程池 |
1.2 利用JWT技术实现了用户安全登录
http是无连接、无状态的协议,客户端的每次请求对于服务器来说,都是一次未知的请求。
每一次请求,服务器都会创建一个线程来处理这个请求。对于Service层来说,对于写文章、写评论等功能,存储到数据库中,是需要作者信息的。那么怎么获取呢?显然需要从请求中来获取作者信息的。但是service层一般来说是处理业务的,如果从controller中传递对象参数,有点不太合适。而拦截器会对特定的请求进行拦截判断用户是否登录,因此,可在拦截判断的时候,如果放行,则将作者信息添加到ThreadLocal线程局部变量中。这样在方法中无需传递参数就可获取到作者信息。而ThreadLocal类,采用单例模式,饿汉式static创建。因为作为线程局部变量,本来就只有一份。为了避免多次创建工具类对象,所以设置为static。
而拦截器拦截判断,则是拦截请求,那么如何判断用户是否登录呢?传统的方式有cookie、session。cookie是HTTP协议规定的,每次请求都会携带cookie。因此在登录成功之后,可将用户信息存储在cooklie中,返回给客户端。之后客户端每次请求都携带这个cookie。但是cookie是明文,很有可能被伪造。并且如果客户端禁用了cookie,也就无法实现登录验证了。所以,此方法是比较行不通的。
另外,服务器会针对客户端的请求创建session对象,这个session可以连接多次request请求。此时这就相当于将多次request进行连接,多次reqeust可通过session进行通信,即会话域。session对应request如下:
(在客户端允许cookie的情况下),客户端的每次请求都会携带cookie,如果是第一次,cookie为空,此时服务器就会创建cookie,并创建session对象,并将生成的cookie与session进行绑定。这时候,之后的客户端每次请求都会携带cookie,进而服务器可找到与之对应的session,这样就能将多次request与session进行对应。
那么之后,该request就会自动消失,但是cookie已经随着响应到达了客户端,之后的其他request会自动携带该cookie。那么之后,其他request也就会和服务器中的该session对应上了。
如果一定时间内,没有reqeust与session对应,那么就会自动回收session,即一次会话结束。
总体上说,客户端通过cookie,表明从本客户端发送出的多个request都是属于本客户端的。而服务器则通过session表明收到的这几个reqeust是属于同一个客户端的。
由于cookie存储用户信息,可容易被伪造。那么可不可以将用户信息存储在session中呢?
可在session中存储用户名,因为request和session已经通过cookie关联了,可通过request找到session。那么首次通过账号密码登录成功后,服务器可在session中存储该用户名,方便后续验证,后续只要session中有用户名,就说明之前已经成功登录过,即认证了该请求【可通过拦截器实现】。
但是这样在服务器中存储数据,显然随着访问量的增加,服务器的压力变大。
另外,由于cookie是存在一个客户端上,而session则也是一台服务器上,如果是分布式架构,并且负载均衡,显然需要服务器之间共享session数据,否则cookie对应的session只能在那一台服务器上识别。
共享数据的话,就需要存储到数据库,可利用redis做缓存数据库。但是安全方面,cookie和session还是没办法做到。其实本质上说,cookie就是无法保证传过来的签名信息是权威的(可参考国科大网络安全李杨老师课上讲解的加解密、权威认证部分)。
因此,为了保证cookie的信息是权威的,可由服务器指定加密方式,对cookie进行加密处理。因此,可对上面cookie中的唯一标识信息(比如用户名)进行加密。这便是token,可经token存放在header中。而JWT是实现token的一种方式。JWT有三部分组成:A.B.C:
- A:Header,{“type”: “JWT”, “alg”: “HS256”},固定内容
- B:payload,存放信息,比如,用户id、过期时间等等,可以被解密,不能存放敏感信息。
- C:签证,A和B加上密钥 加密而成,只要密钥不丢失,可以认为是安全的。
JWT验证,主要就是验证C部分是否合法。
JWT详解_baobao555#的博客-CSDN博客_jwt
1.3 其他
- 注意,SpringBoot配置拦截器的两种方式
- 登录日志,采用注解还是普通方法呢?
- 拦截器,先定义拦截器类,实现拦截操作的方法,pre、after等,然后在配置文件中注入拦截器对象,并声明拦截器的拦截路径。
- ThreadLocal类采用单例模式,只需返回一个就可。
2. 基于SpringBoot的校园外卖系统
2.1 利用Redis缓存购物车数据
注意,Redis存储的是键值对的形式,采用Hash存储,key是用户id,Hkey是菜品id,Hv是HashMap,key是口味id,value是map对象,对象是菜品的数量,名称,口味,金额,图片,并将这些。因为前端显示需要图片名称,所以这里额外存储。并且因为一个菜品,可选择不同的口味,所以这里Hv是List集合。但是添加的时候,需要判断已经存在了还是没有存在,如果已经存在了,那么直接在数量加一即可,如果没有存在,则需要添加。
因为餐厅开放时间以及就餐时间基本上是两个小时,所以Redis中设置的生存时间就是两个小时,到期后直接更新到数据库,然后删除Redis本地。
2.2 利用SpringSecurity实现后台权限认证功能
SpringSecurity是Spring家族中的一个安全管理框架,相比另一个安全框架Shiro,它提供了更丰富的功能。
一般Web应用,需要进行认证和授权。
认证: 验证当前访问系统的用户,是不是用户【用户名密码是不是在数据库中,类似拦截器】,并且要确认具体是哪个用户。
授权:经过认证后判断当前用户是否有权限进行某个操作。
SpringSecurity本质上就是一系列的过滤器,组成了一条链条。认证主要是由UsernamePasswordAuthenticationFilter负责,而授权则是由FilterSecurityInterceptor负责。
而认证过滤器UsernamePasswordAuthenticationFilter的底层是会去调用UserDetailsService这个接口的loadUserByUsername方法来进行一个用户的查询。因此可自定义类来集成这个接口即可。这个实现类根据用户名去数据库查询信息,然后封装成UserDetail对象返回给认证过滤器即可,过滤器会根据前端的密码和后台查到的密码进行匹配,从而判断是否认证通过。
针对员工,管理员可以对所有员工有禁用和启用功能、编辑信息功能。而普通员工,只能禁用和启用自己、编辑自己信息功能。
在配置文件中,自定义类继承WebSecurityConfigurerAdapter类,重写configure方法,自定义权限认证规则。
3. 基于SSM的校园社团活动管理系统
本系统用于社团发布活动,统计活动参与情况等。
- 活动表:id,主办人,活动名称,活动描述,开始日期,结束日期,创建者,创建时间,修改者,修改时间;
- 活动备注表:id,备注内容,所属活动id,创建时间,创建者,修改时间,修改表,是否删除。
- 团员表:id,账号,密码,姓名,手机号,邮箱,部门id,创建时间,过期时间
- 活动参与表,