N

Norlcayn的博客

A text-focused Halo theme

  • 首页
  • 文章
  • 默认分类
  • 关于
主页 微信登录接口对接
文章

微信登录接口对接

发表于 2026-01-15 更新于 昨天
作者 Administrator
72~93 分钟 阅读

三方接口

接口文档

在项目中对接任何三方接口,最重要的参考永远都是官方给提供的接口文档

微信官方提供的微信登录接口:小程序登录 / 小程序登录

这个文档中比较关键的部分就是:

文档梳理

  • 功能描述,查阅是否符合咱们业务对接的需求
  • 调用方式,三方接口一般都是https居多,更加安全,包含了请求方式和请求地址
  • 请求参数,接口的入参,重点关注必须要传的参数
  • 返回参数,查看返回的数据结构和内容,是否符合自己的返回预期
  • 错误码,当接口调用失败的时候,可以根据错误码对应的描述修正代码解决问题

API客户端

如果从后端调用三方接口,一样也是用的http请求,只是这次请求是**从后端到另外一个后端服务**,所以需要借助工具才能发起请求

whiteboard_exported_image (28)

HTTP客户端工具有很多

  • HttpClient 体量庞大,jdk11以上支持不好
  • OkHttp 学习成本较高
  • Spring提供的 RestTemplate 后期课程feign会重点讲解
  • 糊涂工具包,使用说明地址:概述 | Hutool

糊涂工具常见方法

  • 准备:在zzyl-common模块中添加单元测试依赖
<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <scope>test</scope>
</dependency>

GET请求

package com.zzyl.nursing.test;

import cn.hutool.http.HttpResponse;
import cn.hutool.http.HttpUtil;
import cn.hutool.http.Method;
import org.junit.Test;

import java.util.HashMap;
import java.util.Map;

public class HttpTest {

    @Test
    public void testGet() {
        String result = HttpUtil.get("https://www.baidu.com");
        System.out.println(result);
    }

    @Test
    public void testGetByParam() {
        // 构建查询参数
        Map<String, Object> paramMap = new HashMap<>();
        paramMap.put("pageNum", 1);
        paramMap.put("pageSize", 5);
        // 分页查询护理项目
        String result = HttpUtil.get("http://localhost:8080/nursing/project/list", paramMap);
        System.out.println(result);
    }

    @Test
    public void testCreateRequest() {
        // 构建查询参数
        Map<String,Object> paramMap = new HashMap<>();
        paramMap.put("pageNum", 1);
        paramMap.put("pageSize", 5);
        // 分页查询护理项目
        HttpResponse response = HttpUtil.createRequest(Method.GET, "http://localhost:8080/nursing/project/list")
                .header("Authorization", "Bearer eyJhbGciOiJIUzUxMiJ9.eyJsb2dpbl91c2VyX2tleSI6ImUyNTgyNzdhLTMxNzAtNGFhNS05MmNhLWRiNjFiNDYyOGYxNCJ9.p8ieeQe4A6lozsxpzTL4Z3JNAS3UgBOVRZckDFnNvJQs8C2I5X5nZVxAQCaUv_0u9yUN-d0FKDCHwtCkMeIdBA")
                .form(paramMap)
                .execute();
        if(response.isOk()){
            System.out.println(response.body());
        }
    }
}

POST请求

@Test
public void testPost() {
    String url = "http://localhost:8080/nursing/project";
    HashMap<String, Object> paramMap = new HashMap<>();
    paramMap.put("name", "护理项目测试");
    paramMap.put("orderNo", 1);
    paramMap.put("unit", "次");
    paramMap.put("price", 10.00);
    paramMap.put("image", "https://yjy-slwl-oss.oss-cn-hangzhou.aliyuncs.com/ae7cf766-fb7b-49ff-a73c-c86c25f280e1.png");
    paramMap.put("nursingRequirement", "无特殊要求");
    paramMap.put("status", 1);
    String result = HttpUtil.post(url, JSONUtil.toJsonStr(paramMap));
    System.out.println(result);
}

