feat: 授权码列表、创建授权码接口完成
This commit is contained in:
1 parent
5c086a32f5
commit
188406c517
18 files changed
+1656
-19
No files matched your search
@@ -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"
|
||||
@@ -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: "不明なエラー"
|
||||
@@ -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: "未知错误"
|
||||
@@ -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` - 已撤销:被管理员撤销或因违规被停用
|
||||
@@ -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": {
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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,
|
||||
})
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
// 管理员接口
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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"`
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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")
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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'
|
||||
|
||||
Reference in new issue
Block a user