dengjia 2 月之前
當前提交
da51cb92eb
共有 90 個文件被更改,包括 8407 次插入0 次删除
  1. 二進制
      .DS_Store
  2. 2 0
      .gitattributes
  3. 33 0
      .gitignore
  4. 37 0
      README.md
  5. 152 0
      api-docs/risk-check-api.yaml
  6. 683 0
      docs/auth-lifecycle.md
  7. 305 0
      docs/contract-lifecycle-part1.md
  8. 482 0
      docs/contract-lifecycle-part2.md
  9. 679 0
      docs/contract-lifecycle-part3.md
  10. 145 0
      docs/system-architecture.md
  11. 57 0
      java3000.sql
  12. 259 0
      mvnw
  13. 149 0
      mvnw.cmd
  14. 236 0
      pom.xml
  15. 二進制
      src/lib/netsign-sdk-2.5.105.mix.jar
  16. 二進制
      src/lib/netsign-sdk-3.0.6.mix.jar
  17. 19 0
      src/main/java/net/yaoyi/gulop/member/GulopMemberAuthApplication.java
  18. 28 0
      src/main/java/net/yaoyi/gulop/member/auth/api/IErrorCode.java
  19. 28 0
      src/main/java/net/yaoyi/gulop/member/auth/config/MinioConfig.java
  20. 72 0
      src/main/java/net/yaoyi/gulop/member/auth/config/MybatisPlusConfiguration.java
  21. 35 0
      src/main/java/net/yaoyi/gulop/member/auth/config/RedisConfig.java
  22. 129 0
      src/main/java/net/yaoyi/gulop/member/auth/constant/CacheConstants.java
  23. 133 0
      src/main/java/net/yaoyi/gulop/member/auth/constant/CommonConstants.java
  24. 21 0
      src/main/java/net/yaoyi/gulop/member/auth/constant/PaginationConstants.java
  25. 234 0
      src/main/java/net/yaoyi/gulop/member/auth/constant/SecurityConstants.java
  26. 73 0
      src/main/java/net/yaoyi/gulop/member/auth/constant/ServiceNameConstants.java
  27. 36 0
      src/main/java/net/yaoyi/gulop/member/auth/constant/enums/CaptchaFlagTypeEnum.java
  28. 50 0
      src/main/java/net/yaoyi/gulop/member/auth/constant/enums/ContractStatus.java
  29. 36 0
      src/main/java/net/yaoyi/gulop/member/auth/constant/enums/EncFlagTypeEnum.java
  30. 81 0
      src/main/java/net/yaoyi/gulop/member/auth/constant/enums/LoginTypeEnum.java
  31. 41 0
      src/main/java/net/yaoyi/gulop/member/auth/constant/enums/MenuTypeEnum.java
  32. 46 0
      src/main/java/net/yaoyi/gulop/member/auth/constant/enums/ProcessStatusEnum.java
  33. 51 0
      src/main/java/net/yaoyi/gulop/member/auth/constant/enums/ResourceTypeEnum.java
  34. 75 0
      src/main/java/net/yaoyi/gulop/member/auth/constant/enums/ResultCodeEnum.java
  35. 36 0
      src/main/java/net/yaoyi/gulop/member/auth/constant/enums/StyleTypeEnum.java
  36. 61 0
      src/main/java/net/yaoyi/gulop/member/auth/constant/enums/TaskStatusEnum.java
  37. 110 0
      src/main/java/net/yaoyi/gulop/member/auth/constant/enums/flag/CommonFlag.java
  38. 12 0
      src/main/java/net/yaoyi/gulop/member/auth/constant/enums/level/CmsLevel.java
  39. 17 0
      src/main/java/net/yaoyi/gulop/member/auth/constant/enums/level/CommonLevel.java
  40. 37 0
      src/main/java/net/yaoyi/gulop/member/auth/constant/enums/level/UpmsLevel.java
  41. 34 0
      src/main/java/net/yaoyi/gulop/member/auth/constant/enums/state/CheckoutState.java
  42. 35 0
      src/main/java/net/yaoyi/gulop/member/auth/constant/enums/state/CmsStatus.java
  43. 32 0
      src/main/java/net/yaoyi/gulop/member/auth/constant/enums/state/CommonStatus.java
  44. 68 0
      src/main/java/net/yaoyi/gulop/member/auth/constant/enums/state/GigStatus.java
  45. 53 0
      src/main/java/net/yaoyi/gulop/member/auth/constant/enums/state/PmsStatus.java
  46. 100 0
      src/main/java/net/yaoyi/gulop/member/auth/constant/enums/state/UmsStatus.java
  47. 32 0
      src/main/java/net/yaoyi/gulop/member/auth/constant/enums/state/UpmsStatus.java
  48. 50 0
      src/main/java/net/yaoyi/gulop/member/auth/constant/enums/type/CheckoutType.java
  49. 52 0
      src/main/java/net/yaoyi/gulop/member/auth/constant/enums/type/CmsType.java
  50. 226 0
      src/main/java/net/yaoyi/gulop/member/auth/constant/enums/type/CommonType.java
  51. 216 0
      src/main/java/net/yaoyi/gulop/member/auth/constant/enums/type/GigType.java
  52. 15 0
      src/main/java/net/yaoyi/gulop/member/auth/constant/enums/type/PmsType.java
  53. 34 0
      src/main/java/net/yaoyi/gulop/member/auth/constant/enums/type/UmsType.java
  54. 93 0
      src/main/java/net/yaoyi/gulop/member/auth/constant/enums/type/UpmsType.java
  55. 110 0
      src/main/java/net/yaoyi/gulop/member/auth/controller/MemberAuthController.java
  56. 82 0
      src/main/java/net/yaoyi/gulop/member/auth/controller/SignContractController.java
  57. 25 0
      src/main/java/net/yaoyi/gulop/member/auth/dto/FourElementAuthDto.java
  58. 18 0
      src/main/java/net/yaoyi/gulop/member/auth/dto/QualificationDTO.java
  59. 47 0
      src/main/java/net/yaoyi/gulop/member/auth/dto/ThreeElementAuthDTO.java
  60. 133 0
      src/main/java/net/yaoyi/gulop/member/auth/entity/GigConfig.java
  61. 85 0
      src/main/java/net/yaoyi/gulop/member/auth/entity/SmartContract.java
  62. 38 0
      src/main/java/net/yaoyi/gulop/member/auth/entity/UmsExtApiHis.java
  63. 44 0
      src/main/java/net/yaoyi/gulop/member/auth/entity/UmsExtConfig.java
  64. 64 0
      src/main/java/net/yaoyi/gulop/member/auth/entity/UmsMember.java
  65. 85 0
      src/main/java/net/yaoyi/gulop/member/auth/entity/UserAgreementRecord.java
  66. 39 0
      src/main/java/net/yaoyi/gulop/member/auth/handler/ContractStatusTypeHandler.java
  67. 45 0
      src/main/java/net/yaoyi/gulop/member/auth/handler/JSONObjectTypeHandler.java
  68. 28 0
      src/main/java/net/yaoyi/gulop/member/auth/mapper/GigConfigMapper.java
  69. 24 0
      src/main/java/net/yaoyi/gulop/member/auth/mapper/SmartContractMapper.java
  70. 10 0
      src/main/java/net/yaoyi/gulop/member/auth/mapper/UmsExtApiHisMapper.java
  71. 20 0
      src/main/java/net/yaoyi/gulop/member/auth/mapper/UmsExtConfigMapper.java
  72. 37 0
      src/main/java/net/yaoyi/gulop/member/auth/mapper/UmsMemberMapper.java
  73. 36 0
      src/main/java/net/yaoyi/gulop/member/auth/mapper/UserAgreementRecordMapper.java
  74. 21 0
      src/main/java/net/yaoyi/gulop/member/auth/service/GigConfigService.java
  75. 34 0
      src/main/java/net/yaoyi/gulop/member/auth/service/MemberAuthService.java
  76. 13 0
      src/main/java/net/yaoyi/gulop/member/auth/service/UmsExtApiHisService.java
  77. 585 0
      src/main/java/net/yaoyi/gulop/member/auth/service/impl/GigConfigServiceImpl.java
  78. 337 0
      src/main/java/net/yaoyi/gulop/member/auth/service/impl/MemberAuthServiceImpl.java
  79. 49 0
      src/main/java/net/yaoyi/gulop/member/auth/service/impl/UmsExtApiHisServiceImpl.java
  80. 96 0
      src/main/java/net/yaoyi/gulop/member/auth/util/CommonResult.java
  81. 53 0
      src/main/java/net/yaoyi/gulop/member/auth/util/ContractNumberGenerator.java
  82. 61 0
      src/main/java/net/yaoyi/gulop/member/auth/util/ImageToBase64Converter.java
  83. 77 0
      src/main/java/net/yaoyi/gulop/member/auth/util/MinioUtil.java
  84. 48 0
      src/main/java/net/yaoyi/gulop/member/auth/util/RemoteFileUtils.java
  85. 40 0
      src/main/java/net/yaoyi/gulop/member/auth/vo/MemberAuthInfoVO.java
  86. 28 0
      src/main/java/net/yaoyi/gulop/member/auth/vo/SignItemVO.java
  87. 46 0
      src/main/resources/application.properties
  88. 13 0
      src/test/java/net/yaoyi/gulop/member/auth/GulopMemberAuthApplicationTests.java
  89. 216 0
      开发文档.md
  90. 二進制
      要易系统(身份认证+爱签签约)项目移交清单 (1).pdf

二進制
.DS_Store


+ 2 - 0
.gitattributes

@@ -0,0 +1,2 @@
+/mvnw text eol=lf
+*.cmd text eol=crlf

+ 33 - 0
.gitignore

@@ -0,0 +1,33 @@
+HELP.md
+target/
+!.mvn/wrapper/maven-wrapper.jar
+!**/src/main/**/target/
+!**/src/test/**/target/
+
+### STS ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+.sts4-cache
+
+### IntelliJ IDEA ###
+.idea
+*.iws
+*.iml
+*.ipr
+
+### NetBeans ###
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+/.nb-gradle/
+build/
+!**/src/main/**/build/
+!**/src/test/**/build/
+
+### VS Code ###
+.vscode/

+ 37 - 0
README.md

@@ -0,0 +1,37 @@
+# 供应链金融平台
+
+## 项目概述
+
+本项目是基于Spring Cloud实现的供应链金融平台,旨在为供应链上下游企业提供便捷、高效、安全的金融服务。平台连接核心企业、上下游供应商和金融机构,打通供应链金融全流程,实现资金的高效流转和风险的有效控制。
+
+## 核心功能模块
+
+按业务流程排序,系统核心功能模块包括:
+
+1. **认证模块**:用户注册、登录、身份验证、权限管理,支持多角色(核心企业、供应商、金融机构)的身份认证和授权。
+
+2. **签约模块**:电子合同生成、在线签署、合同管理,为供应链各方提供法律保障。
+
+## 技术栈矩阵
+
+| 层级 | 技术栈 | 详细说明 |
+| --- | --- | --- |
+| 前端 | UniApp | 跨平台前端框架,支持同时开发iOS、Android和Web应用 |
+| 后端 | Java 17 + Spring Cloud | 微服务架构,包括Spring Boot、Spring Cloud Gateway、Nacos等组件 |
+| 数据库 | MySQL | 关系型数据库,用于存储业务数据 |
+
+## 项目架构
+
+系统采用微服务架构,主要包含以下服务:
+
+- 网关服务:请求路由、负载均衡
+- 认证服务:用户认证、授权管理
+- 签约服务:合同生成与管理
+- 公共服务:公共功能和工具
+
+## 部署要求
+
+- Java 17+
+- MySQL 8.0+
+- Docker(可选,用于容器化部署)
+- Nacos(服务注册与配置中心) 

+ 152 - 0
api-docs/risk-check-api.yaml

@@ -0,0 +1,152 @@
+openapi: 3.0.3
+info:
+  title: 供应链金融平台风险检查API
+  description: 提供供应链金融平台的风险评估服务
+  version: 1.0.0
+  contact:
+    name: 供应链金融平台开发团队
+    email: support@gulop.com
+servers:
+  - url: https://api.gulop.com
+    description: 生产环境
+  - url: https://test-api.gulop.com
+    description: 测试环境
+
+paths:
+  /api/v1/risk-check:
+    post:
+      summary: 风险评估检查
+      description: 对账单进行风险评估检查
+      tags:
+        - 风险管理
+      requestBody:
+        required: true
+        content:
+          application/json:
+            schema:
+              $ref: '#/components/schemas/RiskCheckRequest'
+            example:
+              billNo: "EB20250701123456"
+              creditScore: 780
+      responses:
+        '200':
+          description: 风险评估成功
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/RiskCheckSuccessResponse'
+              example:
+                code: 200
+                message: "风险评估通过"
+                data:
+                  riskLevel: "LOW"
+                  approved: true
+                  maxAmount: 500000
+                  evaluationDate: "2025-07-01T10:15:30Z"
+        '400':
+          description: 请求参数错误
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+              example:
+                code: 400
+                message: "请求参数错误"
+                errors:
+                  - field: "billNo"
+                    message: "账单号格式不正确"
+        '401':
+          description: 未授权
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+              example:
+                code: 401
+                message: "未授权,请先登录"
+        '500':
+          description: 服务器错误
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+              example:
+                code: 500
+                message: "服务器内部错误"
+
+components:
+  schemas:
+    RiskCheckRequest:
+      type: object
+      required:
+        - billNo
+        - creditScore
+      properties:
+        billNo:
+          type: string
+          description: 账单编号
+          example: "EB20250701123456"
+        creditScore:
+          type: integer
+          description: 信用评分
+          minimum: 300
+          maximum: 850
+          example: 780
+    
+    RiskCheckSuccessResponse:
+      type: object
+      properties:
+        code:
+          type: integer
+          description: 状态码
+          example: 200
+        message:
+          type: string
+          description: 响应消息
+          example: "风险评估通过"
+        data:
+          type: object
+          properties:
+            riskLevel:
+              type: string
+              description: 风险等级(LOW, MEDIUM, HIGH)
+              enum: [LOW, MEDIUM, HIGH]
+              example: "LOW"
+            approved:
+              type: boolean
+              description: 是否通过风险检查
+              example: true
+            maxAmount:
+              type: number
+              description: 最大授信额度
+              example: 500000
+            evaluationDate:
+              type: string
+              format: date-time
+              description: 评估时间
+              example: "2025-07-01T10:15:30Z"
+    
+    ErrorResponse:
+      type: object
+      properties:
+        code:
+          type: integer
+          description: 错误码
+          example: 400
+        message:
+          type: string
+          description: 错误消息
+          example: "请求参数错误"
+        errors:
+          type: array
+          items:
+            type: object
+            properties:
+              field:
+                type: string
+                description: 错误字段
+                example: "billNo"
+              message:
+                type: string
+                description: 错误详情
+                example: "账单号格式不正确" 

+ 683 - 0
docs/auth-lifecycle.md

@@ -0,0 +1,683 @@
+# 认证功能生命周期详解
+
+## 1. 控制器层 (Controller)
+
+### 1.1 用户注册接口
+
+**接口定义**:`UserAuthController.register()`
+
+```java
+@PostMapping("/register")
+public ApiResult<UserRegisterResponse> register(@RequestBody @Validated UserRegisterRequest request) {
+    return userAuthService.register(request);
+}
+```
+
+**功能职责**:
+- 接收用户注册请求
+- 参数校验(通过`@Validated`注解)
+- 调用服务层处理注册逻辑
+- 返回统一格式的注册结果
+
+**请求参数(UserRegisterRequest)**:
+```json
+{
+  "username": "enterprise_user1",
+  "password": "Password@123",
+  "confirmPassword": "Password@123",
+  "mobile": "13812345678",
+  "email": "test@example.com",
+  "userType": "ENTERPRISE",
+  "enterpriseInfo": {
+    "enterpriseName": "测试企业有限公司",
+    "creditCode": "91110000ABCDEFG123",
+    "legalPerson": "张三",
+    "contactPhone": "13812345678"
+  }
+}
+```
+
+**响应结果(UserRegisterResponse)**:
+```json
+{
+  "code": 200,
+  "message": "注册成功",
+  "data": {
+    "userId": "1234567890",
+    "username": "enterprise_user1",
+    "userType": "ENTERPRISE",
+    "registrationTime": "2023-08-15T10:30:45Z"
+  }
+}
+```
+
+### 1.2 用户登录接口
+
+**接口定义**:`UserAuthController.login()`
+
+```java
+@PostMapping("/login")
+public ApiResult<LoginResponse> login(@RequestBody @Validated LoginRequest request) {
+    return userAuthService.login(request);
+}
+```
+
+**功能职责**:
+- 接收用户登录请求
+- 参数校验
+- 调用服务层处理登录逻辑
+- 返回统一格式的登录结果(包含JWT令牌)
+
+**请求参数(LoginRequest)**:
+```json
+{
+  "username": "enterprise_user1",
+  "password": "Password@123"
+}
+```
+
+**响应结果(LoginResponse)**:
+```json
+{
+  "code": 200,
+  "message": "登录成功",
+  "data": {
+    "userId": "1234567890",
+    "username": "enterprise_user1",
+    "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
+    "refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
+    "expiresIn": 3600
+  }
+}
+```
+
+### 1.3 刷新令牌接口
+
+**接口定义**:`UserAuthController.refreshToken()`
+
+```java
+@PostMapping("/refresh-token")
+public ApiResult<RefreshTokenResponse> refreshToken(@RequestBody @Validated RefreshTokenRequest request) {
+    return userAuthService.refreshToken(request);
+}
+```
+
+### 1.4 登出接口
+
+**接口定义**:`UserAuthController.logout()`
+
+```java
+@PostMapping("/logout")
+public ApiResult<Void> logout(@RequestHeader("Authorization") String token) {
+    return userAuthService.logout(token);
+}
+```
+
+## 2. 服务层 (Service)
+
+### 2.1 用户认证服务
+
+**主要实现类**:`UserAuthServiceImpl`
+
+#### 2.1.1 注册业务流程 `register()`
+
+```java
+@Override
+@Transactional(rollbackFor = Exception.class)
+public ApiResult<UserRegisterResponse> register(UserRegisterRequest request) {
+    // 1. 检查用户名是否已存在
+    if (userMapper.existsByUsername(request.getUsername())) {
+        return ApiResult.failed("用户名已存在");
+    }
+    
+    // 2. 检查手机号是否已注册
+    if (userMapper.existsByMobile(request.getMobile())) {
+        return ApiResult.failed("手机号已注册");
+    }
+    
+    // 3. 密码加密
+    String encryptedPassword = passwordEncoder.encode(request.getPassword());
+    
+    // 4. 构建用户对象
+    User user = new User();
+    user.setUserId(IdGenerator.generateId());
+    user.setUsername(request.getUsername());
+    user.setPassword(encryptedPassword);
+    user.setMobile(request.getMobile());
+    user.setEmail(request.getEmail());
+    user.setUserType(request.getUserType());
+    user.setStatus(UserStatus.NORMAL);
+    user.setCreateTime(new Date());
+    
+    // 5. 保存用户信息
+    userMapper.insert(user);
+    
+    // 6. 企业用户需保存企业信息
+    if (UserType.ENTERPRISE.equals(request.getUserType())) {
+        EnterpriseInfo enterpriseInfo = request.getEnterpriseInfo();
+        enterpriseInfo.setUserId(user.getUserId());
+        enterpriseInfo.setVerifyStatus(VerifyStatus.UNVERIFIED);
+        enterpriseInfoMapper.insert(enterpriseInfo);
+    }
+    
+    // 7. 分配默认角色权限
+    roleService.assignDefaultRole(user.getUserId(), user.getUserType());
+    
+    // 8. 发送注册成功通知
+    notificationService.sendRegistrationNotification(user);
+    
+    // 9. 构建返回结果
+    UserRegisterResponse response = new UserRegisterResponse();
+    response.setUserId(user.getUserId());
+    response.setUsername(user.getUsername());
+    response.setUserType(user.getUserType());
+    response.setRegistrationTime(user.getCreateTime());
+    
+    return ApiResult.success("注册成功", response);
+}
+```
+
+**处理逻辑**:
+1. 用户名和手机号唯一性校验
+2. 密码加密存储
+3. 保存用户基本信息
+4. 保存企业附加信息(如适用)
+5. 分配默认角色和权限
+6. 发送注册成功通知
+7. 构建返回结果
+
+#### 2.1.2 登录业务流程 `login()`
+
+```java
+@Override
+public ApiResult<LoginResponse> login(LoginRequest request) {
+    // 1. 根据用户名查询用户
+    User user = userMapper.selectByUsername(request.getUsername());
+    if (user == null) {
+        return ApiResult.failed("用户名或密码错误");
+    }
+    
+    // 2. 检查用户状态
+    if (!UserStatus.NORMAL.equals(user.getStatus())) {
+        return ApiResult.failed("账号已被禁用,请联系管理员");
+    }
+    
+    // 3. 验证密码
+    if (!passwordEncoder.matches(request.getPassword(), user.getPassword())) {
+        // 4. 记录登录失败次数
+        loginFailService.recordFailedLogin(user.getUserId());
+        return ApiResult.failed("用户名或密码错误");
+    }
+    
+    // 5. 清除登录失败记录
+    loginFailService.clearFailedLogin(user.getUserId());
+    
+    // 6. 生成JWT令牌
+    String token = jwtTokenProvider.generateToken(user);
+    String refreshToken = jwtTokenProvider.generateRefreshToken(user);
+    
+    // 7. 保存令牌信息
+    tokenService.saveToken(user.getUserId(), token, refreshToken);
+    
+    // 8. 记录登录日志
+    auditLogService.logLogin(user.getUserId(), request.getLoginIp(), LoginResult.SUCCESS);
+    
+    // 9. 构建返回结果
+    LoginResponse response = new LoginResponse();
+    response.setUserId(user.getUserId());
+    response.setUsername(user.getUsername());
+    response.setToken(token);
+    response.setRefreshToken(refreshToken);
+    response.setExpiresIn(jwtTokenProvider.getExpirationTime());
+    
+    return ApiResult.success("登录成功", response);
+}
+```
+
+**处理逻辑**:
+1. 验证用户存在性
+2. 检查用户状态(是否被禁用)
+3. 验证密码正确性
+4. 处理登录失败记录
+5. 生成JWT令牌和刷新令牌
+6. 保存令牌信息到缓存
+7. 记录登录日志
+8. 构建返回结果
+
+### 2.2 JWT令牌服务
+
+**主要实现类**:`JwtTokenProviderImpl`
+
+#### 2.2.1 生成令牌 `generateToken()`
+
+```java
+@Override
+public String generateToken(User user) {
+    Date now = new Date();
+    Date expiryDate = new Date(now.getTime() + tokenExpirationMs);
+    
+    Map<String, Object> claims = new HashMap<>();
+    claims.put("userId", user.getUserId());
+    claims.put("username", user.getUsername());
+    claims.put("userType", user.getUserType());
+    
+    return Jwts.builder()
+        .setClaims(claims)
+        .setIssuedAt(now)
+        .setExpiration(expiryDate)
+        .signWith(SignatureAlgorithm.HS256, jwtSecret)
+        .compact();
+}
+```
+
+#### 2.2.2 验证令牌 `validateToken()`
+
+```java
+@Override
+public boolean validateToken(String token) {
+    try {
+        Jwts.parser().setSigningKey(jwtSecret).parseClaimsJws(token);
+        return true;
+    } catch (SignatureException ex) {
+        log.error("Invalid JWT signature");
+    } catch (MalformedJwtException ex) {
+        log.error("Invalid JWT token");
+    } catch (ExpiredJwtException ex) {
+        log.error("Expired JWT token");
+    } catch (UnsupportedJwtException ex) {
+        log.error("Unsupported JWT token");
+    } catch (IllegalArgumentException ex) {
+        log.error("JWT claims string is empty");
+    }
+    return false;
+}
+```
+
+## 3. 数据访问层 (Mapper/Repository)
+
+### 3.1 用户数据访问
+
+**主要实现类**:`UserMapper`
+
+```java
+@Mapper
+public interface UserMapper {
+    /**
+     * 插入用户
+     */
+    int insert(User user);
+    
+    /**
+     * 根据用户名查询
+     */
+    User selectByUsername(String username);
+    
+    /**
+     * 检查用户名是否存在
+     */
+    boolean existsByUsername(String username);
+    
+    /**
+     * 检查手机号是否存在
+     */
+    boolean existsByMobile(String mobile);
+    
+    /**
+     * 根据ID查询
+     */
+    User selectById(String userId);
+    
+    /**
+     * 更新用户信息
+     */
+    int update(User user);
+}
+```
+
+### 3.2 企业信息数据访问
+
+**主要实现类**:`EnterpriseInfoMapper`
+
+```java
+@Mapper
+public interface EnterpriseInfoMapper {
+    /**
+     * 插入企业信息
+     */
+    int insert(EnterpriseInfo enterpriseInfo);
+    
+    /**
+     * 根据用户ID查询
+     */
+    EnterpriseInfo selectByUserId(String userId);
+    
+    /**
+     * 根据统一社会信用代码查询
+     */
+    EnterpriseInfo selectByCreditCode(String creditCode);
+    
+    /**
+     * 更新企业信息
+     */
+    int update(EnterpriseInfo enterpriseInfo);
+}
+```
+
+## 4. 安全拦截层
+
+### 4.1 JWT认证过滤器
+
+**主要实现类**:`JwtAuthenticationFilter`
+
+```java
+@Component
+@RequiredArgsConstructor
+public class JwtAuthenticationFilter extends OncePerRequestFilter {
+
+    private final JwtTokenProvider jwtTokenProvider;
+    private final UserDetailsService userDetailsService;
+
+    @Override
+    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
+            throws ServletException, IOException {
+        try {
+            // 1. 从请求中获取JWT令牌
+            String jwt = getJwtFromRequest(request);
+
+            // 2. 验证令牌有效性
+            if (StringUtils.hasText(jwt) && jwtTokenProvider.validateToken(jwt)) {
+                // 3. 从令牌中获取用户ID
+                String userId = jwtTokenProvider.getUserIdFromToken(jwt);
+
+                // 4. 加载用户详情
+                UserDetails userDetails = userDetailsService.loadUserById(userId);
+                
+                // 5. 创建认证对象
+                UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
+                        userDetails, null, userDetails.getAuthorities());
+                authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
+
+                // 6. 设置认证上下文
+                SecurityContextHolder.getContext().setAuthentication(authentication);
+            }
+        } catch (Exception ex) {
+            logger.error("Could not set user authentication in security context", ex);
+        }
+
+        // 7. 继续过滤器链
+        filterChain.doFilter(request, response);
+    }
+
+    private String getJwtFromRequest(HttpServletRequest request) {
+        String bearerToken = request.getHeader("Authorization");
+        if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {
+            return bearerToken.substring(7);
+        }
+        return null;
+    }
+}
+```
+
+**处理逻辑**:
+1. 从HTTP请求头中提取JWT令牌
+2. 验证令牌的有效性
+3. 从令牌中解析用户信息
+4. 加载用户详情和权限
+5. 创建认证对象
+6. 设置Spring Security上下文
+7. 继续过滤器链处理
+
+## 5. 实体类层 (Entity/Model)
+
+### 5.1 用户实体
+
+```java
+@Data
+@Table(name = "t_user")
+public class User implements Serializable {
+    
+    @Id
+    private String userId;
+    
+    private String username;
+    
+    private String password;
+    
+    private String mobile;
+    
+    private String email;
+    
+    @Enumerated(EnumType.STRING)
+    private UserType userType;
+    
+    @Enumerated(EnumType.STRING)
+    private UserStatus status;
+    
+    private Date createTime;
+    
+    private Date updateTime;
+}
+```
+
+### 5.2 企业信息实体
+
+```java
+@Data
+@Table(name = "t_enterprise_info")
+public class EnterpriseInfo implements Serializable {
+    
+    @Id
+    private String id;
+    
+    private String userId;
+    
+    private String enterpriseName;
+    
+    private String creditCode;
+    
+    private String legalPerson;
+    
+    private String contactPhone;
+    
+    @Enumerated(EnumType.STRING)
+    private VerifyStatus verifyStatus;
+    
+    private Date createTime;
+    
+    private Date updateTime;
+}
+```
+
+## 6. 异常处理
+
+### 6.1 全局异常处理器
+
+**主要实现类**:`GlobalExceptionHandler`
+
+```java
+@RestControllerAdvice
+@Slf4j
+public class GlobalExceptionHandler {
+
+    /**
+     * 处理参数验证异常
+     */
+    @ExceptionHandler(MethodArgumentNotValidException.class)
+    public ApiResult<String> handleMethodArgumentNotValidException(MethodArgumentNotValidException ex) {
+        BindingResult bindingResult = ex.getBindingResult();
+        StringBuilder sb = new StringBuilder("参数校验失败:");
+        
+        for (FieldError fieldError : bindingResult.getFieldErrors()) {
+            sb.append(fieldError.getField()).append(":").append(fieldError.getDefaultMessage()).append(", ");
+        }
+        
+        String msg = sb.toString();
+        log.warn(msg);
+        return ApiResult.validateFailed(msg);
+    }
+
+    /**
+     * 处理业务异常
+     */
+    @ExceptionHandler(BusinessException.class)
+    public ApiResult<String> handleBusinessException(BusinessException ex) {
+        log.error("业务异常", ex);
+        return ApiResult.failed(ex.getCode(), ex.getMessage());
+    }
+
+    /**
+     * 处理未授权异常
+     */
+    @ExceptionHandler(AccessDeniedException.class)
+    public ApiResult<String> handleAccessDeniedException(AccessDeniedException ex) {
+        log.error("权限不足", ex);
+        return ApiResult.forbidden("没有权限执行此操作");
+    }
+
+    /**
+     * 处理其他未知异常
+     */
+    @ExceptionHandler(Exception.class)
+    public ApiResult<String> handleException(Exception ex) {
+        log.error("系统异常", ex);
+        return ApiResult.failed("服务器内部错误,请稍后重试");
+    }
+}
+```
+
+## 7. 缓存层
+
+### 7.1 令牌缓存服务
+
+**主要实现类**:`TokenServiceImpl`
+
+```java
+@Service
+@RequiredArgsConstructor
+public class TokenServiceImpl implements TokenService {
+
+    private final RedisTemplate<String, Object> redisTemplate;
+    private final JwtTokenProvider jwtTokenProvider;
+
+    private static final String TOKEN_KEY_PREFIX = "auth:token:";
+    private static final String REFRESH_TOKEN_KEY_PREFIX = "auth:refresh_token:";
+
+    @Override
+    public void saveToken(String userId, String token, String refreshToken) {
+        // 保存访问令牌,过期时间与JWT令牌一致
+        String tokenKey = TOKEN_KEY_PREFIX + userId;
+        redisTemplate.opsForValue().set(tokenKey, token, jwtTokenProvider.getExpirationTime(), TimeUnit.MILLISECONDS);
+        
+        // 保存刷新令牌,过期时间更长
+        String refreshTokenKey = REFRESH_TOKEN_KEY_PREFIX + userId;
+        redisTemplate.opsForValue().set(refreshTokenKey, refreshToken, jwtTokenProvider.getRefreshExpirationTime(), TimeUnit.MILLISECONDS);
+    }
+
+    @Override
+    public String getToken(String userId) {
+        String key = TOKEN_KEY_PREFIX + userId;
+        Object token = redisTemplate.opsForValue().get(key);
+        return token != null ? token.toString() : null;
+    }
+
+    @Override
+    public String getRefreshToken(String userId) {
+        String key = REFRESH_TOKEN_KEY_PREFIX + userId;
+        Object refreshToken = redisTemplate.opsForValue().get(key);
+        return refreshToken != null ? refreshToken.toString() : null;
+    }
+
+    @Override
+    public void removeToken(String userId) {
+        String tokenKey = TOKEN_KEY_PREFIX + userId;
+        String refreshTokenKey = REFRESH_TOKEN_KEY_PREFIX + userId;
+        
+        redisTemplate.delete(tokenKey);
+        redisTemplate.delete(refreshTokenKey);
+    }
+}
+```
+
+## 8. 日志审计
+
+### 8.1 登录日志服务
+
+**主要实现类**:`AuditLogServiceImpl`
+
+```java
+@Service
+@RequiredArgsConstructor
+public class AuditLogServiceImpl implements AuditLogService {
+
+    private final AuditLogMapper auditLogMapper;
+
+    @Override
+    public void logLogin(String userId, String ip, LoginResult result) {
+        AuditLog log = new AuditLog();
+        log.setUserId(userId);
+        log.setOperationType(OperationType.LOGIN);
+        log.setOperationResult(result.toString());
+        log.setIp(ip);
+        log.setCreateTime(new Date());
+        
+        auditLogMapper.insert(log);
+    }
+
+    @Override
+    public void logLogout(String userId, String ip) {
+        AuditLog log = new AuditLog();
+        log.setUserId(userId);
+        log.setOperationType(OperationType.LOGOUT);
+        log.setOperationResult(LoginResult.SUCCESS.toString());
+        log.setIp(ip);
+        log.setCreateTime(new Date());
+        
+        auditLogMapper.insert(log);
+    }
+}
+```
+
+## 9. 完整认证流程时序
+
+```
+┌──────────┐          ┌───────────────┐          ┌─────────────┐          ┌──────────┐          ┌─────────┐
+│  客户端  │          │ 控制器层      │          │  服务层     │          │ 数据访问层│          │ 缓存层  │
+└────┬─────┘          └───────┬───────┘          └──────┬──────┘          └────┬─────┘          └────┬────┘
+     │                        │                          │                      │                     │
+     │   1. 登录请求          │                          │                      │                     │
+     │ ───────────────────────>                          │                      │                     │
+     │                        │                          │                      │                     │
+     │                        │  2. 调用登录服务         │                      │                     │
+     │                        │ ─────────────────────────>                      │                     │
+     │                        │                          │                      │                     │
+     │                        │                          │  3. 查询用户         │                     │
+     │                        │                          │ ─────────────────────>                     │
+     │                        │                          │                      │                     │
+     │                        │                          │  4. 返回用户数据     │                     │
+     │                        │                          │ <─────────────────────                     │
+     │                        │                          │                      │                     │
+     │                        │                          │  5. 生成令牌         │                     │
+     │                        │                          │ ─────────────────────────────────────────> │
+     │                        │                          │                      │                     │
+     │                        │                          │  6. 令牌缓存完成     │                     │
+     │                        │                          │ <───────────────────────────────────────── │
+     │                        │                          │                      │                     │
+     │                        │  7. 返回登录结果         │                      │                     │
+     │                        │ <─────────────────────────                      │                     │
+     │                        │                          │                      │                     │
+     │   8. 返回令牌          │                          │                      │                     │
+     │ <───────────────────────                          │                      │                     │
+     │                        │                          │                      │                     │
+     │   9. 后续请求(带令牌)  │                          │                      │                     │
+     │ ───────────────────────>                          │                      │                     │
+     │                        │                          │                      │                     │
+     │                        │  10.令牌校验            │                      │                     │
+     │                        │ ─────────────────────────>                      │                     │
+     │                        │                          │                      │                     │
+     │                        │  11.校验通过            │                      │                     │
+     │                        │ <─────────────────────────                      │                     │
+     │                        │                          │                      │                     │
+     │   12.返回请求结果      │                          │                      │                     │
+     │ <───────────────────────                          │                      │                     │
+     │                        │                          │                      │                     │
+``` 

+ 305 - 0
docs/contract-lifecycle-part1.md

