From 2682b48d045a57af166f3f3fa1bf79d797325aa6 Mon Sep 17 00:00:00 2001 From: Lmx <1960868911@qq.com> Date: Wed, 11 Feb 2026 15:36:14 +0800 Subject: [PATCH] =?UTF-8?q?feat(auth):=20=E6=B7=BB=E5=8A=A0=E5=BE=AE?= =?UTF-8?q?=E4=BF=A1=E5=B0=8F=E7=A8=8B=E5=BA=8F=E7=99=BB=E5=BD=95=E8=AE=A4?= =?UTF-8?q?=E8=AF=81=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 在app.js中添加token有效性检查机制 - 实现登录页面的完整表单验证和提交逻辑 - 创建request.js工具类封装wx.request请求 - 实现token.js工具类管理认证信息的存储和验证 - 添加自动跳转到登录页面的过期处理机制 - 支持Bearer token认证头信息的自动添加 --- app.js | 19 +++++++ pages/login/login.js | 64 ++++++++++++++++++++++- utils/request.js | 118 +++++++++++++++++++++++++++++++++++++++++++ utils/token.js | 117 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 317 insertions(+), 1 deletion(-) create mode 100644 utils/request.js create mode 100644 utils/token.js diff --git a/app.js b/app.js index 1ed57c4..2569642 100644 --- a/app.js +++ b/app.js @@ -1,4 +1,6 @@ // app.js +const { isAuthenticated } = require('./utils/token'); + App({ onLaunch() { // 展示本地存储能力 @@ -12,8 +14,25 @@ App({ // 发送 res.code 到后台换取 openId, sessionKey, unionId } }) + + // 检查token有效性 + this.checkTokenValidity(); }, + globalData: { userInfo: null + }, + + /** + * 检查token有效性 + */ + checkTokenValidity() { + // 可以在这里添加token过期检查逻辑 + // 例如,如果使用JWT,可以解析token并检查过期时间 + if (isAuthenticated()) { + console.log('用户已认证'); + } else { + console.log('用户未认证,需重新登录'); + } } }) diff --git a/pages/login/login.js b/pages/login/login.js index 24469ad..1271b9f 100644 --- a/pages/login/login.js +++ b/pages/login/login.js @@ -1,13 +1,75 @@ -// pages/login/login.js +const { post } = require('../../utils/request'); +const { setToken, setUserInfo } = require('../../utils/token'); Page({ /** * 页面的初始数据 */ data: { + account:"", + password:"" + }, + + // 账号输入监听(可选,用于实时校验) + handleAccountInput(e) { + this.setData({ account: e.detail }); + }, + // 密码输入监听 + handlePasswordInput(e) { + this.setData({ password: e.detail }); }, + // 重置表单 + handleReset() { + this.setData({ + account: '', + password: '' + }); + wx.showToast({ title: '已重置', icon: 'none' }); + }, + // 登录提交 + async handleLogin() { + const { account, password } = this.data; + + // 基础校验 + if (!account.trim()) { + wx.showToast({ title: '请输入账号', icon: 'none' }); + return; + } + if (!password.trim()) { + wx.showToast({ title: '请输入密码', icon: 'none' }); + return; + } + + try { + const res = await post('/api/login', { + username: account, // 或 phone,根据后端字段调整 + password: password + }, { + showLoading: true, + loadingText: '登录中...' + }); + + // 假设成功返回 { token: 'xxx', userInfo: {...} } + setToken(res.token); + setUserInfo(res.userInfo); + + wx.showToast({ title: '登录成功', icon: 'success' }); + + // 跳转首页 + wx.switchTab({ url: '/pages/index/index' }); // 假设首页是 tab 页 + } catch (error) { + // 错误已在 request 中统一提示,这里可不做处理 + console.error('登录失败:', error); + if (error.statusCode === 401) { + wx.showToast({ + title: `用户名或密码错误`, + icon: 'none' + }); + } + } + }, /** * 生命周期函数--监听页面加载 */ diff --git a/utils/request.js b/utils/request.js new file mode 100644 index 0000000..e73aca5 --- /dev/null +++ b/utils/request.js @@ -0,0 +1,118 @@ +// utils/request.js +const { getAuthHeader, clearAuthData } = require('./token'); + +const BASE_URL = 'http://localhost:3000'; // 替换为你的后端接口域名 + +/** + * 封装 wx.request + * @param {Object} options - 请求配置 + * @param {string} options.url - 接口路径(不包含域名) + * @param {string} [options.method='GET'] - 请求方法 + * @param {Object} [options.data] - 请求参数 + * @param {boolean} [options.showLoading=true] - 是否显示加载提示 + * @param {string} [options.loadingText='加载中...'] - 加载提示文字 + */ +const request = (options) => { + const { + url, + method = 'GET', + data = {}, + showLoading = true, + loadingText = '加载中...' + } = options; + + return new Promise((resolve, reject) => { + let loadingHide = () => {}; + + // 显示 loading + if (showLoading) { + wx.showLoading({ + title: loadingText, + mask: true + }); + loadingHide = wx.hideLoading; + } + + wx.request({ + url: BASE_URL + url, + method, + data, + header: getAuthHeader(), + success: (res) => { + const { statusCode, data: responseData } = res; + if (statusCode === 200) { + // 假设后端返回格式:{ code: 200, data: {}, msg: 'success' } + if (responseData.code === 200) { + resolve(responseData.data); + } else { + // 特殊处理401未授权错误 + if (responseData.code === 401 || statusCode === 401) { + // token可能已过期,清除本地认证信息 + clearAuthData(); + wx.showToast({ + title: '登录已过期,请重新登录', + icon: 'none' + }); + + // 跳转到登录页 + setTimeout(() => { + wx.redirectTo({ + url: '/pages/login/login' + }); + }, 1500); + + reject(responseData); + } else { + // 其他业务错误(如参数错误等) + wx.showToast({ + title: responseData.msg || '请求失败', + icon: 'none' + }); + reject(responseData); + } + } + } else { + // HTTP 状态码非 200 + if (statusCode === 401) { + // token可能已过期,清除本地认证信息 + clearAuthData(); + wx.showToast({ + title: '登录已过期,请重新登录', + icon: 'none' + }); + + // 跳转到登录页 + setTimeout(() => { + wx.redirectTo({ + url: '/pages/login/login' + }); + }, 1500); + } else { + // wx.showToast({ + // title: `请求错误 ${statusCode}`, + // icon: 'none' + // }); + } + reject(res); + } + }, + fail: (err) => { + wx.showToast({ + title: '网络异常,请稍后重试', + icon: 'none' + }); + reject(err); + }, + complete: () => { + // 隐藏 loading + loadingHide(); + } + }); + }); +}; + +module.exports = { + request, + get: (url, data, options = {}) => request({ ...options, url, method: 'GET', data }), + post: (url, data, options = {}) => request({ ...options, url, method: 'POST', data }) +}; \ No newline at end of file diff --git a/utils/token.js b/utils/token.js new file mode 100644 index 0000000..49fa4fc --- /dev/null +++ b/utils/token.js @@ -0,0 +1,117 @@ +// utils/token.js - Token管理工具 + +const TOKEN_KEY = 'token'; +const USER_INFO_KEY = 'userInfo'; + +/** + * 保存token到本地存储 + * @param {string} token - 认证令牌 + */ +const setToken = (token) => { + try { + wx.setStorageSync(TOKEN_KEY, token); + } catch (error) { + console.error('保存token失败:', error); + } +}; + +/** + * 获取本地存储的token + * @returns {string} token值 + */ +const getToken = () => { + try { + return wx.getStorageSync(TOKEN_KEY) || ''; + } catch (error) { + console.error('获取token失败:', error); + return ''; + } +}; + +/** + * 删除本地存储的token + */ +const removeToken = () => { + try { + wx.removeStorageSync(TOKEN_KEY); + } catch (error) { + console.error('删除token失败:', error); + } +}; + +/** + * 保存用户信息到本地存储 + * @param {Object} userInfo - 用户信息对象 + */ +const setUserInfo = (userInfo) => { + try { + wx.setStorageSync(USER_INFO_KEY, userInfo); + } catch (error) { + console.error('保存用户信息失败:', error); + } +}; + +/** + * 获取本地存储的用户信息 + * @returns {Object} 用户信息对象 + */ +const getUserInfo = () => { + try { + return wx.getStorageSync(USER_INFO_KEY) || null; + } catch (error) { + console.error('获取用户信息失败:', error); + return null; + } +}; + +/** + * 删除本地存储的用户信息 + */ +const removeUserInfo = () => { + try { + wx.removeStorageSync(USER_INFO_KEY); + } catch (error) { + console.error('删除用户信息失败:', error); + } +}; + +/** + * 清除所有认证相关信息 + */ +const clearAuthData = () => { + removeToken(); + removeUserInfo(); +}; + +/** + * 检查是否已登录(是否有有效token) + * @returns {boolean} 是否已登录 + */ +const isAuthenticated = () => { + const token = getToken(); + return !!token; +}; + +/** + * 获取完整的认证头信息 + * @returns {Object} 包含认证信息的header对象 + */ +const getAuthHeader = () => { + const token = getToken(); + return { + 'Content-Type': 'application/json', + 'Authorization': token ? `Bearer ${token}` : '' + }; +}; + +module.exports = { + setToken, + getToken, + removeToken, + setUserInfo, + getUserInfo, + removeUserInfo, + clearAuthData, + isAuthenticated, + getAuthHeader +}; \ No newline at end of file