【慧游鲁博】【10】全端优化用户信息存储+网页端user模块与后端对接

发布于:2025-05-28 ⋅ 阅读:(27) ⋅ 点赞:(0)

(小程序端)

本次更新

在开发微信小程序等移动应用时,用户信息的存储和安全性是需要重点考虑的问题。本次优化通过最小化客户端存储的用户数据,仅保留必要的 idrole,既保证了功能完整性,又提升了安全性。

在这里插入图片描述

本文将围绕本次修改,详细介绍:

  1. 数据库与实体类调整(添加 role 字段)
  2. 后端接口修改UserVOgetUserInfo 方法)
  3. 小程序端存储优化Pinia 状态管理调整)
  4. 登录逻辑优化(仅存储 idrole

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,仅存储 userIduserRole

export const useMemberStore = defineStore('member', () => {
    const profile = ref({
        token: '',      // 登录令牌
        userId: null,   // 仅存储用户ID
        userRole: 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 方法

登录成功后,仅存储 idrole

async handleLogin() {
    try {
        const res = await post('/user/login', { ... });
        const memberStore = useMemberStore();
        memberStore.setToken(res);

        // 仅存储 id 和 role
        const 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() 仅允许修改 nicknameemail

(2) Token 管理 (token.js)

  • 新增 clearToken()
    • 清空 Pinia 的 token 状态
    • 移除 localStorage 中的 token
    • 额外清除 Pinia 持久化存储(避免插件自动恢复)

(3) 登录页 (Login.vue)

  • 表单验证同步后端规则
    • 用户名/密码长度:5-16位
    • 禁止空白字符(通过 validateNoWhitespace 自定义校验)
  • 真实登录逻辑
    • 调用 login() API
    • 成功后将 token 存入 Pinia 和 localStorage

(4) 用户信息页 (UserInfo.vue)

  • 字段匹配后端 UserVO
    • 显示 nickname 而非 realName
    • 新增 avatarUrl 头像预览
    • 角色显示为 “管理员”“普通用户”(根据 role: 0 | 1
  • 编辑逻辑优化
    • 仅允许修改 nicknameemail
    • 取消编辑时恢复原始数据

(5) 退出登录

  • 彻底清除所有存储
    tokenStore.clearToken();  // 清空 Pinia + localStorage
    userInfoStore.$reset();  // 重置用户信息
    router.push('/login');   // 跳转登录页
    

网页端关键实现

1. API服务 (user.js)

user.js 是一个用户相关的 API 封装模块,它基于 request.js 提供的 axios 实例封装了一系列与用户相关的接口。

1. 模块结构

user.js 导出了 6 个函数,分别对应不同的用户操作:

  1. login - 用户登录
  2. getUserInfo - 获取用户详细信息
  3. updateUserInfo - 更新用户基本信息
  4. updateAvatar - 更新用户头像
  5. updatePassword - 更新用户密码
  6. 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 = ''; // 清空 state
  localStorage.removeItem('token'); // 清除 localStorage
  
  // 关键:清除 Pinia 持久化插件的存储
  if (this.$hydrate) {
    this.$hydrate({ reset: true }); // 重置持久化状态
  }
  
  // 或者直接清除特定的持久化 key
  const PERSIST_KEY = `pinia-${this.$id}`;
  localStorage.removeItem(PERSIST_KEY);
  sessionStorage.removeItem(PERSIST_KEY);
}
  • 功能:清除 token
  • 清空内存状态
  • 从 localStorage 移除 token
  • 特别处理了 Pinia 持久化插件的存储(两种方式):
    1. 使用插件的 $hydrate 方法(如果存在)
    2. 直接移除插件使用的存储 key

3. 用户信息组件 (UserInfo.vue)

字段与后端 UserVO 匹配

组件中的 userForm 对象字段与后端数据结构对齐:

const userForm = reactive({
  id: '',           // 用户ID
  username: '',     // 用户名
  nickname: '',     // 昵称 (替代了原来的realName)
  email: '',        // 电子邮箱
  avatarUrl: '',    // 新增-头像URL
  role: 0,          // 用户角色 (0-管理员,1-普通用户)
  createTime: '',   // 创建时间
  updateTime: ''    // 更新时间
});

关键字段变更

  • nickname 替代 realName:更符合常见用户系统的命名习惯
  • 新增 avatarUrl:支持头像显示和预览功能
  • 角色显示优化:将数字角色转换为易读文本
    <el-input :value="userForm.role === 0 ? '管理员' : '普通用户'" disabled />
    
编辑逻辑优化
可编辑字段控制
  • 允许编辑的字段:仅 nicknameemail
    <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方法:

  1. 获取用户信息

    import { getUserInfo } from '@/api/user';
    
    const res = await getUserInfo();
    
  2. 更新用户信息

    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. 调用登录API
    const response = await login({
      username: loginForm.username,
      password: loginForm.password,
    });

    if (response.code === 200) {
      // 3. 存储token
      tokenStore.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;
  }
};
  1. 表单验证:先验证表单数据是否符合规则
  2. API调用:使用 login() 方法发送登录请求
  3. Token存储:登录成功后,通过 tokenStore.setToken() 存储token
    • 同时保存到Pinia状态和localStorage
  4. 用户信息获取:获取并存储用户详细信息
  5. 页面跳转:登录成功后跳转到仪表盘页面

使用了两个Pinia store:

  1. tokenStore:管理认证token
    const tokenStore = useTokenStore();
    tokenStore.setToken(response.data);
    
  2. userInfoStore:管理用户信息
    const userInfoStore = useUserInfoStore();
    userInfoStore.setUserInfo(userInfoResponse.data);
    
与API的交互

组件使用了 user.js 中的两个API方法:

  1. 登录接口
    import { login } from "@/api/user";
    const response = await login({ username, password });
    
  2. 获取用户信息
    import { getUserInfo } from "@/api/user";
    const userInfoResponse = await getUserInfo();
    

网站公告

今日签到

点亮在社区的每一天
去签到