优雅可拓展的登录封装,让你远离if-else

前言

Hi,大家好,,相信各位码农朋友在搭建从0到1项目时在搭建完基建等任务后,最先去做的都是去搭建系统的用户体系,那么每一个码农朋友都会去编码属于自己系统的一套用户登录注册体系;但是登录方式极其多样,光小编一个人对接的就有google登录,苹果登录,手机验证码,微信验证码登录,微博登录等各种各样的登录;

针对这么多的登录方式,小编是如何进行功能接入的呢?(Ps:直接switch-case和if-else接入不香吗,又不是不能用,这其实是小编做功能时最真实的想法了,但是迫于团队老大哥的强大气场,小编自然不敢这样硬核编码了),接下来就让秃头小编和大伙一起分享一下是怎么让普普通通的登录也能玩出逼格的!(由于篇幅过长,接下来进入硬核时刻,希望各位能挺住李云龙二营长的意大利跑前进哈)

功能实现

技术栈:SpringBoot,MySQL,MyBatisPlus,hutool,guava,Redis,Jwt,Springboot-emial等;

sdk组件架构

项目结构包:

  1. tea-api(前台聚合服务) 复制代码

  2. tea-mng(后管聚合服务) 复制代码

  3. tea-sdk(SpringBoot相关组件模块) 复制代码

  4. tea-common(公共模块,提供一些工具类支持和公有类引用) 复制代码

项目结构引用关系: sdk引入了common包,api和mng引入了sdk包;

封装思路

思路一:通过前端登录路由请求头key值通过反射生成对应的LoginProvider类来进行登录业务逻辑的执行。具体的做法如下:

  1. 在classPath路径下新增一个json/Provider.json文件,json格式如下图所示:

  1. 定义具体的Provider继承基类Provider,小编这里定义了一般业务系统最常对接的集中Provider(PS:由于google登录和App登录主要是用于对接海外业务,因此小编这里就没把集成代码放出来了)如下图是小编定义的几个Provider:

其中UserLoginService是所有Provider的基类接口,封装了模板方法。EmialLoginProvider类主要是实现邮箱验证码登录,PasswordProvider用于实现账号密码登录,PhoneLoginProvider是用于手机号验证码登录.WbLoginProvider用于实现PC端微博授权登录,WxLoginPrvider用于实现微信PC端授权登录;

3.EmailLoginProvider和PhoneLoginProvider需要用到验证码校验,因此需要实现UserLoginService接口的验证码获取,并将获取到的验证码存储到redis中;

4.将前端的路由gateWay作为key值,需要加载的动态类名作为value值。定义一个LoginService业务处理类,类中定义一个Map缓存对象,在bean注入加载到ioc容器时,通过读取解析json文件对Map缓存进行反射属性注入,该设计理念参考了Springboot的SPI注入原理以此实现对Provider的可拔插操作;

思路二:

  1. 通过SpringBoot事件监听机制,通过前端路由请求头的key值发布生成不同的ApplicationEvent事件,利用事件监听对业务处理解耦;

  2. 定义具体的Event事件以及Listener;

  3. 根据前端路由gateWay值生成需要发布的Event事件基类,在具体的listener类上根据@EventListener注解来对具体的事件进行监听处理;

思路对比

思路一通过模板+工厂+反射等设计模式的原理对多方式登录方式来达到解耦和拓展,从而规避了开发人员大量的if-else或switch等硬编码的方式,思路二通过模板+工厂+事件监听机制等设计模式也做到了对多方式登录的解耦和拓展,两种思路均能做到延伸代码的拓展性的作用;

封装源码

1.基类UserLoginService

/**
 * 登录
 *
 * @param req 登录请求体
 * @return
 */
LoginResp login(LoginReq req);


/**
 * 验证码获取
 *
 * @param req 登录请求体
 * @return
 */
LoginResp vertifyCode(LoginReq req);
复制代码

2.拓展类Provider代码

public class EmailLoginProvider implements UserLoginService {

    @Override
    public LoginResp login(LoginReq req) {
        UserService userService = SpringUtil.getBean(UserService.class);
        User user = userService.getOne(Wrappers.lambdaQuery(new User()).eq(User::getEmail, req.getEmail()).eq(User::getStatus, 1));
        if (Objects.isNull(user)) {
            return null;
        }
        String redisKey = req.getEmail();
        RedisTemplate redisTemplate = SpringUtil.getBean(StringRedisTemplate.class);
        String code = (String) redisTemplate.opsForValue().get(redisKey);
        if (StringUtils.isEmpty(code)||!code.equals(req.getCode())) {
            return null;
        }
        String token = JwtParse.getoken(user);
        LoginResp resp = new LoginResp();
        resp.setToken(token);
        return resp;
    }

