daShangDao_centerBook/pricing.go
2026-02-28 14:27:33 +08:00

330 lines
11 KiB
Go
Raw 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.

// 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": "<id>", "shopType": "<type>" }
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
}