| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806 |
- package handlers
- import (
- "encoding/csv"
- "encoding/json"
- "license-admin/models"
- "math/rand"
- "net/http"
- "strconv"
- "strings"
- "time"
- "github.com/gin-gonic/gin"
- "gorm.io/gorm"
- )
- // CreateLicenseRequest 创建 License 请求结构
- type CreateLicenseRequest struct {
- Key string `json:"key" binding:"required"` // 激活码(必填)
- MaxDevices int `json:"max_devices"` // 最大设备数(可选,默认2)
- BoundDevices string `json:"bound_devices,omitempty"` // 初始绑定设备列表(可选,默认为空数组)
- }
- // UpdateLicenseRequest 更新 License 请求结构
- type UpdateLicenseRequest struct {
- Key string `json:"key,omitempty"` // 激活码(可选)
- MaxDevices *int `json:"max_devices,omitempty"` // 最大设备数(可选,使用指针以区分零值)
- BoundDevices string `json:"bound_devices,omitempty"` // 绑定设备列表(可选)
- }
- // LicenseResponse License 响应结构
- type LicenseResponse struct {
- Code int `json:"code"` // 状态码:0 表示成功,非0 表示失败
- Msg string `json:"msg"` // 响应消息
- Data *models.License `json:"data,omitempty"` // License 数据
- }
- // LicenseListResponse License 列表响应结构
- type LicenseListResponse struct {
- Code int `json:"code"` // 状态码:0 表示成功,非0 表示失败
- Msg string `json:"msg"` // 响应消息
- Data []models.License `json:"data"` // License 列表
- Total int64 `json:"total"` // 总数
- }
- // CreateLicense 创建新的 License
- // POST /api/licenses
- func CreateLicense(db *gorm.DB) gin.HandlerFunc {
- return func(c *gin.Context) {
- var req CreateLicenseRequest
- if err := c.ShouldBindJSON(&req); err != nil {
- c.JSON(http.StatusBadRequest, LicenseResponse{
- Code: 400,
- Msg: "请求参数错误: " + err.Error(),
- })
- return
- }
- // 检查 Key 是否已存在(使用 Count 更高效,不会产生 record not found 日志)
- var count int64
- if err := db.Model(&models.License{}).Where("license_key = ?", req.Key).Count(&count).Error; err != nil {
- // 数据库查询错误
- c.JSON(http.StatusInternalServerError, LicenseResponse{
- Code: 500,
- Msg: "数据库查询错误: " + err.Error(),
- })
- return
- }
- if count > 0 {
- // Key 已存在
- c.JSON(http.StatusBadRequest, LicenseResponse{
- Code: 400,
- Msg: "激活码已存在",
- })
- return
- }
- // 设置默认值
- if req.MaxDevices <= 0 {
- req.MaxDevices = 2 // 默认最大设备数为 2
- }
- if req.BoundDevices == "" {
- req.BoundDevices = "[]" // 默认为空数组
- }
- // 创建新的 License
- license := models.License{
- LicenseKey: req.Key,
- MaxDevices: req.MaxDevices,
- BoundDevices: req.BoundDevices,
- }
- if err := db.Create(&license).Error; err != nil {
- c.JSON(http.StatusInternalServerError, LicenseResponse{
- Code: 500,
- Msg: "创建 License 失败: " + err.Error(),
- })
- return
- }
- c.JSON(http.StatusOK, LicenseResponse{
- Code: 0,
- Msg: "创建成功",
- Data: &license,
- })
- }
- }
- // GetLicenseList 获取 License 列表
- // GET /api/licenses?page=1&page_size=10&search=keyword
- func GetLicenseList(db *gorm.DB) gin.HandlerFunc {
- return func(c *gin.Context) {
- // 解析分页参数
- page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
- pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "10"))
- search := c.Query("search") // 搜索关键词
- status := c.Query("status") // 状态筛选:activated(已激活)、unactivated(未激活)
- if page < 1 {
- page = 1
- }
- if pageSize < 1 || pageSize > 100 {
- pageSize = 10
- }
- var licenses []models.License
- var total int64
- // 构建查询
- query := db.Model(&models.License{})
- // 如果有搜索关键词,添加搜索条件(按 license_key 模糊匹配)
- if search != "" {
- query = query.Where("license_key LIKE ?", "%"+search+"%")
- }
- // 状态筛选
- if status == "activated" {
- // 已激活:有绑定设备
- query = query.Where("JSON_LENGTH(COALESCE(bound_devices, '[]')) > 0")
- } else if status == "unactivated" {
- // 未激活:没有绑定设备
- query = query.Where("JSON_LENGTH(COALESCE(bound_devices, '[]')) = 0 OR bound_devices IS NULL OR bound_devices = ''")
- }
- // 获取总数
- if err := query.Count(&total).Error; err != nil {
- c.JSON(http.StatusInternalServerError, LicenseListResponse{
- Code: 500,
- Msg: "查询总数失败: " + err.Error(),
- Data: []models.License{},
- Total: 0,
- })
- return
- }
- // 使用 SQL 排序:
- // 1. 有绑定设备的优先(bound_devices 不为空且不是 '[]')
- // 2. 然后按心跳时间最新的排序(从 device_heartbeats JSON 中提取最新时间)
- //
- // 使用 MySQL JSON 函数来提取最新的心跳时间:
- // - 对于 MySQL 5.7+,我们可以使用 JSON_EXTRACT 和 JSON_KEYS 来遍历
- // - 但由于提取所有值并找最大值在 SQL 中比较复杂,我们使用一个更实用的方法:
- // 使用 updated_at 字段,因为每次心跳更新(UpdateDeviceHeartbeat)都会更新 updated_at
- // 这样 updated_at 可以准确反映最后心跳时间
- //
- // 排序 SQL:
- // 1. 有设备的排在前面:CASE WHEN JSON_LENGTH(COALESCE(bound_devices, '[]')) > 0 THEN 0 ELSE 1 END
- // 2. 然后按 updated_at 倒序(最新的心跳会更新 updated_at)
- orderBy := `CASE
- WHEN JSON_LENGTH(COALESCE(bound_devices, '[]')) > 0 THEN 0
- ELSE 1
- END ASC,
- updated_at DESC`
- // 分页查询
- offset := (page - 1) * pageSize
- if err := query.Offset(offset).Limit(pageSize).Order(orderBy).Find(&licenses).Error; err != nil {
- c.JSON(http.StatusInternalServerError, LicenseListResponse{
- Code: 500,
- Msg: "查询 License 列表失败: " + err.Error(),
- Data: []models.License{},
- Total: 0,
- })
- return
- }
- c.JSON(http.StatusOK, LicenseListResponse{
- Code: 0,
- Msg: "success",
- Data: licenses,
- Total: total,
- })
- }
- }
- // GetLicense 获取单个 License
- // GET /api/licenses/:id
- func GetLicense(db *gorm.DB) gin.HandlerFunc {
- return func(c *gin.Context) {
- id, err := strconv.ParseUint(c.Param("id"), 10, 32)
- if err != nil {
- c.JSON(http.StatusBadRequest, LicenseResponse{
- Code: 400,
- Msg: "无效的 ID 参数",
- })
- return
- }
- var license models.License
- result := db.First(&license, id)
- if result.Error != nil {
- if result.Error == gorm.ErrRecordNotFound {
- c.JSON(http.StatusNotFound, LicenseResponse{
- Code: 404,
- Msg: "License 不存在",
- })
- return
- }
- c.JSON(http.StatusInternalServerError, LicenseResponse{
- Code: 500,
- Msg: "查询失败: " + result.Error.Error(),
- })
- return
- }
- c.JSON(http.StatusOK, LicenseResponse{
- Code: 0,
- Msg: "success",
- Data: &license,
- })
- }
- }
- // UpdateLicense 更新 License
- // PUT /api/licenses/:id
- func UpdateLicense(db *gorm.DB) gin.HandlerFunc {
- return func(c *gin.Context) {
- id, err := strconv.ParseUint(c.Param("id"), 10, 32)
- if err != nil {
- c.JSON(http.StatusBadRequest, LicenseResponse{
- Code: 400,
- Msg: "无效的 ID 参数",
- })
- return
- }
- // 查找 License
- var license models.License
- result := db.First(&license, id)
- if result.Error != nil {
- if result.Error == gorm.ErrRecordNotFound {
- c.JSON(http.StatusNotFound, LicenseResponse{
- Code: 404,
- Msg: "License 不存在",
- })
- return
- }
- c.JSON(http.StatusInternalServerError, LicenseResponse{
- Code: 500,
- Msg: "查询失败: " + result.Error.Error(),
- })
- return
- }
- // 解析更新请求
- var req UpdateLicenseRequest
- if err := c.ShouldBindJSON(&req); err != nil {
- c.JSON(http.StatusBadRequest, LicenseResponse{
- Code: 400,
- Msg: "请求参数错误: " + err.Error(),
- })
- return
- }
- // 更新字段
- if req.Key != "" {
- // 检查新 Key 是否与其他 License 冲突(使用 Count 更高效)
- var count int64
- if err := db.Model(&models.License{}).Where("license_key = ? AND id != ?", req.Key, id).Count(&count).Error; err != nil {
- c.JSON(http.StatusInternalServerError, LicenseResponse{
- Code: 500,
- Msg: "检查激活码失败: " + err.Error(),
- })
- return
- }
- if count > 0 {
- c.JSON(http.StatusBadRequest, LicenseResponse{
- Code: 400,
- Msg: "激活码已存在",
- })
- return
- }
- license.LicenseKey = req.Key
- }
- if req.MaxDevices != nil {
- if *req.MaxDevices <= 0 {
- c.JSON(http.StatusBadRequest, LicenseResponse{
- Code: 400,
- Msg: "最大设备数必须大于 0",
- })
- return
- }
- license.MaxDevices = *req.MaxDevices
- }
- if req.BoundDevices != "" {
- // 验证 BoundDevices 是否为有效的 JSON 数组
- var testDevices []string
- if err := json.Unmarshal([]byte(req.BoundDevices), &testDevices); err != nil {
- c.JSON(http.StatusBadRequest, LicenseResponse{
- Code: 400,
- Msg: "bound_devices 必须是有效的 JSON 数组: " + err.Error(),
- })
- return
- }
- license.BoundDevices = req.BoundDevices
- }
- // 保存更新
- if err := db.Save(&license).Error; err != nil {
- c.JSON(http.StatusInternalServerError, LicenseResponse{
- Code: 500,
- Msg: "更新失败: " + err.Error(),
- })
- return
- }
- c.JSON(http.StatusOK, LicenseResponse{
- Code: 0,
- Msg: "更新成功",
- Data: &license,
- })
- }
- }
- // DeleteLicense 删除 License
- // DELETE /api/licenses/:id
- func DeleteLicense(db *gorm.DB) gin.HandlerFunc {
- return func(c *gin.Context) {
- id, err := strconv.ParseUint(c.Param("id"), 10, 32)
- if err != nil {
- c.JSON(http.StatusBadRequest, gin.H{
- "code": 400,
- "msg": "无效的 ID 参数",
- })
- return
- }
- // 查找 License
- var license models.License
- result := db.First(&license, id)
- if result.Error != nil {
- if result.Error == gorm.ErrRecordNotFound {
- c.JSON(http.StatusNotFound, gin.H{
- "code": 404,
- "msg": "License 不存在",
- })
- return
- }
- c.JSON(http.StatusInternalServerError, gin.H{
- "code": 500,
- "msg": "查询失败: " + result.Error.Error(),
- })
- return
- }
- // 删除 License
- if err := db.Delete(&license).Error; err != nil {
- c.JSON(http.StatusInternalServerError, gin.H{
- "code": 500,
- "msg": "删除失败: " + err.Error(),
- })
- return
- }
- c.JSON(http.StatusOK, gin.H{
- "code": 0,
- "msg": "删除成功",
- })
- }
- }
- // BatchDeleteLicenseRequest 批量删除 License 请求结构
- type BatchDeleteLicenseRequest struct {
- IDs []uint `json:"ids" binding:"required"` // License ID 列表
- }
- // BatchDeleteLicenseResponse 批量删除响应结构
- type BatchDeleteLicenseResponse struct {
- Code int `json:"code"` // 状态码:0 表示成功,非0 表示失败
- Msg string `json:"msg"` // 响应消息
- Total int `json:"total"` // 成功删除的数量
- }
- // BatchDeleteLicense 批量删除 License
- // DELETE /api/licenses/batch
- func BatchDeleteLicense(db *gorm.DB) gin.HandlerFunc {
- return func(c *gin.Context) {
- var req BatchDeleteLicenseRequest
- if err := c.ShouldBindJSON(&req); err != nil {
- c.JSON(http.StatusBadRequest, BatchDeleteLicenseResponse{
- Code: 400,
- Msg: "请求参数错误: " + err.Error(),
- Total: 0,
- })
- return
- }
- // 验证参数
- if len(req.IDs) == 0 {
- c.JSON(http.StatusBadRequest, BatchDeleteLicenseResponse{
- Code: 400,
- Msg: "请至少选择一个 License",
- Total: 0,
- })
- return
- }
- if len(req.IDs) > 100 {
- c.JSON(http.StatusBadRequest, BatchDeleteLicenseResponse{
- Code: 400,
- Msg: "一次最多只能删除 100 个 License",
- Total: 0,
- })
- return
- }
- // 批量删除
- result := db.Where("id IN ?", req.IDs).Delete(&models.License{})
- if result.Error != nil {
- c.JSON(http.StatusInternalServerError, BatchDeleteLicenseResponse{
- Code: 500,
- Msg: "批量删除失败: " + result.Error.Error(),
- Total: 0,
- })
- return
- }
- deletedCount := int(result.RowsAffected)
- c.JSON(http.StatusOK, BatchDeleteLicenseResponse{
- Code: 0,
- Msg: "成功删除 " + strconv.Itoa(deletedCount) + " 个 License",
- Total: deletedCount,
- })
- }
- }
- // BatchCreateLicenseRequest 批量创建 License 请求结构
- type BatchCreateLicenseRequest struct {
- Prefix string `json:"prefix" binding:"required"` // 激活码前缀(必填)
- Count int `json:"count" binding:"required"` // 生成数量(必填)
- MaxDevices int `json:"max_devices"` // 最大设备数(可选,默认2)
- }
- // BatchCreateLicenseResponse 批量创建响应结构
- type BatchCreateLicenseResponse struct {
- Code int `json:"code"` // 状态码:0 表示成功,非0 表示失败
- Msg string `json:"msg"` // 响应消息
- Data []models.License `json:"data"` // 创建的 License 列表
- Total int `json:"total"` // 成功创建的数量
- }
- // BatchCreateLicense 批量创建 License
- // POST /api/licenses/batch
- func BatchCreateLicense(db *gorm.DB) gin.HandlerFunc {
- return func(c *gin.Context) {
- var req BatchCreateLicenseRequest
- if err := c.ShouldBindJSON(&req); err != nil {
- c.JSON(http.StatusBadRequest, BatchCreateLicenseResponse{
- Code: 400,
- Msg: "请求参数错误: " + err.Error(),
- Data: []models.License{},
- Total: 0,
- })
- return
- }
- // 验证参数
- if req.Count <= 0 || req.Count > 1000 {
- c.JSON(http.StatusBadRequest, BatchCreateLicenseResponse{
- Code: 400,
- Msg: "生成数量必须在 1-1000 之间",
- Data: []models.License{},
- Total: 0,
- })
- return
- }
- // 设置默认值
- if req.MaxDevices <= 0 {
- req.MaxDevices = 2
- }
- // 批量创建 License
- var licenses []models.License
- var successCount int
- var failedKeys []string
- for i := 1; i <= req.Count; i++ {
- // 生成激活码:前缀 + 32位随机字符串
- key := req.Prefix + "-" + generateRandomKey(32)
- // 检查 Key 是否已存在(使用 Count 更高效,不会产生 record not found 日志)
- var count int64
- if err := db.Model(&models.License{}).Where("license_key = ?", key).Count(&count).Error; err != nil {
- // 数据库查询错误
- failedKeys = append(failedKeys, key)
- continue
- }
- if count > 0 {
- // Key 已存在,跳过并记录
- failedKeys = append(failedKeys, key)
- continue
- }
- // 创建新的 License
- license := models.License{
- LicenseKey: key,
- MaxDevices: req.MaxDevices,
- BoundDevices: "[]",
- }
- if err := db.Create(&license).Error; err != nil {
- failedKeys = append(failedKeys, key)
- continue
- }
- licenses = append(licenses, license)
- successCount++
- }
- // 构建响应消息
- msg := "成功创建 " + strconv.Itoa(successCount) + " 个 License"
- if len(failedKeys) > 0 {
- msg += ",跳过 " + strconv.Itoa(len(failedKeys)) + " 个重复或失败的激活码"
- }
- c.JSON(http.StatusOK, BatchCreateLicenseResponse{
- Code: 0,
- Msg: msg,
- Data: licenses,
- Total: successCount,
- })
- }
- }
- // generateRandomKey 生成随机激活码后缀
- func generateRandomKey(length int) string {
- const charset = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
- b := make([]byte, length)
- for i := range b {
- b[i] = charset[getRandomInt(len(charset))]
- }
- return string(b)
- }
- // getRandomInt 获取随机整数
- func getRandomInt(max int) int {
- return rand.Intn(max)
- }
- // ExportLicenseRequest 导出请求结构
- type ExportLicenseRequest struct {
- Format string `json:"format"` // csv 或 json
- }
- // ExportLicenses 导出 License 列表
- // GET /api/licenses/export?format=csv
- func ExportLicenses(db *gorm.DB) gin.HandlerFunc {
- return func(c *gin.Context) {
- format := c.DefaultQuery("format", "csv")
- var licenses []models.License
- // 使用与列表相同的排序逻辑
- orderBy := `CASE
- WHEN JSON_LENGTH(COALESCE(bound_devices, '[]')) > 0 THEN 0
- ELSE 1
- END ASC,
- updated_at DESC`
- if err := db.Order(orderBy).Find(&licenses).Error; err != nil {
- c.JSON(http.StatusInternalServerError, LicenseListResponse{
- Code: 500,
- Msg: "查询失败: " + err.Error(),
- Data: []models.License{},
- Total: 0,
- })
- return
- }
- if format == "csv" {
- // 导出 CSV
- c.Header("Content-Type", "text/csv; charset=utf-8")
- c.Header("Content-Disposition", "attachment; filename=licenses_"+time.Now().Format("20060102_150405")+".csv")
- c.Header("Content-Transfer-Encoding", "binary")
- writer := csv.NewWriter(c.Writer)
- defer writer.Flush()
- // 写入表头
- headers := []string{"ID", "激活码", "最大设备数", "已绑定设备数", "绑定设备列表", "创建时间", "更新时间"}
- writer.Write(headers)
- // 写入数据
- for _, license := range licenses {
- devices, _ := license.GetBoundDeviceList()
- deviceList := strings.Join(devices, "; ")
- row := []string{
- strconv.FormatUint(uint64(license.ID), 10),
- license.LicenseKey,
- strconv.Itoa(license.MaxDevices),
- strconv.Itoa(len(devices)),
- deviceList,
- license.CreatedAt.Format("2006-01-02 15:04:05"),
- license.UpdatedAt.Format("2006-01-02 15:04:05"),
- }
- writer.Write(row)
- }
- } else {
- // 导出 JSON
- c.Header("Content-Type", "application/json; charset=utf-8")
- c.Header("Content-Disposition", "attachment; filename=licenses_"+time.Now().Format("20060102_150405")+".json")
- c.JSON(http.StatusOK, LicenseListResponse{
- Code: 0,
- Msg: "success",
- Data: licenses,
- Total: int64(len(licenses)),
- })
- }
- }
- }
- // BatchUpdateMaxDevicesRequest 批量修改最大设备数请求结构
- type BatchUpdateMaxDevicesRequest struct {
- IDs []uint `json:"ids" binding:"required"`
- MaxDevices int `json:"max_devices" binding:"required,min=1"`
- }
- // BatchUpdateMaxDevices 批量修改最大设备数
- // PUT /api/licenses/batch/max-devices
- func BatchUpdateMaxDevices(db *gorm.DB) gin.HandlerFunc {
- return func(c *gin.Context) {
- var req BatchUpdateMaxDevicesRequest
- if err := c.ShouldBindJSON(&req); err != nil {
- c.JSON(http.StatusBadRequest, LicenseResponse{
- Code: 400,
- Msg: "请求参数错误: " + err.Error(),
- })
- return
- }
- if len(req.IDs) == 0 {
- c.JSON(http.StatusBadRequest, LicenseResponse{
- Code: 400,
- Msg: "请至少选择一个 License",
- })
- return
- }
- // 批量更新
- result := db.Model(&models.License{}).
- Where("id IN ?", req.IDs).
- Update("max_devices", req.MaxDevices)
- if result.Error != nil {
- c.JSON(http.StatusInternalServerError, LicenseResponse{
- Code: 500,
- Msg: "批量更新失败: " + result.Error.Error(),
- })
- return
- }
- c.JSON(http.StatusOK, LicenseResponse{
- Code: 0,
- Msg: "成功更新 " + strconv.FormatInt(result.RowsAffected, 10) + " 个 License 的最大设备数",
- })
- }
- }
- // UnbindDeviceRequest 解绑设备请求结构
- type UnbindDeviceRequest struct {
- DeviceID string `json:"device_id" binding:"required"`
- }
- // UnbindDevice 解绑设备
- // DELETE /api/licenses/:id/devices/:device_id
- func UnbindDevice(db *gorm.DB) gin.HandlerFunc {
- return func(c *gin.Context) {
- id, err := strconv.ParseUint(c.Param("id"), 10, 32)
- if err != nil {
- c.JSON(http.StatusBadRequest, LicenseResponse{
- Code: 400,
- Msg: "无效的 ID 参数",
- })
- return
- }
- deviceID := c.Param("device_id")
- if deviceID == "" {
- c.JSON(http.StatusBadRequest, LicenseResponse{
- Code: 400,
- Msg: "设备ID不能为空",
- })
- return
- }
- var license models.License
- if err := db.First(&license, id).Error; err != nil {
- if err == gorm.ErrRecordNotFound {
- c.JSON(http.StatusNotFound, LicenseResponse{
- Code: 404,
- Msg: "License 不存在",
- })
- return
- }
- c.JSON(http.StatusInternalServerError, LicenseResponse{
- Code: 500,
- Msg: "查询失败: " + err.Error(),
- })
- return
- }
- // 获取设备列表
- devices, err := license.GetBoundDeviceList()
- if err != nil {
- c.JSON(http.StatusInternalServerError, LicenseResponse{
- Code: 500,
- Msg: "解析设备列表失败: " + err.Error(),
- })
- return
- }
- // 移除设备
- newDevices := []string{}
- found := false
- for _, d := range devices {
- if d != deviceID {
- newDevices = append(newDevices, d)
- } else {
- found = true
- }
- }
- if !found {
- c.JSON(http.StatusBadRequest, LicenseResponse{
- Code: 400,
- Msg: "设备未绑定",
- })
- return
- }
- // 更新设备列表
- if err := license.SetBoundDeviceList(newDevices); err != nil {
- c.JSON(http.StatusInternalServerError, LicenseResponse{
- Code: 500,
- Msg: "更新设备列表失败: " + err.Error(),
- })
- return
- }
- // 同时清理激活时间和心跳时间
- var activations map[string]string
- if license.DeviceActivations != "" {
- json.Unmarshal([]byte(license.DeviceActivations), &activations)
- } else {
- activations = make(map[string]string)
- }
- delete(activations, deviceID)
- if activationsData, err := json.Marshal(activations); err == nil {
- license.DeviceActivations = string(activationsData)
- }
- var heartbeats map[string]string
- if license.DeviceHeartbeats != "" {
- json.Unmarshal([]byte(license.DeviceHeartbeats), &heartbeats)
- } else {
- heartbeats = make(map[string]string)
- }
- delete(heartbeats, deviceID)
- if heartbeatsData, err := json.Marshal(heartbeats); err == nil {
- license.DeviceHeartbeats = string(heartbeatsData)
- }
- // 保存到数据库
- if err := db.Save(&license).Error; err != nil {
- c.JSON(http.StatusInternalServerError, LicenseResponse{
- Code: 500,
- Msg: "保存失败: " + err.Error(),
- })
- return
- }
- c.JSON(http.StatusOK, LicenseResponse{
- Code: 0,
- Msg: "成功解绑设备",
- Data: &license,
- })
- }
- }
|