@@ -0,0 +1,305 @@
+# 签约功能生命周期详解 - 第一部分
+
+## 1. 控制器层 (Controller)
+
+### 1.1 合同创建接口
+
+**接口定义**:`ContractController.createContract()`
+
+```java
+@PostMapping("/create")
+@PreAuthorize("hasRole('ENTERPRISE')")
+public ApiResult<ContractCreateResponse> createContract(@RequestBody @Validated ContractCreateRequest request) {
+    return contractService.createContract(request);
+}
+```
+
+**功能职责**:
+- 接收合同创建请求
+- 参数校验
+- 权限校验(仅企业用户可创建合同)
+- 调用服务层处理合同创建逻辑
+- 返回统一格式的创建结果
+
+**请求参数(ContractCreateRequest)**:
+```json
+{
+  "contractName": "供应链采购合同",
+  "contractType": "PURCHASE",
+  "partyA": {
+    "enterpriseId": "ENT10001",
+    "enterpriseName": "核心企业有限公司"
+  },
+  "partyB": {
+    "enterpriseId": "ENT10002",
+    "enterpriseName": "供应商有限公司"
+  },
+  "contractAmount": 500000.00,
+  "contractPeriod": 180,
+  "startDate": "2023-09-01",
+  "endDate": "2024-02-28",
+  "description": "关于设备采购的合同"
+}
+```
+
+**响应结果(ContractCreateResponse)**:
+```json
+{
+  "code": 200,
+  "message": "合同创建成功",
+  "data": {
+    "contractId": "CON20230815001",
+    "contractName": "供应链采购合同",
+    "contractStatus": "DRAFT",
+    "createTime": "2023-08-15T14:30:45Z",
+    "downloadUrl": "/api/v1/contract/download/CON20230815001"
+  }
+}
+```
+
+### 1.2 合同提交签署接口
+
+**接口定义**:`ContractController.submitForSigning()`
+
+```java
+@PostMapping("/{contractId}/submit")
+@PreAuthorize("hasRole('ENTERPRISE')")
+public ApiResult<ContractSubmitResponse> submitForSigning(@PathVariable String contractId) {
+    return contractService.submitForSigning(contractId);
+}
+```
+
+**功能职责**:
+- 接收合同提交签署请求
+- 参数校验(合同ID)
+- 权限校验
+- 调用服务层处理合同提交签署逻辑
+- 返回统一格式的处理结果
+
+**响应结果(ContractSubmitResponse)**:
+```json
+{
+  "code": 200,
+  "message": "合同已提交签署",
+  "data": {
+    "contractId": "CON20230815001",
+    "contractStatus": "PENDING_SIGNING",
+    "submitTime": "2023-08-15T15:30:45Z",
+    "signingUrl": "/api/v1/contract/sign/CON20230815001"
+  }
+}
+```
+
+### 1.3 合同签署接口
+
+**接口定义**:`ContractController.signContract()`
+
+```java
+@PostMapping("/{contractId}/sign")
+@PreAuthorize("hasAnyRole('ENTERPRISE')")
+public ApiResult<ContractSignResponse> signContract(
+        @PathVariable String contractId,
+        @RequestBody @Validated ContractSignRequest request) {
+    return contractService.signContract(contractId, request);
+}
+```
+
+**功能职责**:
+- 接收合同签署请求
+- 参数校验
+- 权限校验
+- 调用服务层处理合同签署逻辑
+- 返回统一格式的签署结果
+
+**请求参数(ContractSignRequest)**:
+```json
+{
+  "signerName": "张三",
+  "signerPosition": "法定代表人",
+  "signMethod": "DIGITAL_SIGNATURE",
+  "verificationCode": "123456"
+}
+```
+
+**响应结果(ContractSignResponse)**:
+```json
+{
+  "code": 200,
+  "message": "合同签署成功",
+  "data": {
+    "contractId": "CON20230815001",
+    "contractStatus": "SIGNED",
+    "signTime": "2023-08-15T16:30:45Z",
+    "signedUrl": "/api/v1/contract/download/signed/CON20230815001"
+  }
+}
+```
+
+### 1.4 合同查询接口
+
+**接口定义**:`ContractController.getContractDetails()`
+
+```java
+@GetMapping("/{contractId}")
+@PreAuthorize("hasAnyRole('ENTERPRISE', 'BANK')")
+public ApiResult<ContractDetailResponse> getContractDetails(@PathVariable String contractId) {
+    return contractService.getContractDetails(contractId);
+}
+```
+
+**功能职责**:
+- 接收合同查询请求
+- 参数校验(合同ID)
+- 权限校验
+- 调用服务层处理合同查询逻辑
+- 返回统一格式的合同详情
+
+**响应结果(ContractDetailResponse)**:
+```json
+{
+  "code": 200,
+  "message": "获取成功",
+  "data": {
+    "contractId": "CON20230815001",
+    "contractName": "供应链采购合同",
+    "contractType": "PURCHASE",
+    "contractStatus": "SIGNED",
+    "contractAmount": 500000.00,
+    "partyA": {
+      "enterpriseId": "ENT10001",
+      "enterpriseName": "核心企业有限公司",
+      "legalPerson": "张三",
+      "signed": true,
+      "signTime": "2023-08-15T16:30:45Z"
+    },
+    "partyB": {
+      "enterpriseId": "ENT10002",
+      "enterpriseName": "供应商有限公司",
+      "legalPerson": "李四",
+      "signed": true,
+      "signTime": "2023-08-15T17:10:30Z"
+    },
+    "contractPeriod": 180,
+    "startDate": "2023-09-01",
+    "endDate": "2024-02-28",
+    "createTime": "2023-08-15T14:30:45Z",
+    "updateTime": "2023-08-15T17:10:30Z",
+    "contractFileId": "FILE20230815001",
+    "downloadUrl": "/api/v1/contract/download/signed/CON20230815001"
+  }
+}
+```
+
+### 1.5 合同列表查询接口
+
+**接口定义**:`ContractController.listContracts()`
+
+```java
+@GetMapping("/list")
+@PreAuthorize("hasAnyRole('ENTERPRISE', 'BANK')")
+public ApiResult<PageResult<ContractListItemResponse>> listContracts(
+        @RequestParam(required = false) String contractName,
+        @RequestParam(required = false) ContractStatus status,
+        @RequestParam(required = false) ContractType type,
+        @RequestParam(defaultValue = "1") Integer pageNum,
+        @RequestParam(defaultValue = "10") Integer pageSize) {
+    ContractQueryParam param = new ContractQueryParam();
+    param.setContractName(contractName);
+    param.setStatus(status);
+    param.setType(type);
+    param.setPageNum(pageNum);
+    param.setPageSize(pageSize);
+    return contractService.listContracts(param);
+}
+```
+
+## 2. 服务层 (Service)
+
+### 2.1 合同服务
+
+**主要实现类**:`ContractServiceImpl`
+
+#### 2.1.1 创建合同流程 `createContract()`
+
+```java
+@Override
+@Transactional(rollbackFor = Exception.class)
+public ApiResult<ContractCreateResponse> createContract(ContractCreateRequest request) {
+    log.info("开始创建合同, 合同名称: {}", request.getContractName());
+    
+    // 1. 校验企业是否存在
+    if (!enterpriseService.checkEnterpriseExists(request.getPartyA().getEnterpriseId())) {
+        return ApiResult.failed("甲方企业不存在");
+    }
+    
+    if (!enterpriseService.checkEnterpriseExists(request.getPartyB().getEnterpriseId())) {
+        return ApiResult.failed("乙方企业不存在");
+    }
+    
+    // 2. 检查用户是否有权限创建合同(是否属于甲方企业)
+    String currentUserId = securityService.getCurrentUserId();
+    if (!enterpriseService.isUserBelongToEnterprise(currentUserId, request.getPartyA().getEnterpriseId())) {
+        return ApiResult.forbidden("您没有权限为该企业创建合同");
+    }
+    
+    // 3. 生成合同ID
+    String contractId = contractIdGenerator.generate();
+    
+    // 4. 构建合同对象
+    Contract contract = new Contract();
+    contract.setContractId(contractId);
+    contract.setContractName(request.getContractName());
+    contract.setContractType(request.getContractType());
+    contract.setPartyAId(request.getPartyA().getEnterpriseId());
+    contract.setPartyAName(request.getPartyA().getEnterpriseName());
+    contract.setPartyBId(request.getPartyB().getEnterpriseId());
+    contract.setPartyBName(request.getPartyB().getEnterpriseName());
+    contract.setContractAmount(request.getContractAmount());
+    contract.setContractPeriod(request.getContractPeriod());
+    contract.setStartDate(DateUtil.parseDate(request.getStartDate()));
+    contract.setEndDate(DateUtil.parseDate(request.getEndDate()));
+    contract.setDescription(request.getDescription());
+    contract.setContractStatus(ContractStatus.DRAFT);
+    contract.setCreateUser(currentUserId);
+    contract.setCreateTime(new Date());
+    
+    // 5. 保存合同基本信息
+    contractMapper.insert(contract);
+    
+    // 6. 生成合同模板文件
+    String contractFileId = contractFileService.generateContractFile(contract);
+    
+    // 7. 更新合同文件ID
+    contract.setContractFileId(contractFileId);
+    contractMapper.updateContractFile(contractId, contractFileId);
+    
+    // 8. 记录合同创建日志
+    contractLogService.logContractOperation(
+            contractId,
+            ContractOperationType.CREATE,
+            "创建合同",
+            currentUserId);
+    
+    // 9. 构建返回结果
+    ContractCreateResponse response = new ContractCreateResponse();
+    response.setContractId(contractId);
+    response.setContractName(request.getContractName());
+    response.setContractStatus(ContractStatus.DRAFT);
+    response.setCreateTime(contract.getCreateTime());
+    response.setDownloadUrl("/api/v1/contract/download/" + contractId);
+    
+    log.info("合同创建成功, 合同ID: {}", contractId);
+    return ApiResult.success("合同创建成功", response);
+}
+```
+
+**处理逻辑**:
+1. 校验企业是否存在
+2. 检查用户是否有权限创建合同
+3. 生成合同ID
+4. 构建合同对象
+5. 保存合同基本信息
+6. 生成合同模板文件
+7. 更新合同文件ID
+8. 记录合同创建日志
+9. 构建返回结果 

+ 482 - 0
docs/contract-lifecycle-part2.md

@@ -0,0 +1,482 @@
+# 签约功能生命周期详解 - 第二部分
+
+## 2. 服务层 (Service) (续)
+
+#### 2.1.2 提交合同签署流程 `submitForSigning()`
+
+```java
+@Override
+@Transactional(rollbackFor = Exception.class)
+public ApiResult<ContractSubmitResponse> submitForSigning(String contractId) {
+    log.info("开始提交合同签署, 合同ID: {}", contractId);
+    
+    // 1. 获取当前用户ID
+    String currentUserId = securityService.getCurrentUserId();
+    
+    // 2. 查询合同信息
+    Contract contract = contractMapper.selectById(contractId);
+    if (contract == null) {
+        return ApiResult.failed("合同不存在");
+    }
+    
+    // 3. 检查合同状态
+    if (contract.getContractStatus() != ContractStatus.DRAFT) {
+        return ApiResult.failed("只有草稿状态的合同可以提交签署");
+    }
+    
+    // 4. 检查用户是否有权限提交合同(是否属于甲方企业)
+    if (!enterpriseService.isUserBelongToEnterprise(currentUserId, contract.getPartyAId())) {
+        return ApiResult.forbidden("您没有权限提交该合同");
+    }
+    
+    // 5. 更新合同状态为待签署
+    contract.setContractStatus(ContractStatus.PENDING_SIGNING);
+    contract.setUpdateUser(currentUserId);
+    contract.setUpdateTime(new Date());
+    contractMapper.updateStatus(contract);
+    
+    // 6. 创建签署任务
+    ContractSignTask taskA = new ContractSignTask();
+    taskA.setTaskId(IdGenerator.generateId());
+    taskA.setContractId(contractId);
+    taskA.setEnterpriseId(contract.getPartyAId());
+    taskA.setEnterpriseName(contract.getPartyAName());
+    taskA.setSignStatus(SignStatus.PENDING);
+    taskA.setCreateTime(new Date());
+    contractSignTaskMapper.insert(taskA);
+    
+    ContractSignTask taskB = new ContractSignTask();
+    taskB.setTaskId(IdGenerator.generateId());
+    taskB.setContractId(contractId);
+    taskB.setEnterpriseId(contract.getPartyBId());
+    taskB.setEnterpriseName(contract.getPartyBName());
+    taskB.setSignStatus(SignStatus.PENDING);
+    taskB.setCreateTime(new Date());
+    contractSignTaskMapper.insert(taskB);
+    
+    // 7. 发送签署通知
+    notificationService.sendContractSigningNotification(contract.getPartyAId(), contract);
+    notificationService.sendContractSigningNotification(contract.getPartyBId(), contract);
+    
+    // 8. 记录合同操作日志
+    contractLogService.logContractOperation(
+            contractId,
+            ContractOperationType.SUBMIT,
+            "提交合同签署",
+            currentUserId);
+    
+    // 9. 构建返回结果
+    ContractSubmitResponse response = new ContractSubmitResponse();
+    response.setContractId(contractId);
+    response.setContractStatus(ContractStatus.PENDING_SIGNING);
+    response.setSubmitTime(contract.getUpdateTime());
+    response.setSigningUrl("/api/v1/contract/sign/" + contractId);
+    
+    log.info("合同提交签署成功, 合同ID: {}", contractId);
+    return ApiResult.success("合同已提交签署", response);
+}
+```
+
+**处理逻辑**:
+1. 获取当前用户ID
+2. 查询合同信息
+3. 检查合同状态
+4. 检查用户权限
+5. 更新合同状态为待签署
+6. 创建签署任务(甲方和乙方)
+7. 发送签署通知
+8. 记录合同操作日志
+9. 构建返回结果
+
+#### 2.1.3 合同签署流程 `signContract()`
+
+```java
+@Override
+@Transactional(rollbackFor = Exception.class)
+public ApiResult<ContractSignResponse> signContract(String contractId, ContractSignRequest request) {
+    log.info("开始签署合同, 合同ID: {}", contractId);
+    
+    // 1. 获取当前用户ID
+    String currentUserId = securityService.getCurrentUserId();
+    
+    // 2. 查询合同信息
+    Contract contract = contractMapper.selectById(contractId);
+    if (contract == null) {
+        return ApiResult.failed("合同不存在");
+    }
+    
+    // 3. 检查合同状态
+    if (contract.getContractStatus() != ContractStatus.PENDING_SIGNING) {
+        return ApiResult.failed("当前合同状态不允许签署");
+    }
+    
+    // 4. 获取用户所属企业
+    String userEnterpriseId = enterpriseService.getUserEnterpriseId(currentUserId);
+    if (userEnterpriseId == null) {
+        return ApiResult.failed("用户未关联企业,无法签署合同");
+    }
+    
+    // 5. 检查用户企业是否为合同相关方
+    boolean isPartyA = userEnterpriseId.equals(contract.getPartyAId());
+    boolean isPartyB = userEnterpriseId.equals(contract.getPartyBId());
+    
+    if (!isPartyA && !isPartyB) {
+        return ApiResult.forbidden("您的企业不是合同签署方,无权签署");
+    }
+    
+    // 6. 查询签署任务
+    ContractSignTask signTask = contractSignTaskMapper.selectByContractAndEnterprise(
+            contractId, userEnterpriseId);
+    
+    if (signTask == null) {
+        return ApiResult.failed("未找到对应的签署任务");
+    }
+    
+    if (signTask.getSignStatus() == SignStatus.SIGNED) {
+        return ApiResult.failed("您已经签署过该合同");
+    }
+    
+    // 7. 验证短信验证码
+    if (!verificationCodeService.verify(currentUserId, request.getVerificationCode())) {
+        return ApiResult.failed("验证码错误或已过期");
+    }
+    
+    // 8. 创建数字签名
+    String signature = digitalSignatureService.createSignature(
+            contractId,
+            userEnterpriseId,
+            request.getSignerName(),
+            request.getSignerPosition());
+    
+    // 9. 更新签署任务状态
+    signTask.setSignStatus(SignStatus.SIGNED);
+    signTask.setSignerName(request.getSignerName());
+    signTask.setSignerPosition(request.getSignerPosition());
+    signTask.setSignMethod(request.getSignMethod());
+    signTask.setSignature(signature);
+    signTask.setSignTime(new Date());
+    signTask.setUpdateTime(new Date());
+    contractSignTaskMapper.update(signTask);
+    
+    // 10. 检查所有签署任务是否完成
+    boolean allSigned = contractSignTaskMapper.checkAllSigned(contractId);
+    
+    // 11. 如果所有方都已签署,更新合同状态为已签署
+    if (allSigned) {
+        contract.setContractStatus(ContractStatus.SIGNED);
+        contract.setUpdateUser(currentUserId);
+        contract.setUpdateTime(new Date());
+        contractMapper.updateStatus(contract);
+        
+        // 12. 生成最终签署版合同
+        String signedContractFileId = contractFileService.generateSignedContractFile(contract, contractSignTaskMapper.selectByContractId(contractId));
+        contractMapper.updateSignedContractFile(contractId, signedContractFileId);
+        
+        // 13. 发送合同签署完成通知
+        notificationService.sendContractSignedNotification(contract.getPartyAId(), contract);
+        notificationService.sendContractSignedNotification(contract.getPartyBId(), contract);
+    }
+    
+    // 14. 记录合同操作日志
+    contractLogService.logContractOperation(
+            contractId,
+            ContractOperationType.SIGN,
+            (isPartyA ? "甲方" : "乙方") + "签署合同",
+            currentUserId);
+    
+    // 15. 构建返回结果
+    ContractSignResponse response = new ContractSignResponse();
+    response.setContractId(contractId);
+    response.setContractStatus(allSigned ? ContractStatus.SIGNED : ContractStatus.PENDING_SIGNING);
+    response.setSignTime(signTask.getSignTime());
+    
+    if (allSigned) {
+        response.setSignedUrl("/api/v1/contract/download/signed/" + contractId);
+    }
+    
+    log.info("合同签署成功, 合同ID: {}, 企业ID: {}", contractId, userEnterpriseId);
+    return ApiResult.success("合同签署成功", response);
+}
+```
+
+**处理逻辑**:
+1. 获取当前用户ID
+2. 查询合同信息
+3. 检查合同状态
+4. 获取用户所属企业
+5. 检查用户企业是否为合同相关方
+6. 查询签署任务
+7. 验证短信验证码
+8. 创建数字签名
+9. 更新签署任务状态
+10. 检查所有签署任务是否完成
+11. 如果所有方都已签署,更新合同状态为已签署
+12. 生成最终签署版合同
+13. 发送合同签署完成通知
+14. 记录合同操作日志
+15. 构建返回结果
+
+### 2.2 数字签名服务
+
+**主要实现类**:`DigitalSignatureServiceImpl`
+
+```java
+@Service
+@RequiredArgsConstructor
+public class DigitalSignatureServiceImpl implements DigitalSignatureService {
+
+    private final EnterpriseInfoMapper enterpriseInfoMapper;
+    
+    @Override
+    public String createSignature(String contractId, String enterpriseId, String signerName, String signerPosition) {
+        try {
+            // 1. 获取企业信息
+            EnterpriseInfo enterpriseInfo = enterpriseInfoMapper.selectById(enterpriseId);
+            if (enterpriseInfo == null) {
+                throw new BusinessException("企业信息不存在");
+            }
+            
+            // 2. 构建签名数据
+            Map<String, Object> signData = new HashMap<>();
+            signData.put("contractId", contractId);
+            signData.put("enterpriseId", enterpriseId);
+            signData.put("enterpriseName", enterpriseInfo.getEnterpriseName());
+            signData.put("signerName", signerName);
+            signData.put("signerPosition", signerPosition);
+            signData.put("signTime", System.currentTimeMillis());
+            
+            // 3. 创建数字签名
+            String signDataStr = JsonUtil.toJson(signData);
+            String signature = SignatureUtil.sign(signDataStr);
+            
+            return signature;
+        } catch (Exception e) {
+            throw new BusinessException("创建数字签名失败:" + e.getMessage());
+        }
+    }
+    
+    @Override
+    public boolean verifySignature(String signature, String contractId, String enterpriseId) {
+        try {
+            // 1. 获取企业信息
+            EnterpriseInfo enterpriseInfo = enterpriseInfoMapper.selectById(enterpriseId);
+            if (enterpriseInfo == null) {
+                return false;
+            }
+            
+            // 2. 获取签署记录
+            ContractSignTask signTask = contractSignTaskMapper.selectByContractAndEnterprise(contractId, enterpriseId);
+            if (signTask == null) {
+                return false;
+            }
+            
+            // 3. 验证签名
+            return SignatureUtil.verify(signature, signTask);
+        } catch (Exception e) {
+            return false;
+        }
+    }
+}
+```
+
+### 2.3 合同文件服务
+
+**主要实现类**:`ContractFileServiceImpl`
+
+```java
+@Service
+@RequiredArgsConstructor
+public class ContractFileServiceImpl implements ContractFileService {
+
+    private final FileStorageService fileStorageService;
+    private final ContractTemplateService contractTemplateService;
+    
+    @Override
+    public String generateContractFile(Contract contract) {
+        try {
+            // 1. 获取合同模板
+            String templateId = getTemplateIdByContractType(contract.getContractType());
+            byte[] templateContent = contractTemplateService.getTemplateContent(templateId);
+            
+            // 2. 填充合同内容
+            Map<String, Object> templateData = buildTemplateData(contract);
+            byte[] contractContent = contractTemplateService.fillTemplate(templateContent, templateData);
+            
+            // 3. 保存合同文件
+            String fileName = "合同_" + contract.getContractId() + ".pdf";
+            String fileId = fileStorageService.storeFile(contractContent, "application/pdf", fileName);
+            
+            return fileId;
+        } catch (Exception e) {
+            throw new BusinessException("生成合同文件失败:" + e.getMessage());
+        }
+    }
+    
+    @Override
+    public String generateSignedContractFile(Contract contract, List<ContractSignTask> signTasks) {
+        try {
+            // 1. 获取原合同文件
+            byte[] originalContract = fileStorageService.getFileContent(contract.getContractFileId());
+            
+            // 2. 添加签名信息
+            byte[] signedContract = addSignaturesToContract(originalContract, signTasks);
+            
+            // 3. 保存签署后的合同文件
+            String fileName = "已签署合同_" + contract.getContractId() + ".pdf";
+            String fileId = fileStorageService.storeFile(signedContract, "application/pdf", fileName);
+            
+            return fileId;
+        } catch (Exception e) {
+            throw new BusinessException("生成已签署合同文件失败:" + e.getMessage());
+        }
+    }
+    
+    @Override
+    public byte[] getContractFileContent(String fileId) {
+        return fileStorageService.getFileContent(fileId);
+    }
+    
+    private String getTemplateIdByContractType(ContractType contractType) {
+        switch (contractType) {
+            case PURCHASE:
+                return "TEMPLATE_PURCHASE";
+            case SALES:
+                return "TEMPLATE_SALES";
+            case FINANCING:
+                return "TEMPLATE_FINANCING";
+            default:
+                return "TEMPLATE_GENERAL";
+        }
+    }
+    
+    private Map<String, Object> buildTemplateData(Contract contract) {
+        Map<String, Object> data = new HashMap<>();
+        data.put("contractId", contract.getContractId());
+        data.put("contractName", contract.getContractName());
+        data.put("partyA", contract.getPartyAName());
+        data.put("partyB", contract.getPartyBName());
+        data.put("contractAmount", contract.getContractAmount());
+        data.put("startDate", DateUtil.formatDate(contract.getStartDate()));
+        data.put("endDate", DateUtil.formatDate(contract.getEndDate()));
+        data.put("contractPeriod", contract.getContractPeriod());
+        data.put("description", contract.getDescription());
+        data.put("createDate", DateUtil.formatDate(contract.getCreateTime()));
+        return data;
+    }
+    
+    private byte[] addSignaturesToContract(byte[] originalContract, List<ContractSignTask> signTasks) {
+        // 使用PDF库添加数字签名、签章等信息
+        // 此处省略具体PDF处理实现...
+        return originalContract;
+    }
+}
+```
+
+## 3. 数据访问层 (Mapper/Repository)
+
+### 3.1 合同数据访问
+
+**主要实现类**:`ContractMapper`
+
+```java
+@Mapper
+public interface ContractMapper {
+    /**
+     * 插入合同
+     */
+    int insert(Contract contract);
+    
+    /**
+     * 根据ID查询
+     */
+    Contract selectById(String contractId);
+    
+    /**
+     * 更新合同状态
+     */
+    int updateStatus(Contract contract);
+    
+    /**
+     * 更新合同文件ID
+     */
+    int updateContractFile(String contractId, String contractFileId);
+    
+    /**
+     * 更新已签署合同文件ID
+     */
+    int updateSignedContractFile(String contractId, String signedContractFileId);
+    
+    /**
+     * 查询企业相关的合同列表
+     */
+    List<Contract> selectByEnterpriseId(String enterpriseId, ContractQueryParam param);
+    
+    /**
+     * 统计企业相关的合同数量
+     */
+    int countByEnterpriseId(String enterpriseId, ContractQueryParam param);
+    
+    /**
+     * 删除合同
+     */
+    int deleteById(String contractId);
+}
+```
+
+### 3.2 合同签署任务数据访问
+
+**主要实现类**:`ContractSignTaskMapper`
+
+```java
+@Mapper
+public interface ContractSignTaskMapper {
+    /**
+     * 插入签署任务
+     */
+    int insert(ContractSignTask task);
+    
+    /**
+     * 更新签署任务
+     */
+    int update(ContractSignTask task);
+    
+    /**
+     * 根据合同ID和企业ID查询签署任务
+     */
+    ContractSignTask selectByContractAndEnterprise(String contractId, String enterpriseId);
+    
+    /**
+     * 根据合同ID查询所有签署任务
+     */
+    List<ContractSignTask> selectByContractId(String contractId);
+    
+    /**
+     * 检查合同是否已全部签署
+     */
+    boolean checkAllSigned(String contractId);
+    
+    /**
+     * 删除签署任务
+     */
+    int deleteByContractId(String contractId);
+}
+```
+
+### 3.3 合同操作日志数据访问
+
+**主要实现类**:`ContractLogMapper`
+
+```java
+@Mapper
+public interface ContractLogMapper {
+    /**
+     * 插入合同操作日志
+     */
+    int insert(ContractLog log);
+    
+    /**
+     * 根据合同ID查询操作日志
+     */
+    List<ContractLog> selectByContractId(String contractId);
+}
+``` 

+ 679 - 0
docs/contract-lifecycle-part3.md