@Test
public void testCreatePost() {
    String url = "http://localhost:8080/nursing/project";
    HashMap<String, Object> paramMap = new HashMap<>();
    paramMap.put("name", "护理项目测试");
    paramMap.put("orderNo", 1);
    paramMap.put("unit", "次");
    paramMap.put("price", 10.00);
    paramMap.put("image", "https://yjy-slwl-oss.oss-cn-hangzhou.aliyuncs.com/ae7cf766-fb7b-49ff-a73c-c86c25f280e1.png");
    paramMap.put("nursingRequirement", "无特殊要求");
    paramMap.put("status", 1);

    // 发送请求
    HttpResponse response = HttpUtil.createPost(url).body(JSONUtil.toJsonStr(paramMap))
            .header("Authorization", "Bearer eyJhbGciOiJIUzUxMiJ9.eyJsb2dpbl91c2VyX2tleSI6ImUyNTgyNzdhLTMxNzAtNGFhNS05MmNhLWRiNjFiNDYyOGYxNCJ9.p8ieeQe4A6lozsxpzTL4Z3JNAS3UgBOVRZckDFnNvJQs8C2I5X5nZVxAQCaUv_0u9yUN-d0FKDCHwtCkMeIdBA")
            .execute();
    if(response.isOk()) {
        System.out.println(response.body());
    }

}

微信登录

原型文档: 定位在家属端原型图–>登录模块(若依版本 - 产品原型,密码:FSTI)

在任意需要登录才能显示信息的页面点击时,都会判断当前用户是否登录,如未登录,则进入登录页面

点击微信快速登录,弹出弹窗,需要获取用户手机号进行登录

c026ae59-8ea9-45c5-b0b5-57678039b0c7

微信登录业务逻辑规则:

whiteboard_exported_image (29)

小程序环境搭建

申请小程序AppId - 飞书云文档

基础环境说明

  • 在资料文件夹中找到”微信开发者工具安装包”安装到本地,也可以直接到官网下载:下载地址
  • 在本章资料文件夹中找到”mp-weixin.zip”的压缩包,解压到一个没有中文和空格的目录
  • 打开微信开发者工具,需要使用微信扫描登录
  • 登录成功后,点击“+”号,找到刚才解压的小程序代码的目录,然后打开

46416460-5bdc-4823-8205-d478103f19ce

b304a6ff-50a6-4257-a149-69338a722c3d

打开之后,需要信任该项目,加载完毕之后的效果如下:

b2595247-6427-40da-810f-dfa3167b6ddb

必要配置

  1. 修改请求的路径

1842c356-219a-4538-be76-30d833eda4ad

  1. 需要忽略https请求

0c462abc-1ccc-4452-ba41-b23f885cb3dc

修改小程序环境的APPID

然后打开 –>详情–>基本信息–>修改APPID,改为自己申请的测试号APPID

792022f1-d0f9-4587-a00d-d53e3afd3803

测试前后端

  1. 放行小程序请求

由于小程序端单独有一套认证校验的逻辑,不需要使用spring security框架来做认证和授权,需要修改SecurityConfig类,对小程序中的所有请求放行

4a05c2b2-3ba9-4537-b41d-ca5536f0766e

  1. 到资料中拷贝准备好的类:MemberRoomTypeController,这个类是查询所有房型的
  2. 拷贝到zzyl-nursing-platform模块中,效果如下:

64a69de8-b33a-400f-8cc5-a3928a2a66e5

  1. 重启项目,访问小程序

c1e51f32-f41a-4942-a301-9b4fa2074475

微信登录思路说明

微信官方推荐登录流程:

999e14e1-3a42-4c14-9e5a-07262f5bbbd2

注意点:

  • 前端在小程序中集成微信相关依赖,当用户请求登录的同时,调用wx.login() 获取 临时登录凭证code ,并回传到开发者服务器
  • 后端服务器调用 auth.code2Session 接口,换取 用户唯一标识 OpenID 、 用户在微信开放平台账号下的唯一标识UnionID(若当前小程序已绑定到微信开放平台账号) 和 会话密钥 session_key
  • 开发者服务器可以根据用户标识来生成自定义登录态,用于后续业务逻辑中前后端交互时识别用户身份

