微信小程序与微信网页开发

微信web开发只需要code值,但是小程序需要一个code值,一个encryptData,一个iv

首先先看图

微信小程序与微信网页开发

 

箭头部分为微信给我们的,就是前端需要传过来的。

步骤为:

  1. 小程序客户端调用wx.login,回调里面包含js_code。
  2. 然后将js_code发送到服务器A(开发者服务器),服务器A向微信服务器发起请求附带js_code、appId、secretkey和grant_type参数,以换取用户的openid和session_key(会话**)。
  3. 服务器A拿到session_key后,生成一个随机数我们叫3rd_session,以3rdSessionId为key,以session_key + openid为value缓存到redis或memcached中;因为微信团队不建议直接将session_key在网络上传输,由开发者自行生成唯一键与session_key关联。其作用是:
    1. 将3rdSessionId返回给客户端,维护小程序登录态。
    2. 通过3rdSessionId找到用户session_key和openid。
  4. 客户端拿到3rdSessionId后缓存到storage,
  5. 通过wx.getUserIinfo可以获取到用户敏感数据encryptedData 。
  6. 客户端将encryptedData、3rdSessionId和偏移量一起发送到服务器A
  7. 服务器A根据3rdSessionId从缓存中获取session_key
  8. 在服务器A使用AES解密encryptedData,从而实现用户敏感数据解密

 

重点在6、7、8三个环节。
AES解密三个参数:

  • 密文 encryptedData
  • ** aesKey
  • 偏移向量 iv

概念性的东西就这些,下面看代码

首先前端需要传给我们的东西就是三个一个code值,一个encryptData,一个iv,

先写方法

 

package com.everest.academy.util;


import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.util.encoders.Base64;

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.URL;
import java.net.URLConnection;
import java.security.AlgorithmParameters;
import java.security.Security;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;


/**
* @ClassName XcxUtils
* @Description 微信小程序方法
* @Author 田野
* @Data 22:14
* @Version 1.0
**/
@Slf4j
public class XcxUtils {


/**
    * 获取微信小程序 session_key 和 openid
    *
    * @author zhy
    * @param code
    *            调用微信登陆返回的Code
    * @return
    */
   public static JSONObject getSessionKeyOropenid(String code, String appid, String secret) {

String requestUrl = "https://api.weixin.qq.com/sns/jscode2session"; // 请求地址

       Map<String, String> requestUrlParam = new HashMap<String, String>();
       requestUrlParam.put("appid", appid); // 开发者设置中的appId
       requestUrlParam.put("secret", secret); // 开发者设置中的appSecret
       requestUrlParam.put("js_code", code); // 小程序调用wx.login返回的code
       requestUrlParam.put("grant_type", "authorization_code"); // 默认参数

       // 发送post请求读取调用微信 https://api.weixin.qq.com/sns/jscode2session
       // 接口获取openid用户唯一标识
       JSONObject jsonObject = JSON.parseObject(sendPost(requestUrl, requestUrlParam));
        System.out.println(jsonObject);
       return jsonObject;

   }

/**
    * 解密用户敏感数据获取用户信息
    *
    * @author zhy
    * @param sessionKey
    *            数据进行加密签名的**
    * @param encryptedData
    *            包括敏感数据在内的完整用户信息的加密数据
    * @param iv
    *            加密算法的初始向量
    * @return
    */
   public static JSONObject getUserInfo(String encryptedData, String sessionKey, String iv) {
encryptedData=encryptedData.replace(" ", "+");
       sessionKey=sessionKey.replace(" ", "+");
       iv=iv.replace(" ", "+");
       // 被加密的数据
       byte[] dataByte = Base64.decode(encryptedData);
       // 加密秘钥
       byte[] keyByte = Base64.decode(sessionKey);
       // 偏移量
       byte[] ivByte = Base64.decode(iv);
       try {
// 如果**不足16位,那么就补足. 这个if 中的内容很重要
           int base = 16;
           if (keyByte.length % base != 0)
{
int groups = keyByte.length / base + (keyByte.length % base != 0 ? 1 : 0);
               byte[] temp = new byte[groups * base];
               Arrays.fill(temp, (byte) 0);
               System.arraycopy(keyByte, 0, temp, 0, keyByte.length);
               keyByte = temp;
           }
// 初始化
           Security.addProvider(new BouncyCastleProvider());
           Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding", "BC");
           SecretKeySpec spec = new SecretKeySpec(keyByte, "AES");
           AlgorithmParameters parameters = AlgorithmParameters.getInstance("AES");
           parameters.init(new IvParameterSpec(ivByte));
           cipher.init(Cipher.DECRYPT_MODE, spec, parameters);// 初始化
           byte[] resultByte = cipher.doFinal(dataByte);
           if (null != resultByte && resultByte.length > 0)
{
String result = new String(resultByte, "UTF-8");
               return JSON.parseObject(result);
           }
} catch (Exception e) {
log.error(e.getMessage(), e);
       }
return null;
   }

/**
    * 向指定 URL 发送POST方法的请求
    *
    * @param url
    *            发送请求的 URL
    * @param
    *
    * @return 所代表远程资源的响应结果
    */
   public static String sendPost(String url, Map<String, ?> paramMap) {
PrintWriter out = null;
       BufferedReader in = null;
       String result = "";

       String param = "";
       Iterator<String> it = paramMap.keySet().iterator();

       while (it.hasNext()) {
String key = it.next();
           param += key + "=" + paramMap.get(key) + "&";
       }
try {
URL realUrl = new URL(url);
           // 打开和URL之间的连接
           URLConnection conn = realUrl.openConnection();
           // 设置通用的请求属性
           conn.setRequestProperty("accept", "*/*");
           conn.setRequestProperty("connection", "Keep-Alive");
           conn.setRequestProperty("Accept-Charset", "utf-8");
           conn.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
           // 发送POST请求必须设置如下两行
           conn.setDoOutput(true);
           conn.setDoInput(true);
           // 获取URLConnection对象对应的输出流
           out = new PrintWriter(conn.getOutputStream());
           // 发送请求参数
           out.print(param);
           // flush输出流的缓冲
           out.flush();
           // 定义BufferedReader输入流来读取URL的响应
           in = new BufferedReader(new InputStreamReader(conn.getInputStream(), "UTF-8"));
           String line;
           while ((line = in.readLine()) != null) {
result += line;
           }
} catch (Exception e) {
log.error(e.getMessage(), e);
       }
// 使用finally块来关闭输出流、输入流
       finally {
try {
if (out != null)
{
out.close();
               }
if (in != null)
{
in.close();
               }
} catch (IOException ex) {
ex.printStackTrace();
           }
}
return result;
   }

}

