This commit is contained in:
Your Name
2026-03-11 14:33:49 +08:00
parent 38ad60f4bb
commit 08dd9cd307
57 changed files with 9547 additions and 379 deletions
+264
View File
@@ -0,0 +1,264 @@
# 医生角色关系说明
## 概述
医生和角色的关系通过中间表 `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` 的管理员
这样可以确保只查询出具有医生角色的管理员。
@@ -0,0 +1,99 @@
<?php
declare(strict_types=1);
namespace app\adminapi\controller\order;
use app\common\model\Order;
use think\response\Json;
/**
* 支付宝支付回调控制器
* Class AlipayNotifyController
* @package app\adminapi\controller\order
*/
class AlipayNotifyController
{
/**
* @notes 支付宝异步通知
* @return string
*/
public function notify()
{
try {
// 获取支付宝配置
$alipayConfig = config('pay.alipay');
// 获取POST数据
$data = request()->post();
// 验证签名
if (!$this->verifyAlipaySign($data, $alipayConfig['alipay_public_key'])) {
return 'fail';
}
// 验证金额
$order = Order::where('order_no', $data['out_trade_no'])->find();
if (!$order) {
return 'fail';
}
if ((float)$data['total_amount'] != (float)$order->amount) {
return 'fail';
}
// 验证交易状态
if ($data['trade_status'] == 'TRADE_SUCCESS' || $data['trade_status'] == 'TRADE_FINISHED') {
// 更新订单状态
if ($order->status == 1) { // 只有待支付的订单才能更新
$order->status = 2; // 已支付
$order->payment_method = 'alipay';
$order->payment_time = date('Y-m-d H:i:s');
$order->trade_no = $data['trade_no'];
$order->save();
}
}
return 'success';
} catch (\Exception $e) {
return 'fail';
}
}
/**
* @notes 验证支付宝签名
* @param array $data
* @param string $publicKey
* @return bool
*/
private function verifyAlipaySign(array $data, string $publicKey): bool
{
try {
// 获取签名
$sign = $data['sign'] ?? '';
unset($data['sign']);
unset($data['sign_type']);
// 按键排序
ksort($data);
// 构建签名字符串
$signStr = '';
foreach ($data as $key => $value) {
if ($value !== '' && $value !== null) {
$signStr .= $key . '=' . $value . '&';
}
}
$signStr = rtrim($signStr, '&');
// 验证签名
$publicKeyResource = openssl_pkey_get_public($publicKey);
$result = openssl_verify($signStr, base64_decode($sign), $publicKeyResource, OPENSSL_ALGO_SHA256);
openssl_free_key($publicKeyResource);
return $result === 1;
} catch (\Exception $e) {
return false;
}
}
}
@@ -32,7 +32,7 @@ class OrderController extends BaseAdminController
public function detail()
{
$params = (new OrderValidate())->post()->goCheck('detail');
$detail = OrderLogic::detail($params['id']);
$detail = OrderLogic::detail((int)$params['id']);
if (!$detail) {
return $this->fail('订单不存在');
@@ -66,7 +66,7 @@ class OrderController extends BaseAdminController
{
$params = (new OrderValidate())->post()->goCheck('edit');
$result = OrderLogic::edit($params['id'], $params);
$result = OrderLogic::edit((int)$params['id'], $params);
if (!$result) {
return $this->fail(OrderLogic::getError());
}
@@ -82,7 +82,26 @@ class OrderController extends BaseAdminController
{
$params = (new OrderValidate())->post()->goCheck('pay');
$result = OrderLogic::pay($params['id'], $params['payment_method']);
// 判断是否是补单支付
if (!empty($params['is_supplement']) && $params['is_supplement'] == 1) {
// 补单支付:通过订单号查找订单
if (empty($params['order_no'])) {
return $this->fail('补单支付需要提供订单号');
}
$order = \app\common\model\Order::where('order_no', $params['order_no'])->find();
if (!$order) {
return $this->fail('订单不存在');
}
$orderId = $order->id;
} else {
// 正常支付:通过订单ID
if (empty($params['id'])) {
return $this->fail('订单ID不能为空');
}
$orderId = (int)$params['id'];
}
$result = OrderLogic::pay($orderId, $params['payment_method']);
if (!$result) {
return $this->fail(OrderLogic::getError());
}
@@ -98,7 +117,7 @@ class OrderController extends BaseAdminController
{
$params = (new OrderValidate())->post()->goCheck('cancel');
$result = OrderLogic::cancel($params['id']);
$result = OrderLogic::cancel((int)$params['id']);
if (!$result) {
return $this->fail(OrderLogic::getError());
}
@@ -114,7 +133,7 @@ class OrderController extends BaseAdminController
{
$params = (new OrderValidate())->post()->goCheck('refund');
$result = OrderLogic::refund($params['id']);
$result = OrderLogic::refund((int)$params['id']);
if (!$result) {
return $this->fail(OrderLogic::getError());
}
@@ -130,7 +149,7 @@ class OrderController extends BaseAdminController
{
$params = (new OrderValidate())->post()->goCheck('delete');
$result = OrderLogic::delete($params['id']);
$result = OrderLogic::delete((int)$params['id']);
if (!$result) {
return $this->fail(OrderLogic::getError());
}
@@ -146,4 +165,92 @@ class OrderController extends BaseAdminController
{
return $this->dataLists(new OrderLists());
}
/**
* @notes 支付宝支付
* @return \think\response\Json
*/
public function alipay()
{
$params = (new OrderValidate())->post()->goCheck('pay');
// 补单支付不需要校验订单是否存在
if (!empty($params['is_supplement']) && $params['is_supplement'] == 1) {
// 补单支付:直接返回支付URL,不校验订单
$payUrl = OrderLogic::alipayPay(['order_no' => $params['order_no']]);
if (!$payUrl) {
return $this->fail(OrderLogic::getError());
}
return $this->data(['pay_url' => $payUrl]);
}
// 正常支付需要校验订单
$order = $this->getOrderByParams($params);
if (!$order) {
return $this->fail('订单不存在');
}
// 调用支付宝支付接口
$payUrl = OrderLogic::alipayPay($order);
if (!$payUrl) {
return $this->fail(OrderLogic::getError());
}
return $this->data(['pay_url' => $payUrl]);
}
/**
* @notes 微信支付
* @return \think\response\Json
*/
public function wechat()
{
$params = (new OrderValidate())->post()->goCheck('pay');
// 补单支付不需要校验订单是否存在
if (!empty($params['is_supplement']) && $params['is_supplement'] == 1) {
// 补单支付:直接返回支付数据,不校验订单
$payData = OrderLogic::wechatPay(['order_no' => $params['order_no']]);
if (!$payData) {
return $this->fail(OrderLogic::getError());
}
return $this->data($payData);
}
// 正常支付需要校验订单
$order = $this->getOrderByParams($params);
if (!$order) {
return $this->fail('订单不存在');
}
// 调用微信支付接口
$payData = OrderLogic::wechatPay($order);
if (!$payData) {
return $this->fail(OrderLogic::getError());
}
return $this->data($payData);
}
/**
* @notes 根据参数获取订单
* @param array $params
* @return mixed
*/
private function getOrderByParams(array $params)
{
if (!empty($params['is_supplement']) && $params['is_supplement'] == 1) {
// 补单支付:通过订单号查找
if (empty($params['order_no'])) {
return null;
}
return \app\common\model\Order::where('order_no', $params['order_no'])->find();
} else {
// 正常支付:通过订单ID
if (empty($params['id'])) {
return null;
}
return \app\common\model\Order::find((int)$params['id']);
}
}
}
@@ -0,0 +1,128 @@
<?php
declare(strict_types=1);
namespace app\adminapi\controller\order;
use app\common\model\Order;
use think\response\Xml;
/**
* 微信支付回调控制器
* Class WechatNotifyController
* @package app\adminapi\controller\order
*/
class WechatNotifyController
{
/**
* @notes 微信异步通知
* @return Xml
*/
public function notify()
{
try {
// 获取微信配置
$wechatConfig = config('pay.wechat');
// 获取XML数据
$xmlData = file_get_contents('php://input');
$data = $this->xmlToArray($xmlData);
// 验证签名
if (!$this->verifyWechatSign($data, $wechatConfig['api_key'])) {
return $this->xmlResponse('FAIL', '签名验证失败');
}
// 验证金额
$order = Order::where('order_no', $data['out_trade_no'])->find();
if (!$order) {
return $this->xmlResponse('FAIL', '订单不存在');
}
if ((int)$data['total_fee'] != (int)($order->amount * 100)) {
return $this->xmlResponse('FAIL', '金额不匹配');
}
// 验证交易状态
if ($data['result_code'] == 'SUCCESS') {
// 更新订单状态
if ($order->status == 1) { // 只有待支付的订单才能更新
$order->status = 2; // 已支付
$order->payment_method = 'wechat';
$order->payment_time = date('Y-m-d H:i:s');
$order->trade_no = $data['transaction_id'];
$order->save();
}
}
return $this->xmlResponse('SUCCESS', '支付成功');
} catch (\Exception $e) {
return $this->xmlResponse('FAIL', $e->getMessage());
}
}
/**
* @notes 验证微信签名
* @param array $data
* @param string $apiKey
* @return bool
*/
private function verifyWechatSign(array $data, string $apiKey): bool
{
try {
// 获取签名
$sign = $data['sign'] ?? '';
unset($data['sign']);
// 按键排序
ksort($data);
// 构建签名字符串
$signStr = '';
foreach ($data as $key => $value) {
if ($value !== '' && $value !== null) {
$signStr .= $key . '=' . $value . '&';
}
}
$signStr .= 'key=' . $apiKey;
// MD5签名
$computedSign = strtoupper(md5($signStr));
return $computedSign === $sign;
} catch (\Exception $e) {
return false;
}
}
/**
* @notes XML转数组
* @param string $xml
* @return array
*/
private function xmlToArray(string $xml): array
{
try {
$data = json_decode(json_encode(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA)), true);
return is_array($data) ? $data : [];
} catch (\Exception $e) {
return [];
}
}
/**
* @notes 返回XML响应
* @param string $returnCode
* @param string $returnMsg
* @return Xml
*/
private function xmlResponse(string $returnCode, string $returnMsg): Xml
{
$xml = '<xml>';
$xml .= '<return_code><![CDATA[' . $returnCode . ']]></return_code>';
$xml .= '<return_msg><![CDATA[' . $returnMsg . ']]></return_msg>';
$xml .= '</xml>';
return response($xml, 200, ['Content-Type' => 'application/xml']);
}
}
@@ -233,7 +233,24 @@ class DiagnosisController extends BaseAdminController
$result = DiagnosisLogic::getCallRecords($params);
return $this->data($result);
}
/**
* @notes 获取患者通话签名(用于测试)
* @return \think\response\Json
*/
public function getDoctorSignature()
{
$params = $this->request->get();
if (empty($params['patient_id'])) {
return $this->fail('医助理ID不能为空');
}
$result = DiagnosisLogic::getDoctorSignature((int)$params['patient_id']);
if ($result) {
return $this->data($result);
}
return $this->fail(DiagnosisLogic::getError());
}
/**
* @notes 获取患者通话签名(用于测试)
* @return \think\response\Json
@@ -300,14 +317,14 @@ class DiagnosisController extends BaseAdminController
$offset = ($page_no - 1) * $page_size;
$lists = \app\common\model\tcm\Diagnosis::where('patient_name|patient_phone|patient_id_card', 'like', '%' . $keyword . '%')
->field(['id', 'patient_name', 'patient_phone', 'patient_id_card', 'gender', 'age'])
$lists = \app\common\model\tcm\Diagnosis::where('patient_name|phone|id_card', 'like', '%' . $keyword . '%')
->field(['id', 'patient_name', 'phone', 'id_card', 'gender', 'age'])
->limit($offset, $page_size)
->order('id desc')
->select()
->toArray();
$count = \app\common\model\tcm\Diagnosis::where('patient_name|patient_phone|patient_id_card', 'like', '%' . $keyword . '%')
$count = \app\common\model\tcm\Diagnosis::where('patient_name|phone|id_card', 'like', '%' . $keyword . '%')
->count();
return $this->success('', [
+41 -7
View File
@@ -7,6 +7,7 @@ namespace app\adminapi\lists\order;
use app\adminapi\lists\BaseAdminDataLists;
use app\common\lists\ListsSearchInterface;
use app\common\model\Order;
use app\common\model\auth\AdminRole;
/**
* 订单列表
@@ -27,6 +28,33 @@ class OrderLists extends BaseAdminDataLists implements ListsSearchInterface
];
}
/**
* @notes 权限过滤 - 普通员工只能看自己创建的订单
* @param array $where
* @return void
*/
private function filterByPermission(array &$where): void
{
// 检查是否是超管(root字段为1)
if (!empty($this->adminInfo['root']) && $this->adminInfo['root'] == 1) {
// 超管不过滤,可以看所有订单
return;
}
// 主管角色ID列表(可根据实际调整)
$supervisorRoles = [0, 3];
// 检查当前用户是否是主管
$roleIds = \app\common\model\auth\AdminRole::where('admin_id', $this->adminId)->column('role_id');
// 如果不是主管,只能看自己创建的订单
$isSupervisor = count(array_intersect($roleIds, $supervisorRoles)) > 0;
if (!$isSupervisor) {
$where[] = ['creator_id', '=', $this->adminId];
}
}
/**
* @notes 获取列表
* @return array
@@ -38,11 +66,14 @@ class OrderLists extends BaseAdminDataLists implements ListsSearchInterface
{
$where = $this->searchWhere;
// 处理患者关键词搜索
// 权限控制:普通员工只能看自己创建的订单
$this->filterByPermission($where);
// 处理患者关键词搜索(从诊单表搜索)
if (!empty($this->params['patient_keyword'])) {
$where[] = ['patient_id', 'in', function ($query) {
$query->table('user')
->where('nickname|mobile', 'like', '%' . $this->params['patient_keyword'] . '%')
$query->table('zyt_tcm_diagnosis')
->where('patient_name|patient_phone', 'like', '%' . $this->params['patient_keyword'] . '%')
->field('id');
}];
}
@@ -57,7 +88,7 @@ class OrderLists extends BaseAdminDataLists implements ListsSearchInterface
}
return Order::where($where)
->with(['patient', 'creator', 'details'])
->with(['patient', 'creator'])
->order(['create_time' => 'desc'])
->limit($this->limitOffset, $this->pageSize)
->select()
@@ -72,11 +103,14 @@ class OrderLists extends BaseAdminDataLists implements ListsSearchInterface
{
$where = $this->searchWhere;
// 处理患者关键词搜索
// 权限控制:普通员工只能看自己创建的订单
$this->filterByPermission($where);
// 处理患者关键词搜索(从诊单表搜索)
if (!empty($this->params['patient_keyword'])) {
$where[] = ['patient_id', 'in', function ($query) {
$query->table('user')
->where('nickname|mobile', 'like', '%' . $this->params['patient_keyword'] . '%')
$query->table('zyt_tcm_diagnosis')
->where('patient_name|patient_phone', 'like', '%' . $this->params['patient_keyword'] . '%')
->field('id');
}];
}
+210 -22
View File
@@ -32,8 +32,6 @@ class OrderLogic
public static function create(array $params)
{
try {
Db::startTrans();
$order = new Order();
$order->order_no = self::generateOrderNo();
$order->patient_id = $params['patient_id'];
@@ -44,24 +42,8 @@ class OrderLogic
$order->remark = $params['remark'] ?? '';
$order->save();
// 创建订单详情
if (!empty($params['details'])) {
foreach ($params['details'] as $detail) {
$orderDetail = new OrderDetail();
$orderDetail->order_id = $order->id;
$orderDetail->related_type = $detail['related_type'];
$orderDetail->related_id = $detail['related_id'];
$orderDetail->quantity = $detail['quantity'] ?? 1;
$orderDetail->unit_price = $detail['unit_price'];
$orderDetail->total_price = $detail['total_price'];
$orderDetail->save();
}
}
Db::commit();
return $order;
} catch (\Exception $e) {
Db::rollback();
self::setError($e->getMessage());
return false;
}
@@ -198,9 +180,6 @@ class OrderLogic
return false;
}
// 删除订单详情
OrderDetail::where('order_id', $id)->delete();
// 删除订单
$order->delete();
@@ -218,7 +197,7 @@ class OrderLogic
*/
public static function detail(int $id): ?array
{
$order = Order::with(['patient', 'creator', 'details'])
$order = Order::with(['patient', 'creator'])
->find($id);
if (!$order) {
@@ -239,4 +218,213 @@ class OrderLogic
{
return self::$error;
}
/**
* @notes 生成支付宝签名
* @param array $params
* @param string $privateKey
* @return string
*/
private static function generateAlipaySign(array $params, string $privateKey): string
{
// 移除sign字段
unset($params['sign']);
// 按键排序
ksort($params);
// 构建签名字符串
$signStr = '';
foreach ($params as $key => $value) {
if ($value !== '' && $value !== null) {
$signStr .= $key . '=' . $value . '&';
}
}
$signStr = rtrim($signStr, '&');
// 使用私钥签名
$privateKeyResource = openssl_pkey_get_private($privateKey);
openssl_sign($signStr, $sign, $privateKeyResource, OPENSSL_ALGO_SHA256);
openssl_free_key($privateKeyResource);
return base64_encode($sign);
}
/**
* @notes 生成微信签名
* @param array $params
* @param string $apiKey
* @return string
*/
private static function generateWechatSign(array $params, string $apiKey): string
{
// 移除sign字段
unset($params['sign']);
// 按键排序
ksort($params);
// 构建签名字符串
$signStr = '';
foreach ($params as $key => $value) {
if ($value !== '' && $value !== null) {
$signStr .= $key . '=' . $value . '&';
}
}
$signStr .= 'key=' . $apiKey;
// MD5签名
return strtoupper(md5($signStr));
}
/**
* @notes 生成随机字符串
* @param int $length
* @return string
*/
private static function generateNonceStr(int $length = 32): string
{
$chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
$str = '';
for ($i = 0; $i < $length; $i++) {
$str .= $chars[mt_rand(0, strlen($chars) - 1)];
}
return $str;
}
/**
* @notes 支付宝支付
* @param $order
* @return string|false
*/
public static function alipayPay($order)
{
try {
// 这里调用支付宝SDK生成支付链接
// 示例:生成支付宝支付URL
$payUrl = self::generateAlipayUrl($order);
return $payUrl;
} catch (\Exception $e) {
self::setError($e->getMessage());
return false;
}
}
/**
* @notes 微信支付
* @param $order
* @return array|false
*/
public static function wechatPay($order)
{
try {
// 这里调用微信SDK生成支付二维码或链接
// 示例:生成微信支付数据
$payData = self::generateWechatPayData($order);
return $payData;
} catch (\Exception $e) {
self::setError($e->getMessage());
return false;
}
}
/**
* @notes 生成支付宝支付URL
* @param $order
* @return string
*/
private static function generateAlipayUrl($order): string
{
try {
// 获取订单号
$orderNo = is_array($order) ? $order['order_no'] : $order->order_no;
$amount = is_array($order) ? ($order['amount'] ?? 0) : $order->amount;
// 获取支付宝配置
$alipayConfig = config('pay.alipay');
if (empty($alipayConfig['app_id']) || empty($alipayConfig['merchant_private_key'])) {
throw new \Exception('支付宝配置不完整');
}
// 使用支付宝SDK生成支付URL
$params = [
'app_id' => $alipayConfig['app_id'],
'method' => 'alipay.trade.page.pay',
'charset' => 'UTF-8',
'sign_type' => 'RSA2',
'timestamp' => date('Y-m-d H:i:s'),
'version' => '1.0',
'notify_url' => $alipayConfig['notify_url'] ?: config('app.url') . '/api/order/alipay-notify',
'return_url' => $alipayConfig['return_url'] ?: config('app.url') . '/order',
'biz_content' => json_encode([
'out_trade_no' => $orderNo,
'product_code' => 'FAST_INSTANT_TRADE_PAY',
'total_amount' => $amount,
'subject' => '订单支付',
'body' => '订单号:' . $orderNo,
], JSON_UNESCAPED_UNICODE),
];
// 生成签名
$sign = self::generateAlipaySign($params, $alipayConfig['merchant_private_key']);
$params['sign'] = $sign;
// 生成支付URL
$payUrl = $alipayConfig['gateway_url'] . '?' . http_build_query($params);
return $payUrl;
} catch (\Exception $e) {
self::setError($e->getMessage());
return '';
}
}
/**
* @notes 生成微信支付数据
* @param $order
* @return array
*/
private static function generateWechatPayData($order): array
{
try {
// 获取订单号
$orderNo = is_array($order) ? $order['order_no'] : $order->order_no;
$amount = is_array($order) ? ($order['amount'] ?? 0) : $order->amount;
// 获取微信配置
$wechatConfig = config('pay.wechat');
if (empty($wechatConfig['app_id']) || empty($wechatConfig['mch_id']) || empty($wechatConfig['api_key'])) {
throw new \Exception('微信支付配置不完整');
}
// 生成微信支付数据
$payData = [
'appid' => $wechatConfig['app_id'],
'mch_id' => $wechatConfig['mch_id'],
'nonce_str' => self::generateNonceStr(),
'body' => '订单支付',
'out_trade_no' => $orderNo,
'total_fee' => (int)($amount * 100), // 转换为分
'spbill_create_ip' => request()->ip() ?: '127.0.0.1',
'notify_url' => $wechatConfig['notify_url'] ?: config('app.url') . '/api/order/wechat-notify',
'trade_type' => 'NATIVE', // 二维码支付
];
// 生成签名
$sign = self::generateWechatSign($payData, $wechatConfig['api_key']);
$payData['sign'] = $sign;
// 返回支付数据
return [
'pay_url' => $wechatConfig['gateway_url'] . '/pay/unifiedorder',
'pay_data' => $payData,
'order_no' => $orderNo,
];
} catch (\Exception $e) {
self::setError($e->getMessage());
return [];
}
}
}
@@ -703,6 +703,55 @@ class DiagnosisLogic extends BaseLogic
return false;
}
}
/**
* @notes 获取医助签名(供小程序调用)
* @param int $patientId
* @return array|bool
*/
public static function getDoctorSignature(int $patientId)
{
try {
// 获取配置
$config = self::getTrtcConfig();
if (!$config) {
self::setError('请先配置腾讯云TRTC参数');
return false;
}
// 患者userId
$patientUserId = 'doctor_' . $patientId;
// 生成患者的 UserSig
$userSig = self::generateUserSig(
$config['sdkAppId'],
$config['secretKey'],
$patientUserId
);
if (!$userSig) {
self::setError('生成签名失败');
return false;
}
// 确保患者账号已导入IM
self::ensurePatientImAccount($patientId, $patientUserId);
\think\facade\Log::info('小程序获取患者签名成功 - doctor_id: ' . $patientId . ', user_id: ' . $patientUserId);
return [
'sdkAppId' => (int)$config['sdkAppId'],
'userId' => $patientUserId,
'userSig' => $userSig,
'expireTime' => 86400
];
} catch (\Exception $e) {
\think\facade\Log::error('获取患者签名失败 - patient_id: ' . $patientId . ', error: ' . $e->getMessage());
self::setError($e->getMessage());
return false;
}
}
/**
* @notes 获取患者签名(供小程序调用)
@@ -16,17 +16,17 @@ class OrderValidate extends BaseValidate
protected $rule = [
'id' => 'require|integer',
'patient_id' => 'require|integer',
'creator_id' => 'require|integer',
'order_type' => 'require|in:1,2,3',
'amount' => 'require|float',
'status' => 'require|in:1,2,3,4',
'payment_method' => 'in:alipay,wechat,bank',
'payment_method' => 'in:alipay,wechat',
'remark' => 'string|max:500',
'order_no' => 'string|max:50',
'is_supplement' => 'in:0,1',
];
protected $message = [
'patient_id.require' => '患者ID必填',
'creator_id.require' => '创建人ID必填',
'order_type.require' => '订单类型必填',
'order_type.in' => '订单类型不正确',
'amount.require' => '订单金额必填',
@@ -36,10 +36,10 @@ class OrderValidate extends BaseValidate
];
protected $scene = [
'create' => ['patient_id', 'creator_id', 'order_type', 'amount'],
'create' => ['patient_id', 'order_type', 'amount'],
'edit' => ['id', 'remark'],
'detail' => ['id'],
'pay' => ['id', 'payment_method'],
'pay' => ['payment_method'],
'cancel' => ['id'],
'refund' => ['id'],
'delete' => ['id'],
@@ -0,0 +1,73 @@
<?php
namespace app\api\controller;
use app\api\logic\DoctorLogic;
/**
* 医生控制器
* Class DoctorController
* @package app\api\controller
*/
class DoctorController extends BaseApiController
{
public array $notNeedLogin = ['lists', 'detail'];
/**
* @notes 获取医生列表
* @return \think\response\Json
*/
public function lists()
{
$page_no = $this->request->get('page_no', 1);
$page_size = $this->request->get('page_size', 10);
$keyword = $this->request->get('keyword', '');
$params = [
'page_no' => $page_no,
'page_size' => $page_size,
'keyword' => $keyword
];
$result = DoctorLogic::getDoctorList($params);
return $this->success('', $result);
}
/**
* @notes 获取医生详情
* @return \think\response\Json
*/
public function detail()
{
$doctorId = $this->request->get('id', 0);
if (!$doctorId) {
return $this->fail('医生ID不能为空');
}
$result = DoctorLogic::getDoctorDetail($doctorId);
if (!$result) {
return $this->fail('医生不存在');
}
return $this->success('', $result);
}
/**
* @notes 获取医生排班
* @return \think\response\Json
*/
public function roster()
{
$doctorId = $this->request->get('doctor_id', 0);
$date = $this->request->get('date', '');
if (!$doctorId || !$date) {
return $this->fail('参数不能为空');
}
$result = DoctorLogic::getDoctorRoster($doctorId, $date);
return $this->success('', $result);
}
}
+152
View File
@@ -0,0 +1,152 @@
<?php
namespace app\api\logic;
use app\common\logic\BaseLogic;
use app\common\model\auth\Admin;
use app\common\model\auth\AdminRole;
use app\common\model\doctor\Roster;
/**
* 医生逻辑层
* Class DoctorLogic
* @package app\api\logic
*/
class DoctorLogic extends BaseLogic
{
/**
* @notes 获取医生列表
* @param array $params
* @return array
*/
public static function getDoctorList($params = [])
{
$page_no = $params['page_no'] ?? 1;
$page_size = $params['page_size'] ?? 10;
$keyword = $params['keyword'] ?? '';
$offset = ($page_no - 1) * $page_size;
// 通过中间表 zyt_admin_role 查询 role_id = 1 的医生
$adminIds = AdminRole::where('role_id', 1)->column('admin_id');
if (empty($adminIds)) {
return [
'lists' => [],
'count' => 0,
'page_no' => $page_no,
'page_size' => $page_size
];
}
$query = Admin::whereIn('id', $adminIds)
->where('disable', 0);
// 搜索关键词
if (!empty($keyword)) {
$query->where('name|account', 'like', '%' . $keyword . '%');
}
$lists = $query->field(['id', 'name', 'account', 'avatar'])
->limit($offset, $page_size)
->order('id asc')
->select()
->toArray();
// 处理医生数据
$doctors = [];
foreach ($lists as $doctor) {
$doctors[] = [
'id' => $doctor['id'],
'name' => $doctor['name'],
'account' => $doctor['account'],
'avatar' => $doctor['avatar'] ?? '',
'specialty' => '医生', // 可根据需要扩展
'title' => '医生', // 可根据需要扩展
'rating' => '9.8', // 可根据需要从其他表获取
];
}
// 获取总数
$countQuery = Admin::whereIn('id', $adminIds)
->where('disable', 0);
if (!empty($keyword)) {
$countQuery->where('name|account', 'like', '%' . $keyword . '%');
}
$count = $countQuery->count();
return [
'lists' => $doctors,
'count' => $count,
'page_no' => $page_no,
'page_size' => $page_size
];
}
/**
* @notes 获取医生详情
* @param int $doctorId
* @return array|null
*/
public static function getDoctorDetail($doctorId)
{
// 检查该医生是否有医生角色(role_id = 1)
$hasRole = AdminRole::where('admin_id', $doctorId)
->where('role_id', 1)
->exists();
if (!$hasRole) {
return null;
}
$doctor = Admin::where('id', $doctorId)
->where('disable', 0)
->field(['id', 'name', 'account', 'avatar', 'mobile', 'email'])
->find();
if (!$doctor) {
return null;
}
$doctorData = $doctor->toArray();
return [
'id' => $doctorData['id'],
'name' => $doctorData['name'],
'account' => $doctorData['account'],
'avatar' => $doctorData['avatar'] ?? '',
'mobile' => $doctorData['mobile'] ?? '',
'email' => $doctorData['email'] ?? '',
'specialty' => '医生',
'title' => '医生',
'rating' => '4.8',
];
}
/**
* @notes 获取医生排班
* @param int $doctorId
* @param string $date
* @return array
*/
public static function getDoctorRoster($doctorId, $date)
{
// 检查该医生是否有医生角色
$hasRole = AdminRole::where('admin_id', $doctorId)
->where('role_id', 1)
->exists();
if (!$hasRole) {
return [];
}
$rosters = Roster::where('doctor_id', $doctorId)
->where('date', $date)
->select()
->toArray();
return $rosters;
}
}
+22 -10
View File
@@ -28,12 +28,12 @@ class Order extends BaseModel
}
/**
* @notes 关联患者
* @notes 关联患者(从诊单表)
* @return \think\model\relation\BelongsTo
*/
public function patient()
{
return $this->belongsTo('app\common\model\user\User', 'patient_id', 'id');
return $this->belongsTo('app\common\model\tcm\Diagnosis', 'patient_id', 'id');
}
/**
@@ -42,7 +42,7 @@ class Order extends BaseModel
*/
public function creator()
{
return $this->belongsTo('app\common\model\admin\Admin', 'creator_id', 'id');
return $this->belongsTo('app\common\model\auth\Admin', 'creator_id', 'id');
}
/**
@@ -62,7 +62,7 @@ class Order extends BaseModel
{
if ($value) {
$query->whereHas('patient', function ($q) use ($value) {
$q->where('nickname|mobile', 'like', '%' . $value . '%');
$q->where('patient_name|patient_phone', 'like', '%' . $value . '%');
});
}
}
@@ -133,8 +133,12 @@ class Order extends BaseModel
if ($value) {
return $value;
}
$patient = $this->patient()->find();
return $patient ? $patient->nickname : '';
try {
$patient = $this->patient()->find();
return $patient ? $patient->patient_name : '';
} catch (\Exception $e) {
return '';
}
}
/**
@@ -145,8 +149,12 @@ class Order extends BaseModel
if ($value) {
return $value;
}
$patient = $this->patient()->find();
return $patient ? $patient->mobile : '';
try {
$patient = $this->patient()->find();
return $patient ? $patient->patient_phone : '';
} catch (\Exception $e) {
return '';
}
}
/**
@@ -157,7 +165,11 @@ class Order extends BaseModel
if ($value) {
return $value;
}
$creator = $this->creator()->find();
return $creator ? $creator->name : '';
try {
$creator = $this->creator()->find();
return $creator ? $creator->name : '';
} catch (\Exception $e) {
return '';
}
}
}
+25
View File
@@ -0,0 +1,25 @@
<?php
/**
* 支付配置
*/
return [
// 支付宝配置
'alipay' => [
'app_id' => env('ALIPAY_APP_ID', ''),
'merchant_private_key' => env('ALIPAY_MERCHANT_PRIVATE_KEY', ''),
'alipay_public_key' => env('ALIPAY_PUBLIC_KEY', ''),
'notify_url' => env('ALIPAY_NOTIFY_URL', ''),
'return_url' => env('ALIPAY_RETURN_URL', ''),
'gateway_url' => env('ALIPAY_GATEWAY_URL', 'https://openapi.alipay.com/gateway.do'),
],
// 微信支付配置
'wechat' => [
'app_id' => env('WECHAT_APP_ID', ''),
'mch_id' => env('WECHAT_MCH_ID', ''),
'api_key' => env('WECHAT_API_KEY', ''),
'notify_url' => env('WECHAT_NOTIFY_URL', ''),
'gateway_url' => env('WECHAT_GATEWAY_URL', 'https://api.mch.weixin.qq.com'),
],
];
+46
View File
@@ -0,0 +1,46 @@
-- 添加测试医生数据
-- 1. 添加医生管理员到 zyt_admin 表
INSERT INTO `zyt_admin` (`name`, `account`, `avatar`, `mobile`, `email`, `disable`, `create_time`, `update_time`) VALUES
('张医生', 'doctor1', '/uploads/avatar/doctor1.jpg', '13800138000', 'doctor1@example.com', 0, UNIX_TIMESTAMP(), UNIX_TIMESTAMP()),
('李医生', 'doctor2', '/uploads/avatar/doctor2.jpg', '13800138001', 'doctor2@example.com', 0, UNIX_TIMESTAMP(), UNIX_TIMESTAMP()),
('王医生', 'doctor3', '/uploads/avatar/doctor3.jpg', '13800138002', 'doctor3@example.com', 0, UNIX_TIMESTAMP(), UNIX_TIMESTAMP()),
('陈医生', 'doctor4', '/uploads/avatar/doctor4.jpg', '13800138003', 'doctor4@example.com', 0, UNIX_TIMESTAMP(), UNIX_TIMESTAMP());
-- 2. 为医生分配医生角色(role_id = 1)
-- 假设上面插入的医生 ID 分别为 2, 3, 4, 5
INSERT INTO `zyt_admin_role` (`admin_id`, `role_id`) VALUES
(2, 1),
(3, 1),
(4, 1),
(5, 1);
-- 3. 验证医生数据
-- 查询所有医生
SELECT a.id, a.name, a.account, a.avatar, a.mobile, a.email
FROM `zyt_admin` a
INNER JOIN `zyt_admin_role` ar ON a.id = ar.admin_id
WHERE ar.role_id = 1 AND a.disable = 0;
-- 4. 添加医生排班数据(可选)
-- 假设医生 ID 为 2, 3, 4, 5
INSERT INTO `la_doctor_roster` (`doctor_id`, `date`, `period`, `status`, `quota`, `max_patients`, `create_time`, `update_time`) VALUES
-- 医生 2 的排班
(2, '2024-03-11', 'morning', 1, 20, 30, UNIX_TIMESTAMP(), UNIX_TIMESTAMP()),
(2, '2024-03-11', 'afternoon', 1, 20, 30, UNIX_TIMESTAMP(), UNIX_TIMESTAMP()),
(2, '2024-03-12', 'morning', 1, 20, 30, UNIX_TIMESTAMP(), UNIX_TIMESTAMP()),
-- 医生 3 的排班
(3, '2024-03-11', 'morning', 1, 20, 30, UNIX_TIMESTAMP(), UNIX_TIMESTAMP()),
(3, '2024-03-12', 'afternoon', 1, 20, 30, UNIX_TIMESTAMP(), UNIX_TIMESTAMP()),
-- 医生 4 的排班
(4, '2024-03-11', 'morning', 1, 20, 30, UNIX_TIMESTAMP(), UNIX_TIMESTAMP()),
(4, '2024-03-13', 'morning', 1, 20, 30, UNIX_TIMESTAMP(), UNIX_TIMESTAMP()),
-- 医生 5 的排班
(5, '2024-03-11', 'afternoon', 1, 20, 30, UNIX_TIMESTAMP(), UNIX_TIMESTAMP()),
(5, '2024-03-12', 'morning', 1, 20, 30, UNIX_TIMESTAMP(), UNIX_TIMESTAMP());
-- 5. 验证排班数据
SELECT * FROM `la_doctor_roster` WHERE doctor_id IN (2, 3, 4, 5);
+91
View File
@@ -0,0 +1,91 @@
<?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";
?>