This commit is contained in:
Your Name
2026-03-27 18:06:12 +08:00
parent 9160c36735
commit 099bc1dd22
645 changed files with 276473 additions and 957 deletions
+268 -35
View File
@@ -16,7 +16,9 @@ namespace app\adminapi\logic\tcm;
use app\common\logic\BaseLogic;
use app\common\model\tcm\Diagnosis;
use app\common\model\tcm\ImChatMessage;
use app\common\model\DiagnosisViewRecord;
use think\facade\Db;
/**
* 中医辨房病因诊单逻辑
* Class DiagnosisLogic
@@ -499,7 +501,10 @@ class DiagnosisLogic extends BaseLogic
$doctorUserId = 'doctor_' . $adminId;
$query = Diagnosis::where('id', $patientId)
->where('delete_time', null)->find();
if(!$query){
self::setError('患者诊单已被删除');
return false;
}
// 患者userId(必须与小程序端一致)
$patientUserId = 'patient_' . $patientId;
@@ -524,7 +529,9 @@ class DiagnosisLogic extends BaseLogic
'userSig' => $userSig,
'assistant_id'=>$query->assistant_id?'doctor_'.$query->assistant_id:'',
'patientUserId' => $patientUserId, // 患者的userId(用于发起通话)
'expireTime' => 86400 // 24小时
'expireTime' => 86400, // 24小时
// 与 .env [trtc] ISLOCHOSTVOD 一致:true 允许浏览器本地录制并上传
'isLochostVod' => (bool)config('trtc.is_lochost_vod', false),
];
} catch (\Exception $e) {
self::setError($e->getMessage());
@@ -620,16 +627,11 @@ class DiagnosisLogic extends BaseLogic
}
/**
* @notes 拉取与本诊单相关的腾讯云 IM 单聊记录C2Cpatient_{患者ID} 与 全部医生/医助账号 doctor_* 分别会话后合并
* @notes 拉取与本诊单相关的 IM 单聊记录:本地归档 + 腾讯云漫游合并(归档突破云端约 7 天限制
*/
public static function getImChatMessagesForDiagnosis(int $diagnosisId)
{
try {
$config = self::getTrtcConfig();
if (!$config) {
self::setError('请先配置腾讯云 TRTC / IM 参数');
return false;
}
$diag = Diagnosis::where('id', $diagnosisId)->where('delete_time', null)->find();
if (!$diag) {
self::setError('诊单不存在');
@@ -641,6 +643,24 @@ class DiagnosisLogic extends BaseLogic
return false;
}
$patientImId = 'patient_' . $patientId;
$archived = self::loadArchivedImChatRows($diagnosisId);
$config = self::getTrtcConfig();
if (!$config) {
if (empty($archived)) {
self::setError('请先配置腾讯云 TRTC / IM 参数');
return false;
}
$lists = self::enrichImMessagesWithStaffNames($archived);
return [
'lists' => $lists,
'patient_im_id' => $patientImId,
'patient_name' => $diag['patient_name'] ?? '',
'doctor_accounts_queried' => [],
];
}
$doctorAccounts = self::collectAllDoctorImPeerAccounts();
$assistantId = isset($diag['assistant_id']) ? (int)$diag['assistant_id'] : 0;
if ($assistantId > 0) {
@@ -652,34 +672,12 @@ class DiagnosisLogic extends BaseLogic
return false;
}
$imService = new \app\common\service\TencentImService();
$merged = [];
foreach ($doctorAccounts as $docAccount) {
$batch = self::pullAllRoamMessages($imService, $docAccount, $patientImId);
foreach ($batch as $row) {
$merged[] = $row;
}
}
usort($merged, function ($a, $b) {
return ($a['time'] ?? 0) <=> ($b['time'] ?? 0);
});
$seen = [];
$unique = [];
foreach ($merged as $row) {
$k = $row['msg_id'] ?? '';
if ($k !== '' && isset($seen[$k])) {
continue;
}
if ($k !== '') {
$seen[$k] = true;
}
$unique[] = $row;
}
$unique = self::enrichImMessagesWithStaffNames($unique);
$live = self::pullLiveImChatMessagesForDiagnosis($diag);
$merged = self::mergeImMessagesByMsgId($archived, $live);
$merged = self::enrichImMessagesWithStaffNames($merged);
return [
'lists' => $unique,
'lists' => $merged,
'patient_im_id' => $patientImId,
'patient_name' => $diag['patient_name'] ?? '',
'doctor_accounts_queried' => $doctorAccounts,
@@ -690,6 +688,239 @@ class DiagnosisLogic extends BaseLogic
}
}
/**
* 定时任务:从腾讯云拉取漫游消息写入归档表
*
* @return array{inserted:int, skipped_live_empty:bool, error?:string}
*/
public static function syncImChatArchiveForDiagnosis(int $diagnosisId): array
{
$out = ['inserted' => 0, 'skipped_live_empty' => false];
try {
if (!self::getTrtcConfig()) {
$out['error'] = 'TRTC/IM 未配置';
return $out;
}
$diag = Diagnosis::where('id', $diagnosisId)->where('delete_time', null)->find();
if (!$diag) {
$out['error'] = '诊单不存在';
return $out;
}
$live = self::pullLiveImChatMessagesForDiagnosis($diag);
if (empty($live)) {
$out['skipped_live_empty'] = true;
return $out;
}
$live = self::enrichImMessagesWithStaffNames($live);
$patientId = (int)$diag['patient_id'];
$out['inserted'] = self::persistImChatArchiveRows($diagnosisId, $patientId, $live);
return $out;
} catch (\Exception $e) {
$out['error'] = $e->getMessage();
return $out;
}
}
/**
* @return array{diagnoses:int, inserted:int, errors:array<int, string>}
*/
public static function syncImChatArchiveBatch(int $sinceDays, int $limit, ?int $onlyDiagnosisId): array
{
$stats = ['diagnoses' => 0, 'inserted' => 0, 'errors' => []];
$limit = max(1, min(500, $limit));
$q = Diagnosis::where('delete_time', null);
if ($onlyDiagnosisId !== null && $onlyDiagnosisId > 0) {
$q->where('id', $onlyDiagnosisId);
} elseif ($sinceDays > 0) {
$q->where('update_time', '>=', time() - $sinceDays * 86400);
}
$ids = $q->order('id', 'desc')->limit($limit)->column('id');
foreach ($ids as $id) {
$stats['diagnoses']++;
$r = self::syncImChatArchiveForDiagnosis((int)$id);
if (!empty($r['error'])) {
$stats['errors'][] = 'diagnosis ' . $id . ': ' . $r['error'];
continue;
}
$stats['inserted'] += (int)($r['inserted'] ?? 0);
}
return $stats;
}
/**
* @return array<int, array<string, mixed>>
*/
private static function loadArchivedImChatRows(int $diagnosisId): array
{
if ($diagnosisId <= 0) {
return [];
}
$list = ImChatMessage::where('diagnosis_id', $diagnosisId)
->order('msg_time', 'asc')
->order('id', 'asc')
->select()
->toArray();
$out = [];
foreach ($list as $row) {
$out[] = [
'msg_id' => (string)($row['msg_id'] ?? ''),
'from_account' => (string)($row['from_account'] ?? ''),
'to_account' => (string)($row['to_account'] ?? ''),
'time' => (int)($row['msg_time'] ?? 0),
'is_from_doctor' => !empty($row['is_from_doctor']),
'msg_type' => (string)($row['msg_type'] ?? ''),
'text' => (string)($row['text'] ?? ''),
'image_url' => (string)($row['image_url'] ?? ''),
'file_url' => (string)($row['file_url'] ?? ''),
'file_name' => (string)($row['file_name'] ?? ''),
'raw_elem_type' => (string)($row['raw_elem_type'] ?? ''),
'from_staff_name' => (string)($row['from_staff_name'] ?? ''),
'doctor_peer_account' => (string)($row['doctor_peer_account'] ?? ''),
];
}
return $out;
}
/**
* @param array<int, array<string, mixed>> $archived
* @param array<int, array<string, mixed>> $live
* @return array<int, array<string, mixed>>
*/
private static function mergeImMessagesByMsgId(array $archived, array $live): array
{
$map = [];
$tail = [];
foreach ($archived as $r) {
$k = (string)($r['msg_id'] ?? '');
if ($k !== '') {
$map[$k] = $r;
} else {
$tail[] = $r;
}
}
foreach ($live as $r) {
$k = (string)($r['msg_id'] ?? '');
if ($k !== '') {
$map[$k] = $r;
} else {
$tail[] = $r;
}
}
$merged = array_values($map);
$merged = array_merge($merged, $tail);
usort($merged, function ($a, $b) {
return ($a['time'] ?? 0) <=> ($b['time'] ?? 0);
});
return $merged;
}
/**
* @param Diagnosis|array<string, mixed> $diag
* @return array<int, array<string, mixed>>
*/
private static function pullLiveImChatMessagesForDiagnosis($diag): array
{
$patientId = (int)$diag['patient_id'];
if ($patientId <= 0) {
return [];
}
$patientImId = 'patient_' . $patientId;
$doctorAccounts = self::collectAllDoctorImPeerAccounts();
$assistantId = isset($diag['assistant_id']) ? (int)$diag['assistant_id'] : 0;
if ($assistantId > 0) {
$doctorAccounts[] = 'doctor_' . $assistantId;
}
$doctorAccounts = array_values(array_unique($doctorAccounts));
if (empty($doctorAccounts)) {
return [];
}
$imService = new \app\common\service\TencentImService();
$merged = [];
foreach ($doctorAccounts as $docAccount) {
$batch = self::pullAllRoamMessages($imService, $docAccount, $patientImId);
foreach ($batch as $row) {
$merged[] = $row;
}
}
usort($merged, function ($a, $b) {
return ($a['time'] ?? 0) <=> ($b['time'] ?? 0);
});
$seen = [];
$unique = [];
foreach ($merged as $row) {
$k = $row['msg_id'] ?? '';
if ($k !== '' && isset($seen[$k])) {
continue;
}
if ($k !== '') {
$seen[$k] = true;
}
$unique[] = $row;
}
return $unique;
}
/**
* @param array<int, array<string, mixed>> $rows
*/
private static function persistImChatArchiveRows(int $diagnosisId, int $patientId, array $rows): int
{
$now = time();
$chunks = [];
foreach ($rows as $r) {
$msgId = (string)($r['msg_id'] ?? '');
if ($msgId === '') {
continue;
}
$chunks[] = [
'diagnosis_id' => $diagnosisId,
'patient_id' => $patientId,
'msg_id' => $msgId,
'from_account' => (string)($r['from_account'] ?? ''),
'to_account' => (string)($r['to_account'] ?? ''),
'msg_time' => (int)($r['time'] ?? 0),
'is_from_doctor' => !empty($r['is_from_doctor']) ? 1 : 0,
'msg_type' => (string)($r['msg_type'] ?? ''),
'text' => (string)($r['text'] ?? ''),
'image_url' => (string)($r['image_url'] ?? ''),
'file_url' => (string)($r['file_url'] ?? ''),
'file_name' => (string)($r['file_name'] ?? ''),
'raw_elem_type' => (string)($r['raw_elem_type'] ?? ''),
'from_staff_name' => (string)($r['from_staff_name'] ?? ''),
'doctor_peer_account' => (string)($r['doctor_peer_account'] ?? ''),
'create_time' => $now,
];
}
$inserted = 0;
foreach (array_chunk($chunks, 80) as $chunk) {
$inserted += self::insertIgnoreImChatBatch($chunk);
}
return $inserted;
}
/**
* @param array<int, array<string, mixed>> $chunk
*/
private static function insertIgnoreImChatBatch(array $chunk): int
{
if (empty($chunk)) {
return 0;
}
$table = (new ImChatMessage())->getTable();
$cols = array_keys($chunk[0]);
$colSql = '`' . implode('`,`', $cols) . '`';
$rowPh = '(' . implode(',', array_fill(0, count($cols), '?')) . ')';
$allPh = implode(',', array_fill(0, count($chunk), $rowPh));
$flat = [];
foreach ($chunk as $row) {
foreach ($cols as $c) {
$flat[] = $row[$c];
}
}
$sql = 'INSERT IGNORE INTO `' . $table . '` (' . $colSql . ') VALUES ' . $allPh;
return (int)Db::execute($sql, $flat);
}
/**
* 为 from_account = doctor_{id} 的消息补充后台姓名,便于前端区分不同医生
*
@@ -747,7 +978,9 @@ class DiagnosisLogic extends BaseLogic
if (!is_array($raw)) {
continue;
}
$out[] = self::normalizeTimMessage($raw);
$normalized = self::normalizeTimMessage($raw);
$normalized['doctor_peer_account'] = $operator;
$out[] = $normalized;
}
$complete = (int)$res['complete'];
if ($complete === 1) {
@@ -5,13 +5,34 @@ declare(strict_types=1);
namespace app\adminapi\logic\tcm;
use app\common\logic\BaseLogic;
use app\common\model\auth\AdminRole;
use app\common\model\tcm\PrescriptionLibrary;
use think\facade\Config;
/**
* 处方库逻辑层
*/
class PrescriptionLibraryLogic extends BaseLogic
{
/**
* @notes 是否可管理全部处方(超级管理员 或 配置中的管理员角色)
*/
public static function canManageAllPrescriptions(int $adminId, array $adminInfo): bool
{
if (!empty($adminInfo['root']) && (int) $adminInfo['root'] === 1) {
return true;
}
$allowRoles = Config::get('project.prescription_library_manage_all_roles', []);
if ($allowRoles === [] || $allowRoles === null) {
$allowRoles = Config::get('project.order_edit_all_roles', [0, 3]);
}
$myRoles = AdminRole::where('admin_id', $adminId)->column('role_id');
return count(array_intersect($myRoles, $allowRoles)) > 0;
}
/**
* @notes 添加处方库
*/
@@ -24,7 +45,7 @@ class PrescriptionLibraryLogic extends BaseLogic
}
$model = PrescriptionLibrary::create($params);
return $model->id;
return (int) $model->id;
} catch (\Exception $e) {
self::setError($e->getMessage());
return null;
@@ -34,7 +55,7 @@ class PrescriptionLibraryLogic extends BaseLogic
/**
* @notes 编辑处方库
*/
public static function edit(array $params, int $adminId): bool
public static function edit(array $params, int $adminId, bool $canManageAll = false): bool
{
try {
$model = PrescriptionLibrary::findOrEmpty($params['id']);
@@ -43,8 +64,7 @@ class PrescriptionLibraryLogic extends BaseLogic
return false;
}
// 权限检查:只有创建者或公开的处方才能编辑
if ($model->creator_id != $adminId && $model->is_public != 1) {
if (!$canManageAll && (int) $model->creator_id !== $adminId) {
self::setError('无权限编辑此处方');
return false;
}
@@ -65,7 +85,7 @@ class PrescriptionLibraryLogic extends BaseLogic
/**
* @notes 删除处方库
*/
public static function delete(int $id, int $adminId): bool
public static function delete(int $id, int $adminId, bool $canManageAll = false): bool
{
try {
$model = PrescriptionLibrary::findOrEmpty($id);
@@ -74,8 +94,7 @@ class PrescriptionLibraryLogic extends BaseLogic
return false;
}
// 权限检查:只有创建者才能删除
if ($model->creator_id != $adminId) {
if (!$canManageAll && (int) $model->creator_id !== $adminId) {
self::setError('无权限删除此处方');
return false;
}
@@ -91,7 +110,7 @@ class PrescriptionLibraryLogic extends BaseLogic
/**
* @notes 处方库详情
*/
public static function detail(int $id, int $adminId): ?array
public static function detail(int $id, int $adminId, bool $canManageAll = false): ?array
{
try {
$model = PrescriptionLibrary::findOrEmpty($id);
@@ -99,8 +118,7 @@ class PrescriptionLibraryLogic extends BaseLogic
return null;
}
// 权限检查:只有创建者或公开的处方才能查看
if ($model->creator_id != $adminId && $model->is_public != 1) {
if (!$canManageAll && (int) $model->creator_id !== $adminId && (int) $model->is_public !== 1) {
return null;
}