浏览代码

增加爱签个人意愿核身认证业务逻辑

zhou.xiaoning 2 年之前
父节点
当前提交
f03ca784c9

+ 69 - 2
api/v1/account/certification.go

@@ -8,12 +8,13 @@ import (
 	signService "mtp20access/service/sign"
 	"mtp20access/utils"
 	"net/http"
+	"net/url"
 
 	"github.com/gin-gonic/gin"
 	"go.uber.org/zap"
 )
 
-// AddUser 查询用户电子签记录表
+// QueryUserESignRecord 查询用户电子签记录表
 // @Summary  查询用户电子签记录表
 // @Security ApiKeyAuth
 // @accept   application/json
@@ -125,10 +126,12 @@ func SignCompleted(c *gin.Context) {
 	}
 }
 
-// POST
+// 爱签合同签署完成后异步通知 POST
 func HandleASignCompleted(c *gin.Context) {
 	g := utils.GinUtils{C: c}
 
+	// global.M2A_LOG.Info("[HandleASignCompleted] 接收爱签爱签合同签署完成后异步通知", zap.Any("url", g.C.Request.URL))
+
 	action, ok := g.C.GetPostForm("action")
 	if !ok {
 		err := errors.New("[HandleASignCompleted] 获取action失败")
@@ -159,5 +162,69 @@ func HandleASignCompleted(c *gin.Context) {
 		err := errors.New("[HandleASignCompleted] 暂不支持的action")
 		response.FailWithMessage(err.Error(), c)
 	}
+}
+
+// WillFace 个人意愿核身认证
+// @Summary  个人意愿核身认证
+// @Security ApiKeyAuth
+// @accept   application/json
+// @Produce  application/json
+// @Param    data body     request.WillFaceReq                       true "入参"
+// @Success  200  {object} response.Response{data=string,msg=string} "出参"
+// @Router   /Account/WillFace [post]
+// @Tags     账户服务
+func WillFace(c *gin.Context) {
+	g := utils.GinUtils{C: c}
+	r := request.WillFaceReq{}
+	g.BindJsonReq(&r)
+	if g.Err != nil {
+		return
+	}
+
+	// 获取请求账号信息
+	claims := g.GetClaims()
+	if g.Err != nil {
+		return
+	}
 
+	if rsp, err := signService.WillFace(r, claims.UserID); err == nil {
+		response.OkWithDetailed(rsp, "操作成功", g.C)
+	} else {
+		global.M2A_LOG.Error(err.Error(), zap.Error(err))
+		response.FailWithMessage(err.Error(), c)
+	}
+}
+
+// 爱签个人意愿核身认证接口异步通知 GET
+func HandleWillFace(c *gin.Context) {
+	g := utils.GinUtils{C: c}
+
+	global.M2A_LOG.Info("[HandleWillFace] 接收爱签个人意愿核身认证接口异步通知", zap.Any("url", g.C.Request.URL))
+
+	sign := g.C.Query("sign")
+	result := g.C.Query("result")
+	msg := g.C.Query("msg")
+	recordId := g.C.Query("recordId")
+	if sign == "" || result == "" || msg == "" || recordId == "" {
+		g.C.String(http.StatusOK, "提交信息失败,请关闭此页面稍后重试")
+		return
+	}
+
+	if result != "1" {
+		// 认证失败
+		t, _ := url.QueryUnescape(msg)
+		if t == "" {
+			t = "提交信息失败,请关闭此页面稍后重试."
+		}
+		g.C.String(http.StatusOK, t)
+		return
+	}
+
+	if err := signService.HandleWillFace(sign, result, msg, recordId); err == nil {
+		g.C.String(http.StatusOK, "提交信息成功,请关闭此页面")
+	} else {
+		global.M2A_LOG.Error(err.Error(), zap.Error(err))
+		// response.FailWithMessage(err.Error(), c)
+		g.C.String(http.StatusOK, "提交信息失败,请关闭此页面稍后重试")
+	}
 }

+ 2 - 0
config.j2

@@ -58,6 +58,8 @@ asign:
   privateKey: '{{asign_privateKey}}'
   notifyUrl: '{{asign_notifyUrl}}'
   openApiUrl: '{{asign_openApiUrl}}'
+  willFaceRedirectUrl: '{{asign_willFaceRedirectUrl}}' # 人脸认证结果回调通知URL,可拼接参数
+  py: {{asign_py}} # python 执行命令
 
 # 跨域配置
 # 需要配合 server/initialize/router.go#L32 使用

+ 3 - 1
config.yaml

@@ -56,8 +56,10 @@ asign:
   url: 'https://prev.asign.cn/' # 测试环境 - https://prev.asign.cn/  正式环境 - https://oapi.asign.cn/
   appId: '290912417' # 测试-290912417 正式-944849860
   privateKey: 'MIIBUwIBADANBgkqhkiG9w0BAQEFAASCAT0wggE5AgEAAkEAkMD+72J6iAF0ZNV+3t628lsRHfJ80nKZWK5/C7Pg+AZmOIzJlwHsKhRzCvxoxqYHQprhiFzW9l73v9vD9l1JYwIDAQABAkBVijccr01JYdKuY5t9iI8D2NzcnZc1pZMI3NUmzT18Uyg7b9CUvGHlLeg/gdT4QtVd7wIzHYCY4letEcEMh54BAiEAwzNWusj5XiLmty7PI0Hbakx4HtcND1+P0UHLEWqWOuECIQC91zQuL7nStgGzT3HvaeBB5Ouapa39fHRm2nCjHaxwwwIgRR2XdvmUOj23XWMomr5F14SN/7V7fVcD0D8wjNElsmECIDYavV5kb7tj7/wgqkInlKhzC8rZaUsTS0F9BBkY/eptAiAQJ8Saz8YlMIESdHMxANGSog01fECbcZqLFMuNf8SorA=='
-  notifyUrl: 'http://218.17.158.45:15105/api/Account/HandleASignCompleted'
+  notifyUrl: 'http://218.17.158.45:15105/api/Account/HandleASignCompleted' # 合同签署回调通知URL
   openApiUrl: 'http://192.168.31.202:5015/mtp2-onlineopen'
+  willFaceRedirectUrl: 'http://218.17.158.45:15105/api/Account/HandleWillFace' # 人脸认证结果回调通知URL,可拼接参数
+  py: 'py' # python 执行命令
 
 # 跨域配置
 # 需要配合 server/initialize/router.go#L32 使用

+ 7 - 5
config/asign.go

@@ -1,9 +1,11 @@
 package config
 
 type Asign struct {
-	URL        string `mapstructure:"url" json:"url" yaml:"url"`                      // 接口地址
-	AppId      string `mapstructure:"appId" json:"appId" yaml:"appId"`                // AppId
-	PrivateKey string `mapstructure:"privateKey" json:"privateKey" yaml:"privateKey"` // 应用私钥
-	NotifyUrl  string `mapstructure:"notifyUrl" json:"notifyUrl" yaml:"notifyUrl"`    // 合同签署完成异步通知
-	OpenApiUrl string `mapstructure:"openApiUrl" json:"openApiUrl" yaml:"openApiUrl"` // JAVA开户地址
+	URL                 string `mapstructure:"url" json:"url" yaml:"url"`                                                 // 接口地址
+	AppId               string `mapstructure:"appId" json:"appId" yaml:"appId"`                                           // AppId
+	PrivateKey          string `mapstructure:"privateKey" json:"privateKey" yaml:"privateKey"`                            // 应用私钥
+	NotifyUrl           string `mapstructure:"notifyUrl" json:"notifyUrl" yaml:"notifyUrl"`                               // 合同签署完成异步通知
+	OpenApiUrl          string `mapstructure:"openApiUrl" json:"openApiUrl" yaml:"openApiUrl"`                            // JAVA开户地址
+	WillFaceRedirectUrl string `mapstructure:"willFaceRedirectUrl" json:"willFaceRedirectUrl" yaml:"willFaceRedirectUrl"` // 人脸认证结果回调通知URL,可拼接参数
+	Py                  string `mapstructure:"py" json:"py" yaml:"py"`                                                    // python 执行命令
 }

+ 70 - 0
docs/docs.go

@@ -329,6 +329,59 @@ const docTemplate = `{
                 }
             }
         },
+        "/Account/WillFace": {
+            "post": {
+                "security": [
+                    {
+                        "ApiKeyAuth": []
+                    }
+                ],
+                "consumes": [
+                    "application/json"
+                ],
+                "produces": [
+                    "application/json"
+                ],
+                "tags": [
+                    "账户服务"
+                ],
+                "summary": "个人意愿核身认证",
+                "parameters": [
+                    {
+                        "description": "入参",
+                        "name": "data",
+                        "in": "body",
+                        "required": true,
+                        "schema": {
+                            "$ref": "#/definitions/request.WillFaceReq"
+                        }
+                    }
+                ],
+                "responses": {
+                    "200": {
+                        "description": "出参",
+                        "schema": {
+                            "allOf": [
+                                {
+                                    "$ref": "#/definitions/response.Response"
+                                },
+                                {
+                                    "type": "object",
+                                    "properties": {
+                                        "data": {
+                                            "type": "string"
+                                        },
+                                        "msg": {
+                                            "type": "string"
+                                        }
+                                    }
+                                }
+                            ]
+                        }
+                    }
+                }
+            }
+        },
         "/MQ/SendMsgToMQ": {
             "post": {
                 "security": [
@@ -612,6 +665,23 @@ const docTemplate = `{
                 }
             }
         },
