chore: 重写初始提交(清空历史,整理后全量提交)

This commit is contained in:
ywxapp
2026-08-16 16:54:14 +08:00
commit 6c1a106bc1
1808 changed files with 238144 additions and 0 deletions
+398
View File
@@ -0,0 +1,398 @@
<?php
// +----------------------------------------------------------------------
// | YwxApp [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2026-2036 http://ywxapp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Author: ywxapp<admin@ywxapp.cn>
// +----------------------------------------------------------------------
declare (strict_types = 1);
namespace addon\wxchat\controller\api;
use think\facade\Request;
use ywxapp\controller\FrontendBase;
use ywxapp\utils\HttpClient;
use think\facade\Db;
use think\facade\Cache;
use ywxapp\service\JwtService;
/**
* Login 类
*
* @author ywxapp <admin@ywxapp.cn>
*/
class Login extends FrontendBase
{
/**
* Summary of noNeedLogin
* @var array
*/
protected $noNeedLogin = ['*'];
/**
* Summary of noNeedVerify
* @var array
*/
protected $noNeedVerify = ['*'];
/**
* 控制器初始化 initialize
* @return void
*/
protected function initialize()
{}
public function index()
{
echo "test";
}
// 短信验证码登录(验证码校验通过后,查找或自动注册用户,并签发 JWT)
public function smsLogin()
{
try {
$mobile = $this->request->post("mobile");
$event = $this->request->post("event", 'login');
$captcha = $this->request->post("captcha");
$data = ['mobile' => $mobile, 'captcha' => $captcha, 'event' => $event];
validate(\addon\wxchat\validate\Login::class)
->scene('sms')
->check($data);
$ret = app('sms')->check($mobile, $captcha, $event);
if (! $ret) {
$this->error("验证码不正确!");
}
$user = Db::name('member')->where('mobile', $mobile)->where('status', '>=', 0)->find();
if (empty($user)) {
$uid = Db::name('member')->insertGetId([
'account' => $mobile,
'mobile' => $mobile,
'nickname' => $this->generateRandomNickname(),
'password' => '',
'status' => 1,
'create_at' => time(),
'create_ip' => Request::ip(),
'update_at' => time(),
'update_ip' => Request::ip(),
]);
$user = Db::name('member')->find($uid);
} else {
Db::name('member')->where('uid', $user['uid'])->update([
'update_at' => time(),
'update_ip' => Request::ip(),
]);
}
$this->issueToken((int) $user['uid']);
$this->success([
'user_info' => $this->formatUserInfo($user),
'is_registered' => true,
], '登录成功');
} catch (\think\exception\ValidateException $e) {
$this->error($e->getMessage(), 400);
} catch (\Exception $e) {
$this->error('系统错误:' . $e->getMessage(), 500);
}
}
// 一键登录(本机号码):用预下发的一次性 pre_token 换取登录态
// 流程:客户端先调用 getOneClickPreToken(phone) 拿到 pre_token(模拟运营商 SDK 下发的本机号码凭证),
// 再携带 pre_token 调用本接口,服务端校验后签发 JWT。
public function oneClickLogin()
{
try {
$preToken = $this->request->post('pre_token');
if (empty($preToken)) {
return json(['code' => 400, 'msg' => '缺少一键登录凭证', 'data' => null]);
}
$phone = Cache::get('wxchat_oneclick_' . $preToken);
if (empty($phone)) {
return json(['code' => 400, 'msg' => '一键登录凭证已失效,请重试', 'data' => null]);
}
Cache::delete('wxchat_oneclick_' . $preToken);
$user = Db::name('member')->where('mobile', $phone)->where('status', '>=', 0)->find();
if (empty($user)) {
$uid = Db::name('member')->insertGetId([
'account' => $phone,
'mobile' => $phone,
'nickname' => $this->generateRandomNickname(),
'password' => '',
'status' => 1,
'create_at' => time(),
'create_ip' => Request::ip(),
'update_at' => time(),
'update_ip' => Request::ip(),
]);
$user = Db::name('member')->find($uid);
} else {
Db::name('member')->where('uid', $user['uid'])->update([
'update_at' => time(),
'update_ip' => Request::ip(),
]);
}
$this->issueToken((int) $user['uid']);
$this->success([
'user_info' => $this->formatUserInfo($user),
'is_registered' => true,
], '登录成功');
} catch (\think\exception\ValidateException $e) {
return json(['code' => 400, 'msg' => $e->getMessage(), 'data' => null]);
} catch (\Exception $e) {
return json(['code' => 500, 'msg' => '系统错误:' . $e->getMessage(), 'data' => null]);
}
}
// 完善个人信息(注册)。仅写入 user 表真实存在的字段。
public function completeUserInfo()
{
try {
$data = Request::only([
'temp_token', 'nickname', 'avatar',
], 'post');
// 验证临时token
$tempData = Cache::get('temp_login_' . ($data['temp_token'] ?? ''));
if (! $tempData || ($tempData['expire_time'] ?? 0) < time()) {
return json(['code' => 401, 'msg' => '临时凭证已过期,请重新登录', 'data' => null]);
}
if (empty($tempData['verified'])) {
return json(['code' => 403, 'msg' => '手机号未验证', 'data' => null]);
}
$phone = $tempData['phone'];
// 检查手机号是否已被注册
$exists = Db::name('member')
->where('mobile', $phone)
->where('status', 1)
->find();
if ($exists) {
return json(['code' => 400, 'msg' => '该手机号已注册', 'data' => null]);
}
// 处理头像上传
$avatarPath = '';
if (! empty($data['avatar'])) {
$avatarPath = $this->handleAvatarUpload($data['avatar'], $phone);
}
$nickname = ! empty($data['nickname']) ? $data['nickname'] : $this->generateRandomNickname();
$userData = [
'account' => $phone,
'mobile' => $phone,
'nickname' => $nickname,
'avatar' => $avatarPath,
'password' => '',
'status' => 1,
'create_at' => time(),
'create_ip' => Request::ip(),
'update_at' => time(),
'update_ip' => Request::ip(),
];
$userId = Db::name('member')->insertGetId($userData);
if (! $userId) {
throw new \Exception('用户注册失败');
}
Cache::delete('temp_login_' . ($data['temp_token'] ?? ''));
$this->issueToken($userId);
$this->logUserRegister($userId, $userData);
$user = Db::name('member')->find($userId);
$this->success([
'user_info' => $this->formatUserInfo($user),
'is_registered' => true,
], '注册成功');
} catch (\think\exception\ValidateException $e) {
return json(['code' => 400, 'msg' => $e->getMessage(), 'data' => null]);
} catch (\Exception $e) {
return json(['code' => 500, 'msg' => '系统错误:' . $e->getMessage(), 'data' => null]);
}
}
// 刷新 Access Token(无感续期):校验 refresh_token,重签 access 并轮转 refresh
public function refresh()
{
try {
$refreshToken = $this->request->post('refresh_token');
if (empty($refreshToken)) {
$this->error('缺少刷新令牌', 400);
}
$jwt = JwtService::instance();
$token = $jwt->parseRefreshToken($refreshToken);
$claims = $token->claims()->all();
$uid = (int) ($claims['uid'] ?? 0);
if ($uid <= 0) {
$this->error('刷新令牌无效', 400);
}
// 重新签发 access token,并轮转 refresh token
$jwt->createAccessToken(['uid' => $uid]);
$jwt->createRefreshToken(['uid' => $uid]);
$this->success([], '刷新成功');
} catch (\Exception $e) {
$this->error('刷新令牌已失效,请重新登录', 401);
}
}
// 下发一键登录预凭证(模拟运营商 SDK 下发的本机号码凭证)
public function getOneClickPreToken()
{
try {
$phone = $this->request->post('phone');
if (! preg_match('/^1[3-9]\d{9}$/', (string) $phone)) {
$this->error('手机号格式错误', 400);
}
$preToken = md5(uniqid((string) microtime(true), true) . $phone . time());
Cache::set('wxchat_oneclick_' . $preToken, $phone, 300);
$this->success(['pre_token' => $preToken]);
} catch (\Exception $e) {
$this->error('系统错误:' . $e->getMessage(), 500);
}
}
// 发送短信验证码
public function sendSmsCode()
{
try {
$phone = Request::param('phone');
// 验证手机号格式
if (! preg_match('/^1[3-9]\d{9}$/', $phone)) {
return json(['code' => 400, 'msg' => '手机号格式错误', 'data' => null]);
}
// 检查发送频率
$cacheKey = 'sms_limit_' . $phone;
$lastSendTime = Cache::get($cacheKey);
if ($lastSendTime && time() - $lastSendTime < 60) {
return json(['code' => 400, 'msg' => '请稍后再试', 'data' => null]);
}
// 生成6位随机验证码
$code = str_pad(random_int(0, 999999), 6, '0', STR_PAD_LEFT);
// 调用短信服务
$smsService = new SmsService();
$sendResult = $smsService->sendLoginCode($phone, $code);
if ($sendResult) {
// 记录发送时间
Cache::set($cacheKey, time(), 60);
return json([
'code' => 200,
'msg' => '验证码发送成功',
'data' => [
'expire_time' => 300, // 5分钟有效期
],
]);
} else {
return json(['code' => 500, 'msg' => '验证码发送失败', 'data' => null]);
}
} catch (\Exception $e) {
return json(['code' => 500, 'msg' => '系统错误:' . $e->getMessage(), 'data' => null]);
}
}
/**
* 签发 JWTaccess + refresh),借助 JwtService 内部写入 app()->result
* 最终由 $this->success() 在响应顶层输出 access_token/refresh_token/...
*/
private function issueToken(int $uid) : void
{
JwtService::instance()->createToken(['uid' => $uid]);
}
/**
* 统一输出用户信息(对齐客户端 IUserInfo 字段)
*/
private function formatUserInfo(array $user) : array
{
return [
'uid' => (int) ($user['uid'] ?? 0),
'account' => $user['account'] ?? '',
'nickname' => $user['nickname'] ?? '',
'avatar' => $user['avatar'] ?? '',
'mobile' => $user['mobile'] ?? '',
'status' => (int) ($user['status'] ?? 1),
'create_time' => $user['create_at'] ?? 0,
];
}
// 处理头像上传
private function handleAvatarUpload($base64Image, $phone)
{
try {
if (strpos($base64Image, 'data:image') === 0) {
// Base64图片
$imageData = base64_decode(preg_replace('#^data:image/\w+;base64,#i', '', $base64Image));
// 生成文件名
$fileName = 'avatar_' . $phone . '_' . time() . '.jpg';
$filePath = 'uploads/avatar/' . date('Ymd') . '/' . $fileName;
// 保存文件
$savePath = public_path() . $filePath;
$dir = dirname($savePath);
if (! is_dir($dir)) {
mkdir($dir, 0755, true);
}
file_put_contents($savePath, $imageData);
return $filePath;
} else {
// URL或其他格式
return $base64Image;
}
} catch (\Exception $e) {
// 头像上传失败不影响注册
return '';
}
}
// 生成随机昵称
private function generateRandomNickname()
{
$adjectives = ['快乐的', '聪明的', '勇敢的', '温柔的', '活泼的', '安静的', '热情的', '冷静的'];
$nouns = ['小猫', '小狗', '小鸟', '小鱼', '小虎', '小兔', '小熊', '小鹿'];
$numbers = ['123', '456', '789', '007', '888', '999'];
$adj = $adjectives[array_rand($adjectives)];
$noun = $nouns[array_rand($nouns)];
$num = $numbers[array_rand($numbers)];
return $adj . $noun . $num;
}
// 记录用户注册日志
private function logUserRegister($userId, $userData)
{
$logData = [
'user_id' => $userId,
'mobile' => $userData['mobile'] ?? '',
'register_time' => date('Y-m-d H:i:s'),
'register_ip' => Request::ip(),
'invite_user_id' => $userData['invite_user_id'] ?? 0,
'create_at' => time(),
];
try {
Db::name('user_register_log')->insert($logData);
} catch (\Exception $e) {
// 注册日志表可能未初始化,不影响主流程
}
}
}