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编码后用.进行连接形成最终传输的字符串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)
a. Header
JWT头是一个描述JWT元数据的JSON对象,alg属性表示签名使用的算法,默认为HMAC SHA256(写为HS256);typ属性表示令牌的类型,JWT令牌统一写为JWT。最后,使用Base64 URL算法将上述JSON对象转换为字符串保存
{
"alg": "HS256",
"typ": "JWT"
}
b. Payload
有效载荷部分,是JWT的主体内容部分,也是一个JSON对象,包含需要传递的数据。 JWT指定七个默认字段供选择
iss:发行人
exp:到期时间
sub:主题
aud:用户
nbf:在此之前不可用
iat:发布时间
jti:JWT ID用于标识该JWT
这些预定义的字段并不要求强制使用。除以上默认字段外,我们还可以自定义私有字段,一般会把包含用户信息的数据放到payload中,如下例:
{
"sub": "1234567890",
"name": "Helen",
"admin": true
}
请注意,默认情况下JWT是未加密的,因为只是采用base64算法,拿到JWT字符串后可以转换回原本的JSON数据,任何人都可以解读其内容,因此不要构建隐私信息字段,比如用户的密码一定不能保存到JWT中,以防止信息泄露。JWT只是适合在网络中传输一些非敏感的信息
c. Signature
签名哈希部分是对上面两部分数据签名,需要使用base64编码后的header和payload数据,通过指定的算法生成哈希,以确保数据不会被篡改。首先,需要指定一个密钥(secret)。该密码仅仅为保存在服务器中,并且不能向用户公开。然后,使用header中指定的签名算法(默认情况下为HMAC SHA256)根据以下公式生成签名
在计算出签名哈希后,JWT头,有效载荷和签名哈希的三个部分组合成一个字符串,每个部分用.分隔,就构成整个JWT对象
1.4 Jwt登录过滤器
判断请求头中的 JWT令牌 是否存在/合法
根据令牌获取token 根据token获取用户名
检查SpringSecurity上下文中是否存在用户信息。
判断token是否有效,有效重新设置用户对象,保存到上下文
2. Swagger
- 导入依赖
- swagger依赖
- swagger-ui依赖
@EnableSwagger2
:开启swagger- 配置信息
- ApiInfo
- basePacket
-
3. 验证码
导入依赖
- 编写配置类(复制)
- 控制类
- 用流的形式直接传图片,需要对response响应头经行处理(复制),主要是为了让response以image/jpeg形式输出
- 业务逻辑:
- 创建文本
.createText()
- 将验证码文本内容放入session
request.getSession().setAttribute("captcha",text);
- 根据文本验证码创建图形验证码
.createImage()
- 将图片以流的方式输出
- 创建文本
校验验证码
引入依赖 添加配置信息
- 编写配置类(序列化)
- 获取菜单的时候先去redis中查找,如果为空去数据库查找,并加入到redis
valueOperations.get("menu_" + adminId)
4.2 根据请求的URL获取菜单列表
编写SQL 根据角色获取菜单
编写权限控制 根据url请求分析请求所需角色
判断请求url与菜单角色是否匹配5. 部门管理
5.1 插入逻辑
插入子部门
根据子部门父部门id查到父部门的路径
查询自己插入的id
设置子部门的路径为 父部门路径+.自己id
设置父部门的子部门为15.2 存储过程
设置5个参数,三个输入(部门名称、父部门id,是否启用),两个输出(result,result2)。
设置两个变量 did pDepPath
插入部门 将受影响的行数赋值给result
将最新插入的id赋值给did 和result2
将副部们的路径赋值给 pDepPath
设置新插入部门的路径
设置父部门的
6. 操作员管理
6.1 更新操作员角色
当遇到既插入有删除的情况时,会判断原先有没有所要的角色,操作复杂,所以采用下面的方式(先全部删除,再全部插入):
- 根据id删除所有角色
-
7. 员工操作(分页)
7.1 准备
配置分页插件
@Bean
public PaginationInterceptor paginationInterceptor(){
return new PaginationInterceptor();
}
添加公共分页返回对象
- 总条数
- 数据List
- 配置全局时间格式转换
- 修改pojo类的时间格式
-
7.2 获取所有员工
传入参数
- 当前页
- 每页大小
- 员工对象
- 入职日期范围
开启分页
Page<Employee> page = new Page<>(currentPage,size);
-
添加员工
将需要的信息全部查询,用于选择
- 计算合同期限 .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 导出
用流的形式导出
- 获取所有员工
- 设置导出参数 ```java ExportParams params = new ExportParams(“员工表”, “员工表”, ExcelType.HSSF);
3. 使用工具类导出
```java
Workbook workbook = ExcelExportUtil.exportExcel(params, Employee.class, list);
-
8.3 导入
重写EqualsAndHashCode属性(使name称为唯一标识)
准备导入参数
ImportParams params = new ImportParams();
删除标题行
使用工具类导入
List<Employee> list = ExcelImportUtil.importExcel(file.getInputStream(), Employee.class, params);
-
9. 邮件发送
9.1 创建邮件发送项目
引入RabbitMQ和mail依赖
- 添加配置信息
- 编写模板
9.2 功能实现
发送邮件
接收邮件rabbitTemplate.convertAndSend(
MailConstants.MAIL_EXCHANGE_NAME,
MailConstants.MAIL_ROUTING_KEY_NAME,
emp,
new CorrelationData(msgId)
);
在邮件模块中新建接收消息的方法,监听传送消息的队列。
创建邮件模板
将邮件内容补充完整并发送,将发送的消息id存入Redis中。
redis的作用是记录消息是否被消费
在server模块中添加邮件常量类
添加员工的时候往数据库中插入一条邮件发送记录
然后把添加的员工信息发送到rabbitMQ相应的队列中
10. 在线聊天
10.1 Websocket
WebSocket是Html5开始提供的一种在单个TCP连接上进行全双工通讯的协议
WebSocket使得客户端和服务器之间的数据交换变得简单,允许服务端主动向客户端推送数据,在WebSocket Api中,浏览器和服务器只完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。
它最大的特点就是,服务器可以主动向客户端推送信息,客户端也可以主动向服务器发送消息,是真正的双向平等对话,属于服务器推送技术的一种
其他特点:
较小的控制开销
更强的实时性
保持连接状态
更好的二进制支持
可以支持 扩展
更好的压缩效果
10.2 配置WebSocket
- 引入依赖 (@EnableWebSocketMessageBroker)
创建配置类,继承
WebSocketMessageBrokerConfigurer
实现三个方法/**
* 添加这个Endpoint,这样可以在网页上通过websocket连接上服务器
* 也就是我们配置websocket的服务地址,并且可以指定是否使用websocketJs
* @param registry
*/
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
/**
* 将/ws/ep注册为端点,用户连接这个端点就可以进行websocket通讯,支持websocketJS
* setAllowedOriginPatterns允许*跨域
* withSocketJS支持socketJS访问
*/
registry.addEndpoint("/ws/ep").setAllowedOriginPatterns("*").withSockJS();
}
/**
* 没用JWT令牌时不用配置
* 输入通道参数配置
* @param registration
*/
@Override
public void configureClientInboundChannel(ChannelRegistration registration) {
registration.interceptors(new ChannelInterceptor() {
@Override
public Message<?> preSend(Message<?> message, MessageChannel channel) {
StompHeaderAccessor accessor = MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor.class);
//判断是否为连接,如果是,需要获取token,并且设置用户对象
if (StompCommand.CONNECT.equals(accessor.getCommand())) {
String token = accessor.getFirstNativeHeader("Auth-Token");
if (StringUtils.hasText(token)) {
String authToken = token.substring(tokenHead.length());
String username = jwtTokenUtil.getUserNameFromToken(authToken);
//token中有username
if (StringUtils.hasText(username)) {
//登录
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
if (jwtTokenUtil.validateToken(authToken,userDetails)) {
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
accessor.setUser(authenticationToken);
}
}
}
}
return message;
}
});
}
/**
* 配置消息代理
* @param registry
*/
@Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
/**
* 配置代理域,可以配置多个,
* 配置代理目的地的前缀/queue,可以在配置域上向客户端发送消息
*/
registry.enableSimpleBroker("queue");
}
10.3 流程
登录时连接相应的websocket
后端添加端点 /ws/ep
- 前端连接端点 /ws/ep 通过 SockJS 的方式
- 连接的时候携带token令牌 通过 key为Auth-Token,value为token的方式传送
- 后台拿到相应的token后做处理 登录并保存当前用户
- 后端发送消息到队列 /queue/chat(代理域)
- 前端订阅消息队列 /queue/chat,接受消息(代理域)