From f03d2d0b12bfa94f05511eb4b90e1ab2f68d7a4e Mon Sep 17 00:00:00 2001 From: skyle1995 Date: Fri, 24 Oct 2025 01:48:54 +0800 Subject: [PATCH] Remove the card password related New application-related --- .gitignore | 4 +- constants/status.go | 31 -- controllers/admin/app.go | 359 +++++++++++++ controllers/admin/card.go | 650 ----------------------- controllers/admin/card_stats.go | 181 ------- controllers/admin/card_type.go | 428 --------------- controllers/admin/login_type.go | 394 -------------- database/migrate.go | 2 +- database/seed_settings.go | 5 - models/app.go | 12 + models/card.go | 27 - models/card_type.go | 24 - models/login_type.go | 24 - recharge.db | Bin 0 -> 45056 bytes server/admin.go | 44 +- services/query.go | 30 -- web/template/admin/apps.html | 305 +++++++++-- web/template/admin/card_types.html | 415 --------------- web/template/admin/cards.html | 771 ---------------------------- web/template/admin/dashboard.html | 144 +----- web/template/admin/layout.html | 8 - web/template/admin/login_types.html | 422 --------------- 22 files changed, 657 insertions(+), 3623 deletions(-) delete mode 100644 controllers/admin/card.go delete mode 100644 controllers/admin/card_stats.go delete mode 100644 controllers/admin/card_type.go delete mode 100644 controllers/admin/login_type.go delete mode 100644 models/card.go delete mode 100644 models/card_type.go delete mode 100644 models/login_type.go create mode 100644 recharge.db delete mode 100644 web/template/admin/card_types.html delete mode 100644 web/template/admin/cards.html delete mode 100644 web/template/admin/login_types.html diff --git a/.gitignore b/.gitignore index f39d356..35e3873 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,6 @@ .DS_Store networkDev node.txt -recharge.db config.json -data.db +database.db +模板 diff --git a/constants/status.go b/constants/status.go index 60a7541..0615256 100644 --- a/constants/status.go +++ b/constants/status.go @@ -1,36 +1,5 @@ package constants - - -// 卡密状态常量 -// CardStatus 定义卡密的状态 -const ( - // CardStatusUnused 未使用 - CardStatusUnused = 0 - // CardStatusUsed 已使用 - CardStatusUsed = 1 - // CardStatusDisabled 禁用 - CardStatusDisabled = 2 -) - -// 登录类型状态常量 -// LoginTypeStatus 定义登录类型的状态 -const ( - // LoginTypeStatusDisabled 禁用 - LoginTypeStatusDisabled = 0 - // LoginTypeStatusEnabled 启用 - LoginTypeStatusEnabled = 1 -) - -// 卡密类型状态常量 -// CardTypeStatus 定义卡密类型的状态 -const ( - // CardTypeStatusDisabled 禁用 - CardTypeStatusDisabled = 0 - // CardTypeStatusEnabled 启用 - CardTypeStatusEnabled = 1 -) - // 验证码类型常量 // VerificationCodeType 定义验证码的类型 const ( diff --git a/controllers/admin/app.go b/controllers/admin/app.go index 773f6ce..bdd591c 100644 --- a/controllers/admin/app.go +++ b/controllers/admin/app.go @@ -2,6 +2,7 @@ package admin import ( "crypto/rand" + "encoding/base64" "encoding/hex" "encoding/json" "net/http" @@ -87,6 +88,141 @@ func AppsListHandler(w http.ResponseWriter, r *http.Request) { json.NewEncoder(w).Encode(response) } +// AppGetAnnouncementHandler 获取应用程序公告处理器 +func AppGetAnnouncementHandler(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodGet { + http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) + return + } + + // 获取UUID参数 + uuid := r.URL.Query().Get("uuid") + if uuid == "" { + response := map[string]interface{}{ + "code": 1, + "msg": "应用UUID不能为空", + } + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(response) + return + } + + // 获取数据库连接 + db, err := database.GetDB() + if err != nil { + logrus.WithError(err).Error("Failed to get database connection") + response := map[string]interface{}{ + "code": 1, + "msg": "数据库连接失败", + } + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(response) + return + } + + // 查找应用 + var app models.App + if err := db.Where("uuid = ?", uuid).First(&app).Error; err != nil { + logrus.WithError(err).Error("Failed to find app") + response := map[string]interface{}{ + "code": 1, + "msg": "应用不存在", + } + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(response) + return + } + + // 解码base64公告内容 + var announcement string + if app.Announcement != "" { + decodedBytes, err := base64.StdEncoding.DecodeString(app.Announcement) + if err != nil { + logrus.WithError(err).Error("Failed to decode announcement") + // 如果解码失败,返回空字符串 + announcement = "" + } else { + announcement = string(decodedBytes) + } + } + + response := map[string]interface{}{ + "code": 0, + "msg": "获取成功", + "data": map[string]interface{}{ + "announcement": announcement, + }, + } + + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(response) +} + +// AppResetSecretHandler 重置应用密钥API处理器 +func AppResetSecretHandler(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodPost { + http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) + return + } + + var req struct { + UUID string `json:"uuid"` + } + + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + http.Error(w, "Invalid JSON", http.StatusBadRequest) + return + } + + if req.UUID == "" { + http.Error(w, "应用UUID不能为空", http.StatusBadRequest) + return + } + + db, err := database.GetDB() + if err != nil { + logrus.WithError(err).Error("Failed to get database connection") + http.Error(w, "数据库连接失败", http.StatusInternalServerError) + return + } + + // 查找应用 + var app models.App + if err := db.Where("uuid = ?", req.UUID).First(&app).Error; err != nil { + logrus.WithError(err).Error("Failed to find app by UUID") + http.Error(w, "应用不存在", http.StatusNotFound) + return + } + + // 生成新的密钥 + bytes := make([]byte, 16) + if _, err := rand.Read(bytes); err != nil { + logrus.WithError(err).Error("Failed to generate random secret") + http.Error(w, "生成密钥失败", http.StatusInternalServerError) + return + } + newSecret := strings.ToUpper(hex.EncodeToString(bytes)) + + // 更新密钥 + if err := db.Model(&app).Update("secret", newSecret).Error; err != nil { + logrus.WithError(err).Error("Failed to update app secret") + http.Error(w, "更新密钥失败", http.StatusInternalServerError) + return + } + + response := map[string]interface{}{ + "code": 0, + "msg": "密钥重置成功", + "data": map[string]interface{}{ + "uuid": app.UUID, + "secret": newSecret, + }, + } + + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(response) +} + // AppCreateHandler 创建应用API处理器 func AppCreateHandler(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { @@ -386,3 +522,226 @@ func AppsBatchUpdateStatusHandler(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(response) } + +// AppUpdateAnnouncementHandler 更新应用程序公告处理器 +func AppUpdateAnnouncementHandler(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodPost { + http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) + return + } + + // 解析请求体 + var req struct { + UUID string `json:"uuid"` + Announcement string `json:"announcement"` + } + + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + logrus.WithError(err).Error("Failed to decode request body") + response := map[string]interface{}{ + "code": 1, + "msg": "请求参数格式错误", + } + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(response) + return + } + + // 验证UUID + if req.UUID == "" { + response := map[string]interface{}{ + "code": 1, + "msg": "应用UUID不能为空", + } + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(response) + return + } + + // 验证UUID格式 + if _, err := uuid.Parse(req.UUID); err != nil { + logrus.WithError(err).Error("Invalid UUID format") + response := map[string]interface{}{ + "code": 1, + "msg": "无效的UUID格式", + } + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(response) + return + } + + // 获取数据库连接 + db, err := database.GetDB() + if err != nil { + logrus.WithError(err).Error("Failed to get database connection") + response := map[string]interface{}{ + "code": 1, + "msg": "数据库连接失败", + } + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(response) + return + } + + // 查找应用 + var app models.App + if err := db.Where("uuid = ?", req.UUID).First(&app).Error; err != nil { + logrus.WithError(err).Error("Failed to find app") + response := map[string]interface{}{ + "code": 1, + "msg": "应用不存在", + } + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(response) + return + } + + // 对公告内容进行base64编码 + encodedAnnouncement := base64.StdEncoding.EncodeToString([]byte(req.Announcement)) + + // 更新应用的公告内容 + if err := db.Model(&app).Update("announcement", encodedAnnouncement).Error; err != nil { + logrus.WithError(err).Error("Failed to update app announcement") + response := map[string]interface{}{ + "code": 1, + "msg": "更新程序公告失败", + } + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(response) + return + } + + logrus.WithFields(logrus.Fields{ + "app_uuid": req.UUID, + "app_name": app.Name, + }).Info("App announcement updated successfully") + + response := map[string]interface{}{ + "code": 0, + "msg": "程序公告更新成功", + } + + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(response) +} + +// ... existing code ... + +// AppGetMultiConfigHandler 获取应用多开配置 +func AppGetMultiConfigHandler(w http.ResponseWriter, r *http.Request) { + appUUID := r.URL.Query().Get("uuid") + if appUUID == "" { + http.Error(w, "缺少应用UUID", http.StatusBadRequest) + return + } + + // 验证UUID格式 + if _, err := uuid.Parse(appUUID); err != nil { + http.Error(w, "无效的UUID格式", http.StatusBadRequest) + return + } + + db, err := database.GetDB() + if err != nil { + logrus.WithError(err).Error("Failed to get database connection") + http.Error(w, "数据库连接失败", http.StatusInternalServerError) + return + } + + var app models.App + if err := db.Where("uuid = ?", appUUID).First(&app).Error; err != nil { + logrus.WithError(err).Error("Failed to find app") + http.Error(w, "应用不存在", http.StatusNotFound) + return + } + + // 返回多开配置信息 + response := map[string]interface{}{ + "login_type": app.LoginType, + "multi_open_scope": app.MultiOpenScope, + "clean_interval": app.CleanInterval, + "check_interval": app.CheckInterval, + "multi_open_count": app.MultiOpenCount, + } + + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(response) +} + +// AppUpdateMultiConfigHandler 更新应用多开配置 +func AppUpdateMultiConfigHandler(w http.ResponseWriter, r *http.Request) { + var req struct { + UUID string `json:"uuid"` + LoginType int `json:"login_type"` + MultiOpenScope int `json:"multi_open_scope"` + CleanInterval int `json:"clean_interval"` + CheckInterval int `json:"check_interval"` + MultiOpenCount int `json:"multi_open_count"` + } + + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + http.Error(w, "Invalid JSON", http.StatusBadRequest) + return + } + + // 验证UUID格式 + if _, err := uuid.Parse(req.UUID); err != nil { + http.Error(w, "无效的UUID格式", http.StatusBadRequest) + return + } + + // 验证参数范围 + if req.LoginType < 0 || req.LoginType > 1 { + http.Error(w, "登录方式参数无效", http.StatusBadRequest) + return + } + if req.MultiOpenScope < 0 || req.MultiOpenScope > 2 { + http.Error(w, "多开范围参数无效", http.StatusBadRequest) + return + } + if req.CleanInterval < 1 { + http.Error(w, "清理间隔必须大于0", http.StatusBadRequest) + return + } + if req.CheckInterval < 1 { + http.Error(w, "校验间隔必须大于0", http.StatusBadRequest) + return + } + if req.MultiOpenCount < 1 { + http.Error(w, "多开数量必须大于0", http.StatusBadRequest) + return + } + + db, err := database.GetDB() + if err != nil { + logrus.WithError(err).Error("Failed to get database connection") + http.Error(w, "数据库连接失败", http.StatusInternalServerError) + return + } + + // 查找应用 + var app models.App + if err := db.Where("uuid = ?", req.UUID).First(&app).Error; err != nil { + logrus.WithError(err).Error("Failed to find app") + http.Error(w, "应用不存在", http.StatusNotFound) + return + } + + // 更新多开配置 + updates := map[string]interface{}{ + "login_type": req.LoginType, + "multi_open_scope": req.MultiOpenScope, + "clean_interval": req.CleanInterval, + "check_interval": req.CheckInterval, + "multi_open_count": req.MultiOpenCount, + } + + if err := db.Model(&app).Updates(updates).Error; err != nil { + logrus.WithError(err).Error("Failed to update app multi config") + http.Error(w, "更新多开配置失败", http.StatusInternalServerError) + return + } + + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(map[string]string{"message": "多开配置更新成功"}) +} diff --git a/controllers/admin/card.go b/controllers/admin/card.go deleted file mode 100644 index b057e37..0000000 --- a/controllers/admin/card.go +++ /dev/null @@ -1,650 +0,0 @@ -package admin - -import ( - "crypto/rand" - // 移除 CSV 导出,改为自定义分隔符文本导出 - // "encoding/csv" - "encoding/hex" - "encoding/json" - "fmt" - "net/http" - "networkDev/database" - "networkDev/models" - "networkDev/utils" - "strconv" - "strings" - "time" - - "github.com/sirupsen/logrus" -) - -// 生成指定长度的十六进制随机字符串 -// 入参 n 表示需要的随机字符数(非字节数);返回小写十六进制字符串 -func genRandomHex(n int) string { - if n <= 0 { - return "" - } - // 由于 hex 每个字节会转成 2 个字符,因此需要 (n+1)/2 个字节 - byteLen := (n + 1) / 2 - b := make([]byte, byteLen) - if _, err := rand.Read(b); err != nil { - return "" - } - s := hex.EncodeToString(b) - if len(s) > n { - s = s[:n] - } - return s -} - -// 根据前缀和总长度构建卡号 -// - totalLen <= 0 时按 18 处理 -// - 若前缀长度 >= totalLen,则自动扩展为 前缀长度+18 -// - uppercase=true 表示最终结果转为大写;false 表示小写 -func buildCardNumber(prefix string, totalLen int, uppercase bool) string { - if totalLen <= 0 { - totalLen = 18 - } - if len(prefix) >= totalLen { - totalLen = len(prefix) + 18 - } - rest := totalLen - len(prefix) - s := prefix + genRandomHex(rest) - if uppercase { - return strings.ToUpper(s) - } - return strings.ToLower(s) -} - -// CardsFragmentHandler 卡密管理片段渲染 -// - 渲染 cards.html 列表与表单界面 -func CardsFragmentHandler(w http.ResponseWriter, r *http.Request) { - utils.RenderTemplate(w, "cards.html", map[string]interface{}{}) -} - -// CardsListHandler 获取卡密列表 -// - 支持GET -// - 支持分页查询参数:page、page_size -// - 支持筛选参数:card_type_id、status、batch、keyword(卡号/备注/批次模糊匹配) -// - 返回卡密列表和分页信息 -func CardsListHandler(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodGet { - http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed) - return - } - - // 获取查询参数 - page, _ := strconv.Atoi(r.URL.Query().Get("page")) - pageSize, _ := strconv.Atoi(r.URL.Query().Get("page_size")) - cardTypeIDStr := r.URL.Query().Get("card_type_id") - statusStr := r.URL.Query().Get("status") - batch := r.URL.Query().Get("batch") - // 中文注释:keyword 支持在 card_number、remark、batch 三个字段上进行模糊匹配 - keyword := strings.TrimSpace(r.URL.Query().Get("keyword")) - - // 设置默认分页参数 - if page <= 0 { - page = 1 - } - if pageSize <= 0 || pageSize > 100 { - pageSize = 20 - } - - db, err := database.GetDB() - if err != nil { - utils.JsonResponse(w, http.StatusInternalServerError, false, "数据库连接失败", nil) - return - } - - // 构建查询条件(去除无效的 Preload,前端已通过 card_type_id 自行映射类型名称) - query := db.Model(&models.Card{}) - - // 筛选条件 - if cardTypeIDStr != "" { - if cardTypeID, err := strconv.Atoi(cardTypeIDStr); err == nil && cardTypeID > 0 { - query = query.Where("card_type_id = ?", cardTypeID) - } - } - if statusStr != "" { - if status, err := strconv.Atoi(statusStr); err == nil { - query = query.Where("status = ?", status) - } - } - if batch != "" { - query = query.Where("batch LIKE ?", "%"+batch+"%") - } - // 中文注释:当提供 keyword 时,在卡号、备注、批次三个字段上进行 OR 模糊匹配 - if keyword != "" { - kw := "%" + keyword + "%" - query = query.Where("(card_number LIKE ? OR remark LIKE ? OR batch LIKE ?)", kw, kw, kw) - } - - // 计算总数 - var total int64 - if err := query.Count(&total).Error; err != nil { - utils.JsonResponse(w, http.StatusInternalServerError, false, "统计总数失败", nil) - return - } - - // 分页查询 - var cards []models.Card - offset := (page - 1) * pageSize - if err := query.Order("id desc").Offset(offset).Limit(pageSize).Find(&cards).Error; err != nil { - utils.JsonResponse(w, http.StatusInternalServerError, false, "查询失败", nil) - return - } - - // 中文注释:为每条卡记录补充类型名称,避免前端依赖异步类型映射导致显示“未知类型” - // 1) 先查询类型列表并构建 id->name 的映射表 - var cardTypeList []models.CardType - _ = db.Model(&models.CardType{}).Find(&cardTypeList).Error - typeNameMap := make(map[uint]string, len(cardTypeList)) - for _, t := range cardTypeList { - typeNameMap[t.ID] = t.Name - } - // 2) 将卡列表转换为通用 map 列表,并附加 card_type_name 字段 - items := make([]map[string]interface{}, 0, len(cards)) - for _, c := range cards { - items = append(items, map[string]interface{}{ - "id": c.ID, - "card_number": c.CardNumber, - "card_type_id": c.CardTypeID, - "card_type_name": typeNameMap[c.CardTypeID], - "status": c.Status, - "batch": c.Batch, - "remark": c.Remark, - "used_at": c.UsedAt, - "created_at": c.CreatedAt, - }) - } - - // 返回分页数据 - result := map[string]interface{}{ - "items": items, - "total": total, - "page": page, - "page_size": pageSize, - "pages": (total + int64(pageSize) - 1) / int64(pageSize), - } - utils.JsonResponse(w, http.StatusOK, true, "ok", result) -} - -// CardCreateHandler 新增卡密 -// - 接收JSON: {card_type_id, status, remark, prefix, length, uppercase, count} -// - card_number 与 batch 不再由前端传入,后端将自动生成: -// 1. 卡号:按 prefix 与 length 生成随机十六进制字符串,支持大小写控制(uppercase,默认小写) -// 2. 批次:基于设置表 card_batch_counter 自增,格式为 YYYYMMDD-000001 -// 3. 生成数量:通过 count 控制一次生成的数量,默认1,最大500 -func CardCreateHandler(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodPost { - http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed) - return - } - type reqBody struct { - CardTypeID uint `json:"card_type_id"` - Status int `json:"status"` - Remark string `json:"remark"` - Prefix string `json:"prefix"` - Length int `json:"length"` - Uppercase bool `json:"uppercase"` - Count int `json:"count"` - } - var body reqBody - if err := json.NewDecoder(r.Body).Decode(&body); err != nil { - utils.JsonResponse(w, http.StatusBadRequest, false, "请求体错误", nil) - return - } - if body.CardTypeID == 0 { - utils.JsonResponse(w, http.StatusBadRequest, false, "卡密类型ID不能为空", nil) - return - } - - db, err := database.GetDB() - if err != nil { - utils.JsonResponse(w, http.StatusInternalServerError, false, "数据库连接失败", nil) - return - } - - // 检查卡密类型是否存在且启用 - var cardType models.CardType - if err := db.First(&cardType, body.CardTypeID).Error; err != nil { - utils.JsonResponse(w, http.StatusBadRequest, false, "卡密类型不存在", nil) - return - } - // 检查卡密类型是否被禁用 - if cardType.Status != 1 { - utils.JsonResponse(w, http.StatusBadRequest, false, "卡密类型已被禁用,无法创建卡密", nil) - return - } - - // 规范化长度与大小写、生成数量参数 - if body.Length <= 0 { - body.Length = 18 - } - if body.Count <= 0 { - body.Count = 1 - } - if body.Count > 500 { - body.Count = 500 - } - - // 生成批次(基于设置表 card_batch_counter 自增) - // 格式:YYYYMMDD-000001(每天不重置,仅简单自增计数) - var batch string - var setting models.Settings - if err := db.Where("name = ?", "card_batch_counter").First(&setting).Error; err != nil { - // 若不存在该设置项,则创建并从 1 开始 - setting = models.Settings{Name: "card_batch_counter", Value: "1", Description: "卡密批次号计数器(用于记录上次生成批次号的序号,自增使用)"} - if e := db.Create(&setting).Error; e != nil { - utils.JsonResponse(w, http.StatusInternalServerError, false, "初始化批次计数器失败", nil) - return - } - batch = time.Now().Format("20060102") + "-" + fmt.Sprintf("%06d", 1) - } else { - cnt, _ := strconv.Atoi(setting.Value) - cnt++ - newVal := strconv.Itoa(cnt) - if e := db.Model(&models.Settings{}).Where("id = ?", setting.ID).Update("value", newVal).Error; e != nil { - utils.JsonResponse(w, http.StatusInternalServerError, false, "更新批次计数器失败", nil) - return - } - batch = time.Now().Format("20060102") + "-" + fmt.Sprintf("%06d", cnt) - } - - // 中文注释:计算合法状态值(1=已使用,2=禁用,其它按未使用0处理) - safeStatus := body.Status - if safeStatus != 1 && safeStatus != 2 { - safeStatus = 0 - } - - // 中文注释:循环生成 count 条卡密,若单条创建失败则重试最多5次 - success := 0 - for i := 0; i < body.Count; i++ { - card := models.Card{ - CardNumber: buildCardNumber(body.Prefix, body.Length, body.Uppercase), - CardTypeID: body.CardTypeID, - Status: safeStatus, - Batch: batch, - Remark: body.Remark, - } - var createErr error - for j := 0; j < 5; j++ { - createErr = db.Create(&card).Error - if createErr == nil { - success++ - break - } - // 失败则重新生成一次卡号后重试 - card.CardNumber = buildCardNumber(body.Prefix, body.Length, body.Uppercase) - } - } - - if success == 0 { - utils.JsonResponse(w, http.StatusBadRequest, false, "创建失败,可能是卡密号码重复", nil) - return - } - - result := map[string]interface{}{ - "created": success, - "batch": batch, - } - utils.JsonResponse(w, http.StatusOK, true, fmt.Sprintf("创建成功:%d条", success), result) -} - -// CardUpdateHandler 更新卡密 -// - 接收JSON: {id, card_number(可选), card_type_id(可选), status, batch(可选), remark} -// - 说明:card_number 与 batch 若未提供或为空,则不会更新对应字段 -func CardUpdateHandler(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodPost { - http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed) - return - } - type reqBody struct { - ID uint `json:"id"` - CardNumber string `json:"card_number"` - CardTypeID uint `json:"card_type_id"` - Status int `json:"status"` - Batch string `json:"batch"` - Remark string `json:"remark"` - } - var body reqBody - if err := json.NewDecoder(r.Body).Decode(&body); err != nil { - utils.JsonResponse(w, http.StatusBadRequest, false, "请求体错误", nil) - return - } - if body.ID == 0 { - utils.JsonResponse(w, http.StatusBadRequest, false, "缺少ID", nil) - return - } - - db, err := database.GetDB() - if err != nil { - utils.JsonResponse(w, http.StatusInternalServerError, false, "数据库连接失败", nil) - return - } - - // 检查卡密类型是否存在且启用(如果提供了新的卡密类型ID) - if body.CardTypeID > 0 { - var cardType models.CardType - if err := db.First(&cardType, body.CardTypeID).Error; err != nil { - utils.JsonResponse(w, http.StatusBadRequest, false, "卡密类型不存在", nil) - return - } - // 检查卡密类型是否被禁用 - if cardType.Status != 1 { - utils.JsonResponse(w, http.StatusBadRequest, false, "卡密类型已被禁用,无法更新为此类型", nil) - return - } - } - - // 中文注释:若尝试将状态置为未使用(0),则直接允许 - if body.Status == 0 { - var existing models.Card - if err := db.First(&existing, body.ID).Error; err != nil { - utils.JsonResponse(w, http.StatusBadRequest, false, "卡密不存在", nil) - return - } - } - - // 构建更新字段 - updates := map[string]interface{}{} - if body.CardNumber != "" { - updates["card_number"] = body.CardNumber - } - if body.CardTypeID > 0 { - updates["card_type_id"] = body.CardTypeID - } - updates["status"] = body.Status - // 仅当提供非空 batch 时才更新,防止被清空 - if strings.TrimSpace(body.Batch) != "" { - updates["batch"] = body.Batch - } - updates["remark"] = body.Remark - - if err := db.Model(&models.Card{}).Where("id = ?", body.ID).Updates(updates).Error; err != nil { - utils.JsonResponse(w, http.StatusBadRequest, false, "更新失败,可能是卡密号码重复", nil) - return - } - utils.JsonResponse(w, http.StatusOK, true, "更新成功", nil) -} - -// CardDeleteHandler 删除单个卡密 -// - 接收JSON: {id} -func CardDeleteHandler(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodPost { - http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed) - return - } - var body struct { - ID uint `json:"id"` - } - if err := json.NewDecoder(r.Body).Decode(&body); err != nil || body.ID == 0 { - utils.JsonResponse(w, http.StatusBadRequest, false, "参数错误", nil) - return - } - db, err := database.GetDB() - if err != nil { - utils.JsonResponse(w, http.StatusInternalServerError, false, "数据库连接失败", nil) - return - } - if err := db.Delete(&models.Card{}, body.ID).Error; err != nil { - utils.JsonResponse(w, http.StatusInternalServerError, false, "删除失败", nil) - return - } - utils.JsonResponse(w, http.StatusOK, true, "删除成功", nil) -} - -// CardsBatchDeleteHandler 批量删除卡密 -// - 接收JSON: {ids: []} -func CardsBatchDeleteHandler(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodPost { - http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed) - return - } - var body struct { - IDs []uint `json:"ids"` - } - if err := json.NewDecoder(r.Body).Decode(&body); err != nil || len(body.IDs) == 0 { - utils.JsonResponse(w, http.StatusBadRequest, false, "参数错误", nil) - return - } - db, err := database.GetDB() - if err != nil { - utils.JsonResponse(w, http.StatusInternalServerError, false, "数据库连接失败", nil) - return - } - if err := db.Delete(&models.Card{}, body.IDs).Error; err != nil { - utils.JsonResponse(w, http.StatusInternalServerError, false, "批量删除失败", nil) - return - } - utils.JsonResponse(w, http.StatusOK, true, "批量删除成功", nil) -} - -// CardsBatchUpdateStatusHandler 批量更新卡密状态 -// - 接收JSON: {ids: [], status: int} -// - status: 0=未使用,1=已使用,2=禁用 -func CardsBatchUpdateStatusHandler(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodPost { - http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed) - return - } - var body struct { - IDs []uint `json:"ids"` - Status int `json:"status"` - } - if err := json.NewDecoder(r.Body).Decode(&body); err != nil || len(body.IDs) == 0 { - utils.JsonResponse(w, http.StatusBadRequest, false, "参数错误", nil) - return - } - db, err := database.GetDB() - if err != nil { - utils.JsonResponse(w, http.StatusInternalServerError, false, "数据库连接失败", nil) - return - } - // 中文注释:允许批量重置为未使用(0) - if err := db.Model(&models.Card{}).Where("id IN ?", body.IDs).Update("status", body.Status).Error; err != nil { - utils.JsonResponse(w, http.StatusInternalServerError, false, "批量更新失败", nil) - return - } - utils.JsonResponse(w, http.StatusOK, true, "操作成功", nil) -} - -// GetCardTypesHandler 获取卡密类型列表(供前端下拉选择) -// - 仅支持GET请求 -// - 只返回启用状态的卡密类型,用于前端下拉选择 -func GetCardTypesHandler(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodGet { - http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed) - return - } - db, err := database.GetDB() - if err != nil { - utils.JsonResponse(w, http.StatusInternalServerError, false, "数据库连接失败", nil) - return - } - var cardTypes []models.CardType - // 中文注释:根据可选参数 all 决定是否仅返回启用类型 - // - 未提供或为其它值:仅返回启用(status=1) - // - all=1/true/yes:返回所有状态的类型(用于筛选下拉场景) - q := db.Model(&models.CardType{}) - all := strings.ToLower(strings.TrimSpace(r.URL.Query().Get("all"))) - if !(all == "1" || all == "true" || all == "yes") { - q = q.Where("status = ?", 1) - } - if err := q.Order("id asc").Find(&cardTypes).Error; err != nil { - utils.JsonResponse(w, http.StatusInternalServerError, false, "查询失败", nil) - return - } - utils.JsonResponse(w, http.StatusOK, true, "ok", cardTypes) -} - -// CardsExportHandler 导出卡密为文本文件 -// - 支持GET -// - 筛选参数:card_type_id、status、batch、remark -// - 导出字段(按顺序):卡号、状态、创建时间;使用“----”分隔 -func CardsExportHandler(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodGet { - http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed) - return - } - - // 解析筛选参数 - cardTypeIDStr := strings.TrimSpace(r.URL.Query().Get("card_type_id")) - statusStr := strings.TrimSpace(r.URL.Query().Get("status")) - batch := strings.TrimSpace(r.URL.Query().Get("batch")) - remark := strings.TrimSpace(r.URL.Query().Get("remark")) - - db, err := database.GetDB() - if err != nil { - http.Error(w, "数据库连接失败", http.StatusInternalServerError) - return - } - - // 构建查询 - query := db.Model(&models.Card{}) - if cardTypeIDStr != "" { - if id, err := strconv.Atoi(cardTypeIDStr); err == nil && id > 0 { - query = query.Where("card_type_id = ?", id) - } - } - if statusStr != "" { - if s, err := strconv.Atoi(statusStr); err == nil { - query = query.Where("status = ?", s) - } - } - if batch != "" { - query = query.Where("batch LIKE ?", "%"+batch+"%") - } - if remark != "" { - query = query.Where("remark LIKE ?", "%"+remark+"%") - } - - // 查询数据(按ID倒序) - var cards []models.Card - if err := query.Order("id desc").Find(&cards).Error; err != nil { - http.Error(w, "查询失败", http.StatusInternalServerError) - return - } - - // 设置响应头(文本下载) - now := time.Now().Format("20060102150405") - filename := fmt.Sprintf("cards_%s.txt", now) - w.Header().Set("Content-Type", "text/plain; charset=utf-8") - w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=%s", filename)) - - // 写入UTF-8 BOM,避免Excel/记事本中文乱码 - _, _ = w.Write([]byte{0xEF, 0xBB, 0xBF}) - - // 写入表头 - _, _ = w.Write([]byte("卡号----状态----创建时间\n")) - - // 时间格式 - const tf = "2006-01-02 15:04:05" - - // 状态转文字 - statusText := func(s int) string { - switch s { - case 0: - return "未使用" - case 1: - return "已使用" - default: - return "禁用" - } - } - - // 写入数据行(以“----”分隔) - for _, c := range cards { - record := []string{ - c.CardNumber, - statusText(c.Status), - c.CreatedAt.Format(tf), - } - line := strings.Join(record, "----") + "\n" - if _, err := w.Write([]byte(line)); err != nil { - continue - } - } -} - -// CardsExportSelectedHandler 导出选中的卡密为文本文件 -// - 支持GET -// - 参数:ids(逗号分隔的卡密ID列表) -// - 导出字段(按顺序):卡号、状态、创建时间;使用"----"分隔 -func CardsExportSelectedHandler(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodGet { - http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed) - return - } - - // 解析选中的卡密ID列表 - idsStr := strings.TrimSpace(r.URL.Query().Get("ids")) - if idsStr == "" { - http.Error(w, "请提供要导出的卡密ID列表", http.StatusBadRequest) - return - } - - // 解析ID列表 - idStrings := strings.Split(idsStr, ",") - var ids []uint - for _, idStr := range idStrings { - if id, err := strconv.Atoi(strings.TrimSpace(idStr)); err == nil && id > 0 { - ids = append(ids, uint(id)) - } - } - - if len(ids) == 0 { - http.Error(w, "无效的卡密ID列表", http.StatusBadRequest) - return - } - - db, err := database.GetDB() - if err != nil { - http.Error(w, "数据库连接失败", http.StatusInternalServerError) - return - } - - // 查询选中的卡密数据(按ID倒序) - var cards []models.Card - if err := db.Where("id IN ?", ids).Order("id desc").Find(&cards).Error; err != nil { - logrus.WithError(err).Error("查询选中卡密失败") - http.Error(w, "查询卡密数据失败", http.StatusInternalServerError) - return - } - - if len(cards) == 0 { - http.Error(w, "未找到指定的卡密数据", http.StatusNotFound) - return - } - - // 设置响应头,触发下载 - filename := fmt.Sprintf("selected_cards_%s.txt", time.Now().Format("20060102_150405")) - w.Header().Set("Content-Type", "text/plain; charset=utf-8") - w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=%s", filename)) - - // 写入数据 - tf := "2006-01-02 15:04:05" - for _, c := range cards { - // 状态转换 - var statusText string - switch c.Status { - case 0: - statusText = "未使用" - case 1: - statusText = "已使用" - default: - statusText = "禁用" - } - - // 格式:卡号----状态----创建时间 - record := []string{ - c.CardNumber, - statusText, - c.CreatedAt.Format(tf), - } - line := strings.Join(record, "----") + "\n" - if _, err := w.Write([]byte(line)); err != nil { - continue - } - } -} diff --git a/controllers/admin/card_stats.go b/controllers/admin/card_stats.go deleted file mode 100644 index 3bc2526..0000000 --- a/controllers/admin/card_stats.go +++ /dev/null @@ -1,181 +0,0 @@ -package admin - -import ( - "net/http" - "networkDev/constants" - "networkDev/database" - "networkDev/models" - "networkDev/utils" - "time" -) - -// CardStatsOverviewHandler 卡密统计概览API -// - 返回当日和所有卡密的统计信息 -// - 包括:总数、使用/未使用/禁用状态分布 -func CardStatsOverviewHandler(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodGet { - http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed) - return - } - - db, err := database.GetDB() - if err != nil { - utils.JsonResponse(w, http.StatusInternalServerError, false, "数据库连接失败", nil) - return - } - - // 获取当日统计 - today := time.Now().Format("2006-01-02") - todayStart := today + " 00:00:00" - todayEnd := today + " 23:59:59" - - // 当日卡密统计 - var todayTotal int64 - var todayByStatus = make(map[int]int64) - - // 当日总数 - db.Model(&models.Card{}).Where("created_at >= ? AND created_at <= ?", todayStart, todayEnd).Count(&todayTotal) - - // 当日按状态分布 - var todayStatusCounts []struct { - Status int `json:"status"` - Count int64 `json:"count"` - } - db.Model(&models.Card{}). - Select("status, count(*) as count"). - Where("created_at >= ? AND created_at <= ?", todayStart, todayEnd). - Group("status"). - Find(&todayStatusCounts) - - for _, sc := range todayStatusCounts { - todayByStatus[sc.Status] = sc.Count - } - - // 所有卡密统计 - var allTotal int64 - var allByStatus = make(map[int]int64) - - // 总数 - db.Model(&models.Card{}).Count(&allTotal) - - // 按状态分布 - var allStatusCounts []struct { - Status int `json:"status"` - Count int64 `json:"count"` - } - db.Model(&models.Card{}). - Select("status, count(*) as count"). - Group("status"). - Find(&allStatusCounts) - - for _, sc := range allStatusCounts { - allByStatus[sc.Status] = sc.Count - } - - // 构建响应数据 - data := map[string]interface{}{ - "today": map[string]interface{}{ - "total": todayTotal, - "by_status": todayByStatus, - }, - "all": map[string]interface{}{ - "total": allTotal, - "by_status": allByStatus, - }, - } - - utils.JsonResponse(w, http.StatusOK, true, "获取成功", data) -} - -// CardStatsTrend30DaysHandler 卡密30天趋势API -// - 返回近30天的卡密创建和使用趋势 -func CardStatsTrend30DaysHandler(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodGet { - http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed) - return - } - - db, err := database.GetDB() - if err != nil { - utils.JsonResponse(w, http.StatusInternalServerError, false, "数据库连接失败", nil) - return - } - - // 生成近30天的日期列表 - var dates []string - var totalCounts []int64 - var usedCounts []int64 - var unusedCounts []int64 - - for i := 29; i >= 0; i-- { - date := time.Now().AddDate(0, 0, -i).Format("2006-01-02") - dates = append(dates, date) - - dayStart := date + " 00:00:00" - dayEnd := date + " 23:59:59" - - // 当天创建的卡密总数 - var totalCount int64 - db.Model(&models.Card{}).Where("created_at >= ? AND created_at <= ?", dayStart, dayEnd).Count(&totalCount) - totalCounts = append(totalCounts, totalCount) - - // 当天创建且已使用的卡密数 - var usedCount int64 - db.Model(&models.Card{}). - Where("created_at >= ? AND created_at <= ? AND status = ?", dayStart, dayEnd, constants.CardStatusUsed). - Count(&usedCount) - usedCounts = append(usedCounts, usedCount) - - // 当天创建且未使用的卡密数 - var unusedCount int64 - db.Model(&models.Card{}). - Where("created_at >= ? AND created_at <= ? AND status = ?", dayStart, dayEnd, constants.CardStatusUnused). - Count(&unusedCount) - unusedCounts = append(unusedCounts, unusedCount) - } - - // 构建响应数据 - data := map[string]interface{}{ - "dates": dates, - "total": totalCounts, - "used": usedCounts, - "unused": unusedCounts, - } - - utils.JsonResponse(w, http.StatusOK, true, "获取成功", data) -} - -// CardStatsSimpleHandler 简单卡密统计API -// - 返回卡密的基本统计信息:总数、已使用、未使用、禁用 -func CardStatsSimpleHandler(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodGet { - http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed) - return - } - - db, err := database.GetDB() - if err != nil { - utils.JsonResponse(w, http.StatusInternalServerError, false, "数据库连接失败", nil) - return - } - - // 统计各状态的卡密数量 - var total int64 - var used int64 - var unused int64 - var disabled int64 - - db.Model(&models.Card{}).Count(&total) - db.Model(&models.Card{}).Where("status = ?", constants.CardStatusUsed).Count(&used) - db.Model(&models.Card{}).Where("status = ?", constants.CardStatusUnused).Count(&unused) - db.Model(&models.Card{}).Where("status = ?", constants.CardStatusDisabled).Count(&disabled) - - data := map[string]interface{}{ - "total": total, - "used": used, - "unused": unused, - "disabled": disabled, - } - - utils.JsonResponse(w, http.StatusOK, true, "获取成功", data) -} \ No newline at end of file diff --git a/controllers/admin/card_type.go b/controllers/admin/card_type.go deleted file mode 100644 index fa9a3de..0000000 --- a/controllers/admin/card_type.go +++ /dev/null @@ -1,428 +0,0 @@ -package admin - -import ( - "encoding/json" - "net/http" - "networkDev/database" - "networkDev/models" - "networkDev/utils" - "strconv" - "strings" -) - -// CardTypesFragmentHandler 卡密类型管理片段渲染 -// - 渲染 card_types.html 列表与表单界面 -func CardTypesFragmentHandler(w http.ResponseWriter, r *http.Request) { - utils.RenderTemplate(w, "card_types.html", map[string]interface{}{}) -} - -// CardTypesListHandler 获取卡密类型列表 -// - 支持GET -// - 支持分页和筛选 -func CardTypesListHandler(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodGet { - http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed) - return - } - - // 获取查询参数 - page, _ := strconv.Atoi(r.URL.Query().Get("page")) - pageSize, _ := strconv.Atoi(r.URL.Query().Get("page_size")) - keyword := r.URL.Query().Get("keyword") - statusStr := r.URL.Query().Get("status") - - // 设置默认分页参数 - if page <= 0 { - page = 1 - } - if pageSize <= 0 || pageSize > 100 { - pageSize = 20 - } - - db, err := database.GetDB() - if err != nil { - utils.JsonResponse(w, http.StatusInternalServerError, false, "数据库连接失败", nil) - return - } - - // 构建查询条件 - query := db.Model(&models.CardType{}) - - // 筛选条件 - if keyword != "" { - query = query.Where("name LIKE ?", "%"+keyword+"%") - } - if statusStr != "" { - if status, err := strconv.Atoi(statusStr); err == nil { - query = query.Where("status = ?", status) - } - } - - // 计算总数 - var total int64 - if err := query.Count(&total).Error; err != nil { - utils.JsonResponse(w, http.StatusInternalServerError, false, "统计总数失败", nil) - return - } - - // 分页查询 - var items []models.CardType - offset := (page - 1) * pageSize - if err := query.Order("id asc").Offset(offset).Limit(pageSize).Find(&items).Error; err != nil { - utils.JsonResponse(w, http.StatusInternalServerError, false, "查询失败", nil) - return - } - - // 返回分页数据 - result := map[string]interface{}{ - "items": items, - "total": total, - "page": page, - "page_size": pageSize, - "pages": (total + int64(pageSize) - 1) / int64(pageSize), - } - utils.JsonResponse(w, http.StatusOK, true, "ok", result) -} - -// CardTypeCreateHandler 新增卡密类型 -// - 接收JSON: {name, status, login_types} -// - Name 必填且唯一 -func CardTypeCreateHandler(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodPost { - http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed) - return - } - type reqBody struct { - Name string `json:"name"` - Status int `json:"status"` - LoginTypes string `json:"login_types"` - } - var body reqBody - if err := json.NewDecoder(r.Body).Decode(&body); err != nil { - utils.JsonResponse(w, http.StatusBadRequest, false, "请求体错误", nil) - return - } - if body.Name == "" { - utils.JsonResponse(w, http.StatusBadRequest, false, "名称不能为空", nil) - return - } - - // 校验登录方式ID是否存在 - if errMsg := validateLoginTypes(body.LoginTypes); errMsg != "" { - utils.JsonResponse(w, http.StatusBadRequest, false, errMsg, nil) - return - } - db, err := database.GetDB() - if err != nil { - utils.JsonResponse(w, http.StatusInternalServerError, false, "数据库连接失败", nil) - return - } - item := models.CardType{ - Name: body.Name, - Status: body.Status, - LoginTypes: body.LoginTypes, - } - if item.Status != 0 { - item.Status = 1 - } - if err := db.Create(&item).Error; err != nil { - utils.JsonResponse(w, http.StatusBadRequest, false, "创建失败,可能是名称重复", nil) - return - } - utils.JsonResponse(w, http.StatusOK, true, "创建成功", item) -} - -// checkCardTypeInUse 检查卡密类型是否被卡密使用 -// - 通过 cards 表中 card_type_id 外键计数 -// - 返回是否被使用以及被使用的数量 -func checkCardTypeInUse(cardTypeID uint) (bool, int64, error) { - db, err := database.GetDB() - if err != nil { - return false, 0, err - } - var count int64 - if err := db.Model(&models.Card{}).Where("card_type_id = ?", cardTypeID).Count(&count).Error; err != nil { - return false, 0, err - } - return count > 0, count, nil -} - -// CardTypeUpdateHandler 更新卡密类型 -// - 接收JSON: {id, name, status, login_types} -func CardTypeUpdateHandler(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodPost { - http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed) - return - } - type reqBody struct { - ID uint `json:"id"` - Name string `json:"name"` - Status int `json:"status"` - LoginTypes string `json:"login_types"` - } - var body reqBody - if err := json.NewDecoder(r.Body).Decode(&body); err != nil { - utils.JsonResponse(w, http.StatusBadRequest, false, "请求体错误", nil) - return - } - if body.ID == 0 { - utils.JsonResponse(w, http.StatusBadRequest, false, "缺少ID", nil) - return - } - - // 校验登录方式名称是否存在且未被禁用 - if errMsg := validateLoginTypes(body.LoginTypes); errMsg != "" { - utils.JsonResponse(w, http.StatusBadRequest, false, errMsg, nil) - return - } - db, err := database.GetDB() - if err != nil { - utils.JsonResponse(w, http.StatusInternalServerError, false, "数据库连接失败", nil) - return - } - - // 查询原始记录,便于后续在用校验(重命名/禁用) - var original models.CardType - if err := db.First(&original, body.ID).Error; err != nil { - utils.JsonResponse(w, http.StatusBadRequest, false, "卡密类型不存在", nil) - return - } - - // 如果名称发生变化且该卡密类型已被卡密使用,则不允许修改名称 - if body.Name != "" && body.Name != original.Name { - inUse, count, err := checkCardTypeInUse(body.ID) - if err != nil { - utils.JsonResponse(w, http.StatusInternalServerError, false, "检查使用状态失败", nil) - return - } - if inUse { - utils.JsonResponse(w, http.StatusBadRequest, false, "该卡密类型已被卡密使用(数量:"+strconv.FormatInt(count, 10)+"),无法修改名称", nil) - return - } - } - - // 当尝试禁用(status=0)且原状态不是禁用时,如该类型已被卡密使用则禁止禁用 - if body.Status == 0 && original.Status != 0 { - inUse, count, err := checkCardTypeInUse(body.ID) - if err != nil { - utils.JsonResponse(w, http.StatusInternalServerError, false, "检查使用状态失败", nil) - return - } - if inUse { - utils.JsonResponse(w, http.StatusBadRequest, false, "该卡密类型已被卡密使用(数量:"+strconv.FormatInt(count, 10)+"),无法禁用", nil) - return - } - } - - // 构建更新字段 - updates := map[string]interface{}{} - if body.Name != "" { - updates["name"] = body.Name - } - updates["status"] = body.Status - updates["login_types"] = body.LoginTypes - if err := db.Model(&models.CardType{}).Where("id = ?", body.ID).Updates(updates).Error; err != nil { - utils.JsonResponse(w, http.StatusBadRequest, false, "更新失败,可能是名称重复", nil) - return - } - utils.JsonResponse(w, http.StatusOK, true, "更新成功", nil) -} - -// CardTypeDeleteHandler 删除单个卡密类型 -// - 接收JSON: {id} -func CardTypeDeleteHandler(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodPost { - http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed) - return - } - var body struct { - ID uint `json:"id"` - } - if err := json.NewDecoder(r.Body).Decode(&body); err != nil || body.ID == 0 { - utils.JsonResponse(w, http.StatusBadRequest, false, "参数错误", nil) - return - } - db, err := database.GetDB() - if err != nil { - utils.JsonResponse(w, http.StatusInternalServerError, false, "数据库连接失败", nil) - return - } - - // 在用校验 - inUse, count, err := checkCardTypeInUse(body.ID) - if err != nil { - utils.JsonResponse(w, http.StatusInternalServerError, false, "检查使用状态失败", nil) - return - } - if inUse { - utils.JsonResponse(w, http.StatusBadRequest, false, "该卡密类型已被卡密使用(数量:"+strconv.FormatInt(count, 10)+"),无法删除", nil) - return - } - - if err := db.Delete(&models.CardType{}, body.ID).Error; err != nil { - utils.JsonResponse(w, http.StatusInternalServerError, false, "删除失败", nil) - return - } - utils.JsonResponse(w, http.StatusOK, true, "删除成功", nil) -} - -// CardTypesBatchDeleteHandler 批量删除卡密类型 -// - 接收JSON: {ids: []} -func CardTypesBatchDeleteHandler(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodPost { - http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed) - return - } - var body struct { - IDs []uint `json:"ids"` - } - if err := json.NewDecoder(r.Body).Decode(&body); err != nil || len(body.IDs) == 0 { - utils.JsonResponse(w, http.StatusBadRequest, false, "参数错误", nil) - return - } - db, err := database.GetDB() - if err != nil { - utils.JsonResponse(w, http.StatusInternalServerError, false, "数据库连接失败", nil) - return - } - - // 批量在用校验 - var blocking []string - for _, id := range body.IDs { - inUse, count, err := checkCardTypeInUse(id) - if err != nil { - utils.JsonResponse(w, http.StatusInternalServerError, false, "检查使用状态失败", nil) - return - } - if inUse { - var ct models.CardType - if db.First(&ct, id).Error == nil { - blocking = append(blocking, ct.Name+"(数量:"+strconv.FormatInt(count, 10)+")") - } else { - blocking = append(blocking, strconv.FormatUint(uint64(id), 10)+"(数量:"+strconv.FormatInt(count, 10)+")") - } - } - } - if len(blocking) > 0 { - utils.JsonResponse(w, http.StatusBadRequest, false, "以下卡密类型已被卡密使用,无法删除:"+strings.Join(blocking, ";"), nil) - return - } - - if err := db.Delete(&models.CardType{}, body.IDs).Error; err != nil { - utils.JsonResponse(w, http.StatusInternalServerError, false, "批量删除失败", nil) - return - } - utils.JsonResponse(w, http.StatusOK, true, "批量删除成功", nil) -} - -// validateLoginTypes 校验登录方式名称是否存在且未被禁用 -// - 接收逗号分隔的登录方式名称字符串 -// - 检查登录方式是否存在且状态为启用(status=1) -// - 返回错误信息,如果所有名称都存在且启用则返回空字符串 -func validateLoginTypes(loginTypesStr string) string { - if loginTypesStr == "" { - return "" - } - - // 分割登录方式名称字符串 - nameStrs := strings.Split(loginTypesStr, ",") - var names []string - - // 去重并清理空格 - nameSet := make(map[string]bool) - for _, nameStr := range nameStrs { - nameStr = strings.TrimSpace(nameStr) - if nameStr == "" { - continue - } - nameSet[nameStr] = true - } - - // 转换为切片 - for name := range nameSet { - names = append(names, name) - } - - if len(names) == 0 { - return "" - } - - // 查询数据库检查名称是否存在 - db, err := database.GetDB() - if err != nil { - return "数据库连接失败" - } - - // 查询所有匹配的登录方式,包括状态信息 - var loginTypes []models.LoginType - if err := db.Where("name IN ?", names).Find(&loginTypes).Error; err != nil { - return "查询登录方式失败" - } - - // 检查是否有不存在的名称和被禁用的登录方式 - existingSet := make(map[string]bool) - disabledNames := []string{} - for _, loginType := range loginTypes { - existingSet[loginType.Name] = true - // 检查登录方式是否被禁用 (status != 1 表示禁用) - if loginType.Status != 1 { - disabledNames = append(disabledNames, loginType.Name) - } - } - - // 检查不存在的名称 - var notFoundNames []string - for _, name := range names { - if !existingSet[name] { - notFoundNames = append(notFoundNames, name) - } - } - - // 返回错误信息 - if len(notFoundNames) > 0 { - return "以下登录方式名称不存在: " + strings.Join(notFoundNames, ", ") - } - if len(disabledNames) > 0 { - return "以下登录方式已被禁用,无法使用: " + strings.Join(disabledNames, ", ") - } - - return "" -} - -// CardTypesBatchEnableHandler 批量启用 -// - 接收JSON: {ids: []} -func CardTypesBatchEnableHandler(w http.ResponseWriter, r *http.Request) { - batchUpdateStatus(w, r, 1) -} - -// CardTypesBatchDisableHandler 批量禁用 -// - 接收JSON: {ids: []} -func CardTypesBatchDisableHandler(w http.ResponseWriter, r *http.Request) { - batchUpdateStatus(w, r, 0) -} - -// batchUpdateStatus 批量更新状态的通用函数 -// - status: 1 启用,0 禁用 -func batchUpdateStatus(w http.ResponseWriter, r *http.Request, status int) { - if r.Method != http.MethodPost { - http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed) - return - } - var body struct { - IDs []uint `json:"ids"` - } - if err := json.NewDecoder(r.Body).Decode(&body); err != nil || len(body.IDs) == 0 { - utils.JsonResponse(w, http.StatusBadRequest, false, "参数错误", nil) - return - } - db, err := database.GetDB() - if err != nil { - utils.JsonResponse(w, http.StatusInternalServerError, false, "数据库连接失败", nil) - return - } - if err := db.Model(&models.CardType{}).Where("id IN ?", body.IDs).Update("status", status).Error; err != nil { - utils.JsonResponse(w, http.StatusInternalServerError, false, "批量更新失败", nil) - return - } - utils.JsonResponse(w, http.StatusOK, true, "操作成功", nil) -} diff --git a/controllers/admin/login_type.go b/controllers/admin/login_type.go deleted file mode 100644 index 97e31e8..0000000 --- a/controllers/admin/login_type.go +++ /dev/null @@ -1,394 +0,0 @@ -package admin - -import ( - "encoding/json" - "net/http" - "networkDev/database" - "networkDev/models" - "networkDev/utils" - "strconv" - "strings" -) - -// LoginTypesFragmentHandler 登录方式管理片段渲染 -// - 渲染 login_types.html 列表与表单界面 -func LoginTypesFragmentHandler(w http.ResponseWriter, r *http.Request) { - utils.RenderTemplate(w, "login_types.html", map[string]interface{}{}) -} - -// LoginTypesListHandler 获取登录方式列表 -// - 支持GET -// - 支持分页和筛选 -func LoginTypesListHandler(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodGet { - http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed) - return - } - - // 获取查询参数 - page, _ := strconv.Atoi(r.URL.Query().Get("page")) - pageSize, _ := strconv.Atoi(r.URL.Query().Get("page_size")) - keyword := r.URL.Query().Get("keyword") - statusStr := r.URL.Query().Get("status") - - // 设置默认分页参数 - if page <= 0 { - page = 1 - } - if pageSize <= 0 || pageSize > 100 { - pageSize = 20 - } - - db, err := database.GetDB() - if err != nil { - utils.JsonResponse(w, http.StatusInternalServerError, false, "数据库连接失败", nil) - return - } - - // 构建查询条件 - query := db.Model(&models.LoginType{}) - - // 筛选条件 - if keyword != "" { - query = query.Where("name LIKE ?", "%"+keyword+"%") - } - if statusStr != "" { - if status, err := strconv.Atoi(statusStr); err == nil { - query = query.Where("status = ?", status) - } - } - - // 计算总数 - var total int64 - if err := query.Count(&total).Error; err != nil { - utils.JsonResponse(w, http.StatusInternalServerError, false, "统计总数失败", nil) - return - } - - // 分页查询 - var items []models.LoginType - offset := (page - 1) * pageSize - if err := query.Order("id asc").Offset(offset).Limit(pageSize).Find(&items).Error; err != nil { - utils.JsonResponse(w, http.StatusInternalServerError, false, "查询失败", nil) - return - } - - // 返回分页数据 - result := map[string]interface{}{ - "items": items, - "total": total, - "page": page, - "page_size": pageSize, - "pages": (total + int64(pageSize) - 1) / int64(pageSize), - } - utils.JsonResponse(w, http.StatusOK, true, "ok", result) -} - -// LoginTypeCreateHandler 新增登录方式 -// - 接收JSON: {name, description, status} -// - Name 必填且唯一 -func LoginTypeCreateHandler(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodPost { - http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed) - return - } - type reqBody struct { - Name string `json:"name"` - VerifyTypes string `json:"verify_types"` - Status int `json:"status"` - } - var body reqBody - if err := json.NewDecoder(r.Body).Decode(&body); err != nil { - utils.JsonResponse(w, http.StatusBadRequest, false, "请求体错误", nil) - return - } - if body.Name == "" { - utils.JsonResponse(w, http.StatusBadRequest, false, "名称不能为空", nil) - return - } - db, err := database.GetDB() - if err != nil { - utils.JsonResponse(w, http.StatusInternalServerError, false, "数据库连接失败", nil) - return - } - item := models.LoginType{ - Name: body.Name, - Status: body.Status, - VerifyTypes: body.VerifyTypes, - } - if item.Status != 0 { - item.Status = 1 - } - if err := db.Create(&item).Error; err != nil { - utils.JsonResponse(w, http.StatusBadRequest, false, "创建失败,可能是名称重复", nil) - return - } - utils.JsonResponse(w, http.StatusOK, true, "创建成功", item) -} - -// checkLoginTypeInUse 检查登录类型是否被卡密类型使用 -// - 检查 card_types 表中的 login_types 字段是否包含该登录类型名称 -// - 返回是否被使用和使用该登录类型的卡密类型名称列表 -func checkLoginTypeInUse(loginTypeName string) (bool, []string, error) { - db, err := database.GetDB() - if err != nil { - return false, nil, err - } - - var cardTypes []models.CardType - // 查询包含该登录类型名称的卡密类型 - if err := db.Where("login_types LIKE ?", "%"+loginTypeName+"%").Find(&cardTypes).Error; err != nil { - return false, nil, err - } - - var usingCardTypes []string - for _, cardType := range cardTypes { - // 精确匹配登录类型名称(避免部分匹配) - loginTypes := strings.Split(cardType.LoginTypes, ",") - for _, lt := range loginTypes { - if strings.TrimSpace(lt) == loginTypeName { - usingCardTypes = append(usingCardTypes, cardType.Name) - break - } - } - } - - return len(usingCardTypes) > 0, usingCardTypes, nil -} - -// checkLoginTypesByIDsInUse 批量检查登录类型ID是否被使用 -// - 先查询登录类型ID对应的名称,再检查是否被使用 -func checkLoginTypesByIDsInUse(loginTypeIDs []uint) (bool, map[uint][]string, error) { - db, err := database.GetDB() - if err != nil { - return false, nil, err - } - - // 查询登录类型名称 - var loginTypes []models.LoginType - if err := db.Where("id IN ?", loginTypeIDs).Find(&loginTypes).Error; err != nil { - return false, nil, err - } - - hasUsage := false - usageMap := make(map[uint][]string) - - for _, loginType := range loginTypes { - inUse, usingCardTypes, err := checkLoginTypeInUse(loginType.Name) - if err != nil { - return false, nil, err - } - if inUse { - hasUsage = true - usageMap[loginType.ID] = usingCardTypes - } - } - - return hasUsage, usageMap, nil -} - -// LoginTypeUpdateHandler 更新登录方式 -// - 接收JSON: {id, name, description, status} -func LoginTypeUpdateHandler(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodPost { - http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed) - return - } - type reqBody struct { - ID uint `json:"id"` - Name string `json:"name"` - VerifyTypes string `json:"verify_types"` - Status int `json:"status"` - } - var body reqBody - if err := json.NewDecoder(r.Body).Decode(&body); err != nil { - utils.JsonResponse(w, http.StatusBadRequest, false, "请求体错误", nil) - return - } - if body.ID == 0 { - utils.JsonResponse(w, http.StatusBadRequest, false, "缺少ID", nil) - return - } - - db, err := database.GetDB() - if err != nil { - utils.JsonResponse(w, http.StatusInternalServerError, false, "数据库连接失败", nil) - return - } - - // 始终查询原始记录,便于后续校验(重命名/禁用) - var originalLoginType models.LoginType - if err := db.First(&originalLoginType, body.ID).Error; err != nil { - utils.JsonResponse(w, http.StatusBadRequest, false, "登录类型不存在", nil) - return - } - - // 如果名称发生变化,检查原名称是否被使用(与删除逻辑一致) - if body.Name != "" && originalLoginType.Name != body.Name { - inUse, usingCardTypes, err := checkLoginTypeInUse(originalLoginType.Name) - if err != nil { - utils.JsonResponse(w, http.StatusInternalServerError, false, "检查使用状态失败", nil) - return - } - if inUse { - utils.JsonResponse(w, http.StatusBadRequest, false, "该登录类型正在被以下卡密类型使用,无法修改名称:"+strings.Join(usingCardTypes, "、"), nil) - return - } - } - - // 当尝试禁用(status=0)时,如被卡密类型使用则禁止禁用 - if body.Status == 0 && originalLoginType.Status != 0 { - inUse, usingCardTypes, err := checkLoginTypeInUse(originalLoginType.Name) - if err != nil { - utils.JsonResponse(w, http.StatusInternalServerError, false, "检查使用状态失败", nil) - return - } - if inUse { - utils.JsonResponse(w, http.StatusBadRequest, false, "该登录类型正在被以下卡密类型使用,无法禁用:"+strings.Join(usingCardTypes, "、"), nil) - return - } - } - - updates := map[string]interface{}{} - if body.Name != "" { - updates["name"] = body.Name - } - updates["status"] = body.Status - updates["verify_types"] = body.VerifyTypes - if err := db.Model(&models.LoginType{}).Where("id = ?", body.ID).Updates(updates).Error; err != nil { - utils.JsonResponse(w, http.StatusBadRequest, false, "更新失败,可能是名称重复", nil) - return - } - utils.JsonResponse(w, http.StatusOK, true, "更新成功", nil) -} - -// LoginTypeDeleteHandler 删除单个登录方式 -// - 接收JSON: {id} -func LoginTypeDeleteHandler(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodPost { - http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed) - return - } - var body struct { - ID uint `json:"id"` - } - if err := json.NewDecoder(r.Body).Decode(&body); err != nil || body.ID == 0 { - utils.JsonResponse(w, http.StatusBadRequest, false, "参数错误", nil) - return - } - - db, err := database.GetDB() - if err != nil { - utils.JsonResponse(w, http.StatusInternalServerError, false, "数据库连接失败", nil) - return - } - - // 查询登录类型名称 - var loginType models.LoginType - if dbErr := db.First(&loginType, body.ID).Error; dbErr != nil { - utils.JsonResponse(w, http.StatusBadRequest, false, "登录类型不存在", nil) - return - } - - // 检查是否被卡密类型使用 - inUse, usingCardTypes, err := checkLoginTypeInUse(loginType.Name) - if err != nil { - utils.JsonResponse(w, http.StatusInternalServerError, false, "检查使用状态失败", nil) - return - } - if inUse { - utils.JsonResponse(w, http.StatusBadRequest, false, "该登录类型正在被以下卡密类型使用,无法删除:"+strings.Join(usingCardTypes, "、"), nil) - return - } - - if err := db.Delete(&models.LoginType{}, body.ID).Error; err != nil { - utils.JsonResponse(w, http.StatusInternalServerError, false, "删除失败", nil) - return - } - utils.JsonResponse(w, http.StatusOK, true, "删除成功", nil) -} - -// LoginTypesBatchDeleteHandler 批量删除登录方式 -// - 接收JSON: {ids: []} -func LoginTypesBatchDeleteHandler(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodPost { - http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed) - return - } - var body struct { - IDs []uint `json:"ids"` - } - if err := json.NewDecoder(r.Body).Decode(&body); err != nil || len(body.IDs) == 0 { - utils.JsonResponse(w, http.StatusBadRequest, false, "参数错误", nil) - return - } - - // 检查批量删除的登录类型是否被使用 - hasUsage, usageMap, err := checkLoginTypesByIDsInUse(body.IDs) - if err != nil { - utils.JsonResponse(w, http.StatusInternalServerError, false, "检查使用状态失败", nil) - return - } - if hasUsage { - // 构建详细的错误信息 - var errorMessages []string - db, _ := database.GetDB() - for loginTypeID, usingCardTypes := range usageMap { - var loginType models.LoginType - if db.First(&loginType, loginTypeID).Error == nil { - errorMessages = append(errorMessages, loginType.Name+"(被"+strings.Join(usingCardTypes, "、")+"使用)") - } - } - utils.JsonResponse(w, http.StatusBadRequest, false, "以下登录类型正在被使用,无法删除:"+strings.Join(errorMessages, ";"), nil) - return - } - - db, err := database.GetDB() - if err != nil { - utils.JsonResponse(w, http.StatusInternalServerError, false, "数据库连接失败", nil) - return - } - if err := db.Delete(&models.LoginType{}, body.IDs).Error; err != nil { - utils.JsonResponse(w, http.StatusInternalServerError, false, "批量删除失败", nil) - return - } - utils.JsonResponse(w, http.StatusOK, true, "批量删除成功", nil) -} - -// LoginTypesBatchEnableHandler 批量启用 -// - 接收JSON: {ids: []} -func LoginTypesBatchEnableHandler(w http.ResponseWriter, r *http.Request) { - batchUpdateLoginTypeStatus(w, r, 1) -} - -// LoginTypesBatchDisableHandler 批量禁用 -// - 接收JSON: {ids: []} -func LoginTypesBatchDisableHandler(w http.ResponseWriter, r *http.Request) { - batchUpdateLoginTypeStatus(w, r, 0) -} - -// batchUpdateLoginTypeStatus 批量更新登录方式状态的通用函数 -// - status: 1 启用,0 禁用 -func batchUpdateLoginTypeStatus(w http.ResponseWriter, r *http.Request, status int) { - if r.Method != http.MethodPost { - http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed) - return - } - var body struct { - IDs []uint `json:"ids"` - } - if err := json.NewDecoder(r.Body).Decode(&body); err != nil || len(body.IDs) == 0 { - utils.JsonResponse(w, http.StatusBadRequest, false, "参数错误", nil) - return - } - db, err := database.GetDB() - if err != nil { - utils.JsonResponse(w, http.StatusInternalServerError, false, "数据库连接失败", nil) - return - } - if err := db.Model(&models.LoginType{}).Where("id IN ?", body.IDs).Update("status", status).Error; err != nil { - utils.JsonResponse(w, http.StatusInternalServerError, false, "批量更新失败", nil) - return - } - utils.JsonResponse(w, http.StatusOK, true, "操作成功", nil) -} diff --git a/database/migrate.go b/database/migrate.go index 760d426..b652bae 100644 --- a/database/migrate.go +++ b/database/migrate.go @@ -17,7 +17,7 @@ func AutoMigrate() error { if err != nil { return err } - if err := db.AutoMigrate(&models.User{}, &models.Settings{}, &models.LoginType{}, &models.CardType{}, &models.Card{}, &models.App{}, &models.API{}); err != nil { + if err := db.AutoMigrate(&models.User{}, &models.Settings{}, &models.App{}, &models.API{}); err != nil { logrus.WithError(err).Error("AutoMigrate 执行失败") return err } diff --git a/database/seed_settings.go b/database/seed_settings.go index 11d92ff..e5805f2 100644 --- a/database/seed_settings.go +++ b/database/seed_settings.go @@ -88,11 +88,6 @@ func SeedDefaultSettings() error { Value: "https://www.beian.gov.cn/portal/registerSystemInfo?recordcode=11000002000001", Description: "公安备案查询链接,留空则不显示", }, - { - Name: "card_batch_counter", - Value: "0", - Description: "卡密批次号计数器(用于记录上次生成批次号的序号,自增使用)", - }, } // 逐个检查并创建不存在的设置项 diff --git a/models/app.go b/models/app.go index b61f681..48e5f47 100644 --- a/models/app.go +++ b/models/app.go @@ -38,6 +38,18 @@ type App struct { DownloadType int `gorm:"default:0;not null;comment:更新方式,0=不启用更新,1=自动更新,2=手动下载" json:"download_type"` // DownloadURL:下载地址 DownloadURL string `gorm:"size:500;comment:下载地址" json:"download_url"` + // Announcement:程序公告内容(base64编码存储) + Announcement string `gorm:"type:text;comment:程序公告内容,base64编码存储" json:"announcement"` + // LoginType:登陆方式(0=顶号登录(默认),1=非顶号登录) + LoginType int `gorm:"default:0;not null;comment:登陆方式,0=顶号登录,1=非顶号登录" json:"login_type"` + // MultiOpenScope:多开范围(0=单电脑,1=单IP,2=全部电脑(默认)) + MultiOpenScope int `gorm:"default:2;not null;comment:多开范围,0=单电脑,1=单IP,2=全部电脑" json:"multi_open_scope"` + // CleanInterval:清理间隔(单位:小时,默认1小时) + CleanInterval int `gorm:"default:1;not null;comment:清理间隔,单位小时" json:"clean_interval"` + // CheckInterval:校验间隔(单位:分钟,默认10分钟) + CheckInterval int `gorm:"default:10;not null;comment:校验间隔,单位分钟" json:"check_interval"` + // MultiOpenCount:多开数量(默认1) + MultiOpenCount int `gorm:"default:1;not null;comment:多开数量" json:"multi_open_count"` // CreatedAt/UpdatedAt:时间字段,返回为 created_at/updated_at,便于前端展示 CreatedAt time.Time `gorm:"comment:创建时间" json:"created_at"` UpdatedAt time.Time `gorm:"comment:更新时间" json:"updated_at"` diff --git a/models/card.go b/models/card.go deleted file mode 100644 index 8a8ab11..0000000 --- a/models/card.go +++ /dev/null @@ -1,27 +0,0 @@ -package models - -import ( - "time" -) - -// Card 卡密模型 -// 用于存储和管理系统中的卡密信息,包括卡密号码、状态、使用情况等 -type Card struct { - // ID:主键,自增 - ID uint `gorm:"primaryKey;comment:卡密ID,自增主键" json:"id"` - // CardNumber:卡密号码,唯一且非空 - CardNumber string `gorm:"size:200;not null;comment:卡密号码(十六进制字符串)" json:"card_number"` - // CardTypeID:所属卡密类型ID(外键) - CardTypeID uint `gorm:"not null;index;comment:所属卡密类型ID(外键)" json:"card_type_id"` - // Status:状态(0=未使用,1=已使用,2=禁用) - Status int `gorm:"default:0;not null;comment:状态,0=未使用,1=已使用,2=禁用" json:"status"` - // Batch:批次标识,用于区分导入或生成批次 - Batch string `gorm:"size:100;comment:批次标识" json:"batch"` - // Remark:备注信息 - Remark string `gorm:"size:255;comment:备注信息" json:"remark"` - // UsedAt:使用时间,未使用为NULL(调整到创建时间前面,以便前端展示顺序一致) - UsedAt *time.Time `gorm:"comment:使用时间" json:"used_at"` - // CreatedAt/UpdatedAt:时间字段 - CreatedAt time.Time `gorm:"comment:创建时间" json:"created_at"` - UpdatedAt time.Time `gorm:"comment:更新时间" json:"updated_at"` -} diff --git a/models/card_type.go b/models/card_type.go deleted file mode 100644 index 919e043..0000000 --- a/models/card_type.go +++ /dev/null @@ -1,24 +0,0 @@ -package models - -import "time" - -// CardType 卡密类型表模型 -// 用于管理不同类型的卡密(如:ChatGPT、Claude、Suno、Grok等) -// ID 为自增主键 -// Name 为卡密类型名称,唯一索引 -// Status 为状态(1:启用 0:禁用),默认为1 -// CreatedAt/UpdatedAt 由 GORM 自动维护 - -type CardType struct { - // ID:主键,自增,同时通过 json 标签保证前端接收为 id - ID uint `gorm:"primaryKey;comment:卡密类型ID,自增主键" json:"id"` - // Name:名称,唯一;json 名称与前端一致 - Name string `gorm:"uniqueIndex;size:100;not null;comment:卡密类型名称,唯一索引" json:"name"` - // Status:状态(1=启用,0=禁用);json 名称与前端一致 - Status int `gorm:"default:1;not null;comment:状态,1=启用,0=禁用" json:"status"` - // LoginTypes:登录方式(逗号分隔);json 使用 login_types - LoginTypes string `gorm:"type:varchar(500);default:'';comment:登录方式,多个用逗号分隔" json:"login_types"` - // CreatedAt/UpdatedAt:时间字段,返回为 created_at/updated_at,便于前端展示 - CreatedAt time.Time `gorm:"comment:创建时间" json:"created_at"` - UpdatedAt time.Time `gorm:"comment:更新时间" json:"updated_at"` -} diff --git a/models/login_type.go b/models/login_type.go deleted file mode 100644 index bd70c9d..0000000 --- a/models/login_type.go +++ /dev/null @@ -1,24 +0,0 @@ -package models - -import "time" - -// LoginType 登录类型表模型 -// 用于管理不同的登录方式,如直登、Google、Microsoft、Apple等 -// ID 为自增主键 -// Name 为登录类型名称,唯一索引 -// Status 为状态(1:启用 0:禁用),默认为1 -// CreatedAt/UpdatedAt 由 GORM 自动维护 - -type LoginType struct { - // ID:主键,自增,同时通过 json 标签保证前端接收为 id - ID uint `gorm:"primaryKey;comment:登录类型ID,自增主键" json:"id"` - // Name:名称,唯一;json 名称与前端一致 - Name string `gorm:"uniqueIndex;size:100;not null;comment:登录类型名称,唯一索引" json:"name"` - // Status:状态(1=启用,0=禁用);json 名称与前端一致 - Status int `gorm:"default:1;not null;comment:状态,1=启用,0=禁用" json:"status"` - // VerifyTypes:验证方式(逗号分隔);json 使用 verify_types;用于记录多种验证方式,输入内容用多个用逗号分隔 - VerifyTypes string `gorm:"type:varchar(500);default:'';comment:验证方式,输入内容用多个用逗号分隔" json:"verify_types"` - // CreatedAt/UpdatedAt:时间字段,返回为 created_at/updated_at,便于前端展示 - CreatedAt time.Time `gorm:"comment:创建时间" json:"created_at"` - UpdatedAt time.Time `gorm:"comment:更新时间" json:"updated_at"` -} diff --git a/recharge.db b/recharge.db new file mode 100644 index 0000000000000000000000000000000000000000..50fcab35019be73973a1911cd676787476141a87 GIT binary patch literal 45056 zcmeI4UrZax9mm%=!N$aFqL%n7HM-4RB0>Rs*EV3HrgtPER|*C~Kuy%D_Of1_Rs1jB zHIRF0Hh%$;kV|qQaETHk(s0QgV-}=-CumAKVQq?|Hsyeg&gE5Ye zs4p$|TbcEGe!rdh&1b$dW@kNn>}b0n@l<~>>}Ms)Mrh^z+v4+*le;`bH5GTt>h41Y0P(*IfiF-n9D2mk>f00e*l z5cr=W@JoZvR9C5ywL-wd4+)+j7aI~pymOu6&*0rG`CxlXIvi>r3f4^t0g3nWVXCXU)!E#Af@*6yK{faGbhdV& zbDS+5J+*4s(wPvy>fF)ULv{4Fx2J5lN|eM8NmOonCeA#AD6vvRT%Q$nxaCN5Z+j2L z)Vjq;pI?w%tj`+^3(`q{5pTNI9pTX@Bsj5;ln4^V|nASl^)E7}%+ASl6 z5SAF$kGuN9FG^JzjQk)igAkHINYv%a0G+aN8K_z3xAR7tGr;)IX61$ily!}SeW`+xt^&M8Q08t&xjEZo&yC8egC8qJ9mqtiZg zJ?j$r(-9s?S6us1yM|bvaUYJT7M)owpVa6~CX?p8Ma@$!Qr(rTIbV(8twxVb%=tY& z#ERlzFpR#6@`i$p!6mZ3^#esX=qu2%-l~Q0wYi352=syt2mk>f00e*l5C8%|00;m9 zAOHk_01(I!klX&W-9VV+t+G+?bg~}55ZGg7_b}Ey+()NR^?83b*l=WstEZcq`KFpxwtG<@uKI;p0av$E<9BZ}7CW&1?2s?fsmU>8oe!t-Q@<=V_LR9{0o_S5vY+41#%2^k~Ef1n*~KmZ5;0U!VbfB+Bx0zd!=00AHX1c1Q*F@bF* z+FhE=??WmK8m*~B{XIh!n##cU|Nlv#7i>TP2mk>f00e*l5C8%|00;m9AOHk_z-A&~ zK-d53FBht|eoq6-2bQ_1GD-_qPW6gCC^M=7Jvn0K?FDv#NVWx%h=LH=@j3j!h58?mbM*KS16!#C1rRrx_BjadnG=zRPfh*cA94FMSlI4W0!Xtb;Rz@-rO2Rm%czT z#9w}r3WLfEZ+sy)LS=kmNS2o6)vv zmw#l?5${J(Gxi6ANM|m*iu6!0bS5l#PfFD9KBuryDCP2)GCCU@JsZ0-Z>E}kKB^nr zMXDRER}K&G9&>W~o5Y1F<*QGTw&GJ4;v>VcyXRxWw-WQuVz;j%zA<^epyq51bfcqK z)2`a((lQ-UM+#1-pT&!@11wsn>+++XK*zs+5r1({Sw5>={2IxMK7bFe&8L!40&@Tv zk`rI15()=mZ?ZQO3uM(UYxPKDm0Vk!$cuQvy$e5~AQ+KQW$5VgRAT;a;`znc#B=mY zPCR?PJRE;?{q^z)3MBdBc4BUZQJ#(?Tj4ltj-p{?&9YIaBP?XZ>GbgZELt_ME{=BL zrM*n-*3xq%ys$k-C)A4JUluvG}RAQ5J$+J@_*Ed9i;6{*aG1b3uPX zzKLX!JU17g`wBrpFrrIWmAMD8J6DwJW6{MaWprA(K8wcF!tpSU;@y7LKCnecyj$1g zbc%STIeH*M(1Mn4^O9)6$L3JLiw4j5lgW1UN%?FgK5{v+Ft4m!ctg32&04Hwn5J~N#WYk-!iza+U2$qJy8{rccg2J92z*alqsg1kuc*-O&Pg+3m%MrCZP;Zst(?@pugRHwHGE%Nr^%n+ zEW`Qa%{A&PwHD2~nLi!&%$t8J-Jz}0teFGbYSg}~$xcFT!A0gz3~BL6YGP*oq{OJ* zU8bg}lMwj+KYHjtY(M}A00AHX1b_e#00KY&2mk>f00e-*CMJOI|HJkFCT?J;8V~>i zKmZ5;0U!VbfB+Bx0zd!=00Be*?*D@ffB+Bx0zd!=00AHX1b_e#00KY&2yA`=aQ}bv zw=vWZ2mk>f00e*l5C8%|00;m9AOHk_09^lr27mw%00KY&2mk>f00e*l5C8%|00?Y; G0{;P>u?i^w literal 0 HcmV?d00001 diff --git a/server/admin.go b/server/admin.go index e884f36..abea1f4 100644 --- a/server/admin.go +++ b/server/admin.go @@ -40,9 +40,7 @@ func RegisterAdminRoutes(mux *http.ServeMux) { mux.HandleFunc("/admin/user", adminctl.AdminAuthRequired(adminctl.UserFragmentHandler)) mux.HandleFunc("/admin/settings", adminctl.AdminAuthRequired(adminctl.SettingsFragmentHandler)) mux.HandleFunc("/admin/apps", adminctl.AdminAuthRequired(adminctl.AppsFragmentHandler)) - mux.HandleFunc("/admin/logintypes", adminctl.AdminAuthRequired(adminctl.LoginTypesFragmentHandler)) - mux.HandleFunc("/admin/cardtypes", adminctl.AdminAuthRequired(adminctl.CardTypesFragmentHandler)) - mux.HandleFunc("/admin/cards", adminctl.AdminAuthRequired(adminctl.CardsFragmentHandler)) + // 个人资料API mux.HandleFunc("/admin/api/user/profile", adminctl.AdminAuthRequired(adminctl.UserProfileQueryHandler)) @@ -52,8 +50,6 @@ func RegisterAdminRoutes(mux *http.ServeMux) { mux.HandleFunc("/admin/api/settings", adminctl.AdminAuthRequired(adminctl.SettingsQueryHandler)) mux.HandleFunc("/admin/api/settings/update", adminctl.AdminAuthRequired(adminctl.SettingsUpdateHandler)) - // 供前端下拉选择卡密类型 - mux.HandleFunc("/admin/api/cards/types", adminctl.AdminAuthRequired(adminctl.GetCardTypesHandler)) // 应用管理API mux.HandleFunc("/admin/api/apps/list", adminctl.AdminAuthRequired(adminctl.AppsListHandler)) mux.HandleFunc("/admin/api/apps/create", adminctl.AdminAuthRequired(adminctl.AppCreateHandler)) @@ -61,39 +57,13 @@ func RegisterAdminRoutes(mux *http.ServeMux) { mux.HandleFunc("/admin/api/apps/delete", adminctl.AdminAuthRequired(adminctl.AppDeleteHandler)) mux.HandleFunc("/admin/api/apps/batch_delete", adminctl.AdminAuthRequired(adminctl.AppsBatchDeleteHandler)) mux.HandleFunc("/admin/api/apps/batch_update_status", adminctl.AdminAuthRequired(adminctl.AppsBatchUpdateStatusHandler)) - // 登录方式管理API - mux.HandleFunc("/admin/api/login_types/list", adminctl.AdminAuthRequired(adminctl.LoginTypesListHandler)) - mux.HandleFunc("/admin/api/login_types/create", adminctl.AdminAuthRequired(adminctl.LoginTypeCreateHandler)) - mux.HandleFunc("/admin/api/login_types/update", adminctl.AdminAuthRequired(adminctl.LoginTypeUpdateHandler)) - mux.HandleFunc("/admin/api/login_types/delete", adminctl.AdminAuthRequired(adminctl.LoginTypeDeleteHandler)) - mux.HandleFunc("/admin/api/login_types/batch_delete", adminctl.AdminAuthRequired(adminctl.LoginTypesBatchDeleteHandler)) - mux.HandleFunc("/admin/api/login_types/batch_enable", adminctl.AdminAuthRequired(adminctl.LoginTypesBatchEnableHandler)) - mux.HandleFunc("/admin/api/login_types/batch_disable", adminctl.AdminAuthRequired(adminctl.LoginTypesBatchDisableHandler)) - // 卡密类型管理API - mux.HandleFunc("/admin/api/card_types/list", adminctl.AdminAuthRequired(adminctl.CardTypesListHandler)) - mux.HandleFunc("/admin/api/card_types/create", adminctl.AdminAuthRequired(adminctl.CardTypeCreateHandler)) - mux.HandleFunc("/admin/api/card_types/update", adminctl.AdminAuthRequired(adminctl.CardTypeUpdateHandler)) - mux.HandleFunc("/admin/api/card_types/delete", adminctl.AdminAuthRequired(adminctl.CardTypeDeleteHandler)) - mux.HandleFunc("/admin/api/card_types/batch_delete", adminctl.AdminAuthRequired(adminctl.CardTypesBatchDeleteHandler)) - mux.HandleFunc("/admin/api/card_types/batch_enable", adminctl.AdminAuthRequired(adminctl.CardTypesBatchEnableHandler)) - mux.HandleFunc("/admin/api/card_types/batch_disable", adminctl.AdminAuthRequired(adminctl.CardTypesBatchDisableHandler)) - // 卡密管理API - mux.HandleFunc("/admin/api/cards/list", adminctl.AdminAuthRequired(adminctl.CardsListHandler)) - mux.HandleFunc("/admin/api/cards/create", adminctl.AdminAuthRequired(adminctl.CardCreateHandler)) - mux.HandleFunc("/admin/api/cards/update", adminctl.AdminAuthRequired(adminctl.CardUpdateHandler)) - mux.HandleFunc("/admin/api/cards/delete", adminctl.AdminAuthRequired(adminctl.CardDeleteHandler)) - mux.HandleFunc("/admin/api/cards/batch_delete", adminctl.AdminAuthRequired(adminctl.CardsBatchDeleteHandler)) - mux.HandleFunc("/admin/api/cards/batch_update_status", adminctl.AdminAuthRequired(adminctl.CardsBatchUpdateStatusHandler)) - // 新增:卡密导出API(CSV下载) - mux.HandleFunc("/admin/api/cards/export", adminctl.AdminAuthRequired(adminctl.CardsExportHandler)) - // 新增:导出选中卡密API - mux.HandleFunc("/admin/api/cards/export_selected", adminctl.AdminAuthRequired(adminctl.CardsExportSelectedHandler)) + mux.HandleFunc("/admin/api/apps/reset_secret", adminctl.AdminAuthRequired(adminctl.AppResetSecretHandler)) + mux.HandleFunc("/admin/api/apps/get_announcement", adminctl.AdminAuthRequired(adminctl.AppGetAnnouncementHandler)) + mux.HandleFunc("/admin/api/apps/update_announcement", adminctl.AdminAuthRequired(adminctl.AppUpdateAnnouncementHandler)) + mux.HandleFunc("/admin/api/apps/get_multi_config", adminctl.AdminAuthRequired(adminctl.AppGetMultiConfigHandler)) + mux.HandleFunc("/admin/api/apps/update_multi_config", adminctl.AdminAuthRequired(adminctl.AppUpdateMultiConfigHandler)) + // 系统信息API(用于仪表盘定时刷新) mux.HandleFunc("/admin/api/system/info", adminctl.AdminAuthRequired(adminctl.SystemInfoHandler)) - - // 卡密统计API(用于仪表盘统计显示) - mux.HandleFunc("/admin/api/cards/stats_overview", adminctl.AdminAuthRequired(adminctl.CardStatsOverviewHandler)) - mux.HandleFunc("/admin/api/cards/trend_30days", adminctl.AdminAuthRequired(adminctl.CardStatsTrend30DaysHandler)) - mux.HandleFunc("/admin/api/cards/stats_simple", adminctl.AdminAuthRequired(adminctl.CardStatsSimpleHandler)) } diff --git a/services/query.go b/services/query.go index 09549d1..a42fe05 100644 --- a/services/query.go +++ b/services/query.go @@ -12,37 +12,7 @@ import ( -// FindCardByCardNumber 根据卡号查找卡密 -// cardNumber: 卡号 -// db: 数据库连接 -// 返回: 卡密信息和错误 -func FindCardByCardNumber(cardNumber string, db *gorm.DB) (*models.Card, error) { - key := fmt.Sprintf("card:number:%s", cardNumber) - return utils.RedisGetOrSet(context.Background(), key, 60*time.Second, func() (*models.Card, error) { - var card models.Card - err := db.Where("card_number = ?", cardNumber).First(&card).Error - if err != nil { - return nil, err - } - return &card, nil - }) -} -// FindCardTypeByID 根据ID查找卡密类型 -// id: 卡密类型ID -// db: 数据库连接 -// 返回: 卡密类型信息和错误 -func FindCardTypeByID(id uint, db *gorm.DB) (*models.CardType, error) { - key := fmt.Sprintf("card_type:id:%d", id) - return utils.RedisGetOrSet(context.Background(), key, 30*time.Minute, func() (*models.CardType, error) { - var cardType models.CardType - err := db.Where("id = ?", id).First(&cardType).Error - if err != nil { - return nil, err - } - return &cardType, nil - }) -} // FindSettingByName 根据名称查找设置 // name: 设置名称 diff --git a/web/template/admin/apps.html b/web/template/admin/apps.html index dc32959..5157f4e 100644 --- a/web/template/admin/apps.html +++ b/web/template/admin/apps.html @@ -33,8 +33,13 @@
-
- - - - - - - -{{ end }} \ No newline at end of file diff --git a/web/template/admin/cards.html b/web/template/admin/cards.html deleted file mode 100644 index 898ff77..0000000 --- a/web/template/admin/cards.html +++ /dev/null @@ -1,771 +0,0 @@ -{{ define "cards.html" }} -
-

