文章目录
- 0.相关依赖
- 1.切面类
- 2.异常处理
- 3.自定义签名注解
- 4.映射请求头签名字段类
- 5.签名工具类
- 6.hibernate-validator校验工具类
- 7.redisTemplate配置类
- 8.测试接口
0.相关依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- spring2.X集成redis所需common-pool2,使用jedis必须依赖它-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.72</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.11</version>
</dependency>
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.14</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>27.1-jre</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.0.19.Final</version>
</dependency>
1.切面类
@Order(2)
@Aspect
@Component
@Slf4j
/**
* 通过Aop的方式实现接口签名
*/
public class ControllerValidatorAspect {
private static final String REQUEST_URL_OPEN = "";
//同一个请求多长时间内有效 10分钟
private static final Long EXPIRE_TIME = 60 * 1000 * 10L;
//同一个nonce 请求多长时间内不允许重复请求 2秒
private static final Long RESUBMIT_DURATION = 2000L;
@Autowired
private RedisTemplate redisTemplate;
@Around("execution(" +
"* com.oyjp.controller..*.*(..)) " +
"&& @annotation(com.oyjp.sign.Signature) " +
"&& (@annotation(org.springframework.web.bind.annotation.RequestMapping) " +
"|| @annotation(org.springframework.web.bind.annotation.GetMapping) " +
"|| @annotation(org.springframework.web.bind.annotation.PostMapping) " +
"|| @annotation(org.springframework.web.bind.annotation.DeleteMapping) " +
"|| @annotation(org.springframework.web.bind.annotation.PatchMapping)" +
")"
)
public Object doAround(ProceedingJoinPoint pjp) throws Throwable {//NOSONAR
HttpServletRequest request = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getRequest();
//如果不是开放的URL, 进行签名校验
if (Objects.isNull(request.getAttribute(REQUEST_URL_OPEN))) {
//获取当前方法的组件
MethodSignature methodSignature = (MethodSignature) pjp.getSignature();
Method method = methodSignature.getMethod();
Signature signature = AnnotationUtils.findAnnotation(method, Signature.class);
//验证并获取header中的相关参数
/*
(1)、appid是否合法
(2)、根据appid从配置中心中拿到appsecret
(3)、请求是否已经过时,默认10分钟
(4)、随机串是否合法
(5)、是否允许重复请求
*/
SignatureHeaders signatureHeaders = generateSignatureHeaders(signature, request);
//客户端签名
String clientSignature = signatureHeaders.getSignature();
//获取到header中的参数进行拼接
String headersToSplice = SignatureUtils.toSplice(signatureHeaders);
//拼接header参数 + 请求参数
List<String> allSplice = SignatureUtils.generateAllSplice(method, pjp.getArgs(), headersToSplice);
//服务端生成签名=>对最终的拼接结果重新生成签名信息
String serverSignature = SignatureUtils.signature(allSplice.toArray(new String[]{}), signatureHeaders.getAppsecret());
//比较客户端与服务端签名
if (!clientSignature.equals(serverSignature)) {
String message = "签名不一致... clientSignature=" + clientSignature + ", serverSignature=" + serverSignature;
log.error(message);
throw new ServiceException("WMH5001", message);
}
//SignatureContext.setSignatureHeaders(signatureHeaders);
log.info("签名验证通过, 相关信息: " + signatureHeaders);
}
try {
return pjp.proceed();
} catch (Throwable e) {//NOSONAR
throw e;
}
}
/**
* 根据request 中 header值生成SignatureHeaders实体
*
* 1.处理header name,通过工具类将header信息绑定到签名实体SignatureHeaders对象上。
* 2.验证appid是否合法。
* 3.根据appid拿到appsecret。
* 4.请求是否已经超时,默认10分钟。
* 5.随机串是否合法。
* 6.是否允许重复请求。
*/
private SignatureHeaders generateSignatureHeaders(Signature signature, HttpServletRequest request) throws Exception {//NOSONAR
//处理header name
Map<String, Object> headerMap = Collections.list(request.getHeaderNames())
.stream()
.filter(headerName -> SignatureHeaders.HEADER_NAME_SET.contains(headerName))
.collect(Collectors.toMap(headerName -> headerName.replaceAll("-", "."), headerName -> request.getHeader(headerName)));
//PropertySource propertySource = new MapPropertySource("signatureHeaders", headerMap);
//1.x将属性binding到带有@ConfigurationProperties注解的类中
/* SignatureHeaders signatureHeaders = RelaxedConfigurationBinder
.with(SignatureHeaders.class)
.setPropertySources(propertySource)
.doBind();*/
//将header信息:name=value转换成PropertySource
ConfigurationPropertySource sources = new MapConfigurationPropertySource(headerMap);
//将header信息绑定到SignatureHeaders对象
Binder binder = new Binder(sources);
SignatureHeaders signatureHeaders = binder.bind("openapi.validate", Bindable.of(SignatureHeaders.class)).get();
//根据到appid获取对应的appsecret
Optional<String> result = ValidatorUtils.validateResultProcess(signatureHeaders);
if (result.isPresent()) {
throw new ServiceException("WMH5000", result.get());
}
//根据appId获取
String appSecret = getAppSecret(signatureHeaders.getAppid());
if (StringUtils.isBlank(appSecret)) {
String errMsg = "未找到appId对应的appSecret, appId=" + signatureHeaders.getAppid();
log.error(errMsg);
throw new ServiceException("WMH5002", "未找到appId对应的appSecret");
}
//其他合法性校验
Long now = System.currentTimeMillis();
Long requestTimestamp = Long.parseLong(signatureHeaders.getTimestamp());
if ((now - requestTimestamp) > EXPIRE_TIME) {
String errMsg = "请求时间超过规定范围时间10分钟, signature=" + signatureHeaders.getSignature();
log.error(errMsg);
throw new ServiceException("WMH5000", errMsg);
}
String nonce = signatureHeaders.getNonce();
if (nonce.length() < 10) {
String errMsg = "随机串nonce长度最少为10位, nonce=" + nonce;
log.error(errMsg);
throw new ServiceException("WMH5000", errMsg);
}
if (!signature.resubmit()) {
String existNonce = (String) redisTemplate.opsForValue().get(nonce);
if (Objects.isNull(existNonce)) {
redisTemplate.opsForValue().set(nonce, nonce, RESUBMIT_DURATION, TimeUnit.MILLISECONDS);
} else {
String errMsg = "不允许重复请求, nonce=" + nonce;
log.error(errMsg);
throw new ServiceException("WMH5000", errMsg);
}
}
signatureHeaders.setAppsecret(appSecret);
return signatureHeaders;
}
/**
* 获取appId对应的secret,假数据
*
* @param appId 应用id
* @return
*/
public String getAppSecret(String appId) {
Map<String, String> map = new HashMap<>();
map.put("zs001", "d3cbeed9baf4e68673a1f69a2445359a20022b7c28ea2933dd9db9f3a29f902b");
map.put("ls001", "d3cbaeddbaf4123123a1f69a2445359a20022b7c28ea2933dd9db9f3a29f902b");
return map.get(appId);
}
}
2.异常处理
自定义异常
/**
* 多数情况下,创建自定义异常需要继承Exception,本例继承Exception的子类RuntimeException
* @author Mahc
*
*/
@Data
public class ServiceException extends RuntimeException {
private String code ; //异常对应的返回码
private String message; //异常对应的描述信息
public ServiceException(String code, String message) {
this.code = code;
this.message = message;
}
public ServiceException(String message) {
this.message = message;
}
}
全局异常捕捉处理
/**
* 全局异常捕捉处理
* @Description TODO
* @Author JianPeng OuYang
* @Date 2021/2/8 16:45
* @Version v1.0
*/
@ControllerAdvice
public class GlobalExceptionController {
@ResponseBody
@ExceptionHandler(value = ServiceException.class)
public JSONObject serviceException(Exception ex) {
ServiceException serviceException = (ServiceException) ex;
JSONObject result = new JSONObject();
result.put("code", serviceException.getCode());
result.put("message", serviceException.getMessage());
return result;
}
}
3.自定义签名注解
指定哪些接口或者哪些实体需要进行签名
/**
* 签名算法实现=>指定哪些接口或者哪些实体需要进行签名
*/
@Target({TYPE, METHOD})
@Retention(RUNTIME)
@Documented
public @interface Signature {
String ORDER_SORT = "ORDER_SORT";//按照order值排序
String ALPHA_SORT = "ALPHA_SORT";//字典顺序排序
boolean resubmit() default true;//允许重复请求
String value() default Signature.ORDER_SORT;
}
指定哪些字段需要进行签名
/**
* 签名算法实现=>指定哪些字段需要进行签名
*/
@Target({FIELD})
@Retention(RUNTIME)
@Documented
public @interface SignatureField {
//签名顺序
int order() default 0;
//字段name自定义值
String customName() default "";
//字段value自定义值
String customValue() default "";
}
4.映射请求头签名字段类
@ConfigurationProperties(prefix = "openapi.validate")
@Signature
@Data
public class SignatureHeaders {
public static final String SIGNATURE_HEADERS_PREFIX = "openapi-validate";
public static final Set<String> HEADER_NAME_SET = new HashSet<>();
private static final String HEADER_APPID = SIGNATURE_HEADERS_PREFIX + "-appid";
private static final String HEADER_TIMESTAMP = SIGNATURE_HEADERS_PREFIX + "-timestamp";
private static final String HEADER_NONCE = SIGNATURE_HEADERS_PREFIX + "-nonce";
private static final String HEADER_SIGNATURE = SIGNATURE_HEADERS_PREFIX + "-signature";
static {
HEADER_NAME_SET.add(HEADER_APPID);
HEADER_NAME_SET.add(HEADER_TIMESTAMP);
HEADER_NAME_SET.add(HEADER_NONCE);
HEADER_NAME_SET.add(HEADER_SIGNATURE);
}
/**
* 线下分配的值
* 客户端和服务端各自保存appId对应的appSecret
*/
@NotBlank(message = "Header中缺少" + HEADER_APPID)
@SignatureField
private String appid;
/**
* 线下分配的值
* 客户端和服务端各自保存,与appId对应
*/
@SignatureField
private String appsecret;
/**
* 时间戳,单位: ms
*/
@NotBlank(message = "Header中缺少" + HEADER_TIMESTAMP, groups = ValidatorUtils.ValidatorGroup.First.class)
@SignatureField
private String timestamp;
/**
* 流水号【防止重复提交】; (备注:针对查询接口,流水号只用于日志落地,便于后期日志核查; 针对办理类接口需校验流水号在有效期内的唯一性,以避免重复请求)
* => 流水号/随机串:至少16位,有效期内防重复提交
*/
@NotBlank(message = "Header中缺少" + HEADER_NONCE)
@SignatureField
private String nonce;
/**
* 签名
*/
@NotBlank(message = "Header中缺少" + HEADER_SIGNATURE)
private String signature;
}
5.签名工具类
@Slf4j
public class SignatureUtils {
private static final byte XOR_TOKEN = (byte) 0xFF;
private static final String NOT_FOUND = "$_$";
private static final String DELIMETER = "^_^";
/**
* 生成header中的参数,mehtod中的参数的拼接
*
* 在控制层切面内执行,可以在方法执行之前获取到已经绑定好的入参。
* 分别对注有@PathVariable、@RequestParam、@RequestBody、@ModelAttribute注解的参数进行参数拼接的处理。
* 其中注@RequestParam注解的参数需要特殊处理一下,分别考虑数组、集合、原始类型这三种情况。
*/
public static List<String> generateAllSplice(Method method, Object[] args, String headersToSplice) {
String beanParams = StringUtils.EMPTY;
//保存Path上面的数据
List<String> pathVariableList = Lists.newArrayList();
//保存query上面的参数
List<String> requestParamList = Lists.newArrayList();
//获取方法参数个数并循环处理
for (int i = 0; i < method.getParameterCount(); ++i) {
//获取方法参数
MethodParameter methodParameter = new MethodParameter(method, i);
//方法参数上面是否存在 PathVariable、RequestParam、RequestBody、ModelAttribute注解
boolean findSignature = false;
//获取方法参数上面的注解
for (Annotation anno : methodParameter.getParameterAnnotations()) {
//如果参数上面有PathVariable注解
if (anno instanceof PathVariable) {
if (!Objects.isNull(args[i])) {
pathVariableList.add(args[i].toString());
}
//找到了PathVariable注解
findSignature = true;
//如果参数上面有RequestParam注解
} else if (anno instanceof RequestParam) {
RequestParam requestParam = (RequestParam) anno;
//获取参数名
String paramName = methodParameter.getParameterName();
if (StringUtils.isNotBlank(requestParam.name())) {
paramName = requestParam.name();
}
//获取参数值
if (!Objects.isNull(args[i])) {
List<String> paramValues = Lists.newArrayList();
if (args[i].getClass().isArray()) {
//数组
for (int j = 0; j < Array.getLength(args[i]); ++j) {
paramValues.add(Array.get(args[i], j).toString());
}
} else if (ClassUtils.isAssignable(Collection.class, args[i].getClass())) {
//集合
for (Object o : (Collection<?>) args[i]) {
paramValues.add(o.toString());
}
} else {
//单个值
paramValues.add(args[i].toString());
}
paramValues.sort(Comparator.naturalOrder());
requestParamList.add(paramName + "=" + StringUtils.join(paramValues));
}
//找到了RequestParam注解
findSignature = true;
//如果参数上面有RequestBody || ModelAttribute注解
} else if (anno instanceof RequestBody || anno instanceof ModelAttribute) {
beanParams = SignatureUtils.toSplice(args[i]);
//找到了RequestBody || ModelAttribute注解
findSignature = true;
}
//有以上注解注解退出,本次循环,开始一次循环
if (findSignature) {
break;
}
}
if (!findSignature) {
log.info("签名未识别的注解, method={}, parameter={}, annotations={}", method.getName(), methodParameter.getParameterName(), StringUtils.join(methodParameter.getMethodAnnotations()));
}
}
List<String> toSpliceList = Lists.newArrayList();
//加入请求头鉴权参数
toSpliceList.add(headersToSplice);
//加入path上面参数
toSpliceList.addAll(pathVariableList);
//将query上面的参数自然排序
requestParamList.sort(Comparator.naturalOrder());
//将query参数集合合并到toSpliceList中
toSpliceList.addAll(requestParamList);
toSpliceList.add(beanParams);
return toSpliceList;
}
/**
* 客户端调用
*
* @param signatureHeaders header中需要的参数
* @param pathParams @PathVariable 需要的参数
* @param requestParamMap @RequestParam需要的参数
* @param entity @ModelAttribute 或者 @RequestBody需要的参数
*/
public static String signature(SignatureHeaders signatureHeaders, List<String> pathParams, Map<String, Object> requestParamMap, Object entity) {
List<String> requestParams = Collections.EMPTY_LIST;
List<String> pathVariables = Collections.EMPTY_LIST;
String beanParams = StringUtils.EMPTY;
if (!CollectionUtils.isEmpty(pathParams)) {
pathVariables = pathParams;
}
if (!CollectionUtils.isEmpty(requestParamMap)) {
requestParams = new ArrayList<>();
for (Map.Entry<String, Object> entry : requestParamMap.entrySet()) {
String key = entry.getKey();
Object value = entry.getValue();
List<String> values = Lists.newArrayList();
if (value.getClass().isArray()) {
//数组
for (int j = 0; j < Array.getLength(value); ++j) {
values.add(Array.get(value, j).toString());
}
} else if (ClassUtils.isAssignable(Collection.class, value.getClass())) {
//集合
for (Object o : (Collection<?>) value) {
values.add(o.toString());
}
} else {
//单个值
values.add(value.toString());
}
values.sort(Comparator.naturalOrder());
requestParams.add(key + "=" + StringUtils.join(values));
}
}
if (!Objects.isNull(entity)) {
beanParams = toSplice(entity);
}
String headersToSplice = SignatureUtils.toSplice(signatureHeaders);
List<String> toSplices = Lists.newArrayList();
toSplices.add(headersToSplice);
toSplices.addAll(pathVariables);
requestParams.sort(Comparator.naturalOrder());
toSplices.addAll(requestParams);
toSplices.add(beanParams);
return SignatureUtils.signature(toSplices.toArray(new String[]{}), signatureHeaders.getAppsecret());
}
/**
* 编码
*
* @param text
* @param appsecret
* @return 例如: w8rAwcXDxcDKwsM
*/
public static String encode(String text, String appsecret) {
byte token = (byte) (appsecret.hashCode() & XOR_TOKEN);
byte[] tb = text.getBytes();
for (int i = 0; i < tb.length; ++i) {
tb[i] ^= token;
}
return Base64.getEncoder().encodeToString(tb);
}
/**
* 解码
*
* @param text
* @param appsecret
* @return
*/
public static String decode(String text, String appsecret) {
byte token = (byte) (appsecret.hashCode() & XOR_TOKEN);
byte[] tb = Base64.getDecoder().decode(text);
for (int i = 0; i < tb.length; ++i) {
tb[i] ^= token;
}
return new String(tb);
}
/**
* 生成签名1
*/
public static String signature(String[] args, String appsecret) {
String splice = StringUtils.join(args, DELIMETER);
log.info("拼接结果: " + splice);
String signature = new HmacUtils(HmacAlgorithms.HMAC_SHA_256, splice).hmacHex(appsecret);
return signature;
}
/**
* 生成签名2
*/
public static String signature(Object object, String appsecret) {
if (Objects.isNull(object)) {
return StringUtils.EMPTY;
}
String splice = toSplice(object);
log.info("拼接结果: " + splice);
if (StringUtils.isBlank(splice)) {
return splice;
}
String signature = new HmacUtils(HmacAlgorithms.HMAC_SHA_256, appsecret).hmacHex(splice);
return signature;
}
/**
* 生成所有注有 SignatureField属性 key=value的 拼接
*
* 首先判断对象是否注有@Signature注解,如果有则获取签名的排序规则(key值字典序排序或者指定order的值进行排序),
* 比如排序规则是Signature.ALPHA_SORT(字典顺序)会调用alphaSignature方法生成key=value的拼接串;
* 如果对象没有@Signature注解,该对象类型可能是数组、者集合类等,则调用toString方法生成key=value的拼接串。
*/
public static String toSplice(Object object) {
//空
if (Objects.isNull(object)) {
return StringUtils.EMPTY;
}
//判断当前元素上面是否存在指定类型的注解
if (isAnnotated(object.getClass(), Signature.class)) {
//获取当前元素上面指定类型的注解
Signature signature = getAnnotation(object.getClass(), Signature.class);
//根据注解value判断采取什么方式拼接参数
switch (signature.value()) {
//字典顺序排序
case Signature.ALPHA_SORT:
return alphaSignature(object);
//按照order值排序
case Signature.ORDER_SORT:
return orderSignature(object);
//默认按照字典顺序排序
default:
return alphaSignature(object);
}
}
//拼接对象字段属性值,并返回
return toString(object);
}
/**
* 生成唯一nonce随机数
* <p>
* 仅供参考,不一定非得使用该方法
*/
public static String generateNonce() {
return new HmacUtils(HmacAlgorithms.HMAC_SHA_256, UUID.randomUUID().toString()).hmacHex(RandomStringUtils.random(10, true, true));
}
/**
* 字典顺序排序
*
* 通过反射获取到对象的所有Field属性,需要判断两种情况:
* (1)获取该Field属性对应的Class信息,如果Class信息含有@Signature注解,则调用toSplice方法生成key=value的拼接串;
* (2)该Field属性含有@SignatureField注解,调用toString方法生成key=value的拼接串。
* @param object
* @return
*/
private static String alphaSignature(Object object) {
StringBuilder result = new StringBuilder();
Map<String, String> sortTreeMap = new TreeMap<>();
//获取当前对象的所有字段
for (Field field : getAllFields(object.getClass())) {
//如果注释SignatureField存在当前字段上,
if (field.isAnnotationPresent(SignatureField.class)) {
field.setAccessible(true);
try {
//如果当前字段存在 Signature 注解
if (isAnnotated(field.getType(), Signature.class)) {
//字段值不为空,保存字段值到 OrderNode中并加入集合
if (Objects.nonNull(field.get(object))) {
sortTreeMap.put(field.getName(), toSplice(field.get(object)));
}
//如果当前字段不存在 Signature 注解
} else {
SignatureField signatureField = field.getAnnotation(SignatureField.class);
if (StringUtils.isNotEmpty(signatureField.customValue()) || Objects.nonNull(field.get(object))) {
//SignatureField注解的customName值存在就优先去customName,否则取字段名
String name = StringUtils.isNotBlank(signatureField.customName()) ? signatureField.customName() : field.getName();
//SignatureField注解的customValue值存在就优先去customValue,否则取字段值
String value = StringUtils.isNotEmpty(signatureField.customValue()) ? signatureField.customValue() : toString(field.get(object));
sortTreeMap.put(name, value);
}
}
} catch (Exception e) {
log.error("签名拼接(alphaSignature)异常", e);
}
}
}
//拼接返回所有的OrderNode 格式为 name1=value1DELIMETERname2=value2DELIMETER
for (Iterator<Map.Entry<String, String>> iterator = sortTreeMap.entrySet().iterator(); iterator.hasNext(); ) {
Map.Entry<String, String> entry = iterator.next();
result.append(entry.getKey()).append("=").append(entry.getValue());
if (iterator.hasNext()) {
result.append(DELIMETER);
}
}
return result.toString();
}
/**
* 按照 @SignatureField 的order值排序
*
* @param object
* @return
*/
private static String orderSignature(Object object) {
StringBuilder result = new StringBuilder();
Set<OrderNode> orderNodeSet = new TreeSet<>();
//获取当前对象的所有字段
for (Field field : getAllFields(object.getClass())) {
//如果当前字段存在SignatureField注解
if (field.isAnnotationPresent(SignatureField.class)) {
field.setAccessible(true);
//获取当前字段上的SignatureField注解
SignatureField signatureField = field.getAnnotation(SignatureField.class);
try {
//如果当前字段存在 Signature 注解
if (isAnnotated(field.getType(), Signature.class)) {
//字段值不为空,保存字段值到 OrderNode中并加入集合
if (Objects.nonNull(field.get(object))) {
orderNodeSet.add(new OrderNode(signatureField.order(), field.getName(), toSplice(field.get(object))));
}
//如果当前字段不存在 Signature 注解
} else {
//SignatureField注解的customValue值不为空 或者 字段值不为空
if (StringUtils.isNotEmpty(signatureField.customValue()) || Objects.nonNull(field.get(object))) {
//SignatureField注解的customName值存在就优先去customName,否则取字段名
String name = StringUtils.isNotBlank(signatureField.customName()) ? signatureField.customName() : field.getName();
//SignatureField注解的customValue值存在就优先去customValue,否则取字段值
String value = StringUtils.isNotEmpty(signatureField.customValue()) ? signatureField.customValue() : toString(field.get(object));
orderNodeSet.add(new OrderNode(signatureField.order(), name, value));
}
}
} catch (Exception e) {
log.error("签名拼接(orderSignature)异常", e);
}
}
}
//拼接返回所有的OrderNode 格式为 name1=value1DELIMETERname2=value2DELIMETER
for (Iterator<OrderNode> iterator = orderNodeSet.iterator(); iterator.hasNext(); ) {
OrderNode node = iterator.next();
result.append(node.getName()).append("=").append(node.getValue());
if (iterator.hasNext()) {
result.append(DELIMETER);
}
}
return result.toString();
}
/**
* 拼接对象字段属性值,并返回
* 针对array, collection, simple property, map类型的数据做处理。
* 其中如果对象是java的simple property类型,直接调用对象的toString方法返回value;
* 如果是array、collection、map类型的数据,再调用toSplice方法生成key=value的拼接串。
* @param object
* @return
*/
private static String toString(Object object) {
Class<?> clazz = object.getClass();
if (BeanUtils.isSimpleProperty(clazz)) {
return object.toString();
}
//如果是数组
if (clazz.isArray()) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < Array.getLength(object); ++i) {
sb.append(toSplice(Array.get(object, i)));
}
return sb.toString();
}
//如果是集合
if (ClassUtils.isAssignable(Collection.class, clazz)) {
StringBuilder sb = new StringBuilder();
for (Iterator<?> iterator = ((Collection<?>) object).iterator(); iterator.hasNext(); ) {
sb.append(toSplice(iterator.next()));
if (iterator.hasNext()) {
sb.append(DELIMETER);
}
}
return sb.toString();
}
//如果是Map
if (ClassUtils.isAssignable(Map.class, clazz)) {
StringBuilder sb = new StringBuilder();
for (Iterator<? extends Map.Entry<String, ?>> iterator = ((Map<String, ?>) object).entrySet().iterator(); iterator.hasNext(); ) {
Map.Entry<String, ?> entry = iterator.next();
if (Objects.isNull(entry.getValue())) {
continue;
}
sb.append(entry.getKey()).append("=").append(toSplice(entry.getValue()));
if (iterator.hasNext()) {
sb.append(DELIMETER);
}
}
return sb.toString();
}
//默认返回 $_$
return NOT_FOUND;
}
/**
* 获取当前元素上面指定类型的注解
*
* @param element
* @param annotationType
* @param <A>
* @return
*/
private static <A extends Annotation> A getAnnotation(AnnotatedElement element, Class<A> annotationType) {
A annotation = element.getAnnotation(annotationType);
return annotation;
}
/**
* 判断当前元素上面是否存在指定类型的注解
*
* @param element
* @param annotationType
* @return
*/
private static boolean isAnnotated(AnnotatedElement element, Class<? extends Annotation> annotationType) {
return element.isAnnotationPresent(annotationType);
}
/**
* 获取当前对象的所有字段
*
* @param type
* @return
*/
public static Set<Field> getAllFields(final Class<?> type) {
Set<Field> result = new HashSet<>(16);
for (Class<?> t : getAllSuperTypes(type)) {
result.addAll(Arrays.asList(t.getDeclaredFields()));
}
return result;
}
private static Set<Class<?>> getAllSuperTypes(final Class<?> type) {
Set<Class<?>> result = new LinkedHashSet<>(16);
if (type != null && !type.equals(Object.class)) {
result.add(type);
for (Class<?> supertype : getSuperTypes(type)) {
result.addAll(getAllSuperTypes(supertype));
}
}
return result;
}
private static Set<Class<?>> getSuperTypes(Class<?> type) {
Set<Class<?>> result = new LinkedHashSet<>();
Class<?> superclass = type.getSuperclass();
Class<?>[] interfaces = type.getInterfaces();
if (superclass != null && !superclass.equals(Object.class)) {
result.add(superclass);
}
if (interfaces != null && interfaces.length > 0) {
result.addAll(Arrays.asList(interfaces));
}
return result;
}
@Data
@AllArgsConstructor
@NoArgsConstructor
private static class OrderNode implements Comparable<OrderNode> {
private int order;
private String name;
private String value;
//按照order进行排序
@Override
public int compareTo(OrderNode o) {
if (this.order == o.order) {
return this.name.compareTo(o.name);
}
return this.order - o.order;
}
@Override
public boolean equals(Object obj) {
return super.equals(obj);
}
@Override
public int hashCode() {
return Objects.hash(order, value);
}
}
}
6.hibernate-validator校验工具类
/**
* 为什么要使用这个工具类呢?
* 1、controller方法中不用加入BindingResult参数
* 2、controller方法中需要校验的参数也不需要加入@Valid或者@Validated注解
* <p>
* 具体使用
* 在controller方法或者全局拦截校验器中调用 ValidatorUtils.validateResultProcess(需要校验的Bean) 直接获取校验的结果。
*
**/
@Component
public class ValidatorUtils implements ApplicationContextAware {
//jackson的对象映射类
private static final ObjectMapper objectMapper = new ObjectMapper();
private static Validator validator;
/*
* 校验bean并返回所有验证失败信息
* @param obj 当前校验对象
* @param groups 当前校验的组
* @return 如: Optional[[{"propertyPath":"Foo.password","message":"password为NULL"},{"propertyPath":"Foo.userType","message":"userType为BLANK"}]]
* @throws ServiceException
*/
public static Optional<String> validateResultProcess(Object obj, Class<?>... groups) throws ServiceException {
// 用验证器执行验证,返回一个验证失败的set集合
Set<ConstraintViolation<Object>> results = validator.validate(obj,groups);
// 判断是否为空,空:说明验证通过,否则就验证失败
if (CollectionUtils.isEmpty(results)) {
return Optional.empty();
}
List<ErrorMessage> errorMessages = results.stream()
//将results转换成 List<ErrorMessage>返回
.map(result -> {
try {
List<ErrorMessage> childErrorMessages = objectMapper.readValue(result.getMessage(), new TypeReference<List<ErrorMessage>>() {
});
return childErrorMessages;
} catch (Exception e) {
ErrorMessage errorMessage = new ErrorMessage();
errorMessage.setPropertyPath(String.format("%s.%s", result.getRootBeanClass().getSimpleName(), result.getPropertyPath().toString()));
errorMessage.setMessage(result.getMessage());
return Arrays.asList(errorMessage);
}
})
//合并 map操作转换成的多个 List<ErrorMessage>为一个
.flatMap(errorMessageList -> errorMessageList.stream())
.collect(Collectors.toList());
try {
return Optional.of(objectMapper.writeValueAsString(errorMessages));
} catch (JsonProcessingException e) {
throw new ServiceException("JsonProcessingException " + e.getMessage());
}
}
/**
* 校验bean校验失败抛出自定义异常 ServiceException
* @param obj 当前校验对象
* @param groups 当前校验的组
* @throws ServiceException
*/
public static void validateResultProcessWithException(Object obj, Class<?>... groups) throws ServiceException {
Optional<String> validateResult = ValidatorUtils.validateResultProcess(obj,groups);
if (validateResult.isPresent()) {
throw new ServiceException(validateResult.get());
}
}
/**
* 初始化validator 对象
* @param applicationContext
* @throws BeansException
*/
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
//获取Hibernate validator 的 validator
//ValidatorUtils.validator = Validation.buildDefaultValidatorFactory().getValidator();
//通过Spring 封装的 LocalValidatorFactoryBean获取validator
ValidatorUtils.validator = (Validator) applicationContext.getBean("validator");
/*
@Bean
public Validator validator() {
return new LocalValidatorFactoryBean();
}
*/
}
/**
* 校验分组
*/
public static class ValidatorGroup {
public interface First extends Default { }
public interface Second extends Default { }
public interface Third extends Default { }
}
/**
* 错误信息封装
*/
public static class ErrorMessage {
private String propertyPath;
private String message;
public String getPropertyPath() { return propertyPath; }
public void setPropertyPath(String propertyPath) { this.propertyPath = propertyPath; }
public String getMessage() { return message; }
public void setMessage(String message) { this.message = message; }
}
}
7.redisTemplate配置类
解决了redisTemplate反序列化失败异常
@Configuration
public class RedisConfig {
@Bean
public <T> RedisTemplate<String, T> redisTemplateKeyString(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<String, T> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory);
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
// 自定义key序列化方式,直接将String字符串直接作为redis中的key
redisTemplate.setKeySerializer(new StringRedisSerializer());
// 自定义value序列化方式,序列化成json格式
redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
/**
* 配置其他类型的redisTemplate
***/
@Bean
public RedisTemplate<Object, Object> redisTemplateKeyObject(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory);
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
}
8.测试接口
@RestController
@RequestMapping("/example")
public class ExampleController {
@PostMapping(value = "test/{var1}/{var2}", produces = MediaType.ALL_VALUE)
@Signature(resubmit=false)
public String myController(@PathVariable String var1
, @PathVariable String var2
, @RequestParam String var3
, @RequestParam String var4
, @RequestBody User user) {
return String.join(",", var1, var2, var3, var4, user.toString());
}
public static class User {
private String name;
private int age;
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public int getAge() { return age; }
public void setAge(int age) { this.age = age; }
@Override
public String toString() {
return new ToStringBuilder(this)
.append("name", name)
.append("age", age)
.toString();
}
}
}