Files
license-manager/backend/internal/api/handlers/authorization_code_handler.go
T

647 lines
22 KiB
Go

package handlers
import (
"archive/zip"
"bytes"
"errors"
"fmt"
"net/http"
"os"
"path/filepath"
"strings"
"time"
"license-manager/internal/api/middleware"
"license-manager/internal/config"
"license-manager/internal/models"
"license-manager/internal/service"
"license-manager/pkg/i18n"
"github.com/gin-gonic/gin"
)
type AuthorizationCodeHandler struct {
authCodeService service.AuthorizationCodeService
}
// NewAuthorizationCodeHandler 创建授权码处理器
func NewAuthorizationCodeHandler(authCodeService service.AuthorizationCodeService) *AuthorizationCodeHandler {
return &AuthorizationCodeHandler{
authCodeService: authCodeService,
}
}
// CreateAuthorizationCode 创建授权码
// @Summary 创建授权码
// @Description 创建新的授权码,自动生成授权码字符串。有效期从当天00:00:00开始,到指定天数后的23:59:59结束
// @Tags 授权码管理
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param authorization_code body models.AuthorizationCodeCreateRequest true "授权码信息"
// @Success 200 {object} models.APIResponse{data=models.AuthorizationCodeCreateResponse} "创建成功"
// @Failure 400 {object} models.ErrorResponse "请求参数无效"
// @Failure 401 {object} models.ErrorResponse "未认证"
// @Failure 404 {object} models.ErrorResponse "客户不存在"
// @Failure 409 {object} models.ErrorResponse "授权码已存在"
// @Failure 500 {object} models.ErrorResponse "服务器内部错误"
// @Router /api/v1/authorization-codes [post]
func (h *AuthorizationCodeHandler) CreateAuthorizationCode(c *gin.Context) {
var req models.AuthorizationCodeCreateRequest
if err := c.ShouldBindJSON(&req); err != nil {
lang := middleware.GetLanguage(c)
status, errCode, message := i18n.NewI18nErrorResponse("900001", lang)
c.JSON(status, models.ErrorResponse{
Code: errCode,
Message: message + ": " + err.Error(),
Timestamp: time.Now().Format(time.RFC3339),
})
return
}
// 直接传递 gin.Context
data, err := h.authCodeService.CreateAuthorizationCode(c, &req)
if err != nil {
// 错误已经在Service层完全包装好了,直接使用
var i18nErr *i18n.I18nError
if errors.As(err, &i18nErr) {
c.JSON(i18nErr.HttpCode, models.ErrorResponse{
Code: i18nErr.Code,
Message: i18nErr.Message,
Timestamp: time.Now().Format(time.RFC3339),
})
} else {
// 兜底:理论上不应该到这里,因为Service层应该返回I18nError
lang := middleware.GetLanguage(c)
status, errCode, message := i18n.NewI18nErrorResponse("900004", lang)
c.JSON(status, models.ErrorResponse{
Code: errCode,
Message: message,
Timestamp: time.Now().Format(time.RFC3339),
})
}
return
}
lang := middleware.GetLanguage(c)
successMessage := i18n.GetErrorMessage("000000", lang)
c.JSON(http.StatusOK, models.APIResponse{
Code: "000000",
Message: successMessage,
Data: data,
})
}
// GetAuthorizationCodeList 查询授权码列表
// @Summary 查询授权码列表
// @Description 分页查询授权码列表,支持筛选和排序
// @Tags 授权码管理
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param page query int false "页码,默认1" minimum(1)
// @Param page_size query int false "每页条数,默认20,最大100" minimum(1) maximum(100)
// @Param customer_id query string false "客户ID筛选"
// @Param status query string false "状态筛选" Enums(normal, locked, expired)
// @Param start_date query string false "创建开始时间"
// @Param end_date query string false "创建结束时间"
// @Param sort query string false "排序字段,默认created_at" Enums(created_at, updated_at, code)
// @Param order query string false "排序方向,默认desc" Enums(asc, desc)
// @Success 200 {object} models.APIResponse{data=models.AuthorizationCodeListResponse} "查询成功"
// @Failure 400 {object} models.ErrorResponse "请求参数无效"
// @Failure 401 {object} models.ErrorResponse "未认证"
// @Failure 500 {object} models.ErrorResponse "服务器内部错误"
// @Router /api/v1/authorization-codes [get]
func (h *AuthorizationCodeHandler) GetAuthorizationCodeList(c *gin.Context) {
var req models.AuthorizationCodeListRequest
if err := c.ShouldBindQuery(&req); err != nil {
lang := middleware.GetLanguage(c)
status, errCode, message := i18n.NewI18nErrorResponse("900001", lang)
c.JSON(status, models.ErrorResponse{
Code: errCode,
Message: message + ": " + err.Error(),
Timestamp: time.Now().Format(time.RFC3339),
})
return
}
// 直接传递 gin.Context
data, err := h.authCodeService.GetAuthorizationCodeList(c, &req)
if err != nil {
// 错误已经在Service层完全包装好了,直接使用
var i18nErr *i18n.I18nError
if errors.As(err, &i18nErr) {
c.JSON(i18nErr.HttpCode, models.ErrorResponse{
Code: i18nErr.Code,
Message: i18nErr.Message,
Timestamp: time.Now().Format(time.RFC3339),
})
} else {
// 兜底:理论上不应该到这里,因为Service层应该返回I18nError
lang := middleware.GetLanguage(c)
status, errCode, message := i18n.NewI18nErrorResponse("900004", lang)
c.JSON(status, models.ErrorResponse{
Code: errCode,
Message: message,
Timestamp: time.Now().Format(time.RFC3339),
})
}
return
}
lang := middleware.GetLanguage(c)
successMessage := i18n.GetErrorMessage("000000", lang)
c.JSON(http.StatusOK, models.APIResponse{
Code: "000000",
Message: successMessage,
Data: data,
})
}
// GetAuthorizationCode 获取授权码详情
// @Summary 获取授权码详情
// @Description 根据授权码ID获取授权码详细信息
// @Tags 授权码管理
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param id path string true "授权码ID"
// @Success 200 {object} models.APIResponse{data=models.AuthorizationCode} "查询成功"
// @Failure 400 {object} models.ErrorResponse "请求参数无效"
// @Failure 401 {object} models.ErrorResponse "未认证"
// @Failure 404 {object} models.ErrorResponse "授权码不存在"
// @Failure 500 {object} models.ErrorResponse "服务器内部错误"
// @Router /api/v1/authorization-codes/{id} [get]
func (h *AuthorizationCodeHandler) GetAuthorizationCode(c *gin.Context) {
id := c.Param("id")
if id == "" {
lang := middleware.GetLanguage(c)
status, errCode, message := i18n.NewI18nErrorResponse("900001", lang)
c.JSON(status, models.ErrorResponse{
Code: errCode,
Message: message,
Timestamp: time.Now().Format(time.RFC3339),
})
return
}
// 直接传递 gin.Context
data, err := h.authCodeService.GetAuthorizationCode(c, id)
if err != nil {
// 错误已经在Service层完全包装好了,直接使用
var i18nErr *i18n.I18nError
if errors.As(err, &i18nErr) {
c.JSON(i18nErr.HttpCode, models.ErrorResponse{
Code: i18nErr.Code,
Message: i18nErr.Message,
Timestamp: time.Now().Format(time.RFC3339),
})
} else {
// 兜底:理论上不应该到这里,因为Service层应该返回I18nError
lang := middleware.GetLanguage(c)
status, errCode, message := i18n.NewI18nErrorResponse("900004", lang)
c.JSON(status, models.ErrorResponse{
Code: errCode,
Message: message,
Timestamp: time.Now().Format(time.RFC3339),
})
}
return
}
lang := middleware.GetLanguage(c)
successMessage := i18n.GetErrorMessage("000000", lang)
c.JSON(http.StatusOK, models.APIResponse{
Code: "000000",
Message: successMessage,
Data: data,
})
}
// DownloadAuthorizationFile 下载授权文件
// @Summary 下载授权文件
// @Description 下载授权码文件,包含授权码和RSA公钥,供客户端使用
// @Tags 授权码管理
// @Accept json
// @Produce application/zip
// @Security BearerAuth
// @Param id path string true "授权码ID"
// @Success 200 {file} binary "包含授权码文件和RSA公钥的压缩包"
// @Failure 400 {object} models.ErrorResponse "请求参数无效"
// @Failure 401 {object} models.ErrorResponse "未认证"
// @Failure 404 {object} models.ErrorResponse "授权码不存在"
// @Failure 409 {object} models.ErrorResponse "授权码不可用"
// @Failure 500 {object} models.ErrorResponse "服务器内部错误"
// @Router /api/v1/authorization-codes/{id}/download [get]
func (h *AuthorizationCodeHandler) DownloadAuthorizationFile(c *gin.Context) {
lang := middleware.GetLanguage(c)
id := c.Param("id")
if id == "" {
status, errCode, message := i18n.NewI18nErrorResponse("900001", lang)
c.JSON(status, models.ErrorResponse{
Code: errCode,
Message: message,
Timestamp: time.Now().Format(time.RFC3339),
})
return
}
ctx := middleware.WithLanguage(c.Request.Context(), c)
fileData, fileName, authCode, err := h.authCodeService.GenerateAuthorizationFile(ctx, id)
if err != nil {
var i18nErr *i18n.I18nError
if errors.As(err, &i18nErr) {
c.JSON(i18nErr.HttpCode, models.ErrorResponse{
Code: i18nErr.Code,
Message: i18nErr.Message,
Timestamp: time.Now().Format(time.RFC3339),
})
} else {
status, errCode, message := i18n.NewI18nErrorResponse("900004", lang)
c.JSON(status, models.ErrorResponse{
Code: errCode,
Message: message,
Timestamp: time.Now().Format(time.RFC3339),
})
}
return
}
cfg := config.GetConfig()
if cfg == nil || cfg.License.RSA.PublicKeyPath == "" {
status, errCode, message := i18n.NewI18nErrorResponse("900004", lang)
c.JSON(status, models.ErrorResponse{
Code: errCode,
Message: message,
Timestamp: time.Now().Format(time.RFC3339),
})
return
}
publicKeyPath := filepath.Clean(cfg.License.RSA.PublicKeyPath)
publicKeyData, err := os.ReadFile(publicKeyPath)
if err != nil {
status, errCode, message := i18n.NewI18nErrorResponse("900004", lang)
c.JSON(status, models.ErrorResponse{
Code: errCode,
Message: message,
Timestamp: time.Now().Format(time.RFC3339),
})
return
}
var buffer bytes.Buffer
zipWriter := zip.NewWriter(&buffer)
const zipAuthFileName = "authorization_code.txt"
authFileWriter, err := zipWriter.Create(zipAuthFileName)
if err != nil {
status, errCode, message := i18n.NewI18nErrorResponse("900004", lang)
c.JSON(status, models.ErrorResponse{
Code: errCode,
Message: message,
Timestamp: time.Now().Format(time.RFC3339),
})
return
}
if _, err := authFileWriter.Write(fileData); err != nil {
status, errCode, message := i18n.NewI18nErrorResponse("900004", lang)
c.JSON(status, models.ErrorResponse{
Code: errCode,
Message: message,
Timestamp: time.Now().Format(time.RFC3339),
})
return
}
publicKeyWriter, err := zipWriter.Create("rsa_public_key.pem")
if err != nil {
status, errCode, message := i18n.NewI18nErrorResponse("900004", lang)
c.JSON(status, models.ErrorResponse{
Code: errCode,
Message: message,
Timestamp: time.Now().Format(time.RFC3339),
})
return
}
if _, err := publicKeyWriter.Write(publicKeyData); err != nil {
status, errCode, message := i18n.NewI18nErrorResponse("900004", lang)
c.JSON(status, models.ErrorResponse{
Code: errCode,
Message: message,
Timestamp: time.Now().Format(time.RFC3339),
})
return
}
if err := zipWriter.Close(); err != nil {
status, errCode, message := i18n.NewI18nErrorResponse("900004", lang)
c.JSON(status, models.ErrorResponse{
Code: errCode,
Message: message,
Timestamp: time.Now().Format(time.RFC3339),
})
return
}
packageFileName := fmt.Sprintf("authorization_package_%s.zip", strings.TrimSpace(authCode))
if strings.TrimSpace(authCode) == "" {
baseName := strings.TrimSuffix(fileName, filepath.Ext(fileName))
packageFileName = fmt.Sprintf("%s_package.zip", baseName)
}
c.Header("Content-Description", "File Transfer")
c.Header("Content-Type", "application/zip")
c.Header("Content-Disposition", fmt.Sprintf("attachment; filename=%s", packageFileName))
c.Header("Content-Transfer-Encoding", "binary")
c.Header("Cache-Control", "must-revalidate")
c.Header("Pragma", "public")
c.Data(http.StatusOK, "application/zip", buffer.Bytes())
}
// UpdateAuthorizationCode 更新授权码
// @Summary 更新授权码
// @Description 更新授权码信息,所有字段都是可选的,需要指定变更类型和原因
// @Tags 授权码管理
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param id path string true "授权码ID"
// @Param authorization_code body models.AuthorizationCodeUpdateRequest true "授权码信息"
// @Success 200 {object} models.APIResponse{data=models.AuthorizationCode} "更新成功"
// @Failure 400 {object} models.ErrorResponse "请求参数无效"
// @Failure 401 {object} models.ErrorResponse "未认证"
// @Failure 404 {object} models.ErrorResponse "授权码不存在"
// @Failure 500 {object} models.ErrorResponse "服务器内部错误"
// @Router /api/v1/authorization-codes/{id} [put]
func (h *AuthorizationCodeHandler) UpdateAuthorizationCode(c *gin.Context) {
id := c.Param("id")
if id == "" {
lang := middleware.GetLanguage(c)
status, errCode, message := i18n.NewI18nErrorResponse("900001", lang)
c.JSON(status, models.ErrorResponse{
Code: errCode,
Message: message,
Timestamp: time.Now().Format(time.RFC3339),
})
return
}
var req models.AuthorizationCodeUpdateRequest
if err := c.ShouldBindJSON(&req); err != nil {
lang := middleware.GetLanguage(c)
status, errCode, message := i18n.NewI18nErrorResponse("900001", lang)
c.JSON(status, models.ErrorResponse{
Code: errCode,
Message: message + ": " + err.Error(),
Timestamp: time.Now().Format(time.RFC3339),
})
return
}
// 直接传递 gin.Context
authCode, err := h.authCodeService.UpdateAuthorizationCode(c, id, &req)
if err != nil {
// 错误已经在Service层完全包装好了,直接使用
var i18nErr *i18n.I18nError
if errors.As(err, &i18nErr) {
c.JSON(i18nErr.HttpCode, models.ErrorResponse{
Code: i18nErr.Code,
Message: i18nErr.Message,
Timestamp: time.Now().Format(time.RFC3339),
})
} else {
// 兜底:理论上不应该到这里,因为Service层应该返回I18nError
lang := middleware.GetLanguage(c)
status, errCode, message := i18n.NewI18nErrorResponse("900004", lang)
c.JSON(status, models.ErrorResponse{
Code: errCode,
Message: message,
Timestamp: time.Now().Format(time.RFC3339),
})
}
return
}
lang := middleware.GetLanguage(c)
successMessage := i18n.GetErrorMessage("000000", lang)
c.JSON(http.StatusOK, models.APIResponse{
Code: "000000",
Message: successMessage,
Data: authCode,
})
}
// LockUnlockAuthorizationCode 锁定/解锁授权码
// @Summary 锁定/解锁授权码
// @Description 锁定或解锁指定的授权码,可以设置锁定原因
// @Tags 授权码管理
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param id path string true "授权码ID"
// @Param lock_request body models.AuthorizationCodeLockRequest true "锁定/解锁信息"
// @Success 200 {object} models.APIResponse{data=models.AuthorizationCode} "操作成功"
// @Failure 400 {object} models.ErrorResponse "请求参数无效"
// @Failure 401 {object} models.ErrorResponse "未认证"
// @Failure 404 {object} models.ErrorResponse "授权码不存在"
// @Failure 500 {object} models.ErrorResponse "服务器内部错误"
// @Router /api/v1/authorization-codes/{id}/lock [put]
func (h *AuthorizationCodeHandler) LockUnlockAuthorizationCode(c *gin.Context) {
id := c.Param("id")
if id == "" {
lang := middleware.GetLanguage(c)
status, errCode, message := i18n.NewI18nErrorResponse("900001", lang)
c.JSON(status, models.ErrorResponse{
Code: errCode,
Message: message,
Timestamp: time.Now().Format(time.RFC3339),
})
return
}
var req models.AuthorizationCodeLockRequest
if err := c.ShouldBindJSON(&req); err != nil {
lang := middleware.GetLanguage(c)
status, errCode, message := i18n.NewI18nErrorResponse("900001", lang)
c.JSON(status, models.ErrorResponse{
Code: errCode,
Message: message + ": " + err.Error(),
Timestamp: time.Now().Format(time.RFC3339),
})
return
}
// 直接传递 gin.Context
data, err := h.authCodeService.LockUnlockAuthorizationCode(c, id, &req)
if err != nil {
// 错误已经在Service层完全包装好了,直接使用
var i18nErr *i18n.I18nError
if errors.As(err, &i18nErr) {
c.JSON(i18nErr.HttpCode, models.ErrorResponse{
Code: i18nErr.Code,
Message: i18nErr.Message,
Timestamp: time.Now().Format(time.RFC3339),
})
} else {
// 兜底:理论上不应该到这里,因为Service层应该返回I18nError
lang := middleware.GetLanguage(c)
status, errCode, message := i18n.NewI18nErrorResponse("900004", lang)
c.JSON(status, models.ErrorResponse{
Code: errCode,
Message: message,
Timestamp: time.Now().Format(time.RFC3339),
})
}
return
}
lang := middleware.GetLanguage(c)
successMessage := i18n.GetErrorMessage("000000", lang)
c.JSON(http.StatusOK, models.APIResponse{
Code: "000000",
Message: successMessage,
Data: data,
})
}
// DeleteAuthorizationCode 删除授权码
// @Summary 删除授权码
// @Description 删除指定的授权码(软删除)
// @Tags 授权码管理
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param id path string true "授权码ID"
// @Success 200 {object} models.APIResponse "删除成功"
// @Failure 400 {object} models.ErrorResponse "请求参数无效"
// @Failure 401 {object} models.ErrorResponse "未认证"
// @Failure 404 {object} models.ErrorResponse "授权码不存在"
// @Failure 500 {object} models.ErrorResponse "服务器内部错误"
// @Router /api/v1/authorization-codes/{id} [delete]
func (h *AuthorizationCodeHandler) DeleteAuthorizationCode(c *gin.Context) {
id := c.Param("id")
if id == "" {
lang := middleware.GetLanguage(c)
status, errCode, message := i18n.NewI18nErrorResponse("900001", lang)
c.JSON(status, models.ErrorResponse{
Code: errCode,
Message: message,
Timestamp: time.Now().Format(time.RFC3339),
})
return
}
// 直接传递 gin.Context
if err := h.authCodeService.DeleteAuthorizationCode(c, id); err != nil {
// 错误已经在Service层完全包装好了,直接使用
var i18nErr *i18n.I18nError
if errors.As(err, &i18nErr) {
c.JSON(i18nErr.HttpCode, models.ErrorResponse{
Code: i18nErr.Code,
Message: i18nErr.Message,
Timestamp: time.Now().Format(time.RFC3339),
})
} else {
// 兜底:理论上不应该到这里,因为Service层应该返回I18nError
lang := middleware.GetLanguage(c)
status, errCode, message := i18n.NewI18nErrorResponse("900004", lang)
c.JSON(status, models.ErrorResponse{
Code: errCode,
Message: message,
Timestamp: time.Now().Format(time.RFC3339),
})
}
return
}
lang := middleware.GetLanguage(c)
successMessage := i18n.GetErrorMessage("000000", lang)
c.JSON(http.StatusOK, models.APIResponse{
Code: "000000",
Message: successMessage,
Data: nil,
})
}
// GetAuthorizationChangeList 查询授权变更历史列表
// @Summary 查询授权变更历史列表
// @Description 分页查询指定授权码的变更历史,支持筛选
// @Tags 授权码管理
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param id path string true "授权码ID"
// @Param page query int false "页码,默认1" minimum(1)
// @Param page_size query int false "每页条数,默认20,最大100" minimum(1) maximum(100)
// @Param change_type query string false "变更类型筛选"
// @Param operator_id query string false "操作人筛选"
// @Param start_date query string false "开始时间 (YYYY-MM-DD格式)"
// @Param end_date query string false "结束时间 (YYYY-MM-DD格式)"
// @Param sort query string false "排序字段,默认created_at" Enums(created_at, change_type)
// @Param order query string false "排序方向,默认desc" Enums(asc, desc)
// @Success 200 {object} models.APIResponse{data=models.AuthorizationChangeListResponse} "查询成功"
// @Failure 400 {object} models.ErrorResponse "请求参数无效"
// @Failure 401 {object} models.ErrorResponse "未认证"
// @Failure 404 {object} models.ErrorResponse "授权码不存在"
// @Failure 500 {object} models.ErrorResponse "服务器内部错误"
// @Router /api/v1/authorization-codes/{id}/changes [get]
func (h *AuthorizationCodeHandler) GetAuthorizationChangeList(c *gin.Context) {
id := c.Param("id")
if id == "" {
lang := middleware.GetLanguage(c)
status, errCode, message := i18n.NewI18nErrorResponse("900001", lang)
c.JSON(status, models.ErrorResponse{
Code: errCode,
Message: message,
Timestamp: time.Now().Format(time.RFC3339),
})
return
}
var req models.AuthorizationChangeListRequest
if err := c.ShouldBindQuery(&req); err != nil {
lang := middleware.GetLanguage(c)
status, errCode, message := i18n.NewI18nErrorResponse("900001", lang)
c.JSON(status, models.ErrorResponse{
Code: errCode,
Message: message + ": " + err.Error(),
Timestamp: time.Now().Format(time.RFC3339),
})
return
}
// 直接传递 gin.Context
data, err := h.authCodeService.GetAuthorizationChangeList(c, id, &req)
if err != nil {
// 错误已经在Service层完全包装好了,直接使用
var i18nErr *i18n.I18nError
if errors.As(err, &i18nErr) {
c.JSON(i18nErr.HttpCode, models.ErrorResponse{
Code: i18nErr.Code,
Message: i18nErr.Message,
Timestamp: time.Now().Format(time.RFC3339),
})
} else {
// 兜底:理论上不应该到这里,因为Service层应该返回I18nError
lang := middleware.GetLanguage(c)
status, errCode, message := i18n.NewI18nErrorResponse("900004", lang)
c.JSON(status, models.ErrorResponse{
Code: errCode,
Message: message,
Timestamp: time.Now().Format(time.RFC3339),
})
}
return
}
lang := middleware.GetLanguage(c)
successMessage := i18n.GetErrorMessage("000000", lang)
c.JSON(http.StatusOK, models.APIResponse{
Code: "000000",
Message: successMessage,
Data: data,
})
}