更新
This commit is contained in:
@@ -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 单聊记录(C2C:patient_{患者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;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user