Files
hfb_sys/docs/OPTIMIZATION_PLAN.md
T
2026-06-05 03:20:01 +08:00

803 lines
19 KiB
Markdown

# HFB Sys 项目优化计划
> 生成时间: 2026-06-05
> 项目版本: refactor/features-architecture 分支
## 项目概况
**项目名称**: HFB Sys - 哈夫币租号平台
**技术栈**:
- 后端: Go 1.26 + Gin + GORM + MySQL 8.4 + Redis 7.4
- 前端: Vue 3 + TypeScript + Vite + Element Plus + Vant
- 基础设施: Docker Compose + MinIO
**代码规模**:
- 后端: 120个Go文件, ~18,625行代码, 21个业务模块
- 前端: 69个Vue组件, ~34,750行代码, 10个功能模块
- 测试覆盖: 仅5个测试文件 (覆盖率极低 ⚠️)
- 依赖包: 185MB node_modules
---
## 🔴 高优先级问题 (立即处理)
### 1. 测试覆盖率严重不足
**问题描述**:
- 整个项目只有5个测试文件 (`backend/internal/modules/order/repository_test.go` 等)
- 核心业务逻辑(订单、钱包、支付)缺少单元测试
- 前端组件完全没有测试
**影响范围**:
- 代码质量无法保证
- 重构和功能迭代风险高
- 无法及时发现回归问题
- 金额计算、订单状态流转等关键逻辑缺少验证
**解决方案**:
#### 后端测试
```go
// 示例: backend/internal/modules/wallet/service_test.go
package wallet_test
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
)
func TestWalletService_Deduct(t *testing.T) {
// 测试余额扣减
// 测试余额不足情况
// 测试并发扣减
}
```
#### 前端测试
```typescript
// 示例: frontend/src/features/orders/__tests__/OrderList.spec.ts
import { describe, it, expect } from 'vitest'
import { mount } from '@vue/test-utils'
import OrderList from '../views/OrderList.vue'
describe('OrderList', () => {
it('renders order items correctly', () => {
// 测试订单列表渲染
})
})
```
**目标**:
- [ ] 核心模块测试覆盖率达到 70%
- [ ] 订单模块: 状态流转、金额计算、超时处理
- [ ] 钱包模块: 余额操作、并发控制、流水记录
- [ ] 支付模块: 支付回调、退款流程
- [ ] 前端核心组件测试覆盖率 50%+
**工具选择**:
- 后端: Go `testing` + `testify` + `mockery`
- 前端: `vitest` + `@vue/test-utils`
---
### 2. 数据库索引优化不足
**问题描述**:
- 迁移文件 `backend/migrations/000001_init.sql` 中索引定义85个
- 缺少针对高频复合查询的覆盖索引
- 时间范围查询缺少优化
**影响范围**:
- 订单列表查询可能较慢
- 钱包流水查询性能不佳
- 后台管理页面加载慢
**需要添加的索引**:
```sql
-- 1. 订单状态+创建时间查询 (后台订单管理、用户订单列表)
CREATE INDEX idx_rental_orders_status_created_at
ON rental_orders(status, created_at DESC);
-- 2. 钱包流水按用户+业务类型+时间查询
CREATE INDEX idx_wallet_ledger_user_biz_created
ON wallet_ledger(user_id, biz_type, created_at DESC);
-- 3. 订单结算状态查询优化
CREATE INDEX idx_rental_orders_settlement
ON rental_orders(settlement_status, owner_id, created_at);
-- 4. 商品筛选查询优化
CREATE INDEX idx_rental_listings_published
ON rental_listings(status, review_status, published_at DESC)
WHERE in_transaction = 0;
-- 5. 用户实名认证状态查询
CREATE INDEX idx_users_realname_status
ON users(realname_status, status);
```
**执行计划**:
- [ ] 创建新的迁移文件 `000003_add_indexes.sql`
- [ ] 在开发环境验证查询性能提升
- [ ] 使用 `EXPLAIN` 分析慢查询
- [ ] 生产环境灰度添加索引
**监控指标**:
- 慢查询日志 (>100ms)
- 索引命中率
- 查询平均响应时间
---
### 3. 环境变量管理不规范
**问题描述**:
- `.env.example` 包含40个环境变量
- JWT_SECRET 默认值为 "change-me" (安全隐患)
- 配置分散在多个文件中
- 缺少配置校验
**影响范围**:
- 生产环境可能使用不安全的默认值
- 配置错误难以发现
- 缺少环境差异化配置管理
**解决方案**:
#### 1. 使用 Viper 进行配置管理
```go
// backend/internal/config/config.go
package config
import (
"github.com/spf13/viper"
)
type Config struct {
App AppConfig
Database DatabaseConfig
JWT JWTConfig
// ...
}
func Load() (*Config, error) {
viper.SetConfigName("config")
viper.SetConfigType("yaml")
viper.AddConfigPath("./configs")
viper.AutomaticEnv()
// 配置校验
if err := viper.ReadInConfig(); err != nil {
return nil, err
}
var cfg Config
if err := viper.Unmarshal(&cfg); err != nil {
return nil, err
}
// 安全配置强制校验
if cfg.App.Env == "production" {
if cfg.JWT.Secret == "change-me" || len(cfg.JWT.Secret) < 32 {
return nil, errors.New("production JWT secret must be set and >= 32 chars")
}
}
return &cfg, nil
}
```
#### 2. 配置文件分层
```
backend/configs/
├── config.yaml # 默认配置
├── config.dev.yaml # 开发环境覆盖
├── config.prod.yaml # 生产环境覆盖
└── config.local.yaml # 本地开发 (git ignore)
```
#### 3. 使用密钥管理服务
```yaml
# 生产环境使用 Vault / AWS Secrets Manager
jwt:
secret: ${VAULT_JWT_SECRET}
database:
password: ${VAULT_DB_PASSWORD}
```
**执行计划**:
- [ ] 安装 Viper: `go get github.com/spf13/viper`
- [ ] 重构 `internal/config`
- [ ] 迁移环境变量到 YAML 配置
- [ ] 添加配置校验逻辑
- [ ] 更新文档和部署脚本
---
### 4. 前端缺少统一的 HTTP 请求封装
**问题描述**:
- 未找到 `frontend/src/utils/request.ts` 文件
- axios 直接在各个 API 文件中使用
- Token 刷新、错误处理逻辑可能分散
- 缺少统一的 Loading 状态管理
**影响范围**:
- Token 过期处理不一致
- 错误提示用户体验差
- 重复代码多
- 难以统一添加日志、监控
**解决方案**:
```typescript
// frontend/src/utils/request.ts
import axios, { AxiosError, AxiosRequestConfig } from 'axios'
import { ElMessage } from 'element-plus'
import { useSessionStore } from '@/stores/session'
import { getAccessToken, getRefreshToken, setAuthTokens } from '@/utils/authStorage'
// 创建 axios 实例
const request = axios.create({
baseURL: '/api',
timeout: 30000,
})
// 请求拦截器
request.interceptors.request.use(
(config) => {
// 自动添加 Token
const token = getAccessToken('user')
if (token) {
config.headers.Authorization = `Bearer ${token}`
}
return config
},
(error) => {
return Promise.reject(error)
}
)
// 响应拦截器
let isRefreshing = false
let refreshSubscribers: Array<(token: string) => void> = []
request.interceptors.response.use(
(response) => {
return response.data
},
async (error: AxiosError) => {
const originalRequest = error.config as AxiosRequestConfig & { _retry?: boolean }
// Token 过期,尝试刷新
if (error.response?.status === 401 && !originalRequest._retry) {
if (isRefreshing) {
// 等待 Token 刷新完成
return new Promise((resolve) => {
refreshSubscribers.push((token: string) => {
if (originalRequest.headers) {
originalRequest.headers.Authorization = `Bearer ${token}`
}
resolve(request(originalRequest))
})
})
}
originalRequest._retry = true
isRefreshing = true
try {
const refreshToken = getRefreshToken('user')
if (!refreshToken) {
throw new Error('No refresh token')
}
// 调用刷新接口
const { data } = await axios.post('/api/auth/refresh', {
refresh_token: refreshToken,
})
setAuthTokens('user', {
access_token: data.access_token,
refresh_token: data.refresh_token,
})
// 通知所有等待的请求
refreshSubscribers.forEach((callback) => callback(data.access_token))
refreshSubscribers = []
// 重试原请求
if (originalRequest.headers) {
originalRequest.headers.Authorization = `Bearer ${data.access_token}`
}
return request(originalRequest)
} catch (refreshError) {
// 刷新失败,跳转登录
const session = useSessionStore()
session.logout()
window.location.href = '/login'
return Promise.reject(refreshError)
} finally {
isRefreshing = false
}
}
// 统一错误处理
const message = error.response?.data?.message || error.message || '请求失败'
ElMessage.error(message)
return Promise.reject(error)
}
)
export default request
```
**使用方式**:
```typescript
// frontend/src/features/orders/api/orders.ts
import request from '@/utils/request'
export const fetchOrders = (params: any) => {
return request.get('/orders', { params })
}
```
**执行计划**:
- [ ] 创建 `frontend/src/utils/request.ts`
- [ ] 重构所有 API 调用使用统一封装
- [ ] 实现 Token 刷新机制
- [ ] 添加请求/响应日志
- [ ] 测试 Token 过期场景
---
## 🟠 中优先级改进 (近期处理)
### 5. 路由守卫性能问题
**问题**: `frontend/src/router/index.ts``beforeEach` 在每次导航时都可能调用 `session.loadMe()`
**优化方案**:
```typescript
// frontend/src/router/index.ts
router.beforeEach(async (to) => {
// ... 其他逻辑
if (to.meta.requiresAuth) {
const session = useSessionStore()
session.syncFromStorage()
const hasToken = !!session.token
if (!hasToken) {
const loginPath = getLoginPath('user', to.path)
return { path: loginPath, query: { redirect: to.fullPath } }
}
// 优化: 添加缓存判断和加载中标志
if (!session.phone && !session._loadingMe) {
session._loadingMe = true
try {
await session.loadMe()
} catch (error) {
const loginPath = getLoginPath('user', to.path)
return { path: loginPath, query: { redirect: to.fullPath } }
} finally {
session._loadingMe = false
}
}
}
// ... 其他逻辑
})
```
---
### 6. 前端组件复用性不足
**现状**:
- `frontend/src/components/` 仅有2个共享组件
- `frontend/src/shared/composables/` 仅有4个组合式函数
- 许多页面可能存在重复的 UI 逻辑
**建议提取的通用组件**:
- [ ] `StatusTag.vue` - 状态标签
- [ ] `DateRangePicker.vue` - 日期范围选择
- [ ] `EmptyState.vue` - 空状态占位
- [ ] `LoadingOverlay.vue` - 加载遮罩
- [ ] `ImageUpload.vue` - 图片上传
- [ ] `MoneyDisplay.vue` - 金额展示
- [ ] `OrderStatusFlow.vue` - 订单状态流程
**建议提取的 Composables**:
- [ ] `useTable.ts` - 表格分页、排序
- [ ] `useForm.ts` - 表单验证、提交
- [ ] `useModal.ts` - 弹窗状态管理
- [ ] `usePermission.ts` - 权限判断
- [ ] `useWebSocket.ts` - WebSocket 连接
---
### 7. 后端路由初始化代码过长
**问题**: `backend/internal/router/router.go` 文件415行,所有依赖注入在一个 `New` 函数中
**重构方案**:
#### 方案一: 模块化路由注册
```go
// backend/internal/router/router.go
func New(cfg config.Config, deps Dependencies, logger *zap.Logger) *gin.Engine {
engine := gin.New()
engine.Use(middleware.RequestID())
engine.Use(middleware.RequestLogger(logger))
engine.Use(middleware.Recovery(logger))
// 初始化依赖
services := initServices(cfg, deps, logger)
// 注册路由
api := engine.Group("/api")
registerAuthRoutes(api, services.auth)
registerUserRoutes(api, services.user, services.auth.JWTManager)
registerOrderRoutes(api, services.order, services.auth.JWTManager)
// ...
return engine
}
// backend/internal/router/auth_routes.go
func registerAuthRoutes(api *gin.RouterGroup, authSvc *auth.Service) {
authHandler := auth.NewHandler(authSvc)
authRoutes := api.Group("/auth")
{
authRoutes.POST("/sms/send", authHandler.SendSMS)
authRoutes.POST("/sms/login", authHandler.Login)
authRoutes.POST("/refresh", authHandler.Refresh)
authRoutes.POST("/logout", authHandler.Logout)
}
}
```
#### 方案二: 使用依赖注入容器 (Wire)
```go
// backend/internal/wire/wire.go
//go:build wireinject
// +build wireinject
package wire
import (
"github.com/google/wire"
"hfb_sys/backend/internal/router"
)
func InitializeApp(cfg config.Config) (*gin.Engine, error) {
wire.Build(
provideDB,
provideRedis,
auth.NewJWTManager,
auth.NewService,
order.NewService,
router.New,
)
return nil, nil
}
```
---
### 8. 缺少 API 文档自动化
**解决方案**: 集成 Swagger/OpenAPI
```go
// 1. 安装 swag
// go install github.com/swaggo/swag/cmd/swag@latest
// 2. 在 handler 添加注释
// backend/internal/modules/order/handler.go
// CreateOrder 创建订单
// @Summary 创建租赁订单
// @Description 根据商品ID创建新的租赁订单
// @Tags 订单
// @Accept json
// @Produce json
// @Param body body CreateOrderRequest true "订单信息"
// @Success 200 {object} CreateOrderResponse
// @Failure 400 {object} ErrorResponse
// @Router /api/orders [post]
// @Security BearerAuth
func (h *Handler) Create(c *gin.Context) {
// ...
}
// 3. 生成文档
// swag init -g cmd/api/main.go -o docs
// 4. 注册路由
import "github.com/swaggo/gin-swagger"
import "github.com/swaggo/files"
engine.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
```
---
## 🟢 低优先级优化 (持续改进)
### 9. 前端构建优化
```typescript
// frontend/vite.config.ts 优化
import { visualizer } from 'rollup-plugin-visualizer'
import viteCompression from 'vite-plugin-compression'
export default defineConfig({
plugins: [
vue(),
// Gzip 压缩
viteCompression({
algorithm: 'gzip',
ext: '.gz',
}),
// Brotli 压缩
viteCompression({
algorithm: 'brotliCompress',
ext: '.br',
}),
// 构建分析
visualizer({
open: true,
filename: 'dist/stats.html',
}),
],
build: {
// 代码分割优化
rollupOptions: {
output: {
manualChunks: {
'vendor-vue': ['vue', 'vue-router', 'pinia'],
'vendor-element': ['element-plus', '@element-plus/icons-vue'],
'vendor-vant': ['vant'],
'vendor-utils': ['axios', 'qrcode'],
},
},
},
// 启用 CSS 代码分割
cssCodeSplit: true,
// 文件大小限制警告
chunkSizeWarningLimit: 1000,
},
})
```
### 10. 日志和监控增强
#### Prometheus 指标收集
```go
// backend/internal/metrics/metrics.go
import "github.com/prometheus/client_golang/prometheus"
var (
httpRequestsTotal = prometheus.NewCounterVec(
prometheus.CounterOpts{
Name: "http_requests_total",
Help: "Total HTTP requests",
},
[]string{"method", "path", "status"},
)
httpRequestDuration = prometheus.NewHistogramVec(
prometheus.HistogramOpts{
Name: "http_request_duration_seconds",
Help: "HTTP request duration",
},
[]string{"method", "path"},
)
)
// 注册中间件
engine.Use(metrics.PrometheusMiddleware())
engine.GET("/metrics", gin.WrapH(promhttp.Handler()))
```
#### 慢查询日志
```go
// backend/internal/database/database.go
import "gorm.io/plugin/dbresolver"
db.Use(&SlowQueryLogger{
SlowThreshold: 100 * time.Millisecond,
Logger: logger,
})
```
### 11. Redis 缓存策略优化
```go
// 示例: 系统配置缓存
func (r *Repository) GetConfig(key string) (*model.SystemConfig, error) {
// 1. 查询缓存
cacheKey := fmt.Sprintf("config:%s", key)
cached, err := r.redis.Get(ctx, cacheKey).Result()
if err == nil {
var config model.SystemConfig
json.Unmarshal([]byte(cached), &config)
return &config, nil
}
// 2. 缓存未命中,查询数据库
var config model.SystemConfig
if err := r.db.Where("key = ?", key).First(&config).Error; err != nil {
return nil, err
}
// 3. 写入缓存 (5分钟过期)
data, _ := json.Marshal(config)
r.redis.Set(ctx, cacheKey, data, 5*time.Minute)
return &config, nil
}
```
### 12. 代码质量工具集成
#### 后端 golangci-lint
```yaml
# backend/.golangci.yml
linters:
enable:
- gofmt
- govet
- errcheck
- staticcheck
- gosimple
- ineffassign
- unused
- misspell
- gocyclo
```
#### 前端 ESLint + Prettier
```json
// frontend/.eslintrc.json
{
"extends": [
"plugin:vue/vue3-recommended",
"@vue/typescript/recommended",
"prettier"
],
"rules": {
"vue/multi-word-component-names": "off",
"@typescript-eslint/no-explicit-any": "warn"
}
}
```
#### Pre-commit hooks
```json
// package.json
{
"husky": {
"hooks": {
"pre-commit": "lint-staged"
}
},
"lint-staged": {
"*.{ts,vue}": ["eslint --fix", "prettier --write"],
"*.go": ["gofmt -w", "golangci-lint run"]
}
}
```
### 13. Docker 镜像优化
```dockerfile
# backend/Dockerfile (多阶段构建)
FROM golang:1.26-alpine AS builder
WORKDIR /app
COPY go.* ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 go build -ldflags="-s -w" -o api ./cmd/api
FROM alpine:latest
RUN apk --no-cache add ca-certificates tzdata
WORKDIR /app
COPY --from=builder /app/api .
EXPOSE 8080
CMD ["./api"]
```
```dockerfile
# frontend/Dockerfile
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM nginx:alpine
COPY --from=builder /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
```
---
## 📅 实施计划
### 第一周 (基础设施)
- [ ] 周一-周二: 为 order、wallet、payment 模块添加单元测试
- [ ] 周三: 创建数据库索引迁移文件并测试
- [ ] 周四: 创建前端统一请求封装 `request.ts`
- [ ] 周五: 重构现有 API 调用使用新封装
### 第二周 (工具集成)
- [ ] 周一: 集成 Swagger API 文档
- [ ] 周二: 配置 golangci-lint 和 ESLint
- [ ] 周三: 添加 pre-commit hooks
- [ ] 周四-周五: 优化路由守卫和路由初始化代码
### 第三周 (性能优化)
- [ ] 周一-周二: 实现 Redis 缓存策略
- [ ] 周三: 添加 Prometheus 监控
- [ ] 周四: Docker 镜像优化
- [ ] 周五: 前端构建优化和测试
### 第四周 (测试和发布)
- [ ] 周一-周三: 补充测试用例,达到覆盖率目标
- [ ] 周四: 性能测试和压力测试
- [ ] 周五: 文档更新,代码审查
---
## 📊 成功指标
### 代码质量
- [ ] 核心模块测试覆盖率 ≥ 70%
- [ ] golangci-lint 检查通过
- [ ] 前端 ESLint 检查通过
- [ ] 0 个 TODO/FIXME 注释
### 性能指标
- [ ] API 平均响应时间 < 100ms (P95)
- [ ] 数据库慢查询 < 10 条/天
- [ ] 前端首屏加载时间 < 2s
- [ ] 缓存命中率 > 80%
### 安全性
- [ ] 生产环境所有默认密码已更改
- [ ] JWT Secret 长度 ≥ 32 字符
- [ ] 所有敏感信息使用密钥管理服务
- [ ] 通过安全扫描 (Snyk/Trivy)
---
## 附录
### 相关文档
- [项目计划](./project-plan.md)
- [数据库设计](./database.md)
- [API 文档](./api.md)
- [业务规则](./business-rules.md)
### 参考资源
- [Go Testing Best Practices](https://go.dev/doc/tutorial/add-a-test)
- [Vue Test Utils](https://test-utils.vuejs.org/)
- [MySQL Index Design](https://dev.mysql.com/doc/refman/8.0/en/optimization-indexes.html)
- [Viper Configuration](https://github.com/spf13/viper)
---
**最后更新**: 2026-06-05
**负责人**: 待分配
**审核人**: 待分配