feat: 增加授权趋势、最近授权两个接口

This commit is contained in:
wood911010 committed 2025-09-23 14:20:38 +08:00
1 parent 2f23e2c07e
commit 795458f6d8
16 files changed
+1755 -2

No files matched your search

+21
View File
@@ -35,6 +35,15 @@ errors:
"300009": "Failed to generate license file"
"300010": "Invalid configuration parameters"
# Dashboard module (40xxxx)
dashboard:
"400001": "Invalid time type parameter"
"400002": "Invalid start date format"
"400003": "Invalid end date format"
"400004": "Time range exceeds limit (over one year)"
"400005": "Start date cannot be later than end date"
"400006": "Invalid limit parameter"
# System general (90xxxx)
system:
"900001": "Invalid request parameters"
@@ -42,14 +51,26 @@ errors:
"900003": "Resource conflict"
"900004": "Internal server error"
# Common text
common:
success: "Success"
# Enum translations
enums:
dashboard_period:
week: "This Week"
month: "This Month"
customer_type:
"individual": "Individual"
"enterprise": "Enterprise"
"government": "Government"
"education": "Education"
authorization_code_status:
normal: "Normal"
locked: "Locked"
expired: "Expired"
customer_level:
"normal": "Normal"
"vip": "VIP"
+21
View File
@@ -35,6 +35,15 @@ errors:
"300009": "ライセンスファイルの生成に失敗しました"
"300010": "無効な設定パラメータ"
# ダッシュボードモジュール (40xxxx)
dashboard:
"400001": "無効な時間タイプパラメータ"
"400002": "無効な開始日付形式"
"400003": "無効な終了日付形式"
"400004": "時間範囲が制限を超えています(1年以上)"
"400005": "開始日は終了日より後にできません"
"400006": "無効な制限パラメータ"
# システム共通 (90xxxx)
system:
"900001": "無効なリクエストパラメータ"
@@ -42,14 +51,26 @@ errors:
"900003": "リソースの競合"
"900004": "内部サーバーエラー"
# 共通テキスト
common:
success: "成功"
# 列挙値多言語
enums:
dashboard_period:
week: "今週"
month: "今月"
customer_type:
"individual": "個人顧客"
"enterprise": "企業顧客"
"government": "政府顧客"
"education": "教育顧客"
authorization_code_status:
normal: "正常"
locked: "ロック済み"
expired: "期限切れ"
customer_level:
"normal": "一般顧客"
"vip": "VIP顧客"
+21
View File
@@ -35,6 +35,15 @@ errors:
"300009": "许可证文件生成失败"
"300010": "配置参数错误"
# 仪表盘模块 (40xxxx)
dashboard:
"400001": "时间类型参数错误"
"400002": "开始日期格式错误"
"400003": "结束日期格式错误"
"400004": "时间范围超过限制(超过一年)"
"400005": "开始日期不能晚于结束日期"
"400006": "数量限制参数错误"
# 系统通用 (90xxxx)
system:
"900001": "请求参数无效"
@@ -42,14 +51,26 @@ errors:
"900003": "资源冲突"
"900004": "服务器内部错误"
# 通用文本
common:
success: "成功"
# 枚举值多语言
enums:
dashboard_period:
week: "本周"
month: "本月"
customer_type:
"individual": "个人客户"
"enterprise": "企业客户"
"government": "政府客户"
"education": "教育客户"
authorization_code_status:
normal: "正常"
locked: "已锁定"
expired: "已过期"
customer_level:
"normal": "普通客户"
"vip": "VIP客户"
+4 -1
View File
@@ -2,6 +2,8 @@
## 核心要点
一定要阅读customer模块的实现,看看其他模块的实,明白编码细节,现然后再进行开发。
### 1. 分层架构
```
internal/
@@ -77,4 +79,5 @@ internal/
- 分层职责是否清晰
- 多语言字段是否完整
- 错误处理是否统一
- Swagger注解是否完整
- Swagger注解是否完整
- 避免不必要的冗余,能写到工具函数的不要写在模块内
+16 -1
View File
@@ -25,8 +25,10 @@ backend/
│ ├── api/
│ │ ├── handlers/ # HTTP处理器
│ │ │ ├── auth_handler.go
│ │ │ ├── authorization_code_handler.go
│ │ │ ├── customer_handler.go
│ │ │ ├── enum_handler.go
│ │ │ ├── license_handler.go
│ │ │ └── system_handler.go
│ │ ├── middleware/ # 中间件
│ │ │ ├── auth.go
@@ -37,19 +39,26 @@ backend/
│ │ └── router.go
│ ├── service/ # 业务逻辑层
│ │ ├── auth_service.go
│ │ ├── authorization_code_service.go
│ │ ├── customer_service.go
│ │ ├── enum_service.go
│ │ ├── license_service.go
│ │ ├── system_service.go
│ │ └── interfaces.go # 接口定义
│ ├── repository/ # 数据访问层
│ │ ├── authorization_code_repository.go
│ │ ├── customer_repository.go
│ │ ├── license_repository.go
│ │ ├── user_repository.go
│ │ ├── errors.go
│ │ └── interfaces.go
│ ├── models/ # 数据模型
│ │ ├── auth.go
│ │ ├── common.go
│ │ ├── customer.go
│ │ ├── license.go
│ │ ├── system.go
│ │ └── common.go
│ │ └── user.go
│ ├── config/ # 配置管理
│ │ └── config.go
│ └── database/ # 数据库连接和迁移
@@ -85,9 +94,11 @@ backend/
│ │ ├── db_install.md
│ │ └── swagger_install.md
│ ├── modules_design/
│ │ ├── auth_api.md
│ │ ├── customer-api.md
│ │ ├── customer.md
│ │ ├── login&auth.md
│ │ ├── user.md
│ │ ├── user_story.md
│ │ ├── 产品原型设计方案.md
│ │ └── 授权模块属性设计.md
@@ -98,6 +109,10 @@ backend/
├── migrations/ # 数据库迁移
│ ├── 001_create_customers_table.sql
│ ├── 002_insert_sample_data.sql
│ ├── 003_create_authorization_codes_table.sql
│ ├── 004_create_licenses_table.sql
│ ├── 005_create_authorization_changes_table.sql
│ ├── 006_create_users_table.sql
│ └── README.md
├── go.mod
└── go.sum
@@ -0,0 +1,125 @@
# 仪表盘 API 设计
## 1. 授权趋势统计 API
### 1.1 获取授权趋势数据
```http
GET /api/v1/dashboard/authorization-trend
```
**查询参数**
- `type` - 时间类型 (week/month/custom) 必填
- `start_date` - 开始日期 (YYYY-MM-DD格式,当type为custom时必填)
- `end_date` - 结束日期 (YYYY-MM-DD格式,当type为custom时必填)
- `timezone` - 时区 (可选,如:Asia/Shanghai、UTC等,默认使用服务器本地时区)
**参数说明:**
- `week`: 返回本周每日的授权总数(周一到周日)
- `month`: 返回本月每日的授权总数(1号到当月最后一天)
- `custom`: 返回指定时间段内每日授权总数,时间间隔不超过一年
- `timezone`: 指定时区,影响"本周"、"本月"以及自定义日期的计算基准。支持标准时区名称如Asia/Shanghai、America/New_York、UTC等
**响应**
```json
{
"code": "000000",
"message": "成功",
"data": {
"period": {
"type": "week",
"start_date": "2024-01-01",
"end_date": "2024-01-07",
"description_display": "本周"
},
"trend_data": [
{
"date": "2024-01-01",
"total_authorizations": 15,
"new_authorizations": 3,
"expired_authorizations": 1
},
{
"date": "2024-01-02",
"total_authorizations": 17,
"new_authorizations": 2,
"expired_authorizations": 0
}
],
"summary": {
"total_count": 120,
"new_count": 15,
"expired_count": 5,
"growth_rate": 5.2
}
}
}
```
**响应字段说明:**
- `description_display` - 多语言
## 2. 最近授权列表 API
### 2.1 获取最近授权列表
```http
GET /api/v1/dashboard/recent-authorizations
```
**查询参数**
- `limit` - 返回数量限制(默认20,最大100)
- `customer_id` - 客户ID筛选(可选)
- `status` - 状态筛选 (normal/locked/expired) 可选
**响应**
```json
{
"code": "000000",
"message": "成功",
"data": {
"list": [
{
"id": "uuid-string",
"code": "LIC-COMP001-A7B9X2-C8F4",
"customer_id": "customer-uuid",
"customer_name": "张三公司",
"description": "企业版授权",
"status": "normal",
"status_display": "正常",
"start_date": "2024-01-01T00:00:00Z",
"end_date": "2024-12-31T23:59:59Z",
"max_activations": 10,
"current_activations": 7,
"created_at": "2024-01-01T10:00:00Z",
"updated_at": "2024-01-01T10:00:00Z"
}
],
"total": 20
}
}
```
**响应字段说明:**
- `total` - 符合条件的授权总数量
- `list` - 当前返回的授权列表数据
- `status_display` - 多语言
## 3. 状态说明
### 3.1 授权码状态
- `normal` - 正常:当前时间在有效期内且未被锁定
- `locked` - 已锁定:is_locked=true
- `expired` - 已过期:当前时间超过end_date
### 3.2 时间类型
- `week` - 本周:从周一00:00:00到周日23:59:59 (基于指定时区)
- `month` - 本月:从1号00:00:00到当月最后一天23:59:59 (基于指定时区)
- `custom` - 自定义:指定开始和结束日期,最大跨度365天 (基于指定时区)
## 4. 错误码定义
- `400001` - 时间类型参数错误
- `400002` - 开始日期格式错误
- `400003` - 结束日期格式错误
- `400004` - 时间范围超过限制(超过一年)
- `400005` - 开始日期不能晚于结束日期
- `400006` - 数量限制参数错误
+343
View File
@@ -1439,6 +1439,185 @@ const docTemplate = `{
}
}
},
"/api/v1/dashboard/authorization-trend": {
"get": {
"security": [
{
"BearerAuth": []
}
],
"description": "根据时间类型(本周/本月/自定义)获取授权趋势统计数据",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"仪表盘"
],
"summary": "获取授权趋势数据",
"parameters": [
{
"enum": [
"week",
"month",
"custom"
],
"type": "string",
"description": "时间类型",
"name": "type",
"in": "query",
"required": true
},
{
"type": "string",
"description": "开始日期(YYYY-MM-DD格式,当type为custom时必填)",
"name": "start_date",
"in": "query"
},
{
"type": "string",
"description": "结束日期(YYYY-MM-DD格式,当type为custom时必填)",
"name": "end_date",
"in": "query"
}
],
"responses": {
"200": {
"description": "授权趋势数据",
"schema": {
"allOf": [
{
"$ref": "#/definitions/models.APIResponse"
},
{
"type": "object",
"properties": {
"data": {
"$ref": "#/definitions/models.DashboardAuthorizationTrendResponse"
}
}
}
]
}
},
"400": {
"description": "请求参数错误",
"schema": {
"$ref": "#/definitions/models.ErrorResponse"
}
},
"401": {
"description": "未认证",
"schema": {
"$ref": "#/definitions/models.ErrorResponse"
}
},
"403": {
"description": "权限不足",
"schema": {
"$ref": "#/definitions/models.ErrorResponse"
}
},
"500": {
"description": "服务器内部错误",
"schema": {
"$ref": "#/definitions/models.ErrorResponse"
}
}
}
}
},
"/api/v1/dashboard/recent-authorizations": {
"get": {
"security": [
{
"BearerAuth": []
}
],
"description": "获取最近创建的授权码列表,支持按客户ID和状态筛选",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"仪表盘"
],
"summary": "获取最近授权列表",
"parameters": [
{
"type": "integer",
"description": "返回数量限制(默认20,最大100)",
"name": "limit",
"in": "query"
},
{
"type": "string",
"description": "客户ID筛选",
"name": "customer_id",
"in": "query"
},
{
"enum": [
"normal",
"locked",
"expired"
],
"type": "string",
"description": "状态筛选",
"name": "status",
"in": "query"
}
],
"responses": {
"200": {
"description": "最近授权列表",
"schema": {
"allOf": [
{
"$ref": "#/definitions/models.APIResponse"
},
{
"type": "object",
"properties": {
"data": {
"$ref": "#/definitions/models.DashboardRecentAuthorizationsResponse"
}
}
}
]
}
},
"400": {
"description": "请求参数错误",
"schema": {
"$ref": "#/definitions/models.ErrorResponse"
}
},
"401": {
"description": "未认证",
"schema": {
"$ref": "#/definitions/models.ErrorResponse"
}
},
"403": {
"description": "权限不足",
"schema": {
"$ref": "#/definitions/models.ErrorResponse"
}
},
"500": {
"description": "服务器内部错误",
"schema": {
"$ref": "#/definitions/models.ErrorResponse"
}
}
}
}
},
"/api/v1/heartbeat": {
"post": {
"description": "客户端定期发送心跳,更新在线状态和使用数据",
@@ -2824,6 +3003,50 @@ const docTemplate = `{
}
}
},
"models.DashboardAuthorizationTrendResponse": {
"type": "object",
"properties": {
"period": {
"description": "时间段信息",
"allOf": [
{
"$ref": "#/definitions/models.TrendPeriod"
}
]
},
"summary": {
"description": "汇总信息",
"allOf": [
{
"$ref": "#/definitions/models.TrendSummary"
}
]
},
"trend_data": {
"description": "趋势数据",
"type": "array",
"items": {
"$ref": "#/definitions/models.TrendData"
}
}
}
},
"models.DashboardRecentAuthorizationsResponse": {
"type": "object",
"properties": {
"list": {
"description": "授权列表",
"type": "array",
"items": {
"$ref": "#/definitions/models.RecentAuthorization"
}
},
"total": {
"description": "符合条件的授权总数量",
"type": "integer"
}
}
},
"models.EnumItem": {
"type": "object",
"properties": {
@@ -3242,6 +3465,63 @@ const docTemplate = `{
}
}
},
"models.RecentAuthorization": {
"type": "object",
"properties": {
"code": {
"description": "授权码",
"type": "string"
},
"created_at": {
"description": "创建时间",
"type": "string"
},
"current_activations": {
"description": "当前激活数量",
"type": "integer"
},
"customer_id": {
"description": "客户ID",
"type": "string"
},
"customer_name": {
"description": "客户名称",
"type": "string"
},
"description": {
"description": "授权描述",
"type": "string"
},
"end_date": {
"description": "到期时间",
"type": "string"
},
"id": {
"description": "授权码ID",
"type": "string"
},
"max_activations": {
"description": "最大激活数量",
"type": "integer"
},
"start_date": {
"description": "生效时间",
"type": "string"
},
"status": {
"description": "状态 (normal/locked/expired)",
"type": "string"
},
"status_display": {
"description": "状态显示文本(多语言)",
"type": "string"
},
"updated_at": {
"description": "更新时间",
"type": "string"
}
}
},
"models.StatsOverviewResponse": {
"type": "object",
"properties": {
@@ -3290,6 +3570,69 @@ const docTemplate = `{
"type": "string"
}
}
},
"models.TrendData": {
"type": "object",
"properties": {
"date": {
"description": "日期 (YYYY-MM-DD)",
"type": "string"
},
"expired_authorizations": {
"description": "当日过期授权数",
"type": "integer"
},
"new_authorizations": {
"description": "当日新增授权数",
"type": "integer"
},
"total_authorizations": {
"description": "当日授权总数",
"type": "integer"
}
}
},
"models.TrendPeriod": {
"type": "object",
"properties": {
"description_display": {
"description": "多语言描述",
"type": "string"
},
"end_date": {
"description": "结束日期",
"type": "string"
},
"start_date": {
"description": "开始日期",
"type": "string"
},
"type": {
"description": "时间类型",
"type": "string"
}
}
},
"models.TrendSummary": {
"type": "object",
"properties": {
"expired_count": {
"description": "期间过期授权数",
"type": "integer"
},
"growth_rate": {
"description": "增长率(百分比)",
"type": "number"
},
"new_count": {
"description": "期间新增授权数",
"type": "integer"
},
"total_count": {
"description": "期间总授权数",
"type": "integer"
}
}
}
},
"securityDefinitions": {
+343
View File
@@ -1436,6 +1436,185 @@
}
}
},
"/api/v1/dashboard/authorization-trend": {
"get": {
"security": [
{
"BearerAuth": []
}
],
"description": "根据时间类型(本周/本月/自定义)获取授权趋势统计数据",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"仪表盘"
],
"summary": "获取授权趋势数据",
"parameters": [
{
"enum": [
"week",
"month",
"custom"
],
"type": "string",
"description": "时间类型",
"name": "type",
"in": "query",
"required": true
},
{
"type": "string",
"description": "开始日期(YYYY-MM-DD格式,当type为custom时必填)",
"name": "start_date",
"in": "query"
},
{
"type": "string",
"description": "结束日期(YYYY-MM-DD格式,当type为custom时必填)",
"name": "end_date",
"in": "query"
}
],
"responses": {
"200": {
"description": "授权趋势数据",
"schema": {
"allOf": [
{
"$ref": "#/definitions/models.APIResponse"
},
{
"type": "object",
"properties": {
"data": {
"$ref": "#/definitions/models.DashboardAuthorizationTrendResponse"
}
}
}
]
}
},
"400": {
"description": "请求参数错误",
"schema": {
"$ref": "#/definitions/models.ErrorResponse"
}
},
"401": {
"description": "未认证",
"schema": {
"$ref": "#/definitions/models.ErrorResponse"
}
},
"403": {
"description": "权限不足",
"schema": {
"$ref": "#/definitions/models.ErrorResponse"
}
},
"500": {
"description": "服务器内部错误",
"schema": {
"$ref": "#/definitions/models.ErrorResponse"
}
}
}
}
},
"/api/v1/dashboard/recent-authorizations": {
"get": {
"security": [
{
"BearerAuth": []
}
],
"description": "获取最近创建的授权码列表,支持按客户ID和状态筛选",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"仪表盘"
],
"summary": "获取最近授权列表",
"parameters": [
{
"type": "integer",
"description": "返回数量限制(默认20,最大100)",
"name": "limit",
"in": "query"
},
{
"type": "string",
"description": "客户ID筛选",
"name": "customer_id",
"in": "query"
},
{
"enum": [
"normal",
"locked",
"expired"
],
"type": "string",
"description": "状态筛选",
"name": "status",
"in": "query"
}
],
"responses": {
"200": {
"description": "最近授权列表",
"schema": {
"allOf": [
{
"$ref": "#/definitions/models.APIResponse"
},
{
"type": "object",
"properties": {
"data": {
"$ref": "#/definitions/models.DashboardRecentAuthorizationsResponse"
}
}
}
]
}
},
"400": {
"description": "请求参数错误",
"schema": {
"$ref": "#/definitions/models.ErrorResponse"
}
},
"401": {
"description": "未认证",
"schema": {
"$ref": "#/definitions/models.ErrorResponse"
}
},
"403": {
"description": "权限不足",
"schema": {
"$ref": "#/definitions/models.ErrorResponse"
}
},
"500": {
"description": "服务器内部错误",
"schema": {
"$ref": "#/definitions/models.ErrorResponse"
}
}
}
}
},
"/api/v1/heartbeat": {
"post": {
"description": "客户端定期发送心跳,更新在线状态和使用数据",
@@ -2821,6 +3000,50 @@
}
}
},
"models.DashboardAuthorizationTrendResponse": {
"type": "object",
"properties": {
"period": {
"description": "时间段信息",
"allOf": [
{
"$ref": "#/definitions/models.TrendPeriod"
}
]
},
"summary": {
"description": "汇总信息",
"allOf": [
{
"$ref": "#/definitions/models.TrendSummary"
}
]
},
"trend_data": {
"description": "趋势数据",
"type": "array",
"items": {
"$ref": "#/definitions/models.TrendData"
}
}
}
},
"models.DashboardRecentAuthorizationsResponse": {
"type": "object",
"properties": {
"list": {
"description": "授权列表",
"type": "array",
"items": {
"$ref": "#/definitions/models.RecentAuthorization"
}
},
"total": {
"description": "符合条件的授权总数量",
"type": "integer"
}
}
},
"models.EnumItem": {
"type": "object",
"properties": {
@@ -3239,6 +3462,63 @@
}
}
},
"models.RecentAuthorization": {
"type": "object",
"properties": {
"code": {
"description": "授权码",
"type": "string"
},
"created_at": {
"description": "创建时间",
"type": "string"
},
"current_activations": {
"description": "当前激活数量",
"type": "integer"
},
"customer_id": {
"description": "客户ID",
"type": "string"
},
"customer_name": {
"description": "客户名称",
"type": "string"
},
"description": {
"description": "授权描述",
"type": "string"
},
"end_date": {
"description": "到期时间",
"type": "string"
},
"id": {
"description": "授权码ID",
"type": "string"
},
"max_activations": {
"description": "最大激活数量",
"type": "integer"
},
"start_date": {
"description": "生效时间",
"type": "string"
},
"status": {
"description": "状态 (normal/locked/expired)",
"type": "string"
},
"status_display": {
"description": "状态显示文本(多语言)",
"type": "string"
},
"updated_at": {
"description": "更新时间",
"type": "string"
}
}
},
"models.StatsOverviewResponse": {
"type": "object",
"properties": {
@@ -3287,6 +3567,69 @@
"type": "string"
}
}
},
"models.TrendData": {
"type": "object",
"properties": {
"date": {
"description": "日期 (YYYY-MM-DD)",
"type": "string"
},
"expired_authorizations": {
"description": "当日过期授权数",
"type": "integer"
},
"new_authorizations": {
"description": "当日新增授权数",
"type": "integer"
},
"total_authorizations": {
"description": "当日授权总数",
"type": "integer"
}
}
},
"models.TrendPeriod": {
"type": "object",
"properties": {
"description_display": {
"description": "多语言描述",
"type": "string"
},
"end_date": {
"description": "结束日期",
"type": "string"
},
"start_date": {
"description": "开始日期",
"type": "string"
},
"type": {
"description": "时间类型",
"type": "string"
}
}
},
"models.TrendSummary": {
"type": "object",
"properties": {
"expired_count": {
"description": "期间过期授权数",
"type": "integer"
},
"growth_rate": {
"description": "增长率(百分比)",
"type": "number"
},
"new_count": {
"description": "期间新增授权数",
"type": "integer"
},
"total_count": {
"description": "期间总授权数",
"type": "integer"
}
}
}
},
"securityDefinitions": {
+225
View File
@@ -513,6 +513,33 @@ definitions:
- disabled
type: string
type: object
models.DashboardAuthorizationTrendResponse:
properties:
period:
allOf:
- $ref: '#/definitions/models.TrendPeriod'
description: 时间段信息
summary:
allOf:
- $ref: '#/definitions/models.TrendSummary'
description: 汇总信息
trend_data:
description: 趋势数据
items:
$ref: '#/definitions/models.TrendData'
type: array
type: object
models.DashboardRecentAuthorizationsResponse:
properties:
list:
description: 授权列表
items:
$ref: '#/definitions/models.RecentAuthorization'
type: array
total:
description: 符合条件的授权总数量
type: integer
type: object
models.EnumItem:
properties:
display:
@@ -796,6 +823,48 @@ definitions:
message:
type: string
type: object
models.RecentAuthorization:
properties:
code:
description: 授权码
type: string
created_at:
description: 创建时间
type: string
current_activations:
description: 当前激活数量
type: integer
customer_id:
description: 客户ID
type: string
customer_name:
description: 客户名称
type: string
description:
description: 授权描述
type: string
end_date:
description: 到期时间
type: string
id:
description: 授权码ID
type: string
max_activations:
description: 最大激活数量
type: integer
start_date:
description: 生效时间
type: string
status:
description: 状态 (normal/locked/expired)
type: string
status_display:
description: 状态显示文本(多语言)
type: string
updated_at:
description: 更新时间
type: string
type: object
models.StatsOverviewResponse:
properties:
abnormal_alerts:
@@ -828,6 +897,51 @@ definitions:
version:
type: string
type: object
models.TrendData:
properties:
date:
description: 日期 (YYYY-MM-DD)
type: string
expired_authorizations:
description: 当日过期授权数
type: integer
new_authorizations:
description: 当日新增授权数
type: integer
total_authorizations:
description: 当日授权总数
type: integer
type: object
models.TrendPeriod:
properties:
description_display:
description: 多语言描述
type: string
end_date:
description: 结束日期
type: string
start_date:
description: 开始日期
type: string
type:
description: 时间类型
type: string
type: object
models.TrendSummary:
properties:
expired_count:
description: 期间过期授权数
type: integer
growth_rate:
description: 增长率(百分比)
type: number
new_count:
description: 期间新增授权数
type: integer
total_count:
description: 期间总授权数
type: integer
type: object
info:
contact:
email: support@swagger.io
@@ -1724,6 +1838,117 @@ paths:
summary: 锁定/解锁授权码
tags:
- 授权码管理
/api/v1/dashboard/authorization-trend:
get:
consumes:
- application/json
description: 根据时间类型(本周/本月/自定义)获取授权趋势统计数据
parameters:
- description: 时间类型
enum:
- week
- month
- custom
in: query
name: type
required: true
type: string
- description: 开始日期(YYYY-MM-DD格式,当type为custom时必填)
in: query
name: start_date
type: string
- description: 结束日期(YYYY-MM-DD格式,当type为custom时必填)
in: query
name: end_date
type: string
produces:
- application/json
responses:
"200":
description: 授权趋势数据
schema:
allOf:
- $ref: '#/definitions/models.APIResponse'
- properties:
data:
$ref: '#/definitions/models.DashboardAuthorizationTrendResponse'
type: object
"400":
description: 请求参数错误
schema:
$ref: '#/definitions/models.ErrorResponse'
"401":
description: 未认证
schema:
$ref: '#/definitions/models.ErrorResponse'
"403":
description: 权限不足
schema:
$ref: '#/definitions/models.ErrorResponse'
"500":
description: 服务器内部错误
schema:
$ref: '#/definitions/models.ErrorResponse'
security:
- BearerAuth: []
summary: 获取授权趋势数据
tags:
- 仪表盘
/api/v1/dashboard/recent-authorizations:
get:
consumes:
- application/json
description: 获取最近创建的授权码列表,支持按客户ID和状态筛选
parameters:
- description: 返回数量限制(默认20,最大100)
in: query
name: limit
type: integer
- description: 客户ID筛选
in: query
name: customer_id
type: string
- description: 状态筛选
enum:
- normal
- locked
- expired
in: query
name: status
type: string
produces:
- application/json
responses:
"200":
description: 最近授权列表
schema:
allOf:
- $ref: '#/definitions/models.APIResponse'
- properties:
data:
$ref: '#/definitions/models.DashboardRecentAuthorizationsResponse'
type: object
"400":
description: 请求参数错误
schema:
$ref: '#/definitions/models.ErrorResponse'
"401":
description: 未认证
schema:
$ref: '#/definitions/models.ErrorResponse'
"403":
description: 权限不足
schema:
$ref: '#/definitions/models.ErrorResponse'
"500":
description: 服务器内部错误
schema:
$ref: '#/definitions/models.ErrorResponse'
security:
- BearerAuth: []
summary: 获取最近授权列表
tags:
- 仪表盘
/api/v1/heartbeat:
post:
consumes:
@@ -0,0 +1,158 @@
package handlers
import (
"errors"
"net/http"
"time"
"github.com/gin-gonic/gin"
"license-manager/internal/api/middleware"
"license-manager/internal/models"
"license-manager/internal/service"
"license-manager/pkg/i18n"
)
type DashboardHandler struct {
dashboardService service.DashboardService
}
// NewDashboardHandler 创建仪表盘处理器
func NewDashboardHandler(dashboardService service.DashboardService) *DashboardHandler {
return &DashboardHandler{
dashboardService: dashboardService,
}
}
// GetAuthorizationTrend 获取授权趋势数据
// @Summary 获取授权趋势数据
// @Description 根据时间类型(本周/本月/自定义)获取授权趋势统计数据
// @Tags 仪表盘
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param type query string true "时间类型" Enums(week,month,custom)
// @Param start_date query string false "开始日期(YYYY-MM-DD格式,当type为custom时必填)"
// @Param end_date query string false "结束日期(YYYY-MM-DD格式,当type为custom时必填)"
// @Success 200 {object} models.APIResponse{data=models.DashboardAuthorizationTrendResponse} "授权趋势数据"
// @Failure 400 {object} models.ErrorResponse "请求参数错误"
// @Failure 401 {object} models.ErrorResponse "未认证"
// @Failure 403 {object} models.ErrorResponse "权限不足"
// @Failure 500 {object} models.ErrorResponse "服务器内部错误"
// @Router /api/v1/dashboard/authorization-trend [get]
func (h *DashboardHandler) GetAuthorizationTrend(c *gin.Context) {
// 绑定查询参数
var req models.DashboardAuthorizationTrendRequest
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)
// 调用服务层获取数据
response, err := h.dashboardService.GetAuthorizationTrend(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: response,
})
}
// GetRecentAuthorizations 获取最近授权列表
// @Summary 获取最近授权列表
// @Description 获取最近创建的授权码列表,支持按客户ID和状态筛选
// @Tags 仪表盘
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param limit query int false "返回数量限制(默认20,最大100)"
// @Param customer_id query string false "客户ID筛选"
// @Param status query string false "状态筛选" Enums(normal,locked,expired)
// @Success 200 {object} models.APIResponse{data=models.DashboardRecentAuthorizationsResponse} "最近授权列表"
// @Failure 400 {object} models.ErrorResponse "请求参数错误"
// @Failure 401 {object} models.ErrorResponse "未认证"
// @Failure 403 {object} models.ErrorResponse "权限不足"
// @Failure 500 {object} models.ErrorResponse "服务器内部错误"
// @Router /api/v1/dashboard/recent-authorizations [get]
func (h *DashboardHandler) GetRecentAuthorizations(c *gin.Context) {
// 绑定查询参数
var req models.DashboardRecentAuthorizationsRequest
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)
// 调用服务层获取数据
response, err := h.dashboardService.GetRecentAuthorizations(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: response,
})
}
+7
View File
@@ -40,6 +40,7 @@ func SetupRouter() *gin.Engine {
userRepo := repository.NewUserRepository(db)
authCodeRepo := repository.NewAuthorizationCodeRepository(db)
licenseRepo := repository.NewLicenseRepository(db)
dashboardRepo := repository.NewDashboardRepository(db)
// 获取logger实例
log := logger.GetLogger()
@@ -51,6 +52,7 @@ func SetupRouter() *gin.Engine {
enumService := service.NewEnumService()
authCodeService := service.NewAuthorizationCodeService(authCodeRepo)
licenseService := service.NewLicenseService(licenseRepo, db, log)
dashboardService := service.NewDashboardService(dashboardRepo)
// 初始化处理器层
authHandler := handlers.NewAuthHandler(authService)
@@ -59,6 +61,7 @@ func SetupRouter() *gin.Engine {
enumHandler := handlers.NewEnumHandler(enumService)
authCodeHandler := handlers.NewAuthorizationCodeHandler(authCodeService)
licenseHandler := handlers.NewLicenseHandler(licenseService)
dashboardHandler := handlers.NewDashboardHandler(dashboardService)
// 健康检测接口(无需认证)
router.GET("/health", systemHandler.HealthCheck)
@@ -117,6 +120,10 @@ func SetupRouter() *gin.Engine {
// 统计分析
auth.GET("/v1/stats/overview", licenseHandler.GetStatsOverview)
// 仪表盘接口
auth.GET("/v1/dashboard/authorization-trend", dashboardHandler.GetAuthorizationTrend)
auth.GET("/v1/dashboard/recent-authorizations", dashboardHandler.GetRecentAuthorizations)
}
// 管理员接口
+72
View File
@@ -0,0 +1,72 @@
package models
import "time"
// DashboardAuthorizationTrendRequest 授权趋势查询请求
type DashboardAuthorizationTrendRequest struct {
Type string `form:"type" binding:"required,oneof=week month custom" json:"type"` // 时间类型: week/month/custom
StartDate string `form:"start_date" json:"start_date,omitempty"` // 开始日期 (YYYY-MM-DD格式,当type为custom时必填)
EndDate string `form:"end_date" json:"end_date,omitempty"` // 结束日期 (YYYY-MM-DD格式,当type为custom时必填)
Timezone string `form:"timezone" json:"timezone,omitempty"` // 时区 (如: Asia/Shanghai, UTC等,默认使用服务器本地时区)
}
// DashboardAuthorizationTrendResponse 授权趋势响应
type DashboardAuthorizationTrendResponse struct {
Period TrendPeriod `json:"period"` // 时间段信息
TrendData []TrendData `json:"trend_data"` // 趋势数据
Summary TrendSummary `json:"summary"` // 汇总信息
}
// TrendPeriod 时间段信息
type TrendPeriod struct {
Type string `json:"type"` // 时间类型
StartDate string `json:"start_date"` // 开始日期
EndDate string `json:"end_date"` // 结束日期
DescriptionDisplay string `json:"description_display"` // 多语言描述
}
// TrendData 单日趋势数据
type TrendData struct {
Date string `json:"date"` // 日期 (YYYY-MM-DD)
TotalAuthorizations int64 `json:"total_authorizations"` // 当日授权总数
NewAuthorizations int64 `json:"new_authorizations"` // 当日新增授权数
ExpiredAuthorizations int64 `json:"expired_authorizations"` // 当日过期授权数
}
// TrendSummary 趋势汇总信息
type TrendSummary struct {
TotalCount int64 `json:"total_count"` // 期间总授权数
NewCount int64 `json:"new_count"` // 期间新增授权数
ExpiredCount int64 `json:"expired_count"` // 期间过期授权数
GrowthRate float64 `json:"growth_rate"` // 增长率(百分比)
}
// DashboardRecentAuthorizationsRequest 最近授权列表查询请求
type DashboardRecentAuthorizationsRequest struct {
Limit int `form:"limit" json:"limit,omitempty"` // 返回数量限制(默认20,最大100)
CustomerID string `form:"customer_id" json:"customer_id,omitempty"` // 客户ID筛选(可选)
Status string `form:"status" json:"status,omitempty"` // 状态筛选 (normal/locked/expired) 可选
}
// DashboardRecentAuthorizationsResponse 最近授权列表响应
type DashboardRecentAuthorizationsResponse struct {
List []RecentAuthorization `json:"list"` // 授权列表
Total int64 `json:"total"` // 符合条件的授权总数量
}
// RecentAuthorization 最近授权信息
type RecentAuthorization struct {
ID string `json:"id"` // 授权码ID
Code string `json:"code"` // 授权码
CustomerID string `json:"customer_id"` // 客户ID
CustomerName string `json:"customer_name"` // 客户名称
Description string `json:"description"` // 授权描述
Status string `json:"status"` // 状态 (normal/locked/expired)
StatusDisplay string `json:"status_display"` // 状态显示文本(多语言)
StartDate time.Time `json:"start_date"` // 生效时间
EndDate time.Time `json:"end_date"` // 到期时间
MaxActivations int `json:"max_activations"` // 最大激活数量
CurrentActivations int `json:"current_activations"` // 当前激活数量
CreatedAt time.Time `json:"created_at"` // 创建时间
UpdatedAt time.Time `json:"updated_at"` // 更新时间
}
@@ -0,0 +1,206 @@
package repository
import (
"context"
"time"
"license-manager/internal/models"
pkgcontext "license-manager/pkg/context"
"license-manager/pkg/i18n"
"gorm.io/gorm"
)
type dashboardRepository struct {
db *gorm.DB
}
// NewDashboardRepository 创建仪表盘数据访问层实例
func NewDashboardRepository(db *gorm.DB) DashboardRepository {
return &dashboardRepository{
db: db,
}
}
// GetAuthorizationTrendData 获取授权趋势数据
func (r *dashboardRepository) GetAuthorizationTrendData(ctx context.Context, startDate, endDate time.Time) ([]models.TrendData, error) {
lang := pkgcontext.GetLanguageFromContext(ctx)
var trendData []models.TrendData
// 生成日期范围
dates := make([]time.Time, 0)
for d := startDate; !d.After(endDate); d = d.AddDate(0, 0, 1) {
dates = append(dates, d)
}
// 查询每日数据
for _, date := range dates {
dayStart := time.Date(date.Year(), date.Month(), date.Day(), 0, 0, 0, 0, date.Location())
dayEnd := dayStart.Add(24*time.Hour - time.Nanosecond)
// 统计当日的授权数据
var totalCount, newCount, expiredCount int64
// 当日总授权数(截至当日24:00的累计有效授权数)
err := r.db.WithContext(ctx).Model(&models.AuthorizationCode{}).
Where("created_at <= ?", dayEnd).
Count(&totalCount).Error
if err != nil {
return nil, i18n.NewI18nError("900004", lang, err.Error())
}
// 当日新增授权数
err = r.db.WithContext(ctx).Model(&models.AuthorizationCode{}).
Where("created_at >= ? AND created_at <= ?", dayStart, dayEnd).
Count(&newCount).Error
if err != nil {
return nil, i18n.NewI18nError("900004", lang, err.Error())
}
// 当日过期授权数
err = r.db.WithContext(ctx).Model(&models.AuthorizationCode{}).
Where("end_date >= ? AND end_date < ?", dayStart, dayEnd).
Count(&expiredCount).Error
if err != nil {
return nil, i18n.NewI18nError("900004", lang, err.Error())
}
trendData = append(trendData, models.TrendData{
Date: date.Format("2006-01-02"),
TotalAuthorizations: totalCount,
NewAuthorizations: newCount,
ExpiredAuthorizations: expiredCount,
})
}
return trendData, nil
}
// GetRecentAuthorizations 获取最近授权列表
func (r *dashboardRepository) GetRecentAuthorizations(ctx context.Context, req *models.DashboardRecentAuthorizationsRequest) (*models.DashboardRecentAuthorizationsResponse, error) {
lang := pkgcontext.GetLanguageFromContext(ctx)
// 设置默认值
limit := req.Limit
if limit <= 0 {
limit = 20
}
if limit > 100 {
limit = 100
}
// 构建查询
query := r.db.WithContext(ctx).Table("authorization_codes ac").
Select(`ac.id, ac.code, ac.customer_id, c.customer_name as customer_name,
ac.description, ac.start_date, ac.end_date, ac.max_activations,
COALESCE(l.active_count, 0) as current_activations,
ac.created_at, ac.updated_at, ac.is_locked`).
Joins("LEFT JOIN customers c ON ac.customer_id = c.id").
Joins(`LEFT JOIN (
SELECT authorization_code_id, COUNT(*) as active_count
FROM licenses
WHERE status = 'active' AND deleted_at IS NULL
GROUP BY authorization_code_id
) l ON ac.id = l.authorization_code_id`).
Where("1=1").
Order("ac.created_at DESC")
// 添加筛选条件
if req.CustomerID != "" {
query = query.Where("ac.customer_id = ?", req.CustomerID)
}
// 状态筛选(虚字段)
now := time.Now()
switch req.Status {
case "normal":
query = query.Where("ac.is_locked = ? AND ac.end_date > ?", false, now)
case "locked":
query = query.Where("ac.is_locked = ?", true)
case "expired":
query = query.Where("ac.end_date <= ?", now)
}
// 查询总数 - 使用简化查询避免JOIN复杂性
var total int64
countQuery := r.db.WithContext(ctx).Model(&models.AuthorizationCode{}).Where("1=1")
// 添加相同的筛选条件
if req.CustomerID != "" {
countQuery = countQuery.Where("customer_id = ?", req.CustomerID)
}
// 状态筛选
switch req.Status {
case "normal":
countQuery = countQuery.Where("is_locked = ? AND end_date > ?", false, now)
case "locked":
countQuery = countQuery.Where("is_locked = ?", true)
case "expired":
countQuery = countQuery.Where("end_date <= ?", now)
}
err := countQuery.Count(&total).Error
if err != nil {
return nil, i18n.NewI18nError("900004", lang, err.Error())
}
// 查询列表数据
type queryResult struct {
ID string `gorm:"column:id"`
Code string `gorm:"column:code"`
CustomerID string `gorm:"column:customer_id"`
CustomerName string `gorm:"column:customer_name"`
Description string `gorm:"column:description"`
StartDate time.Time `gorm:"column:start_date"`
EndDate time.Time `gorm:"column:end_date"`
MaxActivations int `gorm:"column:max_activations"`
CurrentActivations int `gorm:"column:current_activations"`
CreatedAt time.Time `gorm:"column:created_at"`
UpdatedAt time.Time `gorm:"column:updated_at"`
IsLocked bool `gorm:"column:is_locked"`
}
var results []queryResult
err = query.Limit(limit).Find(&results).Error
if err != nil {
return nil, i18n.NewI18nError("900004", lang, err.Error())
}
// 转换为响应格式
authorizations := make([]models.RecentAuthorization, 0, len(results))
for _, result := range results {
// 计算状态
status := "normal"
statusDisplay := i18n.GetEnumMessage("authorization_code_status", "normal", lang)
if result.IsLocked {
status = "locked"
statusDisplay = i18n.GetEnumMessage("authorization_code_status", "locked", lang)
} else if result.EndDate.Before(now) {
status = "expired"
statusDisplay = i18n.GetEnumMessage("authorization_code_status", "expired", lang)
}
authorizations = append(authorizations, models.RecentAuthorization{
ID: result.ID,
Code: result.Code,
CustomerID: result.CustomerID,
CustomerName: result.CustomerName,
Description: result.Description,
Status: status,
StatusDisplay: statusDisplay,
StartDate: result.StartDate,
EndDate: result.EndDate,
MaxActivations: result.MaxActivations,
CurrentActivations: result.CurrentActivations,
CreatedAt: result.CreatedAt,
UpdatedAt: result.UpdatedAt,
})
}
return &models.DashboardRecentAuthorizationsResponse{
List: authorizations,
Total: total,
}, nil
}
+10
View File
@@ -2,6 +2,7 @@ package repository
import (
"context"
"time"
"license-manager/internal/models"
)
@@ -99,4 +100,13 @@ type LicenseRepository interface {
// GetActiveLicenseCount 获取指定授权码的激活许可证数量
GetActiveLicenseCount(ctx context.Context, authCodeID string) (int64, error)
}
// DashboardRepository 仪表盘数据访问接口
type DashboardRepository interface {
// GetAuthorizationTrendData 获取授权趋势数据
GetAuthorizationTrendData(ctx context.Context, startDate, endDate time.Time) ([]models.TrendData, error)
// GetRecentAuthorizations 获取最近授权列表
GetRecentAuthorizations(ctx context.Context, req *models.DashboardRecentAuthorizationsRequest) (*models.DashboardRecentAuthorizationsResponse, error)
}
@@ -0,0 +1,174 @@
package service
import (
"context"
"fmt"
"time"
"license-manager/internal/models"
"license-manager/internal/repository"
pkgcontext "license-manager/pkg/context"
"license-manager/pkg/i18n"
)
type dashboardService struct {
dashboardRepo repository.DashboardRepository
}
// NewDashboardService 创建仪表盘服务实例
func NewDashboardService(dashboardRepo repository.DashboardRepository) DashboardService {
return &dashboardService{
dashboardRepo: dashboardRepo,
}
}
// GetAuthorizationTrend 获取授权趋势数据
func (s *dashboardService) GetAuthorizationTrend(ctx context.Context, req *models.DashboardAuthorizationTrendRequest) (*models.DashboardAuthorizationTrendResponse, error) {
lang := pkgcontext.GetLanguageFromContext(ctx)
// 业务逻辑:参数验证
if req == nil {
return nil, i18n.NewI18nError("900001", lang)
}
var startDate, endDate time.Time
var descriptionDisplay string
// 获取时区,默认使用本地时区
loc := time.Local
if req.Timezone != "" {
var err error
loc, err = time.LoadLocation(req.Timezone)
if err != nil {
// 时区解析失败,使用本地时区
loc = time.Local
}
}
now := time.Now().In(loc)
// 根据类型计算时间范围
switch req.Type {
case "week":
// 本周:从周一到周日
weekday := int(now.Weekday())
if weekday == 0 { // Sunday
weekday = 7
}
startDate = now.AddDate(0, 0, -(weekday-1))
startDate = time.Date(startDate.Year(), startDate.Month(), startDate.Day(), 0, 0, 0, 0, loc)
endDate = startDate.AddDate(0, 0, 6)
endDate = time.Date(endDate.Year(), endDate.Month(), endDate.Day(), 23, 59, 59, 999999999, loc)
descriptionDisplay = i18n.GetEnumMessage("dashboard_period", "week", lang)
case "month":
// 本月:从1号到月末
startDate = time.Date(now.Year(), now.Month(), 1, 0, 0, 0, 0, loc)
endDate = startDate.AddDate(0, 1, -1)
endDate = time.Date(endDate.Year(), endDate.Month(), endDate.Day(), 23, 59, 59, 999999999, loc)
descriptionDisplay = i18n.GetEnumMessage("dashboard_period", "month", lang)
case "custom":
// 自定义时间范围
if req.StartDate == "" || req.EndDate == "" {
return nil, i18n.NewI18nError("400002", lang) // 开始日期和结束日期都是必填的
}
var err error
startDate, err = time.ParseInLocation("2006-01-02", req.StartDate, loc)
if err != nil {
return nil, i18n.NewI18nError("400002", lang) // 开始日期格式错误
}
endDate, err = time.ParseInLocation("2006-01-02", req.EndDate, loc)
if err != nil {
return nil, i18n.NewI18nError("400003", lang) // 结束日期格式错误
}
// 验证时间范围
if startDate.After(endDate) {
return nil, i18n.NewI18nError("400005", lang) // 开始日期不能晚于结束日期
}
// 验证时间跨度不超过一年
if endDate.Sub(startDate) > 365*24*time.Hour {
return nil, i18n.NewI18nError("400004", lang) // 时间范围超过限制
}
// 设置为全天
startDate = time.Date(startDate.Year(), startDate.Month(), startDate.Day(), 0, 0, 0, 0, startDate.Location())
endDate = time.Date(endDate.Year(), endDate.Month(), endDate.Day(), 23, 59, 59, 999999999, endDate.Location())
descriptionDisplay = fmt.Sprintf("%s - %s", req.StartDate, req.EndDate)
default:
return nil, i18n.NewI18nError("400001", lang) // 时间类型参数错误
}
// 获取趋势数据
trendData, err := s.dashboardRepo.GetAuthorizationTrendData(ctx, startDate, endDate)
if err != nil {
return nil, err
}
// 计算汇总信息
var totalCount, newCount, expiredCount int64
for _, data := range trendData {
newCount += data.NewAuthorizations
expiredCount += data.ExpiredAuthorizations
}
// 获取期间结束时的总授权数
if len(trendData) > 0 {
totalCount = trendData[len(trendData)-1].TotalAuthorizations
}
// 计算增长率(本期新增相对于期初的百分比)
var growthRate float64
if totalCount > 0 && newCount > 0 {
periodStartCount := totalCount - newCount
if periodStartCount > 0 {
growthRate = float64(newCount) / float64(periodStartCount) * 100
} else {
growthRate = 100.0 // 如果期初为0,则增长率为100%
}
}
return &models.DashboardAuthorizationTrendResponse{
Period: models.TrendPeriod{
Type: req.Type,
StartDate: startDate.Format("2006-01-02"),
EndDate: endDate.Format("2006-01-02"),
DescriptionDisplay: descriptionDisplay,
},
TrendData: trendData,
Summary: models.TrendSummary{
TotalCount: totalCount,
NewCount: newCount,
ExpiredCount: expiredCount,
GrowthRate: growthRate,
},
}, nil
}
// GetRecentAuthorizations 获取最近授权列表
func (s *dashboardService) GetRecentAuthorizations(ctx context.Context, req *models.DashboardRecentAuthorizationsRequest) (*models.DashboardRecentAuthorizationsResponse, error) {
lang := pkgcontext.GetLanguageFromContext(ctx)
// 业务逻辑:参数验证
if req == nil {
return nil, i18n.NewI18nError("900001", lang)
}
// 验证limit参数
if req.Limit < 0 || req.Limit > 100 {
return nil, i18n.NewI18nError("400006", lang) // 数量限制参数错误
}
// 验证status参数
if req.Status != "" && req.Status != "normal" && req.Status != "locked" && req.Status != "expired" {
return nil, i18n.NewI18nError("400001", lang) // 状态参数错误
}
// 调用repository获取数据
return s.dashboardRepo.GetRecentAuthorizations(ctx, req)
}
+9
View File
@@ -60,3 +60,12 @@ type LicenseService interface {
// 统计接口
GetStatsOverview(ctx context.Context) (*models.StatsOverviewResponse, error)
}
// DashboardService 仪表盘服务接口
type DashboardService interface {
// 获取授权趋势数据
GetAuthorizationTrend(ctx context.Context, req *models.DashboardAuthorizationTrendRequest) (*models.DashboardAuthorizationTrendResponse, error)
// 获取最近授权列表
GetRecentAuthorizations(ctx context.Context, req *models.DashboardRecentAuthorizationsRequest) (*models.DashboardRecentAuthorizationsResponse, error)
}