Compare commits

...

2 Commits

Author SHA1 Message Date
ayflying
d54de73e11 增加虫虫助手支付 2025-07-31 19:11:19 +08:00
ayflying
61d69159ea 下载ip库地址修改 2025-07-29 12:23:08 +08:00
6 changed files with 154 additions and 11 deletions

View File

@@ -13,7 +13,8 @@ var (
) )
func Boot() (err error) { func Boot() (err error) {
err = service.SystemCron().StartCron() // 启动计划任务定时器预防debug工具激活计划任务造成重复执行此处不执行计划任务
//err = service.SystemCron().StartCron()
//用户活动持久化 //用户活动持久化
service.SystemCron().AddCronV2(v1.CronType_DAILY, func(ctx context.Context) error { service.SystemCron().AddCronV2(v1.CronType_DAILY, func(ctx context.Context) error {

View File

@@ -15,6 +15,8 @@ var (
wait = false wait = false
) )
const IpDbPath = "runtime/library/ip2region.xdb"
type sIp2region struct { type sIp2region struct {
searcher *xdb.Searcher searcher *xdb.Searcher
} }
@@ -35,16 +37,17 @@ func init() {
// Load 加载到内存中 // Load 加载到内存中
// //
// @Description: 加载ip2region数据库到内存中。 // @Description: 加载ip2region数据库到内存中。
// @receiver s *sIp2region: sIp2region的实例。 // @receiver s *sIp2region: sIp2region的实例。
func (s *sIp2region) Load() { func (s *sIp2region) Load() {
var err error 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/main/attachment/ip2region.xdb" var url = "https://github.com/ayflying/resource/raw/refs/heads/master/attachment/ip2region.xdb"
if wait { if wait {
return return
} }
if gfile.IsEmpty(dbPath) { if gfile.IsEmpty(IpDbPath) {
wait = true wait = true
defer func() { defer func() {
wait = false wait = false
@@ -55,9 +58,9 @@ func (s *sIp2region) Load() {
if err2 != nil { if err2 != nil {
return return
} }
err = gfile.PutBytes(dbPath, putData.ReadAll()) err = gfile.PutBytes(IpDbPath, putData.ReadAll())
} }
cBuff := gfile.GetBytes(dbPath) cBuff := gfile.GetBytes(IpDbPath)
/* /*
var cBuff []byte var cBuff []byte
if gres.Contains(dbPath) { if gres.Contains(dbPath) {
@@ -78,7 +81,9 @@ func (s *sIp2region) Load() {
func (s *sIp2region) GetIp(ip string) (res []string) { func (s *sIp2region) GetIp(ip string) (res []string) {
//初始化加载 //初始化加载
if s.searcher != nil {
s.Load() s.Load()
}
res = make([]string, 5) res = make([]string, 5)
if s.searcher == nil { if s.searcher == nil {

View File

@@ -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
}

View File

@@ -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:"签名,用于验证请求合法性"`
}

View File

@@ -1,30 +1,105 @@
package common 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) { func FormatPublicKey(publicKey string) (pKey string) {
var buffer strings.Builder var buffer strings.Builder
// 写入PEM格式头部
buffer.WriteString("-----BEGIN PUBLIC KEY-----\n") buffer.WriteString("-----BEGIN PUBLIC KEY-----\n")
// 定义每行公钥的标准长度PEM格式要求64字符/行)
rawLen := 64 rawLen := 64
keyLen := len(publicKey) keyLen := len(publicKey)
// 计算需要拆分的总行数(向上取整)
raws := keyLen / rawLen raws := keyLen / rawLen
temp := keyLen % rawLen temp := keyLen % rawLen
if temp > 0 { if temp > 0 {
raws++ raws++ // 若有余数则增加一行
} }
// 按行拆分并写入公钥内容
start := 0 start := 0
end := start + rawLen end := start + rawLen
for i := 0; i < raws; i++ { for i := 0; i < raws; i++ {
if i == raws-1 { if i == raws-1 {
// 最后一行取剩余所有字符处理不足64字符的情况
buffer.WriteString(publicKey[start:]) buffer.WriteString(publicKey[start:])
} else { } else {
// 非最后行取固定64字符
buffer.WriteString(publicKey[start:end]) buffer.WriteString(publicKey[start:end])
} }
buffer.WriteByte('\n') buffer.WriteByte('\n') // 每行结束添加换行符
start += rawLen start += rawLen
end = start + rawLen end = start + rawLen
} }
// 写入PEM格式尾部
buffer.WriteString("-----END PUBLIC KEY-----\n") buffer.WriteString("-----END PUBLIC KEY-----\n")
pKey = buffer.String() pKey = buffer.String()
return 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
}

View File

@@ -1,6 +1,7 @@
package honor package honor
import ( import (
"context"
"crypto" "crypto"
"crypto/rsa" "crypto/rsa"
"crypto/sha256" "crypto/sha256"
@@ -28,7 +29,11 @@ func New(pay *Pay) *Pay {
// sign: 签名的Base64编码字符串 // sign: 签名的Base64编码字符串
// pubKey: PEM格式的公钥字符串 // 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格式的签名 // 解码Base64格式的签名
signBytes, err := base64.StdEncoding.DecodeString(sign) signBytes, err := base64.StdEncoding.DecodeString(sign)
if err != nil { if err != nil {