- 新增 .gitignore、ARCHITECTURE.md 项目基础设施文件 - 新增前后端路由对接文档,完整映射前端页面到后端 API 端点 - 配置前端 Vitest 测试框架,添加 API/Store/Utils/Components 单元测试 - 添加后端 UserService 单元测试 - 新增统一测试运行脚本 scripts/run-tests.sh - 清理旧文档和过期覆盖率报告文件 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
414 lines
16 KiB
Markdown
414 lines
16 KiB
Markdown
# ARCHITECTURE.md
|
|
|
|
## Project Overview
|
|
|
|
企业供应链合同管理系统 (中尚鹏 / ZSP) — a monorepo enterprise supply chain contract management system. It manages contracts, deliveries, inventory, finance, purchasing, and reporting for a bulk commodity supply chain business.
|
|
|
|
### 5 Business Modules (21 Sub-menus)
|
|
|
|
| Module | Sub-menus |
|
|
|---|---|
|
|
| 合同管理 (Contract) | 业务合同管理, 中尚鹏合同管理, 公司信息管理 |
|
|
| 提货管理 (Delivery) | 我要提货, 提货明细, 库存信息, 货权转移, 其他出库 |
|
|
| 财务管理 (Finance) | 货代费用, 杂费管理, 服务费管理, 发票管理, 结算管理, 对账单, 利润明细 |
|
|
| 采购管理 (Purchase) | 采购报单, 采购运踪明细, 国外供应商, 采购预算 |
|
|
| 查询&报表中心 (Reports) | 合同执行情况, 营业明细表, 其他明细表, 商品基础资料, 其他报表 |
|
|
|
|
---
|
|
|
|
## Directory Structure
|
|
|
|
```
|
|
zsp-project/
|
|
├── frontend/ # Vue 3 SPA (pure-admin-thin)
|
|
│ ├── src/
|
|
│ │ ├── api/ # Axios API modules (one per domain)
|
|
│ │ ├── assets/ # Static assets (icons, images, SVG)
|
|
│ │ ├── components/ # Reusable global components
|
|
│ │ ├── config/ # Runtime platform configuration
|
|
│ │ ├── directives/ # Custom Vue directives (auth, perms, copy, etc.)
|
|
│ │ ├── layout/ # App shell (vertical/horizontal/mix layouts)
|
|
│ │ ├── plugins/ # Plugin installers (Element Plus, ECharts)
|
|
│ │ ├── router/ # Vue Router + auto-imported route modules
|
|
│ │ ├── store/ # Pinia stores (user, app, permission, settings, etc.)
|
|
│ │ ├── style/ # SCSS/CSS (theme, dark mode, transitions, Tailwind)
|
|
│ │ ├── utils/ # HTTP client, auth, tree, message, print, etc.
|
|
│ │ └── views/ # Page components by feature domain
|
|
│ └── public/ # Static public assets
|
|
│
|
|
├── backend/ # Go + Gin REST API
|
|
│ ├── cmd/api/ # Entry point (main.go)
|
|
│ ├── configs/ # YAML configs (dev, prod)
|
|
│ ├── internal/
|
|
│ │ ├── dto/ # Request/response DTOs
|
|
│ │ ├── handler/ # HTTP handlers (Gin controllers)
|
|
│ │ ├── middleware/ # JWT auth middleware
|
|
│ │ ├── model/ # GORM database models (27 tables)
|
|
│ │ ├── repository/ # Data access layer (GORM queries)
|
|
│ │ ├── server/ # Router setup and route definitions
|
|
│ │ └── service/ # Business logic layer
|
|
│ ├── pkg/ # Shared packages (config, database, utils)
|
|
│ ├── test/ # Handler integration tests
|
|
│ ├── scripts/ # SQL init scripts
|
|
│ └── data/ # Uploaded files (runtime)
|
|
│
|
|
├── docs/ # Project documentation
|
|
│ ├── api/ # API docs and frontend route docs
|
|
│ ├── deploy/ # Deployment guides, nginx config
|
|
│ ├── dev/ # Requirements, gap analysis, meeting notes
|
|
│ └── superpowers/ # Implementation plans and specs
|
|
│
|
|
├── scripts/ # Test runner, deployment scripts
|
|
├── .github/workflows/ # CI pipeline (Go + Vue tests)
|
|
└── CLAUDE.md # Claude Code project guide
|
|
```
|
|
|
|
---
|
|
|
|
## Technology Stack
|
|
|
|
### Frontend
|
|
| Technology | Version | Purpose |
|
|
|---|---|---|
|
|
| Vue | 3.5 | UI framework (Composition API) |
|
|
| TypeScript | 5.8 | Type safety |
|
|
| Vite | 6.3 | Build tool & dev server |
|
|
| Pinia | 3.0 | State management |
|
|
| Vue Router | 4.5 | Client-side routing |
|
|
| Element Plus | 2.9 | UI component library |
|
|
| Axios | latest | HTTP client |
|
|
| Tailwind CSS | 4.1 | Utility-first CSS |
|
|
| ECharts | latest | Data visualization |
|
|
| Vitest | latest | Test framework |
|
|
|
|
### Backend
|
|
| Technology | Version | Purpose |
|
|
|---|---|---|
|
|
| Go | 1.25 | Runtime |
|
|
| Gin | 1.10 | HTTP framework |
|
|
| GORM | 1.31 | ORM |
|
|
| MySQL | 8.0 | Relational database |
|
|
| JWT (golang-jwt) | 4.5 | Authentication tokens |
|
|
| Viper | 1.21 | Configuration management |
|
|
| Excelize | 2.9 | Excel import/export |
|
|
| bcrypt | latest | Password hashing |
|
|
|
|
---
|
|
|
|
## Backend Architecture
|
|
|
|
### Layered Design
|
|
|
|
```
|
|
HTTP Request
|
|
│
|
|
▼
|
|
┌─────────────────┐
|
|
│ Gin Router │ internal/server/route.go — single source of truth for all routes
|
|
├─────────────────┤
|
|
│ Middleware │ internal/middleware/auth.go — JWT Bearer token validation
|
|
├─────────────────┤
|
|
│ Handler │ internal/handler/ — request parsing, response writing (thin)
|
|
├─────────────────┤
|
|
│ Service │ internal/service/ — business logic, orchestration
|
|
├─────────────────┤
|
|
│ Repository │ internal/repository/ — GORM data access
|
|
├─────────────────┤
|
|
│ Model │ internal/model/ — GORM structs with table tags
|
|
└─────────────────┘
|
|
│
|
|
▼
|
|
MySQL 8.0
|
|
```
|
|
|
|
**Key pattern**: Manual dependency injection in `cmd/api/main.go` — all repositories, services, and handlers are wired together at startup. Every service and most repositories define a Go interface, enabling mock-based unit testing.
|
|
|
|
### Startup Sequence (main.go)
|
|
|
|
1. Load YAML config via Viper (`CONFIG_PATH` env var)
|
|
2. Ensure MySQL database exists (create/drop per config)
|
|
3. Open GORM DB connection pool
|
|
4. `AutoMigrate` all 27 model tables
|
|
5. Instantiate repositories → services → handlers (manual DI)
|
|
6. Create JWT auth middleware
|
|
7. Setup Gin router (`server.SetupRouter`)
|
|
8. Start HTTP server (goroutine)
|
|
9. Graceful shutdown on SIGINT/SIGTERM
|
|
|
|
### API Route Groups
|
|
|
|
All routes are under `/api/` prefix with JWT middleware applied globally except for auth endpoints and static file serving.
|
|
|
|
| Route Group | Prefix | Handler | Key Endpoints |
|
|
|---|---|---|---|
|
|
| Auth (public) | `/api/auth` | UserHandler | `POST /register`, `POST /login` |
|
|
| Users | `/api/users` | UserHandler | `GET /:id` |
|
|
| Contract | `/api/contract` | ContractHandler | CRUD, file upload, folders, batches |
|
|
| ZSP Contract | `/api/zsp-contract` | ZSPContractHandler | CRUD, details, Excel import/export |
|
|
| Company | `/api/company` | CompanyHandler | Folders, file upload/CRUD |
|
|
| Delivery | `/api/apply-delivery` | DeliveryHandler | Delivery applications |
|
|
| Outbound | `/api/delivery-details` | DeliveryHandler | Outbound orders |
|
|
| Inventory | `/api/inventory` | DeliveryHandler | Warehouses, inbound, summary |
|
|
| Ownership | `/api/ownership-transfer` | DeliveryHandler | Ownership transfer CRUD + files |
|
|
| Finance | `/api/zsp-finances` | ZSPFinancesHandler | Freight, misc, service fees, costs, profits |
|
|
|
|
### Data Models (27 GORM Tables)
|
|
|
|
| Domain | Tables |
|
|
|---|---|
|
|
| User | `users` |
|
|
| Contract | `contracts`, `contract_files`, `contract_folders`, `contract_batches`, `contract_batch_files` |
|
|
| ZSP Contract | `zsp_contract_folders`, `zsp_contract_files`, `zsp_contract_details` |
|
|
| Company | `company_folders`, `company_files` |
|
|
| Delivery | `delivery_applies`, `delivery_outbounds`, `warehouses`, `inventory_inbounds`, `ownership_transfers`, `ownership_transfer_bls`, `ownership_transfer_files` |
|
|
| Finance | `zsp_freight_charges`, `zsp_misc_charges`, `zsp_service_fees`, `zsp_cost_breakdowns`, `zsp_profit_records` |
|
|
| Invoice | `zsp_upstream_invoices`, `zsp_downstream_invoices`, `zsp_freight_misc_invoices` |
|
|
| Settlement | `zsp_settlements`, `zsp_reconciliations` |
|
|
|
|
Note: Invoice and Settlement models are defined and auto-migrated but not yet wired into handlers/services.
|
|
|
|
### File Storage
|
|
|
|
Uploaded files are stored on the local filesystem under `backend/data/`:
|
|
- `./data/contract/` — contract files
|
|
- `./data/contract/batch/` — batch contract files
|
|
- `./data/zsp-contract/files/` — ZSP contract files
|
|
- `./data/company/files/` — company info files
|
|
|
|
Files are served statically via Gin (`/api/contract-files`, `/api/zsp-contract-files`, `/api/company-files`).
|
|
|
|
---
|
|
|
|
## Frontend Architecture
|
|
|
|
### Component Tree
|
|
|
|
```
|
|
App.vue
|
|
└── <el-config-provider>
|
|
├── <router-view> # Page content (or login page)
|
|
└── <ReDialog /> # Global dialog manager
|
|
|
|
Layout (after login)
|
|
└── Layout (index.vue) # Three modes: vertical / horizontal / mix
|
|
├── Navbar # Top bar (user info, search, fullscreen)
|
|
├── Sidebar # Menu (vertical/horizontal/mix variants)
|
|
├── MultiTags # Tab-based page navigation
|
|
├── <router-view> # Keep-alive cached page views
|
|
│ └── Page View # Domain-specific business pages
|
|
└── Settings Panel # Theme/layout preferences drawer
|
|
```
|
|
|
|
### Page Composition Pattern
|
|
|
|
Complex pages follow a consistent decomposition:
|
|
|
|
```
|
|
views/system/contract/business-contract/
|
|
├── index.vue # Page assembly (layout, orchestrates sub-components)
|
|
├── components/ # Page-specific sub-components
|
|
│ ├── ContractTable.vue
|
|
│ ├── EditContractDialog.vue
|
|
│ ├── FolderPanel.vue
|
|
│ ├── SearchPanel.vue
|
|
│ └── UploadDialog.vue
|
|
├── hooks/ # Composition API hooks (business logic)
|
|
│ └── useContract.ts
|
|
└── types.ts # Page-specific TypeScript types
|
|
```
|
|
|
|
### Routing
|
|
|
|
Routes use `import.meta.glob` to auto-import all modules from `src/router/modules/`. Two-tier routing:
|
|
- **Static routes**: Built-in pages (login, error pages, welcome, home) — defined in router modules
|
|
- **Async routes**: Business pages fetched from backend `/get-async-routes` after login, merged with static routes, filtered by user roles
|
|
|
|
Route structure mirrors the 5 business modules:
|
|
```
|
|
/ → Home
|
|
/login → Login
|
|
/system/contract/* → Contract management (3 pages)
|
|
/system/delivery/* → Delivery management (5 pages)
|
|
/system/finance/* → Finance management (7 pages)
|
|
/system/purchase/* → Purchase management (4 pages)
|
|
/system/query-report/* → Query & reports (5 pages)
|
|
```
|
|
|
|
### State Management (Pinia)
|
|
|
|
| Store | Key State |
|
|
|---|---|
|
|
| `user` | avatar, username, roles, permissions, tokens |
|
|
| `app` | sidebar state, layout mode, device type |
|
|
| `permission` | constant/whole menus, flattened routes, keep-alive cache list |
|
|
| `settings` | title, fixed header, sidebar visibility |
|
|
| `multiTags` | open tab list, tab cache toggle |
|
|
| `epTheme` | theme color, light/dark mode |
|
|
|
|
### API Layer (`src/api/`)
|
|
|
|
Each API module exports typed functions returning `BaseResponse<T>`:
|
|
|
|
```typescript
|
|
// Pattern: src/api/contract.ts
|
|
import { http } from "@/utils/http";
|
|
import { baseUrlApi } from "./utils";
|
|
|
|
export const getContractList = (params?: object) => {
|
|
return http.request<BaseResponse<Contract[]>>("get", baseUrlApi("contract"), { params });
|
|
};
|
|
```
|
|
|
|
The HTTP client (`src/utils/http/index.ts`) handles:
|
|
- Base URL resolution (dev proxy vs production)
|
|
- JWT token attachment via request interceptor
|
|
- Automatic token refresh on 401 with request queueing
|
|
- NProgress loading bar integration
|
|
|
|
### Authentication Flow
|
|
|
|
```
|
|
Login → POST /api/auth/login
|
|
→ Receive JWT token
|
|
→ Store in cookies + localStorage
|
|
→ Fetch user info + async routes
|
|
→ Build dynamic menu from roles
|
|
→ Navigate to home
|
|
|
|
Token refresh:
|
|
→ 401 response intercepted
|
|
→ Queue subsequent requests
|
|
→ POST /refresh-token
|
|
→ Replay queued requests with new token
|
|
```
|
|
|
|
### Permission System
|
|
|
|
Two levels:
|
|
- **Route-level**: `meta.roles` in route config filtered against user roles
|
|
- **Button-level**: `v-auth` / `v-perms` directives and `<Auth>` / `<Perms>` components
|
|
|
|
---
|
|
|
|
## Data Flow
|
|
|
|
```
|
|
User Action (UI)
|
|
→ Pinia Store action (optional, for shared state)
|
|
→ API function call (src/api/domain.ts)
|
|
→ Axios request (auto-attaches JWT)
|
|
→ Vite dev proxy (/api → localhost:8080) or nginx proxy (production)
|
|
→ Gin router → JWT middleware → Handler
|
|
→ Service (business logic)
|
|
→ Repository (GORM queries)
|
|
→ MySQL 8.0
|
|
← JSON response { success, data, message }
|
|
← Axios interceptor (deserialize, handle 401)
|
|
← Component re-render
|
|
```
|
|
|
|
---
|
|
|
|
## Authentication & Authorization
|
|
|
|
- **Scheme**: JWT Bearer tokens (HS256, 24-hour expiry)
|
|
- **Password storage**: bcrypt hashed
|
|
- **Public endpoints**: `POST /api/auth/register`, `POST /api/auth/login`, static file routes
|
|
- **Middleware**: Extracts user ID from JWT claims into Gin context; all other routes require valid token
|
|
- **CORS**: All origins allowed with Authorization header (dev mode)
|
|
|
|
---
|
|
|
|
## Testing Strategy
|
|
|
|
### Backend Tests
|
|
|
|
| Location | Type | Coverage |
|
|
|---|---|---|
|
|
| `backend/test/` | Handler integration tests | User, contract, ZSP contract, company, delivery, finance handlers |
|
|
| `backend/internal/service/` | Service unit tests | User service (with mock repository) |
|
|
|
|
Pattern: Mock service/repository interfaces, use `httptest.NewRecorder` with Gin test mode.
|
|
|
|
### Frontend Tests
|
|
|
|
| Location | Type | Coverage |
|
|
|---|---|---|
|
|
| `src/api/__tests__/` | API module tests | All 12 API modules |
|
|
| `src/components/business/__tests__/` | Component tests | ConfirmDialog |
|
|
| `src/store/modules/__tests__/` | Store tests | User store |
|
|
| `src/utils/__tests__/` | Utility tests | Tree utilities |
|
|
|
|
Framework: Vitest + jsdom + @vue/test-utils. Test helpers in `src/api/test-utils.ts`.
|
|
|
|
### Test Runner
|
|
|
|
`scripts/run-tests.sh` supports: `backend`, `frontend`, `coverage`, `ci`, `all`.
|
|
|
|
---
|
|
|
|
## Deployment
|
|
|
|
### Development
|
|
|
|
```bash
|
|
# Backend (port 8080)
|
|
cd backend && go run cmd/api/main.go
|
|
# or with hot-reload
|
|
cd backend && air -c .air.toml
|
|
|
|
# Frontend (port 5173, proxies /api → 8080)
|
|
cd frontend && pnpm dev
|
|
|
|
# Docker Compose (backend + MySQL + Air)
|
|
cd backend && docker-compose -f docker-compose.dev.yml up --build
|
|
```
|
|
|
|
### Production
|
|
|
|
```bash
|
|
# Frontend build
|
|
cd frontend && pnpm build # → dist/
|
|
|
|
# Backend build (Docker)
|
|
cd backend && docker build -t zsp-backend .
|
|
|
|
# Full stack
|
|
docker-compose --env-file .env.prod up -d
|
|
```
|
|
|
|
Nginx serves the frontend SPA and proxies `/api/` to the Go backend. Configuration at `docs/deploy/nginx.conf`.
|
|
|
|
---
|
|
|
|
## CI/CD
|
|
|
|
`.github/workflows/test.yml` — Three-job pipeline triggered on push/PR to `master`/`main`/`dev`:
|
|
|
|
1. **Backend**: Go 1.25 + MySQL 8 service → `go test ./... -race`
|
|
2. **Frontend**: Node 22 + pnpm 9 → `vitest run` + `typecheck` + `eslint`
|
|
3. **Summary**: Gate job, fails if either backend or frontend fails
|
|
|
|
---
|
|
|
|
## Key Design Decisions
|
|
|
|
1. **Manual DI over framework**: All wiring in `main.go` keeps dependencies explicit and avoids magic
|
|
2. **Interface-based services**: Every service defines an interface for testability via mocks
|
|
3. **DTOs at API boundary**: Request/response types in `internal/dto/` decouple API contract from database schema
|
|
4. **AutoMigrate for schema**: All 27 models migrated on startup; additive-only schema changes
|
|
5. **File storage on local FS**: Uploaded files go to `data/` directory, served statically by Gin
|
|
6. **Glob-based route auto-import**: Vite's `import.meta.glob` loads all router modules without manual registration
|
|
7. **Token refresh with request queueing**: Axios interceptor queues failed requests during token refresh, replays them after
|
|
8. **Dual contract models**: Legacy `Contract` coexists with `ZSPContractFile`/`ZSPContractDetail` for backward compatibility
|
|
|
|
## Known Issues
|
|
|
|
1. **Dual contract models** — Legacy `Contract` and new `ZSPContractFile`/`ZSPContractDetail` models coexist with overlapping concerns
|
|
2. **API URL inconsistency** — Some routes use `/list`, `/create`, `/update/:id` suffixes while others use direct REST patterns
|
|
3. **Incomplete module coverage** — Invoice and Settlement models are in AutoMigrate but lack handlers, services, and repositories
|
|
4. **Delivery handler complexity** — `InventorySummary` bypasses the service/repository layer and accesses `*gorm.DB` directly for complex aggregate queries
|
|
5. **Hardcoded UI text** — All UI text is hardcoded in Chinese; no i18n infrastructure
|
|
6. **Handler-level inconsistency** — Some handlers use proper DTOs, others use inline anonymous structs for request binding
|
|
7. **Frontend finance API inconsistency** — Finance API modules use hardcoded `/zsp-finances/` prefix while other modules use `/api/` prefix via `baseUrlApi()`
|