Remove the card password related

New application-related
This commit is contained in:
2025-10-24 01:48:54 +08:00
parent 11bff937bd
commit f03d2d0b12
22 changed files with 657 additions and 3623 deletions

View File

@@ -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": "多开配置更新成功"})
}

View File

@@ -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
}
}
}

View File

@@ -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)
}

View File

@@ -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)
}

View File

@@ -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)
}