Files
zyt/server/app/adminapi/controller/tcm/FollowupAudioController.php
T

162 lines
6.4 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'], fn (array $p): array =>
Logic::saveDraft($this->positive($p, 'id'), $this->positive($p, 'version'),
$this->items($p), $this->adminId, $this->adminInfo));
}
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));
}
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)
{
if ($post ? !$this->request->isPost() : !$this->request->isGet()) {
return $this->fail('请求方式错误');
}
$params = $post ? $this->request->post() : $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;
}
}