首次提交

This commit is contained in:
2026-05-26 10:08:21 +08:00
commit 3b70da2f0b
92 changed files with 7237 additions and 0 deletions
+47
View File
@@ -0,0 +1,47 @@
######################################################################
# Build Tools
.gradle
/build/
!gradle/wrapper/gradle-wrapper.jar
target/
!.mvn/wrapper/maven-wrapper.jar
######################################################################
# IDE
### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr
### JRebel ###
rebel.xml
### NetBeans ###
nbproject/private/
build/*
nbbuild/
dist/
nbdist/
.nb-gradle/
######################################################################
# Others
*.log
*.xml.versionsBackup
*.swp
!*/build/*.java
!*/build/*.html
!*/build/*.xml
+123
View File
@@ -0,0 +1,123 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.5</version>
<relativePath/>
</parent>
<groupId>com.nbport</groupId>
<artifactId>zgwl-end</artifactId>
<version>1.0.0-SNAPSHOT</version>
<name>zgwl-end</name>
<description>zgwl-end single module demo</description>
<packaging>jar</packaging>
<properties>
<java.version>17</java.version>
<mybatis-plus.version>3.5.11</mybatis-plus.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>2.2.0</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-jsqlparser</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>com.oracle.database.jdbc</groupId>
<artifactId>ojdbc11</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.16</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>3.3.2</version>
</dependency>
<dependency>
<groupId>org.apache.xmlbeans</groupId>
<artifactId>xmlbeans</artifactId>
<version>3.1.0</version>
</dependency>
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId>
<version>2.0.47</version>
</dependency>
<dependency>
<groupId>net.coobird</groupId>
<artifactId>thumbnailator</artifactId>
<version>0.4.8</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.32</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</build>
</project>
@@ -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;
}
}
+6
View File
@@ -0,0 +1,6 @@
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/zgwl_end?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&useSSL=false&allowPublicKeyRetrieval=true
username: root
password: root
@@ -0,0 +1,8 @@
spring:
datasource:
driver-class-name: oracle.jdbc.OracleDriver
url: jdbc:oracle:thin:@//127.0.0.1:1521/XEPDB1
username: developer
password: dev123
hikari:
connection-test-query: SELECT 1 FROM DUAL
+64
View File
@@ -0,0 +1,64 @@
server:
port: 8899
spring:
application:
name: zgwl-end
profiles:
active: ${SPRING_PROFILES_ACTIVE:oracle}
servlet:
multipart:
max-file-size: 20MB
max-request-size: 20MB
mybatis-plus:
mapper-locations: classpath*:mapper/**/*.xml
type-aliases-package: com.nbport.zgwl.entity,com.nbport.zgwl.partner.domain,com.nbport.zgwl.manifest.domain
configuration:
map-underscore-to-camel-case: true
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
global-config:
banner: false
springdoc:
swagger-ui:
path: /swagger-ui.html
api-docs:
path: /v3/api-docs
file:
local:
path: ${user.dir}/upload-files
seal:
appKey: A7C4FD32BE454B5
appSecret: 6c74358c28c149f18242bd614a101910
baseUrl: https://saasapi.certca.cn
contract-push:
settlement:
systemKey: stamp-settlement-test-key
secretKey: stamp-settlement-test-secret
tokenUrl: http://10.168.53.211:8888/grcv5/serverapi/restapi/other/v1/ecp/getToken
pushContractUrl: http://10.168.53.211:8888/grcv5/serverapi/restapi/other/v1/ecp/pushContract
approveQueryUrl: http://10.168.53.211:8888/grcv5/serverapi/restapi/other/v1/ecp/queryInfoByContractSysId
archiveUrl: http://10.168.53.211:8888/grcv5/serverapi/restapi/other/v1/ecp/collectContractArchive
approveViewBaseUrl: http://10.168.53.211:8888/wp/grcv5/contractView/G_1_viewContractApprove.htm
dataCollection:
systemKey: stamp-collection-test-key
secretKey: stamp-collection-test-secret
sysCode: stamp-end-demo
tokenUrl: http://10.168.53.211:8888/grcv5/cj/rest/dataCenter/getToken
uploadUrl: http://10.168.53.211:8888/grcv5/cj/rest/dataCenter/upload
collectOppositesUrl: http://10.168.53.211:8888/grcv5/cj/rest/dataCenter/collectOpposites
collectZtbsUrl: http://10.168.53.211:8888/grcv5/cj/rest/dataCenter/collectZtbs
collectContractUrl: http://10.168.53.211:8888/grcv5/cj/rest/dataCenter/collectContract
collectPlanFeedbackUrl: http://10.168.53.211:8888/grcv5/cj/rest/dataCenter/collectPlanFeedback
collectContractCloseUrl: http://10.168.53.211:8888/grcv5/cj/rest/dataCenter/collectContractClose
collectContractArchiveUrl: http://10.168.53.211:8888/grcv5/cj/rest/dataCenter/collectContractArchive
queryApproveInfoUrl: http://10.168.53.211:8888/grcv5/cj/rest/dataCenter/queryApproveInfo
approveViewBaseUrl: http://10.168.53.211:8888/grcv5/wp/contractView/G_1_viewContractApprove.htm
logging:
level:
com.nbport.zgwl.mapper: debug
@@ -0,0 +1,59 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.nbport.zgwl.mapper.StudentMapper">
<select id="queryList" resultType="java.util.Map">
select
id,
student_no as studentNo,
student_name as studentName,
gender,
age,
phone,
address,
create_dt as createDt,
update_dt as updateDt,
create_by as createBy,
update_by as updateBy
from BIZ_STUDENT
<where>
<if test="query.studentNo != null and query.studentNo != ''">
and student_no like CONCAT(CONCAT('%', #{query.studentNo}), '%')
</if>
<if test="query.studentName != null and query.studentName != ''">
and student_name like CONCAT(CONCAT('%', #{query.studentName}), '%')
</if>
<if test="query.gender != null and query.gender != ''">
and gender = #{query.gender}
</if>
</where>
<choose>
<when test="query.sorter != null and query.sorter.field == 'studentNo'">
order by student_no
<choose>
<when test="query.sorter.order == 'desc'">desc</when>
<otherwise>asc</otherwise>
</choose>
</when>
<when test="query.sorter != null and query.sorter.field == 'studentName'">
order by student_name
<choose>
<when test="query.sorter.order == 'desc'">desc</when>
<otherwise>asc</otherwise>
</choose>
</when>
<when test="query.sorter != null and query.sorter.field == 'age'">
order by age
<choose>
<when test="query.sorter.order == 'desc'">desc</when>
<otherwise>asc</otherwise>
</choose>
</when>
<otherwise>
order by update_dt desc, create_dt desc
</otherwise>
</choose>
</select>
</mapper>
@@ -0,0 +1,131 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.nbport.zgwl.manifest.mapper.ContractTemplateMapper">
<select id="selectTemplateSeqNextVal" resultType="java.lang.Long">
select SEQ_CONTRACT_TEMPLATE_MANAGE.NEXTVAL from dual
</select>
<resultMap id="ContractTemplateResult" type="com.nbport.zgwl.manifest.dto.ContractTemplateDto">
<id property="id" column="id"/>
<result property="templateCode" column="template_code"/>
<result property="templateName" column="template_name"/>
<result property="source" column="source"/>
<result property="contractType" column="contract_type"/>
<result property="selfPartyRole" column="self_party_role"/>
<result property="version" column="version"/>
<result property="status" column="status"/>
<result property="variableCount" column="variable_count"/>
<result property="description" column="description"/>
<result property="usageTip" column="usage_tip"/>
<result property="templateFileName" column="template_file_name"/>
<result property="templateFilePath" column="template_file_path"/>
<result property="publishOrgIdsJson" column="publish_org_ids_json"/>
<result property="publishOrgNamesJson" column="publish_org_names_json"/>
<result property="variableMappingsJson" column="variable_mappings_json"/>
<result property="createBy" column="create_by"/>
<result property="createdTime" column="created_time"/>
<result property="updatedBy" column="updated_by"/>
<result property="updatedTime" column="updated_time"/>
<result property="delFlag" column="del_flag"/>
</resultMap>
<sql id="selectTemplateColumns">
select id, template_code, template_name, source, contract_type, self_party_role,
version, status, variable_count, description, usage_tip,
template_file_name, template_file_path,
publish_org_ids_json, publish_org_names_json, variable_mappings_json,
create_by, created_time, updated_by, updated_time, del_flag
from contract_template_manage
</sql>
<select id="selectTemplatePage" resultMap="ContractTemplateResult">
<include refid="selectTemplateColumns"/>
<where>
del_flag = '0'
<if test="query.templateName != null and query.templateName != ''">
and template_name like CONCAT(CONCAT('%', #{query.templateName}), '%')
</if>
<if test="query.contractType != null and query.contractType != ''">
and contract_type = #{query.contractType}
</if>
<if test="query.source != null and query.source != ''">
and source = #{query.source}
</if>
<if test="query.status != null and query.status != ''">
and status = #{query.status}
</if>
</where>
order by updated_time desc, id desc
</select>
<select id="selectPublishedTemplates" resultMap="ContractTemplateResult">
<include refid="selectTemplateColumns"/>
where del_flag = '0'
and status = 'published'
order by updated_time desc, id desc
</select>
<select id="selectTemplateById" resultMap="ContractTemplateResult">
<include refid="selectTemplateColumns"/>
where id = #{id} and del_flag = '0'
</select>
<insert id="insertTemplate" parameterType="com.nbport.zgwl.manifest.dto.ContractTemplateDto">
insert into contract_template_manage
(
id, template_code, template_name, source, contract_type, self_party_role,
version, status, variable_count, description, usage_tip,
template_file_name, template_file_path,
publish_org_ids_json, publish_org_names_json, variable_mappings_json,
create_by, created_time, updated_by, updated_time, del_flag
)
values
(
#{id}, #{templateCode}, #{templateName}, #{source}, #{contractType}, #{selfPartyRole},
#{version}, #{status}, #{variableCount}, #{description}, #{usageTip},
#{templateFileName}, #{templateFilePath},
#{publishOrgIdsJson}, #{publishOrgNamesJson}, #{variableMappingsJson},
#{createBy}, #{createdTime}, #{updatedBy}, #{updatedTime}, #{delFlag}
)
</insert>
<update id="updateTemplate" parameterType="com.nbport.zgwl.manifest.dto.ContractTemplateDto">
update contract_template_manage
<trim prefix="set" suffixOverrides=",">
template_name = #{templateName},
source = #{source},
contract_type = #{contractType},
self_party_role = #{selfPartyRole},
version = #{version},
status = #{status},
variable_count = #{variableCount},
description = #{description},
usage_tip = #{usageTip},
template_file_name = #{templateFileName},
template_file_path = #{templateFilePath},
publish_org_ids_json = #{publishOrgIdsJson},
publish_org_names_json = #{publishOrgNamesJson},
variable_mappings_json = #{variableMappingsJson},
updated_by = #{updatedBy},
updated_time = #{updatedTime},
</trim>
where id = #{id} and del_flag = '0'
</update>
<update id="updateTemplateStatus">
update contract_template_manage
set status = #{status},
updated_by = #{updateBy},
updated_time = #{updateTime}
where id = #{id} and del_flag = '0'
</update>
<update id="deleteTemplateById">
update contract_template_manage
set del_flag = #{delFlag},
updated_by = #{updateBy},
updated_time = #{updateTime}
where id = #{id} and del_flag = '0'
</update>
</mapper>
@@ -0,0 +1,238 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.nbport.zgwl.partner.mapper.PartnerManageMapper">
<select id="selectPartnerSeqNextVal" resultType="java.lang.Long">
select SEQ_ILDM_FEE_CUSTOMER_INFO.NEXTVAL from dual
</select>
<select id="selectPartnerContactSeqNextVal" resultType="java.lang.Long">
select SEQ_SEAL_PARTNER_CONTACT.NEXTVAL from dual
</select>
<resultMap id="PartnerManageResult" type="com.nbport.zgwl.partner.dto.PartnerManageDto">
<id property="customerSysid" column="CUSTOMER_SYSID"/>
<result property="customerNameC" column="CUSTOMER_NAME_C"/>
<result property="customerTaxno" column="CUSTOMER_TAXNO"/>
<result property="customerRegaddr" column="CUSTOMER_REGADDR"/>
<result property="emailAddress" column="EMAIL_ADDRESS"/>
<result property="oppositeId" column="OPPOSITE_ID"/>
<result property="oppositeCharacter" column="OPPOSITE_CHARACTER"/>
<result property="gatFlag" column="GAT_FLAG"/>
<result property="groupFlag" column="GROUP_FLAG"/>
<result property="country" column="COUNTRY"/>
<result property="countryName" column="COUNTRY_NAME"/>
<result property="legalPersonName" column="LEGAL_PERSON_NAME"/>
<result property="certificateType" column="CERTIFICATE_TYPE"/>
<result property="certificateNum" column="CERTIFICATE_NUM"/>
<result property="tinCode" column="TIN_CODE"/>
<result property="registeredCapital" column="REGISTERED_CAPITAL"/>
<result property="regCapital" column="REG_CAPITAL"/>
<result property="oppositeCode" column="OPPOSITE_CODE"/>
<result property="oppositeStatus" column="OPPOSITE_STATUS"/>
<result property="isSgat" column="IS_SGAT"/>
<result property="isJtn" column="IS_JTN"/>
<result property="natureOfEnterpriseOrg" column="NATURE_OF_ENTERPRISE_ORG"/>
<result property="natureOfEnterpriseOrgLev" column="NATURE_OF_ENTERPRISE_ORG_LEV"/>
<result property="relativeType" column="RELATIVE_TYPE"/>
<result property="isDeleted" column="IS_DELETED"/>
<result property="comments" column="COMMENTS"/>
<result property="remark" column="REMARK"/>
<result property="createBy" column="CREATE_BY"/>
<result property="createTime" column="CREATE_TIME"/>
<result property="updateBy" column="UPDATE_BY"/>
<result property="updateTime" column="UPDATE_TIME"/>
<result property="syncStatus" column="sync_status"/>
<result property="syncTime" column="sync_time"/>
<result property="syncMessage" column="sync_message"/>
</resultMap>
<resultMap id="PartnerContactResult" type="com.nbport.zgwl.partner.dto.PartnerContactDto">
<id property="id" column="id"/>
<result property="partnerRecordId" column="partner_record_id"/>
<result property="contactName" column="contact_name"/>
<result property="contactPhone" column="contact_phone"/>
<result property="postalAddress" column="postal_address"/>
<result property="email" column="email"/>
<result property="sortOrder" column="sort_order"/>
<result property="delFlag" column="del_flag"/>
<result property="remark" column="remark"/>
</resultMap>
<sql id="selectPartnerColumns">
select CUSTOMER_SYSID, CUSTOMER_NAME_C, CUSTOMER_TAXNO, CUSTOMER_REGADDR,
EMAIL_ADDRESS, OPPOSITE_ID, OPPOSITE_CHARACTER, GAT_FLAG, GROUP_FLAG,
COUNTRY, COUNTRY_NAME, LEGAL_PERSON_NAME, CERTIFICATE_TYPE,
CERTIFICATE_NUM, TIN_CODE, REGISTERED_CAPITAL, REG_CAPITAL,
OPPOSITE_CODE, OPPOSITE_STATUS, IS_SGAT, IS_JTN,
NATURE_OF_ENTERPRISE_ORG, NATURE_OF_ENTERPRISE_ORG_LEV, RELATIVE_TYPE,
IS_DELETED, COMMENTS, REMARK, CREATE_BY, CREATE_TIME,
UPDATE_BY, UPDATE_TIME, sync_status, sync_time, sync_message
from ILDM_FEE_CUSTOMER_INFO
</sql>
<select id="selectPartnerPage" resultMap="PartnerManageResult">
<include refid="selectPartnerColumns"/>
<where>
IS_DELETED = 'N'
<if test="query.customerNameC != null and query.customerNameC != ''">
and CUSTOMER_NAME_C like CONCAT(CONCAT('%', #{query.customerNameC}), '%')
</if>
<if test="query.relativeType != null and query.relativeType != ''">
and RELATIVE_TYPE = #{query.relativeType}
</if>
<if test="query.oppositeCharacter != null and query.oppositeCharacter != ''">
and OPPOSITE_CHARACTER = #{query.oppositeCharacter}
</if>
<if test="query.groupFlag != null and query.groupFlag != ''">
and GROUP_FLAG = #{query.groupFlag}
</if>
<if test="query.syncStatus != null and query.syncStatus != ''">
and sync_status = #{query.syncStatus}
</if>
</where>
order by CREATE_TIME desc, CUSTOMER_SYSID desc
</select>
<select id="selectPartnerById" resultMap="PartnerManageResult">
<include refid="selectPartnerColumns"/>
where CUSTOMER_SYSID = #{customerSysid}
and IS_DELETED = 'N'
</select>
<insert id="insertPartner" parameterType="com.nbport.zgwl.partner.dto.PartnerManageDto">
insert into ILDM_FEE_CUSTOMER_INFO
(
CUSTOMER_SYSID, CUSTOMER_NAME_C, CUSTOMER_TAXNO, CUSTOMER_REGADDR, EMAIL_ADDRESS,
OPPOSITE_ID, OPPOSITE_CHARACTER, GAT_FLAG, GROUP_FLAG, COUNTRY,
COUNTRY_NAME, LEGAL_PERSON_NAME, CERTIFICATE_TYPE, CERTIFICATE_NUM,
TIN_CODE, REGISTERED_CAPITAL, REG_CAPITAL, OPPOSITE_CODE,
OPPOSITE_STATUS, IS_SGAT, IS_JTN, NATURE_OF_ENTERPRISE_ORG,
NATURE_OF_ENTERPRISE_ORG_LEV, RELATIVE_TYPE, IS_DELETED, COMMENTS,
REMARK, CREATE_BY, CREATE_TIME, UPDATE_BY, UPDATE_TIME,
sync_status, sync_time, sync_message
)
values
(
#{customerSysid}, #{customerNameC}, #{customerTaxno}, #{customerRegaddr}, #{emailAddress},
#{oppositeId}, #{oppositeCharacter}, #{gatFlag}, #{groupFlag}, #{country},
#{countryName}, #{legalPersonName}, #{certificateType}, #{certificateNum},
#{tinCode}, #{registeredCapital}, #{regCapital}, #{oppositeCode},
#{oppositeStatus}, #{isSgat}, #{isJtn}, #{natureOfEnterpriseOrg},
#{natureOfEnterpriseOrgLev}, #{relativeType}, #{isDeleted}, #{comments},
#{remark}, #{createBy}, #{createTime}, #{updateBy}, #{updateTime},
#{syncStatus}, #{syncTime}, #{syncMessage}
)
</insert>
<update id="updatePartner" parameterType="com.nbport.zgwl.partner.dto.PartnerManageDto">
update ILDM_FEE_CUSTOMER_INFO
<trim prefix="set" suffixOverrides=",">
CUSTOMER_NAME_C = #{customerNameC},
CUSTOMER_TAXNO = #{customerTaxno},
CUSTOMER_REGADDR = #{customerRegaddr},
EMAIL_ADDRESS = #{emailAddress},
OPPOSITE_CHARACTER = #{oppositeCharacter},
GAT_FLAG = #{gatFlag},
GROUP_FLAG = #{groupFlag},
COUNTRY = #{country},
COUNTRY_NAME = #{countryName},
LEGAL_PERSON_NAME = #{legalPersonName},
CERTIFICATE_TYPE = #{certificateType},
CERTIFICATE_NUM = #{certificateNum},
TIN_CODE = #{tinCode},
REGISTERED_CAPITAL = #{registeredCapital},
REG_CAPITAL = #{regCapital},
OPPOSITE_CODE = #{oppositeCode},
OPPOSITE_STATUS = #{oppositeStatus},
IS_SGAT = #{isSgat},
IS_JTN = #{isJtn},
NATURE_OF_ENTERPRISE_ORG = #{natureOfEnterpriseOrg},
NATURE_OF_ENTERPRISE_ORG_LEV = #{natureOfEnterpriseOrgLev},
RELATIVE_TYPE = #{relativeType},
COMMENTS = #{comments},
REMARK = #{remark},
UPDATE_BY = #{updateBy},
UPDATE_TIME = #{updateTime},
sync_status = #{syncStatus},
sync_time = #{syncTime},
sync_message = #{syncMessage},
</trim>
where CUSTOMER_SYSID = #{customerSysid}
and IS_DELETED = 'N'
</update>
<update id="deletePartnerByIds">
update ILDM_FEE_CUSTOMER_INFO
set IS_DELETED = #{isDeleted},
UPDATE_BY = #{updateBy},
UPDATE_TIME = #{updateTime}
where CUSTOMER_SYSID in
<foreach collection="customerSysids" item="customerSysid" open="(" separator="," close=")">
#{customerSysid}
</foreach>
</update>
<select id="selectPartnerContactsByPartnerIds" resultMap="PartnerContactResult">
select id, partner_record_id, contact_name, contact_phone, postal_address,
email, sort_order, del_flag, remark
from SEAL_PARTNER_CONTACT
where del_flag = '0'
and partner_record_id in
<foreach collection="partnerRecordIds" item="partnerRecordId" open="(" separator="," close=")">
#{partnerRecordId}
</foreach>
order by partner_record_id asc, sort_order asc, id asc
</select>
<update id="markPartnerContactsDeletedByPartnerId">
update SEAL_PARTNER_CONTACT
set del_flag = #{delFlag},
update_by = #{updateBy},
update_time = #{updateTime}
where partner_record_id = #{partnerRecordId}
and del_flag = '0'
</update>
<update id="markPartnerContactsDeletedByPartnerIds">
update SEAL_PARTNER_CONTACT
set del_flag = #{delFlag},
update_by = #{updateBy},
update_time = #{updateTime}
where partner_record_id in
<foreach collection="partnerRecordIds" item="partnerRecordId" open="(" separator="," close=")">
#{partnerRecordId}
</foreach>
and del_flag = '0'
</update>
<insert id="batchInsertPartnerContacts">
INSERT ALL
<foreach collection="contacts" item="item">
INTO SEAL_PARTNER_CONTACT
(
id, partner_record_id, contact_name, contact_phone, postal_address, email,
sort_order, del_flag, create_by, create_time, update_by, update_time, remark
)
VALUES
(
#{item.id}, #{item.partnerRecordId}, #{item.contactName}, #{item.contactPhone},
#{item.postalAddress}, #{item.email}, #{item.sortOrder},
#{delFlag}, #{createBy}, #{createTime}, #{updateBy}, #{updateTime}, #{item.remark}
)
</foreach>
SELECT 1 FROM DUAL
</insert>
<update id="updatePartnerSyncInfo">
update ILDM_FEE_CUSTOMER_INFO
set OPPOSITE_ID = #{oppositeId},
sync_status = #{syncStatus},
sync_time = #{syncTime},
sync_message = #{syncMessage},
UPDATE_BY = #{updateBy},
UPDATE_TIME = #{updateTime}
where CUSTOMER_SYSID = #{customerSysid}
and IS_DELETED = 'N'
</update>
</mapper>
Binary file not shown.

After

Width:  |  Height:  |  Size: 548 KiB

+25
View File
@@ -0,0 +1,25 @@
CREATE DATABASE IF NOT EXISTS zgwl_end DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
USE zgwl_end;
DROP TABLE IF EXISTS BIZ_STUDENT;
CREATE TABLE BIZ_STUDENT (
id BIGINT NOT NULL COMMENT '主键ID',
student_no VARCHAR(64) NOT NULL COMMENT '学号',
student_name VARCHAR(64) NOT NULL COMMENT '姓名',
gender VARCHAR(16) NOT NULL COMMENT '性别',
age INT NOT NULL COMMENT '年龄',
phone VARCHAR(32) DEFAULT NULL COMMENT '手机号',
address VARCHAR(255) DEFAULT NULL COMMENT '地址',
create_dt DATETIME DEFAULT NULL COMMENT '创建时间',
update_dt DATETIME DEFAULT NULL COMMENT '更新时间',
create_by VARCHAR(64) DEFAULT NULL COMMENT '创建人',
update_by VARCHAR(64) DEFAULT NULL COMMENT '更新人',
PRIMARY KEY (id),
UNIQUE KEY uk_student_no (student_no)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='学生表';
INSERT INTO BIZ_STUDENT (id, student_no, student_name, gender, age, phone, address, create_dt, update_dt, create_by, update_by) VALUES
(1925800000000000001, 'S2024001', '张三', '', 20, '13800001111', '宁波市鄞州区', NOW(), NOW(), 'system', 'system'),
(1925800000000000002, 'S2024002', '李四', '', 21, '13800002222', '宁波市北仑区', NOW(), NOW(), 'system', 'system'),
(1925800000000000003, 'S2024003', '王五', '', 22, '13800003333', '宁波市镇海区', NOW(), NOW(), 'system', 'system');
+68
View File
@@ -0,0 +1,68 @@
-- =============================================
-- Oracle version for zgwl_end.sql
-- Execute with: sqlplus developer/dev123@localhost/XEPDB1 @zgwl_end_oracle.sql
-- =============================================
WHENEVER SQLERROR EXIT SQL.SQLCODE;
BEGIN
EXECUTE IMMEDIATE 'DROP TABLE BIZ_STUDENT';
EXCEPTION
WHEN OTHERS THEN
IF SQLCODE != -942 THEN
RAISE;
END IF;
END;
/
BEGIN
EXECUTE IMMEDIATE 'DROP SEQUENCE SEQ_BIZ_STUDENT';
EXCEPTION
WHEN OTHERS THEN
IF SQLCODE != -2289 THEN
RAISE;
END IF;
END;
/
CREATE TABLE BIZ_STUDENT (
ID NUMBER(19) NOT NULL,
STUDENT_NO VARCHAR2(64 CHAR) NOT NULL,
STUDENT_NAME VARCHAR2(64 CHAR) NOT NULL,
GENDER VARCHAR2(16 CHAR) NOT NULL,
AGE NUMBER(10) NOT NULL,
PHONE VARCHAR2(32 CHAR),
ADDRESS VARCHAR2(255 CHAR),
CREATE_DT TIMESTAMP(6),
UPDATE_DT TIMESTAMP(6),
CREATE_BY VARCHAR2(64 CHAR),
UPDATE_BY VARCHAR2(64 CHAR),
CONSTRAINT PK_BIZ_STUDENT PRIMARY KEY (ID),
CONSTRAINT UK_BIZ_STUDENT_NO UNIQUE (STUDENT_NO)
);
COMMENT ON TABLE BIZ_STUDENT IS '学生表';
COMMENT ON COLUMN BIZ_STUDENT.ID IS '主键ID';
COMMENT ON COLUMN BIZ_STUDENT.STUDENT_NO IS '学号';
COMMENT ON COLUMN BIZ_STUDENT.STUDENT_NAME IS '姓名';
COMMENT ON COLUMN BIZ_STUDENT.GENDER IS '性别';
COMMENT ON COLUMN BIZ_STUDENT.AGE IS '年龄';
COMMENT ON COLUMN BIZ_STUDENT.PHONE IS '手机号';
COMMENT ON COLUMN BIZ_STUDENT.ADDRESS IS '地址';
COMMENT ON COLUMN BIZ_STUDENT.CREATE_DT IS '创建时间';
COMMENT ON COLUMN BIZ_STUDENT.UPDATE_DT IS '更新时间';
COMMENT ON COLUMN BIZ_STUDENT.CREATE_BY IS '创建人';
COMMENT ON COLUMN BIZ_STUDENT.UPDATE_BY IS '更新人';
INSERT INTO BIZ_STUDENT (ID, STUDENT_NO, STUDENT_NAME, GENDER, AGE, PHONE, ADDRESS, CREATE_DT, UPDATE_DT, CREATE_BY, UPDATE_BY) VALUES
(1925800000000000001, 'S2024001', '张三', '', 20, '13800001111', '宁波市鄞州区', SYSTIMESTAMP, SYSTIMESTAMP, 'system', 'system');
INSERT INTO BIZ_STUDENT (ID, STUDENT_NO, STUDENT_NAME, GENDER, AGE, PHONE, ADDRESS, CREATE_DT, UPDATE_DT, CREATE_BY, UPDATE_BY) VALUES
(1925800000000000002, 'S2024002', '李四', '', 21, '13800002222', '宁波市北仑区', SYSTIMESTAMP, SYSTIMESTAMP, 'system', 'system');
INSERT INTO BIZ_STUDENT (ID, STUDENT_NO, STUDENT_NAME, GENDER, AGE, PHONE, ADDRESS, CREATE_DT, UPDATE_DT, CREATE_BY, UPDATE_BY) VALUES
(1925800000000000003, 'S2024003', '王五', '', 22, '13800003333', '宁波市镇海区', SYSTIMESTAMP, SYSTIMESTAMP, 'system', 'system');
CREATE SEQUENCE SEQ_BIZ_STUDENT
START WITH 1925800000000000004
INCREMENT BY 1
NOCACHE
NOCYCLE;
+37
View File
@@ -0,0 +1,37 @@
-- =============================================
-- 合同模板管理模块数据表
-- 执行方式:在 MySQL 中直接执行本文件
-- =============================================
USE zgwl_end;
DROP TABLE IF EXISTS `contract_template_manage`;
CREATE TABLE `contract_template_manage` (
`id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`template_code` VARCHAR(64) NOT NULL COMMENT '范本编号,后端自动生成',
`template_name` VARCHAR(200) NOT NULL COMMENT '范本名称',
`source` VARCHAR(32) NOT NULL COMMENT '范本类型:标准模板/推荐文本/示范文本/对方文本',
`contract_type` VARCHAR(64) NOT NULL COMMENT '合同类型',
`self_party_role` VARCHAR(16) NOT NULL COMMENT '我方地位:甲方/乙方/丙方',
`version` VARCHAR(32) NOT NULL COMMENT '版本号',
`status` VARCHAR(20) NOT NULL DEFAULT 'draft' COMMENT '状态:draft/published/disabled',
`variable_count` INT NOT NULL DEFAULT 0 COMMENT '变量数量',
`description` VARCHAR(1000) DEFAULT NULL COMMENT '范本描述',
`usage_tip` VARCHAR(1000) DEFAULT NULL COMMENT '使用提示',
`template_file_name` VARCHAR(255) NOT NULL COMMENT '模板文件名称',
`template_file_path` VARCHAR(500) NOT NULL COMMENT '模板文件相对路径',
`publish_org_ids_json` TEXT DEFAULT NULL COMMENT '发布范围 orgId JSON 数组',
`publish_org_names_json` TEXT DEFAULT NULL COMMENT '发布范围 orgName JSON 数组',
`variable_mappings_json` LONGTEXT DEFAULT NULL COMMENT '变量映射 JSON 数组',
`create_by` VARCHAR(64) DEFAULT NULL COMMENT '创建人',
`created_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updated_by` VARCHAR(64) DEFAULT NULL COMMENT '更新人',
`updated_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`del_flag` CHAR(1) NOT NULL DEFAULT '0' COMMENT '删除标记:0存在 2删除',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_template_code` (`template_code`),
KEY `idx_template_name` (`template_name`),
KEY `idx_contract_type` (`contract_type`),
KEY `idx_status` (`status`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='合同模板管理表';
+85
View File
@@ -0,0 +1,85 @@
-- =============================================
-- Oracle version for zgwl_manifest_tables.sql
-- Execute with: sqlplus developer/dev123@localhost/XEPDB1 @zgwl_manifest_tables_oracle.sql
-- =============================================
WHENEVER SQLERROR EXIT SQL.SQLCODE;
BEGIN
EXECUTE IMMEDIATE 'DROP TABLE CONTRACT_TEMPLATE_MANAGE';
EXCEPTION
WHEN OTHERS THEN
IF SQLCODE != -942 THEN
RAISE;
END IF;
END;
/
BEGIN
EXECUTE IMMEDIATE 'DROP SEQUENCE SEQ_CONTRACT_TEMPLATE_MANAGE';
EXCEPTION
WHEN OTHERS THEN
IF SQLCODE != -2289 THEN
RAISE;
END IF;
END;
/
CREATE TABLE CONTRACT_TEMPLATE_MANAGE (
ID NUMBER(19) NOT NULL,
TEMPLATE_CODE VARCHAR2(64 CHAR) NOT NULL,
TEMPLATE_NAME VARCHAR2(200 CHAR) NOT NULL,
SOURCE VARCHAR2(32 CHAR) NOT NULL,
CONTRACT_TYPE VARCHAR2(64 CHAR) NOT NULL,
SELF_PARTY_ROLE VARCHAR2(16 CHAR) NOT NULL,
VERSION VARCHAR2(32 CHAR) NOT NULL,
STATUS VARCHAR2(20 CHAR) DEFAULT 'draft' NOT NULL,
VARIABLE_COUNT NUMBER(10) DEFAULT 0 NOT NULL,
DESCRIPTION VARCHAR2(1000 CHAR),
USAGE_TIP VARCHAR2(1000 CHAR),
TEMPLATE_FILE_NAME VARCHAR2(255 CHAR) NOT NULL,
TEMPLATE_FILE_PATH VARCHAR2(500 CHAR) NOT NULL,
PUBLISH_ORG_IDS_JSON CLOB,
PUBLISH_ORG_NAMES_JSON CLOB,
VARIABLE_MAPPINGS_JSON CLOB,
CREATE_BY VARCHAR2(64 CHAR),
CREATED_TIME TIMESTAMP(6) DEFAULT CURRENT_TIMESTAMP,
UPDATED_BY VARCHAR2(64 CHAR),
UPDATED_TIME TIMESTAMP(6) DEFAULT CURRENT_TIMESTAMP,
DEL_FLAG CHAR(1 CHAR) DEFAULT '0' NOT NULL,
CONSTRAINT PK_CONTRACT_TEMPLATE_MANAGE PRIMARY KEY (ID),
CONSTRAINT UK_TEMPLATE_CODE UNIQUE (TEMPLATE_CODE)
);
CREATE INDEX IDX_TEMPLATE_NAME ON CONTRACT_TEMPLATE_MANAGE (TEMPLATE_NAME);
CREATE INDEX IDX_CONTRACT_TYPE ON CONTRACT_TEMPLATE_MANAGE (CONTRACT_TYPE);
CREATE INDEX IDX_STATUS ON CONTRACT_TEMPLATE_MANAGE (STATUS);
COMMENT ON TABLE CONTRACT_TEMPLATE_MANAGE IS '合同模板管理表';
COMMENT ON COLUMN CONTRACT_TEMPLATE_MANAGE.ID IS '主键ID';
COMMENT ON COLUMN CONTRACT_TEMPLATE_MANAGE.TEMPLATE_CODE IS '范本编号,后端自动生成';
COMMENT ON COLUMN CONTRACT_TEMPLATE_MANAGE.TEMPLATE_NAME IS '范本名称';
COMMENT ON COLUMN CONTRACT_TEMPLATE_MANAGE.SOURCE IS '范本类型:标准模板/推荐文本/示范文本/对方文本';
COMMENT ON COLUMN CONTRACT_TEMPLATE_MANAGE.CONTRACT_TYPE IS '合同类型';
COMMENT ON COLUMN CONTRACT_TEMPLATE_MANAGE.SELF_PARTY_ROLE IS '我方地位:甲方/乙方/丙方';
COMMENT ON COLUMN CONTRACT_TEMPLATE_MANAGE.VERSION IS '版本号';
COMMENT ON COLUMN CONTRACT_TEMPLATE_MANAGE.STATUS IS '状态:draft/published/disabled';
COMMENT ON COLUMN CONTRACT_TEMPLATE_MANAGE.VARIABLE_COUNT IS '变量数量';
COMMENT ON COLUMN CONTRACT_TEMPLATE_MANAGE.DESCRIPTION IS '范本描述';
COMMENT ON COLUMN CONTRACT_TEMPLATE_MANAGE.USAGE_TIP IS '使用提示';
COMMENT ON COLUMN CONTRACT_TEMPLATE_MANAGE.TEMPLATE_FILE_NAME IS '模板文件名称';
COMMENT ON COLUMN CONTRACT_TEMPLATE_MANAGE.TEMPLATE_FILE_PATH IS '模板文件相对路径';
COMMENT ON COLUMN CONTRACT_TEMPLATE_MANAGE.PUBLISH_ORG_IDS_JSON IS '发布范围 orgId JSON 数组';
COMMENT ON COLUMN CONTRACT_TEMPLATE_MANAGE.PUBLISH_ORG_NAMES_JSON IS '发布范围 orgName JSON 数组';
COMMENT ON COLUMN CONTRACT_TEMPLATE_MANAGE.VARIABLE_MAPPINGS_JSON IS '变量映射 JSON 数组';
COMMENT ON COLUMN CONTRACT_TEMPLATE_MANAGE.CREATE_BY IS '创建人';
COMMENT ON COLUMN CONTRACT_TEMPLATE_MANAGE.CREATED_TIME IS '创建时间';
COMMENT ON COLUMN CONTRACT_TEMPLATE_MANAGE.UPDATED_BY IS '更新人';
COMMENT ON COLUMN CONTRACT_TEMPLATE_MANAGE.UPDATED_TIME IS '更新时间';
COMMENT ON COLUMN CONTRACT_TEMPLATE_MANAGE.DEL_FLAG IS '删除标记:0存在 2删除';
CREATE SEQUENCE SEQ_CONTRACT_TEMPLATE_MANAGE
START WITH 1
INCREMENT BY 1
NOCACHE
NOCYCLE;
+137
View File
@@ -0,0 +1,137 @@
-- =============================================
-- 相对方管理模块数据表
-- 执行方式:在 MySQL 中直接执行本文件
-- =============================================
USE zgwl_end;
DROP TABLE IF EXISTS `seal_partner_contact`;
DROP TABLE IF EXISTS `ILDM_FEE_CUSTOMER_INFO`;
CREATE TABLE `ILDM_FEE_CUSTOMER_INFO` (
`CUSTOMER_SYSID` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '客户系统ID',
`CUSTOMER_REGNO` VARCHAR(64) DEFAULT NULL COMMENT '客户注册号',
`CUSTOMER_NO` VARCHAR(64) DEFAULT NULL COMMENT '客户编号',
`CUSTOMER_NAME_C` VARCHAR(200) DEFAULT NULL COMMENT '公司全称',
`CUSTOMER_NAME_EN` VARCHAR(200) DEFAULT NULL COMMENT '公司英文名称',
`CUSTOMER_SHORT_NAME` VARCHAR(100) DEFAULT NULL COMMENT '公司简称',
`CUSTOMER_SHORT_NAME_EN` VARCHAR(100) DEFAULT NULL COMMENT '公司英文简称',
`CUSTOMER_TAXNO` VARCHAR(50) DEFAULT NULL COMMENT '统一社会信用代码',
`CUSTOMER_ADDRESS` VARCHAR(200) DEFAULT NULL COMMENT '详细地址',
`OFFICEPHONE` VARCHAR(20) DEFAULT NULL COMMENT '联系电话',
`CUSTOMER_BANK` VARCHAR(100) DEFAULT NULL COMMENT '开户银行',
`CUSTOMER_BANKACCOUNT` VARCHAR(50) DEFAULT NULL COMMENT '银行账户',
`CUSTOMER_CHARACTER` VARCHAR(50) DEFAULT NULL COMMENT '客户性质',
`CUSTOMER_REGADDR` VARCHAR(200) DEFAULT NULL COMMENT '注册地址',
`CUSTOMER_REGDATE` DATE DEFAULT NULL COMMENT '注册日期',
`CUSTOMER_NATURE` VARCHAR(50) DEFAULT NULL COMMENT '客户性质分类',
`CUSTOMER_ZIP` VARCHAR(20) DEFAULT NULL COMMENT '邮编',
`CUSTOMER_1_TYPEFLAG` VARCHAR(10) DEFAULT NULL COMMENT '客户类型标识1',
`CUSTOMER_2_TYPEFLAG` VARCHAR(10) DEFAULT NULL COMMENT '客户类型标识2',
`CUSTOMER_3_TYPEFLAG` VARCHAR(10) DEFAULT NULL COMMENT '客户类型标识3',
`CUSTOMER_4_TYPEFLAG` VARCHAR(10) DEFAULT NULL COMMENT '客户类型标识4',
`PLACE_TYPE` VARCHAR(10) DEFAULT NULL COMMENT '境内外',
`ACTIVEFLAG` VARCHAR(10) DEFAULT NULL COMMENT '启用标识',
`CUSTOMER_SYSID_HT` VARCHAR(64) DEFAULT NULL COMMENT '海铁系统客户ID',
`DOWN_DATE` DATE DEFAULT NULL COMMENT '下载日期',
`OPEN_FLAG` VARCHAR(10) DEFAULT NULL COMMENT '开户标识',
`CREATE_BY` VARCHAR(64) DEFAULT NULL COMMENT '创建人',
`CREATE_TIME` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`UPDATE_BY` VARCHAR(64) DEFAULT NULL COMMENT '更新人',
`UPDATE_TIME` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`SYSTEM_TYPE` VARCHAR(20) DEFAULT NULL COMMENT '业务系统类型(ht:海铁联运系统;xg:箱管系统;qc:全程物流)',
`REMARK` VARCHAR(500) DEFAULT NULL COMMENT '备注',
`ORG_ID` VARCHAR(64) DEFAULT NULL COMMENT '组织体系对应的公司编码',
`EMAIL_ADDRESS` VARCHAR(100) DEFAULT NULL COMMENT '邮箱地址',
`OPPOSITE_ID` VARCHAR(64) DEFAULT NULL COMMENT '法务系统相对方ID(唯一标识)',
`OPPOSITE_CHARACTER` VARCHAR(10) DEFAULT NULL COMMENT '相对方性质分类(1:境内组织,2:境内个人,3:境外组织,4:境外个人)',
`GAT_FLAG` VARCHAR(2) DEFAULT NULL COMMENT '是否涉港澳台标识(Y:是,N:否,法务业务分类用)',
`GROUP_FLAG` VARCHAR(2) DEFAULT NULL COMMENT '是否集团内(Y:是,N:否,法务业务分类用)',
`COUNTRY` VARCHAR(20) DEFAULT NULL COMMENT '国家编码',
`COUNTRY_NAME` VARCHAR(50) DEFAULT NULL COMMENT '国家名称',
`CITY` VARCHAR(50) DEFAULT NULL COMMENT '城市',
`POSTCODE` VARCHAR(20) DEFAULT NULL COMMENT '邮编',
`LEGAL_PERSON_ID` VARCHAR(64) DEFAULT NULL COMMENT '法定代表人或负责人ID',
`LEGAL_PERSON_NAME` VARCHAR(50) DEFAULT NULL COMMENT '法定代表人或负责人姓名',
`LEGAL_PERSON_ACCOUNT` VARCHAR(64) DEFAULT NULL COMMENT '法定代表人或负责人账户',
`LEGAL_PERSON_CARD_ID` VARCHAR(100) DEFAULT NULL COMMENT '法定代表人身份证号(加密存储)',
`WEBSITE` VARCHAR(200) DEFAULT NULL COMMENT '网站地址',
`CERTIFICATE_TYPE` VARCHAR(20) DEFAULT NULL COMMENT '自然人证件类型(idCard:身份证,passport:护照,other:其他)',
`CERTIFICATE_NUM` VARCHAR(100) DEFAULT NULL COMMENT '自然人证件号码(加密存储)',
`TIN_CODE` VARCHAR(50) DEFAULT NULL COMMENT 'TIN码(税务识别号)',
`ABROAD_CODE` VARCHAR(50) DEFAULT NULL COMMENT '境外公司活动证明编码',
`REGISTERED_CAPITAL` DECIMAL(20,2) DEFAULT NULL COMMENT '注册资金(单位:元)',
`REG_CAPITAL` VARCHAR(100) DEFAULT NULL COMMENT '注册资本(文本描述,如:1000万人民币)',
`BUS_SCOPE` TEXT COMMENT '经营范围',
`OPPOSITE_CODE` VARCHAR(64) DEFAULT NULL COMMENT '相对方编码(业务分类编码)',
`OPPOSITE_STATUS` VARCHAR(20) DEFAULT 'EFFECTUAL' COMMENT '状态(EFFECTUAL:有效,INVALID:无效)',
`IS_SGAT` VARCHAR(2) DEFAULT '0' COMMENT '是否SGAT1:是,0:否)',
`IS_JTN` VARCHAR(2) DEFAULT '0' COMMENT '是否JTN1:是,0:否)',
`NATURE_OF_ENTERPRISE_ORG` VARCHAR(10) DEFAULT NULL COMMENT '组织性质(ZY:政府,QY:企业,QT:其他组织)',
`NATURE_OF_ENTERPRISE_ORG_LEV` VARCHAR(20) DEFAULT NULL COMMENT '组织性质子级(GNQY:国有企业,MYQY:民营企业,WZQY:外资企业,HZQY:合资企业,GYQZHKGQY:国有全资和控股企业,QT:其他)',
`RELATIVE_TYPE` VARCHAR(30) DEFAULT NULL COMMENT '类型(client:客户,clientAndSup:客户及供应商,other:其他,Partner:合作伙伴,supplier:供应商)',
`IS_DELETED` VARCHAR(2) DEFAULT 'N' COMMENT '法务系统删除标记(Y:是,N:否,逻辑删除)',
`COMMENTS` VARCHAR(500) DEFAULT NULL COMMENT '备注【法务】',
`sync_status` VARCHAR(20) NOT NULL DEFAULT 'unsynced' COMMENT '同步状态:unsynced/synced/failed',
`sync_time` DATETIME DEFAULT NULL COMMENT '最后同步时间',
`sync_message` VARCHAR(500) DEFAULT NULL COMMENT '同步说明',
PRIMARY KEY (`CUSTOMER_SYSID`),
KEY `idx_customer_no` (`CUSTOMER_NO`),
KEY `idx_opposite_id` (`OPPOSITE_ID`),
KEY `idx_customer_taxno` (`CUSTOMER_TAXNO`),
KEY `idx_system_type` (`SYSTEM_TYPE`),
KEY `idx_create_time` (`CREATE_TIME`),
KEY `idx_sync_status` (`sync_status`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='客户/相对方信息表';
CREATE TABLE `seal_partner_contact` (
`id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`partner_record_id` BIGINT(20) NOT NULL COMMENT '关联相对方主表 CUSTOMER_SYSID',
`contact_name` VARCHAR(50) NOT NULL COMMENT '联系人姓名',
`contact_phone` VARCHAR(20) NOT NULL COMMENT '联系电话',
`postal_address` VARCHAR(100) NOT NULL COMMENT '通讯地址',
`email` VARCHAR(50) NOT NULL COMMENT '电子邮箱',
`sort_order` INT(11) NOT NULL DEFAULT 1 COMMENT '排序号',
`del_flag` CHAR(1) NOT NULL DEFAULT '0' COMMENT '删除标记:0存在 2删除',
`create_by` VARCHAR(64) DEFAULT NULL COMMENT '创建人',
`create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_by` VARCHAR(64) DEFAULT NULL COMMENT '更新人',
`update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`remark` VARCHAR(500) DEFAULT NULL COMMENT '备注',
PRIMARY KEY (`id`),
KEY `idx_partner_record_id` (`partner_record_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='相对方联系人表';
INSERT INTO `ILDM_FEE_CUSTOMER_INFO`
(`CUSTOMER_SYSID`, `CUSTOMER_NAME_C`, `CUSTOMER_TAXNO`, `CUSTOMER_REGADDR`, `CREATE_BY`, `CREATE_TIME`, `UPDATE_BY`, `UPDATE_TIME`,
`EMAIL_ADDRESS`, `OPPOSITE_ID`, `OPPOSITE_CHARACTER`, `GAT_FLAG`, `GROUP_FLAG`, `COUNTRY`, `COUNTRY_NAME`,
`LEGAL_PERSON_NAME`, `REG_CAPITAL`, `OPPOSITE_CODE`, `OPPOSITE_STATUS`, `IS_SGAT`, `IS_JTN`,
`NATURE_OF_ENTERPRISE_ORG`, `NATURE_OF_ENTERPRISE_ORG_LEV`, `RELATIVE_TYPE`, `IS_DELETED`,
`COMMENTS`, `sync_status`, `sync_time`, `sync_message`)
VALUES
(1, '宁波港船务货运代理有限公司', '91330201MA2ABCDE9X', '宁波市北仑区明州西路188号', 'local_test', '2026-05-20 09:12:33', 'local_test', '2026-05-20 09:15:10',
'zhoujingli@example.com', 'JT-10256', '1', 'N', 'N', NULL, NULL,
'李四', '5000万元', 'OPP-202605210001', 'EFFECTUAL', '0', '0',
'QY', 'GNQY', 'supplier', 'N',
'本地初始化测试数据', 'synced', '2026-05-20 09:15:10', '已按数据采集接口规范同步到法务系统'),
(2, '王小明', NULL, NULL, 'local_test', '2026-05-20 14:20:05', 'local_test', '2026-05-20 14:20:05',
NULL, NULL, '2', 'N', 'N', NULL, NULL,
NULL, NULL, 'OPP-202605210002', 'EFFECTUAL', '0', '0',
'QT', NULL, 'client', 'N',
'个人类型测试数据', 'unsynced', NULL, '新增后尚未同步'),
(3, 'Ocean Energy Pte. Ltd.', NULL, '88 Harbour Street, London', 'local_test', '2026-05-18 11:08:22', 'local_test', '2026-05-18 11:10:40',
'alice.brown@ocean-energy.com', 'JT-10888', '3', 'N', 'N', 'GJ826', '英国',
NULL, 'USD 2,000,000', 'OPP-202605210003', 'EFFECTUAL', '0', '0',
'QY', 'WZQY', 'Partner', 'N',
'境外组织测试数据', 'failed', '2026-05-18 11:10:40', '法务系统返回:注册地址长度超限,请修正后重试');
INSERT INTO `seal_partner_contact`
(`id`, `partner_record_id`, `contact_name`, `contact_phone`, `postal_address`, `email`,
`sort_order`, `del_flag`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`)
VALUES
(1, 1, '周经理', '15757460062', '宁波市北仑区明州西路188号A座', 'zhoujingli@example.com',
1, '0', 'local_test', '2026-05-20 09:12:33', 'local_test', '2026-05-20 09:12:33', '主联系人'),
(2, 3, 'Alice Brown', '+44-20-8899-0001', '88 Harbour Street, London', 'alice.brown@ocean-energy.com',
1, '0', 'local_test', '2026-05-18 11:08:22', 'local_test', '2026-05-18 11:08:22', '海外主联系人'),
(3, 3, 'Tom Lee', '+44-20-8899-0002', '88 Harbour Street, London', 'tom.lee@ocean-energy.com',
2, '0', 'local_test', '2026-05-18 11:08:22', 'local_test', '2026-05-18 11:08:22', '备用联系人');
+264
View File
@@ -0,0 +1,264 @@
-- =============================================
-- Oracle version for zgwl_partner_tables.sql
-- Execute with: sqlplus developer/dev123@localhost/XEPDB1 @zgwl_partner_tables_oracle.sql
-- =============================================
WHENEVER SQLERROR EXIT SQL.SQLCODE;
BEGIN
EXECUTE IMMEDIATE 'DROP TABLE SEAL_PARTNER_CONTACT';
EXCEPTION
WHEN OTHERS THEN
IF SQLCODE != -942 THEN
RAISE;
END IF;
END;
/
BEGIN
EXECUTE IMMEDIATE 'DROP TABLE ILDM_FEE_CUSTOMER_INFO';
EXCEPTION
WHEN OTHERS THEN
IF SQLCODE != -942 THEN
RAISE;
END IF;
END;
/
BEGIN
EXECUTE IMMEDIATE 'DROP SEQUENCE SEQ_SEAL_PARTNER_CONTACT';
EXCEPTION
WHEN OTHERS THEN
IF SQLCODE != -2289 THEN
RAISE;
END IF;
END;
/
BEGIN
EXECUTE IMMEDIATE 'DROP SEQUENCE SEQ_ILDM_FEE_CUSTOMER_INFO';
EXCEPTION
WHEN OTHERS THEN
IF SQLCODE != -2289 THEN
RAISE;
END IF;
END;
/
CREATE TABLE ILDM_FEE_CUSTOMER_INFO (
CUSTOMER_SYSID NUMBER(19) NOT NULL,
CUSTOMER_REGNO VARCHAR2(64 CHAR),
CUSTOMER_NO VARCHAR2(64 CHAR),
CUSTOMER_NAME_C VARCHAR2(200 CHAR),
CUSTOMER_NAME_EN VARCHAR2(200 CHAR),
CUSTOMER_SHORT_NAME VARCHAR2(100 CHAR),
CUSTOMER_SHORT_NAME_EN VARCHAR2(100 CHAR),
CUSTOMER_TAXNO VARCHAR2(50 CHAR),
CUSTOMER_ADDRESS VARCHAR2(200 CHAR),
OFFICEPHONE VARCHAR2(20 CHAR),
CUSTOMER_BANK VARCHAR2(100 CHAR),
CUSTOMER_BANKACCOUNT VARCHAR2(50 CHAR),
CUSTOMER_CHARACTER VARCHAR2(50 CHAR),
CUSTOMER_REGADDR VARCHAR2(200 CHAR),
CUSTOMER_REGDATE DATE,
CUSTOMER_NATURE VARCHAR2(50 CHAR),
CUSTOMER_ZIP VARCHAR2(20 CHAR),
CUSTOMER_1_TYPEFLAG VARCHAR2(10 CHAR),
CUSTOMER_2_TYPEFLAG VARCHAR2(10 CHAR),
CUSTOMER_3_TYPEFLAG VARCHAR2(10 CHAR),
CUSTOMER_4_TYPEFLAG VARCHAR2(10 CHAR),
PLACE_TYPE VARCHAR2(10 CHAR),
ACTIVEFLAG VARCHAR2(10 CHAR),
CUSTOMER_SYSID_HT VARCHAR2(64 CHAR),
DOWN_DATE DATE,
OPEN_FLAG VARCHAR2(10 CHAR),
CREATE_BY VARCHAR2(64 CHAR),
CREATE_TIME TIMESTAMP(6) DEFAULT CURRENT_TIMESTAMP,
UPDATE_BY VARCHAR2(64 CHAR),
UPDATE_TIME TIMESTAMP(6) DEFAULT CURRENT_TIMESTAMP,
SYSTEM_TYPE VARCHAR2(20 CHAR),
REMARK VARCHAR2(500 CHAR),
ORG_ID VARCHAR2(64 CHAR),
EMAIL_ADDRESS VARCHAR2(100 CHAR),
OPPOSITE_ID VARCHAR2(64 CHAR),
OPPOSITE_CHARACTER VARCHAR2(10 CHAR),
GAT_FLAG VARCHAR2(2 CHAR),
GROUP_FLAG VARCHAR2(2 CHAR),
COUNTRY VARCHAR2(20 CHAR),
COUNTRY_NAME VARCHAR2(50 CHAR),
CITY VARCHAR2(50 CHAR),
POSTCODE VARCHAR2(20 CHAR),
LEGAL_PERSON_ID VARCHAR2(64 CHAR),
LEGAL_PERSON_NAME VARCHAR2(50 CHAR),
LEGAL_PERSON_ACCOUNT VARCHAR2(64 CHAR),
LEGAL_PERSON_CARD_ID VARCHAR2(100 CHAR),
WEBSITE VARCHAR2(200 CHAR),
CERTIFICATE_TYPE VARCHAR2(20 CHAR),
CERTIFICATE_NUM VARCHAR2(100 CHAR),
TIN_CODE VARCHAR2(50 CHAR),
ABROAD_CODE VARCHAR2(50 CHAR),
REGISTERED_CAPITAL NUMBER(20, 2),
REG_CAPITAL VARCHAR2(100 CHAR),
BUS_SCOPE CLOB,
OPPOSITE_CODE VARCHAR2(64 CHAR),
OPPOSITE_STATUS VARCHAR2(20 CHAR) DEFAULT 'EFFECTUAL',
IS_SGAT VARCHAR2(2 CHAR) DEFAULT '0',
IS_JTN VARCHAR2(2 CHAR) DEFAULT '0',
NATURE_OF_ENTERPRISE_ORG VARCHAR2(10 CHAR),
NATURE_OF_ENTERPRISE_ORG_LEV VARCHAR2(20 CHAR),
RELATIVE_TYPE VARCHAR2(30 CHAR),
IS_DELETED VARCHAR2(2 CHAR) DEFAULT 'N',
COMMENTS VARCHAR2(500 CHAR),
SYNC_STATUS VARCHAR2(20 CHAR) DEFAULT 'unsynced' NOT NULL,
SYNC_TIME TIMESTAMP(6),
SYNC_MESSAGE VARCHAR2(500 CHAR),
CONSTRAINT PK_ILDM_FEE_CUSTOMER_INFO PRIMARY KEY (CUSTOMER_SYSID)
);
CREATE INDEX IDX_CUSTOMER_NO ON ILDM_FEE_CUSTOMER_INFO (CUSTOMER_NO);
CREATE INDEX IDX_OPPOSITE_ID ON ILDM_FEE_CUSTOMER_INFO (OPPOSITE_ID);
CREATE INDEX IDX_CUSTOMER_TAXNO ON ILDM_FEE_CUSTOMER_INFO (CUSTOMER_TAXNO);
CREATE INDEX IDX_SYSTEM_TYPE ON ILDM_FEE_CUSTOMER_INFO (SYSTEM_TYPE);
CREATE INDEX IDX_CREATE_TIME ON ILDM_FEE_CUSTOMER_INFO (CREATE_TIME);
CREATE INDEX IDX_SYNC_STATUS ON ILDM_FEE_CUSTOMER_INFO (SYNC_STATUS);
COMMENT ON TABLE ILDM_FEE_CUSTOMER_INFO IS '客户/相对方信息表';
COMMENT ON COLUMN ILDM_FEE_CUSTOMER_INFO.CUSTOMER_SYSID IS '客户系统ID';
COMMENT ON COLUMN ILDM_FEE_CUSTOMER_INFO.CUSTOMER_NAME_C IS '公司全称';
COMMENT ON COLUMN ILDM_FEE_CUSTOMER_INFO.CUSTOMER_TAXNO IS '统一社会信用代码';
COMMENT ON COLUMN ILDM_FEE_CUSTOMER_INFO.CUSTOMER_REGADDR IS '注册地址';
COMMENT ON COLUMN ILDM_FEE_CUSTOMER_INFO.EMAIL_ADDRESS IS '邮箱地址';
COMMENT ON COLUMN ILDM_FEE_CUSTOMER_INFO.OPPOSITE_ID IS '法务系统相对方ID(唯一标识)';
COMMENT ON COLUMN ILDM_FEE_CUSTOMER_INFO.OPPOSITE_CHARACTER IS '相对方性质分类';
COMMENT ON COLUMN ILDM_FEE_CUSTOMER_INFO.GAT_FLAG IS '是否涉港澳台标识';
COMMENT ON COLUMN ILDM_FEE_CUSTOMER_INFO.GROUP_FLAG IS '是否集团内';
COMMENT ON COLUMN ILDM_FEE_CUSTOMER_INFO.COUNTRY IS '国家编码';
COMMENT ON COLUMN ILDM_FEE_CUSTOMER_INFO.COUNTRY_NAME IS '国家名称';
COMMENT ON COLUMN ILDM_FEE_CUSTOMER_INFO.LEGAL_PERSON_NAME IS '法定代表人或负责人姓名';
COMMENT ON COLUMN ILDM_FEE_CUSTOMER_INFO.CERTIFICATE_TYPE IS '自然人证件类型';
COMMENT ON COLUMN ILDM_FEE_CUSTOMER_INFO.CERTIFICATE_NUM IS '自然人证件号码';
COMMENT ON COLUMN ILDM_FEE_CUSTOMER_INFO.TIN_CODE IS 'TIN码(税务识别号)';
COMMENT ON COLUMN ILDM_FEE_CUSTOMER_INFO.REGISTERED_CAPITAL IS '注册资金(单位:元)';
COMMENT ON COLUMN ILDM_FEE_CUSTOMER_INFO.REG_CAPITAL IS '注册资本';
COMMENT ON COLUMN ILDM_FEE_CUSTOMER_INFO.OPPOSITE_CODE IS '相对方编码';
COMMENT ON COLUMN ILDM_FEE_CUSTOMER_INFO.OPPOSITE_STATUS IS '状态(EFFECTUAL:有效,INVALID:无效)';
COMMENT ON COLUMN ILDM_FEE_CUSTOMER_INFO.IS_SGAT IS '是否SGAT1:是,0:否)';
COMMENT ON COLUMN ILDM_FEE_CUSTOMER_INFO.IS_JTN IS '是否JTN1:是,0:否)';
COMMENT ON COLUMN ILDM_FEE_CUSTOMER_INFO.NATURE_OF_ENTERPRISE_ORG IS '组织性质';
COMMENT ON COLUMN ILDM_FEE_CUSTOMER_INFO.NATURE_OF_ENTERPRISE_ORG_LEV IS '组织性质子级';
COMMENT ON COLUMN ILDM_FEE_CUSTOMER_INFO.RELATIVE_TYPE IS '类型';
COMMENT ON COLUMN ILDM_FEE_CUSTOMER_INFO.IS_DELETED IS '法务系统删除标记';
COMMENT ON COLUMN ILDM_FEE_CUSTOMER_INFO.COMMENTS IS '备注【法务】';
COMMENT ON COLUMN ILDM_FEE_CUSTOMER_INFO.SYNC_STATUS IS '同步状态';
COMMENT ON COLUMN ILDM_FEE_CUSTOMER_INFO.SYNC_TIME IS '最后同步时间';
COMMENT ON COLUMN ILDM_FEE_CUSTOMER_INFO.SYNC_MESSAGE IS '同步说明';
CREATE TABLE SEAL_PARTNER_CONTACT (
ID NUMBER(19) NOT NULL,
PARTNER_RECORD_ID NUMBER(19) NOT NULL,
CONTACT_NAME VARCHAR2(50 CHAR) NOT NULL,
CONTACT_PHONE VARCHAR2(20 CHAR) NOT NULL,
POSTAL_ADDRESS VARCHAR2(100 CHAR) NOT NULL,
EMAIL VARCHAR2(50 CHAR) NOT NULL,
SORT_ORDER NUMBER(10) DEFAULT 1 NOT NULL,
DEL_FLAG CHAR(1 CHAR) DEFAULT '0' NOT NULL,
CREATE_BY VARCHAR2(64 CHAR),
CREATE_TIME TIMESTAMP(6) DEFAULT CURRENT_TIMESTAMP,
UPDATE_BY VARCHAR2(64 CHAR),
UPDATE_TIME TIMESTAMP(6) DEFAULT CURRENT_TIMESTAMP,
REMARK VARCHAR2(500 CHAR),
CONSTRAINT PK_SEAL_PARTNER_CONTACT PRIMARY KEY (ID)
);
CREATE INDEX IDX_PARTNER_RECORD_ID ON SEAL_PARTNER_CONTACT (PARTNER_RECORD_ID);
COMMENT ON TABLE SEAL_PARTNER_CONTACT IS '相对方联系人表';
COMMENT ON COLUMN SEAL_PARTNER_CONTACT.ID IS '主键ID';
COMMENT ON COLUMN SEAL_PARTNER_CONTACT.PARTNER_RECORD_ID IS '关联相对方主表 CUSTOMER_SYSID';
COMMENT ON COLUMN SEAL_PARTNER_CONTACT.CONTACT_NAME IS '联系人姓名';
COMMENT ON COLUMN SEAL_PARTNER_CONTACT.CONTACT_PHONE IS '联系电话';
COMMENT ON COLUMN SEAL_PARTNER_CONTACT.POSTAL_ADDRESS IS '通讯地址';
COMMENT ON COLUMN SEAL_PARTNER_CONTACT.EMAIL IS '电子邮箱';
COMMENT ON COLUMN SEAL_PARTNER_CONTACT.SORT_ORDER IS '排序号';
COMMENT ON COLUMN SEAL_PARTNER_CONTACT.DEL_FLAG IS '删除标记:0存在 2删除';
INSERT INTO ILDM_FEE_CUSTOMER_INFO
(CUSTOMER_SYSID, CUSTOMER_NAME_C, CUSTOMER_TAXNO, CUSTOMER_REGADDR, CREATE_BY, CREATE_TIME, UPDATE_BY, UPDATE_TIME,
EMAIL_ADDRESS, OPPOSITE_ID, OPPOSITE_CHARACTER, GAT_FLAG, GROUP_FLAG, COUNTRY, COUNTRY_NAME,
LEGAL_PERSON_NAME, REG_CAPITAL, OPPOSITE_CODE, OPPOSITE_STATUS, IS_SGAT, IS_JTN,
NATURE_OF_ENTERPRISE_ORG, NATURE_OF_ENTERPRISE_ORG_LEV, RELATIVE_TYPE, IS_DELETED,
COMMENTS, SYNC_STATUS, SYNC_TIME, SYNC_MESSAGE)
VALUES
(1, '宁波港船务货运代理有限公司', '91330201MA2ABCDE9X', '宁波市北仑区明州西路188号', 'local_test',
TO_TIMESTAMP('2026-05-20 09:12:33', 'YYYY-MM-DD HH24:MI:SS'),
'local_test', TO_TIMESTAMP('2026-05-20 09:15:10', 'YYYY-MM-DD HH24:MI:SS'),
'zhoujingli@example.com', 'JT-10256', '1', 'N', 'N', NULL, NULL,
'李四', '5000万元', 'OPP-202605210001', 'EFFECTUAL', '0', '0',
'QY', 'GNQY', 'supplier', 'N',
'本地初始化测试数据', 'synced', TO_TIMESTAMP('2026-05-20 09:15:10', 'YYYY-MM-DD HH24:MI:SS'), '已按数据采集接口规范同步到法务系统');
INSERT INTO ILDM_FEE_CUSTOMER_INFO
(CUSTOMER_SYSID, CUSTOMER_NAME_C, CUSTOMER_TAXNO, CUSTOMER_REGADDR, CREATE_BY, CREATE_TIME, UPDATE_BY, UPDATE_TIME,
EMAIL_ADDRESS, OPPOSITE_ID, OPPOSITE_CHARACTER, GAT_FLAG, GROUP_FLAG, COUNTRY, COUNTRY_NAME,
LEGAL_PERSON_NAME, REG_CAPITAL, OPPOSITE_CODE, OPPOSITE_STATUS, IS_SGAT, IS_JTN,
NATURE_OF_ENTERPRISE_ORG, NATURE_OF_ENTERPRISE_ORG_LEV, RELATIVE_TYPE, IS_DELETED,
COMMENTS, SYNC_STATUS, SYNC_TIME, SYNC_MESSAGE)
VALUES
(2, '王小明', NULL, NULL, 'local_test',
TO_TIMESTAMP('2026-05-20 14:20:05', 'YYYY-MM-DD HH24:MI:SS'),
'local_test', TO_TIMESTAMP('2026-05-20 14:20:05', 'YYYY-MM-DD HH24:MI:SS'),
NULL, NULL, '2', 'N', 'N', NULL, NULL,
NULL, NULL, 'OPP-202605210002', 'EFFECTUAL', '0', '0',
'QT', NULL, 'client', 'N',
'个人类型测试数据', 'unsynced', NULL, '新增后尚未同步');
INSERT INTO ILDM_FEE_CUSTOMER_INFO
(CUSTOMER_SYSID, CUSTOMER_NAME_C, CUSTOMER_TAXNO, CUSTOMER_REGADDR, CREATE_BY, CREATE_TIME, UPDATE_BY, UPDATE_TIME,
EMAIL_ADDRESS, OPPOSITE_ID, OPPOSITE_CHARACTER, GAT_FLAG, GROUP_FLAG, COUNTRY, COUNTRY_NAME,
LEGAL_PERSON_NAME, REG_CAPITAL, OPPOSITE_CODE, OPPOSITE_STATUS, IS_SGAT, IS_JTN,
NATURE_OF_ENTERPRISE_ORG, NATURE_OF_ENTERPRISE_ORG_LEV, RELATIVE_TYPE, IS_DELETED,
COMMENTS, SYNC_STATUS, SYNC_TIME, SYNC_MESSAGE)
VALUES
(3, 'Ocean Energy Pte. Ltd.', NULL, '88 Harbour Street, London', 'local_test',
TO_TIMESTAMP('2026-05-18 11:08:22', 'YYYY-MM-DD HH24:MI:SS'),
'local_test', TO_TIMESTAMP('2026-05-18 11:10:40', 'YYYY-MM-DD HH24:MI:SS'),
'alice.brown@ocean-energy.com', 'JT-10888', '3', 'N', 'N', 'GJ826', '英国',
NULL, 'USD 2,000,000', 'OPP-202605210003', 'EFFECTUAL', '0', '0',
'QY', 'WZQY', 'Partner', 'N',
'境外组织测试数据', 'failed', TO_TIMESTAMP('2026-05-18 11:10:40', 'YYYY-MM-DD HH24:MI:SS'), '法务系统返回:注册地址长度超限,请修正后重试');
INSERT INTO SEAL_PARTNER_CONTACT
(ID, PARTNER_RECORD_ID, CONTACT_NAME, CONTACT_PHONE, POSTAL_ADDRESS, EMAIL,
SORT_ORDER, DEL_FLAG, CREATE_BY, CREATE_TIME, UPDATE_BY, UPDATE_TIME, REMARK)
VALUES
(1, 1, '周经理', '15757460062', '宁波市北仑区明州西路188号A座', 'zhoujingli@example.com',
1, '0', 'local_test', TO_TIMESTAMP('2026-05-20 09:12:33', 'YYYY-MM-DD HH24:MI:SS'),
'local_test', TO_TIMESTAMP('2026-05-20 09:12:33', 'YYYY-MM-DD HH24:MI:SS'), '主联系人');
INSERT INTO SEAL_PARTNER_CONTACT
(ID, PARTNER_RECORD_ID, CONTACT_NAME, CONTACT_PHONE, POSTAL_ADDRESS, EMAIL,
SORT_ORDER, DEL_FLAG, CREATE_BY, CREATE_TIME, UPDATE_BY, UPDATE_TIME, REMARK)
VALUES
(2, 3, 'Alice Brown', '+44-20-8899-0001', '88 Harbour Street, London', 'alice.brown@ocean-energy.com',
1, '0', 'local_test', TO_TIMESTAMP('2026-05-18 11:08:22', 'YYYY-MM-DD HH24:MI:SS'),
'local_test', TO_TIMESTAMP('2026-05-18 11:08:22', 'YYYY-MM-DD HH24:MI:SS'), '海外主联系人');
INSERT INTO SEAL_PARTNER_CONTACT
(ID, PARTNER_RECORD_ID, CONTACT_NAME, CONTACT_PHONE, POSTAL_ADDRESS, EMAIL,
SORT_ORDER, DEL_FLAG, CREATE_BY, CREATE_TIME, UPDATE_BY, UPDATE_TIME, REMARK)
VALUES
(3, 3, 'Tom Lee', '+44-20-8899-0002', '88 Harbour Street, London', 'tom.lee@ocean-energy.com',
2, '0', 'local_test', TO_TIMESTAMP('2026-05-18 11:08:22', 'YYYY-MM-DD HH24:MI:SS'),
'local_test', TO_TIMESTAMP('2026-05-18 11:08:22', 'YYYY-MM-DD HH24:MI:SS'), '备用联系人');
CREATE SEQUENCE SEQ_ILDM_FEE_CUSTOMER_INFO
START WITH 4
INCREMENT BY 1
NOCACHE
NOCYCLE;
CREATE SEQUENCE SEQ_SEAL_PARTNER_CONTACT
START WITH 4
INCREMENT BY 1
NOCACHE
NOCYCLE;