Browse Source

新增接口平台相关代码

dev-V1.0.7
vincent 2 months ago
parent
commit
1e914ddce8
  1. 67
      zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/common/util/ApiSignUtil.java
  2. 16
      zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/config/ApiPlatformConfig.java
  3. 22
      zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/config/ApiPlatformWebConfig.java
  4. 82
      zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/controller/externalInterface/LongGangInterfaceController.java
  5. 8
      zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/domain/EfcodeCpPlan.java
  6. 72
      zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/domain/SysApiPlatformConfigEntity.java
  7. 8
      zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/domain/bo/EfcodeCpPlanBo.java
  8. 68
      zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/domain/bo/externalInterface/LongGangCreateTaskBo.java
  9. 11
      zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/domain/vo/EfcodeCpPlanVo.java
  10. 19
      zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/domain/vo/externalInterface/LongGangCreateTaskVo.java
  11. 85
      zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/handler/ApiCryptoFilter.java
  12. 67
      zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/handler/ApiDecryptRequestWrapper.java
  13. 51
      zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/handler/ApiEncryptResponseWrapper.java
  14. 116
      zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/handler/ApiSignInterceptor.java
  15. 8
      zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/mapper/SysApiPlatformConfigMapper.java
  16. 12
      zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/service/ISysApiPlatformConfigService.java
  17. 3
      zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/service/impl/EfcodeCpPlanServiceImpl.java
  18. 3
      zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/service/impl/EfcodeCpReserveServiceImpl.java
  19. 48
      zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/service/impl/SysApiPlatformConfigServiceImpl.java
  20. 10
      zdxt-modules/zdxt-enforcement-code/src/main/resources/mapper/enforcementcode/EfcodeCpPlanMapper.xml
  21. 2
      zdxt-web-server/zdxt-admin-web-server/src/main/resources/application-dev.yml
  22. 19
      zdxt-web-server/zdxt-admin-web-server/src/main/resources/application.yml

67
zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/common/util/ApiSignUtil.java

@ -0,0 +1,67 @@
package com.zdxt.enforcementcode.common.util.api;
import cn.hutool.core.codec.Base64;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.crypto.asymmetric.KeyType;
import cn.hutool.crypto.asymmetric.RSA;
import java.nio.charset.StandardCharsets;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Map;
import java.util.TreeMap;
import java.util.stream.Collectors;
public class ApiSignUtil {
private static final String SIGN_ALGORITHM = "SHA256withRSA";
public static String generateRsaKeyPair() {
RSA rsa = SecureUtil.rsa();
return "publicKey=" + rsa.getPublicKeyBase64() + "\nprivateKey=" + rsa.getPrivateKeyBase64();
}
public static String sign(String content, String privateKeyBase64) throws Exception {
byte[] keyBytes = Base64.decode(privateKeyBase64);
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes);
PrivateKey privateKey = KeyFactory.getInstance("RSA").generatePrivate(keySpec);
Signature signature = Signature.getInstance(SIGN_ALGORITHM);
signature.initSign(privateKey);
signature.update(content.getBytes(StandardCharsets.UTF_8));
return Base64.encode(signature.sign());
}
public static boolean verify(String content, String signBase64, String publicKeyBase64) throws Exception {
byte[] keyBytes = Base64.decode(publicKeyBase64);
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
PublicKey publicKey = KeyFactory.getInstance("RSA").generatePublic(keySpec);
Signature signature = Signature.getInstance(SIGN_ALGORITHM);
signature.initVerify(publicKey);
signature.update(content.getBytes(StandardCharsets.UTF_8));
return signature.verify(Base64.decode(signBase64));
}
public static String buildSignContent(Map<String, String> params) {
TreeMap<String, String> sorted = new TreeMap<>(params);
return sorted.entrySet().stream()
.filter(e -> e.getValue() != null && !e.getValue().isEmpty())
.map(e -> e.getKey() + "=" + e.getValue())
.collect(Collectors.joining("&"));
}
public static String rsaEncrypt(String data, String publicKeyBase64) {
RSA rsa = SecureUtil.rsa(null, publicKeyBase64);
return rsa.encryptBase64(data, StandardCharsets.UTF_8, KeyType.PublicKey);
}
public static String rsaDecrypt(String data, String privateKeyBase64) {
RSA rsa = SecureUtil.rsa(privateKeyBase64, null);
return rsa.decryptStr(data, KeyType.PrivateKey, StandardCharsets.UTF_8);
}
}

