springboot+redis+mysql快速搭建微信小程序服务端

先贴一份官方流程图

依赖

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-redis</artifactId>
            <version>1.4.5.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.bouncycastle</groupId>
            <artifactId>bcprov-jdk16</artifactId>
            <version>1.46</version>
        </dependency>
        <dependency>
            <groupId>net.minidev</groupId>
            <artifactId>json-smart</artifactId>
            <version>RELEASE</version>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>net.sf.json-lib</groupId>
            <artifactId>json-lib</artifactId>
            <version>2.4</version>
            <classifier>jdk15</classifier>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.16</version>
        </dependency>
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>fastjson</artifactId>
                <version>1.2.58</version>
            </dependency>

客户端获取code

wx.login({
      success: function(res) {
        var code = res.code; //登录凭证
})

request发送code

wx.request({
                url: 'http://localhost:8080/decodeUserInfo', //自己的服务接口地址
                method: 'post',
                header: {
                  'content-type': 'application/x-www-form-urlencoded'
                },
                data: {
                  encryptedData: res.encryptedData,
                  iv: res.iv,
                  code: code
                },)
服务端使用登录凭证 code 向微信服务器获取 session_key 和 openid
@SuppressWarnings({"unchecked", "rawtypes"})
    @RequestMapping(value = "/decodeUserInfo", method = RequestMethod.POST)
    @ResponseBody

    public Map decodeUserInfo(String encryptedData, String iv, String code) {
        Map map = new HashMap();
        String skey;
        // 登录凭证不能为空
        if (code == null || code.length() == 0) {
            map.put("status", 0);
            map.put("msg", "code 不能为空");
            return map;
        }

        // 小程序唯一标识 (在微信小程序管理后台获取)
        //TODO:小程序唯一标识
        String wxspAppid = "";
        // 小程序的 app secret (在微信小程序管理后台获取)
        //TODO:wxspSecret
        String wxspSecret = "";
        // 授权(必填)
        String grant_type = "authorization_code";

         1、向微信服务器 使用登录凭证 code 获取 session_key 和 openid
         
        // 请求参数
        String params = "appid=" + wxspAppid + "&secret=" + wxspSecret + "&js_code=" + code + "&grant_type="
                + grant_type;
        // 发送请求
        String sr = HttpRequest.sendGet("https://api.weixin.qq.com/sns/jscode2session", params);
        // 解析相应内容(转换成json对象)
        JSONObject json = JSONObject.parseObject(sr);
        // 获取会话密钥(session_key)
        String session_key = json.get("session_key").toString();
        // 用户的唯一标识(openid)
        String openid = (String) json.get("openid");

         2、对encryptedData加密数据进行AES解密 
        try {
            String result = AesCbcUtil.decrypt(encryptedData, session_key, iv, "UTF-8");
            if (null != result && result.length() > 0) {
                map.put("status", 1);
                map.put("msg", "解密成功");
                JSONObject userInfoJSON = JSONObject.parseObject(result);
                // 获取用户数据
                String nickName = (String) userInfoJSON.get("nickName");
                Integer gender = (Integer) userInfoJSON.get("gender");
                String city = (String) userInfoJSON.get("city");
                String province = (String) userInfoJSON.get("province");
                String country = (String) userInfoJSON.get("country");
                String avatarUrl = (String) userInfoJSON.get("avatarUrl");
                // 与公众号可以获取unionId
                String unionId = openid + 1;
                Map userInfo = new HashMap();
                userInfo.put("nickName", nickName);
                userInfo.put("gender", gender);
                userInfo.put("city", city);
                userInfo.put("province", province);
                userInfo.put("country", country);
                // 自定义登录态skey
                skey = UUID.randomUUID().toString();
                JSONObject sessionObj = new JSONObject();
                sessionObj.put("openId", openid);
                sessionObj.put("sessionKey", session_key);
                // 以skey为键,openId和sessionKey为值缓存
                redisTemplate.opsForValue().set(skey, sessionObj.toJSONString(), 240, TimeUnit.HOURS);
                userInfo.put("skey", skey);
                Author author = authorRepository.findByOpenId(openid);
                if (author == null) {
                    // 如果用户不存在,添加
                    author = new Author();
                }
                // 存在更新
                authorAdd(author, openid, nickName, gender, city, province, country, avatarUrl, unionId, skey);
                map.put("userInfo", userInfo);
            } else {
                map.put("status", 0);
                map.put("msg", "解密失败");
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println(map);
        return map;
    }
添加用户
private void authorAdd(Author author, String openId,
                           String nickName,
                           Integer gender,
                           String city,
                           String province,
                           String country,
                           String avatarUrl,
                           String unionId,
                           String skey) {
        author.setNickName(nickName);
        author.setOpenId(openId);
        author.setGender(gender);
        author.setCity(city);
        author.setProvince(province);
        author.setCountry(country);
        author.setAvatarUrl(avatarUrl);
        author.setUnionId(unionId);
        author.setItem(null);
        author.setSkey(skey);
        authorRepository.save(author);
    }
Author
public class Author {

    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Id
    private Integer id;
    private String openId;
    private String nickName;
    private Integer gender;
    private String city;
    private String province;
    private String country;
    private String avatarUrl;
    private String unionId;
    private String item;
    private String skey;
}

 

http请求类

public class HttpRequest {

    public static void main(String[] args) {
        //发送 GET 请求
        String s=HttpRequest.sendGet("http://v.qq.com/x/cover/kvehb7okfxqstmc.html?vid=e01957zem6o", "");
        System.out.println(s);

    /**
     * 向指定URL发送GET方法的请求
     *
     * @param url
     *            发送请求的URL
     * @param param
     *            请求参数,请求参数应该是 name1=value1&name2=value2 的形式。
     * @return URL 所代表远程资源的响应结果
     */
    public static String sendGet(String url, String param) {
        String result = "";
        BufferedReader in = null;
        try {
            String urlNameString = url + "?" + param;
            URL realUrl = new URL(urlNameString);
            // 打开和URL之间的连接
            URLConnection connection = realUrl.openConnection();
            // 设置通用的请求属性
            connection.setRequestProperty("accept", "*/*");
            connection.setRequestProperty("connection", "Keep-Alive");
            connection.setRequestProperty("user-agent",
                    "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
            // 建立实际的连接
            connection.connect();
            // 获取所有响应头字段
            Map<String, List<String>> map = connection.getHeaderFields();
            // 遍历所有的响应头字段
            for (String key : map.keySet()) {
                System.out.println(key + "--->" + map.get(key));
            }
            // 定义 BufferedReader输入流来读取URL的响应
            in = new BufferedReader(new InputStreamReader(
                    connection.getInputStream()));
            String line;
            while ((line = in.readLine()) != null) {
                result += line;
            }
        } catch (Exception e) {
            System.out.println("发送GET请求出现异常!" + e);
            e.printStackTrace();
        }
        // 使用finally块来关闭输入流
        finally {
            try {
                if (in != null) {
                    in.close();
                }
            } catch (Exception e2) {
                e2.printStackTrace();
            }
        }
        return result;
    }

解密类

public class AesCbcUtil {
    static {
        //BouncyCastle是一个开源的加解密解决方案,主页在http://www.bouncycastle.org/
        Security.addProvider(new BouncyCastleProvider());
    }
    /**
     * AES解密
     *
     * @param data           //密文,被加密的数据
     * @param key            //秘钥
     * @param iv             //偏移量
     * @param encodingFormat //解密后的结果需要进行的编码
     * @return
     * @throws Exception
     */
    public static String decrypt(String data, String key, String iv, String encodingFormat) throws Exception {
//        initialize();

        //被加密的数据
        byte[] dataByte = Base64.decodeBase64(data);
        //加密秘钥
        byte[] keyByte = Base64.decodeBase64(key);
        //偏移量
        byte[] ivByte = Base64.decodeBase64(iv);

        try {
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding");

            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, encodingFormat);
                return result;
            }
            return null;
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (NoSuchPaddingException e) {
            e.printStackTrace();
        } catch (InvalidParameterSpecException e) {
            e.printStackTrace();
        } catch (InvalidKeyException e) {
            e.printStackTrace();
        } catch (InvalidAlgorithmParameterException e) {
            e.printStackTrace();
        } catch (IllegalBlockSizeException e) {
            e.printStackTrace();
        } catch (BadPaddingException e) {
            e.printStackTrace();
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return null;
    }
}

客户端后续登录使用自定义登录态skey

if (skey) {
      console.log("skey存在");
      // 检查 session_key 是否过期
      wx.checkSession({
        // session_key 有效(未过期)
        success: function() {
          console.log("session_key 有效(未过期)")
          that.doSkey();
          // 业务逻辑处理
        },
        // session_key 过期
        fail: function() {
          // session_key过期,重新登录
          console.log("session_key过期")
          that.doLogin();
        }
      });
    } else {
      console.log("skey不存在,重新授权")
    }

//
oSkey: function() {
    wx.request({
      url: 'http://localhost:8080/skey', //自己的服务接口地址
      method: 'post',
      header: {
        'content-type': 'application/x-www-form-urlencoded'
      },
      data: {
        skey: skey
      },
      success: function(res) {
        console.log("前台skey存在,校验skey后返回值" + JSON.stringify(res.data));
        //如果后台缓存已经不在了
        if (!res.data.success) {
          console.log("如果后台缓存已经失效了,小程序清空token和userinfo和openid");
          wx.removeStorageSync("skey");
          doLogin();
        } else {
          console.log("后台缓存中的信息也存在,直接返回");
          // 进入主页
          wx.redirectTo({
            url: '/pages/index/index'
          })
        }
      }
    });
  }

服务端验证

@SuppressWarnings({"unchecked", "rawtypes"})
    @RequestMapping(value = "/skey", method = RequestMethod.POST)
    @ResponseBody
    public Map authorSkye(String skey){
        Map map=new HashMap();
        if(StringUtils.isNotBlank(skey)){
            // skey未失效,返回业务数据,更新缓存时间
            Author author = authorRepository.findBySkey(skey);
            // 业务数据item
            String item = author.getItem();
            System.out.println("skey过期时间"+redisTemplate.getExpire(skey));
            redisTemplate.expire(skey,240,TimeUnit.HOURS);
            map.put("success",1);
            map.put("msg","后台skey未失效");
            map.put("item",item);
            return map;
        }else {
            map.put("msg","后台skey失效");
           return map;
        }
    }
}

数据库结构

openId,session_key不应作为明文传递

项目地址:

java服务端

小程序客户端

 

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值