第二节 微信扫码登录的SpringBoot后端开发

亮子 2022-05-13 15:39:02 17436 0 0 0

1、配置文件

application.properties文件的配置如下:

## 微信扫码登录
site.web.appid=wx3dfe520b2a
site.web.appkey=e50740f2e8f3608367
site.web.return-url=http://www.xxx.com/wxLoginReturn

2、创建配置类

package com.shenmazong.shenmablogserver.config;

import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

/**
 * @Description: 全站配置类
 * @Author david
 * @Date 2021/5/5 9:35
 * @Copyright www.shenmazong.com
 * @Version V1.0.0
 */
@Data
@Component
public class SiteConfig {

    @Value("${site.app.id}")
    private String siteAppId;

    @Value("${site.shenma.token}")
    private String siteShenmaToken;

    @Value("${site.code.url}")
    private String siteCodeUrl;

    @Value("${site.web.appid}")
    private String siteWebAppId;

    @Value("${site.web.appkey}")
    private String siteWebAppKey;

    @Value("${site.web.return-url}")
    private String siteWebReturnUrl;
}

3、跳转到扫码登录页面

package com.shenmazong.cms.controller;

import com.shenmazong.cms.config.SiteConfig;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.net.URLEncoder;

/**
 * @author 军哥
 * @version 1.0
 * @description: 微信登录控制
 * @date 2022/5/13 23:02
 */

@Controller
@Slf4j
public class WxLoginController {

    @Autowired
    SiteConfig siteConfig;

    /**
     * @description 获取授权code接口,调用这个接口后,会跳转到一个整屏只有二维码的页面
     * @author 军哥
     * @date 2022/5/13 23:06
     * @version 1.0
     */
    @GetMapping(value = "/wxLogin")
    public void wxLogin(HttpServletResponse response) throws IOException {
        String redirect_url = URLEncoder.encode(siteConfig.getSiteWebReturnUrl(), "UTF-8");
        String url = String.format("https://open.weixin.qq.com/connect/qrconnect?appid=%s&redirect_uri=%s&response_type=code&scope=snsapi_login&state=STATE#wechat_redirect",
                siteConfig.getSiteWebAppId(),
                redirect_url
        );

        log.info(url);
        response.sendRedirect(url);
        return;
    }
}

4、扫码授权成功后跳转页面

