50 KiB
活牛采购智能数字化系统 - 技术实施方案
版本历史
| 版本 | 日期 | 作者 | 说明 |
|---|---|---|---|
| v1.0 | 2024-01-20 | 技术团队 | 初版技术方案 |
| v1.1 | 2024-05-15 | 系统架构师 | 更新与系统架构和详细设计文档保持一致 |
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 | 跨平台小程序矩阵 |
| 后端 | Node.js + Express.js | 微服务架构 |
| 数据库 | 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 小程序服务接口
// 统一小程序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 模型定义
// 统一用户模型
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 认证路由示例
// 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 订单路由示例
// 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 运输跟踪路由示例
// 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 文件上传路由示例
// 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 支付路由示例
// 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小程序开发规范
// 统一页面结构规范
{
"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组件库规范
<!-- 统一按钮组件 -->
<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 统一交互体验规范
// 统一加载状态管理
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)
// 主要功能模块
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)
// 主要功能模块
const supplierModules = {
// 订单管理
order: {
orderReceive: '接单管理',
orderProcessing: '订单处理',
orderStatus: '订单状态更新'
},
// 牛只管理
cattle: {
inventory: '牛只库存',
qualityCheck: '质量检验',
certificateUpload: '证件上传'
},
// 装车管理
loading: {
loadingPlan: '装车计划',
loadingProcess: '装车过程记录',
videoRecording: '装车视频录制'
},
// 结算管理
settlement: {
settlementQuery: '结算查询',
invoiceManagement: '发票管理',
paymentRecords: '收款记录'
}
};
// 技术特色
- 视频录制和上传功能
- 证件扫描和OCR识别
- 库存管理系统集成
- 财务结算对接
7.3 司机小程序 (Driver Mini-Program)
// 主要功能模块
const driverModules = {
// 运输任务
transport: {
taskReceive: '任务接收',
taskList: '任务列表',
taskDetail: '任务详情'
},
// 位置跟踪
tracking: {
autoTracking: '自动位置上报',
manualReport: '手动状态报告',
routePlanning: '路线规划'
},
// 牛只监控
monitoring: {
cattleStatus: '牛只状态监测',
environment: '环境参数监测',
emergency: '紧急情况处理'
},
// 单据管理
documents: {
deliveryNote: '交货单管理',
receiptConfirmation: '回执确认',
expenseReporting: '费用报销'
}
};
// 技术特色
- 后台位置持续跟踪
- 离线数据同步机制
- 紧急求助功能
- 多媒体数据采集
7.4 内部员工小程序 (Staff Mini-Program)
// 主要功能模块
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 开发环境
# 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 生产环境
# 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 备份恢复策略
# 数据库备份
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 代码规范
// 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工作流
# 功能开发流程
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 测试规范
// 单元测试示例
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文档自动化
- 数据库文档维护
- 部署文档更新