445 lines
16 KiB
Go
445 lines
16 KiB
Go
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
|
||
}
|