【同步】BOOT 和 CLOUD 的功能

This commit is contained in:
YunaiV
2025-10-02 17:51:59 +08:00
parent f02c004736
commit 96c6f184fa
21 changed files with 96 additions and 36 deletions

View File

@@ -6,6 +6,7 @@ import com.baomidou.mybatisplus.annotation.TableLogic;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fhs.core.trans.vo.TransPojo;
import lombok.Data;
import org.apache.ibatis.type.JdbcType;
import java.io.Serializable;
import java.time.LocalDateTime;
@@ -37,14 +38,14 @@ public abstract class BaseDO implements Serializable, TransPojo {
*
* 使用 String 类型的原因是,未来可能会存在非数值的情况,留好拓展性。
*/
@TableField(fill = FieldFill.INSERT)
@TableField(fill = FieldFill.INSERT, jdbcType = JdbcType.VARCHAR)
private String creator;
/**
* 更新者,目前使用 SysUser 的 id 编号
*
* 使用 String 类型的原因是,未来可能会存在非数值的情况,留好拓展性。
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
@TableField(fill = FieldFill.INSERT_UPDATE, jdbcType = JdbcType.VARCHAR)
private String updater;
/**
* 是否删除

View File

@@ -60,6 +60,12 @@ public enum DbTypeEnum {
* 人大金仓
*/
KINGBASE_ES(DbType.KINGBASE_ES, "KingbaseES", "POSITION('#{value}' IN #{column}) <> 0"),
/**
* OceanBase
*/
OCEAN_BASE(DbType.OCEAN_BASE, "OceanBase", "FIND_IN_SET('#{value}', #{column}) <> 0")
;
public static final Map<String, DbTypeEnum> MAP_BY_NAME = Arrays.stream(values())

View File

@@ -63,7 +63,7 @@ public interface ErrorCodeConstants {
ErrorCode TASK_WITHDRAW_FAIL_PROCESS_NOT_RUNNING = new ErrorCode(1_009_005_017, "撤回失败,流程实例未运行!");
ErrorCode TASK_WITHDRAW_FAIL_TASK_NOT_EXISTS = new ErrorCode(1_009_005_018, "撤回失败,未查询到用户已办任务!");
ErrorCode TASK_WITHDRAW_FAIL_NOT_ALLOW = new ErrorCode(1_009_005_019, "撤回失败,此流程不允许撤回操作!");
ErrorCode TASK_WITHDRAW_FAIL_NEXT_TASK_NOT_ALLOW = new ErrorCode(1_009_005_019, "撤回失败,下一节点不满足撤回条件!");
ErrorCode TASK_WITHDRAW_FAIL_NEXT_TASK_NOT_ALLOW = new ErrorCode(1_009_005_020, "撤回失败,下一节点不满足撤回条件!");
// ========== 动态表单模块 1-009-010-000 ==========
ErrorCode FORM_NOT_EXISTS = new ErrorCode(1_009_010_000, "动态表单不存在");

View File

@@ -1,5 +1,6 @@
package cn.iocoder.yudao.module.bpm.enums.task;
import cn.hutool.core.util.ArrayUtil;
import cn.iocoder.yudao.framework.common.core.ArrayValuable;
import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
import lombok.AllArgsConstructor;
@@ -47,4 +48,8 @@ public enum BpmProcessInstanceStatusEnum implements ArrayValuable<Integer> {
APPROVE.getStatus(), REJECT.getStatus(), CANCEL.getStatus());
}
public static BpmProcessInstanceStatusEnum valueOf(Integer status) {
return ArrayUtil.firstMatch(item -> item.getStatus().equals(status), values());
}
}

View File

