一、前期准备
1、将小程序参数配置在application-dev.yml文件中
sky: # 微信小程序参数 wechat: appid: wxb************ secret: d6***********
2、在 application.yml文件中配置jwt令牌参数和引用小程序的配置参数
sky: jwt: # 设置jwt签名加密时使用的秘钥 user-secret-key: itcast # 设置jwt过期时间 user-ttl: 7200000 # 设置前端传递过来的令牌名称 user-token-name: authentication wechat: appid: ${sky.redis.appid} secret: ${sky.wechat.secret}
3、创建微信参数配置文件 WeChatProperties
package com.sky.properties; import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; @Component @ConfigurationProperties(prefix = "sky.wechat") @Data public class WeChatProperties { private String appid; //小程序的appid private String secret; //小程序的秘钥 private String mchid; //商户号 private String mchSerialNo; //商户API证书的证书序列号 private String privateKeyFilePath; //商户私钥文件 private String apiV3Key; //证书解密的密钥 private String weChatPayCertFilePath; //平台证书 private String notifyUrl; //支付成功的回调地址 private String refundNotifyUrl; //退款成功的回调地址 }
4、创建生成jwt令牌所需参数类 JwtProperties
package com.sky.properties; import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; @Component @ConfigurationProperties(prefix = "sky.jwt") @Data public class JwtProperties { /** * 管理端员工生成jwt令牌相关配置 */ private String adminSecretKey; private long adminTtl; private String adminTokenName; /** * 用户端微信用户生成jwt令牌相关配置 */ private String userSecretKey; private long userTtl; private String userTokenName; }
5、实现生成jwt令牌的工具类 JwtUtil
package com.sky.utils; import io.jsonwebtoken.Claims; import io.jsonwebtoken.JwtBuilder; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; import java.nio.charset.StandardCharsets; import java.util.Date; import java.util.Map; public class JwtUtil { /** * 生成jwt * 使用Hs256算法, 私匙使用固定秘钥 * * @param secretKey jwt秘钥 * @param ttlMillis jwt过期时间(毫秒) * @param claims 设置的信息 * @return */ public static String createJWT(String secretKey, long ttlMillis, Mapclaims) { // 指定签名的时候使用的签名算法,也就是header那部分 SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256; // 生成JWT的时间 long expMillis = System.currentTimeMillis() + ttlMillis; Date exp = new Date(expMillis); // 设置jwt的body JwtBuilder builder = Jwts.builder() // 如果有私有声明,一定要先设置这个自己创建的私有的声明,这个是给builder的claim赋值,一旦写在标准的声明赋值之后,就是覆盖了那些标准的声明的 .setClaims(claims) // 设置签名使用的签名算法和签名使用的秘钥 .signWith(signatureAlgorithm, secretKey.getBytes(StandardCharsets.UTF_8)) // 设置过期时间 .setExpiration(exp); return builder.compact(); } /** * Token解密 * * @param secretKey jwt秘钥 此秘钥一定要保留好在服务端, 不能暴露出去, 否则sign就可以被伪造, 如果对接多个客户端建议改造成多个 * @param token 加密后的token * @return */ public static Claims parseJWT(String secretKey, String token) { // 得到DefaultJwtParser Claims claims = Jwts.parser() // 设置签名的秘钥 .setSigningKey(secretKey.getBytes(StandardCharsets.UTF_8)) // 设置需要解析的jwt .parseClaimsJws(token).getBody(); return claims; } }
6、创建发起HttpClient请求的工具类 HttpClientUtil
package com.sky.utils; import com.alibaba.fastjson.JSONObject; import org.apache.http.NameValuePair; import org.apache.http.client.config.RequestConfig; import org.apache.http.client.entity.UrlEncodedFormEntity; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpPost; import org.apache.http.client.utils.URIBuilder; import org.apache.http.entity.StringEntity; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; import org.apache.http.message.BasicNameValuePair; import org.apache.http.util.EntityUtils; import java.io.IOException; import java.net.URI; import java.util.ArrayList; import java.util.List; import java.util.Map; /** * Http工具类 */ public class HttpClientUtil { static final int TIMEOUT_MSEC = 5 * 1000; /** * 发送GET方式请求 * @param url * @param paramMap * @return */ public static String doGet(String url,MapparamMap){ // 创建Httpclient对象 CloseableHttpClient httpClient = HttpClients.createDefault(); String result = ""; CloseableHttpResponse response = null; try{ URIBuilder builder = new URIBuilder(url); if(paramMap != null){ for (String key : paramMap.keySet()) { builder.addParameter(key,paramMap.get(key)); } } URI uri = builder.build(); //创建GET请求 HttpGet httpGet = new HttpGet(uri); //发送请求 response = httpClient.execute(httpGet); //判断响应状态 if(response.getStatusLine().getStatusCode() == 200){ result = EntityUtils.toString(response.getEntity(),"UTF-8"); } }catch (Exception e){ e.printStackTrace(); }finally { try { response.close(); httpClient.close(); } catch (IOException e) { e.printStackTrace(); } } return result; } /** * 发送POST方式请求 * @param url * @param paramMap * @return * @throws IOException */ public static String doPost(String url, Map paramMap) throws IOException { // 创建Httpclient对象 CloseableHttpClient httpClient = HttpClients.createDefault(); CloseableHttpResponse response = null; String resultString = ""; try { // 创建Http Post请求 HttpPost httpPost = new HttpPost(url); // 创建参数列表 if (paramMap != null) { List paramList = new ArrayList(); for (Map.Entry param : paramMap.entrySet()) { paramList.add(new BasicNameValuePair(param.getKey(), param.getValue())); } // 模拟表单 UrlEncodedFormEntity entity = new UrlEncodedFormEntity(paramList); httpPost.setEntity(entity); } httpPost.setConfig(builderRequestConfig()); // 执行http请求 response = httpClient.execute(httpPost); resultString = EntityUtils.toString(response.getEntity(), "UTF-8"); } catch (Exception e) { throw e; } finally { try { response.close(); } catch (IOException e) { e.printStackTrace(); } } return resultString; } /** * 发送POST方式请求 * @param url * @param paramMap * @return * @throws IOException */ public static String doPost4Json(String url, Map paramMap) throws IOException { // 创建Httpclient对象 CloseableHttpClient httpClient = HttpClients.createDefault(); CloseableHttpResponse response = null; String resultString = ""; try { // 创建Http Post请求 HttpPost httpPost = new HttpPost(url); if (paramMap != null) { //构造json格式数据 JSONObject jsonObject = new JSONObject(); for (Map.Entry param : paramMap.entrySet()) { jsonObject.put(param.getKey(),param.getValue()); } StringEntity entity = new StringEntity(jsonObject.toString(),"utf-8"); //设置请求编码 entity.setContentEncoding("utf-8"); //设置数据类型 entity.setContentType("application/json"); httpPost.setEntity(entity); } httpPost.setConfig(builderRequestConfig()); // 执行http请求 response = httpClient.execute(httpPost); resultString = EntityUtils.toString(response.getEntity(), "UTF-8"); } catch (Exception e) { throw e; } finally { try { response.close(); } catch (IOException e) { e.printStackTrace(); } } return resultString; } private static RequestConfig builderRequestConfig() { return RequestConfig.custom() .setConnectTimeout(TIMEOUT_MSEC) .setConnectionRequestTimeout(TIMEOUT_MSEC) .setSocketTimeout(TIMEOUT_MSEC).build(); } }
二、业务实现
1、实现userController
package com.sky.controller.user; import com.sky.constant.JwtClaimsConstant; import com.sky.dto.UserLoginDTO; import com.sky.entity.User; import com.sky.properties.JwtProperties; import com.sky.result.Result; import com.sky.service.UserService; import com.sky.utils.JwtUtil; import com.sky.vo.UserLoginVO; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import java.util.HashMap; import java.util.Map; @RestController @RequestMapping("/user/user") public class userController { @Autowired UserService userService; @Autowired JwtProperties jwtProperties; /*微信登录*/ @PostMapping("/login") public Result wxLogin(@RequestBody UserLoginDTO loginDTO){ User user = userService.wxLogin(loginDTO); //登录成功后,生成jwt令牌 Mapclaims = new HashMap<>(); claims.put("userId",user.getId()); String token = JwtUtil.createJWT( jwtProperties.getUserSecretKey(), jwtProperties.getUserTtl(), claims ); /*保存用户数据返回给前端*/ UserLoginVO build = UserLoginVO.builder() .id(user.getId()) .openid(user.getOpenid()) .token(token) .build(); return Result.success(build); } }
2、实现 service接口的实现类
package com.sky.service.impl; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import com.sky.dto.UserLoginDTO; import com.sky.entity.User; import com.sky.exception.LoginFailedException; import com.sky.mapper.UserMapper; import com.sky.properties.WeChatProperties; import com.sky.service.UserService; import com.sky.utils.HttpClientUtil; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.time.LocalDateTime; import java.util.HashMap; import java.util.Map; @Service public class UserServiceImpl implements UserService { //微信登录接口 public static final String WX_LOGIN = "https://api.weixin.qq.com/sns/jscode2session"; @Autowired WeChatProperties weChatProperties; @Autowired UserMapper userMapper; /*微信登录*/ @Override public User wxLogin(UserLoginDTO loginDTO) { /*获取openid*/ String openid = getOpenid(loginDTO.getCode()); //判断openid是否为空,如果为空抛出异常 if(openid == null){ throw new LoginFailedException("登录失败"); } //判断当前用户是否为新用户 User user = userMapper.wxLogin(openid); //如果是新用户,自动完成注册 if(user == null){ user = User.builder() .openid(openid) .createTime(LocalDateTime.now()) .build(); userMapper.insert(user); } //返回这个用户对象 return user; }//获取openid方法 private String getOpenid(String code){ //调用微信接口服务,获取当前用户的openid Mapmap = new HashMap<>(); /*微信官方固定参数*/ map.put("appid",weChatProperties.getAppid()); map.put("secret",weChatProperties.getSecret()); map.put("js_code",code); map.put("grant_type","authorization_code"); /*获取返回的id*/ String json = HttpClientUtil.doGet(WX_LOGIN, map); /*解析数据拿到openid*/ JSONObject jsonObject = JSON.parseObject(json); String openid = jsonObject.getString("openid"); return openid; } }
3、实现Mapper层
package com.sky.mapper; import com.sky.entity.User; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Select; @Mapper public interface UserMapper { /*微信登录*/ @Select("select * from sky_take_out.user where openid = #{openid}") User wxLogin(String openid); /*插入新用户数据*/ void insert(User user); }
4、在插入新用户后需要返回主键id,所以这里使用动态sql
id="insert" useGeneratedKeys="true" keyProperty="id"> insert into user (openid, name, phone, sex, id_number, avatar, create_time) values (#{openid},#{name},#{phone},#{sex},#{idNumber},#{avatar},#{createTime})
三、小程序端测试登录接口