文章目录
一、前置知识
获取小程序userid就要先将小程序提交审核->发布小程序->企微后台在自建应用中绑定小程序
1. 阅读 企业微信小程序开发文档
https://developer.work.weixin.qq.com/document/path/92426
2. 企业微信小程序登录流程
3. 微信小程序区别
区分企业微信小程序登录流程和微信小程序登录流程不同点。
不同点:
1.企业微信小程序登录流程和微信小程序登录流程不一样,获取的用户信息也不一样。
2.企业微信小程序登录流程需要将企业微信小程序先发布上传吗,审核通过后,将企业微信小程序与企业应用进行绑定后,获取该应用的agentSecret和corpId去获取AccessToken,然后,通过AccessToken和code获取用户信息,最后通过获取的userid在调用通讯录接口获取该用户的详细信息。
3.开发阶段:可以使用企业微信提供的测试应用的agentSecret和corpId
二、前端部分
2.1. 调用登录接口
执行流程会从前端到后端:
前端调用腾讯企业微信登录接口api
登录api开发文档:https://developer.work.weixin.qq.com/document/path/91506
uniapp项目小程序项目写法:
// 企业微信下程序登录的方法
login() {
wx.qy.login({
success: function(res) {
// 调用成功,会返回code
console.log("res", res)
}
})
},
返回的报文:
{code: "VFUfreDIauQszKQy5mZgRC3aaMijd7DM-xnOcZW3LVE", errMsg: "qy__login:ok"}
小程序项目写法:
//app.js
App({
onLaunch: function() {
wx.qy.login({
success: function(res) {
if (res.code) {
//发起网络请求
wx.request({
url: 'https://test.com/onLogin',
data: {
code: res.code
}
})
} else {
console.log('登录失败!' + res.errMsg)
}
}
});
}
})
2.2. 请求后端接口
携带code调用后端接口
这里使用封装好的axios和user接口api,直接使用即可
utils/request.js,这个js主要封装了axios,对请求统一管理,请求url以及数据拼接以及响应处理(这个工具类需要和后端的返回对象JsonData.java对上,下面会贴出来代码)。
const BASE_URL = 'http://api.ant-qywx.com:9900';
function request({ url, data, method }) {
return new Promise((resolve, reject) => {
// 发起网络请求
uni.request({
url: BASE_URL + url,
data,
method,
success: ({ data }) => {
console.log("data",data)
// 响应成功,获取数据,解析数据
if (data.success) {
resolve(data);
} else {
// 响应失败,给用户提示
uni.showToast({
title: data.message,
icon: 'none',
mask: true,
duration: 3000,
});
reject(data.message);
}
},
fail: (error) => {
reject(error);
},
complete: () => {
// 关闭加载
uni.hideLoading();
},
});
});
}
export default request;
引入api
<script>
// 引用用户接口api 调用后端登录接口
import {loginAuth} from '../../api/user.js';
</script>
编写登录方法调用企业微信登录接口
methods: {
// 企业微信下程序登录的方法
login() {
wx.qy.login({
success: function(res) {
console.log("res", res)
loginAuth(res).then((response) => {
console.log("response", response)
});
}
})
}
}
api/user.js这里js,作用是管理用户相关接口api
import request from '../utils/request';
/**
* 微信用户授权登录,携带appid和code参数,调用后端接口获取Openid
*/
export function loginAuth(data) {
return request({
url: '/mini/login',
data: {
code: data.code,
},
});
}
2.3. 项目源码
https://gitee.com/gblfy/qywx-inner-java-api
三、后端部分
3.1. yml配置
application.yml
server:
port: 9900
qywx:
mini:
agentSecret: i5t-rh8bXeNCgihcYPrG9ZPpWkivzPJ69sv570osk6I
corpId: ww17f8d10783494584
3.2. 获取用户信息接口
Controller
package com.gblfy.qywxin.controller;
import com.gblfy.qywxin.service.QywxInnerMiniService;
import com.gblfy.qywxin.utils.JWTUtils;
import com.gblfy.qywxin.vo.JsonData;
import com.gblfy.qywxin.vo.QywxInnerUser;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.Map;
@RestController
@RequestMapping("/mini")
public class QywxMiniController {
private static final Logger logger = LoggerFactory.getLogger(QywxMiniController.class);
@Value("${qywx.mini.corpId}")
private String corpId;
@Autowired
private QywxInnerMiniService qywxInnerMiniService;
@GetMapping("/login")
public JsonData login(@RequestParam(value = "code", required = false) String code) {
logger.info("accept code->{}", code);
Map result = qywxInnerMiniService.getOauthUser(corpId, code);
logger.info("accept result->{}", result);
//本案例仅从企业微信接口获取未从数据表中获取
QywxInnerUser user = new QywxInnerUser();
user.setCorpId(corpId);
user.setUserId((String) result.get("userId"));
String token = JWTUtils.geneJsonWebToken(user);
result.put("token", token);
return JsonData.buildSuccess(result);
}
}
3.3. 获取token
service
package com.gblfy.qywxin.service;
import com.gblfy.qywxin.config.QywxInnerConfig;
import com.gblfy.qywxin.utils.RestUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import java.util.Map;
@Service
public class QywxInnerMiniService {
private static final Logger logger = LoggerFactory.getLogger(QywxInnerMiniService.class);
@Value("${qywx.mini.agentSecret}")
private String AGENT_SECRET;
public Map getOauthUser(String corpId, String code) {
// 1.通过corpId获取AccessToken
String accessToken = getAccessToken(corpId);
String getOauthUrl = String.format(QywxInnerConfig.MINI_OAUTH_USER_URL, accessToken, code);
Map response = RestUtils.get(getOauthUrl);
if (response.containsKey("errcode") && (Integer) response.get("errcode") != 0) {
logger.error(response.toString());
return response;
}
System.out.println("response->" + response);
// 目前已经获取到userid了
// return response;
//根据用户UserId->获取通讯录用户详情get
String userId = (String) response.get("userid");
String url = String.format(QywxInnerConfig.USER_DETAIL_URL, accessToken, userId);
Map detaiResponse = RestUtils.get(url);
//获取错误日志
if (detaiResponse.containsKey("errcode") && (Integer) detaiResponse.get("errcode") != 0) {
logger.error(detaiResponse.toString());
}
return detaiResponse;
}
/**
* 通过corpId获取AccessToken
*
* @param corpId 企业ID
* @return
*/
public String getAccessToken(String corpId) {
String result = "";
String accessTokenUrl = String.format(QywxInnerConfig.MINI_ACCESS_TOKEN_URL, corpId, AGENT_SECRET);
Map response = RestUtils.get(accessTokenUrl);
//获取错误日志
if (response.containsKey("errcode") && (Integer) response.get("errcode") != 0) {
logger.error(response.toString());
} else {
result = (String) response.get("access_token");
}
return result;
}
}
3.4. 工具类
RestUtils
package com.gblfy.qywxin.utils;
import com.alibaba.fastjson.JSONObject;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.*;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponentsBuilder;
import java.io.IOException;
import java.net.URI;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Map;
import java.util.Objects;
@Configuration
public class RestUtils {
private static final RestTemplate restTemplate = new RestTemplate();
public static JSONObject get(String url, Map<String,String> urlParams){
return get(urlToUri(url,urlParams));
}
//在处理企业微信某些参数时有问题
public static JSONObject get(String url){
return get(URI.create(url));
}
private static JSONObject get(URI uri){
ResponseEntity<JSONObject> responseEntity =restTemplate.getForEntity(uri,JSONObject.class);
serverIsRight(responseEntity); //判断服务器返回状态码
return responseEntity.getBody();
}
public static JSONObject post(String url,Map<String,String> urlParams,JSONObject json){
//组装url
return post(urlToUri(url,urlParams),json);
}
public static JSONObject post(String url,JSONObject json){
//组装urL
return post(URI.create(url),json);
}
private static JSONObject post(URI uri,JSONObject json){
//组装url
//设置提交json格式数据
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<JSONObject> request = new HttpEntity(json, headers);
ResponseEntity<JSONObject> responseEntity = restTemplate.postForEntity(uri,request,JSONObject.class);
serverIsRight(responseEntity); //判断服务器返回状态码
return responseEntity.getBody();
}
private static URI urlToUri(String url,Map<String,String> urlParams){
//设置提交json格式数据
UriComponentsBuilder uriBuilder = UriComponentsBuilder.fromHttpUrl(url);
for(Map.Entry<String,String> entry : urlParams.entrySet()) {
uriBuilder.queryParam((String)entry.getKey(), (String) entry.getValue()) ;
}
return uriBuilder.build(true).toUri();
}
public static JSONObject upload(String url,MultiValueMap formParams){
//设置表单提交
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.MULTIPART_FORM_DATA);
HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(formParams, headers);
ResponseEntity<JSONObject> responseEntity = restTemplate.postForEntity(url,request,JSONObject.class);
serverIsRight(responseEntity); //判断服务器返回状态码
return responseEntity.getBody();
}
public static String download(String url,String targetPath) throws IOException {
ResponseEntity<byte[]> rsp = restTemplate.getForEntity(url, byte[].class);
if(rsp.getStatusCode() != HttpStatus.OK){
System.out.println("文件下载请求结果状态码:" + rsp.getStatusCode());
}
// 将下载下来的文件内容保存到本地
Files.write(Paths.get(targetPath), Objects.requireNonNull(rsp.getBody()));
return targetPath;
}
public static byte[] dowload(String url){
ResponseEntity<byte[]> rsp = restTemplate.getForEntity(url, byte[].class);
return rsp.getBody();
}
private static void serverIsRight(ResponseEntity responseEntity){
if(responseEntity.getStatusCodeValue()==200){
// System.out.println("服务器请求成功:{}"+responseEntity.getStatusCodeValue());
}else {
System.out.println("服务器请求异常:{}"+responseEntity.getStatusCodeValue());
}
}
}
JWTUtils
package com.gblfy.qywxin.utils;
import com.gblfy.qywxin.vo.QywxInnerUser;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import java.util.Date;
public class JWTUtils {
private static final long EXPIRE = 60000 * 60 * 24 * 7;
private static final String SECRET = "tobdev.com";
private static final String TOKEN_PREFIX = "tobdev";
private static final String SUBJECT = "tobdev";
/**
* 生成jwt token
*
* @param user
* @return
*/
public static String geneJsonWebToken(QywxInnerUser user) {
String token = Jwts.builder().setSubject(SUBJECT)
.claim("corp_id", user.getCorpId())
.claim("user_id", user.getUserId())
.claim("user_name", user.getName())
.claim("mobile", user.getMobile())
.claim("qr_code", user.getQrCode())
.claim("user_type", user.getUserType())
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + EXPIRE))
.signWith(SignatureAlgorithm.HS256, SECRET).compact();
token = TOKEN_PREFIX + token;
return token;
}
/**
* 校验token是否合法
*
* @param token
* @return
*/
public static Claims checkJWT(String token) {
try {
final Claims claims = Jwts.parser().setSigningKey(SECRET).parseClaimsJws(token.replace(TOKEN_PREFIX, "")).getBody();
return claims;
} catch (Exception e) {
return null;
}
}
}
3.5. vo对象
package com.gblfy.qywxin.vo;
/**
* 返回封装对象
*
* @author gblfy
* @date 2022-01-12
*/
public class JsonData {
/**
* 业务上的成功或失败
*/
private boolean success = true;
private Integer code;
private String msg;
private Object data;
public JsonData() {
}
public JsonData(Integer code, Object data, String msg,boolean success) {
this.code = code;
this.data = data;
this.msg = msg;
this.success = success;
}
public static JsonData buildSuccess() {
return new JsonData(0, null, null,true);
}
public static JsonData buildSuccess(Object data) {
return new JsonData(0, data, null,true);
}
public static JsonData buildError(String msg) {
return new JsonData(-1, null, msg,false);
}
public static JsonData buildError(Integer code, String msg) {
return new JsonData(code, null, msg,false);
}
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
public boolean getSuccess() {
return success;
}
public void setSuccess(boolean success) {
this.success = success;
}
}
其他相关类,小伙伴们看源码吧,见2.3小节
四、调试部分
4.1. 模式切换
4.2. api执行流程
4.3. 报文赏评
调用login的api返回报文
{
"code": "uXGXKHjUcNLbtO0sBNylDpOHmpgmXTnNkDLVMYWu7MQ"
}
调用后端获取用户信息接口返回的报文
{
"success": true,
"code": 0,
"msg": null,
"data": {
"corpid": "wwea98220fdcd8a38d",
"deviceid": "",
"errcode": 0,
"errmsg": "ok",
"session_key": "qz2VF4V3RTagW+awOAZdpA==",
"token": "tobdeveyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ0b2JkZXYiLCJjb3JwX2lkIjoid3cxN2Y4ZDEwNzgzNDk0NTg0IiwiaWF0IjoxNjQyNDI3ODUyLCJleHAiOjE2NDMwMzI2NTJ9.965kwCYUt6h-BeCA-WUaf20LrHpMvzX8WYigNlkJJIQ",
"userid": "ZeXin"
}
}