掃碼登錄-客戶端定時器輪訓實現(xiàn)
controller接口
詳細可參考源代碼只嚣。cn.codersnet.helloworld.scanlogin.web.ScanLoginController
@RestController
@RequestMapping("/scanlogin")
public class ScanLoginController {
private Log logger = LogFactory.getLog(this.getClass());
private static final String TOKEN_LOGINID_SUFFIX = "_loginID";
@Autowired
private Cache cache;
/**
* 生成二維碼桩撮,暫時返回token
* @return
*/
@GetMapping("/generate")
public String generateCrCode() {
//生成token
String token = UUID.randomUUID().toString();
//將token置于緩存中沼沈,可以設置默認有效期珍昨,可以直接使用redis緩存設置過期時間挂洛。這里簡單通過map實現(xiàn)
cache.put(token, new ScanStatus(ScanStatus.CODE_GENERATE, "二維碼已生成, 待掃描"), 60); //60s后過期
return token;
}
/**
* 移動端掃碼
* @return
*/
@GetMapping("/appScan")
public ScanStatus appScan(String token) {
ScanStatus status = new ScanStatus(ScanStatus.CODE_SCANED, "二維碼已掃描, 待確認");
if(cache.existed(token)) {
cache.put(token, status);
} else {
status.setStatusCode(ScanStatus.CODE_EXPIRED);
status.setMessage("二維碼已過期或不存在");
}
return status;
}
/**
* 移動端確認登錄佩谣,如果存在token未過期凡怎,設置token和loginID關聯(lián)關系
* @param token
* @param loginID
* @return
*/
@GetMapping("/appConfirm")
public ScanStatus appConfirm(String token, String loginID) {
ScanStatus status = new ScanStatus(ScanStatus.CODE_APP_CONFIRM, "移動端已確認");
if(cache.existed(token)) {
cache.put(token, status);
cache.put(token + TOKEN_LOGINID_SUFFIX, loginID);
} else {
status.setStatusCode(ScanStatus.CODE_EXPIRED);
status.setMessage("二維碼已過期或不存在");
}
return status;
}
/**
* 檢查掃碼狀態(tài)
* @param token
* @return
*/
@PostMapping("/check")
public ScanStatus check(String token) {
if(cache.existed(token)) {
ScanStatus scanStatus = (ScanStatus)cache.get(token);
if(ScanStatus.CODE_APP_CONFIRM == scanStatus.getStatusCode()) {
//TODO 執(zhí)行登錄
String loginID = (String)cache.get(token + TOKEN_LOGINID_SUFFIX);
logger.info(loginID + ", app掃描登錄成功");
return new ScanStatus(ScanStatus.CODE_LOGIN_SUCCESS, "登錄成功");
} else {
return scanStatus;
}
}
//二維碼過期
cache.remove(token + TOKEN_LOGINID_SUFFIX);
return new ScanStatus(ScanStatus.CODE_EXPIRED, "二維碼已過期或不存在");
}
}
單元測試
詳細可參考源代碼悉稠。cn.codersnet.helloworld.ScanLoginTests
@RunWith(SpringRunner.class)
@SpringBootTest(classes = HelloworldApplication.class)
@AutoConfigureMockMvc
public class ScanLoginTests {
@Autowired
private MockMvc mvc;
private JacksonTester<ScanStatus> json;
@Before
public void setup() {
ObjectMapper objectMapper = new ObjectMapper();
JacksonTester.initFields(this, objectMapper);
}
/**
* 掃碼登錄測試測試方法
* @throws Exception
*/
@Test
public void scanLoginSuccessTest() throws Exception {
// 1. 獲取token(二維碼)
String token = mvc.perform(get("/scanlogin/generate")).andReturn().getResponse().getContentAsString();
Assert.isTrue(check(token).getStatusCode() == ScanStatus.CODE_GENERATE, "生成二維碼失敗");
// 2. 移動端掃碼
mvc.perform(get("/scanlogin/appScan").param("token", token));
Assert.isTrue(check(token).getStatusCode() == ScanStatus.CODE_SCANED, "掃碼失敗");
// 3. 移動端確認登錄
mvc.perform(get("/scanlogin/appConfirm").param("token", token).param("loginID", "test01"));
// 4. PC端登錄成功
Assert.isTrue(check(token).getStatusCode() == ScanStatus.CODE_LOGIN_SUCCESS, "登錄失敗");
}
/**
* 掃碼登錄超時測試方法宫蛆,示例代碼中超時時間為60s
* @throws Exception
*/
@Test
public void scanLoginTimeoutTest() throws Exception {
// 1. 獲取token(二維碼)
String token = mvc.perform(get("/scanlogin/generate")).andReturn().getResponse().getContentAsString();
Assert.isTrue(check(token).getStatusCode() == ScanStatus.CODE_GENERATE, "生成二維碼失敗");
//2. 等待超時
Thread.sleep(60*1000);
//3. PC端check驗證
Assert.isTrue(check(token).getStatusCode() == ScanStatus.CODE_EXPIRED, "超時校驗失敗");
//4. 移動端掃碼驗證
mvc.perform(get("/scanlogin/appScan").param("token", token));
Assert.isTrue(check(token).getStatusCode() == ScanStatus.CODE_EXPIRED, "超時校驗失敗");
}
private ScanStatus check(String token) throws Exception {
String checkStr = mvc.perform(post("/scanlogin/check").param("token", token)).andReturn().getResponse()
.getContentAsString();
return this.json.parseObject(checkStr);
}
}