提交 c12bd75d authored 作者: hzh's avatar hzh

Merge branch 'dev'

package org.dromara.resource.api;
import cn.hutool.extra.mail.MailAccount;
import org.dromara.common.core.exception.ServiceException;
import java.util.List;
/**
* 邮件服务
*
......@@ -18,4 +21,11 @@ public interface RemoteMailService {
*/
void send(String to, String subject, String text) throws ServiceException;
/**
* 获取邮件账户对象集合
*
* @return 邮件账户对象集合
*/
List<MailAccount> getMailAccountList();
}
......@@ -23,4 +23,11 @@ public interface RemoteDeptService {
*/
Long selectLeaderIdByUserId(Long userId);
/**
* 获取部门负责人id
*
* @param fileId 文件id
* @return 负责人id
*/
Long selectLeaderIdByFileId(Long fileId);
}
......@@ -23,13 +23,23 @@ public interface RemoteWorkflowService {
* 获取当前流程状态
*
* @param taskId 任务id
* @return 当前流程状态
*/
String getBusinessStatusByTaskId(String taskId);
/**
* 根据业务id获取流程key集合
* @param businessKey 业务id
* @return 流程key集合
*/
String[] getTaskDefinitionKeyListByBusinessKey(String businessKey);
/**
* 获取当前流程状态
*
* @param businessKey 业务id
* @return 当前流程状态
*/
String getBusinessStatus(String businessKey);
......
......@@ -101,7 +101,7 @@ public class TokenController {
LoginVo loginVo = IAuthStrategy.login(body, clientVo, grantType);
Long userId = LoginHelper.getUserId();
if(!ObjectUtil.isNotNull(userId)){
if (ObjectUtil.isNotNull(userId)) {
scheduledExecutorService.schedule(() -> {
remoteMessageService.publishMessage(userId, "欢迎登录商旅微服务管理系统");
}, 3, TimeUnit.SECONDS);
......
......@@ -17,7 +17,11 @@ public enum AuthApiEnum implements ApiEnum {
/**
* 刷新AccessToken接口
*/
REFRESH("/api/v1/account/refresh", "刷新AccessToken接口");
REFRESH("/api/v1/account/refresh", "刷新AccessToken接口"),
/**
* 获取登录用户的菜单
*/
MENUS("/api/v1/meta/menus", "获取登录用户的菜单");
/**
* 接口URL
......
package org.dromara.common.weishi.model.res;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
import java.util.Map;
/**
* @author wenhe
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class MenusResponse {
/**
* maps
*/
private Map<String,Object> Maps;
/**
* 菜单
*/
private List<Map<String,Object>> Menus;
}
......@@ -15,14 +15,24 @@ public interface WeishiService {
/**
* 认证
*
* @param request 请求参数
* @return 响应参数
*/
AuthResponse authorize(AuthRequest request);
/**
* 获取菜单
*
* @param token token
* @return 响应数据
*/
MenusResponse menus(String token);
/**
* 保险下单接口
*
* @param token token
* @param request 请求参数
* @return 响应数据
*/
......@@ -31,6 +41,7 @@ public interface WeishiService {
/**
* 核保接口
*
* @param token token
* @param request 请求参数
* @return 响应数据
*/
......@@ -39,6 +50,7 @@ public interface WeishiService {
/**
* 签单接口
*
* @param token token
* @param orderNum 订单号
* @return 数据
*/
......@@ -46,6 +58,7 @@ public interface WeishiService {
/**
* 作废接口
*
* @param token token
* @param orderId 订单id
*/
......@@ -72,6 +85,7 @@ public interface WeishiService {
/**
* 订单撤单接口
*
* @param token token
* @param orderNum 订单号
*/
void insureCancel(String token, String orderNum);
......@@ -79,6 +93,7 @@ public interface WeishiService {
/**
* 保单撤单接口
*
* @param token token
* @param policyNum 保单号
*/
void policyCancel(String token, String policyNum);
......@@ -86,6 +101,7 @@ public interface WeishiService {
/**
* 产品列表接口
*
* @param token token
* @return 产品列表
*/
List<ProductsResponse> productList(String token);
......@@ -93,6 +109,7 @@ public interface WeishiService {
/**
* 产品详情
*
* @param token token
* @param productId 产品id
* @return 产品详情
*/
......@@ -101,6 +118,7 @@ public interface WeishiService {
/**
* 获取保险公司相关文档接口
*
* @param token token
* @param productId 产品id
* @param annexTp annexTp
* @return 数据
......
......@@ -41,6 +41,19 @@ public class WeishiServiceImpl implements WeishiService {
return JSON.parseObject(res.getData(), AuthResponse.class);
}
@Override
public MenusResponse menus(String token) {
ApiHttpResponse res = Api.v1(RequestMethodEnum.POST,
DomainEnum.PROD.getDomain(),
AuthApiEnum.MENUS.getUrl(),
token,
null);
if (!Objects.equals(res.getResultTp(), Code.SUCCESS.getCode())) {
throw new WeishiException(res.getResultMsg());
}
return JSON.parseObject(res.getData(), MenusResponse.class);
}
@Override
public ApplyResponse insureApply(String token, ApplyRequest request) {
ApplyResponse res = Api.v1(RequestMethodEnum.POST,
......
......@@ -114,6 +114,25 @@
<artifactId>hutool-json</artifactId>
</dependency>
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itextpdf</artifactId>
<version>5.5.13.3</version>
</dependency>
<dependency>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio-webp</artifactId>
<version>3.8.2</version>
</dependency>
<!-- TIFF 支持 -->
<dependency>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio-tiff</artifactId>
<version>3.8.2</version>
</dependency>
</dependencies>
</project>
......@@ -2,14 +2,20 @@ package org.dromara.common.core.utils.file;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.URLUtil;
import cn.hutool.http.HttpUtil;
import jakarta.servlet.http.HttpServletResponse;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import lombok.SneakyThrows;
import org.dromara.common.core.utils.StringUtils;
import java.io.File;
import java.net.URL;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.nio.file.Paths;
/**
* 文件处理工具类
......@@ -88,4 +94,66 @@ public class FileUtils extends FileUtil {
FileUtil.writeUtf8String(data, file);
return file;
}
/**
* 将 URL 转换为 File 对象(自动处理本地/网络 URL)
*
* @param urlStr URL 字符串(支持 file:/// 或 http(s):// 协议)
* @return 本地 File 对象(网络 URL 会先下载到临时目录)
* @throws Exception 转换失败时抛出异常
*/
public static File convert(String urlStr, String fileName) throws Exception {
URL url = URLUtil.url(urlStr);
// 1. 处理本地文件 URL(file 协议)
if ("file".equals(url.getProtocol())) {
return new File(url.getPath());
}
// 2. 处理网络 URL(http/https 等),需先下载到本地
else {
return downloadToLocal(urlStr, fileName);
}
}
/**
* 下载网络 URL 到本地临时目录
*/
private static File downloadToLocal(String urlStr, String fileName) {
try {
// 从 URL 中提取文件名
if (StringUtils.isEmpty(fileName)) {
fileName = urlStr.substring(urlStr.lastIndexOf("/") + 1);
}
// 生成临时文件路径(使用系统临时目录)
String tempDir = System.getProperty("java.io.tmpdir");
String localPath = tempDir + File.separator + fileName;
// 使用 Hutool 下载文件(支持超时和重试)
HttpUtil.downloadFile(urlStr, localPath);
return new File(localPath);
} catch (Exception e) {
throw new RuntimeException("URL 下载失败:" + urlStr, e);
}
}
/**
* 在文件名前添加指定文件夹
* @param originalPath 原始文件路径
* @param folderName 要添加的文件夹名
* @return 新的文件路径
*/
public static String addFolderBeforeFileName(String originalPath, String folderName) {
Path path = Paths.get(originalPath);
// 获取文件名
String fileName = path.getFileName().toString();
// 构建新路径:父目录 + 新文件夹 + 文件名
Path newPath = path.getParent()
.resolve(folderName)
.resolve(fileName);
return newPath.toString();
}
}
package org.dromara.common.core.utils.file;
import cn.hutool.core.io.FileUtil;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.core.utils.file.watermark.WatermarkProcessor;
import org.dromara.common.core.utils.file.watermark.config.WatermarkConfig;
import org.dromara.common.core.utils.file.watermark.processor.ImageWatermarkProcessor;
import org.dromara.common.core.utils.file.watermark.processor.PdfWatermarkProcessor;
import java.awt.*;
import java.io.File;
import java.util.HashMap;
import java.util.Map;
/**
* 水印工具类 - 支持图片和PDF添加水印
*/
@Slf4j
public class WatermarkUtil {
// 支持的文件类型处理器映射
private static final Map<String, WatermarkProcessor> PROCESSOR_MAP = new HashMap<>();
static {
// 初始化各类型文件的处理器
PROCESSOR_MAP.put("jpg", new ImageWatermarkProcessor());
PROCESSOR_MAP.put("jpeg", new ImageWatermarkProcessor());
PROCESSOR_MAP.put("png", new ImageWatermarkProcessor());
PROCESSOR_MAP.put("gif", new ImageWatermarkProcessor());
PROCESSOR_MAP.put("pdf", new PdfWatermarkProcessor());
}
/**
* 为文件添加文字水印
*
* @param sourceFile 源文件
* @param targetFile 目标文件
* @param config 水印配置
* @return 是否成功添加水印
*/
public static boolean addTextWatermark(File sourceFile, File targetFile, WatermarkConfig config) {
// 获取文件扩展名
String extension = getFileExtension(sourceFile.getName()).toLowerCase();
// 获取对应的处理器
WatermarkProcessor processor = PROCESSOR_MAP.get(extension);
if (processor == null) {
log.info("不支持的文件类型: " + extension);
return false;
}
// 执行水印处理
return processor.process(sourceFile, targetFile, config);
}
/**
* 获取文件footName,
*
* @param path 文件路径
* @param watermarkText 水印文字
* @return 文件字体
*/
public static String getFontName(String path, String watermarkText) {
// 获取文件扩展名
String extension = getFileExtension(path).toLowerCase();
if (StringUtils.equals(extension, "pdf")) {
return watermarkText.codePoints().anyMatch(c -> Character.UnicodeScript.of(c) == Character.UnicodeScript.HAN) ? "simhei" : "arial";
} else {
return "宋体";
}
}
/**
* 获取文件扩展名
*/
public static String getFileExtension(String fileName) {
int dotIndex = fileName.lastIndexOf('.');
return (dotIndex == -1) ? "" : fileName.substring(dotIndex + 1);
}
public static void main(String[] args) {
// 示例:给PDF添加水印
File pdfFile = new File("C:\\Users\\wenhe\\Downloads\\安盛保险-卓越个人基础版.pdf");
File pdfOutputFile = FileUtil.touch(FileUtils.addFolderBeforeFileName(pdfFile.getPath(), "watermark"));
// 配置PDF水印参数
WatermarkConfig pdfConfig = new WatermarkConfig.Builder()
.watermarkText("PDF")
.fontName(getFontName(pdfFile.getPath(),"PDF"))
.fontSize(30)
.color(Color.RED)
.alpha(0.3f)
.angle(45)
.build();
addTextWatermark(pdfFile, pdfOutputFile, pdfConfig);
}
}
package org.dromara.common.core.utils.file.watermark;
import org.dromara.common.core.utils.file.watermark.config.WatermarkConfig;
import java.io.File;
/**
* 水印处理器接口
*/
public interface WatermarkProcessor {
boolean process(File sourceFile, File targetFile, WatermarkConfig config);
}
package org.dromara.common.core.utils.file.watermark.config;
import java.awt.*;
/**
* 水印配置类
* @author wenhe
*/
public class WatermarkConfig {
/**
* 水印文本内容
*/
private final String watermarkText;
/**
* 水印字体名称,默认为 Arial
* 支持系统安装的所有字体名称,例如:
* - "SimSun"(宋体)
* - "Microsoft YaHei"(微软雅黑)
* - "SimHei"(黑体)
*/
private final String fontName;
/**
* 水印字体大小,单位为像素,默认为 20px
*/
private final float fontSize;
/**
* 水印字体颜色,默认为黑色
*/
private final Color color;
/**
* 水印透明度,取值范围 0.0(完全透明)到 1.0(完全不透明),默认为 0.5
*/
private final float alpha;
/**
* 水印旋转角度,单位为度,默认为 0(水平)
* 正值表示顺时针旋转,负值表示逆时针旋转
*/
private final int angle;
private WatermarkConfig(Builder builder) {
this.watermarkText = builder.watermarkText;
this.fontName = builder.fontName;
this.fontSize = builder.fontSize;
this.color = builder.color;
this.alpha = builder.alpha;
this.angle = builder.angle;
}
public String getWatermarkText() {
return watermarkText;
}
public String getFontName() {
return fontName;
}
public float getFontSize() {
return fontSize;
}
public Color getColor() {
return color;
}
public float getAlpha() {
return alpha;
}
public int getAngle() {
return angle;
}
public static class Builder {
private String watermarkText;
private String fontName = "Arial";
private float fontSize = 20;
private Color color = Color.BLACK;
private float alpha = 0.5f;
private int angle = 0;
public Builder watermarkText(String watermarkText) {
this.watermarkText = watermarkText;
return this;
}
public Builder fontName(String fontName) {
this.fontName = fontName;
return this;
}
public Builder fontSize(float fontSize) {
this.fontSize = fontSize;
return this;
}
public Builder color(Color color) {
this.color = color;
return this;
}
public Builder alpha(float alpha) {
this.alpha = alpha;
return this;
}
public Builder angle(int angle) {
this.angle = angle;
return this;
}
public WatermarkConfig build() {
// 可以添加参数验证逻辑
if (watermarkText == null || watermarkText.isEmpty()) {
throw new IllegalArgumentException("水印文本不能为空");
}
if (fontSize <= 0) {
throw new IllegalArgumentException("字体大小必须大于0");
}
if (alpha < 0 || alpha > 1) {
throw new IllegalArgumentException("透明度必须在0.0到1.0之间");
}
return new WatermarkConfig(this);
}
}
}
package org.dromara.common.core.utils.file.watermark.processor;
import org.dromara.common.core.utils.file.WatermarkUtil;
import org.dromara.common.core.utils.file.watermark.WatermarkProcessor;
import org.dromara.common.core.utils.file.watermark.config.WatermarkConfig;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.*;
/**
* 图片水印处理器
*/
public class ImageWatermarkProcessor implements WatermarkProcessor {
@Override
public boolean process(File sourceFile, File targetFile, WatermarkConfig config) {
try (InputStream is = new FileInputStream(sourceFile);
OutputStream os = new FileOutputStream(targetFile)) {
// 读取图片
BufferedImage image = ImageIO.read(is);
int width = image.getWidth();
int height = image.getHeight();
// 创建绘图上下文
BufferedImage bufferedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
Graphics2D g = bufferedImage.createGraphics();
g.drawImage(image, 0, 0, width, height, null);
// 设置水印字体和颜色
Font font = new Font(config.getFontName(), Font.PLAIN, (int) config.getFontSize());
g.setFont(font);
g.setColor(config.getColor());
g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, config.getAlpha()));
// 设置旋转角度
if (config.getAngle() != 0) {
g.rotate(Math.toRadians(config.getAngle()), width / 2, height / 2);
}
// 计算水印文本的宽度和高度
FontMetrics metrics = g.getFontMetrics(font);
int textWidth = metrics.stringWidth(config.getWatermarkText());
int textHeight = metrics.getHeight();
// 添加水印
int xStep = textWidth * 3;
int yStep = textHeight * 3;
int xCount = (width / xStep) + 2;
int yCount = (height / yStep) + 2;
for (int x = 0; x < xCount; x++) {
for (int y = 0; y < yCount; y++) {
int xPos = x * xStep - textWidth;
int yPos = y * yStep;
g.drawString(config.getWatermarkText(), xPos, yPos);
}
}
// 保存图片
String formatName = WatermarkUtil.getFileExtension(targetFile.getName());
ImageIO.write(bufferedImage, formatName, os);
g.dispose();
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
}
package org.dromara.common.core.utils.file.watermark.processor;
import cn.hutool.core.io.FileUtil;
import com.itextpdf.text.BaseColor;
import com.itextpdf.text.DocumentException;
import com.itextpdf.text.Element;
import com.itextpdf.text.pdf.*;
import org.dromara.common.core.utils.file.watermark.WatermarkProcessor;
import org.dromara.common.core.utils.file.watermark.config.WatermarkConfig;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
/**
* PDF水印处理器
*/
public class PdfWatermarkProcessor implements WatermarkProcessor {
public boolean process(File sourceFile, File targetFile, WatermarkConfig config) {
PdfReader reader = null;
PdfStamper stamper = null;
try {
// 移除try-with-resources中的流声明,手动管理资源
reader = new PdfReader(new FileInputStream(sourceFile));
stamper = new PdfStamper(reader, new FileOutputStream(targetFile));
// 转换颜色
BaseColor pdfColor = new BaseColor(
config.getColor().getRed(),
config.getColor().getGreen(),
config.getColor().getBlue()
);
// 获取字体
BaseFont baseFont = getBaseFont(config.getFontName());
// 设置水印透明度
PdfGState gs = new PdfGState();
gs.setFillOpacity(config.getAlpha());
// 遍历PDF的每一页
int totalPages = reader.getNumberOfPages();
for (int i = 1; i <= totalPages; i++) {
// 获取当前页的内容
PdfContentByte content = stamper.getOverContent(i);
content.setGState(gs);
content.beginText();
content.setFontAndSize(baseFont, config.getFontSize());
content.setColorFill(pdfColor);
// 设置旋转角度
content.setTextMatrix(0, 0);
// 计算水印位置和数量
com.itextpdf.text.Rectangle pageSize = reader.getPageSize(i);
float width = pageSize.getWidth();
float height = pageSize.getHeight();
// 添加多个水印
int xStep = 150;
int yStep = 100;
int xCount = (int) (width / xStep) + 1;
int yCount = (int) (height / yStep) + 1;
for (int x = 0; x < xCount; x++) {
for (int y = 0; y < yCount; y++) {
float xPos = x * xStep;
float yPos = y * yStep;
// 在指定位置添加水印
content.showTextAligned(Element.ALIGN_CENTER,
config.getWatermarkText(), xPos, yPos, config.getAngle());
}
}
content.endText();
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
} finally {
// 先关闭stamper,再关闭reader(顺序很重要)
if (stamper != null) {
try {
stamper.close();
} catch (DocumentException | IOException e) {
e.printStackTrace();
}
}
if (reader != null) {
reader.close();
}
}
}
/**
* 获取PDF字体
*/
private BaseFont getBaseFont(String fontName) throws DocumentException, IOException {
// 默认使用Helvetica字体
BaseFont baseFont = BaseFont.createFont(BaseFont.HELVETICA, BaseFont.WINANSI, BaseFont.EMBEDDED);
// 如果指定了其他字体,尝试使用它
if (!"Helvetica".equalsIgnoreCase(fontName)) {
try {
// 尝试查找系统字体
String fontPath = findSystemFontPath(fontName);
if (fontPath != null) {
baseFont = BaseFont.createFont(fontPath, BaseFont.IDENTITY_H, BaseFont.EMBEDDED);
}
} catch (Exception e) {
System.err.println("无法加载指定字体: " + fontName + ", 使用默认字体");
}
}
return baseFont;
}
/**
* 查找系统字体路径
*/
private String findSystemFontPath(String fontName) {
// 这里简化处理,实际应用中可能需要更复杂的字体查找逻辑
String osName = System.getProperty("os.name").toLowerCase();
String fontPath = null;
if (osName.contains("win")) {
fontPath = "C:/Windows/Fonts/" + fontName + ".ttf";
} else if (osName.contains("mac")) {
fontPath = "/Library/Fonts/" + fontName + ".ttf";
} else if (osName.contains("linux")) {
// Linux下尝试多个路径
String[] paths = {
"/usr/share/fonts/truetype/" + fontName + ".ttf",
"/usr/share/fonts/opentype/" + fontName + ".ttf",
"/usr/local/share/fonts/" + fontName + ".ttf"
};
for (String path : paths) {
if (FileUtil.exist(path)) {
return path;
}
}
return paths[0];
}
return fontPath;
}
}
......@@ -68,6 +68,16 @@ public class WeishiController {
return R.ok(weishiService.annex(getToken(), productId, annexTp));
}
/**
* 获取菜单列表
*
* @return 菜单列表
*/
@GetMapping("/menus")
public R<MenusResponse> menus() {
return R.ok(weishiService.menus(getToken()));
}
/**
* 承保接口
*
......
package org.dromara.resource.controller;
import cn.dev33.satoken.annotation.SaCheckPermission;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import lombok.RequiredArgsConstructor;
import org.dromara.common.core.domain.R;
import org.dromara.common.core.validate.AddGroup;
import org.dromara.common.core.validate.EditGroup;
import org.dromara.common.excel.utils.ExcelUtil;
import org.dromara.common.log.annotation.Log;
import org.dromara.common.log.enums.BusinessType;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.web.core.BaseController;
import org.dromara.resource.domain.bo.SysMailConfigBo;
import org.dromara.resource.domain.vo.SysMailConfigVo;
import org.dromara.resource.service.ISysMailConfigService;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* 邮箱配置
* 前端访问路由地址为:/resource/mailConfig
*
* @author hzh
* @date 2025-05-27
*/
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/mailConfig")
public class SysMailConfigController extends BaseController {
private final ISysMailConfigService sysMailConfigService;
/**
* 查询邮箱配置列表
*/
@SaCheckPermission("resource:mailConfig:list")
@GetMapping("/list")
public TableDataInfo<SysMailConfigVo> list(SysMailConfigBo bo, PageQuery pageQuery) {
return sysMailConfigService.queryPageList(bo, pageQuery);
}
/**
* 导出邮箱配置列表
*/
@SaCheckPermission("resource:mailConfig:export")
@Log(title = "邮箱配置", businessType = BusinessType.EXPORT)
@PostMapping("/export")
public void export(SysMailConfigBo bo, HttpServletResponse response) {
List<SysMailConfigVo> list = sysMailConfigService.queryList(bo);
ExcelUtil.exportExcel(list, "邮箱配置", SysMailConfigVo.class, response);
}
/**
* 获取邮箱配置详细信息
*
* @param mailConfigId 主键
*/
@SaCheckPermission("resource:mailConfig:query")
@GetMapping("/{mailConfigId}")
public R<SysMailConfigVo> getInfo(@NotNull(message = "主键不能为空")
@PathVariable Long mailConfigId) {
return R.ok(sysMailConfigService.queryById(mailConfigId));
}
/**
* 新增邮箱配置
*/
@SaCheckPermission("resource:mailConfig:add")
@Log(title = "邮箱配置", businessType = BusinessType.INSERT)
@PostMapping()
public R<Void> add(@Validated(AddGroup.class) @RequestBody SysMailConfigBo bo) {
return toAjax(sysMailConfigService.insertByBo(bo));
}
/**
* 修改邮箱配置
*/
@SaCheckPermission("resource:mailConfig:edit")
@Log(title = "邮箱配置", businessType = BusinessType.UPDATE)
@PutMapping()
public R<Void> edit(@Validated(EditGroup.class) @RequestBody SysMailConfigBo bo) {
return toAjax(sysMailConfigService.updateByBo(bo));
}
/**
* 删除邮箱配置
*
* @param mailConfigIds 主键串
*/
@SaCheckPermission("resource:mailConfig:remove")
@Log(title = "邮箱配置", businessType = BusinessType.DELETE)
@DeleteMapping("/{mailConfigIds}")
public R<Void> remove(@NotEmpty(message = "主键不能为空")
@PathVariable Long[] mailConfigIds) {
return toAjax(sysMailConfigService.deleteWithValidByIds(List.of(mailConfigIds), true));
}
}
......@@ -2,11 +2,17 @@ package org.dromara.resource.controller;
import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.hutool.core.codec.Base64;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.ObjectUtil;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.constraints.NotEmpty;
import lombok.RequiredArgsConstructor;
import org.dromara.common.core.domain.R;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.utils.file.FileUtils;
import org.dromara.common.core.utils.file.WatermarkUtil;
import org.dromara.common.core.utils.file.watermark.config.WatermarkConfig;
import org.dromara.common.core.validate.QueryGroup;
import org.dromara.common.log.annotation.Log;
import org.dromara.common.log.enums.BusinessType;
......@@ -22,6 +28,8 @@ import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.awt.*;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
......@@ -89,6 +97,50 @@ public class SysOssController extends BaseController {
iSysOssService.download(ossId, response);
}
/**
* 下载水印文件
*
* @param ossId OSS对象ID
*/
@GetMapping("/watermark/{ossId}")
public R<String> download(@PathVariable Long ossId, @RequestParam String watermarkText) {
SysOssVo oss = iSysOssService.getById(ossId);
if (oss == null) {
throw new ServiceException("文件不存在");
}
File originFile = null;
File watermarkFile = null;
try {
originFile = FileUtils.convert(oss.getUrl(), oss.getFileName());
watermarkFile = FileUtil.touch(FileUtils.addFolderBeforeFileName(originFile.getPath(), "watermark"));
WatermarkConfig watermarkConfig = new WatermarkConfig.Builder()
.watermarkText(watermarkText)
.fontName(WatermarkUtil.getFontName(originFile.getPath(), watermarkText))
.color(Color.RED)
.alpha(0.3f)
.angle(45)
.build();
boolean result = WatermarkUtil.addTextWatermark(originFile, watermarkFile, watermarkConfig);
String base64;
if (result) {
base64 = Base64.encode(FileUtil.readBytes(watermarkFile));
} else {
base64 = Base64.encode(FileUtil.readBytes(originFile));
}
return R.ok("操作成功", base64);
} catch (Exception e) {
e.printStackTrace();
throw new ServiceException("文件添加水印失败");
} finally {
if (originFile != null) {
originFile.delete();
}
if (watermarkFile != null) {
watermarkFile.delete();
}
}
}
/**
* 删除OSS对象存储
*
......
package org.dromara.resource.domain;
import org.dromara.common.tenant.core.TenantEntity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.io.Serial;
/**
* 邮箱配置对象 sys_mail_config
*
* @author hzh
* @date 2025-05-27
*/
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("sys_mail_config")
public class SysMailConfig extends TenantEntity {
@Serial
private static final long serialVersionUID = 1L;
/**
* 主键
*/
@TableId(value = "mail_config_id")
private Long mailConfigId;
/**
* SMTP服务器域名
*/
private String host;
/**
* SMTP服务端口
*/
private Integer port;
/**
* 是否需要用户名密码验证
*/
private Boolean auth;
/**
* 用户名
*/
private String user;
/**
* 密码
*/
private String pass;
/**
* 发送方,遵循RFC-822标准
*/
@TableField(value = "`from`")
private String from;
/**
* 是否打开调试模式,调试模式会显示与邮件服务器通信过程,默认不开启
*/
private Boolean debug;
/**
* 使用 STARTTLS安全连接,STARTTLS是对纯文本通信协议的扩展。它将纯文本连接升级为加密连接(TLS或SSL), 而不是使用一个单独的加密通信端口。
*/
private Boolean starttlsEnable;
/**
* 使用 SSL安全连接
*/
private Boolean sslEnable;
/**
* SSL协议,多个协议用空格分隔
*/
private String sslProtocols;
/**
* SMTP超时时长,单位毫秒,缺省值不超时
*/
private Long timeout;
/**
* Socket连接超时值,单位毫秒,缺省值不超时
*/
private Long connectionTimeout;
/**
* Socket写出超时值,单位毫秒,缺省值不超时
*/
private Long writeTimeout;
/**
* 排序字段
*/
private Integer orderNum;
/**
* 备注
*/
private String remark;
}
package org.dromara.resource.domain.bo;
import org.dromara.resource.domain.SysMailConfig;
import org.dromara.common.mybatis.core.domain.BaseEntity;
import org.dromara.common.core.validate.AddGroup;
import org.dromara.common.core.validate.EditGroup;
import io.github.linpeilie.annotations.AutoMapper;
import lombok.Data;
import lombok.EqualsAndHashCode;
import jakarta.validation.constraints.*;
/**
* 邮箱配置业务对象 sys_mail_config
*
* @author hzh
* @date 2025-05-27
*/
@Data
@EqualsAndHashCode(callSuper = true)
@AutoMapper(target = SysMailConfig.class, reverseConvertGenerate = false)
public class SysMailConfigBo extends BaseEntity {
/**
* 主键
*/
@NotNull(message = "主键不能为空", groups = { EditGroup.class })
private Long mailConfigId;
/**
* SMTP服务器域名
*/
@NotBlank(message = "SMTP服务器域名不能为空", groups = { AddGroup.class, EditGroup.class })
private String host;
/**
* SMTP服务端口
*/
@NotNull(message = "SMTP服务端口不能为空", groups = { AddGroup.class, EditGroup.class })
private Integer port;
/**
* 是否需要用户名密码验证
*/
@NotNull(message = "是否需要用户名密码验证不能为空", groups = { AddGroup.class, EditGroup.class })
private Boolean auth;
/**
* 用户名
*/
@NotBlank(message = "用户名不能为空", groups = { AddGroup.class, EditGroup.class })
private String user;
/**
* 密码
*/
@NotBlank(message = "密码不能为空", groups = { AddGroup.class, EditGroup.class })
private String pass;
/**
* 发送方,遵循RFC-822标准
*/
@NotBlank(message = "发送方,遵循RFC-822标准不能为空", groups = { AddGroup.class, EditGroup.class })
private String from;
/**
* 是否打开调试模式,调试模式会显示与邮件服务器通信过程,默认不开启
*/
@NotNull(message = "是否打开调试模式,调试模式会显示与邮件服务器通信过程,默认不开启不能为空", groups = { AddGroup.class, EditGroup.class })
private Boolean debug;
/**
* 使用 STARTTLS安全连接,STARTTLS是对纯文本通信协议的扩展。它将纯文本连接升级为加密连接(TLS或SSL), 而不是使用一个单独的加密通信端口。
*/
@NotNull(message = "使用 STARTTLS安全连接,STARTTLS是对纯文本通信协议的扩展。它将纯文本连接升级为加密连接(TLS或SSL), 而不是使用一个单独的加密通信端口。不能为空", groups = { AddGroup.class, EditGroup.class })
private Boolean starttlsEnable;
/**
* 使用 SSL安全连接
*/
@NotNull(message = "使用 SSL安全连接不能为空", groups = { AddGroup.class, EditGroup.class })
private Boolean sslEnable;
/**
* SSL协议,多个协议用空格分隔
*/
private String sslProtocols;
/**
* SMTP超时时长,单位毫秒,缺省值不超时
*/
@NotNull(message = "SMTP超时时长,单位毫秒,缺省值不超时不能为空", groups = { AddGroup.class, EditGroup.class })
private Long timeout;
/**
* Socket连接超时值,单位毫秒,缺省值不超时
*/
@NotNull(message = "Socket连接超时值,单位毫秒,缺省值不超时不能为空", groups = { AddGroup.class, EditGroup.class })
private Long connectionTimeout;
/**
* Socket写出超时值,单位毫秒,缺省值不超时
*/
@NotNull(message = "Socket写出超时值,单位毫秒,缺省值不超时不能为空", groups = { AddGroup.class, EditGroup.class })
private Long writeTimeout;
/**
* 排序字段
*/
@NotNull(message = "排序字段不能为空", groups = { AddGroup.class, EditGroup.class })
private Integer orderNum;
/**
* 备注
*/
private String remark;
}
package org.dromara.resource.domain.vo;
import org.dromara.resource.domain.SysMailConfig;
import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
import com.alibaba.excel.annotation.ExcelProperty;
import org.dromara.common.excel.annotation.ExcelDictFormat;
import org.dromara.common.excel.convert.ExcelDictConvert;
import io.github.linpeilie.annotations.AutoMapper;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.util.Date;
/**
* 邮箱配置视图对象 sys_mail_config
*
* @author hzh
* @date 2025-05-27
*/
@Data
@ExcelIgnoreUnannotated
@AutoMapper(target = SysMailConfig.class)
public class SysMailConfigVo implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 主键
*/
@ExcelProperty(value = "主键")
private Long mailConfigId;
/**
* SMTP服务器域名
*/
@ExcelProperty(value = "SMTP服务器域名")
private String host;
/**
* SMTP服务端口
*/
@ExcelProperty(value = "SMTP服务端口")
private Integer port;
/**
* 是否需要用户名密码验证
*/
@ExcelProperty(value = "是否需要用户名密码验证")
private Boolean auth;
/**
* 用户名
*/
@ExcelProperty(value = "用户名")
private String user;
/**
* 密码
*/
@ExcelProperty(value = "密码")
private String pass;
/**
* 发送方,遵循RFC-822标准
*/
@ExcelProperty(value = "发送方,遵循RFC-822标准")
private String from;
/**
* 是否打开调试模式,调试模式会显示与邮件服务器通信过程,默认不开启
*/
@ExcelProperty(value = "是否打开调试模式,调试模式会显示与邮件服务器通信过程,默认不开启")
private Boolean debug;
/**
* 使用 STARTTLS安全连接,STARTTLS是对纯文本通信协议的扩展。它将纯文本连接升级为加密连接(TLS或SSL), 而不是使用一个单独的加密通信端口。
*/
@ExcelProperty(value = "使用 STARTTLS安全连接,STARTTLS是对纯文本通信协议的扩展。它将纯文本连接升级为加密连接", converter = ExcelDictConvert.class)
@ExcelDictFormat(readConverterExp = "T=LS或SSL")
private Boolean starttlsEnable;
/**
* 使用 SSL安全连接
*/
@ExcelProperty(value = "使用 SSL安全连接")
private Boolean sslEnable;
/**
* SSL协议,多个协议用空格分隔
*/
@ExcelProperty(value = "SSL协议,多个协议用空格分隔")
private String sslProtocols;
/**
* SMTP超时时长,单位毫秒,缺省值不超时
*/
@ExcelProperty(value = "SMTP超时时长,单位毫秒,缺省值不超时")
private Long timeout;
/**
* Socket连接超时值,单位毫秒,缺省值不超时
*/
@ExcelProperty(value = "Socket连接超时值,单位毫秒,缺省值不超时")
private Long connectionTimeout;
/**
* Socket写出超时值,单位毫秒,缺省值不超时
*/
@ExcelProperty(value = "Socket写出超时值,单位毫秒,缺省值不超时")
private Long writeTimeout;
/**
* 排序字段
*/
@ExcelProperty(value = "排序字段")
private Integer orderNum;
/**
* 备注
*/
@ExcelProperty(value = "备注")
private String remark;
}
package org.dromara.resource.dubbo;
import cn.hutool.extra.mail.MailAccount;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.DubboService;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.utils.StreamUtils;
import org.dromara.common.mail.utils.MailUtils;
import org.dromara.resource.api.RemoteMailService;
import org.dromara.resource.domain.SysMailConfig;
import org.dromara.resource.mapper.SysMailConfigMapper;
import org.springframework.stereotype.Service;
import java.util.Comparator;
import java.util.List;
/**
* 邮件服务
*
......@@ -19,6 +26,8 @@ import org.springframework.stereotype.Service;
@DubboService
public class RemoteMailServiceImpl implements RemoteMailService {
private final SysMailConfigMapper sysMailConfigMapper;
/**
* 发送邮件
*
......@@ -31,4 +40,27 @@ public class RemoteMailServiceImpl implements RemoteMailService {
MailUtils.sendText(to, subject, text);
}
@Override
public List<MailAccount> getMailAccountList() {
List<SysMailConfig> configList = sysMailConfigMapper.selectList();
StreamUtils.sorted(configList, Comparator.comparing(SysMailConfig::getOrderNum));
return StreamUtils.toList(configList, config -> {
MailAccount account = new MailAccount()
.setHost(config.getHost())
.setPort(config.getPort())
.setAuth(config.getAuth())
.setUser(config.getUser())
.setPass(config.getPass())
.setFrom(config.getFrom())
.setDebug(config.getDebug())
.setTimeout(config.getTimeout())
.setConnectionTimeout(config.getConnectionTimeout())
.setWriteTimeout(config.getWriteTimeout())
.setStarttlsEnable(config.getStarttlsEnable())
.setSslEnable(config.getSslEnable());
account.setSslProtocols(config.getSslProtocols());
return account;
});
}
}
package org.dromara.resource.mapper;
import org.dromara.resource.domain.SysMailConfig;
import org.dromara.resource.domain.vo.SysMailConfigVo;
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
/**
* 邮箱配置Mapper接口
*
* @author hzh
* @date 2025-05-27
*/
public interface SysMailConfigMapper extends BaseMapperPlus<SysMailConfig, SysMailConfigVo> {
}
package org.dromara.resource.service;
import org.dromara.resource.domain.SysMailConfig;
import org.dromara.resource.domain.vo.SysMailConfigVo;
import org.dromara.resource.domain.bo.SysMailConfigBo;
import org.dromara.resource.domain.SysMailConfig;
import org.dromara.common.mybatis.service.IBaseService;
import java.util.Collection;
import java.util.List;
/**
* 邮箱配置Service接口
*
* @author hzh
* @date 2025-05-27
*/
public interface ISysMailConfigService extends IBaseService<SysMailConfigVo, SysMailConfigBo, SysMailConfig>{
}
package org.dromara.resource.service.impl;
import org.dromara.common.core.utils.MapstructUtils;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.mybatis.core.page.PageQuery;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.dromara.resource.domain.bo.SysMailConfigBo;
import org.dromara.resource.domain.vo.SysMailConfigVo;
import org.dromara.resource.domain.SysMailConfig;
import org.dromara.resource.mapper.SysMailConfigMapper;
import org.dromara.resource.service.ISysMailConfigService;
import org.dromara.common.mybatis.service.AbstractBaseService;
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
import java.util.List;
import java.util.Map;
import java.util.Collection;
/**
* 邮箱配置Service业务层处理
*
* @author hzh
* @date 2025-05-27
*/
@RequiredArgsConstructor
@Service
public class SysMailConfigServiceImpl extends AbstractBaseService<SysMailConfigVo, SysMailConfigBo, SysMailConfig> implements ISysMailConfigService {
private final SysMailConfigMapper baseMapper;
@Override
public BaseMapperPlus<SysMailConfig, SysMailConfigVo> mapper() {
return baseMapper;
}
@Override
public LambdaQueryWrapper<SysMailConfig> buildQueryWrapper(SysMailConfigBo bo) {
Map<String, Object> params = bo.getParams();
LambdaQueryWrapper<SysMailConfig> lqw = Wrappers.lambdaQuery();
lqw.eq(StringUtils.isNotBlank(bo.getHost()), SysMailConfig::getHost, bo.getHost());
lqw.eq(bo.getPort() != null, SysMailConfig::getPort, bo.getPort());
lqw.eq(bo.getAuth() != null, SysMailConfig::getAuth, bo.getAuth());
lqw.eq(StringUtils.isNotBlank(bo.getUser()), SysMailConfig::getUser, bo.getUser());
lqw.eq(StringUtils.isNotBlank(bo.getPass()), SysMailConfig::getPass, bo.getPass());
lqw.eq(StringUtils.isNotBlank(bo.getFrom()), SysMailConfig::getFrom, bo.getFrom());
lqw.eq(bo.getDebug() != null, SysMailConfig::getDebug, bo.getDebug());
lqw.eq(bo.getStarttlsEnable() != null, SysMailConfig::getStarttlsEnable, bo.getStarttlsEnable());
lqw.eq(bo.getSslEnable() != null, SysMailConfig::getSslEnable, bo.getSslEnable());
lqw.eq(StringUtils.isNotBlank(bo.getSslProtocols()), SysMailConfig::getSslProtocols, bo.getSslProtocols());
lqw.eq(bo.getTimeout() != null, SysMailConfig::getTimeout, bo.getTimeout());
lqw.eq(bo.getConnectionTimeout() != null, SysMailConfig::getConnectionTimeout, bo.getConnectionTimeout());
lqw.eq(bo.getWriteTimeout() != null, SysMailConfig::getWriteTimeout, bo.getWriteTimeout());
lqw.eq(bo.getOrderNum() != null, SysMailConfig::getOrderNum, bo.getOrderNum());
return lqw;
}
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.dromara.resource.mapper.SysMailConfigMapper">
</mapper>
package org.dromara.system.constant;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* @author wenhe
*/
@Getter
@AllArgsConstructor
public enum DeptOssScope {
COMPANY("company", "公司"),
DEPT("dept", "部门");
private String key;
private String value;
}
package org.dromara.system.controller;
import lombok.RequiredArgsConstructor;
import org.dromara.common.core.domain.R;
import org.dromara.common.core.validate.AddGroup;
import org.dromara.common.idempotent.annotation.RepeatSubmit;
import org.dromara.common.log.annotation.Log;
import org.dromara.common.log.enums.BusinessType;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.web.core.BaseController;
import org.dromara.system.domain.bo.SysDeptOssDownloadBo;
import org.dromara.system.domain.vo.SysDeptOssDownloadVo;
import org.dromara.system.service.ISysDeptOssDownloadService;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
/**
* 部门文件下载记录
* 前端访问路由地址为:/system/deptOssDownload
*
* @author hzh
* @date 2025-05-26
*/
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/deptOssDownload")
public class SysDeptOssDownloadController extends BaseController {
private final ISysDeptOssDownloadService sysDeptOssDownloadService;
/**
* 查询部门文件下载记录列表
*/
@GetMapping("/list")
public TableDataInfo<SysDeptOssDownloadVo> list(SysDeptOssDownloadBo bo, PageQuery pageQuery) {
return sysDeptOssDownloadService.queryPageList(bo, pageQuery);
}
/**
* 新增部门文件下载记录
*/
@Log(title = "部门文件下载记录", businessType = BusinessType.INSERT)
@RepeatSubmit()
@PostMapping()
public R<Void> add(@Validated(AddGroup.class) @RequestBody SysDeptOssDownloadBo bo) {
return toAjax(sysDeptOssDownloadService.insertByBo(bo));
}
}
package org.dromara.system.domain;
import org.dromara.common.tenant.core.TenantEntity;
import com.baomidou.mybatisplus.annotation.*;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.dromara.common.tenant.core.TenantEntity;
import java.io.Serial;
......@@ -57,10 +58,20 @@ public class SysDeptOss extends TenantEntity {
*/
private String fileSize;
/**
* 是否需要审批
*/
private Boolean approve;
/**
* 是否公开
*/
private Boolean open;
/**
* 可见范围
*/
private String scope;
}
package org.dromara.system.domain;
import org.dromara.common.tenant.core.TenantEntity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.io.Serial;
/**
* 部门文件下载记录对象 sys_dept_oss_download
*
* @author hzh
* @date 2025-05-26
*/
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("sys_dept_oss_download")
public class SysDeptOssDownload extends TenantEntity {
@Serial
private static final long serialVersionUID = 1L;
/**
* 部门文件下载id
*/
@TableId(value = "dept_oss_download_id")
private Long deptOssDownloadId;
/**
* 部门文件id
*/
private Long deptOssId;
}
......@@ -56,6 +56,11 @@ public class SysDeptOssBo extends BaseEntity {
@NotBlank(message = "文件不能为空", groups = { AddGroup.class, EditGroup.class })
private String file;
/**
* 是否需要审批
*/
private Boolean approve;
/**
* 文件大小
*/
......@@ -64,8 +69,13 @@ public class SysDeptOssBo extends BaseEntity {
/**
* 是否公开
*/
@NotNull(message = "是否公开不能为空", groups = { AddGroup.class, EditGroup.class })
private Boolean open;
/**
* 可见范围
*/
@NotBlank(message = "可见范围不能为空", groups = { AddGroup.class, EditGroup.class })
private String scope;
}
package org.dromara.system.domain.bo;
import org.dromara.system.domain.SysDeptOssDownload;
import org.dromara.common.mybatis.core.domain.BaseEntity;
import org.dromara.common.core.validate.AddGroup;
import org.dromara.common.core.validate.EditGroup;
import io.github.linpeilie.annotations.AutoMapper;
import lombok.Data;
import lombok.EqualsAndHashCode;
import jakarta.validation.constraints.*;
/**
* 部门文件下载记录业务对象 sys_dept_oss_download
*
* @author hzh
* @date 2025-05-26
*/
@Data
@EqualsAndHashCode(callSuper = true)
@AutoMapper(target = SysDeptOssDownload.class, reverseConvertGenerate = false)
public class SysDeptOssDownloadBo extends BaseEntity {
/**
* 部门文件下载id
*/
@NotNull(message = "部门文件下载id不能为空", groups = { EditGroup.class })
private Long deptOssDownloadId;
/**
* 部门文件id
*/
@NotNull(message = "部门文件id不能为空", groups = { AddGroup.class, EditGroup.class })
private Long deptOssId;
}
package org.dromara.system.domain.vo;
import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
import com.alibaba.excel.annotation.ExcelProperty;
import io.github.linpeilie.annotations.AutoMapper;
import lombok.Data;
import org.dromara.common.translation.annotation.Translation;
import org.dromara.common.translation.constant.TransConstant;
import org.dromara.system.domain.SysDeptOssDownload;
import java.io.Serial;
import java.io.Serializable;
import java.util.Date;
/**
* 部门文件下载记录视图对象 sys_dept_oss_download
*
* @author hzh
* @date 2025-05-26
*/
@Data
@ExcelIgnoreUnannotated
@AutoMapper(target = SysDeptOssDownload.class)
public class SysDeptOssDownloadVo implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 部门文件下载id
*/
@ExcelProperty(value = "部门文件下载id")
private Long deptOssDownloadId;
/**
* 部门文件id
*/
@ExcelProperty(value = "部门文件id")
private Long deptOssId;
/**
* 创建部门
*/
@ExcelProperty(value = "创建部门")
@Translation(type = TransConstant.DEPT_ID_TO_NAME)
private Long createDept;
/**
* 创建时间
*/
@ExcelProperty(value = "创建时间")
private Date createTime;
/**
* 上传人
*/
@ExcelProperty(value = "上传人")
@Translation(type = TransConstant.USER_ID_TO_NICKNAME)
private Long createBy;
/**
* 手机号
*/
@ExcelProperty(value = "手机号")
private String phone;
}
......@@ -69,6 +69,11 @@ public class SysDeptOssVo implements Serializable {
@Translation(type = TransConstant.OSS_ID_TO_URL, mapper = "file")
private String fileUrl;
/**
* 是否需要审批
*/
@ExcelProperty(value = "是否需要审批")
private Boolean approve;
/**
* 文件大小
......@@ -76,11 +81,22 @@ public class SysDeptOssVo implements Serializable {
@ExcelProperty(value = "文件大小")
private String fileSize;
/**
* 可见范围
*/
@ExcelProperty(value = "可见范围不能为空")
private String scope;
/**
* 是否公开
*/
@ExcelProperty(value = "是否公开")
private Boolean open;
/**
* 创建人id
*/
private Long createBy;
}
......@@ -3,8 +3,10 @@ package org.dromara.system.dubbo;
import lombok.RequiredArgsConstructor;
import org.apache.dubbo.config.annotation.DubboService;
import org.dromara.system.api.RemoteDeptService;
import org.dromara.system.domain.vo.SysDeptOssVo;
import org.dromara.system.domain.vo.SysDeptVo;
import org.dromara.system.domain.vo.SysUserVo;
import org.dromara.system.service.ISysDeptOssService;
import org.dromara.system.service.ISysDeptService;
import org.dromara.system.service.ISysUserService;
import org.springframework.stereotype.Service;
......@@ -21,6 +23,7 @@ public class RemoteDeptServiceImpl implements RemoteDeptService {
private final ISysDeptService sysDeptService;
private final ISysUserService userService;
private final ISysDeptOssService deptOssService;
/**
* 通过部门ID查询部门名称
......@@ -49,4 +52,15 @@ public class RemoteDeptServiceImpl implements RemoteDeptService {
SysDeptVo dept = sysDeptService.selectDeptById(user.getDeptId());
return dept.getLeader();
}
@Override
public Long selectLeaderIdByFileId(Long fileId) {
SysDeptOssVo sdo = deptOssService.queryById(fileId);
if (sdo == null) {
return null;
}
//获取部门
SysDeptVo dept = sysDeptService.selectDeptById(sdo.getDeptId());
return dept.getLeader();
}
}
package org.dromara.system.mapper;
import org.dromara.system.domain.SysDeptOssDownload;
import org.dromara.system.domain.vo.SysDeptOssDownloadVo;
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
/**
* 部门文件下载记录Mapper接口
*
* @author hzh
* @date 2025-05-26
*/
public interface SysDeptOssDownloadMapper extends BaseMapperPlus<SysDeptOssDownload, SysDeptOssDownloadVo> {
}
package org.dromara.system.service;
import org.dromara.common.mybatis.service.IBaseService;
import org.dromara.system.domain.SysDeptOssDownload;
import org.dromara.system.domain.bo.SysDeptOssDownloadBo;
import org.dromara.system.domain.vo.SysDeptOssDownloadVo;
/**
* 部门文件下载记录Service接口
*
* @author hzh
* @date 2025-05-26
*/
public interface ISysDeptOssDownloadService extends IBaseService<SysDeptOssDownloadVo, SysDeptOssDownloadBo, SysDeptOssDownload>{
}
package org.dromara.system.service.impl;
import cn.hutool.core.collection.CollectionUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import lombok.RequiredArgsConstructor;
import org.dromara.common.core.utils.StreamUtils;
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
import org.dromara.common.mybatis.service.AbstractBaseService;
import org.dromara.common.satoken.utils.LoginHelper;
import org.dromara.system.domain.SysDeptOssDownload;
import org.dromara.system.domain.SysUser;
import org.dromara.system.domain.bo.SysDeptOssDownloadBo;
import org.dromara.system.domain.vo.SysDeptOssDownloadVo;
import org.dromara.system.mapper.SysDeptOssDownloadMapper;
import org.dromara.system.mapper.SysUserMapper;
import org.dromara.system.service.ISysDeptOssDownloadService;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
/**
* 部门文件下载记录Service业务层处理
*
* @author hzh
* @date 2025-05-26
*/
@RequiredArgsConstructor
@Service
public class SysDeptOssDownloadServiceImpl extends AbstractBaseService<SysDeptOssDownloadVo, SysDeptOssDownloadBo, SysDeptOssDownload> implements ISysDeptOssDownloadService {
private final SysDeptOssDownloadMapper baseMapper;
private final SysUserMapper sysUserMapper;
@Override
public BaseMapperPlus<SysDeptOssDownload, SysDeptOssDownloadVo> mapper() {
return baseMapper;
}
@Override
public LambdaQueryWrapper<SysDeptOssDownload> buildQueryWrapper(SysDeptOssDownloadBo bo) {
Map<String, Object> params = bo.getParams();
LambdaQueryWrapper<SysDeptOssDownload> lqw = Wrappers.lambdaQuery();
lqw.eq(bo.getDeptOssId()!=null, SysDeptOssDownload::getDeptOssId, bo.getDeptOssId());
lqw.eq(bo.getCreateDept() != null, SysDeptOssDownload::getCreateDept, bo.getCreateDept());
lqw.eq(bo.getCreateTime() != null, SysDeptOssDownload::getCreateTime, bo.getCreateTime());
lqw.eq(bo.getCreateBy() != null, SysDeptOssDownload::getCreateBy, bo.getCreateBy());
return lqw;
}
@Override
public Boolean insertByBo(SysDeptOssDownloadBo bo) {
bo.setCreateDept(LoginHelper.getDeptId());
return super.insertByBo(bo);
}
@Override
public void processData(List<SysDeptOssDownloadVo> list) {
super.processData(list);
//设置手机号
List<Long> userIds = StreamUtils.toList(list, SysDeptOssDownloadVo::getCreateBy);
if (CollectionUtil.isNotEmpty(userIds)) {
List<SysUser> userList = sysUserMapper.selectList(new LambdaQueryWrapper<SysUser>().in(SysUser::getUserId, userIds));
list.forEach(d -> d.setPhone(Optional.ofNullable(StreamUtils.findFirst(userList, u -> Objects.equals(u.getUserId(), d.getCreateBy()))).map(SysUser::getPhonenumber).orElse(null)));
}
}
}
......@@ -7,6 +7,7 @@ import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
import org.dromara.common.mybatis.service.AbstractBaseService;
import org.dromara.common.satoken.utils.LoginHelper;
import org.dromara.system.constant.DeptOssScope;
import org.dromara.system.domain.SysDeptOss;
import org.dromara.system.domain.bo.SysDeptOssBo;
import org.dromara.system.domain.vo.SysDeptOssVo;
......@@ -15,6 +16,7 @@ import org.dromara.system.service.ISysDeptOssService;
import org.springframework.stereotype.Service;
import java.util.Map;
import java.util.Objects;
/**
* 部门文件Service业务层处理
......@@ -42,7 +44,15 @@ public class SysDeptOssServiceImpl extends AbstractBaseService<SysDeptOssVo, Sys
lqw.like(StringUtils.isNotBlank(bo.getWxAppletName()), SysDeptOss::getWxAppletName, bo.getWxAppletName());
lqw.eq(StringUtils.isNotBlank(bo.getFileType()), SysDeptOss::getFileType, bo.getFileType());
lqw.eq(StringUtils.isNotBlank(bo.getFile()), SysDeptOss::getFile, bo.getFile());
lqw.eq(StringUtils.isNotBlank(bo.getScope()), SysDeptOss::getScope, bo.getScope());
lqw.eq(Objects.nonNull(bo.getApprove()), SysDeptOss::getApprove, bo.getApprove());
lqw.eq(bo.getOpen() != null, SysDeptOss::getOpen, bo.getOpen());
if (!LoginHelper.isSuperAdmin()) {
Long deptId = LoginHelper.getDeptId();
if (bo.getDeptId() != null && !Objects.equals(deptId, bo.getDeptId())) {
lqw.eq(SysDeptOss::getScope, DeptOssScope.COMPANY.getKey());
}
}
return lqw;
}
......
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.dromara.system.mapper.SysDeptOssDownloadMapper">
</mapper>
package org.dromara.workflow.controller;
import cn.dev33.satoken.annotation.SaCheckPermission;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import lombok.RequiredArgsConstructor;
import org.dromara.common.core.domain.R;
import org.dromara.common.core.validate.AddGroup;
import org.dromara.common.core.validate.EditGroup;
import org.dromara.common.idempotent.annotation.RepeatSubmit;
import org.dromara.common.log.annotation.Log;
import org.dromara.common.log.enums.BusinessType;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.web.core.BaseController;
import org.dromara.workflow.domain.bo.FileApproveBo;
import org.dromara.workflow.domain.vo.FileApproveVo;
import org.dromara.workflow.service.IFileApproveService;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* 部门文件下载申请
* 前端访问路由地址为:/workflow/fileApprove
*
* @author hzh
* @date 2025-05-22
*/
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/fileApprove")
public class FileApproveController extends BaseController {
private final IFileApproveService fileApproveService;
/**
* 查询部门文件下载申请列表
*/
@GetMapping("/list")
public TableDataInfo<FileApproveVo> list(FileApproveBo bo, PageQuery pageQuery) {
return fileApproveService.queryPageList(bo, pageQuery);
}
/**
* 获取部门文件下载申请详细信息
*
* @param id 主键
*/
@GetMapping("/{id}")
public R<FileApproveVo> getInfo(@NotNull(message = "主键不能为空")
@PathVariable Long id) {
return R.ok(fileApproveService.queryById(id));
}
/**
* 新增部门文件下载申请
*/
@Log(title = "部门文件下载申请", businessType = BusinessType.INSERT)
@RepeatSubmit()
@PostMapping()
public R<FileApproveVo> add(@Validated(AddGroup.class) @RequestBody FileApproveBo bo) {
return R.ok(fileApproveService.insert(bo));
}
/**
* 修改部门文件下载申请
*/
@Log(title = "部门文件下载申请", businessType = BusinessType.UPDATE)
@RepeatSubmit()
@PutMapping()
public R<FileApproveVo> edit(@Validated(EditGroup.class) @RequestBody FileApproveBo bo) {
return R.ok(fileApproveService.update(bo));
}
/**
* 删除部门文件下载申请
*
* @param ids 主键串
*/
@SaCheckPermission("workflow:fileApprove:remove")
@Log(title = "部门文件下载申请", businessType = BusinessType.DELETE)
@DeleteMapping("/{ids}")
public R<Void> remove(@NotEmpty(message = "主键不能为空")
@PathVariable Long[] ids) {
return toAjax(fileApproveService.deleteWithValidByIds(List.of(ids), true));
}
}
......@@ -60,7 +60,7 @@ public class ActHiTaskinst implements Serializable {
/**
* 流程执行id
*/
@TableField(value = "EXECUTION_ID")
@TableField(value = "EXECUTION_ID_")
private String executionId;
/**
......
package org.dromara.workflow.domain;
import org.dromara.common.tenant.core.TenantEntity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.io.Serial;
/**
* 部门文件下载申请对象 sys_dept_file_approve
*
* @author hzh
* @date 2025-05-22
*/
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("sys_dept_file_approve")
public class FileApprove extends TenantEntity {
@Serial
private static final long serialVersionUID = 1L;
/**
* 主键
*/
@TableId(value = "id")
private Long id;
/**
* 部门文件id
*/
private Long sysDeptFileId;
/**
* 文件名称
*/
private String fileName;
/**
* 用途
*/
private String purpose;
/**
* 邮箱
*/
private String email;
/**
* 状态
*/
private String status;
}
package org.dromara.workflow.domain.bo;
import org.dromara.workflow.domain.FileApprove;
import org.dromara.common.mybatis.core.domain.BaseEntity;
import org.dromara.common.core.validate.AddGroup;
import org.dromara.common.core.validate.EditGroup;
import io.github.linpeilie.annotations.AutoMapper;
import lombok.Data;
import lombok.EqualsAndHashCode;
import jakarta.validation.constraints.*;
/**
* 部门文件下载申请业务对象 sys_dept_file_approve
*
* @author hzh
* @date 2025-05-22
*/
@Data
@EqualsAndHashCode(callSuper = true)
@AutoMapper(target = FileApprove.class, reverseConvertGenerate = false)
public class FileApproveBo extends BaseEntity {
/**
* 主键
*/
@NotNull(message = "主键不能为空", groups = { EditGroup.class })
private Long id;
/**
* 部门文件id
*/
@NotNull(message = "部门文件id不能为空", groups = { AddGroup.class, EditGroup.class })
private Long sysDeptFileId;
/**
* 文件名称
*/
@NotBlank(message = "文件名称不能为空", groups = { AddGroup.class, EditGroup.class })
private String fileName;
/**
* 用途
*/
@NotBlank(message = "用途不能为空", groups = { AddGroup.class, EditGroup.class })
private String purpose;
/**
* 邮箱
*/
@NotBlank(message = "邮箱不能为空", groups = { AddGroup.class, EditGroup.class })
private String email;
/**
* 状态
*/
private String status;
}
......@@ -30,4 +30,9 @@ public class TaskBo implements Serializable {
* 流程定义key
*/
private String processDefinitionKey;
/**
* 表名
*/
private String tableName;
}
package org.dromara.workflow.domain.vo;
import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
import com.alibaba.excel.annotation.ExcelProperty;
import io.github.linpeilie.annotations.AutoMapper;
import lombok.Data;
import org.dromara.workflow.domain.FileApprove;
import java.io.Serial;
import java.io.Serializable;
/**
* 部门文件下载申请视图对象 sys_dept_file_approve
*
* @author hzh
* @date 2025-05-22
*/
@Data
@ExcelIgnoreUnannotated
@AutoMapper(target = FileApprove.class)
public class FileApproveVo implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 主键
*/
@ExcelProperty(value = "主键")
private Long id;
/**
* 部门文件id
*/
@ExcelProperty(value = "部门文件id")
private Long sysDeptFileId;
/**
* 文件名称
*/
@ExcelProperty(value = "文件名称")
private String fileName;
/**
* 用途
*/
@ExcelProperty(value = "用途")
private String purpose;
/**
* 邮箱
*/
@ExcelProperty(value = "邮箱")
private String email;
/**
* 状态
*/
@ExcelProperty(value = "状态")
private String status;
}
......@@ -97,4 +97,9 @@ public class ProcessInstanceVo implements Serializable {
* 节点配置
*/
private WfNodeConfigVo wfNodeConfigVo;
/**
* 对象信息
*/
private Object entity;
}
......@@ -170,4 +170,9 @@ public class TaskVo implements Serializable {
* 节点配置
*/
private WfNodeConfigVo wfNodeConfigVo;
/**
* 对象信息
*/
private Object entity;
}
......@@ -3,7 +3,9 @@ package org.dromara.workflow.dubbo;
import lombok.RequiredArgsConstructor;
import org.apache.dubbo.config.annotation.DubboService;
import org.dromara.workflow.api.domain.RemoteWorkflowService;
import org.dromara.workflow.domain.vo.ActHistoryInfoVo;
import org.dromara.workflow.service.IActHiProcinstService;
import org.dromara.workflow.service.IActProcessInstanceService;
import org.dromara.workflow.service.WorkflowService;
import java.util.List;
......@@ -21,6 +23,7 @@ public class RemoteWorkflowServiceImpl implements RemoteWorkflowService {
private final WorkflowService workflowService;
private final IActHiProcinstService actHiProcinstService;
private final IActProcessInstanceService processInstanceService;
@Override
public boolean deleteRunAndHisInstance(List<String> businessKeys) {
......@@ -32,6 +35,12 @@ public class RemoteWorkflowServiceImpl implements RemoteWorkflowService {
return workflowService.getBusinessStatusByTaskId(taskId);
}
@Override
public String[] getTaskDefinitionKeyListByBusinessKey(String businessKey) {
List<ActHistoryInfoVo> ahiList = processInstanceService.getHistoryRecord(businessKey);
return ahiList.toArray(new String[0]);
}
@Override
public String getBusinessStatus(String businessKey) {
return workflowService.getBusinessStatus(businessKey);
......
package org.dromara.workflow.flowable.listener;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.utils.SpringUtils;
import org.dromara.workflow.common.constant.FlowConstant;
import org.dromara.workflow.domain.FileApprove;
import org.dromara.workflow.mapper.FileApproveMapper;
import org.dromara.workflow.service.IDeptService;
import org.flowable.task.service.delegate.DelegateTask;
import org.flowable.task.service.delegate.TaskListener;
import org.springframework.stereotype.Component;
import java.util.Objects;
/**
* 部门文件下载申请自定义审批人监听器
*
* @author wenhe
*/
@Component
@Slf4j
public class DeptFileApproveAssigneeListener implements TaskListener {
@Override
public void notify(DelegateTask delegateTask) {
// 获取业务id
Long businessKey = Long.parseLong(delegateTask.getVariable(FlowConstant.BUSINESS_KEY).toString());
FileApproveMapper mapper = SpringUtils.getBean(FileApproveMapper.class);
FileApprove fa = mapper.selectById(businessKey);
IDeptService deptService = SpringUtils.getBean(IDeptService.class);
// 调用服务获取审批人列表
Long approveId = deptService.selectLeaderIdByFileId(fa.getSysDeptFileId());
// 设置候选用户
String owner = Objects.isNull(approveId) ? null : approveId.toString();
delegateTask.setAssignee(owner);
}
}
package org.dromara.workflow.flowable.listener;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.utils.SpringUtils;
import org.dromara.workflow.common.constant.FlowConstant;
import org.dromara.workflow.service.IDeptService;
......@@ -10,7 +11,13 @@ import org.springframework.stereotype.Component;
import java.util.Objects;
/**
* 获取提交人的部门负责人监听器
*
* @author wenhe
*/
@Component
@Slf4j
public class DynamicAssigneeListener implements TaskListener {
@Override
......@@ -24,6 +31,8 @@ public class DynamicAssigneeListener implements TaskListener {
Long approveId = deptService.selectLeaderIdByUserId(applicantId);
// 设置候选用户
delegateTask.setAssignee(Objects.isNull(approveId) ? null : approveId.toString());
String owner = Objects.isNull(approveId) ? null : approveId.toString();
delegateTask.setAssignee(owner);
}
}
package org.dromara.workflow.mapper;
import org.dromara.workflow.domain.FileApprove;
import org.dromara.workflow.domain.vo.FileApproveVo;
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
/**
* 部门文件下载申请Mapper接口
*
* @author hzh
* @date 2025-05-22
*/
public interface FileApproveMapper extends BaseMapperPlus<FileApprove, FileApproveVo> {
}
......@@ -10,4 +10,12 @@ public interface IDeptService {
*/
Long selectLeaderIdByUserId(Long userId);
/**
* 获取部门负责人id
*
* @param fileId 文件id
* @return 负责人id
*/
Long selectLeaderIdByFileId(Long fileId);
}
package org.dromara.workflow.service;
import org.dromara.common.mybatis.service.IBaseService;
import org.dromara.workflow.domain.FileApprove;
import org.dromara.workflow.domain.bo.FileApproveBo;
import org.dromara.workflow.domain.vo.FileApproveVo;
/**
* 部门文件下载申请Service接口
*
* @author hzh
* @date 2025-05-22
*/
public interface IFileApproveService extends IBaseService<FileApproveVo, FileApproveBo, FileApprove>{
/**
* 新增
* @param bo bo
* @return 数据
*/
FileApproveVo insert(FileApproveBo bo);
/**
* 修改
* @param bo bo
* @return 数据
*/
FileApproveVo update(FileApproveBo bo);
}
......@@ -655,6 +655,7 @@ public class ActProcessInstanceServiceImpl implements IActProcessInstanceService
List<String> processDefinitionIds = StreamUtils.toList(list, ProcessInstanceVo::getProcessDefinitionId);
List<WfNodeConfigVo> wfNodeConfigVoList = wfNodeConfigService.selectByDefIds(processDefinitionIds);
for (ProcessInstanceVo processInstanceVo : list) {
processInstanceVo.setEntity(WorkflowUtils.getHistoricVariableByBusinessKey(processInstanceVo.getBusinessKey(), "entity"));
if (CollUtil.isNotEmpty(wfNodeConfigVoList)) {
wfNodeConfigVoList.stream().filter(e -> e.getDefinitionId().equals(processInstanceVo.getProcessDefinitionId()) && FlowConstant.TRUE.equals(e.getApplyUserTask())).findFirst().ifPresent(processInstanceVo::setWfNodeConfigVo);
}
......
......@@ -9,6 +9,7 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.DubboReference;
import org.dromara.common.core.enums.BusinessStatusEnum;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.utils.StreamUtils;
import org.dromara.common.core.utils.StringUtils;
......@@ -21,9 +22,9 @@ import org.dromara.system.api.RemoteUserService;
import org.dromara.system.api.domain.vo.RemoteUserVo;
import org.dromara.system.api.model.RoleDTO;
import org.dromara.workflow.common.constant.FlowConstant;
import org.dromara.common.core.enums.BusinessStatusEnum;
import org.dromara.workflow.common.enums.TaskStatusEnum;
import org.dromara.workflow.domain.ActHiTaskinst;
import org.dromara.workflow.domain.WfDefinitionConfig;
import org.dromara.workflow.domain.WfTaskBackNode;
import org.dromara.workflow.domain.bo.*;
import org.dromara.workflow.domain.vo.*;
......@@ -31,6 +32,7 @@ import org.dromara.workflow.flowable.cmd.*;
import org.dromara.workflow.flowable.handler.FlowProcessEventHandler;
import org.dromara.workflow.mapper.ActHiTaskinstMapper;
import org.dromara.workflow.mapper.ActTaskMapper;
import org.dromara.workflow.mapper.WfDefinitionConfigMapper;
import org.dromara.workflow.service.IActTaskService;
import org.dromara.workflow.service.IWfDefinitionConfigService;
import org.dromara.workflow.service.IWfNodeConfigService;
......@@ -89,6 +91,7 @@ public class ActTaskServiceImpl implements IActTaskService {
private final IWfNodeConfigService wfNodeConfigService;
private final IWfDefinitionConfigService wfDefinitionConfigService;
private final FlowProcessEventHandler flowProcessEventHandler;
private final WfDefinitionConfigMapper wfDefinitionConfigMapper;
@DubboReference
private RemoteUserService remoteUserService;
@DubboReference
......@@ -224,6 +227,11 @@ public class ActTaskServiceImpl implements IActTaskService {
if (CollUtil.isEmpty(links) && StringUtils.isBlank(t.getAssignee())) {
throw new ServiceException("下一节点【" + t.getName() + "】没有办理人!");
}
ActHiTaskinst actHiTaskinst = actHiTaskinstMapper.selectById(t.getId());
if (!StringUtils.equals(actHiTaskinst.getAssignee(), t.getAssignee())) {
actHiTaskinst.setAssignee(t.getAssignee());
actHiTaskinstMapper.updateById(actHiTaskinst);
}
}
}
......@@ -271,15 +279,17 @@ public class ActTaskServiceImpl implements IActTaskService {
queryWrapper.eq(TenantHelper.isEnable(), "t.tenant_id_", TenantHelper.getTenantId());
String ids = StreamUtils.join(roleIds, x -> "'" + x + "'");
queryWrapper.and(w1 -> w1.eq("t.assignee_", userId).or(w2 -> w2.isNull("t.assignee_").apply("exists ( select LINK.ID_ from ACT_RU_IDENTITYLINK LINK where LINK.TASK_ID_ = t.ID_ and LINK.TYPE_ = 'candidate' and (LINK.USER_ID_ = {0} or ( LINK.GROUP_ID_ IN (" + ids + ") ) ))", userId)));
if (StringUtils.isNotBlank(taskBo.getName())) {
queryWrapper.like("t.name_", taskBo.getName());
}
if (StringUtils.isNotBlank(taskBo.getProcessDefinitionName())) {
queryWrapper.like("t.processDefinitionName", taskBo.getProcessDefinitionName());
if (StringUtils.isNotBlank(taskBo.getTableName())) {
WfDefinitionConfig wdc = wfDefinitionConfigMapper.selectOne(WfDefinitionConfig::getTableName, taskBo.getTableName());
if (wdc == null) {
queryWrapper.eq("t.id_", -1);
} else {
queryWrapper.likeLeft("t.proc_def_id_", wdc.getProcessKey() + ":");
}
if (StringUtils.isNotBlank(taskBo.getProcessDefinitionKey())) {
queryWrapper.eq("t.processDefinitionKey", taskBo.getProcessDefinitionKey());
}
queryWrapper.like(StringUtils.isNotBlank(taskBo.getName()), "t.name_", taskBo.getName());
queryWrapper.like(StringUtils.isNotBlank(taskBo.getProcessDefinitionName()), "t.processDefinitionName", taskBo.getProcessDefinitionName());
queryWrapper.eq(StringUtils.isNotBlank(taskBo.getProcessDefinitionKey()), "t.processDefinitionKey", taskBo.getProcessDefinitionKey());
queryWrapper.orderByDesc("t.CREATE_TIME_");
Page<TaskVo> page = actTaskMapper.getTaskWaitByPage(pageQuery.build(), queryWrapper);
......@@ -288,6 +298,7 @@ public class ActTaskServiceImpl implements IActTaskService {
List<String> processDefinitionIds = StreamUtils.toList(taskList, TaskVo::getProcessDefinitionId);
List<WfNodeConfigVo> wfNodeConfigVoList = wfNodeConfigService.selectByDefIds(processDefinitionIds);
for (TaskVo task : taskList) {
task.setEntity(WorkflowUtils.getHistoricVariableByBusinessKey(task.getBusinessKey(), "entity"));
task.setBusinessStatusName(BusinessStatusEnum.findByStatus(task.getBusinessStatus()));
task.setParticipantVo(WorkflowUtils.getCurrentTaskParticipant(task.getId(), remoteUserService));
task.setMultiInstance(WorkflowUtils.isMultiInstance(task.getProcessDefinitionId(), task.getTaskDefinitionKey()) != null);
......@@ -369,6 +380,14 @@ public class ActTaskServiceImpl implements IActTaskService {
queryWrapper.like(StringUtils.isNotBlank(taskBo.getName()), "t.name_", taskBo.getName());
queryWrapper.like(StringUtils.isNotBlank(taskBo.getProcessDefinitionName()), "t.processDefinitionName", taskBo.getProcessDefinitionName());
queryWrapper.eq(StringUtils.isNotBlank(taskBo.getProcessDefinitionKey()), "t.processDefinitionKey", taskBo.getProcessDefinitionKey());
if (StringUtils.isNotBlank(taskBo.getTableName())) {
WfDefinitionConfig wdc = wfDefinitionConfigMapper.selectOne(WfDefinitionConfig::getTableName, taskBo.getTableName());
if (wdc == null) {
queryWrapper.eq("t.id_", -1);
} else {
queryWrapper.likeLeft("t.proc_def_id_", wdc.getProcessKey() + ":");
}
}
queryWrapper.eq("t.assignee_", userId);
Page<TaskVo> page = actTaskMapper.getTaskFinishByPage(pageQuery.build(), queryWrapper);
......@@ -377,6 +396,7 @@ public class ActTaskServiceImpl implements IActTaskService {
List<String> processDefinitionIds = StreamUtils.toList(taskList, TaskVo::getProcessDefinitionId);
List<WfNodeConfigVo> wfNodeConfigVoList = wfNodeConfigService.selectByDefIds(processDefinitionIds);
for (TaskVo task : taskList) {
task.setEntity(WorkflowUtils.getHistoricVariableByBusinessKey(task.getBusinessKey(), "entity"));
task.setBusinessStatusName(BusinessStatusEnum.findByStatus(task.getBusinessStatus()));
if (CollUtil.isNotEmpty(wfNodeConfigVoList)) {
wfNodeConfigVoList.stream().filter(e -> e.getDefinitionId().equals(task.getProcessDefinitionId()) && FlowConstant.TRUE.equals(e.getApplyUserTask())).findFirst().ifPresent(task::setWfNodeConfigVo);
......@@ -396,14 +416,16 @@ public class ActTaskServiceImpl implements IActTaskService {
public TableDataInfo<TaskVo> getPageByTaskCopy(TaskBo taskBo, PageQuery pageQuery) {
QueryWrapper<TaskVo> queryWrapper = new QueryWrapper<>();
String userId = String.valueOf(LoginHelper.getUserId());
if (StringUtils.isNotBlank(taskBo.getName())) {
queryWrapper.like("t.name_", taskBo.getName());
}
if (StringUtils.isNotBlank(taskBo.getProcessDefinitionName())) {
queryWrapper.like("t.processDefinitionName", taskBo.getProcessDefinitionName());
queryWrapper.like(StringUtils.isNotBlank(taskBo.getName()), "t.name_", taskBo.getName());
queryWrapper.like(StringUtils.isNotBlank(taskBo.getProcessDefinitionName()), "t.processDefinitionName", taskBo.getProcessDefinitionName());
queryWrapper.eq(StringUtils.isNotBlank(taskBo.getProcessDefinitionKey()), "t.processDefinitionKey", taskBo.getProcessDefinitionKey());
if (StringUtils.isNotBlank(taskBo.getTableName())) {
WfDefinitionConfig wdc = wfDefinitionConfigMapper.selectOne(WfDefinitionConfig::getTableName, taskBo.getTableName());
if (wdc == null) {
queryWrapper.eq("t.id_", -1);
} else {
queryWrapper.likeLeft("t.proc_def_id_", wdc.getProcessKey() + ":");
}
if (StringUtils.isNotBlank(taskBo.getProcessDefinitionKey())) {
queryWrapper.eq("t.processDefinitionKey", taskBo.getProcessDefinitionKey());
}
queryWrapper.eq("t.assignee_", userId);
Page<TaskVo> page = actTaskMapper.getTaskCopyByPage(pageQuery.build(), queryWrapper);
......
......@@ -9,7 +9,6 @@ import org.springframework.stereotype.Service;
/**
* @author hzh
* @date 2025-05-20
* @desc TODO
**/
@Service
@RequiredArgsConstructor
......@@ -22,4 +21,9 @@ public class DeptServiceImpl implements IDeptService {
public Long selectLeaderIdByUserId(Long userId) {
return remoteDeptService.selectLeaderIdByUserId(userId);
}
@Override
public Long selectLeaderIdByFileId(Long fileId) {
return remoteDeptService.selectLeaderIdByFileId(fileId);
}
}
package org.dromara.workflow.service.impl;
import cn.hutool.core.io.FileUtil;
import cn.hutool.extra.mail.MailAccount;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.DubboReference;
import org.dromara.common.core.enums.BusinessStatusEnum;
import org.dromara.common.core.utils.MapstructUtils;
import org.dromara.common.core.utils.StreamUtils;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.core.utils.file.FileUtils;
import org.dromara.common.core.utils.file.WatermarkUtil;
import org.dromara.common.core.utils.file.watermark.config.WatermarkConfig;
import org.dromara.common.mail.utils.MailUtils;
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
import org.dromara.common.mybatis.service.AbstractBaseService;
import org.dromara.resource.api.RemoteMailService;
import org.dromara.workflow.api.domain.RemoteWorkflowService;
import org.dromara.workflow.api.domain.event.ProcessEvent;
import org.dromara.workflow.api.domain.event.ProcessTaskEvent;
import org.dromara.workflow.domain.FileApprove;
import org.dromara.workflow.domain.bo.FileApproveBo;
import org.dromara.workflow.domain.vo.FileApproveVo;
import org.dromara.workflow.mapper.FileApproveMapper;
import org.dromara.workflow.service.IFileApproveService;
import org.dromara.workflow.utils.QueryUtils;
import org.dromara.workflow.utils.WorkflowUtils;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.awt.*;
import java.io.File;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
/**
* 部门文件下载申请Service业务层处理
*
* @author hzh
* @date 2025-05-22
*/
@RequiredArgsConstructor
@Service
@Slf4j
public class FileApproveServiceImpl extends AbstractBaseService<FileApproveVo, FileApproveBo, FileApprove> implements IFileApproveService {
private final FileApproveMapper baseMapper;
@DubboReference
private final RemoteWorkflowService workflowService;
@DubboReference
private final RemoteMailService mailService;
@Override
public BaseMapperPlus<FileApprove, FileApproveVo> mapper() {
return baseMapper;
}
@Override
public LambdaQueryWrapper<FileApprove> buildQueryWrapper(FileApproveBo bo) {
Map<String, Object> params = bo.getParams();
LambdaQueryWrapper<FileApprove> lqw = Wrappers.lambdaQuery();
lqw.eq(bo.getSysDeptFileId() != null, FileApprove::getSysDeptFileId, bo.getSysDeptFileId());
lqw.like(StringUtils.isNotBlank(bo.getFileName()), FileApprove::getFileName, bo.getFileName());
lqw.eq(StringUtils.isNotBlank(bo.getPurpose()), FileApprove::getPurpose, bo.getPurpose());
lqw.eq(StringUtils.isNotBlank(bo.getEmail()), FileApprove::getEmail, bo.getEmail());
lqw.eq(StringUtils.isNotBlank(bo.getStatus()), FileApprove::getStatus, bo.getStatus());
lqw.eq(Objects.nonNull(bo.getSysDeptFileId()), FileApprove::getSysDeptFileId, bo.getSysDeptFileId());
lqw.eq(Objects.nonNull(bo.getCreateBy()), FileApprove::getCreateBy, bo.getCreateBy());
return lqw;
}
@Override
public FileApproveVo insert(FileApproveBo bo) {
FileApprove add = MapstructUtils.convert(bo, FileApprove.class);
if (StringUtils.isBlank(add.getStatus())) {
add.setStatus(BusinessStatusEnum.DRAFT.getStatus());
}
boolean flag = baseMapper.insert(add) > 0;
if (flag) {
bo.setId(add.getId());
}
return MapstructUtils.convert(add, FileApproveVo.class);
}
@Override
public FileApproveVo update(FileApproveBo bo) {
FileApprove update = MapstructUtils.convert(bo, FileApprove.class);
baseMapper.updateById(update);
return MapstructUtils.convert(update, FileApproveVo.class);
}
@Transactional(rollbackFor = Exception.class)
@Override
public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
List<String> idList = StreamUtils.toList(ids, String::valueOf);
workflowService.deleteRunAndHisInstance(idList);
return baseMapper.deleteByIds(ids) > 0;
}
/**
* 总体流程监听(例如: 提交 退回 撤销 终止 作废等)
* 正常使用只需#processEvent.key=='leave1'
* 示例为了方便则使用startsWith匹配了全部示例key
*
* @param processEvent 参数
*/
@EventListener(condition = "#processEvent.key.startsWith('dept_file_approve')")
public void processHandler(ProcessEvent processEvent) {
log.info("当前任务执行了{}", processEvent.toString());
FileApprove fa = baseMapper.selectById(Long.valueOf(processEvent.getBusinessKey()));
fa.setStatus(processEvent.getStatus());
if (processEvent.isSubmit()) {
fa.setStatus(BusinessStatusEnum.WAITING.getStatus());
}
baseMapper.updateById(fa);
//审批完成
if (StringUtils.equals(processEvent.getStatus(), BusinessStatusEnum.FINISH.getStatus())) {
CompletableFuture.runAsync(() -> {
File originFile = null;
File watermarkFile = null;
try {
//发送邮件
Map<String, Object> entity = (Map<String, Object>) WorkflowUtils.getHistoricVariableByBusinessKey(processEvent.getBusinessKey(), "entity");
String url = entity.get("fileUrl").toString();
String fileName = entity.get("fileName").toString();
originFile = FileUtils.convert(url, fileName);
watermarkFile = FileUtil.touch(FileUtils.addFolderBeforeFileName(originFile.getPath(), "watermark"));
WatermarkConfig watermarkConfig = new WatermarkConfig.Builder()
.watermarkText(fa.getPurpose())
.fontName(WatermarkUtil.getFontName(originFile.getPath(), fa.getPurpose()))
.color(Color.RED)
.alpha(0.3f)
.angle(45)
.build();
boolean result = WatermarkUtil.addTextWatermark(originFile, watermarkFile, watermarkConfig);
List<MailAccount> accountList = mailService.getMailAccountList();
for (MailAccount account : accountList) {
try {
MailUtils.send(account, fa.getEmail(), "部门文件下载", "", false, result ? watermarkFile : originFile);
break;
} catch (Exception e) {
continue;
}
}
} catch (Exception e) {
e.printStackTrace();
log.info("邮件发送失败,邮箱{}", fa.getEmail());
} finally {
if (originFile != null) {
originFile.delete();
}
if (watermarkFile != null) {
watermarkFile.delete();
}
}
});
}
}
// private String
/**
* 执行办理任务监听
* 示例:也可通过 @EventListener(condition = "#processTaskEvent.key=='leave1'")进行判断
* 在方法中判断流程节点key
* if ("xxx".equals(processTaskEvent.getTaskDefinitionKey())) {
* //执行业务逻辑
* }
*
* @param processTaskEvent 参数
*/
@EventListener(condition = "#processTaskEvent.key.startsWith('dept_file_approve')")
public void processTaskHandler(ProcessTaskEvent processTaskEvent) {
// 所有demo案例的申请人节点id
String[] ids = QueryUtils.getTaskDefinitionKeyListByBusinessKey(processTaskEvent.getBusinessKey()).toArray(new String[0]);
if (StringUtils.equalsAny(processTaskEvent.getTaskDefinitionKey(), ids)) {
log.info("当前任务执行了{}", processTaskEvent);
FileApprove fa = baseMapper.selectById(Long.valueOf(processTaskEvent.getBusinessKey()));
fa.setStatus(BusinessStatusEnum.WAITING.getStatus());
baseMapper.updateById(fa);
}
}
}
......@@ -21,6 +21,7 @@ import org.dromara.workflow.domain.bo.TestLeaveBo;
import org.dromara.workflow.domain.vo.TestLeaveVo;
import org.dromara.workflow.mapper.TestLeaveMapper;
import org.dromara.workflow.service.ITestLeaveService;
import org.dromara.workflow.utils.QueryUtils;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
......@@ -147,10 +148,9 @@ public class TestLeaveServiceImpl implements ITestLeaveService {
@EventListener(condition = "#processTaskEvent.key.startsWith('leave')")
public void processTaskHandler(ProcessTaskEvent processTaskEvent) {
// 所有demo案例的申请人节点id
String[] ids = {"Activity_14633hx", "Activity_19b1i4j", "Activity_0uscrk3",
"Activity_0uscrk3", "Activity_0x6b71j", "Activity_0zy3g6j", "Activity_06a55t0"};
String[] ids = QueryUtils.getTaskDefinitionKeyListByBusinessKey(processTaskEvent.getBusinessKey()).toArray(new String[0]);
if (StringUtils.equalsAny(processTaskEvent.getTaskDefinitionKey(), ids)) {
log.info("当前任务执行了{}", processTaskEvent.toString());
log.info("当前任务执行了{}", processTaskEvent);
TestLeave testLeave = baseMapper.selectById(Long.valueOf(processTaskEvent.getBusinessKey()));
testLeave.setStatus(BusinessStatusEnum.WAITING.getStatus());
baseMapper.updateById(testLeave);
......
package org.dromara.workflow.utils;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollectionUtil;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import org.dromara.common.core.utils.SpringUtils;
import org.dromara.common.core.utils.StreamUtils;
import org.dromara.common.tenant.helper.TenantHelper;
import org.dromara.workflow.domain.vo.TaskVo;
import org.flowable.engine.ProcessEngine;
......@@ -16,6 +18,7 @@ import org.flowable.engine.runtime.ProcessInstance;
import org.flowable.engine.runtime.ProcessInstanceQuery;
import org.flowable.task.api.Task;
import org.flowable.task.api.TaskQuery;
import org.flowable.task.api.history.HistoricTaskInstance;
import org.flowable.task.api.history.HistoricTaskInstanceQuery;
import java.util.Collection;
......@@ -144,6 +147,10 @@ public class QueryUtils {
return taskQuery().processInstanceId(processInstanceId);
}
public static TaskQuery taskQueryBusinessKey(String businessKey) {
return taskQuery().processInstanceBusinessKey(businessKey);
}
public static TaskQuery taskQuery(Collection<String> processInstanceIds) {
return taskQuery().processInstanceIdIn(processInstanceIds);
}
......@@ -166,4 +173,19 @@ public class QueryUtils {
taskVo.setBusinessStatus(businessStatus);
return taskVo;
}
/**
* 根据业务id获取任务定义key集合
*
* @param businessKey 业务id
* @return 任务定义key集合
*/
public static List<String> getTaskDefinitionKeyListByBusinessKey(String businessKey) {
List<HistoricTaskInstance> historicTaskInstanceList = hisTaskBusinessKeyQuery(businessKey).list();
List<Task> taskList = taskQueryBusinessKey(businessKey).list();
List<String> hisTaskDefinitionKeyList = StreamUtils.toList(historicTaskInstanceList, HistoricTaskInstance::getTaskDefinitionKey);
List<String> taskDefinitionKeyList = StreamUtils.toList(taskList, Task::getTaskDefinitionKey);
return CollectionUtil.unionAll(hisTaskDefinitionKeyList, taskDefinitionKeyList);
}
}
......@@ -28,6 +28,7 @@ import org.dromara.workflow.mapper.ActHiTaskinstMapper;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.bpmn.model.FlowNode;
import org.flowable.common.engine.api.delegate.Expression;
import org.flowable.engine.HistoryService;
import org.flowable.engine.ProcessEngine;
import org.flowable.engine.history.HistoricProcessInstance;
import org.flowable.engine.impl.bpmn.behavior.ParallelMultiInstanceBehavior;
......@@ -293,4 +294,24 @@ public class WorkflowUtils {
}
return taskQuery.singleResult();
}
/**
* 查询历史流程变量(返回特定变量)
*
* @param businessKey 业务id
* @param variableName 变量名称
* @return 流程变量
*/
public static Object getHistoricVariableByBusinessKey(String businessKey, String variableName) {
HistoryService historyService = PROCESS_ENGINE.getHistoryService();
HistoricProcessInstance processInstance = historyService.createHistoricProcessInstanceQuery()
.processInstanceBusinessKey(businessKey)
.singleResult();
return historyService.createHistoricVariableInstanceQuery()
.processInstanceId(processInstance.getId())
.variableName(variableName)
.singleResult()
.getValue();
}
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.dromara.workflow.mapper.FileApproveMapper">
</mapper>
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论