From 9675612496dfa64d8184378ccd8099dc11a1c74c Mon Sep 17 00:00:00 2001 From: likun <906102152@qq.com> Date: Wed, 7 May 2025 18:25:31 +0800 Subject: [PATCH] optimize and add cdkey --- admin/apps/game/domain/cdkey.go | 27 + admin/apps/game/domain/comm_resource.go | 2 +- admin/apps/game/domain/entity/cdkey.go | 20 + admin/apps/game/domain/entity/utils.go | 21 +- admin/apps/game/domain/repo/cdkey.go | 49 ++ admin/apps/game/domain/repo/comm_resource.go | 2 +- admin/apps/game/model/cdkey.go | 24 +- admin/apps/game/model/dto/msg.go | 14 + admin/apps/game/model/rolemail.go | 7 +- admin/apps/game/server/ctl_cdkey.go | 36 + admin/apps/game/server/middleware.go | 5 + admin/apps/game/server/route.go | 9 +- admin/apps/game/service/service.go | 2 + admin/apps/game/service/service_cdkey.go | 21 + admin/internal/consts/consts.go | 7 +- admin/internal/context/ctx_web.go | 31 + ui/src/api/cdkey.js | 11 + ui/src/components/restful/tableCDKey.vue | 681 +++++++++++++++++++ ui/src/router/index.js | 3 + ui/src/utils/request.js | 9 + ui/src/views/Login.vue | 3 +- ui/src/views/project/project_cdkey.vue | 13 + 22 files changed, 979 insertions(+), 18 deletions(-) create mode 100644 admin/apps/game/domain/cdkey.go create mode 100644 admin/apps/game/domain/repo/cdkey.go create mode 100644 admin/apps/game/server/ctl_cdkey.go create mode 100644 admin/apps/game/service/service_cdkey.go create mode 100644 ui/src/api/cdkey.js create mode 100644 ui/src/components/restful/tableCDKey.vue create mode 100644 ui/src/views/project/project_cdkey.vue diff --git a/admin/apps/game/domain/cdkey.go b/admin/apps/game/domain/cdkey.go new file mode 100644 index 0000000..ee9fb49 --- /dev/null +++ b/admin/apps/game/domain/cdkey.go @@ -0,0 +1,27 @@ +package domain + +import ( + "admin/apps/game/domain/entity" + "admin/apps/game/domain/repo" + "gorm.io/gorm" +) + +type CDKeyService struct { + repo repo.ICDKeyRepo +} + +func NewCDKeyService(db *gorm.DB) *CDKeyService { + return &CDKeyService{repo: repo.NewCDKeyRepo(db)} +} + +func (svc *CDKeyService) AddCount(id int, delta int) (int, error) { + et, err := svc.repo.AddCount(id, delta) + if err != nil { + return 0, err + } + return et.GetCount(), nil +} + +func (svc *CDKeyService) Get(id int) (*entity.CDKey, bool, error) { + return svc.repo.GetByID(id) +} diff --git a/admin/apps/game/domain/comm_resource.go b/admin/apps/game/domain/comm_resource.go index 1a22a93..6b94418 100644 --- a/admin/apps/game/domain/comm_resource.go +++ b/admin/apps/game/domain/comm_resource.go @@ -27,7 +27,7 @@ func initCommonResourcesRepo(db *gorm.DB) { r(consts.ResourcesName_MailRole, "个人邮件", repo.NewCommonResourceRepo(db, &model.RoleMail{}), ShowMethod_Get|ShowMethod_Post) // 个人邮件发放就没法撤回? r(consts.ResourcesName_MailGlobal, "全服邮件", repo.NewCommonResourceRepo(db, &model.GlobalMail{}), ShowMethod_Get|ShowMethod_Post) // 直接删除,别修改了,玩家收到的更乱 r(consts.ResourcesName_Notice, "公告", repo.NewCommonResourceRepo(db, &model.Notice{}), ShowMethod_Get|ShowMethod_Post|ShowMethod_Put|ShowMethod_Delete) - r(consts.ResourcesName_RewardCode, "奖励码", repo.NewCommonResourceRepo(db, &model.CDKey{}), ShowMethod_Get) + r(consts.ResourcesName_CDKey, "礼包码", repo.NewCommonResourceRepo(db, &model.CDKey{}), ShowMethod_Get|ShowMethod_Post|ShowMethod_Put|ShowMethod_Delete) r(consts.ResourcesName_DevicePush, "设备推送", repo.NewCommonResourceRepo(db, &model.DevicePush{}), ShowMethod_Get) } diff --git a/admin/apps/game/domain/entity/cdkey.go b/admin/apps/game/domain/entity/cdkey.go index 7ea991b..72a435c 100644 --- a/admin/apps/game/domain/entity/cdkey.go +++ b/admin/apps/game/domain/entity/cdkey.go @@ -2,6 +2,7 @@ package entity import ( "admin/apps/game/model" + "admin/internal/consts" "admin/lib/cdkey" ) @@ -17,6 +18,25 @@ func NewCDKey(po *model.CDKey) *CDKey { } } +func (c *CDKey) GetName() string { + return c.Po.Name +} + +func (c *CDKey) IsGlobalType() bool { + return c.Po.CodeType == consts.CDKeyType_Global +} + func (c *CDKey) GenerateKeys() []string { + if c.IsGlobalType() { + return []string{c.Po.Code} + } return cdkey.GenerateAll(c.Po.ID, MaxKeyNum)[:c.Po.CodeNum] } + +func (c *CDKey) AddCount(delta int) { + c.Po.CodeNum += delta +} + +func (c *CDKey) GetCount() int { + return c.Po.CodeNum +} diff --git a/admin/apps/game/domain/entity/utils.go b/admin/apps/game/domain/entity/utils.go index ea3e3e0..ffece39 100644 --- a/admin/apps/game/domain/entity/utils.go +++ b/admin/apps/game/domain/entity/utils.go @@ -4,6 +4,7 @@ import ( "admin/apps/game/model" "admin/apps/game/model/dto" "admin/lib/xlog" + "database/sql" "fmt" "gorm.io/gorm" "reflect" @@ -31,6 +32,13 @@ func poToCommonDtoNo(po any) dto.CommonDtoValues { if ft.Type.Name() == "Time" && ft.Type.PkgPath() == "time" { obj[ft.Name] = fo.Interface().(time.Time).Format("2006/01/02 15:04:05") + } else if ft.Type.Name() == "NullTime" { + t := fo.Interface().(sql.NullTime) + if t.Valid { + obj[ft.Name] = t.Time.Format("2006/01/02 15:04:05") + } else { + obj[ft.Name] = "" + } } } @@ -62,6 +70,9 @@ func getFieldTypeDtoDescInfo(project *Project, poValue reflect.Value, fieldType if tagType := fieldType.Tag.Get("type"); tagType != "" { f1.Type = tagType } + if f1.Type == "NullTime" { + f1.Type = "Time" + } cf := fieldType.Tag.Get("choices") if cf != "" { @@ -128,6 +139,12 @@ func parseStr2FieldValue(field reflect.StructField, rawValue any) (realSetValue xlog.Debugf("time content:%v", setValue) t, _ := time.ParseInLocation("2006/01/02 15:04:05", setValue, time.Local) return reflect.ValueOf(t) + } else if typeName == "NullTime" { + t, err := time.ParseInLocation("2006/01/02 15:04:05", setValue, time.Local) + if err != nil { + return reflect.ValueOf(sql.NullTime{Time: t}) + } + return reflect.ValueOf(sql.NullTime{Time: t, Valid: true}) } if typeName == "DeletedAt" { return reflect.ValueOf(gorm.DeletedAt{}) @@ -163,13 +180,15 @@ func parseStr2FieldValue(field reflect.StructField, rawValue any) (realSetValue for _, itemI := range rawValue.([]interface{}) { item := &model.MailAttachItem{} for k, vI := range itemI.(map[string]any) { - v := vI.(float64) + v, _ := vI.(float64) if k == "id" { item.ID = int32(v) } else if k == "num" { item.Num = int64(v) } else if k == "item_type" { item.ItemType = int(v) + } else if k == "desc" { + item.Desc = vI.(string) } } items = append(items, item) diff --git a/admin/apps/game/domain/repo/cdkey.go b/admin/apps/game/domain/repo/cdkey.go new file mode 100644 index 0000000..20d7404 --- /dev/null +++ b/admin/apps/game/domain/repo/cdkey.go @@ -0,0 +1,49 @@ +package repo + +import ( + "admin/apps/game/domain/entity" + "admin/apps/game/model" + "admin/internal/errcode" + "errors" + "gorm.io/gorm" +) + +type ICDKeyRepo interface { + GetByID(id int) (*entity.CDKey, bool, error) + AddCount(id int, delta int) (*entity.CDKey, error) +} + +func NewCDKeyRepo(db *gorm.DB) ICDKeyRepo { + return &cdKeyRepoImpl{db: db} +} + +type cdKeyRepoImpl struct { + db *gorm.DB +} + +func (impl *cdKeyRepoImpl) AddCount(id int, delta int) (*entity.CDKey, error) { + po := new(model.CDKey) + err := impl.db.Where("id = ?", id).First(po).Error + if err != nil { + return nil, errcode.New(errcode.ParamsInvalid, "not found cdkey conf:%v", id) + } + et := entity.NewCDKey(po) + et.AddCount(delta) + err = impl.db.Where("id = ?", id).Updates(po).Error + if err != nil { + return nil, errcode.New(errcode.DBError, "update data:%+v error:%v", po, id) + } + return et, nil +} + +func (impl *cdKeyRepoImpl) GetByID(id int) (*entity.CDKey, bool, error) { + po := new(model.CDKey) + err := impl.db.Where("id = ?", id).First(po).Error + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return nil, false, nil + } + return nil, false, errcode.New(errcode.ParamsInvalid, "not found cdkey conf:%v", id) + } + return entity.NewCDKey(po), true, nil +} diff --git a/admin/apps/game/domain/repo/comm_resource.go b/admin/apps/game/domain/repo/comm_resource.go index c0c80ac..67e7607 100644 --- a/admin/apps/game/domain/repo/comm_resource.go +++ b/admin/apps/game/domain/repo/comm_resource.go @@ -102,7 +102,7 @@ func (repo *commonResourceRepoImpl) Create(projectEt *entity.Project, dtoObj dto func (repo *commonResourceRepoImpl) Edit(projectEt *entity.Project, dtoObj dto.CommonDtoValues) error { et := (&entity.CommonResource{}).FromPo(repo.newEmptyPo()).FromDto(dtoObj) - err := repo.db.Where("id=?", et.Po.GetId()).Updates(et.Po).Error + err := repo.db.Where("id=?", et.Po.GetId()).Save(et.Po).Error if err != nil { return errcode.New(errcode.DBError, "edit resource:%v obj:%+v error:%v", repo.poTemplate.TableName(), et, err) } diff --git a/admin/apps/game/model/cdkey.go b/admin/apps/game/model/cdkey.go index a063622..a4a7488 100644 --- a/admin/apps/game/model/cdkey.go +++ b/admin/apps/game/model/cdkey.go @@ -2,7 +2,9 @@ package model import ( "admin/apps/game/model/dto" + "admin/internal/consts" "admin/internal/db" + "database/sql" "time" ) @@ -13,14 +15,14 @@ func init() { type CDKey struct { ID int `gorm:"primarykey" readonly:"true"` ProjectId int `gorm:"index:idx_project_id"` - Desc string `name:"礼包说明" required:"true"` - ServerIDs []string `gorm:"type:json;serializer:json" name:"区服" type:"[]string" choices:"GetChoiceServers" multi_choice:"true"` - CodeType int `name:"礼包类型" required:"true" choices:"GetCodeTypeChoices"` + Name string `name:"礼包说明" required:"true" uneditable:"true"` + ServerIDs []string `gorm:"type:json;serializer:json" name:"区服" desc:"不选就是全服通用" type:"[]string" choices:"GetChoiceServers" multi_choice:"true" uneditable:"true""` + CodeType int `name:"礼包类型" required:"true" choices:"GetCodeTypeChoices" uneditable:"true"` Code string `name:"礼包码" desc:"一码通用才配置"` CodeNum int `name:"礼包数量" desc:"一码一用才配置"` - ValidStartTime time.Time `name:"生效起始时间"` - ValidEndTime time.Time `name:"生效结束时间"` - RewardList []*MailAttachItem `gorm:"type:json;serializer:json" name:"邮件附件" type:"items" desc:"搜索道具并点击添加"` + ValidStartTime sql.NullTime `name:"生效起始时间"` + ValidEndTime sql.NullTime `name:"生效结束时间"` + Attach []*MailAttachItem `gorm:"type:json;serializer:json" name:"礼包码奖励道具" type:"items" desc:"搜索道具并点击添加"` CreatedAt time.Time `readonly:"true"` } @@ -33,9 +35,13 @@ func (m *CDKey) GetId() int { return m.ID } -func (m *CDKey) GetCodeTypeChoices() []*dto.CommonDtoFieldChoice { +func (m *CDKey) GetCodeTypeChoices(project *Project) []*dto.CommonDtoFieldChoice { return []*dto.CommonDtoFieldChoice{ - {Desc: "一码通用", Value: 0}, - {Desc: "一码一用", Value: 1}, + {Desc: "一码通用", Value: consts.CDKeyType_Global}, + {Desc: "一码一用", Value: consts.CDKeyType_Private}, } } + +func (m *CDKey) GetChoiceServers(project *Project) []*dto.CommonDtoFieldChoice { + return getChoiceServers(project) +} diff --git a/admin/apps/game/model/dto/msg.go b/admin/apps/game/model/dto/msg.go index dd8bc54..371b328 100644 --- a/admin/apps/game/model/dto/msg.go +++ b/admin/apps/game/model/dto/msg.go @@ -54,3 +54,17 @@ type CommandListRsp struct { type GetProjectAllItemsRsp struct { Items []*CommonDtoFieldChoice `json:"items"` } + +type CDKeyExportFileReq struct { + ID int `json:"id"` +} + +type CDKeyAddCountReq struct { + ID int `json:"id"` + AddCount int `json:"add_count"` +} + +type CDKeyAddCountRsp struct { + ID int `json:"id"` + NewCount int `json:"new_count"` +} diff --git a/admin/apps/game/model/rolemail.go b/admin/apps/game/model/rolemail.go index ebc5a84..9c22805 100644 --- a/admin/apps/game/model/rolemail.go +++ b/admin/apps/game/model/rolemail.go @@ -11,9 +11,10 @@ func init() { } type MailAttachItem struct { - ID int32 `json:"id"` - Num int64 `json:"num"` - ItemType int `json:"item_type"` + ID int32 `json:"id"` + Num int64 `json:"num"` + Desc string `json:"desc"` + ItemType int `json:"item_type"` } type RoleMail struct { diff --git a/admin/apps/game/server/ctl_cdkey.go b/admin/apps/game/server/ctl_cdkey.go new file mode 100644 index 0000000..ffe8f67 --- /dev/null +++ b/admin/apps/game/server/ctl_cdkey.go @@ -0,0 +1,36 @@ +package server + +import ( + "admin/apps/game/model/dto" + "admin/internal/context" + "bytes" + "fmt" +) + +func (ctl *controller) CDKeyExportFile(ctx *context.WebContext, params *dto.CDKeyExportFileReq, rsp *dto.NilRsp) error { + et, keys, err := ctl.svc.GetCDKeyAllKeys(params.ID) + if err != nil { + return err + } + + content := bytes.NewBuffer(nil) + content.WriteString(fmt.Sprintf("礼包码描述:%s\n", et.GetName())) + content.WriteString(fmt.Sprintf("礼包码数量:%v\n\n", et.GetCount())) + content.WriteString(fmt.Sprintf("礼包码列表:\n")) + for _, key := range keys { + content.WriteString(fmt.Sprintf("%s\n", key)) + } + + ctx.OkFile(fmt.Sprintf("礼包码(%v-%v个).txt", et.GetName(), et.GetCount()), content.String()) + + return nil +} + +func (ctl *controller) CDKeyAddCount(ctx *context.WebContext, params *dto.CDKeyAddCountReq, rsp *dto.CDKeyAddCountRsp) error { + newCount, err := ctl.svc.CDKeyAddCount(params.ID, params.AddCount) + if err != nil { + return err + } + rsp.NewCount = newCount + return nil +} diff --git a/admin/apps/game/server/middleware.go b/admin/apps/game/server/middleware.go index 7a14c6c..2c9ce71 100644 --- a/admin/apps/game/server/middleware.go +++ b/admin/apps/game/server/middleware.go @@ -5,9 +5,14 @@ import ( "admin/internal/context" "admin/internal/errcode" "admin/internal/permission" + "strings" ) func (srv *Server) CheckToken(ctx *context.WebContext) { + if strings.Contains(ctx.GinCtx().Request.URL.Path, "/login") { + return + } + err := ctx.ExtractHeader() if err != nil { ctx.Fail(errcode.New(errcode.HeaderParamsInvalid, "header invalid")) diff --git a/admin/apps/game/server/route.go b/admin/apps/game/server/route.go index 3096018..4c8af4f 100644 --- a/admin/apps/game/server/route.go +++ b/admin/apps/game/server/route.go @@ -9,7 +9,7 @@ func (srv *Server) Route(engine *web.Engine) { engine.Use(srv.CheckToken) apiGroup := engine.Group("/api", "") - + { // 注册项目增删改查接口 projectGroup := apiGroup.Group("/"+consts.ResourcesName_Project, "项目") @@ -28,5 +28,12 @@ func (srv *Server) Route(engine *web.Engine) { } projectGroup.Get("/:projectId/items", "获取项目所有道具列表", consts.WebPathPermit_Read, srv.ctl.GetProjectAllItems) + + { + // 礼包码特殊接口 + cdkeyGroup := projectGroup.Group("/:projectId/cdkey/special", "") + cdkeyGroup.Get("/add_count", "礼包码数量追加", consts.WebPathPermit_Write, srv.ctl.CDKeyAddCount) + cdkeyGroup.Get("/export", "导出礼包码文件", consts.WebPathPermit_Write, srv.ctl.CDKeyExportFile) + } } } diff --git a/admin/apps/game/service/service.go b/admin/apps/game/service/service.go index 39f60e4..3c52de1 100644 --- a/admin/apps/game/service/service.go +++ b/admin/apps/game/service/service.go @@ -16,6 +16,7 @@ type Service struct { db *gorm.DB resourceSvc *domain.CommonResourceService projectSvc *domain.ProjectService + cdkeySvc *domain.CDKeyService } func New(db *gorm.DB) (*Service, error) { @@ -23,6 +24,7 @@ func New(db *gorm.DB) (*Service, error) { db: db, resourceSvc: domain.NewCommonResourceService(db), projectSvc: domain.NewProjectService(db), + cdkeySvc: domain.NewCDKeyService(db), } api.RegisterGameApi(svc) //err := svc.ensureProjectsDBData() diff --git a/admin/apps/game/service/service_cdkey.go b/admin/apps/game/service/service_cdkey.go new file mode 100644 index 0000000..87014c9 --- /dev/null +++ b/admin/apps/game/service/service_cdkey.go @@ -0,0 +1,21 @@ +package service + +import ( + "admin/apps/game/domain/entity" + "admin/internal/errcode" +) + +func (svc *Service) GetCDKeyAllKeys(id int) (*entity.CDKey, []string, error) { + et, find, err := svc.cdkeySvc.Get(id) + if err != nil { + return nil, nil, err + } + if !find { + return nil, nil, errcode.New(errcode.ParamsInvalid, "not found cdkey:%v", id) + } + return et, et.GenerateKeys(), nil +} + +func (svc *Service) CDKeyAddCount(id int, deltaCount int) (int, error) { + return svc.cdkeySvc.AddCount(id, deltaCount) +} diff --git a/admin/internal/consts/consts.go b/admin/internal/consts/consts.go index 77436f7..1359c74 100644 --- a/admin/internal/consts/consts.go +++ b/admin/internal/consts/consts.go @@ -32,7 +32,7 @@ const ( ResourcesName_MailRole = "mail_role" ResourcesName_MailGlobal = "mail_global" ResourcesName_Notice = "notice" - ResourcesName_RewardCode = "reward_code" + ResourcesName_CDKey = "cdkey" ResourcesName_DevicePush = "device_push" ) @@ -49,3 +49,8 @@ const ( BanType_RoleChat = "role_chat" BanType_Device = "device" ) + +const ( + CDKeyType_Global = 0 // 一码通用 + CDKeyType_Private = 1 // 一码一用 +) diff --git a/admin/internal/context/ctx_web.go b/admin/internal/context/ctx_web.go index 75fbf65..918a4f8 100644 --- a/admin/internal/context/ctx_web.go +++ b/admin/internal/context/ctx_web.go @@ -5,7 +5,12 @@ import ( "admin/lib/web" "admin/lib/xlog" "context" + "fmt" "github.com/gin-gonic/gin" + "github.com/gin-gonic/gin/render" + "net/http" + "net/url" + "strings" ) type WebHeader struct { @@ -74,6 +79,32 @@ func (ctx *WebContext) Fail(err error) { ctx.alreadySetRsp = true } +func (ctx *WebContext) OkFile(fileName string, content string) { + if ctx.alreadySetRsp { + return + } + + reader := strings.NewReader(string(content)) + contentLength := len(content) + contentType := "" + + fileName = url.QueryEscape(fileName) + valueName := fmt.Sprintf(`"attachment; filename*=utf-8''%v"`, fileName) + extraHeaders := map[string]string{ + "Content-Disposition": valueName, + "Content-Transfer-Encoding": "binary", + } + + ctx.GinCtx().Render(http.StatusOK, render.Reader{ + Headers: extraHeaders, + ContentType: contentType, + ContentLength: int64(contentLength), + Reader: reader, + }) + + ctx.alreadySetRsp = true +} + func (ctx *WebContext) HandleError(path string, err error) { xlog.Errorf("path:%v handle error:%v ", path, err) ctx.Fail(err) diff --git a/ui/src/api/cdkey.js b/ui/src/api/cdkey.js new file mode 100644 index 0000000..8c8edd4 --- /dev/null +++ b/ui/src/api/cdkey.js @@ -0,0 +1,11 @@ +import request from '@/utils/request' + + +export function cdkeyExport(baseUrl, params) { + return request({ + url: baseUrl + '/special/export', + method: 'get', + params: params, + responseType: 'blob', + }) +} \ No newline at end of file diff --git a/ui/src/components/restful/tableCDKey.vue b/ui/src/components/restful/tableCDKey.vue new file mode 100644 index 0000000..3ae935f --- /dev/null +++ b/ui/src/components/restful/tableCDKey.vue @@ -0,0 +1,681 @@ + + + + + \ No newline at end of file diff --git a/ui/src/router/index.js b/ui/src/router/index.js index 401078c..a328201 100644 --- a/ui/src/router/index.js +++ b/ui/src/router/index.js @@ -123,6 +123,9 @@ export function setProjectOperationRoutes(projectList) { component: () => import('@/views/project/project_op.vue'), props: true } + if (resource.resource === 'cdkey') { + resourceRoute.component = () => import('@/views/project/project_cdkey.vue') + } resource.show_methods.forEach((method) => { if (method == "get") { projectHasAnyResourcePermission = true diff --git a/ui/src/utils/request.js b/ui/src/utils/request.js index 5a18fe7..dd12e84 100644 --- a/ui/src/utils/request.js +++ b/ui/src/utils/request.js @@ -47,6 +47,15 @@ const reqInterceptor = (config) => { const resInterceptor = (res) => { console.log("res:", res.data) + const contentDisposition = res.headers['content-disposition']; + const filenameRegex = /filename\*?=(?:UTF-8'')?"?([^";]+)"?/i; + if (contentDisposition) { + const matches = contentDisposition.match(filenameRegex); + if (matches) { + return res + } + } + const code = res.data.code if (code != 200) { if (code === 5) { diff --git a/ui/src/views/Login.vue b/ui/src/views/Login.vue index c48358e..5aca9b9 100644 --- a/ui/src/views/Login.vue +++ b/ui/src/views/Login.vue @@ -1,6 +1,6 @@