feat: 添加取消订单接口,支持取消未支付订单并返回相应错误信息,更新相关文档和国际化错误码

This commit is contained in:
wood911010 committed 2026-01-22 01:22:52 +08:00
1 parent 874820fabd
commit afa9e8204f
19 files changed
+1688

No files matched your search

+1
View File
@@ -86,6 +86,7 @@ errors:
cu_order:
"601001": "Order does not exist"
"601005": "License quantity exceeds limit"
"601006": "Order has been paid and cannot be cancelled"
# System general (90xxxx)
system:
+1
View File
@@ -86,6 +86,7 @@ errors:
cu_order:
"601001": "注文が存在しません"
"601005": "ライセンス数量が制限を超えています"
"601006": "支払い済みの注文はキャンセルできません"
# システム共通 (90xxxx)
system:
+1
View File
@@ -86,6 +86,7 @@ errors:
cu_order:
"601001": "订单不存在"
"601005": "许可数量超出限制"
"601006": "订单已支付,无法取消"
# 设备管理模块 (62xxxx)
cu_device:
@@ -0,0 +1,95 @@
# 取消订单接口
## 背景
当前客户侧订单支持创建、查询与列表查看(`/api/cu/orders`),但缺少“取消未支付订单”的能力;用户误下单/重复下单时只能等待支付超时,体验较差。
## 目标
- 客户用户可取消“未支付(`pending`)”订单。
- 已支付(`paid`)订单不允许取消,返回明确的业务错误码与错误信息。
- 返回结构与现有接口保持一致:成功返回 `models.APIResponse`,失败返回 `models.ErrorResponse`。
## 参考接口(已实现)
为保持风格一致,本接口沿用以下已实现接口的路径风格、鉴权方式与返回结构:
- `POST /api/cu/orders`:创建订单
- `GET /api/cu/orders/{order_id}`:订单详情
- `GET /api/cu/orders`:订单列表(含 `status` 筛选)
## 业务规则(与当前项目对齐)
> 说明:当前 `cu_orders` 表包含 `deleted_at`(GORM 软删除),但订单列表接口按 `status` 做筛选;为了支持“已取消订单可追溯/可筛选”,本设计采用**状态取消**(`status=cancelled`)而不是直接删除记录。
- 允许取消:订单属于当前登录客户用户,且订单状态为 `pending`。
- 不允许取消:订单状态为 `paid`(包含试用/免费单,当前实现中免费单直接为 `paid`)。
- 幂等性:重复取消同一订单(已为 `cancelled`)应返回成功(不重复变更),避免前端重复点击导致报错。
## 接口定义
### 请求
```http
PUT /api/cu/orders/{order_id}/cancel
```
#### Headers
- `Authorization: Bearer <token>`(必填)
- `Accept-Language`(可选)或 query `?lang=zh-CN`(可选):与现有多语言中间件一致
#### Path 参数
- `order_id`:订单ID(必填,字符串,UUID)
#### Body
无
### 响应
#### 成功(200)
返回取消后的订单信息(与 `GET /api/cu/orders/{order_id}` 一致):
```json
{
"code": "000000",
"message": "成功",
"data": {
"id": "uuid-string",
"order_no": "ORD202601210001",
"customer_id": "customer-uuid",
"cu_user_id": "cu-user-uuid",
"package_id": "basic",
"package_name": "基础版",
"license_count": 10,
"unit_price": 300,
"discount_rate": 1,
"total_amount": 3000,
"status": "cancelled",
"authorization_code": null,
"expired_at": null,
"created_at": "2026-01-21T10:00:00Z",
"updated_at": "2026-01-21T10:05:00Z"
},
"timestamp": "2026-01-21T10:05:00Z"
}
```
#### 失败
失败时返回 `models.ErrorResponse`:
```json
{
"code": "601006",
"message": "订单已支付,无法取消",
"timestamp": "2026-01-21T10:05:00Z"
}
```
## 状态约定
- `pending`:待支付(允许取消)
- `paid`:已支付(不允许取消)
- `cancelled`:已取消(重复取消返回成功)
## 错误码(建议新增/复用)
- `100004`:缺少认证信息(未登录)
- `100005`:权限不足(只能取消自己的订单)
- `601001`:订单不存在
- `601006`:订单已支付,无法取消(建议新增到 `backend/configs/i18n/errors/*` 的 `cu_order` 下)
- `900001`:请求参数无效(如 `order_id` 为空)
- `900004`:服务器内部错误
## 示例
- 取消订单:
- `PUT /api/cu/orders/7b8c2c2e-9f2f-4f0b-9f31-0c8d3f6a9c11/cancel`
@@ -0,0 +1,123 @@
# 用户授权管理相关接口
> 分组建议:放到“用户端授权码管理”(Customer User / CU)分组,统一前缀:`/api/cu`
## 通用约定
- 鉴权:`Authorization: Bearer <token>`(必填)
- 多语言:`Accept-Language` header 或 `?lang=zh-CN`(可选)
- 成功返回:`models.APIResponse`
- 失败返回:`models.ErrorResponse`
## 参考接口(已实现)
为保持接口风格一致(路径、鉴权、返回结构),本设计参考并沿用以下已实现接口:
- `POST /api/cu/authorization-codes/{codeId}/share`:用户分享授权码
- `POST /api/cu/authorization-codes/product-activation-code`:获取产品激活码
- `GET /api/v1/authorization-codes`:授权码列表(管理端,分页结构可复用)
## 状态约定
授权码状态沿用系统枚举:
- `normal`:有效(当前时间未超过 `end_date` 且未锁定)
- `expired`:已过期(当前时间超过 `end_date`)
- `locked`:已锁定(`is_locked=true`;是否过期由 `end_date` 决定)
> 前端展示“有效/已过期”时,可将 `locked` 作为“有效但锁定”或单独状态展示(以 UI 为准)。
## 接口定义
### 1) 获取用户授权码列表(新增)
```http
GET /api/cu/authorization-codes
```
#### Query 参数
- `page`:页码,默认 `1`
- `page_size`:每页数量,默认 `10`,最大 `100`
- `status`:状态筛选(可选)`normal` / `expired` / `locked`
- `search`:授权码模糊匹配(可选,匹配 `code`)
#### 响应(200)
```json
{
"code": "000000",
"message": "成功",
"data": {
"list": [
{
"id": "uuid-string",
"code": "LIC-COMP001-A7B9X2-C8F4",
"status": "normal",
"status_display": "正常",
"max_activations": 10,
"current_activations": 7,
"remaining_activations": 3,
"created_at": "2026-01-21T10:00:00Z",
"end_date": "2026-12-31T23:59:59Z"
}
],
"total": 1,
"page": 1,
"page_size": 10,
"total_pages": 1
},
"timestamp": "2026-01-21T10:05:00Z"
}
```
#### 字段说明
- `max_activations`:设备总数(可绑定/可激活上限)
- `current_activations`:已绑定设备数(已激活许可证数量)
- `remaining_activations`:剩余可绑定设备数(建议字段;也可由前端计算 `max_activations - current_activations`)
- `end_date`:到期时间(授权结束时间)
#### 错误码
- `100004`:缺少认证信息
- `900001`:请求参数无效
- `900004`:服务器内部错误
---
### 2) 用户授权信息统计(新增)
```http
GET /api/cu/authorization-codes/summary
```
#### 响应(200)
```json
{
"code": "000000",
"message": "成功",
"data": {
"total_count": 12,
"expired_count": 3,
"valid_count": 9,
"valid_max_activations_sum": 150
},
"timestamp": "2026-01-21T10:05:00Z"
}
```
#### 字段说明
- `total_count`:总授权码数量
- `expired_count`:已过期授权码数量
- `valid_count`:有效授权码数量(建议口径:`status=normal`,是否包含 `locked` 由产品定义;若包含请在实现中统一)
- `valid_max_activations_sum`:有效授权码的 `max_activations` 之和
#### 错误码
- `100004`:缺少认证信息
- `900004`:服务器内部错误
---
### 3) 用户分享授权码(已实现)
```http
POST /api/cu/authorization-codes/{codeId}/share
```
> 说明:接口实现与 Swagger 注释已存在于 `backend/internal/api/handlers/cu_authorization_handler.go`。
---
### 4) 获取产品激活码(已实现)
```http
POST /api/cu/authorization-codes/product-activation-code
```
> 说明:接口实现与 Swagger 注释已存在于 `backend/internal/api/handlers/cu_authorization_handler.go`。
@@ -0,0 +1,295 @@
# 发票模块设计
实体关系
订单:发票 1:1
## 表结构设计
发票申请号
订单ID
客户ID
发票金额
开票完成时间
开票状态 待处理 已开票 已驳回
发票类型 个人,企业,增值税专用发票
发票抬头
纳税人识别号
开票内容
收票邮箱
备注信息
申请人ID
发票文件url
上传时间
上传人ID
驳回原因
驳回时间
驳回人ID
建议修改内容
创建时间
更新时间
id
## 接口
### 客户申请发票
请求
订单ID
发票类型
收票邮箱
发票抬头
纳税人识别号
开票内容
备注信息
### 客户发票列表
请求
search 发票号或订单号模糊匹配
开票状态
申请时间(某日)
分页
返回
发票ID
发票申请号
用户名
订单号
发票金额
开票完成时间
开票状态
申请时间
套餐类型 订单的package_name
### 客户发票详情
请求
发票ID
返回
发票表所有字段
关联订单号
### 客户发票汇总信息
返回
全部发票数
待处理发票数
已开票数
已驳回数
### 管理员发票列表
请求
search 发票号或订单号模糊匹配
开票状态
申请时间(某日)
分页
返回
发票ID
发票申请号
用户名
订单号
发票金额
开票完成时间
开票状态
申请时间
套餐类型 订单的package_name
### 管理员发票详情
返回
发票表所有字段
关联订单号
申请人电话
申请人姓名
套餐类型 订单的package_name
开票人full_name
### 管理员发票汇总信息
返回
全部发票数
待处理发票数
已开票数
已驳回数
### 管理员发票驳回
请求
发票ID
驳回原因
建议修改内容
### 管理员发票开票(支持覆盖重开)
请求
发票ID
发票文件url
开票完成时间
## 相关表结构
-- license_manager.users definition
CREATE TABLE `users` (
`id` varchar(36) NOT NULL,
`username` varchar(50) NOT NULL,
`email` varchar(255) NOT NULL,
`password_hash` varchar(255) NOT NULL,
`full_name` varchar(100) NOT NULL,
`phone` varchar(20) DEFAULT NULL,
`role` varchar(20) NOT NULL DEFAULT 'viewer',
`status` varchar(20) NOT NULL DEFAULT 'active',
`last_login_at` datetime(3) DEFAULT NULL,
`last_login_ip` varchar(45) DEFAULT NULL,
`login_attempts` bigint NOT NULL DEFAULT '0',
`locked_until` datetime(3) DEFAULT NULL,
`created_at` datetime(3) NOT NULL,
`updated_at` datetime(3) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `idx_users_username` (`username`),
UNIQUE KEY `idx_users_email` (`email`),
KEY `idx_users_role` (`role`),
KEY `idx_users_status` (`status`),
KEY `idx_users_last_login_at` (`last_login_at`),
KEY `idx_users_locked_until` (`locked_until`),
KEY `idx_users_created_at` (`created_at`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
-- license_manager.cu_users definition
CREATE TABLE `cu_users` (
`id` varchar(36) NOT NULL,
`customer_id` varchar(36) NOT NULL,
`phone` varchar(20) NOT NULL,
`phone_country_code` varchar(10) NOT NULL DEFAULT '+86',
`password` varchar(255) DEFAULT NULL,
`salt` varchar(32) DEFAULT NULL,
`user_role` varchar(20) NOT NULL DEFAULT 'member',
`real_name` varchar(100) DEFAULT NULL,
`email` varchar(255) DEFAULT NULL,
`status` varchar(20) NOT NULL DEFAULT 'active',
`phone_verified` tinyint(1) NOT NULL DEFAULT '1',
`email_verified` tinyint(1) NOT NULL DEFAULT '0',
`last_login_at` datetime(3) DEFAULT NULL,
`last_login_ip` varchar(50) DEFAULT NULL,
`login_attempts` bigint NOT NULL DEFAULT '0',
`locked_until` datetime(3) DEFAULT NULL,
`avatar_url` varchar(500) DEFAULT NULL,
`language` varchar(10) NOT NULL DEFAULT 'zh-CN',
`timezone` varchar(50) NOT NULL DEFAULT 'Asia/Shanghai',
`additional_info` json DEFAULT NULL,
`remark` text,
`created_at` datetime(3) NOT NULL,
`updated_at` datetime(3) NOT NULL,
`deleted_at` datetime(3) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `idx_cu_users_customer_id` (`customer_id`),
KEY `idx_cu_users_email` (`email`),
KEY `idx_cu_users_status` (`status`),
KEY `idx_cu_users_last_login_at` (`last_login_at`),
KEY `idx_cu_users_locked_until` (`locked_until`),
KEY `idx_cu_users_created_at` (`created_at`),
KEY `idx_cu_users_deleted_at` (`deleted_at`),
KEY `idx_cu_users_phone` (`phone`,`phone_country_code`),
KEY `idx_cu_users_customer_status` (`customer_id`,`status`),
KEY `idx_cu_users_status_locked` (`status`,`locked_until`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
-- license_manager.cu_orders definition
CREATE TABLE `cu_orders` (
`id` varchar(36) NOT NULL,
`order_no` varchar(50) NOT NULL,
`customer_id` varchar(36) NOT NULL,
`cu_user_id` varchar(36) NOT NULL,
`package_id` varchar(50) NOT NULL,
`package_name` varchar(100) NOT NULL,
`license_count` bigint NOT NULL,
`unit_price` decimal(10,2) NOT NULL,
`discount_rate` decimal(3,2) NOT NULL DEFAULT '1.00',
`total_amount` decimal(10,2) NOT NULL,
`status` varchar(20) NOT NULL DEFAULT 'paid',
`authorization_code` varchar(500) DEFAULT NULL,
`expired_at` datetime(3) DEFAULT NULL,
`created_at` datetime(3) NOT NULL,
`updated_at` datetime(3) NOT NULL,
`deleted_at` datetime(3) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `idx_cu_orders_order_no` (`order_no`),
KEY `idx_cu_orders_customer_id` (`customer_id`),
KEY `idx_cu_orders_cu_user_id` (`cu_user_id`),
KEY `idx_cu_orders_package_id` (`package_id`),
KEY `idx_cu_orders_status` (`status`),
KEY `idx_cu_orders_expired_at` (`expired_at`),
KEY `idx_cu_orders_created_at` (`created_at`),
KEY `idx_cu_orders_deleted_at` (`deleted_at`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
-- license_manager.customers definition
CREATE TABLE `customers` (
`id` varchar(36) NOT NULL DEFAULT (uuid()),
`customer_code` varchar(20) NOT NULL,
`customer_name` varchar(200) NOT NULL,
`customer_type` varchar(20) NOT NULL DEFAULT 'enterprise',
`contact_person` varchar(100) NOT NULL,
`contact_title` varchar(100) DEFAULT NULL,
`email` varchar(255) DEFAULT NULL,
`phone` varchar(20) DEFAULT NULL,
`address` text,
`company_size` varchar(20) DEFAULT NULL,
`customer_level` varchar(20) NOT NULL DEFAULT 'basic',
`status` varchar(20) NOT NULL DEFAULT 'active',
`description` text,
`created_at` datetime(3) NOT NULL,
`updated_at` datetime(3) NOT NULL,
`created_by` varchar(36) NOT NULL,
`updated_by` varchar(36) DEFAULT NULL,
`deleted_at` datetime(3) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `idx_customers_customer_code` (`customer_code`),
KEY `idx_customers_customer_name` (`customer_name`),
KEY `idx_customers_customer_type` (`customer_type`),
KEY `idx_customers_customer_level` (`customer_level`),
KEY `idx_customers_status` (`status`),
KEY `idx_customers_created_at` (`created_at`),
KEY `idx_customers_created_by` (`created_by`),
KEY `idx_customers_deleted_at` (`deleted_at`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
-- license_manager.authorization_codes definition
CREATE TABLE `authorization_codes` (
`id` varchar(36) NOT NULL,
`code` varchar(1000) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL,
`customer_id` varchar(36) NOT NULL,
`created_by` varchar(36) NOT NULL,
`software_id` varchar(50) DEFAULT NULL,
`description` text,
`start_date` datetime(3) NOT NULL,
`end_date` datetime(3) NOT NULL,
`deployment_type` varchar(20) NOT NULL DEFAULT 'standalone',
`encryption_type` varchar(20) DEFAULT 'standard',
`software_version` varchar(50) DEFAULT NULL,
`max_activations` bigint NOT NULL DEFAULT '1',
`is_locked` tinyint(1) NOT NULL DEFAULT '0',
`lock_reason` text,
`locked_at` datetime(3) DEFAULT NULL,
`locked_by` varchar(36) DEFAULT NULL,
`feature_config` json DEFAULT NULL,
`usage_limits` json DEFAULT NULL,
`custom_parameters` json DEFAULT NULL,
`created_at` datetime(3) NOT NULL,
`updated_at` datetime(3) NOT NULL,
PRIMARY KEY (`id`),
KEY `idx_authorization_codes_customer_id` (`customer_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
+279
View File
@@ -25,6 +25,97 @@ const docTemplate = `{
"host": "{{.Host}}",
"basePath": "{{.BasePath}}",
"paths": {
"/api/cu/authorization-codes": {
"get": {
"security": [
{
"BearerAuth": []
}
],
"description": "获取当前登录用户的授权码列表,支持分页、状态筛选与授权码模糊搜索",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"用户端授权码管理"
],
"summary": "获取用户授权码列表",
"parameters": [
{
"type": "integer",
"default": 1,
"description": "页码",
"name": "page",
"in": "query"
},
{
"type": "integer",
"default": 10,
"description": "每页数量",
"name": "page_size",
"in": "query"
},
{
"enum": [
"normal",
"locked",
"expired"
],
"type": "string",
"description": "状态筛选",
"name": "status",
"in": "query"
},
{
"type": "string",
"description": "授权码模糊匹配",
"name": "search",
"in": "query"
}
],
"responses": {
"200": {
"description": "成功",
"schema": {
"allOf": [
{
"$ref": "#/definitions/models.APIResponse"
},
{
"type": "object",
"properties": {
"data": {
"$ref": "#/definitions/models.CuAuthorizationCodeListResponse"
}
}
}
]
}
},
"400": {
"description": "请求参数无效",
"schema": {
"$ref": "#/definitions/models.ErrorResponse"
}
},
"401": {
"description": "未认证",
"schema": {
"$ref": "#/definitions/models.ErrorResponse"
}
},
"500": {
"description": "服务器内部错误",
"schema": {
"$ref": "#/definitions/models.ErrorResponse"
}
}
}
}
},
"/api/cu/authorization-codes/product-activation-code": {
"post": {
"security": [
@@ -106,6 +197,58 @@ const docTemplate = `{
}
}
},
"/api/cu/authorization-codes/summary": {
"get": {
"security": [
{
"BearerAuth": []
}
],
"description": "获取当前登录用户的授权码统计信息",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"用户端授权码管理"
],
"summary": "获取用户授权信息统计",
"responses": {
"200": {
"description": "成功",
"schema": {
"allOf": [
{
"$ref": "#/definitions/models.APIResponse"
},
{
"type": "object",
"properties": {
"data": {
"$ref": "#/definitions/models.CuAuthorizationCodeSummaryResponse"
}
}
}
]
}
},
"401": {
"description": "未认证",
"schema": {
"$ref": "#/definitions/models.ErrorResponse"
}
},
"500": {
"description": "服务器内部错误",
"schema": {
"$ref": "#/definitions/models.ErrorResponse"
}
}
}
}
},
"/api/cu/authorization-codes/{codeId}/share": {
"post": {
"security": [
@@ -959,6 +1102,70 @@ const docTemplate = `{
}
}
},
"/api/cu/orders/{order_id}/cancel": {
"put": {
"security": [
{
"BearerAuth": []
}
],
"description": "取消当前用户的未支付订单(pending);已支付订单不允许取消",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"客户订单管理"
],
"summary": "取消订单",
"parameters": [
{
"type": "string",
"description": "订单ID",
"name": "order_id",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "成功",
"schema": {
"allOf": [
{
"$ref": "#/definitions/models.APIResponse"
},
{
"type": "object",
"properties": {
"data": {
"$ref": "#/definitions/models.CuOrderResponse"
}
}
}
]
}
},
"400": {
"description": "Bad Request"
},
"401": {
"description": "Unauthorized"
},
"403": {
"description": "Forbidden"
},
"404": {
"description": "Not Found"
},
"409": {
"description": "Conflict"
}
}
}
},
"/api/cu/packages": {
"get": {
"description": "获取所有可用的产品套餐信息",
@@ -4766,6 +4973,78 @@ const docTemplate = `{
}
}
},
"models.CuAuthorizationCodeListItem": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"created_at": {
"type": "string"
},
"current_activations": {
"type": "integer"
},
"end_date": {
"type": "string"
},
"id": {
"type": "string"
},
"max_activations": {
"type": "integer"
},
"remaining_activations": {
"type": "integer"
},
"status": {
"type": "string"
},
"status_display": {
"type": "string"
}
}
},
"models.CuAuthorizationCodeListResponse": {
"type": "object",
"properties": {
"list": {
"type": "array",
"items": {
"$ref": "#/definitions/models.CuAuthorizationCodeListItem"
}
},
"page": {
"type": "integer"
},
"page_size": {
"type": "integer"
},
"total": {
"type": "integer"
},
"total_pages": {
"type": "integer"
}
}
},
"models.CuAuthorizationCodeSummaryResponse": {
"type": "object",
"properties": {
"expired_count": {
"type": "integer"
},
"total_count": {
"type": "integer"
},
"valid_count": {
"type": "integer"
},
"valid_max_activations_sum": {
"type": "integer"
}
}
},
"models.CuOrderCreateRequest": {
"type": "object",
"required": [
+279
View File
@@ -21,6 +21,97 @@
},
"basePath": "/",
"paths": {
"/api/cu/authorization-codes": {
"get": {
"security": [
{
"BearerAuth": []
}
],
"description": "获取当前登录用户的授权码列表,支持分页、状态筛选与授权码模糊搜索",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"用户端授权码管理"
],
"summary": "获取用户授权码列表",
"parameters": [
{
"type": "integer",
"default": 1,
"description": "页码",
"name": "page",
"in": "query"
},
{
"type": "integer",
"default": 10,
"description": "每页数量",
"name": "page_size",
"in": "query"
},
{
"enum": [
"normal",
"locked",
"expired"
],
"type": "string",
"description": "状态筛选",
"name": "status",
"in": "query"
},
{
"type": "string",
"description": "授权码模糊匹配",
"name": "search",
"in": "query"
}
],
"responses": {
"200": {
"description": "成功",
"schema": {
"allOf": [
{
"$ref": "#/definitions/models.APIResponse"
},
{
"type": "object",
"properties": {
"data": {
"$ref": "#/definitions/models.CuAuthorizationCodeListResponse"
}
}
}
]
}
},
"400": {
"description": "请求参数无效",
"schema": {
"$ref": "#/definitions/models.ErrorResponse"
}
},
"401": {
"description": "未认证",
"schema": {
"$ref": "#/definitions/models.ErrorResponse"
}
},
"500": {
"description": "服务器内部错误",
"schema": {
"$ref": "#/definitions/models.ErrorResponse"
}
}
}
}
},
"/api/cu/authorization-codes/product-activation-code": {
"post": {
"security": [
@@ -102,6 +193,58 @@
}
}
},
"/api/cu/authorization-codes/summary": {
"get": {
"security": [
{
"BearerAuth": []
}
],
"description": "获取当前登录用户的授权码统计信息",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"用户端授权码管理"
],
"summary": "获取用户授权信息统计",
"responses": {
"200": {
"description": "成功",
"schema": {
"allOf": [
{
"$ref": "#/definitions/models.APIResponse"
},
{
"type": "object",
"properties": {
"data": {
"$ref": "#/definitions/models.CuAuthorizationCodeSummaryResponse"
}
}
}
]
}
},
"401": {
"description": "未认证",
"schema": {
"$ref": "#/definitions/models.ErrorResponse"
}
},
"500": {
"description": "服务器内部错误",
"schema": {
"$ref": "#/definitions/models.ErrorResponse"
}
}
}
}
},
"/api/cu/authorization-codes/{codeId}/share": {
"post": {
"security": [
@@ -955,6 +1098,70 @@
}
}
},
"/api/cu/orders/{order_id}/cancel": {
"put": {
"security": [
{
"BearerAuth": []
}
],
"description": "取消当前用户的未支付订单(pending);已支付订单不允许取消",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"客户订单管理"
],
"summary": "取消订单",
"parameters": [
{
"type": "string",
"description": "订单ID",
"name": "order_id",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "成功",
"schema": {
"allOf": [
{
"$ref": "#/definitions/models.APIResponse"
},
{
"type": "object",
"properties": {
"data": {
"$ref": "#/definitions/models.CuOrderResponse"
}
}
}
]
}
},
"400": {
"description": "Bad Request"
},
"401": {
"description": "Unauthorized"
},
"403": {
"description": "Forbidden"
},
"404": {
"description": "Not Found"
},
"409": {
"description": "Conflict"
}
}
}
},
"/api/cu/packages": {
"get": {
"description": "获取所有可用的产品套餐信息",
@@ -4762,6 +4969,78 @@
}
}
},
"models.CuAuthorizationCodeListItem": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"created_at": {
"type": "string"
},
"current_activations": {
"type": "integer"
},
"end_date": {
"type": "string"
},
"id": {
"type": "string"
},
"max_activations": {
"type": "integer"
},
"remaining_activations": {
"type": "integer"
},
"status": {
"type": "string"
},
"status_display": {
"type": "string"
}
}
},
"models.CuAuthorizationCodeListResponse": {
"type": "object",
"properties": {
"list": {
"type": "array",
"items": {
"$ref": "#/definitions/models.CuAuthorizationCodeListItem"
}
},
"page": {
"type": "integer"
},
"page_size": {
"type": "integer"
},
"total": {
"type": "integer"
},
"total_pages": {
"type": "integer"
}
}
},
"models.CuAuthorizationCodeSummaryResponse": {
"type": "object",
"properties": {
"expired_count": {
"type": "integer"
},
"total_count": {
"type": "integer"
},
"valid_count": {
"type": "integer"
},
"valid_max_activations_sum": {
"type": "integer"
}
}
},
"models.CuOrderCreateRequest": {
"type": "object",
"required": [
+172
View File
@@ -461,6 +461,53 @@ definitions:
description: 总许可证数量
type: integer
type: object
models.CuAuthorizationCodeListItem:
properties:
code:
type: string
created_at:
type: string
current_activations:
type: integer
end_date:
type: string
id:
type: string
max_activations:
type: integer
remaining_activations:
type: integer
status:
type: string
status_display:
type: string
type: object
models.CuAuthorizationCodeListResponse:
properties:
list:
items:
$ref: '#/definitions/models.CuAuthorizationCodeListItem'
type: array
page:
type: integer
page_size:
type: integer
total:
type: integer
total_pages:
type: integer
type: object
models.CuAuthorizationCodeSummaryResponse:
properties:
expired_count:
type: integer
total_count:
type: integer
valid_count:
type: integer
valid_max_activations_sum:
type: integer
type: object
models.CuOrderCreateRequest:
properties:
license_count:
@@ -1612,6 +1659,63 @@ info:
title: License Manager API
version: "1.0"
paths:
/api/cu/authorization-codes:
get:
consumes:
- application/json
description: 获取当前登录用户的授权码列表,支持分页、状态筛选与授权码模糊搜索
parameters:
- default: 1
description: 页码
in: query
name: page
type: integer
- default: 10
description: 每页数量
in: query
name: page_size
type: integer
- description: 状态筛选
enum:
- normal
- locked
- expired
in: query
name: status
type: string
- description: 授权码模糊匹配
in: query
name: search
type: string
produces:
- application/json
responses:
"200":
description: 成功
schema:
allOf:
- $ref: '#/definitions/models.APIResponse'
- properties:
data:
$ref: '#/definitions/models.CuAuthorizationCodeListResponse'
type: object
"400":
description: 请求参数无效
schema:
$ref: '#/definitions/models.ErrorResponse'
"401":
description: 未认证
schema:
$ref: '#/definitions/models.ErrorResponse'
"500":
description: 服务器内部错误
schema:
$ref: '#/definitions/models.ErrorResponse'
security:
- BearerAuth: []
summary: 获取用户授权码列表
tags:
- 用户端授权码管理
/api/cu/authorization-codes/{codeId}/share:
post:
consumes:
@@ -1719,6 +1823,36 @@ paths:
summary: 获取产品激活码
tags:
- 用户端授权码管理
/api/cu/authorization-codes/summary:
get:
consumes:
- application/json
description: 获取当前登录用户的授权码统计信息
produces:
- application/json
responses:
"200":
description: 成功
schema:
allOf:
- $ref: '#/definitions/models.APIResponse'
- properties:
data:
$ref: '#/definitions/models.CuAuthorizationCodeSummaryResponse'
type: object
"401":
description: 未认证
schema:
$ref: '#/definitions/models.ErrorResponse'
"500":
description: 服务器内部错误
schema:
$ref: '#/definitions/models.ErrorResponse'
security:
- BearerAuth: []
summary: 获取用户授权信息统计
tags:
- 用户端授权码管理
/api/cu/devices:
get:
consumes:
@@ -2099,6 +2233,44 @@ paths:
summary: 获取订单详情
tags:
- 客户订单管理
/api/cu/orders/{order_id}/cancel:
put:
consumes:
- application/json
description: 取消当前用户的未支付订单(pending);已支付订单不允许取消
parameters:
- description: 订单ID
in: path
name: order_id
required: true
type: string
produces:
- application/json
responses:
"200":
description: 成功
schema:
allOf:
- $ref: '#/definitions/models.APIResponse'
- properties:
data:
$ref: '#/definitions/models.CuOrderResponse'
type: object
"400":
description: Bad Request
"401":
description: Unauthorized
"403":
description: Forbidden
"404":
description: Not Found
"409":
description: Conflict
security:
- BearerAuth: []
summary: 取消订单
tags:
- 客户订单管理
/api/cu/orders/calculate:
post:
consumes:
@@ -165,3 +165,114 @@ func (h *CuAuthorizationHandler) GetProductActivationCode(c *gin.Context) {
Data: data,
})
}
// GetCuAuthorizationCodes 获取用户授权码列表
// @Summary 获取用户授权码列表
// @Description 获取当前登录用户的授权码列表,支持分页、状态筛选与授权码模糊搜索
// @Tags 用户端授权码管理
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param page query int false "页码" default(1)
// @Param page_size query int false "每页数量" default(10)
// @Param status query string false "状态筛选" Enums(normal, locked, expired)
// @Param search query string false "授权码模糊匹配"
// @Success 200 {object} models.APIResponse{data=models.CuAuthorizationCodeListResponse} "成功"
// @Failure 400 {object} models.ErrorResponse "请求参数无效"
// @Failure 401 {object} models.ErrorResponse "未认证"
// @Failure 500 {object} models.ErrorResponse "服务器内部错误"
// @Router /api/cu/authorization-codes [get]
func (h *CuAuthorizationHandler) GetCuAuthorizationCodes(c *gin.Context) {
claims := c.MustGet("cu_user").(*utils.CuClaims)
var req models.CuAuthorizationCodeListRequest
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: getCurrentTimestamp(),
})
return
}
ctx := middleware.WithLanguage(c.Request.Context(), c)
data, err := h.authCodeService.GetCuAuthorizationCodeList(ctx, claims.UserID, &req)
if err != nil {
i18nErr, ok := err.(*i18n.I18nError)
if ok {
c.JSON(i18nErr.HttpCode, models.ErrorResponse{
Code: i18nErr.Code,
Message: i18nErr.Message,
Timestamp: getCurrentTimestamp(),
})
return
}
lang := middleware.GetLanguage(c)
status, errCode, message := i18n.NewI18nErrorResponse("900004", lang, err.Error())
c.JSON(status, models.ErrorResponse{
Code: errCode,
Message: message,
Timestamp: getCurrentTimestamp(),
})
return
}
lang := middleware.GetLanguage(c)
successMessage := i18n.GetErrorMessage("000000", lang)
c.JSON(http.StatusOK, models.APIResponse{
Code: "000000",
Message: successMessage,
Data: data,
Timestamp: getCurrentTimestamp(),
})
}
// GetCuAuthorizationCodeSummary 获取用户授权信息统计
// @Summary 获取用户授权信息统计
// @Description 获取当前登录用户的授权码统计信息
// @Tags 用户端授权码管理
// @Accept json
// @Produce json
// @Security BearerAuth
// @Success 200 {object} models.APIResponse{data=models.CuAuthorizationCodeSummaryResponse} "成功"
// @Failure 401 {object} models.ErrorResponse "未认证"
// @Failure 500 {object} models.ErrorResponse "服务器内部错误"
// @Router /api/cu/authorization-codes/summary [get]
func (h *CuAuthorizationHandler) GetCuAuthorizationCodeSummary(c *gin.Context) {
claims := c.MustGet("cu_user").(*utils.CuClaims)
ctx := middleware.WithLanguage(c.Request.Context(), c)
data, err := h.authCodeService.GetCuAuthorizationCodeSummary(ctx, claims.UserID)
if err != nil {
i18nErr, ok := err.(*i18n.I18nError)
if ok {
c.JSON(i18nErr.HttpCode, models.ErrorResponse{
Code: i18nErr.Code,
Message: i18nErr.Message,
Timestamp: getCurrentTimestamp(),
})
return
}
lang := middleware.GetLanguage(c)
status, errCode, message := i18n.NewI18nErrorResponse("900004", lang, err.Error())
c.JSON(status, models.ErrorResponse{
Code: errCode,
Message: message,
Timestamp: getCurrentTimestamp(),
})
return
}
lang := middleware.GetLanguage(c)
successMessage := i18n.GetErrorMessage("000000", lang)
c.JSON(http.StatusOK, models.APIResponse{
Code: "000000",
Message: successMessage,
Data: data,
Timestamp: getCurrentTimestamp(),
})
}
@@ -368,6 +368,67 @@ func (h *CuOrderHandler) GetOrder(c *gin.Context) {
})
}
// CancelOrder 取消订单
// @Summary 取消订单
// @Description 取消当前用户的未支付订单(pending);已支付订单不允许取消
// @Tags 客户订单管理
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param order_id path string true "订单ID"
// @Success 200 {object} models.APIResponse{data=models.CuOrderResponse} "成功"
// @Failure 400
// @Failure 401
// @Failure 403
// @Failure 404
// @Failure 409
// @Router /api/cu/orders/{order_id}/cancel [put]
func (h *CuOrderHandler) CancelOrder(c *gin.Context) {
orderID := c.Param("order_id")
if orderID == "" {
lang := middleware.GetLanguage(c)
status, errCode, message := i18n.NewI18nErrorResponse("900001", lang)
c.JSON(status, models.ErrorResponse{
Code: errCode,
Message: message,
Timestamp: getCurrentTimestamp(),
})
return
}
cuUserID, exists := c.Get("cu_user_id")
if !exists {
lang := middleware.GetLanguage(c)
status, errCode, message := i18n.NewI18nErrorResponse("100004", lang)
c.JSON(status, models.ErrorResponse{
Code: errCode,
Message: message,
Timestamp: getCurrentTimestamp(),
})
return
}
ctx := middleware.WithLanguage(c.Request.Context(), c)
order, err := h.cuOrderService.CancelOrder(ctx, orderID, cuUserID.(string))
if err != nil {
c.JSON(err.(*i18n.I18nError).HttpCode, models.ErrorResponse{
Code: err.(*i18n.I18nError).Code,
Message: err.(*i18n.I18nError).Message,
Timestamp: getCurrentTimestamp(),
})
return
}
lang := middleware.GetLanguage(c)
successMsg := i18n.GetI18nErrorMessage("000000", lang)
c.JSON(http.StatusOK, models.APIResponse{
Code: "000000",
Message: successMsg,
Data: order.ToResponse(),
Timestamp: getCurrentTimestamp(),
})
}
// GetUserOrders 获取用户订单列表
// @Summary 获取用户订单列表
// @Description 获取当前用户的订单列表,支持分页
+3
View File
@@ -217,6 +217,7 @@ func SetupRouter() *gin.Engine {
cuAuth.POST("/orders/calculate", cuOrderHandler.CalculatePrice)
cuAuth.POST("/orders", cuOrderHandler.CreateOrder)
cuAuth.GET("/orders/:order_id", cuOrderHandler.GetOrder)
cuAuth.PUT("/orders/:order_id/cancel", cuOrderHandler.CancelOrder)
cuAuth.GET("/orders", cuOrderHandler.GetUserOrders)
cuAuth.GET("/orders/summary", cuOrderHandler.GetOrderSummary)
@@ -227,6 +228,8 @@ func SetupRouter() *gin.Engine {
// 授权码管理
cuAuth.POST("/authorization-codes/:codeId/share", cuAuthorizationHandler.ShareAuthorizationCode)
cuAuth.POST("/authorization-codes/product-activation-code", cuAuthorizationHandler.GetProductActivationCode)
cuAuth.GET("/authorization-codes", cuAuthorizationHandler.GetCuAuthorizationCodes)
cuAuth.GET("/authorization-codes/summary", cuAuthorizationHandler.GetCuAuthorizationCodeSummary)
// 设备管理
cuAuth.GET("/devices", cuDeviceHandler.GetDevices)
@@ -0,0 +1,39 @@
package models
// CuAuthorizationCodeListRequest 用户端授权码列表查询请求
type CuAuthorizationCodeListRequest struct {
Page int `form:"page" binding:"omitempty,min=1"` // 页码,默认1
PageSize int `form:"page_size" binding:"omitempty,min=1,max=100"` // 每页数量,默认10,最大100
Status string `form:"status" binding:"omitempty,oneof=normal locked expired"`
Search string `form:"search" binding:"omitempty"` // 授权码模糊匹配(code LIKE %search%)
}
// CuAuthorizationCodeListItem 用户端授权码列表项
type CuAuthorizationCodeListItem struct {
ID string `json:"id"`
Code string `json:"code"`
Status string `json:"status"`
StatusDisplay string `json:"status_display,omitempty"`
MaxActivations int `json:"max_activations"`
CurrentActivations int `json:"current_activations"`
RemainingActivations int `json:"remaining_activations"`
CreatedAt string `json:"created_at"`
EndDate string `json:"end_date"`
}
// CuAuthorizationCodeListResponse 用户端授权码列表响应
type CuAuthorizationCodeListResponse struct {
List []CuAuthorizationCodeListItem `json:"list"`
Total int64 `json:"total"`
Page int `json:"page"`
PageSize int `json:"page_size"`
TotalPages int `json:"total_pages"`
}
// CuAuthorizationCodeSummaryResponse 用户端授权信息统计响应
type CuAuthorizationCodeSummaryResponse struct {
TotalCount int64 `json:"total_count"`
ExpiredCount int64 `json:"expired_count"`
ValidCount int64 `json:"valid_count"`
ValidMaxActivationsSum int64 `json:"valid_max_activations_sum"`
}
@@ -167,6 +167,137 @@ func (r *authorizationCodeRepository) GetAuthorizationCodeList(ctx context.Conte
}, nil
}
// GetCuAuthorizationCodeList 用户端:查询用户授权码列表
func (r *authorizationCodeRepository) GetCuAuthorizationCodeList(ctx context.Context, cuUserID string, req *models.CuAuthorizationCodeListRequest) (*models.CuAuthorizationCodeListResponse, error) {
page := 1
pageSize := 10
status := ""
search := ""
if req != nil {
if req.Page > 0 {
page = req.Page
}
if req.PageSize > 0 {
pageSize = req.PageSize
}
if pageSize > 100 {
pageSize = 100
}
status = req.Status
search = req.Search
}
query := r.db.WithContext(ctx).Table("authorization_codes ac").
Select(`ac.id, ac.code, ac.created_at, ac.end_date, ac.max_activations,
COALESCE(l.active_count, 0) AS current_activations,
CASE
WHEN ac.end_date < NOW() THEN 'expired'
WHEN ac.is_locked = true THEN 'locked'
WHEN ac.start_date <= NOW() AND ac.end_date >= NOW() THEN 'normal'
ELSE 'expired'
END AS status`).
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("ac.created_by = ?", cuUserID)
if status != "" {
switch status {
case "locked":
query = query.Where("ac.end_date >= NOW() AND ac.is_locked = true")
case "expired":
query = query.Where("ac.end_date < NOW()")
case "normal":
query = query.Where("ac.end_date >= NOW() AND ac.is_locked = false")
}
}
if search != "" {
like := "%" + search + "%"
query = query.Where("ac.code LIKE ?", like)
}
var total int64
if err := query.Count(&total).Error; err != nil {
return nil, err
}
offset := (page - 1) * pageSize
var results []struct {
ID string `json:"id"`
Code string `json:"code"`
CreatedAt time.Time `json:"created_at"`
EndDate time.Time `json:"end_date"`
MaxActivations int `json:"max_activations"`
CurrentActivations int `json:"current_activations" gorm:"column:current_activations"`
Status string `json:"status"`
}
if err := query.Order("ac.created_at DESC").Limit(pageSize).Offset(offset).Scan(&results).Error; err != nil {
return nil, err
}
list := make([]models.CuAuthorizationCodeListItem, len(results))
for i, item := range results {
remaining := item.MaxActivations - item.CurrentActivations
if remaining < 0 {
remaining = 0
}
list[i] = models.CuAuthorizationCodeListItem{
ID: item.ID,
Code: item.Code,
Status: item.Status,
MaxActivations: item.MaxActivations,
CurrentActivations: item.CurrentActivations,
RemainingActivations: remaining,
CreatedAt: item.CreatedAt.Format(time.RFC3339),
EndDate: item.EndDate.Format(time.RFC3339),
}
}
totalPages := int((total + int64(pageSize) - 1) / int64(pageSize))
return &models.CuAuthorizationCodeListResponse{
List: list,
Total: total,
Page: page,
PageSize: pageSize,
TotalPages: totalPages,
}, nil
}
// GetCuAuthorizationCodeSummary 用户端:授权信息统计
func (r *authorizationCodeRepository) GetCuAuthorizationCodeSummary(ctx context.Context, cuUserID string) (*models.CuAuthorizationCodeSummaryResponse, error) {
var result struct {
TotalCount int64 `gorm:"column:total_count"`
ExpiredCount int64 `gorm:"column:expired_count"`
ValidCount int64 `gorm:"column:valid_count"`
ValidMaxActivationsSum int64 `gorm:"column:valid_max_activations_sum"`
}
err := r.db.WithContext(ctx).Table("authorization_codes ac").
Select(`
COUNT(*) AS total_count,
SUM(CASE WHEN ac.end_date < NOW() THEN 1 ELSE 0 END) AS expired_count,
SUM(CASE WHEN ac.end_date >= NOW() THEN 1 ELSE 0 END) AS valid_count,
SUM(CASE WHEN ac.end_date >= NOW() THEN ac.max_activations ELSE 0 END) AS valid_max_activations_sum
`).
Where("ac.created_by = ?", cuUserID).
Scan(&result).Error
if err != nil {
return nil, err
}
return &models.CuAuthorizationCodeSummaryResponse{
TotalCount: result.TotalCount,
ExpiredCount: result.ExpiredCount,
ValidCount: result.ValidCount,
ValidMaxActivationsSum: result.ValidMaxActivationsSum,
}, nil
}
// UpdateAuthorizationCode 更新授权码
func (r *authorizationCodeRepository) UpdateAuthorizationCode(ctx context.Context, authCode *models.AuthorizationCode) error {
return r.db.WithContext(ctx).Save(authCode).Error
@@ -92,6 +92,12 @@ type AuthorizationCodeRepository interface {
// UpdateMaxActivationsWithTx 在事务中更新授权码的最大激活次数
UpdateMaxActivationsWithTx(ctx context.Context, tx interface{}, authCodeID string, newMaxActivations int) error
// 用户端:查询用户授权码列表
GetCuAuthorizationCodeList(ctx context.Context, cuUserID string, req *models.CuAuthorizationCodeListRequest) (*models.CuAuthorizationCodeListResponse, error)
// 用户端:授权信息统计
GetCuAuthorizationCodeSummary(ctx context.Context, cuUserID string) (*models.CuAuthorizationCodeSummaryResponse, error)
}
// LicenseRepository 许可证数据访问接口
@@ -894,3 +894,39 @@ func (s *authorizationCodeService) ShareAuthorizationCode(ctx context.Context, a
return response, nil
}
// GetCuAuthorizationCodeList 用户端:获取当前用户授权码列表
func (s *authorizationCodeService) GetCuAuthorizationCodeList(ctx context.Context, cuUserID string, req *models.CuAuthorizationCodeListRequest) (*models.CuAuthorizationCodeListResponse, error) {
lang := pkgcontext.GetLanguageFromContext(ctx)
if cuUserID == "" {
return nil, i18n.NewI18nError("100004", lang)
}
result, err := s.authCodeRepo.GetCuAuthorizationCodeList(ctx, cuUserID, req)
if err != nil {
return nil, i18n.NewI18nError("900004", lang, err.Error())
}
for i := range result.List {
result.List[i].StatusDisplay = i18n.GetEnumMessage("authorization_code_status", result.List[i].Status, lang)
}
return result, nil
}
// GetCuAuthorizationCodeSummary 用户端:授权信息统计
func (s *authorizationCodeService) GetCuAuthorizationCodeSummary(ctx context.Context, cuUserID string) (*models.CuAuthorizationCodeSummaryResponse, error) {
lang := pkgcontext.GetLanguageFromContext(ctx)
if cuUserID == "" {
return nil, i18n.NewI18nError("100004", lang)
}
result, err := s.authCodeRepo.GetCuAuthorizationCodeSummary(ctx, cuUserID)
if err != nil {
return nil, i18n.NewI18nError("900004", lang, err.Error())
}
return result, nil
}
@@ -22,6 +22,7 @@ type CuOrderService interface {
GetUserOrders(ctx context.Context, cuUserID string, req *models.CuOrderListRequest) ([]*models.CuOrder, int64, error)
GetOrderSummary(ctx context.Context, customerID string) (*models.OrderSummaryResponse, error)
CalculatePrice(ctx context.Context, packageID string, licenseCount int) (*PriceCalculationResult, error)
CancelOrder(ctx context.Context, orderID, cuUserID string) (*models.CuOrder, error)
UpdateOrderStatus(ctx context.Context, orderID string, status string) error
}
@@ -363,6 +364,44 @@ func (s *cuOrderService) GetOrder(ctx context.Context, orderID, cuUserID string)
return order, nil
}
func (s *cuOrderService) CancelOrder(ctx context.Context, orderID, cuUserID string) (*models.CuOrder, error) {
lang := pkgcontext.GetLanguageFromContext(ctx)
if orderID == "" || cuUserID == "" {
return nil, i18n.NewI18nError("900001", lang)
}
order, err := s.repo.GetByID(orderID)
if err != nil {
if err == gorm.ErrRecordNotFound {
return nil, i18n.NewI18nError("601001", lang) // 订单不存在
}
return nil, i18n.NewI18nError("900004", lang, err.Error())
}
// 检查权限:只能取消自己的订单
if order.CuUserID != cuUserID {
return nil, i18n.NewI18nError("100005", lang) // 权限不足
}
switch order.Status {
case "cancelled":
// 幂等:已取消直接返回成功
return order, nil
case "paid":
return nil, i18n.NewI18nError("601006", lang) // 订单已支付,无法取消
case "pending":
order.Status = "cancelled"
if err := s.repo.Update(order); err != nil {
return nil, i18n.NewI18nError("900004", lang, err.Error())
}
return order, nil
default:
// 兜底:未知状态按不可取消处理
return nil, i18n.NewI18nError("601006", lang)
}
}
func (s *cuOrderService) GetUserOrders(ctx context.Context, cuUserID string, req *models.CuOrderListRequest) ([]*models.CuOrder, int64, error) {
lang := pkgcontext.GetLanguageFromContext(ctx)
+4
View File
@@ -50,6 +50,10 @@ type AuthorizationCodeService interface {
// 用户端获取产品激活码
GetProductActivationCode(ctx context.Context, customerID string, req *models.ProductActivationCodeRequest) (*models.ProductActivationCodeResponse, error)
// 用户端授权码列表与统计
GetCuAuthorizationCodeList(ctx context.Context, cuUserID string, req *models.CuAuthorizationCodeListRequest) (*models.CuAuthorizationCodeListResponse, error)
GetCuAuthorizationCodeSummary(ctx context.Context, cuUserID string) (*models.CuAuthorizationCodeSummaryResponse, error)
}
// LicenseService 许可证服务接口
+12
View File
@@ -126,6 +126,18 @@ func getHTTPStatusByCode(code string) int {
return StatusOK
case "30": // 授权模块错误,默认200(业务错误通过响应体区分)
return StatusOK
case "40": // 仪表盘模块错误,默认200(业务错误通过响应体区分)
return StatusOK
case "50": // 客户用户模块错误,默认200(业务错误通过响应体区分)
return StatusOK
case "60": // 套餐订单模块错误,默认200(业务错误通过响应体区分)
return StatusOK
case "61": // 订单模块错误,默认200(业务错误通过响应体区分)
return StatusOK
case "62": // 设备模块错误,默认200(业务错误通过响应体区分)
return StatusOK
case "70": // 发票模块错误,默认200(业务错误通过响应体区分)
return StatusOK
case "90": // 系统错误,默认500
return StatusInternalServerError
}