支持多产品计划展示、LICOFF 离线签发、支付独立配置与调试,以及分析看板;同步更新 README 与集成文档。 Co-authored-by: Cursor <cursoragent@cursor.com>
241 lines
8.2 KiB
Go
241 lines
8.2 KiB
Go
package license
|
||
|
||
import (
|
||
"backend/config"
|
||
"backend/internal/dto"
|
||
licenseservice "backend/internal/service/license"
|
||
"backend/utils"
|
||
"net/http"
|
||
"strconv"
|
||
|
||
"github.com/gin-gonic/gin"
|
||
"gorm.io/gorm"
|
||
)
|
||
|
||
// LicenseController License 控制器 // Logan.Li
|
||
type LicenseController struct {
|
||
svc *licenseservice.LicenseService
|
||
offline *licenseservice.OfflineIssuer
|
||
db *gorm.DB
|
||
}
|
||
|
||
// NewLicenseController 创建控制器实例 // Logan.Li
|
||
func NewLicenseController(db *gorm.DB, cfg *config.Config) *LicenseController {
|
||
return &LicenseController{
|
||
svc: licenseservice.NewLicenseService(db),
|
||
offline: licenseservice.NewOfflineIssuer(db, cfg),
|
||
db: db,
|
||
}
|
||
}
|
||
|
||
// parseListQuery 从请求解析筛选参数 // Logan.Li
|
||
func parseListQuery(c *gin.Context) licenseservice.ListQuery {
|
||
q := licenseservice.ListQuery{
|
||
Keyword: c.Query("keyword"),
|
||
Status: c.Query("status"),
|
||
Channel: c.Query("channel"),
|
||
}
|
||
if v, err := strconv.ParseUint(c.Query("customer_id"), 10, 32); err == nil {
|
||
q.CustomerID = uint(v)
|
||
}
|
||
if v, err := strconv.ParseUint(c.Query("product_id"), 10, 32); err == nil {
|
||
q.ProductID = uint(v)
|
||
}
|
||
return q
|
||
}
|
||
|
||
// List License 列表 // Logan.Li
|
||
func (ctrl *LicenseController) List(c *gin.Context) {
|
||
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
||
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "10"))
|
||
|
||
licenses, total, err := ctrl.svc.List(page, pageSize, parseListQuery(c))
|
||
if err != nil {
|
||
c.JSON(http.StatusInternalServerError, gin.H{"code": 500, "message": err.Error()})
|
||
return
|
||
}
|
||
c.JSON(http.StatusOK, gin.H{"code": 0, "data": gin.H{
|
||
"list": licenses, "total": total, "page": page, "page_size": pageSize,
|
||
"total_pages": (total + int64(pageSize) - 1) / int64(pageSize),
|
||
}})
|
||
}
|
||
|
||
// Get License 详情 // Logan.Li
|
||
func (ctrl *LicenseController) Get(c *gin.Context) {
|
||
id, _ := strconv.ParseUint(c.Param("id"), 10, 32)
|
||
lic, activations, err := ctrl.svc.GetByID(uint(id))
|
||
if err != nil {
|
||
c.JSON(http.StatusOK, gin.H{"code": 404, "message": err.Error()})
|
||
return
|
||
}
|
||
c.JSON(http.StatusOK, gin.H{"code": 0, "data": gin.H{"license": lic, "activations": activations}})
|
||
}
|
||
|
||
// Create 创建 License // Logan.Li
|
||
func (ctrl *LicenseController) Create(c *gin.Context) {
|
||
var req dto.LicenseCreate
|
||
if err := c.ShouldBindJSON(&req); err != nil {
|
||
c.JSON(http.StatusBadRequest, gin.H{"code": 400, "message": "参数错误: " + err.Error()})
|
||
return
|
||
}
|
||
licenses, err := ctrl.svc.Create(req)
|
||
if err != nil {
|
||
c.JSON(http.StatusOK, gin.H{"code": 1, "message": err.Error()})
|
||
return
|
||
}
|
||
utils.LogAction(ctrl.db, c, "create", "license", licenses[0].ID, licenses[0].LicenseKey, "", "",
|
||
"创建 "+strconv.Itoa(len(licenses))+" 个 License")
|
||
c.JSON(http.StatusOK, gin.H{"code": 0, "message": "创建成功", "data": licenses})
|
||
}
|
||
|
||
// IssueOffline 签发离线许可证序列号(最长 365 天,绑定 device_id) // Logan.Li
|
||
func (ctrl *LicenseController) IssueOffline(c *gin.Context) {
|
||
var req dto.OfflineLicenseCreate
|
||
if err := c.ShouldBindJSON(&req); err != nil {
|
||
c.JSON(http.StatusBadRequest, gin.H{"code": 400, "message": "参数错误: " + err.Error()})
|
||
return
|
||
}
|
||
result, err := ctrl.offline.Issue(req)
|
||
if err != nil {
|
||
c.JSON(http.StatusOK, gin.H{"code": 1, "message": err.Error()})
|
||
return
|
||
}
|
||
utils.LogAction(ctrl.db, c, "create_offline", "license", result.LicenseID, result.Serial, "", "",
|
||
"签发离线证 device="+result.DeviceID)
|
||
c.JSON(http.StatusOK, gin.H{"code": 0, "message": "离线许可证已生成", "data": result})
|
||
}
|
||
|
||
// OfflinePublicKey 返回当前离线验签公钥(供运维核对桌面嵌入值) // Logan.Li
|
||
func (ctrl *LicenseController) OfflinePublicKey(c *gin.Context) {
|
||
pub := ""
|
||
if ctrl.offline != nil {
|
||
pub = ctrl.offline.PublicKeyHex()
|
||
}
|
||
if pub == "" {
|
||
c.JSON(http.StatusOK, gin.H{"code": 1, "message": "未配置 OFFLINE_LICENSE_SEED"})
|
||
return
|
||
}
|
||
c.JSON(http.StatusOK, gin.H{"code": 0, "data": gin.H{"public_key_hex": pub}})
|
||
}
|
||
|
||
// Update 更新 License // Logan.Li
|
||
func (ctrl *LicenseController) Update(c *gin.Context) {
|
||
id, _ := strconv.ParseUint(c.Param("id"), 10, 32)
|
||
var updates map[string]interface{}
|
||
if err := c.ShouldBindJSON(&updates); err != nil {
|
||
c.JSON(http.StatusBadRequest, gin.H{"code": 400, "message": "参数错误"})
|
||
return
|
||
}
|
||
lic, err := ctrl.svc.Update(uint(id), updates)
|
||
if err != nil {
|
||
c.JSON(http.StatusOK, gin.H{"code": 1, "message": err.Error()})
|
||
return
|
||
}
|
||
utils.LogAction(ctrl.db, c, "update", "license", lic.ID, lic.LicenseKey, "", "", "更新 License")
|
||
c.JSON(http.StatusOK, gin.H{"code": 0, "message": "更新成功", "data": lic})
|
||
}
|
||
|
||
// Delete 删除 License // Logan.Li
|
||
func (ctrl *LicenseController) Delete(c *gin.Context) {
|
||
id, _ := strconv.ParseUint(c.Param("id"), 10, 32)
|
||
lic, err := ctrl.svc.Delete(uint(id))
|
||
if err != nil {
|
||
c.JSON(http.StatusOK, gin.H{"code": 1, "message": err.Error()})
|
||
return
|
||
}
|
||
utils.LogAction(ctrl.db, c, "delete", "license", lic.ID, lic.LicenseKey, "", "", "删除 License")
|
||
c.JSON(http.StatusOK, gin.H{"code": 0, "message": "删除成功"})
|
||
}
|
||
|
||
// Renew 续期 // Logan.Li
|
||
func (ctrl *LicenseController) Renew(c *gin.Context) {
|
||
id, _ := strconv.ParseUint(c.Param("id"), 10, 32)
|
||
var req struct {
|
||
Duration int `json:"duration" binding:"required"`
|
||
}
|
||
if err := c.ShouldBindJSON(&req); err != nil {
|
||
c.JSON(http.StatusBadRequest, gin.H{"code": 400, "message": "参数错误"})
|
||
return
|
||
}
|
||
lic, err := ctrl.svc.Renew(uint(id), req.Duration)
|
||
if err != nil {
|
||
c.JSON(http.StatusOK, gin.H{"code": 1, "message": err.Error()})
|
||
return
|
||
}
|
||
utils.LogAction(ctrl.db, c, "renew", "license", lic.ID, lic.LicenseKey, "", "", "续期 License")
|
||
c.JSON(http.StatusOK, gin.H{"code": 0, "message": "续期成功", "data": lic})
|
||
}
|
||
|
||
// Revoke 吊销 // Logan.Li
|
||
func (ctrl *LicenseController) Revoke(c *gin.Context) {
|
||
id, _ := strconv.ParseUint(c.Param("id"), 10, 32)
|
||
var req struct {
|
||
Reason string `json:"reason"`
|
||
}
|
||
c.ShouldBindJSON(&req)
|
||
lic, err := ctrl.svc.Revoke(uint(id), req.Reason)
|
||
if err != nil {
|
||
c.JSON(http.StatusOK, gin.H{"code": 1, "message": err.Error()})
|
||
return
|
||
}
|
||
utils.LogAction(ctrl.db, c, "revoke", "license", lic.ID, lic.LicenseKey, "", "", "吊销 License: "+req.Reason)
|
||
c.JSON(http.StatusOK, gin.H{"code": 0, "message": "已吊销", "data": lic})
|
||
}
|
||
|
||
// Suspend 暂停 // Logan.Li
|
||
func (ctrl *LicenseController) Suspend(c *gin.Context) {
|
||
ctrl.setStatus(c, "suspended")
|
||
}
|
||
|
||
// Activate 激活(管理后台) // Logan.Li
|
||
func (ctrl *LicenseController) Activate(c *gin.Context) {
|
||
ctrl.setStatus(c, "active")
|
||
}
|
||
|
||
// setStatus 通用状态切换 // Logan.Li
|
||
func (ctrl *LicenseController) setStatus(c *gin.Context, status string) {
|
||
id, _ := strconv.ParseUint(c.Param("id"), 10, 32)
|
||
lic, err := ctrl.svc.SetStatus(uint(id), status)
|
||
if err != nil {
|
||
c.JSON(http.StatusOK, gin.H{"code": 1, "message": err.Error()})
|
||
return
|
||
}
|
||
utils.LogAction(ctrl.db, c, status, "license", lic.ID, lic.LicenseKey, "", "", "License 状态变更为 "+status)
|
||
c.JSON(http.StatusOK, gin.H{"code": 0, "message": "操作成功", "data": lic})
|
||
}
|
||
|
||
// GetActivations 获取激活记录 // Logan.Li
|
||
func (ctrl *LicenseController) GetActivations(c *gin.Context) {
|
||
id, _ := strconv.ParseUint(c.Param("id"), 10, 32)
|
||
activations, err := ctrl.svc.GetActivations(uint(id))
|
||
if err != nil {
|
||
c.JSON(http.StatusOK, gin.H{"code": 1, "message": err.Error()})
|
||
return
|
||
}
|
||
c.JSON(http.StatusOK, gin.H{"code": 0, "data": activations})
|
||
}
|
||
|
||
// ForceOffline 强制下线 // Logan.Li
|
||
func (ctrl *LicenseController) ForceOffline(c *gin.Context) {
|
||
licenseID, _ := strconv.ParseUint(c.Param("id"), 10, 32)
|
||
activationID, _ := strconv.ParseUint(c.Param("activationId"), 10, 32)
|
||
if err := ctrl.svc.ForceOffline(uint(licenseID), uint(activationID)); err != nil {
|
||
c.JSON(http.StatusOK, gin.H{"code": 1, "message": err.Error()})
|
||
return
|
||
}
|
||
utils.LogAction(ctrl.db, c, "force_offline", "activation", uint(activationID), "", "", "", "强制下线设备")
|
||
c.JSON(http.StatusOK, gin.H{"code": 0, "message": "强制下线成功"})
|
||
}
|
||
|
||
// Export 导出 CSV // Logan.Li
|
||
func (ctrl *LicenseController) Export(c *gin.Context) {
|
||
csv, err := ctrl.svc.ExportCSV(parseListQuery(c))
|
||
if err != nil {
|
||
c.JSON(http.StatusOK, gin.H{"code": 1, "message": err.Error()})
|
||
return
|
||
}
|
||
c.Header("Content-Type", "text/csv; charset=utf-8")
|
||
c.Header("Content-Disposition", "attachment; filename=licenses.csv")
|
||
c.String(http.StatusOK, csv)
|
||
}
|