16
zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/config/ApiPlatformConfig.java

@ -0,0 +1,16 @@
package com.zdxt.enforcementcode.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Data
@Component
@ConfigurationProperties(prefix = "api-platform")
public class ApiPlatformConfig {
private boolean enabled = false;
private long timestampExpireSeconds = 300;
}

22
zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/config/ApiPlatformWebConfig.java

@ -0,0 +1,22 @@
package com.zdxt.enforcementcode.config;
import com.zdxt.enforcementcode.handler.ApiSignInterceptor;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
@RequiredArgsConstructor
public class ApiPlatformWebConfig implements WebMvcConfigurer {
private final ApiSignInterceptor apiSignInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(apiSignInterceptor)
.addPathPatterns("/external/api/**")
.order(10);
}
}

82
zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/controller/externalInterface/LongGangInterfaceController.java

@ -0,0 +1,82 @@
package com.zdxt.enforcementcode.controller.externalInterface;
import cn.dev33.satoken.annotation.SaIgnore;
import cn.hutool.core.util.StrUtil;
import com.zdxt.enforcementcode.domain.EfcodeCpPlan;
import com.zdxt.enforcementcode.domain.SysApiPlatformConfigEntity;
import com.zdxt.enforcementcode.domain.bo.EfcodeCpPlanBo;
import com.zdxt.enforcementcode.domain.bo.externalInterface.LongGangCreateTaskBo;
import com.zdxt.enforcementcode.domain.vo.EfcodeCpPlanVo;
import com.zdxt.enforcementcode.domain.vo.externalInterface.LongGangCreateTaskVo;
import com.zdxt.enforcementcode.service.IEfcodeCpPlanService;
import com.zdxt.enforcementcode.service.IEfcodeCpTaskService;
import com.zdxt.enforcementcode.service.ISysApiPlatformConfigService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.domain.R;
import org.dromara.common.core.utils.MapstructUtils;
import org.dromara.common.idempotent.annotation.RepeatSubmit;
import org.dromara.common.log.annotation.Log;
import org.dromara.common.log.enums.BusinessType;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
@Slf4j
@SaIgnore
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/external/api")
public class LongGangInterfaceController {
private static final String HEADER_APP_KEY = "X-App-Key";
private final IEfcodeCpPlanService planService;
private final IEfcodeCpTaskService taskService;
private final ISysApiPlatformConfigService sysApiPlatformConfigService;
@SaIgnore
@RepeatSubmit()
@Log(title = "外部接口-新增任务", businessType = BusinessType.INSERT)
@PostMapping("/createTask")
public R<LongGangCreateTaskVo> createTask(
@RequestHeader(value = HEADER_APP_KEY, required = false) String appKey,
@Validated @RequestBody LongGangCreateTaskBo bo) {
if (StrUtil.isBlank(appKey)) {
return R.fail("缺少请求头: " + HEADER_APP_KEY);
}
SysApiPlatformConfigEntity consumer = sysApiPlatformConfigService.findByAppKey(appKey).orElse(null);
if (consumer == null) {
return R.fail("无效的 appKey");
}
String channelNo = consumer.getChannelNo();
if (StrUtil.isBlank(channelNo)) {
return R.fail("渠道未配置 channelNo");
}
EfcodeCpPlanBo planBo = MapstructUtils.convert(bo, EfcodeCpPlanBo.class);
planBo.setChannelNo(channelNo);
planBo.setTaskId("");
planBo.setReserveStatus(0L);
planBo.setSystemReserved(0L);
planBo.setManualReserved(0L);
planBo.setIsDelete(0L);
planService.insertByBo(planBo);
EfcodeCpPlanVo planVo = planService.queryById(planBo.getId());
EfcodeCpPlan plan = MapstructUtils.convert(planVo, EfcodeCpPlan.class);
String taskId = taskService.forceGenerateTask(plan);
LongGangCreateTaskVo vo = new LongGangCreateTaskVo();
vo.setPlanId(planBo.getId());
vo.setTaskId(taskId);
vo.setOutSerialNo(bo.getOutSerialNo());
log.info("[外部接口] 新增任务, appKey={}, channelNo={}, planId={}, taskId={}",
appKey, channelNo, planBo.getId(), taskId);
return R.ok(vo);
}
}

