From 03e6e10048d54dc549094ee523f7b57579f53293 Mon Sep 17 00:00:00 2001 From: Rmiku <46063139+Rmiku@users.noreply.github.com> Date: Thu, 24 Apr 2025 17:17:15 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=A3=80=E6=9F=A5=E6=8A=A5=E5=91=8A?= =?UTF-8?q?=E6=A8=A1=E5=9D=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Controllers/Report/InspectController.php | 68 ++++++ app/Http/Logics/Report/InspectLogic.php | 143 ++++++++++++ .../Report/Inspect/DetailsResource.php | 72 ++++++ .../Report/Inspect/ListsResource.php | 51 +++++ app/Services/HisHttp/Client.php | 31 +++ app/Services/MedicalAuth/Client.php | 205 ++++++++++++++++++ app/Utils/Traits/BuildCacheKeyName.php | 22 ++ routes/api.php | 14 ++ 8 files changed, 606 insertions(+) create mode 100644 app/Http/Controllers/Report/InspectController.php create mode 100644 app/Http/Logics/Report/InspectLogic.php create mode 100644 app/Http/Resources/Report/Inspect/DetailsResource.php create mode 100644 app/Http/Resources/Report/Inspect/ListsResource.php create mode 100644 app/Services/MedicalAuth/Client.php diff --git a/app/Http/Controllers/Report/InspectController.php b/app/Http/Controllers/Report/InspectController.php new file mode 100644 index 0000000..7d6c71c --- /dev/null +++ b/app/Http/Controllers/Report/InspectController.php @@ -0,0 +1,68 @@ +inspect_logic = new InspectLogic(); + } + + /** + * 获取检查报告列表 + * @param Request $request + * @param string $patient_id + * @return JsonResponse + * @throws GeneralException + */ + public function lists(Request $request, string $patient_id): JsonResponse + { + $validated = $request->validate([ + 'start_date' => 'required|date_format:Y-m-d', + 'end_date' => 'required|date_format:Y-m-d|after:start_date', + ], [ + 'start_date.required' => '请选择查询开始日期', + 'end_date.required' => '请选择查询结束日期', + 'start_date.date_format' => '日期格式错误', + 'end_date.date_format' => '日期格式错误', + 'end_date.after' => '查询日期错误', + ]); + + // 日期必须在3个月之内 + + $response = $this->inspect_logic->lists($patient_id, $validated['start_date'], $validated['end_date']); + + return jsonResponse(Response::HTTP_OK, 'success', ListsResource::make($response)->toArray()); + } + + /** + * 获取缴费记录详情 + * @param Request $request + * @param string $patient_id + * @param string $serial_no + * @return JsonResponse + * @throws GeneralException + */ + public function details(Request $request, string $patient_id, string $serial_no): JsonResponse + { + $response = $this->inspect_logic->details($patient_id, $serial_no); + + return jsonResponse(Response::HTTP_OK, 'success.', DetailsResource::make($response)->toArray()); + } +} diff --git a/app/Http/Logics/Report/InspectLogic.php b/app/Http/Logics/Report/InspectLogic.php new file mode 100644 index 0000000..9be5440 --- /dev/null +++ b/app/Http/Logics/Report/InspectLogic.php @@ -0,0 +1,143 @@ +authInitialize(); + $this->setChannel('refund'); + $this->his_client = app('HisHttpService'); + $this->patient_model = new Patient(); + } + + /** + * 获取报告列表 + * @param string $patient_number + * @param string $start_date + * @param string $end_date + * @return mixed + * @throws GeneralException + */ + public function lists(string $patient_number, string $start_date, string $end_date): mixed + { + $patient_info = $this->getHisPatientInfo($this->open_id, $patient_number); + + $response = $this->his_client->getInspectReportLists($patient_info['name'], $patient_info['idCardNo'], $start_date, $end_date); + + if (!isset($response['success']) || !$response['success']) { + throw new GeneralException($response['msg'] ?? '找不到该就诊卡!', Response::HTTP_SERVICE_UNAVAILABLE); + } + + Redis::setex($this->getInspectReportListsKey($this->open_id, $patient_info['patient_id']), 3600, json_encode($response['data'], JSON_UNESCAPED_UNICODE)); + return $response['data']; + } + + /** + * 获取报告详情 + * @param string $patient_number + * @param string $serial_no + * @return array + * @throws GeneralException + */ + public function details(string $patient_number, string $serial_no) + { + $patient_info = $this->getPatientInfo($this->open_id, $patient_number); + + $report_lists_cache_key = $this->getInspectReportListsKey($this->open_id, $patient_info['patient_id']); + if (!Redis::exists($report_lists_cache_key)) { + throw new GeneralException('找不到报告详情,请重新再试!', Response::HTTP_BAD_REQUEST); + } + + $lists = Redis::get($report_lists_cache_key); + $lists = json_decode($lists, true); + + $info = []; + foreach ($lists as $k => $v) { + if ($serial_no === $v['sourceId']) { + $info = []; + break; + } + } + + if (empty($info)) { + throw new GeneralException('找不到报告详情,请重新再试!', Response::HTTP_BAD_REQUEST); + } + + return $info; + } + + /** + * 获取数据库里的患者信息 + * @param string $open_id + * @param string $patient_number + * @return mixed + * @throws GeneralException + */ + protected function getPatientInfo(string $open_id, string $patient_number): mixed + { + $info = $this->patient_model->getBindPatientInfoByPatientNumber($open_id, $patient_number); + if (empty($info)) { + throw new GeneralException('找不到患者信息,请重新再试!', Response::HTTP_BAD_REQUEST); + } + + return $info; + } + + /** + * 获取HIS里的患者信息 + * @param string $open_id + * @param string $patient_number + * @return mixed + * @throws GeneralException + */ + protected function getHisPatientInfo(string $open_id, string $patient_number): mixed + { + $info = $this->getPatientInfo($open_id, $patient_number); + + $response = $this->his_client->getPatientInfo($info['patient_number'], CardType::OUTPATIENT_NO, $info['name']); + if (!isset($response['success']) || !$response['success']) { + throw new GeneralException($response['msg'] ?? '找不到该就诊卡!', Response::HTTP_SERVICE_UNAVAILABLE); + } + + return $response['data']; + } +} diff --git a/app/Http/Resources/Report/Inspect/DetailsResource.php b/app/Http/Resources/Report/Inspect/DetailsResource.php new file mode 100644 index 0000000..f86f49d --- /dev/null +++ b/app/Http/Resources/Report/Inspect/DetailsResource.php @@ -0,0 +1,72 @@ + + */ + public function toArray(Request $request = null): array + { + if (empty($this->resource)) { + return []; + } + + $lists = []; + foreach ($this->resource['response']['body'] as $k => $v) { + // 过滤掉诊断为接口调用正常的第一条数据 + if ($v['diagnose'] === '医院接口调用正常') { + continue; + } + + $lists[] = [ + 'clinic_no' => $v['clinicNo'], + 'serial_no' => $v['sourceId'], + 'source_form' => $v['sourceFrom'], + 'dept_id' => $v['dept']['localCode'], + 'dept_name' => $v['dept']['localText'], + 'doctor_id' => $v['doctor']['localText'], + 'doctor_name' => $v['doctor']['localText'], + 'exam_type' => $v['examMethod'], + 'exam_name' => $v['examName'], + 'exam_purpose' => $v['examPurpose'], + 'diagnose' => $v['diagnose'], + 'exam_view' => $v['examView'], + 'diagnose_opinion' =>$v['diagnose_opinion'], + 'created_time' => $v['createDt'], + 'check_time' => $v['checkDt'], + 'report_time' => $v['reportDt'], + 'comment' => $v['comment'], + // 患者信息 + 'patient_name' => $v['patient']['name'], + 'patient_gender' => $v['patient']['gender'], + 'patient_birth' => $v['patient']['birthday'], + // 冗余信息 + 'org_id' => $v['org']['localCode'], + 'org_name' => $v['org']['localText'], + 'equipment_name' => $v['equipmentName'], + 'equipment_no' => $v['equipmentNameNo'], + 'report_doctor_id' => $v['rpDoctor']['localCode'], + 'report_doctor_name' => $v['rpDoctor']['localText'], + 'report_org_id' => $v['rpOrg']['localCode'], + 'report_org_name' => $v['rpOrg']['localText'], + 'crisis_flag' => $v['crisisFlag'], + 'crisis_desc' => $v['crisisDesc'], + 'crisis_content' => $v['crisisContent'], + 'unusual_flag' => $v['unusualFlag'], + 'deal_flag' => $v['dealFlag'], + 'need_deal_flag' => $v['needDealFlag'], + // pacs 图片数组一般为空 + 'pacs_images' => (array) ($v['pacs'] ?? []) + ]; + } + + return $lists; + } +} diff --git a/app/Http/Resources/Report/Inspect/ListsResource.php b/app/Http/Resources/Report/Inspect/ListsResource.php new file mode 100644 index 0000000..bf7250c --- /dev/null +++ b/app/Http/Resources/Report/Inspect/ListsResource.php @@ -0,0 +1,51 @@ + + */ + public function toArray(Request $request = null): array + { + if (empty($this->resource)) { + return []; + } + + $lists = []; + foreach ($this->resource['response']['body'] as $k => $v) { + // 过滤掉诊断为接口调用正常的第一条数据 + if ($v['diagnose'] === '医院接口调用正常') { + continue; + } + + $lists[] = [ + 'clinic_no' => $v['clinicNo'], + 'serial_no' => $v['sourceId'], + 'source_form' => $v['sourceFrom'], + 'dept_id' => $v['dept']['localCode'], + 'dept_name' => $v['dept']['localText'], + 'doctor_id' => $v['doctor']['localText'], + 'doctor_name' => $v['doctor']['localText'], + 'exam_type' => $v['examMethod'], + 'exam_name' => $v['examName'], + 'exam_purpose' => $v['examPurpose'], + 'diagnose' => $v['diagnose'], + 'exam_view' => $v['examView'], + 'diagnose_opinion' =>$v['diagnose_opinion'], + 'created_time' => $v['createDt'], + 'check_time' => $v['checkDt'], + 'report_time' => $v['reportDt'], + 'comment' => $v['comment'], + ]; + } + + return $lists; + } +} diff --git a/app/Services/HisHttp/Client.php b/app/Services/HisHttp/Client.php index 89233e0..602fd7a 100644 --- a/app/Services/HisHttp/Client.php +++ b/app/Services/HisHttp/Client.php @@ -645,4 +645,35 @@ class Client } // Dictionary Module End + + // Inspect/Check Module Start + + /** + * 获取检查报告列表 + * @param string $patient_name 患者姓名 + * @param string $card_no 证件号,居民注册时的证件号,不区分证件类型,一般为身份证 + * @param string $start_date 查询起始日期,查询时间跨度必须小于3个月 + * @param string $end_date 查询截止日期 + * @param string $bar_code 条码,由患者直接录入或者扫描线下单据(可能是发票,处方等)获得 + * @param string $source_id 报告标识,机构内部唯一标识 + * @return mixed + * @throws GeneralException + */ + public function getInspectReportLists(string $patient_name, string $card_no, string $start_date, string $end_date, string $bar_code = '', string $source_id = ''): mixed + { + return $this->requestHandle('POST', '0rientQueryExaReport', [ + 'json' => [ + 'patientName' => $patient_name, + 'orgId' => '45592623X44040211A1001', // 机构ID 珠海市香洲区第二人民医院 + 'barCode' => $bar_code, + 'SourceId' => $source_id, + 'certificateNo' => $card_no, + 'startDt' => $start_date, + 'endDt' => $end_date, + ... $this->commonRequestData() + ] + ]); + } + + // Inspect/Check Module End } diff --git a/app/Services/MedicalAuth/Client.php b/app/Services/MedicalAuth/Client.php new file mode 100644 index 0000000..c093bb9 --- /dev/null +++ b/app/Services/MedicalAuth/Client.php @@ -0,0 +1,205 @@ + "string", + 'partner_id' => "string", + 'partner_secret' => "string", + 'channel' => "string", + 'org_chnl_crtf_codg' => "string", + 'org_codg' => "string", + 'org_app_id' => "string", + 'auth_return_url' => "string", + ])]*/ + private array $config; + + /** + * 城市编码 441721 香洲区 + * @var string + */ + private string $city_code = '441721'; + + /** + * 授权查询host地址 + * @var string + */ + private string $host = 'https://test-receiver.wecity.qq.com'; + // private string $host = 'https://mip-receiver.tengmed.com'; + + /** + * 免密授权host地址 + * @var string + */ + private string $auth_host = 'https://mitest.wecity.qq.com'; + // private string $auth_host = 'https://card.wecity.qq.com'; + + /** + * Authorization Class Construct. + */ + public function __construct() + { + $this->config = config('wechat.medical.auth'); + } + + /** + * 获取签名 + * @param string $timestamp + * @return string + */ + private function getSign(string $timestamp): string + { + return hash_hmac("sha256", $this->config['partner_id'] . $timestamp, $this->config['partner_secret']); + } + + + /** + * 获取RequestId + * @return string + */ + private function getRequestId(): string + { + $char_id = strtoupper(md5(uniqid((string)mt_rand(), true))); + return substr($char_id, 0, 8) + . substr($char_id, 8, 4) + . substr($char_id, 12, 4) + . substr($char_id, 16, 4) + . substr($char_id, 20, 12); + } + + /** + * 获取毫秒 + * @return int + */ + private function getMillisecond(): int + { + [$t1, $t2] = explode(' ', microtime()); + return (int)sprintf('%.0f', ((float)$t1 + (float)$t2) * 1000); + } + + /** + * @param string $endpoint + * @param array $params + * @param string $method + * @param array $options + * @return ResponseInterface|string[] + */ + protected function requestHanle(string $endpoint, array $params = [], string $method = 'post', array $options = []) + { + $timestamp = $this->getMillisecond(); + $params = array_merge([ + 'base_uri' => $this->host, + 'headers' => [ + 'god-portal-timestamp' => $timestamp, + 'god-portal-request-id' => $this->getRequestId(), + 'god-portal-signature' => $this->getSign((string)$timestamp), + ], + 'json' => $params + ], $options); + + try { + $response = $this->request($endpoint, $method, $params); + + $this->writerLog($endpoint, $params, $response); + + return $response; + + } catch (Exception $e) { + return [ + 'code' => -1, + //'message' => "{$e->getMessage()} ON {$e->getFile()}:{$e->getLine()}" + 'message' => $e->getMessage() + ]; + } + } + + /** + * 记录日志 + * @param string $request_url + * @param mixed $request_data + * @param mixed $response + * @return void + */ + private function writerLog(string $request_url, mixed $request_data, mixed $response): void + { + //写入日志 + $log_content = [ + '[REQUEST TIME] ' . date('Y-m-d H:i:s'), + '[REQUEST URL] ' . $request_url, + '[REQUEST PARAM] ' . json_encode($request_data, JSON_UNESCAPED_UNICODE), + '[RESPONSE PARAM] ' . json_encode($response, JSON_UNESCAPED_UNICODE), + "\r\n", + ]; + + recordLog('authorization', implode("\r\n", $log_content)); + } + + /** + * 获取授权跳转地址 + * @param string $return_url 回调地址 + * @return string + */ + public function getAuthRedirectUrl(string $return_url): string + { + $url = '/oauth/code'; + $data = [ + 'authType' => 2, + 'isDepart' => 2, + 'bizType' => '04107', + 'appid' => $this->config['app_id'], + 'cityCode' => $this->config['city_code'], + 'channel' => $this->config['channel'], + 'orgChnlCrtfCodg' => $this->config['org_chnl_crtf_codg'], + 'orgCodg' => $this->config['org_codg'], + 'orgAppId' => $this->config['org_app_id'], + 'redirectUrl' => $return_url + ]; + + $redirect_url = $this->auth_host . $url . '?' . http_build_query($data); + + $this->writerLog($url, $data, ''); + + return $redirect_url; + } + + /** + * 查询用户授权 + * @param string $auth_code + * @param string $open_id + * @return ResponseInterface|string[] + * @throws GuzzleException + */ + public function userQuery(string $auth_code, string $open_id): array|ResponseInterface + { + $uri = '/api/mipuserquery/userQuery/' . $this->config['partner_id']; + $param = [ + 'qrcode' => $auth_code, + 'openid' => $open_id, + ]; + + return $this->requestHanle($uri, $param); + } +} diff --git a/app/Utils/Traits/BuildCacheKeyName.php b/app/Utils/Traits/BuildCacheKeyName.php index 7d43265..b830583 100644 --- a/app/Utils/Traits/BuildCacheKeyName.php +++ b/app/Utils/Traits/BuildCacheKeyName.php @@ -5,6 +5,17 @@ namespace App\Utils\Traits; trait BuildCacheKeyName { + /** + * 获取 检验报告列表 缓存建名 + * @param string $open_id + * @param string $patient_id + * @return string + */ + public function getInspectReportListsKey(string $open_id, string $patient_id): string + { + return 'report.inspect.'. $open_id. '.'. $patient_id; + } + /** * 获取 门诊待缴费列表 缓存键名 * @param string $open_id @@ -27,4 +38,15 @@ trait BuildCacheKeyName { return 'outpatient.pending.'. $open_id.'.'. $patient_id. '.'. $serial_no; } + + /** + * 获取用户 免密授权 缓存键名 + * @param string $open_id + * @param string $patient_id + * @return string + */ + public function getUserMedicalInsuranceAuthKey(string $open_id, string $patient_id): string + { + return 'medical.user.info.'. $open_id.'.'. $patient_id; + } } diff --git a/routes/api.php b/routes/api.php index e376643..22faaf5 100644 --- a/routes/api.php +++ b/routes/api.php @@ -4,6 +4,7 @@ use App\Http\Controllers\Auth\AuthController; use App\Http\Controllers\Hospital\IntroduceController; use App\Http\Controllers\Message\TriggerController; use App\Http\Controllers\Notify\NotifyController; +use App\Http\Controllers\Outpatient\MedicalController; use App\Http\Controllers\Outpatient\PaymentController; use App\Http\Controllers\Outpatient\PendingController; use App\Http\Controllers\Outpatient\RecordController as OutpatientRecordController; @@ -12,6 +13,7 @@ use App\Http\Controllers\Registration\RecordController as RegistrationRecordCont use App\Http\Controllers\Registration\RegisterController; use App\Http\Controllers\Registration\ScheduleController; use App\Http\Controllers\Dictionary\ItemController; +use App\Http\Controllers\Report\InspectController; use App\Http\Controllers\Test\TestController; use Illuminate\Support\Facades\Route; @@ -51,6 +53,12 @@ Route::middleware(['apiLog'])->group(function() { Route::post('/{patient_id}/record/{serial_no}/refund', [RegistrationRecordController::class, 'refund']); }); + // 医保模块 + Route::prefix('Medical')->group(function () { + Route::get('/auth/path', [MedicalController::class, 'redirectAuth']); + Route::post('/auth/{patient_id}/info', [MedicalController::class, 'userAuthInfo']); + }); + // 缴费模块 Route::prefix('Outpatient')->group(function () { Route::get('/{patient_id}/pending', [PendingController::class, 'lists']); @@ -66,6 +74,12 @@ Route::middleware(['apiLog'])->group(function() { Route::get('/', [ItemController::class, 'lists']); Route::get('/{type_id}', [ItemController::class, 'details'])->where('type_id', '[0-9]+'); }); + + // 报告相关模块 + Route::prefix('Report')->group(function () { + Route::get('/{patient_id}/inspect', [InspectController::class, 'lists']); + Route::get('/{patient_id}/inspect/{serial_no}/', [InspectController::class, 'details']); + }); }); // 医院详情相关项目