表结构说明

从小程序端登录的用户主要有两类,第一类是参观预约的用户,第二类是老人的家人(子女),方便查看老人信息、给老人下单、查看账单、查看合同等

如果是老人的家人,需要跟入住的老人进行绑定,方便后期享受更多服务,所以,咱们需要将在小程序端登录的用户保存下来,保存到一张表中 family_member(家属表)

83cc8080-4d54-432f-bbb6-0970bde5cca8

建表语句:

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='老人家属';

接口说明

这个接口跟平时开发的接口略有不同,因为需要参考微信开发者平台提供的流程来开发

目前,小程序的代码已经开发完了,所以只需要在后台提供接口就可以了

  • 接口路径(已固定):/member/user/login
  • 请求方式(已固定):POST
  • 请求参数:(已固定)
{
  "code": "0e36jkGa1ercRF0Fu4Ia1V3fPD06jkGW", // 临时登录凭证code
  "nickName": "微信用户",
  "phoneCode": "13fe315872a4fb9ed3deee1e5909d5af60dfce7911013436fddcfe13f55ecad3"
}

以上三个参数,都是前端开发人员调用wx.login()方法返回的数据

  • code:临时登录凭证code
  • nickName:微信用户昵称(现在微信统一返回为:微信用户)
  • phoneCode:用户详细信息code,后台根据此code可以获取用户手机号
  • 响应示例
{
  "code": 200,
  "msg": "操作成功",
  "data": {
    "token": "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiLlpb3mn7_lvIDoirE4OTE1IiwiZXhwIjoxNDY1MjI3MTMyOCwidXNlcmlkIjoxfQ.nB6ElZbUywh-yiHDNMJS8WqUpcLWCszVdvAMfySFxIM",
    "nickName": "好柿开花8915"
  },
  "operationTime": null
}

功能实现

实现思路说明

基于微信官方提供的流程图,结合实际业务,最终的实现思路,如下图

8f770969-268a-4793-8cf0-b21100ce95ba

基础代码准备

按照之前的套路,使用若依框架的代码生成功能,来生成代码,

  • 包名:com.zzyl.nursing
  • 模块名:nursing
  • 除了主键之外的Long类型,改为Integer

只拷贝后端代码到idea中

71c554fa-6180-4060-88bc-9244e31420b5

控制层

在zzyl-nursing-platform模块中,把FamilyMemberController移动到member包下,结构如下:

b2b2a163-cf6f-4dc7-a49b-8f3e2e3ba6bb

删除里面所有的方法,新定义一个登录方法,最终代码如下:

package com.zzyl.nursing.controller.member;

import com.zzyl.common.core.controller.BaseController;
import com.zzyl.common.core.domain.AjaxResult;
import com.zzyl.nursing.dto.UserLoginRequestDto;
import com.zzyl.nursing.service.IFamilyMemberService;
import com.zzyl.nursing.vo.LoginVo;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * 老人家属Controller
 * 
 * @author ruoyi
 * @date 2024-09-02
 */
@RestController
@RequestMapping("/member/user")
@Api(tags =  "老人家属相关接口")
public class FamilyMemberController extends BaseController
{
    @Autowired
    private IFamilyMemberService familyMemberService;

    @PostMapping("/login")
    @ApiOperation("小程序登录")
    public AjaxResult login(@RequestBody UserLoginRequestDto userLoginRequestDto) {
        LoginVo loginVo = familyMemberService.login(userLoginRequestDto);
        return success(loginVo);
    }
}

基于接口文档创建对应的DTO和VO

UserLoginRequestDto :

package com.zzyl.nursing.dto;

import io.swagger.annotations.ApiModelProperty;
import lombok.Data;

/**
 * C端用户登录
 */
@Data
public class UserLoginRequestDto {

    @ApiModelProperty("昵称")
    private String nickName;

    @ApiModelProperty("登录临时凭证")
    private String code;

    @ApiModelProperty("手机号临时凭证")
    private String phoneCode;
}

LoginVo:

package com.zzyl.nursing.vo;

import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;

/**
 * LoginVO
 * @author itheima
 */
