1 异常处理
全局异常处理在common模块
为什么要使用全局异常处理:如果没有全局异常处理,比如不可预知的异常(空指针异常),返回浏览器的页面,人看上去就是一大堆乱码,非常的不好看。对人极其不友好,而且后端排查问题,看一大堆错误信息也不好排查,所以就用全局异常@GlobalExceptional进行封装。
1.只要是异常都会被管理
2.不能直接返回用户404 500 400,要返回两种异常 可预知异常(自己抛的异常)、不可预知(系统抛出的系统)。
3.所有异常统一响应消息处理回用户:程序员手动抛出(参数有问题,校验逻辑);服务异常,网络异常,系统维护,策略包,系统系统等。(写在common模块,所有微服务都要用到异常)
@ControllerAdvice 控制器增强类
@ExceptionHandler(Exception.class)不可控异常
@ExceptionHandler(CustomException.class)可预知异常
使得异常类生效的加个配置
把类在spring容器中进行加载。
一旦其他服务引用了common这个包,就能够用这个全局异常类。
2 登陆模块
2.1思路分析
注册:知道怎么注册才知道怎么登录
直接123456进行MD5加密,容易被破译。
登录
登陆user微服务 需要依赖 model common feign-api(对外接口) springbootstarterweb springbootstartertest springcloudalibabanacosdiscovery springcloudalibabanacosconfig
路径 zjj-leadnews-service\zjj-leadnews-user\src\main\java\com.zjj.user(config|controller.v1|controller.v2|mapper|service|UserApplication(启动类)|
创建zjj-leadnews-user模块 直接在zjj-leadnews-service右键new model;Parent是zjj-leadnews-service,Name是zjj-leadnews-user。
@SpringBootApplication
@EnableDiscoveryClient
@MapperScan("com.zjj.user.mapper")
@EnableFeignClients(basePackages = "com.zjj.apis")
public class UserApplication {
public static void main(String[] args) {
SpringApplication.run(UserApplication.class,args);
}
}
日志输出
如果是linux的话,改一下路径
debug有更多的错误信息
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<!--定义日志文件的存储地址,使用绝对路径-->
<property name="LOG_HOME" value="e:/logs"/>
<!-- Console 输出设置 -->
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符-->
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
<charset>utf8</charset>
</encoder>
</appender>
<!-- 按照每天生成日志文件 -->
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!--日志文件输出的文件名-->
<fileNamePattern>${LOG_HOME}/leadnews.%d{yyyy-MM-dd}.log</fileNamePattern>
</rollingPolicy>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<!-- 异步输出 -->
<appender name="ASYNC" class="ch.qos.logback.classic.AsyncAppender">
<!-- 不丢失日志.默认的,如果队列的80%已满,则会丢弃TRACT、DEBUG、INFO级别的日志 -->
<discardingThreshold>0</discardingThreshold>
<!-- 更改默认的队列的深度,该值会影响性能.默认值为256 -->
<queueSize>512</queueSize>
<!-- 添加附加的appender,最多只能添加一个 -->
<appender-ref ref="FILE"/>
</appender>
<logger name="org.apache.ibatis.cache.decorators.LoggingCache" level="DEBUG" additivity="false">
<appender-ref ref="CONSOLE"/>
</logger>
<logger name="org.springframework.boot" level="debug"/>
<root level="info">
<!--<appender-ref ref="ASYNC"/>-->
<appender-ref ref="FILE"/>
<appender-ref ref="CONSOLE"/>
</root>
</configuration>
补充:
1.登陆的时候可以将用户信息 封装为UserInfo实体,然后ThreadLoacal.set(userInfo), 这样全局都可以使用。
2.游客登陆 没有输入用户名和密码,后端将用户名设置为0
2.2 user模块对应的引导类
package com.heima.user;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@SpringBootApplication
@EnableDiscoveryClient@MapperScan("groupId(0).groupId(1).模块名.mapper")
public class UserApplication {
public static void main(String[] args) {
SpringApplication.run(UserApplication.class,args);
}
}
2.3 bootstrap.yml
server:
port: 51801
spring:
application: #nacos 这个端口的名字
name: leadnews-user # 该模块的名字 将该微服务给到注册中心
cloud:
nacos:
discovery: #注册
server-addr: 192.168.200.130:8848
config: #配置中心
server-addr: 192.168.200.130:8848
file-extension: yml #剩下的配置在nacos中配置
2.4 nacos配置
1.在nacos中创建配置文件:leadnews-user.yml
2.
spring:
datasource:
driver-class-name: com.mysql.jdbc.Driver
### 注意
url: jdbc:mysql://192.168.200.130:3306/leadnews_user?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
username: root ###不确定
password: root ###不确定
# 设置Mapper接口所对应的XML文件位置,如果你在Mapper接口中有自定义方法,需要进行该配置
mybatis-plus:
mapper-locations: classpath*:mapper/*.xml # :后不要空格
# 设置别名包扫描路径,通过该属性可以给包中的类注册别名 mapper.xml中写的别名
type-aliases-package: com.zjj.model.user.pojos
全局的返回实体ResponseResult
responseResult.setData();
data 属性 封装了 一个Object对象和一个token字符串
/**
* 通用的结果返回类
* @param <T>
*/
public class ResponseResult<T> implements Serializable {
private String host;
private Integer code;
private String errorMessage;
private T data;
public ResponseResult() {
this.code = 200;
}
public ResponseResult(Integer code, T data) {
this.code = code;
this.data = data;
}
public ResponseResult(Integer code, String msg, T data) {
this.code = code;
this.errorMessage = msg;
this.data = data;
}
public ResponseResult(Integer code, String msg) {
this.code = code;
this.errorMessage = msg;
}
public static ResponseResult errorResult(int code, String msg) {
ResponseResult result = new ResponseResult();
return result.error(code, msg);
}
public static ResponseResult okResult(int code, String msg) {
ResponseResult result = new ResponseResult();
return result.ok(code, null, msg);
}
public static ResponseResult okResult(Object data) {
ResponseResult result = setAppHttpCodeEnum(AppHttpCodeEnum.SUCCESS, AppHttpCodeEnum.SUCCESS.getErrorMessage());
if(data!=null) {
result.setData(data);
}
return result;
}
public static ResponseResult errorResult(AppHttpCodeEnum enums){
return setAppHttpCodeEnum(enums,enums.getErrorMessage());
}
public static ResponseResult errorResult(AppHttpCodeEnum enums, String errorMessage){
return setAppHttpCodeEnum(enums,errorMessage);
}
public static ResponseResult setAppHttpCodeEnum(AppHttpCodeEnum enums){
return okResult(enums.getCode(),enums.getErrorMessage());
}
private static ResponseResult setAppHttpCodeEnum(AppHttpCodeEnum enums, String errorMessage){
return okResult(enums.getCode(),errorMessage);
}
public ResponseResult<?> error(Integer code, String msg) {
this.code = code;
this.errorMessage = msg;
return this;
}
public ResponseResult<?> ok(Integer code, T data) {
this.code = code;
this.data = data;
return this;
}
public ResponseResult<?> ok(Integer code, T data, String msg) {
this.code = code;
this.data = data;
this.errorMessage = msg;
return this;
}
public ResponseResult<?> ok(T data) {
this.data = data;
return this;
}
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
public String getErrorMessage() {
return errorMessage;
}
public void setErrorMessage(String errorMessage) {
this.errorMessage = errorMessage;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
public String getHost() {
return host;
}
public void setHost(String host) {
this.host = host;
}
public static void main(String[] args) {
//前置
/*AppHttpCodeEnum success = AppHttpCodeEnum.SUCCESS;
System.out.println(success.getCode());
System.out.println(success.getErrorMessage());*/
//查询一个对象
/*Map map = new HashMap();
map.put("name","zhangsan");
map.put("age",18);
ResponseResult result = ResponseResult.okResult(map);
System.out.println(JSON.toJSONString(result));*/
//新增,修改,删除 在项目中统一返回成功即可
/* ResponseResult result = ResponseResult.okResult(AppHttpCodeEnum.SUCCESS);
System.out.println(JSON.toJSONString(result));*/
//根据不用的业务返回不同的提示信息 比如:当前操作需要登录、参数错误
/*ResponseResult result = ResponseResult.errorResult(AppHttpCodeEnum.NEED_LOGIN);
System.out.println(JSON.toJSONString(result));*/
//查询分页信息
/*PageResponseResult responseResult = new PageResponseResult(1,5,50);
List list = new ArrayList();
list.add("itcast");
list.add("itheima");
responseResult.setData(list);
System.out.println(JSON.toJSONString(responseResult));*/
}
}
public static ResponseResult okResult(Object data) {
ResponseResult result = setAppHttpCodeEnum(AppHttpCodeEnum.SUCCESS, AppHttpCodeEnum.SUCCESS.getErrorMessage());
if(data!=null) {
result.setData(data);
}
return result;
}
private static ResponseResult setAppHttpCodeEnum(AppHttpCodeEnum enums, String errorMessage){
return okResult(enums.getCode(),errorMessage);
}
枚举 AppHttpCodeEnum
public enum AppHttpCodeEnum {
// 成功段0
SUCCESS(200,"操作成功"),
// 登录段1~50
NEED_LOGIN(1,"需要登录后操作"),
LOGIN_PASSWORD_ERROR(2,"密码错误"),
// TOKEN50~100
TOKEN_INVALID(50,"无效的TOKEN"),
TOKEN_EXPIRE(51,"TOKEN已过期"),
TOKEN_REQUIRE(52,"TOKEN是必须的"),
// SIGN验签 100~120
SIGN_INVALID(100,"无效的SIGN"),
SIG_TIMEOUT(101,"SIGN已过期"),
// 参数错误 500~1000
PARAM_REQUIRE(500,"缺少参数"),
PARAM_INVALID(501,"无效参数"),
PARAM_IMAGE_FORMAT_ERROR(502,"图片格式有误"),
SERVER_ERROR(503,"服务器内部错误"),
// 数据错误 1000~2000
DATA_EXIST(1000,"数据已经存在"),
AP_USER_DATA_NOT_EXIST(1001,"ApUser数据不存在"),
DATA_NOT_EXIST(1002,"数据不存在"),
// 数据错误 3000~3500
NO_OPERATOR_AUTH(3000,"无权限操作"),
NEED_ADMIND(3001,"需要管理员权限"),
// 自媒体文章 3501~3600
MATERIAL_REFERENCE_FAIL(3501,"素材引用失效");
int code;
String errorMessage;
AppHttpCodeEnum(int code, String errorMessage){
this.code = code;
this.errorMessage = errorMessage;
}
public int getCode() {
return code;
}
public String getErrorMessage() {
return errorMessage;
}
}
分页
登陆的接口
游客不生成token,全局过滤器无法解析会报错
http://localhost:xxxx/api/v1/login/lgoin_auth
这里是user微服务返回的token
MybatisPlus的使用
ApUser dbUser = getOne(Wrappers.lambdaQuery().eq(ApUser::getPhone, dto.getPhone()));
mapper
@Mapper
public interface ApUserMapper extends BaseMapper<ApUser> {
}
service
public interface ApUserService extends IService<ApUser> {
/**
* app端登录
* @param dto
* @return
*/
public ResponseResult login(LoginDto dto);
}
serviceImpl
@Service
@Transactional
@Slf4j
public class ApUserServiceImpl extends ServiceImpl<ApUserMapper, ApUser> implements ApUserService {
/**
* app端登录
* @param dto
* @return
*/
@Override
public ResponseResult login(LoginDto dto) {
//1.正常登录
if(StringUtils.isNotBlank(dto.getPhone()) && StringUtils.isNotBlank(dto.getPassword())){
//1.1 查询用户
ApUser dbUser = getOne(Wrappers.<ApUser>lambdaQuery().eq(ApUser::getPhone, dto.getPhone()));
if(dbUser == null){
return ResponseResult.errorResult(AppHttpCodeEnum.DATA_NOT_EXIST);
}
//1.2 比对密码
String salt = dbUser.getSalt();
String dbUserPassword = dbUser.getPassword();
String pswd = DigestUtils.md5DigestAsHex((dto.getPassword() + salt).getBytes());
if(!dbUserPassword.equals(pswd)){
return ResponseResult.errorResult(AppHttpCodeEnum.LOGIN_PASSWORD_ERROR);
}
//1.3 结果返回 user token
Map<String,Object> map = new HashMap<>();
dbUser.setSalt("");
dbUser.setPassword("");
map.put("user",dbUser);
map.put("token", AppJwtUtil.getToken(dbUser.getId().longValue())); //唯一主键来创建token
return ResponseResult.okResult(map);
}else {
//2.游客登录
Map<String,Object> map = new HashMap<>();
map.put("token", AppJwtUtil.getToken(0l));
return ResponseResult.okResult(map);
}
}
}
note:
两张表(just me)
ap_user App用户信息表
ap_user_realName App实名认证信息表
一般 记录 id自增主键、username、password(前端和后端各用一次盐)、salt、address、sex、nationality、phone、image、is_or_not_identity_certification是否实名认证、status正常为0异常为1、flag普通用户为0VIP用户为1、注册时间created_time注册后自动登陆等
swagger降低后端人员编写接口文档负担
访问地址 服务加端口
1、model和common模块导入依赖
2、common导入swagger配置类
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
</dependency>
package com.zjj.common.swagger;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
@Configuration
@EnableSwagger2
public class SwaggerConfiguration {
@Bean
public Docket buildDocket() {
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(buildApiInfo())
.select()
// 要扫描的API(Controller)基础包
.apis(RequestHandlerSelectors.basePackage("com.zjj"))
.paths(PathSelectors.any())
.build();
}
private ApiInfo buildApiInfo() {
Contact contact = new Contact("程序员","","");
return new ApiInfoBuilder()
.title("头条-平台管理API文档")
.description("头条后台api")
.contact(contact)
.version("1.0.0").build();
}
}
common模块配置类的生效
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.zjj.common.exception.ExceptionCatch,\
com.zjj.common.swagger.SwaggerConfiguration,\
com.zjj.common.swagger.Swagger2Configuration,\
com.zjj.common.aliyun.GreenImageScan,\
com.zjj.common.aliyun.GreenTextScan,\
com.zjj.common.tess4j.Tess4jClient,\
com.zjj.common.redis.CacheService
@RestController
@RequestMapping("/api/v1/login")
@Api(value = "app端用户登录",tags = "ap_user")
public class ApUserLoginController {
@Autowired
private ApUserService apUserService;
@ApiOperation("app端用户登录")
@PostMapping("/login_auth")
public ResponseResult login(@RequestBody LoginDto dto){
// if(true){
// throw new CustomException(AppHttpCodeEnum.NEED_ADMIND);
// }
// int a = 1/0;
return apUserService.login(dto);
}
}
@Data
public class LoginDto {
/**
* 手机号
*/
@ApiModelProperty(value = "手机号",required = true)
private String phone;
/**
* 密码
*/
@ApiModelProperty(value = "密码",required = true)
private String password;
}
knife4j
集成了swagger 针对swagger进行封装
可以下载离线文档,可以让别人补全这个文档
在common模块即可
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-spring-boot-starter</artifactId>
</dependency>
package com.zjj.common.swagger;
import com.github.xiaoymin.knife4j.spring.annotations.EnableKnife4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import springfox.bean.validators.configuration.BeanValidatorPluginsConfiguration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
@Configuration
@EnableSwagger2
@EnableKnife4j
@Import(BeanValidatorPluginsConfiguration.class)
public class Swagger2Configuration {
@Bean(value = "defaultApi2")
public Docket defaultApi2() {
Docket docket=new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
//分组名称
.groupName("1.0")
.select()
//这里指定Controller扫描包路径
.apis(RequestHandlerSelectors.basePackage("com.zjj"))
.paths(PathSelectors.any())
.build();
return docket;
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("头条API文档")
.description("头条API文档")
.version("1.0")
.build();
}
}
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.zjj.common.swagger.Swagger2Configuration
3 gateway
全局过滤器来做JWT校验
与service类似 多少了服务就应该有多少个网关
zjj-leadnews-service\zjj-leadnews-user
zjj-leadnews-gateway\zjj-leadnews-user-gateway
user写成了app 因为我们做的是app 登陆就是用这个app登陆的
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
</dependency>
</dependencies>
package com.zjj.app.gateway;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@SpringBootApplication
@EnableDiscoveryClient
public class AppGatewayApplication {
public static void main(String[] args) {
SpringApplication.run(AppGatewayApplication.class,args);
}
}
生成bootstrap.yml
server:
port: 51601
spring:
application:
name: leadnews-app-gateway
cloud:
nacos:
discovery:
server-addr: 192.168.200.130:8848
config:
server-addr: 192.168.200.130:8848
file-extension: yml
nacos网关跨域设置
spring:
cloud:
gateway:
globalcors:
add-to-simple-url-handler-mapping: true #跨域 后期用的
corsConfigurations:
'[/**]':
allowedHeaders: "*" ##允许所以域 都可以访问该问服务
allowedOrigins: "*"
allowedMethods:
- GET
- POST
- DELETE
- PUT
- OPTION
routes:
# 平台管理
- id: user #随便写
uri: lb://leadnews-user #均衡负载 给到某个微服务器去访问 可以创建多个登录的微服务
predicates: #断言 请求该微服务必须要/user localhost:51601/user/api/v1/login/...
# 去掉断言前缀 实际访问的是 leadnews-user对应的ip和port/api/v1
- Path=/user/**
filters:
- StripPrefix= 1 #伪装 访问的时候可以去掉user
route路由如何实现
StripPrefix=1 去掉前缀一个 /user/** 去掉前缀user user的作用表示连接的是user微服务 实际地址会去掉user 可以认为user是伪装
访问ip
http://localhost:8092(网关ip)/user/api/v1/login/login_in => leadnews-user微服务的api/v1/login/login_in接口
predicates 谓词,判断
网关全局过滤器给用户授权
注册的代码在哪里?没讲,大概的思路就是输入账号密码,然后MD5(密码+salt(DDUtil.random())),把salt和MD5加密后的密码存到数据库。每个用户都有唯一的一个salt保存在数据库。
用户登陆的时候返回token 用户每次请求(除了登陆请求)会携带token,会被网关全局过滤器拦截
401统一为认证失败的代码
404 找不到任何东西
传token的方式 因为是在headers拿 所有封装在headers给
监听器 > 过滤器(AOP日志记录) > 拦截器(AOP日志记录) > servlet执行 > 拦截器 > 过滤器 > 监听器
可以说URL是URI(URL是URI的子集)
过滤器
@Component //放入到spring容器才能使得该类生效
@Slf4j
public class AuthorizeFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
ServerHttpResponse response = exchange.getResponse();
//判断是否为登录 可以说URL是URI(URL是URI的子集)URL和URI之间的区别是什么
if(request.getURI().getPath().contains("login")){
//放行
return chain.filter(exchange);
}
//校验token是否存在 Request Headers 里面 包含 为key的token 还包含 Accept-Encoding、Cache-Control Cookie Host Origin User-Agent、Connection、Content-Type等
String token = request.getHeaders().getFirst("token");
if(StringUtils.isEmpty(token)){
//返回401
response.setStatusCode(HttpStatus.UNAUTHORIZED);
//结束请求
return response.setComplete();
}
//token是否有效 之前是用AppJwtUtil.getToken(主键id)得到的token
try {
Claims claims = AppJwtUtil.getClaimsBody(token);
//解析完得到的claims然后判断是否过期 -1:有效,0:有效,1:过期,2:过期
int result = AppJwtUtil.verifyToken(claims);
if(result == 1 || result == 2){
//返回401
response.setStatusCode(HttpStatus.UNAUTHORIZED);
return response.setComplete();
}
//获取用户id claims里面包含 {"id","userId"} 放到headers中 这样拦截器或者其他就能拿到headers中的id
Object userId = claims.get("id");
ServerHttpRequest serverHttpRequest = request.mutate().headers(new Consumer<HttpHeaders>() {
@Override
public void accept(HttpHeaders httpHeaders) {
httpHeaders.add("userId", userId + "");
}
}).build();
//重置请求
exchange.mutate().request(serverHttpRequest);
}catch (Exception e){
//打印堆栈错误信息
e.printStackTrace();
//返回401 解析失败
response.setStatusCode(HttpStatus.UNAUTHORIZED);
return response.setComplete();
}
//放行
return chain.filter(exchange);
}
/**
* 优先级设置 值越小,优先级越高
* 过滤器的数量 过滤器0->过滤器1—>...controller
* 监听器 > 过滤器(AOP日志记录) > 拦截器(AOP日志记录) > servlet执行 > 拦截器 > 过滤器 > 监听器
* @return
*/
@Override
public int getOrder() {
return 0;
}
}
AppJwtUtil
public class AppJwtUtil {
// TOKEN的有效期一天(S) 1*24*60*60
private static final int TOKEN_TIME_OUT = 3_600;
// 加密KEY
private static final String TOKEN_ENCRY_KEY = "MDk4ZjZiY2Q0NjIxZDM3M2NhZGU0ZTgzMjYyN2I0ZjY";
// 最小刷新间隔(S)
private static final int REFRESH_TIME = 300;
// 生产ID
public static String getToken(Long id){
Map<String, Object> claimMaps = new HashMap<>();
claimMaps.put("id",id); // claims.get("id") 拿到id 你输入的就是id claims实际上是个map
// claimMaps.put("userName", name); map里面随便你存什么信息
long currentTime = System.currentTimeMillis(); // 毫秒
return Jwts.builder()
.setId(UUID.randomUUID().toString())
.setIssuedAt(new Date(currentTime)) //签发时间
.setSubject("system") //说明
.setIssuer("zjj") //签发者信息
.setAudience("app") //接收用户
.compressWith(CompressionCodecs.GZIP) //数据压缩方式
.signWith(SignatureAlgorithm.HS512, generalKey()) //加密方式
.setExpiration(new Date(currentTime + TOKEN_TIME_OUT * 1000)) //过期时间戳
.addClaims(claimMaps) //cla信息
.compact();
}
/**
* 获取token中的claims信息
*
* @param token
* @return
*/
private static Jws<Claims> getJws(String token) {
return Jwts.parser()
.setSigningKey(generalKey())
.parseClaimsJws(token);
}
/**
* 获取payload body信息
* 该方法用于解析token
* 根据token拿到Claims对象
* @param token
* @return
*/
public static Claims getClaimsBody(String token) {
try {
return getJws(token).getBody();
}catch (ExpiredJwtException e){
return null;
}
}
/**
* 获取hearder body信息
*
* @param token
* @return
*/
public static JwsHeader getHeaderBody(String token) {
return getJws(token).getHeader();
}
/**
* 是否过期
*
* @param claims
* @return -1:有效,0:有效,1:过期,2:过期
*/
public static int verifyToken(Claims claims) {
if(claims==null){
return 1;
}
try {
claims.getExpiration()
.before(new Date());
// 需要自动刷新TOKEN
if((claims.getExpiration().getTime()-System.currentTimeMillis())>REFRESH_TIME*1000){
return -1;
}else {
return 0;
}
} catch (ExpiredJwtException ex) {
return 1;
}catch (Exception e){
return 2;
}
}
/**
* 由字符串生成加密key
*
* @return
*/
public static SecretKey generalKey() {
byte[] encodedKey = Base64.getEncoder().encode(TOKEN_ENCRY_KEY.getBytes());
SecretKey key = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");
return key;
}
public static void main(String[] args) {
/* Map map = new HashMap();
map.put("id","1102");*/
System.out.println(AppJwtUtil.getToken(1102L));
Jws<Claims> jws = AppJwtUtil.getJws("eyJhbGciOiJIUzUxMiIsInppcCI6IkdaSVAifQ.H4sIAAAAAAAAADWLQQqEMAwA_5KzhURNt_qb1KZYQSi0wi6Lf9942NsMw3zh6AVW2DYmDGl2WabkZgreCaM6VXzhFBfJMcMARTqsxIG9Z888QLui3e3Tup5Pb81013KKmVzJTGo11nf9n8v4nMUaEY73DzTabjmDAAAA.4SuqQ42IGqCgBai6qd4RaVpVxTlZIWC826QA9kLvt9d-yVUw82gU47HDaSfOzgAcloZedYNNpUcd18Ne8vvjQA");
Claims claims = jws.getBody();
System.out.println(claims.get("id")); // claims.get("id") 拿到id 你输入的就是id claims实际上是个map
}
}
app前段项目集成
index.html里面的地址有app就会访问下面的地址然后到upstream
反向代理找到网关 反向代理中间件帮后端找到网关服务器
nginx -s reload
自媒体前端搭建 nginx
D:\file\001developer\nginx-1.18.0\conf\leadnews.conf
一个nginx可以访问多了conf文件 可以有多个静态页面
多了一个heima-leadnews-wemedia.conf
重启的话 nginx -s reload
jwt java web token 以及 拦截器和生效 token被全局过滤器拿到然后把id设置到headers请求 然后拦截器拦截
对称加密 密钥都一样 加密解密 TOKEN_ENCRY_KEY都一样
非对称加密 KEY不一样
不可逆加密 token解析不出来token
A.B.C
文件参数 文件上传 heima-file-starter minio工具
heima创建的一个依赖
package com.heima.utils.thread;
import com.heima.model.wemedia.pojos.WmUser;
public class WmThreadLocalUtil {
private static final ThreadLocal<WmUser> WM_USER_THREAD_LOCAL = new ThreadLocal<>();
//存入线程 静态方法方便调用
public static void setUser(WmUser wmUser){
WM_USER_THREAD_LOCAL.set(wmUser);
}
//从线程中获取
public static WmUser getUser(){
return WM_USER_THREAD_LOCAL.get();
}
//清理
public static void clear(){
WM_USER_THREAD_LOCAL.remove();
}
}
package com.heima.wemedia.config;
import com.heima.wemedia.interceptor.WmTokenInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
//使得拦截器生效
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new WmTokenInterceptor()).addPathPatterns("/**");
}
}
package com.heima.wemedia.interceptor;
import com.heima.model.wemedia.pojos.WmUser;
import com.heima.utils.thread.WmThreadLocalUtil;
import org.apache.commons.lang.StringUtils;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
// public class WebMvcConfig implements WebMvcConfigurer 使得拦截器生效
public class WmTokenInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 获取用户数据
String userId = request.getHeader("userId");
if (StringUtils.isNotBlank(userId)) {
WmUser wmUser = new WmUser();
wmUser.setId(Integer.valueOf(userId));
WmThreadLocalUtil.setUser(wmUser);
}
return true;
}
/*@Override //抛异常不会走这里 清理不了数据 所以不用这个方法
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
}*/
/**
* 抛异常会走这 且用来清理数据 防止内存溢出
* @param request
* @param response
* @param handler
* @param ex
* @throws Exception
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
WmThreadLocalUtil.clear();
}
}
项目环境 project Environment 自我感觉稳定版本
maven - 3.3.9 (现在学习用的)/ 3.6.1
jdk 1.8
Intellij Idea 2020.1.2
Git