用户模型设计用户模型我设计得比较简单就是id、用户名、邮箱、昵称这些基本信息再加上一个JWT token。但后端返回的数据结构是嵌套的前端需要自己拆开重组一下把token塞进user对象里这样存到本地的时候就是一个完整的用户对象。模型里我还加了几个辅助方法比如判断用户是否已认证、获取显示名称优先用昵称、没有就用用户名。这些小细节虽然不复杂但后面用起来很方便。认证状态管理认证状态的核心是一个AuthNotifier继承自Riverpod的StateNotifier。它管理三个核心状态用户信息、加载状态、错误信息。登录流程是这样的用户提交用户名密码 - Dio发起请求 - 收到响应后解析data里的accessToken和user - 把token合并到user里 - 保存用户信息到SharedPreferences - 在Dio实例里设置全局token - 更新AuthState。注册流程跟登录差不多但多了前端验证这一步。用户名不能空、至少3个字符邮箱不能空、必须包含和点密码不能空、至少6个字符两次输入要一致。这些验证在发给后端之前就先拦住了用户体验好很多也减轻了后端的压力。自动登录是我比较满意的一个功能。应用启动的时候AuthNotifier的构造函数会调用_loadUserFromStorage从SharedPreferences里读用户信息。如果读到了就自动完成登录状态初始化不用用户重新输账号密码。如果没读到就停留在未登录状态等用户去登录页操作。登出逻辑就简单了清除本地存储的user、清除Dio里的token、把AuthState重置为空。注册页面的表单验证注册页面我用了flutter_hooks来管理表单状态用TextEditingController配合useState。验证逻辑全部写在提交按钮的onPressed里用户名为空或太短 - 显示错误提示邮箱格式不对 - 显示错误提示密码太短 - 显示错误提示两次密码不一致 - 显示错误提示。只有所有验证都通过才会调用注册API。之前有个问题什么都不填直接点注册会报Dio错误就是因为前端没有做验证直接把空字符串发给后端了。加上验证之后这个问题就解决了。用户也能立刻知道自己哪里填错了不用等后端返回错误。路由守卫的实现为了保护需要认证的页面我实现了一个路由守卫。核心逻辑是在go_router的redirect回调里检查用户认证状态。思路是这样的如果用户未认证并且要去的页面是/chat或者/tasks这种需要登录的就重定向到/login。如果用户已认证并且要去的是/login或者/register就重定向到/chat。其他情况就正常放行。实现的时候用了一个白名单数组把所有不需要认证的路由都列在里面。判断当前路由是否在白名单里在不在就决定了要不要拦截。这样以后加新页面的时候也很方便只需要在白名单里添加就行了。不过这里踩了个坑useEffect的依赖项写错了导致页面无限刷新。排查了好久才发现是空依赖数组的问题。Riverpod的状态变化触发了useEffect重新执行又去刷新页面就这样死循环了。后来把依赖项改成了空数组问题解决了。Token管理策略所有需要认证的API请求都要在Header里带accessToken。我在登录成功后会调用一个_setAuthToken方法把token设置到Dio实例的全局配置里。这样后续所有请求都会自动带上token不用在每个请求里单独处理。登出的时候再调用_clearAuthToken把它清掉就行了。这种单例模式的好处是登录后一次设置全局生效token刷新后统一更新避免重复创建Dio实例代码也更简洁。踩坑记录Riverpod 3.x版本对StateNotifier的支持有变化我一开始用了hooks_riverpod: ^3.3.1结果extends StateNotifier报错说什么类只能继承类。查了半天发现3.x推荐用Notifier替代StateNotifier但改起来太麻烦最后回退到2.5.1稳定版一切正常了。后端返回的archived字段类型不固定有时候是bool有时候是int需要做类型转换。我在fromJson里加了一个辅助方法判断类型再转。路由守卫的无限刷新问题前面说过了还有就是GoRouterState.of(context).uri.toString()获取的是当前页面路径不是目标路径调试的时候要注意。小结用户系统虽然简单但涉及的东西不少状态管理、网络请求、本地存储、路由守卫、表单验证。把这一套搭好之后后面加新功能就顺畅多了。
RNA Insight 用户功能开发记录
发布时间:2026/6/15 11:45:14
用户模型设计用户模型我设计得比较简单就是id、用户名、邮箱、昵称这些基本信息再加上一个JWT token。但后端返回的数据结构是嵌套的前端需要自己拆开重组一下把token塞进user对象里这样存到本地的时候就是一个完整的用户对象。模型里我还加了几个辅助方法比如判断用户是否已认证、获取显示名称优先用昵称、没有就用用户名。这些小细节虽然不复杂但后面用起来很方便。认证状态管理认证状态的核心是一个AuthNotifier继承自Riverpod的StateNotifier。它管理三个核心状态用户信息、加载状态、错误信息。登录流程是这样的用户提交用户名密码 - Dio发起请求 - 收到响应后解析data里的accessToken和user - 把token合并到user里 - 保存用户信息到SharedPreferences - 在Dio实例里设置全局token - 更新AuthState。注册流程跟登录差不多但多了前端验证这一步。用户名不能空、至少3个字符邮箱不能空、必须包含和点密码不能空、至少6个字符两次输入要一致。这些验证在发给后端之前就先拦住了用户体验好很多也减轻了后端的压力。自动登录是我比较满意的一个功能。应用启动的时候AuthNotifier的构造函数会调用_loadUserFromStorage从SharedPreferences里读用户信息。如果读到了就自动完成登录状态初始化不用用户重新输账号密码。如果没读到就停留在未登录状态等用户去登录页操作。登出逻辑就简单了清除本地存储的user、清除Dio里的token、把AuthState重置为空。注册页面的表单验证注册页面我用了flutter_hooks来管理表单状态用TextEditingController配合useState。验证逻辑全部写在提交按钮的onPressed里用户名为空或太短 - 显示错误提示邮箱格式不对 - 显示错误提示密码太短 - 显示错误提示两次密码不一致 - 显示错误提示。只有所有验证都通过才会调用注册API。之前有个问题什么都不填直接点注册会报Dio错误就是因为前端没有做验证直接把空字符串发给后端了。加上验证之后这个问题就解决了。用户也能立刻知道自己哪里填错了不用等后端返回错误。路由守卫的实现为了保护需要认证的页面我实现了一个路由守卫。核心逻辑是在go_router的redirect回调里检查用户认证状态。思路是这样的如果用户未认证并且要去的页面是/chat或者/tasks这种需要登录的就重定向到/login。如果用户已认证并且要去的是/login或者/register就重定向到/chat。其他情况就正常放行。实现的时候用了一个白名单数组把所有不需要认证的路由都列在里面。判断当前路由是否在白名单里在不在就决定了要不要拦截。这样以后加新页面的时候也很方便只需要在白名单里添加就行了。不过这里踩了个坑useEffect的依赖项写错了导致页面无限刷新。排查了好久才发现是空依赖数组的问题。Riverpod的状态变化触发了useEffect重新执行又去刷新页面就这样死循环了。后来把依赖项改成了空数组问题解决了。Token管理策略所有需要认证的API请求都要在Header里带accessToken。我在登录成功后会调用一个_setAuthToken方法把token设置到Dio实例的全局配置里。这样后续所有请求都会自动带上token不用在每个请求里单独处理。登出的时候再调用_clearAuthToken把它清掉就行了。这种单例模式的好处是登录后一次设置全局生效token刷新后统一更新避免重复创建Dio实例代码也更简洁。踩坑记录Riverpod 3.x版本对StateNotifier的支持有变化我一开始用了hooks_riverpod: ^3.3.1结果extends StateNotifier报错说什么类只能继承类。查了半天发现3.x推荐用Notifier替代StateNotifier但改起来太麻烦最后回退到2.5.1稳定版一切正常了。后端返回的archived字段类型不固定有时候是bool有时候是int需要做类型转换。我在fromJson里加了一个辅助方法判断类型再转。路由守卫的无限刷新问题前面说过了还有就是GoRouterState.of(context).uri.toString()获取的是当前页面路径不是目标路径调试的时候要注意。小结用户系统虽然简单但涉及的东西不少状态管理、网络请求、本地存储、路由守卫、表单验证。把这一套搭好之后后面加新功能就顺畅多了。