svbadmin学习日志
本学习日志是使用Springboot和Vue来搭建的后台管理系统:
演示地址:http://118.31.68.110:8081/index.html
账号:root
密码:123
所有代码可以在gitbub上找到,切换到相应分支即可。【代码传送门】
正篇
第一节 spring boot 模块化构建项目
第二节 整合mybatisplus完成用户增删改查
第三节 整合springsecurity实现基于RBAC的用户登录
第四节 springsecurity结合jwt实现前后端分离开发
第五节 使用ResponseBodyAdvice格式化接口输出
第六节 springboot结合redis实现缓存策略
第七节 springboot结合rabbitmq实现队列消息
第八节 springboot结合rabbitmq实现异步邮件发送
第九节 利用springboot的aop实现行为日志管理
第十节 利用Quartz实现数据库定时备份
第十一节 springboot配置log输出到本地文件
第十二节 使用flyway对数据库进行版本管理
第十三节 springboot配合VbenAdmin实现前端登录
第十四节 springboot配合VbenAdmin实现用户CURD
第十五节 基于RBAC的权限管理VbenAdmin前端实现
第十六节 springboot 打包vue代码实现前后端统一部署
番外
2.1 数据库设计原则
3.1 配置apifox自动获取登录的token
13.1 springboot 全局捕捉filter中的异常
14.1 springsecurity整合mybatisplus出现isEnable的问题和解决方案
前言
上节提到目前的输出比较随意,正常开发需要把输出进行统一,方便前端调用。这里用到了spring boot 提供的ResponseBodyAdvice来实现。
一、创建格式化类
不管如何高大上的格式化解决方案,第一步还是要建立一个格式化类
ResultFormat
@Data
public class ResultFormat<T> {
private int status;
private String message;
private T data;
public static <T> ResultFormat<T> success(T data){
ResultFormat<T> resultFormat = new ResultFormat<>();
resultFormat.setStatus(ErrorCode.SUCCESS.getCode());
resultFormat.setMessage(ErrorCode.SUCCESS.getMessage());
resultFormat.setData(data);
return resultFormat;
}
public static <T> ResultFormat<T> fail (int code, String message){
ResultFormat<T> resultFormat = new ResultFormat<>();
resultFormat.setStatus(code);
resultFormat.setMessage(message);
return resultFormat;
}
}
ErrorCode
public enum ErrorCode {
SUCCESS(20000,"ok"),
LOGIN_FAILED(401,"未认证");
/**自定义状态码**/
private final int code;
/**自定义描述**/
private final String message;
ErrorCode(int code, String message){
this.code = code;
this.message = message;
}
public int getCode() {
return code;
}
public String getMessage() {
return message;
}
}
二、配置格式化输出
1.JwtLoginFilter
格式化下之前的登录的输出
@Override
protected void successfulAuthentication(HttpServletRequest req, HttpServletResponse resp, FilterChain chain, Authentication authResult) throws IOException, ServletException {
Collection<? extends GrantedAuthority> authorities = authResult.getAuthorities();
StringBuffer as = new StringBuffer();
for (GrantedAuthority authority : authorities) {
as.append(authority.getAuthority())
.append(",");
}
String jwt = Jwts.builder()
.claim("authorities", as)//配置用户角色
.setSubject(authResult.getName())
.setExpiration(new Date(System.currentTimeMillis() + 10 * 60 * 1000))
.signWith(SignatureAlgorithm.HS512,"sang@123")
.compact();
resp.setContentType("application/json;charset=utf-8");
PrintWriter out = resp.getWriter();
Map<String, Object> tokenMap = new HashMap<>(); // map自定义输出结构
tokenMap.put("token", jwt);
tokenMap.put("roles",authorities);
tokenMap.put("username", authResult.getName());
tokenMap.put("name", ((User) authResult.getPrincipal()).getName()); // 获得登录用户的其他信息
out.write(new ObjectMapper().writeValueAsString(ResultFormat.success(tokenMap)));
out.flush();
out.close();
}
2.CustomResponseBodyAdvice
自定义一个格式化类,继承ResponseBodyAdvice
@RestControllerAdvice
public class CustomResponseBodyAdvice implements ResponseBodyAdvice<Object> {
@Autowired
private ObjectMapper objectMapper;
@Override
public boolean supports(MethodParameter methodParameter, Class<? extends HttpMessageConverter<?>> aClass) {
return true;
}
@SneakyThrows
@Override
public Object beforeBodyWrite(Object o, MethodParameter methodParameter, MediaType mediaType, Class<? extends HttpMessageConverter<?>> aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) {
if(o instanceof String){ // 如果返回结果是字符串则要转化下
return objectMapper.writeValueAsString(ResultFormat.success(o));
}
if(o instanceof ResultFormat){ // 解决异常处理和自定义处理
return o;
}
return ResultFormat.success(o); // 直接返回
}
}
3. RestExceptionHandler
RestControllerAdvice 全局异常处理
@Slf4j
@RestControllerAdvice
public class RestExceptionHandler {
@ExceptionHandler(Exception.class)
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public ResultFormat<String> exception(Exception e) {
log.error("全局异常信息 ex={}", e.getMessage(), e);
return ResultFormat.fail(ErrorCode.CODE_500.getCode(),e.getMessage());
}
}
总结
- 注意大部分文章在格式化的beforeBodyWrite里只能处理正确的情况ResultFormat.success(o),这里加入对o的结构类型判断,当已经是ResultFormat的情况下,就直接返回就可以解决fail的输出
问题
- token过期,上节遗留问题
解决方案:JWT(json web token)中的ExpiredJwtException - filter 中无法注入jwtTokenUtil
解决方案:SecurityConfiguration中,用Bean方式注入到配置文件中,而不要用new JwtFilter
代码地址
参考文档:
SpringBoot 如何统一后端返回格式?老鸟们都是这样玩的!
springboot-安全认证security+jwt总结