@@ -0,0 +1,679 @@
+# 签约功能生命周期详解 - 第三部分
+
+## 4. 实体类层 (Entity/Model)
+
+### 4.1 合同实体
+
+```java
+@Data
+@Table(name = "t_contract")
+public class Contract implements Serializable {
+    
+    /**
+     * 合同ID
+     */
+    @Id
+    private String contractId;
+    
+    /**
+     * 合同名称
+     */
+    private String contractName;
+    
+    /**
+     * 合同类型
+     */
+    @Enumerated(EnumType.STRING)
+    private ContractType contractType;
+    
+    /**
+     * 合同状态
+     */
+    @Enumerated(EnumType.STRING)
+    private ContractStatus contractStatus;
+    
+    /**
+     * 甲方企业ID
+     */
+    private String partyAId;
+    
+    /**
+     * 甲方企业名称
+     */
+    private String partyAName;
+    
+    /**
+     * 乙方企业ID
+     */
+    private String partyBId;
+    
+    /**
+     * 乙方企业名称
+     */
+    private String partyBName;
+    
+    /**
+     * 合同金额
+     */
+    private BigDecimal contractAmount;
+    
+    /**
+     * 合同期限(天)
+     */
+    private Integer contractPeriod;
+    
+    /**
+     * 开始日期
+     */
+    private Date startDate;
+    
+    /**
+     * 结束日期
+     */
+    private Date endDate;
+    
+    /**
+     * 合同描述
+     */
+    private String description;
+    
+    /**
+     * 合同文件ID
+     */
+    private String contractFileId;
+    
+    /**
+     * 已签署合同文件ID
+     */
+    private String signedContractFileId;
+    
+    /**
+     * 创建用户
+     */
+    private String createUser;
+    
+    /**
+     * 创建时间
+     */
+    private Date createTime;
+    
+    /**
+     * 更新用户
+     */
+    private String updateUser;
+    
+    /**
+     * 更新时间
+     */
+    private Date updateTime;
+}
+```
+
+### 4.2 合同签署任务实体
+
+```java
+@Data
+@Table(name = "t_contract_sign_task")
+public class ContractSignTask implements Serializable {
+    
+    /**
+     * 任务ID
+     */
+    @Id
+    private String taskId;
+    
+    /**
+     * 合同ID
+     */
+    private String contractId;
+    
+    /**
+     * 企业ID
+     */
+    private String enterpriseId;
+    
+    /**
+     * 企业名称
+     */
+    private String enterpriseName;
+    
+    /**
+     * 签署状态
+     */
+    @Enumerated(EnumType.STRING)
+    private SignStatus signStatus;
+    
+    /**
+     * 签署人姓名
+     */
+    private String signerName;
+    
+    /**
+     * 签署人职位
+     */
+    private String signerPosition;
+    
+    /**
+     * 签署方法
+     */
+    @Enumerated(EnumType.STRING)
+    private SignMethod signMethod;
+    
+    /**
+     * 数字签名
+     */
+    private String signature;
+    
+    /**
+     * 签署时间
+     */
+    private Date signTime;
+    
+    /**
+     * 创建时间
+     */
+    private Date createTime;
+    
+    /**
+     * 更新时间
+     */
+    private Date updateTime;
+}
+```
+
+### 4.3 合同操作日志实体
+
+```java
+@Data
+@Table(name = "t_contract_log")
+public class ContractLog implements Serializable {
+    
+    /**
+     * 日志ID
+     */
+    @Id
+    private String logId;
+    
+    /**
+     * 合同ID
+     */
+    private String contractId;
+    
+    /**
+     * 操作类型
+     */
+    @Enumerated(EnumType.STRING)
+    private ContractOperationType operationType;
+    
+    /**
+     * 操作描述
+     */
+    private String operationDesc;
+    
+    /**
+     * 操作用户
+     */
+    private String operationUser;
+    
+    /**
+     * 操作时间
+     */
+    private Date operationTime;
+    
+    /**
+     * IP地址
+     */
+    private String ip;
+}
+```
+
+### 4.4 枚举类
+
+#### 4.4.1 合同状态枚举
+
+```java
+/**
+ * 合同状态枚举
+ */
+public enum ContractStatus {
+    /**
+     * 草稿
+     */
+    DRAFT,
+    
+    /**
+     * 待签署
+     */
+    PENDING_SIGNING,
+    
+    /**
+     * 已签署
+     */
+    SIGNED,
+    
+    /**
+     * 已完成
+     */
+    COMPLETED,
+    
+    /**
+     * 已取消
+     */
+    CANCELED,
+    
+    /**
+     * 已过期
+     */
+    EXPIRED
+}
+```
+
+#### 4.4.2 合同类型枚举
+
+```java
+/**
+ * 合同类型枚举
+ */
+public enum ContractType {
+    /**
+     * 采购合同
+     */
+    PURCHASE,
+    
+    /**
+     * 销售合同
+     */
+    SALES,
+    
+    /**
+     * 融资合同
+     */
+    FINANCING,
+    
+    /**
+     * 一般合同
+     */
+    GENERAL
+}
+```
+
+#### 4.4.3 签署状态枚举
+
+```java
+/**
+ * 签署状态枚举
+ */
+public enum SignStatus {
+    /**
+     * 待签署
+     */
+    PENDING,
+    
+    /**
+     * 已签署
+     */
+    SIGNED,
+    
+    /**
+     * 已拒绝
+     */
+    REJECTED
+}
+```
+
+#### 4.4.4 签署方法枚举
+
+```java
+/**
+ * 签署方法枚举
+ */
+public enum SignMethod {
+    /**
+     * 数字签名
+     */
+    DIGITAL_SIGNATURE,
+    
+    /**
+     * 电子签章
+     */
+    ELECTRONIC_SEAL,
+    
+    /**
+     * 手写签名
+     */
+    HANDWRITTEN
+}
+```
+
+#### 4.4.5 合同操作类型枚举
+
+```java
+/**
+ * 合同操作类型枚举
+ */
+public enum ContractOperationType {
+    /**
+     * 创建
+     */
+    CREATE,
+    
+    /**
+     * 提交签署
+     */
+    SUBMIT,
+    
+    /**
+     * 签署
+     */
+    SIGN,
+    
+    /**
+     * 拒绝签署
+     */
+    REJECT,
+    
+    /**
+     * 取消
+     */
+    CANCEL,
+    
+    /**
+     * 完成
+     */
+    COMPLETE,
+    
+    /**
+     * 下载
+     */
+    DOWNLOAD,
+    
+    /**
+     * 查看
+     */
+    VIEW
+}
+```
+
+## 5. 异常处理
+
+### 5.1 合同业务异常
+
+```java
+public class ContractBusinessException extends BusinessException {
+    
+    /**
+     * 合同不存在
+     */
+    public static final int CONTRACT_NOT_FOUND = 4001;
+    
+    /**
+     * 合同状态错误
+     */
+    public static final int CONTRACT_STATUS_ERROR = 4002;
+    
+    /**
+     * 无权操作
+     */
+    public static final int NO_PERMISSION = 4003;
+    
+    /**
+     * 签署信息错误
+     */
+    public static final int SIGN_INFO_ERROR = 4004;
+    
+    /**
+     * 签署任务不存在
+     */
+    public static final int SIGN_TASK_NOT_FOUND = 4005;
+    
+    /**
+     * 构造函数
+     *
+     * @param code 错误码
+     * @param message 错误信息
+     */
+    public ContractBusinessException(int code, String message) {
+        super(code, message);
+    }
+}
+```
+
+### 5.2 合同异常处理器
+
+```java
+@RestControllerAdvice
+@Slf4j
+public class ContractExceptionHandler {
+    
+    /**
+     * 处理合同业务异常
+     */
+    @ExceptionHandler(ContractBusinessException.class)
+    public ApiResult<String> handleContractBusinessException(ContractBusinessException ex) {
+        log.warn("合同业务异常: {}", ex.getMessage());
+        return ApiResult.failed(ex.getCode(), ex.getMessage());
+    }
+    
+    /**
+     * 处理合同文件异常
+     */
+    @ExceptionHandler(ContractFileException.class)
+    public ApiResult<String> handleContractFileException(ContractFileException ex) {
+        log.error("合同文件处理异常", ex);
+        return ApiResult.failed(ex.getCode(), ex.getMessage());
+    }
+}
+```
+
+## 6. 缓存层
+
+### 6.1 合同缓存服务
+
+```java
+@Service
+@RequiredArgsConstructor
+@Slf4j
+public class ContractCacheServiceImpl implements ContractCacheService {
+    
+    private final RedisTemplate<String, Object> redisTemplate;
+    
+    private static final String CONTRACT_CACHE_KEY_PREFIX = "contract:detail:";
+    private static final long CONTRACT_CACHE_EXPIRE_TIME = 3600L; // 一小时
+    
+    @Override
+    public void cacheContract(Contract contract) {
+        if (contract == null) {
+            return;
+        }
+        
+        String key = CONTRACT_CACHE_KEY_PREFIX + contract.getContractId();
+        redisTemplate.opsForValue().set(key, contract, CONTRACT_CACHE_EXPIRE_TIME, TimeUnit.SECONDS);
+        log.debug("缓存合同信息, ID: {}", contract.getContractId());
+    }
+    
+    @Override
+    public Contract getContractFromCache(String contractId) {
+        String key = CONTRACT_CACHE_KEY_PREFIX + contractId;
+        Object cachedContract = redisTemplate.opsForValue().get(key);
+        
+        if (cachedContract instanceof Contract) {
+            log.debug("从缓存获取合同信息, ID: {}", contractId);
+            return (Contract) cachedContract;
+        }
+        
+        return null;
+    }
+    
+    @Override
+    public void removeContractCache(String contractId) {
+        String key = CONTRACT_CACHE_KEY_PREFIX + contractId;
+        redisTemplate.delete(key);
+        log.debug("移除合同缓存, ID: {}", contractId);
+    }
+}
+```
+
+## 7. 完整签约流程时序
+
+```
+┌──────────┐     ┌───────────────┐     ┌─────────────┐     ┌──────────┐     ┌───────────┐     ┌─────────┐
+│  客户端  │     │  控制器层     │     │  服务层     │     │ 数据访问层│     │ 文件服务  │     │ 缓存层  │
+└────┬─────┘     └───────┬───────┘     └──────┬──────┘     └────┬─────┘     └─────┬─────┘     └────┬────┘
+     │                   │                     │                 │                 │                │
+     │ 1.创建合同请求    │                     │                 │                 │                │
+     │──────────────────>│                     │                 │                 │                │
+     │                   │                     │                 │                 │                │
+     │                   │ 2.调用创建合同服务  │                 │                 │                │
+     │                   │────────────────────>│                 │                 │                │
+     │                   │                     │                 │                 │                │
+     │                   │                     │ 3.校验企业信息  │                 │                │
+     │                   │                     │────────────────>│                 │                │
+     │                   │                     │                 │                 │                │
+     │                   │                     │ 4.返回校验结果  │                 │                │
+     │                   │                     │<────────────────│                 │                │
+     │                   │                     │                 │                 │                │
+     │                   │                     │ 5.保存合同基本信息                │                │
+     │                   │                     │────────────────>│                 │                │
+     │                   │                     │                 │                 │                │
+     │                   │                     │ 6.生成合同文件  │                 │                │
+     │                   │                     │──────────────────────────────────>│                │
+     │                   │                     │                 │                 │                │
+     │                   │                     │ 7.返回文件ID    │                 │                │
+     │                   │                     │<──────────────────────────────────│                │
+     │                   │                     │                 │                 │                │
+     │                   │                     │ 8.更新合同文件ID│                 │                │
+     │                   │                     │────────────────>│                 │                │
+     │                   │                     │                 │                 │                │
+     │                   │                     │ 9.缓存合同信息  │                 │                │
+     │                   │                     │──────────────────────────────────────────────────>│
+     │                   │                     │                 │                 │                │
+     │                   │ 10.返回创建结果    │                 │                 │                │
+     │                   │<────────────────────│                 │                 │                │
+     │                   │                     │                 │                 │                │
+     │ 11.返回合同ID和下载链接                │                 │                 │                │
+     │<──────────────────│                     │                 │                 │                │
+     │                   │                     │                 │                 │                │
+     │ 12.提交合同签署请求                     │                 │                 │                │
+     │──────────────────>│                     │                 │                 │                │
+     │                   │                     │                 │                 │                │
+     │                   │ 13.调用提交签署服务 │                 │                 │                │
+     │                   │────────────────────>│                 │                 │                │
+     │                   │                     │                 │                 │                │
+     │                   │                     │ 14.查询合同信息 │                 │                │
+     │                   │                     │────────────────>│                 │                │
+     │                   │                     │                 │                 │                │
+     │                   │                     │ 15.更新合同状态 │                 │                │
+     │                   │                     │────────────────>│                 │                │
+     │                   │                     │                 │                 │                │
+     │                   │                     │ 16.创建签署任务 │                 │                │
+     │                   │                     │────────────────>│                 │                │
+     │                   │                     │                 │                 │                │
+     │                   │ 17.返回提交结果    │                 │                 │                │
+     │                   │<────────────────────│                 │                 │                │
+     │                   │                     │                 │                 │                │
+     │ 18.返回签署链接   │                     │                 │                 │                │
+     │<──────────────────│                     │                 │                 │                │
+     │                   │                     │                 │                 │                │
+     │ 19.甲方签署请求   │                     │                 │                 │                │
+     │──────────────────>│                     │                 │                 │                │
+     │                   │                     │                 │                 │                │
+     │                   │ 20.调用签署服务    │                 │                 │                │
+     │                   │────────────────────>│                 │                 │                │
+     │                   │                     │                 │                 │                │
+     │                   │                     │ 21.验证签署权限 │                 │                │
+     │                   │                     │────────────────>│                 │                │
+     │                   │                     │                 │                 │                │
+     │                   │                     │ 22.创建数字签名 │                 │                │
+     │                   │                     │──────────────────────────────────>│                │
+     │                   │                     │                 │                 │                │
+     │                   │                     │ 23.更新签署状态 │                 │                │
+     │                   │                     │────────────────>│                 │                │
+     │                   │                     │                 │                 │                │
+     │                   │ 24.返回签署结果    │                 │                 │                │
+     │                   │<────────────────────│                 │                 │                │
+     │                   │                     │                 │                 │                │
+     │ 25.返回甲方签署成功                     │                 │                 │                │
+     │<──────────────────│                     │                 │                 │                │
+     │                   │                     │                 │                 │                │
+     │ 26.乙方签署请求   │                     │                 │                 │                │
+     │──────────────────>│                     │                 │                 │                │
+     │                   │                     │                 │                 │                │
+     │                   │ 27.调用签署服务    │                 │                 │                │
+     │                   │────────────────────>│                 │                 │                │
+     │                   │                     │                 │                 │                │
+     │                   │                     │ 28.验证签署权限 │                 │                │
+     │                   │                     │────────────────>│                 │                │
+     │                   │                     │                 │                 │                │
+     │                   │                     │ 29.创建数字签名 │                 │                │
+     │                   │                     │──────────────────────────────────>│                │
+     │                   │                     │                 │                 │                │
+     │                   │                     │ 30.更新签署状态 │                 │                │
+     │                   │                     │────────────────>│                 │                │
+     │                   │                     │                 │                 │                │
+     │                   │                     │ 31.检查是否全部签署                │                │
+     │                   │                     │────────────────>│                 │                │
+     │                   │                     │                 │                 │                │
+     │                   │                     │ 32.更新合同为已签署               │                │
+     │                   │                     │────────────────>│                 │                │
+     │                   │                     │                 │                 │                │
+     │                   │                     │ 33.生成签署版合同                 │                │
+     │                   │                     │──────────────────────────────────>│                │
+     │                   │                     │                 │                 │                │
+     │                   │ 34.返回签署结果    │                 │                 │                │
+     │                   │<────────────────────│                 │                 │                │
+     │                   │                     │                 │                 │                │
+     │ 35.返回合同签署完成                     │                 │                 │                │
+     │<──────────────────│                     │                 │                 │                │
+     │                   │                     │                 │                 │                │
+```
+
+## 8. 安全控制
+
+### 8.1 合同接口授权配置
+
+```java
+@Configuration
+@EnableWebSecurity
+@EnableMethodSecurity
+@RequiredArgsConstructor
+public class ContractSecurityConfig {
+
+    /**
+     * 配置合同相关接口的安全规则
+     */
+    @Bean
+    public SecurityFilterChain contractFilterChain(HttpSecurity http) throws Exception {
+        http
+            .authorizeHttpRequests(authorize -> authorize
+                // 创建合同接口,企业用户可访问
+                .requestMatchers(HttpMethod.POST, "/api/v1/contract/create").hasRole("ENTERPRISE")
+                // 提交签署接口,企业用户可访问
+                .requestMatchers(HttpMethod.POST, "/api/v1/contract/*/submit").hasRole("ENTERPRISE")
+                // 签署合同接口,企业用户可访问
+                .requestMatchers(HttpMethod.POST, "/api/v1/contract/*/sign").hasRole("ENTERPRISE")
+                // 查询合同详情,企业用户和银行用户可访问
+                .requestMatchers(HttpMethod.GET, "/api/v1/contract/*").hasAnyRole("ENTERPRISE", "BANK")
+                // 查询合同列表,企业用户和银行用户可访问
+                .requestMatchers(HttpMethod.GET, "/api/v1/contract/list").hasAnyRole("ENTERPRISE", "BANK")
+                // 下载合同文件,企业用户和银行用户可访问
+                .requestMatchers(HttpMethod.GET, "/api/v1/contract/download/**").hasAnyRole("ENTERPRISE", "BANK")
+            );
+        
+        return http.build();
+    }
+}
+```
+
+## 9. 完整签约功能总结
+
+电子合同签署功能是供应链金融平台中的核心功能之一,为供应链参与方提供了便捷、高效、合法的合同签署方式。本文档详细描述了从合同创建到最终签署的完整生命周期,包括:
+
+1. **控制器层**:负责接收HTTP请求,进行初步参数校验,调用服务层处理业务逻辑,并将处理结果返回给客户端。
+2. **服务层**:包含核心业务逻辑,如合同创建、提交签署、签署处理等。
+3. **数据访问层**:负责与数据库交互,提供数据持久化服务。
+4. **实体类层**:定义了系统中的核心数据模型,如合同、签署任务等。
+5. **异常处理**:统一处理业务异常,提供友好的错误信息。
+6. **缓存层**:优化系统性能,减少数据库访问。
+7. **安全控制**:确保接口安全可控,防止未授权访问。
+
+整个签约流程基于数字签名技术,确保了合同的法律效力和防篡改性。同时,系统通过完善的日志记录和状态追踪,保证了合同处理过程的透明性和可追溯性。 

+ 145 - 0
docs/system-architecture.md

@@ -0,0 +1,145 @@
+# 供应链金融平台系统架构
+
+## 整体架构
+
+供应链金融平台采用基于Spring Cloud的微服务架构,系统各组件之间通过RESTful API和消息队列进行通信。整体架构如下图所示:
+
+```
+┌───────────────────────────────────────────────────────────────────┐
+│                            客户端层                                │
+│                                                                   │
+│    ┌─────────────┐    ┌─────────────┐    ┌─────────────────┐     │
+│    │   Web端     │    │  Android端  │    │     iOS端       │     │
+│    │  (UniApp)   │    │  (UniApp)   │    │    (UniApp)     │     │
+│    └─────────────┘    └─────────────┘    └─────────────────┘     │
+└───────────────────────────────────────────────────────────────────┘
+                            ▲
+                            │
+                            ▼
+┌───────────────────────────────────────────────────────────────────┐
+│                           API网关层                               │
+│                                                                   │
+│                      Spring Cloud Gateway                         │
+│                                                                   │
+└───────────────────────────────────────────────────────────────────┘
+                            ▲
+                            │
+                            ▼
+┌───────────────────────────────────────────────────────────────────┐
+│                          微服务业务层                             │
+│                                                                   │
+│  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐  ┌──────────┐ │
+│  │  认证服务   │  │  签约服务   │  │  风险服务   │  │ 其他服务 │ │
+│  └─────────────┘  └─────────────┘  └─────────────┘  └──────────┘ │
+│                                                                   │
+└───────────────────────────────────────────────────────────────────┘
+                            ▲
+                            │
+                            ▼
+┌───────────────────────────────────────────────────────────────────┐
+│                          公共服务层                               │
+│                                                                   │
+│  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐  ┌──────────┐ │
+│  │ 服务注册中心│  │ 配置中心    │  │ 链路追踪    │  │ 日志中心 │ │
+│  │  (Nacos)    │  │  (Nacos)    │  │  (SkyWalking)│  │ (ELK)   │ │
+│  └─────────────┘  └─────────────┘  └─────────────┘  └──────────┘ │
+│                                                                   │
+└───────────────────────────────────────────────────────────────────┘
+                            ▲
+                            │
+                            ▼
+┌───────────────────────────────────────────────────────────────────┐
+│                          数据存储层                               │
+│                                                                   │
+│     ┌─────────────┐    ┌─────────────┐    ┌─────────────┐        │
+│     │  MySQL      │    │   Redis     │    │  MinIO      │        │
+│     │ (业务数据)  │    │ (缓存/会话) │    │ (文件存储)  │        │
+│     └─────────────┘    └─────────────┘    └─────────────┘        │
+│                                                                   │
+└───────────────────────────────────────────────────────────────────┘
+```
+
+## 核心服务说明
+
+### 1. 认证服务 (gulop-member-auth)
+
+认证服务负责用户身份验证和授权管理,主要功能包括:
+
+- 用户注册与登录
+- 身份验证(JWT Token管理)
+- 权限管理
+- 企业实名认证
+- 角色与权限配置
+
+技术实现:
+- Spring Security
+- JWT认证
+- OAuth2.0集成(可选)
+
+### 2. 签约服务
+
+签约服务负责电子合同的生成、签署和管理,主要功能包括:
+
+- 合同模板管理
+- 电子合同生成
+- 在线签署
+- 合同存储与查询
+- 合同有效性验证
+
+技术实现:
+- 电子签章集成
+- PDF生成与处理
+- 区块链存证(可选)
+
+### 3. 风险服务
+
+风险服务负责评估交易风险,包括:
+
+- 信用评分计算
+- 风险等级评估
+- 欺诈检测
+- 交易审核
+
+技术实现:
+- 规则引擎
+- 风险模型评估
+- 异常交易检测
+
+## 数据库设计
+
+系统使用MySQL作为主要的关系型数据库,主要数据模型包括:
+
+1. 用户与账户数据
+2. 企业信息数据
+3. 合同数据
+4. 交易数据
+5. 风险评估数据
+
+数据库采用分库分表设计,按业务领域进行垂直拆分,高并发表采用水平分片。
+
+## 安全设计
+
+系统安全设计包括:
+
+1. 传输层安全:HTTPS加密通信
+2. 应用层安全:
+   - 参数验证与防SQL注入
+   - XSS防护
+   - CSRF防护
+3. 认证与授权:
+   - JWT Token认证
+   - 基于角色的访问控制
+4. 数据安全:
+   - 敏感数据加密存储
+   - 数据脱敏展示
+   - 操作日志审计
+
+## 可扩展性设计
+
+系统可扩展性主要体现在:
+
+1. 微服务架构,支持服务独立扩展
+2. 无状态设计,支持水平扩展
+3. 消息队列解耦,支持异步处理
+4. 配置中心化,支持动态配置调整
+5. 服务发现,支持服务动态扩缩容 

文件差異過大導致無法顯示
+ 57 - 0
java3000.sql


+ 259 - 0
mvnw

@@ -0,0 +1,259 @@
+#!/bin/sh
+# ----------------------------------------------------------------------------
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+# ----------------------------------------------------------------------------
+
+# ----------------------------------------------------------------------------
+# Apache Maven Wrapper startup batch script, version 3.3.2
+#
+# Optional ENV vars
+# -----------------
+#   JAVA_HOME - location of a JDK home dir, required when download maven via java source
+#   MVNW_REPOURL - repo url base for downloading maven distribution
+#   MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven
+#   MVNW_VERBOSE - true: enable verbose log; debug: trace the mvnw script; others: silence the output
+# ----------------------------------------------------------------------------
+
+set -euf
+[ "${MVNW_VERBOSE-}" != debug ] || set -x
+
+# OS specific support.
+native_path() { printf %s\\n "$1"; }
+case "$(uname)" in
+CYGWIN* | MINGW*)
+  [ -z "${JAVA_HOME-}" ] || JAVA_HOME="$(cygpath --unix "$JAVA_HOME")"
+  native_path() { cygpath --path --windows "$1"; }
+  ;;
+esac
+
+# set JAVACMD and JAVACCMD
+set_java_home() {
+  # For Cygwin and MinGW, ensure paths are in Unix format before anything is touched
+  if [ -n "${JAVA_HOME-}" ]; then
+    if [ -x "$JAVA_HOME/jre/sh/java" ]; then
+      # IBM's JDK on AIX uses strange locations for the executables
+      JAVACMD="$JAVA_HOME/jre/sh/java"
+      JAVACCMD="$JAVA_HOME/jre/sh/javac"
+    else
+      JAVACMD="$JAVA_HOME/bin/java"
+      JAVACCMD="$JAVA_HOME/bin/javac"
+
+      if [ ! -x "$JAVACMD" ] || [ ! -x "$JAVACCMD" ]; then
+        echo "The JAVA_HOME environment variable is not defined correctly, so mvnw cannot run." >&2
+        echo "JAVA_HOME is set to \"$JAVA_HOME\", but \"\$JAVA_HOME/bin/java\" or \"\$JAVA_HOME/bin/javac\" does not exist." >&2
+        return 1
+      fi
+    fi
+  else
+    JAVACMD="$(
+      'set' +e
+      'unset' -f command 2>/dev/null
+      'command' -v java
+    )" || :
+    JAVACCMD="$(
+      'set' +e
+      'unset' -f command 2>/dev/null
+      'command' -v javac
+    )" || :
+
+    if [ ! -x "${JAVACMD-}" ] || [ ! -x "${JAVACCMD-}" ]; then
+      echo "The java/javac command does not exist in PATH nor is JAVA_HOME set, so mvnw cannot run." >&2
+      return 1
+    fi
+  fi
+}
+
+# hash string like Java String::hashCode
+hash_string() {
+  str="${1:-}" h=0
+  while [ -n "$str" ]; do
+    char="${str%"${str#?}"}"
+    h=$(((h * 31 + $(LC_CTYPE=C printf %d "'$char")) % 4294967296))
+    str="${str#?}"
+  done
+  printf %x\\n $h
+}
+
+verbose() { :; }
+[ "${MVNW_VERBOSE-}" != true ] || verbose() { printf %s\\n "${1-}"; }
+
+die() {
+  printf %s\\n "$1" >&2
+  exit 1
+}
+
+trim() {
+  # MWRAPPER-139:
+  #   Trims trailing and leading whitespace, carriage returns, tabs, and linefeeds.
+  #   Needed for removing poorly interpreted newline sequences when running in more
+  #   exotic environments such as mingw bash on Windows.
+  printf "%s" "${1}" | tr -d '[:space:]'
+}
+
+# parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties
+while IFS="=" read -r key value; do
+  case "${key-}" in
+  distributionUrl) distributionUrl=$(trim "${value-}") ;;
+  distributionSha256Sum) distributionSha256Sum=$(trim "${value-}") ;;
+  esac
+done <"${0%/*}/.mvn/wrapper/maven-wrapper.properties"
+[ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in ${0%/*}/.mvn/wrapper/maven-wrapper.properties"
+
+case "${distributionUrl##*/}" in
+maven-mvnd-*bin.*)
+  MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/
+  case "${PROCESSOR_ARCHITECTURE-}${PROCESSOR_ARCHITEW6432-}:$(uname -a)" in
+  *AMD64:CYGWIN* | *AMD64:MINGW*) distributionPlatform=windows-amd64 ;;
+  :Darwin*x86_64) distributionPlatform=darwin-amd64 ;;
+  :Darwin*arm64) distributionPlatform=darwin-aarch64 ;;
+  :Linux*x86_64*) distributionPlatform=linux-amd64 ;;
+  *)
+    echo "Cannot detect native platform for mvnd on $(uname)-$(uname -m), use pure java version" >&2
+    distributionPlatform=linux-amd64
+    ;;
+  esac
+  distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip"
+  ;;
+maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;;
+*) MVN_CMD="mvn${0##*/mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;;
+esac
+
+# apply MVNW_REPOURL and calculate MAVEN_HOME
+# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-<version>,maven-mvnd-<version>-<platform>}/<hash>
+[ -z "${MVNW_REPOURL-}" ] || distributionUrl="$MVNW_REPOURL$_MVNW_REPO_PATTERN${distributionUrl#*"$_MVNW_REPO_PATTERN"}"
+distributionUrlName="${distributionUrl##*/}"
+distributionUrlNameMain="${distributionUrlName%.*}"
+distributionUrlNameMain="${distributionUrlNameMain%-bin}"
+MAVEN_USER_HOME="${MAVEN_USER_HOME:-${HOME}/.m2}"
+MAVEN_HOME="${MAVEN_USER_HOME}/wrapper/dists/${distributionUrlNameMain-}/$(hash_string "$distributionUrl")"
+
+exec_maven() {
+  unset MVNW_VERBOSE MVNW_USERNAME MVNW_PASSWORD MVNW_REPOURL || :
+  exec "$MAVEN_HOME/bin/$MVN_CMD" "$@" || die "cannot exec $MAVEN_HOME/bin/$MVN_CMD"
+}
+
+if [ -d "$MAVEN_HOME" ]; then
+  verbose "found existing MAVEN_HOME at $MAVEN_HOME"
+  exec_maven "$@"
+fi
+
+case "${distributionUrl-}" in
+*?-bin.zip | *?maven-mvnd-?*-?*.zip) ;;
+*) die "distributionUrl is not valid, must match *-bin.zip or maven-mvnd-*.zip, but found '${distributionUrl-}'" ;;
+esac
+
+# prepare tmp dir
+if TMP_DOWNLOAD_DIR="$(mktemp -d)" && [ -d "$TMP_DOWNLOAD_DIR" ]; then
+  clean() { rm -rf -- "$TMP_DOWNLOAD_DIR"; }
+  trap clean HUP INT TERM EXIT
+else
+  die "cannot create temp dir"
+fi
+
+mkdir -p -- "${MAVEN_HOME%/*}"
+
+# Download and Install Apache Maven
+verbose "Couldn't find MAVEN_HOME, downloading and installing it ..."
+verbose "Downloading from: $distributionUrl"
+verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName"
+
+# select .zip or .tar.gz
+if ! command -v unzip >/dev/null; then
+  distributionUrl="${distributionUrl%.zip}.tar.gz"
+  distributionUrlName="${distributionUrl##*/}"
+fi
+
+# verbose opt
+__MVNW_QUIET_WGET=--quiet __MVNW_QUIET_CURL=--silent __MVNW_QUIET_UNZIP=-q __MVNW_QUIET_TAR=''
+[ "${MVNW_VERBOSE-}" != true ] || __MVNW_QUIET_WGET='' __MVNW_QUIET_CURL='' __MVNW_QUIET_UNZIP='' __MVNW_QUIET_TAR=v
+
+# normalize http auth
+case "${MVNW_PASSWORD:+has-password}" in
+'') MVNW_USERNAME='' MVNW_PASSWORD='' ;;
+has-password) [ -n "${MVNW_USERNAME-}" ] || MVNW_USERNAME='' MVNW_PASSWORD='' ;;
+esac
+
+if [ -z "${MVNW_USERNAME-}" ] && command -v wget >/dev/null; then
+  verbose "Found wget ... using wget"
+  wget ${__MVNW_QUIET_WGET:+"$__MVNW_QUIET_WGET"} "$distributionUrl" -O "$TMP_DOWNLOAD_DIR/$distributionUrlName" || die "wget: Failed to fetch $distributionUrl"
+elif [ -z "${MVNW_USERNAME-}" ] && command -v curl >/dev/null; then
+  verbose "Found curl ... using curl"
+  curl ${__MVNW_QUIET_CURL:+"$__MVNW_QUIET_CURL"} -f -L -o "$TMP_DOWNLOAD_DIR/$distributionUrlName" "$distributionUrl" || die "curl: Failed to fetch $distributionUrl"
+elif set_java_home; then
+  verbose "Falling back to use Java to download"
+  javaSource="$TMP_DOWNLOAD_DIR/Downloader.java"
+  targetZip="$TMP_DOWNLOAD_DIR/$distributionUrlName"
+  cat >"$javaSource" <<-END
+	public class Downloader extends java.net.Authenticator
+	{
+	  protected java.net.PasswordAuthentication getPasswordAuthentication()
+	  {
+	    return new java.net.PasswordAuthentication( System.getenv( "MVNW_USERNAME" ), System.getenv( "MVNW_PASSWORD" ).toCharArray() );
+	  }
+	  public static void main( String[] args ) throws Exception
+	  {
+	    setDefault( new Downloader() );
+	    java.nio.file.Files.copy( java.net.URI.create( args[0] ).toURL().openStream(), java.nio.file.Paths.get( args[1] ).toAbsolutePath().normalize() );
+	  }
+	}
+	END
+  # For Cygwin/MinGW, switch paths to Windows format before running javac and java
+  verbose " - Compiling Downloader.java ..."
+  "$(native_path "$JAVACCMD")" "$(native_path "$javaSource")" || die "Failed to compile Downloader.java"
+  verbose " - Running Downloader.java ..."
+  "$(native_path "$JAVACMD")" -cp "$(native_path "$TMP_DOWNLOAD_DIR")" Downloader "$distributionUrl" "$(native_path "$targetZip")"
+fi
+
+# If specified, validate the SHA-256 sum of the Maven distribution zip file
+if [ -n "${distributionSha256Sum-}" ]; then
+  distributionSha256Result=false
+  if [ "$MVN_CMD" = mvnd.sh ]; then
+    echo "Checksum validation is not supported for maven-mvnd." >&2
+    echo "Please disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2
+    exit 1
+  elif command -v sha256sum >/dev/null; then
+    if echo "$distributionSha256Sum  $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c >/dev/null 2>&1; then
+      distributionSha256Result=true
+    fi
+  elif command -v shasum >/dev/null; then
+    if echo "$distributionSha256Sum  $TMP_DOWNLOAD_DIR/$distributionUrlName" | shasum -a 256 -c >/dev/null 2>&1; then
+      distributionSha256Result=true
+    fi
+  else
+    echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2
+    echo "Please install either command, or disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2
+    exit 1
+  fi
+  if [ $distributionSha256Result = false ]; then
+    echo "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised." >&2
+    echo "If you updated your Maven version, you need to update the specified distributionSha256Sum property." >&2
+    exit 1
+  fi
+fi
+
+# unzip and move
+if command -v unzip >/dev/null; then
+  unzip ${__MVNW_QUIET_UNZIP:+"$__MVNW_QUIET_UNZIP"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -d "$TMP_DOWNLOAD_DIR" || die "failed to unzip"
+else
+  tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed to untar"
+fi
+printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/mvnw.url"
+mv -- "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME"
+
+clean || :
+exec_maven "$@"

+ 149 - 0
mvnw.cmd

@@ -0,0 +1,149 @@
+<# : batch portion
+@REM ----------------------------------------------------------------------------
+@REM Licensed to the Apache Software Foundation (ASF) under one
+@REM or more contributor license agreements.  See the NOTICE file
+@REM distributed with this work for additional information
+@REM regarding copyright ownership.  The ASF licenses this file
+@REM to you under the Apache License, Version 2.0 (the
+@REM "License"); you may not use this file except in compliance
+@REM with the License.  You may obtain a copy of the License at
+@REM
+@REM    http://www.apache.org/licenses/LICENSE-2.0
+@REM
+@REM Unless required by applicable law or agreed to in writing,
+@REM software distributed under the License is distributed on an
+@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+@REM KIND, either express or implied.  See the License for the
+@REM specific language governing permissions and limitations
+@REM under the License.
+@REM ----------------------------------------------------------------------------
+
+@REM ----------------------------------------------------------------------------
+@REM Apache Maven Wrapper startup batch script, version 3.3.2
+@REM
+@REM Optional ENV vars
+@REM   MVNW_REPOURL - repo url base for downloading maven distribution
+@REM   MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven
+@REM   MVNW_VERBOSE - true: enable verbose log; others: silence the output
+@REM ----------------------------------------------------------------------------
+
+@IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0)
+@SET __MVNW_CMD__=
+@SET __MVNW_ERROR__=
+@SET __MVNW_PSMODULEP_SAVE=%PSModulePath%
+@SET PSModulePath=
+@FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @(
+  IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B)
+)
+@SET PSModulePath=%__MVNW_PSMODULEP_SAVE%
+@SET __MVNW_PSMODULEP_SAVE=
+@SET __MVNW_ARG0_NAME__=
+@SET MVNW_USERNAME=
+@SET MVNW_PASSWORD=
+@IF NOT "%__MVNW_CMD__%"=="" (%__MVNW_CMD__% %*)
+@echo Cannot start maven from wrapper >&2 && exit /b 1
+@GOTO :EOF
+: end batch / begin powershell #>
+
+$ErrorActionPreference = "Stop"
+if ($env:MVNW_VERBOSE -eq "true") {
+  $VerbosePreference = "Continue"
+}
+
+# calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties
+$distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl
+if (!$distributionUrl) {
+  Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties"
+}
+
+switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) {
+  "maven-mvnd-*" {
+    $USE_MVND = $true
+    $distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip"
+    $MVN_CMD = "mvnd.cmd"
+    break
+  }
+  default {
+    $USE_MVND = $false
+    $MVN_CMD = $script -replace '^mvnw','mvn'
+    break
+  }
+}
+
+# apply MVNW_REPOURL and calculate MAVEN_HOME
+# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-<version>,maven-mvnd-<version>-<platform>}/<hash>
+if ($env:MVNW_REPOURL) {
+  $MVNW_REPO_PATTERN = if ($USE_MVND) { "/org/apache/maven/" } else { "/maven/mvnd/" }
+  $distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace '^.*'+$MVNW_REPO_PATTERN,'')"
+}
+$distributionUrlName = $distributionUrl -replace '^.*/',''
+$distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$',''
+$MAVEN_HOME_PARENT = "$HOME/.m2/wrapper/dists/$distributionUrlNameMain"
+if ($env:MAVEN_USER_HOME) {
+  $MAVEN_HOME_PARENT = "$env:MAVEN_USER_HOME/wrapper/dists/$distributionUrlNameMain"
+}
+$MAVEN_HOME_NAME = ([System.Security.Cryptography.MD5]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join ''
+$MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME"
+
+if (Test-Path -Path "$MAVEN_HOME" -PathType Container) {
+  Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME"
+  Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD"
+  exit $?
+}
+
+if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) {
+  Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl"
+}
+
+# prepare tmp dir
+$TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile
+$TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir"
+$TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null
+trap {
+  if ($TMP_DOWNLOAD_DIR.Exists) {
+    try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null }
+    catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" }
+  }
+}
+
+New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null
+
+# Download and Install Apache Maven
+Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..."
+Write-Verbose "Downloading from: $distributionUrl"
+Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName"
+
+$webclient = New-Object System.Net.WebClient
+if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) {
+  $webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD)
+}
+[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
+$webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null
+
+# If specified, validate the SHA-256 sum of the Maven distribution zip file
+$distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum
+if ($distributionSha256Sum) {
+  if ($USE_MVND) {
+    Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties."
+  }
+  Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash
+  if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) {
+    Write-Error "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property."
+  }
+}
+
+# unzip and move
+Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null
+Rename-Item -Path "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" -NewName $MAVEN_HOME_NAME | Out-Null
+try {
+  Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null
+} catch {
+  if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) {
+    Write-Error "fail to move MAVEN_HOME"
+  }
+} finally {
+  try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null }
+  catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" }
+}
+
+Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD"

+ 236 - 0
pom.xml

