2025-09-02 01:23:16 +08:00
|
|
|
|
# 活牛采购智能数字化系统 - 技术实施方案
|
|
|
|
|
|
|
|
|
|
|
|
## 1. 技术架构
|
|
|
|
|
|
|
|
|
|
|
|
### 1.1 系统架构
|
|
|
|
|
|
```
|
|
|
|
|
|
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
|
|
|
|
|
|
│ Mini-Program │ │ Admin System │ │ Website │
|
|
|
|
|
|
│ (uni-app) │ │ (Vue 3) │ │ (HTML5 + Bootstrap) │
|
|
|
|
|
|
└─────────────────┘ └─────────────────┘ └─────────────────┘
|
|
|
|
|
|
│ │ │
|
|
|
|
|
|
└──────────┬───────────┴──────────┬───────────┘
|
|
|
|
|
|
│ │
|
|
|
|
|
|
┌────────┴─────────┐ ┌──────┴───────┐
|
|
|
|
|
|
│ API Gateway │ │ 统一用户中心 │
|
|
|
|
|
|
│ (Authentication)│ │ (Single Sign-On)
|
|
|
|
|
|
└────────┬─────────┘ └──────┬───────┘
|
|
|
|
|
|
│ │
|
|
|
|
|
|
└──────────┬───────────┘
|
|
|
|
|
|
│
|
|
|
|
|
|
┌──────────┴──────────┐
|
|
|
|
|
|
│ 微服务层 │
|
|
|
|
|
|
│ (NestJS Services) │
|
|
|
|
|
|
└──────────┬──────────┘
|
|
|
|
|
|
│
|
|
|
|
|
|
┌──────────┴──────────┐
|
|
|
|
|
|
│ 统一数据库 │
|
|
|
|
|
|
│ (MySQL + Redis) │
|
|
|
|
|
|
└─────────────────────┘
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 1.2 技术选型
|
|
|
|
|
|
| 层级 | 技术栈 | 说明 |
|
|
|
|
|
|
|------|--------|------|
|
|
|
|
|
|
| 官网前端 | HTML5 + Bootstrap | 企业官网展示 |
|
|
|
|
|
|
| 管理后台 | Vue 3 + TypeScript + Element Plus + Vite + Pinia | Web管理后台 |
|
|
|
|
|
|
| 小程序端 | Uni-app + Vue 3 + TypeScript | 跨平台小程序矩阵 |
|
2025-09-11 09:23:37 +08:00
|
|
|
|
| 后端 | |
|
|
|
|
|
|
| **Node版** | Node.js + Express.js | 微服务架构 |
|
|
|
|
|
|
| **Java版** | Spring Boot 3 + JPA | 模块化微服务架构 |
|
2025-09-02 01:23:16 +08:00
|
|
|
|
| 数据库 | MySQL 5.7 + Redis | 统一业务数据 + 缓存 |
|
|
|
|
|
|
| 文件存储 | MinIO/阿里云OSS | 视频文件存储 |
|
|
|
|
|
|
| 消息队列 | RabbitMQ | 异步任务处理 |
|
|
|
|
|
|
| 实时通信 | WebSocket | 实时数据传输 |
|
|
|
|
|
|
|
|
|
|
|
|
**数据库连接信息**:
|
|
|
|
|
|
- **主机**: 129.211.213.226
|
|
|
|
|
|
- **端口**: 9527
|
|
|
|
|
|
- **用户名**: root
|
|
|
|
|
|
- **密码**: aiotAiot123!
|
|
|
|
|
|
- **数据库名**: jiebandata
|
|
|
|
|
|
|
|
|
|
|
|
**管理员默认账号**: admin/admin123
|
|
|
|
|
|
|
|
|
|
|
|
### 1.3 小程序矩阵技术架构
|
|
|
|
|
|
```
|
|
|
|
|
|
┌─────────────────────────────────────────────────┐
|
|
|
|
|
|
│ Uni-app 跨端框架 │
|
|
|
|
|
|
├─────────────────────────────────────────────────┤
|
|
|
|
|
|
│ Client MP Supplier MP Driver MP Staff MP │
|
|
|
|
|
|
│ (客户端) (供应商) (司机) (内部员工) │
|
|
|
|
|
|
└─────────────────────────────────────────────────┘
|
|
|
|
|
|
│
|
|
|
|
|
|
┌─────────┴─────────┐
|
|
|
|
|
|
│ 统一API接口调用 │
|
|
|
|
|
|
│ 统一用户认证 │
|
|
|
|
|
|
│ 统一数据格式 │
|
|
|
|
|
|
└─────────┬─────────┘
|
|
|
|
|
|
│
|
|
|
|
|
|
┌─────────┴─────────┐
|
|
|
|
|
|
│ 后端微服务集群 │
|
|
|
|
|
|
└───────────────────┘
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
## 2. 微服务划分
|
|
|
|
|
|
|
|
|
|
|
|
### 2.1 服务模块
|
|
|
|
|
|
| 服务名称 | 职责 | 技术栈 |
|
|
|
|
|
|
|----------|------|--------|
|
|
|
|
|
|
| user-service | 统一用户管理、权限控制 | Express.js + Sequelize + JWT + RBAC |
|
|
|
|
|
|
| auth-service | 统一认证中心、单点登录 | Express.js + Sequelize + OAuth2.0 |
|
|
|
|
|
|
| order-service | 订单管理、流程控制 | Express.js + Sequelize + MySQL |
|
|
|
|
|
|
| transport-service | 运输跟踪、状态管理 | Express.js + Sequelize + WebSocket |
|
|
|
|
|
|
| payment-service | 支付结算、财务处理 | Express.js + Sequelize + 支付接口 |
|
|
|
|
|
|
| file-service | 文件管理、视频处理 | Express.js + Sequelize + MinIO |
|
|
|
|
|
|
| notification-service | 消息通知、提醒 | Express.js + Sequelize + RabbitMQ |
|
|
|
|
|
|
| mini-program-service | 小程序接口统一管理 | Express.js + Sequelize + 接口网关 |
|
|
|
|
|
|
|
|
|
|
|
|
### 2.2 小程序服务接口
|
|
|
|
|
|
```typescript
|
|
|
|
|
|
// 统一小程序API接口设计
|
|
|
|
|
|
export interface MiniProgramApi {
|
|
|
|
|
|
// 用户认证接口
|
|
|
|
|
|
login(phone: string, code: string): Promise<UserInfo>;
|
|
|
|
|
|
logout(): Promise<void>;
|
|
|
|
|
|
|
|
|
|
|
|
// 订单相关接口
|
|
|
|
|
|
createOrder(orderData: OrderCreateDto): Promise<Order>;
|
|
|
|
|
|
getOrderList(params: OrderQueryDto): Promise<Order[]>;
|
|
|
|
|
|
getOrderDetail(orderId: string): Promise<OrderDetail>;
|
|
|
|
|
|
|
|
|
|
|
|
// 运输跟踪接口
|
|
|
|
|
|
reportLocation(location: LocationDto): Promise<void>;
|
|
|
|
|
|
getTransportTrack(orderId: string): Promise<TransportTrack[]>;
|
|
|
|
|
|
|
|
|
|
|
|
// 文件上传接口
|
|
|
|
|
|
uploadFile(file: File, type: FileType): Promise<FileResponse>;
|
|
|
|
|
|
|
|
|
|
|
|
// 消息通知接口
|
|
|
|
|
|
getNotifications(): Promise<Notification[]>;
|
|
|
|
|
|
markAsRead(notificationId: string): Promise<void>;
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
## 3. 数据库设计
|
|
|
|
|
|
|
|
|
|
|
|
### 3.1 Sequelize ORM 模型定义
|
|
|
|
|
|
```javascript
|
|
|
|
|
|
// 统一用户模型
|
|
|
|
|
|
const User = sequelize.define('User', {
|
|
|
|
|
|
id: {
|
|
|
|
|
|
type: DataTypes.BIGINT,
|
|
|
|
|
|
primaryKey: true,
|
|
|
|
|
|
autoIncrement: true
|
|
|
|
|
|
},
|
|
|
|
|
|
uuid: {
|
|
|
|
|
|
type: DataTypes.STRING(36),
|
|
|
|
|
|
allowNull: false,
|
|
|
|
|
|
unique: true
|
|
|
|
|
|
},
|
|
|
|
|
|
username: {
|
|
|
|
|
|
type: DataTypes.STRING(50),
|
|
|
|
|
|
allowNull: false,
|
|
|
|
|
|
unique: true
|
|
|
|
|
|
},
|
|
|
|
|
|
password_hash: {
|
|
|
|
|
|
type: DataTypes.STRING(255),
|
|
|
|
|
|
allowNull: false
|
|
|
|
|
|
},
|
|
|
|
|
|
phone: {
|
|
|
|
|
|
type: DataTypes.STRING(20),
|
|
|
|
|
|
allowNull: false,
|
|
|
|
|
|
unique: true
|
|
|
|
|
|
},
|
|
|
|
|
|
email: DataTypes.STRING(100),
|
|
|
|
|
|
real_name: DataTypes.STRING(50),
|
|
|
|
|
|
avatar_url: DataTypes.STRING(255),
|
|
|
|
|
|
user_type: {
|
|
|
|
|
|
type: DataTypes.ENUM('client','supplier','driver','staff','admin'),
|
|
|
|
|
|
allowNull: false
|
|
|
|
|
|
},
|
|
|
|
|
|
status: {
|
|
|
|
|
|
type: DataTypes.ENUM('active','inactive','locked'),
|
|
|
|
|
|
defaultValue: 'active'
|
|
|
|
|
|
}
|
|
|
|
|
|
}, {
|
|
|
|
|
|
tableName: 'users',
|
|
|
|
|
|
timestamps: true,
|
|
|
|
|
|
indexes: [
|
|
|
|
|
|
{ fields: ['phone'] },
|
|
|
|
|
|
{ fields: ['user_type'] },
|
|
|
|
|
|
{ fields: ['status'] }
|
|
|
|
|
|
]
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 用户角色模型
|
|
|
|
|
|
const UserRole = sequelize.define('UserRole', {
|
|
|
|
|
|
id: {
|
|
|
|
|
|
type: DataTypes.BIGINT,
|
|
|
|
|
|
primaryKey: true,
|
|
|
|
|
|
autoIncrement: true
|
|
|
|
|
|
},
|
|
|
|
|
|
role_code: {
|
|
|
|
|
|
type: DataTypes.STRING(50),
|
|
|
|
|
|
allowNull: false
|
|
|
|
|
|
}
|
|
|
|
|
|
}, {
|
|
|
|
|
|
tableName: 'user_roles',
|
|
|
|
|
|
timestamps: true
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 用户会话模型
|
|
|
|
|
|
const UserSession = sequelize.define('UserSession', {
|
|
|
|
|
|
id: {
|
|
|
|
|
|
type: DataTypes.BIGINT,
|
|
|
|
|
|
primaryKey: true,
|
|
|
|
|
|
autoIncrement: true
|
|
|
|
|
|
},
|
|
|
|
|
|
session_token: {
|
|
|
|
|
|
type: DataTypes.STRING(255),
|
|
|
|
|
|
allowNull: false,
|
|
|
|
|
|
unique: true
|
|
|
|
|
|
},
|
|
|
|
|
|
device_type: {
|
|
|
|
|
|
type: DataTypes.ENUM('web','mini_program','app'),
|
|
|
|
|
|
allowNull: false
|
|
|
|
|
|
},
|
|
|
|
|
|
device_info: DataTypes.STRING(500),
|
|
|
|
|
|
login_ip: DataTypes.STRING(45),
|
|
|
|
|
|
expires_at: {
|
|
|
|
|
|
type: DataTypes.DATE,
|
|
|
|
|
|
allowNull: false
|
|
|
|
|
|
}
|
|
|
|
|
|
}, {
|
|
|
|
|
|
tableName: 'user_sessions',
|
|
|
|
|
|
timestamps: true,
|
|
|
|
|
|
indexes: [
|
|
|
|
|
|
{ fields: ['session_token'] },
|
|
|
|
|
|
{ fields: ['user_id', 'device_type'] }
|
|
|
|
|
|
]
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 订单模型
|
|
|
|
|
|
const Order = sequelize.define('Order', {
|
|
|
|
|
|
id: {
|
|
|
|
|
|
type: DataTypes.BIGINT,
|
|
|
|
|
|
primaryKey: true,
|
|
|
|
|
|
autoIncrement: true
|
|
|
|
|
|
},
|
|
|
|
|
|
order_no: {
|
|
|
|
|
|
type: DataTypes.STRING(50),
|
|
|
|
|
|
allowNull: false,
|
|
|
|
|
|
unique: true
|
|
|
|
|
|
},
|
|
|
|
|
|
breed_type: {
|
|
|
|
|
|
type: DataTypes.STRING(20),
|
|
|
|
|
|
allowNull: false
|
|
|
|
|
|
},
|
|
|
|
|
|
min_weight: {
|
|
|
|
|
|
type: DataTypes.DECIMAL(10, 2),
|
|
|
|
|
|
allowNull: false
|
|
|
|
|
|
},
|
|
|
|
|
|
max_weight: {
|
|
|
|
|
|
type: DataTypes.DECIMAL(10, 2),
|
|
|
|
|
|
allowNull: false
|
|
|
|
|
|
},
|
|
|
|
|
|
total_count: {
|
|
|
|
|
|
type: DataTypes.INTEGER,
|
|
|
|
|
|
allowNull: false
|
|
|
|
|
|
},
|
|
|
|
|
|
total_weight: DataTypes.DECIMAL(10, 2),
|
|
|
|
|
|
unit_price: {
|
|
|
|
|
|
type: DataTypes.DECIMAL(10, 2),
|
|
|
|
|
|
allowNull: false
|
|
|
|
|
|
},
|
|
|
|
|
|
total_amount: {
|
|
|
|
|
|
type: DataTypes.DECIMAL(15, 2),
|
|
|
|
|
|
allowNull: false
|
|
|
|
|
|
},
|
|
|
|
|
|
status: {
|
|
|
|
|
|
type: DataTypes.ENUM('pending','confirmed','loading','shipping','delivered','completed','cancelled'),
|
|
|
|
|
|
defaultValue: 'pending'
|
|
|
|
|
|
}
|
|
|
|
|
|
}, {
|
|
|
|
|
|
tableName: 'orders',
|
|
|
|
|
|
timestamps: true,
|
|
|
|
|
|
indexes: [
|
|
|
|
|
|
{ fields: ['order_no'] },
|
|
|
|
|
|
{ fields: ['buyer_id'] },
|
|
|
|
|
|
{ fields: ['status'] },
|
|
|
|
|
|
{ fields: ['created_at'] }
|
|
|
|
|
|
]
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 定义模型关联关系
|
|
|
|
|
|
User.hasMany(UserRole, { foreignKey: 'user_id' });
|
|
|
|
|
|
UserRole.belongsTo(User, { foreignKey: 'user_id' });
|
|
|
|
|
|
|
|
|
|
|
|
User.hasMany(UserSession, { foreignKey: 'user_id' });
|
|
|
|
|
|
UserSession.belongsTo(User, { foreignKey: 'user_id' });
|
|
|
|
|
|
|
|
|
|
|
|
User.hasMany(Order, { foreignKey: 'buyer_id', as: 'BuyerOrders' });
|
|
|
|
|
|
User.hasMany(Order, { foreignKey: 'trader_id', as: 'TraderOrders' });
|
|
|
|
|
|
User.hasMany(Order, { foreignKey: 'supplier_id', as: 'SupplierOrders' });
|
|
|
|
|
|
Order.belongsTo(User, { foreignKey: 'buyer_id', as: 'Buyer' });
|
|
|
|
|
|
Order.belongsTo(User, { foreignKey: 'trader_id', as: 'Trader' });
|
|
|
|
|
|
Order.belongsTo(User, { foreignKey: 'supplier_id', as: 'Supplier' });
|
|
|
|
|
|
```
|
|
|
|
|
|
// 运输跟踪模型
|
|
|
|
|
|
const TransportTrack = sequelize.define('TransportTrack', {
|
|
|
|
|
|
id: {
|
|
|
|
|
|
type: DataTypes.BIGINT,
|
|
|
|
|
|
primaryKey: true,
|
|
|
|
|
|
autoIncrement: true
|
|
|
|
|
|
},
|
|
|
|
|
|
latitude: {
|
|
|
|
|
|
type: DataTypes.DECIMAL(10, 8),
|
|
|
|
|
|
allowNull: true
|
|
|
|
|
|
},
|
|
|
|
|
|
longitude: {
|
|
|
|
|
|
type: DataTypes.DECIMAL(11, 8),
|
|
|
|
|
|
allowNull: true
|
|
|
|
|
|
},
|
|
|
|
|
|
speed: {
|
|
|
|
|
|
type: DataTypes.DECIMAL(5, 2),
|
|
|
|
|
|
allowNull: true
|
|
|
|
|
|
},
|
|
|
|
|
|
direction: {
|
|
|
|
|
|
type: DataTypes.DECIMAL(5, 2),
|
|
|
|
|
|
allowNull: true
|
|
|
|
|
|
},
|
|
|
|
|
|
cattle_status: {
|
|
|
|
|
|
type: DataTypes.STRING(20),
|
|
|
|
|
|
allowNull: true
|
|
|
|
|
|
},
|
|
|
|
|
|
video_url: {
|
|
|
|
|
|
type: DataTypes.STRING(255),
|
|
|
|
|
|
allowNull: true
|
|
|
|
|
|
}
|
|
|
|
|
|
}, {
|
|
|
|
|
|
tableName: 'transport_tracks',
|
|
|
|
|
|
timestamps: true
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 结算模型
|
|
|
|
|
|
const Settlement = sequelize.define('Settlement', {
|
|
|
|
|
|
id: {
|
|
|
|
|
|
type: DataTypes.BIGINT,
|
|
|
|
|
|
primaryKey: true,
|
|
|
|
|
|
autoIncrement: true
|
|
|
|
|
|
},
|
|
|
|
|
|
prepayment_amount: {
|
|
|
|
|
|
type: DataTypes.DECIMAL(15, 2),
|
|
|
|
|
|
allowNull: true
|
|
|
|
|
|
},
|
|
|
|
|
|
final_amount: {
|
|
|
|
|
|
type: DataTypes.DECIMAL(15, 2),
|
|
|
|
|
|
allowNull: true
|
|
|
|
|
|
},
|
|
|
|
|
|
total_amount: {
|
|
|
|
|
|
type: DataTypes.DECIMAL(15, 2),
|
|
|
|
|
|
allowNull: false
|
|
|
|
|
|
},
|
|
|
|
|
|
payment_status: {
|
|
|
|
|
|
type: DataTypes.ENUM('pending', 'paid', 'refunded'),
|
|
|
|
|
|
defaultValue: 'pending'
|
|
|
|
|
|
},
|
|
|
|
|
|
payment_time: {
|
|
|
|
|
|
type: DataTypes.DATE,
|
|
|
|
|
|
allowNull: true
|
|
|
|
|
|
}
|
|
|
|
|
|
}, {
|
|
|
|
|
|
tableName: 'settlements',
|
|
|
|
|
|
timestamps: true
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 完善模型关联关系
|
|
|
|
|
|
Order.hasMany(TransportTrack, { foreignKey: 'order_id' });
|
|
|
|
|
|
TransportTrack.belongsTo(Order, { foreignKey: 'order_id' });
|
|
|
|
|
|
|
|
|
|
|
|
Order.hasMany(Settlement, { foreignKey: 'order_id' });
|
|
|
|
|
|
Settlement.belongsTo(Order, { foreignKey: 'order_id' });
|
|
|
|
|
|
|
|
|
|
|
|
User.hasMany(TransportTrack, { foreignKey: 'driver_id', as: 'DriverTracks' });
|
|
|
|
|
|
TransportTrack.belongsTo(User, { foreignKey: 'driver_id', as: 'Driver' });
|
|
|
|
|
|
|
|
|
|
|
|
// Sequelize 连接配置
|
|
|
|
|
|
const sequelize = new Sequelize('jiebandata', 'root', 'aiotAiot123!', {
|
|
|
|
|
|
host: '129.211.213.226',
|
|
|
|
|
|
port: 9527,
|
|
|
|
|
|
dialect: 'mysql',
|
|
|
|
|
|
dialectOptions: {
|
|
|
|
|
|
charset: 'utf8mb4',
|
|
|
|
|
|
collate: 'utf8mb4_unicode_ci'
|
|
|
|
|
|
},
|
|
|
|
|
|
pool: {
|
|
|
|
|
|
max: 10,
|
|
|
|
|
|
min: 0,
|
|
|
|
|
|
acquire: 30000,
|
|
|
|
|
|
idle: 10000
|
|
|
|
|
|
},
|
|
|
|
|
|
logging: process.env.NODE_ENV === 'development' ? console.log : false,
|
|
|
|
|
|
timezone: '+08:00' // 东八区
|
|
|
|
|
|
});
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
## 4. 统一API接口设计
|
|
|
|
|
|
|
|
|
|
|
|
### 4.1 Express.js + Swagger 配置
|
|
|
|
|
|
```javascript
|
|
|
|
|
|
// app.js - Express.js 应用配置
|
|
|
|
|
|
const express = require('express');
|
|
|
|
|
|
const swaggerJsdoc = require('swagger-jsdoc');
|
|
|
|
|
|
const swaggerUi = require('swagger-ui-express');
|
|
|
|
|
|
const cors = require('cors');
|
|
|
|
|
|
const helmet = require('helmet');
|
|
|
|
|
|
const rateLimit = require('express-rate-limit');
|
|
|
|
|
|
|
|
|
|
|
|
const app = express();
|
|
|
|
|
|
|
|
|
|
|
|
// 中间件配置
|
|
|
|
|
|
app.use(helmet());
|
|
|
|
|
|
app.use(cors());
|
|
|
|
|
|
app.use(express.json({ limit: '10mb' }));
|
|
|
|
|
|
app.use(express.urlencoded({ extended: true }));
|
|
|
|
|
|
|
|
|
|
|
|
// 速率限制
|
|
|
|
|
|
const limiter = rateLimit({
|
|
|
|
|
|
windowMs: 15 * 60 * 1000, // 15分钟
|
|
|
|
|
|
max: 100 // 每个IP限制100个请求
|
|
|
|
|
|
});
|
|
|
|
|
|
app.use(limiter);
|
|
|
|
|
|
|
|
|
|
|
|
// Swagger 配置
|
|
|
|
|
|
const swaggerOptions = {
|
|
|
|
|
|
definition: {
|
|
|
|
|
|
openapi: '3.0.0',
|
|
|
|
|
|
info: {
|
|
|
|
|
|
title: '活牛采购智能数字化系统 API',
|
|
|
|
|
|
version: '1.0.0',
|
|
|
|
|
|
description: '活牛采购标准化操作流程系统接口文档',
|
|
|
|
|
|
contact: {
|
|
|
|
|
|
name: 'API支持',
|
|
|
|
|
|
email: 'support@niumall.com'
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
servers: [
|
|
|
|
|
|
{
|
|
|
|
|
|
url: 'http://localhost:3000/api',
|
|
|
|
|
|
description: '开发环境'
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
url: 'https://api.niumall.com/api',
|
|
|
|
|
|
description: '生产环境'
|
|
|
|
|
|
}
|
|
|
|
|
|
],
|
|
|
|
|
|
components: {
|
|
|
|
|
|
securitySchemes: {
|
|
|
|
|
|
BearerAuth: {
|
|
|
|
|
|
type: 'http',
|
|
|
|
|
|
scheme: 'bearer',
|
|
|
|
|
|
bearerFormat: 'JWT'
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
schemas: {
|
|
|
|
|
|
ApiResponse: {
|
|
|
|
|
|
type: 'object',
|
|
|
|
|
|
properties: {
|
|
|
|
|
|
code: { type: 'integer', description: '状态码:200成功,400客户端错误,500服务端错误' },
|
|
|
|
|
|
message: { type: 'string', description: '提示信息' },
|
|
|
|
|
|
data: { type: 'object', description: '响应数据' },
|
|
|
|
|
|
timestamp: { type: 'integer', description: '时间戳' }
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
PaginationParams: {
|
|
|
|
|
|
type: 'object',
|
|
|
|
|
|
properties: {
|
|
|
|
|
|
page: { type: 'integer', description: '当前页码' },
|
|
|
|
|
|
limit: { type: 'integer', description: '每页数量' },
|
|
|
|
|
|
sort: { type: 'string', description: '排序字段' },
|
|
|
|
|
|
order: { type: 'string', enum: ['asc', 'desc'], description: '排序方向' }
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
PaginatedResponse: {
|
|
|
|
|
|
type: 'object',
|
|
|
|
|
|
properties: {
|
|
|
|
|
|
items: { type: 'array', description: '数据列表' },
|
|
|
|
|
|
total: { type: 'integer', description: '总记录数' },
|
|
|
|
|
|
page: { type: 'integer', description: '当前页码' },
|
|
|
|
|
|
limit: { type: 'integer', description: '每页数量' },
|
|
|
|
|
|
totalPages: { type: 'integer', description: '总页数' }
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
apis: ['./routes/*.js', './models/*.js'] // API路由文件路径
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const swaggerSpec = swaggerJsdoc(swaggerOptions);
|
|
|
|
|
|
app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerSpec));
|
|
|
|
|
|
|
|
|
|
|
|
// 统一响应中间件
|
|
|
|
|
|
app.use((req, res, next) => {
|
|
|
|
|
|
res.apiSuccess = (data, message = '成功') => {
|
|
|
|
|
|
res.json({
|
|
|
|
|
|
code: 200,
|
|
|
|
|
|
message,
|
|
|
|
|
|
data,
|
|
|
|
|
|
timestamp: Date.now()
|
|
|
|
|
|
});
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
res.apiError = (message = '服务器错误', code = 500) => {
|
|
|
|
|
|
res.status(code).json({
|
|
|
|
|
|
code,
|
|
|
|
|
|
message,
|
|
|
|
|
|
data: null,
|
|
|
|
|
|
timestamp: Date.now()
|
|
|
|
|
|
});
|
|
|
|
|
|
};
|
|
|
|
|
|
next();
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// JWT认证中间件
|
|
|
|
|
|
const jwt = require('jsonwebtoken');
|
|
|
|
|
|
app.use((req, res, next) => {
|
|
|
|
|
|
const token = req.header('Authorization')?.replace('Bearer ', '');
|
|
|
|
|
|
if (token) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const decoded = jwt.verify(token, process.env.JWT_SECRET || 'your-secret-key');
|
|
|
|
|
|
req.user = decoded;
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
// Token验证失败,但不阻止请求
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
next();
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
module.exports = app;
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 4.2 Express.js 认证路由示例
|
|
|
|
|
|
```javascript
|
|
|
|
|
|
// routes/auth.js - 认证路由
|
|
|
|
|
|
const express = require('express');
|
|
|
|
|
|
const jwt = require('jsonwebtoken');
|
|
|
|
|
|
const { User } = require('../models');
|
|
|
|
|
|
const router = express.Router();
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* @swagger
|
|
|
|
|
|
* /api/auth/mini-program/login:
|
|
|
|
|
|
* post:
|
|
|
|
|
|
* summary: 小程序用户登录
|
|
|
|
|
|
* tags: [认证]
|
|
|
|
|
|
* requestBody:
|
|
|
|
|
|
* required: true
|
|
|
|
|
|
* content:
|
|
|
|
|
|
* application/json:
|
|
|
|
|
|
* schema:
|
|
|
|
|
|
* type: object
|
|
|
|
|
|
* required:
|
|
|
|
|
|
* - phone
|
|
|
|
|
|
* - code
|
|
|
|
|
|
* - miniProgramType
|
|
|
|
|
|
* properties:
|
|
|
|
|
|
* phone:
|
|
|
|
|
|
* type: string
|
|
|
|
|
|
* description: 手机号
|
|
|
|
|
|
* example: "13800138000"
|
|
|
|
|
|
* code:
|
|
|
|
|
|
* type: string
|
|
|
|
|
|
* description: 验证码
|
|
|
|
|
|
* example: "123456"
|
|
|
|
|
|
* miniProgramType:
|
|
|
|
|
|
* type: string
|
|
|
|
|
|
* enum: [client, supplier, driver, staff]
|
|
|
|
|
|
* description: 小程序类型
|
|
|
|
|
|
* example: "client"
|
|
|
|
|
|
* responses:
|
|
|
|
|
|
* 200:
|
|
|
|
|
|
* description: 登录成功
|
|
|
|
|
|
* content:
|
|
|
|
|
|
* application/json:
|
|
|
|
|
|
* schema:
|
|
|
|
|
|
* $ref: '#/components/schemas/ApiResponse'
|
|
|
|
|
|
* example:
|
|
|
|
|
|
* code: 200
|
|
|
|
|
|
* message: "登录成功"
|
|
|
|
|
|
* data:
|
|
|
|
|
|
* token: "jwt_token_string"
|
|
|
|
|
|
* userInfo:
|
|
|
|
|
|
* id: 1
|
|
|
|
|
|
* username: "user123"
|
|
|
|
|
|
* realName: "张三"
|
|
|
|
|
|
* avatar: "https://example.com/avatar.jpg"
|
|
|
|
|
|
* userType: "client"
|
|
|
|
|
|
* roles: ["purchaser"]
|
|
|
|
|
|
* timestamp: 1643097600000
|
|
|
|
|
|
*/
|
|
|
|
|
|
router.post('/mini-program/login', async (req, res) => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const { phone, code, miniProgramType } = req.body;
|
|
|
|
|
|
|
|
|
|
|
|
// 验证验证码逻辑
|
|
|
|
|
|
// ...
|
|
|
|
|
|
|
|
|
|
|
|
// 查找用户
|
|
|
|
|
|
const user = await User.findOne({
|
|
|
|
|
|
where: { phone, user_type: miniProgramType }
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
if (!user) {
|
|
|
|
|
|
return res.apiError('用户不存在', 404);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 生成JWT token
|
|
|
|
|
|
const token = jwt.sign(
|
|
|
|
|
|
{
|
|
|
|
|
|
id: user.id,
|
|
|
|
|
|
username: user.username,
|
|
|
|
|
|
userType: user.user_type
|
|
|
|
|
|
},
|
|
|
|
|
|
process.env.JWT_SECRET || 'your-secret-key',
|
|
|
|
|
|
{ expiresIn: '7d' }
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
res.apiSuccess({
|
|
|
|
|
|
token,
|
|
|
|
|
|
userInfo: {
|
|
|
|
|
|
id: user.id,
|
|
|
|
|
|
username: user.username,
|
|
|
|
|
|
realName: user.real_name,
|
|
|
|
|
|
avatar: user.avatar_url,
|
|
|
|
|
|
userType: user.user_type,
|
|
|
|
|
|
roles: [] // 根据实际业务获取角色
|
|
|
|
|
|
}
|
|
|
|
|
|
}, '登录成功');
|
|
|
|
|
|
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('Login error:', error);
|
|
|
|
|
|
res.apiError('登录失败');
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* @swagger
|
|
|
|
|
|
* /api/auth/user-info:
|
|
|
|
|
|
* get:
|
|
|
|
|
|
* summary: 获取当前用户信息
|
|
|
|
|
|
* tags: [认证]
|
|
|
|
|
|
* security:
|
|
|
|
|
|
* - BearerAuth: []
|
|
|
|
|
|
* responses:
|
|
|
|
|
|
* 200:
|
|
|
|
|
|
* description: 获取成功
|
|
|
|
|
|
* content:
|
|
|
|
|
|
* application/json:
|
|
|
|
|
|
* schema:
|
|
|
|
|
|
* $ref: '#/components/schemas/ApiResponse'
|
|
|
|
|
|
*/
|
|
|
|
|
|
router.get('/user-info', (req, res) => {
|
|
|
|
|
|
if (!req.user) {
|
|
|
|
|
|
return res.apiError('未认证', 401);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 从数据库获取完整的用户信息
|
|
|
|
|
|
User.findByPk(req.user.id)
|
|
|
|
|
|
.then(user => {
|
|
|
|
|
|
res.apiSuccess({
|
|
|
|
|
|
id: user.id,
|
|
|
|
|
|
username: user.username,
|
|
|
|
|
|
realName: user.real_name,
|
|
|
|
|
|
avatar: user.avatar_url,
|
|
|
|
|
|
userType: user.user_type,
|
|
|
|
|
|
phone: user.phone,
|
|
|
|
|
|
email: user.email
|
|
|
|
|
|
});
|
|
|
|
|
|
})
|
|
|
|
|
|
.catch(error => {
|
|
|
|
|
|
res.apiError('获取用户信息失败');
|
|
|
|
|
|
});
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
module.exports = router;
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 4.3 Express.js 订单路由示例
|
|
|
|
|
|
```javascript
|
|
|
|
|
|
// routes/orders.js - 订单路由
|
|
|
|
|
|
const express = require('express');
|
|
|
|
|
|
const { Order, User } = require('../models');
|
|
|
|
|
|
const router = express.Router();
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* @swagger
|
|
|
|
|
|
* /api/orders:
|
|
|
|
|
|
* post:
|
|
|
|
|
|
* summary: 创建采购订单
|
|
|
|
|
|
* tags: [订单]
|
|
|
|
|
|
* security:
|
|
|
|
|
|
* - BearerAuth: []
|
|
|
|
|
|
* requestBody:
|
|
|
|
|
|
* required: true
|
|
|
|
|
|
* content:
|
|
|
|
|
|
* application/json:
|
|
|
|
|
|
* schema:
|
|
|
|
|
|
* type: object
|
|
|
|
|
|
* required:
|
|
|
|
|
|
* - breedType
|
|
|
|
|
|
* - minWeight
|
|
|
|
|
|
* - maxWeight
|
|
|
|
|
|
* - totalCount
|
|
|
|
|
|
* - unitPrice
|
|
|
|
|
|
* properties:
|
|
|
|
|
|
* breedType:
|
|
|
|
|
|
* type: string
|
|
|
|
|
|
* description: 牛品种
|
|
|
|
|
|
* example: "simmental"
|
|
|
|
|
|
* minWeight:
|
|
|
|
|
|
* type: number
|
|
|
|
|
|
* format: float
|
|
|
|
|
|
* description: 最低重量(kg)
|
|
|
|
|
|
* example: 500
|
|
|
|
|
|
* maxWeight:
|
|
|
|
|
|
* type: number
|
|
|
|
|
|
* format: float
|
|
|
|
|
|
* description: 最高重量(kg)
|
|
|
|
|
|
* example: 600
|
|
|
|
|
|
* totalCount:
|
|
|
|
|
|
* type: integer
|
|
|
|
|
|
* description: 总数量
|
|
|
|
|
|
* example: 100
|
|
|
|
|
|
* unitPrice:
|
|
|
|
|
|
* type: number
|
|
|
|
|
|
* format: float
|
|
|
|
|
|
* description: 单价(元/kg)
|
|
|
|
|
|
* example: 35.5
|
|
|
|
|
|
* deliveryAddress:
|
|
|
|
|
|
* type: string
|
|
|
|
|
|
* description: 配送地址
|
|
|
|
|
|
* example: "xxx养殖场"
|
|
|
|
|
|
* deliveryDate:
|
|
|
|
|
|
* type: string
|
|
|
|
|
|
* format: date
|
|
|
|
|
|
* description: 配送日期
|
|
|
|
|
|
* example: "2024-01-25"
|
|
|
|
|
|
* specialRequirements:
|
|
|
|
|
|
* type: string
|
|
|
|
|
|
* description: 特殊要求
|
|
|
|
|
|
* example: "要求健康无病"
|
|
|
|
|
|
* responses:
|
|
|
|
|
|
* 201:
|
|
|
|
|
|
* description: 订单创建成功
|
|
|
|
|
|
*/
|
|
|
|
|
|
router.post('/', async (req, res) => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const {
|
|
|
|
|
|
breedType,
|
|
|
|
|
|
minWeight,
|
|
|
|
|
|
maxWeight,
|
|
|
|
|
|
totalCount,
|
|
|
|
|
|
unitPrice,
|
|
|
|
|
|
deliveryAddress,
|
|
|
|
|
|
deliveryDate,
|
|
|
|
|
|
specialRequirements
|
|
|
|
|
|
} = req.body;
|
|
|
|
|
|
|
|
|
|
|
|
// 计算总金额
|
|
|
|
|
|
const avgWeight = (minWeight + maxWeight) / 2;
|
|
|
|
|
|
const totalAmount = avgWeight * totalCount * unitPrice;
|
|
|
|
|
|
|
|
|
|
|
|
// 生成订单号
|
|
|
|
|
|
const orderNo = 'ORD' + Date.now() + Math.random().toString(36).substr(2, 6);
|
|
|
|
|
|
|
|
|
|
|
|
const order = await Order.create({
|
|
|
|
|
|
order_no: orderNo,
|
|
|
|
|
|
buyer_id: req.user.id,
|
|
|
|
|
|
trader_id: 1, // 默认贸易商ID
|
|
|
|
|
|
supplier_id: 1, // 默认供应商ID
|
|
|
|
|
|
breed_type: breedType,
|
|
|
|
|
|
min_weight: minWeight,
|
|
|
|
|
|
max_weight: maxWeight,
|
|
|
|
|
|
total_count: totalCount,
|
|
|
|
|
|
total_weight: avgWeight * totalCount,
|
|
|
|
|
|
unit_price: unitPrice,
|
|
|
|
|
|
total_amount: totalAmount,
|
|
|
|
|
|
status: 'pending'
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
res.apiSuccess(order, '订单创建成功');
|
|
|
|
|
|
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('Create order error:', error);
|
|
|
|
|
|
res.apiError('创建订单失败');
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* @swagger
|
|
|
|
|
|
* /api/orders:
|
|
|
|
|
|
* get:
|
|
|
|
|
|
* summary: 获取订单列表
|
|
|
|
|
|
* tags: [订单]
|
|
|
|
|
|
* security:
|
|
|
|
|
|
* - BearerAuth: []
|
|
|
|
|
|
* parameters:
|
|
|
|
|
|
* - in: query
|
|
|
|
|
|
* name: status
|
|
|
|
|
|
* schema:
|
|
|
|
|
|
* type: string
|
|
|
|
|
|
* description: 订单状态过滤
|
|
|
|
|
|
* - in: query
|
|
|
|
|
|
* name: page
|
|
|
|
|
|
* schema:
|
|
|
|
|
|
* type: integer
|
|
|
|
|
|
* default: 1
|
|
|
|
|
|
* description: 页码
|
|
|
|
|
|
* - in: query
|
|
|
|
|
|
* name: limit
|
|
|
|
|
|
* schema:
|
|
|
|
|
|
* type: integer
|
|
|
|
|
|
* default: 10
|
|
|
|
|
|
* description: 每页数量
|
|
|
|
|
|
* responses:
|
|
|
|
|
|
* 200:
|
|
|
|
|
|
* description: 获取成功
|
|
|
|
|
|
*/
|
|
|
|
|
|
router.get('/', async (req, res) => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const { status, page = 1, limit = 10 } = req.query;
|
|
|
|
|
|
const offset = (page - 1) * limit;
|
|
|
|
|
|
|
|
|
|
|
|
const whereClause = {};
|
|
|
|
|
|
if (status) {
|
|
|
|
|
|
whereClause.status = status;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 根据用户类型过滤订单
|
|
|
|
|
|
if (req.user.userType === 'client') {
|
|
|
|
|
|
whereClause.buyer_id = req.user.id;
|
|
|
|
|
|
} else if (req.user.userType === 'supplier') {
|
|
|
|
|
|
whereClause.supplier_id = req.user.id;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const { count, rows: orders } = await Order.findAndCountAll({
|
|
|
|
|
|
where: whereClause,
|
|
|
|
|
|
limit: parseInt(limit),
|
|
|
|
|
|
offset: parseInt(offset),
|
|
|
|
|
|
order: [['created_at', 'DESC']],
|
|
|
|
|
|
include: [
|
|
|
|
|
|
{ model: User, as: 'Buyer', attributes: ['id', 'real_name', 'phone'] },
|
|
|
|
|
|
{ model: User, as: 'Supplier', attributes: ['id', 'real_name', 'phone'] }
|
|
|
|
|
|
]
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
res.apiSuccess({
|
|
|
|
|
|
items: orders,
|
|
|
|
|
|
total: count,
|
|
|
|
|
|
page: parseInt(page),
|
|
|
|
|
|
limit: parseInt(limit),
|
|
|
|
|
|
totalPages: Math.ceil(count / limit)
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('Get orders error:', error);
|
|
|
|
|
|
res.apiError('获取订单列表失败');
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
module.exports = router;
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 4.4 Express.js 运输跟踪路由示例
|
|
|
|
|
|
```javascript
|
|
|
|
|
|
// routes/transport.js - 运输跟踪路由
|
|
|
|
|
|
const express = require('express');
|
|
|
|
|
|
const { TransportTrack, Order } = require('../models');
|
|
|
|
|
|
const router = express.Router();
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* @swagger
|
|
|
|
|
|
* /api/transport/tracks:
|
|
|
|
|
|
* post:
|
|
|
|
|
|
* summary: 司机上报位置信息
|
|
|
|
|
|
* tags: [运输跟踪]
|
|
|
|
|
|
* security:
|
|
|
|
|
|
* - BearerAuth: []
|
|
|
|
|
|
* requestBody:
|
|
|
|
|
|
* required: true
|
|
|
|
|
|
* content:
|
|
|
|
|
|
* application/json:
|
|
|
|
|
|
* schema:
|
|
|
|
|
|
* type: object
|
|
|
|
|
|
* required:
|
|
|
|
|
|
* - orderId
|
|
|
|
|
|
* - latitude
|
|
|
|
|
|
* - longitude
|
|
|
|
|
|
* properties:
|
|
|
|
|
|
* orderId:
|
|
|
|
|
|
* type: integer
|
|
|
|
|
|
* description: 订单ID
|
|
|
|
|
|
* example: 123
|
|
|
|
|
|
* latitude:
|
|
|
|
|
|
* type: number
|
|
|
|
|
|
* format: float
|
|
|
|
|
|
* description: 纬度
|
|
|
|
|
|
* example: 39.9042
|
|
|
|
|
|
* longitude:
|
|
|
|
|
|
* type: number
|
|
|
|
|
|
* format: float
|
|
|
|
|
|
* description: 经度
|
|
|
|
|
|
* example: 116.4074
|
|
|
|
|
|
* speed:
|
|
|
|
|
|
* type: number
|
|
|
|
|
|
* format: float
|
|
|
|
|
|
* description: 速度(km/h)
|
|
|
|
|
|
* example: 80.5
|
|
|
|
|
|
* direction:
|
|
|
|
|
|
* type: number
|
|
|
|
|
|
* format: float
|
|
|
|
|
|
* description: 方向(度)
|
|
|
|
|
|
* example: 45.2
|
|
|
|
|
|
* cattleStatus:
|
|
|
|
|
|
* type: string
|
|
|
|
|
|
* description: 牛只状态
|
|
|
|
|
|
* example: "normal"
|
|
|
|
|
|
* temperature:
|
|
|
|
|
|
* type: number
|
|
|
|
|
|
* format: float
|
|
|
|
|
|
* description: 车内温度(℃)
|
|
|
|
|
|
* example: 25.5
|
|
|
|
|
|
* humidity:
|
|
|
|
|
|
* type: number
|
|
|
|
|
|
* format: float
|
|
|
|
|
|
* description: 湿度(%)
|
|
|
|
|
|
* example: 60.2
|
|
|
|
|
|
* videoUrl:
|
|
|
|
|
|
* type: string
|
|
|
|
|
|
* description: 状态视频URL
|
|
|
|
|
|
* example: "https://example.com/status.mp4"
|
|
|
|
|
|
* responses:
|
|
|
|
|
|
* 200:
|
|
|
|
|
|
* description: 位置上报成功
|
|
|
|
|
|
*/
|
|
|
|
|
|
router.post('/tracks', async (req, res) => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const {
|
|
|
|
|
|
orderId,
|
|
|
|
|
|
latitude,
|
|
|
|
|
|
longitude,
|
|
|
|
|
|
speed,
|
|
|
|
|
|
direction,
|
|
|
|
|
|
cattleStatus,
|
|
|
|
|
|
temperature,
|
|
|
|
|
|
humidity,
|
|
|
|
|
|
videoUrl
|
|
|
|
|
|
} = req.body;
|
|
|
|
|
|
|
|
|
|
|
|
// 验证司机权限
|
|
|
|
|
|
const order = await Order.findByPk(orderId);
|
|
|
|
|
|
if (!order || order.driver_id !== req.user.id) {
|
|
|
|
|
|
return res.apiError('无权限操作此订单', 403);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const track = await TransportTrack.create({
|
|
|
|
|
|
order_id: orderId,
|
|
|
|
|
|
driver_id: req.user.id,
|
|
|
|
|
|
latitude,
|
|
|
|
|
|
longitude,
|
|
|
|
|
|
speed,
|
|
|
|
|
|
direction,
|
|
|
|
|
|
cattle_status: cattleStatus,
|
|
|
|
|
|
temperature,
|
|
|
|
|
|
humidity,
|
|
|
|
|
|
video_url: videoUrl
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 实时推送位置信息给相关用户
|
|
|
|
|
|
// WebSocket推送逻辑...
|
|
|
|
|
|
|
|
|
|
|
|
res.apiSuccess(track, '位置上报成功');
|
|
|
|
|
|
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('Report location error:', error);
|
|
|
|
|
|
res.apiError('位置上报失败');
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* @swagger
|
|
|
|
|
|
* /api/transport/orders/{orderId}/tracks:
|
|
|
|
|
|
* get:
|
|
|
|
|
|
* summary: 获取订单运输轨迹
|
|
|
|
|
|
* tags: [运输跟踪]
|
|
|
|
|
|
* security:
|
|
|
|
|
|
* - BearerAuth: []
|
|
|
|
|
|
* parameters:
|
|
|
|
|
|
* - in: path
|
|
|
|
|
|
* name: orderId
|
|
|
|
|
|
* required: true
|
|
|
|
|
|
* schema:
|
|
|
|
|
|
* type: integer
|
|
|
|
|
|
* description: 订单ID
|
|
|
|
|
|
* responses:
|
|
|
|
|
|
* 200:
|
|
|
|
|
|
* description: 获取成功
|
|
|
|
|
|
*/
|
|
|
|
|
|
router.get('/orders/:orderId/tracks', async (req, res) => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const { orderId } = req.params;
|
|
|
|
|
|
|
|
|
|
|
|
// 验证订单访问权限
|
|
|
|
|
|
const order = await Order.findByPk(orderId);
|
|
|
|
|
|
if (!order) {
|
|
|
|
|
|
return res.apiError('订单不存在', 404);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 权限验证:采购人只能查看自己的订单
|
|
|
|
|
|
if (req.user.userType === 'client' && order.buyer_id !== req.user.id) {
|
|
|
|
|
|
return res.apiError('无权限查看此订单', 403);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const tracks = await TransportTrack.findAll({
|
|
|
|
|
|
where: { order_id: orderId },
|
|
|
|
|
|
order: [['created_at', 'ASC']],
|
|
|
|
|
|
attributes: [
|
|
|
|
|
|
'id',
|
|
|
|
|
|
'latitude',
|
|
|
|
|
|
'longitude',
|
|
|
|
|
|
'speed',
|
|
|
|
|
|
'direction',
|
|
|
|
|
|
'cattle_status',
|
|
|
|
|
|
'temperature',
|
|
|
|
|
|
'humidity',
|
|
|
|
|
|
'video_url',
|
|
|
|
|
|
'created_at'
|
|
|
|
|
|
]
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
res.apiSuccess(tracks);
|
|
|
|
|
|
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('Get tracks error:', error);
|
|
|
|
|
|
res.apiError('获取运输轨迹失败');
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
module.exports = router;
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 4.5 Express.js 文件上传路由示例
|
|
|
|
|
|
```javascript
|
|
|
|
|
|
// routes/files.js - 文件上传路由
|
|
|
|
|
|
const express = require('express');
|
|
|
|
|
|
const multer = require('multer');
|
|
|
|
|
|
const path = require('path');
|
|
|
|
|
|
const { v4: uuidv4 } = require('uuid');
|
|
|
|
|
|
const router = express.Router();
|
|
|
|
|
|
|
|
|
|
|
|
// 配置multer文件上传
|
|
|
|
|
|
const storage = multer.diskStorage({
|
|
|
|
|
|
destination: (req, file, cb) => {
|
|
|
|
|
|
cb(null, 'uploads/');
|
|
|
|
|
|
},
|
|
|
|
|
|
filename: (req, file, cb) => {
|
|
|
|
|
|
const ext = path.extname(file.originalname);
|
|
|
|
|
|
cb(null, `${uuidv4()}${ext}`);
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
const upload = multer({
|
|
|
|
|
|
storage,
|
|
|
|
|
|
limits: {
|
|
|
|
|
|
fileSize: 100 * 1024 * 1024 // 100MB限制
|
|
|
|
|
|
},
|
|
|
|
|
|
fileFilter: (req, file, cb) => {
|
|
|
|
|
|
const allowedTypes = [
|
|
|
|
|
|
'image/jpeg',
|
|
|
|
|
|
'image/png',
|
|
|
|
|
|
'image/gif',
|
|
|
|
|
|
'video/mp4',
|
|
|
|
|
|
'video/quicktime',
|
|
|
|
|
|
'application/pdf'
|
|
|
|
|
|
];
|
|
|
|
|
|
if (allowedTypes.includes(file.mimetype)) {
|
|
|
|
|
|
cb(null, true);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
cb(new Error('不支持的文件类型'));
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* @swagger
|
|
|
|
|
|
* /api/files/upload:
|
|
|
|
|
|
* post:
|
|
|
|
|
|
* summary: 统一文件上传接口
|
|
|
|
|
|
* tags: [文件管理]
|
|
|
|
|
|
* security:
|
|
|
|
|
|
* - BearerAuth: []
|
|
|
|
|
|
* requestBody:
|
|
|
|
|
|
* required: true
|
|
|
|
|
|
* content:
|
|
|
|
|
|
* multipart/form-data:
|
|
|
|
|
|
* schema:
|
|
|
|
|
|
* type: object
|
|
|
|
|
|
* required:
|
|
|
|
|
|
* - file
|
|
|
|
|
|
* properties:
|
|
|
|
|
|
* file:
|
|
|
|
|
|
* type: string
|
|
|
|
|
|
* format: binary
|
|
|
|
|
|
* description: 上传的文件
|
|
|
|
|
|
* type:
|
|
|
|
|
|
* type: string
|
|
|
|
|
|
* description: 文件类型
|
|
|
|
|
|
* example: "cattle_video"
|
|
|
|
|
|
* businessId:
|
|
|
|
|
|
* type: string
|
|
|
|
|
|
* description: 业务ID
|
|
|
|
|
|
* example: "order_123"
|
|
|
|
|
|
* description:
|
|
|
|
|
|
* type: string
|
|
|
|
|
|
* description: 文件描述
|
|
|
|
|
|
* example: "装车过程视频"
|
|
|
|
|
|
* responses:
|
|
|
|
|
|
* 200:
|
|
|
|
|
|
* description: 上传成功
|
|
|
|
|
|
*/
|
|
|
|
|
|
router.post('/upload', upload.single('file'), async (req, res) => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const { type, businessId, description } = req.body;
|
|
|
|
|
|
|
|
|
|
|
|
if (!req.file) {
|
|
|
|
|
|
return res.apiError('请选择要上传的文件', 400);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 这里应该集成到云存储(如阿里云OSS、腾讯云COS)
|
|
|
|
|
|
// 实际项目中应该将文件上传到云存储并返回云存储URL
|
|
|
|
|
|
|
|
|
|
|
|
const fileInfo = {
|
|
|
|
|
|
fileId: `file_${uuidv4()}`,
|
|
|
|
|
|
url: `/uploads/${req.file.filename}`,
|
|
|
|
|
|
thumbnail: `/uploads/thumbnails/${req.file.filename}.jpg`,
|
|
|
|
|
|
size: req.file.size,
|
|
|
|
|
|
mimeType: req.file.mimetype,
|
|
|
|
|
|
originalName: req.file.originalname,
|
|
|
|
|
|
type,
|
|
|
|
|
|
businessId,
|
|
|
|
|
|
description
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 保存文件信息到数据库
|
|
|
|
|
|
// await File.create(fileInfo);
|
|
|
|
|
|
|
|
|
|
|
|
res.apiSuccess(fileInfo, '上传成功');
|
|
|
|
|
|
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('File upload error:', error);
|
|
|
|
|
|
res.apiError(error.message || '文件上传失败');
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
module.exports = router;
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 4.6 Express.js 支付路由示例
|
|
|
|
|
|
```javascript
|
|
|
|
|
|
// routes/payments.js - 支付路由
|
|
|
|
|
|
const express = require('express');
|
|
|
|
|
|
const { Payment, Order } = require('../models');
|
|
|
|
|
|
const router = express.Router();
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* @swagger
|
|
|
|
|
|
* /api/payments:
|
|
|
|
|
|
* post:
|
|
|
|
|
|
* summary: 创建支付订单
|
|
|
|
|
|
* tags: [支付管理]
|
|
|
|
|
|
* security:
|
|
|
|
|
|
* - BearerAuth: []
|
|
|
|
|
|
* requestBody:
|
|
|
|
|
|
* required: true
|
|
|
|
|
|
* content:
|
|
|
|
|
|
* application/json:
|
|
|
|
|
|
* schema:
|
|
|
|
|
|
* type: object
|
|
|
|
|
|
* required:
|
|
|
|
|
|
* - orderId
|
|
|
|
|
|
* - amount
|
|
|
|
|
|
* - paymentType
|
|
|
|
|
|
* properties:
|
|
|
|
|
|
* orderId:
|
|
|
|
|
|
* type: integer
|
|
|
|
|
|
* description: 订单ID
|
|
|
|
|
|
* example: 123
|
|
|
|
|
|
* amount:
|
|
|
|
|
|
* type: number
|
|
|
|
|
|
* format: float
|
|
|
|
|
|
* description: 支付金额(元)
|
|
|
|
|
|
* example: 355000
|
|
|
|
|
|
* paymentType:
|
|
|
|
|
|
* type: string
|
|
|
|
|
|
* enum: [wechat, alipay, bank]
|
|
|
|
|
|
* description: 支付类型
|
|
|
|
|
|
* example: "wechat"
|
|
|
|
|
|
* paymentMethod:
|
|
|
|
|
|
* type: string
|
|
|
|
|
|
* enum: [mini_program, app, web]
|
|
|
|
|
|
* description: 支付方式
|
|
|
|
|
|
* example: "mini_program"
|
|
|
|
|
|
* responses:
|
|
|
|
|
|
* 200:
|
|
|
|
|
|
* description: 创建成功
|
|
|
|
|
|
*/
|
|
|
|
|
|
router.post('/', async (req, res) => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const { orderId, amount, paymentType, paymentMethod = 'mini_program' } = req.body;
|
|
|
|
|
|
|
|
|
|
|
|
// 验证订单
|
|
|
|
|
|
const order = await Order.findByPk(orderId);
|
|
|
|
|
|
if (!order) {
|
|
|
|
|
|
return res.apiError('订单不存在', 404);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 验证支付权限
|
|
|
|
|
|
if (order.buyer_id !== req.user.id) {
|
|
|
|
|
|
return res.apiError('无权限支付此订单', 403);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 创建支付订单
|
|
|
|
|
|
const payment = await Payment.create({
|
|
|
|
|
|
order_id: orderId,
|
|
|
|
|
|
user_id: req.user.id,
|
|
|
|
|
|
amount,
|
|
|
|
|
|
payment_type: paymentType,
|
|
|
|
|
|
payment_method: paymentMethod,
|
|
|
|
|
|
status: 'pending',
|
|
|
|
|
|
payment_no: `pay_${Date.now()}${Math.random().toString(36).substr(2, 9)}`
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 调用第三方支付接口(微信支付、支付宝等)
|
|
|
|
|
|
// const paymentData = await callPaymentGateway(paymentType, paymentMethod, amount, payment.payment_no);
|
|
|
|
|
|
|
|
|
|
|
|
res.apiSuccess({
|
|
|
|
|
|
paymentId: payment.id,
|
|
|
|
|
|
paymentNo: payment.payment_no,
|
|
|
|
|
|
amount: payment.amount,
|
|
|
|
|
|
// ...paymentData // 第三方支付返回的数据
|
|
|
|
|
|
}, '支付订单创建成功');
|
|
|
|
|
|
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('Create payment error:', error);
|
|
|
|
|
|
res.apiError('创建支付订单失败');
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* @swagger
|
|
|
|
|
|
* /api/payments/{id}/callback:
|
|
|
|
|
|
* post:
|
|
|
|
|
|
* summary: 支付回调接口
|
|
|
|
|
|
* tags: [支付管理]
|
|
|
|
|
|
* parameters:
|
|
|
|
|
|
* - in: path
|
|
|
|
|
|
* name: id
|
|
|
|
|
|
* required: true
|
|
|
|
|
|
* schema:
|
|
|
|
|
|
* type: integer
|
|
|
|
|
|
* description: 支付订单ID
|
|
|
|
|
|
* requestBody:
|
|
|
|
|
|
* required: true
|
|
|
|
|
|
* content:
|
|
|
|
|
|
* application/json:
|
|
|
|
|
|
* schema:
|
|
|
|
|
|
* type: object
|
|
|
|
|
|
* required:
|
|
|
|
|
|
* - paymentId
|
|
|
|
|
|
* - status
|
|
|
|
|
|
* properties:
|
|
|
|
|
|
* paymentId:
|
|
|
|
|
|
* type: string
|
|
|
|
|
|
* description: 第三方支付ID
|
|
|
|
|
|
* example: "pay_123456"
|
|
|
|
|
|
* status:
|
|
|
|
|
|
* type: string
|
|
|
|
|
|
* enum: [success, failed, canceled]
|
|
|
|
|
|
* description: 支付状态
|
|
|
|
|
|
* example: "success"
|
|
|
|
|
|
* paidAmount:
|
|
|
|
|
|
* type: number
|
|
|
|
|
|
* format: float
|
|
|
|
|
|
* description: 实际支付金额
|
|
|
|
|
|
* example: 355000
|
|
|
|
|
|
* paidTime:
|
|
|
|
|
|
* type: string
|
|
|
|
|
|
* format: date-time
|
|
|
|
|
|
* description: 支付时间
|
|
|
|
|
|
* example: "2024-01-25 15:30:00"
|
|
|
|
|
|
* responses:
|
|
|
|
|
|
* 200:
|
|
|
|
|
|
* description: 回调处理成功
|
|
|
|
|
|
*/
|
|
|
|
|
|
router.post('/:id/callback', async (req, res) => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const { id } = req.params;
|
|
|
|
|
|
const { paymentId, status, paidAmount, paidTime } = req.body;
|
|
|
|
|
|
|
|
|
|
|
|
const payment = await Payment.findByPk(id);
|
|
|
|
|
|
if (!payment) {
|
|
|
|
|
|
return res.apiError('支付订单不存在', 404);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 更新支付状态
|
|
|
|
|
|
await payment.update({
|
|
|
|
|
|
third_party_id: paymentId,
|
|
|
|
|
|
status: status === 'success' ? 'paid' : 'failed',
|
|
|
|
|
|
paid_amount: paidAmount,
|
|
|
|
|
|
paid_time: paidTime ? new Date(paidTime) : new Date()
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 如果支付成功,更新订单状态
|
|
|
|
|
|
if (status === 'success') {
|
|
|
|
|
|
const order = await Order.findByPk(payment.order_id);
|
|
|
|
|
|
if (order) {
|
|
|
|
|
|
await order.update({ status: 'paid' });
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
res.apiSuccess(null, '支付回调处理成功');
|
|
|
|
|
|
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('Payment callback error:', error);
|
|
|
|
|
|
res.apiError('支付回调处理失败');
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* @swagger
|
|
|
|
|
|
* /api/payments/{id}/status:
|
|
|
|
|
|
* get:
|
|
|
|
|
|
* summary: 查询支付状态
|
|
|
|
|
|
* tags: [支付管理]
|
|
|
|
|
|
* security:
|
|
|
|
|
|
* - BearerAuth: []
|
|
|
|
|
|
* parameters:
|
|
|
|
|
|
* - in: path
|
|
|
|
|
|
* name: id
|
|
|
|
|
|
* required: true
|
|
|
|
|
|
* schema:
|
|
|
|
|
|
* type: integer
|
|
|
|
|
|
* description: 支付订单ID
|
|
|
|
|
|
* responses:
|
|
|
|
|
|
* 200:
|
|
|
|
|
|
* description: 查询成功
|
|
|
|
|
|
*/
|
|
|
|
|
|
router.get('/:id/status', async (req, res) => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const { id } = req.params;
|
|
|
|
|
|
|
|
|
|
|
|
const payment = await Payment.findByPk(id);
|
|
|
|
|
|
if (!payment) {
|
|
|
|
|
|
return res.apiError('支付订单不存在', 404);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 验证查询权限
|
|
|
|
|
|
if (payment.user_id !== req.user.id) {
|
|
|
|
|
|
return res.apiError('无权限查询此支付订单', 403);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
res.apiSuccess({
|
|
|
|
|
|
paymentId: payment.id,
|
|
|
|
|
|
paymentNo: payment.payment_no,
|
|
|
|
|
|
amount: payment.amount,
|
|
|
|
|
|
status: payment.status,
|
|
|
|
|
|
paidAmount: payment.paid_amount,
|
|
|
|
|
|
paidTime: payment.paid_time,
|
|
|
|
|
|
createdAt: payment.created_at
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('Get payment status error:', error);
|
|
|
|
|
|
res.apiError('查询支付状态失败');
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
module.exports = router;
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
## 5. 统一交互设计与开发规范
|
|
|
|
|
|
|
|
|
|
|
|
### 5.1 Uni-app小程序开发规范
|
|
|
|
|
|
```javascript
|
|
|
|
|
|
// 统一页面结构规范
|
|
|
|
|
|
{
|
|
|
|
|
|
"pages": [
|
|
|
|
|
|
{
|
|
|
|
|
|
"path": "pages/index/index",
|
|
|
|
|
|
"style": {
|
|
|
|
|
|
"navigationBarTitleText": "首页",
|
|
|
|
|
|
"enablePullDownRefresh": true,
|
|
|
|
|
|
"backgroundColor": "#f5f5f5"
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
],
|
|
|
|
|
|
"globalStyle": {
|
|
|
|
|
|
"navigationBarTextStyle": "black",
|
|
|
|
|
|
"navigationBarTitleText": "uni-app",
|
|
|
|
|
|
"navigationBarBackgroundColor": "#FFFFFF",
|
|
|
|
|
|
"backgroundColor": "#FFFFFF",
|
|
|
|
|
|
"app-plus": {
|
|
|
|
|
|
"background": "#efeff4"
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
"tabBar": {
|
|
|
|
|
|
"color": "#7A7E83",
|
|
|
|
|
|
"selectedColor": "#007AFF",
|
|
|
|
|
|
"borderStyle": "black",
|
|
|
|
|
|
"backgroundColor": "#FFFFFF",
|
|
|
|
|
|
"list": []
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 统一组件命名规范
|
|
|
|
|
|
- 组件目录:/components/
|
|
|
|
|
|
- 页面目录:/pages/
|
|
|
|
|
|
- 工具函数:/utils/
|
|
|
|
|
|
- API接口:/api/
|
|
|
|
|
|
- 状态管理:/store/
|
|
|
|
|
|
- 类型定义:/types/
|
|
|
|
|
|
|
|
|
|
|
|
// 统一状态管理(Vuex)
|
|
|
|
|
|
const store = new Vuex.Store({
|
|
|
|
|
|
state: {
|
|
|
|
|
|
userInfo: null,
|
|
|
|
|
|
token: null,
|
|
|
|
|
|
currentOrder: null
|
|
|
|
|
|
},
|
|
|
|
|
|
mutations: {
|
|
|
|
|
|
SET_USER_INFO(state, userInfo) {
|
|
|
|
|
|
state.userInfo = userInfo;
|
|
|
|
|
|
},
|
|
|
|
|
|
SET_TOKEN(state, token) {
|
|
|
|
|
|
state.token = token;
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
actions: {
|
|
|
|
|
|
async login({ commit }, { phone, code }) {
|
|
|
|
|
|
const response = await uni.request({
|
|
|
|
|
|
url: '/api/auth/mini-program/login',
|
|
|
|
|
|
method: 'POST',
|
|
|
|
|
|
data: { phone, code, miniProgramType: 'client' }
|
|
|
|
|
|
});
|
|
|
|
|
|
commit('SET_USER_INFO', response.data.data.userInfo);
|
|
|
|
|
|
commit('SET_TOKEN', response.data.data.token);
|
|
|
|
|
|
uni.setStorageSync('token', response.data.data.token);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 5.2 统一UI组件库规范
|
|
|
|
|
|
```vue
|
|
|
|
|
|
<!-- 统一按钮组件 -->
|
|
|
|
|
|
<template>
|
|
|
|
|
|
<button
|
|
|
|
|
|
:class="['btn', type, size, { disabled: disabled }]"
|
|
|
|
|
|
:disabled="disabled"
|
|
|
|
|
|
@click="handleClick"
|
|
|
|
|
|
>
|
|
|
|
|
|
<slot></slot>
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<script>
|
|
|
|
|
|
export default {
|
|
|
|
|
|
name: 'UniButton',
|
|
|
|
|
|
props: {
|
|
|
|
|
|
type: {
|
|
|
|
|
|
type: String,
|
|
|
|
|
|
default: 'default', // primary/success/warning/danger
|
|
|
|
|
|
validator: value => ['default', 'primary', 'success', 'warning', 'danger'].includes(value)
|
|
|
|
|
|
},
|
|
|
|
|
|
size: {
|
|
|
|
|
|
type: String,
|
|
|
|
|
|
default: 'medium', // small/medium/large
|
|
|
|
|
|
validator: value => ['small', 'medium', 'large'].includes(value)
|
|
|
|
|
|
},
|
|
|
|
|
|
disabled: Boolean
|
|
|
|
|
|
},
|
|
|
|
|
|
methods: {
|
|
|
|
|
|
handleClick(event) {
|
|
|
|
|
|
if (!this.disabled) {
|
|
|
|
|
|
this.$emit('click', event);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
<style scoped>
|
|
|
|
|
|
.btn {
|
|
|
|
|
|
border: none;
|
|
|
|
|
|
border-radius: 8rpx;
|
|
|
|
|
|
padding: 16rpx 32rpx;
|
|
|
|
|
|
font-size: 28rpx;
|
|
|
|
|
|
transition: all 0.3s;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.btn.primary {
|
|
|
|
|
|
background-color: #007AFF;
|
|
|
|
|
|
color: #FFFFFF;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.btn.small {
|
|
|
|
|
|
padding: 12rpx 24rpx;
|
|
|
|
|
|
font-size: 24rpx;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.btn.disabled {
|
|
|
|
|
|
opacity: 0.6;
|
|
|
|
|
|
cursor: not-allowed;
|
|
|
|
|
|
}
|
|
|
|
|
|
</style>
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 5.3 统一交互体验规范
|
|
|
|
|
|
```javascript
|
|
|
|
|
|
// 统一加载状态管理
|
|
|
|
|
|
const useLoading = () => {
|
|
|
|
|
|
const isLoading = ref(false);
|
|
|
|
|
|
|
|
|
|
|
|
const showLoading = (title = '加载中') => {
|
|
|
|
|
|
isLoading.value = true;
|
|
|
|
|
|
uni.showLoading({ title, mask: true });
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const hideLoading = () => {
|
|
|
|
|
|
isLoading.value = false;
|
|
|
|
|
|
uni.hideLoading();
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
return { isLoading, showLoading, hideLoading };
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 统一错误处理
|
|
|
|
|
|
const handleApiError = (error) => {
|
|
|
|
|
|
console.error('API Error:', error);
|
|
|
|
|
|
|
|
|
|
|
|
if (error.response?.status === 401) {
|
|
|
|
|
|
// token过期,跳转到登录页
|
|
|
|
|
|
uni.showModal({
|
|
|
|
|
|
title: '提示',
|
|
|
|
|
|
content: '登录已过期,请重新登录',
|
|
|
|
|
|
showCancel: false,
|
|
|
|
|
|
success: () => {
|
|
|
|
|
|
uni.navigateTo({ url: '/pages/login/login' });
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
} else if (error.response?.status === 403) {
|
|
|
|
|
|
uni.showToast({ title: '无权限访问', icon: 'none' });
|
|
|
|
|
|
} else {
|
|
|
|
|
|
uni.showToast({
|
|
|
|
|
|
title: error.response?.data?.message || '网络异常,请重试',
|
|
|
|
|
|
icon: 'none'
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 统一页面跳转
|
|
|
|
|
|
const navigateTo = (url, params = {}) => {
|
|
|
|
|
|
if (params) {
|
|
|
|
|
|
const query = Object.keys(params)
|
|
|
|
|
|
.map(key => `${encodeURIComponent(key)}=${encodeURIComponent(params[key])}`)
|
|
|
|
|
|
.join('&');
|
|
|
|
|
|
url += `?${query}`;
|
|
|
|
|
|
}
|
|
|
|
|
|
uni.navigateTo({ url });
|
|
|
|
|
|
};
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
## 6. 安全设计
|
|
|
|
|
|
|
|
|
|
|
|
### 6.1 统一认证授权
|
|
|
|
|
|
- JWT Token认证 + Refresh Token机制
|
|
|
|
|
|
- 基于角色的访问控制(RBAC)
|
|
|
|
|
|
- API访问频率限制和防刷机制
|
|
|
|
|
|
- 多因素认证支持(短信验证码)
|
|
|
|
|
|
|
|
|
|
|
|
### 6.2 数据安全与隐私保护
|
|
|
|
|
|
- 敏感数据加密存储(AES-256)
|
|
|
|
|
|
- HTTPS传输加密(TLS 1.3)
|
|
|
|
|
|
- 视频文件访问权限控制和签名URL
|
|
|
|
|
|
- 用户隐私数据脱敏处理
|
|
|
|
|
|
- GDPR/个人信息保护法合规
|
|
|
|
|
|
|
|
|
|
|
|
### 6.3 业务安全防护
|
|
|
|
|
|
- 订单状态机验证和防篡改
|
|
|
|
|
|
- 支付金额校验和防重放攻击
|
|
|
|
|
|
- 操作日志审计和追溯
|
|
|
|
|
|
- 敏感操作二次确认机制
|
|
|
|
|
|
- 业务数据完整性校验
|
|
|
|
|
|
|
|
|
|
|
|
## 6. 性能优化
|
|
|
|
|
|
|
|
|
|
|
|
### 6.1 数据库优化
|
|
|
|
|
|
- 读写分离
|
|
|
|
|
|
- 索引优化
|
|
|
|
|
|
- 分表分库策略
|
|
|
|
|
|
|
|
|
|
|
|
### 6.2 缓存策略
|
|
|
|
|
|
- Redis缓存热点数据
|
|
|
|
|
|
- 本地缓存减少IO
|
|
|
|
|
|
- 缓存失效策略
|
|
|
|
|
|
|
|
|
|
|
|
### 6.3 文件处理
|
|
|
|
|
|
- 视频文件分片上传
|
|
|
|
|
|
- CDN加速访问
|
|
|
|
|
|
- 压缩和转码处理
|
|
|
|
|
|
|
|
|
|
|
|
## 7. 小程序矩阵详细实现
|
|
|
|
|
|
|
|
|
|
|
|
### 7.1 客户端小程序 (Client Mini-Program)
|
|
|
|
|
|
```javascript
|
|
|
|
|
|
// 主要功能模块
|
|
|
|
|
|
const clientModules = {
|
|
|
|
|
|
// 用户认证
|
|
|
|
|
|
auth: {
|
|
|
|
|
|
login: '用户登录注册',
|
|
|
|
|
|
profile: '个人信息管理',
|
|
|
|
|
|
certification: '企业认证'
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// 采购管理
|
|
|
|
|
|
procurement: {
|
|
|
|
|
|
orderCreate: '创建采购订单',
|
|
|
|
|
|
orderList: '订单列表查看',
|
|
|
|
|
|
orderDetail: '订单详情',
|
|
|
|
|
|
orderTracking: '订单跟踪'
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// 支付结算
|
|
|
|
|
|
payment: {
|
|
|
|
|
|
prepayment: '预付款支付',
|
|
|
|
|
|
balancePayment: '尾款支付',
|
|
|
|
|
|
paymentRecords: '支付记录'
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// 消息通知
|
|
|
|
|
|
notification: {
|
|
|
|
|
|
systemMsg: '系统消息',
|
|
|
|
|
|
orderUpdates: '订单状态更新',
|
|
|
|
|
|
paymentReminders: '付款提醒'
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 技术特色
|
|
|
|
|
|
- 基于uni-app的跨端开发
|
|
|
|
|
|
- 集成微信支付SDK
|
|
|
|
|
|
- 实时订单状态推送
|
|
|
|
|
|
- 地图集成和位置服务
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 7.2 供应商小程序 (Supplier Mini-Program)
|
|
|
|
|
|
```javascript
|
|
|
|
|
|
// 主要功能模块
|
|
|
|
|
|
const supplierModules = {
|
|
|
|
|
|
// 订单管理
|
|
|
|
|
|
order: {
|
|
|
|
|
|
orderReceive: '接单管理',
|
|
|
|
|
|
orderProcessing: '订单处理',
|
|
|
|
|
|
orderStatus: '订单状态更新'
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// 牛只管理
|
|
|
|
|
|
cattle: {
|
|
|
|
|
|
inventory: '牛只库存',
|
|
|
|
|
|
qualityCheck: '质量检验',
|
|
|
|
|
|
certificateUpload: '证件上传'
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// 装车管理
|
|
|
|
|
|
loading: {
|
|
|
|
|
|
loadingPlan: '装车计划',
|
|
|
|
|
|
loadingProcess: '装车过程记录',
|
|
|
|
|
|
videoRecording: '装车视频录制'
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// 结算管理
|
|
|
|
|
|
settlement: {
|
|
|
|
|
|
settlementQuery: '结算查询',
|
|
|
|
|
|
invoiceManagement: '发票管理',
|
|
|
|
|
|
paymentRecords: '收款记录'
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 技术特色
|
|
|
|
|
|
- 视频录制和上传功能
|
|
|
|
|
|
- 证件扫描和OCR识别
|
|
|
|
|
|
- 库存管理系统集成
|
|
|
|
|
|
- 财务结算对接
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 7.3 司机小程序 (Driver Mini-Program)
|
|
|
|
|
|
```javascript
|
|
|
|
|
|
// 主要功能模块
|
|
|
|
|
|
const driverModules = {
|
|
|
|
|
|
// 运输任务
|
|
|
|
|
|
transport: {
|
|
|
|
|
|
taskReceive: '任务接收',
|
|
|
|
|
|
taskList: '任务列表',
|
|
|
|
|
|
taskDetail: '任务详情'
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// 位置跟踪
|
|
|
|
|
|
tracking: {
|
|
|
|
|
|
autoTracking: '自动位置上报',
|
|
|
|
|
|
manualReport: '手动状态报告',
|
|
|
|
|
|
routePlanning: '路线规划'
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// 牛只监控
|
|
|
|
|
|
monitoring: {
|
|
|
|
|
|
cattleStatus: '牛只状态监测',
|
|
|
|
|
|
environment: '环境参数监测',
|
|
|
|
|
|
emergency: '紧急情况处理'
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// 单据管理
|
|
|
|
|
|
documents: {
|
|
|
|
|
|
deliveryNote: '交货单管理',
|
|
|
|
|
|
receiptConfirmation: '回执确认',
|
|
|
|
|
|
expenseReporting: '费用报销'
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 技术特色
|
|
|
|
|
|
- 后台位置持续跟踪
|
|
|
|
|
|
- 离线数据同步机制
|
|
|
|
|
|
- 紧急求助功能
|
|
|
|
|
|
- 多媒体数据采集
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 7.4 内部员工小程序 (Staff Mini-Program)
|
|
|
|
|
|
```javascript
|
|
|
|
|
|
// 主要功能模块
|
|
|
|
|
|
const staffModules = {
|
|
|
|
|
|
// 运营监控
|
|
|
|
|
|
operation: {
|
|
|
|
|
|
dashboard: '运营看板',
|
|
|
|
|
|
orderMonitor: '订单监控',
|
|
|
|
|
|
exceptionHandling: '异常处理'
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// 客户服务
|
|
|
|
|
|
customerService: {
|
|
|
|
|
|
customerInfo: '客户信息',
|
|
|
|
|
|
serviceRecords: '服务记录',
|
|
|
|
|
|
complaintHandling: '投诉处理'
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// 数据统计
|
|
|
|
|
|
statistics: {
|
|
|
|
|
|
businessData: '业务数据',
|
|
|
|
|
|
financialReports: '财务报表',
|
|
|
|
|
|
performanceAnalysis: '绩效分析'
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// 系统管理
|
|
|
|
|
|
system: {
|
|
|
|
|
|
userManagement: '用户管理',
|
|
|
|
|
|
rolePermission: '权限管理',
|
|
|
|
|
|
systemSettings: '系统设置'
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 技术特色
|
|
|
|
|
|
- 管理后台功能移动化
|
|
|
|
|
|
- 实时数据可视化
|
|
|
|
|
|
- 移动审批流程
|
|
|
|
|
|
- 多维度数据分析
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
## 8. 监控告警
|
|
|
|
|
|
|
|
|
|
|
|
### 8.1 系统监控
|
|
|
|
|
|
- 应用性能监控(APM):监控各微服务性能指标
|
|
|
|
|
|
- 数据库监控:MySQL和Redis性能监控
|
|
|
|
|
|
- 服务器资源监控:CPU、内存、磁盘、网络
|
|
|
|
|
|
- 小程序性能监控:加载时间、渲染性能、API响应
|
|
|
|
|
|
|
|
|
|
|
|
### 8.2 业务监控
|
|
|
|
|
|
- 订单流程监控:各状态订单数量和耗时
|
|
|
|
|
|
- 运输异常检测:偏离路线、长时间停留预警
|
|
|
|
|
|
- 支付成功率监控:各渠道支付成功率和失败原因
|
|
|
|
|
|
- 用户行为分析:各小程序用户活跃度和功能使用情况
|
|
|
|
|
|
|
|
|
|
|
|
### 8.3 日志管理
|
|
|
|
|
|
- 操作日志记录:用户关键操作审计日志
|
|
|
|
|
|
- 错误日志收集:系统异常和错误信息收集
|
|
|
|
|
|
- 日志分析和查询:ELK日志分析平台
|
|
|
|
|
|
- 实时日志追踪:分布式请求追踪
|
|
|
|
|
|
|
|
|
|
|
|
## 9. 部署方案
|
|
|
|
|
|
|
|
|
|
|
|
### 9.1 开发环境
|
|
|
|
|
|
```yaml
|
|
|
|
|
|
# docker-compose-dev.yml
|
|
|
|
|
|
version: '3.8'
|
|
|
|
|
|
services:
|
|
|
|
|
|
# 后端服务
|
|
|
|
|
|
user-service:
|
|
|
|
|
|
build: ./services/user-service
|
|
|
|
|
|
ports:
|
|
|
|
|
|
- "3001:3000"
|
|
|
|
|
|
environment:
|
|
|
|
|
|
- NODE_ENV=development
|
|
|
|
|
|
- DB_HOST=mysql
|
|
|
|
|
|
- REDIS_HOST=redis
|
|
|
|
|
|
|
|
|
|
|
|
# 数据库
|
|
|
|
|
|
mysql:
|
|
|
|
|
|
image: mysql:8.0
|
|
|
|
|
|
environment:
|
|
|
|
|
|
- MYSQL_ROOT_PASSWORD=root
|
|
|
|
|
|
- MYSQL_DATABASE=niu_mall
|
|
|
|
|
|
ports:
|
|
|
|
|
|
- "3306:3306"
|
|
|
|
|
|
|
|
|
|
|
|
# 缓存
|
|
|
|
|
|
redis:
|
|
|
|
|
|
image: redis:6.2
|
|
|
|
|
|
ports:
|
|
|
|
|
|
- "6379:6379"
|
|
|
|
|
|
|
|
|
|
|
|
# 小程序开发环境
|
|
|
|
|
|
mini-program-dev:
|
|
|
|
|
|
image: node:16
|
|
|
|
|
|
working_dir: /app
|
|
|
|
|
|
volumes:
|
|
|
|
|
|
- ./mini_program:/app
|
|
|
|
|
|
ports:
|
|
|
|
|
|
- "8080:8080"
|
|
|
|
|
|
command: npm run dev:mp-weixin
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 9.2 生产环境
|
|
|
|
|
|
```yaml
|
|
|
|
|
|
# kubernetes部署配置
|
|
|
|
|
|
apiVersion: apps/v1
|
|
|
|
|
|
kind: Deployment
|
|
|
|
|
|
metadata:
|
|
|
|
|
|
name: niu-mall-api
|
|
|
|
|
|
labels:
|
|
|
|
|
|
app: niu-mall
|
|
|
|
|
|
spec:
|
|
|
|
|
|
replicas: 3
|
|
|
|
|
|
selector:
|
|
|
|
|
|
matchLabels:
|
|
|
|
|
|
app: niu-mall-api
|
|
|
|
|
|
template:
|
|
|
|
|
|
metadata:
|
|
|
|
|
|
labels:
|
|
|
|
|
|
app: niu-mall-api
|
|
|
|
|
|
spec:
|
|
|
|
|
|
containers:
|
|
|
|
|
|
- name: api-gateway
|
|
|
|
|
|
image: niu-mall/api-gateway:latest
|
|
|
|
|
|
ports:
|
|
|
|
|
|
- containerPort: 3000
|
|
|
|
|
|
env:
|
|
|
|
|
|
- name: NODE_ENV
|
|
|
|
|
|
value: production
|
|
|
|
|
|
resources:
|
|
|
|
|
|
requests:
|
|
|
|
|
|
memory: "512Mi"
|
|
|
|
|
|
cpu: "250m"
|
|
|
|
|
|
limits:
|
|
|
|
|
|
memory: "1Gi"
|
|
|
|
|
|
cpu: "500m"
|
|
|
|
|
|
|
|
|
|
|
|
# 小程序生产部署
|
|
|
|
|
|
- 微信小程序平台审核发布
|
|
|
|
|
|
- 阿里云OSS静态资源托管
|
|
|
|
|
|
- CDN加速和域名配置
|
|
|
|
|
|
- SSL证书和HTTPS强制
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 9.3 备份恢复策略
|
|
|
|
|
|
```yaml
|
|
|
|
|
|
# 数据库备份
|
|
|
|
|
|
backup:
|
|
|
|
|
|
schedule: "0 2 * * *" # 每天凌晨2点
|
|
|
|
|
|
retention: 30 # 保留30天
|
|
|
|
|
|
storage:
|
|
|
|
|
|
type: oss # 阿里云OSS存储
|
|
|
|
|
|
bucket: niu-mall-backup
|
|
|
|
|
|
|
|
|
|
|
|
# 文件备份
|
|
|
|
|
|
file_backup:
|
|
|
|
|
|
enabled: true
|
|
|
|
|
|
schedule: "0 3 * * *" # 每天凌晨3点
|
|
|
|
|
|
include:
|
|
|
|
|
|
- /data/uploads # 用户上传文件
|
|
|
|
|
|
- /data/logs # 日志文件
|
|
|
|
|
|
exclude:
|
|
|
|
|
|
- /data/temp # 临时文件
|
|
|
|
|
|
|
|
|
|
|
|
# 灾难恢复
|
|
|
|
|
|
recovery:
|
|
|
|
|
|
rto: "4h" # 恢复时间目标4小时
|
|
|
|
|
|
rpo: "1h" # 恢复点目标1小时
|
|
|
|
|
|
procedures:
|
|
|
|
|
|
- database_restore
|
|
|
|
|
|
- service_restart
|
|
|
|
|
|
- data_validation
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
## 10. 开发规范
|
|
|
|
|
|
|
|
|
|
|
|
### 10.1 代码规范
|
|
|
|
|
|
```javascript
|
|
|
|
|
|
// ESLint配置
|
|
|
|
|
|
module.exports = {
|
|
|
|
|
|
env: {
|
|
|
|
|
|
node: true,
|
|
|
|
|
|
browser: true,
|
|
|
|
|
|
es2021: true
|
|
|
|
|
|
},
|
|
|
|
|
|
extends: [
|
|
|
|
|
|
'eslint:recommended',
|
|
|
|
|
|
'@vue/typescript/recommended',
|
|
|
|
|
|
'prettier'
|
|
|
|
|
|
],
|
|
|
|
|
|
rules: {
|
|
|
|
|
|
'@typescript-eslint/no-explicit-any': 'warn',
|
|
|
|
|
|
'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
|
|
|
|
|
|
'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off'
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// Prettier配置
|
|
|
|
|
|
{
|
|
|
|
|
|
"semi": true,
|
|
|
|
|
|
"trailingComma": "es5",
|
|
|
|
|
|
"singleQuote": true,
|
|
|
|
|
|
"printWidth": 80,
|
|
|
|
|
|
"tabWidth": 2,
|
|
|
|
|
|
"useTabs": false
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 10.2 Git工作流
|
|
|
|
|
|
```bash
|
|
|
|
|
|
# 功能开发流程
|
|
|
|
|
|
git checkout -b feature/order-management
|
|
|
|
|
|
git add .
|
|
|
|
|
|
git commit -m "feat: 添加订单管理功能"
|
|
|
|
|
|
git push origin feature/order-management
|
|
|
|
|
|
|
|
|
|
|
|
# 代码审查
|
|
|
|
|
|
- 至少需要2个reviewer批准
|
|
|
|
|
|
- 所有测试必须通过
|
|
|
|
|
|
- 代码覆盖率要求85%以上
|
|
|
|
|
|
|
|
|
|
|
|
# 发布流程
|
|
|
|
|
|
- 开发 → 测试 → 预发布 → 生产
|
|
|
|
|
|
- 蓝绿部署或金丝雀发布
|
|
|
|
|
|
- 自动化CI/CD流水线
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 10.3 测试规范
|
|
|
|
|
|
```javascript
|
|
|
|
|
|
// 单元测试示例
|
|
|
|
|
|
describe('OrderService', () => {
|
|
|
|
|
|
it('should create order successfully', async () => {
|
|
|
|
|
|
const orderData = {
|
|
|
|
|
|
breedType: 'simmental',
|
|
|
|
|
|
minWeight: 500,
|
|
|
|
|
|
maxWeight: 600,
|
|
|
|
|
|
totalCount: 100,
|
|
|
|
|
|
unitPrice: 35.5
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const result = await orderService.createOrder(orderData);
|
|
|
|
|
|
expect(result).toHaveProperty('id');
|
|
|
|
|
|
expect(result.status).toBe('pending');
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
it('should validate order data', async () => {
|
|
|
|
|
|
const invalidData = { totalCount: -1 };
|
|
|
|
|
|
await expect(orderService.createOrder(invalidData))
|
|
|
|
|
|
.rejects
|
|
|
|
|
|
.toThrow('数量必须大于0');
|
|
|
|
|
|
});
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 测试覆盖率要求
|
|
|
|
|
|
- 单元测试: ≥80%
|
|
|
|
|
|
- 集成测试: ≥70%
|
|
|
|
|
|
- E2E测试: 核心业务流程100%覆盖
|
|
|
|
|
|
```
|
|
|
|
|
|
- 灾难恢复预案
|
|
|
|
|
|
|
|
|
|
|
|
## 9. 开发规范
|
|
|
|
|
|
|
|
|
|
|
|
### 9.1 代码规范
|
|
|
|
|
|
- TypeScript严格模式
|
|
|
|
|
|
- ESLint代码检查
|
|
|
|
|
|
- Prettier代码格式化
|
|
|
|
|
|
|
|
|
|
|
|
### 9.2 Git规范
|
|
|
|
|
|
- 分支管理策略
|
|
|
|
|
|
- Commit message规范
|
|
|
|
|
|
- Code Review流程
|
|
|
|
|
|
|
|
|
|
|
|
### 9.3 文档规范
|
|
|
|
|
|
- API文档自动化
|
|
|
|
|
|
- 数据库文档维护
|
|
|
|
|
|
- 部署文档更新
|