feat(admin): 新增邀请码生成及注册校验功能
- 在管理员页面新增邀请码生成面板,支持限制使用次数和有效期 - 新增后端接口支持创建邀请码,邀请码存储在Redis并设置过期时间 - 用户注册接口新增邀请码参数,校验邀请码有效性和剩余使用次数 - 注册时成功使用邀请码后,Redis中对应邀请码的使用次数减1 - 登录接口及相关服务层逻辑新增邀请码字段支持 - 后端权限配置增加/admin路径的root角色校验 - 优化角色权限同步时Redis存储格式为列表类型 - 调整SaToken相关接口实现以支持角色ID转换逻辑
This commit is contained in:
@@ -24,24 +24,12 @@ public class SaTokenConfigure implements WebMvcConfigurer {
|
||||
.back();
|
||||
|
||||
SaRouter.match("/**")
|
||||
.notMatch("/class/list")
|
||||
.notMatch("/exam/words/get")
|
||||
.notMatch("/exam/words/detail")
|
||||
.notMatch("/exam/words/student/history")
|
||||
.notMatch("grade/list")
|
||||
.notMatch("/student/list")
|
||||
.notMatch("/student/detail")
|
||||
.notMatch("/studentLessonPlans/list")
|
||||
.notMatch("/studentLessonPlans/history")
|
||||
.notMatch("/student/analyze")
|
||||
.notMatch("/student/mastery/detail")
|
||||
.notMatch("/unit/list")
|
||||
.notMatch("/vocabulary/list")
|
||||
.notMatch("/vocabulary/student/detail")
|
||||
.notMatch("/plan/download")
|
||||
.notMatch("/login/**")
|
||||
.check(r -> StpUtil.checkLogin());
|
||||
|
||||
SaRouter.match("/admin/**")
|
||||
.check(r -> StpUtil.checkRole("root"));
|
||||
|
||||
}))
|
||||
.addPathPatterns("/**")
|
||||
.excludePathPatterns("/error");
|
||||
|
||||
@@ -28,7 +28,11 @@ public class StpInterfaceImpl implements StpInterface {
|
||||
|
||||
@Override
|
||||
public List<String> getRoleList(Object loginId, String loginType) {
|
||||
return userToRole((Long) loginId) ;
|
||||
long l = 0L;
|
||||
if (loginId instanceof String loginIdStr) {
|
||||
l = Long.parseLong(loginIdStr);
|
||||
}
|
||||
return userToRole(l);
|
||||
}
|
||||
|
||||
private List<String> userToRole(Long userId) {
|
||||
|
||||
@@ -4,7 +4,13 @@ public class UserRedisConstants {
|
||||
|
||||
public static final String USER_LOGIN_CODE = "user:login:code:";
|
||||
|
||||
public static final String USER_INVITATION_CODE = "user:invitation:code:";
|
||||
|
||||
public static String buildUserLoginCode(String phone) {
|
||||
return USER_LOGIN_CODE + phone;
|
||||
}
|
||||
|
||||
public static String buildUserInvitationCode(String code) {
|
||||
return USER_INVITATION_CODE + code;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
package com.yinlihupo.enlish.service.controller;
|
||||
|
||||
import com.yinlihupo.enlish.service.constant.UserRedisConstants;
|
||||
import com.yinlihupo.enlish.service.domain.dataobject.RoleDO;
|
||||
import com.yinlihupo.enlish.service.domain.dataobject.UserDO;
|
||||
import com.yinlihupo.enlish.service.model.vo.admin.CreateInvitationCodeReqVO;
|
||||
import com.yinlihupo.enlish.service.model.vo.admin.CreateInvitationCodeRspVO;
|
||||
import com.yinlihupo.enlish.service.model.vo.user.CreateUserReqVO;
|
||||
import com.yinlihupo.enlish.service.model.vo.user.FindUserListRepVO;
|
||||
import com.yinlihupo.enlish.service.model.vo.user.FindUserListRspVO;
|
||||
@@ -11,6 +14,7 @@ import com.yinlihupo.framework.biz.operationlog.aspect.ApiOperationLog;
|
||||
import com.yinlihupo.framework.common.response.PageResponse;
|
||||
import com.yinlihupo.framework.common.response.Response;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
@@ -19,6 +23,8 @@ import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Random;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@RequestMapping("/admin/")
|
||||
@RestController
|
||||
@@ -30,6 +36,8 @@ public class AdminController {
|
||||
private PasswordEncoder passwordEncoder;
|
||||
@Resource
|
||||
private RoleService roleService;
|
||||
@Resource
|
||||
private RedisTemplate<String, Object> redisTemplate;
|
||||
|
||||
@PostMapping("user/list")
|
||||
@ApiOperationLog(description = "查询用户列表")
|
||||
@@ -66,4 +74,18 @@ public class AdminController {
|
||||
|
||||
return Response.success();
|
||||
}
|
||||
|
||||
@PostMapping("user/create/invitation/code")
|
||||
@ApiOperationLog(description = "创建邀请码")
|
||||
public Response<CreateInvitationCodeRspVO> createInvitationCode(@RequestBody CreateInvitationCodeReqVO createInvitationCodeReqVO) {
|
||||
|
||||
String code = String.valueOf(new Random().nextInt(1000000));
|
||||
Integer limit = createInvitationCodeReqVO.getLimit();
|
||||
Integer expire = createInvitationCodeReqVO.getExpire();
|
||||
|
||||
redisTemplate.opsForValue().set(UserRedisConstants.buildUserInvitationCode(code), limit);
|
||||
redisTemplate.expire(UserRedisConstants.buildUserInvitationCode(code), expire, TimeUnit.DAYS);
|
||||
|
||||
return Response.success(CreateInvitationCodeRspVO.builder().invitationCode(code).build());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ public class LoginController {
|
||||
@ApiOperationLog(description = "登录")
|
||||
public Response<String> login(@RequestBody LoginReqVO loginReqVO) {
|
||||
try {
|
||||
loginService.login(loginReqVO.getPhone(), loginReqVO.getName(), loginReqVO.getPassword(), loginReqVO.getCode());
|
||||
loginService.login(loginReqVO.getPhone(), loginReqVO.getName(), loginReqVO.getPassword(), loginReqVO.getCode(), loginReqVO.getInvitationCode());
|
||||
return Response.success(StpUtil.getTokenInfo().getTokenValue());
|
||||
} catch (Exception e) {
|
||||
log.error("注册或登录失败 {}", e.getMessage());
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.yinlihupo.enlish.service.model.vo.admin;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@Data
|
||||
@Builder
|
||||
public class CreateInvitationCodeReqVO {
|
||||
|
||||
// 限制人数
|
||||
private Integer limit;
|
||||
// 有效期
|
||||
private Integer expire;
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.yinlihupo.enlish.service.model.vo.admin;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@Data
|
||||
@Builder
|
||||
public class CreateInvitationCodeRspVO {
|
||||
|
||||
private String invitationCode;
|
||||
|
||||
}
|
||||
@@ -15,4 +15,5 @@ public class LoginReqVO {
|
||||
private String name;
|
||||
private String password;
|
||||
private String code;
|
||||
private String invitationCode;
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ package com.yinlihupo.enlish.service.service;
|
||||
|
||||
public interface LoginService {
|
||||
|
||||
void login(String phone, String name, String reqPassword, String reqCode);
|
||||
void login(String phone, String name, String reqPassword, String reqCode, String invitationCode);
|
||||
|
||||
void sendVerificationCode(String phone);
|
||||
}
|
||||
|
||||
@@ -13,11 +13,14 @@ import com.yinlihupo.enlish.service.service.LoginService;
|
||||
import com.yinlihupo.framework.common.util.JsonUtils;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.Objects;
|
||||
|
||||
@Service
|
||||
@Slf4j
|
||||
@@ -28,20 +31,32 @@ public class LoginServiceImpl implements LoginService {
|
||||
@Resource
|
||||
private PasswordEncoder passwordEncoder;
|
||||
@Resource
|
||||
private StringRedisTemplate stringRedisTemplate;
|
||||
private RedisTemplate<String, Object> redisTemplate;
|
||||
@Resource
|
||||
private Client client;
|
||||
|
||||
@Override
|
||||
public void login(String phone, String name, String reqPassword, String reqCode) {
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void login(String phone, String name, String reqPassword, String reqCode, String invitationCode) {
|
||||
UserDO userDO = userDOMapper.selectByPhone(phone);
|
||||
log.info("userDO:{}", userDO);
|
||||
|
||||
String code = stringRedisTemplate.opsForValue().get(UserRedisConstants.buildUserLoginCode(phone));
|
||||
String code = JsonUtils.toJsonString(redisTemplate.opsForValue().get(UserRedisConstants.buildUserLoginCode(phone)));
|
||||
if (userDO == null) {
|
||||
if (code == null || !code.equals(reqCode)) {
|
||||
throw new RuntimeException("验证码错误");
|
||||
}
|
||||
|
||||
Object invitationObj = redisTemplate.opsForValue().get(UserRedisConstants.buildUserInvitationCode(invitationCode));
|
||||
if (invitationObj == null) {
|
||||
throw new RuntimeException("邀请码错误");
|
||||
}
|
||||
int invitationLimit = Integer.parseInt(JsonUtils.toJsonString(invitationObj));
|
||||
if (invitationLimit <= 0) {
|
||||
throw new RuntimeException("邀请码已使用完毕");
|
||||
}
|
||||
redisTemplate.opsForValue().set(UserRedisConstants.buildUserInvitationCode(invitationCode), invitationLimit - 1);
|
||||
|
||||
userDO = UserDO.builder()
|
||||
.phone(phone)
|
||||
.name(name)
|
||||
@@ -68,7 +83,7 @@ public class LoginServiceImpl implements LoginService {
|
||||
@Override
|
||||
public void sendVerificationCode(String phone) {
|
||||
String code = RandomUtil.randomNumbers(6);
|
||||
stringRedisTemplate.opsForValue().set(UserRedisConstants.buildUserLoginCode(phone), code, Duration.ofSeconds(60));
|
||||
redisTemplate.opsForValue().set(UserRedisConstants.buildUserLoginCode(phone), code, Duration.ofSeconds(60));
|
||||
String signName = "短信测试";
|
||||
String templateCode = "SMS_154950909";
|
||||
String templateParam = String.format("{\"code\":\"%s\"}", code);
|
||||
|
||||
@@ -48,7 +48,7 @@ public class RoleServiceImpl implements RoleService {
|
||||
List<RoleDO> roleDOs = roleIds.stream().map(roleId2RoleDO::get).toList();
|
||||
List<String> user2RoleKeys = roleDOs.stream().map(RoleDO::getRoleKey).toList();
|
||||
log.info("将用户 {} 的角色同步到 redis 中, {}", userId, roleKeys);
|
||||
redisTemplate.opsForValue().set(RoleConstants.buildUserRoleKey(userId), JsonUtils.toJsonString(user2RoleKeys));
|
||||
redisTemplate.opsForValue().set(RoleConstants.buildUserRoleKey(userId), user2RoleKeys);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user