重构代码结构与认证体系,集成异步任务消费者

This commit is contained in:
2025-11-17 18:38:10 +08:00
parent a97c970166
commit a245229bc2
70 changed files with 2000 additions and 2334 deletions

View File

@@ -1,6 +1,7 @@
package globals
import (
"fmt"
"platform/pkg/env"
"github.com/smartwalle/alipay/v3"
@@ -8,25 +9,26 @@ import (
var Alipay *alipay.Client
func initAlipay() {
func initAlipay() error {
var client, err = alipay.New(
env.AlipayAppId,
env.AlipayAppPrivateKey,
env.AlipayProduction,
)
if err != nil {
panic("初始化支付宝客户端失败: " + err.Error())
return fmt.Errorf("初始化支付宝客户端失败: %w", err)
}
err = client.LoadAliPayPublicKey(env.AlipayPublicKey)
if err != nil {
panic("加载支付宝公钥失败: " + err.Error())
return fmt.Errorf("加载支付宝公钥失败: %w", err)
}
err = client.SetEncryptKey(env.AlipayApiCert)
if err != nil {
panic("设置支付宝加密密钥失败: " + err.Error())
return fmt.Errorf("设置支付宝加密证书失败: %w", err)
}
Alipay = client
return nil
}

View File

@@ -1,6 +1,7 @@
package globals
import (
"fmt"
"platform/pkg/env"
"platform/pkg/u"
@@ -14,17 +15,18 @@ type aliyunClient struct {
Sms *sms.Client
}
func initAliyun() {
func initAliyun() error {
client, err := sms.NewClient(&openapi.Config{
AccessKeyId: &env.AliyunAccessKey,
AccessKeySecret: &env.AliyunAccessKeySecret,
Endpoint: u.P("dysmsapi.aliyuncs.com"),
})
if err != nil {
panic(err)
return fmt.Errorf("初始化阿里云客户端失败: %w", err)
}
Aliyun = &aliyunClient{
Sms: client,
}
return nil
}

View File

@@ -35,10 +35,11 @@ type cloud struct {
var Cloud CloudClient
func initBaiyin() {
func initBaiyin() error {
Cloud = &cloud{
url: env.BaiyinAddr,
}
return nil
}
type AutoConfig struct {

View File

@@ -1,14 +1,38 @@
package globals
func Init() {
initBaiyin()
initAlipay()
initWechatPay()
initAliyun()
initValidator()
initRedis()
initOrm()
initProxy()
initAsynq()
initSft()
import (
"context"
"platform/pkg/u"
)
func Init(ctx context.Context) error {
errs := make([]error, 0)
errs = append(errs, initBaiyin())
errs = append(errs, initAlipay())
errs = append(errs, initWechatPay())
errs = append(errs, initAliyun())
errs = append(errs, initValidator())
errs = append(errs, initRedis())
errs = append(errs, initOrm())
errs = append(errs, initProxy())
errs = append(errs, initSft())
return u.CombineErrors(errs)
}
func Stop() error {
var errs = make([]error, 0)
err := stopRedis()
if err != nil {
errs = append(errs, err)
}
err = stopOrm()
if err != nil {
errs = append(errs, err)
}
return u.CombineErrors(errs)
}

View File

@@ -1,17 +1,20 @@
package globals
import (
"database/sql"
"fmt"
"platform/pkg/env"
"platform/web/queries"
"gorm.io/driver/postgres"
"gorm.io/gorm"
"gorm.io/gorm/schema"
"log/slog"
"platform/pkg/env"
)
var DB *gorm.DB
var Conn *sql.DB
func initOrm() {
func initOrm() error {
// 连接数据库
dsn := fmt.Sprintf(
@@ -25,27 +28,29 @@ func initOrm() {
},
})
if err != nil {
slog.Error("gorm 初始化数据库失败:", slog.Any("err", err))
panic(err)
return fmt.Errorf("连接数据库失败: %w", err)
}
// 连接池
conn, err := db.DB()
if err != nil {
slog.Error("gorm 初始化数据库失败:", slog.Any("err", err))
panic(err)
return fmt.Errorf("配置连接池失败: %w", err)
}
conn.SetMaxIdleConns(10)
conn.SetMaxOpenConns(100)
queries.SetDefault(db)
DB = db
Conn = conn
return nil
}
func ExitOrm() error {
func stopOrm() error {
if DB != nil {
conn, err := DB.DB()
if err != nil {
return err
return fmt.Errorf("关闭数据库连接失败: %w", err)
}
return conn.Close()
}

View File

@@ -23,8 +23,9 @@ var Proxy *ProxyClient
type ProxyClient struct {
}
func initProxy() {
func initProxy() error {
Proxy = &ProxyClient{}
return nil
}
type ProxyPermitConfig struct {

View File

@@ -1,12 +1,13 @@
package globals
import (
"github.com/go-redsync/redsync/v4/redis/goredis/v9"
"log/slog"
"net"
"platform/pkg/env"
"platform/web/core"
"github.com/go-redsync/redsync/v4/redis/goredis/v9"
"github.com/go-redsync/redsync/v4"
"github.com/redis/go-redis/v9"
)
@@ -18,11 +19,10 @@ type ExtendRedSync struct {
*redsync.Redsync
}
func initRedis() {
func initRedis() error {
client := redis.NewClient(&redis.Options{
Addr: net.JoinHostPort(env.RedisHost, env.RedisPort),
DB: env.RedisDb,
Password: env.RedisPass,
Password: env.RedisPassword,
})
pool := goredis.NewPool(client)
@@ -30,9 +30,11 @@ func initRedis() {
Redis = client
Redsync = &ExtendRedSync{sync}
return nil
}
func ExitRedis() error {
func stopRedis() error {
if Redis != nil {
return Redis.Close()
}

View File

@@ -28,9 +28,9 @@ type SftClient struct {
publicKey *rsa.PublicKey
}
func initSft() {
func initSft() error {
if !env.SftPayEnable {
panic("商福通支付未启用,请检查环境变量 SFTPAY_ENABLE")
return fmt.Errorf("商福通支付未启用,请检查环境变量 SFTPAY_ENABLE")
}
SFTPay = SftClient{
@@ -41,7 +41,7 @@ func initSft() {
// 加载私钥
private, err := base64.StdEncoding.DecodeString(env.SftPayAppPrivateKey)
if err != nil {
panic("解析商福通私钥失败: " + err.Error())
return fmt.Errorf("解析商福通私钥失败: %w", err)
}
var privateKey *rsa.PrivateKey
@@ -49,13 +49,13 @@ func initSft() {
if err != nil {
pkcs8, err := x509.ParsePKCS8PrivateKey(private)
if err != nil {
panic("解析商福通私钥失败: " + err.Error())
return fmt.Errorf("解析商福通私钥失败: %w", err)
}
var ok bool
privateKey, ok = pkcs8.(*rsa.PrivateKey)
if !ok {
panic("解析商福通私钥失败")
return fmt.Errorf("解析商福通私钥失败")
}
}
SFTPay.privateKey = privateKey
@@ -63,35 +63,36 @@ func initSft() {
// 加载公钥
public, err := base64.StdEncoding.DecodeString(env.SftPayPublicKey)
if err != nil {
panic("解析商福通公钥失败: " + err.Error())
return fmt.Errorf("解析商福通公钥失败: %w", err)
}
var publicKey *rsa.PublicKey
pkix, err := x509.ParsePKIXPublicKey(public)
if err != nil {
panic("解析商福通公钥失败: " + err.Error())
return fmt.Errorf("解析商福通公钥失败: %w", err)
}
var ok bool
publicKey, ok = pkix.(*rsa.PublicKey)
if !ok {
panic("解析商福通公钥失败")
return fmt.Errorf("解析商福通公钥失败")
}
SFTPay.publicKey = publicKey
return nil
}
func (s *SftClient) PaymentScanPay(req *PaymentScanPayReq) (*PaymentScanPayResp, error) {
const url = "https://pay.rscygroup.com/api/open/payment/scanpay"
req.ReturnUrl = env.SftReturnUrl
req.NotifyUrl = env.SftNotifyUrl
req.ReturnUrl = u.X(env.SftReturnUrl)
req.NotifyUrl = u.X(env.SftNotifyUrl)
req.RouteNo = u.P(s.routeId)
return call[PaymentScanPayResp](s, url, req)
}
func (s *SftClient) PaymentH5Pay(req *PaymentH5PayReq) (*PaymentH5PayResp, error) {
const url = "https://pay.rscygroup.com/api/open/payment/h5pay"
req.ReturnUrl = env.SftReturnUrl
req.NotifyUrl = env.SftNotifyUrl
req.ReturnUrl = u.X(env.SftReturnUrl)
req.NotifyUrl = u.X(env.SftNotifyUrl)
req.RouteNo = u.P(s.routeId)
return call[PaymentH5PayResp](s, url, req)
}
@@ -256,7 +257,7 @@ func call[T any](s *SftClient, url string, req any) (*T, error) {
encode, err := s.sign(req)
if err != nil {
return nil, fmt.Errorf("加密请求内容失败%w", err)
return nil, fmt.Errorf("加密请求内容失败: %w", err)
}
bytes, err := json.Marshal(encode)
@@ -266,33 +267,33 @@ func call[T any](s *SftClient, url string, req any) (*T, error) {
request, err := http.NewRequest("POST", url, strings.NewReader(string(bytes)))
if err != nil {
return nil, fmt.Errorf("创建请求失败%w", err)
return nil, fmt.Errorf("创建请求失败: %w", err)
}
request.Header.Set("Content-Type", "application/json")
if env.DebugHttpDump == true {
reqDump, err := httputil.DumpRequest(request, true)
if err != nil {
return nil, fmt.Errorf("请求内容转储失败%w", err)
return nil, fmt.Errorf("请求内容转储失败: %w", err)
}
println(string(reqDump) + "\n\n")
}
response, err := http.DefaultClient.Do(request)
if err != nil {
return nil, fmt.Errorf("请求失败%w", err)
return nil, fmt.Errorf("请求失败: %w", err)
}
if env.DebugHttpDump == true {
respDump, err := httputil.DumpResponse(response, true)
if err != nil {
return nil, fmt.Errorf("响应内容转储失败%w", err)
return nil, fmt.Errorf("响应内容转储失败: %w", err)
}
println(string(respDump) + "\n\n")
}
if response.StatusCode != http.StatusOK {
return nil, fmt.Errorf("请求响应失败%d", response.StatusCode)
return nil, fmt.Errorf("请求响应失败: %d", response.StatusCode)
}
defer func(body io.ReadCloser) {
_ = body.Close()
@@ -300,18 +301,18 @@ func call[T any](s *SftClient, url string, req any) (*T, error) {
body, err := io.ReadAll(response.Body)
if err != nil {
return nil, fmt.Errorf("读取响应内容失败%w", err)
return nil, fmt.Errorf("读取响应内容失败: %w", err)
}
decode, err := s.verify(body)
if err != nil {
return nil, fmt.Errorf("解密响应内容失败%w", err)
return nil, fmt.Errorf("解密响应内容失败: %w", err)
}
var resp = new(T)
err = json.Unmarshal([]byte(decode), resp)
if err != nil {
return nil, fmt.Errorf("响应正文解析失败%w", err)
return nil, fmt.Errorf("响应正文解析失败: %w", err)
}
return resp, nil
@@ -321,7 +322,7 @@ func (s *SftClient) sign(msg any) (*request, error) {
bytes, err := json.Marshal(msg)
if err != nil {
return nil, fmt.Errorf("格式化加密正文失败%w", err)
return nil, fmt.Errorf("格式化加密正文失败: %w", err)
}
if env.DebugHttpDump {
@@ -341,7 +342,7 @@ func (s *SftClient) sign(msg any) (*request, error) {
hashed := sha256.Sum256([]byte(body.String()))
signature, err := rsa.SignPKCS1v15(nil, s.privateKey, crypto.SHA256, hashed[:])
if err != nil {
return nil, fmt.Errorf("签名失败%w", err)
return nil, fmt.Errorf("签名失败: %w", err)
}
body.Sign = base64.StdEncoding.EncodeToString(signature)
@@ -353,11 +354,11 @@ func (s *SftClient) verify(str []byte) (string, error) {
var resp = new(response)
err := json.Unmarshal(str, resp)
if err != nil {
return "", fmt.Errorf("解析响应正文失败%w", err)
return "", fmt.Errorf("解析响应正文失败: %w", err)
}
if resp.Code != "000000" {
return "", fmt.Errorf("请求业务响应失败%s", u.Z(resp.Msg))
return "", fmt.Errorf("请求业务响应失败: %s", u.Z(resp.Msg))
}
if resp.Sign == nil {
@@ -371,13 +372,13 @@ func (s *SftClient) verify(str []byte) (string, error) {
ser, err := resp.String()
if err != nil {
return "", fmt.Errorf("格式化响应内容失败%w", err)
return "", fmt.Errorf("格式化响应内容失败: %w", err)
}
hashed := sha256.Sum256([]byte(ser))
err = rsa.VerifyPKCS1v15(s.publicKey, crypto.SHA256, hashed[:], sign)
if err != nil {
return "", fmt.Errorf("验签失败%w", err)
return "", fmt.Errorf("验签失败: %w", err)
}
return *resp.BizData, nil
@@ -412,7 +413,7 @@ type response struct {
func (r response) String() (string, error) {
if r.BizData == nil || r.Msg == nil || r.SignType == nil {
return "", core.NewServErr(fmt.Sprintf(
"上游数据返回有空值BizData %vMsg %v, SignType %v",
"上游数据返回有空值: BizData %vMsg %v, SignType %v",
r.BizData == nil, r.Msg == nil, r.SignType == nil,
))
}

View File

@@ -2,12 +2,14 @@ package globals
import (
"errors"
"fmt"
"strings"
"github.com/go-playground/locales/zh"
ut "github.com/go-playground/universal-translator"
"github.com/go-playground/validator/v10"
zhtrans "github.com/go-playground/validator/v10/translations/zh"
"github.com/gofiber/fiber/v2"
"strings"
)
var Validator *ValidatorClient
@@ -38,17 +40,18 @@ func (v *ValidatorClient) Validate(c *fiber.Ctx, data any) error {
return nil
}
func initValidator() {
func initValidator() error {
var validate = validator.New(validator.WithRequiredStructEnabled())
var translator = ut.New(zh.New()).GetFallback()
err := zhtrans.RegisterDefaultTranslations(validate, translator)
if err != nil {
panic(err)
return fmt.Errorf("初始化验证器失败: %w", err)
}
Validator = &ValidatorClient{
validator: validate,
translator: translator,
}
return nil
}

View File

@@ -3,6 +3,7 @@ package globals
import (
"context"
"encoding/base64"
"fmt"
"platform/pkg/env"
"github.com/wechatpay-apiv3/wechatpay-go/core"
@@ -20,28 +21,28 @@ type WechatPayClient struct {
Notify *notify.Handler
}
func initWechatPay() {
func initWechatPay() error {
// 加载商户私钥
private, err := base64.StdEncoding.DecodeString(env.WechatPayMchPrivateKey)
if err != nil {
panic(err)
return fmt.Errorf("加载微信支付商户私钥失败: %w", err)
}
appPrivateKey, err := utils.LoadPrivateKey(string(private))
if err != nil {
panic(err)
return fmt.Errorf("解析微信支付商户私钥失败: %w", err)
}
// 加载微信支付公钥
public, err := base64.StdEncoding.DecodeString(env.WechatPayPublicKey)
if err != nil {
panic(err)
return fmt.Errorf("加载微信支付公钥失败: %w", err)
}
wechatPublicKey, err := utils.LoadPublicKey(string(public))
if err != nil {
panic(err)
return fmt.Errorf("解析微信支付公钥失败: %w", err)
}
// 创建 WechatPay 客户端
@@ -55,7 +56,7 @@ func initWechatPay() {
),
)
if err != nil {
panic(err)
return fmt.Errorf("创建微信支付客户端失败: %w", err)
}
// 创建 WechatPay 通知处理器
@@ -64,7 +65,7 @@ func initWechatPay() {
*wechatPublicKey,
))
if err != nil {
panic(err)
return fmt.Errorf("创建微信支付通知处理器失败: %w", err)
}
// 创建 WechatPay 服务
@@ -72,4 +73,5 @@ func initWechatPay() {
Native: &native.NativeApiService{Client: client},
Notify: handler,
}
return nil
}