注解写的很明白的,应该没什么看不懂。

之后就是service层

package com.everest.academy.service.impl;

import com.alibaba.fastjson.JSONObject;
import com.everest.academy.business.dto.LoginUserDTO;
import com.everest.academy.business.dto.WechatTokenDto;
import com.everest.academy.framework.exception.ResourceIsNullException;
import com.everest.academy.framework.pojo.User;
import com.everest.academy.persistence.mapper.UserMapper;
import com.everest.academy.service.WechatService;
import com.everest.academy.util.WechatUtil;
import com.everest.academy.util.XcxUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.Date;

/**
* @ClassName WechatServiceImpl
* @Description 微信sgervice
* @Author 田野
* @Data 20:25
* @Version 1.0
**/
@Slf4j
@Service
public class WechatServiceImpl implements WechatService {
@Autowired
   UserMapper userMapper;
 
@Override
   public LoginUserDTO getUserByCode(String code, String encryptedData, String iv)throws Exception{
log.info("传进来的值"+encryptedData+"另一个"+iv);
       JSONObject shopAddress = null;
       if (StringUtils.isNotEmpty(code)) {
String appid = "填自己的";
           String secret = "填自己的";
           shopAddress = XcxUtils.getSessionKeyOropenid(code, appid, secret);
       }
assert shopAddress != null;
       String openId = shopAddress.getString("openid");
       String sessionKey = shopAddress.getString("session_key");
       log.info("session_key为:"+sessionKey);
       JSONObject user1 = XcxUtils.getUserInfo(encryptedData, sessionKey, iv);
       //user这里根据用户openId查询是否有这个用户。
       User user=userMapper.findByOpenId(openId);
       LoginUserDTO loginUserDTO=new LoginUserDTO();
       if (user!=null){
log.info("用户的状态"+user.getState());
           if (user.getState()==1){
//有的话,直接就进入 ,直接将信息返回给前端
               loginUserDTO.setId(user.getId());
               loginUserDTO.setOpenId(openId);
               return loginUserDTO;
           }
throw new Exception("无法登录,账号被冻结");
       }
//没有的话,创建该学生的信息,然后再传给部分数据给前端
       User newUser=new User();
       newUser.setOpenId(openId);
       assert user1 != null;
       newUser.setAddress(user1.getString("city"));
       newUser.setHeadImgUrl(user1.getString("avatarUrl"));
       newUser.setName(user1.getString("nickName"));
       newUser.setState(1);
       newUser.setBeans(0);
       newUser.setBinding(0);
       newUser.setCreate_at(new Date().getTime());
       newUser.setCreate_by("系统创建");
       userMapper.insert(newUser);
       User user2=userMapper.findByOpenId(openId);
       LoginUserDTO loginUserDTO1=new LoginUserDTO();
       loginUserDTO1.setId(user2.getId());
       log.info("id的值"+newUser.getId());
       loginUserDTO1.setOpenId(newUser.getOpenId());
       loginUserDTO1.setBinding(newUser.getState());
       return loginUserDTO1;
   }
}