@@ -0,0 +1,236 @@
+<?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.4</version>
+        <relativePath/> <!-- lookup parent from repository -->
+    </parent>
+
+    <groupId>net.yaoyi</groupId>
+    <artifactId>gulop-member-auth</artifactId>
+    <version>0.0.1-SNAPSHOT</version>
+    <name>gulop-member-auth</name>
+    <description>gulop-member-auth</description>
+    <url/>
+    <licenses>
+        <license/>
+    </licenses>
+    <developers>
+        <developer/>
+    </developers>
+    <scm>
+        <connection/>
+        <developerConnection/>
+        <tag/>
+        <url/>
+    </scm>
+    <properties>
+        <java.version>17</java.version>
+
+        <!-- 新增统一版本控制 -->
+        <fastjson.version>1.2.83</fastjson.version> <!-- 升级到安全版本 -->
+        <httpclient.version>4.5.14</httpclient.version> <!-- 兼容Spring Boot 3.x -->
+        <commons-lang3.version>3.14.0</commons-lang3.version>
+        <itextpdf.version>8.0.3</itextpdf.version> <!-- 统一版本 -->
+    </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-test</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>mysql</groupId>
+            <artifactId>mysql-connector-java</artifactId>
+<!--            <version>${mysql.connector.version}</version>-->
+            <version>8.0.33</version>
+        </dependency>
+
+        <dependency>
+            <groupId>io.minio</groupId>
+            <artifactId>minio</artifactId>
+            <version>8.4.3</version>
+        </dependency>
+
+
+
+        <dependency>
+            <groupId>org.apache.httpcomponents</groupId>
+            <artifactId>httpmime</artifactId>
+            <version>${httpclient.version}</version>
+        </dependency>
+
+        <!-- Commons Lang3 -->
+        <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-lang3</artifactId>
+            <version>${commons-lang3.version}</version>
+        </dependency>
+
+        <!-- iTextPDF(统一版本) -->
+        <dependency>
+            <groupId>com.itextpdf</groupId>
+            <artifactId>itextpdf</artifactId>
+            <version>5.5.13.2</version>
+        </dependency>
+        <dependency>
+            <groupId>com.itextpdf</groupId>
+            <artifactId>kernel</artifactId>
+            <version>${itextpdf.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>com.itextpdf</groupId>
+            <artifactId>io</artifactId>
+            <version>${itextpdf.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>com.itextpdf</groupId>
+            <artifactId>layout</artifactId>
+            <version>${itextpdf.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>ch.qos.logback</groupId>
+            <artifactId>logback-classic</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework</groupId>
+            <artifactId>spring-tx</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>jakarta.validation</groupId>
+            <artifactId>jakarta.validation-api</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-data-jpa</artifactId>
+        </dependency>
+
+
+
+        <dependency>
+            <groupId>org.mybatis</groupId>
+            <artifactId>mybatis-spring</artifactId>
+            <version>3.0.3</version>
+        </dependency>
+
+        <dependency>
+            <groupId>com.baomidou</groupId>
+            <artifactId>mybatis-plus-boot-starter</artifactId>
+            <version>3.5.7</version>
+        </dependency>
+
+        <dependency>
+            <groupId>com.alibaba</groupId>
+            <artifactId>fastjson</artifactId>
+            <version>${fastjson.version}</version>
+            <scope>compile</scope>
+        </dependency>
+        <!-- <dependency>
+            <groupId>net.yaoyi</groupId>
+            <artifactId>gulop-common-core</artifactId>
+            <version>4.3.0</version>
+            <scope>compile</scope>
+        </dependency> -->
+
+
+        <!-- <dependency>
+            <groupId>net.yaoyi</groupId>
+            <artifactId>gulop-common-data</artifactId>
+            <version>4.3.0</version>
+            <scope>compile</scope>
+        </dependency>
+        <dependency>
+            <groupId>net.yaoyi</groupId>
+            <artifactId>gulop-joomla-api</artifactId>
+            <version>4.3.0</version>
+            <scope>compile</scope>
+        </dependency> -->
+
+
+        <!-- <dependency>
+            <groupId>com.ancun.netsign</groupId>
+            <artifactId>netsign-sdk</artifactId>
+            <version>2.5</version>
+            <scope>system</scope>
+            <systemPath>${project.basedir}/src/lib/netsign-sdk-2.5.105.mix.jar</systemPath>
+        </dependency> -->
+
+        <dependency>
+            <groupId>com.ancun.netsign</groupId>
+            <artifactId>netsign-sdk</artifactId>
+            <version>3.0.6</version>
+            <scope>system</scope>
+            <systemPath>${project.basedir}/src/lib/netsign-sdk-3.0.6.mix.jar</systemPath>
+        </dependency>
+        <!-- <dependency>
+            <groupId>net.yaoyi</groupId>
+            <artifactId>gulop-common-tea</artifactId>
+            <version>4.3.0</version>
+            <scope>compile</scope>
+        </dependency> -->
+        <dependency>
+            <groupId>org.codehaus.plexus</groupId>
+            <artifactId>plexus-interpolation</artifactId>
+            <version>1.27</version>
+            <scope>compile</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework</groupId>
+            <artifactId>spring-web</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>io.netty</groupId>
+            <artifactId>netty-common</artifactId>
+        </dependency>
+
+        <!-- 添加Redis依赖 -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-data-redis</artifactId>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-maven-plugin</artifactId>
+
+                <configuration>
+                    <includeSystemScope>true</includeSystemScope>
+                </configuration>
+
+            </plugin>
+            
+            <!-- 添加Maven编译器插件配置 -->
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <version>3.11.0</version>
+                <configuration>
+                    <source>17</source>
+                    <target>17</target>
+                    <encoding>UTF-8</encoding>
+                    <compilerArgs>
+                        <arg>-Xlint:deprecation</arg>
+                        <arg>-Xlint:unchecked</arg>
+                    </compilerArgs>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+</project>

二進制
src/lib/netsign-sdk-2.5.105.mix.jar


二進制
src/lib/netsign-sdk-3.0.6.mix.jar


+ 19 - 0
src/main/java/net/yaoyi/gulop/member/GulopMemberAuthApplication.java

@@ -0,0 +1,19 @@
+package net.yaoyi.gulop.member;
+
+import org.mybatis.spring.annotation.MapperScan;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.context.annotation.ComponentScan;
+
+// @MapperScan({"net.yaoyi.gulop.member.auth.mapper", "net.yaoyi.gulop.gig.mapper"})
+// @ComponentScan({"net.yaoyi.gulop.gig"})
+@SpringBootApplication
+public class GulopMemberAuthApplication {
+
+
+    public static void main(String[] args) {
+        SpringApplication.run(GulopMemberAuthApplication.class, args);
+        System.out.println("启动成功");
+    }
+
+}

+ 28 - 0
src/main/java/net/yaoyi/gulop/member/auth/api/IErrorCode.java

@@ -0,0 +1,28 @@
+package net.yaoyi.gulop.member.auth.api;
+
+/**
+ * {@code IErrorCode}
+ * <p>
+ * 常用API返回对象接口
+ *
+ * @author Hengchen.Sun
+ * @version 1.0.0
+ * @date 2022/03/18 11:05
+ */
+public interface IErrorCode {
+
+    /**
+     * 返回状态码
+     *
+     * @return 状态码
+     */
+    Integer getCode();
+
+
+    /**
+     * 返回状态信息
+     *
+     * @return 状态信息
+     */
+    String getMessage();
+}

+ 28 - 0
src/main/java/net/yaoyi/gulop/member/auth/config/MinioConfig.java

@@ -0,0 +1,28 @@
+package net.yaoyi.gulop.member.auth.config;
+
+import io.minio.MinioClient;
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Data
+@Configuration
+@ConfigurationProperties(prefix = "minio")
+public class MinioConfig {
+    private String endpoint;
+    private String accessKey;
+    private String secretKey;
+    private String bucketName;
+    private String region;
+    private boolean secure;
+
+    @Bean
+    public MinioClient minioClient() {
+        return MinioClient.builder()
+                .endpoint(endpoint)
+                .credentials(accessKey, secretKey)
+                .region(region)
+                .build();
+    }
+}

+ 72 - 0
src/main/java/net/yaoyi/gulop/member/auth/config/MybatisPlusConfiguration.java

@@ -0,0 +1,72 @@
+/*
+ *    Copyright (c) 2018-2025, gulop All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the pig4cloud.com developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: gulop
+ */
+
+package net.yaoyi.gulop.member.auth.config;
+
+import com.alibaba.fastjson.JSONObject;
+import com.baomidou.mybatisplus.autoconfigure.ConfigurationCustomizer;
+import com.baomidou.mybatisplus.extension.handlers.FastjsonTypeHandler;
+import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
+import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
+import net.yaoyi.gulop.member.auth.handler.JSONObjectTypeHandler;
+import org.springframework.boot.autoconfigure.AutoConfigureAfter;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
+import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+
+import javax.sql.DataSource;
+
+/**
+ * @author gulop
+ * @date 2020-02-08
+ */
+@Configuration
+@ConditionalOnBean(DataSource.class)
+@AutoConfigureAfter(DataSourceAutoConfiguration.class)
+public class MybatisPlusConfiguration implements WebMvcConfigurer {
+
+
+	/**
+	 * mybatis plus 拦截器配置
+	 * @return GulopDefaultDatascopeHandle
+	 */
+	@Bean
+	public MybatisPlusInterceptor mybatisPlusInterceptor() {
+		MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
+
+		// 分页支持
+		PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor();
+		paginationInnerInterceptor.setMaxLimit(1000L);
+		interceptor.addInnerInterceptor(paginationInnerInterceptor);
+		return interceptor;
+	}
+
+
+
+	@Bean
+	public ConfigurationCustomizer configurationCustomizer() {
+		return configuration -> {
+			// 注册FastjsonTypeHandler到JSONObject类型
+			configuration.getTypeHandlerRegistry().register(JSONObject.class, new JSONObjectTypeHandler());
+		};
+	}
+
+
+}

+ 35 - 0
src/main/java/net/yaoyi/gulop/member/auth/config/RedisConfig.java

@@ -0,0 +1,35 @@
+package net.yaoyi.gulop.member.auth.config;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.data.redis.connection.RedisConnectionFactory;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
+import org.springframework.data.redis.serializer.StringRedisSerializer;
+
+/**
+ * Redis配置类
+ */
+@Configuration
+public class RedisConfig {
+
+    /**
+     * 配置RedisTemplate,设置序列化
+     */
+    @Bean
+    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
+        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
+        redisTemplate.setConnectionFactory(connectionFactory);
+        
+        // 使用StringRedisSerializer来序列化和反序列化redis的key值
+        redisTemplate.setKeySerializer(new StringRedisSerializer());
+        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
+        
+        // 使用Jackson2JsonRedisSerializer来序列化和反序列化redis的value值
+        redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
+        redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
+        
+        redisTemplate.afterPropertiesSet();
+        return redisTemplate;
+    }
+} 

+ 129 - 0
src/main/java/net/yaoyi/gulop/member/auth/constant/CacheConstants.java

@@ -0,0 +1,129 @@
+package net.yaoyi.gulop.member.auth.constant;
+
+/**
+ * @author gulop
+ * @date 2019-04-28
+ * <p>
+ * 缓存的key 常量
+ */
+public interface CacheConstants {
+
+	/**
+	 * 全局缓存,在缓存名称上加上该前缀表示该缓存不区分租户,比如:
+	 * <p/>
+	 * {@code @Cacheable(value = CacheConstants.GLOBALLY+CacheConstants.MENU_DETAILS, key = "#roleId  + '_menu'", unless = "#result == null")}
+	 */
+	String GLOBALLY = "gl:";
+
+	/**
+	 * 验证码前缀
+	 */
+	String DEFAULT_CODE_KEY = "DEFAULT_CODE_KEY:";
+
+	/**
+	 * 菜单信息缓存
+	 */
+	String MENU_DETAILS = "menu_details";
+
+	/**
+	 * 用户信息缓存
+	 */
+	String USER_DETAILS = "user_details";
+
+    /**
+     * 用户信息缓存
+     */
+    String MEMBER_DETAILS = "member_details";
+
+	/**
+	 * 角色信息缓存
+	 */
+	String ROLE_DETAILS = "role_details";
+
+	/**
+	 * 字典信息缓存
+	 */
+	String DICT_DETAILS = "dict_details";
+
+    /**
+     * 功能套餐包缓存
+     */
+    String BUNDLE_DETAILS = "bundle_details";
+
+    /**
+     * 功能套餐包sku缓存
+     */
+    String BUNDLE_SKU_DETAILS = "bundle_sku_details";
+
+    /**
+     * 功能属性缓存
+     */
+    String FUNC_ATTR_DETAILS = "func_attr_details";
+
+    /**
+     * 功能属性缓存分组
+     */
+    String FUNC_ATTR_GROUP_DETAILS = "func_attr_group_details";
+
+
+    /**
+	 * oauth 客户端信息
+	 */
+	String CLIENT_DETAILS_KEY = "gulop_oauth:client:details";
+
+	/**
+	 * spring boot admin 事件key
+	 */
+	String EVENT_KEY = GLOBALLY + "event_key";
+
+	/**
+	 * 路由存放
+	 */
+	String ROUTE_KEY = GLOBALLY + "gateway_route_key";
+
+	/**
+	 * 内存reload 时间
+	 */
+	String ROUTE_JVM_RELOAD_TOPIC = "gateway_jvm_route_reload_topic";
+
+	/**
+	 * redis 重新加载 路由信息
+	 */
+	String ROUTE_REDIS_RELOAD_TOPIC = "upms_redis_route_reload_topic";
+
+	/**
+	 * redis 重新加载客户端信息
+	 */
+	String CLIENT_REDIS_RELOAD_TOPIC = "upms_redis_client_reload_topic";
+
+	/**
+	 * 公众号 reload
+	 */
+	String MP_REDIS_RELOAD_TOPIC = "mp_redis_reload_topic";
+
+	/**
+	 * 支付 reload 事件
+	 */
+	String PAY_REDIS_RELOAD_TOPIC = "pay_redis_reload_topic";
+
+	/**
+	 * 参数缓存
+	 */
+	String PARAMS_DETAILS = "params_details";
+
+	/**
+	 * 租户缓存 (不区分租户)
+	 */
+	String TENANT_DETAILS = GLOBALLY + "tenant_details";
+
+	/**
+	 * 客户端配置缓存
+	 */
+	String CLIENT_FLAG = "client_config_flag";
+
+    /**
+     * 零工配置缓存
+     */
+    String GIG_CONFIG_DETAILS = "gig_config_details";
+
+}

+ 133 - 0
src/main/java/net/yaoyi/gulop/member/auth/constant/CommonConstants.java

@@ -0,0 +1,133 @@
+/*
+ *
+ *      Copyright (c) 2018-2025, gulop All rights reserved.
+ *
+ *  Redistribution and use in source and binary forms, with or without
+ *  modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ *  this list of conditions and the following disclaimer.
+ *  Redistributions in binary form must reproduce the above copyright
+ *  notice, this list of conditions and the following disclaimer in the
+ *  documentation and/or other materials provided with the distribution.
+ *  Neither the name of the pig4cloud.com developer nor the names of its
+ *  contributors may be used to endorse or promote products derived from
+ *  this software without specific prior written permission.
+ *  Author: gulop
+ *
+ */
+
+package net.yaoyi.gulop.member.auth.constant;
+
+/**
+ * @author gulop
+ * @date 2017/10/29
+ */
+public interface CommonConstants {
+
+    /**
+     * header 中租户ID
+     */
+    String TENANT_ID = "TENANT-ID";
+
+    /**
+     * header 中版本信息
+     */
+    String VERSION = "VERSION";
+
+    /**
+     * 租户ID
+     */
+    Long TENANT_ID_1 = 1L;
+
+    /**
+     * 删除
+     */
+    String STATUS_DEL = "1";
+
+    /**
+     * 正常
+     */
+    String STATUS_NORMAL = "0";
+
+    /**
+     * 锁定
+     */
+    String STATUS_LOCK = "9";
+
+    /**
+     * 菜单树根节点
+     */
+    Long MENU_TREE_ROOT_ID = -1L;
+
+    /**
+     * 编码
+     */
+    String UTF8 = "UTF-8";
+
+    /**
+     * 前端工程名
+     */
+    String FRONT_END_PROJECT = "gulop-ui";
+
+    /**
+     * 后端工程名
+     */
+    String BACK_END_PROJECT = "gulop";
+
+    /**
+     * 公共参数
+     */
+    String PIG_PUBLIC_PARAM_KEY = "PIG_PUBLIC_PARAM_KEY";
+
+    /**
+     * 成功标记
+     */
+    Integer SUCCESS = 0;
+
+    /**
+     * 成功信息
+     */
+    String SUCCESS_MSG = "SUCCESS";
+
+    /**
+     * 失败标记
+     */
+    Integer FAIL = 1;
+
+    /**
+     * 失败信息
+     */
+    String FAIL_MSG = "FAILURE";
+
+    /**
+     * 默认存储bucket
+     */
+    String BUCKET_NAME = "gulop";
+
+    /**
+     * 滑块验证码
+     */
+    String IMAGE_CODE_TYPE = "blockPuzzle";
+
+    /**
+     * 验证码开关
+     */
+    String CAPTCHA_FLAG = "captcha_flag";
+
+    /**
+     * 密码传输是否加密
+     */
+    String ENC_FLAG = "enc_flag";
+
+    /**
+     * 客户端允许同时在线数量
+     */
+    String ONLINE_QUANTITY = "online_quantity";
+
+    /**
+     * redis删除操作脚本
+     */
+    String REDIS_DEL_SCRIPT = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
+
+}

+ 21 - 0
src/main/java/net/yaoyi/gulop/member/auth/constant/PaginationConstants.java

@@ -0,0 +1,21 @@
+package net.yaoyi.gulop.member.auth.constant;
+
+/**
+ * 分页相关的参数
+ *
+ * @author lishangbu
+ * @date 2018/11/22
+ */
+public interface PaginationConstants {
+
+	/**
+	 * 当前页
+	 */
+	String CURRENT = "current";
+
+	/**
+	 * 每页大小
+	 */
+	String SIZE = "size";
+
+}

+ 234 - 0
src/main/java/net/yaoyi/gulop/member/auth/constant/SecurityConstants.java

@@ -0,0 +1,234 @@
+/*
+ *
+ *      Copyright (c) 2018-2025, gulop All rights reserved.
+ *
+ *  Redistribution and use in source and binary forms, with or without
+ *  modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ *  this list of conditions and the following disclaimer.
+ *  Redistributions in binary form must reproduce the above copyright
+ *  notice, this list of conditions and the following disclaimer in the
+ *  documentation and/or other materials provided with the distribution.
+ *  Neither the name of the pig4cloud.com developer nor the names of its
+ *  contributors may be used to endorse or promote products derived from
+ *  this software without specific prior written permission.
+ *  Author: gulop
+ *
+ */
+
+package net.yaoyi.gulop.member.auth.constant;
+
+/**
+ * @author gulop
+ * @date 2017-12-18
+ */
+public interface SecurityConstants {
+
+    /**
+     * 启动时是否检查Inner注解安全性
+     */
+    boolean INNER_CHECK = true;
+
+    /**
+     * 刷新
+     */
+    String REFRESH_TOKEN = "refresh_token";
+
+    /**
+     * 验证码有效期
+     */
+    int CODE_TIME = 60;
+
+    /**
+     * 验证码长度
+     */
+    String CODE_SIZE = "4";
+
+    /**
+     * 角色前缀
+     */
+    String ROLE = "ROLE_";
+
+    /**
+     * 前缀
+     */
+    String GULOP_PREFIX = "gulop_";
+
+    /**
+     * token 相关前缀
+     */
+    String TOKEN_PREFIX = "token:";
+
+    /**
+     * oauth 相关前缀
+     */
+    String OAUTH_PREFIX = "oauth:";
+
+    /**
+     * 授权码模式code key 前缀
+     */
+    String OAUTH_CODE_PREFIX = "oauth:code:";
+
+    /**
+     * 项目的license
+     */
+    String GULOP_LICENSE = "made by gulop";
+
+    /**
+     * 内部
+     */
+    String FROM_IN = "Y";
+
+    /**
+     * 标志
+     */
+    String FROM = "from";
+
+    /**
+     * OAUTH URL
+     */
+    String OAUTH_TOKEN_URL = "/oauth/token";
+
+    /**
+     * 移动端授权
+     */
+    String GRANT_MOBILE = "mobile";
+
+    /**
+     * QQ获取token
+     */
+    String QQ_AUTHORIZATION_CODE_URL = "https://graph.qq.com/oauth2.0/token?grant_type="
+        + "authorization_code&code=%S&client_id=%s&redirect_uri=" + "%s&client_secret=%s";
+
+    /**
+     * 微信获取OPENID
+     */
+    String WX_AUTHORIZATION_CODE_URL = "https://api.weixin.qq.com/sns/oauth2/access_token"
+        + "?appid=%s&secret=%s&code=%s&grant_type=authorization_code";
+
+    /**
+     * 微信小程序OPENID
+     */
+    String MINI_APP_AUTHORIZATION_CODE_URL = "https://api.weixin.qq.com/sns/jscode2session"
+        + "?appid=%s&secret=%s&js_code=%s&grant_type=authorization_code";
+
+    /**
+     * 码云获取token
+     */
+    String GITEE_AUTHORIZATION_CODE_URL = "https://gitee.com/oauth/token?grant_type="
+        + "authorization_code&code=%S&client_id=%s&redirect_uri=" + "%s&client_secret=%s";
+
+    /**
+     * 开源中国获取token
+     */
+    String OSC_AUTHORIZATION_CODE_URL = "https://www.oschina.net/action/openapi/token";
+
+    /**
+     * QQ获取用户信息
+     */
+    String QQ_USER_INFO_URL = "https://graph.qq.com/oauth2.0/me?access_token=%s";
+
+    /**
+     * 码云获取用户信息
+     */
+    String GITEE_USER_INFO_URL = "https://gitee.com/api/v5/user?access_token=%s";
+
+    /**
+     * 开源中国用户信息
+     */
+    String OSC_USER_INFO_URL = "https://www.oschina.net/action/openapi/user?access_token=%s&dataType=json";
+
+    /**
+     * {bcrypt} 加密的特征码
+     */
+    String BCRYPT = "{bcrypt}";
+
+    /**
+     * sys_oauth_client_details 表的字段,不包括client_id、client_secret
+     */
+    String CLIENT_FIELDS = "client_id, CONCAT('{noop}',client_secret) as client_secret, resource_ids, scope, "
+        + "authorized_grant_types, web_server_redirect_uri, authorities, access_token_validity, "
+        + "refresh_token_validity, additional_information, autoapprove";
+
+    /**
+     * JdbcClientDetailsService 查询语句
+     */
+    String BASE_FIND_STATEMENT = "select " + CLIENT_FIELDS + " from sys_oauth_client_details";
+
+    /**
+     * 按条件client_id 查询
+     */
+    String DEFAULT_SELECT_STATEMENT = BASE_FIND_STATEMENT + " where client_id = ? and del_flag = 0 and tenant_id = %s";
+
+    /**
+     * 资源服务器默认bean名称
+     */
+    String RESOURCE_SERVER_CONFIGURER = "resourceServerConfigurerAdapter";
+
+    /**
+     * 客户端模式
+     */
+    String CLIENT_CREDENTIALS = "client_credentials";
+
+    /**
+     * 客户端编号
+     */
+    String CLIENT_ID = "client_id";
+
+    /**
+     * 客户端唯一令牌
+     */
+    String CLIENT_RECREATE = "recreate_flag";
+
+    /**
+     * 用户ID字段
+     */
+    String DETAILS_USER_ID = "user_id";
+
+    /**
+     * 用户名
+     */
+    String DETAILS_USERNAME = "username";
+
+    /**
+     * 用户基本信息
+     */
+    String DETAILS_USER = "user_info";
+
+    /**
+     * 姓名
+     */
+    String NAME = "name";
+
+    /**
+     * 邮箱
+     */
+    String EMAIL = "email";
+
+    /**
+     * 用户部门字段
+     */
+    String DETAILS_DEPT_ID = "dept_id";
+
+    /**
+     * 租户ID 字段
+     */
+    String DETAILS_TENANT_ID = "tenant_id";
+
+    /**
+     * 协议字段
+     */
+    String DETAILS_LICENSE = "license";
+
+    /**
+     * 激活字段 兼容外围系统接入
+     */
+    String ACTIVE = "active";
+
+    /**
+     * AES 加密
+     */
+    String AES = "aes";
+
+}

+ 73 - 0
src/main/java/net/yaoyi/gulop/member/auth/constant/ServiceNameConstants.java

@@ -0,0 +1,73 @@
+/*
+ *
+ *      Copyright (c) 2018-2025, gulop All rights reserved.
+ *
+ *  Redistribution and use in source and binary forms, with or without
+ *  modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ *  this list of conditions and the following disclaimer.
+ *  Redistributions in binary form must reproduce the above copyright
+ *  notice, this list of conditions and the following disclaimer in the
+ *  documentation and/or other materials provided with the distribution.
+ *  Neither the name of the pig4cloud.com developer nor the names of its
+ *  contributors may be used to endorse or promote products derived from
+ *  this software without specific prior written permission.
+ *  Author: gulop
+ *
+ */
+
+package net.yaoyi.gulop.member.auth.constant;
+
+/**
+ * @author gulop
+ * @date 2018年06月22日16:41:01 服务名称
+ */
+public interface ServiceNameConstants {
+
+    /**
+     * 认证中心
+     */
+    String AUTH_SERVICE = "gulop-auth";
+
+    /**
+     * UMPS模块
+     */
+    String UPMS_SERVICE = "gulop-upms-biz";
+
+    /**
+     * 收银台服务
+     */
+    String CHECKOUT_SERVICE = "gulop-checkout-biz";
+
+    /**
+     * 分布式事务协调服务
+     */
+    String TX_MANAGER = "gulop-tx-manager";
+
+    /**
+     * 零工服务
+     */
+    String GIG_SERVICE = "gulop-gig-biz";
+
+    /**
+     * 会员服务
+     */
+    String MEMBER_SERVICE = "gulop-member-biz";
+
+    /**
+     * 内容管理服务
+     */
+    String CMS_SERVICE = "gulop-joomla-biz";
+
+    /**
+     * 项目中心服务
+     */
+    String PROJCENTER_SERVICE = "gulop-projcenter-biz";
+
+    /**
+     * CSO任意门
+     */
+    String ANYWHERE_SERVICE = "gulop-anywhere-biz";
+
+}

+ 36 - 0
src/main/java/net/yaoyi/gulop/member/auth/constant/enums/CaptchaFlagTypeEnum.java

@@ -0,0 +1,36 @@
+package net.yaoyi.gulop.member.auth.constant.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * @author gulop
+ * @date 2020-11-18
+ * <p>
+ * 验证码状态
+ */
+@Getter
+@AllArgsConstructor
+public enum CaptchaFlagTypeEnum {
+
+	/**
+	 * 开启验证码
+	 */
+	ON("1", "开启验证码"),
+
+	/**
+	 * 关闭验证码
+	 */
+	OFF("0", "关闭验证码");
+
+	/**
+	 * 类型
+	 */
+	private String type;
+
+	/**
+	 * 描述
+	 */
+	private String description;
+
+}

+ 50 - 0
src/main/java/net/yaoyi/gulop/member/auth/constant/enums/ContractStatus.java

@@ -0,0 +1,50 @@
+package net.yaoyi.gulop.member.auth.constant.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.util.Arrays;
+
+@Getter
+@AllArgsConstructor
+public enum ContractStatus {
+    WAIT_SIGN(0, "等待签约"),
+    SIGNING(1, "签约中"),
+    SIGNED(2, "已签约"),
+    EXPIRED(3, "过期"),
+    REJECTED(4, "拒签"),
+    INVALID(6, "作废"),
+    REVOKED(7, "撤销");
+
+    /**
+     * 响应结果状态码
+     */
+    private final Integer code;
+
+    /**
+     * 响应结果信息
+     */
+    private final String message;
+
+
+    /**
+     * 根据 code 获取枚举值
+     * @param code 状态码
+     * @return 对应的枚举值,未匹配时抛出异常
+     */
+    public static ContractStatus fromCode(Integer code) {
+        for (ContractStatus status : values()) {
+            if (status.code.equals(code)) {
+                return status;
+            }
+        }
+        throw new IllegalArgumentException("无效状态码: " + code);
+    }
+
+
+    //对比
+    public static boolean in(Integer code, ContractStatus... statuses) {
+        if (code == null || statuses == null) return false;
+        return Arrays.stream(statuses).anyMatch(s -> s.getCode().equals(code));
+    }
+}

+ 36 - 0
src/main/java/net/yaoyi/gulop/member/auth/constant/enums/EncFlagTypeEnum.java

@@ -0,0 +1,36 @@
+package net.yaoyi.gulop.member.auth.constant.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * @author gulop
+ * @date 2020-11-18
+ * <p>
+ * 密码是否加密传输
+ */
+@Getter
+@AllArgsConstructor
+public enum EncFlagTypeEnum {
+
+	/**
+	 * 是
+	 */
+	YES("1", "是"),
+
+	/**
+	 * 否
+	 */
+	NO("0", "否");
+
+	/**
+	 * 类型
+	 */
+	private String type;
+
+	/**
+	 * 描述
+	 */
+	private String description;
+
+}

+ 81 - 0
src/main/java/net/yaoyi/gulop/member/auth/constant/enums/LoginTypeEnum.java

@@ -0,0 +1,81 @@
+/*
+ *    Copyright (c) 2018-2025, gulop All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the pig4cloud.com developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: gulop
+ */
+
+package net.yaoyi.gulop.member.auth.constant.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * @author gulop
+ * @date 2018/8/15 社交登录类型
+ */
+@Getter
+@AllArgsConstructor
+public enum LoginTypeEnum {
+
+	/**
+	 * 账号密码登录
+	 */
+	PWD("PWD", "账号密码登录"),
+
+	/**
+	 * 验证码登录
+	 */
+	SMS("SMS", "验证码登录"),
+
+	/**
+	 * QQ登录
+	 */
+	QQ("QQ", "QQ登录"),
+
+	/**
+	 * 微信登录
+	 */
+	WECHAT("WX", "微信登录"),
+
+	/**
+	 * 微信小程序
+	 */
+	MINI_APP("MINI", "微信小程序"),
+
+	/**
+	 * 码云登录
+	 */
+	GITEE("GITEE", "码云登录"),
+
+	/**
+	 * 开源中国登录
+	 */
+	OSC("OSC", "开源中国登录"),
+
+	/**
+	 * CAS 登录
+	 */
+	CAS("CAS", "CAS 登录");
+
+	/**
+	 * 类型
+	 */
+	private String type;
+
+	/**
+	 * 描述
+	 */
+	private String description;
+
+}

+ 41 - 0
src/main/java/net/yaoyi/gulop/member/auth/constant/enums/MenuTypeEnum.java

@@ -0,0 +1,41 @@
+package net.yaoyi.gulop.member.auth.constant.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * @author gulop
+ * @date 2020-02-17
+ * <p>
+ * 菜单类型
+ */
+@Getter
+@AllArgsConstructor
+public enum MenuTypeEnum {
+
+	/**
+	 * 左侧菜单
+	 */
+	LEFT_MENU("0", "left"),
+
+	/**
+	 * 顶部菜单
+	 */
+	TOP_MENU("2", "top"),
+
+	/**
+	 * 按钮
+	 */
+	BUTTON("1", "button");
+
+	/**
+	 * 类型
+	 */
+	private String type;
+
+	/**
+	 * 描述
+	 */
+	private String description;
+
+}

+ 46 - 0
src/main/java/net/yaoyi/gulop/member/auth/constant/enums/ProcessStatusEnum.java

@@ -0,0 +1,46 @@
+/*
+ *    Copyright (c) 2018-2025, gulop All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the pig4cloud.com developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: gulop
+ */
+
+package net.yaoyi.gulop.member.auth.constant.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * @author gulop
+ * @date 2018/9/30 流程状态
+ */
+@Getter
+@AllArgsConstructor
+public enum ProcessStatusEnum {
+
+	/**
+	 * 激活
+	 */
+	ACTIVE("active"),
+
+	/**
+	 * 暂停
+	 */
+	SUSPEND("suspend");
+
+	/**
+	 * 状态
+	 */
+	private final String status;
+
+}

+ 51 - 0
src/main/java/net/yaoyi/gulop/member/auth/constant/enums/ResourceTypeEnum.java

@@ -0,0 +1,51 @@
+/*
+ *    Copyright (c) 2018-2025, gulop All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the pig4cloud.com developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: gulop
+ */
+
+package net.yaoyi.gulop.member.auth.constant.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * @author gulop
+ * @date 2018/9/30 资源类型
+ */
+@Getter
+@AllArgsConstructor
+public enum ResourceTypeEnum {
+
+	/**
+	 * 图片资源
+	 */
+	IMAGE("image", "图片资源"),
+
+	/**
+	 * xml资源
+	 */
+	XML("xml", "xml资源");
+
+	/**
+	 * 类型
+	 */
+	private final String type;
+
+	/**
+	 * 描述
+	 */
+	private final String description;
+
+}

+ 75 - 0
src/main/java/net/yaoyi/gulop/member/auth/constant/enums/ResultCodeEnum.java

@@ -0,0 +1,75 @@
+package net.yaoyi.gulop.member.auth.constant.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import net.yaoyi.gulop.member.auth.api.IErrorCode;
+import net.yaoyi.gulop.member.auth.constant.CommonConstants;
+
+/**
+ * {@code ResultCodeEnum}
+ * <p>
+ * 响应结果枚举
+ *
+ * @author Hengchen.Sun
+ * @version 1.0.0
+ * @date 2021/10/22 16:51
+ */
+@Getter
+@AllArgsConstructor
+public enum ResultCodeEnum implements IErrorCode {
+
+    // ~ [通用] ========================================================================================================
+    SUCCESS(CommonConstants.SUCCESS, CommonConstants.SUCCESS_MSG),
+    FAILURE(CommonConstants.FAIL, CommonConstants.FAIL_MSG),
+
+    INVALID_PARAMETERS(400001, "无效的参数"),
+    DUPLICATE_UNIQUE_KEY(400002, "唯一键值重复"),
+    CONVERT_TO_RESULT_FAILURE(400003, "包装响应结果失败"),
+    NO_SUCH_RECORD(400004, "查询记录不存在"),
+    ERROR_STATUS(400005, "错误的状态"),
+    CANNOT_DO_THIS(400006, "没有权限操作"),
+    SYSTEM_ELEMENT(400007, "系统元素"),
+    HAS_CHILDREN(400008, "包含子集"),
+    SQL_ERROR(400008, "SQL有误"),
+    SERVICE_EXPIRED(400009, "服务已到期"),
+    REMOTE_REQUEST_ERROR(400010, "远端请求失败"),
+    BABY_ON_BOARD(400011, "存在在途操作"),
+    DEADLINE_EXPIRED(400012, "已过期"),
+    HANDLE_MESSAGE_FAILURE(400013, "处理消息失败"),
+
+
+    // ~ [租户] ========================================================================================================
+    THIS_IS_PLATFORM(401001, "这是平台,不能被删除"),
+
+
+    // ~ [合同] ========================================================================================================
+    EXIST_AVAILABLE_CONTRACT_TEMPLATE(402001, "存在可用的合同模板"),
+
+    // ~ [OSS] ========================================================================================================
+    UPLOAD_FILE_FAILURE(403001, "上传文件失败"),
+    DOWNLOAD_FILE_FAILURE(403002, "文件获取失败"),
+    CREATE_BUCKET_FAILURE(403003, "创建OSS桶失败"),
+    BUCKET_CAPACITY_NOT_ENOUGH(403004, "桶容量不足"),
+
+    // ~ [用户] ========================================================================================================
+    I_AM_SUPERMAN(404001, "我是SUPERMAN,不能被删除"),
+    PASSWORD_INVALID(404002, "密码不正确"),
+
+    // ~ [字典] ========================================================================================================
+
+
+    // ~ [GIG] ========================================================================================================
+
+
+    ;
+
+    /**
+     * 响应结果状态码
+     */
+    private final Integer code;
+
+    /**
+     * 响应结果信息
+     */
+    private final String message;
+}

+ 36 - 0
src/main/java/net/yaoyi/gulop/member/auth/constant/enums/StyleTypeEnum.java

@@ -0,0 +1,36 @@
+package net.yaoyi.gulop.member.auth.constant.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * @author gulop
+ * @date 2020-01-19
+ * <p>
+ * 前端类型类型
+ */
+@Getter
+@AllArgsConstructor
+public enum StyleTypeEnum {
+
+	/**
+	 * 前端类型-avue 风格
+	 */
+	AVUE("0", "avue 风格"),
+
+	/**
+	 * 前端类型-element 风格
+	 */
+	ELEMENT("1", "element 风格");
+
+	/**
+	 * 类型
+	 */
+	private String style;
+
+	/**
+	 * 描述
+	 */
+	private String description;
+
+}

+ 61 - 0
src/main/java/net/yaoyi/gulop/member/auth/constant/enums/TaskStatusEnum.java

@@ -0,0 +1,61 @@
+/*
+ *    Copyright (c) 2018-2025, gulop All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the pig4cloud.com developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: gulop
+ */
+
+package net.yaoyi.gulop.member.auth.constant.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * @author gulop
+ * @date 2018/9/30 流程状态
+ */
+@Getter
+@AllArgsConstructor
+public enum TaskStatusEnum {
+
+	/**
+	 * 未提交
+	 */
+	UNSUBMIT("0", "未提交"),
+
+	/**
+	 * 审核中
+	 */
+	CHECK("1", "审核中"),
+
+	/**
+	 * 已完成
+	 */
+	COMPLETED("2", "已完成"),
+
+	/**
+	 * 驳回
+	 */
+	OVERRULE("9", "驳回");
+
+	/**
+	 * 类型
+	 */
+	private final String status;
+
+	/**
+	 * 描述
+	 */
+	private final String description;
+
+}

+ 110 - 0
src/main/java/net/yaoyi/gulop/member/auth/constant/enums/flag/CommonFlag.java

@@ -0,0 +1,110 @@
+package net.yaoyi.gulop.member.auth.constant.enums.flag;
+
+import com.baomidou.mybatisplus.annotation.EnumValue;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * {@code CommonFlag}
+ * <p>
+ * 标记常量类
+ *
+ * @author Hengchen.Sun
+ * @version 1.0.0
+ * @date 2021/09/08 11:54:05
+ */
+@Getter
+public final class CommonFlag {
+
+    /**
+     * {@code CommonFlag}
+     * <p>
+     * 展示标记枚举
+     *
+     * @author Hengchen.Sun
+     * @version 1.0.0
+     * @date 2021/11/25 00:30
+     */
+    @Getter
+    @AllArgsConstructor
+    public enum ShownFlag {
+
+        SHOWN(0, "展示"),
+        HIDDEN(1, "隐藏");
+
+
+        @EnumValue
+        private final int flag;
+
+        private final String message;
+    }
+
+    /**
+     * {@code ScopeFlag}
+     * <p>
+     * 项目区域标记枚举
+     *
+     * @author Hengchen.Sun
+     * @version 1.0.0
+     * @date 2021/09/01 16:51:33
+     */
+    @Getter
+    @AllArgsConstructor
+    public enum ScopeFlag {
+
+        NATIONWIDE(0, "全国"),
+        PARTIAL(1, "部分地区");
+
+
+        @EnumValue
+        private final int flag;
+
+        private final String message;
+    }
+
+    @Getter
+    @AllArgsConstructor
+    public enum SystemFlag {
+        /**
+         * 字典类型-业务类型
+         */
+        BIZ(0, "业务类"),
+
+
+        /**
+         * 字典类型-系统内置(不可修改)
+         */
+        SYSTEM(1, "系统内置");
+
+
+        /**
+         * 类型
+         */
+        @EnumValue
+        private final int flag;
+
+        /**
+         * 描述
+         */
+        private String message;
+
+    }
+
+    // 操作标识符
+    @Getter
+    @AllArgsConstructor
+    public enum OptFlag {
+
+        OK("OK", 0, "正常"),
+        LOCKED("LOCKED", 1, "已锁定"),
+        DELETED("DELETED", 9, "已删除");
+
+        private final String flag;
+
+        @EnumValue
+        private final int code;
+
+        private final String description;
+
+    }
+}

+ 12 - 0
src/main/java/net/yaoyi/gulop/member/auth/constant/enums/level/CmsLevel.java

@@ -0,0 +1,12 @@
+package net.yaoyi.gulop.member.auth.constant.enums.level;
+
+
+/**
+ * cms等级类
+ *
+ * @author lixuesong
+ * @date 2022年04月11日 17:06
+ */
+public final class CmsLevel {
+
+}

+ 17 - 0
src/main/java/net/yaoyi/gulop/member/auth/constant/enums/level/CommonLevel.java

@@ -0,0 +1,17 @@
+package net.yaoyi.gulop.member.auth.constant.enums.level;
+
+import lombok.Getter;
+
+/**
+ * {@code LevelConstants}
+ * <p>
+ * 等级常量类
+ *
+ * @author Hengchen.Sun
+ * @version 1.0.0
+ * @date 2021/09/14 13:58:43
+ */
+@Getter
+public final class CommonLevel {
+
+}

+ 37 - 0
src/main/java/net/yaoyi/gulop/member/auth/constant/enums/level/UpmsLevel.java

@@ -0,0 +1,37 @@
+package net.yaoyi.gulop.member.auth.constant.enums.level;
+
+import com.baomidou.mybatisplus.annotation.EnumValue;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * {@code UpmsLevel}
+ * <p>
+ * upms等级类
+ *
+ * @author Hengchen.Sun
+ * @version 1.0.0
+ * @date 2021/12/06 15:18
+ */
+public final class UpmsLevel {
+
+    // 套餐包等级
+    @Getter
+    @AllArgsConstructor
+    public enum BundleLevel {
+
+        STARTER(0, "入门版"),
+        BASIC(1, "基础版"),
+        PREMIUM(2, "高级版"),
+        PROFESSIONAL(3, "专业版"),
+        ENTERPRISE(4, "企业版"),
+        ULTIMATE(5, "旗舰版"),
+        CUSTOM(9, "自定义");
+
+
+        @EnumValue
+        private final int level;
+
+        private final String message;
+    }
+}

+ 34 - 0
src/main/java/net/yaoyi/gulop/member/auth/constant/enums/state/CheckoutState.java

@@ -0,0 +1,34 @@
+package net.yaoyi.gulop.member.auth.constant.enums.state;
+
+import com.baomidou.mybatisplus.annotation.EnumValue;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * {@code CheckoutState} 类
+ * 支付中心状态
+ *
+ * @author Hengchen.Sun
+ * @version 1.0.0
+ * @date 2022-02-27
+ * @since ver.1.0.0
+ */
+public final class CheckoutState {
+
+    // 订单状态
+    @Getter
+    @AllArgsConstructor
+    public enum OrderState {
+
+        NEW_ONE("NEW", "待支付"),
+        IN_TRANSIT("IN_TRANSIT", "在途支付"),
+        FAILED("FAILED", "支付失败"),
+        CANCELED("CANCELED", "已取消"),
+        OK("OK", "支付成功");
+
+        @EnumValue
+        private final String state;
+
+        private final String message;
+    }
+}

+ 35 - 0
src/main/java/net/yaoyi/gulop/member/auth/constant/enums/state/CmsStatus.java

@@ -0,0 +1,35 @@
+package net.yaoyi.gulop.member.auth.constant.enums.state;
+
+import com.baomidou.mybatisplus.annotation.EnumValue;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * {@code CmsStatus}
+ * <p>
+ * 模块状态枚举
+ *
+ * @author Hengchen.Sun
+ * @version 1.0.0
+ * @date 2022/03/20 21:06
+ */
+public class CmsStatus {
+
+    /**
+     * 发布状态
+     */
+    @Getter
+    @AllArgsConstructor
+    public enum PublishStatus {
+
+        // 发布状态
+        NEW_ONE(0, "新建"),
+        LAUNCHED(1, "上架"),
+        SOLD_OUT(2, "下架");
+
+        @EnumValue
+        private final int status;
+
+        private final String message;
+    }
+}

+ 32 - 0
src/main/java/net/yaoyi/gulop/member/auth/constant/enums/state/CommonStatus.java

@@ -0,0 +1,32 @@
+package net.yaoyi.gulop.member.auth.constant.enums.state;
+
+import com.baomidou.mybatisplus.annotation.EnumValue;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * {@code StatusConstants}
+ * <p>
+ * 状态枚举常量类
+ *
+ * @author Hengchen.Sun
+ * @version 1.0.0
+ * @date 2021/09/07 18:26:00
+ */
+@Getter
+public final class CommonStatus {
+
+    //  状态枚举
+    @Getter
+    @AllArgsConstructor
+    public enum Condition {
+
+        VALID(1, "有效"),
+        INVALID(2, "无效");
+
+        @EnumValue
+        private final int status;
+
+        private final String message;
+    }
+}

+ 68 - 0
src/main/java/net/yaoyi/gulop/member/auth/constant/enums/state/GigStatus.java

@@ -0,0 +1,68 @@
+package net.yaoyi.gulop.member.auth.constant.enums.state;
+
+import com.baomidou.mybatisplus.annotation.EnumValue;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * {@code GigStatus}
+ * <p>
+ * 零工状态记录
+ *
+ * @author Hengchen.Sun
+ * @version 1.0.0
+ * @date 2022/03/08 13:06
+ */
+@Getter
+public final class GigStatus {
+
+    // 用户协议签约记录
+    @Getter
+    @AllArgsConstructor
+    public enum UserAgreementSignStatus {
+
+        NOT_SIGN(0, "未签约"),
+        SIGNED(1, "签约成功");
+
+        @EnumValue
+        private final Integer code;
+
+        private final String message;
+    }
+
+    // 会员认证信息
+    @Getter
+    @AllArgsConstructor
+    public enum MemberCertState {
+
+
+        UN_CERT("UN_CERT", "未认证"),
+        SIGNED("SIGNED", "已签约"),
+        BOUND("BOUND", "已绑卡"),
+        AGREEMENT("AGREEMENT", "已签协议"),
+        CHECKED("CHECKED", "已合身"),
+        CERT("CERT", "已认证");
+
+        @EnumValue
+        private final String state;
+
+        private final String message;
+
+    }
+
+    // 回调状态
+    @Getter
+    @AllArgsConstructor
+    public enum NotifyState {
+
+        INIT("INIT", "初始化"),
+        IN_PROGRESS("IN_PROGRESS", "远端处理中"),
+        CALL_BACK("CALL_BACK", "已回调");
+
+        @EnumValue
+        private final String state;
+
+        private final String message;
+    }
+
+}

+ 53 - 0
src/main/java/net/yaoyi/gulop/member/auth/constant/enums/state/PmsStatus.java

@@ -0,0 +1,53 @@
+package net.yaoyi.gulop.member.auth.constant.enums.state;
+
+import com.baomidou.mybatisplus.annotation.EnumValue;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * {@code PmsStatus}
+ * <p>
+ * 项目中心状态枚举
+ * </p>
+ *
+ * @author jimmy
+ * @date 3/30/22 15:31
+ */
+public class PmsStatus {
+
+    // 项目发布状态
+    @Getter
+    @AllArgsConstructor
+    public enum ProjectStatus {
+
+        NEW(0, "未发布"),
+        PUBLISHING(1, "发布中"),
+        DONE(2, "已结束"),
+        TERMINATED(3, "已终止");
+
+        @EnumValue
+        private final int status;
+
+        private final String description;
+
+
+    }
+
+    // 审核状态
+    @Getter
+    @AllArgsConstructor
+    public enum ReviewStatus {
+
+        WAITING(0, "未审核"),
+        PASS(1, "通过"),
+        REJECTED(9, "拒绝");
+
+        @EnumValue
+        private final int status;
+
+        private final String description;
+
+
+    }
+
+}

+ 100 - 0
src/main/java/net/yaoyi/gulop/member/auth/constant/enums/state/UmsStatus.java

@@ -0,0 +1,100 @@
+package net.yaoyi.gulop.member.auth.constant.enums.state;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * {@code UmsStatus}
+ * <p>
+ * 会员模块状态枚举
+ *
+ * @author Hengchen.Sun
+ * @version 1.0.0
+ * @date 2022/03/20 21:06
+ */
+public class UmsStatus {
+
+    /**
+     * {@code TaxHelperCertStatus} 税邦云认证枚举
+     *
+     * @author Hengchen.Sun
+     * @version 1.0.0
+     * @date 2021-02-24
+     * @since ver.1.0.0
+     */
+    @Getter
+    @AllArgsConstructor
+    public enum TaxHelperCertStatus {
+
+        // 认证状态
+        UN_CERT(10, "未认证"),
+        ADD_MEMBER(11, "已增员"),
+        CHECKED_UNBIND(12, "已认证未绑卡"),
+        CHECKING_BIND(13, "认证中已绑卡"),
+        CHECKING_UNBIND(14, "认证中未绑卡"),
+        CERT(15, "已认证"),
+        UNCHECKED_BIND(16, "未认证已绑卡");
+
+        private final int code;
+
+        private final String desc;
+
+        /**
+         * 根据枚举编码获取枚举对象
+         *
+         * @param code 枚举编码
+         * @return 如果存在返回枚举,否则返回 {@code null}
+         */
+        public static TaxHelperCertStatus resolve(final int code) {
+            for (TaxHelperCertStatus certStatus : TaxHelperCertStatus.values()) {
+                if (certStatus.getCode() == code) {
+                    return certStatus;
+                }
+            }
+            return UN_CERT;
+
+        }
+    }
+
+    /**
+     * {@code CertStatus}
+     * <p>
+     * 易联数科签约状态枚举
+     *
+     * @author Hengchen.Sun
+     * @author lixuesong
+     * @version 1.0.0
+     * @date 2022/03/09 22:21
+     */
+    @Getter
+    @AllArgsConstructor
+    public enum OladingCertStatus {
+
+        // 签约状态
+        UN_SIGNED("0", "未签约"),
+        CERT("1", "已认证"),
+        SIGNED("2", "已签约"),
+        SIGNING("3", "签约中"),
+        UN_CERT("4", "未认证");
+
+        private final String code;
+
+        private final String message;
+
+        /**
+         * 根据枚举编码获取枚举对象
+         *
+         * @param code 枚举编码
+         * @return 如果存在返回枚举,否则返回 {@code null}
+         */
+        public static OladingCertStatus resolve(final String code) {
+            for (OladingCertStatus oladingCertStatus : OladingCertStatus.values()) {
+                if (oladingCertStatus.getCode().equals(code)) {
+                    return oladingCertStatus;
+                }
+            }
+            return UN_SIGNED;
+
+        }
+    }
+}

+ 32 - 0
src/main/java/net/yaoyi/gulop/member/auth/constant/enums/state/UpmsStatus.java

@@ -0,0 +1,32 @@
+package net.yaoyi.gulop.member.auth.constant.enums.state;
+
+import com.baomidou.mybatisplus.annotation.EnumValue;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * {@code UpmsStatus}
+ * <p>
+ * UPMS状态枚举
+ *
+ * @author Hengchen.Sun
+ * @version 1.0.0
+ * @date 2022/03/19 16:57
+ */
+public final class UpmsStatus {
+
+    // 发布状态
+    @Getter
+    @AllArgsConstructor
+    public enum PublishStatus {
+
+        NEW_ONE(0, "新建"),
+        LAUNCHED(1, "上架"),
+        SOLD_OUT(2, "下架");
+
+        @EnumValue
+        private final int status;
+
+        private final String message;
+    }
+}

+ 50 - 0
src/main/java/net/yaoyi/gulop/member/auth/constant/enums/type/CheckoutType.java

@@ -0,0 +1,50 @@
+package net.yaoyi.gulop.member.auth.constant.enums.type;
+
+import com.baomidou.mybatisplus.annotation.EnumValue;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * {@code CheckoutType} 类
+ * 支付类型常量枚举
+ *
+ * @author Hengchen.Sun
+ * @version 1.0.0
+ * @date 2022-02-27
+ * @since ver.1.0.0
+ */
+public final class CheckoutType {
+
+    // 订单类型
+    @Getter
+    @AllArgsConstructor
+    public enum OrderType {
+
+        NORMAL("NORMAL", "普通订单");
+
+        @EnumValue
+        private final String type;
+
+        private final String message;
+
+    }
+
+
+
+    // 打款方式
+    @Getter
+    @AllArgsConstructor
+    public enum TransferType {
+
+        BALANCE("BALANCE", 0, "账户余额"),
+        BANK_CARD("BANK_CARD", 1, "银行卡");
+
+        @EnumValue
+        private final String type;
+
+        private final int code;
+
+        private final String description;
+
+    }
+}

+ 52 - 0
src/main/java/net/yaoyi/gulop/member/auth/constant/enums/type/CmsType.java

@@ -0,0 +1,52 @@
+package net.yaoyi.gulop.member.auth.constant.enums.type;
+
+import com.baomidou.mybatisplus.annotation.EnumValue;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * {@code CmsType}
+ * <p>
+ * 模块类型
+ *
+ * @author Hengchen.Sun
+ * @version 1.0.0
+ * @date 2022/03/20 21:06
+ */
+public class CmsType {
+
+    /**
+     * 功能属性类型
+     */
+    @Getter
+    @AllArgsConstructor
+    public enum AttrType {
+
+        // 功能属性类型
+        SALES(0, "销售属性"),
+        BASE(1, "基础属性"),
+        SALES_AND_BASE(2, "销售和基础属性");
+
+        @EnumValue
+        private final int type;
+
+        private final String message;
+    }
+
+    /**
+     * 文件来源类型
+     */
+    @Getter
+    @AllArgsConstructor
+    public enum FileSourceType {
+
+        // 文件来源类型
+        PC(0, "PC端"),
+        MINIAPP(1, "小程序端");
+
+        @EnumValue
+        private final int type;
+
+        private final String message;
+    }
+}

+ 226 - 0
src/main/java/net/yaoyi/gulop/member/auth/constant/enums/type/CommonType.java

@@ -0,0 +1,226 @@
+package net.yaoyi.gulop.member.auth.constant.enums.type;
+
+import com.baomidou.mybatisplus.annotation.EnumValue;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * {@code CommonType}
+ * <p>
+ * 类型常量类
+ *
+ * @author Hengchen.Sun
+ * @version 1.0.0
+ * @date 2021/09/06 14:31:39
+ */
+@Getter
+public final class CommonType {
+
+    // 用户职位枚举
+    @Getter
+    @AllArgsConstructor
+    public enum UserPosition {
+
+        // 【高层】 ==================================
+        CEO(0, "CEO", "首席执行官"),
+        COO(1, "COO", "首席运营官"),
+        CFO(2, "CFO", "首席财务官"),
+        CTO(3, "CTO", "首席技术官"),
+        CIO(4, "CIO", "首席信息官 "),
+        CAO(5, "CAO", "首席艺术官 "),
+        CBO(6, "CBO", "首席品牌官 "),
+        CDO(7, "CDO", "首席开发官 "),
+        CHO(8, "CHO", "首席信息官 "),
+        CKO(9, "CKO", "首席知识官 "),
+        CMO(10, "CMO", "首席市场官 "),
+        CNO(11, "CNO", "首席谈判官 "),
+        CPO(12, "CPO", "首席公共官 "),
+        CQO(13, "CQO", "首席质量官 "),
+        CSO(14, "CSO", "首席销售官 "),
+        CVO(15, "CVO", "首席评估官 "),
+
+        // [人力资源部] ===================================
+        HRD(16, "HRD", "人力资源总监 "),
+        HUMAN_RESOURCE_MANAGER(17, "HRM", "人力经理"),
+        ASSISTANT_PERSONNEL_OFFICER(18, "APO", "人力专员"),
+
+        // 【市场与销售】 =================================
+        SALES_DIRECTOR(19, "SD", "销售总监"),
+        SALES_MANAGER(20, "SM", "销售经理"),
+        SALES_REPRESENTATIVE(21, "SR", "销售专员"),
+        SALES_ADMINISTRATOR(22, "SA", "销售助理"),
+        MARKETING_DIRECTOR(23, "MD", "市场总监"),
+        MARKETING_MANAGER(24, "MM", "市场经理"),
+        MARKETING_CONSULTANT(25, "MC", "市场专员"),
+
+        // 【财务】 =================================,
+        FINANCIAL_DIRECTOR(26, "FD", "财务总监"),
+        FINANCIAL_MANAGER(27, "FM", "财务经理"),
+        CASHIER(28, "CASHIER", "出纳"),
+        ACCOUNTING(29, "ACCOUNTING", "会计"),
+
+        // 【开发】 =================================,
+        PROJECT_MANAGER(30, "PM", "项目经理"),
+        DEVELOPMENTAL_ENGINEER(31, "DEV", "开发工程师"),
+        OPERATIONS_ENGINEER(32, "OPS", "运维工程师"),
+
+        // 【运营】 =================================,
+        OPERATIONS_DIRECTOR(33, "OD", "运营总监"),
+        OPERATIONS_MANAGER(34, "OM", "运营经理"),
+        OPERATION_SPECIALIST(35, "OS", "运营专员"),
+
+        // 【行政】 =================================,
+        ADMINISTRATIVE_DIRECTOR(36, "AD", "行政总监"),
+        ADMINISTRATIVE_MANAGER(37, "AM", "行政经理"),
+
+        // 【产品】 =================================,
+        PRODUCT_DIRECTOR(38, "PD", "产品总监"),
+        PRODUCT_MANAGE(39, "PM", "产品经理");
+
+
+        @EnumValue
+        private final int type;
+
+        /**
+         * 缩写
+         */
+        private final String abbrev;
+
+        private final String message;
+    }
+
+    // 用户title
+    @Getter
+    @AllArgsConstructor
+    public enum UserTitle {
+
+        MR(0, "先生"),
+        MRS(1, "夫人"),
+        MS(2, "女士"),
+        MISS(3, "小姐"),
+        DR(4, "博士");
+
+
+        @EnumValue
+        private final int code;
+
+        private final String message;
+    }
+
+    // 性别枚举
+    @Getter
+    @AllArgsConstructor
+    public enum Gender {
+
+        FEMALE("FEMALE", 0, "女性"),
+        MALE("MALE", 1, "男性"),
+        PRIVACY("PRIVACY", 2, "保密");
+
+
+        private final String type;
+
+        @EnumValue
+        private final int code;
+
+        private final String message;
+    }
+
+
+    // 数据权限类型
+    @Getter
+    @AllArgsConstructor
+    public enum DataScopeType {
+
+        ALL(0, "全部"),
+        CUSTOM(1, "自定义"),
+        OWN_CHILD_LEVEL(2, "本级及子级"),
+        OWN_LEVEL(3, "本级");
+
+        /**
+         * 类型
+         */
+        @EnumValue
+        private final int type;
+
+        /**
+         * 描述
+         */
+        private final String message;
+
+    }
+
+
+    /**
+     * {@code DictType}
+     * <p>
+     * 字典类型
+     *
+     * @author Hengchen.Sun
+     * @version 1.0.0
+     * @date 2021/11/10 17:17
+     */
+    @Getter
+    @AllArgsConstructor
+    public enum DictType {
+
+        /**
+         * 字典类型-系统内置(不可修改)
+         */
+        SYSTEM(1, "系统内置"),
+
+        /**
+         * 字典类型-业务类型
+         */
+        BIZ(0, "业务类");
+
+        /**
+         * 类型
+         */
+        private final int type;
+
+        /**
+         * 描述
+         */
+        private final String description;
+
+    }
+
+
+    // 打款方式
+    @Getter
+    @AllArgsConstructor
+    public enum EndType {
+        MEETING("MEETING", "BJ", "会议系统"),
+        CSO("CSO", "SZ", "CSO"),
+        CSO_EASY("CSO_EASY", "SZ", "CSO_EASY"),
+        CSO_DEMO("CSO_DEMO", "SZ", "CSO_DEMO"),
+        HCP("HCP", "BJ", "HCP"),
+        HCP_DEMO("HCP_DEMO", "BJ", "HCP_DEMO"),
+        JY("JY", "BJ", "JY");
+
+        @EnumValue
+        private final String type;
+
+        private final String loc;
+
+        private final String message;
+
+    }
+
+    /**
+     * 登录类型
+     */
+    @Getter
+    @AllArgsConstructor
+    public enum SourceType {
+
+        PC("PC", "PC端"),
+        IOS("IOS", "苹果"),
+        ANDROID("ADNROID", "安卓"),
+        WECHAT_MP("WECHAT_MP", "微信小程序");
+
+        @EnumValue
+        private final String type;
+        private final String description;
+    }
+}

+ 216 - 0
src/main/java/net/yaoyi/gulop/member/auth/constant/enums/type/GigType.java

@@ -0,0 +1,216 @@
+package net.yaoyi.gulop.member.auth.constant.enums.type;
+
+import com.baomidou.mybatisplus.annotation.EnumValue;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * {@code GigType} 类
+ * 结算类型常量枚举
+ *
+ * @author Hengchen.Sun
+ * @version 1.0.0
+ * @date 2022-02-27
+ * @since ver.1.0.0
+ */
+public final class GigType {
+
+
+    // 人员类型
+    @Getter
+    @AllArgsConstructor
+    public enum MemberType {
+        YAOYI_EXCLUSIVE_PERSONNEL("20", "要易专属人员");
+
+        private final String type;
+
+        private final String description;
+
+    }
+
+    // 支付主体类型
+    @Getter
+    @AllArgsConstructor
+    public enum SubjectType {
+
+        //
+        DING("DING", 0, "仁励家"),
+        TAX_HELPOR("TAX_HELPOR", 1, "税邦云"),
+        OLADING("OLADING", 2, "钉零工"),
+        YEE("YEE", 3, "易联数科"),
+        ZHENG_QI_ZHI_XING("ZHENG_QI_ZHI_XING", 4, "正启之星"),
+        LANG_CHAO("LANG_CHAO", 5, "浪潮"),
+        LANG_CHAO_EQUIPMENT("LANG_CHAO_EQUIPMENT", 17, "浪潮装备"),
+        HUI_QI_YUN("HUI_QI_YUN", 6, "汇企云"),
+        ZHONG_YI_YUN("ZHONG_YI_YUN", 7, "众蚁云"),
+        ZHONG_ZHI("ZHONG_ZHI", 8, "中智"),
+        QI_FU("QI_FU", 9, "福建企赋"),
+        SHEN_ZHEN_REN_CAI("SHEN_ZHEN_REN_CAI", 10, "深圳之光"),
+        SHEN_ZHEN_REN_CAI_2("SHEN_ZHEN_REN_CAI_2", 15, "深圳产业园"),
+        YI_MA("YI_MA", 11, "天津易捷"),
+        XIN_SHI_YI("XIN_SHI_YI", 12, "薪事易"),
+        HE_CHUANG("HE_CHUANG", 13, "合创众盈"),
+        XIN_SHI_YI_NEW("XIN_SHI_YI_NEW", 14, "海南飞亿"),
+        QUAN_SI_FU("QUAN_SI_FU", 16, "全思福"),
+        GZLLE("GZLLE", 18, "职乐"),
+        QSF_LJG("QSF_LJG", 19, "全思福_灵就宫"),
+        QSF_SHQZ("QSF_SHQZ", 20, "全思福_上海鹊诊"),
+        DONG_JIN_REN_CAI("DONG_JIN_REN_CAI",21, "东进人才"),
+        FESCO("FESCO", 22, "北京外企"),
+        CLOUD_ACCOUNT("CLOUD_ACCOUNT", 23, "云账号"),
+        FESCO_GUANGXI("FESCO_GUANGXI", 24, "广西有为开放平台"),
+        GULOP("GULOP", 99, "GULOP");
+
+        private final String type;
+
+        @EnumValue
+        private final int code;
+
+        private final String message;
+
+    }
+
+    /**
+     * 结算通道
+     */
+    @Getter
+    @AllArgsConstructor
+    public enum SubjectChannel {
+
+        DEFAULT("DEFAULT", SubjectType.OLADING, "海南飞亿"),
+        CICC("CICC", SubjectType.TAX_HELPOR, "中金"),
+        PING_AN_BANK("PING_AN_BANK", SubjectType.TAX_HELPOR, "平安银行");
+
+        @EnumValue
+        private final String type;
+
+        private final SubjectType subjectType;
+
+        private final String description;
+    }
+
+    /**
+     * 结算税源地
+     */
+    @Getter
+    @AllArgsConstructor
+    public enum SubjectLocation {
+        // 云账号
+        CLOUD_ACCOUNT("CLOUD_ACCOUNT", SubjectType.CLOUD_ACCOUNT, "云账号"),
+
+        // 原力
+        YUAN_LI("YUAN_LI", SubjectType.DONG_JIN_REN_CAI, "原力-东进人才"),
+        YLKJ_HNYX("YLKJ_HNYX", SubjectType.DONG_JIN_REN_CAI, "原力科技-河南远忻"),
+        HENANYUANQI("HENANYUANQI", SubjectType.DONG_JIN_REN_CAI, "河南远忻"),
+
+        // 仁励家通道
+        REN_LI_JIA("REN_LI_JIA", SubjectType.DING, "仁励家"),
+
+        // 税邦云通道
+        ZHONG_YING("ZHONG_YING", SubjectType.TAX_HELPOR, "湖南众盈"),
+        LUO_SHU_YUN("LUO_SHU_YUN", SubjectType.TAX_HELPOR, "永州罗数云"),
+
+        // 钉零工通道
+        FEI_YI("FEI_YI", SubjectType.OLADING, "海南飞亿"),
+        JIN_YUAN("JIN_YUAN", SubjectType.OLADING, "金园数科"),
+        XIN_TAI_ZI("XIN_TAI_ZI", SubjectType.OLADING, "河南薪泰梓"),
+        ZHAO_YU("ZHAO_YU", SubjectType.OLADING, "河南兆宇"),
+
+        // 浪潮通道
+        LANG_CHAO("LANG_CHAO", SubjectType.LANG_CHAO, "浪潮"),
+        LANG_CHAO_EQUIPMENT("LANG_CHAO_EQUIPMENT", SubjectType.LANG_CHAO, "浪潮装备"),
+
+        // 汇企云
+        HUI_QI_YUN("HUI_QI_YUN", SubjectType.HUI_QI_YUN, "汇企云"),
+
+        // 众蚁云
+        ZHONG_YI_YUN("ZHONG_YI_YUN", SubjectType.ZHONG_YI_YUN, "众蚁云"),
+
+        // 中智
+        ZHONG_ZHI("ZHONG_ZHI", SubjectType.ZHONG_ZHI, "中智"),
+
+        // 福建企赋
+        QI_FU("QI_FU", SubjectType.QI_FU, "福建企赋"),
+
+        // 深圳人才
+        SHEN_ZHEN_REN_CAI("SHEN_ZHEN_REN_CAI", SubjectType.SHEN_ZHEN_REN_CAI, "睢宁天泰"),
+        HE_NAN_ZHAO_AN("HE_NAN_ZHAO_AN", SubjectType.SHEN_ZHEN_REN_CAI, "河南兆安"),
+        // 深圳人才2
+        SHEN_ZHEN_REN_CAI_2("SHEN_ZHEN_REN_CAI_2", SubjectType.SHEN_ZHEN_REN_CAI_2, "睢宁天泰"),
+        HE_NAN_ZHAO_AN_2("HE_NAN_ZHAO_AN_2", SubjectType.SHEN_ZHEN_REN_CAI_2, "河南兆安"),
+        HEI_NAN_YAO_YUN("HEI_NAN_YAO_YUN", SubjectType.SHEN_ZHEN_REN_CAI, "深圳之光-河南耀运"),
+
+        // 易马财税
+        YI_MA("YI_MA", SubjectType.YI_MA, "天津易捷"),
+
+        // 合创众盈
+        HE_CHUANG_ZHONG_ZHI("HE_CHUANG_ZHONG_ZHI", SubjectType.HE_CHUANG, "合创众盈-天津中智"),
+        HE_CHUANG_REN_CAI_YUN("HE_CHUANG_REN_CAI_YUN", SubjectType.HE_CHUANG, "合创众盈-福州人才云"),
+        BEIJING_DEWEI("BEIJING_DEWEI", SubjectType.HE_CHUANG, "北京德威-开封广招"),
+        KAIFENG_GUANGZHAO("KAIFENG_GUANGZHAO", SubjectType.HE_CHUANG, "开封广招"),
+        FZRC_KFGZ("FZRC_KFGZ", SubjectType.HE_CHUANG, "福州人才-开封广招"),
+
+        XIN_SHI_YI("XIN_SHI_YI", SubjectType.XIN_SHI_YI, "薪事易"),
+        XIN_SHI_YI_NEW("XIN_SHI_YI_NEW", SubjectType.XIN_SHI_YI_NEW, "海南飞亿"),
+
+        // 正启之星
+        ZHENG_QI_ZHI_XING("ZHENG_QI_ZHI_XING", SubjectType.ZHENG_QI_ZHI_XING, "正启之星"),
+        JIANG_XI("JIANG_XI", SubjectType.ZHENG_QI_ZHI_XING, "正启之星-江西"),
+        GZLLE("GZLLE", SubjectType.GZLLE, "正启之星-湖南"),
+        GZLLE_HUBEI("GZLLE_HUBEI", SubjectType.GZLLE, "正启之星-湖北"),
+
+        // 全思福
+        QUAN_SI_FU("QUAN_SI_FU", SubjectType.QUAN_SI_FU, "全思福"),
+        QSF_LJG("QSF_LJG", SubjectType.QSF_LJG, "全思福_灵就宫"),
+        QSF_SHQZ("QSF_SHQZ", SubjectType.QSF_SHQZ, "全思福_上海鹊诊"),
+
+        // 北京外企
+        FESCO("FESCO", SubjectType.FESCO, "北京外企"),
+        FESCO_GUANGXI("FESCO_GUANGXI", SubjectType.FESCO_GUANGXI, "广西有为平台"),
+        ;
+
+
+        @EnumValue
+        private final String location;
+
+        private final SubjectType subjectType;
+
+        private final String description;
+    }
+
+
+    /**
+     * 认证步骤
+     */
+    @Getter
+    @AllArgsConstructor
+    public enum CertStep {
+
+        UPLOAD_ID_CARD("UPLOAD_ID_CARD", "上传身份证"),
+        UPLOAD_FACE_ID("UPLOAD_FACE_ID", "上传核身视频"),
+        ALL("ALL", "全部"),
+        ;
+
+        @EnumValue
+        private final String type;
+
+        private final String description;
+    }
+
+    /**
+     * 认证步骤
+     */
+    @Getter
+    @AllArgsConstructor
+    public enum BankCardStep {
+
+        CAPTCHA("CAPTCHA", "获取绑卡验证码"),
+        BOUND("BOUND", "绑卡操作");
+
+        @EnumValue
+        private final String type;
+
+        private final String description;
+    }
+
+}

+ 15 - 0
src/main/java/net/yaoyi/gulop/member/auth/constant/enums/type/PmsType.java

@@ -0,0 +1,15 @@
+package net.yaoyi.gulop.member.auth.constant.enums.type;
+
+/**
+ * {@code PmsType}
+ * <p>
+ *   项目中心类型枚举
+ * </p>
+ *
+ * @author jimmy
+ * @date 3/30/22 21:37
+ */
+public class PmsType {
+
+
+}

+ 34 - 0
src/main/java/net/yaoyi/gulop/member/auth/constant/enums/type/UmsType.java

@@ -0,0 +1,34 @@
+package net.yaoyi.gulop.member.auth.constant.enums.type;
+
+import com.baomidou.mybatisplus.annotation.EnumValue;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * {@code UmsType}
+ * <p>
+ * 会员模块类型
+ *
+ * @author Hengchen.Sun
+ * @version 1.0.0
+ * @date 2022/03/20 21:06
+ */
+public class UmsType {
+
+    /**
+     * 身份信息类型
+     */
+    @Getter
+    @AllArgsConstructor
+    public enum IdentificationType {
+        PASSPORT("PASSWORD", 0, "护照"),
+        ID_CARD("ID_CARD", 1, "身份证");
+
+        @EnumValue
+        private final String type;
+
+        private final int code;
+
+        private final String description;
+    }
+}

+ 93 - 0
src/main/java/net/yaoyi/gulop/member/auth/constant/enums/type/UpmsType.java

@@ -0,0 +1,93 @@
+package net.yaoyi.gulop.member.auth.constant.enums.type;
+
+import com.baomidou.mybatisplus.annotation.EnumValue;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * {@code UpmsType}
+ * <p>
+ * UPMS类型枚举
+ *
+ * @author Hengchen.Sun
+ * @version 1.0.0
+ * @date 2022/03/19 16:58
+ */
+public final class UpmsType {
+
+
+    // 菜单类型
+    @Getter
+    @AllArgsConstructor
+    public enum MenuType {
+
+        LEFT_MENU(0, "left"),
+        BUTTON(1, "button"),
+        TOP_MENU(2, "top");
+
+
+        @EnumValue
+        private final int type;
+
+        private final String message;
+
+    }
+
+    // 功能属性类型
+    @Getter
+    @AllArgsConstructor
+    public enum FuncAttrType {
+        SALES(0, "销售属性"),
+        BASE(1, "基础属性"),
+        SALES_AND_BASE(2, "销售和基础属性");
+
+        @EnumValue
+        private final int type;
+
+        private final String message;
+    }
+
+
+    // 企业类型枚举
+    @Getter
+    @AllArgsConstructor
+    public enum EnterpriseType {
+
+        PME(0, "药品生产企业"),
+        CSO(1, "合同销售组织"),
+        CRO(2, "合同研究组织"),
+        CMO(3, "合同生产组织"),
+        MEDICAL_ASSOCIATION(4, "医药协会"),
+        MEDICAL_FOUNDATION(5, "基金会"),
+        YYC(8, "要易云"),
+        GULOP(9, "GULOP");
+
+        @EnumValue
+        private final int type;
+
+        private final String message;
+    }
+
+    /**
+     * 公共参数类型枚举
+     */
+    @Getter
+    @AllArgsConstructor
+    public enum PublicParamType {
+
+        DEFAULT(0, "默认"),
+        SEARCH(1, "检索"),
+        TEXT(2, "原文"),
+        STATEMENT(3, "报表"),
+        DOCUMENT(5, "文档"),
+        MESSAGE(6, "消息"),
+        OTHER(9, "其他");
+
+        @EnumValue
+        private final int code;
+
+        private final String message;
+    }
+
+
+}

+ 110 - 0
src/main/java/net/yaoyi/gulop/member/auth/controller/MemberAuthController.java

@@ -0,0 +1,110 @@
+package net.yaoyi.gulop.member.auth.controller;
+
+import jakarta.validation.Valid;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import net.yaoyi.gulop.member.auth.dto.FourElementAuthDto;
+import net.yaoyi.gulop.member.auth.dto.QualificationDTO;
+import net.yaoyi.gulop.member.auth.dto.ThreeElementAuthDTO;
+import net.yaoyi.gulop.member.auth.entity.UmsMember;
+import net.yaoyi.gulop.member.auth.service.MemberAuthService;
+import net.yaoyi.gulop.member.auth.util.CommonResult;
+import net.yaoyi.gulop.member.auth.util.MinioUtil;
+import org.springframework.http.MediaType;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.util.Map;
+
+
+@Slf4j
+@RestController
+@RequestMapping("/api/member/auth")
+@CrossOrigin(origins = "http://localhost:8080",
+        allowedHeaders = "*",
+        methods = {RequestMethod.GET, RequestMethod.POST})
+//@Api(tags = "用户认证服务接口")
+@RequiredArgsConstructor
+public class MemberAuthController {
+
+    private final MinioUtil minioUtil;
+
+    private final MemberAuthService authService;
+
+
+
+    //获取所有的认证信息
+    @GetMapping("/get-auth_info")
+    public CommonResult getAuthInfo(){
+        return CommonResult.ok(authService.getAuthInfo(2L));
+
+    }
+
+
+
+    //个人运营商三要素认证
+    @PostMapping("/three-element")
+    public CommonResult threeElementAuth(@RequestBody Map<String, Object> request) {
+        try{
+            return authService.threeElementAuth(request,2L);
+        }catch (Exception e){
+            e.printStackTrace();
+
+            return CommonResult.failed(e.getMessage());
+        }
+    }
+
+
+    /**
+     * OCR身份证识别
+     * author admin@fyang.vip
+     * @param file
+     * @return
+     */
+    @PostMapping(value = "/ocr",consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
+    public CommonResult uploadOcr(@RequestParam("file") MultipartFile file, @RequestParam("type") String type) {
+        try {
+            String fileUrl = minioUtil.uploadFile(file);
+            return authService.ocrAuth(fileUrl,type,2L);
+        } catch (Exception e) {
+            return CommonResult.failed("上传失败: " + e.getMessage());
+        }
+    }
+
+
+    //请求个人人脸活体认证
+    @PostMapping("/person-face")
+    public CommonResult personFace(
+            @RequestBody @Valid ThreeElementAuthDTO request) {
+        return authService.personFace(request,2L);
+    }
+
+    //个人银行卡四要素比对
+    @PostMapping("/four-element")
+    public CommonResult fourElementAuth(
+            @RequestBody @Valid FourElementAuthDto request) {
+        return authService.fourElementAuth(request,2L);
+    }
+
+    //保存资质证书
+    @PostMapping("/check-certificate")
+    public CommonResult checkCertificate(
+            @RequestBody @Valid QualificationDTO qualificationDTO) {
+        return authService.checkCertificate(qualificationDTO,2L);
+    }
+
+    @PostMapping("/upload")
+    public CommonResult upload(@RequestParam("file") MultipartFile file) {
+        try {
+            log.info("开始上传");
+            String fileUrl = minioUtil.uploadFile(file);
+            return CommonResult.ok(fileUrl);
+        } catch (Exception e) {
+            return CommonResult.failed("上传失败: " + e.getMessage());
+        }
+    }
+
+
+
+}
+

+ 82 - 0
src/main/java/net/yaoyi/gulop/member/auth/controller/SignContractController.java

@@ -0,0 +1,82 @@
+package net.yaoyi.gulop.member.auth.controller;
+
+
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import net.yaoyi.gulop.member.auth.service.GigConfigService;
+import net.yaoyi.gulop.member.auth.util.CommonResult;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.Map;
+
+@Slf4j
+@RestController
+@RequestMapping("/api/sign/contract")
+@CrossOrigin(origins = "http://localhost:8080",
+        allowedHeaders = "*",
+        methods = {RequestMethod.GET, RequestMethod.POST})
+//@Api(tags = "用户认证服务接口")
+@RequiredArgsConstructor
+public class SignContractController {
+
+    private final GigConfigService gigConfigService;
+
+    //获取签约列表
+    @GetMapping("/contract-list")
+    public CommonResult getList() {
+
+        return CommonResult.ok(gigConfigService.getSignList(2L));
+    }
+
+    //签约
+    @PostMapping("/sign")
+    public CommonResult signContract(@RequestBody Map<String, Long> request) {
+        try {
+            if (request.get("config_id") == null){
+                throw new RuntimeException("获取签约信息失败");
+            }
+            return gigConfigService.signContract(request.get("config_id"),2L );
+        }catch (Exception e) {
+            e.printStackTrace();
+            return CommonResult.failed(e.getMessage());
+        }
+    }
+
+    //查看合同信息(同步合同信息和状态)
+    @PostMapping("/sign-check")
+    public CommonResult signCheck(@RequestBody Map<String, String> request) {
+        try {
+            if (!request.containsKey("contract_id")){
+                throw new RuntimeException("获取签约信息失败");
+            }
+            return gigConfigService.signCheck(Long.valueOf(request.get("contract_id")),2L );
+        }catch (Exception e) {
+            e.printStackTrace();
+            return CommonResult.failed(e.getMessage());
+        }
+    }
+
+
+    //获取报告列表
+    @GetMapping("/report-list")
+    public CommonResult getReportList() {
+
+        return CommonResult.ok(gigConfigService.getReportList());
+    }
+
+
+    //下载报告
+    @PostMapping("/report-down")
+    public CommonResult getReportDown(@RequestBody Map<String, Long> request) {
+        try {
+            if (request.get("config_id") == null){
+                throw new RuntimeException("获取报告信息失败");
+            }
+            return gigConfigService.getReportDown(request.get("config_id"));
+        }catch (Exception e) {
+            e.printStackTrace();
+            return CommonResult.failed(e.getMessage());
+        }
+        }
+
+}

+ 25 - 0
src/main/java/net/yaoyi/gulop/member/auth/dto/FourElementAuthDto.java

@@ -0,0 +1,25 @@
+package net.yaoyi.gulop.member.auth.dto;
+
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import jakarta.validation.constraints.Pattern;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+//@ApiModel("银行卡四要素认证请求")
+public class FourElementAuthDto {
+    @NotBlank(message = "银行卡号不能为空")
+    private String cardNumber;
+
+    @NotNull(message = "开户行不能为空")
+    private String bankName;
+
+    @NotNull(message = "手机号码不能为空")
+    private String phone;
+
+}

+ 18 - 0
src/main/java/net/yaoyi/gulop/member/auth/dto/QualificationDTO.java

@@ -0,0 +1,18 @@
+package net.yaoyi.gulop.member.auth.dto;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.List;
+
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class QualificationDTO {
+
+    private String recordNumber;       // 记录编号
+    private List<String> medicalFiles; // 资质证书
+    private List<String> otherFiles;   // 其他资质证书
+}

+ 47 - 0
src/main/java/net/yaoyi/gulop/member/auth/dto/ThreeElementAuthDTO.java

@@ -0,0 +1,47 @@
+package net.yaoyi.gulop.member.auth.dto;
+
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import jakarta.validation.constraints.Pattern;
+import lombok.Data;
+
+@Data
+public class ThreeElementAuthDTO {
+    @NotBlank(message = "姓名不能为空")
+    private String realname;
+
+    @NotNull(message = "身份证反面照不能为空")
+    private String back;
+
+    @NotNull(message = "身份证反面照不能为空")
+    private String front;
+
+    @NotBlank(message = "性别不能为空")
+    private String sex;
+
+    @NotBlank(message = "性别不能为空")
+    private String nation;
+
+    @NotBlank(message = "生日不能为空")
+    private String born;
+
+    @NotBlank(message = "籍贯不能为空")
+    private String address;
+
+    @NotBlank(message = "身份证号码不能为空")
+    private String idcard;
+
+    @NotBlank(message = "生效日期不能为空")
+    private String begin;
+
+    @NotBlank(message = "截止日期不能为空")
+    private String end;
+
+    @NotBlank(message = "发放机关不能为空")
+    private String department;
+
+    private String serialNo;
+    private String captcha;
+
+
+}

+ 133 - 0
src/main/java/net/yaoyi/gulop/member/auth/entity/GigConfig.java

@@ -0,0 +1,133 @@
+package net.yaoyi.gulop.member.auth.entity;
+
+import com.baomidou.mybatisplus.annotation.*;
+import lombok.Data;
+import lombok.ToString;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+
+/**
+ * {@code GigConfig}
+ * <p>
+ * 零工配置
+ *
+ * @author Hengchen.Sun
+ * @version 1.0.0
+ * @date 2022/03/03 18:05
+ */
+@Data
+@ToString(callSuper = true)
+@TableName(value = "gig_config")
+public class GigConfig {
+    /**
+     * id
+     */
+    @TableId(value = "config_id", type = IdType.AUTO)
+    private Long id;
+
+    /**
+     * 企业三合一码
+     */
+    @TableField(value = "ent_code")
+    private String enterpriseCode;
+
+
+    /**
+     * 主体名称
+     */
+    @TableField(value = "subject_name")
+    private String subjectName;
+
+    /**
+     * 结算类型
+     */
+    @TableField(value = "subject_type")
+    private String subjectType;
+
+    /**
+     * 结算通道
+     */
+    private String subjectChannel;
+
+    /**
+     * 结算税源地
+     */
+    private String subjectLocation;
+
+    /**
+     * 锁定标记
+     */
+    @TableLogic
+    @TableField(fill = FieldFill.INSERT)
+    private int optFlag;
+
+    /**
+     * 默认标记
+     */
+    @TableField(value = "def_flag")
+    private Boolean defaultFlag;
+
+
+    /**
+     * 端类型
+     */
+    private String endType;
+
+    /**
+     * 创建时间
+     */
+    @TableField(value = "created_time", fill = FieldFill.INSERT)
+    private LocalDateTime createdTime;
+
+    /**
+     * 更新时间
+     */
+    @TableField(value = "modified_time", fill = FieldFill.INSERT_UPDATE)
+    private LocalDateTime modifiedTime;
+
+    @TableField(value = "agreement_url")
+    private String agreementUrl;
+
+    @TableField(value = "agreement_template")
+    private String agreementTemplate;
+
+    //企业account
+    @TableField(value = "enterprise_account")
+    private String enterpriseAccount;
+
+    //平台account
+    @TableField(value = "platform_account")
+    private String platformAccount;
+
+    //第三方
+    @TableField(value = "reception_account")
+    private String receptionAccount;
+
+
+    /**
+     * 渠道配额
+     */
+    @TableField(exist = false)
+    private BigDecimal subjectQuota;
+
+
+
+
+    /**
+     * 是否需要签约
+     */
+    @TableField(value = "is_sign")
+    private int isSign;
+
+    //文件类型  文件类型 0 是协议 1是报告
+    @TableField(value = "is_file_type")
+    private int isFileType;
+
+
+    @TableField(exist = false) // 标记非数据库字段
+    private SmartContract contractInfo;
+
+
+
+}

+ 85 - 0
src/main/java/net/yaoyi/gulop/member/auth/entity/SmartContract.java

@@ -0,0 +1,85 @@
+package net.yaoyi.gulop.member.auth.entity;
+
+import com.alibaba.fastjson.JSONObject;
+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 com.baomidou.mybatisplus.extension.activerecord.Model;
+import com.baomidou.mybatisplus.extension.handlers.FastjsonTypeHandler;
+import jakarta.persistence.*;
+import lombok.Data;
+import net.yaoyi.gulop.member.auth.constant.enums.ContractStatus;
+import net.yaoyi.gulop.member.auth.handler.ContractStatusTypeHandler;
+
+import java.time.LocalDateTime;
+
+@Data
+@TableName("smart_contract")
+public class SmartContract  extends Model<SmartContract> {
+
+    @TableId(type = IdType.AUTO)
+    private Long contractId;
+
+    private String contractNo;
+
+    private Long configId;
+
+    private String contractTitle;
+
+
+    private String contractContent;
+
+
+    @TableField(typeHandler = ContractStatusTypeHandler.class)
+    private ContractStatus contractStatus;
+
+  /*  @TableField(typeHandler = ContractStatusTypeHandler.class)
+    private ContractStatus userSignStatus;
+
+    @TableField(typeHandler = ContractStatusTypeHandler.class)
+    private ContractStatus enterpriseSignStatus;
+
+    @TableField(typeHandler = ContractStatusTypeHandler.class)
+    private ContractStatus platformSignStatus;*/
+
+
+    private LocalDateTime createTime = LocalDateTime.now();
+
+    private LocalDateTime expireTime;
+
+    private LocalDateTime updateTime;
+
+    private Long userId;
+
+
+
+
+
+
+    /*private String userSignUrl;
+    private String enterpriseSignUrl;
+    private String platformSignUrl;*/
+
+    private String userAccount;
+
+    private String enterpriseAccount;
+
+    @TableField(value = "platform_account")
+    private String platformAccount;
+
+    @TableField(value = "preview_url")
+    private String previewUrl;
+
+
+    @TableField(typeHandler = FastjsonTypeHandler.class)
+    private JSONObject signInfo; // 签约信息
+
+
+    private LocalDateTime completeTime;
+
+    @PreUpdate
+    protected void onUpdate() {
+        this.updateTime = LocalDateTime.now();
+    }
+}

+ 38 - 0
src/main/java/net/yaoyi/gulop/member/auth/entity/UmsExtApiHis.java

@@ -0,0 +1,38 @@
+package net.yaoyi.gulop.member.auth.entity;
+
+import com.alibaba.fastjson.JSONObject;
+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 com.baomidou.mybatisplus.extension.handlers.FastjsonTypeHandler;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+@Data
+@TableName("ums_ext_api_his")
+public class UmsExtApiHis {
+
+    @TableId(type = IdType.AUTO)
+    private Long id;
+
+    private String extId;
+
+    private Long userId;
+
+    private String apiName;
+
+    @TableField(typeHandler = FastjsonTypeHandler.class)
+    private JSONObject requestData;
+
+    @TableField(typeHandler = FastjsonTypeHandler.class)
+    private JSONObject responseData;
+
+    private Integer status;
+
+    private LocalDateTime createTime;
+
+    // 省略getter/setter方法
+    // 实际开发中请使用lombok或手动添加
+}

+ 44 - 0
src/main/java/net/yaoyi/gulop/member/auth/entity/UmsExtConfig.java

@@ -0,0 +1,44 @@
+package net.yaoyi.gulop.member.auth.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.baomidou.mybatisplus.extension.activerecord.Model;
+import jakarta.persistence.Temporal;
+import jakarta.persistence.TemporalType;
+import lombok.Data;
+
+import java.util.Date;
+
+@Data
+@TableName("ums_ext_config")
+public class UmsExtConfig  extends Model<UmsExtConfig>{
+
+    @TableId(type = IdType.AUTO)
+    private Long id;
+
+    private String extId;
+
+    private String extName;
+
+    private String extStatusConfig;
+
+    private String extKey;
+
+    private String extSecret;
+
+    private Long createBy;
+
+    @Temporal(TemporalType.TIMESTAMP)
+    private Date createTime;
+
+    private Long updateBy;
+
+    @Temporal(TemporalType.TIMESTAMP)
+    private Date updateTime;
+
+    private Integer delFlag = 0;
+
+    private Integer lockFlag = 0;
+
+}

+ 64 - 0
src/main/java/net/yaoyi/gulop/member/auth/entity/UmsMember.java

@@ -0,0 +1,64 @@
+package net.yaoyi.gulop.member.auth.entity;
+
+import com.alibaba.fastjson.JSONObject;
+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 com.baomidou.mybatisplus.extension.activerecord.Model;
+import com.baomidou.mybatisplus.extension.handlers.FastjsonTypeHandler;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.util.Date;
+
+@Data
+
+@TableName("ums_member")
+@EqualsAndHashCode(callSuper = true)
+public class UmsMember extends Model<UmsMember>{
+
+    @TableId(value = "id", type = IdType.AUTO)
+    private Long id; // 用户ID(即member_id)
+
+    private String phone;
+
+    private Integer isThreeAuth; // 三要素认证 0-未认证 1-已认证
+
+    private Integer isFourAuth; // 四要素认证 0-未认证 1-已认证
+
+    private Integer isOcrAuth; // OCR认证 0-未认证 1-已认证
+
+    private Integer isLivenessAuth; // 活体认证 0-未认证 1-已认证
+
+    private Integer isEsignAuth; // 电子签认证 0-未认证 1-已认证
+
+    @TableField(value = "three_auth_info",typeHandler = FastjsonTypeHandler.class)
+    private JSONObject threeAuthInfo; // 三要素认证数据(姓名/身份证/手机号)
+
+    @TableField(typeHandler = FastjsonTypeHandler.class)
+    private JSONObject fourAuthInfo; // 四要素认证数据(+银行卡号)
+
+    @TableField(typeHandler = FastjsonTypeHandler.class)
+    private JSONObject ocrInfo; // OCR识别数据(身份证正反面/有效期)
+
+    @TableField(typeHandler = FastjsonTypeHandler.class)
+    private JSONObject certificateInfo;
+
+    @TableField(typeHandler = FastjsonTypeHandler.class)
+    private JSONObject livenessInfo; // 活体检测数据(视频/分数)
+
+    @TableField(typeHandler = FastjsonTypeHandler.class)
+    private JSONObject esignInfo; // 电子签数据(合同/签章位置)
+
+    private String createBy;
+    private String userSealNo;//印章编号
+    private Date createTime;
+    private String updateBy;
+    private Date updateTime;
+
+    private Integer delFlag; // 删除标记 0-正常 1-已删除
+    private Integer lockFlag; // 锁定标记 0-可编辑 1-只读
+
+
+}

+ 85 - 0
src/main/java/net/yaoyi/gulop/member/auth/entity/UserAgreementRecord.java

@@ -0,0 +1,85 @@
+package net.yaoyi.gulop.member.auth.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.baomidou.mybatisplus.extension.activerecord.Model;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+import java.io.Serializable;
+import java.time.LocalDateTime;
+
+/**
+ * {@code UserAgreementRecord}
+ * <p>
+ * 用户协议记录
+ *
+ * @author Hengchen.Sun
+ * @version 1.0.0
+ * @date 2022/03/08 12:54
+ */
+@Data
+@ToString(callSuper = true)
+@EqualsAndHashCode(callSuper = true)
+@TableName(value = "gig_user_agreement_record")
+public class UserAgreementRecord extends Model<UserAgreementRecord> {
+
+    /**
+     * id
+     */
+    @TableId(value = "record_id", type = IdType.AUTO)
+    private Long id;
+
+
+    private Long userId;
+
+
+    /**
+     * 会员真实名称
+     */
+    private String realName;
+
+    /**
+     * 会员用户名
+     */
+    private String username;
+
+    /**
+     * 所属企业三合一码
+     */
+    private String enterpriseCode;
+
+    /**
+     * 渠道
+     */
+    private String subjectType;
+
+    /**
+     * 协议明文
+     */
+    private String agreement;
+
+    /**
+     * 协议pdf路径
+     */
+    private String agreementUrl;
+
+    /**
+     * 签名图片地址
+     */
+    private String signImageUrl;
+
+    /**
+     * 签约时间
+     */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private LocalDateTime signTime;
+
+    /**
+     * 用户签约状态
+     */
+    private int status;
+}

+ 39 - 0
src/main/java/net/yaoyi/gulop/member/auth/handler/ContractStatusTypeHandler.java

@@ -0,0 +1,39 @@
+package net.yaoyi.gulop.member.auth.handler;
+
+import lombok.extern.slf4j.Slf4j;
+import net.yaoyi.gulop.member.auth.constant.enums.ContractStatus;
+import org.apache.ibatis.type.BaseTypeHandler;
+import org.apache.ibatis.type.JdbcType;
+import java.sql.CallableStatement;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+@Slf4j
+public class ContractStatusTypeHandler extends BaseTypeHandler<ContractStatus> {
+
+    @Override
+    public void setNonNullParameter(PreparedStatement ps, int i, ContractStatus parameter, JdbcType jdbcType) throws SQLException {
+        // 将枚举的 code 值写入数据库
+        ps.setInt(i, parameter.getCode());
+    }
+
+    @Override
+    public ContractStatus getNullableResult(ResultSet rs, String columnName) throws SQLException {
+        // 从数据库读取 code 值并转换为枚举
+        int code = rs.getInt(columnName);
+        return ContractStatus.fromCode(code);
+    }
+
+    @Override
+    public ContractStatus getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
+        int code = rs.getInt(columnIndex);
+        return ContractStatus.fromCode(code);
+    }
+
+    @Override
+    public ContractStatus getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
+        int code = cs.getInt(columnIndex);
+        return ContractStatus.fromCode(code);
+    }
+}

+ 45 - 0
src/main/java/net/yaoyi/gulop/member/auth/handler/JSONObjectTypeHandler.java

@@ -0,0 +1,45 @@
+package net.yaoyi.gulop.member.auth.handler;
+
+import com.alibaba.fastjson.JSONObject;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.ibatis.type.BaseTypeHandler;
+import org.apache.ibatis.type.JdbcType;
+import org.apache.ibatis.type.MappedJdbcTypes;
+import org.apache.ibatis.type.MappedTypes;
+
+import java.sql.*;
+
+@Slf4j
+@MappedTypes(JSONObject.class)
+@MappedJdbcTypes(JdbcType.VARCHAR)
+public class JSONObjectTypeHandler extends BaseTypeHandler<JSONObject> {
+
+    // 将 JSONObject 序列化为 UTF-8 字符串写入数据库
+    @Override
+    public void setNonNullParameter(PreparedStatement ps, int i, JSONObject parameter, JdbcType jdbcType) throws SQLException {
+        ps.setString(i, parameter.toJSONString());
+    }
+
+    // 从数据库读取字符串并反序列化为 JSONObject
+    @Override
+    public JSONObject getNullableResult(ResultSet rs, String columnName) throws SQLException {
+        String jsonString = rs.getString(columnName);
+        return parseJson(jsonString);
+    }
+
+    @Override
+    public JSONObject getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
+        String jsonString = rs.getString(columnIndex);
+        return parseJson(jsonString);
+    }
+
+    @Override
+    public JSONObject getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
+        String jsonString = cs.getString(columnIndex);
+        return parseJson(jsonString);
+    }
+
+    private JSONObject parseJson(String jsonString) {
+        return jsonString == null ? null : JSONObject.parseObject(jsonString);
+    }
+}