@Data
@ApiModel(value = "登录对象")
public class LoginVo {

    @ApiModelProperty(value = "JWT token")
    private String token;

    @ApiModelProperty(value = "昵称")
    private String nickName;
}

业务层

8f770969-268a-4793-8cf0-b21100ce95ba

需要调用两次微信开发者平台的接口来获取数据,一般像这种第三方接口的调用,通常都会将所有相关接口的调用都封装到一个单独的业务类中(Service层接口和实现类),使其更通用

分析微信开发者平台的接口,接口地址:

  • 获取用户openId
  • 获取手机号
  • 获取token
微信接口调用-单独封装

新增WechatService接口

package com.zzyl.nursing.service;

public interface WechatService {

    /**
     * 获取openid
     * @param code
     * @return
     */
    public String getOpenid(String code);

    /**
     * 获取手机号
     * @param detailCode
     * @return
     */
    public String getPhone(String detailCode);
}

新增WechatServiceImpl实现类

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";

    // 获取token
    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;

    /**
     * 获取openid
     * @param code
     * @return
     */
    @Override
    public String getOpenid(String code) {

        // 获取公共参数
        Map<String,Object> paramMap = getAppConfig();
        paramMap.put("js_code", code);

        String result = HttpUtil.get(REQUEST_URL, paramMap);
        // 结果是一个map
        JSONObject jsonObject = JSONUtil.parseObj(result);
        // 判断接口响应是否出错
        if(ObjectUtil.isNotEmpty(jsonObject.getInt("errcode"))) {
            throw new RuntimeException(jsonObject.getStr("errmsg"));
        }

        String openid = jsonObject.getStr("openid");

        return openid;
    }

    /**
     * 封装公共参数
     * @return
     */
    private Map<String, Object> getAppConfig() {
        Map<String, Object> paramMap = new HashMap<>();
        paramMap.put("appid", appid);
        paramMap.put("secret", secret);
        return paramMap;
    }

    /**
     * 获取手机号
     * @param detailCode
     * @return
     */
    @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));
        // 是一个map
        JSONObject jsonObject = JSONUtil.parseObj(result);
        // 判断接口响应是否出错
        if(jsonObject.getInt("errcode") != 0) {
            throw new RuntimeException(jsonObject.getStr("errmsg"));
        }

        return jsonObject.getJSONObject("phone_info").getStr("phoneNumber");
    }

    /**
     * 获取token
     * @return
     */
    private String getToken() {

        Map<String, Object> paramMap = getAppConfig();
        // 发起请求
        String result = HttpUtil.get(TOKEN_URL, paramMap);
        // 是一个map
        JSONObject jsonObject = JSONUtil.parseObj(result);
        // 判断接口响应是否出错
        if(ObjectUtil.isNotEmpty(jsonObject.getInt("errcode"))) {
            throw new RuntimeException(jsonObject.getStr("errmsg"));
        }

        String token = jsonObject.getStr("access_token");

        return token;

    }
}

由于上述代码需要读取配置来获取微信小程序的appid和appSecret ,所以需要在application.yml添加对应配置(appid和secret都是在官网中获取)

单元测试:

在zzyl-admin中创建单元测试WechatServiceImplTest

package com.zzyl.test;

import com.zzyl.nursing.service.WechatService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
public class WechatServiceImplTest {

    @Autowired
    private WechatService wechatService;

    @Test
    public void testGetOpenid() {
        String openid = wechatService.getOpenid("0d3fPOkl2VLGbe4IACol2M0x5Q2fPOk5");
        System.out.println(openid);//o3CsK6_C6f4WP9b0AxXNJOkc6q9Q
    }

    @Test
    public void testGetPhone() {
        String phone = wechatService.getPhone("ff1a2f2e61482537ea19c8e57850919051b43ffadbf78cd4fcd3a1707e0c1e14");
        System.out.println(phone);
    }
}

参数获取方式:在小程序端获取参数,输入到单元测试中

c174c4f7-8bc4-4604-b0bb-81bc60d35b5d

微信登录业务开发

在IFamilyMemberService中定义login方法

