news 2026/10/12 6:33:49

微信扫码登录Spring Boot实现:OAuth2.0授权回调与登录态封装全攻略

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
微信扫码登录Spring Boot实现:OAuth2.0授权回调与登录态封装全攻略

简介:面向需要使用微信开放平台完成用户免密登录的Java开发人员,这套基于SpringBoot的扫码登录项目演示了从获取二维码、接收授权code、换取access_token/openid到获取用户信息并完成本地登录的完整链路。工程以16个class、15个java为主要源码,辅以properties、xml配置及Spring Security相关资源,便于理解OAuth 2.0协议与微信Java SDK的配合方式;项目还包含JWT令牌生成、状态码校验、回调地址管理等安全细节,适合作为企业Web应用对接微信扫码功能的起步模板。压缩包共121个文件,约172KB,结构紧凑,可直接导入IDE查看运行。已有11382人学习下载,适合具备SpringBoot基础、希望快速掌握微信扫码登录落地方法的开发者参考。

1. 微信扫码登录的工程本质:一次 OAuth2.0 授权回调,而不是黑匣子

微信扫码登录在 Web 端的接入频率很高,但我每次接手相关需求都会先确认一件事:对方说的“微信登录”是公众平台网页授权,还是开放平台扫码登录。两者都是 OAuth2.0 授权码流程,但接口域名、scope 参数、回调配置完全不同,混着用会得到一堆意想不到的错误码。微信扫码登录的本质是:用户在 PC 端看到一个二维码,用微信确认授权后,微信把授权结果回调到你的后端,后端用 code 换取 access_token,再拉取 openid 和用户资料,最终映射成自建登录态。下面以 Spring Boot 2.x 为开发背景,从开放平台配置讲到回调换码、获取用户信息的完整代码链路,并整理接入中容易踩的几个坑,适合正在给 Web 端接入微信扫码登录、以及打算把微信账号体系和自建账号体系打通的开发者。

2. 开放平台配置细节:AppID、回调域与授权链接的三个关键参数

接入微信扫码登录的第一步不是写代码,而是先把开放平台后台的配置铺平。后台的差错会直接放大到代码层,这里填错一个字符,后面接口再完美也白搭。

2.1 先分清开发平台:网站应用和公众号应用不是一回事

微信公众平台和微信开放平台是两个独立的开发者平台,账号体系不互通。公众平台里的网页授权是给公众号内 H5 页面用的,授权结果拿到的是公众号视角下的 openid;开放平台里的网站应用是给 PC 网站扫码登录用的,接口域名是 api.weixin.qq.com,scope 固定为 snsapi_login。把这两套体系分开理解,新手接入时的很多困惑就能解开。

最常见的问题是拿公众号的 AppID 去调扫码接口,微信返回 40125 invalid secret,或者一直提示 redirect_uri 不合法。开放平台的网站应用创建流程并不复杂,注册开发者账号,创建应用,填写应用名称、简介、官网地址和应用截图,提交后等审核。审核通过后分配正式的 AppID 和 AppSecret。个人开发者也能创建网站应用,只是不同类型的资质要求不一样,审核周期也略有差异,一般一两个工作日能出结果。

关于 AppSecret,有一点要提前说:它只在创建时完整展示一次,之后只能通过重置的方式重新生成。重置后旧 secret 立即失效,线上服务如果没有同步新值,扫码登录会集体报 40125。我习惯在配置中心单独开一个命名空间,把公众号和开放平台的密钥分成两个条目管理,避免换项目时拿错。

注意:开放平台后台的授权回调域只填域名,不要带协议头和端口。

2.2 授权回调域:不是填完整 URL,而是域名加校验文件

开放平台后台的“网站应用—授权回调域”设置,填的是域名,不是带协议头的完整链接,比如 login-api.example.com。第一次填写时微信会要求下载一个 txt 校验文件,把它放到该域名服务器的 Web 根目录,确保能直接访问到文件内容,再回后台点击确认。这一步校验的是域名归属,不校验子路径,所以不要把它放到二级目录里。