8
zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/domain/EfcodeCpPlan.java

@ -184,5 +184,13 @@ public class EfcodeCpPlan extends BaseEntity {
*/
private String myRemark;
/**
* 渠道编号 0|市局,1|龙岗,2|龙华
*/
private String channelNo;
/**
* 外部流水号
*/
private String outSerialNo;
}

72
zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/domain/SysApiPlatformConfigEntity.java

@ -0,0 +1,72 @@
package com.zdxt.enforcementcode.domain;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import org.dromara.common.mybatis.core.domain.BaseEntity;
import java.io.Serial;
@Data
@TableName("sys_api_platform_config")
public class SysApiPlatformConfigEntity extends BaseEntity {
@Serial
private static final long serialVersionUID = 1L;
/**
* 主键ID
*/
@TableId(value = "id")
private Long id;
/**
* 渠道名称
*/
private String channelName;
/**
* 渠道编号(0=市局,1=龙岗,2=龙华)
*/
private String channelNo;
/**
* 应用Key
*/
private String appKey;
/**
* 应用密钥(HMAC模式)
*/
private String appSecret;
/**
* 平台公钥(RSA)
*/
private String platformPublicKey;
/**
* 平台私钥(RSA)
*/
private String platformPrivateKey;
/**
* 调用方公钥(RSA)
*/
private String callerPublicKey;
/**
* 是否启用签名验证
*/
private Boolean signEnabled;
/**
* 是否启用加解密
*/
private Boolean encryptEnabled;
/**
* 状态(0正常 1停用)
*/
private String status;
}

8
zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/domain/bo/EfcodeCpPlanBo.java

@ -205,5 +205,13 @@ public class EfcodeCpPlanBo extends BaseEntity {
*/
private String myRemark;
/**
* 渠道编号 0|市局,1|龙岗,2|龙华
*/
private String channelNo;
/**
* 外部流水号
*/
private String outSerialNo;
}

68
zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/domain/bo/externalInterface/LongGangCreateTaskBo.java

@ -0,0 +1,68 @@
package com.zdxt.enforcementcode.domain.bo.externalInterface;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import java.util.Date;
@Data
public class LongGangCreateTaskBo {
@NotNull(message = "计划类别不能为空")
private Long planType;
@NotNull(message = "计划执行日期不能为空")
@JsonFormat(pattern = "yyyy-MM-dd")
private Date planDate;
@NotNull(message = "执行时段不能为空")
private Long planTime;
@NotBlank(message = "执法事项不能为空")
private String enforceContent;
@NotBlank(message = "企业名称不能为空")
private String enterpriseName;
@NotBlank(message = "企业信用代码不能为空")
private String enterpriseNumber;
private String enterpriseId;
private String contactPerson;
private String contactPhone;
@NotBlank(message = "执法人员姓名不能为空")
private String enforceUserNames;
private String enforceUserIds;
private String enforceUserCodes;
private String enforceUserTels;
private Long isPublic = 0L;
private Long isUnion = 0L;
@NotBlank(message = "执法单位ID不能为空")
private String bureauDeptId;
@NotBlank(message = "执法单位名称不能为空")
private String bureauDeptName;
private String organId;
private String organName;
private String deptId;
private String deptName;
private String remark;
private String outSerialNo;
}

11
zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/domain/vo/EfcodeCpPlanVo.java

@ -253,5 +253,16 @@ public class EfcodeCpPlanVo implements Serializable {
@com.baomidou.mybatisplus.annotation.TableField(exist = false)
private java.util.List<EfcodeCpReserveVo> reserves;
/**
* 渠道编号 0|市局,1|龙岗,2|龙华
*/
@ExcelProperty(value = "渠道编号")
private String channelNo;
/**
* 外部流水号
*/
@ExcelProperty(value = "外部流水号")
private String outSerialNo;
}

19
zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/domain/vo/externalInterface/LongGangCreateTaskVo.java

@ -0,0 +1,19 @@
package com.zdxt.enforcementcode.domain.vo.externalInterface;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
@Data
public class LongGangCreateTaskVo implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
private String planId;
private String taskId;
private String outSerialNo;
}

85
zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/handler/ApiCryptoFilter.java