    @Override
    public LoginResp vertifyCode(LoginReq req) {
        String redisKey = req.getEmail();
        LoginResp resp = new LoginResp();
        RedisTemplate redisTemplate = SpringUtil.getBean(StringRedisTemplate.class);
        String code = (String) redisTemplate.opsForValue().get(redisKey);
        if (StringUtils.isNotEmpty(code)) {
            resp.setCode(code);
            return resp;
        }
        MailService mailService = SpringUtil.getBean(MailService.class);
        String mailCode = CodeUtils.make(4);
        mailService.sendMail(req.getEmail(), "邮箱验证码", mailCode);
        redisTemplate.opsForValue().set(req.getEmail(), mailCode);
        return resp;
    }
}
复制代码

public class PasswordProvider implements UserLoginService {

    @Override
    public LoginResp login(LoginReq req) {
        UserService userService = SpringUtil.getBean(UserService.class);
        User user = userService.getOne(Wrappers.lambdaQuery(new User()).eq(User::getPassword, req.getPassword()).eq(User::getStatus, 1));
        if (Objects.isNull(user)) {
            return null;
        }
        String token = JwtParse.getoken(user);
        LoginResp resp = new LoginResp();
        resp.setToken(token);
        return resp;
    }

    @Override
    public LoginResp vertifyCode(LoginReq req) {
        return null;
    }
}
复制代码

public class PhoneLoginProvider implements UserLoginService {

    @Override
    public LoginResp login(LoginReq req) {
        UserService userService = SpringUtil.getBean(UserService.class);
        User user = userService.getOne(Wrappers.lambdaQuery(new User()).eq(User::getPhone, req.getPhone()).eq(User::getStatus, 1));
        if (Objects.isNull(user)) {
            return null;
        }
        String redisKey = req.getPhone();
        RedisTemplate redisTemplate = SpringUtil.getBean(RedisTemplate.class);
        String code = (String) redisTemplate.opsForValue().get(redisKey);
        if (!code.equals(req.getCode())) {
            return null;
        }
        String token = JwtParse.getoken(user);
        LoginResp resp = new LoginResp();
        resp.setToken(token);
        return resp;
    }

    @Override
    public LoginResp vertifyCode(LoginReq req) {
        String redisKey = req.getPhone();
        LoginResp resp = new LoginResp();
        RedisTemplate redisTemplate = SpringUtil.getBean(RedisTemplate.class);
        String code = (String) redisTemplate.opsForValue().get(redisKey);
        if (StringUtils.isNotEmpty(code)) {
            resp.setCode(code);
            return resp;
        }
        MailService mailService = SpringUtil.getBean(MailService.class);
        String mailCode = CodeUtils.make(4);
        mailService.sendMail(req.getPhone(), "手机登录验证码", mailCode);
        redisTemplate.opsForValue().set(req.getEmail(), mailCode);
        return resp;
    }
}
复制代码

public class WxLoginProvider implements UserLoginService {

    @Override
    public LoginResp login(LoginReq req) {
        WxService wxService = SpringUtil.getBean(WxService.class);
        WxReq wxReq = new WxReq();
        wxReq.setCode(req.getAuthCode());
        WxResp token = wxService.getAccessToken(wxReq);
        String accessToken = token.getAccessToken();
        if (StringUtils.isEmpty(accessToken)) {

        }
        wxReq.setOpenid(token.getOpenid());
        WxUserInfoResp userInfo = wxService.getUserInfo(wxReq);
        //根据unionId和openid查找一下当前用户是否已经存在系统,如果不存在,帮其注册这里单纯是为了登录;
        UserService userService = SpringUtil.getBean(UserService.class);
        User user = userService.getOne(Wrappers.lambdaQuery(new User()).eq(User::getOpenId, token.getOpenid()).eq(User::getUnionId, token.getUnionId()));
        if (Objects.isNull(user)) {

        }
        String getoken = JwtParse.getoken(user);
        LoginResp resp = new LoginResp();
        resp.setToken(getoken);
        return resp;
    }

    @Override
    public LoginResp vertifyCode(LoginReq req) {
        return null;
    }
}
复制代码

3.接口暴露Service--LoginService源码

@Service
@Slf4j
public class LoginService {

    private Map<String, UserLoginService> loginServiceMap = new ConcurrentHashMap<>();

    @PostConstruct
    public void init() {
        try {
            List<JSONObject> jsonList = JSONArray.parseObject(ResourceUtil.getResource("json/Provider.json").openStream(), List.class);
            for (JSONObject object : jsonList) {
                String key = object.getString("key");
                String className = object.getString("value");
                Class loginProvider = Class.forName(className);
                UserLoginService loginService = (UserLoginService) loginProvider.newInstance();
                loginServiceMap.put(key, loginService);
            }
        } catch (Exception e) {
            log.info("[登录初始化异常]异常堆栈信息为:{}", ExceptionUtils.parseStackTrace(e));
        }
    }