+        "request.WillFaceReq": {
+            "type": "object",
+            "required": [
+                "idCardNo",
+                "realName"
+            ],
+            "properties": {
+                "idCardNo": {
+                    "description": "身份证号",
+                    "type": "string"
+                },
+                "realName": {
+                    "description": "真实姓名",
+                    "type": "string"
+                }
+            }
+        },
         "response.LoginRsp": {
             "type": "object",
             "properties": {

+ 70 - 0
docs/swagger.json

@@ -320,6 +320,59 @@
                 }
             }
         },
+        "/Account/WillFace": {
+            "post": {
+                "security": [
+                    {
+                        "ApiKeyAuth": []
+                    }
+                ],
+                "consumes": [
+                    "application/json"
+                ],
+                "produces": [
+                    "application/json"
+                ],
+                "tags": [
+                    "账户服务"
+                ],
+                "summary": "个人意愿核身认证",
+                "parameters": [
+                    {
+                        "description": "入参",
+                        "name": "data",
+                        "in": "body",
+                        "required": true,
+                        "schema": {
+                            "$ref": "#/definitions/request.WillFaceReq"
+                        }
+                    }
+                ],
+                "responses": {
+                    "200": {
+                        "description": "出参",
+                        "schema": {
+                            "allOf": [
+                                {
+                                    "$ref": "#/definitions/response.Response"
+                                },
+                                {
+                                    "type": "object",
+                                    "properties": {
+                                        "data": {
+                                            "type": "string"
+                                        },
+                                        "msg": {
+                                            "type": "string"
+                                        }
+                                    }
+                                }
+                            ]
+                        }
+                    }
+                }
+            }
+        },
         "/MQ/SendMsgToMQ": {
             "post": {
                 "security": [
@@ -603,6 +656,23 @@
                 }
             }
         },
