Compare commits

28 Commits

Author SHA1 Message Date
疯狂的狮子Li
88f871002c !816 fix 修复 判断条件写反问题
Merge pull request !816 from 疯狂的狮子Li/dev
2025-12-24 05:27:11 +00:00
疯狂的狮子Li
b6eacfa5a8 !813 紧急修复Ip2Region InputStream读取函数导致的OOM问题
Merge pull request !813 from 疯狂的狮子Li/dev
2025-12-23 06:44:24 +00:00
疯狂的狮子Li
da4dffcfce !812 发布 5.5.2 版本 2025年最后一版
Merge pull request !812 from 疯狂的狮子Li/dev
2025-12-23 01:38:19 +00:00
疯狂的狮子Li
b58085fde1 !779 发布 5.5.1 正式版 日常依赖升级bug修复
Merge pull request !779 from 疯狂的狮子Li/dev
2025-10-28 03:21:27 +00:00
疯狂的狮子Li
b30ffa952f !766 发布 5.5.0 喜迎国庆
Merge pull request !766 from 疯狂的狮子Li/dev
2025-09-22 03:17:27 +00:00
疯狂的狮子Li
fd5d028e95 fix 修复 有某些无聊人士 对一个demo案例提漏洞 CVE-2025-6925
Signed-off-by: 疯狂的狮子Li <15040126243@163.com>
2025-07-04 01:10:49 +00:00
疯狂的狮子Li
64100cf1ff !712 发布 5.4.1 小步迭代修复问题
Merge pull request !712 from 疯狂的狮子Li/dev
2025-07-01 01:12:39 +00:00
疯狂的狮子Li
7e7d857ba5 Merge remote-tracking branch 'origin/dev' into 5.X 2025-05-29 18:18:20 +08:00
疯狂的狮子Li
d22b2a10df update 优化 PermissionService 无实现类也可以启动服务 2025-05-29 16:28:56 +08:00
疯狂的狮子Li
957a4d1fcd fix 修复 监听器 flowParams 为null报错问题 2025-05-29 16:28:56 +08:00
疯狂的狮子Li
49ef8378fe !691 发布 5.4.0 正式版
Merge pull request !691 from 疯狂的狮子Li/dev
2025-05-29 03:14:59 +00:00
疯狂的狮子Li
57dd6831d3 !664 发布 5.3.1 正式版
Merge pull request !664 from 疯狂的狮子Li/dev
2025-03-27 02:54:00 +00:00
疯狂的狮子Li
8aa60abb1f !663 回退 'Pull Request !662 : 发布 5.3.1 正式版'
* 回退 'Pull Request !662 : 发布 5.3.1 正式版'
2025-03-27 02:53:23 +00:00
疯狂的狮子Li
7a9f51fc7a !662 发布 5.3.1 正式版
* 🐳发布 5.3.1 正式版
* update 优化 删除无用配置
* fix 修复 excel模板导出数据被覆盖的问题
* update 优化 统一用户密码校验长度
* update mybatis-plus 3.5.10.1 => 3.5.11
* fix 修复 跨域未设置请求头问题(cloud版本不需要 vue版本需要)
2025-03-27 02:51:57 +00:00
疯狂的狮子Li
159e30c982 !661 发布 5.3.1-BETA2 公测版本
Merge pull request !661 from 疯狂的狮子Li/dev
2025-03-21 07:25:25 +00:00
疯狂的狮子Li
7334d91d6b !652 发布 5.3.1-BETA 公测版本
Merge pull request !652 from 疯狂的狮子Li/dev
2025-03-13 05:27:36 +00:00
疯狂的狮子Li
95c01301f6 !644 同步修复一些问题
Merge pull request !644 from 疯狂的狮子Li/dev
2025-02-07 06:19:28 +00:00
疯狂的狮子Li
296466fa13 !640 发布 5.3.0 新春版 祝大家新年快乐
Merge pull request !640 from 疯狂的狮子Li/dev
2025-01-24 05:08:28 +00:00
疯狂的狮子Li
3c8d864b5f !639 发布 5.3.0-BETA 公测版本
Merge pull request !639 from 疯狂的狮子Li/dev
2025-01-20 03:35:45 +00:00
疯狂的狮子Li
ea50a57602 update 优化 xss包装器 Parameter 处理 兼容某些容器不允许改参数的情况 2024-11-21 10:17:34 +08:00
疯狂的狮子Li
7e14b98676 reset 回滚错误修改
Signed-off-by: 疯狂的狮子Li <15040126243@163.com>
2024-10-28 09:46:28 +00:00
疯狂的狮子Li
015b406001 !591 发布 5.2.3 正式版
Merge pull request !591 from 疯狂的狮子Li/dev
2024-10-25 03:09:23 +00:00
疯狂的狮子Li
098d3347a0 !577 发布 5.2.2 正式版 安全性提升
Merge pull request !577 from 疯狂的狮子Li/dev
2024-08-26 03:43:59 +00:00
疯狂的狮子Li
08d4493994 update 优化 bug 模板 2024-07-15 15:19:22 +08:00
疯狂的狮子Li
367d739e2d Merge remote-tracking branch 'origin/5.X' into 5.X 2024-07-09 16:38:43 +08:00
疯狂的狮子Li
d6688a367d !562 ♥️发布 5.2.1 正式版本
Merge pull request !562 from 疯狂的狮子Li/dev
2024-07-09 02:42:40 +00:00
疯狂的狮子Li
0b331796e2 !551 ♥️发布 5.2.0 正式版本
Merge pull request !551 from 疯狂的狮子Li/dev
2024-06-20 02:10:15 +00:00
疯狂的狮子Li
456620b638 !549 ♥️发布 5.2.0-BETA2 公测版本
Merge pull request !549 from 疯狂的狮子Li/dev
2024-06-06 03:13:46 +00:00
31 changed files with 358 additions and 353 deletions

View File

@@ -35,6 +35,7 @@ MaxKey 业界领先单点登录产品 - https://gitee.com/dromara/MaxKey <br>
CCFlow 驰聘低代码-流程-表单 - https://gitee.com/opencc/RuoYi-JFlow <br>
数舵科技 软件定制开发APP小程序等 - http://www.shuduokeji.com/ <br>
引迈信息 软件开发平台 - https://www.jnpfsoft.com/index.html?from=plus-doc <br>
<font color="red">**启山商城系统 多租户商城源码可免费商用可二次开发 - https://www.73app.cn/** </font><br>
Mall4J 高质量Java商城系统 - https://www.mall4j.com/cn/?statId=11 <br>
aizuda flowlong 工作流 - https://gitee.com/aizuda/flowlong <br>
Ruoyi-Plus-Uniapp - https://ruoyi.plus <br>

