前语
Hi,大家好,我是你们的秃头朋友程序员小甲,信任各位码农朋友在建立从0到1项目时在建立完基建等任务后,最早去做的都是去建立体系的用户体系,那么每一个码农朋友都会去编码归于自己体系的一套用户登录注册体系;可是登录方法极端多样,光小甲一个人对接的就有google登录,苹果登录,手机验证码,微信验证码登录,微博登录等各种各样的登录;
针对这么多的登录方法,小甲是怎么进行功用接入的呢?(Ps:直接switch-case和if-else接入不香吗,又不是不能用,这其实是小甲做功用时最真实的主意了,可是迫于团队老大哥的强大气场,小甲自然不敢这样硬核编码了),接下来就让秃头小甲和大伙一起共享一下是怎么让普普通通的登录也能玩出逼格的!(由于篇幅过长,接下来进入硬核时刻,期望各位能挺住李云龙二营长的意大利跑前进哈)
功用完成
技术栈:SpringBoot,MySQL,MyBatisPlus,hutool,guava,Redis,Jwt,Springboot-emial等;
sdk组件架构
项目结构包:
-
tea-api(前台聚合服务)
-
tea-mng(后管聚合服务)
-
tea-sdk(SpringBoot相关组件模块)
-
tea-common(公共模块,供给一些东西类支撑和公有类引用)
项目结构引用联系: sdk引入了common包,api和mng引入了sdk包;
封装思路
思路一:经过前端登录路由恳求头key值经过反射生成对应的LoginProvider类来进行登录事务逻辑的履行。详细的做法如下:
- 在classPath路径下新增一个json/Provider.json文件,json格式如下图所示:
- 界说详细的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的可拔插操作;
思路二:
- 经过SpringBoot事情监听机制,经过前端路由恳求头的key值发布生成不同的ApplicationEvent事情,利用事情监听对事务处理解耦;
- 界说详细的Event事情以及Listener;
- 依据前端路由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来进行事务的功用拓宽,千里之行基于跬步,地基不稳注定是要地动山摇的,期望在接下来的韶光,秃头小甲也能不断提高自己的水平,写出更多有水准的代码;
碎碎念韶光
首要很感谢能看彻底篇幅的各位老铁兄弟们,期望本篇文章能对各位和秃头小甲相同码农有所帮助,当然假如各位技术大大对这模块做法有更优质的做法的,也欢迎各位技术大大能在谈论区留言探讨,写在最后~~~~~~ 创造不易,期望各位老铁能不吝惜于自己的手指,帮秃头点下您宝贵的赞把!