/**
     * @description 微信登录授权成功后的回调地址
     * @author 军哥
     * @date 2022/5/13 23:13
     * @version 1.0
     */
    // https://shenmazong.com/login?code=021Sz3000jNiON15Sl000wO44m1Sz30d&state=STATE
    // https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code
    @RequestMapping(value = "/wxLoginReturn")
    public void wxLoginReturn(HttpServletRequest request, HttpServletResponse response) throws IOException {
        String code = request.getParameter("code");
        String state = request.getParameter("state");
        log.info("wxLoginReturn,code="+code);

        String siteCodeUrl = String.format("https://api.weixin.qq.com/sns/oauth2/access_token?appid=%s&secret=%s&code=%s&grant_type=authorization_code",
                siteConfig.getSiteWebAppId(),
                siteConfig.getSiteWebAppKey(),
                code);
        ResponseEntity<String> responseResultResponseEntity = restTemplate.getForEntity(siteCodeUrl, String.class);
        System.out.println("code,"+responseResultResponseEntity.getStatusCodeValue());
        if(responseResultResponseEntity.getStatusCodeValue() != 200) {
            String url = "https://www.shenmazong.com/login/code/"+responseResultResponseEntity.getStatusCodeValue();
            log.info(url);
            response.sendRedirect(url);
            return;
        }
        /**
         {
         "access_token":"56_httrHDFvN9O5OHJFwAVmdC_snbP5gKTvL7TLdV-Wq60nevbvWKvAdRaPDj2QKa0zBpsTltqc_RZ5M1ArBbtztedUzEq4OKZxrsgh7wWWq9s",
         "expires_in":7200,
         "refresh_token":"56_AEHhOEnjGWQWNnrUBazv4keTv8GLTpURB0G4umER3uv-ZUn4xkuxrDaZn2A8Lc036M4pHCOBOdLW8CxTrjQ74CEz_Bm6PZpQxHSotK4Zdmo",
         "openid":"o5d0V6GMavGavfWyPYdXnGICxG6s",
         "scope":"snsapi_login",
         "unionid":"ozZlq5k-q3dmpxZGwIX5HYwfnp4M"
         }
         */
        System.out.println(responseResultResponseEntity.getBody());
        WxLoginTokenVo wxLoginTokenVo = new ObjectMapper().readValue(responseResultResponseEntity.getBody(), WxLoginTokenVo.class);
        String access_token = wxLoginTokenVo.getAccess_token();
        String openid = wxLoginTokenVo.getOpenid();
        String refresh_token = wxLoginTokenVo.getRefresh_token();
        String unionid = wxLoginTokenVo.getUnionid();

        // 自动登录
        TbUser one = iTbUserService.getOne(new QueryWrapper<TbUser>().lambda().eq(TbUser::getOpenId, openid).last("limit 1"));
        if(one != null) {
            // 设置登录标志
            redisTemplate.opsForValue().set("app_login_code_"+one.getUserCode(), ""+one.getUserCode(), 5, TimeUnit.MINUTES);
            //
            String url = "https://www.shenmazong.com/login/code/"+one.getUserCode();
            log.info(url);
            response.sendRedirect(url);
            return;
        }

        // 获取用户信息
        // https://api.weixin.qq.com/sns/userinfo?access_token=access_token&openid=openid
        siteCodeUrl = String.format("https://api.weixin.qq.com/sns/userinfo?access_token=%s&openid=%s",
                access_token, openid);
        ResponseEntity<String> userinfoEntity = restTemplate.getForEntity(siteCodeUrl, String.class);
        System.out.println("code,"+userinfoEntity.getStatusCodeValue());

        if(userinfoEntity.getStatusCodeValue() != 200) {
            // 登录失败
            String url = "https://www.shenmazong.com/login/code/" + userinfoEntity.getStatusCodeValue();
            log.info(url);
            response.sendRedirect(url);
            return;
        }
        System.out.println(userinfoEntity.getBody());

        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        WxUserInfoVo wxUserInfoVo = objectMapper.readValue(userinfoEntity.getBody(), WxUserInfoVo.class);

        String nickname = wxUserInfoVo.getNickname();
        Integer sex = wxUserInfoVo.getSex();
        String city = wxUserInfoVo.getCity();
        String headimgurl = wxUserInfoVo.getHeadimgurl();
        String country = wxUserInfoVo.getCountry();
        unionid = wxUserInfoVo.getUnionid();

        // 获取userCode
        String userCode = "";
        while (true)
        {
            userCode = RandomUtils.random().randomIntCode(9);
            if(RandomUtils.isMagicCode(Long.valueOf(userCode))) {
                continue;
            }
            //
            TbUser user = iTbUserService.getOne(
                    new QueryWrapper<TbUser>().lambda().eq(TbUser::getUserCode, userCode)
            );
            if(user == null) {
                break;
            }
        }

        // 新建用户
        TbUser tbUser = new TbUser();
        tbUser.setUserId(IdWorker.getId());
        tbUser.setOpenId(openid);
        tbUser.setUnionId(unionid);
        tbUser.setUserCode(Long.valueOf(userCode));

        tbUser.setNickName(nickname);
        tbUser.setUserGender(Integer.valueOf(sex));
        tbUser.setCity(city);
        tbUser.setUserAvatar(headimgurl);
        tbUser.setUnionId(unionid);

        iTbUserService.save(tbUser);

        // 设置登录标志
        redisTemplate.opsForValue().set("app_login_code_"+userCode, userCode, 5, TimeUnit.MINUTES);

        String url = "https://www.shenmazong.com/login/code/"+tbUser.getUserCode();
        log.info(url);
        response.sendRedirect(url);
        return;
    }

