一、登录逻辑分析
登录的身份分为管理员和商家,管理员可以管理用户等信息,而商家只能管理自己的相关信息,如商品信息等。要实现不同的身份登录,使用简单工厂模式实现。简单工厂模式参考文章设计模式 | 简单工厂模式及典型应用 - 知乎 (zhihu.com)进行介绍学习。本项目中创建一个后端登录接口AdminLoginService.java,让管理员EmployeeService和商家MerchandiserService分别继承接口,对其中的方法分别进行实现。如下图所示:
图1-1 登录之简单工厂模式
Controller层接收前端发的请求,通过调用Service的方法,识别不同的身份,返回不同的数据,实现登录操作。
二、具体实现之基础准备
1、后台登录模型 LoginReq.java
@Getter
@Setter
@ApiModel(value = "后台登录模型")
public class LoginReq {
@NotBlank(message = "账号不能为空")
@ApiModelProperty(value = "账号",required = true)
private String account;
@NotBlank(message = "密码不能为空")
@ApiModelProperty(value = "密码",required = true)
private String password;
@NotBlank(message = "验证码不能为空")
@ApiModelProperty(value = "验证码",required = true)
private String code;
@NotBlank(message = "验证码令牌不能为空")
@ApiModelProperty(value = "验证码令牌",required = true)
private String uuid;
@NotBlank(message = "账号类型不能为空")
@ApiModelProperty(value = "账号类型(admin管理员,merchandiser商家)",required = true)
private String type;
}
2、后台登录响应模型 AdminLoginResp.java
@Getter
@Setter
@ApiModel(value = "后台登录响应模型")
public class AdminLoginResp {
@ApiModelProperty(value = "主键")
private Long id;
@ApiModelProperty(value = "用户名")
private String name;
@ApiModelProperty(value = "头像")
private String avatar;
@ApiModelProperty(value = "权限(admin管理员,merchandiser商家)")
private List<String> permissions;
@ApiModelProperty(value = "令牌")
private String uuid;
@ApiModelProperty(value = "令牌过期时间")
private Date expired;
@ApiModelProperty(value = "是否超级管理员")
private Long root;
/**
* 是否是管理员 JsonIgnore不要序列化,忽略该方法
* @return
*/
@JsonIgnore
public boolean isAdmin(){
return permissions.contains("admin");
}
}
3、常量 MarketConstans.java
public interface MarketConstans {
/**
* 员工标识
*/
String ADMIN="admin";
}
4、Redis常量 RedisConstans.java
public class RedisConstans {
/**
* 验证码的前缀
*/
public static final String CAPTCHA_KEY="captcha:";
}
三、具体实现
1、Controller层
通过前端发出的请求,注入AdminLoginService层,调用其中的方法,实现不同身份的登录。
权限的验证使用了Sa-Token,具体使用将在后续文章介绍。
@RestController
@RequestMapping(value = "/api")
@Api(tags = "后台-管理员和商家登录Api")
public class AdminLoginController {
@Qualifier(value = "adminLoginServiceImpl")
@Autowired
private AdminLoginService adminLoginService;
@ApiOperation(value = "登录")
@PostMapping(value = "/login")
public R<String> login(@RequestBody @Valid LoginReq req){
//登录
AdminLoginResp adminLoginResp = this.adminLoginService.login(req);
//换令牌
String token=null;
//令牌有效期为1小时
long one_hour=1000L*60*60;
if (MarketConstans.ADMIN.equals(req.getType())){
//管理员换令牌
StpAdminUtil.login(adminLoginResp.getId(),one_hour);
//获取令牌
token=StpAdminUtil.getTokenValue();
}else {
//商家换令牌
StpMerchandiserUtil.login(adminLoginResp.getId(),one_hour);
//获取令牌
token=StpMerchandiserUtil.getTokenValue();
}
return R.okHasData(token);
}
@SaAdminCheckLogin
@ApiOperation(value = "管理员登录信息")
@GetMapping(value = "/admin/info")
public R<AdminLoginResp> adminInfo(){
long userId = StpAdminUtil.getLoginIdAsLong();
AdminLoginResp resp=this.adminLoginService.getUserInfo(userId,MarketConstans.ADMIN);
return R.okHasData(resp);
}
@SaMerchandiserCheckLogin
@ApiOperation(value = "商家登录信息")
@GetMapping(value = "/merchandiser/info")
public R<AdminLoginResp> merchandierInfo(){
long userId = StpMerchandiserUtil.getLoginIdAsLong();
AdminLoginResp resp=this.adminLoginService.getUserInfo(userId,MarketConstans.MERCHANDISER);
return R.okHasData(resp);
}
@SaAdminCheckLogin
@ApiOperation(value = "管理员登出")
@PostMapping(value = "/admin/logout")
public R adminLoginout(){
StpAdminUtil.logout();
return R.ok();
}
@SaMerchandiserCheckLogin
@ApiOperation(value = "商家登出")
@PostMapping(value = "/merchandiser/logout")
public R merchandiserLogout(){
StpMerchandiserUtil.logout();
return R.ok();
}
}
2、后端登录接口 AdminLoginService.java
public interface AdminLoginService {
/**
* 登录方法
* @param req
* @return
*/
AdminLoginResp login(LoginReq req);
/**
* 根据id,类型获取信息
* @param userId
* @param type
* @return
*/
AdminLoginResp getUserInfo(long userId, String type);
}
3、后台登录接口的具体实现 AdminLoginServiceImpl.java
@Service
public class AdminLoginServiceImpl implements AdminLoginService {
/**
* 管理员业务逻辑
*/
@Autowired
private EmployeeService employeeService;
/**
* 商家业务逻辑
*/
@Autowired
private MerchandiserService merchandiserService;
@Autowired
private RedisTemplate redisTemplate;
@Override
public AdminLoginResp login(LoginReq req) {
//验证 验证码有效性
//得到redis的前缀
String redisKey=RedisConstans.CAPTCHA_KEY+req.getUuid();
//获取redis的值
Object redisValue = this.redisTemplate.opsForValue().get(redisKey);
//查看验证码的值是否存在
if (Objects.isNull(redisValue)){
throw new ServiceException(AckCode.CODE_NOT_FOUND);
}
//移除验证码
this.redisTemplate.delete(redisKey);
//验证码错误
if (!req.getCode().equals(redisValue.toString())){
throw new ServiceException(AckCode.CODE_ERROR);
}
//简单工厂方法
//管理员登录 or 商家登录
if (req.getType().equals(MarketConstans.ADMIN)){
return employeeService.login(req);
}else if (req.getType().equals(MarketConstans.MERCHANDISER)){
return merchandiserService.login(req);
}else {
throw new ServiceException(AckCode.SYSTEM_PARAM_FAIL);
}
}
@Override
public AdminLoginResp getUserInfo(long userId, String type) {
if (type.equals(MarketConstans.ADMIN)){
return employeeService.getUserInfo(userId,type);
}else{
return merchandiserService.getUserInfo(userId,type);
}
}
}
4、员工接口的实现 EmployeeServiceImpl.java
@Log4j2
@Service
public class EmployeeServiceImpl extends ServiceImpl<EmployeeMapper, Employee>
implements EmployeeService{
@Override
public AdminLoginResp login(LoginReq req) {
//构造查询语句
LambdaQueryWrapper<Employee> queryWrapper = new LambdaQueryWrapper<>();
//构建查询条件
queryWrapper.eq(Employee::getLoginName,req.getAccount());
//获取数据
Employee db = this.getOne(queryWrapper);
//判断数据是否为空
if (Objects.isNull(db)){
log.info(req.getAccount()+",不存在");
throw new ServiceException(AckCode.USERNAME_PASSWORD_ERROR);
}
//判断密码是否正确checkpw(明文密码,密文密码)
if (!BCrypt.checkpw(req.getPassword(),db.getLoginPwd())){
log.info(req.getPassword()+",密码错误");
throw new ServiceException(AckCode.USERNAME_PASSWORD_ERROR);
}
//判断状态是否已经被停用
if (MarketConstans.DISABLE.equals(db.getStatus())){
//1表示状态已经被停用
log.info(req.getAccount()+",已经被停用");
throw new ServiceException(AckCode.DEVICE_BANNED);
}
return getAdminLoginResp(db);
}
private AdminLoginResp getAdminLoginResp(Employee db) {
//构造自定义对象
AdminLoginResp resp = new AdminLoginResp();
resp.setId(db.getId());
resp.setName(db.getLoginName());
resp.setAvatar(db.getAvatar());
resp.setRoot(Long.parseLong(db.getRoot()));
resp.setPermissions(Collections.singletonList(MarketConstans.ADMIN));
return resp;
}
@Override
public AdminLoginResp getUserInfo(long userId, String type) {
Employee db = this.getById(userId);
//自定义对象
return getAdminLoginResp(db);
}
}
5、商家接口的实现 MerchandiserServiceImpl.java
@Log4j2
@Service
public class MerchandiserServiceImpl extends ServiceImpl<MerchandiserMapper, Merchandiser>
implements MerchandiserService{
@Override
public AdminLoginResp login(LoginReq req) {
//构建查询语句
LambdaQueryWrapper<Merchandiser> queryWrapper = new LambdaQueryWrapper<>();
//构建查询条件
queryWrapper.eq(Merchandiser::getLoginName,req.getAccount());
//获取数据
Merchandiser db = this.getOne(queryWrapper);
//判断数据是否存在
if (Objects.isNull(db)){
//数据不存在
log.info(req.getAccount()+",不存在");
throw new ServiceException(AckCode.USERNAME_PASSWORD_ERROR);
}
//判断密码是否正确
if (!BCrypt.checkpw(req.getPassword(),db.getLoginPwd())){
log.info(req.getPassword()+",密码不正确");
throw new ServiceException(AckCode.USERNAME_PASSWORD_ERROR);
}
//判断状态
if (MarketConstans.ACCOUNT_UNAUDITED.equals(db.getStatus())){
//如果等于0,则表示未被审核
log.info(req.getAccount()+",商家状态还未审核");
throw new ServiceException(AckCode.MERCHANDISER_ACCOUNT_NOT_VERIFIED);
}else if (MarketConstans.ACCOUNT_REVIEW_FAILED.equals(db.getStatus())){
//如果等于2,则表示审核未通过
log.info(req.getAccount()+",未审核通过");
throw new ServiceException(AckCode.MERCHANDISER_ACCOUNT_VERIFIED_FAILED);
}
//自定义对象
return getAdminLoginResp(db);
}
private AdminLoginResp getAdminLoginResp(Merchandiser db) {
AdminLoginResp resp = new AdminLoginResp();
resp.setId(db.getId());
resp.setName(db.getName());
resp.setAvatar(db.getPicture());
resp.setRoot(0L);
resp.setPermissions(Collections.singletonList(MarketConstans.MERCHANDISER));
return resp;
}
@Override
public AdminLoginResp getUserInfo(long userId, String type) {
Merchandiser db = this.getById(userId);
//自定义对象
return getAdminLoginResp(db);
}
}
四、Swagger调试界面
通过获取验证码,向后端发出请求,得到令牌,进行登录操作。
图4-1 swagger之管理员登录测试
图4-2 swagger之商家登录测试