165 lines
6.8 KiB
PHP
165 lines
6.8 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace app\adminapi\controller\tcm;
|
|
|
|
use app\adminapi\controller\BaseAdminController;
|
|
use app\adminapi\logic\tcm\FollowupAudioLogic as Logic;
|
|
use app\common\service\followupaudio\FollowupAudioAccess as Access;
|
|
use app\common\service\followupaudio\FollowupAudioStream;
|
|
use app\common\service\followupaudio\FollowupAudioUpload as Upload;
|
|
use DomainException;
|
|
|
|
final class FollowupAudioController extends BaseAdminController
|
|
{
|
|
public function capabilities()
|
|
{
|
|
return $this->handle(false, ['diagnosis_id'], fn (array $p): array =>
|
|
Logic::capabilities($this->positive($p, 'diagnosis_id'), $this->adminId, $this->adminInfo));
|
|
}
|
|
|
|
public function uploadSession()
|
|
{
|
|
return $this->handle(true, ['diagnosis_id', 'file_name', 'total_bytes'], function (array $p): array {
|
|
Logic::requireEnabled(true);
|
|
return Upload::createSession($this->positive($p, 'diagnosis_id'), $this->textValue($p, 'file_name', 240),
|
|
$this->positive($p, 'total_bytes'), $this->adminId, $this->adminInfo);
|
|
});
|
|
}
|
|
|
|
public function uploadChunk()
|
|
{
|
|
return $this->handle(true, ['upload_id', 'index'], function (array $p): array {
|
|
Logic::requireEnabled(true);
|
|
$index = $p['index'] ?? null;
|
|
if (!(is_int($index) || is_string($index)) || !preg_match('/^\d{1,5}$/D', (string) $index)) {
|
|
throw new DomainException('录音分片序号无效');
|
|
}
|
|
$file = $this->request->file('file');
|
|
if (!$file instanceof \think\file\UploadedFile || !$file->isValid()) {
|
|
throw new DomainException('录音分片上传失败,请检查文件大小限制');
|
|
}
|
|
return Upload::putChunk($this->textValue($p, 'upload_id', 64), (int) $index,
|
|
$file->getPathname(), $this->adminId, $this->adminInfo);
|
|
});
|
|
}
|
|
|
|
public function uploadComplete()
|
|
{
|
|
return $this->handle(true, ['upload_id'], function (array $p): array {
|
|
Logic::requireEnabled(true);
|
|
return Upload::complete($this->textValue($p, 'upload_id', 64), $this->adminId, $this->adminInfo);
|
|
});
|
|
}
|
|
|
|
public function create()
|
|
{
|
|
return $this->handle(true, ['diagnosis_id', 'upload_id', 'recorded_at', 'model_key'], fn (array $p): array =>
|
|
Logic::create([
|
|
'diagnosis_id' => $this->positive($p, 'diagnosis_id'),
|
|
'upload_id' => $this->textValue($p, 'upload_id', 64),
|
|
'recorded_at' => $this->textValue($p, 'recorded_at', 19),
|
|
'model_key' => $this->textValue($p, 'model_key', 16),
|
|
], $this->adminId, $this->adminInfo));
|
|
}
|
|
|
|
public function lists()
|
|
{
|
|
return $this->handle(false, ['diagnosis_id'], fn (array $p): array =>
|
|
Logic::lists($this->positive($p, 'diagnosis_id'), $this->adminId, $this->adminInfo));
|
|
}
|
|
|
|
public function detail()
|
|
{
|
|
return $this->handle(false, ['id'], fn (array $p): array =>
|
|
Logic::detail($this->positive($p, 'id'), $this->adminId, $this->adminInfo));
|
|
}
|
|
|
|
public function saveDraft()
|
|
{
|
|
return $this->handle(true, ['id', 'version', 'items', 'channel_roles'], fn (array $p): array =>
|
|
Logic::saveDraft($this->positive($p, 'id'), $this->positive($p, 'version'),
|
|
$this->items($p), $this->adminId, $this->adminInfo,
|
|
array_key_exists('channel_roles', $p) ? $this->textValue($p, 'channel_roles', 20) : null), true);
|
|
}
|
|
|
|
public function apply()
|
|
{
|
|
return $this->handle(true, ['id', 'version', 'items'], fn (array $p): array =>
|
|
Logic::apply($this->positive($p, 'id'), $this->positive($p, 'version'),
|
|
$this->items($p), $this->adminId, $this->adminInfo), true);
|
|
}
|
|
|
|
public function retry()
|
|
{
|
|
return $this->handle(true, ['id'], fn (array $p): array =>
|
|
Logic::retry($this->positive($p, 'id'), $this->adminId, $this->adminInfo));
|
|
}
|
|
|
|
public function audio()
|
|
{
|
|
return $this->handle(false, ['id'], function (array $p): FollowupAudioStream {
|
|
$task = Access::task($this->positive($p, 'id'), $this->adminId, $this->adminInfo);
|
|
$upload = Upload::session((string) $task['upload_id']);
|
|
return new FollowupAudioStream(Upload::path($upload), (string) $upload['extension']);
|
|
});
|
|
}
|
|
|
|
private function handle(bool $post, array $allowed, callable $handler, bool $typedJson = false)
|
|
{
|
|
if ($post ? !$this->request->isPost() : !$this->request->isGet()) {
|
|
return $this->fail('请求方式错误');
|
|
}
|
|
// Review JSON carries strict booleans/integers and exact source evidence. Global trim
|
|
// would stringify scalars and alter quotations; keep all explicit validators below.
|
|
$params = $post ? $this->request->post('', null, $typedJson ? null : '') : $this->request->get();
|
|
if (array_diff(array_keys($params), $allowed) !== []) {
|
|
return $this->fail('请求包含不支持的字段');
|
|
}
|
|
try {
|
|
$this->adminInfo = Access::actor($this->adminId);
|
|
$result = $handler($params);
|
|
return $result instanceof \think\Response ? $result : $this->data($result);
|
|
} catch (DomainException $e) {
|
|
if (str_contains($e->getMessage(), 'FOLLOWUP_AUDIO_STALE_REVIEW')) {
|
|
return $this->fail('病历或日常记录已发生变化,请重新审阅差异后确认', [
|
|
'code' => 'FOLLOWUP_AUDIO_STALE_REVIEW',
|
|
]);
|
|
}
|
|
return $this->fail($e->getMessage());
|
|
} catch (\Throwable $e) {
|
|
// Never expose raw SQL, audio paths, transcripts, provider responses or keys.
|
|
return $this->fail('回访录音服务暂不可用,请联系管理员检查部署');
|
|
}
|
|
}
|
|
|
|
private function positive(array $p, string $key): int
|
|
{
|
|
$raw = $p[$key] ?? null;
|
|
if (!(is_int($raw) || is_string($raw)) || !preg_match('/^[1-9]\d{0,17}$/D', (string) $raw)) {
|
|
throw new DomainException('记录标识或版本无效');
|
|
}
|
|
return (int) $raw;
|
|
}
|
|
|
|
private function textValue(array $p, string $key, int $max): string
|
|
{
|
|
$raw = $p[$key] ?? '';
|
|
if (!is_string($raw) || strlen($raw) > $max) {
|
|
throw new DomainException('文本参数无效');
|
|
}
|
|
return trim($raw);
|
|
}
|
|
|
|
private function items(array $p): array
|
|
{
|
|
$items = $p['items'] ?? null;
|
|
if (!is_array($items) || !array_is_list($items) || count($items) > 500
|
|
|| strlen(json_encode($items, JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR)) > 2097152) {
|
|
throw new DomainException('审阅项目无效或数量超限');
|
|
}
|
|
return $items;
|
|
}
|
|
}
|