设计系统
🎨 设计理念
云原生、模块化、可扩展的设计理念,为 Penny Lens 系统提供统一、高效、安全的后端服务架构。
🏗️ 架构设计
整体架构
┌─────────────────────────────────────────────────────────────┐│ Penny Lens Serverless 架构 │├─────────────────────────────────────────────────────────────┤│ ││ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────┐ ││ │ 控制器层 │ │ 服务层 │ │ 数据层 │ ││ │ │ │ │ │ │ ││ │ UserController│ │ UserService │ │ MongoDB │ ││ │ AssetController│ │ AssetService │ │ Collections│ ││ │ AccountingCtrl │ │ AccountingSvc │ │ Indexes │ ││ │ BudgetController│ │ BudgetService │ │ Transactions│ ││ │ ... │ │ ... │ │ ... │ ││ └─────────────────┘ └─────────────────┘ └─────────────┘ ││ │ │ │ ││ └────────────────────────┼────────────────┘ ││ │ ││ ┌─────────────────────────────────────────────────────────┐ ││ │ 云函数层 │ ││ │ │ ││ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ ││ │ │ 路由处理 │ │ 权限验证 │ │ 异常处理 │ │ ││ │ │ Router │ │ Auth │ │ Exception │ │ ││ │ └─────────────┘ └─────────────┘ └─────────────┘ │ ││ └─────────────────────────────────────────────────────────┘ │└─────────────────────────────────────────────────────────────┘模块化设计
1. 控制器层 (Controllers)
// 用户控制器
export class UserController extends BaseController {
async login(req: Request, res: Response) {
// 登录逻辑
}
async getUserInfo(req: Request, res: Response) {
// 获取用户信息
}
async updateUserInfo(req: Request, res: Response) {
// 更新用户信息
}
}2. 服务层 (Services)
// 用户服务
export class UserService extends BaseService {
async login(credentials: LoginCredentials): Promise<LoginResponse> {
// 登录业务逻辑
}
async getUserInfo(userId: string): Promise<UserInfo> {
// 获取用户信息业务逻辑
}
async updateUserInfo(userId: string, data: UserInfo): Promise<UserInfo> {
// 更新用户信息业务逻辑
}
}3. 数据层 (Data Layer)
// 用户数据模型
export interface User {
_id: string;
username: string;
email: string;
password: string;
createdAt: Date;
updatedAt: Date;
}
// 用户查询接口
export interface UserQuery {
username?: string;
email?: string;
page?: number;
pageSize?: number;
}🔧 核心组件
1. 基础控制器 (BaseController)
/**
* 基础控制器类
* 提供统一的请求处理和响应格式
*/
export abstract class BaseController {
/**
* 包装异步方法,自动处理异常
*/
protected wrapAsync<T>(
asyncFn: () => Promise<T>,
successMessage: string = "操作成功"
): Promise<AppResponse<T>> {
try {
const data = await asyncFn();
return {
code: 200,
message: successMessage,
data
};
} catch (error) {
// 统一异常处理
return this.handleError(error);
}
}
/**
* 统一错误处理
*/
private handleError(error: unknown): AppResponse<null> {
if (error instanceof AppException) {
return {
code: error.code,
message: error.message,
data: null
};
}
const errorMessage = error instanceof Error ? error.message : "未知错误";
return {
code: 500,
message: `服务器错误: ${errorMessage}`,
data: null
};
}
}2. 基础服务 (BaseService)
/**
* 基础服务类
* 提供通用的数据库操作和业务逻辑处理
*/
export abstract class BaseService {
/**
* 构建基础查询条件
*/
protected buildBaseQueryCondition(userInfo: UserResponse): any {
return {
userId: userInfo.id,
delFlag: false
};
}
/**
* 分页查询
*/
protected async paginateQuery<T>(
collection: any,
where: any,
page: number,
pageSize: number,
orderBy: { field: string; order: "asc" | "desc"; }[] = [{ field: "createdAt", order: "desc" }]
): Promise<Page<T>> {
// 分页查询实现
const skip = (page - 1) * pageSize;
const result = await collection
.where(where)
.orderBy(orderBy[0].field, orderBy[0].order)
.skip(skip)
.limit(pageSize)
.get();
const countResult = await collection.where(where).count();
return {
data: result.data,
total: countResult.total,
page,
pageSize,
totalPages: Math.ceil(countResult.total / pageSize)
};
}
/**
* 验证记录是否存在
*/
protected async validateRecordExists(
collection: any,
condition: any,
errorMessage: string
): Promise<any> {
const result = await collection.where(condition).get();
if (result.data.length === 0) {
throw new NotFoundException(errorMessage);
}
return result.data[0];
}
/**
* 验证用户访问权限
*/
protected async validateUserAccess(
record: any,
userInfo: UserResponse,
errorMessage: string
): Promise<void> {
if (record.userId !== userInfo.id) {
throw new AuthorizationException(errorMessage);
}
}
}3. 异常处理 (Exception Handling)
/**
* 应用异常基类
*/
export abstract class AppException extends Error {
constructor(
public code: number,
public message: string,
public data?: any
) {
super(message);
this.name = 'AppException';
}
}
/**
* 验证异常
*/
export class ValidationException extends AppException {
constructor(message: string, data?: any) {
super(400, message, data);
}
}
/**
* 认证异常
*/
export class AuthenticationException extends AppException {
constructor(message: string = 'Authentication failed') {
super(401, message);
}
}
/**
* 授权异常
*/
export class AuthorizationException extends AppException {
constructor(message: string = 'Access denied') {
super(403, message);
}
}
/**
* 未找到异常
*/
export class NotFoundException extends AppException {
constructor(message: string = 'Resource not found') {
super(404, message);
}
}
/**
* 数据库异常
*/
export class DatabaseException extends AppException {
constructor(message: string = 'Database operation failed') {
super(500, message);
}
}4. 路由配置系统
/**
* 路由配置接口
*/
export interface RouteConfig {
controller: any;
method: string;
requireAuth: boolean;
}
/**
* 批量构建路由配置
*/
export function buildRoutes(
serviceName: string,
actions: Array<{ action: string; requireAuth: boolean }>,
controller: any
): Record<string, RouteConfig> {
const routes: Record<string, RouteConfig> = {};
actions.forEach(({ action, requireAuth }) => {
const routeKey = `${serviceName}.${action}`;
const methodName = METHOD_MAPPINGS[routeKey] || action;
routes[routeKey] = {
controller,
method: methodName,
requireAuth
};
});
return routes;
}
/**
* 方法映射表(特殊 action 到 service 方法名的映射)
*/
export const METHOD_MAPPINGS: Record<string, string> = {
"asset.record.queryByAssetId": "queryListByAssetId",
"user.generateQrCodeLogin": "generateQrCodeLogin",
"user.confirmQrCodeLogin": "confirmQrCodeLogin",
"user.pollQrCodeLogin": "pollQrCodeLogin",
};📊 数据模型设计
1. 用户模型 (User Model)
export interface User {
_id: string;
username: string;
email: string;
password: string;
avatar?: string;
phone?: string;
isActive: boolean;
lastLoginAt?: Date;
createdAt: Date;
updatedAt: Date;
}
export interface UserCreate {
username: string;
email: string;
password: string;
phone?: string;
}
export interface UserUpdate {
username?: string;
email?: string;
phone?: string;
avatar?: string;
}2. 资产模型 (Asset Model)
export interface Asset {
_id: string;
userId: string;
name: string;
type: AssetType;
balance: number;
currency: string;
isActive: boolean;
createdAt: Date;
updatedAt: Date;
}
export enum AssetType {
CASH = 'cash',
BANK = 'bank',
CREDIT_CARD = 'credit_card',
INVESTMENT = 'investment',
OTHER = 'other'
}3. 记账模型 (Accounting Model)
export interface AccountingRecord {
_id: string;
userId: string;
type: TransactionType;
amount: number;
description: string;
categoryId: string;
assetId: string;
targetAssetId?: string;
date: Date;
status: TransactionStatus;
createdAt: Date;
updatedAt: Date;
}
export enum TransactionType {
INCOME = 'income',
EXPENSE = 'expense',
TRANSFER = 'transfer'
}
export enum TransactionStatus {
PENDING = 'pending',
COMPLETED = 'completed',
CANCELLED = 'cancelled'
}🔐 安全设计
1. 认证机制
// JWT Token 认证
export class AuthService {
generateToken(user: User): string {
return jwt.sign(
{ userId: user._id, username: user.username },
process.env.JWT_SECRET!,
{ expiresIn: '7d' }
);
}
verifyToken(token: string): any {
return jwt.verify(token, process.env.JWT_SECRET!);
}
}2. 权限控制
// 权限中间件
export const requireAuth = (req: Request, res: Response, next: NextFunction) => {
const token = req.headers.authorization?.replace('Bearer ', '');
if (!token) {
throw new AuthenticationException('Token required');
}
try {
const decoded = authService.verifyToken(token);
req.user = decoded;
next();
} catch (error) {
throw new AuthenticationException('Invalid token');
}
};3. 数据加密
// 数据加密服务
export class EncryptionService {
encrypt(text: string): string {
const cipher = crypto.createCipher('aes-256-cbc', process.env.ENCRYPTION_KEY!);
let encrypted = cipher.update(text, 'utf8', 'hex');
encrypted += cipher.final('hex');
return encrypted;
}
decrypt(encryptedText: string): string {
const decipher = crypto.createDecipher('aes-256-cbc', process.env.ENCRYPTION_KEY!);
let decrypted = decipher.update(encryptedText, 'hex', 'utf8');
decrypted += decipher.final('utf8');
return decrypted;
}
}📈 性能优化
1. 数据库优化
// 数据库索引
export const createIndexes = async () => {
// 用户索引
await db.collection('users').createIndex({ username: 1 }, { unique: true });
await db.collection('users').createIndex({ email: 1 }, { unique: true });
// 资产索引
await db.collection('assets').createIndex({ userId: 1 });
await db.collection('assets').createIndex({ type: 1 });
// 记账记录索引
await db.collection('accounting').createIndex({ userId: 1, date: -1 });
await db.collection('accounting').createIndex({ categoryId: 1 });
};2. 缓存机制
// 缓存服务
export class CacheService {
private cache = new Map<string, { data: any; timestamp: number; ttl: number }>();
set(key: string, data: any, ttl: number = 300000): void {
this.cache.set(key, {
data,
timestamp: Date.now(),
ttl
});
}
get(key: string): any | null {
const item = this.cache.get(key);
if (!item) return null;
if (Date.now() - item.timestamp > item.ttl) {
this.cache.delete(key);
return null;
}
return item.data;
}
}🧪 测试设计
1. 单元测试
// 用户服务测试
describe('UserService', () => {
let userService: UserService;
beforeEach(() => {
userService = new UserService();
});
it('should create user successfully', async () => {
const userData = {
username: 'testuser',
email: 'test@example.com',
password: 'password123'
};
const result = await userService.createUser(userData);
expect(result).toBeDefined();
expect(result.username).toBe(userData.username);
});
});2. 集成测试
// API 集成测试
describe('User API', () => {
it('should login successfully', async () => {
const response = await request(app)
.post('/api/user/login')
.send({
username: 'testuser',
password: 'password123'
});
expect(response.status).toBe(200);
expect(response.body.code).toBe(200);
expect(response.body.data.token).toBeDefined();
});
});📋 最佳实践
1. 开发规约实践
Service 层开发规范
- 继承 BaseService: 所有 Service 必须继承
BaseService - 使用工具方法: 使用
buildBaseQueryCondition、paginateQuery、validateRecordExists等工具方法 - 参数验证: 在方法开始处进行参数验证,抛出
ValidationException - 异常处理: 使用具体的业务异常类,提供清晰的错误信息
- 数据库操作: 统一在文件顶部定义数据库连接和集合
Controller 层开发规范
- 继承 BaseController: 所有 Controller 必须继承
BaseController - 使用 wrapAsync: 使用
wrapAsync包装业务逻辑,自动处理异常 - 不使用 public 关键字: TypeScript 类成员默认为 public
- 异步方法: 使用
async关键字声明异步方法 - 未使用参数: 使用下划线前缀(如
_userInfo)表示有意忽略的参数
路由配置规范
- 使用 buildRoutes: 使用
buildRoutes函数批量构建路由配置 - 认证控制: 明确指定
requireAuth属性 - 业务分组: 相关路由按业务领域分组定义
- 方法映射: 使用
METHOD_MAPPINGS处理特殊方法映射
2. 代码规范
- TypeScript: 使用 TypeScript 进行类型约束
- ESLint: 使用 ESLint 进行代码检查
- 命名规范: 遵循 camelCase 命名规范
- 注释规范: 完整的 JSDoc 注释
3. 错误处理
- 统一异常: 使用统一的异常处理机制
- 错误日志: 记录详细的错误日志
- 用户友好: 返回用户友好的错误信息
- 监控告警: 设置错误监控和告警
4. 性能优化
- 数据库优化: 优化数据库查询和索引
- 缓存策略: 合理使用缓存机制
- 异步处理: 使用异步处理提升性能
- 监控指标: 监控关键性能指标
5. 安全实践
- 输入验证: 严格验证用户输入
- 权限控制: 实现细粒度权限控制
- 数据加密: 敏感数据加密存储
- 安全审计: 记录安全相关操作
6. 开发检查清单
在创建新的功能模块时,请确保:
- Service 类继承
BaseService - Controller 类继承
BaseController - 类名以
Service或Controller结尾 - Import 顺序正确
- 不使用
public关键字 - 所有异步方法使用
async关键字 - 使用
wrapAsync包装业务逻辑 - wrapAsync 内部使用
async () => { return ... }格式 - 未使用的参数使用下划线前缀
- 类型定义明确(Request、Response)
- 添加 JSDoc 注释
- 在
routes.ts中注册路由 - 使用
buildBaseQueryCondition构建查询条件 - 使用
paginateQuery进行分页查询 - 使用具体的业务异常类
重要提醒:
- 始终遵循设计系统规范和开发规约
- 确保云原生架构兼容性
- 保持代码质量和性能
- 优化安全性和可维护性
- 遵循最佳实践和设计模式
- 严格按照开发规约实现 Service 和 Controller 层
- 使用统一的异常处理和路由配置机制
- 遵循统一的命名规范和代码风格
