fix: resolve rename/delete conflicts

This commit is contained in:
Your Name
2026-04-21 09:52:27 +08:00
39 changed files with 618 additions and 3336 deletions
-379
View File
@@ -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:3030分钟间隔)
- 下午时段:13:00 - 20:3030分钟间隔)
### 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
-229
View File
@@ -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 会报错
}
```
## 总结
问题已修复,批量排班接口现在可以正常工作,并且返回详细的统计信息。
-264
View File
@@ -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` 的管理员
这样可以确保只查询出具有医生角色的管理员。
-245
View File
@@ -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. 测试日期范围查询
-142
View File
@@ -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
**状态:** ✅ 完成
-395
View File
@@ -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. 使用批量插入而不是循环插入
## 总结
批量排班接口已经完整实现,包括:
- ✅ 批量创建排班
- ✅ 批量更新排班
- ✅ 事务处理
- ✅ 数据验证
- ✅ 错误处理
可以正常使用了!
-101
View File
@@ -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. 检查数据库表结构是否正确
-271
View File
@@ -1,271 +0,0 @@
<?php
/**
* 向DiagnosisLogic.php添加小程序二维码相关方法
*/
$file = __DIR__ . '/app/adminapi/logic/tcm/DiagnosisLogic.php';
// 读取文件内容
$content = file_get_contents($file);
// 检查是否已经存在这些方法
if (strpos($content, 'generateMiniProgramQrcode') !== false) {
echo "方法已存在,无需添加\n";
exit(0);
}
// 要添加的方法
$methods = <<<'PHP'
/**
* @notes 生成小程序码
* @param array $params
* @return array|false
*/
public static function generateMiniProgramQrcode(array $params)
{
try {
$diagnosisId = $params['diagnosis_id'];
$patientId = $params['patient_id'];
$shareUserId = $params['share_user_id'];
// 获取小程序配置
$config = self::getMiniProgramConfig();
if (!$config) {
throw new \Exception('小程序配置未设置');
}
// 构建小程序路径和参数
$page = 'pages/order/monad/monad';
$scene = "id={$diagnosisId}&share_user={$shareUserId}";
// 调用微信接口生成小程序码
$qrcodeUrl = self::generateWxQrcode($config, $page, $scene);
if (!$qrcodeUrl) {
throw new \Exception('生成小程序码失败: ' . self::getError());
}
return [
'qrcode_url' => $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";
-90
View File
@@ -1,90 +0,0 @@
<?php
/**
* 快速检查快递100配置
*/
// 简单读取 .env 文件
$envFile = __DIR__ . '/.env';
if (!file_exists($envFile)) {
die("错误:.env 文件不存在\n");
}
$envContent = file_get_contents($envFile);
$lines = explode("\n", $envContent);
$config = [];
foreach ($lines as $line) {
$line = trim($line);
if (empty($line) || $line[0] === '#' || $line[0] === ';' || $line[0] === '[') {
continue;
}
if (strpos($line, '=') !== false) {
list($key, $value) = explode('=', $line, 2);
$key = trim($key);
$value = trim($value);
// 去除引号
$value = trim($value, '"\'');
$config[$key] = $value;
}
}
echo "===========================================\n";
echo "快递100配置检查\n";
echo "===========================================\n\n";
$customer = $config['LOGISTICS_KUAIDI100_CUSTOMER'] ?? '';
$key = $config['LOGISTICS_KUAIDI100_KEY'] ?? '';
$disable = $config['LOGISTICS_KUAIDI100_DISABLE'] ?? 'false';
echo "CUSTOMER: " . ($customer ? substr($customer, 0, 8) . '****' : '未配置') . "\n";
echo "KEY: " . ($key ? substr($key, 0, 4) . '****' : '未配置') . "\n";
echo "DISABLE: " . $disable . "\n\n";
if (empty($customer) || empty($key)) {
echo "❌ 错误:CUSTOMER 或 KEY 未配置\n";
exit(1);
}
if (strtolower($disable) === 'true' || $disable === '1') {
echo "❌ 错误:快递100已被禁用 (DISABLE = true)\n";
echo "解决方案:将 LOGISTICS_KUAIDI100_DISABLE 改为 false\n";
exit(1);
}
echo "✅ 配置正确!快递100应该可以正常使用\n\n";
echo "测试查询(需要真实运单号):\n";
echo "-------------------------------------------\n";
// 测试快递100 API
$testNumber = 'SF1234567890'; // 替换为真实运单号
$phone = '1234'; // 收件人手机后4位
$paramArr = [
'com' => '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";
-91
View File
@@ -1,91 +0,0 @@
<?php
/**
* 检查物流追踪记录状态
*/
// 数据库配置
$host = '39.97.232.35';
$port = '3306';
$database = 'cs_zyt';
$username = 'root';
$password = '749b460091647766';
$charset = 'utf8mb4';
// 连接数据库
$dsn = "mysql:host={$host};port={$port};charset={$charset}";
$pdo = new PDO($dsn, $username, $password);
$pdo->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";
-97
View File
@@ -1,97 +0,0 @@
<?php
/**
* 清除微信AccessToken缓存脚本
* 用于解决 AccessToken 失效问题
*/
require __DIR__ . '/vendor/autoload.php';
// 初始化应用
$app = new think\App();
$app->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);
}
-164
View File
@@ -1,164 +0,0 @@
<?php
/**
* 调试快递100 API响应
*/
// 从 .env 读取配置
$envFile = __DIR__ . '/.env';
$envContent = file_get_contents($envFile);
$lines = explode("\n", $envContent);
$config = [];
foreach ($lines as $line) {
$line = trim($line);
if (empty($line) || $line[0] === '#' || $line[0] === ';' || $line[0] === '[') {
continue;
}
if (strpos($line, '=') !== false) {
list($key, $value) = explode('=', $line, 2);
$key = trim($key);
$value = trim($value);
$value = trim($value, '"\'');
$config[$key] = $value;
}
}
$customer = $config['LOGISTICS_KUAIDI100_CUSTOMER'] ?? '';
$key = $config['LOGISTICS_KUAIDI100_KEY'] ?? '';
if (empty($customer) || empty($key)) {
die("错误:未配置快递100 CUSTOMER 或 KEY\n");
}
echo "===========================================\n";
echo "快递100 API 调试\n";
echo "===========================================\n\n";
// 测试用例
$testCases = [
[
'name' => '京东快递',
'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";
-70
View File
@@ -1,70 +0,0 @@
<?php
/**
* 安装物流追踪表
*/
// 数据库配置
$host = '39.97.232.35';
$port = '3306';
$database = 'cs_zyt';
$username = 'root';
$password = '749b460091647766';
$charset = 'utf8mb4';
// 连接数据库
$dsn = "mysql:host={$host};port={$port};charset={$charset}";
$pdo = new PDO($dsn, $username, $password);
$pdo->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";
@@ -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` - 数据库迁移脚本
@@ -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;
+28
View File
@@ -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;
+23
View File
@@ -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`;
@@ -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 = '测试医生';
@@ -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;
@@ -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`;
@@ -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`;
@@ -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');
@@ -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`);
@@ -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`;
@@ -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;
@@ -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;
@@ -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`;
@@ -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;
@@ -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());
@@ -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`;
@@ -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;
-165
View File
@@ -1,165 +0,0 @@
<?php
/**
* 预约API测试脚本
* 使用方法: php test_appointment_api.php
*/
// 测试配置
$baseUrl = 'http://localhost/adminapi'; // 修改为你的实际地址
$token = 'your_admin_token_here'; // 修改为实际的管理员token
// 测试数据
$doctorId = 1;
$patientId = 1;
$testDate = date('Y-m-d', strtotime('+1 day'));
echo "=== 医生预约API测试 ===\n\n";
// 测试1: 获取可用时间段
echo "测试1: 获取可用时间段\n";
echo "URL: {$baseUrl}/doctor.appointment/availableSlots\n";
echo "参数: doctor_id={$doctorId}, appointment_date={$testDate}, period=morning\n";
$url = "{$baseUrl}/doctor.appointment/availableSlots?doctor_id={$doctorId}&appointment_date={$testDate}&period=morning";
$response = makeRequest($url, 'GET', null, $token);
echo "响应: " . json_encode($response, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT) . "\n\n";
if (isset($response['data']['slots']) && count($response['data']['slots']) > 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;
}
-90
View File
@@ -1,90 +0,0 @@
<?php
/**
* 测试快递100自动识别功能
*/
// 从 .env 读取配置
$envFile = __DIR__ . '/.env';
$envContent = file_get_contents($envFile);
$lines = explode("\n", $envContent);
$config = [];
foreach ($lines as $line) {
$line = trim($line);
if (empty($line) || $line[0] === '#' || $line[0] === ';' || $line[0] === '[') {
continue;
}
if (strpos($line, '=') !== false) {
list($key, $value) = explode('=', $line, 2);
$key = trim($key);
$value = trim($value);
$value = trim($value, '"\'');
$config[$key] = $value;
}
}
$customer = $config['LOGISTICS_KUAIDI100_CUSTOMER'] ?? '';
$key = $config['LOGISTICS_KUAIDI100_KEY'] ?? '';
echo "===========================================\n";
echo "快递100 自动识别测试\n";
echo "===========================================\n\n";
$testNumber = 'JD0230761381812';
echo "测试单号: {$testNumber}\n";
echo "使用自动识别(com=auto\n\n";
$paramArr = [
'com' => '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";
-91
View File
@@ -1,91 +0,0 @@
<?php
/**
* 医生API测试脚本
* 用于测试医生列表、详情和排班API
*/
// 设置基础URL
$baseUrl = 'http://localhost:8000/api';
// 测试函数
function testApi($method, $endpoint, $params = []) {
global $baseUrl;
$url = $baseUrl . $endpoint;
// 构建查询字符串
if ($method === 'GET' && !empty($params)) {
$url .= '?' . http_build_query($params);
}
echo "\n========================================\n";
echo "测试: $method $endpoint\n";
echo "URL: $url\n";
echo "参数: " . json_encode($params, JSON_UNESCAPED_UNICODE) . "\n";
echo "========================================\n";
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_TIMEOUT, 10);
if ($method === 'POST') {
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($params));
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
}
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$error = curl_error($ch);
curl_close($ch);
if ($error) {
echo "错误: $error\n";
return null;
}
echo "HTTP状态码: $httpCode\n";
$data = json_decode($response, true);
echo "响应: " . json_encode($data, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT) . "\n";
return $data;
}
// 执行测试
echo "开始测试医生API...\n";
// 1. 测试获取医生列表
echo "\n\n【测试1】获取医生列表\n";
$result1 = testApi('GET', '/doctor/lists', [
'page_no' => 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";
?>
-62
View File
@@ -1,62 +0,0 @@
<?php
/**
* 测试物流自动更新逻辑
*/
echo "===========================================\n";
echo "物流自动更新逻辑测试\n";
echo "===========================================\n\n";
// 模拟查询条件
$conditions = [
'auto_update = 1',
'next_update_time <= ' . time(),
'is_signed = 0',
"current_state NOT IN ('3', '4', '14')", // 排除:已签收、退签、拒签
'delete_time IS NULL',
];
echo "查询条件:\n";
foreach ($conditions as $condition) {
echo " - {$condition}\n";
}
echo "\n说明:\n";
echo " 1. auto_update = 1:启用自动更新\n";
echo " 2. next_update_time <= now:到达更新时间\n";
echo " 3. is_signed = 0:未签收\n";
echo " 4. current_state NOT IN ('3','4','14'):排除终态\n";
echo " - 3: 已签收\n";
echo " - 4: 退签\n";
echo " - 14: 拒签\n";
echo " 5. delete_time IS NULL:未删除\n";
echo "\n过滤逻辑:\n";
echo " ✓ 已签收的快递不会被处理\n";
echo " ✓ 退签的快递不会被处理\n";
echo " ✓ 拒签的快递不会被处理\n";
echo " ✓ 已删除的记录不会被处理\n";
echo " ✓ 停止自动更新的不会被处理\n";
echo "\n会被处理的快递状态:\n";
echo " - 0: 在途\n";
echo " - 1: 揽收\n";
echo " - 2: 疑难\n";
echo " - 5: 派件中\n";
echo " - 6: 退回\n";
echo " - 7: 转投\n";
echo " - 8: 清关\n";
echo " - 10: 待清关\n";
echo " - 11: 清关中\n";
echo " - 12: 已清关\n";
echo " - 13: 清关异常\n";
echo "\n自动停止更新的触发条件:\n";
echo " 当快递状态变为以下任一状态时,系统会自动设置 auto_update = 0\n";
echo " - 3: 已签收\n";
echo " - 4: 退签\n";
echo " - 14: 拒签\n";
echo "\n===========================================\n";
echo "测试完成\n";
echo "===========================================\n";
-51
View File
@@ -1,51 +0,0 @@
<?php
/**
* 测试极兔速递单号识别
*/
require __DIR__ . '/vendor/autoload.php';
use app\common\service\ExpressTrackService;
$testNumber = 'JT5472795424064';
echo "===========================================\n";
echo "极兔速递单号测试\n";
echo "===========================================\n\n";
echo "测试单号: {$testNumber}\n\n";
// 测试官网链接生成
echo "1. 官网链接生成\n";
echo "-------------------------------------------\n";
$urls = ExpressTrackService::officialUrls($testNumber);
echo "顺丰: {$urls['sf']}\n";
echo "京东: {$urls['jd']}\n";
echo "极兔: {$urls['jt']}\n\n";
// 测试自动识别
echo "2. 快递公司自动识别\n";
echo "-------------------------------------------\n";
try {
$result = ExpressTrackService::query('auto', $testNumber, '');
echo "识别结果: {$result['carrier_label']}\n";
echo "快递100编码: {$result['kuaidi_com']}\n";
echo "数据源: {$result['source']}\n";
if (!empty($result['hint'])) {
echo "提示: {$result['hint']}\n";
}
if (!empty($result['traces'])) {
echo "\n物流轨迹:\n";
foreach ($result['traces'] as $idx => $trace) {
echo " " . ($idx + 1) . ". {$trace['time']} - {$trace['context']}\n";
}
}
} catch (\Exception $e) {
echo "查询失败: {$e->getMessage()}\n";
}
echo "\n===========================================\n";
echo "测试完成\n";
echo "===========================================\n";
-135
View File
@@ -1,135 +0,0 @@
<?php
/**
* 物流查询测试脚本
*
* 使用方法:
* php test_logistics.php
*
* 功能:
* 1. 测试快递100配置是否正确
* 2. 测试顺丰和京东快递查询
* 3. 生成官网查询链接
*/
require __DIR__ . '/vendor/autoload.php';
use app\common\service\ExpressTrackService;
echo "===========================================\n";
echo "物流查询测试脚本\n";
echo "===========================================\n\n";
// 测试运单号(请替换为真实运单号)
$testCases = [
[
'company' => '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";
-103
View File
@@ -1,103 +0,0 @@
<?php
/**
* 测试物流追踪查询(从数据库)
*/
// 数据库配置
$host = '39.97.232.35';
$port = '3306';
$database = 'cs_zyt';
$username = 'root';
$password = '749b460091647766';
$charset = 'utf8mb4';
// 连接数据库
$dsn = "mysql:host={$host};port={$port};charset={$charset}";
$pdo = new PDO($dsn, $username, $password);
$pdo->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";
-101
View File
@@ -1,101 +0,0 @@
<?php
/**
* UserSig测试脚本
* 用于验证SDKAppID和SecretKey是否正确
*
* 使用方法:
* php test_usersig.php
*/
// 加载ThinkPHP框架
require __DIR__ . '/vendor/autoload.php';
// 启动应用
$app = new think\App();
$app->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";