/**
 * 微信登录
 * @param userLoginRequestDto
 * @return
 */
LoginVo login(UserLoginRequestDto userLoginRequestDto);

实现方法:

@Autowired
private WechatService wechatService;

@Autowired
private TokenService tokenService;

static List<String> DEFAULT_NICKNAME_PREFIX = ListUtil.of(
        "生活更美好",
        "大桔大利",
        "日富一日",
        "好柿开花",
        "柿柿如意",
        "一椰暴富",
        "大柚所为",
        "杨梅吐气",
        "天生荔枝"
);

/**
 * 小程序端登录
 * @param userLoginRequestDto
 * @return
 */
@Override
public LoginVo login(UserLoginRequestDto userLoginRequestDto) {
    // 1.调用微信api,根据code获取openId
    String openId = wechatService.getOpenid(userLoginRequestDto.getCode());

    // 2.根据openId查询用户
    FamilyMember familyMember = getOne(Wrappers.<FamilyMember>lambdaQuery(FamilyMember.class)
            .eq(FamilyMember::getOpenId, openId));

    // 3.如果用户为空,则将openId设置到对象中
    if (ObjectUtil.isEmpty(familyMember)) {
        familyMember = FamilyMember.builder().openId(openId).build();
    }

    // 4.调用微信api获取用户绑定的手机号
    String phone = wechatService.getPhone(userLoginRequestDto.getPhoneCode());

    // 5.保存或修改用户
    saveOrUpdateFamilyMember(familyMember, phone);

    // 6.将用户id存入token,返回
    Map<String, Object> claims = new HashMap<>();
    claims.put("userId", familyMember.getId());
    claims.put("nickName", familyMember.getName());

    String token = tokenService.createToken(claims);
    LoginVo loginVo = new LoginVo();
    loginVo.setToken(token);
    loginVo.setNickName(familyMember.getName());
    return loginVo;
}

/**
 * 保存或修改客户
 * @param member
 * @param phone
 */
private void saveOrUpdateFamilyMember(FamilyMember member, String phone) {

    // 1.判断取到的手机号与数据库中保存的手机号不一样
    if(ObjectUtil.notEqual(phone, member.getPhone())) {
        // 修改手机号
        member.setPhone(phone);
    }
    // 2.判断id存在,则修改
    if (ObjectUtil.isNotEmpty(member.getId())) {
        updateById(member);
        return;
    }
    // 3.id不存在,保存新用户
    // 随机组装昵称,词组+手机号后四位
    String nickName = DEFAULT_NICKNAME_PREFIX.get((int) (Math.random() * DEFAULT_NICKNAME_PREFIX.size()))
            + StringUtils.substring(member.getPhone(), 7);

    member.setName(nickName);
    save(member);
}

其他补充

  1. 在构建Member对象的时候,用到了构建则设计模式,参考文档:构建者设计模式

在FamilyMember添加三个注解,如下图:

61d51934-b6b8-43bc-9cd5-c8e25ea884b9

  1. 使用TokenService生成token

在zzyl-nursing-platform中导入zzyl-framework包

<dependency>
    <groupId>com.zzyl</groupId>
    <artifactId>zzyl-framework</artifactId>
</dependency>

创建JWT的token的时候,可以使用项目中自带的TokenService中的方法createToken该方法的访问权限为private,需要将其设置为public

01752f4c-f905-4fc4-8a4e-c7dbb9875f67

  1. 修改MP自动填充拦截器

由于小程序端的所有请求不走后台的用户,在新增或修改的时候,不需要自动填充创建人和修改人

package com.zzyl.framework.interceptor;

import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import com.zzyl.common.core.domain.model.LoginUser;
import com.zzyl.common.utils.SecurityUtils;
import lombok.SneakyThrows;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

@Component
public class MyMetaObjectHandler implements MetaObjectHandler {

    @Autowired
    private HttpServletRequest request;

    @SneakyThrows
    public boolean isExclude() {
        String requestURI = request.getRequestURI();
        if(requestURI.startsWith("/member")) {
            return true;
        }
        return false;
    }

