diff --git a/docs/DEPLOYMENT.md b/docs/DEPLOYMENT.md index 5781fee2..699ed853 100644 --- a/docs/DEPLOYMENT.md +++ b/docs/DEPLOYMENT.md @@ -306,7 +306,7 @@ s3: - **Scheduler 独占**:**【注意】** 为避免重复触发定时 Cron 任务,`wavelet scheduler` 定时调度器进程**同一时间应仅运行单个活跃实例**(主备高可用可以通过容器平台的单实例保障或 K8s Job 机制来限制实例数为 1)。 #### 5. ClickHouse 高并发同步 -在大数据量、高频支付结算场景下,开启 ClickHouse 以接收系统的历史数据同步(如订单流水和任务大宽表),通过定时器把 PostgreSQL 的压力转移到 ClickHouse 列式存储中。 +在大数据量、高频支付结算场景下,开启 ClickHouse 以接收系统的历史数据同步,通过定时器把 PostgreSQL 的压力转移到 ClickHouse 列式存储中。 ```yaml clickhouse: enabled: true diff --git a/frontend/lib/services/README.md b/frontend/lib/services/README.md deleted file mode 100644 index af263d95..00000000 --- a/frontend/lib/services/README.md +++ /dev/null @@ -1,524 +0,0 @@ -# 服务层架构文档 - -> **前端服务层** - 统一的 API 交互层,基于 TypeScript 和面向对象设计 - -## 架构设计 - -### 设计理念 - -服务层采用**分层架构**和**继承模式**,遵循以下核心原则: - -1. **单一职责** - 每个服务模块负责一个业务领域 -2. **类型安全** - 全量 TypeScript 类型定义,杜绝 `any` -3. **统一规范** - 所有服务遵循相同的设计模式 -4. **错误分类** - 细粒度错误类型,便于精确处理 -5. **可扩展性** - 基于继承的设计,易于添加新服务 - ---- - -## 架构分层 - -```mermaid -graph TB - subgraph UI["业务组件层"] - Components["React Components
Hooks & Contexts"] - end - - subgraph Services["服务层 (Services Layer)"] - direction LR - Auth["AuthService
认证服务"] - Admin["AdminService
管理员服务"] - Merchant["MerchantService
商户服务"] - Transaction["TransactionService
交易服务"] - Dispute["DisputeService
争议服务"] - User["UserService
用户服务"] - Config["ConfigService
配置服务"] - end - - subgraph BaseLayer["服务基类"] - BaseService["BaseService
• get() post() put() delete()
• rawGet() rawPost()
"] - end - - subgraph Core["核心层 (Core Layer)"] - direction LR - ApiClient["api-client.ts
• 请求/响应拦截
• 错误处理
• 请求去重
• 401重定向
"] - Errors["errors.ts
• ApiErrorBase
• NetworkError
• ValidationError
• 等7种错误
"] - Types["types.ts
• ApiResponse
• PaginationParams
• RequestConfig
"] - CoreConfig["config.ts
• baseURL
• timeout
• credentials
"] - end - - Backend["Backend API
(Go + Gin)"] - - Components -->|调用| Auth - Components -->|调用| Admin - Components -->|调用| Merchant - Components -->|调用| Transaction - Components -->|调用| Dispute - Components -->|调用| User - Components -->|调用| Config - - Auth -.->|继承| BaseService - Admin -.->|继承| BaseService - Merchant -.->|继承| BaseService - Transaction -.->|继承| BaseService - Dispute -.->|继承| BaseService - User -.->|继承| BaseService - Config -.->|继承| BaseService - - BaseService -->|使用| ApiClient - BaseService -->|使用| Errors - BaseService -->|使用| Types - ApiClient -->|读取| CoreConfig - - ApiClient ==>|HTTP 请求| Backend - - style UI fill:#e1f5ff - style Services fill:#fff9e6 - style BaseLayer fill:#f0f0f0 - style Core fill:#e8f5e9 - style Backend fill:#ffebee -``` - - ---- - -## 目录结构 - -``` -lib/services/ -├── core/ # 核心基础设施层 -│ ├── api-client.ts # HTTP 客户端(请求拦截、错误处理) -│ ├── base.service.ts # 服务基类(封装 CRUD 方法) -│ ├── config.ts # API 配置(环境变量、超时等) -│ ├── errors.ts # 错误类型定义(7种错误类型) -│ ├── types.ts # 核心类型(ApiResponse、分页等) -│ └── index.ts # 核心模块导出 -│ -├── auth/ # 认证服务模块 -│ ├── auth.service.ts # OAuth 认证、登录、登出 -│ ├── types.ts # User、OAuthLoginUrlResponse 等 -│ └── index.ts # 模块导出 + 文档 -│ -├── admin/ # 管理员服务模块 -│ ├── admin.service.ts # 系统配置、用户积分配置管理 -│ ├── types.ts # SystemConfig、UserPayConfig 等 -│ └── index.ts # 模块导出 + 文档 -│ -├── merchant/ # 商户服务模块 -│ ├── merchant.service.ts # API Key、支付链接、订单管理 -│ ├── types.ts # MerchantAPIKey、PaymentLink 等 -│ └── index.ts # 模块导出 + 文档 -│ -├── transaction/ # 交易服务模块 -│ ├── transaction.service.ts # 交易记录 -│ ├── types.ts # Order、TransactionQueryParams 等 -│ └── index.ts # 模块导出 + 文档 -│ -├── dispute/ # 争议服务模块 -│ ├── dispute.service.ts # 创建争议、退款审核 -│ ├── types.ts # Dispute、DisputeWithOrder 等 -│ └── index.ts # 模块导出 + 文档 -│ -├── user/ # 用户服务模块 -│ ├── user.service.ts # 用户设置(支付密钥等) -│ ├── types.ts # UpdatePayKeyRequest 等 -│ └── index.ts # 模块导出 + 文档 -│ -├── config/ # 配置服务模块 -│ ├── config.service.ts # 获取公共配置 -│ ├── types.ts # PublicConfigResponse -│ └── index.ts # 模块导出 + 文档 -│ -├── index.ts # 统一导出入口 -└── README.md # 本文档 -``` - ---- - -## 核心模块详解 - -### 1. BaseService - 服务基类 - -**职责**:为所有业务服务提供统一的 HTTP 方法封装 - -**关键设计**: -```typescript -export class BaseService { - protected static readonly basePath: string = ''; - - // 标准 RESTful 方法 - protected static async get(path: string, params?: Record): Promise - protected static async post(path: string, data?: unknown): Promise - protected static async put(path: string, data?: unknown): Promise - protected static async patch(path: string, data?: unknown): Promise - protected static async delete(path: string, params?: Record): Promise - - // 特殊端点支持(不遵循标准响应格式) - protected static async rawGet(url: string, params?: unknown): Promise - protected static async rawPost(url: string, data?: unknown): Promise -} -``` - -**设计优势**: -- ✅ 子类只需设置 `basePath`,无需重复实现 HTTP 逻辑 -- ✅ 统一的响应解包(`response.data.data`) -- ✅ 类型安全的泛型设计 -- ✅ 支持特殊端点(如 `/api.php`) - ---- - -### 2. API Client - HTTP 客户端 - -**职责**:提供全局唯一的 Axios 实例,处理所有 HTTP 请求 - -**核心功能**: - -#### 请求拦截器 -- 自动添加 Cancel Token(支持请求取消) -- 请求去重(避免重复请求) - -#### 响应拦截器 -- **401 自动重定向** - 未授权时自动跳转登录页 -- **错误分类** - 将 HTTP 状态码映射为具体错误类型 -- **统一响应格式** - 解析 `ApiResponse` 结构 - -#### 错误处理映射 -| HTTP 状态码 | 错误类型 | 说明 | -|------------|---------|------| -| 400 | `ValidationError` | 参数验证失败 | -| 401 | 自动重定向 | 跳转到登录页 | -| 403 | `ForbiddenError` | 权限不足 | -| 404 | `NotFoundError` | 资源不存在 | -| 5xx | `ServerError` | 服务器错误 | -| 超时 | `TimeoutError` | 请求超时 | -| 网络 | `NetworkError` | 网络连接失败 | - ---- - -### 3. 错误类型层级 - -``` -ApiErrorBase (基类) -├── NetworkError (网络连接错误) -├── TimeoutError (请求超时) -├── UnauthorizedError (401 - 未授权) -├── ForbiddenError (403 - 权限不足) -├── NotFoundError (404 - 资源不存在) -├── ValidationError (400 - 参数验证失败) -└── ServerError (5xx - 服务器错误) -``` - -**设计优势**: -- 支持 `instanceof` 类型判断 -- 携带详细错误信息(`error_code`、`details`) -- 便于前端精确处理不同错误场景 - ---- - -## 开发规范 - -### 1. 服务类规范 - -#### 必须遵循 -```typescript -export class SomeService extends BaseService { - // 1. 必须继承 BaseService - // 2. basePath 必须是 protected static readonly - protected static readonly basePath = '/api/v1/resource'; - - // 3. 方法必须是 static async - // 4. 返回类型必须明确(禁止 any) - static async getAll(): Promise { - return this.get('/'); - } - - // 5. 参数类型必须明确定义在 types.ts - static async create(request: CreateResourceRequest): Promise { - return this.post('/', request); - } -} -``` - -#### 方法命名规范 -| 操作 | 命名 | 示例 | -|------|------|------| -| 获取列表 | `list*` 或 `getAll` | `listAPIKeys()` | -| 获取单个 | `get*` 或 `getById` | `getAPIKey(id)` | -| 创建 | `create*` | `createAPIKey(request)` | -| 更新 | `update*` | `updateAPIKey(id, request)` | -| 删除 | `delete*` | `deleteAPIKey(id)` | -| 特殊操作 | 动词开头 | `payMerchantOrder()` | - -#### 方法排序规范 -```typescript -export class SomeService extends BaseService { - protected static readonly basePath = '/api/v1/resource'; - - // 1. CRUD 操作(按 Create → Read → Update → Delete) - static async create() { } - static async list() { } - static async get() { } - static async update() { } - static async delete() { } - - // 2. 其他业务方法(按业务逻辑分组) - static async someAction() { } -} -``` - ---- - -### 2. 类型定义规范 - -#### types.ts 文件结构 -```typescript -// 1. 类型别名(Type Aliases) -export type ResourceStatus = 'active' | 'inactive' | 'pending'; - -// 2. 枚举(Enums) - 使用 const enum 提升性能 -export const enum ResourceLevel { - Basic = 1, - Premium = 2, - Enterprise = 3, -} - -// 3. 接口(Interfaces) - 按业务逻辑分组 -/** - * 资源信息 - */ -export interface Resource { - /** 资源 ID */ - id: number; - /** 资源名称 */ - name: string; - /** 状态 */ - status: ResourceStatus; - /** 创建时间 */ - created_at: string; -} - -/** - * 创建资源请求 - */ -export interface CreateResourceRequest { - /** 资源名称(最大 50 字符) */ - name: string; - /** 描述(可选,最大 200 字符) */ - description?: string; -} -``` - -#### 注释规范 -- ✅ 所有 interface 必须有 JSDoc 描述 -- ✅ 所有字段必须有行内注释 -- ✅ 包含约束信息(长度、范围、格式等) -- ✅ 可选字段使用 `?` 标记 - ---- - -### 3. JSDoc 注释规范 - -**标准模板**: -```typescript -/** - * [一句话描述方法功能] - * - * @param paramName - 参数说明 - * @returns 返回值说明 - * @throws {ErrorType} 错误条件说明 - * - * @example - * ```typescript - * // 使用示例 - * const result = await Service.method({ param: 'value' }); - * ``` - * - * @remarks [可选] - * - 业务规则或注意事项 - */ -``` - -**示例**: -```typescript -/** - * 创建商户 API Key - * - * @param request - API Key 配置 - * @returns 创建的 API Key 信息 - * @throws {UnauthorizedError} 当未登录时 - * @throws {ValidationError} 当参数验证失败时 - * - * @example - * ```typescript - * const apiKey = await MerchantService.createAPIKey({ - * app_name: '我的应用', - * app_homepage_url: 'https://example.com' - * }); - * ``` - * - * @remarks - * - app_name 最大 20 字符 - * - 需要登录权限 - */ -static async createAPIKey(request: CreateAPIKeyRequest): Promise -``` - ---- - -### 4. 模块 index.ts 规范 - -**标准格式**: -```typescript -/** - * [模块名] 服务模块 - * - * @description - * 提供 [业务领域] 相关的功能,包括: - * - 功能点1 - * - 功能点2 - * - 功能点3 - * - * @example - * ```typescript - * import { ServiceName } from '@/lib/services'; - * - * // 使用示例(展示最常用的1-2个方法) - * const result = await ServiceName.commonMethod(); - * ``` - * - * @remarks [可选] - * - 特殊说明(如权限要求等) - */ - -export { ServiceName } from './service-name.service'; -export type * from './types'; -// 或明确导出 -export type { - Type1, - Type2, -} from './types'; -``` - ---- - -## 创建新服务指南 - -### Step 1: 创建目录和文件 -```bash -mkdir lib/services/resource -touch lib/services/resource/types.ts -touch lib/services/resource/resource.service.ts -touch lib/services/resource/index.ts -``` - -### Step 2: 定义类型(types.ts) -```typescript -export interface Resource { - id: number; - name: string; -} - -export interface CreateResourceRequest { - name: string; -} -``` - -### Step 3: 实现服务(resource.service.ts) -```typescript -import { BaseService } from '../core/base.service'; -import type { Resource, CreateResourceRequest } from './types'; - -export class ResourceService extends BaseService { - protected static readonly basePath = '/api/v1/resources'; - - static async list(): Promise { - return this.get('/'); - } - - static async create(request: CreateResourceRequest): Promise { - return this.post('/', request); - } -} -``` - -### Step 4: 导出模块(index.ts) -```typescript -/** - * 资源服务模块 - * - * @description - * 提供资源管理相关的功能,包括: - * - 资源列表查询 - * - 资源创建 - */ - -export { ResourceService } from './resource.service'; -export type * from './types'; -``` - -### Step 5: 注册到统一入口(services/index.ts) -```typescript -import { ResourceService } from './resource'; - -const services = { - // ... existing services - resource: ResourceService, // 新增 -}; - -export default services; -export { ResourceService } from './resource'; -export type * from './resource'; -``` - ---- - -## 使用示例 - -```typescript -import services from '@/lib/services'; - -// 调用服务 -const user = await services.auth.getUserInfo(); -const transactions = await services.transaction.getTransactions({ - page: 1, - page_size: 20 -}); - -// 错误处理 -import { UnauthorizedError, ValidationError } from '@/lib/services'; - -try { - await services.merchant.createAPIKey(request); -} catch (error) { - if (error instanceof UnauthorizedError) { - router.push('/login'); - } else if (error instanceof ValidationError) { - toast.error(error.message); - } -} -``` - ---- - -## 注意事项 - -### 禁止事项 -- ❌ 使用 `any` 类型 -- ❌ 直接使用 `apiClient`(除非在 `BaseService` 内部) -- ❌ 绕过 BaseService 实现 HTTP 请求 -- ❌ 在业务组件中直接导入 axios - -### 必须遵循 -- ✅ 所有服务继承 `BaseService` -- ✅ 使用 `protected static readonly basePath` -- ✅ 方法必须有完整的 JSDoc 注释 -- ✅ 类型定义必须在 `types.ts` 中 -- ✅ 通过 `services` 对象调用服务 - ---- - -## 相关文档 - -- [TypeScript 官方文档](https://www.typescriptlang.org/) -- [Axios 文档](https://axios-http.com/) -- [JSDoc 规范](https://jsdoc.app/) diff --git a/frontend/lib/services/admin/admin.service.ts b/frontend/lib/services/admin/admin.service.ts index 53d66d2f..6cdddce5 100644 --- a/frontend/lib/services/admin/admin.service.ts +++ b/frontend/lib/services/admin/admin.service.ts @@ -27,7 +27,6 @@ export type { AdminUser } from './types'; /** * 管理员服务 - * 处理系统配置和用户积分配置管理相关的 API 请求 * * @remarks * 所有接口都需要管理员权限 @@ -199,28 +198,6 @@ export class AdminService extends BaseService { * @throws {UnauthorizedError} 当未登录时 * @throws {ForbiddenError} 当无管理员权限时 * @throws {ValidationError} 当参数验证失败时 - * - * @example - * ```typescript - * // 下发订单同步任务(带时间范围) - * await AdminService.dispatchTask({ - * task_type: 'order_sync', - * start_time: '2025-12-01T00:00:00Z', - * end_time: '2025-12-27T23:59:59Z' - * }); - * - * // 下发用户积分更新任务 - * await AdminService.dispatchTask({ - * task_type: 'user_gamification', - * user_id: 123 - * }); - * - * // 下发争议自动退款任务 - * await AdminService.dispatchTask({ - * task_type: 'dispute_auto_refund' - * }); - * ``` - * * @remarks * - 不同任务类型需要不同的参数 * - order_sync 支持 start_time 和 end_time 参数 diff --git a/internal/apps/upload/errs.go b/internal/apps/upload/errs.go index 1863bd27..f4687fe2 100644 --- a/internal/apps/upload/errs.go +++ b/internal/apps/upload/errs.go @@ -44,6 +44,5 @@ const ( ErrS3KeyTooLongFormat = "s3 key exceeds maximum length of %d" ErrS3KeyStartsWithSlash = "s3 key must not start with /" ErrS3KeyContainsNullBytes = "s3 key must not contain null bytes" - // - ErrQueryUnusedUploadsFailed = "查询未使用的上传文件失败: %w" + ErrQueryUnusedUploadsFailed = "查询未使用的上传文件失败: %w" ) diff --git a/internal/common/errs.go b/internal/common/errs.go index 03371302..b3d6712d 100644 --- a/internal/common/errs.go +++ b/internal/common/errs.go @@ -19,21 +19,6 @@ package common // 通用业务错误消息常量 const ( - BannedAccount = "账号已被封禁" - AmountMustBeGreaterThanZero = "金额必须大于0" - AmountDecimalPlacesExceeded = "金额小数位数不能超过2位" - RateMustBeBetweenZeroAndOne = "比率必须在 0 到 1 之间" - RateDecimalPlacesExceeded = "比率小数位数不能超过2位" - InsufficientBalance = "余额不足" - DailyLimitExceeded = "已超过每日限额" - PayKeyIncorrect = "支付密钥错误" - CannotPaySelf = "不能给自己付款" - TestModeCannotProcessOrder = "测试模式下无法处理订单" - TestModeOrderRemark = "[测试模式] 此订单为测试订单,未实际扣款" - UnAuthorized = "未登录" -) - -// 保护期相关错误消息 -const ( - GetProtectionDaysFailed = "获取新用户保护期配置失败" + BannedAccount = "账号已被封禁" + UnAuthorized = "未登录" ) diff --git a/internal/util/validate.go b/internal/util/validate.go deleted file mode 100644 index 42e06dab..00000000 --- a/internal/util/validate.go +++ /dev/null @@ -1,53 +0,0 @@ -/* -Copyright 2025 linux.do -Modified by Arctel.net, 2026 - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package util - -import ( - "errors" - - "github.com/Rain-kl/Wavelet/internal/common" - "github.com/shopspring/decimal" -) - -// ValidateRates 所有 rate 必须在 [0, 1] 范围内,且小数位数不超过2位 -func ValidateRates(rates ...decimal.Decimal) error { - for _, rate := range rates { - // 验证范围:必须在 [0, 1] 之间 - if rate.LessThan(decimal.Zero) || rate.GreaterThan(decimal.NewFromInt(1)) { - return errors.New(common.RateMustBeBetweenZeroAndOne) - } - - // 验证小数位数:不超过2位 - if rate.Exponent() < -2 { - return errors.New(common.RateDecimalPlacesExceeded) - } - } - - return nil -} - -// ValidateAmount 验证金额:必须大于0,且小数位数不超过2位 -func ValidateAmount(amount decimal.Decimal) error { - if amount.LessThanOrEqual(decimal.Zero) { - return errors.New(common.AmountMustBeGreaterThanZero) - } - if amount.Exponent() < -2 { - return errors.New(common.AmountDecimalPlacesExceeded) - } - return nil -}