diff --git a/go.mod b/go.mod index 04daadd..3e110be 100644 --- a/go.mod +++ b/go.mod @@ -44,7 +44,7 @@ require ( go.opentelemetry.io/otel/sdk v1.29.0 go.opentelemetry.io/otel/trace v1.29.0 go.uber.org/zap v1.27.0 - golang.org/x/crypto v0.32.0 + golang.org/x/crypto v0.35.0 golang.org/x/oauth2 v0.25.0 golang.org/x/time v0.6.0 gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df @@ -138,8 +138,8 @@ require ( golang.org/x/arch v0.13.0 // indirect golang.org/x/exp v0.0.0-20240525044651-4c93da0ed11d // indirect golang.org/x/net v0.34.0 // indirect - golang.org/x/sys v0.29.0 // indirect - golang.org/x/text v0.21.0 // indirect + golang.org/x/sys v0.30.0 // indirect + golang.org/x/text v0.22.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20240513163218-0867130af1f8 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240513163218-0867130af1f8 // indirect gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect diff --git a/go.sum b/go.sum index 9e9f2c5..6ab9d74 100644 --- a/go.sum +++ b/go.sum @@ -402,8 +402,8 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= -golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= -golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= +golang.org/x/crypto v0.35.0 h1:b15kiHdrGCHrP6LvwaQ3c03kgNhhiMgvlhxHQhmg2Xs= +golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20240525044651-4c93da0ed11d h1:N0hmiNbwsSNwHBAvR3QB5w25pUwH4tK0Y/RltD1j1h4= golang.org/x/exp v0.0.0-20240525044651-4c93da0ed11d/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc= @@ -463,8 +463,8 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= -golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= +golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= @@ -478,8 +478,8 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= -golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= +golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= +golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U= golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= diff --git a/initialize/migrate/database/02115_ads.up.sql b/initialize/migrate/database/02115_ads.up.sql index 341bc84..39cfaf2 100644 --- a/initialize/migrate/database/02115_ads.up.sql +++ b/initialize/migrate/database/02115_ads.up.sql @@ -1,2 +1,20 @@ -ALTER TABLE `ads` - ADD COLUMN `description` VARCHAR(255) DEFAULT '' COMMENT 'Description'; +-- 只有当 ads 表中不存在 description 字段时才添加 +SET +@col_exists := ( + SELECT COUNT(*) + FROM INFORMATION_SCHEMA.COLUMNS + WHERE TABLE_SCHEMA = DATABASE() + AND TABLE_NAME = 'ads' + AND COLUMN_NAME = 'description' +); + +SET +@query := IF( + @col_exists = 0, + 'ALTER TABLE `ads` ADD COLUMN `description` VARCHAR(255) DEFAULT '''' COMMENT ''Description'';', + 'SELECT "Column `description` already exists"' +); + +PREPARE stmt FROM @query; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; diff --git a/initialize/migrate/database/02116_user_algo.down.sql b/initialize/migrate/database/02116_user_algo.down.sql new file mode 100644 index 0000000..41644c2 --- /dev/null +++ b/initialize/migrate/database/02116_user_algo.down.sql @@ -0,0 +1,3 @@ +ALTER TABLE `user` +DROP COLUMN `algo`, + DROP COLUMN `salt`; diff --git a/initialize/migrate/database/02116_user_algo.up.sql b/initialize/migrate/database/02116_user_algo.up.sql new file mode 100644 index 0000000..4a79ef2 --- /dev/null +++ b/initialize/migrate/database/02116_user_algo.up.sql @@ -0,0 +1,35 @@ +-- 添加 algo 列(如果不存在) +SET @dbname = DATABASE(); +SET @tablename = 'user'; +SET @colname = 'algo'; +SET @sql = ( + SELECT IF( + COUNT(*) = 0, + 'ALTER TABLE `user` ADD COLUMN `algo` VARCHAR(20) NOT NULL DEFAULT ''default'' COMMENT ''Encryption Algorithm'' AFTER `password`;', + 'SELECT "Column `algo` already exists";' + ) + FROM information_schema.COLUMNS + WHERE TABLE_SCHEMA = @dbname + AND TABLE_NAME = @tablename + AND COLUMN_NAME = @colname +); +PREPARE stmt FROM @sql; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + +-- 添加 salt 列(如果不存在) +SET @colname = 'salt'; +SET @sql = ( + SELECT IF( + COUNT(*) = 0, + 'ALTER TABLE `user` ADD COLUMN `salt` VARCHAR(20) NOT NULL DEFAULT ''default'' COMMENT ''Password Salt'' AFTER `algo`;', + 'SELECT "Column `salt` already exists";' + ) + FROM information_schema.COLUMNS + WHERE TABLE_SCHEMA = @dbname + AND TABLE_NAME = @tablename + AND COLUMN_NAME = @colname +); +PREPARE stmt FROM @sql; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; diff --git a/initialize/migrate/database/02117_site_custom_data.down.sql b/initialize/migrate/database/02117_site_custom_data.down.sql new file mode 100644 index 0000000..c8581e8 --- /dev/null +++ b/initialize/migrate/database/02117_site_custom_data.down.sql @@ -0,0 +1,7 @@ +INSERT INTO `system` (`category`, `key`, `value`, `type`, `desc`, `created_at`, `updated_at`) +SELECT 'site', 'CustomData', '{ + "kr_website_id": "" +}', 'string', 'Custom Data', '2025-04-22 14:25:16.637', '2025-10-14 15:47:19.187' + WHERE NOT EXISTS ( + SELECT 1 FROM `system` WHERE `category` = 'site' AND `key` = 'CustomData' +); diff --git a/initialize/migrate/database/02117_site_custom_data.up.sql b/initialize/migrate/database/02117_site_custom_data.up.sql new file mode 100644 index 0000000..c8581e8 --- /dev/null +++ b/initialize/migrate/database/02117_site_custom_data.up.sql @@ -0,0 +1,7 @@ +INSERT INTO `system` (`category`, `key`, `value`, `type`, `desc`, `created_at`, `updated_at`) +SELECT 'site', 'CustomData', '{ + "kr_website_id": "" +}', 'string', 'Custom Data', '2025-04-22 14:25:16.637', '2025-10-14 15:47:19.187' + WHERE NOT EXISTS ( + SELECT 1 FROM `system` WHERE `category` = 'site' AND `key` = 'CustomData' +); diff --git a/initialize/migrate/database/02118_traffic_log_idx.down.sql b/initialize/migrate/database/02118_traffic_log_idx.down.sql new file mode 100644 index 0000000..25598dd --- /dev/null +++ b/initialize/migrate/database/02118_traffic_log_idx.down.sql @@ -0,0 +1 @@ +ALTER TABLE traffic_log DROP INDEX idx_timestamp; diff --git a/initialize/migrate/database/02118_traffic_log_idx.up.sql b/initialize/migrate/database/02118_traffic_log_idx.up.sql new file mode 100644 index 0000000..cdd308f --- /dev/null +++ b/initialize/migrate/database/02118_traffic_log_idx.up.sql @@ -0,0 +1 @@ +ALTER TABLE traffic_log ADD INDEX idx_timestamp (timestamp); diff --git a/internal/logic/admin/user/createUserLogic.go b/internal/logic/admin/user/createUserLogic.go index 5f6c858..0fb6b43 100644 --- a/internal/logic/admin/user/createUserLogic.go +++ b/internal/logic/admin/user/createUserLogic.go @@ -40,6 +40,7 @@ func (l *CreateUserLogic) CreateUser(req *types.CreateUserRequest) error { pwd := tool.EncodePassWord(req.Password) newUser := &user.User{ Password: pwd, + Algo: "default", ReferralPercentage: req.ReferralPercentage, OnlyFirstPurchase: &req.OnlyFirstPurchase, ReferCode: req.ReferCode, diff --git a/internal/logic/admin/user/updateUserBasicInfoLogic.go b/internal/logic/admin/user/updateUserBasicInfoLogic.go index 9f57f75..faa7930 100644 --- a/internal/logic/admin/user/updateUserBasicInfoLogic.go +++ b/internal/logic/admin/user/updateUserBasicInfoLogic.go @@ -129,6 +129,7 @@ func (l *UpdateUserBasicInfoLogic) UpdateUserBasicInfo(req *types.UpdateUserBasi return errors.Wrapf(xerr.NewErrCodeMsg(503, "Demo mode does not allow modification of the admin user password"), "UpdateUserBasicInfo failed: cannot update admin user password in demo mode") } userInfo.Password = tool.EncodePassWord(req.Password) + userInfo.Algo = "default" } err = l.svcCtx.UserModel.Update(l.ctx, userInfo) diff --git a/internal/logic/auth/resetPasswordLogic.go b/internal/logic/auth/resetPasswordLogic.go index c4e2ece..aef245a 100644 --- a/internal/logic/auth/resetPasswordLogic.go +++ b/internal/logic/auth/resetPasswordLogic.go @@ -104,7 +104,8 @@ func (l *ResetPasswordLogic) ResetPassword(req *types.ResetPasswordRequest) (res // Update password userInfo.Password = tool.EncodePassWord(req.Password) - if err := l.svcCtx.UserModel.Update(l.ctx, userInfo); err != nil { + userInfo.Algo = "default" + if err = l.svcCtx.UserModel.Update(l.ctx, userInfo); err != nil { return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseUpdateError), "update user info failed: %v", err.Error()) } diff --git a/internal/logic/auth/telephoneLoginLogic.go b/internal/logic/auth/telephoneLoginLogic.go index 737157f..8a54ff5 100644 --- a/internal/logic/auth/telephoneLoginLogic.go +++ b/internal/logic/auth/telephoneLoginLogic.go @@ -98,7 +98,7 @@ func (l *TelephoneLoginLogic) TelephoneLogin(req *types.TelephoneLoginRequest, r if req.TelephoneCode == "" { // Verify password - if !tool.VerifyPassWord(req.Password, userInfo.Password) { + if !tool.MultiPasswordVerify(userInfo.Algo, userInfo.Salt, req.Password, userInfo.Password) { return nil, errors.Wrapf(xerr.NewErrCode(xerr.UserPasswordError), "user password") } } else { diff --git a/internal/logic/auth/telephoneResetPasswordLogic.go b/internal/logic/auth/telephoneResetPasswordLogic.go index f119c55..5cb47cc 100644 --- a/internal/logic/auth/telephoneResetPasswordLogic.go +++ b/internal/logic/auth/telephoneResetPasswordLogic.go @@ -78,6 +78,7 @@ func (l *TelephoneResetPasswordLogic) TelephoneResetPassword(req *types.Telephon // Generate password pwd := tool.EncodePassWord(req.Password) userInfo.Password = pwd + userInfo.Algo = "default" err = l.svcCtx.UserModel.Update(l.ctx, userInfo) if err != nil { return nil, errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "update user password failed: %v", err.Error()) diff --git a/internal/logic/auth/telephoneUserRegisterLogic.go b/internal/logic/auth/telephoneUserRegisterLogic.go index aa2dde9..af16811 100644 --- a/internal/logic/auth/telephoneUserRegisterLogic.go +++ b/internal/logic/auth/telephoneUserRegisterLogic.go @@ -107,6 +107,7 @@ func (l *TelephoneUserRegisterLogic) TelephoneUserRegister(req *types.TelephoneR pwd := tool.EncodePassWord(req.Password) userInfo := &user.User{ Password: pwd, + Algo: "default", OnlyFirstPurchase: &l.svcCtx.Config.Invite.OnlyFirstPurchase, AuthMethods: []user.AuthMethods{ { diff --git a/internal/logic/auth/userLoginLogic.go b/internal/logic/auth/userLoginLogic.go index 3062e63..deecaae 100644 --- a/internal/logic/auth/userLoginLogic.go +++ b/internal/logic/auth/userLoginLogic.go @@ -77,7 +77,7 @@ func (l *UserLoginLogic) UserLogin(req *types.UserLoginRequest) (resp *types.Log } // Verify password - if !tool.VerifyPassWord(req.Password, userInfo.Password) { + if !tool.MultiPasswordVerify(userInfo.Algo, userInfo.Salt, req.Password, userInfo.Password) { return nil, errors.Wrapf(xerr.NewErrCode(xerr.UserPasswordError), "user password") } diff --git a/internal/logic/auth/userRegisterLogic.go b/internal/logic/auth/userRegisterLogic.go index 0b9da43..cf959a9 100644 --- a/internal/logic/auth/userRegisterLogic.go +++ b/internal/logic/auth/userRegisterLogic.go @@ -90,6 +90,7 @@ func (l *UserRegisterLogic) UserRegister(req *types.UserRegisterRequest) (resp * pwd := tool.EncodePassWord(req.Password) userInfo := &user.User{ Password: pwd, + Algo: "default", OnlyFirstPurchase: &l.svcCtx.Config.Invite.OnlyFirstPurchase, } if referer != nil { diff --git a/internal/logic/notify/ePayNotifyLogic.go b/internal/logic/notify/ePayNotifyLogic.go index 8def591..efdd127 100644 --- a/internal/logic/notify/ePayNotifyLogic.go +++ b/internal/logic/notify/ePayNotifyLogic.go @@ -57,7 +57,7 @@ func (l *EPayNotifyLogic) EPayNotify(req *types.EPayNotifyRequest) error { return err } // Verify sign - client := epay.NewClient(config.Pid, config.Url, config.Key) + client := epay.NewClient(config.Pid, config.Url, config.Key, config.Type) if !client.VerifySign(urlParamsToMap(l.ctx.Request.URL.RawQuery)) && !l.svcCtx.Config.Debug { l.Logger.Error("[EPayNotify] Verify sign failed") return nil diff --git a/internal/logic/public/portal/purchaseCheckoutLogic.go b/internal/logic/public/portal/purchaseCheckoutLogic.go index f72ed4f..f016a49 100644 --- a/internal/logic/public/portal/purchaseCheckoutLogic.go +++ b/internal/logic/public/portal/purchaseCheckoutLogic.go @@ -267,7 +267,7 @@ func (l *PurchaseCheckoutLogic) epayPayment(config *payment.Payment, info *order return "", errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "Unmarshal error: %s", err.Error()) } // Initialize EPay client with merchant credentials - client := epay.NewClient(epayConfig.Pid, epayConfig.Url, epayConfig.Key) + client := epay.NewClient(epayConfig.Pid, epayConfig.Url, epayConfig.Key, epayConfig.Type) // Convert order amount to CNY using current exchange rate amount, err := l.queryExchangeRate("CNY", info.Amount) @@ -309,7 +309,7 @@ func (l *PurchaseCheckoutLogic) CryptoSaaSPayment(config *payment.Payment, info return "", errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "Unmarshal error: %s", err.Error()) } // Initialize EPay client with merchant credentials - client := epay.NewClient(epayConfig.AccountID, epayConfig.Endpoint, epayConfig.SecretKey) + client := epay.NewClient(epayConfig.AccountID, epayConfig.Endpoint, epayConfig.SecretKey, epayConfig.Type) // Convert order amount to CNY using current exchange rate amount, err := l.queryExchangeRate("CNY", info.Amount) @@ -347,6 +347,11 @@ func (l *PurchaseCheckoutLogic) queryExchangeRate(to string, src int64) (amount // Convert cents to decimal amount amount = float64(src) / float64(100) + if l.svcCtx.ExchangeRate != 0 && to == "CNY" { + amount = amount * l.svcCtx.ExchangeRate + return amount, nil + } + // Retrieve system currency configuration currency, err := l.svcCtx.SystemModel.GetCurrencyConfig(l.ctx) if err != nil { diff --git a/internal/logic/public/portal/purchaseLogic.go b/internal/logic/public/portal/purchaseLogic.go index 2c0cd69..322f94c 100644 --- a/internal/logic/public/portal/purchaseLogic.go +++ b/internal/logic/public/portal/purchaseLogic.go @@ -83,6 +83,12 @@ func (l *PurchaseLogic) Purchase(req *types.PortalPurchaseRequest) (resp *types. if couponInfo.Count != 0 && couponInfo.Count <= couponInfo.UsedCount { return nil, errors.Wrapf(xerr.NewErrCode(xerr.CouponInsufficientUsage), "coupon used") } + // Check expiration time + expireTime := time.Unix(couponInfo.ExpireTime, 0) + if time.Now().After(expireTime) { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.CouponExpired), "coupon expired") + } + couponSub := tool.StringToInt64Slice(couponInfo.Subscribe) if len(couponSub) > 0 && !tool.Contains(couponSub, req.SubscribeId) { return nil, errors.Wrapf(xerr.NewErrCode(xerr.CouponNotApplicable), "coupon not match") diff --git a/internal/model/payment/payment.go b/internal/model/payment/payment.go index 46ee0a0..ad2f046 100644 --- a/internal/model/payment/payment.go +++ b/internal/model/payment/payment.go @@ -85,9 +85,10 @@ func (l *AlipayF2FConfig) Unmarshal(data []byte) error { } type EPayConfig struct { - Pid string `json:"pid"` - Url string `json:"url"` - Key string `json:"key"` + Pid string `json:"pid"` + Url string `json:"url"` + Key string `json:"key"` + Type string `json:"type"` } func (l *EPayConfig) Marshal() ([]byte, error) { @@ -109,6 +110,7 @@ type CryptoSaaSConfig struct { Endpoint string `json:"endpoint"` AccountID string `json:"account_id"` SecretKey string `json:"secret_key"` + Type string `json:"type"` } func (l *CryptoSaaSConfig) Marshal() ([]byte, error) { diff --git a/internal/model/user/user.go b/internal/model/user/user.go index 603f98e..98bfbcb 100644 --- a/internal/model/user/user.go +++ b/internal/model/user/user.go @@ -7,6 +7,8 @@ import ( type User struct { Id int64 `gorm:"primaryKey"` Password string `gorm:"type:varchar(100);not null;comment:User Password"` + Algo string `gorm:"type:varchar(20);default:'default';comment:Encryption Algorithm"` + Salt string `gorm:"type:varchar(20);default:null;comment:Password Salt"` Avatar string `gorm:"type:MEDIUMTEXT;comment:User Avatar"` Balance int64 `gorm:"default:0;comment:User Balance"` // User Balance Amount ReferCode string `gorm:"type:varchar(20);default:'';comment:Referral Code"` diff --git a/internal/svc/serviceContext.go b/internal/svc/serviceContext.go index 184dc69..be05079 100644 --- a/internal/svc/serviceContext.go +++ b/internal/svc/serviceContext.go @@ -32,10 +32,12 @@ import ( ) type ServiceContext struct { - DB *gorm.DB - Redis *redis.Client - Config config.Config - Queue *asynq.Client + DB *gorm.DB + Redis *redis.Client + Config config.Config + Queue *asynq.Client + ExchangeRate float64 + //NodeCache *cache.NodeCacheClient AuthModel auth.Model AdsModel ads.Model @@ -82,10 +84,11 @@ func NewServiceContext(c config.Config) *ServiceContext { } authLimiter := limit.NewPeriodLimit(86400, 15, rds, config.SendCountLimitKeyPrefix, limit.Align()) srv := &ServiceContext{ - DB: db, - Redis: rds, - Config: c, - Queue: NewAsynqClient(c), + DB: db, + Redis: rds, + Config: c, + Queue: NewAsynqClient(c), + ExchangeRate: 1.0, //NodeCache: cache.NewNodeCacheClient(rds), AuthLimiter: authLimiter, AdsModel: ads.NewModel(db, rds), diff --git a/pkg/payment/epay/epay.go b/pkg/payment/epay/epay.go index ae1315e..8933d9f 100644 --- a/pkg/payment/epay/epay.go +++ b/pkg/payment/epay/epay.go @@ -14,9 +14,10 @@ import ( ) type Client struct { - Pid string - Url string - Key string + Pid string + Url string + Key string + Type string } type Order struct { @@ -37,11 +38,12 @@ type queryOrderStatusResponse struct { Status int `json:"status"` } -func NewClient(pid, url, key string) *Client { +func NewClient(pid, url, key string, Type string) *Client { return &Client{ - Pid: pid, - Url: url, - Key: key, + Pid: pid, + Url: url, + Key: key, + Type: Type, } } @@ -53,6 +55,7 @@ func (c *Client) CreatePayUrl(order Order) string { params.Set("notify_url", order.NotifyUrl) params.Set("out_trade_no", order.OrderNo) params.Set("pid", c.Pid) + params.Set("type", c.Type) params.Set("return_url", order.ReturnUrl) // Generate the sign using the CreateSign function @@ -117,6 +120,7 @@ func (c *Client) structToMap(order Order) map[string]string { result["notify_url"] = order.NotifyUrl result["out_trade_no"] = order.OrderNo result["pid"] = c.Pid + result["type"] = c.Type result["return_url"] = order.ReturnUrl return result } diff --git a/pkg/payment/epay/epay_test.go b/pkg/payment/epay/epay_test.go index a3c6884..87265e6 100644 --- a/pkg/payment/epay/epay_test.go +++ b/pkg/payment/epay/epay_test.go @@ -3,7 +3,7 @@ package epay import "testing" func TestEpay(t *testing.T) { - client := NewClient("", "http://127.0.0.1", "") + client := NewClient("", "http://127.0.0.1", "", "") order := Order{ Name: "测试", OrderNo: "123456789", @@ -19,7 +19,7 @@ func TestEpay(t *testing.T) { func TestQueryOrderStatus(t *testing.T) { t.Skipf("Skip TestQueryOrderStatus test") - client := NewClient("Pid", "Url", "Key") + client := NewClient("Pid", "Url", "Key", "Type") orderNo := "123456789" status := client.QueryOrderStatus(orderNo) t.Logf("OrderNo: %s, Status: %v\n", orderNo, status) @@ -40,7 +40,7 @@ func TestVerifySign(t *testing.T) { } key := "LbTabbB580zWyhXhyyww7wwvy5u8k0wl" - c := NewClient("Pid", "Url", key) + c := NewClient("Pid", "Url", key, "Type") if c.VerifySign(params) { t.Logf("Sign verification success!") } else { diff --git a/pkg/payment/platform.go b/pkg/payment/platform.go index 42b8815..7ad12ea 100644 --- a/pkg/payment/platform.go +++ b/pkg/payment/platform.go @@ -65,9 +65,10 @@ func GetSupportedPlatforms() []types.PlatformInfo { Platform: EPay.String(), PlatformUrl: "", PlatformFieldDescription: map[string]string{ - "pid": "PID", - "url": "URL", - "key": "Key", + "pid": "PID", + "url": "URL", + "key": "Key", + "type": "Type", }, }, { diff --git a/pkg/tool/encryption.go b/pkg/tool/encryption.go index e4f205e..f51f61a 100644 --- a/pkg/tool/encryption.go +++ b/pkg/tool/encryption.go @@ -2,12 +2,14 @@ package tool import ( "crypto/md5" + "crypto/sha256" "crypto/sha512" "encoding/hex" "fmt" "strings" "github.com/anaskhan96/go-password-encoder" + "golang.org/x/crypto/bcrypt" ) var options = &password.Options{SaltLen: 16, Iterations: 100, KeyLen: 32, HashFunction: sha512.New} @@ -32,3 +34,24 @@ func Md5Encode(str string, isUpper bool) string { } return res } + +func MultiPasswordVerify(algo, salt, password, hash string) bool { + switch algo { + case "md5": + sum := md5.Sum([]byte(password)) + return hex.EncodeToString(sum[:]) == hash + case "sha256": + sum := sha256.Sum256([]byte(password)) + return hex.EncodeToString(sum[:]) == hash + case "md5salt": + sum := md5.Sum([]byte(password + salt)) + return hex.EncodeToString(sum[:]) == hash + case "default": // PPanel's default algorithm + return VerifyPassWord(password, hash) + case "bcrypt": + // Bcrypt (corresponding to PHP's password_hash/password_verify) + err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password)) + return err == nil + } + return false +} diff --git a/pkg/tool/encryption_test.go b/pkg/tool/encryption_test.go index 8841072..8e420cf 100644 --- a/pkg/tool/encryption_test.go +++ b/pkg/tool/encryption_test.go @@ -1,7 +1,15 @@ package tool -import "testing" +import ( + "testing" +) func TestEncodePassWord(t *testing.T) { t.Logf("EncodePassWord: %v", EncodePassWord("password")) } + +func TestMultiPasswordVerify(t *testing.T) { + pwd := "$2y$10$WFO17pdtohfeBILjEChoGeVxpDG.u9kVCKhjDAeEeNmCjIlj3tDRy" + status := MultiPasswordVerify("bcrypt", "", "admin1", pwd) + t.Logf("MultiPasswordVerify: %v", status) +} diff --git a/pkg/xerr/errCode.go b/pkg/xerr/errCode.go index b708ac4..c9377c4 100644 --- a/pkg/xerr/errCode.go +++ b/pkg/xerr/errCode.go @@ -57,6 +57,7 @@ const ( CouponAlreadyUsed uint32 = 50002 // Coupon has already been used CouponNotApplicable uint32 = 50003 // Coupon does not match the order or conditions CouponInsufficientUsage uint32 = 50004 // Coupon has insufficient remaining uses + CouponExpired uint32 = 50005 // Coupon is expired ) // Subscribe diff --git a/pkg/xerr/errMsg.go b/pkg/xerr/errMsg.go index f4f73a3..f688854 100644 --- a/pkg/xerr/errMsg.go +++ b/pkg/xerr/errMsg.go @@ -46,6 +46,7 @@ func init() { CouponAlreadyUsed: "Coupon has already been used", CouponNotApplicable: "Coupon does not match the order or conditions", CouponInsufficientUsage: "Coupon has insufficient remaining uses", + CouponExpired: "Coupon is expired", // Subscribe SubscribeExpired: "Subscribe is expired", diff --git a/queue/handler/routes.go b/queue/handler/routes.go index edf2293..2e96219 100644 --- a/queue/handler/routes.go +++ b/queue/handler/routes.go @@ -3,7 +3,6 @@ package handler import ( "github.com/hibiken/asynq" "github.com/perfect-panel/server/internal/svc" - countrylogic "github.com/perfect-panel/server/queue/logic/country" orderLogic "github.com/perfect-panel/server/queue/logic/order" smslogic "github.com/perfect-panel/server/queue/logic/sms" "github.com/perfect-panel/server/queue/logic/subscription" @@ -15,8 +14,6 @@ import ( ) func RegisterHandlers(mux *asynq.ServeMux, serverCtx *svc.ServiceContext) { - // get country task - mux.Handle(types.ForthwithGetCountry, countrylogic.NewGetNodeCountryLogic(serverCtx)) // Send email task mux.Handle(types.ForthwithSendEmail, emailLogic.NewSendEmailLogic(serverCtx)) // Send sms task diff --git a/queue/logic/country/getCountryLogic.go b/queue/logic/country/getCountryLogic.go deleted file mode 100644 index 75e0f6f..0000000 --- a/queue/logic/country/getCountryLogic.go +++ /dev/null @@ -1,22 +0,0 @@ -package countrylogic - -import ( - "context" - - "github.com/hibiken/asynq" - "github.com/perfect-panel/server/internal/svc" -) - -type GetNodeCountryLogic struct { - svcCtx *svc.ServiceContext -} - -func NewGetNodeCountryLogic(svcCtx *svc.ServiceContext) *GetNodeCountryLogic { - return &GetNodeCountryLogic{ - svcCtx: svcCtx, - } -} -func (l *GetNodeCountryLogic) ProcessTask(ctx context.Context, task *asynq.Task) error { - - return nil -} diff --git a/queue/logic/order/activateOrderLogic.go b/queue/logic/order/activateOrderLogic.go index f6c8f9c..55dc284 100644 --- a/queue/logic/order/activateOrderLogic.go +++ b/queue/logic/order/activateOrderLogic.go @@ -223,6 +223,7 @@ func (l *ActivateOrderLogic) createGuestUser(ctx context.Context, orderInfo *ord userInfo := &user.User{ Password: tool.EncodePassWord(tempOrder.Password), + Algo: "default", AuthMethods: []user.AuthMethods{ { AuthType: tempOrder.AuthType, diff --git a/queue/logic/task/rateLogic.go b/queue/logic/task/rateLogic.go new file mode 100644 index 0000000..2e33fae --- /dev/null +++ b/queue/logic/task/rateLogic.go @@ -0,0 +1,52 @@ +package task + +import ( + "context" + + "github.com/hibiken/asynq" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/pkg/exchangeRate" + "github.com/perfect-panel/server/pkg/logger" + "github.com/perfect-panel/server/pkg/tool" +) + +type RateLogic struct { + svcCtx *svc.ServiceContext +} + +func NewRateLogic(svcCtx *svc.ServiceContext) *RateLogic { + return &RateLogic{ + svcCtx: svcCtx, + } +} + +func (l *RateLogic) ProcessTask(ctx context.Context, _ *asynq.Task) error { + // Retrieve system currency configuration + currency, err := l.svcCtx.SystemModel.GetCurrencyConfig(ctx) + if err != nil { + logger.Errorw("[PurchaseCheckout] GetCurrencyConfig error", logger.Field("error", err.Error())) + return err + } + // Parse currency configuration + configs := struct { + CurrencyUnit string + CurrencySymbol string + AccessKey string + }{} + tool.SystemConfigSliceReflectToStruct(currency, &configs) + + // Skip conversion if no exchange rate API key configured + if configs.AccessKey == "" { + logger.Debugf("[RateLogic] skip exchange rate, no access key configured") + return nil + } + // Update exchange rates + result, err := exchangeRate.GetExchangeRete(configs.CurrencyUnit, "CNY", configs.AccessKey, 1) + if err != nil { + logger.Errorw("[RateLogic] GetExchangeRete error", logger.Field("error", err.Error())) + return err + } + l.svcCtx.ExchangeRate = result + logger.WithContext(ctx).Infof("[RateLogic] GetExchangeRete success, result: %+v", result) + return nil +} diff --git a/queue/logic/traffic/trafficStatLogic.go b/queue/logic/traffic/trafficStatLogic.go index 81a422b..e5357a8 100644 --- a/queue/logic/traffic/trafficStatLogic.go +++ b/queue/logic/traffic/trafficStatLogic.go @@ -167,7 +167,7 @@ func (l *StatLogic) ProcessTask(ctx context.Context, _ *asynq.Task) error { // Delete old traffic logs if l.svc.Config.Log.AutoClear { - err = tx.WithContext(ctx).Model(&traffic.TrafficLog{}).Where("created_at <= ?", end.AddDate(0, 0, int(-l.svc.Config.Log.ClearDays))).Delete(&traffic.TrafficLog{}).Error + err = tx.WithContext(ctx).Model(&traffic.TrafficLog{}).Where("timestamp <= ?", end.AddDate(0, 0, int(-l.svc.Config.Log.ClearDays))).Delete(&traffic.TrafficLog{}).Error if err != nil { logger.Errorf("[Traffic Stat Queue] Delete server traffic log failed: %v", err.Error()) } diff --git a/queue/types/country.go b/queue/types/country.go deleted file mode 100644 index 13b9e0c..0000000 --- a/queue/types/country.go +++ /dev/null @@ -1,11 +0,0 @@ -package types - -const ( - // ForthwithGetCountry forthwith country get - ForthwithGetCountry = "forthwith:country:get" -) - -type GetNodeCountry struct { - Protocol string `json:"protocol"` - ServerAddr string `json:"server_addr"` -} diff --git a/queue/types/task.go b/queue/types/task.go index 0115f28..85da331 100644 --- a/queue/types/task.go +++ b/queue/types/task.go @@ -6,4 +6,7 @@ const ( // ForthwithQuotaTask create quota task immediately ForthwithQuotaTask = "forthwith:quota:task" + + // SchedulerExchangeRate fetch exchange rate task + SchedulerExchangeRate = "scheduler:exchange:rate" ) diff --git a/scheduler/scheduler.go b/scheduler/scheduler.go index 377dca3..bf131e8 100644 --- a/scheduler/scheduler.go +++ b/scheduler/scheduler.go @@ -46,6 +46,12 @@ func (m *Service) Start() { logger.Errorf("register traffic stat task failed: %s", err.Error()) } + // schedule update exchange rate task: every day at 01:00 + rateTask := asynq.NewTask(types.ForthwithQuotaTask, nil) + if _, err := m.server.Register("0 1 * * *", rateTask, asynq.MaxRetry(3)); err != nil { + logger.Errorf("register update exchange rate task failed: %s", err.Error()) + } + if err := m.server.Run(); err != nil { logger.Errorf("run scheduler failed: %s", err.Error()) }