自定义 Claim HandlerMethodArgumentResolver

This commit is contained in:
Shikong 2023-05-08 20:17:37 +08:00
parent b85d3b1861
commit 3b821041a3
13 changed files with 179 additions and 14 deletions

View File

@ -0,0 +1,12 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="IncorrectHttpHeaderInspection" enabled="true" level="WARNING" enabled_by_default="true">
<option name="customHeaders">
<set>
<option value="token" />
</set>
</option>
</inspection_tool>
</profile>
</component>

View File

@ -1,6 +1,9 @@
package cn.skcks.matrix.v2.advice; package cn.skcks.matrix.v2.advice;
import cn.skcks.matrix.v2.auth.claims.ClaimException;
import cn.skcks.matrix.v2.model.casbin.CasbinConstant;
import cn.skcks.matrix.v2.utils.json.JsonResponse; import cn.skcks.matrix.v2.utils.json.JsonResponse;
import cn.skcks.matrix.v2.utils.json.ResponseStatus;
import jakarta.validation.ConstraintViolationException; import jakarta.validation.ConstraintViolationException;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -25,8 +28,13 @@ import java.util.Objects;
public class ExceptionAdvice { public class ExceptionAdvice {
private final static Logger logger = LoggerFactory.getLogger(ExceptionAdvice.class); private final static Logger logger = LoggerFactory.getLogger(ExceptionAdvice.class);
@ExceptionHandler(ClaimException.class)
public JsonResponse<String> ClaimException(ClaimException e){
return JsonResponse.build(ResponseStatus.UNAUTHORIZED, e.getMessage());
}
@ExceptionHandler(MissingServletRequestParameterException.class) @ExceptionHandler(MissingServletRequestParameterException.class)
public JsonResponse<String> MissingServletRequestParameterException(MissingServletRequestParameterException e) { public JsonResponse<String> missingServletRequestParameterException(MissingServletRequestParameterException e) {
return JsonResponse.error(e.getMessage()); return JsonResponse.error(e.getMessage());
} }

View File

@ -1,29 +1,42 @@
package cn.skcks.matrix.v2.api.locatioin.record; package cn.skcks.matrix.v2.api.locatioin.record;
import cn.skcks.matrix.v2.annotation.web.JsonMapping; import cn.skcks.matrix.v2.annotation.web.JsonMapping;
import cn.skcks.matrix.v2.annotation.web.auth.Auth;
import cn.skcks.matrix.v2.auth.claims.Claim;
import cn.skcks.matrix.v2.annotation.web.methods.PostJson; import cn.skcks.matrix.v2.annotation.web.methods.PostJson;
import cn.skcks.matrix.v2.config.swagger.SwaggerConfig; import cn.skcks.matrix.v2.config.swagger.SwaggerConfig;
import cn.skcks.matrix.v2.model.jwt.dto.Claims;
import cn.skcks.matrix.v2.model.location.record.convert.LocationRecordConvertor;
import cn.skcks.matrix.v2.model.location.record.dto.LocationRecordParams; import cn.skcks.matrix.v2.model.location.record.dto.LocationRecordParams;
import cn.skcks.matrix.v2.model.location.record.dto.PageLocationRecordParams;
import cn.skcks.matrix.v2.model.location.record.vo.LocationRecordVo; import cn.skcks.matrix.v2.model.location.record.vo.LocationRecordVo;
import cn.skcks.matrix.v2.model.services.ServiceResult;
import cn.skcks.matrix.v2.orm.mybatis.dynamic.model.LocationRecord;
import cn.skcks.matrix.v2.services.jwt.JwtService;
import cn.skcks.matrix.v2.services.location.record.LocationRecordService; import cn.skcks.matrix.v2.services.location.record.LocationRecordService;
import cn.skcks.matrix.v2.utils.json.JsonResponse; import cn.skcks.matrix.v2.utils.json.JsonResponse;
import cn.skcks.matrix.v2.utils.page.PageWrapper;
import com.github.pagehelper.PageInfo;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springdoc.core.models.GroupedOpenApi; import org.springdoc.core.models.GroupedOpenApi;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import java.util.Collection; import java.util.Collection;
import java.util.List;
@Auth
@Tag(name = "定位记录") @Tag(name = "定位记录")
@RestController @RestController
@JsonMapping("/location/record") @JsonMapping("/location/record")
@RequiredArgsConstructor @RequiredArgsConstructor
@Slf4j
public class LocationRecordController { public class LocationRecordController {
private final JwtService jwtService;
private final LocationRecordService locationRecordService; private final LocationRecordService locationRecordService;
@Bean @Bean
@ -31,9 +44,21 @@ public class LocationRecordController {
return SwaggerConfig.api("Location Record", "/location/record"); return SwaggerConfig.api("Location Record", "/location/record");
} }
@PostJson("/") @PostJson("/list")
@Operation(summary = "查询记录") @Operation(summary = "查询记录")
public JsonResponse<Collection<LocationRecordVo>> getLocationRecord(@RequestBody LocationRecordParams params){ public JsonResponse<Collection<LocationRecordVo>> getLocationRecord(@Claim Claims claims, @RequestBody LocationRecordParams params){
return locationRecordService.getLocationRecord(params).parseResponse(); log.info("claims {}", claims);
ServiceResult<Collection<LocationRecord>> result = locationRecordService.getLocationRecord(params, claims.getUserId());
return JsonResponse.success(LocationRecordConvertor.INSTANCE.daoToVo(result.getResult()));
}
@PostJson("/page")
@Operation(summary = "分页查询记录")
public JsonResponse<PageWrapper<LocationRecordVo>> getPageLocationRecord(@RequestBody PageLocationRecordParams params, @RequestHeader("token") String token){
Claims claims = jwtService.parseToken(token);
PageInfo<LocationRecordVo> pageInfo =
LocationRecordConvertor.INSTANCE.daoToVo(locationRecordService.getPageLocationRecord(params, claims.getUserId()).getResult());
return JsonResponse.success(PageWrapper.of(pageInfo));
} }
} }

View File

@ -5,6 +5,7 @@ import cn.skcks.matrix.v2.annotation.web.auth.UnAuth;
import cn.skcks.matrix.v2.handler.AuthHandler; import cn.skcks.matrix.v2.handler.AuthHandler;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.lang.NonNull; import org.springframework.lang.NonNull;
@ -20,6 +21,7 @@ import java.util.List;
@Component @Component
@SuppressWarnings({"unused"}) @SuppressWarnings({"unused"})
@ConditionalOnClass({Auth.class, UnAuth.class}) @ConditionalOnClass({Auth.class, UnAuth.class})
@RequiredArgsConstructor
public class AuthorizationInterceptor implements HandlerInterceptor { public class AuthorizationInterceptor implements HandlerInterceptor {
private final List<AuthHandler> authHandlers = new ArrayList<>(); private final List<AuthHandler> authHandlers = new ArrayList<>();

View File

@ -18,9 +18,9 @@ public abstract class LocationRecordConvertor {
@Mapping(source = "id", target = "id"), @Mapping(source = "id", target = "id"),
@Mapping(source = "userId", target = "userId") @Mapping(source = "userId", target = "userId")
}) })
abstract public LocationRecordVo daoToVo(LocationRecord user); abstract public LocationRecordVo daoToVo(LocationRecord dao);
abstract public Collection<LocationRecordVo> daoToVo(Collection<LocationRecord> users); abstract public Collection<LocationRecordVo> daoToVo(Collection<LocationRecord> dao);
abstract public PageInfo<LocationRecordVo> daoToVo(PageInfo<LocationRecord> userPageInfo); abstract public PageInfo<LocationRecordVo> daoToVo(PageInfo<LocationRecord> daoPageInfo);
} }

