【技能派面试篇】技能派微信公众号自动登录技能面试要点全解析 ...

打印 上一主题 下一主题

主题 2085|帖子 2085|积分 6255

你好,欢迎来到本次关于技能派微信公众号自动登录技能的面试系列分享。在这篇文章中,我们将深入探讨这一技能范畴的相关面试题推测。若想对相关内容有更透彻的理解,强烈推荐参考之前发布的博文:【技能派后端篇】技能派微信公众号自动登录技能 、【技能派后端篇】技能派中 Session/Cookie 与 JWT 身份验证技能的应用及实现解析。
焦点技能原理概述:系统通过验证码与前端建立并维持半长链接映射关系。当用户扫描二维码关注公众号并输入接收到的验证码后,系统会接收到微信的回调信息,进而辨认用户信息,并精准找到对应的半长链接,最终实现系统的自动登录功能。
1. 请描述验证码登录的完整流程设计?


以下是对微信公众号自动登录流程的具体梳理:

  • 用户发起登录请求:用户自动向“技能派”系统发起登录操作,这是整个流程的起始点。
  • 获取关键信息:“技能派”系统相应请求,返回用于关注的公众号二维码以及对应的验证码给用户,为后续的操作提供必要信息。
  • 关注公众号:用户通过微信应用扫描获取到的二维码,乐成关注对应的公众号,建立起与公众号的联系。
  • 输入验证码:在微信公众号内,用户按照提示输入之前接收到的验证码,完成信息的提交。
  • 验证码处理与严格校验:微信将用户输入的验证码回调转发给“技能派”系统。系统会对验证码举行严格的校验,同时处理与用户注册或登录相关的一系列操作,确保登录的合法性和安全性。
  • 登录乐成反馈:若验证码校验通过,“技能派”系统会实时告知用户登录乐成的信息,让用户知晓操作结果。
  • 重定向到首页:末了,“技能派”系统将用户重定向到首页,用户顺利进入首页,至此完成整个登录流程。
2. 怎样实现验证码与前端的长连接映射?



  • 双缓存设计策略:接纳Guava Cache构建双缓存机制,分别维护设备ID与验证码之间的映射关系,以及验证码与SSE(Server-Sent Events)连接之间的映射关系。具体代码定义如下:
  1. LoadingCache<String, String> deviceCodeCache; // 实现设备ID到验证码的映射
  2. LoadingCache<String, SseEmitter> verifyCodeCache; // 实现验证码到SSE连接的映射
复制代码


  • 验证码生成策略:通过特定的生成工具类CodeGenerateUtil来生成验证码,具体代码如下:
  1. String code = CodeGenerateUtil.genCode(cnt++);
复制代码


  • SSE长连接生命周期管理:在建立SSE连接时,设定合理的超时时间(如15分钟),并在超时发生时举行相应的处理,包括清理缓存和完成连接。相关代码示例如下:
  1. // 建立SSE连接,设置超时时间为15分钟(900_000毫秒)
  2. SseEmitter sseEmitter = new SseEmitter(900_000L);
  3. // 定义超时回调函数,当连接超时时清理缓存并完成连接
  4. sseEmitter.onTimeout(() -> {
  5.     verifyCodeCache.invalidate(realCode);
  6.     sseEmitter.complete();
  7. });
  8. // 将验证码与对应的SSE连接存入缓存
  9. verifyCodeCache.put(realCode, sseEmitter);
复制代码


  • 回调处理流程:当微信回调时,系统会举行用户自动注册和登录验证等操作,具体代码如下:
  1. // 微信回调时,自动注册用户信息
  2. sessionService.autoRegisterWxUserInfo(fromUser);
  3. // 对输入的验证码进行校验,校验通过后触发登录操作
  4. qrLoginHelper.login(code);
复制代码
3. 怎样包管验证码系统的安全性?