校验通过后,实际回调地址由自己拼接,路径可以任意,比如 https://login-api.example.com/wechat/callback,但域名部分必须和后台填写的完全一致。协议头可以用 http 也可以用 https,端口则被限制在 80 和 443。如果你在本地起了 8080 端口,微信服务器无法回调进来,扫码后页面会一直停留在授权完成状态,后端却收不到任何请求。

很多团队卡在这一步:开发环境没有公网域名怎么办。我常用的做法是准备一台公网测试机,把回调域名解析到测试机,本地代码打包上去跑,日志打全,联调完再撤。这样虽然多花几分钟,但比在本机用各种别扭的方式模拟回调要省心得多。

另外,回调地址里的路径不需要在后台登记,后台只校验域名归属,路径完全由自己的应用控制。这一点和公众平台的网页授权域名逻辑类似,但开放平台校验的是域名所有权,不校验具体目录。

2.3 授权链接拼接:scope 固定为 snsapi_login,redirect_uri 要编码

开放平台扫码登录的授权链接格式是固定的:

https://open.weixin.qq.com/connect/qrconnect?appid=APPID&redirect_uri=REDIRECT_URI&response_type=code&scope=snsapi_login&state=STATE#wechat_redirect

参数拆解如下:

  • appid 用开放平台网站应用的 AppID,公众号的拿过来无效。
  • redirect_uri 是用户授权后跳回后端的地址,必须进行 URL 编码,否则链接里的参数可能被截断。编码前域名要和后台授权回调域一致。
  • response_type 固定为 code,表示走授权码模式。
  • scope 固定为 snsapi_login,这是开放平台扫码登录的专用值。
  • state 由开发者生成,要保证随机性,并在本次请求生命周期内不变,回调时用来校验来源。

有一条经验分享:二维码不能直接拿<img>标签指向这个授权链接。浏览器直接打开授权链接,会看到微信的授权页面或提示页,而不是一个可扫描的二维码。产品层面需要在前端用 qrcode 类组件把这个链接渲染成二维码图片,或者把链接交给扫码组件处理。项目里最后选了前者,用户点击“微信登录”后弹出层内展示二维码,扫码动作完成前二维码自动定时刷新,防止过期。

配置项正确写法易错点
AppID开放平台网站应用的 AppID误用公众号/小程序 AppID
AppSecret对应网站应用的 secret重置后旧 secret 立即失效
授权回调域api.example.com填了 https:// 或带了端口
校验文件放在域名的根目录放到了子目录导致验证失败
回调地址/wechat/callback 任意路径域名必须与后台完全一致

2.4 本地调试环境的妥协方案

回到端口限制的问题,我补充一种我常用的部署结构:测试环境单独分配一个域名,例如 wechat-test.example.com,开放平台的授权回调域直接填这个测试域名。申请一个 80 或 443 端口的 Nginx 站点,把 Spring Boot 服务挂在同机监听,Nginx 转发到应用端口。这样微信回调请求进来时,链路是完整的,和线上一致,只是请求的是测试应用而已。

这样的妥协方案会带来一点点不便:所有调试都要在测试机上看日志,但换来的是扫码流程的端到端可验证,而不是自己在本地 mock 一个回调。接口的时序、code 的一次性、token 的有效期,这些都要真实验证才算数。我个人的习惯是本地保留一套完全 mock 的测试用例,只验证 Service 层逻辑;联调和验签则全部依赖测试环境,两套东西互不干扰。

3. 扫码流程的 Spring Boot 实现:生成授权链接、回调换码与用户信息获取

把后台配置做完,代码实现就水到渠成了。我会按 Service 和 Controller 两层来组织,先讲微信侧接口调用,再讲业务侧接入。这里先说明一个选型问题:为什么用 RestTemplate 而不是 OkHttp 或 WebClient?因为微信扫码登录涉及到的接口都是简单 GET 请求,不需要连接池、不需要异步回调,RestTemplate 作为 Spring 内置客户端足够用,少引入一个依赖,将来维护成本也更低。

3.1 授权链接生成:让前端拿去渲染二维码

先建一个配置类,把微信开放平台的参数收拢起来,别在业务代码里散落一堆魔法值:

@Data @ConfigurationProperties(prefix = "wechat.login") public class WechatLoginProperties { /** 开放平台网站应用的 AppID */ private String appId; /** 对应的 AppSecret */ private String secret; /** 扫码授权后的回调地址 */ private String redirectUri; }
wechat: login: app-id: wx1234567890abcdef secret: 你的应用密钥 redirect-uri: https://login-api.example.com/wechat/callback

