475 lines
19 KiB
Markdown
475 lines
19 KiB
Markdown
# 浙江贝凡溯源赋码平台 - 后端服务 (Go 版本)
|
||
|
||
这是一个使用 Go 语言开发的溯源赋码平台后端服务,提供权限下发、产品溯源、项目工单、售后工单等功能。
|
||
|
||
## 技术栈
|
||
|
||
- **Web 框架**: Gin (高性能 HTTP 框架)
|
||
- **ORM**: GORM (Go 对象关系映射)
|
||
- **数据库**:
|
||
- SQLite (默认,适用于开发和轻量级部署)
|
||
- PostgreSQL (生产环境推荐)
|
||
- **认证**: JWT (JSON Web Token) - golang-jwt/jwt/v5
|
||
- **密码加密**: bcrypt (golang.org/x/crypto)
|
||
- **二维码生成**: yeqown/go-qrcode/v2
|
||
- **API 文档**: Swagger/OpenAPI (swaggo/swag)
|
||
- **CORS**: gin-contrib/cors (跨域资源共享)
|
||
- **配置管理**:
|
||
- Viper (环境变量和配置文件)
|
||
- gotenv (.env 文件加载)
|
||
- **日志**: Zap (高性能结构化日志)
|
||
- **验证**: go-playground/validator
|
||
- **测试**: Testify (测试框架)
|
||
- **工具**: UUID (github.com/google/uuid)
|
||
|
||
## 项目结构
|
||
|
||
```
|
||
backend-go/
|
||
├── config/ # 配置管理
|
||
│ └── config.go # 配置加载和解析(支持 .env 文件和环境变量)
|
||
├── controllers/ # 控制器层,处理 HTTP 请求
|
||
│ ├── aftersales_controller.go # 售后工单接口
|
||
│ ├── auth_controller.go # 认证相关接口
|
||
│ ├── dashboard_controller.go # 控制台统计接口
|
||
│ ├── employees_controller.go # 员工赋码接口
|
||
│ ├── helper.go # 控制器通用辅助函数
|
||
│ ├── product_traces_controller.go # 产品溯源接口
|
||
│ ├── project_orders_controller.go # 项目工单接口
|
||
│ └── users_controller.go # 员工主档管理接口(仅管理员)
|
||
├── database/ # 数据库连接和操作
|
||
│ └── database.go # 数据库初始化、连接池配置
|
||
├── docs/ # Swagger API 文档(自动生成)
|
||
│ ├── docs.go # Swagger 文档定义
|
||
│ ├── swagger.json # Swagger JSON 格式文档
|
||
│ └── swagger.yaml # Swagger YAML 格式文档
|
||
├── logger/ # 日志管理
|
||
│ └── logger.go # 结构化日志(使用 Zap)
|
||
├── middleware/ # 中间件层
|
||
│ └── auth.go # JWT 认证、管理员/技术员权限检查
|
||
├── models/ # 数据模型和 DTO
|
||
│ └── models.go # User、Company、EmployeeSerial、ProductTrace、AftersalesOrder、ProjectOrder 等模型定义
|
||
├── routes/ # 路由配置
|
||
│ └── routes.go # API 路由注册
|
||
├── services/ # 业务逻辑层
|
||
│ ├── aftersales_service.go # 售后工单业务逻辑
|
||
│ ├── auth_service.go # 认证业务逻辑
|
||
│ ├── dashboard_service.go # 控制台统计业务逻辑
|
||
│ ├── employees_service.go # 员工赋码业务逻辑
|
||
│ ├── product_traces_service.go # 产品溯源业务逻辑
|
||
│ ├── project_orders_service.go # 项目工单业务逻辑
|
||
│ ├── aftersales_service_test.go # 售后工单单元测试
|
||
│ ├── services_test.go # 认证/员工赋码单元测试
|
||
│ ├── users_service.go # 员工主档/后台账号业务逻辑
|
||
│ └── users_service_test.go # 员工主档/后台账号单元测试
|
||
├── tests/ # 集成测试
|
||
│ └── main_test.go # 端到端测试
|
||
├── data/ # 数据目录(SQLite 数据库存储位置)
|
||
├── main.go # 应用程序入口
|
||
├── go.mod # Go 模块依赖
|
||
├── go.sum # Go 模块校验和
|
||
├── Makefile # 构建和开发任务
|
||
├── .env.example # 环境变量示例
|
||
├── .env # 环境变量配置(需手动创建)
|
||
├── .golangci.yml # 代码检查配置
|
||
└── .gitignore # Git 忽略文件
|
||
```
|
||
|
||
## 快速开始
|
||
|
||
### 1. 安装依赖
|
||
|
||
```bash
|
||
cd backend-go
|
||
go mod download
|
||
|
||
# 或使用 Makefile
|
||
make deps
|
||
```
|
||
|
||
### 2. 配置环境变量
|
||
|
||
复制 `.env.example` 文件为 `.env` 并根据需要修改:
|
||
|
||
```bash
|
||
cp .env.example .env
|
||
```
|
||
|
||
**重要**: 生产环境请务必修改 `JWT_SECRET` 环境变量!
|
||
|
||
### 3. 使用 Makefile(推荐)
|
||
|
||
```bash
|
||
# 启动开发服务器
|
||
make run
|
||
|
||
# 编译项目
|
||
make build
|
||
|
||
# 运行测试
|
||
make test
|
||
|
||
# 生成测试覆盖率报告
|
||
make test-coverage
|
||
|
||
# 代码质量检查
|
||
make quality
|
||
|
||
# 清理构建文件
|
||
make clean
|
||
```
|
||
|
||
### 4. 手动启动
|
||
|
||
```bash
|
||
go run main.go
|
||
```
|
||
|
||
服务器将在 http://localhost:3000 上运行。
|
||
|
||
### 5. 配置管理
|
||
|
||
项目采用 **YAML 结构化管理 + 环境变量动态覆盖** 的方案:
|
||
|
||
#### 配置文件优先级(从高到低)
|
||
|
||
1. **环境变量** (最高优先级) - 格式:`APP_配置项`
|
||
2. **.env 文件** - 本地开发环境配置
|
||
3. **config.yaml** - 默认配置文件
|
||
4. **内置默认值** (最低优先级)
|
||
|
||
#### 环境变量命名规则
|
||
|
||
所有环境变量使用 `APP_` 前缀,多级配置使用 `_` 连接:
|
||
|
||
```bash
|
||
# 示例
|
||
APP_SERVER_PORT=8080 # 覆盖 server.port
|
||
APP_DATABASE_DRIVER=postgres # 覆盖 database.driver
|
||
APP_DATABASE_POSTGRES_HOST=db.com # 覆盖 database.postgres.host
|
||
APP_JWT_SECRET=my-secret-key # 覆盖 jwt.secret
|
||
```
|
||
|
||
#### 常用配置项
|
||
|
||
| 环境变量 | 对应配置项 | 说明 | 默认值 |
|
||
|---------|-----------|------|--------|
|
||
| `APP_SERVER_PORT` | server.port | 服务器端口 | 3000 |
|
||
| `APP_SERVER_ENVIRONMENT` | server.environment | 运行环境 | development |
|
||
| `APP_DATABASE_DRIVER` | database.driver | 数据库驱动 | sqlite |
|
||
| `APP_DATABASE_SQLITE_PATH` | database.sqlite.path | SQLite 路径 | ./data/database.sqlite |
|
||
| `APP_DATABASE_POSTGRES_HOST` | database.postgres.host | PostgreSQL 主机 | localhost |
|
||
| `APP_DATABASE_POSTGRES_PORT` | database.postgres.port | PostgreSQL 端口 | 5432 |
|
||
| `APP_DATABASE_POSTGRES_USER` | database.postgres.user | PostgreSQL 用户名 | trace |
|
||
| `APP_DATABASE_POSTGRES_PASSWORD` | database.postgres.password | PostgreSQL 密码 | trace123 |
|
||
| `APP_DATABASE_POSTGRES_DBNAME` | database.postgres.dbname | PostgreSQL 数据库名 | trace |
|
||
| `APP_JWT_SECRET` | jwt.secret | JWT 签名密钥 | your-secret-key... |
|
||
| `APP_JWT_EXPIRE` | jwt.expire | JWT 过期时间(秒) | 7200 |
|
||
|
||
#### 快速配置示例
|
||
|
||
**开发环境(.env)**:
|
||
```bash
|
||
# 使用 SQLite
|
||
APP_DATABASE_DRIVER=sqlite
|
||
APP_DATABASE_SQLITE_PATH=./data/dev.sqlite
|
||
```
|
||
|
||
**生产环境(环境变量)**:
|
||
```bash
|
||
# 使用 PostgreSQL
|
||
export APP_SERVER_PORT=8080
|
||
export APP_SERVER_ENVIRONMENT=production
|
||
export APP_DATABASE_DRIVER=postgres
|
||
export APP_DATABASE_POSTGRES_HOST=prod-db.example.com
|
||
export APP_DATABASE_POSTGRES_PASSWORD=secure-password
|
||
export APP_JWT_SECRET=your-production-secret-key
|
||
```
|
||
|
||
### 6. 测试 API
|
||
|
||
**健康检查**:
|
||
|
||
```bash
|
||
curl -X GET http://localhost:3000/api/health
|
||
```
|
||
|
||
**用户登录**:
|
||
|
||
```bash
|
||
curl -X POST http://localhost:3000/api/auth/login \
|
||
-H "Content-Type: application/json" \
|
||
-d '{"username":"admin","password":"Beifan@2026"}'
|
||
```
|
||
|
||
首次启动且用户表为空时,系统会自动创建默认管理员账号(请在生产环境立即修改密码):
|
||
|
||
- username: `admin`
|
||
- password: `Beifan@2026`
|
||
|
||
## API 文档
|
||
|
||
项目使用 Swagger 生成交互式 API 文档。
|
||
|
||
启动服务器后,访问以下地址查看完整的 API 文档:
|
||
|
||
```
|
||
http://localhost:3000/swagger/index.html
|
||
```
|
||
|
||
### Swagger 文档功能
|
||
|
||
- **交互式测试**: 直接在浏览器中测试 API 端点
|
||
- **请求/响应示例**: 查看每个接口的请求参数和响应格式
|
||
- **认证支持**: 支持 Bearer Token 认证,可以输入 JWT 令牌进行测试
|
||
- **按分组浏览**: API 按功能模块分组(认证、控制台、权限下发、产品溯源、项目工单、售后工单等)
|
||
|
||
### 重新生成 Swagger 文档
|
||
|
||
如果修改了代码中的 API 注解,需要重新生成 Swagger 文档:
|
||
|
||
```bash
|
||
# 生成文档
|
||
make swagger
|
||
|
||
# 或直接执行
|
||
go run github.com/swaggo/swag/cmd/swag@v1.16.6 init -g main.go
|
||
|
||
# 文档将生成在 docs/ 目录下
|
||
```
|
||
|
||
### 认证路由
|
||
|
||
| 方法 | 路径 | 描述 | 需要认证 |
|
||
| ---- | --------------------------- | ----------------------- | -------- |
|
||
| POST | `/api/auth/login` | 用户登录,返回 JWT 令牌 | 否 |
|
||
| POST | `/api/auth/logout` | 用户登出 | 是 |
|
||
| GET | `/api/auth/profile` | 获取当前用户信息 | 是 |
|
||
| PUT | `/api/auth/profile` | 更新用户信息 | 是 |
|
||
| POST | `/api/auth/change-password` | 修改密码 | 是 |
|
||
|
||
> 错误提示已做用户友好化,例如登录失败统一返回:`用户名或密码不正确`。
|
||
|
||
### 控制台
|
||
|
||
| 方法 | 路径 | 描述 | 需要认证 | 角色 |
|
||
| ---- | ---- | ---- | -------- | ---- |
|
||
| GET | `/api/dashboard/stats` | 获取工单统计和最近售后工单 | 是 | 管理员 |
|
||
|
||
### 权限下发(员工赋码)
|
||
|
||
| 方法 | 路径 | 描述 | 需要认证 | 角色 |
|
||
| ---- | -------------------------------------- | ------------------ | -------- | ------ |
|
||
| POST | `/api/employee-serials/generate` | 生成员工权限码 | 是 | 管理员 |
|
||
| GET | `/api/employee-serials` | 获取员工权限码列表 | 是 | 管理员 |
|
||
| GET | `/api/employee-serials/:serialNumber/query` | 查询员工权限码信息 | 否 | 任何 |
|
||
| POST | `/api/employee-serials/:serialNumber/qrcode` | 生成员工二维码 | 是 | 管理员 |
|
||
| PATCH | `/api/employee-serials/:serialNumber` | 更新员工权限码信息 | 是 | 管理员 |
|
||
| PUT | `/api/employee-serials/:serialNumber` | 更新员工权限码信息 | 是 | 管理员 |
|
||
| POST | `/api/employee-serials/:serialNumber/revoke` | 吊销员工权限码 | 是 | 管理员 |
|
||
|
||
**员工权限码特点**:
|
||
- 无有效期限制
|
||
- 创建员工主档时会自动生成 1 个员工码,并通过 `employeeId` 绑定员工
|
||
- 包含岗位(position)和员工姓名(employeeName)信息
|
||
- 序列号格式: `EMP26xxxxxx`(EMP + 年份后两位 + 6位随机字符)
|
||
|
||
### 产品溯源
|
||
|
||
| 方法 | 路径 | 描述 | 需要认证 | 角色 |
|
||
| ---- | ---- | ---- | -------- | ---- |
|
||
| GET | `/api/product-traces/:serialNumber/query` | 公开查询产品溯源信息 | 否 | 任何 |
|
||
| POST | `/api/product-traces` | 创建产品溯源 | 是 | 管理员 |
|
||
| GET | `/api/product-traces` | 产品溯源列表 | 是 | 管理员 |
|
||
| GET | `/api/product-traces/:serialNumber` | 产品溯源详情 | 是 | 管理员 |
|
||
| PATCH | `/api/product-traces/:serialNumber` | 更新产品溯源 | 是 | 管理员 |
|
||
| POST | `/api/product-traces/:serialNumber/qrcode` | 生成产品二维码 | 是 | 管理员 |
|
||
| POST | `/api/product-traces/:serialNumber/wechat-qrcode` | 上传公众号二维码 | 是 | 管理员 |
|
||
| POST | `/api/product-traces/:serialNumber/revoke` | 停用产品溯源 | 是 | 管理员 |
|
||
| DELETE | `/api/product-traces/:serialNumber` | 删除产品溯源 | 是 | 管理员 |
|
||
|
||
**产品溯源字段顺序**:
|
||
企业名称、地址、电话、设备信息、质保期、出厂日期、产品序列号、官网链接(可选)、公众号二维码(可选)。
|
||
|
||
### 项目工单
|
||
|
||
| 方法 | 路径 | 描述 | 需要认证 | 角色 |
|
||
| ---- | ---- | ---- | -------- | ---- |
|
||
| GET | `/api/project-orders/:serialNumber/query` | 公开查询项目工单 | 否 | 任何 |
|
||
| POST | `/api/project-orders/:serialNumber/site-images` | 上传现场图片 | 否 | 任何 |
|
||
| POST | `/api/project-orders/:serialNumber/complete` | 工程师提交完成 | 否 | 任何 |
|
||
| POST | `/api/project-orders` | 创建项目工单 | 是 | 管理员/技术员 |
|
||
| GET | `/api/project-orders` | 项目工单列表 | 是 | 管理员/技术员 |
|
||
| GET | `/api/project-orders/:serialNumber` | 项目工单详情 | 是 | 管理员/技术员 |
|
||
| PATCH | `/api/project-orders/:serialNumber` | 更新项目工单 | 是 | 管理员/技术员 |
|
||
| POST | `/api/project-orders/:serialNumber/qrcode` | 生成项目工单二维码 | 是 | 管理员/技术员 |
|
||
| POST | `/api/project-orders/:serialNumber/submit` | 后台提交项目完成 | 是 | 管理员/技术员 |
|
||
| POST | `/api/project-orders/:serialNumber/reassign` | 工单分配 | 是 | 管理员 |
|
||
| POST | `/api/project-orders/:serialNumber/force-close` | 强制完成 | 是 | 管理员 |
|
||
| DELETE | `/api/project-orders/:serialNumber` | 删除项目工单 | 是 | 管理员 |
|
||
|
||
**项目工单特点**:
|
||
- 用于现场勘查、现场实施等项目任务
|
||
- 工单号格式: `zjbf-xm-YYMMDDNN`
|
||
- 现场图片最多 18 张
|
||
- 仅需要工程师签名,无客户签字环节
|
||
- 完成状态使用 `已完成`
|
||
|
||
### 售后工单
|
||
|
||
| 方法 | 路径 | 描述 | 需要认证 | 角色 |
|
||
| ------ | --------------------------------------------- | -------------------------- | -------- | --------------- |
|
||
| GET | `/api/aftersales/:serialNumber/query` | 公开查询工单(脱敏) | 否 | 任何 |
|
||
| POST | `/api/aftersales/:serialNumber/confirm` | 客户授权/未授权确认 | 否 | 任何 |
|
||
| POST | `/api/aftersales` | 创建售后工单 | 是 | 管理员/技术员 |
|
||
| GET | `/api/aftersales` | 工单列表(支持筛选) | 是 | 管理员/技术员 |
|
||
| GET | `/api/aftersales/:serialNumber` | 工单详情 | 是 | 管理员/技术员 |
|
||
| PATCH | `/api/aftersales/:serialNumber` | 更新工单(仅负责人或管理员)| 是 | 管理员/技术员 |
|
||
| POST | `/api/aftersales/:serialNumber/qrcode` | 生成工单二维码 | 是 | 管理员/技术员 |
|
||
| POST | `/api/aftersales/:serialNumber/submit` | 提交客户确认 | 是 | 管理员/技术员 |
|
||
| POST | `/api/aftersales/:serialNumber/reassign` | 工单分配(重新分配技术员) | 是 | 管理员 |
|
||
| POST | `/api/aftersales/:serialNumber/force-close` | 强制关闭工单 | 是 | 管理员 |
|
||
| DELETE | `/api/aftersales/:serialNumber` | 删除工单 | 是 | 管理员 |
|
||
|
||
**售后工单特点**:
|
||
- 服务类型枚举:`software`(软件故障)、`hardware`(硬件故障)、`maintenance`(售后维保)
|
||
- 工单号格式: `zjbf-sh-YYMMDDNN`(年份后 2 位 + 月份 2 位 + 日期 2 位 + 当天序号至少 2 位,例:`zjbf-sh-26052801`)
|
||
- 序号按天重置,软删除工单不释放编号(避免回收造成混淆)
|
||
- 工单里的企业名称是售后客户信息,只保存在工单中
|
||
- 二维码扫码后客户在网页签名(canvas)后点「已授权」确认;选择「未授权」需填写退回原因
|
||
- 签名以 PNG dataURL 形式持久化到工单(`signature` 字段),管理员详情页可查看留底
|
||
- 签名校验:必须为 `data:image/png;base64,` 或 `data:image/jpeg;base64,` 前缀,解码后 200B–500KB
|
||
- 客户确认接口每分钟同一工单最多 5 次请求
|
||
- 工单状态机: `created` → `pending_confirmation` → `closed` / `rejected`,被退回后可重新提交
|
||
- 公开查询不返回手机号(脱敏)
|
||
|
||
### 权限下发(仅管理员)
|
||
|
||
| 方法 | 路径 | 描述 | 需要认证 | 角色 |
|
||
| ------ | ----------------------------------- | -------------------------- | -------- | ------------- |
|
||
| GET | `/api/users/assignable` | 可分配用户列表(用于售后) | 是 | 管理员/技术员 |
|
||
| POST | `/api/employees` | 创建员工并自动生成员工码 | 是 | 管理员 |
|
||
| GET | `/api/employees` | 员工列表(分页+筛选) | 是 | 管理员 |
|
||
| PATCH | `/api/employees/:id` | 更新员工姓名/电话/工号/岗位/角色 | 是 | 管理员 |
|
||
| POST | `/api/employees/:id/reset-password` | 重置后台账号密码 | 是 | 管理员 |
|
||
| DELETE | `/api/employees/:id` | 删除员工 | 是 | 管理员 |
|
||
|
||
**员工角色**:
|
||
- `admin`:管理员,拥有全部后台权限,包括权限下发、产品溯源、工单分配、强制关闭和删除工单
|
||
- `technician`:技术员,仅拥有工单模块权限,可创建/处理工单,可使用 `assignable` 查询可分配同事
|
||
- `employee`:员工,无后台登录权限,不需要密码,仅用于员工主档和员工码查询
|
||
|
||
**保护规则**:
|
||
- 不能删除自己;不能将自己的 admin 角色降级;不能删除最后一个 admin
|
||
- 创建 `admin` / `technician` 必须设置初始密码,密码 bcrypt 加密存储
|
||
- 创建 `employee` 不要求密码,且不能登录后台
|
||
|
||
## 测试
|
||
|
||
### 运行所有测试
|
||
|
||
```bash
|
||
# 运行所有测试
|
||
go test -v ./...
|
||
|
||
# 仅运行服务层单元测试
|
||
cd services && go test -v -cover
|
||
|
||
# 仅运行集成测试
|
||
go test -v ./tests/...
|
||
```
|
||
|
||
### 生成测试覆盖率报告
|
||
|
||
```bash
|
||
# 生成覆盖率报告
|
||
go test -v ./services/... -coverprofile=coverage.out
|
||
go tool cover -html=coverage.out
|
||
```
|
||
|
||
### 当前测试覆盖
|
||
|
||
- **services/**: 包含 AuthService、EmployeeSerialsService、AftersalesService、ProjectOrdersService、ProductTracesService 和 UsersService 的单元测试
|
||
- 用户认证测试(登录、获取用户信息、修改密码、更新资料)
|
||
- 员工赋码测试(生成、查询、更新、吊销、二维码生成)
|
||
- 售后工单测试(YYMMDDNN 序号生成、状态机、客户确认手机号校验、强制关闭)
|
||
- 项目工单测试(创建、完成、现场图片、工程师签名)
|
||
- 产品溯源测试(创建、查询、二维码、公众号二维码)
|
||
- 权限下发测试(创建员工自动生成员工码、重复工号、自降级保护、最后管理员保护、密码重置)
|
||
- **tests/**: 集成测试(健康检查、登录流程)
|
||
|
||
## 代码检查
|
||
|
||
使用 golangci-lint 进行代码检查:
|
||
|
||
```bash
|
||
golangci-lint run ./...
|
||
```
|
||
|
||
## 部署
|
||
|
||
### 编译为二进制文件
|
||
|
||
```bash
|
||
# Linux/Mac
|
||
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o trace-backend
|
||
|
||
# Windows
|
||
go build -o trace-backend.exe main.go
|
||
|
||
# macOS
|
||
CGO_ENABLED=0 GOOS=darwin GOARCH=arm64 go build -o trace-backend
|
||
```
|
||
|
||
### 运行
|
||
|
||
```bash
|
||
./trace-backend
|
||
```
|
||
|
||
## 数据库迁移
|
||
|
||
### SQLite 到 PostgreSQL 的迁移
|
||
|
||
修改 `.env` 文件中的数据库配置:
|
||
|
||
```bash
|
||
# 将数据库驱动改为 postgres
|
||
DATABASE_DRIVER=postgres
|
||
|
||
# PostgreSQL 配置
|
||
POSTGRES_HOST=localhost
|
||
POSTGRES_PORT=5432
|
||
POSTGRES_USER=trace
|
||
POSTGRES_PASSWORD=trace123
|
||
POSTGRES_DB=trace
|
||
POSTGRES_SSLMODE=disable
|
||
```
|
||
|
||
然后重新启动应用即可:
|
||
|
||
```bash
|
||
./trace-backend
|
||
```
|
||
|
||
**注意**: 切换数据库后,原有 SQLite 中的数据不会自动迁移到 PostgreSQL。需要使用数据库迁移工具(如 pgloader)手动迁移数据。
|
||
|
||
## 贡献指南
|
||
|
||
1. 克隆项目
|
||
2. 创建新功能分支
|
||
3. 提交更改
|
||
4. 推送到远程仓库
|
||
5. 创建 Pull Request
|
||
|
||
### 代码风格要求
|
||
|
||
- 使用 gofmt 自动格式化代码
|
||
- 遵循 Go 官方编码规范
|
||
- 使用 golangci-lint 进行代码检查
|
||
- 保持代码简洁和高效
|
||
|
||
## 许可证
|
||
|
||
MIT License
|