@@ -1,10 +1,14 @@
package cn.iocoder.yudao.module.bpm.enums.task;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ObjUtil;
import cn.iocoder.yudao.framework.common.core.ArrayValuable;
import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.Arrays;
/**
* 流程任务 Task 的状态枚举
*
@@ -12,7 +16,7 @@ import lombok.Getter;
*/
@Getter
@AllArgsConstructor
public enum BpmTaskStatusEnum {
public enum BpmTaskStatusEnum implements ArrayValuable<Integer> {
SKIP(-2, "跳过"),
NOT_START(-1, "未开始"),
@@ -35,6 +39,8 @@ public enum BpmTaskStatusEnum {
*/
WAIT(0, "待审批");
public static final Integer[] ARRAYS = Arrays.stream(values()).map(BpmTaskStatusEnum::getStatus).toArray(Integer[]::new);
/**
* 状态
* <p>
@@ -46,6 +52,11 @@ public enum BpmTaskStatusEnum {
*/
private final String name;
@Override
public Integer[] array() {
return ARRAYS;
}
public static boolean isRejectStatus(Integer status) {
return REJECT.getStatus().equals(status);
}
@@ -68,4 +79,8 @@ public enum BpmTaskStatusEnum {
return ObjUtil.equal(status, CANCEL.getStatus());
}
public static BpmTaskStatusEnum valueOf(Integer status) {
return ArrayUtil.firstMatch(item -> item.getStatus().equals(status), values());
}
}

View File

@@ -224,4 +224,10 @@ public class BpmProcessDefinitionInfoDO extends BaseDO {
@TableField(typeHandler = JacksonTypeHandler.class)
private BpmModelMetaInfoVO.HttpRequestSetting taskAfterTriggerSetting;
/**
* 自定义打印模板设置
*/
@TableField(typeHandler = JacksonTypeHandler.class)
private BpmModelMetaInfoVO.PrintTemplateSetting printTemplateSetting;
}

View File

@@ -51,8 +51,8 @@ public class BpmProcessIdRedisDAO {
String noPrefix = processIdRule.getPrefix() + infix + processIdRule.getPostfix();
String key = RedisKeyConstants.BPM_PROCESS_ID + noPrefix;
Long no = stringRedisTemplate.opsForValue().increment(key);
if (StrUtil.isNotEmpty(infix)) {
// 特殊:没有前缀,则不能过期,不能每次都是从 0 开始
if (StrUtil.isEmpty(infix)) {
// 特殊:没有前缀,则不能过期,不能每次都是从 0 开始。可见 https://t.zsxq.com/MU1E2 讨论
stringRedisTemplate.expire(key, Duration.ofDays(1L));
}
return noPrefix + String.format("%0" + processIdRule.getLength() + "d", no);

View File

@@ -658,10 +658,11 @@ public class BpmnModelUtils {
// 根据类型,获取入口连线
List<SequenceFlow> sequenceFlows = getElementIncomingFlows(source);
// 1. 没有入口连线,则返回 false
if (CollUtil.isEmpty(sequenceFlows)) {
return true;
return false;
}
// 循环找目标元素
// 2. 循环找目标元素, 找到目标节点
for (SequenceFlow sequenceFlow : sequenceFlows) {
// 如果发现连线重复,说明循环了,跳过这个循环
if (visitedElements.contains(sequenceFlow.getId())) {
@@ -669,21 +670,22 @@ public class BpmnModelUtils {
}
// 添加已经走过的连线
visitedElements.add(sequenceFlow.getId());
// 这条线路存在目标节点,这条线路完成,进入下个线路
// 这条线路存在目标节点,直接返回 true
FlowElement sourceFlowElement = sequenceFlow.getSourceFlowElement();
if (target.getId().equals(sourceFlowElement.getId())) {
return true;
}
// 如果目标节点为并行网关,跳过这个循环 (TODO 疑问:这个判断作用是防止回退到并行网关分支上的节点吗?)
if (sourceFlowElement instanceof ParallelGateway) {
continue;
}
// 如果目标节点为并行网关,则不继续
if (sourceFlowElement instanceof ParallelGateway) {
return false;
}
// 否则就继续迭代
if (!isSequentialReachable(sourceFlowElement, target, visitedElements)) {
return false;
// 继续迭代,如果找到目标节点直接返回 true
if (isSequentialReachable(sourceFlowElement, target, visitedElements)) {
return true;
}
}
return true;
// 未找到返回 false
return false;
}
/**
@@ -783,7 +785,6 @@ public class BpmnModelUtils {
return resultElements;
}
@SuppressWarnings("PatternVariableCanBeUsed")
private static void simulateNextFlowElements(FlowElement currentElement, Map<String, Object> variables,
List<FlowElement> resultElements, Set<FlowElement> visitElements) {
// 如果为空,或者已经遍历过,则直接结束

View File

@@ -737,10 +737,10 @@ public class SimpleModelUtils {
BoundaryEvent boundaryEvent = null;
if (node.getDelaySetting().getDelayType().equals(BpmDelayTimerTypeEnum.FIXED_DATE_TIME.getType())) {
boundaryEvent = buildTimeoutBoundaryEvent(receiveTask, BpmBoundaryEventTypeEnum.DELAY_TIMER_TIMEOUT.getType(),
node.getDelaySetting().getDelayTime(), null, null);
null, null, node.getDelaySetting().getDelayTime());
} else if (node.getDelaySetting().getDelayType().equals(BpmDelayTimerTypeEnum.FIXED_TIME_DURATION.getType())) {
boundaryEvent = buildTimeoutBoundaryEvent(receiveTask, BpmBoundaryEventTypeEnum.DELAY_TIMER_TIMEOUT.getType(),
null, null, node.getDelaySetting().getDelayTime());
node.getDelaySetting().getDelayTime(), null, null);
} else {
throw new UnsupportedOperationException("不支持的延迟类型:" + node.getDelaySetting());
}

View File

@@ -7,6 +7,11 @@ import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
@Schema(description = "管理后台 - 线索分页 Request VO")
@Data
@@ -42,4 +47,8 @@ public class CrmCluePageReqVO extends PageParam {
@Schema(description = "跟进状态", example = "true")
private Boolean followUpStatus;
@Schema(description = "创建时间", example = "[2023-01-01 00:00:00, 2023-01-31 23:59:59]")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime[] createTime;
}

View File

@@ -33,6 +33,7 @@ public interface CrmClueMapper extends BaseMapperX<CrmClueDO> {
.eqIfPresent(CrmClueDO::getLevel, pageReqVO.getLevel())
.eqIfPresent(CrmClueDO::getSource, pageReqVO.getSource())
.eqIfPresent(CrmClueDO::getFollowUpStatus, pageReqVO.getFollowUpStatus())
.betweenIfPresent(CrmClueDO::getCreateTime, pageReqVO.getCreateTime())
.orderByDesc(CrmClueDO::getId);
return selectJoinPage(pageReqVO, CrmClueDO.class, query);
}

View File

@@ -21,9 +21,8 @@ public enum CodegenFrontTypeEnum {
VUE3_VBEN5_ANTD_SCHEMA(40), // Vue3 VBEN5 + ANTD + schema 模版
VUE3_VBEN5_ANTD_GENERAL(41), // Vue3 VBEN5 + ANTD 标准模版
// TODO @puhui999:50、51 会好点;
VUE3_VBEN5_EP_SCHEMA(42), // Vue3 VBEN5 + EP + schema 模版
VUE3_VBEN5_EP_GENERAL(43), // Vue3 VBEN5 + EP 标准模版
VUE3_VBEN5_EP_SCHEMA(50), // Vue3 VBEN5 + EP + schema 模版
VUE3_VBEN5_EP_GENERAL(51), // Vue3 VBEN5 + EP 标准模版
;
/**

View File

@@ -26,6 +26,7 @@ import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.nio.charset.StandardCharsets;
import java.util.List;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
@@ -99,8 +100,10 @@ public class FileController {
if (StrUtil.isEmpty(path)) {
throw new IllegalArgumentException("结尾的 path 路径必须传递");
}
// 解码,解决中文路径的问题 https://gitee.com/zhijiantianya/ruoyi-vue-pro/pulls/807/
path = URLUtil.decode(path);
// 解码,解决中文路径的问题
// https://gitee.com/zhijiantianya/ruoyi-vue-pro/pulls/807/
// https://gitee.com/zhijiantianya/ruoyi-vue-pro/pulls/1432/
path = URLUtil.decode(path, StandardCharsets.UTF_8, false);
// 读取内容
byte[] content = fileService.getFileContent(configId, path);

View File

@@ -343,7 +343,7 @@ public class CodegenEngine {
filePath = formatFilePath(filePath, bindingMap);
String content = templateEngine.getTemplate(vmPath).render(bindingMap);
// 格式化代码
content = prettyCode(content);
content = prettyCode(content, vmPath);
result.put(filePath, content);
}
@@ -383,11 +383,14 @@ public class CodegenEngine {
* 如果不处理Vue 的 Pretty 格式校验可能会报错
*
* @param content 格式化前的代码
* @param vmPath 模板路径
* @return 格式化后的代码
*/
private String prettyCode(String content) {
// Vue 界面:去除字段后面多余的 , 逗号,解决前端的 Pretty 代码格式检查的报错
content = content.replaceAll(",\n}", "\n}").replaceAll(",\n }", "\n }");
private String prettyCode(String content, String vmPath) {
// Vue 界面:去除字段后面多余的 , 逗号,解决前端的 Pretty 代码格式检查的报错(需要排除 vben5
if (!StrUtil.contains(vmPath, "vben5")) {
content = content.replaceAll(",\n}", "\n}").replaceAll(",\n }", "\n }");
}
// Vue 界面:去除多的 dateFormatter只有一个的情况下说明没使用到
if (StrUtil.count(content, "dateFormatter") == 1) {
content = StrUtils.removeLineContains(content, "dateFormatter");

View File

@@ -19,7 +19,6 @@ import java.util.Arrays;
@Getter
public enum IotSceneRuleTriggerTypeEnum implements ArrayValuable<Integer> {
// TODO @芋艿:后续“对应”部分,要 @下,等包结构梳理完;
/**
* 设备上下线变更
*

View File

@@ -76,4 +76,12 @@ public interface RedisKeyConstants {
*/
String DATA_SINK = "iot:data_sink";
/**
* 场景联动规则的数据缓存,使用 Spring Cache 操作
* <p>
* KEY 格式scene_rule_list_${productId}_${deviceId}
* VALUE 数据类型String 数组(JSON),即 {@link cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO} 列表
*/
String SCENE_RULE_LIST = "iot:scene_rule_list";
}

View File

@@ -72,7 +72,7 @@
FROM device_property_${reqVO.deviceId}
WHERE ${@cn.hutool.core.util.StrUtil@toUnderlineCase(reqVO.identifier)} IS NOT NULL
AND ts BETWEEN ${@cn.hutool.core.date.LocalDateTimeUtil@toEpochMilli(reqVO.times[0])}
AND ${@cn.hutool.core.date.LocalDateTimeUtil@toEpochMilli(reqVO.times[1])}
AND ${@cn.hutool.core.date.LocalDateTimeUtil@toEpochMilli(reqVO.times[1])}
ORDER BY ts DESC
</select>

View File

@@ -14,7 +14,7 @@ import java.time.LocalDateTime;
public class RewardActivityRespVO extends RewardActivityBaseVO {
@Schema(description = "活动编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Integer id;
private Long id;
@Schema(description = "活动状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Integer status;

View File

@@ -68,8 +68,11 @@ public abstract class AbstractWxPayClient extends AbstractPayClient<WxPayClientC
payConfig.setKeyPath(FileUtils.createTempFile(Base64.decode(config.getKeyContent())).getPath());
} else if (Objects.equals(config.getApiVersion(), API_VERSION_V3)) {
payConfig.setPrivateKeyPath(FileUtils.createTempFile(config.getPrivateKeyContent()).getPath());
payConfig.setPublicKeyPath(FileUtils.createTempFile(config.getPublicKeyContent()).getPath());
// 特殊:强制使用微信公用模式,避免灰度期间的问题!!!
// 参考 https://gitee.com/yudaocode/yudao-ui-admin-vue3/issues/ICUE53 和 https://t.zsxq.com/ODR5V
if (StrUtil.isNotBlank(config.getPublicKeyContent())) {
payConfig.setPrivateCertPath(FileUtils.createTempFile(Base64.decode(config.getPublicKeyContent())).getPath());
}
// 特殊:强制使用微信公钥模式,避免灰度期间的问题!!!
payConfig.setStrictlyNeedWechatPaySerial(true);
}

View File

@@ -81,7 +81,6 @@ public class WxPayClientConfig implements PayClientConfig {
/**
* pub_key.pem 证书文件的对应字符串
*/
@NotBlank(message = "pub_key.pem 不能为空", groups = V3.class)
private String publicKeyContent;
@NotBlank(message = "publicKeyId 不能为空", groups = V3.class)
private String publicKeyId;

View File

@@ -54,6 +54,7 @@ import me.zhyd.oauth.model.AuthResponse;
import me.zhyd.oauth.model.AuthUser;
import me.zhyd.oauth.request.AuthRequest;
import me.zhyd.oauth.utils.AuthStateUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.redis.core.StringRedisTemplate;
@@ -101,7 +102,8 @@ public class SocialClientServiceImpl implements SocialClientService {
@Value("${yudao.wxa-subscribe-message.miniprogram-state:formal}")
public String miniprogramState;
@Resource
@SuppressWarnings("SpringJavaAutowiredFieldsWarningInspection")
@Autowired(required = false) // 由于 justauth.enable 配置项,可以关闭 AuthRequestFactory 的功能,所以这里只能不强制注入
private AuthRequestFactory authRequestFactory;
@Resource