1. 校服订购系统技术架构解析
作为一名经历过多个校园信息化项目的全栈开发者,我深知校服订购这类看似简单的业务系统背后隐藏的技术复杂性。本次项目采用Node.js+PHP+Vue的混合技术栈,既发挥了各自语言的优势,又解决了实际业务中的特殊需求。
1.1 为什么选择混合技术栈
Node.js作为API主力层,主要基于以下考虑:
- 高并发I/O处理能力(实测单机可达3000+ QPS)
- 与前端Vue.js同属JavaScript生态,减少上下文切换
- 中间层可灵活对接不同后端服务
保留PHP主要出于历史原因:
- 学校原有支付系统基于PHP开发
- 教务系统接口使用PHP SOAP协议
- 维护团队现有PHP技术储备
Vue.js 3.x的选择理由更直接:
- 组合式API更适合复杂业务组件开发
- 与Element Plus组件库完美契合
- 静态资源编译后CDN分发效率极高
1.2 架构拓扑设计
系统采用分层架构设计:
code复制[前端层] Vue.js + Vant UI
↓ HTTPS
[API网关] Node.js + Express
↓ RESTful
[业务层] PHP + ThinkPHP
↓
[数据层] MySQL + Redis
↓
[存储层] 阿里云OSS
这种架构在保证性能的同时,实现了以下关键特性:
- 前端完全解耦,可独立部署
- PHP业务变更不影响整体接口规范
- 敏感支付操作与传统业务逻辑隔离
2. 核心模块实现细节
2.1 用户认证系统设计
采用改良版JWT实现方案:
javascript复制// Node.js生成令牌示例
const jwt = require('jsonwebtoken');
function generateToken(user) {
return jwt.sign(
{
uid: user.id,
role: user.role,
class: user.class_id // 添加班级信息
},
process.env.JWT_SECRET,
{ expiresIn: '8h' } // 较传统方案缩短有效期
);
}
安全增强措施:
- 令牌加入黑名单机制(Redis存储)
- 敏感操作需二次验证
- 登录IP异常检测
2.2 校服SKU管理系统
商品数据模型设计要点:
sql复制CREATE TABLE `uniforms` (
`id` int NOT NULL AUTO_INCREMENT,
`name` varchar(100) NOT NULL COMMENT '款式名称',
`season` enum('spring','summer','autumn','winter') NOT NULL,
`gender` enum('unisex','male','female') DEFAULT 'unisex',
`base_price` decimal(10,2) unsigned NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB;
CREATE TABLE `sku` (
`id` char(12) NOT NULL COMMENT 'SKU编码',
`uniform_id` int NOT NULL,
`size` varchar(10) NOT NULL COMMENT '160, S, M等',
`color` varchar(20) NOT NULL COMMENT '藏青, 白色等',
`stock` int unsigned NOT NULL DEFAULT '0',
`image_url` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `idx_uniform` (`uniform_id`)
) ENGINE=InnoDB;
前端采用Vue虚拟滚动优化长列表:
vue复制<template>
<RecycleScroller
:items="filteredSkus"
:item-size="120"
key-field="id"
>
<template #default="{ item }">
<SkuCard :sku="item" @select="handleSelect"/>
</template>
</RecycleScroller>
</template>
3. 订单与支付系统实现
3.1 高并发库存控制
采用Redis+Lua实现原子操作:
lua复制-- inventory.lua
local key = KEYS[1]
local num = tonumber(ARGV[1])
local curr = tonumber(redis.call('GET', key))
if curr >= num then
redis.call('DECRBY', key, num)
return 1
else
return 0
end
Node.js调用示例:
javascript复制const reserveStock = async (sku, quantity) => {
const result = await redis.eval(
await fs.readFileSync('inventory.lua'),
1,
`stock:${sku}`,
quantity
);
return result === 1;
};
3.2 支付系统对接
PHP处理支付回调的健壮性方案:
php复制class PaymentCallback {
private $signKey;
public function __construct($key) {
$this->signKey = $key;
}
public function verify($data) {
$sign = $data['sign'];
unset($data['sign']);
ksort($data);
$str = http_build_query($data).$this->signKey;
return md5($str) === $sign;
}
public function process($orderNo, $amount) {
try {
DB::beginTransaction();
$order = Order::where('no', $orderNo)
->lockForUpdate()
->first();
if ($order->status !== 'pending') {
throw new Exception('订单状态异常');
}
if (bccomp($order->total, $amount, 2) !== 0) {
throw new Exception('金额不匹配');
}
$order->update([
'status' => 'paid',
'paid_at' => now()
]);
DB::commit();
return true;
} catch (Exception $e) {
DB::rollBack();
Log::error($e);
return false;
}
}
}
4. 性能优化实战记录
4.1 前端性能提升
-
组件级优化:
- 使用
<script setup>语法减少运行时开销 - 静态DOM节点标记
v-once - 列表项添加
key属性
- 使用
-
资源加载策略:
javascript复制// vite.config.js export default defineConfig({ build: { rollupOptions: { output: { manualChunks: { echarts: ['echarts'], element: ['element-plus'] } } } } });
4.2 后端缓存策略
多级缓存设计方案:
- 热点数据:Redis内存缓存(60s TTL)
- 静态数据:Node.js内存缓存(LRU算法)
- 数据库查询:MySQL查询缓存
javascript复制// 缓存装饰器实现
function cacheable(keyFn, ttl = 60) {
return (target, name, descriptor) => {
const original = descriptor.value;
descriptor.value = async function(...args) {
const key = keyFn(...args);
const cached = await redis.get(key);
if (cached) return JSON.parse(cached);
const result = await original.apply(this, args);
await redis.setex(key, ttl, JSON.stringify(result));
return result;
};
};
}
// 使用示例
class ProductService {
@cacheable((id) => `product:${id}`)
async getById(id) {
return Product.findByPk(id);
}
}
5. 异常处理与监控
5.1 错误收集系统
Node.js错误日志方案:
javascript复制// 错误中间件
app.use(async (ctx, next) => {
try {
await next();
} catch (err) {
const errorId = nanoid();
// 结构化日志
log.error({
id: errorId,
url: ctx.url,
method: ctx.method,
params: ctx.params,
body: ctx.request.body,
stack: err.stack,
timestamp: new Date()
});
// 告警通知
if (err.isSevere) {
await sendAlert({
title: `API Error [${errorId}]`,
content: err.message
});
}
ctx.status = err.status || 500;
ctx.body = {
error: 'Internal Error',
reference: errorId
};
}
});
5.2 事务补偿机制
订单超时处理方案:
javascript复制const agenda = new Agenda({ db: { address: config.mongoUrl } });
agenda.define('cancel-unpaid-order', async (job) => {
const orders = await Order.findAll({
where: {
status: 'pending',
createdAt: { [Op.lt]: new Date(Date.now() - 30*60*1000) }
}
});
for (const order of orders) {
await sequelize.transaction(async (t) => {
await order.update({ status: 'canceled' }, { transaction: t });
for (const item of order.items) {
await redis.incrby(`stock:${item.sku}`, item.quantity);
}
});
}
});
// 每5分钟执行
agenda.every('5 minutes', 'cancel-unpaid-order');
6. 部署与运维方案
6.1 Docker编排设计
docker-compose.prod.yml核心配置:
yaml复制services:
api:
build: ./node-server
ports:
- "3000:3000"
environment:
- NODE_ENV=production
- REDIS_URL=redis://redis:6379
depends_on:
- redis
- mysql
php:
image: php:8.1-fpm
volumes:
- ./php-app:/var/www/html
environment:
- DB_HOST=mysql
nginx:
image: nginx:alpine
ports:
- "80:80"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf
- ./vue-dist:/usr/share/nginx/html
mysql:
image: mysql:5.7
environment:
- MYSQL_ROOT_PASSWORD=${DB_ROOT_PASS}
- MYSQL_DATABASE=uniform
volumes:
- mysql-data:/var/lib/mysql
redis:
image: redis:6-alpine
command: redis-server --save 60 1 --loglevel warning
volumes:
- redis-data:/data
volumes:
mysql-data:
redis-data:
6.2 CI/CD流程
GitHub Actions关键步骤:
yaml复制name: Deploy Production
on:
push:
branches: [ main ]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Build Vue
run: |
cd frontend
npm install
npm run build
mv dist ../vue-dist
- name: Docker Build
run: |
docker-compose -f docker-compose.prod.yml build
- name: Deploy to Server
uses: appleboy/ssh-action@v0.1.10
with:
host: ${{ secrets.SSH_HOST }}
username: ${{ secrets.SSH_USER }}
key: ${{ secrets.SSH_KEY }}
script: |
cd /opt/uniform
git pull
docker-compose -f docker-compose.prod.yml up -d --build
7. 项目经验总结
在实际开发中,我们遇到了几个关键挑战和解决方案:
-
跨时区问题:
- 数据库统一使用UTC时间
- 前端根据用户时区转换显示
javascript复制// 前端时间处理 const formatLocalTime = (utcString) => { const date = new Date(utcString); return date.toLocaleString(navigator.language, { timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone }); }; -
打印服务对接:
- 使用PDFKit生成打印模板
- 对接热敏打印机指令集
javascript复制function generatePrintDoc(order) { const doc = new PDFDocument({ size: [80, 'mm'] }); doc.fontSize(10) .text(`订单号: ${order.no}`, { align: 'center' }); // 更多打印内容... return doc; } -
移动端适配陷阱:
- Vant组件库按需引入
- 禁用iOS橡皮筋效果
css复制body { overscroll-behavior-y: contain; }
这个项目让我深刻体会到,教育行业的系统开发需要特别注重:
- 极端情况下的数据一致性
- 非技术人员的操作体验
- 学期制业务周期特性
- 突发流量处理能力
后续计划将核心服务迁移到Serverless架构,进一步降低运维成本。对于需要开发类似系统的同行,建议提前与学校确认好校服发放流程的每个细节,这往往比技术实现更具挑战性。