first commit

This commit is contained in:
陈现府
2025-11-20 10:45:16 +08:00
commit 8355431361
2643 changed files with 291314 additions and 0 deletions
@@ -0,0 +1,28 @@
package com.mhd.user.domain.security.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @author ZhouGY
* @title Encrypt
* @description: 加密注解
* @date 2024/1/25 15:37
**/
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Encrypt {
/**
* 入参是否解密,默认解密
*/
boolean in() default true;
/**
* 返回是否加密,默认加密
*/
boolean out() default true;
}
@@ -0,0 +1,33 @@
package com.mhd.user.domain.security.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 自定义操作日志记录注解
*
* @author mhd
*/
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface OpenApiLog {
/**
* 模块
*/
public String title() default "";
/**
* 是否保存请求的参数
*/
public boolean isSaveRequestData() default true;
/**
* 是否保存响应的参数
*/
public boolean isSaveResponseData() default true;
}
@@ -0,0 +1,187 @@
package com.mhd.user.domain.security.aspect;
import cn.hutool.json.JSONUtil;
import com.mhd.common.core.domain.entity.DecryptRequestDO;
import com.mhd.common.core.domain.entity.R;
import com.mhd.common.core.domain.po.SysTenantsPo;
import com.mhd.common.core.domain.po.thirdparty.SysDepartInterfaceInfoPo;
import com.mhd.common.core.domain.thirdparty.SysDepartInterfaceInfoDTO;
import com.mhd.common.core.exception.ServiceException;
import com.mhd.common.core.feign.ThirdPartyServiceFeign;
import com.mhd.common.core.utils.AESUtil;
import com.mhd.common.core.utils.SignatureUtil;
import com.mhd.user.domain.security.annotation.Encrypt;
import com.mhd.user.infrastructure.feign.ProductServiceFeign;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.MethodParameter;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.RequestBodyAdvice;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Type;
import java.rmi.ServerException;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Function;
import java.util.stream.Collectors;
/**
* @author ZhouGY
* @title DecryptRequestBodyAdvice
* @description post请求的加密参数进行解密,返回一个JSONObject对象
* @date 2023/8/17
**/
@ControllerAdvice(basePackages = "com.mhd.user.interfaces.facade.openApi")
@Slf4j
public class DecryptRequestBodyAdvice implements RequestBodyAdvice {
@Autowired
private ThirdPartyServiceFeign thirdPartyServiceFeign;
@Autowired
private ProductServiceFeign productServiceFeign;
/**
* 该方法用于判断当前请求,是否要执行beforeBodyRead方法
* methodParameter: 方法的参数对象
* type: 方法的参数类型
* aClass: 将会使用到的Http消息转换器类类型
* 注意:此判断方法,会在beforeBodyRead 和 afterBodyRead方法前都触发一次。
*
* @return 返回true则会执行beforeBodyRead
*/
@Override
public boolean supports(MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) {
//获取配置的组织ID
R<SysTenantsPo> sysTenantsPoR = productServiceFeign.getTenantsInfoByOpen();
if (R.SUCCESS != sysTenantsPoR.getCode()) {
throw new ServiceException("未获取到组织配置信息");
}
SysTenantsPo sysTenantsPo = sysTenantsPoR.getData();
//从三方配置表中取值
SysDepartInterfaceInfoDTO sysDepartInterfaceInfoDTO = new SysDepartInterfaceInfoDTO();
sysDepartInterfaceInfoDTO.setTopOrganizationId(sysTenantsPo.getTopOrganizationId());
sysDepartInterfaceInfoDTO.setInterfaceType("openApi");
sysDepartInterfaceInfoDTO.setInterfaceTypeKey("encrypt");
List<SysDepartInterfaceInfoPo> sysDepartInterfaceInfoPos = thirdPartyServiceFeign.queryListByFeign(sysDepartInterfaceInfoDTO);
if (sysDepartInterfaceInfoPos.isEmpty()){
return false;
}
SysDepartInterfaceInfoPo sysDepartInterfaceInfoPo = sysDepartInterfaceInfoPos.get(0);
//是否开启加密
if(!"true".equals(sysDepartInterfaceInfoPo.getInterfaceTypeValue())) {
return false;
}
log.info("【进入-RequestBody-supports】");
boolean encode = false;
// 判断该方法是否含有@Encrypt注解
if (Objects.requireNonNull(methodParameter.getMethod()).isAnnotationPresent(Encrypt.class)) {
//获取注解配置的包含和去除字段
Encrypt serializedField = methodParameter.getMethodAnnotation(Encrypt.class);
if (serializedField != null && serializedField.in()) {
//入参是否需要解密
encode = true;
}
}
return encode;
}
/**
* 在Http消息转换器执转换,之前执行
* inputMessage: 客户端的请求数据
* methodParameter: 方法的参数对象
* type: 方法的参数类型
* aClass: 将会使用到的Http消息转换器类类型
*
* @return 返回 一个自定义的HttpInputMessage
*/
@Override
public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) throws IOException {
log.info("【进入-RequestBody-beforeBodyRead】");
// 解密-使用解密后的数据,构造新的读取流
return new DecryptHttpInputMessage(inputMessage);
}
/**
* 在Http消息转换器执转换,之后执行
* o: 转换后的对象
* httpInputMessage: 客户端的请求数据
* methodParameter: handler方法的参数类型
* type: handler方法的参数类型
* aClass: 使用的Http消息转换器类类型
*
* @return 返回一个新的对象
*/
@Override
public Object afterBodyRead(Object o, HttpInputMessage httpInputMessage, MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) {
log.info("【进入-RequestBody-afterBodyRead】");
return o;
}
/**
* 参数与afterBodyRead相同,不过这个方法处理的是,body为空的情况
*/
@Override
public Object handleEmptyBody(Object o, HttpInputMessage httpInputMessage, MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) {
log.info("【进入-RequestBody-handleEmptyBody】");
return o;
}
/**
* 解密-使用解密后的数据,构造新的读取流
*/
class DecryptHttpInputMessage implements HttpInputMessage {
private HttpInputMessage inputMessage;
public DecryptHttpInputMessage(HttpInputMessage inputMessage) {
this.inputMessage = inputMessage;
}
@Override
public InputStream getBody() throws IOException {
//获取配置的组织ID
R<SysTenantsPo> sysTenantsPoR = productServiceFeign.getTenantsInfoByOpen();
if (R.SUCCESS != sysTenantsPoR.getCode()) {
throw new RuntimeException("未获取到组织配置信息");
}
SysTenantsPo sysTenantsPo = sysTenantsPoR.getData();
//从三方配置表中取值
SysDepartInterfaceInfoDTO sysDepartInterfaceInfoDTO = new SysDepartInterfaceInfoDTO();
sysDepartInterfaceInfoDTO.setTopOrganizationId(sysTenantsPo.getTopOrganizationId());
sysDepartInterfaceInfoDTO.setInterfaceType("openApi");
List<SysDepartInterfaceInfoPo> sysDepartInterfaceInfoPos = thirdPartyServiceFeign.queryListByFeign(sysDepartInterfaceInfoDTO);
Map<String, SysDepartInterfaceInfoPo> stringSysDepartInterfaceInfoPoMap = sysDepartInterfaceInfoPos.stream().collect(Collectors.toMap(SysDepartInterfaceInfoPo::getInterfaceTypeKey, Function.identity()));
// 1.参数解密处理
String body = StringUtils.defaultString(IOUtils.toString(inputMessage.getBody(), "UTF-8"));
log.info("解密前 请求参数:" + body);
DecryptRequestDO decryptRequestDO = JSONUtil.toBean(JSONUtil.parseObj(body), DecryptRequestDO.class);
//2.验证参数
String secret = stringSysDepartInterfaceInfoPoMap.get(SignatureUtil.SECRET_KEY).getInterfaceTypeValue();
boolean flag = SignatureUtil.validateSign(decryptRequestDO, secret);
if (!flag){
throw new RuntimeException("未通过签名验证,请检查签名信息是否有误");
}
// 4.用服务端私钥进行解密
String iv = stringSysDepartInterfaceInfoPoMap.get(SignatureUtil.IV_KEY).getInterfaceTypeValue();
String result = AESUtil.decrypt(decryptRequestDO.getEncryptContent(),secret,iv);
log.info("解密后 请求参数:{}", result);
return IOUtils.toInputStream(result, "UTF-8");
}
@Override
public HttpHeaders getHeaders() {
return inputMessage.getHeaders();
}
}
}
@@ -0,0 +1,176 @@
package com.mhd.user.domain.security.aspect;
import com.alibaba.fastjson2.JSON;
import com.mhd.common.core.domain.dto.open.SystemOpenApiLogDTO;
import com.mhd.common.core.domain.entity.R;
import com.mhd.common.core.domain.po.SysTenantsPo;
import com.mhd.common.core.exception.ServiceException;
import com.mhd.common.core.utils.ServletUtils;
import com.mhd.common.core.utils.StringUtils;
import com.mhd.common.core.utils.ip.IpUtils;
import com.mhd.user.application.service.OpenApiLogApplicationService;
import com.mhd.user.domain.security.annotation.OpenApiLog;
import com.mhd.user.infrastructure.feign.ProductServiceFeign;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpMethod;
import org.springframework.stereotype.Component;
import org.springframework.validation.BindingResult;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Collection;
import java.util.Date;
import java.util.Map;
/**
* 操作日志记录处理
*
* @author mhd
*/
@Slf4j
@Aspect
@Component
public class OpenApiLogAspect {
@Autowired
private ProductServiceFeign productServiceFeign;
@Autowired
private OpenApiLogApplicationService openApiLogApplicationService;
/**
* 处理完请求后执行
*
* @param joinPoint 切点
*/
@AfterReturning(pointcut = "@annotation(controllerLog)", returning = "jsonResult")
public void doAfterReturning(JoinPoint joinPoint, OpenApiLog controllerLog, Object jsonResult) {
handleLog(joinPoint, controllerLog, jsonResult);
}
protected void handleLog(final JoinPoint joinPoint, OpenApiLog controllerLog, Object jsonResult) {
try {
// *========数据库日志=========*//
SystemOpenApiLogDTO systemOpenApiLogDTO = new SystemOpenApiLogDTO();
systemOpenApiLogDTO.setServiceCode("userApi");
systemOpenApiLogDTO.setServiceName("用户服务");
// 请求的地址
String ip = IpUtils.getIpAddr(ServletUtils.getRequest());
systemOpenApiLogDTO.setOperIp(ip);
systemOpenApiLogDTO.setOperUrl(ServletUtils.getRequest().getRequestURI());
//根据当前登录获取客户信息 TODO
//获取配置的组织ID
R<SysTenantsPo> sysTenantsPoR = productServiceFeign.getTenantsInfoByOpen();
if (R.SUCCESS != sysTenantsPoR.getCode()) {
throw new ServiceException("未获取到组织配置信息");
}
SysTenantsPo sysTenantsPo = sysTenantsPoR.getData();
systemOpenApiLogDTO.setTopOrganizationId(sysTenantsPo.getTopOrganizationId());
systemOpenApiLogDTO.setOrganizationId(sysTenantsPo.getOrganizationId());
systemOpenApiLogDTO.setOrganizationName(sysTenantsPo.getOrganizationName());
systemOpenApiLogDTO.setCreateTime(new Date());
// 设置方法名称
String className = joinPoint.getTarget().getClass().getName();
String methodName = joinPoint.getSignature().getName();
systemOpenApiLogDTO.setMethod(className + "." + methodName + "()");
// 设置请求方式
systemOpenApiLogDTO.setRequestMethod(ServletUtils.getRequest().getMethod());
// 处理设置注解上的参数
getControllerMethodDescription(joinPoint, controllerLog, systemOpenApiLogDTO, jsonResult);
// 保存数据库
openApiLogApplicationService.saveLog(systemOpenApiLogDTO);
} catch (Exception exp) {
// 记录本地异常日志
log.error("==前置通知异常==");
log.error("异常信息:{}", exp.getMessage());
exp.printStackTrace();
}
}
/**
* 获取注解中对方法的描述信息 用于Controller层注解
*
* @param log 日志
* @param systemOpenApiLogDTO 操作日志
* @throws Exception
*/
public void getControllerMethodDescription(JoinPoint joinPoint, OpenApiLog log, SystemOpenApiLogDTO systemOpenApiLogDTO, Object jsonResult) throws Exception {
// 设置标题
systemOpenApiLogDTO.setTitle(log.title());
// 是否需要保存request,参数和值
if (log.isSaveRequestData()) {
// 获取参数的信息,传入到数据库中。
setRequestValue(joinPoint, systemOpenApiLogDTO);
}
// 是否需要保存response,参数和值
if (log.isSaveResponseData() && StringUtils.isNotNull(jsonResult)) {
systemOpenApiLogDTO.setJsonResult(JSON.toJSONString(jsonResult));
}
}
/**
* 获取请求的参数,放到log中
*
* @param openApiLogDO 操作日志
* @throws Exception 异常
*/
private void setRequestValue(JoinPoint joinPoint, SystemOpenApiLogDTO openApiLogDO) throws Exception {
String requestMethod = openApiLogDO.getRequestMethod();
if (HttpMethod.PUT.name().equals(requestMethod) || HttpMethod.POST.name().equals(requestMethod)) {
String params = argsArrayToString(joinPoint.getArgs());
openApiLogDO.setOperParam(params);
}
}
/**
* 参数拼装
*/
private String argsArrayToString(Object[] paramsArray) {
String params = "";
if (paramsArray != null && paramsArray.length > 0) {
for (Object o : paramsArray) {
if (StringUtils.isNotNull(o) && !isFilterObject(o)) {
try {
Object jsonObj = JSON.toJSON(o);
params += jsonObj.toString() + " ";
} catch (Exception e) {
}
}
}
}
return params.trim();
}
/**
* 判断是否需要过滤的对象。
*
* @param o 对象信息。
* @return 如果是需要过滤的对象,则返回true;否则返回false。
*/
@SuppressWarnings("rawtypes")
public boolean isFilterObject(final Object o) {
Class<?> clazz = o.getClass();
if (clazz.isArray()) {
return clazz.getComponentType().isAssignableFrom(MultipartFile.class);
} else if (Collection.class.isAssignableFrom(clazz)) {
Collection collection = (Collection) o;
for (Object value : collection) {
return value instanceof MultipartFile;
}
} else if (Map.class.isAssignableFrom(clazz)) {
Map map = (Map) o;
for (Object value : map.entrySet()) {
Map.Entry entry = (Map.Entry) value;
return entry.getValue() instanceof MultipartFile;
}
}
return o instanceof MultipartFile || o instanceof HttpServletRequest || o instanceof HttpServletResponse
|| o instanceof BindingResult;
}
}