View File

@ -1,11 +1,13 @@
package cn.skcks.matrix.v2.model.location.record.dto; package cn.skcks.matrix.v2.model.location.record.dto;
import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data; import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat; import org.springframework.format.annotation.DateTimeFormat;
import java.util.Date; import java.util.Date;
@Schema(title = "按时间段 查询定位记录列表")
@Data @Data
public class LocationRecordParams { public class LocationRecordParams {
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")

View File

@ -0,0 +1,22 @@
package cn.skcks.matrix.v2.model.location.record.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import lombok.EqualsAndHashCode;
@Schema(title = "按时间段 分页查询定位记录列表")
@EqualsAndHashCode(callSuper = true)
@Data
public class PageLocationRecordParams extends LocationRecordParams{
@Schema(description = "页数")
@NotNull(message = "page 不能为空")
@Min(value = 1, message = "page 必须为正整数")
int page;
@Schema(description = "每页条数", example = "10")
@NotNull(message = "size 不能为空")
@Min(value = 1, message = "size 必须为正整数")
int size;
}

View File

@ -0,0 +1,21 @@
package cn.skcks.matrix.v2.auth.claims;
import io.swagger.v3.oas.annotations.Hidden;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Controller 参数 注入 Claims
* 从请求头的 token 解析 Claims 并注入到 Controller 参数中
* @author Shikong
*/
@Target({ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Hidden
@SuppressWarnings({"unused"})
public @interface Claim {
boolean required() default true;
}

View File

@ -0,0 +1,7 @@
package cn.skcks.matrix.v2.auth.claims;
public class ClaimException extends Exception{
ClaimException(){
super("未登录");
}
}

View File

@ -0,0 +1,32 @@
package cn.skcks.matrix.v2.auth.claims;
import cn.skcks.matrix.v2.model.jwt.dto.Claims;
import cn.skcks.matrix.v2.services.jwt.JwtService;
import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.springframework.core.MethodParameter;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;
@RequiredArgsConstructor
@Component
public class ClaimResolver implements HandlerMethodArgumentResolver {
private final JwtService jwtService;
@Override
public boolean supportsParameter(MethodParameter methodParameter) {
return methodParameter.hasMethodAnnotation(Claim.class) || methodParameter.getParameterType().equals(Claims.class);
}
@Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
String token = webRequest.getHeader("token");
if(StringUtils.isBlank(token)){
throw new ClaimException();
}
return jwtService.parseToken(token);
}
}

View File

@ -1,5 +1,6 @@
package cn.skcks.matrix.v2.config; package cn.skcks.matrix.v2.config;
import cn.skcks.matrix.v2.auth.claims.ClaimResolver;
import cn.skcks.matrix.v2.interceptor.AuthorizationInterceptor; import cn.skcks.matrix.v2.interceptor.AuthorizationInterceptor;
import cn.skcks.matrix.v2.model.jwt.dto.Claims; import cn.skcks.matrix.v2.model.jwt.dto.Claims;
import cn.skcks.matrix.v2.services.auth.AuthService; import cn.skcks.matrix.v2.services.auth.AuthService;
@ -16,6 +17,7 @@ import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@ -34,12 +36,18 @@ public class WebConfig implements WebMvcConfigurer {
private final AuthService authService; private final AuthService authService;
private final CasbinService casbinService; private final CasbinService casbinService;
private final ClaimResolver claimResolver;
private final static JsonResponse<String> NO_LOGIN = JsonResponse.build( ResponseStatus.UNAUTHORIZED,"未登录"); private final static JsonResponse<String> NO_LOGIN = JsonResponse.build( ResponseStatus.UNAUTHORIZED,"未登录");
private final static JsonResponse<String> TOKEN_EXPIRE = JsonResponse.build(ResponseStatus.UNAUTHORIZED,"认证失效 请重新登录"); private final static JsonResponse<String> TOKEN_EXPIRE = JsonResponse.build(ResponseStatus.UNAUTHORIZED,"认证失效 请重新登录");
private final static JsonResponse<String> NO_PERMISSION = JsonResponse.build(ResponseStatus.FORBIDDEN,"无权访问"); private final static JsonResponse<String> NO_PERMISSION = JsonResponse.build(ResponseStatus.FORBIDDEN,"无权访问");
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
resolvers.add(claimResolver);
}
@Override @Override
public void addInterceptors(InterceptorRegistry registry) { public void addInterceptors(InterceptorRegistry registry) {
// 添加请求 权限校验 拦截器 // 添加请求 权限校验 拦截器
@ -93,6 +101,7 @@ public class WebConfig implements WebMvcConfigurer {
} }
Claims claims = jwtService.parseToken(token); Claims claims = jwtService.parseToken(token);
request.setAttribute("claims", claims);
log.info("[解析的令牌信息] {}", claims); log.info("[解析的令牌信息] {}", claims);
if (claims == null) { if (claims == null) {
log.info("[认证失败] 无效 token => {}", token); log.info("[认证失败] 无效 token => {}", token);

View File

@ -1,11 +1,15 @@
package cn.skcks.matrix.v2.services.location.record; package cn.skcks.matrix.v2.services.location.record;
import cn.skcks.matrix.v2.model.location.record.dto.LocationRecordParams; import cn.skcks.matrix.v2.model.location.record.dto.LocationRecordParams;
import cn.skcks.matrix.v2.model.location.record.vo.LocationRecordVo; import cn.skcks.matrix.v2.model.location.record.dto.PageLocationRecordParams;
import cn.skcks.matrix.v2.model.services.ServiceResult; import cn.skcks.matrix.v2.model.services.ServiceResult;
import cn.skcks.matrix.v2.orm.mybatis.dynamic.model.LocationRecord;
import com.github.pagehelper.PageInfo;
import java.util.Collection; import java.util.Collection;
public interface LocationRecordService { public interface LocationRecordService {
ServiceResult<Collection<LocationRecordVo>> getLocationRecord(LocationRecordParams params); ServiceResult<Collection<LocationRecord>> getLocationRecord(LocationRecordParams params, String userId);
ServiceResult<PageInfo<LocationRecord>> getPageLocationRecord(PageLocationRecordParams params, String userId);
} }

View File

@ -2,16 +2,22 @@ package cn.skcks.matrix.v2.services.location.record;
import cn.skcks.matrix.v2.model.location.record.convert.LocationRecordConvertor; import cn.skcks.matrix.v2.model.location.record.convert.LocationRecordConvertor;
import cn.skcks.matrix.v2.model.location.record.dto.LocationRecordParams; import cn.skcks.matrix.v2.model.location.record.dto.LocationRecordParams;
import cn.skcks.matrix.v2.model.location.record.dto.PageLocationRecordParams;
import cn.skcks.matrix.v2.model.location.record.vo.LocationRecordVo; import cn.skcks.matrix.v2.model.location.record.vo.LocationRecordVo;
import cn.skcks.matrix.v2.model.services.ServiceResult; import cn.skcks.matrix.v2.model.services.ServiceResult;
import cn.skcks.matrix.v2.orm.mybatis.dynamic.mapper.LocationRecordDynamicSqlSupport; import cn.skcks.matrix.v2.orm.mybatis.dynamic.mapper.LocationRecordDynamicSqlSupport;
import cn.skcks.matrix.v2.orm.mybatis.dynamic.mapper.LocationRecordMapper; import cn.skcks.matrix.v2.orm.mybatis.dynamic.mapper.LocationRecordMapper;
import cn.skcks.matrix.v2.orm.mybatis.dynamic.mapper.RoleDynamicSqlSupport; import cn.skcks.matrix.v2.orm.mybatis.dynamic.mapper.RoleDynamicSqlSupport;
import cn.skcks.matrix.v2.orm.mybatis.dynamic.model.LocationRecord; import cn.skcks.matrix.v2.orm.mybatis.dynamic.model.LocationRecord;
import cn.skcks.matrix.v2.utils.page.PageWrapper;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.mybatis.dynamic.sql.SqlBuilder; import org.mybatis.dynamic.sql.SqlBuilder;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.math.BigInteger;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
@ -20,7 +26,7 @@ import java.util.List;
public class LocationRecordServiceImpl implements LocationRecordService{ public class LocationRecordServiceImpl implements LocationRecordService{
private final LocationRecordMapper locationRecordMapper; private final LocationRecordMapper locationRecordMapper;
@Override @Override
public ServiceResult<Collection<LocationRecordVo>> getLocationRecord(LocationRecordParams params) { public ServiceResult<Collection<LocationRecord>> getLocationRecord(LocationRecordParams params, String userId) {
List<LocationRecord> recordList = locationRecordMapper.select((s)-> s.applyWhere(dsl -> { List<LocationRecord> recordList = locationRecordMapper.select((s)-> s.applyWhere(dsl -> {
if (params.getStartTime() != null) { if (params.getStartTime() != null) {
dsl.and(LocationRecordDynamicSqlSupport.locationTime, SqlBuilder.isGreaterThanOrEqualTo(params.getStartTime())); dsl.and(LocationRecordDynamicSqlSupport.locationTime, SqlBuilder.isGreaterThanOrEqualTo(params.getStartTime()));
@ -30,11 +36,26 @@ public class LocationRecordServiceImpl implements LocationRecordService{
dsl.and(LocationRecordDynamicSqlSupport.locationTime,SqlBuilder.isLessThanOrEqualTo(params.getEndTime())); dsl.and(LocationRecordDynamicSqlSupport.locationTime,SqlBuilder.isLessThanOrEqualTo(params.getEndTime()));
} }
dsl.and(LocationRecordDynamicSqlSupport.userId, SqlBuilder.isEqualTo(Long.valueOf(userId)));
dsl.and(LocationRecordDynamicSqlSupport.id, SqlBuilder.isNotNull()); dsl.and(LocationRecordDynamicSqlSupport.id, SqlBuilder.isNotNull());
}).orderBy((LocationRecordDynamicSqlSupport.id))); }).orderBy((LocationRecordDynamicSqlSupport.id)));
return ServiceResult.<Collection<LocationRecordVo>>builder() return ServiceResult.<Collection<LocationRecord>>builder()
.result(recordList.stream().map(LocationRecordConvertor.INSTANCE::daoToVo).toList()) .result(recordList)
.build();
}
@Override
public ServiceResult<PageInfo<LocationRecord>> getPageLocationRecord(PageLocationRecordParams params, String userId) {
PageInfo<LocationRecord> pageInfo;
try(Page<LocationRecord> page = PageHelper.startPage(params.getPage(),params.getSize())){
pageInfo = page.doSelectPageInfo(()->{
getLocationRecord(params, userId);
});
}
return ServiceResult.<PageInfo<LocationRecord>>builder()
.result(pageInfo)
.build(); .build();
} }
} }