1. 登录

1.1 登录逻辑

前端发送的用户名和密码与数据库中的用户名密码进行比较,如果正确,则生成一个JWT令牌,返回给前端,前端拿到JWT请求令牌后,会把它放在请求头里面,后面的每一次请求都会携带JWT令牌。
编写token的拦截器,判断携带的token是否有效,无效需要重新登录,有效才能允许访问其他接口。
登录用的是SpringSecurity中自带的userDatails中的loadUserByUsername()
设置用户对象至SpringSecurity全局上下文,方便其他地方使用

1.2 SpringConfig

重写UserDetailsService:自定义登录规则
passwordEncoder:密码匹配 默认的:BCryptPasswordEncoder();
配置拦截
添加登录授权过滤器
自定义异常返回结果

1.3 JWT

1.3.1 JWT认证的优势

对比传统的session认证方式,JWT的优势是:

  • 简洁:JWT Token数据量小,传输速度也很快
  • 因为JWT Token是以JSON加密形式保存在客户端的,所以JWT是跨语言的,原则上任何web形式都支持
  • 不需要在服务端保存会话信息,也就是说不依赖于cookie和session,所以没有了传统session认证的弊端,特别适用于分布式微服务
  • 单点登录友好:使用Session进行身份认证的话,由于cookie无法跨域,难以实现单点登录。但是,使用token进行认证的话, token可以被保存在客户端的任意位置的内存中,不一定是cookie,所以不依赖cookie,不会存在这些问题
  • 适合移动端应用:使用Session进行身份认证的话,需要保存一份信息在服务器端,而且这种方式会依赖到Cookie(需要 Cookie 保存 SessionId),所以不适合移动端

    1.3.2 JWT结构

    JWT由3部分组成:标头(Header)、有效载荷(Payload)和签名(Signature)。在传输的时候,会将JWT的3部分分别进行Base64编码后用.进行连接形成最终传输的字符串
    1. JWTString = Base 64 (Header ).Base64(Payload).HMACSHA 256 (base64UrlEncode(header) + " . " + base64UrlEncode (payload) , secret) JWTString = Base64(Header).Base64(Payload).HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)JWTString=Base64(Header).Base64(Payload).HMACSHA256(base64UrlEncode(header)+"."+base64UrlEncode(payload),secret)
    image.png
    a. Header
    JWT头是一个描述JWT元数据的JSON对象,alg属性表示签名使用的算法,默认为HMAC SHA256(写为HS256);typ属性表示令牌的类型,JWT令牌统一写为JWT。最后,使用Base64 URL算法将上述JSON对象转换为字符串保存
  1. {
  2. "alg": "HS256",
  3. "typ": "JWT"
  4. }

b. Payload

有效载荷部分,是JWT的主体内容部分,也是一个JSON对象,包含需要传递的数据。 JWT指定七个默认字段供选择

  1. iss:发行人
  2. exp:到期时间
  3. sub:主题
  4. aud:用户
  5. nbf:在此之前不可用
  6. iat:发布时间
  7. jtiJWT ID用于标识该JWT

这些预定义的字段并不要求强制使用。除以上默认字段外,我们还可以自定义私有字段,一般会把包含用户信息的数据放到payload中,如下例:

  1. {
  2. "sub": "1234567890",
  3. "name": "Helen",
  4. "admin": true
  5. }

请注意,默认情况下JWT是未加密的,因为只是采用base64算法,拿到JWT字符串后可以转换回原本的JSON数据,任何人都可以解读其内容,因此不要构建隐私信息字段,比如用户的密码一定不能保存到JWT中,以防止信息泄露。JWT只是适合在网络中传输一些非敏感的信息

c. Signature

签名哈希部分是对上面两部分数据签名,需要使用base64编码后的header和payload数据,通过指定的算法生成哈希,以确保数据不会被篡改。首先,需要指定一个密钥(secret)。该密码仅仅为保存在服务器中,并且不能向用户公开。然后,使用header中指定的签名算法(默认情况下为HMAC SHA256)根据以下公式生成签名
image.png

在计算出签名哈希后,JWT头,有效载荷和签名哈希的三个部分组合成一个字符串,每个部分用.分隔,就构成整个JWT对象
image.png

1.4 Jwt登录过滤器

判断请求头中的 JWT令牌 是否存在/合法
根据令牌获取token 根据token获取用户名
检查SpringSecurity上下文中是否存在用户信息。
判断token是否有效,有效重新设置用户对象,保存到上下文

