diff --git a/backend/services/authorization-service/.env.example b/backend/services/authorization-service/.env.example new file mode 100644 index 00000000..62360e5b --- /dev/null +++ b/backend/services/authorization-service/.env.example @@ -0,0 +1,24 @@ +# Application +APP_PORT=3002 +APP_ENV=development + +# Database +DATABASE_URL=postgresql://postgres:password@localhost:5432/authorization_db?schema=public + +# Redis +REDIS_HOST=localhost +REDIS_PORT=6379 +REDIS_PASSWORD= + +# Kafka +KAFKA_BROKERS=localhost:9092 +KAFKA_CLIENT_ID=authorization-service +KAFKA_CONSUMER_GROUP=authorization-consumer-group + +# JWT +JWT_SECRET=your-jwt-secret-key +JWT_EXPIRES_IN=7d + +# Service URLs +IDENTITY_SERVICE_URL=http://localhost:3000 +REFERRAL_SERVICE_URL=http://localhost:3001 diff --git a/backend/services/authorization-service/.eslintrc.js b/backend/services/authorization-service/.eslintrc.js new file mode 100644 index 00000000..21134f4e --- /dev/null +++ b/backend/services/authorization-service/.eslintrc.js @@ -0,0 +1,25 @@ +module.exports = { + parser: '@typescript-eslint/parser', + parserOptions: { + project: 'tsconfig.json', + tsconfigRootDir: __dirname, + sourceType: 'module', + }, + plugins: ['@typescript-eslint/eslint-plugin'], + extends: [ + 'plugin:@typescript-eslint/recommended', + 'plugin:prettier/recommended', + ], + root: true, + env: { + node: true, + jest: true, + }, + ignorePatterns: ['.eslintrc.js'], + rules: { + '@typescript-eslint/interface-name-prefix': 'off', + '@typescript-eslint/explicit-function-return-type': 'off', + '@typescript-eslint/explicit-module-boundary-types': 'off', + '@typescript-eslint/no-explicit-any': 'off', + }, +} diff --git a/backend/services/authorization-service/.gitignore b/backend/services/authorization-service/.gitignore new file mode 100644 index 00000000..e8cf90be --- /dev/null +++ b/backend/services/authorization-service/.gitignore @@ -0,0 +1,47 @@ +# compiled output +/dist +/node_modules + +# Logs +logs +*.log +npm-debug.log* +pnpm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* + +# OS +.DS_Store + +# Tests +/coverage +/.nyc_output + +# IDEs and editors +/.idea +.project +.classpath +.c9/ +*.launch +.settings/ +*.sublime-workspace + +# IDE - VSCode +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json + +# Environment +.env +.env.local +.env.development +.env.test +.env.production +!.env.example + +# Prisma +prisma/migrations/* +!prisma/migrations/.gitkeep diff --git a/backend/services/authorization-service/.prettierrc b/backend/services/authorization-service/.prettierrc new file mode 100644 index 00000000..54fa2504 --- /dev/null +++ b/backend/services/authorization-service/.prettierrc @@ -0,0 +1,7 @@ +{ + "singleQuote": true, + "trailingComma": "all", + "semi": false, + "printWidth": 100, + "tabWidth": 2 +} diff --git a/backend/services/authorization-service/Dockerfile b/backend/services/authorization-service/Dockerfile index e69de29b..ddc3b005 100644 --- a/backend/services/authorization-service/Dockerfile +++ b/backend/services/authorization-service/Dockerfile @@ -0,0 +1,44 @@ +# Build stage +FROM node:20-alpine AS builder + +WORKDIR /app + +# Copy package files +COPY package*.json ./ +COPY prisma ./prisma/ + +# Install dependencies +RUN npm ci + +# Generate Prisma client +RUN npx prisma generate + +# Copy source code +COPY . . + +# Build application +RUN npm run build + +# Production stage +FROM node:20-alpine AS production + +WORKDIR /app + +# Copy package files +COPY package*.json ./ +COPY prisma ./prisma/ + +# Install production dependencies only +RUN npm ci --only=production + +# Copy Prisma client +COPY --from=builder /app/node_modules/.prisma ./node_modules/.prisma + +# Copy built application +COPY --from=builder /app/dist ./dist + +# Expose port +EXPOSE 3002 + +# Start application +CMD ["node", "dist/main"] diff --git a/backend/services/authorization-service/Dockerfile.test b/backend/services/authorization-service/Dockerfile.test new file mode 100644 index 00000000..944694cb --- /dev/null +++ b/backend/services/authorization-service/Dockerfile.test @@ -0,0 +1,23 @@ +# Test Dockerfile +FROM node:20-alpine + +WORKDIR /app + +# Install OpenSSL for Prisma +RUN apk add --no-cache openssl openssl-dev libc6-compat + +# Copy package files +COPY package*.json ./ +COPY prisma ./prisma/ + +# Install all dependencies (including devDependencies) +RUN npm ci + +# Generate Prisma client +RUN npx prisma generate + +# Copy source code and tests +COPY . . + +# Default command +CMD ["npm", "run", "test:all"] diff --git a/backend/services/authorization-service/Makefile b/backend/services/authorization-service/Makefile new file mode 100644 index 00000000..ae0e837c --- /dev/null +++ b/backend/services/authorization-service/Makefile @@ -0,0 +1,97 @@ +.PHONY: test test-unit test-integration test-e2e test-cov test-docker test-docker-all clean + +# Default test command +test: + npm test + +# Unit tests only +test-unit: + npm run test:unit + +# Integration tests only +test-integration: + npm run test:integration + +# E2E tests only +test-e2e: + npm run test:e2e + +# Run all tests with coverage +test-cov: + npm run test:cov + +# Run all tests locally +test-all: + npm run test:all + +# Docker-based tests +test-docker: + docker-compose -f docker-compose.test.yml up --build --abort-on-container-exit + +test-docker-all: + docker-compose -f docker-compose.test.yml up --build --abort-on-container-exit authorization-service-test + +# Clean up test containers +test-docker-clean: + docker-compose -f docker-compose.test.yml down -v --remove-orphans + +# Build the application +build: + npm run build + +# Lint the code +lint: + npm run lint + +# Format the code +format: + npm run format + +# Generate Prisma client +prisma-generate: + npx prisma generate + +# Run database migrations +prisma-migrate: + npx prisma migrate dev + +# Start development server +dev: + npm run start:dev + +# Start production server +start: + npm run start:prod + +# Clean build artifacts +clean: + rm -rf dist coverage node_modules/.cache + +# Install dependencies +install: + npm ci && npx prisma generate + +# Full CI pipeline +ci: install lint test-all build + @echo "CI pipeline completed successfully" + +# Help +help: + @echo "Available targets:" + @echo " test - Run all tests" + @echo " test-unit - Run unit tests" + @echo " test-integration - Run integration tests" + @echo " test-e2e - Run E2E tests" + @echo " test-cov - Run tests with coverage" + @echo " test-all - Run all test suites" + @echo " test-docker - Run tests in Docker" + @echo " test-docker-all - Run all tests in Docker" + @echo " test-docker-clean - Clean up Docker test containers" + @echo " build - Build the application" + @echo " lint - Lint the code" + @echo " format - Format the code" + @echo " dev - Start development server" + @echo " start - Start production server" + @echo " clean - Clean build artifacts" + @echo " install - Install dependencies" + @echo " ci - Run full CI pipeline" diff --git a/backend/services/authorization-service/docker-compose.test.yml b/backend/services/authorization-service/docker-compose.test.yml new file mode 100644 index 00000000..1edea40f --- /dev/null +++ b/backend/services/authorization-service/docker-compose.test.yml @@ -0,0 +1,75 @@ +services: + test-db: + image: postgres:15-alpine + environment: + POSTGRES_USER: test + POSTGRES_PASSWORD: test + POSTGRES_DB: authorization_test + ports: + - "5433:5432" + healthcheck: + test: ["CMD-SHELL", "pg_isready -U test -d authorization_test"] + interval: 5s + timeout: 5s + retries: 5 + + test-redis: + image: redis:7-alpine + ports: + - "6380:6379" + healthcheck: + test: ["CMD", "redis-cli", "ping"] + interval: 5s + timeout: 5s + retries: 5 + + test-kafka: + image: apache/kafka:3.7.0 + environment: + KAFKA_NODE_ID: 1 + KAFKA_PROCESS_ROLES: broker,controller + KAFKA_LISTENERS: PLAINTEXT://:9092,CONTROLLER://:9093 + KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://test-kafka:9092 + KAFKA_CONTROLLER_LISTENER_NAMES: CONTROLLER + KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: CONTROLLER:PLAINTEXT,PLAINTEXT:PLAINTEXT + KAFKA_CONTROLLER_QUORUM_VOTERS: 1@test-kafka:9093 + KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1 + KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR: 1 + KAFKA_TRANSACTION_STATE_LOG_MIN_ISR: 1 + KAFKA_GROUP_INITIAL_REBALANCE_DELAY_MS: 0 + CLUSTER_ID: MkU3OEVBNTcwNTJENDM2Qk + ports: + - "9093:9092" + healthcheck: + test: ["CMD-SHELL", "/opt/kafka/bin/kafka-topics.sh --bootstrap-server localhost:9092 --list || exit 1"] + interval: 10s + timeout: 10s + retries: 10 + start_period: 30s + + authorization-service-test: + build: + context: . + dockerfile: Dockerfile.test + environment: + DATABASE_URL: postgresql://test:test@test-db:5432/authorization_test + REDIS_HOST: test-redis + REDIS_PORT: 6379 + KAFKA_BROKERS: test-kafka:9092 + JWT_SECRET: test-jwt-secret-key-for-docker-tests + JWT_EXPIRES_IN: 1h + NODE_ENV: test + depends_on: + test-db: + condition: service_healthy + test-redis: + condition: service_healthy + test-kafka: + condition: service_healthy + volumes: + - ./coverage:/app/coverage + command: sh -c "npx prisma migrate deploy && npm run test:unit && npm run test:integration && npm run test:e2e" + +networks: + default: + driver: bridge diff --git a/backend/services/authorization-service/docs/API.md b/backend/services/authorization-service/docs/API.md new file mode 100644 index 00000000..59d58774 --- /dev/null +++ b/backend/services/authorization-service/docs/API.md @@ -0,0 +1,796 @@ +# Authorization Service API 文档 + +## 目录 + +1. [概述](#概述) +2. [认证](#认证) +3. [通用响应格式](#通用响应格式) +4. [用户授权接口](#用户授权接口) +5. [管理员授权接口](#管理员授权接口) +6. [考核接口](#考核接口) +7. [管理员考核接口](#管理员考核接口) +8. [错误码](#错误码) + +--- + +## 概述 + +Authorization Service 提供 RESTful API 用于管理用户授权和月度考核。 + +- **Base URL**: `/api/v1` +- **Content-Type**: `application/json` +- **认证方式**: JWT Bearer Token + +--- + +## 认证 + +所有接口都需要 JWT 认证,除非特别说明。 + +### 请求头 + +```http +Authorization: Bearer +``` + +### Token 结构 + +```json +{ + "sub": "user-id", + "roles": ["USER", "ADMIN"], + "iat": 1700000000, + "exp": 1700086400 +} +``` + +--- + +## 通用响应格式 + +### 成功响应 + +```json +{ + "success": true, + "data": { ... }, + "message": "操作成功" +} +``` + +### 错误响应 + +```json +{ + "success": false, + "error": { + "code": "ERROR_CODE", + "message": "错误描述" + } +} +``` + +--- + +## 用户授权接口 + +### 1. 获取我的授权列表 + +获取当前用户的所有授权角色。 + +**请求** + +```http +GET /api/v1/authorizations/my +``` + +**响应** + +```json +{ + "success": true, + "data": [ + { + "id": "auth-123", + "roleType": "AUTH_PROVINCE_COMPANY", + "status": "ACTIVE", + "provinceCode": "430000", + "provinceName": "湖南省", + "cityCode": null, + "cityName": null, + "communityName": null, + "authorizedAt": "2024-01-15T10:30:00Z", + "createdAt": "2024-01-01T08:00:00Z" + } + ] +} +``` + +--- + +### 2. 申请省代公司授权 + +用户申请成为省代公司。 + +**请求** + +```http +POST /api/v1/authorizations/province +Content-Type: application/json + +{ + "provinceCode": "430000", + "provinceName": "湖南省" +} +``` + +**参数说明** + +| 参数 | 类型 | 必填 | 说明 | +|------|------|------|------| +| provinceCode | string | 是 | 省份代码(6位数字) | +| provinceName | string | 是 | 省份名称 | + +**响应** + +```json +{ + "success": true, + "data": { + "id": "auth-456", + "roleType": "AUTH_PROVINCE_COMPANY", + "status": "PENDING", + "provinceCode": "430000", + "provinceName": "湖南省", + "createdAt": "2024-01-20T09:00:00Z" + }, + "message": "授权申请已提交,等待审核" +} +``` + +**错误场景** + +| 错误码 | 说明 | +|--------|------| +| ALREADY_HAS_AUTHORIZATION | 用户已有省代或市代授权 | +| TEAM_CONFLICT | 本团队已有人申请该区域授权 | +| INVALID_PROVINCE_CODE | 省份代码格式无效 | + +--- + +### 3. 申请市代公司授权 + +用户申请成为市代公司。 + +**请求** + +```http +POST /api/v1/authorizations/city +Content-Type: application/json + +{ + "provinceCode": "430000", + "provinceName": "湖南省", + "cityCode": "430100", + "cityName": "长沙市" +} +``` + +**参数说明** + +| 参数 | 类型 | 必填 | 说明 | +|------|------|------|------| +| provinceCode | string | 是 | 省份代码 | +| provinceName | string | 是 | 省份名称 | +| cityCode | string | 是 | 城市代码(6位数字) | +| cityName | string | 是 | 城市名称 | + +**响应** + +```json +{ + "success": true, + "data": { + "id": "auth-789", + "roleType": "AUTH_CITY_COMPANY", + "status": "PENDING", + "provinceCode": "430000", + "provinceName": "湖南省", + "cityCode": "430100", + "cityName": "长沙市", + "createdAt": "2024-01-20T09:00:00Z" + }, + "message": "授权申请已提交,等待审核" +} +``` + +--- + +### 4. 申请社区授权 + +用户申请成为社区管理员。 + +**请求** + +```http +POST /api/v1/authorizations/community +Content-Type: application/json + +{ + "communityName": "阳光社区" +} +``` + +**参数说明** + +| 参数 | 类型 | 必填 | 说明 | +|------|------|------|------| +| communityName | string | 是 | 社区名称 | + +**响应** + +```json +{ + "success": true, + "data": { + "id": "auth-abc", + "roleType": "COMMUNITY", + "status": "PENDING", + "communityName": "阳光社区", + "createdAt": "2024-01-20T09:00:00Z" + }, + "message": "授权申请已提交,等待审核" +} +``` + +--- + +### 5. 获取授权详情 + +获取指定授权的详细信息。 + +**请求** + +```http +GET /api/v1/authorizations/:id +``` + +**响应** + +```json +{ + "success": true, + "data": { + "id": "auth-123", + "userId": "user-001", + "roleType": "AUTH_PROVINCE_COMPANY", + "status": "ACTIVE", + "provinceCode": "430000", + "provinceName": "湖南省", + "cityCode": null, + "cityName": null, + "communityName": null, + "authorizedBy": "admin-001", + "authorizedAt": "2024-01-15T10:30:00Z", + "activatedAt": "2024-02-01T00:00:00Z", + "createdAt": "2024-01-01T08:00:00Z", + "updatedAt": "2024-02-01T00:00:00Z" + } +} +``` + +--- + +## 管理员授权接口 + +> 需要 `ADMIN` 角色 + +### 1. 创建省代公司授权(直接) + +管理员直接为用户创建省代公司授权。 + +**请求** + +```http +POST /api/v1/admin/authorizations/province-company +Content-Type: application/json + +{ + "userId": "user-001", + "provinceCode": "430000", + "provinceName": "湖南省" +} +``` + +**参数说明** + +| 参数 | 类型 | 必填 | 说明 | +|------|------|------|------| +| userId | string | 是 | 目标用户ID | +| provinceCode | string | 是 | 省份代码 | +| provinceName | string | 是 | 省份名称 | + +**响应** + +```json +{ + "success": true, + "data": { + "id": "auth-123", + "userId": "user-001", + "roleType": "AUTH_PROVINCE_COMPANY", + "status": "APPROVED", + "provinceCode": "430000", + "provinceName": "湖南省", + "authorizedBy": "admin-001", + "authorizedAt": "2024-01-20T10:00:00Z" + }, + "message": "省代公司授权创建成功" +} +``` + +--- + +### 2. 创建市代公司授权(直接) + +管理员直接为用户创建市代公司授权。 + +**请求** + +```http +POST /api/v1/admin/authorizations/city-company +Content-Type: application/json + +{ + "userId": "user-002", + "provinceCode": "430000", + "provinceName": "湖南省", + "cityCode": "430100", + "cityName": "长沙市" +} +``` + +--- + +### 3. 审核授权申请 + +审核用户提交的授权申请。 + +**请求** + +```http +POST /api/v1/admin/authorizations/:id/review +Content-Type: application/json + +{ + "approved": true, + "reason": "审核通过" +} +``` + +**参数说明** + +| 参数 | 类型 | 必填 | 说明 | +|------|------|------|------| +| approved | boolean | 是 | 是否通过 | +| reason | string | 否 | 审核原因/备注 | + +**响应** + +```json +{ + "success": true, + "data": { + "id": "auth-123", + "status": "APPROVED", + "reviewedBy": "admin-001", + "reviewedAt": "2024-01-20T11:00:00Z", + "reviewReason": "审核通过" + }, + "message": "授权审核完成" +} +``` + +--- + +### 4. 撤销授权 + +撤销用户的授权角色。 + +**请求** + +```http +POST /api/v1/admin/authorizations/:id/revoke +Content-Type: application/json + +{ + "reason": "违规操作" +} +``` + +**响应** + +```json +{ + "success": true, + "data": { + "id": "auth-123", + "status": "REVOKED", + "revokedBy": "admin-001", + "revokedAt": "2024-03-01T14:00:00Z", + "revokeReason": "违规操作" + }, + "message": "授权已撤销" +} +``` + +--- + +### 5. 查询待审核列表 + +获取所有待审核的授权申请。 + +**请求** + +```http +GET /api/v1/admin/authorizations/pending?page=1&limit=20 +``` + +**查询参数** + +| 参数 | 类型 | 必填 | 默认值 | 说明 | +|------|------|------|--------|------| +| page | number | 否 | 1 | 页码 | +| limit | number | 否 | 20 | 每页数量 | +| roleType | string | 否 | - | 筛选角色类型 | + +**响应** + +```json +{ + "success": true, + "data": { + "items": [ + { + "id": "auth-456", + "userId": "user-003", + "userName": "张三", + "roleType": "AUTH_PROVINCE_COMPANY", + "status": "PENDING", + "provinceCode": "440000", + "provinceName": "广东省", + "createdAt": "2024-01-19T16:00:00Z" + } + ], + "total": 15, + "page": 1, + "limit": 20, + "totalPages": 1 + } +} +``` + +--- + +### 6. 查询区域授权列表 + +按区域查询授权列表。 + +**请求** + +```http +GET /api/v1/admin/authorizations/region/:regionCode +``` + +**响应** + +```json +{ + "success": true, + "data": [ + { + "id": "auth-123", + "userId": "user-001", + "userName": "李四", + "roleType": "AUTH_PROVINCE_COMPANY", + "status": "ACTIVE", + "provinceCode": "430000", + "provinceName": "湖南省" + } + ] +} +``` + +--- + +## 考核接口 + +### 1. 获取我的考核记录 + +获取当前用户的考核历史。 + +**请求** + +```http +GET /api/v1/assessments/my?page=1&limit=12 +``` + +**响应** + +```json +{ + "success": true, + "data": { + "items": [ + { + "id": "assess-001", + "authorizationId": "auth-123", + "roleType": "AUTH_PROVINCE_COMPANY", + "assessmentMonth": "2024-01", + "monthIndex": 1, + "monthlyTarget": 150, + "cumulativeTarget": 150, + "monthlyCompleted": 180, + "cumulativeCompleted": 180, + "localPercentage": 35.5, + "localPercentagePass": true, + "exceedRatio": 1.2, + "result": "PASS", + "rankingInRegion": 3, + "isFirstPlace": false, + "isBypassed": false, + "assessedAt": "2024-02-01T00:30:00Z" + } + ], + "total": 6, + "page": 1, + "limit": 12 + } +} +``` + +--- + +### 2. 获取当月考核进度 + +获取当前授权的本月考核进度。 + +**请求** + +```http +GET /api/v1/assessments/current/:authorizationId +``` + +**响应** + +```json +{ + "success": true, + "data": { + "authorizationId": "auth-123", + "roleType": "AUTH_PROVINCE_COMPANY", + "currentMonth": "2024-02", + "monthIndex": 2, + "monthlyTarget": 300, + "cumulativeTarget": 450, + "currentProgress": 280, + "cumulativeProgress": 460, + "progressPercentage": 93.3, + "cumulativePercentage": 102.2, + "localTeamCount": 50, + "totalTeamCount": 140, + "localPercentage": 35.7, + "requiredLocalPercentage": 30, + "daysRemaining": 12 + } +} +``` + +--- + +### 3. 获取区域排名 + +获取指定区域的授权排名。 + +**请求** + +```http +GET /api/v1/assessments/rankings/:regionCode?month=2024-01 +``` + +**查询参数** + +| 参数 | 类型 | 必填 | 说明 | +|------|------|------|------| +| month | string | 否 | 考核月份(默认上月) | + +**响应** + +```json +{ + "success": true, + "data": { + "regionCode": "430000", + "regionName": "湖南省", + "month": "2024-01", + "rankings": [ + { + "rank": 1, + "userId": "user-005", + "userName": "王五", + "exceedRatio": 1.85, + "cumulativeCompleted": 277, + "cumulativeTarget": 150, + "isFirstPlace": true + }, + { + "rank": 2, + "userId": "user-001", + "userName": "李四", + "exceedRatio": 1.20, + "cumulativeCompleted": 180, + "cumulativeTarget": 150, + "isFirstPlace": false + } + ] + } +} +``` + +--- + +## 管理员考核接口 + +> 需要 `ADMIN` 角色 + +### 1. 授予考核豁免 + +为指定用户的考核授予单月豁免。 + +**请求** + +```http +POST /api/v1/admin/assessments/:assessmentId/bypass +Content-Type: application/json + +{ + "reason": "特殊情况豁免" +} +``` + +**响应** + +```json +{ + "success": true, + "data": { + "id": "assess-001", + "result": "BYPASSED", + "bypassedBy": "admin-001", + "bypassedAt": "2024-02-15T10:00:00Z" + }, + "message": "豁免已授予" +} +``` + +--- + +### 2. 手动执行月度考核 + +手动触发指定月份的考核计算。 + +**请求** + +```http +POST /api/v1/admin/assessments/run +Content-Type: application/json + +{ + "month": "2024-01", + "roleType": "AUTH_PROVINCE_COMPANY", + "regionCode": "430000" +} +``` + +**参数说明** + +| 参数 | 类型 | 必填 | 说明 | +|------|------|------|------| +| month | string | 是 | 考核月份(YYYY-MM) | +| roleType | string | 否 | 角色类型(不填则全部) | +| regionCode | string | 否 | 区域代码(不填则全部) | + +**响应** + +```json +{ + "success": true, + "data": { + "month": "2024-01", + "processedCount": 45, + "passedCount": 38, + "failedCount": 7, + "bypassedCount": 0 + }, + "message": "考核执行完成" +} +``` + +--- + +### 3. 查询区域考核汇总 + +查询指定区域的考核统计数据。 + +**请求** + +```http +GET /api/v1/admin/assessments/summary/:regionCode?month=2024-01 +``` + +**响应** + +```json +{ + "success": true, + "data": { + "regionCode": "430000", + "month": "2024-01", + "totalAuthorizations": 50, + "assessedCount": 48, + "passedCount": 40, + "failedCount": 6, + "bypassedCount": 2, + "passRate": 83.3, + "averageExceedRatio": 1.15 + } +} +``` + +--- + +## 错误码 + +| 错误码 | HTTP状态码 | 说明 | +|--------|------------|------| +| UNAUTHORIZED | 401 | 未认证或Token无效 | +| FORBIDDEN | 403 | 无权限访问 | +| NOT_FOUND | 404 | 资源不存在 | +| VALIDATION_ERROR | 400 | 请求参数验证失败 | +| ALREADY_HAS_AUTHORIZATION | 400 | 用户已有省代或市代授权 | +| TEAM_CONFLICT | 400 | 团队内存在冲突授权 | +| INVALID_STATUS_TRANSITION | 400 | 无效的状态转换 | +| AUTHORIZATION_NOT_ACTIVE | 400 | 授权未激活 | +| ASSESSMENT_ALREADY_BYPASSED | 400 | 考核已豁免 | +| INTERNAL_ERROR | 500 | 服务器内部错误 | + +--- + +## 枚举值 + +### RoleType(角色类型) + +| 值 | 说明 | +|----|------| +| AUTH_PROVINCE_COMPANY | 省代公司 | +| AUTH_CITY_COMPANY | 市代公司 | +| COMMUNITY | 社区 | + +### AuthorizationStatus(授权状态) + +| 值 | 说明 | +|----|------| +| PENDING | 待审核 | +| APPROVED | 已审核(待激活) | +| REJECTED | 已拒绝 | +| ACTIVE | 已激活 | +| REVOKED | 已撤销 | + +### AssessmentResult(考核结果) + +| 值 | 说明 | +|----|------| +| NOT_ASSESSED | 未考核 | +| PASS | 通过 | +| FAIL | 未通过 | +| BYPASSED | 已豁免 | diff --git a/backend/services/authorization-service/docs/ARCHITECTURE.md b/backend/services/authorization-service/docs/ARCHITECTURE.md new file mode 100644 index 00000000..99c20199 --- /dev/null +++ b/backend/services/authorization-service/docs/ARCHITECTURE.md @@ -0,0 +1,393 @@ +# Authorization Service 架构文档 + +## 目录 + +1. [概述](#概述) +2. [架构设计](#架构设计) +3. [分层架构](#分层架构) +4. [领域模型](#领域模型) +5. [技术栈](#技术栈) +6. [目录结构](#目录结构) + +--- + +## 概述 + +Authorization Service(授权服务)是 RWAdurian 平台的核心微服务之一,负责管理用户的授权角色,包括省代公司、市代公司和社区授权。该服务采用 **领域驱动设计(DDD)** 和 **六边形架构(Hexagonal Architecture)** 模式构建,确保业务逻辑的清晰性和系统的可维护性。 + +### 核心功能 + +- **授权角色管理**:省代公司、市代公司、社区授权的申请、审核、激活和撤销 +- **月度考核评估**:基于阶梯目标的月度业绩考核和排名 +- **团队验证**:推荐链路中的授权冲突检测 +- **事件驱动**:通过 Kafka 发布领域事件,实现服务间解耦 + +--- + +## 架构设计 + +### 六边形架构(端口与适配器) + +``` + ┌─────────────────────────────────────────┐ + │ API Layer │ + │ (Controllers, DTOs, Guards) │ + └──────────────────┬──────────────────────┘ + │ + ┌──────────────────▼──────────────────────┐ + │ Application Layer │ + │ (Commands, Services, Schedulers) │ + └──────────────────┬──────────────────────┘ + │ + ┌──────────────────────────────────▼──────────────────────────────────┐ + │ Domain Layer │ + │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐│ + │ │ Aggregates │ │ Entities │ │Value Objects│ │ Events ││ + │ └─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘│ + │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ + │ │ Repositories│ │ Services │ │ Enums │ │ + │ │ (Interfaces)│ │ (Domain) │ │ │ │ + │ └─────────────┘ └─────────────┘ └─────────────┘ │ + └──────────────────────────────────┬──────────────────────────────────┘ + │ + ┌──────────────────▼──────────────────────┐ + │ Infrastructure Layer │ + │ ┌─────────────┐ ┌─────────────┐ │ + │ │ Prisma │ │ Redis │ │ + │ │ Repositories│ │ Cache │ │ + │ └─────────────┘ └─────────────┘ │ + │ ┌─────────────┐ ┌─────────────┐ │ + │ │ Kafka │ │ External │ │ + │ │ Publisher │ │ Services │ │ + │ └─────────────┘ └─────────────┘ │ + └─────────────────────────────────────────┘ +``` + +### 依赖方向 + +- 外层依赖内层,内层不依赖外层 +- Domain Layer 是核心,不依赖任何外部框架 +- Infrastructure Layer 实现 Domain Layer 定义的接口 +- Application Layer 编排领域对象完成用例 + +--- + +## 分层架构 + +### 1. Domain Layer(领域层) + +领域层是系统的核心,包含所有业务逻辑。 + +#### 聚合根(Aggregates) + +| 聚合根 | 说明 | +|--------|------| +| `AuthorizationRole` | 授权角色聚合根,管理授权的完整生命周期 | +| `MonthlyAssessment` | 月度考核聚合根,管理考核评估和排名 | + +#### 实体(Entities) + +| 实体 | 说明 | +|------|------| +| `LadderTargetRule` | 阶梯目标规则,定义省代/市代/社区的考核目标 | + +#### 值对象(Value Objects) + +| 值对象 | 说明 | +|--------|------| +| `AuthorizationId` | 授权ID | +| `AssessmentId` | 考核ID | +| `UserId` | 用户ID | +| `AdminUserId` | 管理员用户ID | +| `RegionCode` | 区域代码(省/市) | +| `Month` | 月份(YYYY-MM格式) | +| `ValidationResult` | 验证结果 | + +#### 领域服务(Domain Services) + +| 服务 | 说明 | +|------|------| +| `AuthorizationValidatorService` | 授权验证服务,检查推荐链路冲突 | +| `AssessmentCalculatorService` | 考核计算服务,计算业绩和排名 | + +#### 领域事件(Domain Events) + +| 事件 | 触发时机 | +|------|----------| +| `AuthorizationAppliedEvent` | 授权申请提交 | +| `AuthorizationApprovedEvent` | 授权审核通过 | +| `AuthorizationRejectedEvent` | 授权审核拒绝 | +| `AuthorizationActivatedEvent` | 授权激活 | +| `AuthorizationRevokedEvent` | 授权撤销 | +| `MonthlyAssessmentPassedEvent` | 月度考核通过 | +| `MonthlyAssessmentFailedEvent` | 月度考核失败 | +| `MonthlyBypassGrantedEvent` | 授予考核豁免 | +| `FirstPlaceAchievedEvent` | 获得区域第一名 | + +### 2. Application Layer(应用层) + +应用层负责编排领域对象,实现用例。 + +#### 命令(Commands) + +```typescript +// 用户命令 +ApplyProvincialAuthorizationCommand // 申请省代授权 +ApplyCityAuthorizationCommand // 申请市代授权 +ApplyCommunityAuthorizationCommand // 申请社区授权 + +// 管理员命令 +AdminApproveAuthorizationCommand // 审核通过 +AdminRejectAuthorizationCommand // 审核拒绝 +AdminRevokeAuthorizationCommand // 撤销授权 +AdminGrantBypassCommand // 授予豁免 + +// 系统命令 +RunMonthlyAssessmentCommand // 执行月度考核 +``` + +#### 应用服务(Application Services) + +| 服务 | 说明 | +|------|------| +| `AuthorizationCommandService` | 处理授权相关命令 | +| `AuthorizationQueryService` | 处理授权相关查询 | +| `AssessmentCommandService` | 处理考核相关命令 | +| `AssessmentQueryService` | 处理考核相关查询 | + +#### 定时任务(Schedulers) + +| 任务 | 说明 | +|------|------| +| `MonthlyAssessmentScheduler` | 每月1日执行上月考核 | + +### 3. Infrastructure Layer(基础设施层) + +基础设施层提供技术实现。 + +#### 持久化(Persistence) + +- **Prisma ORM**:PostgreSQL 数据库访问 +- **Repository 实现**:实现领域层定义的仓储接口 + +#### 缓存(Cache) + +- **Redis**:缓存热点数据,如用户授权信息 + +#### 消息队列(Messaging) + +- **Kafka**:发布领域事件到其他服务 + +#### 外部服务(External Services) + +- **Identity Service**:用户身份验证 +- **Referral Service**:推荐关系查询 +- **Statistics Service**:团队统计数据查询 + +### 4. API Layer(接口层) + +API 层处理 HTTP 请求和响应。 + +#### 控制器(Controllers) + +| 控制器 | 路由前缀 | 说明 | +|--------|----------|------| +| `AuthorizationController` | `/authorizations` | 用户授权操作 | +| `AdminAuthorizationController` | `/admin/authorizations` | 管理员授权操作 | +| `AssessmentController` | `/assessments` | 考核查询 | +| `AdminAssessmentController` | `/admin/assessments` | 管理员考核操作 | + +--- + +## 领域模型 + +### 授权角色状态机 + +``` + ┌─────────┐ + │ PENDING │ ◄─── 用户申请 + └────┬────┘ + │ + ┌───────┴───────┐ + ▼ ▼ + ┌─────────┐ ┌──────────┐ + │APPROVED │ │ REJECTED │ + └────┬────┘ └──────────┘ + │ + ▼ + ┌─────────┐ + │ ACTIVE │ ◄─── 首月考核通过后激活 + └────┬────┘ + │ + ▼ + ┌─────────┐ + │ REVOKED │ ◄─── 管理员撤销或考核失败 + └─────────┘ +``` + +### 考核规则 + +#### 省代公司阶梯目标 + +| 月份 | 月度目标 | 累计目标 | +|------|----------|----------| +| 1 | 150 | 150 | +| 2 | 300 | 450 | +| 3 | 600 | 1,050 | +| 4 | 1,200 | 2,250 | +| 5 | 2,400 | 4,650 | +| 6 | 4,700 | 9,350 | +| 7 | 6,900 | 16,250 | +| 8 | 10,000 | 26,250 | +| 9+ | 11,750 | 50,000 | + +#### 市代公司阶梯目标 + +| 月份 | 月度目标 | 累计目标 | +|------|----------|----------| +| 1 | 30 | 30 | +| 2 | 60 | 90 | +| 3 | 120 | 210 | +| 4 | 240 | 450 | +| 5 | 480 | 930 | +| 6 | 940 | 1,870 | +| 7 | 1,380 | 3,250 | +| 8 | 2,000 | 5,250 | +| 9+ | 2,350 | 10,000 | + +#### 社区授权目标 + +固定目标:10(无阶梯) + +--- + +## 技术栈 + +| 技术 | 用途 | +|------|------| +| **NestJS** | Node.js 后端框架 | +| **TypeScript** | 编程语言 | +| **Prisma** | ORM 数据库访问 | +| **PostgreSQL** | 关系型数据库 | +| **Redis** | 缓存 | +| **Kafka** | 消息队列 | +| **Jest** | 测试框架 | +| **Docker** | 容器化部署 | + +--- + +## 目录结构 + +``` +authorization-service/ +├── src/ +│ ├── domain/ # 领域层 +│ │ ├── aggregates/ # 聚合根 +│ │ │ ├── authorization-role.aggregate.ts +│ │ │ └── monthly-assessment.aggregate.ts +│ │ ├── entities/ # 实体 +│ │ │ └── ladder-target-rule.entity.ts +│ │ ├── value-objects/ # 值对象 +│ │ │ ├── authorization-id.vo.ts +│ │ │ ├── assessment-id.vo.ts +│ │ │ ├── user-id.vo.ts +│ │ │ ├── region-code.vo.ts +│ │ │ ├── month.vo.ts +│ │ │ └── validation-result.vo.ts +│ │ ├── events/ # 领域事件 +│ │ │ ├── authorization-applied.event.ts +│ │ │ ├── authorization-approved.event.ts +│ │ │ └── ... +│ │ ├── services/ # 领域服务 +│ │ │ ├── authorization-validator.service.ts +│ │ │ └── assessment-calculator.service.ts +│ │ ├── repositories/ # 仓储接口 +│ │ │ ├── authorization-role.repository.ts +│ │ │ └── monthly-assessment.repository.ts +│ │ └── enums/ # 枚举 +│ │ ├── role-type.enum.ts +│ │ ├── authorization-status.enum.ts +│ │ └── assessment-result.enum.ts +│ │ +│ ├── application/ # 应用层 +│ │ ├── commands/ # 命令 +│ │ │ ├── apply-provincial-authorization.command.ts +│ │ │ └── ... +│ │ ├── services/ # 应用服务 +│ │ │ ├── authorization-command.service.ts +│ │ │ ├── authorization-query.service.ts +│ │ │ ├── assessment-command.service.ts +│ │ │ └── assessment-query.service.ts +│ │ └── schedulers/ # 定时任务 +│ │ └── monthly-assessment.scheduler.ts +│ │ +│ ├── infrastructure/ # 基础设施层 +│ │ ├── persistence/ # 持久化 +│ │ │ ├── prisma/ +│ │ │ │ └── prisma.service.ts +│ │ │ └── repositories/ # 仓储实现 +│ │ │ ├── authorization-role.repository.impl.ts +│ │ │ └── monthly-assessment.repository.impl.ts +│ │ ├── cache/ # 缓存 +│ │ │ └── redis.service.ts +│ │ ├── messaging/ # 消息队列 +│ │ │ └── kafka/ +│ │ │ └── event-publisher.service.ts +│ │ └── external/ # 外部服务 +│ │ ├── referral.service.ts +│ │ └── statistics.service.ts +│ │ +│ ├── api/ # 接口层 +│ │ ├── controllers/ # 控制器 +│ │ │ ├── authorization.controller.ts +│ │ │ ├── admin-authorization.controller.ts +│ │ │ ├── assessment.controller.ts +│ │ │ └── admin-assessment.controller.ts +│ │ ├── dtos/ # 数据传输对象 +│ │ │ ├── apply-authorization.dto.ts +│ │ │ └── ... +│ │ └── guards/ # 守卫 +│ │ ├── jwt-auth.guard.ts +│ │ └── admin.guard.ts +│ │ +│ ├── shared/ # 共享模块 +│ │ ├── filters/ # 异常过滤器 +│ │ │ └── global-exception.filter.ts +│ │ ├── interceptors/ # 拦截器 +│ │ │ └── transform.interceptor.ts +│ │ └── exceptions/ # 自定义异常 +│ │ └── domain.exception.ts +│ │ +│ ├── app.module.ts # 应用模块 +│ └── main.ts # 入口文件 +│ +├── prisma/ +│ ├── schema.prisma # 数据库模型 +│ └── migrations/ # 数据库迁移 +│ +├── test/ # 测试 +│ ├── app.e2e-spec.ts # E2E 测试 +│ └── domain-services.integration-spec.ts # 集成测试 +│ +├── docs/ # 文档 +│ ├── ARCHITECTURE.md # 架构文档 +│ ├── API.md # API 文档 +│ ├── DEVELOPMENT.md # 开发指南 +│ ├── TESTING.md # 测试文档 +│ └── DEPLOYMENT.md # 部署文档 +│ +├── docker-compose.test.yml # 测试环境配置 +├── Dockerfile # 生产镜像 +├── Dockerfile.test # 测试镜像 +├── Makefile # 常用命令 +└── package.json # 项目配置 +``` + +--- + +## 参考资料 + +- [Domain-Driven Design Reference](https://www.domainlanguage.com/ddd/reference/) +- [Hexagonal Architecture](https://alistair.cockburn.us/hexagonal-architecture/) +- [NestJS Documentation](https://docs.nestjs.com/) +- [Prisma Documentation](https://www.prisma.io/docs/) diff --git a/backend/services/authorization-service/docs/DEPLOYMENT.md b/backend/services/authorization-service/docs/DEPLOYMENT.md new file mode 100644 index 00000000..82b6038f --- /dev/null +++ b/backend/services/authorization-service/docs/DEPLOYMENT.md @@ -0,0 +1,813 @@ +# Authorization Service 部署文档 + +## 目录 + +1. [部署架构](#部署架构) +2. [环境配置](#环境配置) +3. [Docker 部署](#docker-部署) +4. [Kubernetes 部署](#kubernetes-部署) +5. [数据库迁移](#数据库迁移) +6. [监控与日志](#监控与日志) +7. [故障排除](#故障排除) + +--- + +## 部署架构 + +### 整体架构 + +``` + ┌─────────────────┐ + │ Load Balancer │ + │ (Nginx/ALB) │ + └────────┬────────┘ + │ + ┌───────────────────┼───────────────────┐ + │ │ │ + ┌─────────▼─────────┐ ┌──────▼──────┐ ┌─────────▼─────────┐ + │ Authorization │ │ Identity │ │ Other │ + │ Service │ │ Service │ │ Services │ + │ (Port 3002) │ │ (Port 3001) │ │ │ + └─────────┬─────────┘ └─────────────┘ └──────────────────┘ + │ + ┌─────────┴─────────────────────────────────┐ + │ │ +┌───▼───┐ ┌────────┐ ┌────────┐ ┌──────────┐ +│ DB │ │ Redis │ │ Kafka │ │ External │ +│(PG 15)│ │ (7.x) │ │(3.7.x) │ │ Services │ +└───────┘ └────────┘ └────────┘ └──────────┘ +``` + +### 服务依赖 + +| 依赖 | 版本 | 用途 | +|------|------|------| +| PostgreSQL | 15.x | 主数据库 | +| Redis | 7.x | 缓存、会话 | +| Kafka | 3.7.x | 事件消息队列 | +| Identity Service | - | JWT 验证 | +| Referral Service | - | 推荐关系查询 | +| Statistics Service | - | 团队统计查询 | + +--- + +## 环境配置 + +### 环境变量 + +```bash +# .env.production + +# 应用配置 +NODE_ENV=production +PORT=3002 + +# 数据库配置 +DATABASE_URL=postgresql://user:password@db-host:5432/authorization_prod + +# Redis 配置 +REDIS_HOST=redis-host +REDIS_PORT=6379 +REDIS_PASSWORD=redis-password + +# Kafka 配置 +KAFKA_BROKERS=kafka-1:9092,kafka-2:9092,kafka-3:9092 +KAFKA_CLIENT_ID=authorization-service +KAFKA_CONSUMER_GROUP=authorization-service-group + +# JWT 配置 +JWT_SECRET=your-production-jwt-secret-key +JWT_EXPIRES_IN=1h + +# 外部服务 +IDENTITY_SERVICE_URL=http://identity-service:3001 +REFERRAL_SERVICE_URL=http://referral-service:3003 +STATISTICS_SERVICE_URL=http://statistics-service:3004 + +# 日志 +LOG_LEVEL=info +``` + +### 配置说明 + +| 配置项 | 说明 | 默认值 | +|--------|------|--------| +| NODE_ENV | 运行环境 | production | +| PORT | 服务端口 | 3002 | +| DATABASE_URL | PostgreSQL 连接字符串 | - | +| REDIS_HOST | Redis 主机地址 | localhost | +| REDIS_PORT | Redis 端口 | 6379 | +| KAFKA_BROKERS | Kafka broker 地址列表 | - | +| JWT_SECRET | JWT 签名密钥 | - | +| LOG_LEVEL | 日志级别 | info | + +### 密钥管理 + +生产环境建议使用密钥管理服务: + +- **AWS**: AWS Secrets Manager / Parameter Store +- **阿里云**: KMS / 密钥管理服务 +- **Kubernetes**: Secrets + +--- + +## Docker 部署 + +### 生产 Dockerfile + +```dockerfile +# Dockerfile + +# 构建阶段 +FROM node:20-alpine AS builder + +WORKDIR /app + +# 安装 OpenSSL +RUN apk add --no-cache openssl openssl-dev libc6-compat + +# 复制依赖文件 +COPY package*.json ./ +COPY prisma ./prisma/ + +# 安装依赖 +RUN npm ci --only=production + +# 生成 Prisma 客户端 +RUN npx prisma generate + +# 复制源代码 +COPY . . + +# 构建 +RUN npm run build + +# 生产阶段 +FROM node:20-alpine AS production + +WORKDIR /app + +# 安装运行时依赖 +RUN apk add --no-cache openssl libc6-compat + +# 复制构建产物 +COPY --from=builder /app/dist ./dist +COPY --from=builder /app/node_modules ./node_modules +COPY --from=builder /app/package*.json ./ +COPY --from=builder /app/prisma ./prisma + +# 创建非 root 用户 +RUN addgroup -g 1001 -S nodejs && \ + adduser -S nestjs -u 1001 && \ + chown -R nestjs:nodejs /app + +USER nestjs + +# 健康检查 +HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \ + CMD wget --no-verbose --tries=1 --spider http://localhost:3002/health || exit 1 + +EXPOSE 3002 + +CMD ["node", "dist/main.js"] +``` + +### Docker Compose (生产) + +```yaml +# docker-compose.prod.yml + +version: '3.8' + +services: + authorization-service: + build: + context: . + dockerfile: Dockerfile + ports: + - "3002:3002" + environment: + - NODE_ENV=production + - DATABASE_URL=postgresql://postgres:password@db:5432/authorization + - REDIS_HOST=redis + - REDIS_PORT=6379 + - KAFKA_BROKERS=kafka:9092 + - JWT_SECRET=${JWT_SECRET} + depends_on: + db: + condition: service_healthy + redis: + condition: service_healthy + kafka: + condition: service_healthy + restart: unless-stopped + deploy: + resources: + limits: + cpus: '1' + memory: 1G + reservations: + cpus: '0.5' + memory: 512M + + db: + image: postgres:15-alpine + environment: + POSTGRES_USER: postgres + POSTGRES_PASSWORD: password + POSTGRES_DB: authorization + volumes: + - postgres_data:/var/lib/postgresql/data + healthcheck: + test: ["CMD-SHELL", "pg_isready -U postgres"] + interval: 10s + timeout: 5s + retries: 5 + restart: unless-stopped + + redis: + image: redis:7-alpine + command: redis-server --requirepass ${REDIS_PASSWORD} + volumes: + - redis_data:/data + healthcheck: + test: ["CMD", "redis-cli", "ping"] + interval: 10s + timeout: 5s + retries: 5 + restart: unless-stopped + + kafka: + image: apache/kafka:3.7.0 + environment: + KAFKA_NODE_ID: 1 + KAFKA_PROCESS_ROLES: broker,controller + KAFKA_LISTENERS: PLAINTEXT://:9092,CONTROLLER://:9093 + KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka:9092 + KAFKA_CONTROLLER_LISTENER_NAMES: CONTROLLER + KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: CONTROLLER:PLAINTEXT,PLAINTEXT:PLAINTEXT + KAFKA_CONTROLLER_QUORUM_VOTERS: 1@kafka:9093 + KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1 + CLUSTER_ID: MkU3OEVBNTcwNTJENDM2Qk + volumes: + - kafka_data:/var/lib/kafka/data + healthcheck: + test: ["CMD-SHELL", "/opt/kafka/bin/kafka-topics.sh --bootstrap-server localhost:9092 --list"] + interval: 30s + timeout: 10s + retries: 5 + start_period: 60s + restart: unless-stopped + +volumes: + postgres_data: + redis_data: + kafka_data: + +networks: + default: + driver: bridge +``` + +### 构建和部署 + +```bash +# 构建镜像 +docker build -t authorization-service:latest . + +# 推送到镜像仓库 +docker tag authorization-service:latest your-registry/authorization-service:latest +docker push your-registry/authorization-service:latest + +# 使用 Docker Compose 部署 +docker compose -f docker-compose.prod.yml up -d + +# 查看日志 +docker compose -f docker-compose.prod.yml logs -f authorization-service + +# 扩容 +docker compose -f docker-compose.prod.yml up -d --scale authorization-service=3 +``` + +--- + +## Kubernetes 部署 + +### Deployment + +```yaml +# k8s/deployment.yaml + +apiVersion: apps/v1 +kind: Deployment +metadata: + name: authorization-service + namespace: rwadurian + labels: + app: authorization-service +spec: + replicas: 3 + selector: + matchLabels: + app: authorization-service + template: + metadata: + labels: + app: authorization-service + spec: + containers: + - name: authorization-service + image: your-registry/authorization-service:latest + ports: + - containerPort: 3002 + env: + - name: NODE_ENV + value: "production" + - name: PORT + value: "3002" + - name: DATABASE_URL + valueFrom: + secretKeyRef: + name: authorization-secrets + key: database-url + - name: REDIS_HOST + valueFrom: + configMapKeyRef: + name: authorization-config + key: redis-host + - name: REDIS_PORT + valueFrom: + configMapKeyRef: + name: authorization-config + key: redis-port + - name: KAFKA_BROKERS + valueFrom: + configMapKeyRef: + name: authorization-config + key: kafka-brokers + - name: JWT_SECRET + valueFrom: + secretKeyRef: + name: authorization-secrets + key: jwt-secret + resources: + requests: + memory: "512Mi" + cpu: "250m" + limits: + memory: "1Gi" + cpu: "1000m" + readinessProbe: + httpGet: + path: /health + port: 3002 + initialDelaySeconds: 10 + periodSeconds: 5 + livenessProbe: + httpGet: + path: /health + port: 3002 + initialDelaySeconds: 30 + periodSeconds: 10 + imagePullSecrets: + - name: registry-credentials +``` + +### Service + +```yaml +# k8s/service.yaml + +apiVersion: v1 +kind: Service +metadata: + name: authorization-service + namespace: rwadurian +spec: + selector: + app: authorization-service + ports: + - port: 3002 + targetPort: 3002 + type: ClusterIP +``` + +### ConfigMap + +```yaml +# k8s/configmap.yaml + +apiVersion: v1 +kind: ConfigMap +metadata: + name: authorization-config + namespace: rwadurian +data: + redis-host: "redis-master.redis.svc.cluster.local" + redis-port: "6379" + kafka-brokers: "kafka-0.kafka.svc.cluster.local:9092,kafka-1.kafka.svc.cluster.local:9092" +``` + +### Secret + +```yaml +# k8s/secret.yaml + +apiVersion: v1 +kind: Secret +metadata: + name: authorization-secrets + namespace: rwadurian +type: Opaque +stringData: + database-url: "postgresql://user:password@postgres:5432/authorization" + jwt-secret: "your-production-jwt-secret" +``` + +### Ingress + +```yaml +# k8s/ingress.yaml + +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: authorization-ingress + namespace: rwadurian + annotations: + nginx.ingress.kubernetes.io/rewrite-target: / +spec: + ingressClassName: nginx + rules: + - host: api.rwadurian.com + http: + paths: + - path: /authorization + pathType: Prefix + backend: + service: + name: authorization-service + port: + number: 3002 +``` + +### HPA (自动扩缩容) + +```yaml +# k8s/hpa.yaml + +apiVersion: autoscaling/v2 +kind: HorizontalPodAutoscaler +metadata: + name: authorization-service-hpa + namespace: rwadurian +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: authorization-service + minReplicas: 2 + maxReplicas: 10 + metrics: + - type: Resource + resource: + name: cpu + target: + type: Utilization + averageUtilization: 70 + - type: Resource + resource: + name: memory + target: + type: Utilization + averageUtilization: 80 +``` + +### 部署命令 + +```bash +# 创建命名空间 +kubectl create namespace rwadurian + +# 应用配置 +kubectl apply -f k8s/ + +# 查看部署状态 +kubectl get pods -n rwadurian -l app=authorization-service + +# 查看日志 +kubectl logs -f -n rwadurian -l app=authorization-service + +# 扩缩容 +kubectl scale deployment authorization-service -n rwadurian --replicas=5 +``` + +--- + +## 数据库迁移 + +### 迁移策略 + +1. **新部署**: 自动运行所有迁移 +2. **升级部署**: 先迁移数据库,再部署新版本 +3. **回滚**: 支持向下迁移 + +### 迁移命令 + +```bash +# 创建新迁移 +npx prisma migrate dev --name add_new_field + +# 应用迁移(生产) +npx prisma migrate deploy + +# 重置数据库(仅开发) +npx prisma migrate reset + +# 查看迁移状态 +npx prisma migrate status +``` + +### 迁移脚本 + +```bash +#!/bin/bash +# scripts/migrate.sh + +set -e + +echo "Running database migrations..." + +# 等待数据库就绪 +until npx prisma migrate status > /dev/null 2>&1; do + echo "Waiting for database..." + sleep 2 +done + +# 运行迁移 +npx prisma migrate deploy + +echo "Migrations completed successfully!" +``` + +### Kubernetes Job (迁移) + +```yaml +# k8s/migration-job.yaml + +apiVersion: batch/v1 +kind: Job +metadata: + name: authorization-migration + namespace: rwadurian +spec: + template: + spec: + containers: + - name: migration + image: your-registry/authorization-service:latest + command: ["npx", "prisma", "migrate", "deploy"] + env: + - name: DATABASE_URL + valueFrom: + secretKeyRef: + name: authorization-secrets + key: database-url + restartPolicy: Never + backoffLimit: 3 +``` + +--- + +## 监控与日志 + +### 健康检查端点 + +```typescript +// src/health/health.controller.ts + +@Controller('health') +export class HealthController { + constructor( + private health: HealthCheckService, + private db: PrismaHealthIndicator, + private redis: RedisHealthIndicator, + ) {} + + @Get() + @HealthCheck() + check() { + return this.health.check([ + () => this.db.pingCheck('database'), + () => this.redis.pingCheck('redis'), + ]) + } + + @Get('live') + live() { + return { status: 'ok' } + } + + @Get('ready') + @HealthCheck() + ready() { + return this.health.check([ + () => this.db.pingCheck('database'), + ]) + } +} +``` + +### Prometheus 指标 + +```typescript +// src/metrics/metrics.module.ts + +import { PrometheusModule } from '@willsoto/nestjs-prometheus' + +@Module({ + imports: [ + PrometheusModule.register({ + defaultMetrics: { + enabled: true, + }, + path: '/metrics', + }), + ], +}) +export class MetricsModule {} +``` + +### 日志配置 + +```typescript +// src/main.ts + +import { WinstonModule } from 'nest-winston' +import * as winston from 'winston' + +const app = await NestFactory.create(AppModule, { + logger: WinstonModule.createLogger({ + transports: [ + new winston.transports.Console({ + format: winston.format.combine( + winston.format.timestamp(), + winston.format.json(), + ), + }), + ], + }), +}) +``` + +### 结构化日志格式 + +```json +{ + "timestamp": "2024-01-20T10:30:00.000Z", + "level": "info", + "message": "Authorization created", + "service": "authorization-service", + "traceId": "abc-123", + "userId": "user-001", + "authorizationId": "auth-456", + "roleType": "AUTH_PROVINCE_COMPANY" +} +``` + +--- + +## 故障排除 + +### 常见问题 + +#### 1. 数据库连接失败 + +```bash +# 检查数据库连接 +npx prisma db pull + +# 查看连接字符串 +echo $DATABASE_URL + +# 测试网络连通性 +nc -zv db-host 5432 +``` + +#### 2. Redis 连接失败 + +```bash +# 测试 Redis 连接 +redis-cli -h redis-host -p 6379 -a password ping + +# 查看 Redis 状态 +redis-cli -h redis-host info +``` + +#### 3. Kafka 连接失败 + +```bash +# 列出 topics +kafka-topics.sh --bootstrap-server kafka:9092 --list + +# 查看 consumer groups +kafka-consumer-groups.sh --bootstrap-server kafka:9092 --list +``` + +#### 4. Pod 启动失败 + +```bash +# 查看 Pod 状态 +kubectl describe pod -n rwadurian + +# 查看容器日志 +kubectl logs -n rwadurian + +# 进入容器调试 +kubectl exec -it -n rwadurian -- sh +``` + +### 性能调优 + +#### 数据库连接池 + +```typescript +// prisma/schema.prisma + +datasource db { + provider = "postgresql" + url = env("DATABASE_URL") + // 连接池配置 + // ?connection_limit=20&pool_timeout=30 +} +``` + +#### Redis 连接池 + +```typescript +// src/infrastructure/cache/redis.service.ts + +const redis = new Redis({ + host: process.env.REDIS_HOST, + port: parseInt(process.env.REDIS_PORT), + password: process.env.REDIS_PASSWORD, + maxRetriesPerRequest: 3, + enableReadyCheck: true, + // 连接池大小 + lazyConnect: true, +}) +``` + +### 运维命令 + +```bash +# 查看服务状态 +kubectl get all -n rwadurian + +# 查看资源使用 +kubectl top pods -n rwadurian + +# 滚动更新 +kubectl rollout restart deployment/authorization-service -n rwadurian + +# 回滚 +kubectl rollout undo deployment/authorization-service -n rwadurian + +# 查看回滚历史 +kubectl rollout history deployment/authorization-service -n rwadurian +``` + +--- + +## 部署检查清单 + +### 部署前 + +- [ ] 环境变量配置完成 +- [ ] 数据库迁移已准备 +- [ ] 镜像已构建并推送 +- [ ] 配置文件已验证 +- [ ] 密钥已配置 + +### 部署中 + +- [ ] 数据库迁移成功 +- [ ] Pod 启动正常 +- [ ] 健康检查通过 +- [ ] 服务可访问 + +### 部署后 + +- [ ] 功能测试通过 +- [ ] 监控指标正常 +- [ ] 日志无异常 +- [ ] 通知相关人员 + +--- + +## 参考资源 + +- [Docker 官方文档](https://docs.docker.com/) +- [Kubernetes 官方文档](https://kubernetes.io/docs/) +- [Prisma 部署指南](https://www.prisma.io/docs/guides/deployment) +- [NestJS 部署指南](https://docs.nestjs.com/faq/common-errors) diff --git a/backend/services/authorization-service/docs/DEVELOPMENT.md b/backend/services/authorization-service/docs/DEVELOPMENT.md new file mode 100644 index 00000000..8490a84e --- /dev/null +++ b/backend/services/authorization-service/docs/DEVELOPMENT.md @@ -0,0 +1,615 @@ +# Authorization Service 开发指南 + +## 目录 + +1. [环境准备](#环境准备) +2. [项目初始化](#项目初始化) +3. [开发规范](#开发规范) +4. [代码结构约定](#代码结构约定) +5. [DDD 实践指南](#ddd-实践指南) +6. [常见开发任务](#常见开发任务) +7. [调试技巧](#调试技巧) + +--- + +## 环境准备 + +### 系统要求 + +| 软件 | 版本 | 说明 | +|------|------|------| +| Node.js | 20.x LTS | 推荐使用 nvm 管理 | +| npm | 10.x | 随 Node.js 安装 | +| PostgreSQL | 15.x | 关系型数据库 | +| Redis | 7.x | 缓存服务 | +| Kafka | 3.7.x | 消息队列 | +| Docker | 24.x | 容器运行时 | + +### 开发工具推荐 + +- **IDE**: VSCode +- **VSCode 扩展**: + - ESLint + - Prettier + - Prisma + - REST Client + - Docker + +### 本地环境配置 + +#### 1. 安装 Node.js + +```bash +# 使用 nvm 安装 Node.js +nvm install 20 +nvm use 20 +``` + +#### 2. 启动基础设施 + +使用 Docker Compose 启动开发环境: + +```bash +# 启动 PostgreSQL、Redis、Kafka +docker compose up -d +``` + +#### 3. 配置环境变量 + +复制环境变量模板: + +```bash +cp .env.example .env.development +``` + +编辑 `.env.development`: + +```env +# 数据库 +DATABASE_URL="postgresql://postgres:postgres@localhost:5432/authorization_dev" + +# Redis +REDIS_HOST=localhost +REDIS_PORT=6379 + +# Kafka +KAFKA_BROKERS=localhost:9092 + +# JWT +JWT_SECRET=your-development-secret-key +JWT_EXPIRES_IN=1h + +# 应用 +NODE_ENV=development +PORT=3002 +``` + +--- + +## 项目初始化 + +### 1. 安装依赖 + +```bash +npm install +``` + +### 2. 生成 Prisma 客户端 + +```bash +npx prisma generate +``` + +### 3. 运行数据库迁移 + +```bash +npx prisma migrate dev +``` + +### 4. 启动开发服务器 + +```bash +npm run start:dev +``` + +服务将在 `http://localhost:3002` 启动。 + +### 5. 访问 API 文档 + +``` +http://localhost:3002/api/docs +``` + +--- + +## 开发规范 + +### 代码风格 + +项目使用 ESLint + Prettier 进行代码规范检查: + +```bash +# 检查代码风格 +npm run lint + +# 自动修复 +npm run lint:fix + +# 格式化代码 +npm run format +``` + +### 命名规范 + +#### 文件命名 + +| 类型 | 格式 | 示例 | +|------|------|------| +| 聚合根 | `{name}.aggregate.ts` | `authorization-role.aggregate.ts` | +| 实体 | `{name}.entity.ts` | `ladder-target-rule.entity.ts` | +| 值对象 | `{name}.vo.ts` | `month.vo.ts` | +| 服务 | `{name}.service.ts` | `authorization-command.service.ts` | +| 控制器 | `{name}.controller.ts` | `authorization.controller.ts` | +| 仓储接口 | `{name}.repository.ts` | `authorization-role.repository.ts` | +| 仓储实现 | `{name}.repository.impl.ts` | `authorization-role.repository.impl.ts` | +| DTO | `{name}.dto.ts` | `apply-authorization.dto.ts` | +| 测试 | `{name}.spec.ts` | `authorization-role.aggregate.spec.ts` | + +#### 类命名 + +| 类型 | 格式 | 示例 | +|------|------|------| +| 聚合根 | `{Name}` | `AuthorizationRole` | +| 值对象 | `{Name}` | `Month`, `RegionCode` | +| 服务 | `{Name}Service` | `AuthorizationCommandService` | +| 控制器 | `{Name}Controller` | `AuthorizationController` | +| 仓储接口 | `I{Name}Repository` | `IAuthorizationRoleRepository` | +| DTO | `{Name}Dto` | `ApplyAuthorizationDto` | + +### Git 提交规范 + +使用 Conventional Commits 规范: + +``` +(): + + + +