diff --git a/internal/boot/boot.go b/internal/boot/boot.go index 5509e92..37e243c 100644 --- a/internal/boot/boot.go +++ b/internal/boot/boot.go @@ -13,7 +13,8 @@ var ( ) func Boot() (err error) { - err = service.SystemCron().StartCron() + // 启动计划任务定时器,预防debug工具激活计划任务造成重复执行,此处不执行计划任务 + //err = service.SystemCron().StartCron() //用户活动持久化 service.SystemCron().AddCronV2(v1.CronType_DAILY, func(ctx context.Context) error { diff --git a/internal/logic/ip2region/ip2region.go b/internal/logic/ip2region/ip2region.go index 88ba870..d474fe6 100644 --- a/internal/logic/ip2region/ip2region.go +++ b/internal/logic/ip2region/ip2region.go @@ -15,6 +15,8 @@ var ( wait = false ) +const IpDbPath = "runtime/library/ip2region.xdb" + type sIp2region struct { searcher *xdb.Searcher } @@ -35,16 +37,17 @@ func init() { // Load 加载到内存中 // // @Description: 加载ip2region数据库到内存中。 + // @receiver s *sIp2region: sIp2region的实例。 func (s *sIp2region) Load() { var err error - var dbPath = "runtime/library/ip2region.xdb" + //var dbPath = "runtime/library/ip2region.xdb" var url = "https://github.com/ayflying/resource/raw/refs/heads/master/attachment/ip2region.xdb" if wait { return } - if gfile.IsEmpty(dbPath) { + if gfile.IsEmpty(IpDbPath) { wait = true defer func() { wait = false @@ -55,9 +58,9 @@ func (s *sIp2region) Load() { if err2 != nil { return } - err = gfile.PutBytes(dbPath, putData.ReadAll()) + err = gfile.PutBytes(IpDbPath, putData.ReadAll()) } - cBuff := gfile.GetBytes(dbPath) + cBuff := gfile.GetBytes(IpDbPath) /* var cBuff []byte if gres.Contains(dbPath) { @@ -78,7 +81,9 @@ func (s *sIp2region) Load() { func (s *sIp2region) GetIp(ip string) (res []string) { //初始化加载 - s.Load() + if s.searcher != nil { + s.Load() + } res = make([]string, 5) if s.searcher == nil { diff --git a/package/pay/chongchong/chongchong.go b/package/pay/chongchong/chongchong.go new file mode 100644 index 0000000..6a50031 --- /dev/null +++ b/package/pay/chongchong/chongchong.go @@ -0,0 +1,32 @@ +package chongchong + +import ( + "fmt" + "github.com/ayflying/utility_go/package/pay/common" + "github.com/gogf/gf/v2/crypto/gmd5" +) + +//验单 +func (p *Pay) Verify(req *CallbackData, sign string) (isOk bool, err error) { + //req := g.RequestFromCtx(ctx).Request + //data, err := common.ParseNotifyToBodyMap(req) + + var data = map[string]interface{}{ + "orderPrice": req.OrderPrice, + "packageId": req.PackageId, + "partnerTransactionNo": req.PartnerTransactionNo, + "productId": req.ProductId, + "statusCode": req.StatusCode, + "transactionNo": req.TransactionNo, + } + + dataStr, err := common.BuildSignStr(data) + + var SingStr = fmt.Sprintf("%v&%v", dataStr, p.ApiKey) + sign2, err := gmd5.EncryptString(SingStr) + + if sign == sign2 { + isOk = true + } + return +} diff --git a/package/pay/chongchong/model.go b/package/pay/chongchong/model.go new file mode 100644 index 0000000..268acb5 --- /dev/null +++ b/package/pay/chongchong/model.go @@ -0,0 +1,25 @@ +package chongchong + +type Pay struct { + ApiKey string `json:"api_key"` +} + +func New(pay *Pay) *Pay { + return &Pay{ + ApiKey: pay.ApiKey, + } +} + +// CallbackData 用于处理回调数据的结构体 +type CallbackData struct { + TransactionNo string `json:"transactionNo" dc:"平台交易单号,唯一标识一笔交易"` + PartnerTransactionNo string `json:"partnerTransactionNo" dc:"合作方交易单号,由合作方生成"` + StatusCode string `json:"statusCode" dc:"交易状态码,SUCCESS表示成功,FAIL表示失败"` + ProductId int `json:"productId" dc:"产品ID,对应后台配置的商品"` + OrderPrice float64 `json:"orderPrice" dc:"订单金额,单位为元"` + PackageId int `json:"packageId" dc:"套餐ID,可选字段,部分商品有套餐区分"` + ProductName string `json:"productName" dc:"产品名称,展示用"` + ExtParam string `json:"extParam" dc:"扩展参数,回调时原样返回"` + UserId int `json:"userId" dc:"用户ID,标识购买者"` + Sign string `json:"sign" dc:"签名,用于验证请求合法性"` +} diff --git a/package/pay/common/func.go b/package/pay/common/func.go index 192b22e..859fa50 100644 --- a/package/pay/common/func.go +++ b/package/pay/common/func.go @@ -1,30 +1,105 @@ package common -import "strings" +import ( + "errors" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/util/gconv" + "net/http" + "sort" + "strings" +) +// FormatPublicKey 将原始公钥字符串格式化为标准PEM格式的公钥 +// 功能:为原始公钥添加PEM头部和尾部,并按64字符长度拆分换行,符合PKCS#8标准格式要求 +// 参数 publicKey: 原始未格式化的公钥字符串(通常为Base64编码且无换行) +// 返回值: 格式化后的PEM格式公钥字符串 func FormatPublicKey(publicKey string) (pKey string) { var buffer strings.Builder + // 写入PEM格式头部 buffer.WriteString("-----BEGIN PUBLIC KEY-----\n") + + // 定义每行公钥的标准长度(PEM格式要求64字符/行) rawLen := 64 keyLen := len(publicKey) + // 计算需要拆分的总行数(向上取整) raws := keyLen / rawLen temp := keyLen % rawLen if temp > 0 { - raws++ + raws++ // 若有余数则增加一行 } + + // 按行拆分并写入公钥内容 start := 0 end := start + rawLen for i := 0; i < raws; i++ { if i == raws-1 { + // 最后一行取剩余所有字符(处理不足64字符的情况) buffer.WriteString(publicKey[start:]) } else { + // 非最后行取固定64字符 buffer.WriteString(publicKey[start:end]) } - buffer.WriteByte('\n') + buffer.WriteByte('\n') // 每行结束添加换行符 start += rawLen end = start + rawLen } + + // 写入PEM格式尾部 buffer.WriteString("-----END PUBLIC KEY-----\n") pKey = buffer.String() return } + +// ParseNotifyToBodyMap 将HTTP请求中的表单数据解析为键值对映射 +// 功能:解析请求表单数据,提取单值字段并转换为map[string]interface{}格式 +// 参数 req: 包含表单数据的HTTP请求对象 +// 返回值: 解析后的键值对映射(bm)和可能的错误(err) +func ParseNotifyToBodyMap(req *http.Request) (bm map[string]interface{}, err error) { + // 解析请求表单数据,若失败则返回错误 + if err = req.ParseForm(); err != nil { + return nil, err + } + // 获取解析后的表单数据(key为字段名,value为字符串切片形式的字段值) + var form map[string][]string = req.Form + // 初始化结果映射,预分配容量(表单字段数+1,预留扩展空间) + bm = make(map[string]interface{}, len(form)+1) + // 遍历表单字段,仅保留单值字段(忽略多值字段) + for k, v := range form { + if len(v) == 1 { + bm[k] = v[0] + } + } + return +} + +// BuildSignStr 根据传入的g.Map构建签名字符串 +// 规则:对所有非空值的键进行字母排序后,按"key=value&"格式拼接,最后去除末尾的"&" +// 参数 bm: 包含键值对的g.Map +// 返回值: 构建好的签名字符串和可能的错误 +func BuildSignStr(bm g.Map) (string, error) { + var ( + buf strings.Builder + keyList []string + ) + // 收集所有键名 + for k := range bm { + keyList = append(keyList, k) + } + // 对键名进行字母排序 + sort.Strings(keyList) + // 遍历排序后的键,拼接非空值的键值对 + for _, k := range keyList { + if v := bm[k]; v != "" { + buf.WriteString(k) + buf.WriteByte('=') + buf.WriteString(gconv.String(v)) + buf.WriteByte('&') + // 去除末尾多余的'&'字符 + // 检查是否有有效的键值对被拼接 + } + } + if buf.Len() <= 0 { + return "", errors.New("length is error") + } + return buf.String()[:buf.Len()-1], nil +} diff --git a/package/pay/honor/honor.go b/package/pay/honor/honor.go index 2738a5e..f55cf37 100644 --- a/package/pay/honor/honor.go +++ b/package/pay/honor/honor.go @@ -1,6 +1,7 @@ package honor import ( + "context" "crypto" "crypto/rsa" "crypto/sha256" @@ -28,7 +29,11 @@ func New(pay *Pay) *Pay { // sign: 签名的Base64编码字符串 // pubKey: PEM格式的公钥字符串 // 返回验证结果和可能的错误 -func (p *Pay) VerifyRSASignature(data []byte, sign string) (bool, error) { +func (p *Pay) VerifyRSASignature(ctx context.Context, data []byte, sign string) (bool, error) { + //req := g.RequestFromCtx(ctx).Request + //post, err := common.ParseNotifyToBodyMap(req) + //var data = gjson.MustEncode(post) + // 解码Base64格式的签名 signBytes, err := base64.StdEncoding.DecodeString(sign) if err != nil {