+        "request.WillFaceReq": {
+            "type": "object",
+            "required": [
+                "idCardNo",
+                "realName"
+            ],
+            "properties": {
+                "idCardNo": {
+                    "description": "身份证号",
+                    "type": "string"
+                },
+                "realName": {
+                    "description": "真实姓名",
+                    "type": "string"
+                }
+            }
+        },
         "response.LoginRsp": {
             "type": "object",
             "properties": {

+ 42 - 0
docs/swagger.yaml

@@ -133,6 +133,18 @@ definitions:
     - data
     - funCode
     type: object
+  request.WillFaceReq:
+    properties:
+      idCardNo:
+        description: 身份证号
+        type: string
+      realName:
+        description: 真实姓名
+        type: string
+    required:
+    - idCardNo
+    - realName
+    type: object
   response.LoginRsp:
     properties:
       expiresAt:
@@ -348,6 +360,36 @@ paths:
       summary: Token校验
       tags:
       - 账户服务
+  /Account/WillFace:
+    post:
+      consumes:
+      - application/json
+      parameters:
+      - description: 入参
+        in: body
+        name: data
+        required: true
+        schema:
+          $ref: '#/definitions/request.WillFaceReq'
+      produces:
+      - application/json
+      responses:
+        "200":
+          description: 出参
+          schema:
+            allOf:
+            - $ref: '#/definitions/response.Response'
+            - properties:
+                data:
+                  type: string
+                msg:
+                  type: string
+              type: object
+      security:
+      - ApiKeyAuth: []
+      summary: 个人意愿核身认证
+      tags:
+      - 账户服务
   /MQ/SendMsgToMQ:
     post:
       consumes:

+ 26 - 7
model/account/request/certification.go

@@ -15,11 +15,30 @@ type CreateContractAndAddSignerReq struct {
 	TemplateNo string `json:"templateNo" binding:"required"` // 合同模板编号
 }
 
-type HandleASignCompletedReq struct {
-	Action       string `json:"signCompleted"`
-	ContractNo   string `json:"contractNo"`
-	Status       string `json:"status"`
-	SignTime     string `json:"signTime"`
-	Timestamp    string `json:"timestamp"`
-	ValidityTime string `json:"validityTime"`
+// type HandleASignCompletedReq struct {
+// 	Action       string `json:"signCompleted"`
+// 	ContractNo   string `json:"contractNo"`
+// 	Status       string `json:"status"`
+// 	SignTime     string `json:"signTime"`
+// 	Timestamp    string `json:"timestamp"`
+// 	ValidityTime string `json:"validityTime"`
+// }
+
+// WillFaceReq 个人意愿核身认证请求参数
+type WillFaceReq struct {
+	RealName string `json:"realName" binding:"required"` // 真实姓名
+	IdCardNo string `json:"idCardNo" binding:"required"` // 身份证号
+}
+
+// 给爱签个人意愿核身认证接口异步通知使用
+type HandleWillFaceReq struct {
+	Sign     string `from:"sign" binding:"required"`     // 签名字符串
+	Result   string `from:"result" binding:"required"`   // 人脸认证结果 1:认证成功 2:认证失败
+	Msg      string `from:"msg" binding:"required"`      // 失败原因
+	RecordId int    `from:"recordId" binding:"required"` // 电子签记录ID
+}
+
+// 获取电子签记录信息
+type GetUserESignRecordReq struct {
+	Recordid int `from:"recordId" binding:"required"` // 记录ID
 }

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

@@ -4,3 +4,8 @@ package response
 type CreateContractAndAddSignerRsp struct {
 	SignUrl string `json:"signUrl"` // 合同签署链接
 }
+
+// WillFaceRsp 个人意愿核身认证请求响应参数
+type WillFaceRsp struct {
+	FaceUrl string `json:"faceUrl"` // 意愿核身链接
+}

+ 28 - 0
py/Api.py

@@ -66,6 +66,7 @@ def addSigner(inputData):
     account = inputData["account"]
     signType = inputData["signType"]
     signStrategyList = inputData["signStrategyList"]
+    validateType = inputData["validateType"]
     
     appId = inputData["appId"]
     appKey = inputData["appKey"]
@@ -76,6 +77,7 @@ def addSigner(inputData):
         "contractNo": contractNo, 
         "account": account, 
         "signType": signType,
+        "validateType": validateType,
         "signStrategyList": signStrategyList
     }]
     #请求地址
@@ -119,4 +121,30 @@ def contractStatus(inputData):
     #请求地址
     Result = HttpUtils.HttpUtils.doPOST(apiUrl, reqBodyData, appId, appKey)
 
+    return Result
+
+# 个人意愿核身认证 https://{host}/auth/person/willFace
+def willFace(inputData):
+    # 从输入数据中获取调用接口
+    realName = inputData["realName"]
+    idCardNo = inputData["idCardNo"]
+    question = inputData["question"]
+    answer = inputData["answer"]
+    redirectUrl = inputData["redirectUrl"]
+    
+    appId = inputData["appId"]
+    appKey = inputData["appKey"]
+    apiUrl = inputData["apiUrl"]
+
+    # 组装参数
+    reqBodyData = {
+        "realName": realName,
+        "idCardNo": idCardNo,
+        "question": question,
+        "answer": answer,
+        "redirectUrl": redirectUrl,
+    }
+    #请求地址
+    Result = HttpUtils.HttpUtils.doPOST(apiUrl, reqBodyData, appId, appKey)
+
     return Result

+ 2 - 0
py/Enter.py

@@ -23,6 +23,8 @@ def main():
         result = Api.downloadContract(inputData)
     elif (api == "contractStatus"):
         result = Api.contractStatus(inputData)
+    elif (api == "willFace"):
+        result = Api.willFace(inputData)
     else:
         result = ""
 

+ 2 - 0
router/account.go

@@ -11,6 +11,7 @@ func InitAccountPublicRouter(r *gin.RouterGroup) {
 	{
 		accountR.POST("Login", account.Login)
 		accountR.POST("HandleASignCompleted", account.HandleASignCompleted)
+		accountR.GET("HandleWillFace", account.HandleWillFace)
 	}
 }
 
@@ -23,5 +24,6 @@ func InitAccountPrivateRouter(r *gin.RouterGroup) {
 		accountR.POST("AddUser", account.AddUser)
 		accountR.POST("CreateContractAndAddSigner", account.CreateContractAndAddSigner)
 		accountR.POST("SignCompleted", account.SignCompleted)
+		accountR.POST("WillFace", account.WillFace)
 	}
 }

