更新
This commit is contained in:
@@ -1572,32 +1572,67 @@ class DiagnosisLogic extends BaseLogic
|
||||
/**
|
||||
* @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-视频
|
||||
* @return array{call_record_id:int}|false
|
||||
*/
|
||||
public static function startCall(array $params, array $adminInfo = [])
|
||||
{
|
||||
try {
|
||||
$adminId = (int)($params['admin_id'] ?? 0);
|
||||
$diagnosisId = (int)($params['diagnosis_id'] ?? 0);
|
||||
$patientId = (int)($params['patient_id'] ?? 0);
|
||||
$callType = (int)($params['call_type'] ?? 2);
|
||||
if ($adminId <= 0 || $diagnosisId <= 0 || $patientId <= 0) {
|
||||
self::setError('通话身份参数无效');
|
||||
return false;
|
||||
}
|
||||
if (!in_array($callType, [1, 2], true)) {
|
||||
self::setError('通话类型无效');
|
||||
return false;
|
||||
}
|
||||
$diagnosisQuery = Diagnosis::where('id', $diagnosisId)
|
||||
->where('patient_id', $patientId);
|
||||
$roleIds = array_map(
|
||||
'intval',
|
||||
AdminRole::where('admin_id', $adminId)->column('role_id')
|
||||
);
|
||||
if (in_array(2, $roleIds, true)) {
|
||||
$diagnosisQuery->where('assistant_id', $adminId);
|
||||
}
|
||||
if (DataScopeService::isEnabled()) {
|
||||
$visibleIds = DataScopeService::getVisibleAdminIds($adminId, $adminInfo);
|
||||
if ($visibleIds === []) {
|
||||
self::setError('诊单不存在或无权访问');
|
||||
return false;
|
||||
}
|
||||
if (is_array($visibleIds)) {
|
||||
$diagnosisQuery->whereIn('assistant_id', $visibleIds);
|
||||
}
|
||||
}
|
||||
$diagnosis = $diagnosisQuery->find();
|
||||
if (!$diagnosis) {
|
||||
self::setError('诊单不存在、患者不匹配或无权访问');
|
||||
return false;
|
||||
}
|
||||
|
||||
// 创建通话记录
|
||||
$record = \app\common\model\tcm\CallRecord::create([
|
||||
'diagnosis_id' => $diagnosisId,
|
||||
'caller_id' => $adminId,
|
||||
'caller_type' => 'doctor',
|
||||
'callee_id' => $patientId,
|
||||
'callee_type' => 'patient',
|
||||
'call_type' => $callType,
|
||||
'status' => 1, // 1-进行中
|
||||
'start_time' => time()
|
||||
]);
|
||||
|
||||
return true;
|
||||
'start_time' => time()
|
||||
]);
|
||||
|
||||
$callRecordId = (int)($record['id'] ?? 0);
|
||||
if ($callRecordId <= 0) {
|
||||
self::setError('创建通话记录后未取得记录ID');
|
||||
return false;
|
||||
}
|
||||
|
||||
return ['call_record_id' => $callRecordId];
|
||||
} catch (\Exception $e) {
|
||||
self::setError($e->getMessage());
|
||||
return false;
|
||||
@@ -1807,7 +1842,7 @@ class DiagnosisLogic extends BaseLogic
|
||||
* @param array $params
|
||||
* @return array
|
||||
*/
|
||||
public static function getCallRecords(array $params): array
|
||||
public static function getCallRecords(array $params): array
|
||||
{
|
||||
try {
|
||||
$records = \app\common\model\tcm\CallRecord::where('diagnosis_id', $params['diagnosis_id'])
|
||||
@@ -1826,16 +1861,323 @@ class DiagnosisLogic extends BaseLogic
|
||||
$urls = $decoded;
|
||||
}
|
||||
}
|
||||
$record['recording_urls_list'] = $urls;
|
||||
$record['recording_status_text'] = self::recordingStatusText((int)($record['recording_status'] ?? 0));
|
||||
}
|
||||
$record['recording_urls_list'] = $urls;
|
||||
$record['recording_status_text'] = self::recordingStatusText((int)($record['recording_status'] ?? 0));
|
||||
$record['transcription_status_text'] = self::transcriptionStatusText(
|
||||
(string)($record['transcription_status'] ?? '')
|
||||
);
|
||||
}
|
||||
|
||||
return $records;
|
||||
} catch (\Exception $e) {
|
||||
self::setError($e->getMessage());
|
||||
return [];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @notes 为指定通话记录创建幂等的实时转写会话
|
||||
* @return array|false
|
||||
*/
|
||||
public static function startCallTranscription(array $params)
|
||||
{
|
||||
try {
|
||||
$sessionId = trim((string)($params['transcription_session_id'] ?? ''));
|
||||
$language = trim((string)($params['language'] ?? 'zh-CN'));
|
||||
if ($sessionId === '' || mb_strlen($sessionId) > 128) {
|
||||
self::setError('转写会话ID无效');
|
||||
return false;
|
||||
}
|
||||
if ($language === '' || mb_strlen($language) > 32) {
|
||||
self::setError('转写语言无效');
|
||||
return false;
|
||||
}
|
||||
return \think\facade\Db::transaction(function () use ($params, $sessionId, $language) {
|
||||
$record = self::resolveCallRecordForTranscription($params, true, true);
|
||||
if (!$record) {
|
||||
return false;
|
||||
}
|
||||
$existingSession = trim((string)($record['transcription_session_id'] ?? ''));
|
||||
if ($existingSession !== '' && $existingSession !== $sessionId) {
|
||||
self::setError('该通话记录已绑定其他转写会话');
|
||||
return false;
|
||||
}
|
||||
if ($existingSession === '') {
|
||||
$now = time();
|
||||
$record->save([
|
||||
'transcription_session_id' => $sessionId,
|
||||
'transcription_language' => $language,
|
||||
'transcription_status' => 'running',
|
||||
'transcription_segment_count' => 0,
|
||||
'transcript_text' => '',
|
||||
'transcription_started_at' => $now,
|
||||
'transcription_finished_at' => 0,
|
||||
'update_time' => $now,
|
||||
]);
|
||||
}
|
||||
|
||||
return [
|
||||
'call_record_id' => (int)$record['id'],
|
||||
'transcription_session_id' => $sessionId,
|
||||
'status' => (string)($record['transcription_status'] ?? 'running'),
|
||||
];
|
||||
});
|
||||
} catch (\Throwable $e) {
|
||||
self::setError($e->getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @notes 幂等写入已完成的实时转写分段,并刷新通话记录文字字段
|
||||
* @return array|false
|
||||
*/
|
||||
public static function upsertCallTranscriptSegments(array $params)
|
||||
{
|
||||
try {
|
||||
$sessionId = trim((string)($params['transcription_session_id'] ?? ''));
|
||||
$segments = $params['segments'] ?? null;
|
||||
if ($sessionId === '' || mb_strlen($sessionId) > 128) {
|
||||
self::setError('转写会话ID无效');
|
||||
return false;
|
||||
}
|
||||
if (!is_array($segments) || count($segments) < 1 || count($segments) > 50) {
|
||||
self::setError('转写分段数量必须为1到50条');
|
||||
return false;
|
||||
}
|
||||
$normalized = [];
|
||||
foreach ($segments as $segment) {
|
||||
if (!is_array($segment)) {
|
||||
self::setError('转写分段格式无效');
|
||||
return false;
|
||||
}
|
||||
$segmentId = trim((string)($segment['segment_id'] ?? ''));
|
||||
$text = trim((string)($segment['text'] ?? ''));
|
||||
$speakerUserId = trim((string)($segment['speaker_user_id'] ?? ''));
|
||||
$speakerRole = trim((string)($segment['speaker_role'] ?? 'unknown'));
|
||||
if ($segmentId === '' || mb_strlen($segmentId) > 160) {
|
||||
self::setError('转写分段ID无效');
|
||||
return false;
|
||||
}
|
||||
if ($text === '' || mb_strlen($text) > 4000) {
|
||||
self::setError('转写文字长度无效');
|
||||
return false;
|
||||
}
|
||||
if (mb_strlen($speakerUserId) > 160) {
|
||||
self::setError('说话人ID过长');
|
||||
return false;
|
||||
}
|
||||
if (!in_array($speakerRole, ['doctor', 'patient', 'unknown'], true)) {
|
||||
$speakerRole = 'unknown';
|
||||
}
|
||||
$normalized[] = [
|
||||
'segment_id' => $segmentId,
|
||||
'speaker_user_id' => $speakerUserId,
|
||||
'speaker_role' => $speakerRole,
|
||||
'timestamp_ms' => max(0, (int)($segment['timestamp'] ?? 0)),
|
||||
'text' => $text,
|
||||
];
|
||||
}
|
||||
|
||||
$result = \think\facade\Db::transaction(function () use (
|
||||
$params,
|
||||
$sessionId,
|
||||
$normalized
|
||||
) {
|
||||
$record = self::resolveCallRecordForTranscription($params, false, true);
|
||||
if (!$record) {
|
||||
return false;
|
||||
}
|
||||
if ((string)($record['transcription_session_id'] ?? '') !== $sessionId) {
|
||||
self::setError('转写会话与通话记录不匹配');
|
||||
return false;
|
||||
}
|
||||
if ((string)($record['transcription_status'] ?? '') !== 'running') {
|
||||
self::setError('该通话转写已经结束');
|
||||
return false;
|
||||
}
|
||||
$callRecordId = (int)$record['id'];
|
||||
$now = time();
|
||||
foreach ($normalized as $segment) {
|
||||
$values = array_merge($segment, [
|
||||
'call_record_id' => $callRecordId,
|
||||
'transcription_session_id' => $sessionId,
|
||||
'create_time' => $now,
|
||||
'update_time' => $now,
|
||||
]);
|
||||
\think\facade\Db::name('tcm_call_transcript_segment')
|
||||
->duplicate([
|
||||
'speaker_user_id',
|
||||
'speaker_role',
|
||||
'timestamp_ms',
|
||||
'text',
|
||||
'update_time',
|
||||
])
|
||||
->insert($values);
|
||||
}
|
||||
$snapshot = self::buildCallTranscriptSnapshot($callRecordId, $sessionId);
|
||||
$record->save([
|
||||
'transcription_segment_count' => $snapshot['segment_count'],
|
||||
'transcript_text' => $snapshot['transcript_text'],
|
||||
'update_time' => $now,
|
||||
]);
|
||||
return [
|
||||
'call_record_id' => $callRecordId,
|
||||
'segment_count' => (int)$snapshot['segment_count'],
|
||||
];
|
||||
});
|
||||
if ($result === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return [
|
||||
'call_record_id' => (int)$result['call_record_id'],
|
||||
'transcription_session_id' => $sessionId,
|
||||
'stored_segment_count' => (int)$result['segment_count'],
|
||||
];
|
||||
} catch (\Throwable $e) {
|
||||
self::setError($e->getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @notes 完成实时转写并将最终文字固化在通话记录上
|
||||
* @return array|false
|
||||
*/
|
||||
public static function finishCallTranscription(array $params)
|
||||
{
|
||||
try {
|
||||
$sessionId = trim((string)($params['transcription_session_id'] ?? ''));
|
||||
$requestedStatus = strtolower(trim((string)($params['status'] ?? 'completed')));
|
||||
$expectedCount = (int)($params['expected_segment_count'] ?? 0);
|
||||
if ($sessionId === '' || mb_strlen($sessionId) > 128 || $expectedCount < 0) {
|
||||
self::setError('转写完成参数无效');
|
||||
return false;
|
||||
}
|
||||
if (!in_array($requestedStatus, ['completed', 'partial', 'failed'], true)) {
|
||||
self::setError('转写完成状态无效');
|
||||
return false;
|
||||
}
|
||||
return \think\facade\Db::transaction(function () use (
|
||||
$params,
|
||||
$sessionId,
|
||||
$requestedStatus,
|
||||
$expectedCount
|
||||
) {
|
||||
$record = self::resolveCallRecordForTranscription($params, false, true);
|
||||
if (!$record) {
|
||||
return false;
|
||||
}
|
||||
if ((string)($record['transcription_session_id'] ?? '') !== $sessionId) {
|
||||
self::setError('转写会话与通话记录不匹配');
|
||||
return false;
|
||||
}
|
||||
$existingStatus = (string)($record['transcription_status'] ?? '');
|
||||
if ($existingStatus !== '' && $existingStatus !== 'running') {
|
||||
return [
|
||||
'call_record_id' => (int)$record['id'],
|
||||
'transcription_session_id' => $sessionId,
|
||||
'status' => $existingStatus,
|
||||
'segment_count' => (int)($record['transcription_segment_count'] ?? 0),
|
||||
];
|
||||
}
|
||||
|
||||
$callRecordId = (int)$record['id'];
|
||||
$snapshot = self::buildCallTranscriptSnapshot($callRecordId, $sessionId);
|
||||
$actualCount = (int)$snapshot['segment_count'];
|
||||
$finalStatus = $requestedStatus;
|
||||
if ($requestedStatus === 'completed' && $actualCount < $expectedCount) {
|
||||
$finalStatus = 'partial';
|
||||
}
|
||||
$now = time();
|
||||
$record->save([
|
||||
'transcription_status' => $finalStatus,
|
||||
'transcription_segment_count' => $actualCount,
|
||||
'transcript_text' => $snapshot['transcript_text'],
|
||||
'transcription_finished_at' => $now,
|
||||
'update_time' => $now,
|
||||
]);
|
||||
|
||||
return [
|
||||
'call_record_id' => $callRecordId,
|
||||
'transcription_session_id' => $sessionId,
|
||||
'status' => $finalStatus,
|
||||
'segment_count' => $actualCount,
|
||||
];
|
||||
});
|
||||
} catch (\Throwable $e) {
|
||||
self::setError($e->getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static function resolveCallRecordForTranscription(
|
||||
array $params,
|
||||
bool $requireRunning,
|
||||
bool $lock = false
|
||||
)
|
||||
{
|
||||
$diagnosisId = (int)($params['diagnosis_id'] ?? 0);
|
||||
$callRecordId = (int)($params['call_record_id'] ?? 0);
|
||||
$adminId = (int)($params['admin_id'] ?? 0);
|
||||
if ($diagnosisId <= 0 || $callRecordId <= 0 || $adminId <= 0) {
|
||||
self::setError('通话转写身份参数无效');
|
||||
return null;
|
||||
}
|
||||
$query = \app\common\model\tcm\CallRecord::where('id', $callRecordId)
|
||||
->where('diagnosis_id', $diagnosisId)
|
||||
->where('caller_id', $adminId)
|
||||
->where('caller_type', 'doctor');
|
||||
if ($lock) {
|
||||
$query->lock(true);
|
||||
}
|
||||
$record = $query->find();
|
||||
if (!$record) {
|
||||
self::setError('通话记录不存在或无权操作');
|
||||
return null;
|
||||
}
|
||||
if ($requireRunning && (int)($record['status'] ?? 0) !== 1) {
|
||||
self::setError('通话记录已经结束');
|
||||
return null;
|
||||
}
|
||||
return $record;
|
||||
}
|
||||
|
||||
/** @return array{segment_count:int,transcript_text:string} */
|
||||
private static function buildCallTranscriptSnapshot(int $callRecordId, string $sessionId): array
|
||||
{
|
||||
$rows = \think\facade\Db::name('tcm_call_transcript_segment')
|
||||
->where('call_record_id', $callRecordId)
|
||||
->where('transcription_session_id', $sessionId)
|
||||
->order('timestamp_ms asc, id asc')
|
||||
->select()
|
||||
->toArray();
|
||||
$labels = ['doctor' => '医生', 'patient' => '患者', 'unknown' => '未知说话人'];
|
||||
$lines = [];
|
||||
foreach ($rows as $row) {
|
||||
$text = trim((string)($row['text'] ?? ''));
|
||||
if ($text === '') {
|
||||
continue;
|
||||
}
|
||||
$role = (string)($row['speaker_role'] ?? 'unknown');
|
||||
$lines[] = ($labels[$role] ?? $labels['unknown']) . ':' . $text;
|
||||
}
|
||||
return [
|
||||
'segment_count' => count($rows),
|
||||
'transcript_text' => implode("\n", $lines),
|
||||
];
|
||||
}
|
||||
|
||||
private static function transcriptionStatusText(string $status): string
|
||||
{
|
||||
return [
|
||||
'running' => '录音转写中',
|
||||
'completed' => '文字已生成',
|
||||
'partial' => '文字部分保存',
|
||||
'failed' => '文字生成失败',
|
||||
][$status] ?? '未生成文字';
|
||||
}
|
||||
|
||||
/**
|
||||
* @notes 将 TRTC 房间号写入当前诊单通话记录,并尝试 API 合流云端录制
|
||||
|
||||
Reference in New Issue
Block a user