这里有两个容易犯错的点。第一,appId 看起来像数字,但它是字符串,千万别用 Long 或者 Integer 去接收。第二,redirect-uri 必须和开放平台后台的授权回调域保持同域,否则微信在扫码后跳转时直接提示 redirect_uri 参数错误。

生成授权链接的方法放到 WechatLoginService 里:

@Service public class WechatLoginService { private final RestTemplate restTemplate = new RestTemplate(); private final WechatLoginProperties props; public WechatLoginService(WechatLoginProperties props) { this.props = props; } /** 构造微信扫码授权链接 */ public String buildQrConnectUrl(String state) { String encodedRedirectUri = URLEncoder.encode(props.getRedirectUri(), StandardCharsets.UTF_8); return String.format( "https://open.weixin.qq.com/connect/qrconnect?appid=%s&redirect_uri=%s&response_type=code&scope=snsapi_login&state=%s#wechat_redirect", props.getAppId(), encodedRedirectUri, state ); } }

逻辑说明:redirect_uri 必须编码,否则链接里出现 & 时微信会把它当成参数分隔符,导致回调地址参数丢失。state 是外部传入的,Controller 里生成后先存缓存,再传进来拼接,这样回调时能取出来比对。

参数方面,scope 固定是 snsapi_login,不要改成 snsapi_userinfo,开放平台不支持。response_type 固定是 code,微信才会在回调里带上授权码而不是直接返回 token。

3.2 回调接口:先用 code 换 access_token

用户扫码确认后,微信会重定向到回调地址,带两个参数:code 和 state。后端第一步是校验 state,第二步就是用 code 换 access_token。这里先写一个数据模型:

@Data public class WechatOauthToken { private String access_token; private Integer expires_in; private String refresh_token; private String openid; private String scope; private String unionid; private Integer errcode; private String errmsg; }

为什么字段名不改成驼峰?因为微信返回的 JSON 就是 snake_case,用 @JsonProperty 也能映射,但直接沿用微信文档的字段名,反序列化不容易出错,换来换去反而容易漏。errmsg 是错误码,正常响应里没有这个字段,所以在交换 token 之后要检查一下 errcode 是否为 null。

换 token 的方法:

public WechatOauthToken exchangeToken(String code) { String url = String.format( "https://api.weixin.qq.com/sns/oauth2/access_token?appid=%s&secret=%s&code=%s&grant_type=authorization_code", props.getAppId(), props.getSecret(), code ); WechatOauthToken token = restTemplate.getForObject(url, WechatOauthToken.class); if (token == null || token.getErrcode() != null) { throw new WechatLoginException("微信换取 access_token 失败: " + token); } return token; }

逻辑说明:grant_type 固定为 authorization_code,微信文档里没有其他可选项。code 只能用一次,一次交换完成后立即作废,重复使用同一 code 会返回 40029。access_token 有效期 7200 秒,refresh_token 有效期 30 天,但在扫码登录这种一次性授权场景里,我们拿到用户信息之后 token 就没用了,不需要实现刷新逻辑。

另外一个细节:RestTemplate 默认的 SimpleClientHttpRequestFactory 在 GET 请求里会把 url 里的中文字符自动转义,但如果回调参数里带了特殊字符,建议统一用 UriComponentsBuilder 来构造。上面的 String.format 写法方便阅读,生产环境我会换成 UriComponentsBuilder,并设置 UTF-8 编码。

3.3 获取用户信息:openid、unionid、头像一个都不能漏

换到 access_token 后,马上请求用户信息接口:

public WechatUserInfo getUserInfo(String accessToken, String openid) { String url = String.format( "https://api.weixin.qq.com/sns/userinfo?access_token=%s&openid=%s", accessToken, openid ); WechatUserInfo userInfo = restTemplate.getForObject(url, WechatUserInfo.class); if (userInfo == null || userInfo.getErrcode() != null) { throw new WechatLoginException("获取微信用户信息失败: " + userInfo); } return userInfo; }

用户信息字段:

字段含义注意事项
openid应用维度唯一标识换 AppID 后会变
unionid开放平台账号统一标识绑定公众号/小程序后才返回
nickname昵称可能有 emoji,建表用 utf8mb4
headimgurl头像 URL存库前建议转存到自己服务器
province/city/country地区信息可能为空

这里要解释一下 openid 和 unionid 的关系。openid 是用户在当前 AppID 下的唯一标识,如果以后换了一个开放平台应用,openid 就变了。unionid 是用户在同一个开放平台账号下所有应用的统一标识,但前提是这些应用都要绑定到同一个开放平台账号下,并且用户至少使用过其中的公众号或小程序。如果你的产品同时有 PC 网站、公众号和 App 小程序端,最好用 unionid 作为用户主键,这样各端数据能打通。

关于 headimgurl,微信 CDN 上的地址默认带一个尺寸参数,直接存库问题不大,但保存时间久了可能失效。我一般会在登录成功后把它抓下来转存到自己服务器的存储路径,同时在库里保留原始地址字段用于排查。昵称里的 emoji 也是高频坑,建表字符集如果用 utf8 会报错甚至存成问号,用 utf8mb4 才能完整保存四字节字符。

4. 登录态的打通与封装:Controller 分层、user 映射与 token 生成

微信侧的三次请求都封装好后,真正落到业务系统的是最后两步:把微信用户映射成本地用户,生成登录态。这一步做得干净,后续接小程序或者公众号登录时也能复用同一套账号体系。

4.1 Controller 层:只做编排,不写微信参数

Controller 拆成两个接口,一个生成二维码链接,一个处理微信回调:

@RestController @RequestMapping("/wechat") @RequiredArgsConstructor public class WechatLoginController { private final WechatLoginService wechatLoginService; private final LocalUserService userService; private final LoginTokenService loginTokenService; private final RedisTemplate<String, String> redisTemplate; /** 前端拿这个链接去渲染二维码 */ @GetMapping("/qr-url") public String qrUrl() { String state = UUID.randomUUID().toString().replace("-", ""); redisTemplate.opsForValue().set("wx:state:" + state, "1", Duration.ofMinutes(10)); return wechatLoginService.buildQrConnectUrl(state); } /** 微信扫码后回调 */ @GetMapping("/callback") public String callback(@RequestParam String code, @RequestParam String state, HttpServletResponse response) throws IOException { // 1. 校验 state,防止 CSRF String cacheKey = "wx:state:" + state; if (Boolean.FALSE.equals(redisTemplate.hasKey(cacheKey))) { throw new WechatLoginException("state 校验失败"); } redisTemplate.delete(cacheKey); // 2. 换 token,拿用户信息 WechatOauthToken oauthToken = wechatLoginService.exchangeToken(code); WechatUserInfo userInfo = wechatLoginService.getUserInfo(oauthToken.getAccess_token(), oauthToken.getOpenid()); // 3. 映射本地用户并生成登录态 LocalUser localUser = userService.findOrCreateByOpenid( oauthToken.getOpenid(), oauthToken.getUnionid(), userInfo ); String token = loginTokenService.generate(localUser.getId()); response.sendRedirect("/#/login?token=" + token); return null; } }

逻辑说明:state 的存储约定是 10 分钟过期,回调校验通过后立刻删除,保证一次性使用。redisTemplate.hasKey 不要直接用 ! 取反,因为 can还返回 null,用 Boolean.FALSE.equals 包一下就安全了。

code 换 token 和拉用户信息两步合在一起,虽然也能拆成异步,但扫码登录这种低频操作同步处理更简单,微信接口本身也就几百毫秒的延迟,不需要额外引入消息队列。

回调里最终发起重定向,把生成的登录 token 放在 URL 的 query 参数中,前端在登录页读取后存入 localStorage 或 cookie。这种做法的好处是分离了微信域和业务域,后续页面切换时只需要关心自己的 token。

4.2 用户表设计:openid 做唯一索引,字符集用 utf8mb4

数据库表结构参考:

CREATE TABLE `t_wechat_user` ( `id` bigint NOT NULL AUTO_INCREMENT, `openid` varchar(64) NOT NULL, `unionid` varchar(64) DEFAULT '', `nickname` varchar(64) DEFAULT '', `avatar_url` varchar(500) DEFAULT '', `created_at` datetime DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `uk_openid` (`openid`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

这里的关键点:openid 字段一定要加唯一索引,findOrCreate 逻辑依赖这个约束来做并发防重。unionid 只作为普通索引,因为不是所有用户都有 unionid,空字符串允许重复。nickname 用 varchar(64),有的昵称包含中文加 emoji,64 字符在 utf8mb4 下通常够用。avatar_url 留 500,微信头像地址带参数时确实比较长。

4.3 首登注册:按 openid 维度做一次查询

findOrCreateByOpenid 的实现思路值得单独说一下:

public LocalUser findOrCreateByOpenid(String openid, String unionid, WechatUserInfo userInfo) { LocalUser user = userMapper.selectByOpenid(openid); if (user != null) { // 老用户:每次登录都刷新一下昵称和头像,避免资料过期 user.setNickname(userInfo.getNickname()); user.setAvatarUrl(userInfo.getHeadimgurl()); userMapper.updateById(user); return user; } // 新用户:用 openid 落库,unionid 为空时存空串 LocalUser newUser = new LocalUser(); newUser.setOpenid(openid); newUser.setUnionid(unionid == null ? "" : unionid); newUser.setNickname(userInfo.getNickname()); newUser.setAvatarUrl(userInfo.getHeadimgurl()); userMapper.insert(newUser); return newUser; }

说明:首次登录的注册动作很简单,不需要填手机号,因为微信已经提供了身份信息。如果产品要求强制绑定手机号,可以在登录成功后弹一个绑定页面,把 openid 暂存在缓存里,绑定完成后再写用户表。我见过一个项目,他们会在扫码前先把用户建出来,等回调回来再补资料,结果产生大量只有 openid 的僵尸账号。更合理的方式是回调确认成功之后再落库,这样用户至少是完整身份。

4.4 登录态生成:用自包含 token 而不是长 Session

重定向携带 token 到前端时,token 本身就是一个自包含的登录凭证。用 JWT 实现很简单:把 userId 和过期时间放进去,签名用应用自己的密钥。生成 Token 后,回调接口把 token 拼在重定向 URL 里,前端解析后保存,后续请求在 Header 带上即可。

public String generate(Long userId) { long expireAt = System.currentTimeMillis() + 7 * 24 * 60 * 60 * 1000L; String payload = userId + ":" + expireAt; // 用应用密钥做签名,防止篡改;生产环境建议用成熟 JWT 库 String sign = DigestUtils.sha256Hex(payload + secretKey); return Base64.getUrlEncoder().encodeToString((payload + "." + sign).getBytes(StandardCharsets.UTF_8)); }

参数说明:这里只做演示,把 userId 和过期时间拼起来再做一次签名校验,能挡住大部分篡改请求。生产环境其实没必要自己造轮子,直接用成熟的 JWT 库,把 userId 放进 subject 或自定义 claim,用 HS256 签名即可。签名的密钥要和微信的 AppSecret 分开管理,别混用,否则微信侧密钥泄露会连累业务侧 token 安全。

5. 微信扫码登录避坑记录:五个真实翻车点与排查方式

上面的流程在一个项目里跑通之后,踩过的坑有一大半都能归到“微信文档没说清”。以下五条是我认为最值得提前记住的,每条按现象、原因、解决三部分展开。

5.1 code 被重复消费,第二次回调报 40029

现象:回调接口处理成功后,用户手一抖刷新了页面,或者前端重试了一次请求,第二次请求报 40029 invalid code。

原因:微信的 code 是一次性的,换完 token 之后就作废。刷新页面会重新发起 callback,参数还是同一个 code,第二次必然失败。

解决:在回调逻辑里,成功写入用户信息后立即重定向到业务页,并携带自家 token。这样浏览器地址栏替换成业务页地址,微信回调地址被挤掉,刷新也只是刷新业务页。如果在这之前就收到重复 code,可以在 Redis 里对 code 做临时去重,key 是 code,value 是已消费标记,TTL 设置五分钟,超时自动清理。

5.2 state 校验失败,登录一直跳回错误页

现象:回调接口报 state 校验失败,或者扫码完成后莫名其妙回到首页。

原因:state 的生成和校验如果依赖 HttpSession,项目一旦拆成多实例部署,session 不共享,第二个实例上校验自然失败。还有一种情况,用户开了多个页面,最早生成的 state 被后续页面的 state 覆盖,回调对应不上。

解决:不要依赖单机 Session,用 Redis 按 state 存一个带过期时间的占位符。生成后存进缓存,回调时判断存在才放行,用完即删。state 里可以再包一层随机数和时间戳,提高复杂度,避免被猜出来。多实例部署时,Redis 是共享的,天然不会出现上面说的 session 不互通问题。

5.3 redirect_uri 参数错误,或者回调 URL 里 code 丢失

现象:微信授权页面提示 redirect_uri 参数错误,或者扫码后跳回的回调 URL 里 code 参数缺了一半。

原因:后台授权回调域填了协议头或端口,微信校验失败;或者 redirect_uri 没有做完整 URL 编码,链接里带 & 或 ? 时被提前截断,code 参数因此丢失。

解决:先核对后台填的域名,去掉协议头和端口,只填 domain。再用 URLEncoder.encode 对 redirect_uri 完整编码,编码后再拼接到授权链接里。调通以后可以加一条测试用例,把生成的授权链接整个打印出来,人工核对参数顺序,确认 redirect_uri 后面没有混入其他参数。

5.4 unionid 返回为空,跨端账号体系建不起来

现象:拉取用户信息后 unionid 是 null,导致按 unionid 维度建立账号体系的方案失效。

原因:开放平台的 unionid 只有在同一开放平台账号下绑定了已认证的公众号或小程序,且用户至少关注过其中一个的情况下才会返回。只创建了网站应用,没有绑过公众号,unionid 大概率是空。

解决:如果只需要 PC 端登录,直接用 openid 作为用户主键就够。如果确实要跨端统一,先在开放平台后台把公众号和小程序都绑到同一个账号下,绑定关系生效后再用 unionid 走统一流程。这里要注意,绑定完成后老用户只有在再次授权时才会补齐 unionid,存量数据需要做一轮补偿更新。

5.5 授权链接在 PC 端打不开或白屏

现象:前端把授权链接放进二维码组件,扫码后用手机打开页面,提示“请在微信客户端打开链接”或白屏。

原因:这里要区分开两个概念。开放平台扫码链接是给 PC 端浏览器展示二维码用的,不是给手机端直接访问的。如果直接把授权链接作为二维码内容,用户用微信扫出来的就是授权链接,微信内置浏览器反而不认,大概率白屏或报错。

解决:PC 端页面先把授权链接转换成二维码,用户用微信扫这个二维码时,微信服务器识别的是授权链接并引导授权,授权完成后再跳转回调地址。真正要确保的是授权链接本身可以被微信域名正确解析,而不是把链接放到<img>或者 a 标签里直接打开。

6. 进阶技巧:把扫码登录封装成一次配置就能运行的登录模板

考虑到接入微信扫码的步骤虽然不多,但代码链路在多个项目里都是同一套,我后来把它抽象成了一个模板类,业务系统只需要传入一个用户持久化接口。这样 Controller 里的职责就只剩下拿参数、调模板、重定向,微信相关的接口细节被隔离在一层。

public interface WechatUserRepository { LocalUser findOrCreate(String openid, String unionid, WechatUserInfo wxUser); } @Component public class WechatLoginTemplate { private final WechatLoginProperties props; private final RedisTemplate<String, String> redisTemplate; private final RestTemplate restTemplate = new RestTemplate(); public WechatLoginResult login(String code, String state) { // 1. 校验 state,且只使用一次 String stateKey = "wx:state:" + state; if (Boolean.FALSE.equals(redisTemplate.hasKey(stateKey))) { throw new WechatLoginException("state 校验失败"); } redisTemplate.delete(stateKey); // 2. 换 token 和用户信息 WechatOauthToken token = exchangeToken(code); WechatUserInfo userInfo = getUserInfo(token.getAccess_token(), token.getOpenid()); // 3. 由业务方决定怎么持久化 LocalUser localUser = userRepository.findOrCreate(token.getOpenid(), token.getUnionid(), userInfo); return new WechatLoginResult(localUser.getId(), userInfo); } }

模板化的好处是,后面如果要接扫码登录的新项目,只需要实现 WechatUserRepository 接口,把用户映射逻辑交给业务方,微信侧的时间序、状态校验、错误码处理都不需要再碰。

验证这个模板组件是否好用的最快方式是写一个集成测试,但微信 code 只能使用一次,很难用真实 code 反复测试。更好的办法是在配置里加一个 mock 开关:

wechat: login: enabled: true mock-enabled: true

mock 开关打开时,用预设的 openid 和用户信息替代微信接口,专门验证第3步之后的本地用户映射与 token 生成环节。这样即使微信回调域名在本地不可达,业务侧的登录链路依然能完整测试。

我第一次接这个项目的时候,把 unionid 当成必然存在的字段来建主键,结果测试账号怎么都拉不到 unionid,查了半天才想起要绑定公众号。从那以后,每次接扫码登录我都会强制走一遍检查:先确认开放平台绑定关系,再确认授权回调域里是不是漏掉了协议头,最后确认 state 的存储和校验是不是同一套缓存。希望这篇文章能帮你把流程一次跑通,省去那些同样折腾过我的时间。希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/12 6:33:04

操作系统调度与上下文切换:从原理到性能调优实战

最近在线上环境排查一个性能问题时&#xff0c;我盯着监控面板上的cs&#xff08;context switch&#xff09;列看了足足半小时&#xff1a;CPU 利用率不到 30%&#xff0c;但请求延迟翻了整整五倍&#xff0c;线程疯狂地被切进切出&#xff0c;像一群人挤在一个只容得下单脚站…

作者头像 李华
网站建设 2026/10/12 6:33:02

武汉路网Shp数据在ArcGIS中的处理:坐标、清洗与密度分析

简介&#xff1a;本资源是武汉市路网矢量数据包&#xff0c;面向GIS开发、城市规划及交通分析等需要武汉市陆路交通与行政边界的ArcGIS用户。数据提供武汉市道路矢量图层、各行政区和武汉市边界图层&#xff0c;不含地铁、铁路、水路、航空&#xff0c;专注陆路路网&#xff0c…

作者头像 李华
网站建设 2026/10/12 6:33:01

自定义View思想在Flutter和Compose中的跨技术栈实践

1. 先想清楚&#xff1a;自定义View思想到底在讲什么1.1 传统自定义View的“三大件”不是API&#xff0c;是职责拆解很多从原生开发转过来的朋友&#xff0c;对自定义View这套东西又爱又恨。爱的是它上限高&#xff0c;屏幕里任何像素都能自己说了算&#xff1b;恨的是它门道多…

作者头像 李华
网站建设 2026/10/12 6:32:30

DB2 V11.1下载安装避坑指南:老版本为何仍是运维必选项

简介&#xff1a;DB2 V11.1 是 IBM 推出的企业级关系型数据库管理系统&#xff0c;这份 Linux 版安装压缩包专为需要稳定、安全数据存储环境的中大型企业及系统管理员、DBA 设计&#xff0c;可用于生产或测试环境的快速部署。包内共 405 个文件&#xff0c;包含 174 个 cat 消息…

作者头像 李华
网站建设 2026/10/12 6:30:59

RAG评估实战:三层拆解检索、生成与端到端质量

前面几篇陆续聊了RAG里的数据处理、chunk切分、Embedding模型选择、召回排序还有Prompt设计&#xff0c;很多朋友私下问我最多的问题是&#xff1a;系统搭起来之后&#xff0c;怎么证明它“好用”&#xff1f;用户说回答得不对&#xff0c;但到底哪里不对&#xff1f;优化了一版…

作者头像 李华
网站建设 2026/10/12 6:30:53

C# 接入 ActiveMQ 实战:从 Demo 到故障转移与幂等

简介&#xff1a;这套ActiveMQ Demo是面向C#开发者的消息队列示例程序&#xff0c;基于WinForm窗体实现&#xff0c;包含发送端与接收端两个独立工程。它主要解决初学者在搭建ActiveMQ环境后不知如何用C#进行消息投递和消费的痛点&#xff0c;适合正在学习消息中间件、或需要在…

作者头像 李华