登录注册
This commit is contained in:
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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'));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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(), '更新成功');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
@@ -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
File diff suppressed because it is too large
Load Diff
@@ -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
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user