扫码登录的简单实现-服务器端

服务器端使用springboot框架。

一、需要添加的pom依赖

本实例没有什么特殊的依赖。

    
        
            org.springframework.boot
            spring-boot-starter-web
        

        
            org.projectlombok
            lombok
            true
        
        
            org.springframework.boot
            spring-boot-starter-test
            test
        
    

二、yml配置文件。

只是端口设置。

server:
  port: 7021

三、跨域设置文件CorsConfig.java

package com.chris.sl.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;

/**
 * 跨域访问设置
 */
@Configuration
public class CorsConfig {
    private CorsConfiguration buildConfig() {
        CorsConfiguration corsConfiguration = new CorsConfiguration();
        corsConfiguration.addAllowedOrigin("*"); // 1允许任何域名使用
        corsConfiguration.addAllowedHeader("*"); // 2允许任何头
        corsConfiguration.addAllowedMethod("*"); // 3允许任何方法(post、get等) 
        return corsConfiguration;
    }

    @Bean
    public CorsFilter corsFilter() {
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", buildConfig()); // 4  
        return new CorsFilter(source);
    }
} 

四、需要用到的实例类

1. 用于传输登录信息的LoginUser.java

package com.chris.sl.model;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * create by: Chris Chan
 * create on: 2019/10/2 11:31
 * use for: 登录用户
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class LoginUser {
    private String username;
    private String password;
}

2. 用于存放扫码登录信息的LoginInfo.java

package com.chris.sl.model;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * create by: Chris Chan
 * create on: 2019/10/2 11:34
 * use for: 登录信息
 * 包括登录用户名和识别码过期时间
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class LoginInfo {
    private String username;
    private long expire;
    //是否已被占用
    private boolean occ;
}

3. 用于给前端包装返回识别码的ScanCode.java

package com.chris.sl.model;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * create by: Chris Chan
 * create on: 2019/10/2 13:13
 * use for:
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class ScanCode {
    private String code;
}

五、数据管理器DataManager.java,类似于数据库的管理。本文使用内存操作。实际业务可能需要操作redis等数据库,改变一下各个方法的具体实现即可。

package com.chris.sl.manager;

import com.chris.sl.model.LoginInfo;
import com.chris.sl.model.LoginUser;

import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

/**
 * create by: Chris Chan
 * create on: 2019/10/2 11:28
 * use for: 数据管理器
 * 因为目的只是为了走通逻辑,为方便移植,此处不用外部数据库,用内存数据模拟数据库
 * 如果改用mysql、redis,只需要改变方法的实现
 */
public class DataManager {
    //用户信息
    public static Map userMap = new HashMap<>(16);
    //登录识别码池
    public static Map loginInfoMap = new HashMap<>(16);

    /**
     * 初始化
     */
    public static void init() {
        userMap.put("kaly", new LoginUser("kaly", "123456"));
        userMap.put("chris", new LoginUser("chris", "123456"));
        userMap.put("will", new LoginUser("will", "123456"));
        userMap.put("chenfabao", new LoginUser("chenfabao", "123456"));
    }

    /**
     * 添加一个登录信息
     *
     * @param code
     */
    public static void addLoginInfo(String code) {
        //过期时间设置为5分钟
        long exp = LocalDateTime.now().plusMinutes(5).toInstant(ZoneOffset.of("+8")).toEpochMilli();
        loginInfoMap.put(code, new LoginInfo(null, exp, false));
    }

    /**
     * 占用登录信息
     *
     * @param code
     * @param username
     */
    public static void occLoginInfo(String code, String username) {
        LoginInfo loginInfo = loginInfoMap.get(code);
        if (null == loginInfo) {
            return;
        }
        loginInfo.setUsername(username);
        loginInfo.setOcc(true);
    }

    /**
     * 验证用户
     *
     * @param username
     * @param password
     * @param code
     * @return
     */
    public static boolean verification(String username, String password, String code) {
        LoginUser loginUser = userMap.get(username);
        //检查用户是否存在
        if (loginUser == null) {
            return false;
        }
        //检查密码是否正确
        if (!password.equalsIgnoreCase(loginUser.getPassword())) {
            return false;
        }
        //检查二维码识别码是否存在或过期
        LoginInfo loginInfo = loginInfoMap.get(code);
        long currentTime = LocalDateTime.now().toInstant(ZoneOffset.of("+8")).toEpochMilli();
        if (null == loginInfo || currentTime > loginInfo.getExpire()) {
            return false;
        }
        return true;
    }

    /**
     * 检查占用
     *
     * @param code
     * @return
     */
    public static boolean checkOcc(String code) {
        refreshLoginInfo();//整理
        LoginInfo loginInfo = loginInfoMap.get(code);
        if (null == loginInfo) {
            return false;
        }
        return loginInfo.isOcc();
    }

    /**
     * 获取登录用户名
     *
     * @param code
     * @return
     */
    public static LoginUser getUserByCode(String code) {
        LoginInfo loginInfo = loginInfoMap.get(code);
        if (null == loginInfo) {
            return null;
        }
        return userMap.get(loginInfo.getUsername());
    }

    /**
     * 整理登录信息
     * 主要是把已经过期的部分全部删除,否则数据会很大
     */
    public static void refreshLoginInfo() {
        //收集过期的code
        Set codeSet = new HashSet<>(16);
        //收集
        long currentTime = LocalDateTime.now().toInstant(ZoneOffset.of("+8")).toEpochMilli();
        for (Map.Entry entry : loginInfoMap.entrySet()) {
            if (currentTime > entry.getValue().getExpire()) {
                codeSet.add(entry.getKey());
            }
        }
        //删除
        for (String code : codeSet) {
            loginInfoMap.remove(code);
        }
    }
}

六、接口层UserApi.java,主要就是三个接口

package com.chris.sl.api;

import com.chris.sl.manager.DataManager;
import com.chris.sl.model.LoginUser;
import com.chris.sl.model.ScanCode;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.UUID;

/**
 * create by: Chris Chan
 * create on: 2019/10/2 11:11
 * use for: 接口
 */
@RestController
@RequestMapping("/api/user")
public class UserApi {
    /**
     * 登录 App调用
     * App扫描二维码之后,获得登录识别码,然后携带自己的帐号密码一起调用登录
     * 服务器在后台对三个信息进行验证,验证成功后把帐号放进登录呼叫池,等待前端下一次轮询
     *
     * @param username
     * @param password
     * @param code
     * @return
     */
    @GetMapping("/login")
    public Boolean login(String username, String password, String code) {
        //验证
        if (DataManager.verification(username, password, code)) {
            //占用
            DataManager.occLoginInfo(code, username);
            return true;
        }
        System.out.println("验证失败");
        return false;
    }

    /**
     * 获取扫描识别码 Web前端调用
     * 用于实现一个二维码
     * 同时服务器将这个二维码保存在数据库,同时设置好过期时间
     *
     * @return
     */
    @GetMapping("/getScanCode")
    public ScanCode getScanCode() {
        String code = UUID.randomUUID().toString();
        //添加到数据库
        DataManager.addLoginInfo(code);
        return new ScanCode(code);
    }

    /**
     * 询问登录 Web前端轮询
     * 1秒一次,后端根据code查看数据库,是否由用户绑定此code且通过了验证,等待登录
     * 如果成功,将用户名返回给前端
     *
     * @param code
     * @return
     */
    @GetMapping("/askLogin")
    public LoginUser askLogin(String code) {
        System.out.println(code);
        if (DataManager.checkOcc(code)) {
            return DataManager.getUserByCode(code);
        }
        return null;
    }
}

 

你可能感兴趣的:(Java)