1. 项目概述:基于ThinkPHP的药品商城系统开发实录
作为一名长期从事医药行业信息化解决方案的开发者,我最近完成了一个基于ThinkPHP框架的药品商城系统项目。这个系统专为中小型药店、连锁药房和医疗机构设计,实现了从药品管理到线上销售的全流程数字化。系统上线后帮助合作药房将线上订单量提升了47%,库存周转率提高了32%,今天就来详细拆解这个项目的技术实现和实战经验。
系统采用经典的B/S架构,前端使用Vue.js构建响应式界面,后端基于ThinkPHP 6.0开发,数据库选用MySQL 8.0。特别设计了处方药审核流程和药品库存预警机制,严格符合医药电商监管要求。下面我将从技术选型、核心模块实现、特殊业务处理等维度,分享这个项目的完整开发历程。
2. 技术架构设计与选型考量
2.1 后端技术栈决策过程
选择ThinkPHP作为核心框架主要基于以下考量:
- 开发效率:ThinkPHP的ORM和内置工具可以快速实现药品、订单等核心业务模型
- 安全合规:框架自带的XSS过滤、CSRF防护对医药电商至关重要
- 扩展能力:方便对接第三方支付、物流接口和电子处方平台
数据库选用MySQL 8.0而非MongoDB的原因:
- 药品数据具有强一致性要求(库存、价格等)
- 事务处理在订单创建、库存扣减等场景必不可少
- GIS功能支持附近药店定位查询
php复制// 典型的事务处理示例 - 创建订单时同步扣减库存
Db::transaction(function(){
$order = new Order($orderData);
$order->save();
foreach($cartItems as $item){
Db::table('medicine')
->where('id', $item['med_id'])
->dec('stock', $item['quantity'])
->update();
}
});
2.2 前端技术方案解析
采用Vue 3 + Element Plus的组合实现了:
- 响应式布局:自动适配PC、平板和手机端
- 组件化开发:药品卡片、购物车等高频复用组件
- 状态管理:Vuex统一管理用户登录状态、购物车数据
特别优化了药品搜索体验:
- 防抖处理搜索输入(300ms延迟)
- 支持拼音首字母快捷检索
- 复合索引优化查询性能
javascript复制// 药品搜索核心逻辑
const searchMeds = _.debounce(async (keyword) => {
if(!keyword.trim()) return;
const res = await axios.get('/api/meds/search', {
params: {
keyword,
pinyin: convertToPinyin(keyword)
}
});
searchResults.value = res.data;
}, 300);
3. 核心业务模块实现细节
3.1 药品管理子系统
3.1.1 药品信息建模
设计了扩展性强的药品数据模型:
php复制class Medicine extends Model
{
// 基础信息
protected $schema = [
'id' => 'int',
'name' => 'string', // 通用名
'trade_name' => 'string', // 商品名
'spec' => 'string', // 规格
'price' => 'float',
'stock' => 'int',
'is_rx' => 'bool', // 是否处方药
'category_id' => 'int' // 分类ID
];
// 关联分类
public function category()
{
return $this->belongsTo(Category::class);
}
}
3.1.2 库存预警机制
实现多维度库存监控:
- 阈值预警:设置最低库存阈值
- 效期预警:近效期药品提醒
- 滞销预警:设定时间内无销售记录
php复制// 每日执行的库存检查任务
class StockCheckTask
{
public function handle()
{
// 低库存预警
$lowStock = Medicine::where('stock', '<', Db::raw('warning_stock'))
->select();
// 近效期预警(30天内到期)
$expiring = MedicineBatch::where('expire_date', '<', date('Y-m-d', strtotime('+30 days')))
->select();
// 触发预警通知
$this->sendAlerts($lowStock, $expiring);
}
}
3.2 处方药销售流程
3.2.1 在线问诊对接
与合规互联网医院对接实现:
- 患者提交症状描述
- 医师在线开具电子处方
- 药师审核处方合理性
- 系统锁定处方药购买权限
php复制// 处方审核状态机
class Prescription extends Model
{
const STATUS_PENDING = 0; // 待审核
const STATUS_APPROVED = 1; // 已通过
const STATUS_REJECTED = 2; // 已拒绝
public function approve()
{
if($this->status != self::STATUS_PENDING){
throw new Exception('非法状态转换');
}
$this->status = self::STATUS_APPROVED;
$this->reviewed_at = date('Y-m-d H:i:s');
$this->save();
// 解锁对应药品购买权限
$this->unlockMedicines();
}
}
3.2.2 特殊药品管控
对麻醉类、精神类药品实施:
- 身份证信息核验
- 购买数量限制
- 销售记录备案
4. 关键业务逻辑实现
4.1 订单创建与库存扣减
实现高并发的库存扣减方案:
- 乐观锁防止超卖
- 队列处理高峰流量
- 自动库存补偿机制
php复制// 使用Redis队列处理订单
class OrderService
{
public function createOrder($cartData)
{
Redis::lpush('order_queue', json_encode($cartData));
return ['status' => 'queued'];
}
// 实际处理订单的Worker
public function processOrder($cartData)
{
try {
Db::transaction(function() use ($cartData){
// 检查库存
foreach($cartData['items'] as $item){
$med = Medicine::where('id', $item['id'])
->lock(true)
->find();
if($med->stock < $item['qty']){
throw new Exception("{$med->name}库存不足");
}
}
// 扣减库存
foreach($cartData['items'] as $item){
Medicine::where('id', $item['id'])
->dec('stock', $item['qty'])
->update();
}
// 创建订单
$order = new Order([
'user_id' => $cartData['user_id'],
'amount' => $cartData['total']
]);
$order->save();
});
} catch(Exception $e) {
// 失败重试或补偿逻辑
$this->handleOrderError($e, $cartData);
}
}
}
4.2 支付系统对接
集成微信支付和支付宝的注意事项:
- 签名验证:严格校验回调签名防止伪造
- 幂等处理:同一订单多次回调只处理一次
- 对账机制:每日定时核对支付平台记录
php复制// 支付回调处理示例
class PaymentController
{
public function wechatNotify()
{
$data = request()->post();
// 验证签名
if(!$this->verifyWechatSign($data)){
return response('<xml><return_code>FAIL</return_code></xml>');
}
// 查询本地订单
$order = Order::where('order_no', $data['out_trade_no'])
->find();
// 幂等检查
if($order->status == Order::STATUS_PAID){
return response('<xml><return_code>SUCCESS</return_code></xml>');
}
// 更新订单状态
$order->status = Order::STATUS_PAID;
$order->paid_at = date('Y-m-d H:i:s');
$order->save();
// 触发后续逻辑(发货、通知等)
event(new OrderPaid($order));
return response('<xml><return_code>SUCCESS</return_code></xml>');
}
}
5. 系统安全与合规实践
5.1 医药数据保护措施
-
敏感数据加密:
- 患者联系方式AES加密存储
- 处方信息单独加密
- 数据库字段级权限控制
-
操作审计:
- 关键操作日志记录
- 数据修改留痕
- 定期审计报告
php复制// 数据修改审计示例
class MedicineController
{
public function update($id)
{
$med = Medicine::find($id);
$original = $med->toArray();
$med->save(request()->post());
// 记录变更
AuditLog::create([
'user_id' => auth()->id(),
'action' => 'update_medicine',
'before' => json_encode($original),
'after' => json_encode($med->toArray())
]);
return success();
}
}
5.2 合规性设计要点
-
处方药销售:
- 强制上传处方照片
- 药师二次审核
- 购买限制策略
-
资质管理:
- 药店证照有效期监控
- 药品经营许可证校验
- 自动提醒续期
6. 性能优化实战经验
6.1 数据库优化方案
-
索引策略:
- 药品名称复合索引(name+pinyin)
- 订单查询覆盖索引(user_id+status+created_at)
- 避免过度索引影响写入性能
-
查询优化:
- 大量使用关联预加载
- 分页查询优化
- 热点数据缓存
php复制// 优化后的药品查询
class MedicineService
{
public function search($params)
{
return Medicine::with(['category'])
->when($params['keyword'], function($q) use ($params){
$q->where(function($q) use ($params){
$q->where('name', 'like', "%{$params['keyword']}%")
->orWhere('pinyin', 'like', "%{$params['keyword']}%");
});
})
->when($params['category'], function($q) use ($params){
$q->where('category_id', $params['category']);
})
->orderBy('sales', 'desc')
->paginate(20);
}
}
6.2 缓存应用实践
-
多级缓存架构:
- 本地缓存:高频访问的药品分类
- Redis缓存:热门药品详情
- 数据库:全量数据
-
缓存更新策略:
- 药品信息变更时双删缓存
- 库存数据不缓存确保准确性
- 分类数据定时刷新
php复制// 缓存处理示例
class CategoryService
{
public function getTree()
{
$key = 'category_tree';
if($data = Cache::get($key)){
return $data;
}
$data = Category::with(['children'])
->where('parent_id', 0)
->get()
->toTree();
Cache::put($key, $data, 3600); // 缓存1小时
return $data;
}
public function clearCache()
{
Cache::forget('category_tree');
}
}
7. 部署与运维方案
7.1 生产环境部署
采用Docker Compose编排服务:
yaml复制version: '3'
services:
app:
image: php:8.1-fpm
volumes:
- ./:/var/www/html
depends_on:
- redis
- mysql
nginx:
image: nginx:alpine
ports:
- "80:80"
volumes:
- ./:/var/www/html
- ./docker/nginx.conf:/etc/nginx/conf.d/default.conf
mysql:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: ${DB_PASSWORD}
MYSQL_DATABASE: medicine
volumes:
- mysql_data:/var/lib/mysql
redis:
image: redis:alpine
volumes:
mysql_data:
7.2 监控与告警配置
-
基础监控:
- 服务器CPU/内存/磁盘
- 服务进程状态
- 数据库连接数
-
业务监控:
- 订单创建异常
- 库存预警触发
- 支付成功率波动
-
告警渠道:
- 企业微信机器人
- 短信通知
- 邮件报告
8. 踩坑经验与解决方案
8.1 处方审核流程的并发问题
问题现象:
当多个药师同时处理同一处方时,可能出现重复审核
解决方案:
引入数据库行锁+状态机校验
php复制// 使用悲观锁确保同一处方不会被并发处理
$prescription = Prescription::where('id', $id)
->lockForUpdate()
->first();
if($prescription->status != Prescription::STATUS_PENDING){
throw new Exception('该处方已被其他药师处理');
}
// 继续审核逻辑...
8.2 库存超卖问题排查
问题场景:
促销活动期间出现库存扣减为负数
根本原因:
- 并发请求导致竞态条件
- 缓存与数据库不一致
最终方案:
- 数据库层面使用乐观锁
- 引入Redis分布式锁
- 增加库存预留机制
php复制// 使用Redis分布式锁
$lock = Redis::lock('stock_'.$medId, 10);
try {
$lock->block(5); // 最多等待5秒
$med = Medicine::find($medId);
if($med->stock >= $qty){
$med->decrement('stock', $qty);
}
} finally {
$lock->release();
}
9. 项目扩展与演进方向
9.1 微服务化改造
当前单体架构的局限性:
- 药品搜索服务影响核心交易
- 促销活动期间资源争用
- 局部故障影响范围大
改造方案:
-
拆分为独立服务:
- 药品服务
- 订单服务
- 用户服务
- 支付服务
-
服务通信方式:
- REST API基础交互
- 事件驱动架构处理最终一致性
9.2 智能推荐系统
规划中的增强功能:
-
基于症状的推荐:
- 用户输入症状描述
- NLP分析匹配适应症
- 推荐对症药品
-
关联推荐:
- 购买A药品的顾客也买了B
- 季节性疾病预防组合
- 疗程配套用药建议
-
健康档案:
- 记录用户用药历史
- 过敏药物自动过滤
- 用药提醒服务
这个药品商城系统从最初的需求分析到最终上线历时6个月,期间遇到了医药行业特有的各种合规性挑战和技术难题。最大的体会是:医药电商系统必须在便捷性和安全性之间找到平衡点,任何技术决策都要以合规为前提。比如处方药销售流程我们迭代了5个版本才通过药监部门的审核,虽然增加了开发成本,但这是必须坚守的底线。