diff --git a/server/APPOINTMENT_BACKEND_SETUP.md b/server/APPOINTMENT_BACKEND_SETUP.md deleted file mode 100644 index ecf5c634..00000000 --- a/server/APPOINTMENT_BACKEND_SETUP.md +++ /dev/null @@ -1,379 +0,0 @@ -# 医生预约系统后端安装说明 - -## 文件清单 - -已创建以下后端文件: - -### 1. 数据库模型 -- `server/app/common/model/doctor/Appointment.php` - 预约模型 - -### 2. 控制器 -- `server/app/adminapi/controller/doctor/AppointmentController.php` - 预约控制器 - -### 3. 业务逻辑层 -- `server/app/adminapi/logic/doctor/AppointmentLogic.php` - 预约业务逻辑 - -### 4. 验证器 -- `server/app/adminapi/validate/doctor/AppointmentValidate.php` - 预约数据验证 - -### 5. 数据列表 -- `server/app/adminapi/lists/doctor/AppointmentLists.php` - 预约列表 - -### 6. 数据库脚本 -- `server/sql/doctor_appointment.sql` - 数据库表创建脚本 - -## 安装步骤 - -### 1. 创建数据库表 - -在MySQL中执行以下SQL脚本: - -```bash -mysql -u your_username -p your_database < server/sql/doctor_appointment.sql -``` - -或者在phpMyAdmin中导入 `server/sql/doctor_appointment.sql` 文件。 - -### 2. 验证文件 - -确认所有文件已正确创建: - -```bash -# 检查模型 -ls -la server/app/common/model/doctor/Appointment.php - -# 检查控制器 -ls -la server/app/adminapi/controller/doctor/AppointmentController.php - -# 检查逻辑层 -ls -la server/app/adminapi/logic/doctor/AppointmentLogic.php - -# 检查验证器 -ls -la server/app/adminapi/validate/doctor/AppointmentValidate.php - -# 检查列表 -ls -la server/app/adminapi/lists/doctor/AppointmentLists.php -``` - -### 3. 清除缓存 - -```bash -cd server -php think clear -``` - -### 4. 测试API接口 - -#### 4.1 获取可用时间段 - -```bash -GET /adminapi/doctor.appointment/availableSlots?doctor_id=1&appointment_date=2026-03-04&period=morning -``` - -预期返回: -```json -{ - "code": 1, - "msg": "success", - "data": { - "slots": [ - { - "time": "09:00", - "available": true, - "quota": 5 - }, - ... - ] - } -} -``` - -#### 4.2 创建预约 - -```bash -POST /adminapi/doctor.appointment/create -Content-Type: application/json - -{ - "patient_id": 1, - "doctor_id": 1, - "appointment_date": "2026-03-04", - "period": "morning", - "appointment_time": "09:00", - "appointment_type": "video", - "remark": "测试预约" -} -``` - -预期返回: -```json -{ - "code": 1, - "msg": "预约成功", - "data": { - "id": 1 - } -} -``` - -#### 4.3 获取预约列表 - -```bash -GET /adminapi/doctor.appointment/lists?page_no=1&page_size=10 -``` - -#### 4.4 取消预约 - -```bash -POST /adminapi/doctor.appointment/cancel -Content-Type: application/json - -{ - "id": 1 -} -``` - -#### 4.5 获取医生可用号源数 - -```bash -GET /adminapi/doctor.appointment/doctorAvailability?doctor_id=1&date=2026-03-04 -``` - -预期返回: -```json -{ - "code": 1, - "msg": "success", - "data": { - "available_count": 10 - } -} -``` - -## API接口说明 - -### 1. 获取可用时间段 - -**接口地址**: `/adminapi/doctor.appointment/availableSlots` - -**请求方式**: GET - -**请求参数**: -| 参数名 | 类型 | 必填 | 说明 | -|--------|------|------|------| -| doctor_id | int | 是 | 医生ID | -| appointment_date | date | 是 | 预约日期 YYYY-MM-DD | -| period | string | 是 | 时段 morning/afternoon | - -**返回数据**: -```json -{ - "slots": [ - { - "time": "09:00", - "available": true, - "quota": 5 - } - ] -} -``` - -### 2. 创建预约 - -**接口地址**: `/adminapi/doctor.appointment/create` - -**请求方式**: POST - -**请求参数**: -| 参数名 | 类型 | 必填 | 说明 | -|--------|------|------|------| -| patient_id | int | 是 | 患者ID | -| doctor_id | int | 是 | 医生ID | -| appointment_date | date | 是 | 预约日期 | -| period | string | 是 | 时段 morning/afternoon | -| appointment_time | time | 是 | 预约时间 HH:mm | -| appointment_type | string | 否 | 预约类型 video/text/phone | -| remark | string | 否 | 备注 | - -**返回数据**: -```json -{ - "id": 1 -} -``` - -### 3. 取消预约 - -**接口地址**: `/adminapi/doctor.appointment/cancel` - -**请求方式**: POST - -**请求参数**: -| 参数名 | 类型 | 必填 | 说明 | -|--------|------|------|------| -| id | int | 是 | 预约ID | - -### 4. 预约列表 - -**接口地址**: `/adminapi/doctor.appointment/lists` - -**请求方式**: GET - -**请求参数**: -| 参数名 | 类型 | 必填 | 说明 | -|--------|------|------|------| -| page_no | int | 否 | 页码 | -| page_size | int | 否 | 每页数量 | -| patient_id | int | 否 | 患者ID | -| doctor_id | int | 否 | 医生ID | -| status | int | 否 | 状态 | -| appointment_date | date | 否 | 预约日期 | - -### 5. 预约详情 - -**接口地址**: `/adminapi/doctor.appointment/detail` - -**请求方式**: GET - -**请求参数**: -| 参数名 | 类型 | 必填 | 说明 | -|--------|------|------|------| -| id | int | 是 | 预约ID | - -### 6. 医生可用号源数 - -**接口地址**: `/adminapi/doctor.appointment/doctorAvailability` - -**请求方式**: GET - -**请求参数**: -| 参数名 | 类型 | 必填 | 说明 | -|--------|------|------|------| -| doctor_id | int | 是 | 医生ID | -| date | date | 是 | 日期 YYYY-MM-DD | - -## 业务逻辑说明 - -### 1. 时间段生成规则 - -- 上午时段:09:00 - 11:30(30分钟间隔) -- 下午时段:13:00 - 20:30(30分钟间隔) - -### 2. 预约规则 - -1. 必须有医生排班且状态为"出诊"(status=1) -2. 时间段不能重复预约 -3. 预约数量不能超过排班设置的号源数(quota) -4. 使用数据库唯一索引防止并发冲突 - -### 3. 状态说明 - -**预约状态**: -- 1: 已预约 -- 2: 已取消 -- 3: 已完成 - -**排班状态**: -- 1: 出诊 -- 2: 停诊 -- 3: 休息 -- 4: 请假 - -## 注意事项 - -1. **并发控制**: 使用数据库唯一索引 `unique_appointment` 防止重复预约 -2. **事务处理**: 创建预约时使用事务确保数据一致性 -3. **时间格式**: - - 日期格式:YYYY-MM-DD - - 时间格式:HH:mm - - 时间戳:整型(秒) -4. **权限控制**: 需要在后台权限管理中添加相应的权限节点 - -## 权限节点 - -建议添加以下权限节点: - -``` -doctor.appointment/availableSlots - 查看可用时间段 -doctor.appointment/create - 创建预约 -doctor.appointment/cancel - 取消预约 -doctor.appointment/lists - 预约列表 -doctor.appointment/detail - 预约详情 -doctor.appointment/doctorAvailability - 医生可用号源 -``` - -## 故障排查 - -### 1. 控制器不存在 - -**错误**: `控制器不存在: app\adminapi\controller\doctor\AppointmentController` - -**解决方案**: -```bash -# 清除缓存 -cd server -php think clear - -# 检查文件是否存在 -ls -la server/app/adminapi/controller/doctor/AppointmentController.php - -# 检查命名空间是否正确 -head -n 5 server/app/adminapi/controller/doctor/AppointmentController.php -``` - -### 2. 数据库表不存在 - -**错误**: `Table 'database.la_doctor_appointment' doesn't exist` - -**解决方案**: -```bash -# 执行SQL脚本 -mysql -u username -p database < server/sql/doctor_appointment.sql - -# 或在MySQL中手动执行 -mysql> source /path/to/server/sql/doctor_appointment.sql; -``` - -### 3. 参数验证失败 - -**错误**: `参数错误` - -**解决方案**: -- 检查请求参数是否完整 -- 检查参数类型是否正确 -- 查看验证器规则是否匹配 - -## 测试数据 - -### 创建测试排班 - -```sql -INSERT INTO `la_doctor_roster` (`doctor_id`, `date`, `period`, `status`, `quota`, `max_patients`, `create_time`, `update_time`) -VALUES -(1, '2026-03-04', 'morning', 1, 10, 15, UNIX_TIMESTAMP(), UNIX_TIMESTAMP()), -(1, '2026-03-04', 'afternoon', 1, 10, 15, UNIX_TIMESTAMP(), UNIX_TIMESTAMP()); -``` - -### 创建测试预约 - -```sql -INSERT INTO `la_doctor_appointment` (`patient_id`, `doctor_id`, `roster_id`, `appointment_date`, `period`, `appointment_time`, `appointment_type`, `status`, `create_time`, `update_time`) -VALUES -(1, 1, 1, '2026-03-04', 'morning', '09:00:00', 'video', 1, UNIX_TIMESTAMP(), UNIX_TIMESTAMP()); -``` - -## 完成状态 - -✅ 数据库模型创建完成 -✅ 控制器创建完成 -✅ 业务逻辑层创建完成 -✅ 验证器创建完成 -✅ 数据列表创建完成 -✅ SQL脚本创建完成 -⏳ 等待数据库表创建 -⏳ 等待接口测试 - ---- - -**创建时间**: 2024-02-26 -**最后更新**: 2024-02-26 diff --git a/server/BATCH_ROSTER_FIX.md b/server/BATCH_ROSTER_FIX.md deleted file mode 100644 index 2aac7555..00000000 --- a/server/BATCH_ROSTER_FIX.md +++ /dev/null @@ -1,229 +0,0 @@ -# 批量排班接口修复说明 - -## 问题描述 - -接口报错: -``` -app\common\controller\BaseLikeAdminController::success(): -Argument #2 ($data) must be of type array, bool given -``` - -## 问题原因 - -`RosterLogic::batchSave()` 方法返回 `true`(布尔值),但控制器的 `success()` 方法要求第二个参数必须是数组类型。 - -## 解决方案 - -### 1. 修改 Logic 层返回值 - -**修改前**: -```php -public static function batchSave(array $params) -{ - // ... - Db::commit(); - return true; // 返回布尔值 -} -``` - -**修改后**: -```php -public static function batchSave(array $params) -{ - // ... - Db::commit(); - return [ - 'success_count' => $successCount, - 'create_count' => $createCount, - 'update_count' => $updateCount, - ]; // 返回数组,包含统计信息 -} -``` - -### 2. 修改控制器 - -**修改前**: -```php -public function batchSave() -{ - $params = (new RosterValidate())->post()->goCheck('batchSave'); - $result = RosterLogic::batchSave($params); - return $this->success('批量保存成功', $result); // $result 是 bool -} -``` - -**修改后**: -```php -public function batchSave() -{ - $params = (new RosterValidate())->post()->goCheck('batchSave'); - $result = RosterLogic::batchSave($params); - if ($result === false) { - return $this->fail(RosterLogic::getError()); - } - return $this->success('批量保存成功', $result); // $result 是 array -} -``` - -## 改进内容 - -### 1. 返回统计信息 - -现在批量保存接口会返回详细的统计信息: - -```json -{ - "code": 1, - "msg": "批量保存成功", - "data": { - "success_count": 20, - "create_count": 15, - "update_count": 5 - } -} -``` - -- `success_count`: 成功处理的记录数 -- `create_count`: 新创建的记录数 -- `update_count`: 更新的记录数 - -### 2. 错误处理 - -如果批量保存失败,会返回错误信息: - -```json -{ - "code": 0, - "msg": "错误信息", - "data": [] -} -``` - -### 3. 同样修复了 copy 方法 - -`copy` 方法也有同样的问题,已一并修复。 - -## 测试验证 - -### 测试1:批量创建 - -**请求**: -```json -POST /adminapi/doctor.roster/batchSave -{ - "rosters": [ - { - "doctor_id": 1, - "date": "2026-03-04", - "period": "morning", - "status": 1, - "quota": 20, - "max_patients": 30 - }, - { - "doctor_id": 1, - "date": "2026-03-04", - "period": "afternoon", - "status": 1, - "quota": 20, - "max_patients": 30 - } - ] -} -``` - -**响应**: -```json -{ - "code": 1, - "msg": "批量保存成功", - "data": { - "success_count": 2, - "create_count": 2, - "update_count": 0 - } -} -``` - -### 测试2:批量更新 - -再次执行相同的请求: - -**响应**: -```json -{ - "code": 1, - "msg": "批量保存成功", - "data": { - "success_count": 2, - "create_count": 0, - "update_count": 2 - } -} -``` - -## 相关文件 - -修改的文件: -1. `server/app/adminapi/logic/doctor/RosterLogic.php` - 修改返回值类型 -2. `server/app/adminapi/controller/doctor/RosterController.php` - 添加错误处理 - -## 注意事项 - -1. 所有返回布尔值的 Logic 方法都应该检查是否会传递给 `success()` 方法 -2. 如果需要返回数据,应该返回数组而不是布尔值 -3. 失败时应该使用 `fail()` 方法而不是 `success()` - -## 最佳实践 - -### Logic 层返回值规范 - -```php -// ✅ 正确:返回数组或 false -public static function someMethod($params) -{ - try { - // 处理逻辑 - return ['id' => $id, 'count' => $count]; - } catch (\Exception $e) { - self::setError($e->getMessage()); - return false; - } -} - -// ❌ 错误:返回 true -public static function someMethod($params) -{ - try { - // 处理逻辑 - return true; // 不要这样做 - } catch (\Exception $e) { - return false; - } -} -``` - -### 控制器层处理规范 - -```php -// ✅ 正确:检查返回值 -public function someAction() -{ - $result = SomeLogic::someMethod($params); - if ($result === false) { - return $this->fail(SomeLogic::getError()); - } - return $this->success('操作成功', $result); -} - -// ❌ 错误:直接传递布尔值 -public function someAction() -{ - $result = SomeLogic::someMethod($params); - return $this->success('操作成功', $result); // 如果 $result 是 bool 会报错 -} -``` - -## 总结 - -问题已修复,批量排班接口现在可以正常工作,并且返回详细的统计信息。 diff --git a/server/DOCTOR_ROLE_RELATIONSHIP.md b/server/DOCTOR_ROLE_RELATIONSHIP.md deleted file mode 100644 index cccad3a7..00000000 --- a/server/DOCTOR_ROLE_RELATIONSHIP.md +++ /dev/null @@ -1,264 +0,0 @@ -# 医生角色关系说明 - -## 概述 - -医生和角色的关系通过中间表 `zyt_admin_role` 来管理。医生是具有 `role_id = 1` 角色的管理员。 - -## 数据库表结构 - -### 1. 管理员表 (zyt_admin) - -```sql -CREATE TABLE `zyt_admin` ( - `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键ID', - `name` varchar(50) NOT NULL COMMENT '管理员名称', - `account` varchar(50) NOT NULL COMMENT '管理员账号', - `avatar` varchar(255) DEFAULT NULL COMMENT '头像', - `mobile` varchar(20) DEFAULT NULL COMMENT '手机号', - `email` varchar(100) DEFAULT NULL COMMENT '邮箱', - `disable` tinyint(1) DEFAULT '0' COMMENT '是否禁用', - `create_time` int(10) unsigned DEFAULT NULL COMMENT '创建时间', - `update_time` int(10) unsigned DEFAULT NULL COMMENT '更新时间', - `delete_time` int(10) unsigned DEFAULT NULL COMMENT '删除时间', - PRIMARY KEY (`id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='管理员表'; -``` - -### 2. 角色表 (zyt_admin_role) - -```sql -CREATE TABLE `zyt_admin_role` ( - `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键ID', - `admin_id` int(11) NOT NULL COMMENT '管理员ID', - `role_id` int(11) NOT NULL COMMENT '角色ID', - PRIMARY KEY (`id`), - UNIQUE KEY `uk_admin_role` (`admin_id`, `role_id`), - KEY `idx_admin_id` (`admin_id`), - KEY `idx_role_id` (`role_id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='管理员角色中间表'; -``` - -### 3. 角色表 (zyt_admin_role_info) - -```sql -CREATE TABLE `zyt_admin_role_info` ( - `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键ID', - `name` varchar(50) NOT NULL COMMENT '角色名称', - `remark` varchar(255) DEFAULT NULL COMMENT '备注', - `create_time` int(10) unsigned DEFAULT NULL COMMENT '创建时间', - `update_time` int(10) unsigned DEFAULT NULL COMMENT '更新时间', - PRIMARY KEY (`id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='角色信息表'; -``` - -## 角色 ID 说明 - -| role_id | 角色名称 | 说明 | -|---------|---------|------| -| 1 | 医生 | 医生角色 | -| 2 | 管理员 | 系统管理员 | -| ... | ... | 其他角色 | - -## 查询医生的方式 - -### 方式1:通过中间表查询(推荐) - -```php -// 获取所有医生的 admin_id -$adminIds = AdminRole::where('role_id', 1)->column('admin_id'); - -// 查询医生信息 -$doctors = Admin::whereIn('id', $adminIds) - ->where('disable', 0) - ->select(); -``` - -### 方式2:通过 Admin 模型的 role_id 属性 - -```php -// Admin 模型中有 getRoleIdAttr 方法,会自动从中间表获取 role_id -$admin = Admin::find(1); -$roleIds = $admin->role_id; // 返回数组 [1, 2, ...] - -// 检查是否是医生 -if (in_array(1, $roleIds)) { - // 是医生 -} -``` - -## 后端实现 - -### DoctorLogic.php - -```php -use app\common\model\auth\AdminRole; - -// 获取医生列表 -$adminIds = AdminRole::where('role_id', 1)->column('admin_id'); - -$doctors = Admin::whereIn('id', $adminIds) - ->where('disable', 0) - ->select(); - -// 获取医生详情 -$hasRole = AdminRole::where('admin_id', $doctorId) - ->where('role_id', 1) - ->exists(); - -if ($hasRole) { - $doctor = Admin::find($doctorId); -} -``` - -## API 端点 - -### 获取医生列表 - -**请求**: -``` -GET /api/doctor/lists?page_no=1&page_size=10 -``` - -**流程**: -1. 从 `zyt_admin_role` 表查询所有 `role_id = 1` 的 `admin_id` -2. 从 `zyt_admin` 表查询这些 `admin_id` 的管理员信息 -3. 过滤 `disable = 0` 的管理员 -4. 返回医生列表 - -**响应**: -```json -{ - "code": 1, - "msg": "success", - "data": { - "lists": [ - { - "id": 2, - "name": "张医生", - "account": "doctor1", - "avatar": "http://example.com/avatar.jpg", - "mobile": "13800138000", - "email": "doctor@example.com", - "specialty": "医生", - "title": "医生", - "rating": "4.8" - } - ], - "count": 1, - "page_no": 1, - "page_size": 10 - } -} -``` - -### 获取医生详情 - -**请求**: -``` -GET /api/doctor/detail?id=2 -``` - -**流程**: -1. 检查 `admin_id = 2` 是否在 `zyt_admin_role` 表中有 `role_id = 1` 的记录 -2. 如果有,从 `zyt_admin` 表查询医生信息 -3. 返回医生详情 - -### 获取医生排班 - -**请求**: -``` -GET /api/doctor/roster?doctor_id=2&date=2024-03-11 -``` - -**流程**: -1. 检查医生是否有医生角色 -2. 从 `la_doctor_roster` 表查询排班信息 - -## 添加医生的步骤 - -### 1. 创建管理员 - -在后台管理系统中创建新的管理员: -- 名称:张医生 -- 账号:doctor1 -- 头像:上传头像 -- 手机:13800138000 -- 邮箱:doctor@example.com - -### 2. 分配医生角色 - -在后台管理系统中为该管理员分配医生角色(role_id = 1)。 - -系统会自动在 `zyt_admin_role` 表中插入一条记录: -```sql -INSERT INTO zyt_admin_role (admin_id, role_id) VALUES (2, 1); -``` - -### 3. 验证 - -调用 API 验证医生是否已添加: -``` -GET /api/doctor/lists -``` - -## 常见问题 - -### Q: 如何查询某个管理员的所有角色? - -A: -```php -$admin = Admin::find($adminId); -$roleIds = $admin->role_id; // 返回数组 -``` - -### Q: 如何检查某个管理员是否是医生? - -A: -```php -$hasRole = AdminRole::where('admin_id', $adminId) - ->where('role_id', 1) - ->exists(); -``` - -### Q: 如何为管理员添加医生角色? - -A: -```php -AdminRole::create([ - 'admin_id' => $adminId, - 'role_id' => 1 -]); -``` - -### Q: 如何移除管理员的医生角色? - -A: -```php -AdminRole::where('admin_id', $adminId) - ->where('role_id', 1) - ->delete(); -``` - -### Q: 医生列表为空? - -A: 检查以下几点: -1. 是否有管理员被分配了医生角色(role_id = 1) -2. 医生的 `disable` 字段是否为 0 -3. 医生是否被软删除(`delete_time` 不为空) - -## 相关文件 - -- 医生逻辑: `server/app/api/logic/DoctorLogic.php` -- 医生控制器: `server/app/api/controller/DoctorController.php` -- Admin 模型: `server/app/common/model/auth/Admin.php` -- AdminRole 模型: `server/app/common/model/auth/AdminRole.php` - -## 总结 - -医生是通过中间表 `zyt_admin_role` 与管理员关联的。查询医生时需要: - -1. 从 `zyt_admin_role` 表查询 `role_id = 1` 的所有 `admin_id` -2. 从 `zyt_admin` 表查询这些 `admin_id` 的管理员信息 -3. 过滤 `disable = 0` 的管理员 - -这样可以确保只查询出具有医生角色的管理员。 diff --git a/server/DOCTOR_ROSTER_BACKEND.md b/server/DOCTOR_ROSTER_BACKEND.md deleted file mode 100644 index 2c505cfd..00000000 --- a/server/DOCTOR_ROSTER_BACKEND.md +++ /dev/null @@ -1,245 +0,0 @@ -# 医生排班管理 - 后端实现说明 - -## 文件结构 - -``` -server/app/ -├── adminapi/ -│ ├── controller/doctor/ -│ │ └── RosterController.php # 排班控制器 -│ ├── lists/doctor/ -│ │ └── RosterLists.php # 排班列表 -│ ├── logic/doctor/ -│ │ └── RosterLogic.php # 排班逻辑 -│ └── validate/doctor/ -│ └── RosterValidate.php # 排班验证器 -└── common/ - └── model/doctor/ - └── Roster.php # 排班模型 -``` - -## 数据库表 - -表名:`la_doctor_roster` - -```sql -CREATE TABLE `la_doctor_roster` ( - `id` int(11) unsigned NOT NULL AUTO_INCREMENT, - `doctor_id` int(11) NOT NULL COMMENT '医生ID(对应 zyt_admin.id)', - `date` date NOT NULL COMMENT '日期', - `period` varchar(20) NOT NULL COMMENT '时段 morning/afternoon', - `status` tinyint(1) NOT NULL DEFAULT '1' COMMENT '1-出诊 2-停诊 3-休息 4-请假', - `quota` int(11) DEFAULT '0' COMMENT '号源数', - `max_patients` int(11) DEFAULT '0' COMMENT '最大接诊数', - `booked_count` int(11) DEFAULT '0' COMMENT '已预约数', - `remark` varchar(500) DEFAULT '', - `create_time` int(11) NOT NULL, - `update_time` int(11) NOT NULL, - `delete_time` int(11) DEFAULT NULL, - PRIMARY KEY (`id`), - UNIQUE KEY `uk_doctor_date_period` (`doctor_id`,`date`,`period`,`delete_time`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -``` - -## API 接口 - -### 1. 获取排班列表 - -**URL**: `GET /adminapi/doctor.roster/lists` - -**请求参数**: -- `start_date`: 开始日期(必填) -- `end_date`: 结束日期(必填) -- `doctor_id`: 医生ID(可选) -- `period`: 时段(可选,morning/afternoon) -- `status`: 状态(可选,1/2/3/4) - -**返回示例**: -```json -{ - "code": 1, - "msg": "success", - "data": { - "lists": [ - { - "id": 1, - "doctor_id": 1, - "date": "2026-03-02", - "period": "morning", - "status": 1, - "quota": 20, - "max_patients": 30, - "booked_count": 5, - "remark": "", - "create_time": 1709366400, - "update_time": 1709366400 - } - ], - "count": 1, - "page_no": 1, - "page_size": 15 - } -} -``` - -### 2. 保存排班 - -**URL**: `POST /adminapi/doctor.roster/save` - -**请求参数**: -```json -{ - "id": 1, - "doctor_id": 1, - "date": "2026-03-02", - "period": "morning", - "status": 1, - "quota": 20, - "max_patients": 30, - "remark": "" -} -``` - -**说明**: -- `id` 存在时为更新,不存在时为新增 -- `status`: 1-出诊 2-停诊 3-休息 4-请假 -- `period`: morning-上午 afternoon-下午 - -### 3. 删除排班 - -**URL**: `POST /adminapi/doctor.roster/delete` - -**请求参数**: -```json -{ - "id": 1 -} -``` - -### 4. 排班详情 - -**URL**: `GET /adminapi/doctor.roster/detail` - -**请求参数**: -- `id`: 排班ID - -### 5. 批量保存排班 - -**URL**: `POST /adminapi/doctor.roster/batchSave` - -**请求参数**: -```json -{ - "rosters": [ - { - "doctor_id": 1, - "date": "2026-03-02", - "period": "morning", - "status": 1, - "quota": 20, - "max_patients": 30 - }, - { - "doctor_id": 1, - "date": "2026-03-02", - "period": "afternoon", - "status": 1, - "quota": 20, - "max_patients": 30 - } - ] -} -``` - -### 6. 复制排班 - -**URL**: `POST /adminapi/doctor.roster/copy` - -**请求参数**: -```json -{ - "source_start_date": "2026-03-02", - "source_end_date": "2026-03-08", - "target_start_date": "2026-03-09", - "doctor_id": 1 -} -``` - -**说明**: -- 将源日期范围的排班复制到目标日期 -- `doctor_id` 可选,不传则复制所有医生的排班 - -## 核心逻辑 - -### RosterLogic::save() - -保存排班逻辑: -1. 检查是否有 `id` 参数 -2. 有 `id` 则更新,无 `id` 则新增 -3. 自动设置 `create_time` 和 `update_time` - -### RosterLogic::batchSave() - -批量保存逻辑: -1. 开启事务 -2. 遍历排班数组 -3. 检查是否已存在(根据 doctor_id + date + period) -4. 存在则更新,不存在则新增 -5. 提交事务 - -### RosterLogic::copy() - -复制排班逻辑: -1. 获取源日期范围的排班数据 -2. 计算日期差 -3. 为每条排班创建新记录,日期加上日期差 -4. 检查目标日期是否已存在排班,避免重复 - -## 数据验证 - -### RosterValidate - -验证规则: -- `doctor_id`: 必填 -- `date`: 必填,日期格式 -- `period`: 必填,只能是 morning 或 afternoon -- `status`: 必填,只能是 1/2/3/4 -- `quota`: 数字 -- `max_patients`: 数字 - -## 模型特性 - -### Roster Model - -自动属性: -- `status_desc`: 状态描述(出诊/停诊/休息/请假) -- `period_desc`: 时段描述(上午/下午) -- `date_text`: 日期文本 - -软删除: -- 使用 `delete_time` 字段 -- 删除时不会真正删除记录,只是设置删除时间 - -## 注意事项 - -1. **唯一索引**: `doctor_id + date + period + delete_time` 确保同一医生同一天同一时段只有一条有效排班 - -2. **医生数据来源**: 医生数据来自 `zyt_admin` 表,`role_id=1` - -3. **时间格式**: - - 数据库存储使用整型时间戳 - - 日期字段使用 DATE 类型 - -4. **事务处理**: 批量操作和复制操作使用事务确保数据一致性 - -5. **错误处理**: 所有 Logic 方法都有异常捕获,失败时返回 false 并设置错误信息 - -## 测试建议 - -1. 测试新增排班 -2. 测试更新排班 -3. 测试删除排班 -4. 测试同一时段重复排班(应该失败) -5. 测试批量保存 -6. 测试复制排班 -7. 测试日期范围查询 diff --git a/server/ROSTER_DATE_SEARCH_FIX.md b/server/ROSTER_DATE_SEARCH_FIX.md deleted file mode 100644 index 5552d8fb..00000000 --- a/server/ROSTER_DATE_SEARCH_FIX.md +++ /dev/null @@ -1,142 +0,0 @@ -# 医生排班日期范围搜索修复 - -## 问题描述 - -访问 `adminapi/doctor.roster/lists?start_date=2026-03-02&end_date=2026-03-08` 时,`searchWhere` 为空,无法按日期范围筛选排班数据。 - -## 根本原因 - -`ListsSearchTrait` 中的 `between` 搜索类型只支持固定的参数名 `start` 和 `end`,而不支持自定义参数名如 `start_date` 和 `end_date`。 - -### 原始代码问题 - -```php -public function setSearch(): array -{ - return [ - '=' => ['doctor_id', 'period', 'status'], - 'between' => ['date' => ['start_date', 'end_date']], // ❌ 不会生效 - ]; -} -``` - -`ListsSearchTrait` 中的 `between` 处理逻辑: - -```php -case 'between': - if (empty($this->start) || empty($this->end)) { // 只检查 start 和 end - break; - } - $where[] = [$whereFields, 'between', [$this->start, $this->end]]; - break; -``` - -## 解决方案 - -在 `lists()` 和 `count()` 方法中手动处理日期范围搜索。 - -### 修复后的代码 - -```php -public function setSearch(): array -{ - return [ - '=' => ['doctor_id', 'period', 'status'], - ]; -} - -public function lists(): array -{ - $where = $this->searchWhere; - - // 处理日期范围搜索 - if (!empty($this->params['start_date']) && !empty($this->params['end_date'])) { - $where[] = ['date', 'between', [$this->params['start_date'], $this->params['end_date']]]; - } - - $lists = Roster::where($where) - ->field(['id', 'doctor_id', 'date', 'period', 'status', 'quota', 'max_patients', 'booked_count', 'remark', 'create_time', 'update_time']) - ->order(['date' => 'asc', 'period' => 'asc']) - ->limit($this->limitOffset, $this->limitLength) - ->select() - ->toArray(); - - return $lists; -} - -public function count(): int -{ - $where = $this->searchWhere; - - // 处理日期范围搜索 - if (!empty($this->params['start_date']) && !empty($this->params['end_date'])) { - $where[] = ['date', 'between', [$this->params['start_date'], $this->params['end_date']]]; - } - - return Roster::where($where)->count(); -} -``` - -## 测试 - -### 测试 URL - -``` -GET /adminapi/doctor.roster/lists?start_date=2026-03-02&end_date=2026-03-08 -``` - -### 预期结果 - -- ✅ 返回 2026-03-02 到 2026-03-08 之间的排班数据 -- ✅ `where` 条件包含日期范围:`['date', 'between', ['2026-03-02', '2026-03-08']]` - -### 其他搜索参数 - -``` -GET /adminapi/doctor.roster/lists?doctor_id=1&start_date=2026-03-02&end_date=2026-03-08 -GET /adminapi/doctor.roster/lists?period=1&status=1&start_date=2026-03-02&end_date=2026-03-08 -``` - -## 其他说明 - -### ListsSearchTrait 支持的搜索类型 - -1. **等值查询**:`=`, `<>`, `>`, `>=`, `<`, `<=`, `in` -2. **模糊查询**:`%like%`, `%like`, `like%` -3. **时间范围**:`between_time`(使用 `start_time` 和 `end_time`) -4. **范围查询**:`between`(使用 `start` 和 `end`) -5. **集合查询**:`find_in_set` - -### 如果要使用框架的 between - -需要修改前端传递的参数名: - -```javascript -// 前端修改 -params: { - start: '2026-03-02', // 改为 start - end: '2026-03-08' // 改为 end -} -``` - -```php -// 后端配置 -public function setSearch(): array -{ - return [ - '=' => ['doctor_id', 'period', 'status'], - 'between' => 'date', // 简化配置 - ]; -} -``` - -但这种方式不够语义化,不推荐使用。 - -## 已修复的文件 - -- ✅ `server/app/adminapi/lists/doctor/RosterLists.php` - ---- - -**修复日期:** 2024-03-04 -**状态:** ✅ 完成 diff --git a/server/TEST_BATCH_ROSTER.md b/server/TEST_BATCH_ROSTER.md deleted file mode 100644 index d2bc1160..00000000 --- a/server/TEST_BATCH_ROSTER.md +++ /dev/null @@ -1,395 +0,0 @@ -# 批量排班接口测试 - -## 接口信息 - -**URL**: `POST /adminapi/doctor.roster/batchSave` - -**Content-Type**: `application/json` - -## 测试用例 - -### 测试1:批量创建工作日排班 - -为2位医生创建一周工作日的排班(周一至周五,上午和下午)。 - -**请求体**: -```json -{ - "rosters": [ - { - "doctor_id": 1, - "date": "2026-03-02", - "period": "morning", - "status": 1, - "quota": 20, - "max_patients": 30, - "remark": "工作日排班" - }, - { - "doctor_id": 1, - "date": "2026-03-02", - "period": "afternoon", - "status": 1, - "quota": 20, - "max_patients": 30, - "remark": "工作日排班" - }, - { - "doctor_id": 1, - "date": "2026-03-03", - "period": "morning", - "status": 1, - "quota": 20, - "max_patients": 30, - "remark": "工作日排班" - }, - { - "doctor_id": 1, - "date": "2026-03-03", - "period": "afternoon", - "status": 1, - "quota": 20, - "max_patients": 30, - "remark": "工作日排班" - }, - { - "doctor_id": 2, - "date": "2026-03-02", - "period": "morning", - "status": 1, - "quota": 15, - "max_patients": 25, - "remark": "工作日排班" - }, - { - "doctor_id": 2, - "date": "2026-03-02", - "period": "afternoon", - "status": 1, - "quota": 15, - "max_patients": 25, - "remark": "工作日排班" - } - ] -} -``` - -**预期结果**: -```json -{ - "code": 1, - "msg": "批量保存成功", - "data": true -} -``` - -### 测试2:批量设置周末休息 - -为所有医生设置周末休息。 - -**请求体**: -```json -{ - "rosters": [ - { - "doctor_id": 1, - "date": "2026-03-07", - "period": "morning", - "status": 3, - "quota": 0, - "max_patients": 0, - "remark": "周末休息" - }, - { - "doctor_id": 1, - "date": "2026-03-07", - "period": "afternoon", - "status": 3, - "quota": 0, - "max_patients": 0, - "remark": "周末休息" - }, - { - "doctor_id": 1, - "date": "2026-03-08", - "period": "morning", - "status": 3, - "quota": 0, - "max_patients": 0, - "remark": "周末休息" - }, - { - "doctor_id": 1, - "date": "2026-03-08", - "period": "afternoon", - "status": 3, - "quota": 0, - "max_patients": 0, - "remark": "周末休息" - } - ] -} -``` - -### 测试3:更新已存在的排班 - -如果某个排班已存在,应该更新而不是创建新记录。 - -**第一次请求**: -```json -{ - "rosters": [ - { - "doctor_id": 1, - "date": "2026-03-04", - "period": "morning", - "status": 1, - "quota": 20, - "max_patients": 30 - } - ] -} -``` - -**第二次请求(更新)**: -```json -{ - "rosters": [ - { - "doctor_id": 1, - "date": "2026-03-04", - "period": "morning", - "status": 2, - "quota": 0, - "max_patients": 0, - "remark": "临时停诊" - } - ] -} -``` - -**预期结果**: 第二次请求应该更新第一次创建的记录,而不是创建新记录。 - -### 测试4:空数组 - -**请求体**: -```json -{ - "rosters": [] -} -``` - -**预期结果**: 应该返回成功,但不创建任何记录。 - -### 测试5:数据验证 - -**请求体(缺少必填字段)**: -```json -{ - "rosters": [ - { - "doctor_id": 1, - "date": "2026-03-04" - // 缺少 period 和 status - } - ] -} -``` - -**预期结果**: 应该返回验证错误。 - -## 使用 cURL 测试 - -### 基本测试 - -```bash -curl -X POST http://your-domain/adminapi/doctor.roster/batchSave \ - -H "Content-Type: application/json" \ - -H "token: your-token-here" \ - -d '{ - "rosters": [ - { - "doctor_id": 1, - "date": "2026-03-04", - "period": "morning", - "status": 1, - "quota": 20, - "max_patients": 30 - } - ] - }' -``` - -### 批量测试(10条记录) - -```bash -curl -X POST http://your-domain/adminapi/doctor.roster/batchSave \ - -H "Content-Type: application/json" \ - -H "token: your-token-here" \ - -d @batch_roster_data.json -``` - -**batch_roster_data.json**: -```json -{ - "rosters": [ - {"doctor_id": 1, "date": "2026-03-04", "period": "morning", "status": 1, "quota": 20, "max_patients": 30}, - {"doctor_id": 1, "date": "2026-03-04", "period": "afternoon", "status": 1, "quota": 20, "max_patients": 30}, - {"doctor_id": 1, "date": "2026-03-05", "period": "morning", "status": 1, "quota": 20, "max_patients": 30}, - {"doctor_id": 1, "date": "2026-03-05", "period": "afternoon", "status": 1, "quota": 20, "max_patients": 30}, - {"doctor_id": 1, "date": "2026-03-06", "period": "morning", "status": 1, "quota": 20, "max_patients": 30}, - {"doctor_id": 1, "date": "2026-03-06", "period": "afternoon", "status": 1, "quota": 20, "max_patients": 30}, - {"doctor_id": 1, "date": "2026-03-07", "period": "morning", "status": 3, "quota": 0, "max_patients": 0}, - {"doctor_id": 1, "date": "2026-03-07", "period": "afternoon", "status": 3, "quota": 0, "max_patients": 0}, - {"doctor_id": 1, "date": "2026-03-08", "period": "morning", "status": 3, "quota": 0, "max_patients": 0}, - {"doctor_id": 1, "date": "2026-03-08", "period": "afternoon", "status": 3, "quota": 0, "max_patients": 0} - ] -} -``` - -## 验证结果 - -### 1. 检查数据库 - -```sql --- 查看批量创建的排班 -SELECT * FROM la_doctor_roster -WHERE doctor_id = 1 - AND date BETWEEN '2026-03-04' AND '2026-03-08' -ORDER BY date, period; -``` - -### 2. 检查唯一索引 - -```sql --- 尝试插入重复记录,应该失败 -INSERT INTO la_doctor_roster (doctor_id, date, period, status, create_time, update_time) -VALUES (1, '2026-03-04', 'morning', 1, UNIX_TIMESTAMP(), UNIX_TIMESTAMP()); --- 错误: Duplicate entry '1-2026-03-04-morning' for key 'uk_doctor_date_period' -``` - -### 3. 检查更新逻辑 - -```sql --- 第一次插入 --- 第二次用相同的 doctor_id, date, period 但不同的 status --- 应该更新而不是插入新记录 -SELECT COUNT(*) FROM la_doctor_roster -WHERE doctor_id = 1 AND date = '2026-03-04' AND period = 'morning'; --- 应该返回 1,不是 2 -``` - -## 性能测试 - -### 测试100条记录 - -```bash -# 生成100条记录的JSON -php generate_test_data.php 100 > test_100.json - -# 测试 -time curl -X POST http://your-domain/adminapi/doctor.roster/batchSave \ - -H "Content-Type: application/json" \ - -H "token: your-token-here" \ - -d @test_100.json -``` - -**预期**: 应该在2秒内完成 - -### 测试500条记录 - -```bash -php generate_test_data.php 500 > test_500.json - -time curl -X POST http://your-domain/adminapi/doctor.roster/batchSave \ - -H "Content-Type: application/json" \ - -H "token: your-token-here" \ - -d @test_500.json -``` - -**预期**: 应该在10秒内完成 - -## 错误处理测试 - -### 1. 事务回滚测试 - -在批量数据中插入一条错误数据,整个批量操作应该回滚。 - -```json -{ - "rosters": [ - { - "doctor_id": 1, - "date": "2026-03-04", - "period": "morning", - "status": 1, - "quota": 20, - "max_patients": 30 - }, - { - "doctor_id": 999999, - "date": "2026-03-04", - "period": "morning", - "status": 1, - "quota": 20, - "max_patients": 30 - } - ] -} -``` - -**预期**: 如果第二条记录失败,第一条也不应该被保存。 - -### 2. 日期格式错误 - -```json -{ - "rosters": [ - { - "doctor_id": 1, - "date": "2026/03/04", - "period": "morning", - "status": 1, - "quota": 20, - "max_patients": 30 - } - ] -} -``` - -**预期**: 返回日期格式错误 - -## 常见问题排查 - -### 问题1: 批量保存失败 - -**检查**: -1. 数据库连接是否正常 -2. 表是否存在 -3. 字段类型是否匹配 -4. 唯一索引是否正确 - -### 问题2: 部分记录保存失败 - -**检查**: -1. 查看错误日志 -2. 检查是否有数据验证失败 -3. 检查是否有外键约束 - -### 问题3: 性能问题 - -**优化**: -1. 减少单次批量的记录数 -2. 添加数据库索引 -3. 使用批量插入而不是循环插入 - -## 总结 - -批量排班接口已经完整实现,包括: -- ✅ 批量创建排班 -- ✅ 批量更新排班 -- ✅ 事务处理 -- ✅ 数据验证 -- ✅ 错误处理 - -可以正常使用了! diff --git a/server/TEST_ROSTER_API.md b/server/TEST_ROSTER_API.md deleted file mode 100644 index 193b50b3..00000000 --- a/server/TEST_ROSTER_API.md +++ /dev/null @@ -1,101 +0,0 @@ -# 排班接口测试 - -## 测试前准备 - -1. 确保数据库已创建 `la_doctor_roster` 表 -2. 确保 `zyt_admin` 表中有 `role_id=1` 的用户(医生) - -## 测试步骤 - -### 1. 测试获取排班列表 - -```bash -GET /adminapi/doctor.roster/lists?start_date=2026-03-02&end_date=2026-03-08 -``` - -预期结果:返回空列表或已有的排班数据 - -### 2. 测试新增排班 - -```bash -POST /adminapi/doctor.roster/save -Content-Type: application/json - -{ - "doctor_id": 1, - "date": "2026-03-03", - "period": "morning", - "status": 1, - "quota": 20, - "max_patients": 30, - "remark": "测试排班" -} -``` - -预期结果:返回成功,包含新增的排班ID - -### 3. 测试更新排班 - -```bash -POST /adminapi/doctor.roster/save -Content-Type: application/json - -{ - "id": 1, - "doctor_id": 1, - "date": "2026-03-03", - "period": "morning", - "status": 2, - "quota": 0, - "max_patients": 0, - "remark": "临时停诊" -} -``` - -预期结果:返回成功,排班状态更新为停诊 - -### 4. 测试删除排班 - -```bash -POST /adminapi/doctor.roster/delete -Content-Type: application/json - -{ - "id": 1 -} -``` - -预期结果:返回成功,排班被软删除 - -## 常见问题 - -### 1. 控制器不存在 - -错误:`控制器不存在:\\app\\adminapi\\controller\\doctor\\RosterController` - -解决:确保文件路径正确,文件名大小写匹配 - -### 2. 类不存在 - -错误:`Class 'app\common\model\doctor\Roster' not found` - -解决:检查 Model 文件是否创建,命名空间是否正确 - -### 3. 表不存在 - -错误:`Table 'database.la_doctor_roster' doesn't exist` - -解决:执行 SQL 创建表 - -### 4. 唯一索引冲突 - -错误:`Duplicate entry '1-2026-03-03-morning' for key 'uk_doctor_date_period'` - -说明:同一医生同一天同一时段已有排班,这是正常的约束 - -## 调试技巧 - -1. 查看日志文件:`server/runtime/log/` -2. 开启调试模式:`.env` 中设置 `APP_DEBUG=true` -3. 使用 Postman 或类似工具测试接口 -4. 检查数据库表结构是否正确 diff --git a/server/add_qrcode_methods.php b/server/add_qrcode_methods.php deleted file mode 100644 index 9315e288..00000000 --- a/server/add_qrcode_methods.php +++ /dev/null @@ -1,271 +0,0 @@ - $qrcodeUrl, - 'diagnosis_id' => $diagnosisId, - 'patient_id' => $patientId - ]; - } catch (\Exception $e) { - self::setError($e->getMessage()); - return false; - } - } - - /** - * @notes 获取小程序配置 - * @return array|null - */ - private static function getMiniProgramConfig(): ?array - { - try { - // 从配置表获取小程序配置 - $appId = \app\common\service\ConfigService::get('mnp_setting', 'app_id', ''); - $appSecret = \app\common\service\ConfigService::get('mnp_setting', 'app_secret', ''); - - if (empty($appId) || empty($appSecret)) { - return null; - } - - return [ - 'app_id' => $appId, - 'app_secret' => $appSecret - ]; - } catch (\Exception $e) { - return null; - } - } - - /** - * @notes 生成微信小程序码 - * @param array $config - * @param string $page - * @param string $scene - * @return string|false - */ - private static function generateWxQrcode(array $config, string $page, string $scene) - { - try { - // 记录请求参数 - \think\facade\Log::info('开始生成小程序码', [ - 'page' => $page, - 'scene' => $scene, - 'app_id' => $config['app_id'] - ]); - - // 获取access_token - $accessToken = self::getWxAccessToken($config['app_id'], $config['app_secret']); - if (!$accessToken) { - throw new \Exception('获取access_token失败: ' . self::getError()); - } - - // 调用微信接口生成小程序码 - $url = "https://api.weixin.qq.com/wxa/getwxacodeunlimit?access_token={$accessToken}"; - - $data = [ - 'scene' => $scene, - 'page' => $page, - 'check_path' => false, - 'env_version' => 'release', - 'width' => 280 - ]; - - \think\facade\Log::info('调用微信API生成小程序码', ['data' => $data]); - - $response = self::httpPost($url, json_encode($data)); - - // 检查是否返回错误(JSON格式) - $result = json_decode($response, true); - if (is_array($result) && isset($result['errcode']) && $result['errcode'] != 0) { - $errorMsg = "生成小程序码失败: errcode={$result['errcode']}, errmsg=" . ($result['errmsg'] ?? '未知错误'); - \think\facade\Log::error($errorMsg); - - // 如果是AccessToken错误,清除缓存 - if ($result['errcode'] == 40001 || $result['errcode'] == 42001) { - $cacheKey = 'wx_access_token_' . $config['app_id']; - cache($cacheKey, null); - \think\facade\Log::info('已清除无效的AccessToken缓存'); - } - - throw new \Exception($errorMsg); - } - - // 如果不是JSON错误,说明返回的是图片二进制数据 - // 保存图片 - $filename = 'qrcode_' . $config['app_id'] . '_' . md5($scene) . '_' . time() . '.png'; - $savePath = 'uploads/qrcode/' . date('Ymd') . '/'; - $fullPath = public_path() . $savePath; - - if (!is_dir($fullPath)) { - mkdir($fullPath, 0755, true); - } - - $saved = file_put_contents($fullPath . $filename, $response); - - if (!$saved) { - throw new \Exception('保存二维码图片失败'); - } - - $qrcodeUrl = request()->domain() . '/' . $savePath . $filename; - \think\facade\Log::info('小程序码生成成功', ['url' => $qrcodeUrl]); - - return $qrcodeUrl; - } catch (\Exception $e) { - \think\facade\Log::error('generateWxQrcode异常: ' . $e->getMessage()); - self::setError($e->getMessage()); - return false; - } - } - - /** - * @notes 获取微信access_token - * @param string $appId - * @param string $appSecret - * @return string|false - */ - private static function getWxAccessToken(string $appId, string $appSecret) - { - try { - // 尝试从缓存获取 - $cacheKey = 'wx_access_token_' . $appId; - $accessToken = cache($cacheKey); - - if ($accessToken) { - return $accessToken; - } - - // 从微信服务器获取 - $url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid={$appId}&secret={$appSecret}"; - $response = self::httpGet($url); - - // 记录原始响应用于调试 - \think\facade\Log::info('微信AccessToken响应: ' . $response); - - $result = json_decode($response, true); - - // 检查是否有错误 - if (isset($result['errcode']) && $result['errcode'] != 0) { - $errorMsg = "获取AccessToken失败: errcode={$result['errcode']}, errmsg=" . ($result['errmsg'] ?? '未知错误'); - \think\facade\Log::error($errorMsg); - throw new \Exception($errorMsg); - } - - if (isset($result['access_token'])) { - // 缓存access_token,有效期7000秒(微信官方7200秒,提前200秒过期) - cache($cacheKey, $result['access_token'], 7000); - \think\facade\Log::info('AccessToken获取成功并已缓存'); - return $result['access_token']; - } - - throw new \Exception('获取access_token失败: 响应中没有access_token字段'); - } catch (\Exception $e) { - \think\facade\Log::error('getWxAccessToken异常: ' . $e->getMessage()); - self::setError($e->getMessage()); - return false; - } - } - - /** - * @notes HTTP GET请求 - * @param string $url - * @return string|false - */ - private static function httpGet(string $url) - { - $ch = curl_init(); - curl_setopt($ch, CURLOPT_URL, $url); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); - curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); - curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); - $response = curl_exec($ch); - curl_close($ch); - return $response; - } - - /** - * @notes HTTP POST请求 - * @param string $url - * @param string $data - * @return string|false - */ - private static function httpPost(string $url, string $data) - { - $ch = curl_init(); - curl_setopt($ch, CURLOPT_URL, $url); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); - curl_setopt($ch, CURLOPT_POST, 1); - curl_setopt($ch, CURLOPT_POSTFIELDS, $data); - curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); - curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); - curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']); - $response = curl_exec($ch); - curl_close($ch); - return $response; - } -PHP; - -// 在最后一个}之前插入方法 -$lastBrace = strrpos($content, '}'); -if ($lastBrace !== false) { - $content = substr($content, 0, $lastBrace) . $methods . "\n}"; -} else { - echo "错误:找不到类的结束括号\n"; - exit(1); -} - -// 写回文件 -file_put_contents($file, $content); - -echo "✓ 方法添加成功\n"; -echo "已添加以下方法:\n"; -echo " - generateMiniProgramQrcode()\n"; -echo " - getMiniProgramConfig()\n"; -echo " - generateWxQrcode()\n"; -echo " - getWxAccessToken()\n"; -echo " - httpGet()\n"; -echo " - httpPost()\n"; diff --git a/server/check_logistics_config.php b/server/check_logistics_config.php deleted file mode 100644 index dfc12797..00000000 --- a/server/check_logistics_config.php +++ /dev/null @@ -1,90 +0,0 @@ - 'shunfeng', - 'num' => $testNumber, - 'phone' => $phone, - 'resultv2' => '1', -]; - -$paramJson = json_encode($paramArr, JSON_UNESCAPED_UNICODE); -$sign = strtoupper(md5($paramJson . $key . $customer)); - -$postData = http_build_query([ - 'customer' => $customer, - 'param' => $paramJson, - 'sign' => $sign, -]); - -echo "请求参数已生成\n"; -echo "Customer: " . substr($customer, 0, 8) . "****\n"; -echo "Sign: " . substr($sign, 0, 8) . "****\n\n"; - -echo "如需测试实际查询,请:\n"; -echo "1. 将上面的 testNumber 替换为真实运单号\n"; -echo "2. 将 phone 替换为收件人手机后4位\n"; -echo "3. 在浏览器或前端系统中测试查询功能\n\n"; - -echo "===========================================\n"; -echo "检查完成\n"; -echo "===========================================\n"; diff --git a/server/check_tracking_status.php b/server/check_tracking_status.php deleted file mode 100644 index d5da5e6f..00000000 --- a/server/check_tracking_status.php +++ /dev/null @@ -1,91 +0,0 @@ -setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); -$pdo->exec("USE `{$database}`"); - -echo "========================================\n"; -echo "物流追踪记录状态检查\n"; -echo "========================================\n\n"; - -// 查询所有记录 -$stmt = $pdo->query(" - SELECT - id, - tracking_number, - express_company, - current_state, - current_state_text, - is_signed, - auto_update, - next_update_time, - FROM_UNIXTIME(next_update_time) as next_update_datetime, - UNIX_TIMESTAMP() as now_timestamp, - FROM_UNIXTIME(UNIX_TIMESTAMP()) as now_datetime - FROM zyt_express_tracking - WHERE delete_time IS NULL -"); - -$records = $stmt->fetchAll(PDO::FETCH_ASSOC); - -if (empty($records)) { - echo "没有找到任何物流追踪记录\n"; - exit; -} - -echo "找到 " . count($records) . " 条记录:\n\n"; - -foreach ($records as $record) { - echo "快递单号: {$record['tracking_number']}\n"; - echo "快递公司: {$record['express_company']}\n"; - echo "当前状态: {$record['current_state']} - {$record['current_state_text']}\n"; - echo "是否签收: " . ($record['is_signed'] ? '是' : '否') . "\n"; - echo "自动更新: " . ($record['auto_update'] ? '启用' : '禁用') . "\n"; - echo "下次更新时间: {$record['next_update_datetime']} ({$record['next_update_time']})\n"; - echo "当前时间: {$record['now_datetime']} ({$record['now_timestamp']})\n"; - - $shouldUpdate = $record['auto_update'] == 1 - && $record['next_update_time'] <= $record['now_timestamp'] - && $record['is_signed'] == 0 - && !in_array($record['current_state'], ['3', '4', '5']); - - echo "是否应该更新: " . ($shouldUpdate ? '是' : '否') . "\n"; - - if (!$shouldUpdate) { - echo "不更新原因: "; - if ($record['auto_update'] != 1) echo "自动更新未启用 "; - if ($record['next_update_time'] > $record['now_timestamp']) echo "未到更新时间 "; - if ($record['is_signed'] == 1) echo "已签收 "; - if (in_array($record['current_state'], ['3', '4', '5'])) echo "状态为终态 "; - echo "\n"; - } - - echo "\n" . str_repeat('-', 60) . "\n\n"; -} - -// 查询符合更新条件的记录数 -$stmt = $pdo->query(" - SELECT COUNT(*) as count - FROM zyt_express_tracking - WHERE auto_update = 1 - AND next_update_time <= UNIX_TIMESTAMP() - AND is_signed = 0 - AND current_state NOT IN ('3', '4', '5') - AND delete_time IS NULL -"); - -$result = $stmt->fetch(PDO::FETCH_ASSOC); -echo "符合自动更新条件的记录数: {$result['count']}\n"; diff --git a/server/clear_wx_token_cache.php b/server/clear_wx_token_cache.php deleted file mode 100644 index 59d1c97c..00000000 --- a/server/clear_wx_token_cache.php +++ /dev/null @@ -1,97 +0,0 @@ -initialize(); - -echo "=== 清除微信AccessToken缓存 ===\n\n"; - -// 获取小程序配置 -$appId = \app\common\service\ConfigService::get('mnp_setting', 'app_id', ''); - -if (empty($appId)) { - echo "✗ 错误: 未配置小程序AppID\n"; - echo "请先在后台配置小程序信息\n"; - exit(1); -} - -echo "小程序AppID: {$appId}\n"; - -// 清除缓存 -$cacheKey = 'wx_access_token_' . $appId; -$oldToken = cache($cacheKey); - -if ($oldToken) { - echo "旧Token: {$oldToken}\n"; - cache($cacheKey, null); - echo "✓ 已清除缓存的AccessToken\n"; -} else { - echo "✓ 缓存中没有AccessToken\n"; -} - -// 尝试重新获取 -echo "\n=== 尝试重新获取AccessToken ===\n\n"; - -$appSecret = \app\common\service\ConfigService::get('mnp_setting', 'app_secret', ''); - -if (empty($appSecret)) { - echo "✗ 错误: 未配置小程序AppSecret\n"; - exit(1); -} - -$url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid={$appId}&secret={$appSecret}"; - -$ch = curl_init(); -curl_setopt($ch, CURLOPT_URL, $url); -curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); -curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); -curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); -$response = curl_exec($ch); -$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); -curl_close($ch); - -echo "HTTP状态码: {$httpCode}\n"; -echo "响应内容: {$response}\n\n"; - -$result = json_decode($response, true); - -if (isset($result['access_token'])) { - echo "✓ AccessToken获取成功!\n"; - echo "Token: {$result['access_token']}\n"; - echo "有效期: {$result['expires_in']}秒\n"; - - // 缓存新Token - cache($cacheKey, $result['access_token'], 7000); - echo "\n✓ 已缓存新的AccessToken\n"; - - echo "\n=== 操作完成 ===\n"; - echo "现在可以重新尝试生成小程序码了\n"; -} else { - echo "✗ AccessToken获取失败!\n"; - if (isset($result['errcode'])) { - echo "错误码: {$result['errcode']}\n"; - echo "错误信息: {$result['errmsg']}\n\n"; - - // 常见错误提示 - switch ($result['errcode']) { - case 40013: - echo "提示: AppID无效,请检查配置\n"; - break; - case 40125: - echo "提示: AppSecret无效,请检查配置或重置AppSecret\n"; - break; - case 40164: - echo "提示: IP不在白名单中,请在微信公众平台添加服务器IP\n"; - break; - default: - echo "请参考微信官方文档: https://developers.weixin.qq.com/miniprogram/dev/framework/usability/PublicErrno.html\n"; - } - } - exit(1); -} diff --git a/server/debug_kuaidi100.php b/server/debug_kuaidi100.php deleted file mode 100644 index 735c50a1..00000000 --- a/server/debug_kuaidi100.php +++ /dev/null @@ -1,164 +0,0 @@ - '京东快递', - 'com' => 'jingdong', // 修正为正确的编码 - 'num' => 'JD0230761381812', - 'phone' => '' - ], - [ - 'name' => '极兔速递', - 'com' => 'jtexpress', - 'num' => 'JT5472795424064', - 'phone' => '' - ], -]; - -foreach ($testCases as $test) { - echo "测试: {$test['name']}\n"; - echo "-------------------------------------------\n"; - echo "快递公司编码: {$test['com']}\n"; - echo "运单号: {$test['num']}\n\n"; - - $paramArr = [ - 'com' => $test['com'], - 'num' => $test['num'], - 'resultv2' => '1', - ]; - - if ($test['phone'] !== '') { - $paramArr['phone'] = $test['phone']; - } - - $paramJson = json_encode($paramArr, JSON_UNESCAPED_UNICODE); - $sign = strtoupper(md5($paramJson . $key . $customer)); - - $postBody = http_build_query([ - 'customer' => $customer, - 'param' => $paramJson, - 'sign' => $sign, - ]); - - echo "请求参数:\n"; - echo " customer: " . substr($customer, 0, 8) . "****\n"; - echo " param: {$paramJson}\n"; - echo " sign: " . substr($sign, 0, 16) . "****\n\n"; - - // 发送请求 - $url = 'https://poll.kuaidi100.com/poll/query.do'; - - $ch = curl_init(); - curl_setopt($ch, CURLOPT_URL, $url); - curl_setopt($ch, CURLOPT_POST, true); - curl_setopt($ch, CURLOPT_POSTFIELDS, $postBody); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - curl_setopt($ch, CURLOPT_TIMEOUT, 15); - curl_setopt($ch, CURLOPT_HTTPHEADER, [ - 'Content-Type: application/x-www-form-urlencoded', - ]); - curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); - curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); - - $response = curl_exec($ch); - $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); - $error = curl_error($ch); - curl_close($ch); - - echo "HTTP状态码: {$httpCode}\n"; - - if ($error) { - echo "CURL错误: {$error}\n\n"; - continue; - } - - if ($response === false || $response === '') { - echo "错误: 无响应\n\n"; - continue; - } - - echo "原始响应:\n"; - echo $response . "\n\n"; - - $json = json_decode($response, true); - if (!is_array($json)) { - echo "错误: 无法解析JSON\n\n"; - continue; - } - - echo "解析后的响应:\n"; - echo " result: " . var_export($json['result'] ?? null, true) . "\n"; - echo " message: " . ($json['message'] ?? 'N/A') . "\n"; - echo " returnCode: " . ($json['returnCode'] ?? 'N/A') . "\n"; - - if (isset($json['data']) && is_array($json['data'])) { - echo " data: " . count($json['data']) . " 条轨迹记录\n"; - if (count($json['data']) > 0) { - echo "\n 最新轨迹:\n"; - $latest = $json['data'][0]; - echo " 时间: " . ($latest['ftime'] ?? $latest['time'] ?? 'N/A') . "\n"; - echo " 内容: " . ($latest['context'] ?? 'N/A') . "\n"; - } - } else { - echo " data: 无数据\n"; - } - - echo "\n"; -} - -echo "===========================================\n"; -echo "调试完成\n"; -echo "===========================================\n\n"; - -echo "常见错误码说明:\n"; -echo "-------------------------------------------\n"; -echo "200: 查询成功\n"; -echo "500: 服务器错误\n"; -echo "501: 重复提交\n"; -echo "600: 您不是合法的订阅者\n"; -echo "601: SIGN签名错误\n"; -echo "700: 订阅方的订阅数据已达到上限\n"; -echo "701: 快递公司参数异常\n"; -echo "702: 快递单号参数异常\n"; -echo "703: 查询无结果\n"; -echo "704: 快递公司识别失败\n\n"; - -echo "如果返回 '找不到对应公司',可能原因:\n"; -echo "1. 快递公司编码错误(如:jd 应该是 jingdong)\n"; -echo "2. 快递100不支持该快递公司\n"; -echo "3. 需要使用快递100的自动识别功能\n\n"; diff --git a/server/install_tracking_tables.php b/server/install_tracking_tables.php deleted file mode 100644 index e235331a..00000000 --- a/server/install_tracking_tables.php +++ /dev/null @@ -1,70 +0,0 @@ -setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); - -// 选择数据库 -$pdo->exec("USE `{$database}`"); - -echo "开始创建物流追踪表...\n\n"; - -// 读取SQL文件 -$sql = file_get_contents(__DIR__ . '/database/migrations/create_express_tracking_tables.sql'); - -// 分割SQL语句 -$statements = array_filter( - array_map('trim', explode(';', $sql)), - function($stmt) { - return !empty($stmt) && - !preg_match('/^--/', $stmt) && - !preg_match('/^\/\*/', $stmt); - } -); - -$success = 0; -$failed = 0; - -foreach ($statements as $statement) { - try { - $pdo->exec($statement); - - // 提取表名 - if (preg_match('/CREATE TABLE.*?`(\w+)`/i', $statement, $matches)) { - echo "✓ 创建表: {$matches[1]}\n"; - $success++; - } - } catch (PDOException $e) { - if (strpos($e->getMessage(), 'already exists') !== false) { - if (preg_match('/CREATE TABLE.*?`(\w+)`/i', $statement, $matches)) { - echo "- 表已存在: {$matches[1]}\n"; - } - } else { - echo "✗ 错误: {$e->getMessage()}\n"; - $failed++; - } - } -} - -echo "\n========================================\n"; -echo "安装完成!\n"; -echo "========================================\n"; -echo "成功: {$success}\n"; -echo "失败: {$failed}\n"; -echo "\n"; -echo "下一步:\n"; -echo " php think express:sync # 同步现有快递单号\n"; -echo " php think express:auto-update # 测试自动更新\n"; -echo "\n"; diff --git a/server/sql/1.9.20260420/ADD_DOSE_UNIT_TO_ORDER_SUMMARY.md b/server/sql/1.9.20260420/ADD_DOSE_UNIT_TO_ORDER_SUMMARY.md new file mode 100644 index 00000000..bd9abfe7 --- /dev/null +++ b/server/sql/1.9.20260420/ADD_DOSE_UNIT_TO_ORDER_SUMMARY.md @@ -0,0 +1,75 @@ +# 添加剂量单位和剂数字段到订单编辑表单 + +## 任务概述 +在处方订单编辑表单中添加 `dose_unit`(剂量单位)和 `dose_count`(剂数)字段,支持用户选择和编辑。 + +## 实现内容 + +### 1. 前端表单字段(admin/src/views/consumer/prescription/order_list.vue) + +#### 添加的表单项: +- **剂量单位** (`dose_unit`): 下拉选择框,7个选项 + - 剂、丸、袋、盒、瓶、膏、贴 + - 默认值:剂 + +- **剂数** (`dose_count`): 数字输入框 + - 最小值:1 + - 默认值:1 + +#### 修改位置: +- 表单定义:在 `medication_days` 字段后添加 +- 数据加载:`openEdit()` 函数中加载 `dose_count` 和 `dose_unit` +- 数据提交:`submitEdit()` 函数中包含 `dose_count` 和 `dose_unit` + +### 2. 后端逻辑(server/app/adminapi/logic/tcm/PrescriptionOrderLogic.php) + +#### 修改的方法: +- `edit()` 方法添加字段处理: + ```php + $order->dose_unit = (string) ($params['dose_unit'] ?? '剂'); + $order->dose_count = isset($params['dose_count']) && (int)$params['dose_count'] > 0 ? (int)$params['dose_count'] : 1; + ``` + +### 3. 数据库迁移(add_dose_unit_to_prescription_order.sql) + +#### 添加的字段: +```sql +ALTER TABLE `zyt_tcm_prescription_order` +ADD COLUMN `dose_unit` varchar(20) NOT NULL DEFAULT '剂' COMMENT '剂量单位:剂、丸、袋、盒、瓶、膏、贴' AFTER `medication_days`, +ADD COLUMN `dose_count` int(11) NOT NULL DEFAULT 1 COMMENT '剂数' AFTER `dose_unit`; +``` + +## 字段说明 + +| 字段名 | 类型 | 默认值 | 说明 | +|--------|------|--------|------| +| dose_unit | varchar(20) | 剂 | 剂量单位:剂、丸、袋、盒、瓶、膏、贴 | +| dose_count | int(11) | 1 | 剂数,最小值为1 | + +## 部署步骤 + +1. **执行数据库迁移**: + ```bash + mysql -u用户名 -p数据库名 < add_dose_unit_to_prescription_order.sql + ``` + +2. **部署后端代码**: + - 更新 `server/app/adminapi/logic/tcm/PrescriptionOrderLogic.php` + +3. **部署前端代码**: + - 更新 `admin/src/views/consumer/prescription/order_list.vue` + - 重新构建前端:`npm run build` + +## 测试要点 + +- [ ] 创建新订单时,`dose_unit` 默认为"剂",`dose_count` 默认为1 +- [ ] 编辑订单时,能正确加载已保存的 `dose_unit` 和 `dose_count` 值 +- [ ] 修改 `dose_unit` 和 `dose_count` 后能正确保存 +- [ ] 验证 `dose_count` 不能小于1 +- [ ] 验证所有7个剂量单位选项都能正常选择和保存 + +## 相关文件 + +- `admin/src/views/consumer/prescription/order_list.vue` - 前端订单列表和编辑表单 +- `server/app/adminapi/logic/tcm/PrescriptionOrderLogic.php` - 后端订单编辑逻辑 +- `add_dose_unit_to_prescription_order.sql` - 数据库迁移脚本 diff --git a/server/sql/1.9.20260420/ADD_MISSING_FIELDS.sql b/server/sql/1.9.20260420/ADD_MISSING_FIELDS.sql new file mode 100644 index 00000000..b84f0c0d --- /dev/null +++ b/server/sql/1.9.20260420/ADD_MISSING_FIELDS.sql @@ -0,0 +1,26 @@ +-- 添加缺失的字段 +-- 请在数据库管理工具中执行以下SQL + +-- 1. 添加 usage_days(服用天数)- 在 dose_unit 之后 +ALTER TABLE `zyt_tcm_prescription` ADD COLUMN `usage_days` int(11) NOT NULL DEFAULT 7 COMMENT '服用天数' AFTER `dose_unit`; + +-- 2. 添加 usage_time(服用时间)- 在 usage_instruction 之后 +ALTER TABLE `zyt_tcm_prescription` ADD COLUMN `usage_time` varchar(50) NOT NULL DEFAULT '饭前' COMMENT '服用时间:饭前、饭后、饭中、空腹、睡前、晨起、随时' AFTER `usage_instruction`; + +-- 3. 添加 usage_way(服用方式)- 在 usage_time 之后 +ALTER TABLE `zyt_tcm_prescription` ADD COLUMN `usage_way` varchar(50) NOT NULL DEFAULT '温水送服' COMMENT '服用方式:温水送服、开水冲服、黄酒送服等' AFTER `usage_time`; + +-- 4. 添加 dietary_taboo(忌口)- 在 usage_way 之后 +ALTER TABLE `zyt_tcm_prescription` ADD COLUMN `dietary_taboo` varchar(200) NOT NULL DEFAULT '' COMMENT '忌口(逗号分隔):辛辣食物、生冷食物、油腻食物等' AFTER `usage_way`; + +-- 5. 添加 usage_notes(其他服用说明)- 在 dietary_taboo 之后 +ALTER TABLE `zyt_tcm_prescription` ADD COLUMN `usage_notes` varchar(200) NOT NULL DEFAULT '' COMMENT '其他服用说明' AFTER `dietary_taboo`; + +-- 6. 添加 is_shared(是否共享)- 在 template_id 之后 +ALTER TABLE `zyt_tcm_prescription` ADD COLUMN `is_shared` tinyint(1) NOT NULL DEFAULT 0 COMMENT '是否共享:0-不共享(仅自己可见) 1-共享(所有人可见)' AFTER `template_id`; + +-- 执行完成后,查看表结构确认 +SELECT COLUMN_NAME, DATA_TYPE, COLUMN_DEFAULT, COLUMN_COMMENT +FROM information_schema.COLUMNS +WHERE TABLE_SCHEMA = 'zyt' AND TABLE_NAME = 'zyt_tcm_prescription' +ORDER BY ORDINAL_POSITION; diff --git a/server/sql/1.9.20260420/FINAL_FIX.sql b/server/sql/1.9.20260420/FINAL_FIX.sql new file mode 100644 index 00000000..d80a36c1 --- /dev/null +++ b/server/sql/1.9.20260420/FINAL_FIX.sql @@ -0,0 +1,28 @@ +-- 最终修复SQL - 根据实际表结构添加字段 +-- 请在数据库管理工具中执行 + +-- 1. 在 dose_unit 后面添加 usage_days(服用天数) +ALTER TABLE `zyt_tcm_prescription` ADD COLUMN `usage_days` int(11) NOT NULL DEFAULT 7 COMMENT '服用天数' AFTER `dose_unit`; + +-- 2. 在 usage_instruction 后面添加 usage_time(服用时间) +ALTER TABLE `zyt_tcm_prescription` ADD COLUMN `usage_time` varchar(50) NOT NULL DEFAULT '饭前' COMMENT '服用时间:饭前、饭后、饭中、空腹、睡前、晨起、随时' AFTER `usage_instruction`; + +-- 3. 在 usage_time 后面添加 usage_way(服用方式) +ALTER TABLE `zyt_tcm_prescription` ADD COLUMN `usage_way` varchar(50) NOT NULL DEFAULT '温水送服' COMMENT '服用方式:温水送服、开水冲服、黄酒送服等' AFTER `usage_time`; + +-- 4. 在 usage_way 后面添加 dietary_taboo(忌口) +ALTER TABLE `zyt_tcm_prescription` ADD COLUMN `dietary_taboo` varchar(200) NOT NULL DEFAULT '' COMMENT '忌口(逗号分隔)' AFTER `usage_way`; + +-- 5. 在 dietary_taboo 后面添加 usage_notes(其他说明) +ALTER TABLE `zyt_tcm_prescription` ADD COLUMN `usage_notes` varchar(200) NOT NULL DEFAULT '' COMMENT '其他服用说明' AFTER `dietary_taboo`; + +-- 6. 在 template_id 后面添加 is_shared(是否共享) +ALTER TABLE `zyt_tcm_prescription` ADD COLUMN `is_shared` tinyint(1) NOT NULL DEFAULT 0 COMMENT '是否共享:0-不共享 1-共享' AFTER `template_id`; + +-- 验证字段是否添加成功 +SELECT COLUMN_NAME, DATA_TYPE, COLUMN_DEFAULT, COLUMN_COMMENT +FROM information_schema.COLUMNS +WHERE TABLE_SCHEMA = 'zyt' + AND TABLE_NAME = 'zyt_tcm_prescription' + AND COLUMN_NAME IN ('usage_days', 'usage_time', 'usage_way', 'dietary_taboo', 'usage_notes', 'is_shared') +ORDER BY ORDINAL_POSITION; diff --git a/server/sql/1.9.20260420/QUICK_FIX_SQL.sql b/server/sql/1.9.20260420/QUICK_FIX_SQL.sql new file mode 100644 index 00000000..ca77340f --- /dev/null +++ b/server/sql/1.9.20260420/QUICK_FIX_SQL.sql @@ -0,0 +1,23 @@ +-- 快速修复SQL - 只添加缺失的字段 +-- 如果某个字段已存在会报错,但不影响其他字段的添加,可以忽略错误继续执行 + +-- 检查当前表结构 +-- SHOW COLUMNS FROM `zyt_tcm_prescription`; + +-- 根据错误提示,prescription_type 已存在,所以跳过 +-- 只添加缺失的字段: + +-- 服用时间(如果不存在) +ALTER TABLE `zyt_tcm_prescription` ADD COLUMN `usage_time` varchar(50) NOT NULL DEFAULT '饭前' COMMENT '服用时间:饭前、饭后、饭中、空腹、睡前、晨起、随时' AFTER `usage_instruction`; + +-- 服用方式(如果不存在) +ALTER TABLE `zyt_tcm_prescription` ADD COLUMN `usage_way` varchar(50) NOT NULL DEFAULT '温水送服' COMMENT '服用方式:温水送服、开水冲服、黄酒送服等' AFTER `usage_time`; + +-- 忌口(如果不存在) +ALTER TABLE `zyt_tcm_prescription` ADD COLUMN `dietary_taboo` varchar(200) NOT NULL DEFAULT '' COMMENT '忌口(逗号分隔):辛辣食物、生冷食物、油腻食物等' AFTER `usage_way`; + +-- 其他服用说明(如果不存在) +ALTER TABLE `zyt_tcm_prescription` ADD COLUMN `usage_notes` varchar(200) NOT NULL DEFAULT '' COMMENT '其他服用说明' AFTER `dietary_taboo`; + +-- 完成后查看表结构确认 +-- SHOW COLUMNS FROM `zyt_tcm_prescription`; diff --git a/server/sql/1.9.20260420/TEST_PRESCRIPTION_LIBRARY.sql b/server/sql/1.9.20260420/TEST_PRESCRIPTION_LIBRARY.sql new file mode 100644 index 00000000..bc7f6a2e --- /dev/null +++ b/server/sql/1.9.20260420/TEST_PRESCRIPTION_LIBRARY.sql @@ -0,0 +1,32 @@ +-- 测试处方库功能的SQL语句 + +-- 1. 查看表结构 +DESC zyt_prescription_library; + +-- 2. 插入测试数据 +INSERT INTO `zyt_prescription_library` +(`prescription_name`, `herbs`, `is_public`, `creator_id`, `creator_name`, `create_time`, `update_time`) +VALUES +('六味地黄丸', '[{"name":"熟地黄","dosage":24},{"name":"山茱萸","dosage":12},{"name":"山药","dosage":12},{"name":"泽泻","dosage":9},{"name":"牡丹皮","dosage":9},{"name":"茯苓","dosage":9}]', 1, 1, '测试医生', UNIX_TIMESTAMP(), UNIX_TIMESTAMP()), +('补中益气汤', '[{"name":"黄芪","dosage":15},{"name":"党参","dosage":10},{"name":"白术","dosage":10},{"name":"当归","dosage":10},{"name":"陈皮","dosage":6},{"name":"升麻","dosage":6},{"name":"柴胡","dosage":6},{"name":"甘草","dosage":5}]', 1, 1, '测试医生', UNIX_TIMESTAMP(), UNIX_TIMESTAMP()), +('逍遥散', '[{"name":"柴胡","dosage":10},{"name":"当归","dosage":10},{"name":"白芍","dosage":10},{"name":"白术","dosage":10},{"name":"茯苓","dosage":10},{"name":"薄荷","dosage":6},{"name":"生姜","dosage":6},{"name":"甘草","dosage":5}]', 0, 1, '测试医生', UNIX_TIMESTAMP(), UNIX_TIMESTAMP()); + +-- 3. 查询所有处方 +SELECT * FROM zyt_prescription_library WHERE delete_time IS NULL; + +-- 4. 查询公开的处方 +SELECT * FROM zyt_prescription_library WHERE is_public = 1 AND delete_time IS NULL; + +-- 5. 查询某个医生创建的处方 +SELECT * FROM zyt_prescription_library WHERE creator_id = 1 AND delete_time IS NULL; + +-- 6. 统计处方数量 +SELECT + COUNT(*) as total, + SUM(CASE WHEN is_public = 1 THEN 1 ELSE 0 END) as public_count, + SUM(CASE WHEN is_public = 0 THEN 1 ELSE 0 END) as private_count +FROM zyt_prescription_library +WHERE delete_time IS NULL; + +-- 7. 清理测试数据(可选) +-- DELETE FROM zyt_prescription_library WHERE creator_name = '测试医生'; diff --git a/server/sql/1.9.20260420/UPDATE_OLD_DATA.sql b/server/sql/1.9.20260420/UPDATE_OLD_DATA.sql new file mode 100644 index 00000000..0c6d6f05 --- /dev/null +++ b/server/sql/1.9.20260420/UPDATE_OLD_DATA.sql @@ -0,0 +1,18 @@ +-- 更新旧数据,为新字段设置默认值 +-- 这样可以确保所有记录都有完整的数据 + +UPDATE `zyt_tcm_prescription` +SET + `usage_days` = COALESCE(`usage_days`, 7), + `usage_time` = COALESCE(`usage_time`, '饭前'), + `usage_way` = COALESCE(`usage_way`, '温水送服'), + `dietary_taboo` = COALESCE(`dietary_taboo`, ''), + `usage_notes` = COALESCE(`usage_notes`, ''), + `is_shared` = COALESCE(`is_shared`, 0) +WHERE `delete_time` IS NULL; + +-- 查看更新结果 +SELECT id, sn, prescription_name, usage_days, usage_time, usage_way, dietary_taboo, usage_notes, is_shared +FROM `zyt_tcm_prescription` +WHERE `delete_time` IS NULL +LIMIT 5; diff --git a/server/sql/1.9.20260420/add_bags_per_dose_field.sql b/server/sql/1.9.20260420/add_bags_per_dose_field.sql new file mode 100644 index 00000000..d359451a --- /dev/null +++ b/server/sql/1.9.20260420/add_bags_per_dose_field.sql @@ -0,0 +1,3 @@ +-- 添加每贴出包数字段到处方表 +ALTER TABLE `zyt_tcm_prescription` +ADD COLUMN `bags_per_dose` TINYINT UNSIGNED NULL DEFAULT 1 COMMENT '每贴出包数(饮片专用,1-9包)' AFTER `need_decoction`; diff --git a/server/sql/1.9.20260420/add_completion_request_field.sql b/server/sql/1.9.20260420/add_completion_request_field.sql new file mode 100644 index 00000000..be0586a5 --- /dev/null +++ b/server/sql/1.9.20260420/add_completion_request_field.sql @@ -0,0 +1,8 @@ +-- 为处方业务订单表添加完单申请字段 +-- 用于记录补齐支付单时是否申请完成订单 + +ALTER TABLE `zyt_tcm_prescription_order` +ADD COLUMN `completion_request` tinyint(1) unsigned NOT NULL DEFAULT 0 COMMENT '完单申请:0=未申请,1=已申请' AFTER `payment_slip_audit_remark`, +ADD COLUMN `completion_request_time` int(11) unsigned NOT NULL DEFAULT 0 COMMENT '完单申请时间' AFTER `completion_request`, +ADD COLUMN `completion_request_by` int(11) unsigned NOT NULL DEFAULT 0 COMMENT '完单申请人ID' AFTER `completion_request_time`, +ADD COLUMN `completion_request_by_name` varchar(64) NOT NULL DEFAULT '' COMMENT '完单申请人姓名' AFTER `completion_request_by`; diff --git a/server/sql/1.9.20260420/add_dose_unit_to_prescription_order.sql b/server/sql/1.9.20260420/add_dose_unit_to_prescription_order.sql new file mode 100644 index 00000000..b25bb9ae --- /dev/null +++ b/server/sql/1.9.20260420/add_dose_unit_to_prescription_order.sql @@ -0,0 +1,13 @@ +-- 添加剂量单位和剂数字段到处方业务订单表 +-- 用于记录订单的剂量单位和剂数信息 + +ALTER TABLE `zyt_tcm_prescription_order` +ADD COLUMN `dose_unit` varchar(20) NOT NULL DEFAULT '剂' COMMENT '剂量单位:剂、丸、袋、盒、瓶、膏、贴' AFTER `medication_days`, +ADD COLUMN `dose_count` int(11) NOT NULL DEFAULT 1 COMMENT '剂数' AFTER `dose_unit`; + +-- 验证字段是否添加成功 +SELECT COLUMN_NAME, DATA_TYPE, COLUMN_DEFAULT, COLUMN_COMMENT +FROM information_schema.COLUMNS +WHERE TABLE_SCHEMA = DATABASE() + AND TABLE_NAME = 'zyt_tcm_prescription_order' + AND COLUMN_NAME IN ('dose_unit', 'dose_count'); diff --git a/server/sql/1.9.20260420/add_gancao_callback_fields.sql b/server/sql/1.9.20260420/add_gancao_callback_fields.sql new file mode 100644 index 00000000..6522c2d1 --- /dev/null +++ b/server/sql/1.9.20260420/add_gancao_callback_fields.sql @@ -0,0 +1,12 @@ +-- 添加甘草订单回调相关字段 +-- 在 zyt_prescription_order 表中添加 + +ALTER TABLE `zyt_prescription_order` +ADD COLUMN `gancao_order_state` INT(11) DEFAULT 0 COMMENT '甘草订单状态:10=审核中,11=审核通过,110=制作中,20=物流中,30=完成,90=拦截,91=撤单,92=驳回' AFTER `gancao_submit_time`, +ADD COLUMN `gancao_flow_name` VARCHAR(100) DEFAULT '' COMMENT '甘草订单流程名称(如:派单、审方、调配、复核、浸泡、煎药、包装、发货等)' AFTER `gancao_order_state`, +ADD COLUMN `gancao_supplier` VARCHAR(100) DEFAULT '' COMMENT '甘草供应商/药房名称' AFTER `gancao_flow_name`, +ADD COLUMN `gancao_remark` VARCHAR(500) DEFAULT '' COMMENT '甘草订单备注(拦截原因、驳回原因等)' AFTER `gancao_supplier`; + +-- 添加索引 +ALTER TABLE `zyt_prescription_order` +ADD INDEX `idx_gancao_order_state` (`gancao_order_state`); diff --git a/server/sql/1.9.20260420/add_is_paw_field.sql b/server/sql/1.9.20260420/add_is_paw_field.sql new file mode 100644 index 00000000..5d03d94c --- /dev/null +++ b/server/sql/1.9.20260420/add_is_paw_field.sql @@ -0,0 +1,5 @@ +-- 添加 is_paw 字段到 admin 表 +-- is_paw: 0=需要修改密码,1=正常(默认值) + +ALTER TABLE `la_admin` +ADD COLUMN `is_paw` TINYINT(1) NOT NULL DEFAULT 1 COMMENT '是否已修改初始密码:0=需要修改,1=正常' AFTER `multipoint_login`; diff --git a/server/sql/1.9.20260420/add_prescription_complete_fields.sql b/server/sql/1.9.20260420/add_prescription_complete_fields.sql new file mode 100644 index 00000000..23f63a19 --- /dev/null +++ b/server/sql/1.9.20260420/add_prescription_complete_fields.sql @@ -0,0 +1,20 @@ +-- 添加处方完整字段(用量、代煎、每天几次) +-- 执行前请先备份数据库 + +-- 1. 添加用量相关字段(如果不存在) +ALTER TABLE `zyt_tcm_prescription` +ADD COLUMN IF NOT EXISTS `dosage_amount` decimal(10,2) DEFAULT NULL COMMENT '用量数值' AFTER `prescription_type`, +ADD COLUMN IF NOT EXISTS `dosage_unit` varchar(10) NOT NULL DEFAULT '' COMMENT '用量单位(g/ml)' AFTER `dosage_amount`, +ADD COLUMN IF NOT EXISTS `need_decoction` tinyint(1) NOT NULL DEFAULT 0 COMMENT '是否代煎(0否1是,仅饮片)' AFTER `dosage_unit`; + +-- 2. 添加每天几次字段(如果不存在) +ALTER TABLE `zyt_tcm_prescription` +ADD COLUMN IF NOT EXISTS `times_per_day` int(11) NOT NULL DEFAULT 2 COMMENT '每天服用次数' AFTER `usage_days`; + +-- 验证字段是否添加成功 +SELECT COLUMN_NAME, DATA_TYPE, COLUMN_DEFAULT, COLUMN_COMMENT +FROM information_schema.COLUMNS +WHERE TABLE_SCHEMA = DATABASE() + AND TABLE_NAME = 'zyt_tcm_prescription' + AND COLUMN_NAME IN ('dosage_amount', 'dosage_unit', 'need_decoction', 'times_per_day') +ORDER BY ORDINAL_POSITION; diff --git a/server/sql/1.9.20260420/add_prescription_dosage_fields.sql b/server/sql/1.9.20260420/add_prescription_dosage_fields.sql new file mode 100644 index 00000000..cf91eb7c --- /dev/null +++ b/server/sql/1.9.20260420/add_prescription_dosage_fields.sql @@ -0,0 +1,15 @@ +-- 添加处方用量相关字段 +-- 用于存储处方类型对应的用量、单位和代煎信息 + +ALTER TABLE `zyt_tcm_prescription` +ADD COLUMN `dosage_amount` decimal(10,2) DEFAULT NULL COMMENT '用量数值' AFTER `prescription_type`, +ADD COLUMN `dosage_unit` varchar(10) NOT NULL DEFAULT '' COMMENT '用量单位(g/ml)' AFTER `dosage_amount`, +ADD COLUMN `need_decoction` tinyint(1) NOT NULL DEFAULT 0 COMMENT '是否代煎(0否1是,仅饮片)' AFTER `dosage_unit`; + +-- 验证字段是否添加成功 +SELECT COLUMN_NAME, DATA_TYPE, COLUMN_DEFAULT, COLUMN_COMMENT +FROM information_schema.COLUMNS +WHERE TABLE_SCHEMA = DATABASE() + AND TABLE_NAME = 'zyt_tcm_prescription' + AND COLUMN_NAME IN ('dosage_amount', 'dosage_unit', 'need_decoction') +ORDER BY ORDINAL_POSITION; diff --git a/server/sql/1.9.20260420/add_shipping_region_fields.sql b/server/sql/1.9.20260420/add_shipping_region_fields.sql new file mode 100644 index 00000000..0d95a8c4 --- /dev/null +++ b/server/sql/1.9.20260420/add_shipping_region_fields.sql @@ -0,0 +1,5 @@ +-- 添加省市区字段到处方业务订单表 +ALTER TABLE `zyt_tcm_prescription_order` +ADD COLUMN `shipping_province` varchar(50) NOT NULL DEFAULT '' COMMENT '收货省份' AFTER `recipient_phone`, +ADD COLUMN `shipping_city` varchar(50) NOT NULL DEFAULT '' COMMENT '收货城市' AFTER `shipping_province`, +ADD COLUMN `shipping_district` varchar(50) NOT NULL DEFAULT '' COMMENT '收货区县' AFTER `shipping_city`; diff --git a/server/sql/1.9.20260420/check_and_add_indexes.sql b/server/sql/1.9.20260420/check_and_add_indexes.sql new file mode 100644 index 00000000..94ae1b65 --- /dev/null +++ b/server/sql/1.9.20260420/check_and_add_indexes.sql @@ -0,0 +1,208 @@ +-- 安全添加索引脚本(检查后添加,避免重复) +-- 使用方法:在MySQL客户端或phpMyAdmin中执行 + +-- ============================================ +-- 1. 检查现有索引 +-- ============================================ +SELECT + TABLE_NAME, + INDEX_NAME, + GROUP_CONCAT(COLUMN_NAME ORDER BY SEQ_IN_INDEX) as COLUMNS +FROM + INFORMATION_SCHEMA.STATISTICS +WHERE + TABLE_SCHEMA = DATABASE() + AND TABLE_NAME IN ( + 'zyt_appointment', + 'zyt_tcm_diagnosis', + 'zyt_admin', + 'zyt_diagnosis_view_records', + 'zyt_prescription', + 'zyt_admin_role' + ) +GROUP BY + TABLE_NAME, INDEX_NAME +ORDER BY + TABLE_NAME, INDEX_NAME; + +-- ============================================ +-- 2. 安全添加索引(如果不存在) +-- ============================================ + +-- 预约表索引 +SET @sql = IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.STATISTICS + WHERE TABLE_SCHEMA = DATABASE() + AND TABLE_NAME = 'zyt_appointment' + AND INDEX_NAME = 'idx_doctor_id') = 0, + 'ALTER TABLE `zyt_appointment` ADD INDEX `idx_doctor_id` (`doctor_id`)', + 'SELECT "索引 idx_doctor_id 已存在" as message' +); +PREPARE stmt FROM @sql; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + +SET @sql = IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.STATISTICS + WHERE TABLE_SCHEMA = DATABASE() + AND TABLE_NAME = 'zyt_appointment' + AND INDEX_NAME = 'idx_patient_id') = 0, + 'ALTER TABLE `zyt_appointment` ADD INDEX `idx_patient_id` (`patient_id`)', + 'SELECT "索引 idx_patient_id 已存在" as message' +); +PREPARE stmt FROM @sql; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + +SET @sql = IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.STATISTICS + WHERE TABLE_SCHEMA = DATABASE() + AND TABLE_NAME = 'zyt_appointment' + AND INDEX_NAME = 'idx_status') = 0, + 'ALTER TABLE `zyt_appointment` ADD INDEX `idx_status` (`status`)', + 'SELECT "索引 idx_status 已存在" as message' +); +PREPARE stmt FROM @sql; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + +SET @sql = IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.STATISTICS + WHERE TABLE_SCHEMA = DATABASE() + AND TABLE_NAME = 'zyt_appointment' + AND INDEX_NAME = 'idx_appointment_date') = 0, + 'ALTER TABLE `zyt_appointment` ADD INDEX `idx_appointment_date` (`appointment_date`)', + 'SELECT "索引 idx_appointment_date 已存在" as message' +); +PREPARE stmt FROM @sql; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + +SET @sql = IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.STATISTICS + WHERE TABLE_SCHEMA = DATABASE() + AND TABLE_NAME = 'zyt_appointment' + AND INDEX_NAME = 'idx_status_date_time') = 0, + 'ALTER TABLE `zyt_appointment` ADD INDEX `idx_status_date_time` (`status`, `appointment_date`, `appointment_time`)', + 'SELECT "索引 idx_status_date_time 已存在" as message' +); +PREPARE stmt FROM @sql; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + +SET @sql = IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.STATISTICS + WHERE TABLE_SCHEMA = DATABASE() + AND TABLE_NAME = 'zyt_appointment' + AND INDEX_NAME = 'idx_doctor_status_date') = 0, + 'ALTER TABLE `zyt_appointment` ADD INDEX `idx_doctor_status_date` (`doctor_id`, `status`, `appointment_date`)', + 'SELECT "索引 idx_doctor_status_date 已存在" as message' +); +PREPARE stmt FROM @sql; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + +-- 诊断表索引 +SET @sql = IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.STATISTICS + WHERE TABLE_SCHEMA = DATABASE() + AND TABLE_NAME = 'zyt_tcm_diagnosis' + AND INDEX_NAME = 'idx_patient_name') = 0, + 'ALTER TABLE `zyt_tcm_diagnosis` ADD INDEX `idx_patient_name` (`patient_name`(20))', + 'SELECT "索引 idx_patient_name 已存在" as message' +); +PREPARE stmt FROM @sql; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + +SET @sql = IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.STATISTICS + WHERE TABLE_SCHEMA = DATABASE() + AND TABLE_NAME = 'zyt_tcm_diagnosis' + AND INDEX_NAME = 'idx_assistant_id') = 0, + 'ALTER TABLE `zyt_tcm_diagnosis` ADD INDEX `idx_assistant_id` (`assistant_id`)', + 'SELECT "索引 idx_assistant_id 已存在" as message' +); +PREPARE stmt FROM @sql; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + +-- 管理员表索引 +SET @sql = IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.STATISTICS + WHERE TABLE_SCHEMA = DATABASE() + AND TABLE_NAME = 'zyt_admin' + AND INDEX_NAME = 'idx_name') = 0, + 'ALTER TABLE `zyt_admin` ADD INDEX `idx_name` (`name`(20))', + 'SELECT "索引 idx_name 已存在" as message' +); +PREPARE stmt FROM @sql; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + +-- 诊断查看记录表索引 +SET @sql = IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.STATISTICS + WHERE TABLE_SCHEMA = DATABASE() + AND TABLE_NAME = 'zyt_diagnosis_view_records' + AND INDEX_NAME = 'idx_diagnosis_confirmed') = 0, + 'ALTER TABLE `zyt_diagnosis_view_records` ADD INDEX `idx_diagnosis_confirmed` (`diagnosis_id`, `is_confirmed`, `delete_time`)', + 'SELECT "索引 idx_diagnosis_confirmed 已存在" as message' +); +PREPARE stmt FROM @sql; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + +-- 处方表索引 +SET @sql = IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.STATISTICS + WHERE TABLE_SCHEMA = DATABASE() + AND TABLE_NAME = 'zyt_prescription' + AND INDEX_NAME = 'idx_diagnosis_delete') = 0, + 'ALTER TABLE `zyt_prescription` ADD INDEX `idx_diagnosis_delete` (`diagnosis_id`, `delete_time`)', + 'SELECT "索引 idx_diagnosis_delete 已存在" as message' +); +PREPARE stmt FROM @sql; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + +-- 管理员角色表索引 +SET @sql = IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.STATISTICS + WHERE TABLE_SCHEMA = DATABASE() + AND TABLE_NAME = 'zyt_admin_role' + AND INDEX_NAME = 'idx_admin_role') = 0, + 'ALTER TABLE `zyt_admin_role` ADD INDEX `idx_admin_role` (`admin_id`, `role_id`)', + 'SELECT "索引 idx_admin_role 已存在" as message' +); +PREPARE stmt FROM @sql; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + +-- ============================================ +-- 3. 再次检查索引是否创建成功 +-- ============================================ +SELECT + TABLE_NAME, + INDEX_NAME, + GROUP_CONCAT(COLUMN_NAME ORDER BY SEQ_IN_INDEX) as COLUMNS, + INDEX_TYPE, + NON_UNIQUE +FROM + INFORMATION_SCHEMA.STATISTICS +WHERE + TABLE_SCHEMA = DATABASE() + AND TABLE_NAME IN ( + 'zyt_appointment', + 'zyt_tcm_diagnosis', + 'zyt_admin', + 'zyt_diagnosis_view_records', + 'zyt_prescription', + 'zyt_admin_role' + ) +GROUP BY + TABLE_NAME, INDEX_NAME, INDEX_TYPE, NON_UNIQUE +ORDER BY + TABLE_NAME, INDEX_NAME; + +SELECT '索引创建完成!' as message; diff --git a/server/sql/1.9.20260420/install_medicine_library.sql b/server/sql/1.9.20260420/install_medicine_library.sql new file mode 100644 index 00000000..e10e17e4 --- /dev/null +++ b/server/sql/1.9.20260420/install_medicine_library.sql @@ -0,0 +1,28 @@ +-- 药品库表 +CREATE TABLE IF NOT EXISTS `la_doctor_medicine` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID', + `name` varchar(100) NOT NULL DEFAULT '' COMMENT '药材名称', + `supplier` varchar(100) NOT NULL DEFAULT '' COMMENT '供应商名称', + `unit` varchar(20) NOT NULL DEFAULT '' COMMENT '单位', + `settlement_price` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '结算价', + `retail_price` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '零售价', + `stock` int(11) NOT NULL DEFAULT '0' COMMENT '库存数量', + `image` varchar(255) NOT NULL DEFAULT '' COMMENT '药品图片', + `status` tinyint(1) NOT NULL DEFAULT '1' COMMENT '状态:0=停用,1=启用', + `remark` varchar(500) NOT NULL DEFAULT '' COMMENT '备注', + `create_time` int(11) NOT NULL DEFAULT '0' COMMENT '创建时间', + `update_time` int(11) NOT NULL DEFAULT '0' COMMENT '更新时间', + `delete_time` int(11) DEFAULT NULL COMMENT '删除时间', + PRIMARY KEY (`id`), + KEY `idx_name` (`name`), + KEY `idx_supplier` (`supplier`), + KEY `idx_status` (`status`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='药品库表'; + +-- 插入示例数据 +INSERT INTO `la_doctor_medicine` (`name`, `supplier`, `unit`, `settlement_price`, `retail_price`, `stock`, `image`, `status`, `remark`, `create_time`, `update_time`) VALUES +('当归', '同仁堂', '克', 0.50, 0.80, 1000, '', 1, '补血活血,调经止痛', UNIX_TIMESTAMP(), UNIX_TIMESTAMP()), +('黄芪', '云南白药', '克', 0.30, 0.50, 2000, '', 1, '补气固表,利尿托毒', UNIX_TIMESTAMP(), UNIX_TIMESTAMP()), +('枸杞子', '宁夏枸杞', '克', 0.40, 0.70, 1500, '', 1, '滋补肝肾,益精明目', UNIX_TIMESTAMP(), UNIX_TIMESTAMP()), +('人参', '长白山人参', '克', 5.00, 8.00, 500, '', 1, '大补元气,复脉固脱', UNIX_TIMESTAMP(), UNIX_TIMESTAMP()), +('甘草', '内蒙古甘草', '克', 0.20, 0.35, 3000, '', 1, '补脾益气,清热解毒', UNIX_TIMESTAMP(), UNIX_TIMESTAMP()); diff --git a/server/sql/1.9.20260420/optimize_appointment_indexes.sql b/server/sql/1.9.20260420/optimize_appointment_indexes.sql new file mode 100644 index 00000000..68e4a04c --- /dev/null +++ b/server/sql/1.9.20260420/optimize_appointment_indexes.sql @@ -0,0 +1,76 @@ +-- 预约表索引优化 +-- 用于提升预约列表查询性能 + +-- 1. 预约表 (zyt_appointment) 索引 +-- 主要查询字段:doctor_id, patient_id, status, appointment_date, appointment_time + +-- 医生ID索引(医生角色查询自己的预约) +ALTER TABLE `zyt_appointment` ADD INDEX `idx_doctor_id` (`doctor_id`); + +-- 患者ID索引(关联查询) +ALTER TABLE `zyt_appointment` ADD INDEX `idx_patient_id` (`patient_id`); + +-- 状态索引(状态筛选) +ALTER TABLE `zyt_appointment` ADD INDEX `idx_status` (`status`); + +-- 预约日期索引(日期范围查询) +ALTER TABLE `zyt_appointment` ADD INDEX `idx_appointment_date` (`appointment_date`); + +-- 组合索引:状态+日期+时间(用于排序和筛选) +ALTER TABLE `zyt_appointment` ADD INDEX `idx_status_date_time` (`status`, `appointment_date`, `appointment_time`); + +-- 组合索引:医生+状态+日期(医生角色常用查询) +ALTER TABLE `zyt_appointment` ADD INDEX `idx_doctor_status_date` (`doctor_id`, `status`, `appointment_date`); + +-- 2. 诊断表 (zyt_tcm_diagnosis) 索引 +-- 主要查询字段:patient_name, assistant_id + +-- 患者姓名索引(模糊搜索,使用前缀索引) +ALTER TABLE `zyt_tcm_diagnosis` ADD INDEX `idx_patient_name` (`patient_name`(20)); + +-- 医助ID索引(医助角色查询) +ALTER TABLE `zyt_tcm_diagnosis` ADD INDEX `idx_assistant_id` (`assistant_id`); + +-- 3. 管理员表 (zyt_admin) 索引 +-- 主要查询字段:name(医生姓名搜索) + +-- 管理员姓名索引(模糊搜索,使用前缀索引) +ALTER TABLE `zyt_admin` ADD INDEX `idx_name` (`name`(20)); + +-- 4. 诊断查看记录表 (zyt_diagnosis_view_records) 索引 +-- 主要查询字段:diagnosis_id, is_confirmed, delete_time + +-- 组合索引:诊断ID+确认状态+删除时间(用于确认诊单查询) +ALTER TABLE `zyt_diagnosis_view_records` ADD INDEX `idx_diagnosis_confirmed` (`diagnosis_id`, `is_confirmed`, `delete_time`); + +-- 5. 处方表 (zyt_prescription) 索引 +-- 主要查询字段:diagnosis_id, delete_time + +-- 组合索引:诊断ID+删除时间(用于开方状态查询) +ALTER TABLE `zyt_prescription` ADD INDEX `idx_diagnosis_delete` (`diagnosis_id`, `delete_time`); + +-- 6. 管理员角色表 (zyt_admin_role) 索引 +-- 主要查询字段:admin_id, role_id + +-- 组合索引:管理员ID+角色ID(用于权限判断) +ALTER TABLE `zyt_admin_role` ADD INDEX `idx_admin_role` (`admin_id`, `role_id`); + +-- ============================================ +-- 查看已创建的索引 +-- ============================================ +-- SHOW INDEX FROM `zyt_appointment`; +-- SHOW INDEX FROM `zyt_tcm_diagnosis`; +-- SHOW INDEX FROM `zyt_admin`; +-- SHOW INDEX FROM `zyt_diagnosis_view_records`; +-- SHOW INDEX FROM `zyt_prescription`; +-- SHOW INDEX FROM `zyt_admin_role`; + +-- ============================================ +-- 如果索引已存在,先删除再创建(可选) +-- ============================================ +-- ALTER TABLE `zyt_appointment` DROP INDEX `idx_doctor_id`; +-- ALTER TABLE `zyt_appointment` DROP INDEX `idx_patient_id`; +-- ALTER TABLE `zyt_appointment` DROP INDEX `idx_status`; +-- ALTER TABLE `zyt_appointment` DROP INDEX `idx_appointment_date`; +-- ALTER TABLE `zyt_appointment` DROP INDEX `idx_status_date_time`; +-- ALTER TABLE `zyt_appointment` DROP INDEX `idx_doctor_status_date`; diff --git a/server/sql/1.9.20260420/test_first_login.sql b/server/sql/1.9.20260420/test_first_login.sql new file mode 100644 index 00000000..c0d51934 --- /dev/null +++ b/server/sql/1.9.20260420/test_first_login.sql @@ -0,0 +1,23 @@ +-- 测试首次登录强制修改密码功能 + +-- 1. 查看当前所有管理员的 is_paw 状态 +SELECT id, account, name, is_paw, + CASE is_paw + WHEN 0 THEN '需要修改密码' + WHEN 1 THEN '正常' + ELSE '未知' + END as status +FROM la_admin +WHERE delete_time = 0; + +-- 2. 设置指定管理员需要修改密码(将 ID 改为实际的管理员 ID) +-- UPDATE la_admin SET is_paw = 0 WHERE id = 1; + +-- 3. 设置所有管理员需要修改密码(谨慎使用) +-- UPDATE la_admin SET is_paw = 0 WHERE delete_time = 0; + +-- 4. 恢复指定管理员为正常状态 +-- UPDATE la_admin SET is_paw = 1 WHERE id = 1; + +-- 5. 恢复所有管理员为正常状态 +-- UPDATE la_admin SET is_paw = 1 WHERE delete_time = 0; diff --git a/server/test_appointment_api.php b/server/test_appointment_api.php deleted file mode 100644 index 0b5ddda1..00000000 --- a/server/test_appointment_api.php +++ /dev/null @@ -1,165 +0,0 @@ - 0) { - echo "✓ 获取时间段成功\n"; - $firstSlot = $response['data']['slots'][0]; - $appointmentTime = $firstSlot['time']; - echo " 可用时间段数量: " . count($response['data']['slots']) . "\n"; - echo " 第一个时间段: {$appointmentTime}\n\n"; - - // 测试2: 创建预约 - echo "测试2: 创建预约\n"; - $createData = [ - 'patient_id' => $patientId, - 'doctor_id' => $doctorId, - 'appointment_date' => $testDate, - 'period' => 'morning', - 'appointment_time' => $appointmentTime, - 'appointment_type' => 'video', - 'remark' => '测试预约' - ]; - - echo "URL: {$baseUrl}/doctor.appointment/create\n"; - echo "数据: " . json_encode($createData, JSON_UNESCAPED_UNICODE) . "\n"; - - $url = "{$baseUrl}/doctor.appointment/create"; - $response = makeRequest($url, 'POST', $createData, $token); - echo "响应: " . json_encode($response, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT) . "\n\n"; - - if (isset($response['data']['id'])) { - $appointmentId = $response['data']['id']; - echo "✓ 创建预约成功, ID: {$appointmentId}\n\n"; - - // 测试3: 获取预约详情 - echo "测试3: 获取预约详情\n"; - $url = "{$baseUrl}/doctor.appointment/detail?id={$appointmentId}"; - $response = makeRequest($url, 'GET', null, $token); - echo "响应: " . json_encode($response, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT) . "\n\n"; - - if (isset($response['data']['id'])) { - echo "✓ 获取详情成功\n\n"; - } else { - echo "✗ 获取详情失败\n\n"; - } - - // 测试4: 取消预约 - echo "测试4: 取消预约\n"; - $url = "{$baseUrl}/doctor.appointment/cancel"; - $response = makeRequest($url, 'POST', ['id' => $appointmentId], $token); - echo "响应: " . json_encode($response, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT) . "\n\n"; - - if ($response['code'] == 1) { - echo "✓ 取消预约成功\n\n"; - } else { - echo "✗ 取消预约失败\n\n"; - } - } else { - echo "✗ 创建预约失败: " . ($response['msg'] ?? '未知错误') . "\n\n"; - } -} else { - echo "✗ 获取时间段失败或无可用时间段\n"; - echo " 请确保:\n"; - echo " 1. 数据库表已创建\n"; - echo " 2. 医生ID={$doctorId}存在\n"; - echo " 3. 该医生在{$testDate}有排班\n\n"; -} - -// 测试5: 获取医生可用号源数 -echo "测试5: 获取医生可用号源数\n"; -$url = "{$baseUrl}/doctor.appointment/doctorAvailability?doctor_id={$doctorId}&date={$testDate}"; -$response = makeRequest($url, 'GET', null, $token); -echo "响应: " . json_encode($response, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT) . "\n\n"; - -if (isset($response['data']['available_count'])) { - echo "✓ 获取可用号源数成功: " . $response['data']['available_count'] . "\n\n"; -} else { - echo "✗ 获取可用号源数失败\n\n"; -} - -// 测试6: 获取预约列表 -echo "测试6: 获取预约列表\n"; -$url = "{$baseUrl}/doctor.appointment/lists?page_no=1&page_size=10"; -$response = makeRequest($url, 'GET', null, $token); -echo "响应: " . json_encode($response, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT) . "\n\n"; - -if (isset($response['data']['lists'])) { - echo "✓ 获取列表成功, 记录数: " . count($response['data']['lists']) . "\n\n"; -} else { - echo "✗ 获取列表失败\n\n"; -} - -echo "=== 测试完成 ===\n"; - -/** - * 发送HTTP请求 - */ -function makeRequest($url, $method = 'GET', $data = null, $token = null) -{ - $ch = curl_init(); - - if ($method == 'GET' && $data) { - $url .= '?' . http_build_query($data); - } - - curl_setopt($ch, CURLOPT_URL, $url); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); - curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); - - $headers = ['Content-Type: application/json']; - if ($token) { - $headers[] = 'token: ' . $token; - } - curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); - - if ($method == 'POST') { - curl_setopt($ch, CURLOPT_POST, true); - if ($data) { - curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data)); - } - } - - $response = curl_exec($ch); - $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); - - if (curl_errno($ch)) { - echo "CURL错误: " . curl_error($ch) . "\n"; - curl_close($ch); - return ['code' => 0, 'msg' => 'CURL错误']; - } - - curl_close($ch); - - $result = json_decode($response, true); - if (!$result) { - echo "响应解析失败, HTTP状态码: {$httpCode}\n"; - echo "原始响应: {$response}\n"; - return ['code' => 0, 'msg' => '响应解析失败']; - } - - return $result; -} diff --git a/server/test_auto_detect.php b/server/test_auto_detect.php deleted file mode 100644 index 46d0b9b7..00000000 --- a/server/test_auto_detect.php +++ /dev/null @@ -1,90 +0,0 @@ - 'auto', - 'num' => $testNumber, - 'resultv2' => '1', -]; - -$paramJson = json_encode($paramArr, JSON_UNESCAPED_UNICODE); -$sign = strtoupper(md5($paramJson . $key . $customer)); - -$postBody = http_build_query([ - 'customer' => $customer, - 'param' => $paramJson, - 'sign' => $sign, -]); - -$url = 'https://poll.kuaidi100.com/poll/query.do'; - -$ch = curl_init(); -curl_setopt($ch, CURLOPT_URL, $url); -curl_setopt($ch, CURLOPT_POST, true); -curl_setopt($ch, CURLOPT_POSTFIELDS, $postBody); -curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); -curl_setopt($ch, CURLOPT_TIMEOUT, 15); -curl_setopt($ch, CURLOPT_HTTPHEADER, [ - 'Content-Type: application/x-www-form-urlencoded', -]); -curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); -curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); - -$response = curl_exec($ch); -curl_close($ch); - -echo "响应:\n"; -echo $response . "\n\n"; - -$json = json_decode($response, true); -if (is_array($json)) { - echo "解析:\n"; - echo " result: " . var_export($json['result'] ?? null, true) . "\n"; - echo " message: " . ($json['message'] ?? 'N/A') . "\n"; - echo " returnCode: " . ($json['returnCode'] ?? 'N/A') . "\n"; - - if (isset($json['data']) && is_array($json['data']) && count($json['data']) > 0) { - echo " 轨迹数量: " . count($json['data']) . "\n"; - echo "\n最新3条轨迹:\n"; - for ($i = 0; $i < min(3, count($json['data'])); $i++) { - $trace = $json['data'][$i]; - echo " " . ($i + 1) . ". " . ($trace['ftime'] ?? $trace['time'] ?? '') . " - " . ($trace['context'] ?? '') . "\n"; - } - } -} - -echo "\n===========================================\n"; diff --git a/server/test_doctor_api.php b/server/test_doctor_api.php deleted file mode 100644 index 0ae2e074..00000000 --- a/server/test_doctor_api.php +++ /dev/null @@ -1,91 +0,0 @@ - 1, - 'page_size' => 10 -]); - -// 2. 测试获取医生列表(带搜索) -echo "\n\n【测试2】获取医生列表(带搜索)\n"; -$result2 = testApi('GET', '/doctor/lists', [ - 'page_no' => 1, - 'page_size' => 10, - 'keyword' => '医生' -]); - -// 3. 测试获取医生详情 -if ($result1 && isset($result1['data']['lists'][0])) { - $doctorId = $result1['data']['lists'][0]['id']; - echo "\n\n【测试3】获取医生详情 (ID: $doctorId)\n"; - $result3 = testApi('GET', '/doctor/detail', [ - 'id' => $doctorId - ]); - - // 4. 测试获取医生排班 - echo "\n\n【测试4】获取医生排班 (ID: $doctorId)\n"; - $result4 = testApi('GET', '/doctor/roster', [ - 'doctor_id' => $doctorId, - 'date' => date('Y-m-d') - ]); -} - -echo "\n\n测试完成!\n"; -?> diff --git a/server/test_express_auto_update.php b/server/test_express_auto_update.php deleted file mode 100644 index 2b66a4a9..00000000 --- a/server/test_express_auto_update.php +++ /dev/null @@ -1,62 +0,0 @@ - $trace) { - echo " " . ($idx + 1) . ". {$trace['time']} - {$trace['context']}\n"; - } - } -} catch (\Exception $e) { - echo "查询失败: {$e->getMessage()}\n"; -} - -echo "\n===========================================\n"; -echo "测试完成\n"; -echo "===========================================\n"; diff --git a/server/test_logistics.php b/server/test_logistics.php deleted file mode 100644 index 5db7ba60..00000000 --- a/server/test_logistics.php +++ /dev/null @@ -1,135 +0,0 @@ - 'sf', - 'number' => 'SF1234567890', - 'phone' => '13800138000', - 'label' => '顺丰速运' - ], - [ - 'company' => 'jd', - 'number' => 'JD1234567890', - 'phone' => '13800138000', - 'label' => '京东快递' - ], -]; - -echo "1. 检查快递100配置\n"; -echo "-------------------------------------------\n"; - -$config = \think\facade\Config::get('logistics.kuaidi100', []); -$enabled = !empty($config['enable']); -$customer = $config['customer'] ?? ''; -$key = $config['key'] ?? ''; - -if ($enabled && $customer !== '' && $key !== '') { - echo "✓ 快递100已配置\n"; - echo " Customer: " . substr($customer, 0, 4) . "****\n"; - echo " Key: " . substr($key, 0, 4) . "****\n"; -} else { - echo "✗ 快递100未配置或已关闭\n"; - echo " 提示:请在 .env 文件中配置 LOGISTICS_KUAIDI100_CUSTOMER 和 LOGISTICS_KUAIDI100_KEY\n"; -} - -echo "\n2. 测试官网查询链接生成\n"; -echo "-------------------------------------------\n"; - -foreach ($testCases as $case) { - echo "\n{$case['label']} ({$case['number']}):\n"; - - $urls = ExpressTrackService::officialUrls($case['number']); - - echo " 顺丰官网: {$urls['sf']}\n"; - echo " 京东官网: {$urls['jd']}\n"; -} - -echo "\n3. 测试快递查询(需要真实运单号)\n"; -echo "-------------------------------------------\n"; - -if (!$enabled) { - echo "✗ 跳过:快递100未配置\n"; - echo " 提示:配置后可测试实时查询功能\n"; -} else { - echo "提示:请将上面的测试运单号替换为真实运单号后再测试\n"; - echo "是否继续测试?(y/n): "; - - $handle = fopen("php://stdin", "r"); - $line = fgets($handle); - fclose($handle); - - if (trim($line) === 'y') { - foreach ($testCases as $case) { - echo "\n测试 {$case['label']} ({$case['number']}):\n"; - - try { - $result = ExpressTrackService::query( - $case['company'], - $case['number'], - $case['phone'] - ); - - echo " 承运商: {$result['carrier_label']}\n"; - echo " 状态: {$result['state_text']}\n"; - echo " 数据源: {$result['source']}\n"; - - if (!empty($result['hint'])) { - echo " 提示: {$result['hint']}\n"; - } - - if (!empty($result['traces'])) { - echo " 轨迹节点数: " . count($result['traces']) . "\n"; - echo " 最新轨迹: {$result['traces'][0]['time']} - {$result['traces'][0]['context']}\n"; - } else { - echo " 轨迹: 暂无\n"; - } - - } catch (\Exception $e) { - echo " ✗ 查询失败: {$e->getMessage()}\n"; - } - } - } else { - echo "已跳过实时查询测试\n"; - } -} - -echo "\n===========================================\n"; -echo "测试完成\n"; -echo "===========================================\n\n"; - -echo "故障排查提示:\n"; -echo "1. 如果顺丰查询失败,请确认:\n"; -echo " - 运单号格式正确(通常以SF开头)\n"; -echo " - 收件人手机号后4位正确\n"; -echo " - 快递已揽收\n\n"; - -echo "2. 如果京东查询失败,请确认:\n"; -echo " - 运单号格式正确(通常以JD开头)\n"; -echo " - 快递已揽收\n\n"; - -echo "3. 如果快递100返回错误,请检查:\n"; -echo " - Customer 和 Key 是否正确\n"; -echo " - 账户余额是否充足\n"; -echo " - 网络连接是否正常\n\n"; - -echo "更多帮助请查看:LOGISTICS_INTEGRATION_GUIDE.md\n"; diff --git a/server/test_tracking_query.php b/server/test_tracking_query.php deleted file mode 100644 index 9fcc1394..00000000 --- a/server/test_tracking_query.php +++ /dev/null @@ -1,103 +0,0 @@ -setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); -$pdo->exec("USE `{$database}`"); - -echo "========================================\n"; -echo "测试物流追踪查询(从数据库)\n"; -echo "========================================\n\n"; - -$trackingNumber = 'JD0230761381812'; - -// 1. 查询追踪主记录 -echo "[1] 查询追踪主记录...\n"; -$stmt = $pdo->prepare(" - SELECT * FROM zyt_express_tracking - WHERE tracking_number = :tracking_number - AND delete_time IS NULL -"); -$stmt->execute(['tracking_number' => $trackingNumber]); -$tracking = $stmt->fetch(PDO::FETCH_ASSOC); - -if (!$tracking) { - echo "❌ 未找到追踪记录\n"; - exit(1); -} - -echo "✓ 找到追踪记录\n"; -echo " 快递公司: {$tracking['express_company_name']}\n"; -echo " 当前状态: {$tracking['current_state_text']}\n"; -echo " 是否签收: " . ($tracking['is_signed'] ? '是' : '否') . "\n"; -echo " 查询次数: {$tracking['query_count']}\n"; -echo " 最后查询: " . date('Y-m-d H:i:s', $tracking['last_query_time']) . "\n\n"; - -// 2. 查询轨迹明细 -echo "[2] 查询轨迹明细...\n"; -$stmt = $pdo->prepare(" - SELECT * FROM zyt_express_trace - WHERE tracking_number = :tracking_number - ORDER BY trace_time_stamp DESC -"); -$stmt->execute(['tracking_number' => $trackingNumber]); -$traces = $stmt->fetchAll(PDO::FETCH_ASSOC); - -echo "✓ 找到 " . count($traces) . " 条轨迹记录\n\n"; - -if (!empty($traces)) { - echo "最新3条轨迹:\n"; - foreach (array_slice($traces, 0, 3) as $trace) { - echo " [{$trace['trace_time']}] {$trace['trace_context']}\n"; - if ($trace['location']) { - echo " 位置: {$trace['location']}\n"; - } - } - echo "\n"; -} - -// 3. 模拟API返回格式 -echo "[3] 模拟API返回格式...\n"; -$payload = [ - 'carrier' => $tracking['express_company'], - 'carrier_label' => $tracking['express_company_name'], - 'kuaidi_com' => $tracking['express_company'], - 'traces' => array_map(function($trace) { - return [ - 'time' => $trace['trace_time'], - 'ftime' => $trace['trace_time'], - 'context' => $trace['trace_context'], - 'location' => $trace['location'], - 'status' => $trace['status'], - 'statusCode' => $trace['status_code'], - ]; - }, $traces), - 'state' => $tracking['current_state'], - 'state_text' => $tracking['current_state_text'], - 'source' => 'database', - 'hint' => '', - 'last_query_time' => date('Y-m-d H:i:s', $tracking['last_query_time']), - 'query_count' => $tracking['query_count'], -]; - -echo json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT); -echo "\n\n"; - -echo "========================================\n"; -echo "测试完成!\n"; -echo "========================================\n"; -echo "✓ 数据库查询正常\n"; -echo "✓ 数据格式兼容API\n"; -echo "✓ 可以直接返回给前端\n"; diff --git a/server/test_usersig.php b/server/test_usersig.php deleted file mode 100644 index 7cd6c5fa..00000000 --- a/server/test_usersig.php +++ /dev/null @@ -1,101 +0,0 @@ -initialize(); - -// 加载配置 -$config = config('project.trtc'); - -echo "=== 腾讯云IM UserSig测试 ===\n\n"; -echo "SDKAppID: " . $config['sdkAppId'] . "\n"; -echo "SecretKey长度: " . strlen($config['secretKey']) . "\n"; -echo "SecretKey前10位: " . substr($config['secretKey'], 0, 10) . "...\n\n"; - -// 测试生成UserSig -try { - $api = new \app\common\service\TLSSigAPIv2($config['sdkAppId'], $config['secretKey']); - - // 测试用户ID - $testUserId = 'test_user_' . time(); - $expire = 86400; // 1天 - - echo "测试用户ID: $testUserId\n"; - echo "过期时间: $expire 秒\n\n"; - - // 生成UserSig - $userSig = $api->genUserSig($testUserId, $expire); - - echo "✅ UserSig生成成功!\n"; - echo "UserSig长度: " . strlen($userSig) . "\n"; - echo "UserSig前50位: " . substr($userSig, 0, 50) . "...\n\n"; - - // 验证UserSig - $initTime = 0; - $expireTime = 0; - $userbuf = ''; - $errorMsg = ''; - - $isValid = $api->verifySig($userSig, $testUserId, $initTime, $expireTime, $userbuf, $errorMsg); - - if ($isValid) { - echo "✅ UserSig验证成功!\n"; - echo "初始化时间: " . date('Y-m-d H:i:s', $initTime) . "\n"; - echo "过期时间: " . $expireTime . " 秒\n"; - echo "到期时间: " . date('Y-m-d H:i:s', $initTime + $expireTime) . "\n\n"; - } else { - echo "❌ UserSig验证失败: $errorMsg\n\n"; - } - - // 测试IM账号导入 - echo "=== 测试IM账号导入 ===\n\n"; - - $imService = new \app\common\service\TencentImService(); - $result = $imService->importAccount($testUserId, '测试用户'); - - if ($result['success']) { - echo "✅ IM账号导入成功!\n"; - echo "用户ID: " . $result['userId'] . "\n"; - echo "消息: " . $result['message'] . "\n\n"; - - echo "请登录腾讯云控制台验证:\n"; - echo "1. 进入:即时通信IM > 应用列表\n"; - echo "2. 选择SDKAppID: " . $config['sdkAppId'] . "\n"; - echo "3. 进入:账号管理\n"; - echo "4. 搜索:$testUserId\n"; - echo "5. 应该能看到该账号\n\n"; - } else { - echo "❌ IM账号导入失败!\n"; - echo "错误信息: " . $result['message'] . "\n\n"; - - echo "可能的原因:\n"; - echo "1. SDKAppID不正确(请确认是IM应用的SDKAppID)\n"; - echo "2. SecretKey不正确\n"; - echo "3. IM服务未开通或已过期\n"; - echo "4. 网络连接问题\n\n"; - - echo "排查步骤:\n"; - echo "1. 登录腾讯云控制台\n"; - echo "2. 进入:即时通信IM > 应用列表\n"; - echo "3. 确认SDKAppID: " . $config['sdkAppId'] . "\n"; - echo "4. 进入应用详情 > 密钥管理\n"; - echo "5. 确认SecretKey是否正确\n"; - echo "6. 使用官方工具测试:https://console.cloud.tencent.com/im/tool-usersig\n\n"; - } - -} catch (\Exception $e) { - echo "❌ 发生异常: " . $e->getMessage() . "\n"; - echo "堆栈跟踪:\n" . $e->getTraceAsString() . "\n"; -} - -echo "\n=== 测试完成 ===\n";