连云港网站建设服务一些可以做翻译的网站
连云港网站建设服务,一些可以做翻译的网站,网站备案的意义,高端瓶装水品牌1. 为什么获取登录用户信息是个技术活#xff1f;
刚开始用JeecgBoot做项目那会儿#xff0c;我总觉得获取当前登录用户信息不就是一行代码的事儿嘛#xff0c;能有多复杂#xff1f;结果真到用的时候#xff0c;发现坑还真不少。比如#xff0c;我在一个审批流程的节点监…1. 为什么获取登录用户信息是个技术活刚开始用JeecgBoot做项目那会儿我总觉得获取当前登录用户信息不就是一行代码的事儿嘛能有多复杂结果真到用的时候发现坑还真不少。比如我在一个审批流程的节点监听器里想记录一下操作人是谁结果发现获取到的用户对象是空的直接给我抛了个空指针异常。又或者前端明明已经登录了但在某个异步调用的函数里死活拿不到用户信息页面显示异常。这些问题相信不少刚开始接触JeecgBoot的朋友都遇到过。其实这背后涉及到几个关键点。首先JeecgBoot是一个前后端分离的快速开发平台它的用户认证和授权体系是构建在Spring Boot Apache Shiro或Spring Security取决于版本之上的。这意味着用户信息的管理和获取在前端和后端是两套不同的逻辑。前端主要依赖Vuex状态管理而后端则依赖于安全框架的上下文。其次JeecgBoot本身也在快速迭代不同版本比如经典的1.x和主流的2.x/3.x在用户信息封装对象上做了调整如果你不看版本直接照搬代码大概率会出错。所以高效获取用户信息核心在于“对症下药”。你得清楚你现在在哪个环境前端Vue页面还是后端Java代码用的是哪个版本的JeecgBoot然后选择最直接、最不容易出错的方法。这篇文章我就结合自己这几年在多个JeecgBoot项目里摸爬滚打的经验把前后端各种场景下的获取技巧以及那些容易踩的坑给你掰开揉碎了讲清楚。目标就一个让你以后用到这块时能信手拈来不再掉坑里。2. 前端获取用户信息不止于Vuex很多教程一提到JeecgBoot前端获取用户就说用Vuex这没错但只说对了一半。在实际项目中我们可能会在多种场景下需要用户信息而不同的场景最优解可能略有不同。2.1 基础操作在Vue组件中从Store获取这是最标准、最常用的方式。JeecgBoot前端基于Vue Vuex登录成功后用户信息会被存储到Vuex的state中。你可以在任何Vue组件.vue文件里轻松获取。// 1. 首先在组件中引入store import store from /store // 2. 在计算属性(computed)中映射用户信息这是响应式的最佳实践 export default { computed: { currentUser() { // 直接从store的getters里获取userInfo return store.getters.userInfo }, username() { // 也可以直接获取用户名等具体字段 return this.currentUser ? this.currentUser.username : 未登录 }, userId() { return this.currentUser ? this.currentUser.id : null } }, mounted() { // 在组件挂载后可以直接使用 console.log(当前用户, this.currentUser) console.log(用户名, this.username) if (this.userId) { // 根据用户ID做一些操作比如查询个人数据 this.loadUserSpecificData(this.userId) } }, methods: { loadUserSpecificData(userId) { // 你的业务逻辑 } } }为什么推荐用计算属性因为Vuex中的state是响应式的通过计算属性获取能确保当store中的用户信息发生变化时虽然登录后很少变但理论上存在强制更新或重新登录的情况你的视图也能自动更新。直接在mounted里用变量接住就失去了响应性。2.2 进阶场景在非Vue上下文中获取这里有个容易踩坑的地方。如果你的代码不在Vue组件的上下文中比如在一个普通的工具函数utils里、一个独立的JS模块里或者在某个第三方库的回调函数中你无法直接使用this.$store或导入的store因为可能未初始化或上下文不对。我遇到过的一个典型场景是在封装一个通用的API请求拦截器时需要在每个请求的header里自动加上当前用户的token。这个拦截器通常在src/utils/request.js这样的独立文件中。// src/utils/request.js import axios from axios import { Modal } from ant-design-vue import store from /store // 关键必须导入store // 创建axios实例 const service axios.create({ baseURL: process.env.VUE_APP_API_BASE_URL, timeout: 15000 }) // 请求拦截器 service.interceptors.request.use( config { // 在发送请求之前做些什么 // 判断store里是否有用户token if (store.getters.token) { // 让每个请求携带token config.headers[X-Access-Token] store.getters.token } // 也可以附加用户ID等信息根据后端接口需要 const userInfo store.getters.userInfo if (userInfo userInfo.id) { config.headers[X-User-Id] userInfo.id } return config }, error { // 对请求错误做些什么 console.error(请求拦截器错误:, error) return Promise.reject(error) } )关键点只要这个JS模块能被正确打包到你的应用生命周期中并且导入store的时机是在Vuex store创建之后这种方式就是可行的。JeecgBoot的项目结构已经确保了这一点。2.3 使用Hooks组合式API获取如果你的项目使用的是Vue 3或者JeecgBoot的新版本开始支持组合式API那么使用useStore会是一种更优雅的方式。// 在Vue 3的setup语法糖中 import { computed } from vue import { useStore } from vuex export default { setup() { const store useStore() // 使用计算属性 const currentUser computed(() store.getters.userInfo) const username computed(() currentUser.value?.username || ) // 在逻辑中使用 const doSomething () { if (!username.value) { console.warn(用户未登录) return } // 执行需要用户信息的操作 } return { username, doSomething } } }这种方式逻辑更清晰尤其适合复杂的组件逻辑。它把状态获取和组件逻辑更好地分离开。3. 后端获取用户信息版本差异是关键转到后端Java代码这里是问题的高发区。我见过最多的错误就是版本搞混把V1.0的代码抄到V2.0的项目里然后debug半天找不到原因。3.1 核心原理SecurityUtils与SubjectJeecgBoot后端默认使用Apache Shiro做安全框架部分新版本可能集成Spring Security但Shiro仍是主流。Shiro的核心概念是Subject它代表当前执行操作的用户。SecurityUtils.getSubject()就是获取当前线程绑定的这个用户Subject的入口。无论版本如何变化下面这行代码的骨架是不变的// 这是基石任何版本都这么开始 Object principal SecurityUtils.getSubject().getPrincipal();getPrincipal()方法返回的是一个Object这才是“魔鬼”所在的地方。你需要把它强制转换成正确的类型而这个类型随着JeecgBoot版本升级发生了变化。3.2 V1.x版本SysUser时代在JeecgBoot早期的1.x版本比如1.0, 1.1框架直接将数据库中的用户实体SysUser对象放到了Shiro的Principal里。SysUser是一个实体类包含了用户的所有字段如id、username、realname、password加密后的、手机号、邮箱等等。import org.apache.shiro.SecurityUtils; import org.jeecg.modules.system.entity.SysUser; // 注意导入路径 // 在Controller、Service的任何方法中 public void someBusinessMethod() { // V1.0 方式强制转换为SysUser SysUser sysUser (SysUser) SecurityUtils.getSubject().getPrincipal(); if (sysUser ! null) { String username sysUser.getUsername(); // 登录账号 String realName sysUser.getRealname(); // 真实姓名 String userId sysUser.getId(); // 用户ID // ... 其他业务逻辑 System.out.println(操作人 realName ( username )); } else { // 处理未登录情况可能是接口允许匿名访问 throw new RuntimeException(用户未登录或登录已过期); } }注意事项空指针判断一定要先判断sysUser是否为null。因为如果当前请求是一个匿名请求比如登录接口本身getPrincipal()会返回null。字段熟悉你需要熟悉SysUser实体类有哪些字段像getAvatar()获取头像getPhone()获取手机号等。性能考虑SysUser对象较大包含了相对完整的用户信息。在只需要用户ID或姓名的简单场景虽然可以直接用但要知道它承载的数据量。3.3 V2.x/3.x版本LoginUser的登场随着架构演进JeecgBoot团队意识到直接将数据库实体作为Principal存在一些问题比如实体字段变更会影响安全上下文、暴露不必要的信息等。于是在2.x及以后的版本包括现在主流的3.x引入了一个专门的用户上下文对象LoginUser。LoginUser可以看作是一个“安全视图”对象它通常只包含安全验证和基础上下文所必需的信息比如用户ID、用户名、角色列表、权限列表等而不是用户的所有个人资料。import org.apache.shiro.SecurityUtils; import org.jeecg.common.system.vo.LoginUser; // 注意导入路径变了 // 在Controller、Service的任何方法中 public void someBusinessMethod() { // V2.0 方式强制转换为LoginUser LoginUser loginUser (LoginUser) SecurityUtils.getSubject().getPrincipal(); if (loginUser ! null) { String username loginUser.getUsername(); // 登录账号 String realName loginUser.getRealname(); // 真实姓名 String userId loginUser.getId(); // 用户ID // LoginUser 通常还包含权限信息 ListString roles loginUser.getRoles(); // 角色编码列表 SetString permissions loginUser.getPermissions(); // 权限字符串集合 // 示例检查是否有某个权限 if (permissions.contains(user:add)) { // 执行添加用户的操作 } System.out.println(操作人ID userId); } }划重点导入类路径不同LoginUser通常在org.jeecg.common.system.vo包下而SysUser在org.jeecg.modules.system.entity下。导错包是编译错误很容易发现。获取用户ID的方式这是最容易出错的地方在SysUser里是sysUser.getId()。在LoginUser里也是loginUser.getId()。网上有些老教程或代码片段写的是loginUser.getUser().getId()这在某些中间版本或自定义版本里可能存在但在我接触的官方标准V2.0版本中LoginUser本身就有id字段。最稳妥的方式是拿到LoginUser对象后点开看看它的结构。如何判断版本最直接的方法是看你的项目里有没有LoginUser这个类。在IDE里全局搜索一下class LoginUser如果存在并且被广泛使用那大概率就是2.x版本。另一个方法是看pom.xml里jeecg-boot-parent的版本号。3.4 通用工具类封装一劳永逸在每个需要用户信息的地方都写一遍判空和转换代码显得冗余。我习惯在项目中封装一个工具类统一处理这个逻辑还能加上缓存和错误处理。// 文件名UserContextUtils.java package com.yourcompany.project.common.util; import org.apache.shiro.SecurityUtils; import org.apache.shiro.subject.Subject; import org.jeecg.common.system.vo.LoginUser; import org.jeecg.modules.system.entity.SysUser; import org.springframework.stereotype.Component; /** * 用户上下文工具类 */ Component public class UserContextUtils { /** * 获取当前登录用户兼容V1和V2版本 * return LoginUser 或 SysUser取决于版本 */ public static Object getCurrentUser() { Subject subject SecurityUtils.getSubject(); if (subject null) { return null; } Object principal subject.getPrincipal(); return principal; } /** * 获取当前登录用户的ID推荐使用 * return 用户ID未登录返回null */ public static String getCurrentUserId() { Object user getCurrentUser(); if (user instanceof LoginUser) { return ((LoginUser) user).getId(); } else if (user instanceof SysUser) { return ((SysUser) user).getId(); } return null; } /** * 获取当前登录用户名 * return 用户名未登录返回null */ public static String getCurrentUsername() { Object user getCurrentUser(); if (user instanceof LoginUser) { return ((LoginUser) user).getUsername(); } else if (user instanceof SysUser) { return ((SysUser) user).getUsername(); } return null; } /** * 获取当前登录用户真实姓名 * return 真实姓名未登录返回null */ public static String getCurrentRealName() { Object user getCurrentUser(); if (user instanceof LoginUser) { return ((LoginUser) user).getRealname(); } else if (user instanceof SysUser) { return ((SysUser) user).getRealname(); } return null; } /** * 判断当前用户是否拥有某个权限 * param permission 权限字符串如 sys:user:add * return 是否拥有 */ public static boolean hasPermission(String permission) { Subject subject SecurityUtils.getSubject(); return subject ! null subject.isPermitted(permission); } /** * 安全地获取用户如果未登录则抛出业务异常 */ public static LoginUser getLoginUserRequired() { Object user getCurrentUser(); if (user instanceof LoginUser) { return (LoginUser) user; } // 这里抛出一个自定义的业务异常比如“用户未登录” throw new BusinessException(500, 用户未登录或登录已过期请重新登录); } }这样封装之后在业务代码里调用就非常清爽和安全了// 在Service中 public void updateOrder(Order order) { String currentUserId UserContextUtils.getCurrentUserId(); if (currentUserId null) { throw new RuntimeException(无法获取操作人信息); } order.setUpdateBy(currentUserId); order.setUpdateTime(new Date()); // ... 其他更新逻辑 } // 或者直接获取完整对象 LoginUser loginUser UserContextUtils.getLoginUserRequired(); ListString myRoles loginUser.getRoles();4. 实战避坑指南与高级技巧知道了基本方法我们来看看那些容易让人栽跟头的场景以及一些能提升效率的高级用法。4.1 坑点一异步线程中用户信息丢失这是我踩过的一个大坑。在一个定时任务里或者当你使用Async开启异步执行时新线程是无法直接通过SecurityUtils.getSubject()获取到用户信息的。因为Shiro的Subject是和线程绑定的ThreadLocal新线程没有这个上下文。解决方案在开启异步任务前将当前用户信息显式地传递过去。Service public class OrderService { Autowired private AsyncService asyncService; public void placeOrder(OrderDTO orderDTO) { // 在主线程请求线程中获取用户 LoginUser currentUser (LoginUser) SecurityUtils.getSubject().getPrincipal(); String userId currentUser.getId(); // 1. 保存订单同步 Order order convertToOrder(orderDTO); order.setCreateBy(userId); orderRepository.save(order); // 2. 异步发送通知将用户ID作为参数传入 asyncService.sendOrderNotification(order.getId(), userId); } } Service public class AsyncService { Async // 启用异步执行 public void sendOrderNotification(String orderId, String userId) { // 在这个异步方法里无法使用SecurityUtils获取用户 // 但我们已经通过参数传入了userId // 可以根据userId查询用户详情如果需要的话 // UserDetail user userService.getById(userId); // ... 发送通知的逻辑 System.out.println(异步通知订单 orderId 创建者ID userId); } }更优雅的方案如果异步任务很多每次都传参数麻烦可以考虑使用自定义的TaskDecorator在Spring异步任务执行前将父线程的上下文包括Shiro的Subject复制到子线程。但这涉及较深的定制需要谨慎处理。4.2 坑点二单元测试中的Mock在编写Service层的单元测试时你的测试方法跑在测试线程中没有Shiro的登录环境直接调用SecurityUtils.getSubject().getPrincipal()会返回null导致测试失败。解决方案在测试类中Mock模拟Shiro的Subject。SpringBootTest RunWith(SpringRunner.class) // JUnit 4 // 或者 ExtendWith(SpringExtension.class) // JUnit 5 public class UserServiceTest { Autowired private UserService userService; Test public void testGetCurrentUserInfo() { // 1. 创建一个Mock的Subject Subject mockSubject mock(Subject.class); // 2. 创建一个模拟的LoginUser LoginUser mockUser new LoginUser(); mockUser.setId(test-user-123); mockUser.setUsername(tester); mockUser.setRealname(测试员); // 3. 设置Mock行为当调用getPrincipal时返回模拟用户 when(mockSubject.getPrincipal()).thenReturn(mockUser); // 4. 关键将Mock的Subject设置到SecurityUtils的静态上下文中 // 注意这需要Shiro允许设置SecurityManager或者使用反射工具 // 这里演示一种思路实际可能需要借助Before方法设置测试环境 ThreadContext.bind(mockSubject); // Apache Shiro的ThreadContext // 5. 现在执行测试 // 你的service方法内部调用SecurityUtils.getSubject()就会得到mockSubject UserInfoVO info userService.getCurrentUserInfo(); // 6. 断言验证 assertNotNull(info); assertEquals(test-user-123, info.getUserId()); // 7. 测试完成后清理线程上下文避免影响其他测试 ThreadContext.unbindSubject(); } }设置Shiro的测试环境稍微有点繁琐但这是保证单元测试覆盖率的必要步骤。一些项目会抽象出一个TestBase类在Before方法里统一设置好模拟登录状态。4.3 高级技巧自定义注解快速获取如果你觉得在每个Controller方法参数里都写一遍获取用户信息的代码很烦可以借鉴Spring MVC的思路自定义一个注解配合HandlerMethodArgumentResolver接口实现自动注入。// 1. 定义注解 Target(ElementType.PARAMETER) Retention(RetentionPolicy.RUNTIME) public interface CurrentUser { } // 2. 实现参数解析器 Component public class CurrentUserMethodArgumentResolver implements HandlerMethodArgumentResolver { Override public boolean supportsParameter(MethodParameter parameter) { // 支持带有CurrentUser注解的参数 return parameter.hasParameterAnnotation(CurrentUser.class); } Override public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { // 在这里实现获取当前用户的逻辑 Object principal SecurityUtils.getSubject().getPrincipal(); if (principal null) { // 可以根据需要返回null或抛出未登录异常 throw new UnauthorizedException(用户未登录); } // 检查参数类型是否匹配 Class? parameterType parameter.getParameterType(); if (parameterType.isAssignableFrom(principal.getClass())) { return principal; } else { // 类型不匹配可以尝试转换或抛出异常 throw new IllegalArgumentException(参数类型不匹配期望 parameterType 实际是 principal.getClass()); } } } // 3. 将解析器注册到Spring MVCWebConfig中 Configuration public class WebMvcConfig implements WebMvcConfigurer { Autowired private CurrentUserMethodArgumentResolver currentUserResolver; Override public void addArgumentResolvers(ListHandlerMethodArgumentResolver resolvers) { resolvers.add(currentUserResolver); } } // 4. 在Controller中使用极其简洁 RestController RequestMapping(/api/user) public class UserController { GetMapping(/profile) public Result? getProfile(CurrentUser LoginUser loginUser) { // loginUser 已经被自动注入为当前登录用户 // 直接使用即可 UserProfileVO profile userService.buildProfile(loginUser); return Result.OK(profile); } }用了这个技巧后Controller层的代码会干净很多业务逻辑更聚焦。这算是一个“高级玩家”的优化在团队协作的项目中能统一规范提升代码可读性。4.4 微服务场景下的考虑如果你的JeecgBoot项目被拆成了微服务用户登录认证可能在网关Gateway统一处理然后将用户信息如用户ID、用户名通过请求头如X-User-Id传递给下游业务服务。这时业务服务里就没有Shiro的Subject了。解决方案在每个微服务中通过过滤器Filter或拦截器Interceptor解析请求头中的用户信息并将其放入当前线程的上下文中比如一个ThreadLocal变量然后提供一个类似的工具类来获取。// 在业务微服务中定义一个过滤器 Component public class UserInfoFilter implements Filter { Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest httpRequest (HttpServletRequest) request; // 从请求头中获取网关传递的用户信息 String userId httpRequest.getHeader(X-User-Id); String username httpRequest.getHeader(X-Username); if (StringUtils.isNotBlank(userId)) { // 构造一个简单的用户上下文对象可以不是LoginUser SimpleUserContext userContext new SimpleUserContext(userId, username); // 存入ThreadLocal UserContextHolder.set(userContext); } try { chain.doFilter(request, response); } finally { // 请求结束后务必清理ThreadLocal防止内存泄漏 UserContextHolder.clear(); } } } // ThreadLocal持有器 public class UserContextHolder { private static final ThreadLocalSimpleUserContext CONTEXT_HOLDER new ThreadLocal(); public static void set(SimpleUserContext context) { CONTEXT_HOLDER.set(context); } public static SimpleUserContext get() { return CONTEXT_HOLDER.get(); } public static void clear() { CONTEXT_HOLDER.remove(); } } // 业务代码中通过UserContextHolder获取 public void someServiceMethod() { SimpleUserContext user UserContextHolder.get(); if (user ! null) { String currentUserId user.getUserId(); // ... 使用userId } }这种方式下业务服务不再依赖Shiro而是依赖统一的请求头协议。这要求网关和业务服务之间要有良好的约定。