微信小程序登录小实例

首先知道两个最基本的属性:codeopenId,这两个是做什么的呢?

code和openId

openId是永久的,是微信对该用户该小程序的唯一标识,也就是微信小程序官方数据库里存的userId
code就是前端由前端获取的一个一次性的,有时限的用来获取openId的东西

设计思路:
首先由前端工作人员将这个code发到后端,
后端再根据这个一次性的code,调用微信的api获取该小程序的openId。每个小程序的openId是不一样的。
然后获得了openId后,从本地库里查询用户数据,如果有就返回token

前置准备

了解完后,由于openId是该小程序唯一标识,所以我们本地的数据库也得加个字段openId(这里我写成了wechatId),用来装官方库里的openId。匹配成功后才证明本地这个用户已经有数据了
请添加图片描述

然后因为要用到微信官方的接口,所以pom文件要引入依赖

<dependency>
            <groupId>com.github.binarywang</groupId>
            <artifactId>weixin-java-miniapp</artifactId>
            <version>4.3.0</version>
        </dependency>

写一个配置类

package com.haoyu.framework.modules.wechat.config;

import cn.binarywang.wx.miniapp.api.WxMaService;
import cn.binarywang.wx.miniapp.api.impl.WxMaServiceImpl;
import cn.binarywang.wx.miniapp.bean.WxMaKefuMessage;
import cn.binarywang.wx.miniapp.bean.WxMaSubscribeMessage;
import cn.binarywang.wx.miniapp.config.WxMaConfig;
import cn.binarywang.wx.miniapp.config.impl.WxMaDefaultConfigImpl;
import cn.binarywang.wx.miniapp.config.impl.WxMaRedisConfigImpl;
import cn.binarywang.wx.miniapp.message.WxMaMessageHandler;
import cn.binarywang.wx.miniapp.message.WxMaMessageRouter;
import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.common.bean.result.WxMediaUploadResult;
import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.common.error.WxRuntimeException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.annotation.Scheduled;
import redis.clients.jedis.JedisPool;

import javax.annotation.PostConstruct;
import java.io.File;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * @author <a href="https://github.com/binarywang">Binary Wang</a>
 */
@Slf4j
@Configuration
@EnableConfigurationProperties(WxMaProperties.class)
public class WxMaConfiguration {

    private final WxMaProperties properties;

    @Autowired
    public WxMaConfiguration(WxMaProperties properties) {
        this.properties = properties;
    }

    @Autowired
    private RedisTemplate redisTemplate;

    private static final Map<String, WxMaMessageRouter> routers = Maps.newHashMap();
    private static WxMaService maServices;

    public static WxMaService getMaService() {
        return maServices;
    }

    public static Map<String, WxMaMessageRouter> getRouters() {
        return routers;
    }


    @PostConstruct
    public void init() {
        WxMaProperties.Config config = this.properties.getConfigs().get(0);
        WxMaDefaultConfigImpl configStorage = new WxMaDefaultConfigImpl();
        configStorage.setAppid(config.getAppid());
        configStorage.setSecret(config.getSecret());
        configStorage.setToken(config.getToken());
        configStorage.setAesKey(config.getAesKey());
        configStorage.setMsgDataFormat(config.getMsgDataFormat());
        WxMaService service = new WxMaServiceImpl();
        service.setWxMaConfig(configStorage);
        routers.put(config.getAppid(), this.newRouter(service));
        maServices = service;

//        List<WxMaProperties.Config> configs = this.properties.getConfigs();
//        if (configs == null) {
//            throw new WxRuntimeException("未添加小程序相关配置");
//        }
//
//        maServices = configs.stream()
//            .map(a -> {
//                WxMaDefaultConfigImpl config = new WxMaDefaultConfigImpl();
                WxMaDefaultConfigImpl config = new WxMaRedisConfigImpl(new JedisPool());
//                // 使用上面的配置时,需要同时引入jedis-lock的依赖,否则会报类无法找到的异常
//                config.setAppid(a.getAppid());
//                config.setSecret(a.getSecret());
//                config.setToken(a.getToken());
//                config.setAesKey(a.getAesKey());
//                config.setMsgDataFormat(a.getMsgDataFormat());
//
//                WxMaService service = new WxMaServiceImpl();
//                service.setWxMaConfig(config);
//                routers.put(a.getAppid(), this.newRouter(service));
//
//                WxMaConfig wxMaConfig =  service.getWxMaConfig();
//                log.info("maServices-->WxMaConfig-->getAppid-->"+config.getAppid());
//                log.info("maServices-->WxMaConfig-->getAccessToken-->"+config.getAccessToken());
//                System.out.println("maServices-->WxMaConfig-->getAppid-->"+config.getAppid());
//                System.out.println("maServices-->WxMaConfig-->getAccessToken-->"+config.getAccessToken());
//                System.out.println("----------------------------");
//
//                return service;
//            }).collect(Collectors.toMap(s -> s.getWxMaConfig().getAppid(), a -> a));
    }