为了确保验证码系统的安全性,接纳了以下多方面的措施,具体实现代码如下:
  1. // 定义验证码与SSE连接的缓存,设置最大容量为300,写入后5分钟过期
  2. private LoadingCache<String, SseEmitter> verifyCodeCache = CacheBuilder.newBuilder().maximumSize(300).expireAfterWrite(5, TimeUnit.MINUTES).build(new CacheLoader<String, SseEmitter>() {
  3. });
  4. // 定义设备ID与验证码的缓存,设置最大容量为300,写入后5分钟过期
  5. private LoadingCache<String, String> deviceCodeCache = CacheBuilder.newBuilder().maximumSize(300).expireAfterWrite(5, TimeUnit.MINUTES).build(new CacheLoader<String, String>() {
  6. });
复制代码


  • 限流机制:在WxLoginHelper类中,通过Guava Cache设置缓存的最大容量为300,有效限制了同时存在的验证码数目,防止恶意请求导致系统资源耗尽。
  • 时效控制:同样在WxLoginHelper类中,设置缓存的过期时间为5分钟,确保验证码在一定时间后自动失效,降低被破解或滥用的风险。
  • 绑定校验:接纳deviceId与验证码的双重校验方式,有效防止中间人攻击,确保只有合法的设备和用户才能完成登录操作。
  • 登录限制:在登录校验方法中(WxLoginHelper.java),确保单设备单验证码只能利用一次。当验证码被利用后,通过verifyCodeCache.invalidate方法将其从缓存中移除,避免重复利用带来的安全隐患。具体代码如下:
  1. // 登录校验方法,验证验证码的有效性并确保单次使用
  2. public boolean login(String verifyCode) {
  3.     SseEmitter sseEmitter = verifyCodeCache.getIfPresent(verifyCode);
  4.     if (sseEmitter == null) return false; // 若验证码不存在或已失效,返回false
  5.     verifyCodeCache.invalidate(verifyCode); // 将已使用的验证码从缓存中移除,确保单次有效
  6.     return true;
  7. }
复制代码
4. 为什么选择SSE而非WebSocket?

在技能选型过程中,对于服务器向客户端推送登录状态这一需求,SSE(Server-Sent Events)相较于WebSocket具有更合适的特性。SSE专门设计用于实现单向的服务器到客户端的消息推送,非常符合我们仅需要服务器推送登录状态的场景。而WebSocket则更侧重于双向通信,提供了更复杂的全双工通信功能。因此,综合思量项目需求和技能特点,选择SSE能够更简洁、高效地满意系统的功能要求,同时降低不必要的复杂性和开辟本钱。
5. 怎样处理高并发下的SSE连接?

在高并发场景下,为了确保SSE连接的稳固运行和系统资源的有效利用,接纳了以下处理策略,相关代码如下:
  1. // 在WxLoginHelper类中定义验证码与SSE连接的缓存,设置最大容量为300,写入后5分钟过期
  2. private LoadingCache<String, SseEmitter> verifyCodeCache = CacheBuilder.newBuilder().maximumSize(300).expireAfterWrite(5, TimeUnit.MINUTES).build(new CacheLoader<String, SseEmitter>() {
  3. });
复制代码
  1. // 定义SSE连接的异常处理机制,包括超时和错误处理
  2. sseEmitter.onTimeout(() -> {
  3.     log.info("sse 超时中断 --> {}", realCode);
  4.     verifyCodeCache.invalidate(realCode);
  5.     sseEmitter.complete();
  6. });
  7. sseEmitter.onError((e) -> {
  8.     log.warn("sse error! --> {}", realCode, e);
  9.     verifyCodeCache.invalidate(realCode);
  10.     sseEmitter.complete();
  11. });