也写的挺清楚的,通过openId判断是否有这个用户

controller

 

package com.everest.academy.controller;

import com.everest.academy.business.vo.ResponseVo;
import com.everest.academy.framework.exception.ResourceIsNullException;
import com.everest.academy.service.WechatService;
import com.everest.academy.util.ResultUtil;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

import java.util.Map;

/**
* @ClassName WechatController
* @Description 微信登录验证
* @Author 田野
* @Data 15:56
* @Version 1.0
**/
@Slf4j
@Api(tags = "WechatController",description = "微信开发API")
@RestController
@RequestMapping("/a/home")
@Validated
public class WechatController {
@Autowired
   WechatService wechatService;
   @ApiOperation(value = "微信登录验证",notes = "通过获取的codeId值登录")
@PostMapping("/{code}")
public ResponseVo WechatLogin( @PathVariable("code") String code,
                                  @RequestParam("encryptedData") String encryptedData,@RequestParam("iv") String iv)throws Exception{
log.info("传进来的未"+encryptedData +"iv为" + iv);
       return ResultUtil.success("微信登录成功",wechatService.getUserByCode(code,encryptedData,iv));
   }
}

就有一点问题

测试的时候,每次传参都读不到+号,每次都把我的+号弄掉。

微信小程序与微信网页开发

所以这里运用了一个replace,完美解决

微信网页开发,通过codeId得到access_token,通过access_token和openid获取用户基本信息

package com.everest.academy.util;

import com.everest.academy.business.dto.LoginUserDTO;
import com.everest.academy.business.dto.WechatTokenDto;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;


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

/**
 * @ClassName WechatUtil
 * @Description TODO
 * @Author 田野
 * @Data 19:25
 * @Version 1.0
 **/
@Slf4j
public class WechatUtil {
    public final static String APPID = "自己的";
    public final static String APPSECRET = "自己的";
    /**
     * 获取请求用户信息的access_token
     * @param code
     * @return
     */
    public static WechatTokenDto getUserInfoAccessToken(String code) {
        JsonObject object = null;
        WechatTokenDto wechatTokenDto=new WechatTokenDto();
        Map<String, String> data = new HashMap();
        try {
            String url = String.format("https://api.weixin.qq.com/sns/oauth2/access_token?appid=%s&secret=%s&code=%s&grant_type=authorization_code",
                    APPID, APPSECRET, code);
            log.info("request accessToken from url: {}", url);
            CloseableHttpClient httpClient =HttpClients.createDefault();
            HttpGet httpGet = new HttpGet(url);
            HttpResponse httpResponse = httpClient.execute(httpGet);
            HttpEntity httpEntity = httpResponse.getEntity();
            String tokens = EntityUtils.toString(httpEntity, "utf-8");
            Gson token_gson = new Gson();
            object = token_gson.fromJson(tokens, JsonObject.class);
            log.info("request accessToken success. [result={}]", object);
            wechatTokenDto.setOpenid(object.get("openid").toString().replaceAll("\"", ""));
            wechatTokenDto.setAccess_token(object.get("access_token").toString().replaceAll("\"", ""));
            wechatTokenDto.setRefresh_token(object.get("refresh_token").toString().replaceAll("\"", ""));
            wechatTokenDto.setScope(object.get("scope").toString().replaceAll("\"", ""));
            wechatTokenDto.setExpires_in(Integer.parseInt(object.get("expires_in").toString().replaceAll("\"","")));
//            data.put("openid", object.get("openid").toString().replaceAll("\"", ""));
//            data.put("access_token", object.get("access_token").toString().replaceAll("\"", ""));
//            data.put("expires_in",object.get("expires_in").toString().replaceAll("\"",""));
//            data.put("refresh_token",object.get("refresh_token").toString().replaceAll("\"", ""));
//            data.put("scope",object.get("scope").toString().replaceAll("\"", ""));
        } catch (Exception ex) {
            log.error("fail to request wechat access token. [error={}]", ex);
        }
        return wechatTokenDto;
    }

