更新
This commit is contained in:
@@ -19,6 +19,9 @@ use app\model\System\OperationLog;
|
||||
|
||||
class LoginController extends BaseController
|
||||
{
|
||||
private const SMS_CODE_TTL_SECONDS = 300; // 5分钟
|
||||
private const SMS_CODE_RESEND_SECONDS = 60; // 60秒可重发
|
||||
|
||||
private function generateToken($userInfo): string
|
||||
{
|
||||
return JwtService::generateToken($userInfo);
|
||||
@@ -29,6 +32,186 @@ class LoginController extends BaseController
|
||||
return JwtService::verifyToken($token);
|
||||
}
|
||||
|
||||
private function getSiteSettingValue(string $label): string
|
||||
{
|
||||
$row = SystemSiteSettings::where('label', $label)
|
||||
->where('delete_time', null)
|
||||
->order('id', 'asc')
|
||||
->find();
|
||||
return $row ? (string)$row['value'] : '';
|
||||
}
|
||||
|
||||
private function smsCodeCacheKey(string $scene, string $tenantName, string $account, string $phone): string
|
||||
{
|
||||
return 'sms_code:' . md5($scene . '|' . $tenantName . '|' . $account . '|' . $phone);
|
||||
}
|
||||
|
||||
private function smsCodeThrottleKey(string $scene, string $tenantName, string $account, string $phone): string
|
||||
{
|
||||
return 'sms_code_throttle:' . md5($scene . '|' . $tenantName . '|' . $account . '|' . $phone);
|
||||
}
|
||||
|
||||
private function postJson(string $url, array $payload, array $headers): array
|
||||
{
|
||||
$body = json_encode($payload, JSON_UNESCAPED_UNICODE);
|
||||
if ($body === false) {
|
||||
return ['ok' => false, 'error' => 'json_encode failed'];
|
||||
}
|
||||
|
||||
$context = stream_context_create([
|
||||
'http' => [
|
||||
'method' => 'POST',
|
||||
'header' => implode("\r\n", $headers),
|
||||
'content' => $body,
|
||||
'timeout' => 10,
|
||||
'ignore_errors' => true,
|
||||
],
|
||||
]);
|
||||
|
||||
$respBody = @file_get_contents($url, false, $context);
|
||||
$status = 0;
|
||||
if (!empty($http_response_header) && is_array($http_response_header)) {
|
||||
foreach ($http_response_header as $line) {
|
||||
if (preg_match('#^HTTP/\\S+\\s+(\\d+)#', $line, $m)) {
|
||||
$status = (int)$m[1];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($respBody === false) {
|
||||
$last = error_get_last();
|
||||
return ['ok' => false, 'status' => $status, 'error' => (string)($last['message'] ?? 'http request failed')];
|
||||
}
|
||||
|
||||
$decoded = json_decode((string)$respBody, true);
|
||||
if (!is_array($decoded)) {
|
||||
$decoded = [];
|
||||
}
|
||||
|
||||
return [
|
||||
'ok' => $status >= 200 && $status < 300,
|
||||
'status' => $status,
|
||||
'body' => (string)$respBody,
|
||||
'json' => $decoded,
|
||||
];
|
||||
}
|
||||
|
||||
private function sendSmsCodeInternal(string $scene, string $tenantName, string $account, string $phone): Json
|
||||
{
|
||||
$throttleKey = $this->smsCodeThrottleKey($scene, $tenantName, $account, $phone);
|
||||
if (Cache::has($throttleKey)) {
|
||||
return json(['code' => 429, 'msg' => '验证码发送过于频繁,请稍后再试']);
|
||||
}
|
||||
|
||||
$backendUrl = $this->getSiteSettingValue('backendUrl');
|
||||
$apiKey = $this->getSiteSettingValue('apiKey');
|
||||
if ($backendUrl === '' || $apiKey === '') {
|
||||
return json(['code' => 500, 'msg' => '短信网关配置缺失,请联系管理员']);
|
||||
}
|
||||
|
||||
$code = (string)random_int(100000, 999999);
|
||||
$sceneTextMap = [
|
||||
'register' => '注册',
|
||||
'reset' => '找回密码',
|
||||
'login' => '登录',
|
||||
];
|
||||
$sceneText = $sceneTextMap[$scene] ?? '验证';
|
||||
$content = "【云泽网】{$sceneText}验证码:{$code},5分钟内有效。";
|
||||
|
||||
$enqueueUrl = rtrim($backendUrl, '/') . '/api/v1/business/outbound-tasks';
|
||||
$resp = $this->postJson($enqueueUrl, [
|
||||
'phone' => $phone,
|
||||
'content' => $content,
|
||||
], [
|
||||
'X-Api-Key: ' . $apiKey,
|
||||
'Content-Type: application/json; charset=utf-8',
|
||||
'Accept: application/json',
|
||||
]);
|
||||
|
||||
if (empty($resp['ok'])) {
|
||||
return json([
|
||||
'code' => 500,
|
||||
'msg' => '验证码短信发送失败',
|
||||
'detail' => [
|
||||
'http_status' => $resp['status'] ?? null,
|
||||
'body_preview' => isset($resp['body']) ? substr((string)$resp['body'], 0, 200) : '',
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
$codeKey = $this->smsCodeCacheKey($scene, $tenantName, $account, $phone);
|
||||
Cache::set($codeKey, $code, self::SMS_CODE_TTL_SECONDS);
|
||||
Cache::set($throttleKey, 1, self::SMS_CODE_RESEND_SECONDS);
|
||||
|
||||
return json(['code' => 200, 'msg' => '验证码已发送']);
|
||||
}
|
||||
|
||||
private function buildLoginSuccessResponse($user, $tenant): Json
|
||||
{
|
||||
$tid = (int)$tenant->id;
|
||||
|
||||
try {
|
||||
$loginCount = isset($user['login_count']) && $user['login_count'] !== null ? (int)$user['login_count'] : 0;
|
||||
AdminUser::where('id', $user['id'])->update([
|
||||
'login_count' => $loginCount + 1,
|
||||
'last_login_ip' => $this->request->ip(),
|
||||
'last_login_time' => date('Y-m-d H:i:s')
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
error_log('更新登录信息失败: ' . $e->getMessage());
|
||||
}
|
||||
|
||||
$userInfo = [
|
||||
'id' => $user['id'],
|
||||
'account' => $user['account'],
|
||||
'name' => $user['name'],
|
||||
'group_id' => $user['group_id'],
|
||||
'tid' => $tid,
|
||||
'tenant' => $tenant
|
||||
];
|
||||
|
||||
if ($user['group_id']) {
|
||||
$userGroup = AdminUserGroup::where('id', $user['group_id'])->find();
|
||||
if ($userGroup && $userGroup->rights) {
|
||||
$userInfo['rights'] = json_decode($userGroup->rights, true);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
$token = $this->generateToken($userInfo);
|
||||
} catch (\Exception $e) {
|
||||
$this->logFail('登录管理', '登录', 'Token生成失败: ' . $e->getMessage());
|
||||
return json(['code' => 500, 'msg' => '登录失败,请稍后重试']);
|
||||
}
|
||||
|
||||
try {
|
||||
$cacheKey = 'admin_user_' . $user['id'] . '_' . $tid;
|
||||
\think\facade\Cache::set($cacheKey, $userInfo, 86400 * 7);
|
||||
} catch (\Exception $e) {
|
||||
error_log('用户缓存写入失败: ' . $e->getMessage());
|
||||
}
|
||||
|
||||
try {
|
||||
$this->logSuccess('登录管理', '登录', [
|
||||
'id' => $user['id'],
|
||||
'tid' => $tid,
|
||||
'tenant' => $tenant
|
||||
], $userInfo);
|
||||
} catch (\Exception $e) {
|
||||
error_log('登录日志记录失败: ' . $e->getMessage());
|
||||
}
|
||||
|
||||
return json([
|
||||
'code' => 200,
|
||||
'msg' => '登录成功',
|
||||
'data' => [
|
||||
'token' => $token,
|
||||
'user' => $userInfo
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 登录接口
|
||||
* @return Json
|
||||
@@ -191,6 +374,87 @@ class LoginController extends BaseController
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送手机号登录验证码
|
||||
*/
|
||||
public function sendLoginCode(): Json
|
||||
{
|
||||
try {
|
||||
$data = $this->request->post();
|
||||
$this->validate($data, [
|
||||
'tenant_name|租户名称' => 'require|length:1,128',
|
||||
'phone|手机号' => 'require|mobile',
|
||||
]);
|
||||
|
||||
$tenant = Tenant::where('tenant_name', (string)$data['tenant_name'])
|
||||
->where('status', 1)
|
||||
->find();
|
||||
if (!$tenant) {
|
||||
return json(['code' => 400, 'msg' => '租户不存在或已禁用']);
|
||||
}
|
||||
|
||||
$user = AdminUser::where('tid', (int)$tenant['id'])
|
||||
->where('phone', (string)$data['phone'])
|
||||
->where('status', 1)
|
||||
->where('delete_time', null)
|
||||
->find();
|
||||
if (!$user) {
|
||||
return json(['code' => 404, 'msg' => '手机号未绑定可用账号']);
|
||||
}
|
||||
|
||||
return $this->sendSmsCodeInternal('login', (string)$data['tenant_name'], (string)$data['phone'], (string)$data['phone']);
|
||||
} catch (ValidateException $e) {
|
||||
return json(['code' => 400, 'msg' => $e->getError()]);
|
||||
} catch (\Throwable $e) {
|
||||
return json(['code' => 500, 'msg' => '发送失败:' . $e->getMessage()]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 手机号验证码登录
|
||||
*/
|
||||
public function loginBySms(): Json
|
||||
{
|
||||
try {
|
||||
$data = $this->request->post();
|
||||
$this->validate($data, [
|
||||
'tenant_name|租户名称' => 'require|length:1,128',
|
||||
'phone|手机号' => 'require|mobile',
|
||||
'sms_code|短信验证码' => 'require|length:4,8',
|
||||
]);
|
||||
|
||||
$codeKey = $this->smsCodeCacheKey('login', (string)$data['tenant_name'], (string)$data['phone'], (string)$data['phone']);
|
||||
$cachedCode = (string)Cache::get($codeKey, '');
|
||||
if ($cachedCode === '' || $cachedCode !== (string)$data['sms_code']) {
|
||||
return json(['code' => 400, 'msg' => '短信验证码错误或已过期']);
|
||||
}
|
||||
|
||||
$tenant = Tenant::where('tenant_name', (string)$data['tenant_name'])
|
||||
->where('status', 1)
|
||||
->field(['id', 'tenant_name'])
|
||||
->find();
|
||||
if (!$tenant) {
|
||||
return json(['code' => 401, 'msg' => '租户不存在或已禁用']);
|
||||
}
|
||||
|
||||
$user = AdminUser::where('tid', (int)$tenant['id'])
|
||||
->where('phone', (string)$data['phone'])
|
||||
->where('status', 1)
|
||||
->where('delete_time', null)
|
||||
->find();
|
||||
if (!$user) {
|
||||
return json(['code' => 401, 'msg' => '手机号未绑定可用账号']);
|
||||
}
|
||||
|
||||
Cache::delete($codeKey);
|
||||
return $this->buildLoginSuccessResponse($user, $tenant);
|
||||
} catch (ValidateException $e) {
|
||||
return json(['code' => 400, 'msg' => $e->getError()]);
|
||||
} catch (\Throwable $e) {
|
||||
return json(['code' => 500, 'msg' => '登录失败:' . $e->getMessage()]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 退出登录
|
||||
* @return Json
|
||||
@@ -298,6 +562,182 @@ class LoginController extends BaseController
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册账号(按租户维度)
|
||||
*/
|
||||
public function sendRegisterCode(): Json
|
||||
{
|
||||
try {
|
||||
$data = $this->request->post();
|
||||
$this->validate($data, [
|
||||
'tenant_name|租户名称' => 'require|length:1,128',
|
||||
'account|账号' => 'require|length:3,32',
|
||||
'phone|手机号' => 'require|mobile',
|
||||
]);
|
||||
return $this->sendSmsCodeInternal('register', (string)$data['tenant_name'], (string)$data['account'], (string)$data['phone']);
|
||||
} catch (ValidateException $e) {
|
||||
return json(['code' => 400, 'msg' => $e->getError()]);
|
||||
} catch (\Throwable $e) {
|
||||
return json(['code' => 500, 'msg' => '发送失败:' . $e->getMessage()]);
|
||||
}
|
||||
}
|
||||
|
||||
public function register(): Json
|
||||
{
|
||||
try {
|
||||
$data = $this->request->post();
|
||||
$this->validate($data, [
|
||||
'tenant_name|租户名称' => 'require|length:1,128',
|
||||
'account|账号' => 'require|length:3,32',
|
||||
'name|姓名' => 'require|length:2,32',
|
||||
'password|密码' => 'require|length:6,32',
|
||||
'confirm_password|确认密码' => 'require',
|
||||
'phone|手机号' => 'require|mobile',
|
||||
'sms_code|短信验证码' => 'require|length:4,8',
|
||||
]);
|
||||
|
||||
if ((string)$data['password'] !== (string)$data['confirm_password']) {
|
||||
return json(['code' => 400, 'msg' => '两次输入的密码不一致']);
|
||||
}
|
||||
|
||||
$codeKey = $this->smsCodeCacheKey('register', (string)$data['tenant_name'], (string)$data['account'], (string)$data['phone']);
|
||||
$cachedCode = (string)Cache::get($codeKey, '');
|
||||
if ($cachedCode === '' || $cachedCode !== (string)$data['sms_code']) {
|
||||
return json(['code' => 400, 'msg' => '短信验证码错误或已过期']);
|
||||
}
|
||||
|
||||
$tenant = Tenant::where('tenant_name', (string)$data['tenant_name'])
|
||||
->where('status', 1)
|
||||
->find();
|
||||
if (!$tenant) {
|
||||
return json(['code' => 400, 'msg' => '租户不存在或已禁用']);
|
||||
}
|
||||
|
||||
$exists = AdminUser::where('tid', (int)$tenant['id'])
|
||||
->where('account', (string)$data['account'])
|
||||
->where('delete_time', null)
|
||||
->find();
|
||||
if ($exists) {
|
||||
return json(['code' => 400, 'msg' => '账号已存在']);
|
||||
}
|
||||
|
||||
$now = date('Y-m-d H:i:s');
|
||||
$user = new AdminUser();
|
||||
$user->save([
|
||||
'tid' => (int)$tenant['id'],
|
||||
'account' => (string)$data['account'],
|
||||
'name' => (string)$data['name'],
|
||||
'phone' => (string)$data['phone'],
|
||||
'email' => (string)($data['email'] ?? ''),
|
||||
'password' => md5((string)$data['password']),
|
||||
'status' => 1,
|
||||
'group_id' => 0,
|
||||
'login_count' => 0,
|
||||
'create_time' => $now,
|
||||
'update_time' => $now,
|
||||
]);
|
||||
|
||||
Cache::delete($codeKey);
|
||||
|
||||
return json(['code' => 200, 'msg' => '注册成功']);
|
||||
} catch (ValidateException $e) {
|
||||
return json(['code' => 400, 'msg' => $e->getError()]);
|
||||
} catch (\Throwable $e) {
|
||||
return json(['code' => 500, 'msg' => '注册失败:' . $e->getMessage()]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 忘记密码:通过租户 + 账号 + 手机号重置密码
|
||||
*/
|
||||
public function sendResetCode(): Json
|
||||
{
|
||||
try {
|
||||
$data = $this->request->post();
|
||||
$this->validate($data, [
|
||||
'tenant_name|租户名称' => 'require|length:1,128',
|
||||
'account|账号' => 'require|length:3,32',
|
||||
'phone|手机号' => 'require|mobile',
|
||||
]);
|
||||
|
||||
$tenant = Tenant::where('tenant_name', (string)$data['tenant_name'])
|
||||
->where('status', 1)
|
||||
->find();
|
||||
if (!$tenant) {
|
||||
return json(['code' => 400, 'msg' => '租户不存在或已禁用']);
|
||||
}
|
||||
|
||||
$user = AdminUser::where('tid', (int)$tenant['id'])
|
||||
->where('account', (string)$data['account'])
|
||||
->where('phone', (string)$data['phone'])
|
||||
->where('delete_time', null)
|
||||
->find();
|
||||
if (!$user) {
|
||||
return json(['code' => 404, 'msg' => '账号不存在或手机号不匹配']);
|
||||
}
|
||||
|
||||
return $this->sendSmsCodeInternal('reset', (string)$data['tenant_name'], (string)$data['account'], (string)$data['phone']);
|
||||
} catch (ValidateException $e) {
|
||||
return json(['code' => 400, 'msg' => $e->getError()]);
|
||||
} catch (\Throwable $e) {
|
||||
return json(['code' => 500, 'msg' => '发送失败:' . $e->getMessage()]);
|
||||
}
|
||||
}
|
||||
|
||||
public function resetPassword(): Json
|
||||
{
|
||||
try {
|
||||
$data = $this->request->post();
|
||||
$this->validate($data, [
|
||||
'tenant_name|租户名称' => 'require|length:1,128',
|
||||
'account|账号' => 'require|length:3,32',
|
||||
'phone|手机号' => 'require|mobile',
|
||||
'new_password|新密码' => 'require|length:6,32',
|
||||
'confirm_password|确认密码' => 'require',
|
||||
'sms_code|短信验证码' => 'require|length:4,8',
|
||||
]);
|
||||
|
||||
if ((string)$data['new_password'] !== (string)$data['confirm_password']) {
|
||||
return json(['code' => 400, 'msg' => '两次输入的密码不一致']);
|
||||
}
|
||||
|
||||
$codeKey = $this->smsCodeCacheKey('reset', (string)$data['tenant_name'], (string)$data['account'], (string)$data['phone']);
|
||||
$cachedCode = (string)Cache::get($codeKey, '');
|
||||
if ($cachedCode === '' || $cachedCode !== (string)$data['sms_code']) {
|
||||
return json(['code' => 400, 'msg' => '短信验证码错误或已过期']);
|
||||
}
|
||||
|
||||
$tenant = Tenant::where('tenant_name', (string)$data['tenant_name'])
|
||||
->where('status', 1)
|
||||
->find();
|
||||
if (!$tenant) {
|
||||
return json(['code' => 400, 'msg' => '租户不存在或已禁用']);
|
||||
}
|
||||
|
||||
$user = AdminUser::where('tid', (int)$tenant['id'])
|
||||
->where('account', (string)$data['account'])
|
||||
->where('phone', (string)$data['phone'])
|
||||
->where('delete_time', null)
|
||||
->find();
|
||||
if (!$user) {
|
||||
return json(['code' => 404, 'msg' => '账号不存在或手机号不匹配']);
|
||||
}
|
||||
|
||||
AdminUser::where('id', (int)$user['id'])->update([
|
||||
'password' => md5((string)$data['new_password']),
|
||||
'update_time' => date('Y-m-d H:i:s'),
|
||||
]);
|
||||
|
||||
Cache::delete($codeKey);
|
||||
|
||||
return json(['code' => 200, 'msg' => '密码重置成功']);
|
||||
} catch (ValidateException $e) {
|
||||
return json(['code' => 400, 'msg' => $e->getError()]);
|
||||
} catch (\Throwable $e) {
|
||||
return json(['code' => 500, 'msg' => '重置失败:' . $e->getMessage()]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取极验3.0的id和key
|
||||
* @return Json
|
||||
|
||||
Reference in New Issue
Block a user