1
	
		
			
	
		
	
	
		
	
		
			Some checks failed
		
		
	
	
		
			
				
	
				Build / build (push) Has been cancelled
				
			
		
			
				
	
				CodeQL / Analyze (go) (push) Has been cancelled
				
			
		
			
				
	
				build / Build (push) Has been cancelled
				
			
		
			
				
	
				GitHub Actions Mirror / mirror_to_gitee (push) Has been cancelled
				
			
		
			
				
	
				GitHub Actions Mirror / mirror_to_gitlab (push) Has been cancelled
				
			
		
			
				
	
				Issue Close Require / issue-close-require (push) Has been cancelled
				
			
		
		
	
	
				
					
				
			
		
			Some checks failed
		
		
	
	Build / build (push) Has been cancelled
				
			CodeQL / Analyze (go) (push) Has been cancelled
				
			build / Build (push) Has been cancelled
				
			GitHub Actions Mirror / mirror_to_gitee (push) Has been cancelled
				
			GitHub Actions Mirror / mirror_to_gitlab (push) Has been cancelled
				
			Issue Close Require / issue-close-require (push) Has been cancelled
				
			This commit is contained in:
		
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @ -21,3 +21,4 @@ cmd/migrate/migration/version-local/* | |||||||
| # go sum | # go sum | ||||||
| go.sum | go.sum | ||||||
| config/settings.deva.yml | config/settings.deva.yml | ||||||
|  | /aggregate_translate_server | ||||||
|  | |||||||
										
											Binary file not shown.
										
									
								
							| @ -222,3 +222,29 @@ func (e TmMemberPlatform) GetStatistic(c *gin.Context) { | |||||||
|  |  | ||||||
| 	e.OK(data, "获取数据成功") | 	e.OK(data, "获取数据成功") | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // 修改用户-翻译通道字符消耗 | ||||||
|  | func (e TmMemberPlatform) ChangeChars(c *gin.Context) { | ||||||
|  | 	s := service.TmMemberPlatform{} | ||||||
|  | 	req := dto.TmMemberPlatformChangeCharsReq{} | ||||||
|  | 	err := e.MakeContext(c). | ||||||
|  | 		MakeOrm(). | ||||||
|  | 		Bind(&req). | ||||||
|  | 		MakeService(&s.Service). | ||||||
|  | 		Errors | ||||||
|  | 	if err != nil { | ||||||
|  | 		e.Logger.Error(err) | ||||||
|  | 		e.Error(500, err, err.Error()) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	req.SetUpdateBy(user.GetUserId(c)) | ||||||
|  | 	p := actions.GetPermissionFromContext(c) | ||||||
|  |  | ||||||
|  | 	err = s.ChangeChars(&req, p) | ||||||
|  | 	if err != nil { | ||||||
|  | 		e.Error(500, err, fmt.Sprintf("修改用户-翻译通道字符消耗失败,\r\n失败信息 %s", err.Error())) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	e.OK(nil, "修改成功") | ||||||
|  | } | ||||||
|  | |||||||
							
								
								
									
										131
									
								
								app/admin/apis/tm_recharge_log.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										131
									
								
								app/admin/apis/tm_recharge_log.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,131 @@ | |||||||
|  | package apis | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"go-admin/app/admin/service" | ||||||
|  | 	"go-admin/app/admin/service/dto" | ||||||
|  | 	"go-admin/common/actions" | ||||||
|  | 	"go-admin/common/statuscode" | ||||||
|  |  | ||||||
|  | 	"github.com/gin-gonic/gin" | ||||||
|  | 	"github.com/gin-gonic/gin/binding" | ||||||
|  | 	"github.com/go-admin-team/go-admin-core/sdk/api" | ||||||
|  | 	"github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth/user" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | type TmRechargeLog struct { | ||||||
|  | 	api.Api | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // GetPage 分页查询 | ||||||
|  | func (e TmRechargeLog) GetPage(c *gin.Context) { | ||||||
|  | 	s := service.TmRechargeLog{} | ||||||
|  | 	req := dto.TmRechargeLogGetPageReq{} | ||||||
|  | 	err := e.MakeContext(c). | ||||||
|  | 		MakeOrm(). | ||||||
|  | 		Bind(&req). | ||||||
|  | 		MakeService(&s.Service). | ||||||
|  | 		Errors | ||||||
|  |  | ||||||
|  | 	if err != nil { | ||||||
|  | 		e.Error(500, err, "") | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	p := actions.GetPermissionFromContext(c) | ||||||
|  | 	datas := make([]dto.TmRechargeLogResp, 0) | ||||||
|  | 	var count int64 | ||||||
|  |  | ||||||
|  | 	err = s.GetPage(&req, p, &datas, &count) | ||||||
|  |  | ||||||
|  | 	if err != nil { | ||||||
|  | 		e.Error(500, err, "") | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	e.PageOK(datas, int(count), req.GetPageIndex(), req.GetPageSize(), "") | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // 创建充值订单 | ||||||
|  | func (e TmRechargeLog) CreateOrder(c *gin.Context) { | ||||||
|  | 	req := dto.TmRechargeCreateOrderReq{} | ||||||
|  | 	s := service.TmRechargeLog{} | ||||||
|  | 	err := e.MakeContext(c). | ||||||
|  | 		MakeOrm(). | ||||||
|  | 		Bind(&req). | ||||||
|  | 		MakeService(&s.Service). | ||||||
|  | 		Errors | ||||||
|  |  | ||||||
|  | 	if err != nil { | ||||||
|  | 		e.Error(500, nil, statuscode.ErrorMessage[statuscode.ServerError]) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	apiKey := c.GetString("apiKey") | ||||||
|  |  | ||||||
|  | 	code := s.CreateOrder(&req, apiKey) | ||||||
|  | 	if code != statuscode.Success { | ||||||
|  | 		e.OK(code, statuscode.ErrorMessage[code]) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	e.OK(nil, "success") | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // 后台充值 | ||||||
|  | func (e TmRechargeLog) ManagerRecharge(c *gin.Context) { | ||||||
|  | 	req := dto.TmRechargeCreateOrderReq{} | ||||||
|  | 	s := service.TmRechargeLog{} | ||||||
|  | 	err := e.MakeContext(c). | ||||||
|  | 		MakeOrm(). | ||||||
|  | 		Bind(&req). | ||||||
|  | 		MakeService(&s.Service). | ||||||
|  | 		Errors | ||||||
|  |  | ||||||
|  | 	if err != nil { | ||||||
|  | 		e.Error(500, err, "") | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if err := req.Validate(); err != nil { | ||||||
|  | 		e.Error(500, err, "") | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	req.SetCreateBy(user.GetUserId(c)) | ||||||
|  | 	p := actions.GetPermissionFromContext(c) | ||||||
|  |  | ||||||
|  | 	err = s.ManagerRecharge(&req, p) | ||||||
|  | 	if err != nil { | ||||||
|  | 		e.Error(500, err, "") | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	e.OK(nil, "充值成功") | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // 获取即将过期充值记录 | ||||||
|  | func (e TmMember) GetMemberAdvent(c *gin.Context) { | ||||||
|  | 	s := service.TmRechargeLog{} | ||||||
|  | 	req := dto.TmRechargeLogFrontReq{} | ||||||
|  | 	err := e.MakeContext(c). | ||||||
|  | 		MakeOrm(). | ||||||
|  | 		Bind(&req, binding.Query, binding.Form). | ||||||
|  | 		MakeService(&s.Service). | ||||||
|  | 		Errors | ||||||
|  |  | ||||||
|  | 	if err != nil { | ||||||
|  | 		e.Logger.Errorf("获取即将过期充值记录失败:", err) | ||||||
|  | 		e.Error(statuscode.ServerError, nil, statuscode.ErrorMessage[statuscode.ServerError]) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	userId := user.GetUserId(c) | ||||||
|  | 	datas := []dto.TmRechargeLogFrontResp{} | ||||||
|  |  | ||||||
|  | 	code := s.GetMemberAdvent(&req, &datas, userId) | ||||||
|  | 	if code != statuscode.Success { | ||||||
|  | 		e.Error(code, nil, statuscode.ErrorMessage[code]) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	e.OK(datas, "success") | ||||||
|  | } | ||||||
| @ -42,7 +42,7 @@ func (e *Translate) Translate(c *gin.Context) { | |||||||
|  |  | ||||||
| 	if code != statuscode.Success { | 	if code != statuscode.Success { | ||||||
| 		e.Logger.Error(err) | 		e.Logger.Error(err) | ||||||
| 		e.Error(code, nil, statuscode.ErrorMessage[code]) | 		e.OK(code, statuscode.ErrorMessage[code]) | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | |||||||
| @ -10,6 +10,7 @@ type TmMemberPlatform struct { | |||||||
| 	MemberId           int    `json:"memberId" gorm:"type:bigint;comment:用户id"` | 	MemberId           int    `json:"memberId" gorm:"type:bigint;comment:用户id"` | ||||||
| 	RemainingCharacter int    `json:"remainingCharacter" gorm:"type:bigint;comment:剩余字符数"` | 	RemainingCharacter int    `json:"remainingCharacter" gorm:"type:bigint;comment:剩余字符数"` | ||||||
| 	TotalCharacter     int    `json:"totalCharacter" gorm:"type:bigint;comment:总字符数"` | 	TotalCharacter     int    `json:"totalCharacter" gorm:"type:bigint;comment:总字符数"` | ||||||
|  | 	UseCharacter       int    `json:"useCharacter" gorm:"type:bigint;comment:已用字符数"` | ||||||
| 	PlatformId         int    `json:"platformId" gorm:"type:bigint;comment:平台id"` | 	PlatformId         int    `json:"platformId" gorm:"type:bigint;comment:平台id"` | ||||||
| 	PlatformKey        string `json:"platformKey" gorm:"type:varchar(50);comment:平台key"` | 	PlatformKey        string `json:"platformKey" gorm:"type:varchar(50);comment:平台key"` | ||||||
| 	Status             int    `json:"status" gorm:"type:tinyint;comment:状态 1-启用 2-禁用"` | 	Status             int    `json:"status" gorm:"type:tinyint;comment:状态 1-启用 2-禁用"` | ||||||
|  | |||||||
| @ -1,14 +1,28 @@ | |||||||
| package models | package models | ||||||
|  |  | ||||||
| import "go-admin/common/models" | import ( | ||||||
|  | 	"go-admin/common/models" | ||||||
|  | 	"time" | ||||||
|  |  | ||||||
|  | 	"github.com/shopspring/decimal" | ||||||
|  | ) | ||||||
|  |  | ||||||
| type TmRechargeLog struct { | type TmRechargeLog struct { | ||||||
| 	models.Model | 	models.Model | ||||||
|  |  | ||||||
| 	UserId     int `json:"userId" gorm:"column:user_id;type:bigint;not null;comment:用户id"` | 	Type           int             `json:"type" gorm:"column:type;type:tinyint;not null;comment:类型 1-充值 2-后台充值 3-赠送"` | ||||||
| 	MemberId   int `json:"memberId" gorm:"column:member_id;type:bigint;not null;comment:翻译用户id"` | 	OrderNo        string          `json:"orderNo" gorm:"column:order_no;type:varchar(36);not null;comment:订单号"` | ||||||
| 	Status     int `json:"status" gorm:"column:status;type:tinyint;not null;comment:状态 1-正常 2-作废"` | 	UserId         int             `json:"userId" gorm:"column:user_id;type:bigint;not null;comment:用户id"` | ||||||
| 	TotalChars int `json:"totalChars" gorm:"column:total_chars;type:bigint;not null;comment:充值字符数"` | 	MemberId       int             `json:"memberId" gorm:"column:member_id;type:bigint;not null;comment:翻译用户id"` | ||||||
|  | 	PlatformId     int             `json:"platformId" gorm:"column:platform_id;type:bigint;not null;comment:平台id"` | ||||||
|  | 	Status         int             `json:"status" gorm:"column:status;type:tinyint;not null;comment:状态 1-待支付 2-已支付 3-已取消 4-申请退款 5-已退款 6-已过期"` | ||||||
|  | 	TotalChars     int             `json:"totalChars" gorm:"column:total_chars;type:bigint;not null;comment:充值字符数"` | ||||||
|  | 	TxHash         string          `json:"txHash" gorm:"column:tx_hash;type:varchar(64);comment:交易hash"` | ||||||
|  | 	Amount         decimal.Decimal `json:"amount" gorm:"column:amount;type:decimal(10,6);not null;comment:充值金额U"` | ||||||
|  | 	ReceiveChannel string          `json:"receiveChannel" gorm:"column:receive_channel;type:varchar(100);not null;comment:充值渠道"` | ||||||
|  | 	ReceiveAddress string          `json:"receiveAddress" gorm:"column:receive_address;type:varchar(100);not null;comment:充值地址"` | ||||||
|  | 	PayTime        *time.Time      `json:"payTime" gorm:"column:pay_time;type:datetime;comment:支付时间"` | ||||||
|  | 	ExpireAt       time.Time       `json:"expireAt" gorm:"column:expire_at;type:datetime;not null;comment:过期时间"` | ||||||
| 	models.ModelTime | 	models.ModelTime | ||||||
| 	models.ControlBy | 	models.ControlBy | ||||||
| } | } | ||||||
|  | |||||||
| @ -16,6 +16,7 @@ func init() { | |||||||
| // registerTmMemberRouter | // registerTmMemberRouter | ||||||
| func registerTmMemberRouter(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddleware) { | func registerTmMemberRouter(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddleware) { | ||||||
| 	api := apis.TmMember{} | 	api := apis.TmMember{} | ||||||
|  | 	rechargeApi := apis.TmRechargeLog{} | ||||||
| 	r := v1.Group("/tm-member").Use(authMiddleware.MiddlewareFunc()).Use(middleware.AuthCheckRole()) | 	r := v1.Group("/tm-member").Use(authMiddleware.MiddlewareFunc()).Use(middleware.AuthCheckRole()) | ||||||
| 	{ | 	{ | ||||||
| 		r.GET("", actions.PermissionAction(), api.GetPage) | 		r.GET("", actions.PermissionAction(), api.GetPage) | ||||||
| @ -24,7 +25,8 @@ func registerTmMemberRouter(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddl | |||||||
| 		r.PUT("/:id", actions.PermissionAction(), api.Update) | 		r.PUT("/:id", actions.PermissionAction(), api.Update) | ||||||
| 		r.DELETE("", api.Delete) | 		r.DELETE("", api.Delete) | ||||||
|  |  | ||||||
| 		r.POST("recharge", actions.PermissionAction(), api.Recharge)  //字符充值 | 		// r.POST("recharge", actions.PermissionAction(), api.Recharge)  //字符充值 | ||||||
|  | 		r.POST("manager-recharge", actions.PermissionAction(), rechargeApi.ManagerRecharge) | ||||||
| 		r.PUT("status", actions.PermissionAction(), api.ChangeStatus) //状态变更 | 		r.PUT("status", actions.PermissionAction(), api.ChangeStatus) //状态变更 | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| @ -32,5 +34,6 @@ func registerTmMemberRouter(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddl | |||||||
| 	{ | 	{ | ||||||
| 		r2.GET("/api-key", api.GetMyApiKey) | 		r2.GET("/api-key", api.GetMyApiKey) | ||||||
| 		r2.GET("platforms", api.GetPlatforms) | 		r2.GET("platforms", api.GetPlatforms) | ||||||
|  | 		r2.GET("member-advent", api.GetMemberAdvent) //获取用户即将过期的充值信息 | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  | |||||||
| @ -23,6 +23,7 @@ func registerTmMemberPlatformRouter(v1 *gin.RouterGroup, authMiddleware *jwt.Gin | |||||||
| 		r.POST("", api.Insert) | 		r.POST("", api.Insert) | ||||||
| 		r.PUT("/:id", actions.PermissionAction(), api.Update) | 		r.PUT("/:id", actions.PermissionAction(), api.Update) | ||||||
| 		r.DELETE("", api.Delete) | 		r.DELETE("", api.Delete) | ||||||
|  | 		r.POST("/change-chars", actions.PermissionAction(), api.ChangeChars) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	f2 := v1.Group("/tm-member-platform").Use(authMiddleware.MiddlewareFunc()) | 	f2 := v1.Group("/tm-member-platform").Use(authMiddleware.MiddlewareFunc()) | ||||||
|  | |||||||
| @ -120,21 +120,24 @@ func (s *TmMemberDeleteReq) GetId() interface{} { | |||||||
| } | } | ||||||
|  |  | ||||||
| type TmMemberResp struct { | type TmMemberResp struct { | ||||||
| 	Id          int                    `json:"id"` | 	Id           int                    `json:"id"` | ||||||
| 	UserId      int                    `json:"userId"` | 	UserId       int                    `json:"userId"` | ||||||
| 	NickName    string                 `json:"nickName"` | 	NickName     string                 `json:"nickName"` | ||||||
| 	UserStatus  int                    `json:"userStatus"` | 	UserStatus   int                    `json:"userStatus"` | ||||||
| 	Status      int                    `json:"status"` | 	Status       int                    `json:"status"` | ||||||
| 	ApiKey      string                 `json:"apiKey"` | 	ApiKey       string                 `json:"apiKey"` | ||||||
| 	TotalChars  int                    `json:"totalChars"` | 	TotalChars   int                    `json:"totalChars"` | ||||||
| 	RemainChars int                    `json:"remainChars"` | 	RemainChars  int                    `json:"remainChars"` | ||||||
| 	CreatedAt   time.Time              `json:"createdAt"` | 	CreatedAt    time.Time              `json:"createdAt"` | ||||||
| 	Platforms   []TmMemberPlatformResp `json:"platforms"` | 	Platforms    []TmMemberPlatformResp `json:"platforms"` | ||||||
|  | 	UsedPlatform []TmMemberPlatformResp `json:"usedPlatform"` | ||||||
| } | } | ||||||
|  |  | ||||||
| type TmMemberPlatformResp struct { | type TmMemberPlatformResp struct { | ||||||
| 	Name        string `json:"name"` | 	PlatformId int    `json:"platformId"` | ||||||
| 	RemainChars int    `json:"remainChars"` | 	MemberId   int    `json:"memberId"` | ||||||
|  | 	Name       string `json:"name"` | ||||||
|  | 	TotalChars int    `json:"totalChars"` | ||||||
| } | } | ||||||
|  |  | ||||||
| type TmMemberRechargeReq struct { | type TmMemberRechargeReq struct { | ||||||
| @ -175,9 +178,11 @@ func (e *TmMemberChangeStatusReq) Validate() error { | |||||||
|  |  | ||||||
| type TmMemberPlatformFrontedResp struct { | type TmMemberPlatformFrontedResp struct { | ||||||
| 	Id          int    `json:"id"` | 	Id          int    `json:"id"` | ||||||
|  | 	PlatformId  int    `json:"platformId"` | ||||||
| 	Name        string `json:"name"` | 	Name        string `json:"name"` | ||||||
| 	RemainChars int    `json:"remainChars"` | 	RemainChars int    `json:"remainChars"` | ||||||
| 	TotalChars  int    `json:"totalChars"` | 	TotalChars  int    `json:"totalChars"` | ||||||
|  | 	UseChars    int    `json:"useChars"` | ||||||
| 	Price       int    `json:"price"` | 	Price       int    `json:"price"` | ||||||
| 	ApiKey      string `json:"apiKey"` | 	ApiKey      string `json:"apiKey"` | ||||||
| } | } | ||||||
|  | |||||||
| @ -99,3 +99,10 @@ type TmMemberDailyUsageDeleteReq struct { | |||||||
| func (s *TmMemberDailyUsageDeleteReq) GetId() interface{} { | func (s *TmMemberDailyUsageDeleteReq) GetId() interface{} { | ||||||
| 	return s.Ids | 	return s.Ids | ||||||
| } | } | ||||||
|  |  | ||||||
|  | type TmMemberPlatformGroupData struct { | ||||||
|  | 	MemberId   int `json:"memberId" comment:"用户id"` | ||||||
|  | 	PlatformId int `json:"platformId" comment:"平台id"` | ||||||
|  | 	// PlatformKey string `json:"platformKey" comment:"平台key"` | ||||||
|  | 	Total int `json:"total" comment:"总使用量"` | ||||||
|  | } | ||||||
|  | |||||||
| @ -109,3 +109,11 @@ type TmMemberPlatformStatisticItemResp struct { | |||||||
| 	PlatformId   int    `json:"platformId"` | 	PlatformId   int    `json:"platformId"` | ||||||
| 	Data         []int  `json:"data" comment:"数据"` | 	Data         []int  `json:"data" comment:"数据"` | ||||||
| } | } | ||||||
|  |  | ||||||
|  | type TmMemberPlatformChangeCharsReq struct { | ||||||
|  | 	Type       int `json:"type" comment:"1-可用字符,2-已使用字符"` | ||||||
|  | 	MemberId   int `json:"memberId" comment:"用户id"` | ||||||
|  | 	PlatformId int `json:"platformId" comment:"平台id"` | ||||||
|  | 	ChangeNum  int `json:"changeNum" comment:"变更数量"` | ||||||
|  | 	common.ControlBy | ||||||
|  | } | ||||||
|  | |||||||
							
								
								
									
										165
									
								
								app/admin/service/dto/tm_recharge_log.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										165
									
								
								app/admin/service/dto/tm_recharge_log.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,165 @@ | |||||||
|  | package dto | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"errors" | ||||||
|  | 	"go-admin/app/admin/models" | ||||||
|  | 	"go-admin/common/dto" | ||||||
|  | 	common "go-admin/common/models" | ||||||
|  | 	"time" | ||||||
|  |  | ||||||
|  | 	"github.com/shopspring/decimal" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | type TmRechargeLogGetPageReq struct { | ||||||
|  | 	dto.Pagination `search:"-"` | ||||||
|  | 	Type           int `form:"type" search:"type:exact;column:type;table:tm_recharge_log"` | ||||||
|  | 	Status         int `form:"status" search:"type:exact;column:status;table:tm_recharge_log"` | ||||||
|  | 	PlatformId     int `form:"platformId" search:"type:exact;column:platform_id;table:tm_recharge_log"` | ||||||
|  | 	TmRechargeLogOrder | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type TmRechargeLogOrder struct { | ||||||
|  | 	Id      string `form:"idOrder"  search:"type:order;column:id;table:tm_platform"` | ||||||
|  | 	PayTime string `form:"payTimeOrder"  search:"type:order;column:pay_time;table:tm_platform"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (m *TmRechargeLogGetPageReq) GetNeedSearch() interface{} { | ||||||
|  | 	return *m | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type TmRechargeLogInsertReq struct { | ||||||
|  | 	Id             int             `json:"-" comment:"平台id"` | ||||||
|  | 	MemberId       int             `json:"memberId" comment:"会员id"` | ||||||
|  | 	PlatformId     int             `json:"platformId" comment:"平台id"` | ||||||
|  | 	Amount         decimal.Decimal `json:"amount" comment:"充值金额"` | ||||||
|  | 	TotalChars     int             `json:"totalChars" comment:"总字数"` | ||||||
|  | 	ReceiveChannel string          `json:"receiveChannel" comment:"充值渠道"` | ||||||
|  | 	Status         int             `json:"status" comment:"充值状态"` | ||||||
|  |  | ||||||
|  | 	common.ControlBy | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (s *TmRechargeLogInsertReq) Generate(model *models.TmPlatform) { | ||||||
|  | 	if s.Id == 0 { | ||||||
|  | 		model.Model = common.Model{Id: s.Id} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	model.CreateBy = s.CreateBy // 添加这而,需要记录是被谁创建的 | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (s *TmRechargeLogInsertReq) GetId() interface{} { | ||||||
|  | 	return s.Id | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type TmRechargeLogListResp struct { | ||||||
|  | 	Id       int             `json:"id"` | ||||||
|  | 	ShowName string          `json:"showName"` | ||||||
|  | 	Code     string          `json:"code"` | ||||||
|  | 	Price    decimal.Decimal `json:"price"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // TmPlatformGetReq 功能获取请求参数 | ||||||
|  | type TmRechargeLogGetReq struct { | ||||||
|  | 	Id int `uri:"id"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (s *TmRechargeLogGetReq) GetId() interface{} { | ||||||
|  | 	return s.Id | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // TmPlatformDeleteReq 功能删除请求参数 | ||||||
|  | type TmRechargeLogDeleteReq struct { | ||||||
|  | 	Ids []int `json:"ids"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (s *TmRechargeLogDeleteReq) GetId() interface{} { | ||||||
|  | 	return s.Ids | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type TmRechargeLogResp struct { | ||||||
|  | 	Id int `json:"id"` | ||||||
|  |  | ||||||
|  | 	OrderNo        string          `json:"orderNo"` | ||||||
|  | 	Type           int             `json:"type"` | ||||||
|  | 	MemberId       int             `json:"memberId"` | ||||||
|  | 	UserId         int             `json:"userId"` | ||||||
|  | 	PlatformId     int             `json:"platformId"` | ||||||
|  | 	Amount         decimal.Decimal `json:"amount"` | ||||||
|  | 	TotalChars     int             `json:"totalChars"` | ||||||
|  | 	ReceiveChannel string          `json:"receiveChannel"` | ||||||
|  | 	Status         int             `json:"status"` | ||||||
|  | 	TxHash         string          `json:"txHash"` | ||||||
|  | 	PayTime        string          `json:"payTime"` | ||||||
|  | 	CreatedAt      string          `json:"createdAt"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // 用户充值订单创建请求参数 | ||||||
|  | type TmRechargeCreateOrderReq struct { | ||||||
|  | 	Type           int             `json:"type" comment:"充值类型 1-用户充值 2-平台充值"` | ||||||
|  | 	MemberId       int             `json:"memberId" comment:"会员id"` | ||||||
|  | 	UserId         int             `json:"userId" comment:"用户id"` | ||||||
|  | 	PlatformId     int             `json:"platformId" comment:"平台id"` | ||||||
|  | 	TotalChars     int             `json:"totalChars" comment:"总字数"` | ||||||
|  | 	ReceiveChannel string          `json:"receiveChannel" comment:"充值渠道"` | ||||||
|  | 	ReceiveAddress string          `json:"receiveAddress" comment:"充值地址"` | ||||||
|  | 	TxHash         string          `json:"txHash" comment:"交易hash"` | ||||||
|  | 	Amount         decimal.Decimal `json:"amount" comment:"充值金额"` | ||||||
|  | 	ExpireDays     int             `json:"expireDays" comment:"过期天数"` | ||||||
|  | 	common.ControlBy | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (e *TmRechargeCreateOrderReq) Validate() error { | ||||||
|  | 	if e.ExpireDays <= 0 { | ||||||
|  | 		return errors.New("过期天数必须大于0") | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if e.MemberId <= 0 { | ||||||
|  | 		return errors.New("会员不存在") | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if e.PlatformId <= 0 { | ||||||
|  | 		return errors.New("平台不存在") | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (e *TmRechargeCreateOrderReq) Generate(model *models.TmRechargeLog) { | ||||||
|  | 	if model == nil { | ||||||
|  | 		model = &models.TmRechargeLog{} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	model.PlatformId = e.PlatformId | ||||||
|  | 	model.MemberId = e.MemberId | ||||||
|  | 	model.UserId = e.UserId | ||||||
|  | 	model.Type = e.Type | ||||||
|  | 	model.TotalChars = e.TotalChars * 10000 | ||||||
|  | 	model.Amount = e.Amount | ||||||
|  | 	model.Status = 1 | ||||||
|  | 	model.ReceiveChannel = e.ReceiveChannel | ||||||
|  | 	model.ReceiveAddress = e.ReceiveAddress | ||||||
|  | 	model.TxHash = e.TxHash | ||||||
|  | 	model.CreateBy = e.CreateBy | ||||||
|  | 	model.CreatedAt = time.Now() | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // 用户充值订单创建参数 | ||||||
|  | type TmRechargeLogInsertOrUpdateReq struct { | ||||||
|  | 	MemberId       int    `json:"memberId" comment:"会员id"` | ||||||
|  | 	PlatformId     int    `json:"platformId" comment:"平台id"` | ||||||
|  | 	PlatformKey    string `json:"platformKey" comment:"平台key"` | ||||||
|  | 	RemainCharater int    `json:"remainChars" comment:"剩余字数"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type TmRechargeLogFrontReq struct { | ||||||
|  | 	PlatformId int `json:"platformId" query:"platformId" form:"platformId" comment:"平台id"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type TmRechargeLogFrontResp struct { | ||||||
|  | 	Id             int    `json:"id"` | ||||||
|  | 	OrderNo        string `json:"orderNo"` | ||||||
|  | 	PlatformName   string `json:"platformName"` | ||||||
|  | 	ExpireUnix     int64  `json:"expireUnix"` | ||||||
|  | 	TotalCharater  int    `json:"totalCharater"` | ||||||
|  | 	RemainCharater int    `json:"remainCharater"` | ||||||
|  | } | ||||||
							
								
								
									
										57
									
								
								app/admin/service/quota_manager/lua/deduct.lua
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										57
									
								
								app/admin/service/quota_manager/lua/deduct.lua
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,57 @@ | |||||||
|  | -- KEYS[1] = tm_member_remain_count:{key}:{platformCode} | ||||||
|  | -- ARGV[1] = 当前时间戳(秒) | ||||||
|  | -- ARGV[2] = 扣减数量 | ||||||
|  |  | ||||||
|  | local zset_key = KEYS[1] | ||||||
|  | local now = tonumber(ARGV[1]) | ||||||
|  | local deduct = tonumber(ARGV[2]) | ||||||
|  | local remain = deduct | ||||||
|  | local result = {} | ||||||
|  |  | ||||||
|  |  | ||||||
|  | -- 获取所有过期的 quota_id | ||||||
|  | local expired_quota_ids = redis.call("ZRANGEBYSCORE", zset_key, "-inf", now) | ||||||
|  | for _, quota_id in ipairs(expired_quota_ids) do | ||||||
|  |     redis.call("DEL", "quota:" .. quota_id) | ||||||
|  | end | ||||||
|  |  | ||||||
|  | -- 清理过期额度 | ||||||
|  | redis.call("ZREMRANGEBYSCORE", zset_key, "-inf", now) | ||||||
|  |  | ||||||
|  | local quota_ids = redis.call("ZRANGEBYSCORE", zset_key, now, "+inf") | ||||||
|  |  | ||||||
|  | for _, quota_id in ipairs(quota_ids) do | ||||||
|  |     local quota_key = "quota:" .. quota_id | ||||||
|  |     local amount = tonumber(redis.call("HGET", quota_key, "amount") or "0") | ||||||
|  |  | ||||||
|  |     if amount > 0 then | ||||||
|  |         local used = 0 | ||||||
|  |         if amount >= remain then | ||||||
|  |             used = remain | ||||||
|  |             redis.call("HINCRBY", quota_key, "amount", -remain) | ||||||
|  |             remain = 0 | ||||||
|  |         else | ||||||
|  |             used = amount | ||||||
|  |             redis.call("HINCRBY", quota_key, "amount", -amount) | ||||||
|  |             remain = remain - amount | ||||||
|  |         end | ||||||
|  |  | ||||||
|  |         table.insert(result, quota_id .. ":" .. used) | ||||||
|  |  | ||||||
|  |         local new_amount = tonumber(redis.call("HGET", quota_key, "amount") or "0") | ||||||
|  |         if new_amount <= 0 then | ||||||
|  |             redis.call("DEL", quota_key) | ||||||
|  |             redis.call("ZREM", zset_key, quota_id) | ||||||
|  |         end | ||||||
|  |  | ||||||
|  |         if remain == 0 then | ||||||
|  |             break | ||||||
|  |         end | ||||||
|  |     end | ||||||
|  | end | ||||||
|  |  | ||||||
|  | if remain > 0 then | ||||||
|  |     return {} | ||||||
|  | end | ||||||
|  |  | ||||||
|  | return result | ||||||
							
								
								
									
										21
									
								
								app/admin/service/quota_manager/lua/refund.lua
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								app/admin/service/quota_manager/lua/refund.lua
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,21 @@ | |||||||
|  | -- KEYS[1] = tm_member_remain_count:{key}:{platformCode} | ||||||
|  | -- ARGV = {quota_id_1, amount_1, expire_1, quota_id_2, amount_2, expire_2, ...} | ||||||
|  |  | ||||||
|  | local zset_key = KEYS[1] | ||||||
|  | local len = table.getn(ARGV) | ||||||
|  |  | ||||||
|  | for i = 1, len, 3 do | ||||||
|  |     local quota_id = ARGV[i] | ||||||
|  |     local amount = tonumber(ARGV[i+1]) | ||||||
|  |     local expire = tonumber(ARGV[i+2]) | ||||||
|  |     local quota_key = "quota:" .. quota_id | ||||||
|  |  | ||||||
|  |     if redis.call("EXISTS", quota_key) == 0 then | ||||||
|  |         redis.call("HSET", quota_key, "amount", amount) | ||||||
|  |         redis.call("ZADD", zset_key, expire, quota_id) | ||||||
|  |     else | ||||||
|  |         redis.call("HINCRBY", quota_key, "amount", amount) | ||||||
|  |     end | ||||||
|  | end | ||||||
|  |  | ||||||
|  | return 1 | ||||||
							
								
								
									
										229
									
								
								app/admin/service/quota_manager/quota_manager.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										229
									
								
								app/admin/service/quota_manager/quota_manager.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,229 @@ | |||||||
|  | package quota_manager | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"context" | ||||||
|  | 	"fmt" | ||||||
|  | 	rediskey "go-admin/common/redis_key" | ||||||
|  | 	"strconv" | ||||||
|  | 	"strings" | ||||||
|  | 	"time" | ||||||
|  |  | ||||||
|  | 	"github.com/go-redis/redis/v8" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | type QuotaUsage struct { | ||||||
|  | 	QuotaID string | ||||||
|  | 	Used    int64 | ||||||
|  | 	Expire  int64 | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type QuotaRecharge struct { | ||||||
|  | 	QuotaID  string | ||||||
|  | 	Amount   int64 | ||||||
|  | 	ExpireAt int64 | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type QuotaManager struct { | ||||||
|  | 	rdb           *redis.Client | ||||||
|  | 	deductLua     *redis.Script | ||||||
|  | 	refundLua     *redis.Script | ||||||
|  | 	zsetKeyPrefix string // "tm_member_remain_count" | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func NewQuotaManager(rdb *redis.Client) *QuotaManager { | ||||||
|  | 	deductScript := ` | ||||||
|  | -- Lua 脚本内容复制 deduct.lua | ||||||
|  | local zset_key = KEYS[1] | ||||||
|  | local now = tonumber(ARGV[1]) | ||||||
|  | local deduct = tonumber(ARGV[2]) | ||||||
|  | local remain = deduct | ||||||
|  | local result = {} | ||||||
|  |  | ||||||
|  | local expired_quota_ids = redis.call("ZRANGEBYSCORE", zset_key, "-inf", now) | ||||||
|  | for _, quota_id in ipairs(expired_quota_ids) do | ||||||
|  |     redis.call("DEL", "quota:" .. quota_id) | ||||||
|  | end | ||||||
|  |  | ||||||
|  | redis.call("ZREMRANGEBYSCORE", zset_key, "-inf", now) | ||||||
|  |  | ||||||
|  | local quota_ids = redis.call("ZRANGEBYSCORE", zset_key, now, "+inf") | ||||||
|  |  | ||||||
|  | for _, quota_id in ipairs(quota_ids) do | ||||||
|  |     local quota_key = "quota:" .. quota_id | ||||||
|  |     local amount = tonumber(redis.call("HGET", quota_key, "amount") or "0") | ||||||
|  |  | ||||||
|  |     if amount > 0 then | ||||||
|  |         local used = 0 | ||||||
|  |         if amount >= remain then | ||||||
|  |             used = remain | ||||||
|  |             redis.call("HINCRBY", quota_key, "amount", -remain) | ||||||
|  |             remain = 0 | ||||||
|  |         else | ||||||
|  |             used = amount | ||||||
|  |             redis.call("HINCRBY", quota_key, "amount", -amount) | ||||||
|  |             remain = remain - amount | ||||||
|  |         end | ||||||
|  |  | ||||||
|  |         table.insert(result, quota_id .. ":" .. used) | ||||||
|  |  | ||||||
|  |         local new_amount = tonumber(redis.call("HGET", quota_key, "amount") or "0") | ||||||
|  |         if new_amount <= 0 then | ||||||
|  |             redis.call("DEL", quota_key) | ||||||
|  |             redis.call("ZREM", zset_key, quota_id) | ||||||
|  |         end | ||||||
|  |  | ||||||
|  |         if remain == 0 then | ||||||
|  |             break | ||||||
|  |         end | ||||||
|  |     end | ||||||
|  | end | ||||||
|  |  | ||||||
|  | if remain > 0 then | ||||||
|  |     return {} | ||||||
|  | end | ||||||
|  |  | ||||||
|  | return result | ||||||
|  | ` | ||||||
|  | 	refundScript := ` | ||||||
|  | -- Lua 脚本内容复制 refund.lua | ||||||
|  | local zset_key = KEYS[1] | ||||||
|  | local len = table.getn(ARGV) | ||||||
|  |  | ||||||
|  | for i = 1, len, 3 do | ||||||
|  |     local quota_id = ARGV[i] | ||||||
|  |     local amount = tonumber(ARGV[i+1]) | ||||||
|  |     local expire = tonumber(ARGV[i+2]) | ||||||
|  |     local quota_key = "quota:" .. quota_id | ||||||
|  |  | ||||||
|  |     if redis.call("EXISTS", quota_key) == 0 then | ||||||
|  |         redis.call("HSET", quota_key, "amount", amount) | ||||||
|  |         redis.call("ZADD", zset_key, expire, quota_id) | ||||||
|  |     else | ||||||
|  |         redis.call("HINCRBY", quota_key, "amount", amount) | ||||||
|  |     end | ||||||
|  | end | ||||||
|  |  | ||||||
|  | return 1 | ||||||
|  | ` | ||||||
|  |  | ||||||
|  | 	return &QuotaManager{ | ||||||
|  | 		rdb:           rdb, | ||||||
|  | 		deductLua:     redis.NewScript(deductScript), | ||||||
|  | 		refundLua:     redis.NewScript(refundScript), | ||||||
|  | 		zsetKeyPrefix: rediskey.TM_MEMBER_REMAIN_COUNT_PURE, | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (q *QuotaManager) zsetKey(userKey, platformCode string) string { | ||||||
|  | 	return fmt.Sprintf("%s:%s:%s", q.zsetKeyPrefix, userKey, platformCode) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // 充值写入 | ||||||
|  | func (q *QuotaManager) AddQuota(ctx context.Context, userKey, platformCode string, recharge QuotaRecharge) error { | ||||||
|  | 	zsetKey := q.zsetKey(userKey, platformCode) | ||||||
|  | 	quotaKey := fmt.Sprintf("quota:%s", recharge.QuotaID) | ||||||
|  |  | ||||||
|  | 	err := q.rdb.HSet(ctx, quotaKey, map[string]interface{}{ | ||||||
|  | 		"amount": recharge.Amount, | ||||||
|  | 	}).Err() | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	err = q.rdb.ZAdd(ctx, zsetKey, &redis.Z{ | ||||||
|  | 		Score:  float64(recharge.ExpireAt), | ||||||
|  | 		Member: recharge.QuotaID, | ||||||
|  | 	}).Err() | ||||||
|  | 	return err | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // 扣减 | ||||||
|  | func (q *QuotaManager) Deduct(ctx context.Context, userKey, platformCode string, deductCount int64) ([]QuotaUsage, error) { | ||||||
|  | 	zsetKey := q.zsetKey(userKey, platformCode) | ||||||
|  | 	now := time.Now().Unix() | ||||||
|  |  | ||||||
|  | 	res, err := q.deductLua.Run(ctx, q.rdb, []string{zsetKey}, now, deductCount).Result() | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	arr, ok := res.([]interface{}) | ||||||
|  | 	if !ok || len(arr) == 0 { | ||||||
|  | 		return nil, fmt.Errorf("insufficient quota or invalid response") | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	var usages []QuotaUsage | ||||||
|  | 	for _, v := range arr { | ||||||
|  | 		s, ok := v.(string) | ||||||
|  | 		if !ok { | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  | 		parts := strings.Split(s, ":") | ||||||
|  | 		if len(parts) != 2 { | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  | 		used, _ := strconv.ParseInt(parts[1], 10, 64) | ||||||
|  | 		usages = append(usages, QuotaUsage{ | ||||||
|  | 			QuotaID: parts[0], | ||||||
|  | 			Used:    used, | ||||||
|  | 		}) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	for i := range usages { | ||||||
|  | 		score, err := q.rdb.ZScore(ctx, zsetKey, usages[i].QuotaID).Result() | ||||||
|  | 		if err == nil { | ||||||
|  | 			usages[i].Expire = int64(score) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return usages, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // 回滚 | ||||||
|  | func (q *QuotaManager) Refund(ctx context.Context, userKey, platformCode string, usages []QuotaUsage) error { | ||||||
|  | 	zsetKey := q.zsetKey(userKey, platformCode) | ||||||
|  |  | ||||||
|  | 	var args []interface{} | ||||||
|  | 	for _, u := range usages { | ||||||
|  | 		args = append(args, u.QuotaID, u.Used, u.Expire) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	_, err := q.refundLua.Run(ctx, q.rdb, []string{zsetKey}, args...).Result() | ||||||
|  | 	return err | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (q *QuotaManager) GetTotalRemainingQuota(ctx context.Context, apiKey, platformCode string) (int, error) { | ||||||
|  | 	zsetKey := q.zsetKey(apiKey, platformCode) | ||||||
|  |  | ||||||
|  | 	now := time.Now().Unix() | ||||||
|  | 	total := 0 | ||||||
|  |  | ||||||
|  | 	// 获取全部 quotaID 和过期时间 | ||||||
|  | 	zsetEntries, err := q.rdb.ZRangeWithScores(ctx, zsetKey, 0, -1).Result() | ||||||
|  | 	if err != nil { | ||||||
|  | 		return 0, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	for _, entry := range zsetEntries { | ||||||
|  | 		expireAt := int64(entry.Score) | ||||||
|  | 		if expireAt <= now { | ||||||
|  | 			continue // 跳过已过期的 quota | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		quotaID := fmt.Sprintf("%v", entry.Member) | ||||||
|  | 		quotaKey := fmt.Sprintf("quota:%s", quotaID) | ||||||
|  |  | ||||||
|  | 		amountStr, err := q.rdb.HGet(ctx, quotaKey, "amount").Result() | ||||||
|  | 		if err != nil || amountStr == "" { | ||||||
|  | 			continue // 不存在或异常 | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		amount, err := strconv.Atoi(amountStr) | ||||||
|  | 		if err != nil { | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		total += amount | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return total, nil | ||||||
|  | } | ||||||
							
								
								
									
										80
									
								
								app/admin/service/quota_manager/quota_manager_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										80
									
								
								app/admin/service/quota_manager/quota_manager_test.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,80 @@ | |||||||
|  | package quota_manager | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"context" | ||||||
|  | 	"go-admin/utils/redishelper" | ||||||
|  | 	"os" | ||||||
|  | 	"strconv" | ||||||
|  | 	"testing" | ||||||
|  | 	"time" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | var ( | ||||||
|  | 	redisAddr = "localhost:6379" | ||||||
|  | 	redisPwd  = "" | ||||||
|  | 	redisDB   = 1 | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func setupRedis() { | ||||||
|  | 	redishelper.InitDefaultRedis(redisAddr, redisPwd, redisDB) | ||||||
|  | 	redishelper.InitLockRedisConn(redisAddr, redisPwd, strconv.Itoa(redisDB)) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestQuotaManager_AddDeductRefund(t *testing.T) { | ||||||
|  | 	ctx := context.Background() | ||||||
|  | 	setupRedis() | ||||||
|  | 	qmgr := NewQuotaManager(redishelper.DefaultRedis.GetClient()) | ||||||
|  |  | ||||||
|  | 	userKey := "Bw4iSj9Y90ix0e05GrMFp6EuFFTIbE9j" | ||||||
|  | 	platformCode := "deepl_free" | ||||||
|  | 	quotaID := "testquota1" | ||||||
|  | 	expireAt := time.Now().Add(1 * time.Hour).Unix() | ||||||
|  | 	amount := int64(1000) | ||||||
|  |  | ||||||
|  | 	// 充值写入 | ||||||
|  | 	err := qmgr.AddQuota(ctx, userKey, platformCode, QuotaRecharge{ | ||||||
|  | 		QuotaID:  quotaID, | ||||||
|  | 		Amount:   amount, | ||||||
|  | 		ExpireAt: expireAt, | ||||||
|  | 	}) | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Fatalf("AddQuota failed: %v", err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// 扣减500 | ||||||
|  | 	usages, err := qmgr.Deduct(ctx, userKey, platformCode, 500) | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Fatalf("Deduct failed: %v", err) | ||||||
|  | 	} | ||||||
|  | 	if len(usages) == 0 { | ||||||
|  | 		t.Fatal("Deduct returned empty usage") | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if usages[0].Used != 500 { | ||||||
|  | 		t.Errorf("Expected Used=500, got %d", usages[0].Used) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// 回滚500 | ||||||
|  | 	err = qmgr.Refund(ctx, userKey, platformCode, usages) | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Fatalf("Refund failed: %v", err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// 再次扣减1000,确认余额充足 | ||||||
|  | 	usages, err = qmgr.Deduct(ctx, userKey, platformCode, 1000) | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Fatalf("Second Deduct failed: %v", err) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // 设置过期时间 | ||||||
|  | func TestQuotaManager_Deduct_Refund_WithExpire(t *testing.T) { | ||||||
|  | 	setupRedis() | ||||||
|  | 	if err := redishelper.DefaultRedis.ZUpdateScore("tm_member_remain_count:MiyJrgfh3gYhwyDO43fdhHNswm4CeAfn:deepl", 1751436958, "573610499514565142"); err != nil { | ||||||
|  | 		t.Fatalf("ZUpdateScore failed: %v", err) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestMain(m *testing.M) { | ||||||
|  | 	os.Exit(m.Run()) | ||||||
|  | } | ||||||
| @ -82,7 +82,7 @@ func (e *SysUser) Insert(c *dto.SysUserInsertReq) error { | |||||||
| 	e.Orm.Model(role).Where("role_id = ?", c.RoleId).Find(&role) | 	e.Orm.Model(role).Where("role_id = ?", c.RoleId).Find(&role) | ||||||
|  |  | ||||||
| 	if role.RoleId == 0 { | 	if role.RoleId == 0 { | ||||||
| 		err = errors.New("角色不存在") | 		return errors.New("角色不存在") | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	err = e.Orm.Model(&data).Where("username = ?", c.Username).Count(&i).Error | 	err = e.Orm.Model(&data).Where("username = ?", c.Username).Count(&i).Error | ||||||
|  | |||||||
| @ -1,6 +1,7 @@ | |||||||
| package service | package service | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
|  | 	"context" | ||||||
| 	"errors" | 	"errors" | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"strconv" | 	"strconv" | ||||||
| @ -9,12 +10,12 @@ import ( | |||||||
|  |  | ||||||
| 	"github.com/bytedance/sonic" | 	"github.com/bytedance/sonic" | ||||||
| 	"github.com/go-admin-team/go-admin-core/sdk/service" | 	"github.com/go-admin-team/go-admin-core/sdk/service" | ||||||
| 	"github.com/go-redis/redis/v8" |  | ||||||
| 	"github.com/jinzhu/copier" | 	"github.com/jinzhu/copier" | ||||||
| 	"gorm.io/gorm" | 	"gorm.io/gorm" | ||||||
|  |  | ||||||
| 	"go-admin/app/admin/models" | 	"go-admin/app/admin/models" | ||||||
| 	"go-admin/app/admin/service/dto" | 	"go-admin/app/admin/service/dto" | ||||||
|  | 	"go-admin/app/admin/service/quota_manager" | ||||||
| 	"go-admin/common/actions" | 	"go-admin/common/actions" | ||||||
| 	cDto "go-admin/common/dto" | 	cDto "go-admin/common/dto" | ||||||
| 	rediskey "go-admin/common/redis_key" | 	rediskey "go-admin/common/redis_key" | ||||||
| @ -31,6 +32,7 @@ func (e TmMember) GetUserPlatforms(userId int, resp *[]dto.TmMemberPlatformFront | |||||||
| 	var memberAccount models.TmMember | 	var memberAccount models.TmMember | ||||||
|  |  | ||||||
| 	if err := e.Orm.Model(&memberAccount). | 	if err := e.Orm.Model(&memberAccount). | ||||||
|  | 		Where("user_id=?", userId). | ||||||
| 		Find(&memberAccount).Error; err != nil { | 		Find(&memberAccount).Error; err != nil { | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
| @ -51,8 +53,10 @@ func (e TmMember) GetUserPlatforms(userId int, resp *[]dto.TmMemberPlatformFront | |||||||
|  |  | ||||||
| 		dataItem.ApiKey = memberAccount.ApiKey | 		dataItem.ApiKey = memberAccount.ApiKey | ||||||
| 		dataItem.Name = platform.ShowName | 		dataItem.Name = platform.ShowName | ||||||
|  | 		dataItem.PlatformId = platform.Id | ||||||
| 		dataItem.Price = int(platform.Price.IntPart()) | 		dataItem.Price = int(platform.Price.IntPart()) | ||||||
| 		dataItem.RemainChars, _ = e.GetRemainCount(item.PlatformKey, dataItem.ApiKey) | 		dataItem.RemainChars = item.RemainingCharacter | ||||||
|  | 		dataItem.UseChars = item.UseCharacter | ||||||
|  |  | ||||||
| 		*resp = append(*resp, dataItem) | 		*resp = append(*resp, dataItem) | ||||||
| 	} | 	} | ||||||
| @ -82,25 +86,30 @@ func (e *TmMember) GetPage(c *dto.TmMemberGetPageReq, p *actions.DataPermission, | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	userIds := []int{} | 	userIds := []int{} | ||||||
|  | 	memberIds := []int{} | ||||||
|  |  | ||||||
| 	for _, item := range datas { | 	for _, item := range datas { | ||||||
| 		if !utility.ContainsInt(userIds, item.UserId) { | 		if !utility.ContainsInt(userIds, item.UserId) { | ||||||
| 			userIds = append(userIds, item.UserId) | 			userIds = append(userIds, item.UserId) | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|  | 		if !utility.ContainsInt(memberIds, item.Id) { | ||||||
|  | 			memberIds = append(memberIds, item.Id) | ||||||
|  | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	memberPlatformService := TmMemberPlatform{Service: e.Service} | ||||||
| 	platformService := TmPlatform{Service: e.Service} | 	platformService := TmPlatform{Service: e.Service} | ||||||
| 	userService := SysUser{Service: e.Service} | 	userService := SysUser{Service: e.Service} | ||||||
| 	users, _ := userService.GetByIds(userIds) | 	users, _ := userService.GetByIds(userIds) | ||||||
| 	activeList, _ := platformService.GetActiveList() | 	activeList, _ := platformService.GetActiveList() | ||||||
|  | 	memberPlatforms, _ := memberPlatformService.GetMemberList(memberIds) | ||||||
|  |  | ||||||
| 	for _, item := range datas { | 	for _, item := range datas { | ||||||
| 		dataItem := dto.TmMemberResp{} | 		dataItem := dto.TmMemberResp{} | ||||||
| 		copier.Copy(&dataItem, &item) | 		copier.Copy(&dataItem, &item) | ||||||
|  |  | ||||||
| 		// count, _ := e.GetRemainCount(,dataItem.ApiKey) |  | ||||||
| 		dataItem.ApiKey = utility.DesensitizeGeneric(dataItem.ApiKey, 2, 2, '*') | 		dataItem.ApiKey = utility.DesensitizeGeneric(dataItem.ApiKey, 2, 2, '*') | ||||||
| 		// dataItem.RemainChars = count |  | ||||||
|  |  | ||||||
| 		for _, user := range users { | 		for _, user := range users { | ||||||
| 			if user.UserId == item.UserId { | 			if user.UserId == item.UserId { | ||||||
| @ -108,14 +117,33 @@ func (e *TmMember) GetPage(c *dto.TmMemberGetPageReq, p *actions.DataPermission, | |||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|  | 		//剩余字符 | ||||||
| 		for _, platform := range activeList { | 		for _, platform := range activeList { | ||||||
| 			platformItem := dto.TmMemberPlatformResp{} | 			platformItem := dto.TmMemberPlatformResp{} | ||||||
|  | 			platformItem.PlatformId = platform.Id | ||||||
|  | 			platformItem.MemberId = item.Id | ||||||
| 			platformItem.Name = platform.Name | 			platformItem.Name = platform.Name | ||||||
| 			platformItem.RemainChars, _ = e.GetRemainCount(platform.Code, item.ApiKey) | 			platformItem.TotalChars, _ = e.GetRemainCount(platform.Code, item.ApiKey) | ||||||
|  |  | ||||||
| 			dataItem.Platforms = append(dataItem.Platforms, platformItem) | 			dataItem.Platforms = append(dataItem.Platforms, platformItem) | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|  | 		//已用字符 | ||||||
|  | 		for _, platform := range activeList { | ||||||
|  | 			platformItem := dto.TmMemberPlatformResp{} | ||||||
|  | 			platformItem.PlatformId = platform.Id | ||||||
|  | 			platformItem.MemberId = item.Id | ||||||
|  | 			platformItem.Name = platform.Name | ||||||
|  |  | ||||||
|  | 			for _, memberPlatform := range memberPlatforms { | ||||||
|  | 				if memberPlatform.PlatformId == platform.Id && memberPlatform.MemberId == item.Id { | ||||||
|  | 					platformItem.TotalChars = memberPlatform.UseCharacter | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			dataItem.UsedPlatform = append(dataItem.UsedPlatform, platformItem) | ||||||
|  | 		} | ||||||
|  |  | ||||||
| 		*list = append(*list, dataItem) | 		*list = append(*list, dataItem) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| @ -190,13 +218,11 @@ func (e *TmMember) SaveAllCache() error { | |||||||
| 	} | 	} | ||||||
| 	for _, item := range list { | 	for _, item := range list { | ||||||
| 		key := fmt.Sprintf(rediskey.TM_MEMBER_BY_KEY, item.ApiKey) | 		key := fmt.Sprintf(rediskey.TM_MEMBER_BY_KEY, item.ApiKey) | ||||||
| 		// remainKey := fmt.Sprintf(rediskey.TM_MEMBER_REMAIN_COUNT, item.ApiKey) |  | ||||||
| 		val, err := sonic.MarshalString(item) | 		val, err := sonic.MarshalString(item) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return err | 			return err | ||||||
| 		} | 		} | ||||||
| 		redishelper.DefaultRedis.SetString(key, val) | 		redishelper.DefaultRedis.SetString(key, val) | ||||||
| 		// redishelper.DefaultRedis.SetString(remainKey, strconv.Itoa(item.RemainChars)) |  | ||||||
| 	} | 	} | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
| @ -262,64 +288,101 @@ func (e *TmMember) RemoveByUserIds(userIds []int) error { | |||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
| // SyncMemberRemain 同步剩余字符 | // SyncMemberRemain 同步剩余字符(ZSET 多笔充值版) | ||||||
| func (e *TmMember) SyncMemberRemain() error { | func (e *TmMember) SyncMemberRemain() error { | ||||||
| 	scanKeys, err := redishelper.DefaultRedis.ScanKeys(fmt.Sprintf("%s*", rediskey.TM_MEMBER_REMAIN_COUNT_PURE)) | 	scanKeys, err := redishelper.DefaultRedis.ScanKeys(fmt.Sprintf("%s:*:*", rediskey.TM_MEMBER_REMAIN_COUNT_PURE)) | ||||||
|  |  | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	members := e.getCacheMembers() | 	members := e.getCacheMembers() | ||||||
|  | 	dailyUsageService := TmMemberDailyUsage{Service: e.Service} | ||||||
| 	datas := make([]models.TmMemberPlatform, 0) | 	datas := make([]models.TmMemberPlatform, 0) | ||||||
|  | 	now := time.Now().Unix() | ||||||
|  |  | ||||||
| 	for _, key := range scanKeys { | 	for _, key := range scanKeys { | ||||||
| 		items := strings.Split(key, ":") | 		items := strings.Split(key, ":") | ||||||
| 		apiKey := items[len(items)-2] | 		if len(items) < 3 { | ||||||
| 		platform := items[len(items)-1] |  | ||||||
| 		val, err := redishelper.DefaultRedis.GetString(key) |  | ||||||
| 		remainCount, err1 := strconv.Atoi(val) |  | ||||||
|  |  | ||||||
| 		if err != nil || err1 != nil { |  | ||||||
| 			e.Log.Errorf("TmMemberService SyncMemberRemain GetString error:%s \r\n err1:%s \r\n", err, err1) |  | ||||||
| 			continue | 			continue | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|  | 		apiKey := items[len(items)-2] | ||||||
|  | 		platform := items[len(items)-1] | ||||||
|  |  | ||||||
|  | 		// 清除过期的 quotaID(ZSET + Hash) | ||||||
|  | 		expiredIDs, err := redishelper.DefaultRedis.ZRangeByScore(key, "-inf", strconv.FormatInt(now, 10)) | ||||||
|  | 		if err == nil && len(expiredIDs) > 0 { | ||||||
|  | 			for _, quotaID := range expiredIDs { | ||||||
|  | 				redishelper.DefaultRedis.DeleteString(fmt.Sprintf("quota:%s", quotaID)) | ||||||
|  | 			} | ||||||
|  | 			redishelper.DefaultRedis.ZRemValues(key, expiredIDs...) | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		// 从 ZSET 获取所有 quota ID(不清理过期,数据库中保留的是理论值) | ||||||
|  | 		zsetEntries, err := redishelper.DefaultRedis.GetRevRangeScoresSortSet(key) | ||||||
|  | 		if err != nil { | ||||||
|  | 			e.Log.Errorf("TmMemberService SyncMemberRemain ZRANGE failed for key %s: %v", key, err) | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		totalRemain := 0 | ||||||
|  | 		for _, entry := range zsetEntries { | ||||||
|  | 			quotaID, ok := entry.Member.(string) | ||||||
|  | 			if !ok { | ||||||
|  | 				continue | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			quotaKey := fmt.Sprintf("quota:%s", quotaID) | ||||||
|  | 			amountStr, err := redishelper.DefaultRedis.HGetField(quotaKey, "amount") | ||||||
|  | 			if err != nil || amountStr == "" { | ||||||
|  | 				continue | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			amount, err := strconv.Atoi(amountStr) | ||||||
|  | 			if err != nil { | ||||||
|  | 				continue | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			totalRemain += amount | ||||||
|  | 		} | ||||||
|  |  | ||||||
| 		if member := members[apiKey]; member.Id > 0 { | 		if member := members[apiKey]; member.Id > 0 { | ||||||
| 			item := models.TmMemberPlatform{} | 			memberPlatform := models.TmMemberPlatform{ | ||||||
| 			item.Id = member.Id | 				PlatformKey:        platform, | ||||||
| 			item.RemainingCharacter = remainCount | 				RemainingCharacter: totalRemain, | ||||||
| 			item.PlatformKey = platform | 			} | ||||||
| 			datas = append(datas, item) | 			memberPlatform.Id = member.Id | ||||||
|  | 			datas = append(datas, memberPlatform) | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	// 批量更新数据库(分批,每批最多 1000 条) | ||||||
| 	arrayData := utility.SplitSlice(datas, 1000) | 	arrayData := utility.SplitSlice(datas, 1000) | ||||||
| 	for _, dataBatch := range arrayData { | 	for _, dataBatch := range arrayData { | ||||||
|  |  | ||||||
| 		// 遍历当前批次的所有记录,为每条记录单独执行 UPDATE |  | ||||||
| 		for _, record := range dataBatch { | 		for _, record := range dataBatch { | ||||||
| 			stmt := ` | 			stmt := ` | ||||||
|                 UPDATE tm_member_platform | 				UPDATE tm_member_platform | ||||||
|                 SET | 				SET | ||||||
|                     remaining_character = ?, | 					remaining_character = ?, | ||||||
|                     updated_at = NOW() | 					updated_at = NOW() | ||||||
|                 WHERE platform_key = ? AND member_id = ?; | 				WHERE platform_key = ? AND member_id = ?; | ||||||
|             ` | 			` | ||||||
| 			args := []interface{}{ | 			args := []interface{}{ | ||||||
| 				record.RemainingCharacter, | 				record.RemainingCharacter, | ||||||
| 				record.PlatformKey, | 				record.PlatformKey, | ||||||
| 				record.Id, | 				record.Id, | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			// 执行单个 UPDATE 语句 |  | ||||||
| 			if err := e.Orm.Exec(stmt, args...).Error; err != nil { | 			if err := e.Orm.Exec(stmt, args...).Error; err != nil { | ||||||
| 				// 记录错误,但继续处理批次中的其他记录 | 				e.Log.Errorf("TmMemberService SyncMemberRemain single Exec for PlatformKey %s, MemberID %d error: %s \r\n", | ||||||
| 				e.Log.Errorf("TmMemberService SyncMemberRemain single Exec for PlatformKey %s, MemberID %d error: %s \r\n", record.PlatformKey, record.Id, err) | 					record.PlatformKey, record.Id, err) | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	// 同步每日使用量(不变) | ||||||
|  | 	dailyUsageService.SyncTotalUse() | ||||||
|  |  | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
| @ -444,24 +507,6 @@ func (e *TmMember) loadSyncData(keys []string, members *map[string]models.TmMemb | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| // func (e *TmMember) GetMyApiKey(userId int) (dto.TranslateUserInfoResp, error) { |  | ||||||
| // 	var data models.TmMember |  | ||||||
| // 	resp := dto.TranslateUserInfoResp{} |  | ||||||
| // 	if err := e.Orm.Model(&data).Where("user_id = ?", userId).First(&data).Error; err != nil { |  | ||||||
| // 		e.Log.Errorf("TmMemberService GetMyApiKey error:%s \r\n", err) |  | ||||||
| // 		return resp, nil |  | ||||||
| // 	} |  | ||||||
|  |  | ||||||
| // 	var err error |  | ||||||
| // 	resp.UserApiKey = data.ApiKey |  | ||||||
| // 	resp.RemainChars, err = e.GetRemainCount(data.ApiKey) |  | ||||||
|  |  | ||||||
| // 	if err != nil { |  | ||||||
| // 		e.Log.Errorf("转换类型失败,error:%v", err) |  | ||||||
| // 	} |  | ||||||
| // 	return resp, nil |  | ||||||
| // } |  | ||||||
|  |  | ||||||
| // GetTranslateStatistic 获取翻译统计 | // GetTranslateStatistic 获取翻译统计 | ||||||
| func (e *TmMember) GetTranslateStatistic(userId int, list *[]dto.TranslateStatisticResp) error { | func (e *TmMember) GetTranslateStatistic(userId int, list *[]dto.TranslateStatisticResp) error { | ||||||
| 	endDate := time.Now().Format("2006-01-02") | 	endDate := time.Now().Format("2006-01-02") | ||||||
| @ -535,31 +580,24 @@ func (e *TmMember) Recharge(req *dto.TmMemberRechargeReq, p *actions.DataPermiss | |||||||
|  |  | ||||||
| // 获取可用字符数 | // 获取可用字符数 | ||||||
| func (e *TmMember) GetRemainCount(platformKey, apiKey string) (int, error) { | func (e *TmMember) GetRemainCount(platformKey, apiKey string) (int, error) { | ||||||
| 	key := fmt.Sprintf(rediskey.TM_MEMBER_REMAIN_COUNT, apiKey, platformKey) | 	ctx := context.Background() | ||||||
| 	val, err := redishelper.DefaultRedis.GetString(key) | 	quotaManager := quota_manager.NewQuotaManager(redishelper.DefaultRedis.GetClient()) | ||||||
| 	result := 0 | 	result, err := quotaManager.GetTotalRemainingQuota(ctx, apiKey, platformKey) | ||||||
|  |  | ||||||
| 	if err != nil && !errors.Is(err, redis.Nil) { | 	if err != nil { | ||||||
| 		return 0, err | 		return 0, err | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if val != "" { | 	// if result == 0 { | ||||||
| 		result, err = strconv.Atoi(val) | 	// 	var data models.TmMember | ||||||
| 		if err != nil { |  | ||||||
| 			return 0, err |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	if result == 0 { | 	// 	if err := e.Orm.Model(&data).Where("api_key = ?", apiKey).First(&data).Error; err != nil { | ||||||
| 		var data models.TmMember | 	// 		return 0, err | ||||||
|  | 	// 	} | ||||||
|  |  | ||||||
| 		if err := e.Orm.Model(&data).Where("api_key = ?", apiKey).First(&data).Error; err != nil { | 	// 	result = data.RemainChars | ||||||
| 			return 0, err | 	// 	// redishelper.DefaultRedis.SetString(key, strconv.Itoa(result)) | ||||||
| 		} | 	// } | ||||||
|  |  | ||||||
| 		result = data.RemainChars |  | ||||||
| 		redishelper.DefaultRedis.SetString(key, strconv.Itoa(result)) |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	return result, nil | 	return result, nil | ||||||
| } | } | ||||||
| @ -578,6 +616,55 @@ func (e *TmMember) DecrBy(platformKey, apiKey string, totalChars int) error { | |||||||
| 	return redishelper.DefaultRedis.DecrBy(remainCountKey, int64(totalChars)).Err() | 	return redishelper.DefaultRedis.DecrBy(remainCountKey, int64(totalChars)).Err() | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // 增加字符 | ||||||
|  | func (e *TmMember) IncyByQuote(ctx context.Context, platformKey, apiKey string, totalChars int, orderNo string, expireAt time.Time) error { | ||||||
|  | 	quotaManager := quota_manager.NewQuotaManager(redishelper.DefaultRedis.GetClient()) | ||||||
|  | 	rechargeData := quota_manager.QuotaRecharge{ | ||||||
|  | 		QuotaID:  orderNo, | ||||||
|  | 		Amount:   int64(totalChars), | ||||||
|  | 		ExpireAt: expireAt.Unix(), | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if err := quotaManager.AddQuota(ctx, apiKey, platformKey, rechargeData); err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // 扣除字符 | ||||||
|  | func (e *TmMember) DecrByQuote(ctx context.Context, platformKey, apiKey string, totalChars int) ([]quota_manager.QuotaUsage, error) { | ||||||
|  | 	quoteManage := quota_manager.NewQuotaManager(redishelper.DefaultRedis.GetClient()) | ||||||
|  |  | ||||||
|  | 	datas, err := quoteManage.Deduct(ctx, apiKey, platformKey, int64(totalChars)) | ||||||
|  |  | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return datas, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // 回滚扣除字符 | ||||||
|  | // apiKey 用户翻译密钥 | ||||||
|  | // platformKey 平台标识 | ||||||
|  | // datas 扣除的字符数据 | ||||||
|  | func (e *TmMember) RefundQuote(ctx context.Context, apiKey, platformKey string, datas *[]quota_manager.QuotaUsage) error { | ||||||
|  | 	quoteManage := quota_manager.NewQuotaManager(redishelper.DefaultRedis.GetClient()) | ||||||
|  | 	if err := quoteManage.Refund(ctx, apiKey, platformKey, *datas); err != nil { | ||||||
|  |  | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // 设置可用字符数 | ||||||
|  | func (e *TmMember) SetChars(platformKey, apiKey string, totalChars int) error { | ||||||
|  | 	remainCountKey := fmt.Sprintf(rediskey.TM_MEMBER_REMAIN_COUNT, apiKey, platformKey) | ||||||
|  |  | ||||||
|  | 	return redishelper.DefaultRedis.SetString(remainCountKey, strconv.Itoa(totalChars)) | ||||||
|  | } | ||||||
|  |  | ||||||
| // 根据id获取数据 | // 根据id获取数据 | ||||||
| func (e *TmMember) GetById(id int, data *models.TmMember) error { | func (e *TmMember) GetById(id int, data *models.TmMember) error { | ||||||
| 	if err := e.Orm.Model(data).Where("id = ?", id).First(data).Error; err != nil { | 	if err := e.Orm.Model(data).Where("id = ?", id).First(data).Error; err != nil { | ||||||
| @ -586,6 +673,15 @@ func (e *TmMember) GetById(id int, data *models.TmMember) error { | |||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // 根据userId 获取数据 | ||||||
|  | func (e *TmMember) GetByUserId(userId int, data *models.TmMember) error { | ||||||
|  | 	if err := e.Orm.Model(data).Where("user_id = ?", userId).First(data).Error; err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
| // 修改翻译用户状态 | // 修改翻译用户状态 | ||||||
| func (e TmMember) ChangeStatus(req *dto.TmMemberChangeStatusReq, p *actions.DataPermission) error { | func (e TmMember) ChangeStatus(req *dto.TmMemberChangeStatusReq, p *actions.DataPermission) error { | ||||||
| 	var err error | 	var err error | ||||||
| @ -638,7 +734,6 @@ func (e *TmMember) SyncInsert(req *dto.TmMemberSyncInsertReq, entity *models.TmM | |||||||
| 	platformService := TmPlatform{Service: e.Service} | 	platformService := TmPlatform{Service: e.Service} | ||||||
| 	activePlatforms, _ := platformService.GetActiveList() | 	activePlatforms, _ := platformService.GetActiveList() | ||||||
| 	TmMemberPlatforms := make([]models.TmMemberPlatform, 0) | 	TmMemberPlatforms := make([]models.TmMemberPlatform, 0) | ||||||
|  |  | ||||||
| 	copier.Copy(entity, req) | 	copier.Copy(entity, req) | ||||||
|  |  | ||||||
| 	apiKey, err := utility.GenerateBase62Key(32) | 	apiKey, err := utility.GenerateBase62Key(32) | ||||||
| @ -661,8 +756,8 @@ func (e *TmMember) SyncInsert(req *dto.TmMemberSyncInsertReq, entity *models.TmM | |||||||
| 			PlatformId:         platform.Id, | 			PlatformId:         platform.Id, | ||||||
| 			PlatformKey:        platform.Code, | 			PlatformKey:        platform.Code, | ||||||
| 			Status:             1, | 			Status:             1, | ||||||
| 			TotalCharacter:     10000, | 			TotalCharacter:     0, | ||||||
| 			RemainingCharacter: 10000, | 			RemainingCharacter: 0, | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		TmMemberPlatforms = append(TmMemberPlatforms, item) | 		TmMemberPlatforms = append(TmMemberPlatforms, item) | ||||||
| @ -672,9 +767,10 @@ func (e *TmMember) SyncInsert(req *dto.TmMemberSyncInsertReq, entity *models.TmM | |||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	for _, platform := range TmMemberPlatforms { | 	// for _, platform := range TmMemberPlatforms { | ||||||
| 		e.IncrBy(platform.PlatformKey, entity.ApiKey, platform.RemainingCharacter) | 	// 	// e.IncrBy(platform.PlatformKey, entity.ApiKey, platform.RemainingCharacter) | ||||||
| 	} | 	// 	e.IncyByQuote(ctx,platform.PlatformKey,entity.ApiKey,platform.TotalCharacter,,) | ||||||
|  | 	// } | ||||||
|  |  | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|  | |||||||
| @ -128,7 +128,7 @@ func (e *TmMemberDailyUsage) GetStatistic(userId int, resp *dto.TmMemberPlatform | |||||||
|  |  | ||||||
| 	if err := e.Orm.Model(models.TmMemberDailyUsage{}). | 	if err := e.Orm.Model(models.TmMemberDailyUsage{}). | ||||||
| 		Joins("JOIN tm_member on tm_member.id=tm_member_daily_usage.member_id"). | 		Joins("JOIN tm_member on tm_member.id=tm_member_daily_usage.member_id"). | ||||||
| 		Where("tm_member_daily_usage.date >= ? and tm_member_daily_usage.date <= ?", startTime, endTime).Find(&datas).Error; err != nil { | 		Where("tm_member.user_id =? and tm_member_daily_usage.date >= ? and tm_member_daily_usage.date <= ?", userId, startTime, endTime).Find(&datas).Error; err != nil { | ||||||
| 		e.Log.Error("获取折线图数据失败", err) | 		e.Log.Error("获取折线图数据失败", err) | ||||||
| 		return nil | 		return nil | ||||||
| 	} | 	} | ||||||
| @ -178,3 +178,24 @@ func (e *TmMemberDailyUsage) GetStatistic(userId int, resp *dto.TmMemberPlatform | |||||||
| 	resp.Data = respDatas | 	resp.Data = respDatas | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // 同步总使用量 | ||||||
|  | func (e *TmMemberDailyUsage) SyncTotalUse() error { | ||||||
|  | 	var datas []dto.TmMemberPlatformGroupData | ||||||
|  | 	var data models.TmMemberPlatform | ||||||
|  |  | ||||||
|  | 	if err := e.Orm.Model(models.TmMemberDailyUsage{}). | ||||||
|  | 		Group("member_id, platform_id"). | ||||||
|  | 		Select("member_id, platform_id, sum(use_chars) as total"). | ||||||
|  | 		Find(&datas).Error; err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	for _, item := range datas { | ||||||
|  | 		if err := e.Orm.Model(data).Where("member_id = ? and platform_id = ? ", item.MemberId, item.PlatformId).Update("use_character", item.Total).Error; err != nil { | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | |||||||
| @ -16,6 +16,42 @@ type TmMemberPlatform struct { | |||||||
| 	service.Service | 	service.Service | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // 修改用户字符 | ||||||
|  | func (e TmMemberPlatform) ChangeChars(req *dto.TmMemberPlatformChangeCharsReq, p *actions.DataPermission) error { | ||||||
|  | 	var data models.TmMemberPlatform | ||||||
|  |  | ||||||
|  | 	switch req.Type { | ||||||
|  | 	case 1: | ||||||
|  | 		member := models.TmMember{} | ||||||
|  | 		memberService := TmMember{Service: e.Service} | ||||||
|  | 		memberService.GetById(req.MemberId, &member) | ||||||
|  |  | ||||||
|  | 		if member.Id == 0 { | ||||||
|  | 			return errors.New("用户不存在") | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		platformService := TmPlatform{Service: e.Service} | ||||||
|  | 		platform, err := platformService.GetById(req.PlatformId) | ||||||
|  |  | ||||||
|  | 		if err != nil { | ||||||
|  | 			return errors.New("平台不存在") | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if err := memberService.SetChars(platform.Code, member.ApiKey, req.ChangeNum); err != nil { | ||||||
|  | 			return errors.New("设置剩余字符失败") | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if err := e.Orm.Model(data).Where("member_id =? AND platform_id =?", req.MemberId, req.PlatformId).Update("remaining_character", req.ChangeNum).Error; err != nil { | ||||||
|  | 			e.Log.Errorf("TmMemberPlatformService ChangeChars error:%s \r\n", err) | ||||||
|  | 		} | ||||||
|  | 	case 2: | ||||||
|  | 	default: | ||||||
|  | 		return errors.New("修改类型错误") | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
| // GetPage 获取TmMemberPlatform列表 | // GetPage 获取TmMemberPlatform列表 | ||||||
| func (e *TmMemberPlatform) GetPage(c *dto.TmMemberPlatformGetPageReq, p *actions.DataPermission, list *[]models.TmMemberPlatform, count *int64) error { | func (e *TmMemberPlatform) GetPage(c *dto.TmMemberPlatformGetPageReq, p *actions.DataPermission, list *[]models.TmMemberPlatform, count *int64) error { | ||||||
| 	var err error | 	var err error | ||||||
| @ -107,3 +143,40 @@ func (e *TmMemberPlatform) Remove(d *dto.TmMemberPlatformDeleteReq, p *actions.D | |||||||
| 	} | 	} | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // GetMemberList 获取用户列表 | ||||||
|  | func (e *TmMemberPlatform) GetMemberList(memberIds []int) ([]models.TmMemberPlatform, error) { | ||||||
|  | 	result := make([]models.TmMemberPlatform, 0) | ||||||
|  | 	err := e.Orm.Model(&models.TmMemberPlatform{}). | ||||||
|  | 		Where("member_id IN (?)", memberIds). | ||||||
|  | 		Find(&result).Error | ||||||
|  | 	if err != nil { | ||||||
|  | 		e.Log.Errorf("Service GetMemberList error:%s \r\n", err) | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return result, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // InsertOrUpdateRemainChars 新增或更新用户剩余字符 | ||||||
|  | func (e *TmMemberPlatform) GetOrInsert(req *dto.TmRechargeLogInsertOrUpdateReq) (models.TmMemberPlatform, error) { | ||||||
|  | 	result := models.TmMemberPlatform{} | ||||||
|  |  | ||||||
|  | 	e.Orm.Model(result).Where("member_id =? AND platform_id =?", req.MemberId, req.PlatformId).First(&result) | ||||||
|  |  | ||||||
|  | 	if result.Id == 0 { | ||||||
|  | 		result.MemberId = req.MemberId | ||||||
|  | 		result.PlatformId = req.PlatformId | ||||||
|  | 		result.PlatformKey = req.PlatformKey | ||||||
|  | 		result.RemainingCharacter = 0 | ||||||
|  | 		result.TotalCharacter = 0 | ||||||
|  | 		result.Status = 1 | ||||||
|  |  | ||||||
|  | 		if err := e.Orm.Save(&result).Error; err != nil { | ||||||
|  | 			e.Log.Errorf("Service InsertOrUpdateRemainChars error:%s \r\n", err) | ||||||
|  | 			return result, err | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return result, nil | ||||||
|  | } | ||||||
|  | |||||||
| @ -225,6 +225,17 @@ func (e *TmPlatform) GetByKey(code string) (*models.TmPlatform, error) { | |||||||
| 	return &result, nil | 	return &result, nil | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // 根据id 获取翻译平台信息 | ||||||
|  | func (e *TmPlatform) GetById(id int) (*models.TmPlatform, error) { | ||||||
|  | 	var result models.TmPlatform | ||||||
|  | 	err := e.Orm.Model(&result).Where("id = ?", id).Find(&result).Error | ||||||
|  | 	if err != nil { | ||||||
|  | 		e.Log.Errorf("db error:%s", err) | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	return &result, nil | ||||||
|  | } | ||||||
|  |  | ||||||
| func (e *TmPlatform) GetActiveList() ([]models.TmPlatform, error) { | func (e *TmPlatform) GetActiveList() ([]models.TmPlatform, error) { | ||||||
| 	var list []models.TmPlatform | 	var list []models.TmPlatform | ||||||
| 	err := e.Orm.Model(&models.TmPlatform{}).Find(&list).Error | 	err := e.Orm.Model(&models.TmPlatform{}).Find(&list).Error | ||||||
|  | |||||||
							
								
								
									
										205
									
								
								app/admin/service/tm_recharge_log.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										205
									
								
								app/admin/service/tm_recharge_log.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,205 @@ | |||||||
|  | package service | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"context" | ||||||
|  | 	"errors" | ||||||
|  | 	"fmt" | ||||||
|  | 	"go-admin/app/admin/models" | ||||||
|  | 	"go-admin/app/admin/service/dto" | ||||||
|  | 	"go-admin/app/admin/service/quota_manager" | ||||||
|  | 	"go-admin/common/actions" | ||||||
|  | 	cDto "go-admin/common/dto" | ||||||
|  | 	"go-admin/common/statuscode" | ||||||
|  | 	"go-admin/utils/redishelper" | ||||||
|  | 	"go-admin/utils/utility" | ||||||
|  | 	"sort" | ||||||
|  | 	"strconv" | ||||||
|  | 	"time" | ||||||
|  |  | ||||||
|  | 	"github.com/go-admin-team/go-admin-core/sdk/service" | ||||||
|  | 	"gorm.io/gorm" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | type TmRechargeLog struct { | ||||||
|  | 	service.Service | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // 首页获取即将过期的数据 | ||||||
|  | func (e *TmRechargeLog) GetMemberAdvent(req *dto.TmRechargeLogFrontReq, resp *[]dto.TmRechargeLogFrontResp, userId int) int { | ||||||
|  | 	var datas []models.TmRechargeLog | ||||||
|  |  | ||||||
|  | 	if err := e.Orm.Model(&models.TmRechargeLog{}). | ||||||
|  | 		Where("user_id =? and platform_id =? and expire_at > now() and status =2", userId, req.PlatformId). | ||||||
|  | 		Find(&datas).Error; err != nil { | ||||||
|  | 		e.Log.Errorf("TmRechargeLogService GetMemberAdvent error:%s \r\n", err) | ||||||
|  | 		return statuscode.ServerError | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	for _, item := range datas { | ||||||
|  | 		respItem := dto.TmRechargeLogFrontResp{} | ||||||
|  | 		respItem.Id = item.Id | ||||||
|  | 		respItem.OrderNo = item.OrderNo | ||||||
|  | 		// respItem.PlatformName=item.PlatformName | ||||||
|  | 		respItem.TotalCharater = item.TotalChars | ||||||
|  | 		respItem.ExpireUnix = item.ExpireAt.Unix() | ||||||
|  | 		count, err := e.GetRemainByOrderNo(item.OrderNo) | ||||||
|  |  | ||||||
|  | 		if err != nil { | ||||||
|  | 			e.Log.Errorf("TmRechargeLogService GetRemainByOrderNo error:%s \r\n", err) | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		respItem.RemainCharater = count | ||||||
|  | 		*resp = append(*resp, respItem) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	sort.Slice(*resp, func(i, j int) bool { | ||||||
|  | 		return (*resp)[i].ExpireUnix < (*resp)[j].ExpireUnix | ||||||
|  | 	}) | ||||||
|  |  | ||||||
|  | 	return statuscode.Success | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // 后台充值 | ||||||
|  | func (e *TmRechargeLog) ManagerRecharge(req *dto.TmRechargeCreateOrderReq, p *actions.DataPermission) error { | ||||||
|  | 	ctx := context.Background() | ||||||
|  | 	var data models.TmRechargeLog | ||||||
|  | 	member, platform, memberPlatform, code := e.CreateOrderJudge(req) | ||||||
|  | 	if code != statuscode.Success { | ||||||
|  | 		return errors.New(statuscode.ErrorMessage[code]) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	req.Generate(&data) | ||||||
|  | 	now := time.Now() | ||||||
|  | 	data.OrderNo = utility.GenerateTraceID() | ||||||
|  | 	data.Type = 2 | ||||||
|  | 	data.Status = 2 | ||||||
|  | 	data.UserId = member.UserId | ||||||
|  | 	data.ExpireAt = time.Now().AddDate(0, 0, req.ExpireDays) | ||||||
|  | 	data.PayTime = &now | ||||||
|  |  | ||||||
|  | 	rechargeData := quota_manager.QuotaRecharge{ | ||||||
|  | 		QuotaID:  data.OrderNo, | ||||||
|  | 		Amount:   int64(data.TotalChars), | ||||||
|  | 		ExpireAt: data.ExpireAt.Unix(), | ||||||
|  | 	} | ||||||
|  | 	qmgr := quota_manager.NewQuotaManager(redishelper.DefaultRedis.GetClient()) | ||||||
|  |  | ||||||
|  | 	// 事务处理 | ||||||
|  | 	err := e.Orm.Transaction(func(tx *gorm.DB) error { | ||||||
|  | 		//写入充值记录 | ||||||
|  | 		if err1 := e.Orm.Create(&data).Error; err1 != nil { | ||||||
|  | 			return err1 | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		//更新用户翻译可用字符 | ||||||
|  | 		if err1 := tx.Model(&models.TmMemberPlatform{}).Where("id =?", memberPlatform.Id).Update("remaining_character", data.TotalChars).Error; err1 != nil { | ||||||
|  | 			return err1 | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		//写入可用字符 | ||||||
|  | 		if err1 := qmgr.AddQuota(ctx, member.ApiKey, platform.Code, rechargeData); err1 != nil { | ||||||
|  | 			return err1 | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		return nil | ||||||
|  | 	}) | ||||||
|  |  | ||||||
|  | 	return err | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // 新增充值校验 | ||||||
|  | func (e *TmRechargeLog) CreateOrderJudge(req *dto.TmRechargeCreateOrderReq) (models.TmMember, *models.TmPlatform, models.TmMemberPlatform, int) { | ||||||
|  | 	memberService := TmMember{Service: e.Service} | ||||||
|  | 	member := models.TmMember{} | ||||||
|  | 	if err := memberService.GetById(req.MemberId, &member); err != nil { | ||||||
|  | 		return models.TmMember{}, nil, models.TmMemberPlatform{}, statuscode.NotFindMember | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if member.ApiKey == "" { | ||||||
|  | 		return models.TmMember{}, nil, models.TmMemberPlatform{}, statuscode.NotFindApiKey | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	platformService := TmPlatform{Service: e.Service} | ||||||
|  | 	platform, err := platformService.GetById(req.PlatformId) | ||||||
|  |  | ||||||
|  | 	if err != nil { | ||||||
|  | 		e.Log.Errorf("获取平台信息失败:%s \r\n", err.Error()) | ||||||
|  | 		return models.TmMember{}, nil, models.TmMemberPlatform{}, statuscode.PlatformNotSupport | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if platform == nil || platform.Id == 0 { | ||||||
|  | 		return models.TmMember{}, nil, models.TmMemberPlatform{}, statuscode.PlatformNotSupport | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	memberPlatformService := TmMemberPlatform{Service: e.Service} | ||||||
|  | 	memberPlatform, err := memberPlatformService.GetOrInsert(&dto.TmRechargeLogInsertOrUpdateReq{ | ||||||
|  | 		MemberId:    req.MemberId, | ||||||
|  | 		PlatformId:  req.PlatformId, | ||||||
|  | 		PlatformKey: platform.Code, | ||||||
|  | 	}) | ||||||
|  |  | ||||||
|  | 	if err != nil { | ||||||
|  | 		return models.TmMember{}, nil, models.TmMemberPlatform{}, statuscode.ServerError | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if memberPlatform.Status == 2 { | ||||||
|  | 		return models.TmMember{}, nil, models.TmMemberPlatform{}, statuscode.MemberPlatformNotSupport | ||||||
|  | 	} | ||||||
|  | 	return member, platform, memberPlatform, statuscode.Success | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // 用户发起充值 | ||||||
|  | // return code | ||||||
|  | func (e TmRechargeLog) CreateOrder(req *dto.TmRechargeCreateOrderReq, apiKey string) int { | ||||||
|  | 	// ctx := context.Background() | ||||||
|  | 	// var data models.TmRechargeLog | ||||||
|  | 	// member, platform, memberPlatform, code := e.CreateOrderJudge(req) | ||||||
|  |  | ||||||
|  | 	// if code != statuscode.Success { | ||||||
|  | 	// 	return code | ||||||
|  | 	// } | ||||||
|  | 	// req.Generate(&data) | ||||||
|  | 	// now := time.Now() | ||||||
|  | 	// data.OrderNo = utility.GenerateTraceID() | ||||||
|  | 	// data.Type = 1 | ||||||
|  | 	// data.Status = 1 | ||||||
|  | 	// data.UserId = member.UserId | ||||||
|  | 	// data.ExpireAt = time.Now().AddDate(0, 0, 30) | ||||||
|  | 	// data.PaymentTime = &now | ||||||
|  |  | ||||||
|  | 	return statuscode.Success | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // 分页查询 | ||||||
|  | func (e *TmRechargeLog) GetPage(req *dto.TmRechargeLogGetPageReq, p *actions.DataPermission, datas *[]dto.TmRechargeLogResp, count *int64) error { | ||||||
|  | 	var err error | ||||||
|  | 	var data models.TmRechargeLog | ||||||
|  | 	var list []models.TmRechargeLog | ||||||
|  |  | ||||||
|  | 	err = e.Orm.Model(&data). | ||||||
|  | 		Scopes( | ||||||
|  | 			cDto.MakeCondition(req.GetNeedSearch()), | ||||||
|  | 			cDto.Paginate(req.GetPageSize(), req.GetPageIndex()), | ||||||
|  | 			actions.Permission(data.TableName(), p), | ||||||
|  | 		). | ||||||
|  | 		Find(list).Limit(-1).Offset(-1). | ||||||
|  | 		Count(count).Error | ||||||
|  | 	if err != nil { | ||||||
|  | 		e.Log.Errorf("TmPlatformService GetPage error:%s \r\n", err) | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // 根据充值订单号 获取剩余可用字符 | ||||||
|  | func (e *TmRechargeLog) GetRemainByOrderNo(orderNo string) (int, error) { | ||||||
|  | 	key := fmt.Sprintf("quota:%s", orderNo) | ||||||
|  | 	val, err := redishelper.DefaultRedis.HGetField(key, "amount") | ||||||
|  |  | ||||||
|  | 	if err != nil { | ||||||
|  | 		return 0, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return strconv.Atoi(val) | ||||||
|  | } | ||||||
| @ -1,6 +1,7 @@ | |||||||
| package service | package service | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
|  | 	"context" | ||||||
| 	"encoding/json" | 	"encoding/json" | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"go-admin/app/admin/models" | 	"go-admin/app/admin/models" | ||||||
| @ -95,6 +96,7 @@ func (s *TranslatorService) RegisterProvider(name string, provider Translator) { | |||||||
| // 翻译校验 | // 翻译校验 | ||||||
| // return statusCode | // return statusCode | ||||||
| func (s *TranslatorService) TranslateJudge(req *dto.TranslateReq, apiKey string) (result *dto.TranslateResult, respCode int) { | func (s *TranslatorService) TranslateJudge(req *dto.TranslateReq, apiKey string) (result *dto.TranslateResult, respCode int) { | ||||||
|  | 	ctx := context.Background() | ||||||
| 	tmMemberService := TmMember{Service: s.Service} | 	tmMemberService := TmMember{Service: s.Service} | ||||||
| 	tmPlatformAccount := TmPlatformAccount{Service: s.Service} | 	tmPlatformAccount := TmPlatformAccount{Service: s.Service} | ||||||
| 	memberInfo, err1 := tmMemberService.GetByKey(apiKey) | 	memberInfo, err1 := tmMemberService.GetByKey(apiKey) | ||||||
| @ -126,16 +128,23 @@ func (s *TranslatorService) TranslateJudge(req *dto.TranslateReq, apiKey string) | |||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	result, err := Translator.providers[req.Platform].Translate(req.Text, req.SourceLang, req.TargetLang) | 	decyDatas, err := tmMemberService.DecrByQuote(ctx, req.Platform, apiKey, count) | ||||||
|  |  | ||||||
|  | 	if err != nil { | ||||||
|  | 		s.Log.Errorf("翻译计数失败:%v", err) | ||||||
|  | 		return nil, statuscode.ServerError | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	result, err = Translator.providers[req.Platform].Translate(req.Text, req.SourceLang, req.TargetLang) | ||||||
|  |  | ||||||
| 	if err == nil { | 	if err == nil { | ||||||
| 		err2 := tmMemberService.DecrBy(req.Platform, apiKey, count) | 		// err2 := tmMemberService.DecrBy(req.Platform, apiKey, count) | ||||||
|  |  | ||||||
| 		if err2 != nil { | 		// if err2 != nil { | ||||||
| 			s.Log.Errorf("翻译计数失败:%v", err2) | 		// 	s.Log.Errorf("翻译计数失败:%v", err2) | ||||||
| 			respCode = statuscode.ServerError | 		// 	respCode = statuscode.ServerError | ||||||
| 			return | 		// 	return | ||||||
| 		} | 		// } | ||||||
|  |  | ||||||
| 		platformConfigInterface := Translator.config.ProviderConfigs[req.Platform] | 		platformConfigInterface := Translator.config.ProviderConfigs[req.Platform] | ||||||
|  |  | ||||||
| @ -149,6 +158,8 @@ func (s *TranslatorService) TranslateJudge(req *dto.TranslateReq, apiKey string) | |||||||
| 		//每日统计保留三天 | 		//每日统计保留三天 | ||||||
| 		redishelper.DefaultRedis.Expire(fmt.Sprintf(rediskey.TM_MEMBER_DAILY_COUNT, date, apiKey, req.Platform), 3*24*time.Hour) | 		redishelper.DefaultRedis.Expire(fmt.Sprintf(rediskey.TM_MEMBER_DAILY_COUNT, date, apiKey, req.Platform), 3*24*time.Hour) | ||||||
| 	} else { | 	} else { | ||||||
|  | 		tmMemberService.RefundQuote(ctx, apiKey, req.Platform, &decyDatas) | ||||||
|  |  | ||||||
| 		code = statuscode.ServerError | 		code = statuscode.ServerError | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | |||||||
| @ -35,6 +35,7 @@ import ( | |||||||
| 	"go-admin/common/storage" | 	"go-admin/common/storage" | ||||||
| 	ext "go-admin/config" | 	ext "go-admin/config" | ||||||
| 	"go-admin/utils/redishelper" | 	"go-admin/utils/redishelper" | ||||||
|  | 	"go-admin/utils/utility" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| var ( | var ( | ||||||
| @ -177,6 +178,9 @@ func run() error { | |||||||
| } | } | ||||||
|  |  | ||||||
| func initCommon() { | func initCommon() { | ||||||
|  | 	//初始化雪花算法 | ||||||
|  | 	utility.InitSnowflake() | ||||||
|  |  | ||||||
| 	redishelper.InitDefaultRedis(config.CacheConfig.Redis.Addr, config.CacheConfig.Redis.Password, config.CacheConfig.Redis.DB) | 	redishelper.InitDefaultRedis(config.CacheConfig.Redis.Addr, config.CacheConfig.Redis.Password, config.CacheConfig.Redis.DB) | ||||||
|  |  | ||||||
| 	if err := redishelper.DefaultRedis.Ping(); err != nil { | 	if err := redishelper.DefaultRedis.Ping(); err != nil { | ||||||
| @ -195,6 +199,7 @@ func initCommon() { | |||||||
|  |  | ||||||
| 		os.Exit(-1) | 		os.Exit(-1) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| } | } | ||||||
|  |  | ||||||
| var Router runtime.Router | var Router runtime.Router | ||||||
|  | |||||||
| @ -10,16 +10,19 @@ type Response struct { | |||||||
| } | } | ||||||
|  |  | ||||||
| var ErrorMessage = map[int]string{ | var ErrorMessage = map[int]string{ | ||||||
| 	Success:                 "success", | 	Success:                  "success", | ||||||
| 	Unauthorized:            "unauthorized", | 	Unauthorized:             "unauthorized", | ||||||
| 	ServerError:             "server error", | 	ServerError:              "server error", | ||||||
| 	NotFound:                "not found", | 	NotFound:                 "not found", | ||||||
| 	Forbidden:               "forbidden", | 	Forbidden:                "forbidden", | ||||||
| 	InvalidParams:           "invalid params", | 	InvalidParams:            "invalid params", | ||||||
| 	InSufficRemainChar:      "insufficent remain char", | 	InSufficRemainChar:       "insufficent remain char", | ||||||
| 	PlatformNotSupport:      "platform not support", | 	PlatformNotSupport:       "platform not support", | ||||||
| 	TransactionNotAvailable: "transaction not available", | 	TransactionNotAvailable:  "transaction not available", | ||||||
| 	ApiUnauthorized:         "api unauthorized", | 	ApiUnauthorized:          "api unauthorized", | ||||||
|  | 	NotFindMember:            "not find member", | ||||||
|  | 	NotFindApiKey:            "not find api key", | ||||||
|  | 	MemberPlatformNotSupport: "member platform not support", | ||||||
| } | } | ||||||
|  |  | ||||||
| const ( | const ( | ||||||
| @ -37,4 +40,8 @@ const ( | |||||||
| 	PlatformNotSupport      = 20002 //平台不支持 | 	PlatformNotSupport      = 20002 //平台不支持 | ||||||
| 	TransactionNotAvailable = 20003 //翻译服务不可用 | 	TransactionNotAvailable = 20003 //翻译服务不可用 | ||||||
| 	ApiUnauthorized         = 20004 //api禁止访问 | 	ApiUnauthorized         = 20004 //api禁止访问 | ||||||
|  |  | ||||||
|  | 	NotFindMember            = 30001 //未找到用户 | ||||||
|  | 	NotFindApiKey            = 30002 //未找到api key | ||||||
|  | 	MemberPlatformNotSupport = 30003 //用户平台不支持 | ||||||
| ) | ) | ||||||
|  | |||||||
							
								
								
									
										1
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										1
									
								
								go.mod
									
									
									
									
									
								
							| @ -126,6 +126,7 @@ require ( | |||||||
| 	github.com/shamsher31/goimgext v1.0.0 // indirect | 	github.com/shamsher31/goimgext v1.0.0 // indirect | ||||||
| 	github.com/shopspring/decimal v1.4.0 // indirect | 	github.com/shopspring/decimal v1.4.0 // indirect | ||||||
| 	github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect | 	github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect | ||||||
|  | 	github.com/sony/sonyflake v1.2.1 // indirect | ||||||
| 	github.com/spf13/cast v1.3.1 // indirect | 	github.com/spf13/cast v1.3.1 // indirect | ||||||
| 	github.com/spf13/pflag v1.0.3 // indirect | 	github.com/spf13/pflag v1.0.3 // indirect | ||||||
| 	github.com/tklauser/go-sysconf v0.3.9 // indirect | 	github.com/tklauser/go-sysconf v0.3.9 // indirect | ||||||
|  | |||||||
| @ -51,6 +51,14 @@ func NewRedisHelper(addr, password string, db int) *RedisHelper { | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func (r *RedisHelper) GetClient() *redis.Client { | ||||||
|  | 	return r.client | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (r *RedisHelper) GetCtx() context.Context { | ||||||
|  | 	return r.ctx | ||||||
|  | } | ||||||
|  |  | ||||||
| // 测试连接 | // 测试连接 | ||||||
| func (r *RedisHelper) Ping() error { | func (r *RedisHelper) Ping() error { | ||||||
| 	return r.client.Ping(r.ctx).Err() | 	return r.client.Ping(r.ctx).Err() | ||||||
| @ -100,6 +108,19 @@ func (r *RedisHelper) SetAdd(key, value string, expireTime time.Duration) error | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // 更新zset score | ||||||
|  | func (r *RedisHelper) ZUpdateScore(key string, score float64, value string) error { | ||||||
|  | 	return r.client.ZAddArgs(r.ctx, key, redis.ZAddArgs{ | ||||||
|  | 		XX: true, // 只更新已存在 | ||||||
|  | 		Members: []redis.Z{ | ||||||
|  | 			{ | ||||||
|  | 				Score:  float64(score), | ||||||
|  | 				Member: value, | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 	}).Err() | ||||||
|  | } | ||||||
|  |  | ||||||
| // 设置对象 | // 设置对象 | ||||||
| func SetObjString[T any](r *RedisHelper, key string, value T) error { | func SetObjString[T any](r *RedisHelper, key string, value T) error { | ||||||
| 	keyValue, err := sonic.Marshal(value) | 	keyValue, err := sonic.Marshal(value) | ||||||
| @ -736,6 +757,28 @@ func (e *RedisHelper) GetRevRangeScoresSortSet(key string) ([]redis.Z, error) { | |||||||
| 	return e.client.ZRevRangeWithScores(e.ctx, key, 0, -1).Result() | 	return e.client.ZRevRangeWithScores(e.ctx, key, 0, -1).Result() | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // ZSET 中按 score 范围取出成员 | ||||||
|  | func (r *RedisHelper) ZRangeByScore(key string, min, max string) ([]string, error) { | ||||||
|  | 	ctx := context.Background() | ||||||
|  | 	return r.client.ZRangeByScore(ctx, key, &redis.ZRangeBy{ | ||||||
|  | 		Min: min, | ||||||
|  | 		Max: max, | ||||||
|  | 	}).Result() | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // ZSET 中移除指定成员: | ||||||
|  | func (r *RedisHelper) ZRemValues(key string, members ...string) error { | ||||||
|  | 	ctx := context.Background() | ||||||
|  |  | ||||||
|  | 	// 转换为 interface{} 类型参数 | ||||||
|  | 	vals := make([]interface{}, len(members)) | ||||||
|  | 	for i, m := range members { | ||||||
|  | 		vals[i] = m | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return r.client.ZRem(ctx, key, vals...).Err() | ||||||
|  | } | ||||||
|  |  | ||||||
| // 获取最后一条数据 | // 获取最后一条数据 | ||||||
| func (e *RedisHelper) GetLastSortSet(key string) ([]redis.Z, error) { | func (e *RedisHelper) GetLastSortSet(key string) ([]redis.Z, error) { | ||||||
| 	// 获取最后一个元素及其分数 | 	// 获取最后一个元素及其分数 | ||||||
|  | |||||||
| @ -1,6 +1,7 @@ | |||||||
| package utility | package utility | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
|  | 	"strconv" | ||||||
| 	"strings" | 	"strings" | ||||||
|  |  | ||||||
| 	"github.com/rs/xid" | 	"github.com/rs/xid" | ||||||
| @ -14,6 +15,7 @@ import ( | |||||||
| 	"time" | 	"time" | ||||||
|  |  | ||||||
| 	log "github.com/go-admin-team/go-admin-core/logger" | 	log "github.com/go-admin-team/go-admin-core/logger" | ||||||
|  | 	"github.com/sony/sonyflake" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| const base62Chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" | const base62Chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" | ||||||
| @ -79,3 +81,20 @@ func GenerateBase62Key(length int) (string, error) { | |||||||
|  |  | ||||||
| 	return b.String(), nil | 	return b.String(), nil | ||||||
| } | } | ||||||
|  |  | ||||||
|  | var sf *sonyflake.Sonyflake | ||||||
|  |  | ||||||
|  | func InitSnowflake() { | ||||||
|  | 	sf = sonyflake.NewSonyflake(sonyflake.Settings{}) | ||||||
|  | 	if sf == nil { | ||||||
|  | 		log.Fatalf("Failed to initialize sonyflake") | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func GenerateTraceID() string { | ||||||
|  | 	id, err := sf.NextID() | ||||||
|  | 	if err != nil { | ||||||
|  | 		log.Fatalf("Failed to generate ID: %v", err) | ||||||
|  | 	} | ||||||
|  | 	return strconv.FormatUint(id, 10) | ||||||
|  | } | ||||||
|  | |||||||
		Reference in New Issue
	
	Block a user