三年前接手某医药连锁企业电商平台重构项目时,我深刻体会到药品在线销售系统与传统电商的本质差异。这个基于PHP+Laravel的药品销售系统,不仅需要实现常规电商功能,更要处理处方药审核、药品分类合规性验证等特殊业务场景。系统上线后日均处理处方审核2000+,订单转化率提升37%,下面将完整还原这个项目的技术实现细节。
药品电商系统的特殊性在于其强监管属性。根据《药品网络销售监督管理办法》要求,系统必须实现处方药与非处方药分类管理、执业药师在线审核、药品信息展示合规等核心功能。同时作为商业系统,又需要保证用户购物体验流畅,这对技术架构设计提出了双重挑战。
药品销售系统的功能模块可归纳为三个层级:
基础电商功能层
医药合规层
php复制// 处方审核状态机实现
class PrescriptionStatus {
const PENDING = 0; // 待审核
const APPROVED = 1; // 审核通过
const REJECTED = 2; // 审核驳回
const EXPIRED = 3; // 处方过期
}
增值服务层
数据库响应时间必须控制在200ms以内,这对药品目录查询优化提出高要求。我们通过以下方案实现:
MySQL索引优化:
sql复制ALTER TABLE `drugs`
ADD INDEX `idx_category_status` (`category_id`, `status`),
ADD FULLTEXT `idx_search` (`generic_name`, `brand_name`);
Redis缓存策略:
相比ThinkPHP,Laravel在以下方面更适合医药电商项目:
更完善的队列系统:处理处方审核异步流程
php复制// 处方审核任务分发
ProcessPrescription::dispatch($prescription)
->onQueue('prescription_review');
更强大的ORM:实现复杂的药品关联查询
php复制$drugs = Drug::with(['category', 'specs'])
->whereHas('inventory', fn($q) => $q->where('stock', '>', 0))
->paginate(20);
更健全的安全机制:内置CSRF保护、XSS过滤等
药品主表结构设计要点:
| 字段 | 类型 | 说明 | 约束 |
|---|---|---|---|
| drug_id | BIGINT | 药品ID | 主键 |
| category_id | INT | 分类ID | 外键 |
| is_rx | TINYINT | 是否处方药 | 0/1 |
| generic_name | VARCHAR(100) | 通用名 | NOT NULL |
| approval_number | VARCHAR(50) | 批准文号 | 唯一 |
处方审核关联表设计特别注意审计字段:
sql复制CREATE TABLE `prescription_reviews` (
`review_id` BIGINT PRIMARY KEY,
`prescription_id` BIGINT NOT NULL,
`pharmacist_id` BIGINT NOT NULL,
`status` TINYINT DEFAULT 0,
`review_notes` TEXT,
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
`updated_at` TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB;
完整的处方审核包含以下技术要点:
图片处理方案:
php复制$ocrResult = AliyunOCR::recognizeMedical(
$prescription->image_path,
['diagnosis', 'drug_list']
);
审核状态机:
mermaid复制stateDiagram
[*] --> 待审核
待审核 --> 审核通过: 药师确认
待审核 --> 审核驳回: 发现问题
审核通过 --> 订单生成
审核驳回 --> 重新上传
审核超时处理:
php复制// 定时任务处理超时处方
$expired = Prescription::where('status', PrescriptionStatus::PENDING)
->where('created_at', '<', now()->subHours(24))
->update(['status' => PrescriptionStatus::EXPIRED]);
药品订单支付需要特别注意:
处方药支付限制:
php复制if ($order->containsRxDrugs() && !$order->prescription_approved) {
throw new InvalidOrderException('处方药需审核通过后才能支付');
}
医保支付对接:
敏感信息加密:
php复制// 病历信息加密存储
$encrypted = encrypt($medicalRecord, [
'key' => config('app.medical_key'),
'cipher' => 'AES-256-CBC'
]);
审计日志实现:
php复制DB::table('drug_view_logs')->insert([
'user_id' => Auth::id(),
'drug_id' => $drug->id,
'ip_address' => request()->ip(),
'user_agent' => request()->userAgent()
]);
针对医药系统的特殊攻击方式:
药品爬虫防御:
php复制Route::middleware('throttle:60,1')
->group(function () {
Route::get('/api/drugs', 'DrugController@index');
});
处方伪造防护:
药品分类页的典型优化过程:
原始查询(执行时间1200ms):
sql复制SELECT * FROM drugs
WHERE category_id = 5
AND status = 1
ORDER BY sales DESC;
优化后方案(执行时间80ms):
sql复制SELECT d.* FROM drugs d
JOIN (SELECT id FROM drugs
WHERE category_id = 5 AND status = 1
ORDER BY sales DESC LIMIT 20) AS tmp
ON d.id = tmp.id;
药品详情页加载优化:
关键资源预加载:
html复制<link rel="preload" href="/css/drug.css" as="style">
<link rel="preload" href="/js/drug.js" as="script">
图片懒加载方案:
javascript复制const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target;
img.src = img.dataset.src;
observer.unobserve(img);
}
});
});
根据法规要求,前端展示必须包含:
php复制$filter = new DrugDescriptionFilter();
$cleanContent = $filter->clean($userInput);
订单数据保存策略:
问题现象:药师审核通过后订单未自动生成
排查过程:
解决方案:
php复制// 修改任务处理类
class ProcessPrescription implements ShouldQueue
{
public $timeout = 120;
public $tries = 3;
public function handle()
{
// 先压缩图片再处理
$this->compressImage();
// ...后续处理
}
}
问题场景:促销活动期间库存扣减异常
最终方案:
php复制DB::transaction(function () use ($order) {
foreach ($order->items as $item) {
$affected = DB::table('inventory')
->where('drug_id', $item->drug_id)
->where('stock', '>=', $item->quantity)
->decrement('stock', $item->quantity);
if ($affected === 0) {
throw new OutOfStockException();
}
}
});
当前系统已在以下方面进行迭代:
在技术架构上,我们正在逐步将核心服务拆分为微服务,其中处方审核服务已独立部署。对于中小型医药电商项目,我的建议是前期采用单体架构快速验证业务,待日均订单超过3000后再考虑服务化拆分。