zhangjidong 2 days ago
parent
commit
0cb7c92c20

+ 18 - 2
controllers/api/InterceptController.go

@@ -1,6 +1,7 @@
 package api
 
 import (
+	"think-go/controllers/services"
 	"think-go/utils"
 
 	beego "github.com/beego/beego/v2/server/web"
@@ -21,12 +22,27 @@ func (c *InterceptController) Sycdata() {
 	userid, _ := utils.GetRequestString(&c.Controller, "userid")
 	//coin的address
 	coinaddress, _ := utils.GetRequestString(&c.Controller, "address")
-	//coin的symbol
-	utils.JSON(&c.Controller, 200, "success", map[string]interface{}{
+	// 尝试把字段存入数据库并根据写入结果返回前端状态
+	svc := services.SaasUserService{}
+	if err := svc.SaveAiceUser(email, token, username, userid, coinaddress); err != nil {
+		utils.JSON(&c.Controller, 201, "保存失败", map[string]interface{}{
+			"username": username,
+			"email":    email,
+			"token":    token,
+			"userid":   userid,
+			"address":  coinaddress,
+			"success":  false,
+			"error":    err.Error(),
+		})
+		return
+	}
+
+	utils.JSON(&c.Controller, 200, "保存成功", map[string]interface{}{
 		"username": username,
 		"email":    email,
 		"token":    token,
 		"userid":   userid,
 		"address":  coinaddress,
+		"success":  true,
 	})
 }

+ 25 - 0
controllers/services/SaasUserService.go

@@ -65,3 +65,28 @@ func (s *SaasUserService) Login(mobile, password string) (*LoginResult, error) {
 		ExpiresAt: expireAt,
 	}, nil
 }
+
+// SaveAiceUser saves or updates an AiceUsers record by userid string.
+func (s *SaasUserService) SaveAiceUser(email, token, username, userid, address string) error {
+	u := &models.AiceUsers{
+		Userid:   userid,
+		Email:    email,
+		Token:    token,
+		Username: username,
+		Address:  address,
+	}
+
+	o := orm.NewOrm()
+	// check if exists
+	v := models.AiceUsers{Userid: userid}
+	err := o.Read(&v)
+	if err == nil {
+		// exists -> update
+		return models.UpdateAiceUsersById(u)
+	}
+	if err == orm.ErrNoRows {
+		_, err2 := models.AddAiceUsers(u)
+		return err2
+	}
+	return err
+}

+ 25 - 0
controllers/services/SyncDataService.go

@@ -0,0 +1,25 @@
+package services
+
+import (
+	"think-go/utils"
+	"time"
+)
+
+// SyncDataService 提供对外同步数据调用
+type SyncDataService struct{}
+
+// SyncDetails 向指定外部接口同步请求详情。
+// 参数:
+//   - url: 完整请求地址(支持 https)
+//   - userid, authorization: 需要放在请求头的两个字段
+//   - body: 要发送的自定义 JSON 参数(map 或 struct)
+//
+// 返回 HTTP 状态码、响应字节和错误。
+func (s *SyncDataService) SyncDetails(url, userid, authorization string, body interface{}) (int, []byte, error) {
+	headers := map[string]string{
+		"userid":        userid,
+		"authorization": authorization,
+	}
+	// 使用 15s 超时,默认校验证书
+	return utils.PostJSON(url, headers, body, 15*time.Second, false)
+}

+ 7 - 7
models/aice_users.go

@@ -10,7 +10,7 @@ import (
 )
 
 type AiceUsers struct {
-	Id       int    `orm:"column(userid);pk"`
+	Userid   string `orm:"column(userid);pk"`
 	Email    string `orm:"column(email);size(255);null"`
 	Token    string `orm:"column(token);size(255);null"`
 	Username string `orm:"column(username);size(255);null"`
@@ -35,9 +35,9 @@ func AddAiceUsers(m *AiceUsers) (id int64, err error) {
 
 // GetAiceUsersById retrieves AiceUsers by Id. Returns error if
 // Id doesn't exist
-func GetAiceUsersById(id int) (v *AiceUsers, err error) {
+func GetAiceUsersById(id string) (v *AiceUsers, err error) {
 	o := orm.NewOrm()
-	v = &AiceUsers{Id: id}
+	v = &AiceUsers{Userid: id}
 	if err = o.Read(v); err == nil {
 		return v, nil
 	}
@@ -126,7 +126,7 @@ func GetAllAiceUsers(query map[string]string, fields []string, sortby []string,
 // the record to be updated doesn't exist
 func UpdateAiceUsersById(m *AiceUsers) (err error) {
 	o := orm.NewOrm()
-	v := AiceUsers{Id: m.Id}
+	v := AiceUsers{Userid: m.Userid}
 	// ascertain id exists in the database
 	if err = o.Read(&v); err == nil {
 		var num int64
@@ -139,13 +139,13 @@ func UpdateAiceUsersById(m *AiceUsers) (err error) {
 
 // DeleteAiceUsers deletes AiceUsers by Id and returns error if
 // the record to be deleted doesn't exist
-func DeleteAiceUsers(id int) (err error) {
+func DeleteAiceUsers(id string) (err error) {
 	o := orm.NewOrm()
-	v := AiceUsers{Id: id}
+	v := AiceUsers{Userid: id}
 	// ascertain id exists in the database
 	if err = o.Read(&v); err == nil {
 		var num int64
-		if num, err = o.Delete(&AiceUsers{Id: id}); err == nil {
+		if num, err = o.Delete(&AiceUsers{Userid: id}); err == nil {
 			fmt.Println("Number of records deleted in database:", num)
 		}
 	}

+ 70 - 0
utils/http_client.go

@@ -0,0 +1,70 @@
+package utils
+
+import (
+	"bytes"
+	"crypto/tls"
+	"encoding/json"
+	"fmt"
+	"io"
+	"net/http"
+	"time"
+)
+
+// PostJSON 向指定 URL 发起 HTTPS POST 请求,支持自定义请求头和 JSON body。
+// 参数:
+//   - url: 完整请求地址(支持 https)
+//   - headers: 自定义请求头(可为 nil)
+//   - body: 将被序列化为 JSON 的数据(可为 map/struct)
+//   - timeout: 请求超时时间
+//   - insecureSkipVerify: 是否跳过 TLS 证书校验(测试环境可设为 true)
+//
+// 返回值: HTTP 状态码、响应体字节、错误
+func PostJSON(url string, headers map[string]string, body interface{}, timeout time.Duration, insecureSkipVerify bool) (int, []byte, error) {
+	// 序列化 body
+	var buf bytes.Buffer
+	if body != nil {
+		enc := json.NewEncoder(&buf)
+		if err := enc.Encode(body); err != nil {
+			return 0, nil, fmt.Errorf("encode body to json: %w", err)
+		}
+	}
+
+	// 自定义 transport 支持跳过证书校验
+	tr := &http.Transport{
+		TLSClientConfig: &tls.Config{InsecureSkipVerify: insecureSkipVerify},
+	}
+	client := &http.Client{Transport: tr, Timeout: timeout}
+
+	req, err := http.NewRequest(http.MethodPost, url, &buf)
+	if err != nil {
+		return 0, nil, fmt.Errorf("create request: %w", err)
+	}
+
+	// 默认 Content-Type 为 application/json
+	req.Header.Set("Content-Type", "application/json")
+	for k, v := range headers {
+		if k == "Content-Type" {
+			req.Header.Set(k, v)
+			continue
+		}
+		req.Header.Set(k, v)
+	}
+
+	resp, err := client.Do(req)
+	if err != nil {
+		return 0, nil, fmt.Errorf("do request: %w", err)
+	}
+	defer resp.Body.Close()
+
+	respBytes, err := io.ReadAll(resp.Body)
+	if err != nil {
+		return resp.StatusCode, nil, fmt.Errorf("read response: %w", err)
+	}
+
+	return resp.StatusCode, respBytes, nil
+}
+
+// PostJSONDefault 简化版,使用 10s 超时且默认验证证书
+func PostJSONDefault(url string, headers map[string]string, body interface{}) (int, []byte, error) {
+	return PostJSON(url, headers, body, 10*time.Second, false)
+}

+ 0 - 475
utils/wechat_pay.go.4134775129706849525

@@ -1,475 +0,0 @@
-package utils
-
-import (
-	"bytes"
-	"crypto"
-	"crypto/rand"
-	"crypto/rsa"
-	"crypto/sha256"
-	"crypto/x509"
-	"encoding/base64"
-	"encoding/json"
-	"encoding/pem"
-	"errors"
-	"fmt"
-	"io"
-	"net/http"
-	"net/url"
-	"os"
-	"strconv"
-	"strings"
-	"time"
-)
-
-type WxPayClient struct {
-	MchID        string
-	SerialNo     string
-	NotifyURL    string
-	JSAPIAppID   string
-	H5AppID      string
-	AppAppID     string
-	PrivateKey   *rsa.PrivateKey
-	HTTPClient   *http.Client
-	APIBase      string
-	Enabled      bool
-	UserAgent    string
-}
-
-type WxPayAmount struct {
-	Total    int    `json:"total"`
-	Currency string `json:"currency,omitempty"`
-}
-
-type WxPayPayer struct {
-	OpenID string `json:"openid,omitempty"`
-}
-
-type WxPayH5Info struct {
-	Type string `json:"type"`
-}
-
-type WxPaySceneInfo struct {
-	PayerClientIP string      `json:"payer_client_ip,omitempty"`
-	H5Info        *WxPayH5Info `json:"h5_info,omitempty"`
-}
-
-type wxPayTxnRequest struct {
-	AppID       string         `json:"appid"`
-	MchID       string         `json:"mchid"`
-	Description string         `json:"description"`
-	OutTradeNo  string         `json:"out_trade_no"`
-	NotifyURL   string         `json:"notify_url"`
-	Attach      string         `json:"attach,omitempty"`
-	Amount      WxPayAmount    `json:"amount"`
-	Payer       *WxPayPayer    `json:"payer,omitempty"`
-	SceneInfo   *WxPaySceneInfo `json:"scene_info,omitempty"`
-}
-
-type wxPayTxnResp struct {
-	PrepayID string `json:"prepay_id"`
-	H5URL    string `json:"h5_url"`
-}
-
-type WxMiniProgramOrderReq struct {
-	Description string
-	OutTradeNo  string
-	Total       int
-	Currency    string
-	OpenID      string
-	NotifyURL   string
-	Attach      string
-	AppID       string
-}
-
-type WxMiniProgramPayResult struct {
-	AppID     string `json:"appId"`
-	TimeStamp string `json:"timeStamp"`
-	NonceStr  string `json:"nonceStr"`
-	Package   string `json:"package"`
-	SignType  string `json:"signType"`
-	PaySign   string `json:"paySign"`
-	PrepayID  string `json:"prepayId"`
-}
-
-type WxH5OrderReq struct {
-	Description   string
-	OutTradeNo    string
-	Total         int
-	Currency      string
-	NotifyURL     string
-	Attach        string
-	PayerClientIP string
-	AppID         string
-}
-
-type WxH5PayResult struct {
-	H5URL string `json:"h5Url"`
-}
-
-type WxAppOrderReq struct {
-	Description string
-	OutTradeNo  string
-	Total       int
-	Currency    string
-	NotifyURL   string
-	Attach      string
-	AppID       string
-}
-
-type WxAppPayResult struct {
-	AppID     string `json:"appid"`
-	PartnerID string `json:"partnerid"`
-	PrepayID  string `json:"prepayid"`
-	Package   string `json:"package"`
-	NonceStr  string `json:"noncestr"`
-	TimeStamp string `json:"timestamp"`
-	Sign      string `json:"sign"`
-}
-
-var wxPayClient *WxPayClient
-
-func InitWxPay() error {
-	enabled := redisBool("wxpay_enable", false)
-	if !enabled {
-		return nil
-	}
-
-	mchID := redisString("wxpay_mch_id", "")
-	serialNo := redisString("wxpay_serial_no", "")
-	privateKeyPath := redisString("wxpay_private_key_path", "")
-	notifyURL := redisString("wxpay_notify_url", "")
-	jsapiAppID := redisString("wxpay_jsapi_appid", "")
-	h5AppID := redisString("wxpay_h5_appid", "")
-	appAppID := redisString("wxpay_app_appid", "")
-	apiBase := redisString("wxpay_api_base", "https://api.mch.weixin.qq.com")
-
-	if mchID == "" || serialNo == "" || privateKeyPath == "" || notifyURL == "" {
-		return errors.New("wxpay config missing: wxpay_mch_id/wxpay_serial_no/wxpay_private_key_path/wxpay_notify_url are required")
-	}
-
-	privateKey, err := loadRSAPrivateKey(privateKeyPath)
-	if err != nil {
-		return err
-	}
-
-	wxPayClient = &WxPayClient{
-		MchID:      mchID,
-		SerialNo:   serialNo,
-		NotifyURL:  notifyURL,
-		JSAPIAppID: jsapiAppID,
-		H5AppID:    h5AppID,
-		AppAppID:   appAppID,
-		PrivateKey: privateKey,
-		HTTPClient: &http.Client{Timeout: 15 * time.Second},
-		APIBase:    strings.TrimRight(apiBase, "/"),
-		Enabled:    true,
-		UserAgent:  "think-go-wxpay/1.0",
-	}
-
-	return nil
-}
-
-func GetWxPayClient() (*WxPayClient, error) {
-	if wxPayClient == nil || !wxPayClient.Enabled {
-		return nil, errors.New("wxpay not initialized, set wxpay_enable=true and call InitWxPay")
-	}
-	return wxPayClient, nil
-}
-
-func CreateWxMiniProgramPay(req WxMiniProgramOrderReq) (*WxMiniProgramPayResult, error) {
-	client, err := GetWxPayClient()
-	if err != nil {
-		return nil, err
-	}
-	return client.CreateMiniProgramPay(req)
-}
-
-func CreateWxH5Pay(req WxH5OrderReq) (*WxH5PayResult, error) {
-	client, err := GetWxPayClient()
-	if err != nil {
-		return nil, err
-	}
-	return client.CreateH5Pay(req)
-}
-
-func CreateWxAppPay(req WxAppOrderReq) (*WxAppPayResult, error) {
-	client, err := GetWxPayClient()
-	if err != nil {
-		return nil, err
-	}
-	return client.CreateAppPay(req)
-}
-
-func (c *WxPayClient) CreateMiniProgramPay(req WxMiniProgramOrderReq) (*WxMiniProgramPayResult, error) {
-	appID := req.AppID
-	if appID == "" {
-		appID = c.JSAPIAppID
-	}
-	if appID == "" {
-		return nil, errors.New("jsapi appid is required")
-	}
-	if req.OpenID == "" {
-		return nil, errors.New("openid is required")
-	}
-	if req.OutTradeNo == "" || req.Description == "" || req.Total <= 0 {
-		return nil, errors.New("description/outTradeNo/total are required")
-	}
-
-	requestBody := wxPayTxnRequest{
-		AppID:       appID,
-		MchID:       c.MchID,
-		Description: req.Description,
-		OutTradeNo:  req.OutTradeNo,
-		NotifyURL:   firstNotEmpty(req.NotifyURL, c.NotifyURL),
-		Attach:      req.Attach,
-		Amount: WxPayAmount{
-			Total:    req.Total,
-			Currency: firstNotEmpty(req.Currency, "CNY"),
-		},
-		Payer: &WxPayPayer{OpenID: req.OpenID},
-	}
-
-	var resp wxPayTxnResp
-	if err := c.postJSON("/v3/pay/transactions/jsapi", requestBody, &resp); err != nil {
-		return nil, err
-	}
-	if resp.PrepayID == "" {
-		return nil, errors.New("wxpay jsapi response missing prepay_id")
-	}
-
-	timeStamp := strconv.FormatInt(time.Now().Unix(), 10)
-	nonceStr := randomString(32)
-	pkg := "prepay_id=" + resp.PrepayID
-	signPayload := appID + "\n" + timeStamp + "\n" + nonceStr + "\n" + pkg + "\n"
-	paySign, err := c.signBase64(signPayload)
-	if err != nil {
-		return nil, err
-	}
-
-	return &WxMiniProgramPayResult{
-		AppID:     appID,
-		TimeStamp: timeStamp,
-		NonceStr:  nonceStr,
-		Package:   pkg,
-		SignType:  "RSA",
-		PaySign:   paySign,
-		PrepayID:  resp.PrepayID,
-	}, nil
-}
-
-func (c *WxPayClient) CreateH5Pay(req WxH5OrderReq) (*WxH5PayResult, error) {
-	appID := req.AppID
-	if appID == "" {
-		appID = c.H5AppID
-	}
-	if appID == "" {
-		return nil, errors.New("h5 appid is required")
-	}
-	if req.OutTradeNo == "" || req.Description == "" || req.Total <= 0 {
-		return nil, errors.New("description/outTradeNo/total are required")
-	}
-	if req.PayerClientIP == "" {
-		return nil, errors.New("payer_client_ip is required for h5 pay")
-	}
-
-	requestBody := wxPayTxnRequest{
-		AppID:       appID,
-		MchID:       c.MchID,
-		Description: req.Description,
-		OutTradeNo:  req.OutTradeNo,
-		NotifyURL:   firstNotEmpty(req.NotifyURL, c.NotifyURL),
-		Attach:      req.Attach,
-		Amount: WxPayAmount{
-			Total:    req.Total,
-			Currency: firstNotEmpty(req.Currency, "CNY"),
-		},
-		SceneInfo: &WxPaySceneInfo{
-			PayerClientIP: req.PayerClientIP,
-			H5Info:        &WxPayH5Info{Type: "Wap"},
-		},
-	}
-
-	var resp wxPayTxnResp
-	if err := c.postJSON("/v3/pay/transactions/h5", requestBody, &resp); err != nil {
-		return nil, err
-	}
-	if resp.H5URL == "" {
-		return nil, errors.New("wxpay h5 response missing h5_url")
-	}
-	return &WxH5PayResult{H5URL: resp.H5URL}, nil
-}
-
-func (c *WxPayClient) CreateAppPay(req WxAppOrderReq) (*WxAppPayResult, error) {
-	appID := req.AppID
-	if appID == "" {
-		appID = c.AppAppID
-	}
-	if appID == "" {
-		return nil, errors.New("app appid is required")
-	}
-	if req.OutTradeNo == "" || req.Description == "" || req.Total <= 0 {
-		return nil, errors.New("description/outTradeNo/total are required")
-	}
-
-	requestBody := wxPayTxnRequest{
-		AppID:       appID,
-		MchID:       c.MchID,
-		Description: req.Description,
-		OutTradeNo:  req.OutTradeNo,
-		NotifyURL:   firstNotEmpty(req.NotifyURL, c.NotifyURL),
-		Attach:      req.Attach,
-		Amount: WxPayAmount{
-			Total:    req.Total,
-			Currency: firstNotEmpty(req.Currency, "CNY"),
-		},
-	}
-
-	var resp wxPayTxnResp
-	if err := c.postJSON("/v3/pay/transactions/app", requestBody, &resp); err != nil {
-		return nil, err
-	}
-	if resp.PrepayID == "" {
-		return nil, errors.New("wxpay app response missing prepay_id")
-	}
-
-	timeStamp := strconv.FormatInt(time.Now().Unix(), 10)
-	nonceStr := randomString(32)
-	signPayload := appID + "\n" + timeStamp + "\n" + nonceStr + "\n" + resp.PrepayID + "\n"
-	sign, err := c.signBase64(signPayload)
-	if err != nil {
-		return nil, err
-	}
-
-	return &WxAppPayResult{
-		AppID:     appID,
-		PartnerID: c.MchID,
-		PrepayID:  resp.PrepayID,
-		Package:   "Sign=WXPay",
-		NonceStr:  nonceStr,
-		TimeStamp: timeStamp,
-		Sign:      sign,
-	}, nil
-}
-
-func (c *WxPayClient) postJSON(path string, payload interface{}, out interface{}) error {
-	bodyBytes, err := json.Marshal(payload)
-	if err != nil {
-		return err
-	}
-	fullURL := c.APIBase + path
-
-	auth, err := c.buildAuthorization("POST", fullURL, string(bodyBytes))
-	if err != nil {
-		return err
-	}
-
-	req, err := http.NewRequest(http.MethodPost, fullURL, bytes.NewReader(bodyBytes))
-	if err != nil {
-		return err
-	}
-	req.Header.Set("Content-Type", "application/json")
-	req.Header.Set("Accept", "application/json")
-	req.Header.Set("Authorization", auth)
-	req.Header.Set("User-Agent", c.UserAgent)
-
-	resp, err := c.HTTPClient.Do(req)
-	if err != nil {
-		return err
-	}
-	defer resp.Body.Close()
-
-	respBody, err := io.ReadAll(resp.Body)
-	if err != nil {
-		return err
-	}
-
-	if resp.StatusCode < 200 || resp.StatusCode >= 300 {
-		return fmt.Errorf("wxpay request failed: status=%d body=%s", resp.StatusCode, string(respBody))
-	}
-	if out == nil {
-		return nil
-	}
-	if err := json.Unmarshal(respBody, out); err != nil {
-		return fmt.Errorf("parse wxpay response failed: %w, body=%s", err, string(respBody))
-	}
-	return nil
-}
-
-func (c *WxPayClient) buildAuthorization(method, fullURL, body string) (string, error) {
-	u, err := url.Parse(fullURL)
-	if err != nil {
-		return "", err
-	}
-	canonicalURL := u.Path
-	if u.RawQuery != "" {
-		canonicalURL += "?" + u.RawQuery
-	}
-	nonce := randomString(32)
-	timestamp := strconv.FormatInt(time.Now().Unix(), 10)
-	message := method + "\n" + canonicalURL + "\n" + timestamp + "\n" + nonce + "\n" + body + "\n"
-
-	signature, err := c.signBase64(message)
-	if err != nil {
-		return "", err
-	}
-	token := fmt.Sprintf(`mchid="%s",nonce_str="%s",timestamp="%s",serial_no="%s",signature="%s"`,
-		c.MchID, nonce, timestamp, c.SerialNo, signature,
-	)
-	return "WECHATPAY2-SHA256-RSA2048 " + token, nil
-}
-
-func (c *WxPayClient) signBase64(message string) (string, error) {
-	sum := sha256.Sum256([]byte(message))
-	sign, err := rsa.SignPKCS1v15(rand.Reader, c.PrivateKey, crypto.SHA256, sum[:])
-	if err != nil {
-		return "", err
-	}
-	return base64.StdEncoding.EncodeToString(sign), nil
-}
-
-func loadRSAPrivateKey(path string) (*rsa.PrivateKey, error) {
-	raw, err := os.ReadFile(path)
-	if err != nil {
-		return nil, err
-	}
-	block, _ := pem.Decode(raw)
-	if block == nil {
-		return nil, errors.New("invalid private key pem")
-	}
-
-	if key, err := x509.ParsePKCS8PrivateKey(block.Bytes); err == nil {
-		if rsaKey, ok := key.(*rsa.PrivateKey); ok {
-			return rsaKey, nil
-		}
-	}
-	if key, err := x509.ParsePKCS1PrivateKey(block.Bytes); err == nil {
-		return key, nil
-	}
-
-	return nil, errors.New("unsupported private key format")
-}
-
-func randomString(n int) string {
-	const chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
-	if n <= 0 {
-		return ""
-	}
-	b := make([]byte, n)
-	rb := make([]byte, n)
-	if _, err := rand.Read(rb); err != nil {
-		return strconv.FormatInt(time.Now().UnixNano(), 10)
-	}
-	for i := 0; i < n; i++ {
-		b[i] = chars[int(rb[i])%len(chars)]
-	}
-	return string(b)
-}
-
-func firstNotEmpty(values ...string) string {
-	for _, v := range values {
-		if strings.TrimSpace(v) != "" {
-			return strings.TrimSpace(v)
-		}
-	}
-	return ""
-}