+ 79 - 7
service/asign/asign.go

@@ -96,7 +96,7 @@ import (
 // 	// 要执行的Python脚本命令
 // 	pythonScriptPath := "./py/sign.py"
 // 	// 创建一个命令对象
-// 	cmd := exec.Command("python", pythonScriptPath)
+// 	cmd := exec.Command(global.M2A_CONFIG.Asign.Py, pythonScriptPath)
 // 	// 设置标准输入为JSON字符串
 // 	cmd.Stdin = strings.NewReader(string(reqJSON))
 // 	// 获取标准输出
@@ -112,7 +112,7 @@ import (
 // }
 
 type AsignData interface {
-	AddPersonalUserData | CreateContractData | AddSignerData | DownloadContractData | ContractStatusData
+	AddPersonalUserData | CreateContractData | AddSignerData | DownloadContractData | ContractStatusData | WillFaceData
 }
 
 // AsignRsp 爱签响应参数
@@ -246,7 +246,7 @@ func AddPersonalUserBy(account, name, idCard, mobile string, idCardType *int) (r
 	// 要执行的Python脚本命令
 	pythonScriptPath := "./py/Enter.py"
 	// 创建一个命令对象
-	cmd := exec.Command("python", pythonScriptPath)
+	cmd := exec.Command(global.M2A_CONFIG.Asign.Py, pythonScriptPath)
 	// 设置标准输入为JSON字符串
 	cmd.Stdin = strings.NewReader(string(reqJSON))
 	// 创建一个字节缓冲区来捕获命令的输出
@@ -317,7 +317,7 @@ func CreateContract(contractNo, contractName, templateNo string) (rspData AsignR
 	// 要执行的Python脚本命令
 	pythonScriptPath := "./py/Enter.py"
 	// 创建一个命令对象
-	cmd := exec.Command("python", pythonScriptPath)
+	cmd := exec.Command(global.M2A_CONFIG.Asign.Py, pythonScriptPath)
 	// 设置标准输入为JSON字符串
 	cmd.Stdin = strings.NewReader(string(reqJSON))
 	// 创建一个字节缓冲区来捕获命令的输出
@@ -380,6 +380,16 @@ func AddSigner(contractNo, account string) (rspData AsignRsp[AddSignerData], err
 	reqData["contractNo"] = contractNo
 	reqData["account"] = account
 	reqData["signType"] = 3
+	// 签署方式指定:(从以下分类中指定一种)
+	// 1:短信验证码签约(支持企业和个人)
+	// 2:签约密码签约(支持企业和个人)
+	// 3:人脸识别签约(支持企业和个人)
+	// 4:手写签名(不推荐,仅限个人)
+	// 6:手写识别签名+短信签约(仅限个人)
+	// 7:手写签名+短信签约(仅限个人)
+	// 8:手写签名+人脸识别签约(仅限个人)
+	// 9:手写识别签名+人脸识别签约(仅限个人)
+	reqData["validateType"] = 4
 	reqData["signStrategyList"] = []map[string]interface{}{
 		{"attachNo": 1, "locationMode": 4, "signKey": "key_sign_name"},
 		{"attachNo": 1, "locationMode": 4, "signKey": "key_sign_date"},
@@ -399,7 +409,7 @@ func AddSigner(contractNo, account string) (rspData AsignRsp[AddSignerData], err
 	// 要执行的Python脚本命令
 	pythonScriptPath := "./py/Enter.py"
 	// 创建一个命令对象
-	cmd := exec.Command("python", pythonScriptPath)
+	cmd := exec.Command(global.M2A_CONFIG.Asign.Py, pythonScriptPath)
 	// 设置标准输入为JSON字符串
 	cmd.Stdin = strings.NewReader(string(reqJSON))
 	// 创建一个字节缓冲区来捕获命令的输出
@@ -462,7 +472,7 @@ func DownloadContract(contractNo string) (rspData AsignRsp[DownloadContractData]
 	// 要执行的Python脚本命令
 	pythonScriptPath := "./py/Enter.py"
 	// 创建一个命令对象
-	cmd := exec.Command("python", pythonScriptPath)
+	cmd := exec.Command(global.M2A_CONFIG.Asign.Py, pythonScriptPath)
 	// 设置标准输入为JSON字符串
 	cmd.Stdin = strings.NewReader(string(reqJSON))
 	// 创建一个字节缓冲区来捕获命令的输出
@@ -521,7 +531,7 @@ func ContractStatus(contractNo string) (rspData AsignRsp[ContractStatusData], er
 	// 要执行的Python脚本命令
 	pythonScriptPath := "./py/Enter.py"
 	// 创建一个命令对象
-	cmd := exec.Command("python", pythonScriptPath)
+	cmd := exec.Command(global.M2A_CONFIG.Asign.Py, pythonScriptPath)
 	// 设置标准输入为JSON字符串
 	cmd.Stdin = strings.NewReader(string(reqJSON))
 	// 创建一个字节缓冲区来捕获命令的输出
@@ -546,3 +556,65 @@ func ContractStatus(contractNo string) (rspData AsignRsp[ContractStatusData], er
 
 	return
 }
+
+// 个人意愿核身认证响应数据
+type WillFaceData struct {
+	Result   int    `json:"result"`   // 认证结果:0.暂无结果/认证中
+	SerialNo string `json:"serialNo"` // 认证流水号
+	Type     string `json:"type"`     // 认证类型
+	FaceUrl  string `json:"faceUrl"`  // 意愿核身链接
+}
+
+/*
+WillFace 个人意愿核身认证
+*/
+func WillFace(realName, idCardNo, question, answer, recordId string) (rspData AsignRsp[WillFaceData], err error) {
+	apiUrl := global.M2A_CONFIG.Asign.URL + "auth/person/willFace"
+
+	// 构建请求数据结构
+	reqData := make(map[string]interface{})
+	reqData["realName"] = realName
+	reqData["idCardNo"] = idCardNo
+	reqData["question"] = question
+	reqData["answer"] = answer
+	reqData["redirectUrl"] = global.M2A_CONFIG.Asign.WillFaceRedirectUrl + "?recordId=" + recordId
+
+	reqData["api"] = "willFace"
+	reqData["appId"] = global.M2A_CONFIG.Asign.AppId
+	reqData["appKey"] = global.M2A_CONFIG.Asign.PrivateKey
+	reqData["apiUrl"] = apiUrl
+
+	// 将请求数据转换为JSON字符串
+	reqJSON, err := json.Marshal(reqData)
+	if err != nil {
+		global.M2A_LOG.Error("[WillFace] 构建请求参数失败", zap.Error(err))
+		return
+	}
+	// 要执行的Python脚本命令
+	pythonScriptPath := "./py/Enter.py"
+	// 创建一个命令对象
+	cmd := exec.Command(global.M2A_CONFIG.Asign.Py, pythonScriptPath)
+	// 设置标准输入为JSON字符串
+	cmd.Stdin = strings.NewReader(string(reqJSON))
+	// 创建一个字节缓冲区来捕获命令的输出
+	var stdoutBuf, stderrBuf bytes.Buffer
+	cmd.Stdout = &stdoutBuf
+	cmd.Stderr = &stderrBuf
+	// 执行命令
+	err = cmd.Run()
+	// 输出标准输出和标准错误
+	fmt.Println("ContractStatus 标准输出:", stdoutBuf.String())
+	fmt.Println("ContractStatus 标准错误:", stderrBuf.String())
+	if err != nil {
+		global.M2A_LOG.Error("[ContractStatus] 请求失败", zap.Error(err))
+		return
+	}
+	// 结果
+	rspBody := stdoutBuf.String()
+	if err = json.Unmarshal([]byte(rspBody), &rspData); err != nil {
+		global.M2A_LOG.Error("[WillFace] 反序列化body失败", zap.Error(err))
+		return
+	}
+
+	return
+}

+ 155 - 15
service/sign/sign.go

@@ -25,7 +25,26 @@ import (
 // QueryUserESignRecord 查询用户电子签记录表
 func QueryUserESignRecord(userId int) (rsp []account.Useresignrecord, err error) {
 	rsp = make([]account.Useresignrecord, 0)
-	err = global.M2A_DB.Where("USERID = ?", userId).Find(&rsp)
+	if err = global.M2A_DB.Where("USERID = ?", userId).Find(&rsp); err != nil {
+		return
+	}
+	for i := range rsp {
+		data := &rsp[i]
+		if data.TEMPLATETYPE == 2 && data.RECORDSTATUS == 2 {
+			// 下载合同并修改合同状态
+			e := modifyContractStatus(data.CONTRACTNO, strconv.Itoa(int(data.RECORDSTATUS)))
+			if e != nil {
+				// 从新获取记录
+				data = new(account.Useresignrecord)
+				has, e := global.M2A_DB.Where("RECORDID = ?", data.RECORDID, userId).Get(data)
+				if e != nil || !has {
+					err = e
+					global.M2A_LOG.Error("【QueryUserESignRecord】 获取用户电子签记录失败", zap.Error(err))
+					return
+				}
+			}
+		}
+	}
 
 	return
 }
@@ -168,7 +187,7 @@ func SignCompleted(userId int) (err error) {
 	datas := make([]account.Useresignrecord, 0)
 	err = global.M2A_DB.Where("USERID = ?", userId).Find(&datas)
 	if err != nil {
-		global.M2A_LOG.Error("SignCompleted 获取用户电子签记录失败", zap.Error(err))
+		global.M2A_LOG.Error("[SignCompleted] 获取用户电子签记录失败", zap.Error(err))
 		return
 	}
 
@@ -177,21 +196,34 @@ func SignCompleted(userId int) (err error) {
 		if item.TEMPLATETYPE == 1 {
 			record = &datas[i]
 		}
-		// 如果是签署中状态,则查询一下合同状态
-		if item.RECORDSTATUS == 2 {
-			rspCTStatus, e := asign.ContractStatus(item.CONTRACTNO)
-			if e != nil {
-				err = e
-				global.M2A_LOG.Error("[SignCompleted] 查询合同状态失败", zap.Error(err))
+		switch item.TEMPLATETYPE {
+		case 1: // 实名认证
+			if item.RECORDSTATUS != 3 {
+				err = errors.New("未完成用户添加")
+				return
+			}
+		case 2: // 合同签署
+			// 如果是签署中状态,则查询一下合同状态
+			if item.RECORDSTATUS == 2 {
+				rspCTStatus, e := asign.ContractStatus(item.CONTRACTNO)
+				if e != nil {
+					err = e
+					global.M2A_LOG.Error("[SignCompleted] 查询合同状态失败", zap.Error(err))
+					return
+				}
+				// 下载合同并修改合同状态
+				err = modifyContractStatus(rspCTStatus.Data.ContractNo, strconv.Itoa(rspCTStatus.Data.Status))
+				return
+			} else if (item.RECORDSTATUS != 3 && item.RECORDSTATUS != 2) && item.TEMPLATETYPE == 2 {
+				err = errors.New("未完成所有合同签署")
+				global.M2A_LOG.Error(err.Error())
+				return
+			}
+		case 3: // 个人意愿
+			if item.RECORDSTATUS != 3 {
+				err = errors.New("未完成个人意愿核身认证")
 				return
 			}
-			// 下载合同并修改合同状态
-			err = modifyContractStatus(rspCTStatus.Data.ContractNo, strconv.Itoa(rspCTStatus.Data.Status))
-			return
-		} else if item.RECORDSTATUS != 3 && item.RECORDSTATUS != 2 {
-			err = errors.New("未完成所有合同签署")
-			global.M2A_LOG.Error(err.Error())
-			return
 		}
 	}
 
@@ -406,3 +438,111 @@ func modifyContractStatus(contractNo, status string) (err error) {
 
 	return
 }
+
+// WillFace 个人意愿核身认证
+func WillFace(req request.WillFaceReq, userId int) (wrsp response.WillFaceRsp, err error) {
+	// 获取用户电子签记录
+	useresignrecord := new(account.Useresignrecord)
+	has, err := global.M2A_DB.Where("USERID = ? AND TEMPLATETYPE = 3", userId).Get(useresignrecord)
+	if err != nil || !has {
+		global.M2A_LOG.Error("【WillFace】 获取用户电子签记录失败", zap.Error(err))
+		return
+	}
+	if useresignrecord.RECORDSTATUS == 3 {
+		err = errors.New("不可重复进行个人意愿核身认证")
+		return
+	}
+
+	// 调用爱签API-个人意愿核身认证(https://{host}/auth/person/willFace)
+	rsp, err := asign.WillFace(
+		req.RealName,
+		req.IdCardNo,
+		useresignrecord.TEMPLATENO,
+		useresignrecord.TEMPLATENAME,
+		strconv.Itoa(int(useresignrecord.RECORDID)))
+	if err != nil {
+		return
+	}
+	if rsp.Code != 100000 {
+		err = errors.New(strconv.Itoa(rsp.Code))
+		global.M2A_LOG.Error("【WillFace】 接口调用失败", zap.Error(err))
+		return
+	}
+	wrsp.FaceUrl = rsp.Data.FaceUrl
+
+	// 更新用户电子签记录
+	sql := fmt.Sprintf(`
+		UPDATE useresignrecord
+		SET RECORDSTATUS = 2,
+			UPDATETIME = SYSDATE, 
+			SIGNURL = '%v'
+		WHERE RECORDID = %v 
+	`, rsp.Data.FaceUrl, useresignrecord.RECORDID)
+	if _, err = global.M2A_DB.Exec(sql); err != nil {
+		global.M2A_LOG.Error("【WillFace】 更新用户电子签记录失败", zap.Error(err))
+		return
+	}
+
+	return
+}
+
+// 个人意愿核身认证爱签回调通知处理
+func HandleWillFace(sign, result, msg, recordId string) (err error) {
+	// 获取用户电子签记录
+	useresignrecord := new(account.Useresignrecord)
+	has, e := global.M2A_DB.Where("RECORDID = ?", recordId).Get(useresignrecord)
+	if e != nil || !has {
+		err = e
+		global.M2A_LOG.Error("【HandleWillFace】 获取用户电子签记录失败", zap.Error(err))
+		return
+	}
+
+	// 人脸认证结果
+	// 1:认证成功
+	// 2:认证失败
+	if result == "1" {
+		// FIXME: - 这里应该校验签名(姓名和身份证号码)
+
+		// 更新数据库记录
+		sql := fmt.Sprintf(`
+			UPDATE useresignrecord
+			SET RECORDSTATUS = 3,
+				UPDATETIME = SYSDATE
+			WHERE RECORDID = %v  
+		`, recordId)
+		if _, err = global.M2A_DB.Exec(sql); err != nil {
+			global.M2A_LOG.Error("【HandleWillFace】 更新用户电子签记录失败", zap.Error(err))
+			return
+		}
+	}
+
+	return
+}
+
+// GetUserESignRecord 获取电子签记录,如果是合同类型并状态为2,则会去爱签同步合同状态和下载合同
+func GetUserESignRecord(req request.GetUserESignRecordReq, userId int) (data *account.Useresignrecord, err error) {
+	// 获取用户电子签记录
+	data = new(account.Useresignrecord)
+	has, e := global.M2A_DB.Where("RECORDID = ? AND USERID = ?", req.Recordid, userId).Get(data)
+	if e != nil || !has {
+		err = e
+		global.M2A_LOG.Error("【HandleWillFace】 获取用户电子签记录失败", zap.Error(err))
+		return
+	}
+	if data.TEMPLATETYPE == 2 && data.RECORDSTATUS == 2 {
+		// 下载合同并修改合同状态
+		e := modifyContractStatus(data.CONTRACTNO, strconv.Itoa(int(data.RECORDSTATUS)))
+		if e != nil {
+			// 从新获取记录
+			data = new(account.Useresignrecord)
+			has, e := global.M2A_DB.Where("RECORDID = ? AND USERID = ?", req.Recordid, userId).Get(data)
+			if e != nil || !has {
+				err = e
+				global.M2A_LOG.Error("【HandleWillFace】 获取用户电子签记录失败", zap.Error(err))
+				return
+			}
+		}
+	}
+
+	return
+}