@ -0,0 +1,85 @@
package com.zdxt.enforcementcode.handler;
import cn.hutool.core.util.StrUtil;
import com.zdxt.enforcementcode.common.util.api.ApiSignUtil;
import com.zdxt.enforcementcode.config.ApiPlatformConfig;
import com.zdxt.enforcementcode.domain.SysApiPlatformConfigEntity;
import com.zdxt.enforcementcode.service.ISysApiPlatformConfigService;
import jakarta.servlet.*;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.constant.HttpStatus;
import org.dromara.common.core.domain.R;
import org.dromara.common.json.utils.JsonUtils;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import java.io.IOException;
@Slf4j
@Component
@Order(FilterRegistrationBean.HIGHEST_PRECEDENCE + 10)
@RequiredArgsConstructor
public class ApiCryptoFilter implements Filter {
private final ApiPlatformConfig apiPlatformConfig;
private final ISysApiPlatformConfigService sysApiPlatformConfigService;
private static final String HEADER_ENCRYPTED = "X-Encrypted";
private static final String HEADER_APP_KEY = "X-App-Key";
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
if (!apiPlatformConfig.isEnabled()) {
chain.doFilter(request, response);
return;
}
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpServletResponse httpResponse = (HttpServletResponse) response;
String appKey = httpRequest.getHeader(HEADER_APP_KEY);
String encryptedFlag = httpRequest.getHeader(HEADER_ENCRYPTED);
if (StrUtil.isBlank(appKey) || !"true".equals(encryptedFlag)) {
chain.doFilter(request, response);
return;
}
SysApiPlatformConfigEntity consumer = sysApiPlatformConfigService.findByAppKey(appKey).orElse(null);
if (consumer == null || !consumer.getEncryptEnabled()) {
chain.doFilter(request, response);
return;
}
ApiDecryptRequestWrapper decryptWrapper = new ApiDecryptRequestWrapper(
httpRequest, consumer.getPlatformPrivateKey());
ApiEncryptResponseWrapper encryptWrapper = new ApiEncryptResponseWrapper(httpResponse);
try {
chain.doFilter(decryptWrapper, encryptWrapper);
} catch (Exception e) {
log.error("API加解密处理异常", e);
httpResponse.reset();
httpResponse.setStatus(HttpStatus.SUCCESS);
httpResponse.setContentType("application/json;charset=UTF-8");
httpResponse.getWriter().write(JsonUtils.toJsonString(R.fail("数据处理异常")));
return;
}
String responseBody = encryptWrapper.getContent();
String encryptedResponse = ApiSignUtil.rsaEncrypt(responseBody, consumer.getCallerPublicKey());
httpResponse.reset();
httpResponse.setContentType("application/json;charset=UTF-8");
httpResponse.setHeader(HEADER_ENCRYPTED, "true");
httpResponse.getWriter().write(encryptedResponse);
}
}

67
zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/handler/ApiDecryptRequestWrapper.java

@ -0,0 +1,67 @@
package com.zdxt.enforcementcode.handler;
import cn.hutool.core.io.IoUtil;
import com.zdxt.enforcementcode.common.util.api.ApiSignUtil;
import jakarta.servlet.ReadListener;
import jakarta.servlet.ServletInputStream;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletRequestWrapper;
import org.springframework.http.MediaType;
import java.io.*;
import java.nio.charset.StandardCharsets;
public class ApiDecryptRequestWrapper extends HttpServletRequestWrapper {
private final byte[] body;
public ApiDecryptRequestWrapper(HttpServletRequest request, String platformPrivateKey) throws IOException {
super(request);
byte[] rawBytes = IoUtil.readBytes(request.getInputStream(), false);
String rawBody = new String(rawBytes, StandardCharsets.UTF_8);
if (rawBody.startsWith("{") || rawBody.startsWith("[")) {
this.body = rawBytes;
} else {
String decrypted = ApiSignUtil.rsaDecrypt(rawBody, platformPrivateKey);
this.body = decrypted.getBytes(StandardCharsets.UTF_8);
}
}
@Override
public BufferedReader getReader() {
return new BufferedReader(new InputStreamReader(getInputStream()));
}
@Override
public int getContentLength() {
return body.length;
}
@Override
public long getContentLengthLong() {
return body.length;
}
@Override
public String getContentType() {
return MediaType.APPLICATION_JSON_VALUE;
}
@Override
public ServletInputStream getInputStream() {
final ByteArrayInputStream bais = new ByteArrayInputStream(body);
return new ServletInputStream() {
@Override
public int read() { return bais.read(); }
@Override
public int available() { return body.length; }
@Override
public boolean isFinished() { return false; }
@Override
public boolean isReady() { return true; }
@Override
public void setReadListener(ReadListener readListener) { }
};
}
}