    /**
     * 通过access_token和openid获取用户基本信息
     * @param accessToken
     * @param openId
     * @return
     */
    public static LoginUserDTO getUserInfo(String accessToken, String openId) {
        LoginUserDTO loginUserDTO=new LoginUserDTO();
        String url = "https://api.weixin.qq.com/sns/userinfo?access_token=" + accessToken + "&openid=" + openId + "&lang=zh_CN";
        log.info("request user info from url: {}", url);
        JsonObject userInfo = null;
        try {
            CloseableHttpClient httpClient =HttpClients.createDefault();
            HttpGet httpGet = new HttpGet(url);
            HttpResponse httpResponse = httpClient.execute(httpGet);
            HttpEntity httpEntity = httpResponse.getEntity();
            String response = EntityUtils.toString(httpEntity, "utf-8");
            Gson token_gson = new Gson();
            userInfo = token_gson.fromJson(response, JsonObject.class);
            log.info("get userinfo success. [result={}]", userInfo);
            loginUserDTO.setOpenId(userInfo.get("openid").toString().replaceAll("\"", ""));
            loginUserDTO.setCountry(userInfo.get("country").toString().replaceAll("\"", ""));
            loginUserDTO.setProvince(userInfo.get("province").toString().replaceAll("\"", ""));
            loginUserDTO.setCity(userInfo.get("city").toString().replaceAll("\"", ""));
            loginUserDTO.setHeadImgUrl(userInfo.get("headimgurl").toString().replaceAll("\"", ""));
            loginUserDTO.setSex(Integer.valueOf(userInfo.get("sex").toString().replaceAll("\"", "")));
            loginUserDTO.setNickname(userInfo.get("nickname").toString().replaceAll("\"", ""));
//            data.put("openid", userInfo.get("openid").toString().replaceAll("\"", ""));
//            data.put("nickname", userInfo.get("nickname").toString().replaceAll("\"", ""));
//            data.put("city", userInfo.get("city").toString().replaceAll("\"", ""));
//            data.put("province", userInfo.get("province").toString().replaceAll("\"", ""));
//            data.put("country", userInfo.get("country").toString().replaceAll("\"", ""));
//            data.put("headimgurl", userInfo.get("headimgurl").toString().replaceAll("\"", ""));
        } catch (Exception ex) {
            log.error("fail to request wechat user info. [error={}]", ex);
        }
        return loginUserDTO;
    }


}
@Override
    public LoginUserDTO wechat(String code)throws ResourceIsNullException{
        //调用封装的微信方法,通过code值得到wechatTokenDto
        WechatTokenDto wechatTokenDto=WechatUtil.getUserInfoAccessToken(code);
        String accessToken = wechatTokenDto.getAccess_token();//得到accessToken
        String openId = wechatTokenDto.getOpenid();//得到openId
        //user这里根据用户openId查询是否有这个用户。
        User user=userMapper.findByOpenId(openId);
        LoginUserDTO loginUserDTO=WechatUtil.getUserInfo(accessToken,openId);
        log.info("用户信息"+user);
        if(user!=null){
            //有的话,直接就进入 ,直接将信息返回给前端
//            loginUserDTO.setOpenId(openId);
            loginUserDTO.setId(user.getId());
            loginUserDTO.setBinding(user.getState());
            return loginUserDTO;
        }else {
            //没有的话,创建该学生的信息,然后再传给部分数据给前端
            User newUser=new User();
            newUser.setOpenId(loginUserDTO.getOpenId());
            newUser.setAddress(loginUserDTO.getCity());
            newUser.setHeadImgUrl(loginUserDTO.getHeadImgUrl());
            newUser.setName(loginUserDTO.getNickname());
            log.info("用户昵称"+loginUserDTO.getNickname());
            newUser.setState(1);
            newUser.setBeans(0);
            newUser.setBinding(0);
            newUser.setCreate_at(new Date().getTime());
            newUser.setCreate_by("系统创建");
            userMapper.insert(newUser);
            LoginUserDTO loginUserDTO1=new LoginUserDTO();
            loginUserDTO1.setId(newUser.getId());
            log.info("id的值"+newUser.getId());
            loginUserDTO1.setOpenId(newUser.getOpenId());
            loginUserDTO1.setBinding(newUser.getState());
            return loginUserDTO1;
        }
    }

基本就是这个样子,后续看下能不能写的更加详细。