    private WxMaMessageRouter newRouter(WxMaService service) {
        final WxMaMessageRouter router = new WxMaMessageRouter(service);
        router
            .rule().handler(logHandler).next()
            .rule().async(false).content("订阅消息").handler(subscribeMsgHandler).end()
            .rule().async(false).content("文本").handler(textHandler).end()
            .rule().async(false).content("图片").handler(picHandler).end()
            .rule().async(false).content("二维码").handler(qrcodeHandler).end();
        return router;
    }

    private final WxMaMessageHandler subscribeMsgHandler = (wxMessage, context, service, sessionManager) -> {
        service.getMsgService().sendSubscribeMsg(WxMaSubscribeMessage.builder()
            .templateId("此处更换为自己的模板id")
            .data(Lists.newArrayList(
                new WxMaSubscribeMessage.MsgData("keyword1", "339208499")))
            .toUser(wxMessage.getFromUser())
            .build());
        return null;
    };

    private final WxMaMessageHandler logHandler = (wxMessage, context, service, sessionManager) -> {
        log.info("收到消息:" + wxMessage.toString());
        service.getMsgService().sendKefuMsg(WxMaKefuMessage.newTextBuilder().content("收到信息为:" + wxMessage.toJson())
            .toUser(wxMessage.getFromUser()).build());
        return null;
    };

    private final WxMaMessageHandler textHandler = (wxMessage, context, service, sessionManager) -> {
        service.getMsgService().sendKefuMsg(WxMaKefuMessage.newTextBuilder().content("回复文本消息")
            .toUser(wxMessage.getFromUser()).build());
        return null;
    };

    private final WxMaMessageHandler picHandler = (wxMessage, context, service, sessionManager) -> {
        try {
            WxMediaUploadResult uploadResult = service.getMediaService()
                .uploadMedia("image", "png",
                    ClassLoader.getSystemResourceAsStream("tmp.png"));
            service.getMsgService().sendKefuMsg(
                WxMaKefuMessage
                    .newImageBuilder()
                    .mediaId(uploadResult.getMediaId())
                    .toUser(wxMessage.getFromUser())
                    .build());
        } catch (WxErrorException e) {
            e.printStackTrace();
        }

        return null;
    };

    private final WxMaMessageHandler qrcodeHandler = (wxMessage, context, service, sessionManager) -> {
        try {
            final File file = service.getQrcodeService().createQrcode("123", 430);
            WxMediaUploadResult uploadResult = service.getMediaService().uploadMedia("image", file);
            service.getMsgService().sendKefuMsg(
                WxMaKefuMessage
                    .newImageBuilder()
                    .mediaId(uploadResult.getMediaId())
                    .toUser(wxMessage.getFromUser())
                    .build());
        } catch (WxErrorException e) {
            e.printStackTrace();
        }

        return null;
    };

//    @Scheduled(fixedDelay = 2*3000*1000)
    public void getAccessToken() {
        //利用hutool发送https请求
        Map<String, Object> paramMap = new HashMap<String, Object>(3);
        paramMap.put("appid", this.properties.getConfigs().get(0).getAppid());
        paramMap.put("secret", this.properties.getConfigs().get(0).getSecret());
        paramMap.put("grant_type", this.properties.getConfigs().get(0).getGrantType());
        // 利用Hutool工具包的HttpUtil
        Object result = HttpUtil.get("https://api.weixin.qq.com/cgi-bin/token?", paramMap);
        JSONObject jsonObject = JSONUtil.parseObj(result);
        redisTemplate.delete("ipas_wx_access_token");
        redisTemplate.opsForValue().set("ipas_wx_access_token",(String)jsonObject.get("access_token"));
    }

}

