package ikucun import ( "context" "crypto/sha1" "encoding/hex" "fmt" "github.com/gogf/gf/encoding/gjson" "github.com/gogf/gf/frame/g" "github.com/gogf/gf/os/gtime" "github.com/gogf/gf/util/gconv" "math/rand" "net/url" "sort" "strings" "time" ) var server *Config func New(req *Config) { server = req return } const pkgName = "ikucun" type Config struct { AppId string AppSecret string ApiUrl string AccessToken string } // 生成随机 nonce 字符串 (等效 JS 的 makeNonceStr) func makeNonceStr() string { // 原始有效字符集(排除易混淆字符) const baseStr = "abcdefghjkmnpqrstuvwxyzABCDEFGHJKMNPQRSTUVWXYZ23456789" // 打乱字符顺序(Go 的稳定洗牌算法) shuffled := []rune(baseStr) rand.Seed(time.Now().UnixNano()) rand.Shuffle(len(shuffled), func(i, j int) { shuffled[i], shuffled[j] = shuffled[j], shuffled[i] }) // 随机截取 10 位字符 start := rand.Intn(len(shuffled) - 10) return string(shuffled[start : start+10]) } // 构建签名前字符串 (等效 JS 的 beforeSignBuild) func beforeSignBuild(nonceStr, interfaceName string, queryParams map[string]string, body string) string { // 处理空参数 if queryParams == nil { queryParams = make(map[string]string) } // 准备 POST 参数 postParams := make(map[string]string) if body != "" { postParams["body"] = body } // 获取公共参数 appSecret := server.AppSecret timestamp := time.Now().Unix() // 直接获取秒级时间戳 commonParams := map[string]string{ "appid": queryParams["appid"], "noncestr": nonceStr, "timestamp": fmt.Sprintf("%d", timestamp), "version": queryParams["version"], "appsecret": appSecret, "format": queryParams["json"], "interfaceName": interfaceName, } // 合并参数(注意合并顺序) allParams := make(map[string]string) for k, v := range commonParams { allParams[k] = v } for k, v := range queryParams { allParams[k] = v } for k, v := range postParams { allParams[k] = v } // 按键名排序 keys := make([]string, 0, len(allParams)) for k := range allParams { keys = append(keys, k) } sort.Strings(keys) // 构建签名字符串 var builder strings.Builder for i, k := range keys { if i > 0 { builder.WriteString("&") } builder.WriteString(fmt.Sprintf("%s=%s", k, allParams[k])) } return builder.String() } func getQueryParams(interfaceName, body string, queryParamsTemp map[string]interface{}) map[string]string { // 生成 nonce 并添加到查询参数 nonceStr := makeNonceStr() timestamp := time.Now().Unix() // 直接获取秒级时间戳 queryParams := map[string]string{ "appid": server.AppId, "version": "1.0", "format": "json", "timestamp": fmt.Sprintf("%d", timestamp), "interfaceName": interfaceName, "accessToken": server.AccessToken, // 其他查询参数... } if nil != queryParamsTemp { for k, v := range queryParamsTemp { queryParams[k] = gconv.String(v) } } queryParams["noncestr"] = nonceStr // 构建签名前字符串 signStr := beforeSignBuild( nonceStr, interfaceName, queryParams, body, ) // 计算 SHA1 签名 hasher := sha1.New() hasher.Write([]byte(signStr)) sign := hex.EncodeToString(hasher.Sum(nil)) // 添加签名到查询参数 queryParams["sign"] = sign //res := gsha1.Encrypt(req) return queryParams } // 处理 interface{} 类型的值 func mapToSortedQuery(params map[string]string) string { if len(params) == 0 { return "" } keys := make([]string, 0, len(params)) for k := range params { keys = append(keys, k) } sort.Strings(keys) var builder strings.Builder for i, k := range keys { if i > 0 { builder.WriteByte('&') } builder.WriteString(url.QueryEscape(k)) builder.WriteByte('=') builder.WriteString(url.QueryEscape(params[k])) } return builder.String() } func post(ctx context.Context, method string, req interface{}) (res string, err error) { Start := gtime.TimestampMilli() param := gjson.New(req) queryParam := getQueryParams(method, param.MustToJsonString(), nil) Url := server.ApiUrl + "?" + mapToSortedQuery(queryParam) Request := g.Client() Request.SetHeader("Content-Type", "application/json") resp, err := Request.Timeout(time.Second*5).Post(Url, param.MustToJsonString()) defer func() { _ = resp.Close() ctx = context.WithValue(ctx, "Method", "POST") ctx = context.WithValue(ctx, "URI", Url) if err != nil { g.Log().Ctx(ctx).Cat(pkgName).Cat("error").Infof("参数【%v】错误【%v】响应时间【%v ms】", param.MustToJsonString(), err.Error(), gtime.TimestampMilli()-Start) } else { g.Log().Ctx(ctx).Cat(pkgName).Infof("参数【%v】响应【%v】响应时间【%v ms】", param.MustToJsonString(), res, gtime.TimestampMilli()-Start) } }() res = resp.ReadAllString() return } func get(ctx context.Context, method string, req interface{}) (res string, err error) { Start := gtime.TimestampMilli() param := gjson.New(req) queryParam := getQueryParams(method, "", gconv.Map(req)) Url := server.ApiUrl + "?" + mapToSortedQuery(queryParam) Request := g.Client() Request.SetHeader("Content-Type", "application/json") resp, err := Request.Timeout(time.Second*5).Get(Url, param.MustToJsonString()) defer func() { _ = resp.Close() ctx = context.WithValue(ctx, "Method", "GET") ctx = context.WithValue(ctx, "URI", Url) if err != nil { g.Log().Ctx(ctx).Cat(pkgName).Cat("error").Infof("参数【%v】错误【%v】响应时间【%v ms】", param.MustToJsonString(), err.Error(), gtime.TimestampMilli()-Start) } else { g.Log().Ctx(ctx).Cat(pkgName).Infof("参数【%v】响应【%v】响应时间【%v ms】", param.MustToJsonString(), res, gtime.TimestampMilli()-Start) } }() res = resp.ReadAllString() return }