【JavaWeb】基于SpringBoot的Aop+自定义注解的方式实现接口签名源码

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();
        }
    }
}
  • 1
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 15
    评论
【资源说明】 javaweb课设基于SpringBoot+SpringMVC+Mybatis实现的手机销售后台管理系统源码.zip javaweb课设基于SpringBoot+SpringMVC+Mybatis实现的手机销售后台管理系统源码.zipjavaweb课设基于SpringBoot+SpringMVC+Mybatis实现的手机销售后台管理系统源码.zip javaweb课设基于SpringBoot+SpringMVC+Mybatis实现的手机销售后台管理系统源码.zipjavaweb课设基于SpringBoot+SpringMVC+Mybatis实现的手机销售后台管理系统源码.zipjavaweb课设基于SpringBoot+SpringMVC+Mybatis实现的手机销售后台管理系统源码.zip javaweb课设基于SpringBoot+SpringMVC+Mybatis实现的手机销售后台管理系统源码.zip javaweb课设基于SpringBoot+SpringMVC+Mybatis实现的手机销售后台管理系统源码.zipjavaweb课设基于SpringBoot+SpringMVC+Mybatis实现的手机销售后台管理系统源码.zip javaweb课设基于SpringBoot+SpringMVC+Mybatis实现的手机销售后台管理系统源码.zip 【备注】 1、该资源内项目代码都经过测试运行成功,功能ok的情况下才上传的,请放心下载使用! 2、本项目适合计算机相关专业(如计科、人工智能、通信工程、自动化、电子信息等)的在校学生、老师或者企业员工下载使用,也适合小白学习进阶,当然也可作为毕设项目、课程设计、作业、项目初期立项演示等。 3、如果基础还行,也可在此代码基础上进行修改,以实现其他功能,也可直接用于毕设、课设、作业等。 欢迎下载,沟通交流,互相学习,共同进步!
Javaweb是一种使用Java语言编写的Web开发技术,结合JSP(JavaServer Pages)和Servlet(Java Servlet)可以实现动态Web页面的开发。MySQL是一种开源的关系型数据库管理系统,可以使用SQL语言对其进行操作。 一个典型的Javaweb JSP Servlet MySQL案例源码可以如下: 1. 首先,我们可以创建一个简单的数据库表格,例如一个学生信息表格,包含学生ID、姓名和年龄等字段。 2. 创建一个数据库连接类,用于连接MySQL数据库。在这个类中,我们需要配置数据库连接参数,如数据库URL、用户名和密码等。 3. 创建一个Servlet类,用于处理前端页面请求。在这个类中,我们可以编写处理逻辑,例如查询学生信息、插入新的学生记录等操作。可以使用JDBC(Java Database Connectivity)来实现数据库的增删改查操作。 4. 创建一个JSP页面,用于展示数据。在这个页面中,可以使用JSP的标签和表达式语言来获取Servlet返回的数据,并在页面中进行展示。 5. 在web.xml文件中配置Servlet和JSP的映射关系,以及其他必要的配置。 通过以上步骤,我们可以实现一个简单的Javaweb JSP Servlet MySQL案例。用户可以通过前端页面输入查询条件,后端Servlet会将查询结果从数据库中获取并返回给JSP页面进行展示。同时,用户还可以通过前端页面提交数据,后端Servlet会将数据插入到数据库中。 这个案例可以用于教学或者实际项目开发中,通过理解和学习这个案例,可以了解Javaweb开发的基本流程,以及如何使用JSP、Servlet和MySQL进行Web开发。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 15
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

墩墩分墩

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值