复制代码


  • 容量控制:通过设置缓存的最大连接数为300,有效防止因连接过多导致的内存溢出(OOM)题目,确保系统在高并发情况下的稳固性。
  • 超时机制:设定5分钟的自动断开时间,当连接在规定时间内没有活动时,自动断开连接,开释系统资源,避免资源浪费。
  • 异常处理:通过onError和onTimeout回调函数,对SSE连接过程中出现的错误和超时情况举行实时处理,确保在异常情况下能够正确开释资源,维持系统的正常运行。
  • 分布式扩展:当前系统为单机版,在生产情况中,为了应对更高的并发和更好的扩展性,可以将SSE连接管理替换为Redis PubSub机制,实现分布式情况下的高效消息推送和连接管理。
6. 怎样实现用户自动注册?

用户自动注册流程通过以下步骤实现:


  • 触发自动注册:调用特定方法,传入包罗用户信息的参数,启动用户自动注册流程。
  1. // 自动注册逻辑(WxCallbackRestController.java:69)
  2. sessionService.autoRegisterWxUserInfo(fromUser);
复制代码


  • 查抄用户是否已注册:根据用户的第三方账号 ID 查询用户信息。

    • 若用户未注册,进入注册流程。
    • 若用户已注册,直接返回用户 ID,后续举行登录操作。

  1. /**
  2.   * 没有注册时,先注册一个用户;若已经有,则登录
  3.   *
  4.   * @param req
  5.   */
  6. private Long registerOrGetUserInfo(UserSaveReq req) {
  7.      UserDO user = userDao.getByThirdAccountId(req.getThirdAccountId());
  8.      if (user == null) {
  9.          return registerService.registerByWechat(req.getThirdAccountId());
  10.      }
  11.      return user.getId();
  12. }
复制代码


  • 实行注册操作(用户未注册时)

    • 生存用户登录信息:创建用户对象,设置第三方账号 ID 和微信登录类型,生存到数据层。
    • 初始化用户信息:创建用户信息对象,设置用户 ID,随机生成用户昵称和头像并设置,生存到数据层。
    • 生存 AI 相关信息:初始化与用户相关的 AI 信息,创建 AI 信息对象并生存或更新到数据层,实行用户注册后的后续处理逻辑。

  1.     @Override
  2.     @Transactional(rollbackFor = Exception.class)
  3.     public Long registerByWechat(String thirdAccount) {
  4.         // 用户不存在,则需要注册
  5.         // 1. 保存用户登录信息
  6.         UserDO user = new UserDO();
  7.         user.setThirdAccountId(thirdAccount);
  8.         user.setLoginType(LoginTypeEnum.WECHAT.getType());
  9.         userDao.saveUser(user);
  10.         // 2. 初始化用户信息,随机生成用户昵称 + 头像
  11.         UserInfoDO userInfo = new UserInfoDO();
  12.         userInfo.setUserId(user.getId());
  13.         userInfo.setUserName(UserRandomGenHelper.genNickName());
  14.         userInfo.setPhoto(UserRandomGenHelper.genAvatar());
  15.         userDao.save(userInfo);
  16.         // 3. 保存ai相互信息
  17.         UserAiDO userAiDO = UserAiConverter.initAi(user.getId());
  18.         userAiDao.saveOrUpdateAiBindInfo(userAiDO, null);
  19.         processAfterUserRegister(user.getId());
  20.         return user.getId();
  21.     }
复制代码


  • 注册完成:注册方法实行完毕,返回用户 ID,标志自动注册流程乐成,后续系统可依此 ID 举行登录后操作 。
  1. qrLoginHelper.login(code);