在上述代码中,我们需要有一些补充类。

  • 进行http访问的RestTemplate模板
package com.shenmazong.shenmablogserver.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.OkHttp3ClientHttpRequestFactory;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.web.client.RestTemplate;

import java.nio.charset.StandardCharsets;
import java.util.List;

/**
 * @Description: 参码配置类
 * @Author david
 * @Date 2021/5/5 11:39
 * @Copyright www.shenmazong.com
 * @Version V1.0.0
 */

@Configuration
public class ShenmaConfig {

    @Bean
    public RestTemplate restTemplate() {
        RestTemplate restTemplate = new RestTemplate();
        //消息转换器列表
        List<HttpMessageConverter<?>> messageConverters = restTemplate.getMessageConverters();
        //配置消息转换器StringHttpMessageConverter,并设置utf-8
        messageConverters.set(1,
                new StringHttpMessageConverter(StandardCharsets.UTF_8));//支持中文字符集,默认ISO-8859-1,支持utf-8

        return restTemplate;
    }
}

这个配置类有两个作用,一个是用来作为http的工具类,另一个作用是解决页面访问返回数据乱码的问题。

  • access_token返回值的实体类
package com.shenmazong.shenmablogserver.vo;

import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * @author 军哥
 * @version 1.0
 * @description: https://api.weixin.qq.com/sns/oauth2/access_token?appid=%s&secret=%s&code=%s&grant_type=authorization_code
 * @date 2022/5/10 18:59
 */

/**
 {
     "access_token":"56_httrHDFvN9O5OHJFwAVmdC_snbP5gKTvL7TLdV-Wq60nevbvWKvAdRaPDj2QKa0zBpsTltqc_RZ5M1ArBbtztedUzEq4OKZxrsgh7wWWq9s",
     "expires_in":7200,
     "refresh_token":"56_AEHhOEnjGWQWNnrUBazv4keTv8GLTpURB0G4umER3uv-ZUn4xkuxrDaZn2A8Lc036M4pHCOBOdLW8CxTrjQ74CEz_Bm6PZpQxHSotK4Zdmo",
     "openid":"o5d0V6GMavGavfWyPYdXnGICxG6s",
     "scope":"snsapi_login",
     "unionid":"ozZlq5k-q3dmpxZGwIX5HYwfnp4M"
 }
 */

@Data
@NoArgsConstructor
public class WxLoginTokenVo {
    private String access_token;
    private Integer expires_in;
    private String refresh_token;
    private String openid;
    private String scope;
    private String unionid;
}
  • userinfo返回值实体类
package com.shenmazong.cms.vo;

import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * @author 军哥
 * @version 1.0
 * @description: https://api.weixin.qq.com/sns/userinfo?access_token=%s&openid=%s
 * @date 2022/5/10 19:22
 */


//{
//        "openid":"o5d0V6GMavGavfWyPYdXnGICxG6s",
//        "nickname":""­,
//        "sex":0,
//        "language":"",
//        "city":"",
//        "province":"",
//        "country":"",
//        "headimgurl":"https:\/\/thirdwx.qlogo.cn\/mmopen\/vi_32\/Q0j4TwGTfTITyu0M64BWia147QUOaWPkcwMSqmxpQRVKSI3KBO2oeGaot0Cko2RcOWPEP38dQfZbKr511TpvLnw\/132",
//        "privilege":[],
//        "unionid":"ozZlq5k-q3dmpxZGwIX5HYwfnp4M"
//}
@Data
@NoArgsConstructor
public class WxUserInfoVo {
    private String openid;
    private String nickname;
    private Integer sex;
    private String language;
    private String city;
    private String province;
    private String country;
    private String headimgurl;
    private String unionid;
}