+ 28 - 0
src/main/java/net/yaoyi/gulop/member/auth/mapper/GigConfigMapper.java

@@ -0,0 +1,28 @@
+package net.yaoyi.gulop.member.auth.mapper;
+
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import net.yaoyi.gulop.member.auth.entity.GigConfig;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+import org.apache.ibatis.annotations.Select;
+
+import java.util.List;
+
+@Mapper
+public interface GigConfigMapper extends BaseMapper<GigConfig> {
+
+    @Select("SELECT * FROM gig_config WHERE is_sign = 1")
+    List<GigConfig> getSignList();
+
+
+    @Select("SELECT COUNT(*) FROM gig_user_agreement_record " +
+            "WHERE enterprise_code = #{entCode} AND username = #{username} " +
+            "AND subject_type = #{subjectType} AND status = 1")
+    int existsSignedRecord(@Param("entCode") String entCode,
+                           @Param("username") String username,
+                           @Param("subjectType") int subjectType);
+
+    @Select("SELECT * FROM gig_config WHERE is_sign = 1 and config_id = #{configId}")
+    GigConfig getSignAgreement(Long configId);
+}

+ 24 - 0
src/main/java/net/yaoyi/gulop/member/auth/mapper/SmartContractMapper.java

@@ -0,0 +1,24 @@
+package net.yaoyi.gulop.member.auth.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import net.yaoyi.gulop.member.auth.entity.SmartContract;
+import net.yaoyi.gulop.member.auth.entity.UserAgreementRecord;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+import org.apache.ibatis.annotations.Select;
+
+import java.util.Map;
+
+@Mapper
+public interface SmartContractMapper extends BaseMapper<SmartContract> {
+
+
+    @Select("SELECT * FROM smart_contract " +
+            "WHERE config_id = #{configId} and user_id = #{userId} LIMIT 1")
+    SmartContract getSignAgreementByConfig(Long configId,Long userId);
+
+
+    @Select("SELECT * FROM smart_contract " +
+            "WHERE contract_id = #{contractId} and user_id = #{userId} LIMIT 1")
+    SmartContract getSignAgreementById(Long contractId,Long userId);
+}