    /**
     * 统一登录
     *
     * @param gateWayRoute 路由路径
     * @param req          登录请求
     * @return
     */
    public RetunrnT<LoginResp> login(String gateWayRoute, LoginReq req) {
        UserLoginService userLoginService = loginServiceMap.get(gateWayRoute);
        LoginResp loginResp = userLoginService.login(req);
        return RetunrnT.success(loginResp);
    }


    /**
     * 验证码发送
     *
     * @param gateWayRoute 路由路径
     * @param req          登录请求
     * @return
     */
    public RetunrnT<LoginResp> vertifyCode(String gateWayRoute, LoginReq req) {
        UserLoginService userLoginService = loginServiceMap.get(gateWayRoute);
        LoginResp resp = userLoginService.vertifyCode(req);
        return RetunrnT.success(resp);
    }

}
复制代码

4.邮件发送Service具体实现--MailService

public interface MailService {

    /**
     * 发送邮件
     *
     * @param to      收件人
     * @param subject 主题
     * @param content 内容
     */
    void sendMail(String to, String subject, String content);
}
复制代码

@Service
@Slf4j
public class MailServiceImpl implements MailService {

    /**
     * Spring Boot 提供了一个发送邮件的简单抽象,直接注入即可使用
     */
    @Resource
    private JavaMailSender mailSender;
    /**
     * 配置文件中的发送邮箱
     */
    @Value("${spring.mail.from}")
    private String from;

    @Override
    @Async
    public void sendMail(String to, String subject, String content) {
//创建一个邮箱消息对象
        SimpleMailMessage message = new SimpleMailMessage();
        //邮件发送人
        message.setFrom(from);
        //邮件接收人
        message.setTo(to);
        //邮件主题
        message.setSubject(subject);
        //邮件内容
        message.setText(content);
        //发送邮件
        mailSender.send(message);
        log.info("邮件发成功:{}", message.toString());
    }
}
复制代码

5.token生成JsonParse类

private static final String SECRECTKEY = "zshsjcbchsssks123";

public static String getoken(User user) {
    //Jwts.builder()生成
    //Jwts.parser()验证
    JwtBuilder jwtBuilder = Jwts.builder()
            .setId(user.getId() + "")
            .setSubject(JSON.toJSONString(user))    //用户对象
            .setIssuedAt(new Date())//登录时间
            .signWith(SignatureAlgorithm.HS256, SECRECTKEY).setExpiration(new Date(System.currentTimeMillis() + 86400000));
    //设置过期时间
    //前三个为载荷playload 最后一个为头部 header
    log.info("token为:{}", jwtBuilder.compact());
    return jwtBuilder.compact();
}
复制代码

6.微信认证授权Service---WxService


public interface WxService {

    /**
     * 通过code获取access_token
     */
    WxResp getAccessToken(WxReq req);

    /**
     * 通过accessToken获取用户信息
     */
    WxUserInfoResp getUserInfo(WxReq req);
}
复制代码

@Service
@Slf4j
public class WxServiceImpl implements WxService {

    @Resource
    private WxConfig wxConfig;


    @Override
    public WxResp getAccessToken(WxReq req) {
        req.setAppid(wxConfig.getAppid());
        req.setSecret(wxConfig.getSecret());
        Map map = JSON.parseObject(JSON.toJSONString(req), Map.class);
        WxResp wxResp = JSON.parseObject(HttpUtil.createGet(wxConfig.getTokenUrl()).formStr(map).execute().body(), WxResp.class);
        return wxResp;
    }

    @Override
    public WxUserInfoResp getUserInfo(WxReq req) {
        req.setAppid(wxConfig.getAppid());
        req.setSecret(wxConfig.getSecret());
        Map map = JSON.parseObject(JSON.toJSONString(req), Map.class);
        return JSON.parseObject(HttpUtil.createGet(wxConfig.getGetUserUrl()).formStr(map).execute().body(), WxUserInfoResp.class);
    }
}
复制代码

功能演练

项目总结

相信很多小伙伴在平时开发过程中都能看到一定的业务硬核代码,前期设计不合理,后续开发只能在前人的基础上不断的进行if-else或者switch来进行业务的功能拓展,千里之行基于跬步,地基不稳注定是要地动山摇的,希望在接下来的时光,小编也能不断提升自己的水平,写出更多有水准的代码;

碎碎念时光

首先很感谢能看完全篇幅的各位老铁兄弟们,希望本篇文章能对各位和小编一样码农有所帮助,当然如果各位技术大大对这模块做法有更优质的做法的,也欢迎各位技术大大能在评论区留言探讨,写在最后~~~~~~ 创作不易,希望各位老铁能不吝惜于自己的手指,帮秃头点下您宝贵的赞把!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值