读取配置文件

package com.haoyu.framework.modules.wechat.config;

import java.util.List;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;

import lombok.Data;

/**
 * @author <a href="https://github.com/binarywang">Binary Wang</a>
 */
@Data
@ConfigurationProperties(prefix = "wx.miniapp")
public class WxMaProperties {

    private List<Config> configs;

    @Data
    public static class Config {
        /**
         * 设置微信小程序的appid
         */
        private String appid;

        /**
         * 设置微信小程序的Secret
         */
        private String secret;

        /**
         * 设置微信小程序消息服务器配置的token
         */
        private String token;

        /**
         * 设置微信小程序消息服务器配置的EncodingAESKey
         */
        private String aesKey;

        /**
         * 消息格式,XML或者JSON
         */
        private String msgDataFormat;

        private String grantType;
    }

}

登录功能

接下来就是写接口,前端发送code,后端根据code获取openId,然后生成token返回给前端,这里的token生成用的是sa-token的技术

 /**
     * 小程序登录(示例)
     *
     * @param xcxCode 小程序code
     * @return 结果
     */
    @PostMapping("/xcxLogin")
    public R<Map<String, Object>> xcxLogin(@NotBlank(message = "{xcx.code.not.blank}") String xcxCode) {
        Map<String, Object> ajax = new HashMap<>();
        // 生成令牌
        String token = sysLoginService.xcxLogin(xcxCode);
        ajax.put(Constants.TOKEN, token);
        return R.ok(ajax);
    }
