Files
hospital-applets-api/app/Services/CaService.php
T
2025-12-31 11:35:03 +08:00

545 lines
19 KiB
PHP

<?php
namespace App\Services;
use App\Exception\BusinessException;
use App\Model\HospitalDepartmentCustom;
use App\Model\OrderInquiryCase;
use App\Model\OrderPrescriptionIcd;
use App\Model\OrderPrescriptionProduct;
use App\Model\UserCaCert;
use App\Model\UserDoctor;
use App\Model\UserDoctorInfo;
use App\Model\UserPharmacistInfo;
use Extend\Alibaba\Oss;
use Extend\Ca\CaOnline;
use Hyperf\Utils\WaitGroup;
use Intervention\Image\ImageManager;
use Swoole\Coroutine\Channel;
use TCPDF;
/**
* CA平台
*/
class CaService extends BaseService
{
// 疾病名称
protected string $icd_name;
// 处方关联商品
protected array $order_prescription_product;
// 获取医生自定义科室名称
protected string $department_name;
// 过敏史
protected string $allergy_history;
// 是否已添加签章配置(第一次需申请)
protected string $is_sign_config;
// 签名图片地址
public string $sign_image_path;
// 唯一标识
protected string $entity_id;
// 证书序列号
protected string $cert_serial_number;
// 身份证号/信用代码
protected string $card_num;
// 处方pdf本地地址
protected string $prescription_pdf_local_path;
// 处方pdf oss地址
protected string $prescription_pdf_oss_path;
/**
* 初始化类,此处会获取基础数据
* @param array|object $order_prescription 处方表数据
* @param string|int $user_id 用户id
* @param int $type 类型 1:医院 2:医生 3:药师
*/
public function __construct(array|object $order_prescription,int $type,string|int $user_id = ""){
// 获取用户、医院签名图片地址、用户标识信息
if ($type == 1){
// 医院
$this->sign_image_path = "basic/file/hospital_signature.png";
$app_env = config('app_env','dev');
if ($app_env == 'prod'){
$this->entity_id = "1";
}else{
$this->entity_id = "53453454621";
}
$this->card_num = "91510106MABTJY4K9R";
}elseif($type == 2){
// 用户
$params = array();
$params['user_id'] = $user_id;
$user_doctor_info = UserDoctorInfo::getOne($params);
if (empty($user_doctor_info)) {
throw new BusinessException("用户数据错误");
}
$this->sign_image_path = substr($user_doctor_info['sign_image'], 1, strlen($user_doctor_info['sign_image']) - 1);
$app_env = config('app_env','dev');
if ($app_env == 'prod'){
$this->entity_id = $user_doctor_info['user_id'];
}else{
$this->entity_id = "516904855112556544";
}
$this->card_num = $user_doctor_info['card_num'];
} elseif ($type == 3){
$params = array();
$params['user_id'] = $user_id;
$user_pharmacist_info = UserPharmacistInfo::getOne($params);
if (empty($user_pharmacist_info)) {
throw new BusinessException("用户数据错误");
}
// 去除用户签名图片第一个/ oss不识别
$this->sign_image_path = substr($user_pharmacist_info['sign_image'], 1, strlen($user_pharmacist_info['sign_image']) - 1);
$this->entity_id = $user_id;
$this->card_num = $user_pharmacist_info['card_num'];
} else{
throw new BusinessException("用户类型错误");
}
if (empty($this->sign_image_path) || empty($this->entity_id)) {
throw new BusinessException("无签名图片/用户唯一标识");
}
$order_prescription_id = $order_prescription['order_prescription_id'];
// 创建协程客户端
$wg = new WaitGroup();
// 创建通道
$channel = new Channel();
$wg->add(2);
// 获取医生自定义科室名称
$doctor_id = $order_prescription['doctor_id'];
co(function () use ($wg,$channel,$doctor_id) {
defer(function() use ($wg) {
$wg->done();
});
// 获取医生数据
$params = array();
$params['doctor_id'] = $doctor_id;
$user_doctor = UserDoctor::getOne($params);
if (empty($user_doctor)) {
$channel->push('医生数据错误');
return;
}
// 获取医生自定义科室数据
$params = array();
$params['department_custom_id'] = $user_doctor['department_custom_id'];
$hospital_department_custom = HospitalDepartmentCustom::getOne($params);
if (empty($hospital_department_custom)) {
$channel->push('医生自定义数据错误');
return;
}
$this->department_name = $hospital_department_custom['department_name'] ?: "";
});
// 获取过敏史
$order_inquiry_id = $order_prescription['order_inquiry_id'];
co(function () use ($wg,$channel,$order_inquiry_id) {
defer(function() use ($wg) {
$wg->done();
});
$params = array();
$params['order_inquiry_id'] = $order_inquiry_id;
$order_inquiry_case = OrderInquiryCase::getOne($params);
if (empty($order_inquiry_case)) {
$channel->push('处方病例数据错误');
return;
}
$this->allergy_history = $order_inquiry_case['allergy_history'] ?: "无";
});
// 结束
$wg->wait();
// 判断通道是否存在异常数据
$res = $channel->isEmpty();
if (!$res){
// 读取通道数据
$data = $channel->pop();
// 关闭channel通道
$channel->close();
throw new BusinessException($data);
}else{
// 关闭channel通道
$channel->close();
}
// 获取处方关联疾病名称
$params = array();
$params['order_prescription_id'] = $order_prescription_id;
$order_prescription_icd = OrderPrescriptionIcd::getList($params);
if (empty($order_prescription_icd)) {
throw new BusinessException("处方疾病数据错误");
}
$this->icd_name = "";
$icd_name = array_column($order_prescription_icd->toArray(), 'icd_name');
if (!empty($icd_name)) {
if (count($icd_name) > 1) {
$this->icd_name = implode(';', $icd_name);
} else {
$this->icd_name = $icd_name[0];
}
}
// 获取处方药品数据
$params = array();
$params['order_prescription_id'] = $order_prescription_id;
$order_prescription_product = OrderPrescriptionProduct::getList($params);
if (empty($order_prescription_product)) {
throw new BusinessException("处方药品数据错误");
}
$this->order_prescription_product = $order_prescription_product->toArray();
// 处方pdf本地地址
$this->prescription_pdf_local_path = "./runtime/file/prescription/" . $order_prescription_id . '.pdf';
// 处方pdf oss地址
$this->prescription_pdf_oss_path = "applet/prescription/" . $order_prescription_id. '.pdf';
}
/**
* 获取云证书签名+验证云证书签名
* @param array|object $order_prescription
*/
public function getVerifyCertSign(array|object $order_prescription)
{
$CaOnline = new CaOnline();
// 获取云证书签名
$data = array();
$data['created_at'] = $order_prescription['doctor_created_time'];
$data['department_custom_name'] = $this->department_name;
$data['user_name'] = $order_prescription['patient_name'];
$data['sex'] = sexToStringSex($order_prescription['patient_sex']);
$data['age'] = $order_prescription['patient_age'];
$data['allergy_history'] = $this->allergy_history;
$data['icd_name'] = $this->icd_name;
$data['doctor_advice'] = $order_prescription['doctor_advice'] ?: "无";
// 商品数据
$data['product'] = array();
foreach ($this->order_prescription_product as $item) {
$product = array();
$product['product_name'] = $item['product_name'] . "(" . $item['product_spec'] . ")"; // 商品名称+商品规格
$product['single_unit'] = $item['single_unit'] ?: ""; // 单次剂量(例:1次1包)
$product['frequency_use'] = $item['frequency_use'] ?: ""; // 使用频率(例:1天3次)
$product['single_use'] = $item['single_use'] ?: ""; // 单次用法(例:口服)
$product['prescription_product_num'] = $item['prescription_product_num'] . $item['packaging_unit']; // 商品数量 + 基本包装单位(例:盒/瓶)
$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'];
}
/**
* 生成处方图片+处方图片生成pdf
* @param array|object $order_prescription
* @return string
*/
public function createPrescriptionImgPdf(array|object $order_prescription): string
{
// 打开基础处方图片
$prescription_image = fopen("./extend/Ca/prescription.jpg", 'r+');
$manager = new ImageManager();
$image = $manager->make($prescription_image);
$fontPath = './extend/Ca/msyh.ttf';
// 处方号
$image->text($order_prescription['prescription_code'], 1480, 540, function ($font) use ($fontPath) {
$font->file($fontPath);
$font->size(60);
$font->align('left');
});
// 日期
$image->text(date('Y-m-d', strtotime($order_prescription['doctor_created_time'])), 354, 675, function ($font) use ($fontPath) {
$font->file($fontPath);
$font->size(60);
$font->align('left');
});
// 科室
$image->text($this->department_name ?: "", 1385, 675, function ($font) use ($fontPath) {
$font->file($fontPath);
$font->size(60);
$font->align('left');
});
// 姓名
$image->text($order_prescription['patient_name'], 354, 795, function ($font) use ($fontPath) {
$font->file($fontPath);
$font->size(60);
$font->align('left');
});
// 性别
$image->text(sexToStringSex($order_prescription['patient_sex']), 1385, 790, function ($font) use ($fontPath) {
$font->file($fontPath);
$font->size(60);
$font->align('left');
});
// 年龄
$image->text($order_prescription['patient_age'], 354, 900, function ($font) use ($fontPath) {
$font->file($fontPath);
$font->size(60);
$font->align('left');
});
// 过敏史
$image->text($this->allergy_history ?: "无", 405, 1030, function ($font) use ($fontPath) {
$font->file($fontPath);
$font->size(60);
$font->align('left');
});
// 初步诊断
$image->text($this->icd_name, 445, 1145, function ($font) use ($fontPath) {
$font->file($fontPath);
$font->size(60);
$font->align('left');
});
// 医生建议
$image->text($order_prescription['doctor_advice'], 445, 1252, function ($font) use ($fontPath) {
$font->file($fontPath);
$font->size(60);
$font->align('left');
});
// 商品数据
foreach ($this->order_prescription_product as $key => $item) {
$x_axis = 229;
$y_axis = 1600 + $key * 250;
$x_axis_num = 1900;// 数量使用
// 商品名称
$image->text($item['product_name'], $x_axis, $y_axis, function ($font) use ($fontPath) {
$font->file($fontPath);
$font->size(50);
$font->align('left');
});
$image->text("X" . $item['prescription_product_num'] . $item['packaging_unit'], $x_axis_num, $y_axis, function ($font) use ($fontPath) {
$font->file($fontPath);
$font->size(50);
$font->align('left');
});
// 用量
$image->text("用量:" . $item['single_unit'] . " " . $item['frequency_use'], $x_axis, $y_axis + 70, function ($font) use ($fontPath) {
$font->file($fontPath);
$font->size(50);
$font->align('left');
});
// 用法
$image->text("用法:" . $item['single_use'], $x_axis, $y_axis + 140, function ($font) use ($fontPath) {
$font->file($fontPath);
$font->size(50);
$font->align('left');
});
}
// 生成图片
$img_result = (string)$image->encode('png', 75);
// 上传处方图片至oss
$oss = new Oss();
$prescription_img_oss_filename = "applet/prescription/" . $order_prescription['order_prescription_id'] . '.' . 'jpg';
$prescription_img_oss_path = $oss->putObject($prescription_img_oss_filename, $img_result);
$prescription_img_oss_path = '/' . $prescription_img_oss_path;
// 图片生成pdf
$pdf = new TCPDF('P', 'mm', 'A4', true, 'UTF-8', false);
$pdf->AddPage();
$pdf->Image('@' . $img_result, 10, 10, 0, 0, '', '', '', false, 300, '', false, false, 0, false, false, false);
$prescription_pdf_local_filename = $order_prescription['order_prescription_id'] . '.' . 'pdf';
$prescription_pdf_local_path = BASE_PATH . "/runtime/file/prescription/";
$res = mkdirs($prescription_pdf_local_path);
if (!$res){
throw new BusinessException("处方临时目录创建失败");
}
// 图片生成的处方pdf存储为本地文件 runtime目录下
$pdf->Output($prescription_pdf_local_path . $prescription_pdf_local_filename, "F");
return $prescription_img_oss_path;
}
/**
* 下载Oss中处方pdf
* @return void
*/
public function downOssPdfToLocal(): void
{
$oss = new Oss();
$oss->getObjectToLocal($this->prescription_pdf_oss_path, $this->prescription_pdf_local_path);
}
/**
* 下载CA中pdf至本地文件
* @param string $file_id
* @return void
*/
public function downCaPdfToLocal(string $file_id): void
{
$CaOnline = new CaOnline();
$prescription_pdf_result = $CaOnline->getSignedFile($this->entity_id, $file_id);
$file = fopen($this->prescription_pdf_local_path, "w");
fwrite($file, $prescription_pdf_result);
fclose($file);
}
/**
* 下载CA中的pdf并上传至oss
* @param string $file_id
* @return string oss地址
*/
public function downCaPdfToOss(string $file_id): string
{
$CaOnline = new CaOnline();
$prescription_pdf_result = $CaOnline->getSignedFile($this->entity_id, $file_id);
// 上传oss
$oss = new Oss();
return $oss->putObject($this->prescription_pdf_oss_path, $prescription_pdf_result);
}
/**
* 进行处方pdf签章
* @param string|int $type 类型 1:医院 2:医生 3:药师
* @return string 文件id
*/
public function addSignPdf(string|int $type): string
{
$oss = new Oss();
if ($type == 1){
$style = "image/resize,w_300,h_300";
}else{
$style = "image/resize,m_lfit,w_100,h_350";
}
$sign_image = $oss->getCusTomObjectToRAM($this->sign_image_path, $style);
$sign_image = base64_encode($sign_image);
if (!$sign_image) {
throw new BusinessException("签名图片下载失败");
}
if ($type == 1){
// 医院
$llx = "370"; // 左边底部X坐标 控制左右(越小越左)
$lly = "210"; // 左边底部Y坐标 控制上下(越小越下)
$urx = "520"; // 右边上部x坐标
$ury = "360"; // 右边上部y坐标
} elseif ($type == 2){
// 医生端
$llx = "120"; // 左边底部X坐标
$lly = "190"; // 左边底部Y坐标
$urx = "190"; // 右边上部x坐标
$ury = "140"; // 右边上部y坐标
}else{
// 药师端
$llx = "350"; // 左边底部X坐标
$lly = "190"; // 左边底部Y坐标
$urx = "440"; // 右边上部x坐标
$ury = "140"; // 右边上部y坐标
}
$sign_param = [
[
"llx" => $llx, // 左边底部X坐标
"lly" => $lly, // 左边底部Y坐标
"urx" => $urx, // 右边上部x坐标
"ury" => $ury, // 右边上部y坐标
"pageList" => [1],
"sealImg" => $sign_image
],
];
$CaOnline = new CaOnline();
// 检测是否已添加签章配置
$params = array();
$params['cert_serial_number'] = $this->cert_serial_number;
$user_ca_cert = UserCaCert::getOne($params);
if (empty($user_ca_cert)){
throw new BusinessException("未申请证书");
}
if ($user_ca_cert['is_sign_config'] == 0){
// 添加签章配置
$data = array();
$data['sign_param'] = json_encode($sign_param);
$data['seal_img'] = $sign_image;
$CaOnline->addUserSignConfig($this->entity_id,$this->card_num,$data);
$params = array();
$params['cert_id'] = $user_ca_cert['cert_id'];
$data = array();
$data['sign_config'] = json_encode($sign_param);
$data['is_sign_config'] = 1;
UserCaCert::edit($params,$data);
}
// 打开处方pdf文件
$pdf_file = fopen($this->prescription_pdf_local_path, 'r');
if (!$pdf_file) {
throw new BusinessException("处方pdf打开失败");
}
// 处方pdf进行签章
$data = array();
$data['sign_param'] = json_encode($sign_param);
$data['pdf_file'] = $pdf_file;
$sign_pdf_result = $CaOnline->addSignPdf($this->entity_id, $data);
if (empty($sign_pdf_result[0]['fileId'])) {
throw new BusinessException("处方签章失败");
}
return $sign_pdf_result[0]['fileId'];
}
}