This commit is contained in:
Your Name
2026-03-06 14:27:24 +08:00
parent 9a1b67ca18
commit 52bb80e2f1
134 changed files with 5337 additions and 401 deletions
@@ -272,4 +272,15 @@ class DiagnosisController extends BaseAdminController
$result = DiagnosisLogic::getDoctors();
return $this->data($result);
}
/**
* @notes 生成小程序码
* @return \think\response\Json
*/
public function generateMiniProgramQrcode()
{
$params = (new DiagnosisValidate())->post()->goCheck('generateQrcode');
$result = DiagnosisLogic::generateMiniProgramQrcode($params);
return $this->data($result);
}
}
@@ -800,4 +800,252 @@ class DiagnosisLogic extends BaseLogic
return [];
}
}
}
/**
* @notes 生成小程序码
* @param array $params
* @return array|false
*/
public static function generateMiniProgramQrcode(array $params)
{
try {
$diagnosisId = $params['diagnosis_id'];
$patientId = $params['patient_id'];
$shareUserId = $params['share_user_id'];
// 获取小程序配置
$config = self::getMiniProgramConfig();
if (!$config) {
throw new \Exception('小程序配置未设置');
}
// 构建小程序路径和参数
$page = 'pages/order/monad/monad';
$scene = "id={$diagnosisId}&share_user={$shareUserId}";
// 调用微信接口生成小程序码
$qrcodeUrl = self::generateWxQrcode($config, $page, $scene);
if (!$qrcodeUrl) {
throw new \Exception('生成小程序码失败: ' . self::getError());
}
return [
'qrcode_url' => $qrcodeUrl,
'diagnosis_id' => $diagnosisId,
'patient_id' => $patientId
];
} catch (\Exception $e) {
self::setError($e->getMessage());
return false;
}
}
/**
* @notes 获取小程序配置
* @return array|null
*/
private static function getMiniProgramConfig(): ?array
{
try {
// 从配置表获取小程序配置
$appId = \app\common\service\ConfigService::get('mnp_setting', 'app_id', '');
$appSecret = \app\common\service\ConfigService::get('mnp_setting', 'app_secret', '');
if (empty($appId) || empty($appSecret)) {
return null;
}
return [
'app_id' => $appId,
'app_secret' => $appSecret
];
} catch (\Exception $e) {
return null;
}
}
/**
* @notes 生成微信小程序码
* @param array $config
* @param string $page
* @param string $scene
* @return string|false
*/
private static function generateWxQrcode(array $config, string $page, string $scene, int $retryCount = 0)
{
try {
// 记录请求参数
\think\facade\Log::info('开始生成小程序码', [
'page' => $page,
'scene' => $scene,
'app_id' => $config['app_id'],
'retry_count' => $retryCount
]);
// 获取access_token(如果是重试,强制刷新)
$forceRefresh = $retryCount > 0;
$accessToken = self::getWxAccessToken($config['app_id'], $config['app_secret'], $forceRefresh);
if (!$accessToken) {
throw new \Exception('获取access_token失败: ' . self::getError());
}
// 调用微信接口生成小程序码
$url = "https://api.weixin.qq.com/wxa/getwxacodeunlimit?access_token={$accessToken}";
$data = [
'scene' => $scene,
'page' => $page,
'check_path' => false,
'env_version' => 'release',
'width' => 280
];
$response = self::httpPost($url, json_encode($data));
// 检查是否返回错误(JSON格式)
$result = json_decode($response, true);
if (is_array($result) && isset($result['errcode']) && $result['errcode'] != 0) {
$errorMsg = "生成小程序码失败: errcode={$result['errcode']}, errmsg=" . ($result['errmsg'] ?? '未知错误');
\think\facade\Log::error($errorMsg);
// 如果是AccessToken错误且未重试过,自动重试一次
if (($result['errcode'] == 40001 || $result['errcode'] == 42001) && $retryCount < 1) {
\think\facade\Log::info('AccessToken失效,自动重试(第' . ($retryCount + 1) . '次)');
// 清除缓存
$cacheKey = 'wx_access_token_' . $config['app_id'];
cache($cacheKey, null);
// 递归调用,重试一次
return self::generateWxQrcode($config, $page, $scene, $retryCount + 1);
}
throw new \Exception($errorMsg);
}
// 如果不是JSON错误,说明返回的是图片二进制数据
// 保存图片
$filename = 'qrcode_' . $config['app_id'] . '_' . md5($scene) . '_' . time() . '.png';
$savePath = 'uploads/qrcode/' . date('Ymd') . '/';
$fullPath = public_path() . $savePath;
if (!is_dir($fullPath)) {
mkdir($fullPath, 0755, true);
}
$saved = file_put_contents($fullPath . $filename, $response);
if (!$saved) {
throw new \Exception('保存二维码图片失败');
}
$qrcodeUrl = request()->domain() . '/' . $savePath . $filename;
\think\facade\Log::info('小程序码生成成功', ['url' => $qrcodeUrl, 'retry_count' => $retryCount]);
return $qrcodeUrl;
} catch (\Exception $e) {
\think\facade\Log::error('generateWxQrcode异常: ' . $e->getMessage());
self::setError($e->getMessage());
return false;
}
}
/**
* @notes 获取微信access_token
* @param string $appId
* @param string $appSecret
* @param bool $forceRefresh 是否强制刷新(不使用缓存)
* @return string|false
*/
private static function getWxAccessToken(string $appId, string $appSecret, bool $forceRefresh = false)
{
try {
$cacheKey = 'wx_access_token_' . $appId;
// 如果不是强制刷新,尝试从缓存获取
if (!$forceRefresh) {
$accessToken = cache($cacheKey);
if ($accessToken) {
\think\facade\Log::info('使用缓存的AccessToken');
return $accessToken;
}
} else {
\think\facade\Log::info('强制刷新AccessToken,清除缓存');
cache($cacheKey, null);
}
// 从微信服务器获取
$url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid={$appId}&secret={$appSecret}";
$response = self::httpGet($url);
// 记录原始响应用于调试
\think\facade\Log::info('微信AccessToken响应: ' . $response);
$result = json_decode($response, true);
// 检查是否有错误
if (isset($result['errcode']) && $result['errcode'] != 0) {
$errorMsg = "获取AccessToken失败: errcode={$result['errcode']}, errmsg=" . ($result['errmsg'] ?? '未知错误');
\think\facade\Log::error($errorMsg);
throw new \Exception($errorMsg);
}
if (isset($result['access_token'])) {
// 缓存access_token,有效期7000秒(微信官方7200秒,提前200秒过期)
cache($cacheKey, $result['access_token'], 7000);
\think\facade\Log::info('AccessToken获取成功并已缓存');
return $result['access_token'];
}
throw new \Exception('获取access_token失败: 响应中没有access_token字段');
} catch (\Exception $e) {
\think\facade\Log::error('getWxAccessToken异常: ' . $e->getMessage());
self::setError($e->getMessage());
return false;
}
}
/**
* @notes HTTP GET请求
* @param string $url
* @return string|false
*/
private static function httpGet(string $url)
{
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
$response = curl_exec($ch);
curl_close($ch);
return $response;
}
/**
* @notes HTTP POST请求
* @param string $url
* @param string $data
* @return string|false
*/
private static function httpPost(string $url, string $data)
{
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
$response = curl_exec($ch);
curl_close($ch);
return $response;
}
}
@@ -0,0 +1,231 @@
/**
* @notes 生成小程序码
* @param array $params
* @return array|false
*/
public static function generateMiniProgramQrcode(array $params)
{
try {
$diagnosisId = $params['diagnosis_id'];
$patientId = $params['patient_id'];
$shareUserId = $params['share_user_id'];
// 获取小程序配置
$config = self::getMiniProgramConfig();
if (!$config) {
throw new \Exception('小程序配置未设置');
}
// 构建小程序路径和参数
$page = 'pages/order/monad/monad';
$scene = "id={$diagnosisId}&share_user={$shareUserId}";
// 调用微信接口生成小程序码
$qrcodeUrl = self::generateWxQrcode($config, $page, $scene);
if (!$qrcodeUrl) {
throw new \Exception('生成小程序码失败: ' . self::getError());
}
return [
'qrcode_url' => $qrcodeUrl,
'diagnosis_id' => $diagnosisId,
'patient_id' => $patientId
];
} catch (\Exception $e) {
self::setError($e->getMessage());
return false;
}
}
/**
* @notes 获取小程序配置
* @return array|null
*/
private static function getMiniProgramConfig(): ?array
{
try {
// 从配置表获取小程序配置
$appId = \app\common\service\ConfigService::get('mnp_setting', 'app_id', '');
$appSecret = \app\common\service\ConfigService::get('mnp_setting', 'app_secret', '');
if (empty($appId) || empty($appSecret)) {
return null;
}
return [
'app_id' => $appId,
'app_secret' => $appSecret
];
} catch (\Exception $e) {
return null;
}
}
/**
* @notes 生成微信小程序码
* @param array $config
* @param string $page
* @param string $scene
* @return string|false
*/
private static function generateWxQrcode(array $config, string $page, string $scene)
{
try {
// 记录请求参数
\think\facade\Log::info('开始生成小程序码', [
'page' => $page,
'scene' => $scene,
'app_id' => $config['app_id']
]);
// 获取access_token
$accessToken = self::getWxAccessToken($config['app_id'], $config['app_secret']);
if (!$accessToken) {
throw new \Exception('获取access_token失败: ' . self::getError());
}
// 调用微信接口生成小程序码
$url = "https://api.weixin.qq.com/wxa/getwxacodeunlimit?access_token={$accessToken}";
$data = [
'scene' => $scene,
'page' => $page,
'check_path' => false,
'env_version' => 'release',
'width' => 280
];
\think\facade\Log::info('调用微信API生成小程序码', ['data' => $data]);
$response = self::httpPost($url, json_encode($data));
// 检查是否返回错误(JSON格式)
$result = json_decode($response, true);
if (is_array($result) && isset($result['errcode']) && $result['errcode'] != 0) {
$errorMsg = "生成小程序码失败: errcode={$result['errcode']}, errmsg=" . ($result['errmsg'] ?? '未知错误');
\think\facade\Log::error($errorMsg);
// 如果是AccessToken错误,清除缓存
if ($result['errcode'] == 40001 || $result['errcode'] == 42001) {
$cacheKey = 'wx_access_token_' . $config['app_id'];
cache($cacheKey, null);
\think\facade\Log::info('已清除无效的AccessToken缓存');
}
throw new \Exception($errorMsg);
}
// 如果不是JSON错误,说明返回的是图片二进制数据
// 保存图片
$filename = 'qrcode_' . $config['app_id'] . '_' . md5($scene) . '_' . time() . '.png';
$savePath = 'uploads/qrcode/' . date('Ymd') . '/';
$fullPath = public_path() . $savePath;
if (!is_dir($fullPath)) {
mkdir($fullPath, 0755, true);
}
$saved = file_put_contents($fullPath . $filename, $response);
if (!$saved) {
throw new \Exception('保存二维码图片失败');
}
$qrcodeUrl = request()->domain() . '/' . $savePath . $filename;
\think\facade\Log::info('小程序码生成成功', ['url' => $qrcodeUrl]);
return $qrcodeUrl;
} catch (\Exception $e) {
\think\facade\Log::error('generateWxQrcode异常: ' . $e->getMessage());
self::setError($e->getMessage());
return false;
}
}
/**
* @notes 获取微信access_token
* @param string $appId
* @param string $appSecret
* @return string|false
*/
private static function getWxAccessToken(string $appId, string $appSecret)
{
try {
// 尝试从缓存获取
$cacheKey = 'wx_access_token_' . $appId;
$accessToken = cache($cacheKey);
if ($accessToken) {
return $accessToken;
}
// 从微信服务器获取
$url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid={$appId}&secret={$appSecret}";
$response = self::httpGet($url);
// 记录原始响应用于调试
\think\facade\Log::info('微信AccessToken响应: ' . $response);
$result = json_decode($response, true);
// 检查是否有错误
if (isset($result['errcode']) && $result['errcode'] != 0) {
$errorMsg = "获取AccessToken失败: errcode={$result['errcode']}, errmsg=" . ($result['errmsg'] ?? '未知错误');
\think\facade\Log::error($errorMsg);
throw new \Exception($errorMsg);
}
if (isset($result['access_token'])) {
// 缓存access_token,有效期7000秒(微信官方7200秒,提前200秒过期)
cache($cacheKey, $result['access_token'], 7000);
\think\facade\Log::info('AccessToken获取成功并已缓存');
return $result['access_token'];
}
throw new \Exception('获取access_token失败: 响应中没有access_token字段');
} catch (\Exception $e) {
\think\facade\Log::error('getWxAccessToken异常: ' . $e->getMessage());
self::setError($e->getMessage());
return false;
}
}
/**
* @notes HTTP GET请求
* @param string $url
* @return string|false
*/
private static function httpGet(string $url)
{
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
$response = curl_exec($ch);
curl_close($ch);
return $response;
}
/**
* @notes HTTP POST请求
* @param string $url
* @param string $data
* @return string|false
*/
private static function httpPost(string $url, string $data)
{
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
$response = curl_exec($ch);
curl_close($ch);
return $response;
}
@@ -69,6 +69,14 @@ class DiagnosisValidate extends BaseValidate
{
return $this->only(['id']);
}
public function sceneGenerateQrcode()
{
return $this->only(['diagnosis_id', 'patient_id', 'share_user_id'])
->append('diagnosis_id', 'require')
->append('patient_id', 'require')
->append('share_user_id', 'require');
}
protected function checkDiagnosis($value)
{
+42 -1
View File
@@ -15,6 +15,7 @@
namespace app\api\controller;
use app\adminapi\logic\tcm\DiagnosisLogic;
use app\adminapi\logic\ConfigLogic;
/**
* 中医诊断控制器(供小程序调用)
@@ -27,7 +28,7 @@ class TcmController extends BaseApiController
* @notes 不需要登录的方法
* @var array
*/
public array $notNeedLogin = ['getPatientSignature'];
public array $notNeedLogin = ['getPatientSignature', 'diagnosisDetail', 'getDict'];
/**
* @notes 获取患者签名(供小程序调用)
@@ -50,4 +51,44 @@ class TcmController extends BaseApiController
return $this->data($result);
}
/**
* @notes 获取诊单详情(供小程序调用)
* @return \think\response\Json
*/
public function diagnosisDetail()
{
$id = $this->request->get('id');
if (!$id) {
return $this->fail('诊单ID不能为空');
}
try {
$result = DiagnosisLogic::detail(['id' => $id]);
return $this->data($result);
} catch (\Exception $e) {
return $this->fail($e->getMessage());
}
}
/**
* @notes 获取字典数据(供小程序调用)
* @return \think\response\Json
*/
public function getDict()
{
$type = $this->request->get('type', '');
if (!$type) {
return $this->fail('字典类型不能为空');
}
try {
$data = ConfigLogic::getDictByType($type);
return $this->data($data);
} catch (\Exception $e) {
return $this->fail($e->getMessage());
}
}
}
+1 -1
View File
@@ -75,7 +75,7 @@ class WechatUserService
$unionid = $this->unionid;
$user = User::alias('u')
->field('u.id,u.sn,u.mobile,u.nickname,u.avatar,u.mobile,u.is_disable,u.is_new_user')
->field('u.id,u.sn,u.mobile,u.nickname,u.avatar,u.mobile,u.is_disable,u.is_new_user,au.openid')
->join('user_auth au', 'au.user_id = u.id')
->where(function ($query) use ($openid, $unionid) {
$query->whereOr(['au.openid' => $openid]);