Ver código fonte

增加爱签相关接口

zhou.xiaoning 2 anos atrás
pai
commit
12a10a45b3

+ 32 - 1
api/v1/account/certification.go

@@ -42,7 +42,7 @@ func QueryUserESignRecord(c *gin.Context) {
 // @accept   application/json
 // @Produce  application/json
 // @Param    data body     request.AddUserReq            true "入参"
-// @Success  200  {object} response.Response{msg=string} "出参"
+// @Success  200  {object} response.Response{msg=string}         "出参"
 // @Router   /Account/AddUser [post]
 // @Tags     账户服务
 func AddUser(c *gin.Context) {
@@ -66,3 +66,34 @@ func AddUser(c *gin.Context) {
 		response.FailWithMessage(err.Error(), c)
 	}
 }
+
+// AddUser 上传待签署文件和添加签署方
+// @Summary  上传待签署文件和添加签署方
+// @Security ApiKeyAuth
+// @accept   application/json
+// @Produce  application/json
+// @Param    data body     request.CreateContractAndAddSignerReq true "入参"
+// @Success  200  {object} response.Response{msg=string} "出参"
+// @Router   /Account/CreateContractAndAddSigner [post]
+// @Tags     账户服务
+func CreateContractAndAddSigner(c *gin.Context) {
+	g := utils.GinUtils{C: c}
+	r := request.CreateContractAndAddSignerReq{}
+	g.BindJsonReq(&r)
+	if g.Err != nil {
+		return
+	}
+
+	// 获取请求账号信息
+	claims := g.GetClaims()
+	if g.Err != nil {
+		return
+	}
+
+	if err := signService.CreateContractAndAddSigner(r, claims.UserID); err == nil {
+		response.Ok(g.C)
+	} else {
+		global.M2A_LOG.Error(err.Error(), zap.Error(err))
+		response.FailWithMessage(err.Error(), c)
+	}
+}

+ 62 - 0
docs/docs.go

@@ -66,6 +66,56 @@ const docTemplate = `{
                 }
             }
         },
+        "/Account/CreateContractAndAddSigner": {
+            "post": {
+                "security": [
+                    {
+                        "ApiKeyAuth": []
+                    }
+                ],
+                "consumes": [
+                    "application/json"
+                ],
+                "produces": [
+                    "application/json"
+                ],
+                "tags": [
+                    "账户服务"
+                ],
+                "summary": "上传待签署文件和添加签署方",
+                "parameters": [
+                    {
+                        "description": "入参",
+                        "name": "data",
+                        "in": "body",
+                        "required": true,
+                        "schema": {
+                            "$ref": "#/definitions/request.CreateContractAndAddSignerReq"
+                        }
+                    }
+                ],
+                "responses": {
+                    "200": {
+                        "description": "出参",
+                        "schema": {
+                            "allOf": [
+                                {
+                                    "$ref": "#/definitions/response.Response"
+                                },
+                                {
+                                    "type": "object",
+                                    "properties": {
+                                        "msg": {
+                                            "type": "string"
+                                        }
+                                    }
+                                }
+                            ]
+                        }
+                    }
+                }
+            }
+        },
         "/Account/Login": {
             "post": {
                 "consumes": [
@@ -489,6 +539,18 @@ const docTemplate = `{
                 }
             }
         },
+        "request.CreateContractAndAddSignerReq": {
+            "type": "object",
+            "required": [
+                "templateNo"
+            ],
+            "properties": {
+                "templateNo": {
+                    "description": "合同模板编号",
+                    "type": "string"
+                }
+            }
+        },
         "request.LoginReq": {
             "type": "object",
             "required": [

+ 62 - 0
docs/swagger.json

@@ -57,6 +57,56 @@
                 }
             }
         },
+        "/Account/CreateContractAndAddSigner": {
+            "post": {
+                "security": [
+                    {
+                        "ApiKeyAuth": []
+                    }
+                ],
+                "consumes": [
+                    "application/json"
+                ],
+                "produces": [
+                    "application/json"
+                ],
+                "tags": [
+                    "账户服务"
+                ],
+                "summary": "上传待签署文件和添加签署方",
+                "parameters": [
+                    {
+                        "description": "入参",
+                        "name": "data",
+                        "in": "body",
+                        "required": true,
+                        "schema": {
+                            "$ref": "#/definitions/request.CreateContractAndAddSignerReq"
+                        }
+                    }
+                ],
+                "responses": {
+                    "200": {
+                        "description": "出参",
+                        "schema": {
+                            "allOf": [
+                                {
+                                    "$ref": "#/definitions/response.Response"
+                                },
+                                {
+                                    "type": "object",
+                                    "properties": {
+                                        "msg": {
+                                            "type": "string"
+                                        }
+                                    }
+                                }
+                            ]
+                        }
+                    }
+                }
+            }
+        },
         "/Account/Login": {
             "post": {
                 "consumes": [
@@ -480,6 +530,18 @@
                 }
             }
         },