+ 10 - 0
src/main/java/net/yaoyi/gulop/member/auth/mapper/UmsExtApiHisMapper.java

@@ -0,0 +1,10 @@
+package net.yaoyi.gulop.member.auth.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import net.yaoyi.gulop.member.auth.entity.UmsExtApiHis;
+import org.apache.ibatis.annotations.Mapper;
+
+@Mapper
+public interface UmsExtApiHisMapper extends BaseMapper<UmsExtApiHis> {
+
+}

+ 20 - 0
src/main/java/net/yaoyi/gulop/member/auth/mapper/UmsExtConfigMapper.java

@@ -0,0 +1,20 @@
+package net.yaoyi.gulop.member.auth.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import net.yaoyi.gulop.member.auth.entity.UmsExtConfig;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+import org.apache.ibatis.annotations.Select;
+
+@Mapper
+public interface UmsExtConfigMapper extends BaseMapper<UmsExtConfig> {
+
+    /**
+     * 查询配置
+     * author admin@fyang.vip
+     * @param ext_name
+     * @return
+     */
+    @Select("SELECT * FROM ums_ext_config WHERE ext_name= #{ext_name} LIMIT  1")
+    UmsExtConfig findByName(@Param("ext_name") String ext_name);
+}

+ 37 - 0
src/main/java/net/yaoyi/gulop/member/auth/mapper/UmsMemberMapper.java

@@ -0,0 +1,37 @@
+package net.yaoyi.gulop.member.auth.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import net.yaoyi.gulop.member.auth.entity.UmsMember;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+import org.apache.ibatis.annotations.Select;
+import org.apache.ibatis.annotations.Update;
+
+import java.time.LocalDateTime;
+
+@Mapper
+public interface UmsMemberMapper extends BaseMapper<UmsMember> {
+
+    /**
+     * 根据ID查询有效用户(自动过滤已删除)
+     *获取用户手机号
+     */
+    @Select("SELECT * FROM ums_member WHERE id = #{id} AND del_flag = 0")
+    UmsMember findById(@Param("id") Long id);
+
+
+    /**
+     * 更新三要素认证状态
+     */
+    @Update("UPDATE ums_member SET " +
+            "is_three_auth = #{threeAuth}, " +
+            "three_auth_info = #{threeAuthInfo}, " +
+            "update_time = NOW() " +
+            "WHERE id = #{id} AND update_time = #{updateTime}")
+    int updateThreeAuthStatus(@Param("id") Long id,
+                              @Param("threeAuth") Boolean threeAuth,
+                              @Param("threeAuthInfo") String threeAuthInfoJson,
+                              @Param("updateTime") LocalDateTime updateTime);
+
+    void updateTAuthInfo();
+}

+ 36 - 0
src/main/java/net/yaoyi/gulop/member/auth/mapper/UserAgreementRecordMapper.java

@@ -0,0 +1,36 @@
+package net.yaoyi.gulop.member.auth.mapper;
+
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import net.yaoyi.gulop.member.auth.entity.UserAgreementRecord;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+import org.apache.ibatis.annotations.Select;
+
+import java.util.Map;
+
+/**
+ * {@code UserAgreementRecordMapper}
+ * <p>
+ * 零工用户协议dao
+ *
+ * @author Hengchen.Sun
+ * @version 1.0.0
+ * @date 2022/03/08 13:18
+ */
+@Mapper
+public interface UserAgreementRecordMapper extends BaseMapper<UserAgreementRecord> {
+
+    // 根据用户ID获取企业编码和用户名(需要你实际表结构)
+    @Select("SELECT enterprise_code, username FROM user WHERE user_id = #{userId}")
+    Map<String, Object> getUserInfo(@Param("userId") Long userId);
+
+
+
+
+    @Select("SELECT * FROM gig_user_agreement_record" +
+            "WHERE enterprise_code = #{enterpriseCode} LIMIT 1")
+    UserAgreementRecord getSignAgreement(@Param("entCode") String entCode);
+
+
+}

+ 21 - 0
src/main/java/net/yaoyi/gulop/member/auth/service/GigConfigService.java

@@ -0,0 +1,21 @@
+package net.yaoyi.gulop.member.auth.service;
+
+
+import net.yaoyi.gulop.member.auth.entity.GigConfig;
+import net.yaoyi.gulop.member.auth.util.CommonResult;
+
+import java.io.IOException;
+import java.util.List;
+
+public interface GigConfigService {
+
+    List<GigConfig> getSignList(Long userId);
+
+    CommonResult signContract(Long configId,Long userId) throws IOException;
+
+    CommonResult signCheck(Long contractId, long userId);
+
+    List<GigConfig>  getReportList();
+
+    CommonResult getReportDown(Long configId) throws IOException;
+}

+ 34 - 0
src/main/java/net/yaoyi/gulop/member/auth/service/MemberAuthService.java

@@ -0,0 +1,34 @@
+package net.yaoyi.gulop.member.auth.service;
+
+
+import net.yaoyi.gulop.member.auth.dto.FourElementAuthDto;
+import net.yaoyi.gulop.member.auth.dto.QualificationDTO;
+import net.yaoyi.gulop.member.auth.dto.ThreeElementAuthDTO;
+import net.yaoyi.gulop.member.auth.util.CommonResult;
+import net.yaoyi.gulop.member.auth.vo.MemberAuthInfoVO;
+
+import java.io.IOException;
+import java.util.Map;
+
+public interface MemberAuthService {
+
+    /**
+     * 处理三要素实名认证
+     *
+     * @param request 认证请求体
+     */
+    CommonResult threeElementAuth(Map<String, Object> request,Long userId);
+
+
+    CommonResult fourElementAuth(FourElementAuthDto request,Long userId);
+
+    CommonResult ocrAuth(String fileUrl, String type,Long userId) throws IOException;
+
+    CommonResult personFace(ThreeElementAuthDTO request,Long userId);
+
+    CommonResult checkCertificate(QualificationDTO qualificationDTO,Long userId);
+
+    MemberAuthInfoVO getAuthInfo(Long userId);
+
+
+}

+ 13 - 0
src/main/java/net/yaoyi/gulop/member/auth/service/UmsExtApiHisService.java

@@ -0,0 +1,13 @@
+package net.yaoyi.gulop.member.auth.service;
+
+import com.alibaba.fastjson.JSONObject;
+import net.yaoyi.gulop.member.auth.entity.UmsExtApiHis;
+
+public interface UmsExtApiHisService {
+    boolean addApiHistory(UmsExtApiHis apiHis);
+
+    Long apiHistoryBefore(String extId, Long userId, String apiName, JSONObject requestData);
+
+
+    boolean apiHistoryAfter(Long apiHistoryId, int status, JSONObject responseData);
+}

+ 585 - 0
src/main/java/net/yaoyi/gulop/member/auth/service/impl/GigConfigServiceImpl.java

