2025-10-21 09:02:55 +08:00

343 lines
13 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
namespace addons\shopro\controller;
use addons\shopro\controller\Common;
use addons\shopro\library\ccblife\CcbUrlDecrypt;
use app\admin\model\shopro\user\User;
use think\Db;
use think\Log;
/**
* 建行生活用户登录控制器
* 处理建行用户登录、绑定、参数解密等功能
*/
class Ccblife extends Common
{
/**
* 不需要登录的方法
* @var array
*/
protected $noNeedLogin = ['autoLogin', 'login', 'callback', 'decryptParam'];
/**
* 不需要权限的方法
* @var array
*/
protected $noNeedRight = ['*'];
/**
* 建行生活用户登录URL跳转方式或POST方式
* 建行App会携带加密参数跳转到此地址
*
* GET/POST /addons/shopro/ccblife/login
*/
public function login()
{
try {
// 获取参数(支持 GET 和 POST
$ccbParamSJ = $this->request->param('ccbParamSJ', '');
$otherParams = $this->request->param();
// 记录接收到的参数(调试用)
Log::info('建行登录接收参数: ' . json_encode([
'method' => $this->request->method(),
'ccbParamSJ_exists' => !empty($ccbParamSJ),
'ccbParamSJ_length' => strlen($ccbParamSJ),
'all_params' => array_keys($otherParams),
'content_type' => $this->request->header('content-type')
], JSON_UNESCAPED_UNICODE));
// 验证必要参数
if (empty($ccbParamSJ)) {
$this->error('缺少必要参数 ccbParamSJ', [
'received_params' => array_keys($otherParams)
]);
}
// 从插件配置文件直接加载避免config()缓存问题)
$configFile = __DIR__ . '/../config/ccblife.php';
if (!file_exists($configFile)) {
$this->error('建行配置文件不存在');
}
$config = include $configFile;
// 验证配置
if (empty($config['private_key'])) {
$this->error('配置错误private_key 为空');
}
Log::info('建行登录解密参数: ' . json_encode([
'ccbParamSJ_length' => strlen($ccbParamSJ),
'service_id' => $config['service_id'],
'private_key_length' => strlen($config['private_key'])
], JSON_UNESCAPED_UNICODE));
// 解密参数(使用服务方私钥)
$decryptedParams = CcbUrlDecrypt::decrypt($ccbParamSJ, $config['private_key']);
if (!$decryptedParams) {
// 检查日志文件获取详细错误
$logFile = RUNTIME_PATH . 'log/' . date('Ym') . '/' . date('d') . '.log';
$errorDetail = '';
if (file_exists($logFile)) {
$logContent = file_get_contents($logFile);
// 提取最后几行日志
$lines = explode("\n", $logContent);
$errorDetail = implode("\n", array_slice($lines, -10));
}
$this->error('参数解密失败,请查看日志: ' . $errorDetail);
}
// 验证解密结果
if (!is_array($decryptedParams)) {
$this->error('解密参数格式错误');
}
Log::info('解密参数成功: ' . json_encode($decryptedParams, JSON_UNESCAPED_UNICODE));
// 获取建行用户信息(直接从解密参数中获取)
$ccbUserId = $decryptedParams['userid'] ?? '';
$mobile = $decryptedParams['mobile'] ?? '';
if (empty($ccbUserId)) {
$this->error('用户信息获取失败');
}
// 处理用户登录/注册
$userInfo = $this->processUserLogin($ccbUserId, $mobile, $decryptedParams);
// 生成商城Token
$this->auth->direct($userInfo['user_id']);
$token = $this->auth->getToken();
// 构建跳转URL
$redirectUrl = $decryptedParams['redirect_url'] ?? '/pages/index/index';
set_token_in_header($this->auth->getToken());
$this->success(__('Logged in successful'), [
'token' => $token,
'user_info' => $userInfo,
'redirect_url' => $redirectUrl
]);
} catch (\think\exception\HttpResponseException $e) {
// HttpResponseException 是框架正常的响应机制,直接向上抛出
throw $e;
} catch (\Exception $e) {
// 记录详细错误日志
Log::error('建行生活登录失败: ' . $e->getMessage());
Log::error('错误文件: ' . $e->getFile());
Log::error('错误行号: ' . $e->getLine());
Log::error('错误堆栈: ' . $e->getTraceAsString());
// 返回友好的错误信息(如果异常消息为空,给默认提示)
$errorMsg = $e->getMessage() ?: '登录失败,请稍后重试';
$this->error($errorMsg, [
'error_type' => get_class($e),
'error_file' => basename($e->getFile()),
'error_line' => $e->getLine()
]);
}
}
/**
* 处理用户登录/注册
*
* @param string $ccbUserId 建行用户ID
* @param string $mobile 手机号
* @param array $params 其他参数(包含 Usr_Name 等建行返回的数据)
* @return array 用户信息
*/
private function processUserLogin($ccbUserId, $mobile, $params)
{
Db::startTrans();
try {
// 查询是否已存在建行用户
$user = Db::name('user')->where('ccb_user_id', $ccbUserId)->find();
if ($user) {
// 用户已存在,更新登录信息
$isNew = false;
Db::name('user')->where('id', $user['id'])->update([
'prevtime' => $user['logintime'] ?? null, // 记录上次登录时间
'logintime' => time(),
'loginip' => $this->request->ip(),
'updatetime' => time()
]);
// 重新获取更新后的用户信息
$user = Db::name('user')->where('id', $user['id'])->find();
} else {
// 用户不存在,先尝试通过手机号查找
if (!empty($mobile)) {
$user = Db::name('user')->where('mobile', $mobile)->find();
}
if ($user) {
// 手机号已存在绑定建行用户ID
$isNew = false;
Db::name('user')->where('id', $user['id'])->update([
'ccb_user_id' => $ccbUserId,
'prevtime' => $user['logintime'] ?? null,
'logintime' => time(),
'loginip' => $this->request->ip(),
'updatetime' => time()
]);
// 重新获取更新后的用户信息
$user = Db::name('user')->where('id', $user['id'])->find();
} else {
// 创建新用户
$isNew = true;
// 从建行参数中获取用户名Usr_Name 或 nickname
$userName = $params['Usr_Name'] ?? $params['nickname'] ?? '';
if (empty($userName)) {
$userName = '建行用户' . substr($ccbUserId, -4);
}
// 生成盐值
$salt = \fast\Random::alnum();
$userData = [
'ccb_user_id' => $ccbUserId,
'username' => 'ccb_' . substr(md5($ccbUserId), 0, 10), // 唯一用户名
'nickname' => $userName,
'mobile' => $mobile ?: '',
'avatar' => $params['avatar'] ?? '/assets/img/avatar.png',
'status' => 'normal',
'salt' => $salt,
'password' => md5(md5(\fast\Random::alnum(32)) . $salt), // 随机密码
'group_id' => 0, // 默认用户组
'level' => 0, // 默认等级
'gender' => 0, // 未知性别
'money' => 0.00,
'commission' => 0.00,
'score' => 0,
'successions' => 1,
'maxsuccessions' => 1,
'loginfailure' => 0,
'joinip' => $this->request->ip(),
'jointime' => time(),
'logintime' => time(),
'loginip' => $this->request->ip(),
'createtime' => time(),
'updatetime' => time()
];
Log::info('创建建行新用户: ' . json_encode([
'ccb_user_id' => $ccbUserId,
'mobile' => $mobile,
'nickname' => $userName
], JSON_UNESCAPED_UNICODE));
$userId = Db::name('user')->insertGetId($userData);
if (!$userId) {
throw new \Exception('创建用户失败');
}
$user = Db::name('user')->where('id', $userId)->find();
}
}
Db::commit();
Log::info('建行用户登录处理成功: ' . json_encode([
'user_id' => $user['id'],
'ccb_user_id' => $ccbUserId,
'is_new' => $isNew
], JSON_UNESCAPED_UNICODE));
return [
'user_id' => $user['id'],
'nickname' => $user['nickname'],
'avatar' => $user['avatar'],
'mobile' => $user['mobile'],
'is_new' => $isNew,
'ccb_user_id' => $ccbUserId
];
} catch (\Exception $e) {
Db::rollback();
Log::error('建行用户登录处理失败: ' . json_encode([
'error' => $e->getMessage(),
'ccb_user_id' => $ccbUserId,
'trace' => $e->getTraceAsString()
], JSON_UNESCAPED_UNICODE));
throw $e;
}
}
/**
* 解密建行参数(调试用)
* 用于前端测试页面解密建行传递的加密参数
*
* POST /addons/shopro/ccblife/decryptParam
*/
public function decryptParam()
{
try {
// 获取加密参数
$ccbParamSJ = $this->request->post('ccbParamSJ', '');
if (empty($ccbParamSJ)) {
$this->error('缺少 ccbParamSJ 参数');
}
// 从插件配置文件直接加载避免config()缓存问题)
$configFile = __DIR__ . '/../config/ccblife.php';
if (!file_exists($configFile)) {
$this->error('建行配置文件不存在');
}
$config = include $configFile;
// 验证配置
if (empty($config['private_key'])) {
$this->error('配置错误private_key 为空');
}
Log::info('解密建行参数: ' . json_encode([
'ccbParamSJ_length' => strlen($ccbParamSJ),
'service_id' => $config['service_id'],
'private_key_length' => strlen($config['private_key'])
], JSON_UNESCAPED_UNICODE));
// 解密参数(使用服务方私钥)
$decryptedParams = CcbUrlDecrypt::decrypt($ccbParamSJ, $config['private_key']);
if ($decryptedParams === false || empty($decryptedParams)) {
// 检查日志文件获取详细错误
$logFile = RUNTIME_PATH . 'log/' . date('Ymd') . '.log';
$errorDetail = '';
if (file_exists($logFile)) {
$logContent = file_get_contents($logFile);
// 提取最后几行日志
$lines = explode("\n", $logContent);
$errorDetail = implode("\n", array_slice($lines, -10));
}
$this->error('参数解密失败,请查看日志: ' . $errorDetail);
}
// 返回解密后的数据
$this->success('解密成功', $decryptedParams);
} catch (\think\exception\HttpResponseException $e) {
// HttpResponseException 是框架正常的响应机制,直接向上抛出
throw $e;
} catch (\Exception $e) {
Log::error('建行参数解密失败: ' . json_encode([
'error' => $e->getMessage(),
'trace' => $e->getTraceAsString()
], JSON_UNESCAPED_UNICODE));
$this->error('解密失败: ' . $e->getMessage());
}
}
}