复制代码
7. 怎样实现基于Cookie和JWT的会话管理?



  • 通过sessionService生成session
  1.   /**
  2.    * 微信公众号登录
  3.    *
  4.    * @param verifyCode 用户输入的登录验证码
  5.    * @return
  6.    */
  7.   public boolean login(String verifyCode) {
  8.       // 通过验证码找到对应的长连接
  9.       SseEmitter sseEmitter = verifyCodeCache.getIfPresent(verifyCode);
  10.       if (sseEmitter == null) {
  11.           return false;
  12.       }
  13.       String session = sessionService.loginByWx(ReqInfoContext.getReqInfo().getUserId());
  14.       try {
  15.           // 登录成功,写入session
  16.           sseEmitter.send(session);
  17.           // 设置cookie的路径
  18.           sseEmitter.send("login#" + LoginService.SESSION_KEY + "=" + session + ";path=/;");
  19.           return true;
  20.       } catch (Exception e) {
  21.           log.error("登录异常: {}", verifyCode, e);
  22.       } finally {
  23.           sseEmitter.complete();
  24.           verifyCodeCache.invalidate(verifyCode);
  25.       }
  26.       return false;
  27.   }
复制代码


  • JWT生成策略
  1.     public String genSession(Long userId) {
  2.         // 1.生成jwt格式的会话,内部持有有效期,用户信息
  3.         String session = JsonUtil.toStr(MapUtils.create("s", SelfTraceIdGenerator.generate(), "u", userId));
  4.         String token = JWT.create().withIssuer(jwtProperties.getIssuer()).withExpiresAt(new Date(System.currentTimeMillis() + jwtProperties.getExpire()))
  5.                 .withPayload(session)
  6.                 .sign(algorithm);
  7.         // 2.使用jwt生成的token时,后端可以不存储这个session信息, 完全依赖jwt的信息
  8.         // 但是需要考虑到用户登出,需要主动失效这个token,而jwt本身无状态,所以再这里的redis做一个简单的token -> userId的缓存,用于双重判定
  9.         RedisClient.setStrWithExpire(token, String.valueOf(userId), jwtProperties.getExpire() / 1000);
  10.         return token;
  11.     }
复制代码


  • Cookie设置流程
  1. // 设置cookie的路径
  2. sseEmitter.send("login#" + LoginService.SESSION_KEY + "=" + session + ";path=/;");
  3. // 前端登录成功后设置Cookie:
  4. document.cookie = "f-session=" + sessionToken;
复制代码
8. 怎样实现分布式Session管理?



  • 双重校验机制

    • JWT自带底子会话信息(用户ID、有效期)
    • Redis存储会话映射关系,解决JWT无法自动失效的题目

  • 失效策略
  1. public void removeSession(String session) {
  2.     RedisClient.del(session);  // 清除Redis映射
  3.     // JWT本身仍有效但无法通过校验
  4. }
复制代码
9.怎样实现接口权限控制?



  • 权限注解定义(Permission.java)
  1. @Target({ElementType.METHOD, ElementType.TYPE})
  2. @Retention(RetentionPolicy.RUNTIME)
  3. @Documented
  4. public @interface Permission {
  5.     /**
  6.      * 限定权限
  7.      *
  8.      * @return
  9.      */
  10.     UserRole role() default UserRole.ALL;
  11. }
复制代码


  • 角色枚举(UserRole.java)
  1. public enum UserRole {
  2.     ADMIN,    // 管理员
  3.     LOGIN,    // 登录用户
  4.     ALL;      // 所有用户
  5. }
