Penny Lens Serverless 设计系统

2025年1月27日
9 分钟阅读
作者:Penny Lens Team

设计系统

🎨 设计理念

云原生、模块化、可扩展的设计理念,为 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
  • 使用工具方法: 使用 buildBaseQueryConditionpaginateQueryvalidateRecordExists 等工具方法
  • 参数验证: 在方法开始处进行参数验证,抛出 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
  • 类名以 ServiceController 结尾
  • Import 顺序正确
  • 不使用 public 关键字
  • 所有异步方法使用 async 关键字
  • 使用 wrapAsync 包装业务逻辑
  • wrapAsync 内部使用 async () => { return ... } 格式
  • 未使用的参数使用下划线前缀
  • 类型定义明确(Request、Response)
  • 添加 JSDoc 注释
  • routes.ts 中注册路由
  • 使用 buildBaseQueryCondition 构建查询条件
  • 使用 paginateQuery 进行分页查询
  • 使用具体的业务异常类

重要提醒:

  1. 始终遵循设计系统规范和开发规约
  2. 确保云原生架构兼容性
  3. 保持代码质量和性能
  4. 优化安全性和可维护性
  5. 遵循最佳实践和设计模式
  6. 严格按照开发规约实现 Service 和 Controller 层
  7. 使用统一的异常处理和路由配置机制
  8. 遵循统一的命名规范和代码风格