1. 项目背景与需求分析
医院血库管理系统是医疗信息化建设中至关重要的组成部分。随着医疗业务量的增长和血液制品管理要求的提高,传统手工记录方式已无法满足现代医院对血液制品全流程追踪、实时库存预警和安全使用的需求。
在技术选型上,我们选择了Vue3作为前端框架,主要基于以下考虑:
- 组合式API更适合复杂业务逻辑的封装
- 更好的TypeScript支持便于团队协作开发
- 性能优化显著(Proxy响应式、编译时优化等)
- 更小的打包体积(相比Vue2减少约41%)
后端框架采用ThinkPHP-Laravel混合架构,这种设计主要基于:
- 历史系统兼容:部分旧模块基于ThinkPHP开发
- 新功能开发:利用Laravel更现代的ORM和队列系统
- 团队技术栈:开发人员对两个框架都有丰富经验
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 系统架构设计
2.1 整体技术架构
系统采用前后端分离架构:
code复制[浏览器] ←HTTP→ [Nginx] ←FastCGI→ [PHP-FPM] ←→ [MySQL]
↑
[Vue3 SPA]
关键组件说明:
- 前端:Vue3 + Pinia + Element Plus
- 后端:ThinkPHP6(兼容层) + Laravel8(核心业务)
- 数据库:MySQL8(事务隔离级别RR)
- 队列:Laravel Queue + Redis
- 缓存:Redis(血型匹配规则等热点数据)
2.2 数据库设计要点
血液库存表核心字段设计:
sql复制CREATE TABLE `blood_inventory` (
`id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT,
`blood_code` varchar(32) NOT NULL COMMENT '血液编码',
`blood_type` enum('A','B','AB','O') NOT NULL COMMENT '血型',
`rh_factor` enum('+','-') NOT NULL COMMENT 'Rh因子',
`product_type` enum('全血','红细胞','血浆','血小板') NOT NULL,
`volume` decimal(8,2) NOT NULL COMMENT '容量(ml)',
`collection_date` datetime NOT NULL COMMENT '采集日期',
`expiry_date` datetime NOT NULL COMMENT '失效日期',
`storage_location` varchar(20) NOT NULL COMMENT '存储位置',
`status` enum('可用','已预约','已出库','已报废') NOT NULL DEFAULT '可用',
PRIMARY KEY (`id`),
UNIQUE KEY `idx_blood_code` (`blood_code`),
KEY `idx_expiry` (`expiry_date`),
KEY `idx_status` (`status`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
重要提示:血液编码必须遵循ISBT128国际标准,包含采血机构代码、产品代码、ABO/Rh血型等信息的组合。
3. 核心功能实现
3.1 血液入库流程
前端实现要点(Vue3 Composition API):
javascript复制// 入库表单组件
const form = reactive({
bloodCode: '',
bloodType: null,
rhFactor: '+',
productType: '全血',
volume: 400,
collectionDate: dayjs().format('YYYY-MM-DD'),
expiryDate: computed(() => {
const days = form.productType === '血小板' ? 5 :
form.productType === '血浆' ? 365 : 42
return dayjs(form.collectionDate).add(days, 'day').format('YYYY-MM-DD')
})
})
const handleSubmit = async () => {
try {
const { data } = await api.bloodInventory.create(form)
message.success(`血液${data.bloodCode}入库成功`)
resetForm()
} catch (err) {
message.error(err.response?.data?.message || '入库失败')
}
}
后端关键逻辑(Laravel):
php复制class BloodInventoryController extends Controller
{
public function store(Request $request)
{
$validated = $request->validate([
'blood_code' => 'required|unique:blood_inventory',
'blood_type' => 'required|in:A,B,AB,O',
'product_type' => 'required|in:全血,红细胞,血浆,血小板',
'volume' => 'required|numeric|min:50'
]);
DB::transaction(function() use ($validated) {
$inventory = BloodInventory::create($validated);
// 记录操作日志
OperationLog::create([
'type' => '入库',
'operator_id' => auth()->id(),
'content' => "新增血液:{$inventory->blood_code}"
]);
// 触发库存预警检查
dispatch(new CheckInventoryWarning($inventory->blood_type));
});
return response()->json($inventory, 201);
}
}
3.2 血液出库与配型
配型算法核心逻辑:
php复制class BloodMatchingService
{
public function match(Request $request)
{
$requiredType = $request->input('blood_type');
$requiredRh = $request->input('rh_factor');
$query = BloodInventory::where('status', '可用')
->where('expiry_date', '>', now());
// 精确匹配优先
$exactMatch = $query->clone()
->where('blood_type', $requiredType)
->where('rh_factor', $requiredRh)
->orderBy('expiry_date')
->first();
if ($exactMatch) return $exactMatch;
// 兼容性匹配规则
$compatibleTypes = $this->getCompatibleTypes($requiredType);
return $query->whereIn('blood_type', $compatibleTypes)
->where('rh_factor', $requiredRh)
->orderBy('expiry_date')
->first();
}
protected function getCompatibleTypes($bloodType)
{
$rules = [
'O' => ['O'],
'A' => ['A', 'O'],
'B' => ['B', 'O'],
'AB' => ['A', 'B', 'AB', 'O']
];
return $rules[$bloodType] ?? [];
}
}
4. 关键技术难点与解决方案
4.1 混合框架整合
ThinkPHP与Laravel共存方案:
- 统一入口文件(public/index.php):
php复制define('APP_PATH', __DIR__.'/../app/');
// Laravel路由优先
$request = Illuminate\Http\Request::capture();
$response = (new Laravel\App\Http\Kernel($app))
->handle($request);
if ($response->getStatusCode() === 404) {
// 未匹配Laravel路由时回退到ThinkPHP
$response = (new think\App())->http->run();
}
$response->send();
- 数据库配置兼容:
php复制// config/database.php
return [
'default' => env('DB_CONNECTION', 'mysql'),
'connections' => [
'mysql' => [
'driver' => 'mysql',
'url' => env('DATABASE_URL'),
'host' => env('DB_HOST', '127.0.0.1'),
'port' => env('DB_PORT', '3306'),
'database' => env('DB_DATABASE', 'blood_library'),
'username' => env('DB_USERNAME', 'root'),
'password' => env('DB_PASSWORD', ''),
// Laravel风格配置
'charset' => 'utf8mb4',
'collation' => 'utf8mb4_unicode_ci',
// ThinkPHP兼容配置
'params' => [
\PDO::ATTR_CASE => \PDO::CASE_NATURAL
]
]
]
];
4.2 高并发库存操作
使用Redis分布式锁解决超卖问题:
php复制class BloodOutService
{
public function outbound($bloodCode)
{
$lockKey = "blood_out:{$bloodCode}";
$lock = Redis::connection()->lock($lockKey, 10);
try {
if (!$lock->get()) {
throw new Exception('系统繁忙,请稍后重试');
}
DB::transaction(function() use ($bloodCode) {
$blood = BloodInventory::where('blood_code', $bloodCode)
->where('status', '可用')
->lockForUpdate()
->firstOrFail();
$blood->status = '已出库';
$blood->save();
OperationLog::create([...]);
});
return true;
} finally {
$lock->release();
}
}
}
5. 系统安全与合规
5.1 数据加密方案
敏感字段加密处理:
php复制// 在BloodInventory模型中使用加密
class BloodInventory extends Model
{
protected $encryptable = ['donor_info'];
public function setDonorInfoAttribute($value)
{
$this->attributes['donor_info'] = encrypt($value);
}
public function getDonorInfoAttribute($value)
{
return $value ? decrypt($value) : null;
}
}
5.2 审计日志实现
完整操作审计方案:
php复制// 全局中间件
class OperationLogMiddleware
{
public function handle($request, Closure $next)
{
$response = $next($request);
if ($request->isMethod('post|put|delete')) {
OperationLog::create([
'user_id' => auth()->id(),
'path' => $request->path(),
'method' => $request->method(),
'ip' => $request->ip(),
'input' => $this->filterSensitive($request->all())
]);
}
return $response;
}
protected function filterSensitive($input)
{
foreach (['password', 'cvv', 'donor_info'] as $field) {
if (isset($input[$field])) {
$input[$field] = '******';
}
}
return $input;
}
}
6. 性能优化实践
6.1 前端性能优化
Vue3特定优化措施:
- 组件按需加载:
javascript复制const BloodList = defineAsyncComponent(() =>
import('./components/BloodList.vue')
)
- 列表虚拟滚动:
html复制<el-table
:data="tableData"
height="calc(100vh - 180px)"
row-key="id"
v-loading="loading">
<!-- 列定义 -->
</el-table>
- Webpack分包策略:
javascript复制// vite.config.js
export default defineConfig({
build: {
rollupOptions: {
output: {
manualChunks: {
echarts: ['echarts'],
element: ['element-plus']
}
}
}
}
})
6.2 后端缓存策略
多级缓存设计方案:
- 热点数据缓存:
php复制class BloodTypeService
{
public function getCompatibilityRules()
{
return Cache::remember('blood_compatibility_rules', 3600, function() {
return BloodCompatibility::all()
->groupBy('blood_type')
->toArray();
});
}
}
- 数据库查询优化:
php复制// 使用Laravel查询构造器优化
BloodInventory::select([
'blood_type',
'rh_factor',
DB::raw('COUNT(*) as total'),
DB::raw('SUM(CASE WHEN status = "可用" THEN 1 ELSE 0 END) as available')
])
->groupBy('blood_type', 'rh_factor')
->get();
7. 部署与运维
7.1 生产环境部署
Nginx关键配置:
nginx复制server {
listen 80;
server_name blood.example.com;
root /var/www/blood-system/public;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php$ {
fastcgi_pass unix:/var/run/php/php8.1-fpm.sock;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
}
location ~ /\.(?!well-known).* {
deny all;
}
}
7.2 队列处理方案
使用Supervisor管理Laravel队列:
ini复制[program:laravel-worker]
process_name=%(program_name)s_%(process_num)02d
command=php /var/www/blood-system/artisan queue:work --queue=default,emails --sleep=3 --tries=3
autostart=true
autorestart=true
user=www-data
numprocs=4
redirect_stderr=true
stdout_logfile=/var/log/worker.log
8. 测试策略
8.1 单元测试示例
Laravel测试用例:
php复制class BloodMatchingTest extends TestCase
{
public function test_blood_type_compatibility()
{
// 准备测试数据
BloodInventory::factory()->create([
'blood_type' => 'O',
'rh_factor' => '+',
'status' => '可用'
]);
// 测试O型血患者
$response = $this->postJson('/api/blood/match', [
'blood_type' => 'O',
'rh_factor' => '+'
]);
$response->assertStatus(200)
->assertJsonPath('data.blood_type', 'O');
}
}
8.2 端到端测试
Cypress测试示例:
javascript复制describe('血液入库流程', () => {
it('成功完成血液入库', () => {
cy.login('admin', 'password123')
cy.visit('/blood-in/create')
cy.get('[data-test="blood-code"]').type('ISBT-2023-0001')
cy.get('[data-test="blood-type"]').select('A')
cy.get('[data-test="volume"]').clear().type('400')
cy.intercept('POST', '/api/blood-inventory').as('createRequest')
cy.get('[data-test="submit-btn"]').click()
cy.wait('@createRequest').its('response.statusCode').should('eq', 201)
cy.contains('.el-message', '入库成功').should('be.visible')
})
})
9. 项目经验总结
在实际开发过程中,有几个关键经验值得分享:
- 血型兼容性规则的实现要特别注意:
- AB型可以接受所有血型(万能受血者)
- O型只能接受O型血
- Rh阴性血只能接受Rh阴性血(除非紧急情况)
- 库存预警的阈值设置应该可配置化:
php复制// 在config/blood.php中定义
return [
'warning_threshold' => [
'O' => 20, // 单位:袋
'A' => 15,
'B' => 15,
'AB' => 10
]
];
- 对于血液有效期管理,我们最终采用的方案是:
- 每天凌晨2点执行过期检查任务
- 提前3天触发预警通知
- 使用Laravel Task Scheduling实现:
php复制// app/Console/Kernel.php
protected function schedule(Schedule $schedule)
{
$schedule->command('blood:check-expiry')
->dailyAt('02:00')
->onOneServer();
}
- 在Vue3组件设计时,我们发现使用Composition API封装业务逻辑能显著提高代码复用性。例如将血液类型验证逻辑提取为可组合函数:
javascript复制// composables/useBloodType.js
export function useBloodType() {
const validateBloodCode = (code) => {
return /^ISBT-\d{4}-\d{4}$/.test(code)
}
const getExpiryDays = (productType) => {
const daysMap = {
'全血': 42,
'红细胞': 42,
'血浆': 365,
'血小板': 5
}
return daysMap[productType] || 0
}
return { validateBloodCode, getExpiryDays }
}
