Compare commits
8 Commits
14f96acd60
...
14f9193703
| Author | SHA1 | Date | |
|---|---|---|---|
| 14f9193703 | |||
| 060da45f84 | |||
| 1cb5d841d9 | |||
| 00a1c1037c | |||
| 9e192f5197 | |||
| 4875594bcb | |||
| 90f8142d0b | |||
| 3a6d62d695 |
@@ -11,18 +11,20 @@ RUN mkdir -p /ruoyi/server/logs \
|
|||||||
|
|
||||||
WORKDIR /ruoyi/server
|
WORKDIR /ruoyi/server
|
||||||
|
|
||||||
ENV SERVER_PORT=8080 SNAIL_PORT=28080 LANG=C.UTF-8 LC_ALL=C.UTF-8 JAVA_OPTS=""
|
ENV SERVER_PORT=8080 SNAIL_JOB_PORT=28080 SNAIL_AI_PORT=38080 LANG=C.UTF-8 LC_ALL=C.UTF-8 JAVA_OPTS=""
|
||||||
|
|
||||||
EXPOSE ${SERVER_PORT}
|
EXPOSE ${SERVER_PORT}
|
||||||
# 暴露 snail job 客户端端口 用于定时任务调度中心通信
|
# 暴露 snail 客户端端口 用于定时任务调度中心通信
|
||||||
EXPOSE ${SNAIL_PORT}
|
EXPOSE ${SNAIL_JOB_PORT}
|
||||||
|
EXPOSE ${SNAIL_AI_PORT}
|
||||||
|
|
||||||
ADD ./target/ruoyi-admin.jar ./app.jar
|
ADD ./target/ruoyi-admin.jar ./app.jar
|
||||||
|
|
||||||
SHELL ["/bin/bash", "-c"]
|
SHELL ["/bin/bash", "-c"]
|
||||||
|
|
||||||
ENTRYPOINT java -Djava.security.egd=file:/dev/./urandom -Dserver.port=${SERVER_PORT} \
|
ENTRYPOINT java -Djava.security.egd=file:/dev/./urandom -Dserver.port=${SERVER_PORT} \
|
||||||
-Dsnail-job.port=${SNAIL_PORT} \
|
-Dsnail-job.port=${SNAIL_JOB_PORT} \
|
||||||
|
-Dsnail-ai.port=${SNAIL_AI_PORT} \
|
||||||
# 应用名称 如果想区分集群节点监控 改成不同的名称即可
|
# 应用名称 如果想区分集群节点监控 改成不同的名称即可
|
||||||
#-Dskywalking.agent.service_name=ruoyi-server \
|
#-Dskywalking.agent.service_name=ruoyi-server \
|
||||||
#-javaagent:/ruoyi/skywalking/agent/skywalking-agent.jar \
|
#-javaagent:/ruoyi/skywalking/agent/skywalking-agent.jar \
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
+8
-1
@@ -45,7 +45,14 @@ public class SaTokenExceptionHandler {
|
|||||||
public R<Void> handleNotLoginException(NotLoginException e, HttpServletRequest request) {
|
public R<Void> handleNotLoginException(NotLoginException e, HttpServletRequest request) {
|
||||||
String requestURI = request.getRequestURI();
|
String requestURI = request.getRequestURI();
|
||||||
log.error("请求地址'{}',认证失败'{}',无法访问系统资源", requestURI, e.getMessage());
|
log.error("请求地址'{}',认证失败'{}',无法访问系统资源", requestURI, e.getMessage());
|
||||||
return R.fail(HttpStatus.HTTP_UNAUTHORIZED, "认证失败,无法访问系统资源");
|
String msg = switch (e.getType()) {
|
||||||
|
case NotLoginException.TOKEN_TIMEOUT -> "登录已过期,请重新登录";
|
||||||
|
case NotLoginException.BE_REPLACED -> "当前账号已在其他设备登录,您已被强制下线";
|
||||||
|
case NotLoginException.KICK_OUT -> "账号已被管理员强制下线";
|
||||||
|
case NotLoginException.TOKEN_FREEZE -> "账号已被冻结,请联系管理员处理";
|
||||||
|
default -> "登录状态异常,请重新登录";
|
||||||
|
};
|
||||||
|
return R.fail(HttpStatus.HTTP_UNAUTHORIZED, msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -618,7 +618,7 @@ const { resetQuery } = useSearchReset({
|
|||||||
});
|
});
|
||||||
|
|
||||||
/** 新增按钮操作 */
|
/** 新增按钮操作 */
|
||||||
const handleAdd = (row?: ${BusinessName}VO) => {
|
const handleAdd = (row?: Partial<${BusinessName}VO>) => {
|
||||||
openDialog('添加${functionName}');
|
openDialog('添加${functionName}');
|
||||||
getTreeselect();
|
getTreeselect();
|
||||||
if (row != null && row.${treeCode}) {
|
if (row != null && row.${treeCode}) {
|
||||||
@@ -629,7 +629,7 @@ const handleAdd = (row?: ${BusinessName}VO) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/** 修改按钮操作 */
|
/** 修改按钮操作 */
|
||||||
const handleUpdate = async (row: ${BusinessName}VO) => {
|
const handleUpdate = async (row: Partial<${BusinessName}VO>) => {
|
||||||
reset();
|
reset();
|
||||||
await getTreeselect(row.${treeCode});
|
await getTreeselect(row.${treeCode});
|
||||||
if (row != null) {
|
if (row != null) {
|
||||||
@@ -668,7 +668,7 @@ const submitForm = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/** 删除按钮操作 */
|
/** 删除按钮操作 */
|
||||||
const handleDelete = async (row: ${BusinessName}VO) => {
|
const handleDelete = async (row: Partial<${BusinessName}VO>) => {
|
||||||
await modal.confirm('是否确认删除${functionName}编号为"' + row.${pkColumn.javaField} + '"的数据项?');
|
await modal.confirm('是否确认删除${functionName}编号为"' + row.${pkColumn.javaField} + '"的数据项?');
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
await del${BusinessName}(row.${pkColumn.javaField}).finally(() => setLoading(false));
|
await del${BusinessName}(row.${pkColumn.javaField}).finally(() => setLoading(false));
|
||||||
@@ -687,7 +687,7 @@ const filterTreeOptions = (options: ${BusinessName}Option[], excludeId: string |
|
|||||||
|
|
||||||
#if($enableStatus)
|
#if($enableStatus)
|
||||||
/** 状态修改 */
|
/** 状态修改 */
|
||||||
const handleStatusChange = async (row: ${BusinessName}VO) => {
|
const handleStatusChange = async (row: Partial<${BusinessName}VO>) => {
|
||||||
const text = row.${statusField} === ${statusField}ActiveValue ? '启用' : '停用';
|
const text = row.${statusField} === ${statusField}ActiveValue ? '启用' : '停用';
|
||||||
try {
|
try {
|
||||||
await modal.confirm('确认要"' + text + '"吗?');
|
await modal.confirm('确认要"' + text + '"吗?');
|
||||||
@@ -701,7 +701,7 @@ const handleStatusChange = async (row: ${BusinessName}VO) => {
|
|||||||
|
|
||||||
#if($enableSort)
|
#if($enableSort)
|
||||||
/** 排序调整 */
|
/** 排序调整 */
|
||||||
const handleSortChange = async (row: ${BusinessName}VO) => {
|
const handleSortChange = async (row: Partial<${BusinessName}VO>) => {
|
||||||
try {
|
try {
|
||||||
await update${BusinessName}Sort(row.${pkColumn.javaField}, row.${sortField});
|
await update${BusinessName}Sort(row.${pkColumn.javaField}, row.${sortField});
|
||||||
modal.msgSuccess('排序更新成功');
|
modal.msgSuccess('排序更新成功');
|
||||||
|
|||||||
@@ -561,7 +561,7 @@ const handleAdd = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/** 修改按钮操作 */
|
/** 修改按钮操作 */
|
||||||
const handleUpdate = async (row?: ${BusinessName}VO) => {
|
const handleUpdate = async (row?: Partial<${BusinessName}VO>) => {
|
||||||
reset();
|
reset();
|
||||||
const _${pkColumn.javaField} = row?.${pkColumn.javaField} || ids.value[0];
|
const _${pkColumn.javaField} = row?.${pkColumn.javaField} || ids.value[0];
|
||||||
const res = await get${BusinessName}(_${pkColumn.javaField});
|
const res = await get${BusinessName}(_${pkColumn.javaField});
|
||||||
@@ -597,7 +597,7 @@ const submitForm = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/** 删除按钮操作 */
|
/** 删除按钮操作 */
|
||||||
const handleDelete = async (row?: ${BusinessName}VO) => {
|
const handleDelete = async (row?: Partial<${BusinessName}VO>) => {
|
||||||
const _${pkColumn.javaField}s = row?.${pkColumn.javaField} || ids.value;
|
const _${pkColumn.javaField}s = row?.${pkColumn.javaField} || ids.value;
|
||||||
await modal.confirm('是否确认删除${functionName}编号为"' + _${pkColumn.javaField}s + '"的数据项?');
|
await modal.confirm('是否确认删除${functionName}编号为"' + _${pkColumn.javaField}s + '"的数据项?');
|
||||||
await del${BusinessName}(_${pkColumn.javaField}s);
|
await del${BusinessName}(_${pkColumn.javaField}s);
|
||||||
@@ -620,7 +620,7 @@ const handleExport = () => {
|
|||||||
|
|
||||||
#if($enableStatus)
|
#if($enableStatus)
|
||||||
/** 状态修改 */
|
/** 状态修改 */
|
||||||
const handleStatusChange = async (row: ${BusinessName}VO) => {
|
const handleStatusChange = async (row: Partial<${BusinessName}VO>) => {
|
||||||
const text = row.${statusField} === ${statusField}ActiveValue ? '启用' : '停用';
|
const text = row.${statusField} === ${statusField}ActiveValue ? '启用' : '停用';
|
||||||
try {
|
try {
|
||||||
await modal.confirm('确认要"' + text + '"吗?');
|
await modal.confirm('确认要"' + text + '"吗?');
|
||||||
@@ -634,7 +634,7 @@ const handleStatusChange = async (row: ${BusinessName}VO) => {
|
|||||||
|
|
||||||
#if($enableSort)
|
#if($enableSort)
|
||||||
/** 排序调整 */
|
/** 排序调整 */
|
||||||
const handleSortChange = async (row: ${BusinessName}VO) => {
|
const handleSortChange = async (row: Partial<${BusinessName}VO>) => {
|
||||||
try {
|
try {
|
||||||
await update${BusinessName}Sort(row.${pkColumn.javaField}, row.${sortField});
|
await update${BusinessName}Sort(row.${pkColumn.javaField}, row.${sortField});
|
||||||
modal.msgSuccess('排序更新成功');
|
modal.msgSuccess('排序更新成功');
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import org.dromara.common.core.constant.SystemConstants;
|
|||||||
import org.dromara.common.core.utils.StringUtils;
|
import org.dromara.common.core.utils.StringUtils;
|
||||||
import org.dromara.common.mybatis.core.domain.BaseEntity;
|
import org.dromara.common.mybatis.core.domain.BaseEntity;
|
||||||
|
|
||||||
import java.io.Serial;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@@ -25,8 +24,6 @@ import java.util.List;
|
|||||||
@TableName("sys_menu")
|
@TableName("sys_menu")
|
||||||
public class SysMenu extends BaseEntity {
|
public class SysMenu extends BaseEntity {
|
||||||
|
|
||||||
@Serial
|
|
||||||
private static final long serialVersionUID = 6967394714839655267L;
|
|
||||||
/**
|
/**
|
||||||
* 菜单ID
|
* 菜单ID
|
||||||
*/
|
*/
|
||||||
|
|||||||
+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;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
+9
-9
@@ -27,10 +27,7 @@ import org.dromara.workflow.common.constant.FlowConstant;
|
|||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.*;
|
||||||
import java.util.LinkedHashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
@@ -74,10 +71,12 @@ public class FlwChartExtServiceImpl implements ChartExtService {
|
|||||||
Map<String, List<FlowHisTask>> groupedByNode = StreamUtils.groupByKey(flowHisTasks, FlowHisTask::getNodeCode);
|
Map<String, List<FlowHisTask>> groupedByNode = StreamUtils.groupByKey(flowHisTasks, FlowHisTask::getNodeCode);
|
||||||
|
|
||||||
// 批量查询所有审批人的用户信息
|
// 批量查询所有审批人的用户信息
|
||||||
List<UserDTO> userDTOList = userService.selectListByIds(StreamUtils.toList(flowHisTasks, e -> Convert.toLong(e.getApprover())));
|
List<UserDTO> userDTOList = userService.selectListByIds(StreamUtils.toSet(flowHisTasks, e -> Convert.toLong(e.getApprover())));
|
||||||
|
|
||||||
// 将查询到的用户列表转换为以用户ID为key的映射
|
// 将查询到的用户列表转换为以用户ID为key的映射
|
||||||
Map<Long, UserDTO> userMap = StreamUtils.toIdentityMap(userDTOList, UserDTO::getUserId);
|
Map<Long, UserDTO> userMap = StreamUtils.toIdentityMap(userDTOList, UserDTO::getUserId);
|
||||||
|
Set<Long> deptIds = StreamUtils.toSet(userDTOList, UserDTO::getDeptId);
|
||||||
|
Map<Long, String> deptNameMap = deptService.selectDeptNamesByIds(deptIds);
|
||||||
|
|
||||||
Map<String, String> dictType = dictService.getAllDictByDictType(FlowConstant.WF_TASK_STATUS);
|
Map<String, String> dictType = dictService.getAllDictByDictType(FlowConstant.WF_TASK_STATUS);
|
||||||
|
|
||||||
@@ -100,7 +99,7 @@ public class FlwChartExtServiceImpl implements ChartExtService {
|
|||||||
));
|
));
|
||||||
|
|
||||||
// 处理当前节点的扩展信息
|
// 处理当前节点的扩展信息
|
||||||
this.processNodeExtInfo(nodeJson, latestPerApprover, userMap, dictType);
|
this.processNodeExtInfo(nodeJson, latestPerApprover, userMap, deptNameMap, dictType);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -168,9 +167,11 @@ public class FlwChartExtServiceImpl implements ChartExtService {
|
|||||||
* @param nodeJson 当前流程节点对象,包含节点基础信息和提示内容容器
|
* @param nodeJson 当前流程节点对象,包含节点基础信息和提示内容容器
|
||||||
* @param taskList 当前节点关联的历史审批任务列表,用于生成提示信息
|
* @param taskList 当前节点关联的历史审批任务列表,用于生成提示信息
|
||||||
* @param userMap 用户信息映射表,key 为用户ID,value 为用户DTO对象,用于获取审批人信息
|
* @param userMap 用户信息映射表,key 为用户ID,value 为用户DTO对象,用于获取审批人信息
|
||||||
|
* @param deptNameMap 部门名称映射表,key 为部门ID,value 为部门名称
|
||||||
* @param dictType 数据字典映射表,key 为字典项编码,value 为对应显示值,用于翻译审批状态等
|
* @param dictType 数据字典映射表,key 为字典项编码,value 为对应显示值,用于翻译审批状态等
|
||||||
*/
|
*/
|
||||||
private void processNodeExtInfo(NodeJson nodeJson, List<FlowHisTask> taskList, Map<Long, UserDTO> userMap, Map<String, String> dictType) {
|
private void processNodeExtInfo(NodeJson nodeJson, List<FlowHisTask> taskList, Map<Long, UserDTO> userMap,
|
||||||
|
Map<Long, String> deptNameMap, Map<String, String> dictType) {
|
||||||
|
|
||||||
// 获取节点提示内容对象中的 info 列表,用于追加提示项
|
// 获取节点提示内容对象中的 info 列表,用于追加提示项
|
||||||
List<PromptContent.InfoItem> info = nodeJson.getPromptContent().getInfo();
|
List<PromptContent.InfoItem> info = nodeJson.getPromptContent().getInfo();
|
||||||
@@ -182,8 +183,7 @@ public class FlwChartExtServiceImpl implements ChartExtService {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 查询用户所属部门名称
|
String deptName = deptNameMap.getOrDefault(userDTO.getDeptId(), StringUtils.EMPTY);
|
||||||
String deptName = deptService.selectDeptNameByIds(Convert.toStr(userDTO.getDeptId()));
|
|
||||||
|
|
||||||
// 添加标题项,如:👤 张三(市场部)
|
// 添加标题项,如:👤 张三(市场部)
|
||||||
info.add(new PromptContent.InfoItem()
|
info.add(new PromptContent.InfoItem()
|
||||||
|
|||||||
+7
-3
@@ -338,10 +338,14 @@ public class FlwTaskServiceImpl implements IFlwTaskService {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// 添加抄送人记录
|
// 添加抄送人记录
|
||||||
FlowHisTask flowHisTask = flowHisTaskMapper.selectList(
|
List<FlowHisTask> flowHisTasks = flowHisTaskMapper.selectList(
|
||||||
QueryBuilder.lambda(FlowHisTask.class)
|
QueryBuilder.lambda(FlowHisTask.class)
|
||||||
.eq(FlowHisTask::getTaskId, task.getId())
|
.eq(FlowHisTask::getTaskId, task.getId())
|
||||||
.build()).get(0);
|
.build());
|
||||||
|
if (CollUtil.isEmpty(flowHisTasks)) {
|
||||||
|
throw new ServiceException("流程历史任务不存在,无法添加抄送记录");
|
||||||
|
}
|
||||||
|
FlowHisTask flowHisTask = flowHisTasks.get(0);
|
||||||
FlowNode flowNode = new FlowNode();
|
FlowNode flowNode = new FlowNode();
|
||||||
flowNode.setNodeCode(flowHisTask.getTargetNodeCode());
|
flowNode.setNodeCode(flowHisTask.getTargetNodeCode());
|
||||||
flowNode.setNodeName(flowHisTask.getTargetNodeName());
|
flowNode.setNodeName(flowHisTask.getTargetNodeName());
|
||||||
@@ -897,7 +901,7 @@ public class FlwTaskServiceImpl implements IFlwTaskService {
|
|||||||
if (CollUtil.isEmpty(userList)) {
|
if (CollUtil.isEmpty(userList)) {
|
||||||
return Collections.emptyList();
|
return Collections.emptyList();
|
||||||
}
|
}
|
||||||
return userService.selectListByIds(StreamUtils.toList(userList, e -> Convert.toLong(e.getProcessedBy())));
|
return userService.selectListByIds(StreamUtils.toSet(userList, e -> Convert.toLong(e.getProcessedBy())));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -103,7 +103,8 @@ services:
|
|||||||
# 时区上海
|
# 时区上海
|
||||||
TZ: Asia/Shanghai
|
TZ: Asia/Shanghai
|
||||||
SERVER_PORT: 8080
|
SERVER_PORT: 8080
|
||||||
SNAIL_PORT: 28080
|
SNAIL_JOB_PORT: 28080
|
||||||
|
SNAIL_AI_PORT: 38080
|
||||||
volumes:
|
volumes:
|
||||||
# 配置文件
|
# 配置文件
|
||||||
- /docker/server1/logs/:/ruoyi/server/logs/
|
- /docker/server1/logs/:/ruoyi/server/logs/
|
||||||
@@ -119,7 +120,8 @@ services:
|
|||||||
# 时区上海
|
# 时区上海
|
||||||
TZ: Asia/Shanghai
|
TZ: Asia/Shanghai
|
||||||
SERVER_PORT: 8081
|
SERVER_PORT: 8081
|
||||||
SNAIL_PORT: 28081
|
SNAIL_JOB_PORT: 28081
|
||||||
|
SNAIL_AI_PORT: 38081
|
||||||
volumes:
|
volumes:
|
||||||
# 配置文件
|
# 配置文件
|
||||||
- /docker/server2/logs/:/ruoyi/server/logs/
|
- /docker/server2/logs/:/ruoyi/server/logs/
|
||||||
@@ -153,3 +155,17 @@ services:
|
|||||||
- /docker/snailjob/logs/:/ruoyi/snailjob/logs
|
- /docker/snailjob/logs/:/ruoyi/snailjob/logs
|
||||||
privileged: true
|
privileged: true
|
||||||
network_mode: "host"
|
network_mode: "host"
|
||||||
|
|
||||||
|
ruoyi-snailai-server:
|
||||||
|
image: ruoyi/ruoyi-snailai-server:5.5.3
|
||||||
|
container_name: ruoyi-snailai-server
|
||||||
|
environment:
|
||||||
|
# 时区上海
|
||||||
|
TZ: Asia/Shanghai
|
||||||
|
ports:
|
||||||
|
- "8900:8900"
|
||||||
|
- "18888:18888"
|
||||||
|
volumes:
|
||||||
|
- /docker/snailai/logs/:/ruoyi/snailai/logs
|
||||||
|
privileged: true
|
||||||
|
network_mode: "host"
|
||||||
|
|||||||
Reference in New Issue
Block a user