Master WeChat Mini‑Program Login: From Code to Token in 10 Steps
This guide walks you through the complete WeChat mini‑program login flow, covering front‑end wx.login usage, back‑end code2Session exchange for openId and unionId, phone number retrieval, database schema design, API contracts, token generation, interceptor validation, and environment configuration.
Preface
Hello, I am your friend "Architecture Jun", an architect who writes code and poetry.
Requirement Analysis
Clicking the login button opens a popup that requires the user's phone number for login.
WeChat Login Business Logic Rules
Thought Process
Reference the official WeChat documentation: https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/login.html
Official recommended login flow:
Key Points
Front‑end integrates WeChat dependencies in the mini‑program and calls wx.login to obtain a temporary login credential (code) and passes it to the back‑end.
Back‑end calls the auth.code2Session interface to exchange the code for openId, unionId and a session key.
The server can create a custom login state to identify the user in subsequent front‑back interactions.
Table Structure
Create a table to store user information and openId:
CREATE TABLE "family_member" (
"id" bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
"phone" varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '手机号',
"name" varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '名称',
"avatar" varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '头像',
"open_id" varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT 'OpenID',
"gender" int DEFAULT NULL COMMENT '性别(0:男,1:女)',
"create_time" timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
"update_time" timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
"create_by" bigint DEFAULT NULL COMMENT '创建人',
"update_by" bigint DEFAULT NULL COMMENT '更新人',
"remark" varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '备注',
PRIMARY KEY ("id") USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=DYNAMIC COMMENT='老人家属';API Specification
Request parameters:
{
"code": "0e36jkGa1ercRF0Fu4Ia1V3fPD06jkGW",
"nickName": "微信用户",
"phoneCode": "13fe315872a4fb9ed3deee1e5909d5af60dfce7911013436fddcfe13f55ecad3"
}code : temporary login credential (valid 5 minutes)
nickName : WeChat user nickname (currently always "微信用户")
phoneCode : code to obtain the user's phone number
Response example:
{
"code": 200,
"msg": "操作成功",
"data": {
"token": "eyJhbGciOiJIUzI1NiJ9...",
"nickName": "好柿开花8915"
},
"operationTime": null
}Mini‑Program Environment Setup
During testing use a test account to obtain appId and secret; both front‑end and back‑end need these parameters.
Basic Environment Description
Modify request paths as needed.
Ignore HTTPS verification in local development.
Replace the mini‑program APPID with your own test APPID.
Feature Implementation
Controller
@PostMapping("/login")
@ApiOperation("小程序登录")
public AjaxResult login(@RequestBody UserLoginRequestDto dto){
LoginVo loginVo = familyMemberService.login(dto);
return success(loginVo);
}DTO and VO
public class UserLoginRequestDto {
@ApiModelProperty("昵称")
private String nickName;
@ApiModelProperty("登录临时凭证")
private String code;
@ApiModelProperty("手机号临时凭证")
private String phoneCode;
} public class LoginVo {
@ApiModelProperty("JWT token")
private String token;
@ApiModelProperty("昵称")
private String nickName;
}Service Interface
public interface WechatService {
String getOpenid(String code);
String getPhone(String detailCode);
}Service Implementation
package com.zzyl.nursing.service.impl;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.zzyl.nursing.service.WechatService;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.Map;
@Service
public class WechatServiceImpl implements WechatService {
private static final String REQUEST_URL = "https://api.weixin.qq.com/sns/jscode2session?grant_type=authorization_code";
private static final String TOKEN_URL = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential";
private static final String PHONE_REQUEST_URL = "https://api.weixin.qq.com/wxa/business/getuserphonenumber?access_token=";
@Value("${wechat.appId}")
private String appid;
@Value("${wechat.appSecret}")
private String secret;
@Override
public String getOpenid(String code) {
Map<String,Object> paramMap = getAppConfig();
paramMap.put("js_code", code);
String result = HttpUtil.get(REQUEST_URL, paramMap);
JSONObject jsonObject = JSONUtil.parseObj(result);
if(ObjectUtil.isNotEmpty(jsonObject.getInt("errcode")){
throw new RuntimeException(jsonObject.getStr("errmsg"));
}
return jsonObject.getStr("openid");
}
private Map<String, Object> getAppConfig() {
Map<String, Object> paramMap = new HashMap<>();
paramMap.put("appid", appid);
paramMap.put("secret", secret);
return paramMap;
}
@Override
public String getPhone(String detailCode) {
String token = getToken();
String url = PHONE_REQUEST_URL + token;
Map<String,Object> paramMap = new HashMap<>();
paramMap.put("code", detailCode);
String result = HttpUtil.post(url, JSONUtil.toJsonStr(paramMap));
JSONObject jsonObject = JSONUtil.parseObj(result);
if(jsonObject.getInt("errcode") != 0){
throw new RuntimeException(jsonObject.getStr("errmsg"));
}
return jsonObject.getJSONObject("phone_info").getStr("phoneNumber");
}
private String getToken() {
Map<String,Object> paramMap = getAppConfig();
String result = HttpUtil.get(TOKEN_URL, paramMap);
JSONObject jsonObject = JSONUtil.parseObj(result);
if(ObjectUtil.isNotEmpty(jsonObject.getInt("errcode"))){
throw new RuntimeException(jsonObject.getStr("errmsg"));
}
return jsonObject.getStr("access_token");
}
}Login Business Logic
@Override
public LoginVo login(UserLoginRequestDto dto) {
String openId = wechatService.getOpenid(dto.getCode());
FamilyMember member = getOne(Wrappers.<FamilyMember>lambdaQuery(FamilyMember.class)
.eq(FamilyMember::getOpenId, openId));
if(ObjectUtil.isEmpty(member)){
member = FamilyMember.builder().openId(openId).build();
}
String phone = wechatService.getPhone(dto.getPhoneCode());
saveOrUpdateFamilyMember(member, phone);
Map<String,Object> claims = new HashMap<>();
claims.put("userId", member.getId());
claims.put("userName", member.getName());
String token = tokenService.createToken(claims);
LoginVo vo = new LoginVo();
vo.setToken(token);
vo.setNickName(member.getName());
return vo;
}
private void saveOrUpdateFamilyMember(FamilyMember member, String phone) {
if(ObjectUtil.notEqual(phone, member.getPhone())){
member.setPhone(phone);
}
if(ObjectUtil.isNotEmpty(member.getId())){
updateById(member);
return;
}
String nickName = DEFAULT_NICKNAME_PREFIX.get((int)(Math.random()*DEFAULT_NICKNAME_PREFIX.size()))
+ StringUtils.substring(member.getPhone(),7);
member.setName(nickName);
save(member);
}Token Validation Interceptor
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if(!(handler instanceof HandlerMethod)) return true;
String token = request.getHeader("authorization");
if(StringUtils.isEmpty(token)) throw new BaseException("认证失败");
Map<String,Object> claims = tokenService.parseToken(token);
if(ObjectUtil.isEmpty(claims)) throw new BaseException("认证失败");
Long userId = MapUtil.get(claims, "userId", Long.class);
if(ObjectUtil.isEmpty(userId)) throw new BaseException("认证失败");
UserThreadLocal.set(userId);
return true;
}
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
UserThreadLocal.remove();
}Summary
openIduniquely identifies a user within this mini‑program; unionId is the unique identifier across all WeChat apps.
Front‑end calls wx.login to obtain a temporary code, which the back‑end exchanges for openId.
Retrieving the phone number requires first obtaining an access token, then calling the phone‑number API.
Java Architect Essentials
Committed to sharing quality articles and tutorials to help Java programmers progress from junior to mid-level to senior architect. We curate high-quality learning resources, interview questions, videos, and projects from across the internet to help you systematically improve your Java architecture skills. Follow and reply '1024' to get Java programming resources. Learn together, grow together.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
