fix 修复 一些发现的小bug
- 短信验证码限流 key 改为 #phoneNumber,验证码改为发送成功后再写 Redis,失败消息避免 NPE - 邮箱验证码改为邮件发送成功后再写 Redis - ValidatorUtils 增加 NPE 校验 - 部门导入路径不存在时明确报错,不再静默转成 null - 重复提交切面完整扫描数组、集合、Map 中的过滤对象 - Excel 级联下拉补齐一级选项的 Excel 名称规则校验和重复校验
This commit is contained in:
@@ -58,7 +58,7 @@ public class CaptchaController {
|
|||||||
* @param phoneNumber 用户手机号
|
* @param phoneNumber 用户手机号
|
||||||
* @return 操作结果
|
* @return 操作结果
|
||||||
*/
|
*/
|
||||||
@RateLimiter(key = "#phonenumber", time = 60, count = 1)
|
@RateLimiter(key = "#phoneNumber", time = 60, count = 1)
|
||||||
@GetMapping("/resource/sms/code")
|
@GetMapping("/resource/sms/code")
|
||||||
public R<Void> smsCode(@NotBlank(message = "{user.phonenumber.not.blank}") String phoneNumber) {
|
public R<Void> smsCode(@NotBlank(message = "{user.phonenumber.not.blank}") String phoneNumber) {
|
||||||
if (!RegexValidator.isMobile(phoneNumber)) {
|
if (!RegexValidator.isMobile(phoneNumber)) {
|
||||||
@@ -66,7 +66,6 @@ public class CaptchaController {
|
|||||||
}
|
}
|
||||||
String key = GlobalConstants.CAPTCHA_CODE_KEY + phoneNumber;
|
String key = GlobalConstants.CAPTCHA_CODE_KEY + phoneNumber;
|
||||||
String code = RandomUtil.randomNumbers(4);
|
String code = RandomUtil.randomNumbers(4);
|
||||||
RedisUtils.setCacheObject(key, code, Duration.ofMinutes(Constants.CAPTCHA_EXPIRATION));
|
|
||||||
// 验证码模板id 自行处理 (查数据库或写死均可)
|
// 验证码模板id 自行处理 (查数据库或写死均可)
|
||||||
String templateId = "";
|
String templateId = "";
|
||||||
LinkedHashMap<String, String> map = new LinkedHashMap<>(1);
|
LinkedHashMap<String, String> map = new LinkedHashMap<>(1);
|
||||||
@@ -75,8 +74,10 @@ public class CaptchaController {
|
|||||||
SmsResponse smsResponse = smsBlend.sendMessage(phoneNumber, templateId, map);
|
SmsResponse smsResponse = smsBlend.sendMessage(phoneNumber, templateId, map);
|
||||||
if (!smsResponse.isSuccess()) {
|
if (!smsResponse.isSuccess()) {
|
||||||
log.error("验证码短信发送异常 => {}", smsResponse);
|
log.error("验证码短信发送异常 => {}", smsResponse);
|
||||||
return R.fail(smsResponse.getData().toString());
|
Object data = smsResponse.getData();
|
||||||
|
return R.fail(data == null ? "验证码短信发送失败" : data.toString());
|
||||||
}
|
}
|
||||||
|
RedisUtils.setCacheObject(key, code, Duration.ofMinutes(Constants.CAPTCHA_EXPIRATION));
|
||||||
return R.ok();
|
return R.ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -107,13 +108,13 @@ public class CaptchaController {
|
|||||||
public void emailCodeImpl(String email) {
|
public void emailCodeImpl(String email) {
|
||||||
String key = GlobalConstants.CAPTCHA_CODE_KEY + email;
|
String key = GlobalConstants.CAPTCHA_CODE_KEY + email;
|
||||||
String code = RandomUtil.randomNumbers(4);
|
String code = RandomUtil.randomNumbers(4);
|
||||||
RedisUtils.setCacheObject(key, code, Duration.ofMinutes(Constants.CAPTCHA_EXPIRATION));
|
|
||||||
try {
|
try {
|
||||||
MailBuilder.of()
|
MailBuilder.of()
|
||||||
.to(email)
|
.to(email)
|
||||||
.subject("登录验证码")
|
.subject("登录验证码")
|
||||||
.text("您本次验证码为:" + code + ",有效性为" + Constants.CAPTCHA_EXPIRATION + "分钟,请尽快填写。")
|
.text("您本次验证码为:" + code + ",有效性为" + Constants.CAPTCHA_EXPIRATION + "分钟,请尽快填写。")
|
||||||
.send();
|
.send();
|
||||||
|
RedisUtils.setCacheObject(key, code, Duration.ofMinutes(Constants.CAPTCHA_EXPIRATION));
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("验证码短信发送异常 => {}", e.getMessage());
|
log.error("验证码短信发送异常 => {}", e.getMessage());
|
||||||
throw new ServiceException(e.getMessage());
|
throw new ServiceException(e.getMessage());
|
||||||
|
|||||||
+3
@@ -26,6 +26,9 @@ public class ValidatorUtils {
|
|||||||
* @throws ConstraintViolationException 如果校验不通过,则抛出参数校验异常
|
* @throws ConstraintViolationException 如果校验不通过,则抛出参数校验异常
|
||||||
*/
|
*/
|
||||||
public static <T> void validate(T object, Class<?>... groups) {
|
public static <T> void validate(T object, Class<?>... groups) {
|
||||||
|
if (object == null) {
|
||||||
|
throw new RuntimeException("请求参数不能为空");
|
||||||
|
}
|
||||||
Set<ConstraintViolation<T>> validate = VALID.validate(object, groups);
|
Set<ConstraintViolation<T>> validate = VALID.validate(object, groups);
|
||||||
if (!validate.isEmpty()) {
|
if (!validate.isEmpty()) {
|
||||||
throw new ConstraintViolationException("参数校验异常", validate);
|
throw new ConstraintViolationException("参数校验异常", validate);
|
||||||
|
|||||||
+25
-4
@@ -51,6 +51,9 @@ public class DropDownOptions {
|
|||||||
* 分隔符
|
* 分隔符
|
||||||
*/
|
*/
|
||||||
private static final String DELIMITER = "_";
|
private static final String DELIMITER = "_";
|
||||||
|
private static final String OPTION_PART_REGEX = "^[A-Za-z0-9\\u4e00-\\u9fa5]+$";
|
||||||
|
private static final String EXCEL_NAME_REGEX = "^[A-Za-z_\\u4e00-\\u9fa5][A-Za-z0-9_\\u4e00-\\u9fa5]*$";
|
||||||
|
private static final String CELL_REFERENCE_REGEX = "^[A-Za-z]{1,3}[1-9][0-9]*$";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 创建只有一级的下拉选
|
* 创建只有一级的下拉选
|
||||||
@@ -69,10 +72,9 @@ public class DropDownOptions {
|
|||||||
*/
|
*/
|
||||||
public static String createOptionValue(Object... vars) {
|
public static String createOptionValue(Object... vars) {
|
||||||
StringBuilder stringBuffer = new StringBuilder();
|
StringBuilder stringBuffer = new StringBuilder();
|
||||||
String regex = "^[\\S\\d\\u4e00-\\u9fa5]+$";
|
|
||||||
for (int i = 0; i < vars.length; i++) {
|
for (int i = 0; i < vars.length; i++) {
|
||||||
String var = StrUtil.trimToEmpty(Convert.toStr(vars[i]));
|
String var = StrUtil.trimToEmpty(Convert.toStr(vars[i]));
|
||||||
if (!var.matches(regex)) {
|
if (!var.matches(OPTION_PART_REGEX)) {
|
||||||
throw new ServiceException("选项数据不符合规则,仅允许使用中英文字符以及数字");
|
throw new ServiceException("选项数据不符合规则,仅允许使用中英文字符以及数字");
|
||||||
}
|
}
|
||||||
stringBuffer.append(var);
|
stringBuffer.append(var);
|
||||||
@@ -81,10 +83,29 @@ public class DropDownOptions {
|
|||||||
stringBuffer.append(DELIMITER);
|
stringBuffer.append(DELIMITER);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (stringBuffer.toString().matches("^\\d_*$")) {
|
String optionValue = stringBuffer.toString();
|
||||||
|
validateOptionValue(optionValue);
|
||||||
|
return optionValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 校验级联下拉选项值是否可作为 Excel 名称管理器名称。
|
||||||
|
*
|
||||||
|
* @param optionValue 选项值
|
||||||
|
*/
|
||||||
|
public static void validateOptionValue(String optionValue) {
|
||||||
|
if (StrUtil.isBlank(optionValue)) {
|
||||||
|
throw new ServiceException("选项数据不能为空");
|
||||||
|
}
|
||||||
|
if (optionValue.matches("^[0-9].*")) {
|
||||||
throw new ServiceException("禁止以数字开头");
|
throw new ServiceException("禁止以数字开头");
|
||||||
}
|
}
|
||||||
return stringBuffer.toString();
|
if (!optionValue.matches(EXCEL_NAME_REGEX)) {
|
||||||
|
throw new ServiceException("选项数据不符合Excel名称规则,仅允许中英文、数字或下划线,且不能以数字开头");
|
||||||
|
}
|
||||||
|
if (optionValue.matches(CELL_REFERENCE_REGEX)) {
|
||||||
|
throw new ServiceException("选项数据不能为Excel单元格引用");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
+14
@@ -202,6 +202,7 @@ public class ExcelDownHandler implements SheetWriteHandler {
|
|||||||
if (CollUtil.isEmpty(firstOptions)) {
|
if (CollUtil.isEmpty(firstOptions)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
validateLinkedOptionNames(firstOptions);
|
||||||
Map<String, List<String>> secoundOptionsMap = new HashMap<>();
|
Map<String, List<String>> secoundOptionsMap = new HashMap<>();
|
||||||
options.getNextOptions().forEach((k, v) -> secoundOptionsMap.put(k, new ArrayList<>(v)));
|
options.getNextOptions().forEach((k, v) -> secoundOptionsMap.put(k, new ArrayList<>(v)));
|
||||||
|
|
||||||
@@ -313,6 +314,19 @@ public class ExcelDownHandler implements SheetWriteHandler {
|
|||||||
currentLinkedOptionsSheetIndex++;
|
currentLinkedOptionsSheetIndex++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 校验级联下拉一级选项可作为 Excel 名称,且不能重复。
|
||||||
|
*/
|
||||||
|
private void validateLinkedOptionNames(List<String> firstOptions) {
|
||||||
|
Set<String> names = new HashSet<>();
|
||||||
|
for (String firstOption : firstOptions) {
|
||||||
|
DropDownOptions.validateOptionValue(firstOption);
|
||||||
|
if (!names.add(firstOption)) {
|
||||||
|
throw new ServiceException("级联下拉一级选项重复:" + firstOption);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <h2>额外表格形式的普通下拉框</h2>
|
* <h2>额外表格形式的普通下拉框</h2>
|
||||||
* 由于下拉框可选值数量过多,为提升Excel打开效率,使用额外表格形式做下拉
|
* 由于下拉框可选值数量过多,为提升Excel打开效率,使用额外表格形式做下拉
|
||||||
|
|||||||
+18
-3
@@ -24,6 +24,7 @@ import org.dromara.common.redis.utils.RedisUtils;
|
|||||||
import org.springframework.validation.BindingResult;
|
import org.springframework.validation.BindingResult;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
|
import java.lang.reflect.Array;
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@@ -150,16 +151,30 @@ public class RepeatSubmitAspect {
|
|||||||
public boolean isFilterObject(final Object o) {
|
public boolean isFilterObject(final Object o) {
|
||||||
Class<?> clazz = o.getClass();
|
Class<?> clazz = o.getClass();
|
||||||
if (clazz.isArray()) {
|
if (clazz.isArray()) {
|
||||||
return MultipartFile.class.isAssignableFrom(clazz.getComponentType());
|
if (MultipartFile.class.isAssignableFrom(clazz.getComponentType())) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
int length = Array.getLength(o);
|
||||||
|
for (int i = 0; i < length; i++) {
|
||||||
|
Object value = Array.get(o, i);
|
||||||
|
if (ObjectUtil.isNotNull(value) && isFilterObject(value)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
} else if (Collection.class.isAssignableFrom(clazz)) {
|
} else if (Collection.class.isAssignableFrom(clazz)) {
|
||||||
Collection collection = (Collection) o;
|
Collection collection = (Collection) o;
|
||||||
for (Object value : collection) {
|
for (Object value : collection) {
|
||||||
return value instanceof MultipartFile;
|
if (ObjectUtil.isNotNull(value) && isFilterObject(value)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else if (Map.class.isAssignableFrom(clazz)) {
|
} else if (Map.class.isAssignableFrom(clazz)) {
|
||||||
Map map = (Map) o;
|
Map map = (Map) o;
|
||||||
for (Object value : map.values()) {
|
for (Object value : map.values()) {
|
||||||
return value instanceof MultipartFile;
|
if (ObjectUtil.isNotNull(value) && isFilterObject(value)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return o instanceof MultipartFile || o instanceof HttpServletRequest || o instanceof HttpServletResponse
|
return o instanceof MultipartFile || o instanceof HttpServletRequest || o instanceof HttpServletResponse
|
||||||
|
|||||||
+6
-1
@@ -10,6 +10,7 @@ import org.apache.fesod.sheet.metadata.GlobalConfiguration;
|
|||||||
import org.apache.fesod.sheet.metadata.data.ReadCellData;
|
import org.apache.fesod.sheet.metadata.data.ReadCellData;
|
||||||
import org.apache.fesod.sheet.metadata.data.WriteCellData;
|
import org.apache.fesod.sheet.metadata.data.WriteCellData;
|
||||||
import org.apache.fesod.sheet.metadata.property.ExcelContentProperty;
|
import org.apache.fesod.sheet.metadata.property.ExcelContentProperty;
|
||||||
|
import org.dromara.common.core.exception.ServiceException;
|
||||||
import org.dromara.common.core.utils.SpringUtils;
|
import org.dromara.common.core.utils.SpringUtils;
|
||||||
import org.dromara.common.core.utils.StringUtils;
|
import org.dromara.common.core.utils.StringUtils;
|
||||||
import org.dromara.common.core.utils.TreeBuildUtils;
|
import org.dromara.common.core.utils.TreeBuildUtils;
|
||||||
@@ -93,7 +94,11 @@ public class DeptExcelConverter implements Converter<Long> {
|
|||||||
if (StringUtils.isBlank(deptName)) {
|
if (StringUtils.isBlank(deptName)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return getDeptMaps().nameToId().get(deptName);
|
Long deptId = getDeptMaps().nameToId().get(deptName);
|
||||||
|
if (deptId == null) {
|
||||||
|
throw new ServiceException("部门不存在:" + deptName);
|
||||||
|
}
|
||||||
|
return deptId;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user