View File

@@ -38,7 +38,7 @@
<bouncycastle.version>1.80</bouncycastle.version>
<justauth.version>1.16.7</justauth.version>
<!-- 离线IP地址定位库 -->
<ip2region.version>3.3.2</ip2region.version>
<ip2region.version>3.3.1</ip2region.version>
<!-- OSS 配置 -->
<aws.sdk.version>2.28.22</aws.sdk.version>
<!-- SMS 配置 -->

View File

@@ -1,9 +1,8 @@
package org.dromara.web.controller;
import cn.dev33.satoken.annotation.SaIgnore;
import cn.hutool.captcha.AbstractCaptcha;
import cn.hutool.captcha.generator.CodeGenerator;
import cn.hutool.captcha.generator.MathGenerator;
import cn.hutool.captcha.generator.RandomGenerator;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.RandomUtil;
import jakarta.validation.constraints.NotBlank;
@@ -15,13 +14,14 @@ import org.dromara.common.core.domain.R;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.utils.SpringUtils;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.core.utils.reflect.ReflectUtils;
import org.dromara.common.mail.config.properties.MailProperties;
import org.dromara.common.mail.utils.MailUtils;
import org.dromara.common.ratelimiter.annotation.RateLimiter;
import org.dromara.common.ratelimiter.enums.LimitType;
import org.dromara.common.redis.utils.RedisUtils;
import org.dromara.common.web.core.WaveAndCircleCaptcha;
import org.dromara.common.web.config.properties.CaptchaProperties;
import org.dromara.common.web.enums.CaptchaType;
import org.dromara.sms4j.api.SmsBlend;
import org.dromara.sms4j.api.entity.SmsResponse;
import org.dromara.sms4j.core.factory.SmsFactory;
@@ -33,7 +33,6 @@ import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.awt.*;
import java.time.Duration;
import java.util.LinkedHashMap;
@@ -131,21 +130,19 @@ public class CaptchaController {
String uuid = IdUtil.simpleUUID();
String verifyKey = GlobalConstants.CAPTCHA_CODE_KEY + uuid;
// 生成验证码
String captchaType = captchaProperties.getType();
CaptchaType captchaType = captchaProperties.getType();
CodeGenerator codeGenerator;
if ("math".equals(captchaType)) {
codeGenerator = new MathGenerator(captchaProperties.getNumberLength(), false);
if (CaptchaType.MATH == captchaType) {
codeGenerator = ReflectUtils.newInstance(captchaType.getClazz(), captchaProperties.getNumberLength(), false);
} else {
codeGenerator = new RandomGenerator(captchaProperties.getCharLength());
codeGenerator = ReflectUtils.newInstance(captchaType.getClazz(), captchaProperties.getCharLength());
}
WaveAndCircleCaptcha captcha = new WaveAndCircleCaptcha(160, 60);
// captcha.setBackground(Color.WHITE); // 不设置就是透明底
captcha.setFont(new Font("Arial", Font.BOLD, 45));
AbstractCaptcha captcha = SpringUtils.getBean(captchaProperties.getCategory().getClazz());
captcha.setGenerator(codeGenerator);
captcha.createCode();
// 如果是数学验证码使用SpEL表达式处理验证码结果
String code = captcha.getCode();
if ("math".equals(captchaType)) {
if (CaptchaType.MATH == captchaType) {
ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression(StringUtils.remove(code, "="));
code = exp.getValue(String.class);

View File

@@ -24,7 +24,9 @@ captcha:
# 是否启用验证码校验
enable: true
# 验证码类型 math 数组计算 char 字符验证
type: math
type: MATH
# line 线段干扰 circle 圆圈干扰 shear 扭曲干扰
category: CIRCLE
# 数字验证码位数
numberLength: 1
# 字符验证码长度

View File

@@ -5,7 +5,7 @@ user.jcaptcha.expire=验证码已失效
user.not.exists=对不起, 您的账号:{0} 不存在.
user.password.not.match=用户不存在/密码错误
user.password.retry.limit.count=密码输入错误{0}次
user.password.retry.limit.exceed=密码输入错误{0}次,户锁定{1}分钟
user.password.retry.limit.exceed=密码输入错误{0}次,户锁定{1}分钟
user.password.delete=对不起,您的账号:{0} 已被删除
user.blocked=对不起,您的账号:{0} 已禁用,请联系管理员
role.blocked=角色已封禁,请联系管理员
@@ -47,10 +47,10 @@ repeat.submit.message=不允许重复提交,请稍候再试
rate.limiter.message=访问过于频繁,请稍候再试
sms.code.not.blank=短信验证码不能为空
sms.code.retry.limit.count=短信验证码输入错误{0}次
sms.code.retry.limit.exceed=短信验证码输入错误{0}次,户锁定{1}分钟
sms.code.retry.limit.exceed=短信验证码输入错误{0}次,户锁定{1}分钟
email.code.not.blank=邮箱验证码不能为空
email.code.retry.limit.count=邮箱验证码输入错误{0}次
email.code.retry.limit.exceed=邮箱验证码输入错误{0}次,户锁定{1}分钟
email.code.retry.limit.exceed=邮箱验证码输入错误{0}次,户锁定{1}分钟
xcx.code.not.blank=小程序[code]不能为空
social.source.not.blank=第三方登录平台[source]不能为空
social.code.not.blank=第三方登录平台[code]不能为空

View File

@@ -5,7 +5,7 @@ user.jcaptcha.expire=验证码已失效
user.not.exists=对不起, 您的账号:{0} 不存在.
user.password.not.match=用户不存在/密码错误
user.password.retry.limit.count=密码输入错误{0}次
user.password.retry.limit.exceed=密码输入错误{0}次,户锁定{1}分钟
user.password.retry.limit.exceed=密码输入错误{0}次,户锁定{1}分钟
user.password.delete=对不起,您的账号:{0} 已被删除
user.blocked=对不起,您的账号:{0} 已禁用,请联系管理员
role.blocked=角色已封禁,请联系管理员
@@ -47,10 +47,10 @@ repeat.submit.message=不允许重复提交,请稍候再试
rate.limiter.message=访问过于频繁,请稍候再试
sms.code.not.blank=短信验证码不能为空
sms.code.retry.limit.count=短信验证码输入错误{0}次
sms.code.retry.limit.exceed=短信验证码输入错误{0}次,户锁定{1}分钟
sms.code.retry.limit.exceed=短信验证码输入错误{0}次,户锁定{1}分钟
email.code.not.blank=邮箱验证码不能为空
email.code.retry.limit.count=邮箱验证码输入错误{0}次
email.code.retry.limit.exceed=邮箱验证码输入错误{0}次,户锁定{1}分钟
email.code.retry.limit.exceed=邮箱验证码输入错误{0}次,户锁定{1}分钟
xcx.code.not.blank=小程序[code]不能为空
social.source.not.blank=第三方登录平台[source]不能为空
social.code.not.blank=第三方登录平台[code]不能为空

View File

@@ -61,7 +61,7 @@ public class UserDTO implements Serializable {
private String sex;
/**
* 号状态0正常 1停用
* 号状态0正常 1停用
*/
private String status;

View File

@@ -1,5 +1,6 @@
package org.dromara.common.core.utils.ip;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.resource.ResourceUtil;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.exception.ServiceException;
@@ -8,6 +9,7 @@ import org.lionsoul.ip2region.service.Config;
import org.lionsoul.ip2region.service.Ip2Region;
import org.lionsoul.ip2region.xdb.Util;
import java.io.File;
import java.io.InputStream;
import java.time.Duration;
@@ -29,11 +31,6 @@ public class RegionUtils {
// 下载地址https://gitee.com/lionsoul/ip2region/blob/master/data/ip2region_v6.xdb
public static final String DEFAULT_IPV6_XDB_PATH = "ip2region_v6.xdb";
// 默认缓存切片大小为15MB仅针对BufferCache全量读取有效如果你的xdb数据库很大合理设置该值可以有效提升BufferCache模式下的查询效率具体可以查看Ip2Region的README
// 注意设置过大的值可能会申请内存时因内存不足而导致OOM请合理设置该值。
// READMEhttps://gitee.com/lionsoul/ip2region/tree/master/binding/java
public static final int DEFAULT_CACHE_SLICE_BYTES = 1024 * 1024 * 15;
// 未知地址
public static final String UNKNOWN_ADDRESS = "未知";
@@ -46,18 +43,20 @@ public class RegionUtils {
// 注意Ip2Region 的xdb文件加载策略 CachePolicy 有三种分别是BufferCache全量读取xdb到内存中、VIndexCache默认策略按需读取并缓存、NoCache实时读取
// 本项目工具使用的 CachePolicy 为 BufferCacheBufferCache会加载整个xdb文件到内存中setXdbInputStream 仅支持 BufferCache 策略。
// 因为加载整个xdb文件会耗费非常大的内存如果你不希望加载整个xdb到内存中更推荐使用 VIndexCache 或 NoCache即实时读取文件策略和 setXdbPath/setXdbFile 加载方法需要注意的一点setXdbPath 和 setXdbFile 不支持读取ClassPath即源码和resource目录中的文件
// 一般而言更建议把xdb数据库放到一个指定的文件目录中即不打包进jar包中然后使用 VIndexCache + 配合SearcherPool的并发池读取数据更方便随时更新xdb数据库
// 一般而言更建议把xdb数据库放到一个指定的文件目录中即不打包进jar包中然后使用 NoCache + 配合SearcherPool的并发池读取数据更方便随时更新xdb数据库
InputStream v4InputStream = ResourceUtil.getStream(DEFAULT_IPV4_XDB_PATH);
// TODO 2025年12月23日 Ip2Region封装的 InputStream 读取函数 Searcher.loadContentFromInputStream 在Linux环境下会申请过大的byte[]空间而导致OOM这里先用临时文件的方案解决等后续 Ip2Region 更新解决方案
// 创建临时文件
File v4TempXdb = FileUtil.writeFromStream(ResourceUtil.getStream(DEFAULT_IPV4_XDB_PATH), FileUtil.createTempFile());
// IPv4配置
Config v4Config = Config.custom()
.setCachePolicy(Config.BufferCache)
//.setXdbFile(v4TempXdb)
.setXdbInputStream(v4InputStream)
//
.setCacheSliceBytes(DEFAULT_CACHE_SLICE_BYTES)
.setXdbFile(v4TempXdb)
// .setXdbInputStream(ResourceUtil.getStream(DEFAULT_IPV4_XDB_PATH))
.asV4();
// 删除临时文件
v4TempXdb.delete();
// IPv6配置
Config v6Config = null;
@@ -65,12 +64,17 @@ public class RegionUtils {
if (v6XdbInputStream == null) {
log.warn("未加载 IPv6 地址库:未在类路径下找到文件 {}。当前仅启用 IPv4 查询。如需启用 IPv6请将 ip2region_v6.xdb 放置到 resources 目录", DEFAULT_IPV6_XDB_PATH);
} else {
// 创建临时文件
File v6TempXdb = FileUtil.writeFromStream(ResourceUtil.getStream(DEFAULT_IPV4_XDB_PATH), FileUtil.createTempFile());
v6Config = Config.custom()
.setCachePolicy(Config.BufferCache)
//.setXdbFile(v6TempXdb)
.setXdbInputStream(v6XdbInputStream)
.setCacheSliceBytes(DEFAULT_CACHE_SLICE_BYTES)
.setXdbFile(v6TempXdb)
// .setXdbInputStream(v6XdbInputStream)
.asV6();
// 删除临时文件
v6TempXdb.delete();
}
// 初始化Ip2Region实例

View File

@@ -192,7 +192,7 @@ public class MailUtils {
/**
* 发送邮件给多人
*
* @param mailAccount 邮件户信息
* @param mailAccount 邮件户信息
* @param tos 收件人列表
* @param subject 标题
* @param content 正文
@@ -207,7 +207,7 @@ public class MailUtils {
/**
* 发送邮件给多人
*
* @param mailAccount 邮件户信息
* @param mailAccount 邮件户信息
* @param tos 收件人列表
* @param ccs 抄送人列表可以为null或空
* @param bccs 密送人列表可以为null或空
@@ -343,7 +343,7 @@ public class MailUtils {
/**
* 发送邮件给多人
*
* @param mailAccount 邮件户信息
* @param mailAccount 邮件户信息
* @param tos 收件人列表
* @param subject 标题
* @param content 正文
@@ -360,7 +360,7 @@ public class MailUtils {
/**
* 发送邮件给多人
*
* @param mailAccount 邮件户信息
* @param mailAccount 邮件户信息
* @param tos 收件人列表
* @param ccs 抄送人列表可以为null或空
* @param bccs 密送人列表可以为null或空
@@ -400,7 +400,7 @@ public class MailUtils {
/**
* 发送邮件给多人
*
* @param mailAccount 邮件户信息
* @param mailAccount 邮件户信息
* @param useGlobalSession 是否全局共享Session
* @param tos 收件人列表
* @param ccs 抄送人列表可以为null或空

View File

@@ -141,8 +141,7 @@ public class OssClient {
try {
// 构建上传请求对象
FileUpload fileUpload = transferManager.uploadFile(
x -> {
x.source(filePath).putObjectRequest(
x -> x.putObjectRequest(
y -> y.bucket(properties.getBucketName())
.key(key)
.contentMD5(StringUtils.isNotEmpty(md5Digest) ? md5Digest : null)
@@ -150,13 +149,10 @@ public class OssClient {
// 用于设置对象的访问控制列表ACL。不同云厂商对ACL的支持和实现方式有所不同
// 因此根据具体的云服务提供商你可能需要进行不同的配置自行开启阿里云有acl权限配置腾讯云没有acl权限配置
//.acl(getAccessPolicy().getObjectCannedACL())
.build()
);
if (log.isDebugEnabled()) {
x.addTransferListener(LoggingTransferListener.create());
}
}
);
.build())
.addTransferListener(LoggingTransferListener.create())
.source(filePath).build());
// 等待上传完成并获取上传结果
CompletedFileUpload uploadResult = fileUpload.completionFuture().join();
String eTag = uploadResult.response().eTag();
@@ -196,21 +192,16 @@ public class OssClient {
// 使用 transferManager 进行上传
Upload upload = transferManager.upload(
x -> {
x.requestBody(body).putObjectRequest(
x -> x.requestBody(body).addTransferListener(LoggingTransferListener.create())
.putObjectRequest(
y -> y.bucket(properties.getBucketName())
.key(key)
.contentType(contentType)
// 用于设置对象的访问控制列表ACL。不同云厂商对ACL的支持和实现方式有所不同
// 因此根据具体的云服务提供商你可能需要进行不同的配置自行开启阿里云有acl权限配置腾讯云没有acl权限配置
//.acl(getAccessPolicy().getObjectCannedACL())
.build()
);
if (log.isDebugEnabled()) {
x.addTransferListener(LoggingTransferListener.create());
}
}
);
.build())
.build());
// 将输入流写入请求体
body.writeInputStream(inputStream);
@@ -238,17 +229,13 @@ public class OssClient {
Path tempFilePath = FileUtils.createTempFile().toPath();
// 使用 S3TransferManager 下载文件
FileDownload downloadFile = transferManager.downloadFile(
x -> {
x.destination(tempFilePath).getObjectRequest(
x -> x.getObjectRequest(
y -> y.bucket(properties.getBucketName())
.key(removeBaseUrl(path))
.build()
);
if (log.isDebugEnabled()) {
x.addTransferListener(LoggingTransferListener.create());
}
}
);
.build())
.addTransferListener(LoggingTransferListener.create())
.destination(tempFilePath)
.build());
// 等待文件下载操作完成
downloadFile.completionFuture().join();
return tempFilePath;
@@ -257,8 +244,8 @@ public class OssClient {
/**
* 下载文件从 Amazon S3 到 输出流
*
* @param key 文件在 Amazon S3 中的对象键
* @param out 输出流
* @param key 文件在 Amazon S3 中的对象键
* @param out 输出流
* @param consumer 自定义处理逻辑
* @throws OssException 如果下载失败,抛出自定义异常
*/
@@ -273,24 +260,26 @@ public class OssClient {
/**
* 下载文件从 Amazon S3 到 输出流
*
* @param key 文件在 Amazon S3 中的对象键
* @param key 文件在 Amazon S3 中的对象键
* @param contentLengthConsumer 文件大小消费者函数
* @return 写出订阅器
* @throws OssException 如果下载失败,抛出自定义异常
*/
public WriteOutSubscriber<OutputStream> download(String key, Consumer<Long> contentLengthConsumer) {
try {
DownloadRequest.TypedBuilder<ResponsePublisher<GetObjectResponse>> typedBuilder = DownloadRequest.builder()
// 构建下载请求
DownloadRequest<ResponsePublisher<GetObjectResponse>> publisherDownloadRequest = DownloadRequest.builder()
// 文件对象
.getObjectRequest(y -> y.bucket(properties.getBucketName())
.key(key)
.build())
.addTransferListener(LoggingTransferListener.create())
// 使用发布订阅转换器
.responseTransformer(AsyncResponseTransformer.toPublisher())
// 文件对象
.getObjectRequest(y -> y.bucket(properties.getBucketName()).key(key).build());
if (log.isDebugEnabled()) {
typedBuilder.addTransferListener(LoggingTransferListener.create());
}
.build();
// 使用 S3TransferManager 下载文件
Download<ResponsePublisher<GetObjectResponse>> publisherDownload = transferManager.download(typedBuilder.build());
Download<ResponsePublisher<GetObjectResponse>> publisherDownload = transferManager.download(publisherDownloadRequest);
// 获取下载发布订阅转换器
ResponsePublisher<GetObjectResponse> publisher = publisherDownload.completionFuture().join().result();
// 执行文件大小消费者函数
@@ -300,7 +289,7 @@ public class OssClient {
// 构建写出订阅器对象
return out -> {
// 创建可写入的字节通道
try (WritableByteChannel channel = Channels.newChannel(out)) {
try(WritableByteChannel channel = Channels.newChannel(out)){
// 订阅数据
publisher.subscribe(byteBuffer -> {
while (byteBuffer.hasRemaining()) {
@@ -358,7 +347,7 @@ public class OssClient {
*
* @param objectKey 对象KEY
* @param expiredTime 链接授权到期时间
* @param metadata 元数据
* @param metadata 元数据
*/
public String createPresignedPutUrl(String objectKey, Duration expiredTime, Map<String, String> metadata) {
// 使用 AWS S3 预签名 URL 的生成器 获取上传文件对象的预签名 URL

View File

@@ -1,8 +1,16 @@
package org.dromara.common.web.config;
import cn.hutool.captcha.CaptchaUtil;
import cn.hutool.captcha.CircleCaptcha;
import cn.hutool.captcha.LineCaptcha;
import cn.hutool.captcha.ShearCaptcha;
import org.dromara.common.web.config.properties.CaptchaProperties;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Lazy;
import java.awt.*;
/**
* 验证码配置
@@ -13,4 +21,45 @@ import org.springframework.boot.context.properties.EnableConfigurationProperties
@EnableConfigurationProperties(CaptchaProperties.class)
public class CaptchaConfig {
private static final int WIDTH = 160;
private static final int HEIGHT = 60;
private static final Color BACKGROUND = Color.LIGHT_GRAY;
private static final Font FONT = new Font("Arial", Font.BOLD, 48);
/**
* 圆圈干扰验证码
*/
@Lazy
@Bean
public CircleCaptcha circleCaptcha() {
CircleCaptcha captcha = CaptchaUtil.createCircleCaptcha(WIDTH, HEIGHT);
captcha.setBackground(BACKGROUND);
captcha.setFont(FONT);
return captcha;
}
/**
* 线段干扰的验证码
*/
@Lazy
@Bean
public LineCaptcha lineCaptcha() {
LineCaptcha captcha = CaptchaUtil.createLineCaptcha(WIDTH, HEIGHT);
captcha.setBackground(BACKGROUND);
captcha.setFont(FONT);
return captcha;
}
/**
* 扭曲干扰验证码
*/
@Lazy
@Bean
public ShearCaptcha shearCaptcha() {
ShearCaptcha captcha = CaptchaUtil.createShearCaptcha(WIDTH, HEIGHT);
captcha.setBackground(BACKGROUND);
captcha.setFont(FONT);
return captcha;
}
}

View File

@@ -1,5 +1,7 @@
package org.dromara.common.web.config.properties;
import org.dromara.common.web.enums.CaptchaCategory;
import org.dromara.common.web.enums.CaptchaType;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
@@ -17,7 +19,12 @@ public class CaptchaProperties {
/**
* 验证码类型
*/
private String type;
private CaptchaType type;
/**
* 验证码类别
*/
private CaptchaCategory category;
/**
* 数字验证码位数

View File

@@ -1,197 +0,0 @@
package org.dromara.common.web.core;
import cn.hutool.captcha.AbstractCaptcha;
import cn.hutool.captcha.generator.CodeGenerator;
import cn.hutool.captcha.generator.RandomGenerator;
import cn.hutool.core.img.GraphicsUtil;
import cn.hutool.core.img.ImgUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.RandomUtil;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.Serial;
import java.util.concurrent.ThreadLocalRandom;
/**
* 带干扰线、波浪、圆的验证码
*
* @author Lion Li
*/
public class WaveAndCircleCaptcha extends AbstractCaptcha {
@Serial
private static final long serialVersionUID = 1L;
// 构造方法(略,与之前一致)
public WaveAndCircleCaptcha(int width, int height) {
this(width, height, 4);
}
public WaveAndCircleCaptcha(int width, int height, int codeCount) {
this(width, height, codeCount, 6);
}
public WaveAndCircleCaptcha(int width, int height, int codeCount, int interfereCount) {
this(width, height, new RandomGenerator(codeCount), interfereCount);
}
public WaveAndCircleCaptcha(int width, int height, CodeGenerator generator, int interfereCount) {
super(width, height, generator, interfereCount);
}
public WaveAndCircleCaptcha(int width, int height, int codeCount, int interfereCount, float size) {
super(width, height, new RandomGenerator(codeCount), interfereCount, size);
}
@Override
public Image createImage(String code) {
final BufferedImage image = new BufferedImage(
width,
height,
(null == this.background) ? BufferedImage.TYPE_4BYTE_ABGR : BufferedImage.TYPE_INT_RGB
);
final Graphics2D g = ImgUtil.createGraphics(image, this.background);
try {
drawString(g, code);
// 扭曲
shear(g, this.width, this.height, ObjectUtil.defaultIfNull(this.background, Color.WHITE));
drawInterfere(g);
} finally {
g.dispose();
}
return image;
}
private void drawString(Graphics2D g, String code) {
// 设置抗锯齿(让字体渲染更清晰)
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
if (this.textAlpha != null) {
g.setComposite(this.textAlpha);
}
GraphicsUtil.drawStringColourful(g, code, this.font, this.width, this.height);
}
protected void drawInterfere(Graphics2D g) {
ThreadLocalRandom random = RandomUtil.getRandom();
int circleCount = Math.max(0, this.interfereCount - 1);
// 圈圈
for (int i = 0; i < circleCount; i++) {
g.setColor(ImgUtil.randomColor(random));
int x = random.nextInt(width);
int y = random.nextInt(height);
int w = random.nextInt(height >> 1);
int h = random.nextInt(height >> 1);
g.drawOval(x, y, w, h);
}
// 仅 1 条平滑波浪线
if (this.interfereCount >= 1) {
g.setColor(getRandomColor(120, 230, random));
drawSmoothWave(g, random);
}
}
private void drawSmoothWave(Graphics2D g, ThreadLocalRandom random) {
int amplitude = random.nextInt(8) + 5; // 波动幅度
int wavelength = random.nextInt(40) + 30; // 波长
double phase = random.nextDouble() * Math.PI * 2;
// ✅ 关键:限制 baseY 在中间区域
int centerY = height / 2;
int verticalJitter = Math.max(5, height / 6); // 至少偏移5像素
int baseY = centerY - verticalJitter + random.nextInt(verticalJitter * 2);
g.setStroke(new BasicStroke(2.5f)); // 线宽
int[] xPoints = new int[width];
int[] yPoints = new int[width];
for (int x = 0; x < width; x++) {
int y = baseY + (int) (amplitude * Math.sin((double) x / wavelength * 2 * Math.PI + phase));
// 限制 y 不要超出图像边界(可选)
y = Math.max(amplitude, Math.min(y, height - amplitude));
xPoints[x] = x;
yPoints[x] = y;
}
g.drawPolyline(xPoints, yPoints, width);
}
private Color getRandomColor(int min, int max, ThreadLocalRandom random) {
int range = max - min;
return new Color(
min + random.nextInt(range),
min + random.nextInt(range),
min + random.nextInt(range)
);
}
/**
* 扭曲
*
* @param g {@link Graphics}
* @param w1 w1
* @param h1 h1
* @param color 颜色
*/
private void shear(Graphics g, int w1, int h1, Color color) {
shearX(g, w1, h1, color);
shearY(g, w1, h1, color);
}
/**
* X坐标扭曲
*
* @param g {@link Graphics}
* @param w1 宽
* @param h1 高
* @param color 颜色
*/
private void shearX(Graphics g, int w1, int h1, Color color) {
int period = RandomUtil.randomInt(this.width);
int frames = 1;
int phase = RandomUtil.randomInt(2);
for (int i = 0; i < h1; i++) {
double d = (double) (period >> 1) * Math.sin((double) i / (double) period + (6.2831853071795862D * (double) phase) / (double) frames);
g.copyArea(0, i, w1, 1, (int) d, 0);
g.setColor(color);
g.drawLine((int) d, i, 0, i);
g.drawLine((int) d + w1, i, w1, i);
}
}
/**
* Y坐标扭曲
*
* @param g {@link Graphics}
* @param w1 宽
* @param h1 高
* @param color 颜色
*/
private void shearY(Graphics g, int w1, int h1, Color color) {
int period = RandomUtil.randomInt(this.height >> 1);
int frames = 20;
int phase = 7;
for (int i = 0; i < w1; i++) {
double d = (double) (period >> 1) * Math.sin((double) i / (double) period + (6.2831853071795862D * (double) phase) / (double) frames);
g.copyArea(i, 0, 1, h1, 0, (int) d);
g.setColor(color);
// 擦除原位置的痕迹
g.drawLine(i, (int) d, i, 0);
g.drawLine(i, (int) d + h1, i, h1);
}
}
}

View File

@@ -0,0 +1,35 @@
package org.dromara.common.web.enums;
import cn.hutool.captcha.AbstractCaptcha;
import cn.hutool.captcha.CircleCaptcha;
import cn.hutool.captcha.LineCaptcha;
import cn.hutool.captcha.ShearCaptcha;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 验证码类别
*
* @author Lion Li
*/
@Getter
@AllArgsConstructor
public enum CaptchaCategory {
/**
* 线段干扰
*/
LINE(LineCaptcha.class),
/**
* 圆圈干扰
*/
CIRCLE(CircleCaptcha.class),
/**
* 扭曲干扰
*/
SHEAR(ShearCaptcha.class);
private final Class<? extends AbstractCaptcha> clazz;
}

View File

@@ -0,0 +1,29 @@
package org.dromara.common.web.enums;
import cn.hutool.captcha.generator.CodeGenerator;
import cn.hutool.captcha.generator.MathGenerator;
import cn.hutool.captcha.generator.RandomGenerator;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 验证码类型
*
* @author Lion Li
*/
@Getter
@AllArgsConstructor
public enum CaptchaType {
/**
* 数字
*/
MATH(MathGenerator.class),
/**
* 字符
*/
CHAR(RandomGenerator.class);
private final Class<? extends CodeGenerator> clazz;
}

View File

@@ -0,0 +1,146 @@
package com.aizuda.snailjob.server.common.register;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import com.aizuda.snailjob.common.core.enums.NodeTypeEnum;
import com.aizuda.snailjob.common.core.util.JsonUtil;
import com.aizuda.snailjob.common.core.util.NetUtil;
import com.aizuda.snailjob.common.core.util.SnailJobVersion;
import com.aizuda.snailjob.common.core.util.StreamUtils;
import com.aizuda.snailjob.common.log.SnailJobLog;
import com.aizuda.snailjob.server.common.cache.CacheConsumerGroup;
import com.aizuda.snailjob.server.common.config.SystemProperties;
import com.aizuda.snailjob.server.common.convert.RegisterNodeInfoConverter;
import com.aizuda.snailjob.server.common.dto.ServerNodeExtAttrs;
import com.aizuda.snailjob.server.common.handler.InstanceManager;
import com.aizuda.snailjob.template.datasource.persistence.po.ServerNode;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.google.common.collect.Lists;
import lombok.RequiredArgsConstructor;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
/**
* 服务端注册
*
* @author opensnail
* @date 2023-06-07
* @since 1.6.0
*/
@Component(ServerRegister.BEAN_NAME)
@RequiredArgsConstructor
public class ServerRegister extends AbstractRegister {
public static final String BEAN_NAME = "serverRegister";
private final ScheduledExecutorService serverRegisterNode = Executors.newSingleThreadScheduledExecutor(r -> new Thread(r, "server-register-node"));
public static final int DELAY_TIME = 30;
public static final String CURRENT_CID;
public static final String GROUP_NAME = "DEFAULT_SERVER";
public static final String NAMESPACE_ID = "DEFAULT_SERVER_NAMESPACE_ID";
private final InstanceManager instanceManager;
private final SystemProperties systemProperties;
private final ServerProperties serverProperties;
static {
CURRENT_CID = IdUtil.getSnowflakeNextIdStr();
}
@Override
public boolean supports(int type) {
return getNodeType().equals(type);
}
@Override
protected void beforeProcessor(RegisterContext context) {
// 新增扩展参数
ServerNodeExtAttrs serverNodeExtAttrs = new ServerNodeExtAttrs();
serverNodeExtAttrs.setWebPort(serverProperties.getPort());
serverNodeExtAttrs.setSystemVersion(SnailJobVersion.getVersion());
context.setGroupName(GROUP_NAME);
context.setHostId(CURRENT_CID);
String serverHost = systemProperties.getServerHost();
if (StrUtil.isEmptyIfStr(serverHost)) {
serverHost = NetUtil.getLocalIpStr();
}
context.setHostIp(serverHost);
context.setHostPort(systemProperties.getServerPort());
context.setContextPath(Optional.ofNullable(serverProperties.getServlet().getContextPath()).orElse(StrUtil.EMPTY));
context.setNamespaceId(NAMESPACE_ID);
context.setExtAttrs(JsonUtil.toJsonString(serverNodeExtAttrs));
}
@Override
protected LocalDateTime getExpireAt() {
return LocalDateTime.now().plusSeconds(DELAY_TIME);
}
@Override
protected boolean doRegister(RegisterContext context, ServerNode serverNode) {
refreshExpireAt(Lists.newArrayList(serverNode));
return Boolean.TRUE;
}
@Override
protected void afterProcessor(final ServerNode serverNode) {
try {
// 同步当前POD消费的组的节点信息
// netty的client只会注册到一个服务端若组分配的和client连接的不是一个POD则会导致当前POD没有其他客户端的注册信息
ConcurrentMap<String /*groupName*/, Set<String>/*namespaceId*/> allConsumerGroupName = CacheConsumerGroup.getAllConsumerGroupName();
if (CollUtil.isNotEmpty(allConsumerGroupName)) {
Set<String> namespaceIdSets = StreamUtils.toSetByFlatMap(allConsumerGroupName.values(), Set::stream);
if (CollUtil.isEmpty(namespaceIdSets)) {
return;
}
List<ServerNode> serverNodes = serverNodeMapper.selectList(
new LambdaQueryWrapper<ServerNode>()
.eq(ServerNode::getNodeType, NodeTypeEnum.CLIENT.getType())
.in(ServerNode::getNamespaceId, namespaceIdSets)
.in(ServerNode::getGroupName, allConsumerGroupName.keySet()));
for (final ServerNode node : serverNodes) {
// 刷新全量本地缓存
instanceManager.registerOrUpdate(RegisterNodeInfoConverter.INSTANCE.toRegisterNodeInfo(node));
// 刷新过期时间
CacheConsumerGroup.addOrUpdate(node.getGroupName(), node.getNamespaceId());
}
}
} catch (Exception e) {
SnailJobLog.LOCAL.error("Client refresh failed", e);
}
}
@Override
protected Integer getNodeType() {
return NodeTypeEnum.SERVER.getType();
}
@Override
public void start() {
SnailJobLog.LOCAL.info("ServerRegister start");
serverRegisterNode.scheduleAtFixedRate(() -> {
try {
this.register(new RegisterContext());
} catch (Exception e) {
SnailJobLog.LOCAL.error("Server-side registration failed", e);
}
}, 0, DELAY_TIME * 2 / 3, TimeUnit.SECONDS);
}
@Override
public void close() {
SnailJobLog.LOCAL.info("ServerRegister close");
}
}

View File

@@ -137,8 +137,6 @@ public class SysMenuController extends BaseController {
return R.fail("新增菜单'" + menu.getMenuName() + "'失败,菜单名称已存在");
} else if (SystemConstants.YES_FRAME.equals(menu.getIsFrame()) && !StringUtils.ishttp(menu.getPath())) {
return R.fail("新增菜单'" + menu.getMenuName() + "'失败地址必须以http(s)://开头");
} else if (!menuService.checkRouteConfigUnique(menu)) {
return R.fail("新增菜单'" + menu.getMenuName() + "'失败,路由名称或地址已存在");
}
return toAjax(menuService.insertMenu(menu));
}
@@ -158,8 +156,6 @@ public class SysMenuController extends BaseController {
return R.fail("修改菜单'" + menu.getMenuName() + "'失败地址必须以http(s)://开头");
} else if (menu.getMenuId().equals(menu.getParentId())) {
return R.fail("修改菜单'" + menu.getMenuName() + "'失败,上级菜单不能选择自己");
} else if (!menuService.checkRouteConfigUnique(menu)) {
return R.fail("修改菜单'" + menu.getMenuName() + "'失败,路由名称或地址已存在");
}
return toAjax(menuService.updateMenu(menu));
}

View File

@@ -130,11 +130,11 @@ public class SysMenu extends BaseEntity {
public String getRouterPath() {
String routerPath = this.path;
// 内链打开外网方式
if (!Constants.TOP_PARENT_ID.equals(getParentId()) && isInnerLink()) {
if (getParentId() != 0L && isInnerLink()) {
routerPath = innerLinkReplaceEach(routerPath);
}
// 非外链并且是一级目录(类型为目录)
if (Constants.TOP_PARENT_ID.equals(getParentId()) && SystemConstants.TYPE_DIR.equals(getMenuType())
if (0L == getParentId() && SystemConstants.TYPE_DIR.equals(getMenuType())
&& SystemConstants.NO_FRAME.equals(getIsFrame())) {
routerPath = "/" + this.path;
}
@@ -152,7 +152,7 @@ public class SysMenu extends BaseEntity {
String component = SystemConstants.LAYOUT;
if (StringUtils.isNotEmpty(this.component) && !isMenuFrame()) {
component = this.component;
} else if (StringUtils.isEmpty(this.component) && !Constants.TOP_PARENT_ID.equals(getParentId()) && isInnerLink()) {
} else if (StringUtils.isEmpty(this.component) && getParentId() != 0L && isInnerLink()) {
component = SystemConstants.INNER_LINK;
} else if (StringUtils.isEmpty(this.component) && isParentView()) {
component = SystemConstants.PARENT_VIEW;
@@ -164,7 +164,7 @@ public class SysMenu extends BaseEntity {
* 是否为菜单内部跳转
*/
public boolean isMenuFrame() {
return Constants.TOP_PARENT_ID.equals(getParentId()) && SystemConstants.TYPE_MENU.equals(menuType) && isFrame.equals(SystemConstants.NO_FRAME);
return getParentId() == 0L && SystemConstants.TYPE_MENU.equals(menuType) && isFrame.equals(SystemConstants.NO_FRAME);
}
/**
@@ -178,7 +178,7 @@ public class SysMenu extends BaseEntity {
* 是否为parent_view组件
*/
public boolean isParentView() {
return !Constants.TOP_PARENT_ID.equals(getParentId()) && SystemConstants.TYPE_DIR.equals(menuType);
return getParentId() != 0L && SystemConstants.TYPE_DIR.equals(menuType);
}
/**

View File

@@ -78,7 +78,7 @@ public class SysUser extends TenantEntity {
private String password;
/**
* 号状态0正常 1停用
* 号状态0正常 1停用
*/
private String status;

View File

@@ -78,7 +78,7 @@ public class SysUserBo extends BaseEntity {
private String password;
/**
* 号状态0正常 1停用
* 号状态0正常 1停用
*/
private String status;

View File

@@ -63,9 +63,9 @@ public class SysUserExportVo implements Serializable {
private String sex;
/**
* 号状态0正常 1停用
* 号状态0正常 1停用
*/
@ExcelProperty(value = "号状态", converter = ExcelDictConvert.class)
@ExcelProperty(value = "号状态", converter = ExcelDictConvert.class)
@ExcelDictFormat(dictType = "sys_normal_disable")
private String status;

View File

@@ -67,9 +67,9 @@ public class SysUserImportVo implements Serializable {
private String sex;
/**
* 号状态0正常 1停用
* 号状态0正常 1停用
*/
@ExcelProperty(value = "号状态", converter = ExcelDictConvert.class)
@ExcelProperty(value = "号状态", converter = ExcelDictConvert.class)
@ExcelDictFormat(dictType = "sys_normal_disable")
private String status;

View File

@@ -89,7 +89,7 @@ public class SysUserVo implements Serializable {
private String password;
/**
* 号状态0正常 1停用
* 号状态0正常 1停用
*/
private String status;

View File

@@ -160,13 +160,4 @@ public interface ISysMenuService {
* @return 结果
*/
boolean checkMenuNameUnique(SysMenuBo menu);
/**
* 校验路由组合是否唯一
*
* @param menu 菜单信息
* @return 结果
*/
boolean checkRouteConfigUnique(SysMenuBo menu);
}

View File

@@ -174,7 +174,7 @@ public interface ISysUserService {
* 修改用户状态
*
* @param userId 用户ID
* @param status 号状态
* @param status 号状态
* @return 结果
*/
int updateUserStatus(Long userId, String status);

View File

@@ -6,7 +6,6 @@ import cn.hutool.core.lang.tree.Tree;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.constant.Constants;
import org.dromara.common.core.constant.SystemConstants;
import org.dromara.common.core.utils.MapstructUtils;
@@ -30,17 +29,13 @@ import org.dromara.system.service.ISysMenuService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.*;
/**
* 菜单 业务层处理
*
* @author Lion Li
*/
@Slf4j
@RequiredArgsConstructor
@Service
public class SysMenuServiceImpl implements ISysMenuService {
@@ -358,45 +353,6 @@ public class SysMenuServiceImpl implements ISysMenuService {
return !exist;
}
/**
* 校验路由名称是否唯一
*
* @param menuBo 菜单信息
* @return 结果
*/
@Override
public boolean checkRouteConfigUnique(SysMenuBo menuBo) {
SysMenu menu = MapstructUtils.convert(menuBo, SysMenu.class);
long menuId = ObjectUtil.isNull(menu.getMenuId()) ? -1L : menu.getMenuId();
Long parentId = menu.getParentId();
String path = menu.getPath();
String routeName = StringUtils.isEmpty(menu.getRouteName()) ? path : menu.getRouteName();
List<SysMenu> sysMenuList = baseMapper.selectList(
new LambdaQueryWrapper<SysMenu>()
.in(SysMenu::getMenuType, SystemConstants.TYPE_DIR, SystemConstants.TYPE_MENU)
.eq(SysMenu::getPath, path).or().eq(SysMenu::getPath, routeName));
for (SysMenu sysMenu : sysMenuList) {
if (sysMenu.getMenuId() != menuId) {
Long dbParentId = sysMenu.getParentId();
String dbPath = sysMenu.getPath();
String dbRouteName = StringUtils.isEmpty(sysMenu.getRouteName()) ? dbPath : sysMenu.getRouteName();
if (StringUtils.equalsAnyIgnoreCase(path, dbPath) && parentId.longValue() == dbParentId.longValue()) {
log.warn("[同级路由冲突] 同级下已存在相同路由路径 '{}',冲突菜单:{}", dbPath, sysMenu.getMenuName());
return false;
} else if (StringUtils.equalsAnyIgnoreCase(path, dbPath)
&& parentId.longValue() == Constants.TOP_PARENT_ID
&& dbParentId.longValue() == Constants.TOP_PARENT_ID) {
log.warn("[根目录路由冲突] 根目录下路由 '{}' 必须唯一,已被菜单 '{}' 占用", path, sysMenu.getMenuName());
return false;
} else if (StringUtils.equalsAnyIgnoreCase(routeName, dbRouteName)) {
log.warn("[路由名称冲突] 路由名称 '{}' 需全局唯一,已被菜单 '{}' 使用", routeName, sysMenu.getMenuName());
return false;
}
}
}
return true;
}
/**
* 根据父节点的ID获取所有子节点
*

View File

@@ -375,7 +375,7 @@ public class SysUserServiceImpl implements ISysUserService, UserService {
* 修改用户状态
*
* @param userId 用户ID
* @param status 号状态
* @param status 号状态
* @return 结果
*/
@Override

View File

@@ -257,7 +257,7 @@ comment on column sys_user.phonenumber is '手机号码';
comment on column sys_user.sex is '用户性别0男 1女 2未知';
comment on column sys_user.avatar is '头像路径';
comment on column sys_user.password is '密码';
comment on column sys_user.status is '号状态0正常 1停用';
comment on column sys_user.status is '号状态0正常 1停用';
comment on column sys_user.del_flag is '删除标志0代表存在 1代表删除';
comment on column sys_user.login_ip is '最后登录IP';
comment on column sys_user.login_date is '最后登录时间';

View File

@@ -258,7 +258,7 @@ comment on column sys_user.phonenumber is '手机号码';
comment on column sys_user.sex is '用户性别0男 1女 2未知';
comment on column sys_user.avatar is '头像地址';
comment on column sys_user.password is '密码';
comment on column sys_user.status is '号状态0正常 1停用';
comment on column sys_user.status is '号状态0正常 1停用';
comment on column sys_user.del_flag is '删除标志0代表存在 1代表删除';
comment on column sys_user.login_ip is '最后登陆IP';
comment on column sys_user.login_date is '最后登陆时间';

View File

@@ -148,7 +148,7 @@ create table sys_user (
sex char(1) default '0' comment '用户性别0男 1女 2未知',
avatar bigint(20) comment '头像地址',
password varchar(100) default '' comment '密码',
status char(1) default '0' comment '号状态0正常 1停用',
status char(1) default '0' comment '号状态0正常 1停用',
del_flag char(1) default '0' comment '删除标志0代表存在 1代表删除',
login_ip varchar(128) default '' comment '最后登录IP',
login_date datetime comment '最后登录时间',

View File

@@ -2802,7 +2802,7 @@ EXEC sys.sp_addextendedproperty
'COLUMN', N'password'
GO
EXEC sys.sp_addextendedproperty
'MS_Description', N'号状态0正常 1停用' ,
'MS_Description', N'号状态0正常 1停用' ,
'SCHEMA', N'dbo',
'TABLE', N'sys_user',
'COLUMN', N'status'