复制代码


  • 权限校验拦截器GlobalViewInterceptor.java
  1. public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
  2.         if (handler instanceof HandlerMethod) {
  3.             HandlerMethod handlerMethod = (HandlerMethod) handler;
  4.             Permission permission = handlerMethod.getMethod().getAnnotation(Permission.class);
  5.             if (permission == null) {
  6.                 permission = handlerMethod.getBeanType().getAnnotation(Permission.class);
  7.             }
  8.             if (permission == null || permission.role() == UserRole.ALL) {
  9.                 if (ReqInfoContext.getReqInfo() != null) {
  10.                     // 用户活跃度更新
  11.                     SpringUtil.getBean(UserActivityRankService.class).addActivityScore(ReqInfoContext.getReqInfo().getUserId(), new ActivityScoreBo().setPath(ReqInfoContext.getReqInfo().getPath()));
  12.                 }
  13.                 return true;
  14.             }
  15.             if (ReqInfoContext.getReqInfo() == null || ReqInfoContext.getReqInfo().getUserId() == null) {
  16.                 if (handlerMethod.getMethod().getAnnotation(ResponseBody.class) != null
  17.                         || handlerMethod.getMethod().getDeclaringClass().getAnnotation(RestController.class) != null) {
  18.                     // 访问需要登录的rest接口
  19.                     response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
  20.                     response.getWriter().println(JsonUtil.toStr(ResVo.fail(StatusEnum.FORBID_NOTLOGIN)));
  21.                     response.getWriter().flush();
  22.                     return false;
  23.                 } else if (request.getRequestURI().startsWith("/api/admin/") || request.getRequestURI().startsWith("/admin/")) {
  24.                     response.sendRedirect("/admin");
  25.                 } else {
  26.                     // 访问需要登录的页面时,直接跳转到登录界面
  27.                     response.sendRedirect("/");
  28.                 }
  29.                 return false;
  30.             }
  31.             if (permission.role() == UserRole.ADMIN && !UserRole.ADMIN.name().equalsIgnoreCase(ReqInfoContext.getReqInfo().getUser().getRole())) {
  32.                 // 设置为无权限
  33.                 response.setStatus(HttpStatus.FORBIDDEN.value());
  34.                 return false;
  35.             }
  36.         }
  37.         return true;
  38.     }
复制代码


  • 利用示例(AdminLoginController.java)
  1. @Permission(role = UserRole.LOGIN)
  2. @GetMapping("logout")
  3. public ResVo<Boolean> logOut(HttpServletResponse response) {
  4. }
复制代码


  • 实现特点

    • 通过自定义注解@Permission声明接口权限要求
    • 利用拦截器统一处理权限校验
    • 用户角色信息存储在会话上下文中(ReqInfoContext)
    • 支持方法级和类级权限声明(类级声明作为默认配置)

  • 权限控制流程: 请求 → 权限注解解析 → 会话信息校验 → 角色权限匹配 → 放行/拦截
10. 如果要做分布式摆设,需要改造哪些模块?

当系统需要举行分布式摆设时,为了顺应分布式情况的特点和要求,需要对以下模块举行改造:


  • 改造要点

    • 缓存层:将现有的Guava Cache替换为Redis Cluster,以实现分布式情况下的缓存共享和高可用性,提高系统的性能和可扩展性。
    • SSE连接管理:将SSE连接管理方式改为利用Redis的Pub/Sub机制,实现分布式情况下的消息推送和连接管理,确保在多节点情况中能够正常工作。
    • 设备ID生成:利用雪花算法替代原有的UUID生成方式,生成更具唯一性和有序性的设备ID,满意分布式系统中对ID生成的更高要求。
    • 回调通知:引入消息队列(MQ)实现跨服务的通知功能,确保在分布式系统中各个服务之间能够实时、可靠地传递回调信息,提高系统的团体协作能力。
    • 限流熔断:添加Sentinel等限流熔断组件,对系统的请求流量举行有效控制,防止因流量过大导致系统瓦解,并在服务出现故障时实时举行熔断处理,保障系统的稳固性和可用性。

  • 项目中相关代码文件

    • 焦点登录逻辑:WxLoginHelper.java文件包罗了焦点的登录逻辑和验证码处理等关键功能。
    • 微信回调处理:WxCallbackRestController.java文件负责处理微信回调信息,举行用户自动注册和登录验证等操作。
    • SSE连接管理:WxLoginController.java文件实现了SSE连接的建立、管理和异常处理等功能。
    • 用户服务接口:UserService.java文件定义了用户信息管理和自动注册相关的接口方法,是系统中用户信息处理的关键部分。

通过以上对微信公众号自动登录技能的全面分析和具体介绍,盼望能够帮助你更好地理解这一技能范畴的焦点要点和面试考察方向,祝你在面试中取得优异的结果!

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

钜形不锈钢水箱

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表