    @Override
    public void insertFill(MetaObject metaObject) {
        this.strictInsertFill(metaObject, "createTime", Date.class, new Date());
        if(!isExclude()) {
            this.strictInsertFill(metaObject, "createBy", String.class, loadUserId() + "");
        }

    }

    @Override
    public void updateFill(MetaObject metaObject) {
        this.setFieldValByName("updateTime", new Date(), metaObject);
        if(!isExclude()) {
            this.setFieldValByName("updateBy", loadUserId() + "", metaObject);
        }

    }

    /**
     * 获取当前登录人的ID
     *
     * @return
     */
    private static Long loadUserId() {

        // 获取当前登录人的id
        try {
            LoginUser loginUser = SecurityUtils.getLoginUser();
            if (ObjectUtils.isNotEmpty(loginUser)) {
                return loginUser.getUserId();
            }
            return 1L;
        } catch (Exception e) {
            return 1L;
        }
    }
}

校验token

思路分析

通过刚才的流程,用户登录成功以后,会给前端返回一个token,这个token就是来验证用户信息的,当用户点击了小程序中的其他操作(需要登录),则会把token携带到请求头(header)中,方便后台去验证并获取用户信息,简易流程如下

微信登录接口对接2.png

如果想要验证用户的token,可以使用自定义的拦截器实现,整体的流程如下:

微信登录接口对接3.png

Threadlocal的使用

详细请查看文档:ThreadLocal

把资料中提供的工具类UserThreadLocal拷贝到zzyl-common模块中

548f4d6d-7b8c-4ab3-9b8a-4bfb6ce933de

功能实现

在zzyl-framework模块中定义拦截器MemberInterceptor

de52a25d-cffd-4316-9006-fe6de2d56a68

代码如下:

package com.zzyl.framework.interceptor;

import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.ObjectUtil;
import com.zzyl.common.exception.base.BaseException;
import com.zzyl.common.utils.StringUtils;
import com.zzyl.common.utils.UserThreadLocal;
import com.zzyl.framework.web.service.TokenService;
import io.jsonwebtoken.Claims;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@Component
public class MemberInterceptor implements HandlerInterceptor {
    private final TokenService tokenService;

    public MemberInterceptor(TokenService tokenService) {
        this.tokenService = tokenService;
    }

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        // 如果不是Controller层的请求,直接放行
        if (!(handler instanceof HandlerMethod)) {
            return true;
        }

        // 获取header中的token
        String token = request.getHeader("authorization");

        // 如果token为空,响应401,重新登录
        if (StringUtils.isEmpty(token)) {
            response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
            throw new BaseException("认证失败");
        }

        // 解析token
        Claims claims = tokenService.parseToken(token);
        if (ObjectUtil.isEmpty(claims)) {
            response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
            throw new BaseException("认证失败");
        }

        // 获取token中的数据
        Long userId = MapUtil.get(claims, "userId", Long.class);
        if (ObjectUtil.isEmpty(userId)) {
            response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
            throw new BaseException("认证失败");
        }

        // 将用户id放入ThreadLocal中
        UserThreadLocal.set(userId);

        return true;
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        UserThreadLocal.remove();
    }
}

注意,在上述代码中咱们使用了tokenService来解析token,其中的parseToken方法需要修改为public才可以

使拦截器生效

找到ResourcesConfig类,这里面定义了拦截器的生效规则,如下图

b06b08cb-418f-4d03-95db-bb4d05ba0a5f

详细的代码如下:

package com.zzyl.framework.config;

import java.util.concurrent.TimeUnit;

import com.zzyl.framework.interceptor.MemberInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.CacheControl;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import com.zzyl.common.config.RuoYiConfig;
import com.zzyl.common.constant.Constants;
import com.zzyl.framework.interceptor.RepeatSubmitInterceptor;

/**
 * 通用配置
 * 
 * @author ruoyi
 */
@Configuration
public class ResourcesConfig implements WebMvcConfigurer
{
    @Autowired
    private RepeatSubmitInterceptor repeatSubmitInterceptor;

    @Autowired
    private MemberInterceptor membersInterceptor;