51
zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/handler/ApiEncryptResponseWrapper.java

@ -0,0 +1,51 @@
package com.zdxt.enforcementcode.handler;
import jakarta.servlet.ServletOutputStream;
import jakarta.servlet.WriteListener;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpServletResponseWrapper;
import java.io.*;
public class ApiEncryptResponseWrapper extends HttpServletResponseWrapper {
private final ByteArrayOutputStream byteArrayOutputStream;
private final ServletOutputStream servletOutputStream;
private final PrintWriter printWriter;
public ApiEncryptResponseWrapper(HttpServletResponse response) throws IOException {
super(response);
this.byteArrayOutputStream = new ByteArrayOutputStream();
this.servletOutputStream = new ServletOutputStream() {
@Override
public boolean isReady() { return true; }
@Override
public void setWriteListener(WriteListener writeListener) { }
@Override
public void write(int b) throws IOException { byteArrayOutputStream.write(b); }
@Override
public void write(byte[] b, int off, int len) throws IOException { byteArrayOutputStream.write(b, off, len); }
};
this.printWriter = new PrintWriter(new OutputStreamWriter(byteArrayOutputStream));
}
@Override
public PrintWriter getWriter() { return printWriter; }
@Override
public ServletOutputStream getOutputStream() { return servletOutputStream; }
@Override
public void flushBuffer() throws IOException {
servletOutputStream.flush();
printWriter.flush();
}
@Override
public void reset() { byteArrayOutputStream.reset(); }
public String getContent() throws IOException {
flushBuffer();
return byteArrayOutputStream.toString();
}
}

116
zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/handler/ApiSignInterceptor.java

@ -0,0 +1,116 @@
package com.zdxt.enforcementcode.handler;
import cn.hutool.core.util.StrUtil;
import com.zdxt.enforcementcode.common.util.api.ApiSignUtil;
import com.zdxt.enforcementcode.config.ApiPlatformConfig;
import com.zdxt.enforcementcode.domain.SysApiPlatformConfigEntity;
import com.zdxt.enforcementcode.service.ISysApiPlatformConfigService;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.constant.HttpStatus;
import org.dromara.common.core.domain.R;
import org.dromara.common.json.utils.JsonUtils;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
@Slf4j
@Component
@RequiredArgsConstructor
public class ApiSignInterceptor implements HandlerInterceptor {
private final ApiPlatformConfig apiPlatformConfig;
private final ISysApiPlatformConfigService sysApiPlatformConfigService;
private static final String HEADER_APP_KEY = "X-App-Key";
private static final String HEADER_TIMESTAMP = "X-Timestamp";
private static final String HEADER_NONCE = "X-Nonce";
private static final String HEADER_SIGN = "X-Sign";
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if (!apiPlatformConfig.isEnabled()) {
return true;
}
String appKey = request.getHeader(HEADER_APP_KEY);
String timestamp = request.getHeader(HEADER_TIMESTAMP);
String nonce = request.getHeader(HEADER_NONCE);
String sign = request.getHeader(HEADER_SIGN);
if (StrUtil.hasBlank(appKey, timestamp, nonce, sign)) {
writeError(response, "缺少必要的签名参数: X-App-Key, X-Timestamp, X-Nonce, X-Sign");
return false;
}
SysApiPlatformConfigEntity consumer = sysApiPlatformConfigService.findByAppKey(appKey).orElse(null);
if (consumer == null) {
writeError(response, "无效的 appKey");
return false;
}
if (!consumer.getSignEnabled()) {
return true;
}
long ts;
try {
ts = Long.parseLong(timestamp);
} catch (NumberFormatException e) {
writeError(response, "timestamp 格式错误");
return false;
}
long now = System.currentTimeMillis() / 1000;
if (Math.abs(now - ts) > apiPlatformConfig.getTimestampExpireSeconds()) {
writeError(response, "请求已过期,timestamp 超时");
return false;
}
Map<String, String> signParams = new HashMap<>();
signParams.put("appKey", appKey);
signParams.put("timestamp", timestamp);
signParams.put("nonce", nonce);
String queryString = request.getQueryString();
if (StrUtil.isNotBlank(queryString)) {
for (String param : queryString.split("&")) {
String[] kv = param.split("=", 2);
if (kv.length == 2) {
signParams.put(kv[0], kv[1]);
}
}
}
String signContent = ApiSignUtil.buildSignContent(signParams);
log.debug("签名原文: {}", signContent);
boolean verified = ApiSignUtil.verify(signContent, sign, consumer.getAppSecret());
if (!verified) {
try {
verified = ApiSignUtil.verify(signContent, sign, consumer.getCallerPublicKey());
} catch (Exception ignored) {
}
}
if (!verified) {
writeError(response, "签名验证失败");
return false;
}
log.info("API签名验证通过, appKey={}, path={}", appKey, request.getRequestURI());
return true;
}
private void writeError(HttpServletResponse response, String message) throws IOException {
response.setStatus(HttpStatus.FORBIDDEN);
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write(JsonUtils.toJsonString(R.fail(message)));
}
}

