migu_server/app/admin/models/gdyd_model.go

445 lines
16 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package models
import (
"bytes"
"crypto"
"crypto/rand"
"crypto/rsa"
"crypto/sha256"
"crypto/tls"
"crypto/x509"
"encoding/base64"
"encoding/json"
"encoding/pem"
"errors"
"fmt"
"github.com/google/uuid"
"io"
"net/http"
"sort"
"strings"
"time"
)
const (
AppId = "114320"
PrivateKeyPEM = `-----BEGIN PRIVATE KEY-----
MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQCoqhTbIPDpg8OzUDsJ8Po+UthsUWlugC+Ti7UCLHxp1Eask8UBOloOGWO9IUHS03y80SEWM+oy94Vb/g2QIe7Jk5QdfJzxa0cnptIzVxIRpyCFNlaSheNwCD4wjoZvXKXEoqb1J2C5U4ks0Jd9dYuUa7aLtwHBYLb2QL7hN9v7GyP4YOPU65pnHW6nrIDk6s+FZWkjdw6nT+dwCO3dl/l5MNbp0oaH+73xgxElOgABBStcelDEPc0YOwSPXlfwl68Ozd4cxwU5SGRH/zj1h7YBP+mwEOFUE62EO2mgc3JIF5YpaLsMHF960/VkM6j4qBM0Hds/WmxtJ1COXhVzZTWbAgMBAAECggEAYf8gSwlisGrMhBzzkJ0g6KE9+gF7Xqa//dxIeVDboKmjvpXE+yeqN1LtLnBqTFJwwUxJCxty0dYa+A4uVzZABYLnphJHJcYM+67jpszKRNN0A7JErrF4Khm/+Hp3BbEw54URuJL+ke9FXnJ78nsfdEb5M0hLjs3gwEdyIG1SNRzoI0VpcWxP430esn1vXTxfyWedLPhIhfdgVtzwLnaOrpRy5Qd4JiZacoSxCHnV3zsT6pp5QD1RwClg1UJbiH0B2TiUbBiyl5GJgMvVtOjj2yX80c8JbpiRxYMujIbHcmEjzqvbkCX5/q/WjxPkR5Fk4MlDAlogr68pOF3rL0H0oQKBgQDY5aARizxt6NABaZJtNElUqrjNBxQCaLMt7nMRGOjb5o7LKPRhwRcqUoTYvDIn4qKN0V8lLnvRJjT089WRwBHWqwJNfp14z3HqfJMWqLIFWnp1CaJCCusc6zdjTqu8KaztCyjlxlp0usZXfCw1SgKmSnziFOSRkxt1X0HFn/0WGQKBgQDHEmSoPH7MpqkrCWRvSysbbcdIbpYiBoru8+irdy0EVxzu5s9mGb6IncZ2Y41f+/T0cpPuNlcrJQfQ0tEKo0uDjpPmiKQa3AVlOElOJI9H/KOZQHHM835STSxjsOb5RjMdFBYbutzO5h7EAaQQEFWNfcNsbaSPQlXNttdWH8XX0wKBgQCr8z83KV868zsUI5IGKVGJYd8oC9h9IGwMmeF3SHwy+VFzFoDHjsDCuLDA8lIA9NdR/w6i93sJkHSjTTufVNnPibtFnH9S64KwFxq0+ABJ5jT23DBakzVZs9AxVoknnxKMyjAeGWZU7E2ZxcN2a7o2Aw+GXHHoRuuZ3W7TMcb+4QKBgQCdHTssPHKm+nJRcRw/akgfYckCtaTwPdGxPffIPErfPhGry6asomzqTfuwvGl789MkirmOLH0npBZDDd/GUZLrxb+dFwLN4BCyDnZsohYjbpWAAojOhO6R7i62j4v7+RemP5AjWpui/6QQdmsR8pJTFYsDLJXQKz6lGUVix7jR2QKBgQCztD1gFG2rMVvSiXqMbGfqjAvZjVygzBvpTidRB/ZKPcfXSdhrvEv1pYmrYoBjQ/oBM2HncyI7EsQsIed17n8r9tart4VMGCQfpQw3mneq4TZIjzqkpYP0qLY+mDS9v8l+FotRfVQtyOnff6xsTnWw3S9fTF5oSe5Yc9dnpxCCAw==
-----END PRIVATE KEY-----`
CreateOrderUrl = "https://221.179.11.204/eaop/rest/BSS/commodity/create_productorder_ckcommid/v1.1.1"
ApplySmsCodeUrl = "https://221.179.11.204/eaop/rest/BSS/service/smscodeapply/v1.1.1"
CheckSmsCodeCommitOrderUrl = "https://221.179.11.204/eaop/rest/BSS/commodity/smscodechkodcommitorder/v1.1.1"
QueryOrderUrl = "https://221.179.11.204/eaop/rest/BSS/commodity/odqryorderbaseinfo_orderid/v1.1.1"
)
var GdYdCodeMessageMap = map[string]string{
"0": "成功",
"500": "数据错误",
"600": "业务失败",
"999": "系统内部失败",
"1009999999": "默认异常编码",
"1009999000": "系统内部错误",
"1000010001": "请求IP缺失",
"1000010002": "请求IP在黑名单中",
"1000010003": "请求IP不在白名单中",
"1000010009": "解析能力请求报文出错",
"1000010010": "缺失APPID参数",
"1000010011": "应用不存在",
"1000010012": "应用状态不合法",
"1000010013": "应用关联不到有效商户",
"1000010031": "能力不存在",
"1000010035": "传入参数错误",
"1000010043": "缺少签名参数",
"1000010047": "数字签名验证不通过",
"1000010049": "时间戳和系统时间隔太大",
"1000010060": "无访问能力权限",
"1000020001": "流量控制超流量后停止服务",
"1000020002": "配额控制未找到配额规则",
"1000020003": "配额控制超配额后停止服务",
"1000030001": "能力不支持此协议",
"1000030002": "能力不支持此报文格式",
"1000030004": "能力参数未配置",
"1000030005": "请求参数为空",
"1000030007": "缺少必选参数",
"1000030009": "非法的数据格式,参数值与字段类型定义不符",
"1000030015": "获取服务参数信息失败",
"1000030016": "服务返回报文格式错误",
"1000030017": "参数模糊化错误",
"1000040001": "模拟应答编码缺失",
"1000040002": "环境标识不合法",
"1000050001": "服务流程模板调用出错",
"1000050002": "能力回调出错",
"1000050003": "反向能力调用出错",
"1000050004": "服务调用超时",
"1000050005": "调用服务失败",
"1000050006": "调用服务返回结果不完整",
"1000050007": "调用服务返回结果为空",
"1000050008": "根据服务id获取流程模板出错",
"1000050010": "本地自服务调用失败",
"1000060001": "获取路由关键信息失败",
"1000060002": "服务路由失败",
}
// CommonParams 公共参数
type CommonParams struct {
AppID string `json:"appId"` // 必选,开放平台分配给应用的唯一标识
Timestamp string `json:"timestamp"` // 必选请求时间戳格式yyyyMMddHHmmssSSS需重新生成服务器时间差不得超5分钟
BusiSerial string `json:"busiSerial"` // 必选32位UUID去掉4个减号作为请求流水号
Sign string `json:"sign"` // 必选,数字签名
Nonce string `json:"nonce"` // 必选32位数字+大写字母组成的随机字符串
AuthCode string `json:"authCode,omitempty"` // 可选,用户授权调用的验证码
OperatorID string `json:"operatorid,omitempty"` // 可选,操作员工号
ComFlowCode string `json:"comflowcode,omitempty"` // 可选,通信流程编号
InstanceID string `json:"instanceid,omitempty"` // 可选,流程实例唯一标识
RouteType string `json:"route_type,omitempty"` // 可选路由类型0=地市1=号码
RouteValue string `json:"route_value,omitempty"` // 可选,路由字段值(手机号码或区号)
UnitID string `json:"unitid,omitempty"` // 可选子渠道ID
}
// GenerateSignature 通用签名函数
func GenerateSignature(publicParams map[string]string, bizJson string, privateKeyPEM string) (string, error) {
// 删除 sign 字段(如果存在)
delete(publicParams, "sign")
// 1. 排序参数
keys := make([]string, 0, len(publicParams))
for k := range publicParams {
keys = append(keys, k)
}
sort.Strings(keys)
// 2. 拼接参数
var sb strings.Builder
for i, k := range keys {
if i > 0 {
sb.WriteString("&")
}
sb.WriteString(fmt.Sprintf("%s=%s", k, publicParams[k]))
}
sb.WriteString(bizJson)
signContent := sb.String()
// 3. SHA256withRSA签名
signature, err := rsaSignSHA256([]byte(signContent), privateKeyPEM)
if err != nil {
return "", err
}
return signature, nil
}
// 签名逻辑SHA256withRSA + Base64
func rsaSignSHA256(data []byte, privateKeyPEM string) (string, error) {
block, _ := pem.Decode([]byte(privateKeyPEM))
if block == nil {
return "", fmt.Errorf("invalid private key PEM")
}
priv, err := x509.ParsePKCS8PrivateKey(block.Bytes)
if err != nil {
return "", fmt.Errorf("parse private key error: %v", err)
}
rsaPriv, ok := priv.(*rsa.PrivateKey)
if !ok {
return "", fmt.Errorf("not RSA private key")
}
hash := sha256.Sum256(data)
signature, err := rsa.SignPKCS1v15(rand.Reader, rsaPriv, crypto.SHA256, hash[:])
if err != nil {
return "", fmt.Errorf("sign error: %v", err)
}
return base64.StdEncoding.EncodeToString(signature), nil
}
// CommonResponse 通用返回结构体
type CommonResponse struct {
RespCode string `json:"respcode"`
RespDesc string `json:"respdesc"`
RespType string `json:"resptype"`
Result json.RawMessage `json:"result,omitempty"`
}
// 32位由数字+大写字母组成的随机字符串,每次请求必须重新生成
func generateNonce(n int) string {
const charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
b := make([]byte, n)
for i := range b {
b[i] = charset[int(time.Now().UnixNano()+int64(i))%len(charset)]
}
return string(b)
}
// 请求报文流水号(UUID去掉其中的四个减号)
func generateUUID() string {
return strings.ReplaceAll(uuid.New().String(), "-", "")
}
// DoSecurePostRequest 发送签名后的通用 POST 请求
func DoSecurePostRequest(url, phone string, bizPayload interface{}) (*CommonResponse, error) {
fmt.Println("enter DoSecurePostRequest")
fmt.Println("请求地址(URL):", url)
// Marshal 业务参数
bizJSON, err := json.Marshal(bizPayload)
if err != nil {
return nil, fmt.Errorf("marshal biz payload failed: %w", err)
}
fmt.Println("请求体(业务参数 bizPayload):", string(bizJSON))
// 生成公共参数
publicParams := map[string]string{
"appId": AppId,
"busiSerial": generateUUID(),
"nonce": generateNonce(32),
"route_value": phone,
"route_type": "1",
"timestamp": time.Now().Format("20060102150405") + fmt.Sprintf("%03d", time.Now().Nanosecond()/1e6), // 精确到毫秒
}
fmt.Println("publicParams is:", publicParams)
// 生成签名
signature, err := GenerateSignature(publicParams, string(bizJSON), PrivateKeyPEM)
if err != nil {
return nil, fmt.Errorf("sign failed: %w", err)
}
fmt.Println("signature is:", signature)
// 添加签名
publicParams["sign"] = signature
// 准备请求
req, err := http.NewRequest("POST", url, bytes.NewBuffer(bizJSON))
if err != nil {
return nil, fmt.Errorf("create request failed: %w", err)
}
req.Header.Set("Content-Type", "application/json")
// 设置公共参数为 Header
for k, v := range publicParams {
req.Header.Set(k, v)
}
// 打印请求头
fmt.Println("请求头信息:")
for k, v := range req.Header {
fmt.Printf(" %s: %s\n", k, strings.Join(v, ","))
}
// 忽略 TLS 校验
tr := &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}
client := &http.Client{
Timeout: 10 * time.Second,
Transport: tr,
}
// 发送请求
//client := http.Client{}
resp, err := client.Do(req)
if err != nil {
return nil, fmt.Errorf("http request failed: %w", err)
}
defer resp.Body.Close()
// 读取响应
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("read response failed: %w", err)
}
fmt.Println("响应内容(Response Body):", string(body))
// 解析返回体
var result CommonResponse
if err = json.Unmarshal(body, &result); err != nil {
fmt.Println("unmarshal response failed, err is:", err.Error())
return nil, fmt.Errorf("unmarshal response failed: %w", err)
}
fmt.Println("leave DoSecurePostRequest")
return &result, nil
}
// CreateProductOrderCKCommIDReq 商品订单生成 - 请求结构体JSON
type CreateProductOrderCKCommIDReq struct {
UserInfo struct {
ServerNum string `json:"servernum"` // 手机号码
Area string `json:"area,omitempty"` // 地市
Brand string `json:"brand,omitempty"` // 品牌
} `json:"userinfo"`
MoreCommOrder string `json:"morecommorder"` // 多商品订购0 或 1
ProductInfo struct {
ProductID string `json:"productid"` // 产品编号
ProductGroup string `json:"productgroup"` // 产品组
ProductType string `json:"producttype"` // 产品类别
ProductName string `json:"productname,omitempty"` // 产品名称
OrderType string `json:"ordertype"` // 订购类型1=办理 2=取消 3=修改
} `json:"productinfo"`
}
// CreateProductOrderCKCommIDResp 商品订单生成 - 响应结构体
type CreateProductOrderCKCommIDResp struct {
MsgBody struct {
OrderInfo struct {
OrderID string `json:"orderid"` // CRM订单号
OrderDQ string `json:"orderdq"` // 电渠订单号
} `json:"orderinfo"`
} `json:"msgbody"`
}
// SendCreateProductOrderCKCommIDRequest 商品订单生成请求接口
func SendCreateProductOrderCKCommIDRequest(payload *CreateProductOrderCKCommIDReq) (*CreateProductOrderCKCommIDResp, error) {
// 发送通用 POST 请求
commonResp, err := DoSecurePostRequest(CreateOrderUrl, payload.UserInfo.ServerNum, payload)
if err != nil {
return nil, err
}
// 检查返回是否成功
if commonResp == nil || commonResp.Result == nil {
fmt.Println("commonResp == nil || commonResp.Result == nil")
return nil, errors.New(fmt.Sprintf("%s", commonResp.RespDesc))
}
if commonResp.RespCode != "0" {
return nil, errors.New(fmt.Sprintf("%s", commonResp.RespDesc))
}
// 反序列化 Data 字段为目标响应结构
var resp CreateProductOrderCKCommIDResp
dataBytes, err := json.Marshal(commonResp.Result) // 先转成 JSON 字节
if err != nil {
fmt.Println("marshal CommonResponse.Data failed, err is:", err)
return nil, fmt.Errorf("marshal CommonResponse.Data failed: %v", err)
}
err = json.Unmarshal(dataBytes, &resp)
if err != nil {
fmt.Println("unmarshal to CreateProductOrderCKCommIDResp failed, err is:", err)
return nil, fmt.Errorf("unmarshal to CreateProductOrderCKCommIDResp failed: %v", err)
}
return &resp, nil
}
// ApplySmsCodeReq 短信验证码申请 - 请求结构体
type ApplySmsCodeReq struct {
Rectype string `json:"rectype,omitempty"` // 可选
OrderID string `json:"orderid"` // 固定为 "0" 时表示仅申请验证码
MobileNo string `json:"mobileno"` // 手机号
RectypeExtInfoList []ApplySmsCodeExtAttr `json:"rectypeextinfolist,omitempty"` // 可选
}
// ApplySmsCodeExtAttr 扩展属性
type ApplySmsCodeExtAttr struct {
ExtAttrID string `json:"extattrid,omitempty"`
ExtAttrValue string `json:"extattrvalue,omitempty"`
}
// ApplySmsCodeResp 短信验证码申请 - 响应结构体
type ApplySmsCodeResp struct {
MsgBody struct {
SmsSeq string `json:"smsseq"` // 短信流水
} `json:"msgbody"`
}
func SendApplySmsCodeRequest(payload *ApplySmsCodeReq) (*ApplySmsCodeResp, error) {
resp, err := DoSecurePostRequest(ApplySmsCodeUrl, payload.MobileNo, payload)
if err != nil {
return nil, err
}
if resp == nil || resp.Result == nil {
return nil, errors.New("empty response from smscodeapply")
}
var result ApplySmsCodeResp
raw, err := json.Marshal(resp.Result)
if err != nil {
return nil, fmt.Errorf("marshal ApplySmsCodeResp.Data failed: %v", err)
}
if err := json.Unmarshal(raw, &result); err != nil {
return nil, fmt.Errorf("unmarshal to ApplySmsCodeResp failed: %v", err)
}
return &result, nil
}
// CheckSmsCodeAndCommitOrderReq 短信验证及订单提交 - 请求结构体
type CheckSmsCodeAndCommitOrderReq struct {
OrderID string `json:"orderid"` // 订单编码
ServerNum string `json:"servernum"` // 手机号
SmsSeq string `json:"smsseq"` // 短信流水
SmsCode string `json:"smscode"` // 短信验证码
}
// CheckSmsCodeAndCommitOrderResp 响应结构体
type CheckSmsCodeAndCommitOrderResp struct {
Code int `json:"code"`
Message string `json:"message"`
}
func SendCheckSmsCodeAndCommitOrderRequest(payload *CheckSmsCodeAndCommitOrderReq) (*CheckSmsCodeAndCommitOrderResp, error) {
resp, err := DoSecurePostRequest(CheckSmsCodeCommitOrderUrl, payload.ServerNum, payload)
if err != nil {
return nil, err
}
if resp == nil || resp.Result == nil {
return nil, errors.New("empty response from CheckSmsCodeAndCommitOrder")
}
var result CheckSmsCodeAndCommitOrderResp
raw, err := json.Marshal(resp.Result)
if err != nil {
return nil, fmt.Errorf("marshal response data failed: %v", err)
}
if err := json.Unmarshal(raw, &result); err != nil {
return nil, fmt.Errorf("unmarshal to CheckSmsCodeAndCommitOrderResp failed: %v", err)
}
return &result, nil
}
// QueryOrderReq 查询订单状态 - 请求结构体
type QueryOrderReq struct {
ServerNum string `json:"servernum"` // 手机号
OrderID string `json:"orderid"` // CRM订单编码
}
// QueryOrderResp 查询订单状态 - 响应结构体
type QueryOrderResp struct {
MsgBody struct {
OrderInfoList []struct {
OrderID string `json:"orderid"` // 订单编号
RecDate string `json:"recdate"` // 订单产生时间
Status string `json:"status"` // 订单状态码
} `json:"orderinfolist"`
} `json:"msgbody"`
}
func SendQueryOrderRequest(payload *QueryOrderReq) (*QueryOrderResp, error) {
resp, err := DoSecurePostRequest(QueryOrderUrl, payload.ServerNum, payload)
if err != nil {
return nil, err
}
if resp == nil || resp.Result == nil {
return nil, errors.New("empty response from QueryOrder")
}
var result QueryOrderResp
raw, err := json.Marshal(resp.Result)
if err != nil {
return nil, fmt.Errorf("marshal response data failed: %v", err)
}
if err := json.Unmarshal(raw, &result); err != nil {
return nil, fmt.Errorf("unmarshal to QueryOrderResp failed: %v", err)
}
return &result, nil
}