目录
要实现解耦版的登录切换功能,策略模式+工厂模式+ConcurrentHashMap+在Bean的生命周期期间就进行策略绑定,带有Aware的接口进行方法增强
策略模式:策略模式是一种行为型设计模式,定义一系列算法,并使这些算法可以相互替换,使得算法的变化独立于使用算法的客户。策略模式通常包含一个策略接口和多个实现这个接口的策略类。
工厂模式:工厂是创建型模式,它的作用就是创建对象。将对象统一管理,在业务不同时在调用不同的对象进行使用
1.创建用户行为的策略接口
package com.example.barterbuddy.strategy;
import com.example.barterbuddy.dto.LoginFromDTO;
import com.example.barterbuddy.dto.Result;
/**
* 抽象策略类
*/
public interface UserGranter {
/**
* 获取数据
* @param loginReq 传入的参数
* @return map值
*/
Result login (LoginFromDTO loginReq);
}
2.实现具体行为,实现策略接口
2.1账号登录实现
package com.example.barterbuddy.strategy;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.bean.copier.CopyOptions;
import com.example.barterbuddy.dto.LoginFromDTO;
import com.example.barterbuddy.dto.Result;
import com.example.barterbuddy.dto.UserDTO;
import com.example.barterbuddy.pojo.Integral;
import com.example.barterbuddy.pojo.User;
import com.example.barterbuddy.service.IIntegralService;
import com.example.barterbuddy.service.UserService;
import com.example.barterbuddy.utils.PasswordEncoder;
import com.example.barterbuddy.utils.RedisConstants;
import com.example.barterbuddy.utils.RegexUtils;
import com.example.barterbuddy.utils.UserHolder;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import static com.example.barterbuddy.utils.RedisConstants.LOGIN_USER_TTL;
/**
* 策略:账号登录
**/
@Component
@Slf4j
public class AccountGranter implements UserGranter {
@Autowired
private StringRedisTemplate redisTemplate;
@Autowired
private UserService userService;
@Autowired
private IIntegralService integralService;
/**
*<p> </p>
*密码登录
*@author panwu
*@descripion
*@date 2023/8/19 14:53
*/
@Override
public Result login(LoginFromDTO loginReq) {
if (!RegexUtils.isPhoneInvalid(loginReq.getPhone())) {
return Result.fail("手机号错误");
}
//查询数据库的账号密码是否存在
/* User user = userService.query()
.eq("phone", loginReq.getPhone())
.eq("password", loginReq.getPassword()).one();
*/
User user = userService.query()
.eq("phone", loginReq.getPhone()).one();
//解密
Boolean matches = PasswordEncoder.matches(user.getPassword(), loginReq.getPassword());
log.debug("用户{}的密码{}解密{},查询出的密码为{}",
loginReq.getPhone(),loginReq.getPassword(),matches,user.getPassword());
if (!matches){
return Result.fail("用户名错误或密码不存在");
}
String token = UUID.randomUUID().toString();
//过滤重要信息
UserDTO userDTO = BeanUtil.copyProperties(user, UserDTO.class);
Map<String, Object> userMap = BeanUtil.beanToMap(userDTO, new HashMap<>(),
CopyOptions.create()
.setIgnoreNullValue(true)
.setFieldValueEditor((fieldName, fieldValue) -> fieldValue.toString()));
//生成token
String tokenKey = RedisConstants.LOGIN_USER_KEY+token;
//存入redis
redisTemplate.opsForHash().putAll(tokenKey,userMap);
redisTemplate.expire(tokenKey, LOGIN_USER_TTL, TimeUnit.MINUTES);
UserHolder.saveUser(userDTO);
return Result.ok(token);
}
}
该类中的登录行为你们自己写,不必照我的写
2.2邮箱登录实现
package com.example.barterbuddy.strategy;
import cn.hutool.core.lang.UUID;
import cn.hutool.core.util.RandomUtil;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.example.barterbuddy.dto.LoginFromDTO;
import com.example.barterbuddy.dto.Result;
import com.example.barterbuddy.pojo.User;
import com.example.barterbuddy.service.UserService;
import com.example.barterbuddy.utils.Email;
import com.example.barterbuddy.utils.PasswordEncoder;
import com.example.barterbuddy.utils.RedisConstants;
import com.example.barterbuddy.utils.RegexUtils;
import org.checkerframework.checker.units.qual.C;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
@Component
/**
*<p> </p>
*邮箱登录
*@author panwu
*@descripion
*@date 2023/8/31 9:01
*/
public class EmailGranter implements UserGranter {
@Autowired
private Email emails;
@Autowired
private UserService userService;
@Autowired
private StringRedisTemplate redisTemplate;
@Override
public Result login(LoginFromDTO loginReq) {
String email = loginReq.getEmail();
//判断是否为正确格式
if (!RegexUtils.isEmailInvalid(email)) {
return Result.fail("格式不正确");
}
String code = redisTemplate.opsForValue().get(RedisConstants.LOGIN_CODE_KEY + email);
if (!loginReq.getCode().equals(code)){
return Result.fail("验证码错误");
}
// User one = userService.query().eq("email", email).one();
//生成新密码
String password = RandomUtil.randomString(9);
User user = new User();
//修改密码
UpdateWrapper<User> userUpdateWrapper = new UpdateWrapper<>();
userUpdateWrapper.eq("email",email);
userUpdateWrapper.set("password", PasswordEncoder.encode(password));
userService.update(userUpdateWrapper);
try {
emails.sendEmail(email,password,"");
} catch (Exception e) {
e.printStackTrace();
}
return Result.ok();
}
}
2.3短信登录实现
package com.example.barterbuddy.strategy;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.bean.copier.CopyOptions;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import com.example.barterbuddy.dto.LoginFromDTO;
import com.example.barterbuddy.dto.Result;
import com.example.barterbuddy.dto.UserDTO;
import com.example.barterbuddy.pojo.Integral;
import com.example.barterbuddy.pojo.User;
import com.example.barterbuddy.service.IIntegralService;
import com.example.barterbuddy.service.UserService;
import com.example.barterbuddy.service.VipService;
import com.example.barterbuddy.service.impl.IntegralServiceImpl;
import com.example.barterbuddy.utils.GeoUtils;
import com.example.barterbuddy.utils.PasswordEncoder;
import com.example.barterbuddy.utils.RegexUtils;
import com.example.barterbuddy.utils.UserHolder;
import lombok.extern.log4j.Log4j;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.geo.Point;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import static com.example.barterbuddy.utils.RedisConstants.*;
/**
*<p> </p>
*短信登录
*@author panwu
*@descripion
*@date 2023/8/19 13:36
*/
@Component
@Slf4j
public class SmsGranter implements UserGranter{
@Autowired
private StringRedisTemplate redisTemplate;
@Autowired
private UserService userService;
@Autowired
private VipService vipService;
@Autowired
private IIntegralService integralService;
@Override
public Result login(LoginFromDTO loginFrom) {
String phone = loginFrom.getPhone();
// 1.校验手机号
if (!RegexUtils.isPhoneInvalid(phone)) {
// 2.如果不符合,返回错误信息
return Result.fail("手机号格式错误!");
}
//3.符合 获取验证码
String cachecode = redisTemplate.opsForValue().get(LOGIN_CODE_KEY+phone);
String code = loginFrom.getCode();
if (StrUtil.isEmpty(code) || !code.equals(cachecode)) {
//验证码为空或者错误
return Result.fail("请输入验证码");
}
//4.验证码正确
//5.根据手机号查询用户 select * from user where phone=?
User user =userService.query().eq("phone", phone).one();
//6.判断用户是否存在
if (BeanUtil.isEmpty(user)) {
//不存在用户,创建用户
user = createUserWithPhone(loginFrom);
}
//7.保存用户信息到 redis中
//7.1.随机生成token,作为登录令牌
String token= UUID.randomUUID().toString();
//7.2将User对象转为HashMap存储
UserDTO userDTO = BeanUtil.copyProperties(user, UserDTO.class);
log.debug("短信登录的用户消息为:{}",userDTO.toString());
Map<String, Object> userMap = BeanUtil.beanToMap(userDTO, new HashMap<>(),
CopyOptions.create()
.setIgnoreNullValue(true)
.setFieldValueEditor((fieldName, fieldValue) -> fieldValue.toString()));
//8.存入redis key=BarterBuddy:login:token:phone
String tokenkey = LOGIN_USER_KEY + token;
redisTemplate.opsForHash().putAll(tokenkey, userMap);
//设置token有效时间 36000L
redisTemplate.expire(tokenkey, LOGIN_USER_TTL, TimeUnit.MINUTES);
//存入Threadlocal
UserHolder.saveUser(userDTO);
//返回
return Result.ok(token);
}
//创建用户
private User createUserWithPhone(LoginFromDTO loginFrom) {
//删除Geo
redisTemplate.delete("BarterBuddy:cache:user:");
// 1.创建用户
User user = new User();
user.setPhone(loginFrom.getPhone());
user.setUsername("Buddy_" + RandomUtil.randomString(10));
user.setPassword(PasswordEncoder.encode("123456"));//默认密码 123456
//默认头像
user.setIcon("");
user.setAddress(loginFrom.getAddress());
String[] split = loginFrom.getAddress().split(",");
user.setAddressName(GeoUtils.getAddress(Double.valueOf(split[0]),Double.valueOf(split[1])).get(2));
// 2.保存用户
userService.save(user);
//创建积分表
Integral integral = new Integral();
integral.setUserid(user.getId());
integral.setRecenttime(LocalDateTime.now());
integral.setScore(10);
integralService.save(integral);
Collection<User> list = userService.list();
// System.out.println("list = " + list);
// ("id:店名:是否在站长":113.7505,23.0177)
Map<String, Point> UserMap = list.stream().collect(Collectors.toMap(user1 ->
user.getId()+":"+user.getAddressName()+":"+user.getIspresident(),
user1 -> {
String[] split1 = user.getAddress().split(",");
Point point = new Point(Double.valueOf(split1[0]), Double.valueOf(split1[1]));
return point;
}));
// System.out.println("UserMap = " + UserMap);
//PW TODO : 缓存时间,删除时机
redisTemplate.opsForGeo().add("BarterBuddy:cache:user:",UserMap);
return user;
}
}
3.yml文件中添加登录方式
右边的一定要和Bean名字一样
#登录的自定义信息 login: types: account: accountGranter #密码登录 sms: smsGranter #短信登录 we_chat: weChatGranter # email: emailGranter
4.读取yml文件的参数
package com.example.barterbuddy.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import java.util.Map;
/**
*<p> </p>
*新增读取数据配置类
*@author panwu
*@descripion
*@date 2023/8/19 13:45
*/
@Data
@Configuration
@ConfigurationProperties(prefix = "login")
public class LoginTypeConfig {
private Map<String,String> types;
}
5.操作策略的上下文环境类 工具类
package com.example.barterbuddy.strategy;
import com.example.barterbuddy.config.LoginTypeConfig;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* 操作策略的上下文环境类 工具类
* 将策略整合起来 方便管理
*/
@Component
public class UserLoginFactory implements ApplicationContextAware {
/**
*<p> </p>
*集合存入 及登录的类方法
*@author panwu
*@descripion
* 登录类型:String
* 类方法: UserGranter
*@date 2023/8/19 13:51
*/
private static Map<String, UserGranter> granterPool = new ConcurrentHashMap<>();
//读取配置信息
@Autowired
private LoginTypeConfig loginTypeConfig;
/**
* 从配置文件中读取策略信息存储到map中
* {
* account:accountGranter,
* sms:smsGranter,
* we_chat:weChatGranter
* }
*
* @param applicationContext
* @throws BeansException
*/
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
loginTypeConfig.getTypes().forEach((k, y) -> {
granterPool.put(k, (UserGranter) applicationContext.getBean(y));
});
}
/**
* 对外提供获取具体策略
* @param grantType 用户的登录方式,需要跟配置文件中匹配
* @return 具体策略
*/
public UserGranter getGranter(String grantType) {
UserGranter tokenGranter = granterPool.get(grantType);
return tokenGranter;
}
}
类中采用了ConcurrentHashMap来保证线程安全,
实现了bean的生命周期中的ApplicationContextAware接口,创建bean的时候进行增强
重写setApplicationContext()方法把名字和具体的策略绑定在一起
6.调用
6.1Controller层调用
/**
* 登录功能
* @param loginFrom 登录参数,包含手机号、验证码;或者手机号、密码
*/
@PostMapping(value = "/login")
public Result loginByphone(@RequestBody LoginFromDTO loginFrom) {
// log.debug("user/login:loginFromDTO=" + loginFrom);
//返回的是token值
return userService.login(loginFrom);
}
6.2Service层
@Override
public Result login(LoginFromDTO loginFrom) {
//策略模式实现的登录方式
UserGranter granter = userLoginFactory.getGranter(loginFrom.getTypes());
return granter.login(loginFrom);
}
loginFrom.getTypes()这个方法获取的是前端传的参数,参数值必须是yml文件中左边的
6.2实体类
package com.example.barterbuddy.dto;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
*<p> </p>
*接受前端登录信息
*@author panwu
*@descripion
*@date 2023/8/14 10:30
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class LoginFromDTO {
//手机号
private String phone;
//密码
private String password;
//邮箱号
private String email;
//验证码
private String code;
private String address;
//登录类型,短信登录:smsGranter,密码登录:accountGranter等
private String types;
}
以上就是整个Demo的实现了,各位仔细阅读=.=