@@ -0,0 +1,585 @@
+package net.yaoyi.gulop.member.auth.service.impl;
+
+import com.alibaba.fastjson.JSONObject;
+import com.ancun.netsign.client.NetSignClient;
+import com.ancun.netsign.model.*;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.google.common.collect.Table;
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import net.yaoyi.gulop.member.auth.constant.enums.ContractStatus;
+import net.yaoyi.gulop.member.auth.entity.*;
+import net.yaoyi.gulop.member.auth.mapper.*;
+import net.yaoyi.gulop.member.auth.service.GigConfigService;
+import net.yaoyi.gulop.member.auth.service.UmsExtApiHisService;
+import net.yaoyi.gulop.member.auth.util.CommonResult;
+import net.yaoyi.gulop.member.auth.util.ContractNumberGenerator;
+import net.yaoyi.gulop.member.auth.util.RemoteFileUtils;
+import org.junit.platform.commons.function.Try;
+import org.springframework.stereotype.Service;
+
+import java.io.IOException;
+import java.time.LocalDateTime;
+import java.util.*;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+@Service
+@Slf4j
+@AllArgsConstructor
+public class GigConfigServiceImpl implements GigConfigService {
+
+    private UmsExtConfigMapper umsExtConfigMapper;
+    private final GigConfigMapper configMapper;
+    private final SmartContractMapper contractMapper;
+    private UmsMemberMapper umsMemberMapper;
+    private UmsExtApiHisService umsExtApiHisService;
+
+    // private final MemberOptManager memberOptManager;
+
+    @Override
+    public List<GigConfig> getSignList(Long userId) {
+        // 查询需要签约的配置项
+
+        // 1. 查询需要签约的配置列表
+        LambdaQueryWrapper<GigConfig> configWrapper = new LambdaQueryWrapper<>();
+        configWrapper./*eq(GigConfig::getIsSign, 1).*/eq(GigConfig::getIsFileType,0); // 需要签约的配置
+
+        List<GigConfig> configList = configMapper.selectList(configWrapper);
+
+        // 2. 提取关联条件(假设通过enterprise_account关联)
+        List<Long> configIds = configList.stream()
+                .map(GigConfig::getId)
+                .collect(Collectors.toList());
+
+        if (configIds.isEmpty()) {
+            return configList;
+        }
+
+        // 3. 查询对应的协议信息
+        LambdaQueryWrapper<SmartContract> contractWrapper = new LambdaQueryWrapper<>();
+
+        contractWrapper.eq(SmartContract::getUserId, userId).in(SmartContract::getConfigId, configIds); // 当前用户的协议
+
+        List<SmartContract> contracts = contractMapper.selectList(contractWrapper);
+
+        // 4. 构建快速查询映射表(ent_code -> 最新签约记录)
+        Map<Long, SmartContract> contractMap = contracts.stream()
+                .collect(Collectors.toMap(
+                        SmartContract::getConfigId,
+                        Function.identity(),
+                        (oldContract, newContract) -> newContract
+                ));
+
+
+        // 5. 动态挂载签约记录到配置对象
+        configList.forEach(config -> {
+            SmartContract contract = contractMap.get(config.getId());
+            if (contract != null) {
+                config.setContractInfo(contract); // 挂载到虚拟字段
+            }
+        });
+
+        return  configList;
+    }
+
+
+    //发起协议签约
+    @Override
+    public CommonResult signContract(Long configId, Long userId) throws IOException {
+        SmartContract smartContract = contractMapper.getSignAgreementByConfig(configId,userId);
+        if (smartContract != null ) {
+              if (smartContract.getContractStatus() == ContractStatus.SIGNED) {
+                throw new RuntimeException("该协议已签约");
+            }
+
+
+            if (smartContract.getContractStatus().getCode() > ContractStatus.SIGNED.getCode()) {
+                //让他签
+            }else{
+                return CommonResult.ok(smartContract);
+            }
+
+        }else{
+            smartContract = new SmartContract();
+        }
+
+        //获取用户信息
+        UmsMember umsMember = umsMemberMapper.findById(userId);
+        if (umsMember.getIsLivenessAuth() != 1) {
+            throw new RuntimeException("请先完成身份信息认证");
+        }
+
+        if (umsMember.getIsFourAuth() != 1) {
+            return CommonResult.failed("请先完成银行卡四要素认证");
+        }
+
+        //获取爱签配置
+        UmsExtConfig config = getConfig();
+        //获取协议
+        GigConfig signAgreemenInfo = configMapper.getSignAgreement(configId);
+        if (signAgreemenInfo == null) {
+            throw new RuntimeException("该协议无需签署");
+        }
+        if(signAgreemenInfo.getIsSign() == 0) {
+            //调用税地
+
+
+            /*Map<net.yaoyi.gulop.common.core.constant.enums.type.GigType.SubjectType, MemberInfo> memberInfoMap = new HashMap<>(4);
+            JSONObject threeAuthInfo = umsMember.getThreeAuthInfo();
+            net.yaoyi.gulop.common.core.constant.enums.type.GigType.SubjectType subjectType = Enum.valueOf(net.yaoyi.gulop.common.core.constant.enums.type.GigType.SubjectType.class, signAgreemenInfo.getSubjectName());
+
+            MemberInfo info = new MemberInfo();
+            info.setRealName(threeAuthInfo.getString("realname"));
+            info.setUsername(umsMember.getPhone());
+            info.setIdCard(threeAuthInfo.getString("idcard"));
+            info.setPayChannel(PayChannel.BANK_CARD);
+            info.setSubjectType(subjectType);
+            info.setIdCardType(MemberIDCardType.ID_CARD);
+            info.setEnterpriseCode(signAgreemenInfo.getEnterpriseCode());
+            info.setEmail("");
+            info.setChannel(Enum.valueOf(net.yaoyi.gulop.common.core.constant.enums.type.GigType.SubjectChannel.class, (String) signAgreemenInfo.getSubjectChannel()));
+            info.setMemberType(MemberType.YAOYI_EXCLUSIVE_PERSONNEL);
+            info.setAddress(threeAuthInfo.getString("address"));
+            info.setRequestId("voWZf9M9x5h9039X");
+            // info.setRequestId("12345789");
+
+            memberInfoMap.put(GigType.SubjectType.TAX_HELPOR, info);
+            log.info("税地请求:{}",JSONObject.toJSON(memberInfoMap));
+            Table<net.yaoyi.gulop.common.core.constant.enums.type.GigType.SubjectType, String, TeaModel> gigSubjectTypeTeaModelMap = memberOptManager.doSignAndCert(memberInfoMap);
+            log.info("税地返回:{}",JSONObject.toJSON(gigSubjectTypeTeaModelMap));
+            return CommonResult.ok(gigSubjectTypeTeaModelMap);*/
+            return CommonResult.ok(null,"这是税地,需要调用税地的接口");
+
+        }
+
+
+        NetSignClient netSignClient = new NetSignClient("https://prev.asign.cn/", config.getExtId(), config.getExtSecret());
+
+
+        //1.创建待签署文件
+        String contractId = ContractNumberGenerator.generate();
+
+        List<FileDto> files = new ArrayList<>();
+        log.info("签署的本地合同 {}",signAgreemenInfo.getAgreementUrl());
+        FileDto fileDto = RemoteFileUtils.fromUrl(signAgreemenInfo.getAgreementUrl());
+        files.add(fileDto);
+
+
+        //1.创建待签署文件
+        ContractInput contractInput = new ContractInput();
+        contractInput.setContractNo(contractId);
+        contractInput.setContractName(signAgreemenInfo.getSubjectName());
+        contractInput.setContractFiles(files);
+        contractInput.setSignOrder(1);
+        contractInput.setValidityTime(30);
+
+        log.info("创建待签署文件 {}",JSONObject.toJSON(contractInput));
+
+        Long apiHistoryId = umsExtApiHisService.apiHistoryBefore(config.getExtId(), userId, "创建待签署文件", (JSONObject) JSONObject.toJSON(contractInput));
+        ApiRespBody<ContractOutput> apiRespBody = netSignClient.createContract(contractInput);
+
+        log.info("创建待签署文件 返回:{}",JSONObject.toJSON(apiRespBody));
+
+        if (!apiRespBody.success()) {
+            umsExtApiHisService.apiHistoryAfter(apiHistoryId,0, (JSONObject) JSONObject.toJSON(apiRespBody));
+            throw new RuntimeException(apiRespBody.getMsg());
+        }
+
+        umsExtApiHisService.apiHistoryAfter(apiHistoryId,1, (JSONObject) JSONObject.toJSON(apiRespBody.getData()));
+
+
+
+        //2.添加签署方
+        List < ContractUserInput > contractUserInputs = new ArrayList<>();
+        // ApiRespBody<ContractOutput> contractOutputApiRespBody = netSignClient.addSigner(List < ContractUserInput > contractUserInputs);
+
+        // 2.1添加用户
+        List<UserSignStrategyInput> objects = new ArrayList<>();
+        UserSignStrategyInput userSignStrategyInput = new UserSignStrategyInput();
+        userSignStrategyInput.setAttachNo(1);
+        userSignStrategyInput.setLocationMode(3);
+        userSignStrategyInput.setSignKey("乙方签署区");
+        objects.add(userSignStrategyInput);
+
+        userSignStrategyInput = new UserSignStrategyInput();
+        userSignStrategyInput.setAttachNo(1);
+        userSignStrategyInput.setLocationMode(3);
+        userSignStrategyInput.setSignKey("乙方签署日期");
+        userSignStrategyInput.setSignType(2);
+        objects.add(userSignStrategyInput);
+
+        ContractUserInput contractUserInput = new ContractUserInput();
+        contractUserInput.setContractNo(contractInput.getContractNo());
+        contractUserInput.setAccount(umsMember.getPhone());
+        contractUserInput.setSignType(2);//2:无感知签约(需要开通权限)
+        contractUserInput.setValidateType(1);//签署方式指定
+        // contractUserInput.setFaceAuthMode(4);//微信小程序
+        contractUserInput.setIsNoticeComplete(1);//签署完成后是否通知用户
+        contractUserInput.setWaterMark(1);//是否在距底部10px中央位置添加日期水印
+        contractUserInput.setCustomSignFlag(0);//签章位置
+        contractUserInput.setSignStrategyList(objects);//签章策略
+
+        contractUserInputs.add(contractUserInput);
+
+
+
+        // 2.2添加企业
+         objects = new ArrayList<>();
+        userSignStrategyInput = new UserSignStrategyInput();
+        userSignStrategyInput.setAttachNo(1);
+        userSignStrategyInput.setLocationMode(3);
+        userSignStrategyInput.setSignKey("甲方签署区");
+        objects.add(userSignStrategyInput);
+
+        /*userSignStrategyInput = new UserSignStrategyInput();
+        userSignStrategyInput.setAttachNo(1);
+        userSignStrategyInput.setLocationMode(3);
+        userSignStrategyInput.setSignKey("甲方签署日期");
+        userSignStrategyInput.setSignType(2);
+        objects.add(userSignStrategyInput);*/
+
+        ContractUserInput contractUserInput2 = new ContractUserInput();
+        contractUserInput2.setContractNo(contractInput.getContractNo());
+        contractUserInput2.setAccount(signAgreemenInfo.getEnterpriseAccount());
+        contractUserInput2.setSignType(2);
+        contractUserInput2.setValidateType(1);//签署方式指定
+        // contractUserInput2.setFaceAuthMode(2);//h5
+        contractUserInput2.setIsNoticeComplete(1);//签署完成后是否通知用户
+        contractUserInput2.setWaterMark(1);//是否在距底部10px中央位置添加日期水印
+        contractUserInput2.setCustomSignFlag(0);//签章位置
+        contractUserInput2.setSignStrategyList(objects);//签章策略
+        contractUserInputs.add(contractUserInput2);
+
+
+
+        log.info("添加签署方 {}",JSONObject.toJSON(contractUserInputs));
+        //准备签署
+
+
+        JSONObject requestData = new JSONObject();
+        requestData.put("",contractUserInputs);
+        apiHistoryId = umsExtApiHisService.apiHistoryBefore(config.getExtId(), userId, "添加签署方", (JSONObject) JSONObject.toJSON(requestData));
+        ApiRespBody<ContractOutput> contractOutputApiRespBody = netSignClient.addSigner(contractUserInputs);
+        log.info("添加签署方 返回:{}",JSONObject.toJSON(contractOutputApiRespBody));
+        if (contractOutputApiRespBody.success()) {
+            ContractOutput data = contractOutputApiRespBody.getData();
+
+            // JSONObject responseData = new JSONObject();
+            /*for (SignUserOutput signUserOutput : data.getSignUser()) {
+                if (Objects.equals(signUserOutput.getAccount(), umsMember.getPhone())) {
+                    smartContract.setUserSignUrl(signUserOutput.getSignUrl());
+                    // responseData.put("sign_url",signUserOutput.getSignUrl());
+                    responseData = (JSONObject) JSONObject.toJSON(signUserOutput);
+                } else if (Objects.equals(signUserOutput.getAccount(), signAgreemenInfo.getEnterpriseAccount())) {
+                    smartContract.setEnterpriseSignUrl(signUserOutput.getSignUrl());
+                } else if (Objects.equals(signUserOutput.getAccount(), signAgreemenInfo.getPlatformAccount())) {
+                    smartContract.setPlatformSignUrl(signUserOutput.getSignUrl());
+                }
+            }*/
+
+
+            //创建用户发起的协议记录
+            smartContract.setConfigId(configId);
+            smartContract.setContractNo(contractInput.getContractNo());
+            smartContract.setContractTitle(contractInput.getContractName());
+            smartContract.setContractContent(signAgreemenInfo.getAgreementUrl());
+            smartContract.setContractContent(signAgreemenInfo.getAgreementUrl());
+            smartContract.setContractStatus(ContractStatus.WAIT_SIGN);
+            smartContract.setUserId(userId);
+            // smartContract.setUserSignStatus(ContractStatus.WAIT_SIGN);
+            // smartContract.setEnterpriseSignStatus(ContractStatus.WAIT_SIGN);
+            // smartContract.setPlatformSignStatus(ContractStatus.WAIT_SIGN);
+            smartContract.setSignInfo((JSONObject) JSONObject.toJSON(data));
+            smartContract.setPreviewUrl(apiRespBody.getData().getPreviewUrl());
+            smartContract.setUserAccount(umsMember.getPhone());
+            smartContract.setEnterpriseAccount(signAgreemenInfo.getEnterpriseAccount());
+            smartContract.setPlatformAccount(signAgreemenInfo.getPlatformAccount());
+            smartContract.setExpireTime(LocalDateTime.now().plusDays(contractInput.getValidityTime()));
+
+            if (smartContract.getContractId() == null) {
+                smartContract.insert();
+            }else{
+                smartContract.updateById();
+            }
+
+            umsExtApiHisService.apiHistoryAfter(apiHistoryId,1, (JSONObject) JSONObject.toJSON(contractOutputApiRespBody));
+
+
+            //通过api查询合同状态
+            this.signCheck(smartContract.getContractId(),userId);
+            // return CommonResult.ok(responseData);
+            return CommonResult.ok(smartContract);
+        }else{
+            log.info("err {}" ,contractOutputApiRespBody.getData());
+            umsExtApiHisService.apiHistoryAfter(apiHistoryId,0, (JSONObject) JSONObject.toJSON(contractOutputApiRespBody));
+            return CommonResult.failed(contractOutputApiRespBody.getMsg());
+        }
+
+    }
+
+
+    //同步状态
+    public CommonResult signCheck(Long contractId, long userId){
+
+        SmartContract smartContract = contractMapper.getSignAgreementById(contractId,userId);
+        if (smartContract != null ) {
+            if (smartContract.getContractStatus() == ContractStatus.SIGNED) {
+                throw new RuntimeException("该协议已签署完成");
+            }
+            if (smartContract.getContractStatus().getCode() >= ContractStatus.SIGNED.getCode()) {
+                throw new RuntimeException("该协议已作废");
+            }
+        }else{
+            throw new RuntimeException("你没有可签署的协议");
+        }
+
+
+        //查询状态并同步
+        UmsMember umsMember = umsMemberMapper.findById(userId);
+
+        //获取爱签配置
+        UmsExtConfig config = getConfig();
+        NetSignClient netSignClient = new NetSignClient("https://prev.asign.cn/", config.getExtId(), config.getExtSecret());
+        JSONObject requestData = new JSONObject();
+        requestData.put("contractNo",smartContract.getContractNo());
+        Long apiHistoryId = umsExtApiHisService.apiHistoryBefore(config.getExtId(), userId, "查询合同信息",requestData);
+
+        ApiRespBody contractInfo = netSignClient.getContractInfo(smartContract.getContractNo());
+
+        if (contractInfo.success()) {
+            ContractOutput data = (ContractOutput) contractInfo.getData();
+            log.info("查询签约结果 {}",JSONObject.toJSON(data));
+
+            umsExtApiHisService.apiHistoryAfter(apiHistoryId,1,(JSONObject) JSONObject.toJSON(data));
+            /*for (SignUserOutput item : data.getSignUser()) {
+                if (Objects.equals(item.getAccount(), smartContract.getUserAccount())) {
+                    smartContract.setUserSignStatus(ContractStatus.fromCode(item.getSignStatus()));
+                } else if (Objects.equals(item.getAccount(), smartContract.getEnterpriseAccount())) {
+                    smartContract.setEnterpriseSignStatus(ContractStatus.fromCode(item.getSignStatus()));
+                }  else if (Objects.equals(item.getAccount(), smartContract.getPlatformAccount())) {
+                    smartContract.setPlatformSignStatus(ContractStatus.fromCode(item.getSignStatus()));
+                }
+            }*/
+
+
+            smartContract.setContractStatus(ContractStatus.fromCode(data.getStatus()));
+            smartContract.setSignInfo((JSONObject) JSONObject.toJSON(data));
+            smartContract.updateById();
+
+            return CommonResult.ok(smartContract);
+        }
+
+        umsExtApiHisService.apiHistoryAfter(apiHistoryId,0,(JSONObject) JSONObject.toJSON(contractInfo));
+        throw new RuntimeException(contractInfo.getMsg());
+
+    }
+
+
+
+
+
+    protected UmsExtConfig getConfig() {
+        //todo 获取配置
+        UmsExtConfig asignConfig = umsExtConfigMapper.findByName("asign");
+        if (asignConfig == null) {
+            throw new RuntimeException("没有配置");
+        }
+
+        return asignConfig;
+    }
+
+
+
+
+
+    //获取报告列表
+    @Override
+    public List<GigConfig> getReportList() {
+        // 查询需要签约的配置项
+
+        // 1. 查询需要签约的配置列表
+        LambdaQueryWrapper<GigConfig> configWrapper = new LambdaQueryWrapper<>();
+        configWrapper.eq(GigConfig::getIsSign, 1).eq(GigConfig::getIsFileType,1); // 需要签约的配置
+
+        return configMapper.selectList(configWrapper);
+    }
+
+
+    @Override
+    public CommonResult getReportDown(Long configId) throws IOException {
+        //获取爱签配置
+        UmsExtConfig config = getConfig();
+        //获取协议
+        GigConfig signAgreemenInfo = configMapper.getSignAgreement(configId);
+        if (signAgreemenInfo == null) {
+            throw new RuntimeException("该协议无需签署");
+        }
+
+        NetSignClient netSignClient = new NetSignClient("https://prev.asign.cn/", config.getExtId(), config.getExtSecret());
+
+
+        //1.创建待签署文件
+        String contractId = ContractNumberGenerator.generate();
+
+        List<FileDto> files = new ArrayList<>();
+        log.info("xx {}",signAgreemenInfo.getAgreementUrl());
+        FileDto fileDto = RemoteFileUtils.fromUrl(signAgreemenInfo.getAgreementUrl());
+        files.add(fileDto);
+
+
+        //1.创建待签署文件
+        ContractInput contractInput = new ContractInput();
+        contractInput.setContractNo(contractId);
+        contractInput.setContractName(signAgreemenInfo.getSubjectName());
+        contractInput.setContractFiles(files);
+        contractInput.setSignOrder(1);
+        contractInput.setValidityTime(30);
+
+        ApiRespBody<ContractOutput> apiRespBody = netSignClient.createContract(contractInput);
+        if (!apiRespBody.success()) {
+            throw new RuntimeException(apiRespBody.getMsg());
+        }
+
+        log.info("创建待签署文件 {}",JSONObject.toJSON(apiRespBody.getData()));
+
+        //2.添加签署方
+        List < ContractUserInput > contractUserInputs = new ArrayList<>();
+
+        // 2.1 企业A签署区
+        List<UserSignStrategyInput> objects = new ArrayList<>();
+        UserSignStrategyInput userSignStrategyInput = new UserSignStrategyInput();
+        userSignStrategyInput.setAttachNo(1);
+        userSignStrategyInput.setLocationMode(3);
+        userSignStrategyInput.setSignKey("企业A签署区");
+        objects.add(userSignStrategyInput);
+
+        /*userSignStrategyInput = new UserSignStrategyInput();
+        userSignStrategyInput.setAttachNo(1);
+        userSignStrategyInput.setLocationMode(3);
+        userSignStrategyInput.setSignKey("企业A签署日期");
+        userSignStrategyInput.setSignType(2);
+        objects.add(userSignStrategyInput);*/
+
+        ContractUserInput contractUserInput = new ContractUserInput();
+        contractUserInput.setContractNo(contractInput.getContractNo());
+        contractUserInput.setAccount(signAgreemenInfo.getEnterpriseAccount());
+        contractUserInput.setSignType(2);//2:无感知签约(需要开通权限)
+        contractUserInput.setValidateType(1);//签署方式指定
+        // contractUserInput.setFaceAuthMode(4);//微信小程序
+        contractUserInput.setIsNoticeComplete(1);//签署完成后是否通知用户
+        contractUserInput.setWaterMark(1);//是否在距底部10px中央位置添加日期水印
+        contractUserInput.setCustomSignFlag(0);//签章位置
+        contractUserInput.setSignStrategyList(objects);//签章策略
+
+        contractUserInputs.add(contractUserInput);
+
+
+
+        // 2.2 企业B签署区
+        objects = new ArrayList<>();
+        userSignStrategyInput = new UserSignStrategyInput();
+        userSignStrategyInput.setAttachNo(1);
+        userSignStrategyInput.setLocationMode(3);
+        userSignStrategyInput.setSignKey("企业B签署区");
+        objects.add(userSignStrategyInput);
+
+        /*userSignStrategyInput = new UserSignStrategyInput();
+        userSignStrategyInput.setAttachNo(1);
+        userSignStrategyInput.setLocationMode(3);
+        userSignStrategyInput.setSignKey("企业B签署日期");
+        userSignStrategyInput.setSignType(2);
+        objects.add(userSignStrategyInput);*/
+
+        contractUserInput = new ContractUserInput();
+        contractUserInput.setContractNo(contractInput.getContractNo());
+        contractUserInput.setAccount(signAgreemenInfo.getReceptionAccount());
+        contractUserInput.setSignType(2);
+        contractUserInput.setValidateType(1);//签署方式指定
+        // contractUserInput.setFaceAuthMode(2);//h5
+        contractUserInput.setIsNoticeComplete(1);//签署完成后是否通知用户
+        contractUserInput.setWaterMark(1);//是否在距底部10px中央位置添加日期水印
+        contractUserInput.setCustomSignFlag(0);//签章位置
+        contractUserInput.setSignStrategyList(objects);//签章策略
+        contractUserInputs.add(contractUserInput);
+
+
+
+        //2.3 要易签署区
+        objects = new ArrayList<>();
+        userSignStrategyInput = new UserSignStrategyInput();
+        userSignStrategyInput.setAttachNo(1);
+        userSignStrategyInput.setLocationMode(3);
+        userSignStrategyInput.setSignKey("要易签署区");
+        objects.add(userSignStrategyInput);
+
+        /*userSignStrategyInput = new UserSignStrategyInput();
+        userSignStrategyInput.setAttachNo(1);
+        userSignStrategyInput.setLocationMode(3);
+        userSignStrategyInput.setSignKey("要易签署日期");
+        userSignStrategyInput.setSignType(2);
+        objects.add(userSignStrategyInput);*/
+
+
+        //骑缝章
+        List<UserSignStrikeInput> userSignStrikeInputs = new ArrayList<>();
+        UserSignStrikeInput userSignStrikeInput = new UserSignStrikeInput();
+        userSignStrikeInput.setAttachNo(1);
+        userSignStrikeInput.setCrossStyle(6);//右骑缝
+        userSignStrikeInput.setSignPage("42-43");//骑缝章所在页码
+        userSignStrikeInputs.add(userSignStrikeInput);
+
+        contractUserInput = new ContractUserInput();
+        contractUserInput.setContractNo(contractInput.getContractNo());
+        contractUserInput.setAccount(signAgreemenInfo.getPlatformAccount());
+        contractUserInput.setSignType(2);
+        contractUserInput.setValidateType(1);//签署方式指定
+        // contractUserInput.setFaceAuthMode(2);//h5
+        contractUserInput.setIsNoticeComplete(1);//签署完成后是否通知用户
+        contractUserInput.setWaterMark(1);//是否在距底部10px中央位置添加日期水印
+        contractUserInput.setCustomSignFlag(0);//签章位置
+        contractUserInput.setSignStrategyList(objects);//签章策略
+        contractUserInput.setSignStrikeList(userSignStrikeInputs);//签章策略 signStrikeList
+        contractUserInputs.add(contractUserInput);
+
+        log.info("报告签署 :{}",JSONObject.toJSON(contractUserInputs));
+
+        ApiRespBody<ContractOutput> contractOutputApiRespBody = netSignClient.addSigner(contractUserInputs);
+        log.info("报告签署 返回:{}",JSONObject.toJSON(contractOutputApiRespBody));
+        if (contractOutputApiRespBody.success()) {
+            ContractOutput data = contractOutputApiRespBody.getData();
+
+            //TODO 下载合同
+            try{
+
+                log.info("xxx {} {} {}",data.getContractNo(), 1, "P:\\wwwroot\\java\\3000\\"+data.getContractNo()+".pdf");
+                // DownloadContractOutput down = this.down(data.getContractNo(), 1, "P:\\wwwroot\\java\\3000\\"+data.getContractNo()+".pdf");
+                DownloadContractOutput down = this.down(contractUserInput.getContractNo(), 1, "/www/wwwroot/mini.fyang.vip/"+contractUserInput.getContractNo()+".pdf");
+                log.info("下载成功");
+            }catch (Exception e){
+                System.out.println("异常");
+                e.printStackTrace();
+            }
+            return CommonResult.ok(data);
+        }else{
+            log.info("err {}" ,contractOutputApiRespBody.getData());
+            return CommonResult.failed(contractOutputApiRespBody.getMsg());
+        }
+    }
+
+
+    public DownloadContractOutput down(String contractNo, int downloadFileType, String outfile){
+
+        //获取爱签配置
+        UmsExtConfig config = getConfig();
+        NetSignClient netSignClient = new NetSignClient("https://prev.asign.cn/", config.getExtId(), config.getExtSecret());
+
+        ApiRespBody<DownloadContractOutput> downloadContractOutputApiRespBody = netSignClient.downloadContract(contractNo, downloadFileType, outfile);
+        if (downloadContractOutputApiRespBody.success()) {
+            return downloadContractOutputApiRespBody.getData();
+        }
+
+        return  null;
+
+    }
+}

+ 337 - 0
src/main/java/net/yaoyi/gulop/member/auth/service/impl/MemberAuthServiceImpl.java

@@ -0,0 +1,337 @@
+package net.yaoyi.gulop.member.auth.service.impl;
+
+import com.alibaba.fastjson.JSONObject;
+import com.ancun.netsign.client.NetSignClient;
+import com.ancun.netsign.model.*;
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import net.yaoyi.gulop.member.auth.dto.QualificationDTO;
+import net.yaoyi.gulop.member.auth.service.UmsExtApiHisService;
+import net.yaoyi.gulop.member.auth.util.CommonResult;
+import net.yaoyi.gulop.member.auth.dto.FourElementAuthDto;
+import net.yaoyi.gulop.member.auth.dto.ThreeElementAuthDTO;
+import net.yaoyi.gulop.member.auth.entity.UmsExtConfig;
+import net.yaoyi.gulop.member.auth.entity.UmsMember;
+import net.yaoyi.gulop.member.auth.mapper.UmsExtConfigMapper;
+import net.yaoyi.gulop.member.auth.mapper.UmsMemberMapper;
+import net.yaoyi.gulop.member.auth.service.MemberAuthService;
+import net.yaoyi.gulop.member.auth.util.ContractNumberGenerator;
+import net.yaoyi.gulop.member.auth.util.ImageToBase64Converter;
+import net.yaoyi.gulop.member.auth.vo.MemberAuthInfoVO;
+import org.springframework.beans.BeanUtils;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.io.IOException;
+import java.util.Map;
+
+@Service
+@Slf4j
+@AllArgsConstructor
+public class MemberAuthServiceImpl implements MemberAuthService {
+
+    private UmsExtConfigMapper umsExtConfigMapper;
+    private UmsMemberMapper umsMemberMapper;
+    private UmsExtApiHisService umsExtApiHisService;
+
+
+
+    public MemberAuthInfoVO getAuthInfo(Long userId){
+        //获取当前的登录用户信息
+        UmsMember umsMember = umsMemberMapper.findById(userId);
+
+
+
+        MemberAuthInfoVO vo = new MemberAuthInfoVO();
+        try {
+            BeanUtils.copyProperties(umsMember, vo);
+            return vo;
+        } catch (Exception e) {
+
+        }
+
+        return vo;
+
+    }
+
+
+
+    /**
+     * 个人运营商三要素比对
+     * @param request 认证请求体
+     * @return
+     */
+    @Transactional
+    public CommonResult threeElementAuth(Map<String, Object> request, Long userId) {
+
+        UmsMember umsMember = umsMemberMapper.findById(userId);
+        UmsExtConfig config = getConfig();
+
+        JSONObject threeAuthInfo = umsMember.getThreeAuthInfo();
+        AuthInput authInput = new AuthInput();
+
+        authInput.setRealName(threeAuthInfo.getString("realname"));
+        authInput.setIdCardNo(threeAuthInfo.getString("idcard"));
+        authInput.setMobile(umsMember.getPhone());
+
+        log.info("authInput {}",(JSONObject) JSONObject.toJSON(authInput));
+        NetSignClient netSignClient = new NetSignClient("https://prev.asign.cn/", config.getExtId(), config.getExtSecret());
+        Long apiHistoryId = umsExtApiHisService.apiHistoryBefore(config.getExtId(), umsMember.getId(), "个人运营商三要素比对", (JSONObject) JSONObject.toJSON(authInput));
+
+        ApiRespBody<AuthOutput> apiRespBody = netSignClient.personVerifyMobile3(authInput);
+
+
+        /*int i = 1;
+        int x = 0;
+        if (i - x  == 1) {
+            String s = "{\"msg\": \"成功\", \"code\": 100000, \"data\": {\"bizId\":\"6p-{1,.?\",\"type\": \"个人运营商三要素认证\", \"result\": 0, \"faceUrl\": null, \"process\": null, \"serialNo\": \"PA10120250318200936327868\", \"identifyUrl\": null}, \"requestId\": null}";
+            JSONObject parse = (JSONObject) JSONObject.parse(s);
+            return CommonResult.ok(parse.get("data"));
+        }*/
+
+
+        log.info("个人运营商三要素比对结果: {}",apiRespBody);
+        umsExtApiHisService.apiHistoryAfter(apiHistoryId,1,(JSONObject) JSONObject.toJSON(apiRespBody));
+        umsMember.setIsLivenessAuth(1);
+        if (apiRespBody.success()) {
+            umsMember.setIsThreeAuth(1);
+
+            //添加到实名人
+            UserInput userInput = new UserInput();
+            userInput.setAccount(umsMember.getPhone());
+            userInput.setSerialNo((String) request.get("serialNo"));
+            userInput.setMobile(umsMember.getPhone());
+            userInput.setSignPwd(threeAuthInfo.getString("idcard"));
+            userInput.setSealName(apiRespBody.getData().getSerialNo());
+
+            ApiRespBody apiRespBody1 = netSignClient.addPersonalUserV2(userInput);
+            log.info("添加到实名人 {}",apiRespBody1);
+            if (apiRespBody1.success()) {
+                umsMember.setEsignInfo((JSONObject) JSONObject.toJSON(apiRespBody1.getData()));
+            }
+        }
+            umsMemberMapper.updateById(umsMember);
+        // return CommonResult.failed(apiRespBody.getMsg());
+        return CommonResult.ok(null,"成功");
+    }
+
+
+    /**
+     * 个人银行卡四要素比对
+     * @param request
+     * @return
+     */
+    public CommonResult fourElementAuth(FourElementAuthDto request,Long userId) {
+
+        UmsMember umsMember = umsMemberMapper.findById(userId);
+        UmsExtConfig config = getConfig();
+        if (umsMember.getIsLivenessAuth() != 1) {
+            return CommonResult.failed("请先完成活体校验");
+        }
+
+
+        JSONObject threeAuthInfo = (JSONObject) JSONObject.parse(umsMember.getThreeAuthInfo().toString());
+
+        AuthInput authInput = new AuthInput();
+        authInput.setRealName(threeAuthInfo.getString("realname"));
+        authInput.setIdCardNo(threeAuthInfo.getString("idcard"));
+        authInput.setMobile(request.getPhone());
+        authInput.setBankCard(request.getCardNumber());
+
+        NetSignClient netSignClient = new NetSignClient("https://prev.asign.cn/", config.getExtId(), config.getExtSecret());
+
+
+        int apiStatus = 0;
+        ApiRespBody<AuthOutput> apiRespBody = null;
+        Long apiHistoryId = umsExtApiHisService.apiHistoryBefore(config.getExtId(), umsMember.getId(), "个人银行卡四要素比对", (JSONObject) JSONObject.toJSON(authInput));
+        try {
+            apiRespBody = netSignClient.personVerifyBank4(authInput);
+            if (apiRespBody.success()) {
+                apiStatus = 1;
+
+                //加入数据库
+                JSONObject fourInfo = new JSONObject();
+                fourInfo.put("idCard", threeAuthInfo.getString("idcard"));
+                fourInfo.put("phone", request.getPhone());
+                fourInfo.put("realName", threeAuthInfo.getString("realname"));
+                fourInfo.put("bankName", request.getBankName());
+                fourInfo.put("cardNumber", request.getCardNumber());
+                umsMember.setFourAuthInfo(fourInfo);
+                umsMember.setIsFourAuth(1);
+
+                umsMemberMapper.updateById(umsMember);
+                return CommonResult.ok(apiRespBody.getData());
+            }
+            return CommonResult.failed(apiRespBody.getMsg());
+        } catch (Exception e){
+            return CommonResult.failed(e.getMessage());
+        }finally {
+            if (apiRespBody != null && apiRespBody.getData() != null) {
+                umsExtApiHisService.apiHistoryAfter(apiHistoryId, apiStatus, (JSONObject) JSONObject.toJSON(apiRespBody.getData()));
+            }else{
+                umsExtApiHisService.apiHistoryAfter(apiHistoryId, apiStatus, (JSONObject) JSONObject.toJSON(apiRespBody));
+            }
+        }
+    }
+
+
+
+    public CommonResult ocrAuth(String fileUrl, String type,Long userId) throws IOException {
+        UmsExtConfig config = getConfig();
+        UmsMember umsMember = umsMemberMapper.findById(userId);
+        log.info("config {}",config);
+        //假装用户的手机号码
+        String phone = umsMember.getPhone();
+        String base64 = ImageToBase64Converter.convertImageUrlToBase64(fileUrl);
+        NetSignClient netSignClient = new NetSignClient("https://prev.asign.cn/", config.getExtId().toString(), config.getExtSecret());
+        UserInput userInput = new UserInput();
+        userInput.setBase64Img(base64);
+        userInput.setSide(type);
+        ApiRespBody apiRespBody = netSignClient.ocrIdentify(userInput);
+        if (apiRespBody.success()) {
+            JSONObject data = (JSONObject) apiRespBody.getData();
+            data.put("fileUrl",fileUrl);
+            return CommonResult.ok(data);
+        }
+
+        return CommonResult.failed(apiRespBody.getMsg());
+
+    }
+
+
+    //请求人脸认证
+    //miniprogram-kyc.tencentcloudapi.com
+    @Override
+    public CommonResult personFace(ThreeElementAuthDTO request,Long userId) {
+
+        /*int i = 1;
+        int x = 0;
+        if (i - x  == 1) {
+            String s = "{\"type\": \"个人活体人脸认证\", \"result\": 0, \"faceUrl\": \"https://miniprogram-kyc.tencentcloudapi.com/api/web/login?webankAppId=IDAfndk9&version=1.0.0&nonce=VBMLBTtdABjs20BPYLWBMOy242Drur0H&orderNo=TC511322199205103651742299776831&h5faceId=tx00cb72c924567a41e8d5e6bb80d767&url=https%3A%2F%2Fprev.asign.cn%2Fauth%2Fface%2Fredirect%2F25764824841872561658677&resultType=1&userId=51132219920510365X&sign=FDF3710D2EBEB4241AD9FBC3E84FACCA5CA742D0&from=browser\", \"process\": null, \"serialNo\": \"PA10320250318200938694393\", \"identifyUrl\": null}";
+            JSONObject parse = (JSONObject) JSONObject.parse(s);
+            return CommonResult.ok(parse);
+        }*/
+
+        UmsExtConfig config = getConfig();
+        UmsMember umsMember = umsMemberMapper.findById(userId);
+
+        //假装用户的手机号码
+        NetSignClient netSignClient = new NetSignClient("https://prev.asign.cn/", config.getExtId(), config.getExtSecret());
+        AuthInput authInput = new AuthInput();
+        authInput.setRealName(request.getRealname());
+        authInput.setIdCardNo(request.getIdcard());
+        authInput.setIdCardType(1);
+        authInput.setAuthMode(2);//h5
+        authInput.setFaceAuthMode(4);//小程序
+        authInput.setShowResult(1);//是否展示人脸认证结果页
+        authInput.setBizId(ContractNumberGenerator.generate(8));
+
+        int apiStatus = 0;
+        ApiRespBody<AuthOutput> apiRespBody = null;
+        Long apiHistoryId = umsExtApiHisService.apiHistoryBefore(config.getExtId(), umsMember.getId(), "个人人脸活体认证", (JSONObject) JSONObject.toJSON(authInput));
+        try{
+            apiRespBody = netSignClient.personAuthFace(authInput);
+            if (apiRespBody.success()) {
+
+                //保存信息入库
+                JSONObject ocrInfo = new JSONObject();
+                ocrInfo.put("front",request.getFront());
+                ocrInfo.put("back",request.getBack());
+                umsMember.setOcrInfo(ocrInfo);
+                umsMember.setThreeAuthInfo((JSONObject) JSONObject.toJSON(request));
+                umsMember.updateById();
+
+                apiStatus = 1;
+                JSONObject json = (JSONObject) JSONObject.toJSON(apiRespBody.getData());
+                //把bizId给前端,待会查结果
+                json.put("bizId",authInput.getBizId());
+                log.info("给到前端 {}",json);
+                return CommonResult.ok(json);
+            }
+
+            return CommonResult.failed(apiRespBody.getMsg());
+        } catch (Exception e){
+            return CommonResult.failed(e.getMessage());
+        }finally {
+            if (apiRespBody != null) {
+                umsExtApiHisService.apiHistoryAfter(apiHistoryId,apiStatus,(JSONObject) JSONObject.toJSON(apiRespBody.getData()));
+            }else {
+                umsExtApiHisService.apiHistoryAfter(apiHistoryId,apiStatus,(JSONObject) JSONObject.toJSON(apiRespBody));
+            }
+        }
+    }
+
+
+    //保存资质证书
+    public CommonResult checkCertificate(QualificationDTO qualificationDTO,Long userId){
+        UmsMember umsMember = umsMemberMapper.findById(userId);
+        JSONObject json = (JSONObject) JSONObject.toJSON(qualificationDTO);
+        umsMember.setCertificateInfo(json);
+
+        log.info("qualificationDTO {}",json);
+        umsMemberMapper.updateById(umsMember);
+        return CommonResult.ok("保存成功");
+    }
+
+
+    /**
+     * 获取认证所需的额外数据
+     * 1.配置
+     * 2.用户额外信息(手机号)
+     * @return
+     */
+    protected UmsExtConfig getConfig(){
+        //todo 获取配置
+        UmsExtConfig asignConfig = umsExtConfigMapper.findByName("asign");
+        if (asignConfig == null) {
+            throw new RuntimeException("没有配置");
+        }
+
+
+        return asignConfig;
+        //假装拿到了配置信息
+        // String appId = "213417306";
+        // String privateKey = "MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDkElfTHF01z4qNia9pzmGT/n7GS1BFtwFpXaiZNmEWru6saW8ZEWjgtpPq4b5vzArLnztagNfPGzZy5cMVY8XH48Il0n+yRJKrNgCLC7WXWCuFcYhv6/HOnFd9+aMwtg5Q1KMpv+KGWE8lroUMQ/ZA7QFNqjoHEyTmxGMFS0mvJT0Om6ySuji9VTt58PAvJceP/F5Tx4jQGVmUxp3ZDE0dCkXlAVW81cPH+HnrhcRy0fdBq2uB37b+DC0VBkGEpzKTk9lLl6FMJYylwoStAv3QIG6qF4jPxLw2O9RtGNkQoORxZEtSLCiBDClMzearq9Elxmlmh9+o5ZRGbayG7abNAgMBAAECggEAO4zukZjeXaaLLtm8i4vvvoy83Vnc49cc7z4dP6QLRbj3aN0+vPTmxYXmqXW4G8jTKMey37a0oscgOG/1uCgNBFxb3TRRU1LESSAYlDXCfHnxIy65yT96K9B2p9X+3kErC7/5unT32MxGHjirVMCKE/nOVVEVRbOh9v9gO4mk+63nJnCphPM/iIeAVO3Y4koF09Hfixvk9CjVFn4++5eoYmmmpt9ZtXuqe5nVc3LqwRcPKLBcQ7jZ4ACAol4MOxEfZ1ZsI5mQlMLq7uqWvQIZG0oZSmrd6PLIHcC68g0Ia3Sj0p2nUT3f34H8g1WvPHILUxg2aNKi002H8CUnVC3GoQKBgQD9lnXuWkMbj9NOxemWo1LYSwUaHInCPgaiBWpXGGld6EndhBVnU6k3MDYEmagPayDEe00bIzHQowHKFk8Mk57MS2HTTSaXqanbbGEHWLtXVYXHn2/LWj6V7QT049Hn0QrDYHoPTttoIv42MfFfeTryBiO2EGyhDIuaRZQxxiOwawKBgQDmPb7RKoLGl1IomQH/LxWDd9XTKU6HvJfnkAdXAHKF+Xp/WYf88JLgvYO6qAgZ+1Z5wAhO32F1e478JSdADie4Iz8EScGFaTkIry9guzGoMYzSD0K5No+WqNPiud5NoroqDalWeq4023QZFZADRodBbOWzM4CuL294isRNu5DzpwKBgQCo6/lS9T2OtpgD+UJI3j2R5ydW5Xu+sKKqewEDU3ec3oeVz86SLeCiqrLRDaot2ybQu3H5idA9MtiWTZYKwqsdW3mmz9XNYW+Mgv1/b1QcMRQUjdhilZLrPB/5IWlKCq4ox7OHTvhIvkaow0wOVSs23XAcb4RtI3Y7Je452cscUQKBgQCeF3eWl+IECpNXXPiKCAJ1qysfOxW5Dj4fuj3P93arl4oNN6SwgV/ZAUgaOpaVgIb4Qmtx/OAmkxiy+a/hw9ZyEYGLqDlBKecHl4KooOCP5LYU5nWiR1yAzpvu6kFCfaHmjkrgHozProAhWTTxitQEkImIaqzI7blUw7PWzBvTsQKBgQCsrXeP232Jsc3pihHdRr1iRWF1PyPzyTDquQiJwGRw8uNOuVnPMDbvf+DnAf8fLKMMwNWdFAGfr9BUhN0ftXPy0kToa9+M9nLeCKmlFt8mmayrGeMzU8Mu9WvU4OP4NPZT3rrwnHE3BDeNqciAXF68aWgngcX+R5R6eLNx4z3gqg==";
+
+        /*appId = "701237453";
+        privateKey = "MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCvMACCFYRgxR1H\n" +
+                "XF6Ar9NH39sDZWEjh9rrZHpx9hcqU4v+QA6NSyDhfWttuZeLzWQTUVxTkSRmazBt\n" +
+                "MIyTfFe8bF18zlJmSk+QRhiZieILOcweP5ZLLHkJHpVXpMA8vvbBYBOtwBDwkMGK\n" +
+                "nsvOXb44laF6VkhObv9JScEmRE0+utHdUhpXJxecIDGP+DZ9QzauNvNEZHRQNgGg\n" +
+                "WQgj3BPu/6wR0ytNFG/eVbsZL7ScZXVEENKbYK7lDmd8r6xZGuGEVMqLW7Jqx4lo\n" +
+                "ozIBHRY1SORV5m2xiSbFZ2tGR5UQpjuTe0FKIT+y83tQtL6PdOwxtLdrXWGe49vt\n" +
+                "pkQ0jAZBAgMBAAECgf88ZA/IyCIaOdkeAHmTYwkyfbjCMvCJjwuombqcDnlt3d8k\n" +
+                "hnB9xKhREys1aAUV7HfTMPji1eX2aH0JJvNO5E5aZgDURUuid3t2ALP6Oc3ykLoM\n" +
+                "s17cCziuLBzPWbvrNOlbQAOIoe01ALG3V4ZqXWZ1mx9PTO8gGqNmYEZ1KpZ2SN4y\n" +
+                "WZC9WdtevEMx+Odm+8V+AEd9auUYC6MPGF6x/u+r/XTANokkuBxy9bmvm9hk/4B6\n" +
+                "fyph0hfBsNogbQMyCC5b+O2+Lx/tnWwvSzKqfQUE8yTqQeRiD+CHkB/0m8jo+9CO\n" +
+                "lfZnBhOlJ7MeQI/6Kp5f8yDkfmm1UJGA338EiI0CgYEA4xo5Z1Dr+Uwz7nqj3+oc\n" +
+                "Bp3XDH4rFyUTiYoppfr6vqFAd6QBRQhyog0yRbdEOwR4p96UzE0xxV/WTNI0KKZi\n" +
+                "xYSP8DUnx/FMOesXtvxegiWQniLg3H+aTcXGNJGUcI3aCU6cR03BBMYnDTYSxmoF\n" +
+                "iGQqPcbb20uK2jOxrHmxnQ0CgYEAxXqrKD/D50etgmDLtfEIOY/yVQhfaL0TS4u4\n" +
+                "p6mh27Tw35sEju8K8lCR6P5SXUz1FIkGH1BmGeKsjFQZ7q2Dhuu+N0O+0MQ1ZdPx\n" +
+                "WcaPoF4hrkv25aMy/P5aa5e9Ut7YmSGf05VvhSHKtGHX1h74LUrN6Q7B6F5KbNw1\n" +
+                "3PEoiQUCgYEAq+ho9Y0q4w5M7UPGkrO7PULzy1AiPCXjsSR1vF/MuMqFa0spbuEj\n" +
+                "LQoCOQWuT2JmFuSFDFWOzFJsfMBNnUu9zrg24OTL1S0Rv3H3BJvqyme3Hg1W23J4\n" +
+                "ElEYZSiiDcVC+/KWl98CsiNw4i5BbhJJV+JCm3rRaCG53MxuvRltWm0CgYEAjrm3\n" +
+                "1mEvr4qIhsB6usa30RKObJKekk6FEYasMNFTID3IEXQyeTOfT76XljOpR7GFOBBI\n" +
+                "kn1DLdY8KzflfjF97BeZ5Mtbr/r/NNdXijEOQTOHWDei/BlVkrAqnbSPqsNQ/Gvm\n" +
+                "3b8VWL0xiellW2YKrIFyDM0TrGPpmoAVldfeGhkCgYB5bsxuXXrAnVGPOMESdhwc\n" +
+                "8lIZ6WWnDtGI5j7SFubs5DexXI7TFWw7DCiCnTlrJFMG/v2hwZ9sMnBSqeWkrtgS\n" +
+                "MhgdxppddwFoZTNx8zs500CIDfZW91LPgiXcaRfbONzKQrNs+vC2cb1wkCQWqz8e\n" +
+                "6hTWATapdDazlP1vZanyvw==";*/
+
+        //todo 获取用户手机号
+//        UmsMember umsMember = umsMemberMapper.findById(2L);
+
+
+        /*Map<String, String> objectObjectMap = new HashMap<>();
+        objectObjectMap.put("appId",appId);
+        objectObjectMap.put("privateKey",privateKey);
+        objectObjectMap.put("api_name",privateKey);*/
+
+        // return objectObjectMap;
+    }
+
+
+}

