fix: resolve rename/delete conflicts
This commit is contained in:
@@ -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
|
||||
@@ -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 会报错
|
||||
}
|
||||
```
|
||||
|
||||
## 总结
|
||||
|
||||
问题已修复,批量排班接口现在可以正常工作,并且返回详细的统计信息。
|
||||
@@ -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` 的管理员
|
||||
|
||||
这样可以确保只查询出具有医生角色的管理员。
|
||||
@@ -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. 测试日期范围查询
|
||||
@@ -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
|
||||
**状态:** ✅ 完成
|
||||
@@ -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. 使用批量插入而不是循环插入
|
||||
|
||||
## 总结
|
||||
|
||||
批量排班接口已经完整实现,包括:
|
||||
- ✅ 批量创建排班
|
||||
- ✅ 批量更新排班
|
||||
- ✅ 事务处理
|
||||
- ✅ 数据验证
|
||||
- ✅ 错误处理
|
||||
|
||||
可以正常使用了!
|
||||
@@ -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. 检查数据库表结构是否正确
|
||||
@@ -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";
|
||||
@@ -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";
|
||||
@@ -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";
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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";
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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";
|
||||
@@ -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";
|
||||
?>
|
||||
@@ -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";
|
||||
@@ -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";
|
||||
@@ -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";
|
||||
@@ -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";
|
||||
@@ -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";
|
||||
Reference in New Issue
Block a user