// go/main.go package main import ( "bytes" "crypto/aes" "crypto/cipher" "crypto/rand" "crypto/rsa" "crypto/sha256" "crypto/x509" "encoding/base64" "encoding/json" "errors" "fmt" "log" "net/http" "net/url" "strconv" "strings" ) // 与 Java 保持一致的 RSA 公钥/私钥(Base64) var publicKeyStr = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqmdgZjjpySFAd+3Go33tshTOhRcSl6Sl4x8bR5vrEzsvFqQQW+VXLco0E1jy9dIR4NguRIGWOowi/4EU5PEM3ZVrXQCxCnyyqIuuDtY9QNTh5DTn60aDOLEL2X7+mvICgFg+VAKPik+8fBSUfzcGiLqFlx+VhUAkq9hCyd/wtYInuAxPSoCr8F2cmI4/V6sAhVUkHUZhJvWlyDLUpYKOGgYM4rXjCXXKrPO0FNf1iY70AWACSJmXUwBVuIRYWfTRVOvzEPWkp/tuqir/XcvMfKKVU5/eCr8abNVIG99HTF1iKvQPdQUldAyk5z9YPV5IwAbrjlEACmJ5JvuT3bypewIDAQAB" var privateKeyStr = "MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCqZ2BmOOnJIUB37cajfe2yFM6FFxKXpKXjHxtHm+sTOy8WpBBb5VctyjQTWPL10hHg2C5EgZY6jCL/gRTk8QzdlWtdALEKfLKoi64O1j1A1OHkNOfrRoM4sQvZfv6a8gKAWD5UAo+KT7x8FJR/NwaIuoWXH5WFQCSr2ELJ3/C1gie4DE9KgKvwXZyYjj9XqwCFVSQdRmEm9aXIMtSlgo4aBgziteMJdcqs87QU1/WJjvQBYAJImZdTAFW4hFhZ9NFU6/MQ9aSn+26qKv9dy8x8opVTn94Kvxps1Ugb30dMXWIq9A91BSV0DKTnP1g9XkjABuuOUQAKYnkm+5PdvKl7AgMBAAECggEAY0xmYmsb4PadiMVokXEaiEGTrv6o+PEbMeS4ktwK+mPsprboSYS1bpt8CSI2QoUtoeaX35fcITX0VwuzT04gfydJLyLuB/xuZ8Utoru5agQjtkYWN4YZhXm2PAHDACuyxXOmrnHnj2OzpGKhvhgkmJyIqG3hRYsBU5psIRN8Q2gnCarhiB948YDu6EfvFJPv0ET9T+mzQWLmMVz+lorepfcXV1Wwvr0awRPfyS2s7te9AW4GYEzKN9ijZx05XnYOSgh/hD82iqh+poPXiqamhZGcQBQ8CveVbyNatTpbZYca8gXByOIipSEqg3UVQ2rnd/hYAh4VKSagyKXtFt7REQKBgQDgjGtbit3JK8jJuQTWBqgNHWphc1/4tAidjWeEF1NBi0NZuSZiVLoh9J794lO67psAZheZV70gXD9pHF7tiSESTIsHZiOtU9rrEVACc6EHJIeBrmB4oNWvzzix5S0S6RZxLYm5oiEoNjXSfqcFqCHGiT3lnxUhSp3JywlcIx0QZQKBgQDCRYEMt+u0Hp8JbhxhHx5Z87dZRJe0AGRQXnz3blxq+MTIVB6oj6NKBxPN6BmNLGI/xnxO8XTog5JCv9SZRZpY6eZmdCt2sVK2k2i1kxpd3sLjlbFTNnyC9RJFeZMjIlvW03KmDu0VBDf4CW/zSP3aej+vgxvOZesYcgI9isoEXwKBgEmrJ+mflIXUfIpZzhFdm7K5zNXt4TWZ8x2lb6mxcVoWk2ETUll+TJapR6Qppai1cVrfI6zmUSEVwqP8b9RkYdo8DHy/8MKDuVXXlzVGtDTAskhEalgJBDIqvQH4GyKSIA+/jei+HTyxFFVbwfYkI/ibvBfiai9C6KN0njyBNJ7VAoGAVgDZCZ1efmXT+CPD8ocJM78+GwnPswM9ZYr+/bbguQaabyk2TV8RZdNORCiNLz9H233uSDCClfCxTlWIM7ZphxU9R3wERc5olKUbhM6zrHzSgFgjoXgMlRkTVqhkp/gs+iSvq64N7PDqKidbZTOaFh9qlDORmsTp1++Y6E/J8TcCgYEA0cszTR9OuvrQDpiX5PW+HB66GIxuEFBCla0HphtV16i/tzXnIaZ6q2hf1e9qejO3lOIzi3e1PVHOuMIemzl17batonERhBIjDYEtGraFyaHSgkp+zRdjPGj8A0dq7iwdv0M4ravQcF9dVvfEucVhN3XSJXSqdJRSoZzOvRZH4VY=" // parsePublicKey 解析 Java Base64 公钥为 rsa.PublicKey // 返回 rsa.PublicKey 或错误 func parsePublicKey(b64 string) (*rsa.PublicKey, error) { der, err := base64.StdEncoding.DecodeString(b64) if err != nil { return nil, err } pub, err := x509ParsePublicKey(der) return pub, err } // parsePrivateKey 解析 Java Base64 私钥为 rsa.PrivateKey // 返回 rsa.PrivateKey 或错误 func parsePrivateKey(b64 string) (*rsa.PrivateKey, error) { der, err := base64.StdEncoding.DecodeString(b64) if err != nil { return nil, err } priv, err := x509ParsePrivateKey(der) return priv, err } // x509ParsePublicKey X509 公钥解析(PKCS#8) func x509ParsePublicKey(der []byte) (*rsa.PublicKey, error) { key, err := x509.ParsePKIXPublicKey(der) if err != nil { return nil, err } pub, ok := key.(*rsa.PublicKey) if !ok { return nil, errors.New("公钥类型错误") } return pub, nil } // x509ParsePrivateKey PKCS#8 私钥解析 func x509ParsePrivateKey(der []byte) (*rsa.PrivateKey, error) { key, err := x509.ParsePKCS8PrivateKey(der) if err != nil { return nil, err } priv, ok := key.(*rsa.PrivateKey) if !ok { return nil, errors.New("私钥类型错误") } return priv, nil } // EncryptHybrid 混合加密:随机 AES-256-GCM + RSA-OAEP(SHA-256) 加密 AES 密钥 // 输出 Base64(iv || rsa(aesKey) || gcmCiphertext) 与 Java 完全一致 func EncryptHybrid(plaintext []byte) (string, error) { pub, err := parsePublicKey(publicKeyStr) if err != nil { return "", err } aesKey := make([]byte, 32) if _, err = rand.Read(aesKey); err != nil { return "", err } block, err := aes.NewCipher(aesKey) if err != nil { return "", err } gcm, err := cipher.NewGCM(block) if err != nil { return "", err } iv := make([]byte, 12) if _, err = rand.Read(iv); err != nil { return "", err } ciphertext := gcm.Seal(nil, iv, plaintext, nil) label := []byte{} encKey, err := rsa.EncryptOAEP(sha256.New(), rand.Reader, pub, aesKey, label) if err != nil { return "", err } buf := bytes.Join([][]byte{iv, encKey, ciphertext}, nil) return base64.StdEncoding.EncodeToString(buf), nil } // DecryptHybrid 混合解密:拆分 iv、RSA 加密的 AES 密钥、GCM 密文 // 使用 RSA-OAEP(SHA-256) 解密 AES 密钥,再用 AES-256-GCM 解密数据 func DecryptHybrid(token string) ([]byte, error) { log.Println("=== 开始解密 DecryptHybrid ===") log.Println("原始 token:", token) // 兼容 Java 的 " "→"+" token = strings.ReplaceAll(token, " ", "+") log.Println("空格替换后的 token:", token) data, err := base64.StdEncoding.DecodeString(token) if err != nil { log.Println("Base64 解码失败:", err) return nil, err } log.Println("Base64 解码成功,长度:", len(data)) if len(data) < 12+256 { log.Println("密文长度非法:", len(data)) return nil, errors.New("密文长度非法") } iv := data[:12] encKey := data[12 : 12+256] ct := data[12+256:] log.Println("IV 长度:", len(iv)) log.Println("RSA 加密的 AES Key 长度:", len(encKey)) log.Println("GCM 密文长度:", len(ct)) priv, err := parsePrivateKey(privateKeyStr) if err != nil { log.Println("解析私钥失败:", err) return nil, err } // RSA PKCS1Padding 解密 aesKey, err := rsa.DecryptPKCS1v15(rand.Reader, priv, encKey) if err != nil { log.Println("RSA PKCS1 解密失败:", err) return nil, err } log.Println("RSA 解密 AES Key 成功,长度:", len(aesKey)) block, err := aes.NewCipher(aesKey) if err != nil { log.Println("AES Cipher 创建失败:", err) return nil, err } gcm, err := cipher.NewGCM(block) if err != nil { log.Println("GCM 创建失败:", err) return nil, err } pt, err := gcm.Open(nil, iv, ct, nil) if err != nil { log.Println("GCM 解密失败:", err) return nil, err } log.Println("解密成功,明文长度:", len(pt)) log.Println("=== 解密结束 ===") return pt, nil } // PricingLinkHandler 处理 GET /pricingLink // 构造与 Java 相同结构的载荷:number(=numbers*100)、total、qureyApiUrl、taskMapList,并进行混合加密 func PricingLinkHandler(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodGet { http.Error(w, "仅支持 GET", http.StatusMethodNotAllowed) return } q := r.URL.Query() numbers := atoi(q.Get("numbers")) total := atoi(q.Get("total")) res := map[string]any{ "number": strconv.Itoa(numbers * 100), "total": strconv.Itoa(total), "qureyApiUrl": buildQueryApiUrl(q), "taskMapList": buildTaskMapList(q), } body, err := json.Marshal(res) if err != nil { http.Error(w, "序列化失败", http.StatusInternalServerError) return } token, err := EncryptHybrid(body) if err != nil { http.Error(w, "加密失败", http.StatusInternalServerError) return } writeJSON(w, http.StatusOK, token) } // PricingLinkDecHandler 处理 GET /pricingLinkDec // 解密并返回原始 Map,兼容 Java 的 Base64 空格替换逻辑 func PricingLinkDecHandler(w http.ResponseWriter, r *http.Request) { log.Println("进入 /pricingLinkDec") if r.Method != http.MethodGet { log.Println("方法非法:", r.Method) http.Error(w, "仅支持 GET", http.StatusMethodNotAllowed) return } link := r.URL.Query().Get("link") log.Println("收到 link 参数:", link) if strings.TrimSpace(link) == "" { log.Println("缺少 link 参数") http.Error(w, "缺少参数 link", http.StatusBadRequest) return } pt, err := DecryptHybrid(link) if err != nil { log.Println("解密失败:", err) http.Error(w, "解密失败", http.StatusBadRequest) return } var payload map[string]any if err := json.Unmarshal(pt, &payload); err != nil { log.Println("JSON 解析失败:", err) http.Error(w, "载荷解析失败", http.StatusBadRequest) return } log.Println("解密成功,返回 payload:", payload) writeJSON(w, http.StatusOK, payload) } // buildQueryApiUrl 生成 qureyApiUrl,与 Java 的 getUrl 逻辑一致(按字段编码并拼接) // 基础路径固定为 https://api.buzhiyushu.cn/zhishu/baseInfo/pricing/list func buildQueryApiUrl(q url.Values) string { base := "https://api.buzhiyushu.cn/zhishu/baseInfo/pricing/list?" add := func(k string) string { v := strings.TrimSpace(q.Get(k)) if v == "" { return "" } return fmt.Sprintf("%s=%s&", k, url.QueryEscape(v)) } var b strings.Builder b.WriteString(base) // 字段与 Java 对齐 b.WriteString(add("bookName")) b.WriteString(add("bookPic")) b.WriteString(add("isbn")) b.WriteString(add("author")) b.WriteString(add("publisher")) if v := q.Get("vio_book"); v != "" { b.WriteString(fmt.Sprintf("vio_book=%s&", url.QueryEscape(v))) } if v := q.Get("book_set"); v != "" { b.WriteString(fmt.Sprintf("book_set=%s&", url.QueryEscape(v))) } if v := q.Get("onenum_mbooks"); v != "" { b.WriteString(fmt.Sprintf("onenum_mbooks=%s&", url.QueryEscape(v))) } if v := q.Get("ill_publisher"); v != "" { b.WriteString(fmt.Sprintf("ill_publisher=%s&", url.QueryEscape(v))) } b.WriteString(add("saleSelect")) b.WriteString(add("category")) if v := q.Get("ill_author"); v != "" { b.WriteString(fmt.Sprintf("ill_author=%s&", url.QueryEscape(v))) } b.WriteString(add("publiction_times")) b.WriteString(add("buy_counts")) // sell_counts 最后一个不加 & if v := strings.TrimSpace(q.Get("sell_counts")); v != "" { b.WriteString(fmt.Sprintf("sell_counts=%s", url.QueryEscape(v))) } // 去除可能的尾部 & u := b.String() if strings.HasSuffix(u, "&") { u = strings.TrimSuffix(u, "&") } return u } // buildTaskMapList 构造 taskMapList(无数据库,仅透传 shopIds 形成占位) // 兼容 Java 的数组元素形态:{ "taskId": "", "shopId": "", "shopType": "" } func buildTaskMapList(q url.Values) []map[string]string { ids := strings.TrimSpace(q.Get("shopIds")) types := q["shopType"] // 可选:与 shopIds 对应 if ids == "" { return []map[string]string{} } idList := strings.Split(ids, ",") out := make([]map[string]string, 0, len(idList)) for i, id := range idList { m := map[string]string{ "taskId": "", // 无任务系统,留空 "shopId": strings.TrimSpace(id), "shopType": "", } if i < len(types) { m["shopType"] = strings.TrimSpace(types[i]) } out = append(out, m) } return out } // writeJSON 写出 JSON 响应 func writeJSON(w http.ResponseWriter, status int, v any) { w.Header().Set("Content-Type", "application/json; charset=utf-8") w.WriteHeader(status) _ = json.NewEncoder(w).Encode(v) } // atoi 安全转换 func atoi(s string) int { v, _ := strconv.Atoi(strings.TrimSpace(s)) return v }