+ 49 - 0
src/main/java/net/yaoyi/gulop/member/auth/service/impl/UmsExtApiHisServiceImpl.java

@@ -0,0 +1,49 @@
+package net.yaoyi.gulop.member.auth.service.impl;
+
+import com.alibaba.fastjson.JSONObject;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import net.yaoyi.gulop.member.auth.entity.UmsExtApiHis;
+import net.yaoyi.gulop.member.auth.mapper.UmsExtApiHisMapper;
+import net.yaoyi.gulop.member.auth.service.UmsExtApiHisService;
+import org.springframework.stereotype.Service;
+
+@Service
+public class UmsExtApiHisServiceImpl extends ServiceImpl<UmsExtApiHisMapper, UmsExtApiHis> implements UmsExtApiHisService {
+
+
+    @Override
+    public boolean addApiHistory(UmsExtApiHis apiHis) {
+        return this.save(apiHis);
+    }
+
+
+
+
+    public Long apiHistoryBefore(String extId, Long userId, String apiName, JSONObject requestData){
+
+        Object parse = JSONObject.parse(requestData.toString());
+        UmsExtApiHis apiHis = new UmsExtApiHis();
+        apiHis.setUserId(userId);
+        apiHis.setExtId(extId);
+        apiHis.setApiName(apiName);
+        apiHis.setRequestData((JSONObject) JSONObject.toJSON(parse));
+        apiHis.setStatus(0);
+        this.save(apiHis);
+        return apiHis.getId();
+    }
+
+
+
+
+    public boolean apiHistoryAfter(Long apiHistoryId, int status, JSONObject responseData){
+
+        UmsExtApiHis apiHis = new UmsExtApiHis();
+        apiHis.setId(apiHistoryId);
+        apiHis.setResponseData(responseData);
+        apiHis.setStatus(status);
+
+        return this.updateById(apiHis);
+    }
+
+
+}

+ 96 - 0
src/main/java/net/yaoyi/gulop/member/auth/util/CommonResult.java

@@ -0,0 +1,96 @@
+package net.yaoyi.gulop.member.auth.util;
+
+import lombok.*;
+import lombok.experimental.Accessors;
+import net.yaoyi.gulop.member.auth.api.IErrorCode;
+import net.yaoyi.gulop.member.auth.constant.CommonConstants;
+
+import java.io.Serializable;
+
+/**
+ * 响应信息主体
+ *
+ * @author jimmy
+ * @version 1.0.0
+ * @date 2022-06-23 10:43
+ */
+@Builder
+@ToString
+@NoArgsConstructor
+@AllArgsConstructor
+@Accessors(chain = true)
+public class CommonResult<T> implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @Getter
+    @Setter
+    private int code;
+
+    @Getter
+    @Setter
+    private String msg;
+
+    @Getter
+    @Setter
+    private T data;
+
+    public static <T> CommonResult<T> ok() {
+        return restResult(null, CommonConstants.SUCCESS, CommonConstants.SUCCESS_MSG);
+    }
+
+    public static <T> CommonResult<T> ok(T data) {
+        return restResult(data, CommonConstants.SUCCESS, CommonConstants.SUCCESS_MSG);
+    }
+
+    public static <T> CommonResult<T> ok(T data, String msg) {
+        return restResult(data, CommonConstants.SUCCESS, msg);
+    }
+
+    public static <T> CommonResult<T> failed() {
+        return restResult(null, CommonConstants.FAIL, CommonConstants.FAIL_MSG);
+    }
+
+    public static <T> CommonResult<T> failed(String msg) {
+        return restResult(null, CommonConstants.FAIL, msg);
+    }
+
+    public static <T> CommonResult<T> failed(T data) {
+        return restResult(data, CommonConstants.FAIL, CommonConstants.FAIL_MSG);
+    }
+
+    public static <T> CommonResult<T> failed(T data, String msg) {
+        return restResult(data, CommonConstants.FAIL, msg);
+    }
+
+    public static <T> CommonResult<T> failed(T data, Integer code, String msg) {
+        return restResult(data, code, msg);
+    }
+
+    /**
+     * 失败返回结果
+     *
+     * @param errorCode 错误码
+     */
+    public static <T> CommonResult<T> failed(IErrorCode errorCode) {
+        return new CommonResult<>(errorCode.getCode(), errorCode.getMessage(), null);
+    }
+
+    private static <T> CommonResult<T> restResult(T data, int code, String msg) {
+        CommonResult<T> apiResult = new CommonResult<>();
+        apiResult.setCode(code);
+        apiResult.setData(data);
+        apiResult.setMsg(msg);
+        return apiResult;
+    }
+
+    /**
+     * 判断请求是否成功
+     *
+     * @return 是否成功
+     */
+    public Boolean success() {
+        return this.code == CommonConstants.SUCCESS;
+    }
+
+}

+ 53 - 0
src/main/java/net/yaoyi/gulop/member/auth/util/ContractNumberGenerator.java

@@ -0,0 +1,53 @@
+package net.yaoyi.gulop.member.auth.util;
+
+import io.netty.util.internal.ThreadLocalRandom;
+
+import java.security.SecureRandom;
+import java.time.LocalDate;
+import java.time.format.DateTimeFormatter;
+import java.util.concurrent.atomic.AtomicInteger;
+
+public class ContractNumberGenerator {
+    private static final String DATE_FORMAT = "yyyyMMdd";
+    private static final AtomicInteger counter = new AtomicInteger(1);
+
+    public static synchronized String generate() {
+        // 获取当前日期
+        String date = LocalDate.now().format(DateTimeFormatter.ofPattern(DATE_FORMAT));
+
+
+        int random = ThreadLocalRandom.current().nextInt(0, 10000);
+
+        // 生成4位自增序号(每日重置)
+        // int num = counter.getAndIncrement();
+        // String sequence = String.format("%04d", num);
+
+        String sequence = String.format("%05d", random);
+
+        return "CONTRACT-" + date + "-" + sequence;
+    }
+
+
+
+    /**
+     * 生成随机字符串
+     * @param length 字符串长度
+     * @return 生成的随机字符串
+     */
+    public static String generate(int length) {
+        if (length < 1) {
+            throw new IllegalArgumentException("长度必须大于0");
+        }
+
+        String characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
+        SecureRandom random = new SecureRandom();
+        StringBuilder sb = new StringBuilder(length);
+
+        for (int i = 0; i < length; i++) {
+            int randomIndex = random.nextInt(characters.length());
+            sb.append(characters.charAt(randomIndex));
+        }
+
+        return sb.toString();
+    }
+}

+ 61 - 0
src/main/java/net/yaoyi/gulop/member/auth/util/ImageToBase64Converter.java

@@ -0,0 +1,61 @@
+package net.yaoyi.gulop.member.auth.util;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.util.Base64;
+
+public class ImageToBase64Converter {
+
+    public static String convertImageUrlToBase64(String imageUrl) throws IOException {
+        HttpURLConnection connection = null;
+        InputStream inputStream = null;
+
+        try {
+            // 创建URL对象
+            URL url = new URL(imageUrl);
+
+            // 打开HTTP连接
+            connection = (HttpURLConnection) url.openConnection();
+            connection.setRequestMethod("GET");
+            connection.setConnectTimeout(5000);    // 5秒连接超时
+            connection.setReadTimeout(15000);      // 15秒读取超时
+
+            // 检查HTTP响应状态
+            int statusCode = connection.getResponseCode();
+            if (statusCode != HttpURLConnection.HTTP_OK) {
+                throw new IOException("HTTP请求失败,状态码: " + statusCode);
+            }
+
+            // 获取输入流并读取字节
+            inputStream = connection.getInputStream();
+            ByteArrayOutputStream buffer = new ByteArrayOutputStream();
+            byte[] data = new byte[4096];
+            int bytesRead;
+
+            while ((bytesRead = inputStream.read(data, 0, data.length)) != -1) {
+                buffer.write(data, 0, bytesRead);
+            }
+
+            // 转换为Base64
+            byte[] imageBytes = buffer.toByteArray();
+            return Base64.getEncoder().encodeToString(imageBytes);
+
+        } finally {
+            // 清理资源
+            if (inputStream != null) {
+                try {
+                    inputStream.close();
+                } catch (IOException e) {
+                    e.printStackTrace();
+                }
+            }
+            if (connection != null) {
+                connection.disconnect();
+            }
+        }
+    }
+
+}

+ 77 - 0
src/main/java/net/yaoyi/gulop/member/auth/util/MinioUtil.java

@@ -0,0 +1,77 @@
+package net.yaoyi.gulop.member.auth.util;
+
+import com.alibaba.fastjson.JSONObject;
+import io.minio.*;
+import io.minio.http.Method;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import net.yaoyi.gulop.member.auth.config.MinioConfig;
+import org.springframework.stereotype.Component;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.util.UUID;
+import java.util.concurrent.TimeUnit;
+
+@Component
+@Slf4j
+@RequiredArgsConstructor
+public class MinioUtil {
+    private final MinioClient minioClient;
+    private final MinioConfig config;
+
+
+    /**
+     * 上传文件到MinIO
+     * @param file 要上传的文件
+     * @return 文件访问URL
+     */
+    public String uploadFile(MultipartFile file) {
+        try {
+            // 确保存储桶存在
+            boolean found = minioClient.bucketExists(
+                    BucketExistsArgs.builder()
+                            .bucket(config.getBucketName())
+                            .build());
+
+            if (!found) {
+                minioClient.makeBucket(
+                        MakeBucketArgs.builder()
+                                .bucket(config.getBucketName())
+                                .build());
+            }
+
+            // 生成唯一文件名
+            String fileName = generateFileName(file.getOriginalFilename());
+
+            // 上传文件
+            minioClient.putObject(
+                    PutObjectArgs.builder()
+                            .bucket(config.getBucketName())
+                            .object(fileName)
+                            .stream(file.getInputStream(), file.getSize(), -1)
+                            .contentType(file.getContentType())
+                            .build());
+            log.info("minioClient {}",minioClient);
+
+            // 获取访问URL
+            String files = minioClient.getPresignedObjectUrl(
+                    GetPresignedObjectUrlArgs.builder()
+                            .method(Method.GET)
+                            .bucket(config.getBucketName())
+                            .object(fileName)
+                            .expiry(7, TimeUnit.DAYS)
+                            .build());
+
+            return files;
+        } catch (Exception e) {
+            log.error("文件上传失败: {}", e.getMessage());
+            throw new RuntimeException("文件上传失败", e);
+        }
+    }
+
+    private String generateFileName(String originalName) {
+        return UUID.randomUUID().toString().replace("-", "")
+                + "-"
+                + originalName.replace(" ", "_");
+    }
+}

+ 48 - 0
src/main/java/net/yaoyi/gulop/member/auth/util/RemoteFileUtils.java

@@ -0,0 +1,48 @@
+package net.yaoyi.gulop.member.auth.util;
+
+import com.ancun.netsign.model.FileDto;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.HttpURLConnection;
+import java.net.URL;
+
+public class RemoteFileUtils {
+
+    public static FileDto fromUrl(String imageUrl) throws IOException {
+        // 创建URL对象并打开连接
+        URL url = new URL(imageUrl);
+        HttpURLConnection connection = (HttpURLConnection) url.openConnection();
+        connection.setRequestMethod("GET");
+        connection.connect();
+
+        // 检查HTTP响应状态
+        int statusCode = connection.getResponseCode();
+        if (statusCode != HttpURLConnection.HTTP_OK) {
+            throw new IOException("Failed to fetch image: HTTP " + statusCode);
+        }
+
+        // 获取输入流和文件名
+        InputStream inputStream = connection.getInputStream();
+        String fileName = extractFileNameFromUrl(url);
+
+        // 封装到FileDto
+        FileDto fileDto = new FileDto();
+        fileDto.setFileName(fileName);
+        fileDto.setFileInputStream(inputStream);
+        // fileDto.setFilePath(""); // 根据需求设置本地存储路径(如果有)
+
+        return fileDto;
+    }
+
+    private static String extractFileNameFromUrl(URL url) {
+        String path = url.getPath();
+        // 移除查询参数
+        int queryIndex = path.indexOf('?');
+        if (queryIndex > 0) {
+            path = path.substring(0, queryIndex);
+        }
+        // 提取最后的文件名部分
+        int slashIndex = path.lastIndexOf('/');
+        return (slashIndex < 0) ? "default.jpg" : path.substring(slashIndex + 1);
+    }
+}

+ 40 - 0
src/main/java/net/yaoyi/gulop/member/auth/vo/MemberAuthInfoVO.java

@@ -0,0 +1,40 @@
+package net.yaoyi.gulop.member.auth.vo;
+
+import com.alibaba.fastjson.JSONObject;
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.extension.handlers.FastjsonTypeHandler;
+import lombok.Data;
+
+import java.util.Date;
+
+@Data
+public class MemberAuthInfoVO {
+    private String phone;
+
+    private Integer isThreeAuth; // 三要素认证 0-未认证 1-已认证
+
+    private Integer isFourAuth; // 四要素认证 0-未认证 1-已认证
+
+    private Integer isOcrAuth; // OCR认证 0-未认证 1-已认证
+
+    private Integer isLivenessAuth; // 活体认证 0-未认证 1-已认证
+
+    private Integer isEsignAuth; // 电子签认证 0-未认证 1-已认证
+
+    private JSONObject threeAuthInfo; // 三要素认证数据(姓名/身份证/手机号)
+
+    @TableField(typeHandler = FastjsonTypeHandler.class)
+    private JSONObject fourAuthInfo; // 四要素认证数据(+银行卡号)
+
+    @TableField(typeHandler = FastjsonTypeHandler.class)
+    private JSONObject ocrInfo; // OCR识别数据(身份证正反面/有效期)
+
+    @TableField(typeHandler = FastjsonTypeHandler.class)
+    private JSONObject certificateInfo;
+
+    @TableField(typeHandler = FastjsonTypeHandler.class)
+    private JSONObject esignInfo; // 电子签数据(合同/签章位置)
+
+}

+ 28 - 0
src/main/java/net/yaoyi/gulop/member/auth/vo/SignItemVO.java

@@ -0,0 +1,28 @@
+package net.yaoyi.gulop.member.auth.vo;
+
+import lombok.Data;
+
+@Data
+public class SignItemVO {
+    /** 配置ID(对应gig_config.config_id) */
+    private Long configId;
+
+    /** 结算主体名称(对应gig_config.subject_name) */
+    private String subjectName;
+
+    /** 主体类型(对应gig_config.subject_type) */
+    private Integer subjectType;
+
+    /** 端类型(对应gig_config.end_type) */
+    private String endType;
+
+    /** 协议类型(对应gig_config.protocol) */
+    private String protocol;
+
+    /** 签约状态(true=已签约,false=未签约) */
+    private boolean signed;
+
+    /** 需要签约标记(对应gig_config.is_sign) */
+    private boolean needSign;
+
+}

+ 46 - 0
src/main/resources/application.properties

@@ -0,0 +1,46 @@
+spring.application.name=gulop-member-auth
+server.port=9099
+spring.profiles.active=dev
+
+
+# ??????
+
+# ???????
+#spring.datasource.url=jdbc:mysql://localhost:3306/java3000?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=UTC
+#spring.datasource.username=root
+#spring.datasource.password=root
+
+spring.datasource.url=jdbc:mysql://119.91.140.133:3306/java3000?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=UTC
+spring.datasource.username=java3000
+spring.datasource.password=java3000java3000
+spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
+#mybatis.configuration.map-underscore-to-camel-case=true
+logging.level.net.yaoyi.gulop.member.auth.mapper=false
+mybatis-plus.type-handlers-package=net.yaoyi.gulop.member.auth.handler
+# ????
+logging.level.root=INFO
+logging.level.org.springframework=INFO
+logging.level.com.example=DEBUG
+
+spring.data.redis.port=6379
+spring.data.redis.host=127.0.0.1
+spring.data.redis.username=
+spring.data.redis.password=
+
+
+minio.access-key=QPKdC5ZjfmkHmllDehTu
+minio.secret-key=GchitQwqKldkx8bt6hij0cSVUSTfbejSj9SSSgrO
+minio.bucket-name=temp
+#minio.endpoint=http://127.0.0.1:9981
+#minio.endpoint=http://file.fyang.vip
+minio.endpoint=http://yunpang.hejusuan.com:9000
+minio.region=us-east-1
+minio.secure=false
+
+
+# ????????
+spring.servlet.multipart.max-file-size=10MB
+# ????????
+spring.servlet.multipart.max-request-size=20MB
+# ??????????????
+#spring.servlet.multipart.location=/tmp

+ 13 - 0
src/test/java/net/yaoyi/gulop/member/auth/GulopMemberAuthApplicationTests.java

@@ -0,0 +1,13 @@
+package net.yaoyi.gulop.member.auth;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.test.context.SpringBootTest;
+
+@SpringBootTest
+class GulopMemberAuthApplicationTests {
+
+    @Test
+    void contextLoads() {
+    }
+
+}

+ 216 - 0
开发文档.md

@@ -0,0 +1,216 @@
+### 1. 用户认证,相关控制器`MemberAuthController`
+
+* 获取所有的认证信息 `getAuthInfo`
+* 个人运营商三要素认证 `threeElementAuth`
+* OCR身份证识别 `uploadOcr`
+* 请求个人人脸活体认证 `personFace`
+
+* 个人银行卡四要素比对 `fourElementAuth`
+* 保存资质证书 `checkCertificate`
+* 上传文件 `upload`
+
+#### 数据表 
+
+* 用户表 `ums_member`
+
+  ```sql
+  CREATE TABLE `ums_member` (
+    `id` bigint NOT NULL AUTO_INCREMENT COMMENT '用户ID(即member_id)',
+    `phone` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT 'phone',
+    `is_three_auth` tinyint DEFAULT '0' COMMENT '三要素认证 0-未认证 1-已认证',
+    `is_four_auth` tinyint DEFAULT '0' COMMENT '四要素认证 0-未认证 1-已认证',
+    `is_ocr_auth` tinyint DEFAULT '0' COMMENT 'OCR认证 0-未认证 1-已认证',
+    `is_liveness_auth` tinyint DEFAULT '0' COMMENT '活体认证 0-未认证 1-已认证',
+    `is_esign_auth` tinyint DEFAULT '0' COMMENT '电子签认证 0-未认证 1-已认证',
+    `three_auth_info` json DEFAULT NULL COMMENT '三要素认证数据(姓名/身份证/手机号)',
+    `four_auth_info` json DEFAULT NULL COMMENT '四要素认证数据(+银行卡号)',
+    `ocr_info` json DEFAULT NULL COMMENT 'OCR识别数据(身份证正反面/有效期)',
+    `liveness_info` json DEFAULT NULL COMMENT '活体检测数据(视频/分数)',
+    `user_seal_no` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '生成默认印章编号',
+    `certificate_info` json DEFAULT NULL COMMENT '证书',
+    `esign_info` json DEFAULT NULL COMMENT '电子签数据(合同/签章位置)',
+    `create_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '创建人',
+    `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    `update_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '更新人',
+    `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+    `del_flag` tinyint DEFAULT '0' COMMENT '删除标记 0-正常 1-已删除',
+    `lock_flag` tinyint DEFAULT '0' COMMENT '锁定标记 0-可编辑 1-只读',
+    PRIMARY KEY (`id`) USING BTREE,
+    KEY `idx_auth_status` (`is_three_auth`,`is_four_auth`,`is_esign_auth`) USING BTREE,
+    KEY `idx_update` (`update_time`) USING BTREE
+  ) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci ROW_FORMAT=DYNAMIC COMMENT='统一用户认证信息表'
+  ```
+
+  
+
+  
+
+### 2. 报告/协议相关控制器
+
+* 获取签约列表 `getList`
+* 签约 `signContract`
+* 查看合同信息(同步合同信息和状态) `signCheck`
+* 获取报告列表 `getReportList`
+* 下载报告 `getReportDown`
+
+
+
+#### 数据表
+
+* 报告、协议配置表
+
+  ```sql
+  CREATE TABLE `gig_config` (
+    `config_id` bigint NOT NULL AUTO_INCREMENT COMMENT 'id',
+    `ent_code` varchar(18) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '企业三合一码',
+    `protocol` varchar(5) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '协议',
+    `gateway_host` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '网关域名',
+    `subject_name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '结算主体名称',
+    `subject_type` int NOT NULL COMMENT '结算主体',
+    `subject_location` varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '结算税源地',
+    `subject_channel` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL,
+    `app_id` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '主体id',
+    `merchant_private_key` varchar(2048) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '私钥',
+    `public_key` varchar(2048) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '公钥',
+    `location_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '所属地id',
+    `sign_type` varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '签名算法类型',
+    `opt_flag` int NOT NULL DEFAULT '1' COMMENT '启用标记',
+    `def_flag` tinyint(1) NOT NULL DEFAULT '0' COMMENT '默认标记',
+    `end_type` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '端类型',
+    `created_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    `modified_time` datetime NOT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间',
+    `is_sign` tinyint DEFAULT '0' COMMENT '需要签约',
+    `agreement_url` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci COMMENT '需要签约的协议',
+    `agreement_template` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '协议对应的模板编号',
+    `enterprise_account` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '企业在爱签的account',
+    `platform_account` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '平台在爱签的account',
+    `is_file_type` tinyint DEFAULT '0' COMMENT '文件类型 0 是协议 1是报告',
+    `reception_account` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '接包方企业在爱签的account (报告才有)',
+    PRIMARY KEY (`config_id`) USING BTREE,
+    KEY `gig_config_ent_code` (`ent_code`) USING BTREE
+  ) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci ROW_FORMAT=DYNAMIC COMMENT='结算主体配置信息表';
+  ```
+
+* 用户已签约表
+
+  ```sql
+  CREATE TABLE `smart_contract` (
+    `contract_id` bigint NOT NULL AUTO_INCREMENT COMMENT '协议唯一标识,自增主键',
+    `config_id` bigint NOT NULL,
+    `contract_no` varchar(32) CHARACTER SET utf8mb3 COLLATE utf8mb3_unicode_ci NOT NULL COMMENT '唯一协议编号(如:SC_年月日+流水号)',
+    `contract_title` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_unicode_ci NOT NULL COMMENT '协议标题(前端展示用)',
+    `preview_url` text CHARACTER SET utf8mb3 COLLATE utf8mb3_unicode_ci NOT NULL COMMENT '签约协议预览地址',
+    `contract_content` text CHARACTER SET utf8mb3 COLLATE utf8mb3_unicode_ci NOT NULL COMMENT '协议的文件地址',
+    `contract_status` tinyint NOT NULL DEFAULT '1' COMMENT '协议状态:1-待签署 2-签署中 3-已完成 4-已终止',
+    `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '协议创建时间',
+    `expire_time` datetime NOT NULL COMMENT '签署截止时间',
+    `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '最后更新时间',
+    `user_id` bigint NOT NULL COMMENT '发起用户',
+    `user_account` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_unicode_ci NOT NULL COMMENT '用户在爱签ID',
+    `enterprise_account` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_unicode_ci NOT NULL COMMENT '企业在爱签ID',
+    `platform_account` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_unicode_ci NOT NULL COMMENT '平台在爱签的ID',
+    `complete_time` datetime DEFAULT NULL COMMENT '完成时间',
+    `sign_info` json DEFAULT NULL,
+    PRIMARY KEY (`contract_id`) USING BTREE,
+    UNIQUE KEY `contract_no` (`contract_no`) USING BTREE,
+    KEY `idx_status` (`contract_status`) USING BTREE,
+    KEY `idx_expire` (`expire_time`) USING BTREE
+  ) ENGINE=MyISAM AUTO_INCREMENT=7 DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci ROW_FORMAT=DYNAMIC COMMENT='智能合约主表,集成签约方预置与状态跟踪';
+  ```
+
+  
+
+
+
+
+
+
+
+### 3. 其他数据表
+
+* 爱签配置表
+
+  ```
+  CREATE TABLE `gig_config` (
+    `config_id` bigint NOT NULL AUTO_INCREMENT COMMENT 'id',
+    `ent_code` varchar(18) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '企业三合一码',
+    `protocol` varchar(5) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '协议',
+    `gateway_host` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '网关域名',
+    `subject_name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '结算主体名称',
+    `subject_type` int NOT NULL COMMENT '结算主体',
+    `subject_location` varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '结算税源地',
+    `subject_channel` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL,
+    `app_id` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '主体id',
+    `merchant_private_key` varchar(2048) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '私钥',
+    `public_key` varchar(2048) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '公钥',
+    `location_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '所属地id',
+    `sign_type` varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '签名算法类型',
+    `opt_flag` int NOT NULL DEFAULT '1' COMMENT '启用标记',
+    `def_flag` tinyint(1) NOT NULL DEFAULT '0' COMMENT '默认标记',
+    `end_type` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '端类型',
+    `created_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    `modified_time` datetime NOT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间',
+    `is_sign` tinyint DEFAULT '0' COMMENT '需要签约',
+    `agreement_url` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci COMMENT '需要签约的协议',
+    `agreement_template` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '协议对应的模板编号',
+    `enterprise_account` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '企业在爱签的account',
+    `platform_account` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '平台在爱签的account',
+    `is_file_type` tinyint DEFAULT '0' COMMENT '文件类型 0 是协议 1是报告',
+    `reception_account` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '接包方企业在爱签的account (报告才有)',
+    PRIMARY KEY (`config_id`) USING BTREE,
+    KEY `gig_config_ent_code` (`ent_code`) USING BTREE
+  ) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci ROW_FORMAT=DYNAMIC COMMENT='结算主体配置信息表';
+  ```
+
+  
+
+* 爱签api调用历史表
+
+  ```
+  CREATE TABLE `ums_ext_api_his` (
+    `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+    `ext_id` varchar(50) CHARACTER SET utf8mb3 COLLATE utf8mb3_unicode_ci NOT NULL COMMENT '第三方接口ID',
+    `user_id` bigint NOT NULL COMMENT '用户ID',
+    `api_name` varchar(100) CHARACTER SET utf8mb3 COLLATE utf8mb3_unicode_ci NOT NULL COMMENT '接口名称',
+    `request_data` json DEFAULT NULL COMMENT '请求数据',
+    `response_data` json DEFAULT NULL COMMENT '响应数据',
+    `status` tinyint DEFAULT '1' COMMENT '状态:0-失败,1-成功',
+    `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    PRIMARY KEY (`id`) USING BTREE
+  ) ENGINE=MyISAM AUTO_INCREMENT=94 DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci ROW_FORMAT=DYNAMIC COMMENT='接口调用历史表';
+  ```
+
+  
+
+​	数据表参考原有代码里的数据表,并增加了一些字段上去
+
+
+
+
+
+### 4. 需要注意的地方
+
+* 4.1 没有登录功能,因此在控制器里固定了用户ID,并传递用户ID到服务层、数据层
+
+* 4.2 活体认证通过后爱签是直接在小程序的webview组件中返回成功/失败结果,根据此结果再调用后台其他逻辑(保存认证结果、添加实名人到爱签)
+
+* 4.3 报告、协议配置表`gig_config`新增字段说明:
+
+  * agreement_url 协议或报告URL
+  * is_sign 为是否需要签约 若不需要,用户则调用税地(调用代码在签约功能中,已注释)
+
+  * 企业在爱签的account (报告和协议需要)
+
+  * 平台在爱签的account (报告需要)
+
+  * 接包方企业在爱签的account (报告需要)
+
+  * is_file_type 文件类型 0 是协议 1是报告
+
+    
+
+* 4.4 报告的骑缝章需要指定加盖的页码,因此每个报告可能不一致,需要在配置表中配置
+
+  ### 现在的demo六里,各个配置是手动添加到数据库的
+
+​	

二進制
要易系统(身份认证+爱签签约)项目移交清单 (1).pdf


部分文件因文件數量過多而無法顯示