feat: 授权码列表、创建授权码接口完成

This commit is contained in:
wood911010 committed 2025-09-17 00:01:51 +08:00
1 parent 5c086a32f5
commit 188406c517
18 files changed
+1656 -19

No files matched your search

+26 -6
View File
@@ -22,12 +22,18 @@ errors:
"200004": "Invalid customer information format"
"200005": "Failed to query customer list"
# License module (30xxxx) - Reserved
license:
"300001": "Invalid license key"
"300002": "License expired"
"300003": "License key is required"
"300004": "License key already used"
# Authorization module (30xxxx)
authorization:
"300001": "Authorization code not found"
"300002": "Authorization code already exists"
"300003": "Authorization code is locked"
"300004": "Maximum activation limit reached"
"300005": "Invalid hardware fingerprint format"
"300006": "License not found"
"300007": "License has been revoked"
"300008": "Hardware fingerprint mismatch"
"300009": "Failed to generate license file"
"300010": "Invalid configuration parameters"
# System general (90xxxx)
system:
@@ -59,6 +65,20 @@ enums:
"medium": "Medium"
"large": "Large"
"enterprise": "Enterprise"
deployment_type:
"standalone": "Standalone"
"cloud": "Cloud"
"hybrid": "Hybrid"
encryption_type:
"standard": "Standard"
"advanced": "Advanced"
authorization_status:
"normal": "Normal"
"locked": "Locked"
"expired": "Expired"
# Default error message
default_error: "Unknown error"
+50 -6
View File
@@ -22,12 +22,18 @@ errors:
"200004": "無効な顧客情報形式"
"200005": "顧客リストの照会に失敗しました"
# ライセンスモジュール (30xxxx) - 予約済み
license:
"300001": "無効なライセンスキー"
"300002": "ライセンスが期限切れです"
"300003": "ライセンスキーは必須です"
"300004": "ライセンスキーは既に使用されています"
# 認可モジュール (30xxxx)
authorization:
"300001": "認可コードが見つかりません"
"300002": "認可コードが既に存在します"
"300003": "認可コードがロックされています"
"300004": "最大アクティベーション数に達しました"
"300005": "無効なハードウェア指紋形式"
"300006": "ライセンスが見つかりません"
"300007": "ライセンスが取り消されました"
"300008": "ハードウェア指紋が一致しません"
"300009": "ライセンスファイルの生成に失敗しました"
"300010": "無効な設定パラメータ"
# システム共通 (90xxxx)
system:
@@ -36,5 +42,43 @@ errors:
"900003": "リソースの競合"
"900004": "内部サーバーエラー"
# 列挙値多言語
enums:
customer_type:
"individual": "個人顧客"
"enterprise": "企業顧客"
"government": "政府顧客"
"education": "教育顧客"
customer_level:
"normal": "一般顧客"
"vip": "VIP顧客"
"enterprise": "企業顧客"
"strategic": "戦略顧客"
customer_status:
"active": "アクティブ"
"disabled": "無効"
company_size:
"small": "小企業"
"medium": "中企業"
"large": "大企業"
"enterprise": "超大企業"
deployment_type:
"standalone": "スタンドアロン"
"cloud": "クラウド"
"hybrid": "ハイブリッド"
encryption_type:
"standard": "標準暗号化"
"advanced": "高度暗号化"
authorization_status:
"normal": "正常"
"locked": "ロック済み"
"expired": "期限切れ"
# デフォルトエラーメッセージ
default_error: "不明なエラー"
+26 -6
View File
@@ -22,12 +22,18 @@ errors:
"200004": "客户信息格式无效"
"200005": "客户列表查询失败"
# 授权模块 (30xxxx) - 预留
license:
"300001": "授权码无效"
"300002": "授权已过期"
"300003": "授权码不能为空"
"300004": "授权已被使用"
# 授权模块 (30xxxx)
authorization:
"300001": "授权码不存在"
"300002": "授权码已存在"
"300003": "授权码已被锁定"
"300004": "激活数量已达上限"
"300005": "硬件指纹格式错误"
"300006": "许可证不存在"
"300007": "许可证已被撤销"
"300008": "硬件指纹不匹配"
"300009": "许可证文件生成失败"
"300010": "配置参数错误"
# 系统通用 (90xxxx)
system:
@@ -59,6 +65,20 @@ enums:
"medium": "中型企业"
"large": "大型企业"
"enterprise": "超大型企业"
deployment_type:
"standalone": "单机版"
"cloud": "云端版"
"hybrid": "混合版"
encryption_type:
"standard": "标准加密"
"advanced": "高级加密"
authorization_status:
"normal": "正常"
"locked": "已锁定"
"expired": "已过期"
# 默认错误信息
default_error: "未知错误"
+425
View File
@@ -0,0 +1,425 @@
# 授权模块API设计
## 1. 授权码管理 API
### 1.1 授权码列表
```http
GET /api/v1/authorization-codes
```
**查询参数**
- `customer_id` - 客户ID
- `status` - 状态 (normal/locked/expired)
- `page` - 页码
- `limit` - 每页数量
- `start_date` - 创建开始时间
- `end_date` - 创建结束时间
**响应**
```json
{
"code": 0,
"message": "success",
"data": {
"list": [
{
"id": 1,
"code": "LIC-COMP001-A7B9X2-C8F4",
"customer_id": 1,
"customer_name": "张三公司",
"customer_name_display": "张三公司",
"status": "normal",
"status_display": "正常",
"start_date": "2024-01-01",
"end_date": "2024-12-31",
"max_activations": 10,
"current_activations": 7,
"deployment_type": "standalone",
"deployment_type_display": "单机版",
"is_locked": false,
"description": "企业版授权",
"created_at": "2024-01-01T10:00:00Z"
}
],
"total": 100,
"page": 1,
"limit": 20
}
}
```
### 1.2 创建授权码
```http
POST /api/v1/authorization-codes
```
**说明:** 有效期从当天00:00:00开始,到指定天数后的23:59:59结束。例如:validity_days=1表示今天一整天有效。
**请求体**
```json
{
"customer_id": "customer-uuid-123",
"software_id": "erp-system",
"description": "企业版授权",
"validity_days": 365,
"deployment_type": "standalone",
"encryption_type": "standard",
"software_version": "1.0.0",
"max_activations": 10,
"feature_config": {
"modules": ["user_mgmt", "inventory", "finance"]
},
"usage_limits": {
"max_users": 100,
"api_calls_per_day": 10000
},
"custom_parameters": {}
}
```
**参数说明:**
- `customer_id`: 客户ID(必填)
- `software_id`: 软件产品ID(可选)
- `description`: 授权描述(可选)
- `validity_days`: 有效期天数,范围1-36500天(必填)
- `deployment_type`: 部署类型,枚举值:standalone/cloud/hybrid(必填)
- `encryption_type`: 加密类型,枚举值:standard/advanced(可选,默认standard)
- `software_version`: 软件版本(可选)
- `max_activations`: 最大激活数量(必填,最小值1)
- `feature_config`: 功能配置JSON(可选)
- `usage_limits`: 使用限制JSON(可选)
- `custom_parameters`: 自定义参数JSON(可选)
**响应**
```json
{
"code": 0,
"message": "success",
"data": {
"id": 1,
"code": "LIC-COMP001-A7B9X2-C8F4"
}
}
```
### 1.3 授权码详情
```http
GET /api/v1/authorization-codes/{id}
```
**响应**
```json
{
"code": 0,
"message": "success",
"data": {
"id": 1,
"code": "LIC-COMP001-A7B9X2-C8F4",
"customer_id": 1,
"customer_name": "张三公司",
"status": "normal",
"status_display": "正常",
"start_date": "2024-01-01",
"end_date": "2024-12-31",
"max_activations": 10,
"current_activations": 7,
"deployment_type": "standalone",
"deployment_type_display": "单机版",
"encryption_type": "standard",
"encryption_type_display": "标准加密",
"feature_config": {
"modules": ["user_mgmt", "inventory", "finance"]
},
"usage_limits": {
"max_users": 100,
"api_calls_per_day": 10000
},
"custom_parameters": {},
"is_locked": false,
"lock_reason": null,
"description": "企业版授权",
"created_at": "2024-01-01T10:00:00Z",
"updated_at": "2024-01-01T10:00:00Z"
}
}
```
### 1.4 更新授权码
```http
PUT /api/v1/authorization-codes/{id}
```
**请求体**
```json
{
"start_date": "2024-01-01",
"end_date": "2025-12-31",
"max_activations": 20,
"feature_config": {
"modules": ["user_mgmt", "inventory", "finance", "crm"]
},
"usage_limits": {
"max_users": 200,
"api_calls_per_day": 20000
},
"change_type": "upgrade",
"reason": "客户升级套餐"
}
```
### 1.5 锁定/解锁授权码
```http
PUT /api/v1/authorization-codes/{id}/lock
```
**请求体**
```json
{
"is_locked": true,
"lock_reason": "违规使用"
}
```
### 1.6 删除授权码
```http
DELETE /api/v1/authorization-codes/{id}
```
## 2. 许可证管理 API
### 2.1 许可证列表
```http
GET /api/v1/licenses
```
**查询参数**
- `authorization_code_id` - 授权码ID
- `customer_id` - 客户ID
- `status` - 状态 (active/inactive/revoked)
- `is_online` - 在线状态
**响应**
```json
{
"code": 0,
"message": "success",
"data": {
"list": [
{
"id": 1,
"license_key": "LIC-DEVICE-ABC123",
"authorization_code_id": 1,
"authorization_code": "LIC-COMP001-A7B9X2-C8F4",
"customer_name": "张三公司",
"hardware_fingerprint": "CPU:ABC123,MB:DEF456",
"status": "active",
"status_display": "激活",
"is_online": true,
"is_online_display": "在线",
"activation_ip": "192.168.1.100",
"last_online_ip": "192.168.1.100",
"activated_at": "2024-01-01T10:00:00Z",
"last_heartbeat": "2024-01-01T14:30:00Z"
}
],
"total": 50,
"page": 1,
"limit": 20
}
}
```
### 2.2 许可证详情
```http
GET /api/v1/licenses/{id}
```
### 2.3 手动添加许可证
```http
POST /api/v1/licenses
```
**请求体**
```json
{
"authorization_code_id": 1,
"hardware_fingerprint": "CPU:ABC123,MB:DEF456,MAC:00:11:22:33:44:55",
"device_info": {
"cpu": "Intel i7-8700",
"memory": "16GB",
"disk": "512GB SSD",
"os": "Windows 10 Pro"
},
"activation_ip": "192.168.1.100"
}
```
### 2.4 撤销许可证
```http
PUT /api/v1/licenses/{id}/revoke
```
**请求体**
```json
{
"reason": "设备更换"
}
```
### 2.5 下载许可证文件
```http
GET /api/v1/licenses/{id}/download
```
**响应**: 返回加密的许可证文件
## 3. 客户端激活 API
### 3.1 软件激活
```http
POST /api/v1/activate
```
**请求体**
```json
{
"authorization_code": "LIC-COMP001-A7B9X2-C8F4",
"hardware_fingerprint": "CPU:ABC123,MB:DEF456,MAC:00:11:22:33:44:55",
"device_info": {
"cpu": "Intel i7-8700",
"memory": "16GB",
"os": "Windows 10 Pro"
},
"software_version": "1.0.0"
}
```
**响应**
```json
{
"code": 0,
"message": "success",
"data": {
"license_key": "LIC-DEVICE-ABC123",
"license_file": "base64编码的加密许可证文件",
"heartbeat_interval": 300
}
}
```
### 3.2 心跳检测
```http
POST /api/v1/heartbeat
```
**请求体**
```json
{
"license_key": "LIC-DEVICE-ABC123",
"hardware_fingerprint": "CPU:ABC123,MB:DEF456",
"config_updated_at": "2024-01-01T10:00:00Z",
"usage_data": {
"active_users": 50,
"api_calls_today": 5000
},
"software_version": "1.0.0"
}
```
**响应**
```json
{
"code": 0,
"message": "success",
"data": {
"status": "active",
"config_updated": true,
"license_file": "base64编码的新许可证文件",
"heartbeat_interval": 300
}
}
```
## 4. 统计报表 API
### 4.1 授权概览统计
```http
GET /api/v1/stats/overview
```
**响应**
```json
{
"code": 0,
"message": "success",
"data": {
"total_auth_codes": 1234,
"active_licenses": 856,
"expiring_soon": 23,
"abnormal_alerts": 5,
"growth_rate": {
"auth_codes": 5.2,
"licenses": 3.1
}
}
}
```
### 4.2 授权变更历史
```http
GET /api/v1/authorization-codes/{id}/changes
```
**响应**
```json
{
"code": 0,
"message": "success",
"data": {
"list": [
{
"id": 1,
"change_type": "upgrade",
"change_type_display": "升级",
"reason": "客户升级套餐",
"operator_name": "张三",
"created_at": "2024-01-01T10:00:00Z"
}
]
}
}
```
## 5. 错误码定义
- `300001` - 授权码不存在
- `300002` - 授权码已存在
- `300003` - 授权码已被锁定
- `300004` - 激活数量已达上限
- `300005` - 硬件指纹格式错误
- `300006` - 许可证不存在
- `300007` - 许可证已被撤销
- `300008` - 硬件指纹不匹配
- `300009` - 许可证文件生成失败
- `300010` - 配置参数错误
## 6. 状态说明
### 6.1 授权码状态
- `normal` - 正常:当前时间在有效期内且未被锁定
- `locked` - 已锁定:is_locked=true
- `expired` - 已过期:当前时间超过end_date
### 6.2 部署类型
- `standalone` - 单机版:独立部署的软件
- `cloud` - 云端版:基于云的SaaS服务
- `hybrid` - 混合版:结合本地和云端的部署
### 6.3 加密类型
- `standard` - 标准加密:基础安全级别
- `advanced` - 高级加密:增强安全级别
### 6.4 许可证状态
- `active` - 激活:正常使用中
- `inactive` - 未激活:已创建但尚未激活
- `revoked` - 已撤销:被管理员撤销或因违规被停用
+143
View File
@@ -733,6 +733,87 @@ const docTemplate = `{
}
}
},
"/api/v1/authorization-codes": {
"post": {
"security": [
{
"BearerAuth": []
}
],
"description": "创建新的授权码,自动生成授权码字符串。有效期从当天00:00:00开始,到指定天数后的23:59:59结束",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"授权码管理"
],
"summary": "创建授权码",
"parameters": [
{
"description": "授权码信息",
"name": "authorization_code",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/models.AuthorizationCodeCreateRequest"
}
}
],
"responses": {
"200": {
"description": "创建成功",
"schema": {
"allOf": [
{
"$ref": "#/definitions/models.APIResponse"
},
{
"type": "object",
"properties": {
"data": {
"$ref": "#/definitions/models.AuthorizationCodeCreateResponse"
}
}
}
]
}
},
"400": {
"description": "请求参数无效",
"schema": {
"$ref": "#/definitions/models.ErrorResponse"
}
},
"401": {
"description": "未认证",
"schema": {
"$ref": "#/definitions/models.ErrorResponse"
}
},
"404": {
"description": "客户不存在",
"schema": {
"$ref": "#/definitions/models.ErrorResponse"
}
},
"409": {
"description": "授权码已存在",
"schema": {
"$ref": "#/definitions/models.ErrorResponse"
}
},
"500": {
"description": "服务器内部错误",
"schema": {
"$ref": "#/definitions/models.ErrorResponse"
}
}
}
}
},
"/api/v1/login": {
"post": {
"description": "管理员用户登录接口",
@@ -859,6 +940,68 @@ const docTemplate = `{
}
}
},
"models.AuthorizationCodeCreateRequest": {
"type": "object",
"required": [
"customer_id",
"deployment_type",
"max_activations",
"validity_days"
],
"properties": {
"custom_parameters": {},
"customer_id": {
"type": "string"
},
"deployment_type": {
"type": "string",
"enum": [
"standalone",
"cloud",
"hybrid"
]
},
"description": {
"type": "string",
"maxLength": 1000
},
"encryption_type": {
"type": "string",
"enum": [
"standard",
"advanced"
]
},
"feature_config": {},
"max_activations": {
"type": "integer",
"minimum": 1
},
"software_id": {
"type": "string"
},
"software_version": {
"type": "string"
},
"usage_limits": {},
"validity_days": {
"type": "integer",
"maximum": 36500,
"minimum": 1
}
}
},
"models.AuthorizationCodeCreateResponse": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"id": {
"type": "string"
}
}
},
"models.Customer": {
"type": "object",
"properties": {
+143
View File
@@ -730,6 +730,87 @@
}
}
},
"/api/v1/authorization-codes": {
"post": {
"security": [
{
"BearerAuth": []
}
],
"description": "创建新的授权码,自动生成授权码字符串。有效期从当天00:00:00开始,到指定天数后的23:59:59结束",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"授权码管理"
],
"summary": "创建授权码",
"parameters": [
{
"description": "授权码信息",
"name": "authorization_code",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/models.AuthorizationCodeCreateRequest"
}
}
],
"responses": {
"200": {
"description": "创建成功",
"schema": {
"allOf": [
{
"$ref": "#/definitions/models.APIResponse"
},
{
"type": "object",
"properties": {
"data": {
"$ref": "#/definitions/models.AuthorizationCodeCreateResponse"
}
}
}
]
}
},
"400": {
"description": "请求参数无效",
"schema": {
"$ref": "#/definitions/models.ErrorResponse"
}
},
"401": {
"description": "未认证",
"schema": {
"$ref": "#/definitions/models.ErrorResponse"
}
},
"404": {
"description": "客户不存在",
"schema": {
"$ref": "#/definitions/models.ErrorResponse"
}
},
"409": {
"description": "授权码已存在",
"schema": {
"$ref": "#/definitions/models.ErrorResponse"
}
},
"500": {
"description": "服务器内部错误",
"schema": {
"$ref": "#/definitions/models.ErrorResponse"
}
}
}
}
},
"/api/v1/login": {
"post": {
"description": "管理员用户登录接口",
@@ -856,6 +937,68 @@
}
}
},
"models.AuthorizationCodeCreateRequest": {
"type": "object",
"required": [
"customer_id",
"deployment_type",
"max_activations",
"validity_days"
],
"properties": {
"custom_parameters": {},
"customer_id": {
"type": "string"
},
"deployment_type": {
"type": "string",
"enum": [
"standalone",
"cloud",
"hybrid"
]
},
"description": {
"type": "string",
"maxLength": 1000
},
"encryption_type": {
"type": "string",
"enum": [
"standard",
"advanced"
]
},
"feature_config": {},
"max_activations": {
"type": "integer",
"minimum": 1
},
"software_id": {
"type": "string"
},
"software_version": {
"type": "string"
},
"usage_limits": {},
"validity_days": {
"type": "integer",
"maximum": 36500,
"minimum": 1
}
}
},
"models.AuthorizationCodeCreateResponse": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"id": {
"type": "string"
}
}
},
"models.Customer": {
"type": "object",
"properties": {
+94
View File
@@ -10,6 +10,51 @@ definitions:
timestamp:
type: string
type: object
models.AuthorizationCodeCreateRequest:
properties:
custom_parameters: {}
customer_id:
type: string
deployment_type:
enum:
- standalone
- cloud
- hybrid
type: string
description:
maxLength: 1000
type: string
encryption_type:
enum:
- standard
- advanced
type: string
feature_config: {}
max_activations:
minimum: 1
type: integer
software_id:
type: string
software_version:
type: string
usage_limits: {}
validity_days:
maximum: 36500
minimum: 1
type: integer
required:
- customer_id
- deployment_type
- max_activations
- validity_days
type: object
models.AuthorizationCodeCreateResponse:
properties:
code:
type: string
id:
type: string
type: object
models.Customer:
properties:
address:
@@ -778,6 +823,55 @@ paths:
summary: 刷新Token
tags:
- 认证
/api/v1/authorization-codes:
post:
consumes:
- application/json
description: 创建新的授权码,自动生成授权码字符串。有效期从当天00:00:00开始,到指定天数后的23:59:59结束
parameters:
- description: 授权码信息
in: body
name: authorization_code
required: true
schema:
$ref: '#/definitions/models.AuthorizationCodeCreateRequest'
produces:
- application/json
responses:
"200":
description: 创建成功
schema:
allOf:
- $ref: '#/definitions/models.APIResponse'
- properties:
data:
$ref: '#/definitions/models.AuthorizationCodeCreateResponse'
type: object
"400":
description: 请求参数无效
schema:
$ref: '#/definitions/models.ErrorResponse'
"401":
description: 未认证
schema:
$ref: '#/definitions/models.ErrorResponse'
"404":
description: 客户不存在
schema:
$ref: '#/definitions/models.ErrorResponse'
"409":
description: 授权码已存在
schema:
$ref: '#/definitions/models.ErrorResponse'
"500":
description: 服务器内部错误
schema:
$ref: '#/definitions/models.ErrorResponse'
security:
- BearerAuth: []
summary: 创建授权码
tags:
- 授权码管理
/api/v1/login:
post:
consumes:
@@ -0,0 +1,156 @@
package handlers
import (
"errors"
"net/http"
"time"
"license-manager/internal/api/middleware"
"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
}
// 设置语言到Context中
ctx := middleware.WithLanguage(c.Request.Context(), c)
data, err := h.authCodeService.CreateAuthorizationCode(ctx, &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
}
// 设置语言到Context中
ctx := middleware.WithLanguage(c.Request.Context(), c)
data, err := h.authCodeService.GetAuthorizationCodeList(ctx, &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,
})
}
+7
View File
@@ -37,18 +37,21 @@ func SetupRouter() *gin.Engine {
db := database.GetDB()
customerRepo := repository.NewCustomerRepository(db)
userRepo := repository.NewUserRepository(db)
authCodeRepo := repository.NewAuthorizationCodeRepository(db)
// 初始化服务层
authService := service.NewAuthService(userRepo)
systemService := service.NewSystemService()
customerService := service.NewCustomerService(customerRepo)
enumService := service.NewEnumService()
authCodeService := service.NewAuthorizationCodeService(authCodeRepo)
// 初始化处理器层
authHandler := handlers.NewAuthHandler(authService)
systemHandler := handlers.NewSystemHandler(systemService)
customerHandler := handlers.NewCustomerHandler(customerService)
enumHandler := handlers.NewEnumHandler(enumService)
authCodeHandler := handlers.NewAuthorizationCodeHandler(authCodeService)
// 健康检测接口(无需认证)
router.GET("/health", systemHandler.HealthCheck)
@@ -84,6 +87,10 @@ func SetupRouter() *gin.Engine {
// 枚举管理
auth.GET("/enums", enumHandler.GetAllEnums)
auth.GET("/enums/:type", enumHandler.GetEnumsByType)
// 授权码管理
auth.GET("/v1/authorization-codes", authCodeHandler.GetAuthorizationCodeList)
auth.POST("/v1/authorization-codes", authCodeHandler.CreateAuthorizationCode)
}
// 管理员接口
+1
View File
@@ -23,6 +23,7 @@ func AutoMigrate() error {
&models.Customer{},
&models.CustomerCodeSequence{},
&models.User{},
&models.AuthorizationCode{},
)
if err != nil {
return fmt.Errorf("failed to migrate database: %w", err)
+128
View File
@@ -1,5 +1,12 @@
package models
import (
"time"
"github.com/google/uuid"
"gorm.io/gorm"
)
// LoginRequest 登录请求结构
type LoginRequest struct {
Username string `json:"username" binding:"required"`
@@ -24,4 +31,125 @@ type LoginData struct {
type UserInfo struct {
Username string `json:"username"`
Role string `json:"role"`
}
// AuthorizationCode 授权码模型
type AuthorizationCode struct {
ID string `gorm:"type:varchar(36);primaryKey" json:"id"`
Code string `gorm:"type:varchar(100);uniqueIndex;not null" json:"code"`
CustomerID string `gorm:"type:varchar(36);not null;index" json:"customer_id"`
CustomerName string `gorm:"-" json:"customer_name,omitempty"`
CustomerNameDisplay string `gorm:"-" json:"customer_name_display,omitempty"`
CreatedBy string `gorm:"type:varchar(36);not null" json:"created_by"`
SoftwareID *string `gorm:"type:varchar(50)" json:"software_id"`
Description *string `gorm:"type:text" json:"description"`
StartDate time.Time `gorm:"type:datetime(3);not null" json:"start_date"`
EndDate time.Time `gorm:"type:datetime(3);not null" json:"end_date"`
DeploymentType string `gorm:"type:varchar(20);not null;default:'standalone'" json:"deployment_type"`
DeploymentTypeDisplay string `gorm:"-" json:"deployment_type_display,omitempty"`
EncryptionType *string `gorm:"type:varchar(20);default:'standard'" json:"encryption_type"`
EncryptionTypeDisplay string `gorm:"-" json:"encryption_type_display,omitempty"`
SoftwareVersion *string `gorm:"type:varchar(50)" json:"software_version"`
MaxActivations int `gorm:"not null;default:1" json:"max_activations"`
CurrentActivations int `gorm:"-" json:"current_activations,omitempty"`
IsLocked bool `gorm:"not null;default:false" json:"is_locked"`
LockReason *string `gorm:"type:text" json:"lock_reason"`
LockedAt *time.Time `gorm:"type:datetime(3)" json:"locked_at"`
LockedBy *string `gorm:"type:varchar(36)" json:"locked_by"`
FeatureConfig JSON `gorm:"type:json" json:"feature_config"`
UsageLimits JSON `gorm:"type:json" json:"usage_limits"`
CustomParameters JSON `gorm:"type:json" json:"custom_parameters"`
Status string `gorm:"-" json:"status,omitempty"`
StatusDisplay string `gorm:"-" json:"status_display,omitempty"`
CreatedAt time.Time `gorm:"type:datetime(3);not null" json:"created_at"`
UpdatedAt time.Time `gorm:"type:datetime(3);not null" json:"updated_at"`
}
// TableName 指定表名
func (AuthorizationCode) TableName() string {
return "authorization_codes"
}
// BeforeCreate 创建前自动设置时间戳和ID
func (a *AuthorizationCode) BeforeCreate(tx *gorm.DB) error {
if a.ID == "" {
a.ID = uuid.New().String()
}
now := time.Now()
if a.CreatedAt.IsZero() {
a.CreatedAt = now
}
if a.UpdatedAt.IsZero() {
a.UpdatedAt = now
}
return nil
}
// BeforeUpdate 更新前自动刷新更新时间
func (a *AuthorizationCode) BeforeUpdate(tx *gorm.DB) error {
a.UpdatedAt = time.Now()
return nil
}
// AuthorizationCodeCreateRequest 创建授权码请求结构
type AuthorizationCodeCreateRequest struct {
CustomerID string `json:"customer_id" binding:"required"`
SoftwareID *string `json:"software_id" binding:"omitempty"`
Description *string `json:"description" binding:"omitempty,max=1000"`
ValidityDays int `json:"validity_days" binding:"required,min=1,max=36500"`
DeploymentType string `json:"deployment_type" binding:"required,oneof=standalone cloud hybrid"`
EncryptionType *string `json:"encryption_type" binding:"omitempty,oneof=standard advanced"`
SoftwareVersion *string `json:"software_version" binding:"omitempty"`
MaxActivations int `json:"max_activations" binding:"required,min=1"`
FeatureConfig interface{} `json:"feature_config" binding:"omitempty"`
UsageLimits interface{} `json:"usage_limits" binding:"omitempty"`
CustomParameters interface{} `json:"custom_parameters" binding:"omitempty"`
}
// AuthorizationCodeCreateResponse 创建授权码响应结构
type AuthorizationCodeCreateResponse struct {
ID string `json:"id"`
Code string `json:"code"`
}
// AuthorizationCodeListRequest 授权码列表查询请求结构
type AuthorizationCodeListRequest struct {
Page int `form:"page" binding:"omitempty,min=1"` // 页码,默认1
PageSize int `form:"page_size" binding:"omitempty,min=1,max=100"` // 每页条数,默认20,最大100
CustomerID string `form:"customer_id" binding:"omitempty"` // 客户ID筛选
Status string `form:"status" binding:"omitempty,oneof=normal locked expired"` // 状态筛选
StartDate string `form:"start_date" binding:"omitempty"` // 创建开始时间
EndDate string `form:"end_date" binding:"omitempty"` // 创建结束时间
Sort string `form:"sort" binding:"omitempty,oneof=created_at updated_at code"` // 排序字段,默认created_at
Order string `form:"order" binding:"omitempty,oneof=asc desc"` // 排序方向,默认desc
}
// AuthorizationCodeListItem 授权码列表项结构
type AuthorizationCodeListItem struct {
ID string `json:"id"`
Code string `json:"code"`
CustomerID string `json:"customer_id"`
CustomerName string `json:"customer_name"`
CustomerNameDisplay string `json:"customer_name_display,omitempty"`
Status string `json:"status"`
StatusDisplay string `json:"status_display,omitempty"`
StartDate string `json:"start_date"`
EndDate string `json:"end_date"`
MaxActivations int `json:"max_activations"`
CurrentActivations int `json:"current_activations"`
DeploymentType string `json:"deployment_type"`
DeploymentTypeDisplay string `json:"deployment_type_display,omitempty"`
IsLocked bool `json:"is_locked"`
Description *string `json:"description"`
CreatedAt string `json:"created_at"`
}
// AuthorizationCodeListResponse 授权码列表响应结构
type AuthorizationCodeListResponse struct {
List []AuthorizationCodeListItem `json:"list"`
Total int64 `json:"total"`
Page int `json:"page"`
PageSize int `json:"page_size"`
TotalPages int `json:"total_pages"`
}
+53
View File
@@ -1,5 +1,58 @@
package models
import (
"database/sql/driver"
"encoding/json"
"errors"
)
// JSON 类型用于GORM JSON字段
type JSON json.RawMessage
// 实现 driver.Valuer 接口
func (j JSON) Value() (driver.Value, error) {
if len(j) == 0 {
return nil, nil
}
return json.RawMessage(j).MarshalJSON()
}
// 实现 sql.Scanner 接口
func (j *JSON) Scan(value interface{}) error {
if value == nil {
*j = nil
return nil
}
var bytes []byte
switch v := value.(type) {
case []byte:
bytes = v
case string:
bytes = []byte(v)
default:
return errors.New("cannot scan into JSON")
}
*j = append((*j)[0:0], bytes...)
return nil
}
// MarshalJSON 实现 json.Marshaler 接口
func (j JSON) MarshalJSON() ([]byte, error) {
if len(j) == 0 {
return []byte("null"), nil
}
return j, nil
}
// UnmarshalJSON 实现 json.Unmarshaler 接口
func (j *JSON) UnmarshalJSON(data []byte) error {
if j == nil {
return errors.New("JSON receiver is nil")
}
*j = append((*j)[0:0], data...)
return nil
}
// APIResponse 通用API响应结构
type APIResponse struct {
Code string `json:"code"`
@@ -0,0 +1,150 @@
package repository
import (
"context"
"license-manager/internal/models"
"gorm.io/gorm"
)
type authorizationCodeRepository struct {
db *gorm.DB
}
// NewAuthorizationCodeRepository 创建授权码仓储实例
func NewAuthorizationCodeRepository(db *gorm.DB) AuthorizationCodeRepository {
return &authorizationCodeRepository{
db: db,
}
}
// CreateAuthorizationCode 创建授权码
func (r *authorizationCodeRepository) CreateAuthorizationCode(ctx context.Context, authCode *models.AuthorizationCode) error {
return r.db.WithContext(ctx).Create(authCode).Error
}
// GetAuthorizationCodeByID 根据ID获取授权码
func (r *authorizationCodeRepository) GetAuthorizationCodeByID(ctx context.Context, id string) (*models.AuthorizationCode, error) {
var authCode models.AuthorizationCode
err := r.db.WithContext(ctx).First(&authCode, "id = ?", id).Error
if err != nil {
if err == gorm.ErrRecordNotFound {
return nil, ErrAuthorizationCodeNotFound
}
return nil, err
}
return &authCode, nil
}
// GetAuthorizationCodeList 查询授权码列表
func (r *authorizationCodeRepository) GetAuthorizationCodeList(ctx context.Context, req *models.AuthorizationCodeListRequest) (*models.AuthorizationCodeListResponse, error) {
// 设置默认值
page := req.Page
if page <= 0 {
page = 1
}
pageSize := req.PageSize
if pageSize <= 0 {
pageSize = 20
}
sort := req.Sort
if sort == "" {
sort = "created_at"
}
order := req.Order
if order == "" {
order = "desc"
}
// 构建查询
query := r.db.WithContext(ctx).Table("authorization_codes ac").
Select(`ac.id, ac.code, ac.customer_id, c.customer_name,
ac.start_date, ac.end_date, ac.max_activations,
ac.deployment_type, ac.is_locked, ac.description, ac.created_at`).
Joins("LEFT JOIN customers c ON ac.customer_id = c.id AND c.deleted_at IS NULL").
Where("ac.deleted_at IS NULL")
// 添加筛选条件
if req.CustomerID != "" {
query = query.Where("ac.customer_id = ?", req.CustomerID)
}
// 状态筛选需要在应用层处理,因为status是计算字段
if req.StartDate != "" {
query = query.Where("ac.created_at >= ?", req.StartDate)
}
if req.EndDate != "" {
query = query.Where("ac.created_at <= ?", req.EndDate)
}
// 获取总数
var total int64
if err := query.Count(&total).Error; err != nil {
return nil, err
}
// 分页查询
var results []struct {
ID string `json:"id"`
Code string `json:"code"`
CustomerID string `json:"customer_id"`
CustomerName *string `json:"customer_name"`
StartDate string `json:"start_date"`
EndDate string `json:"end_date"`
MaxActivations int `json:"max_activations"`
DeploymentType string `json:"deployment_type"`
IsLocked bool `json:"is_locked"`
Description *string `json:"description"`
CreatedAt string `json:"created_at"`
}
offset := (page - 1) * pageSize
orderClause := sort + " " + order
if err := query.Order(orderClause).Limit(pageSize).Offset(offset).Scan(&results).Error; err != nil {
return nil, err
}
// 转换为响应格式
list := make([]models.AuthorizationCodeListItem, len(results))
for i, result := range results {
customerName := ""
if result.CustomerName != nil {
customerName = *result.CustomerName
}
list[i] = models.AuthorizationCodeListItem{
ID: result.ID,
Code: result.Code,
CustomerID: result.CustomerID,
CustomerName: customerName,
StartDate: result.StartDate,
EndDate: result.EndDate,
MaxActivations: result.MaxActivations,
CurrentActivations: 0, // TODO: 从licenses表统计
DeploymentType: result.DeploymentType,
IsLocked: result.IsLocked,
Description: result.Description,
CreatedAt: result.CreatedAt,
}
}
totalPages := int((total + int64(pageSize) - 1) / int64(pageSize))
return &models.AuthorizationCodeListResponse{
List: list,
Total: total,
Page: page,
PageSize: pageSize,
TotalPages: totalPages,
}, nil
}
// CheckCustomerExists 检查客户是否存在
func (r *authorizationCodeRepository) CheckCustomerExists(ctx context.Context, customerID string) (bool, error) {
var count int64
err := r.db.WithContext(ctx).Model(&models.Customer{}).Where("id = ? AND deleted_at IS NULL", customerID).Count(&count).Error
if err != nil {
return false, err
}
return count > 0, nil
}
+7
View File
@@ -9,6 +9,13 @@ var (
ErrCustomerCodeDuplicate = errors.New("customer code already exists")
)
// 授权码领域的业务错误
var (
ErrAuthorizationCodeNotFound = errors.New("authorization code not found")
ErrAuthorizationCodeAlreadyExists = errors.New("authorization code already exists")
ErrAuthorizationCodeDuplicate = errors.New("authorization code already exists")
)
// 通用的数据库/系统错误(给开发者看的)
var (
ErrDatabaseConnection = errors.New("database connection failed")
+15
View File
@@ -45,4 +45,19 @@ type UserRepository interface {
// LockUser 锁定用户账号
LockUser(ctx context.Context, id string, lockDuration int) error
}
// AuthorizationCodeRepository 授权码数据访问接口
type AuthorizationCodeRepository interface {
// CreateAuthorizationCode 创建授权码
CreateAuthorizationCode(ctx context.Context, authCode *models.AuthorizationCode) error
// GetAuthorizationCodeByID 根据ID获取授权码
GetAuthorizationCodeByID(ctx context.Context, id string) (*models.AuthorizationCode, error)
// GetAuthorizationCodeList 查询授权码列表
GetAuthorizationCodeList(ctx context.Context, req *models.AuthorizationCodeListRequest) (*models.AuthorizationCodeListResponse, error)
// CheckCustomerExists 检查客户是否存在
CheckCustomerExists(ctx context.Context, customerID string) (bool, error)
}
@@ -0,0 +1,225 @@
package service
import (
"context"
"crypto/rand"
"encoding/json"
"fmt"
"strings"
"time"
"license-manager/internal/models"
"license-manager/internal/repository"
pkgcontext "license-manager/pkg/context"
"license-manager/pkg/i18n"
)
type authorizationCodeService struct {
authCodeRepo repository.AuthorizationCodeRepository
}
// NewAuthorizationCodeService 创建授权码服务实例
func NewAuthorizationCodeService(authCodeRepo repository.AuthorizationCodeRepository) AuthorizationCodeService {
return &authorizationCodeService{
authCodeRepo: authCodeRepo,
}
}
// CreateAuthorizationCode 创建授权码
func (s *authorizationCodeService) CreateAuthorizationCode(ctx context.Context, req *models.AuthorizationCodeCreateRequest) (*models.AuthorizationCodeCreateResponse, error) {
lang := pkgcontext.GetLanguageFromContext(ctx)
// 业务逻辑:参数验证
if req == nil {
return nil, i18n.NewI18nError("900001", lang)
}
// 验证客户是否存在
exists, err := s.authCodeRepo.CheckCustomerExists(ctx, req.CustomerID)
if err != nil {
return nil, i18n.NewI18nError("900004", lang, err.Error())
}
if !exists {
return nil, i18n.NewI18nError("200001", lang) // 客户不存在
}
// 业务逻辑:计算开始时间和结束时间
now := time.Now()
startDate := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location()) // 当天00:00:00开始
endDate := startDate.AddDate(0, 0, req.ValidityDays-1).Add(23*time.Hour + 59*time.Minute + 59*time.Second) // 有效期最后一天的23:59:59结束
// 获取当前用户ID
currentUserID := "admin_uuid" // TODO: 从JWT token中获取
// 生成授权码
authCode, err := s.generateAuthorizationCode(req.CustomerID)
if err != nil {
return nil, i18n.NewI18nError("900004", lang, err.Error())
}
// 处理JSON字段
var featureConfig, usageLimits, customParameters models.JSON
if req.FeatureConfig != nil {
featureConfigBytes, err := json.Marshal(req.FeatureConfig)
if err != nil {
return nil, i18n.NewI18nError("300010", lang) // 配置参数错误
}
featureConfig = models.JSON(featureConfigBytes)
}
if req.UsageLimits != nil {
usageLimitsBytes, err := json.Marshal(req.UsageLimits)
if err != nil {
return nil, i18n.NewI18nError("300010", lang) // 配置参数错误
}
usageLimits = models.JSON(usageLimitsBytes)
}
if req.CustomParameters != nil {
customParametersBytes, err := json.Marshal(req.CustomParameters)
if err != nil {
return nil, i18n.NewI18nError("300010", lang) // 配置参数错误
}
customParameters = models.JSON(customParametersBytes)
}
// 设置默认加密类型
encryptionType := req.EncryptionType
if encryptionType == nil {
defaultEncryption := "standard"
encryptionType = &defaultEncryption
}
// 构建授权码实体
authCodeEntity := &models.AuthorizationCode{
Code: authCode,
CustomerID: req.CustomerID,
CreatedBy: currentUserID,
SoftwareID: req.SoftwareID,
Description: req.Description,
StartDate: startDate,
EndDate: endDate,
DeploymentType: req.DeploymentType,
EncryptionType: encryptionType,
SoftwareVersion: req.SoftwareVersion,
MaxActivations: req.MaxActivations,
IsLocked: false,
FeatureConfig: featureConfig,
UsageLimits: usageLimits,
CustomParameters: customParameters,
}
// 委托给Repository层进行数据创建
if err := s.authCodeRepo.CreateAuthorizationCode(ctx, authCodeEntity); err != nil {
// 根据错误类型包装为完整的I18nError
if strings.Contains(err.Error(), "duplicate") || strings.Contains(err.Error(), "unique") {
return nil, i18n.NewI18nError("300002", lang) // 授权码已存在
}
// 数据库相关错误
return nil, i18n.NewI18nError("900004", lang, err.Error())
}
return &models.AuthorizationCodeCreateResponse{
ID: authCodeEntity.ID,
Code: authCodeEntity.Code,
}, nil
}
// GetAuthorizationCodeList 查询授权码列表
func (s *authorizationCodeService) GetAuthorizationCodeList(ctx context.Context, req *models.AuthorizationCodeListRequest) (*models.AuthorizationCodeListResponse, error) {
lang := pkgcontext.GetLanguageFromContext(ctx)
// 业务逻辑:参数验证
if req == nil {
return nil, i18n.NewI18nError("900001", lang)
}
// 委托给Repository层进行数据访问
result, err := s.authCodeRepo.GetAuthorizationCodeList(ctx, req)
if err != nil {
// 数据库相关错误
return nil, i18n.NewI18nError("900004", lang, err.Error())
}
// 业务逻辑:添加多语言显示字段和状态计算
now := time.Now()
for i := range result.List {
item := &result.List[i]
s.fillAuthCodeDisplayFields(item, lang, now)
// 根据状态筛选
if req.Status != "" && item.Status != req.Status {
// 移除不匹配的项 - 这里简化处理,实际应该在查询时过滤
continue
}
}
return result, nil
}
// fillAuthCodeDisplayFields 填充授权码列表项多语言显示字段
func (s *authorizationCodeService) fillAuthCodeDisplayFields(item *models.AuthorizationCodeListItem, lang string, now time.Time) {
// 计算状态
endDate, _ := time.Parse("2006-01-02T15:04:05Z07:00", item.EndDate)
if item.IsLocked {
item.Status = "locked"
} else if now.After(endDate) {
item.Status = "expired"
} else {
item.Status = "normal"
}
// 添加多语言显示字段
item.StatusDisplay = i18n.GetEnumMessage("authorization_status", item.Status, lang)
item.DeploymentTypeDisplay = i18n.GetEnumMessage("deployment_type", item.DeploymentType, lang)
item.CustomerNameDisplay = item.CustomerName // 客户名称暂时不需要翻译
}
// generateAuthorizationCode 生成授权码
func (s *authorizationCodeService) generateAuthorizationCode(customerID string) (string, error) {
// 获取客户编码的前4位作为前缀
customerCode := "COMP" // 默认前缀
if len(customerID) >= 4 {
customerCode = strings.ToUpper(customerID[:4])
}
// 生成8位随机字符串
randomStr, err := s.generateRandomString(8)
if err != nil {
return "", err
}
// 生成4位校验码
checksum := s.generateChecksum(customerCode + randomStr)
// 格式: LIC-{customer_code}-{random}-{checksum}
return fmt.Sprintf("LIC-%s-%s-%s", customerCode, randomStr, checksum), nil
}
// generateRandomString 生成随机字符串
func (s *authorizationCodeService) generateRandomString(length int) (string, error) {
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
bytes := make([]byte, length)
if _, err := rand.Read(bytes); err != nil {
return "", err
}
for i, b := range bytes {
bytes[i] = chars[b%byte(len(chars))]
}
return string(bytes), nil
}
// generateChecksum 生成校验码
func (s *authorizationCodeService) generateChecksum(input string) string {
sum := 0
for _, char := range input {
sum += int(char)
}
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
checksum := ""
for i := 0; i < 4; i++ {
checksum += string(chars[sum%len(chars)])
sum = sum / len(chars)
}
return checksum
}
+6
View File
@@ -33,3 +33,9 @@ type EnumService interface {
GetAllEnums(ctx context.Context) (*models.EnumListResponse, error)
GetEnumsByType(ctx context.Context, enumType string) (*models.EnumTypeResponse, error)
}
// AuthorizationCodeService 授权码服务接口
type AuthorizationCodeService interface {
CreateAuthorizationCode(ctx context.Context, req *models.AuthorizationCodeCreateRequest) (*models.AuthorizationCodeCreateResponse, error)
GetAuthorizationCodeList(ctx context.Context, req *models.AuthorizationCodeListRequest) (*models.AuthorizationCodeListResponse, error)
}
+1 -1
View File
@@ -7,7 +7,7 @@
# @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
###
# 开发环境接口地址
VITE_API_BASE_URL = 'http://lm.cedar-v.com:18888'
VITE_API_BASE_URL = 'http://localhost:18888'
# # 开发环境打包名称
# VITE_OUTDIR='dev'