feat: app扫码登录

This commit is contained in:
xiaochangbai 2022-08-07 21:34:20 +08:00
parent 2d63d36f51
commit 15adbf489e
8 changed files with 253 additions and 8 deletions

View File

@ -7,6 +7,8 @@ import cn.lili.common.security.enums.UserEnums;
import cn.lili.common.vo.ResultMessage;
import cn.lili.modules.member.entity.dos.Member;
import cn.lili.modules.member.entity.dto.MemberEditDTO;
import cn.lili.modules.member.entity.enums.QRCodeLoginSessionStatusEnum;
import cn.lili.modules.member.entity.vo.QRLoginResultVo;
import cn.lili.modules.member.service.MemberService;
import cn.lili.modules.sms.SmsUtil;
import cn.lili.modules.verification.entity.enums.VerificationEnums;
@ -15,10 +17,18 @@ import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.context.request.async.DeferredResult;
import javax.validation.constraints.NotNull;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
/**
* 买家端,会员接口
@ -26,6 +36,7 @@ import javax.validation.constraints.NotNull;
* @author Chopper
* @since 2020/11/16 10:07 下午
*/
@Slf4j
@RestController
@Api(tags = "买家端,会员接口")
@RequestMapping("/buyer/passport/member")
@ -39,6 +50,72 @@ public class MemberBuyerController {
private VerificationService verificationService;
@ApiOperation(value = "web-获取登录二维码")
@PostMapping(value = "/pc_session", produces = "application/json;charset=UTF-8")
public ResultMessage<Object> createPcSession() {
return ResultUtil.data(memberService.createPcSession());
}
/**
* 长轮询参考nacos
* @param token
* @param beforeSessionStatus 上次记录的session状态
* @return
*/
@ApiOperation(value = "web-二维码登录")
@PostMapping(value = "/session_login/{token}", produces = "application/json;charset=UTF-8")
public Object loginWithSession(@PathVariable("token") String token,Integer beforeSessionStatus) {
log.info("receive login with session key {}", token);
ResponseEntity<ResultMessage> timeoutResponseEntity =
new ResponseEntity<>(ResultUtil.error(ResultCode.ERROR), HttpStatus.OK);
int timeoutSecond = 10;
DeferredResult<ResponseEntity> deferredResult = new DeferredResult<>(timeoutSecond * 1000L, timeoutResponseEntity);
CompletableFuture.runAsync(() -> {
try {
int i = 0;
while (i < timeoutSecond) {
QRLoginResultVo queryResult = memberService.loginWithSession(token);
int status = queryResult.getStatus();
if(status==beforeSessionStatus
&& (QRCodeLoginSessionStatusEnum.WAIT_SCANNING.getCode()==status
|| QRCodeLoginSessionStatusEnum.SCANNING.getCode()==status)){
//睡眠一秒种继续等待结果
TimeUnit.SECONDS.sleep(1);
}else{
deferredResult.setResult(new ResponseEntity<>(ResultUtil.data(queryResult), HttpStatus.OK));
break;
}
i ++;
}
} catch (Exception e) {
log.error("获取登录状态异常,",e);
deferredResult.setResult(new ResponseEntity(ResultUtil.error(ResultCode.ERROR), HttpStatus.OK));
}
}, Executors.newCachedThreadPool());
return deferredResult;
}
@ApiOperation(value = "app扫码")
@PostMapping(value = "/app_scanner", produces = "application/json;charset=UTF-8")
public ResultMessage<Object> appScanner(String token) {
return ResultUtil.data(memberService.appScanner(token));
}
@ApiOperation(value = "app扫码-登录确认:同意/拒绝")
@ApiImplicitParams({
@ApiImplicitParam(name = "token", value = "sessionToken", required = true, paramType = "query"),
@ApiImplicitParam(name = "code", value = "操作0拒绝登录1同意登录", required = true, paramType = "query")
})
@PostMapping(value = "/app_confirm", produces = "application/json;charset=UTF-8")
public ResultMessage<Object> appSConfirm(String token,Integer code) {
boolean flag = memberService.appSConfirm(token,code);
return flag ? ResultUtil.success():ResultUtil.error(ResultCode.ERROR);
}
@ApiOperation(value = "登录接口")
@ApiImplicitParams({
@ApiImplicitParam(name = "username", value = "用户名", required = true, paramType = "query"),

View File

@ -487,7 +487,16 @@ public enum CachePrefix {
/**
* 敏感词
*/
SENSITIVE;
SENSITIVE,
/**
* 扫码登录
* @param str
* @return
*/
QR_CODE_LOGIN_SESSION
;
public static String removePrefix(String str) {

View File

@ -0,0 +1,46 @@
package cn.lili.modules.member.entity.enums;
import lombok.Getter;
@Getter
public enum QRCodeLoginSessionStatusEnum {
/**
* 二维码创建完毕等待app端扫码
*/
WAIT_SCANNING(0,"等待扫码"),
/**
* app端已经扫码等待确认同意登录
*/
SCANNING(1,"已经扫码"),
/**
* 用户在app端点击了同意登录
*/
VERIFIED(2,"确认登录"),
/**
* 用户在app端点击了取消登录
*/
CANCELED(3,"取消登录"),
/**
* 二维码不存在/或者已经过期
*/
NO_EXIST(4,"二维码已过期")
;
private Integer code;
private String desc;
QRCodeLoginSessionStatusEnum(Integer code,String desc){
this.code = code;
this.desc = desc;
}
}

View File

@ -0,0 +1,21 @@
package cn.lili.modules.member.entity.vo;
import cn.lili.modules.member.entity.enums.QRCodeLoginSessionStatusEnum;
import lombok.Data;
import java.io.Serializable;
@Data
public class QRCodeLoginSessionVo implements Serializable {
private static final long serialVersionUID = 8793639296995408322L;
private String token;
private Integer status;
private long duration;
private long userId;
}

View File

@ -0,0 +1,12 @@
package cn.lili.modules.member.entity.vo;
import cn.lili.common.security.token.Token;
import lombok.Data;
@Data
public class QRLoginResultVo {
private Token token;
private int status;
}

View File

@ -11,6 +11,8 @@ import cn.lili.modules.member.entity.dto.MemberAddDTO;
import cn.lili.modules.member.entity.dto.MemberEditDTO;
import cn.lili.modules.member.entity.vo.MemberSearchVO;
import cn.lili.modules.member.entity.vo.MemberVO;
import cn.lili.modules.member.entity.vo.QRLoginResultVo;
import cn.lili.modules.member.entity.vo.QRCodeLoginSessionVo;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.IService;
@ -272,4 +274,12 @@ public interface MemberService extends IService<Member> {
* @return 用户VO
*/
MemberVO getMember(String id);
QRCodeLoginSessionVo createPcSession();
Object appScanner(String token);
boolean appSConfirm(String token, Integer code);
QRLoginResultVo loginWithSession(String token);
}

View File

@ -26,8 +26,11 @@ import cn.lili.modules.member.aop.annotation.PointLogPoint;
import cn.lili.modules.member.entity.dos.Member;
import cn.lili.modules.member.entity.dto.*;
import cn.lili.modules.member.entity.enums.PointTypeEnum;
import cn.lili.modules.member.entity.enums.QRCodeLoginSessionStatusEnum;
import cn.lili.modules.member.entity.vo.MemberSearchVO;
import cn.lili.modules.member.entity.vo.MemberVO;
import cn.lili.modules.member.entity.vo.QRLoginResultVo;
import cn.lili.modules.member.entity.vo.QRCodeLoginSessionVo;
import cn.lili.modules.member.mapper.MemberMapper;
import cn.lili.modules.member.service.MemberService;
import cn.lili.modules.member.token.MemberTokenGenerate;
@ -49,10 +52,8 @@ import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.*;
import java.util.concurrent.TimeUnit;
/**
* 会员接口业务层实现
@ -668,6 +669,74 @@ public class MemberServiceImpl extends ServiceImpl<MemberMapper, Member> impleme
return new MemberVO(this.getById(id));
}
@Override
public QRCodeLoginSessionVo createPcSession() {
QRCodeLoginSessionVo session = new QRCodeLoginSessionVo();
session.setStatus(QRCodeLoginSessionStatusEnum.WAIT_SCANNING.getCode());
//过期时间10s
Long duration= 10 * 1000L;
session.setDuration(duration);
String token = CachePrefix.QR_CODE_LOGIN_SESSION.name()+SnowFlake.getIdStr();
session.setToken(token);
cache.put(token,session,duration, TimeUnit.MILLISECONDS);
return session;
}
@Override
public Object appScanner(String token) {
QRCodeLoginSessionVo session = (QRCodeLoginSessionVo) cache.get(token);
if(session == null){
return QRCodeLoginSessionStatusEnum.NO_EXIST.getCode();
}
session.setStatus(QRCodeLoginSessionStatusEnum.SCANNING.getCode());
cache.put(token,session,session.getDuration(), TimeUnit.MILLISECONDS);
return QRCodeLoginSessionStatusEnum.SCANNING.getCode();
}
@Override
public boolean appSConfirm(String token, Integer code) {
QRCodeLoginSessionVo session = (QRCodeLoginSessionVo) cache.get(token);
if(session == null){
return false;
}
if(code==1){
//同意
session.setStatus(QRCodeLoginSessionStatusEnum.VERIFIED.getCode());
AuthUser currentUser = Objects.requireNonNull(UserContext.getCurrentUser());
session.setUserId(Long.valueOf(currentUser.getId()));
}else{
//拒绝
session.setStatus(QRCodeLoginSessionStatusEnum.CANCELED.getCode());
}
cache.put(token,session,session.getDuration(), TimeUnit.MILLISECONDS);
return true;
}
@Override
public QRLoginResultVo loginWithSession(String sessionToken) {
QRLoginResultVo result = new QRLoginResultVo();
result.setStatus(QRCodeLoginSessionStatusEnum.NO_EXIST.getCode());
QRCodeLoginSessionVo session = (QRCodeLoginSessionVo) cache.get(sessionToken);
if(session == null){
return result;
}
result.setStatus(session.getStatus());
if(QRCodeLoginSessionStatusEnum.VERIFIED.getCode().equals(session.getStatus())){
//生成token
Member member = this.getById(session.getUserId());
if(member==null){
throw new ServiceException(ResultCode.USER_NOT_EXIST);
}else{
//生成token
Token token = memberTokenGenerate.createToken(member, false);
result.setToken(token);
cache.vagueDel(sessionToken);
}
}
return result;
}
/**
* 检测会员
*

View File

@ -37,17 +37,18 @@ public class MemberTokenGenerate extends AbstractTokenGenerate<Member> {
@Override
public Token createToken(Member member, Boolean longTerm) {
//获取客户端类型
String clientType = ThreadContextHolder.getHttpRequest().getHeader("clientType");
ClientTypeEnum clientTypeEnum;
try {
//获取客户端类型
String clientType = ThreadContextHolder.getHttpRequest().getHeader("clientType");
//如果客户端为空则缺省值为PCpc第三方登录时不会传递此参数
if (clientType == null) {
clientTypeEnum = ClientTypeEnum.PC;
} else {
clientTypeEnum = ClientTypeEnum.valueOf(clientType);
}
} catch (IllegalArgumentException e) {
} catch (Exception e) {
clientTypeEnum = ClientTypeEnum.UNKNOWN;
}
//记录最后登录时间客户端类型