2. Swagger

  1. 导入依赖
    1. swagger依赖
    2. swagger-ui依赖
  2. @EnableSwagger2:开启swagger
  3. 配置信息
    1. ApiInfo
    2. basePacket
  4. 添加全局Authorization

    3. 验证码

  5. 导入依赖

  6. 编写配置类(复制)
  7. 控制类
    1. 用流的形式直接传图片,需要对response响应头经行处理(复制),主要是为了让response以image/jpeg形式输出
    2. 业务逻辑:
      1. 创建文本 .createText()
      2. 将验证码文本内容放入session request.getSession().setAttribute("captcha",text);
      3. 根据文本验证码创建图形验证码 .createImage()
      4. 将图片以流的方式输出
  8. 校验验证码

    1. 从session中获取验证码
    2. 判断是否为空或者是否正确

      4. 菜单列表

      添加子菜单属性
      编写SQL语句

      4.1 Redis集成菜单

  9. 引入依赖 添加配置信息

  10. 编写配置类(序列化)
  11. 获取菜单的时候先去redis中查找,如果为空去数据库查找,并加入到redis valueOperations.get("menu_" + adminId)

    4.2 根据请求的URL获取菜单列表

    编写SQL 根据角色获取菜单
    编写权限控制 根据url请求分析请求所需角色
    判断请求url与菜单角色是否匹配

    5. 部门管理

    5.1 插入逻辑

    插入子部门
    根据子部门父部门id查到父部门的路径
    查询自己插入的id
    设置子部门的路径为 父部门路径+.自己id
    设置父部门的子部门为1

    5.2 存储过程

    设置5个参数,三个输入(部门名称、父部门id,是否启用),两个输出(result,result2)。
    设置两个变量 did pDepPath
    插入部门 将受影响的行数赋值给result
    将最新插入的id赋值给did 和result2
    将副部们的路径赋值给 pDepPath
    设置新插入部门的路径
    设置父部门的

6. 操作员管理

6.1 更新操作员角色

当遇到既插入有删除的情况时,会判断原先有没有所要的角色,操作复杂,所以采用下面的方式(先全部删除,再全部插入):

  1. 根据id删除所有角色
  2. 插入更改后的所有的角色

    7. 员工操作(分页)

    7.1 准备

  3. 配置分页插件

    1. @Bean
    2. public PaginationInterceptor paginationInterceptor(){
    3. return new PaginationInterceptor();
    4. }
  4. 添加公共分页返回对象

    1. 总条数
    2. 数据List
  5. 配置全局时间格式转换
  6. 修改pojo类的时间格式
  7. 将所需要的类在pojo中引入对象(外键id)

    7.2 获取所有员工

  8. 传入参数

    1. 当前页
    2. 每页大小
    3. 员工对象
    4. 入职日期范围
  9. 开启分页

    1. Page<Employee> page = new Page<>(currentPage,size);
  10. 编写SQL

    添加员工

  11. 将需要的信息全部查询,用于选择

  12. 计算合同期限 .utils

    8. 导入导出

    8.1 EasyPOI

    8.1.1 独特的功能

  • 基于注解的导入导出,修改注解就可以修改Excel
  • 支持常用的样式自定义
  • 基于map可以灵活定义的表头字段
  • 支持一对多的导出,导入
  • 支持模板的导出,一些常见的标签,自定义标签
  • 支持HTML/Excel转换,如果模板还不能满足用户的变态需求,请用这个功能
  • 支持word的导出,支持图片,Excel

利用easypoi为我们提供的注解可以将一个实体类和我们的excel表进行对应,一个实体类对象就是我们excel表中的一行,对象的属性和excel表的列相互映射。

8.1.2 注解

@Excel:作用在实体类属性上,是对excel列的描述;
@ExcelCollection:作用在一个集合属性上,主要针对一对多(关联关系)的导出;
@ExcelEntity:作用在一个对象属性上,主要针对一对一(关联关系)的导出;
@ExcelIngore:表示在导出时忽略该字段的导出,主要针对那些不希望展示的数据;
@ExcelTarget:表示要导出的目标对象,作用在实体类上。

8.2 导出

