This commit is contained in:
parent
f9ba59aa55
commit
012984f636
@ -261,7 +261,12 @@ class AutoPharmacistCaVerifyDelayDirectConsumer extends ConsumerMessage
|
||||
}
|
||||
|
||||
// 修改处方文件表,添加prescription_pdf_oss_path字段
|
||||
$this->modifyOrderPrescriptionFile($data['prescription_file_id'],$prescription_pdf_oss_path,$prescription_open_result['file_id']);
|
||||
$this->modifyOrderPrescriptionFile(
|
||||
$data['prescription_file_id'],
|
||||
$prescription_pdf_oss_path,
|
||||
$prescription_open_result['file_id'],
|
||||
$prescription_open_result
|
||||
);
|
||||
|
||||
// 修改处方表为通过
|
||||
$this->modifyOrderPrescription($data['order_prescription_id'],1);
|
||||
@ -438,7 +443,7 @@ class AutoPharmacistCaVerifyDelayDirectConsumer extends ConsumerMessage
|
||||
* @param string $hospital_ca_file_id 医院签章文件id
|
||||
* @return void
|
||||
*/
|
||||
protected function modifyOrderPrescriptionFile(string $prescription_file_id,string $prescription_pdf_oss_path,string $hospital_ca_file_id): void
|
||||
protected function modifyOrderPrescriptionFile(string $prescription_file_id,string $prescription_pdf_oss_path,string $hospital_ca_file_id,array $pharmacist_ca_data = []): void
|
||||
{
|
||||
try {
|
||||
$params = array();
|
||||
@ -447,6 +452,11 @@ class AutoPharmacistCaVerifyDelayDirectConsumer extends ConsumerMessage
|
||||
$data = array();
|
||||
$data['hospital_ca_file_id'] = $hospital_ca_file_id;
|
||||
$data['prescription_pdf_oss_path'] = $prescription_pdf_oss_path;
|
||||
$data['pharmacist_ca_supplier'] = $pharmacist_ca_data['ca_supplier'] ?? "";
|
||||
$data['pharmacist_ca_sign_type'] = $pharmacist_ca_data['ca_sign_type'] ?? "";
|
||||
$data['pharmacist_ca_sign_originaltext'] = $pharmacist_ca_data['ca_sign_originaltext'] ?? "";
|
||||
$data['pharmacist_ca_sign_text'] = $pharmacist_ca_data['ca_sign_text'] ?? "";
|
||||
$data['pharmacist_ca_sign_time'] = $pharmacist_ca_data['ca_sign_time'] ?? "";
|
||||
|
||||
OrderPrescriptionFile::edit($params,$data);
|
||||
}catch(\Exception $e){
|
||||
|
||||
@ -9,12 +9,12 @@ use App\Model\HospitalDepartmentCustom;
|
||||
use App\Model\OrderInquiry;
|
||||
use App\Model\OrderInquiryCase;
|
||||
use App\Model\OrderPrescription;
|
||||
use App\Model\OrderPrescriptionFile;
|
||||
use App\Model\OrderPrescriptionIcd;
|
||||
use App\Model\OrderPrescriptionProduct;
|
||||
use App\Model\PatientFamily;
|
||||
use App\Model\Product;
|
||||
use App\Model\ReportRegulatory;
|
||||
use App\Model\UserCaCert;
|
||||
use App\Model\UserDoctor;
|
||||
use App\Model\UserDoctorInfo;
|
||||
use App\Model\UserPharmacist;
|
||||
@ -188,7 +188,7 @@ class ReportRegulatoryCommand extends HyperfCommand
|
||||
}else{
|
||||
$this->line("C-2、检测处方(抄方)数据");
|
||||
// 获取上报数据-处方 (抄方类型)
|
||||
$report_prescription_data = $this->getReportTransferPrescriptionData($order_inquiry, $order_prescription);
|
||||
$report_prescription_data = $this->getReportTransferPrescriptionDataV2($order_inquiry, $order_prescription);
|
||||
}
|
||||
|
||||
$this->line("C-3、上报处方");
|
||||
@ -198,6 +198,17 @@ class ReportRegulatoryCommand extends HyperfCommand
|
||||
|
||||
$this->line("C-4、上报处方成功" . json_encode($result,JSON_UNESCAPED_UNICODE));
|
||||
|
||||
$report_prescription_detail_data = $this->getReportPrescriptionDetailData(
|
||||
$order_prescription,
|
||||
$report_prescription_data
|
||||
);
|
||||
if (empty($report_prescription_detail_data)) {
|
||||
throw new BusinessException("处方明细数据错误");
|
||||
}
|
||||
|
||||
$result = $regulatoryPlatform->uploadRecipeDetail($report_prescription_detail_data);
|
||||
$this->line("C-4-1、上报处方明细成功" . json_encode($result,JSON_UNESCAPED_UNICODE));
|
||||
|
||||
// 上报成功
|
||||
$res = $this->modifyReportRegulatoryPrescription($report_regulatory, 1);
|
||||
if (!$res) {
|
||||
@ -474,7 +485,7 @@ class ReportRegulatoryCommand extends HyperfCommand
|
||||
$params['user_id'] = $user_doctor['user_id'];
|
||||
$params['type'] = 2;
|
||||
$params['is_latest'] = 1;
|
||||
$doctor_user_ca_cert = UserCaCert::getOne($params);
|
||||
$doctor_user_ca_cert = $this->buildLegacyCaCertPayload($order_prescription['order_prescription_id'], 'doctor');
|
||||
if (empty($doctor_user_ca_cert)){
|
||||
$this->line("错误:无医生ca数据");
|
||||
return false;
|
||||
@ -485,7 +496,7 @@ class ReportRegulatoryCommand extends HyperfCommand
|
||||
$params['user_id'] = $user_pharmacist['user_id'];
|
||||
$params['type'] = 2;
|
||||
$params['is_latest'] = 1;
|
||||
$pharmacist_user_ca_cert = UserCaCert::getOne($params);
|
||||
$pharmacist_user_ca_cert = $this->buildLegacyCaCertPayload($order_prescription['order_prescription_id'], 'pharmacist');
|
||||
if (empty($pharmacist_user_ca_cert)){
|
||||
$this->line("错误:无药师ca数据");
|
||||
return false;
|
||||
@ -533,6 +544,13 @@ class ReportRegulatoryCommand extends HyperfCommand
|
||||
$data['recipeNo'] = $order_prescription['order_prescription_id']; // 医院处方编号
|
||||
$data['cityId'] = "510100"; // 城市ID(参考地区字段)
|
||||
|
||||
$pharmacist_ca_data = $this->getStoredPrescriptionCaData($order_prescription['order_prescription_id'], 'pharmacist');
|
||||
$data['caSupplier'] = $pharmacist_ca_data['caSupplier'] ?? "";
|
||||
$data['caSignType'] = $pharmacist_ca_data['caSignType'] ?? "";
|
||||
$data['caSignTime'] = $pharmacist_ca_data['caSignTime'] ?? "";
|
||||
$data['caSignOriginaltext'] = $pharmacist_ca_data['caSignOriginaltext'] ?? "";
|
||||
$data['caSignText'] = $pharmacist_ca_data['caSignText'] ?? "";
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
@ -544,6 +562,7 @@ class ReportRegulatoryCommand extends HyperfCommand
|
||||
*/
|
||||
private function getReportTransferPrescriptionData(array|object $order_inquiry, array|object $order_prescription): bool|array
|
||||
{
|
||||
return $this->getReportTransferPrescriptionDataV2($order_inquiry, $order_prescription);
|
||||
// 获取医生数据
|
||||
$params = array();
|
||||
$params['doctor_id'] = $order_prescription['doctor_id'];
|
||||
@ -638,7 +657,7 @@ class ReportRegulatoryCommand extends HyperfCommand
|
||||
$params['user_id'] = $user_doctor['user_id'];
|
||||
$params['type'] = 2;
|
||||
$params['is_latest'] = 1;
|
||||
$doctor_user_ca_cert = UserCaCert::getOne($params);
|
||||
$doctor_user_ca_cert = $this->buildLegacyCaCertPayload($order_prescription['order_prescription_id'], 'doctor');
|
||||
if (empty($doctor_user_ca_cert)){
|
||||
$this->line("错误:无医生ca数据");
|
||||
return false;
|
||||
@ -649,7 +668,7 @@ class ReportRegulatoryCommand extends HyperfCommand
|
||||
$params['user_id'] = $user_pharmacist['user_id'];
|
||||
$params['type'] = 2;
|
||||
$params['is_latest'] = 1;
|
||||
$pharmacist_user_ca_cert = UserCaCert::getOne($params);
|
||||
$pharmacist_user_ca_cert = $this->buildLegacyCaCertPayload($order_prescription['order_prescription_id'], 'pharmacist');
|
||||
if (empty($pharmacist_user_ca_cert)){
|
||||
$this->line("错误:无药师ca数据");
|
||||
return false;
|
||||
@ -697,6 +716,160 @@ class ReportRegulatoryCommand extends HyperfCommand
|
||||
$data['recipeNo'] = $order_prescription['order_prescription_id']; // 医院处方编号
|
||||
$data['cityId'] = "510100"; // 城市ID(参考地区字段)
|
||||
|
||||
$pharmacist_ca_data = $this->getStoredPrescriptionCaData($order_prescription['order_prescription_id'], 'pharmacist');
|
||||
$data['caSupplier'] = $pharmacist_ca_data['caSupplier'] ?? "";
|
||||
$data['caSignType'] = $pharmacist_ca_data['caSignType'] ?? "";
|
||||
$data['caSignTime'] = $pharmacist_ca_data['caSignTime'] ?? "";
|
||||
$data['caSignOriginaltext'] = $pharmacist_ca_data['caSignOriginaltext'] ?? "";
|
||||
$data['caSignText'] = $pharmacist_ca_data['caSignText'] ?? "";
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
private function getReportTransferPrescriptionDataV2(array|object $order_inquiry, array|object $order_prescription): bool|array
|
||||
{
|
||||
$params = array();
|
||||
$params['doctor_id'] = $order_prescription['doctor_id'];
|
||||
$user_doctor = UserDoctor::getOne($params);
|
||||
if (empty($user_doctor)) {
|
||||
$this->line("閿欒锛氬尰鐢熸暟鎹敊璇?");
|
||||
return false;
|
||||
}
|
||||
|
||||
$params = array();
|
||||
$params['doctor_id'] = $order_prescription['doctor_id'];
|
||||
$user_doctor_info = UserDoctorInfo::getOne($params);
|
||||
if (empty($user_doctor_info)) {
|
||||
$this->line("閿欒锛氬尰鐢熻鎯呮暟鎹敊璇?");
|
||||
return false;
|
||||
}
|
||||
|
||||
$params = array();
|
||||
$params['department_custom_id'] = $user_doctor['department_custom_id'];
|
||||
$hospital_department_custom = HospitalDepartmentCustom::getOne($params);
|
||||
if (empty($hospital_department_custom)) {
|
||||
$this->line("閿欒锛氬尰鐢熻嚜瀹氫箟鏁版嵁閿欒");
|
||||
return false;
|
||||
}
|
||||
|
||||
$params = array();
|
||||
$params['family_id'] = $order_inquiry['family_id'];
|
||||
$patient_family = PatientFamily::getOne($params);
|
||||
if (empty($patient_family)) {
|
||||
$this->line("閿欒锛氶棶璇婃偅鑰呮暟鎹敊璇?");
|
||||
return false;
|
||||
}
|
||||
|
||||
$params = array();
|
||||
$params['order_inquiry_id'] = $order_inquiry['order_inquiry_id'];
|
||||
$params['status'] = 1;
|
||||
$order_inquiry_case = OrderInquiryCase::getOne($params);
|
||||
if (empty($order_inquiry_case)) {
|
||||
$this->line("閿欒锛氭偅鑰呴棶璇婄梾渚嬮敊璇?");
|
||||
return false;
|
||||
}
|
||||
|
||||
$params = array();
|
||||
$params['pharmacist_id'] = $order_prescription['pharmacist_id'];
|
||||
$user_pharmacist = UserPharmacist::getOne($params);
|
||||
if (empty($user_pharmacist)) {
|
||||
$this->line("閿欒锛氳嵂甯堟暟鎹敊璇?");
|
||||
return false;
|
||||
}
|
||||
|
||||
$params = array();
|
||||
$params['pharmacist_id'] = $order_prescription['pharmacist_id'];
|
||||
$user_pharmacist_info = UserPharmacistInfo::getOne($params);
|
||||
if (empty($user_pharmacist_info)) {
|
||||
$this->line("閿欒锛氳嵂甯堣鎯呮暟鎹敊璇?");
|
||||
return false;
|
||||
}
|
||||
|
||||
$params = array();
|
||||
$params['order_prescription_id'] = $order_prescription['order_prescription_id'];
|
||||
$order_prescription_icd = OrderPrescriptionIcd::getList($params);
|
||||
if (empty($order_prescription_icd)) {
|
||||
$this->line("閿欒锛氭棤澶嶈瘖鐤剧梾璇婃柇鏁版嵁");
|
||||
return false;
|
||||
}
|
||||
|
||||
$icd_name = "";
|
||||
$icd_name_data = array_column($order_prescription_icd->toArray(), 'icd_name');
|
||||
if (!empty($icd_name_data)) {
|
||||
if (count($icd_name_data) > 1) {
|
||||
$icd_name = implode('|', $icd_name_data);
|
||||
} else {
|
||||
$icd_name = $icd_name_data[0];
|
||||
}
|
||||
}
|
||||
|
||||
$order_prescription_product = $this->getPreProductData($order_prescription['order_prescription_id']);
|
||||
if (empty($order_prescription_product)) {
|
||||
$this->line("閿欒锛氭棤澶勬柟鍟嗗搧鏁版嵁");
|
||||
return false;
|
||||
}
|
||||
|
||||
$doctor_user_ca_cert = $this->buildLegacyCaCertPayload($order_prescription['order_prescription_id'], 'doctor');
|
||||
if (empty($doctor_user_ca_cert)) {
|
||||
$this->line("閿欒锛氭棤鍖荤敓ca鏁版嵁");
|
||||
return false;
|
||||
}
|
||||
|
||||
$pharmacist_user_ca_cert = $this->buildLegacyCaCertPayload($order_prescription['order_prescription_id'], 'pharmacist');
|
||||
if (empty($pharmacist_user_ca_cert)) {
|
||||
$this->line("閿欒锛氭棤鑽笀ca鏁版嵁");
|
||||
return false;
|
||||
}
|
||||
|
||||
$data = array();
|
||||
$data['thirdUniqueid'] = $order_inquiry['order_inquiry_id'];
|
||||
$data['orgName'] = "鎴愰兘閲戠墰娆f鐩哥収浜掕仈缃戝尰闄?";
|
||||
$data['orgCode'] = "MA6CGUDA251010619D2112";
|
||||
$data['section'] = $hospital_department_custom['department_name'];
|
||||
$data['sectionCode'] = $hospital_department_custom['department_code'];
|
||||
$data['docName'] = $user_doctor['user_name'];
|
||||
$data['docCertificateNum'] = $user_doctor_info['qualification_cert_num'];
|
||||
$data['pharmacistName'] = $user_pharmacist_info['card_name'];
|
||||
$data['pharmacistOrg'] = "鎴愰兘閲戠墰娆f鐩哥収浜掕仈缃戝尰闄?";
|
||||
$data['pharmacistCertificateNum'] = $user_pharmacist_info['qualification_cert_num'];
|
||||
$data['furtherConsultNo'] = $order_inquiry['order_inquiry_id'];
|
||||
$data['furtherConsultDiagnosis'] = $icd_name;
|
||||
$data['patientName'] = $order_inquiry['patient_name'];
|
||||
$data['patientSex'] = $order_inquiry['patient_sex'] == 0 ?: 1;
|
||||
$data['patientAge'] = (int) $order_inquiry['patient_age'];
|
||||
$data['patientIdcardType'] = 1;
|
||||
$data['patientIdcardNum'] = $patient_family['id_number'];
|
||||
$data['feeType'] = 1;
|
||||
$data['medicalHistory'] = $order_inquiry_case['disease_desc'];
|
||||
$data['recipeTime'] = $order_prescription['doctor_created_time'];
|
||||
$data['recipeType'] = 2;
|
||||
$data['reviewTime'] = $order_prescription['pharmacist_verify_time'];
|
||||
$data['recipeUnitPrice'] = $order_prescription_product['amount_total'];
|
||||
$data['drugName'] = $order_prescription_product['drug_name'];
|
||||
$data['drugCode'] = $order_prescription_product['drug_code'];
|
||||
$data['drugCommonName'] = $order_prescription_product['drug_common_name'];
|
||||
$data['specification'] = $order_prescription_product['specification'];
|
||||
$data['frequency'] = $order_prescription_product['frequency'];
|
||||
$data['usage'] = $order_prescription_product['usage'];
|
||||
$data['doseUnit'] = $order_prescription_product['dose_unit'];
|
||||
$data['doseEachTime'] = $order_prescription_product['dose_each_time'];
|
||||
$data['medicationDays'] = $order_prescription_product['medication_days'];
|
||||
$data['quantity'] = $order_prescription_product['quantity'];
|
||||
$data['drugPackage'] = $order_prescription_product['drug_package'];
|
||||
$data['recipeAllPrice'] = $order_prescription_product['amount_total'];
|
||||
$data['uploadTime'] = date("Y-m-d H:i:s", time());
|
||||
$data['docCaSign'] = $doctor_user_ca_cert['cert_base64'];
|
||||
$data['pharmacistCaSign'] = $pharmacist_user_ca_cert['cert_base64'];
|
||||
$data['recipeNo'] = $order_prescription['order_prescription_id'];
|
||||
$data['cityId'] = "510100";
|
||||
|
||||
$pharmacist_ca_data = $this->getStoredPrescriptionCaData($order_prescription['order_prescription_id'], 'pharmacist');
|
||||
$data['caSupplier'] = $pharmacist_ca_data['caSupplier'] ?? "";
|
||||
$data['caSignType'] = $pharmacist_ca_data['caSignType'] ?? "";
|
||||
$data['caSignTime'] = $pharmacist_ca_data['caSignTime'] ?? "";
|
||||
$data['caSignOriginaltext'] = $pharmacist_ca_data['caSignOriginaltext'] ?? "";
|
||||
$data['caSignText'] = $pharmacist_ca_data['caSignText'] ?? "";
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
@ -736,9 +909,9 @@ class ReportRegulatoryCommand extends HyperfCommand
|
||||
$specification = $item['product_spec']; // 规格
|
||||
$frequency = $item['frequency_use']; // 使用频度
|
||||
$usage = $item['single_use']; // 用法
|
||||
$dose_unit = $product['single_unit']; // 剂量单位
|
||||
$dose_each_time = $product['single_unit']; // 每次剂量
|
||||
$medication_days = (double)$product['available_days']; // 用药天数
|
||||
$dose_unit = $item['single_unit']; // 剂量单位
|
||||
$dose_each_time = $item['single_unit']; // 每次剂量
|
||||
$medication_days = (double)$item['available_days']; // 用药天数
|
||||
$quantity = (double)$item['prescription_product_num']; // 数量
|
||||
$drug_package = $item['packaging_unit']; // 药品包装
|
||||
} else {
|
||||
@ -748,14 +921,22 @@ class ReportRegulatoryCommand extends HyperfCommand
|
||||
$specification = $specification . "|" . $item['product_spec']; // 规格
|
||||
$frequency = $frequency . "|" . $item['frequency_use']; // 使用频度
|
||||
$usage = $usage . "|" . $item['single_use']; // 用法
|
||||
$dose_unit = $dose_unit . "|" . $product['single_unit']; // 剂量单位
|
||||
$dose_each_time = $dose_each_time . "|" . $product['single_unit']; // 每次剂量
|
||||
$medication_days = $medication_days . "|" . (double)$product['available_days']; // 用药天数
|
||||
$dose_unit = $dose_unit . "|" . $item['single_unit']; // 剂量单位
|
||||
$dose_each_time = $dose_each_time . "|" . $item['single_unit']; // 每次剂量
|
||||
$medication_days = $medication_days . "|" . (double)$item['available_days']; // 用药天数
|
||||
$quantity = $quantity . "|" . (double)$item['prescription_product_num']; // 数量
|
||||
$drug_package = $drug_package . "|" . $item['packaging_unit']; // 药品包装
|
||||
}
|
||||
|
||||
$amount_total += $item['product_price'] * $item['prescription_product_num'];
|
||||
$amount_total = bcadd(
|
||||
(string) $amount_total,
|
||||
bcmul(
|
||||
(string) ($item['product_price'] ?? $product['product_price'] ?? 0),
|
||||
(string) $item['prescription_product_num'],
|
||||
2
|
||||
),
|
||||
2
|
||||
);
|
||||
}
|
||||
|
||||
$result = array();
|
||||
@ -775,11 +956,121 @@ class ReportRegulatoryCommand extends HyperfCommand
|
||||
return $result;
|
||||
}
|
||||
|
||||
private function getPreProductDetailData(string $order_prescription_id): array
|
||||
{
|
||||
$params = array();
|
||||
$params['order_prescription_id'] = $order_prescription_id;
|
||||
$order_prescription_product = OrderPrescriptionProduct::getList($params);
|
||||
if (empty($order_prescription_product)) {
|
||||
$this->line("错误:无处方药品数据");
|
||||
return [];
|
||||
}
|
||||
|
||||
$result = [];
|
||||
foreach ($order_prescription_product as $item) {
|
||||
$params = array();
|
||||
$params['product_id'] = $item['product_id'];
|
||||
$product = Product::getWithAmountOne($params);
|
||||
if (empty($product)) {
|
||||
$this->line("错误:无药品数据");
|
||||
return [];
|
||||
}
|
||||
|
||||
$amount_total = bcmul(
|
||||
(string) ($item['product_price'] ?? $product['product_price'] ?? 0),
|
||||
(string) $item['prescription_product_num'],
|
||||
2
|
||||
);
|
||||
|
||||
$result[] = [
|
||||
'drug_name' => (string) ($item['product_name'] ?? ''),
|
||||
'drug_code' => (string) ($product['product_pharmacy_code'] ?? ''),
|
||||
'drug_common_name' => (string) ($product['common_name'] ?? ''),
|
||||
'specification' => (string) ($item['product_spec'] ?? ''),
|
||||
'frequency' => (string) ($item['frequency_use'] ?? ''),
|
||||
'usage' => (string) ($item['single_use'] ?? ''),
|
||||
'dose_unit' => (string) ($item['single_unit'] ?? $product['single_unit'] ?? ''),
|
||||
'dose_each_time' => (string) ($item['single_unit'] ?? $product['single_unit'] ?? ''),
|
||||
'medication_days' => (string) ($item['available_days'] ?? $product['available_days'] ?? ''),
|
||||
'quantity' => (string) $item['prescription_product_num'],
|
||||
'drug_package' => (string) ($item['packaging_unit'] ?? ''),
|
||||
'product_price' => (string) ($item['product_price'] ?? $product['product_price'] ?? 0),
|
||||
'amount_total' => $amount_total,
|
||||
];
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
private function getReportPrescriptionDetailData(
|
||||
array|object $order_prescription,
|
||||
array $report_prescription_data
|
||||
): array {
|
||||
$product_details = $this->getPreProductDetailData((string) $order_prescription['order_prescription_id']);
|
||||
if (empty($product_details)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$result = [];
|
||||
foreach ($product_details as $detail) {
|
||||
$row = $report_prescription_data;
|
||||
$row['recipeUnitPrice'] = $detail['product_price'];
|
||||
$row['drugName'] = $detail['drug_name'];
|
||||
$row['drugCode'] = $detail['drug_code'];
|
||||
$row['drugCommonName'] = $detail['drug_common_name'];
|
||||
$row['specification'] = $detail['specification'];
|
||||
$row['frequency'] = $detail['frequency'];
|
||||
$row['usage'] = $detail['usage'];
|
||||
$row['doseUnit'] = $detail['dose_unit'];
|
||||
$row['doseEachTime'] = $detail['dose_each_time'];
|
||||
$row['medicationDays'] = $detail['medication_days'];
|
||||
$row['quantity'] = $detail['quantity'];
|
||||
$row['drugPackage'] = $detail['drug_package'];
|
||||
$row['recipeAllPrice'] = $detail['amount_total'];
|
||||
$row['uploadTime'] = date("Y-m-d H:i:s", time());
|
||||
|
||||
$result[] = $row;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取上报数据-网络咨询
|
||||
* @param array|object $order_inquiry
|
||||
* @return array
|
||||
*/
|
||||
private function getStoredPrescriptionCaData(string|int $order_prescription_id, string $role): array
|
||||
{
|
||||
$params = array();
|
||||
$params['order_prescription_id'] = $order_prescription_id;
|
||||
$order_prescription_file = OrderPrescriptionFile::getOne($params);
|
||||
if (empty($order_prescription_file)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$prefix = $role === 'doctor' ? 'doctor' : 'pharmacist';
|
||||
return [
|
||||
'caSupplier' => (string) ($order_prescription_file[$prefix . '_ca_supplier'] ?? ''),
|
||||
'caSignType' => (string) ($order_prescription_file[$prefix . '_ca_sign_type'] ?? ''),
|
||||
'caSignTime' => (string) ($order_prescription_file[$prefix . '_ca_sign_time'] ?? ''),
|
||||
'caSignOriginaltext' => (string) ($order_prescription_file[$prefix . '_ca_sign_originaltext'] ?? ''),
|
||||
'caSignText' => (string) ($order_prescription_file[$prefix . '_ca_sign_text'] ?? ''),
|
||||
];
|
||||
}
|
||||
|
||||
private function buildLegacyCaCertPayload(string|int $order_prescription_id, string $role): array
|
||||
{
|
||||
$ca_data = $this->getStoredPrescriptionCaData($order_prescription_id, $role);
|
||||
if (empty($ca_data['caSignText'])) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return [
|
||||
'cert_base64' => $ca_data['caSignText'],
|
||||
];
|
||||
}
|
||||
|
||||
private function getConsultData(array|object $order_inquiry): array
|
||||
{
|
||||
// 获取医生数据
|
||||
@ -836,6 +1127,7 @@ class ReportRegulatoryCommand extends HyperfCommand
|
||||
$data['section'] = $hospital_department_custom['department_name'];//科室名称
|
||||
$data['sectionCode'] = $hospital_department_custom['department_code'];//科室编码
|
||||
$data['docName'] = $user_doctor['user_name'];// 姓名(医师、护师、技师)
|
||||
$data['doc_idcard'] = $user_doctor_info['card_num']; // 医生身份证号
|
||||
$data['certificateNum'] = $user_doctor_info['qualification_cert_num']; // 执业资格证号
|
||||
$data['patientName'] = $order_inquiry['patient_name']; // 患者姓名
|
||||
$data['patientAge'] = (int)$order_inquiry['patient_age']; // 患者年龄
|
||||
@ -992,6 +1284,13 @@ class ReportRegulatoryCommand extends HyperfCommand
|
||||
$data['cityId'] = "510100"; // 城市ID(参考地区字段)
|
||||
$data['isMark'] = 1;//是否留痕 1:代表留痕;0:代表未留痕
|
||||
|
||||
$doctor_ca_data = $this->getStoredPrescriptionCaData($order_prescription['order_prescription_id'], 'doctor');
|
||||
$data['caSupplier'] = $doctor_ca_data['caSupplier'] ?? "";
|
||||
$data['caSignType'] = $doctor_ca_data['caSignType'] ?? "";
|
||||
$data['caSignTime'] = $doctor_ca_data['caSignTime'] ?? "";
|
||||
$data['caSignOriginaltext'] = $doctor_ca_data['caSignOriginaltext'] ?? "";
|
||||
$data['caSignText'] = $doctor_ca_data['caSignText'] ?? "";
|
||||
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
|
||||
@ -33,7 +33,7 @@ class OrderPrescriptionFile extends Model
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*/
|
||||
protected array $fillable = ['prescription_file_id', 'order_prescription_id', 'doctor_ca_file_id', 'hospital_ca_file_id', 'prescription_img_oss_path', 'prescription_pdf_oss_path', 'is_converted_pdf', 'created_at', 'updated_at'];
|
||||
protected array $fillable = ['prescription_file_id', 'order_prescription_id', 'doctor_ca_file_id', 'doctor_ca_supplier', 'doctor_ca_sign_type', 'doctor_ca_sign_originaltext', 'doctor_ca_sign_text', 'doctor_ca_sign_time', 'pharmacist_ca_supplier', 'pharmacist_ca_sign_type', 'pharmacist_ca_sign_originaltext', 'pharmacist_ca_sign_text', 'pharmacist_ca_sign_time', 'hospital_ca_file_id', 'prescription_img_oss_path', 'prescription_pdf_oss_path', 'is_converted_pdf', 'created_at', 'updated_at'];
|
||||
|
||||
protected string $primaryKey = "prescription_file_id";
|
||||
|
||||
|
||||
@ -40,7 +40,7 @@ class OrderPrescriptionProduct extends Model
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*/
|
||||
protected array $fillable = ['prescription_product_id', 'order_prescription_id', 'product_id', 'use_status', 'prescription_product_num', 'product_name', 'product_spec', 'license_number', 'manufacturer', 'single_unit', 'single_use', 'packaging_unit', 'frequency_use', 'available_days', 'created_at', 'updated_at'];
|
||||
protected array $fillable = ['prescription_product_id', 'order_prescription_id', 'product_id', 'use_status', 'prescription_product_num', 'product_name', 'product_price', 'product_spec', 'license_number', 'manufacturer', 'single_unit', 'single_use', 'packaging_unit', 'frequency_use', 'available_days', 'created_at', 'updated_at'];
|
||||
|
||||
protected string $primaryKey = "prescription_product_id";
|
||||
|
||||
|
||||
@ -56,6 +56,8 @@ class CaService extends BaseService
|
||||
// 处方pdf oss地址
|
||||
protected string $prescription_pdf_oss_path;
|
||||
|
||||
protected array $sign_result = [];
|
||||
|
||||
/**
|
||||
* 初始化类,此处会获取基础数据
|
||||
* @param array|object $order_prescription 处方表数据
|
||||
@ -254,13 +256,33 @@ class CaService extends BaseService
|
||||
$data['product'][] = $product;
|
||||
}
|
||||
|
||||
dump($this->entity_id);
|
||||
$cert_sign_result = $CaOnline->getCertSign($this->entity_id, $this->entity_id, $data);
|
||||
|
||||
// 验证云证书签名 验证无需处理,只要不返回错误即可
|
||||
$CaOnline->verifyPkcs7($cert_sign_result['signP7'], $data);
|
||||
|
||||
$this->cert_serial_number = $cert_sign_result['certSerialnumber'];
|
||||
$sign_originaltext = hash_hmac("sha1", json_encode($data, JSON_UNESCAPED_UNICODE), config('ca.online.secret'));
|
||||
$timestamp_sign = "";
|
||||
try {
|
||||
$timestamp_result = $CaOnline->getTimestampSign($sign_originaltext);
|
||||
if (is_array($timestamp_result)) {
|
||||
$timestamp_sign = $timestamp_result['signedData'] ?? "";
|
||||
} elseif (is_string($timestamp_result)) {
|
||||
$timestamp_sign = $timestamp_result;
|
||||
}
|
||||
} catch (\Throwable $throwable) {
|
||||
$timestamp_sign = "";
|
||||
}
|
||||
|
||||
$this->sign_result = [
|
||||
'ca_supplier' => (string) config('regulatory_platform.ca_supplier', '2'),
|
||||
'ca_sign_type' => (string) config('regulatory_platform.ca_sign_type', '1'),
|
||||
'ca_sign_originaltext' => $sign_originaltext,
|
||||
'ca_sign_text' => $cert_sign_result['signP7'] ?? "",
|
||||
'ca_sign_time' => $timestamp_sign,
|
||||
'cert_serial_number' => $cert_sign_result['certSerialnumber'] ?? "",
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
@ -268,6 +290,11 @@ class CaService extends BaseService
|
||||
* @param array|object $order_prescription
|
||||
* @return string
|
||||
*/
|
||||
public function getSignResult(): array
|
||||
{
|
||||
return $this->sign_result;
|
||||
}
|
||||
|
||||
public function createPrescriptionImgPdf(array|object $order_prescription): string
|
||||
{
|
||||
// 打开基础处方图片
|
||||
|
||||
@ -152,7 +152,6 @@ class OrderPrescriptionService extends BaseService
|
||||
throw new BusinessException("医生开方日期错误");
|
||||
}
|
||||
|
||||
dump($user_id);
|
||||
$CaService = new CaService($order_prescription,$type,$user_id);
|
||||
|
||||
// 获取云证书签名+验证云证书签名
|
||||
@ -167,9 +166,15 @@ class OrderPrescriptionService extends BaseService
|
||||
|
||||
// 进行处方pdf签章
|
||||
$file_id = $CaService->addSignPdf($type);
|
||||
$sign_result = $CaService->getSignResult();
|
||||
$result = array();
|
||||
$result['prescription_img_oss_path'] = $prescription_img_oss_path ?? "";
|
||||
$result['file_id'] = $file_id;
|
||||
$result['ca_supplier'] = $sign_result['ca_supplier'] ?? "";
|
||||
$result['ca_sign_type'] = $sign_result['ca_sign_type'] ?? "";
|
||||
$result['ca_sign_originaltext'] = $sign_result['ca_sign_originaltext'] ?? "";
|
||||
$result['ca_sign_text'] = $sign_result['ca_sign_text'] ?? "";
|
||||
$result['ca_sign_time'] = $sign_result['ca_sign_time'] ?? "";
|
||||
return $result;
|
||||
|
||||
} catch (\Throwable $e) {
|
||||
|
||||
@ -1730,6 +1730,7 @@ class UserDoctorService extends BaseService
|
||||
$data['product_id'] = $item['product_id'];
|
||||
$data['prescription_product_num'] = $item['prescription_product_num'];
|
||||
$data['product_name'] = $product['product_name'];
|
||||
$data['product_price'] = $product['product_price'];
|
||||
$data['product_spec'] = $product['product_spec'];
|
||||
$data['license_number'] = $product['license_number'];
|
||||
$data['manufacturer'] = $product['manufacturer'];
|
||||
@ -1799,6 +1800,11 @@ class UserDoctorService extends BaseService
|
||||
$data = array();
|
||||
$data['order_prescription_id'] = $order_prescription->order_prescription_id;
|
||||
$data['doctor_ca_file_id'] = $prescription_open_result['file_id'];
|
||||
$data['doctor_ca_supplier'] = $prescription_open_result['ca_supplier'] ?? "";
|
||||
$data['doctor_ca_sign_type'] = $prescription_open_result['ca_sign_type'] ?? "";
|
||||
$data['doctor_ca_sign_originaltext'] = $prescription_open_result['ca_sign_originaltext'] ?? "";
|
||||
$data['doctor_ca_sign_text'] = $prescription_open_result['ca_sign_text'] ?? "";
|
||||
$data['doctor_ca_sign_time'] = $prescription_open_result['ca_sign_time'] ?? "";
|
||||
$data['prescription_img_oss_path'] = $prescription_open_result['prescription_img_oss_path'];
|
||||
$order_prescription_file = OrderPrescriptionFile::addOrderPrescriptionFile($data);
|
||||
if (empty($order_prescription_file)){
|
||||
|
||||
@ -25,8 +25,8 @@ class Log
|
||||
self::getInstance()->{$name}(...$arguments);
|
||||
}
|
||||
|
||||
public static function getInstance(string $name = 'app'): LoggerInterface
|
||||
public static function getInstance(string $name = 'app', string $group = 'default'): LoggerInterface
|
||||
{
|
||||
return ApplicationContext::getContainer()->get(LoggerFactory::class)->get($name);
|
||||
return ApplicationContext::getContainer()->get(LoggerFactory::class)->get($name, $group);
|
||||
}
|
||||
}
|
||||
|
||||
@ -51,4 +51,34 @@ return [
|
||||
],
|
||||
'formatter' => $formatter,
|
||||
],
|
||||
'regulatory_platform_http_detail' => [
|
||||
'handler' => [
|
||||
'class' => Monolog\Handler\RotatingFileHandler::class,
|
||||
'constructor' => [
|
||||
'filename' => BASE_PATH . '/runtime/logs/regulatory-platform-http-detail.log',
|
||||
'level' => Monolog\Logger::INFO,
|
||||
],
|
||||
],
|
||||
'formatter' => $formatter,
|
||||
],
|
||||
'regulatory_platform_business' => [
|
||||
'handler' => [
|
||||
'class' => Monolog\Handler\RotatingFileHandler::class,
|
||||
'constructor' => [
|
||||
'filename' => BASE_PATH . '/runtime/logs/regulatory-platform-business.log',
|
||||
'level' => Monolog\Logger::INFO,
|
||||
],
|
||||
],
|
||||
'formatter' => $formatter,
|
||||
],
|
||||
'regulatory_platform_token' => [
|
||||
'handler' => [
|
||||
'class' => Monolog\Handler\RotatingFileHandler::class,
|
||||
'constructor' => [
|
||||
'filename' => BASE_PATH . '/runtime/logs/regulatory-platform-token.log',
|
||||
'level' => Monolog\Logger::INFO,
|
||||
],
|
||||
],
|
||||
'formatter' => $formatter,
|
||||
],
|
||||
];
|
||||
|
||||
@ -121,6 +121,12 @@ return [
|
||||
"client_id" => env('REG_PLAT_CLIENT_ID', '09b117f8d1eb4dbfbf565447205ea60f'),
|
||||
"client_secret" => env('REG_PLAT_CLIENT_SECRET', 'dcfd9223a3f448b0aae83ce22cdcc015'),
|
||||
"api_url" => env('REG_PLAT_APP_URL', 'https://202.61.88.184:19200/'),
|
||||
"sm4_key" => env('REG_PLAT_SM4_KEY', 'e0e295da97ed46cc9ad71b61ed361721'),
|
||||
"sm4_iv" => env('REG_PLAT_SM4_IV', '8a1f69a58603416a9d5b93c4bb67f72f'),
|
||||
"sm2_private_key" => env('REG_PLAT_SM2_PRIVATE_KEY', 'bbc97b0418cc92b8f3b7b7764e554beed6b3adb1aa9bfb9e7839583e5ccf4722'),
|
||||
"sm2_public_key" => env('REG_PLAT_SM2_PUBLIC_KEY', '04fca188451748a35b8c14890a0270fbe787e74f2286b72f6305939c8d527d1fd9fec2926cc0ff6c4f04cfbee2a3d65230565691a8f8453b44665d2b6fbbc79c91'),
|
||||
"ca_supplier" => env('REG_PLAT_CA_SUPPLIER', '2'),
|
||||
"ca_sign_type" => env('REG_PLAT_CA_SIGN_TYPE', '1'),
|
||||
],
|
||||
'kuaidi100' => [ // 快递100
|
||||
"key" => env('LOGISTICS_KEY', 'Mpjjgebe8764'),
|
||||
|
||||
@ -110,6 +110,36 @@ abstract class Ca
|
||||
* @param array $data
|
||||
* @return bool
|
||||
*/
|
||||
/**
|
||||
* Timestamp sign service.
|
||||
* @param string $to_sign
|
||||
* @return mixed
|
||||
*/
|
||||
public function getTimestampSign(string $to_sign): mixed
|
||||
{
|
||||
$generator = $this->container->get(IdGeneratorInterface::class);
|
||||
|
||||
$option = [
|
||||
'form_params' => [
|
||||
'requestId' => $generator->generate(),
|
||||
'toSign' => $to_sign,
|
||||
]
|
||||
];
|
||||
|
||||
try {
|
||||
$response = $this->httpRequest(
|
||||
$this->api_url . '/signgw-service/api/signgw/timestamap/sign',
|
||||
$option
|
||||
);
|
||||
if (empty($response)) {
|
||||
throw new BusinessException(HttpEnumCode::getMessage(HttpEnumCode::SERVER_ERROR));
|
||||
}
|
||||
return $response;
|
||||
} catch (GuzzleException $e) {
|
||||
throw new BusinessException($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public function addUserSignConfig(string $user_id, string $card_num, array $data): bool
|
||||
{
|
||||
$arg = [
|
||||
|
||||
595
extend/RegulatoryPlatform/RegulatoryPlatformProtocol.php
Normal file
595
extend/RegulatoryPlatform/RegulatoryPlatformProtocol.php
Normal file
@ -0,0 +1,595 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Extend\RegulatoryPlatform;
|
||||
|
||||
use App\Exception\BusinessException;
|
||||
|
||||
use function Hyperf\Config\config;
|
||||
|
||||
class RegulatoryPlatformProtocol
|
||||
{
|
||||
protected const SM2_CURVE_OID = '1.2.156.10197.1.301';
|
||||
|
||||
protected const EC_PUBLIC_KEY_OID = '1.2.840.10045.2.1';
|
||||
|
||||
protected ?string $clientId;
|
||||
|
||||
protected ?string $clientSecret;
|
||||
|
||||
protected ?string $sm4KeyHex;
|
||||
|
||||
protected ?string $sm4IvHex;
|
||||
|
||||
protected ?string $sm2PrivateKey;
|
||||
|
||||
protected ?string $sm2PublicKey;
|
||||
|
||||
public function __construct(array $config = [])
|
||||
{
|
||||
$this->clientId = $this->resolveConfigValue($config, [
|
||||
'client_id',
|
||||
'v2.client_id',
|
||||
'prod.client_id',
|
||||
'default.client_id',
|
||||
]);
|
||||
$this->clientSecret = $this->resolveConfigValue($config, [
|
||||
'client_secret',
|
||||
'v2.client_secret',
|
||||
'prod.client_secret',
|
||||
'default.client_secret',
|
||||
]);
|
||||
$this->sm4KeyHex = $this->resolveConfigValue($config, [
|
||||
'sm4_key',
|
||||
'encrypt_key',
|
||||
'v2.sm4_key',
|
||||
'crypto.sm4_key',
|
||||
'sm4.key',
|
||||
]);
|
||||
$this->sm4IvHex = $this->resolveConfigValue($config, [
|
||||
'sm4_iv',
|
||||
'encrypt_iv',
|
||||
'v2.sm4_iv',
|
||||
'crypto.sm4_iv',
|
||||
'sm4.iv',
|
||||
]);
|
||||
$this->sm2PrivateKey = $this->resolveConfigValue($config, [
|
||||
'sm2_private_key',
|
||||
'sign_private_key',
|
||||
'v2.sm2_private_key',
|
||||
'crypto.sm2_private_key',
|
||||
'sm2.private_key',
|
||||
]);
|
||||
$this->sm2PublicKey = $this->resolveConfigValue($config, [
|
||||
'sm2_public_key',
|
||||
'verify_public_key',
|
||||
'platform_public_key',
|
||||
'v2.sm2_public_key',
|
||||
'crypto.sm2_public_key',
|
||||
'sm2.public_key',
|
||||
]);
|
||||
}
|
||||
|
||||
public static function fromConfig(?array $config = null): self
|
||||
{
|
||||
$config ??= (array) config('regulatory_platform', []);
|
||||
|
||||
return new self($config);
|
||||
}
|
||||
|
||||
public function getClientId(): ?string
|
||||
{
|
||||
return $this->clientId;
|
||||
}
|
||||
|
||||
public function getClientSecret(): ?string
|
||||
{
|
||||
return $this->clientSecret;
|
||||
}
|
||||
|
||||
public function buildRequestEnvelope(
|
||||
array $payload,
|
||||
bool $includeClientId = false,
|
||||
?string $clientId = null,
|
||||
?string $clientSecret = null
|
||||
): array {
|
||||
$plainText = $this->encodeCanonicalJson($payload, true);
|
||||
|
||||
return $this->buildRequestEnvelopeFromPlainText($plainText, $includeClientId, $clientId, $clientSecret);
|
||||
}
|
||||
|
||||
public function buildRequestEnvelopeFromPlainText(
|
||||
string $plainText,
|
||||
bool $includeClientId = false,
|
||||
?string $clientId = null,
|
||||
?string $clientSecret = null
|
||||
): array {
|
||||
$resolvedClientId = $clientId ?? $this->requireClientId();
|
||||
$resolvedClientSecret = $clientSecret ?? $this->requireClientSecret();
|
||||
$encryptedHex = $this->encrypt($plainText);
|
||||
$sign = $this->sign($this->buildSignString($resolvedClientId, $encryptedHex, $resolvedClientSecret));
|
||||
|
||||
$envelope = [
|
||||
'sign' => $sign,
|
||||
'data' => $encryptedHex,
|
||||
];
|
||||
|
||||
if ($includeClientId) {
|
||||
$envelope['client_id'] = $resolvedClientId;
|
||||
}
|
||||
|
||||
return $envelope;
|
||||
}
|
||||
|
||||
public function decodeResponseEnvelope(
|
||||
array $response,
|
||||
bool $verifySignature = false,
|
||||
?string $clientId = null,
|
||||
?string $clientSecret = null
|
||||
): array {
|
||||
$envelope = $this->extractResponseEnvelope($response);
|
||||
$resolvedClientId = $clientId ?? $this->requireClientId();
|
||||
$resolvedClientSecret = $clientSecret ?? $this->requireClientSecret();
|
||||
|
||||
if ($verifySignature) {
|
||||
$verified = $this->verify(
|
||||
$this->buildSignString($resolvedClientId, $envelope['data'], $resolvedClientSecret),
|
||||
$envelope['sign']
|
||||
);
|
||||
if (! $verified) {
|
||||
throw new BusinessException('监管平台响应验签失败');
|
||||
}
|
||||
}
|
||||
|
||||
$plainText = $this->decrypt($envelope['data']);
|
||||
$decoded = json_decode($plainText, true);
|
||||
if (! is_array($decoded)) {
|
||||
throw new BusinessException('监管平台响应解密成功,但 JSON 解析失败');
|
||||
}
|
||||
|
||||
return [
|
||||
'envelope' => $envelope,
|
||||
'plain_text' => $plainText,
|
||||
'decoded' => $decoded,
|
||||
];
|
||||
}
|
||||
|
||||
public function buildSignString(string $clientId, string $encryptedHex, ?string $clientSecret = null): string
|
||||
{
|
||||
$resolvedClientSecret = $clientSecret ?? $this->requireClientSecret();
|
||||
|
||||
return sprintf('client_id=%s&data=%s&key=%s', $clientId, $encryptedHex, $resolvedClientSecret);
|
||||
}
|
||||
|
||||
public function encodeCanonicalJson(array $payload, bool $removeEmptyValues = true): string
|
||||
{
|
||||
$normalized = $this->canonicalizePayload($payload, $removeEmptyValues);
|
||||
$json = json_encode(
|
||||
$normalized,
|
||||
JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRESERVE_ZERO_FRACTION
|
||||
);
|
||||
|
||||
if ($json === false) {
|
||||
throw new BusinessException('监管平台请求 JSON 编码失败');
|
||||
}
|
||||
|
||||
return $json;
|
||||
}
|
||||
|
||||
public function canonicalizePayload(array $payload, bool $removeEmptyValues = true): array
|
||||
{
|
||||
$normalized = $this->normalizeValue($payload, $removeEmptyValues);
|
||||
|
||||
if (! is_array($normalized)) {
|
||||
throw new BusinessException('监管平台请求体格式错误');
|
||||
}
|
||||
|
||||
return $normalized;
|
||||
}
|
||||
|
||||
public function encrypt(string $plainText): string
|
||||
{
|
||||
$key = $this->decodeHex($this->requireSm4Key(), 'SM4 key');
|
||||
$iv = $this->decodeHex($this->requireSm4Iv(), 'SM4 iv');
|
||||
$cipherText = openssl_encrypt($plainText, 'sm4-cbc', $key, OPENSSL_RAW_DATA, $iv);
|
||||
|
||||
if ($cipherText === false) {
|
||||
throw new BusinessException('监管平台 SM4 加密失败: ' . $this->getLastOpenSslError());
|
||||
}
|
||||
|
||||
return strtolower(bin2hex($cipherText));
|
||||
}
|
||||
|
||||
public function decrypt(string $cipherHex): string
|
||||
{
|
||||
$cipherRaw = $this->decodeHex($cipherHex, 'SM4 cipher');
|
||||
$key = $this->decodeHex($this->requireSm4Key(), 'SM4 key');
|
||||
$iv = $this->decodeHex($this->requireSm4Iv(), 'SM4 iv');
|
||||
$plainText = openssl_decrypt($cipherRaw, 'sm4-cbc', $key, OPENSSL_RAW_DATA, $iv);
|
||||
|
||||
if ($plainText === false) {
|
||||
throw new BusinessException('监管平台 SM4 解密失败: ' . $this->getLastOpenSslError());
|
||||
}
|
||||
|
||||
return $plainText;
|
||||
}
|
||||
|
||||
public function sign(string $plainText): string
|
||||
{
|
||||
$privateKey = openssl_pkey_get_private($this->resolveKeyMaterial($this->sm2PrivateKey, 'SM2 private key'));
|
||||
if ($privateKey === false) {
|
||||
throw new BusinessException('监管平台 SM2 私钥加载失败: ' . $this->getLastOpenSslError());
|
||||
}
|
||||
|
||||
$signature = '';
|
||||
$result = openssl_sign($plainText, $signature, $privateKey, 'sm3');
|
||||
if ($result !== true) {
|
||||
throw new BusinessException('监管平台 SM2 签名失败: ' . $this->getLastOpenSslError());
|
||||
}
|
||||
|
||||
return strtolower(bin2hex($signature));
|
||||
}
|
||||
|
||||
public function verify(string $plainText, string $signatureHex): bool
|
||||
{
|
||||
$publicKey = openssl_pkey_get_public($this->resolveKeyMaterial($this->sm2PublicKey, 'SM2 public key'));
|
||||
if ($publicKey === false) {
|
||||
throw new BusinessException('监管平台 SM2 公钥加载失败: ' . $this->getLastOpenSslError());
|
||||
}
|
||||
|
||||
$signatureRaw = $this->decodeHex($signatureHex, 'SM2 signature');
|
||||
$result = openssl_verify($plainText, $signatureRaw, $publicKey, 'sm3');
|
||||
if ($result === -1) {
|
||||
throw new BusinessException('监管平台 SM2 验签失败: ' . $this->getLastOpenSslError());
|
||||
}
|
||||
|
||||
return $result === 1;
|
||||
}
|
||||
|
||||
protected function extractResponseEnvelope(array $response): array
|
||||
{
|
||||
if (isset($response['data']) && is_array($response['data']) && isset($response['data']['data'])) {
|
||||
$response = $response['data'];
|
||||
}
|
||||
|
||||
$sign = $response['sign'] ?? null;
|
||||
$data = $response['data'] ?? null;
|
||||
if (! is_string($sign) || ! is_string($data) || $sign === '' || $data === '') {
|
||||
throw new BusinessException('监管平台响应报文缺少 sign/data');
|
||||
}
|
||||
|
||||
return [
|
||||
'sign' => $sign,
|
||||
'data' => $data,
|
||||
];
|
||||
}
|
||||
|
||||
protected function normalizeValue(mixed $value, bool $removeEmptyValues): mixed
|
||||
{
|
||||
if (is_array($value)) {
|
||||
if ($this->isAssoc($value)) {
|
||||
$normalized = [];
|
||||
ksort($value);
|
||||
foreach ($value as $key => $item) {
|
||||
$item = $this->normalizeValue($item, $removeEmptyValues);
|
||||
if ($removeEmptyValues && $this->isEmptyValue($item)) {
|
||||
continue;
|
||||
}
|
||||
$normalized[$key] = $item;
|
||||
}
|
||||
|
||||
return $normalized;
|
||||
}
|
||||
|
||||
$normalized = [];
|
||||
foreach ($value as $item) {
|
||||
$item = $this->normalizeValue($item, $removeEmptyValues);
|
||||
if ($removeEmptyValues && $this->isEmptyValue($item)) {
|
||||
continue;
|
||||
}
|
||||
$normalized[] = $item;
|
||||
}
|
||||
|
||||
return $normalized;
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
protected function isAssoc(array $value): bool
|
||||
{
|
||||
return array_keys($value) !== range(0, count($value) - 1);
|
||||
}
|
||||
|
||||
protected function isEmptyValue(mixed $value): bool
|
||||
{
|
||||
if ($value === null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (is_string($value)) {
|
||||
return trim($value) === '';
|
||||
}
|
||||
|
||||
if (is_array($value)) {
|
||||
return $value === [];
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
protected function decodeHex(string $value, string $label): string
|
||||
{
|
||||
$value = trim($value);
|
||||
if ($value === '' || strlen($value) % 2 !== 0 || ! ctype_xdigit($value)) {
|
||||
throw new BusinessException(sprintf('监管平台 %s 不是合法十六进制字符串', $label));
|
||||
}
|
||||
|
||||
$decoded = hex2bin($value);
|
||||
if ($decoded === false) {
|
||||
throw new BusinessException(sprintf('监管平台 %s 十六进制解码失败', $label));
|
||||
}
|
||||
|
||||
return $decoded;
|
||||
}
|
||||
|
||||
protected function resolveKeyMaterial(?string $key, string $label): string
|
||||
{
|
||||
$key = trim((string) $key);
|
||||
if ($key === '') {
|
||||
throw new BusinessException(sprintf('监管平台 %s 未配置', $label));
|
||||
}
|
||||
|
||||
if (str_starts_with($key, 'file://')) {
|
||||
$key = substr($key, 7);
|
||||
}
|
||||
|
||||
if (is_file($key)) {
|
||||
$contents = file_get_contents($key);
|
||||
if ($contents === false || trim($contents) === '') {
|
||||
throw new BusinessException(sprintf('监管平台 %s 文件读取失败', $label));
|
||||
}
|
||||
|
||||
return $contents;
|
||||
}
|
||||
|
||||
if ($label === 'SM2 private key' && $this->isRawHexPrivateKey($key)) {
|
||||
return $this->buildSm2PrivateKeyPem($key, $this->sm2PublicKey);
|
||||
}
|
||||
|
||||
if ($label === 'SM2 public key' && $this->isRawHexPublicKey($key)) {
|
||||
return $this->buildSm2PublicKeyPem($key);
|
||||
}
|
||||
|
||||
return $key;
|
||||
}
|
||||
|
||||
protected function resolveConfigValue(array $config, array $paths): ?string
|
||||
{
|
||||
foreach ($paths as $path) {
|
||||
$value = $this->getByPath($config, $path);
|
||||
if ($value !== null && $value !== '') {
|
||||
return (string) $value;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
protected function getByPath(array $config, string $path): mixed
|
||||
{
|
||||
$current = $config;
|
||||
foreach (explode('.', $path) as $segment) {
|
||||
if (! is_array($current) || ! array_key_exists($segment, $current)) {
|
||||
return null;
|
||||
}
|
||||
$current = $current[$segment];
|
||||
}
|
||||
|
||||
return $current;
|
||||
}
|
||||
|
||||
protected function requireClientId(): string
|
||||
{
|
||||
if (empty($this->clientId)) {
|
||||
throw new BusinessException('监管平台 client_id 未配置');
|
||||
}
|
||||
|
||||
return $this->clientId;
|
||||
}
|
||||
|
||||
protected function requireClientSecret(): string
|
||||
{
|
||||
if (empty($this->clientSecret)) {
|
||||
throw new BusinessException('监管平台 client_secret 未配置');
|
||||
}
|
||||
|
||||
return $this->clientSecret;
|
||||
}
|
||||
|
||||
protected function requireSm4Key(): string
|
||||
{
|
||||
if (empty($this->sm4KeyHex)) {
|
||||
throw new BusinessException('监管平台 SM4 key 未配置');
|
||||
}
|
||||
|
||||
return $this->sm4KeyHex;
|
||||
}
|
||||
|
||||
protected function requireSm4Iv(): string
|
||||
{
|
||||
if (empty($this->sm4IvHex)) {
|
||||
throw new BusinessException('监管平台 SM4 iv 未配置');
|
||||
}
|
||||
|
||||
return $this->sm4IvHex;
|
||||
}
|
||||
|
||||
protected function getLastOpenSslError(): string
|
||||
{
|
||||
$errors = [];
|
||||
while (($error = openssl_error_string()) !== false) {
|
||||
$errors[] = $error;
|
||||
}
|
||||
|
||||
return $errors === [] ? 'unknown openssl error' : implode(' | ', $errors);
|
||||
}
|
||||
|
||||
protected function isRawHexPrivateKey(string $key): bool
|
||||
{
|
||||
return strlen($key) === 64 && ctype_xdigit($key);
|
||||
}
|
||||
|
||||
protected function isRawHexPublicKey(string $key): bool
|
||||
{
|
||||
if (! ctype_xdigit($key)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return in_array(strlen($key), [128, 130], true);
|
||||
}
|
||||
|
||||
protected function buildSm2PrivateKeyPem(string $privateKeyHex, ?string $publicKeyHex = null): string
|
||||
{
|
||||
$privateKeyRaw = $this->decodeHex($privateKeyHex, 'SM2 private key');
|
||||
$sequence = $this->derSequence(
|
||||
$this->derInteger(1),
|
||||
$this->derOctetString($privateKeyRaw),
|
||||
$this->derContextSpecific(0, $this->derObjectIdentifier(self::SM2_CURVE_OID))
|
||||
);
|
||||
|
||||
if (is_string($publicKeyHex) && $this->isRawHexPublicKey(trim($publicKeyHex))) {
|
||||
$publicKeyRaw = $this->normalizeRawPublicKey(trim($publicKeyHex));
|
||||
$sequence .= $this->derContextSpecific(1, $this->derBitString($publicKeyRaw));
|
||||
$sequence = $this->derSequence($this->derInteger(1), $this->derOctetString($privateKeyRaw), $this->derContextSpecific(0, $this->derObjectIdentifier(self::SM2_CURVE_OID)), $this->derContextSpecific(1, $this->derBitString($publicKeyRaw)));
|
||||
}
|
||||
|
||||
return $this->pemEncode('EC PRIVATE KEY', $sequence);
|
||||
}
|
||||
|
||||
protected function buildSm2PublicKeyPem(string $publicKeyHex): string
|
||||
{
|
||||
$publicKeyRaw = $this->normalizeRawPublicKey($publicKeyHex);
|
||||
$algorithm = $this->derSequence(
|
||||
$this->derObjectIdentifier(self::EC_PUBLIC_KEY_OID),
|
||||
$this->derObjectIdentifier(self::SM2_CURVE_OID)
|
||||
);
|
||||
$subjectPublicKeyInfo = $this->derSequence(
|
||||
$algorithm,
|
||||
$this->derBitString($publicKeyRaw)
|
||||
);
|
||||
|
||||
return $this->pemEncode('PUBLIC KEY', $subjectPublicKeyInfo);
|
||||
}
|
||||
|
||||
protected function normalizeRawPublicKey(string $publicKeyHex): string
|
||||
{
|
||||
$publicKeyHex = strtolower($publicKeyHex);
|
||||
if (strlen($publicKeyHex) === 128) {
|
||||
$publicKeyHex = '04' . $publicKeyHex;
|
||||
}
|
||||
|
||||
return $this->decodeHex($publicKeyHex, 'SM2 public key');
|
||||
}
|
||||
|
||||
protected function pemEncode(string $label, string $der): string
|
||||
{
|
||||
return "-----BEGIN {$label}-----\n"
|
||||
. chunk_split(base64_encode($der), 64, "\n")
|
||||
. "-----END {$label}-----\n";
|
||||
}
|
||||
|
||||
protected function derSequence(string ...$parts): string
|
||||
{
|
||||
return $this->derWrap(0x30, implode('', $parts));
|
||||
}
|
||||
|
||||
protected function derInteger(int $value): string
|
||||
{
|
||||
$encoded = '';
|
||||
$current = $value;
|
||||
do {
|
||||
$encoded = chr($current & 0xff) . $encoded;
|
||||
$current >>= 8;
|
||||
} while ($current > 0);
|
||||
|
||||
if ((ord($encoded[0]) & 0x80) !== 0) {
|
||||
$encoded = "\x00" . $encoded;
|
||||
}
|
||||
|
||||
return $this->derWrap(0x02, $encoded);
|
||||
}
|
||||
|
||||
protected function derOctetString(string $value): string
|
||||
{
|
||||
return $this->derWrap(0x04, $value);
|
||||
}
|
||||
|
||||
protected function derBitString(string $value): string
|
||||
{
|
||||
return $this->derWrap(0x03, "\x00" . $value);
|
||||
}
|
||||
|
||||
protected function derObjectIdentifier(string $oid): string
|
||||
{
|
||||
$parts = array_map('intval', explode('.', $oid));
|
||||
if (count($parts) < 2) {
|
||||
throw new BusinessException('监管平台 OID 格式错误');
|
||||
}
|
||||
|
||||
$encoded = chr(($parts[0] * 40) + $parts[1]);
|
||||
for ($i = 2; $i < count($parts); $i++) {
|
||||
$encoded .= $this->encodeOidPart($parts[$i]);
|
||||
}
|
||||
|
||||
return $this->derWrap(0x06, $encoded);
|
||||
}
|
||||
|
||||
protected function derContextSpecific(int $index, string $value): string
|
||||
{
|
||||
return $this->derWrap(0xa0 + $index, $value);
|
||||
}
|
||||
|
||||
protected function derWrap(int $tag, string $value): string
|
||||
{
|
||||
return chr($tag) . $this->derLength(strlen($value)) . $value;
|
||||
}
|
||||
|
||||
protected function derLength(int $length): string
|
||||
{
|
||||
if ($length < 0x80) {
|
||||
return chr($length);
|
||||
}
|
||||
|
||||
$encoded = '';
|
||||
$current = $length;
|
||||
while ($current > 0) {
|
||||
$encoded = chr($current & 0xff) . $encoded;
|
||||
$current >>= 8;
|
||||
}
|
||||
|
||||
return chr(0x80 | strlen($encoded)) . $encoded;
|
||||
}
|
||||
|
||||
protected function encodeOidPart(int $value): string
|
||||
{
|
||||
if ($value === 0) {
|
||||
return "\x00";
|
||||
}
|
||||
|
||||
$encoded = '';
|
||||
$current = $value;
|
||||
while ($current > 0) {
|
||||
$encoded = chr($current & 0x7f) . $encoded;
|
||||
$current >>= 7;
|
||||
}
|
||||
|
||||
$length = strlen($encoded);
|
||||
for ($i = 0; $i < $length - 1; $i++) {
|
||||
$encoded[$i] = chr(ord($encoded[$i]) | 0x80);
|
||||
}
|
||||
|
||||
return $encoded;
|
||||
}
|
||||
}
|
||||
@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Extend\RegulatoryPlatform;
|
||||
|
||||
use App\Constants\HttpEnumCode;
|
||||
@ -7,18 +9,31 @@ use App\Exception\BusinessException;
|
||||
use App\Utils\Log;
|
||||
use GuzzleHttp\Client;
|
||||
use GuzzleHttp\Exception\GuzzleException;
|
||||
use Hyperf\Context\ApplicationContext;
|
||||
use Hyperf\Di\Annotation\Inject;
|
||||
use Hyperf\Redis\Redis;
|
||||
use Hyperf\Context\ApplicationContext;
|
||||
use Psr\Container\ContainerExceptionInterface;
|
||||
use Psr\Container\ContainerInterface;
|
||||
use Psr\Container\NotFoundExceptionInterface;
|
||||
|
||||
/**
|
||||
* 四川省互联网医疗服务监管平台
|
||||
*/
|
||||
class regulatoryPlatform
|
||||
{
|
||||
protected const ACCESS_TOKEN_CACHE_KEY = 'regulatory_platform_access_token';
|
||||
|
||||
protected const REFRESH_TOKEN_CACHE_KEY = 'regulatory_platform_refresh_token';
|
||||
|
||||
protected const EXPIRES_AT_CACHE_KEY = 'regulatory_platform_access_token_expires_at';
|
||||
|
||||
protected const DEFAULT_ACCESS_TOKEN_TTL = 7200;
|
||||
|
||||
protected const DEFAULT_REFRESH_TOKEN_TTL = 2592000;
|
||||
|
||||
protected const ACCESS_TOKEN_BUFFER = 60;
|
||||
|
||||
protected const HTTP_TIMEOUT = 30;
|
||||
|
||||
protected const HTTP_CONNECT_TIMEOUT = 10;
|
||||
|
||||
#[Inject]
|
||||
protected ContainerInterface $container;
|
||||
|
||||
@ -28,237 +43,770 @@ class regulatoryPlatform
|
||||
#[Inject]
|
||||
protected Redis $redis;
|
||||
|
||||
protected string $api_url;
|
||||
protected string $client_id;
|
||||
protected string $client_secret;
|
||||
protected array $config;
|
||||
|
||||
protected ?string $api_url;
|
||||
|
||||
protected ?string $client_id;
|
||||
|
||||
protected ?string $client_secret;
|
||||
|
||||
protected RegulatoryPlatformProtocol $protocol;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
// 请求地址
|
||||
$this->api_url = \Hyperf\Config\config('regulatory_platform.api_url');
|
||||
$this->client_id = \Hyperf\Config\config('regulatory_platform.client_id');
|
||||
$this->client_secret = \Hyperf\Config\config('regulatory_platform.client_secret');
|
||||
$this->config = (array) \Hyperf\Config\config('regulatory_platform', []);
|
||||
$this->container = ApplicationContext::getContainer();
|
||||
$this->client = $this->container->get(Client::class);
|
||||
$this->redis = $this->container->get(Redis::class);
|
||||
$this->protocol = RegulatoryPlatformProtocol::fromConfig($this->config);
|
||||
$this->client_id = $this->protocol->getClientId();
|
||||
$this->client_secret = $this->protocol->getClientSecret();
|
||||
$this->api_url = $this->resolveConfigValue([
|
||||
'api_url',
|
||||
'v2.api_url',
|
||||
'v2.data_upload_base_url',
|
||||
'data_upload_base_url',
|
||||
'data_api_url',
|
||||
'base_url',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取请求token
|
||||
* @return string
|
||||
*/
|
||||
public function getAccessToken(): string
|
||||
public function protocol(): RegulatoryPlatformProtocol
|
||||
{
|
||||
// 获取token
|
||||
$option = [
|
||||
"json" => array(
|
||||
"clientId" => $this->client_id,
|
||||
"appSecret" => $this->client_secret,
|
||||
),
|
||||
];
|
||||
return $this->protocol;
|
||||
}
|
||||
|
||||
public function buildV2RequestEnvelope(
|
||||
array $payload,
|
||||
bool $includeClientId = false,
|
||||
?string $clientId = null,
|
||||
?string $clientSecret = null
|
||||
): array {
|
||||
return $this->protocol->buildRequestEnvelope($payload, $includeClientId, $clientId, $clientSecret);
|
||||
}
|
||||
|
||||
public function decodeV2ResponseEnvelope(
|
||||
array $response,
|
||||
bool $verifySignature = false,
|
||||
?string $clientId = null,
|
||||
?string $clientSecret = null
|
||||
): array {
|
||||
return $this->protocol->decodeResponseEnvelope($response, $verifySignature, $clientId, $clientSecret);
|
||||
}
|
||||
|
||||
public function getAccessToken(bool $forceRefresh = false): string
|
||||
{
|
||||
if (! $forceRefresh) {
|
||||
$cachedAccessToken = $this->getCachedAccessToken();
|
||||
if (! empty($cachedAccessToken)) {
|
||||
return $cachedAccessToken;
|
||||
}
|
||||
}
|
||||
|
||||
$refreshToken = $this->getCachedRefreshToken();
|
||||
if (! empty($refreshToken)) {
|
||||
try {
|
||||
$response = $this->httpRequest($this->api_url . 'wjw/third/oauth/getAccessToken', $option);
|
||||
if (isset($response['status'])) {
|
||||
if ($response['status'] != 0) {
|
||||
if (!empty($response['message'])) {
|
||||
throw new BusinessException($response['message']);
|
||||
}
|
||||
$tokenPayload = $this->refreshAccessToken($refreshToken);
|
||||
|
||||
return $tokenPayload['accessToken'];
|
||||
} catch (\Throwable $throwable) {
|
||||
Log::getInstance('regulatoryPlatform-token', 'regulatory_platform_token')->warning($throwable->getMessage());
|
||||
$this->clearCachedRefreshToken();
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($response['data'])) {
|
||||
// 返回值为空
|
||||
if (!empty($response['message'])) {
|
||||
throw new BusinessException($response['message']);
|
||||
}
|
||||
throw new BusinessException(HttpEnumCode::getMessage(HttpEnumCode::SERVER_ERROR));
|
||||
$tokenPayload = $this->requestAccessToken();
|
||||
|
||||
return $tokenPayload['accessToken'];
|
||||
}
|
||||
|
||||
$data = json_decode($response['data'], true);
|
||||
if (empty($data['accessToken'])) {
|
||||
// 返回值为空
|
||||
throw new BusinessException(HttpEnumCode::getMessage(HttpEnumCode::SERVER_ERROR));
|
||||
public function refreshAccessToken(?string $refreshToken = null): array
|
||||
{
|
||||
$refreshToken ??= $this->getCachedRefreshToken();
|
||||
if (empty($refreshToken)) {
|
||||
throw new BusinessException('Regulatory platform refresh token cache is empty');
|
||||
}
|
||||
|
||||
// 默认为6天
|
||||
$expires_in = 60 * 60 * 24 * 6;
|
||||
if (!empty($data['expiresIn'])) {
|
||||
if ($data['expiresIn'] > 100){
|
||||
$expires_in = $data['expiresIn'];
|
||||
}
|
||||
return $this->requestToken(
|
||||
$this->buildAuthEndpoint('refresh'),
|
||||
[
|
||||
'grantType' => 'refresh_token',
|
||||
'refreshToken' => $refreshToken,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
$this->redis->set("regulatory_platform_access_token", $data['accessToken'],$expires_in);
|
||||
public function clearTokenCache(): void
|
||||
{
|
||||
$this->redis->del(self::ACCESS_TOKEN_CACHE_KEY);
|
||||
$this->redis->del(self::REFRESH_TOKEN_CACHE_KEY);
|
||||
$this->redis->del(self::EXPIRES_AT_CACHE_KEY);
|
||||
}
|
||||
|
||||
return $data['accessToken'];
|
||||
public function requestV2Decoded(
|
||||
string $url,
|
||||
array $payload,
|
||||
bool $includeClientId = false,
|
||||
bool $verifySignature = false
|
||||
): array {
|
||||
try {
|
||||
$envelope = $this->buildV2RequestEnvelope($payload, $includeClientId);
|
||||
$this->logBusinessRequest('requestV2Decoded', $url, [
|
||||
'payload' => $payload,
|
||||
'includeClientId' => $includeClientId,
|
||||
'verifySignature' => $verifySignature,
|
||||
'requestEnvelope' => $envelope,
|
||||
]);
|
||||
$response = $this->httpRequestV2($url, ['json' => $envelope]);
|
||||
$this->assertV2SuccessResponse($response);
|
||||
|
||||
$decoded = $this->decodeV2ResponseEnvelope($response, $verifySignature);
|
||||
$this->logBusinessResponse('requestV2Decoded', $url, [
|
||||
'verifySignature' => $verifySignature,
|
||||
'response' => $response,
|
||||
'decodedResponse' => $decoded,
|
||||
]);
|
||||
|
||||
return $decoded;
|
||||
} catch (GuzzleException $e) {
|
||||
$this->logBusinessException('requestV2Decoded', $url, $e, [
|
||||
'payload' => $payload,
|
||||
'includeClientId' => $includeClientId,
|
||||
'verifySignature' => $verifySignature,
|
||||
]);
|
||||
throw new BusinessException($e->getMessage());
|
||||
} catch (\Throwable $throwable) {
|
||||
$this->logBusinessException('requestV2Decoded', $url, $throwable, [
|
||||
'payload' => $payload,
|
||||
'includeClientId' => $includeClientId,
|
||||
'verifySignature' => $verifySignature,
|
||||
]);
|
||||
throw $throwable;
|
||||
}
|
||||
}
|
||||
|
||||
public function requestV2Raw(string $url, array $payload, bool $includeClientId = false): array
|
||||
{
|
||||
try {
|
||||
$envelope = $this->buildV2RequestEnvelope($payload, $includeClientId);
|
||||
$this->logBusinessRequest('requestV2Raw', $url, [
|
||||
'payload' => $payload,
|
||||
'includeClientId' => $includeClientId,
|
||||
'requestEnvelope' => $envelope,
|
||||
]);
|
||||
$response = $this->httpRequestV2($url, ['json' => $envelope]);
|
||||
$this->assertV2SuccessResponse($response);
|
||||
$this->logBusinessResponse('requestV2Raw', $url, [
|
||||
'response' => $response,
|
||||
]);
|
||||
|
||||
return $response;
|
||||
} catch (GuzzleException $e) {
|
||||
$this->logBusinessException('requestV2Raw', $url, $e, [
|
||||
'payload' => $payload,
|
||||
'includeClientId' => $includeClientId,
|
||||
]);
|
||||
throw new BusinessException($e->getMessage());
|
||||
} catch (\Throwable $throwable) {
|
||||
$this->logBusinessException('requestV2Raw', $url, $throwable, [
|
||||
'payload' => $payload,
|
||||
'includeClientId' => $includeClientId,
|
||||
]);
|
||||
throw $throwable;
|
||||
}
|
||||
}
|
||||
|
||||
public function extractV2UploadErrors(array $decodedResponse): array
|
||||
{
|
||||
$rows = $decodedResponse['data']['list'] ?? [];
|
||||
if (! is_array($rows)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$errors = [];
|
||||
foreach ($rows as $row) {
|
||||
if (! is_array($row)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$rowErrors = $row['errors'] ?? [];
|
||||
if (! is_array($rowErrors) || $rowErrors === []) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$errors[] = [
|
||||
'tableName' => isset($row['tableName']) && is_string($row['tableName']) ? $row['tableName'] : '',
|
||||
'errors' => $rowErrors,
|
||||
];
|
||||
}
|
||||
|
||||
return $errors;
|
||||
}
|
||||
|
||||
public function hasV2UploadErrors(array $decodedResponse): bool
|
||||
{
|
||||
return $this->extractV2UploadErrors($decodedResponse) !== [];
|
||||
}
|
||||
|
||||
/**
|
||||
* 上报 网络咨询(网络门诊)服务
|
||||
* @param array $arg
|
||||
* @return array
|
||||
* @throws ContainerExceptionInterface
|
||||
* @throws NotFoundExceptionInterface
|
||||
* 上报网络咨询(网络门诊)服务
|
||||
*/
|
||||
public function uploadConsult(array $arg): array
|
||||
{
|
||||
try {
|
||||
$this->redis = $this->container->get(Redis::class);
|
||||
|
||||
$access_token = $this->redis->get("regulatory_platform_access_token");
|
||||
if (empty($access_token)) {
|
||||
$access_token = $this->getAccessToken();
|
||||
}
|
||||
|
||||
foreach ($arg as &$item){
|
||||
$item['accessToken'] = $access_token;
|
||||
$item['clientId'] = $this->client_id;
|
||||
}
|
||||
|
||||
$option = [
|
||||
"json" => $arg
|
||||
];
|
||||
|
||||
$response = $this->httpRequest($this->api_url . '/wjw/upload/uploadConsult', $option);
|
||||
if (isset($response['status'])) {
|
||||
if ($response['status'] != 0) {
|
||||
if (!empty($response['message'])) {
|
||||
throw new BusinessException($response['message']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $response;
|
||||
} catch (GuzzleException $e) {
|
||||
throw new BusinessException($e->getMessage());
|
||||
}
|
||||
return $this->uploadData('api_upload_consult', $arg);
|
||||
}
|
||||
|
||||
/**
|
||||
* 上报 网络复诊服务
|
||||
* @param array $arg
|
||||
* @return array
|
||||
* @throws ContainerExceptionInterface
|
||||
* @throws NotFoundExceptionInterface
|
||||
* 上报网络复诊服务
|
||||
*/
|
||||
public function uploadFurtherConsult(array $arg): array
|
||||
{
|
||||
try {
|
||||
$this->redis = $this->container->get(Redis::class);
|
||||
|
||||
$access_token = $this->redis->get("regulatory_platform_access_token");
|
||||
if (empty($access_token)) {
|
||||
$access_token = $this->getAccessToken();
|
||||
}
|
||||
|
||||
foreach ($arg as &$item){
|
||||
$item['accessToken'] = $access_token;
|
||||
$item['clientId'] = $this->client_id;
|
||||
}
|
||||
|
||||
$option = [
|
||||
"json" => $arg
|
||||
];
|
||||
|
||||
$response = $this->httpRequest($this->api_url . '/wjw/upload/uploadFurtherConsult', $option);
|
||||
if (isset($response['status'])) {
|
||||
if ($response['status'] != 0) {
|
||||
if (!empty($response['message'])) {
|
||||
throw new BusinessException($response['message']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $response;
|
||||
} catch (GuzzleException $e) {
|
||||
throw new BusinessException($e->getMessage());
|
||||
}
|
||||
return $this->uploadData('api_upload_further_consult', $arg);
|
||||
}
|
||||
|
||||
/**
|
||||
* 上报 电子处方服务
|
||||
* @param array $arg
|
||||
* @return array
|
||||
* @throws ContainerExceptionInterface
|
||||
* @throws NotFoundExceptionInterface
|
||||
* 上报电子处方服务
|
||||
*/
|
||||
public function uploadRecipe(array $arg): array
|
||||
{
|
||||
try {
|
||||
$this->redis = $this->container->get(Redis::class);
|
||||
|
||||
$access_token = $this->redis->get("regulatory_platform_access_token");
|
||||
if (empty($access_token)) {
|
||||
$access_token = $this->getAccessToken();
|
||||
return $this->uploadData('api_upload_recipe', $arg);
|
||||
}
|
||||
|
||||
foreach ($arg as &$item){
|
||||
$item['accessToken'] = $access_token;
|
||||
$item['clientId'] = $this->client_id;
|
||||
}
|
||||
|
||||
$option = [
|
||||
"json" => $arg
|
||||
];
|
||||
|
||||
$response = $this->httpRequest($this->api_url . '/wjw/upload/uploadRecipe', $option);
|
||||
if (isset($response['status'])) {
|
||||
if ($response['status'] != 0) {
|
||||
if (!empty($response['message'])) {
|
||||
throw new BusinessException($response['message']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $response;
|
||||
} catch (GuzzleException $e) {
|
||||
throw new BusinessException($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 请求封装
|
||||
* @param string $path
|
||||
* @param array $arg
|
||||
* @return array
|
||||
* @throws GuzzleException
|
||||
* @throws ContainerExceptionInterface
|
||||
* @throws NotFoundExceptionInterface
|
||||
* 上报药品处方明细
|
||||
*/
|
||||
public function uploadRecipeDetail(array $arg): array
|
||||
{
|
||||
return $this->uploadData('api_upload_recipe_detail_yp', $arg);
|
||||
}
|
||||
|
||||
protected function requestAccessToken(): array
|
||||
{
|
||||
return $this->requestToken(
|
||||
$this->buildAuthEndpoint('access'),
|
||||
[
|
||||
'grantType' => 'client_credentials',
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
protected function requestToken(string $url, array $payload): array
|
||||
{
|
||||
$decoded = $this->requestV2Decoded($url, $payload, true, false);
|
||||
$tokenPayload = $this->extractTokenPayload($decoded['decoded']);
|
||||
$this->cacheTokenPayload($tokenPayload);
|
||||
|
||||
return $tokenPayload;
|
||||
}
|
||||
|
||||
protected function extractTokenPayload(array $decoded): array
|
||||
{
|
||||
$result = $decoded['result'] ?? null;
|
||||
if (! is_array($result)) {
|
||||
throw new BusinessException('Regulatory platform token response missing result');
|
||||
}
|
||||
|
||||
$accessToken = $result['accessToken'] ?? '';
|
||||
if (! is_string($accessToken) || $accessToken === '') {
|
||||
throw new BusinessException('Regulatory platform token response missing accessToken');
|
||||
}
|
||||
|
||||
$refreshToken = $result['refreshToken'] ?? $this->getCachedRefreshToken();
|
||||
$expiresIn = (int) ($result['expiresIn'] ?? self::DEFAULT_ACCESS_TOKEN_TTL);
|
||||
if ($expiresIn <= 0) {
|
||||
$expiresIn = self::DEFAULT_ACCESS_TOKEN_TTL;
|
||||
}
|
||||
|
||||
return [
|
||||
'accessToken' => $accessToken,
|
||||
'refreshToken' => is_string($refreshToken) ? $refreshToken : '',
|
||||
'expiresIn' => $expiresIn,
|
||||
'tokenType' => isset($result['tokenType']) && is_string($result['tokenType']) ? $result['tokenType'] : '',
|
||||
];
|
||||
}
|
||||
|
||||
protected function uploadData(string $tableName, array $rows): array
|
||||
{
|
||||
$accessToken = $this->getAccessToken();
|
||||
$payload = [
|
||||
$tableName => $this->normalizeUploadRows($rows),
|
||||
];
|
||||
$envelope = $this->buildV2RequestEnvelope($payload, false);
|
||||
$response = $this->httpRequestV2($this->buildDataUploadRequestUrl(), [
|
||||
'query' => $this->buildClientCredentialQuery(),
|
||||
'headers' => [
|
||||
'du-token' => $accessToken,
|
||||
],
|
||||
'json' => $envelope,
|
||||
]);
|
||||
$this->assertV2SuccessResponse($response);
|
||||
|
||||
$decodedResponse = $this->decodeV2ResponseEnvelope($response, false);
|
||||
$decodedPayload = $decodedResponse['decoded'];
|
||||
if ($this->hasV2UploadErrors($decodedPayload)) {
|
||||
throw new BusinessException($this->formatV2UploadErrors($this->extractV2UploadErrors($decodedPayload)));
|
||||
}
|
||||
|
||||
return $decodedPayload;
|
||||
}
|
||||
|
||||
protected function cacheTokenPayload(array $tokenPayload): void
|
||||
{
|
||||
$expiresIn = (int) ($tokenPayload['expiresIn'] ?? self::DEFAULT_ACCESS_TOKEN_TTL);
|
||||
if ($expiresIn <= 0) {
|
||||
$expiresIn = self::DEFAULT_ACCESS_TOKEN_TTL;
|
||||
}
|
||||
|
||||
$accessTokenTtl = max($expiresIn - self::ACCESS_TOKEN_BUFFER, 1);
|
||||
$refreshTokenTtl = max($expiresIn, self::DEFAULT_REFRESH_TOKEN_TTL);
|
||||
|
||||
$this->redis->setex(self::ACCESS_TOKEN_CACHE_KEY, $accessTokenTtl, $tokenPayload['accessToken']);
|
||||
$this->redis->setex(self::EXPIRES_AT_CACHE_KEY, $refreshTokenTtl, (string) (time() + $accessTokenTtl));
|
||||
|
||||
if (! empty($tokenPayload['refreshToken'])) {
|
||||
$this->redis->setex(self::REFRESH_TOKEN_CACHE_KEY, $refreshTokenTtl, $tokenPayload['refreshToken']);
|
||||
}
|
||||
}
|
||||
|
||||
protected function getCachedAccessToken(): ?string
|
||||
{
|
||||
$token = $this->redis->get(self::ACCESS_TOKEN_CACHE_KEY);
|
||||
|
||||
return is_string($token) && $token !== '' ? $token : null;
|
||||
}
|
||||
|
||||
protected function getCachedRefreshToken(): ?string
|
||||
{
|
||||
$token = $this->redis->get(self::REFRESH_TOKEN_CACHE_KEY);
|
||||
|
||||
return is_string($token) && $token !== '' ? $token : null;
|
||||
}
|
||||
|
||||
protected function clearCachedRefreshToken(): void
|
||||
{
|
||||
$this->redis->del(self::REFRESH_TOKEN_CACHE_KEY);
|
||||
}
|
||||
|
||||
protected function httpRequest(string $path, array $arg = []): array
|
||||
{
|
||||
$option = [
|
||||
"verify" => false
|
||||
];
|
||||
if (!empty($option)) {
|
||||
$arg = array_merge($arg, $option);
|
||||
}
|
||||
$body = $this->sendJsonRequest($path, $arg, 'regulatoryPlatform-httpRequest', false);
|
||||
|
||||
Log::getInstance("regulatoryPlatform-httpRequest")->info(json_encode($arg,JSON_UNESCAPED_UNICODE));
|
||||
$response = $this->client->post($path, $arg);
|
||||
|
||||
if ($response->getStatusCode() != '200') {
|
||||
// 请求失败
|
||||
throw new BusinessException($response->getBody()->getContents());
|
||||
}
|
||||
|
||||
$body = json_decode($response->getBody(), true);
|
||||
Log::getInstance("regulatoryPlatform-httpRequest")->info(json_encode($body,JSON_UNESCAPED_UNICODE));
|
||||
if (empty($body)) {
|
||||
// 返回值为空
|
||||
throw new BusinessException(HttpEnumCode::getMessage(HttpEnumCode::SERVER_ERROR));
|
||||
}
|
||||
|
||||
// 特殊情况下会返回携带code的数据
|
||||
if (isset($body['code'])) {
|
||||
if (isset($body['message'])) {
|
||||
throw new BusinessException($body['message']);
|
||||
throw new BusinessException((string) $body['message']);
|
||||
}
|
||||
|
||||
throw new BusinessException(HttpEnumCode::getMessage(HttpEnumCode::SERVER_ERROR));
|
||||
}
|
||||
|
||||
return $body;
|
||||
}
|
||||
|
||||
protected function httpRequestV2(string $path, array $arg = []): array
|
||||
{
|
||||
return $this->sendJsonRequest($path, $arg, 'regulatoryPlatform-httpRequestV2', true);
|
||||
}
|
||||
|
||||
protected function assertV2SuccessResponse(array $response): void
|
||||
{
|
||||
if (isset($response['code']) && (int) $response['code'] !== 0) {
|
||||
throw new BusinessException($this->extractV2ErrorMessage($response));
|
||||
}
|
||||
|
||||
if (isset($response['success']) && $response['success'] !== true) {
|
||||
throw new BusinessException($this->extractV2ErrorMessage($response));
|
||||
}
|
||||
|
||||
if (isset($response['serviceSuccess']) && $response['serviceSuccess'] !== true) {
|
||||
throw new BusinessException($this->extractV2ErrorMessage($response));
|
||||
}
|
||||
}
|
||||
|
||||
protected function extractV2ErrorMessage(array $response): string
|
||||
{
|
||||
if (! empty($response['message']) && is_string($response['message'])) {
|
||||
return $response['message'];
|
||||
}
|
||||
|
||||
if (! empty($response['errors']) && is_array($response['errors'])) {
|
||||
$first = $response['errors'][0] ?? null;
|
||||
if (is_string($first) && $first !== '') {
|
||||
return $first;
|
||||
}
|
||||
if (is_array($first)) {
|
||||
$json = json_encode($first, JSON_UNESCAPED_UNICODE);
|
||||
if ($json !== false) {
|
||||
return $json;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return HttpEnumCode::getMessage(HttpEnumCode::SERVER_ERROR);
|
||||
}
|
||||
|
||||
protected function buildAuthEndpoint(string $type): string
|
||||
{
|
||||
if (empty($this->api_url)) {
|
||||
throw new BusinessException('Regulatory platform api_url is not configured');
|
||||
}
|
||||
|
||||
if ($type === 'access') {
|
||||
return $this->joinUrl($this->api_url, '/auth-api/oauth2/accessToken');
|
||||
}
|
||||
|
||||
return $this->joinUrl($this->api_url, '/auth-api/oauth2/refreshToken');
|
||||
}
|
||||
|
||||
protected function buildDataUploadEndpoint(): string
|
||||
{
|
||||
if (empty($this->api_url)) {
|
||||
throw new BusinessException('Regulatory platform api_url is not configured');
|
||||
}
|
||||
|
||||
return $this->joinUrl($this->api_url, '/du-api/v1/dataUpload');
|
||||
}
|
||||
|
||||
protected function buildDataUploadRequestUrl(): string
|
||||
{
|
||||
return $this->buildDataUploadEndpoint();
|
||||
}
|
||||
|
||||
protected function sendJsonRequest(string $path, array $arg, string $logChannel, bool $sanitize): array
|
||||
{
|
||||
$option = [
|
||||
'verify' => false,
|
||||
'timeout' => self::HTTP_TIMEOUT,
|
||||
'connect_timeout' => self::HTTP_CONNECT_TIMEOUT,
|
||||
'http_errors' => false,
|
||||
];
|
||||
if (! empty($option)) {
|
||||
$arg = array_merge($arg, $option);
|
||||
}
|
||||
|
||||
$requestLog = $sanitize ? $this->sanitizeLogContext($arg) : $arg;
|
||||
$requestStartAt = microtime(true);
|
||||
Log::getInstance($logChannel)->info(json_encode([
|
||||
'phase' => 'request',
|
||||
'url' => $path,
|
||||
'request' => $requestLog,
|
||||
], JSON_UNESCAPED_UNICODE));
|
||||
|
||||
$httpLogChannel = 'regulatoryPlatform-http-detail';
|
||||
$requestDetail = $this->buildHttpRequestLogContext($path, $arg, $sanitize);
|
||||
Log::getInstance($httpLogChannel, 'regulatory_platform_http_detail')->info(json_encode([
|
||||
'phase' => 'request',
|
||||
'url' => $path,
|
||||
'request' => $requestDetail,
|
||||
], JSON_UNESCAPED_UNICODE));
|
||||
|
||||
try {
|
||||
$response = $this->client->post($path, $arg);
|
||||
$statusCode = $response->getStatusCode();
|
||||
$bodyText = (string) $response->getBody();
|
||||
$body = json_decode($bodyText, true);
|
||||
$durationMs = $this->toDurationMs($requestStartAt);
|
||||
|
||||
$responseLog = $sanitize
|
||||
? $this->sanitizeLogContext(is_array($body) ? $body : ['raw' => $bodyText])
|
||||
: (is_array($body) ? $body : ['raw' => $bodyText]);
|
||||
Log::getInstance($logChannel)->info(json_encode([
|
||||
'phase' => 'response',
|
||||
'status' => $statusCode,
|
||||
'duration_ms' => $durationMs,
|
||||
'response' => $responseLog,
|
||||
], JSON_UNESCAPED_UNICODE));
|
||||
|
||||
Log::getInstance($httpLogChannel, 'regulatory_platform_http_detail')->info(json_encode([
|
||||
'phase' => 'response',
|
||||
'url' => $path,
|
||||
'status' => $statusCode,
|
||||
'duration_ms' => $durationMs,
|
||||
'response' => $this->buildHttpResponseLogContext($response, $bodyText, $body, $sanitize),
|
||||
], JSON_UNESCAPED_UNICODE));
|
||||
|
||||
if ($statusCode !== 200) {
|
||||
$message = is_array($body) ? $this->extractV2ErrorMessage($body) : $bodyText;
|
||||
throw new BusinessException(sprintf('Regulatory platform HTTP request failed[%s]: %s', $statusCode, $message));
|
||||
}
|
||||
|
||||
if (! is_array($body) || $body === []) {
|
||||
throw new BusinessException(HttpEnumCode::getMessage(HttpEnumCode::SERVER_ERROR));
|
||||
}
|
||||
|
||||
return $body;
|
||||
} catch (\Throwable $throwable) {
|
||||
Log::getInstance($httpLogChannel, 'regulatory_platform_http_detail')->error(json_encode([
|
||||
'phase' => 'exception',
|
||||
'url' => $path,
|
||||
'duration_ms' => $this->toDurationMs($requestStartAt),
|
||||
'request' => $requestDetail,
|
||||
'exception' => [
|
||||
'message' => $throwable->getMessage(),
|
||||
'file' => $throwable->getFile(),
|
||||
'line' => $throwable->getLine(),
|
||||
],
|
||||
], JSON_UNESCAPED_UNICODE));
|
||||
|
||||
throw $throwable;
|
||||
}
|
||||
}
|
||||
|
||||
protected function buildHttpRequestLogContext(string $url, array $arg, bool $sanitize): array
|
||||
{
|
||||
$query = $arg['query'] ?? [];
|
||||
$headers = $arg['headers'] ?? [];
|
||||
$json = $arg['json'] ?? null;
|
||||
$formParams = $arg['form_params'] ?? null;
|
||||
|
||||
return [
|
||||
'method' => 'POST',
|
||||
'url' => $this->appendQueryString($url, is_array($query) ? $query : []),
|
||||
'headers' => $sanitize ? $this->sanitizeLogContext(is_array($headers) ? $headers : []) : $headers,
|
||||
'query' => $sanitize ? $this->sanitizeLogContext(is_array($query) ? $query : []) : $query,
|
||||
'json' => $sanitize ? $this->sanitizeLogContext($json) : $json,
|
||||
'form_params' => $sanitize ? $this->sanitizeLogContext($formParams) : $formParams,
|
||||
'timeout' => $arg['timeout'] ?? null,
|
||||
'connect_timeout' => $arg['connect_timeout'] ?? null,
|
||||
'verify' => $arg['verify'] ?? null,
|
||||
];
|
||||
}
|
||||
|
||||
protected function buildHttpResponseLogContext(object $response, string $bodyText, mixed $body, bool $sanitize): array
|
||||
{
|
||||
$headers = [];
|
||||
foreach ($response->getHeaders() as $key => $value) {
|
||||
$headers[$key] = is_array($value) ? implode('; ', $value) : $value;
|
||||
}
|
||||
|
||||
$normalizedBody = is_array($body) ? $body : ['raw' => $bodyText];
|
||||
|
||||
return [
|
||||
'headers' => $sanitize ? $this->sanitizeLogContext($headers) : $headers,
|
||||
'body' => $sanitize ? $this->sanitizeLogContext($normalizedBody) : $normalizedBody,
|
||||
];
|
||||
}
|
||||
|
||||
protected function appendQueryString(string $url, array $query): string
|
||||
{
|
||||
if ($query === []) {
|
||||
return $url;
|
||||
}
|
||||
|
||||
$queryString = http_build_query($query);
|
||||
if ($queryString === '') {
|
||||
return $url;
|
||||
}
|
||||
|
||||
return str_contains($url, '?') ? $url . '&' . $queryString : $url . '?' . $queryString;
|
||||
}
|
||||
|
||||
protected function toDurationMs(float $requestStartAt): int
|
||||
{
|
||||
return (int) round((microtime(true) - $requestStartAt) * 1000);
|
||||
}
|
||||
|
||||
protected function logBusinessRequest(string $action, string $url, array $context): void
|
||||
{
|
||||
Log::getInstance('regulatoryPlatform-business', 'regulatory_platform_business')->info(json_encode([
|
||||
'phase' => 'request',
|
||||
'action' => $action,
|
||||
'url' => $url,
|
||||
'context' => $this->sanitizeLogContext($context),
|
||||
], JSON_UNESCAPED_UNICODE));
|
||||
}
|
||||
|
||||
protected function logBusinessResponse(string $action, string $url, array $context): void
|
||||
{
|
||||
Log::getInstance('regulatoryPlatform-business', 'regulatory_platform_business')->info(json_encode([
|
||||
'phase' => 'response',
|
||||
'action' => $action,
|
||||
'url' => $url,
|
||||
'context' => $this->sanitizeLogContext($context),
|
||||
], JSON_UNESCAPED_UNICODE));
|
||||
}
|
||||
|
||||
protected function logBusinessException(string $action, string $url, \Throwable $throwable, array $context = []): void
|
||||
{
|
||||
Log::getInstance('regulatoryPlatform-business', 'regulatory_platform_business')->error(json_encode([
|
||||
'phase' => 'exception',
|
||||
'action' => $action,
|
||||
'url' => $url,
|
||||
'context' => $this->sanitizeLogContext($context),
|
||||
'exception' => [
|
||||
'message' => $throwable->getMessage(),
|
||||
'file' => $throwable->getFile(),
|
||||
'line' => $throwable->getLine(),
|
||||
],
|
||||
], JSON_UNESCAPED_UNICODE));
|
||||
}
|
||||
|
||||
protected function sanitizeLogContext(mixed $payload): mixed
|
||||
{
|
||||
if (! is_array($payload)) {
|
||||
if (is_string($payload) && strlen($payload) > 160) {
|
||||
return substr($payload, 0, 24) . '...' . substr($payload, -24);
|
||||
}
|
||||
|
||||
return $payload;
|
||||
}
|
||||
|
||||
$masked = [];
|
||||
foreach ($payload as $key => $value) {
|
||||
if (is_array($value)) {
|
||||
$masked[$key] = $this->sanitizeLogContext($value);
|
||||
continue;
|
||||
}
|
||||
|
||||
$keyString = strtolower((string) $key);
|
||||
if (in_array($keyString, ['sign', 'data', 'accesstoken', 'refreshtoken', 'client_secret', 'key'], true)) {
|
||||
if (is_string($value)) {
|
||||
$masked[$key] = [
|
||||
'len' => strlen($value),
|
||||
'preview' => strlen($value) > 16 ? substr($value, 0, 8) . '...' . substr($value, -8) : $value,
|
||||
];
|
||||
} else {
|
||||
$masked[$key] = '[masked]';
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
$masked[$key] = $value;
|
||||
}
|
||||
|
||||
return $masked;
|
||||
}
|
||||
|
||||
protected function normalizeUploadRows(array $rows): array
|
||||
{
|
||||
$normalized = [];
|
||||
foreach ($rows as $row) {
|
||||
if (! is_array($row)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
unset($row['accessToken'], $row['clientId']);
|
||||
if (! isset($row['client_id']) || ! is_string($row['client_id']) || trim($row['client_id']) === '') {
|
||||
$row['client_id'] = (string) $this->client_id;
|
||||
}
|
||||
|
||||
if (array_key_exists('patientSex', $row)) {
|
||||
$row['patientSex'] = $this->normalizePatientSex(
|
||||
$row['patientSex'],
|
||||
isset($row['patientIdcardType']) ? (int) $row['patientIdcardType'] : null,
|
||||
isset($row['patientIdcardNum']) && is_scalar($row['patientIdcardNum']) ? (string) $row['patientIdcardNum'] : null
|
||||
);
|
||||
}
|
||||
|
||||
$normalized[] = $row;
|
||||
}
|
||||
|
||||
return $normalized;
|
||||
}
|
||||
|
||||
protected function normalizePatientSex(mixed $patientSex, ?int $patientIdcardType = null, ?string $patientIdcardNum = null): int
|
||||
{
|
||||
$inferredSex = $this->inferPatientSexFromIdCard($patientIdcardType, $patientIdcardNum);
|
||||
if ($inferredSex !== null) {
|
||||
return $inferredSex;
|
||||
}
|
||||
|
||||
$patientSex = (int) $patientSex;
|
||||
|
||||
return in_array($patientSex, [1, 2], true) ? $patientSex : 0;
|
||||
}
|
||||
|
||||
protected function inferPatientSexFromIdCard(?int $patientIdcardType, ?string $patientIdcardNum): ?int
|
||||
{
|
||||
if ($patientIdcardType !== 1 || empty($patientIdcardNum)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$patientIdcardNum = strtoupper(trim($patientIdcardNum));
|
||||
if (preg_match('/^\d{17}[\dX]$/', $patientIdcardNum) === 1) {
|
||||
return ((int) $patientIdcardNum[16]) % 2 === 1 ? 1 : 2;
|
||||
}
|
||||
|
||||
if (preg_match('/^\d{15}$/', $patientIdcardNum) === 1) {
|
||||
return ((int) $patientIdcardNum[14]) % 2 === 1 ? 1 : 2;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
protected function formatV2UploadErrors(array $errors): string
|
||||
{
|
||||
$messages = [];
|
||||
foreach ($errors as $error) {
|
||||
$tableName = isset($error['tableName']) && is_string($error['tableName']) ? $error['tableName'] : '';
|
||||
$rowErrors = $error['errors'] ?? [];
|
||||
if (! is_array($rowErrors) || $rowErrors === []) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$message = implode('; ', array_map(static function (mixed $item): string {
|
||||
if (is_string($item)) {
|
||||
return $item;
|
||||
}
|
||||
|
||||
$json = json_encode($item, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||
|
||||
return $json === false ? '[invalid error payload]' : $json;
|
||||
}, $rowErrors));
|
||||
|
||||
$messages[] = $tableName !== '' ? sprintf('%s: %s', $tableName, $message) : $message;
|
||||
}
|
||||
|
||||
if ($messages === []) {
|
||||
return 'Regulatory platform upload validation failed';
|
||||
}
|
||||
|
||||
return implode(' | ', $messages);
|
||||
}
|
||||
|
||||
protected function buildClientCredentialQuery(): array
|
||||
{
|
||||
return array_filter([
|
||||
'client_id' => $this->client_id,
|
||||
'client_secret' => $this->client_secret,
|
||||
], static fn (?string $value): bool => is_string($value) && $value !== '');
|
||||
}
|
||||
|
||||
protected function joinUrl(string $baseUrl, string $path): string
|
||||
{
|
||||
return rtrim($baseUrl, '/') . '/' . ltrim($path, '/');
|
||||
}
|
||||
|
||||
protected function resolveConfigValue(array $paths): ?string
|
||||
{
|
||||
foreach ($paths as $path) {
|
||||
$value = $this->getByPath($this->config, $path);
|
||||
if ($value !== null && $value !== '') {
|
||||
return (string) $value;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
protected function getByPath(array $config, string $path): mixed
|
||||
{
|
||||
$current = $config;
|
||||
foreach (explode('.', $path) as $segment) {
|
||||
if (! is_array($current) || ! array_key_exists($segment, $current)) {
|
||||
return null;
|
||||
}
|
||||
$current = $current[$segment];
|
||||
}
|
||||
|
||||
return $current;
|
||||
}
|
||||
}
|
||||
11
sql/20260512_add_order_prescription_file_ca_fields.sql
Normal file
11
sql/20260512_add_order_prescription_file_ca_fields.sql
Normal file
@ -0,0 +1,11 @@
|
||||
ALTER TABLE `order_prescription_file`
|
||||
ADD COLUMN `doctor_ca_supplier` varchar(16) NOT NULL DEFAULT '' COMMENT '医生CA供应商' AFTER `doctor_ca_file_id`,
|
||||
ADD COLUMN `doctor_ca_sign_type` varchar(16) NOT NULL DEFAULT '' COMMENT '医生CA签名类型' AFTER `doctor_ca_supplier`,
|
||||
ADD COLUMN `doctor_ca_sign_originaltext` text COMMENT '医生CA签名原文' AFTER `doctor_ca_sign_type`,
|
||||
ADD COLUMN `doctor_ca_sign_text` longtext COMMENT '医生CA签名值' AFTER `doctor_ca_sign_originaltext`,
|
||||
ADD COLUMN `doctor_ca_sign_time` longtext COMMENT '医生CA时间戳签名值' AFTER `doctor_ca_sign_text`,
|
||||
ADD COLUMN `pharmacist_ca_supplier` varchar(16) NOT NULL DEFAULT '' COMMENT '药师CA供应商' AFTER `doctor_ca_sign_time`,
|
||||
ADD COLUMN `pharmacist_ca_sign_type` varchar(16) NOT NULL DEFAULT '' COMMENT '药师CA签名类型' AFTER `pharmacist_ca_supplier`,
|
||||
ADD COLUMN `pharmacist_ca_sign_originaltext` text COMMENT '药师CA签名原文' AFTER `pharmacist_ca_sign_type`,
|
||||
ADD COLUMN `pharmacist_ca_sign_text` longtext COMMENT '药师CA签名值' AFTER `pharmacist_ca_sign_originaltext`,
|
||||
ADD COLUMN `pharmacist_ca_sign_time` longtext COMMENT '药师CA时间戳签名值' AFTER `pharmacist_ca_sign_text`;
|
||||
2
sql/20260512_add_order_prescription_product_price.sql
Normal file
2
sql/20260512_add_order_prescription_product_price.sql
Normal file
@ -0,0 +1,2 @@
|
||||
ALTER TABLE `order_prescription_product`
|
||||
ADD COLUMN `product_price` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '开方时商品单价' AFTER `product_name`;
|
||||
Loading…
x
Reference in New Issue
Block a user