秋招_项目复习


本文复习做过的项目

1. 基于SpringBoot的博客系统

1.1 利用线程池实现了文章阅读次数的更新以及登录日志的记录

在点击文章详情的时候,从整体上会发送两次请求,一次是获取文章的详细内容,一次是获取该文章的评论内容。

获取评论内容,无需过多解释,就是查询ms_comments数据表即可。

获取文章详细内容,除了获取文章体之外,还需要获取文章的附加信息(比如创建时间,阅读次数,评论等等),另外还需要获取所属类别、包含标签等信息。

因为是文章详情,也就是阅读文章,所以阅读次数需要后台增加1。但是这种操作和前端没什么太大的关系,而前端只需要尽可能快的看到文章内容和评论,因此只需要尽快返回这些结果即可,而增加阅读次数可以交给后台开启多线程默默运行。

因此,这里采用线程池进行阅读次数的更新。首先要在SpringBoot容器中创建线程对象。采用Bean注解创建对象。主要有以下步骤:

  1. 添加@EnableAsync注解,使得SpringBoot开启异步

  2. 创建线程池对象,这里以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
    @Configuration
    @EnableAsync // 开启线程池
    public class ThreadPoolConfig {

    // 创建对象,注入到SpringBoot容器中
    @Bean("taskExecutor")
    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;
    }
    }
  3. 定义service方法【也可先定义接口】,本质上和service层没什么区别,只不过这里的service不再是此时请求线程负责处理;而是交给线程池来处理。因此,需要在service方法上面加入@Async(“taskExecutor”)注解,注解的值就是要交给的线程池对象。代码如下所示:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    @Component
    public class ThreadService {

    // 声明本任务采用的线程池
    @Async("taskExecutor")
    public void updateArticleViewCount(ArticleMapper articleMapper, Article article) {

    // 在线程池中更新操作,不会影响原有的主线程。
    Integer viewCounts = article.getViewCounts();

    // 注意,因为多线程的缘故,为了线程安全,防止读脏数据
    // 即当前更新操作,必须用子查询的方式来更新。
    int i = articleMapper.updateByPrimaryKeyAndMul(article.getId());
    }
    }
  4. 之后,就像调用其他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
2
3
4
5
6
7
8
9
10
# 异步线程池
spring:
task:
execution:
thread-name-prefix: test-thread- #线程名称前缀
pool:
core-size: 10 #核心线程数,默认是8
max-size: 10 #最大线程数
keep-alive: 3s #空闲线程保留时间,自动装配需要加单位
queue-capacity: 1000 #队列容量

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:

  1. A:Header,{“type”: “JWT”, “alg”: “HS256”},固定内容
  2. B:payload,存放信息,比如,用户id、过期时间等等,可以被解密,不能存放敏感信息。
  3. C:签证,A和B加上密钥 加密而成,只要密钥不丢失,可以认为是安全的。

JWT验证,主要就是验证C部分是否合法。

JWT详解_baobao555#的博客-CSDN博客_jwt

1.3 其他

  1. 注意,SpringBoot配置拦截器的两种方式
  2. 登录日志,采用注解还是普通方法呢?
  3. 拦截器,先定义拦截器类,实现拦截操作的方法,pre、after等,然后在配置文件中注入拦截器对象,并声明拦截器的拦截路径。
  4. 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应用,需要进行认证和授权。

认证: 验证当前访问系统的用户,是不是用户【用户名密码是不是在数据库中,类似拦截器】,并且要确认具体是哪个用户。

授权:经过认证后判断当前用户是否有权限进行某个操作。

SpringBoot 学习笔记_安全管理 —— Spring Security 配置_◣NSD◥的博客-CSDN博客

SpringSecurity本质上就是一系列的过滤器,组成了一条链条。认证主要是由UsernamePasswordAuthenticationFilter负责,而授权则是由FilterSecurityInterceptor负责。

而认证过滤器UsernamePasswordAuthenticationFilter的底层是会去调用UserDetailsService这个接口的loadUserByUsername方法来进行一个用户的查询。因此可自定义类来集成这个接口即可。这个实现类根据用户名去数据库查询信息,然后封装成UserDetail对象返回给认证过滤器即可,过滤器会根据前端的密码和后台查到的密码进行匹配,从而判断是否认证通过。

针对员工,管理员可以对所有员工有禁用和启用功能、编辑信息功能。而普通员工,只能禁用和启用自己、编辑自己信息功能。

在配置文件中,自定义类继承WebSecurityConfigurerAdapter类,重写configure方法,自定义权限认证规则。

3. 基于SSM的校园社团活动管理系统

本系统用于社团发布活动,统计活动参与情况等。

  1. 活动表:id,主办人,活动名称,活动描述,开始日期,结束日期,创建者,创建时间,修改者,修改时间;
  2. 活动备注表:id,备注内容,所属活动id,创建时间,创建者,修改时间,修改表,是否删除。
  3. 团员表:id,账号,密码,姓名,手机号,邮箱,部门id,创建时间,过期时间
  4. 活动参与表,

文章作者: 浮云
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 浮云 !
  目录