    private static final String[] EXCLUDE_PATH_PATTERNS = new String[] {
            "/member/user/login",
            "/member/roomTypes"
    };

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry)
    {
        /** 本地文件上传路径 */
        registry.addResourceHandler(Constants.RESOURCE_PREFIX + "/**")
                .addResourceLocations("file:" + RuoYiConfig.getProfile() + "/");

        /** swagger配置 */
        registry.addResourceHandler("/swagger-ui/**")
                .addResourceLocations("classpath:/META-INF/resources/webjars/springfox-swagger-ui/")
                .setCacheControl(CacheControl.maxAge(5, TimeUnit.HOURS).cachePublic());;
    }

    /**
     * 自定义拦截规则
     */
    @Override
    public void addInterceptors(InterceptorRegistry registry)
    {
        registry.addInterceptor(repeatSubmitInterceptor).addPathPatterns("/**");
        registry.addInterceptor(membersInterceptor).excludePathPatterns(EXCLUDE_PATH_PATTERNS).addPathPatterns("/member/**");
    }

    /**
     * 跨域配置
     */
    @Bean
    public CorsFilter corsFilter()
    {
        CorsConfiguration config = new CorsConfiguration();
        config.setAllowCredentials(true);
        // 设置访问源地址
        config.addAllowedOriginPattern("*");
        // 设置访问源请求头
        config.addAllowedHeader("*");
        // 设置访问源请求方法
        config.addAllowedMethod("*");
        // 有效期 1800秒
        config.setMaxAge(1800L);
        // 添加映射路径,拦截一切请求
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", config);
        // 返回新的CorsFilter
        return new CorsFilter(source);
    }
}

想要测试当前拦截器是否生效,可以随便定义一个控制层代码(需符合拦截要求的),测试代码如下:

package com.zzyl.nursing.controller.member;

import com.zzyl.common.core.controller.BaseController;
import com.zzyl.common.core.domain.R;
import com.zzyl.common.utils.UserThreadLocal;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * 预约信息Controller
 * 
 * @author ruoyi
 */
@RestController
@RequestMapping("/member/reservation")
@Api(tags =  "预约信息相关接口")
public class MemberReservationController extends BaseController
{
    @GetMapping("/cancelled-count")
    @ApiOperation("查询取消预约数量")
    public R<Integer> getCancelledReservationCount() {
        Long userId = UserThreadLocal.getUserId();
        System.out.println("------" + userId);
        return R.ok(1);
    }
}
第三方平台
登录功能 微信 三方接口
许可协议:  CC BY 4.0
分享

相关文章

1月 15, 2026

微信登录接口对接

三方接口 接口文档 在项目中对接任何三方接口,最重要的参考永远都是官方给提供的接口文档 微信官方提供的微信登录接口:小程序登录 / 小程序登录 这个文档中比较关键的部分就是: 功能描述,查阅是否符合咱们业务对接的需求 调用方式,三方接口一般都是https居多,更加安全,包含了请求方式和请求地址 请求

1月 15, 2026

利用OSS实现文件上传

文件上传 文件上传:是指将本地图片、视频、音频等文件上传到服务器,供其他用户浏览或下载的过程 文件上传在项目中应用非常广泛,微博、微信朋友圈都用到了文件上传功能 前端要求: <form action="/upload" method="post" enctype="multipart/form-da

1月 15, 2026

接入华为云IOT平台

华为云IOT平台 在开发智能监测功能之前,必须先熟悉和掌握华为云IoTDA平台的使用及对接 什么是物联网 物联网(英文:Internet of Things,缩写:IoT)起源于传媒领域,是信息科技产业的第三次革命。物联网是指通过信息传感设备,按约定的协议,将任何物体与网络相连接,物体通过信息传播媒

下一篇

若依框架

上一篇

后端鉴权(认证授权)

最近更新

  • Hello Halo
  • 状态机
  • 数据表设计规范
  • 缓存优化
  • 事务失效

热门标签

Halo 异常处理 Redisson 并行 Vue 并发 递归 请求限流 SpringAMQP 类加载器

目录

©2026 Norlcayn的博客. 保留部分权利。

使用 Halo 主题 Chirpy