卡密管理

-
- - - - - - - - -
- -
-
筛选
-
-
-
-
- -
- -
-
-
- -
- -
-
-
- -
- -
-
-
- - -
-
-
-
-
- -
-
卡密列表
-
-
- -
-
- - - - - - - - - -
- - -{{ end }} \ No newline at end of file diff --git a/web/template/admin/dashboard.html b/web/template/admin/dashboard.html index 8ac534e..4cc6c85 100644 --- a/web/template/admin/dashboard.html +++ b/web/template/admin/dashboard.html @@ -39,41 +39,7 @@ - -
-
- -
-
-
当日卡密统计 总数:-
-
-
-
-
-
- -
-
-
所有卡密统计 总数:-
-
-
-
-
-
-
- -
-
-
-
近30天卡密走势
-
-
-
-
-
-
-
{{ end }} \ No newline at end of file diff --git a/web/template/admin/layout.html b/web/template/admin/layout.html index 9a8d6fd..ff33551 100644 --- a/web/template/admin/layout.html +++ b/web/template/admin/layout.html @@ -54,14 +54,6 @@
应用列表
-
  • - 卡密管理 -
    -
    登录类型
    -
    卡密类型
    -
    卡密列表
    -
    -
  • diff --git a/web/template/admin/login_types.html b/web/template/admin/login_types.html deleted file mode 100644 index 0fae95a..0000000 --- a/web/template/admin/login_types.html +++ /dev/null @@ -1,422 +0,0 @@ -{{ define "login_types.html" }} -
    -

    登录方式管理

    -
    - - - - -
    - -
    -
    筛选
    -
    -
    -
    -
    - -
    - -
    -
    -
    - -
    - -
    -
    -
    - - -
    -
    -
    -
    -
    - -
    -
    登录方式列表
    -
    -
    - -
    -
    - - - -
    - - -{{ end }} \ No newline at end of file