Files
zyt/server/app/adminapi/logic/tcm/DiagnosisLogic.php
T
2026-03-11 14:33:49 +08:00

1404 lines
49 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
// +----------------------------------------------------------------------
// | likeadmin快速开发前后端分离管理后台(PHP版)
// +----------------------------------------------------------------------
// | 欢迎阅读学习系统程序代码,建议反馈是我们前进的动力
// | 开源版本可自由商用,可去除界面版权logo
// | gitee下载:https://gitee.com/likeshop_gitee/likeadmin
// | github下载:https://github.com/likeshop-github/likeadmin
// | 访问官网:https://www.likeadmin.cn
// | likeadmin团队 版权所有 拥有最终解释权
// +----------------------------------------------------------------------
// | author: likeadminTeam
// +----------------------------------------------------------------------
namespace app\adminapi\logic\tcm;
use app\common\logic\BaseLogic;
use app\common\model\tcm\Diagnosis;
/**
* 中医辨房病因诊单逻辑
* Class DiagnosisLogic
* @package app\adminapi\logic\tcm
*/
class DiagnosisLogic extends BaseLogic
{
/**
* @notes 添加诊单
* @param array $params
* @return int|bool 成功返回ID,失败返回false
*/
public static function add(array $params)
{
try {
// 检查手机号是否重复
if (!empty($params['phone'])) {
$exists = Diagnosis::where('phone', $params['phone'])
->where('delete_time', null)
->find();
if ($exists) {
self::setError('该手机号已存在');
return false;
}
}
// 检查身份证号是否重复
if (!empty($params['id_card'])) {
$exists = Diagnosis::where('id_card', $params['id_card'])
->where('delete_time', null)
->find();
if ($exists) {
self::setError('该身份证号已存在');
return false;
}
}
// 生成患者ID
$params['patient_id'] = self::generatePatientId();
// 处理既往史数组
if (isset($params['past_history']) && is_array($params['past_history'])) {
$params['past_history'] = implode(',', $params['past_history']);
}
// 处理现病史多选字段
$multiSelectFields = [
'diet_condition', 'body_feeling', 'sleep_condition', 'eye_condition',
'head_feeling', 'sweat_condition', 'skin_condition', 'urine_condition',
'stool_condition', 'kidney_condition'
];
foreach ($multiSelectFields as $field) {
if (isset($params[$field]) && is_array($params[$field])) {
$params[$field] = implode(',', $params[$field]);
}
}
// 处理舌苔照片(JSON数组)
if (isset($params['tongue_images']) && is_array($params['tongue_images'])) {
$params['tongue_images'] = json_encode($params['tongue_images'], JSON_UNESCAPED_UNICODE);
}
// 处理检查报告(JSON数组)
if (isset($params['report_files']) && is_array($params['report_files'])) {
$params['report_files'] = json_encode($params['report_files'], JSON_UNESCAPED_UNICODE);
}
// 处理诊断日期
if (isset($params['diagnosis_date'])) {
$params['diagnosis_date'] = strtotime($params['diagnosis_date']);
}
$model = Diagnosis::create($params);
// 自动为患者创建 TRTC 账号
self::createPatientTrtcAccount($model->patient_id);
return $model->id;
} catch (\Exception $e) {
self::setError($e->getMessage());
return false;
}
}
/**
* @notes 生成患者ID
* @return int
*/
private static function generatePatientId(): int
{
// 获取当前最大的患者ID
$maxPatientId = Diagnosis::max('patient_id') ?? 100000;
// 返回下一个患者ID
return $maxPatientId + 1;
}
/**
* @notes 编辑诊单
* @param array $params
* @return bool
*/
public static function edit(array $params): bool
{
try {
// 检查手机号是否重复(排除当前记录)
if (!empty($params['phone'])) {
$exists = Diagnosis::where('phone', $params['phone'])
->where('id', '<>', $params['id'])
->where('delete_time', null)
->find();
if ($exists) {
self::setError('该手机号已存在');
return false;
}
}
// 检查身份证号是否重复(排除当前记录)
if (!empty($params['id_card'])) {
$exists = Diagnosis::where('id_card', $params['id_card'])
->where('id', '<>', $params['id'])
->where('delete_time', null)
->find();
if ($exists) {
self::setError('该身份证号已存在');
return false;
}
}
// 处理既往史数组
if (isset($params['past_history']) && is_array($params['past_history'])) {
$params['past_history'] = implode(',', $params['past_history']);
}
// 处理现病史多选字段
$multiSelectFields = [
'diet_condition', 'body_feeling', 'sleep_condition', 'eye_condition',
'head_feeling', 'sweat_condition', 'skin_condition', 'urine_condition',
'stool_condition', 'kidney_condition'
];
foreach ($multiSelectFields as $field) {
if (isset($params[$field]) && is_array($params[$field])) {
$params[$field] = implode(',', $params[$field]);
}
}
// 处理舌苔照片(JSON数组)
if (isset($params['tongue_images']) && is_array($params['tongue_images'])) {
$params['tongue_images'] = json_encode($params['tongue_images'], JSON_UNESCAPED_UNICODE);
}
// 处理检查报告(JSON数组)
if (isset($params['report_files']) && is_array($params['report_files'])) {
$params['report_files'] = json_encode($params['report_files'], JSON_UNESCAPED_UNICODE);
}
// 处理诊断日期
if (isset($params['diagnosis_date'])) {
$params['diagnosis_date'] = strtotime($params['diagnosis_date']);
}
Diagnosis::update($params);
// 如果是编辑,也确保患者有 TRTC 账号
$diagnosis = Diagnosis::find($params['id']);
if ($diagnosis) {
self::createPatientTrtcAccount($diagnosis->patient_id);
}
return true;
} catch (\Exception $e) {
self::setError($e->getMessage());
return false;
}
}
/**
* @notes 删除诊单
* @param array $params
* @return bool
*/
public static function delete(array $params): bool
{
try {
Diagnosis::destroy($params['id']);
return true;
} catch (\Exception $e) {
self::setError($e->getMessage());
return false;
}
}
/**
* @notes 诊单详情
* @param $params
* @return array
*/
public static function detail($params): array
{
$diagnosis = Diagnosis::findOrEmpty($params['id'])->toArray();
// 处理既往史为数组
if (!empty($diagnosis['past_history'])) {
$diagnosis['past_history'] = explode(',', $diagnosis['past_history']);
} else {
$diagnosis['past_history'] = [];
}
// 处理现病史多选字段为数组
$multiSelectFields = [
'diet_condition', 'body_feeling', 'sleep_condition', 'eye_condition',
'head_feeling', 'sweat_condition', 'skin_condition', 'urine_condition',
'stool_condition', 'kidney_condition'
];
foreach ($multiSelectFields as $field) {
if (!empty($diagnosis[$field])) {
$diagnosis[$field] = explode(',', $diagnosis[$field]);
} else {
$diagnosis[$field] = [];
}
}
// 处理舌苔照片(JSON转数组)
if (!empty($diagnosis['tongue_images'])) {
$diagnosis['tongue_images'] = json_decode($diagnosis['tongue_images'], true) ?: [];
} else {
$diagnosis['tongue_images'] = [];
}
// 处理检查报告(JSON转数组)
if (!empty($diagnosis['report_files'])) {
$diagnosis['report_files'] = json_decode($diagnosis['report_files'], true) ?: [];
} else {
$diagnosis['report_files'] = [];
}
// 处理诊断日期格式
if (!empty($diagnosis['diagnosis_date'])) {
$diagnosis['diagnosis_date'] = date('Y-m-d', $diagnosis['diagnosis_date']);
}
return $diagnosis;
}
/**
* @notes 检查手机号是否重复
* @param array $params
* @return array
*/
public static function checkPhone(array $params): array
{
$query = Diagnosis::where('phone', $params['phone'])
->where('delete_time', null);
// 编辑时排除当前记录
if (isset($params['id']) && $params['id']) {
$query->where('id', '<>', $params['id']);
}
$exists = $query->find();
return [
'exists' => !empty($exists),
'message' => $exists ? '该手机号已存在' : ''
];
}
/**
* @notes 检查身份证号是否重复
* @param array $params
* @return array
*/
public static function checkIdCard(array $params): array
{
$query = Diagnosis::where('id_card', $params['id_card'])
->where('delete_time', null);
// 编辑时排除当前记录
if (isset($params['id']) && $params['id']) {
$query->where('id', '<>', $params['id']);
}
$exists = $query->find();
return [
'exists' => !empty($exists),
'message' => $exists ? '该身份证号已存在' : ''
];
}
/**
* @notes 指派医助
* @param array $params
* @return bool
*/
public static function assign(array $params): bool
{
try {
Diagnosis::where('id', $params['id'])->update([
'assistant_id' => $params['assistant_id']
]);
return true;
} catch (\Exception $e) {
self::setError($e->getMessage());
return false;
}
}
/**
* @notes 获取通话签名
* @param array $params
* @return array|bool
*/
public static function getCallSignature(array $params)
{
try {
// 获取配置
$config = self::getTrtcConfig();
if (!$config) {
self::setError('请先配置腾讯云TRTC参数');
return false;
}
// 获取当前管理员ID(从参数中获取)
$adminId = $params['admin_id'] ?? 0;
$patientId = $params['patient_id'] ?? 0;
if (!$adminId) {
self::setError('获取管理员信息失败');
return false;
}
if (!$patientId) {
self::setError('获取患者信息失败');
return false;
}
// 医生userId
$doctorUserId = 'doctor_' . $adminId;
// 患者userId(必须与小程序端一致)
$patientUserId = 'patient_' . $patientId;
// 生成医生的 UserSig
$userSig = self::generateUserSig($config['sdkAppId'], $config['secretKey'], $doctorUserId);
if (!$userSig) {
self::setError('生成签名失败');
return false;
}
// 导入医生账号到IM
self::importDoctorAccountToIm($adminId, $doctorUserId);
// 确保患者账号也已导入IM(用于跨平台通话)
self::ensurePatientImAccount($patientId, $patientUserId);
return [
'sdkAppId' => (int)$config['sdkAppId'], // 确保返回整数
'userId' => $doctorUserId, // 医生的userId
'userSig' => $userSig,
'patientUserId' => $patientUserId, // 患者的userId(用于发起通话)
'expireTime' => 86400 // 24小时
];
} catch (\Exception $e) {
self::setError($e->getMessage());
return false;
}
}
/**
* @notes 确保患者IM账号存在
* @param int $patientId
* @param string $userId
* @return bool
*/
private static function ensurePatientImAccount(int $patientId, string $userId): bool
{
try {
// 获取患者信息
$diagnosis = Diagnosis::where('patient_id', $patientId)->find();
$nick = $diagnosis ? $diagnosis->patient_name : '患者' . $patientId;
// 调用IM服务导入账号
$imService = new \app\common\service\TencentImService();
$result = $imService->importAccount($userId, $nick);
if ($result['success']) {
\think\facade\Log::info('确保患者IM账号存在 - patient_id: ' . $patientId . ', user_id: ' . $userId . ', nick: ' . $nick);
return true;
} else {
\think\facade\Log::warning('导入患者IM账号失败 - patient_id: ' . $patientId . ', error: ' . ($result['message'] ?? '未知错误'));
return false;
}
} catch (\Exception $e) {
\think\facade\Log::error('确保患者IM账号失败: ' . $e->getMessage());
return false;
}
}
/**
* @notes 导入医生账号到腾讯云IM
* @param int $adminId
* @param string $userId
* @return bool
*/
private static function importDoctorAccountToIm(int $adminId, string $userId): bool
{
try {
// 获取医生信息
$admin = \app\common\model\auth\Admin::find($adminId);
$nick = $admin ? $admin->name : '医生' . $adminId;
// 调用IM服务导入账号
$imService = new \app\common\service\TencentImService();
$result = $imService->importAccount($userId, $nick);
if ($result['success']) {
\think\facade\Log::info('导入医生IM账号成功 - admin_id: ' . $adminId . ', user_id: ' . $userId . ', nick: ' . $nick);
return true;
} else {
\think\facade\Log::warning('导入医生IM账号失败 - admin_id: ' . $adminId . ', user_id: ' . $userId . ', error: ' . $result['message']);
return false;
}
} catch (\Exception $e) {
\think\facade\Log::error('导入医生IM账号异常 - admin_id: ' . $adminId . ', user_id: ' . $userId . ', error: ' . $e->getMessage());
return false;
}
}
/**
* @notes 发起通话
* @param array $params
* @return bool
*/
public static function startCall(array $params): bool
{
try {
// 获取当前管理员ID(从参数中获取)
$adminId = $params['admin_id'] ?? 0;
if (!$adminId) {
self::setError('获取管理员信息失败');
return false;
}
// 创建通话记录
\app\common\model\tcm\CallRecord::create([
'diagnosis_id' => $params['diagnosis_id'],
'caller_id' => $adminId,
'caller_type' => 'doctor',
'callee_id' => $params['patient_id'] ?? 0,
'callee_type' => 'patient',
'call_type' => $params['call_type'] ?? 2, // 1-语音 2-视频
'status' => 1, // 1-进行中
'start_time' => time()
]);
return true;
} catch (\Exception $e) {
self::setError($e->getMessage());
return false;
}
}
/**
* @notes 结束通话
* @param array $params
* @return bool
*/
public static function endCall(array $params): bool
{
try {
// 获取当前管理员ID(从参数中获取)
$adminId = $params['admin_id'] ?? 0;
if (!$adminId) {
self::setError('获取管理员信息失败');
return false;
}
// 查找最近的通话记录
$record = \app\common\model\tcm\CallRecord::where('diagnosis_id', $params['diagnosis_id'])
->where('caller_id', $adminId)
->where('status', 1)
->order('id', 'desc')
->find();
if ($record) {
$endTime = time();
$duration = $endTime - $record->start_time;
$record->save([
'status' => 2, // 2-已结束
'end_time' => $endTime,
'duration' => $duration
]);
}
return true;
} catch (\Exception $e) {
self::setError($e->getMessage());
return false;
}
}
/**
* @notes 获取通话记录
* @param array $params
* @return array
*/
public static function getCallRecords(array $params): array
{
try {
$records = \app\common\model\tcm\CallRecord::where('diagnosis_id', $params['diagnosis_id'])
->order('id', 'desc')
->select()
->toArray();
foreach ($records as &$record) {
$record['start_time_text'] = date('Y-m-d H:i:s', $record['start_time']);
$record['end_time_text'] = $record['end_time'] ? date('Y-m-d H:i:s', $record['end_time']) : '';
$record['duration_text'] = self::formatDuration($record['duration']);
}
return $records;
} catch (\Exception $e) {
self::setError($e->getMessage());
return [];
}
}
/**
* @notes 获取TRTC配置
* @return array|null
*/
private static function getTrtcConfig(): ?array
{
// 从配置文件或数据库读取
// 这里使用配置文件方式
$config = config('project.trtc');
if (empty($config['sdkAppId']) || empty($config['secretKey'])) {
return null;
}
return $config;
}
/**
* @notes 生成UserSig
* @param int $sdkAppId
* @param string $secretKey
* @param string $userId
* @param int $expire
* @return string|false
*/
private static function generateUserSig(int $sdkAppId, string $secretKey, string $userId, int $expire = 86400)
{
try {
// 使用官方的TLSSigAPIv2类生成UserSig
$api = new \app\common\service\TLSSigAPIv2($sdkAppId, $secretKey);
$userSig = $api->genUserSig($userId, $expire);
return $userSig;
} catch (\Exception $e) {
\think\facade\Log::error('生成UserSig失败: ' . $e->getMessage());
return false;
}
}
/**
* @notes 格式化通话时长
* @param int $seconds
* @return string
*/
private static function formatDuration(int $seconds): string
{
if ($seconds < 60) {
return $seconds . '秒';
} elseif ($seconds < 3600) {
$minutes = floor($seconds / 60);
$secs = $seconds % 60;
return $minutes . '分' . $secs . '秒';
} else {
$hours = floor($seconds / 3600);
$minutes = floor(($seconds % 3600) / 60);
$secs = $seconds % 60;
return $hours . '小时' . $minutes . '分' . $secs . '秒';
}
}
/**
* @notes 为患者创建 TRTC 账号(自动调用)
* @param int $patientId
* @return bool
*/
private static function createPatientTrtcAccount(int $patientId): bool
{
try {
$config = self::getTrtcConfig();
if (!$config || !$config['enable']) {
// 如果 TRTC 未启用,不创建账号
return false;
}
$userId = 'patient_' . $patientId;
// 检查是否已存在
$existingAccount = \app\common\model\tcm\PatientTrtc::where('patient_id', $patientId)
->where('delete_time', null)
->find();
// 生成 UserSig(长期有效,180天)
$expireTime = 86400 * 180;
$userSig = self::generateUserSig($config['sdkAppId'], $config['secretKey'], $userId, $expireTime);
if (!$userSig) {
return false;
}
$data = [
'patient_id' => $patientId,
'user_id' => $userId,
'user_sig' => $userSig,
'sdk_app_id' => $config['sdkAppId'],
'expire_time' => time() + $expireTime,
'is_active' => 1
];
if ($existingAccount) {
// 更新现有账号
$existingAccount->save($data);
\think\facade\Log::info('更新患者 TRTC 账号 - patient_id: ' . $patientId . ', user_id: ' . $userId);
} else {
// 创建新账号
\app\common\model\tcm\PatientTrtc::create($data);
\think\facade\Log::info('创建患者 TRTC 账号 - patient_id: ' . $patientId . ', user_id: ' . $userId);
}
// 导入账号到腾讯云IM(用于TUICallKit
self::importAccountToTencentIm($patientId, $userId);
return true;
} catch (\Exception $e) {
\think\facade\Log::error('创建患者 TRTC 账号失败 - patient_id: ' . $patientId . ', error: ' . $e->getMessage());
return false;
}
}
/**
* @notes 导入账号到腾讯云IM
* @param int $patientId
* @param string $userId
* @return bool
*/
private static function importAccountToTencentIm(int $patientId, string $userId): bool
{
try {
// 获取患者信息
$diagnosis = Diagnosis::where('patient_id', $patientId)
->order('id', 'desc')
->find();
$nick = $diagnosis ? $diagnosis->patient_name : '患者' . $patientId;
// 调用IM服务导入账号
$imService = new \app\common\service\TencentImService();
$result = $imService->importAccount($userId, $nick);
if ($result['success']) {
\think\facade\Log::info('导入患者IM账号成功 - patient_id: ' . $patientId . ', user_id: ' . $userId . ', nick: ' . $nick);
return true;
} else {
\think\facade\Log::warning('导入患者IM账号失败 - patient_id: ' . $patientId . ', user_id: ' . $userId . ', error: ' . $result['message']);
return false;
}
} catch (\Exception $e) {
\think\facade\Log::error('导入患者IM账号异常 - patient_id: ' . $patientId . ', user_id: ' . $userId . ', error: ' . $e->getMessage());
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 获取患者签名(供小程序调用)
* @param int $patientId
* @return array|bool
*/
public static function getPatientSignature(int $patientId)
{
try {
// 获取配置
$config = self::getTrtcConfig();
if (!$config) {
self::setError('请先配置腾讯云TRTC参数');
return false;
}
// 患者userId
$patientUserId = 'patient_' . $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('小程序获取患者签名成功 - patient_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 获取医助列表
* @return array
*/
public static function getAssistants()
{
try {
// 通过中间表查询角色ID为2的管理员(医助)
$assistants = \app\common\model\auth\Admin::alias('a')
->join('admin_role ar', 'a.id = ar.admin_id')
->where('ar.role_id', 2)
->where('a.disable', 0)
->field(['a.id', 'a.name', 'a.account'])
->order('a.id', 'asc')
->select()
->toArray();
return $assistants;
} catch (\Exception $e) {
\think\facade\Log::error('获取医助列表失败: ' . $e->getMessage());
return [];
}
}
/**
* @notes 获取医生列表
* @return array
*/
public static function getDoctors()
{
try {
// 通过中间表查询角色ID为1的管理员(医生)
$doctors = \app\common\model\auth\Admin::alias('a')
->join('admin_role ar', 'a.id = ar.admin_id')
->where('ar.role_id', 1)
->where('a.disable', 0)
->field(['a.id', 'a.name', 'a.account'])
->order('a.id', 'asc')
->select()
->toArray();
return $doctors;
} catch (\Exception $e) {
\think\facade\Log::error('获取医生列表失败: ' . $e->getMessage());
return [];
}
}
/**
* @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, int $retryCount = 0)
{
try {
// 记录请求参数
\think\facade\Log::info('开始生成小程序码', [
'page' => $page,
'scene' => $scene,
'app_id' => $config['app_id'],
'retry_count' => $retryCount
]);
// 获取access_token(如果是重试,强制刷新)
$forceRefresh = $retryCount > 0;
$accessToken = self::getWxAccessToken($config['app_id'], $config['app_secret'], $forceRefresh);
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
];
$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) && $retryCount < 1) {
\think\facade\Log::info('AccessToken失效,自动重试(第' . ($retryCount + 1) . '次)');
// 清除缓存
$cacheKey = 'wx_access_token_' . $config['app_id'];
cache($cacheKey, null);
// 递归调用,重试一次
return self::generateWxQrcode($config, $page, $scene, $retryCount + 1);
}
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, 'retry_count' => $retryCount]);
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
* @param bool $forceRefresh 是否强制刷新(不使用缓存)
* @return string|false
*/
private static function getWxAccessToken(string $appId, string $appSecret, bool $forceRefresh = false)
{
try {
$cacheKey = 'wx_access_token_' . $appId;
// 如果不是强制刷新,尝试从缓存获取
if (!$forceRefresh) {
$accessToken = cache($cacheKey);
if ($accessToken) {
\think\facade\Log::info('使用缓存的AccessToken');
return $accessToken;
}
} else {
\think\facade\Log::info('强制刷新AccessToken,清除缓存');
cache($cacheKey, null);
}
// 从微信服务器获取
$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;
}
/**
* @notes 获取诊单详情并记录查看
* @param array $params
* @return array|false
*/
public static function diagnosisDetailWithRecord(array $params)
{
try {
$diagnosisId = $params['id'];
$userId = $params['user_id'] ?? 0;
$shareUserId = $params['share_user_id'] ?? 0;
// 获取诊单详情
$diagnosis = self::detail(['id' => $diagnosisId]);
if (!$diagnosis) {
throw new \Exception('诊单不存在');
}
// 记录查看行为
self::recordDiagnosisView([
'user_id' => $userId,
'diagnosis_id' => $diagnosisId,
'patient_id' => $diagnosis['patient_id'] ?? 0,
'share_user_id' => $shareUserId
]);
return $diagnosis;
} catch (\Exception $e) {
self::setError($e->getMessage());
return false;
}
}
/**
* @notes 记录诊单查看
* @param array $params
* @return bool
*/
private static function recordDiagnosisView(array $params)
{
try {
$userId = $params['user_id'];
$diagnosisId = $params['diagnosis_id'];
$patientId = $params['patient_id'];
$shareUserId = $params['share_user_id'];
// 查找是否已有记录
$record = \app\common\model\DiagnosisViewRecord::where([
'user_id' => $userId,
'diagnosis_id' => $diagnosisId
])->find();
$now = time();
if ($record) {
// 更新记录
$record->view_count = $record->view_count + 1;
$record->last_view_time = $now;
$record->update_time = $now;
$record->save();
} else {
// 创建新记录
\app\common\model\DiagnosisViewRecord::create([
'user_id' => $userId,
'diagnosis_id' => $diagnosisId,
'patient_id' => $patientId,
'share_user_id' => $shareUserId,
'view_count' => 1,
'first_view_time' => $now,
'last_view_time' => $now,
'is_confirmed' => 0,
'create_time' => $now,
'update_time' => $now
]);
}
return true;
} catch (\Exception $e) {
\think\facade\Log::error('记录诊单查看失败: ' . $e->getMessage());
return false;
}
}
/**
* @notes 确认诊单
* @param array $params
* @return bool
*/
public static function confirmDiagnosisRecord(array $params)
{
try {
$userId = $params['user_id'] ?? 0;
$diagnosisId = $params['id'];
$shareUserId = $params['share_user_id'] ?? 0;
if(!$userId){
throw new \Exception('请重新登录!');
}
// 查找查看记录
$record = \app\common\model\DiagnosisViewRecord::where([
'user_id' => $userId,
'diagnosis_id' => $diagnosisId
])->find();
$now = time();
if ($record) {
// 更新确认状态
$record->is_confirmed = 1;
$record->confirmed_time = $now;
$record->update_time = $now;
$record->save();
} else {
// 如果没有查看记录,创建一条已确认的记录
$diagnosis = Diagnosis::find($diagnosisId);
if (!$diagnosis) {
throw new \Exception('诊单不存在');
}
// 再次检查是否存在记录,防止并发创建重复记录
$existingRecord = \app\common\model\DiagnosisViewRecord::where([
'user_id' => $userId,
'diagnosis_id' => $diagnosisId
])->find();
if (!$existingRecord) {
\app\common\model\DiagnosisViewRecord::create([
'user_id' => $userId,
'diagnosis_id' => $diagnosisId,
'patient_id' => $diagnosis->patient_id ?? 0,
'share_user_id' => $shareUserId,
'view_count' => 1,
'first_view_time' => $now,
'last_view_time' => $now,
'is_confirmed' => 1,
'confirmed_time' => $now,
'create_time' => $now,
'update_time' => $now
]);
}
}
return true;
} catch (\Exception $e) {
self::setError($e->getMessage());
return false;
}
}
/**
* @notes 获取就诊卡列表(供小程序调用)
* @param int $patientId
* @return array|false
*/
public static function getCardList(int $patientId)
{
try {
if (!$patientId) {
self::setError('患者ID不能为空');
return false;
}
// 查询该患者的所有诊单
$cardList = Diagnosis::where('patient_id', $patientId)
->where('delete_time', null)
->field([
'id', 'patient_id', 'patient_name', 'gender', 'age',
'diagnosis_date', 'diagnosis_type', 'syndrome_type',
'status', 'create_time', 'update_time'
])
->order('create_time', 'desc')
->select()
->toArray();
// 处理数据格式
foreach ($cardList as &$card) {
// 转换诊断日期为时间戳和格式化文本
if ($card['diagnosis_date']) {
$card['diagnosis_date_text'] = date('Y-m-d', $card['diagnosis_date']);
} else {
$card['diagnosis_date_text'] = '-';
}
// 添加性别描述
$card['gender_desc'] = $card['gender'] == 1 ? '男' : '女';
// 添加状态描述
$card['status_desc'] = $card['status'] == 1 ? '启用' : '禁用';
// 格式化创建时间
if ($card['create_time']) {
$card['create_time'] = date('Y-m-d H:i:s', $card['create_time']);
}
// 格式化更新时间
if ($card['update_time']) {
$card['update_time'] = date('Y-m-d H:i:s', $card['update_time']);
}
}
return $cardList;
} catch (\Exception $e) {
\think\facade\Log::error('获取就诊卡列表失败 - patient_id: ' . $patientId . ', error: ' . $e->getMessage());
self::setError($e->getMessage());
return false;
}
}
/**
* @notes 更新就诊卡(供小程序调用)
* @param array $params
* @return bool
*/
public static function updateCard(array $params)
{
try {
$diagnosisId = $params['id'] ?? 0;
$patientId = $params['patient_id'] ?? 0;
if (!$diagnosisId) {
self::setError('诊单ID不能为空');
return false;
}
if (!$patientId) {
self::setError('患者ID不能为空');
return false;
}
// 获取诊单信息
$diagnosis = Diagnosis::find($diagnosisId);
if (!$diagnosis) {
self::setError('诊单不存在');
return false;
}
// 权限验证:只能修改自己的诊单
if ($diagnosis->patient_id != $patientId) {
\think\facade\Log::warning('非法修改诊单 - diagnosis_id: ' . $diagnosisId . ', patient_id: ' . $patientId . ', actual_patient_id: ' . $diagnosis->patient_id);
self::setError('无权限修改此诊单');
return false;
}
// 准备更新数据
$updateData = [
'patient_name' => $params['patient_name'] ?? '',
'gender' => $params['gender'] ?? 1,
'age' => $params['age'] ?? 0,
'diagnosis_type' => $params['diagnosis_type'] ?? '',
'syndrome_type' => $params['syndrome_type'] ?? '',
'diabetes_type' => $params['diabetes_type'] ?? '',
// 现病史字段
'appetite' => $params['appetite'] ?? '',
'water_intake' => $params['water_intake'] ?? '',
'diet_condition' => $params['diet_condition'] ?? '',
'weight_change' => $params['weight_change'] ?? '',
'body_feeling' => $params['body_feeling'] ?? '',
'sleep_condition' => $params['sleep_condition'] ?? '',
'eye_condition' => $params['eye_condition'] ?? '',
'head_feeling' => $params['head_feeling'] ?? '',
'sweat_condition' => $params['sweat_condition'] ?? '',
'skin_condition' => $params['skin_condition'] ?? '',
'urine_condition' => $params['urine_condition'] ?? '',
'stool_condition' => $params['stool_condition'] ?? '',
'kidney_condition' => $params['kidney_condition'] ?? '',
'fatty_liver_degree' => $params['fatty_liver_degree'] ?? '',
// 既往史字段
'past_history' => $params['past_history'] ?? '',
'trauma_history' => $params['trauma_history'] ?? 0,
'surgery_history' => $params['surgery_history'] ?? 0,
'allergy_history' => $params['allergy_history'] ?? 0,
'family_history' => $params['family_history'] ?? 0,
'pregnancy_history' => $params['pregnancy_history'] ?? 0,
// 临床信息
'symptoms' => $params['symptoms'] ?? '',
'tongue_coating' => $params['tongue_coating'] ?? '',
'pulse' => $params['pulse'] ?? '',
'treatment_principle' => $params['treatment_principle'] ?? '',
'prescription' => $params['prescription'] ?? '',
'doctor_advice' => $params['doctor_advice'] ?? '',
'remark' => $params['remark'] ?? '',
'update_time' => time()
];
// 处理诊断日期
if (!empty($params['diagnosis_date'])) {
$updateData['diagnosis_date'] = strtotime($params['diagnosis_date']);
}
// 执行更新
$diagnosis->save($updateData);
\think\facade\Log::info('诊单更新成功 - diagnosis_id: ' . $diagnosisId . ', patient_id: ' . $patientId);
return true;
} catch (\Exception $e) {
\think\facade\Log::error('更新诊单失败 - error: ' . $e->getMessage());
self::setError($e->getMessage());
return false;
}
}
}