【慧游鲁博】【10】全端优化用户信息存储+网页端user模块与后端对接
文章目录
- (小程序端)
- 本次更新
- 1. 数据库与实体类调整
- 1.1 数据库表修改
- 1.2 Java 实体类 `User` 调整
- 2. 后端接口修改
- 2.1 更新 `UserVO`(视图对象)
- 2.2 修改 `getUserInfo` 方法
- 3. 小程序端存储优化(Pinia 状态管理)
- 3.1 重构 `member.js`
- 3.2 持久化配置
- 4. 登录逻辑优化
- 4.1 修改 `handleLogin` 方法
- 4.2 获取用户信息的策略
- (网页端)
- 本次更新
- 网页端调整
- 网页端关键实现
- 1. API服务 (user.js)
- 1. 模块结构
- 2. 依赖分析
- 3. API 方法详解
- 3.1 用户登录 (`login`)
- 3.2 获取用户信息 (`getUserInfo`)
- 3.3 更新用户信息 (`updateUserInfo`)
- 3.4 更新用户头像 (`updateAvatar`)
- 3.5 更新用户密码 (`updatePassword`)
- 3.6 获取用户列表 (`getUserList`)
- 2. Token管理 (token.js)
- setToken
- clearToken
- 3. 用户信息组件 (UserInfo.vue)
- 字段与后端 UserVO 匹配
- 编辑逻辑优化
- 可编辑字段控制
- 编辑状态管理
- 数据恢复机制
- 保存逻辑
- 与API的交互
- 4. 登录组件 (Login.vue)
- 表单验证修改
- 验证规则配置
- 自定义验证方法
- 登录逻辑实现
- 与API的交互
(小程序端)
本次更新
在开发微信小程序等移动应用时,用户信息的存储和安全性是需要重点考虑的问题。本次优化通过最小化客户端存储的用户数据,仅保留必要的 id
和 role
,既保证了功能完整性,又提升了安全性。
本文将围绕本次修改,详细介绍:
- 数据库与实体类调整(添加
role
字段) - 后端接口修改(
UserVO
和getUserInfo
方法) - 小程序端存储优化(
Pinia
状态管理调整) - 登录逻辑优化(仅存储
id
和role
)
1. 数据库与实体类调整
1.1 数据库表修改
在 users
表中新增 role
字段,用于区分用户权限:
ALTER TABLE users
ADD COLUMN role INTEGER NOT NULL DEFAULT 1; -- 默认普通用户(1),0表示管理员
1.2 Java 实体类 User
调整
在 User.java
中添加 role
字段:
@Data
@TableName("users")
public class User {// 其他字段...@TableField("role")private Integer role; // 0=管理员,1=普通用户
}
2. 后端接口修改
2.1 更新 UserVO
(视图对象)
在 UserVO.java
中添加 role
字段,确保返回给小程序端的数据包含权限信息:
@Data
public class UserVO {// 其他字段...private Integer role; // 0=管理员,1=普通用户
}
2.2 修改 getUserInfo
方法
确保 getUserInfo
返回的数据包含 role
:
@Override
public Result<UserVO> getUserInfo() {// 查询用户...UserVO userVO = new UserVO();userVO.setId(user.getId());// 其他字段...userVO.setRole(user.getRole()); // 新增 role 字段return Result.success(userVO);
}
3. 小程序端存储优化(Pinia 状态管理)
3.1 重构 member.js
优化 useMemberStore
,仅存储 userId
和 userRole
:
export const useMemberStore = defineStore('member', () => {const profile = ref({token: '', // 登录令牌userId: null, // 仅存储用户IDuserRole: null // 仅存储用户角色});const setUserBasicInfo = (userInfo) => {profile.value.userId = userInfo?.id || null;profile.value.userRole = userInfo?.role ?? null;};// 其他方法...
});
3.2 持久化配置
仍然使用 uni-app
的本地存储,但数据量更小:
persist: {storage: {getItem(key) { return uni.getStorageSync(key); },setItem(key, value) { uni.setStorageSync(key, value); },},
}
4. 登录逻辑优化
4.1 修改 handleLogin
方法
登录成功后,仅存储 id
和 role
:
async handleLogin() {try {const res = await post('/user/login', { ... });const memberStore = useMemberStore();memberStore.setToken(res);// 仅存储 id 和 roleconst userInfoRes = await get('/user/getUserInfo');memberStore.setUserBasicInfo({id: userInfoRes.id,role: userInfoRes.role});// 跳转逻辑...} catch (error) {// 错误处理...}
}
4.2 获取用户信息的策略
- 需要完整信息时(如个人中心页面),再通过
userId
调用接口获取。 - 权限检查 直接使用
getUserRole()
判断:if (memberStore.getUserRole() === 0) {// 管理员逻辑 }
(网页端)
本次更新
- ✅ 用户登录/退出流程完整
- ✅ 个人信息显示与编辑功能完善
- ✅ 前后端数据无缝对接
- ✅ 存储管理安全可靠
网页端调整
(1) API 请求 (user.js
)
- 标准化请求方式(与
story.js
保持一致):- 使用
instance
替代request
- 改为 函数式导出(如
export function login(data)
)
- 使用
- 接口调整:
getUserInfo()
不再需要userId
(从 Token 获取当前用户)updateUserInfo()
仅允许修改nickname
和email
(2) Token 管理 (token.js
)
- 新增
clearToken()
:- 清空 Pinia 的
token
状态 - 移除
localStorage
中的 token - 额外清除 Pinia 持久化存储(避免插件自动恢复)
- 清空 Pinia 的
(3) 登录页 (Login.vue
)
- 表单验证同步后端规则:
- 用户名/密码长度:5-16位
- 禁止空白字符(通过
validateNoWhitespace
自定义校验)
- 真实登录逻辑:
- 调用
login()
API - 成功后将 token 存入 Pinia 和
localStorage
- 调用
(4) 用户信息页 (UserInfo.vue
)
- 字段匹配后端
UserVO
:- 显示
nickname
而非realName
- 新增
avatarUrl
头像预览 - 角色显示为 “管理员” 或 “普通用户”(根据
role: 0 | 1
)
- 显示
- 编辑逻辑优化:
- 仅允许修改
nickname
和email
- 取消编辑时恢复原始数据
- 仅允许修改
(5) 退出登录
- 彻底清除所有存储:
tokenStore.clearToken(); // 清空 Pinia + localStorage userInfoStore.$reset(); // 重置用户信息 router.push('/login'); // 跳转登录页
网页端关键实现
1. API服务 (user.js)
user.js
是一个用户相关的 API 封装模块,它基于 request.js
提供的 axios 实例封装了一系列与用户相关的接口。
1. 模块结构
user.js
导出了 6 个函数,分别对应不同的用户操作:
login
- 用户登录getUserInfo
- 获取用户详细信息updateUserInfo
- 更新用户基本信息updateAvatar
- 更新用户头像updatePassword
- 更新用户密码getUserList
- 获取用户列表(分页)
2. 依赖分析
模块顶部导入了 request.js
中创建的 axios 实例:
import instance from '@/utils/request';
3. API 方法详解
3.1 用户登录 (login
)
export function login(data) {return instance({url: '/user/login',method: 'post',data});
}
- 功能:用户登录接口
- 方法:POST
- 参数:通过
data
传递登录表单数据(如用户名和密码) - 特点:不需要 token,是获取 token 的入口
3.2 获取用户信息 (getUserInfo
)
export function getUserInfo() {return instance({url: '/user/getUserInfo',method: 'get'});
}
- 功能:获取当前登录用户的详细信息
- 方法:GET
- 特点:需要 token(通过请求拦截器自动添加)
3.3 更新用户信息 (updateUserInfo
)
export function updateUserInfo(data) {return instance({url: '/user/updateUserInfo',method: 'put',data});
}
- 功能:更新用户基本信息
- 方法:PUT
- 参数:通过
data
传递更新后的用户信息 - 特点:需要 token,使用 PUT 方法表示更新操作
3.4 更新用户头像 (updateAvatar
)
export function updateAvatar(avatarUrl) {return instance({url: '/user/avatar',method: 'patch',params: { avatarUrl }});
}
- 功能:更新用户头像
- 方法:PATCH(部分更新)
- 参数:通过
params
传递头像 URL - 特点:使用 PATCH 方法表示部分更新,参数通过 URL 查询字符串传递
3.5 更新用户密码 (updatePassword
)
export function updatePassword(data) {return instance({url: '/user/password',method: 'patch',data});
}
- 功能:更新用户密码
- 方法:PATCH
- 参数:通过
data
传递新旧密码 - 特点:敏感操作,通常需要额外验证
3.6 获取用户列表 (getUserList
)
export function getUserList(params) {return instance({url: '/user/page',method: 'get',params});
}
- 功能:分页获取用户列表(管理员功能)
- 方法:GET
- 参数:通过
params
传递分页参数(如 pageNum, pageSize) - 特点:需要管理员权限,参数通过 URL 查询字符串传递
2. Token管理 (token.js)
token.js
是一个使用 Pinia 实现的状态管理模块,专门用于管理用户的认证 token。本次完善如下:
setToken
setToken(token) {this.token = token;localStorage.setItem('token', token); // 同步到 localStorage
},
- 功能:设置 token
- 同时更新内存状态和 localStorage
- 确保 token 在页面刷新后仍然可用
clearToken
clearToken() {this.token = ''; // 清空 statelocalStorage.removeItem('token'); // 清除 localStorage// 关键:清除 Pinia 持久化插件的存储if (this.$hydrate) {this.$hydrate({ reset: true }); // 重置持久化状态}// 或者直接清除特定的持久化 keyconst PERSIST_KEY = `pinia-${this.$id}`;localStorage.removeItem(PERSIST_KEY);sessionStorage.removeItem(PERSIST_KEY);
}
- 功能:清除 token
- 清空内存状态
- 从 localStorage 移除 token
- 特别处理了 Pinia 持久化插件的存储(两种方式):
- 使用插件的
$hydrate
方法(如果存在) - 直接移除插件使用的存储 key
- 使用插件的
3. 用户信息组件 (UserInfo.vue)
字段与后端 UserVO 匹配
组件中的 userForm
对象字段与后端数据结构对齐:
const userForm = reactive({id: '', // 用户IDusername: '', // 用户名nickname: '', // 昵称 (替代了原来的realName)email: '', // 电子邮箱avatarUrl: '', // 新增-头像URLrole: 0, // 用户角色 (0-管理员,1-普通用户)createTime: '', // 创建时间updateTime: '' // 更新时间
});
关键字段变更:
nickname
替代realName
:更符合常见用户系统的命名习惯- 新增
avatarUrl
:支持头像显示和预览功能 - 角色显示优化:将数字角色转换为易读文本
<el-input :value="userForm.role === 0 ? '管理员' : '普通用户'" disabled />
编辑逻辑优化
可编辑字段控制
- 允许编辑的字段:仅
nickname
和email
<el-form-item label="昵称"><el-input v-model="userForm.nickname" /> </el-form-item><el-form-item label="电子邮箱"><el-input v-model="userForm.email" /> </el-form-item>
- 禁用字段:其他所有字段都设置为
disabled
编辑状态管理
- 使用
isEditing
ref 控制编辑状态 - 编辑按钮条件渲染:
<el-button type="primary" @click="handleEdit" v-if="!isEditing">编辑</el-button> <div v-else class="action-buttons"><el-button type="success" @click="handleSave">保存</el-button><el-button @click="cancelEdit">取消</el-button> </div>
数据恢复机制
- 使用
originalUserData
保存原始数据:const originalUserData = reactive({});// 获取用户信息时保存原始数据 Object.assign(originalUserData, res.data);
- 取消编辑时恢复数据:
const cancelEdit = () => {isEditing.value = false;Object.assign(userForm, originalUserData); };
保存逻辑
- 仅提交可修改的字段:
const res = await updateUserInfo({nickname: userForm.nickname,email: userForm.email });
- 成功保存后刷新数据:
if (res.code === 200) {ElMessage.success('用户信息更新成功');isEditing.value = false;fetchUserInfo(); // 刷新数据 }
与API的交互
组件使用了 user.js
中的两个API方法:
-
获取用户信息:
import { getUserInfo } from '@/api/user';const res = await getUserInfo();
-
更新用户信息:
import { updateUserInfo } from '@/api/user';const res = await updateUserInfo({ nickname, email });
4. 登录组件 (Login.vue)
表单验证修改
验证规则配置
组件中定义了严格的表单验证规则,与后端要求保持一致:
const loginRules = {username: [{ required: true, message: "请输入用户名", trigger: "blur" },{ min: 5, max: 16, message: "用户名长度应为5-16个字符", trigger: "blur" },{ validator: validateNoWhitespace, trigger: "blur" },],password: [{ required: true, message: "请输入密码", trigger: "blur" },{ min: 5, max: 16, message: "密码长度应为5-16个字符", trigger: "blur" },{ validator: validateNoWhitespace, trigger: "blur" },],
};
自定义验证方法
实现了禁止空白字符的自定义验证:
const validateNoWhitespace = (rule, value, callback) => {if (/\s/.test(value)) {callback(new Error("不能包含空白字符"));} else {callback();}
};
登录逻辑实现
const handleLogin = async () => {try {// 1. 表单验证await loginFormRef.value.validate();loading.value = true;// 2. 调用登录APIconst response = await login({username: loginForm.username,password: loginForm.password,});if (response.code === 200) {// 3. 存储tokentokenStore.setToken(response.data);// 4. 获取用户信息const userInfoResponse = await getUserInfo();if (userInfoResponse.code === 200) {userInfoStore.setUserInfo(userInfoResponse.data);}ElMessage.success("登录成功");// 5. 跳转到首页router.push("/dashboard");} else {ElMessage.error(response.message || "登录失败");}} catch (error) {ElMessage.error(error.message || "登录失败");} finally {loading.value = false;}
};
- 表单验证:先验证表单数据是否符合规则
- API调用:使用
login()
方法发送登录请求 - Token存储:登录成功后,通过
tokenStore.setToken()
存储token- 同时保存到Pinia状态和localStorage
- 用户信息获取:获取并存储用户详细信息
- 页面跳转:登录成功后跳转到仪表盘页面
使用了两个Pinia
store:
- tokenStore:管理认证token
const tokenStore = useTokenStore(); tokenStore.setToken(response.data);
- userInfoStore:管理用户信息
const userInfoStore = useUserInfoStore(); userInfoStore.setUserInfo(userInfoResponse.data);
与API的交互
组件使用了 user.js
中的两个API方法:
- 登录接口:
import { login } from "@/api/user"; const response = await login({ username, password });
- 获取用户信息:
import { getUserInfo } from "@/api/user"; const userInfoResponse = await getUserInfo();