+        "request.CreateContractAndAddSignerReq": {
+            "type": "object",
+            "required": [
+                "templateNo"
+            ],
+            "properties": {
+                "templateNo": {
+                    "description": "合同模板编号",
+                    "type": "string"
+                }
+            }
+        },
         "request.LoginReq": {
             "type": "object",
             "required": [

+ 36 - 0
docs/swagger.yaml

@@ -75,6 +75,14 @@ definitions:
     - idCard
     - name
     type: object
+  request.CreateContractAndAddSignerReq:
+    properties:
+      templateNo:
+        description: 合同模板编号
+        type: string
+    required:
+    - templateNo
+    type: object
   request.LoginReq:
     properties:
       clientType:
@@ -211,6 +219,34 @@ paths:
       summary: 实名认证添加用户
       tags:
       - 账户服务
+  /Account/CreateContractAndAddSigner:
+    post:
+      consumes:
+      - application/json
+      parameters:
+      - description: 入参
+        in: body
+        name: data
+        required: true
+        schema:
+          $ref: '#/definitions/request.CreateContractAndAddSignerReq'
+      produces:
+      - application/json
+      responses:
+        "200":
+          description: 出参
+          schema:
+            allOf:
+            - $ref: '#/definitions/response.Response'
+            - properties:
+                msg:
+                  type: string
+              type: object
+      security:
+      - ApiKeyAuth: []
+      summary: 上传待签署文件和添加签署方
+      tags:
+      - 账户服务
   /Account/Login:
     post:
       consumes:

+ 5 - 0
model/account/request/certification.go

@@ -9,3 +9,8 @@ type AddUserReq struct {
 	IdCardPhotoBackURL string `json:"idCardPhotoBackURL"`         // 证件照背面
 	Mobile             string `json:"mobile"`                     // 手机号码
 }
+
+// CreateContractAndAddSignerReq 上传待签署文件和添加签署方请求参数
+type CreateContractAndAddSignerReq struct {
+	TemplateNo string `json:"templateNo" binding:"required"` // 合同模板编号
+}

+ 42 - 0
py/Api.py

@@ -55,4 +55,46 @@ def createContract(inputData):
     #请求地址
     Result = HttpUtils.HttpUtils.doPOST(apiUrl, reqBodyData, appId, appKey)
 
+    return Result
+
+# 添加签署方 https://{host}/contract/addSigner
+def addSigner(inputData):
+    # 从输入数据中获取调用接口
+    contractNo = inputData["contractNo"]
+    account = inputData["account"]
+    signStrategyList = inputData["signStrategyList"]
+    
+    appId = inputData["appId"]
+    appKey = inputData["appKey"]
+    apiUrl = inputData["apiUrl"]
+
+    # 组装参数
+    reqBodyData = [{
+        "contractNo": contractNo, 
+        "account": account, 
+        "signStrategyList": signStrategyList
+    }]
+    #请求地址
+    Result = HttpUtils.HttpUtils.doPOST(apiUrl, reqBodyData, appId, appKey)
+
+    return Result
+
+# 下载合同 https://{host}/contract/downloadContract
+def downloadContract(inputData):
+    # 从输入数据中获取调用接口
+    contractNo = inputData["contractNo"]
+    downloadFileType = inputData["downloadFileType"]
+    
+    appId = inputData["appId"]
+    appKey = inputData["appKey"]
+    apiUrl = inputData["apiUrl"]
+
+    # 组装参数
+    reqBodyData = {
+        "contractNo": contractNo, 
+        "downloadFileType": downloadFileType
+    }
+    #请求地址
+    Result = HttpUtils.HttpUtils.doPOST(apiUrl, reqBodyData, appId, appKey)
+
     return Result

+ 9 - 0
py/Enter.py

@@ -10,10 +10,19 @@ def main():
     input_json = sys.stdin.read().strip()
     # 解析JSON数据
     inputData = json.loads(input_json)
+    
     # 从输入数据中获取调用接口
     api = inputData["api"]
     if (api == "addPerson"):
         result = Api.addPerson(inputData)
+    elif (api == "createContract"):
+        result = Api.createContract(inputData)
+    elif (api == "addSigner"):
+        result = Api.addSigner(inputData)
+    elif (api == "downloadContract"):
+        result = Api.downloadContract(inputData)
+    else:
+        result = ""
 
     # 输出结果
     print(result)

+ 1 - 0
router/account.go

@@ -20,5 +20,6 @@ func InitAccountPrivateRouter(r *gin.RouterGroup) {
 		accountR.GET("Loginout", account.Loginout)
 		accountR.GET("QueryUserESignRecord", account.QueryUserESignRecord)
 		accountR.POST("AddUser", account.AddUser)
+		accountR.POST("CreateContractAndAddSigner", account.CreateContractAndAddSigner)
 	}
 }

+ 288 - 168
service/asign/asign.go

@@ -1,30 +1,17 @@
 package asign
 
 import (
-	"bytes"
-	"crypto"
-	"crypto/md5"
-	"crypto/rsa"
-	"crypto/sha256"
-	"crypto/x509"
-	"encoding/base64"
-	"encoding/hex"
 	"encoding/json"
-	"encoding/pem"
-	"errors"
-	"fmt"
-	"io"
 	"mtp20access/global"
-	"net/http"
-	"net/url"
 	"os/exec"
-	"strconv"
 	"strings"
-	"time"
 
 	"go.uber.org/zap"
 )
 
+var appId = global.M2A_CONFIG.Asign.AppId
+var privateKey = global.M2A_CONFIG.Asign.PrivateKey
+
 // 签名规范:
 //
 //	1、表单提交方式:form-data
@@ -38,102 +25,102 @@ import (
 //	4、签名算法:
 //	    4.1、将上述3所属的bizData(json字符串),按照阿拉伯字母排序(如:{"ba":1,"ac":2}--->{"ac":2,"ba":1}),
 //	    4.2、将4.1排序后的字符串,将【bizData+md5(bizData)+ appId + timestatmp】拼接后利用RSA非对称加密算法(SHA1withRSA),计算出最后的签名sign,对其base64编码,放入head的key(sign)中。
-func getSignature(bizData string, appId string, privateKeyPEM string) (signatureBase64 string, timestamp string, err error) {
-	timestamp = strconv.FormatInt(time.Now().UnixMilli(), 10)
+// func getSignature(bizData string, appId string, privateKeyPEM string) (signatureBase64 string, timestamp string, err error) {
+// 	timestamp = strconv.FormatInt(time.Now().UnixMilli(), 10)
 
-	privateKeyBlock, _ := pem.Decode([]byte(privateKeyPEM))
-	// if privateKeyBlock == nil || privateKeyBlock.Type != "RSA PRIVATE KEY" {
-	if privateKeyBlock == nil {
-		err = errors.New("签名失败: Error decoding private key PEM")
-		return
-	}
+// 	privateKeyBlock, _ := pem.Decode([]byte(privateKeyPEM))
+// 	// if privateKeyBlock == nil || privateKeyBlock.Type != "RSA PRIVATE KEY" {
+// 	if privateKeyBlock == nil {
+// 		err = errors.New("签名失败: Error decoding private key PEM")
+// 		return
+// 	}
 
-	// 解析 PKCS#8 格式的私钥
-	privateKey, err := x509.ParsePKCS1PrivateKey(privateKeyBlock.Bytes)
-	if err != nil {
-		fmt.Println("Failed to parse private key:", err)
-		return
-	}
+// 	// 解析 PKCS#8 格式的私钥
+// 	privateKey, err := x509.ParsePKCS1PrivateKey(privateKeyBlock.Bytes)
+// 	if err != nil {
+// 		fmt.Println("Failed to parse private key:", err)
+// 		return
+// 	}
 
-	// md5(bizData)
-	m := md5.New()
-	m.Write([]byte(bizData))
-	bdMd5Hx := hex.EncodeToString(m.Sum(nil))
+// 	// md5(bizData)
+// 	m := md5.New()
+// 	m.Write([]byte(bizData))
+// 	bdMd5Hx := hex.EncodeToString(m.Sum(nil))
 
-	// 待签内容
-	message := bizData + bdMd5Hx + appId + timestamp
+// 	// 待签内容
+// 	message := bizData + bdMd5Hx + appId + timestamp
 
-	// 使用私钥进行签名
-	hashed := sha256.Sum256([]byte(message))
-	signature, err := rsa.SignPKCS1v15(nil, privateKey, crypto.SHA256, hashed[:])
-	if err != nil {
-		fmt.Println("Error signing:", err)
-		return
-	}
+// 	// 使用私钥进行签名
+// 	hashed := sha256.Sum256([]byte(message))
+// 	signature, err := rsa.SignPKCS1v15(nil, privateKey, crypto.SHA256, hashed[:])
+// 	if err != nil {
+// 		fmt.Println("Error signing:", err)
+// 		return
+// 	}
 
-	// fmt.Println(signature)
-	signatureBase64 = base64.StdEncoding.EncodeToString(signature)
+// 	// fmt.Println(signature)
+// 	signatureBase64 = base64.StdEncoding.EncodeToString(signature)
 
-	return
-}
+// 	return
+// }
 
-type pySignReqData struct {
-	ReqBodyData string `json:"reqBodyData"`
-	Timestamp   string `json:"timestamp"`
-	AppId       string `json:"appId"`
-	AppKey      string `json:"appKey"`
-}
+// type pySignReqData struct {
+// 	ReqBodyData string `json:"reqBodyData"`
+// 	Timestamp   string `json:"timestamp"`
+// 	AppId       string `json:"appId"`
+// 	AppKey      string `json:"appKey"`
+// }
 
-func getSignatureByPy(bizData string, appId string, privateKeyPEM string) (signatureBase64 string, timestamp string, err error) {
-	// md5(bizData)
-	m := md5.New()
-	m.Write([]byte(bizData))
-	bdMd5Hx := hex.EncodeToString(m.Sum(nil))
-	timestamp = strconv.FormatInt(time.Now().UnixMilli(), 10)
-	// timestamp = "1691559290641"
-	// 待签内容
-	message := bizData + bdMd5Hx + appId + timestamp
+// func getSignatureByPy(bizData string, appId string, privateKeyPEM string) (signatureBase64 string, timestamp string, err error) {
+// 	// md5(bizData)
+// 	m := md5.New()
+// 	m.Write([]byte(bizData))
+// 	bdMd5Hx := hex.EncodeToString(m.Sum(nil))
+// 	timestamp = strconv.FormatInt(time.Now().UnixMilli(), 10)
+// 	// timestamp = "1691559290641"
+// 	// 待签内容
+// 	message := bizData + bdMd5Hx + appId + timestamp
 
-	// 构建请求数据结构
-	reqData := pySignReqData{
-		ReqBodyData: message,
-		Timestamp:   timestamp,
-		AppId:       appId,
-		AppKey:      privateKeyPEM,
-	}
-	// 将请求数据转换为JSON字符串
-	reqJSON, err := json.Marshal(reqData)
-	if err != nil {
-		global.M2A_LOG.Error("[getSignatureByPy] 构建请求参数失败", zap.Error(err))
-		return
-	}
-	// 要执行的Python脚本命令
-	pythonScriptPath := "./py/sign.py"
-	// 创建一个命令对象
-	cmd := exec.Command("py", pythonScriptPath)
-	// 设置标准输入为JSON字符串
-	cmd.Stdin = strings.NewReader(string(reqJSON))
-	// 获取标准输出
-	output, err := cmd.CombinedOutput()
-	if err != nil {
-		global.M2A_LOG.Error("[getSignatureByPy] 签名失败", zap.Error(err))
-		return
-	}
-	// 获取签名结果
-	signatureBase64 = string(output)
+// 	// 构建请求数据结构
+// 	reqData := pySignReqData{
+// 		ReqBodyData: message,
+// 		Timestamp:   timestamp,
+// 		AppId:       appId,
+// 		AppKey:      privateKeyPEM,
+// 	}
+// 	// 将请求数据转换为JSON字符串
+// 	reqJSON, err := json.Marshal(reqData)
+// 	if err != nil {
+// 		global.M2A_LOG.Error("[getSignatureByPy] 构建请求参数失败", zap.Error(err))
+// 		return
+// 	}
+// 	// 要执行的Python脚本命令
+// 	pythonScriptPath := "./py/sign.py"
+// 	// 创建一个命令对象
+// 	cmd := exec.Command("py", pythonScriptPath)
+// 	// 设置标准输入为JSON字符串
+// 	cmd.Stdin = strings.NewReader(string(reqJSON))
+// 	// 获取标准输出
+// 	output, err := cmd.CombinedOutput()
+// 	if err != nil {
+// 		global.M2A_LOG.Error("[getSignatureByPy] 签名失败", zap.Error(err))
+// 		return
+// 	}
+// 	// 获取签名结果
+// 	signatureBase64 = string(output)
 
-	return
-}
+// 	return
+// }
 
-// addPersonalUser 响应参数
-type AddPersonalUserRsp struct {
-	Code int                 `json:"code"` // 响应码,100000表示成功,其他表示异常
-	Msg  string              `json:"msg"`  // 响应信息
-	Data AddPersonalUserData `json:"data"` // 	响应数据
+type AsignData interface {
+	AddPersonalUserData | CreateContractData | AddSignerData
 }
 
-type AddPersonalUserData struct {
-	SealNo string `json:"sealNo"` // 默认印章编号
+// AsignRsp 爱签响应参数
+type AsignRsp[T AsignData] struct {
+	Code int    `json:"code"` // 响应码,100000表示成功,其他表示异常
+	Msg  string `json:"msg"`  // 响应信息
+	Data T      `json:"data"` // 	响应数据
 }
 
 // https://preweb.asign.cn/platform/openDoc/docDetail?mid=addPersonalUser
@@ -147,74 +134,78 @@ type AddPersonalUserData struct {
 // 100579	参数错误,{param}不能为空
 // 100598	参数错误,身份证号码格式不正确
 // 100639	参数错误,名字点号格式不正确
-func AddPersonalUser(account, name, idCard, mobile string, idCardType *int) (rspData AddPersonalUserRsp, err error) {
-	apiUrl := global.M2A_CONFIG.Asign.URL + "user/addPersonalUser"
-	appId := "290912417"
-	privateKey := `MIIBUwIBADANBgkqhkiG9w0BAQEFAASCAT0wggE5AgEAAkEAkMD+72J6iAF0ZNV+3t628lsRHfJ80nKZWK5/C7Pg+AZmOIzJlwHsKhRzCvxoxqYHQprhiFzW9l73v9vD9l1JYwIDAQABAkBVijccr01JYdKuY5t9iI8D2NzcnZc1pZMI3NUmzT18Uyg7b9CUvGHlLeg/gdT4QtVd7wIzHYCY4letEcEMh54BAiEAwzNWusj5XiLmty7PI0Hbakx4HtcND1+P0UHLEWqWOuECIQC91zQuL7nStgGzT3HvaeBB5Ouapa39fHRm2nCjHaxwwwIgRR2XdvmUOj23XWMomr5F14SN/7V7fVcD0D8wjNElsmECIDYavV5kb7tj7/wgqkInlKhzC8rZaUsTS0F9BBkY/eptAiAQJ8Saz8YlMIESdHMxANGSog01fECbcZqLFMuNf8SorA==`
-
-	// 请求参数
-	params := make(map[string]interface{})
-	params["account"] = account
-	params["name"] = name
-	params["idCard"] = idCard
-	params["idCardType"] = 1
-	if idCardType != nil {
-		params["idCardType"] = *idCardType
-	}
-	params["mobile"] = mobile
-	// 用户实名认证模式为强制认证时,需要选择认证方法:
-	// 1:身份证二要素认证
-	// 2:运营商三要素认证
-	// 3:银行卡四要素认证
-	params["identifyType"] = 2
-	params["identifyMobile"] = mobile
-	params["isNotice"] = 1
-	bizData, err := json.Marshal(params)
-	if err != nil {
-		global.M2A_LOG.Error("[AddPersonalUser] 构建请求参数失败", zap.Error(err))
-		return
-	}
-	global.M2A_LOG.Info("[AddPersonalUser] 构建请求参数", zap.Any("params", string(bizData)))
-	// 签名
-	sign, timestamp, err := getSignatureByPy(string(bizData), appId, privateKey)
-	sign = strings.Replace(sign, "\r\n", "", -1)
-	sign = strings.Replace(sign, "\r", "", -1)
-	if err != nil {
-		global.M2A_LOG.Error("[AddPersonalUser] 签名失败", zap.Error(err))
-		return
-	}
-	global.M2A_LOG.Info("[AddPersonalUser] 签名", zap.Any("sign", sign))
-	// 构建form-data请求参数
-	formValues := url.Values{}
-	formValues.Set("appId", appId)
-	formValues.Set("timestamp", timestamp)
-	formValues.Set("bizData", string(bizData))
-	// 构建请求
-	req, err := http.NewRequest("POST", apiUrl, bytes.NewReader([]byte(formValues.Encode())))
-	// 设置请求头
-	req.Header.Set("sign", sign)
-	req.Header.Set("timestamp", timestamp)
-	req.Header.Set("Content-Type", "multipart/form-data; charset=UTF-8")
-	req.Header.Set("Accept", "*/*")
-
-	client := &http.Client{}
-	rsp, err := client.Do(req)
-	if err != nil {
-		global.M2A_LOG.Error("[AddPersonalUser] 请求失败", zap.Error(err))
-		return
-	}
-	defer rsp.Body.Close()
-	body, err := io.ReadAll(rsp.Body)
-	if err != nil {
-		global.M2A_LOG.Error("[AddPersonalUser] 获取body失败", zap.Error(err))
-		return
-	}
-	if err = json.Unmarshal(body, &rspData); err != nil {
-		global.M2A_LOG.Error("[AddPersonalUser] 反序列化body失败", zap.Error(err))
-		return
-	}
+// func AddPersonalUser(account, name, idCard, mobile string, idCardType *int) (rspData AddPersonalUserRsp, err error) {
+// 	apiUrl := global.M2A_CONFIG.Asign.URL + "user/addPersonalUser"
+// 	appId := "290912417"
+// 	privateKey := `MIIBUwIBADANBgkqhkiG9w0BAQEFAASCAT0wggE5AgEAAkEAkMD+72J6iAF0ZNV+3t628lsRHfJ80nKZWK5/C7Pg+AZmOIzJlwHsKhRzCvxoxqYHQprhiFzW9l73v9vD9l1JYwIDAQABAkBVijccr01JYdKuY5t9iI8D2NzcnZc1pZMI3NUmzT18Uyg7b9CUvGHlLeg/gdT4QtVd7wIzHYCY4letEcEMh54BAiEAwzNWusj5XiLmty7PI0Hbakx4HtcND1+P0UHLEWqWOuECIQC91zQuL7nStgGzT3HvaeBB5Ouapa39fHRm2nCjHaxwwwIgRR2XdvmUOj23XWMomr5F14SN/7V7fVcD0D8wjNElsmECIDYavV5kb7tj7/wgqkInlKhzC8rZaUsTS0F9BBkY/eptAiAQJ8Saz8YlMIESdHMxANGSog01fECbcZqLFMuNf8SorA==`
 
-	return
+// 	// 请求参数
+// 	params := make(map[string]interface{})
+// 	params["account"] = account
+// 	params["name"] = name
+// 	params["idCard"] = idCard
+// 	params["idCardType"] = 1
+// 	if idCardType != nil {
+// 		params["idCardType"] = *idCardType
+// 	}
+// 	params["mobile"] = mobile
+// 	// 用户实名认证模式为强制认证时,需要选择认证方法:
+// 	// 1:身份证二要素认证
+// 	// 2:运营商三要素认证
+// 	// 3:银行卡四要素认证
+// 	params["identifyType"] = 2
+// 	params["identifyMobile"] = mobile
+// 	params["isNotice"] = 1
+// 	bizData, err := json.Marshal(params)
+// 	if err != nil {
+// 		global.M2A_LOG.Error("[AddPersonalUser] 构建请求参数失败", zap.Error(err))
+// 		return
+// 	}
+// 	global.M2A_LOG.Info("[AddPersonalUser] 构建请求参数", zap.Any("params", string(bizData)))
+// 	// 签名
+// 	sign, timestamp, err := getSignatureByPy(string(bizData), appId, privateKey)
+// 	sign = strings.Replace(sign, "\r\n", "", -1)
+// 	sign = strings.Replace(sign, "\r", "", -1)
+// 	if err != nil {
+// 		global.M2A_LOG.Error("[AddPersonalUser] 签名失败", zap.Error(err))
+// 		return
+// 	}
+// 	global.M2A_LOG.Info("[AddPersonalUser] 签名", zap.Any("sign", sign))
+// 	// 构建form-data请求参数
+// 	formValues := url.Values{}
+// 	formValues.Set("appId", appId)
+// 	formValues.Set("timestamp", timestamp)
+// 	formValues.Set("bizData", string(bizData))
+// 	// 构建请求
+// 	req, err := http.NewRequest("POST", apiUrl, bytes.NewReader([]byte(formValues.Encode())))
+// 	// 设置请求头
+// 	req.Header.Set("sign", sign)
+// 	req.Header.Set("timestamp", timestamp)
+// 	req.Header.Set("Content-Type", "multipart/form-data; charset=UTF-8")
+// 	req.Header.Set("Accept", "*/*")
+
+// 	client := &http.Client{}
+// 	rsp, err := client.Do(req)
+// 	if err != nil {
+// 		global.M2A_LOG.Error("[AddPersonalUser] 请求失败", zap.Error(err))
+// 		return
+// 	}
+// 	defer rsp.Body.Close()
+// 	body, err := io.ReadAll(rsp.Body)
+// 	if err != nil {
+// 		global.M2A_LOG.Error("[AddPersonalUser] 获取body失败", zap.Error(err))
+// 		return
+// 	}
+// 	if err = json.Unmarshal(body, &rspData); err != nil {
+// 		global.M2A_LOG.Error("[AddPersonalUser] 反序列化body失败", zap.Error(err))
+// 		return
+// 	}
+
+// 	return
+// }
+
+type AddPersonalUserData struct {
+	SealNo string `json:"sealNo"` // 默认印章编号
 }
 
 // https://preweb.asign.cn/platform/openDoc/docDetail?mid=addPersonalUser
@@ -228,9 +219,7 @@ func AddPersonalUser(account, name, idCard, mobile string, idCardType *int) (rsp
 // 100579	参数错误,{param}不能为空
 // 100598	参数错误,身份证号码格式不正确
 // 100639	参数错误,名字点号格式不正确
-func AddPersonalUserBy(account, name, idCard, mobile string, idCardType *int) (rspData AddPersonalUserRsp, err error) {
-	appId := global.M2A_CONFIG.Asign.AppId
-	privateKey := global.M2A_CONFIG.Asign.PrivateKey
+func AddPersonalUserBy(account, name, idCard, mobile string, idCardType *int) (rspData AsignRsp[AddPersonalUserData], err error) {
 	apiUrl := global.M2A_CONFIG.Asign.URL + "user/addPersonalUser"
 
 	// 构建请求数据结构
@@ -277,3 +266,134 @@ func AddPersonalUserBy(account, name, idCard, mobile string, idCardType *int) (r
 
 	return
 }
+
+type CreateContractData struct {
+	PreviewUrl    string        `json:"previewUrl"`    // 合同预览链接
+	ContractFiles []interface{} `json:"contractFiles"` // 合同文件信息(文件名称,附件编号,页数)
+}
+
+/*
+*
+CreateContract 上传待签署文件
+
+contractNo 合同ID,合同唯一编号
+contractName 合同名称
+templateNo 合同模板编号
+*
+*/
+func CreateContract(contractNo, contractName, templateNo string) (rspData AsignRsp[CreateContractData], err error) {
+	apiUrl := global.M2A_CONFIG.Asign.URL + "contract/createContract"
+
+	// 构建请求数据结构
+	reqData := make(map[string]interface{})
+	reqData["contractNo"] = contractNo
+	reqData["contractName"] = contractName
+	reqData["signOrder"] = 1 // 1:无序签约(默认
+	reqData["templates"] = []map[string]string{
+		{"templateNo": templateNo}} // 合同模板编号 - 目前只支持一份合同签一份协议
+	reqData["notifyUrl"] = ""
+
+	reqData["api"] = "createContract"
+	reqData["appId"] = appId
+	reqData["appKey"] = privateKey
+	reqData["apiUrl"] = apiUrl
+
+	// 将请求数据转换为JSON字符串
+	reqJSON, err := json.Marshal(reqData)
+	if err != nil {
+		global.M2A_LOG.Error("[AddPersonalUserBy] 构建请求参数失败", zap.Error(err))
+		return
+	}
+	// 要执行的Python脚本命令
+	pythonScriptPath := "./py/Enter.py"
+	// 创建一个命令对象
+	cmd := exec.Command("py", pythonScriptPath)
+	// 设置标准输入为JSON字符串
+	cmd.Stdin = strings.NewReader(string(reqJSON))
+	// 获取标准输出
+	output, err := cmd.CombinedOutput()
+	if err != nil {
+		global.M2A_LOG.Error("[AddPersonalUserBy] 请求失败", zap.Error(err))
+		return
+	}
+
+	// 结果
+	rspBody := string(output)
+	if err = json.Unmarshal([]byte(rspBody), &rspData); err != nil {
+		global.M2A_LOG.Error("[AddPersonalUserBy] 反序列化body失败", zap.Error(err))
+		return
+	}
+
+	return
+}
+
+// 合同用户信息
+type SignUserData struct {
+	Account    string `json:"account"`    // 用户唯一识别码
+	SignUrl    string `json:"signUrl"`    // 合同签署链接
+	PwdSignUrl string `json:"pwdSignUrl"` // 密码签署链接
+	SignOrder  int    `json:"signOrder"`  // 顺序签约的序号
+	Name       string `json:"name"`       // 用户姓名
+	IdCard     string `json:"idCard"`     // 	用户身份证
+}
+
+// 添加签署方响应数据
+type AddSignerData struct {
+	ContractNo   string         `json:"contractNo"`   // 合同编号
+	ContractName string         `json:"contractName"` // 合同名称
+	ValidityTime string         `json:"validityTime"` // 合同有效期
+	PreviewUrl   string         `json:"previewUrl"`   // 合同预览链接
+	SignUser     []SignUserData `json:"signUser"`     // 合同用户信息
+}
+
+/*
+*
+AddSigner 添加签署方
+
+contractNo 合同ID,合同唯一编号
+account 用户唯一识别码(UserID)
+*
+*/
+func AddSigner(contractNo, account string) (rspData AsignRsp[AddSignerData], err error) {
+	apiUrl := global.M2A_CONFIG.Asign.URL + "contract/addSigner"
+
+	// 构建请求数据结构
+	reqData := make(map[string]interface{})
+	reqData["contractNo"] = contractNo
+	reqData["account"] = account
+	reqData["signStrategyList"] = []map[string]int{
+		{"attachNo": 1, "locationMode": 4}}
+
+	reqData["api"] = "addSigner"
+	reqData["appId"] = appId
+	reqData["appKey"] = privateKey
+	reqData["apiUrl"] = apiUrl
+
+	// 将请求数据转换为JSON字符串
+	reqJSON, err := json.Marshal(reqData)
+	if err != nil {
+		global.M2A_LOG.Error("[AddSigner] 构建请求参数失败", zap.Error(err))
+		return
+	}
+	// 要执行的Python脚本命令
+	pythonScriptPath := "./py/Enter.py"
+	// 创建一个命令对象
+	cmd := exec.Command("py", pythonScriptPath)
+	// 设置标准输入为JSON字符串
+	cmd.Stdin = strings.NewReader(string(reqJSON))
+	// 获取标准输出
+	output, err := cmd.CombinedOutput()
+	if err != nil {
+		global.M2A_LOG.Error("[AddSigner] 请求失败", zap.Error(err))
+		return
+	}
+
+	// 结果
+	rspBody := string(output)
+	if err = json.Unmarshal([]byte(rspBody), &rspData); err != nil {
+		global.M2A_LOG.Error("[AddSigner] 反序列化body失败", zap.Error(err))
+		return
+	}
+
+	return
+}

+ 81 - 0
service/sign/sign.go

@@ -9,6 +9,7 @@ import (
 	"mtp20access/model/account/request"
 	"mtp20access/service/asign"
 	"strconv"
+	"time"
 
 	"go.uber.org/zap"
 )
@@ -60,3 +61,83 @@ func AddUser(req request.AddUserReq, userId int) (err error) {
 
 	return
 }
+
+// CreateContractAndAddSigner 上传待签署文件和添加签署方
+func CreateContractAndAddSigner(req request.CreateContractAndAddSignerReq, userId int) (err error) {
+	// 获取用户电子签记录
+	useresignrecord := new(account.Useresignrecord)
+	has, err := global.M2A_DB.Where("USERID = ? AND TEMPLATENO = ?", userId, req.TemplateNo).Get(useresignrecord)
+	if err != nil || !has {
+		global.M2A_LOG.Error("【CreateContractAndAddSigner】 获取用户电子签记录失败", zap.Error(err))
+		return
+	}
+
+	// 判断是否需要创建合同(上传待签署文件)
+	if useresignrecord.CONTRACTNO == "" {
+		// 生成合同编号
+		// #{userid} || '_' || to_char(sysdate, 'yyyyMMddhh24miss') || '_' ||              seq_useresignrecord.currval,
+		contractNo := fmt.Sprintf("%d_%s_%v", userId, time.Now().Format("2006-01-02 15:04:05"), useresignrecord.RECORDID)
+
+		// 调用爱签API-上传待签署文件(https://{host}/contract/createContract)
+		rsp, e := asign.CreateContract(
+			contractNo,
+			useresignrecord.TEMPLATENAME,
+			useresignrecord.TEMPLATENO,
+		)
+		if e != nil {
+			err = e
+			return
+		}
+		if rsp.Code != 100000 {
+			err = errors.New(strconv.Itoa(rsp.Code))
+			global.M2A_LOG.Error("【CreateContractAndAddSigner】 上传待签署文件接口调用失败", zap.Error(err))
+			return
+		}
+		// 将返回的合同编号写入数据库
+		useresignrecord.CONTRACTNO = contractNo
+		sql := fmt.Sprintf(`
+			UPDATE useresignrecord 
+			SET SIGNURL = '%v' 
+				UPDATETIME = SYSDATE
+			WHERE RECORDID = %v
+		`, useresignrecord.CONTRACTNO, useresignrecord.RECORDID)
+		if _, err = global.M2A_DB.Exec(sql); err != nil {
+			global.M2A_LOG.Error("【CreateContractAndAddSigner】 写入合同编号失败", zap.Error(err))
+			return
+		}
+	}
+
+	// 判断是否需要添加签署方(获取合同签约地址)
+	if useresignrecord.SIGNURL == "" {
+		// 调用爱签API-添加签署方(https://{host}/contract/addSigner)
+		rsp, e := asign.AddSigner(
+			useresignrecord.CONTRACTNO,
+			strconv.Itoa(userId),
+		)
+		if e != nil {
+			err = e
+			return
+		}
+		if rsp.Code != 100000 {
+			err = errors.New(strconv.Itoa(rsp.Code))
+			global.M2A_LOG.Error("【CreateContractAndAddSigner】 添加签署方接口调用失败", zap.Error(err))
+			return
+		}
+		if len(rsp.Data.SignUser) > 0 {
+			useresignrecord.SIGNURL = rsp.Data.SignUser[0].SignUrl
+		}
+		// 将返回的合同编号写入数据库
+		sql := fmt.Sprintf(`
+			UPDATE useresignrecord 
+			SET SIGNURL = '%v' 
+				UPDATETIME = SYSDATE
+			WHERE RECORDID = %v
+		`, useresignrecord.SIGNURL, useresignrecord.RECORDID)
+		if _, err = global.M2A_DB.Exec(sql); err != nil {
+			global.M2A_LOG.Error("【CreateContractAndAddSigner】 写入合同签署链接失败", zap.Error(err))
+			return
+		}
+	}
+
+	return
+}