登录注册

This commit is contained in:
liu
2026-07-15 12:07:32 +08:00
parent 9eed7c87f6
commit 93c65b56f6
9 changed files with 1231 additions and 51 deletions
+6
View File
@@ -38,6 +38,12 @@ QUEUE_CONNECTION=database
MEMCACHED_HOST=127.0.0.1 MEMCACHED_HOST=127.0.0.1
# 微信小程序配置
WECHAT_MINI_PROGRAM_APP_ID=
WECHAT_MINI_PROGRAM_SECRET=
WECHAT_MINI_PROGRAM_TOKEN=
WECHAT_MINI_PROGRAM_AES_KEY=
# 系统设置 # 系统设置
SETTING_CACHE_KEY=settings SETTING_CACHE_KEY=settings
+69 -5
View File
@@ -4,6 +4,7 @@ namespace App\Http\Controllers;
use App\Http\Requests\UserRegisterRequest; use App\Http\Requests\UserRegisterRequest;
use App\Models\UserModel; use App\Models\UserModel;
use App\Services\Wechat\MiniProgramService;
use Illuminate\Http\JsonResponse; use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Auth;
@@ -16,11 +17,9 @@ use Modules\Common\Trait\RequestJson;
class IndexController class IndexController
{ {
use RequestJson; use RequestJson;
// 权限验证白名单
protected array $noPermission = ['index', 'login', 'register', 'mail'];
/** 获取首页信息 */ /** 获取首页信息 */
#[GetRoute('/index')] #[GetRoute('/index', false)]
public function index(): JsonResponse public function index(): JsonResponse
{ {
$web_setting = site_config('web'); $web_setting = site_config('web');
@@ -29,7 +28,7 @@ class IndexController
} }
/** 用户登录 */ /** 用户登录 */
#[PostRoute('/login')] #[PostRoute('/login', false)]
public function login(Request $request): JsonResponse public function login(Request $request): JsonResponse
{ {
$credentials = $request->validate([ $credentials = $request->validate([
@@ -46,7 +45,7 @@ class IndexController
} }
/** 用户注册 */ /** 用户注册 */
#[PostRoute('/register')] #[PostRoute('/register', false)]
public function register(UserRegisterRequest $request): JsonResponse public function register(UserRegisterRequest $request): JsonResponse
{ {
$data = $request->validated(); $data = $request->validated();
@@ -60,4 +59,69 @@ class IndexController
return $this->error('创建用户失败'); return $this->error('创建用户失败');
} }
/**
* 小程序登录
*
* 仅需 code 完成 openid 换取,首次登录使用默认昵称和头像,
* 用户信息后续通过编辑接口完善。
*/
#[PostRoute('/wx/login', false)]
public function loginMiniProgram(Request $request, MiniProgramService $miniProgram): JsonResponse
{
$validated = $request->validate([
'code' => 'required|string',
'phoneCode' => 'nullable|string',
]);
// 1. 通过 code 换取 openid、session_key、unionid
$session = $miniProgram->codeToSession($validated['code']);
// 2. 构建用户数据,昵称/头像/性别使用默认值
$userData = [
'openid' => $session['openid'],
'unionid' => $session['unionid'] ?? '',
'username' => 'wx_'.uniqid(),
'nickname' => '微信用户',
'avatar' => '',
'gender' => 0,
'password' => '',
];
// 3. 解密手机号
if (! empty($validated['phoneCode'])) {
try {
$phoneInfo = $miniProgram->getPhoneNumber($validated['phoneCode']);
$userData['mobile'] = $phoneInfo['phone_info']['purePhoneNumber'] ?? '';
} catch (\Throwable) {
// 手机号解密失败不阻塞登录
}
}
// 4. 查找或创建用户
$user = UserModel::firstOrCreate(
['openid' => $userData['openid']],
$userData
);
// 5. 更新已有用户缺失的手机号和 unionid
$needUpdate = false;
foreach (['mobile', 'unionid'] as $field) {
if (! empty($userData[$field]) && empty($user->$field)) {
$user->$field = $userData[$field];
$needUpdate = true;
}
}
if ($needUpdate) {
$user->save();
}
// 6. 创建 Sanctum token
$token = $user->createToken($user->username)->toArray();
return $this->success([
'token' => $token['plainTextToken'],
'user' => $user->toArray(),
], __('user.login_success'));
}
} }
+30 -35
View File
@@ -21,48 +21,43 @@ class UserController
#[GetRoute] #[GetRoute]
public function getUserInfo(): JsonResponse public function getUserInfo(): JsonResponse
{ {
$info = auth()->user(); $user = UserModel::query()->find(auth()->id());
return $this->success(compact('info'));
}
#[PostRoute('/logout')] if (! $user) {
public function logout(): JsonResponse return $this->error('用户不存在');
{
$user_id = auth('users')->id();
$model = new UserModel;
if ($model->logout($user_id)) {
return $this->success('退出登录成功');
} else {
return $this->error($model->getErrorMsg());
} }
$data = $user->toArray();
// 手机号脱敏:13812345678 → 138****5678
if (! empty($data['mobile'])) {
$data['mobile'] = substr_replace($data['mobile'], '****', 3, 4);
}
// 邮箱脱敏:test@example.com → t***@example.com
if (! empty($data['email'])) {
$parts = explode('@', $data['email']);
$parts[0] = substr($parts[0], 0, 1).'***';
$data['email'] = implode('@', $parts);
}
return $this->success($data);
} }
#[PutRoute] /** 编辑用户信息(头像、昵称、性别) */
public function setUserInfo(UserUpdateInfoRequest $request): JsonResponse #[PutRoute('/profile')]
{ public function updateProfile(Request $request): JsonResponse
UserModel::where('user_id', auth('user')->id())->update($request->validated());
return $this->error('更新成功');
}
#[PostRoute('/setPwd')]
public function setPassword(Request $request): JsonResponse
{ {
$data = $request->validate([ $data = $request->validate([
'oldPassword' => 'required|string|max:20', 'avatar' => 'required|string|max:500',
'newPassword' => 'required|string|min:6|max:20', 'nickname' => 'required|string|max:32',
'rePassword' => 'required|same:newPassword', 'gender' => 'required|integer|in:0,1,2',
]); ]);
$user_id = auth('user')->id();
$user = UserModel::query()->find($user_id);
if (! password_verify($data['oldPassword'], $user['password'])) {
return $this->error('旧密码不正确!');
}
$user->password = password_hash($data['newPassword'], PASSWORD_DEFAULT);
if ($user->save()) {
return $this->success('更新成功');
}
return $this->error('更新失败'); $user = UserModel::query()->find(auth()->id());
$user->update($data);
return $this->success($user->toArray(), '更新成功');
} }
} }
+4
View File
@@ -28,5 +28,9 @@ class UserModel extends Authenticatable
'email', 'email',
'password', 'password',
'nickname', 'nickname',
'openid',
'unionid',
'avatar',
'gender'
]; ];
} }
@@ -0,0 +1,72 @@
<?php
namespace App\Services\Wechat;
use EasyWeChat\Kernel\Exceptions\HttpException;
use EasyWeChat\MiniApp\Application;
use EasyWeChat\MiniApp\Utils;
/**
* 微信小程序服务基类
*
* 封装 EasyWeChat MiniApp Application,提供单例访问和小程序常用方法。
* 业务模块可继承此类或通过依赖注入使用。
*/
class MiniProgramService
{
protected ?Application $app = null;
/**
* 获取 EasyWeChat MiniApp Application 实例
*/
public function app(): Application
{
if (! $this->app) {
$this->app = new Application(config('wechat.mini_program'));
}
return $this->app;
}
/**
* 获取 Utils 实例(code2Session、解密等)
*/
public function utils(): Utils
{
return $this->app()->getUtils();
}
/**
* 通过 code 换取 session
*
* @return array{openid: string, session_key: string, unionid?: string}
*
* @throws HttpException
*/
public function codeToSession(string $code): array
{
return $this->utils()->codeToSession($code);
}
/**
* 解密加密数据(如用户信息、手机号)
*
* @return array<string, mixed>
*/
public function decryptData(string $sessionKey, string $iv, string $ciphertext): array
{
return $this->utils()->decryptSession($sessionKey, $iv, $ciphertext);
}
/**
* 获取用户手机号
*
* @return array<string, mixed>
*
* @throws HttpException
*/
public function getPhoneNumber(string $code): array
{
return $this->utils()->getPhoneNumber($code);
}
}
+2 -1
View File
@@ -14,7 +14,8 @@
"laravel/framework": "^13.0", "laravel/framework": "^13.0",
"laravel/sanctum": "^4.0", "laravel/sanctum": "^4.0",
"laravel/tinker": "^3.0", "laravel/tinker": "^3.0",
"predis/predis": "2.0" "predis/predis": "2.0",
"w7corp/easywechat": "^6.19"
}, },
"require-dev": { "require-dev": {
"laravel/boost": "^2.0", "laravel/boost": "^2.0",
Generated
+1017 -4
View File
File diff suppressed because it is too large Load Diff
+26
View File
@@ -0,0 +1,26 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| 微信小程序配置
|--------------------------------------------------------------------------
*/
'mini_program' => [
'app_id' => env('WECHAT_MINI_PROGRAM_APP_ID', ''),
'secret' => env('WECHAT_MINI_PROGRAM_SECRET', ''),
'token' => env('WECHAT_MINI_PROGRAM_TOKEN', ''),
'aes_key' => env('WECHAT_MINI_PROGRAM_AES_KEY', ''),
/**
* HTTP 客户端配置
* 参考:https://symfony.com/doc/current/http_client.html
*/
'http' => [
'throw' => false, // 是否直接抛出异常
'timeout' => 10.0,
'retry' => true, // 失败重试
'max_retries' => 2,
],
],
];
+5 -6
View File
@@ -8,9 +8,10 @@ Route::get('/', function () {
// IndexController // IndexController
Route::controller(App\Http\Controllers\IndexController::class)->prefix('api')->group(function () { Route::controller(App\Http\Controllers\IndexController::class)->prefix('api')->group(function () {
Route::get('/index', 'index')->middleware(['auth:sanctum', 'authGuard:users']); Route::get('/index', 'index');
Route::post('/login', 'login')->middleware(['auth:sanctum', 'authGuard:users']); Route::post('/login', 'login');
Route::post('/register', 'register')->middleware(['auth:sanctum', 'authGuard:users']); Route::post('/register', 'register');
Route::post('/wx/login', 'loginMiniProgram');
}); });
// PaymentTransactionController // PaymentTransactionController
@@ -22,9 +23,7 @@ Route::controller(App\Http\Controllers\PaymentTransactionController::class)->pre
// UserController // UserController
Route::controller(App\Http\Controllers\UserController::class)->prefix('api/user')->group(function () { Route::controller(App\Http\Controllers\UserController::class)->prefix('api/user')->group(function () {
Route::get('/', 'getUserInfo')->middleware(['auth:sanctum', 'authGuard:users']); Route::get('/', 'getUserInfo')->middleware(['auth:sanctum', 'authGuard:users']);
Route::post('/logout', 'logout')->middleware(['auth:sanctum', 'authGuard:users']); Route::put('/profile', 'updateProfile')->middleware(['auth:sanctum', 'authGuard:users']);
Route::put('/', 'setUserInfo')->middleware(['auth:sanctum', 'authGuard:users']);
Route::post('/setPwd', 'setPassword')->middleware(['auth:sanctum', 'authGuard:users']);
}); });
// ForumCategoryController // ForumCategoryController