Spring Boot中几个实用的方法

也有好几周没写东西了,主要是上上周被调去前公司产品组做前端了,然后这周刚换工作也比较忙,以至于无暇他顾,着实惭愧啊。这周新工作主要是C/S项目,C端使用C#+Devexpress,S端使用的Spring MVC+Spring Boot(Spring MVC是之前老项目,这次的新业务准备用Spring Boot,所以才有了这二者)。在项目搭建过程中想到几个比较实用的思路,所以,这里就来总结记录一下。

获取用户信息

在系统中,经常会涉及到获取当前登录的用户信息。

生成Token

现在大多情况下都是实用Token来使得HTTP有状态,所以这里我们就使用Token来获取用户信息,JWT的库也是比较多比如jjwtjava-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_TIMECREATE_BYUPDATE_TIMEMODIFY_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,今天就先到这里,后面有一些技巧,再持续更新吧。由于个人能力有限,若有不正确的地方,还请指正。万分感谢。

Spring Boot中几个实用的方法

https://blogs.52fx.biz/posts/3304155076.html

作者

eyiadmin

发布于

2020-02-29

更新于

2024-05-31

许可协议

评论