环境:debian13\php8.3\tp8; jwt版本:7.1;mySQL:8.4/9.7
bash
composer require firebase/php-jwt
sql
CREATE TABLE `user` (
`id` int unsigned NOT NULL AUTO_INCREMENT,
`openid` varchar(64) NOT NULL DEFAULT '',
`nickname` varchar(100) NOT NULL DEFAULT '',
`avatar` varchar(255) NOT NULL DEFAULT '',
`created_at` datetime DEFAULT NULL,
`updated_at` datetime DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_openid` (`openid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
php
#app/api/route/app.php
<?php
declare(strict_types=1);
use think\facade\Route;
Route::post('login', 'Auth/login');
Route::get('user', 'Auth/user');
php
# app/api/config/jwt.php
<?php
declare(strict_types=1);
return [
'key' => env('JWT.KEY', ''),
'algorithm' => 'HS256',
'expire' => 7200,
'issuer' => 'wechat-mini-program',
];
# app/api/config/wechat.php
<?php
declare(strict_types=1);
return [
'app_id' => env('WECHAT.APP_ID', ''),
'app_secret' => env('WECHAT.APP_SECRET', ''),
];
php
# app/api/controller/Auth.php
<?php
declare(strict_types=1);
namespace app\api\controller;
use Firebase\JWT\JWT;
use Firebase\JWT\Key;
use think\facade\Db;
use think\facade\Http;
use think\Request;
use think\response\Json;
use Throwable;
class Auth
{
/**
* 微信小程序登录
*/
public function login(Request $request): Json
{
$code = trim((string) $request->post('code', ''));
if ($code === '') {
return json([
'code' => 400,
'msg' => '缺少微信登录 code',
'data' => null,
], 400);
}
try {
// 使用 code 换取 openid 和 session_key
$response = Http::get(
'https://api.weixin.qq.com/sns/jscode2session',
[
'appid' => config('wechat.app_id'),
'secret' => config('wechat.app_secret'),
'js_code' => $code,
'grant_type' => 'authorization_code',
]
);
$wechatData = $response->json();
if (empty($wechatData['openid'])) {
return json([
'code' => 400,
'msg' => '微信登录失败',
'data' => null,
'error' => $wechatData,
], 400);
}
$openid = $wechatData['openid'];
// 查询本地用户
$user = Db::name('user')
->where('openid', $openid)
->find();
// 第一次登录,创建本地用户
if (!$user) {
$userId = Db::name('user')->insertGetId([
'openid' => $openid,
'created_at' => date('Y-m-d H:i:s'),
'updated_at' => date('Y-m-d H:i:s'),
]);
$user = [
'id' => $userId,
'openid' => $openid,
];
}
$now = time();
$payload = [
'iss' => config('jwt.issuer'),
'iat' => $now,
'nbf' => $now,
'exp' => $now + (int) config('jwt.expire'),
// JWT 中保存本地用户 ID
'sub' => (string) $user['id'],
'user' => [
'id' => $user['id'],
],
];
$token = JWT::encode(
$payload,
config('jwt.key'),
config('jwt.algorithm')
);
return json([
'code' => 0,
'msg' => '登录成功',
'data' => [
'token' => $token,
'token_type' => 'Bearer',
'expires_in' => config('jwt.expire'),
'user_id' => $user['id'],
],
]);
} catch (Throwable $exception) {
return json([
'code' => 500,
'msg' => '服务器处理失败',
'data' => null,
'error' => $exception->getMessage(),
], 500);
}
}
/**
* 验证 JWT
*/
public function user(Request $request): Json
{
$authorization = $request->header('authorization', '');
if (!preg_match('/^Bearer\s+(.+)$/i', $authorization, $matches)) {
return json([
'code' => 401,
'msg' => '请传入 Bearer Token',
'data' => null,
], 401);
}
try {
$token = trim($matches[1]);
$decoded = JWT::decode(
$token,
new Key(
config('jwt.key'),
config('jwt.algorithm')
)
);
$userId = (int) $decoded->sub;
$user = Db::name('user')
->where('id', $userId)
->field('id,nickname,avatar')
->find();
if (!$user) {
return json([
'code' => 404,
'msg' => '用户不存在',
'data' => null,
], 404);
}
return json([
'code' => 0,
'msg' => '获取成功',
'data' => $user,
]);
} catch (Throwable $exception) {
return json([
'code' => 401,
'msg' => 'Token 无效或已经过期',
'data' => null,
'error' => $exception->getMessage(),
], 401);
}
}
}
javascript
// utils/request.js
const BASE_URL = 'https://你的域名/api';
export function request({
url,
method = 'GET',
data = {},
auth = true
}) {
const token = wx.getStorageSync('token');
return new Promise((resolve, reject) => {
wx.request({
url: `${BASE_URL}${url}`,
method,
data,
header: {
'content-type': 'application/json',
...(auth && token
? { Authorization: `Bearer ${token}` }
: {})
},
success: ({ statusCode, data: responseData }) => {
if (statusCode === 401) {
wx.removeStorageSync('token');
wx.showToast({
title: '登录已过期,请重新登录',
icon: 'none'
});
reject(responseData);
return;
}
if (statusCode < 200 || statusCode >= 300) {
reject(responseData);
return;
}
resolve(responseData);
},
fail: reject
});
});
}
javascript
// 调用登录接口:
import { request } from '../../utils/request';
wx.login({
success: async ({ code }) => {
const result = await request({
url: '/login',
method: 'POST',
data: { code },
auth: false
});
wx.setStorageSync('token', result.data.token);
}
});
// 调用需要登录的接口:
import { request } from '../../utils/request';
Page({
async onLoad() {
try {
const result = await request({
url: '/user'
});
console.log(result.data);
} catch (error) {
console.error(error);
}
}
});