也有好几周没写东西了,主要是上上周被调去前公司产品组做前端了,然后这周刚换工作也比较忙,以至于无暇他顾,着实惭愧啊。这周新工作主要是C/S项目,C端使用C#+Devexpress,S端使用的Spring MVC+Spring Boot(Spring MVC是之前老项目,这次的新业务准备用Spring Boot,所以才有了这二者)。在项目搭建过程中想到几个比较实用的思路,所以,这里就来总结记录一下。
获取用户信息
在系统中,经常会涉及到获取当前登录的用户信息。
生成Token
现在大多情况下都是实用Token
来使得HTTP
有状态,所以这里我们就使用Token来获取用户信息,JWT的库也是比较多比如jjwt、java-jwt。
这里我选用的java-jwt
,需要加入其依赖包
1 2 3 4 5 6 7
| <!-- https://mvnrepository.com/artifact/com.auth0/java-jwt --> <dependency> <groupId>com.auth0</groupId> <artifactId>java-jwt</artifactId> <version>3.10.0</version> </dependency>
|
然后新增一个TokenUtil
类,用于生成和解码token
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 36 37 38 39 40 41
| public class TokenUtil {
private static final String SECENT_KEY = "eyiadmin";
public static String sign(String userId) { Algorithm algorithm = Algorithm.HMAC256(SECENT_KEY); String token = JWT.create() .withIssuer("auth0") .withClaim("userId",userId) .sign(algorithm); return token; }
public static boolean verify(String token) { try { Algorithm algorithm = Algorithm.HMAC256(SECENT_KEY); JWTVerifier verifier = JWT.require(algorithm) .withIssuer("auth0") .build(); //Reusable verifier instance DecodedJWT jwt = verifier.verify(token); return true; } catch (JWTVerificationException exception){ //Invalid signature/claims return false; } }
public static DecodedJWT getClaims(String token) { try { Algorithm algorithm = Algorithm.HMAC256(SECENT_KEY); JWTVerifier verifier = JWT.require(algorithm) .withIssuer("auth0") .build(); //Reusable verifier instance DecodedJWT jwt = verifier.verify(token); return jwt; } catch (JWTVerificationException exception){ //Invalid signature/claims return null; } } }
|
然后我们来生成一个Token,首先我创建了一个UserVO
和一个AuthController
类
1 2 3 4 5 6 7 8 9 10
| @Data public class UserVO { @NotEmpty(message ="用户名不能为空") @ApiModelProperty("用户名") private String userName;
@NotEmpty(message ="密码不能为空") @ApiModelProperty("密码") private String password; }
|
1 2 3 4 5 6 7 8 9 10 11 12
| @RequestMapping("/v1/auth") @RestController @Api(tags = "jwt") public class AuthController { @PostMapping("/sign") public ResponseResult<?> Sign(@Valid UserVO userVO) { String token= TokenUtil.sign(userVO.getUserName()); return ResponseResult.success(token); }
}
|
获取用户信息
接下来我们就用过Token来获取用户信息,这里我们新建一下几个类
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
| public class User { public String getUserName() { return userName; }
public void setUserName(String userName) { this.userName = userName; }
private String userName; }
public class CurrentUserContext {
private static ThreadLocal<User> context = new ThreadLocal<User>();
public static void setUser(User user) { context.set(user); }
public static User getUser() { return context.get(); }
}
|
创建一个拦截器
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 36 37 38 39
| @Component public class UserHandlerInterceptor extends HandlerInterceptorAdapter { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { if (handler instanceof HandlerMethod) { return getUser(request, response); } return super.preHandle(request, response, handler); }
private boolean getUser(HttpServletRequest request, HttpServletResponse response) throws Exception { String token = request.getHeader("token"); if (StringUtils.isEmpty(token)) { toLogin(request, response); return false; } DecodedJWT jwt = TokenUtil.getClaims(token); if (jwt == null) { toLogin(request, response); return false; } User user=new User(); String userName = jwt.getClaim("userId").asString(); user.setUserName(userName); CurrentUserContext.setUser(user); return true; }
private void toLogin(HttpServletRequest request, HttpServletResponse response) throws Exception { response.setStatus(401); response.setCharacterEncoding("utf-8"); response.setContentType("application/json;charset=" + "utf-8"); String noLogin = "{\n" + "\"code\": \"401\"\n," + "\"msg\":\"未获取到用户信息\"\n" + "}"; response.getWriter().write(noLogin); }
}
|
配置我们的拦截器
1 2 3 4 5 6 7 8 9
| @Configuration public class WebConfig implements WebMvcConfigurer { @Autowired private UserHandlerInterceptor userHandlerInterceptor;
@Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(userHandlerInterceptor).excludePathPatterns("/v1/auth/*").addPathPatterns("/**"); //配置拦截规则 }
|
然后新建一个http请求接口
1 2 3 4 5 6
| @ApiOperation(value = "user", notes = "获取用户信息") @GetMapping("/user") public ResponseResult<?> getUser() { return ResponseResult.success(CurrentUserContext.getUser().getUserName()); }
|
最后我们在请求头中加入token
,调用接口
可以看到最后的结果,那么我们再来尝试一下更骚的操作,通过注解获取用户信息
这里我们新建一个注解
1 2 3 4 5
| @Target({PARAMETER }) @Retention(RUNTIME) @Documented public @interface UserName { }
|
在新建一个HandlerMethodArgumentResolver
类
1 2 3 4 5 6 7 8 9 10 11 12
| @Component public class CurrentUserMethodArgumentResolver implements HandlerMethodArgumentResolver { @Override public boolean supportsParameter(MethodParameter methodParameter) { return methodParameter.hasParameterAnnotation(UserName.class); }
@Override public Object resolveArgument(MethodParameter methodParameter, ModelAndViewContainer modelAndViewContainer, NativeWebRequest nativeWebRequest, WebDataBinderFactory webDataBinderFactory) throws Exception { return CurrentUserContext.getUser().getUserName(); } }
|
再在我们的WebMvcConfigurer
添加进去,最后代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| @Configuration public class WebConfig implements WebMvcConfigurer { @Autowired private UserHandlerInterceptor userHandlerInterceptor;
@Autowired private CurrentUserMethodArgumentResolver currentUserMethodArgumentResolver;
@Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(userHandlerInterceptor).excludePathPatterns("/v1/auth/*").addPathPatterns("/**"); //配置拦截规则 }
@Override public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers){ resolvers.add(currentUserMethodArgumentResolver); } }
|
这时候我们来修改一下刚才的接口方法
1 2 3 4 5 6
| @ApiOperation(value = "user", notes = "获取用户信息") @GetMapping("/user") public ResponseResult<?> getUser(@UserName String userName) { return ResponseResult.success(userName); }
|
最后效果如下
自动更新字段
在我们系统中,大部分表都涉及到了CREATE_TIME
、CREATE_BY
、UPDATE_TIME
、MODIFY_BY
,如果每次添加/修改的时候,都手动去操作这些公共字段,显得有些重复,所以是想着将它们处理一下,由于我们是用的Mybatis-Plus
,可以看看相关资料https://mp.baomidou.com/guide/auto-fill-metainfo.html,这里需要继承自接口MetaObjectHandler
来实现拦截处理,,因为是公共字段,那么我们先把这几个字段抽离成一个基类BaseEntity
,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| @Data public class BaseEntity { @TableField(value = "create_time", fill = FieldFill.INSERT) private Date createTime;
@TableField(value = "create_by", fill = FieldFill.INSERT) private String createBy;
@TableField(value = "update_time", fill = FieldFill.INSERT_UPDATE) private Date updateTime;
@TableField(value = "modify_by", fill = FieldFill.INSERT_UPDATE) private String modifyBy;
}
|
注意!这里的@TableField(value = "modify_by", fill = FieldFill.INSERT)
是标记为填充字段,FieldFill
是个枚举类型
1 2 3 4 5 6 7 8 9
| public enum FieldFill { DEFAULT, //默认不处理 INSERT, //插入填充字段 UPDATE, //更新填充字段 INSERT_UPDATE; //插入和更新填充字段
private FieldFill() { } }
|
接下来新建一个MetaObjectHandler
实现类,类名为:AutoUpdateFieldsMetaHandler
,具体实现如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| @Slf4j @Component public class AutoUpdateFieldsMetaHandler implements MetaObjectHandler { @Override public void insertFill(MetaObject metaObject) { log.info("start insert fill ...."); this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now()); this.strictInsertFill(metaObject, "createBy", String.class, CurrentUserContext.getUser().getUserName()); this.strictInsertFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now()); this.strictInsertFill(metaObject, "modifyBy", String.class, CurrentUserContext.getUser().getUserName()); }
@Override public void updateFill(MetaObject metaObject) { log.info("start update fill ...."); this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now()); this.strictUpdateFill(metaObject, "modifyBy", String.class, CurrentUserContext.getUser().getUserName()); } }
|
我们在类上加入@Component
标记,会自动加载到IOC容器中,并注入到Mybatis-Plus
的配置中,另外这里是直接给属性赋值,最好是结合实际业务,先判断,再赋值,避免出现不必要的麻烦,例如:
1 2 3 4 5
| Object time= this.getFieldValByName("createTime",metaObject); if(null==time) { this.strictInsertFill(metaObject, "create_time", LocalDateTime.class, LocalDateTime.now()); }
|
注意,这里的字段名称,是实体的属性名称,而非数据库的字段名,因为在这里是需要给实体属性赋值。
接下来,我们来测试一下这个功能,首先我创建一个类
1 2 3 4 5 6 7 8
| @Data @TableName("auto_test") @NoArgsConstructor @AllArgsConstructor public class AutoTest extends BaseEntity { @TableField("content") private String content; }
|
在创建一个Mapper
接口类
1 2 3 4
| @Mapper public interface AutoTestMapper extends BaseMapper<AutoTest> { }
|
现在我们来调用一下
1 2 3 4 5 6 7 8 9
| @ApiOperation(value = "test", notes = "测试自动更新") @GetMapping("/test") public ResponseResult<?> autoUpdate() { AutoTest test=new AutoTest(); test.setContent("测试"); autoTestMapper.insert(test); return ResponseResult.success(); }
|
最后可以看到结果
1 2 3
| 2020-02-29 19:35:43.658 DEBUG org.apache.ibatis.logging.jdbc.BaseJdbcLogger.debug(BaseJdbcLogger.java:143) 2CNU7X5OLAUE004 --- [nio-8080-exec-1] c.e.d.m.m.A.insert : ==> Preparing: INSERT INTO auto_test ( content, create_time, create_by, update_time, modify_by ) VALUES ( ?, ?, ?, ?, ? ) 2020-02-29 19:35:43.749 DEBUG org.apache.ibatis.logging.jdbc.BaseJdbcLogger.debug(BaseJdbcLogger.java:143) 2CNU7X5OLAUE004 --- [nio-8080-exec-1] c.e.d.m.m.A.insert : ==> Parameters: 测试(String), 2020-02-29T19:35:43.647(LocalDateTime), eyiadmin(String), 2020-02-29T19:35:43.647(LocalDateTime), eyiadmin(String) 2020-02-29 19:35:43.815 DEBUG org.apache.ibatis.logging.jdbc.BaseJdbcLogger.debug(BaseJdbcLogger.java:143) 2CNU7X5OLAUE004 --- [nio-8080-exec-1] c.e.d.m.m.A.insert : <== Updates: 1
|
Ok,今天就先到这里,后面有一些技巧,再持续更新吧。由于个人能力有限,若有不正确的地方,还请指正。万分感谢。