public String xcxLogin(String xcxCode) {
        // xcxCode 为 小程序调用 wx.login 授权后获取
       
        String openid = "";
        // 调用官方接口根据code获得openId
        openid = remoteUserService.getOpenIdByXCXCode(xcxCode);
//       根据openId从本地数据库获取用户
        XcxLoginUser userInfo = remoteUserService.getUserInfoByOpenid(openid);

        // 生成token
        LoginHelper.loginByDevice(userInfo, DeviceType.XCX);

        recordLogininfor(userInfo.getUsername(), Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success"));
        return StpUtil.getTokenValue();
    }

根据code获取openId代码

@Override
    public String getOpenIdByXCXCode(String code)  {
        // 根据code调微信api获得openId
        try{
            WxMaJscode2SessionResult session = WxMaConfiguration.getMaService().getUserService().getSessionInfo(code);
            if(session != null && session.getOpenid() != null && "".equals(session.getOpenid())){
                return session.getOpenid();
            }
        }catch (WxErrorException e) {
            // 打印错误日志
            log.error("微信登录异常:"+e.toString());
            return "";
        }

        return "";
    }

注册功能

注册的思路是怎么样呢?分两种情况,
情况1:数据库里已经有该手机号用户,但是没有绑上微信唯一的openId,此时数据库中将openId 给update进指定的用户里
情况2:数据库里已经没有该手机号用户,但是没有绑上微信唯一的openId,此时新增一条数据库用户记录

注册完后,需要自动登录,返回token(这里用的springSecurity)到前端

为了整洁性,实例并没有包括自动填充微信昵称的功能
java代码

@Override
public R bind(WxLoginInfo wxLoginInfo, HttpServletRequest request) {

	String error = null;

	String mobileCode = wxLoginInfo.getMobileCode();
	String openId = wxLoginInfo.getOpenId();
	String sessionKey = null;
	String mobilePhone = null;
	if(StringUtils.isBlank(mobileCode)){
		// 打印错误日志
		log.error("微信绑定错误:code参数不存在");
		return R.fail(401,"code参数不存在");
	}
	if(StringUtils.isBlank(openId)){
		// 打印错误日志
		log.error("微信绑定错误:openId参数不存在");
		return R.fail(401,"openId参数不存在");
	}


	if(StringUtils.isBlank(sessionKey)){
		error = "参数错误";
	}else{

		//通过微信接口获取手机信息
		try {
			WxMaPhoneNumberInfo phoneNoInfo = wxMaService.getUserService().getNewPhoneNoInfo(mobileCode);
			mobilePhone = phoneNoInfo.getPhoneNumber();
		} catch (WxErrorException e) {
			e.printStackTrace();
		}
		if(StringUtils.isBlank(mobilePhone)){
			// 打印错误日志
			log.error("获取手机号失败");
			return R.fail("获取手机号失败");
		}


		//根据手机号获取登录用户信息
		LoginUser loginUserInfo = loginUserService.getByMobilePhone(mobilePhone);
		if(loginUserInfo != null){
			//存在用户,自动绑定
			// 将wechatId给updata到用户上
			WeixinAuthenticationToken authRequest = new WeixinAuthenticationToken(loginUserInfo.getUsername());
			Authentication authentication = authenticationManager.authenticate(authRequest);
			SecurityContextHolder.getContext().setAuthentication(authentication);
			LoginUser loginUser = (LoginUser)authentication.getPrincipal();
			//保证ThreadLocal里面能获取到登录人信息
			ThreadContext.bind(loginUser);
			//记录登陆日志
			loginLogService.createLoginLog(loginUser,request);

			//用户绑定微信号(openId)
			User user = new User();
			user.setId(loginUser.getId());
			user.setWechatId(openId);

			userService.update(user);

			// 根据 LoginUser 生成 jwtToken
			String jwt = authUtils.createJWTByLoginUser(loginUser, false);
			JwtResponse jwtResponse = new JwtResponse(jwt,loginUser);
			return R.success().setData(jwtResponse);
		}else{
			//用户不存在,自动注册
			// 新增用户至用户表
			User user = new User();
			user.setUserName(mobilePhone);//账号
			user.setMobilePhone(mobilePhone);
			user.setWechatId(openId);
			user.setWechatNickname("微信用户");

			R r = userService.weixinRegisterByMobilePhoneAndPass(user);
			//注册成功
			if(r.isSuccess()){
				// 自动登录
				WeixinAuthenticationToken authRequest = new WeixinAuthenticationToken(user.getUserName());
				Authentication authentication = authenticationManager.authenticate(authRequest);
				SecurityContextHolder.getContext().setAuthentication(authentication);
				LoginUser loginUser = (LoginUser)authentication.getPrincipal();
				//保证ThreadLocal里面能获取到登录人信息
				ThreadContext.bind(loginUser);
				//记录登陆日志
				loginLogService.createLoginLog(loginUser,request);

				// 根据 LoginUser 生成 jwtToken
				String jwt = authUtils.createJWTByLoginUser(loginUser, false);
				JwtResponse jwtResponse = new JwtResponse(jwt,loginUser);
				return R.success().setData(jwtResponse);
			}else{
				error = "注册用户失败";
			}
		}
	}


	// 打印错误日志
	if(!StringUtils.isBlank(error)){
		log.error("微信绑定错误:"+error);
	}


	return StringUtils.isBlank(error) ? R.success() : R.fail(error);
}

前端发送的数据:

{
	// 小程序唯一标识用户id
    "openId": "94",
    
    // 微信获取的加密信息,发送到java后台根据可以根据openId和微信code获取微信昵称等等信息
    //"userEncryptedData": "amet id ut esse magna",
    //"userIv": "non",
    
    // 小程序一次性code
    "mobileCode": "80"
}

附上解密用户信息和手机信息,可以用来自动填入昵称

// 解密用户信息
            //encryptedData和vi都是前端 wx.getUserInfo 接口获取
            WxMaUserInfo wxUserInfo = wxMaService.getUserService().getUserInfo(sessionKey, wxLoginInfo.getUserEncryptedData(), wxLoginInfo.getUserIv());

            //解密手机信息
            //encryptedData和vi都是前端 getPhoneNumber 接口获取
           WxMaPhoneNumberInfo phoneNoInfo = wxMaService.getUserService().getPhoneNoInfo(sessionKey, wxLoginInfo.getMobileEncryptedData(), wxLoginInfo.getMobileIv());

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值