🔀 合并 dev 分支到 master
解决 9 个文件的冲突,取舍如下: - 导出模型:采用 dev 的 *ExportExcel 命名与拆分,并保留 master 的 @DateTimeFormat(dev 改名时漏加,会导致时间列显示为 Date.toString)。 - PortTerminal 导入:保留 master 的两阶段导入 + ImportFailureException 全量回滚(ImportFailureException 仅 master 有,合并后的 controller 依赖它), 导出失败明细改用 dev 的 exportFailureReasonOnly(仅标红失败原因列)。 - PortTerminal 导入模板:采用 dev 的"港口编码/码头编码"两列结构, 相应补上 resolveImportCode 归并规则,并在构建实体时显式赋 code/parentCode。 - PortTerminal 导出:采用 dev 的 PortTerminalExportExcel(接口已如此声明), 并保留 updateUserName 审计人翻译。 - 违章记录导入:保留 dev 的多错误收集 + 导入失败明细导出流水线, 删除已被拆列取代的 violationTypeOrItem 映射,补上 clearIrrelevantField, 并为导入校验补齐"对侧字段应留空"规则以与表单校验一致。 验证:mvn compile -DskipTests 全模块 BUILD SUCCESS。
This commit is contained in:
@@ -27,6 +27,7 @@ package org.springblade.file;
|
||||
|
||||
import org.springblade.core.cloud.client.BladeCloudApplication;
|
||||
import org.springblade.core.launch.BladeApplication;
|
||||
import org.springblade.core.launch.constant.AppConstant;
|
||||
import org.springframework.context.annotation.ComponentScan;
|
||||
|
||||
/**
|
||||
@@ -39,7 +40,9 @@ import org.springframework.context.annotation.ComponentScan;
|
||||
public class FileApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
BladeApplication.run("blade-file", FileApplication.class, args);
|
||||
BladeApplication.disableNacosLaunchConfig();
|
||||
BladeApplication.run(AppConstant.APPLICATION_FILE_NAME, FileApplication.class, args);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
+21
-20
@@ -11,6 +11,7 @@ import lombok.AllArgsConstructor;
|
||||
import org.springblade.core.boot.ctrl.BladeController;
|
||||
import org.springblade.core.log.annotation.ApiLog;
|
||||
import org.springblade.core.tenant.annotation.NonDS;
|
||||
import org.springblade.core.tool.api.FR;
|
||||
import org.springblade.core.tool.api.R;
|
||||
import org.springblade.core.tool.utils.StringUtil;
|
||||
import org.springblade.file.listener.FileEvent;
|
||||
@@ -57,29 +58,29 @@ public class FileController extends BladeController {
|
||||
@ApiLog("附件管理-批量上传")
|
||||
@Operation(summary = "OBS批量上传", description = "OBS批量上传,参数名:files")
|
||||
@PostMapping("/ossUpload")
|
||||
public R ossUpload(MultipartFile[] files, @RequestParam(value = "fileName", required = false) String fileName) {
|
||||
public FR ossUpload(MultipartFile[] files, @RequestParam(value = "fileName", required = false) String fileName) {
|
||||
return attachmentService.batchOssUpload(files, fileName);
|
||||
}
|
||||
|
||||
@Operation(summary = "上传", description = "上传,参数名:file")
|
||||
@PostMapping("/upload")
|
||||
public R upload(MultipartFile file) {
|
||||
public FR upload(MultipartFile file) {
|
||||
MultipartFile[] files = {file};
|
||||
R<List<Attachment>> result = attachmentService.batchOssUpload(files, null);
|
||||
List<Attachment> data = result.getData();
|
||||
if(data != null && !data.isEmpty())return R.data(data.get(0));
|
||||
return R.fail("上传失败");
|
||||
if(data != null && !data.isEmpty())return FR.data(data.get(0));
|
||||
return FR.fail("上传失败");
|
||||
}
|
||||
|
||||
@Operation(summary = "获取附件,多个附件id用逗号分割", description = "获取附件,多个附件id用逗号分割")
|
||||
@GetMapping("/getAttachment")
|
||||
public R getAttachment(@RequestParam(value = "id") String id) {
|
||||
return R.data(attachmentService.getAttachment(id));
|
||||
public FR getAttachment(@RequestParam(value = "id") String id) {
|
||||
return FR.data(attachmentService.getAttachment(id));
|
||||
}
|
||||
|
||||
@Operation(summary = "获取文件url", description = "获取文件url,参数:objectKey")
|
||||
@GetMapping("/getFileUrl")
|
||||
public R getFileUrl(@RequestParam(value = "objectKey") String objectKey, @RequestParam(value = "attachmentName", required = false) String attachmentName) {
|
||||
public FR getFileUrl(@RequestParam(value = "objectKey") String objectKey, @RequestParam(value = "attachmentName", required = false) String attachmentName) {
|
||||
if (StringUtil.isBlank(attachmentName)) {
|
||||
// 附件名为空,查询附件名
|
||||
Attachment attachment = attachmentService.getOne(Wrappers.<Attachment>lambdaQuery()
|
||||
@@ -94,7 +95,7 @@ public class FileController extends BladeController {
|
||||
attachmentName = attachmentName.replaceAll(",", "_");
|
||||
}
|
||||
}
|
||||
return R.data(fileService.getFileUrl(objectKey, attachmentName, null));
|
||||
return FR.data(fileService.getFileUrl(objectKey, attachmentName, null));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -104,9 +105,9 @@ public class FileController extends BladeController {
|
||||
*/
|
||||
@Operation(summary = "获取wps文件预览url", description = "获取wps文件预览url")
|
||||
@GetMapping("/getWpsFilePreviewUrl")
|
||||
public R getWpsFilePreviewUrl(@NotNull(message = "附件id不能为空") @RequestParam(value = "attachmentId", required = false) String attachmentId,
|
||||
public FR getWpsFilePreviewUrl(@NotNull(message = "附件id不能为空") @RequestParam(value = "attachmentId", required = false) String attachmentId,
|
||||
@NotBlank(message = "附件名称不能为空") @RequestParam(value = "attachmentName", required = false) String attachmentName) {
|
||||
return R.data(wpsService.getFilePreviewUrl(attachmentId, attachmentName));
|
||||
return FR.data(wpsService.getFilePreviewUrl(attachmentId, attachmentName));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -117,41 +118,41 @@ public class FileController extends BladeController {
|
||||
*/
|
||||
@Operation(summary = "获取wps文件编辑url", description = "获取wps文件编辑url")
|
||||
@GetMapping("/getWpsFileEditUrl")
|
||||
public R getWpsFileEditUrl(@NotNull(message = "附件id不能为空") @RequestParam(value = "attachmentId", required = false) String attachmentId,
|
||||
public FR getWpsFileEditUrl(@NotNull(message = "附件id不能为空") @RequestParam(value = "attachmentId", required = false) String attachmentId,
|
||||
@NotBlank(message = "附件名称不能为空") @RequestParam(value = "attachmentName", required = false) String attachmentName) {
|
||||
return R.data(wpsService.getWpsFileEditUrl(attachmentId, attachmentName));
|
||||
return FR.data(wpsService.getWpsFileEditUrl(attachmentId, attachmentName));
|
||||
}
|
||||
|
||||
@Operation(summary = "批量获取文件url", description = "批量获取文件url,参数:objectKey数组")
|
||||
@PostMapping("/getFileUrls")
|
||||
public R getFileUrls(@RequestBody List<String> objectKeys) {
|
||||
return R.data(fileService.getFileUrls(objectKeys, null));
|
||||
public FR getFileUrls(@RequestBody List<String> objectKeys) {
|
||||
return FR.data(fileService.getFileUrls(objectKeys, null));
|
||||
}
|
||||
|
||||
@ApiLog("OCR识别-识别身份证")
|
||||
@Operation(summary = "识别身份证信息支持正反面", description = "参数:url")
|
||||
@GetMapping("/recognitionIDCard")
|
||||
public R recognitionIDCard(@RequestParam(value = "url", required = false) String url,
|
||||
@RequestParam(value = "objectKey", required = false) String objectKey) {
|
||||
public FR recognitionIDCard(@RequestParam(value = "url", required = false) String url,
|
||||
@RequestParam(value = "objectKey", required = false) String objectKey) {
|
||||
String imageUrl = StringUtil.isNotBlank(url) ? url : objectKey;
|
||||
if (StringUtil.isBlank(imageUrl)) {
|
||||
return R.fail("图片地址不能为空");
|
||||
return FR.fail("图片地址不能为空");
|
||||
}
|
||||
if (!imageUrl.startsWith("http://") && !imageUrl.startsWith("https://")) {
|
||||
imageUrl = fileService.getFileUrl(imageUrl, null);
|
||||
}
|
||||
return R.data(ocrService.recognitionIDCard(List.of(imageUrl)));
|
||||
return FR.data(ocrService.recognitionIDCard(List.of(imageUrl)));
|
||||
}
|
||||
|
||||
@SentinelResource("ocr:batchCards")
|
||||
@ApiLog("OCR识别-识别车辆运输凭证(不知道类型)")
|
||||
@Operation(summary = "识别车辆运输凭证,不知道类型", description = "参数:objectKeylist")
|
||||
@PostMapping("/recognitionTransportCertificates")
|
||||
public R<TransportCertificateVO> recognitionTransportCertificates(
|
||||
public FR<TransportCertificateVO> recognitionTransportCertificates(
|
||||
@RequestBody List<Object> attachments,
|
||||
@RequestParam(value = "projectAbbreviation", required = false) String projectAbbreviation,
|
||||
@RequestParam(value = "code", required = false) String code) {
|
||||
return R.data(ocrConvertService.recognitionTransportCertificate(
|
||||
return FR.data(ocrConvertService.recognitionTransportCertificate(
|
||||
buildCertificateBatchRecognitionDTO(attachments, projectAbbreviation, code)
|
||||
));
|
||||
}
|
||||
|
||||
+2
-1
@@ -26,6 +26,7 @@
|
||||
package org.springblade.file.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import org.springblade.core.tool.api.FR;
|
||||
import org.springblade.core.tool.api.R;
|
||||
import org.springblade.file.pojo.entity.Attachment;
|
||||
import org.springblade.file.pojo.vo.AttachmentDetailVO;
|
||||
@@ -49,7 +50,7 @@ public interface IAttachmentService extends IService<Attachment> {
|
||||
* @param fileName 文件名,如果 files只有1个,且fileName不为空,设置文件名为 fileName
|
||||
* @return
|
||||
*/
|
||||
R<List<Attachment>> batchOssUpload(MultipartFile[] files, String fileName);
|
||||
FR<List<Attachment>> batchOssUpload(MultipartFile[] files, String fileName);
|
||||
|
||||
/**
|
||||
* 获取附件,多个附件id用逗号分割
|
||||
|
||||
+4
-3
@@ -32,6 +32,7 @@ import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.springblade.core.log.exception.ServiceException;
|
||||
import org.springblade.core.tool.api.FR;
|
||||
import org.springblade.core.tool.api.R;
|
||||
import org.springblade.core.tool.utils.CollectionUtil;
|
||||
import org.springblade.core.tool.utils.SpringUtil;
|
||||
@@ -78,7 +79,7 @@ public class AttachmentServiceImpl extends ServiceImpl<AttachmentMapper, Attachm
|
||||
private IAttachmentService self;
|
||||
|
||||
@Override
|
||||
public R<List<Attachment>> batchOssUpload(MultipartFile[] files, String overWriteFileName){
|
||||
public FR<List<Attachment>> batchOssUpload(MultipartFile[] files, String overWriteFileName){
|
||||
try {
|
||||
List<Attachment> attachmentList = new ArrayList<>();
|
||||
// 文件数量为1个,且重写的文件名不为空,使用重写的文件名,给uniapp上传使用,uniapp上传的文件名不是原始文件名
|
||||
@@ -98,11 +99,11 @@ public class AttachmentServiceImpl extends ServiceImpl<AttachmentMapper, Attachm
|
||||
}
|
||||
this.saveBatch(attachmentList);
|
||||
log.info("OBS上传 successfully");
|
||||
return R.data(attachmentList,"OBS上传 successfully");
|
||||
return FR.data(attachmentList,"OBS上传 successfully");
|
||||
} catch (Exception e) {
|
||||
log.error("OBS上传 failed,Exception", e);
|
||||
}
|
||||
return R.fail("OBS上传 failed");
|
||||
return FR.fail("OBS上传 failed");
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+5
-1
@@ -57,6 +57,7 @@ import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
@@ -127,6 +128,8 @@ public class FileTaskServiceImpl extends ServiceImpl<FileTaskMapper, FileTask> i
|
||||
addParam.setUploadId(uploadId);
|
||||
// 正在上传
|
||||
addParam.setStatus(FileTaskStatus.UPLOADING.getCode());
|
||||
// 文件任务创建即代表上传开始,显式记录时间,避免依赖自动填充导致上传时间为空。
|
||||
addParam.setCreateTime(new Date());
|
||||
// 兼容历史表未配置 is_deleted 默认值的场景。
|
||||
addParam.setIsDeleted(0);
|
||||
this.save(addParam);
|
||||
@@ -344,7 +347,8 @@ public class FileTaskServiceImpl extends ServiceImpl<FileTaskMapper, FileTask> i
|
||||
.eq(StringUtil.isNotBlank(businessId), FileTask::getBusinessId, businessId)
|
||||
.like(StringUtil.isNotBlank(attachmentName), FileTask::getAttachmentName, attachmentName)
|
||||
.eq(StringUtil.isNotBlank(status), FileTask::getStatus, status)
|
||||
.orderByDesc(FileTask::getCreateTime));
|
||||
.orderByDesc(FileTask::getCreateTime)
|
||||
.orderByDesc(FileTask::getId));
|
||||
return page.convert(this::getFileTaskUpdateVO);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
server:
|
||||
port: 8107
|
||||
|
||||
spring:
|
||||
application:
|
||||
name: blade-file
|
||||
config:
|
||||
import:
|
||||
- nacos:blade.yaml?group=DEFAULT_GROUP&refreshEnabled=true
|
||||
- nacos:blade-${spring.profiles.active}.yaml?group=DEFAULT_GROUP&refreshEnabled=true
|
||||
- optional:nacos:${spring.application.name}-${spring.profiles.active}.yaml?group=DEFAULT_GROUP&refreshEnabled=true
|
||||
cloud:
|
||||
nacos:
|
||||
username: ${NACOS_USERNAME:${NACOS_PROD_USERNAME:nacos}}
|
||||
password: ${NACOS_PASSWORD:${NACOS_PROD_PASSWORD:nacos}}
|
||||
server-addr: ${NACOS_HOST:${NACOS_PROD_HOST:127.0.0.1:8848}}
|
||||
discovery:
|
||||
namespace: "${NACOS_NAMESPACE:}"
|
||||
config:
|
||||
file-extension: yaml
|
||||
namespace: "${NACOS_NAMESPACE:}"
|
||||
datasource:
|
||||
url: ${blade.datasource.${spring.profiles.active}.url}
|
||||
username: ${blade.datasource.${spring.profiles.active}.username}
|
||||
password: ${blade.datasource.${spring.profiles.active}.password}
|
||||
@@ -1,6 +0,0 @@
|
||||
#spring:
|
||||
# cloud:
|
||||
# nacos:
|
||||
# username: nacos
|
||||
# password: ${NACOS_PASSWORD:gr30wIs5%Hi7keQj}
|
||||
# server-addr: ${NACOS_ADDR:10.38.16.127:8848}
|
||||
@@ -1,6 +0,0 @@
|
||||
#spring:
|
||||
# cloud:
|
||||
# nacos:
|
||||
# username: nacos
|
||||
# password: rWrMrVTWyf%ekjuw
|
||||
# server-addr: ${NACOS_ADDR:192.168.0.242:8848}
|
||||
@@ -1,8 +0,0 @@
|
||||
#server:
|
||||
# port: 38107
|
||||
#spring:
|
||||
# cloud:
|
||||
# nacos:
|
||||
# username: nacos
|
||||
# password: gr30wIs5%Hi7keQj
|
||||
# server-addr: ${NACOS_ADDR:10.38.16.127:8848}
|
||||
@@ -26,6 +26,10 @@
|
||||
<groupId>org.springblade</groupId>
|
||||
<artifactId>blade-starter-swagger</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springblade</groupId>
|
||||
<artifactId>blade-starter-threadpool</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springblade</groupId>
|
||||
<artifactId>blade-open-api</artifactId>
|
||||
@@ -38,6 +42,20 @@
|
||||
<groupId>org.springblade</groupId>
|
||||
<artifactId>blade-mk-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springblade</groupId>
|
||||
<artifactId>blade-process-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springblade</groupId>
|
||||
<artifactId>blade-core-launch</artifactId>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<artifactId>spring-cloud-starter-bootstrap</artifactId>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.mapstruct</groupId>
|
||||
|
||||
+11
-4
@@ -25,24 +25,31 @@
|
||||
*/
|
||||
package org.springblade.openapi;
|
||||
|
||||
import org.springblade.common.utils.ObsUtil;
|
||||
|
||||
import org.dromara.dynamictp.core.spring.EnableDynamicTp;
|
||||
import org.springblade.core.cloud.client.BladeCloudApplication;
|
||||
import org.springblade.core.launch.BladeApplication;
|
||||
import org.springblade.core.launch.constant.AppConstant;
|
||||
import org.springframework.context.annotation.ComponentScan;
|
||||
import org.springframework.context.annotation.Import;
|
||||
|
||||
/**
|
||||
* Desk启动器
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@EnableDynamicTp
|
||||
@BladeCloudApplication
|
||||
@ComponentScan({"org.springblade.openapi", "org.springblade.**.feign"})
|
||||
@Import(ObsUtil.class)
|
||||
public class OpenApiApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
BladeApplication.run("blade-openapi", OpenApiApplication.class, args);
|
||||
BladeApplication.disableNacosLaunchConfig();
|
||||
// 当前处理人刷新依赖 RedisLockClient;Nacos 全局 blade.lock.enabled=false 时仍需为本服务开启
|
||||
if (System.getProperty("blade.lock.enabled") == null) {
|
||||
System.setProperty("blade.lock.enabled", "true");
|
||||
}
|
||||
BladeApplication.run(AppConstant.APPLICATION_OPENAPI_NAME, OpenApiApplication.class, args);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
package org.springblade.openapi.mk;
|
||||
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import io.swagger.v3.oas.annotations.Hidden;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springblade.core.log.exception.ServiceException;
|
||||
import org.springblade.core.secure.annotation.PreAuth;
|
||||
import org.springblade.core.secure.constant.AuthConstant;
|
||||
import org.springblade.core.tool.api.FR;
|
||||
import org.springblade.openapi.mk.api.IApi4MK;
|
||||
import org.springblade.openapi.mk.pojo.dto.Api4MKProcessApprovalDTO;
|
||||
import org.springblade.openapi.mk.pojo.enums.ProcessOperationType;
|
||||
import org.springblade.openapi.mk.support.base.ProcessHandler;
|
||||
import org.springblade.openapi.mk.util.ProcessTypeUtils;
|
||||
import org.springblade.thirdparty.mk.config.MKProperties;
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 提供给mk的api实现类
|
||||
* @author bfhuange
|
||||
* @date 2024/9/9
|
||||
*/
|
||||
@Slf4j
|
||||
@Hidden
|
||||
@RestController
|
||||
public class Api4MK implements IApi4MK {
|
||||
private final MKProperties mkProperties;
|
||||
private final Map<String, ProcessHandler> handlerMap;
|
||||
|
||||
public Api4MK(MKProperties mkProperties, ObjectProvider<List<ProcessHandler>> handlersProvider) {
|
||||
this.mkProperties = mkProperties;
|
||||
handlerMap = handlersProvider.getIfAvailable(Collections::emptyList).stream()
|
||||
.flatMap(handler -> handler.getProcessTypes().stream()
|
||||
.collect(Collectors.toMap(Function.identity(), type -> handler, (a, b) -> {
|
||||
throw new ServiceException("重复的流程类型处理器");
|
||||
}))
|
||||
.entrySet()
|
||||
.stream())
|
||||
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (a, b) -> {
|
||||
throw new ServiceException("重复的流程类型处理器");
|
||||
}));
|
||||
}
|
||||
|
||||
@Override
|
||||
@PreAuth(AuthConstant.PERMIT_ALL)
|
||||
public FR<Boolean> processCommonCallback(Api4MKProcessApprovalDTO param) {
|
||||
log.info("mk流程通用回调 操作名称:{} 参数:{}", ProcessOperationType.getOperationName(param.getOperation()), JSON.toJSONString(param));
|
||||
callback(param, ProcessHandler::approve);
|
||||
return FR.status(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FR<Boolean> processFinishCallback(Api4MKProcessApprovalDTO param) {
|
||||
log.info("mk流程结束回调 参数:{}", JSON.toJSONString(param));
|
||||
// 手动设置操作类型,兼容历史接口
|
||||
param.setOperation(ProcessOperationType.PROCESS_FINISH);
|
||||
callback(param, ProcessHandler::approve);
|
||||
return FR.status(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取处理器
|
||||
* @param processType
|
||||
* @return
|
||||
*/
|
||||
private ProcessHandler getHandler(String processType) {
|
||||
if (StringUtils.isBlank(processType)) {
|
||||
return null;
|
||||
}
|
||||
return handlerMap.get(processType);
|
||||
}
|
||||
|
||||
/**
|
||||
* 回调处理
|
||||
* @param param
|
||||
* @param consumer
|
||||
*/
|
||||
private void callback(Api4MKProcessApprovalDTO param, BiConsumer<ProcessHandler, Api4MKProcessApprovalDTO> consumer) {
|
||||
String processType = ProcessTypeUtils.getProcessType(param.getTemplateCode(), mkProperties.getTemplateCodePrefix());
|
||||
ProcessHandler handler = getHandler(processType);
|
||||
if (handler != null) {
|
||||
consumer.accept(handler, param);
|
||||
return;
|
||||
}
|
||||
log.warn("未配置流程类型对应的处理器 流程类型:{}", processType);
|
||||
}
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
package org.springblade.openapi.mk.config;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* 当前处理人刷新相关异步线程池配置。
|
||||
*
|
||||
* @author bfhuange
|
||||
* @since 2026/4/9
|
||||
*/
|
||||
@Data
|
||||
@Component
|
||||
@ConfigurationProperties(prefix = "async")
|
||||
public class AsyncExecutorProperties {
|
||||
|
||||
/**
|
||||
* 当前处理人刷新工作线程池名称
|
||||
*/
|
||||
private String workerExecutorName = "mkRefreshWorkerExecutor";
|
||||
|
||||
/**
|
||||
* 当前处理人刷新调度线程池名称
|
||||
*/
|
||||
private String schedulerExecutorName = "mkRefreshSchedulerExecutor";
|
||||
}
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
package org.springblade.openapi.mk.config;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* @author bfhuange
|
||||
* @since 2026/4/9
|
||||
*/
|
||||
@Component
|
||||
@ConfigurationProperties(prefix = "process.current-handler-refresh")
|
||||
@Data
|
||||
public class CurrentHandlerRefreshProperties {
|
||||
/**
|
||||
* 服务启动后的首次派工延迟,单位毫秒
|
||||
*/
|
||||
private long startupDispatchDelayMillis = 3000L;
|
||||
|
||||
/**
|
||||
* 首次执行延迟,单位秒
|
||||
*/
|
||||
private long initialDelaySeconds = 1;
|
||||
/**
|
||||
* 轮询间隔,单位秒
|
||||
*/
|
||||
private long intervalSeconds = 1;
|
||||
/**
|
||||
* 最大重试次数
|
||||
*/
|
||||
private int maxAttempts = 30;
|
||||
/**
|
||||
* 最大worker数
|
||||
*/
|
||||
private int maxWorkers = 5;
|
||||
/**
|
||||
* worker租约秒数
|
||||
*/
|
||||
private long workerLeaseSeconds =15;
|
||||
/**
|
||||
* 锁等待秒数
|
||||
*/
|
||||
private long lockWaitSeconds = 1;
|
||||
/**
|
||||
* 完成任务TTL
|
||||
*/
|
||||
private long doneTtlMinutes = 5L;
|
||||
/**
|
||||
* 失败任务TTL
|
||||
*/
|
||||
private long failedTtlMinutes = 30L;
|
||||
}
|
||||
+301
@@ -0,0 +1,301 @@
|
||||
package org.springblade.openapi.mk.support.base;
|
||||
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springblade.core.log.exception.ServiceException;
|
||||
import org.springblade.core.tool.api.FR;
|
||||
import org.springblade.core.tool.utils.StringUtil;
|
||||
import org.springblade.openapi.mk.pojo.dto.Api4MKProcessApprovalDTO;
|
||||
import org.springblade.openapi.mk.pojo.enums.ProcessCallbackType;
|
||||
import org.springblade.openapi.mk.support.handler.ProcessCurrentHandlerRefreshService;
|
||||
import org.springblade.openapi.mk.util.ProcessTypeUtils;
|
||||
import org.springblade.process.feign.IBusinessProcessClient;
|
||||
import org.springblade.process.pojo.dto.BusinessProcessUpdateDTO;
|
||||
import org.springblade.process.pojo.enums.ApproveStatusEnum;
|
||||
import org.springblade.process.pojo.vo.BusinessProcessVO;
|
||||
import org.springblade.thirdparty.mk.config.MKProperties;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* 抽象流程操作处理器,实现公共逻辑
|
||||
* @author bfhuange
|
||||
* @date 2024/9/9
|
||||
*/
|
||||
@Slf4j
|
||||
public abstract class AbstractProcessOperationHandler implements ProcessHandler, ProcessOperationHandler {
|
||||
|
||||
@Autowired
|
||||
protected IBusinessProcessClient processClient;
|
||||
|
||||
@Autowired
|
||||
protected ProcessCurrentHandlerRefreshService refreshService;
|
||||
|
||||
@Autowired
|
||||
protected MKProperties mkProperties;
|
||||
|
||||
@Override
|
||||
public List<String> getProcessTypes() {
|
||||
return List.of(this.getProcessType());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void approve(Api4MKProcessApprovalDTO param) {
|
||||
// 入口层只接收 MK 原始回调参数,随后统一组装为内部上下文对象,
|
||||
// 把流程类型、审批状态、是否完成、是否异步等内部处理语义集中收口在这里。
|
||||
ProcessCallbackType callbackType = ProcessCallbackType.getCallbackType(param.getOperation());
|
||||
if (callbackType == null) {
|
||||
log.error("未配置事件的操作:{}", param.getOperation());
|
||||
return;
|
||||
}
|
||||
switch (callbackType) {
|
||||
// 提交
|
||||
case SUBMIT -> submit(buildSubmitContext(param));
|
||||
// 审批结束
|
||||
case FINISH -> approveFinish(buildFinishContext(param));
|
||||
// 撤回
|
||||
case RETRACT -> approveRevoke(buildRevokeContext(param));
|
||||
// 通过
|
||||
case PASS -> approvePass(buildPassContext(param));
|
||||
// 驳回
|
||||
case REJECT -> approveReject(buildRejectContext(param));
|
||||
// 废弃
|
||||
case ABANDON -> approveAbandon(buildAbandonContext(param));
|
||||
// 修改当前处理人
|
||||
case CHANGE_CUR_HANDLER -> handleCommon(buildChangeCurrentHandlerContext(param));
|
||||
default -> log.error("未配置事件的操作:{}", param.getOperation());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void approveFinish(ProcessOperationContext param) {
|
||||
approveCommon(param, this::approveFinishBusiness);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void approvePass(ProcessOperationContext param) {
|
||||
approveCommon(param, this::approvePassBusiness);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void approveReject(ProcessOperationContext param) {
|
||||
approveCommon(param, this::approveRejectBusiness);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void approveRevoke(ProcessOperationContext param) {
|
||||
approveCommon(param, this::approveRevokeBusiness);
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理提交
|
||||
* @param param
|
||||
*/
|
||||
@Override
|
||||
public void submit(ProcessOperationContext param) {
|
||||
// 一般提交后只需要更新当前处理人
|
||||
handleCommon(param);
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理废弃
|
||||
* @param param
|
||||
*/
|
||||
@Override
|
||||
public void approveAbandon(ProcessOperationContext param) {
|
||||
approveCommon(param, this::approveAbandonBusiness);
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理审批通用逻辑
|
||||
* @param param
|
||||
* @param businessHandler
|
||||
*/
|
||||
protected void approveCommon(ProcessOperationContext param, Consumer<ProcessOperationContext> businessHandler) {
|
||||
// 1. 更新流程状态
|
||||
updateBusinessProcessStatus(param);
|
||||
// 2. 同步处理业务逻辑
|
||||
businessHandler.accept(param);
|
||||
// 3. 处理当前处理人刷新
|
||||
handleCommon(param);
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理公共异步逻辑
|
||||
* @param param
|
||||
*/
|
||||
protected void handleCommon(ProcessOperationContext param) {
|
||||
// 当前处理人支持按事件选择同步刷新或任务调度刷新
|
||||
if (param.isAsync()) {
|
||||
refreshService.enqueue(param);
|
||||
} else {
|
||||
refreshService.refreshNow(param);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新流程状态
|
||||
* @param param
|
||||
*/
|
||||
private void updateBusinessProcessStatus(ProcessOperationContext param) {
|
||||
// 更新流程状态
|
||||
BusinessProcessUpdateDTO updateStatusParam = getBusinessProcessUpdateParam(param);
|
||||
FR<String> statusResult = processClient.updateBusinessProcessStatus(updateStatusParam);
|
||||
if (FR.isNotSuccess(statusResult)) {
|
||||
log.error("更新流程状态异常 :{}", JSON.toJSONString(statusResult));
|
||||
String errorMessage = Optional.ofNullable(statusResult)
|
||||
.map(FR::getMsg)
|
||||
.orElse("");
|
||||
throw new ServiceException("更新流程状态异常:" + errorMessage);
|
||||
}
|
||||
// 具体审批状态要以更新 BusinessProcess 返回的为准,有些比如驳回到上一个审批节点(非起草节点)的,不需要更新状态
|
||||
String approveStatus = statusResult.getData();
|
||||
if (StringUtil.isBlank(approveStatus)) {
|
||||
param.setApproveStatus(null);
|
||||
} else {
|
||||
param.setApproveStatus(approveStatus);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取流程更新参数
|
||||
* @param param
|
||||
* @return
|
||||
*/
|
||||
private BusinessProcessUpdateDTO getBusinessProcessUpdateParam(ProcessOperationContext param) {
|
||||
BusinessProcessUpdateDTO updateParam = new BusinessProcessUpdateDTO();
|
||||
updateParam.setProcessInstanceId(param.getProcessInstanceId());
|
||||
updateParam.setPromoterLoginName(param.getApplicantLoginName());
|
||||
updateParam.setOperationNodeId(param.getCurrentNodeId());
|
||||
updateParam.setOperationNodeNumber(param.getCurrentNodeNumber());
|
||||
updateParam.setComplete(param.isComplete());
|
||||
updateParam.setApproveStatus(param.getApproveStatus());
|
||||
updateParam.setRejectNodeId(param.getRejectNodeId());
|
||||
return updateParam;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造提交流程上下文。
|
||||
*/
|
||||
protected ProcessOperationContext buildSubmitContext(Api4MKProcessApprovalDTO callbackParam) {
|
||||
return buildContext(callbackParam, ApproveStatusEnum.APPROVING.getValue(), false, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造审批通过上下文。
|
||||
*/
|
||||
protected ProcessOperationContext buildPassContext(Api4MKProcessApprovalDTO callbackParam) {
|
||||
return buildContext(callbackParam, ApproveStatusEnum.APPROVING.getValue(), false, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造流程结束上下文。
|
||||
*/
|
||||
protected ProcessOperationContext buildFinishContext(Api4MKProcessApprovalDTO callbackParam) {
|
||||
return buildContext(callbackParam, ApproveStatusEnum.APPROVED.getValue(), true, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造驳回上下文。
|
||||
*/
|
||||
protected ProcessOperationContext buildRejectContext(Api4MKProcessApprovalDTO callbackParam) {
|
||||
return buildContext(callbackParam, ApproveStatusEnum.REJECTED.getValue(), false, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造撤回上下文。
|
||||
*/
|
||||
protected ProcessOperationContext buildRevokeContext(Api4MKProcessApprovalDTO callbackParam) {
|
||||
return buildContext(callbackParam, ApproveStatusEnum.REVOCATION.getValue(), false, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造废弃上下文。
|
||||
*/
|
||||
protected ProcessOperationContext buildAbandonContext(Api4MKProcessApprovalDTO callbackParam) {
|
||||
return buildContext(callbackParam, ApproveStatusEnum.ABANDON.getValue(), false, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造仅刷新当前处理人的上下文。
|
||||
*/
|
||||
protected ProcessOperationContext buildChangeCurrentHandlerContext(Api4MKProcessApprovalDTO callbackParam) {
|
||||
return buildContext(callbackParam, null, false, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造流程内部处理上下文。
|
||||
* 这里统一固化 processType,避免后续业务处理和异步刷新阶段再次根据模板编码反推。
|
||||
*/
|
||||
protected ProcessOperationContext buildContext(Api4MKProcessApprovalDTO callbackParam,
|
||||
String approveStatus,
|
||||
boolean complete,
|
||||
boolean async) {
|
||||
return ProcessOperationContext.builder()
|
||||
.callbackParam(callbackParam)
|
||||
.processType(ProcessTypeUtils.getProcessType(callbackParam.getTemplateCode(), mkProperties.getTemplateCodePrefix()))
|
||||
.approveStatus(approveStatus)
|
||||
.complete(complete)
|
||||
.async(async)
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取流程类型
|
||||
* @return
|
||||
*/
|
||||
protected String getProcessType() {
|
||||
throw new ServiceException("未配置流程类型");
|
||||
};
|
||||
|
||||
/**
|
||||
* 当前处理人刷新成功后回调各业务模块
|
||||
* @param param 回调参数
|
||||
* @param businessProcessVO 最新流程快照
|
||||
*/
|
||||
public void handleCurrentHandlerRefresh(ProcessOperationContext param, BusinessProcessVO businessProcessVO) {
|
||||
if (businessProcessVO != null) {
|
||||
this.commonBusiness(param, businessProcessVO);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理公共业务逻辑
|
||||
* @param param
|
||||
* @param businessProcessVO
|
||||
*/
|
||||
protected abstract void commonBusiness(ProcessOperationContext param, BusinessProcessVO businessProcessVO);
|
||||
|
||||
/**
|
||||
* 处理审批通过同步逻辑
|
||||
* @param param
|
||||
*/
|
||||
protected abstract void approvePassBusiness(ProcessOperationContext param);
|
||||
|
||||
/**
|
||||
* 处理流程结束同步逻辑
|
||||
* @param param
|
||||
*/
|
||||
protected abstract void approveFinishBusiness(ProcessOperationContext param);
|
||||
|
||||
/**
|
||||
* 处理审批驳回同步逻辑
|
||||
* @param param
|
||||
*/
|
||||
protected abstract void approveRejectBusiness(ProcessOperationContext param);
|
||||
|
||||
/**
|
||||
* 处理审批撤回同步逻辑
|
||||
* @param param
|
||||
*/
|
||||
protected abstract void approveRevokeBusiness(ProcessOperationContext param);
|
||||
|
||||
/**
|
||||
* 处理审批废弃同步逻辑 todo 为了避免代码报错,先用空实现
|
||||
* @param param
|
||||
*/
|
||||
protected void approveAbandonBusiness(ProcessOperationContext param) {};
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
package org.springblade.openapi.mk.support.base;
|
||||
|
||||
|
||||
import org.springblade.openapi.mk.pojo.dto.Api4MKProcessApprovalDTO;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 流程处理器
|
||||
* @author bfhuange
|
||||
* @date 2024/9/9
|
||||
*/
|
||||
public interface ProcessHandler {
|
||||
|
||||
/**
|
||||
* 获取流程类型列表
|
||||
* @return
|
||||
*/
|
||||
List<String> getProcessTypes();
|
||||
|
||||
/**
|
||||
* 通用审批
|
||||
* @param param
|
||||
*/
|
||||
void approve(Api4MKProcessApprovalDTO param);
|
||||
|
||||
}
|
||||
+139
@@ -0,0 +1,139 @@
|
||||
package org.springblade.openapi.mk.support.base;
|
||||
|
||||
import com.alibaba.fastjson2.annotation.JSONField;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import org.springblade.openapi.mk.pojo.dto.Api4MKProcessApprovalDTO;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 流程回调内部处理上下文。
|
||||
* <p>
|
||||
* callbackParam 仅保留 MK 原始回调参数,
|
||||
* 其余字段为 openapi 在处理过程中补充的上下文参数。
|
||||
* </p>
|
||||
* <p>
|
||||
* 设计目的:
|
||||
* 1. 避免把内部推导字段继续堆到 MK 原始回调 DTO 上;
|
||||
* 2. 对外保留原始回调对象,便于排查问题、记录日志和后续扩展;
|
||||
* 3. 通过代理 getter 尽量兼容原来直接读取 DTO 字段的使用习惯,降低老流程和后续分支合并成本。
|
||||
* </p>
|
||||
*
|
||||
* @author bfhuange
|
||||
* @date 2026/4/9
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
public class ProcessOperationContext implements Serializable {
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* MK 原始回调参数
|
||||
*/
|
||||
private Api4MKProcessApprovalDTO callbackParam;
|
||||
|
||||
/**
|
||||
* 流程类型
|
||||
*/
|
||||
private String processType;
|
||||
|
||||
/**
|
||||
* 业务审批状态。
|
||||
* 这是系统内部按事件语义统一补充的状态,不属于 MK 原始回调参数。
|
||||
*/
|
||||
private String approveStatus;
|
||||
|
||||
/**
|
||||
* 是否流程已完成。
|
||||
* 这是系统内部按事件语义统一补充的状态,不属于 MK 原始回调参数。
|
||||
*/
|
||||
private boolean complete;
|
||||
|
||||
/**
|
||||
* 是否异步刷新当前处理人。
|
||||
* 用于控制当前处理人更新是走同步刷新还是异步调度任务。
|
||||
*/
|
||||
private boolean async;
|
||||
|
||||
@JsonIgnore
|
||||
@JSONField(serialize = false)
|
||||
public String getProcessInstanceId() {
|
||||
return callbackParam == null ? null : callbackParam.getProcessInstanceId();
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
@JSONField(serialize = false)
|
||||
public String getFormInstanceId() {
|
||||
return callbackParam == null ? null : callbackParam.getFormInstanceId();
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
@JSONField(serialize = false)
|
||||
public String getTemplateId() {
|
||||
return callbackParam == null ? null : callbackParam.getTemplateId();
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
@JSONField(serialize = false)
|
||||
public String getTemplateCode() {
|
||||
return callbackParam == null ? null : callbackParam.getTemplateCode();
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
@JSONField(serialize = false)
|
||||
public String getProcessStatus() {
|
||||
return callbackParam == null ? null : callbackParam.getProcessStatus();
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
@JSONField(serialize = false)
|
||||
public String getApplicantLoginName() {
|
||||
return callbackParam == null ? null : callbackParam.getApplicantLoginName();
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
@JSONField(serialize = false)
|
||||
public String getRejectNodeId() {
|
||||
return callbackParam == null ? null : callbackParam.getRejectNodeId();
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
@JSONField(serialize = false)
|
||||
public String getCurrentNodeId() {
|
||||
return callbackParam == null ? null : callbackParam.getCurrentNodeId();
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
@JSONField(serialize = false)
|
||||
public String getCurrentNodeNumber() {
|
||||
return callbackParam == null ? null : callbackParam.getCurrentNodeNumber();
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
@JSONField(serialize = false)
|
||||
public String getOperation() {
|
||||
return callbackParam == null ? null : callbackParam.getOperation();
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
@JSONField(serialize = false)
|
||||
public String getOperationName() {
|
||||
return callbackParam == null ? null : callbackParam.getOperationName();
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
@JSONField(serialize = false)
|
||||
public String getApprovalOpinion() {
|
||||
return callbackParam == null ? null : callbackParam.getApprovalOpinion();
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
@JSONField(serialize = false)
|
||||
public String getOperatorLoginName() {
|
||||
return callbackParam == null ? null : callbackParam.getOperatorLoginName();
|
||||
}
|
||||
}
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
package org.springblade.openapi.mk.support.base;
|
||||
|
||||
/**
|
||||
* 流程操作处理器
|
||||
* @author bfhuange
|
||||
* @since 2024/11/25
|
||||
*/
|
||||
public interface ProcessOperationHandler {
|
||||
|
||||
/**
|
||||
* 提交
|
||||
* @param param
|
||||
*/
|
||||
void submit(ProcessOperationContext param);
|
||||
|
||||
/**
|
||||
* 审批结束
|
||||
* @param param
|
||||
*/
|
||||
void approveFinish(ProcessOperationContext param);
|
||||
|
||||
/**
|
||||
* 审批同意
|
||||
* @param param
|
||||
*/
|
||||
void approvePass(ProcessOperationContext param);
|
||||
|
||||
/**
|
||||
* 审批拒绝
|
||||
* @param param
|
||||
*/
|
||||
void approveReject(ProcessOperationContext param);
|
||||
|
||||
/**
|
||||
* 审批撤销
|
||||
* @param param
|
||||
*/
|
||||
void approveRevoke(ProcessOperationContext param);
|
||||
|
||||
/**
|
||||
* 审批废弃
|
||||
* @param param
|
||||
*/
|
||||
void approveAbandon(ProcessOperationContext param);
|
||||
}
|
||||
+113
@@ -0,0 +1,113 @@
|
||||
package org.springblade.openapi.mk.support.handler;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.dynamictp.core.DtpRegistry;
|
||||
import org.dromara.dynamictp.core.aware.TaskEnhanceAware;
|
||||
import org.springblade.core.log.exception.ServiceException;
|
||||
import org.springblade.openapi.mk.config.AsyncExecutorProperties;
|
||||
import org.springframework.boot.context.event.ApplicationReadyEvent;
|
||||
import org.springframework.context.event.EventListener;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* @author bfhuange
|
||||
* @date 2024/9/20
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
public class AsyncService {
|
||||
private final AsyncExecutorProperties asyncExecutorProperties;
|
||||
private Executor workerExecutor;
|
||||
private ScheduledExecutorService schedulerExecutor;
|
||||
|
||||
public AsyncService(AsyncExecutorProperties asyncExecutorProperties) {
|
||||
this.asyncExecutorProperties = asyncExecutorProperties;
|
||||
}
|
||||
|
||||
/**
|
||||
* 启动完成后预加载并校验线程池配置,避免等到第一次真正执行任务时才发现线程池缺失或类型配置错误。
|
||||
*/
|
||||
@Order(Ordered.HIGHEST_PRECEDENCE)
|
||||
@EventListener(ApplicationReadyEvent.class)
|
||||
public void initExecutors() {
|
||||
this.workerExecutor = resolveWorkerExecutor();
|
||||
this.schedulerExecutor = resolveSchedulerExecutor();
|
||||
log.info("当前处理人刷新异步线程池初始化完成,workerExecutorName:{},schedulerExecutorName:{}",
|
||||
asyncExecutorProperties.getWorkerExecutorName(), asyncExecutorProperties.getSchedulerExecutorName());
|
||||
}
|
||||
|
||||
/**
|
||||
* 立即异步执行
|
||||
* @param runnable 任务
|
||||
*/
|
||||
public void execute(Runnable runnable) {
|
||||
getWorkerExecutor().execute(runnable);
|
||||
}
|
||||
|
||||
/**
|
||||
* 延迟执行指定毫秒数。
|
||||
* <p>
|
||||
* 这里改为使用 ScheduledDtpExecutor 做真正的定时调度,
|
||||
* 避免再通过线程池线程 sleep 的方式占用工作线程,导致真正的业务任务迟迟无法启动。
|
||||
* </p>
|
||||
*
|
||||
* @param delayMillis 延迟毫秒数
|
||||
* @param runnable 任务
|
||||
*/
|
||||
public void delayExecute(long delayMillis, Runnable runnable) {
|
||||
if (delayMillis <= 0) {
|
||||
execute(runnable);
|
||||
return;
|
||||
}
|
||||
Runnable dispatchRunnable = wrapWithConfiguredTaskWrappers(
|
||||
asyncExecutorProperties.getSchedulerExecutorName(),
|
||||
() -> execute(runnable)
|
||||
);
|
||||
getSchedulerExecutor().schedule(dispatchRunnable, delayMillis, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
private Executor getWorkerExecutor() {
|
||||
return workerExecutor != null ? workerExecutor : resolveWorkerExecutor();
|
||||
}
|
||||
|
||||
private ScheduledExecutorService getSchedulerExecutor() {
|
||||
return schedulerExecutor != null ? schedulerExecutor : resolveSchedulerExecutor();
|
||||
}
|
||||
|
||||
private Executor resolveWorkerExecutor() {
|
||||
return DtpRegistry.getExecutor(asyncExecutorProperties.getWorkerExecutorName());
|
||||
}
|
||||
|
||||
private ScheduledExecutorService resolveSchedulerExecutor() {
|
||||
String schedulerExecutorName = asyncExecutorProperties.getSchedulerExecutorName();
|
||||
Executor executor = DtpRegistry.getExecutor(schedulerExecutorName);
|
||||
if (executor instanceof ScheduledExecutorService scheduledExecutorService) {
|
||||
return scheduledExecutorService;
|
||||
}
|
||||
String message = "线程池未按 ScheduledExecutorService 注册,name: " + schedulerExecutorName;
|
||||
log.error(message);
|
||||
throw new ServiceException(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* 按线程池已配置的 task wrappers 手动包装任务。
|
||||
* <p>
|
||||
* 当前使用的 dynamic-tp 版本下,ScheduledDtpExecutor 对 taskWrapper 的透传存在缺口,
|
||||
* 这里直接读取线程池上已生效的 wrappers,按框架默认增强链顺序主动包装一次,
|
||||
* 这样既能复用现有配置,又避免手写 mdc 透传逻辑与框架实现产生偏差。
|
||||
* </p>
|
||||
*/
|
||||
private Runnable wrapWithConfiguredTaskWrappers(String executorName, Runnable runnable) {
|
||||
Executor executor = DtpRegistry.getExecutor(executorName);
|
||||
if (executor instanceof TaskEnhanceAware taskEnhanceAware) {
|
||||
return taskEnhanceAware.getEnhancedTask(runnable, taskEnhanceAware.getTaskWrappers());
|
||||
}
|
||||
return runnable;
|
||||
}
|
||||
}
|
||||
+903
@@ -0,0 +1,903 @@
|
||||
package org.springblade.openapi.mk.support.handler;
|
||||
|
||||
import cn.hutool.core.util.IdUtil;
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.redisson.api.RLock;
|
||||
import org.redisson.api.RMapCache;
|
||||
import org.springblade.core.redis.cache.BladeRedis;
|
||||
import org.springblade.core.redis.lock.RedisLockClient;
|
||||
import org.springblade.core.tool.api.FR;
|
||||
import org.springblade.openapi.mk.config.CurrentHandlerRefreshProperties;
|
||||
import org.springblade.openapi.mk.constant.ProcessLockKeyConstant;
|
||||
import org.springblade.openapi.mk.pojo.enums.ProcessCallbackType;
|
||||
import org.springblade.openapi.mk.support.base.AbstractProcessOperationHandler;
|
||||
import org.springblade.openapi.mk.support.base.ProcessOperationContext;
|
||||
import org.springblade.process.feign.IBusinessProcessClient;
|
||||
import org.springblade.process.pojo.dto.BusinessProcessCurrentHandlerRefreshDTO;
|
||||
import org.springblade.process.pojo.vo.BusinessProcessVO;
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.boot.context.event.ApplicationReadyEvent;
|
||||
import org.springframework.context.event.EventListener;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* 当前处理人刷新调度服务。
|
||||
* <p>
|
||||
* 背景:
|
||||
* 流程引擎回调业务系统时,流程往往还没有真正流转到下一个激活节点,
|
||||
* 此时立即查询当前节点/当前处理人,拿到的仍可能是上一节点的旧结果。
|
||||
* 因此这里不再依赖一次性的固定延迟,而是改成“按流程实例维度入队 + 固定间隔轮询刷新”的调度模型。
|
||||
* </p>
|
||||
* <p>
|
||||
* 整体流程:
|
||||
* 1. openapi 收到流程事件后,先同步更新业务流程状态;
|
||||
* 2. 如果当前事件要求异步刷新当前处理人,则调用 {@link #enqueue(ProcessOperationContext)} 写入刷新任务;
|
||||
* 3. 任务以流程实例 id 为唯一主记录保存在 Redis,记录期望版本、执行版本、基线快照、最近回调参数、重试次数等信息;
|
||||
* 4. 同一个流程实例只保留一条主任务记录,新的回调不会重复创建任务,只会提升 {@code desiredVersion} 并覆盖最近一次回调参数;
|
||||
* 5. 等待中的流程实例 id 会放入 Redis ZSet,score 为下次重试时间,用于按时间顺序派工;
|
||||
* 6. 调度器 {@link #tryDispatch()} 会在集群范围内抢占派工锁,按配置的最大 worker 数拉起异步 worker;
|
||||
* 7. worker 执行时调用 system 侧“只刷新当前节点/当前处理人”接口,并比较“当前节点 + 当前处理人”快照是否相对基线发生变化;
|
||||
* 8. 如果快照未变化,说明流程大概率还没流转完成,则按固定间隔重新入队重试;
|
||||
* 9. 如果快照发生变化,则回调对应业务处理器 {@link AbstractProcessOperationHandler#handleCurrentHandlerRefresh(ProcessOperationContext, BusinessProcessVO)};
|
||||
* 10. 若执行期间又收到同一流程的新回调,则旧版本执行完后会把最新快照提升为新基线,并重新排到队尾,避免同一流程长期占用 worker;
|
||||
* 11. 当达到最大重试次数后,任务进入失败态并保留一段时间,便于排查;
|
||||
* 12. 成功完成的任务进入完成态并短期保留,随后自动过期。
|
||||
* </p>
|
||||
* <p>
|
||||
* 集群与并发约束:
|
||||
* 1. 流程实例级别使用分布式锁,保证同一流程实例的任务状态变更串行化;
|
||||
* 2. 派工使用全局分布式锁,保证多个实例不会同时超发 worker;
|
||||
* 3. 活跃 worker 数通过 Redis 租约控制,服务异常中断后,租约超时即可视为 worker 失活;
|
||||
* 4. 运行中的任务会持续更新心跳,如果服务升级、中断或线程异常退出,超时恢复逻辑会把任务重新转回等待态;
|
||||
* 5. 启动时不会全量恢复运行中任务,避免在集群环境中误伤其他实例上仍在执行的任务。
|
||||
* </p>
|
||||
* <p>
|
||||
* 成功判定规则:
|
||||
* 不再区分终态/非终态,也不依赖回调里传入的 complete true/false 单独判定是否成功,
|
||||
* 统一以“当前节点变化 + 当前处理人变化后的最新快照”是否相对基线发生变化作为刷新成功依据。
|
||||
* </p>
|
||||
*
|
||||
* @author bfhuange
|
||||
* @date 2026/4/9
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
public class ProcessCurrentHandlerRefreshService {
|
||||
|
||||
|
||||
private final AsyncService asyncService;
|
||||
private final BladeRedis bladeRedis;
|
||||
private final RedisLockClient redisLockClient;
|
||||
private final IBusinessProcessClient processClient;
|
||||
private final CurrentHandlerRefreshProperties refreshProperties;
|
||||
private final ObjectProvider<List<AbstractProcessOperationHandler>> handlersProvider;
|
||||
private final Map<String, AbstractProcessOperationHandler> handlerMap;
|
||||
|
||||
public ProcessCurrentHandlerRefreshService(AsyncService asyncService,
|
||||
BladeRedis bladeRedis,
|
||||
RedisLockClient redisLockClient,
|
||||
IBusinessProcessClient processClient,
|
||||
CurrentHandlerRefreshProperties refreshProperties,
|
||||
ObjectProvider<List<AbstractProcessOperationHandler>> handlersProvider) {
|
||||
this.asyncService = asyncService;
|
||||
this.bladeRedis = bladeRedis;
|
||||
this.redisLockClient = redisLockClient;
|
||||
this.processClient = processClient;
|
||||
this.refreshProperties = refreshProperties;
|
||||
this.handlersProvider = handlersProvider;
|
||||
this.handlerMap = new ConcurrentHashMap<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* 服务启动后恢复未完成任务
|
||||
*/
|
||||
@Order
|
||||
@EventListener(ApplicationReadyEvent.class)
|
||||
public void init() {
|
||||
// 集群环境下不能在启动时无差别回收所有运行中任务,否则会误伤其他实例正在执行的任务
|
||||
asyncService.delayExecute(refreshProperties.getStartupDispatchDelayMillis(), this::tryDispatch);
|
||||
}
|
||||
|
||||
/**
|
||||
* 按流程类型懒加载处理器,避免在bean初始化阶段提前拉起handler导致循环依赖
|
||||
*/
|
||||
private AbstractProcessOperationHandler getHandler(String processType) {
|
||||
if (StringUtils.isBlank(processType)) {
|
||||
return null;
|
||||
}
|
||||
if (handlerMap.isEmpty()) {
|
||||
synchronized (this) {
|
||||
if (handlerMap.isEmpty()) {
|
||||
List<AbstractProcessOperationHandler> handlers = handlersProvider.getIfAvailable(Collections::emptyList);
|
||||
handlers.forEach(handler -> handler.getProcessTypes()
|
||||
.forEach(type -> this.handlerMap.put(type, handler)));
|
||||
}
|
||||
}
|
||||
}
|
||||
return handlerMap.get(processType);
|
||||
}
|
||||
|
||||
/**
|
||||
* 写入刷新任务
|
||||
*
|
||||
* @param param 回调参数
|
||||
*/
|
||||
public void enqueue(ProcessOperationContext param) {
|
||||
if (param == null || StringUtils.isAnyBlank(param.getProcessType(), param.getProcessInstanceId())) {
|
||||
log.warn("当前处理人刷新任务入队失败,上下文为空或流程类型/流程实例id为空,param:{}", JSON.toJSONString(param));
|
||||
return;
|
||||
}
|
||||
long now = System.currentTimeMillis();
|
||||
String processInstanceId = param.getProcessInstanceId();
|
||||
RLock lock = getProcessLock(processInstanceId);
|
||||
boolean locked = false;
|
||||
try {
|
||||
locked = lock.tryLock(refreshProperties.getLockWaitSeconds(), TimeUnit.SECONDS);
|
||||
if (!locked) {
|
||||
log.warn("获取流程刷新任务锁失败,流程实例id:{}", processInstanceId);
|
||||
return;
|
||||
}
|
||||
ProcessCurrentHandlerRefreshTask task = getTask(processInstanceId);
|
||||
if (task == null) {
|
||||
task = new ProcessCurrentHandlerRefreshTask();
|
||||
task.setProcessInstanceId(processInstanceId);
|
||||
task.setState(TaskState.STATE_WAITING);
|
||||
}
|
||||
task.setProcessType(param.getProcessType());
|
||||
task.setContext(param);
|
||||
task.setDesiredVersion(task.getDesiredVersion() + 1);
|
||||
task.setLastCallbackAt(now);
|
||||
if (!TaskState.STATE_RUNNING.equals(task.getState())) {
|
||||
// 非运行中任务表示上一轮刷新周期已经结束或尚未开始。
|
||||
// 这里必须按本次回调重新建立基线,避免撤回后再次提交时沿用上一轮旧快照,导致新一轮刷新永远无法命中成功条件。
|
||||
task.setBaselineSnapshot(queryCurrentSnapshot(processInstanceId));
|
||||
task.setLatestSnapshot(null);
|
||||
task.setLastSuccessAt(null);
|
||||
task.setState(TaskState.STATE_WAITING);
|
||||
task.setAttemptCount(0);
|
||||
task.setProcessingVersion(0);
|
||||
task.setRunToken(null);
|
||||
task.setStartedAt(null);
|
||||
task.setHeartbeatAt(null);
|
||||
task.setNextRetryAt(now + initialDelayMillis());
|
||||
saveTask(task);
|
||||
putWaitingTask(processInstanceId, task.getNextRetryAt());
|
||||
log.info("当前处理人刷新任务入队,流程实例id:{},{}", processInstanceId, formatTaskLog(task));
|
||||
scheduleDispatch(initialDelayMillis());
|
||||
} else {
|
||||
saveTask(task);
|
||||
log.info("当前处理人刷新任务更新执行中版本,流程实例id:{},{}", processInstanceId, formatTaskLog(task));
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
log.error("当前处理人刷新任务入队被中断,流程实例id:{}", processInstanceId, e);
|
||||
} catch (Exception e) {
|
||||
log.error("当前处理人刷新任务入队异常,流程实例id:{}", processInstanceId, e);
|
||||
} finally {
|
||||
unlock(lock);
|
||||
}
|
||||
tryDispatch();
|
||||
}
|
||||
|
||||
/**
|
||||
* 同步立即刷新当前处理人
|
||||
*
|
||||
* @param param 回调参数
|
||||
*/
|
||||
public void refreshNow(ProcessOperationContext param) {
|
||||
if (param == null || StringUtils.isAnyBlank(param.getProcessType(), param.getProcessInstanceId())) {
|
||||
log.warn("同步刷新当前处理人失败,上下文为空或流程类型/流程实例id为空,param:{}", JSON.toJSONString(param));
|
||||
return;
|
||||
}
|
||||
FR<BusinessProcessVO> result = processClient.refreshBusinessProcessCurrentHandlers(buildUpdateParam(param));
|
||||
if (result == null || FR.isNotSuccess(result)) {
|
||||
log.warn("同步刷新当前处理人失败,转入异步队列重试,流程实例id:{} result:{}", param.getProcessInstanceId(), JSON.toJSONString(result));
|
||||
enqueue(param);
|
||||
return;
|
||||
}
|
||||
AbstractProcessOperationHandler handler = getHandler(param.getProcessType());
|
||||
if (handler == null) {
|
||||
log.error("同步刷新当前处理人失败,未找到处理器,流程类型:{} 流程实例id:{}", param.getProcessType(), param.getProcessInstanceId());
|
||||
return;
|
||||
}
|
||||
handler.handleCurrentHandlerRefresh(param, result.getData());
|
||||
}
|
||||
|
||||
/**
|
||||
* 派发worker执行任务
|
||||
*/
|
||||
public void tryDispatch() {
|
||||
int activeWorkerCount = getActiveWorkerCount();
|
||||
if (activeWorkerCount >= refreshProperties.getMaxWorkers()) {
|
||||
log.info("当前处理人刷新派工跳过,活跃worker已满,activeWorkers:{},maxWorkers:{}", activeWorkerCount, refreshProperties.getMaxWorkers());
|
||||
return;
|
||||
}
|
||||
RLock dispatchLock = getDispatchLock();
|
||||
boolean locked = false;
|
||||
try {
|
||||
locked = dispatchLock.tryLock(refreshProperties.getLockWaitSeconds(), TimeUnit.SECONDS);
|
||||
if (!locked) {
|
||||
return;
|
||||
}
|
||||
// 先恢复真正超时的运行中任务,再判断是否有可执行任务
|
||||
recoverTimeoutTasks();
|
||||
if (!hasWaitingTask()) {
|
||||
return;
|
||||
}
|
||||
activeWorkerCount = getActiveWorkerCount();
|
||||
if (activeWorkerCount >= refreshProperties.getMaxWorkers()) {
|
||||
log.info("当前处理人刷新派工二次检查跳过,活跃worker已满,activeWorkers:{},maxWorkers:{}", activeWorkerCount, refreshProperties.getMaxWorkers());
|
||||
return;
|
||||
}
|
||||
while ((activeWorkerCount = getActiveWorkerCount()) < refreshProperties.getMaxWorkers()) {
|
||||
ProcessCurrentHandlerRefreshTask task = claimNextRunnableTaskUnderDispatchLock();
|
||||
if (task == null) {
|
||||
return;
|
||||
}
|
||||
String workerId = IdUtil.fastSimpleUUID();
|
||||
refreshWorkerLease(workerId);
|
||||
log.info("当前处理人刷新任务派工成功,workerId:{},activeWorkers:{},maxWorkers:{},流程实例id:{},{}",
|
||||
workerId, activeWorkerCount, refreshProperties.getMaxWorkers(), task.getProcessInstanceId(), formatTaskLog(task));
|
||||
// worker租约到期前再触发一次派工,用于兜底恢复异常中断任务
|
||||
scheduleDispatch(workerLeaseMillis());
|
||||
asyncService.execute(() -> workerLoop(workerId, task));
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
log.error("当前处理人刷新派工被中断", e);
|
||||
} catch (Exception e) {
|
||||
log.error("当前处理人刷新派工异常", e);
|
||||
} finally {
|
||||
unlock(dispatchLock);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* worker循环拉取任务,尽量复用已经占用的worker槽位
|
||||
*
|
||||
* @param workerId worker id
|
||||
* @param firstTask 第一条任务
|
||||
*/
|
||||
private void workerLoop(String workerId, ProcessCurrentHandlerRefreshTask firstTask) {
|
||||
try {
|
||||
ProcessCurrentHandlerRefreshTask task = firstTask;
|
||||
while (task != null) {
|
||||
log.info("当前处理人刷新worker开始执行任务,workerId:{},流程实例id:{},{}", workerId, task.getProcessInstanceId(), formatTaskLog(task));
|
||||
processTask(workerId, task);
|
||||
refreshWorkerLease(workerId);
|
||||
task = claimNextRunnableTask();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("当前处理人刷新worker执行异常,workerId:{}", workerId, e);
|
||||
} finally {
|
||||
removeWorkerLease(workerId);
|
||||
log.info("当前处理人刷新worker结束,workerId:{}", workerId);
|
||||
tryDispatch();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行单条刷新任务
|
||||
*
|
||||
* @param workerId worker id
|
||||
* @param task 任务
|
||||
*/
|
||||
private void processTask(String workerId, ProcessCurrentHandlerRefreshTask task) {
|
||||
String processInstanceId = task.getProcessInstanceId();
|
||||
String runToken = task.getRunToken();
|
||||
if (!isTaskTokenMatched(processInstanceId, runToken)) {
|
||||
log.info("当前处理人刷新任务执行前token已失效,workerId:{},流程实例id:{},runToken:{}", workerId, processInstanceId, runToken);
|
||||
return;
|
||||
}
|
||||
updateHeartbeat(processInstanceId, runToken);
|
||||
long startAt = System.currentTimeMillis();
|
||||
log.info("当前处理人刷新任务开始查询,workerId:{},流程实例id:{},runToken:{},attemptCount:{},baselineSnapshot:{}",
|
||||
workerId, processInstanceId, runToken, task.getAttemptCount(), task.getBaselineSnapshot());
|
||||
FR<BusinessProcessVO> result = processClient.refreshBusinessProcessCurrentHandlers(buildUpdateParam(task.getContext()));
|
||||
updateHeartbeat(processInstanceId, runToken);
|
||||
if (!isTaskTokenMatched(processInstanceId, runToken)) {
|
||||
log.info("当前处理人刷新任务查询后token已失效,workerId:{},流程实例id:{},runToken:{}", workerId, processInstanceId, runToken);
|
||||
return;
|
||||
}
|
||||
if (result == null || FR.isNotSuccess(result)) {
|
||||
log.error("刷新当前处理人失败,流程实例id:{} result:{}", processInstanceId, JSON.toJSONString(result));
|
||||
requeueAfterMiss(task, false);
|
||||
return;
|
||||
}
|
||||
BusinessProcessVO businessProcessVO = result.getData();
|
||||
// 只有“提交”事件需要额外等待离开回调节点;
|
||||
// 审批通过/会签等场景允许节点不变但处理人变化,不能套用同一条规则,否则会误判为一直未流转
|
||||
if (shouldWaitForNextNode(task.getContext(), businessProcessVO)) {
|
||||
log.info("当前处理人刷新任务命中等待下一节点条件,workerId:{},流程实例id:{},耗时:{}ms,latestSnapshot:{}",
|
||||
workerId, processInstanceId, System.currentTimeMillis() - startAt, buildSnapshot(businessProcessVO));
|
||||
requeueAfterMiss(task, false);
|
||||
return;
|
||||
}
|
||||
String latestSnapshot = buildSnapshot(businessProcessVO);
|
||||
if (StringUtils.equals(latestSnapshot, task.getBaselineSnapshot())) {
|
||||
log.info("当前处理人刷新任务快照未变化,workerId:{},流程实例id:{},耗时:{}ms,baselineSnapshot:{},latestSnapshot:{}",
|
||||
workerId, processInstanceId, System.currentTimeMillis() - startAt, task.getBaselineSnapshot(), latestSnapshot);
|
||||
requeueAfterMiss(task, false);
|
||||
return;
|
||||
}
|
||||
log.info("当前处理人刷新任务命中成功条件,workerId:{},流程实例id:{},耗时:{}ms,baselineSnapshot:{},latestSnapshot:{}",
|
||||
workerId, processInstanceId, System.currentTimeMillis() - startAt, task.getBaselineSnapshot(), latestSnapshot);
|
||||
handleRefreshSuccess(task, businessProcessVO, latestSnapshot);
|
||||
refreshWorkerLease(workerId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理刷新成功
|
||||
*
|
||||
* @param task 任务
|
||||
* @param businessProcessVO 最新流程快照
|
||||
* @param latestSnapshot 最新快照
|
||||
*/
|
||||
private void handleRefreshSuccess(ProcessCurrentHandlerRefreshTask task, BusinessProcessVO businessProcessVO, String latestSnapshot) {
|
||||
String processInstanceId = task.getProcessInstanceId();
|
||||
String runToken = task.getRunToken();
|
||||
AbstractProcessOperationHandler handler = getHandler(task.getProcessType());
|
||||
if (handler == null) {
|
||||
log.error("未找到当前处理人刷新处理器,流程类型:{} 流程实例id:{}", task.getProcessType(), processInstanceId);
|
||||
requeueAfterMiss(task, true);
|
||||
return;
|
||||
}
|
||||
ProcessOperationContext callbackParam = task.getContext();
|
||||
try {
|
||||
handler.handleCurrentHandlerRefresh(callbackParam, businessProcessVO);
|
||||
} catch (Exception e) {
|
||||
log.error("刷新当前处理人后执行业务回调异常,流程实例id:{}", processInstanceId, e);
|
||||
requeueAfterMiss(task, true);
|
||||
return;
|
||||
}
|
||||
|
||||
RLock lock = getProcessLock(processInstanceId);
|
||||
boolean locked = false;
|
||||
try {
|
||||
locked = lock.tryLock(refreshProperties.getLockWaitSeconds(), TimeUnit.SECONDS);
|
||||
if (!locked) {
|
||||
log.warn("刷新成功后回写任务失败,未获取到流程锁,流程实例id:{}", processInstanceId);
|
||||
scheduleDispatch(intervalMillis());
|
||||
return;
|
||||
}
|
||||
ProcessCurrentHandlerRefreshTask latestTask = getTask(processInstanceId);
|
||||
if (latestTask == null || !StringUtils.equals(runToken, latestTask.getRunToken())) {
|
||||
return;
|
||||
}
|
||||
long now = System.currentTimeMillis();
|
||||
latestTask.setLatestSnapshot(latestSnapshot);
|
||||
latestTask.setLastSuccessAt(now);
|
||||
latestTask.setStartedAt(null);
|
||||
latestTask.setHeartbeatAt(null);
|
||||
latestTask.setRunToken(null);
|
||||
if (latestTask.getDesiredVersion() > task.getProcessingVersion()) {
|
||||
// 有新版本到来时,把最新快照提升为新基线,并重新排队到后面,避免一个流程长期占用worker
|
||||
latestTask.setBaselineSnapshot(latestSnapshot);
|
||||
latestTask.setAttemptCount(0);
|
||||
latestTask.setState(TaskState.STATE_WAITING);
|
||||
latestTask.setNextRetryAt(now + intervalMillis());
|
||||
saveTask(latestTask);
|
||||
putWaitingTask(processInstanceId, latestTask.getNextRetryAt());
|
||||
log.info("当前处理人刷新任务成功后发现新版本,重新排队,流程实例id:{},{}", processInstanceId, formatTaskLog(latestTask));
|
||||
scheduleDispatch(intervalMillis());
|
||||
return;
|
||||
}
|
||||
latestTask.setState(TaskState.STATE_DONE);
|
||||
saveTask(latestTask, Duration.ofMinutes(refreshProperties.getDoneTtlMinutes()));
|
||||
removeWaitingTask(processInstanceId);
|
||||
log.info("当前处理人刷新任务执行完成,流程实例id:{},{}", processInstanceId, formatTaskLog(latestTask));
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
log.error("刷新成功后回写任务被中断,流程实例id:{}", processInstanceId, e);
|
||||
} finally {
|
||||
unlock(lock);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 未命中最新快照时重新排队
|
||||
*
|
||||
* @param task 任务
|
||||
* @param resetAttempt 是否重置重试次数
|
||||
*/
|
||||
private void requeueAfterMiss(ProcessCurrentHandlerRefreshTask task, boolean resetAttempt) {
|
||||
String processInstanceId = task.getProcessInstanceId();
|
||||
String runToken = task.getRunToken();
|
||||
RLock lock = getProcessLock(processInstanceId);
|
||||
boolean locked = false;
|
||||
try {
|
||||
locked = lock.tryLock(refreshProperties.getLockWaitSeconds(), TimeUnit.SECONDS);
|
||||
if (!locked) {
|
||||
log.warn("刷新任务重新排队失败,未获取到流程锁,流程实例id:{}", processInstanceId);
|
||||
scheduleDispatch(intervalMillis());
|
||||
return;
|
||||
}
|
||||
ProcessCurrentHandlerRefreshTask latestTask = getTask(processInstanceId);
|
||||
if (latestTask == null || !StringUtils.equals(runToken, latestTask.getRunToken())) {
|
||||
return;
|
||||
}
|
||||
long now = System.currentTimeMillis();
|
||||
boolean hasNewVersion = latestTask.getDesiredVersion() > task.getProcessingVersion();
|
||||
latestTask.setRunToken(null);
|
||||
latestTask.setStartedAt(null);
|
||||
latestTask.setHeartbeatAt(null);
|
||||
latestTask.setState(TaskState.STATE_WAITING);
|
||||
latestTask.setNextRetryAt(now + intervalMillis());
|
||||
if (resetAttempt || hasNewVersion) {
|
||||
latestTask.setAttemptCount(0);
|
||||
} else {
|
||||
latestTask.setAttemptCount(latestTask.getAttemptCount() + 1);
|
||||
}
|
||||
if (latestTask.getAttemptCount() >= refreshProperties.getMaxAttempts()) {
|
||||
latestTask.setState(TaskState.STATE_FAILED);
|
||||
saveTask(latestTask, Duration.ofMinutes(refreshProperties.getFailedTtlMinutes()));
|
||||
removeWaitingTask(processInstanceId);
|
||||
log.warn("当前处理人刷新任务达到最大重试次数,流程实例id:{},{}", processInstanceId, formatTaskLog(latestTask));
|
||||
return;
|
||||
}
|
||||
saveTask(latestTask);
|
||||
putWaitingTask(processInstanceId, latestTask.getNextRetryAt());
|
||||
log.info("当前处理人刷新任务重新排队,流程实例id:{},resetAttempt:{},hasNewVersion:{},{}",
|
||||
processInstanceId, resetAttempt, hasNewVersion, formatTaskLog(latestTask));
|
||||
scheduleDispatch(intervalMillis());
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
log.error("刷新任务重新排队被中断,流程实例id:{}", processInstanceId, e);
|
||||
} finally {
|
||||
unlock(lock);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* claim下一条可执行任务
|
||||
*
|
||||
* @return 任务,不存在时返回null
|
||||
*/
|
||||
private ProcessCurrentHandlerRefreshTask claimNextRunnableTask() {
|
||||
RLock dispatchLock = getDispatchLock();
|
||||
boolean locked = false;
|
||||
try {
|
||||
locked = dispatchLock.tryLock(refreshProperties.getLockWaitSeconds(), TimeUnit.SECONDS);
|
||||
if (!locked) {
|
||||
return null;
|
||||
}
|
||||
recoverTimeoutTasks();
|
||||
return claimNextRunnableTaskUnderDispatchLock();
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
log.error("claim当前处理人刷新任务被中断", e);
|
||||
return null;
|
||||
} finally {
|
||||
unlock(dispatchLock);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 在已持有派工锁的前提下claim下一条可执行任务
|
||||
*
|
||||
* @return 任务,不存在时返回null
|
||||
*/
|
||||
private ProcessCurrentHandlerRefreshTask claimNextRunnableTaskUnderDispatchLock() {
|
||||
Set<String> processIds = bladeRedis.getStringRedisTemplate().opsForZSet()
|
||||
.rangeByScore(ProcessLockKeyConstant.WAITING_KEY, 0, System.currentTimeMillis(), 0, 1);
|
||||
if (processIds == null || processIds.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
String processInstanceId = processIds.iterator().next();
|
||||
RLock processLock = getProcessLock(processInstanceId);
|
||||
boolean processLocked = false;
|
||||
try {
|
||||
processLocked = processLock.tryLock(refreshProperties.getLockWaitSeconds(), TimeUnit.SECONDS);
|
||||
if (!processLocked) {
|
||||
return null;
|
||||
}
|
||||
ProcessCurrentHandlerRefreshTask task = getTask(processInstanceId);
|
||||
if (task == null) {
|
||||
removeWaitingTask(processInstanceId);
|
||||
return null;
|
||||
}
|
||||
if (!TaskState.STATE_WAITING.equals(task.getState())) {
|
||||
removeWaitingTask(processInstanceId);
|
||||
return null;
|
||||
}
|
||||
if (task.getNextRetryAt() > System.currentTimeMillis()) {
|
||||
putWaitingTask(processInstanceId, task.getNextRetryAt());
|
||||
return null;
|
||||
}
|
||||
task.setState(TaskState.STATE_RUNNING);
|
||||
task.setProcessingVersion(task.getDesiredVersion());
|
||||
task.setRunToken(IdUtil.fastSimpleUUID());
|
||||
task.setStartedAt(System.currentTimeMillis());
|
||||
task.setHeartbeatAt(task.getStartedAt());
|
||||
saveTask(task);
|
||||
removeWaitingTask(processInstanceId);
|
||||
log.info("当前处理人刷新任务claim成功,流程实例id:{},{}", processInstanceId, formatTaskLog(task));
|
||||
return task;
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
log.error("claim当前处理人刷新任务被中断,流程实例id:{}", processInstanceId, e);
|
||||
return null;
|
||||
} finally {
|
||||
if (processLocked) {
|
||||
unlock(processLock);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 恢复超时的运行中任务
|
||||
*/
|
||||
private void recoverTimeoutTasks() {
|
||||
Set<String> taskKeys = bladeRedis.getStringRedisTemplate().keys(ProcessLockKeyConstant.TASK_KEY_PREFIX + "*");
|
||||
if (taskKeys == null || taskKeys.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
long now = System.currentTimeMillis();
|
||||
for (String taskKey : taskKeys) {
|
||||
ProcessCurrentHandlerRefreshTask task = getTaskByKey(taskKey);
|
||||
if (task == null || !TaskState.STATE_RUNNING.equals(task.getState())) {
|
||||
continue;
|
||||
}
|
||||
Long heartbeatAt = task.getHeartbeatAt();
|
||||
if (heartbeatAt != null && now - heartbeatAt <= workerLeaseMillis()) {
|
||||
continue;
|
||||
}
|
||||
String processInstanceId = task.getProcessInstanceId();
|
||||
RLock lock = getProcessLock(processInstanceId);
|
||||
boolean locked = false;
|
||||
try {
|
||||
locked = lock.tryLock(refreshProperties.getLockWaitSeconds(), TimeUnit.SECONDS);
|
||||
if (!locked) {
|
||||
continue;
|
||||
}
|
||||
ProcessCurrentHandlerRefreshTask latestTask = getTask(processInstanceId);
|
||||
if (latestTask == null || !TaskState.STATE_RUNNING.equals(latestTask.getState())) {
|
||||
continue;
|
||||
}
|
||||
Long latestHeartbeatAt = latestTask.getHeartbeatAt();
|
||||
if (latestHeartbeatAt != null && now - latestHeartbeatAt <= workerLeaseMillis()) {
|
||||
continue;
|
||||
}
|
||||
latestTask.setState(TaskState.STATE_WAITING);
|
||||
latestTask.setRunToken(null);
|
||||
latestTask.setStartedAt(null);
|
||||
latestTask.setHeartbeatAt(null);
|
||||
latestTask.setNextRetryAt(now);
|
||||
saveTask(latestTask);
|
||||
putWaitingTask(processInstanceId, now);
|
||||
log.warn("恢复超时的当前处理人刷新任务,流程实例id:{},{}", processInstanceId, formatTaskLog(latestTask));
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
log.error("恢复超时任务被中断,流程实例id:{}", processInstanceId, e);
|
||||
return;
|
||||
} finally {
|
||||
unlock(lock);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询业务流程当前快照
|
||||
*
|
||||
* @param processInstanceId 流程实例id
|
||||
* @return 快照字符串
|
||||
*/
|
||||
private String queryCurrentSnapshot(String processInstanceId) {
|
||||
FR<BusinessProcessVO> result = processClient.queryBusinessProcessSnapshot(processInstanceId);
|
||||
if (result == null || FR.isNotSuccess(result)) {
|
||||
log.warn("查询业务流程当前快照失败,流程实例id:{} result:{}", processInstanceId, JSON.toJSONString(result));
|
||||
return buildSnapshot(null);
|
||||
}
|
||||
return buildSnapshot(result.getData());
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造刷新请求参数
|
||||
*
|
||||
* @param callbackParam 回调参数
|
||||
* @return 刷新参数
|
||||
*/
|
||||
private BusinessProcessCurrentHandlerRefreshDTO buildUpdateParam(ProcessOperationContext callbackParam) {
|
||||
BusinessProcessCurrentHandlerRefreshDTO updateParam = new BusinessProcessCurrentHandlerRefreshDTO();
|
||||
updateParam.setProcessInstanceId(callbackParam.getProcessInstanceId());
|
||||
updateParam.setPromoterLoginName(callbackParam.getApplicantLoginName());
|
||||
updateParam.setComplete(callbackParam.isComplete());
|
||||
return updateParam;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造快照,统一用当前节点+当前处理人作为变更依据
|
||||
*
|
||||
* @param businessProcessVO 业务流程快照
|
||||
* @return 快照字符串
|
||||
*/
|
||||
private String buildSnapshot(BusinessProcessVO businessProcessVO) {
|
||||
if (businessProcessVO == null) {
|
||||
return "|";
|
||||
}
|
||||
return normalizeCsv(businessProcessVO.getCurrentNodeIds()) + "|" + normalizeCsv(businessProcessVO.getCurrentHandlers());
|
||||
}
|
||||
|
||||
/**
|
||||
* 提交回调时,如果刷新后仍停留在本次回调节点,说明流程尚未真正流转到下一激活节点,需要继续等待。
|
||||
* <p>
|
||||
* 这里只针对提交事件生效,不能推广到审批通过/会签等场景:
|
||||
* 会签节点在部分人审批完成后,当前节点可能仍然不变,但当前处理人已经发生变化,
|
||||
* 此时应当允许按“快照变化”判定成功,而不是继续等待节点变化。
|
||||
* </p>
|
||||
*
|
||||
* @param callbackParam 回调参数
|
||||
* @param businessProcessVO 最新流程快照
|
||||
* @return 是否继续等待下一节点
|
||||
*/
|
||||
private boolean shouldWaitForNextNode(ProcessOperationContext callbackParam, BusinessProcessVO businessProcessVO) {
|
||||
if (callbackParam == null || businessProcessVO == null) {
|
||||
return false;
|
||||
}
|
||||
if (ProcessCallbackType.SUBMIT != ProcessCallbackType.getCallbackType(callbackParam.getOperation())) {
|
||||
return false;
|
||||
}
|
||||
String callbackNodeId = callbackParam.getCurrentNodeId();
|
||||
if (StringUtils.isBlank(callbackNodeId)) {
|
||||
return false;
|
||||
}
|
||||
return containsCsvValue(businessProcessVO.getCurrentNodeIds(), callbackNodeId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 统一规范逗号拼接字段,避免比较时顺序影响结果
|
||||
*
|
||||
* @param value 原始值
|
||||
* @return 规范化后的字符串
|
||||
*/
|
||||
private String normalizeCsv(String value) {
|
||||
if (StringUtils.isBlank(value)) {
|
||||
return "";
|
||||
}
|
||||
return Stream.of(value.split(","))
|
||||
.map(String::trim)
|
||||
.filter(StringUtils::isNotBlank)
|
||||
.distinct()
|
||||
.sorted()
|
||||
.collect(Collectors.joining(","));
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断逗号分隔字段中是否包含指定值
|
||||
*
|
||||
* @param csv 逗号分隔字段
|
||||
* @param target 目标值
|
||||
* @return 是否包含
|
||||
*/
|
||||
private boolean containsCsvValue(String csv, String target) {
|
||||
if (StringUtils.isAnyBlank(csv, target)) {
|
||||
return false;
|
||||
}
|
||||
return Stream.of(csv.split(","))
|
||||
.map(String::trim)
|
||||
.anyMatch(target::equals);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断任务token是否仍然有效
|
||||
*
|
||||
* @param processInstanceId 流程实例id
|
||||
* @param runToken 运行token
|
||||
* @return 是否匹配
|
||||
*/
|
||||
private boolean isTaskTokenMatched(String processInstanceId, String runToken) {
|
||||
ProcessCurrentHandlerRefreshTask latestTask = getTask(processInstanceId);
|
||||
return latestTask != null
|
||||
&& TaskState.STATE_RUNNING.equals(latestTask.getState())
|
||||
&& StringUtils.equals(runToken, latestTask.getRunToken());
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新任务心跳,表示当前worker仍然存活
|
||||
*
|
||||
* @param processInstanceId 流程实例id
|
||||
* @param runToken 运行token
|
||||
*/
|
||||
private void updateHeartbeat(String processInstanceId, String runToken) {
|
||||
RLock lock = getProcessLock(processInstanceId);
|
||||
boolean locked = false;
|
||||
try {
|
||||
locked = lock.tryLock(refreshProperties.getLockWaitSeconds(), TimeUnit.SECONDS);
|
||||
if (!locked) {
|
||||
return;
|
||||
}
|
||||
ProcessCurrentHandlerRefreshTask task = getTask(processInstanceId);
|
||||
if (task == null || !StringUtils.equals(runToken, task.getRunToken())) {
|
||||
return;
|
||||
}
|
||||
task.setHeartbeatAt(System.currentTimeMillis());
|
||||
saveTask(task);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
log.error("更新刷新任务心跳被中断,流程实例id:{}", processInstanceId, e);
|
||||
} finally {
|
||||
unlock(lock);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 按默认方式保存任务
|
||||
*
|
||||
* @param task 任务
|
||||
*/
|
||||
private void saveTask(ProcessCurrentHandlerRefreshTask task) {
|
||||
bladeRedis.getStringRedisTemplate().opsForValue().set(getTaskKey(task.getProcessInstanceId()), JSON.toJSONString(task));
|
||||
}
|
||||
|
||||
/**
|
||||
* 按TTL保存任务
|
||||
*
|
||||
* @param task 任务
|
||||
* @param ttl TTL
|
||||
*/
|
||||
private void saveTask(ProcessCurrentHandlerRefreshTask task, Duration ttl) {
|
||||
bladeRedis.getStringRedisTemplate().opsForValue()
|
||||
.set(getTaskKey(task.getProcessInstanceId()), JSON.toJSONString(task), ttl);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取任务
|
||||
*
|
||||
* @param processInstanceId 流程实例id
|
||||
* @return 任务
|
||||
*/
|
||||
private ProcessCurrentHandlerRefreshTask getTask(String processInstanceId) {
|
||||
return getTaskByKey(getTaskKey(processInstanceId));
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过key读取任务
|
||||
*
|
||||
* @param taskKey 任务key
|
||||
* @return 任务
|
||||
*/
|
||||
private ProcessCurrentHandlerRefreshTask getTaskByKey(String taskKey) {
|
||||
String content = bladeRedis.getStringRedisTemplate().opsForValue().get(taskKey);
|
||||
if (StringUtils.isBlank(content)) {
|
||||
return null;
|
||||
}
|
||||
return JSON.parseObject(content, ProcessCurrentHandlerRefreshTask.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* 放入等待队列
|
||||
*
|
||||
* @param processInstanceId 流程实例id
|
||||
* @param nextRetryAt 下次执行时间
|
||||
*/
|
||||
private void putWaitingTask(String processInstanceId, long nextRetryAt) {
|
||||
bladeRedis.getStringRedisTemplate().opsForZSet().add(ProcessLockKeyConstant.WAITING_KEY, processInstanceId, nextRetryAt);
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除等待队列中的任务
|
||||
*
|
||||
* @param processInstanceId 流程实例id
|
||||
*/
|
||||
private void removeWaitingTask(String processInstanceId) {
|
||||
bladeRedis.getStringRedisTemplate().opsForZSet().remove(ProcessLockKeyConstant.WAITING_KEY, processInstanceId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否存在等待任务
|
||||
*
|
||||
* @return 是否存在
|
||||
*/
|
||||
private boolean hasWaitingTask() {
|
||||
Long size = bladeRedis.getStringRedisTemplate().opsForZSet().zCard(ProcessLockKeyConstant.WAITING_KEY);
|
||||
return size != null && size > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取worker租约map
|
||||
*
|
||||
* @return worker租约map
|
||||
*/
|
||||
private RMapCache<String, String> getWorkerLeaseMap() {
|
||||
return redisLockClient.getRedissonClient().getMapCache(ProcessLockKeyConstant.WORKER_LEASE_KEY);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前活跃worker数量
|
||||
*
|
||||
* @return 活跃worker数量
|
||||
*/
|
||||
private int getActiveWorkerCount() {
|
||||
return getWorkerLeaseMap().size();
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新worker租约
|
||||
*
|
||||
* @param workerId worker id
|
||||
*/
|
||||
private void refreshWorkerLease(String workerId) {
|
||||
getWorkerLeaseMap().put(workerId, workerId, refreshProperties.getWorkerLeaseSeconds(), TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除worker租约
|
||||
*
|
||||
* @param workerId worker id
|
||||
*/
|
||||
private void removeWorkerLease(String workerId) {
|
||||
getWorkerLeaseMap().remove(workerId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 安排稍后再次派工
|
||||
*
|
||||
* @param delayMillis 延迟毫秒数
|
||||
*/
|
||||
private void scheduleDispatch(long delayMillis) {
|
||||
asyncService.delayExecute(delayMillis, this::tryDispatch);
|
||||
}
|
||||
|
||||
private RLock getProcessLock(String processInstanceId) {
|
||||
return redisLockClient.getRedissonClient().getLock(ProcessLockKeyConstant.PROCESS_LOCK_KEY_PREFIX + processInstanceId);
|
||||
}
|
||||
|
||||
private RLock getDispatchLock() {
|
||||
return redisLockClient.getRedissonClient().getLock(ProcessLockKeyConstant.DISPATCH_LOCK_KEY);
|
||||
}
|
||||
|
||||
/**
|
||||
* 释放锁
|
||||
*
|
||||
* @param lock
|
||||
*/
|
||||
private void unlock(RLock lock) {
|
||||
if (lock.isLocked() && lock.isHeldByCurrentThread()) {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
private String getTaskKey(String processInstanceId) {
|
||||
return ProcessLockKeyConstant.TASK_KEY_PREFIX + processInstanceId;
|
||||
}
|
||||
|
||||
private long initialDelayMillis() {
|
||||
return refreshProperties.getInitialDelaySeconds() * 1000L;
|
||||
}
|
||||
|
||||
private long intervalMillis() {
|
||||
return refreshProperties.getIntervalSeconds() * 1000L;
|
||||
}
|
||||
|
||||
private long workerLeaseMillis() {
|
||||
return refreshProperties.getWorkerLeaseSeconds() * 1000L;
|
||||
}
|
||||
|
||||
private String formatTaskLog(ProcessCurrentHandlerRefreshTask task) {
|
||||
if (task == null) {
|
||||
return "task=null";
|
||||
}
|
||||
return "state=" + task.getState()
|
||||
+ ", desiredVersion=" + task.getDesiredVersion()
|
||||
+ ", processingVersion=" + task.getProcessingVersion()
|
||||
+ ", attemptCount=" + task.getAttemptCount()
|
||||
+ ", nextRetryAt=" + task.getNextRetryAt()
|
||||
+ ", startedAt=" + task.getStartedAt()
|
||||
+ ", heartbeatAt=" + task.getHeartbeatAt()
|
||||
+ ", lastCallbackAt=" + task.getLastCallbackAt()
|
||||
+ ", lastSuccessAt=" + task.getLastSuccessAt();
|
||||
}
|
||||
}
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
package org.springblade.openapi.mk.support.handler;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springblade.openapi.mk.support.base.ProcessOperationContext;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 当前处理人刷新任务
|
||||
*
|
||||
* @author bfhuange
|
||||
* @date 2026/4/9
|
||||
*/
|
||||
@Data
|
||||
public class ProcessCurrentHandlerRefreshTask implements Serializable {
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 流程实例id
|
||||
*/
|
||||
private String processInstanceId;
|
||||
/**
|
||||
* 流程类型
|
||||
*/
|
||||
private String processType;
|
||||
/**
|
||||
* 任务状态
|
||||
*/
|
||||
private String state;
|
||||
/**
|
||||
* 期望处理版本
|
||||
*/
|
||||
private long desiredVersion;
|
||||
/**
|
||||
* 当前执行版本
|
||||
*/
|
||||
private long processingVersion;
|
||||
/**
|
||||
* 当前执行令牌
|
||||
*/
|
||||
private String runToken;
|
||||
/**
|
||||
* 当前基线快照
|
||||
*/
|
||||
private String baselineSnapshot;
|
||||
/**
|
||||
* 最近一次成功快照
|
||||
*/
|
||||
private String latestSnapshot;
|
||||
/**
|
||||
* 重试次数
|
||||
*/
|
||||
private int attemptCount;
|
||||
/**
|
||||
* 下次重试时间
|
||||
*/
|
||||
private long nextRetryAt;
|
||||
/**
|
||||
* 开始执行时间
|
||||
*/
|
||||
private Long startedAt;
|
||||
/**
|
||||
* 最近心跳时间
|
||||
*/
|
||||
private Long heartbeatAt;
|
||||
/**
|
||||
* 最近成功时间
|
||||
*/
|
||||
private Long lastSuccessAt;
|
||||
/**
|
||||
* 最近回调时间
|
||||
*/
|
||||
private Long lastCallbackAt;
|
||||
/**
|
||||
* 最近一次回调参数
|
||||
*/
|
||||
private ProcessOperationContext context;
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
package org.springblade.openapi.mk.support.handler;
|
||||
|
||||
/**
|
||||
* 当前处理人刷新任务状态常量类
|
||||
* @author bfhuange
|
||||
* @since 2026/4/9
|
||||
*/
|
||||
public class TaskState {
|
||||
/**
|
||||
* 等待执行
|
||||
*/
|
||||
public static final String STATE_WAITING = "WAITING";
|
||||
/**
|
||||
* 执行中
|
||||
*/
|
||||
public static final String STATE_RUNNING = "RUNNING";
|
||||
/**
|
||||
* 执行完成
|
||||
*/
|
||||
public static final String STATE_DONE = "DONE";
|
||||
/**
|
||||
* 执行失败
|
||||
*/
|
||||
public static final String STATE_FAILED = "FAILED";
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
package org.springblade.openapi.mk.util;
|
||||
|
||||
import org.springblade.core.tool.utils.StringUtil;
|
||||
|
||||
/**
|
||||
* @author bfhuange
|
||||
* @since 2026/7/12
|
||||
*/
|
||||
public class ProcessTypeUtils {
|
||||
|
||||
/**
|
||||
* 获取流程类型
|
||||
* @param templateCode
|
||||
* @param templateCodePrefix
|
||||
* @return
|
||||
*/
|
||||
public static String getProcessType(String templateCode, String templateCodePrefix) {
|
||||
if (StringUtil.isBlank(templateCode)) {
|
||||
return templateCode;
|
||||
}
|
||||
return templateCode.replace(templateCodePrefix, "");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
server:
|
||||
port: 8108
|
||||
|
||||
spring:
|
||||
application:
|
||||
name: blade-openapi
|
||||
config:
|
||||
import:
|
||||
- nacos:blade.yaml?group=DEFAULT_GROUP&refreshEnabled=true
|
||||
- nacos:blade-${spring.profiles.active}.yaml?group=DEFAULT_GROUP&refreshEnabled=true
|
||||
- nacos:blade-openapi-dynamictp.yaml?group=DEFAULT_GROUP&refreshEnabled=true
|
||||
- optional:nacos:${spring.application.name}-${spring.profiles.active}.yaml?group=DEFAULT_GROUP&refreshEnabled=true
|
||||
- optional:classpath:openapi-lock.yaml
|
||||
cloud:
|
||||
nacos:
|
||||
username: ${NACOS_USERNAME:${NACOS_PROD_USERNAME:nacos}}
|
||||
password: ${NACOS_PASSWORD:${NACOS_PROD_PASSWORD:nacos}}
|
||||
server-addr: ${NACOS_HOST:${NACOS_PROD_HOST:127.0.0.1:8848}}
|
||||
discovery:
|
||||
namespace: "${NACOS_NAMESPACE:}"
|
||||
config:
|
||||
file-extension: yaml
|
||||
namespace: "${NACOS_NAMESPACE:}"
|
||||
datasource:
|
||||
url: ${blade.datasource.${spring.profiles.active}.url}
|
||||
username: ${blade.datasource.${spring.profiles.active}.username}
|
||||
password: ${blade.datasource.${spring.profiles.active}.password}
|
||||
@@ -1,6 +0,0 @@
|
||||
#spring:
|
||||
# cloud:
|
||||
# nacos:
|
||||
# username: nacos
|
||||
# password: ${NACOS_PASSWORD:gr30wIs5%Hi7keQj}
|
||||
# server-addr: ${NACOS_ADDR:10.38.16.127:8848}
|
||||
@@ -1,6 +0,0 @@
|
||||
#spring:
|
||||
# cloud:
|
||||
# nacos:
|
||||
# username: nacos
|
||||
# password: rWrMrVTWyf%ekjuw
|
||||
# server-addr: ${NACOS_ADDR:192.168.0.242:8848}
|
||||
@@ -1,8 +0,0 @@
|
||||
#server:
|
||||
# port: 38108
|
||||
#spring:
|
||||
# cloud:
|
||||
# nacos:
|
||||
# username: nacos
|
||||
# password: gr30wIs5%Hi7keQj
|
||||
# server-addr: ${NACOS_ADDR:10.38.16.127:8848}
|
||||
@@ -0,0 +1,9 @@
|
||||
# openapi 当前处理人刷新依赖 Redisson 分布式锁。
|
||||
# 该文件必须在 nacos blade-*.yaml 之后导入,用于覆盖全局 blade.lock.enabled=false。
|
||||
# 连接信息与业务 Redis 保持一致,避免 Redisson 因缺少密码出现 NOAUTH。
|
||||
blade:
|
||||
lock:
|
||||
enabled: true
|
||||
address: redis://${spring.data.redis.host:127.0.0.1}:${spring.data.redis.port:6379}
|
||||
password: ${spring.data.redis.password:}
|
||||
database: ${spring.data.redis.database:0}
|
||||
@@ -45,6 +45,14 @@
|
||||
<groupId>org.springblade</groupId>
|
||||
<artifactId>blade-user-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springblade</groupId>
|
||||
<artifactId>blade-resource-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springblade</groupId>
|
||||
<artifactId>blade-process-api</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springblade</groupId>
|
||||
@@ -72,8 +80,16 @@
|
||||
<groupId>org.springblade</groupId>
|
||||
<artifactId>blade-core-oauth2</artifactId>
|
||||
</dependency>
|
||||
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springblade</groupId>
|
||||
<artifactId>blade-core-launch</artifactId>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<artifactId>spring-cloud-starter-bootstrap</artifactId>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
|
||||
+91
@@ -0,0 +1,91 @@
|
||||
package org.springblade.process.controller;
|
||||
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import jakarta.validation.Valid;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.AllArgsConstructor;
|
||||
import org.springblade.core.boot.ctrl.BladeController;
|
||||
import org.springblade.core.mp.support.Condition;
|
||||
import org.springblade.core.mp.support.Query;
|
||||
import org.springblade.core.secure.utils.AuthUtil;
|
||||
import org.springblade.core.tool.api.R;
|
||||
import org.springblade.process.pojo.dto.ApprovalDTO;
|
||||
import org.springblade.process.pojo.vo.ApprovalVO;
|
||||
import org.springblade.process.pojo.vo.ProcessApprovedRecordVO;
|
||||
import org.springblade.process.service.IBusinessProcessService;
|
||||
import org.springblade.thirdparty.mk.pojo.dto.MKProcessCreateDTO;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 业务流程关联表 控制器
|
||||
*
|
||||
* @author BladeX
|
||||
* @since 2024-09-19
|
||||
*/
|
||||
@Valid
|
||||
@RestController
|
||||
@AllArgsConstructor
|
||||
@RequestMapping("businessProcess")
|
||||
@Tag(name = "业务流程关联表", description = "业务流程关联表接口")
|
||||
public class BusinessProcessController extends BladeController {
|
||||
|
||||
private final IBusinessProcessService businessProcessService;
|
||||
|
||||
/**
|
||||
* 业务流程关联表 分页
|
||||
*/
|
||||
@PostMapping("/mkList")
|
||||
@ApiOperationSupport(order = 1)
|
||||
@Operation(summary = "分页", description = "传入businessProcess")
|
||||
public R<IPage<ApprovalVO>> mkList(@Validated @RequestBody(required = false) ApprovalDTO param, Query query) {
|
||||
if (param == null) {
|
||||
param = new ApprovalDTO();
|
||||
}
|
||||
param.setLoginName(AuthUtil.getUserAccount());
|
||||
IPage<ApprovalVO> pages = businessProcessService.queryMkApprovalList(Condition.getPage(query), param);
|
||||
return R.data(pages);
|
||||
}
|
||||
|
||||
@PostMapping("/processSubmit")
|
||||
@ApiOperationSupport(order = 2)
|
||||
@Operation(summary = "提交MK审核流", description = "调用mk processSubmit,传入templateCode/submitIdentity/formInstanceId")
|
||||
public R<String> processSubmit(@Validated @RequestBody MKProcessCreateDTO param) {
|
||||
return R.data(businessProcessService.processSubmit(param));
|
||||
}
|
||||
|
||||
@GetMapping("/isEditView")
|
||||
@ApiOperationSupport(order = 3)
|
||||
@Operation(summary = "是否编辑页", description = "传入业务id")
|
||||
public R<Boolean> isEditView(@Valid @NotBlank(message = "业务id不能为空") String bizId) {
|
||||
return R.data(businessProcessService.isEditView(bizId));
|
||||
}
|
||||
|
||||
@GetMapping("/getMKApprovalUrl")
|
||||
@ApiOperationSupport(order = 4)
|
||||
@Operation(summary = "获取mk审批页链接", description = "传入业务id或流程实例id")
|
||||
public R<String> getMKApprovalUrl(String bizId, String processInstanceId) {
|
||||
return R.data(businessProcessService.getMKApprovalUrl(bizId, processInstanceId));
|
||||
}
|
||||
|
||||
@GetMapping("/getApprovedRecords")
|
||||
@ApiOperationSupport(order = 5)
|
||||
@Operation(summary = "查询审批记录", description = "传入业务id或流程实例id")
|
||||
public R<List<ProcessApprovedRecordVO>> getApprovedRecords(String bizId, String processInstanceId) {
|
||||
return R.data(businessProcessService.queryApprovedRecords(bizId, processInstanceId));
|
||||
}
|
||||
|
||||
@GetMapping("/downloadFile")
|
||||
@ApiOperationSupport(order = 6)
|
||||
@Operation(summary = "下载附件", description = "传入附件id")
|
||||
public void downloadFile(HttpServletResponse response, @Valid @NotBlank(message = "附件id不能为空") String fileId) {
|
||||
businessProcessService.downloadFile(response, fileId);
|
||||
}
|
||||
|
||||
}
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
package org.springblade.process.convert;
|
||||
|
||||
import org.mapstruct.*;
|
||||
import org.springblade.common.constant.DictTypeEnum;
|
||||
import org.springblade.core.tool.utils.StringUtil;
|
||||
import org.springblade.process.pojo.dto.ApprovalDTO;
|
||||
import org.springblade.process.pojo.vo.ApprovalVO;
|
||||
import org.springblade.system.cache.DictCache;
|
||||
import org.springblade.thirdparty.mk.pojo.dto.approval.MKProcessDTO;
|
||||
import org.springblade.thirdparty.mk.pojo.vo.MKApprovalVO;
|
||||
import org.springblade.thirdparty.mk.pojo.vo.MKProcessVO;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author bfhuange
|
||||
* @since 2025/4/3
|
||||
*/
|
||||
@Mapper(componentModel = MappingConstants.ComponentModel.SPRING, unmappedTargetPolicy = ReportingPolicy.IGNORE)
|
||||
public interface ApprovalConvert {
|
||||
|
||||
@Mapping(source = "dynamicProps.templateNameCn", target = "templateNameCn")
|
||||
@Mapping(source = "status", target = "statusStr", qualifiedByName = "statusStr")
|
||||
ApprovalVO mk2vo(MKApprovalVO vo);
|
||||
|
||||
List<ApprovalVO> mk2vos(List<MKApprovalVO> vos);
|
||||
|
||||
@Mapping(source = "creator", target = "applicantName")
|
||||
@Mapping(source = "currentNode", target = "nodeName")
|
||||
@Mapping(source = "templateName", target = "templateNameCn")
|
||||
@Mapping(source = "createTime", target = "startTime")
|
||||
ApprovalVO mk2vo(MKProcessVO vo);
|
||||
|
||||
List<ApprovalVO> mkProcess2vos(List<MKProcessVO> vos);
|
||||
|
||||
@Mapping(source = "docType", target = "mydoc")
|
||||
@Mapping(source = "applicantTimeStart", target = "createBeginTime", qualifiedByName = "date2long")
|
||||
@Mapping(source = "applicantTimeEnd", target = "createEndTime", qualifiedByName = "date2long")
|
||||
MKProcessDTO dto2mk(ApprovalDTO dto);
|
||||
|
||||
@Named("statusStr")
|
||||
default String statusStr(String status) {
|
||||
return StringUtil.isBlank(status) ? "" : DictCache.getValue(DictTypeEnum.MK_STATUS.getType(), status);
|
||||
}
|
||||
|
||||
@Named("date2long")
|
||||
default Long date2long(Date date) {
|
||||
return date == null ? null : date.getTime();
|
||||
}
|
||||
}
|
||||
+84
@@ -0,0 +1,84 @@
|
||||
package org.springblade.process.convert;
|
||||
|
||||
import org.mapstruct.*;
|
||||
import org.springblade.common.constant.DictTypeEnum;
|
||||
import org.springblade.core.tool.utils.CollectionUtil;
|
||||
import org.springblade.core.tool.utils.StringUtil;
|
||||
import org.springblade.process.pojo.dto.AdditionOperationParameterDTO;
|
||||
import org.springblade.process.pojo.dto.BusinessProcessSubmitDTO;
|
||||
import org.springblade.process.pojo.dto.ProcessExecuteDTO;
|
||||
import org.springblade.process.pojo.entity.BusinessProcess;
|
||||
import org.springblade.process.pojo.vo.BusinessProcessListVO;
|
||||
import org.springblade.process.pojo.vo.ProcessApprovedRecordVO;
|
||||
import org.springblade.process.pojo.vo.ProcessAttachmentVO;
|
||||
import org.springblade.process.pojo.vo.ProcessCommentVO;
|
||||
import org.springblade.system.cache.DictCache;
|
||||
import org.springblade.thirdparty.mk.constant.MKConstant;
|
||||
import org.springblade.thirdparty.mk.pojo.dto.MKAdditionOperationParameterDTO;
|
||||
import org.springblade.thirdparty.mk.pojo.dto.MKProcessExecuteDTO;
|
||||
import org.springblade.thirdparty.mk.pojo.vo.MKAttachmentVO;
|
||||
import org.springblade.thirdparty.mk.pojo.vo.MKAuditNoteVO;
|
||||
import org.springblade.thirdparty.mk.pojo.vo.MKProcessCommentVO;
|
||||
import org.springblade.thirdparty.mk.pojo.vo.MKUserOrgVO;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* @author bfhuange
|
||||
* @date 2024/9/19
|
||||
*/
|
||||
@Mapper(componentModel = MappingConstants.ComponentModel.SPRING, unmappedTargetPolicy = ReportingPolicy.IGNORE)
|
||||
public interface BusinessProcessConvert {
|
||||
|
||||
BusinessProcess dto2entity(BusinessProcessSubmitDTO<?> dto);
|
||||
|
||||
MKProcessExecuteDTO dto2mk(ProcessExecuteDTO dto);
|
||||
|
||||
MKAdditionOperationParameterDTO dto2mk(AdditionOperationParameterDTO dto);
|
||||
|
||||
ProcessApprovedRecordVO mk2vo(MKAuditNoteVO vo);
|
||||
|
||||
ProcessAttachmentVO mk2vo(MKAttachmentVO vo);
|
||||
|
||||
List<ProcessAttachmentVO> attachments2vos(List<MKAttachmentVO> vos);
|
||||
|
||||
@Mapping(source = "userOrgInfo", target = "userName", qualifiedByName = "userName")
|
||||
ProcessCommentVO mk2vo(MKProcessCommentVO vo);
|
||||
|
||||
List<ProcessCommentVO> comments2vos(List<MKProcessCommentVO> vos);
|
||||
|
||||
default List<ProcessApprovedRecordVO> auditNotes2vos(List<MKAuditNoteVO> vos, Function<ProcessApprovedRecordVO, List<String>> senderFunction) {
|
||||
if (CollectionUtil.isEmpty(vos)) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
return vos.stream()
|
||||
.map(auditNote -> {
|
||||
ProcessApprovedRecordVO record = this.mk2vo(auditNote);
|
||||
if (record != null && MKConstant.NODE_TYPE_SEND.equals(record.getNodeType())) {
|
||||
// 抄送节点,查询抄送人员
|
||||
record.setSenders(senderFunction.apply(record));
|
||||
}
|
||||
return record;
|
||||
}).toList();
|
||||
}
|
||||
|
||||
default void handleDict(BusinessProcessListVO vo) {
|
||||
if (vo == null) {
|
||||
return;
|
||||
}
|
||||
String processTypeStr = StringUtil.isBlank(vo.getProcessType()) ? "" : DictCache.getValue(DictTypeEnum.PROCESS_TYPE.getType(), vo.getProcessType());
|
||||
vo.setProcessTypeStr(processTypeStr);
|
||||
String approveStatusStr = StringUtil.isBlank(vo.getApproveStatus()) ? "" : DictCache.getValue(DictTypeEnum.APPROVE_STATUS.getType(), vo.getApproveStatus());
|
||||
vo.setApproveStatusStr(approveStatusStr);
|
||||
}
|
||||
|
||||
@Named("userName")
|
||||
default String userName(MKUserOrgVO userOrgInfo) {
|
||||
return Optional.ofNullable(userOrgInfo)
|
||||
.map(MKUserOrgVO::getName)
|
||||
.orElse(null);
|
||||
}
|
||||
}
|
||||
+149
@@ -0,0 +1,149 @@
|
||||
package org.springblade.process.convert;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.springblade.process.pojo.dto.ApprovalDTO;
|
||||
import org.springblade.thirdparty.mk.pojo.dto.approval.MKApprovalConditionDTO;
|
||||
import org.springblade.thirdparty.mk.pojo.dto.approval.MKConditionDTO;
|
||||
import org.springblade.thirdparty.mk.pojo.dto.approval.MKConditionDTO.MKConditionDTOBuilder;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* @author bfhuange
|
||||
* @since 2025/4/3
|
||||
*/
|
||||
@Getter
|
||||
public enum MKApprovalConvert {
|
||||
/**
|
||||
* 单据类型
|
||||
*/
|
||||
DOC_TYPE(MKApprovalConditionDTO::setMydoc, compose(MKConditionDTOBuilder::eq, ApprovalDTO::getDocType)),
|
||||
/**
|
||||
* 关键字
|
||||
*/
|
||||
KEYWORD((condition, keyword) -> {
|
||||
if (condition.getKeyword() == null) {
|
||||
condition.setKeyword(new ArrayList<>());
|
||||
}
|
||||
condition.getKeyword().add(keyword);
|
||||
}, compose(MKConditionDTOBuilder::eq, ApprovalDTO::getKeyword)),
|
||||
/**
|
||||
* 模板名称
|
||||
*/
|
||||
TEMPLATE_NAME(MKApprovalConditionDTO::setTemplateName, compose(MKConditionDTOBuilder::contains, ApprovalDTO::getTemplateName)),
|
||||
/**
|
||||
* 发起时间
|
||||
*/
|
||||
START_TIME(MKApprovalConditionDTO::setStartTime,
|
||||
compose(MKConditionDTOBuilder::gte, getGetter(ApprovalDTO::getApplicantTimeStart)),
|
||||
compose(MKConditionDTOBuilder::lte, getGetter(ApprovalDTO::getApplicantTimeEnd))
|
||||
),
|
||||
/**
|
||||
* 接收时间
|
||||
*/
|
||||
RECEIVE_TIME(MKApprovalConditionDTO::setReceiveTime,
|
||||
compose(MKConditionDTOBuilder::gte, getGetter(ApprovalDTO::getReceiveTimeStart)),
|
||||
compose(MKConditionDTOBuilder::lte, getGetter(ApprovalDTO::getReceiveTimeEnd))
|
||||
),
|
||||
/**
|
||||
* 流程状态
|
||||
*/
|
||||
STATUS(MKApprovalConditionDTO::setStatus, compose(MKConditionDTOBuilder::eq, ApprovalDTO::getStatus)),
|
||||
/**
|
||||
* 结束时间
|
||||
*/
|
||||
FINISH_TIME(MKApprovalConditionDTO::setFinishTime,
|
||||
compose(MKConditionDTOBuilder::gte, getGetter(ApprovalDTO::getFinishTimeStart)),
|
||||
compose(MKConditionDTOBuilder::lte, getGetter(ApprovalDTO::getFinishTimeEnd))
|
||||
),
|
||||
/**
|
||||
* 最后处理时间
|
||||
*/
|
||||
LAST_HANDLE_TIME(MKApprovalConditionDTO::setLastHandleTime,
|
||||
compose(MKConditionDTOBuilder::gte, getGetter(ApprovalDTO::getLastHandleStart)),
|
||||
compose(MKConditionDTOBuilder::lte, getGetter(ApprovalDTO::getLastHandleEnd))
|
||||
),
|
||||
/**
|
||||
* 阅读时间
|
||||
*/
|
||||
READ_TIME(MKApprovalConditionDTO::setReadTime,
|
||||
compose(MKConditionDTOBuilder::gte, getGetter(ApprovalDTO::getReadTimeStart)),
|
||||
compose(MKConditionDTOBuilder::lte, getGetter(ApprovalDTO::getReadTimeEnd))
|
||||
),
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
CREATE_TIME(MKApprovalConditionDTO::setCreateTime,
|
||||
compose(MKConditionDTOBuilder::gte, getGetter(ApprovalDTO::getCreateTimeStart)),
|
||||
compose(MKConditionDTOBuilder::lte, getGetter(ApprovalDTO::getCreateTimeEnd))
|
||||
),
|
||||
;
|
||||
/**
|
||||
* 最终设置条件方法
|
||||
*/
|
||||
private final BiConsumer<MKApprovalConditionDTO, MKConditionDTO> setter;
|
||||
/**
|
||||
* 组合参数
|
||||
*/
|
||||
private final List<Compose> composes;
|
||||
|
||||
MKApprovalConvert(BiConsumer<MKApprovalConditionDTO, MKConditionDTO> setter, Compose... compose) {
|
||||
this.setter = setter;
|
||||
this.composes = List.of(compose);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取时间戳
|
||||
* @param date
|
||||
* @return
|
||||
*/
|
||||
private static String getTimestamp(Date date) {
|
||||
return Optional.ofNullable(date)
|
||||
.map(e -> String.valueOf(e.getTime()))
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* date 转 string
|
||||
* @param dateGetter
|
||||
* @return
|
||||
*/
|
||||
private static Function<ApprovalDTO, String> getGetter(Function<ApprovalDTO, Date> dateGetter) {
|
||||
return approvalDTO -> getTimestamp(dateGetter.apply(approvalDTO));
|
||||
}
|
||||
|
||||
/**
|
||||
* 工厂方法
|
||||
* @param builderSetter
|
||||
* @param getter
|
||||
* @return
|
||||
*/
|
||||
private static Compose compose(BiConsumer<MKConditionDTOBuilder, String> builderSetter, Function<ApprovalDTO, String> getter) {
|
||||
return new Compose(builderSetter, getter);
|
||||
}
|
||||
|
||||
/**
|
||||
* 组合参数,条件和取值
|
||||
*/
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@Data
|
||||
public static class Compose {
|
||||
/**
|
||||
* 条件builder的setter
|
||||
*/
|
||||
private BiConsumer<MKConditionDTOBuilder, String> builderSetter;
|
||||
/**
|
||||
* 从参数取值
|
||||
*/
|
||||
private Function<ApprovalDTO, String> getter;
|
||||
}
|
||||
}
|
||||
+89
@@ -0,0 +1,89 @@
|
||||
package org.springblade.process.feign;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Hidden;
|
||||
import jakarta.validation.Valid;
|
||||
import lombok.AllArgsConstructor;
|
||||
import org.springblade.core.secure.annotation.PreAuth;
|
||||
import org.springblade.core.secure.constant.AuthConstant;
|
||||
import org.springblade.core.tool.api.FR;
|
||||
import org.springblade.process.pojo.dto.BusinessProcessCurrentHandlerRefreshDTO;
|
||||
import org.springblade.process.pojo.dto.BusinessProcessDeleteDTO;
|
||||
import org.springblade.process.pojo.dto.BusinessProcessSubmitDTO;
|
||||
import org.springblade.process.pojo.dto.BusinessProcessUpdateDTO;
|
||||
import org.springblade.process.pojo.vo.BusinessProcessVO;
|
||||
import org.springblade.process.pojo.vo.ProcessApprovedRecordVO;
|
||||
import org.springblade.process.pojo.vo.ProcessTodoVO;
|
||||
import org.springblade.process.service.IBusinessProcessService;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 业务流程关联表 Feign实现类
|
||||
*
|
||||
* @author BladeX
|
||||
* @since 2024-09-19
|
||||
*/
|
||||
@Valid
|
||||
@Hidden
|
||||
@RestController
|
||||
@AllArgsConstructor
|
||||
public class BusinessProcessClient implements IBusinessProcessClient {
|
||||
|
||||
private final IBusinessProcessService businessProcessService;
|
||||
|
||||
@PostMapping(SUBMIT_BUSINESS_PROCESS)
|
||||
@Override
|
||||
public FR<BusinessProcessVO> submitBusinessProcess(@Validated @RequestBody BusinessProcessSubmitDTO<?> param) {
|
||||
return FR.data(businessProcessService.submitBusinessProcess(param));
|
||||
}
|
||||
|
||||
@Override
|
||||
public FR<String> updateBusinessProcessStatus(@Validated @RequestBody BusinessProcessUpdateDTO param) {
|
||||
return FR.data(businessProcessService.updateBusinessProcessStatus(param));
|
||||
}
|
||||
|
||||
@Override
|
||||
public FR<BusinessProcessVO> updateBusinessProcessApprover(@Validated @RequestBody BusinessProcessUpdateDTO param) {
|
||||
return FR.data(businessProcessService.updateBusinessProcessApprover(param));
|
||||
}
|
||||
|
||||
@Override
|
||||
public FR<BusinessProcessVO> refreshBusinessProcessCurrentHandlers(@Validated @RequestBody BusinessProcessCurrentHandlerRefreshDTO param) {
|
||||
return FR.data(businessProcessService.refreshBusinessProcessCurrentHandlers(param));
|
||||
}
|
||||
|
||||
@Override
|
||||
public FR<List<ProcessTodoVO>> queryTodoList(String processInstanceId) {
|
||||
return FR.data(businessProcessService.queryTodoList(processInstanceId));
|
||||
}
|
||||
|
||||
@Override
|
||||
public FR<BusinessProcessVO> queryBusinessProcessSnapshot(String processInstanceId) {
|
||||
return FR.data(businessProcessService.queryBusinessProcessSnapshot(processInstanceId));
|
||||
}
|
||||
|
||||
@PostMapping(DELETE_BUSINESS_PROCESS)
|
||||
@Override
|
||||
public FR<Boolean> deleteBusinessProcess(@Validated @RequestBody BusinessProcessDeleteDTO param) {
|
||||
return FR.data(businessProcessService.deleteBusinessProcess(param));
|
||||
}
|
||||
|
||||
@Override
|
||||
public FR<List<ProcessApprovedRecordVO>> queryApprovedRecordsNoAttachments(String bizId, String processInstanceId) {
|
||||
return FR.data(businessProcessService.queryApprovedRecordsNoAttachments(bizId, processInstanceId));
|
||||
}
|
||||
|
||||
@PreAuth(AuthConstant.PERMIT_ALL)
|
||||
@GetMapping(GET_CURRENT_NODES)
|
||||
@Override
|
||||
public FR<Object> getCurrentNodes(@RequestParam("processInstanceId") String processInstanceId,
|
||||
@RequestParam(value = "loginName", required = false) String loginName) {
|
||||
return FR.data(businessProcessService.getCurrentNodes(processInstanceId, loginName));
|
||||
}
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
package org.springblade.process.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.springblade.process.pojo.entity.BusinessProcess;
|
||||
|
||||
/**
|
||||
* 业务流程关联表 Mapper 接口
|
||||
*
|
||||
* @author BladeX
|
||||
* @since 2024-09-19
|
||||
*/
|
||||
public interface BusinessProcessMapper extends BaseMapper<BusinessProcess> {
|
||||
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
<?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.springblade.process.mapper.BusinessProcessMapper">
|
||||
|
||||
<!-- 通用查询映射结果 -->
|
||||
<resultMap id="businessProcessResultMap" type="org.springblade.process.pojo.entity.BusinessProcess">
|
||||
<result column="id" property="id"/>
|
||||
<result column="biz_id" property="bizId"/>
|
||||
<result column="process_instance_id" property="processInstanceId"/>
|
||||
<result column="process_type" property="processType"/>
|
||||
<result column="doc_code" property="docCode"/>
|
||||
<result column="subject" property="subject"/>
|
||||
<result column="promoter_id" property="promoterId"/>
|
||||
<result column="promoter_name" property="promoterName"/>
|
||||
<result column="promoter_login_name" property="promoterLoginName"/>
|
||||
<result column="submit_time" property="submitTime"/>
|
||||
<result column="complete_time" property="completeTime"/>
|
||||
<result column="current_node_ids" property="currentNodeIds"/>
|
||||
<result column="current_node_names" property="currentNodeNames"/>
|
||||
<result column="current_handlers" property="currentHandlers"/>
|
||||
<result column="receive_time" property="receiveTime"/>
|
||||
<result column="is_completed" property="isCompleted"/>
|
||||
<result column="approve_status" property="approveStatus"/>
|
||||
<result column="tenant_id" property="tenantId"/>
|
||||
<result column="create_time" property="createTime"/>
|
||||
<result column="update_time" property="updateTime"/>
|
||||
</resultMap>
|
||||
|
||||
</mapper>
|
||||
+133
@@ -0,0 +1,133 @@
|
||||
package org.springblade.process.service;
|
||||
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.springblade.process.pojo.dto.*;
|
||||
import org.springblade.process.pojo.entity.BusinessProcess;
|
||||
import org.springblade.process.pojo.vo.*;
|
||||
import org.springblade.thirdparty.mk.pojo.dto.MKProcessCreateDTO;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 业务流程关联表 服务类
|
||||
*
|
||||
* @author BladeX
|
||||
* @since 2024-09-19
|
||||
*/
|
||||
public interface IBusinessProcessService extends IService<BusinessProcess> {
|
||||
|
||||
/**
|
||||
* 提交业务流程
|
||||
*
|
||||
* @param param
|
||||
* @return
|
||||
*/
|
||||
BusinessProcessVO submitBusinessProcess(BusinessProcessSubmitDTO<?> param);
|
||||
|
||||
/**
|
||||
* 直接调用 MK processSubmit 提交流程
|
||||
*
|
||||
* @param param MK 流程创建参数
|
||||
* @return 流程实例 id
|
||||
*/
|
||||
String processSubmit(MKProcessCreateDTO param);
|
||||
|
||||
/**
|
||||
* 获取流程当前节点详情
|
||||
*
|
||||
* @param processInstanceId 流程实例id
|
||||
* @param loginName MK登录名(手机号),可为空,为空时按流程发起人解析
|
||||
* @return 当前节点列表
|
||||
*/
|
||||
List<?> getCurrentNodes(String processInstanceId, String loginName);
|
||||
|
||||
/**
|
||||
* 修改业务流程状态
|
||||
* @param param
|
||||
* @return
|
||||
*/
|
||||
String updateBusinessProcessStatus(BusinessProcessUpdateDTO param);
|
||||
|
||||
/**
|
||||
* 修改业务流程审批人
|
||||
* @param param
|
||||
* @return
|
||||
*/
|
||||
BusinessProcessVO updateBusinessProcessApprover(BusinessProcessUpdateDTO param);
|
||||
|
||||
/**
|
||||
* 只刷新当前节点和当前处理人
|
||||
* @param param
|
||||
* @return
|
||||
*/
|
||||
BusinessProcessVO refreshBusinessProcessCurrentHandlers(BusinessProcessCurrentHandlerRefreshDTO param);
|
||||
|
||||
/**
|
||||
* 查询业务流程当前快照
|
||||
* @param processInstanceId
|
||||
* @return
|
||||
*/
|
||||
BusinessProcessVO queryBusinessProcessSnapshot(String processInstanceId);
|
||||
|
||||
/**
|
||||
* 是否编辑页
|
||||
* @param bizId
|
||||
* @return
|
||||
*/
|
||||
boolean isEditView(String bizId);
|
||||
|
||||
/**
|
||||
* 获取mk审批页面链接,业务id和流程实例id任意一个即可
|
||||
* @param bizId 业务id
|
||||
* @param processInstanceId 流程实例id
|
||||
* @return
|
||||
*/
|
||||
String getMKApprovalUrl(String bizId, String processInstanceId);
|
||||
|
||||
/**
|
||||
* 删除业务流程
|
||||
* @param param
|
||||
* @return
|
||||
*/
|
||||
boolean deleteBusinessProcess(BusinessProcessDeleteDTO param);
|
||||
|
||||
/**
|
||||
* 查询流程审批记录
|
||||
* @param bizId
|
||||
* @param processInstanceId
|
||||
* @return
|
||||
*/
|
||||
List<ProcessApprovedRecordVO> queryApprovedRecords(String bizId, String processInstanceId);
|
||||
|
||||
/**
|
||||
* 查询流程审批记录不处理附件
|
||||
* @param bizId
|
||||
* @param processInstanceId
|
||||
* @return
|
||||
*/
|
||||
List<ProcessApprovedRecordVO> queryApprovedRecordsNoAttachments(String bizId, String processInstanceId);
|
||||
|
||||
/**
|
||||
* 下载文件
|
||||
* @param response
|
||||
* @param fileId
|
||||
*/
|
||||
void downloadFile(HttpServletResponse response, String fileId);
|
||||
|
||||
/**
|
||||
* 查询业务流程当前处理人
|
||||
* @param processInstanceId
|
||||
* @return
|
||||
*/
|
||||
List<ProcessTodoVO> queryTodoList(String processInstanceId);
|
||||
|
||||
/**
|
||||
* 查询mk审批记录
|
||||
* @param page
|
||||
* @param param
|
||||
* @return
|
||||
*/
|
||||
IPage<ApprovalVO> queryMkApprovalList(IPage<ApprovalVO> page, ApprovalDTO param);
|
||||
}
|
||||
+826
@@ -0,0 +1,826 @@
|
||||
package org.springblade.process.service.impl;
|
||||
|
||||
import cn.hutool.core.collection.CollectionUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springblade.process.pojo.dto.*;
|
||||
import org.springblade.process.pojo.enums.ApproveStatusEnum;
|
||||
import org.springblade.core.log.exception.ServiceException;
|
||||
import org.springblade.core.log.utils.AssertUtils;
|
||||
import org.springblade.core.secure.utils.AuthUtil;
|
||||
import org.springblade.core.tool.utils.StringUtil;
|
||||
import org.springblade.process.convert.ApprovalConvert;
|
||||
import org.springblade.process.convert.BusinessProcessConvert;
|
||||
import org.springblade.process.convert.MKApprovalConvert;
|
||||
import org.springblade.process.mapper.BusinessProcessMapper;
|
||||
import org.springblade.process.pojo.entity.BusinessProcess;
|
||||
import org.springblade.process.pojo.enums.TodoStatus;
|
||||
import org.springblade.process.pojo.vo.*;
|
||||
import org.springblade.process.service.IBusinessProcessService;
|
||||
import org.springblade.system.pojo.entity.User;
|
||||
import org.springblade.system.service.IUserService;
|
||||
import org.springblade.thirdparty.mk.config.MKProperties;
|
||||
import org.springblade.thirdparty.mk.constant.MKConstant;
|
||||
import org.springblade.thirdparty.mk.constant.MKDoc;
|
||||
import org.springblade.thirdparty.mk.exception.MKException;
|
||||
import org.springblade.thirdparty.mk.pojo.dto.MKAuditNoteDTO;
|
||||
import org.springblade.thirdparty.mk.pojo.dto.MKProcessCreateDTO;
|
||||
import org.springblade.thirdparty.mk.pojo.dto.MKProcessExecuteDTO;
|
||||
import org.springblade.thirdparty.mk.pojo.dto.MKSenderDTO;
|
||||
import org.springblade.thirdparty.mk.pojo.dto.approval.MKApprovalConditionDTO;
|
||||
import org.springblade.thirdparty.mk.pojo.dto.approval.MKApprovalDTO;
|
||||
import org.springblade.thirdparty.mk.pojo.dto.approval.MKConditionDTO;
|
||||
import org.springblade.thirdparty.mk.pojo.dto.approval.MKProcessDTO;
|
||||
import org.springblade.thirdparty.mk.pojo.dto.sort.*;
|
||||
import org.springblade.thirdparty.mk.pojo.vo.*;
|
||||
import org.springblade.thirdparty.mk.service.IMKService;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 业务流程关联表 服务实现类
|
||||
*
|
||||
* @author BladeX
|
||||
* @since 2024-09-19
|
||||
*/
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
@Service
|
||||
public class BusinessProcessServiceImpl extends ServiceImpl<BusinessProcessMapper, BusinessProcess> implements IBusinessProcessService {
|
||||
|
||||
private final BusinessProcessConvert convert;
|
||||
private final IMKService mkService;
|
||||
private final MKProperties mkProperties;
|
||||
private final ApprovalConvert approvalConvert;
|
||||
private final IUserService userService;
|
||||
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
@Override
|
||||
public BusinessProcessVO submitBusinessProcess(BusinessProcessSubmitDTO<?> param) {
|
||||
if (param == null) {
|
||||
log.warn("提交业务流程参数为空");
|
||||
return null;
|
||||
}
|
||||
Long bizId = param.getBizId();
|
||||
if (bizId == null) {
|
||||
log.warn("提交业务流程业务id为空");
|
||||
return null;
|
||||
}
|
||||
log.info("提交业务流程参数:{}", JSON.toJSONString(param));
|
||||
BusinessProcess businessProcess = this.getOne(Wrappers.<BusinessProcess>lambdaQuery()
|
||||
.eq(BusinessProcess::getBizId, bizId)
|
||||
);
|
||||
if (businessProcess == null) {
|
||||
businessProcess = convert.dto2entity(param);
|
||||
}
|
||||
// 设置发起人
|
||||
if (businessProcess.getPromoterId() == null) {
|
||||
businessProcess.setPromoterId(AuthUtil.getUserId());
|
||||
}
|
||||
if (businessProcess.getPromoterName() == null) {
|
||||
businessProcess.setPromoterName(AuthUtil.getNickName());
|
||||
}
|
||||
if (businessProcess.getPromoterLoginName() == null) {
|
||||
businessProcess.setPromoterLoginName(AuthUtil.getUserName());
|
||||
param.setPromoterLoginName(AuthUtil.getUserName());
|
||||
}
|
||||
// 设置提交时间
|
||||
if (businessProcess.getSubmitTime() == null) {
|
||||
businessProcess.setSubmitTime(new Date());
|
||||
}
|
||||
// 1.提交流程
|
||||
long start = System.currentTimeMillis();
|
||||
log.info("提交流程开始");
|
||||
String processInstanceId = submitMKProcess(param);
|
||||
long end = System.currentTimeMillis();
|
||||
log.info("提交流程结束 耗时:{}", end - start);
|
||||
businessProcess.setProcessInstanceId(processInstanceId);
|
||||
// 提交是审批中状态
|
||||
businessProcess.setApproveStatus(ApproveStatusEnum.APPROVING.getValue());
|
||||
// 2.保存业务流程
|
||||
this.saveOrUpdate(businessProcess);
|
||||
|
||||
// 3.查询当前节点
|
||||
BusinessProcessVO businessProcessVO = new BusinessProcessVO();
|
||||
businessProcessVO.setProcessInstanceId(processInstanceId);
|
||||
return businessProcessVO;
|
||||
}
|
||||
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
@Override
|
||||
public String processSubmit(MKProcessCreateDTO param) {
|
||||
AssertUtils.notNull(param, "提交流程参数不能为空");
|
||||
AssertUtils.notBlank(param.getFormInstanceId(), "表单实例id不能为空");
|
||||
AssertUtils.notBlank(param.getTemplateCode(), "模板编码不能为空");
|
||||
// 前端拿到的手机号可能经 @Sensitive 脱敏(如 137****8880),这里从库取真实手机号覆盖
|
||||
String loginName = resolveCurrentUserPhone();
|
||||
AssertUtils.notBlank(loginName, "当前用户手机号为空,无法提交审核流");
|
||||
param.setSubmitIdentity(loginName);
|
||||
param.setLoginName(loginName);
|
||||
log.info("调用mk processSubmit 参数:{}", JSON.toJSONString(param));
|
||||
String processInstanceId = mkService.processSubmit(param);
|
||||
AssertUtils.notBlank(processInstanceId, "提交流程失败,未返回流程实例id");
|
||||
|
||||
Long bizId;
|
||||
try {
|
||||
bizId = Long.valueOf(param.getFormInstanceId());
|
||||
} catch (NumberFormatException e) {
|
||||
throw new ServiceException("表单实例id格式不正确");
|
||||
}
|
||||
BusinessProcess businessProcess = this.getOne(Wrappers.<BusinessProcess>lambdaQuery()
|
||||
.eq(BusinessProcess::getBizId, bizId)
|
||||
);
|
||||
if (businessProcess == null) {
|
||||
businessProcess = new BusinessProcess();
|
||||
businessProcess.setBizId(bizId);
|
||||
businessProcess.setProcessType(param.getTemplateCode());
|
||||
businessProcess.setSubject(param.getSubject());
|
||||
businessProcess.setPromoterId(AuthUtil.getUserId());
|
||||
businessProcess.setPromoterName(AuthUtil.getNickName());
|
||||
businessProcess.setPromoterLoginName(loginName);
|
||||
businessProcess.setSubmitTime(new Date());
|
||||
} else {
|
||||
businessProcess.setProcessType(param.getTemplateCode());
|
||||
if (StringUtil.isNotBlank(param.getSubject())) {
|
||||
businessProcess.setSubject(param.getSubject());
|
||||
}
|
||||
businessProcess.setPromoterLoginName(loginName);
|
||||
if (businessProcess.getSubmitTime() == null) {
|
||||
businessProcess.setSubmitTime(new Date());
|
||||
}
|
||||
}
|
||||
businessProcess.setProcessInstanceId(processInstanceId);
|
||||
businessProcess.setApproveStatus(ApproveStatusEnum.APPROVING.getValue());
|
||||
this.saveOrUpdate(businessProcess);
|
||||
this.getCurrentNodes(processInstanceId, loginName);
|
||||
return processInstanceId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<?> getCurrentNodes(String processInstanceId, String loginName) {
|
||||
if (StringUtils.isBlank(processInstanceId)) {
|
||||
log.warn("查询当前节点失败,processInstanceId为空");
|
||||
return Collections.emptyList();
|
||||
}
|
||||
String resolvedLoginName = loginName;
|
||||
if (StringUtils.isBlank(resolvedLoginName)) {
|
||||
BusinessProcess businessProcess = this.getBusinessProcessByProcessInstanceId(processInstanceId);
|
||||
resolvedLoginName = resolvePromoterLoginName(businessProcess, null);
|
||||
}
|
||||
if (StringUtils.isBlank(resolvedLoginName)) {
|
||||
log.warn("查询当前节点失败,loginName为空 processInstanceId={}", processInstanceId);
|
||||
return Collections.emptyList();
|
||||
}
|
||||
try {
|
||||
List<MKNodeVO> currentNodes = mkService.getCurrentNodes(processInstanceId, resolvedLoginName);
|
||||
log.info("获取流程当前节点详情 processInstanceId={} loginName={} result={}",
|
||||
processInstanceId, resolvedLoginName, JSON.toJSONString(currentNodes));
|
||||
return currentNodes == null ? Collections.emptyList() : currentNodes;
|
||||
} catch (Exception e) {
|
||||
log.error("查询当前节点异常 processInstanceId={} loginName={}", processInstanceId, resolvedLoginName, e);
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从当前登录用户实体读取真实手机号(绕过接口返回脱敏)
|
||||
*/
|
||||
private String resolveCurrentUserPhone() {
|
||||
Long userId = AuthUtil.getUserId();
|
||||
if (userId != null) {
|
||||
User user = userService.getById(userId);
|
||||
if (user != null && StringUtil.isNotBlank(user.getPhone()) && !user.getPhone().contains("*")) {
|
||||
return user.getPhone().trim();
|
||||
}
|
||||
if (user != null && StringUtil.isNotBlank(user.getAccount()) && user.getAccount().matches("^1\\d{10}$")) {
|
||||
return user.getAccount().trim();
|
||||
}
|
||||
}
|
||||
String account = AuthUtil.getUserAccount();
|
||||
if (StringUtil.isNotBlank(account) && account.matches("^1\\d{10}$")) {
|
||||
return account.trim();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
@Override
|
||||
public String updateBusinessProcessStatus(BusinessProcessUpdateDTO param) {
|
||||
AssertUtils.notNull(param, "参数不能为空");
|
||||
BusinessProcess businessProcess = this.getOne(Wrappers.<BusinessProcess>lambdaQuery()
|
||||
.eq(BusinessProcess::getProcessInstanceId, param.getProcessInstanceId())
|
||||
);
|
||||
AssertUtils.notNull(businessProcess, "流程实例不存在");
|
||||
// if (StringUtils.isNotBlank(approveStatus) && !rejectAfterPass(approveStatus, operationNodeNumber)) {
|
||||
if (StringUtils.isNotBlank(param.getApproveStatus()) && updateApproveStatus(param.getApproveStatus(), param.getRejectNodeId())) {
|
||||
// 审批状态不为空且需要修改审批状态
|
||||
BusinessProcess updateParam = new BusinessProcess();
|
||||
updateParam.setId(businessProcess.getId());
|
||||
updateParam.setApproveStatus(param.getApproveStatus());
|
||||
boolean update = this.updateById(updateParam);
|
||||
return update ? param.getApproveStatus() : null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
@Override
|
||||
public BusinessProcessVO updateBusinessProcessApprover(BusinessProcessUpdateDTO param) {
|
||||
BusinessProcess businessProcess = this.getBusinessProcessByProcessInstanceId(param.getProcessInstanceId());
|
||||
String promoterLoginName = resolvePromoterLoginName(businessProcess, param.getPromoterLoginName());
|
||||
if (StringUtils.isBlank(promoterLoginName)) {
|
||||
log.warn("修改业务流程审批人失败,发起人登录名为空,流程实例id:{}", param.getProcessInstanceId());
|
||||
return null;
|
||||
}
|
||||
BusinessProcessVO businessProcessVO = this.refreshBusinessProcessCurrentHandlers(param);
|
||||
if (businessProcessVO == null) {
|
||||
return null;
|
||||
}
|
||||
// 再补历史已办逻辑,兼容旧代码
|
||||
if (param.getOperationNodeId() != null && !MKConstant.DAFTER_NODE_ID.equals(param.getOperationNodeId())) {
|
||||
MKAllHandlerVO nodeHandlers = mkService.getNodeHandlers(param.getProcessInstanceId(), promoterLoginName, param.getOperationNodeId());
|
||||
this.handleNodeHandlers(param.getProcessInstanceId(), nodeHandlers, param);
|
||||
}
|
||||
return businessProcessVO;
|
||||
}
|
||||
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
@Override
|
||||
public BusinessProcessVO refreshBusinessProcessCurrentHandlers(BusinessProcessCurrentHandlerRefreshDTO param) {
|
||||
if (param == null) {
|
||||
log.warn("刷新业务流程当前处理人参数为空");
|
||||
return null;
|
||||
}
|
||||
BusinessProcess businessProcess = this.getBusinessProcessByProcessInstanceId(param.getProcessInstanceId());
|
||||
String promoterLoginName = resolvePromoterLoginName(businessProcess, param.getPromoterLoginName());
|
||||
if (StringUtils.isBlank(promoterLoginName)) {
|
||||
log.warn("刷新业务流程当前处理人失败,发起人登录名为空,流程实例id:{}", param.getProcessInstanceId());
|
||||
return null;
|
||||
}
|
||||
Long businessProcessId = Optional.ofNullable(businessProcess).map(BusinessProcess::getId).orElse(null);
|
||||
// 1. 查询当前节点
|
||||
BusinessProcessVO businessProcessVO = new BusinessProcessVO();
|
||||
businessProcessVO.setProcessInstanceId(param.getProcessInstanceId());
|
||||
List<MKNodeVO> currentNodes = mkService.getCurrentNodes(param.getProcessInstanceId(), promoterLoginName);
|
||||
// 处理当前节点信息
|
||||
BusinessProcess updateBusinessProcess = this.handleCurrentNodes(businessProcessId, param.getProcessInstanceId(), param.isComplete(), currentNodes, businessProcessVO);
|
||||
if (updateBusinessProcess != null) {
|
||||
// 设置当前处理人、当前节点、接收时间
|
||||
businessProcessVO.setCurrentHandlers(updateBusinessProcess.getCurrentHandlers());
|
||||
businessProcessVO.setCurrentNodeIds(updateBusinessProcess.getCurrentNodeIds());
|
||||
businessProcessVO.setCurrentNodeNames(updateBusinessProcess.getCurrentNodeNames());
|
||||
businessProcessVO.setReceiveTime(updateBusinessProcess.getReceiveTime());
|
||||
}
|
||||
return businessProcessVO;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BusinessProcessVO queryBusinessProcessSnapshot(String processInstanceId) {
|
||||
AssertUtils.notBlank(processInstanceId, "流程实例id不能为空");
|
||||
BusinessProcess businessProcess = this.getBusinessProcessByProcessInstanceId(processInstanceId);
|
||||
AssertUtils.notNull(businessProcess, "业务流程不存在");
|
||||
return this.buildBusinessProcessVO(businessProcess);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEditView(String bizId) {
|
||||
if (StringUtils.isBlank(bizId)) {
|
||||
log.warn("查询是否编辑页,业务id为空");
|
||||
return false;
|
||||
}
|
||||
BusinessProcess businessProcess = this.baseMapper.selectOne(Wrappers.<BusinessProcess>lambdaQuery()
|
||||
.eq(BusinessProcess::getBizId, bizId)
|
||||
.last("limit 1")
|
||||
);
|
||||
if (businessProcess == null) {
|
||||
log.warn("查询是否编辑页,业务流程不存在 业务id:{}", bizId);
|
||||
return false;
|
||||
}
|
||||
String approveStatus = businessProcess.getApproveStatus();
|
||||
String userAccount = AuthUtil.getUserAccount();
|
||||
String promoterLoginName = businessProcess.getPromoterLoginName();
|
||||
// (驳回或撤销或草稿)且当前登录人是流程提交人
|
||||
boolean result = ApproveStatusEnum.canEdit(approveStatus) && userAccount.equals(promoterLoginName);
|
||||
log.info("是否编辑页 审批状态:{} 当前登录人:{} 提交人:{} 结果:{}", approveStatus, userAccount, promoterLoginName, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMKApprovalUrl(String bizId, String processInstanceId) {
|
||||
boolean bizIdBlank = StringUtil.isBlank(bizId);
|
||||
boolean processInstanceIdBlank = StringUtil.isBlank(processInstanceId);
|
||||
AssertUtils.isFalse(bizIdBlank && processInstanceIdBlank, "参数不能为空");
|
||||
if (processInstanceIdBlank) {
|
||||
BusinessProcess businessProcess = this.getOne(Wrappers.<BusinessProcess>lambdaQuery()
|
||||
.eq(BusinessProcess::getBizId, bizId)
|
||||
.last("limit 1")
|
||||
);
|
||||
AssertUtils.notNull(businessProcess, "业务流程不存在");
|
||||
processInstanceId = businessProcess.getProcessInstanceId();
|
||||
}
|
||||
try {
|
||||
String mkApprovalUrl = mkService.getMKApprovalUrl(processInstanceId, AuthUtil.getUserAccount());
|
||||
AssertUtils.notBlank(mkApprovalUrl, "获取mk审批页面链接异常");
|
||||
return mkApprovalUrl;
|
||||
} catch (MKException e) {
|
||||
throw new ServiceException("获取mk审批页面链接异常 " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
@Override
|
||||
public boolean deleteBusinessProcess(BusinessProcessDeleteDTO param) {
|
||||
log.info("删除业务流程 参数:{}", JSON.toJSONString(param));
|
||||
Long bizId = param.getBizId();
|
||||
String promoterLoginName = param.getPromoterLoginName();
|
||||
if (bizId == null) {
|
||||
return false;
|
||||
}
|
||||
BusinessProcess businessProcess = this.getOne(Wrappers.<BusinessProcess>lambdaQuery()
|
||||
.eq(BusinessProcess::getBizId, bizId)
|
||||
);
|
||||
if (businessProcess == null) {
|
||||
log.warn("业务流程不存在 业务id:{}", bizId);
|
||||
return false;
|
||||
}
|
||||
if (promoterLoginName == null) {
|
||||
promoterLoginName = businessProcess.getPromoterLoginName();
|
||||
}
|
||||
// 1. 删除业务流程
|
||||
this.removeById(businessProcess.getId());
|
||||
// 2. 删除待办
|
||||
if (StringUtils.isEmpty(businessProcess.getProcessInstanceId())) {
|
||||
log.warn("流程id为空 id:{} 业务id:{}", businessProcess.getId(), bizId);
|
||||
return true;
|
||||
}
|
||||
// 3. 删除流程
|
||||
return mkService.processDelete(businessProcess.getProcessInstanceId(), promoterLoginName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ProcessApprovedRecordVO> queryApprovedRecords(String bizId, String processInstanceId) {
|
||||
List<ProcessApprovedRecordVO> records = this.queryApprovedRecordsNoAttachments(bizId, processInstanceId);
|
||||
if (CollectionUtil.isEmpty(records)) {
|
||||
return records;
|
||||
}
|
||||
Map<String, String> fileMap = new HashMap<>();
|
||||
for (ProcessApprovedRecordVO record : records) {
|
||||
List<ProcessAttachmentVO> attachmentParameter = record.getAttachmentParameter();
|
||||
// 处理电子签名base64并排序附件
|
||||
attachmentParameter = handleAttachmentBase64(attachmentParameter, fileMap);
|
||||
record.setAttachmentParameter(attachmentParameter);
|
||||
if (CollectionUtil.isNotEmpty(record.getProcessComments())) {
|
||||
for (ProcessCommentVO processComment : record.getProcessComments()) {
|
||||
// 处理电子签名base64并排序附件
|
||||
List<ProcessAttachmentVO> attachments = handleAttachmentBase64(processComment.getAttachments(), fileMap);
|
||||
processComment.setAttachments(attachments);
|
||||
}
|
||||
}
|
||||
}
|
||||
return records;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ProcessApprovedRecordVO> queryApprovedRecordsNoAttachments(String bizId, String processInstanceId) {
|
||||
boolean bizIdBlank = StringUtil.isBlank(bizId);
|
||||
boolean processInstanceIdBlank = StringUtil.isBlank(processInstanceId);
|
||||
AssertUtils.isFalse(bizIdBlank && processInstanceIdBlank, "参数不能为空");
|
||||
|
||||
BusinessProcess businessProcess = this.getOne(Wrappers.<BusinessProcess>lambdaQuery()
|
||||
.eq(!bizIdBlank, BusinessProcess::getBizId, bizId)
|
||||
.eq(!processInstanceIdBlank, BusinessProcess::getProcessInstanceId, processInstanceId)
|
||||
.last("limit 1")
|
||||
);
|
||||
AssertUtils.notNull(businessProcess, "业务流程不存在");
|
||||
if (processInstanceIdBlank) {
|
||||
processInstanceId = businessProcess.getProcessInstanceId();
|
||||
}
|
||||
// 查询审批记录
|
||||
List<MKAuditNoteVO> mkAuditNotes = mkService.queryAuditNotes(new MKAuditNoteDTO(businessProcess.getPromoterLoginName(), processInstanceId));
|
||||
// 转换参数
|
||||
return convert.auditNotes2vos(mkAuditNotes, record -> {
|
||||
List<MKSenderVO> mkSenders = mkService.querySenderList(new MKSenderDTO(record.getProcessInstanceId(), record.getNodeInstanceId()));
|
||||
return mkSenders.stream()
|
||||
.map(MKSenderVO::getName)
|
||||
.toList();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理附件base64并排序附件
|
||||
* @param attachmentParameter
|
||||
* @param fileMap
|
||||
*/
|
||||
private List<ProcessAttachmentVO> handleAttachmentBase64(List<ProcessAttachmentVO> attachmentParameter, Map<String, String> fileMap) {
|
||||
if (CollectionUtil.isEmpty(attachmentParameter)) {
|
||||
return attachmentParameter;
|
||||
}
|
||||
// 电子签名的附件
|
||||
List<ProcessAttachmentVO> signAttachments = attachmentParameter.stream()
|
||||
.filter(attachment -> MKConstant.FILE_TYPE_SIGN.equals(attachment.getType()))
|
||||
.peek(attachment -> {
|
||||
// 电子签名,查询图片base64
|
||||
if (fileMap.containsKey(attachment.getFileId())) {
|
||||
attachment.setBase64(fileMap.get(attachment.getFileId()));
|
||||
} else {
|
||||
String fileBase64 = mkService.getFileBase64(attachment.getFileId());
|
||||
fileMap.put(attachment.getFileId(), fileBase64);
|
||||
attachment.setBase64(fileBase64);
|
||||
}
|
||||
}).toList();
|
||||
if (CollectionUtil.isEmpty(signAttachments)) {
|
||||
// 没有电子签名的附件,无需处理
|
||||
return attachmentParameter;
|
||||
}
|
||||
List<ProcessAttachmentVO> result = new ArrayList<>();
|
||||
// 纯附件,非电子签名附件
|
||||
List<ProcessAttachmentVO> attachments = attachmentParameter.stream()
|
||||
.filter(attachment -> !MKConstant.FILE_TYPE_SIGN.equals(attachment.getType()))
|
||||
.toList();
|
||||
if (CollectionUtil.isNotEmpty(attachments)) {
|
||||
result.addAll(attachments);
|
||||
}
|
||||
// 把电子签名附件放到最后
|
||||
result.addAll(signAttachments);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void downloadFile(HttpServletResponse response, String fileId) {
|
||||
mkService.downloadFile(response,fileId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ProcessTodoVO> queryTodoList(String processInstanceId) {
|
||||
AssertUtils.notNull(processInstanceId, "流程实例id不能为空");
|
||||
BusinessProcess businessProcess = this.getBusinessProcessByProcessInstanceId(processInstanceId);
|
||||
if (businessProcess == null) {
|
||||
return null;
|
||||
}
|
||||
// 1. 查询当前节点处理人
|
||||
List<MKNodeVO> currentNodes = mkService.getCurrentNodes(processInstanceId, businessProcess.getPromoterLoginName());
|
||||
if (CollectionUtil.isEmpty(currentNodes)) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
return getProcessTodoList(currentNodes);
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过流程实例id查询业务流程
|
||||
* @param processInstanceId 流程实例id
|
||||
* @return 业务流程
|
||||
*/
|
||||
private BusinessProcess getBusinessProcessByProcessInstanceId(String processInstanceId) {
|
||||
return this.getOne(Wrappers.<BusinessProcess>lambdaQuery()
|
||||
.eq(BusinessProcess::getProcessInstanceId, processInstanceId)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析最终使用的发起人登录名
|
||||
* @param businessProcess 业务流程
|
||||
* @param fallbackPromoterLoginName 调用方传入的发起人登录名
|
||||
* @return 发起人登录名
|
||||
*/
|
||||
private String resolvePromoterLoginName(BusinessProcess businessProcess, String fallbackPromoterLoginName) {
|
||||
return Optional.ofNullable(businessProcess)
|
||||
.map(BusinessProcess::getPromoterLoginName)
|
||||
.filter(StringUtils::isNotBlank)
|
||||
.orElse(fallbackPromoterLoginName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造业务流程快照
|
||||
* @param businessProcess 业务流程
|
||||
* @return 快照
|
||||
*/
|
||||
private BusinessProcessVO buildBusinessProcessVO(BusinessProcess businessProcess) {
|
||||
BusinessProcessVO businessProcessVO = new BusinessProcessVO();
|
||||
businessProcessVO.setProcessInstanceId(businessProcess.getProcessInstanceId());
|
||||
businessProcessVO.setCurrentNodeIds(businessProcess.getCurrentNodeIds());
|
||||
businessProcessVO.setCurrentNodeNames(businessProcess.getCurrentNodeNames());
|
||||
businessProcessVO.setCurrentHandlers(businessProcess.getCurrentHandlers());
|
||||
businessProcessVO.setReceiveTime(businessProcess.getReceiveTime());
|
||||
return businessProcessVO;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取流程待办列表
|
||||
* @param currentNodes
|
||||
* @return
|
||||
*/
|
||||
private List<ProcessTodoVO> getProcessTodoList(List<MKNodeVO> currentNodes) {
|
||||
if (CollectionUtil.isEmpty(currentNodes)) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
return currentNodes.stream()
|
||||
.filter(node -> CollectionUtil.isNotEmpty(node.getNodeHandlers()))
|
||||
.flatMap(node -> node.getNodeHandlers().stream()
|
||||
// 过滤掉登录名为空的脏数据
|
||||
.filter(handler -> handler.getFdHandlerOrgInfo() != null && StringUtil.isNotBlank(handler.getFdHandlerOrgInfo().getLoginName()))
|
||||
.map(handler -> {
|
||||
ProcessTodoVO addParam = new ProcessTodoVO();
|
||||
addParam.setProcessInstanceId(node.getProcessInstanceId());
|
||||
addParam.setNodeId(node.getNodeId());
|
||||
addParam.setNodeNumber(node.getNodeNumber());
|
||||
addParam.setNodeName(node.getNodeName());
|
||||
addParam.setLoginName(handler.getFdHandlerOrgInfo().getLoginName());
|
||||
addParam.setUserName(handler.getHandlerName());
|
||||
addParam.setStatus(TodoStatus.TODO.getCode());
|
||||
addParam.setReceiveTime(handler.getReceiveTime());
|
||||
return addParam;
|
||||
})
|
||||
).toList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public IPage<ApprovalVO> queryMkApprovalList(IPage<ApprovalVO> page, ApprovalDTO param) {
|
||||
if (MKDoc.RELATED.getCode().equals(param.getDocType())) {
|
||||
// 我参与的,调用流程列表接口
|
||||
MKProcessDTO processParam = approvalConvert.dto2mk(param);
|
||||
processParam.setPage((int) page.getCurrent(), (int) page.getSize());
|
||||
MKPageVO<MKProcessVO> mkPage = mkService.queryProcessList(processParam);
|
||||
page.setTotal(mkPage.getTotalSize());
|
||||
page.setRecords(approvalConvert.mkProcess2vos(mkPage.getContent()));
|
||||
return page;
|
||||
}
|
||||
// 非我参与的,调用审批中心接口
|
||||
MKApprovalDTO approvalParam = getMkApprovalParam(param);
|
||||
approvalParam.setPage((int) page.getCurrent(), (int) page.getSize());
|
||||
MKPageVO<MKApprovalVO> mkPage = mkService.queryApprovalList(approvalParam);
|
||||
page.setTotal(mkPage.getTotalSize());
|
||||
page.setRecords(approvalConvert.mk2vos(mkPage.getContent()));
|
||||
return page;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取mk查询参数
|
||||
*
|
||||
* @param param
|
||||
* @return
|
||||
*/
|
||||
private MKApprovalDTO getMkApprovalParam(ApprovalDTO param) {
|
||||
String docType = param.getDocType();
|
||||
// 我的待审
|
||||
// mk页面接口参数 {"offset":0,"pageNo":1,"pageSize":10,"conditions":{"fdStartTime":{"$gte":1711900800000,"$lte":1746374399999},"fdReceiveTime":{"$gte":1712160000000,"$lte":1746115199999},"fdTemplateName":{"$contains":"测试"},"keyword":{"$eq":"提交"},"mydoc":{"$eq":"myApproving"}},"sorts":{"fdLevel":"asc","fdReceiveTime":"desc"}}
|
||||
MKApprovalDTO approvalParam = new MKApprovalDTO();
|
||||
approvalParam.setLoginName(param.getLoginName());
|
||||
ISort sort = getSort(docType, approvalParam);
|
||||
approvalParam.setSorts(sort);
|
||||
// 获取查询条件的参数
|
||||
MKApprovalConditionDTO condition = getCondition(param);
|
||||
approvalParam.setConditions(condition);
|
||||
return approvalParam;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取排序
|
||||
* @param docType
|
||||
* @param approvalParam
|
||||
* @return
|
||||
*/
|
||||
private ISort getSort(String docType, MKApprovalDTO approvalParam) {
|
||||
if (MKDoc.APPROVING.getCode().equals(docType) || MKDoc.READING.getCode().equals(docType)) {
|
||||
// 待办、待阅排序是相同的
|
||||
return new MKApprovingSortDTO();
|
||||
}
|
||||
if (MKDoc.APPROVED.getCode().equals(docType)) {
|
||||
// 已办
|
||||
return new MKApprovedSortDTO();
|
||||
}
|
||||
if (MKDoc.READ.getCode().equals(docType)) {
|
||||
// 已阅
|
||||
return new MKReadSortDTO();
|
||||
}
|
||||
if (MKDoc.CREATE.getCode().equals(docType) || MKDoc.RELATED.getCode().equals(docType)) {
|
||||
// 我发起的/我关联的
|
||||
return new MKCreateSortDTO();
|
||||
}
|
||||
throw new ServiceException("不支持的单据类型");
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取查询条件
|
||||
* @param param
|
||||
* @return
|
||||
*/
|
||||
private MKApprovalConditionDTO getCondition(ApprovalDTO param) {
|
||||
MKApprovalConditionDTO condition = null;
|
||||
for (MKApprovalConvert convert : MKApprovalConvert.values()) {
|
||||
MKConditionDTO.MKConditionDTOBuilder builder = null;
|
||||
BiConsumer<MKApprovalConditionDTO, MKConditionDTO> setter = convert.getSetter();
|
||||
List<MKApprovalConvert.Compose> composes = convert.getComposes();
|
||||
// 是否多个 Compose
|
||||
boolean multi = composes.size() > 1;
|
||||
if (multi) {
|
||||
// 不是多个setter
|
||||
for (MKApprovalConvert.Compose compose : composes) {
|
||||
// 遍历获取参数值
|
||||
String value = compose.getGetter().apply(param);
|
||||
if (StringUtils.isNotBlank(value)) {
|
||||
// 参数值不为空,设置到builder
|
||||
if (builder == null) {
|
||||
builder = MKConditionDTO.builder();
|
||||
}
|
||||
compose.getBuilderSetter().accept(builder, value);
|
||||
}
|
||||
}
|
||||
if (builder != null) {
|
||||
// builder不为空,设置到最终的条件
|
||||
if (condition == null) {
|
||||
condition = new MKApprovalConditionDTO();
|
||||
}
|
||||
setter.accept(condition, builder.build());
|
||||
convert.getSetter().accept(condition, builder.build());
|
||||
}
|
||||
} else {
|
||||
// 只有1个compose
|
||||
MKApprovalConvert.Compose compose = composes.get(0);
|
||||
String value = compose.getGetter().apply(param);
|
||||
if (StringUtils.isNotBlank(value)) {
|
||||
// 替换中文逗号为英文逗号
|
||||
value = value.replace(",", ",");
|
||||
// 按英文逗号拆分值
|
||||
String[] values = value.split(",");
|
||||
for (String singleValue: values) {
|
||||
if (StringUtils.isNotBlank(value)) {
|
||||
// 参数值不为空,设置到builder
|
||||
if (builder == null) {
|
||||
builder = MKConditionDTO.builder();
|
||||
}
|
||||
compose.getBuilderSetter().accept(builder, singleValue);
|
||||
if (builder != null) {
|
||||
// builder不为空,设置到最终的条件
|
||||
if (condition == null) {
|
||||
condition = new MKApprovalConditionDTO();
|
||||
}
|
||||
// 索引不超过setters长度,设置条件
|
||||
setter.accept(condition, builder.build());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return condition;
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理已审批的人
|
||||
*
|
||||
* @param processInstanceId
|
||||
* @param nodeHandlers
|
||||
* @param param
|
||||
*/
|
||||
private void handleNodeHandlers(String processInstanceId, MKAllHandlerVO nodeHandlers, BusinessProcessUpdateDTO param) {
|
||||
if (nodeHandlers == null) {
|
||||
log.warn("修改业务流程 查询操作节点历史处理信息为空 流程实例id:{} 操作节点id:{}", param.getProcessInstanceId(), param.getOperationNodeId());
|
||||
return;
|
||||
}
|
||||
List<MKApprovedHandlerVO> approvedHandlers = nodeHandlers.getApprovedHandlers();
|
||||
if (CollectionUtil.isEmpty(approvedHandlers)) {
|
||||
// 已审批为空
|
||||
log.warn("修改业务流程 查询操作节点已处理信息为空 流程实例id:{} 操作节点id:{}", param.getProcessInstanceId(), param.getOperationNodeId());
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理当前节点信息
|
||||
*
|
||||
* @param businessProcessId
|
||||
* @param processInstanceId
|
||||
* @param complete
|
||||
* @param currentNodes
|
||||
* @param businessProcessVO
|
||||
*/
|
||||
private BusinessProcess handleCurrentNodes(Long businessProcessId, String processInstanceId, boolean complete, List<MKNodeVO> currentNodes, BusinessProcessVO businessProcessVO) {
|
||||
if (CollectionUtil.isEmpty(currentNodes)) {
|
||||
log.info("修改业务流程 当前节点处理人为空");
|
||||
if (businessProcessId != null) {
|
||||
// 清空当前节点,当前处理人,接收时间
|
||||
log.info("修改业务流程 流程结束清空当前节点,当前处理人,接收时间 业务流程id:{} 流程实例id:{}", businessProcessId, processInstanceId);
|
||||
this.lambdaUpdate()
|
||||
.eq(BusinessProcess::getId, businessProcessId)
|
||||
.set(BusinessProcess::getCurrentNodeIds, null)
|
||||
.set(BusinessProcess::getCurrentNodeNames, null)
|
||||
.set(BusinessProcess::getCurrentHandlers, null)
|
||||
.set(complete, BusinessProcess::getIsCompleted, true)
|
||||
.set(complete, BusinessProcess::getCompleteTime, new Date())
|
||||
.set(BusinessProcess::getUpdateTime, new Date())
|
||||
.update();
|
||||
} else {
|
||||
log.warn("修改业务流程 流程结束 业务流程id为空");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
// 处理待办
|
||||
List<ProcessTodoVO> processTodoList = getProcessTodoList(currentNodes);
|
||||
|
||||
// 更新业务流程
|
||||
return updateBusinessProcess(businessProcessId, processTodoList);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新业务流程
|
||||
*
|
||||
* @param businessProcessId
|
||||
* @param addToDos
|
||||
*/
|
||||
private BusinessProcess updateBusinessProcess(Long businessProcessId, List<ProcessTodoVO> addToDos) {
|
||||
if (CollectionUtil.isEmpty(addToDos)) {
|
||||
log.warn("新增待办为空");
|
||||
return null;
|
||||
}
|
||||
String nodeIds = addToDos.stream()
|
||||
.map(ProcessTodoVO::getNodeId)
|
||||
.distinct()
|
||||
.collect(Collectors.joining(","));
|
||||
String nodeNames = addToDos.stream()
|
||||
.map(ProcessTodoVO::getNodeName)
|
||||
.distinct()
|
||||
.collect(Collectors.joining(","));
|
||||
String usernames = addToDos.stream()
|
||||
.map(ProcessTodoVO::getUserName)
|
||||
.distinct()
|
||||
.collect(Collectors.joining(","));
|
||||
Date receiveTime = addToDos.get(0).getReceiveTime();
|
||||
// 更新当前节点id,当前节点名称,当前处理人,接收时间
|
||||
BusinessProcess updateParam = new BusinessProcess();
|
||||
updateParam.setId(businessProcessId);
|
||||
updateParam.setCurrentNodeIds(nodeIds);
|
||||
updateParam.setCurrentNodeNames(nodeNames);
|
||||
updateParam.setCurrentHandlers(usernames);
|
||||
updateParam.setReceiveTime(receiveTime);
|
||||
if (businessProcessId != null) {
|
||||
this.updateById(updateParam);
|
||||
} else {
|
||||
log.warn("新增待办,业务流程id为空");
|
||||
}
|
||||
return updateParam;
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否修改审批状态,驳回状态只修改驳回节点是起草节点的
|
||||
* @param approveStatus
|
||||
* @param rejectNodeId
|
||||
* @return
|
||||
*/
|
||||
private boolean updateApproveStatus(String approveStatus, String rejectNodeId) {
|
||||
if (!ApproveStatusEnum.REJECTED.getValue().equals(approveStatus)) {
|
||||
// 不是驳回状态,直接修改
|
||||
return true;
|
||||
}
|
||||
if (StringUtils.isBlank(rejectNodeId)) {
|
||||
// 驳回节点id为空说明是老流程,没有配置参数,可以修改
|
||||
return true;
|
||||
}
|
||||
// 是驳回状态,只修改驳回节点id是起草节点id的
|
||||
return MKConstant.DAFTER_NODE_ID.equals(rejectNodeId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 提交mk流程
|
||||
* @param param
|
||||
* @return
|
||||
*/
|
||||
private String submitMKProcess(BusinessProcessSubmitDTO<?> param) {
|
||||
if (param.getExecuteParam() == null || StringUtil.isBlank(param.getExecuteParam().getProcessId())) {
|
||||
// 执行参数为空,是提交
|
||||
MKProcessCreateDTO processParam = new MKProcessCreateDTO();
|
||||
processParam.setFormInstanceId(String.valueOf(param.getBizId()));
|
||||
processParam.setLoginName(param.getPromoterLoginName());
|
||||
processParam.setSubmitIdentity(param.getPromoterLoginName());
|
||||
processParam.setSubject(param.getSubject());
|
||||
processParam.setTemplateCode(mkProperties.getTemplateCodePrefix() + param.getProcessType());
|
||||
processParam.setFormValues(param.getProcessParam());
|
||||
// 临时变量设置为业务表单对象,以便不用修改mk流程表单字段,直接使用流程模板临时变量
|
||||
processParam.setTempVarData(ObjectUtil.cloneByStream(param.getProcessParam()));
|
||||
return mkService.processSubmit(processParam);
|
||||
}
|
||||
// 执行参数不为空,是驳回/撤销后提交/废弃
|
||||
ProcessExecuteDTO executeParam = param.getExecuteParam();
|
||||
MKProcessExecuteDTO processExecuteDTO = convert.dto2mk(executeParam);
|
||||
processExecuteDTO.setLoginName(param.getPromoterLoginName());
|
||||
processExecuteDTO.setFormValues(param.getProcessParam());
|
||||
// 临时变量设置为业务表单对象,以便不用修改mk流程表单字段,直接使用流程模板临时变量
|
||||
processExecuteDTO.setTempVarData(ObjectUtil.cloneByStream(param.getProcessParam()));
|
||||
// 重新设置标题,防止标题变了
|
||||
processExecuteDTO.setSubject(param.getSubject());
|
||||
mkService.processExecute(processExecuteDTO);
|
||||
return executeParam.getProcessId();
|
||||
}
|
||||
|
||||
}
|
||||
+6
-1
@@ -28,17 +28,22 @@ package org.springblade.system;
|
||||
import org.springblade.core.cloud.client.BladeCloudApplication;
|
||||
import org.springblade.core.launch.BladeApplication;
|
||||
import org.springblade.core.launch.constant.AppConstant;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.annotation.ComponentScan;
|
||||
import org.springblade.system.props.IamSyncProperties;
|
||||
|
||||
/**
|
||||
* 系统模块启动器
|
||||
* @author Chill
|
||||
*/
|
||||
@ComponentScan(basePackages = {"org.springblade.system", "org.springblade.process"})
|
||||
@EnableConfigurationProperties(IamSyncProperties.class)
|
||||
@BladeCloudApplication
|
||||
public class SystemApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
BladeApplication.disableNacosLaunchConfig();
|
||||
BladeApplication.run(AppConstant.APPLICATION_SYSTEM_NAME, SystemApplication.class, args);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
+4
-3
@@ -44,6 +44,7 @@ import org.springblade.core.tool.api.R;
|
||||
import org.springblade.core.tool.utils.DateUtil;
|
||||
import org.springblade.core.tool.utils.Func;
|
||||
import org.springblade.system.excel.AirportMasterExcel;
|
||||
import org.springblade.system.excel.AirportMasterExportExcel;
|
||||
import org.springblade.system.excel.AirportMasterImporter;
|
||||
import org.springblade.system.pojo.entity.AirportMaster;
|
||||
import org.springblade.system.pojo.vo.AirportMasterVO;
|
||||
@@ -158,7 +159,7 @@ public class AirportMasterController extends BladeController {
|
||||
}
|
||||
List<AirportMasterExcel> failureList = airportMasterService.importAirportMaster(ExcelUtil.read(file, AirportMasterExcel.class));
|
||||
if (Func.isNotEmpty(failureList)) {
|
||||
org.springblade.common.excel.ImportFailureExcelUtil.export(response, "空港机场主数据导入失败明细" + DateUtil.time(), "导入失败明细", failureList, AirportMasterExcel.class);
|
||||
org.springblade.common.excel.ImportFailureExcelUtil.exportFailureReasonOnly(response, "空港机场主数据导入失败明细" + DateUtil.time(), "导入失败明细", failureList, AirportMasterExcel.class);
|
||||
return null;
|
||||
}
|
||||
return R.success("操作成功");
|
||||
@@ -180,8 +181,8 @@ public class AirportMasterController extends BladeController {
|
||||
if (Func.isNotEmpty(ids)) {
|
||||
queryWrapper.lambda().in(AirportMaster::getId, Func.toLongList(ids.toString()));
|
||||
}
|
||||
List<AirportMasterExcel> list = airportMasterService.exportAirportMaster(queryWrapper);
|
||||
ExcelUtil.export(response, "空港机场主数据" + DateUtil.time(), "空港机场主数据表", list, AirportMasterExcel.class);
|
||||
List<AirportMasterExportExcel> list = airportMasterService.exportAirportMaster(queryWrapper);
|
||||
ExcelUtil.export(response, "空港机场主数据" + DateUtil.time(), "空港机场主数据表", list, AirportMasterExportExcel.class);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+54
-3
@@ -50,8 +50,10 @@ import org.springblade.system.pojo.entity.Dept;
|
||||
import org.springblade.system.pojo.entity.User;
|
||||
import org.springblade.system.pojo.enums.DictEnum;
|
||||
import org.springblade.system.pojo.vo.DeptVO;
|
||||
import org.springblade.system.pojo.vo.OaOrgSyncPageVO;
|
||||
import org.springblade.system.pojo.vo.UserVO;
|
||||
import org.springblade.system.service.IDeptService;
|
||||
import org.springblade.system.service.IOASyncService;
|
||||
import org.springblade.system.wrapper.DeptWrapper;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
@@ -73,6 +75,7 @@ import static org.springblade.core.cache.constant.CacheConstant.SYS_CACHE;
|
||||
public class DeptController extends BladeController {
|
||||
|
||||
private final IDeptService deptService;
|
||||
private final IOASyncService oaSyncService;
|
||||
|
||||
/**
|
||||
* 详情
|
||||
@@ -159,12 +162,49 @@ public class DeptController extends BladeController {
|
||||
return R.fail("操作失败");
|
||||
}
|
||||
|
||||
/**
|
||||
* 同步IAM组织。
|
||||
*/
|
||||
@IsAdmin
|
||||
@PostMapping("/sync-iam-organizations")
|
||||
@ApiOperationSupport(order = 7)
|
||||
@Operation(summary = "同步IAM组织")
|
||||
public R<Integer> syncIamOrganizations() {
|
||||
return R.data(deptService.syncIamOrganizations());
|
||||
}
|
||||
|
||||
/**
|
||||
* 从OA按页同步公司
|
||||
*/
|
||||
@IsAdmin
|
||||
@PostMapping("/sync-oa-company")
|
||||
@ApiOperationSupport(order = 8)
|
||||
@Operation(summary = "同步OA公司")
|
||||
public R<OaOrgSyncPageVO> syncOaCompany(
|
||||
@RequestParam(defaultValue = "1") Integer current,
|
||||
@RequestParam(defaultValue = "20") Integer size) {
|
||||
return R.data(oaSyncService.syncCompanyPage(current, size));
|
||||
}
|
||||
|
||||
/**
|
||||
* 从OA按页同步部门(需先完成公司同步)
|
||||
*/
|
||||
@IsAdmin
|
||||
@PostMapping("/sync-oa-department")
|
||||
@ApiOperationSupport(order = 9)
|
||||
@Operation(summary = "同步OA部门")
|
||||
public R<OaOrgSyncPageVO> syncOaDepartment(
|
||||
@RequestParam(defaultValue = "1") Integer current,
|
||||
@RequestParam(defaultValue = "20") Integer size) {
|
||||
return R.data(oaSyncService.syncDepartmentPage(current, size));
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除
|
||||
*/
|
||||
@IsAdmin
|
||||
@PostMapping("/remove")
|
||||
@ApiOperationSupport(order = 7)
|
||||
@ApiOperationSupport(order = 10)
|
||||
@Operation(summary = "删除", description = "传入ids")
|
||||
public R remove(@Parameter(description = "主键集合", required = true) @RequestParam String ids) {
|
||||
CacheUtil.clear(SYS_CACHE);
|
||||
@@ -177,7 +217,7 @@ public class DeptController extends BladeController {
|
||||
*/
|
||||
@PreAuth(AuthConstant.PERMIT_ALL)
|
||||
@GetMapping("/select")
|
||||
@ApiOperationSupport(order = 8)
|
||||
@ApiOperationSupport(order = 11)
|
||||
@Operation(summary = "下拉数据源", description = "传入id集合")
|
||||
public R<List<Dept>> select(Long userId, String deptId) {
|
||||
if (Func.isNotEmpty(userId)) {
|
||||
@@ -189,12 +229,23 @@ public class DeptController extends BladeController {
|
||||
return R.data(deptService.selectDept(deptId));
|
||||
}
|
||||
|
||||
/**
|
||||
* 平台公司下拉(是否平台公司=是)
|
||||
*/
|
||||
@PreAuth(AuthConstant.PERMIT_ALL)
|
||||
@GetMapping("/platform-company-select")
|
||||
@ApiOperationSupport(order = 12)
|
||||
@Operation(summary = "平台公司下拉", description = "返回是否平台公司=是的部门列表")
|
||||
public R<List<Dept>> platformCompanySelect() {
|
||||
return R.data(deptService.listPlatformCompany());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取部门的主管信息
|
||||
*/
|
||||
@IsAdmin
|
||||
@GetMapping("/dept-leader-info")
|
||||
@ApiOperationSupport(order = 9)
|
||||
@ApiOperationSupport(order = 13)
|
||||
@Operation(summary = "获取部门的主管信息", description = "传入deptId")
|
||||
public R<List<UserVO>> deptLeaderInfo(@Parameter(description = "部门id", required = true) @RequestParam Long deptId) {
|
||||
List<UserVO> list = deptService.deptLeaderInfo(deptId);
|
||||
|
||||
+3
-2
@@ -46,6 +46,7 @@ import org.springblade.core.tool.api.R;
|
||||
import org.springblade.core.tool.utils.DateUtil;
|
||||
import org.springblade.core.tool.utils.Func;
|
||||
import org.springblade.system.excel.FeeItemExcel;
|
||||
import org.springblade.system.excel.FeeItemExportExcel;
|
||||
import org.springblade.system.excel.FeeItemImportFailureExcel;
|
||||
import org.springblade.system.pojo.entity.FeeItem;
|
||||
import org.springblade.system.pojo.vo.FeeItemVO;
|
||||
@@ -155,8 +156,8 @@ public class FeeItemController extends BladeController {
|
||||
public void exportFeeItem(FeeItemVO feeItem,
|
||||
@RequestParam(required = false) String ids,
|
||||
HttpServletResponse response) {
|
||||
List<FeeItemExcel> list = feeItemService.exportFeeItem(buildExportQuery(feeItem, ids));
|
||||
ExcelUtil.export(response, "费用项" + DateUtil.time(), "费用项表", list, FeeItemExcel.class);
|
||||
List<FeeItemExportExcel> list = feeItemService.exportFeeItem(buildExportQuery(feeItem, ids));
|
||||
ExcelUtil.export(response, "费用项" + DateUtil.time(), "费用项表", list, FeeItemExportExcel.class);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+90
@@ -0,0 +1,90 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments from this software for such purposes. Copyright of this software remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.system.controller;
|
||||
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.validation.Valid;
|
||||
import lombok.AllArgsConstructor;
|
||||
import org.springblade.core.boot.ctrl.BladeController;
|
||||
import org.springblade.core.mp.support.Condition;
|
||||
import org.springblade.core.mp.support.Query;
|
||||
import org.springblade.core.secure.annotation.PreAuth;
|
||||
import org.springblade.core.tenant.annotation.NonDS;
|
||||
import org.springblade.core.tool.api.R;
|
||||
import org.springblade.core.tool.utils.Func;
|
||||
import org.springblade.system.pojo.entity.InvoiceItem;
|
||||
import org.springblade.system.pojo.vo.InvoiceItemVO;
|
||||
import org.springblade.system.service.IInvoiceItemService;
|
||||
import org.springblade.system.wrapper.InvoiceItemWrapper;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/**
|
||||
* 开票项目控制器
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@NonDS
|
||||
@RestController
|
||||
@AllArgsConstructor
|
||||
@PreAuth(menu = "invoice_item")
|
||||
@RequestMapping("/invoice-item")
|
||||
@Tag(name = "开票项目", description = "开票项目")
|
||||
public class InvoiceItemController extends BladeController {
|
||||
|
||||
private final IInvoiceItemService invoiceItemService;
|
||||
|
||||
@GetMapping("/detail")
|
||||
@ApiOperationSupport(order = 1)
|
||||
@Operation(summary = "详情", description = "传入invoiceItem")
|
||||
public R<InvoiceItemVO> detail(InvoiceItem invoiceItem) {
|
||||
InvoiceItem detail = invoiceItemService.getOne(Condition.getQueryWrapper(invoiceItem));
|
||||
return R.data(InvoiceItemWrapper.build().entityVO(detail));
|
||||
}
|
||||
|
||||
@GetMapping("/list")
|
||||
@ApiOperationSupport(order = 2)
|
||||
@Operation(summary = "分页", description = "传入invoiceItem")
|
||||
public R<IPage<InvoiceItemVO>> list(InvoiceItemVO invoiceItem, Query query) {
|
||||
return R.data(invoiceItemService.selectInvoiceItemPage(Condition.getPage(query), invoiceItem));
|
||||
}
|
||||
|
||||
@PostMapping("/submit")
|
||||
@ApiOperationSupport(order = 3)
|
||||
@Operation(summary = "新增或修改", description = "传入invoiceItem")
|
||||
public R submit(@Valid @RequestBody InvoiceItem invoiceItem) {
|
||||
return R.status(invoiceItemService.submit(invoiceItem));
|
||||
}
|
||||
|
||||
@PostMapping("/remove")
|
||||
@ApiOperationSupport(order = 4)
|
||||
@Operation(summary = "逻辑删除", description = "传入ids")
|
||||
public R remove(@Parameter(description = "主键集合", required = true) @RequestParam String ids) {
|
||||
return R.status(invoiceItemService.deleteLogic(Func.toLongList(ids)));
|
||||
}
|
||||
|
||||
}
|
||||
+137
@@ -0,0 +1,137 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.system.controller;
|
||||
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.validation.Valid;
|
||||
import lombok.AllArgsConstructor;
|
||||
import org.springblade.core.boot.ctrl.BladeController;
|
||||
import org.springblade.core.mp.support.Condition;
|
||||
import org.springblade.core.mp.support.Query;
|
||||
import org.springblade.core.secure.annotation.PreAuth;
|
||||
import org.springblade.core.tenant.annotation.NonDS;
|
||||
import org.springblade.core.tool.api.R;
|
||||
import org.springblade.core.tool.utils.Func;
|
||||
import org.springblade.system.pojo.entity.MeasurementUnit;
|
||||
import org.springblade.system.pojo.vo.MeasurementUnitVO;
|
||||
import org.springblade.system.service.IMeasurementUnitService;
|
||||
import org.springblade.system.wrapper.MeasurementUnitWrapper;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/**
|
||||
* 计量单位控制器
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@NonDS
|
||||
@RestController
|
||||
@AllArgsConstructor
|
||||
@PreAuth(menu = "measurement_unit")
|
||||
@RequestMapping("/measurement-unit")
|
||||
@Tag(name = "计量单位", description = "计量单位")
|
||||
public class MeasurementUnitController extends BladeController {
|
||||
|
||||
private final IMeasurementUnitService measurementUnitService;
|
||||
|
||||
/**
|
||||
* 详情
|
||||
*
|
||||
* @param measurementUnit 查询条件
|
||||
* @return 计量单位详情
|
||||
*/
|
||||
@GetMapping("/detail")
|
||||
@ApiOperationSupport(order = 1)
|
||||
@Operation(summary = "详情", description = "传入measurementUnit")
|
||||
public R<MeasurementUnitVO> detail(MeasurementUnit measurementUnit) {
|
||||
MeasurementUnit detail = measurementUnitService.getOne(Condition.getQueryWrapper(measurementUnit));
|
||||
if (detail == null) {
|
||||
return R.fail("数据不存在");
|
||||
}
|
||||
return R.data(MeasurementUnitWrapper.build().entityVO(detail));
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页
|
||||
*
|
||||
* @param measurementUnit 查询条件
|
||||
* @param query 分页参数
|
||||
* @return 计量单位分页
|
||||
*/
|
||||
@GetMapping("/list")
|
||||
@ApiOperationSupport(order = 2)
|
||||
@Operation(summary = "分页", description = "传入measurementUnit")
|
||||
public R<IPage<MeasurementUnitVO>> list(MeasurementUnitVO measurementUnit, Query query) {
|
||||
IPage<MeasurementUnitVO> pages = measurementUnitService.selectMeasurementUnitPage(
|
||||
Condition.getPage(query), measurementUnit
|
||||
);
|
||||
return R.data(pages);
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增或修改
|
||||
*
|
||||
* @param measurementUnit 计量单位
|
||||
* @return 操作结果
|
||||
*/
|
||||
@PostMapping("/submit")
|
||||
@ApiOperationSupport(order = 3)
|
||||
@Operation(summary = "新增或修改", description = "传入measurementUnit")
|
||||
public R submit(@Valid @RequestBody MeasurementUnit measurementUnit) {
|
||||
return R.status(measurementUnitService.submit(measurementUnit));
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除
|
||||
*
|
||||
* @param ids 主键集合
|
||||
* @return 操作结果
|
||||
*/
|
||||
@PostMapping("/remove")
|
||||
@ApiOperationSupport(order = 4)
|
||||
@Operation(summary = "逻辑删除", description = "传入ids")
|
||||
public R remove(@Parameter(description = "主键集合", required = true) @RequestParam String ids) {
|
||||
return R.status(measurementUnitService.deleteLogic(Func.toLongList(ids)));
|
||||
}
|
||||
|
||||
/**
|
||||
* 启用或停用
|
||||
*
|
||||
* @param id 主键
|
||||
* @param status 状态
|
||||
* @return 操作结果
|
||||
*/
|
||||
@PostMapping("/status")
|
||||
@ApiOperationSupport(order = 5)
|
||||
@Operation(summary = "启用或停用", description = "传入id和status")
|
||||
public R status(@Parameter(description = "主键", required = true) @RequestParam Long id,
|
||||
@Parameter(description = "状态", required = true) @RequestParam Integer status) {
|
||||
return R.status(measurementUnitService.changeStatus(id, status));
|
||||
}
|
||||
|
||||
}
|
||||
+276
-257
@@ -1,257 +1,276 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.system.controller;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import jakarta.validation.Valid;
|
||||
import lombok.AllArgsConstructor;
|
||||
import org.springblade.core.boot.ctrl.BladeController;
|
||||
import org.springblade.core.excel.util.ExcelUtil;
|
||||
import org.springblade.core.mp.support.Condition;
|
||||
import org.springblade.core.mp.support.Query;
|
||||
import org.springblade.core.secure.annotation.PreAuth;
|
||||
import org.springblade.core.tenant.annotation.NonDS;
|
||||
import org.springblade.core.tool.api.R;
|
||||
import org.springblade.core.tool.utils.DateUtil;
|
||||
import org.springblade.core.tool.utils.Func;
|
||||
import org.springblade.common.excel.ImportFailureExcelUtil;
|
||||
import org.springblade.system.excel.ImportFailureException;
|
||||
import org.springblade.system.excel.PortTerminalExcel;
|
||||
import org.springblade.system.excel.PortTerminalImporter;
|
||||
import org.springblade.system.pojo.entity.PortTerminal;
|
||||
import org.springblade.system.pojo.vo.PortTerminalVO;
|
||||
import org.springblade.system.service.IPortTerminalService;
|
||||
import org.springblade.system.wrapper.PortTerminalWrapper;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 港口码头主数据 控制器
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@NonDS
|
||||
@RestController
|
||||
@AllArgsConstructor
|
||||
@PreAuth(menu = "port_terminal")
|
||||
@RequestMapping("/port-terminal")
|
||||
@Tag(name = "港口码头主数据", description = "港口码头主数据")
|
||||
public class PortTerminalController extends BladeController {
|
||||
|
||||
private static final int DEFAULT_CURRENT = 1;
|
||||
private static final int DEFAULT_SIZE = 10;
|
||||
private static final int MAX_SIZE = 100;
|
||||
private static final String SOURCE_INITIAL = "初始化导入";
|
||||
|
||||
private final IPortTerminalService portTerminalService;
|
||||
|
||||
/**
|
||||
* 详情
|
||||
*/
|
||||
@GetMapping("/detail")
|
||||
@ApiOperationSupport(order = 1)
|
||||
@Operation(summary = "详情", description = "传入portTerminal")
|
||||
public R<PortTerminalVO> detail(PortTerminal portTerminal) {
|
||||
if (Func.isEmpty(portTerminal.getId())) {
|
||||
return R.fail("主键不能为空");
|
||||
}
|
||||
PortTerminal detail = portTerminalService.getById(portTerminal.getId());
|
||||
if (Func.isEmpty(detail)) {
|
||||
return R.fail("港口码头不存在");
|
||||
}
|
||||
return R.data(PortTerminalWrapper.build().entityVO(detail));
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页
|
||||
*/
|
||||
@GetMapping("/list")
|
||||
@ApiOperationSupport(order = 2)
|
||||
@Operation(summary = "分页", description = "传入portTerminal")
|
||||
public R<IPage<PortTerminalVO>> list(PortTerminalVO portTerminal, Query query) {
|
||||
IPage<PortTerminalVO> pages = portTerminalService.selectPortTerminalPage(Condition.getPage(normalizeQuery(query)), portTerminal);
|
||||
return R.data(pages);
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增或修改
|
||||
*/
|
||||
@PostMapping("/submit")
|
||||
@ApiOperationSupport(order = 3)
|
||||
@Operation(summary = "新增或修改", description = "传入portTerminal")
|
||||
public R submit(@Valid @RequestBody PortTerminal portTerminal) {
|
||||
return R.status(portTerminalService.submit(portTerminal));
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除
|
||||
*/
|
||||
@PostMapping("/remove")
|
||||
@ApiOperationSupport(order = 4)
|
||||
@Operation(summary = "逻辑删除", description = "传入ids")
|
||||
public R remove(@Parameter(description = "主键集合", required = true) @RequestParam String ids) {
|
||||
if (Func.isEmpty(ids)) {
|
||||
return R.fail("主键不能为空");
|
||||
}
|
||||
return R.status(portTerminalService.deleteLogic(Func.toLongList(ids)));
|
||||
}
|
||||
|
||||
/**
|
||||
* 启用或停用
|
||||
*/
|
||||
@PostMapping("/status")
|
||||
@ApiOperationSupport(order = 5)
|
||||
@Operation(summary = "启用或停用", description = "传入id和status")
|
||||
public R status(@Parameter(description = "主键", required = true) @RequestParam Long id,
|
||||
@Parameter(description = "状态", required = true) @RequestParam Integer status) {
|
||||
return R.status(portTerminalService.changeStatus(id, status));
|
||||
}
|
||||
|
||||
/**
|
||||
* 上级港口下拉数据源
|
||||
*/
|
||||
@GetMapping("/port-select")
|
||||
@ApiOperationSupport(order = 6)
|
||||
@Operation(summary = "上级港口下拉数据源")
|
||||
public R<List<PortTerminal>> portSelect() {
|
||||
return R.data(portTerminalService.selectEnabledPorts());
|
||||
}
|
||||
|
||||
/**
|
||||
* 导入港口码头主数据
|
||||
*/
|
||||
@PostMapping("/import-port-terminal")
|
||||
@ApiOperationSupport(order = 7)
|
||||
@Operation(summary = "导入港口码头主数据", description = "传入excel")
|
||||
public R importPortTerminal(MultipartFile file, HttpServletResponse response) {
|
||||
if (file == null || file.isEmpty()) {
|
||||
return R.fail("上传文件不能为空");
|
||||
}
|
||||
String fileName = Func.toStrWithEmpty(file.getOriginalFilename(), "").toLowerCase();
|
||||
if (!fileName.endsWith(".xls") && !fileName.endsWith(".xlsx")) {
|
||||
return R.fail("请上传 .xls,.xlsx 标准格式文件");
|
||||
}
|
||||
try {
|
||||
portTerminalService.importPortTerminal(ExcelUtil.read(file, PortTerminalExcel.class));
|
||||
} catch (ImportFailureException exception) {
|
||||
// 全失败即整批回滚,导出原表全部数据并标注错误,用户修正后重新导入。
|
||||
exportFailure(response, exception.getFailureList());
|
||||
return null;
|
||||
}
|
||||
return R.success("操作成功");
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出导入失败明细,内容为原表全部数据并在末尾追加失败原因列。
|
||||
*/
|
||||
private void exportFailure(HttpServletResponse response, List<?> failureList) {
|
||||
ImportFailureExcelUtil.export(response, "港口码头主数据导入失败明细" + DateUtil.time(), "导入失败明细", failureList, PortTerminalExcel.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出港口码头主数据
|
||||
*/
|
||||
@GetMapping("/export-port-terminal")
|
||||
@ApiOperationSupport(order = 8)
|
||||
@Operation(summary = "导出港口码头主数据")
|
||||
public void exportPortTerminal(@Parameter(hidden = true) @RequestParam Map<String, Object> portTerminal, HttpServletResponse response) {
|
||||
Object ids = portTerminal.remove("ids");
|
||||
Object dataSource = portTerminal.remove("dataSource");
|
||||
portTerminal.remove("Blade-Auth");
|
||||
portTerminal.remove("Authorization");
|
||||
portTerminal.remove("access_token");
|
||||
normalizeRegionCodeCondition(portTerminal);
|
||||
QueryWrapper<PortTerminal> queryWrapper = Condition.getQueryWrapper(portTerminal, PortTerminal.class);
|
||||
applyDataSourceCondition(queryWrapper, dataSource);
|
||||
if (Func.isNotEmpty(ids)) {
|
||||
queryWrapper.lambda().in(PortTerminal::getId, Func.toLongList(ids.toString()));
|
||||
}
|
||||
List<PortTerminalExcel> list = portTerminalService.exportPortTerminal(queryWrapper);
|
||||
ExcelUtil.export(response, "港口码头主数据" + DateUtil.time(), "港口码头主数据表", list, PortTerminalExcel.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出模板
|
||||
*/
|
||||
@GetMapping("/export-template")
|
||||
@ApiOperationSupport(order = 9)
|
||||
@Operation(summary = "导出模板")
|
||||
public void exportTemplate(HttpServletResponse response) {
|
||||
List<PortTerminalExcel> list = new ArrayList<>();
|
||||
ExcelUtil.export(response, "港口码头主数据模板", "港口码头主数据表", list, PortTerminalExcel.class);
|
||||
}
|
||||
|
||||
private Query normalizeQuery(Query query) {
|
||||
if (query == null) {
|
||||
query = new Query();
|
||||
}
|
||||
if (query.getCurrent() == null || query.getCurrent() < DEFAULT_CURRENT) {
|
||||
query.setCurrent(DEFAULT_CURRENT);
|
||||
}
|
||||
if (query.getSize() == null || query.getSize() <= 0) {
|
||||
query.setSize(DEFAULT_SIZE);
|
||||
}
|
||||
if (query.getSize() > MAX_SIZE) {
|
||||
query.setSize(MAX_SIZE);
|
||||
}
|
||||
return query;
|
||||
}
|
||||
|
||||
private void normalizeRegionCodeCondition(Map<String, Object> params) {
|
||||
Object regionCode = params.remove("regionCode");
|
||||
if (Func.isNotEmpty(regionCode) && Func.isEmpty(params.get("districtCode"))) {
|
||||
params.put("districtCode", regionCode);
|
||||
}
|
||||
}
|
||||
|
||||
private void applyDataSourceCondition(QueryWrapper<PortTerminal> queryWrapper, Object dataSource) {
|
||||
String value = Func.toStrWithEmpty(dataSource, "");
|
||||
if (Func.isEmpty(value)) {
|
||||
return;
|
||||
}
|
||||
if (SOURCE_INITIAL.equals(value)) {
|
||||
queryWrapper.in("data_source", SOURCE_INITIAL, "初始导入");
|
||||
} else {
|
||||
queryWrapper.eq("data_source", value);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.system.controller;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import jakarta.validation.Valid;
|
||||
import lombok.AllArgsConstructor;
|
||||
import org.springblade.core.boot.ctrl.BladeController;
|
||||
import org.springblade.core.excel.util.ExcelUtil;
|
||||
import org.springblade.core.mp.support.Condition;
|
||||
import org.springblade.core.mp.support.Query;
|
||||
import org.springblade.core.secure.annotation.PreAuth;
|
||||
import org.springblade.core.tenant.annotation.NonDS;
|
||||
import org.springblade.core.tool.api.R;
|
||||
import org.springblade.core.tool.utils.DateUtil;
|
||||
import org.springblade.core.tool.utils.Func;
|
||||
import org.springblade.common.excel.ImportFailureExcelUtil;
|
||||
import org.springblade.system.excel.ImportFailureException;
|
||||
import org.springblade.system.excel.PortTerminalExcel;
|
||||
import org.springblade.system.excel.PortTerminalExportExcel;
|
||||
import org.springblade.system.excel.PortTerminalImporter;
|
||||
import org.springblade.system.pojo.entity.PortTerminal;
|
||||
import org.springblade.system.pojo.vo.PortTerminalVO;
|
||||
import org.springblade.system.service.IPortTerminalService;
|
||||
import org.springblade.system.wrapper.PortTerminalWrapper;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 港口码头主数据 控制器
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@NonDS
|
||||
@RestController
|
||||
@AllArgsConstructor
|
||||
@PreAuth(menu = "port_terminal")
|
||||
@RequestMapping("/port-terminal")
|
||||
@Tag(name = "港口码头主数据", description = "港口码头主数据")
|
||||
public class PortTerminalController extends BladeController {
|
||||
|
||||
private static final int DEFAULT_CURRENT = 1;
|
||||
private static final int DEFAULT_SIZE = 10;
|
||||
private static final int MAX_SIZE = 100;
|
||||
private static final String SOURCE_INITIAL = "初始化录入";
|
||||
private static final String SOURCE_INITIAL_IMPORT = "初始化导入";
|
||||
private static final String SOURCE_INITIAL_OLD = "初始导入";
|
||||
private static final String SOURCE_MANUAL = "手动录入";
|
||||
private static final String SOURCE_MANUAL_OLD = "手工导入";
|
||||
|
||||
private final IPortTerminalService portTerminalService;
|
||||
|
||||
/**
|
||||
* 详情
|
||||
*/
|
||||
@GetMapping("/detail")
|
||||
@ApiOperationSupport(order = 1)
|
||||
@Operation(summary = "详情", description = "传入portTerminal")
|
||||
public R<PortTerminalVO> detail(PortTerminal portTerminal) {
|
||||
if (Func.isEmpty(portTerminal.getId())) {
|
||||
return R.fail("主键不能为空");
|
||||
}
|
||||
PortTerminal detail = portTerminalService.getById(portTerminal.getId());
|
||||
if (Func.isEmpty(detail)) {
|
||||
return R.fail("港口码头不存在");
|
||||
}
|
||||
detail.setDataSource(normalizeDataSource(detail.getDataSource()));
|
||||
return R.data(PortTerminalWrapper.build().entityVO(detail));
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页
|
||||
*/
|
||||
@GetMapping("/list")
|
||||
@ApiOperationSupport(order = 2)
|
||||
@Operation(summary = "分页", description = "传入portTerminal")
|
||||
public R<IPage<PortTerminalVO>> list(PortTerminalVO portTerminal, Query query) {
|
||||
IPage<PortTerminalVO> pages = portTerminalService.selectPortTerminalPage(Condition.getPage(normalizeQuery(query)), portTerminal);
|
||||
return R.data(pages);
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增或修改
|
||||
*/
|
||||
@PostMapping("/submit")
|
||||
@ApiOperationSupport(order = 3)
|
||||
@Operation(summary = "新增或修改", description = "传入portTerminal")
|
||||
public R submit(@Valid @RequestBody PortTerminal portTerminal) {
|
||||
return R.status(portTerminalService.submit(portTerminal));
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除
|
||||
*/
|
||||
@PostMapping("/remove")
|
||||
@ApiOperationSupport(order = 4)
|
||||
@Operation(summary = "逻辑删除", description = "传入ids")
|
||||
public R remove(@Parameter(description = "主键集合", required = true) @RequestParam String ids) {
|
||||
if (Func.isEmpty(ids)) {
|
||||
return R.fail("主键不能为空");
|
||||
}
|
||||
return R.status(portTerminalService.deleteLogic(Func.toLongList(ids)));
|
||||
}
|
||||
|
||||
/**
|
||||
* 启用或停用
|
||||
*/
|
||||
@PostMapping("/status")
|
||||
@ApiOperationSupport(order = 5)
|
||||
@Operation(summary = "启用或停用", description = "传入id和status")
|
||||
public R status(@Parameter(description = "主键", required = true) @RequestParam Long id,
|
||||
@Parameter(description = "状态", required = true) @RequestParam Integer status) {
|
||||
return R.status(portTerminalService.changeStatus(id, status));
|
||||
}
|
||||
|
||||
/**
|
||||
* 上级港口下拉数据源
|
||||
*/
|
||||
@GetMapping("/port-select")
|
||||
@ApiOperationSupport(order = 6)
|
||||
@Operation(summary = "上级港口下拉数据源")
|
||||
public R<List<PortTerminal>> portSelect() {
|
||||
List<PortTerminal> ports = portTerminalService.selectEnabledPorts();
|
||||
ports.forEach(port -> port.setDataSource(normalizeDataSource(port.getDataSource())));
|
||||
return R.data(ports);
|
||||
}
|
||||
|
||||
/**
|
||||
* 导入港口码头主数据
|
||||
*/
|
||||
@PostMapping("/import-port-terminal")
|
||||
@ApiOperationSupport(order = 7)
|
||||
@Operation(summary = "导入港口码头主数据", description = "传入excel")
|
||||
public R importPortTerminal(MultipartFile file, HttpServletResponse response) {
|
||||
if (file == null || file.isEmpty()) {
|
||||
return R.fail("上传文件不能为空");
|
||||
}
|
||||
String fileName = Func.toStrWithEmpty(file.getOriginalFilename(), "").toLowerCase();
|
||||
if (!fileName.endsWith(".xls") && !fileName.endsWith(".xlsx")) {
|
||||
return R.fail("请上传 .xls,.xlsx 标准格式文件");
|
||||
}
|
||||
try {
|
||||
portTerminalService.importPortTerminal(ExcelUtil.read(file, PortTerminalExcel.class));
|
||||
} catch (ImportFailureException exception) {
|
||||
// 全失败即整批回滚,导出原表全部数据并标注错误,用户修正后重新导入。
|
||||
exportFailure(response, exception.getFailureList());
|
||||
return null;
|
||||
}
|
||||
return R.success("操作成功");
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出导入失败明细,内容为原表全部数据并在末尾追加失败原因列。
|
||||
* <p>
|
||||
* 失败数据仅标红出错单元格与失败原因列,表头保持默认样式。
|
||||
*/
|
||||
private void exportFailure(HttpServletResponse response, List<?> failureList) {
|
||||
ImportFailureExcelUtil.exportFailureReasonOnly(response, "港口码头主数据导入失败明细" + DateUtil.time(), "导入失败明细", failureList, PortTerminalExcel.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出港口码头主数据
|
||||
*/
|
||||
@GetMapping("/export-port-terminal")
|
||||
@ApiOperationSupport(order = 8)
|
||||
@Operation(summary = "导出港口码头主数据")
|
||||
public void exportPortTerminal(@Parameter(hidden = true) @RequestParam Map<String, Object> portTerminal, HttpServletResponse response) {
|
||||
Object ids = portTerminal.remove("ids");
|
||||
Object dataSource = portTerminal.remove("dataSource");
|
||||
portTerminal.remove("Blade-Auth");
|
||||
portTerminal.remove("Authorization");
|
||||
portTerminal.remove("access_token");
|
||||
normalizeRegionCodeCondition(portTerminal);
|
||||
QueryWrapper<PortTerminal> queryWrapper = Condition.getQueryWrapper(portTerminal, PortTerminal.class);
|
||||
applyDataSourceCondition(queryWrapper, dataSource);
|
||||
if (Func.isNotEmpty(ids)) {
|
||||
queryWrapper.lambda().in(PortTerminal::getId, Func.toLongList(ids.toString()));
|
||||
}
|
||||
List<PortTerminalExportExcel> list = portTerminalService.exportPortTerminal(queryWrapper);
|
||||
ExcelUtil.export(response, "港口码头主数据" + DateUtil.time(), "港口码头主数据表", list, PortTerminalExportExcel.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出模板
|
||||
*/
|
||||
@GetMapping("/export-template")
|
||||
@ApiOperationSupport(order = 9)
|
||||
@Operation(summary = "导出模板")
|
||||
public void exportTemplate(HttpServletResponse response) {
|
||||
List<PortTerminalExcel> list = new ArrayList<>();
|
||||
ExcelUtil.export(response, "港口码头主数据模板", "港口码头主数据表", list, PortTerminalExcel.class);
|
||||
}
|
||||
|
||||
private Query normalizeQuery(Query query) {
|
||||
if (query == null) {
|
||||
query = new Query();
|
||||
}
|
||||
if (query.getCurrent() == null || query.getCurrent() < DEFAULT_CURRENT) {
|
||||
query.setCurrent(DEFAULT_CURRENT);
|
||||
}
|
||||
if (query.getSize() == null || query.getSize() <= 0) {
|
||||
query.setSize(DEFAULT_SIZE);
|
||||
}
|
||||
if (query.getSize() > MAX_SIZE) {
|
||||
query.setSize(MAX_SIZE);
|
||||
}
|
||||
return query;
|
||||
}
|
||||
|
||||
private void normalizeRegionCodeCondition(Map<String, Object> params) {
|
||||
Object regionCode = params.remove("regionCode");
|
||||
if (Func.isNotEmpty(regionCode) && Func.isEmpty(params.get("districtCode"))) {
|
||||
params.put("districtCode", regionCode);
|
||||
}
|
||||
}
|
||||
|
||||
private void applyDataSourceCondition(QueryWrapper<PortTerminal> queryWrapper, Object dataSource) {
|
||||
String value = Func.toStrWithEmpty(dataSource, "");
|
||||
if (Func.isEmpty(value)) {
|
||||
return;
|
||||
}
|
||||
if (SOURCE_INITIAL.equals(value)) {
|
||||
queryWrapper.in("data_source", SOURCE_INITIAL, SOURCE_INITIAL_IMPORT, SOURCE_INITIAL_OLD);
|
||||
} else if (SOURCE_MANUAL.equals(value)) {
|
||||
queryWrapper.in("data_source", SOURCE_MANUAL, SOURCE_MANUAL_OLD);
|
||||
} else {
|
||||
queryWrapper.eq("data_source", value);
|
||||
}
|
||||
}
|
||||
|
||||
private String normalizeDataSource(String dataSource) {
|
||||
if (SOURCE_INITIAL_IMPORT.equals(dataSource) || SOURCE_INITIAL_OLD.equals(dataSource)) {
|
||||
return SOURCE_INITIAL;
|
||||
}
|
||||
return SOURCE_MANUAL_OLD.equals(dataSource) ? SOURCE_MANUAL : dataSource;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+4
-3
@@ -44,6 +44,7 @@ import org.springblade.core.tool.api.R;
|
||||
import org.springblade.core.tool.utils.DateUtil;
|
||||
import org.springblade.core.tool.utils.Func;
|
||||
import org.springblade.system.excel.RailwayStationExcel;
|
||||
import org.springblade.system.excel.RailwayStationExportExcel;
|
||||
import org.springblade.system.excel.RailwayStationImporter;
|
||||
import org.springblade.system.pojo.entity.RailwayStation;
|
||||
import org.springblade.system.pojo.vo.RailwayStationVO;
|
||||
@@ -161,7 +162,7 @@ public class RailwayStationController extends BladeController {
|
||||
}
|
||||
List<RailwayStationExcel> failureList = railwayStationService.importRailwayStation(ExcelUtil.read(file, RailwayStationExcel.class));
|
||||
if (Func.isNotEmpty(failureList)) {
|
||||
org.springblade.common.excel.ImportFailureExcelUtil.export(response, "铁路车站主数据导入失败明细" + DateUtil.time(), "导入失败明细", failureList, RailwayStationExcel.class);
|
||||
org.springblade.common.excel.ImportFailureExcelUtil.exportFailureReasonOnly(response, "铁路车站主数据导入失败明细" + DateUtil.time(), "导入失败明细", failureList, RailwayStationExcel.class);
|
||||
return null;
|
||||
}
|
||||
return R.success("操作成功");
|
||||
@@ -185,8 +186,8 @@ public class RailwayStationController extends BladeController {
|
||||
if (Func.isNotEmpty(ids)) {
|
||||
queryWrapper.lambda().in(RailwayStation::getId, Func.toLongList(ids.toString()));
|
||||
}
|
||||
List<RailwayStationExcel> list = railwayStationService.exportRailwayStation(queryWrapper);
|
||||
ExcelUtil.export(response, "铁路车站主数据" + DateUtil.time(), "铁路车站主数据表", list, RailwayStationExcel.class);
|
||||
List<RailwayStationExportExcel> list = railwayStationService.exportRailwayStation(queryWrapper);
|
||||
ExcelUtil.export(response, "铁路车站主数据" + DateUtil.time(), "铁路车站主数据表", list, RailwayStationExportExcel.class);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+3
-2
@@ -48,6 +48,7 @@ import org.springblade.core.tool.api.R;
|
||||
import org.springblade.core.tool.utils.DateUtil;
|
||||
import org.springblade.system.pojo.entity.Region;
|
||||
import org.springblade.system.excel.RegionExcel;
|
||||
import org.springblade.system.excel.RegionExportExcel;
|
||||
import org.springblade.system.excel.RegionImporter;
|
||||
import org.springblade.system.service.IRegionService;
|
||||
import org.springblade.system.pojo.vo.RegionVO;
|
||||
@@ -206,8 +207,8 @@ public class RegionController extends BladeController {
|
||||
@Operation(summary = "导出行政区划", description = "传入user")
|
||||
public void exportRegion(@Parameter(hidden = true) @RequestParam Map<String, Object> region, HttpServletResponse response) {
|
||||
QueryWrapper<Region> queryWrapper = Condition.getQueryWrapper(region, Region.class);
|
||||
List<RegionExcel> list = regionService.exportRegion(queryWrapper);
|
||||
ExcelUtil.export(response, "行政区划数据" + DateUtil.time(), "行政区划数据表", list, RegionExcel.class);
|
||||
List<RegionExportExcel> list = regionService.exportRegion(queryWrapper);
|
||||
ExcelUtil.export(response, "行政区划数据" + DateUtil.time(), "行政区划数据表", list, RegionExportExcel.class);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+30
@@ -54,7 +54,9 @@ import org.springblade.core.tool.utils.StringPool;
|
||||
import org.springblade.system.excel.UserExcel;
|
||||
import org.springblade.system.excel.UserImporter;
|
||||
import org.springblade.system.pojo.entity.User;
|
||||
import org.springblade.system.pojo.vo.OaPersonSyncPageVO;
|
||||
import org.springblade.system.pojo.vo.UserVO;
|
||||
import org.springblade.system.service.IOASyncService;
|
||||
import org.springblade.system.service.IUserService;
|
||||
import org.springblade.system.wrapper.UserWrapper;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
@@ -77,6 +79,7 @@ import java.util.Map;
|
||||
public class UserController {
|
||||
|
||||
private final IUserService userService;
|
||||
private final IOASyncService oaSyncService;
|
||||
|
||||
/**
|
||||
* 查询单条
|
||||
@@ -157,6 +160,19 @@ public class UserController {
|
||||
return R.status(userService.submit(user));
|
||||
}
|
||||
|
||||
/**
|
||||
* 从OA按页同步人员,并按公司/部门生成组织后绑定到三级部门。
|
||||
*/
|
||||
@IsAdmin
|
||||
@PostMapping("/sync-iam-accounts")
|
||||
@ApiOperationSupport(order = 6)
|
||||
@Operation(summary = "同步OA人员")
|
||||
public R<OaPersonSyncPageVO> syncIamAccounts(
|
||||
@RequestParam(defaultValue = "1") Integer current,
|
||||
@RequestParam(defaultValue = "20") Integer size) {
|
||||
return R.data(oaSyncService.syncPersonFromUserList(current, size));
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改
|
||||
*/
|
||||
@@ -216,6 +232,20 @@ public class UserController {
|
||||
return R.status(temp);
|
||||
}
|
||||
|
||||
/**
|
||||
* 当前用户设置/重置登录密码(小程序首次设密、短信验证后改密)
|
||||
* <p>
|
||||
* 对外路径:/blade-system/user/password ;网关别名 /blade-user/password 亦可到达。
|
||||
*/
|
||||
@PostMapping("/password")
|
||||
@ApiOperationSupport(order = 10)
|
||||
@Operation(summary = "设置登录密码", description = "当前登录用户设置密码,无需原密码")
|
||||
public R password(BladeUser user,
|
||||
@Parameter(description = "新密码", required = true) @RequestParam String password,
|
||||
@Parameter(description = "确认密码", required = true) @RequestParam String password2) {
|
||||
return R.status(userService.setPassword(user.getUserId(), password, password2));
|
||||
}
|
||||
|
||||
/**
|
||||
* 管理员修改密码
|
||||
*/
|
||||
|
||||
+93
@@ -0,0 +1,93 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.system.controller;
|
||||
|
||||
import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.validation.Valid;
|
||||
import lombok.AllArgsConstructor;
|
||||
import org.springblade.core.tenant.annotation.NonDS;
|
||||
import org.springblade.core.tool.api.R;
|
||||
import org.springblade.system.pojo.dto.PhoneChangeDTO;
|
||||
import org.springblade.system.pojo.dto.PhoneVerifyDTO;
|
||||
import org.springblade.system.service.IUserPhoneService;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/**
|
||||
* 用户手机号变更(小程序「修改手机号」)
|
||||
* <p>
|
||||
* 对外路径:/blade-system/user/phone/** ;网关别名 /blade-user/phone/** 亦可到达。
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@NonDS
|
||||
@RestController
|
||||
@AllArgsConstructor
|
||||
@RequestMapping("/user/phone")
|
||||
@Tag(name = "用户手机号", description = "修改手机号")
|
||||
public class UserPhoneController {
|
||||
|
||||
private final IUserPhoneService userPhoneService;
|
||||
|
||||
/**
|
||||
* 发送短信验证码(需登录)
|
||||
* <p>
|
||||
* 当前手机号、未占用的新手机号均可发送;新号若已被其他账号占用则拒绝。
|
||||
*/
|
||||
@PostMapping("/send-code")
|
||||
@ApiOperationSupport(order = 1)
|
||||
@Operation(summary = "发送手机号变更验证码", description = "传入明文手机号,返回短信校验 id")
|
||||
public R sendCode(@Parameter(description = "手机号", required = true) @RequestParam String phone) {
|
||||
return userPhoneService.sendCode(phone);
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验原手机号验证码(修改手机号第 1 步)
|
||||
*/
|
||||
@PostMapping("/verify-old")
|
||||
@ApiOperationSupport(order = 2)
|
||||
@Operation(summary = "校验原手机号验证码", description = "传入发送验证码返回的 id 与验证码")
|
||||
public R verifyOld(@Valid @RequestBody PhoneVerifyDTO phoneVerify) {
|
||||
return R.status(userPhoneService.verifyOldPhone(phoneVerify));
|
||||
}
|
||||
|
||||
/**
|
||||
* 绑定新手机号(修改手机号第 3 步,需先完成 verify-old)
|
||||
*/
|
||||
@PostMapping("/change")
|
||||
@ApiOperationSupport(order = 3)
|
||||
@Operation(summary = "更换手机号", description = "传入新手机号及短信校验 id、验证码")
|
||||
public R change(@Valid @RequestBody PhoneChangeDTO phoneChange) {
|
||||
return R.status(userPhoneService.changePhone(phoneChange));
|
||||
}
|
||||
|
||||
}
|
||||
+25
-3
@@ -64,11 +64,11 @@ public interface UserConvert {
|
||||
@Mapping(target = "password", ignore = true)
|
||||
@Mapping(target = "birthday", ignore = true)
|
||||
@Mapping(target = "sex", ignore = true)
|
||||
@Mapping(target = "account", ignore = true)
|
||||
@Mapping(source = "workcode", target = "code")
|
||||
@Mapping(source = "lastname", target = "name")
|
||||
@Mapping(source = "lastname", target = "realName")
|
||||
@Mapping(source = "mobile", target = "phone")
|
||||
@Mapping(source = "mobile", target = "account")
|
||||
@Mapping(source = "email", target = "email")
|
||||
User baseConvert(OAPersonResponse person);
|
||||
|
||||
@@ -86,7 +86,7 @@ public interface UserConvert {
|
||||
*/
|
||||
default User person2user(OAPersonResponse person, String defaultPassword) {
|
||||
User user = baseConvert(person);
|
||||
|
||||
user.setAccount(resolveAccount(person));
|
||||
// 密码
|
||||
user.setPassword(defaultPassword);
|
||||
// 性别
|
||||
@@ -102,6 +102,28 @@ public interface UserConvert {
|
||||
return user;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析本系统登录账号:优先 OA loginid,其次工号,最后手机号
|
||||
*
|
||||
* @param person OA人员
|
||||
* @return 账号,无法识别时返回 null
|
||||
*/
|
||||
default String resolveAccount(OAPersonResponse person) {
|
||||
if (person == null) {
|
||||
return null;
|
||||
}
|
||||
if (StringUtils.isNotBlank(person.getLoginid())) {
|
||||
return person.getLoginid().trim();
|
||||
}
|
||||
if (StringUtils.isNotBlank(person.getWorkcode())) {
|
||||
return person.getWorkcode().trim();
|
||||
}
|
||||
if (StringUtils.isNotBlank(person.getMobile())) {
|
||||
return person.getMobile().trim();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* oa人员转本系统用户部门
|
||||
* @param person
|
||||
@@ -120,7 +142,7 @@ public interface UserConvert {
|
||||
// 排序
|
||||
userDept.setSort(OAUtils.parseInt(person.getDsporder()));
|
||||
// 用户id
|
||||
userDept.setUserId(userMap.get(person.getMobile()));
|
||||
userDept.setUserId(userMap.get(resolveAccount(person)));
|
||||
userDept.setSyncTime(new Date());
|
||||
return userDept;
|
||||
}
|
||||
|
||||
+2
-14
@@ -53,9 +53,6 @@ public class AirportMasterExcel implements Serializable {
|
||||
@ExcelIgnore
|
||||
private Long id;
|
||||
|
||||
@ExcelProperty("编码")
|
||||
private String code;
|
||||
|
||||
@ExcelProperty("IATA编码*")
|
||||
private String iataCode;
|
||||
|
||||
@@ -77,7 +74,7 @@ public class AirportMasterExcel implements Serializable {
|
||||
@ExcelProperty("所属区县*")
|
||||
private String districtName;
|
||||
|
||||
@ExcelProperty("详细地址")
|
||||
@ExcelProperty("详细地址*")
|
||||
private String detailAddress;
|
||||
|
||||
@ExcelProperty("经度*")
|
||||
@@ -88,19 +85,10 @@ public class AirportMasterExcel implements Serializable {
|
||||
@NumberFormat("0.000000")
|
||||
private BigDecimal latitude;
|
||||
|
||||
@ExcelProperty("行政区划编号")
|
||||
private String regionCode;
|
||||
|
||||
@ExcelProperty("数据来源")
|
||||
private String dataSource;
|
||||
|
||||
@ExcelProperty("启停状态")
|
||||
private String statusName;
|
||||
|
||||
@ExcelProperty("备注")
|
||||
private String remark;
|
||||
|
||||
@ExcelProperty
|
||||
@ExcelIgnore
|
||||
private String errorMessage;
|
||||
|
||||
}
|
||||
|
||||
+118
@@ -0,0 +1,118 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.system.excel;
|
||||
|
||||
import cn.idev.excel.annotation.ExcelIgnore;
|
||||
import cn.idev.excel.annotation.ExcelProperty;
|
||||
import cn.idev.excel.annotation.format.DateTimeFormat;
|
||||
import cn.idev.excel.annotation.format.NumberFormat;
|
||||
import cn.idev.excel.annotation.write.style.ColumnWidth;
|
||||
import cn.idev.excel.annotation.write.style.ContentRowHeight;
|
||||
import cn.idev.excel.annotation.write.style.HeadRowHeight;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 空港机场主数据导出 Excel
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@Data
|
||||
@ColumnWidth(18)
|
||||
@HeadRowHeight(20)
|
||||
@ContentRowHeight(18)
|
||||
public class AirportMasterExportExcel implements Serializable {
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@ExcelIgnore
|
||||
private Long id;
|
||||
|
||||
@ExcelProperty("编码")
|
||||
private String code;
|
||||
|
||||
@ExcelProperty("IATA编码*")
|
||||
private String iataCode;
|
||||
|
||||
@ExcelProperty("ICAO代码*")
|
||||
private String icaoCode;
|
||||
|
||||
@ExcelProperty("机场标准名称*")
|
||||
private String name;
|
||||
|
||||
@ExcelProperty("机场简称")
|
||||
private String shortName;
|
||||
|
||||
@ExcelProperty("所属省份*")
|
||||
private String provinceName;
|
||||
|
||||
@ExcelProperty("所属城市*")
|
||||
private String cityName;
|
||||
|
||||
@ExcelProperty("所属区县*")
|
||||
private String districtName;
|
||||
|
||||
@ExcelProperty("详细地址")
|
||||
private String detailAddress;
|
||||
|
||||
@ExcelProperty("经度*")
|
||||
@NumberFormat("0.000000")
|
||||
private BigDecimal longitude;
|
||||
|
||||
@ExcelProperty("纬度*")
|
||||
@NumberFormat("0.000000")
|
||||
private BigDecimal latitude;
|
||||
|
||||
@ExcelProperty("行政区划编号")
|
||||
private String regionCode;
|
||||
|
||||
@ExcelProperty("数据来源")
|
||||
private String dataSource;
|
||||
|
||||
@ExcelProperty("启停状态")
|
||||
private String statusName;
|
||||
|
||||
@ExcelProperty("备注")
|
||||
private String remark;
|
||||
|
||||
@ExcelProperty("更新人")
|
||||
private String updateUserName;
|
||||
|
||||
@ExcelProperty("更新时间")
|
||||
@DateTimeFormat("yyyy-MM-dd HH:mm:ss")
|
||||
private Date updateTime;
|
||||
|
||||
@ExcelProperty("创建时间")
|
||||
@DateTimeFormat("yyyy-MM-dd HH:mm:ss")
|
||||
private Date createTime;
|
||||
|
||||
@ExcelIgnore
|
||||
private String errorMessage;
|
||||
}
|
||||
+4
@@ -34,6 +34,7 @@ import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/**
|
||||
* 费用项 Excel
|
||||
@@ -60,6 +61,9 @@ public class FeeItemExcel implements Serializable {
|
||||
@ExcelProperty("*费用项")
|
||||
private String name;
|
||||
|
||||
@ExcelProperty("*税率")
|
||||
private BigDecimal taxRate;
|
||||
|
||||
@ExcelIgnore
|
||||
private String errorMessage;
|
||||
|
||||
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* This file is part of the TMS extension for BladeX.
|
||||
*/
|
||||
package org.springblade.system.excel;
|
||||
|
||||
import cn.idev.excel.annotation.ExcelProperty;
|
||||
import cn.idev.excel.annotation.format.DateTimeFormat;
|
||||
import cn.idev.excel.annotation.write.style.ColumnWidth;
|
||||
import cn.idev.excel.annotation.write.style.ContentRowHeight;
|
||||
import cn.idev.excel.annotation.write.style.HeadRowHeight;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 费用项导出 Excel。
|
||||
*
|
||||
* <p>该模型仅用于导出,避免导出字段变更影响导入模板。</p>
|
||||
*/
|
||||
@Data
|
||||
@ColumnWidth(22)
|
||||
@HeadRowHeight(20)
|
||||
@ContentRowHeight(18)
|
||||
public class FeeItemExportExcel implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@ExcelProperty("费用类型")
|
||||
private String feeCategory;
|
||||
|
||||
@ExcelProperty("费用项代码")
|
||||
private String englishName;
|
||||
|
||||
@ExcelProperty("费用项")
|
||||
private String name;
|
||||
|
||||
@ExcelProperty("税率")
|
||||
private BigDecimal taxRate;
|
||||
|
||||
@ExcelProperty("备注")
|
||||
private String remark;
|
||||
|
||||
@ExcelProperty("组织")
|
||||
private String createDeptName;
|
||||
|
||||
@ExcelProperty("更新人")
|
||||
private String updateUserName;
|
||||
|
||||
@ExcelProperty("更新时间")
|
||||
@DateTimeFormat("yyyy-MM-dd HH:mm:ss")
|
||||
private Date updateTime;
|
||||
|
||||
@ExcelProperty("创建时间")
|
||||
@DateTimeFormat("yyyy-MM-dd HH:mm:ss")
|
||||
private Date createTime;
|
||||
|
||||
@ExcelProperty("状态")
|
||||
private String statusName;
|
||||
}
|
||||
+4
@@ -33,6 +33,7 @@ import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/**
|
||||
* 费用项导入失败 Excel
|
||||
@@ -56,6 +57,9 @@ public class FeeItemImportFailureExcel implements Serializable {
|
||||
@ExcelProperty("*费用项")
|
||||
private String name;
|
||||
|
||||
@ExcelProperty("*税率")
|
||||
private BigDecimal taxRate;
|
||||
|
||||
@ExcelProperty("导入失败原因")
|
||||
private String failureReason;
|
||||
|
||||
|
||||
+19
-22
@@ -53,54 +53,51 @@ public class PortTerminalExcel implements Serializable {
|
||||
@ExcelIgnore
|
||||
private Long id;
|
||||
|
||||
@ExcelProperty("编码")
|
||||
private String code;
|
||||
@ExcelProperty("港口编码*")
|
||||
private String portCode;
|
||||
|
||||
@ExcelProperty("港口/码头名称")
|
||||
@ExcelProperty("码头编码(如为港口则不需填写)")
|
||||
private String terminalCode;
|
||||
|
||||
@ExcelProperty("港口/码头名称*")
|
||||
private String name;
|
||||
|
||||
@ExcelProperty("类型")
|
||||
@ExcelProperty("类型*")
|
||||
private String category;
|
||||
|
||||
@ExcelProperty("上级港口")
|
||||
@ExcelProperty("上级港口(如为港口则不需填写)")
|
||||
private String parentName;
|
||||
|
||||
@ExcelProperty("上级港口编码")
|
||||
@ExcelProperty("上级港口编码(如为港口则不需填写)")
|
||||
private String parentCode;
|
||||
|
||||
@ExcelProperty("国家")
|
||||
@ExcelProperty("国家*")
|
||||
private String country;
|
||||
|
||||
@ExcelProperty("城市")
|
||||
@ExcelProperty("所属省份*")
|
||||
private String provinceName;
|
||||
|
||||
@ExcelProperty("城市*")
|
||||
private String city;
|
||||
|
||||
@ExcelProperty("区县")
|
||||
@ExcelProperty("区县*")
|
||||
private String districtName;
|
||||
|
||||
@ExcelProperty("行政区划编码")
|
||||
private String regionCode;
|
||||
|
||||
@ExcelProperty("详细地址")
|
||||
@ExcelProperty("详细地址*")
|
||||
private String detailAddress;
|
||||
|
||||
@ExcelProperty("经度")
|
||||
@ExcelProperty("经度*")
|
||||
@NumberFormat("0.000000")
|
||||
private BigDecimal longitude;
|
||||
|
||||
@ExcelProperty("纬度")
|
||||
@ExcelProperty("纬度*")
|
||||
@NumberFormat("0.000000")
|
||||
private BigDecimal latitude;
|
||||
|
||||
@ExcelProperty("数据来源")
|
||||
private String dataSource;
|
||||
|
||||
@ExcelProperty("启停状态")
|
||||
private String statusName;
|
||||
|
||||
@ExcelProperty("备注")
|
||||
private String remark;
|
||||
|
||||
@ExcelProperty
|
||||
@ExcelIgnore
|
||||
private String errorMessage;
|
||||
|
||||
}
|
||||
|
||||
+96
@@ -0,0 +1,96 @@
|
||||
package org.springblade.system.excel;
|
||||
|
||||
import cn.idev.excel.annotation.ExcelIgnore;
|
||||
import cn.idev.excel.annotation.ExcelProperty;
|
||||
import cn.idev.excel.annotation.format.DateTimeFormat;
|
||||
import cn.idev.excel.annotation.format.NumberFormat;
|
||||
import cn.idev.excel.annotation.write.style.ColumnWidth;
|
||||
import cn.idev.excel.annotation.write.style.ContentRowHeight;
|
||||
import cn.idev.excel.annotation.write.style.HeadRowHeight;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 港口码头主数据导出 Excel
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@Data
|
||||
@ColumnWidth(18)
|
||||
@HeadRowHeight(20)
|
||||
@ContentRowHeight(18)
|
||||
public class PortTerminalExportExcel implements Serializable {
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@ExcelIgnore
|
||||
private Long id;
|
||||
|
||||
@ExcelProperty("编码")
|
||||
private String code;
|
||||
|
||||
@ExcelProperty("港口/码头名称")
|
||||
private String name;
|
||||
|
||||
@ExcelProperty("类型")
|
||||
private String category;
|
||||
|
||||
@ExcelProperty("上级港口")
|
||||
private String parentName;
|
||||
|
||||
@ExcelProperty("上级港口编码")
|
||||
private String parentCode;
|
||||
|
||||
@ExcelProperty("国家")
|
||||
private String country;
|
||||
|
||||
@ExcelProperty("所属省份")
|
||||
private String provinceName;
|
||||
|
||||
@ExcelProperty("城市")
|
||||
private String city;
|
||||
|
||||
@ExcelProperty("区县")
|
||||
private String districtName;
|
||||
|
||||
@ExcelProperty("行政区划编码")
|
||||
private String regionCode;
|
||||
|
||||
@ExcelProperty("详细地址")
|
||||
private String detailAddress;
|
||||
|
||||
@ExcelProperty("经度")
|
||||
@NumberFormat("0.000000")
|
||||
private BigDecimal longitude;
|
||||
|
||||
@ExcelProperty("纬度")
|
||||
@NumberFormat("0.000000")
|
||||
private BigDecimal latitude;
|
||||
|
||||
@ExcelProperty("数据来源")
|
||||
private String dataSource;
|
||||
|
||||
@ExcelProperty("启停状态")
|
||||
private String statusName;
|
||||
|
||||
@ExcelProperty("备注")
|
||||
private String remark;
|
||||
|
||||
@ExcelProperty("更新人")
|
||||
private String updateUserName;
|
||||
|
||||
@ExcelProperty("更新时间")
|
||||
@DateTimeFormat("yyyy-MM-dd HH:mm:ss")
|
||||
private Date updateTime;
|
||||
|
||||
@ExcelProperty("创建时间")
|
||||
@DateTimeFormat("yyyy-MM-dd HH:mm:ss")
|
||||
private Date createTime;
|
||||
|
||||
@ExcelIgnore
|
||||
private String errorMessage;
|
||||
}
|
||||
+5
-17
@@ -51,13 +51,10 @@ public class RailwayStationExcel implements Serializable {
|
||||
@ExcelIgnore
|
||||
private Long id;
|
||||
|
||||
@ExcelProperty("编码")
|
||||
private String code;
|
||||
|
||||
@ExcelProperty("TMIS国标编码*")
|
||||
private String tmisCode;
|
||||
|
||||
@ExcelProperty("电报码*")
|
||||
@ExcelProperty("电报略码*")
|
||||
private String telegraphCode;
|
||||
|
||||
@ExcelProperty("车站名称*")
|
||||
@@ -75,28 +72,19 @@ public class RailwayStationExcel implements Serializable {
|
||||
@ExcelProperty("所属区县*")
|
||||
private String districtName;
|
||||
|
||||
@ExcelProperty("详细地址")
|
||||
@ExcelProperty("详细地址*")
|
||||
private String detailAddress;
|
||||
|
||||
@ExcelProperty("经度")
|
||||
@ExcelProperty("经度*")
|
||||
private String longitude;
|
||||
|
||||
@ExcelProperty("纬度")
|
||||
@ExcelProperty("纬度*")
|
||||
private String latitude;
|
||||
|
||||
@ExcelProperty("行政区划编号")
|
||||
private String regionCode;
|
||||
|
||||
@ExcelProperty("数据来源")
|
||||
private String dataSource;
|
||||
|
||||
@ExcelProperty("启停状态")
|
||||
private String statusName;
|
||||
|
||||
@ExcelProperty("备注")
|
||||
private String remark;
|
||||
|
||||
@ExcelProperty
|
||||
@ExcelIgnore
|
||||
private String errorMessage;
|
||||
|
||||
}
|
||||
|
||||
+108
@@ -0,0 +1,108 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.system.excel;
|
||||
|
||||
import cn.idev.excel.annotation.ExcelIgnore;
|
||||
import cn.idev.excel.annotation.ExcelProperty;
|
||||
import cn.idev.excel.annotation.format.DateTimeFormat;
|
||||
import cn.idev.excel.annotation.write.style.ColumnWidth;
|
||||
import cn.idev.excel.annotation.write.style.ContentRowHeight;
|
||||
import cn.idev.excel.annotation.write.style.HeadRowHeight;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 铁路车站主数据导出 Excel
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@Data
|
||||
@ColumnWidth(18)
|
||||
@HeadRowHeight(20)
|
||||
@ContentRowHeight(18)
|
||||
public class RailwayStationExportExcel implements Serializable {
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@ExcelProperty("编码")
|
||||
private String code;
|
||||
|
||||
@ExcelProperty("TMIS国标编码*")
|
||||
private String tmisCode;
|
||||
|
||||
@ExcelProperty("电报略码*")
|
||||
private String telegraphCode;
|
||||
|
||||
@ExcelProperty("车站名称*")
|
||||
private String name;
|
||||
|
||||
@ExcelProperty("所属省份*")
|
||||
private String provinceName;
|
||||
|
||||
@ExcelProperty("所属城市*")
|
||||
private String cityName;
|
||||
|
||||
@ExcelProperty("所属区县*")
|
||||
private String districtName;
|
||||
|
||||
@ExcelProperty("详细地址")
|
||||
private String detailAddress;
|
||||
|
||||
@ExcelProperty("经度*")
|
||||
private String longitude;
|
||||
|
||||
@ExcelProperty("纬度*")
|
||||
private String latitude;
|
||||
|
||||
@ExcelProperty("行政区划编号")
|
||||
private String regionCode;
|
||||
|
||||
@ExcelProperty("数据来源")
|
||||
private String dataSource;
|
||||
|
||||
@ExcelProperty("启停状态")
|
||||
private String statusName;
|
||||
|
||||
@ExcelProperty("备注")
|
||||
private String remark;
|
||||
|
||||
@ExcelProperty("更新人")
|
||||
private String updateUserName;
|
||||
|
||||
@ExcelProperty("更新时间")
|
||||
@DateTimeFormat("yyyy-MM-dd HH:mm:ss")
|
||||
private Date updateTime;
|
||||
|
||||
@ExcelProperty("创建时间")
|
||||
@DateTimeFormat("yyyy-MM-dd HH:mm:ss")
|
||||
private Date createTime;
|
||||
|
||||
@ExcelIgnore
|
||||
private Long id;
|
||||
}
|
||||
+5
-41
@@ -48,54 +48,18 @@ public class RegionExcel implements Serializable {
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@ExcelProperty("区划编号")
|
||||
@ExcelProperty("区域编码")
|
||||
private String code;
|
||||
|
||||
@ExcelProperty("父区划编号")
|
||||
private String parentCode;
|
||||
|
||||
@ExcelProperty("祖区划编号")
|
||||
private String ancestors;
|
||||
|
||||
@ExcelProperty("区划名称")
|
||||
@ExcelProperty("区域名称")
|
||||
private String name;
|
||||
|
||||
@ExcelProperty("省级区划编号")
|
||||
private String provinceCode;
|
||||
@ExcelProperty("父级编码")
|
||||
private String parentCode;
|
||||
|
||||
@ExcelProperty("省级名称")
|
||||
private String provinceName;
|
||||
|
||||
@ExcelProperty("市级区划编号")
|
||||
private String cityCode;
|
||||
|
||||
@ExcelProperty("市级名称")
|
||||
private String cityName;
|
||||
|
||||
@ExcelProperty("区级区划编号")
|
||||
private String districtCode;
|
||||
|
||||
@ExcelProperty("区级名称")
|
||||
private String districtName;
|
||||
|
||||
@ExcelProperty("镇级区划编号")
|
||||
private String townCode;
|
||||
|
||||
@ExcelProperty("镇级名称")
|
||||
private String townName;
|
||||
|
||||
@ExcelProperty("村级区划编号")
|
||||
private String villageCode;
|
||||
|
||||
@ExcelProperty("村级名称")
|
||||
private String villageName;
|
||||
|
||||
@ExcelProperty("层级")
|
||||
@ExcelProperty("区域层级")
|
||||
private Integer regionLevel;
|
||||
|
||||
@ExcelProperty("排序")
|
||||
private Integer sort;
|
||||
|
||||
@ExcelProperty("备注")
|
||||
private String remark;
|
||||
|
||||
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* This file is part of the TMS extension for BladeX.
|
||||
*/
|
||||
package org.springblade.system.excel;
|
||||
|
||||
import cn.idev.excel.annotation.ExcelProperty;
|
||||
import cn.idev.excel.annotation.format.DateTimeFormat;
|
||||
import cn.idev.excel.annotation.write.style.ColumnWidth;
|
||||
import cn.idev.excel.annotation.write.style.ContentRowHeight;
|
||||
import cn.idev.excel.annotation.write.style.HeadRowHeight;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 行政区划数据导出 Excel。
|
||||
*
|
||||
* <p>该模型仅用于导出,避免导入模板字段变更影响已有导入文件。</p>
|
||||
*/
|
||||
@Data
|
||||
@ColumnWidth(18)
|
||||
@HeadRowHeight(20)
|
||||
@ContentRowHeight(18)
|
||||
public class RegionExportExcel implements Serializable {
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@ExcelProperty("序号")
|
||||
private Integer serialNumber;
|
||||
|
||||
@ExcelProperty("区域编码")
|
||||
private String code;
|
||||
|
||||
@ExcelProperty("区域名称")
|
||||
private String name;
|
||||
|
||||
@ExcelProperty("父级编码")
|
||||
private String parentCode;
|
||||
|
||||
@ExcelProperty("父级名称")
|
||||
private String parentName;
|
||||
|
||||
@ExcelProperty("区域层级")
|
||||
private Integer regionLevel;
|
||||
|
||||
@ExcelProperty("状态")
|
||||
private String statusName;
|
||||
|
||||
@ExcelProperty("数据来源")
|
||||
private String dataSource;
|
||||
|
||||
@ExcelProperty("更新人")
|
||||
private String updateUserName;
|
||||
|
||||
@ExcelProperty("更新时间")
|
||||
@DateTimeFormat("yyyy-MM-dd HH:mm:ss")
|
||||
private Date updateTime;
|
||||
|
||||
@ExcelProperty("创建时间")
|
||||
@DateTimeFormat("yyyy-MM-dd HH:mm:ss")
|
||||
private Date createTime;
|
||||
}
|
||||
@@ -25,6 +25,7 @@
|
||||
*/
|
||||
package org.springblade.system.feign;
|
||||
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import lombok.AllArgsConstructor;
|
||||
import org.springblade.core.tenant.annotation.NonDS;
|
||||
import org.springblade.core.tool.api.R;
|
||||
@@ -63,6 +64,9 @@ public class SysClient implements ISysClient {
|
||||
|
||||
private final IRegionService regionService;
|
||||
|
||||
private final IFeeItemService feeItemService;
|
||||
private final ICargoTypeService cargoTypeService;
|
||||
|
||||
@Override
|
||||
@GetMapping(MENU)
|
||||
public R<Menu> getMenu(Long id) {
|
||||
@@ -163,6 +167,16 @@ public class SysClient implements ISysClient {
|
||||
return R.data(roleService.getRoleAliases(roleIds));
|
||||
}
|
||||
|
||||
@Override
|
||||
@GetMapping(ROLE_ID_BY_ALIAS)
|
||||
public R<String> getRoleIdByAlias(String tenantId, String roleAlias) {
|
||||
Role role = roleService.getOne(Wrappers.<Role>lambdaQuery()
|
||||
.eq(Role::getTenantId, tenantId)
|
||||
.eq(Role::getRoleAlias, roleAlias)
|
||||
.last("LIMIT 1"));
|
||||
return R.data(role == null || role.getId() == null ? null : String.valueOf(role.getId()));
|
||||
}
|
||||
|
||||
@Override
|
||||
@GetMapping(TENANT)
|
||||
public R<Tenant> getTenant(Long id) {
|
||||
@@ -200,5 +214,27 @@ public class SysClient implements ISysClient {
|
||||
return R.data(regionService.getById(code));
|
||||
}
|
||||
|
||||
@Override
|
||||
@GetMapping(FEE_ITEMS)
|
||||
public R<List<FeeItem>> getFeeItems() {
|
||||
return R.data(feeItemService.list(Wrappers.<FeeItem>lambdaQuery()
|
||||
.eq(FeeItem::getStatus, 1)
|
||||
.eq(FeeItem::getIsDeleted, 0)
|
||||
.orderByAsc(FeeItem::getFeeCategory, FeeItem::getName)));
|
||||
}
|
||||
|
||||
@Override
|
||||
@GetMapping(CARGO_TYPES)
|
||||
public R<List<CargoType>> getCargoTypes() {
|
||||
return R.data(cargoTypeService.list(Wrappers.<CargoType>lambdaQuery()
|
||||
.eq(CargoType::getIsDeleted, 0)
|
||||
.orderByAsc(CargoType::getCargoCode)));
|
||||
}
|
||||
|
||||
@Override
|
||||
@GetMapping(PERMISSIONS)
|
||||
public R<List<String>> getPermissions(String roleId) {
|
||||
return R.data(menuService.permissionCodes(roleId));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -108,6 +108,18 @@ public class UserClient implements IUserClient {
|
||||
return R.data(service.submit(user));
|
||||
}
|
||||
|
||||
@Override
|
||||
@PostMapping(UPDATE_USER)
|
||||
public R<Boolean> updateUser(@RequestBody User user) {
|
||||
return R.data(service.updateUser(user));
|
||||
}
|
||||
|
||||
@Override
|
||||
@PostMapping(SAVE_IAM_USER)
|
||||
public R<Boolean> saveIamUser(@RequestBody User user) {
|
||||
return R.data(service.saveIamUser(user));
|
||||
}
|
||||
|
||||
@Override
|
||||
@PostMapping(REGISTER_USER)
|
||||
public R<String> registerUser(User user) {
|
||||
@@ -121,4 +133,10 @@ public class UserClient implements IUserClient {
|
||||
return R.data(service.remove(Wrappers.<User>query().lambda().in(User::getTenantId, Func.toStrList(tenantIds))));
|
||||
}
|
||||
|
||||
@Override
|
||||
@PostMapping(BIND_WX_MINI_OPENID)
|
||||
public R<Boolean> bindWxMiniOpenId(String tenantId, Long userId, String openid, String phone) {
|
||||
return R.data(service.bindWxMiniOpenId(tenantId, userId, openid, phone));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+10
@@ -40,6 +40,16 @@ import java.util.List;
|
||||
*/
|
||||
public interface AirportMasterMapper extends BaseMapper<AirportMaster> {
|
||||
|
||||
/**
|
||||
* 按编码查询空港机场(包含逻辑删除记录,用于唯一性校验)。
|
||||
*/
|
||||
AirportMaster selectByCodeIncludingDeleted(@Param("code") String code);
|
||||
|
||||
/**
|
||||
* 恢复逻辑删除空港机场。
|
||||
*/
|
||||
int restoreById(@Param("id") Long id);
|
||||
|
||||
/**
|
||||
* 自定义分页
|
||||
*
|
||||
|
||||
+14
@@ -32,6 +32,20 @@
|
||||
<result column="remark" property="remark"/>
|
||||
</resultMap>
|
||||
|
||||
<select id="selectByCodeIncludingDeleted" resultMap="airportMasterResultMap">
|
||||
SELECT
|
||||
am.*
|
||||
FROM blade_airport_master am
|
||||
WHERE am.code = #{code}
|
||||
</select>
|
||||
|
||||
<update id="restoreById">
|
||||
UPDATE blade_airport_master
|
||||
SET is_deleted = 0
|
||||
WHERE id = #{id}
|
||||
AND is_deleted = 1
|
||||
</update>
|
||||
|
||||
<select id="selectAirportMasterPage" resultMap="airportMasterResultMap">
|
||||
SELECT
|
||||
am.id,
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
<result column="pinyin_mnemonic" property="pinyinMnemonic"/>
|
||||
<result column="mnemonic_code" property="mnemonicCode"/>
|
||||
<result column="carrier_customer_id" property="carrierCustomerId"/>
|
||||
<result column="is_platform_company" property="isPlatformCompany"/>
|
||||
<result column="sort" property="sort"/>
|
||||
<result column="remark" property="remark"/>
|
||||
<result column="status" property="status"/>
|
||||
@@ -35,6 +36,7 @@
|
||||
<result column="pinyin_mnemonic" property="pinyinMnemonic"/>
|
||||
<result column="mnemonic_code" property="mnemonicCode"/>
|
||||
<result column="carrier_customer_id" property="carrierCustomerId"/>
|
||||
<result column="is_platform_company" property="isPlatformCompany"/>
|
||||
<result column="sort" property="sort"/>
|
||||
<result column="remark" property="remark"/>
|
||||
<result column="status" property="status"/>
|
||||
|
||||
+2
@@ -16,6 +16,8 @@
|
||||
<result column="fee_category" property="feeCategory"/>
|
||||
<result column="name" property="name"/>
|
||||
<result column="english_name" property="englishName"/>
|
||||
<result column="tax_rate" property="taxRate"/>
|
||||
<result column="remark" property="remark"/>
|
||||
</resultMap>
|
||||
|
||||
<select id="selectFeeItemPage" resultMap="feeItemResultMap">
|
||||
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments from this software for such purposes. Copyright of this software remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.system.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.springblade.system.pojo.entity.InvoiceItem;
|
||||
import org.springblade.system.pojo.vo.InvoiceItemVO;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 开票项目 Mapper 接口
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
public interface InvoiceItemMapper extends BaseMapper<InvoiceItem> {
|
||||
|
||||
List<InvoiceItemVO> selectInvoiceItemPage(IPage<InvoiceItemVO> page,
|
||||
@Param("invoiceItem") InvoiceItemVO invoiceItem);
|
||||
|
||||
}
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
<?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.springblade.system.mapper.InvoiceItemMapper">
|
||||
<resultMap id="invoiceItemResultMap" type="org.springblade.system.pojo.vo.InvoiceItemVO">
|
||||
<result column="id" property="id"/>
|
||||
<result column="create_user" property="createUser"/>
|
||||
<result column="create_dept" property="createDept"/>
|
||||
<result column="create_time" property="createTime"/>
|
||||
<result column="update_user" property="updateUser"/>
|
||||
<result column="update_time" property="updateTime"/>
|
||||
<result column="status" property="status"/>
|
||||
<result column="is_deleted" property="isDeleted"/>
|
||||
<result column="create_dept_name" property="createDeptName"/>
|
||||
<result column="update_user_name" property="updateUserName"/>
|
||||
<result column="short_name" property="shortName"/>
|
||||
<result column="tax_classification_code" property="taxClassificationCode"/>
|
||||
<result column="category_name" property="categoryName"/>
|
||||
<result column="default_tax_rate" property="defaultTaxRate"/>
|
||||
</resultMap>
|
||||
<select id="selectInvoiceItemPage" resultMap="invoiceItemResultMap">
|
||||
SELECT bii.*, bd.dept_name AS create_dept_name, bu.real_name AS update_user_name
|
||||
FROM blade_invoice_item bii
|
||||
LEFT JOIN blade_dept bd ON bd.id = bii.create_dept
|
||||
LEFT JOIN blade_user bu ON bu.id = bii.update_user
|
||||
WHERE bii.is_deleted = 0
|
||||
<if test="invoiceItem.shortName != null and invoiceItem.shortName != ''">
|
||||
<bind name="shortNameLike" value="'%' + invoiceItem.shortName + '%'"/>
|
||||
AND bii.short_name LIKE #{shortNameLike}
|
||||
</if>
|
||||
<if test="invoiceItem.categoryName != null and invoiceItem.categoryName != ''">
|
||||
<bind name="categoryNameLike" value="'%' + invoiceItem.categoryName + '%'"/>
|
||||
AND bii.category_name LIKE #{categoryNameLike}
|
||||
</if>
|
||||
<if test="invoiceItem.taxClassificationCode != null and invoiceItem.taxClassificationCode != ''">
|
||||
<bind name="taxCodeLike" value="'%' + invoiceItem.taxClassificationCode + '%'"/>
|
||||
AND bii.tax_classification_code LIKE #{taxCodeLike}
|
||||
</if>
|
||||
ORDER BY bii.create_time DESC
|
||||
</select>
|
||||
</mapper>
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.system.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.springblade.system.pojo.entity.MeasurementUnit;
|
||||
import org.springblade.system.pojo.vo.MeasurementUnitVO;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 计量单位 Mapper 接口
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
public interface MeasurementUnitMapper extends BaseMapper<MeasurementUnit> {
|
||||
|
||||
/**
|
||||
* 自定义分页
|
||||
*
|
||||
* @param page 分页参数
|
||||
* @param measurementUnit 查询参数
|
||||
* @return 计量单位分页
|
||||
*/
|
||||
List<MeasurementUnitVO> selectMeasurementUnitPage(IPage<MeasurementUnitVO> page,
|
||||
@Param("measurementUnit") MeasurementUnitVO measurementUnit);
|
||||
|
||||
}
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
<?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.springblade.system.mapper.MeasurementUnitMapper">
|
||||
|
||||
<resultMap id="measurementUnitResultMap" type="org.springblade.system.pojo.vo.MeasurementUnitVO">
|
||||
<result column="id" property="id"/>
|
||||
<result column="create_user" property="createUser"/>
|
||||
<result column="create_user_name" property="createUserName"/>
|
||||
<result column="create_dept" property="createDept"/>
|
||||
<result column="create_time" property="createTime"/>
|
||||
<result column="update_user" property="updateUser"/>
|
||||
<result column="update_user_name" property="updateUserName"/>
|
||||
<result column="update_time" property="updateTime"/>
|
||||
<result column="status" property="status"/>
|
||||
<result column="is_deleted" property="isDeleted"/>
|
||||
<result column="unit_code" property="unitCode"/>
|
||||
<result column="unit_name" property="unitName"/>
|
||||
<result column="dimension" property="dimension"/>
|
||||
<result column="remark" property="remark"/>
|
||||
</resultMap>
|
||||
|
||||
<select id="selectMeasurementUnitPage" resultMap="measurementUnitResultMap">
|
||||
SELECT
|
||||
mmu.id,
|
||||
mmu.create_user,
|
||||
cu.real_name AS create_user_name,
|
||||
mmu.create_dept,
|
||||
mmu.create_time,
|
||||
mmu.update_user,
|
||||
uu.real_name AS update_user_name,
|
||||
mmu.update_time,
|
||||
mmu.status,
|
||||
mmu.is_deleted,
|
||||
mmu.unit_code,
|
||||
mmu.unit_name,
|
||||
mmu.dimension,
|
||||
mmu.remark
|
||||
FROM
|
||||
blade_measurement_unit mmu
|
||||
LEFT JOIN blade_user cu ON cu.id = mmu.create_user
|
||||
LEFT JOIN blade_user uu ON uu.id = mmu.update_user
|
||||
WHERE
|
||||
mmu.is_deleted = 0
|
||||
<if test="measurementUnit.unitCode != null and measurementUnit.unitCode != ''">
|
||||
<bind name="unitCodeLike" value="'%' + measurementUnit.unitCode + '%'"/>
|
||||
AND mmu.unit_code LIKE #{unitCodeLike}
|
||||
</if>
|
||||
<if test="measurementUnit.unitName != null and measurementUnit.unitName != ''">
|
||||
<bind name="unitNameLike" value="'%' + measurementUnit.unitName + '%'"/>
|
||||
AND mmu.unit_name LIKE #{unitNameLike}
|
||||
</if>
|
||||
<if test="measurementUnit.dimension != null and measurementUnit.dimension != ''">
|
||||
AND mmu.dimension = #{measurementUnit.dimension}
|
||||
</if>
|
||||
<if test="measurementUnit.status != null">
|
||||
AND mmu.status = #{measurementUnit.status}
|
||||
</if>
|
||||
ORDER BY mmu.create_time DESC
|
||||
</select>
|
||||
|
||||
</mapper>
|
||||
+11
@@ -27,6 +27,7 @@ package org.springblade.system.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.springblade.system.pojo.entity.PortTerminal;
|
||||
import org.springblade.system.pojo.vo.PortTerminalVO;
|
||||
|
||||
@@ -39,6 +40,16 @@ import java.util.List;
|
||||
*/
|
||||
public interface PortTerminalMapper extends BaseMapper<PortTerminal> {
|
||||
|
||||
/**
|
||||
* 按编码查询港口码头(包含逻辑删除记录,用于导入恢复)。
|
||||
*/
|
||||
PortTerminal selectByCodeIncludingDeleted(@Param("code") String code);
|
||||
|
||||
/**
|
||||
* 恢复逻辑删除港口码头。
|
||||
*/
|
||||
int restoreById(@Param("id") Long id);
|
||||
|
||||
/**
|
||||
* 自定义分页
|
||||
*
|
||||
|
||||
+33
-3
@@ -19,6 +19,8 @@
|
||||
<result column="parent_code" property="parentCode"/>
|
||||
<result column="parent_name" property="parentName"/>
|
||||
<result column="country" property="country"/>
|
||||
<result column="province_code" property="provinceCode"/>
|
||||
<result column="province_name" property="provinceName"/>
|
||||
<result column="city" property="city"/>
|
||||
<result column="district_code" property="districtCode"/>
|
||||
<result column="district_name" property="districtName"/>
|
||||
@@ -30,6 +32,19 @@
|
||||
<result column="remark" property="remark"/>
|
||||
</resultMap>
|
||||
|
||||
<select id="selectByCodeIncludingDeleted" resultType="org.springblade.system.pojo.entity.PortTerminal">
|
||||
SELECT *
|
||||
FROM blade_port_terminal
|
||||
WHERE code = #{code}
|
||||
</select>
|
||||
|
||||
<update id="restoreById">
|
||||
UPDATE blade_port_terminal
|
||||
SET is_deleted = 0
|
||||
WHERE id = #{id}
|
||||
AND is_deleted = 1
|
||||
</update>
|
||||
|
||||
<select id="selectPortTerminalPage" resultMap="portTerminalResultMap">
|
||||
SELECT
|
||||
pt.id,
|
||||
@@ -48,13 +63,19 @@
|
||||
pt.parent_code,
|
||||
pt.parent_name,
|
||||
pt.country,
|
||||
pt.province_code,
|
||||
pt.province_name,
|
||||
pt.city,
|
||||
pt.district_code,
|
||||
pt.district_name,
|
||||
pt.detail_address,
|
||||
CASE WHEN pt.longitude BETWEEN -180 AND 180 THEN pt.longitude ELSE NULL END AS longitude,
|
||||
CASE WHEN pt.latitude BETWEEN -90 AND 90 THEN pt.latitude ELSE NULL END AS latitude,
|
||||
CASE WHEN pt.data_source = '初始导入' THEN '初始化导入' ELSE pt.data_source END AS data_source,
|
||||
CASE
|
||||
WHEN pt.data_source IN ('初始化导入', '初始导入') THEN '初始化录入'
|
||||
WHEN pt.data_source = '手工导入' THEN '手动录入'
|
||||
ELSE pt.data_source
|
||||
END AS data_source,
|
||||
pt.remark
|
||||
FROM
|
||||
blade_port_terminal pt
|
||||
@@ -77,8 +98,11 @@
|
||||
</if>
|
||||
<if test="portTerminal.dataSource != null and portTerminal.dataSource != ''">
|
||||
<choose>
|
||||
<when test="portTerminal.dataSource == '初始化导入'">
|
||||
AND pt.data_source IN ('初始化导入', '初始导入')
|
||||
<when test="portTerminal.dataSource == '初始化录入'">
|
||||
AND pt.data_source IN ('初始化录入', '初始化导入', '初始导入')
|
||||
</when>
|
||||
<when test="portTerminal.dataSource == '手动录入'">
|
||||
AND pt.data_source IN ('手动录入', '手工导入')
|
||||
</when>
|
||||
<otherwise>
|
||||
AND pt.data_source = #{portTerminal.dataSource}
|
||||
@@ -88,6 +112,12 @@
|
||||
<if test="portTerminal.city != null and portTerminal.city != ''">
|
||||
AND pt.city = #{portTerminal.city}
|
||||
</if>
|
||||
<if test="portTerminal.provinceName != null and portTerminal.provinceName != ''">
|
||||
AND pt.province_name = #{portTerminal.provinceName}
|
||||
</if>
|
||||
<if test="portTerminal.provinceCode != null and portTerminal.provinceCode != ''">
|
||||
AND pt.province_code = #{portTerminal.provinceCode}
|
||||
</if>
|
||||
<if test="portTerminal.districtCode != null and portTerminal.districtCode != ''">
|
||||
AND pt.district_code = #{portTerminal.districtCode}
|
||||
</if>
|
||||
|
||||
+10
@@ -40,6 +40,16 @@ import java.util.List;
|
||||
*/
|
||||
public interface RailwayStationMapper extends BaseMapper<RailwayStation> {
|
||||
|
||||
/**
|
||||
* 按编码查询铁路车站(包含逻辑删除记录,用于导入恢复)。
|
||||
*/
|
||||
RailwayStation selectByCodeIncludingDeleted(@Param("code") String code);
|
||||
|
||||
/**
|
||||
* 恢复逻辑删除铁路车站。
|
||||
*/
|
||||
int restoreById(@Param("id") Long id);
|
||||
|
||||
/**
|
||||
* 自定义分页
|
||||
*
|
||||
|
||||
+13
@@ -32,6 +32,19 @@
|
||||
<result column="remark" property="remark"/>
|
||||
</resultMap>
|
||||
|
||||
<select id="selectByCodeIncludingDeleted" resultType="org.springblade.system.pojo.entity.RailwayStation">
|
||||
SELECT *
|
||||
FROM blade_railway_station
|
||||
WHERE code = #{code}
|
||||
</select>
|
||||
|
||||
<update id="restoreById">
|
||||
UPDATE blade_railway_station
|
||||
SET is_deleted = 0
|
||||
WHERE id = #{id}
|
||||
AND is_deleted = 1
|
||||
</update>
|
||||
|
||||
<select id="selectRailwayStationPage" resultMap="railwayStationResultMap">
|
||||
SELECT
|
||||
rs.id,
|
||||
|
||||
+2
-1
@@ -30,6 +30,7 @@ import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.springblade.system.pojo.entity.Region;
|
||||
import org.springblade.system.excel.RegionExcel;
|
||||
import org.springblade.system.excel.RegionExportExcel;
|
||||
import org.springblade.system.pojo.vo.RegionVO;
|
||||
|
||||
import java.util.List;
|
||||
@@ -66,6 +67,6 @@ public interface RegionMapper extends BaseMapper<Region> {
|
||||
* @param queryWrapper
|
||||
* @return
|
||||
*/
|
||||
List<RegionExcel> exportRegion(@Param("ew") Wrapper<Region> queryWrapper);
|
||||
List<RegionExportExcel> exportRegion(@Param("ew") Wrapper<Region> queryWrapper);
|
||||
|
||||
}
|
||||
|
||||
+49
-13
@@ -18,7 +18,7 @@
|
||||
<result column="town_name" property="townName"/>
|
||||
<result column="village_code" property="villageCode"/>
|
||||
<result column="village_name" property="villageName"/>
|
||||
<result column="level" property="regionLevel"/>
|
||||
<result column="region_level" property="regionLevel"/>
|
||||
<result column="sort" property="sort"/>
|
||||
<result column="remark" property="remark"/>
|
||||
</resultMap>
|
||||
@@ -38,7 +38,7 @@
|
||||
<result column="town_name" property="townName"/>
|
||||
<result column="village_code" property="villageCode"/>
|
||||
<result column="village_name" property="villageName"/>
|
||||
<result column="level" property="regionLevel"/>
|
||||
<result column="region_level" property="regionLevel"/>
|
||||
<result column="sort" property="sort"/>
|
||||
<result column="remark" property="remark"/>
|
||||
<result column="id" property="id"/>
|
||||
@@ -72,7 +72,13 @@
|
||||
and region.name like concat(concat('%', #{param2.name}),'%')
|
||||
</if>
|
||||
<if test="param2.regionLevel!=null">
|
||||
and region.level = #{param2.regionLevel}
|
||||
and region.region_level = #{param2.regionLevel}
|
||||
</if>
|
||||
<if test="param2.dataSource!=null and param2.dataSource!=''">
|
||||
and region.data_source = #{param2.dataSource}
|
||||
</if>
|
||||
<if test="param2.status!=null and param2.status!=''">
|
||||
and region.status = #{param2.status}
|
||||
</if>
|
||||
</where>
|
||||
ORDER BY region.sort ASC, region.code ASC
|
||||
@@ -92,21 +98,51 @@
|
||||
<if test="param1!=null">
|
||||
and region.parent_code = #{param1}
|
||||
</if>
|
||||
<if test="param2.code!=null and param2.code!=''">
|
||||
and region.code like concat(concat('%', #{param2.code}),'%')
|
||||
</if>
|
||||
<if test="param2.name!=null and param2.name!=''">
|
||||
and region.name like concat(concat('%', #{param2.name}),'%')
|
||||
</if>
|
||||
<if test="param2.regionLevel!=null">
|
||||
and region.level = #{param2.regionLevel}
|
||||
<if test="param2 != null and ((param2.code!=null and param2.code!='') or (param2.name!=null and param2.name!='') or param2.regionLevel!=null or (param2.dataSource!=null and param2.dataSource!='') or param2.status!=null)">
|
||||
and exists (
|
||||
select 1
|
||||
from blade_region matched
|
||||
where (matched.code = region.code or find_in_set(region.code, matched.ancestors) > 0)
|
||||
<if test="param2.code!=null and param2.code!=''">
|
||||
and matched.code like concat(concat('%', #{param2.code}),'%')
|
||||
</if>
|
||||
<if test="param2.name!=null and param2.name!=''">
|
||||
and matched.name like concat(concat('%', #{param2.name}),'%')
|
||||
</if>
|
||||
<if test="param2.regionLevel!=null">
|
||||
and matched.region_level = #{param2.regionLevel}
|
||||
</if>
|
||||
<if test="param2.dataSource!=null and param2.dataSource!=''">
|
||||
and matched.data_source = #{param2.dataSource}
|
||||
</if>
|
||||
<if test="param2.status!=null and param2.status!=''">
|
||||
and matched.status = #{param2.status}
|
||||
</if>
|
||||
)
|
||||
</if>
|
||||
</where>
|
||||
ORDER BY region.sort ASC, region.code ASC
|
||||
</select>
|
||||
|
||||
<select id="exportRegion" resultType="org.springblade.system.excel.RegionExcel">
|
||||
SELECT * FROM blade_region ${ew.customSqlSegment}
|
||||
<select id="exportRegion" resultType="org.springblade.system.excel.RegionExportExcel">
|
||||
SELECT
|
||||
region.code,
|
||||
region.name,
|
||||
region.parent_code,
|
||||
CASE WHEN region.parent_code = '0' THEN '根节点'
|
||||
ELSE (SELECT parent.name FROM blade_region parent WHERE parent.code = region.parent_code)
|
||||
END AS parent_name,
|
||||
region.region_level,
|
||||
CASE region.status WHEN 1 THEN '启用' WHEN 2 THEN '停用' ELSE '' END AS status_name,
|
||||
region.data_source,
|
||||
COALESCE(updater.real_name, creator.real_name) AS update_user_name,
|
||||
region.update_time,
|
||||
region.create_time
|
||||
FROM blade_region region
|
||||
LEFT JOIN blade_user updater ON updater.id = region.update_user
|
||||
LEFT JOIN blade_user creator ON creator.id = region.create_user
|
||||
${ew.customSqlSegment}
|
||||
ORDER BY region.create_time DESC, region.code ASC
|
||||
</select>
|
||||
|
||||
</mapper>
|
||||
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.system.props;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
/**
|
||||
* IAM账号同步配置。
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@Data
|
||||
@ConfigurationProperties(prefix = "iam.sync")
|
||||
public class IamSyncProperties {
|
||||
|
||||
/** IAM增量账号接口地址。 */
|
||||
private String accountListUrl;
|
||||
|
||||
/** IAM组织接口地址。 */
|
||||
private String orgListUrl;
|
||||
|
||||
/** IAM接口Authorization请求头。 */
|
||||
private String authorization;
|
||||
|
||||
/** IAM接口Auth请求头。 */
|
||||
private String profileAuthorization;
|
||||
|
||||
/** 单页请求数量。 */
|
||||
private int pageSize = 50;
|
||||
|
||||
}
|
||||
+2
-1
@@ -29,6 +29,7 @@ import com.baomidou.mybatisplus.core.conditions.Wrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import org.springblade.core.mp.base.BaseService;
|
||||
import org.springblade.system.excel.AirportMasterExcel;
|
||||
import org.springblade.system.excel.AirportMasterExportExcel;
|
||||
import org.springblade.system.pojo.entity.AirportMaster;
|
||||
import org.springblade.system.pojo.vo.AirportMasterVO;
|
||||
|
||||
@@ -80,6 +81,6 @@ public interface IAirportMasterService extends BaseService<AirportMaster> {
|
||||
* @param queryWrapper 查询条件
|
||||
* @return 导出数据
|
||||
*/
|
||||
List<AirportMasterExcel> exportAirportMaster(Wrapper<AirportMaster> queryWrapper);
|
||||
List<AirportMasterExportExcel> exportAirportMaster(Wrapper<AirportMaster> queryWrapper);
|
||||
|
||||
}
|
||||
|
||||
+14
@@ -97,6 +97,13 @@ public interface IDeptService extends IService<Dept> {
|
||||
*/
|
||||
List<Dept> selectDept(String deptId);
|
||||
|
||||
/**
|
||||
* 平台公司下拉(是否平台公司=是)
|
||||
*
|
||||
* @return 平台公司部门列表
|
||||
*/
|
||||
List<Dept> listPlatformCompany();
|
||||
|
||||
/**
|
||||
* 根据部门名称精确匹配获取部门ID集合
|
||||
*
|
||||
@@ -149,6 +156,13 @@ public interface IDeptService extends IService<Dept> {
|
||||
*/
|
||||
boolean submit(Dept dept);
|
||||
|
||||
/**
|
||||
* 从IAM同步管理租户组织。
|
||||
|
||||
* @return 同步处理的组织数量
|
||||
*/
|
||||
int syncIamOrganizations();
|
||||
|
||||
/**
|
||||
* 按名称与父级查询部门列表(限定当前会话租户)
|
||||
*
|
||||
|
||||
+2
-1
@@ -29,6 +29,7 @@ import com.baomidou.mybatisplus.core.conditions.Wrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import org.springblade.core.mp.base.BaseService;
|
||||
import org.springblade.system.excel.FeeItemExcel;
|
||||
import org.springblade.system.excel.FeeItemExportExcel;
|
||||
import org.springblade.system.excel.FeeItemImportFailureExcel;
|
||||
import org.springblade.system.pojo.entity.FeeItem;
|
||||
import org.springblade.system.pojo.vo.FeeItemVO;
|
||||
@@ -82,6 +83,6 @@ public interface IFeeItemService extends BaseService<FeeItem> {
|
||||
* @param queryWrapper 查询条件
|
||||
* @return 导出数据
|
||||
*/
|
||||
List<FeeItemExcel> exportFeeItem(Wrapper<FeeItem> queryWrapper);
|
||||
List<FeeItemExportExcel> exportFeeItem(Wrapper<FeeItem> queryWrapper);
|
||||
|
||||
}
|
||||
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments from this software for such purposes. Copyright of this software remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.system.service;
|
||||
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import org.springblade.core.mp.base.BaseService;
|
||||
import org.springblade.system.pojo.entity.InvoiceItem;
|
||||
import org.springblade.system.pojo.vo.InvoiceItemVO;
|
||||
|
||||
/**
|
||||
* 开票项目服务类
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
public interface IInvoiceItemService extends BaseService<InvoiceItem> {
|
||||
|
||||
IPage<InvoiceItemVO> selectInvoiceItemPage(IPage<InvoiceItemVO> page, InvoiceItemVO invoiceItem);
|
||||
|
||||
boolean submit(InvoiceItem invoiceItem);
|
||||
|
||||
}
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.system.service;
|
||||
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import org.springblade.core.mp.base.BaseService;
|
||||
import org.springblade.system.pojo.entity.MeasurementUnit;
|
||||
import org.springblade.system.pojo.vo.MeasurementUnitVO;
|
||||
|
||||
/**
|
||||
* 计量单位服务类
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
public interface IMeasurementUnitService extends BaseService<MeasurementUnit> {
|
||||
|
||||
/**
|
||||
* 自定义分页
|
||||
*
|
||||
* @param page 分页参数
|
||||
* @param measurementUnit 查询参数
|
||||
* @return 计量单位分页
|
||||
*/
|
||||
IPage<MeasurementUnitVO> selectMeasurementUnitPage(IPage<MeasurementUnitVO> page,
|
||||
MeasurementUnitVO measurementUnit);
|
||||
|
||||
/**
|
||||
* 新增或修改计量单位
|
||||
*
|
||||
* @param measurementUnit 计量单位
|
||||
* @return 是否成功
|
||||
*/
|
||||
boolean submit(MeasurementUnit measurementUnit);
|
||||
|
||||
/**
|
||||
* 启用或停用计量单位
|
||||
*
|
||||
* @param id 主键
|
||||
* @param status 状态
|
||||
* @return 是否成功
|
||||
*/
|
||||
boolean changeStatus(Long id, Integer status);
|
||||
|
||||
}
|
||||
+8
@@ -77,6 +77,14 @@ public interface IMenuService extends IService<Menu> {
|
||||
*/
|
||||
List<MenuVO> buttons(String roleId);
|
||||
|
||||
/**
|
||||
* 权限标识集合(按钮编号,与前端 GetButtons 叶子 code 一致)
|
||||
*
|
||||
* @param roleId 角色id
|
||||
* @return 权限标识
|
||||
*/
|
||||
List<String> permissionCodes(String roleId);
|
||||
|
||||
/**
|
||||
* 树形结构
|
||||
*
|
||||
|
||||
+37
@@ -1,5 +1,8 @@
|
||||
package org.springblade.system.service;
|
||||
|
||||
import org.springblade.system.pojo.vo.OaOrgSyncPageVO;
|
||||
import org.springblade.system.pojo.vo.OaPersonSyncPageVO;
|
||||
|
||||
/**
|
||||
* oa同步接口
|
||||
* @author bfhuange
|
||||
@@ -18,4 +21,38 @@ public interface IOASyncService {
|
||||
* @param syncAll 是否同步所有
|
||||
*/
|
||||
void syncPersonAndPushMK(boolean syncAll);
|
||||
|
||||
/**
|
||||
* 从 OA 人员接口全量同步组织与人员,不推送 MK
|
||||
*
|
||||
* @return 处理的人员数量
|
||||
*/
|
||||
int syncPersonFromUserList();
|
||||
|
||||
/**
|
||||
* 按页从 OA 人员接口同步组织与人员
|
||||
*
|
||||
* @param current 当前页,从 1 开始
|
||||
* @param size 每页条数
|
||||
* @return 本页同步结果
|
||||
*/
|
||||
OaPersonSyncPageVO syncPersonFromUserList(int current, int size);
|
||||
|
||||
/**
|
||||
* 按页从 OA 公司接口同步公司
|
||||
*
|
||||
* @param current 当前页,从 1 开始
|
||||
* @param size 每页条数
|
||||
* @return 本页同步结果
|
||||
*/
|
||||
OaOrgSyncPageVO syncCompanyPage(int current, int size);
|
||||
|
||||
/**
|
||||
* 按页从 OA 部门接口同步部门;最后一页完成后更新祖级列表
|
||||
*
|
||||
* @param current 当前页,从 1 开始
|
||||
* @param size 每页条数
|
||||
* @return 本页同步结果
|
||||
*/
|
||||
OaOrgSyncPageVO syncDepartmentPage(int current, int size);
|
||||
}
|
||||
|
||||
+2
-1
@@ -29,6 +29,7 @@ import com.baomidou.mybatisplus.core.conditions.Wrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import org.springblade.core.mp.base.BaseService;
|
||||
import org.springblade.system.excel.PortTerminalExcel;
|
||||
import org.springblade.system.excel.PortTerminalExportExcel;
|
||||
import org.springblade.system.pojo.entity.PortTerminal;
|
||||
import org.springblade.system.pojo.vo.PortTerminalVO;
|
||||
|
||||
@@ -87,6 +88,6 @@ public interface IPortTerminalService extends BaseService<PortTerminal> {
|
||||
* @param queryWrapper 查询条件
|
||||
* @return 导出数据
|
||||
*/
|
||||
List<PortTerminalExcel> exportPortTerminal(Wrapper<PortTerminal> queryWrapper);
|
||||
List<PortTerminalExportExcel> exportPortTerminal(Wrapper<PortTerminal> queryWrapper);
|
||||
|
||||
}
|
||||
|
||||
+2
-1
@@ -29,6 +29,7 @@ import com.baomidou.mybatisplus.core.conditions.Wrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import org.springblade.core.mp.base.BaseService;
|
||||
import org.springblade.system.excel.RailwayStationExcel;
|
||||
import org.springblade.system.excel.RailwayStationExportExcel;
|
||||
import org.springblade.system.pojo.entity.RailwayStation;
|
||||
import org.springblade.system.pojo.vo.RailwayStationVO;
|
||||
|
||||
@@ -80,6 +81,6 @@ public interface IRailwayStationService extends BaseService<RailwayStation> {
|
||||
* @param queryWrapper 查询条件
|
||||
* @return 导出数据
|
||||
*/
|
||||
List<RailwayStationExcel> exportRailwayStation(Wrapper<RailwayStation> queryWrapper);
|
||||
List<RailwayStationExportExcel> exportRailwayStation(Wrapper<RailwayStation> queryWrapper);
|
||||
|
||||
}
|
||||
|
||||
+2
-1
@@ -29,6 +29,7 @@ import com.baomidou.mybatisplus.core.conditions.Wrapper;
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import org.springblade.system.pojo.entity.Region;
|
||||
import org.springblade.system.excel.RegionExcel;
|
||||
import org.springblade.system.excel.RegionExportExcel;
|
||||
import org.springblade.system.pojo.vo.RegionVO;
|
||||
|
||||
import java.util.List;
|
||||
@@ -90,6 +91,6 @@ public interface IRegionService extends IService<Region> {
|
||||
* @param queryWrapper
|
||||
* @return
|
||||
*/
|
||||
List<RegionExcel> exportRegion(Wrapper<Region> queryWrapper);
|
||||
List<RegionExportExcel> exportRegion(Wrapper<Region> queryWrapper);
|
||||
|
||||
}
|
||||
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.system.service;
|
||||
|
||||
import org.springblade.core.tool.api.R;
|
||||
import org.springblade.system.pojo.dto.PhoneChangeDTO;
|
||||
import org.springblade.system.pojo.dto.PhoneVerifyDTO;
|
||||
|
||||
/**
|
||||
* 用户手机号变更服务
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
public interface IUserPhoneService {
|
||||
|
||||
/**
|
||||
* 发送变更手机号短信验证码
|
||||
*
|
||||
* @param phone 明文手机号
|
||||
* @return 含短信校验 id 的响应
|
||||
*/
|
||||
R sendCode(String phone);
|
||||
|
||||
/**
|
||||
* 校验原手机号验证码,通过后写入短期凭证
|
||||
*
|
||||
* @param phoneVerify 校验参数
|
||||
* @return 是否通过
|
||||
*/
|
||||
boolean verifyOldPhone(PhoneVerifyDTO phoneVerify);
|
||||
|
||||
/**
|
||||
* 校验新手机号验证码并更换手机号
|
||||
*
|
||||
* @param phoneChange 更换参数
|
||||
* @return 是否成功
|
||||
*/
|
||||
boolean changePhone(PhoneChangeDTO phoneChange);
|
||||
|
||||
}
|
||||
+20
@@ -78,6 +78,13 @@ public interface IUserService extends BaseService<User> {
|
||||
*/
|
||||
boolean submit(User user);
|
||||
|
||||
/**
|
||||
* 从IAM同步管理租户账号。
|
||||
*
|
||||
* @return 同步处理的账号数量
|
||||
*/
|
||||
int syncIamAccounts();
|
||||
|
||||
/**
|
||||
* 修改用户(租户守卫校验用户归属,含账号 / 手机查重)
|
||||
*
|
||||
@@ -181,6 +188,11 @@ public interface IUserService extends BaseService<User> {
|
||||
*/
|
||||
UserInfo userInfo(UserOauth userOauth);
|
||||
|
||||
/**
|
||||
* 绑定微信小程序 openid 到已有用户(blade_user_oauth,source=WECHAT_MINI)
|
||||
*/
|
||||
boolean bindWxMiniOpenId(String tenantId, Long userId, String openid, String phone);
|
||||
|
||||
/**
|
||||
* 根据租户与账号获取用户
|
||||
*
|
||||
@@ -280,6 +292,14 @@ public interface IUserService extends BaseService<User> {
|
||||
*/
|
||||
boolean registerUser(User user);
|
||||
|
||||
/**
|
||||
* 新建或补齐IAM统一身份认证用户(按可信租户落库,默认分配角色 1123598816738675203)
|
||||
*
|
||||
* @param user 用户实体
|
||||
* @return 是否成功
|
||||
*/
|
||||
boolean saveIamUser(User user);
|
||||
|
||||
/**
|
||||
* 配置用户平台扩展信息(租户守卫校验用户归属)
|
||||
*
|
||||
|
||||
+216
-12
@@ -34,7 +34,9 @@ import org.springblade.core.log.exception.ServiceException;
|
||||
import org.springblade.core.mp.base.BaseServiceImpl;
|
||||
import org.springblade.core.tool.utils.BeanUtil;
|
||||
import org.springblade.core.tool.utils.Func;
|
||||
import org.springblade.system.cache.UserCache;
|
||||
import org.springblade.system.excel.AirportMasterExcel;
|
||||
import org.springblade.system.excel.AirportMasterExportExcel;
|
||||
import org.springblade.system.mapper.AirportMasterMapper;
|
||||
import org.springblade.system.pojo.entity.AirportMaster;
|
||||
import org.springblade.system.pojo.entity.Region;
|
||||
@@ -43,12 +45,15 @@ import org.springblade.system.service.IAirportMasterService;
|
||||
import org.springblade.system.service.IRegionService;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.transaction.interceptor.TransactionAspectSupport;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
@@ -66,7 +71,7 @@ public class AirportMasterServiceImpl extends BaseServiceImpl<AirportMasterMappe
|
||||
private static final String SOURCE_BATCH = "批量导入";
|
||||
private static final String SOURCE_MANUAL = "手动录入";
|
||||
private static final String SOURCE_MANUAL_OLD = "手工导入";
|
||||
private static final String DEFAULT_COUNTRY_CODE = "+86";
|
||||
private static final int PROVINCE_REGION_LEVEL = 1;
|
||||
private static final int STATUS_ENABLED = 1;
|
||||
private static final int STATUS_DISABLED = 2;
|
||||
private static final int CODE_MAX_LENGTH = 20;
|
||||
@@ -95,6 +100,7 @@ public class AirportMasterServiceImpl extends BaseServiceImpl<AirportMasterMappe
|
||||
public boolean submit(AirportMaster airportMaster) {
|
||||
prepare(airportMaster, SOURCE_MANUAL);
|
||||
validate(airportMaster);
|
||||
prepareSubmitTarget(airportMaster);
|
||||
return saveOrUpdate(airportMaster);
|
||||
}
|
||||
|
||||
@@ -124,33 +130,199 @@ public class AirportMasterServiceImpl extends BaseServiceImpl<AirportMasterMappe
|
||||
throw new ServiceException("导入数据不能为空");
|
||||
}
|
||||
List<AirportMasterExcel> errorList = new ArrayList<>();
|
||||
List<AirportMaster> airportMasterList = new ArrayList<>();
|
||||
Map<String, Integer> iataCodeCountMap = buildImportValueCountMap(data.stream()
|
||||
.map(excel -> trimToEmpty(excel.getIataCode()).toUpperCase(Locale.ROOT))
|
||||
.toList());
|
||||
Map<String, Integer> icaoCodeCountMap = buildImportValueCountMap(data.stream()
|
||||
.map(excel -> trimToEmpty(excel.getIcaoCode()).toUpperCase(Locale.ROOT))
|
||||
.toList());
|
||||
for (int index = 0; index < data.size(); index++) {
|
||||
AirportMasterExcel excel = data.get(index);
|
||||
AirportMaster airportMaster = Objects.requireNonNull(BeanUtil.copyProperties(excel, AirportMaster.class));
|
||||
airportMaster.setDataSource(SOURCE_BATCH);
|
||||
airportMaster.setStatus(STATUS_ENABLED);
|
||||
normalizeImportAirportMaster(airportMaster);
|
||||
List<String> validationErrors = validateImportAirportMaster(airportMaster, iataCodeCountMap, icaoCodeCountMap);
|
||||
if (Func.isNotEmpty(validationErrors)) {
|
||||
excel.setErrorMessage(formatImportErrorMessage(validationErrors));
|
||||
errorList.add(excel);
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
AirportMaster airportMaster = Objects.requireNonNull(BeanUtil.copyProperties(excel, AirportMaster.class));
|
||||
airportMaster.setDataSource(SOURCE_BATCH);
|
||||
airportMaster.setStatus(STATUS_ENABLED);
|
||||
prepare(airportMaster, SOURCE_BATCH);
|
||||
validate(airportMaster);
|
||||
save(airportMaster);
|
||||
airportMasterList.add(airportMaster);
|
||||
} catch (Exception exception) {
|
||||
String message = exception instanceof ServiceException ? exception.getMessage() : "导入失败";
|
||||
excel.setErrorMessage("第" + (index + 2) + "行:" + message);
|
||||
excel.setErrorMessage(formatImportErrorMessage(List.of(message)));
|
||||
errorList.add(excel);
|
||||
}
|
||||
}
|
||||
if (Func.isNotEmpty(errorList)) {
|
||||
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
|
||||
return errorList;
|
||||
}
|
||||
for (AirportMaster airportMaster : airportMasterList) {
|
||||
// 编码命中逻辑删除记录时复用原主键,恢复后更新,避免唯一索引冲突。
|
||||
prepareSubmitTarget(airportMaster);
|
||||
if (!saveOrUpdate(airportMaster)) {
|
||||
throw new ServiceException("空港机场保存失败");
|
||||
}
|
||||
}
|
||||
return errorList;
|
||||
}
|
||||
|
||||
private Map<String, Integer> buildImportValueCountMap(List<String> values) {
|
||||
Map<String, Integer> valueCountMap = new HashMap<>();
|
||||
for (String value : values) {
|
||||
if (Func.isNotEmpty(value)) {
|
||||
valueCountMap.merge(value, 1, Integer::sum);
|
||||
}
|
||||
}
|
||||
return valueCountMap;
|
||||
}
|
||||
|
||||
private void normalizeImportAirportMaster(AirportMaster airportMaster) {
|
||||
airportMaster.setIataCode(trimToEmpty(airportMaster.getIataCode()).toUpperCase(Locale.ROOT));
|
||||
airportMaster.setCode(CODE_PREFIX + airportMaster.getIataCode());
|
||||
airportMaster.setIcaoCode(normalizeOptionalCode(airportMaster.getIcaoCode()));
|
||||
airportMaster.setName(trimToEmpty(airportMaster.getName()));
|
||||
airportMaster.setShortName(trimToNull(airportMaster.getShortName()));
|
||||
airportMaster.setProvinceCode(trimToNull(airportMaster.getProvinceCode()));
|
||||
airportMaster.setProvinceName(trimToNull(airportMaster.getProvinceName()));
|
||||
airportMaster.setCityCode(trimToNull(airportMaster.getCityCode()));
|
||||
airportMaster.setCityName(trimToNull(airportMaster.getCityName()));
|
||||
airportMaster.setDistrictCode(trimToNull(airportMaster.getDistrictCode()));
|
||||
airportMaster.setDistrictName(trimToNull(airportMaster.getDistrictName()));
|
||||
airportMaster.setRegionCode(trimToNull(airportMaster.getRegionCode()));
|
||||
if (Func.isEmpty(airportMaster.getDistrictCode()) && Func.isNotEmpty(airportMaster.getRegionCode())) {
|
||||
airportMaster.setDistrictCode(airportMaster.getRegionCode());
|
||||
}
|
||||
airportMaster.setDetailAddress(trimToNull(airportMaster.getDetailAddress()));
|
||||
airportMaster.setRemark(trimToNull(airportMaster.getRemark()));
|
||||
}
|
||||
|
||||
private List<String> validateImportAirportMaster(AirportMaster airportMaster,
|
||||
Map<String, Integer> iataCodeCountMap, Map<String, Integer> icaoCodeCountMap) {
|
||||
List<String> validationErrors = new ArrayList<>();
|
||||
if (Func.isEmpty(airportMaster.getIataCode())) {
|
||||
addValidationError(validationErrors, "IATA编码不能为空");
|
||||
} else {
|
||||
if (!IATA_CODE_PATTERN.matcher(airportMaster.getIataCode()).matches()) {
|
||||
addValidationError(validationErrors, "IATA编码为3位大写字母");
|
||||
}
|
||||
if (iataCodeCountMap.getOrDefault(airportMaster.getIataCode(), 0) > 1) {
|
||||
addValidationError(validationErrors, "IATA编码在本次导入中重复");
|
||||
}
|
||||
validateImportUnique(AirportMaster::getIataCode, airportMaster.getIataCode(), "该IATA编码已存在", validationErrors);
|
||||
validateImportUnique(AirportMaster::getCode, airportMaster.getCode(), "该编码已存在", validationErrors);
|
||||
}
|
||||
if (Func.isEmpty(airportMaster.getIcaoCode())) {
|
||||
addValidationError(validationErrors, "ICAO代码不能为空");
|
||||
} else {
|
||||
if (!ICAO_CODE_PATTERN.matcher(airportMaster.getIcaoCode()).matches()) {
|
||||
addValidationError(validationErrors, "ICAO代码为4位大写字母");
|
||||
}
|
||||
if (icaoCodeCountMap.getOrDefault(airportMaster.getIcaoCode(), 0) > 1) {
|
||||
addValidationError(validationErrors, "ICAO代码在本次导入中重复");
|
||||
}
|
||||
validateImportUnique(AirportMaster::getIcaoCode, airportMaster.getIcaoCode(), "该ICAO代码已存在", validationErrors);
|
||||
}
|
||||
if (Func.isEmpty(airportMaster.getName())) {
|
||||
addValidationError(validationErrors, "机场标准名称不能为空");
|
||||
}
|
||||
validateImportLength(airportMaster.getCode(), CODE_MAX_LENGTH, "编码不能超过20字", validationErrors);
|
||||
validateImportLength(airportMaster.getName(), NAME_MAX_LENGTH, "机场标准名称不能超过100字", validationErrors);
|
||||
validateImportLength(airportMaster.getShortName(), SHORT_NAME_MAX_LENGTH, "机场简称不能超过100字", validationErrors);
|
||||
validateImportLength(airportMaster.getProvinceName(), REGION_NAME_MAX_LENGTH, "所属省份不能超过128字", validationErrors);
|
||||
validateImportLength(airportMaster.getCityName(), REGION_NAME_MAX_LENGTH, "所属城市不能超过128字", validationErrors);
|
||||
validateImportLength(airportMaster.getDistrictName(), REGION_NAME_MAX_LENGTH, "所属区县不能超过128字", validationErrors);
|
||||
validateImportLength(airportMaster.getRegionCode(), REGION_CODE_MAX_LENGTH, "行政区划编号不能超过32字", validationErrors);
|
||||
if (Func.isEmpty(airportMaster.getDetailAddress())) {
|
||||
addValidationError(validationErrors, "详细地址不能为空");
|
||||
}
|
||||
validateImportLength(airportMaster.getDetailAddress(), DETAIL_ADDRESS_MAX_LENGTH, "详细地址不能超过255字", validationErrors);
|
||||
validateImportLength(airportMaster.getRemark(), REMARK_MAX_LENGTH, "备注不能超过200字", validationErrors);
|
||||
if (Func.isEmpty(airportMaster.getLongitude())) {
|
||||
addValidationError(validationErrors, "经度不能为空");
|
||||
} else if (!validRange(airportMaster.getLongitude(), MIN_LONGITUDE, MAX_LONGITUDE)) {
|
||||
addValidationError(validationErrors, "经度范围为 -180 到 180");
|
||||
}
|
||||
if (Func.isEmpty(airportMaster.getLatitude())) {
|
||||
addValidationError(validationErrors, "纬度不能为空");
|
||||
} else if (!validRange(airportMaster.getLatitude(), MIN_LATITUDE, MAX_LATITUDE)) {
|
||||
addValidationError(validationErrors, "纬度范围为 -90 到 90");
|
||||
}
|
||||
validateImportAirportRegion(airportMaster, validationErrors);
|
||||
return validationErrors;
|
||||
}
|
||||
|
||||
private void validateImportAirportRegion(AirportMaster airportMaster, List<String> validationErrors) {
|
||||
boolean provinceMissing = Func.isEmpty(airportMaster.getProvinceCode()) && Func.isEmpty(airportMaster.getProvinceName());
|
||||
boolean cityMissing = Func.isEmpty(airportMaster.getCityCode()) && Func.isEmpty(airportMaster.getCityName());
|
||||
boolean districtMissing = Func.isEmpty(airportMaster.getDistrictCode()) && Func.isEmpty(airportMaster.getDistrictName());
|
||||
if (provinceMissing) {
|
||||
addValidationError(validationErrors, "所属省份不能为空");
|
||||
}
|
||||
if (cityMissing) {
|
||||
addValidationError(validationErrors, "所属城市不能为空");
|
||||
}
|
||||
if (districtMissing) {
|
||||
addValidationError(validationErrors, "所属区县不能为空");
|
||||
}
|
||||
if (provinceMissing || cityMissing || districtMissing) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
fillRegion(airportMaster);
|
||||
} catch (ServiceException exception) {
|
||||
addValidationError(validationErrors, exception.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private void validateImportUnique(com.baomidou.mybatisplus.core.toolkit.support.SFunction<AirportMaster, ?> column,
|
||||
String value, String message, List<String> validationErrors) {
|
||||
if (count(Wrappers.<AirportMaster>lambdaQuery()
|
||||
.eq(column, value)
|
||||
.eq(AirportMaster::getIsDeleted, 0)) > 0L) {
|
||||
addValidationError(validationErrors, message);
|
||||
}
|
||||
}
|
||||
|
||||
private void validateImportLength(String value, int maxLength, String message, List<String> validationErrors) {
|
||||
if (Func.isNotEmpty(value) && value.length() > maxLength) {
|
||||
addValidationError(validationErrors, message);
|
||||
}
|
||||
}
|
||||
|
||||
private void addValidationError(List<String> validationErrors, String message) {
|
||||
if (Func.isNotEmpty(message) && !validationErrors.contains(message)) {
|
||||
validationErrors.add(message);
|
||||
}
|
||||
}
|
||||
|
||||
private String formatImportErrorMessage(List<String> validationErrors) {
|
||||
StringBuilder errorMessage = new StringBuilder();
|
||||
for (int index = 0; index < validationErrors.size(); index++) {
|
||||
if (index > 0) {
|
||||
errorMessage.append(System.lineSeparator());
|
||||
}
|
||||
errorMessage.append(index + 1).append(". ").append(validationErrors.get(index));
|
||||
}
|
||||
return errorMessage.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<AirportMasterExcel> exportAirportMaster(Wrapper<AirportMaster> queryWrapper) {
|
||||
public List<AirportMasterExportExcel> exportAirportMaster(Wrapper<AirportMaster> queryWrapper) {
|
||||
List<AirportMaster> airportMasterList = list(queryWrapper);
|
||||
return airportMasterList.stream().map(airportMaster -> {
|
||||
AirportMasterExcel excel = Objects.requireNonNull(BeanUtil.copyProperties(airportMaster, AirportMasterExcel.class));
|
||||
AirportMasterExportExcel excel = Objects.requireNonNull(BeanUtil.copyProperties(airportMaster, AirportMasterExportExcel.class));
|
||||
excel.setLongitude(scaleCoordinate(airportMaster.getLongitude(), MIN_LONGITUDE, MAX_LONGITUDE));
|
||||
excel.setLatitude(scaleCoordinate(airportMaster.getLatitude(), MIN_LATITUDE, MAX_LATITUDE));
|
||||
excel.setDataSource(normalizeDataSource(airportMaster.getDataSource()));
|
||||
excel.setStatusName(Objects.equals(airportMaster.getStatus(), STATUS_ENABLED) ? "启用" : "停用");
|
||||
excel.setUpdateUserName(UserCache.getUserRealName(airportMaster.getUpdateUser()));
|
||||
return excel;
|
||||
}).toList();
|
||||
}
|
||||
@@ -165,7 +337,7 @@ public class AirportMasterServiceImpl extends BaseServiceImpl<AirportMasterMappe
|
||||
private void prepare(AirportMaster airportMaster, String defaultDataSource) {
|
||||
airportMaster.setIataCode(trimToEmpty(airportMaster.getIataCode()).toUpperCase(Locale.ROOT));
|
||||
airportMaster.setCode(CODE_PREFIX + airportMaster.getIataCode());
|
||||
airportMaster.setIcaoCode(trimToEmpty(airportMaster.getIcaoCode()).toUpperCase(Locale.ROOT));
|
||||
airportMaster.setIcaoCode(normalizeOptionalCode(airportMaster.getIcaoCode()));
|
||||
airportMaster.setName(trimToEmpty(airportMaster.getName()));
|
||||
airportMaster.setShortName(trimToNull(airportMaster.getShortName()));
|
||||
airportMaster.setProvinceCode(trimToNull(airportMaster.getProvinceCode()));
|
||||
@@ -220,6 +392,9 @@ public class AirportMasterServiceImpl extends BaseServiceImpl<AirportMasterMappe
|
||||
if (Func.isEmpty(airportMaster.getProvinceCode()) || Func.isEmpty(airportMaster.getCityCode()) || Func.isEmpty(airportMaster.getDistrictCode())) {
|
||||
throw new ServiceException("请选择省份、城市和区县");
|
||||
}
|
||||
if (Func.isEmpty(airportMaster.getDetailAddress())) {
|
||||
throw new ServiceException("详细地址不能为空");
|
||||
}
|
||||
validateCoordinate(airportMaster.getLongitude(), MIN_LONGITUDE, MAX_LONGITUDE, "经度");
|
||||
validateCoordinate(airportMaster.getLatitude(), MIN_LATITUDE, MAX_LATITUDE, "纬度");
|
||||
validateDataSource(airportMaster.getDataSource());
|
||||
@@ -235,9 +410,10 @@ public class AirportMasterServiceImpl extends BaseServiceImpl<AirportMasterMappe
|
||||
province = regionService.getById(airportMaster.getProvinceCode());
|
||||
}
|
||||
if (Func.isEmpty(province) && Func.isNotEmpty(airportMaster.getProvinceName())) {
|
||||
String provinceName = normalizeRegionName(airportMaster.getProvinceName());
|
||||
province = regionService.getOne(Wrappers.<Region>lambdaQuery()
|
||||
.eq(Region::getParentCode, DEFAULT_COUNTRY_CODE)
|
||||
.eq(Region::getName, airportMaster.getProvinceName()), false);
|
||||
.eq(Region::getRegionLevel, PROVINCE_REGION_LEVEL)
|
||||
.eq(Region::getName, provinceName), false);
|
||||
}
|
||||
if (Func.isEmpty(province)) {
|
||||
throw new ServiceException("请选择省份");
|
||||
@@ -315,7 +491,26 @@ public class AirportMasterServiceImpl extends BaseServiceImpl<AirportMasterMappe
|
||||
}
|
||||
}
|
||||
|
||||
private void prepareSubmitTarget(AirportMaster airportMaster) {
|
||||
if (Func.isNotEmpty(airportMaster.getId())) {
|
||||
return;
|
||||
}
|
||||
AirportMaster existingAirportMaster = baseMapper.selectByCodeIncludingDeleted(airportMaster.getCode());
|
||||
if (existingAirportMaster == null) {
|
||||
return;
|
||||
}
|
||||
if (!Objects.equals(existingAirportMaster.getIsDeleted(), 1)) {
|
||||
throw new ServiceException("该编码已存在");
|
||||
}
|
||||
baseMapper.restoreById(existingAirportMaster.getId());
|
||||
airportMaster.setId(existingAirportMaster.getId());
|
||||
airportMaster.setIsDeleted(0);
|
||||
}
|
||||
|
||||
private void validateUnique(AirportMaster airportMaster, com.baomidou.mybatisplus.core.toolkit.support.SFunction<AirportMaster, ?> column, String value, String message) {
|
||||
if (Func.isEmpty(value)) {
|
||||
return;
|
||||
}
|
||||
LambdaQueryWrapper<AirportMaster> queryWrapper = Wrappers.<AirportMaster>lambdaQuery()
|
||||
.eq(column, value)
|
||||
.eq(AirportMaster::getIsDeleted, 0);
|
||||
@@ -328,7 +523,11 @@ public class AirportMasterServiceImpl extends BaseServiceImpl<AirportMasterMappe
|
||||
}
|
||||
|
||||
private String trimToEmpty(String value) {
|
||||
return value == null ? "" : value.trim();
|
||||
return value == null ? "" : value.replace("\uFEFF", "").strip();
|
||||
}
|
||||
|
||||
private String normalizeRegionName(String value) {
|
||||
return trimToEmpty(value);
|
||||
}
|
||||
|
||||
private String trimToNull(String value) {
|
||||
@@ -336,6 +535,11 @@ public class AirportMasterServiceImpl extends BaseServiceImpl<AirportMasterMappe
|
||||
return trimValue.isEmpty() ? null : trimValue;
|
||||
}
|
||||
|
||||
private String normalizeOptionalCode(String value) {
|
||||
String normalizedValue = trimToNull(value);
|
||||
return normalizedValue == null ? null : normalizedValue.toUpperCase(Locale.ROOT);
|
||||
}
|
||||
|
||||
private String normalizeDataSource(String dataSource) {
|
||||
String value = trimToEmpty(dataSource);
|
||||
return SOURCE_MANUAL_OLD.equals(value) ? SOURCE_MANUAL : value;
|
||||
|
||||
+1
-1
@@ -149,7 +149,7 @@ public class CargoTypeServiceImpl extends BaseServiceImpl<CargoTypeMapper, Cargo
|
||||
} catch (Exception exception) {
|
||||
String message = exception instanceof ServiceException ? exception.getMessage() : "导入失败";
|
||||
excel.setErrorMessage(message);
|
||||
failureList.add(toImportFailureExcel(excel, "第" + (index + 2) + "行:" + message));
|
||||
failureList.add(toImportFailureExcel(excel, message));
|
||||
}
|
||||
}
|
||||
return failureList;
|
||||
|
||||
+1
-1
@@ -134,7 +134,7 @@ public class CurrencyServiceImpl extends BaseServiceImpl<CurrencyMapper, Currenc
|
||||
successCount++;
|
||||
} catch (Exception exception) {
|
||||
String message = exception instanceof ServiceException ? exception.getMessage() : "导入失败";
|
||||
excel.setErrorMessage("第" + (index + 2) + "行:" + message);
|
||||
excel.setErrorMessage(message);
|
||||
errorList.add(excel);
|
||||
}
|
||||
}
|
||||
|
||||
+213
-1
@@ -29,8 +29,12 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springblade.common.constant.DataStatusEnum;
|
||||
import org.springblade.core.cache.utils.CacheUtil;
|
||||
import org.springblade.core.log.exception.ServiceException;
|
||||
import org.springblade.core.mp.support.Condition;
|
||||
import org.springblade.core.secure.utils.AuthUtil;
|
||||
@@ -44,6 +48,7 @@ import org.springblade.system.pojo.entity.Dept;
|
||||
import org.springblade.system.pojo.entity.User;
|
||||
import org.springblade.system.pojo.vo.DeptVO;
|
||||
import org.springblade.system.pojo.vo.UserVO;
|
||||
import org.springblade.system.props.IamSyncProperties;
|
||||
import org.springblade.system.service.IDeptService;
|
||||
import org.springblade.system.service.IUserService;
|
||||
import org.springblade.system.wrapper.DeptWrapper;
|
||||
@@ -52,6 +57,13 @@ import org.springblade.thirdparty.oa.constant.OAConvertConstant;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.net.http.HttpClient;
|
||||
import java.net.http.HttpRequest;
|
||||
import java.net.http.HttpResponse;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.Duration;
|
||||
import java.util.*;
|
||||
import java.util.function.Function;
|
||||
import java.util.regex.Pattern;
|
||||
@@ -59,6 +71,7 @@ import java.util.stream.Collectors;
|
||||
|
||||
import static org.springblade.core.tenant.TenantGuard.EntityType.DEPT;
|
||||
import static org.springblade.core.tenant.TenantGuard.EntityType.DEPT_PARENT;
|
||||
import static org.springblade.core.cache.constant.CacheConstant.SYS_CACHE;
|
||||
|
||||
/**
|
||||
* 服务实现类
|
||||
@@ -72,8 +85,13 @@ public class DeptServiceImpl extends ServiceImpl<DeptMapper, Dept> implements ID
|
||||
|
||||
private static final String TENANT_ID = "tenantId";
|
||||
private static final String PARENT_ID = "parentId";
|
||||
private static final String IAM_SYNC_TENANT_ID = "000000";
|
||||
private static final Long IAM_SYNC_PARENT_ID = 1123598813738675201L;
|
||||
private static final HttpClient IAM_HTTP_CLIENT = HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(10)).build();
|
||||
|
||||
private final IUserService userService;
|
||||
private final IamSyncProperties iamSyncProperties;
|
||||
private final ObjectMapper objectMapper;
|
||||
|
||||
@Override
|
||||
public Dept getDetail(Dept dept) {
|
||||
@@ -151,6 +169,18 @@ public class DeptServiceImpl extends ServiceImpl<DeptMapper, Dept> implements ID
|
||||
return baseMapper.selectList(queryWrapper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Dept> listPlatformCompany() {
|
||||
LambdaQueryWrapper<Dept> queryWrapper = Wrappers.<Dept>lambdaQuery()
|
||||
.eq(Dept::getIsPlatformCompany, 1)
|
||||
.orderByAsc(Dept::getSort)
|
||||
.orderByAsc(Dept::getId);
|
||||
if (!AuthUtil.isAdministrator()) {
|
||||
queryWrapper.eq(Dept::getTenantId, AuthUtil.getTenantId());
|
||||
}
|
||||
return list(queryWrapper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDeptIds(String tenantId, String deptNames) {
|
||||
List<Dept> deptList = baseMapper.selectList(Wrappers.<Dept>query().lambda().eq(Dept::getTenantId, tenantId).in(Dept::getDeptName, Func.toStrList(deptNames)));
|
||||
@@ -232,6 +262,9 @@ public class DeptServiceImpl extends ServiceImpl<DeptMapper, Dept> implements ID
|
||||
dept.setAncestors(parent.getAncestors() + StringPool.COMMA + dept.getParentId());
|
||||
}
|
||||
dept.setIsDeleted(BladeConstant.DB_NOT_DELETED);
|
||||
if (dept.getIsPlatformCompany() == null) {
|
||||
dept.setIsPlatformCompany(0);
|
||||
}
|
||||
if (Func.isEmpty(dept.getTenantId())) {
|
||||
throw new ServiceException("租户ID不能为空");
|
||||
}
|
||||
@@ -240,6 +273,183 @@ public class DeptServiceImpl extends ServiceImpl<DeptMapper, Dept> implements ID
|
||||
return saveOrUpdate(dept);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public int syncIamOrganizations() {
|
||||
int pageNumber = 1;
|
||||
int fetchedCount = 0;
|
||||
int syncedCount = 0;
|
||||
int totalCount = -1;
|
||||
int pageSize = iamSyncProperties.getPageSize() > 0 ? iamSyncProperties.getPageSize() : 50;
|
||||
while (true) {
|
||||
JsonNode dataNode = requestIamOrgPage(pageNumber, pageSize);
|
||||
JsonNode orgList = dataNode.path("list");
|
||||
if (!orgList.isArray() || orgList.isEmpty()) {
|
||||
break;
|
||||
}
|
||||
if (dataNode.has("total")) {
|
||||
totalCount = dataNode.path("total").asInt(totalCount);
|
||||
}
|
||||
for (JsonNode orgNode : orgList) {
|
||||
if (syncIamOrganization(orgNode)) {
|
||||
syncedCount++;
|
||||
}
|
||||
}
|
||||
fetchedCount += orgList.size();
|
||||
int responsePage = dataNode.path("page").asInt(pageNumber);
|
||||
int responseSize = dataNode.path("size").asInt(pageSize);
|
||||
if ((totalCount >= 0 && fetchedCount >= totalCount)
|
||||
|| orgList.size() < pageSize
|
||||
|| (totalCount >= 0 && responsePage * responseSize >= totalCount)) {
|
||||
break;
|
||||
}
|
||||
pageNumber = responsePage + 1;
|
||||
}
|
||||
log.info("IAM组织同步完成,tenantId={}, fetchedCount={}, syncedCount={}", IAM_SYNC_TENANT_ID, fetchedCount, syncedCount);
|
||||
return syncedCount;
|
||||
}
|
||||
|
||||
private JsonNode requestIamOrgPage(int pageNumber, int pageSize) {
|
||||
try {
|
||||
Map<String, String> requestBody = new LinkedHashMap<>();
|
||||
requestBody.put("size", String.valueOf(pageSize));
|
||||
requestBody.put("page", String.valueOf(pageNumber));
|
||||
HttpRequest request = HttpRequest.newBuilder(URI.create(iamSyncProperties.getOrgListUrl()))
|
||||
.timeout(Duration.ofSeconds(20))
|
||||
.header("Accept", "application/json")
|
||||
.header("Content-Type", "application/json")
|
||||
.header("Auth", normalizeAuthorizationHeader(iamSyncProperties.getProfileAuthorization()))
|
||||
.header("Authorization", normalizeAuthorizationHeader(iamSyncProperties.getAuthorization()))
|
||||
.POST(HttpRequest.BodyPublishers.ofString(objectMapper.writeValueAsString(requestBody), StandardCharsets.UTF_8))
|
||||
.build();
|
||||
HttpResponse<String> response = IAM_HTTP_CLIENT.send(request, HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8));
|
||||
if (response.statusCode() < 200 || response.statusCode() >= 300) {
|
||||
throw new ServiceException(StringUtil.format("IAM组织接口调用失败,HTTP状态码:{}", response.statusCode()));
|
||||
}
|
||||
JsonNode responseNode = objectMapper.readTree(response.body());
|
||||
if (!"0".equals(responseNode.path("code").asText())) {
|
||||
throw new ServiceException(StringUtil.format("IAM组织接口调用失败:{}", responseNode.path("msg").asText()));
|
||||
}
|
||||
JsonNode dataNode = responseNode.path("data");
|
||||
if (!dataNode.isObject()) {
|
||||
throw new ServiceException("IAM组织接口返回数据格式错误");
|
||||
}
|
||||
return dataNode;
|
||||
} catch (InterruptedException exception) {
|
||||
Thread.currentThread().interrupt();
|
||||
log.error("调用IAM组织接口被中断,page={}", pageNumber, exception);
|
||||
throw new ServiceException("调用IAM组织接口被中断");
|
||||
} catch (IOException | IllegalArgumentException exception) {
|
||||
log.error("调用IAM组织接口失败,page={}", pageNumber, exception);
|
||||
throw new ServiceException("调用IAM组织接口失败");
|
||||
}
|
||||
}
|
||||
|
||||
private boolean syncIamOrganization(JsonNode orgNode) {
|
||||
String orgCode = readIamText(orgNode, "orgCode", "org_code", "organizationCode", "organization_code",
|
||||
"code", "app_org__org_code", "app_org__org_no", "app_org__organization_code", "app_org__code");
|
||||
String orgId = readIamText(orgNode, "orgId", "org_id", "id", "app_org__id", "app_org__org_id");
|
||||
if (StringUtil.isBlank(orgCode)) {
|
||||
orgCode = orgId;
|
||||
}
|
||||
if (StringUtil.isBlank(orgCode)) {
|
||||
log.warn("IAM组织缺少组织编码,跳过同步");
|
||||
return false;
|
||||
}
|
||||
if (orgCode.length() > 30) {
|
||||
log.warn("IAM组织编码超过30个字符,跳过同步,orgCode={}", orgCode);
|
||||
return false;
|
||||
}
|
||||
String orgName = readIamText(orgNode, "name", "orgName", "org_name", "organizationName", "organization_name",
|
||||
"fullName", "app_org__name", "app_org__org_name", "app_org__org_full_name", "app_org__organization_name");
|
||||
if (StringUtil.isBlank(orgName)) {
|
||||
log.warn("IAM组织缺少组织名称,跳过同步,orgCode={}", orgCode);
|
||||
return false;
|
||||
}
|
||||
Integer status = readIamInt(orgNode, "status", "org_status", "app_org__status", "app_org__org_status") == 1
|
||||
? DataStatusEnum.ENABLE.getCode() : DataStatusEnum.DISABLE.getCode();
|
||||
Dept dept = getOne(Wrappers.<Dept>lambdaQuery()
|
||||
.eq(Dept::getTenantId, IAM_SYNC_TENANT_ID)
|
||||
.eq(Dept::getDeptCode, orgCode), false);
|
||||
if (dept == null) {
|
||||
dept = new Dept();
|
||||
dept.setTenantId(IAM_SYNC_TENANT_ID);
|
||||
dept.setParentId(IAM_SYNC_PARENT_ID);
|
||||
dept.setAncestors(resolveIamParentAncestors());
|
||||
dept.setDeptCode(orgCode);
|
||||
dept.setDeptName(orgName);
|
||||
dept.setFullName(orgName);
|
||||
dept.setShortName(orgName);
|
||||
dept.setDeptCategory(1);
|
||||
dept.setSort(0);
|
||||
dept.setStatus(status);
|
||||
dept.setIsDeleted(BladeConstant.DB_NOT_DELETED);
|
||||
dept.setIsOa(1);
|
||||
dept.setIsPlatformCompany(0);
|
||||
dept.setSyncTime(new Date());
|
||||
boolean saved = save(dept);
|
||||
if (saved) {
|
||||
CacheUtil.clear(SYS_CACHE);
|
||||
CacheUtil.clear(SYS_CACHE, Boolean.FALSE);
|
||||
}
|
||||
return saved;
|
||||
}
|
||||
boolean changed = !Objects.equals(dept.getDeptName(), orgName) || !Objects.equals(dept.getFullName(), orgName)
|
||||
|| !Objects.equals(dept.getShortName(), orgName) || !Objects.equals(dept.getStatus(), status)
|
||||
|| !Objects.equals(dept.getIsOa(), 1);
|
||||
if (!changed) {
|
||||
return true;
|
||||
}
|
||||
dept.setDeptName(orgName);
|
||||
dept.setFullName(orgName);
|
||||
dept.setShortName(orgName);
|
||||
dept.setStatus(status);
|
||||
dept.setIsOa(1);
|
||||
dept.setSyncTime(new Date());
|
||||
CacheUtil.clear(SYS_CACHE);
|
||||
CacheUtil.clear(SYS_CACHE, Boolean.FALSE);
|
||||
return updateById(dept);
|
||||
}
|
||||
|
||||
private String resolveIamParentAncestors() {
|
||||
Dept parent = getById(IAM_SYNC_PARENT_ID);
|
||||
String ancestors = parent == null ? String.valueOf(BladeConstant.TOP_PARENT_ID) : parent.getAncestors();
|
||||
if (StringUtil.isBlank(ancestors)) {
|
||||
ancestors = String.valueOf(BladeConstant.TOP_PARENT_ID);
|
||||
}
|
||||
return ancestors + StringPool.COMMA + IAM_SYNC_PARENT_ID;
|
||||
}
|
||||
|
||||
private String readIamText(JsonNode node, String... fieldNames) {
|
||||
JsonNode valueNode = findIamNode(node, fieldNames);
|
||||
return valueNode == null || valueNode.isNull() ? StringPool.EMPTY : valueNode.asText().trim();
|
||||
}
|
||||
|
||||
private int readIamInt(JsonNode node, String... fieldNames) {
|
||||
JsonNode valueNode = findIamNode(node, fieldNames);
|
||||
return valueNode == null || valueNode.isNull() ? 0 : valueNode.asInt(0);
|
||||
}
|
||||
|
||||
private JsonNode findIamNode(JsonNode node, String... fieldNames) {
|
||||
for (String fieldName : fieldNames) {
|
||||
JsonNode valueNode = node.get(fieldName);
|
||||
if (valueNode != null && !valueNode.isNull()) {
|
||||
return valueNode;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private String normalizeAuthorizationHeader(String value) {
|
||||
if (StringUtil.isBlank(value)) {
|
||||
return StringPool.EMPTY;
|
||||
}
|
||||
if (StringUtil.startsWithIgnoreCase(value, "Basic ") || StringUtil.startsWithIgnoreCase(value, "Bearer ")) {
|
||||
return value;
|
||||
}
|
||||
return "Basic " + value;
|
||||
}
|
||||
|
||||
private void validateDeptCategory(Dept dept, Dept parent) {
|
||||
if (parent == null) {
|
||||
throw new ServiceException("请选择上级组织");
|
||||
@@ -264,8 +474,10 @@ public class DeptServiceImpl extends ServiceImpl<DeptMapper, Dept> implements ID
|
||||
|
||||
private void validateDeptCode(Dept dept, Dept parent) {
|
||||
String deptCode = dept.getDeptCode();
|
||||
// 组织编码非必填,为空时存 null,避免唯一索引冲突
|
||||
if (StringUtil.isBlank(deptCode)) {
|
||||
throw new ServiceException("组织编码不能为空");
|
||||
dept.setDeptCode(null);
|
||||
return;
|
||||
}
|
||||
deptCode = deptCode.trim();
|
||||
if (deptCode.length() > 30) {
|
||||
|
||||
+4
-1
@@ -136,7 +136,10 @@ public class DictBizServiceImpl extends ServiceImpl<DictBizMapper, DictBiz> impl
|
||||
|
||||
@Override
|
||||
public IPage<DictBizVO> parentList(Map<String, Object> dict, Query query) {
|
||||
IPage<DictBiz> page = this.page(Condition.getPage(query), Condition.getQueryWrapper(dict, DictBiz.class).lambda().eq(DictBiz::getParentId, CommonConstant.TOP_PARENT_ID).orderByAsc(DictBiz::getSort));
|
||||
IPage<DictBiz> page = this.page(Condition.getPage(query), Condition.getQueryWrapper(dict, DictBiz.class).lambda()
|
||||
.eq(DictBiz::getParentId, CommonConstant.TOP_PARENT_ID)
|
||||
.orderByAsc(DictBiz::getSort)
|
||||
.orderByDesc(DictBiz::getId));
|
||||
return DictBizWrapper.build().pageVO(page);
|
||||
}
|
||||
|
||||
|
||||
+4
-1
@@ -123,7 +123,10 @@ public class DictServiceImpl extends ServiceImpl<DictMapper, Dict> implements ID
|
||||
|
||||
@Override
|
||||
public IPage<DictVO> parentList(Map<String, Object> dict, Query query) {
|
||||
IPage<Dict> page = this.page(Condition.getPage(query), Condition.getQueryWrapper(dict, Dict.class).lambda().eq(Dict::getParentId, CommonConstant.TOP_PARENT_ID).orderByAsc(Dict::getSort));
|
||||
IPage<Dict> page = this.page(Condition.getPage(query), Condition.getQueryWrapper(dict, Dict.class).lambda()
|
||||
.eq(Dict::getParentId, CommonConstant.TOP_PARENT_ID)
|
||||
.orderByAsc(Dict::getSort)
|
||||
.orderByDesc(Dict::getId));
|
||||
return DictWrapper.build().pageVO(page);
|
||||
}
|
||||
|
||||
|
||||
+46
-5
@@ -34,7 +34,10 @@ import org.springblade.core.mp.base.BaseServiceImpl;
|
||||
import org.springblade.core.tool.utils.BeanUtil;
|
||||
import org.springblade.core.tool.utils.Func;
|
||||
import org.springblade.system.cache.DictBizCache;
|
||||
import org.springblade.system.cache.SysCache;
|
||||
import org.springblade.system.cache.UserCache;
|
||||
import org.springblade.system.excel.FeeItemExcel;
|
||||
import org.springblade.system.excel.FeeItemExportExcel;
|
||||
import org.springblade.system.excel.FeeItemImportFailureExcel;
|
||||
import org.springblade.system.mapper.FeeItemMapper;
|
||||
import org.springblade.system.pojo.entity.DictBiz;
|
||||
@@ -44,6 +47,7 @@ import org.springblade.system.service.IFeeItemService;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
@@ -62,6 +66,9 @@ public class FeeItemServiceImpl extends BaseServiceImpl<FeeItemMapper, FeeItem>
|
||||
private static final int FEE_CATEGORY_MAX_LENGTH = 50;
|
||||
private static final int NAME_MAX_LENGTH = 50;
|
||||
private static final int ENGLISH_NAME_MAX_LENGTH = 100;
|
||||
private static final int REMARK_MAX_LENGTH = 200;
|
||||
private static final BigDecimal TAX_RATE_MIN = BigDecimal.ZERO;
|
||||
private static final BigDecimal TAX_RATE_MAX = new BigDecimal("100");
|
||||
|
||||
@Override
|
||||
public IPage<FeeItemVO> selectFeeItemPage(IPage<FeeItemVO> page, FeeItemVO feeItem) {
|
||||
@@ -118,7 +125,7 @@ public class FeeItemServiceImpl extends BaseServiceImpl<FeeItemMapper, FeeItem>
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<FeeItemExcel> exportFeeItem(Wrapper<FeeItem> queryWrapper) {
|
||||
public List<FeeItemExportExcel> exportFeeItem(Wrapper<FeeItem> queryWrapper) {
|
||||
return list(queryWrapper).stream().map(this::toExcel).toList();
|
||||
}
|
||||
|
||||
@@ -126,6 +133,7 @@ public class FeeItemServiceImpl extends BaseServiceImpl<FeeItemMapper, FeeItem>
|
||||
feeItem.setFeeCategory(trimToEmpty(feeItem.getFeeCategory()));
|
||||
feeItem.setName(trimToEmpty(feeItem.getName()));
|
||||
feeItem.setEnglishName(trimToNull(feeItem.getEnglishName()));
|
||||
feeItem.setRemark(trimToNull(feeItem.getRemark()));
|
||||
appendFeeCategoryPrefix(feeItem);
|
||||
if (Func.isEmpty(feeItem.getStatus())) {
|
||||
feeItem.setStatus(STATUS_ENABLED);
|
||||
@@ -151,10 +159,23 @@ public class FeeItemServiceImpl extends BaseServiceImpl<FeeItemMapper, FeeItem>
|
||||
if (Func.isNotEmpty(feeItem.getEnglishName()) && feeItem.getEnglishName().length() > ENGLISH_NAME_MAX_LENGTH) {
|
||||
throw new ServiceException("费用项代码不能超过100字");
|
||||
}
|
||||
validateUniqueName(feeItem);
|
||||
BigDecimal taxRate = feeItem.getTaxRate();
|
||||
if (taxRate == null) {
|
||||
throw new ServiceException("税率不能为空");
|
||||
}
|
||||
if (taxRate.compareTo(TAX_RATE_MIN) < 0 || taxRate.compareTo(TAX_RATE_MAX) > 0) {
|
||||
throw new ServiceException("税率必须在0到100之间");
|
||||
}
|
||||
if (taxRate.stripTrailingZeros().scale() > 2) {
|
||||
throw new ServiceException("税率最多保留2位小数");
|
||||
}
|
||||
if (Func.isNotEmpty(feeItem.getRemark()) && feeItem.getRemark().length() > REMARK_MAX_LENGTH) {
|
||||
throw new ServiceException("备注不能超过200个字");
|
||||
}
|
||||
validateUnique(feeItem);
|
||||
}
|
||||
|
||||
private void validateUniqueName(FeeItem feeItem) {
|
||||
private void validateUnique(FeeItem feeItem) {
|
||||
LambdaQueryWrapper<FeeItem> queryWrapper = Wrappers.<FeeItem>lambdaQuery()
|
||||
.eq(FeeItem::getName, feeItem.getName())
|
||||
.eq(FeeItem::getIsDeleted, 0);
|
||||
@@ -164,6 +185,19 @@ public class FeeItemServiceImpl extends BaseServiceImpl<FeeItemMapper, FeeItem>
|
||||
if (count(queryWrapper) > 0L) {
|
||||
throw new ServiceException("该费用项已存在");
|
||||
}
|
||||
|
||||
if (Func.isEmpty(feeItem.getEnglishName())) {
|
||||
return;
|
||||
}
|
||||
LambdaQueryWrapper<FeeItem> codeQueryWrapper = Wrappers.<FeeItem>lambdaQuery()
|
||||
.eq(FeeItem::getEnglishName, feeItem.getEnglishName())
|
||||
.eq(FeeItem::getIsDeleted, 0);
|
||||
if (Func.isNotEmpty(feeItem.getId())) {
|
||||
codeQueryWrapper.ne(FeeItem::getId, feeItem.getId());
|
||||
}
|
||||
if (count(codeQueryWrapper) > 0L) {
|
||||
throw new ServiceException("该费用项代码已存在");
|
||||
}
|
||||
}
|
||||
|
||||
private FeeItem buildImportFeeItem(FeeItemExcel excel) {
|
||||
@@ -171,13 +205,19 @@ public class FeeItemServiceImpl extends BaseServiceImpl<FeeItemMapper, FeeItem>
|
||||
feeItem.setFeeCategory(resolveFeeCategory(excel.getFeeCategory()));
|
||||
feeItem.setEnglishName(trimToNull(excel.getEnglishName()));
|
||||
feeItem.setName(trimToEmpty(excel.getName()));
|
||||
feeItem.setTaxRate(excel.getTaxRate());
|
||||
feeItem.setStatus(STATUS_ENABLED);
|
||||
return feeItem;
|
||||
}
|
||||
|
||||
private FeeItemExcel toExcel(FeeItem feeItem) {
|
||||
FeeItemExcel excel = Objects.requireNonNull(BeanUtil.copyProperties(feeItem, FeeItemExcel.class));
|
||||
private FeeItemExportExcel toExcel(FeeItem feeItem) {
|
||||
FeeItemExportExcel excel = Objects.requireNonNull(BeanUtil.copyProperties(feeItem, FeeItemExportExcel.class));
|
||||
excel.setFeeCategory(formatFeeCategory(feeItem.getFeeCategory()));
|
||||
excel.setCreateDeptName(
|
||||
Func.isEmpty(feeItem.getCreateDept()) ? "" : SysCache.getDeptName(feeItem.getCreateDept())
|
||||
);
|
||||
excel.setUpdateUserName(UserCache.getUserRealName(feeItem.getUpdateUser()));
|
||||
excel.setStatusName(Objects.equals(feeItem.getStatus(), STATUS_ENABLED) ? "启用" : "停用");
|
||||
return excel;
|
||||
}
|
||||
|
||||
@@ -186,6 +226,7 @@ public class FeeItemServiceImpl extends BaseServiceImpl<FeeItemMapper, FeeItem>
|
||||
failureExcel.setFeeCategory(excel.getFeeCategory());
|
||||
failureExcel.setEnglishName(excel.getEnglishName());
|
||||
failureExcel.setName(excel.getName());
|
||||
failureExcel.setTaxRate(excel.getTaxRate());
|
||||
failureExcel.setFailureReason(failureReason);
|
||||
return failureExcel;
|
||||
}
|
||||
|
||||
+92
@@ -0,0 +1,92 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments from this software for such purposes. Copyright of this software remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.system.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import org.springblade.core.log.exception.ServiceException;
|
||||
import org.springblade.core.mp.base.BaseServiceImpl;
|
||||
import org.springblade.core.tool.utils.Func;
|
||||
import org.springblade.system.mapper.InvoiceItemMapper;
|
||||
import org.springblade.system.pojo.entity.InvoiceItem;
|
||||
import org.springblade.system.pojo.vo.InvoiceItemVO;
|
||||
import org.springblade.system.service.IInvoiceItemService;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/**
|
||||
* 开票项目服务实现类
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@Service
|
||||
public class InvoiceItemServiceImpl extends BaseServiceImpl<InvoiceItemMapper, InvoiceItem>
|
||||
implements IInvoiceItemService {
|
||||
|
||||
private static final int SHORT_NAME_MAX_LENGTH = 100;
|
||||
private static final int TAX_CODE_MAX_LENGTH = 30;
|
||||
private static final int CATEGORY_NAME_MAX_LENGTH = 200;
|
||||
|
||||
@Override
|
||||
public IPage<InvoiceItemVO> selectInvoiceItemPage(IPage<InvoiceItemVO> page, InvoiceItemVO invoiceItem) {
|
||||
return page.setRecords(baseMapper.selectInvoiceItemPage(page, invoiceItem));
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public boolean submit(InvoiceItem invoiceItem) {
|
||||
prepare(invoiceItem);
|
||||
validate(invoiceItem);
|
||||
return saveOrUpdate(invoiceItem);
|
||||
}
|
||||
|
||||
private void prepare(InvoiceItem invoiceItem) {
|
||||
invoiceItem.setShortName(trim(invoiceItem.getShortName()));
|
||||
invoiceItem.setTaxClassificationCode(trim(invoiceItem.getTaxClassificationCode()));
|
||||
invoiceItem.setCategoryName(trim(invoiceItem.getCategoryName()));
|
||||
if (Func.isEmpty(invoiceItem.getStatus())) {
|
||||
invoiceItem.setStatus(1);
|
||||
}
|
||||
}
|
||||
|
||||
private void validate(InvoiceItem invoiceItem) {
|
||||
if (Func.isEmpty(invoiceItem.getShortName())) throw new ServiceException("货物或服务简称不能为空");
|
||||
if (invoiceItem.getShortName().length() > SHORT_NAME_MAX_LENGTH) throw new ServiceException("货物或服务简称不能超过100字");
|
||||
if (Func.isEmpty(invoiceItem.getTaxClassificationCode())) throw new ServiceException("税收分类编码不能为空");
|
||||
if (invoiceItem.getTaxClassificationCode().length() > TAX_CODE_MAX_LENGTH) throw new ServiceException("税收分类编码不能超过30字");
|
||||
if (Func.isEmpty(invoiceItem.getCategoryName())) throw new ServiceException("商品和服务分类名称不能为空");
|
||||
if (invoiceItem.getCategoryName().length() > CATEGORY_NAME_MAX_LENGTH) throw new ServiceException("商品和服务分类名称不能超过200字");
|
||||
BigDecimal taxRate = invoiceItem.getDefaultTaxRate();
|
||||
if (taxRate == null) throw new ServiceException("默认税率不能为空");
|
||||
if (taxRate.compareTo(BigDecimal.ZERO) < 0 || taxRate.compareTo(new BigDecimal("100")) > 0) throw new ServiceException("默认税率必须在0到100之间");
|
||||
boolean duplicate = count(Wrappers.<InvoiceItem>lambdaQuery()
|
||||
.eq(InvoiceItem::getTaxClassificationCode, invoiceItem.getTaxClassificationCode())
|
||||
.eq(InvoiceItem::getShortName, invoiceItem.getShortName())
|
||||
.eq(InvoiceItem::getIsDeleted, 0)
|
||||
.ne(Func.isNotEmpty(invoiceItem.getId()), InvoiceItem::getId, invoiceItem.getId())) > 0;
|
||||
if (duplicate) throw new ServiceException("该开票项目已存在");
|
||||
}
|
||||
|
||||
private String trim(String value) {
|
||||
return value == null ? "" : value.trim();
|
||||
}
|
||||
|
||||
}
|
||||
+163
@@ -0,0 +1,163 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.system.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import org.springblade.core.log.exception.ServiceException;
|
||||
import org.springblade.core.mp.base.BaseServiceImpl;
|
||||
import org.springblade.core.tool.utils.Func;
|
||||
import org.springblade.system.mapper.MeasurementUnitMapper;
|
||||
import org.springblade.system.pojo.entity.MeasurementUnit;
|
||||
import org.springblade.system.pojo.vo.MeasurementUnitVO;
|
||||
import org.springblade.system.service.IMeasurementUnitService;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* 计量单位服务实现类
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@Service
|
||||
public class MeasurementUnitServiceImpl extends BaseServiceImpl<MeasurementUnitMapper, MeasurementUnit>
|
||||
implements IMeasurementUnitService {
|
||||
|
||||
private static final int STATUS_ENABLED = 1;
|
||||
private static final int STATUS_DISABLED = 2;
|
||||
private static final int UNIT_CODE_MAX_LENGTH = 50;
|
||||
private static final int UNIT_NAME_MAX_LENGTH = 50;
|
||||
private static final int DIMENSION_MAX_LENGTH = 20;
|
||||
private static final int REMARK_MAX_LENGTH = 200;
|
||||
private static final Set<String> DIMENSIONS = Set.of("重量", "体积", "数量");
|
||||
|
||||
@Override
|
||||
public IPage<MeasurementUnitVO> selectMeasurementUnitPage(IPage<MeasurementUnitVO> page,
|
||||
MeasurementUnitVO measurementUnit) {
|
||||
return page.setRecords(baseMapper.selectMeasurementUnitPage(page, measurementUnit));
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public boolean submit(MeasurementUnit measurementUnit) {
|
||||
prepare(measurementUnit);
|
||||
validate(measurementUnit);
|
||||
return saveOrUpdate(measurementUnit);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public boolean changeStatus(Long id, Integer status) {
|
||||
if (Func.isEmpty(id)) {
|
||||
throw new ServiceException("主键不能为空");
|
||||
}
|
||||
MeasurementUnit measurementUnit = getById(id);
|
||||
if (Func.isEmpty(measurementUnit)) {
|
||||
throw new ServiceException("计量单位不存在");
|
||||
}
|
||||
if (!Objects.equals(status, STATUS_ENABLED) && !Objects.equals(status, STATUS_DISABLED)) {
|
||||
throw new ServiceException("启停状态不正确");
|
||||
}
|
||||
MeasurementUnit update = new MeasurementUnit();
|
||||
update.setId(id);
|
||||
update.setStatus(status);
|
||||
return updateById(update);
|
||||
}
|
||||
|
||||
private void prepare(MeasurementUnit measurementUnit) {
|
||||
measurementUnit.setUnitCode(trimToEmpty(measurementUnit.getUnitCode()));
|
||||
measurementUnit.setUnitName(trimToEmpty(measurementUnit.getUnitName()));
|
||||
measurementUnit.setDimension(trimToEmpty(measurementUnit.getDimension()));
|
||||
measurementUnit.setRemark(trimToNull(measurementUnit.getRemark()));
|
||||
if (Func.isEmpty(measurementUnit.getStatus())) {
|
||||
measurementUnit.setStatus(STATUS_ENABLED);
|
||||
}
|
||||
}
|
||||
|
||||
private void validate(MeasurementUnit measurementUnit) {
|
||||
if (Func.isEmpty(measurementUnit.getUnitCode())) {
|
||||
throw new ServiceException("计量单位编码不能为空");
|
||||
}
|
||||
if (measurementUnit.getUnitCode().length() > UNIT_CODE_MAX_LENGTH) {
|
||||
throw new ServiceException("计量单位编码不能超过50字");
|
||||
}
|
||||
if (Func.isEmpty(measurementUnit.getUnitName())) {
|
||||
throw new ServiceException("计量单位不能为空");
|
||||
}
|
||||
if (measurementUnit.getUnitName().length() > UNIT_NAME_MAX_LENGTH) {
|
||||
throw new ServiceException("计量单位不能超过50字");
|
||||
}
|
||||
if (Func.isEmpty(measurementUnit.getDimension())) {
|
||||
throw new ServiceException("计量维度不能为空");
|
||||
}
|
||||
if (measurementUnit.getDimension().length() > DIMENSION_MAX_LENGTH
|
||||
|| !DIMENSIONS.contains(measurementUnit.getDimension())) {
|
||||
throw new ServiceException("计量维度不正确");
|
||||
}
|
||||
if (Func.isNotEmpty(measurementUnit.getRemark())
|
||||
&& measurementUnit.getRemark().length() > REMARK_MAX_LENGTH) {
|
||||
throw new ServiceException("备注不能超过200个字");
|
||||
}
|
||||
if (!Objects.equals(measurementUnit.getStatus(), STATUS_ENABLED)
|
||||
&& !Objects.equals(measurementUnit.getStatus(), STATUS_DISABLED)) {
|
||||
throw new ServiceException("启停状态不正确");
|
||||
}
|
||||
validateUniqueUnitCode(measurementUnit);
|
||||
validateUniqueUnitName(measurementUnit);
|
||||
}
|
||||
|
||||
private void validateUniqueUnitCode(MeasurementUnit measurementUnit) {
|
||||
LambdaQueryWrapper<MeasurementUnit> queryWrapper = Wrappers.<MeasurementUnit>lambdaQuery()
|
||||
.eq(MeasurementUnit::getUnitCode, measurementUnit.getUnitCode())
|
||||
.eq(MeasurementUnit::getIsDeleted, 0);
|
||||
if (Func.isNotEmpty(measurementUnit.getId())) {
|
||||
queryWrapper.ne(MeasurementUnit::getId, measurementUnit.getId());
|
||||
}
|
||||
if (count(queryWrapper) > 0L) {
|
||||
throw new ServiceException("该计量单位编码已存在");
|
||||
}
|
||||
}
|
||||
|
||||
private void validateUniqueUnitName(MeasurementUnit measurementUnit) {
|
||||
LambdaQueryWrapper<MeasurementUnit> queryWrapper = Wrappers.<MeasurementUnit>lambdaQuery()
|
||||
.eq(MeasurementUnit::getUnitName, measurementUnit.getUnitName())
|
||||
.eq(MeasurementUnit::getIsDeleted, 0);
|
||||
if (Func.isNotEmpty(measurementUnit.getId())) {
|
||||
queryWrapper.ne(MeasurementUnit::getId, measurementUnit.getId());
|
||||
}
|
||||
if (count(queryWrapper) > 0L) {
|
||||
throw new ServiceException("该计量单位已存在");
|
||||
}
|
||||
}
|
||||
|
||||
private String trimToEmpty(String value) {
|
||||
return value == null ? "" : value.trim();
|
||||
}
|
||||
|
||||
private String trimToNull(String value) {
|
||||
String trimValue = trimToEmpty(value);
|
||||
return trimValue.isEmpty() ? null : trimValue;
|
||||
}
|
||||
|
||||
}
|
||||
+29
@@ -164,6 +164,35 @@ public class MenuServiceImpl extends ServiceImpl<MenuMapper, Menu> implements IM
|
||||
return menuWrapper.listNodeVO(buttons);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> permissionCodes(String roleId) {
|
||||
List<String> permissionCodes = new ArrayList<>();
|
||||
// Feign 调用时无登录态,不能走 AuthUtil.isAdministrator() 分支;按 roleId 取按钮权限
|
||||
List<Menu> buttons = StringUtil.isBlank(roleId)
|
||||
? Collections.emptyList()
|
||||
: baseMapper.buttons(Func.toLongList(roleId));
|
||||
MenuWrapper menuWrapper = new MenuWrapper();
|
||||
collectLeafPermissionCodes(menuWrapper.listNodeVO(buttons), permissionCodes);
|
||||
return permissionCodes;
|
||||
}
|
||||
|
||||
/**
|
||||
* 递归收集按钮树叶子节点的权限编号(与前端 SET_PERMISSION 逻辑一致)
|
||||
*/
|
||||
private void collectLeafPermissionCodes(List<MenuVO> menuList, List<String> permissionCodes) {
|
||||
if (menuList == null || menuList.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
for (MenuVO menu : menuList) {
|
||||
List<MenuVO> children = menu.getChildren();
|
||||
if (children != null && !children.isEmpty()) {
|
||||
collectLeafPermissionCodes(children, permissionCodes);
|
||||
} else if (StringUtil.isNotBlank(menu.getCode())) {
|
||||
permissionCodes.add(menu.getCode());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<TreeNode> tree() {
|
||||
return ForestNodeMerger.merge(baseMapper.tree());
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user