feat(微信登录): 新增微信登录相关接口及文档
添加微信登录控制器,实现获取微信登录二维码、临时token、短信验证码发送与验证、用户退出登录等功能。同时新增相关接口文档,详细描述各接口的请求与响应格式。
This commit is contained in:
parent
7e06c9f73f
commit
e18463279b
|
@ -0,0 +1,94 @@
|
|||
package com.agileboot.api.controller;
|
||||
|
||||
import com.agileboot.common.core.dto.ResponseDTO;
|
||||
import com.agileboot.common.exception.ApiException;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import com.agileboot.domain.ab98.api.Ab98ApiUtil;
|
||||
|
||||
import javax.validation.constraints.NotBlank;
|
||||
import java.util.Map;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/wx/login")
|
||||
@CrossOrigin(origins = "*", allowedHeaders = "*")
|
||||
@RequiredArgsConstructor
|
||||
@Api(tags = "微信登录接口")
|
||||
public class WxLoginController {
|
||||
|
||||
@PostMapping("/logout")
|
||||
@ApiOperation(value = "用户退出登录")
|
||||
public ResponseDTO<Ab98ApiUtil.LogoutResponse> logout(@RequestParam @NotBlank String token) {
|
||||
try {
|
||||
return ResponseDTO.ok(Ab98ApiUtil.doLogout(token));
|
||||
} catch (ApiException e) {
|
||||
return ResponseDTO.fail(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取微信登录二维码链接
|
||||
*/
|
||||
@GetMapping("/wechat/qrcode")
|
||||
public ResponseDTO<String> getWechatQrCode(@RequestParam @NotBlank String token) {
|
||||
try {
|
||||
return ResponseDTO.ok(Ab98ApiUtil.generateWechatLoginUrl(token));
|
||||
} catch (ApiException e) {
|
||||
return ResponseDTO.fail(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取临时token
|
||||
*/
|
||||
@GetMapping("/getToken")
|
||||
@ApiOperation(value = "获取临时令牌", notes = "用于后续登录流程")
|
||||
public ResponseDTO<Ab98ApiUtil.TokenResponse> getToken(@RequestParam String appName) {
|
||||
try {
|
||||
return ResponseDTO.ok(Ab98ApiUtil.getToken(appName));
|
||||
} catch (ApiException e) {
|
||||
return ResponseDTO.fail(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送短信验证码
|
||||
*/
|
||||
@PostMapping("/sendSms")
|
||||
public ResponseDTO<Ab98ApiUtil.SmsSendResponse> sendSms(
|
||||
@RequestParam String token,
|
||||
@RequestParam String tel) {
|
||||
try {
|
||||
return ResponseDTO.ok(Ab98ApiUtil.sendLoginSms(token, tel));
|
||||
} catch (ApiException e) {
|
||||
return ResponseDTO.fail(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证短信验证码
|
||||
*/
|
||||
@PostMapping("/verifySms")
|
||||
public ResponseDTO<Ab98ApiUtil.LoginData> verifySms(
|
||||
@RequestParam String token,
|
||||
@RequestParam String tel,
|
||||
@RequestParam String vcode) {
|
||||
try {
|
||||
Ab98ApiUtil.LoginResponse loginResponse = Ab98ApiUtil.verifySmsCode(token, tel, vcode);
|
||||
Ab98ApiUtil.LoginData data = new Ab98ApiUtil.LoginData();
|
||||
data.setFace_img(loginResponse.getOutputData().getFace_img());
|
||||
data.setSuccess(loginResponse.getOutputData().isSuccess());
|
||||
data.setSex(loginResponse.getOutputData().getSex());
|
||||
data.setName(loginResponse.getOutputData().getName());
|
||||
data.setUserid(loginResponse.getOutputData().getUserid());
|
||||
data.setRegistered(loginResponse.getOutputData().isRegistered());
|
||||
data.setTel(loginResponse.getOutputData().getTel());
|
||||
return ResponseDTO.ok(data);
|
||||
} catch (ApiException e) {
|
||||
return ResponseDTO.fail(e);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,234 @@
|
|||
package com.agileboot.domain.ab98.api;
|
||||
|
||||
import cn.hutool.http.HttpUtil;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import com.agileboot.common.exception.ApiException;
|
||||
import com.agileboot.common.exception.error.ErrorCode;
|
||||
import lombok.Data;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@Slf4j
|
||||
public class Ab98ApiUtil {
|
||||
|
||||
private static final String BASE_URL = "https://www.ab98.cn/api/doInterface";
|
||||
private static final String WEBSOCKET_URL = "wss://www.ab98.cn/login.ws/";
|
||||
|
||||
/**
|
||||
* 短信登录(发送验证码)
|
||||
*/
|
||||
/**
|
||||
* 发送短信验证码(短信登录)
|
||||
* @param token 通过getToken获取的临时令牌(有效期5分钟)
|
||||
* @param tel 接收验证码的手机号码(需符合格式:11位数字)
|
||||
* @param nobind "true"表示不绑定手机号(固定值)
|
||||
* @param for_login "true"表示用于登录(固定值)
|
||||
* @param from 来源渠道(固定值"jt")
|
||||
*/
|
||||
public static SmsSendResponse sendLoginSms(String token, String tel) {
|
||||
String url = BASE_URL + "?code=doSendSms";
|
||||
|
||||
Map<String, Object> paramMap = new HashMap<String, Object>() {{
|
||||
put("token", token);
|
||||
put("tel", tel);
|
||||
put("nobind", "true");
|
||||
put("for_login", "true");
|
||||
put("from", "jt");
|
||||
}};
|
||||
|
||||
String response = HttpUtil.createPost(url)
|
||||
.body(JSONUtil.toJsonStr(paramMap))
|
||||
.header("noSign", "true")
|
||||
.header("source", "api")
|
||||
.execute().body();
|
||||
log.info("短信发送响应: {}", response);
|
||||
|
||||
SmsSendResponse resp = JSONUtil.toBean(response, SmsSendResponse.class);
|
||||
checkApiResponse(resp);
|
||||
return resp;
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证短信验证码
|
||||
*/
|
||||
/**
|
||||
* 验证短信验证码(短信登录)
|
||||
* @param token 通过getToken获取的临时令牌
|
||||
* @param tel 接收验证码的手机号码
|
||||
* @param vcode 用户输入的6位验证码(有效期5分钟)
|
||||
* @return 登录结果,包含用户身份信息
|
||||
* @throws ApiException 当验证失败或接口返回异常时抛出
|
||||
*/
|
||||
public static LoginResponse verifySmsCode(String token, String tel, String vcode) {
|
||||
String url = BASE_URL + "?code=doCheckSmsCode";
|
||||
|
||||
Map<String, Object> paramMap = new HashMap<String, Object>() {{
|
||||
put("token", token);
|
||||
put("tel", tel);
|
||||
put("vcode", vcode);
|
||||
}};
|
||||
|
||||
String response = HttpUtil.createPost(url)
|
||||
.body(JSONUtil.toJsonStr(paramMap))
|
||||
.header("noSign", "true")
|
||||
.header("source", "api")
|
||||
.execute().body();
|
||||
log.info("短信验证响应: {}", response);
|
||||
|
||||
LoginResponse resp = JSONUtil.toBean(response, LoginResponse.class);
|
||||
checkApiResponse(resp);
|
||||
return handleLoginResult(resp);
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理登录结果
|
||||
*/
|
||||
private static LoginResponse handleLoginResult(LoginResponse response) {
|
||||
if (response.getOutputData() == null || !response.getOutputData().isSuccess()) {
|
||||
log.error("登录失败: {}", response);
|
||||
throw new ApiException(ErrorCode.FAILED, "登录验证失败");
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成微信扫码登录URL
|
||||
*/
|
||||
public static String generateWechatLoginUrl(String token) {
|
||||
return "https://www.ab98.cn/online/index.html?content=doLogin%60" + token;
|
||||
}
|
||||
|
||||
/**
|
||||
* 监听扫码登录websocket
|
||||
*/
|
||||
/*public static void listenLoginWebsocket(String token, WebSocketListener listener) {
|
||||
String url = WEBSOCKET_URL + token;
|
||||
HttpUtil.createWebSocket(url, listener).connect();
|
||||
}*/
|
||||
|
||||
/**
|
||||
* 获取登录token
|
||||
*/
|
||||
/**
|
||||
* 获取登录token
|
||||
* @param appName 应用英文名(需在汇邦数字平台登记)
|
||||
* @return outputData.token 临时令牌(后续接口需携带)
|
||||
* @return outputData.takeFace 是否需要人脸验证(true需要刷脸)
|
||||
*/
|
||||
public static TokenResponse getToken(String appName) {
|
||||
// String url = BASE_URL + "?code=doGetToken&from=jt&app=" + appName;
|
||||
String url = BASE_URL + "?code=doGetToken&from=jt";
|
||||
|
||||
String response = HttpUtil.createGet(url)
|
||||
.header("noSign", "true")
|
||||
.header("source", "api")
|
||||
.execute().body();
|
||||
log.info("获取token响应: {}", response);
|
||||
|
||||
TokenResponse tokenResponse = JSONUtil.toBean(response, TokenResponse.class);
|
||||
checkApiResponse(tokenResponse);
|
||||
return tokenResponse;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建带参数的请求URL
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* 检查接口响应状态
|
||||
*/
|
||||
private static void checkApiResponse(BaseResponse response) {
|
||||
if (response.getStateCode() != 200 || !"ok".equals(response.getState())) {
|
||||
log.error("接口调用失败: {}", response);
|
||||
throw new ApiException(ErrorCode.FAILED, "第三方接口调用失败");
|
||||
}
|
||||
}
|
||||
|
||||
// 基础响应对象
|
||||
@Data
|
||||
public static class BaseResponse {
|
||||
private String state;
|
||||
private Integer stateCode;
|
||||
private Object outputData;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class SmsSendResponse extends BaseResponse {
|
||||
private SmsResult outputData;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class SmsResult {
|
||||
private boolean success;
|
||||
private String errMsg;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class LoginResponse extends BaseResponse {
|
||||
private LoginData outputData;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class LoginData {
|
||||
private String idcard_back; // 身份证背面照片地址
|
||||
private String face_img; // 人脸照片地址
|
||||
private String address; // 身份证登记地址
|
||||
private String nation; // 民族(接口文档显示可能为null)
|
||||
private boolean success; // 验证是否成功
|
||||
private String sex; // 性别(男/女)
|
||||
private String name; // 真实姓名
|
||||
private String userid; // 用户在平台的唯一ID
|
||||
private boolean registered; // 是否已注册
|
||||
private String tel; // 手机号码
|
||||
private String idnum; // 身份证号码
|
||||
private String idcard_front; // 身份证正面照片地址
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 用户退出接口
|
||||
*/
|
||||
public static LogoutResponse doLogout(String token) {
|
||||
String url = BASE_URL + "?code=doLogout";
|
||||
|
||||
Map<String, Object> paramMap = new HashMap<String, Object>() {{
|
||||
put("token", token);
|
||||
}};
|
||||
|
||||
String response = HttpUtil.createPost(url)
|
||||
.body(JSONUtil.toJsonStr(paramMap))
|
||||
.header("noSign", "true")
|
||||
.header("source", "api")
|
||||
.execute().body();
|
||||
log.info("用户退出响应: {}", response);
|
||||
|
||||
LogoutResponse resp = JSONUtil.toBean(response, LogoutResponse.class);
|
||||
checkApiResponse(resp);
|
||||
return resp;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class LogoutResponse extends BaseResponse {
|
||||
private LogoutData outputData; // 包含success和logoutTime(登出时间)
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class LogoutData {
|
||||
private boolean success;
|
||||
private String logoutTime;
|
||||
}
|
||||
|
||||
// Token响应对象
|
||||
@Data
|
||||
public static class TokenResponse extends BaseResponse {
|
||||
private TokenData outputData;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class TokenData {
|
||||
private String token;
|
||||
private boolean takeFace;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,232 @@
|
|||
## 请求概述
|
||||
### 请求header
|
||||
所有请求必须包含以下两个header:
|
||||
* `noSign`: `true`
|
||||
* `source`: `api`
|
||||
获取登录token后,也可以在header中添加:
|
||||
* `token`: `${token}`
|
||||
### 登录校验结果
|
||||
接口返回结果为json格式,如果包含`code`字段,则说明登录校验失败。
|
||||
* `code`: `0001` - 登录状态已失效
|
||||
* `code`: `0002` - 在cookie、Get参数、Header中均未检测到token
|
||||
## 登录接口
|
||||
### 2.1 获取token
|
||||
* **API**: `https://www.ab98.cn/api/doInterface?code=doGetToken&from=jt&app=${appname}`
|
||||
* **请求方式**: GET
|
||||
* **请求参数**:
|
||||
* `code`: `doGetToken` (必传)
|
||||
* `from`: `jt` (可选)
|
||||
* `app`: 调用该接口的应用在汇邦数字平台登记的应用英文名 (可选)
|
||||
**成功返回**:
|
||||
```json
|
||||
{
|
||||
"outputData": {
|
||||
"token": "1a6ea39e84c406283839856640e3aa66",
|
||||
"takeFace": true
|
||||
},
|
||||
"state": "ok"
|
||||
}
|
||||
```
|
||||
**失败返回**:
|
||||
```json
|
||||
{
|
||||
"outputData": "错误信息",
|
||||
"stateCode": 0,
|
||||
"state": "fail"
|
||||
}
|
||||
```
|
||||
### 2.2 短信登录
|
||||
#### 2.2.1 发送短信
|
||||
* **API**: `https://www.ab98.cn/api/doInterface?code=doSendSms`
|
||||
* **请求方式**: POST
|
||||
* **Content-Type**: application/json
|
||||
* **请求BODY**:
|
||||
```json
|
||||
{
|
||||
"token":"358900e1005c33a1dd059b07042ceec3", //必传
|
||||
"tel":"137xxxxxxxx", //必传
|
||||
"nobind":"true", //必传
|
||||
"for_login":"true", //必传
|
||||
"from":"jt" //必传
|
||||
}
|
||||
```
|
||||
**成功返回**:
|
||||
```json
|
||||
{
|
||||
"outputData": {
|
||||
"success": true
|
||||
},
|
||||
"state": "ok"
|
||||
}
|
||||
```
|
||||
**失败返回**:
|
||||
```json
|
||||
{
|
||||
"outputData": {
|
||||
"success": false,
|
||||
"errMsg": "失败原因"
|
||||
},
|
||||
"state": "ok"
|
||||
}
|
||||
```
|
||||
#### 2.2.2 验证短信验证码
|
||||
* **API**: `https://www.ab98.cn/api/doInterface?code=doCheckSmsCode`
|
||||
* **请求方式**: POST
|
||||
* **Content-Type**: application/json
|
||||
* **请求BODY**:
|
||||
```json
|
||||
{
|
||||
"token":"358900e1005c33a1dd059b07042ceec3",
|
||||
"tel":"137xxxxxxxx",
|
||||
"vcode":"123456"
|
||||
}
|
||||
```
|
||||
**成功返回**:
|
||||
```json
|
||||
{
|
||||
"outputData": {
|
||||
"idcard_back": "https://www.ab98.cn/upload/temp/images/202102/xxx.jpeg",
|
||||
"face_img": "https://www.ab98.cn/upload/temp/images/202102/yyy.jpeg",
|
||||
"address": "身份证上的地址",
|
||||
"nation": null,
|
||||
"success": true,
|
||||
"sex": "男",
|
||||
"name": "xxx",
|
||||
"userid": 123,
|
||||
"registered": true,
|
||||
"tel": "137xxxxxxxx",
|
||||
"idnum": "450981xxxxxxxxxxxx",
|
||||
"idcard_front": "https://www.ab98.cn/upload/temp/images/202102/zzz.jpeg"
|
||||
},
|
||||
"state": "ok"
|
||||
}
|
||||
```
|
||||
**失败返回**:
|
||||
```json
|
||||
{
|
||||
"outputData": {
|
||||
"success": false
|
||||
},
|
||||
"state": "ok"
|
||||
}
|
||||
```
|
||||
### 2.3 微信扫码登录
|
||||
* **API**: `https://www.ab98.cn/online/index.html?content=doLogin%60`
|
||||
#### 2.3.1 二维码
|
||||
假设获取到的token为`358900e1005c33a1dd059b07042ceec3`,则构造的最终链接为:
|
||||
`https://www.ab98.cn/online/index.html?content=doLogin%60358900e1005c33a1dd059b07042ceec3`
|
||||
使用该链接生成二维码图片供用户使用微信“扫一扫”。
|
||||
#### 2.3.2 监听websocket
|
||||
* **监听地址**: `wss://www.ab98.cn/login.ws/${token}`
|
||||
* **登录失败**:
|
||||
```json
|
||||
{
|
||||
"outputData": "登录失败原因",
|
||||
"state": "fail"
|
||||
}
|
||||
```
|
||||
* **登录成功**:
|
||||
```json
|
||||
{
|
||||
"outputData": "登录成功",
|
||||
"state": "ok",
|
||||
"username": "扫码者姓名",
|
||||
"sex": "扫码者性别",
|
||||
"head_img": "扫码者头像",
|
||||
"idnum": "身份证号码",
|
||||
"userid": "人员记录ID",
|
||||
"tel": "手机号码"
|
||||
}
|
||||
```
|
||||
### 2.4 刷脸登录
|
||||
* **API**: `https://www.ab98.cn/api/doInterface?code=doStrongFaceLogin`
|
||||
* **请求方式**: POST
|
||||
* **Content-Type**: application/json
|
||||
* **请求BODY**:
|
||||
```json
|
||||
{
|
||||
"token":"358900e1005c33a1dd059b07042ceec3", //必传
|
||||
"check_code":"1234", //必传
|
||||
"imgBase64":"/9j/4AAQS…" //必传
|
||||
}
|
||||
```
|
||||
**成功返回**:
|
||||
```json
|
||||
{
|
||||
"state": "ok",
|
||||
"outputData": "登录成功",
|
||||
"openid": "oGfFD1jBEZ2sq4PhOc8zKKejHA9E",
|
||||
"head_img": "https://www.ab98.cn/… ",
|
||||
"sex": "男",
|
||||
"userid": 1,
|
||||
"token": "dbe11ef64cd6350c5d935d531db765c0",
|
||||
"username": "xxx",
|
||||
"tel": "xxx"
|
||||
}
|
||||
```
|
||||
### 2.5 动态码登录
|
||||
1. **发起websocket监听**:
|
||||
`wss://www.ab98.cn/login.ws/{监听token}`
|
||||
其中 `{监听token}` 由客户端自定义,由数字和英文字母组成,保证多用户同时监听时 `{监听token}` 互不相同即可,否则会接收到错误消息。
|
||||
2. **主动发送消息**:
|
||||
```json
|
||||
{
|
||||
"type": "mfa",
|
||||
"do": "checkCode",
|
||||
"token": "xxx",
|
||||
"code": "000000"
|
||||
}
|
||||
```
|
||||
**服务器响应消息**:
|
||||
1. **登录失败**:
|
||||
```json
|
||||
{
|
||||
"state": "fail",
|
||||
"msg": "失败原因"
|
||||
}
|
||||
```
|
||||
2. **动态码校验通过,等待移动端授权登录**:
|
||||
```json
|
||||
{
|
||||
"state": "ok",
|
||||
"step": "0"
|
||||
}
|
||||
```
|
||||
3. **等待移动端授权超时**:
|
||||
```json
|
||||
{
|
||||
"state": "ok",
|
||||
"step": "1"
|
||||
}
|
||||
```
|
||||
4. **移动端已授权登录**:
|
||||
```json
|
||||
{
|
||||
"state": "ok",
|
||||
"step": "2",
|
||||
"openid": "xxx",
|
||||
"head_img": "/xxx",
|
||||
"sex": "男/女",
|
||||
"company": "xxx",
|
||||
"userid": 123,
|
||||
"idnum": "xxx",
|
||||
"username": "xxx",
|
||||
"tel": "xxx"
|
||||
}
|
||||
```
|
||||
### 2.6 退出登录
|
||||
* **API**: `https://www.ab98.cn/api/doInterface?code=doLogout`
|
||||
* **请求方式**: POST
|
||||
* **Content-Type**: application/json
|
||||
* **请求BODY**:
|
||||
```json
|
||||
{
|
||||
"token":"358900e1005c33a1dd059b07042ceec3"
|
||||
}
|
||||
```
|
||||
**成功返回**:
|
||||
```json
|
||||
{
|
||||
"state": "ok"
|
||||
}
|
||||
```
|
Loading…
Reference in New Issue