首次提交

This commit is contained in:
2026-05-26 10:08:21 +08:00
commit 3b70da2f0b
92 changed files with 7237 additions and 0 deletions
@@ -0,0 +1,18 @@
package com.nbport.zgwl;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@MapperScan({
"com.nbport.zgwl.mapper",
"com.nbport.zgwl.partner.mapper",
"com.nbport.zgwl.manifest.mapper"
})
@SpringBootApplication
public class ZgwlEndApplication {
public static void main(String[] args) {
SpringApplication.run(ZgwlEndApplication.class, args);
}
}
@@ -0,0 +1,21 @@
package com.nbport.zgwl.common;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serial;
import java.io.Serializable;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class ErrorCode implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
private String code;
private String msg;
}
@@ -0,0 +1,14 @@
package com.nbport.zgwl.common;
import com.nbport.zgwl.exception.ServiceErrorCodeRange;
public interface GlobalErrorCodeConstants {
ErrorCode SUCCESS = new ErrorCode("0000", "成功");
ErrorCode ERROR = new ErrorCode("9999", "失败");
ErrorCode USER_NOT_FOUND = new ErrorCode(ServiceErrorCodeRange.USER_NOT_FOUND, "用户不存在");
ErrorCode USERNAME_OR_PWD_ERROR = new ErrorCode(ServiceErrorCodeRange.USERNAME_OR_PWD_ERROR, "用户名或密码错误");
ErrorCode USER_NOT_FOUND_OR_NO_DEFAULT = new ErrorCode(ServiceErrorCodeRange.USER_NOT_FOUND, "用户不存在或多租户未配置");
ErrorCode UNKNOWN = new ErrorCode(ServiceErrorCodeRange.DEFAULT, "未知错误");
ErrorCode PARAMS_ERROR = new ErrorCode(ServiceErrorCodeRange.DEFAULT, "参数不正确");
}
@@ -0,0 +1,36 @@
package com.nbport.zgwl.common;
import com.baomidou.mybatisplus.core.metadata.IPage;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serial;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
@Data
@NoArgsConstructor
public class PageResult<T> implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
private Long pageNo;
private Long pageSize;
private Long total;
private Long totalPage;
private List<T> records = new ArrayList<>();
public PageResult(IPage<T> page) {
this.pageNo = page.getCurrent();
this.pageSize = page.getSize();
this.total = page.getTotal();
this.totalPage = page.getPages();
this.records = page.getRecords();
}
}
+158
View File
@@ -0,0 +1,158 @@
package com.nbport.zgwl.common;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonView;
import com.nbport.zgwl.exception.ServiceErrorCodeRange;
import com.nbport.zgwl.exception.ServiceException;
import io.swagger.v3.oas.annotations.media.Schema;
import java.io.Serial;
import java.io.Serializable;
import java.util.Objects;
public class R<T> implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
@Schema(description = "响应状态码", example = "0000")
@JsonView(Views.Public.class)
private String code;
@Schema(description = "响应数据")
@JsonView(Views.Public.class)
private T result;
@Schema(description = "响应消息", example = "操作成功")
@JsonView(Views.Public.class)
private String message;
public static <T> R<T> error(R<?> result) {
return error(result.getCode(), result.getMessage());
}
public static <T> R<T> error(String code, String message) {
if (Objects.equals(GlobalErrorCodeConstants.SUCCESS.getCode(), code)) {
throw new ServiceException(ServiceErrorCodeRange.DEFAULT, "code 必须是错误的!");
}
R<T> result = new R<>();
result.code = code;
result.message = message;
return result;
}
public static <T> R<T> error(ErrorCode errorCode) {
return error(errorCode.getCode(), errorCode.getMsg());
}
public static <T> R<T> success(T data) {
R<T> result = new R<>();
result.code = GlobalErrorCodeConstants.SUCCESS.getCode();
result.result = data;
result.message = "";
return result;
}
public static <T> R<T> success(String message) {
R<T> result = new R<>();
result.code = GlobalErrorCodeConstants.SUCCESS.getCode();
result.result = null;
result.message = message;
return result;
}
public static <T> R<T> success(String code, String message) {
R<T> result = new R<>();
result.code = code;
result.result = null;
result.message = message;
return result;
}
public static <T> R<T> error(String message) {
R<T> result = new R<>();
result.code = GlobalErrorCodeConstants.ERROR.getCode();
result.result = null;
result.message = message;
return result;
}
public static <T> R<T> success() {
R<T> result = new R<>();
result.code = GlobalErrorCodeConstants.SUCCESS.getCode();
result.result = null;
result.message = "";
return result;
}
public static <T> R<T> data(String message, T data) {
R<T> result = new R<>();
result.code = GlobalErrorCodeConstants.SUCCESS.getCode();
result.result = data;
result.message = message;
return result;
}
public static <T> R<T> data(T data) {
R<T> result = new R<>();
result.code = GlobalErrorCodeConstants.SUCCESS.getCode();
result.result = data;
result.message = null;
return result;
}
public static boolean isSuccess(String code) {
return Objects.equals(code, GlobalErrorCodeConstants.SUCCESS.getCode());
}
@JsonIgnore
public boolean isSuccess() {
return isSuccess(code);
}
@JsonIgnore
public boolean isError() {
return !isSuccess();
}
public void checkError() throws ServiceException {
if (isSuccess()) {
return;
}
throw new ServiceException(code, message);
}
@JsonIgnore
public T getCheckedData() {
checkError();
return result;
}
public static <T> R<T> error(ServiceException serviceException) {
return error(serviceException.getCode(), serviceException.getMessage());
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public T getResult() {
return result;
}
public void setResult(T result) {
this.result = result;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
@@ -0,0 +1,10 @@
package com.nbport.zgwl.common;
public class Views {
private Views() {
}
public interface Public {
}
}
@@ -0,0 +1,39 @@
package com.nbport.zgwl.config;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MybatisPlusConfig {
@Value("${spring.datasource.url:}")
private String datasourceUrl;
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
PaginationInnerInterceptor paginationInnerInterceptor =
new PaginationInnerInterceptor(resolveDbType(datasourceUrl));
paginationInnerInterceptor.setMaxLimit(500L);
interceptor.addInnerInterceptor(paginationInnerInterceptor);
return interceptor;
}
private DbType resolveDbType(String jdbcUrl) {
if (jdbcUrl == null) {
return DbType.MYSQL;
}
String url = jdbcUrl.toLowerCase();
if (url.startsWith("jdbc:oracle:")) {
return DbType.ORACLE;
}
if (url.startsWith("jdbc:mysql:")) {
return DbType.MYSQL;
}
return DbType.MYSQL;
}
}
@@ -0,0 +1,19 @@
package com.nbport.zgwl.contractpush.config;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
/**
* 推送模块基础配置
*/
@Configuration
@EnableConfigurationProperties(ContractPushProperties.class)
public class ContractPushConfiguration {
@Bean
public RestTemplate contractPushRestTemplate() {
return new RestTemplate();
}
}
@@ -0,0 +1,66 @@
package com.nbport.zgwl.contractpush.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* 合同推送相关配置
* 默认从 application.yml 读取测试地址
* 生产环境可以通过 application-prod.yml 覆盖
*/
@Data
@ConfigurationProperties(prefix = "contract-push")
public class ContractPushProperties {
private Settlement settlement = new Settlement();
private DataCollection dataCollection = new DataCollection();
@Data
public static class Settlement {
private String systemKey;
private String secretKey;
private String tokenUrl;
private String pushContractUrl;
private String approveQueryUrl;
private String archiveUrl;
private String approveViewBaseUrl;
}
@Data
public static class DataCollection {
private String systemKey;
private String secretKey;
private String sysCode;
private String tokenUrl;
private String uploadUrl;
private String collectOppositesUrl;
private String collectZtbsUrl;
private String collectContractUrl;
private String collectPlanFeedbackUrl;
private String collectContractCloseUrl;
private String collectContractArchiveUrl;
private String queryApproveInfoUrl;
private String approveViewBaseUrl;
}
}
@@ -0,0 +1,84 @@
package com.nbport.zgwl.contractpush.controller;
import com.nbport.zgwl.common.R;
import com.nbport.zgwl.contractpush.dto.ApproveQueryRequestDTO;
import com.nbport.zgwl.contractpush.dto.ApproveViewUrlRequestDTO;
import com.nbport.zgwl.contractpush.dto.PushPayloadDTO;
import com.nbport.zgwl.contractpush.dto.PushTokenRequestDTO;
import com.nbport.zgwl.contractpush.service.DataCollectionPushService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import java.util.Map;
/**
* 数据采集推送相关接口
* 对应《数据采集接口规范V3.0》
*/
@RestController
@RequestMapping("/contract/push/data-collection")
public class DataCollectionPushController {
private final DataCollectionPushService dataCollectionPushService;
public DataCollectionPushController(DataCollectionPushService dataCollectionPushService) {
this.dataCollectionPushService = dataCollectionPushService;
}
@GetMapping("/token")
public R<Map<String, Object>> getToken(PushTokenRequestDTO requestDTO) {
return R.success(dataCollectionPushService.getToken(requestDTO));
}
@PostMapping("/upload")
public R<Map<String, Object>> upload(@RequestPart("file") MultipartFile file,
@RequestParam(value = "token", required = false) String token) {
return R.success(dataCollectionPushService.upload(file, token));
}
@PostMapping("/opposites")
public R<Map<String, Object>> collectOpposites(@RequestBody PushPayloadDTO requestDTO) {
return R.success(dataCollectionPushService.collectOpposites(requestDTO));
}
@PostMapping("/ztbs")
public R<Map<String, Object>> collectZtbs(@RequestBody PushPayloadDTO requestDTO) {
return R.success(dataCollectionPushService.collectZtbs(requestDTO));
}
@PostMapping("/contract")
public R<Map<String, Object>> collectContract(@RequestBody PushPayloadDTO requestDTO) {
return R.success(dataCollectionPushService.collectContract(requestDTO));
}
@PostMapping("/plan-feedback")
public R<Map<String, Object>> collectPlanFeedback(@RequestBody PushPayloadDTO requestDTO) {
return R.success(dataCollectionPushService.collectPlanFeedback(requestDTO));
}
@PostMapping("/close")
public R<Map<String, Object>> collectContractClose(@RequestBody PushPayloadDTO requestDTO) {
return R.success(dataCollectionPushService.collectContractClose(requestDTO));
}
@PostMapping("/archive")
public R<Map<String, Object>> collectContractArchive(@RequestBody PushPayloadDTO requestDTO) {
return R.success(dataCollectionPushService.collectContractArchive(requestDTO));
}
@PostMapping("/approve/query")
public R<Map<String, Object>> queryApproveInfo(@RequestBody ApproveQueryRequestDTO requestDTO) {
return R.success(dataCollectionPushService.queryApproveInfo(requestDTO));
}
@PostMapping("/approve/view-url")
public R<Map<String, Object>> buildApproveViewUrl(@RequestBody ApproveViewUrlRequestDTO requestDTO) {
return R.success(dataCollectionPushService.buildApproveViewUrl(requestDTO));
}
}
@@ -0,0 +1,55 @@
package com.nbport.zgwl.contractpush.controller;
import com.nbport.zgwl.common.R;
import com.nbport.zgwl.contractpush.dto.ApproveQueryRequestDTO;
import com.nbport.zgwl.contractpush.dto.ApproveViewUrlRequestDTO;
import com.nbport.zgwl.contractpush.dto.PushPayloadDTO;
import com.nbport.zgwl.contractpush.dto.PushTokenRequestDTO;
import com.nbport.zgwl.contractpush.service.SettlementPushService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Map;
/**
* 结算平台推送相关接口
* 对应《结算平台推送合同接口文档V9.5》
*/
@RestController
@RequestMapping("/contract/push/settlement")
public class SettlementPushController {
private final SettlementPushService settlementPushService;
public SettlementPushController(SettlementPushService settlementPushService) {
this.settlementPushService = settlementPushService;
}
@GetMapping("/token")
public R<Map<String, Object>> getToken(PushTokenRequestDTO requestDTO) {
return R.success(settlementPushService.getToken(requestDTO));
}
@PostMapping("/contract")
public R<Map<String, Object>> pushContract(@RequestBody PushPayloadDTO requestDTO) {
return R.success(settlementPushService.pushContract(requestDTO));
}
@PostMapping("/approve/query")
public R<Map<String, Object>> queryApproveInfo(@RequestBody ApproveQueryRequestDTO requestDTO) {
return R.success(settlementPushService.queryApproveInfo(requestDTO));
}
@PostMapping("/archive")
public R<Map<String, Object>> pushArchive(@RequestBody PushPayloadDTO requestDTO) {
return R.success(settlementPushService.pushArchive(requestDTO));
}
@PostMapping("/approve/view-url")
public R<Map<String, Object>> buildApproveViewUrl(@RequestBody ApproveViewUrlRequestDTO requestDTO) {
return R.success(settlementPushService.buildApproveViewUrl(requestDTO));
}
}
@@ -0,0 +1,24 @@
package com.nbport.zgwl.contractpush.dto;
import lombok.Data;
import java.io.Serializable;
/**
* 审批信息查询 / 单点查看地址拼装请求
*/
@Data
public class ApproveQueryRequestDTO implements Serializable {
private static final long serialVersionUID = 1L;
private String token;
private String phoneNum;
private String contractSysId;
private String contractCode;
private String contractId;
}
@@ -0,0 +1,18 @@
package com.nbport.zgwl.contractpush.dto;
import lombok.Data;
import java.io.Serializable;
/**
* 审批查看地址拼装参数
*/
@Data
public class ApproveViewUrlRequestDTO implements Serializable {
private static final long serialVersionUID = 1L;
private String contractId;
private String ivUser;
}
@@ -0,0 +1,22 @@
package com.nbport.zgwl.contractpush.dto;
import lombok.Data;
import java.io.Serializable;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* 通用推送请求体
* token 不传时,Service 内部会自动获取
* payload 直接放文档原始 JSON 结构即可
*/
@Data
public class PushPayloadDTO implements Serializable {
private static final long serialVersionUID = 1L;
private String token;
private Map<String, Object> payload = new LinkedHashMap<>();
}
@@ -0,0 +1,18 @@
package com.nbport.zgwl.contractpush.dto;
import lombok.Data;
import java.io.Serializable;
/**
* 获取 token 时可选覆盖默认 systemKey / secretKey
*/
@Data
public class PushTokenRequestDTO implements Serializable {
private static final long serialVersionUID = 1L;
private String systemKey;
private String secretKey;
}
@@ -0,0 +1,51 @@
package com.nbport.zgwl.contractpush.handler;
import cn.hutool.core.util.StrUtil;
import com.nbport.zgwl.exception.ServiceException;
import org.springframework.stereotype.Component;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* 推送请求加工器
* 这里只做最基础的 token、sys_code、时间戳补全,保持逻辑简单
*/
@Component
public class ContractPushRequestHandler {
public Map<String, Object> copyPayload(Map<String, Object> payload) {
return payload == null ? new LinkedHashMap<>() : new LinkedHashMap<>(payload);
}
public Map<String, Object> prepareSettlementPayload(Map<String, Object> payload, String token) {
Map<String, Object> requestBody = copyPayload(payload);
if (StrUtil.isBlank(token)) {
throw new ServiceException("结算平台 token 不能为空");
}
requestBody.put("token", token);
return requestBody;
}
public Map<String, Object> prepareDataCollectionPayload(Map<String, Object> payload, String sysCode) {
Map<String, Object> requestBody = copyPayload(payload);
if (StrUtil.isBlank(sysCode)) {
throw new ServiceException("数据采集 sysCode 未配置");
}
requestBody.put("sys_code", sysCode);
requestBody.putIfAbsent("timeStamp", String.valueOf(System.currentTimeMillis()));
return requestBody;
}
public Map<String, String> buildDataCollectionHeaders(String token, String systemKey, boolean includeSystemKey) {
if (StrUtil.isBlank(token)) {
throw new ServiceException("数据采集 cj_token 不能为空");
}
Map<String, String> headers = new LinkedHashMap<>();
headers.put("cj_token", token);
if (includeSystemKey) {
headers.put("systemKey", systemKey);
}
return headers;
}
}
@@ -0,0 +1,147 @@
package com.nbport.zgwl.contractpush.service;
import cn.hutool.core.util.StrUtil;
import com.nbport.zgwl.contractpush.config.ContractPushProperties;
import com.nbport.zgwl.contractpush.dto.ApproveQueryRequestDTO;
import com.nbport.zgwl.contractpush.dto.ApproveViewUrlRequestDTO;
import com.nbport.zgwl.contractpush.dto.PushPayloadDTO;
import com.nbport.zgwl.contractpush.dto.PushTokenRequestDTO;
import com.nbport.zgwl.contractpush.handler.ContractPushRequestHandler;
import com.nbport.zgwl.contractpush.utils.ContractPushHttpUtils;
import com.nbport.zgwl.contractpush.utils.ContractPushUrlUtils;
import com.nbport.zgwl.exception.ServiceException;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* 数据采集推送服务
* 对应《数据采集接口规范V3.0》
*/
@Service
public class DataCollectionPushService {
private final ContractPushProperties properties;
private final ContractPushHttpUtils httpUtils;
private final ContractPushRequestHandler requestHandler;
public DataCollectionPushService(ContractPushProperties properties,
ContractPushHttpUtils httpUtils,
ContractPushRequestHandler requestHandler) {
this.properties = properties;
this.httpUtils = httpUtils;
this.requestHandler = requestHandler;
}
public Map<String, Object> getToken(PushTokenRequestDTO requestDTO) {
String systemKey = requestDTO != null && StrUtil.isNotBlank(requestDTO.getSystemKey())
? requestDTO.getSystemKey()
: properties.getDataCollection().getSystemKey();
String secretKey = requestDTO != null && StrUtil.isNotBlank(requestDTO.getSecretKey())
? requestDTO.getSecretKey()
: properties.getDataCollection().getSecretKey();
Map<String, String> query = new LinkedHashMap<>();
query.put("systemKey", systemKey);
query.put("secretKey", secretKey);
return httpUtils.get(properties.getDataCollection().getTokenUrl(), query);
}
public Map<String, Object> upload(MultipartFile file, String token) {
Map<String, String> headers = requestHandler.buildDataCollectionHeaders(
resolveCollectionToken(token),
properties.getDataCollection().getSystemKey(),
true
);
return httpUtils.postMultipart(properties.getDataCollection().getUploadUrl(), file, headers);
}
public Map<String, Object> collectOpposites(PushPayloadDTO requestDTO) {
return postCollectionJson(properties.getDataCollection().getCollectOppositesUrl(), requestDTO, false);
}
public Map<String, Object> collectZtbs(PushPayloadDTO requestDTO) {
return postCollectionJson(properties.getDataCollection().getCollectZtbsUrl(), requestDTO, false);
}
public Map<String, Object> collectContract(PushPayloadDTO requestDTO) {
return postCollectionJson(properties.getDataCollection().getCollectContractUrl(), requestDTO, false);
}
public Map<String, Object> collectPlanFeedback(PushPayloadDTO requestDTO) {
return postCollectionJson(properties.getDataCollection().getCollectPlanFeedbackUrl(), requestDTO, false);
}
public Map<String, Object> collectContractClose(PushPayloadDTO requestDTO) {
return postCollectionJson(properties.getDataCollection().getCollectContractCloseUrl(), requestDTO, false);
}
public Map<String, Object> collectContractArchive(PushPayloadDTO requestDTO) {
return postCollectionJson(properties.getDataCollection().getCollectContractArchiveUrl(), requestDTO, false);
}
public Map<String, Object> queryApproveInfo(ApproveQueryRequestDTO requestDTO) {
Map<String, Object> body = new LinkedHashMap<>();
body.put("contractCode", requestDTO == null ? null : requestDTO.getContractCode());
body.put("contractId", requestDTO == null ? null : requestDTO.getContractId());
body.put("phoneNum", requestDTO == null ? null : requestDTO.getPhoneNum());
Map<String, String> headers = requestHandler.buildDataCollectionHeaders(
resolveCollectionToken(requestDTO == null ? null : requestDTO.getToken()),
properties.getDataCollection().getSystemKey(),
true
);
Map<String, Object> requestBody = requestHandler.prepareDataCollectionPayload(
body,
properties.getDataCollection().getSysCode()
);
return httpUtils.postJson(properties.getDataCollection().getQueryApproveInfoUrl(), requestBody, headers);
}
public Map<String, Object> buildApproveViewUrl(ApproveViewUrlRequestDTO requestDTO) {
if (requestDTO == null || StrUtil.isBlank(requestDTO.getContractId()) || StrUtil.isBlank(requestDTO.getIvUser())) {
throw new ServiceException("contractId 和 ivUser 不能为空");
}
Map<String, Object> result = new LinkedHashMap<>();
result.put(
"approveViewUrl",
ContractPushUrlUtils.buildApproveViewUrl(
properties.getDataCollection().getApproveViewBaseUrl(),
requestDTO.getContractId(),
requestDTO.getIvUser()
)
);
return result;
}
private Map<String, Object> postCollectionJson(String url, PushPayloadDTO requestDTO, boolean includeSystemKeyHeader) {
String token = resolveCollectionToken(requestDTO == null ? null : requestDTO.getToken());
Map<String, String> headers = requestHandler.buildDataCollectionHeaders(
token,
properties.getDataCollection().getSystemKey(),
includeSystemKeyHeader
);
Map<String, Object> requestBody = requestHandler.prepareDataCollectionPayload(
requestDTO == null ? null : requestDTO.getPayload(),
properties.getDataCollection().getSysCode()
);
return httpUtils.postJson(url, requestBody, headers);
}
private String resolveCollectionToken(String inputToken) {
if (StrUtil.isNotBlank(inputToken)) {
return inputToken;
}
Map<String, Object> tokenResult = getToken(new PushTokenRequestDTO());
Object data = tokenResult.get("data");
if (data instanceof Map) {
Object accessToken = ((Map<?, ?>) data).get("access_token");
if (accessToken != null) {
return String.valueOf(accessToken);
}
}
throw new ServiceException("获取数据采集 token 失败");
}
}
@@ -0,0 +1,103 @@
package com.nbport.zgwl.contractpush.service;
import cn.hutool.core.util.StrUtil;
import com.nbport.zgwl.contractpush.config.ContractPushProperties;
import com.nbport.zgwl.contractpush.dto.ApproveQueryRequestDTO;
import com.nbport.zgwl.contractpush.dto.ApproveViewUrlRequestDTO;
import com.nbport.zgwl.contractpush.dto.PushPayloadDTO;
import com.nbport.zgwl.contractpush.dto.PushTokenRequestDTO;
import com.nbport.zgwl.contractpush.handler.ContractPushRequestHandler;
import com.nbport.zgwl.contractpush.utils.ContractPushHttpUtils;
import com.nbport.zgwl.contractpush.utils.ContractPushUrlUtils;
import com.nbport.zgwl.exception.ServiceException;
import org.springframework.stereotype.Service;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* 结算平台推送服务
* 对应《结算平台推送合同接口文档V9.5》
*/
@Service
public class SettlementPushService {
private final ContractPushProperties properties;
private final ContractPushHttpUtils httpUtils;
private final ContractPushRequestHandler requestHandler;
public SettlementPushService(ContractPushProperties properties,
ContractPushHttpUtils httpUtils,
ContractPushRequestHandler requestHandler) {
this.properties = properties;
this.httpUtils = httpUtils;
this.requestHandler = requestHandler;
}
public Map<String, Object> getToken(PushTokenRequestDTO requestDTO) {
String systemKey = requestDTO != null && StrUtil.isNotBlank(requestDTO.getSystemKey())
? requestDTO.getSystemKey()
: properties.getSettlement().getSystemKey();
Map<String, String> query = new LinkedHashMap<>();
query.put("systemKey", systemKey);
return httpUtils.get(properties.getSettlement().getTokenUrl(), query);
}
public Map<String, Object> pushContract(PushPayloadDTO requestDTO) {
String token = resolveSettlementToken(requestDTO == null ? null : requestDTO.getToken());
Map<String, Object> body = requestHandler.prepareSettlementPayload(
requestDTO == null ? null : requestDTO.getPayload(),
token
);
return httpUtils.postJson(properties.getSettlement().getPushContractUrl(), body, null);
}
public Map<String, Object> queryApproveInfo(ApproveQueryRequestDTO requestDTO) {
Map<String, Object> body = new LinkedHashMap<>();
body.put("token", resolveSettlementToken(requestDTO == null ? null : requestDTO.getToken()));
body.put("phoneNum", requestDTO == null ? null : requestDTO.getPhoneNum());
body.put("contractSysId", requestDTO == null ? null : requestDTO.getContractSysId());
return httpUtils.postJson(properties.getSettlement().getApproveQueryUrl(), body, null);
}
public Map<String, Object> pushArchive(PushPayloadDTO requestDTO) {
String token = resolveSettlementToken(requestDTO == null ? null : requestDTO.getToken());
Map<String, Object> body = requestHandler.prepareSettlementPayload(
requestDTO == null ? null : requestDTO.getPayload(),
token
);
return httpUtils.postJson(properties.getSettlement().getArchiveUrl(), body, null);
}
public Map<String, Object> buildApproveViewUrl(ApproveViewUrlRequestDTO requestDTO) {
if (requestDTO == null || StrUtil.isBlank(requestDTO.getContractId()) || StrUtil.isBlank(requestDTO.getIvUser())) {
throw new ServiceException("contractId 和 ivUser 不能为空");
}
Map<String, Object> result = new LinkedHashMap<>();
result.put(
"approveViewUrl",
ContractPushUrlUtils.buildApproveViewUrl(
properties.getSettlement().getApproveViewBaseUrl(),
requestDTO.getContractId(),
requestDTO.getIvUser()
)
);
return result;
}
private String resolveSettlementToken(String inputToken) {
if (StrUtil.isNotBlank(inputToken)) {
return inputToken;
}
Map<String, Object> tokenResult = getToken(new PushTokenRequestDTO());
Object data = tokenResult.get("data");
if (data instanceof Map) {
Object accessToken = ((Map<?, ?>) data).get("access_token");
if (accessToken != null) {
return String.valueOf(accessToken);
}
}
throw new ServiceException("获取结算平台 token 失败");
}
}
@@ -0,0 +1,101 @@
package com.nbport.zgwl.contractpush.utils;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.TypeReference;
import com.nbport.zgwl.exception.ServiceException;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* 最简单的 HTTP 调用工具
* 当前只做 GET / POST JSON / POST MULTIPART 三种
*/
@Component
public class ContractPushHttpUtils {
private final RestTemplate restTemplate;
public ContractPushHttpUtils(RestTemplate contractPushRestTemplate) {
this.restTemplate = contractPushRestTemplate;
}
public Map<String, Object> get(String url, Map<String, String> queryParams) {
String finalUrl = ContractPushUrlUtils.appendQuery(url, queryParams);
ResponseEntity<String> response = restTemplate.getForEntity(finalUrl, String.class);
return parseResponse(response.getBody());
}
public Map<String, Object> postJson(String url, Map<String, Object> body, Map<String, String> headers) {
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setContentType(MediaType.APPLICATION_JSON);
addHeaders(httpHeaders, headers);
HttpEntity<Map<String, Object>> entity = new HttpEntity<>(body, httpHeaders);
ResponseEntity<String> response = restTemplate.exchange(url, HttpMethod.POST, entity, String.class);
return parseResponse(response.getBody());
}
public Map<String, Object> postMultipart(String url, MultipartFile file, Map<String, String> headers) {
if (file == null || file.isEmpty()) {
throw new ServiceException("上传文件不能为空");
}
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setContentType(MediaType.MULTIPART_FORM_DATA);
addHeaders(httpHeaders, headers);
MultiValueMap<String, Object> formData = new LinkedMultiValueMap<>();
formData.add("file", toByteArrayResource(file));
HttpEntity<MultiValueMap<String, Object>> entity = new HttpEntity<>(formData, httpHeaders);
ResponseEntity<String> response = restTemplate.exchange(url, HttpMethod.POST, entity, String.class);
return parseResponse(response.getBody());
}
private ByteArrayResource toByteArrayResource(MultipartFile file) {
try {
return new ByteArrayResource(file.getBytes()) {
@Override
public String getFilename() {
return file.getOriginalFilename();
}
};
} catch (IOException e) {
throw new ServiceException("读取上传文件失败: " + e.getMessage());
}
}
private void addHeaders(HttpHeaders httpHeaders, Map<String, String> headers) {
if (headers == null || headers.isEmpty()) {
return;
}
headers.forEach(httpHeaders::add);
}
private Map<String, Object> parseResponse(String responseBody) {
if (responseBody == null || responseBody.trim().isEmpty()) {
return Collections.emptyMap();
}
try {
return JSON.parseObject(responseBody, new TypeReference<LinkedHashMap<String, Object>>() {
});
} catch (Exception ex) {
Map<String, Object> fallback = new LinkedHashMap<>();
fallback.put("raw", responseBody);
return fallback;
}
}
}
@@ -0,0 +1,50 @@
package com.nbport.zgwl.contractpush.utils;
import cn.hutool.core.util.StrUtil;
import com.nbport.zgwl.exception.ServiceException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.StringJoiner;
/**
* URL 拼装工具
*/
public final class ContractPushUrlUtils {
private ContractPushUrlUtils() {
}
public static String appendQuery(String url, Map<String, String> queryParams) {
if (queryParams == null || queryParams.isEmpty()) {
return url;
}
StringJoiner joiner = new StringJoiner("&");
queryParams.forEach((key, value) -> {
if (StrUtil.isNotBlank(value)) {
joiner.add(encode(key) + "=" + encode(value));
}
});
return url + (url.contains("?") ? "&" : "?") + joiner;
}
public static String buildApproveViewUrl(String baseUrl, String contractId, String ivUser) {
if (StrUtil.isBlank(baseUrl)) {
throw new ServiceException("审批查看地址未配置");
}
Map<String, String> params = new LinkedHashMap<>();
params.put("id", contractId);
params.put("ssoToken", ivUser);
return appendQuery(baseUrl, params);
}
private static String encode(String value) {
try {
return URLEncoder.encode(value, "UTF-8");
} catch (UnsupportedEncodingException e) {
throw new ServiceException("URL 编码失败: " + e.getMessage());
}
}
}
@@ -0,0 +1,49 @@
package com.nbport.zgwl.controller;
import com.nbport.zgwl.common.R;
import com.nbport.zgwl.exception.ServiceException;
import com.nbport.zgwl.service.LocalFileService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.util.CollectionUtils;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.MultipartHttpServletRequest;
import java.util.Map;
@RestController
@RequestMapping("/trans/minio/file")
@Slf4j
@RequiredArgsConstructor
@Validated
public class MinioFileController {
private final LocalFileService localFileService;
@PostMapping(value = "/uploadOutie", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public R<String> uploadOutie(MultipartHttpServletRequest request) {
Map<String, MultipartFile> fileMap = request.getFileMap();
if (CollectionUtils.isEmpty(fileMap)) {
throw new ServiceException("请上传文件");
}
MultipartFile file = fileMap.values().iterator().next();
log.info("接收到上传文件请求,fileName={}", file.getOriginalFilename());
String relativePath = localFileService.upload(file);
return R.success(relativePath);
}
@GetMapping("/download")
public ResponseEntity<ByteArrayResource> download(@RequestParam("fileName") String fileName) {
log.info("接收到文件下载请求,fileName={}", fileName);
return localFileService.download(fileName);
}
}
@@ -0,0 +1,65 @@
package com.nbport.zgwl.controller;
import com.nbport.zgwl.common.PageResult;
import com.nbport.zgwl.common.R;
import com.nbport.zgwl.dto.StudentQueryDto;
import com.nbport.zgwl.dto.StudentSaveDto;
import com.nbport.zgwl.entity.StudentEntity;
import com.nbport.zgwl.entity.SysUserEntity;
import com.nbport.zgwl.service.StudentService;
import com.nbport.zgwl.utils.MockSecurityManager;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Map;
@RestController
@RequestMapping("/student")
@Slf4j
@RequiredArgsConstructor
@Validated
public class StudentController {
private final StudentService studentService;
private final MockSecurityManager securityManager;
@PostMapping("/list")
public R<PageResult<Map<String, Object>>> queryList(@RequestBody StudentQueryDto queryDto) {
SysUserEntity user = securityManager.getLoginUser();
PageResult<Map<String, Object>> pageResult = studentService.queryList(queryDto, user);
return R.success(pageResult);
}
@GetMapping("/detail/{id}")
public R<StudentEntity> detail(@PathVariable("id") Long id) {
return R.success(studentService.detail(id));
}
@PostMapping("/add")
public R<String> add(@RequestBody @Valid StudentSaveDto dto) {
log.info("新增学生请求:{}", dto);
studentService.add(dto);
return R.success("新增成功");
}
@PostMapping("/update")
public R<String> update(@RequestBody @Valid StudentSaveDto dto) {
log.info("修改学生请求:{}", dto);
studentService.update(dto);
return R.success("修改成功");
}
@PostMapping("/delete/{id}")
public R<String> delete(@PathVariable("id") Long id) {
studentService.delete(id);
return R.success("删除成功");
}
}
@@ -0,0 +1,43 @@
package com.nbport.zgwl.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serial;
import java.io.Serializable;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class BasePageDto implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
private Long pageNo = 1L;
private Long pageSize = 15L;
private Sorter sorter;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public static class Sorter implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
public static final String ASC = "asc";
public static final String DESC = "desc";
private String field;
@Builder.Default
private String order = ASC;
}
}
@@ -0,0 +1,21 @@
package com.nbport.zgwl.dto;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.io.Serial;
import java.io.Serializable;
@Data
@EqualsAndHashCode(callSuper = true)
public class StudentQueryDto extends BasePageDto implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
private String studentNo;
private String studentName;
private String gender;
}
@@ -0,0 +1,37 @@
package com.nbport.zgwl.dto;
import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
@Data
public class StudentSaveDto implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
private Long id;
@NotBlank(message = "学号不能为空")
private String studentNo;
@NotBlank(message = "姓名不能为空")
private String studentName;
@NotBlank(message = "性别不能为空")
private String gender;
@NotNull(message = "年龄不能为空")
@Min(value = 1, message = "年龄不能小于 1")
@Max(value = 120, message = "年龄不能大于 120")
private Integer age;
private String phone;
private String address;
}
@@ -0,0 +1,40 @@
package com.nbport.zgwl.entity;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.extension.activerecord.Model;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import lombok.ToString;
import lombok.experimental.Accessors;
import java.io.Serial;
import java.io.Serializable;
import java.time.LocalDateTime;
@Data
@ToString
@NoArgsConstructor
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
public class BaseEntity<T extends Model<?>> extends Model<T> implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
@TableField(value = "create_dt", fill = FieldFill.INSERT)
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private LocalDateTime createDt;
@TableField(value = "update_dt", fill = FieldFill.UPDATE)
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private LocalDateTime updateDt;
@TableField(value = "create_by", fill = FieldFill.INSERT)
private String createBy;
@TableField(value = "update_by", fill = FieldFill.UPDATE)
private String updateBy;
}
@@ -0,0 +1,43 @@
package com.nbport.zgwl.entity;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.io.Serial;
@Data
@TableName(value = "BIZ_STUDENT")
@EqualsAndHashCode(callSuper = true)
@KeySequence(value = "SEQ_BIZ_STUDENT", dbType = DbType.ORACLE)
public class StudentEntity extends BaseEntity<StudentEntity> {
@Serial
private static final long serialVersionUID = 1L;
@TableId(value = "id", type = IdType.INPUT)
private Long id;
@TableField("student_no")
private String studentNo;
@TableField("student_name")
private String studentName;
@TableField("gender")
private String gender;
@TableField("age")
private Integer age;
@TableField("phone")
private String phone;
@TableField("address")
private String address;
}
@@ -0,0 +1,31 @@
package com.nbport.zgwl.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.io.Serial;
@Data
@TableName(value = "sys_user")
@EqualsAndHashCode(callSuper = true)
public class SysUserEntity extends BaseEntity<SysUserEntity> {
@Serial
private static final long serialVersionUID = 1L;
@TableId(value = "id", type = IdType.INPUT)
private Long id;
@TableField("username")
private String username;
@TableField("mobile_phone")
private String mobilePhone;
@TableField("email")
private String email;
}
@@ -0,0 +1,14 @@
package com.nbport.zgwl.exception;
public interface ServiceErrorCodeRange {
String DEFAULT = "A0001";
String USER_NOT_FOUND = "A0002";
String USERNAME_OR_PWD_ERROR = "A0003";
String DATA_NOT_FOUND = "A0004";
String FILE_NOT_FOUND = "A0005";
}
@@ -0,0 +1,19 @@
package com.nbport.zgwl.exception;
import lombok.Getter;
@Getter
public class ServiceException extends RuntimeException {
private final String code;
public ServiceException(String code, String message) {
super(message);
this.code = code;
}
public ServiceException(String message) {
super(message);
this.code = ServiceErrorCodeRange.DEFAULT;
}
}
@@ -0,0 +1,58 @@
package com.nbport.zgwl.handler;
import com.nbport.zgwl.common.GlobalErrorCodeConstants;
import com.nbport.zgwl.common.R;
import com.nbport.zgwl.exception.ServiceException;
import jakarta.validation.ConstraintViolationException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.validation.BindException;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import java.util.stream.Collectors;
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(ServiceException.class)
public R<Void> handleServiceException(ServiceException e) {
log.error("业务异常", e);
return R.error(e);
}
@ExceptionHandler(MethodArgumentNotValidException.class)
public R<Void> handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
String message = e.getBindingResult().getFieldErrors().stream()
.map(FieldError::getDefaultMessage)
.collect(Collectors.joining(""));
return R.error(GlobalErrorCodeConstants.PARAMS_ERROR.getCode(), message);
}
@ExceptionHandler(BindException.class)
public R<Void> handleBindException(BindException e) {
String message = e.getBindingResult().getFieldErrors().stream()
.map(FieldError::getDefaultMessage)
.collect(Collectors.joining(""));
return R.error(GlobalErrorCodeConstants.PARAMS_ERROR.getCode(), message);
}
@ExceptionHandler(ConstraintViolationException.class)
public R<Void> handleConstraintViolationException(ConstraintViolationException e) {
return R.error(GlobalErrorCodeConstants.PARAMS_ERROR.getCode(), e.getMessage());
}
@ExceptionHandler(HttpMessageNotReadableException.class)
public R<Void> handleHttpMessageNotReadableException(HttpMessageNotReadableException e) {
return R.error(GlobalErrorCodeConstants.PARAMS_ERROR.getCode(), "请求体格式不正确");
}
@ExceptionHandler(Exception.class)
public R<Void> handleException(Exception e) {
log.error("系统异常", e);
return R.error(GlobalErrorCodeConstants.ERROR.getCode(), e.getMessage());
}
}
@@ -0,0 +1,26 @@
package com.nbport.zgwl.handler;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
LocalDateTime now = LocalDateTime.now();
this.strictInsertFill(metaObject, "createDt", LocalDateTime.class, now);
this.strictInsertFill(metaObject, "updateDt", LocalDateTime.class, now);
this.strictInsertFill(metaObject, "createBy", String.class, "system");
this.strictInsertFill(metaObject, "updateBy", String.class, "system");
}
@Override
public void updateFill(MetaObject metaObject) {
this.strictUpdateFill(metaObject, "updateDt", LocalDateTime.class, LocalDateTime.now());
this.strictUpdateFill(metaObject, "updateBy", String.class, "system");
}
}
@@ -0,0 +1,74 @@
package com.nbport.zgwl.manifest.controller;
import com.nbport.zgwl.common.PageResult;
import com.nbport.zgwl.common.R;
import com.nbport.zgwl.manifest.dto.ContractTemplateDto;
import com.nbport.zgwl.manifest.dto.ContractTemplateQueryDto;
import com.nbport.zgwl.manifest.dto.TemplateOrgOptionDto;
import com.nbport.zgwl.manifest.service.ContractTemplateService;
import com.nbport.zgwl.manifest.utils.ManifestManageConstants;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
/**
* 模板管理 Controller
*
* 路由尽量保持简单:
* - /list 分页
* - /published 已发布列表
* - /org/options 假组织选项
* - /{id} 详情
* - POST 新增
* - PUT 编辑
* - PUT /{id}/status/{status} 状态切换
*/
@RestController
@RequiredArgsConstructor
@RequestMapping(ManifestManageConstants.REQUEST_PREFIX)
public class ContractTemplateController {
private final ContractTemplateService contractTemplateService;
@PostMapping("/list")
public R<PageResult<ContractTemplateDto>> queryList(@RequestBody ContractTemplateQueryDto queryDto) {
return R.success(contractTemplateService.queryTemplateList(queryDto));
}
@GetMapping("/published")
public R<List<ContractTemplateDto>> queryPublishedList() {
return R.success(contractTemplateService.queryPublishedTemplates());
}
@GetMapping("/org/options")
public R<List<TemplateOrgOptionDto>> queryOrgOptions() {
return R.success(contractTemplateService.queryOrgOptions());
}
@GetMapping("/{id}")
public R<ContractTemplateDto> getDetail(@PathVariable("id") Long id) {
return R.success(contractTemplateService.getTemplateDetail(id));
}
@PostMapping
public R<ContractTemplateDto> add(@RequestBody ContractTemplateDto template) {
return R.success(contractTemplateService.createTemplate(template));
}
@PutMapping
public R<ContractTemplateDto> edit(@RequestBody ContractTemplateDto template) {
return R.success(contractTemplateService.updateTemplate(template));
}
@PutMapping("/{id}/status/{status}")
public R<ContractTemplateDto> changeStatus(@PathVariable("id") Long id, @PathVariable("status") String status) {
return R.success(contractTemplateService.changeTemplateStatus(id, status));
}
}
@@ -0,0 +1,87 @@
package com.nbport.zgwl.manifest.domain;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.io.Serial;
import java.io.Serializable;
import java.time.LocalDateTime;
@TableName(value = "CONTRACT_TEMPLATE_MANAGE")
@Data
@EqualsAndHashCode(callSuper = false)
@KeySequence(value = "SEQ_CONTRACT_TEMPLATE_MANAGE", dbType = DbType.ORACLE)
public class ContractTemplateManage implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
@TableId(value = "ID", type = IdType.INPUT)
private Long id;
@TableField("TEMPLATE_CODE")
private String templateCode;
@TableField("TEMPLATE_NAME")
private String templateName;
@TableField("SOURCE")
private String source;
@TableField("CONTRACT_TYPE")
private String contractType;
@TableField("SELF_PARTY_ROLE")
private String selfPartyRole;
@TableField("VERSION")
private String version;
@TableField("STATUS")
private String status;
@TableField("VARIABLE_COUNT")
private Integer variableCount;
@TableField("DESCRIPTION")
private String description;
@TableField("USAGE_TIP")
private String usageTip;
@TableField("TEMPLATE_FILE_NAME")
private String templateFileName;
@TableField("TEMPLATE_FILE_PATH")
private String templateFilePath;
@TableField("PUBLISH_ORG_IDS_JSON")
private String publishOrgIdsJson;
@TableField("PUBLISH_ORG_NAMES_JSON")
private String publishOrgNamesJson;
@TableField("VARIABLE_MAPPINGS_JSON")
private String variableMappingsJson;
@TableField("CREATE_BY")
private String createBy;
@TableField("CREATED_TIME")
private LocalDateTime createdTime;
@TableField("UPDATED_BY")
private String updatedBy;
@TableField("UPDATED_TIME")
private LocalDateTime updatedTime;
@TableField("DEL_FLAG")
private String delFlag;
}
@@ -0,0 +1,113 @@
package com.nbport.zgwl.manifest.dto;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serial;
import java.io.Serializable;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
/**
* 合同模板对象
*
* 这里直接用 DTO 承载页面需要的字段,方便前后端对接。
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ContractTemplateDto implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
private Long id;
/**
* 范本编号,保存时后端自动生成。
*/
private String templateCode;
private String templateName;
/**
* 范本类型:标准模板 / 推荐文本 / 示范文本 / 对方文本
*/
private String source;
private String contractType;
/**
* 我方地位:甲方 / 乙方 / 丙方
*/
private String selfPartyRole;
private String version;
/**
* 状态:draft / published / disabled
*/
private String status;
private Integer variableCount;
private String description;
private String usageTip;
private String templateFileName;
/**
* 模板文件相对路径
*/
private String templateFilePath;
/**
* 发布范围 orgId 集合
*/
@Builder.Default
private List<String> publishOrgIds = new ArrayList<>();
/**
* 发布范围 orgName 集合
*/
@Builder.Default
private List<String> publishOrgNames = new ArrayList<>();
/**
* 为列表直接展示准备的文本。
*/
private String publishScopeText;
@Builder.Default
private List<TemplateVariableMappingDto> variableMappings = new ArrayList<>();
@Builder.Default
private List<String> variables = new ArrayList<>();
private String createBy;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private LocalDateTime createdTime;
private String updatedBy;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private LocalDateTime updatedTime;
private String delFlag;
/**
* 以下两个字段只在数据库层临时承接 JSON 文本,不给页面直接使用。
*/
private String publishOrgIdsJson;
private String publishOrgNamesJson;
private String variableMappingsJson;
}
@@ -0,0 +1,27 @@
package com.nbport.zgwl.manifest.dto;
import com.nbport.zgwl.dto.BasePageDto;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.io.Serial;
import java.io.Serializable;
/**
* 模板分页查询参数
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class ContractTemplateQueryDto extends BasePageDto implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
private String templateName;
private String contractType;
private String source;
private String status;
}
@@ -0,0 +1,26 @@
package com.nbport.zgwl.manifest.dto;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serial;
import java.io.Serializable;
/**
* 发布范围组织选项
*
* 当前先用假数据代替真实组织表,后续内网联调时再替换成真实组织接口。
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class TemplateOrgOptionDto implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
private String orgId;
private String orgName;
}
@@ -0,0 +1,29 @@
package com.nbport.zgwl.manifest.dto;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serial;
import java.io.Serializable;
/**
* 模板变量映射
*
* variableKeyWord 模板里的变量名
* bindField:业务字段名
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class TemplateVariableMappingDto implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
private String id;
private String variableKey;
private String bindField;
}
@@ -0,0 +1,161 @@
package com.nbport.zgwl.manifest.handler;
import cn.hutool.core.util.StrUtil;
import com.nbport.zgwl.exception.ServiceException;
import com.nbport.zgwl.manifest.dto.ContractTemplateDto;
import com.nbport.zgwl.manifest.dto.TemplateOrgOptionDto;
import com.nbport.zgwl.manifest.dto.TemplateVariableMappingDto;
import com.nbport.zgwl.manifest.utils.ManifestJsonUtils;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
/**
* 模板数据处理器
*
* 负责:
* 1. 保存前清洗数据
* 2. 保存前校验数据
* 3. 查询后把 JSON 文本转成前端需要的列表
*/
@Component
public class ContractTemplateDataHandler {
public void normalizeBeforeSave(ContractTemplateDto template) {
template.setTemplateName(StrUtil.trim(template.getTemplateName()));
template.setSource(StrUtil.trim(template.getSource()));
template.setContractType(StrUtil.trim(template.getContractType()));
template.setSelfPartyRole(StrUtil.trim(template.getSelfPartyRole()));
template.setVersion(StrUtil.trim(template.getVersion()));
template.setDescription(StrUtil.trim(template.getDescription()));
template.setUsageTip(StrUtil.trim(template.getUsageTip()));
template.setTemplateFileName(StrUtil.trim(template.getTemplateFileName()));
template.setTemplateFilePath(StrUtil.trim(template.getTemplateFilePath()));
List<String> publishOrgIds = template.getPublishOrgIds() == null ? new ArrayList<>() : template.getPublishOrgIds();
List<String> publishOrgNames = template.getPublishOrgNames() == null ? new ArrayList<>() : template.getPublishOrgNames();
template.setPublishOrgIds(
publishOrgIds.stream()
.filter(StrUtil::isNotBlank)
.map(StrUtil::trim)
.distinct()
.collect(Collectors.toList())
);
template.setPublishOrgNames(
publishOrgNames.stream()
.filter(StrUtil::isNotBlank)
.map(StrUtil::trim)
.distinct()
.collect(Collectors.toList())
);
List<TemplateVariableMappingDto> mappings = template.getVariableMappings() == null ? new ArrayList<>() : template.getVariableMappings();
mappings = mappings.stream()
.filter(Objects::nonNull)
.map(item -> new TemplateVariableMappingDto(
StrUtil.blankToDefault(item.getId(), "var_" + System.nanoTime()),
StrUtil.trim(item.getVariableKey()),
StrUtil.trim(item.getBindField())
))
.filter(item -> StrUtil.isNotBlank(item.getVariableKey()) || StrUtil.isNotBlank(item.getBindField()))
.collect(Collectors.toList());
template.setVariableMappings(mappings);
template.setVariables(
mappings.stream()
.map(TemplateVariableMappingDto::getVariableKey)
.filter(StrUtil::isNotBlank)
.distinct()
.collect(Collectors.toList())
);
template.setVariableCount(template.getVariables().size());
template.setPublishScopeText(String.join("", template.getPublishOrgNames()));
}
public void validateForSave(ContractTemplateDto template) {
if (StrUtil.isBlank(template.getTemplateName())) {
throw new ServiceException("范本名称不能为空");
}
if (StrUtil.isBlank(template.getSource())) {
throw new ServiceException("范本类型不能为空");
}
if (StrUtil.isBlank(template.getContractType())) {
throw new ServiceException("合同类型不能为空");
}
if (StrUtil.isBlank(template.getSelfPartyRole())) {
throw new ServiceException("我方地位不能为空");
}
if (StrUtil.isBlank(template.getVersion())) {
throw new ServiceException("版本号不能为空");
}
if (StrUtil.isBlank(template.getTemplateFileName()) || StrUtil.isBlank(template.getTemplateFilePath())) {
throw new ServiceException("请先上传 Word 模板文件");
}
if (template.getPublishOrgIds() == null || template.getPublishOrgIds().isEmpty()) {
throw new ServiceException("发布范围不能为空");
}
if (template.getVariableMappings() == null || template.getVariableMappings().isEmpty()) {
throw new ServiceException("请至少维护一个变量映射");
}
for (TemplateVariableMappingDto mapping : template.getVariableMappings()) {
if (StrUtil.isBlank(mapping.getVariableKey())) {
throw new ServiceException("模板变量名称不能为空");
}
if (StrUtil.isBlank(mapping.getBindField())) {
throw new ServiceException("模板变量【" + mapping.getVariableKey() + "】尚未绑定业务字段");
}
}
}
public void fillUiFields(ContractTemplateDto template) {
if (template == null) {
return;
}
template.setPublishOrgIds(ManifestJsonUtils.parseStringList(template.getPublishOrgIdsJson()));
template.setPublishOrgNames(ManifestJsonUtils.parseStringList(template.getPublishOrgNamesJson()));
template.setVariableMappings(
ManifestJsonUtils.parseList(
template.getVariableMappingsJson(),
new com.alibaba.fastjson2.TypeReference<List<TemplateVariableMappingDto>>() {
}
)
);
template.setVariables(
template.getVariableMappings().stream()
.map(TemplateVariableMappingDto::getVariableKey)
.filter(StrUtil::isNotBlank)
.distinct()
.collect(Collectors.toList())
);
template.setVariableCount(template.getVariables().size());
template.setPublishScopeText(String.join("", template.getPublishOrgNames()));
}
public void fillUiFields(List<ContractTemplateDto> templates) {
if (templates == null) {
return;
}
templates.forEach(this::fillUiFields);
}
public void fillOrgNamesByIds(ContractTemplateDto template, List<TemplateOrgOptionDto> orgOptions) {
if (template.getPublishOrgIds() == null) {
template.setPublishOrgNames(new ArrayList<>());
return;
}
List<String> names = new ArrayList<>();
for (String orgId : template.getPublishOrgIds()) {
TemplateOrgOptionDto option = orgOptions.stream()
.filter(item -> Objects.equals(item.getOrgId(), orgId))
.findFirst()
.orElse(null);
if (option != null) {
names.add(option.getOrgName());
}
}
template.setPublishOrgNames(names);
template.setPublishScopeText(String.join("", names));
}
}
@@ -0,0 +1,38 @@
package com.nbport.zgwl.manifest.mapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.nbport.zgwl.manifest.dto.ContractTemplateDto;
import com.nbport.zgwl.manifest.dto.ContractTemplateQueryDto;
import org.apache.ibatis.annotations.Param;
import java.time.LocalDateTime;
import java.util.List;
/**
* 模板管理 Mapper
*/
public interface ContractTemplateMapper {
Long selectTemplateSeqNextVal();
IPage<ContractTemplateDto> selectTemplatePage(Page<ContractTemplateDto> page, @Param("query") ContractTemplateQueryDto queryDto);
List<ContractTemplateDto> selectPublishedTemplates();
ContractTemplateDto selectTemplateById(@Param("id") Long id);
int insertTemplate(ContractTemplateDto template);
int updateTemplate(ContractTemplateDto template);
int updateTemplateStatus(@Param("id") Long id,
@Param("status") String status,
@Param("updateBy") String updateBy,
@Param("updateTime") LocalDateTime updateTime);
int deleteTemplateById(@Param("id") Long id,
@Param("updateBy") String updateBy,
@Param("updateTime") LocalDateTime updateTime,
@Param("delFlag") String delFlag);
}
@@ -0,0 +1,28 @@
package com.nbport.zgwl.manifest.service;
import com.nbport.zgwl.common.PageResult;
import com.nbport.zgwl.manifest.dto.ContractTemplateDto;
import com.nbport.zgwl.manifest.dto.ContractTemplateQueryDto;
import com.nbport.zgwl.manifest.dto.TemplateOrgOptionDto;
import java.util.List;
/**
* 模板管理 Service
*/
public interface ContractTemplateService {
PageResult<ContractTemplateDto> queryTemplateList(ContractTemplateQueryDto queryDto);
List<ContractTemplateDto> queryPublishedTemplates();
ContractTemplateDto getTemplateDetail(Long id);
ContractTemplateDto createTemplate(ContractTemplateDto template);
ContractTemplateDto updateTemplate(ContractTemplateDto template);
ContractTemplateDto changeTemplateStatus(Long id, String status);
List<TemplateOrgOptionDto> queryOrgOptions();
}
@@ -0,0 +1,146 @@
package com.nbport.zgwl.manifest.service;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.nbport.zgwl.common.PageResult;
import com.nbport.zgwl.exception.ServiceException;
import com.nbport.zgwl.manifest.dto.ContractTemplateDto;
import com.nbport.zgwl.manifest.dto.ContractTemplateQueryDto;
import com.nbport.zgwl.manifest.dto.TemplateOrgOptionDto;
import com.nbport.zgwl.manifest.handler.ContractTemplateDataHandler;
import com.nbport.zgwl.manifest.mapper.ContractTemplateMapper;
import com.nbport.zgwl.manifest.utils.ManifestCodeUtils;
import com.nbport.zgwl.manifest.utils.ManifestJsonUtils;
import com.nbport.zgwl.manifest.utils.ManifestManageConstants;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDateTime;
import java.util.List;
/**
* 模板管理 Service 实现
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class ContractTemplateServiceImpl implements ContractTemplateService {
private final ContractTemplateMapper contractTemplateMapper;
private final ContractTemplateDataHandler contractTemplateDataHandler;
@Override
public PageResult<ContractTemplateDto> queryTemplateList(ContractTemplateQueryDto queryDto) {
Page<ContractTemplateDto> page = new Page<>(queryDto.getPageNo(), queryDto.getPageSize());
PageResult<ContractTemplateDto> pageResult = new PageResult<>(contractTemplateMapper.selectTemplatePage(page, queryDto));
contractTemplateDataHandler.fillUiFields(pageResult.getRecords());
return pageResult;
}
@Override
public List<ContractTemplateDto> queryPublishedTemplates() {
List<ContractTemplateDto> templates = contractTemplateMapper.selectPublishedTemplates();
contractTemplateDataHandler.fillUiFields(templates);
return templates;
}
@Override
public ContractTemplateDto getTemplateDetail(Long id) {
ContractTemplateDto template = contractTemplateMapper.selectTemplateById(id);
if (template == null) {
throw new ServiceException("模板不存在");
}
contractTemplateDataHandler.fillUiFields(template);
return template;
}
@Override
@Transactional(rollbackFor = Exception.class)
public ContractTemplateDto createTemplate(ContractTemplateDto template) {
contractTemplateDataHandler.normalizeBeforeSave(template);
contractTemplateDataHandler.fillOrgNamesByIds(template, queryOrgOptions());
contractTemplateDataHandler.validateForSave(template);
LocalDateTime now = LocalDateTime.now();
template.setId(contractTemplateMapper.selectTemplateSeqNextVal());
template.setTemplateCode(ManifestCodeUtils.buildTemplateCode(template.getContractType()));
template.setCreateBy(ManifestManageConstants.DEFAULT_OPERATOR);
template.setCreatedTime(now);
template.setUpdatedBy(ManifestManageConstants.DEFAULT_OPERATOR);
template.setUpdatedTime(now);
template.setDelFlag(ManifestManageConstants.DEL_FLAG_NORMAL);
saveJsonFields(template);
contractTemplateMapper.insertTemplate(template);
return getTemplateDetail(template.getId());
}
@Override
@Transactional(rollbackFor = Exception.class)
public ContractTemplateDto updateTemplate(ContractTemplateDto template) {
if (template.getId() == null) {
throw new ServiceException("修改模板时 id 不能为空");
}
ContractTemplateDto dbTemplate = contractTemplateMapper.selectTemplateById(template.getId());
if (dbTemplate == null) {
throw new ServiceException("模板不存在");
}
contractTemplateDataHandler.normalizeBeforeSave(template);
contractTemplateDataHandler.fillOrgNamesByIds(template, queryOrgOptions());
contractTemplateDataHandler.validateForSave(template);
template.setTemplateCode(dbTemplate.getTemplateCode());
template.setCreateBy(dbTemplate.getCreateBy());
template.setCreatedTime(dbTemplate.getCreatedTime());
template.setUpdatedBy(ManifestManageConstants.DEFAULT_OPERATOR);
template.setUpdatedTime(LocalDateTime.now());
template.setDelFlag(dbTemplate.getDelFlag());
saveJsonFields(template);
contractTemplateMapper.updateTemplate(template);
return getTemplateDetail(template.getId());
}
@Override
@Transactional(rollbackFor = Exception.class)
public ContractTemplateDto changeTemplateStatus(Long id, String status) {
if (!ManifestManageConstants.STATUS_DRAFT.equals(status)
&& !ManifestManageConstants.STATUS_PUBLISHED.equals(status)
&& !ManifestManageConstants.STATUS_DISABLED.equals(status)) {
throw new ServiceException("模板状态不合法");
}
ContractTemplateDto dbTemplate = contractTemplateMapper.selectTemplateById(id);
if (dbTemplate == null) {
throw new ServiceException("模板不存在");
}
contractTemplateMapper.updateTemplateStatus(
id,
status,
ManifestManageConstants.DEFAULT_OPERATOR,
LocalDateTime.now()
);
return getTemplateDetail(id);
}
@Override
public List<TemplateOrgOptionDto> queryOrgOptions() {
return List.of(
new TemplateOrgOptionDto("ORG001", "浙港物流平台有限公司"),
new TemplateOrgOptionDto("ORG002", "宁波舟山港集团有限公司"),
new TemplateOrgOptionDto("ORG003", "浙江省海港投资运营集团有限公司"),
new TemplateOrgOptionDto("ORG004", "宁波港东南物流集团有限公司")
);
}
private void saveJsonFields(ContractTemplateDto template) {
template.setPublishOrgIdsJson(ManifestJsonUtils.toJson(template.getPublishOrgIds()));
template.setPublishOrgNamesJson(ManifestJsonUtils.toJson(template.getPublishOrgNames()));
template.setVariableMappingsJson(ManifestJsonUtils.toJson(template.getVariableMappings()));
}
}
@@ -0,0 +1,56 @@
package com.nbport.zgwl.manifest.utils;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Map;
import java.util.concurrent.ThreadLocalRandom;
/**
* 模板编号生成工具
*
* 需求里要求“范本编号自动生成”,这里采用:
* TPL-合同类型编码-时间戳-随机数
*
* 例子:TPL-MM-20260522113045-128
*/
public final class ManifestCodeUtils {
private static final Map<String, String> CONTRACT_TYPE_CODE_MAP = Map.ofEntries(
Map.entry("买卖合同", "MM"),
Map.entry("采购合同", "CG"),
Map.entry("销售合同", "XS"),
Map.entry("服务合同", "FW"),
Map.entry("框架合同", "FJ"),
Map.entry("供用电、水、气、热力合同", "GY"),
Map.entry("赠与合同", "ZY"),
Map.entry("借款合同", "JK"),
Map.entry("保证合同", "BZ"),
Map.entry("租赁合同", "ZL"),
Map.entry("融资租赁合同", "RZ"),
Map.entry("保理合同", "BL"),
Map.entry("承揽合同", "CL"),
Map.entry("建设工程合同", "GC"),
Map.entry("运输合同", "YS"),
Map.entry("技术合同", "JS"),
Map.entry("保管合同", "BG"),
Map.entry("仓储合同", "CC"),
Map.entry("委托合同", "WT"),
Map.entry("物业服务合同", "WY"),
Map.entry("行纪合同", "HJ"),
Map.entry("中介合同", "ZJ"),
Map.entry("合伙合同", "HH"),
Map.entry("用工合同", "YG"),
Map.entry("业务合同", "YW"),
Map.entry("其他合同", "QT")
);
private ManifestCodeUtils() {
}
public static String buildTemplateCode(String contractType) {
String typeCode = CONTRACT_TYPE_CODE_MAP.getOrDefault(contractType, "QT");
String now = new SimpleDateFormat("yyyyMMddHHmmss").format(new Date());
int random = ThreadLocalRandom.current().nextInt(100, 1000);
return "TPL-" + typeCode + "-" + now + "-" + random;
}
}
@@ -0,0 +1,41 @@
package com.nbport.zgwl.manifest.utils;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.TypeReference;
import java.util.Collections;
import java.util.List;
/**
* 模板管理 JSON 工具
*
* 模板的“发布范围、变量映射”等字段更适合存成 JSON 文本。
* 这里统一做转换,避免 Service 里反复写解析代码。
*/
public final class ManifestJsonUtils {
private ManifestJsonUtils() {
}
public static String toJson(Object value) {
if (value == null) {
return null;
}
return JSON.toJSONString(value);
}
public static List<String> parseStringList(String json) {
if (json == null || json.isBlank()) {
return Collections.emptyList();
}
return JSON.parseObject(json, new TypeReference<List<String>>() {
});
}
public static <T> List<T> parseList(String json, TypeReference<List<T>> typeReference) {
if (json == null || json.isBlank()) {
return Collections.emptyList();
}
return JSON.parseObject(json, typeReference);
}
}
@@ -0,0 +1,26 @@
package com.nbport.zgwl.manifest.utils;
/**
* 模板管理模块常量
*
* 把常量单独放这里,是为了后续迁移到内网时只改一个地方。
*/
public final class ManifestManageConstants {
private ManifestManageConstants() {
}
public static final String REQUEST_PREFIX = "/manifest/manage";
public static final String DEFAULT_OPERATOR = "local_test";
public static final String DEL_FLAG_NORMAL = "0";
public static final String DEL_FLAG_DELETED = "2";
public static final String STATUS_DRAFT = "draft";
public static final String STATUS_PUBLISHED = "published";
public static final String STATUS_DISABLED = "disabled";
}
@@ -0,0 +1,15 @@
package com.nbport.zgwl.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.nbport.zgwl.dto.StudentQueryDto;
import com.nbport.zgwl.entity.StudentEntity;
import org.apache.ibatis.annotations.Param;
import java.util.Map;
public interface StudentMapper extends BaseMapper<StudentEntity> {
IPage<Map<String, Object>> queryList(Page<Map<String, Object>> page, @Param("query") StudentQueryDto queryDto);
}
@@ -0,0 +1,68 @@
package com.nbport.zgwl.partner.controller;
import com.nbport.zgwl.common.PageResult;
import com.nbport.zgwl.common.R;
import com.nbport.zgwl.partner.dto.PartnerManageDto;
import com.nbport.zgwl.partner.dto.PartnerQueryDto;
import com.nbport.zgwl.partner.service.PartnerManageService;
import com.nbport.zgwl.partner.utils.PartnerManageConstants;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
/**
* 相对方管理 Controller
*/
@RestController
@RequiredArgsConstructor
@RequestMapping(PartnerManageConstants.REQUEST_PREFIX)
public class PartnerManageController {
private final PartnerManageService partnerManageService;
@PostMapping("/list")
public R<PageResult<PartnerManageDto>> queryList(@RequestBody PartnerQueryDto queryDto) {
return R.success(partnerManageService.queryPartnerList(queryDto));
}
@GetMapping("/{customerSysid}")
public R<PartnerManageDto> getInfo(@PathVariable("customerSysid") Long customerSysid) {
return R.success(partnerManageService.selectPartnerById(customerSysid));
}
@PostMapping
public R<Long> add(@RequestBody PartnerManageDto partner) {
return R.success(partnerManageService.insertPartner(partner));
}
@PutMapping
public R<String> edit(@RequestBody PartnerManageDto partner) {
partnerManageService.updatePartner(partner);
return R.success("保存成功");
}
@DeleteMapping("/{customerSysids}")
public R<String> remove(@PathVariable("customerSysids") String customerSysids) {
List<Long> idList = Arrays.stream(customerSysids.split(","))
.filter(item -> item != null && !item.isBlank())
.map(Long::valueOf)
.collect(Collectors.toList());
partnerManageService.deletePartnerByIds(idList);
return R.success("删除成功");
}
@PostMapping("/{customerSysid}/sync")
public R<PartnerManageDto> mockSync(@PathVariable("customerSysid") Long customerSysid) {
return R.success(partnerManageService.mockSyncPartner(customerSysid));
}
}
@@ -0,0 +1,224 @@
package com.nbport.zgwl.partner.domain;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.io.Serial;
import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
@TableName(value = "ILDM_FEE_CUSTOMER_INFO")
@Data
@EqualsAndHashCode(callSuper = false)
@KeySequence(value = "SEQ_ILDM_FEE_CUSTOMER_INFO", dbType = DbType.ORACLE)
public class IldmFeeCustomerInfo implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
@TableId(value = "CUSTOMER_SYSID", type = IdType.INPUT)
private Long customerSysid;
@TableField("CUSTOMER_REGNO")
private String customerRegno;
@TableField("CUSTOMER_NO")
private String customerNo;
@TableField("CUSTOMER_NAME_C")
private String customerNameC;
@TableField("CUSTOMER_NAME_EN")
private String customerNameEn;
@TableField("CUSTOMER_SHORT_NAME")
private String customerShortName;
@TableField("CUSTOMER_SHORT_NAME_EN")
private String customerShortNameEn;
@TableField("CUSTOMER_TAXNO")
private String customerTaxno;
@TableField("CUSTOMER_ADDRESS")
private String customerAddress;
@TableField("OFFICEPHONE")
private String officephone;
@TableField("CUSTOMER_BANK")
private String customerBank;
@TableField("CUSTOMER_BANKACCOUNT")
private String customerBankaccount;
@TableField("CUSTOMER_CHARACTER")
private String customerCharacter;
@TableField("CUSTOMER_REGADDR")
private String customerRegaddr;
@TableField("CUSTOMER_REGDATE")
private LocalDate customerRegdate;
@TableField("CUSTOMER_NATURE")
private String customerNature;
@TableField("CUSTOMER_ZIP")
private String customerZip;
@TableField("CUSTOMER_1_TYPEFLAG")
private String customer1Typeflag;
@TableField("CUSTOMER_2_TYPEFLAG")
private String customer2Typeflag;
@TableField("CUSTOMER_3_TYPEFLAG")
private String customer3Typeflag;
@TableField("CUSTOMER_4_TYPEFLAG")
private String customer4Typeflag;
@TableField("PLACE_TYPE")
private String placeType;
@TableField("ACTIVEFLAG")
private String activeflag;
@TableField("CUSTOMER_SYSID_HT")
private String customerSysidHt;
@TableField("DOWN_DATE")
private LocalDate downDate;
@TableField("OPEN_FLAG")
private String openFlag;
@TableField("CREATE_BY")
private String createBy;
@TableField("CREATE_TIME")
private LocalDateTime createTime;
@TableField("UPDATE_BY")
private String updateBy;
@TableField("UPDATE_TIME")
private LocalDateTime updateTime;
@TableField("SYSTEM_TYPE")
private String systemType;
@TableField("REMARK")
private String remark;
@TableField("ORG_ID")
private String orgId;
@TableField("EMAIL_ADDRESS")
private String emailAddress;
@TableField("OPPOSITE_ID")
private String oppositeId;
@TableField("OPPOSITE_CHARACTER")
private String oppositeCharacter;
@TableField("GAT_FLAG")
private String gatFlag;
@TableField("GROUP_FLAG")
private String groupFlag;
@TableField("COUNTRY")
private String country;
@TableField("COUNTRY_NAME")
private String countryName;
@TableField("CITY")
private String city;
@TableField("POSTCODE")
private String postcode;
@TableField("LEGAL_PERSON_ID")
private String legalPersonId;
@TableField("LEGAL_PERSON_NAME")
private String legalPersonName;
@TableField("LEGAL_PERSON_ACCOUNT")
private String legalPersonAccount;
@TableField("LEGAL_PERSON_CARD_ID")
private String legalPersonCardId;
@TableField("WEBSITE")
private String website;
@TableField("CERTIFICATE_TYPE")
private String certificateType;
@TableField("CERTIFICATE_NUM")
private String certificateNum;
@TableField("TIN_CODE")
private String tinCode;
@TableField("ABROAD_CODE")
private String abroadCode;
@TableField("REGISTERED_CAPITAL")
private BigDecimal registeredCapital;
@TableField("REG_CAPITAL")
private String regCapital;
@TableField("BUS_SCOPE")
private String busScope;
@TableField("OPPOSITE_CODE")
private String oppositeCode;
@TableField("OPPOSITE_STATUS")
private String oppositeStatus;
@TableField("IS_SGAT")
private String isSgat;
@TableField("IS_JTN")
private String isJtn;
@TableField("NATURE_OF_ENTERPRISE_ORG")
private String natureOfEnterpriseOrg;
@TableField("NATURE_OF_ENTERPRISE_ORG_LEV")
private String natureOfEnterpriseOrgLev;
@TableField("RELATIVE_TYPE")
private String relativeType;
@TableField("IS_DELETED")
private String isDeleted;
@TableField("COMMENTS")
private String comments;
@TableField("SYNC_STATUS")
private String syncStatus;
@TableField("SYNC_TIME")
private LocalDateTime syncTime;
@TableField("SYNC_MESSAGE")
private String syncMessage;
}
@@ -0,0 +1,63 @@
package com.nbport.zgwl.partner.domain;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.io.Serial;
import java.io.Serializable;
import java.time.LocalDateTime;
@TableName(value = "SEAL_PARTNER_CONTACT")
@Data
@EqualsAndHashCode(callSuper = false)
@KeySequence(value = "SEQ_SEAL_PARTNER_CONTACT", dbType = DbType.ORACLE)
public class SealPartnerContact implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
@TableId(value = "ID", type = IdType.INPUT)
private Long id;
@TableField("PARTNER_RECORD_ID")
private Long partnerRecordId;
@TableField("CONTACT_NAME")
private String contactName;
@TableField("CONTACT_PHONE")
private String contactPhone;
@TableField("POSTAL_ADDRESS")
private String postalAddress;
@TableField("EMAIL")
private String email;
@TableField("SORT_ORDER")
private Integer sortOrder;
@TableField("DEL_FLAG")
private String delFlag;
@TableField("CREATE_BY")
private String createBy;
@TableField("CREATE_TIME")
private LocalDateTime createTime;
@TableField("UPDATE_BY")
private String updateBy;
@TableField("UPDATE_TIME")
private LocalDateTime updateTime;
@TableField("REMARK")
private String remark;
}
@@ -0,0 +1,47 @@
package com.nbport.zgwl.partner.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serial;
import java.io.Serializable;
/**
* 相对方联系人信息
*
* 字段命名直接采用 seal_partner_contact 的驼峰写法。
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class PartnerContactDto implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
private Long id;
/**
* 前端渲染联系人列表时使用的临时唯一键。
*/
private String uid;
private Long partnerRecordId;
private String contactName;
private String contactPhone;
private String postalAddress;
private String email;
private Integer sortOrder;
private String delFlag;
private String remark;
}
@@ -0,0 +1,243 @@
package com.nbport.zgwl.partner.dto;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serial;
import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
/**
* 相对方主对象
*
* 字段命名直接采用 ILDM_FEE_CUSTOMER_INFO 的驼峰写法,
* 避免再维护一套“业务语义字段”和“数据库字段”的双重命名。
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class PartnerManageDto implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
// ============== 客户基本信息字段 ==============
/** 客户系统ID */
private Long customerSysid; // 注意:数据库中是VARCHAR(64),不是Long
/** 客户注册号 */
private String customerRegno;
/** 客户编号 */
private String customerNo;
/** 公司全称 */
private String customerNameC;
/** 公司英文名称 */
private String customerNameEn;
/** 公司简称 */
private String customerShortName;
/** 公司英文简称 */
private String customerShortNameEn;
/** 统一社会信用代码 */
private String customerTaxno;
/** 详细地址 */
private String customerAddress;
/** 联系电话 */
private String officephone;
/** 开户银行 */
private String customerBank;
/** 银行账户 */
private String customerBankaccount;
/** 客户性质 */
private String customerCharacter;
/** 注册地址 */
private String customerRegaddr;
/** 注册日期 */
private LocalDate customerRegdate;
/** 客户性质分类 */
private String customerNature;
/** 邮编 */
private String customerZip;
/** 客户类型标识1 */
private String customer1Typeflag;
/** 客户类型标识2 */
private String customer2Typeflag;
/** 客户类型标识3 */
private String customer3Typeflag;
/** 客户类型标识4 */
private String customer4Typeflag;
/** 境内外 */
private String placeType;
/** 启用标识 */
private String activeflag;
/** 海铁系统客户ID */
private String customerSysidHt;
/** 下载日期 */
private LocalDate downDate;
/** 开户标识 */
private String openFlag;
/** 业务系统类型(ht:海铁联运系统;xg:箱管系统;qc:全程物流) */
private String systemType;
/** 组织体系对应的公司编码 */
private String orgId;
/** 邮箱地址 */
private String emailAddress;
// ============== 法务系统相关字段 ==============
/** 法务系统相对方ID(唯一标识) */
private String oppositeId;
/** 相对方性质分类(1:境内组织,2:境内个人,3:境外组织,4:境外个人) */
private String oppositeCharacter;
/** 是否涉港澳台标识(Y:是,N:否,法务业务分类用) */
private String gatFlag;
/** 是否集团内(Y:是,N:否,法务业务分类用) */
private String groupFlag;
/** 国家编码 */
private String country;
/** 国家名称 */
private String countryName;
/** 城市 */
private String city;
/** 邮编 */
private String postcode;
/** 法定代表人或负责人ID */
private String legalPersonId;
/** 法定代表人或负责人姓名 */
private String legalPersonName;
/** 法定代表人或负责人账户 */
private String legalPersonAccount;
/** 法定代表人身份证号(加密存储) */
private String legalPersonCardId;
/** 网站地址 */
private String website;
/** 自然人证件类型(idCard:身份证,passport:护照,other:其他) */
private String certificateType;
/** 自然人证件号码(加密存储) */
private String certificateNum;
/** TIN码(税务识别号) */
private String tinCode;
/** 境外公司活动证明编码 */
private String abroadCode;
/** 注册资金(单位:元) */
private BigDecimal registeredCapital;
/** 注册资本(文本描述,如:1000万人民币) */
private String regCapital;
/** 经营范围 */
private String busScope;
/** 相对方编码(业务分类编码) */
private String oppositeCode;
/** 状态(EFFECTUAL:有效,INVALID:无效) */
private String oppositeStatus;
/** 是否SGAT1:是,0:否) */
private String isSgat;
/** 是否JTN1:是,0:否) */
private String isJtn;
/** 组织性质(ZY:政府,QY:企业,QT:其他组织) */
private String natureOfEnterpriseOrg;
/** 组织性质子级(GNQY:国有企业,MYQY:民营企业,WZQY:外资企业,HZQY:合资企业,GYQZHKGQY:国有全资和控股企业,QT:其他) */
private String natureOfEnterpriseOrgLev;
/** 类型(client:客户,clientAndSup:客户及供应商,other:其他,Partner:合作伙伴,supplier:供应商) */
private String relativeType;
/** 法务系统删除标记(Y:是,N:否,逻辑删除) */
private String isDeleted;
/** 备注【法务】 */
private String comments;
/** 备注 */
private String remark;
// ============== 系统通用字段 ==============
/** 创建人 */
private String createBy;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private LocalDateTime createTime;
/** 更新人 */
private String updateBy;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private LocalDateTime updateTime;
// ============== 同步状态字段 ==============
/** 同步状态:unsynced/synced/failed */
private String syncStatus;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private LocalDateTime syncTime;
/** 同步说明 */
private String syncMessage;
// ============== 非数据库字段 ==============
/** 联系人列表 */
@Builder.Default
private List<PartnerContactDto> contacts = new ArrayList<>();
}
@@ -0,0 +1,29 @@
package com.nbport.zgwl.partner.dto;
import com.nbport.zgwl.dto.BasePageDto;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.io.Serial;
import java.io.Serializable;
/**
* 相对方列表查询参数
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class PartnerQueryDto extends BasePageDto implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
private String customerNameC;
private String relativeType;
private String oppositeCharacter;
private String groupFlag;
private String syncStatus;
}
@@ -0,0 +1,219 @@
package com.nbport.zgwl.partner.handler;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Validator;
import cn.hutool.core.util.StrUtil;
import com.nbport.zgwl.exception.ServiceException;
import com.nbport.zgwl.partner.dto.PartnerContactDto;
import com.nbport.zgwl.partner.dto.PartnerManageDto;
import com.nbport.zgwl.partner.utils.PartnerManageConstants;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
/**
* 相对方数据处理器
*/
@Component
public class PartnerDataHandler {
public void normalizeBeforeSave(PartnerManageDto partner) {
partner.setCustomerNameC(StrUtil.trim(partner.getCustomerNameC()));
partner.setCustomerTaxno(StrUtil.trim(partner.getCustomerTaxno()));
partner.setCustomerRegaddr(StrUtil.trim(partner.getCustomerRegaddr()));
partner.setEmailAddress(StrUtil.trim(partner.getEmailAddress()));
partner.setCountry(StrUtil.trim(partner.getCountry()));
partner.setCountryName(StrUtil.trim(partner.getCountryName()));
partner.setLegalPersonName(StrUtil.trim(partner.getLegalPersonName()));
partner.setCertificateNum(StrUtil.trim(partner.getCertificateNum()));
partner.setTinCode(StrUtil.trim(partner.getTinCode()));
partner.setRegCapital(StrUtil.trim(partner.getRegCapital()));
partner.setOppositeCode(StrUtil.trim(partner.getOppositeCode()));
partner.setOppositeStatus(StrUtil.trim(partner.getOppositeStatus()));
partner.setComments(StrUtil.trim(partner.getComments()));
partner.setRemark(StrUtil.trim(partner.getRemark()));
partner.setSyncMessage(StrUtil.trim(partner.getSyncMessage()));
if (!"QY".equals(partner.getNatureOfEnterpriseOrg())) {
partner.setNatureOfEnterpriseOrgLev(null);
}
if (!isDomesticOrg(partner)) {
partner.setCustomerTaxno(null);
partner.setLegalPersonName(null);
}
if (!isDomesticPerson(partner) && !isForeignPerson(partner)) {
partner.setCertificateType(null);
partner.setCertificateNum(null);
}
if (!isForeignOrg(partner)) {
partner.setTinCode(null);
}
if (!isForeignOrg(partner) && !isForeignPerson(partner)) {
partner.setCountry(null);
partner.setCountryName(null);
}
if (!isDomesticOrg(partner) && !isForeignOrg(partner)) {
partner.setCustomerRegaddr(null);
partner.setRegCapital(null);
partner.setRegisteredCapital(null);
}
if (!showGatFlag(partner)) {
partner.setGatFlag(PartnerManageConstants.GAT_FLAG_NO);
}
partner.setGroupFlag(StrUtil.blankToDefault(partner.getGroupFlag(), PartnerManageConstants.GROUP_FLAG_NO));
partner.setGatFlag(StrUtil.blankToDefault(partner.getGatFlag(), PartnerManageConstants.GAT_FLAG_NO));
partner.setSyncStatus(StrUtil.blankToDefault(partner.getSyncStatus(), PartnerManageConstants.SYNC_STATUS_UNSYNCED));
partner.setOppositeStatus(StrUtil.blankToDefault(partner.getOppositeStatus(), PartnerManageConstants.OPPOSITE_STATUS_EFFECTUAL));
partner.setIsDeleted(StrUtil.blankToDefault(partner.getIsDeleted(), PartnerManageConstants.PARTNER_IS_DELETED_NORMAL));
partner.setIsSgat(PartnerManageConstants.GAT_FLAG_YES.equals(partner.getGatFlag())
? PartnerManageConstants.FLAG_ONE
: PartnerManageConstants.FLAG_ZERO);
partner.setIsJtn(PartnerManageConstants.GROUP_FLAG_YES.equals(partner.getGroupFlag())
? PartnerManageConstants.FLAG_ONE
: PartnerManageConstants.FLAG_ZERO);
List<PartnerContactDto> contacts = partner.getContacts() == null ? new ArrayList<>() : partner.getContacts();
contacts = contacts.stream()
.filter(Objects::nonNull)
.peek(this::normalizeContact)
.filter(this::hasAnyContactValue)
.collect(Collectors.toList());
partner.setContacts(contacts);
}
public void validateForSave(PartnerManageDto partner) {
if (StrUtil.isBlank(partner.getCustomerNameC())) {
throw new ServiceException("公司名称不能为空");
}
if (StrUtil.isBlank(partner.getRelativeType())) {
throw new ServiceException("公司类型不能为空");
}
if (StrUtil.isBlank(partner.getOppositeCharacter())) {
throw new ServiceException("公司性质不能为空");
}
if (StrUtil.isBlank(partner.getNatureOfEnterpriseOrg())) {
throw new ServiceException("组织性质不能为空");
}
if ("QY".equals(partner.getNatureOfEnterpriseOrg()) && StrUtil.isBlank(partner.getNatureOfEnterpriseOrgLev())) {
throw new ServiceException("组织性质选择企业时,组织性质企业二级不能为空");
}
if (StrUtil.isBlank(partner.getGroupFlag())) {
throw new ServiceException("是否集团内不能为空");
}
if (isDomesticOrg(partner) && StrUtil.isBlank(partner.getCustomerTaxno())) {
throw new ServiceException("境内组织必须填写统一社会信用代码");
}
if (isDomesticPerson(partner) && StrUtil.isBlank(partner.getCertificateType())) {
throw new ServiceException("境内个人必须选择自然人证件类型");
}
if (isDomesticPerson(partner) && StrUtil.isBlank(partner.getCertificateNum())) {
throw new ServiceException("境内个人必须填写自然人证件号码");
}
if (isForeignOrg(partner) && StrUtil.isBlank(partner.getTinCode())) {
throw new ServiceException("境外组织必须填写TIN号");
}
if (isForeignOrg(partner) && StrUtil.isBlank(partner.getCountry())) {
throw new ServiceException("境外组织必须选择国家");
}
if (isForeignPerson(partner) && StrUtil.isBlank(partner.getCountry())) {
throw new ServiceException("境外个人必须选择国家");
}
if (isDomesticOrg(partner) && StrUtil.isBlank(partner.getLegalPersonName())) {
throw new ServiceException("境内组织必须填写法定代表人或负责人");
}
if ((isDomesticOrg(partner) || isForeignOrg(partner)) && StrUtil.isBlank(partner.getCustomerRegaddr())) {
throw new ServiceException("组织类型必须填写注册地址");
}
if ((isDomesticOrg(partner) || isForeignOrg(partner)) && StrUtil.isBlank(partner.getRegCapital())) {
throw new ServiceException("组织类型必须填写注册资本");
}
validateContacts(partner);
}
public void fillContactUiKeys(List<PartnerContactDto> contacts) {
if (contacts == null) {
return;
}
contacts.forEach(contact -> {
if (StrUtil.isBlank(contact.getUid())) {
contact.setUid(contact.getId() == null ? null : String.valueOf(contact.getId()));
}
});
}
private void validateContacts(PartnerManageDto partner) {
if (!needContacts(partner)) {
return;
}
if (CollUtil.isEmpty(partner.getContacts())) {
throw new ServiceException("组织类型至少需要维护一个联系人");
}
for (PartnerContactDto contact : partner.getContacts()) {
if (StrUtil.isBlank(contact.getContactName())) {
throw new ServiceException("联系人姓名不能为空");
}
if (StrUtil.isBlank(contact.getContactPhone())) {
throw new ServiceException("联系人联系电话不能为空");
}
if (StrUtil.isBlank(contact.getPostalAddress())) {
throw new ServiceException("联系人通讯地址不能为空");
}
if (StrUtil.isBlank(contact.getEmail())) {
throw new ServiceException("联系人电子邮箱不能为空");
}
if (!Validator.isEmail(contact.getEmail())) {
throw new ServiceException("联系人电子邮箱格式不正确");
}
}
}
private void normalizeContact(PartnerContactDto contact) {
contact.setContactName(StrUtil.trim(contact.getContactName()));
contact.setContactPhone(StrUtil.trim(contact.getContactPhone()));
contact.setPostalAddress(StrUtil.trim(contact.getPostalAddress()));
contact.setEmail(StrUtil.trim(contact.getEmail()));
contact.setRemark(StrUtil.trim(contact.getRemark()));
contact.setDelFlag(PartnerManageConstants.CONTACT_DEL_FLAG_NORMAL);
}
private boolean hasAnyContactValue(PartnerContactDto contact) {
return StrUtil.isNotBlank(contact.getContactName())
|| StrUtil.isNotBlank(contact.getContactPhone())
|| StrUtil.isNotBlank(contact.getPostalAddress())
|| StrUtil.isNotBlank(contact.getEmail());
}
private boolean needContacts(PartnerManageDto partner) {
return isDomesticOrg(partner) || isForeignOrg(partner);
}
private boolean showGatFlag(PartnerManageDto partner) {
return isDomesticOrg(partner) || isDomesticPerson(partner);
}
private boolean isDomesticOrg(PartnerManageDto partner) {
return "1".equals(partner.getOppositeCharacter());
}
private boolean isDomesticPerson(PartnerManageDto partner) {
return "2".equals(partner.getOppositeCharacter());
}
private boolean isForeignOrg(PartnerManageDto partner) {
return "3".equals(partner.getOppositeCharacter());
}
private boolean isForeignPerson(PartnerManageDto partner) {
return "4".equals(partner.getOppositeCharacter());
}
}
@@ -0,0 +1,61 @@
package com.nbport.zgwl.partner.mapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.nbport.zgwl.partner.dto.PartnerContactDto;
import com.nbport.zgwl.partner.dto.PartnerManageDto;
import com.nbport.zgwl.partner.dto.PartnerQueryDto;
import org.apache.ibatis.annotations.Param;
import java.time.LocalDateTime;
import java.util.List;
/**
* 相对方管理 Mapper
*/
public interface PartnerManageMapper {
Long selectPartnerSeqNextVal();
Long selectPartnerContactSeqNextVal();
IPage<PartnerManageDto> selectPartnerPage(Page<PartnerManageDto> page, @Param("query") PartnerQueryDto queryDto);
PartnerManageDto selectPartnerById(@Param("customerSysid") Long customerSysid);
int insertPartner(PartnerManageDto partner);
int updatePartner(PartnerManageDto partner);
int deletePartnerByIds(@Param("customerSysids") List<Long> customerSysids,
@Param("updateBy") String updateBy,
@Param("updateTime") LocalDateTime updateTime,
@Param("isDeleted") String isDeleted);
List<PartnerContactDto> selectPartnerContactsByPartnerIds(@Param("partnerRecordIds") List<Long> partnerRecordIds);
int markPartnerContactsDeletedByPartnerId(@Param("partnerRecordId") Long partnerRecordId,
@Param("updateBy") String updateBy,
@Param("updateTime") LocalDateTime updateTime,
@Param("delFlag") String delFlag);
int markPartnerContactsDeletedByPartnerIds(@Param("partnerRecordIds") List<Long> partnerRecordIds,
@Param("updateBy") String updateBy,
@Param("updateTime") LocalDateTime updateTime,
@Param("delFlag") String delFlag);
int batchInsertPartnerContacts(@Param("contacts") List<PartnerContactDto> contacts,
@Param("createBy") String createBy,
@Param("createTime") LocalDateTime createTime,
@Param("updateBy") String updateBy,
@Param("updateTime") LocalDateTime updateTime,
@Param("delFlag") String delFlag);
int updatePartnerSyncInfo(@Param("customerSysid") Long customerSysid,
@Param("oppositeId") String oppositeId,
@Param("syncStatus") String syncStatus,
@Param("syncTime") LocalDateTime syncTime,
@Param("syncMessage") String syncMessage,
@Param("updateBy") String updateBy,
@Param("updateTime") LocalDateTime updateTime);
}
@@ -0,0 +1,25 @@
package com.nbport.zgwl.partner.service;
import com.nbport.zgwl.common.PageResult;
import com.nbport.zgwl.partner.dto.PartnerManageDto;
import com.nbport.zgwl.partner.dto.PartnerQueryDto;
import java.util.List;
/**
* 相对方管理 Service
*/
public interface PartnerManageService {
PageResult<PartnerManageDto> queryPartnerList(PartnerQueryDto queryDto);
PartnerManageDto selectPartnerById(Long customerSysid);
Long insertPartner(PartnerManageDto partner);
void updatePartner(PartnerManageDto partner);
void deletePartnerByIds(List<Long> customerSysids);
PartnerManageDto mockSyncPartner(Long customerSysid);
}
@@ -0,0 +1,260 @@
package com.nbport.zgwl.partner.service;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.nbport.zgwl.common.PageResult;
import com.nbport.zgwl.contractpush.dto.PushPayloadDTO;
import com.nbport.zgwl.exception.ServiceException;
import com.nbport.zgwl.partner.dto.PartnerContactDto;
import com.nbport.zgwl.partner.dto.PartnerManageDto;
import com.nbport.zgwl.partner.dto.PartnerQueryDto;
import com.nbport.zgwl.partner.handler.PartnerDataHandler;
import com.nbport.zgwl.partner.mapper.PartnerManageMapper;
import com.nbport.zgwl.partner.utils.PartnerIdUtils;
import com.nbport.zgwl.partner.utils.PartnerManageConstants;
import com.nbport.zgwl.partner.utils.PartnerSyncPayloadBuilder;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDateTime;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
/**
* 相对方管理 Service 实现
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class PartnerManageServiceImpl implements PartnerManageService {
private final PartnerManageMapper partnerManageMapper;
private final PartnerDataHandler partnerDataHandler;
@Override
public PageResult<PartnerManageDto> queryPartnerList(PartnerQueryDto queryDto) {
Page<PartnerManageDto> page = new Page<>(queryDto.getPageNo(), queryDto.getPageSize());
PageResult<PartnerManageDto> pageResult = new PageResult<>(
partnerManageMapper.selectPartnerPage(page, queryDto));
attachContacts(pageResult.getRecords());
return pageResult;
}
@Override
public PartnerManageDto selectPartnerById(Long customerSysid) {
PartnerManageDto partner = partnerManageMapper.selectPartnerById(customerSysid);
if (partner == null) {
throw new ServiceException("相对方不存在");
}
attachContacts(Collections.singletonList(partner));
return partner;
}
@Override
@Transactional(rollbackFor = Exception.class)
public Long insertPartner(PartnerManageDto partner) {
partnerDataHandler.normalizeBeforeSave(partner);
partnerDataHandler.validateForSave(partner);
LocalDateTime now = LocalDateTime.now();
partner.setCustomerSysid(partnerManageMapper.selectPartnerSeqNextVal());
partner.setOppositeCode(ensureOppositeCode(partner.getOppositeCode()));
partner.setOppositeId(null);
partner.setSyncStatus(PartnerManageConstants.SYNC_STATUS_UNSYNCED);
partner.setSyncTime(null);
partner.setSyncMessage("新增后尚未同步");
partner.setOppositeStatus(PartnerManageConstants.OPPOSITE_STATUS_EFFECTUAL);
partner.setCreateBy(PartnerManageConstants.DEFAULT_OPERATOR);
partner.setCreateTime(now);
partner.setUpdateBy(PartnerManageConstants.DEFAULT_OPERATOR);
partner.setUpdateTime(now);
partner.setIsDeleted(PartnerManageConstants.PARTNER_IS_DELETED_NORMAL);
partnerManageMapper.insertPartner(partner);
saveContacts(partner.getCustomerSysid(), partner.getContacts(), now);
return partner.getCustomerSysid();
}
@Override
@Transactional(rollbackFor = Exception.class)
public void updatePartner(PartnerManageDto partner) {
if (partner.getCustomerSysid() == null) {
throw new ServiceException("修改时 customerSysid 不能为空");
}
PartnerManageDto dbPartner = partnerManageMapper.selectPartnerById(partner.getCustomerSysid());
if (dbPartner == null) {
throw new ServiceException("相对方不存在或已删除");
}
partnerDataHandler.normalizeBeforeSave(partner);
partnerDataHandler.validateForSave(partner);
LocalDateTime now = LocalDateTime.now();
partner.setOppositeCode(ensureOppositeCode(partner.getOppositeCode(), dbPartner.getOppositeCode()));
partner.setOppositeId(dbPartner.getOppositeId());
partner.setOppositeStatus(ensureValue(partner.getOppositeStatus(), dbPartner.getOppositeStatus(),
PartnerManageConstants.OPPOSITE_STATUS_EFFECTUAL));
partner.setSyncStatus(PartnerManageConstants.SYNC_STATUS_UNSYNCED);
partner.setSyncTime(null);
partner.setSyncMessage("信息已修改,待重新同步");
partner.setUpdateBy(PartnerManageConstants.DEFAULT_OPERATOR);
partner.setUpdateTime(now);
partner.setIsDeleted(PartnerManageConstants.PARTNER_IS_DELETED_NORMAL);
partnerManageMapper.updatePartner(partner);
partnerManageMapper.markPartnerContactsDeletedByPartnerId(
partner.getCustomerSysid(),
PartnerManageConstants.DEFAULT_OPERATOR,
now,
PartnerManageConstants.CONTACT_DEL_FLAG_DELETED);
saveContacts(partner.getCustomerSysid(), partner.getContacts(), now);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void deletePartnerByIds(List<Long> customerSysids) {
if (customerSysids == null || customerSysids.isEmpty()) {
return;
}
LocalDateTime now = LocalDateTime.now();
partnerManageMapper.markPartnerContactsDeletedByPartnerIds(
customerSysids,
PartnerManageConstants.DEFAULT_OPERATOR,
now,
PartnerManageConstants.CONTACT_DEL_FLAG_DELETED);
partnerManageMapper.deletePartnerByIds(
customerSysids,
PartnerManageConstants.DEFAULT_OPERATOR,
now,
PartnerManageConstants.PARTNER_IS_DELETED_DELETED);
}
@Override
@Transactional(rollbackFor = Exception.class)
public PartnerManageDto mockSyncPartner(Long customerSysid) {
PartnerManageDto partner = partnerManageMapper.selectPartnerById(customerSysid);
if (partner == null) {
throw new ServiceException("相对方不存在或已删除");
}
attachContacts(Collections.singletonList(partner));
LocalDateTime now = LocalDateTime.now();
String oppositeId = StrUtilHelper.hasText(partner.getOppositeId())
? partner.getOppositeId()
: PartnerIdUtils.buildGroupPartnerId();
PartnerSyncPayloadBuilder.buildCollectOppositesPayload(partner);
// PushPayloadDTO payloadDTO =
// PartnerSyncPayloadBuilder.buildCollectOppositesPayload(partner);
// Map<String, Object> response =
// dataCollectionPushService.collectOpposites(payloadDTO);
String syncMessage = "本地测试模拟同步成功。TODO:后续在这里调用相对方信息采集接口 collectOpposites。";
// // 从法务系统响应中取出返回的 oppositeId(比如 response 里有个 "jtId" 字段)
// String oppositeIdFromLawSystem = ...; // 具体 key 看法务返回的字段名
// String syncMessage = response.containsKey("msg")
// ? String.valueOf(response.get("msg"))
// : "相对方信息采集同步成功";
// // 回写法务返回的 oppositeId,而不是本地生成的
partnerManageMapper.updatePartnerSyncInfo(
customerSysid,
oppositeId,
PartnerManageConstants.SYNC_STATUS_SYNCED,
now,
syncMessage,
PartnerManageConstants.DEFAULT_OPERATOR,
now);
return selectPartnerById(customerSysid);
}
private void attachContacts(List<PartnerManageDto> partnerList) {
if (partnerList == null || partnerList.isEmpty()) {
return;
}
List<Long> customerSysids = partnerList.stream()
.map(PartnerManageDto::getCustomerSysid)
.filter(Objects::nonNull)
.collect(Collectors.toList());
if (customerSysids.isEmpty()) {
return;
}
List<PartnerContactDto> contacts = partnerManageMapper.selectPartnerContactsByPartnerIds(customerSysids);
Map<Long, List<PartnerContactDto>> contactMap = contacts.stream()
.collect(Collectors.groupingBy(PartnerContactDto::getPartnerRecordId));
partnerList.forEach(item -> {
List<PartnerContactDto> currentContacts = contactMap.getOrDefault(item.getCustomerSysid(),
Collections.emptyList());
partnerDataHandler.fillContactUiKeys(currentContacts);
item.setContacts(currentContacts);
});
}
private void saveContacts(Long customerSysid, List<PartnerContactDto> contacts, LocalDateTime now) {
if (customerSysid == null || contacts == null || contacts.isEmpty()) {
return;
}
int sortOrder = 1;
for (PartnerContactDto contact : contacts) {
contact.setId(partnerManageMapper.selectPartnerContactSeqNextVal());
contact.setPartnerRecordId(customerSysid);
contact.setSortOrder(sortOrder++);
contact.setDelFlag(PartnerManageConstants.CONTACT_DEL_FLAG_NORMAL);
}
partnerManageMapper.batchInsertPartnerContacts(
contacts,
PartnerManageConstants.DEFAULT_OPERATOR,
now,
PartnerManageConstants.DEFAULT_OPERATOR,
now,
PartnerManageConstants.CONTACT_DEL_FLAG_NORMAL);
}
private String ensureOppositeCode(String oppositeCode) {
return ensureOppositeCode(oppositeCode, null);
}
private String ensureOppositeCode(String oppositeCode, String fallback) {
if (StrUtilHelper.hasText(oppositeCode)) {
return oppositeCode;
}
if (StrUtilHelper.hasText(fallback)) {
return fallback;
}
return PartnerIdUtils.buildPartnerId();
}
private String ensureValue(String value, String fallback, String defaultValue) {
if (StrUtilHelper.hasText(value)) {
return value;
}
if (StrUtilHelper.hasText(fallback)) {
return fallback;
}
return defaultValue;
}
/**
* 避免为了一个判空再引入新依赖,这里保留一个最小工具。
*/
private static final class StrUtilHelper {
private StrUtilHelper() {
}
private static boolean hasText(String value) {
return value != null && !value.isBlank();
}
}
}
@@ -0,0 +1,41 @@
package com.nbport.zgwl.partner.utils;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.ThreadLocalRandom;
/**
* 相关方ID生成工具
*
* 本地相关方ID:自己系统生成,后续同步给法务系统时对应 o_id。
* 集团相对方ID:这里先做模拟生成,后续真实联调时用法务系统回传值覆盖。
*/
public final class PartnerIdUtils {
private static final String PARTNER_PREFIX = "OPP";
private static final String GROUP_PREFIX = "JT";
private PartnerIdUtils() {
}
public static String buildPartnerId() {
return PARTNER_PREFIX + "-" + formatNow("yyyyMMddHHmmss") + "-" + randomDigits(4);
}
public static String buildGroupPartnerId() {
return GROUP_PREFIX + "-" + formatNow("yyyyMMddHHmmss") + "-" + randomDigits(4);
}
private static String formatNow(String pattern) {
return new SimpleDateFormat(pattern).format(new Date());
}
private static String randomDigits(int length) {
StringBuilder builder = new StringBuilder();
for (int i = 0; i < length; i++) {
builder.append(ThreadLocalRandom.current().nextInt(10));
}
return builder.toString();
}
}
@@ -0,0 +1,42 @@
package com.nbport.zgwl.partner.utils;
/**
* 相对方模块常量
*/
public final class PartnerManageConstants {
private PartnerManageConstants() {
}
public static final String REQUEST_PREFIX = "/partner/manage";
public static final String DEFAULT_OPERATOR = "local_test";
public static final String PARTNER_IS_DELETED_NORMAL = "N";
public static final String PARTNER_IS_DELETED_DELETED = "Y";
public static final String CONTACT_DEL_FLAG_NORMAL = "0";
public static final String CONTACT_DEL_FLAG_DELETED = "2";
public static final String OPPOSITE_STATUS_EFFECTUAL = "EFFECTUAL";
public static final String GAT_FLAG_YES = "Y";
public static final String GAT_FLAG_NO = "N";
public static final String GROUP_FLAG_YES = "Y";
public static final String GROUP_FLAG_NO = "N";
public static final String FLAG_ONE = "1";
public static final String FLAG_ZERO = "0";
public static final String SYNC_STATUS_UNSYNCED = "unsynced";
public static final String SYNC_STATUS_SYNCED = "synced";
public static final String SYNC_STATUS_FAILED = "failed";
}
@@ -0,0 +1,71 @@
package com.nbport.zgwl.partner.utils;
import com.nbport.zgwl.contractpush.dto.PushPayloadDTO;
import com.nbport.zgwl.partner.dto.PartnerContactDto;
import com.nbport.zgwl.partner.dto.PartnerManageDto;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* 相对方同步报文构造工具
*/
public final class PartnerSyncPayloadBuilder {
private PartnerSyncPayloadBuilder() {
}
public static PushPayloadDTO buildCollectOppositesPayload(PartnerManageDto partner) {
Map<String, Object> payload = new LinkedHashMap<>();
payload.put("o_id", partner.getCustomerSysid() == null ? null : String.valueOf(partner.getCustomerSysid()));
payload.put("o_name", partner.getCustomerNameC());
payload.put("o_type", partner.getRelativeType());
payload.put("o_nature", partner.getOppositeCharacter());
payload.put("o_orgNature", convertOrgNature(partner.getNatureOfEnterpriseOrg()));
payload.put("o_orgNature_2", partner.getNatureOfEnterpriseOrgLev());
payload.put("o_GAT", PartnerManageConstants.GAT_FLAG_YES.equals(partner.getGatFlag())
? PartnerManageConstants.FLAG_ONE
: PartnerManageConstants.FLAG_ZERO);
payload.put("o_isGroup", PartnerManageConstants.GROUP_FLAG_YES.equals(partner.getGroupFlag())
? PartnerManageConstants.FLAG_ONE
: PartnerManageConstants.FLAG_ZERO);
payload.put("o_creditCode", partner.getCustomerTaxno());
payload.put("o_zjType", partner.getCertificateType());
payload.put("o_zjNo", partner.getCertificateNum());
payload.put("o_tin", partner.getTinCode());
payload.put("o_country", partner.getCountry());
payload.put("o_legalPerson", partner.getLegalPersonName());
payload.put("o_address", partner.getCustomerRegaddr());
payload.put("o_capital", partner.getRegCapital());
payload.put("o_creator", partner.getCreateBy());
payload.put("o_creationTime", partner.getCreateTime() == null ? null : partner.getCreateTime().toString().replace("T", " "));
payload.put("o_contacts", buildContactList(partner.getContacts()));
PushPayloadDTO requestDTO = new PushPayloadDTO();
requestDTO.setPayload(payload);
return requestDTO;
}
private static List<Map<String, Object>> buildContactList(List<PartnerContactDto> contacts) {
if (contacts == null) {
return List.of();
}
return contacts.stream().map(item -> {
Map<String, Object> contact = new LinkedHashMap<>();
contact.put("name", item.getContactName());
contact.put("phone", item.getContactPhone());
contact.put("postalAddress", item.getPostalAddress());
contact.put("email", item.getEmail());
return contact;
}).collect(Collectors.toList());
}
private static String convertOrgNature(String natureOfEnterpriseOrg) {
if ("ZY".equals(natureOfEnterpriseOrg)) {
return "ZF";
}
return natureOfEnterpriseOrg;
}
}
@@ -0,0 +1,54 @@
package com.nbport.zgwl.seal.config;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
* 签章平台配置类
* 从application.yml中读取签章平台相关配置
*
* 在application.yml中添加如下配置:
* seal:
* appKey: A7C4FD32BE454B5
* appSecret: 你的appSecret(联系浙江CA获取)
* baseUrl: https://saasapi.certca.cn
*
* @author xiaofengzhou
*/
@Component
@ConfigurationProperties(prefix = "seal")
public class SealConfig {
/** 应用Key,由浙江CA提供,用于获取访问令牌 */
private String appKey;
/** 应用密钥,由浙江CA提供,用于计算HMAC签名 */
private String appSecret;
/** 签章平台API基础地址,例如 https://saasapi.certca.cn */
private String baseUrl;
public String getAppKey() {
return appKey;
}
public void setAppKey(String appKey) {
this.appKey = appKey;
}
public String getAppSecret() {
return appSecret;
}
public void setAppSecret(String appSecret) {
this.appSecret = appSecret;
}
public String getBaseUrl() {
return baseUrl;
}
public void setBaseUrl(String baseUrl) {
this.baseUrl = baseUrl;
}
}
@@ -0,0 +1,34 @@
package com.nbport.zgwl.seal.config;
import com.nbport.zgwl.seal.handler.SealWebSocketHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
/**
* WebSocket配置类
*
* 配置签章平台回调的WebSocket连接端点
* 前端通过 ws://localhost:8080/seal/ws/callback 建立连接
*
* 注意:
* 1. setAllowedOrigins("*") 允许所有来源,生产环境应限制为具体域名
* 2. WebSocket不走Spring Security的HTTP认证,需要额外考虑安全性
*
* @author xiaofengzhou
*/
@Configuration
@EnableWebSocket
public class SealWebSocketConfig implements WebSocketConfigurer {
@Autowired
private SealWebSocketHandler sealWebSocketHandler;
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(sealWebSocketHandler, "/seal/ws/callback")
.setAllowedOrigins("*");
}
}
@@ -0,0 +1,111 @@
package com.nbport.zgwl.seal.controller;
import com.alibaba.fastjson2.JSON;
import com.nbport.zgwl.common.R;
import com.nbport.zgwl.seal.handler.SealWebSocketHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Map;
/**
* 签章平台回调接口控制器
*
* @author xiaofengzhou
*/
@RestController
@RequestMapping("/seal/callback")
public class SealCallbackController {
private static final Logger log = LoggerFactory.getLogger(SealCallbackController.class);
@Autowired
private SealWebSocketHandler webSocketHandler;
@PostMapping("/login")
public R<String> loginCallback(@RequestBody Map<String, Object> params) {
log.info("收到身份认证回调: {}", JSON.toJSONString(params));
try {
String requestId = (String) params.get("requestId");
webSocketHandler.sendCallbackMessage(requestId, "login", params);
return R.success("回调接收成功");
} catch (Exception e) {
log.error("处理身份认证回调失败", e);
return R.error("回调处理失败: " + e.getMessage());
}
}
@PostMapping("/sign")
public R<String> signCallback(@RequestBody Map<String, Object> params) {
log.info("收到签署回调: {}", JSON.toJSONString(params));
try {
String contractId = (String) params.get("contractId");
String requestId = (String) params.get("requestId");
webSocketHandler.sendCallbackMessage(requestId, "sign", params);
webSocketHandler.sendCallbackMessage(contractId, "contract_status", params);
return R.success("回调接收成功");
} catch (Exception e) {
log.error("处理签署回调失败", e);
return R.error("回调处理失败: " + e.getMessage());
}
}
@PostMapping("/presign")
public R<String> presignCallback(@RequestBody Map<String, Object> params) {
log.info("收到预盖章回调: {}", JSON.toJSONString(params));
try {
String contractId = (String) params.get("contractId");
String requestId = (String) params.get("requestId");
webSocketHandler.sendCallbackMessage(requestId, "presign", params);
webSocketHandler.sendCallbackMessage(contractId, "contract_status", params);
return R.success("回调接收成功");
} catch (Exception e) {
log.error("处理预盖章回调失败", e);
return R.error("回调处理失败: " + e.getMessage());
}
}
@PostMapping("/trusteeship")
public R<String> trusteeshipCallback(@RequestBody Map<String, Object> params) {
log.info("收到托管章回调: {}", JSON.toJSONString(params));
try {
String requestId = (String) params.get("requestId");
webSocketHandler.sendCallbackMessage(requestId, "trusteeship", params);
return R.success("回调接收成功");
} catch (Exception e) {
log.error("处理托管章回调失败", e);
return R.error("回调处理失败: " + e.getMessage());
}
}
@PostMapping("/signQrCode")
public R<String> signQrCodeCallback(@RequestBody Map<String, Object> params) {
log.info("收到签名二维码回调: {}", JSON.toJSONString(params));
try {
String requestId = (String) params.get("requestId");
webSocketHandler.sendCallbackMessage(requestId, "signQrCode", params);
return R.success("回调接收成功");
} catch (Exception e) {
log.error("处理签名二维码回调失败", e);
return R.error("回调处理失败: " + e.getMessage());
}
}
@GetMapping("/poll/{requestId}")
public R<Map<String, Object>> pollCallback(@PathVariable String requestId) {
Map<String, Object> callbackData = webSocketHandler.getCallbackData(requestId);
return R.success(callbackData);
}
}
@@ -0,0 +1,353 @@
package com.nbport.zgwl.seal.controller;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.nbport.zgwl.common.R;
import com.nbport.zgwl.seal.config.SealConfig;
import com.nbport.zgwl.seal.service.SealTokenService;
import com.nbport.zgwl.seal.utils.SealHttpClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import java.util.HashMap;
import java.util.Map;
/**
* 签章平台代理控制器
*
* @author xiaofengzhou
*/
@RestController
@RequestMapping("/seal/api")
public class SealProxyController {
private static final Logger log = LoggerFactory.getLogger(SealProxyController.class);
@Autowired
private SealConfig sealConfig;
@Autowired
private SealTokenService sealTokenService;
private String proxyPost(String apiPath, String requestBody) {
String accessToken = sealTokenService.getAccessToken();
String url = sealConfig.getBaseUrl() + apiPath;
log.info("代理请求签章平台: {} body: {}", url, requestBody);
return SealHttpClient.post(url, requestBody, accessToken, MediaType.APPLICATION_JSON_VALUE);
}
private String proxyPost(String apiPath) {
return proxyPost(apiPath, "{}");
}
private R<Object> parseSealResponse(String responseJson) {
try {
JSONObject result = JSON.parseObject(responseJson);
if (result.getBooleanValue("isSuccess")) {
return R.success(result.get("data"));
} else {
return R.error(result.getString("msg"));
}
} catch (Exception e) {
log.error("解析签章平台响应失败: {}", responseJson, e);
return R.error("解析签章平台响应失败: " + e.getMessage());
}
}
@PostMapping("/token/getAccessToken")
public R<Object> getAccessToken() {
try {
String token = sealTokenService.forceRefreshToken();
Map<String, Object> data = new HashMap<>();
data.put("access_token", token);
data.put("expires_in", 7200);
return R.success(data);
} catch (Exception e) {
log.error("获取Token失败", e);
return R.error("获取Token失败: " + e.getMessage());
}
}
@PostMapping("/token/clear")
public R<String> clearToken() {
sealTokenService.clearToken();
return R.success("Token已清除");
}
@PostMapping("/qrcode/getLoginQrCode")
public R<Object> getLoginQrCode(@RequestBody Map<String, String> params) {
try {
String notifyUrl = params.get("notifyUrl");
JSONObject body = new JSONObject();
body.put("notifyUrl", notifyUrl);
String response = proxyPost("/saas/api/qrcode/getLoginQrCode", body.toJSONString());
return parseSealResponse(response);
} catch (Exception e) {
log.error("获取身份认证二维码失败", e);
return R.error("获取身份认证二维码失败: " + e.getMessage());
}
}
@PostMapping("/contract/start")
public R<Object> startContract(@RequestParam("file") MultipartFile file,
@RequestParam("contractName") String contractName,
@RequestParam(value = "description", required = false) String description) {
try {
String accessToken = sealTokenService.getAccessToken();
String url = sealConfig.getBaseUrl() + "/saas/api/contract/start";
String response = SealHttpClient.postMultipart(url, file, contractName, description, accessToken);
return parseSealResponse(response);
} catch (Exception e) {
log.error("创建签署流程失败", e);
return R.error("创建签署流程失败: " + e.getMessage());
}
}
@PostMapping("/qrcode/getStandardSignQrCode")
public R<Object> getStandardSignQrCode(@RequestBody Map<String, Object> params) {
try {
JSONObject body = buildSignQrCodeBody(params);
String response = proxyPost("/saas/api/qrcode/getStandardSignQrCode", body.toJSONString());
return parseSealResponse(response);
} catch (Exception e) {
log.error("获取标准签章二维码失败", e);
return R.error("获取标准签章二维码失败: " + e.getMessage());
}
}
@PostMapping("/qrcode/getPreSignQrCode")
public R<Object> getPreSignQrCode(@RequestBody Map<String, Object> params) {
try {
JSONObject body = buildPreSignBody(params);
String response = proxyPost("/saas/api/qrcode/getPreSignQrCode", body.toJSONString());
return parseSealResponse(response);
} catch (Exception e) {
log.error("获取预盖章二维码失败", e);
return R.error("获取预盖章二维码失败: " + e.getMessage());
}
}
@PostMapping("/qrcode/getTrusteeshipSealQrCode")
public R<Object> getTrusteeshipSealQrCode(@RequestBody Map<String, String> params) {
try {
JSONObject body = new JSONObject();
body.put("notifyUrl", params.get("notifyUrl"));
String response = proxyPost("/saas/api/qrcode/getTrusteeshipSealQrCode", body.toJSONString());
return parseSealResponse(response);
} catch (Exception e) {
log.error("获取托管章二维码失败", e);
return R.error("获取托管章二维码失败: " + e.getMessage());
}
}
@PostMapping("/contract/confirmSign")
public R<Object> confirmSign(@RequestBody Map<String, Object> params) {
try {
JSONObject body = new JSONObject(params);
String response = proxyPost("/saas/api/contract/confirmSign", body.toJSONString());
return parseSealResponse(response);
} catch (Exception e) {
log.error("托管签署失败", e);
return R.error("托管签署失败: " + e.getMessage());
}
}
@PostMapping("/seal/getTimestamp")
public R<Object> getTimestamp(@RequestBody Map<String, String> params) {
try {
JSONObject body = new JSONObject();
body.put("formatPattern", params.getOrDefault("formatPattern", "yyyy-MM-dd"));
String response = proxyPost("/saas/api/seal/getTimestamp", body.toJSONString());
return parseSealResponse(response);
} catch (Exception e) {
log.error("获取时间戳印章失败", e);
return R.error("获取时间戳印章失败: " + e.getMessage());
}
}
@PostMapping("/contract/signTimestamp")
public R<Object> signTimestamp(@RequestBody Map<String, Object> params) {
try {
JSONObject body = new JSONObject(params);
String response = proxyPost("/saas/api/contract/signTimestamp", body.toJSONString());
return parseSealResponse(response);
} catch (Exception e) {
log.error("签署时间签章失败", e);
return R.error("签署时间签章失败: " + e.getMessage());
}
}
@PostMapping("/seal/getCommentSeal")
public R<Object> getCommentSeal(@RequestBody Map<String, Object> params) {
try {
JSONObject body = new JSONObject();
body.put("text", params.get("text"));
body.put("fontSize", params.get("fontSize"));
String response = proxyPost("/saas/api/seal/getCommentSeal", body.toJSONString());
return parseSealResponse(response);
} catch (Exception e) {
log.error("获取批注章预览失败", e);
return R.error("获取批注章预览失败: " + e.getMessage());
}
}
@PostMapping("/contract/signCommentSeal")
public R<Object> signCommentSeal(@RequestBody Map<String, Object> params) {
try {
JSONObject body = new JSONObject(params);
String response = proxyPost("/saas/api/contract/signCommentSeal", body.toJSONString());
return parseSealResponse(response);
} catch (Exception e) {
log.error("签署批注签章失败", e);
return R.error("签署批注签章失败: " + e.getMessage());
}
}
@PostMapping("/contract/complete")
public R<Object> completeContract(@RequestBody Map<String, String> params) {
try {
JSONObject body = new JSONObject();
body.put("contractId", params.get("contractId"));
String response = proxyPost("/saas/api/contract/complete", body.toJSONString());
return parseSealResponse(response);
} catch (Exception e) {
log.error("完成签署流程失败", e);
return R.error("完成签署流程失败: " + e.getMessage());
}
}
@PostMapping("/contract/cancel")
public R<Object> cancelContract(@RequestBody Map<String, String> params) {
try {
JSONObject body = new JSONObject();
body.put("contractId", params.get("contractId"));
String response = proxyPost("/saas/api/contract/cancel", body.toJSONString());
return parseSealResponse(response);
} catch (Exception e) {
log.error("取消签署流程失败", e);
return R.error("取消签署流程失败: " + e.getMessage());
}
}
@PostMapping("/user/getUserUniqueId")
public R<Object> getUserUniqueId(@RequestBody Map<String, String> params) {
try {
JSONObject body = new JSONObject();
body.put("name", params.get("name"));
body.put("mobile", params.get("mobile"));
body.put("idCode", params.get("idCode"));
String response = proxyPost("/saas/api/user/getUserUniqueId ", body.toJSONString());
return parseSealResponse(response);
} catch (Exception e) {
log.error("获取用户身份编码失败", e);
return R.error("获取用户身份编码失败: " + e.getMessage());
}
}
@PostMapping("/seal/getSealBySignerId")
public R<Object> getSealBySignerId(@RequestBody Map<String, Object> params) {
try {
JSONObject body = new JSONObject(params);
String response = proxyPost("/saas/api/seal/getSealBySignerId", body.toJSONString());
return parseSealResponse(response);
} catch (Exception e) {
log.error("获取用户印章失败", e);
return R.error("获取用户印章失败: " + e.getMessage());
}
}
@PostMapping("/contract/getDetail")
public R<Object> getContractDetail(@RequestBody Map<String, String> params) {
try {
JSONObject body = new JSONObject();
body.put("contractId", params.get("contractId"));
String response = proxyPost("/saas/api/contract/getDetail", body.toJSONString());
return parseSealResponse(response);
} catch (Exception e) {
log.error("获取合约详情失败", e);
return R.error("获取合约详情失败: " + e.getMessage());
}
}
@PostMapping("/contract/preview")
public R<Object> previewContract(@RequestBody Map<String, String> params) {
try {
JSONObject body = new JSONObject();
body.put("contractId", params.get("contractId"));
String response = proxyPost("/saas/api/contract/preview", body.toJSONString());
return parseSealResponse(response);
} catch (Exception e) {
log.error("预览合约文件失败", e);
return R.error("预览合约文件失败: " + e.getMessage());
}
}
@PostMapping("/contract/download")
public R<Object> downloadContract(@RequestBody Map<String, String> params) {
try {
JSONObject body = new JSONObject();
body.put("contractId", params.get("contractId"));
String response = proxyPost("/saas/api/contract/download", body.toJSONString());
return parseSealResponse(response);
} catch (Exception e) {
log.error("下载合约文件失败", e);
return R.error("下载合约文件失败: " + e.getMessage());
}
}
@PostMapping("/user/getUserInfos")
public R<Object> getUserInfos(@RequestBody Map<String, String> params) {
try {
JSONObject body = new JSONObject();
body.put("requestId", params.get("requestId"));
String response = proxyPost("/api/user/getUserInfos", body.toJSONString());
return parseSealResponse(response);
} catch (Exception e) {
log.error("获取用户隐私数据失败", e);
return R.error("获取用户隐私数据失败: " + e.getMessage());
}
}
@PostMapping("/qrcode/getSignQrCode")
public R<Object> getSignQrCode(@RequestBody Map<String, String> params) {
try {
JSONObject body = new JSONObject();
body.put("notifyUrl", params.get("notifyUrl"));
body.put("originalBase64Str", params.get("originalBase64Str"));
String response = proxyPost("/saas/api/qrcode/getSignQrCode", body.toJSONString());
return parseSealResponse(response);
} catch (Exception e) {
log.error("获取签名二维码失败", e);
return R.error("获取签名二维码失败: " + e.getMessage());
}
}
private JSONObject buildSignQrCodeBody(Map<String, Object> params) {
JSONObject body = new JSONObject();
body.put("contractId", params.get("contractId"));
body.put("notifyUrl", params.get("notifyUrl"));
body.put("isNeedUrlLink", params.getOrDefault("isNeedUrlLink", false));
body.put("sealConfigs", params.get("sealConfigs"));
body.put("pagingSealConfig", params.get("pagingSealConfig"));
return body;
}
private JSONObject buildPreSignBody(Map<String, Object> params) {
JSONObject body = new JSONObject();
body.put("contractId", params.get("contractId"));
body.put("notifyUrl", params.get("notifyUrl"));
body.put("isNeedUrlLink", params.getOrDefault("isNeedUrlLink", false));
body.put("sealConfigs", params.get("sealConfigs"));
body.put("pagingSealConfigs", params.get("pagingSealConfigs"));
body.put("timestampSealConfigs", params.get("timestampSealConfigs"));
return body;
}
}
@@ -0,0 +1,923 @@
package com.nbport.zgwl.seal.dto;
import java.util.List;
/**
* 签章平台API数据传输对象(DTO)
*
* 包含所有签章平台API的请求和响应数据结构
* 统一管理在一个文件中,方便后续迁移和维护
*
* @author xiaofengzhou
*/
public class SealApiDTO {
public static class SealResponse<T> {
private boolean isSuccess;
private String msg;
private int code;
private T data;
public boolean isSuccess() {
return isSuccess;
}
public void setSuccess(boolean success) {
isSuccess = success;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
}
public static class TokenResponseData {
private String access_token;
private Integer expires_in;
public String getAccess_token() {
return access_token;
}
public void setAccess_token(String access_token) {
this.access_token = access_token;
}
public Integer getExpires_in() {
return expires_in;
}
public void setExpires_in(Integer expires_in) {
this.expires_in = expires_in;
}
}
public static class ContractStartData {
private String contractId;
private Long fileSize;
private Integer imageWidth;
private Integer imageHeight;
private String pdfUrl;
private List<String> pdfToImageList;
public String getContractId() {
return contractId;
}
public void setContractId(String contractId) {
this.contractId = contractId;
}
public Long getFileSize() {
return fileSize;
}
public void setFileSize(Long fileSize) {
this.fileSize = fileSize;
}
public Integer getImageWidth() {
return imageWidth;
}
public void setImageWidth(Integer imageWidth) {
this.imageWidth = imageWidth;
}
public Integer getImageHeight() {
return imageHeight;
}
public void setImageHeight(Integer imageHeight) {
this.imageHeight = imageHeight;
}
public String getPdfUrl() {
return pdfUrl;
}
public void setPdfUrl(String pdfUrl) {
this.pdfUrl = pdfUrl;
}
public List<String> getPdfToImageList() {
return pdfToImageList;
}
public void setPdfToImageList(List<String> pdfToImageList) {
this.pdfToImageList = pdfToImageList;
}
}
public static class QrCodeResponseData {
private String requestId;
private Integer expiresIn;
private String qrCode;
private String urlLink;
public String getRequestId() {
return requestId;
}
public void setRequestId(String requestId) {
this.requestId = requestId;
}
public Integer getExpiresIn() {
return expiresIn;
}
public void setExpiresIn(Integer expiresIn) {
this.expiresIn = expiresIn;
}
public String getQrCode() {
return qrCode;
}
public void setQrCode(String qrCode) {
this.qrCode = qrCode;
}
public String getUrlLink() {
return urlLink;
}
public void setUrlLink(String urlLink) {
this.urlLink = urlLink;
}
}
public static class SealConfigItem {
private String sealId;
private Float x;
private Float y;
private Double scale;
private Integer pageNum;
public String getSealId() {
return sealId;
}
public void setSealId(String sealId) {
this.sealId = sealId;
}
public Float getX() {
return x;
}
public void setX(Float x) {
this.x = x;
}
public Float getY() {
return y;
}
public void setY(Float y) {
this.y = y;
}
public Double getScale() {
return scale;
}
public void setScale(Double scale) {
this.scale = scale;
}
public Integer getPageNum() {
return pageNum;
}
public void setPageNum(Integer pageNum) {
this.pageNum = pageNum;
}
}
public static class PagingSealConfig {
private String sealId;
private Double scale;
private Integer startPage;
private Integer endPage;
private Integer coordinateY;
public String getSealId() {
return sealId;
}
public void setSealId(String sealId) {
this.sealId = sealId;
}
public Double getScale() {
return scale;
}
public void setScale(Double scale) {
this.scale = scale;
}
public Integer getStartPage() {
return startPage;
}
public void setStartPage(Integer startPage) {
this.startPage = startPage;
}
public Integer getEndPage() {
return endPage;
}
public void setEndPage(Integer endPage) {
this.endPage = endPage;
}
public Integer getCoordinateY() {
return coordinateY;
}
public void setCoordinateY(Integer coordinateY) {
this.coordinateY = coordinateY;
}
}
public static class TimestampSealConfig {
private String formatPattern;
private Float x;
private Float y;
private Integer pageNum;
private Double scale;
private Integer width;
private Integer height;
public String getFormatPattern() {
return formatPattern;
}
public void setFormatPattern(String formatPattern) {
this.formatPattern = formatPattern;
}
public Float getX() {
return x;
}
public void setX(Float x) {
this.x = x;
}
public Float getY() {
return y;
}
public void setY(Float y) {
this.y = y;
}
public Integer getPageNum() {
return pageNum;
}
public void setPageNum(Integer pageNum) {
this.pageNum = pageNum;
}
public Double getScale() {
return scale;
}
public void setScale(Double scale) {
this.scale = scale;
}
public Integer getWidth() {
return width;
}
public void setWidth(Integer width) {
this.width = width;
}
public Integer getHeight() {
return height;
}
public void setHeight(Integer height) {
this.height = height;
}
}
public static class SignatureResult {
private String contractId;
private String contractName;
private Integer contractStatus;
private String signatureId;
private String signer;
private String signatureTime;
private List<SignatureItem> signatureList;
public String getContractId() {
return contractId;
}
public void setContractId(String contractId) {
this.contractId = contractId;
}
public String getContractName() {
return contractName;
}
public void setContractName(String contractName) {
this.contractName = contractName;
}
public Integer getContractStatus() {
return contractStatus;
}
public void setContractStatus(Integer contractStatus) {
this.contractStatus = contractStatus;
}
public String getSignatureId() {
return signatureId;
}
public void setSignatureId(String signatureId) {
this.signatureId = signatureId;
}
public String getSigner() {
return signer;
}
public void setSigner(String signer) {
this.signer = signer;
}
public String getSignatureTime() {
return signatureTime;
}
public void setSignatureTime(String signatureTime) {
this.signatureTime = signatureTime;
}
public List<SignatureItem> getSignatureList() {
return signatureList;
}
public void setSignatureList(List<SignatureItem> signatureList) {
this.signatureList = signatureList;
}
}
public static class SignatureItem {
private String owner;
private String signatureId;
private String signer;
private String signatureTime;
public String getOwner() {
return owner;
}
public void setOwner(String owner) {
this.owner = owner;
}
public String getSignatureId() {
return signatureId;
}
public void setSignatureId(String signatureId) {
this.signatureId = signatureId;
}
public String getSigner() {
return signer;
}
public void setSigner(String signer) {
this.signer = signer;
}
public String getSignatureTime() {
return signatureTime;
}
public void setSignatureTime(String signatureTime) {
this.signatureTime = signatureTime;
}
}
public static class SealInfo {
private String sealId;
private String sealName;
private String pictureUrl;
private Integer sealType;
private String owner;
public String getSealId() {
return sealId;
}
public void setSealId(String sealId) {
this.sealId = sealId;
}
public String getSealName() {
return sealName;
}
public void setSealName(String sealName) {
this.sealName = sealName;
}
public String getPictureUrl() {
return pictureUrl;
}
public void setPictureUrl(String pictureUrl) {
this.pictureUrl = pictureUrl;
}
public Integer getSealType() {
return sealType;
}
public void setSealType(Integer sealType) {
this.sealType = sealType;
}
public String getOwner() {
return owner;
}
public void setOwner(String owner) {
this.owner = owner;
}
}
public static class ContractDetail {
private String contractId;
private String contractName;
private Integer status;
private String statusName;
private String startTime;
private Integer fileSize;
private List<SignatureItem> signatureList;
private Integer imageWidth;
private Integer imageHeight;
private String pdfUrl;
private List<String> pdfToImageList;
private String downloadUrl;
public String getContractId() {
return contractId;
}
public void setContractId(String contractId) {
this.contractId = contractId;
}
public String getContractName() {
return contractName;
}
public void setContractName(String contractName) {
this.contractName = contractName;
}
public Integer getStatus() {
return status;
}
public void setStatus(Integer status) {
this.status = status;
}
public String getStatusName() {
return statusName;
}
public void setStatusName(String statusName) {
this.statusName = statusName;
}
public String getStartTime() {
return startTime;
}
public void setStartTime(String startTime) {
this.startTime = startTime;
}
public Integer getFileSize() {
return fileSize;
}
public void setFileSize(Integer fileSize) {
this.fileSize = fileSize;
}
public List<SignatureItem> getSignatureList() {
return signatureList;
}
public void setSignatureList(List<SignatureItem> signatureList) {
this.signatureList = signatureList;
}
public Integer getImageWidth() {
return imageWidth;
}
public void setImageWidth(Integer imageWidth) {
this.imageWidth = imageWidth;
}
public Integer getImageHeight() {
return imageHeight;
}
public void setImageHeight(Integer imageHeight) {
this.imageHeight = imageHeight;
}
public String getPdfUrl() {
return pdfUrl;
}
public void setPdfUrl(String pdfUrl) {
this.pdfUrl = pdfUrl;
}
public List<String> getPdfToImageList() {
return pdfToImageList;
}
public void setPdfToImageList(List<String> pdfToImageList) {
this.pdfToImageList = pdfToImageList;
}
public String getDownloadUrl() {
return downloadUrl;
}
public void setDownloadUrl(String downloadUrl) {
this.downloadUrl = downloadUrl;
}
}
public static class SignerIdData {
private String signerId;
public String getSignerId() {
return signerId;
}
public void setSignerId(String signerId) {
this.signerId = signerId;
}
}
public static class UserInfoData {
private String name;
private String mobile;
private String code;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getMobile() {
return mobile;
}
public void setMobile(String mobile) {
this.mobile = mobile;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
}
public static class LoginCallbackData {
private String requestId;
private String accountId;
private String accountType;
private String username;
private String entName;
private String entCode;
public String getRequestId() {
return requestId;
}
public void setRequestId(String requestId) {
this.requestId = requestId;
}
public String getAccountId() {
return accountId;
}
public void setAccountId(String accountId) {
this.accountId = accountId;
}
public String getAccountType() {
return accountType;
}
public void setAccountType(String accountType) {
this.accountType = accountType;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getEntName() {
return entName;
}
public void setEntName(String entName) {
this.entName = entName;
}
public String getEntCode() {
return entCode;
}
public void setEntCode(String entCode) {
this.entCode = entCode;
}
}
public static class SignCallbackData {
private String contractId;
private String contractName;
private Integer contractStatus;
private String signer;
private String signerId;
private String requestId;
public String getContractId() {
return contractId;
}
public void setContractId(String contractId) {
this.contractId = contractId;
}
public String getContractName() {
return contractName;
}
public void setContractName(String contractName) {
this.contractName = contractName;
}
public Integer getContractStatus() {
return contractStatus;
}
public void setContractStatus(Integer contractStatus) {
this.contractStatus = contractStatus;
}
public String getSigner() {
return signer;
}
public void setSigner(String signer) {
this.signer = signer;
}
public String getSignerId() {
return signerId;
}
public void setSignerId(String signerId) {
this.signerId = signerId;
}
public String getRequestId() {
return requestId;
}
public void setRequestId(String requestId) {
this.requestId = requestId;
}
}
public static class TrusteeshipCallbackData {
private String requestId;
private List<TrusteeshipSealItem> sealList;
private String signer;
private String signerId;
public String getRequestId() {
return requestId;
}
public void setRequestId(String requestId) {
this.requestId = requestId;
}
public List<TrusteeshipSealItem> getSealList() {
return sealList;
}
public void setSealList(List<TrusteeshipSealItem> sealList) {
this.sealList = sealList;
}
public String getSigner() {
return signer;
}
public void setSigner(String signer) {
this.signer = signer;
}
public String getSignerId() {
return signerId;
}
public void setSignerId(String signerId) {
this.signerId = signerId;
}
}
public static class TrusteeshipSealItem {
private String sealId;
private String sealName;
private String entName;
private String pictureUrl;
private String owner;
private String ownerSubAccountId;
private Integer sealType;
public String getSealId() {
return sealId;
}
public void setSealId(String sealId) {
this.sealId = sealId;
}
public String getSealName() {
return sealName;
}
public void setSealName(String sealName) {
this.sealName = sealName;
}
public String getEntName() {
return entName;
}
public void setEntName(String entName) {
this.entName = entName;
}
public String getPictureUrl() {
return pictureUrl;
}
public void setPictureUrl(String pictureUrl) {
this.pictureUrl = pictureUrl;
}
public String getOwner() {
return owner;
}
public void setOwner(String owner) {
this.owner = owner;
}
public String getOwnerSubAccountId() {
return ownerSubAccountId;
}
public void setOwnerSubAccountId(String ownerSubAccountId) {
this.ownerSubAccountId = ownerSubAccountId;
}
public Integer getSealType() {
return sealType;
}
public void setSealType(Integer sealType) {
this.sealType = sealType;
}
}
public static class SignQrCodeCallbackData {
private String requestId;
private String accountId;
private String accountType;
private String username;
private String entName;
private String entCode;
private String signatureValue;
public String getRequestId() {
return requestId;
}
public void setRequestId(String requestId) {
this.requestId = requestId;
}
public String getAccountId() {
return accountId;
}
public void setAccountId(String accountId) {
this.accountId = accountId;
}
public String getAccountType() {
return accountType;
}
public void setAccountType(String accountType) {
this.accountType = accountType;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getEntName() {
return entName;
}
public void setEntName(String entName) {
this.entName = entName;
}
public String getEntCode() {
return entCode;
}
public void setEntCode(String entCode) {
this.entCode = entCode;
}
public String getSignatureValue() {
return signatureValue;
}
public void setSignatureValue(String signatureValue) {
this.signatureValue = signatureValue;
}
}
}
@@ -0,0 +1,151 @@
package com.nbport.zgwl.seal.handler;
import com.alibaba.fastjson2.JSON;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;
import java.io.IOException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* 签章平台WebSocket处理器
*
* 职责:
* 1. 管理WebSocket连接
* 2. 接收签章平台回调后,实时推送给对应的前端页面
* 3. 提供轮询兜底机制
*
* @author xiaofengzhou
*/
@Component
public class SealWebSocketHandler extends TextWebSocketHandler {
private static final Logger log = LoggerFactory.getLogger(SealWebSocketHandler.class);
private static final ConcurrentHashMap<String, WebSocketSession> sessions = new ConcurrentHashMap<>();
private static final ConcurrentHashMap<String, String> requestSessionMap = new ConcurrentHashMap<>();
private static final ConcurrentHashMap<String, Map<String, Object>> callbackCache = new ConcurrentHashMap<>();
@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
String sessionId = session.getId();
sessions.put(sessionId, session);
log.info("WebSocket连接建立: sessionId={}", sessionId);
sendMessage(session, "{\"type\":\"connected\",\"sessionId\":\"" + sessionId + "\"}");
}
@Override
protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
String payload = message.getPayload();
log.debug("收到WebSocket消息: sessionId={}, payload={}", session.getId(), payload);
try {
Map<String, Object> msgMap = JSON.parseObject(payload, Map.class);
String action = (String) msgMap.get("action");
if ("subscribe".equals(action)) {
String requestId = (String) msgMap.get("requestId");
if (requestId != null) {
requestSessionMap.put(requestId, session.getId());
log.info("WebSocket订阅: requestId={}, sessionId={}", requestId, session.getId());
Map<String, Object> cached = callbackCache.get(requestId);
if (cached != null) {
sendCallbackToSession(session, requestId, (String) cached.get("callbackType"), cached);
callbackCache.remove(requestId);
}
}
} else if ("unsubscribe".equals(action)) {
String requestId = (String) msgMap.get("requestId");
if (requestId != null) {
requestSessionMap.remove(requestId);
log.info("WebSocket取消订阅: requestId={}", requestId);
}
} else if ("ping".equals(action)) {
sendMessage(session, "{\"type\":\"pong\"}");
}
} catch (Exception e) {
log.error("处理WebSocket消息失败", e);
}
}
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
String sessionId = session.getId();
sessions.remove(sessionId);
requestSessionMap.entrySet().removeIf(entry -> sessionId.equals(entry.getValue()));
log.info("WebSocket连接关闭: sessionId={}, status={}", sessionId, status);
}
@Override
public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
log.error("WebSocket传输错误: sessionId={}", session.getId(), exception);
sessions.remove(session.getId());
}
public void sendCallbackMessage(String requestId, String callbackType, Map<String, Object> data) {
if (requestId == null) {
return;
}
Map<String, Object> cacheEntry = new ConcurrentHashMap<>();
cacheEntry.put("callbackType", callbackType);
cacheEntry.putAll(data);
callbackCache.put(requestId, cacheEntry);
String sessionId = requestSessionMap.get(requestId);
if (sessionId != null) {
WebSocketSession session = sessions.get(sessionId);
if (session != null && session.isOpen()) {
sendCallbackToSession(session, requestId, callbackType, data);
log.info("WebSocket推送回调成功: requestId={}, callbackType={}", requestId, callbackType);
} else {
log.warn("WebSocket session不存在或已关闭: sessionId={}, requestId={}", sessionId, requestId);
requestSessionMap.remove(requestId);
}
} else {
log.info("没有前端订阅此requestId的回调: requestId={}, 数据已缓存供轮询使用", requestId);
}
}
private void sendCallbackToSession(WebSocketSession session, String requestId, String callbackType, Map<String, Object> data) {
try {
Map<String, Object> message = new ConcurrentHashMap<>();
message.put("type", "callback");
message.put("requestId", requestId);
message.put("callbackType", callbackType);
message.put("data", data);
sendMessage(session, JSON.toJSONString(message));
} catch (Exception e) {
log.error("WebSocket推送失败: sessionId={}", session.getId(), e);
}
}
private void sendMessage(WebSocketSession session, String message) {
try {
if (session.isOpen()) {
synchronized (session) {
session.sendMessage(new TextMessage(message));
}
}
} catch (IOException e) {
log.error("WebSocket发送消息失败", e);
}
}
public Map<String, Object> getCallbackData(String requestId) {
return callbackCache.remove(requestId);
}
public int getOnlineCount() {
return sessions.size();
}
}
@@ -0,0 +1,119 @@
package com.nbport.zgwl.seal.service;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.nbport.zgwl.seal.config.SealConfig;
import com.nbport.zgwl.seal.utils.HMacSHA256Util;
import com.nbport.zgwl.seal.utils.SeaHttpUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Service;
import java.util.concurrent.locks.ReentrantLock;
/**
* 签章平台Token管理服务
*
* @author xiaofengzhou
*/
@Service
public class SealTokenService {
private static final Logger log = LoggerFactory.getLogger(SealTokenService.class);
@Autowired
private SealConfig sealConfig;
private volatile String cachedToken;
private volatile long tokenExpireTime = 0;
private final ReentrantLock lock = new ReentrantLock();
private static final long TOKEN_EXPIRE_SECONDS = 7200;
private static final long REFRESH_BEFORE_SECONDS = 300;
public String getAccessToken() {
long now = System.currentTimeMillis();
if (cachedToken != null && now < tokenExpireTime - REFRESH_BEFORE_SECONDS * 1000) {
return cachedToken;
}
try {
lock.lock();
if (cachedToken != null && now < tokenExpireTime - REFRESH_BEFORE_SECONDS * 1000) {
return cachedToken;
}
refreshToken();
return cachedToken;
} finally {
lock.unlock();
}
}
public String forceRefreshToken() {
try {
lock.lock();
refreshToken();
return cachedToken;
} finally {
lock.unlock();
}
}
private void refreshToken() {
try {
String appKey = sealConfig.getAppKey();
String appSecret = sealConfig.getAppSecret();
long timestamp = System.currentTimeMillis();
JSONObject signData = new JSONObject();
signData.put("appKey", appKey);
signData.put("timestamp", timestamp);
String signatureStr = JSON.toJSONString(signData);
String signature = HMacSHA256Util.encrypt(signatureStr, appSecret);
log.debug("签章平台Token请求 - 签名原文: {}, 签名值: {}", signatureStr, signature);
JSONObject requestBody = new JSONObject();
requestBody.put("appKey", appKey);
requestBody.put("timestamp", timestamp);
requestBody.put("signature", signature);
String url = sealConfig.getBaseUrl() + "/saas/api/token/getAccessToken";
log.info("请求签章平台获取Token, URL: {}", url);
String response = SeaHttpUtils.sendPost(url, requestBody.toJSONString(), MediaType.APPLICATION_JSON_VALUE);
JSONObject result = JSON.parseObject(response);
if (result.getBooleanValue("isSuccess")) {
JSONObject data = result.getJSONObject("data");
cachedToken = data.getString("access_token");
long expiresIn = data.getLongValue("expires_in", TOKEN_EXPIRE_SECONDS);
tokenExpireTime = System.currentTimeMillis() + expiresIn * 1000;
log.info("签章平台Token获取成功, 有效期: {}秒", expiresIn);
} else {
String msg = result.getString("msg");
log.error("签章平台Token获取失败: {}", msg);
throw new RuntimeException("获取签章平台Token失败: " + msg);
}
} catch (RuntimeException e) {
throw e;
} catch (Exception e) {
log.error("获取签章平台Token异常", e);
throw new RuntimeException("获取签章平台Token异常: " + e.getMessage(), e);
}
}
public void clearToken() {
try {
lock.lock();
cachedToken = null;
tokenExpireTime = 0;
} finally {
lock.unlock();
}
}
}
@@ -0,0 +1,40 @@
package com.nbport.zgwl.seal.utils;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
/**
* HMacSHA256签名算法工具类
*
* @author xiaofengzhou
*/
public class HMacSHA256Util {
public static String encrypt(String message, String appSecret) {
final String algorithm = "HmacSHA256";
try {
Mac hmacSha256 = Mac.getInstance(algorithm);
byte[] keyBytes = appSecret.getBytes(StandardCharsets.UTF_8);
byte[] messageBytes = message.getBytes(StandardCharsets.UTF_8);
hmacSha256.init(new SecretKeySpec(keyBytes, 0, keyBytes.length, algorithm));
byte[] digestBytes = hmacSha256.doFinal(messageBytes);
return byteArrayToHexString(digestBytes);
} catch (Exception e) {
throw new RuntimeException("HMacSHA256签名计算失败: " + e.getMessage(), e);
}
}
private static String byteArrayToHexString(byte[] bytes) {
StringBuilder hs = new StringBuilder();
String stmp;
for (int n = 0; (bytes != null) && (n < bytes.length); n++) {
stmp = Integer.toHexString(bytes[n] & 0xFF);
if (stmp.length() == 1) {
hs.append('0');
}
hs.append(stmp);
}
return hs.toString().toLowerCase();
}
}
@@ -0,0 +1,16 @@
package com.nbport.zgwl.seal.utils;
/**
* 签章平台基础HTTP工具
*
* 保留单独工具类名称,便于和原项目调用习惯保持一致。
*/
public class SeaHttpUtils {
private SeaHttpUtils() {
}
public static String sendPost(String url, String jsonBody, String contentType) {
return SealHttpClient.post(url, jsonBody, null, contentType);
}
}
@@ -0,0 +1,179 @@
package com.nbport.zgwl.seal.utils;
import cn.hutool.core.util.StrUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.multipart.MultipartFile;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.security.cert.X509Certificate;
import java.util.UUID;
/**
* 签章平台HTTP客户端工具类
*
* @author xiaofengzhou
*/
public class SealHttpClient {
private static final Logger log = LoggerFactory.getLogger(SealHttpClient.class);
private static final int CONNECT_TIMEOUT = 30000;
private static final int READ_TIMEOUT = 60000;
public static String post(String url, String jsonBody, String accessToken, String contentType) {
HttpURLConnection conn = null;
try {
conn = createConnection(url);
conn.setRequestMethod("POST");
conn.setDoOutput(true);
conn.setDoInput(true);
conn.setRequestProperty("Content-Type", contentType);
conn.setRequestProperty("Accept", "application/json");
if (StrUtil.isNotBlank(accessToken)) {
conn.setRequestProperty("access_token", accessToken);
}
conn.setRequestProperty("User-Agent", "Mozilla/5.0");
if (jsonBody != null && !jsonBody.isEmpty()) {
try (OutputStream os = conn.getOutputStream()) {
byte[] input = jsonBody.getBytes(StandardCharsets.UTF_8);
os.write(input, 0, input.length);
}
}
return readResponse(conn);
} catch (Exception e) {
log.error("HTTP POST请求失败, URL: {}", url, e);
throw new RuntimeException("请求签章平台失败: " + e.getMessage(), e);
} finally {
if (conn != null) {
conn.disconnect();
}
}
}
public static String postMultipart(String url, MultipartFile file, String contractName, String description, String accessToken) {
HttpURLConnection conn = null;
String boundary = "----WebKitFormBoundary" + UUID.randomUUID().toString().replace("-", "");
try {
conn = createConnection(url);
conn.setRequestMethod("POST");
conn.setDoOutput(true);
conn.setDoInput(true);
conn.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + boundary);
if (StrUtil.isNotBlank(accessToken)) {
conn.setRequestProperty("access_token", accessToken);
}
conn.setRequestProperty("User-Agent", "Mozilla/5.0");
try (OutputStream os = conn.getOutputStream();
PrintWriter writer = new PrintWriter(new OutputStreamWriter(os, StandardCharsets.UTF_8), true)) {
addFormField(writer, boundary, "contractName", contractName);
if (description != null && !description.isEmpty()) {
addFormField(writer, boundary, "description", description);
}
writer.append("--").append(boundary).append("\r\n");
writer.append("Content-Disposition: form-data; name=\"file\"; filename=\"")
.append(file.getOriginalFilename()).append("\"\r\n");
writer.append("Content-Type: ").append(file.getContentType()).append("\r\n\r\n");
writer.flush();
os.write(file.getBytes());
os.flush();
writer.append("\r\n");
writer.append("--").append(boundary).append("--\r\n");
writer.flush();
}
return readResponse(conn);
} catch (Exception e) {
log.error("文件上传请求失败, URL: {}", url, e);
throw new RuntimeException("文件上传失败: " + e.getMessage(), e);
} finally {
if (conn != null) {
conn.disconnect();
}
}
}
private static void addFormField(PrintWriter writer, String boundary, String name, String value) {
writer.append("--").append(boundary).append("\r\n");
writer.append("Content-Disposition: form-data; name=\"")
.append(name).append("\"\r\n\r\n");
writer.append(value).append("\r\n");
writer.flush();
}
private static HttpURLConnection createConnection(String urlStr) throws Exception {
URL url = new URL(urlStr);
HttpURLConnection conn;
if ("https".equalsIgnoreCase(url.getProtocol())) {
HttpsURLConnection httpsConn = (HttpsURLConnection) url.openConnection();
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, new TrustManager[]{new TrustAllManager()}, new java.security.SecureRandom());
httpsConn.setSSLSocketFactory(sslContext.getSocketFactory());
httpsConn.setHostnameVerifier((hostname, session) -> true);
conn = httpsConn;
} else {
conn = (HttpURLConnection) url.openConnection();
}
conn.setConnectTimeout(CONNECT_TIMEOUT);
conn.setReadTimeout(READ_TIMEOUT);
return conn;
}
private static String readResponse(HttpURLConnection conn) throws IOException {
int responseCode = conn.getResponseCode();
InputStream inputStream = responseCode >= 200 && responseCode < 300 ? conn.getInputStream() : conn.getErrorStream();
if (inputStream == null) {
return "{}";
}
StringBuilder response = new StringBuilder();
try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8))) {
String line;
while ((line = reader.readLine()) != null) {
response.append(line);
}
}
log.debug("签章平台响应 [{}]: {}", responseCode, response);
return response.toString();
}
private static class TrustAllManager implements X509TrustManager {
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType) {
}
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType) {
}
@Override
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}
}
}
@@ -0,0 +1,81 @@
package com.nbport.zgwl.service;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.IdUtil;
import com.nbport.zgwl.exception.ServiceErrorCodeRange;
import com.nbport.zgwl.exception.ServiceException;
import com.nbport.zgwl.utils.FileStorageUtils;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.http.ContentDisposition;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.Objects;
@Service
@Slf4j
@RequiredArgsConstructor
public class LocalFileService {
@Value("${file.local.path:${user.dir}/upload-files}")
private String localPath;
public String upload(MultipartFile file) {
if (file == null || file.isEmpty()) {
throw new ServiceException("上传文件不能为空");
}
try {
Path rootPath = Paths.get(localPath).toAbsolutePath().normalize();
Files.createDirectories(rootPath);
String dateFolder = LocalDate.now().format(DateTimeFormatter.BASIC_ISO_DATE);
String suffix = FileStorageUtils.getFileSuffix(Objects.requireNonNullElse(file.getOriginalFilename(), ""));
String fileName = IdUtil.simpleUUID() + suffix;
String relativePath = dateFolder + "/" + fileName;
Path targetPath = FileStorageUtils.resolveFilePath(rootPath, relativePath);
Files.createDirectories(targetPath.getParent());
file.transferTo(targetPath);
log.info("文件上传成功,targetPath={}", targetPath);
return relativePath;
} catch (IOException e) {
log.error("文件上传失败", e);
throw new ServiceException("文件上传失败");
}
}
public ResponseEntity<ByteArrayResource> download(String fileName) {
try {
Path rootPath = Paths.get(localPath).toAbsolutePath().normalize();
Path filePath = FileStorageUtils.resolveFilePath(rootPath, fileName);
if (!Files.exists(filePath) || Files.isDirectory(filePath)) {
throw new ServiceException(ServiceErrorCodeRange.FILE_NOT_FOUND, "文件不存在");
}
byte[] bytes = Files.readAllBytes(filePath);
String contentType = Files.probeContentType(filePath);
MediaType mediaType = contentType == null ? MediaType.APPLICATION_OCTET_STREAM : MediaType.parseMediaType(contentType);
HttpHeaders headers = new HttpHeaders();
headers.setContentType(mediaType);
headers.setContentLength(bytes.length);
headers.setContentDisposition(ContentDisposition.inline().filename(filePath.getFileName().toString()).build());
return ResponseEntity.ok()
.headers(headers)
.body(new ByteArrayResource(bytes));
} catch (IOException e) {
log.error("文件下载失败", e);
throw new ServiceException("文件下载失败");
}
}
}
@@ -0,0 +1,79 @@
package com.nbport.zgwl.service;
import cn.hutool.core.bean.BeanUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.nbport.zgwl.common.PageResult;
import com.nbport.zgwl.dto.StudentQueryDto;
import com.nbport.zgwl.dto.StudentSaveDto;
import com.nbport.zgwl.entity.StudentEntity;
import com.nbport.zgwl.entity.SysUserEntity;
import com.nbport.zgwl.exception.ServiceErrorCodeRange;
import com.nbport.zgwl.exception.ServiceException;
import com.nbport.zgwl.mapper.StudentMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Map;
@Service
@Slf4j
@RequiredArgsConstructor
public class StudentService {
private final StudentMapper studentMapper;
public PageResult<Map<String, Object>> queryList(StudentQueryDto queryDto, SysUserEntity user) {
try {
log.info("学生分页查询,user={}, queryDto={}", user, queryDto);
Page<Map<String, Object>> page = new Page<>(queryDto.getPageNo(), queryDto.getPageSize());
return new PageResult<>(studentMapper.queryList(page, queryDto));
} catch (Exception e) {
log.error("分页查询学生失败", e);
throw new ServiceException("分页查询失败");
}
}
public StudentEntity detail(Long id) {
StudentEntity studentEntity = studentMapper.selectById(id);
if (studentEntity == null) {
throw new ServiceException(ServiceErrorCodeRange.DATA_NOT_FOUND, "学生信息不存在");
}
return studentEntity;
}
@Transactional(rollbackFor = Exception.class)
public void add(StudentSaveDto dto) {
boolean exists = studentMapper.exists(new LambdaQueryWrapper<StudentEntity>()
.eq(StudentEntity::getStudentNo, dto.getStudentNo()));
if (exists) {
throw new ServiceException("学号已存在");
}
StudentEntity entity = BeanUtil.copyProperties(dto, StudentEntity.class);
studentMapper.insert(entity);
}
@Transactional(rollbackFor = Exception.class)
public void update(StudentSaveDto dto) {
if (dto.getId() == null) {
throw new ServiceException("id 不能为空");
}
StudentEntity dbEntity = detail(dto.getId());
boolean exists = studentMapper.exists(new LambdaQueryWrapper<StudentEntity>()
.eq(StudentEntity::getStudentNo, dto.getStudentNo())
.ne(StudentEntity::getId, dto.getId()));
if (exists) {
throw new ServiceException("学号已存在");
}
BeanUtil.copyProperties(dto, dbEntity);
studentMapper.updateById(dbEntity);
}
@Transactional(rollbackFor = Exception.class)
public void delete(Long id) {
detail(id);
studentMapper.deleteById(id);
}
}
@@ -0,0 +1,28 @@
package com.nbport.zgwl.utils;
import cn.hutool.core.util.StrUtil;
import com.nbport.zgwl.exception.ServiceException;
import java.nio.file.Path;
public class FileStorageUtils {
private FileStorageUtils() {
}
public static String getFileSuffix(String originalFilename) {
if (StrUtil.isBlank(originalFilename) || !originalFilename.contains(".")) {
return "";
}
return originalFilename.substring(originalFilename.lastIndexOf("."));
}
public static Path resolveFilePath(Path rootPath, String relativePath) {
String normalizedRelativePath = relativePath.replace("\\", "/");
Path targetPath = rootPath.resolve(normalizedRelativePath).normalize();
if (!targetPath.startsWith(rootPath)) {
throw new ServiceException("非法文件路径");
}
return targetPath;
}
}
@@ -0,0 +1,17 @@
package com.nbport.zgwl.utils;
import com.nbport.zgwl.entity.SysUserEntity;
import org.springframework.stereotype.Component;
@Component
public class MockSecurityManager {
public SysUserEntity getLoginUser() {
SysUserEntity user = new SysUserEntity();
user.setId(1L);
user.setUsername("admin");
user.setMobilePhone("13800000000");
user.setEmail("admin@test.com");
return user;
}
}