mirror of
https://gitee.com/yudaocode/yudao-boot-mini.git
synced 2026-03-22 05:27:15 +08:00
Merge branch 'master-jdk17' of https://gitee.com/zhijiantianya/ruoyi-vue-pro
This commit is contained in:
@@ -25,16 +25,16 @@ public class CommonResult<T> implements Serializable {
|
||||
* @see ErrorCode#getCode()
|
||||
*/
|
||||
private Integer code;
|
||||
/**
|
||||
* 返回数据
|
||||
*/
|
||||
private T data;
|
||||
/**
|
||||
* 错误提示,用户可阅读
|
||||
*
|
||||
* @see ErrorCode#getMsg() ()
|
||||
*/
|
||||
private String msg;
|
||||
/**
|
||||
* 返回数据
|
||||
*/
|
||||
private T data;
|
||||
|
||||
/**
|
||||
* 将传入的 result 对象,转换成另外一个泛型结果的对象
|
||||
|
||||
@@ -11,12 +11,12 @@ import java.util.List;
|
||||
@Data
|
||||
public final class PageResult<T> implements Serializable {
|
||||
|
||||
@Schema(description = "数据", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private List<T> list;
|
||||
|
||||
@Schema(description = "总量", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private Long total;
|
||||
|
||||
@Schema(description = "数据", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private List<T> list;
|
||||
|
||||
public PageResult() {
|
||||
}
|
||||
|
||||
|
||||
@@ -7,35 +7,44 @@ import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCand
|
||||
import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils;
|
||||
import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils;
|
||||
import lombok.Setter;
|
||||
import org.flowable.bpmn.model.*;
|
||||
import org.flowable.bpmn.model.Activity;
|
||||
import org.flowable.bpmn.model.CallActivity;
|
||||
import org.flowable.bpmn.model.FlowElement;
|
||||
import org.flowable.bpmn.model.UserTask;
|
||||
import org.flowable.engine.delegate.DelegateExecution;
|
||||
import org.flowable.engine.impl.bpmn.behavior.AbstractBpmnActivityBehavior;
|
||||
import org.flowable.engine.impl.bpmn.behavior.SequentialMultiInstanceBehavior;
|
||||
import org.flowable.engine.impl.persistence.entity.ExecutionEntity;
|
||||
import org.flowable.engine.impl.bpmn.behavior.ParallelMultiInstanceBehavior;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* 自定义的【串行】的【多个】流程任务的 assignee 负责人的分配
|
||||
* 自定义的【并行】的【多个】流程任务的 assignee 负责人的分配
|
||||
* 第一步,基于分配规则,计算出分配任务的【多个】候选人们。
|
||||
* 第二步,将【多个】任务候选人们,设置到 DelegateExecution 的 collectionVariable 变量中,以便 BpmUserTaskActivityBehavior 使用它
|
||||
*
|
||||
* 本质上,实现和 {@link BpmParallelMultiInstanceBehavior} 一样,只是继承的类不一样
|
||||
*
|
||||
* @author 芋道源码
|
||||
* @author kemengkai
|
||||
* @since 2022-04-21 16:57
|
||||
*/
|
||||
@Setter
|
||||
public class BpmSequentialMultiInstanceBehavior extends SequentialMultiInstanceBehavior {
|
||||
public class BpmParallelMultiInstanceBehavior extends ParallelMultiInstanceBehavior {
|
||||
|
||||
private BpmTaskCandidateInvoker taskCandidateInvoker;
|
||||
|
||||
public BpmSequentialMultiInstanceBehavior(Activity activity, AbstractBpmnActivityBehavior innerActivityBehavior) {
|
||||
public BpmParallelMultiInstanceBehavior(Activity activity,
|
||||
AbstractBpmnActivityBehavior innerActivityBehavior) {
|
||||
super(activity, innerActivityBehavior);
|
||||
}
|
||||
|
||||
/**
|
||||
* 逻辑和 {@link BpmParallelMultiInstanceBehavior#resolveNrOfInstances(DelegateExecution)} 类似
|
||||
* 重写该方法,主要实现两个功能:
|
||||
* 1. 忽略原有的 collectionVariable、collectionElementVariable 表达式,而是采用自己定义的
|
||||
* 2. 获得任务的处理人,并设置到 collectionVariable 中,用于 BpmUserTaskActivityBehavior 从中可以获取任务的处理人
|
||||
*
|
||||
* 差异的点:是在【第二步】的时候,需要返回 LinkedHashSet 集合!因为它需要有序!
|
||||
* 注意,多个任务实例,每个任务实例对应一个处理人,所以返回的数量就是任务处理人的数量
|
||||
*
|
||||
* @param execution 执行任务
|
||||
* @return 数量
|
||||
*/
|
||||
@Override
|
||||
protected int resolveNrOfInstances(DelegateExecution execution) {
|
||||
@@ -49,9 +58,8 @@ public class BpmSequentialMultiInstanceBehavior extends SequentialMultiInstanceB
|
||||
super.collectionElementVariable = FlowableUtils.formatExecutionCollectionElementVariable(execution.getCurrentActivityId());
|
||||
|
||||
// 第二步,获取任务的所有处理人
|
||||
// 不使用 execution.getVariable 原因:目前依次审批任务回退后 collectionVariable 变量没有清理, 如果重新进入该任务不会重新分配审批人
|
||||
@SuppressWarnings("unchecked")
|
||||
Set<Long> assigneeUserIds = (Set<Long>) execution.getVariableLocal(super.collectionVariable, Set.class);
|
||||
Set<Long> assigneeUserIds = (Set<Long>) execution.getVariable(super.collectionVariable, Set.class);
|
||||
if (assigneeUserIds == null) {
|
||||
assigneeUserIds = taskCandidateInvoker.calculateUsersByTask(execution);
|
||||
if (CollUtil.isEmpty(assigneeUserIds)) {
|
||||
@@ -80,19 +88,4 @@ public class BpmSequentialMultiInstanceBehavior extends SequentialMultiInstanceB
|
||||
return super.resolveNrOfInstances(execution);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void executeOriginalBehavior(DelegateExecution execution, ExecutionEntity multiInstanceRootExecution, int loopCounter) {
|
||||
// 参见 https://t.zsxq.com/53Meo 情况
|
||||
if (execution.getCurrentFlowElement() instanceof CallActivity
|
||||
|| execution.getCurrentFlowElement() instanceof SubProcess) {
|
||||
super.executeOriginalBehavior(execution, multiInstanceRootExecution, loopCounter);
|
||||
return;
|
||||
}
|
||||
// 参见 https://gitee.com/zhijiantianya/yudao-cloud/issues/IC239F
|
||||
super.collectionExpression = null;
|
||||
super.collectionVariable = FlowableUtils.formatExecutionCollectionVariable(execution.getCurrentActivityId());
|
||||
super.collectionElementVariable = FlowableUtils.formatExecutionCollectionElementVariable(execution.getCurrentActivityId());
|
||||
super.executeOriginalBehavior(execution, multiInstanceRootExecution, loopCounter);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -7,10 +7,7 @@ import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCand
|
||||
import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils;
|
||||
import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils;
|
||||
import lombok.Setter;
|
||||
import org.flowable.bpmn.model.Activity;
|
||||
import org.flowable.bpmn.model.CallActivity;
|
||||
import org.flowable.bpmn.model.FlowElement;
|
||||
import org.flowable.bpmn.model.UserTask;
|
||||
import org.flowable.bpmn.model.*;
|
||||
import org.flowable.engine.delegate.DelegateExecution;
|
||||
import org.flowable.engine.impl.bpmn.behavior.AbstractBpmnActivityBehavior;
|
||||
import org.flowable.engine.impl.bpmn.behavior.SequentialMultiInstanceBehavior;
|
||||
@@ -85,6 +82,12 @@ public class BpmSequentialMultiInstanceBehavior extends SequentialMultiInstanceB
|
||||
|
||||
@Override
|
||||
protected void executeOriginalBehavior(DelegateExecution execution, ExecutionEntity multiInstanceRootExecution, int loopCounter) {
|
||||
// 参见 https://t.zsxq.com/53Meo 情况
|
||||
if (execution.getCurrentFlowElement() instanceof CallActivity
|
||||
|| execution.getCurrentFlowElement() instanceof SubProcess) {
|
||||
super.executeOriginalBehavior(execution, multiInstanceRootExecution, loopCounter);
|
||||
return;
|
||||
}
|
||||
// 参见 https://gitee.com/zhijiantianya/yudao-cloud/issues/IC239F
|
||||
super.collectionExpression = null;
|
||||
super.collectionVariable = FlowableUtils.formatExecutionCollectionVariable(execution.getCurrentActivityId());
|
||||
|
||||
@@ -43,7 +43,7 @@ public class FileController {
|
||||
|
||||
@PostMapping("/upload")
|
||||
@Operation(summary = "上传文件", description = "模式一:后端上传文件")
|
||||
public CommonResult<String> uploadFile(FileUploadReqVO uploadReqVO) throws Exception {
|
||||
public CommonResult<String> uploadFile(@Valid FileUploadReqVO uploadReqVO) throws Exception {
|
||||
MultipartFile file = uploadReqVO.getFile();
|
||||
byte[] content = IoUtil.readBytes(file.getInputStream());
|
||||
return success(fileService.createFile(content, file.getOriginalFilename(),
|
||||
|
||||
@@ -585,7 +585,7 @@ public class PayOrderServiceImpl implements PayOrderService {
|
||||
log.error("[expireOrder][order({}) 更新为支付关闭失败]", order.getId());
|
||||
return false;
|
||||
}
|
||||
log.info("[expireOrder][order({}) 更新为支付关闭失败]", order.getId());
|
||||
log.info("[expireOrder][order({}) 更新为支付关闭成功]", order.getId());
|
||||
return true;
|
||||
} catch (Throwable e) {
|
||||
log.error("[expireOrder][order({}) 过期订单异常]", order.getId(), e);
|
||||
|
||||
@@ -0,0 +1,212 @@
|
||||
package cn.iocoder.yudao.module.system.framework.captcha.core;
|
||||
|
||||
import com.anji.captcha.model.common.RepCodeEnum;
|
||||
import com.anji.captcha.model.common.ResponseModel;
|
||||
import com.anji.captcha.model.vo.CaptchaVO;
|
||||
import com.anji.captcha.service.impl.AbstractCaptchaService;
|
||||
import com.anji.captcha.service.impl.CaptchaServiceFactory;
|
||||
import com.anji.captcha.util.AESUtil;
|
||||
import com.anji.captcha.util.ImageUtils;
|
||||
import com.anji.captcha.util.RandomUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import cn.hutool.core.util.RandomUtil;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.geom.AffineTransform;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.util.Properties;
|
||||
|
||||
/**
|
||||
* 图片文字验证码
|
||||
*
|
||||
* @author Tsui
|
||||
* @since 2025/7/23 20:44
|
||||
*/
|
||||
public class PictureWordCaptchaServiceImpl extends AbstractCaptchaService {
|
||||
|
||||
/**
|
||||
* 验证码的基础字符
|
||||
*/
|
||||
private static final String CHARACTERS = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789";
|
||||
/**
|
||||
* 验证码长度
|
||||
*/
|
||||
private static final Integer LENGTH = 4;
|
||||
|
||||
private static final int WIDTH = 120;
|
||||
private static final int HEIGHT = 40;
|
||||
private static final int LINES = 10;
|
||||
|
||||
@Override
|
||||
public void init(Properties config) {
|
||||
super.init(config);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy(Properties config) {
|
||||
logger.info("start-clear-history-data-{}", captchaType());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String captchaType() {
|
||||
return "pictureWord";
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResponseModel get(CaptchaVO captchaVO) {
|
||||
String text = generateRandomText(LENGTH);
|
||||
CaptchaVO imageData = getImageData(text);
|
||||
// pointJson 不传到前端,只做后端校验,测试时放开
|
||||
// imageData.setPointJson(text);
|
||||
return ResponseModel.successData(imageData);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResponseModel check(CaptchaVO captchaVO) {
|
||||
ResponseModel r = super.check(captchaVO);
|
||||
if (!validatedReq(r)) {
|
||||
return r;
|
||||
}
|
||||
|
||||
// 取出验证码
|
||||
String codeKey = String.format(REDIS_CAPTCHA_KEY, captchaVO.getToken());
|
||||
if (!CaptchaServiceFactory.getCache(cacheType).exists(codeKey)) {
|
||||
return ResponseModel.errorMsg(RepCodeEnum.API_CAPTCHA_INVALID);
|
||||
}
|
||||
// 正确的验证码
|
||||
String codeValue = CaptchaServiceFactory.getCache(cacheType).get(codeKey);
|
||||
String code = getCodeByCodeValue(codeValue);
|
||||
String secretKey = getSecretKeyByCodeValue(codeValue);
|
||||
// 验证码只用一次,即刻失效
|
||||
CaptchaServiceFactory.getCache(cacheType).delete(codeKey);
|
||||
|
||||
// 用户输入的验证码(CaptchaVO 中 没有预留字段,暂时用 pointJson 无需加解密)
|
||||
String userCode = captchaVO.getPointJson();
|
||||
if (!StringUtils.equalsIgnoreCase(code, userCode)) {
|
||||
afterValidateFail(captchaVO);
|
||||
return ResponseModel.errorMsg(RepCodeEnum.API_CAPTCHA_COORDINATE_ERROR);
|
||||
}
|
||||
|
||||
// 校验成功,将信息存入缓存
|
||||
String value;
|
||||
try {
|
||||
value = AESUtil.aesEncrypt(captchaVO.getToken().concat("---").concat(userCode), secretKey);
|
||||
} catch (Exception e) {
|
||||
logger.error("AES 加密失败", e);
|
||||
afterValidateFail(captchaVO);
|
||||
return ResponseModel.errorMsg(e.getMessage());
|
||||
}
|
||||
String secondKey = String.format(REDIS_SECOND_CAPTCHA_KEY, value);
|
||||
CaptchaServiceFactory.getCache(cacheType).set(secondKey, captchaVO.getToken(), EXPIRESIN_THREE);
|
||||
captchaVO.setResult(true);
|
||||
captchaVO.resetClientFlag();
|
||||
return ResponseModel.successData(captchaVO);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResponseModel verification(CaptchaVO captchaVO) {
|
||||
ResponseModel r = super.verification(captchaVO);
|
||||
if (!validatedReq(r)) {
|
||||
return r;
|
||||
}
|
||||
try {
|
||||
String codeKey = String.format(REDIS_SECOND_CAPTCHA_KEY, captchaVO.getCaptchaVerification());
|
||||
if (!CaptchaServiceFactory.getCache(cacheType).exists(codeKey)) {
|
||||
return ResponseModel.errorMsg(RepCodeEnum.API_CAPTCHA_INVALID);
|
||||
}
|
||||
// 二次校验取值后,即刻失效
|
||||
CaptchaServiceFactory.getCache(cacheType).delete(codeKey);
|
||||
} catch (Exception e) {
|
||||
logger.error("验证码解析失败", e);
|
||||
return ResponseModel.errorMsg(e.getMessage());
|
||||
}
|
||||
return ResponseModel.success();
|
||||
}
|
||||
|
||||
|
||||
private CaptchaVO getImageData(String text) {
|
||||
CaptchaVO dataVO = new CaptchaVO();
|
||||
BufferedImage image = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB);
|
||||
Graphics2D g = image.createGraphics();
|
||||
|
||||
// 设置背景色
|
||||
g.setColor(getRandomColor(200, 250));
|
||||
g.fillRect(0, 0, WIDTH, HEIGHT);
|
||||
// 绘制干扰线
|
||||
for (int i = 0; i < LINES; i++) {
|
||||
g.setColor(getRandomColor(100, 200));
|
||||
int x1 = RandomUtil.randomInt(WIDTH);
|
||||
int y1 = RandomUtil.randomInt(HEIGHT);
|
||||
int x2 = RandomUtil.randomInt(WIDTH);
|
||||
int y2 = RandomUtil.randomInt(HEIGHT);
|
||||
g.drawLine(x1, y1, x2, y2);
|
||||
}
|
||||
// 设置字体
|
||||
g.setFont(new Font("Arial", Font.BOLD, 24));
|
||||
// 绘制验证码文本
|
||||
for (int i = 0; i < text.length(); i++) {
|
||||
g.setColor(getRandomColor(20, 130));
|
||||
// 文字旋转
|
||||
AffineTransform affineTransform = new AffineTransform();
|
||||
int x = 20 + i * 20;
|
||||
int y = 24 + RandomUtil.randomInt(8);
|
||||
// 旋转范围 -45 ~ 45
|
||||
affineTransform.setToRotation(Math.toRadians(RandomUtil.randomInt(-45, 45)), x, y);
|
||||
g.setTransform(affineTransform);
|
||||
g.drawString(text.charAt(i) + "", x, y);
|
||||
}
|
||||
// 添加噪点
|
||||
for (int i = 0; i < 100; i++) {
|
||||
int x = RandomUtil.randomInt(WIDTH);
|
||||
int y = RandomUtil.randomInt(HEIGHT);
|
||||
image.setRGB(x, y, getRandomColor(0, 255).getRGB());
|
||||
}
|
||||
g.dispose();
|
||||
|
||||
String secretKey = null;
|
||||
if (captchaAesStatus) {
|
||||
secretKey = AESUtil.getKey();
|
||||
}
|
||||
dataVO.setSecretKey(secretKey);
|
||||
|
||||
dataVO.setOriginalImageBase64(ImageUtils.getImageToBase64Str(image).replaceAll("\r|\n", ""));
|
||||
dataVO.setToken(RandomUtils.getUUID());
|
||||
// dataVO.setSecretKey(secretKey);
|
||||
// 将坐标信息存入 redis 中
|
||||
String codeKey = String.format(REDIS_CAPTCHA_KEY, dataVO.getToken());
|
||||
CaptchaServiceFactory.getCache(cacheType).set(codeKey, getCodeValue(text, secretKey), EXPIRESIN_SECONDS);
|
||||
return dataVO;
|
||||
}
|
||||
|
||||
private String getCodeValue(String text, String secretKey) {
|
||||
return text + "," + secretKey;
|
||||
}
|
||||
|
||||
private String getCodeByCodeValue(String codeValue) {
|
||||
return codeValue.split(",")[0];
|
||||
}
|
||||
|
||||
private String getSecretKeyByCodeValue(String codeValue) {
|
||||
return codeValue.split(",")[1];
|
||||
}
|
||||
|
||||
private Color getRandomColor(int min, int max) {
|
||||
int minVal = Math.min(min, max);
|
||||
int maxVal = Math.max(min, max);
|
||||
int r = RandomUtil.randomInt(minVal, maxVal);
|
||||
int g = RandomUtil.randomInt(minVal, maxVal);
|
||||
int b = RandomUtil.randomInt(minVal, maxVal);
|
||||
return new Color(r, g, b);
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成指定长度的随机字符串
|
||||
*
|
||||
* @param length 长度
|
||||
* @return {@link String}
|
||||
*/
|
||||
public static String generateRandomText(int length) {
|
||||
return RandomUtil.randomString(CHARACTERS, length);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
cn.iocoder.yudao.module.system.framework.captcha.core.PictureWordCaptchaServiceImpl
|
||||
@@ -107,7 +107,7 @@ aj:
|
||||
cache-type: redis # 缓存 local/redis...
|
||||
cache-number: 1000 # local 缓存的阈值,达到这个值,清除缓存
|
||||
timing-clear: 180 # local定时清除过期缓存(单位秒),设置为0代表不执行
|
||||
type: blockPuzzle # 验证码类型 default两种都实例化。 blockPuzzle 滑块拼图 clickWord 文字点选
|
||||
type: blockPuzzle # 验证码类型 default 三种都实例化。blockPuzzle 滑块拼图、clickWord 文字点选、pictureWord 文本输入
|
||||
water-mark: 芋道源码 # 右下角水印文字(我的水印),可使用 https://tool.chinaz.com/tools/unicode.aspx 中文转 Unicode,Linux 可能需要转 unicode
|
||||
interference-options: 0 # 滑动干扰项(0/1/2)
|
||||
req-frequency-limit-enable: false # 接口请求次数一分钟限制是否开启 true|false
|
||||
|
||||
Reference in New Issue
Block a user