用流的形式导出

  1. 获取所有员工
  2. 设置导出参数 ```java ExportParams params = new ExportParams(“员工表”, “员工表”, ExcelType.HSSF);
  1. 3. 使用工具类导出
  2. ```java
  3. Workbook workbook = ExcelExportUtil.exportExcel(params, Employee.class, list);
  1. 输出

    8.3 导入

  2. 重写EqualsAndHashCode属性(使name称为唯一标识)

  3. 准备导入参数

    1. ImportParams params = new ImportParams();
  4. 删除标题行

  5. 使用工具类导入

    1. List<Employee> list = ExcelImportUtil.importExcel(file.getInputStream(), Employee.class, params);
  6. 设置外键属性

    9. 邮件发送

    9.1 创建邮件发送项目

  7. 引入RabbitMQ和mail依赖

  8. 添加配置信息
  9. 编写模板

    9.2 功能实现

    发送邮件
    1. rabbitTemplate.convertAndSend(
    2. MailConstants.MAIL_EXCHANGE_NAME,
    3. MailConstants.MAIL_ROUTING_KEY_NAME,
    4. emp,
    5. new CorrelationData(msgId)
    6. );
    接收邮件
    在邮件模块中新建接收消息的方法,监听传送消息的队列。
    创建邮件模板
    将邮件内容补充完整并发送,将发送的消息id存入Redis中。
    redis的作用是记录消息是否被消费

在server模块中添加邮件常量类
添加员工的时候往数据库中插入一条邮件发送记录
然后把添加的员工信息发送到rabbitMQ相应的队列中

10. 在线聊天

10.1 Websocket

WebSocket是Html5开始提供的一种在单个TCP连接上进行全双工通讯的协议
WebSocket使得客户端和服务器之间的数据交换变得简单,允许服务端主动向客户端推送数据,在WebSocket Api中,浏览器和服务器只完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。
它最大的特点就是,服务器可以主动向客户端推送信息,客户端也可以主动向服务器发送消息,是真正的双向平等对话,属于服务器推送技术的一种

其他特点:
较小的控制开销
更强的实时性
保持连接状态
更好的二进制支持
可以支持 扩展
更好的压缩效果

10.2 配置WebSocket

  1. 引入依赖 (@EnableWebSocketMessageBroker)
  2. 创建配置类,继承WebSocketMessageBrokerConfigurer实现三个方法

    1. /**
    2. * 添加这个Endpoint,这样可以在网页上通过websocket连接上服务器
    3. * 也就是我们配置websocket的服务地址,并且可以指定是否使用websocketJs
    4. * @param registry
    5. */
    6. @Override
    7. public void registerStompEndpoints(StompEndpointRegistry registry) {
    8. /**
    9. * 将/ws/ep注册为端点,用户连接这个端点就可以进行websocket通讯,支持websocketJS
    10. * setAllowedOriginPatterns允许*跨域
    11. * withSocketJS支持socketJS访问
    12. */
    13. registry.addEndpoint("/ws/ep").setAllowedOriginPatterns("*").withSockJS();
    14. }
    1. /**
    2. * 没用JWT令牌时不用配置
    3. * 输入通道参数配置
    4. * @param registration
    5. */
    6. @Override
    7. public void configureClientInboundChannel(ChannelRegistration registration) {
    8. registration.interceptors(new ChannelInterceptor() {
    9. @Override
    10. public Message<?> preSend(Message<?> message, MessageChannel channel) {
    11. StompHeaderAccessor accessor = MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor.class);
    12. //判断是否为连接,如果是,需要获取token,并且设置用户对象
    13. if (StompCommand.CONNECT.equals(accessor.getCommand())) {
    14. String token = accessor.getFirstNativeHeader("Auth-Token");
    15. if (StringUtils.hasText(token)) {
    16. String authToken = token.substring(tokenHead.length());
    17. String username = jwtTokenUtil.getUserNameFromToken(authToken);
    18. //token中有username
    19. if (StringUtils.hasText(username)) {
    20. //登录
    21. UserDetails userDetails = userDetailsService.loadUserByUsername(username);
    22. if (jwtTokenUtil.validateToken(authToken,userDetails)) {
    23. UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
    24. SecurityContextHolder.getContext().setAuthentication(authenticationToken);
    25. accessor.setUser(authenticationToken);
    26. }
    27. }
    28. }
    29. }
    30. return message;
    31. }
    32. });
    33. }
    1. /**
    2. * 配置消息代理
    3. * @param registry
    4. */
    5. @Override
    6. public void configureMessageBroker(MessageBrokerRegistry registry) {
    7. /**
    8. * 配置代理域,可以配置多个,
    9. * 配置代理目的地的前缀/queue,可以在配置域上向客户端发送消息
    10. */
    11. registry.enableSimpleBroker("queue");
    12. }

    10.3 流程

    登录时连接相应的websocket

  3. 后端添加端点 /ws/ep

  4. 前端连接端点 /ws/ep 通过 SockJS 的方式
    1. 连接的时候携带token令牌 通过 key为Auth-Token,value为token的方式传送
  5. 后台拿到相应的token后做处理 登录并保存当前用户
  6. 后端发送消息到队列 /queue/chat(代理域)
  7. 前端订阅消息队列 /queue/chat,接受消息(代理域)