8
zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/mapper/SysApiPlatformConfigMapper.java

@ -0,0 +1,8 @@
package com.zdxt.enforcementcode.mapper;
import com.zdxt.enforcementcode.domain.SysApiPlatformConfigEntity;
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
public interface SysApiPlatformConfigMapper extends BaseMapperPlus<SysApiPlatformConfigEntity, Void> {
}

12
zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/service/ISysApiPlatformConfigService.java

@ -0,0 +1,12 @@
package com.zdxt.enforcementcode.service;
import com.zdxt.enforcementcode.domain.SysApiPlatformConfigEntity;
import java.util.Optional;
public interface ISysApiPlatformConfigService {
void refreshCache();
Optional<SysApiPlatformConfigEntity> findByAppKey(String appKey);
}

3
zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/service/impl/EfcodeCpPlanServiceImpl.java

@ -329,6 +329,8 @@ public class EfcodeCpPlanServiceImpl implements IEfcodeCpPlanService {
lqw.like(StringUtils.isNotBlank(bo.getDeptName()), EfcodeCpPlan::getDeptName, bo.getDeptName());
lqw.eq(bo.getIsDelete() != null, EfcodeCpPlan::getIsDelete, bo.getIsDelete());
lqw.eq(StringUtils.isNotBlank(bo.getMyRemark()), EfcodeCpPlan::getMyRemark, bo.getMyRemark());
lqw.eq(StringUtils.isNotBlank(bo.getChannelNo()), EfcodeCpPlan::getChannelNo, bo.getChannelNo());
lqw.eq(StringUtils.isNotBlank(bo.getOutSerialNo()), EfcodeCpPlan::getOutSerialNo, bo.getOutSerialNo());
return lqw;
}
@ -790,6 +792,7 @@ public class EfcodeCpPlanServiceImpl implements IEfcodeCpPlanService {
lqw.eq(EfcodeCpPlan::getIsPublic, 1L);
lqw.eq(EfcodeCpPlan::getIsUnion, 1L);
lqw.eq(EfcodeCpPlan::getIsDelete, 0L);
lqw.eq(EfcodeCpPlan::getChannelNo, "0");
lqw.isNotNull(EfcodeCpPlan::getCreateBy);
lqw.orderByAsc(EfcodeCpPlan::getPlanDate);
List<EfcodeCpPlan> pendingPlans = baseMapper.selectList(lqw);

3
zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/service/impl/EfcodeCpReserveServiceImpl.java

@ -200,6 +200,9 @@ public class EfcodeCpReserveServiceImpl implements IEfcodeCpReserveService {
if (targetPlan == null) {
return R.fail("目标计划不存在");
}
if (!"0".equals(targetPlan.getChannelNo())) {//只有市司法局的计划支持预约
return R.fail("该渠道的计划不支持预约");
}
// 1.1 校验目标计划状态:只有待预约(1)、已取消(4)、预约中可加入(5)的计划才允许预约
Long reserveStatus = targetPlan.getReserveStatus();
if (reserveStatus == null || !Arrays.asList(1L, 4L, 5L).contains(reserveStatus)) {

48
zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/service/impl/SysApiPlatformConfigServiceImpl.java

@ -0,0 +1,48 @@
package com.zdxt.enforcementcode.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.zdxt.enforcementcode.domain.SysApiPlatformConfigEntity;
import com.zdxt.enforcementcode.mapper.SysApiPlatformConfigMapper;
import com.zdxt.enforcementcode.service.ISysApiPlatformConfigService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import jakarta.annotation.PostConstruct;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
@Slf4j
@Service
@RequiredArgsConstructor
public class SysApiPlatformConfigServiceImpl implements ISysApiPlatformConfigService{
private final SysApiPlatformConfigMapper sysApiPlatformConfigMapper;
private final Map<String, SysApiPlatformConfigEntity> consumerCache = new ConcurrentHashMap<>();
@PostConstruct
public void init() {
refreshCache();
}
public void refreshCache() {
List<SysApiPlatformConfigEntity> list = sysApiPlatformConfigMapper.selectList(
new LambdaQueryWrapper<SysApiPlatformConfigEntity>()
.eq(SysApiPlatformConfigEntity::getStatus, "0")
);
Map<String, SysApiPlatformConfigEntity> newMap = new ConcurrentHashMap<>();
for (SysApiPlatformConfigEntity entity : list) {
newMap.put(entity.getAppKey(), entity);
}
consumerCache.clear();
consumerCache.putAll(newMap);
log.info("API接入配置缓存刷新, 共加载{}个渠道", newMap.size());
}
public Optional<SysApiPlatformConfigEntity> findByAppKey(String appKey) {
return Optional.ofNullable(consumerCache.get(appKey));
}
}

10
zdxt-modules/zdxt-enforcement-code/src/main/resources/mapper/enforcementcode/EfcodeCpPlanMapper.xml

@ -20,6 +20,7 @@
<where>
reserve_status in (1,4,5)
AND is_delete = 0
AND channel_no = '0'
<if test="currentUserId != null">
AND create_by != #{currentUserId}
<!-- 排除当前用户已预约的计划(活跃状态:1=同意, 3=待确认) -->
@ -67,12 +68,13 @@
reserve_status, system_reserved, manual_reserved,
bureau_dept_id, bureau_dept_name,
organ_id, organ_name, dept_id, dept_name,
is_delete, remark, my_remark,
is_delete, remark, my_remark, channel_no, out_serial_no,
create_by, create_dept, create_time, update_by, update_time
FROM efcode_cp_plan cp
<where>
cp.is_delete = 0
and cp.reserve_status!=2
AND cp.channel_no = '0'
<!-- 排除当前用户自己的计划 -->
<if test="currentUserId != null">
AND cp.create_by != #{currentUserId}
@ -113,6 +115,12 @@
<if test="bo.enforceContent != null and bo.enforceContent != ''">
AND cp.enforce_content LIKE CONCAT('%', #{bo.enforceContent}, '%')
</if>
<if test="bo.channelNo != null and bo.channelNo != ''">
AND cp.channel_no = #{bo.channelNo}
</if>
<if test="bo.outSerialNo != null and bo.outSerialNo != ''">
AND cp.out_serial_no = #{bo.outSerialNo}
</if>
</where>
ORDER BY cp.plan_date DESC, cp.plan_time DESC
</select>

2
zdxt-web-server/zdxt-admin-web-server/src/main/resources/application-dev.yml

@ -109,7 +109,7 @@ spring:
spring.data:
redis:
# 地址
host: 139.186.148.21
host: 146.56.223.227
# host: 172.16.20.72
# 端口,默认为6379
port: 6379

19
zdxt-web-server/zdxt-admin-web-server/src/main/resources/application.yml

@ -86,7 +86,7 @@ spring:
# 国际化资源文件路径
basename: i18n/messages
profiles:
active: prod
active: dev
# 文件上传
servlet:
multipart:
@ -335,3 +335,20 @@ http:
connect-timeout: 10000 # 连接超时:5秒
read-timeout: 10000 # 读取超时:30秒
write-timeout: 10000 # 写入超时:30秒
# 外部接口平台配置
api-platform:
enabled: true
timestamp-expire-seconds: 300
consumers:
partner-a:
app-key: ak_partner_a_xxxx
app-secret: sk_partner_a_xxxx
platform-public-key: MIGfMA0GCSq...
platform-private-key: MIICdQIBADANBg...
caller-public-key: MIGfMA0GCSq...
sign-enabled: true
encrypt-enabled: true
allowed-paths:
- /api/v1/data/**
- /api/v1/query/**

Loading…
Cancel
Save