1、準(zhǔn)備工作
1、調(diào)用微信公眾號接口,需要實(shí)現(xiàn)獲取AccessToken
,參考《獲取AccessToken接口調(diào)用憑據(jù)》
2、在本地進(jìn)行聯(lián)調(diào)時(shí),為讓微信端能夠訪問到本地服務(wù),需要進(jìn)行內(nèi)網(wǎng)穿透,參考《本地服務(wù)器內(nèi)網(wǎng)穿透實(shí)現(xiàn)(NATAPP)》
3、配置微信接口配置信息
,用于告訴微信接收消息的回調(diào)地址
2、二維碼掃碼登錄說明
- 前端調(diào)用接口,后端生成
二維碼
以及一個(gè)loginId
參數(shù)(就是一個(gè)微信掃碼場景值 sceneStr,loginId存入redis中,時(shí)效為5分鐘); - 同時(shí)前端通過loginId進(jìn)行輪詢訪問判斷是否成功登錄
- 如果掃碼后,用戶沒有進(jìn)行任何綁定,則直接回復(fù)用戶消息并且附帶上進(jìn)行綁定的引導(dǎo)鏈接(綁定操作就是一個(gè)前端頁面,進(jìn)入頁面后用戶需要同意微信網(wǎng)頁授權(quán)、并且輸入手機(jī)號、短信驗(yàn)證碼,后端將授權(quán)后得到的code查詢到openId,最后將手機(jī)號與openId進(jìn)行綁定),綁定操作參考《微信網(wǎng)頁授權(quán)自動登錄業(yè)務(wù)系統(tǒng)》;
- 如果掃碼后,loginId已過期則要求用戶重新刷新二維碼,并且只要是授權(quán)不成功都需要重新刷新二維碼再次執(zhí)行授權(quán)流程;
- 如果掃碼后 發(fā)現(xiàn)以及已經(jīng)綁定了openid則授權(quán)登錄成功,將用戶信息及token信息通過輪詢接口返回到前端,并且刪除loginId值;
3、yaml配置
wx:
# 來源于測試平臺
appid: wx79ec4331f29311b9
secret: 1c79a199560f94096f26b8caa2a73a08
apiUrl: https://api.weixin.qq.com/
openApiUrl: https://open.weixin.qq.com/
authRedirectUri: http://6uks3d.natappfree.cc/wechat/auth
4、定義工具類
MapUtils:
public class MapUtils {
/**
* Map轉(zhuǎn)換為 Entity
*
* @param params 包含參數(shù)的Map
* @param t 需要賦值的實(shí)體
* @param <T> 類型
*/
public static <T> T mapToEntity(Map<String, Object> params, T t) {
if (null == params) {
return t;
}
Class<?> clazz = t.getClass();
Field[] declaredFields = clazz.getDeclaredFields();
try {
for (Field declaredField : declaredFields) {
declaredField.setAccessible(true);
String name = declaredField.getName();
if (null != params.get(name)) {
declaredField.set(t, params.get(name));
}
}
} catch (Exception e) {
throw new RuntimeException("屬性設(shè)置失?。?);
}
return t;
}
/**
* 將對象轉(zhuǎn)換為HashMap
*
* @param t 轉(zhuǎn)換為Map的對象
* @param <T> 轉(zhuǎn)換為Map的類
* @return Map
*/
public static <T> Map<String, Object> entityToMap(T t) {
Class<?> clazz = t.getClass();
List<Field> allField = getAllField(clazz);
Map<String, Object> hashMap = new LinkedHashMap<>(allField.size());
try {
for (Field declaredField : allField) {
declaredField.setAccessible(true);
Object o = declaredField.get(t);
if (null != o) {
hashMap.put(declaredField.getName(), o);
}
}
} catch (Exception e) {
throw new RuntimeException("屬性獲取失??!");
}
return hashMap;
}
/**
* 獲取所有屬性
*
* @param clazz class
* @param <T> 泛型
* @return List<Field>
*/
public static <T> List<Field> getAllField(Class<T> clazz) {
List<Field> fields = new ArrayList<>();
Class<?> superClazz = clazz;
while (null != superClazz) {
fields.addAll(Arrays.asList(superClazz.getDeclaredFields()));
superClazz = superClazz.getSuperclass();
}
return fields;
}
/**
* 將Map參數(shù)轉(zhuǎn)換為字符串
*
* @param map
* @return
*/
public static String mapToString(Map<String, Object> map) {
StringBuffer sb = new StringBuffer();
map.forEach((key, value) -> {
sb.append(key).append("=").append(value.toString()).append("&");
});
String str = sb.toString();
str = str.substring(0, str.length() - 1);
return str;
}
/**
* 將Bean對象轉(zhuǎn)換Url請求的字符串
*
* @param t
* @param <T>
* @return
*/
public static <T> String getUrlByBean(T t) {
String pre = "?";
Map<String, Object> map = entityToMap(t);
return pre + mapToString(map);
}
}
5、掃描帶參數(shù)二維碼事件
@Service
@Slf4j
public class MessageService {
@Resource
private ScanAuthService scanAuthService;
public String receiveAndResponseMessage(HttpServletRequest request) {
log.info("------------微信消息開始處理-------------");
try {
// 調(diào)用消息工具類MessageUtil解析微信發(fā)來的xml格式的消息,解析的結(jié)果放在HashMap里;
Map<String, String> map = WeixinMessageUtil.xmlToMap(request);
String jsonString = JSON.toJSONString(map);
// 發(fā)送方賬號(用戶方)
String fromUserName = map.get("FromUserName");
// 接受方賬號(公眾號)
String toUserName = map.get("ToUserName");
// 消息類型
String msgType = map.get("MsgType");
log.info("fromUserName is:" + fromUserName + " toUserName is:" + toUserName + " msgType is:" + msgType);
// 事件類型
String eventType = map.get("Event");
if (eventType.equals(MessageType.EVENT_TYPE_SCAN)) {
BaseEvent message = JSON.parseObject(jsonString, BaseEvent.class);
log.info("掃碼成功 {}", message);
// 事件 KEY 值,qrscene_為前綴,后面為二維碼的參數(shù)值
String eventKey = map.get("EventKey");
String openId = message.getFromUserName();
// 進(jìn)行后續(xù)的授權(quán)處理
if (!StringUtils.isEmpty(eventKey)) {
scanAuthService.getAuth(openId, eventKey);
}
}
} catch (Exception e) {
e.printStackTrace();
log.error("系統(tǒng)出錯");
return null;
}
}
}
6、掃碼登錄流程示例代碼
掃碼請求實(shí)體類定義:
@Data
public class QrCodeRep {
/**
* 該二維碼有效時(shí)間,以秒為單位。 最大不超過2592000(即30天),此字段如果不填,則默認(rèn)有效期為60秒
*/
private Integer expire_seconds;
/**
* 二維碼類型
* QR_SCENE為臨時(shí)的整型參數(shù)值,QR_STR_SCENE為臨時(shí)的字符串參數(shù)值
* QR_LIMIT_SCENE為永久的整型參數(shù)值,QR_LIMIT_STR_SCENE為永久的字符串參數(shù)值
*/
private String action_name;
/**
* 二維碼詳細(xì)信息
*/
private ActionInfo action_info;
@Data
public static class ActionInfo {
private Scene scene;
}
@Data
public static class Scene {
/**
* 場景值ID(字符串形式的ID),字符串類型,長度限制為1到64
*/
private String scene_str;
}
}
掃碼請求響應(yīng)類定義:
@Data
public class QrCodeRes {
/**
* 獲取的二維碼ticket,憑借此 ticket 可以在有效時(shí)間內(nèi)換取二維碼。
*/
private String ticket;
/**
* 通過ticket換取的二維碼
*/
private String ticketUrl;
/**
* 該二維碼有效時(shí)間,以秒為單位。 最大不超過2592000(即30天)
*/
private Integer expire_seconds;
/**
* 二維碼圖片解析后的地址,開發(fā)者可根據(jù)該地址自行生成需要的二維碼圖片
*/
private String url;
/**
* 場景值ID(字符串形式的ID),字符串類型,長度限制為1到64
*/
private String sceneStr;
}
Service層:
@Slf4j
@Service
public class ScanAuthService {
@Resource
private WeChantService weChantService;
@Resource
private RestHttpRequest restHttpRequest;
@Resource
private UserInfoMapper userInfoMapper;
@Resource
private WxBean wxBean;
public String getAuth(String openId, String loginId) {
String msg = "掃碼登錄成功!";
// 如果掃碼后,loginId已過期則要求用戶重新刷新二維碼,并且只要授權(quán)不成功都需要重新刷新二維碼再次執(zhí)行授權(quán)流程
if (!RedisUtils.hasKey(loginId)) {
msg = "掃碼登錄已過期,請重新刷新二維碼!";
}
Object o = RedisUtils.get(loginId);
if (o == null) {
msg = "掃碼登錄已過期,請重新刷新二維碼!";
} else {
ScanStatusAuthRes authRes = JSON.parseObject(o.toString(), ScanStatusAuthRes.class);
UserInfo userInfo = userInfoMapper.selectByOpenId(openId);
if (userInfo == null) {
// 如果掃碼后,如果沒有進(jìn)行任何綁定,則直接回復(fù)用戶消息并且附帶上進(jìn)行綁定的鏈接
msg = "<a href =\"https://www.baidu.com/\">請前往綁定</a>";
RedisUtils.del(loginId);
}
// 如果掃碼后 發(fā)現(xiàn)以及已經(jīng)綁定了openid則授權(quán)登錄成功,將用戶信息及token信息通過輪詢接口返回到前端,并且刪除loginId值
authRes.setStatus(2);
authRes.setUserInfo(userInfo);
long expire = RedisUtils.getExpire(loginId);
RedisUtils.setEx(loginId, JSON.toJSONString(authRes), expire, TimeUnit.SECONDS);
}
return msg;
}
public PageResult qrcodeCreate() {
String url = wxBean.getApiUrl() + InterfaceConstant.QR_CODE_CREATE;
BaseRep rep = new BaseRep();
rep.setAccess_token(weChantService.getAccessToken());
url = url + MapUtils.getUrlByBean(rep);
QrCodeRep codeRep = new QrCodeRep();
codeRep.setAction_name("QR_STR_SCENE");
codeRep.setExpire_seconds(5 * 60);
QrCodeRep.ActionInfo actionInfo = new QrCodeRep.ActionInfo();
QrCodeRep.Scene scene = new QrCodeRep.Scene();
// 設(shè)置場景值
String loginId = UUIDUtils.getUuId();
scene.setScene_str(loginId);
actionInfo.setScene(scene);
codeRep.setAction_info(actionInfo);
Map map = restHttpRequest.doHttp(url, HttpMethod.POST, codeRep);
QrCodeRes res = new QrCodeRes();
MapUtils.mapToEntity(map, res);
res.setSceneStr(scene.getScene_str());
try {
// 通過ticket獲取二維碼
String ticket = res.getTicket();
String ticketUrl = "https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket=" + URLEncoder.encode(ticket, "UTF-8");
res.setTicketUrl(ticketUrl);
} catch (
UnsupportedEncodingException e) {
e.printStackTrace();
}
// 將狀態(tài)寫入redis
ScanStatusAuthRes authRes = new ScanStatusAuthRes();
authRes.setStatus(1);
RedisUtils.setEx(loginId, JSON.toJSONString(authRes), res.getExpire_seconds(), TimeUnit.SECONDS);
return ResultUtils.success(res);
}
public PageResult scanStatus(String loginId) {
// 通過redis查詢是否存在
if (!RedisUtils.hasKey(loginId)) {
return ResultUtils.fail("掃碼登錄已過期,請重新刷新二維碼!");
}
Object o = RedisUtils.get(loginId);
ScanStatusAuthRes authRes = JSON.parseObject(o.toString(), ScanStatusAuthRes.class);
if (authRes.getStatus() == 2) {
// 刪除key
RedisUtils.del(loginId);
}
return ResultUtils.success(authRes);
}
}
Controller層:文章來源:http://www.zghlxwxcb.cn/news/detail-632162.html
@Slf4j
@RestController
public class ScanAuthController {
@Resource
private ScanAuthService scanAuthService;
/**
* 獲取掃碼登錄二維碼
*
* @return
*/
@ApiOperation(value = "獲取掃碼登錄二維碼", notes = "獲取掃碼登錄二維碼")
@GetMapping("/scanLogin")
public PageResult qrcodeCreate() {
return ResultUtils.success(scanAuthService.qrcodeCreate());
}
/**
* 輪詢獲取登錄狀態(tài)
*
* @param loginId
* @return
*/
@ApiOperation(value = "輪詢獲取登錄狀態(tài)", notes = "輪詢獲取登錄狀態(tài)")
@GetMapping("/scanStatus")
public PageResult scanStatus(@RequestParam("loginId") String loginId) {
return ResultUtils.success(scanAuthService.scanStatus(loginId));
}
}
文章來源地址http://www.zghlxwxcb.cn/news/detail-632162.html
到了這里,關(guān)于微信公眾號開發(fā)—掃描二維碼實(shí)現(xiàn)登錄方案的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!