Mobile Development 16 min read

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.

Java Architect Essentials
Java Architect Essentials
Java Architect Essentials
Master WeChat Mini‑Program Login: From Code to Token in 10 Steps

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

openId

uniquely 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.

JavaMiniProgramSpringBootloginTokenWeChatOpenID
Java Architect Essentials
Written by

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.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.