diff --git a/blade-auth/src/main/java/org/springblade/auth/AuthApplication.java b/blade-auth/src/main/java/org/springblade/auth/AuthApplication.java index 62627f3..c674021 100644 --- a/blade-auth/src/main/java/org/springblade/auth/AuthApplication.java +++ b/blade-auth/src/main/java/org/springblade/auth/AuthApplication.java @@ -29,6 +29,7 @@ package org.springblade.auth; 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.session.data.redis.config.annotation.web.http.EnableRedisHttpSession; /** @@ -38,6 +39,7 @@ import org.springframework.session.data.redis.config.annotation.web.http.EnableR */ @EnableRedisHttpSession @BladeCloudApplication +@ComponentScan({"org.springblade.auth", "org.springblade.**.feign"}) public class AuthApplication { public static void main(String[] args) { diff --git a/blade-auth/src/main/java/org/springblade/auth/config/BladeAuthConfiguration.java b/blade-auth/src/main/java/org/springblade/auth/config/BladeAuthConfiguration.java index f587e7e..15a2061 100644 --- a/blade-auth/src/main/java/org/springblade/auth/config/BladeAuthConfiguration.java +++ b/blade-auth/src/main/java/org/springblade/auth/config/BladeAuthConfiguration.java @@ -25,7 +25,6 @@ */ package org.springblade.auth.config; -import org.springblade.auth.granter.IamAwareTokenGranterFactory; import org.springblade.auth.handler.BladeAuthorizationHandler; import org.springblade.auth.handler.BladeLockHandler; import org.springblade.auth.handler.BladeLogHandler; @@ -38,9 +37,6 @@ import org.springblade.core.jwt.props.JwtProperties; import org.springblade.core.launch.props.BladeProperties; import org.springblade.core.launch.server.ServerInfo; import org.springblade.core.oauth2.config.OAuth2AutoConfiguration; -import org.springblade.core.oauth2.granter.TokenGranter; -import org.springblade.core.oauth2.granter.TokenGranterEnhancer; -import org.springblade.core.oauth2.granter.TokenGranterFactory; import org.springblade.core.oauth2.handler.AuthorizationHandler; import org.springblade.core.oauth2.handler.PasswordHandler; import org.springblade.core.oauth2.handler.TokenHandler; @@ -55,11 +51,8 @@ import org.springframework.boot.autoconfigure.AutoConfigureBefore; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Primary; import org.springframework.jdbc.core.JdbcTemplate; -import java.util.List; - /** * BladeAuthConfiguration * @@ -108,12 +101,4 @@ public class BladeAuthConfiguration { return new BladeUserDetailService(userClient); } - @Primary - @Bean("iamAwareTokenGranterFactory") - public TokenGranterFactory tokenGranterFactory(List tokenGranters, - List tokenGranterEnhancers, - OAuth2Properties properties) { - return new IamAwareTokenGranterFactory(tokenGranters, tokenGranterEnhancers, properties); - } - } diff --git a/blade-auth/src/main/resources/application.yml b/blade-auth/src/main/resources/application.yml index 108eb04..9e7573c 100644 --- a/blade-auth/src/main/resources/application.yml +++ b/blade-auth/src/main/resources/application.yml @@ -63,16 +63,3 @@ social: client-id: 233************ client-secret: 233************************************ redirect-uri: ${social.domain}/oauth/redirect/dingtalk - -# IAM统一身份认证 -iam: - sso: - token-url: http://172.16.204.83:38000/gwzh/IAM/IAM_SSO_TOKEN - profile-url: http://172.16.204.83:38000/gwzh/IAM/IAM_SSO_PROFILE - client-id: f0b52f23f71b1649c468 - client-secret: 5b3d8575f0899946623ab86523ac3dd8ee98 - system-client-id: saber3 - system-client-secret: saber3_secret - redirect-uri: http://172.16.203.228:8000/callback - authorization: Z3d6aF90bXMtOVJJU0RVN1U6YW0yYkcwWnBJZ0RQZmtrSjNaZkZjUDBSaGFuSEtxQng= - profile-authorization: Z3d6aF90bXMtOVJJU0RVN1U6YW0yYkcwWnBJZ0RQZmtrSjNaZkZjUDBSaGFuSEtxQng= diff --git a/blade-common/pom.xml b/blade-common/pom.xml index 3ae15c5..5fb0c22 100644 --- a/blade-common/pom.xml +++ b/blade-common/pom.xml @@ -22,14 +22,24 @@ org.springblade blade-starter-loadbalancer + + org.springblade + blade-core-auto + provided + cn.idev.excel fastexcel - org.springblade - blade-core-auto - provided + com.huaweicloud + esdk-obs-java-bundle + 3.24.9 + + + commons-io + commons-io + 2.16.1 diff --git a/blade-common/src/main/java/org/springblade/common/constant/CommonConstant.java b/blade-common/src/main/java/org/springblade/common/constant/CommonConstant.java index a3e1cfa..e6cda7e 100644 --- a/blade-common/src/main/java/org/springblade/common/constant/CommonConstant.java +++ b/blade-common/src/main/java/org/springblade/common/constant/CommonConstant.java @@ -72,4 +72,35 @@ public interface CommonConstant { */ Integer API_SCOPE_CATEGORY = 2; + + /** + * 是 2 + */ + Integer YES = 2; + /** + * 不是 1 + */ + Integer NO = 1; + /** + * 系统名称 + */ + String SYSTEM_CODE = "CK-ERP"; + /** + * 默认密码参数值 + */ + String DEFAULT_PARAM_PASSWORD = "account.initPassword"; + /** + * 默认角色参数值 + */ + String DEFAULT_PARAM_ROLE = "account.initRole"; + /** + * 默认角色 + */ + String DEFAULT_ROLE = "default"; + /** + * 同步的子公司名称根公司的值 + */ + String DICT_KEY_SYNC_ROOT_COMPANY_NAME = "root_company"; + + } diff --git a/blade-common/src/main/java/org/springblade/common/constant/DataStatusEnum.java b/blade-common/src/main/java/org/springblade/common/constant/DataStatusEnum.java new file mode 100644 index 0000000..f7c8adb --- /dev/null +++ b/blade-common/src/main/java/org/springblade/common/constant/DataStatusEnum.java @@ -0,0 +1,69 @@ +package org.springblade.common.constant; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 数据状态枚举 + * + * @author vic + * @date 2024/8/26 6:45 PM + * @description DataStatusEnum + */ +@Getter +@AllArgsConstructor +public enum DataStatusEnum { + + /** + * + */ + ENABLE(1, "启用"), + + DISABLE(2, "禁用"), + + + ; + + + private final Integer code; + + private final String name; + + + /** + * 匹配枚举值 + * + * @param code + * @return + */ + public static boolean match(Integer code) { + for (DataStatusEnum state : values()) { + if (state.code.equals(code)) { + return true; + } + } + return false; + } + + public static String getNameStr(Integer code) { + for (DataStatusEnum state : values()) { + if (state.code.equals(code)) { + return state.name; + } + } + return null; + + } + + + /** + * 是否可以编辑修改表单 .没有审批业务的使用此方法, 系统设计的是反的 + * true:可编辑 false:不可编辑 + * @param value + * @return + */ + public static boolean canEdit(Integer value) { + return DISABLE.getCode().equals(value); + } + +} diff --git a/blade-common/src/main/java/org/springblade/common/constant/DictTypeEnum.java b/blade-common/src/main/java/org/springblade/common/constant/DictTypeEnum.java new file mode 100644 index 0000000..78dd418 --- /dev/null +++ b/blade-common/src/main/java/org/springblade/common/constant/DictTypeEnum.java @@ -0,0 +1,171 @@ +package org.springblade.common.constant; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 系统字典类型枚举 + * 响功能和流程的字典 + * @author vic + * @date 2024/9/9 8:56 PM + * @description DictTypeEnum + */ +@Getter +@AllArgsConstructor +public enum DictTypeEnum { + /** + * + */ + DATA_STATUS("data_status", "数据状态"), + APPROVE_STATUS("approve_state", "通用审批状态"), + COOPERATE_STATUS("cooperate_status", "合作状态"), + CUSTOMER_TYPE("customer_type", "客户类型"), + DELIVERER_TYPE("deliverer_type", "投送人类型"), + ENTERPRISE_TYPE("enterprise_type", "企业类型"), + SETTLEMENT_TYPE("settlement_type", "结算中心类型"), + CUSTOMER_COMPANY_TYPE("company_type","客户管理的企业类型"), + PARTNER_TYPE("partner_type","合作伙伴类型"), + SYSTEM_YES_NO("yes_no","系统是否"), + PLATFORM_TYPE("settlement_type","平台类型/结算中心类型"), + TAXPAYER_TYPE("taxpayer_type","纳税人分类"), + //部门简称 + DEPARTMENT_SHORT_NAME("department_short_name","部门简称"), + PROCESS_TYPE("process_type","流程类型"), + + //评级维度 + RATING_DIMENSION("rating_dimension","评级维度"), + // oa公司部门同步 + SYNC_COMPANY_NAME("sync_company_name","同步公司名称"), + SYNC_ROOT_COMPANY_DEPT_NAME("sync_root_company_dept_name","同步根公司下部门名称"), + DEPARTMENT_CODE("department_code","部门编号"), + + //公式管理 + FORMULA_TYPE("formula_type","公式类型"), + + //财务管理 + FINANCIAL_TYPE("financial_type","财务类型"), + SAP_TYPE("sap_type","财务结算类型"), + SETTLEMENT_CLASSIFICATION("settlement_classification","财务结算分类"),//内部结算、外部结算 + SAP_DATA_STATUS("sap_data_status","财务结算单状态"), + SETTLEMENT_VOUCHER_TYPE("settlement_voucher_type","结算凭证类型"), + SETTLEMENT_NEXT_STATUS("settlement_next_status","下一步结算状态"), + + PAYMENT_TYPE("payment_type","财务付款分类"), + PAYMENT_DATA_STATUS("payment_data_status","财务付款单状态"), + PAYEMENT_CLASSIFICATION("payment_classification","财务付款分类"), //内部付款、外部付款 + PAYMENT_METHOD_TYPE("payment_method_type","财务付款支付方式"), + + SAP_VERIFICATION_METHOD("verification_method","验证方式"), + + /** 结算管理列表页的开票状态、付款状态 **/ + SETT_PAYMENT_STATUS("payment_status","付款状态"), + SETT_INVOICE_STATUS("invoice_status","开票状态"), + + + // 车辆运输凭证 + CERTIFICATE_BATCH_STATUS("certificate_batch_status","车辆运输凭证批次状态"), + CERTIFICATE_IDENTIFICATION_STATUS("certificate_identification_status","车辆运输凭证识别状态"), + CERTIFICATE_IDENTIFICATION_RESULT("certificate_identification_result","车辆运输凭证识别结果"), + + // 文件任务状态 + FILE_TASK_STATUS("file_task_status", "文件任务状态"), + // 运单管理-差异项 总净重 + WEIGHT_STATUS("weight_status", "总净重"), + // 运单管理-比对结果 差异类型 + DIFFERENCE_TYPE("difference_type", "差异类型"), + // 收款 + CLAIM_STATUS("claim_status", "认领状态"), + RECEIPT_SOURCE("receipt_source", "收款来源"), + CURRENCY("currency", "币种"), + + TRAJECTORY_STATUS("trajectory_status", "轨迹状态"), + + HOME_BASE_TYPE("base_type","基地类型"), + + FEE_NAME_TYPE("fee_name_type","费用类型"), + + TAX_CODE("taxCode","税收编码"), + INPUT_INVOICE_STATUS("input_invoice_status","录入开票状态"), + + /** + * 城矿结算公司财务负责人 + */ + SETTLEMENT_FINANCE_CONTROLLER("settlement_finance_controller", "城矿结算公司财务负责人"), + + /** + * 付款单据类型 + */ + PAYMENT_DOCUMENT_TYPE("payment_document_type", "付款单据类型"), + + /** + * 归档状态 + */ + ARCHIVE_STATUS("archive_status", "归档状态"), + + /** + * 预警类型 + */ + WARNING_TYPE("warning_type", "预警类型"), + + /** + * 消息状态 + */ + MESSAGE_STATUS("message_status", "消息状态"), + + /** + * 通知类型 + */ + NOTICE_TYPE("notice_type", "通知类型"), + + /** + * 通知对象类型 + */ + NOTICE_OBJECT_TYPE("notice_object_type", "通知对象类型"), + + /** + * 项目磅单校验类型 + */ + PROJECT_POUND_VALID_TYPE("project_pound_valid_type", "项目磅单校验类型"), + + /** + * 推送单据类型 + */ + PUSH_DOC_TYPE("push_doc_type", "推送单据类型"), + + /** + * mk流程状态 + */ + MK_STATUS("mk_status", "mk流程状态"), + + ; + + final String type; + + final String text; + + + /** + * 匹配枚举值 + * + * @param type + * @return + */ + public static boolean match(String type) { + for (DictTypeEnum state : values()) { + if (state.type.equals(type)) { + return true; + } + } + return false; + } + + public static String getNameStr(String type) { + for (DictTypeEnum state : values()) { + if (state.type.equals(type)) { + return state.text; + } + } + return null; + } + +} diff --git a/blade-common/src/main/java/org/springblade/common/utils/ObsUtil.java b/blade-common/src/main/java/org/springblade/common/utils/ObsUtil.java new file mode 100644 index 0000000..d5f7957 --- /dev/null +++ b/blade-common/src/main/java/org/springblade/common/utils/ObsUtil.java @@ -0,0 +1,275 @@ +package org.springblade.common.utils; + +import com.alibaba.cloud.commons.lang.StringUtils; +import com.obs.services.ObsClient; +import com.obs.services.exception.ObsException; +import com.obs.services.model.*; +import jakarta.activation.MimetypesFileTypeMap; +import lombok.SneakyThrows; +import org.apache.commons.io.FilenameUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; + +import java.io.*; +import java.net.URL; +import java.util.List; + +/** + * @ClassName: OBSUtil + * @Description: + * @Author: zhaowei + * @Date: 2024/8/22 + */ +public class ObsUtil { + private final static Logger logger = LoggerFactory.getLogger(ObsUtil.class); + + private static MimetypesFileTypeMap fileTypeMap = new MimetypesFileTypeMap(); + /** + * 华为云OBS + */ + public static String obsEndpoint; + public static String obsAccessKeyId; + public static String obsAccessKeySecret; + public static String obsBucketName; + public static String obsRootDirectory; + + @Value("${obs.endpoint}") + public void setObsEndpoint(String endpoint) { + ObsUtil.obsEndpoint = endpoint; + } + + @Value("${obs.access-key-id}") + public void setObsAccessKeyId(String accessKeyId) { + ObsUtil.obsAccessKeyId = accessKeyId; + } + + @Value("${obs.access-key-secret}") + public void setObsAccessKeySecret(String accessKeySecret) { + ObsUtil.obsAccessKeySecret = accessKeySecret; + } + + @Value("${obs.bucket-name}") + public void setObsBucketName(String bucketName) { + ObsUtil.obsBucketName = bucketName; + } + + @Value("${obs.root-directory}") + public void setObsRootDirectory(String rootDirectory) { + ObsUtil.obsRootDirectory = rootDirectory; + } + + + // 如果Bucket不存在,则创建它。 + private static void ensureBucket(ObsClient client, String bucketName) throws ObsException { + + if (client.headBucket(bucketName)) { + return; + } + // 创建bucket + client.createBucket(bucketName); + } + + // 把Bucket设置为所有人可读 + private static void setBucketPublicReadable(String bucketName) throws ObsException { + //华为云OBS + ObsClient obsClient = new ObsClient(obsAccessKeyId, obsAccessKeySecret, obsEndpoint); + if (obsClient.doesObjectExist(obsBucketName, obsRootDirectory)) { + + } else { + obsClient.createBucket(obsBucketName); + } + obsClient.setBucketAcl(obsBucketName, com.obs.services.model.AccessControlList.REST_CANNED_PUBLIC_READ_WRITE); + } + + /** + * 上传一个Object + * + * @param key + * @param content + * @throws FileNotFoundException + */ + public static String putObject(String key, InputStream content) throws IOException { + //华为云OBS + ObsClient obsClient = new ObsClient(obsAccessKeyId, obsAccessKeySecret, obsEndpoint); + try { + obsClient.putObject(obsBucketName, key, content); + } catch (Exception e) { + logger.error("obs put object error", e); + throw new RuntimeException("obs eror" + e.getMessage()); + } finally { + obsClient.close(); + } + return getExpireUrl(key); + } + + + /** + * 下载文件 + * @param objectKey 上传到OBS起的名 + * @param filename 文件下载到本地保存的路径 + * @throws ObsException + * @throws IOException + */ + public static void downloadFile(String objectKey, String filename) throws ObsException, IOException { + //华为云OBS + ObsClient obsClient = new ObsClient(obsAccessKeyId, obsAccessKeySecret, obsEndpoint); + try { + obsClient.getObject(obsBucketName, objectKey); + } finally { + obsClient.close(); + } + } + + /** + * 删除一个Bucket和其中的Objects + * + * @param bucketName Bucket名 + * @throws ObsException + */ + private static void deleteBucket(String bucketName) throws ObsException { + ObsClient obsClient = new ObsClient(obsAccessKeyId, obsAccessKeySecret, obsEndpoint); + com.obs.services.model.ObjectListing objectListing = obsClient.listObjects(bucketName); + List listObject = objectListing.getObjects(); + for (ObsObject o : listObject) { + obsClient.deleteObject(bucketName, o.getObjectKey()); + } + obsClient.deleteBucket(bucketName); + } + + /** + * 批量删除Object + * @param objList Object的key(目录路径/文件名) + */ + public static void delelteObjectList(List objList) { + ObsClient obsClient = new ObsClient(obsAccessKeyId, obsAccessKeySecret, obsEndpoint); + for (String key : objList) { + obsClient.deleteObject(obsBucketName, key); + } + } + + + /** + * 删除Object + * @param key 目录路径/文件名 + */ + public static void delelteObject(String key) { + ObsClient obsClient = new ObsClient(obsAccessKeyId, obsAccessKeySecret, obsEndpoint); + obsClient.deleteObject(obsBucketName, key); + } + + /** + * 通过key获取带签名的url + */ + public static String getExpireUrl(String key) { + String fileUrl = ""; + ObsClient obsClient = new ObsClient(obsAccessKeyId, obsAccessKeySecret, obsEndpoint); + // URL有效期,3600秒.5分钟 + // long expireSeconds = 3600L; + // 约等于1年 + long expireSeconds = 31536000L; + TemporarySignatureRequest request = new TemporarySignatureRequest(HttpMethodEnum.GET, expireSeconds); + request.setBucketName(obsBucketName); + request.setObjectKey(key); + TemporarySignatureResponse response = obsClient.createTemporarySignature(request); + fileUrl = response.getSignedUrl(); + return fileUrl; + } + + /** + * 上传文件至OSS并且返回链接地址 + * @param url 远程路径 + * @param ossDir OSS目录 + * @param fileName 文件名 + * @return 文件OSS链接 + * @modify + */ + public static String uploadFile2Oss(URL url, String ossDir, String fileName) { + InputStream is; + String key = ossDir + fileName; + try { + is = url.openStream(); + return uploadFile2OSS(is, key); + } catch (Exception ex) { + logger.error("上传失败!", ex); + } + return StringUtils.EMPTY; + } + + /** + * 上传到OSS服务器 如果同名文件会覆盖服务器上的 + * + * @param inputStream 文件流 + * @return 返回文件访问路径 + */ + @SneakyThrows + public static String uploadFile2OSS(InputStream inputStream, String key) { + ObjectMetadata objectMetadata = new ObjectMetadata(); + objectMetadata.setCacheControl("no-cache"); + String fileName = FilenameUtils.getName(key); + objectMetadata.setContentType(fileTypeMap.getContentType(fileName)); + objectMetadata.addUserMetadata("filename", fileName); + + ObsClient obsClient = new ObsClient(obsAccessKeyId, + obsAccessKeySecret, obsEndpoint); + try { + obsClient.putObject(obsBucketName, obsRootDirectory + key, inputStream, objectMetadata); + return obsRootDirectory + "/" + key; + } finally { + obsClient.close(); + } + } + + @SneakyThrows + public Boolean checkObjectExist(String key) { + Boolean existsFlag = false; + ObsClient obsClient = new ObsClient(obsAccessKeyId, obsAccessKeySecret, obsEndpoint); + try { + existsFlag = obsClient.doesObjectExist(obsBucketName, key); + } finally { + obsClient.close(); + } + return existsFlag; + } + + + /** + * 下载文件 + * @param objectKey 上传到OBS起的名 + * @param filename 文件下载到本地保存的路径 + * @throws ObsException + * @throws IOException + */ + public static void downloadFileNew(String objectKey, String filename){ + //华为云OBS + ObsClient obsClient = new ObsClient(obsAccessKeyId, obsAccessKeySecret, obsEndpoint); + try { + ObsObject ossObject = obsClient.getObject(obsBucketName, objectKey); + + OutputStream outputStream = null; + try { + outputStream = new BufferedOutputStream(new FileOutputStream(filename)); + byte[] buffer = new byte[8192]; + int bytesRead; + while ((bytesRead = ossObject.getObjectContent().read(buffer)) != -1) { + outputStream.write(buffer, 0, bytesRead); + } + } catch (IOException ex) { + } finally { + if (outputStream != null) { + try { + outputStream.close(); + } catch (IOException e) { + } + } + } + } finally { + if (obsClient != null) { + try { + obsClient.close(); + } catch (IOException e) { + } + } + } + } +} diff --git a/blade-gateway/src/main/java/org/springblade/gateway/provider/AuthProvider.java b/blade-gateway/src/main/java/org/springblade/gateway/provider/AuthProvider.java index 964273a..5bc679d 100644 --- a/blade-gateway/src/main/java/org/springblade/gateway/provider/AuthProvider.java +++ b/blade-gateway/src/main/java/org/springblade/gateway/provider/AuthProvider.java @@ -52,9 +52,6 @@ public class AuthProvider { DEFAULT_SKIP_URL.add("/oauth/callback/**"); DEFAULT_SKIP_URL.add("/oauth/revoke/**"); DEFAULT_SKIP_URL.add("/oauth/refresh/**"); - DEFAULT_SKIP_URL.add("/iam/sso/token/**"); - DEFAULT_SKIP_URL.add("/blade-auth/iam/sso/token/**"); - DEFAULT_SKIP_URL.add("/api/blade-auth/iam/sso/token/**"); DEFAULT_SKIP_URL.add("/token/**"); DEFAULT_SKIP_URL.add("/actuator/**"); DEFAULT_SKIP_URL.add("/v3/api-docs/**"); diff --git a/blade-service-api/blade-file-api/pom.xml b/blade-service-api/blade-file-api/pom.xml new file mode 100644 index 0000000..d85da74 --- /dev/null +++ b/blade-service-api/blade-file-api/pom.xml @@ -0,0 +1,22 @@ + + + + org.springblade + blade-service-api + ${revision} + + 4.0.0 + + blade-file-api + ${project.artifactId} + jar + + + + org.springblade + blade-ocr-api + + + \ No newline at end of file diff --git a/blade-service-api/blade-file-api/src/main/java/org/springblade/file/callback/UnzipCallback.java b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/callback/UnzipCallback.java new file mode 100644 index 0000000..7135272 --- /dev/null +++ b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/callback/UnzipCallback.java @@ -0,0 +1,22 @@ +package org.springblade.file.callback; + +/** + * 解压回调接口 + * @author bfhuange + * @date 2024/11/8 + */ +public interface UnzipCallback { + /** + * 解压任务的业务编码 + * @return + */ + String getBizCode(); + + /** + * 回调接口 + * @param bizId 业务id + * @param event 事件 org.springblade.file.callback.UnzipEventType name + * @return + */ + boolean callback(Long bizId, String event); +} diff --git a/blade-service-api/blade-file-api/src/main/java/org/springblade/file/callback/UnzipEventType.java b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/callback/UnzipEventType.java new file mode 100644 index 0000000..3df651d --- /dev/null +++ b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/callback/UnzipEventType.java @@ -0,0 +1,22 @@ +package org.springblade.file.callback; + +/** + * 解压事件类型 + * @author bfhuange + * @date 2024/11/15 + */ +public enum UnzipEventType { + /** + * 开始解压 + */ + START, + /** + * 解压成功 + */ + SUCCESS, + /** + * 解压失败 + */ + FAIL, + ; +} diff --git a/blade-service-api/blade-file-api/src/main/java/org/springblade/file/config/FeignFileUploadClientConfig.java b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/config/FeignFileUploadClientConfig.java new file mode 100644 index 0000000..b23ff27 --- /dev/null +++ b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/config/FeignFileUploadClientConfig.java @@ -0,0 +1,20 @@ +package org.springblade.file.config; + +import feign.codec.Encoder; +import feign.form.spring.SpringFormEncoder; +import org.springframework.beans.factory.ObjectFactory; +import org.springframework.boot.autoconfigure.http.HttpMessageConverters; +import org.springframework.cloud.openfeign.support.SpringEncoder; +import org.springframework.context.annotation.Bean; + +/** + * @author bfhuange + * @since 2025/4/9 + */ +public class FeignFileUploadClientConfig { + + @Bean + public Encoder feignFormEncoder(ObjectFactory messageConverters) { + return new SpringFormEncoder(new SpringEncoder(messageConverters)); + } +} diff --git a/blade-service-api/blade-file-api/src/main/java/org/springblade/file/feign/IFileClient.java b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/feign/IFileClient.java new file mode 100644 index 0000000..8dc9747 --- /dev/null +++ b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/feign/IFileClient.java @@ -0,0 +1,188 @@ +package org.springblade.file.feign; + +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import org.springblade.core.tool.api.R; +import org.springblade.file.pojo.dto.*; +import org.springblade.file.pojo.entity.Attachment; +import org.springblade.file.pojo.vo.*; +import org.springblade.thirdparty.ocr.pojo.vo.TransportCertificateVO; +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.cloud.openfeign.SpringQueryMap; +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 java.util.List; + +/** + * @author bfhuange + * @date 2024/10/21 + */ +@FeignClient( + value = "blade-file", + fallback = IFileClientFallback.class +) +public interface IFileClient { + + String API_PREFIX = "/feign/client/file"; + String ATTACHMENT = API_PREFIX + "/attachment"; + String ATTACHMENT_DETAIL = API_PREFIX + "/attachment-detail"; + String BUSINESS_ATTACHMENT_SAVE_REMOVE = API_PREFIX + "/businessAttachment-save-remove"; + String BUSINESS_ATTACHMENT_BATCH_SAVE_REMOVE = API_PREFIX + "/businessAttachment-batch-save-remove"; + String BUSINESS_ATTACHMENT_BATCH_SAVE_REMOVE_BY_PARAM = API_PREFIX + "/businessAttachment-batch-save-remove-by-param"; + String BUSINESS_ATTACHMENT_BATCH_SAVE_REMOVE_NO_BIZ_ID = API_PREFIX + "/businessAttachment-batch-save-remove-no-bizId"; + String BUSINESS_ATTACHMENT_REMOVE = API_PREFIX + "/businessAttachment-remove"; + String BUSINESS_ATTACHMENT_LIST = API_PREFIX + "/businessAttachment-list"; + String BUSINESS_UNZIP_TASK_SAVE = API_PREFIX + "/businessUnzipTask-save"; + String BUSINESS_UNZIP_TASK_UPDATE = API_PREFIX + "/businessUnzipTask-update"; + String BUSINESS_UNZIP_TASK_CANCEL = API_PREFIX + "/businessUnzipTask-cancel"; + String BUSINESS_UNZIP_TASK_REMOVE = API_PREFIX + "/businessUnzipTask-remove"; + String BUSINESS_UNZIP_TASK_LIST = API_PREFIX + "/businessUnzipTask-list"; + String BUSINESS_UNZIP_DETAIL_LIST = API_PREFIX + "/businessUnzipDetail-list"; + String RECOGNITION_TRANSPORT_CERTIFICATE = API_PREFIX + "/recognitionTransportCertificate"; + String FILE_URLS = API_PREFIX + "/getFileUrls"; + + /** + * 获取附件,多个附件id用逗号分割 + * + * @param id + * @return List + */ + @GetMapping(ATTACHMENT) + R> getAttachment(@RequestParam("id") String id); + + /** + * 获取附件详情 + * + * @param id + * @return List + */ + @GetMapping(ATTACHMENT_DETAIL) + R getAttachmentDetail(@RequestParam("id") Long id); + + /** + * 保存并删除业务附件,remove为true,先用bizId删除,再新增 + * + * @param param + * @return + */ + @PostMapping(BUSINESS_ATTACHMENT_SAVE_REMOVE) + R saveAndRemoveBusinessAttachment(@RequestBody @Validated BusinessAttachmentBatchAddDTO param); + + /** + * 批量保存并删除业务附件,先删除附件,再新增,业务id为空,从params中获取bizId,先删除 + * + * @param params 参数为空不新增 + * @param bizId 业务id + * @return + */ + @PostMapping(BUSINESS_ATTACHMENT_BATCH_SAVE_REMOVE) + R batchSaveAndRemoveBusinessAttachment(@RequestBody List<@Valid BusinessAttachmentAddDTO> params, @RequestParam(value = "bizId", required = false) Long bizId); + + /** + * 批量保存并删除业务附件,deleteParam为空,从params获取bizId删除,不为空,使用bizIds和bizCodes删除附件,bizCodes为空删除所有bizIds的附件 + * deleteParam 中 delete 为false,不删除 + * + * @param params 参数为空不新增 + * @param deleteParam 删除参数,支持只删除业务编码参数的附件 + * @return + */ + @PostMapping(BUSINESS_ATTACHMENT_BATCH_SAVE_REMOVE_BY_PARAM) + R batchSaveAndRemoveByParamBusinessAttachment(@RequestBody List<@Valid BusinessAttachmentAddDTO> params, @Valid @SpringQueryMap BusinessAttachmentDeleteDTO deleteParam); + + /** + * 批量保存并删除业务附件,从参数中提取所有bizId + * + * @param params 参数 + * @return + */ + @PostMapping(BUSINESS_ATTACHMENT_BATCH_SAVE_REMOVE_NO_BIZ_ID) + R batchSaveAndRemoveBusinessAttachment(@RequestBody List<@Valid BusinessAttachmentAddDTO> params); + + /** + * 根据业务id删除业务附件 + * + * @param bizId + * @return + */ + @PostMapping(BUSINESS_ATTACHMENT_REMOVE) + R removeBusinessAttachment(@NotNull(message = "业务id不能为空") @RequestParam(value = "bizId", required = false) Long bizId); + + /** + * 查询业务附件列表 + * + * @param param + * @return + */ + @PostMapping(BUSINESS_ATTACHMENT_LIST) + R> getBusinessAttachments(@Validated @RequestBody BusinessAttachmentQueryDTO param); + + /** + * 保存业务解压任务 + * @param param + * @return + */ + @PostMapping(BUSINESS_UNZIP_TASK_SAVE) + R saveBusinessUnzipTask(@Validated @RequestBody BusinessUnzipTaskAddDTO param); + + /** + * 更新业务解压任务(重新上传) + * @param param + * @return + */ + @PostMapping(BUSINESS_UNZIP_TASK_UPDATE) + R updateBusinessUnzipTask(@Validated @RequestBody BusinessUnzipTaskUpdateDTO param); + + /** + * 取消业务解压任务 + * @param bizId + * @return + */ + @PostMapping(BUSINESS_UNZIP_TASK_CANCEL) + R cancelBusinessUnzipTask(@NotNull(message = "业务id不能为空") @RequestParam("bizId") Long bizId); + + /** + * 删除业务解压任务 + * @param bizId + * @return + */ + @PostMapping(BUSINESS_UNZIP_TASK_REMOVE) + R removeBusinessUnzipTask(@NotNull(message = "业务id不能为空") @RequestParam("bizId") Long bizId); + + /** + * 根据业务id列表查询业务解压任务列表 + * @param bizIds + * @return + */ + @PostMapping(BUSINESS_UNZIP_TASK_LIST) + R> getBusinessUnzipTasksByBizIds(@Validated @NotEmpty(message = "业务id列表不能为空") @RequestBody List bizIds); + + /** + * 查询解压明细列表 + * @param bizId + * @return + */ + @GetMapping(BUSINESS_UNZIP_DETAIL_LIST) + R> queryBusinessUnzipDetail(@NotNull(message = "业务id不能为空") @RequestParam("bizId") Long bizId); + + /** + * 识别车辆运输凭证 + * + * @param param 同一车牌文件夹下的附件objectKey列表 + * @return + */ + @PostMapping(RECOGNITION_TRANSPORT_CERTIFICATE) + R recognitionTransportCertificates(@Validated @RequestBody FileCertificateBatchRecognitionDTO param); + + /** + * 批量获取附件下载地址 + * @param param + * @return + */ + @PostMapping(FILE_URLS) + R> getFileUrls(@Validated @RequestBody IdsDTO param); +} diff --git a/blade-service-api/blade-file-api/src/main/java/org/springblade/file/feign/IFileClientFallback.java b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/feign/IFileClientFallback.java new file mode 100644 index 0000000..8e33ae8 --- /dev/null +++ b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/feign/IFileClientFallback.java @@ -0,0 +1,113 @@ +package org.springblade.file.feign; + +import jakarta.validation.Valid; +import org.springblade.core.tool.api.R; +import org.springblade.file.pojo.dto.*; +import org.springblade.file.pojo.entity.Attachment; +import org.springblade.file.pojo.vo.*; +import org.springblade.thirdparty.ocr.pojo.vo.TransportCertificateVO; +import org.springframework.cloud.openfeign.SpringQueryMap; +import org.springframework.stereotype.Component; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.multipart.MultipartFile; + +import java.util.List; + +/** + * @author bfhuange + * @date 2024/10/21 + */ +@Component +public class IFileClientFallback implements IFileClient, IFileUploadClient { + + @Override + public R> getAttachment(String id) { + return R.fail("获取数据失败"); + } + + @Override + public R getAttachmentDetail(Long id) { + return R.fail("获取数据失败"); + } + + @Override + public R saveAndRemoveBusinessAttachment(BusinessAttachmentBatchAddDTO param) { + return R.fail("获取数据失败"); + } + + @Override + public R batchSaveAndRemoveBusinessAttachment(List params, Long bizId) { + return R.fail("获取数据失败"); + } + + @Override + public R batchSaveAndRemoveByParamBusinessAttachment(List<@Valid BusinessAttachmentAddDTO> params, @Valid @SpringQueryMap BusinessAttachmentDeleteDTO deleteParam) { + return R.fail("获取数据失败"); + } + + @Override + public R batchSaveAndRemoveBusinessAttachment(List<@Valid BusinessAttachmentAddDTO> params) { + return R.fail("获取数据失败"); + } + + @Override + public R removeBusinessAttachment(Long bizId) { + return R.fail("获取数据失败"); + } + + @Override + public R> getBusinessAttachments(BusinessAttachmentQueryDTO param) { + return R.fail("获取数据失败"); + } + + @Override + public R saveBusinessUnzipTask(BusinessUnzipTaskAddDTO param) { + return R.fail("获取数据失败"); + } + + @Override + public R updateBusinessUnzipTask(BusinessUnzipTaskUpdateDTO param) { + return R.fail("获取数据失败"); + } + + @Override + public R cancelBusinessUnzipTask(Long bizId) { + return R.fail("获取数据失败"); + } + + @Override + public R removeBusinessUnzipTask(Long bizId) { + return R.fail("获取数据失败"); + } + + @Override + public R> getBusinessUnzipTasksByBizIds(List bizIds) { + return R.fail("获取数据失败"); + } + + @Override + public R> queryBusinessUnzipDetail(Long bizId) { + return R.fail("获取数据失败"); + } + + @Override + public R recognitionTransportCertificates(@Validated @RequestBody FileCertificateBatchRecognitionDTO param) { + return R.fail("获取数据失败"); + } + + @Override + public R> getFileUrls(IdsDTO param) { + return R.fail("获取数据失败"); + } + + @Override + public R> ossUpload(MultipartFile[] files) { + return R.fail("获取数据失败"); + } + + @Override + public R ossUploadTemp(MultipartFile file, Integer expires) { + return R.fail("获取数据失败"); + } +} diff --git a/blade-service-api/blade-file-api/src/main/java/org/springblade/file/feign/IFileUploadClient.java b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/feign/IFileUploadClient.java new file mode 100644 index 0000000..2d04b74 --- /dev/null +++ b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/feign/IFileUploadClient.java @@ -0,0 +1,48 @@ +package org.springblade.file.feign; + +import jakarta.validation.constraints.NotNull; +import org.springblade.core.tool.api.R; +import org.springblade.file.config.FeignFileUploadClientConfig; +import org.springblade.file.pojo.entity.Attachment; +import org.springblade.file.pojo.vo.TempFileUrlVO; +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RequestPart; +import org.springframework.web.multipart.MultipartFile; + +import java.util.List; + +/** + * @author bfhuange + * @date 2024/10/21 + */ +@FeignClient( + value = "blade-file", + fallback = IFileClientFallback.class, + configuration = FeignFileUploadClientConfig.class +) +public interface IFileUploadClient { + + String API_PREFIX = "/feign/client/file"; + String UPLOAD = API_PREFIX + "/ossUpload"; + String UPLOAD_TEMP = API_PREFIX + "/ossUploadTemp"; + + /** + * 文件上传 + * @param files + * @return + */ + @PostMapping(value = UPLOAD, consumes = MediaType.MULTIPART_FORM_DATA_VALUE) + R> ossUpload(@RequestPart("files") MultipartFile[] files); + + /** + * 上传临时文件,不保存到附件表,过期后自动删除 + * @param file + * @param expires 过期时间,单位天,过期后自动删除 + * @return + */ + @PostMapping(value = UPLOAD_TEMP, consumes = MediaType.MULTIPART_FORM_DATA_VALUE) + R ossUploadTemp(@RequestPart("file") MultipartFile file, @NotNull(message = "过期时间不能为空") @RequestParam(name = "expires", required = false) Integer expires); +} diff --git a/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/dto/BusinessAttachmentAddDTO.java b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/dto/BusinessAttachmentAddDTO.java new file mode 100644 index 0000000..6870a20 --- /dev/null +++ b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/dto/BusinessAttachmentAddDTO.java @@ -0,0 +1,53 @@ +package org.springblade.file.pojo.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Builder; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 业务附件信息表 数据传输对象实体类 + * + * @author BladeX + * @since 2024-09-04 + */ +@Data +@Schema(description = "业务附件新增参数") +@Builder +public class BusinessAttachmentAddDTO implements Serializable { + @Serial + private static final long serialVersionUID = 1L; + + /** + * 业务id + */ + @NotNull(message = "业务id不能为空") + @Schema(description = "业务id") + private Long bizId; + /** + * 业务code + */ + @NotBlank(message = "业务code不能为空") + @Schema(description = "业务code") + private String bizCode; + /** + * 附件id + */ + @NotNull(message = "附件id不能为空") + @Schema(description = "附件id") + private Long attachmentId; + /** + * 附件描述 + */ + @Schema(description = "附件描述") + private String desc; + /** + * 排序 + */ + @Schema(description = "排序") + private Integer sort; +} diff --git a/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/dto/BusinessAttachmentBatchAddDTO.java b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/dto/BusinessAttachmentBatchAddDTO.java new file mode 100644 index 0000000..6ae7ce0 --- /dev/null +++ b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/dto/BusinessAttachmentBatchAddDTO.java @@ -0,0 +1,73 @@ +package org.springblade.file.pojo.dto; + +import com.google.common.collect.Lists; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotNull; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serial; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +/** + * 业务附件信息表 数据传输对象实体类 + * + * @author BladeX + * @since 2024-09-04 + */ +@NoArgsConstructor +@AllArgsConstructor +@Data +@Schema(description = "业务附件批量新增参数") +public class BusinessAttachmentBatchAddDTO implements Serializable { + @Serial + private static final long serialVersionUID = 1L; + + /** + * 业务id + */ + @NotNull(message = "业务id不能为空") + @Schema(description = "业务id") + private Long bizId; + + /** + * 明细列表 + */ + @Schema(description = "明细列表") + private List<@Valid BusinessAttachmentBatchAddDetailDTO> details; + + /** + * 是否先删除,默认 是 + */ + @Schema(description = "是否先删除,默认 是") + private boolean remove = true; + + public BusinessAttachmentBatchAddDTO(Long bizId) { + this.bizId = bizId; + } + + public BusinessAttachmentBatchAddDTO(Long bizId, String bizCode, Long attachmentId) { + this.bizId = bizId; + this.details = Lists.newArrayList(new BusinessAttachmentBatchAddDetailDTO(bizCode, attachmentId)); + } + + public BusinessAttachmentBatchAddDTO addDetail(BusinessAttachmentBatchAddDetailDTO detail) { + if (this.details == null) { + this.details = new ArrayList<>(); + } + this.details.add(detail); + return this; + } + + public BusinessAttachmentBatchAddDTO addDetail(String bizCode, Long attachmentId) { + return this.addDetail(new BusinessAttachmentBatchAddDetailDTO(bizCode, attachmentId)); + } + + public BusinessAttachmentBatchAddDTO addDetail(String bizCode, List attachmentIds) { + return this.addDetail(new BusinessAttachmentBatchAddDetailDTO(bizCode, attachmentIds)); + } +} diff --git a/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/dto/BusinessAttachmentBatchAddDetailDTO.java b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/dto/BusinessAttachmentBatchAddDetailDTO.java new file mode 100644 index 0000000..ac5d3a7 --- /dev/null +++ b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/dto/BusinessAttachmentBatchAddDetailDTO.java @@ -0,0 +1,48 @@ +package org.springblade.file.pojo.dto; + +import com.google.common.collect.Lists; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serial; +import java.io.Serializable; +import java.util.List; + +/** + * 业务附件信息表 数据传输对象实体类 + * + * @author BladeX + * @since 2024-09-04 + */ +@NoArgsConstructor +@AllArgsConstructor +@Data +@Schema(description = "业务附件批量新增明细参数") +@Builder +public class BusinessAttachmentBatchAddDetailDTO implements Serializable { + @Serial + private static final long serialVersionUID = 1L; + /** + * 业务code + */ + @NotBlank(message = "业务编码不能为空") + @Schema(description = "业务code") + private String bizCode; + /** + * 附件id列表 + */ + @NotEmpty(message = "附件id列表不能为空") + @Schema(description = "附件id列表") + private List<@NotNull(message = "附件id不能为空") Long> attachmentIds; + + public BusinessAttachmentBatchAddDetailDTO(String bizCode, Long attachmentId) { + this.bizCode = bizCode; + this.attachmentIds = Lists.newArrayList(attachmentId); + } +} diff --git a/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/dto/BusinessAttachmentDeleteDTO.java b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/dto/BusinessAttachmentDeleteDTO.java new file mode 100644 index 0000000..d4ea148 --- /dev/null +++ b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/dto/BusinessAttachmentDeleteDTO.java @@ -0,0 +1,54 @@ +package org.springblade.file.pojo.dto; + +import com.google.common.collect.Lists; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.springblade.core.tool.utils.StringUtil; + +import java.io.Serial; +import java.io.Serializable; +import java.util.List; + +/** + * 业务附件删除参数 + * @author bfhuange + * @since 2024/12/3 + */ +@NoArgsConstructor +@AllArgsConstructor +@Data +@Schema(description = "业务附件删除参数") +public class BusinessAttachmentDeleteDTO implements Serializable { + @Serial + private static final long serialVersionUID = 1L; + /** + * 业务id列表 + */ + @Schema(description = "业务id列表") + private List bizIds; + /** + * 业务code列表 + */ + @Schema(description = "业务code列表") + private List bizCodes; + /** + * 是否删除 + */ + @Schema(description = "是否删除") + private boolean delete = true; + + public BusinessAttachmentDeleteDTO(Long bizId) { + this.bizIds = Lists.newArrayList(bizId); + } + + public BusinessAttachmentDeleteDTO(Long bizId, String bizCode) { + this.bizIds = Lists.newArrayList(bizId); + if (StringUtil.isNotBlank(bizCode)) { + this.bizCodes = Lists.newArrayList(bizCode); + } + } + +} diff --git a/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/dto/BusinessAttachmentQueryDTO.java b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/dto/BusinessAttachmentQueryDTO.java new file mode 100644 index 0000000..6bd17d0 --- /dev/null +++ b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/dto/BusinessAttachmentQueryDTO.java @@ -0,0 +1,62 @@ +package org.springblade.file.pojo.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serial; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +/** + * 业务附件信息表 数据传输对象实体类 + * + * @author BladeX + * @since 2024-09-04 + */ +@AllArgsConstructor +@NoArgsConstructor +@Data +@Schema(description = "业务附件查询参数") +public class BusinessAttachmentQueryDTO implements Serializable { + @Serial + private static final long serialVersionUID = 1L; + /** + * 业务id列表 + */ + @NotNull(message = "业务id列表不能为空") + @Schema(description = "业务id列表") + private List<@NotNull(message = "业务id不能为空") Long> bizIds; + /** + * 业务code + */ + @Schema(description = "业务code") + private String bizCode; + /** + * 业务code列表 + */ + @Schema(description = "业务code列表") + private List bizCodes; + + public BusinessAttachmentQueryDTO(Long bizId) { + this.bizIds = List.of(bizId); + } + + public BusinessAttachmentQueryDTO(List bizIds) { + this.bizIds = bizIds; + } + + /** + * 兼容历史参数 + * @param bizId + */ + public void setBizId(Long bizId) { + if (this.bizIds == null) { + this.bizIds = new ArrayList<>(); + } + this.bizIds.add(bizId); + } +} diff --git a/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/dto/BusinessUnzipTaskAddDTO.java b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/dto/BusinessUnzipTaskAddDTO.java new file mode 100644 index 0000000..913eafa --- /dev/null +++ b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/dto/BusinessUnzipTaskAddDTO.java @@ -0,0 +1,55 @@ +package org.springblade.file.pojo.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import org.springblade.file.pojo.enums.UnzipTask; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 业务解压任务新增参数 + * @author bfhuange + * @date 2024/10/28 + */ +@Data +@Schema(description = "业务解压任务新增参数") +public class BusinessUnzipTaskAddDTO implements Serializable { + @Serial + private static final long serialVersionUID = 1L; + /** + * 业务id + */ + @NotNull(message = "业务id不能为空") + @Schema(description = "业务id") + private Long bizId; + /** + * 部门名称ID + */ + @Schema(description = "部门名称ID") + private Long departmentId; + /** + * 业务code + */ + @NotBlank(message = "业务code不能为空") + @Schema(description = "业务code") + private String bizCode = UnzipTask.BizCode.WAYBILL.getCode(); + /** + * 文档编号 + */ + @Schema(description = "文档编号") + private String docCode; + /** + * 关联类型,file_task 文件任务,attachment 附件 + */ + @Schema(description = "关联类型,file_task 文件任务,attachment 附件") + private String relateType = UnzipTask.RelateType.FILE_TASK.getCode(); + /** + * 关联id,文件任务id或附件id + */ + @NotNull(message = "关联id不能为空") + @Schema(description = "关联id,文件任务id或附件id") + private Long relateId; +} diff --git a/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/dto/BusinessUnzipTaskQueryDTO.java b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/dto/BusinessUnzipTaskQueryDTO.java new file mode 100644 index 0000000..9b66c8c --- /dev/null +++ b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/dto/BusinessUnzipTaskQueryDTO.java @@ -0,0 +1,55 @@ +package org.springblade.file.pojo.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; +import java.util.List; + +/** + * 业务解压任务查询参数 + * @author bfhuange + * @date 2024/10/28 + */ +@Data +@Schema(description = "业务解压任务查询参数") +public class BusinessUnzipTaskQueryDTO implements Serializable { + @Serial + private static final long serialVersionUID = 1L; + /** + * 文档编号(凭证批次号) + */ + @Schema(description = "文档编号(凭证批次号)") + private String docCode; + /** + * 附件名称 + */ + @Schema(description = "附件名称") + private String attachmentName; + /** + * 状态 uploading:正在上传 completed:上传完成 failed:上传失败 paused:已暂停 + */ + @Schema(description = "状态 uploading:正在上传 completed:上传完成 failed:上传失败 paused:已暂停") + private String status; + /** + * 关联类型,file_task 文件任务,attachment 附件 + */ + @Schema(description = "关联类型,file_task 文件任务,attachment 附件", hidden = true) + private String relateType; + /** + * 是否只查询当前用户 + */ + @Schema(description = "是否只查询当前用户") + private boolean currentUser; + /** + * 当前登录人id + */ + @Schema(hidden = true) + private Long currentUserId; + /** + * 业务id列表 + */ + @Schema(description = "业务id列表", hidden = true) + private List bizIds; +} diff --git a/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/dto/BusinessUnzipTaskUpdateDTO.java b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/dto/BusinessUnzipTaskUpdateDTO.java new file mode 100644 index 0000000..e838efc --- /dev/null +++ b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/dto/BusinessUnzipTaskUpdateDTO.java @@ -0,0 +1,42 @@ +package org.springblade.file.pojo.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import org.springblade.file.pojo.enums.UnzipTask; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 业务解压任务更新参数 + * @author bfhuange + * @date 2024/10/28 + */ +@Data +@Schema(description = "业务解压任务更新参数") +public class BusinessUnzipTaskUpdateDTO implements Serializable { + @Serial + private static final long serialVersionUID = 1L; + /** + * 主键 + */ + @Schema(description = "主键") + private Long id; + /** + * 业务id + */ + @Schema(description = "业务id") + private Long bizId; + /** + * 关联id,文件任务id或附件id + */ + @NotNull(message = "关联id不能为空") + @Schema(description = "关联id,文件任务id或附件id") + private Long relateId; + /** + * 关联类型,file_task 文件任务,attachment 附件 + */ + @Schema(description = "关联类型,file_task 文件任务,attachment 附件", hidden = true) + private String relateType = UnzipTask.RelateType.FILE_TASK.getCode(); +} diff --git a/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/dto/CertificateAttachmentDTO.java b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/dto/CertificateAttachmentDTO.java new file mode 100644 index 0000000..0e0738a --- /dev/null +++ b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/dto/CertificateAttachmentDTO.java @@ -0,0 +1,44 @@ +package org.springblade.file.pojo.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 凭证识别参数,依次调用单张识别 + * @author bfhuange + * @since 2024/12/13 + */ +@Data +@Schema(description = "凭证附件参数") +public class CertificateAttachmentDTO implements Serializable { + @Serial + private static final long serialVersionUID = 1L; + /** + * 磅单ObjectKey + */ + @Schema(description = "磅单ObjectKey") + private String poundObjectKey; + /** + * 车头ObjectKey + */ + @Schema(description = "车头ObjectKey") + private String carFrontObjectKey; + /** + * 驾驶证ObjectKey + */ + @Schema(description = "驾驶证ObjectKey") + private String driverLicenseObjectKey; + /** + * 行驶证ObjectKey + */ + @Schema(description = "行驶证ObjectKey") + private String vehicleLicenseObjectKey; + /** + * 项目简称 + */ + @Schema(description = "项目简称") + private String projectAbbreviation; +} diff --git a/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/dto/FileCertificateBatchRecognitionDTO.java b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/dto/FileCertificateBatchRecognitionDTO.java new file mode 100644 index 0000000..92c6a0c --- /dev/null +++ b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/dto/FileCertificateBatchRecognitionDTO.java @@ -0,0 +1,45 @@ +package org.springblade.file.pojo.dto; + +import jakarta.validation.constraints.NotEmpty; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serial; +import java.io.Serializable; +import java.util.List; + +/** + * 凭证批量识别参数,所有图像链接统一识别,不分类 + * @author bfhuange + * @since 2025/3/13 + */ +@AllArgsConstructor +@NoArgsConstructor +@Data +public class FileCertificateBatchRecognitionDTO implements Serializable { + @Serial + private static final long serialVersionUID = 1L; + /** + * 所有图像 obs key列表 + */ + @NotEmpty(message = "图像 obs key列表不能为空") + private List objectKeys; + /** + * 项目简称 + */ + private String projectAbbreviation; + /** + * 凭证编码 + */ + private String code; + /** + * 目录名称 + */ + private String dirName; + + public FileCertificateBatchRecognitionDTO(List objectKeys) { + this.objectKeys = objectKeys; + } + +} diff --git a/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/dto/FileTaskCreateDTO.java b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/dto/FileTaskCreateDTO.java new file mode 100644 index 0000000..4b3acf0 --- /dev/null +++ b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/dto/FileTaskCreateDTO.java @@ -0,0 +1,62 @@ +package org.springblade.file.pojo.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 文件任务创建对象 + * @author bfhuange + * @date 2024/10/22 + */ +@Data +@Schema(description = "文件任务创建对象") +public class FileTaskCreateDTO implements Serializable { + @Serial + private static final long serialVersionUID = 1L; + + /** + * Md5 + */ + @NotBlank(message = "Md5不能为空") + @Schema(description = "Md5") + private String md5; + + /** + * 附件名 + */ + @NotBlank(message = "附件名不能为空") + @Schema(description = "附件名") + private String attachmentName; + + /** + * 附件大小(单位:B) + */ + @NotNull(message = "附件大小不能为空") + @Schema(description = "附件大小(单位:B)") + private Long size; + + /** + * 分段大小 + */ + @NotNull(message = "分段大小不能为空") + @Schema(description = "分段大小") + private Integer chunkSize; + + /** + * 分段数量 + */ + @NotNull(message = "分段数量不能为空") + @Schema(description = "分段数量") + private Integer chunkTotal; + + /** + * 是否强制创建新任务,默认 false,如果存在相同md5的任务,会创建失败,true则新创建一个任务 + */ + @Schema(description = "是否强制创建新任务,默认 false,如果存在相同md5的任务,会创建失败,true则新创建一个任务") + private boolean force; +} diff --git a/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/dto/FileTaskUpdateDTO.java b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/dto/FileTaskUpdateDTO.java new file mode 100644 index 0000000..9a6d6bf --- /dev/null +++ b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/dto/FileTaskUpdateDTO.java @@ -0,0 +1,41 @@ +package org.springblade.file.pojo.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import org.springblade.file.pojo.vo.PartEtagVO; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 文件任务更新对象 + * @author bfhuange + * @date 2024/10/22 + */ +@Data +@Schema(description = "文件任务更新对象") +public class FileTaskUpdateDTO implements Serializable { + @Serial + private static final long serialVersionUID = 1L; + + /** + * 文件任务id + */ + @NotNull(message = "文件任务id不能为空") + @Schema(description = "主键id") + private Long id; + /** + * 段的ETag值。分段的Base64编码的128位MD5摘要,长度为32的字符串 + */ + @NotBlank(message = "etag不能为空") + @Schema(description = "etag") + private String etag; + /** + * 段号。分段号可以是不连续的。取值范围是[1,10000]的非负整数 + */ + @NotNull(message = "段号不能为空") + @Schema(description = "段号") + private Integer partNumber; +} diff --git a/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/dto/FileUploadDTO.java b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/dto/FileUploadDTO.java new file mode 100644 index 0000000..a53e06c --- /dev/null +++ b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/dto/FileUploadDTO.java @@ -0,0 +1,15 @@ +package org.springblade.file.pojo.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import org.springframework.web.multipart.MultipartFile; + +@Data +@Schema(description = "分段上传文件对象") +public class FileUploadDTO { + private MultipartFile multipartFile; // 文件对象 + private String md5; // 文件md5值 + private Long chunkTotal; // 分段总数 + private Long chunkSize; // 分段大小 + private Integer index; // 当前分段的位置 +} \ No newline at end of file diff --git a/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/dto/IdsDTO.java b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/dto/IdsDTO.java new file mode 100644 index 0000000..7119e61 --- /dev/null +++ b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/dto/IdsDTO.java @@ -0,0 +1,33 @@ +package org.springblade.file.pojo.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serial; +import java.io.Serializable; +import java.util.List; + +/** + * id批量参数 + * @author bfhuange + * @date 2024/10/29 + */ +@AllArgsConstructor +@NoArgsConstructor +@Data +@Schema(description = "id批量参数") +public class IdsDTO implements Serializable { + @Serial + private static final long serialVersionUID = 1L; + /** + * id列表 + */ + @NotEmpty(message = "id列表不能为空") + @Schema(description = "id列表") + private @Valid List<@NotNull(message = "id不能为空") Long> ids; +} diff --git a/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/entity/Attachment.java b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/entity/Attachment.java new file mode 100644 index 0000000..82492e7 --- /dev/null +++ b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/entity/Attachment.java @@ -0,0 +1,111 @@ +package org.springblade.file.pojo.entity; +import com.baomidou.mybatisplus.annotation.*; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import org.springblade.core.tool.utils.DateUtil; +import org.springframework.format.annotation.DateTimeFormat; +import java.io.Serializable; +import java.util.Date; + +/** + * @author zhaowei + * @date 2024/8/16 + */ + +@Data +@TableName("blade_attachment") +@Schema(description = "附件对象") +public class Attachment implements Serializable { + + private static final long serialVersionUID = 1L; + + @JsonSerialize(using = ToStringSerializer.class) + @Schema(description = "主键id") + @TableId(value = "id", type = IdType.ASSIGN_ID) + private Long id; + + /** + * 附件名 + */ + @Schema(description = "附件名") + private String attachmentName; + + /** + * 压缩文件所在压缩包相对路径 + */ + @Schema(description = "压缩文件所在压缩包相对路径") + private String dir; + + /** + * 附件大小(单位:B) + */ + @Schema(description = "附件大小(单位:B)") + private Long size; + + /** + * objectKey + */ + @Schema(description = "华为OBS objectKey") + private String objectKey; + + /** + * 创建人-用户名称 + */ + @JsonSerialize(using = ToStringSerializer.class) + @Schema(description = "创建人-用户名称") + @TableField(fill = FieldFill.INSERT) + private String createUserName; + + /** + * 创建人 + */ + @JsonSerialize(using = ToStringSerializer.class) + @Schema(description = "创建人") + @TableField(fill = FieldFill.INSERT) + private Long createUser; + + /** + * 创建时间 + */ + @DateTimeFormat(pattern = DateUtil.PATTERN_DATETIME) + @JsonFormat(pattern = DateUtil.PATTERN_DATETIME) + @Schema(description = "创建时间") + @TableField(fill = FieldFill.INSERT) + private Date createTime; + + /** + * 更新人-用户名称 + */ + @JsonSerialize(using = ToStringSerializer.class) + @Schema(description = "创建人-用户名称") + @TableField(fill = FieldFill.INSERT_UPDATE) + private String updateUserName; + + /** + * 更新人 + */ + @JsonSerialize(using = ToStringSerializer.class) + @Schema(description = "更新人") + @TableField(fill = FieldFill.INSERT_UPDATE) + private Long updateUser; + + /** + * 更新时间 + */ + @DateTimeFormat(pattern = DateUtil.PATTERN_DATETIME) + @JsonFormat(pattern = DateUtil.PATTERN_DATETIME) + @Schema(description = "更新时间") + @TableField(fill = FieldFill.INSERT_UPDATE) + private Date updateTime; + + /** + * 状态[0:未删除,1:删除] + */ + @TableLogic + @Schema(description = "是否已删除 [0:未删除,1:删除]") + private Integer isDeleted; + +} diff --git a/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/entity/BusinessAttachment.java b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/entity/BusinessAttachment.java new file mode 100644 index 0000000..a19529f --- /dev/null +++ b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/entity/BusinessAttachment.java @@ -0,0 +1,113 @@ +package org.springblade.file.pojo.entity; + +import com.baomidou.mybatisplus.annotation.*; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import org.springblade.core.tool.utils.DateUtil; +import org.springframework.format.annotation.DateTimeFormat; + +import java.io.Serial; +import java.io.Serializable; +import java.util.Date; + +/** + * 业务附件信息表 实体类 + * + * @author BladeX + * @since 2024-09-04 + */ +@Data +@TableName("blade_business_attachment") +@Schema(description = "BusinessAttachment对象") +public class BusinessAttachment implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 业务id + */ + @Schema(description = "业务id") + private Long bizId; + /** + * 业务code + */ + @Schema(description = "业务code") + private String bizCode; + /** + * 附件id + */ + @Schema(description = "附件id") + private Long attachmentId; + /** + * 附件描述 + */ + @Schema(description = "附件描述") + @TableField(value = "`desc`") + private String desc; + /** + * 排序 + */ + @Schema(description = "排序") + private Integer sort; + + /** + * 租户ID + */ + @Schema(description = "租户ID") + @TableField(fill = FieldFill.INSERT) + private String tenantId; + + /** + * 主键id + */ + @JsonSerialize(using = ToStringSerializer.class) + @Schema(description = "主键id") + @TableId(value = "id", type = IdType.ASSIGN_ID) + private Long id; + + /** + * 创建人 + */ + @JsonSerialize(using = ToStringSerializer.class) + @Schema(description = "创建人", hidden = true) + @TableField(fill = FieldFill.INSERT) + private Long createUser; + + /** + * 创建时间 + */ + @DateTimeFormat(pattern = DateUtil.PATTERN_DATETIME) + @JsonFormat(pattern = DateUtil.PATTERN_DATETIME) + @Schema(description = "创建时间", hidden = true) + @TableField(fill = FieldFill.INSERT) + private Date createTime; + + /** + * 更新人 + */ + @JsonSerialize(using = ToStringSerializer.class) + @Schema(description = "更新人", hidden = true) + @TableField(fill = FieldFill.INSERT_UPDATE) + private Long updateUser; + + /** + * 更新时间 + */ + @DateTimeFormat(pattern = DateUtil.PATTERN_DATETIME) + @JsonFormat(pattern = DateUtil.PATTERN_DATETIME) + @Schema(description = "更新时间", hidden = true) + @TableField(fill = FieldFill.INSERT_UPDATE) + private Date updateTime; + + /** + * 状态[0:未删除,1:删除] + */ + @TableLogic + @Schema(description = "是否已删除", hidden = true) + @TableField(fill = FieldFill.INSERT) + private Integer isDeleted; +} diff --git a/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/entity/BusinessUnzipDetail.java b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/entity/BusinessUnzipDetail.java new file mode 100644 index 0000000..6d21a9f --- /dev/null +++ b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/entity/BusinessUnzipDetail.java @@ -0,0 +1,85 @@ +package org.springblade.file.pojo.entity; + +import com.baomidou.mybatisplus.annotation.*; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import org.springblade.core.tool.utils.DateUtil; +import org.springframework.format.annotation.DateTimeFormat; + +import java.io.Serial; +import java.io.Serializable; +import java.util.Date; + +/** + *

+ * 业务解压明细表 + *

+ * + * @author bfhuange + * @since 2024-10-30 + */ +@Data +@TableName("blade_business_unzip_detail") +@Schema(description = "BusinessUnzipDetail对象") +public class BusinessUnzipDetail implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + /** + * 主键 + */ + @JsonSerialize(using = ToStringSerializer.class) + @Schema(description = "主键") + @TableId(value = "id", type = IdType.ASSIGN_ID) + private Long id; + /** + * 业务解压任务id + */ + @Schema(description = "业务解压任务id") + private Long businessUnzipTaskId; + /** + * 附件id + */ + @Schema(description = "附件id") + private Long attachmentId; + /** + * 创建人 + */ + @JsonSerialize(using = ToStringSerializer.class) + @Schema(description = "创建人") + @TableField(fill = FieldFill.INSERT) + private Long createUser; + /** + * 创建时间 + */ + @DateTimeFormat(pattern = DateUtil.PATTERN_DATETIME) + @JsonFormat(pattern = DateUtil.PATTERN_DATETIME) + @Schema(description = "创建时间") + @TableField(fill = FieldFill.INSERT) + private Date createTime; + /** + * 更新人 + */ + @JsonSerialize(using = ToStringSerializer.class) + @Schema(description = "更新人") + @TableField(fill = FieldFill.INSERT_UPDATE) + private Long updateUser; + /** + * 更新时间 + */ + @DateTimeFormat(pattern = DateUtil.PATTERN_DATETIME) + @JsonFormat(pattern = DateUtil.PATTERN_DATETIME) + @Schema(description = "更新时间") + @TableField(fill = FieldFill.INSERT_UPDATE) + private Date updateTime; + /** + * 状态[0:未删除,1:删除] + */ + @TableLogic + @Schema(description = "是否已删除 [0:未删除,1:删除]") + @TableField(fill = FieldFill.INSERT) + private Integer isDeleted; +} diff --git a/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/entity/BusinessUnzipTask.java b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/entity/BusinessUnzipTask.java new file mode 100644 index 0000000..8f9d027 --- /dev/null +++ b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/entity/BusinessUnzipTask.java @@ -0,0 +1,89 @@ +package org.springblade.file.pojo.entity; + +import com.baomidou.mybatisplus.annotation.*; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import org.springblade.core.tool.utils.DateUtil; +import org.springframework.format.annotation.DateTimeFormat; + +import java.io.Serial; +import java.io.Serializable; +import java.util.Date; + +/** + *

+ * 业务解压任务表 + *

+ * + * @author bfhuange + * @since 2024-10-28 + */ +@Data +@TableName("blade_business_unzip_task") +@Schema(description = "业务解压任务表") +public class BusinessUnzipTask implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + @TableId(value = "id", type = IdType.ASSIGN_ID) + @Schema(description = "主键") + private Long id; + + @Schema(description = "业务id") + private Long bizId; + /** + * 部门名称ID + */ + @Schema(description = "部门名称ID") + private Long departmentId; + + @Schema(description = "业务code") + private String bizCode; + + @Schema(description = "文档编号") + private String docCode; + + @Schema(description = "关联类型,file_task 文件任务,attachment 附件") + private String relateType; + + @Schema(description = "关联id,文件任务id或附件id") + private Long relateId; + + @Schema(description = "状态 uploading:正在上传 unzipping:解压中 finished:已完成") + private String status; + + @Schema(description = "租户ID") + @TableField(fill = FieldFill.INSERT) + private String tenantId; + + @JsonSerialize(using = ToStringSerializer.class) + @Schema(description = "创建人", hidden = true) + @TableField(fill = FieldFill.INSERT) + private Long createUser; + + @DateTimeFormat(pattern = DateUtil.PATTERN_DATETIME) + @JsonFormat(pattern = DateUtil.PATTERN_DATETIME) + @Schema(description = "创建时间", hidden = true) + @TableField(fill = FieldFill.INSERT) + private Date createTime; + + @JsonSerialize(using = ToStringSerializer.class) + @Schema(description = "更新人", hidden = true) + @TableField(fill = FieldFill.INSERT_UPDATE) + private Long updateUser; + + @DateTimeFormat(pattern = DateUtil.PATTERN_DATETIME) + @JsonFormat(pattern = DateUtil.PATTERN_DATETIME) + @Schema(description = "更新时间", hidden = true) + @TableField(fill = FieldFill.INSERT_UPDATE) + private Date updateTime; + + @Schema(description = "是否已删除", hidden = true) + @TableField(fill = FieldFill.INSERT) + @TableLogic + private Integer isDeleted; +} diff --git a/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/entity/FileTask.java b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/entity/FileTask.java new file mode 100644 index 0000000..36d0ed2 --- /dev/null +++ b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/entity/FileTask.java @@ -0,0 +1,152 @@ +package org.springblade.file.pojo.entity; + +import com.baomidou.mybatisplus.annotation.*; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import org.springblade.core.tool.utils.DateUtil; +import org.springframework.format.annotation.DateTimeFormat; + +import java.io.Serial; +import java.io.Serializable; +import java.util.Date; + +/** + * @author zhaowei + * @date 2024/8/23 + */ + +@Data +@TableName("blade_file_task") +@Schema(description = "文件上传任务对象") +public class FileTask implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + @JsonSerialize(using = ToStringSerializer.class) + @Schema(description = "主键id") + @TableId(value = "id", type = IdType.ASSIGN_ID) + private Long id; + + /** + * Md5 + */ + @Schema(description = "Md5") + private String md5; + + /** + * OBS objectKey + */ + @Schema(description = "OBS objectKey") + private String objectKey; + + /** + * 附件名 + */ + @Schema(description = "附件名") + private String attachmentName; + + /** + * 文件类型 + */ + @Schema(description = "文件类型") + private String suffix; + + /** + * 附件大小(单位:B) + */ + @Schema(description = "附件大小(单位:B)") + private Long size; + + /** + * 分段大小 + */ + @Schema(description = "分段大小") + private Integer chunkSize; + + /** + * 分段数量 + */ + @Schema(description = "分段数量") + private Integer chunkTotal; + + /** + * 当前分段的位置(已上传完成的分段),从0开始,上传完第1段,为1 + */ + @Schema(description = "当前分段的位置(已上传完成的分段),从0开始,上传完第1段,为1") + private Integer currentIndex; + + /** + * 华为云初始化上传任务生成的uploadId + */ + @Schema(description = "华为云初始化上传任务生成的uploadId") + private String uploadId; + + /** + * 状态 uploading:正在上传 completed:上传完成 failed:上传失败 paused:已暂停 + */ + @Schema(description = "状态 uploading:正在上传 completed:上传完成 failed:上传失败 paused:已暂停") + private String status; + + /** + * 创建人-用户名称 + */ + @JsonSerialize(using = ToStringSerializer.class) + @Schema(description = "创建人-用户名称") + @TableField(fill = FieldFill.INSERT) + private String createUserName; + + /** + * 创建人 + */ + @JsonSerialize(using = ToStringSerializer.class) + @Schema(description = "创建人") + @TableField(fill = FieldFill.INSERT) + private Long createUser; + + /** + * 创建时间 + */ + @DateTimeFormat(pattern = DateUtil.PATTERN_DATETIME) + @JsonFormat(pattern = DateUtil.PATTERN_DATETIME) + @Schema(description = "创建时间") + @TableField(fill = FieldFill.INSERT) + private Date createTime; + + /** + * 更新人-用户名称 + */ + @JsonSerialize(using = ToStringSerializer.class) + @Schema(description = "创建人-用户名称") + @TableField(fill = FieldFill.INSERT_UPDATE) + private String updateUserName; + + /** + * 更新人 + */ + @JsonSerialize(using = ToStringSerializer.class) + @Schema(description = "更新人") + @TableField(fill = FieldFill.INSERT_UPDATE) + private Long updateUser; + + /** + * 更新时间 + */ + @DateTimeFormat(pattern = DateUtil.PATTERN_DATETIME) + @JsonFormat(pattern = DateUtil.PATTERN_DATETIME) + @Schema(description = "更新时间") + @TableField(fill = FieldFill.INSERT_UPDATE) + private Date updateTime; + + /** + * 状态[0:未删除,1:删除] + */ + @TableLogic + @Schema(description = "是否已删除 [0:未删除,1:删除]") + @TableField(fill = FieldFill.INSERT) + private Integer isDeleted; + +} diff --git a/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/entity/FileTaskPart.java b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/entity/FileTaskPart.java new file mode 100644 index 0000000..a69e1a5 --- /dev/null +++ b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/entity/FileTaskPart.java @@ -0,0 +1,69 @@ +package org.springblade.file.pojo.entity; + +import com.baomidou.mybatisplus.annotation.*; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import org.springblade.core.tool.utils.DateUtil; +import org.springframework.format.annotation.DateTimeFormat; + +import java.io.Serial; +import java.io.Serializable; +import java.util.Date; + +/** + * @author zhaowei + * @date 2024/8/16 + */ + +@Data +@TableName("blade_file_task_part") +@Schema(description = "文件任务分段表") +public class FileTaskPart implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + @JsonSerialize(using = ToStringSerializer.class) + @Schema(description = "主键id") + @TableId(value = "id", type = IdType.ASSIGN_ID) + private Long id; + + /** + * 文件任务Id + */ + @Schema(description = "文件任务Id") + private Long fileTaskId; + + /** + * 段号。分段号可以是不连续的。取值范围是[1,10000]的非负整数 + */ + @Schema(description = "段号。分段号可以是不连续的。取值范围是[1,10000]的非负整数") + private Integer partNumber; + + /** + * 段的ETag值。分段的Base64编码的128位MD5摘要,长度为32的字符串 + */ + @Schema(description = "段的ETag值。分段的Base64编码的128位MD5摘要,长度为32的字符串") + private String etag; + + /** + * 创建人 + */ + @JsonSerialize(using = ToStringSerializer.class) + @Schema(description = "创建人") + @TableField(fill = FieldFill.INSERT) + private Long createUser; + + /** + * 创建时间 + */ + @DateTimeFormat(pattern = DateUtil.PATTERN_DATETIME) + @JsonFormat(pattern = DateUtil.PATTERN_DATETIME) + @Schema(description = "创建时间") + @TableField(fill = FieldFill.INSERT) + private Date createTime; + +} diff --git a/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/enums/FileTaskStatus.java b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/enums/FileTaskStatus.java new file mode 100644 index 0000000..b258db8 --- /dev/null +++ b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/enums/FileTaskStatus.java @@ -0,0 +1,39 @@ +package org.springblade.file.pojo.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 文件任务状态 + * @author bfhuange + * @date 2024/10/22 + */ +@AllArgsConstructor +@Getter +public enum FileTaskStatus { + /** + * 正在上传 + */ + UPLOADING("uploading", "正在上传"), + /** + * 上传完成 + */ + COMPLETED("completed", "上传完成"), + /** + * 上传失败 + */ + FAILED("failed", "上传失败"), + /** + * 已暂停 + */ + PAUSED("paused", "已暂停"), + ; + /** + * 编码 + */ + private final String code; + /** + * 名称 + */ + private final String name; +} diff --git a/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/enums/UnzipTask.java b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/enums/UnzipTask.java new file mode 100644 index 0000000..7ea5d49 --- /dev/null +++ b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/enums/UnzipTask.java @@ -0,0 +1,99 @@ +package org.springblade.file.pojo.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 解压任务枚举类 + * @author bfhuange + * @date 2024/10/27 + */ +public class UnzipTask { + + /** + * 业务编码枚举 + */ + @AllArgsConstructor + @Getter + public enum BizCode { + /** + * 运单 + */ + WAYBILL("waybill", "运单"), + /** + * 基地 + */ + WAREHOUSE("warehouse", "基地"), + ; + /** + * 编码 + */ + private final String code; + /** + * 名称 + */ + private final String name; + } + + /** + * 关联类型枚举 + */ + @AllArgsConstructor + @Getter + public enum RelateType { + /** + * 文件任务 + */ + FILE_TASK("file_task", "文件任务"), + /** + * 附件 + */ + ATTACHMENT("attachment", "附件"), + ; + /** + * 编码 + */ + private final String code; + /** + * 名称 + */ + private final String name; + } + + /** + * 状态枚举 + */ + @AllArgsConstructor + @Getter + public enum Status { + /** + * 正在上传 + */ + UPLOADING("uploading", "正在上传"), + /** + * 上传完成 + */ + UPLOADED("uploaded", "上传完成"), + /** + * 正在解压 + */ + UNZIPPING("unzipping", "正在解压"), + /** + * 解压失败 + */ + UNZIP_FAIL("unzip_fail", "解压失败"), + /** + * 已完成 + */ + FINISHED("finished", "已完成"), + ; + /** + * 编码 + */ + private final String code; + /** + * 名称 + */ + private final String name; + } +} diff --git a/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/vo/AttachmentDetailVO.java b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/vo/AttachmentDetailVO.java new file mode 100644 index 0000000..7059435 --- /dev/null +++ b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/vo/AttachmentDetailVO.java @@ -0,0 +1,73 @@ +package org.springblade.file.pojo.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; +import java.util.Date; + +/** + * 附件详情对象 + * @author bfhuange + * @date 2024/10/30 + */ +@Data +@Schema(description = "附件详情对象") +public class AttachmentDetailVO implements Serializable { + @Serial + private static final long serialVersionUID = 1L; + /** + * 主键id + */ + @Schema(description = "主键id") + private Long id; + + /** + * 附件名 + */ + @Schema(description = "附件名") + private String attachmentName; + + /** + * objectKey + */ + @Schema(description = "华为OBS objectKey") + private String objectKey; + + /** + * 附件大小(单位:B) + */ + @Schema(description = "附件大小(单位:B)") + private long size; + + /** + * 创建人 + */ + @Schema(description = "创建人") + private Long createUser; + + /** + * 创建时间 + */ + @Schema(description = "创建时间") + private Date createTime; + + /** + * 更新人 + */ + @Schema(description = "更新人") + private Long updateUser; + + /** + * 更新时间 + */ + @Schema(description = "更新时间") + private Date updateTime; + + /** + * 下载链接 + */ + @Schema(description = "下载链接") + private String url; +} diff --git a/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/vo/BusinessAttachmentVO.java b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/vo/BusinessAttachmentVO.java new file mode 100644 index 0000000..0444bda --- /dev/null +++ b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/vo/BusinessAttachmentVO.java @@ -0,0 +1,105 @@ +package org.springblade.file.pojo.vo; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.Size; +import lombok.Data; +import org.springblade.core.tool.utils.DateUtil; +import org.springframework.format.annotation.DateTimeFormat; + +import java.io.Serial; +import java.io.Serializable; +import java.util.Date; + +/** + * 业务附件信息表 视图实体类 + * + * @author BladeX + * @since 2024-09-04 + */ +@Data +@Schema(description = "业务附件响应参数") +public class BusinessAttachmentVO implements Serializable { + @Serial + private static final long serialVersionUID = 1L; + + /** + * 主键id + */ + @JsonSerialize(using = ToStringSerializer.class) + @Schema(description = "主键id") + private Long id; + /** + * 业务id + */ + @Schema(description = "业务id") + @JsonSerialize(using = ToStringSerializer.class) + private Long bizId; + /** + * 业务code + */ + @Schema(description = "业务code") + private String bizCode; + /** + * 业务code名称 + */ + @Schema(description = "业务code名称") + private String bizCodeStr; + /** + * 附件id + */ + @Schema(description = "附件id") + @JsonSerialize(using = ToStringSerializer.class) + private Long attachmentId; + /** + * 附件名 + */ + @Schema(description = "附件名") + private String attachmentName; + /** + * 附件大小(单位:B) + */ + @Schema(description = "附件大小(单位:B)") + private long size; + /** + * objectKey + */ + @Schema(description = "华为OBS objectKey") + private String objectKey; + /** + * 附件描述 + */ + @Schema(description = "附件描述") + @Size(max = 100, message = "附件描述长度不能超过100") + private String desc; + /** + * 排序 + */ + @Schema(description = "排序") + private Integer sort; + /** + * 上传人id + */ + @Schema(description = "上传人id") + @JsonSerialize(using = ToStringSerializer.class) + private Long uploadUserId; + /** + * 上传人姓名 + */ + @Schema(description = "上传人姓名") + private String uploadUserName; + /** + * 上传人姓名 + */ + @Schema(description = "上传人姓名") + private String createUserName; + /** + * 创建时间 + */ + @DateTimeFormat(pattern = DateUtil.PATTERN_DATETIME) + @JsonFormat(pattern = DateUtil.PATTERN_DATETIME) + @Schema(description = "创建时间") + private Date createTime; +} diff --git a/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/vo/BusinessUnzipDetailListVO.java b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/vo/BusinessUnzipDetailListVO.java new file mode 100644 index 0000000..28f8f62 --- /dev/null +++ b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/vo/BusinessUnzipDetailListVO.java @@ -0,0 +1,94 @@ +package org.springblade.file.pojo.vo; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.Size; +import lombok.Data; +import org.springblade.core.tool.utils.DateUtil; +import org.springframework.format.annotation.DateTimeFormat; + +import java.io.Serial; +import java.io.Serializable; +import java.util.Date; + +/** + * 业务解压任务明细对象 + * @author bfhuange + * @date 2024/11/08 + */ +@Data +@Schema(description = "业务解压任务明细对象") +public class BusinessUnzipDetailListVO implements Serializable { + @Serial + private static final long serialVersionUID = 1L; + /** + * 主键 + */ + @JsonSerialize(using = ToStringSerializer.class) + @Schema(description = "主键") + private Long id; + /** + * 附件id + */ + @Schema(description = "附件id") + @JsonSerialize(using = ToStringSerializer.class) + private Long attachmentId; + + //==========================附件属性============================= + /** + * 压缩文件所在压缩包相对路径 + */ + @Schema(description = "压缩文件所在压缩包相对路径") + private String dir; + /** + * 附件名 + */ + @Schema(description = "附件名") + private String attachmentName; + /** + * 附件大小(单位:B) + */ + @Schema(description = "附件大小(单位:B)") + private long size; + /** + * objectKey + */ + @Schema(description = "华为OBS objectKey") + private String objectKey; + /** + * 附件描述 + */ + @Schema(description = "附件描述") + @Size(max = 100, message = "附件描述长度不能超过100") + private String desc; + /** + * 排序 + */ + @Schema(description = "排序") + private Integer sort; + /** + * 上传人id + */ + @Schema(description = "上传人id") + @JsonSerialize(using = ToStringSerializer.class) + private Long uploadUserId; + /** + * 上传人姓名 + */ + @Schema(description = "上传人姓名") + private String uploadUserName; + /** + * 上传人姓名 + */ + @Schema(description = "上传人姓名") + private String createUserName; + /** + * 创建时间 + */ + @DateTimeFormat(pattern = DateUtil.PATTERN_DATETIME) + @JsonFormat(pattern = DateUtil.PATTERN_DATETIME) + @Schema(description = "创建时间") + private Date createTime; +} diff --git a/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/vo/BusinessUnzipTaskListVO.java b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/vo/BusinessUnzipTaskListVO.java new file mode 100644 index 0000000..4e1de52 --- /dev/null +++ b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/vo/BusinessUnzipTaskListVO.java @@ -0,0 +1,169 @@ +package org.springblade.file.pojo.vo; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import org.springblade.core.tool.utils.DateUtil; +import org.springframework.format.annotation.DateTimeFormat; + +import java.io.Serial; +import java.io.Serializable; +import java.util.Date; + +/** + * 业务解压任务分页对象 + * @author bfhuange + * @date 2024/10/28 + */ +@Data +@Schema(description = "业务解压任务分页对象") +public class BusinessUnzipTaskListVO implements Serializable { + @Serial + private static final long serialVersionUID = 1L; + /** + * 主键 + */ + @JsonSerialize(using = ToStringSerializer.class) + @Schema(description = "主键") + private Long id; + /** + * 业务id + */ + @JsonSerialize(using = ToStringSerializer.class) + @Schema(description = "业务id") + private Long bizId; + /** + * 业务code + */ + @Schema(description = "业务code") + private String bizCode; + /** + * 文档编号 + */ + @Schema(description = "文档编号") + private String docCode; + /** + * 关联类型,file_task 文件任务,attachment 附件 + */ + @Schema(description = "关联类型,file_task 文件任务,attachment 附件") + private String relateType; + /** + * 关联id,文件任务id或附件id + */ + @JsonSerialize(using = ToStringSerializer.class) + @Schema(description = "关联id,文件任务id或附件id") + private Long relateId; + + //==========================文件任务属性============================= + /** + * Md5 + */ + @Schema(description = "Md5") + private String md5; + + /** + * OBS objectKey + */ + @Schema(description = "OBS objectKey") + private String objectKey; + + /** + * 附件名 + */ + @Schema(description = "附件名") + private String attachmentName; + + /** + * 文件类型 + */ + @Schema(description = "文件类型") + private String suffix; + + /** + * 附件大小(单位:B) + */ + @Schema(description = "附件大小(单位:B)") + private Long size; + + /** + * 分段大小 + */ + @Schema(description = "分段大小") + private Integer chunkSize; + + /** + * 分段数量 + */ + @Schema(description = "分段数量") + private Integer chunkTotal; + + /** + * 当前分段的位置(已上传完成的分段),从0开始,上传完第1段,为1 + */ + @Schema(description = "当前分段的位置(已上传完成的分段),从0开始,上传完第1段,为1") + private Integer currentIndex; + + /** + * 华为云初始化上传任务生成的uploadId + */ + @Schema(description = "华为云初始化上传任务生成的uploadId") + private String uploadId; + + /** + * 状态 uploading:正在上传 completed:上传完成 failed:上传失败 paused:已暂停 + */ + @Schema(description = "状态 uploading:正在上传 completed:上传完成 failed:上传失败 paused:已暂停") + private String status; + + /** + * 状态名称 + */ + @Schema(description = "状态名称") + private String statusStr; + + /** + * 创建人-用户名称 + */ + @JsonSerialize(using = ToStringSerializer.class) + @Schema(description = "创建人-用户名称") + private String createUserName; + + /** + * 创建人 + */ + @JsonSerialize(using = ToStringSerializer.class) + @Schema(description = "创建人") + private Long createUser; + + /** + * 创建时间 + */ + @DateTimeFormat(pattern = DateUtil.PATTERN_DATETIME) + @JsonFormat(pattern = DateUtil.PATTERN_DATETIME) + @Schema(description = "创建时间") + private Date createTime; + + /** + * 更新人-用户名称 + */ + @JsonSerialize(using = ToStringSerializer.class) + @Schema(description = "创建人-用户名称") + private String updateUserName; + + /** + * 更新人 + */ + @JsonSerialize(using = ToStringSerializer.class) + @Schema(description = "更新人") + private Long updateUser; + + /** + * 更新时间 + */ + @DateTimeFormat(pattern = DateUtil.PATTERN_DATETIME) + @JsonFormat(pattern = DateUtil.PATTERN_DATETIME) + @Schema(description = "更新时间") + private Date updateTime; +} diff --git a/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/vo/FileHandleVO.java b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/vo/FileHandleVO.java new file mode 100644 index 0000000..c2af5f6 --- /dev/null +++ b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/vo/FileHandleVO.java @@ -0,0 +1,36 @@ +package org.springblade.file.pojo.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.io.File; +import java.io.Serial; +import java.io.Serializable; +import java.util.List; + +/** + * 文件处理结果对象 + * @author bfhuange + * @date 2024/10/30 + */ +@Data +@Schema(description = "文件处理结果对象") +public class FileHandleVO implements Serializable { + @Serial + private static final long serialVersionUID = 1L; + /** + * 下载的压缩文件 + */ + @Schema(description = "下载的压缩文件") + private File downloadFile; + /** + * 解压目录 + */ + @Schema(description = "解压目录") + private File unzippedDir; + /** + * 解压后的文件列表 + */ + @Schema(description = "解压后的文件列表") + private List unzippedFiles; +} diff --git a/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/vo/FileTaskUpdateVO.java b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/vo/FileTaskUpdateVO.java new file mode 100644 index 0000000..3bb8b6c --- /dev/null +++ b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/vo/FileTaskUpdateVO.java @@ -0,0 +1,25 @@ +package org.springblade.file.pojo.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.io.Serial; + +/** + * 文件任务更新结果 + * @author bfhuange + * @date 2024/10/28 + */ +@EqualsAndHashCode(callSuper = true) +@Data +@Schema(description = "文件任务更新结果") +public class FileTaskUpdateVO extends FileTaskVO { + @Serial + private static final long serialVersionUID = 1L; + /** + * 文档编号(凭证批次号),如果有则返回,没有为空 + */ + @Schema(description = "文档编号(凭证批次号),如果有则返回,没有为空") + private String docCode; +} diff --git a/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/vo/FileTaskVO.java b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/vo/FileTaskVO.java new file mode 100644 index 0000000..0407ff8 --- /dev/null +++ b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/vo/FileTaskVO.java @@ -0,0 +1,133 @@ +package org.springblade.file.pojo.vo; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import org.springblade.core.tool.utils.DateUtil; +import org.springframework.format.annotation.DateTimeFormat; + +import java.io.Serial; +import java.io.Serializable; +import java.util.Date; + +/** + * 文件任务对象 + * @author bfhuange + * @date 2024/10/22 + */ +@Data +@Schema(description = "文件任务对象") +public class FileTaskVO implements Serializable { + @Serial + private static final long serialVersionUID = 1L; + + @JsonSerialize(using = ToStringSerializer.class) + @Schema(description = "主键id") + private Long id; + + /** + * Md5 + */ + @Schema(description = "Md5") + private String md5; + + /** + * OBS objectKey + */ + @Schema(description = "OBS objectKey") + private String objectKey; + + /** + * 附件名 + */ + @Schema(description = "附件名") + private String attachmentName; + + /** + * 文件类型 + */ + @Schema(description = "文件类型") + private String suffix; + + /** + * 附件大小(单位:B) + */ + @Schema(description = "附件大小(单位:B)") + private Long size; + + /** + * 分段大小 + */ + @Schema(description = "分段大小") + private Integer chunkSize; + + /** + * 分段数量 + */ + @Schema(description = "分段数量") + private Integer chunkTotal; + + /** + * 当前分段的位置(已上传完成的分段),从0开始,上传完第1段,为1 + */ + @Schema(description = "当前分段的位置(已上传完成的分段),从0开始,上传完第1段,为1") + private Integer currentIndex; + + /** + * 华为云初始化上传任务生成的uploadId + */ + @Schema(description = "华为云初始化上传任务生成的uploadId") + private String uploadId; + + /** + * 状态 uploading:正在上传 completed:上传完成 failed:上传失败 paused:已暂停 + */ + @Schema(description = "状态 uploading:正在上传 completed:上传完成 failed:上传失败 paused:已暂停") + private String status; + + /** + * 创建人-用户名称 + */ + @JsonSerialize(using = ToStringSerializer.class) + @Schema(description = "创建人-用户名称") + private String createUserName; + + /** + * 创建人 + */ + @JsonSerialize(using = ToStringSerializer.class) + @Schema(description = "创建人") + private Long createUser; + + /** + * 创建时间 + */ + @DateTimeFormat(pattern = DateUtil.PATTERN_DATETIME) + @JsonFormat(pattern = DateUtil.PATTERN_DATETIME) + @Schema(description = "创建时间") + private Date createTime; + + /** + * 更新人-用户名称 + */ + @JsonSerialize(using = ToStringSerializer.class) + @Schema(description = "创建人-用户名称") + private String updateUserName; + + /** + * 更新人 + */ + @JsonSerialize(using = ToStringSerializer.class) + @Schema(description = "更新人") + private Long updateUser; + + /** + * 更新时间 + */ + @DateTimeFormat(pattern = DateUtil.PATTERN_DATETIME) + @JsonFormat(pattern = DateUtil.PATTERN_DATETIME) + @Schema(description = "更新时间") + private Date updateTime; +} diff --git a/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/vo/FileUrlVO.java b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/vo/FileUrlVO.java new file mode 100644 index 0000000..2d19918 --- /dev/null +++ b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/vo/FileUrlVO.java @@ -0,0 +1,33 @@ +package org.springblade.file.pojo.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 文件下载地址对象 + * @author bfhuange + * @since 2025/4/22 + */ +@AllArgsConstructor +@NoArgsConstructor +@Data +@Schema(description = "文件下载地址对象") +public class FileUrlVO implements Serializable { + @Serial + private static final long serialVersionUID = 1L; + /** + * 主键id + */ + @Schema(description = "主键id") + private Long id; + /** + * 文件下载地址 + */ + @Schema(description ="文件下载地址") + private String url; +} diff --git a/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/vo/FileVO.java b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/vo/FileVO.java new file mode 100644 index 0000000..446d5a3 --- /dev/null +++ b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/vo/FileVO.java @@ -0,0 +1,35 @@ +package org.springblade.file.pojo.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 文件对象 + * @author bfhuange + * @date 2024/11/19 + */ +@AllArgsConstructor +@NoArgsConstructor +@Data +@Schema(description = "文件对象") +public class FileVO implements Serializable { + @Serial + private static final long serialVersionUID = 1L; + + /** + * OBS objectKey + */ + @Schema(description = "OBS objectKey") + private String objectKey; + + /** + * Md5 + */ + @Schema(description = "Md5") + private String md5; +} diff --git a/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/vo/PartEtagVO.java b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/vo/PartEtagVO.java new file mode 100644 index 0000000..281ed29 --- /dev/null +++ b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/vo/PartEtagVO.java @@ -0,0 +1,29 @@ +package org.springblade.file.pojo.vo; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 待合并的段 + * @author bfhuange + * @date 2024/10/21 + */ +@AllArgsConstructor +@NoArgsConstructor +@Data +public class PartEtagVO implements Serializable { + @Serial + private static final long serialVersionUID = 1L; + /** + * 段的ETag值。分段的Base64编码的128位MD5摘要,长度为32的字符串 + */ + private String etag; + /** + * 段号。分段号可以是不连续的。取值范围是[1,10000]的非负整数 + */ + private Integer partNumber; +} diff --git a/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/vo/TempFileUrlVO.java b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/vo/TempFileUrlVO.java new file mode 100644 index 0000000..6d92f0e --- /dev/null +++ b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/vo/TempFileUrlVO.java @@ -0,0 +1,33 @@ +package org.springblade.file.pojo.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 文件下载地址对象 + * @author bfhuange + * @since 2025/4/22 + */ +@AllArgsConstructor +@NoArgsConstructor +@Data +@Schema(description = "临时文件下载地址对象") +public class TempFileUrlVO implements Serializable { + @Serial + private static final long serialVersionUID = 1L; + /** + * obs key + */ + @Schema(description = "obs key") + private String objectKey; + /** + * 文件下载地址 + */ + @Schema(description ="文件下载地址") + private String url; +} diff --git a/blade-service-api/blade-open-api/pom.xml b/blade-service-api/blade-open-api/pom.xml new file mode 100644 index 0000000..93a2acd --- /dev/null +++ b/blade-service-api/blade-open-api/pom.xml @@ -0,0 +1,15 @@ + + + 4.0.0 + + org.springblade + blade-service-api + ${revision} + + + blade-open-api + ${project.artifactId} + jar + \ No newline at end of file diff --git a/blade-service-api/blade-open-api/src/main/java/org/springblade/openapi/common/vo/PageVO.java b/blade-service-api/blade-open-api/src/main/java/org/springblade/openapi/common/vo/PageVO.java new file mode 100644 index 0000000..9c9af71 --- /dev/null +++ b/blade-service-api/blade-open-api/src/main/java/org/springblade/openapi/common/vo/PageVO.java @@ -0,0 +1,40 @@ +package org.springblade.openapi.common.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serial; +import java.io.Serializable; +import java.util.Collections; +import java.util.List; + +/** + * 分页参数 + * @author bfhuange + * @since 2025/4/23 + */ +@AllArgsConstructor +@NoArgsConstructor +@Data +@Schema(description = "分页结果") +public class PageVO implements Serializable { + @Serial + private static final long serialVersionUID = 1L; + /** + * 查询数据列表 + */ + @Schema(description = "查询数据列表") + protected List records = Collections.emptyList(); + /** + * 总数 + */ + @Schema(description = "总数") + protected long total = 0; + /** + * 总页数 + */ + @Schema(description = "总页数") + protected long pages = 0; +} diff --git a/blade-service-api/blade-open-api/src/main/java/org/springblade/openapi/mk/api/IApi4MK.java b/blade-service-api/blade-open-api/src/main/java/org/springblade/openapi/mk/api/IApi4MK.java new file mode 100644 index 0000000..3b71190 --- /dev/null +++ b/blade-service-api/blade-open-api/src/main/java/org/springblade/openapi/mk/api/IApi4MK.java @@ -0,0 +1,61 @@ +package org.springblade.openapi.mk.api; + +import org.springblade.core.tool.api.R; +import org.springblade.openapi.mk.pojo.dto.Api4MKProcessApprovalDTO; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; + +/** + * 给mk回调的api + * @author bfhuange + * @date 2024/9/5 + */ +public interface IApi4MK { + String API_PREFIX = "/openApi/mk"; + String PROCESS_API_PREFIX = API_PREFIX + "/process"; + String PROCESS_FINISH_CALLBACK = PROCESS_API_PREFIX + "/finishCallback"; + String PROCESS_APPROVAL_CALLBACK = PROCESS_API_PREFIX + "/approvalCallback"; + String PROCESS_REJECT_CALLBACK = PROCESS_API_PREFIX + "/rejectCallback"; + String PROCESS_REVOKE_CALLBACK = PROCESS_API_PREFIX + "/revokeCallback"; + String PROCESS_COMMON_CALLBACK = PROCESS_API_PREFIX + "/commonCallback"; + + /** + * 流程通用回调接口 + * @param param + * @return + */ + @PostMapping(PROCESS_COMMON_CALLBACK) + R processCommonCallback(@RequestBody Api4MKProcessApprovalDTO param); + + /** + * 流程结束回调接口 + * @param param + * @return + */ + @PostMapping(PROCESS_FINISH_CALLBACK) + R processFinishCallback(@RequestBody Api4MKProcessApprovalDTO param); + + /** + * 流程审批同意回调接口 + * @param param + * @return + */ + @PostMapping(PROCESS_APPROVAL_CALLBACK) + R processApprovalCallback(@RequestBody Api4MKProcessApprovalDTO param); + + /** + * 流程审批拒绝回调接口 + * @param param + * @return + */ + @PostMapping(PROCESS_REJECT_CALLBACK) + R processRejectCallback(@RequestBody Api4MKProcessApprovalDTO param); + + /** + * 流程撤销回调接口 + * @param param + * @return + */ + @PostMapping(PROCESS_REVOKE_CALLBACK) + R processRevokeCallback(@RequestBody Api4MKProcessApprovalDTO param); +} diff --git a/blade-service-api/blade-open-api/src/main/java/org/springblade/openapi/mk/constant/MKConstant.java b/blade-service-api/blade-open-api/src/main/java/org/springblade/openapi/mk/constant/MKConstant.java new file mode 100644 index 0000000..73a2208 --- /dev/null +++ b/blade-service-api/blade-open-api/src/main/java/org/springblade/openapi/mk/constant/MKConstant.java @@ -0,0 +1,8 @@ +package org.springblade.openapi.mk.constant; + +/** + * @author bfhuange + * @date 2024/9/12 + */ +public class MKConstant { +} diff --git a/blade-service-api/blade-open-api/src/main/java/org/springblade/openapi/mk/pojo/dto/Api4MKProcessApprovalDTO.java b/blade-service-api/blade-open-api/src/main/java/org/springblade/openapi/mk/pojo/dto/Api4MKProcessApprovalDTO.java new file mode 100644 index 0000000..b71f71e --- /dev/null +++ b/blade-service-api/blade-open-api/src/main/java/org/springblade/openapi/mk/pojo/dto/Api4MKProcessApprovalDTO.java @@ -0,0 +1,81 @@ +package org.springblade.openapi.mk.pojo.dto; + +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; + +/** + * mk审批回调参数 + * @author bfhuange + * @date 2024/9/5 + */ +@Data +public class Api4MKProcessApprovalDTO implements Serializable { + @Serial + private static final long serialVersionUID = 1L; + /** + * 流程实例id + */ + private String processInstanceId; + /** + * 表单实例id + */ + private String formInstanceId; + /** + * 模板id + */ + private String templateId; + /** + * 模板编码,template_拼接 ProcessTypeEnum 的值 + */ + private String templateCode; + /** + * 流程状态 + */ + private String processStatus; + /** + * 申请人登录名 + */ + private String applicantLoginName; + + //===========================下面5个参数流程结束没有========================= + /** + * 驳回节点id N2 是起草节点 + */ + private String rejectNodeId; + /** + * 当前节点id + */ + private String currentNodeId; + /** + * 当前节点编号 + */ + private String currentNodeNumber; + /** + * 执行的操作 + */ + private String operation; + /** + * 操作名称 + */ + private String operationName; + /** + * 审批意见 + */ + private String approvalOpinion; + /** + * 操作人登录名 + */ + private String operatorLoginName; + + //====================非mk回调参数,回调接口设置参数=================== + /** + * 是否流程已完成,非mk回调参数,回调接口设置参数 + */ + private boolean complete; + /** + * 审批状态,非mk回调参数,回调接口设置参数 + */ + private String approveStatus; +} diff --git a/blade-service-api/blade-open-api/src/main/java/org/springblade/openapi/mk/pojo/dto/ApiMKProcessFinishDTO.java b/blade-service-api/blade-open-api/src/main/java/org/springblade/openapi/mk/pojo/dto/ApiMKProcessFinishDTO.java new file mode 100644 index 0000000..2e2b57f --- /dev/null +++ b/blade-service-api/blade-open-api/src/main/java/org/springblade/openapi/mk/pojo/dto/ApiMKProcessFinishDTO.java @@ -0,0 +1,34 @@ +package org.springblade.openapi.mk.pojo.dto; + +import jakarta.validation.constraints.NotBlank; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; + +/** + * mk审批结束回调参数 + * @author bfhuange + * @date 2024/9/5 + */ +@Data +public class ApiMKProcessFinishDTO implements Serializable { + @Serial + private static final long serialVersionUID = 1L; + /** + * 流程实例id + */ + private String processInstanceId; + /** + * 表单实例id + */ + private String formInstanceId; + /** + * 模板编码,template_拼接 ProcessTypeEnum 的值 + */ + private String templateCode; + /** + * 流程状态 + */ + private String processStatus; +} diff --git a/blade-service-api/blade-open-api/src/main/java/org/springblade/openapi/mk/pojo/dto/ProcessApprovalDTO.java b/blade-service-api/blade-open-api/src/main/java/org/springblade/openapi/mk/pojo/dto/ProcessApprovalDTO.java new file mode 100644 index 0000000..2bed1af --- /dev/null +++ b/blade-service-api/blade-open-api/src/main/java/org/springblade/openapi/mk/pojo/dto/ProcessApprovalDTO.java @@ -0,0 +1,40 @@ +package org.springblade.openapi.mk.pojo.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +@Builder +@Data +@NoArgsConstructor +@AllArgsConstructor +public class ProcessApprovalDTO implements Serializable { + + /** + * 流程实例id + */ + @Schema(description = "流程实例id") + protected String flowInstId; + + + /** + * 表单实例id + */ + protected String formInstanceId; + + /** + * 审批状态 + */ + protected String approveStatus; + + /** + * 下级审批人 + */ + protected String nextApproveUser; + + +} diff --git a/blade-service-api/blade-open-api/src/main/java/org/springblade/openapi/mk/pojo/enums/ProcessCallbackType.java b/blade-service-api/blade-open-api/src/main/java/org/springblade/openapi/mk/pojo/enums/ProcessCallbackType.java new file mode 100644 index 0000000..44e479a --- /dev/null +++ b/blade-service-api/blade-open-api/src/main/java/org/springblade/openapi/mk/pojo/enums/ProcessCallbackType.java @@ -0,0 +1,101 @@ +package org.springblade.openapi.mk.pojo.enums; + +import com.google.common.collect.Lists; +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.List; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * 流程回调类型 + * @author bfhuange + * @since 2024/11/23 + */ +@AllArgsConstructor +@Getter +public enum ProcessCallbackType { + /** + * 提交 + */ + SUBMIT(ProcessOperationType.DRAFTER_SUBMIT), + /** + * 起草人撤回 + */ + RETRACT(ProcessOperationType.DRAFTER_RETRACT), + /** + * 审批通过 + */ + PASS(ProcessOperationType.HANDLER_PASS), + /** + * 审批驳回 + */ + REJECT(ProcessOperationType.HANDLER_REJECT), + /** + * 废弃(起草人废弃、审批人废弃、超管废弃) + */ + ABANDON(ProcessOperationType.DRAFTER_ABANDON, ProcessOperationType.HANDLER_ABANDON, ProcessOperationType.ADMIN_ABANDON), + /** + * 修改当前处理人(超管修改当前处理人、审批人转办、审批人撤回、特权人前后跳转) + */ + CHANGE_CUR_HANDLER(ProcessOperationType.ADMIN_CHANGE_CUR_HANDLER, ProcessOperationType.HANDLER_TRANSFER, ProcessOperationType.HISTORY_HANDLER_RETRACT, ProcessOperationType.ADMIN_JUMP), + /** + * 流程结束 + */ + FINISH(ProcessOperationType.PROCESS_FINISH), + ; + /** + * 编码 + */ + private final List operationTypes; + + ProcessCallbackType(ProcessOperationType... operationTypes) { + if (operationTypes.length == 0) { + throw new RuntimeException("流程操作类型不能为空"); + } + this.operationTypes = Stream.of(operationTypes) + .map(ProcessOperationType::getCode) + .toList(); + } + + ProcessCallbackType(String... operationTypes) { + if (operationTypes.length == 0) { + throw new RuntimeException("流程操作类型不能为空"); + } + this.operationTypes = Lists.newArrayList(operationTypes); + } + + + /** + * 流程操作类型对应的事件 + */ + private static final Map CALLBACK_MAP = initCallbackMap(); + + /** + * 初始化流程操作类型对应的事件 + * @return + */ + private static Map initCallbackMap() { + return Stream.of(values()) + .flatMap(callbackType -> callbackType.getOperationTypes().stream() + .collect(Collectors.toMap(Function.identity(), type -> callbackType)) + .entrySet() + .stream() + ) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (a, b) -> { + throw new RuntimeException("流程操作类型不能重复"); + })); + } + + /** + * 根据流程操作类型,获取对应的回调 + * @param operationType + * @return + */ + public static ProcessCallbackType getCallbackType(String operationType) { + return CALLBACK_MAP.get(operationType); + } +} diff --git a/blade-service-api/blade-open-api/src/main/java/org/springblade/openapi/mk/pojo/enums/ProcessOperationType.java b/blade-service-api/blade-open-api/src/main/java/org/springblade/openapi/mk/pojo/enums/ProcessOperationType.java new file mode 100644 index 0000000..92d20e6 --- /dev/null +++ b/blade-service-api/blade-open-api/src/main/java/org/springblade/openapi/mk/pojo/enums/ProcessOperationType.java @@ -0,0 +1,175 @@ +package org.springblade.openapi.mk.pojo.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.stream.Stream; + +/** + * 流程操作类型枚举 + * @author bfhuange + * @since 2024/11/23 + */ +@AllArgsConstructor +@Getter +public enum ProcessOperationType { + /** + * 起草人协同 + */ + DRAFTER_COOPERATE("drafter_cooperate", "起草人协同"), + /** + * 起草人取消协同 + */ + DRAFTER_CANCEL_DRAFT_COOPERATE("drafter_cancelDraftCooperate", "起草人取消协同"), + /** + * 协同接收人-回复协同 + */ + HANDLER_REPLY_DRAFT_COOPERATE("handler_replyDraftCooperate", "协同接收人-回复协同"), + /** + * 起草人-提交 + */ + DRAFTER_SUBMIT("drafter_submit", "起草人-提交"), + /** + * 起草人-废弃 + */ + DRAFTER_ABANDON("drafter_abandon", "起草人-废弃"), + /** + * 起草人-撤回 + */ + DRAFTER_RETRACT("drafter_retract", "起草人-撤回"), + /** + * 审批人-通过 + */ + HANDLER_PASS("handler_pass", "审批人-通过"), + /** + * 审批人-驳回 + */ + HANDLER_REJECT("handler_reject", "审批人-驳回"), + /** + * 审批人-废弃 + */ + HANDLER_ABANDON("handler_abandon", "审批人-废弃"), + /** + * 审批人-撤回 + */ + HISTORY_HANDLER_RETRACT("historyHandler_retract", "审批人-撤回"), + /** + * 审批人-签字 + */ + HANDLER_SIGN("handler_sign", "审批人-签字"), + /** + * 审批人-转办 + */ + HANDLER_TRANSFER("handler_transfer", "审批人-转办"), + /** + * 审批人-分发 + */ + HANDLER_ASSIGN("handler_assign", "审批人-分发"), + /** + * 审批人-催办 + */ + HISTORY_HANDLER_URGE("historyHandler_urge", "审批人-催办"), + /** + * 审批人-沟通 + */ + HANDLER_COMMUNICATE("handler_communicate", "审批人-沟通"), + /** + * 审批人-回复沟通 + */ + HANDLER_REPLY_COMMUNICATE("handler_replyCommunicate", "审批人-回复沟通"), + /** + * 审批人-取消沟通 + */ + HANDLER_CANCEL_COMMUNICATE("handler_cancelCommunicate", "审批人-取消沟通"), + /** + * 审批人-补签 + */ + HANDLER_APPEND_SIGN("handler_appendSign", "审批人-补签"), + /** + * 审批人-加签 + */ + HANDLER_PREPEND_SIGN("handler_prependSign", "审批人-加签"), + /** + * 审批人(加签人)-取消加签 + */ + HANDLER_PREPEND_SIGN_CANCEL("handler_prependSignCancel", "审批人(加签人)-取消加签"), + /** + * 审批人(被加签人)-退回加签 + */ + HANDLER_PREPEND_SIGN_REFUSE("handler_prependSignRefuse", "审批人(被加签人)-退回加签"), + /** + * 审批人(被加签人)-通过加签 + */ + HANDLER_PREPEND_SIGN_PASS("handler_prependSignPass", "审批人(被加签人)-通过加签"), + /** + * 审批人(被加签人)-审批人-传阅 + */ + HANDLER_CIRCULATION("handler_circulation", "审批人(被加签人)-审批人-传阅"), + /** + * 特权人-前后跳转 + */ + ADMIN_JUMP("admin_jump", "特权人-前后跳转"), + /** + * 特权人-流程暂停 + */ + ADMIN_PROCESS_SUSPEND("admin_processSuspend", "特权人-流程暂停"), + /** + * 特权人-节点暂停 + */ + ADMIN_NODE_SUSPEND("admin_nodeSuspend", "特权人-节点暂停"), + /** + * 审批人-节点唤醒 + */ + HANDLER_NODE_RESUME("handler_nodeResume", "审批人-节点唤醒"), + /** + * 特权人-节点唤醒 + */ + ADMIN_NODE_RESUME("admin_nodeResume", "特权人-节点唤醒"), + /** + * 特权人-流程唤醒 + */ + ADMIN_PROCESS_RESUME("admin_processResume", "特权人-流程唤醒"), + /** + * 特权人-修改流程 + */ + ADMIN_MODIFY_PROCESS("admin_modifyProcess", "特权人-修改流程"), + /** + * 特权人-修改当前处理人 + */ + ADMIN_CHANGE_CUR_HANDLER("admin_changeCurHandler", "特权人-修改当前处理人"), + /** + * 特权人-终审通过 + */ + ADMIN_END("admin_end", "特权人-终审通过"), + /** + * 特权人-直接废弃 + */ + ADMIN_ABANDON("admin_abandon", "特权人-直接废弃"), + ; + /** + * 编码 + */ + private final String code; + /** + * 名称 + */ + private final String name; + + /** + * 流程结束,mk没有这个操作,但是有这个事件,自定义一个操作类型,统一管理 + */ + public static final String PROCESS_FINISH = "erp_process_finish"; + + /** + * 根据操作编码获取名称 + * @param code + * @return + */ + public static String getOperationName(String code) { + return Stream.of(values()) + .filter(type -> type.getCode().equals(code)) + .findFirst() + .map(ProcessOperationType::getName) + .orElse(null); + } +} diff --git a/blade-service-api/blade-open-api/src/main/java/org/springblade/openapi/ocr/api/IApi4OCR.java b/blade-service-api/blade-open-api/src/main/java/org/springblade/openapi/ocr/api/IApi4OCR.java new file mode 100644 index 0000000..ac19222 --- /dev/null +++ b/blade-service-api/blade-open-api/src/main/java/org/springblade/openapi/ocr/api/IApi4OCR.java @@ -0,0 +1,36 @@ +package org.springblade.openapi.ocr.api; + +import org.springblade.core.tool.api.R; +import org.springblade.openapi.common.vo.PageVO; +import org.springblade.openapi.ocr.pojo.dto.Api4OCRProjectDTO; +import org.springblade.openapi.ocr.pojo.dto.Api4OCRResultDTO; +import org.springblade.openapi.ocr.pojo.vo.Api4OCRProjectVO; +import org.springblade.openapi.ocr.pojo.vo.Api4OCRResultVO; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; + +/** + * @author bfhuange + * @since 2025/04/22 + */ +public interface IApi4OCR { + String API_PREFIX = "/openApi/ocr"; + String PROJECT_LIST = API_PREFIX + "/queryProjectList"; + String OCR_RESULT_LIST = API_PREFIX + "/queryOCRResultList"; + + /** + * 查询项目列表 + * @param param + * @return + */ + @PostMapping(PROJECT_LIST) + R> queryProjectList(@RequestBody(required = false) Api4OCRProjectDTO param); + + /** + * 查询凭证ocr识别结果列表 + * @param param + * @return + */ + @PostMapping(OCR_RESULT_LIST) + R> queryOCRResultList(@RequestBody(required = false) Api4OCRResultDTO param); +} diff --git a/blade-service-api/blade-open-api/src/main/java/org/springblade/openapi/ocr/pojo/dto/Api4OCRProjectDTO.java b/blade-service-api/blade-open-api/src/main/java/org/springblade/openapi/ocr/pojo/dto/Api4OCRProjectDTO.java new file mode 100644 index 0000000..2c3d618 --- /dev/null +++ b/blade-service-api/blade-open-api/src/main/java/org/springblade/openapi/ocr/pojo/dto/Api4OCRProjectDTO.java @@ -0,0 +1,24 @@ +package org.springblade.openapi.ocr.pojo.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.io.Serial; + +/** + * @author bfhuange + * @since 2025/4/22 + */ +@EqualsAndHashCode(callSuper = true) +@Data +@Schema(description = "提供给ocr的项目信息") +public class Api4OCRProjectDTO extends PageQuery { + @Serial + private static final long serialVersionUID = 1L; + /** + * 关键字,项目名称或简称模糊查询 + */ + @Schema(description = "关键字,项目名称或简称模糊查询") + private String keyword; +} diff --git a/blade-service-api/blade-open-api/src/main/java/org/springblade/openapi/ocr/pojo/dto/Api4OCRResultDTO.java b/blade-service-api/blade-open-api/src/main/java/org/springblade/openapi/ocr/pojo/dto/Api4OCRResultDTO.java new file mode 100644 index 0000000..c45f37e --- /dev/null +++ b/blade-service-api/blade-open-api/src/main/java/org/springblade/openapi/ocr/pojo/dto/Api4OCRResultDTO.java @@ -0,0 +1,62 @@ +package org.springblade.openapi.ocr.pojo.dto; + +import com.fasterxml.jackson.annotation.JsonFormat; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.springblade.core.tool.utils.DateUtil; +import org.springframework.format.annotation.DateTimeFormat; + +import java.io.Serial; +import java.util.Date; + +/** + * @author bfhuange + * @since 2025/4/22 + */ +@EqualsAndHashCode(callSuper = true) +@Data +@Schema(description = "凭证ocr结果查询参数") +public class Api4OCRResultDTO extends PageQuery { + @Serial + private static final long serialVersionUID = 1L; + /** + * 项目id + */ + @Schema(description = "项目id,项目id、凭证批次号、凭证号至少一个不为空") + private Long projectId; + /** + * 凭证批次号 + */ + @Schema(description = "凭证批次号,项目id、凭证批次号、凭证号至少一个不为空") + private String batchCode; + /** + * 凭证号 + */ + @Schema(description = "凭证号,项目id、凭证批次号、凭证号至少一个不为空") + private String detailCode; + /** + * 是否类型错误(1 否 2 是) + */ + @Schema(description = "是否类型错误(1 否 2 是)") + private Integer isTypeError; + /** + * 是否数据错误(1 否 2 是) + */ + @Schema(description = "是否数据错误(1 否 2 是)") + private Integer isDataError; + /** + * 创建时间始 + */ + @DateTimeFormat(pattern = DateUtil.PATTERN_DATETIME) + @JsonFormat(pattern = DateUtil.PATTERN_DATETIME, timezone = "GMT+8") + @Schema(description = "创建时间始,格式yyyy-MM-dd HH:mm:ss") + private Date createTimeStart; + /** + * 创建时间至 + */ + @DateTimeFormat(pattern = DateUtil.PATTERN_DATETIME) + @JsonFormat(pattern = DateUtil.PATTERN_DATETIME, timezone = "GMT+8") + @Schema(description = "创建时间至,格式yyyy-MM-dd HH:mm:ss") + private Date createTimeEnd; +} diff --git a/blade-service-api/blade-open-api/src/main/java/org/springblade/openapi/ocr/pojo/dto/PageQuery.java b/blade-service-api/blade-open-api/src/main/java/org/springblade/openapi/ocr/pojo/dto/PageQuery.java new file mode 100644 index 0000000..2fe2e51 --- /dev/null +++ b/blade-service-api/blade-open-api/src/main/java/org/springblade/openapi/ocr/pojo/dto/PageQuery.java @@ -0,0 +1,41 @@ +package org.springblade.openapi.ocr.pojo.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 分页查询参数 + * @author bfhuange + * @since 2025/4/22 + */ +@Data +@Schema(description = "分页查询参数") +public class PageQuery implements Serializable { + @Serial + private static final long serialVersionUID = 1L; + /** + * 每页最大数量 200 + */ + public static final int MAX_SIZE = 200; + /** + * 当前页 + */ + @Schema(description = "当前页,默认1") + protected Integer current = 1; + + /** + * 每页的数量 + */ + @Schema(description = "每页的数量,默认10,超过200会修改为200") + protected Integer size = 10; + + public void setSize(Integer size) { + if (size != null && size > MAX_SIZE) { + size = MAX_SIZE; + } + this.size = size; + } +} diff --git a/blade-service-api/blade-open-api/src/main/java/org/springblade/openapi/ocr/pojo/vo/Api4OCRProjectVO.java b/blade-service-api/blade-open-api/src/main/java/org/springblade/openapi/ocr/pojo/vo/Api4OCRProjectVO.java new file mode 100644 index 0000000..c10d1ed --- /dev/null +++ b/blade-service-api/blade-open-api/src/main/java/org/springblade/openapi/ocr/pojo/vo/Api4OCRProjectVO.java @@ -0,0 +1,56 @@ +package org.springblade.openapi.ocr.pojo.vo; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import org.springblade.core.tool.utils.DateUtil; +import org.springframework.format.annotation.DateTimeFormat; + +import java.io.Serial; +import java.io.Serializable; +import java.util.Date; + +/** + * @author bfhuange + * @since 2025/4/22 + */ +@Data +@Schema(description = "提供给ocr的项目信息") +public class Api4OCRProjectVO implements Serializable { + @Serial + private static final long serialVersionUID = 1L; + /** + * 项目id + */ + @JsonSerialize(using = ToStringSerializer.class) + @Schema(description = "项目id") + private Long id; + /** + * 项目名称 + */ + @Schema(description = "项目名称") + private String name; + /** + * 项目简称 + */ + @Schema(description = "项目简称") + private String abbreviation; + + /** + * 创建时间 + */ + @DateTimeFormat(pattern = DateUtil.PATTERN_DATETIME) + @JsonFormat(pattern = DateUtil.PATTERN_DATETIME) + @Schema(description = "创建时间") + private Date createTime; + + /** + * 更新时间 + */ + @DateTimeFormat(pattern = DateUtil.PATTERN_DATETIME) + @JsonFormat(pattern = DateUtil.PATTERN_DATETIME) + @Schema(description = "更新时间") + private Date updateTime; +} diff --git a/blade-service-api/blade-open-api/src/main/java/org/springblade/openapi/ocr/pojo/vo/Api4OCRResultVO.java b/blade-service-api/blade-open-api/src/main/java/org/springblade/openapi/ocr/pojo/vo/Api4OCRResultVO.java new file mode 100644 index 0000000..4cad15b --- /dev/null +++ b/blade-service-api/blade-open-api/src/main/java/org/springblade/openapi/ocr/pojo/vo/Api4OCRResultVO.java @@ -0,0 +1,103 @@ +package org.springblade.openapi.ocr.pojo.vo; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.databind.ser.std.NullSerializer; +import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import org.springblade.core.tool.utils.DateUtil; +import org.springblade.openapi.ocr.pojo.vo.result.AttachmentType; +import org.springblade.openapi.ocr.pojo.vo.result.ErrorAttachmentType; +import org.springblade.openapi.ocr.pojo.vo.result.ErrorField; +import org.springblade.openapi.ocr.pojo.vo.result.TransportCertificateBatchDetailData; +import org.springframework.format.annotation.DateTimeFormat; + +import java.io.Serial; +import java.io.Serializable; +import java.util.Date; +import java.util.List; + +/** + * @author bfhuange + * @since 2025/4/22 + */ +@Data +@Schema(description = "凭证ocr结果") +public class Api4OCRResultVO implements Serializable { + @Serial + private static final long serialVersionUID = 1L; + + /** + * 主键 + */ + @JsonSerialize(using = ToStringSerializer.class, nullsUsing = NullSerializer.class) + @Schema(description = "主键") + private Long id; + + /** + * 凭证号 + */ + @Schema(description = "凭证号") + private String code; + + /** + * 识别类型(batch 批量识别,mixed 单张识别合并) + */ + @Schema(description = "识别类型(batch 批量识别,mixed 单张识别合并)") + private String identifyType; + + /** + * 是否类型错误(1 否 2 是) + */ + @JsonSerialize(nullsUsing = NullSerializer.class) + @Schema(description = "是否类型错误(1 否 2 是)") + private Integer isTypeError; + + /** + * 是否数据错误(1 否 2 是) + */ + @JsonSerialize(nullsUsing = NullSerializer.class) + @Schema(description = "是否数据错误(1 否 2 是)") + private Integer isDataError; + + /** + * 附件类型列表 + */ + @Schema(description = "附件类型列表") + private List attachmentTypeList; + + /** + * 数据 + */ + @Schema(description = "数据") + private TransportCertificateBatchDetailData data; + + /** + * 错误类型列表 + */ + @Schema(description = "错误类型列表") + private List errorTypeList; + + /** + * 错误字段列表 + */ + @Schema(description = "错误字段列表") + private List errorFieldList; + + /** + * 错误字段列表 + */ + @DateTimeFormat(pattern = DateUtil.PATTERN_DATETIME) + @JsonFormat(pattern = DateUtil.PATTERN_DATETIME) + @Schema(description = "创建时间") + private Date createTime; + + /** + * 更新时间 + */ + @DateTimeFormat(pattern = DateUtil.PATTERN_DATETIME) + @JsonFormat(pattern = DateUtil.PATTERN_DATETIME) + @Schema(description = "更新时间") + private Date updateTime; +} diff --git a/blade-service-api/blade-open-api/src/main/java/org/springblade/openapi/ocr/pojo/vo/result/AttachmentType.java b/blade-service-api/blade-open-api/src/main/java/org/springblade/openapi/ocr/pojo/vo/result/AttachmentType.java new file mode 100644 index 0000000..c01c4e2 --- /dev/null +++ b/blade-service-api/blade-open-api/src/main/java/org/springblade/openapi/ocr/pojo/vo/result/AttachmentType.java @@ -0,0 +1,40 @@ +package org.springblade.openapi.ocr.pojo.vo.result; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 附件类型 + * @author bfhuange + * @since 2025/4/18 + */ +@Data +@Schema(description = "附件类型") +public class AttachmentType implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + /** + * 附件id + */ + @JsonSerialize(using = ToStringSerializer.class) + @Schema(description = "附件id") + private Long attachmentId; + /** + * ocr附件类型 + */ + @Schema(description = "ocr附件类型") + private String ocrType; + /** + * 附件url + */ + @JsonInclude(JsonInclude.Include.NON_EMPTY) + @Schema(description = "附件url") + private String url; +} diff --git a/blade-service-api/blade-open-api/src/main/java/org/springblade/openapi/ocr/pojo/vo/result/ErrorAttachmentType.java b/blade-service-api/blade-open-api/src/main/java/org/springblade/openapi/ocr/pojo/vo/result/ErrorAttachmentType.java new file mode 100644 index 0000000..8340781 --- /dev/null +++ b/blade-service-api/blade-open-api/src/main/java/org/springblade/openapi/ocr/pojo/vo/result/ErrorAttachmentType.java @@ -0,0 +1,25 @@ +package org.springblade.openapi.ocr.pojo.vo.result; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.io.Serial; + +/** + * 错误附件类型 + * @author bfhuange + * @since 2025/4/21 + */ +@EqualsAndHashCode(callSuper = true) +@Data +@Schema(description = "错误附件类型") +public class ErrorAttachmentType extends AttachmentType { + @Serial + private static final long serialVersionUID = 1L; + /** + * erp附件类型 + */ + @Schema(description = "erp附件类型") + private String erpType; +} diff --git a/blade-service-api/blade-open-api/src/main/java/org/springblade/openapi/ocr/pojo/vo/result/ErrorField.java b/blade-service-api/blade-open-api/src/main/java/org/springblade/openapi/ocr/pojo/vo/result/ErrorField.java new file mode 100644 index 0000000..dba828a --- /dev/null +++ b/blade-service-api/blade-open-api/src/main/java/org/springblade/openapi/ocr/pojo/vo/result/ErrorField.java @@ -0,0 +1,45 @@ +package org.springblade.openapi.ocr.pojo.vo.result; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; +import java.util.List; + +/** + * 错误字段 + * @author bfhuange + * @since 2025/4/21 + */ +@Data +@Schema(description = "错误字段") +public class ErrorField implements Serializable { + @Serial + private static final long serialVersionUID = 1L; + /** + * 字段名 + */ + @Schema(description = "字段名") + private String filed; + /** + * ocr类型 + */ + @Schema(description = "ocr类型") + private String ocrType; + /** + * ocr字段值 + */ + @Schema(description = "ocr字段值") + private String ocrValue; + /** + * erp字段值 + */ + @Schema(description = "erp字段值") + private String erpValue; + /** + * 附件列表 + */ + @Schema(description = "附件列表") + private List attachmentList; +} diff --git a/blade-service-api/blade-open-api/src/main/java/org/springblade/openapi/ocr/pojo/vo/result/TransportCertificateBatchDetailData.java b/blade-service-api/blade-open-api/src/main/java/org/springblade/openapi/ocr/pojo/vo/result/TransportCertificateBatchDetailData.java new file mode 100644 index 0000000..9ae2488 --- /dev/null +++ b/blade-service-api/blade-open-api/src/main/java/org/springblade/openapi/ocr/pojo/vo/result/TransportCertificateBatchDetailData.java @@ -0,0 +1,94 @@ +package org.springblade.openapi.ocr.pojo.vo.result; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.databind.ser.std.NullSerializer; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import org.springblade.core.tool.utils.DateUtil; + +import java.io.Serial; +import java.io.Serializable; +import java.math.BigDecimal; +import java.util.Date; + +/** + * 车辆运输凭证批次明细表 实体类 + * + * @author BladeX + * @since 2024-10-23 + */ +@Data +@Schema(description = "凭证数据对象") +public class TransportCertificateBatchDetailData implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + /** + * 磅单车牌号 + */ + @Schema(description = "磅单车牌号") + private String poundPlateNo; + /** + * 行驶证上的车牌号 + */ + @Schema(description = "行驶证上的车牌号") + private String drivingPlateNo; + /** + * 车头车牌号 + */ + @Schema(description = "车头车牌号") + private String carFrontPlateNo; + /** + * 到货日期 + */ + @JsonFormat(pattern = DateUtil.PATTERN_DATE, timezone = "GMT+8") + @Schema(description = "到货日期") + private Date supplyMaterialDate; + /** + * 货物名称 + */ + @Schema(description = "货物名称") + private String productName; + /** + * 规格料型 + */ + @Schema(description = "规格料型") + private String specificationMaterial; + /** + * 毛重(吨) + */ + @JsonSerialize(nullsUsing = NullSerializer.class) + @Schema(description = "毛重(吨)") + private BigDecimal grossWeight; + /** + * 皮重(吨) + */ + @JsonSerialize(nullsUsing = NullSerializer.class) + @Schema(description = "皮重(吨)") + private BigDecimal skinWeight; + /** + * 净重(吨) + */ + @JsonSerialize(nullsUsing = NullSerializer.class) + @Schema(description = "净重(吨)") + private BigDecimal netWeight; + /** + * 扣杂(吨) + */ + @JsonSerialize(nullsUsing = NullSerializer.class) + @Schema(description = "扣杂(吨)") + private BigDecimal miscellaneousWeight; + /** + * 实收(吨) + */ + @JsonSerialize(nullsUsing = NullSerializer.class) + @Schema(description = "实收(吨)") + private BigDecimal realityWeight; + /** + * 驾驶证是否有效(1 否 2 是) + */ + @JsonSerialize(nullsUsing = NullSerializer.class) + @Schema(description = "驾驶证是否有效(1 否 2 是)") + private Integer isDriverLicenseValid; +} diff --git a/blade-service-api/blade-open-api/src/main/java/org/springblade/openapi/track/api/IApi4Track.java b/blade-service-api/blade-open-api/src/main/java/org/springblade/openapi/track/api/IApi4Track.java new file mode 100644 index 0000000..57d6477 --- /dev/null +++ b/blade-service-api/blade-open-api/src/main/java/org/springblade/openapi/track/api/IApi4Track.java @@ -0,0 +1,40 @@ +package org.springblade.openapi.track.api; + +import org.springblade.openapi.track.pojo.dto.Api4TrackWaybillDTO; +import org.springblade.openapi.track.pojo.vo.Api4TrackResultVO; +import org.springblade.openapi.track.pojo.vo.Api4TrackWaybillVO; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestParam; + +import java.util.List; + +/** + * @author bfhuange + * @since 2024/12/16 + */ +public interface IApi4Track { + String API_PREFIX = "/openApi/gj"; + String WAYBILL_LIST = API_PREFIX + "/mycode/zhcx/getZhcx"; + String WAYBILL_TRACK_UPDATE = API_PREFIX + "/mycode/zhcx/callback"; + + /** + * 轨迹查询运单信息 + * + * @param param@return + */ + @PostMapping(WAYBILL_LIST) + Api4TrackResultVO> queryWaybillList(@RequestBody Api4TrackWaybillDTO param); + + /** + * 轨迹更新运单轨迹url和状态 + * @param waybillId + * @param trajectoryId + * @param trajectoryStatus + * @return + */ + @PostMapping(WAYBILL_TRACK_UPDATE) + Api4TrackResultVO updateWaybillTrack(@RequestParam(name = "zhcxId", required = false) String waybillId, + @RequestParam(name = "diTuUrl", required = false) String trajectoryId, + @RequestParam(name = "guiJiZhuangTai", required = false) String trajectoryStatus); +} diff --git a/blade-service-api/blade-open-api/src/main/java/org/springblade/openapi/track/constant/TrackConstant.java b/blade-service-api/blade-open-api/src/main/java/org/springblade/openapi/track/constant/TrackConstant.java new file mode 100644 index 0000000..554e785 --- /dev/null +++ b/blade-service-api/blade-open-api/src/main/java/org/springblade/openapi/track/constant/TrackConstant.java @@ -0,0 +1,17 @@ +package org.springblade.openapi.track.constant; + +/** + * 轨迹常量 + * @author bfhuange + * @since 2024/12/16 + */ +public class TrackConstant { + /** + * 响应成功状态码 + */ + public static final Integer SUCCESS_CODE = 200; + /** + * 响应失败状态码 + */ + public static final Integer FAIL_CODE = 500; +} diff --git a/blade-service-api/blade-open-api/src/main/java/org/springblade/openapi/track/pojo/dto/Api4TrackWaybillDTO.java b/blade-service-api/blade-open-api/src/main/java/org/springblade/openapi/track/pojo/dto/Api4TrackWaybillDTO.java new file mode 100644 index 0000000..e3ca7f3 --- /dev/null +++ b/blade-service-api/blade-open-api/src/main/java/org/springblade/openapi/track/pojo/dto/Api4TrackWaybillDTO.java @@ -0,0 +1,22 @@ +package org.springblade.openapi.track.pojo.dto; + +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; +import java.util.List; + +/** + * @author bfhuange + * @since 2024/12/16 + */ +@Data +public class Api4TrackWaybillDTO implements Serializable { + @Serial + private static final long serialVersionUID = 1L; + + /** + * 运单id列表 + */ + private List ids; +} diff --git a/blade-service-api/blade-open-api/src/main/java/org/springblade/openapi/track/pojo/dto/Api4TrackWaybillUpdateDTO.java b/blade-service-api/blade-open-api/src/main/java/org/springblade/openapi/track/pojo/dto/Api4TrackWaybillUpdateDTO.java new file mode 100644 index 0000000..db43fef --- /dev/null +++ b/blade-service-api/blade-open-api/src/main/java/org/springblade/openapi/track/pojo/dto/Api4TrackWaybillUpdateDTO.java @@ -0,0 +1,33 @@ +package org.springblade.openapi.track.pojo.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; + +/** + * @author bfhuange + * @since 2024/12/16 + */ +@Data +public class Api4TrackWaybillUpdateDTO implements Serializable { + @Serial + private static final long serialVersionUID = 1L; + + /** + * 运单id + */ + @JsonProperty("zhcxId") + private String waybillId; + /** + * 轨迹id + */ + @JsonProperty("diTuUrl") + private String trajectoryId; + /** + * 轨迹状态 + */ + @JsonProperty("guiJiZhuangTai") + private String trajectoryStatus; +} diff --git a/blade-service-api/blade-open-api/src/main/java/org/springblade/openapi/track/pojo/vo/Api4TrackResultVO.java b/blade-service-api/blade-open-api/src/main/java/org/springblade/openapi/track/pojo/vo/Api4TrackResultVO.java new file mode 100644 index 0000000..6c9a929 --- /dev/null +++ b/blade-service-api/blade-open-api/src/main/java/org/springblade/openapi/track/pojo/vo/Api4TrackResultVO.java @@ -0,0 +1,50 @@ +package org.springblade.openapi.track.pojo.vo; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.springblade.openapi.track.constant.TrackConstant; + +import java.io.Serial; +import java.io.Serializable; + +/** + * @author bfhuange + * @since 2024/12/16 + */ +@AllArgsConstructor +@NoArgsConstructor +@Data +public class Api4TrackResultVO implements Serializable { + @Serial + private static final long serialVersionUID = 1L; + /** + * 响应代码。200为正确 + */ + private Integer state; + /** + * 对应的提示信息 + */ + private String message; + /** + * 数据 + */ + private T data; + + public Api4TrackResultVO(Integer state, T data) { + this.state = state; + this.data = data; + } + + public static Api4TrackResultVO success() { + return Api4TrackResultVO.success(null); + } + + public static Api4TrackResultVO fail(String message) { + return new Api4TrackResultVO<>(TrackConstant.FAIL_CODE, message, null); + } + + public static Api4TrackResultVO success(T data) { + return new Api4TrackResultVO<>(TrackConstant.SUCCESS_CODE, data); + } +} diff --git a/blade-service-api/blade-open-api/src/main/java/org/springblade/openapi/track/pojo/vo/Api4TrackWaybillVO.java b/blade-service-api/blade-open-api/src/main/java/org/springblade/openapi/track/pojo/vo/Api4TrackWaybillVO.java new file mode 100644 index 0000000..5dc24ff --- /dev/null +++ b/blade-service-api/blade-open-api/src/main/java/org/springblade/openapi/track/pojo/vo/Api4TrackWaybillVO.java @@ -0,0 +1,86 @@ +package org.springblade.openapi.track.pojo.vo; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import org.springblade.core.tool.utils.DateUtil; +import org.springframework.format.annotation.DateTimeFormat; + +import java.io.Serial; +import java.io.Serializable; +import java.math.BigDecimal; +import java.util.Date; + +/** + * @author bfhuange + * @since 2024/12/16 + */ +@Data +public class Api4TrackWaybillVO implements Serializable { + @Serial + private static final long serialVersionUID = 1L; + /** + * 运单id + */ + private String id; + /** + * 收货日期 + */ + @DateTimeFormat(pattern = DateUtil.PATTERN_DATE) + @JsonFormat(pattern = DateUtil.PATTERN_DATE) + @JsonProperty("shouHuoRiQi") + private Date enterDate; + /** + * 车牌号 + */ + @JsonProperty("chePaiHao") + private String plateNo; + /** + * 钢厂简称 + */ + @JsonProperty("gangChangJianCheng") + private String customerAbbreviation; + /** + * 钢厂合同编号 + */ + @JsonIgnore + private String contractGcCode; + + //===========================设置默认值的参数========================== + /** + * 托运人 + */ + @JsonProperty("tuoYunRen") + private String shipper = "托运人"; + /** + * 收货人 + */ + @JsonProperty("shouHuoRen") + private String consignee = "收货人"; + /** + * 收货地址 + */ + @JsonProperty("shouHuoDiZhi") + private String address = "收货地址"; + /** + * 投送人 + */ + @JsonProperty("touSongRen") + private String delivery = "投送人"; + /** + * 货物名称 + */ + @JsonProperty("huoWuMingCheng") + private String goodsName = "货物名称"; + /** + * 重量 + */ + @JsonProperty("zhongLiang") + private BigDecimal weight = BigDecimal.ZERO; + /** + * 事业部 + */ + @JsonProperty("shiYeBu") + private String division = "事业部"; +} diff --git a/blade-service-api/blade-open-api/src/main/java/org/springblade/openapi/wps/api/IApi4Wps.java b/blade-service-api/blade-open-api/src/main/java/org/springblade/openapi/wps/api/IApi4Wps.java new file mode 100644 index 0000000..4fb82ed --- /dev/null +++ b/blade-service-api/blade-open-api/src/main/java/org/springblade/openapi/wps/api/IApi4Wps.java @@ -0,0 +1,50 @@ +package org.springblade.openapi.wps.api; + +import org.springblade.openapi.wps.pojo.dto.Api4WpsFileInfoDTO; +import org.springblade.openapi.wps.pojo.vo.Api4WpsFileInfoVO; +import org.springframework.cloud.openfeign.SpringQueryMap; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestHeader; +import org.springframework.web.bind.annotation.RequestPart; +import org.springframework.web.multipart.MultipartFile; + +/** + * 给wps回调的api + * @author bfhuange + * @date 2024/10/30 + */ +public interface IApi4Wps { + String API_PREFIX = "/openApi/wps"; + String VERSION = "/v1"; + String FILE_INFO = API_PREFIX + VERSION + "/3rd/file/info"; + String FILE_SAVE = API_PREFIX + VERSION + "/3rd/file/save"; + + /** + * wps回调查询文件信息 + * + * @param fileId 文件id,必填 + * @param token 非必填 + * @param param 在获取预览链接时按需求传入的所有自定义参数 + * @return + */ + @GetMapping(FILE_INFO) + Api4WpsFileInfoVO getFileInfo(@RequestHeader("X-Weboffice-File-Id") String fileId, + @RequestHeader(value = "X-Wps-Weboffice-Token", required = false) String token, + @SpringQueryMap Api4WpsFileInfoDTO param); + + /** + * wps回调-上传文件新版本 + * + * @param fileId 文件id,必填 + * @param token 非必填 + * @param param 在获取预览链接时按需求传入的所有自定义参数 + * @return + */ + @PostMapping(FILE_SAVE) + Api4WpsFileInfoVO getFileSave(@RequestHeader("X-Weboffice-File-Id") String fileId, + @RequestHeader(value = "X-Weboffice-Save-Type") String saveType, + @RequestHeader(value = "X-Wps-Weboffice-Token", required = false) String token, + @RequestPart("file") MultipartFile file, + @SpringQueryMap Api4WpsFileInfoDTO param); +} diff --git a/blade-service-api/blade-open-api/src/main/java/org/springblade/openapi/wps/constant/WpsConstant.java b/blade-service-api/blade-open-api/src/main/java/org/springblade/openapi/wps/constant/WpsConstant.java new file mode 100644 index 0000000..2276279 --- /dev/null +++ b/blade-service-api/blade-open-api/src/main/java/org/springblade/openapi/wps/constant/WpsConstant.java @@ -0,0 +1,21 @@ +package org.springblade.openapi.wps.constant; + +/** + * wps常量类 + * @author bfhuange + * @date 2024/10/30 + */ +public class WpsConstant { + /** + * 是 + */ + public static final Integer YES = 1; + /** + * 否 + */ + public static final Integer NO = 0; + /** + * 文件信息查询默认缓存时间 120 秒 + */ + public static final String FILE_INFO_DEFAULT_CACHE_TIME = "120"; +} diff --git a/blade-service-api/blade-open-api/src/main/java/org/springblade/openapi/wps/pojo/dto/Api4WpsFileInfoDTO.java b/blade-service-api/blade-open-api/src/main/java/org/springblade/openapi/wps/pojo/dto/Api4WpsFileInfoDTO.java new file mode 100644 index 0000000..e386e36 --- /dev/null +++ b/blade-service-api/blade-open-api/src/main/java/org/springblade/openapi/wps/pojo/dto/Api4WpsFileInfoDTO.java @@ -0,0 +1,17 @@ +package org.springblade.openapi.wps.pojo.dto; + +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; + +/** + * wps文件信息查询参数 + * @author bfhuange + * @date 2024/10/30 + */ +@Data +public class Api4WpsFileInfoDTO implements Serializable { + @Serial + private static final long serialVersionUID = 1L; +} diff --git a/blade-service-api/blade-open-api/src/main/java/org/springblade/openapi/wps/pojo/enums/Api4WpsError.java b/blade-service-api/blade-open-api/src/main/java/org/springblade/openapi/wps/pojo/enums/Api4WpsError.java new file mode 100644 index 0000000..95fe34b --- /dev/null +++ b/blade-service-api/blade-open-api/src/main/java/org/springblade/openapi/wps/pojo/enums/Api4WpsError.java @@ -0,0 +1,60 @@ +package org.springblade.openapi.wps.pojo.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * wps 回调接口错误枚举 + * @author bfhuange + * @since 2024/11/26 + */ +@AllArgsConstructor +@Getter +public enum Api4WpsError { + /** + * 服务处理正常 + */ + OK(0, "OK"), + /** + * token过期 + */ + SESSION_EXPIRED(40002, "SessionExpired"), + /** + * 用户无权限访问 + */ + PERMISSION_DENIED(40003, "PermissionDenied"), + /** + * 资源不存在 + */ + NOT_EXISTS(40004, "NotExists"), + /** + * 参数错误 + */ + INVALID_ARGUMENT(40005, "InvalidArgument"), + /** + * 保存空间已满 + */ + SPACE_FULL(40006, "SpaceFull"), + /** + * 自定义错误提示,前端页面将显示此错误内容,在回调错误响应中的hint字段中自定义错误提示,该错误提示不支持多语言 + */ + CUSTOM_MSG(40007, "CustomMsg"), + /** + * 文件重命名冲突 + */ + FNAME_CONFLICT(40008, "FnameConflict"), + /** + * 对接系统错误 + */ + SERVER_ERROR(50001, "ServerError"), + ; + + /** + * 错误码 + */ + private final Integer code; + /** + * 信息 + */ + private final String message; +} diff --git a/blade-service-api/blade-open-api/src/main/java/org/springblade/openapi/wps/pojo/vo/Api4WpsAttrsVO.java b/blade-service-api/blade-open-api/src/main/java/org/springblade/openapi/wps/pojo/vo/Api4WpsAttrsVO.java new file mode 100644 index 0000000..19e4356 --- /dev/null +++ b/blade-service-api/blade-open-api/src/main/java/org/springblade/openapi/wps/pojo/vo/Api4WpsAttrsVO.java @@ -0,0 +1,27 @@ +package org.springblade.openapi.wps.pojo.vo; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serial; +import java.io.Serializable; + +/** + * wps附加参数 + * @author bfhuange + * @date 2024/10/30 + */ +@AllArgsConstructor +@NoArgsConstructor +@Data +public class Api4WpsAttrsVO implements Serializable { + @Serial + private static final long serialVersionUID = 1L; + /** + * 单位秒,取值范围为1-300,超过300秒不会缓存回调信息,此字段需服务端全局配置开启缓存,且需要使用token访问预览链接,非必填 + */ + @JsonProperty("cachetime") + private String cacheTime; +} diff --git a/blade-service-api/blade-open-api/src/main/java/org/springblade/openapi/wps/pojo/vo/Api4WpsErrorVO.java b/blade-service-api/blade-open-api/src/main/java/org/springblade/openapi/wps/pojo/vo/Api4WpsErrorVO.java new file mode 100644 index 0000000..993cb74 --- /dev/null +++ b/blade-service-api/blade-open-api/src/main/java/org/springblade/openapi/wps/pojo/vo/Api4WpsErrorVO.java @@ -0,0 +1,41 @@ +package org.springblade.openapi.wps.pojo.vo; + +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serial; +import java.io.Serializable; + +/** + * wps回调接口错误 + * @author bfhuange + * @since 2024/11/26 + */ +@NoArgsConstructor +@Data +public class Api4WpsErrorVO implements Serializable { + @Serial + private static final long serialVersionUID = 1L; + /** + * 错误码 + */ + private Integer code; + /** + * 错误提示 + */ + private String message; + /** + * 错误提示详细信息 + */ + private String details; + /** + * 自定义错误信息,当code为40007错误码时必填 + */ + private String hint; + + public Api4WpsErrorVO(Integer code, String message, String hint) { + this.code = code; + this.message = message; + this.hint = hint; + } +} diff --git a/blade-service-api/blade-open-api/src/main/java/org/springblade/openapi/wps/pojo/vo/Api4WpsFileInfoVO.java b/blade-service-api/blade-open-api/src/main/java/org/springblade/openapi/wps/pojo/vo/Api4WpsFileInfoVO.java new file mode 100644 index 0000000..2f80c8c --- /dev/null +++ b/blade-service-api/blade-open-api/src/main/java/org/springblade/openapi/wps/pojo/vo/Api4WpsFileInfoVO.java @@ -0,0 +1,34 @@ +package org.springblade.openapi.wps.pojo.vo; + +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.databind.ser.std.NullSerializer; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; + +/** + * wps文件信息查询结果 + * @author bfhuange + * @date 2024/10/30 + */ +@Data +public class Api4WpsFileInfoVO implements Serializable { + @Serial + private static final long serialVersionUID = 1L; + /** + * 文件信息,必填 + */ + @JsonSerialize(nullsUsing = NullSerializer.class) + private Api4WpsFileVO file; + /** + * 用户信息,必填 + */ + @JsonSerialize(nullsUsing = NullSerializer.class) + private Api4WpsUserVO user; + /** + * 附加参数,v6.0.2209.20220920新增 + */ + @JsonSerialize(nullsUsing = NullSerializer.class) + private Api4WpsAttrsVO attrs; +} diff --git a/blade-service-api/blade-open-api/src/main/java/org/springblade/openapi/wps/pojo/vo/Api4WpsFileVO.java b/blade-service-api/blade-open-api/src/main/java/org/springblade/openapi/wps/pojo/vo/Api4WpsFileVO.java new file mode 100644 index 0000000..450a890 --- /dev/null +++ b/blade-service-api/blade-open-api/src/main/java/org/springblade/openapi/wps/pojo/vo/Api4WpsFileVO.java @@ -0,0 +1,83 @@ +package org.springblade.openapi.wps.pojo.vo; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.databind.ser.std.NullSerializer; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; + +/** + * wps文件结果 + * @author bfhuange + * @date 2024/10/30 + */ +@Data +public class Api4WpsFileVO implements Serializable { + @Serial + private static final long serialVersionUID = 1L; + /** + * 文件id,字符串长度不超过64位,必填 + */ + private String id; + /** + * 文件名必须带后缀,必填 + */ + private String name; + /** + * 文档版本号,从1开始累加,位数小于11,必填 + */ + private Integer version = 1; + /** + * 文档大小,单位为字节;此处需传文件真实大小,否则会出现异常,必填 + */ + private Integer size; + /** + * readonly默认为false + * true开启表格筛选,支持对EXCEL表格文档筛选,但不支持对EXCEL表格文档开启多人同步筛选,该预览特性见常见问题 + * false关闭表格筛选(20220707新增),非必填 + */ + private Boolean readonly = true; + /** + * 创建者id,字符串长度不超过32位。与user里的id一致,必填 + */ + private String creator; + /** + * 创建时间,时间戳,单位为秒,必填 + */ + @JsonProperty("create_time") + private Integer createTime; + /** + * 修改者id,字符串长度不超过32位,必填 + */ + private String modifier; + /** + * 最近修改时间,时间戳,单位为秒,必填 + */ + @JsonProperty("modify_time") + private Integer modifyTime; + /** + * 文档下载地址,必填 + */ + @JsonProperty("download_url") + private String downloadUrl; + /** + * 普通预览模式下可限制预览页数(不超过5000) + * previewPages 默认值为 0,不限制预览页数; + * previewPages >= 1 时,限制生效,限制的页数为 previewpages 字段的值,必填 + */ + @JsonProperty("preview_pages") + private Integer previewPages = 0; + /** + * 用户权限,非必填 + */ + @JsonProperty("user_acl") + @JsonSerialize(nullsUsing = NullSerializer.class) + private Api4WpsUserAclVO userAcl; + /** + * 水印参数,非必填 + */ + @JsonSerialize(nullsUsing = NullSerializer.class) + private Api4WpsWatermarkVO watermark; +} diff --git a/blade-service-api/blade-open-api/src/main/java/org/springblade/openapi/wps/pojo/vo/Api4WpsUserAclVO.java b/blade-service-api/blade-open-api/src/main/java/org/springblade/openapi/wps/pojo/vo/Api4WpsUserAclVO.java new file mode 100644 index 0000000..d1371a7 --- /dev/null +++ b/blade-service-api/blade-open-api/src/main/java/org/springblade/openapi/wps/pojo/vo/Api4WpsUserAclVO.java @@ -0,0 +1,37 @@ +package org.springblade.openapi.wps.pojo.vo; + +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; + +/** + * wps用户权限 + * @author bfhuange + * @date 2024/10/30 + */ +@Data +public class Api4WpsUserAclVO implements Serializable { + @Serial + private static final long serialVersionUID = 1L; + /** + * 重命名权限,1为打开该权限,0为关闭该权限,默认为0,非必填 + */ + private Integer rename; + /** + * 历史文件打开权限,1为打开该权限,0为关闭该权限,默认为1,非必填 + */ + private Integer history; + /** + * 复制权限,1为打开该权限,0为关闭该权限,默认为1,非必填 + */ + private Integer copy; + /** + * 导出权限,1为打开该权限,0为关闭该权限,默认为1,非必填 + */ + private Integer export; + /** + * 打印权限,1为打开该权限,0为关闭该权限,默认为1,非必填 + */ + private Integer print; +} diff --git a/blade-service-api/blade-open-api/src/main/java/org/springblade/openapi/wps/pojo/vo/Api4WpsUserVO.java b/blade-service-api/blade-open-api/src/main/java/org/springblade/openapi/wps/pojo/vo/Api4WpsUserVO.java new file mode 100644 index 0000000..8c23c65 --- /dev/null +++ b/blade-service-api/blade-open-api/src/main/java/org/springblade/openapi/wps/pojo/vo/Api4WpsUserVO.java @@ -0,0 +1,36 @@ +package org.springblade.openapi.wps.pojo.vo; + +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serial; +import java.io.Serializable; + +/** + * wps用户信息 + * @author bfhuange + * @date 2024/10/30 + */ +@NoArgsConstructor +@Data +public class Api4WpsUserVO implements Serializable { + @Serial + private static final long serialVersionUID = 1L; + /** + * 用户id,长度不超过32位,与creator、modifier一致,必填 + */ + private String id; + /** + * 用户名称,必填 + */ + private String name; + /** + * 用户操作权限,预览固定为read,必填 + */ + private String permission = "write"; + + public Api4WpsUserVO(String id, String name) { + this.id = id; + this.name = name; + } +} diff --git a/blade-service-api/blade-open-api/src/main/java/org/springblade/openapi/wps/pojo/vo/Api4WpsWatermarkVO.java b/blade-service-api/blade-open-api/src/main/java/org/springblade/openapi/wps/pojo/vo/Api4WpsWatermarkVO.java new file mode 100644 index 0000000..49aa119 --- /dev/null +++ b/blade-service-api/blade-open-api/src/main/java/org/springblade/openapi/wps/pojo/vo/Api4WpsWatermarkVO.java @@ -0,0 +1,48 @@ +package org.springblade.openapi.wps.pojo.vo; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; + +/** + * wps水印参数 + * @author bfhuange + * @date 2024/10/30 + */ +@Data +public class Api4WpsWatermarkVO implements Serializable { + @Serial + private static final long serialVersionUID = 1L; + /** + * 水印类型, 0为无水印; 1为文字水印,非必填 + */ + private Integer type; + /** + * 文字水印的文字,支持通过\r\n换行,支持emoji表情 + * 当type为1时此字段必选 + */ + private String value; + /** + * 水印的颜色(含透明度),非必选,有默认值。格式为:rgba( 192, 192, 192, 0.6 ) + */ + @JsonProperty("fillstyle") + private String fillStyle; + /** + * 水印的字体,非必选,有默认值,格式为bold 20px Serif,非必填 + */ + private String font; + /** + * 水印的旋转度(弧度),非必选,有默认值,默认为-0.7853982,非必填 + */ + private Float rotate; + /** + * 水印水平间距,非必选,有默认值,默认为50,非必填 + */ + private Integer horizontal; + /** + * 水印垂直间距,非必选,有默认值,默认为100,非必填 + */ + private Integer vertical; +} diff --git a/blade-service-api/blade-system-api/src/main/java/org/springblade/system/pojo/entity/AppVersion.java b/blade-service-api/blade-system-api/src/main/java/org/springblade/system/pojo/entity/AppVersion.java new file mode 100644 index 0000000..3387a92 --- /dev/null +++ b/blade-service-api/blade-system-api/src/main/java/org/springblade/system/pojo/entity/AppVersion.java @@ -0,0 +1,81 @@ +/** + * BladeX Commercial License Agreement + * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved. + *

+ * Use of this software is governed by the Commercial License Agreement + * obtained after purchasing a license from BladeX. + *

+ * 1. This software is for development use only under a valid license + * from BladeX. + *

+ * 2. Redistribution of this software's source code to any third party + * without a commercial license is strictly prohibited. + *

+ * 3. Licensees may copyright their own code but cannot use segments + * from this software for such purposes. Copyright of this software + * remains with BladeX. + *

+ * Using this software signifies agreement to this License, and the software + * must not be used for illegal purposes. + *

+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is + * not liable for any claims arising from secondary or illegal development. + *

+ * Author: Chill Zhuang (bladejava@qq.com) + */ +package org.springblade.system.pojo.entity; + +import com.baomidou.mybatisplus.annotation.SqlCondition; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableName; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.springblade.core.tenant.mp.TenantEntity; + +import java.io.Serial; + + +@Data +@Schema(description = "APP版本管理") +@TableName("t_app_version") +@EqualsAndHashCode(callSuper = true) +public class AppVersion extends TenantEntity { + + @Serial + private static final long serialVersionUID = 1L; + + @NotEmpty + @Schema(description = "更新类型") + protected String updateType; + + @TableField(condition = SqlCondition.LIKE) + @NotEmpty + @Schema(description = "版本号") + protected String version; + + @TableField(condition = SqlCondition.LIKE) + @NotEmpty + @Schema(description = "子版本号") + protected String subVersion; + + @NotEmpty + @Schema(description = "强制更新(是/否)") + protected String isForceUpdate; + + @NotEmpty + @Schema(description = "安装方式") + protected String installMethod; + + @NotNull + @Schema(description = "安装包(附件Id)") + protected Long installAttachmentId; + + @TableField(condition = SqlCondition.LIKE) + @NotEmpty + @Schema(description = "更新说明") + protected String updateExplanation; + +} diff --git a/blade-service-api/blade-system-api/src/main/java/org/springblade/system/pojo/entity/DataSyncRecord.java b/blade-service-api/blade-system-api/src/main/java/org/springblade/system/pojo/entity/DataSyncRecord.java new file mode 100644 index 0000000..f62e9c5 --- /dev/null +++ b/blade-service-api/blade-system-api/src/main/java/org/springblade/system/pojo/entity/DataSyncRecord.java @@ -0,0 +1,88 @@ +package org.springblade.system.pojo.entity; + +import com.baomidou.mybatisplus.annotation.*; +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.Data; +import io.swagger.v3.oas.annotations.media.Schema; + +import java.util.Date; + +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; +import lombok.experimental.Accessors; +import org.springblade.core.tool.utils.DateUtil; +import org.springframework.format.annotation.DateTimeFormat; + +import java.io.Serializable; +import java.io.Serial; + +/** + * 数据同步记录表 实体类 + * + * @author BladeX + * @since 2024-09-27 + */ +@Accessors(chain = true) +@Data +@TableName("blade_data_sync_record") +@Schema(description = "DataSyncRecord对象") +public class DataSyncRecord implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 主键 + */ + @JsonSerialize(using = ToStringSerializer.class) + @Schema(description = "主键") + @TableId(value = "id", type = IdType.ASSIGN_ID) + private Long id; + + /** + * 系统编码 + */ + @Schema(description = "系统编码") + private String systemCode; + /** + * 数据类型 + */ + @Schema(description = "数据类型") + private String dataType; + /** + * 模式 + */ + @Schema(description = "模式") + private String mode; + /** + * 结果 + */ + @Schema(description = "结果") + private String result; + /** + * 同步时间 + */ + @Schema(description = "同步时间") + private Date syncTime; + /** + * 租户ID + */ + @Schema(description = "租户ID") + @TableField(fill = FieldFill.INSERT) + private String tenantId; + /** + * 创建时间 + */ + @DateTimeFormat(pattern = DateUtil.PATTERN_DATETIME) + @JsonFormat(pattern = DateUtil.PATTERN_DATETIME) + @Schema(description = "创建时间", hidden = true) + @TableField(fill = FieldFill.INSERT) + private Date createTime; + /** + * 状态[0:未删除,1:删除] + */ + @TableLogic + @Schema(description = "是否已删除", hidden = true) + @TableField(fill = FieldFill.INSERT) + private Integer isDeleted; +} diff --git a/blade-service-api/blade-system-api/src/main/java/org/springblade/system/pojo/entity/Dept.java b/blade-service-api/blade-system-api/src/main/java/org/springblade/system/pojo/entity/Dept.java index 32da844..53002eb 100644 --- a/blade-service-api/blade-system-api/src/main/java/org/springblade/system/pojo/entity/Dept.java +++ b/blade-service-api/blade-system-api/src/main/java/org/springblade/system/pojo/entity/Dept.java @@ -33,9 +33,11 @@ import com.fasterxml.jackson.databind.annotation.JsonSerialize; import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; +import org.springblade.core.tenant.mp.TenantEntity; import java.io.Serial; import java.io.Serializable; +import java.util.Date; /** * 实体类 @@ -45,7 +47,7 @@ import java.io.Serializable; @Data @TableName("blade_dept") @Schema(description = "Dept对象") -public class Dept implements Serializable { +public class Dept extends TenantEntity { @Serial private static final long serialVersionUID = 1L; @@ -127,4 +129,30 @@ public class Dept implements Serializable { private Integer isDeleted; + /** + * 部门编码 + */ + @Schema(description = "部门编码") + private String deptCode; + /** + * 父部门编码 + */ + @Schema(description = "父部门编码") + private String parentCode; + /** + * 同步时间 + */ + @Schema(description = "同步时间") + private Date syncTime; + /** + * 所属公司编码 + */ + @Schema(description = "所属公司编码") + private String belongCompanyCode; + /** + * 是否是oa的部门 + */ + @Schema(description = "是否是oa的部门") + private Integer isOa; + } diff --git a/blade-service-api/blade-system-api/src/main/java/org/springblade/system/pojo/enums/DataSync.java b/blade-service-api/blade-system-api/src/main/java/org/springblade/system/pojo/enums/DataSync.java new file mode 100644 index 0000000..2ea2bfa --- /dev/null +++ b/blade-service-api/blade-system-api/src/main/java/org/springblade/system/pojo/enums/DataSync.java @@ -0,0 +1,69 @@ +package org.springblade.system.pojo.enums; + +/** + * 数据同步枚举类 + * @author bfhuange + * @date 2024/9/27 + */ +public class DataSync { + + /** + * 系统编码 + */ + public enum SystemCode { + /** + * oa 系统 + */ + OA, + /** + * mk 系统 + */ + MK, + ; + } + + /** + * 数据类型 + */ + public enum DataType { + /** + * 组织,包含公司和部门 + */ + ORG, + /** + * 人员 + */ + PERSON, + ; + } + + /** + * 模式 + */ + public enum Mode { + /** + * 获取 + */ + FETCH, + /** + * 推送 + */ + PUSH, + ; + } + + /** + * 结果 + */ + public enum Result { + /** + * 成功 + */ + SUCCESS, + /** + * 失败 + */ + FAIL, + ; + } +} diff --git a/blade-service-api/blade-system-api/src/main/java/org/springblade/system/pojo/enums/DeptCategory.java b/blade-service-api/blade-system-api/src/main/java/org/springblade/system/pojo/enums/DeptCategory.java new file mode 100644 index 0000000..cf21c31 --- /dev/null +++ b/blade-service-api/blade-system-api/src/main/java/org/springblade/system/pojo/enums/DeptCategory.java @@ -0,0 +1,32 @@ +package org.springblade.system.pojo.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 部门类型枚举 + * @author bfhuange + * @date 2024/8/26 + */ +@AllArgsConstructor +@Getter +public enum DeptCategory { + /** + * 公司 + */ + COMPANY(1, "公司"), + /** + * 部门 + */ + DEPT(2, "部门"), + ; + + /** + * 部门类型编码 + */ + private final Integer code; + /** + * 部门类型名称 + */ + private final String name; +} diff --git a/blade-service-api/blade-system-api/src/main/java/org/springblade/system/pojo/enums/UserSex.java b/blade-service-api/blade-system-api/src/main/java/org/springblade/system/pojo/enums/UserSex.java new file mode 100644 index 0000000..3b37571 --- /dev/null +++ b/blade-service-api/blade-system-api/src/main/java/org/springblade/system/pojo/enums/UserSex.java @@ -0,0 +1,43 @@ +package org.springblade.system.pojo.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.stream.Stream; + +/** + * 用户性别枚举 + * @author bfhuange + * @date 2024/8/27 + */ +@Getter +@AllArgsConstructor +public enum UserSex { + /** + * 男 + */ + MAN(1, "男"), + /** + * 女 + */ + WOMAN(2, "女"), + ; + /** + * 编码 + */ + private final Integer code; + /** + * 名称 + */ + private final String name; + + public static Integer getCodeByName(String name) { + if (name == null || name.isEmpty()) { + return null; + } + return Stream.of(values()) + .filter(e -> e.getName().equals(name)) + .map(UserSex::getCode) + .findFirst().orElse(null); + } +} diff --git a/blade-service-api/blade-system-api/src/main/java/org/springblade/system/pojo/vo/DeptListVO.java b/blade-service-api/blade-system-api/src/main/java/org/springblade/system/pojo/vo/DeptListVO.java new file mode 100644 index 0000000..9346dd4 --- /dev/null +++ b/blade-service-api/blade-system-api/src/main/java/org/springblade/system/pojo/vo/DeptListVO.java @@ -0,0 +1,52 @@ +package org.springblade.system.pojo.vo; + +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; + +/** + * @author bfhuange + * @date 2024/8/27 + */ +@Data +@Schema(description = "DeptListVO") +public class DeptListVO implements Serializable { + @Serial + private static final long serialVersionUID = 1L; + /** + * 主键id + */ + @JsonSerialize(using = ToStringSerializer.class) + @Schema(description = "主键id") + private Long id; + /** + * 部门编码 + */ + @Schema(description = "部门编码") + private String deptCode; + /** + * 机构名 + */ + @Schema(description = "机构名") + private String deptName; + /** + * 父主键 + */ + @JsonSerialize(using = ToStringSerializer.class) + @Schema(description = "父主键") + private Long parentId; + /** + * 父部门编码 + */ + @Schema(description = "父部门编码") + private String parentCode; + /** + * 部门类型 + */ + @Schema(description = "部门类型") + private Integer deptCategory; +} diff --git a/blade-service-api/blade-system-api/src/main/java/org/springblade/system/pojo/vo/FeeItemVO.java b/blade-service-api/blade-system-api/src/main/java/org/springblade/system/pojo/vo/FeeItemVO.java index c2df4aa..b45d458 100644 --- a/blade-service-api/blade-system-api/src/main/java/org/springblade/system/pojo/vo/FeeItemVO.java +++ b/blade-service-api/blade-system-api/src/main/java/org/springblade/system/pojo/vo/FeeItemVO.java @@ -25,6 +25,7 @@ */ package org.springblade.system.pojo.vo; +import com.baomidou.mybatisplus.annotation.TableField; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import lombok.EqualsAndHashCode; @@ -44,4 +45,12 @@ public class FeeItemVO extends FeeItem { @Serial private static final long serialVersionUID = 1L; + @TableField(exist = false) + @Schema(description = "组织") + private String createDeptName; + + @TableField(exist = false) + @Schema(description = "更新人") + private String updateUserName; + } diff --git a/blade-service-api/blade-system-api/src/main/java/org/springblade/system/pojo/vo/UserDeptIdsVO.java b/blade-service-api/blade-system-api/src/main/java/org/springblade/system/pojo/vo/UserDeptIdsVO.java new file mode 100644 index 0000000..9486204 --- /dev/null +++ b/blade-service-api/blade-system-api/src/main/java/org/springblade/system/pojo/vo/UserDeptIdsVO.java @@ -0,0 +1,37 @@ +package org.springblade.system.pojo.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 用户部门id + * @author bfhuange + * @date 2024/9/12 + */ +@Data +@Schema(description = "UserDeptIdsVO对象") +public class UserDeptIdsVO implements Serializable { + @Serial + private static final long serialVersionUID = 1L; + + /** + * 用户id + */ + @Schema(description = "用户id") + private Long userId; + + /** + * 部门id,多个用逗号拼接 + */ + @Schema(description = "部门id,多个用逗号拼接") + private String deptIds; + + /** + * 部门编码,多个用逗号拼接 + */ + @Schema(description = "部门编码,多个用逗号拼接") + private String deptCodes; +} diff --git a/blade-service-api/blade-system-api/src/main/java/org/springblade/system/util/DataSyncRecordUtils.java b/blade-service-api/blade-system-api/src/main/java/org/springblade/system/util/DataSyncRecordUtils.java new file mode 100644 index 0000000..aca6cea --- /dev/null +++ b/blade-service-api/blade-system-api/src/main/java/org/springblade/system/util/DataSyncRecordUtils.java @@ -0,0 +1,83 @@ +package org.springblade.system.util; + +import org.springblade.system.pojo.entity.DataSyncRecord; +import org.springblade.system.pojo.enums.DataSync; + +/** + * 数据同步记录工具类 + * @author bfhuange + * @date 2024/9/27 + */ +public class DataSyncRecordUtils { + private DataSyncRecordUtils() {} + + /** + * 创建获取oa组织的数据同步记录 + * @return + */ + public static DataSyncRecord createOAOrgFetch() { + return DataSyncRecordUtils.createOAFetch() + // 数据类型是组织 + .setDataType(DataSync.DataType.ORG.name()); + } + + /** + * 创建获取oa人员的数据同步记录 + * @return + */ + public static DataSyncRecord createOAPersonFetch() { + return DataSyncRecordUtils.createOAFetch() + // 数据类型是人员 + .setDataType(DataSync.DataType.PERSON.name()); + } + + /** + * 创建推送mk组织的数据同步记录 + * @return + */ + public static DataSyncRecord createMKOrgPush() { + return DataSyncRecordUtils.createMKPush() + // 数据类型是组织 + .setDataType(DataSync.DataType.ORG.name()); + } + + /** + * 创建推送mk人员的数据同步记录 + * @return + */ + public static DataSyncRecord createMKPersonPush() { + return DataSyncRecordUtils.createMKPush() + // 数据类型是人员 + .setDataType(DataSync.DataType.PERSON.name()); + } + + /** + * 创建oa 获取数据记录 + * + * @return + */ + private static DataSyncRecord createOAFetch() { + return new DataSyncRecord() + // oa 系统 + .setSystemCode(DataSync.SystemCode.OA.name()) + // 获取数据 + .setMode(DataSync.Mode.FETCH.name()) + // 默认结果是成功 + .setResult(DataSync.Result.SUCCESS.name()); + } + + /** + * 创建mk 推送数据记录 + * + * @return + */ + private static DataSyncRecord createMKPush() { + return new DataSyncRecord() + // mk 系统 + .setSystemCode(DataSync.SystemCode.MK.name()) + // 推送数据 + .setMode(DataSync.Mode.PUSH.name()) + // 默认结果是成功 + .setResult(DataSync.Result.SUCCESS.name()); + } +} diff --git a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/ProjectApplyVO.java b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/ProjectApplyVO.java index 3c1b744..1fbed13 100644 --- a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/ProjectApplyVO.java +++ b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/ProjectApplyVO.java @@ -67,4 +67,8 @@ public class ProjectApplyVO extends ProjectApply { @Schema(description = "生效类型名称") private String effectiveTypeName; + @TableField(exist = false) + @Schema(description = "变更类型") + private String changeType; + } diff --git a/blade-service-api/blade-user-api/src/main/java/org/springblade/system/pojo/context/IPhone.java b/blade-service-api/blade-user-api/src/main/java/org/springblade/system/pojo/context/IPhone.java new file mode 100644 index 0000000..7e3f6bd --- /dev/null +++ b/blade-service-api/blade-user-api/src/main/java/org/springblade/system/pojo/context/IPhone.java @@ -0,0 +1,19 @@ +package org.springblade.system.pojo.context; + +/** + * 获取设置手机号接口 + * @author bfhuange + * @since 2026/4/1 + */ +public interface IPhone { + /** + * 设置手机号 + * @return + */ + void setPhone(String phone); + /** + * 获取手机号 + * @return + */ + String getPhone(); +} diff --git a/blade-service-api/blade-user-api/src/main/java/org/springblade/system/pojo/entity/User.java b/blade-service-api/blade-user-api/src/main/java/org/springblade/system/pojo/entity/User.java index 9da8101..63ee3cc 100644 --- a/blade-service-api/blade-user-api/src/main/java/org/springblade/system/pojo/entity/User.java +++ b/blade-service-api/blade-user-api/src/main/java/org/springblade/system/pojo/entity/User.java @@ -31,6 +31,7 @@ import lombok.EqualsAndHashCode; import org.springblade.core.tenant.mp.TenantEntity; import org.springblade.core.tool.jackson.Sensitive; import org.springblade.core.tool.sensitive.SensitiveType; +import org.springblade.system.pojo.context.IPhone; import java.io.Serial; import java.util.Date; @@ -43,7 +44,7 @@ import java.util.Date; @Data @TableName("blade_user") @EqualsAndHashCode(callSuper = true) -public class User extends TenantEntity { +public class User extends TenantEntity implements IPhone { @Serial private static final long serialVersionUID = 1L; @@ -116,4 +117,22 @@ public class User extends TenantEntity { private Integer isLeader; + /** + * 部门编码 + */ + private String deptCodes; + /** + * 同步时间 + */ + private Date syncTime; + /** + * 是否是oa的账号 + */ + private Integer isOa; + /** + * 是否推送mk + */ + private Integer isPushMk; + + } diff --git a/blade-service-api/blade-user-api/src/main/java/org/springblade/system/pojo/entity/UserDept.java b/blade-service-api/blade-user-api/src/main/java/org/springblade/system/pojo/entity/UserDept.java index 3ccc986..ff76ad7 100644 --- a/blade-service-api/blade-user-api/src/main/java/org/springblade/system/pojo/entity/UserDept.java +++ b/blade-service-api/blade-user-api/src/main/java/org/springblade/system/pojo/entity/UserDept.java @@ -36,6 +36,7 @@ import lombok.Data; import java.io.Serial; import java.io.Serializable; +import java.util.Date; /** * 实体类 @@ -85,4 +86,45 @@ public class UserDept implements Serializable { @Schema(description = "是否已删除") private Integer isDeleted; + + /** + * 公司编码 + */ + @Schema(description = "公司编码") + private String companyCode; + /** + * 公司名 + */ + @Schema(description = "公司名") + private String companyName; + /** + * 部门编码 + */ + @Schema(description = "部门编码") + private String deptCode; + /** + * 部门名 + */ + @Schema(description = "部门名") + private String deptName; + /** + * 排序 + */ + @Schema(description = "排序") + private Integer sort; + /** + * oaid + */ + @Schema(description = "oaid") + private String oaId; + /** + * oa的loginId + */ + @Schema(description = "oa的loginId") + private String oaLoginId; + /** + * 同步时间 + */ + @Schema(description = "同步时间") + private Date syncTime; } diff --git a/blade-service-api/blade-user-api/src/main/java/org/springblade/system/pojo/vo/UserListVO.java b/blade-service-api/blade-user-api/src/main/java/org/springblade/system/pojo/vo/UserListVO.java new file mode 100644 index 0000000..680eeda --- /dev/null +++ b/blade-service-api/blade-user-api/src/main/java/org/springblade/system/pojo/vo/UserListVO.java @@ -0,0 +1,38 @@ +package org.springblade.system.pojo.vo; + +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; + +/** + * @author bfhuange + * @date 2024/8/28 + */ +@Data +@Schema(description = "UserListVO对象") +public class UserListVO implements Serializable { + @Serial + private static final long serialVersionUID = 1L; + + /** + * 主键ID + */ + @JsonSerialize(using = ToStringSerializer.class) + private Long id; + /** + * 用户编号 + */ + private String code; + /** + * 昵称 + */ + private String name; + /** + * 手机 + */ + private String phone; +} diff --git a/blade-service-api/pom.xml b/blade-service-api/pom.xml index f8fd776..f62c479 100644 --- a/blade-service-api/pom.xml +++ b/blade-service-api/pom.xml @@ -21,9 +21,11 @@ blade-ratelimit-api blade-scope-api blade-system-api - blade-transport-api blade-user-api blade-record-api + blade-file-api + blade-open-api + blade-transport-api diff --git a/blade-service/blade-file/Dockerfile b/blade-service/blade-file/Dockerfile new file mode 100644 index 0000000..939092c --- /dev/null +++ b/blade-service/blade-file/Dockerfile @@ -0,0 +1,19 @@ +FROM bladex/alpine-java:openjdk17_cn_slim +ARG ACTIVE + +LABEL maintainer="bladejava@qq.com" + +# 设置时区为上海 +ENV TZ=Asia/Shanghai +# 设置时区信息 +RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone + +COPY ./target/blade-file.jar /opt/app.jar + +WORKDIR /opt + +EXPOSE 8107 + +ENTRYPOINT ["java", "--add-opens", "java.base/java.lang=ALL-UNNAMED", "--add-opens", "java.base/java.lang.reflect=ALL-UNNAMED", "-Djava.security.egd=file:/dev/./urandom", "-XX:+HeapDumpOnOutOfMemoryError", "-XX:HeapDumpPath=/opt/logs", "-XX:ErrorFile=/opt/logs/hs_err_pid%p.log", "-Xlog:gc*:file=/opt/logs/gc.log:time,uptime,level,tags:filecount=5,filesize=50M", "-XX:MaxDirectMemorySize=512m", "-Xms1024m", "-Xmx4096m", "-jar", "app.jar"] + +CMD ["--spring.profiles.active=${ACTIVE}","--logging.path=/opt/logs"] diff --git a/blade-service/blade-file/Dockerfile-rt b/blade-service/blade-file/Dockerfile-rt new file mode 100644 index 0000000..e005354 --- /dev/null +++ b/blade-service/blade-file/Dockerfile-rt @@ -0,0 +1,19 @@ +#FROM 10.38.16.129/bladex/alpine-java:openjdk17_cn_slim +#FROM registry.cn-hangzhou.aliyuncs.com/vic_person/alpine-java:openjdk17_cn_slim +FROM 10.38.16.129:80/bladex/alpine-java:openjdk17_cn_slim +ARG ACTIVE + +LABEL maintainer="bladejava@qq.com" + +RUN apk add --no-cache tzdata + +# 设置时区为上海 +ENV TZ=Asia/Shanghai +# 设置时区信息 +RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone + +COPY ./target/*.jar /opt/app.jar + +ENTRYPOINT ["java", "--add-opens", "java.base/java.lang=ALL-UNNAMED", "--add-opens", "java.base/java.lang.reflect=ALL-UNNAMED", "-Djava.security.egd=file:/dev/./urandom", "-jar", "/opt/app.jar"] + +CMD ["--spring.profiles.active=${ACTIVE}"] diff --git a/blade-service/blade-file/Dockerfile-zh-dev b/blade-service/blade-file/Dockerfile-zh-dev new file mode 100644 index 0000000..66022e9 --- /dev/null +++ b/blade-service/blade-file/Dockerfile-zh-dev @@ -0,0 +1,19 @@ +FROM bladex/alpine-java:openjdk17_cn_slim +ARG ACTIVE + +LABEL maintainer="bladejava@qq.com" + +# 设置时区为上海 +ENV TZ=Asia/Shanghai +# 设置时区信息 +RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone + +COPY ./target/blade-file.jar /opt/app.jar + +WORKDIR /opt + +EXPOSE 8107 + +ENTRYPOINT ["java", "--add-opens", "java.base/java.lang=ALL-UNNAMED", "--add-opens", "java.base/java.lang.reflect=ALL-UNNAMED", "-Djava.security.egd=file:/dev/./urandom", "-XX:+HeapDumpOnOutOfMemoryError", "-XX:HeapDumpPath=/opt/logs", "-XX:ErrorFile=/opt/logs/hs_err_pid%p.log", "-Xlog:gc*:file=/opt/logs/gc.log:time,uptime,level,tags:filecount=5,filesize=50M", "-XX:MaxDirectMemorySize=512m", "-Xms1024m", "-Xmx1024m", "-jar", "app.jar"] + +CMD ["--spring.profiles.active=${ACTIVE}","--logging.path=/opt/logs"] diff --git a/blade-service/blade-file/pom.xml b/blade-service/blade-file/pom.xml new file mode 100644 index 0000000..4be31d8 --- /dev/null +++ b/blade-service/blade-file/pom.xml @@ -0,0 +1,151 @@ + + + + blade-service + org.springblade + ${revision} + + 4.0.0 + + blade-file + ${project.artifactId} + jar + + + 1.27.1 + 1.10 + + + 1.18.30 + 1.5.5.Final + 7.5.5 + + + + + org.springblade + blade-core-boot + + + org.springblade + blade-starter-swagger + + + + org.springblade + blade-file-api + + + + org.springblade + blade-wps-api + + + + org.springblade + blade-ocr-api + + + + + net.lingala.zip4j + zip4j + 1.3.1 + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + org.apache.commons + commons-compress + ${commons-compress.version} + + + org.tukaani + xz + ${xz.version} + + + + com.github.junrar + junrar + ${junrar.version} + + + + com.alibaba.csp + sentinel-datasource-nacos + + + + + io.minio + minio + + + + + com.aliyun.oss + aliyun-sdk-oss + + + + org.mapstruct + mapstruct + ${org.mapstruct.version} + + + + + + + + io.fabric8 + docker-maven-plugin + + ${docker.fabric.skip} + + + + org.apache.maven.plugins + maven-antrun-plugin + + + + org.apache.maven.plugins + maven-compiler-plugin + ${maven.plugin.version} + + ${java.version} + ${java.version} + UTF-8 + + -parameters + + + + org.projectlombok + lombok + ${lombok.version} + + + org.mapstruct + mapstruct-processor + ${org.mapstruct.version} + + + + + + + + + + diff --git a/blade-service/blade-file/src/main/java/org/springblade/file/FileApplication.java b/blade-service/blade-file/src/main/java/org/springblade/file/FileApplication.java new file mode 100644 index 0000000..9499397 --- /dev/null +++ b/blade-service/blade-file/src/main/java/org/springblade/file/FileApplication.java @@ -0,0 +1,45 @@ +/** + * BladeX Commercial License Agreement + * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved. + *

+ * Use of this software is governed by the Commercial License Agreement + * obtained after purchasing a license from BladeX. + *

+ * 1. This software is for development use only under a valid license + * from BladeX. + *

+ * 2. Redistribution of this software's source code to any third party + * without a commercial license is strictly prohibited. + *

+ * 3. Licensees may copyright their own code but cannot use segments + * from this software for such purposes. Copyright of this software + * remains with BladeX. + *

+ * Using this software signifies agreement to this License, and the software + * must not be used for illegal purposes. + *

+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is + * not liable for any claims arising from secondary or illegal development. + *

+ * Author: Chill Zhuang (bladejava@qq.com) + */ +package org.springblade.file; + +import org.springblade.core.cloud.client.BladeCloudApplication; +import org.springblade.core.launch.BladeApplication; +import org.springframework.context.annotation.ComponentScan; + +/** + * Desk启动器 + * + * @author Chill + */ +@BladeCloudApplication +@ComponentScan({"org.springblade.file", "org.springblade.**.feign"}) +public class FileApplication { + + public static void main(String[] args) { + BladeApplication.run("blade-file", FileApplication.class, args); + } + +} diff --git a/blade-service/blade-file/src/main/java/org/springblade/file/config/FileConfig.java b/blade-service/blade-file/src/main/java/org/springblade/file/config/FileConfig.java new file mode 100644 index 0000000..64632b2 --- /dev/null +++ b/blade-service/blade-file/src/main/java/org/springblade/file/config/FileConfig.java @@ -0,0 +1,141 @@ +package org.springblade.file.config; + +import com.aliyun.oss.OSSClientBuilder; +import com.obs.services.ObsClient; +import io.minio.MinioClient; +import org.springblade.core.log.exception.ServiceException; +import org.springblade.core.tool.utils.StringUtil; +import org.springblade.file.service.IFileService; +import org.springblade.file.service.impl.LocalFileServiceImpl; +import org.springblade.file.service.impl.MinioFileServiceImpl; +import org.springblade.file.service.impl.OssFileServiceImpl; +import org.springblade.file.service.impl.ObsFileServiceImpl; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.env.Environment; + +/** + * 文件配置类 + * @author bfhuange + * @date 2024/10/21 + */ +@Configuration +public class FileConfig { + + private static final String STORAGE_AUTO = "auto"; + private static final String STORAGE_OBS = "obs"; + private static final String STORAGE_MINIO = "minio"; + private static final String STORAGE_OSS = "oss"; + private static final String STORAGE_LOCAL = "local"; + + @Bean + @ConfigurationProperties(prefix = "obs") + public ObsProperties obsProperties() { + return new ObsProperties(); + } + + @Bean + @ConfigurationProperties(prefix = "file.storage") + public FileStorageProperties fileStorageProperties() { + return new FileStorageProperties(); + } + + @Bean + public IFileService fileService(ObsProperties obsProperties, FileStorageProperties fileStorageProperties, Environment environment) { + FileStorageProperties.StoreProperties minioProperties = mergeStoreProperties(fileStorageProperties.getMinio(), environment, STORAGE_MINIO); + FileStorageProperties.StoreProperties ossProperties = mergeStoreProperties(fileStorageProperties.getOss(), environment, STORAGE_OSS); + String storageType = resolveStorageType(fileStorageProperties.getType(), obsProperties, minioProperties, ossProperties); + return switch (storageType) { + case STORAGE_OBS -> buildObsFileService(obsProperties); + case STORAGE_MINIO -> buildMinioFileService(minioProperties); + case STORAGE_OSS -> buildOssFileService(ossProperties); + case STORAGE_LOCAL -> new LocalFileServiceImpl(fileStorageProperties.getLocal()); + default -> throw new ServiceException("文件存储类型配置错误"); + }; + } + + private IFileService buildObsFileService(ObsProperties properties) { + validateObsProperties(properties); + ObsClient obsClient = new ObsClient(properties.getAccessKeyId(), properties.getAccessKeySecret(), properties.getEndpoint()); + return new ObsFileServiceImpl(properties, obsClient); + } + + private IFileService buildMinioFileService(FileStorageProperties.StoreProperties properties) { + validateStoreProperties(STORAGE_MINIO, properties); + MinioClient minioClient = MinioClient.builder() + .endpoint(properties.getEndpoint()) + .credentials(properties.getAccessKeyId(), properties.getAccessKeySecret()) + .build(); + return new MinioFileServiceImpl(properties, minioClient); + } + + private IFileService buildOssFileService(FileStorageProperties.StoreProperties properties) { + validateStoreProperties(STORAGE_OSS, properties); + return new OssFileServiceImpl(properties, new OSSClientBuilder().build(properties.getEndpoint(), properties.getAccessKeyId(), properties.getAccessKeySecret())); + } + + private String resolveStorageType(String type, ObsProperties obsProperties, FileStorageProperties.StoreProperties minioProperties, FileStorageProperties.StoreProperties ossProperties) { + if (StringUtil.isNotBlank(type) && !STORAGE_AUTO.equalsIgnoreCase(type)) { + return type.toLowerCase(); + } + if (hasObsProperties(obsProperties)) { + return STORAGE_OBS; + } + if (hasStoreProperties(minioProperties)) { + return STORAGE_MINIO; + } + if (hasStoreProperties(ossProperties)) { + return STORAGE_OSS; + } + return STORAGE_LOCAL; + } + + private FileStorageProperties.StoreProperties mergeStoreProperties(FileStorageProperties.StoreProperties properties, Environment environment, String prefix) { + FileStorageProperties.StoreProperties mergedProperties = new FileStorageProperties.StoreProperties(); + String fileStoragePrefix = "file.storage." + prefix; + mergedProperties.setEndpoint(firstNotBlank(properties.getEndpoint(), environment.getProperty(fileStoragePrefix + ".endpoint"), environment.getProperty(prefix + ".endpoint"))); + mergedProperties.setTransformEndpoint(firstNotBlank(properties.getTransformEndpoint(), environment.getProperty(fileStoragePrefix + ".transform-endpoint"), environment.getProperty(prefix + ".transform-endpoint"))); + mergedProperties.setAccessKeyId(firstNotBlank(properties.getAccessKeyId(), environment.getProperty(fileStoragePrefix + ".access-key-id"), environment.getProperty(fileStoragePrefix + ".access-key"), environment.getProperty(prefix + ".access-key-id"), environment.getProperty(prefix + ".access-key"))); + mergedProperties.setAccessKeySecret(firstNotBlank(properties.getAccessKeySecret(), environment.getProperty(fileStoragePrefix + ".access-key-secret"), environment.getProperty(fileStoragePrefix + ".secret-key"), environment.getProperty(prefix + ".access-key-secret"), environment.getProperty(prefix + ".secret-key"))); + mergedProperties.setBucketName(firstNotBlank(properties.getBucketName(), environment.getProperty(fileStoragePrefix + ".bucket-name"), environment.getProperty(prefix + ".bucket-name"))); + mergedProperties.setRootDirectory(firstNotBlank(properties.getRootDirectory(), environment.getProperty(fileStoragePrefix + ".root-directory"), environment.getProperty(prefix + ".root-directory"))); + mergedProperties.setExpires(properties.getExpires()); + return mergedProperties; + } + + private String firstNotBlank(String... values) { + for (String value : values) { + if (StringUtil.isNotBlank(value)) { + return value; + } + } + return null; + } + + private boolean hasObsProperties(ObsProperties properties) { + return StringUtil.isNotBlank(properties.getEndpoint()) + && StringUtil.isNotBlank(properties.getAccessKeyId()) + && StringUtil.isNotBlank(properties.getAccessKeySecret()) + && StringUtil.isNotBlank(properties.getBucketName()); + } + + private boolean hasStoreProperties(FileStorageProperties.StoreProperties properties) { + return StringUtil.isNotBlank(properties.getEndpoint()) + && StringUtil.isNotBlank(properties.getAccessKeyId()) + && StringUtil.isNotBlank(properties.getAccessKeySecret()) + && StringUtil.isNotBlank(properties.getBucketName()); + } + + private void validateObsProperties(ObsProperties properties) { + if (!hasObsProperties(properties)) { + throw new ServiceException("OBS文件存储配置不完整"); + } + } + + private void validateStoreProperties(String storageType, FileStorageProperties.StoreProperties properties) { + if (!hasStoreProperties(properties)) { + throw new ServiceException(storageType + "文件存储配置不完整"); + } + } +} diff --git a/blade-service/blade-file/src/main/java/org/springblade/file/config/FileStorageProperties.java b/blade-service/blade-file/src/main/java/org/springblade/file/config/FileStorageProperties.java new file mode 100644 index 0000000..b8c0c00 --- /dev/null +++ b/blade-service/blade-file/src/main/java/org/springblade/file/config/FileStorageProperties.java @@ -0,0 +1,115 @@ +/** + * BladeX Commercial License Agreement + * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved. + *

+ * Use of this software is governed by the Commercial License Agreement + * obtained after purchasing a license from BladeX. + *

+ * 1. This software is for development use only under a valid license + * from BladeX. + *

+ * 2. Redistribution of this software's source code to any third party + * without a commercial license is strictly prohibited. + *

+ * 3. Licensees may copyright their own code but cannot use segments + * from this software for such purposes. Copyright of this software + * remains with BladeX. + *

+ * Using this software signifies agreement to this License, and the software + * must not be used for illegal purposes. + *

+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is + * not liable for any claims arising from secondary or illegal development. + *

+ * Author: Chill Zhuang (bladejava@qq.com) + */ +package org.springblade.file.config; + +import lombok.Data; + +/** + * 文件存储配置 + * + * @author Chill + */ +@Data +public class FileStorageProperties { + + /** + * 存储类型,auto按obs、minio、oss、local优先级自动选择 + */ + private String type = "auto"; + + /** + * MinIO配置 + */ + private StoreProperties minio = new StoreProperties(); + + /** + * 阿里云OSS配置 + */ + private StoreProperties oss = new StoreProperties(); + + /** + * 本地文件配置 + */ + private LocalProperties local = new LocalProperties(); + + @Data + public static class StoreProperties { + + /** + * 资源地址 + */ + private String endpoint; + + /** + * 外网资源地址 + */ + private String transformEndpoint; + + /** + * AccessKey + */ + private String accessKeyId; + + /** + * SecretKey + */ + private String accessKeySecret; + + /** + * 桶名 + */ + private String bucketName; + + /** + * 根目录 + */ + private String rootDirectory; + + /** + * 链接过期时间,单位秒 + */ + private Long expires = 3600L; + } + + @Data + public static class LocalProperties { + + /** + * 本地存储根目录 + */ + private String rootPath = "tmp/file-storage"; + + /** + * 文件访问地址 + */ + private String accessUrl = "http://127.0.0.1:8107/file/static"; + + /** + * 链接过期时间,单位秒,本地存储暂不校验 + */ + private Long expires = 3600L; + } +} diff --git a/blade-service/blade-file/src/main/java/org/springblade/file/config/ObsProperties.java b/blade-service/blade-file/src/main/java/org/springblade/file/config/ObsProperties.java new file mode 100644 index 0000000..9e42e6e --- /dev/null +++ b/blade-service/blade-file/src/main/java/org/springblade/file/config/ObsProperties.java @@ -0,0 +1,69 @@ +package org.springblade.file.config; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; + +import java.io.File; +import java.time.Duration; + +/** + * obs 配置 + * @author bfhuange + * @date 2024/10/21 + */ +@Data +@ConfigurationProperties(prefix = "obs") +public class ObsProperties { + /** + * OBS endpoint + */ + public String endpoint; + /** + * AK in the access key + */ + public String accessKeyId; + /** + * SK in the access key + */ + public String accessKeySecret; + /** + * 桶名 + */ + public String bucketName; + /** + * 根目录 + */ + public String rootDirectory; + /** + * 过期时间,默认1小时 + */ + public Long expires = Duration.ofHours(1).toSeconds(); + /** + * 本地临时下载文件目录 + */ + public String tmpDownloadDir = "tmp" + File.separator + "download"; + /** + * 本地临时解压文件目录 + */ + public String tmpUnzipDir = "tmp" + File.separator + "unzip"; + /** + * obs上压缩文件上传目录 + */ + public String zipUploadDir = "part"; + /** + * obs上解压后的文件上传目录 + */ + public String unzipUploadDir = "unzip"; + /** + * obs上设置了过期时间自动删除的上传目录 + */ + public String tmpUploadDir = "tmp"; + /** + * 下载进度间隔,默认 10m + */ + public Long downloadProgressInterval = 10 * 1024 * 1024L; + /** + * 上传进度间隔,默认 10m + */ + public Long uploadProgressInterval = 10 * 1024 * 1024L; +} diff --git a/blade-service/blade-file/src/main/java/org/springblade/file/controller/AttachmentController.java b/blade-service/blade-file/src/main/java/org/springblade/file/controller/AttachmentController.java new file mode 100644 index 0000000..fadf4af --- /dev/null +++ b/blade-service/blade-file/src/main/java/org/springblade/file/controller/AttachmentController.java @@ -0,0 +1,138 @@ +/** + * BladeX Commercial License Agreement + * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved. + *

+ * Use of this software is governed by the Commercial License Agreement + * obtained after purchasing a license from BladeX. + *

+ * 1. This software is for development use only under a valid license + * from BladeX. + *

+ * 2. Redistribution of this software's source code to any third party + * without a commercial license is strictly prohibited. + *

+ * 3. Licensees may copyright their own code but cannot use segments + * from this software for such purposes. Copyright of this software + * remains with BladeX. + *

+ * Using this software signifies agreement to this License, and the software + * must not be used for illegal purposes. + *

+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is + * not liable for any claims arising from secondary or illegal development. + *

+ * Author: Chill Zhuang (bladejava@qq.com) + */ +package org.springblade.file.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.IsAdmin; +import org.springblade.core.tenant.annotation.NonDS; +import org.springblade.core.tool.api.R; +import org.springblade.core.tool.utils.Func; +import org.springblade.file.pojo.entity.Attachment; +import org.springblade.file.service.IAttachmentService; +import org.springframework.web.bind.annotation.*; + +/** + * 附件表 控制器 + * + * @author Chill + */ +@NonDS +@RestController +@AllArgsConstructor +//@IsAdmin +@RequestMapping("/attachment") +@Tag(name = "附件", description = "附件") +public class AttachmentController extends BladeController { + + private final IAttachmentService attachmentService; + + /** + * 详情 + */ + @GetMapping("/detail") + @ApiOperationSupport(order = 1) + @Operation(summary = "详情", description = "传入attach") + public R detail(Attachment attach) { + Attachment detail = attachmentService.getOne(Condition.getQueryWrapper(attach)); + return R.data(detail); + } + + /** + * 分页 附件表 + */ + @GetMapping("/list") + @ApiOperationSupport(order = 2) + @Operation(summary = "分页", description = "传入attach") + public R> list(Attachment attach, Query query) { + attach.setSize(null); + query.setDescs("create_time"); + IPage pages = attachmentService.page(Condition.getPage(query), Condition.getQueryWrapper(attach)); + return R.data(pages); + } + +// /** +// * 自定义分页 附件表 +// */ +// @GetMapping("/page") +// @ApiOperationSupport(order = 3) +// @Operation(summary = "分页", description = "传入attach") +// public R> page(Attachment attach, Query query) { +// IPage pages = attachmentService.selectAttachPage(Condition.getPage(query), attach); +// return R.data(pages); +// } + + /** + * 新增 附件表 + */ + @PostMapping("/save") + @ApiOperationSupport(order = 4) + @Operation(summary = "新增", description = "传入attach") + public R save(@Valid @RequestBody Attachment attach) { + return R.status(attachmentService.save(attach)); + } + + /** + * 修改 附件表 + */ + @PostMapping("/update") + @ApiOperationSupport(order = 5) + @Operation(summary = "修改", description = "传入attach") + public R update(@Valid @RequestBody Attachment attach) { + return R.status(attachmentService.updateById(attach)); + } + + /** + * 新增或修改 附件表 + */ + @PostMapping("/submit") + @ApiOperationSupport(order = 6) + @Operation(summary = "新增或修改", description = "传入attach") + public R submit(@Valid @RequestBody Attachment attach) { + return R.status(attachmentService.saveOrUpdate(attach)); + } + + + /** + * 删除 附件表 + */ + @PostMapping("/remove") + @ApiOperationSupport(order = 7) + @Operation(summary = "逻辑删除", description = "传入ids") + public R remove(@Parameter(description = "主键集合", required = true) @RequestParam String ids) { + return R.status(attachmentService.removeByIds(Func.toLongList(ids))); + } + + +} diff --git a/blade-service/blade-file/src/main/java/org/springblade/file/controller/BusinessAttachmentController.java b/blade-service/blade-file/src/main/java/org/springblade/file/controller/BusinessAttachmentController.java new file mode 100644 index 0000000..d698223 --- /dev/null +++ b/blade-service/blade-file/src/main/java/org/springblade/file/controller/BusinessAttachmentController.java @@ -0,0 +1,75 @@ +package org.springblade.file.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.tool.api.R; +import org.springblade.file.pojo.dto.BusinessAttachmentAddDTO; +import org.springblade.file.pojo.dto.BusinessAttachmentBatchAddDTO; +import org.springblade.file.pojo.dto.BusinessAttachmentQueryDTO; +import org.springblade.file.pojo.vo.BusinessAttachmentVO; +import org.springblade.file.service.IBusinessAttachmentService; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +/** + * 业务附件信息表 控制器 + * + * @author BladeX + * @since 2024-09-04 + */ +@Valid +@RestController +@AllArgsConstructor +@RequestMapping("businessAttachment") +@Tag(name = "业务附件信息表", description = "业务附件信息表接口") +public class BusinessAttachmentController { + + private final IBusinessAttachmentService businessAttachmentService; + + /** + * 业务附件信息表 新增 + */ + @PostMapping("/batchSave") + @ApiOperationSupport(order = 1) + @Operation(summary = "批量新增", description = "批量新增") + public R batchSave(@Validated @RequestBody List params) { + return R.status(businessAttachmentService.saveAndRemove(params, null)); + } + + /** + * 业务附件信息表 新增 + */ + @PostMapping("/save") + @ApiOperationSupport(order = 2) + @Operation(summary = "新增", description = "新增") + public R save(@Validated @RequestBody BusinessAttachmentBatchAddDTO param) { + return R.status(businessAttachmentService.saveAndRemove(param)); + } + + /** + * 业务附件信息表 删除 + */ + @PostMapping("/remove") + @ApiOperationSupport(order = 3) + @Operation(summary = "逻辑删除", description = "传入bizId") + public R remove(@Parameter(description = "bizId", required = true) @RequestParam Long bizId) { + return R.status(businessAttachmentService.removeByBizIds(List.of(bizId))); + } + + /** + * 查询业务附件信息列表 + */ + @PostMapping("/queryList") + @ApiOperationSupport(order = 4) + @Operation(summary = "查询业务附件信息列表", description = "查询业务附件信息列表") + public R> queryList(@Validated @RequestBody BusinessAttachmentQueryDTO param) { + return R.data(businessAttachmentService.queryList(param)); + } + +} diff --git a/blade-service/blade-file/src/main/java/org/springblade/file/controller/BusinessUnzipTaskController.java b/blade-service/blade-file/src/main/java/org/springblade/file/controller/BusinessUnzipTaskController.java new file mode 100644 index 0000000..555687f --- /dev/null +++ b/blade-service/blade-file/src/main/java/org/springblade/file/controller/BusinessUnzipTaskController.java @@ -0,0 +1,69 @@ +package org.springblade.file.controller; + +import com.baomidou.mybatisplus.core.metadata.IPage; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.AllArgsConstructor; +import org.springblade.core.log.annotation.ApiLog; +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.secure.utils.AuthUtil; +import org.springblade.core.tool.api.R; +import org.springblade.core.tool.utils.Func; +import org.springblade.file.pojo.dto.BusinessUnzipTaskQueryDTO; +import org.springblade.file.pojo.dto.BusinessUnzipTaskUpdateDTO; +import org.springblade.file.pojo.enums.UnzipTask; +import org.springblade.file.pojo.vo.BusinessUnzipTaskListVO; +import org.springblade.file.service.IBusinessUnzipTaskService; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +/** + * @author bfhuange + * @date 2024/10/28 + */ +@RestController +@AllArgsConstructor +@RequestMapping("/businessUnzipTask") +@Tag(name = "业务文件解压任务", description = "业务文件解压任务") +public class BusinessUnzipTaskController { + + private final IBusinessUnzipTaskService unzipTaskService; + + /** + * 文件任务列表 + * @param param + * @param query + */ + @GetMapping("/list") + @Operation(summary = "业务文件解压任务分页查询", description = "业务文件解压任务分页查询") + @PreAuth("hasPermission('waybill:uploadTask:list')") + public R> list(BusinessUnzipTaskQueryDTO param, Query query) { + if (param == null) { + param = new BusinessUnzipTaskQueryDTO(); + } + // 只查文件任务类型 + param.setRelateType(UnzipTask.RelateType.FILE_TASK.getCode()); + if (param.isCurrentUser()) { + // 只查自己的 + param.setCurrentUserId(AuthUtil.getUserId()); + } + IPage pages = unzipTaskService.selectPage(Condition.getPage(query), param); + return R.data(pages); + } + + /** + * 业务文件解压任务更新(重新上传) + * @param param + * @return + */ + @ApiLog("上传任务-重新上传") + @PostMapping("/update") + @Operation(summary = "业务文件解压任务更新(重新上传)", description = "业务文件解压任务更新(重新上传)") + @PreAuth("hasPermission('waybill:uploadTask:update')") + public R update(@Validated @RequestBody BusinessUnzipTaskUpdateDTO param) { + Func.requireNotNull(param.getId(), "id不能为空"); + return R.status(unzipTaskService.updateReupload(param)); + } +} diff --git a/blade-service/blade-file/src/main/java/org/springblade/file/controller/FileController.java b/blade-service/blade-file/src/main/java/org/springblade/file/controller/FileController.java new file mode 100644 index 0000000..dc2c499 --- /dev/null +++ b/blade-service/blade-file/src/main/java/org/springblade/file/controller/FileController.java @@ -0,0 +1,175 @@ +package org.springblade.file.controller; + +import com.alibaba.csp.sentinel.annotation.SentinelResource; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +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.R; +import org.springblade.core.tool.utils.StringUtil; +import org.springblade.file.listener.FileEvent; +import org.springblade.file.pojo.dto.CertificateAttachmentDTO; +import org.springblade.file.pojo.dto.FileCertificateBatchRecognitionDTO; +import org.springblade.file.pojo.entity.Attachment; +import org.springblade.file.service.IAttachmentService; +import org.springblade.file.service.IFileService; +import org.springblade.file.service.IOCRConvertService; +import org.springblade.thirdparty.ocr.pojo.dto.CertificateRecognitionDTO; +import org.springblade.thirdparty.ocr.pojo.vo.TransportCertificateVO; +import org.springblade.thirdparty.ocr.service.IOCRService; +import org.springblade.thirdparty.wps.service.IWpsService; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationEvent; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import java.util.List; +import java.util.Optional; + +/** + * @ClassName: fileUploadController + * @Author: zhaowei + * @Date: 2024/8/22 + */ +@Valid +@NonDS +@RestController +@AllArgsConstructor +@RequestMapping("/file") +@Tag(name = "文件相关管理", description = "文件相关管理") +public class FileController extends BladeController { + + private final IAttachmentService attachmentService; + private final IFileService fileService; + private final IWpsService wpsService; + private final IOCRService ocrService; + private final IOCRConvertService ocrConvertService; + private final ApplicationContext applicationContext; + + @ApiLog("附件管理-批量上传") + @Operation(summary = "OBS批量上传", description = "OBS批量上传,参数名:files") + @PostMapping("/ossUpload") + public R 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) { + MultipartFile[] files = {file}; + R> result = attachmentService.batchOssUpload(files, null); + List data = result.getData(); + if(data != null && !data.isEmpty())return R.data(data.get(0)); + return R.fail("上传失败"); + } + + @Operation(summary = "获取附件,多个附件id用逗号分割", description = "获取附件,多个附件id用逗号分割") + @GetMapping("/getAttachment") + public R getAttachment(@RequestParam(value = "id") String id) { + return R.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) { + if (StringUtil.isBlank(attachmentName)) { + // 附件名为空,查询附件名 + Attachment attachment = attachmentService.getOne(Wrappers.lambdaQuery() + .eq(Attachment::getObjectKey, objectKey) + .last("limit 1") + ); + attachmentName = Optional.ofNullable(attachment) + .map(Attachment::getAttachmentName) + .orElse(null); + if (attachmentName != null) { + // 附件名中如果有逗号,则替换为下划线 + attachmentName = attachmentName.replaceAll(",", "_"); + } + } + return R.data(fileService.getFileUrl(objectKey, attachmentName, null)); + } + + /** + * 获取wps文件预览url + * @param attachmentId * @param attachmentName + * @return + */ + @Operation(summary = "获取wps文件预览url", description = "获取wps文件预览url") + @GetMapping("/getWpsFilePreviewUrl") + public R 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)); + } + + /** + * 获取wps文件编辑url + * @param attachmentId + * @param attachmentName + * @return + */ + @Operation(summary = "获取wps文件编辑url", description = "获取wps文件编辑url") + @GetMapping("/getWpsFileEditUrl") + public R 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)); + } + + @Operation(summary = "批量获取文件url", description = "批量获取文件url,参数:objectKey数组") + @PostMapping("/getFileUrls") + public R getFileUrls(@RequestBody List objectKeys) { + return R.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) { + String imageUrl = StringUtil.isNotBlank(url) ? url : objectKey; + if (StringUtil.isBlank(imageUrl)) { + return R.fail("图片地址不能为空"); + } + if (!imageUrl.startsWith("http://") && !imageUrl.startsWith("https://")) { + imageUrl = fileService.getFileUrl(imageUrl, null); + } + return R.data(ocrService.recognitionIDCard(List.of(imageUrl))); + } + + @SentinelResource("ocr:batchCards") + @ApiLog("OCR识别-识别车辆运输凭证(不知道类型)") + @Operation(summary = "识别车辆运输凭证,不知道类型", description = "参数:objectKeylist") + @PostMapping("/recognitionTransportCertificates") + public R recognitionTransportCertificates(@RequestBody List objectKeys, @RequestParam(value = "projectAbbreviation", required = false) String projectAbbreviation, @RequestParam(value = "code", required = false) String code) { + return R.data(ocrConvertService.recognitionTransportCertificate(new FileCertificateBatchRecognitionDTO(objectKeys, projectAbbreviation, code, null))); + } + + @ApiLog("OCR识别-识别车辆运输凭证(知道类型)") + @Operation(summary = "识别车辆运输凭证,知道类型", description = "参数:objectKeylist") + @PostMapping("/recognitionTransportCertificatesByType") + public R recognitionTransportCertificatesByType(@RequestBody CertificateAttachmentDTO param, @RequestParam(value = "code", required = false) String code) { + CertificateRecognitionDTO recognitionParam = new CertificateRecognitionDTO(); + recognitionParam.setPoundUrl(fileService.getFileUrl(param.getPoundObjectKey(), null)); + recognitionParam.setCarFrontUrl(fileService.getFileUrl(param.getCarFrontObjectKey(), null)); + recognitionParam.setDriverLicenseUrl(fileService.getFileUrl(param.getDriverLicenseObjectKey(), null)); + recognitionParam.setVehicleLicenseUrl(fileService.getFileUrl(param.getVehicleLicenseObjectKey(), null)); + recognitionParam.setName(param.getProjectAbbreviation()); + return R.data(ocrService.recognitionTransportCertificate(recognitionParam, code)); + } + + @Operation(hidden = true) + @PostMapping("/publishEvent") + public R publishEvent(String code, Long id) { + ApplicationEvent event = FileEvent.createEvent(code, id); + if (event == null) { + return R.fail("事件名称错误"); + } + applicationContext.publishEvent(event); + return R.status(true); + } +} diff --git a/blade-service/blade-file/src/main/java/org/springblade/file/controller/FileTaskController.java b/blade-service/blade-file/src/main/java/org/springblade/file/controller/FileTaskController.java new file mode 100644 index 0000000..071625d --- /dev/null +++ b/blade-service/blade-file/src/main/java/org/springblade/file/controller/FileTaskController.java @@ -0,0 +1,85 @@ +package org.springblade.file.controller; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.Parameters; +import io.swagger.v3.oas.annotations.enums.ParameterIn; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotNull; +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.R; +import org.springblade.file.pojo.dto.FileTaskCreateDTO; +import org.springblade.file.pojo.dto.FileTaskUpdateDTO; +import org.springblade.file.pojo.dto.IdsDTO; +import org.springblade.file.pojo.vo.FileTaskUpdateVO; +import org.springblade.file.service.IFileTaskService; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +/** + * @ClassName: FileTaskController + * @Author: zhaowei + * @Date: 2024/8/28 + */ +@Valid +@NonDS +@RestController +@AllArgsConstructor +@RequestMapping("/fileTask") +@Tag(name = "文件任务", description = "文件任务") +public class FileTaskController extends BladeController { + + private final IFileTaskService fileTaskService; + + @GetMapping("/queryFileTask") + @Operation(summary = "根据文件的MD5值查询文件任务", description = "文件的MD5值检查分段,参数名:md5") + public R queryFileTask(@RequestParam(value = "md5") String md5) { + FileTaskUpdateVO fileTask = fileTaskService.queryFileTask(null, md5); + return R.data(fileTask); + } + + @ApiLog("上传任务-创建文件任务") + @PostMapping("/createFileTask") + @Operation(summary = "创建文件任务", description = "创建文件任务") + public R createFileTask(@RequestBody FileTaskCreateDTO param) { + Long id = fileTaskService.createFileTask(param); + return R.data(fileTaskService.queryFileTask(id, null)); + } + + @ApiLog("上传任务-更新文件任务进度") + @PostMapping("/updateFileTask") + @Operation(summary = "更新文件任务进度", description = "更新文件任务进度") + public R updateFileTask(@RequestBody FileTaskUpdateDTO param) { + return R.data(fileTaskService.updateFileTask(param)); + } + + @ApiLog("上传任务-修改文件任务状态为失败") + @PostMapping("/updateFailed") + @Operation(summary = "更新文件任务为失败", description = "更新文件任务为失败") + public R updateFailed(@NotNull(message = "文件任务id不能为空") Long id) { + return R.data(fileTaskService.updateFailed(id)); + } + + @ApiLog("上传任务-暂停文件任务") + @PostMapping("/updatePaused") + @Operation(summary = "更新文件任务为暂停", description = "更新文件任务为暂停") + public R updatePaused(@Validated @RequestBody IdsDTO param) { + return R.status(fileTaskService.updatePaused(param.getIds())); + } + + @GetMapping("/getUploadUrl") + @Parameters({ + @Parameter(name = "id", description = "文件任务id", in = ParameterIn.QUERY, schema = @Schema(type = "string")), + @Parameter(name = "partNumber", description = "分段编号", in = ParameterIn.QUERY, schema = @Schema(type = "int")) + }) + @Operation(summary = "根据文件任务id查询上传链接", description = "根据文件任务id查询上传链接") + public R getUploadUrl(Long id, Integer partNumber) { + String uploadUrl = fileTaskService.getUploadUrl(id, partNumber); + return R.data(uploadUrl); + } +} diff --git a/blade-service/blade-file/src/main/java/org/springblade/file/controller/LocalFileController.java b/blade-service/blade-file/src/main/java/org/springblade/file/controller/LocalFileController.java new file mode 100644 index 0000000..fbd7f0a --- /dev/null +++ b/blade-service/blade-file/src/main/java/org/springblade/file/controller/LocalFileController.java @@ -0,0 +1,87 @@ +/** + * BladeX Commercial License Agreement + * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved. + *

+ * Use of this software is governed by the Commercial License Agreement + * obtained after purchasing a license from BladeX. + *

+ * 1. This software is for development use only under a valid license + * from BladeX. + *

+ * 2. Redistribution of this software's source code to any third party + * without a commercial license is strictly prohibited. + *

+ * 3. Licensees may copyright their own code but cannot use segments + * from this software for such purposes. Copyright of this software + * remains with BladeX. + *

+ * Using this software signifies agreement to this License, and the software + * must not be used for illegal purposes. + *

+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is + * not liable for any claims arising from secondary or illegal development. + *

+ * Author: Chill Zhuang (bladejava@qq.com) + */ +package org.springblade.file.controller; + +import jakarta.servlet.http.HttpServletResponse; +import lombok.AllArgsConstructor; +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.tenant.annotation.NonDS; +import org.springblade.core.tool.utils.StringUtil; +import org.springblade.file.config.FileStorageProperties; +import org.springframework.http.MediaType; +import org.springframework.util.StreamUtils; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; + +/** + * 本地文件访问控制器 + * + * @author Chill + */ +@NonDS +@RestController +@AllArgsConstructor +@RequestMapping("/file") +public class LocalFileController { + + private final FileStorageProperties fileStorageProperties; + + @PreAuth(AuthConstant.PERMIT_ALL) + @GetMapping("/static") + public void staticFile(@RequestParam("objectKey") String objectKey, HttpServletResponse response) { + Path objectPath = resolveObjectPath(objectKey); + if (!Files.exists(objectPath) || !Files.isRegularFile(objectPath)) { + throw new ServiceException("文件不存在"); + } + try (InputStream inputStream = Files.newInputStream(objectPath)) { + String contentType = Files.probeContentType(objectPath); + response.setContentType(StringUtil.isBlank(contentType) ? MediaType.APPLICATION_OCTET_STREAM_VALUE : contentType); + StreamUtils.copy(inputStream, response.getOutputStream()); + } catch (Exception exception) { + throw new ServiceException("读取本地文件异常"); + } + } + + private Path resolveObjectPath(String objectKey) { + if (StringUtil.isBlank(objectKey)) { + throw new ServiceException("文件key不能为空"); + } + Path rootPath = Path.of(fileStorageProperties.getLocal().getRootPath()).toAbsolutePath().normalize(); + Path objectPath = rootPath.resolve(objectKey).normalize(); + if (!objectPath.startsWith(rootPath)) { + throw new ServiceException("文件key不合法"); + } + return objectPath; + } +} diff --git a/blade-service/blade-file/src/main/java/org/springblade/file/convert/BusinessAttachmentConvert.java b/blade-service/blade-file/src/main/java/org/springblade/file/convert/BusinessAttachmentConvert.java new file mode 100644 index 0000000..6a81f10 --- /dev/null +++ b/blade-service/blade-file/src/main/java/org/springblade/file/convert/BusinessAttachmentConvert.java @@ -0,0 +1,44 @@ +package org.springblade.file.convert; + +import org.mapstruct.Mapper; +import org.mapstruct.MappingConstants; +import org.mapstruct.ReportingPolicy; +import org.springblade.file.pojo.dto.BusinessAttachmentAddDTO; +import org.springblade.file.pojo.dto.BusinessAttachmentBatchAddDTO; +import org.springblade.file.pojo.entity.Attachment; +import org.springblade.file.pojo.entity.BusinessAttachment; +import org.springblade.file.pojo.vo.AttachmentDetailVO; + +import java.util.Collections; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * @author bfhuange + * @date 2024/9/4 + */ +@Mapper(componentModel = MappingConstants.ComponentModel.SPRING, unmappedTargetPolicy = ReportingPolicy.IGNORE) +public interface BusinessAttachmentConvert { + BusinessAttachment dto2entity(BusinessAttachmentAddDTO dto); + + List dtos2entities(List dtos); + + AttachmentDetailVO entity2vo(Attachment entity); + + default List dto2entities(BusinessAttachmentBatchAddDTO dto) { + if (dto == null || dto.getDetails() == null) { + return Collections.emptyList(); + } + AtomicInteger sort = new AtomicInteger(1); + return dto.getDetails().stream() + .flatMap(detail -> detail.getAttachmentIds().stream().map(attachmentId -> { + BusinessAttachment businessAttachment = new BusinessAttachment(); + businessAttachment.setAttachmentId(attachmentId); + businessAttachment.setBizId(dto.getBizId()); + businessAttachment.setBizCode(detail.getBizCode()); + businessAttachment.setSort(sort.getAndIncrement()); + return businessAttachment; + })).toList(); + } + +} diff --git a/blade-service/blade-file/src/main/java/org/springblade/file/convert/FileTaskConvert.java b/blade-service/blade-file/src/main/java/org/springblade/file/convert/FileTaskConvert.java new file mode 100644 index 0000000..f966920 --- /dev/null +++ b/blade-service/blade-file/src/main/java/org/springblade/file/convert/FileTaskConvert.java @@ -0,0 +1,31 @@ +package org.springblade.file.convert; + +import org.mapstruct.Mapper; +import org.mapstruct.MappingConstants; +import org.mapstruct.ReportingPolicy; +import org.springblade.file.pojo.dto.BusinessUnzipTaskAddDTO; +import org.springblade.file.pojo.dto.FileTaskCreateDTO; +import org.springblade.file.pojo.entity.BusinessUnzipTask; +import org.springblade.file.pojo.entity.FileTask; +import org.springblade.file.pojo.entity.FileTaskPart; +import org.springblade.file.pojo.vo.FileTaskUpdateVO; +import org.springblade.file.pojo.vo.FileTaskVO; +import org.springblade.file.pojo.vo.PartEtagVO; + +/** + * @author bfhuange + * @date 2024/10/22 + */ +@Mapper(componentModel = MappingConstants.ComponentModel.SPRING, unmappedTargetPolicy = ReportingPolicy.IGNORE) +public interface FileTaskConvert { + + FileTaskVO entity2vo(FileTask entity); + + FileTaskUpdateVO entity2updateVo(FileTask entity); + + FileTask dto2entity(FileTaskCreateDTO dto); + + PartEtagVO entity2vo(FileTaskPart entity); + + BusinessUnzipTask dto2entity(BusinessUnzipTaskAddDTO dto); +} diff --git a/blade-service/blade-file/src/main/java/org/springblade/file/convert/OCRConverter.java b/blade-service/blade-file/src/main/java/org/springblade/file/convert/OCRConverter.java new file mode 100644 index 0000000..bd551bb --- /dev/null +++ b/blade-service/blade-file/src/main/java/org/springblade/file/convert/OCRConverter.java @@ -0,0 +1,17 @@ +package org.springblade.file.convert; + +import org.mapstruct.Mapper; +import org.mapstruct.MappingConstants; +import org.mapstruct.ReportingPolicy; +import org.springblade.file.pojo.dto.FileCertificateBatchRecognitionDTO; +import org.springblade.thirdparty.ocr.pojo.dto.CertificateBatchRecognitionDTO; + +/** + * @author bfhuange + * @since 2025/3/13 + */ +@Mapper(componentModel = MappingConstants.ComponentModel.SPRING, unmappedTargetPolicy = ReportingPolicy.IGNORE) +public interface OCRConverter { + + CertificateBatchRecognitionDTO dto2ocr(FileCertificateBatchRecognitionDTO dto); +} diff --git a/blade-service/blade-file/src/main/java/org/springblade/file/exception/UnzipFailException.java b/blade-service/blade-file/src/main/java/org/springblade/file/exception/UnzipFailException.java new file mode 100644 index 0000000..739c41e --- /dev/null +++ b/blade-service/blade-file/src/main/java/org/springblade/file/exception/UnzipFailException.java @@ -0,0 +1,28 @@ +package org.springblade.file.exception; + +import lombok.Getter; + +import java.io.File; + +/** + * 文件解压失败异常 + * @author bfhuange + * @date 2024/11/11 + */ +@Getter +public class UnzipFailException extends RuntimeException{ + /** + * 解压目录 + */ + private final File unzipDir; + + public UnzipFailException(Throwable cause, File unzipDir) { + super(cause); + this.unzipDir = unzipDir; + } + + public UnzipFailException(String message, File unzipDir) { + super(message); + this.unzipDir = unzipDir; + } +} diff --git a/blade-service/blade-file/src/main/java/org/springblade/file/feign/FileClient.java b/blade-service/blade-file/src/main/java/org/springblade/file/feign/FileClient.java new file mode 100644 index 0000000..f872c9c --- /dev/null +++ b/blade-service/blade-file/src/main/java/org/springblade/file/feign/FileClient.java @@ -0,0 +1,156 @@ +package org.springblade.file.feign; + +import com.alibaba.csp.sentinel.annotation.SentinelResource; +import io.swagger.v3.oas.annotations.Hidden; +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotNull; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springblade.core.tool.api.R; +import org.springblade.core.tool.utils.Func; +import org.springblade.file.config.ObsProperties; +import org.springblade.file.pojo.dto.*; +import org.springblade.file.pojo.entity.Attachment; +import org.springblade.file.pojo.vo.*; +import org.springblade.file.service.*; +import org.springblade.file.util.ObjectKeyUtils; +import org.springblade.thirdparty.ocr.pojo.vo.TransportCertificateVO; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; + +import java.util.List; + +/** + * @author bfhuange + * @date 2024/10/21 + */ +@Slf4j +@Valid +@Hidden +@RestController +@AllArgsConstructor +public class FileClient implements IFileClient, IFileUploadClient { + + private final IAttachmentService attachmentService; + private final IBusinessAttachmentService businessAttachmentService; + private final IBusinessUnzipTaskService businessUnzipTaskService; + private final IOCRConvertService ocrConvertService; + private final IFileService fileService; + private final ObsProperties obsProperties; + + @Override + @GetMapping(ATTACHMENT) + public R> getAttachment(String id) { + return R.data(attachmentService.getAttachment(id)); + } + + @Override + public R getAttachmentDetail(Long id) { + return R.data(attachmentService.getDetail(id)); + } + + @Override + public R saveAndRemoveBusinessAttachment(@RequestBody @Validated BusinessAttachmentBatchAddDTO param) { + return R.status(businessAttachmentService.saveAndRemove(param)); + } + + @Override + public R batchSaveAndRemoveBusinessAttachment(@RequestBody List<@Valid BusinessAttachmentAddDTO> params, @RequestParam(value = "bizId", required = false) Long bizId) { + return R.status(businessAttachmentService.saveAndRemove(params, new BusinessAttachmentDeleteDTO(bizId))); + } + + @Override + public R batchSaveAndRemoveByParamBusinessAttachment(@RequestBody List<@Valid BusinessAttachmentAddDTO> params, @Valid BusinessAttachmentDeleteDTO deleteParam) { + return R.status(businessAttachmentService.saveAndRemove(params, deleteParam)); + } + + @Override + public R batchSaveAndRemoveBusinessAttachment(List<@Valid BusinessAttachmentAddDTO> params) { + return R.status(businessAttachmentService.saveAndRemove(params, null)); + } + + @Override + public R removeBusinessAttachment(@NotNull(message = "业务id不能为空") @RequestParam(value = "bizId", required = false) Long bizId) { + businessAttachmentService.removeByBizIds(List.of(bizId)); + return R.data(true); + } + + @Override + public R> getBusinessAttachments(@Validated @RequestBody BusinessAttachmentQueryDTO param) { + return R.data(businessAttachmentService.queryList(param)); + } + + @Override + public R saveBusinessUnzipTask(BusinessUnzipTaskAddDTO param) { + return R.status(businessUnzipTaskService.save(param)); + } + + @Override + public R updateBusinessUnzipTask(BusinessUnzipTaskUpdateDTO param) { + Func.requireNotNull(param.getBizId(), "业务id不能为空"); + return R.status(businessUnzipTaskService.updateReupload(param)); + } + + @Override + public R cancelBusinessUnzipTask(@NotNull(message = "业务id不能为空") Long bizId) { + return R.status(businessUnzipTaskService.cancelBusinessUnzipTask(bizId, true)); + } + + @Override + public R removeBusinessUnzipTask(Long bizId) { + return R.status(businessUnzipTaskService.cancelBusinessUnzipTask(bizId, false)); + } + + @Override + public R> getBusinessUnzipTasksByBizIds(List bizIds) { + return R.data(businessUnzipTaskService.getBusinessUnzipTasksByBizIds(bizIds)); + } + + @Override + public R> queryBusinessUnzipDetail(@NotNull(message = "业务id不能为空") Long bizId) { + return R.data(businessUnzipTaskService.queryBusinessUnzipDetail(bizId)); + } + + @SentinelResource("ocr:batchCards") + @Override + public R recognitionTransportCertificates(@Validated @RequestBody FileCertificateBatchRecognitionDTO param) { + return R.data(ocrConvertService.recognitionTransportCertificate(param)); + } + + @Override + public R> getFileUrls(IdsDTO param) { + List list = attachmentService.listByIds(param.getIds()).stream() + .map(attachment -> { + String fileUrl = fileService.getFileUrl(attachment.getObjectKey(), attachment.getAttachmentName(), null); + return new FileUrlVO(attachment.getId(), fileUrl); + }) + .toList(); + return R.data(list); + } + + @Override + public R> ossUpload(MultipartFile[] files) { + R> r = attachmentService.batchOssUpload(files, null); + return R.isNotSuccess(r) ? R.fail(r.getCode(), r.getMsg()) : R.data(r.getData(), r.getMsg()); + } + + @Override + public R ossUploadTemp(MultipartFile file, Integer expires) { + if (expires == null) { + // 默认1天过期 + expires = 1; + } + try { + String objectKey = ObjectKeyUtils.generateObjectKey(obsProperties.getTmpUploadDir(), file.getOriginalFilename()); + String fileUrl = fileService.uploadTempFile(objectKey, file.getInputStream(), expires); + return R.data(new TempFileUrlVO(objectKey, fileUrl)); + } catch (Exception e) { + log.error("上传临时文件异常", e); + return R.fail("上传临时文件异常"); + } + } +} diff --git a/blade-service/blade-file/src/main/java/org/springblade/file/listener/FileEvent.java b/blade-service/blade-file/src/main/java/org/springblade/file/listener/FileEvent.java new file mode 100644 index 0000000..78c9a3a --- /dev/null +++ b/blade-service/blade-file/src/main/java/org/springblade/file/listener/FileEvent.java @@ -0,0 +1,56 @@ +package org.springblade.file.listener; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.springframework.context.ApplicationEvent; + +import java.util.function.Function; +import java.util.stream.Stream; + +/** + * 文件事件枚举 + * @author bfhuange + * @since 2024/11/22 + */ +@AllArgsConstructor +@Getter +public enum FileEvent { + /** + * 上传完成事件 + */ + UPLOADED(FileUploadedEvent::new), + /** + * 解压开始事件 + */ + UNZIP_START(UnzipStartEvent::new), + /** + * 解压完成事件 + */ + UNZIP_COMPLETED(UnzipCompletedEvent::new), + /** + * 解压失败事件 + */ + UNZIP_FAILED(UnzipFailEvent::new), + ; + /** + * 事件函数 + */ + private final Function eventFunction; + + /** + * 根据编码创建事件 + * @param code + * @param id + * @return + */ + public static ApplicationEvent createEvent(String code, Long id) { + if (code == null || code.isBlank()) { + return null; + } + return Stream.of(values()) + .filter(event -> event.name().equalsIgnoreCase(code)) + .findFirst() + .map(event -> event.getEventFunction().apply(id)) + .orElse(null); + } +} diff --git a/blade-service/blade-file/src/main/java/org/springblade/file/listener/FileListener.java b/blade-service/blade-file/src/main/java/org/springblade/file/listener/FileListener.java new file mode 100644 index 0000000..8079a64 --- /dev/null +++ b/blade-service/blade-file/src/main/java/org/springblade/file/listener/FileListener.java @@ -0,0 +1,173 @@ +package org.springblade.file.listener; + +import com.alibaba.fastjson2.JSON; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import lombok.extern.slf4j.Slf4j; +import org.springblade.core.log.exception.ServiceException; +import org.springblade.core.tool.utils.Func; +import org.springblade.file.callback.UnzipCallback; +import org.springblade.file.callback.UnzipEventType; +import org.springblade.file.pojo.entity.BusinessUnzipTask; +import org.springblade.file.pojo.enums.UnzipTask; +import org.springblade.file.pojo.vo.FileVO; +import org.springblade.file.service.IBusinessUnzipDetailService; +import org.springblade.file.service.IBusinessUnzipTaskService; +import org.springframework.context.event.EventListener; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Component; +import org.springframework.transaction.event.TransactionPhase; +import org.springframework.transaction.event.TransactionalEventListener; + +import java.util.List; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; + +/** + * 文件监听器 + * @author bfhuange + * @date 2024/11/8 + */ +@Slf4j +@Component +public class FileListener { + + private final IBusinessUnzipTaskService taskService; + private final IBusinessUnzipDetailService detailService; + private final Map callbackMap; + + public FileListener(IBusinessUnzipTaskService taskService, IBusinessUnzipDetailService detailService, List callbacks) { + this.taskService = taskService; + this.detailService = detailService; + this.callbackMap = callbacks.stream() + .collect(Collectors.toMap(UnzipCallback::getBizCode, Function.identity(), (a, b) -> { + throw new ServiceException("重复的解压完成回调接口"); + })); + } + + /** + * 文件上传完成监听器 + * @param event + */ + @Async + @TransactionalEventListener(value = FileUploadedEvent.class, phase = TransactionPhase.AFTER_COMMIT, fallbackExecution = true) + public void fileUploaded(FileUploadedEvent event) { + try { + Long fileTaskId = event.getFileTaskId(); + log.info("文件上传任务完成事件 id:{}", fileTaskId); + BusinessUnzipTask businessUnzipTask = taskService.getOne(Wrappers.lambdaQuery() + .eq(BusinessUnzipTask::getRelateId, fileTaskId) + .last("limit 1") + ); + if (businessUnzipTask != null) { + // 存在解压任务,更新任务为上传完成 + BusinessUnzipTask updateParam = new BusinessUnzipTask(); + updateParam.setId(businessUnzipTask.getId()); + updateParam.setStatus(UnzipTask.Status.UPLOADED.getCode()); + taskService.updateById(updateParam); + // 解压开始 + this.unzipStart(new UnzipStartEvent(businessUnzipTask)); + } + } catch (Exception e) { + log.error("文件上传任务完成事件 异常", e); + } + } + + /** + * 解压开始 + * @param event + */ + @Async + @TransactionalEventListener(value = UnzipStartEvent.class, phase = TransactionPhase.AFTER_COMMIT, fallbackExecution = true) + public void unzipStart(UnzipStartEvent event) { + try { + Long unzipTaskId = event.getUnzipTaskId(); + BusinessUnzipTask task = event.getTask(); + log.info("解压开始事件 id:{} 业务解压任务:{}", unzipTaskId, JSON.toJSONString(task)); + if (task == null) { + // 手动触发时,只有id + task = taskService.getById(unzipTaskId); + } + // 回调接口 + callback(task, UnzipEventType.START); + // 解压并保存 + detailService.unzipAndSaveAsync(task.getId(), taskService.getFile(task)); + } catch (Exception e) { + log.error("解压开始事件 异常", e); + } + } + + /** + * 解压完成监听器 + * @param event + */ + @Async + @EventListener(UnzipCompletedEvent.class) + public void unzipCompleted(UnzipCompletedEvent event) { + try { + Long unzipTaskId = event.getUnzipTaskId(); + log.info("解压完成事件 id:{}", unzipTaskId); + // 查询解压任务 + BusinessUnzipTask unzipTask = taskService.getById(unzipTaskId); + Func.requireNotNull(unzipTask, "解压任务不存在"); + // 更新解压任务状态 + BusinessUnzipTask updateParam = new BusinessUnzipTask(); + updateParam.setId(unzipTaskId); + updateParam.setStatus(UnzipTask.Status.FINISHED.getCode()); + taskService.updateById(updateParam); + // 回调接口 + callback(unzipTask, UnzipEventType.SUCCESS); + } catch (Exception e) { + log.error("解压完成事件 异常", e); + } + } + + /** + * 解压失败监听器 + * @param event + */ + @Async + @EventListener(UnzipFailEvent.class) + public void unzipFail(UnzipFailEvent event) { + try { + Long unzipTaskId = event.getUnzipTaskId(); + log.info("解压失败事件 id:{}", unzipTaskId); + // 查询解压任务 + BusinessUnzipTask unzipTask = taskService.getById(unzipTaskId); + Func.requireNotNull(unzipTask, "解压任务不存在"); + // 更新解压任务状态 + BusinessUnzipTask updateParam = new BusinessUnzipTask(); + updateParam.setId(unzipTaskId); + updateParam.setStatus(UnzipTask.Status.UNZIP_FAIL.getCode()); + taskService.updateById(updateParam); + // 回调接口 + callback(unzipTask, UnzipEventType.FAIL); + } catch (Exception e) { + log.error("解压失败事件 异常", e); + } + } + + /** + * 回调接口 + * @param unzipTask + * @param event + */ + private void callback(BusinessUnzipTask unzipTask, UnzipEventType event) { + if (unzipTask == null) { + log.warn("解压回调接口 解压任务为空"); + return; + } + UnzipCallback unzipCallback = callbackMap.get(unzipTask.getBizCode()); + if (unzipCallback == null) { + log.error("未配置业务编码:{}对应的解压回调接口", unzipTask.getBizCode()); + return; + } + Boolean result = null; + try { + result = unzipCallback.callback(unzipTask.getBizId(), event.name()); + } catch (Exception e) { + log.error("解压回调接口异常", e); + } + log.info("解压回调接口参数:{} 业务编码:{} 事件:{} 结果:{}", unzipTask.getBizId(), unzipTask.getBizCode(), event.name(), result); + } +} diff --git a/blade-service/blade-file/src/main/java/org/springblade/file/listener/FileUploadedEvent.java b/blade-service/blade-file/src/main/java/org/springblade/file/listener/FileUploadedEvent.java new file mode 100644 index 0000000..459193e --- /dev/null +++ b/blade-service/blade-file/src/main/java/org/springblade/file/listener/FileUploadedEvent.java @@ -0,0 +1,21 @@ +package org.springblade.file.listener; + +import lombok.Getter; +import org.springframework.context.ApplicationEvent; + +/** + * @author bfhuange + * @date 2024/11/8 + */ +@Getter +public class FileUploadedEvent extends ApplicationEvent { + /** + * 文件任务id + */ + private final Long fileTaskId; + + public FileUploadedEvent(Long fileTaskId) { + super(fileTaskId); + this.fileTaskId = fileTaskId; + } +} diff --git a/blade-service/blade-file/src/main/java/org/springblade/file/listener/UnzipCompletedEvent.java b/blade-service/blade-file/src/main/java/org/springblade/file/listener/UnzipCompletedEvent.java new file mode 100644 index 0000000..32a60ad --- /dev/null +++ b/blade-service/blade-file/src/main/java/org/springblade/file/listener/UnzipCompletedEvent.java @@ -0,0 +1,22 @@ +package org.springblade.file.listener; + +import lombok.Getter; +import org.springframework.context.ApplicationEvent; + +/** + * 解压完成事件 + * @author bfhuange + * @date 2024/11/8 + */ +@Getter +public class UnzipCompletedEvent extends ApplicationEvent { + /** + * 解压任务id + */ + private final Long unzipTaskId; + + public UnzipCompletedEvent(Long unzipTaskId) { + super(unzipTaskId); + this.unzipTaskId = unzipTaskId; + } +} diff --git a/blade-service/blade-file/src/main/java/org/springblade/file/listener/UnzipFailEvent.java b/blade-service/blade-file/src/main/java/org/springblade/file/listener/UnzipFailEvent.java new file mode 100644 index 0000000..771d464 --- /dev/null +++ b/blade-service/blade-file/src/main/java/org/springblade/file/listener/UnzipFailEvent.java @@ -0,0 +1,22 @@ +package org.springblade.file.listener; + +import lombok.Getter; +import org.springframework.context.ApplicationEvent; + +/** + * 解压失败事件 + * @author bfhuange + * @date 2024/11/8 + */ +@Getter +public class UnzipFailEvent extends ApplicationEvent { + /** + * 解压任务id + */ + private final Long unzipTaskId; + + public UnzipFailEvent(Long unzipTaskId) { + super(unzipTaskId); + this.unzipTaskId = unzipTaskId; + } +} diff --git a/blade-service/blade-file/src/main/java/org/springblade/file/listener/UnzipStartEvent.java b/blade-service/blade-file/src/main/java/org/springblade/file/listener/UnzipStartEvent.java new file mode 100644 index 0000000..a317fc6 --- /dev/null +++ b/blade-service/blade-file/src/main/java/org/springblade/file/listener/UnzipStartEvent.java @@ -0,0 +1,35 @@ +package org.springblade.file.listener; + +import lombok.Getter; +import org.springblade.file.pojo.entity.BusinessUnzipTask; +import org.springblade.file.pojo.vo.FileVO; +import org.springframework.context.ApplicationEvent; + +/** + * 解压开始事件 + * @author bfhuange + * @date 2024/11/8 + */ +@Getter +public class UnzipStartEvent extends ApplicationEvent { + /** + * 解压任务id + */ + private final Long unzipTaskId; + /** + * 解压任务 + */ + private final BusinessUnzipTask task; + + public UnzipStartEvent(Long unzipTaskId) { + super(unzipTaskId); + this.unzipTaskId = unzipTaskId; + this.task = null; + } + + public UnzipStartEvent(BusinessUnzipTask task) { + super(task); + this.task = task; + this.unzipTaskId = null; + } +} diff --git a/blade-service/blade-file/src/main/java/org/springblade/file/mapper/AttachmentMapper.java b/blade-service/blade-file/src/main/java/org/springblade/file/mapper/AttachmentMapper.java new file mode 100644 index 0000000..c607e20 --- /dev/null +++ b/blade-service/blade-file/src/main/java/org/springblade/file/mapper/AttachmentMapper.java @@ -0,0 +1,37 @@ +/** + * BladeX Commercial License Agreement + * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved. + *

+ * Use of this software is governed by the Commercial License Agreement + * obtained after purchasing a license from BladeX. + *

+ * 1. This software is for development use only under a valid license + * from BladeX. + *

+ * 2. Redistribution of this software's source code to any third party + * without a commercial license is strictly prohibited. + *

+ * 3. Licensees may copyright their own code but cannot use segments + * from this software for such purposes. Copyright of this software + * remains with BladeX. + *

+ * Using this software signifies agreement to this License, and the software + * must not be used for illegal purposes. + *

+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is + * not liable for any claims arising from secondary or illegal development. + *

+ * Author: Chill Zhuang (bladejava@qq.com) + */ +package org.springblade.file.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.springblade.file.pojo.entity.Attachment; + +/** + * AttachmentMapper 接口 + * + * @author zhaowei + */ +public interface AttachmentMapper extends BaseMapper { +} diff --git a/blade-service/blade-file/src/main/java/org/springblade/file/mapper/AttachmentMapper.xml b/blade-service/blade-file/src/main/java/org/springblade/file/mapper/AttachmentMapper.xml new file mode 100644 index 0000000..d5bc4eb --- /dev/null +++ b/blade-service/blade-file/src/main/java/org/springblade/file/mapper/AttachmentMapper.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/blade-service/blade-file/src/main/java/org/springblade/file/mapper/BusinessAttachmentMapper.java b/blade-service/blade-file/src/main/java/org/springblade/file/mapper/BusinessAttachmentMapper.java new file mode 100644 index 0000000..65b5af0 --- /dev/null +++ b/blade-service/blade-file/src/main/java/org/springblade/file/mapper/BusinessAttachmentMapper.java @@ -0,0 +1,24 @@ +package org.springblade.file.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.springblade.file.pojo.dto.BusinessAttachmentQueryDTO; +import org.springblade.file.pojo.entity.BusinessAttachment; +import org.springblade.file.pojo.vo.BusinessAttachmentVO; + +import java.util.List; + +/** + * 业务附件信息表 Mapper 接口 + * + * @author BladeX + * @since 2024-09-04 + */ +public interface BusinessAttachmentMapper extends BaseMapper { + + /** + * 查询业务附件列表 + * @param param + * @return + */ + List queryList(BusinessAttachmentQueryDTO param); +} diff --git a/blade-service/blade-file/src/main/java/org/springblade/file/mapper/BusinessAttachmentMapper.xml b/blade-service/blade-file/src/main/java/org/springblade/file/mapper/BusinessAttachmentMapper.xml new file mode 100644 index 0000000..0d3b4fd --- /dev/null +++ b/blade-service/blade-file/src/main/java/org/springblade/file/mapper/BusinessAttachmentMapper.xml @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/blade-service/blade-file/src/main/java/org/springblade/file/mapper/BusinessUnzipDetailMapper.java b/blade-service/blade-file/src/main/java/org/springblade/file/mapper/BusinessUnzipDetailMapper.java new file mode 100644 index 0000000..8c284af --- /dev/null +++ b/blade-service/blade-file/src/main/java/org/springblade/file/mapper/BusinessUnzipDetailMapper.java @@ -0,0 +1,16 @@ +package org.springblade.file.mapper; + +import org.springblade.file.pojo.entity.BusinessUnzipDetail; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +/** + *

+ * 业务解压明细表 Mapper 接口 + *

+ * + * @author bfhuange + * @since 2024-10-30 + */ +public interface BusinessUnzipDetailMapper extends BaseMapper { + +} diff --git a/blade-service/blade-file/src/main/java/org/springblade/file/mapper/BusinessUnzipDetailMapper.xml b/blade-service/blade-file/src/main/java/org/springblade/file/mapper/BusinessUnzipDetailMapper.xml new file mode 100644 index 0000000..ff49b0c --- /dev/null +++ b/blade-service/blade-file/src/main/java/org/springblade/file/mapper/BusinessUnzipDetailMapper.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + id, business_unzip_task_id, attachment_id, create_user, create_time, update_user, update_time, is_deleted + + + diff --git a/blade-service/blade-file/src/main/java/org/springblade/file/mapper/BusinessUnzipTaskMapper.java b/blade-service/blade-file/src/main/java/org/springblade/file/mapper/BusinessUnzipTaskMapper.java new file mode 100644 index 0000000..1b0ddfe --- /dev/null +++ b/blade-service/blade-file/src/main/java/org/springblade/file/mapper/BusinessUnzipTaskMapper.java @@ -0,0 +1,37 @@ +package org.springblade.file.mapper; + +import com.baomidou.mybatisplus.core.metadata.IPage; +import org.apache.ibatis.annotations.Param; +import org.springblade.file.pojo.dto.BusinessUnzipTaskQueryDTO; +import org.springblade.file.pojo.entity.BusinessUnzipTask; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.springblade.file.pojo.vo.BusinessUnzipDetailListVO; +import org.springblade.file.pojo.vo.BusinessUnzipTaskListVO; + +import java.util.List; + +/** + *

+ * 业务解压任务表 Mapper 接口 + *

+ * + * @author bfhuange + * @since 2024-10-28 + */ +public interface BusinessUnzipTaskMapper extends BaseMapper { + + /** + * 查询列表 + * @param page + * @param param + * @return + */ + List selectTaskList(IPage page, @Param("param") BusinessUnzipTaskQueryDTO param); + + /** + * 根据业务id查询解压明细 + * @param bizId + * @return + */ + List queryBusinessUnzipDetail(@Param("bizId") Long bizId); +} diff --git a/blade-service/blade-file/src/main/java/org/springblade/file/mapper/BusinessUnzipTaskMapper.xml b/blade-service/blade-file/src/main/java/org/springblade/file/mapper/BusinessUnzipTaskMapper.xml new file mode 100644 index 0000000..3fb6be6 --- /dev/null +++ b/blade-service/blade-file/src/main/java/org/springblade/file/mapper/BusinessUnzipTaskMapper.xml @@ -0,0 +1,95 @@ + + + + + + + + + + + + + + + + + + + + + + + + + id, biz_id, department_id, biz_code, doc_code, relate_type, relate_id, status, tenant_id, create_user, create_time, update_user, update_time, is_deleted + + + + + diff --git a/blade-service/blade-file/src/main/java/org/springblade/file/mapper/FileTaskMapper.java b/blade-service/blade-file/src/main/java/org/springblade/file/mapper/FileTaskMapper.java new file mode 100644 index 0000000..aa70585 --- /dev/null +++ b/blade-service/blade-file/src/main/java/org/springblade/file/mapper/FileTaskMapper.java @@ -0,0 +1,38 @@ +/** + * BladeX Commercial License Agreement + * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved. + *

+ * Use of this software is governed by the Commercial License Agreement + * obtained after purchasing a license from BladeX. + *

+ * 1. This software is for development use only under a valid license + * from BladeX. + *

+ * 2. Redistribution of this software's source code to any third party + * without a commercial license is strictly prohibited. + *

+ * 3. Licensees may copyright their own code but cannot use segments + * from this software for such purposes. Copyright of this software + * remains with BladeX. + *

+ * Using this software signifies agreement to this License, and the software + * must not be used for illegal purposes. + *

+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is + * not liable for any claims arising from secondary or illegal development. + *

+ * Author: Chill Zhuang (bladejava@qq.com) + */ +package org.springblade.file.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.springblade.file.pojo.entity.FileTask; + +/** + * FileTaskMapper 接口 + * + * @author zhaowei + */ +public interface FileTaskMapper extends BaseMapper { + +} diff --git a/blade-service/blade-file/src/main/java/org/springblade/file/mapper/FileTaskMapper.xml b/blade-service/blade-file/src/main/java/org/springblade/file/mapper/FileTaskMapper.xml new file mode 100644 index 0000000..3a983c3 --- /dev/null +++ b/blade-service/blade-file/src/main/java/org/springblade/file/mapper/FileTaskMapper.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/blade-service/blade-file/src/main/java/org/springblade/file/mapper/FileTaskPartMapper.java b/blade-service/blade-file/src/main/java/org/springblade/file/mapper/FileTaskPartMapper.java new file mode 100644 index 0000000..fc3e278 --- /dev/null +++ b/blade-service/blade-file/src/main/java/org/springblade/file/mapper/FileTaskPartMapper.java @@ -0,0 +1,38 @@ +/** + * BladeX Commercial License Agreement + * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved. + *

+ * Use of this software is governed by the Commercial License Agreement + * obtained after purchasing a license from BladeX. + *

+ * 1. This software is for development use only under a valid license + * from BladeX. + *

+ * 2. Redistribution of this software's source code to any third party + * without a commercial license is strictly prohibited. + *

+ * 3. Licensees may copyright their own code but cannot use segments + * from this software for such purposes. Copyright of this software + * remains with BladeX. + *

+ * Using this software signifies agreement to this License, and the software + * must not be used for illegal purposes. + *

+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is + * not liable for any claims arising from secondary or illegal development. + *

+ * Author: Chill Zhuang (bladejava@qq.com) + */ +package org.springblade.file.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.springblade.file.pojo.entity.FileTaskPart; + +/** + * FileUploadRecordMapper 接口 + * + * @author zhaowei + */ +public interface FileTaskPartMapper extends BaseMapper { + +} diff --git a/blade-service/blade-file/src/main/java/org/springblade/file/mapper/FileTaskPartMapper.xml b/blade-service/blade-file/src/main/java/org/springblade/file/mapper/FileTaskPartMapper.xml new file mode 100644 index 0000000..924e84b --- /dev/null +++ b/blade-service/blade-file/src/main/java/org/springblade/file/mapper/FileTaskPartMapper.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/blade-service/blade-file/src/main/java/org/springblade/file/service/IAttachmentService.java b/blade-service/blade-file/src/main/java/org/springblade/file/service/IAttachmentService.java new file mode 100644 index 0000000..0a54e01 --- /dev/null +++ b/blade-service/blade-file/src/main/java/org/springblade/file/service/IAttachmentService.java @@ -0,0 +1,89 @@ +/** + * BladeX Commercial License Agreement + * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved. + *

+ * Use of this software is governed by the Commercial License Agreement + * obtained after purchasing a license from BladeX. + *

+ * 1. This software is for development use only under a valid license + * from BladeX. + *

+ * 2. Redistribution of this software's source code to any third party + * without a commercial license is strictly prohibited. + *

+ * 3. Licensees may copyright their own code but cannot use segments + * from this software for such purposes. Copyright of this software + * remains with BladeX. + *

+ * Using this software signifies agreement to this License, and the software + * must not be used for illegal purposes. + *

+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is + * not liable for any claims arising from secondary or illegal development. + *

+ * Author: Chill Zhuang (bladejava@qq.com) + */ +package org.springblade.file.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import org.springblade.core.tool.api.R; +import org.springblade.file.pojo.entity.Attachment; +import org.springblade.file.pojo.vo.AttachmentDetailVO; +import org.springblade.file.pojo.vo.FileVO; +import org.springframework.web.multipart.MultipartFile; + +import java.io.File; +import java.util.List; + +/** + * 服务类 + * + * @author zhaowei + */ +public interface IAttachmentService extends IService { + + /** + * 批量上传 + * + * @param files + * @param fileName 文件名,如果 files只有1个,且fileName不为空,设置文件名为 fileName + * @return + */ + R> batchOssUpload(MultipartFile[] files, String fileName); + + /** + * 获取附件,多个附件id用逗号分割 + * @param id + * @return List + */ + List getAttachment(String id); + + /** + * 批量上传解压缩文件并删除文件 + * + * @param files + * @param workDir 工作目录,取文件相对于工作目录的相对路径作为前缀 + * @return + */ + List batchOssUploadUnzipFilesAndDelete(List files, File workDir); + + /** + * 新事务批量保存 + * @param list + */ + void batchSaveWithNewTransaction(List list); + + /** + * 获取附件详情 + * @param id + * @return + */ + AttachmentDetailVO getDetail(Long id); + + /** + * 解压文件并上传 + * @param file + * @return + */ + List unzipAndUpload(FileVO file); +} diff --git a/blade-service/blade-file/src/main/java/org/springblade/file/service/IBusinessAttachmentService.java b/blade-service/blade-file/src/main/java/org/springblade/file/service/IBusinessAttachmentService.java new file mode 100644 index 0000000..9b46ba8 --- /dev/null +++ b/blade-service/blade-file/src/main/java/org/springblade/file/service/IBusinessAttachmentService.java @@ -0,0 +1,51 @@ +package org.springblade.file.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import org.springblade.file.pojo.dto.BusinessAttachmentAddDTO; +import org.springblade.file.pojo.dto.BusinessAttachmentBatchAddDTO; +import org.springblade.file.pojo.dto.BusinessAttachmentDeleteDTO; +import org.springblade.file.pojo.dto.BusinessAttachmentQueryDTO; +import org.springblade.file.pojo.entity.BusinessAttachment; +import org.springblade.file.pojo.vo.BusinessAttachmentVO; + +import java.util.List; + +/** + * 业务附件信息表 服务类 + * + * @author BladeX + * @since 2024-09-04 + */ +public interface IBusinessAttachmentService extends IService { + /** + * 保存 + * @param param + * @return + */ + boolean saveAndRemove(BusinessAttachmentBatchAddDTO param); + + /** + * 保存并删除 + * + * @param params + * @param deleteParam 业务id不为空,则先删除 + * @return + */ + boolean saveAndRemove(List params, BusinessAttachmentDeleteDTO deleteParam); + + /** + * 根据bizId列表删除 + * + * @param bizIds + * @return + */ + boolean removeByBizIds(List bizIds); + + /** + * 查询业务附件列表 + * + * @param param + * @return + */ + List queryList(BusinessAttachmentQueryDTO param); +} diff --git a/blade-service/blade-file/src/main/java/org/springblade/file/service/IBusinessUnzipDetailService.java b/blade-service/blade-file/src/main/java/org/springblade/file/service/IBusinessUnzipDetailService.java new file mode 100644 index 0000000..eda034a --- /dev/null +++ b/blade-service/blade-file/src/main/java/org/springblade/file/service/IBusinessUnzipDetailService.java @@ -0,0 +1,23 @@ +package org.springblade.file.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import org.springblade.file.pojo.entity.BusinessUnzipDetail; +import org.springblade.file.pojo.vo.FileVO; + +/** + *

+ * 业务解压明细表 服务类 + *

+ * + * @author bfhuange + * @since 2024-10-30 + */ +public interface IBusinessUnzipDetailService extends IService { + + /** + * 异步解压并保存解压明细 + * @param unzipTaskId 解压任务id + * @param file obs对象存储的key合md5值,md5有可能为空 + */ + void unzipAndSaveAsync(Long unzipTaskId, FileVO file); +} diff --git a/blade-service/blade-file/src/main/java/org/springblade/file/service/IBusinessUnzipTaskService.java b/blade-service/blade-file/src/main/java/org/springblade/file/service/IBusinessUnzipTaskService.java new file mode 100644 index 0000000..56f7531 --- /dev/null +++ b/blade-service/blade-file/src/main/java/org/springblade/file/service/IBusinessUnzipTaskService.java @@ -0,0 +1,82 @@ +package org.springblade.file.service; + +import com.baomidou.mybatisplus.core.metadata.IPage; +import org.springblade.file.pojo.dto.BusinessUnzipTaskAddDTO; +import org.springblade.file.pojo.dto.BusinessUnzipTaskQueryDTO; +import org.springblade.file.pojo.dto.BusinessUnzipTaskUpdateDTO; +import org.springblade.file.pojo.entity.BusinessUnzipTask; +import com.baomidou.mybatisplus.extension.service.IService; +import org.springblade.file.pojo.vo.BusinessUnzipDetailListVO; +import org.springblade.file.pojo.vo.BusinessUnzipTaskListVO; +import org.springblade.file.pojo.vo.FileVO; + +import java.util.List; + +/** + *

+ * 业务解压任务表 服务类 + *

+ * + * @author bfhuange + * @since 2024-10-28 + */ +public interface IBusinessUnzipTaskService extends IService { + + /** + * 保存业务解压任务 + * @param param + * @return + */ + boolean save(BusinessUnzipTaskAddDTO param); + + /** + * 获取obs对象存储的key + * @param task + * @return + */ + String getObjectKey(BusinessUnzipTask task); + + /** + * 获取文件对象 + * @param task + * @return + */ + FileVO getFile(BusinessUnzipTask task); + + /** + * 业务解压任务分页查询 + * @param page + * @param param + * @return + */ + IPage selectPage(IPage page, BusinessUnzipTaskQueryDTO param); + + /** + * 业务文件解压任务更新(重新上传) + * @param param + * @return + */ + boolean updateReupload(BusinessUnzipTaskUpdateDTO param); + + /** + * 根据业务id删除业务文件解压任务 + * @param bizId + * @param cancelFile 是否取消文件,如果取消,上传完成后取消会报错 + * @return + */ + boolean cancelBusinessUnzipTask(Long bizId, boolean cancelFile); + + /** + * 根据业务id查询解压明细 + * @param bizId + * @return + */ + List queryBusinessUnzipDetail(Long bizId); + + /** + * 根据业务id列表查询解压任务列表 + * @param bizIds + * @return + */ + List getBusinessUnzipTasksByBizIds(List bizIds); +} diff --git a/blade-service/blade-file/src/main/java/org/springblade/file/service/IFileHandleService.java b/blade-service/blade-file/src/main/java/org/springblade/file/service/IFileHandleService.java new file mode 100644 index 0000000..bffbbee --- /dev/null +++ b/blade-service/blade-file/src/main/java/org/springblade/file/service/IFileHandleService.java @@ -0,0 +1,20 @@ +package org.springblade.file.service; + +import org.springblade.file.pojo.vo.FileHandleVO; +import org.springblade.file.pojo.vo.FileVO; + +/** + * 文件处理 + * @author bfhuange + * @date 2024/10/22 + */ +public interface IFileHandleService { + + /** + * 下载文件并解压 + * + * @param file obs对象存储的key合md5,md5可能为空 + * @return + */ + FileHandleVO downloadAndUnzip(FileVO file); +} diff --git a/blade-service/blade-file/src/main/java/org/springblade/file/service/IFileService.java b/blade-service/blade-file/src/main/java/org/springblade/file/service/IFileService.java new file mode 100644 index 0000000..f021fdf --- /dev/null +++ b/blade-service/blade-file/src/main/java/org/springblade/file/service/IFileService.java @@ -0,0 +1,122 @@ +package org.springblade.file.service; + +import org.springblade.file.pojo.vo.PartEtagVO; + +import java.io.File; +import java.io.InputStream; +import java.util.List; + +/** + * 文件接口 + * @author bfhuange + * @date 2024/10/21 + */ +public interface IFileService { + + /** + * 上传文件 + * @param objectKey 文件key + * @param is 输入流 + */ + void uploadFile(String objectKey, InputStream is); + + /** + * 上传临时文件 + * @param objectKey 文件key + * @param is 输入流 + * @param expires 过期时间,单位天,过期后自动删除 + */ + String uploadTempFile(String objectKey, InputStream is, Integer expires); + + /** + * 上传文件 + * @param objectKey 文件key + * @param file 文件 + */ + void uploadFile(String objectKey, File file); + + /** + * 初始化分片上传 + * + * @param objectKey 文件key + * @return 上传任务id + */ + String initiateMultipartUpload(String objectKey); + + /** + * 分段上传 + * @param objectKey 文件key + * @param uploadId 上传任务id + * @param file 文件 + * @param partNumber 分段编号 + * @param offset 偏移位 + * @param partSize 分段大小 + * @return + */ + PartEtagVO uploadPart(String objectKey, String uploadId, File file, int partNumber, Long offset, Long partSize); + + /** + * 完成分段上传任务 + * @param objectKey 文件key + * @param uploadId 上传任务id + * @param partEtagVOS 要合并的分段 + */ + void completeMultipartUpload(String objectKey, String uploadId, List partEtagVOS); + + /** + * 取消分段上传任务 + * @param objectKey + * @param uploadId + */ + void cancelMultipartUpload(String objectKey, String uploadId); + + /** + * 获取文件链接 + * @param objectKey 文件key + * @param expires 过期时间,单位秒 + * @return + */ + String getFileUrl(String objectKey, Long expires); + + /** + * 获取文件链接 + * @param objectKey 文件key + * @param fileName 文件名 + * @param expires 过期时间,单位秒 + * @return + */ + String getFileUrl(String objectKey, String fileName, Long expires); + + /** + * 获取文件上传链接 + * + * @param objectKey 文件key + * @param uploadId + * @param partNumber + * @param expires 过期时间,单位秒 + * @return + */ + String getUploadPartUrl(String objectKey, String uploadId, Integer partNumber, Long expires); + + /** + * 获取文件链接批量 + * @param objectKeys + * @param expires 过期时间 + * @return + */ + List getFileUrls(List objectKeys, Long expires); + + /** + * 下载文件 + * @param objectKey + * @param downloadFile 下载路径 + */ + void downloadFile(String objectKey, String downloadFile); + + /** + * 获取文件md5 + * @param file + * @return + */ + String getFileMd5(File file); +} diff --git a/blade-service/blade-file/src/main/java/org/springblade/file/service/IFileTaskPartService.java b/blade-service/blade-file/src/main/java/org/springblade/file/service/IFileTaskPartService.java new file mode 100644 index 0000000..844aa44 --- /dev/null +++ b/blade-service/blade-file/src/main/java/org/springblade/file/service/IFileTaskPartService.java @@ -0,0 +1,38 @@ +/** + * BladeX Commercial License Agreement + * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved. + *

+ * Use of this software is governed by the Commercial License Agreement + * obtained after purchasing a license from BladeX. + *

+ * 1. This software is for development use only under a valid license + * from BladeX. + *

+ * 2. Redistribution of this software's source code to any third party + * without a commercial license is strictly prohibited. + *

+ * 3. Licensees may copyright their own code but cannot use segments + * from this software for such purposes. Copyright of this software + * remains with BladeX. + *

+ * Using this software signifies agreement to this License, and the software + * must not be used for illegal purposes. + *

+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is + * not liable for any claims arising from secondary or illegal development. + *

+ * Author: Chill Zhuang (bladejava@qq.com) + */ +package org.springblade.file.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import org.springblade.file.pojo.entity.FileTaskPart; + +/** + * 服务类 + * + * @author zhaowei + */ +public interface IFileTaskPartService extends IService { + +} diff --git a/blade-service/blade-file/src/main/java/org/springblade/file/service/IFileTaskService.java b/blade-service/blade-file/src/main/java/org/springblade/file/service/IFileTaskService.java new file mode 100644 index 0000000..3a4a992 --- /dev/null +++ b/blade-service/blade-file/src/main/java/org/springblade/file/service/IFileTaskService.java @@ -0,0 +1,94 @@ +/** + * BladeX Commercial License Agreement + * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved. + *

+ * Use of this software is governed by the Commercial License Agreement + * obtained after purchasing a license from BladeX. + *

+ * 1. This software is for development use only under a valid license + * from BladeX. + *

+ * 2. Redistribution of this software's source code to any third party + * without a commercial license is strictly prohibited. + *

+ * 3. Licensees may copyright their own code but cannot use segments + * from this software for such purposes. Copyright of this software + * remains with BladeX. + *

+ * Using this software signifies agreement to this License, and the software + * must not be used for illegal purposes. + *

+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is + * not liable for any claims arising from secondary or illegal development. + *

+ * Author: Chill Zhuang (bladejava@qq.com) + */ +package org.springblade.file.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import org.springblade.file.pojo.dto.FileTaskCreateDTO; +import org.springblade.file.pojo.dto.FileTaskUpdateDTO; +import org.springblade.file.pojo.entity.FileTask; +import org.springblade.file.pojo.vo.FileTaskUpdateVO; + +import java.util.List; + +/** + * 服务类 + * + * @author zhaowei + */ +public interface IFileTaskService extends IService { + + /** + * 根据文件md5值查询最新一条上传任务记录 + * + * @param id + * @param md5 + * @return + */ + FileTaskUpdateVO queryFileTask(Long id, String md5); + + /** + * 创建文件任务 + * @param param + * @return + */ + Long createFileTask(FileTaskCreateDTO param); + + /** + * 更新文件任务进度 + * @param param + * @return + */ + FileTaskUpdateVO updateFileTask(FileTaskUpdateDTO param); + + /** + * 根据文件任务id获取上传链接 + * + * @param id + * @param partNumber + * @return + */ + String getUploadUrl(Long id, Integer partNumber); + + /** + * 取消文件任务 + * @param taskIds + */ + void cancelBatchByIds(List taskIds); + + /** + * 更新文件任务状态为失败 + * @param id + * @return + */ + FileTaskUpdateVO updateFailed(Long id); + + /** + * 根据id列表更新文件任务状态为暂停 + * @param param + * @return + */ + boolean updatePaused(List param); +} diff --git a/blade-service/blade-file/src/main/java/org/springblade/file/service/IOCRConvertService.java b/blade-service/blade-file/src/main/java/org/springblade/file/service/IOCRConvertService.java new file mode 100644 index 0000000..6e6784c --- /dev/null +++ b/blade-service/blade-file/src/main/java/org/springblade/file/service/IOCRConvertService.java @@ -0,0 +1,19 @@ +package org.springblade.file.service; + +import org.springblade.file.pojo.dto.FileCertificateBatchRecognitionDTO; +import org.springblade.thirdparty.ocr.pojo.vo.TransportCertificateVO; + +/** + * ocr转换 + * @author bfhuange + * @since 2025/3/13 + */ +public interface IOCRConvertService { + + /** + * 凭证批量识别,不分类 + * @param param + * @return + */ + TransportCertificateVO recognitionTransportCertificate(FileCertificateBatchRecognitionDTO param); +} diff --git a/blade-service/blade-file/src/main/java/org/springblade/file/service/impl/AbstractFileService.java b/blade-service/blade-file/src/main/java/org/springblade/file/service/impl/AbstractFileService.java new file mode 100644 index 0000000..521dc7f --- /dev/null +++ b/blade-service/blade-file/src/main/java/org/springblade/file/service/impl/AbstractFileService.java @@ -0,0 +1,98 @@ +/** + * BladeX Commercial License Agreement + * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved. + *

+ * Use of this software is governed by the Commercial License Agreement + * obtained after purchasing a license from BladeX. + *

+ * 1. This software is for development use only under a valid license + * from BladeX. + *

+ * 2. Redistribution of this software's source code to any third party + * without a commercial license is strictly prohibited. + *

+ * 3. Licensees may copyright their own code but cannot use segments + * from this software for such purposes. Copyright of this software + * remains with BladeX. + *

+ * Using this software signifies agreement to this License, and the software + * must not be used for illegal purposes. + *

+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is + * not liable for any claims arising from secondary or illegal development. + *

+ * Author: Chill Zhuang (bladejava@qq.com) + */ +package org.springblade.file.service.impl; + +import org.springblade.core.log.exception.ServiceException; +import org.springblade.file.pojo.vo.PartEtagVO; +import org.springblade.file.service.IFileService; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Base64; +import java.util.List; + +/** + * 文件服务基础实现 + * + * @author Chill + */ +public abstract class AbstractFileService implements IFileService { + + @Override + public String initiateMultipartUpload(String objectKey) { + throw new ServiceException("当前文件存储不支持分段上传"); + } + + @Override + public PartEtagVO uploadPart(String objectKey, String uploadId, File file, int partNumber, Long offset, Long partSize) { + throw new ServiceException("当前文件存储不支持分段上传"); + } + + @Override + public void completeMultipartUpload(String objectKey, String uploadId, List partEtagVOS) { + throw new ServiceException("当前文件存储不支持分段上传"); + } + + @Override + public void cancelMultipartUpload(String objectKey, String uploadId) { + throw new ServiceException("当前文件存储不支持分段上传"); + } + + @Override + public String getUploadPartUrl(String objectKey, String uploadId, Integer partNumber, Long expires) { + throw new ServiceException("当前文件存储不支持分段上传"); + } + + @Override + public String getFileUrl(String objectKey, Long expires) { + return this.getFileUrl(objectKey, null, expires); + } + + @Override + public List getFileUrls(List objectKeys, Long expires) { + return objectKeys.stream() + .map(objectKey -> this.getFileUrl(objectKey, expires)) + .toList(); + } + + @Override + public String getFileMd5(File file) { + try (FileInputStream inputStream = new FileInputStream(file)) { + MessageDigest messageDigest = MessageDigest.getInstance("MD5"); + byte[] buffer = new byte[8192]; + int length; + while ((length = inputStream.read(buffer)) != -1) { + messageDigest.update(buffer, 0, length); + } + return Base64.getEncoder().encodeToString(messageDigest.digest()); + } catch (NoSuchAlgorithmException | IOException exception) { + throw new ServiceException("获取文件md5异常"); + } + } +} diff --git a/blade-service/blade-file/src/main/java/org/springblade/file/service/impl/AttachmentServiceImpl.java b/blade-service/blade-file/src/main/java/org/springblade/file/service/impl/AttachmentServiceImpl.java new file mode 100644 index 0000000..67bd5fb --- /dev/null +++ b/blade-service/blade-file/src/main/java/org/springblade/file/service/impl/AttachmentServiceImpl.java @@ -0,0 +1,255 @@ +/** + * BladeX Commercial License Agreement + * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved. + *

+ * Use of this software is governed by the Commercial License Agreement + * obtained after purchasing a license from BladeX. + *

+ * 1. This software is for development use only under a valid license + * from BladeX. + *

+ * 2. Redistribution of this software's source code to any third party + * without a commercial license is strictly prohibited. + *

+ * 3. Licensees may copyright their own code but cannot use segments + * from this software for such purposes. Copyright of this software + * remains with BladeX. + *

+ * Using this software signifies agreement to this License, and the software + * must not be used for illegal purposes. + *

+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is + * not liable for any claims arising from secondary or illegal development. + *

+ * Author: Chill Zhuang (bladejava@qq.com) + */ +package org.springblade.file.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.toolkit.IdWorker; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +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.R; +import org.springblade.core.tool.utils.CollectionUtil; +import org.springblade.core.tool.utils.SpringUtil; +import org.springblade.core.tool.utils.StringPool; +import org.springblade.core.tool.utils.StringUtil; +import org.springblade.file.config.ObsProperties; +import org.springblade.file.convert.BusinessAttachmentConvert; +import org.springblade.file.exception.UnzipFailException; +import org.springblade.file.mapper.AttachmentMapper; +import org.springblade.file.pojo.entity.Attachment; +import org.springblade.file.pojo.vo.AttachmentDetailVO; +import org.springblade.file.pojo.vo.FileHandleVO; +import org.springblade.file.pojo.vo.FileVO; +import org.springblade.file.service.IAttachmentService; +import org.springblade.file.service.IFileHandleService; +import org.springblade.file.service.IFileService; +import org.springblade.file.util.ObjectKeyUtils; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Propagation; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.multipart.MultipartFile; + +import java.io.File; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * 服务实现类 + * @author zhaowei + */ +@Service +@Slf4j +@RequiredArgsConstructor +public class AttachmentServiceImpl extends ServiceImpl implements IAttachmentService { + + private final IFileService fileService; + private final BusinessAttachmentConvert convert; + private final ObsProperties obsProperties; + private final IFileHandleService fileHandleService; + private IAttachmentService self; + + @Override + public R> batchOssUpload(MultipartFile[] files, String overWriteFileName){ + try { + List attachmentList = new ArrayList<>(); + // 文件数量为1个,且重写的文件名不为空,使用重写的文件名,给uniapp上传使用,uniapp上传的文件名不是原始文件名 + boolean useOverWriteFileName = files.length == 1 && StringUtil.isNotBlank(overWriteFileName); + for(MultipartFile file : files){ + String fileName = useOverWriteFileName ? overWriteFileName : file.getOriginalFilename(); + String objectkey = ObjectKeyUtils.generateObjectKey(fileName); + fileService.uploadFile(objectkey, file.getInputStream()); + // 插入附件数据 + Attachment attachment = new Attachment(); + Long id = IdWorker.getId(); + attachment.setId(id); + attachment.setAttachmentName(fileName); + attachment.setSize(file.getSize()); + attachment.setObjectKey(objectkey); + attachmentList.add(attachment); + } + this.saveBatch(attachmentList); + log.info("OBS上传 successfully"); + return R.data(attachmentList,"OBS上传 successfully"); + } catch (Exception e) { + log.error("OBS上传 failed,Exception", e); + } + return R.fail("OBS上传 failed"); + } + + @Override + public List getAttachment(String id) { + return this.list(new LambdaQueryWrapper() + .in(Attachment::getId, Arrays.stream(id.split(",")).toList()) + ); + } + + @Override + public List batchOssUploadUnzipFilesAndDelete(List files, File workDir) { + if (CollectionUtil.isEmpty(files)) { + return Collections.emptyList(); + } + List attachmentList = new ArrayList<>(); + AtomicInteger index = new AtomicInteger(1); + try { + log.info("上传文件总数:{}", files.size()); + for(File file : files){ + String fileName = file.getName(); + // 压缩文件所在压缩包父目录相对路径 + String dir = this.getDir(workDir, file); + // 获取上传的目录前缀 + String prefixDir = getUploadPrefix(); + String objectkey = prefixDir + ObjectKeyUtils.generateObjectKey(fileName); + fileService.uploadFile(objectkey, file); + int completeFiles = index.getAndIncrement(); + if (completeFiles % 10 == 0) { + log.info("上传文件总进度:{}/{}", completeFiles, files.size()); + } + // 插入附件数据 + Attachment attachment = new Attachment(); + Long id = IdWorker.getId(); + attachment.setId(id); + attachment.setDir(dir); + attachment.setAttachmentName(fileName); + attachment.setSize(file.length()); + attachment.setObjectKey(objectkey); + attachmentList.add(attachment); + } + log.info("上传文件完成:{}", files.size()); + // 新事务批量保存 + this.getSelf().batchSaveWithNewTransaction(attachmentList); + log.info("OBS上传 successfully"); + } catch (Exception e) { + log.error("OBS上传失败", e); + throw new ServiceException("OBS上传失败"); + } + // 返回新增的附件列表 + return attachmentList; + } + + /** + * 获取上传文件目录前缀 + * @return + */ + private String getUploadPrefix() { + // 上传路径前缀 = 配置的解压上传路径 + / + return obsProperties.getUnzipUploadDir() + StringPool.SLASH; + } + + /** + * 获取压缩文件所在压缩包父目录的相对路径,结尾不带/ + * @param workDir 工作目录,解压的根目录 + * @param file 解压后的文件 + * @return + */ + private String getDir(File workDir, File file) { + if (workDir == null) { + return StringPool.EMPTY; + } + Path workDirPath = workDir.toPath(); + Path filePath = file.toPath(); + return workDirPath.relativize(filePath).toString() + // 去掉文件名以及文件所在父目录的后缀/或\ + .replace(File.separator + file.getName(), "") + // 将文件分隔符替换为/,windows下是\,需要处理 + .replace(File.separator, StringPool.SLASH); + } + + @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRES_NEW) + @Override + public void batchSaveWithNewTransaction(List list) { + this.saveBatch(list); + } + + @Override + public AttachmentDetailVO getDetail(Long id) { + if (id == null) { + return null; + } + Attachment attachment = this.getById(id); + if (attachment == null) { + return null; + } + AttachmentDetailVO detailVO = convert.entity2vo(attachment); + // 查询下载链接 + String fileUrl = fileService.getFileUrl(attachment.getObjectKey(), null); + detailVO.setUrl(fileUrl); + return detailVO; + } + + @Override + public List unzipAndUpload(FileVO file) { + // 1. 下载并解压文件 + try { + FileHandleVO fileHandleVO = fileHandleService.downloadAndUnzip(file); + List unzippedFiles = fileHandleVO.getUnzippedFiles(); + if (CollectionUtil.isEmpty(unzippedFiles)) { + log.warn("下载解压文件为空 objectKey:{}", file.getObjectKey()); + throw new UnzipFailException("解压文件为空", fileHandleVO.getUnzippedDir()); + } + File unzippedDir = fileHandleVO.getUnzippedDir(); + // 2. 上传文件 + long start = System.currentTimeMillis(); + try { + List attachments = getSelf().batchOssUploadUnzipFilesAndDelete(unzippedFiles, unzippedDir); + if (CollectionUtil.isEmpty(attachments)) { + log.warn("上传解压文件为空 objectKey:{}", file.getObjectKey()); + return Collections.emptyList(); + } + // 删除下载的压缩文件 + fileHandleVO.getDownloadFile().delete(); + // 删除解压后的目录 + FileUtils.deleteQuietly(unzippedDir); + return attachments; + } finally { + long end = System.currentTimeMillis(); + log.info("文件:{} 解压后上传完成 耗时:{}", file.getObjectKey(), end - start); + } + } catch (UnzipFailException e) { + // 文件解压失败,删除解压后的文件 + File unzipDir = e.getUnzipDir(); + // 删除解压后的目录 + FileUtils.deleteQuietly(unzipDir); + throw e; + } + } + + /** + * 获取自己 + * @return + */ + private IAttachmentService getSelf() { + if (self == null) { + self = SpringUtil.getBean(IAttachmentService.class); + } + return self; + } +} diff --git a/blade-service/blade-file/src/main/java/org/springblade/file/service/impl/BusinessAttachmentServiceImpl.java b/blade-service/blade-file/src/main/java/org/springblade/file/service/impl/BusinessAttachmentServiceImpl.java new file mode 100644 index 0000000..db199e7 --- /dev/null +++ b/blade-service/blade-file/src/main/java/org/springblade/file/service/impl/BusinessAttachmentServiceImpl.java @@ -0,0 +1,136 @@ +package org.springblade.file.service.impl; + +import cn.hutool.core.collection.CollectionUtil; +import com.baomidou.mybatisplus.core.toolkit.IdWorker; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springblade.core.secure.utils.AuthUtil; +import org.springblade.core.tool.constant.BladeConstant; +import org.springblade.core.tool.utils.StringUtil; +import org.springblade.file.convert.BusinessAttachmentConvert; +import org.springblade.file.mapper.BusinessAttachmentMapper; +import org.springblade.file.pojo.dto.BusinessAttachmentAddDTO; +import org.springblade.file.pojo.dto.BusinessAttachmentBatchAddDTO; +import org.springblade.file.pojo.dto.BusinessAttachmentDeleteDTO; +import org.springblade.file.pojo.dto.BusinessAttachmentQueryDTO; +import org.springblade.file.pojo.entity.BusinessAttachment; +import org.springblade.file.pojo.vo.BusinessAttachmentVO; +import org.springblade.file.service.IBusinessAttachmentService; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * 业务附件信息表 服务实现类 + * + * @author BladeX + * @since 2024-09-04 + */ +@Slf4j +@RequiredArgsConstructor +@Service +public class BusinessAttachmentServiceImpl extends ServiceImpl implements IBusinessAttachmentService { + + private final BusinessAttachmentConvert convert; + + @Transactional(rollbackFor = Exception.class) + @Override + public boolean saveAndRemove(BusinessAttachmentBatchAddDTO param) { + if (param == null) { + log.warn("批量保存业务附件,参数为空"); + return false; + } + if (param.isRemove() && param.getBizId() != null) { + // 先删除 + this.removeByBizIds(List.of(param.getBizId())); + } + List addList = convert.dto2entities(param); + if (CollectionUtil.isNotEmpty(addList)) { + return this.saveBatch(addList); + } + return true; + } + + @Transactional(rollbackFor = Exception.class) + @Override + public boolean saveAndRemove(List params, BusinessAttachmentDeleteDTO deleteParam) { + boolean hasBizIds = deleteParam != null && deleteParam.isDelete() && CollectionUtil.isNotEmpty(deleteParam.getBizIds()); + if (hasBizIds) { + // bizIds不为空,先删除 + this.removeByBizIdsAndCodes(deleteParam.getBizIds(), deleteParam.getBizCodes()); + } + if (CollectionUtil.isEmpty(params)) { + log.warn("保存业务附件,参数为空"); + return true; + } + if (!hasBizIds && (deleteParam == null || deleteParam.isDelete())) { + // bizIds为空,从参数提取bizIds,再删除 + // 删除参数为空,默认删除,删除参数中delete为true默认删除,false不删除 + List bizIds = params.stream() + .map(BusinessAttachmentAddDTO::getBizId) + .distinct() + .toList(); + this.removeByBizIds(bizIds); + } + + List businessAttachments = convert.dtos2entities(params); + + // 按照业务id分组排序 + Map> bizIdAttachmentsMap = businessAttachments.stream() + .collect(Collectors.groupingBy(BusinessAttachment::getBizId)); + String tenantId = AuthUtil.getTenantId(); + for (Map.Entry> entry : bizIdAttachmentsMap.entrySet()) { + int i = 1; + for (BusinessAttachment businessAttachment : entry.getValue()) { + businessAttachment.setId(IdWorker.getId()); + // 处理排序 + businessAttachment.setSort(i++); + if (StringUtil.isBlank(tenantId)) { + businessAttachment.setTenantId(BladeConstant.ADMIN_TENANT_ID); + } + } + } + return this.saveBatch(businessAttachments); + } + + @Transactional(rollbackFor = Exception.class) + @Override + public boolean removeByBizIds(List bizIds) { + return this.removeByBizIdsAndCodes(bizIds, null); + } + + @Override + public List queryList(BusinessAttachmentQueryDTO param) { + return baseMapper.queryList(param); + } + + /** + * 批量删除 + * @param bizIds 业务id列表,为空,不删除 + * @param bizCodes 业务编码列表,为空,删除所有 + * @return + */ + private boolean removeByBizIdsAndCodes(List bizIds, List bizCodes) { + if (CollectionUtil.isEmpty(bizIds)) { + log.warn("删除附件,参数为空"); + return false; + } + List ids = this.list(Wrappers.lambdaQuery() + .in(BusinessAttachment::getBizId, bizIds) + // 业务编码列表不为空,添加业务编码查询条件 + .in(CollectionUtil.isNotEmpty(bizCodes), BusinessAttachment::getBizCode, bizCodes) + ).stream() + .map(BusinessAttachment::getId) + .toList(); + if (CollectionUtil.isNotEmpty(ids)) { + return this.removeBatchByIds(ids); + } + return true; + } +} diff --git a/blade-service/blade-file/src/main/java/org/springblade/file/service/impl/BusinessUnzipDetailServiceImpl.java b/blade-service/blade-file/src/main/java/org/springblade/file/service/impl/BusinessUnzipDetailServiceImpl.java new file mode 100644 index 0000000..ce66261 --- /dev/null +++ b/blade-service/blade-file/src/main/java/org/springblade/file/service/impl/BusinessUnzipDetailServiceImpl.java @@ -0,0 +1,71 @@ +package org.springblade.file.service.impl; + +import com.alibaba.fastjson2.JSON; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springblade.core.tool.utils.CollectionUtil; +import org.springblade.file.exception.UnzipFailException; +import org.springblade.file.listener.UnzipCompletedEvent; +import org.springblade.file.listener.UnzipFailEvent; +import org.springblade.file.mapper.BusinessUnzipDetailMapper; +import org.springblade.file.pojo.entity.Attachment; +import org.springblade.file.pojo.entity.BusinessUnzipDetail; +import org.springblade.file.pojo.vo.FileVO; +import org.springblade.file.service.IAttachmentService; +import org.springblade.file.service.IBusinessUnzipDetailService; +import org.springframework.context.ApplicationContext; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * @author bfhuange + * @date 2024/10/30 + */ +@AllArgsConstructor +@Slf4j +@Service +public class BusinessUnzipDetailServiceImpl extends ServiceImpl implements IBusinessUnzipDetailService { + + private final IAttachmentService attachmentService; + private final ApplicationContext applicationContext; + + @Async + @Override + public void unzipAndSaveAsync(Long unzipTaskId, FileVO file) { + log.info("解压文件并上传开始 unzipTaskId: {} file: {}", unzipTaskId, JSON.toJSONString(file)); + long start = System.currentTimeMillis(); + try { + // 1. 上传文件并解压 + List attachments = attachmentService.unzipAndUpload(file); + if (CollectionUtil.isEmpty(attachments)) { + log.warn("上传解压文件为空 unzipTaskId:{} objectKey:{}", unzipTaskId, file.getObjectKey()); + return; + } + // 2. 保存压缩文件和解压文件关系 + List addParams = attachments.stream() + .map(attachment -> { + BusinessUnzipDetail addParam = new BusinessUnzipDetail(); + addParam.setBusinessUnzipTaskId(unzipTaskId); + addParam.setAttachmentId(attachment.getId()); + return addParam; + }).toList(); + if (CollectionUtil.isNotEmpty(addParams)) { + this.saveBatch(addParams); + } + // 3. 发布解压完成事件 + applicationContext.publishEvent(new UnzipCompletedEvent(unzipTaskId)); + } catch (UnzipFailException e) { + log.error("解压文件异常", e); + // 发布解压失败事件 + applicationContext.publishEvent(new UnzipFailEvent(unzipTaskId)); + } catch (Exception e) { + log.error("解压文件并上传异常", e); + } finally { + long end = System.currentTimeMillis(); + log.info("解压文件并上传 完成 unzipTaskId: {} objectKey: {} 耗时:{}", unzipTaskId, file.getObjectKey(), end - start); + } + } +} diff --git a/blade-service/blade-file/src/main/java/org/springblade/file/service/impl/BusinessUnzipTaskServiceImpl.java b/blade-service/blade-file/src/main/java/org/springblade/file/service/impl/BusinessUnzipTaskServiceImpl.java new file mode 100644 index 0000000..52555a8 --- /dev/null +++ b/blade-service/blade-file/src/main/java/org/springblade/file/service/impl/BusinessUnzipTaskServiceImpl.java @@ -0,0 +1,235 @@ +package org.springblade.file.service.impl; + +import com.alibaba.fastjson2.JSON; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.google.common.collect.Lists; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springblade.common.constant.DictTypeEnum; +import org.springblade.core.log.exception.ServiceException; +import org.springblade.core.tool.utils.CollectionUtil; +import org.springblade.core.tool.utils.Func; +import org.springblade.core.tool.utils.StringUtil; +import org.springblade.file.convert.FileTaskConvert; +import org.springblade.file.listener.UnzipStartEvent; +import org.springblade.file.pojo.dto.BusinessUnzipTaskAddDTO; +import org.springblade.file.pojo.dto.BusinessUnzipTaskQueryDTO; +import org.springblade.file.pojo.dto.BusinessUnzipTaskUpdateDTO; +import org.springblade.file.pojo.entity.Attachment; +import org.springblade.file.pojo.entity.BusinessUnzipTask; +import org.springblade.file.mapper.BusinessUnzipTaskMapper; +import org.springblade.file.pojo.entity.FileTask; +import org.springblade.file.pojo.enums.FileTaskStatus; +import org.springblade.file.pojo.enums.UnzipTask; +import org.springblade.file.pojo.vo.BusinessUnzipDetailListVO; +import org.springblade.file.pojo.vo.BusinessUnzipTaskListVO; +import org.springblade.file.pojo.vo.FileVO; +import org.springblade.file.service.IAttachmentService; +import org.springblade.file.service.IBusinessUnzipDetailService; +import org.springblade.file.service.IBusinessUnzipTaskService; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import org.springblade.file.service.IFileTaskService; +import org.springblade.system.cache.DictCache; +import org.springframework.context.ApplicationContext; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.Collections; +import java.util.List; + +/** + *

+ * 业务解压任务表 服务实现类 + *

+ * + * @author bfhuange + * @since 2024-10-28 + */ +@Slf4j +@AllArgsConstructor +@Service +public class BusinessUnzipTaskServiceImpl extends ServiceImpl implements IBusinessUnzipTaskService { + + private final FileTaskConvert convert; + private final IFileTaskService fileTaskService; + private final IBusinessUnzipDetailService detailService; + private final IAttachmentService attachmentService; + private final ApplicationContext applicationContext; + + @Transactional(rollbackFor = Exception.class) + @Override + public boolean save(BusinessUnzipTaskAddDTO param) { + BusinessUnzipTask addParam = convert.dto2entity(param); + String status = getStatus(param.getRelateType(), param.getRelateId()); + addParam.setStatus(status); + boolean result = this.save(addParam); + if (result && UnzipTask.Status.UPLOADED.getCode().equals(status)) { + // 解压任务状态是上传完成,发布解压开始事件 + applicationContext.publishEvent(new UnzipStartEvent(addParam)); + } + return result; + } + + @Override + public String getObjectKey(BusinessUnzipTask task) { + return this.getFile(task).getObjectKey(); + } + + @Override + public FileVO getFile(BusinessUnzipTask task) { + Func.requireNotNull(task, "文件任务不存在"); + String relateType = task.getRelateType(); + if (StringUtil.isBlank(relateType)) { + throw new ServiceException("文件任务关联类型不能为空"); + } + Long relateId = task.getRelateId(); + Func.requireNotNull(relateId, "文件任务关联id不能为空"); + if (UnzipTask.RelateType.FILE_TASK.getCode().equals(relateType)) { + // 文件任务关联类型 + FileTask fileTask = fileTaskService.getById(relateId); + Func.requireNotNull(fileTask, "文件任务不存在"); + return new FileVO(fileTask.getObjectKey(), fileTask.getMd5()); + } + if (UnzipTask.RelateType.ATTACHMENT.getCode().equals(relateType)) { + // 附件关联类型 + Attachment attachment = attachmentService.getById(relateId); + Func.requireNotNull(attachment, "附件不存在"); + // 附件暂时没有md5 + return new FileVO(attachment.getObjectKey(), null); + } + throw new ServiceException("文件任务关联类型错误"); + } + + @Override + public IPage selectPage(IPage page, BusinessUnzipTaskQueryDTO param) { + List list = baseMapper.selectTaskList(page, param); + if (CollectionUtil.isNotEmpty(list)) { + // 处理状态枚举值名称 + list.forEach(vo -> { + String status = vo.getStatus(); + if (StringUtil.isNotBlank(status)) { + vo.setStatusStr(DictCache.getValue(DictTypeEnum.FILE_TASK_STATUS.getType(), vo.getStatus())); + } + }); + } + IPage newPage = new Page<>(page.getCurrent(), page.getSize(), page.getTotal()); + return newPage.setRecords(list); + } + + @Transactional(rollbackFor = Exception.class) + @Override + public boolean updateReupload(BusinessUnzipTaskUpdateDTO param) { + log.info("修改业务解压任务重新上传参数:{}", JSON.toJSONString(param)); + if (param == null || (param.getId() == null && param.getBizId() == null)) { + throw new ServiceException("参数不能为空"); + } + // 校验业务解压任务 + BusinessUnzipTask unzipTask = param.getId() != null ? this.getById(param.getId()) : this.getOne(Wrappers.lambdaQuery() + .eq(BusinessUnzipTask::getBizId, param.getBizId()) + .last("limit 1") + ); + Func.requireNotNull(unzipTask, "业务任务不存在"); + // 上传中或解压失败才可以重新上传 + if (!UnzipTask.Status.UPLOADING.getCode().equals(unzipTask.getStatus()) + && !UnzipTask.Status.UNZIP_FAIL.getCode().equals(unzipTask.getStatus())) { + throw new ServiceException("只有上传中或解压失败的任务才可以重新上传"); + } + + if (UnzipTask.Status.UPLOADING.getCode().equals(unzipTask.getStatus())) { + // 上传中的才需要取消上传任务,解压失败的无需取消 + try { + // 取消文件任务 + fileTaskService.cancelBatchByIds(Lists.newArrayList(unzipTask.getRelateId())); + } catch (Exception e) { + log.error("取消上传异常", e); + } + } + + // 更新业务解压任务 + BusinessUnzipTask updateParam = new BusinessUnzipTask(); + updateParam.setId(unzipTask.getId()); + updateParam.setRelateId(param.getRelateId()); + String status = getStatus(param.getRelateType(), param.getRelateId()); + updateParam.setStatus(status); + boolean result = this.updateById(updateParam); + if (result && UnzipTask.Status.UPLOADED.getCode().equals(status)) { + // 重新查询,防止解压开始事件中查询时事务未提交查询不到最新的数据 + unzipTask = this.getById(unzipTask.getId()); + // 解压任务状态是上传完成,发布解压开始事件 + applicationContext.publishEvent(new UnzipStartEvent(unzipTask)); + } + return result; + } + + @Transactional(rollbackFor = Exception.class) + @Override + public boolean cancelBusinessUnzipTask(Long bizId, boolean cancelFile) { + List list = this.list(Wrappers.lambdaQuery() + .eq(BusinessUnzipTask::getBizId, bizId) + ); + if (CollectionUtil.isEmpty(list)) { + return true; + } + if (cancelFile) { + boolean hasUpload = list.stream() + .anyMatch(task -> !UnzipTask.Status.UPLOADING.getCode().equals(task.getStatus())); + if (hasUpload) { + throw new ServiceException("当前任务已上传完成,不能取消"); + } + // 取消文件任务 + List taskIds = list.stream() + .filter(task -> UnzipTask.RelateType.FILE_TASK.getCode().equals(task.getRelateType())) + .map(BusinessUnzipTask::getRelateId) + .toList(); + fileTaskService.cancelBatchByIds(taskIds); + } + // 删除业务文件解压任务 + List unzipTaskIds = list.stream() + .map(BusinessUnzipTask::getId) + .toList(); + this.removeBatchByIds(unzipTaskIds); + return true; + } + + @Override + public List queryBusinessUnzipDetail(Long bizId) { + return baseMapper.queryBusinessUnzipDetail(bizId); + } + + @Override + public List getBusinessUnzipTasksByBizIds(List bizIds) { + if (CollectionUtil.isEmpty(bizIds)) { + return Collections.emptyList(); + } + BusinessUnzipTaskQueryDTO param = new BusinessUnzipTaskQueryDTO(); + param.setBizIds(bizIds); + return baseMapper.selectTaskList(null, param); + } + + /** + * 获取解压任务状态 + * @param relateType 关联类型 + * @param relateId 关联id + * @return + */ + private String getStatus(String relateType, Long relateId) { + if (UnzipTask.RelateType.ATTACHMENT.getCode().equals(relateType)) { + // 关联类型是附件,状态直接就是上传完成 + return UnzipTask.Status.UPLOADED.getCode(); + } + if (UnzipTask.RelateType.FILE_TASK.getCode().equals(relateType)) { + // 关联类型是文件任务,查询文件任务状态 + FileTask fileTask = fileTaskService.getById(relateId); + Func.requireNotNull(fileTask, "文件任务不存在"); + if (FileTaskStatus.COMPLETED.getCode().equals(fileTask.getStatus())) { + // 文件任务上传完成,解压任务设置为上传完成 + return UnzipTask.Status.UPLOADED.getCode(); + } + // 否则解压任务设置为上传中状态 + return UnzipTask.Status.UPLOADING.getCode(); + } + throw new ServiceException("关联类型错误"); + } +} diff --git a/blade-service/blade-file/src/main/java/org/springblade/file/service/impl/FileHandleServiceImpl.java b/blade-service/blade-file/src/main/java/org/springblade/file/service/impl/FileHandleServiceImpl.java new file mode 100644 index 0000000..f3134ec --- /dev/null +++ b/blade-service/blade-file/src/main/java/org/springblade/file/service/impl/FileHandleServiceImpl.java @@ -0,0 +1,92 @@ +package org.springblade.file.service.impl; + +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springblade.core.tool.utils.StringUtil; +import org.springblade.file.config.ObsProperties; +import org.springblade.file.exception.UnzipFailException; +import org.springblade.file.pojo.vo.FileHandleVO; +import org.springblade.file.pojo.vo.FileVO; +import org.springblade.file.service.IFileHandleService; +import org.springblade.file.service.IFileService; +import org.springblade.file.util.UnzipUtil; +import org.springframework.stereotype.Service; + +import java.io.File; +import java.util.List; + +/** + * @author bfhuange + * @date 2024/10/22 + */ +@Slf4j +@AllArgsConstructor +@Service +public class FileHandleServiceImpl implements IFileHandleService { + + private final IFileService fileService; + private final ObsProperties obsProperties; + + @Override + public FileHandleVO downloadAndUnzip(FileVO file) { + File downloadDir = new File(obsProperties.getTmpDownloadDir()); + if (!downloadDir.exists()) { + downloadDir.mkdirs(); + } + File downloadFile = new File(downloadDir, file.getObjectKey()); + String downloadFilePath = downloadFile.getAbsolutePath(); + File unzipDir = null; + long unzipStart = System.currentTimeMillis(); + try { + // 下载文件 + downloadFile(file, downloadFile, downloadFilePath); + // 2. 解压文件目录 + unzipDir = new File(obsProperties.getTmpUnzipDir(), file.getObjectKey()); + if (!unzipDir.exists()) { + unzipDir.mkdirs(); + } + long start = System.currentTimeMillis(); + List files = UnzipUtil.unzipOrUnrar(downloadFilePath, unzipDir); + long end = System.currentTimeMillis(); + log.info("文件:{} 解压完成 共{}个 耗时:{}", file.getObjectKey(), files.size(), end - start); + FileHandleVO fileHandleVO = new FileHandleVO(); + fileHandleVO.setDownloadFile(downloadFile); + fileHandleVO.setUnzippedDir(unzipDir); + fileHandleVO.setUnzippedFiles(files); + return fileHandleVO; + } catch (Exception e) { + log.error("文件解压失败异常", e); + throw new UnzipFailException(e, unzipDir); + } finally { + long unzipEnd = System.currentTimeMillis(); + log.info("文件:{} 下载并解压完成 耗时:{}", file.getObjectKey(), unzipEnd - unzipStart); + } + } + + /** + * 下载文件 + * + * @param file + * @param downloadFile + * @param downloadFilePath + */ + private void downloadFile(FileVO file, File downloadFile, String downloadFilePath) { + if (downloadFile.exists() && file.getMd5() != null) { + // 下载文件存在且md5不为空,可能是重新解压 + String fileMd5 = fileService.getFileMd5(downloadFile); + if (StringUtil.equals(fileMd5, file.getMd5())) { + log.info("文件存在且md5一致,无需重新下载 文件路径:{}", downloadFile.getAbsolutePath()); + return; + } + } + long downloadStart = System.currentTimeMillis(); + try { + // 1. 下载文件 + fileService.downloadFile(file.getObjectKey(), downloadFilePath); + } finally { + long downloadEnd = System.currentTimeMillis(); + log.info("文件:{} 下载完成 耗时:{}", file.getObjectKey(), downloadEnd - downloadStart); + } + } + +} diff --git a/blade-service/blade-file/src/main/java/org/springblade/file/service/impl/FileTaskPartServiceImpl.java b/blade-service/blade-file/src/main/java/org/springblade/file/service/impl/FileTaskPartServiceImpl.java new file mode 100644 index 0000000..540c170 --- /dev/null +++ b/blade-service/blade-file/src/main/java/org/springblade/file/service/impl/FileTaskPartServiceImpl.java @@ -0,0 +1,46 @@ +/** + * BladeX Commercial License Agreement + * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved. + *

+ * Use of this software is governed by the Commercial License Agreement + * obtained after purchasing a license from BladeX. + *

+ * 1. This software is for development use only under a valid license + * from BladeX. + *

+ * 2. Redistribution of this software's source code to any third party + * without a commercial license is strictly prohibited. + *

+ * 3. Licensees may copyright their own code but cannot use segments + * from this software for such purposes. Copyright of this software + * remains with BladeX. + *

+ * Using this software signifies agreement to this License, and the software + * must not be used for illegal purposes. + *

+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is + * not liable for any claims arising from secondary or illegal development. + *

+ * Author: Chill Zhuang (bladejava@qq.com) + */ +package org.springblade.file.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springblade.file.mapper.FileTaskPartMapper; +import org.springblade.file.pojo.entity.FileTaskPart; +import org.springblade.file.service.IFileTaskPartService; +import org.springframework.stereotype.Service; + +/** + * 服务实现类 + * + * @author zhaowei + */ +@Service +@Slf4j +@AllArgsConstructor +public class FileTaskPartServiceImpl extends ServiceImpl implements IFileTaskPartService { + +} diff --git a/blade-service/blade-file/src/main/java/org/springblade/file/service/impl/FileTaskServiceImpl.java b/blade-service/blade-file/src/main/java/org/springblade/file/service/impl/FileTaskServiceImpl.java new file mode 100644 index 0000000..af8d4f2 --- /dev/null +++ b/blade-service/blade-file/src/main/java/org/springblade/file/service/impl/FileTaskServiceImpl.java @@ -0,0 +1,372 @@ +/** + * BladeX Commercial License Agreement + * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved. + *

+ * Use of this software is governed by the Commercial License Agreement + * obtained after purchasing a license from BladeX. + *

+ * 1. This software is for development use only under a valid license + * from BladeX. + *

+ * 2. Redistribution of this software's source code to any third party + * without a commercial license is strictly prohibited. + *

+ * 3. Licensees may copyright their own code but cannot use segments + * from this software for such purposes. Copyright of this software + * remains with BladeX. + *

+ * Using this software signifies agreement to this License, and the software + * must not be used for illegal purposes. + *

+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is + * not liable for any claims arising from secondary or illegal development. + *

+ * Author: Chill Zhuang (bladejava@qq.com) + */ +package org.springblade.file.service.impl; + +import com.baomidou.mybatisplus.core.toolkit.IdWorker; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springblade.core.log.exception.ServiceException; +import org.springblade.core.tool.utils.CollectionUtil; +import org.springblade.core.tool.utils.Func; +import org.springblade.core.tool.utils.StringUtil; +import org.springblade.file.config.ObsProperties; +import org.springblade.file.convert.FileTaskConvert; +import org.springblade.file.listener.FileUploadedEvent; +import org.springblade.file.mapper.BusinessUnzipTaskMapper; +import org.springblade.file.mapper.FileTaskMapper; +import org.springblade.file.pojo.dto.FileTaskCreateDTO; +import org.springblade.file.pojo.dto.FileTaskUpdateDTO; +import org.springblade.file.pojo.entity.BusinessUnzipTask; +import org.springblade.file.pojo.entity.FileTask; +import org.springblade.file.pojo.entity.FileTaskPart; +import org.springblade.file.pojo.enums.FileTaskStatus; +import org.springblade.file.pojo.vo.FileTaskUpdateVO; +import org.springblade.file.pojo.vo.PartEtagVO; +import org.springblade.file.service.IFileService; +import org.springblade.file.service.IFileTaskPartService; +import org.springblade.file.service.IFileTaskService; +import org.springblade.file.util.ObjectKeyUtils; +import org.springframework.context.ApplicationContext; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; + +/** + * 服务实现类 + * + * @author zhaowei + */ +@Service +@Slf4j +@AllArgsConstructor +public class FileTaskServiceImpl extends ServiceImpl implements IFileTaskService { + + private final FileTaskConvert convert; + private final IFileTaskPartService partService; + private final IFileService fileService; + private final BusinessUnzipTaskMapper unzipTaskMapper; + private final ObsProperties obsProperties; + private final ApplicationContext applicationContext; + + @Override + public FileTaskUpdateVO queryFileTask(Long id, String md5) { + if (id == null && StringUtil.isBlank(md5)) { + throw new ServiceException("id和md5不能都为空"); + } + // 根据md5按创建时间倒序取一条 + FileTask fileTask = this.getOne(Wrappers.lambdaQuery() + .eq(id != null, FileTask::getId, id) + .eq(StringUtil.isNotBlank(md5), FileTask::getMd5, md5) + .orderByDesc(FileTask::getCreateTime) + .last("limit 1") + ); + if (fileTask == null) { + return null; + } + return getFileTaskUpdateVO(fileTask); + } + + @Transactional(rollbackFor = Exception.class) + @Override + public Long createFileTask(FileTaskCreateDTO param) { + if (!param.isForce()) { + // 不是强制创建新任务,文件任务存在就报错 + FileTask fileTask = this.getOne(Wrappers.lambdaQuery() + .eq(FileTask::getMd5, param.getMd5()) + .last("limit 1") + ); + if (Func.notNull(fileTask)) { + throw new ServiceException("文件任务已存在"); + } + } + + // 1. 初始化上传任务 + String fileName = param.getAttachmentName(); + String fileSuffix = fileName.substring(fileName.lastIndexOf(".")); + String objectKey = ObjectKeyUtils.generateObjectKey(obsProperties.getZipUploadDir(), fileName); + String uploadId = fileService.initiateMultipartUpload(objectKey); + + // 2. 保存文件任务 + FileTask addParam = convert.dto2entity(param); + addParam.setId(IdWorker.getId()); + addParam.setObjectKey(objectKey); + addParam.setSuffix(fileSuffix); + // 当前位置 + addParam.setCurrentIndex(0); + addParam.setUploadId(uploadId); + // 正在上传 + addParam.setStatus(FileTaskStatus.UPLOADING.getCode()); + this.save(addParam); + + return addParam.getId(); + } + + @Transactional(rollbackFor = Exception.class) + @Override + public FileTaskUpdateVO updateFileTask(FileTaskUpdateDTO param) { + // 校验文件任务 + FileTask fileTask = this.getById(param.getId()); + Func.requireNotNull(fileTask, "文件任务不存在"); + if (FileTaskStatus.COMPLETED.getCode().equals(fileTask.getStatus())) { + throw new ServiceException("文件任务已完成"); + } + + // 1. 新增分段 + addPart(param, fileTask); + + // 是否最后一段 + boolean isLast = fileTask.getChunkTotal().equals(param.getPartNumber()); + // 2. 更新文件任务 + boolean update = updateTask(param, isLast); + // 3. 处理上传结束 + handleFinished(isLast, update, fileTask); + return this.getUpdateVO(param.getId()); + } + + /** + * 处理结束 + * @param isLast + * @param update + * @param fileTask + */ + private void handleFinished(boolean isLast, boolean update, FileTask fileTask) { + if (isLast && update) { + // 最后一段并且更新成功,合并文件 + List parts = partService.list(Wrappers.lambdaQuery() + .eq(FileTaskPart::getFileTaskId, fileTask.getId()) + .orderByAsc(FileTaskPart::getPartNumber) + ); + + try { + // 按分段号去重,相同分段号取第一个,如果分段号相同,etag不同,报错 + Map numberTag = parts.stream() + .collect(Collectors.toMap(FileTaskPart::getPartNumber, Function.identity(), (a, b) -> { + if (StringUtil.equals(a.getEtag(), b.getEtag())) { + return a; + } + log.error("etag不一致 id1:{} id2:{} etag1:{} etag2:{}", a.getId(), b.getId(), a.getEtag(), b.getEtag()); + throw new ServiceException("etag不一致"); + })); + // 获取要保留的分段id + List partId = numberTag.values().stream() + .map(FileTaskPart::getId) + .toList(); + // 去重 + parts = parts.stream() + .filter(e -> partId.contains(e.getId())) + .toList(); + List partEtags = parts.stream() + .map(convert::entity2vo) + .toList(); + // 3. 合并文件 + fileService.completeMultipartUpload(fileTask.getObjectKey(), fileTask.getUploadId(), partEtags); + // 4. 删除文件任务分段 + List partIds = parts.stream() + .map(FileTaskPart::getId) + .toList(); + partService.removeByIds(partIds); + // 5. 发布上传完成事件 + applicationContext.publishEvent(new FileUploadedEvent(fileTask.getId())); + } catch (Exception e) { + log.error("文件分段上传合并文件异常", e); + log.error("文件任务id:{} objectKey:{}", fileTask.getId(), fileTask.getObjectKey()); + // 更新文件任务状态为失败 + FileTask updateParam = new FileTask(); + updateParam.setId(fileTask.getId()); + updateParam.setStatus(FileTaskStatus.FAILED.getCode()); + this.updateById(updateParam); + } + } + } + + /** + * 新增分段 + * @param param + * @param fileTask + */ + private void addPart(FileTaskUpdateDTO param, FileTask fileTask) { + // 校验分段 + FileTaskPart part = partService.getOne(Wrappers.lambdaQuery() + .eq(FileTaskPart::getFileTaskId, fileTask.getId()) + .eq(FileTaskPart::getPartNumber, param.getPartNumber()) + .last("limit 1") + ); + if (part != null) { + // 分段存在 + if (StringUtil.equals(part.getEtag(), param.getEtag())) { + // 且etag和当前的etag相同,不做任何处理 + return; + } + // etag和当前的etag不同,报错 + throw new ServiceException("当前分段已存在"); + } + + // 新增分段 + FileTaskPart addParam = new FileTaskPart(); + addParam.setId(IdWorker.getId()); + addParam.setFileTaskId(fileTask.getId()); + addParam.setPartNumber(param.getPartNumber()); + addParam.setEtag(param.getEtag()); + partService.save(addParam); + } + + @Override + public String getUploadUrl(Long id, Integer partNumber) { + FileTask fileTask = this.getById(id); + Func.requireNotNull(fileTask, "文件任务不存在"); + if (FileTaskStatus.COMPLETED.getCode().equals(fileTask.getStatus())) { + throw new ServiceException("文件任务已完成"); + } + return fileService.getUploadPartUrl(fileTask.getObjectKey(), fileTask.getUploadId(), partNumber, null); + } + + @Transactional(rollbackFor = Exception.class) + @Override + public void cancelBatchByIds(List taskIds) { + // 查询任务 + List fileTasks = this.listByIds(taskIds); + if (CollectionUtil.isEmpty(fileTasks)) { + return; + } + // 校验状态 + fileTasks.forEach(task -> { + if (FileTaskStatus.COMPLETED.getCode().equals(task.getStatus())) { + throw new ServiceException("文件任务已上传完成"); + } + }); + // 调用obs取消上传任务 + try { + for (FileTask fileTask : fileTasks) { + fileService.cancelMultipartUpload(fileTask.getObjectKey(), fileTask.getUploadId()); + } + } catch (Exception e) { + log.error("调用obs取消文件上传任务异常", e); + throw new ServiceException("调用obs取消文件上传任务异常"); + } + // 删除上传任务 + this.removeBatchByIds(taskIds); + // 删除文件任务分片 + List list = partService.list(Wrappers.lambdaQuery() + .in(FileTaskPart::getFileTaskId, taskIds) + ); + if (CollectionUtil.isNotEmpty(list)) { + List partIds = list.stream() + .map(FileTaskPart::getId) + .toList(); + partService.removeByIds(partIds); + } + } + + @Override + public FileTaskUpdateVO updateFailed(Long id) { + // 校验文件任务 + FileTask fileTask = this.getById(id); + Func.requireNotNull(fileTask, "文件任务不存在"); + if (FileTaskStatus.COMPLETED.getCode().equals(fileTask.getStatus())) { + throw new ServiceException("文件任务已完成"); + } + // 更新文件任务状态为失败 + FileTask updateParam = new FileTask(); + updateParam.setId(id); + updateParam.setStatus(FileTaskStatus.FAILED.getCode()); + this.updateById(updateParam); + return this.getUpdateVO(id); + } + + @Override + public boolean updatePaused(List ids) { + if (CollectionUtil.isEmpty(ids)) { + return false; + } + FileTask updateParam = new FileTask(); + // 修改状态为已暂停 + updateParam.setStatus(FileTaskStatus.PAUSED.getCode()); + return this.update(updateParam, Wrappers.lambdaUpdate() + .in(FileTask::getId, ids) + // 只更新正在上传状态的 + .eq(FileTask::getStatus, FileTaskStatus.UPLOADING.getCode()) + ); + } + + /** + * 查询文件任务 + * @param id + * @return + */ + private FileTaskUpdateVO getUpdateVO(Long id) { + FileTask fileTask = this.getById(id); + return getFileTaskUpdateVO(fileTask); + } + + /** + * 查询文件任务 + * @param fileTask + * @return + */ + private FileTaskUpdateVO getFileTaskUpdateVO(FileTask fileTask) { + if (fileTask == null) { + return null; + } + FileTaskUpdateVO updateVO = convert.entity2updateVo(fileTask); + BusinessUnzipTask businessUnzipTask = unzipTaskMapper.selectOne(Wrappers.lambdaQuery() + .eq(BusinessUnzipTask::getRelateId, fileTask.getId()) + .last("limit 1") + ); + if (businessUnzipTask != null && businessUnzipTask.getDocCode() != null) { + updateVO.setDocCode(businessUnzipTask.getDocCode()); + } + return updateVO; + } + + /** + * 更新任务 + * + * @param param + * @param isLast 是否最后一段 + * @return + */ + private boolean updateTask(FileTaskUpdateDTO param, boolean isLast) { + FileTask updateParam = new FileTask(); + updateParam.setCurrentIndex(param.getPartNumber()); + if (isLast) { + // 最后一段,修改任务状态为完成 + updateParam.setStatus(FileTaskStatus.COMPLETED.getCode()); + } else { + // 非最后一段,修改任务状态为上传中 + updateParam.setStatus(FileTaskStatus.UPLOADING.getCode()); + } + return this.update(updateParam, Wrappers.lambdaUpdate() + .eq(FileTask::getId, param.getId()) + .lt(FileTask::getCurrentIndex, param.getPartNumber()) + ); + } +} diff --git a/blade-service/blade-file/src/main/java/org/springblade/file/service/impl/LocalFileServiceImpl.java b/blade-service/blade-file/src/main/java/org/springblade/file/service/impl/LocalFileServiceImpl.java new file mode 100644 index 0000000..6c76c4b --- /dev/null +++ b/blade-service/blade-file/src/main/java/org/springblade/file/service/impl/LocalFileServiceImpl.java @@ -0,0 +1,115 @@ +/** + * BladeX Commercial License Agreement + * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved. + *

+ * Use of this software is governed by the Commercial License Agreement + * obtained after purchasing a license from BladeX. + *

+ * 1. This software is for development use only under a valid license + * from BladeX. + *

+ * 2. Redistribution of this software's source code to any third party + * without a commercial license is strictly prohibited. + *

+ * 3. Licensees may copyright their own code but cannot use segments + * from this software for such purposes. Copyright of this software + * remains with BladeX. + *

+ * Using this software signifies agreement to this License, and the software + * must not be used for illegal purposes. + *

+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is + * not liable for any claims arising from secondary or illegal development. + *

+ * Author: Chill Zhuang (bladejava@qq.com) + */ +package org.springblade.file.service.impl; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springblade.core.log.exception.ServiceException; +import org.springblade.core.tool.utils.StringUtil; +import org.springblade.file.config.FileStorageProperties; + +import java.io.File; +import java.io.InputStream; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; + +/** + * 本地文件存储 + * + * @author Chill + */ +@Slf4j +@RequiredArgsConstructor +public class LocalFileServiceImpl extends AbstractFileService { + + private final FileStorageProperties.LocalProperties properties; + + @Override + public void uploadFile(String objectKey, InputStream inputStream) { + Path targetPath = resolveObjectPath(objectKey); + try { + Files.createDirectories(targetPath.getParent()); + Files.copy(inputStream, targetPath, StandardCopyOption.REPLACE_EXISTING); + } catch (Exception exception) { + log.error("本地文件上传异常 objectKey:{}", objectKey, exception); + throw new ServiceException("本地文件上传异常"); + } + } + + @Override + public String uploadTempFile(String objectKey, InputStream inputStream, Integer expires) { + this.uploadFile(objectKey, inputStream); + return this.getFileUrl(objectKey, null); + } + + @Override + public void uploadFile(String objectKey, File file) { + Path targetPath = resolveObjectPath(objectKey); + try { + Files.createDirectories(targetPath.getParent()); + Files.copy(file.toPath(), targetPath, StandardCopyOption.REPLACE_EXISTING); + } catch (Exception exception) { + log.error("本地文件上传异常 objectKey:{}", objectKey, exception); + throw new ServiceException("本地文件上传异常"); + } + } + + @Override + public String getFileUrl(String objectKey, String fileName, Long expires) { + if (StringUtil.isBlank(objectKey)) { + return null; + } + return properties.getAccessUrl() + "?objectKey=" + URLEncoder.encode(objectKey, StandardCharsets.UTF_8); + } + + @Override + public void downloadFile(String objectKey, String downloadFile) { + Path sourcePath = resolveObjectPath(objectKey); + try { + Path downloadPath = Path.of(downloadFile); + Files.createDirectories(downloadPath.getParent()); + Files.copy(sourcePath, downloadPath, StandardCopyOption.REPLACE_EXISTING); + } catch (Exception exception) { + log.error("本地文件下载异常 objectKey:{}", objectKey, exception); + throw new ServiceException("本地文件下载异常"); + } + } + + private Path resolveObjectPath(String objectKey) { + if (StringUtil.isBlank(objectKey)) { + throw new ServiceException("文件key不能为空"); + } + Path rootPath = Path.of(properties.getRootPath()).toAbsolutePath().normalize(); + Path objectPath = rootPath.resolve(objectKey).normalize(); + if (!objectPath.startsWith(rootPath)) { + throw new ServiceException("文件key不合法"); + } + return objectPath; + } +} diff --git a/blade-service/blade-file/src/main/java/org/springblade/file/service/impl/MinioFileServiceImpl.java b/blade-service/blade-file/src/main/java/org/springblade/file/service/impl/MinioFileServiceImpl.java new file mode 100644 index 0000000..e075588 --- /dev/null +++ b/blade-service/blade-file/src/main/java/org/springblade/file/service/impl/MinioFileServiceImpl.java @@ -0,0 +1,143 @@ +/** + * BladeX Commercial License Agreement + * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved. + *

+ * Use of this software is governed by the Commercial License Agreement + * obtained after purchasing a license from BladeX. + *

+ * 1. This software is for development use only under a valid license + * from BladeX. + *

+ * 2. Redistribution of this software's source code to any third party + * without a commercial license is strictly prohibited. + *

+ * 3. Licensees may copyright their own code but cannot use segments + * from this software for such purposes. Copyright of this software + * remains with BladeX. + *

+ * Using this software signifies agreement to this License, and the software + * must not be used for illegal purposes. + *

+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is + * not liable for any claims arising from secondary or illegal development. + *

+ * Author: Chill Zhuang (bladejava@qq.com) + */ +package org.springblade.file.service.impl; + +import io.minio.GetObjectArgs; +import io.minio.GetPresignedObjectUrlArgs; +import io.minio.MinioClient; +import io.minio.PutObjectArgs; +import io.minio.UploadObjectArgs; +import io.minio.http.Method; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springblade.core.log.exception.ServiceException; +import org.springblade.core.tool.utils.StringUtil; +import org.springblade.file.config.FileStorageProperties; + +import java.io.File; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; +import java.time.Duration; +import java.util.Optional; + +/** + * MinIO文件存储 + * + * @author Chill + */ +@Slf4j +@RequiredArgsConstructor +public class MinioFileServiceImpl extends AbstractFileService { + + private static final long PART_SIZE = 10 * 1024 * 1024L; + + private final FileStorageProperties.StoreProperties properties; + private final MinioClient minioClient; + + @Override + public void uploadFile(String objectKey, InputStream inputStream) { + try { + minioClient.putObject(PutObjectArgs.builder() + .bucket(properties.getBucketName()) + .object(resolveObjectKey(objectKey)) + .stream(inputStream, -1, PART_SIZE) + .build()); + } catch (Exception exception) { + log.error("MinIO文件上传异常 objectKey:{}", objectKey, exception); + throw new ServiceException("MinIO文件上传异常"); + } + } + + @Override + public String uploadTempFile(String objectKey, InputStream inputStream, Integer expires) { + this.uploadFile(objectKey, inputStream); + return this.getFileUrl(objectKey, Optional.ofNullable(expires).map(Duration::ofDays).map(Duration::toSeconds).orElse(null)); + } + + @Override + public void uploadFile(String objectKey, File file) { + try { + minioClient.uploadObject(UploadObjectArgs.builder() + .bucket(properties.getBucketName()) + .object(resolveObjectKey(objectKey)) + .filename(file.getAbsolutePath()) + .build()); + } catch (Exception exception) { + log.error("MinIO文件上传异常 objectKey:{}", objectKey, exception); + throw new ServiceException("MinIO文件上传异常"); + } + } + + @Override + public String getFileUrl(String objectKey, String fileName, Long expires) { + if (StringUtil.isBlank(objectKey)) { + return null; + } + try { + return minioClient.getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder() + .method(Method.GET) + .bucket(properties.getBucketName()) + .object(resolveObjectKey(objectKey)) + .expiry(Math.toIntExact(resolveExpires(expires))) + .build()); + } catch (Exception exception) { + log.error("MinIO获取文件链接异常 objectKey:{}", objectKey, exception); + throw new ServiceException("MinIO获取文件链接异常"); + } + } + + @Override + public void downloadFile(String objectKey, String downloadFile) { + try (InputStream inputStream = minioClient.getObject(GetObjectArgs.builder() + .bucket(properties.getBucketName()) + .object(resolveObjectKey(objectKey)) + .build())) { + Path downloadPath = Path.of(downloadFile); + Files.createDirectories(downloadPath.getParent()); + Files.copy(inputStream, downloadPath, StandardCopyOption.REPLACE_EXISTING); + } catch (Exception exception) { + log.error("MinIO文件下载异常 objectKey:{}", objectKey, exception); + throw new ServiceException("MinIO文件下载异常"); + } + } + + private String resolveObjectKey(String objectKey) { + if (StringUtil.isBlank(properties.getRootDirectory())) { + return objectKey; + } + if (properties.getRootDirectory().endsWith("/")) { + return properties.getRootDirectory() + objectKey; + } + return properties.getRootDirectory() + "/" + objectKey; + } + + private Long resolveExpires(Long expires) { + return Optional.ofNullable(expires) + .orElse(Optional.ofNullable(properties.getExpires()).orElse(Duration.ofHours(1).toSeconds())); + } +} diff --git a/blade-service/blade-file/src/main/java/org/springblade/file/service/impl/OCRConvertServiceImpl.java b/blade-service/blade-file/src/main/java/org/springblade/file/service/impl/OCRConvertServiceImpl.java new file mode 100644 index 0000000..1f5fa9c --- /dev/null +++ b/blade-service/blade-file/src/main/java/org/springblade/file/service/impl/OCRConvertServiceImpl.java @@ -0,0 +1,57 @@ +package org.springblade.file.service.impl; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springblade.core.log.exception.ServiceException; +import org.springblade.core.tool.utils.CollectionUtil; +import org.springblade.core.tool.utils.Func; +import org.springblade.core.tool.utils.StringUtil; +import org.springblade.file.convert.OCRConverter; +import org.springblade.file.pojo.dto.FileCertificateBatchRecognitionDTO; +import org.springblade.file.service.IFileService; +import org.springblade.file.service.IOCRConvertService; +import org.springblade.thirdparty.ocr.pojo.dto.CertificateBatchRecognitionDTO; +import org.springblade.thirdparty.ocr.pojo.vo.TransportCertificateVO; +import org.springblade.thirdparty.ocr.service.IOCRService; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * @author bfhuange + * @since 2025/3/13 + */ +@Slf4j +@RequiredArgsConstructor +@Service +public class OCRConvertServiceImpl implements IOCRConvertService { + + private final IFileService fileService; + private final IOCRService ocrService; + private final OCRConverter converter; + + @Override + public TransportCertificateVO recognitionTransportCertificate(FileCertificateBatchRecognitionDTO param) { + Func.requireNotNull(param, "参数不能为空"); + if (CollectionUtil.isEmpty(param.getObjectKeys())) { + throw new ServiceException("图像obs key不能为空"); + } + CertificateBatchRecognitionDTO ocrParam = converter.dto2ocr(param); + // 转换文件链接 + List fileUrls = param.getObjectKeys().stream() + .map(this::resolveFileUrl) + .toList(); + ocrParam.setUrls(fileUrls); + return ocrService.recognitionTransportCertificate(ocrParam); + } + + private String resolveFileUrl(String objectKey) { + if (StringUtil.isBlank(objectKey)) { + return objectKey; + } + if (objectKey.startsWith("http://") || objectKey.startsWith("https://")) { + return objectKey; + } + return fileService.getFileUrl(objectKey, null); + } +} diff --git a/blade-service/blade-file/src/main/java/org/springblade/file/service/impl/ObsFileServiceImpl.java b/blade-service/blade-file/src/main/java/org/springblade/file/service/impl/ObsFileServiceImpl.java new file mode 100644 index 0000000..82014e2 --- /dev/null +++ b/blade-service/blade-file/src/main/java/org/springblade/file/service/impl/ObsFileServiceImpl.java @@ -0,0 +1,293 @@ +package org.springblade.file.service.impl; + +import cn.hutool.core.net.URLEncodeUtil; +import com.obs.services.ObsClient; +import com.obs.services.exception.ObsException; +import com.obs.services.internal.Constants; +import com.obs.services.internal.utils.ServiceUtils; +import com.obs.services.model.*; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springblade.core.log.exception.ServiceException; +import org.springblade.core.tool.utils.CollectionUtil; +import org.springblade.core.tool.utils.StringUtil; +import org.springblade.file.config.ObsProperties; +import org.springblade.file.pojo.vo.PartEtagVO; +import org.springblade.file.service.IFileService; +import org.springblade.file.service.support.DownloadProgressListener; +import org.springblade.file.service.support.UploadProgressListener; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.security.NoSuchAlgorithmException; +import java.time.Duration; +import java.util.*; +import java.util.stream.Collectors; + +/** + * 华为obs对象存储 + * @author bfhuange + * @date 2024/10/21 + */ +@Slf4j +@RequiredArgsConstructor +public class ObsFileServiceImpl implements IFileService { + private final ObsProperties properties; + private final ObsClient obsClient; + + @Override + public void uploadFile(String objectKey, InputStream is) { + this.uploadFile(objectKey, null, is, null); + } + + @Override + public String uploadTempFile(String objectKey, InputStream is, Integer expires) { + // 上传文件 + this.uploadFile(objectKey, null, is, expires); + // 生成下载链接 + return this.getFileUrl(objectKey, properties.getExpires()); + } + + @Override + public void uploadFile(String objectKey, File file) { + this.uploadFile(objectKey, file, null, null); + } + + @Override + public String initiateMultipartUpload(String objectKey) { + InitiateMultipartUploadRequest initiateRequest = new InitiateMultipartUploadRequest(properties.getBucketName(), objectKey); + try { + InitiateMultipartUploadResult initiateResult = obsClient.initiateMultipartUpload(initiateRequest); + return initiateResult.getUploadId(); + } catch (Exception e) { + log.error("初始化分段上传异常", e); + throw new ServiceException("初始化分段上传异常"); + } + } + + @Override + public PartEtagVO uploadPart(String objectKey, String uploadId, File file, int partNumber, Long offset, Long partSize) { + UploadPartRequest uploadPartRequest = new UploadPartRequest(); + uploadPartRequest.setBucketName(properties.getBucketName()); + uploadPartRequest.setObjectKey(objectKey); + uploadPartRequest.setUploadId(uploadId); + uploadPartRequest.setPartNumber(partNumber); + uploadPartRequest.setFile(file); + if (offset != null) { + uploadPartRequest.setOffset(offset); + } + if (partSize != null) { + uploadPartRequest.setPartSize(partSize); + } + try { + UploadPartResult uploadPartResult = obsClient.uploadPart(uploadPartRequest); + return new PartEtagVO(uploadPartResult.getEtag(), uploadPartResult.getPartNumber()); + } catch (Exception e) { + log.error("分段上传异常", e); + throw new ServiceException("分段上传异常"); + } + } + + @Override + public void completeMultipartUpload(String objectKey, String uploadId, List partEtagVOS) { + if (CollectionUtil.isEmpty(partEtagVOS)) { + log.warn("完成分段上传,要合并的分段列表为空"); + return; + } + // 参数转换 + List list = partEtagVOS.stream() + .map(partEtagVO -> new PartEtag(partEtagVO.getEtag(), partEtagVO.getPartNumber())) + .collect(Collectors.toCollection(ArrayList::new)); + CompleteMultipartUploadRequest request = + new CompleteMultipartUploadRequest(properties.getBucketName(), objectKey, uploadId, list); + try { + obsClient.completeMultipartUpload(request); + } catch (Exception e) { + log.error("完成分段上传异常", e); + throw new ServiceException("完成分段上传异常"); + } + } + + @Override + public void cancelMultipartUpload(String objectKey, String uploadId) { + AbortMultipartUploadRequest request = new AbortMultipartUploadRequest(properties.getBucketName(), objectKey, uploadId); + try { + obsClient.abortMultipartUpload(request); + } catch (Exception e) { + log.error("取消分段上传任务异常", e); + throw new ServiceException("取消分段上传任务异常"); + } + } + + @Override + public String getFileUrl(String objectKey, Long expires) { + return this.getFileUrl(objectKey, null, expires); + } + + @Override + public String getFileUrl(String objectKey, String fileName, Long expires) { + if (StringUtil.isBlank(objectKey)) { + return null; + } + // 链接失效时间 + expires = Optional.ofNullable(expires) + // 参数为空取配置 + .orElse(Optional.ofNullable(properties.getExpires()) + // 配置为空,默认1小时 + .orElse(Duration.ofHours(1).getSeconds())); + TemporarySignatureRequest request = new TemporarySignatureRequest(HttpMethodEnum.GET, expires); + request.setBucketName(properties.getBucketName()); + request.setObjectKey(objectKey); + request.setRequestDate(new Date()); + if (StringUtil.isNotBlank(fileName)) { + // 文件名不为空,修改响应的文件名 + Map queryParam = new HashMap<>(); + String encodeFileName = encode(fileName); + queryParam.put(Constants.ObsRequestParams.RESPONSE_CONTENT_DISPOSITION, "attachment;filename*=UTF-8''" + encodeFileName); + request.setQueryParams(queryParam); + } + try { + TemporarySignatureResponse temporarySignature = obsClient.createTemporarySignature(request); + return temporarySignature.getSignedUrl(); + } catch (Exception e) { + log.error("获取文件链接异常", e); + throw new ServiceException("获取文件链接异常"); + } + } + + /** + * 编码文件名 + * @param fileName + * @return + */ + private String encode(String fileName) { + int index = fileName.lastIndexOf("."); + if (index < 0) { + return URLEncodeUtil.encode(fileName); + } + // 文件名 + String prefix = fileName.substring(0, index); + // 后缀 + String ext = fileName.substring(index); + return URLEncodeUtil.encode(prefix) + ext; + } + + @Override + public String getUploadPartUrl(String objectKey, String uploadId, Integer partNumber, Long expires) { + TemporarySignatureRequest request = new TemporarySignatureRequest(); + request.setBucketName(properties.getBucketName()); + request.setObjectKey(objectKey); + request.setRequestDate(new Date()); + // 链接失效时间 + expires = Optional.ofNullable(expires) + .orElse(properties.getExpires()); + if (expires != null) { + request.setExpires(expires); + } + request.setMethod(HttpMethodEnum.PUT); + request.setHeaders(Map.of(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)); + Map params = new HashMap<>(); + params.put(Constants.ObsRequestParams.PART_NUMBER, String.valueOf(partNumber)); + params.put(Constants.ObsRequestParams.UPLOAD_ID, uploadId); + request.setQueryParams(params); + try { + TemporarySignatureResponse temporarySignature = obsClient.createTemporarySignature(request); + return temporarySignature.getSignedUrl(); + } catch (Exception e) { + log.error("获取文件链接异常", e); + throw new ServiceException("获取文件链接异常"); + } + } + + @Override + public List getFileUrls(List objectKeys, Long expires) { + if (CollectionUtil.isEmpty(objectKeys)) { + return Collections.emptyList(); + } + return objectKeys.stream() + .map(objectKey -> this.getFileUrl(objectKey, expires)) + .toList(); + } + + @Override + public void downloadFile(String objectKey, String downloadFile) { + DownloadFileRequest request = new DownloadFileRequest(properties.getBucketName(), objectKey); + request.setDownloadFile(downloadFile); + // 开启断点续传模式 + request.setEnableCheckpoint(true); + Long progressInterval = properties.getDownloadProgressInterval(); + // 设置下载进度日志 + request.setProgressListener(new DownloadProgressListener(objectKey, progressInterval)); + request.setProgressInterval(progressInterval); + DownloadFileResult result = null; + try { + result = obsClient.downloadFile(request); + } catch (ObsException e) { + log.error("下载文件异常", e); + log.error("下载文件异常 objectKey:{} responseCode:{} errorCode:{} errorMessage:{} errorRequestId:{} errorHostId:{}", objectKey, e.getResponseCode(), e.getErrorCode(), e.getErrorMessage(), e.getErrorRequestId(), e.getErrorHostId()); + throw new ServiceException("下载文件异常"); + } catch (Exception e) { + log.error("下载文件异常", e); + throw new ServiceException("下载文件异常"); + } finally { + String etag = Optional.ofNullable(result) + .map(DownloadFileResult::getObjectMetadata) + .map(ObjectMetadata::getEtag) + .orElse(null); + log.info("下载文件结束 objectKey:{} etag:{}", objectKey, etag); + } + } + + /** + * 上传文件 + * @param objectKey + * @param file + * @param is + * @param expires 过期时间,单位天,过期后自动删除 + */ + private void uploadFile(String objectKey, File file, InputStream is, Integer expires) { + if (file == null && is == null ) { + throw new ServiceException("文件不存在"); + } + PutObjectRequest request = new PutObjectRequest(); + request.setBucketName(properties.getBucketName()); + request.setObjectKey(objectKey); + if (expires != null) { + request.setExpires(expires); + } + if (is != null) { + request.setInput(is); + } else { + request.setFile(file); + // 文件不为空,设置md5 + String md5 = getFileMd5(file); + ObjectMetadata metadata = new ObjectMetadata(); + metadata.setContentMd5(md5); + request.setMetadata(metadata); + } + Long progressInterval = properties.getUploadProgressInterval(); + // 设置上传进度日志 + request.setProgressListener(new UploadProgressListener(objectKey, progressInterval)); + request.setProgressInterval(progressInterval); + try { + obsClient.putObject(request); + } catch (Exception e) { + log.error("上传文件异常", e); + throw new ServiceException("上传文件异常"); + } + } + + @Override + public String getFileMd5(File file) { + try { + return ServiceUtils.toBase64(ServiceUtils.computeMD5Hash(new FileInputStream(file))); + } catch (NoSuchAlgorithmException | IOException e) { + log.error("获取文件md5异常", e); + return null; + } + } +} diff --git a/blade-service/blade-file/src/main/java/org/springblade/file/service/impl/OssFileServiceImpl.java b/blade-service/blade-file/src/main/java/org/springblade/file/service/impl/OssFileServiceImpl.java new file mode 100644 index 0000000..ff82887 --- /dev/null +++ b/blade-service/blade-file/src/main/java/org/springblade/file/service/impl/OssFileServiceImpl.java @@ -0,0 +1,118 @@ +/** + * BladeX Commercial License Agreement + * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved. + *

+ * Use of this software is governed by the Commercial License Agreement + * obtained after purchasing a license from BladeX. + *

+ * 1. This software is for development use only under a valid license + * from BladeX. + *

+ * 2. Redistribution of this software's source code to any third party + * without a commercial license is strictly prohibited. + *

+ * 3. Licensees may copyright their own code but cannot use segments + * from this software for such purposes. Copyright of this software + * remains with BladeX. + *

+ * Using this software signifies agreement to this License, and the software + * must not be used for illegal purposes. + *

+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is + * not liable for any claims arising from secondary or illegal development. + *

+ * Author: Chill Zhuang (bladejava@qq.com) + */ +package org.springblade.file.service.impl; + +import com.aliyun.oss.OSS; +import com.aliyun.oss.model.GetObjectRequest; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springblade.core.log.exception.ServiceException; +import org.springblade.core.tool.utils.StringUtil; +import org.springblade.file.config.FileStorageProperties; + +import java.io.File; +import java.io.InputStream; +import java.time.Duration; +import java.util.Date; +import java.util.Optional; + +/** + * 阿里云OSS文件存储 + * + * @author Chill + */ +@Slf4j +@RequiredArgsConstructor +public class OssFileServiceImpl extends AbstractFileService { + + private final FileStorageProperties.StoreProperties properties; + private final OSS ossClient; + + @Override + public void uploadFile(String objectKey, InputStream inputStream) { + try { + ossClient.putObject(properties.getBucketName(), resolveObjectKey(objectKey), inputStream); + } catch (Exception exception) { + log.error("OSS文件上传异常 objectKey:{}", objectKey, exception); + throw new ServiceException("OSS文件上传异常"); + } + } + + @Override + public String uploadTempFile(String objectKey, InputStream inputStream, Integer expires) { + this.uploadFile(objectKey, inputStream); + return this.getFileUrl(objectKey, Optional.ofNullable(expires).map(Duration::ofDays).map(Duration::toSeconds).orElse(null)); + } + + @Override + public void uploadFile(String objectKey, File file) { + try { + ossClient.putObject(properties.getBucketName(), resolveObjectKey(objectKey), file); + } catch (Exception exception) { + log.error("OSS文件上传异常 objectKey:{}", objectKey, exception); + throw new ServiceException("OSS文件上传异常"); + } + } + + @Override + public String getFileUrl(String objectKey, String fileName, Long expires) { + if (StringUtil.isBlank(objectKey)) { + return null; + } + try { + Date expiration = new Date(System.currentTimeMillis() + resolveExpires(expires) * 1000); + return ossClient.generatePresignedUrl(properties.getBucketName(), resolveObjectKey(objectKey), expiration).toString(); + } catch (Exception exception) { + log.error("OSS获取文件链接异常 objectKey:{}", objectKey, exception); + throw new ServiceException("OSS获取文件链接异常"); + } + } + + @Override + public void downloadFile(String objectKey, String downloadFile) { + try { + ossClient.getObject(new GetObjectRequest(properties.getBucketName(), resolveObjectKey(objectKey)), new File(downloadFile)); + } catch (Exception exception) { + log.error("OSS文件下载异常 objectKey:{}", objectKey, exception); + throw new ServiceException("OSS文件下载异常"); + } + } + + private String resolveObjectKey(String objectKey) { + if (StringUtil.isBlank(properties.getRootDirectory())) { + return objectKey; + } + if (properties.getRootDirectory().endsWith("/")) { + return properties.getRootDirectory() + objectKey; + } + return properties.getRootDirectory() + "/" + objectKey; + } + + private Long resolveExpires(Long expires) { + return Optional.ofNullable(expires) + .orElse(Optional.ofNullable(properties.getExpires()).orElse(Duration.ofHours(1).toSeconds())); + } +} diff --git a/blade-service/blade-file/src/main/java/org/springblade/file/service/support/DownloadProgressListener.java b/blade-service/blade-file/src/main/java/org/springblade/file/service/support/DownloadProgressListener.java new file mode 100644 index 0000000..7703cc5 --- /dev/null +++ b/blade-service/blade-file/src/main/java/org/springblade/file/service/support/DownloadProgressListener.java @@ -0,0 +1,31 @@ +package org.springblade.file.service.support; + +import com.obs.services.model.ProgressListener; +import com.obs.services.model.ProgressStatus; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +/** + * 文件下载进度监听器 + * @author bfhuange + * @date 2024/10/28 + */ +@AllArgsConstructor +@Slf4j +public class DownloadProgressListener implements ProgressListener { + + /** + * obs对象名称 + */ + private final String objectKey; + + /** + * 进度间隔,如果新传输的大小不等于进度间隔,说明是结束了 + */ + private final long progressInterval; + + @Override + public void progressChanged(ProgressStatus status) { + log.info("文件下载进度 文件名:{} 是否结束:{} 总大小:{} 已下载大小:{} 百分比:{} 下载速度:{}", objectKey, progressInterval != status.getNewlyTransferredBytes(), status.getTotalBytes(), status.getTransferredBytes(), status.getTransferPercentage(), String.format("%.2f", status.getAverageSpeed())); + } +} diff --git a/blade-service/blade-file/src/main/java/org/springblade/file/service/support/UploadProgressListener.java b/blade-service/blade-file/src/main/java/org/springblade/file/service/support/UploadProgressListener.java new file mode 100644 index 0000000..f1af602 --- /dev/null +++ b/blade-service/blade-file/src/main/java/org/springblade/file/service/support/UploadProgressListener.java @@ -0,0 +1,31 @@ +package org.springblade.file.service.support; + +import com.obs.services.model.ProgressListener; +import com.obs.services.model.ProgressStatus; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +/** + * 文件上传进度监听器 + * @author bfhuange + * @date 2024/10/28 + */ +@AllArgsConstructor +@Slf4j +public class UploadProgressListener implements ProgressListener { + + /** + * obs对象名称 + */ + private final String objectKey; + + /** + * 进度间隔,如果新传输的大小不等于进度间隔,说明是结束了 + */ + private final long progressInterval; + + @Override + public void progressChanged(ProgressStatus status) { + log.info("文件上传进度 文件名:{} 是否结束:{} 已上传大小:{} 上传速度:{}", objectKey, progressInterval != status.getNewlyTransferredBytes(), status.getTransferredBytes(), String.format("%.2f", status.getAverageSpeed())); + } +} diff --git a/blade-service/blade-file/src/main/java/org/springblade/file/util/ObjectKeyUtils.java b/blade-service/blade-file/src/main/java/org/springblade/file/util/ObjectKeyUtils.java new file mode 100644 index 0000000..e658836 --- /dev/null +++ b/blade-service/blade-file/src/main/java/org/springblade/file/util/ObjectKeyUtils.java @@ -0,0 +1,98 @@ +package org.springblade.file.util; + +import com.baomidou.mybatisplus.core.toolkit.IdWorker; +import org.springblade.core.secure.utils.AuthUtil; +import org.springblade.core.tool.utils.RandomType; +import org.springblade.core.tool.utils.StringPool; +import org.springblade.core.tool.utils.StringUtil; + +/** + * 对象存储key工具类 + * @author bfhuange + * @date 2024/10/29 + */ +public class ObjectKeyUtils { + /** + * object key 格式,前缀目录/ + id + 用户id后4位 + 后缀 + */ + private static final String FORMAT = "%s%s%s%s"; + /** + * 用户id后缀位数 + */ + private static final int ID_SUFFIX_LENGTH = 4; + + private ObjectKeyUtils() {} + + /** + * 生成 object key + * @param dir 文件目录 + * @param fileName 文件名 + * @return + */ + public static String generateObjectKey(String dir, String fileName) { + // 目录字符串 + String dirString = ObjectKeyUtils.getDirString(dir); + // 文件名后缀 + String fileNameSuffix = ObjectKeyUtils.getFileNameSuffix(fileName); + // 用户id后4位 + String userIdSuffix = ObjectKeyUtils.getUserIdSuffix(); + // 格式,目录 + id + 用户id后4位 + 后缀 + return String.format(FORMAT, dirString, IdWorker.getId(), userIdSuffix, fileNameSuffix); + } + + /** + * 生成 object key + * @param fileName 文件名 + * @return + */ + public static String generateObjectKey(String fileName) { + return ObjectKeyUtils.generateObjectKey(StringPool.EMPTY, fileName); + } + + /** + * 获取用户id后缀字符串 + * @return + */ + private static String getUserIdSuffix() { + Long userId = AuthUtil.getUserId(); + if (userId > 0) { + String idString = userId.toString(); + if (idString.length() >= ID_SUFFIX_LENGTH) { + // 截取用户id后几位 + return idString.substring(idString.length() - ID_SUFFIX_LENGTH); + } + } + // 未获取到用户id,取随机数字 + return StringUtil.random(ID_SUFFIX_LENGTH, RandomType.INT); + } + + /** + * 获取目录字符串 + * @param dir + * @return + */ + private static String getDirString(String dir) { + if (StringUtil.isBlank(dir)) { + return StringPool.EMPTY; + } + if (dir.endsWith(StringPool.SLASH)) { + return dir; + } + return dir + StringPool.SLASH; + } + + /** + * 获取文件名后缀 + * @param fileName 文件名 + * @return + */ + private static String getFileNameSuffix(String fileName) { + if (StringUtil.isBlank(fileName)) { + return StringPool.EMPTY; + } + if (!fileName.contains(StringPool.DOT)) { + return StringPool.EMPTY; + } + return fileName.substring(fileName.lastIndexOf(StringPool.DOT)); + } +} diff --git a/blade-service/blade-file/src/main/java/org/springblade/file/util/UnzipUtil.java b/blade-service/blade-file/src/main/java/org/springblade/file/util/UnzipUtil.java new file mode 100644 index 0000000..af0aceb --- /dev/null +++ b/blade-service/blade-file/src/main/java/org/springblade/file/util/UnzipUtil.java @@ -0,0 +1,245 @@ +package org.springblade.file.util; + +import cn.hutool.core.io.CharsetDetector; +import com.github.junrar.Archive; +import com.github.junrar.rarfile.FileHeader; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.compress.archivers.ArchiveEntry; +import org.apache.commons.compress.archivers.ArchiveException; +import org.apache.commons.compress.archivers.ArchiveInputStream; +import org.apache.commons.compress.archivers.ArchiveStreamFactory; +import org.apache.commons.compress.archivers.sevenz.SevenZArchiveEntry; +import org.apache.commons.compress.archivers.sevenz.SevenZFile; +import org.apache.commons.compress.archivers.zip.ZipArchiveEntry; +import org.apache.commons.io.IOUtils; +import org.springblade.core.log.exception.ServiceException; +import org.springblade.core.tool.utils.StringPool; + +import java.io.*; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; +import java.util.zip.ZipInputStream; + +/** + * 解压工具类 + * @author bfhuange + * @since 2025/2/28 + */ +@Slf4j +public class UnzipUtil { + /** + * rar格式 + */ + public static final String RAR = "rar"; + + /** + * macos下垃圾目录,不处理 + */ + private static final String MACOSX_DIR = "__MACOSX"; + + /** + * 解压文件 + * @param zipFilePath 压缩文件路径 + * @param unzipDir 解压目录 + */ + public static List unzipOrUnrar(String zipFilePath, File unzipDir) { + if (zipFilePath.toLowerCase().endsWith(RAR)) { + // 解压rar + return unrar(zipFilePath, unzipDir); + } else if (zipFilePath.toLowerCase().endsWith(ArchiveStreamFactory.SEVEN_Z)) { + // 解压7z + return un7z(zipFilePath, unzipDir); + } + // 解压其余格式 + return unzip(zipFilePath, unzipDir); + } + + /** + * 解压zip格式 + * @param downloadFile + * @param destDir + */ + public static List un7z(String downloadFile, File destDir) { + List files = new ArrayList<>(); + try( + SevenZFile sevenZFile = new SevenZFile.Builder() + .setFile(new File(downloadFile)) + .get() + ) { + SevenZArchiveEntry entry = sevenZFile.getNextEntry(); + while (entry != null) { + log.debug("压缩文件条目:{}", entry.getName()); + File file = new File(destDir, entry.getName()); + if (!entry.isDirectory()) { + // 不是目录才处理 + files.add(file); + File parentFile = file.getParentFile(); + if (!parentFile.exists()) { + parentFile.mkdirs(); + } + try( + OutputStream os = new FileOutputStream(file); + InputStream is = sevenZFile.getInputStream(entry); + ) { + IOUtils.copy(is, os); + } + } + entry = sevenZFile.getNextEntry(); + } + } catch (IOException e) { + log.error("解压文件异常", e); + throw new RuntimeException(e); + } + return files; + } + + /** + * 解压rar格式 + * @param zipFilePath 压缩文件路径 + * @param unzipDir 解压文件目录 + */ + public static List unrar(String zipFilePath, File unzipDir) { + List files = new ArrayList<>(); + try ( + // 加载rar文件 + InputStream inputStream = Files.newInputStream(Paths.get(zipFilePath)); + Archive archive = new Archive(inputStream); + ) { + List fileHeaders = archive.getFileHeaders(); + if (fileHeaders == null) { + throw new ServiceException("压缩文件为空"); + } + int total = fileHeaders.size(); + for (int i = 0; i < fileHeaders.size(); i++) { + FileHeader fileHeader = fileHeaders.get(i); + log.debug("解压进度:{}/{} 文件:{}", i + 1, total, fileHeader.getFileName()); + // 解压文件 + File file = new File(unzipDir, fileHeader.getFileName()); + if (!fileHeader.isDirectory()) { + // 不是目录才处理 + files.add(file); + File parentFile = file.getParentFile(); + if (!parentFile.exists()) { + parentFile.mkdirs(); + } + InputStream is = archive.getInputStream(fileHeader); + try (OutputStream os = new FileOutputStream(file)) { + IOUtils.copy(is, os); + } + } + } + } catch (ServiceException e) { + throw e; + } catch (Exception e) { + log.error("解压文件异常", e); + throw new ServiceException("解压文件异常"); + } + return files; + } + + /** + * 解压zip格式 + * @param zipFilePath 压缩文件路径 + * @param unzipDir 解压目录 + */ + public static List unzip(String zipFilePath, File unzipDir) { + List files = new ArrayList<>(); + ArchiveStreamFactory archiveStreamFactory = new ArchiveStreamFactory(); + try( + FileInputStream is = new FileInputStream(zipFilePath); + BufferedInputStream bis = new BufferedInputStream(is); + ArchiveInputStream ais = archiveStreamFactory.createArchiveInputStream(bis); + ) { + ArchiveEntry entry; + while ((entry = ais.getNextEntry()) != null) { + // 获取文件名称 + String entryName = UnzipUtil.getEntryName(entry); + log.debug("压缩文件条目:{}", entryName); + if (entryName.contains(MACOSX_DIR)) { + // macos下垃圾目录,跳过 + log.info("跳过macos下__MACOSX的文件:{}", entryName); + continue; + } + // 压缩文件内文件夹分隔符是\,linux下不会识别为文件夹,会识别成文件名,处理下 + if (entryName.contains(StringPool.BACK_SLASH)) { + entryName = entryName.replace(StringPool.BACK_SLASH, StringPool.SLASH); + } + if (!isDirectory(entryName)) { + File file = new File(unzipDir, entryName); + if (file.getName().startsWith(StringPool.DOT)) { + // .开头的文件,是隐藏文件,跳过 + log.info("跳过.开头的文件:{}", entryName); + continue; + } + // 不是目录才处理 + files.add(file); + File parentFile = file.getParentFile(); + if (!parentFile.exists()) { + parentFile.mkdirs(); + } + try(OutputStream os = new FileOutputStream(file)) { + IOUtils.copy(ais, os); + } + } + } + } catch (IOException e) { + log.error("解压文件异常", e); + throw new RuntimeException(e); + } catch (ArchiveException e) { + throw new RuntimeException(e); + } + return files; + } + + /** + * 获取文件名称 + * @param entry + * @return + */ + private static String getEntryName(ArchiveEntry entry) { + if (entry instanceof ZipArchiveEntry zipArchiveEntry) { + byte[] rawName = zipArchiveEntry.getRawName(); + // 检测文件名编码 + Charset charset = CharsetDetector.detect(new ByteArrayInputStream(rawName)); + return new String(rawName, charset); + } + return entry.getName(); + } + + /** + * 判断压缩文件是否utf8编码 + * @param zipFilePath 压缩文件路径 + * @return + */ + public static boolean isUtf8(String zipFilePath) { + try ( + FileInputStream fileInputStream = new FileInputStream(zipFilePath); + ZipInputStream zipInputStream = new ZipInputStream(fileInputStream, StandardCharsets.UTF_8); + ) { + while (zipInputStream.getNextEntry() != null) { + + } + return true; + } catch (Exception ignore) { + // gbk编码的用utf8编码读取会抛异常 + return false; + } + } + + /** + * 判断压缩文件中条目是否文件夹 + * @param entryName + * @return + */ + private static boolean isDirectory(String entryName) { + if (entryName == null) { + return false; + } + return entryName.endsWith(StringPool.SLASH) || entryName.endsWith(StringPool.BACK_SLASH); + } + +} diff --git a/blade-service/blade-file/src/main/resources/bootstrap-dev.yml b/blade-service/blade-file/src/main/resources/bootstrap-dev.yml new file mode 100644 index 0000000..303fc85 --- /dev/null +++ b/blade-service/blade-file/src/main/resources/bootstrap-dev.yml @@ -0,0 +1,6 @@ +#spring: +# cloud: +# nacos: +# username: nacos +# password: ${NACOS_PASSWORD:gr30wIs5%Hi7keQj} +# server-addr: ${NACOS_ADDR:10.38.16.127:8848} diff --git a/blade-service/blade-file/src/main/resources/bootstrap-prod.yml b/blade-service/blade-file/src/main/resources/bootstrap-prod.yml new file mode 100644 index 0000000..673f396 --- /dev/null +++ b/blade-service/blade-file/src/main/resources/bootstrap-prod.yml @@ -0,0 +1,6 @@ +#spring: +# cloud: +# nacos: +# username: nacos +# password: rWrMrVTWyf%ekjuw +# server-addr: ${NACOS_ADDR:192.168.0.242:8848} diff --git a/blade-service/blade-file/src/main/resources/bootstrap-test.yml b/blade-service/blade-file/src/main/resources/bootstrap-test.yml new file mode 100644 index 0000000..7586f5a --- /dev/null +++ b/blade-service/blade-file/src/main/resources/bootstrap-test.yml @@ -0,0 +1,8 @@ +#server: +# port: 38107 +#spring: +# cloud: +# nacos: +# username: nacos +# password: gr30wIs5%Hi7keQj +# server-addr: ${NACOS_ADDR:10.38.16.127:8848} diff --git a/blade-service/blade-file/src/main/resources/bootstrap.yml b/blade-service/blade-file/src/main/resources/bootstrap.yml new file mode 100644 index 0000000..f2240b3 --- /dev/null +++ b/blade-service/blade-file/src/main/resources/bootstrap.yml @@ -0,0 +1,61 @@ +server: + port: 8107 + +#数据源配置 +spring: + datasource: + url: ${blade.datasource.dev.url} + username: ${blade.datasource.dev.username} + password: ${blade.datasource.dev.password} + cloud: + nacos: + config: + shared-configs: + - data-id: third-party-api.yaml + refresh: true + +#第三方接口本地默认配置,Nacos third-party-api.yaml 可覆盖 +thirdParty: + ocr: + baseUrl: ${OCR_BASE_URL:http://127.0.0.1:8080} + baseOtherUrl: ${OCR_BASE_OTHER_URL:http://127.0.0.1:8080} + wps: + baseUrl: ${WPS_BASE_URL:http://127.0.0.1:8080} + oa: + baseUrl: ${OA_BASE_URL:http://127.0.0.1:8080} + track: + baseUrl: ${TRACK_BASE_URL:http://127.0.0.1:8080} + mk: + baseUrl: ${MK_BASE_URL:http://127.0.0.1:8080} + +#spring: +# application: +# name: blade-file +# cloud: +# nacos: +# discovery: +# namespace: ${spring.profiles.active} +# # 不注册到nacos +# #registerEnabled: false +# config: +# # 文件后缀名 +# file-extension: yaml +# namespace: ${spring.profiles.active} +# shared-configs: +# - data-id: blade.yaml +# refresh: true +# - data-id: blade-${spring.profiles.active}.yaml +# refresh: true +# - data-id: third-party-api.yaml +# refresh: true +# sentinel: +# datasource: +# flow: +# nacos: +# serverAddr: ${spring.cloud.nacos.server-addr} +# username: ${spring.cloud.nacos.username} +# password: ${spring.cloud.nacos.password} +# dataId: sentinel-${spring.application.name}.json +# groupId: DEFAULT_GROUP +# namespace: ${spring.profiles.active} +# ruleType: flow diff --git a/blade-service/blade-openapi/Dockerfile b/blade-service/blade-openapi/Dockerfile new file mode 100644 index 0000000..d4b8014 --- /dev/null +++ b/blade-service/blade-openapi/Dockerfile @@ -0,0 +1,19 @@ +FROM bladex/alpine-java:openjdk17_cn_slim +ARG ACTIVE + +LABEL maintainer="bladejava@qq.com" + +# 设置时区为上海 +ENV TZ=Asia/Shanghai +# 设置时区信息 +RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone + +COPY ./target/blade-openapi.jar /opt/app.jar + +WORKDIR /opt + +EXPOSE 8108 + +ENTRYPOINT ["java", "--add-opens", "java.base/java.lang=ALL-UNNAMED", "--add-opens", "java.base/java.lang.reflect=ALL-UNNAMED", "-Djava.security.egd=file:/dev/./urandom", "-XX:+HeapDumpOnOutOfMemoryError", "-XX:HeapDumpPath=/opt/logs", "-XX:ErrorFile=/opt/logs/hs_err_pid%p.log", "-Xlog:gc*:file=/opt/logs/gc.log:time,uptime,level,tags:filecount=5,filesize=50M", "-XX:MaxDirectMemorySize=512m", "-Xms1024m", "-Xmx4096m", "-jar", "app.jar"] + +CMD ["--spring.profiles.active=${ACTIVE}","--logging.path=/opt/logs"] diff --git a/blade-service/blade-openapi/Dockerfile-rt b/blade-service/blade-openapi/Dockerfile-rt new file mode 100644 index 0000000..55d86d2 --- /dev/null +++ b/blade-service/blade-openapi/Dockerfile-rt @@ -0,0 +1,17 @@ +#FROM 10.38.16.129/bladex/alpine-java:openjdk17_cn_slim +#FROM registry.cn-hangzhou.aliyuncs.com/vic_person/alpine-java:openjdk17_cn_slim +FROM 10.38.16.129:80/bladex/alpine-java:openjdk17_cn_slim +ARG ACTIVE + +LABEL maintainer="bladejava@qq.com" + +# 设置时区为上海 +ENV TZ=Asia/Shanghai +# 设置时区信息 +RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone + +COPY ./target/*.jar /opt/app.jar + +ENTRYPOINT ["java", "--add-opens", "java.base/java.lang=ALL-UNNAMED", "--add-opens", "java.base/java.lang.reflect=ALL-UNNAMED", "-Djava.security.egd=file:/dev/./urandom", "-jar", "/opt/app.jar"] + +CMD ["--spring.profiles.active=${ACTIVE}"] diff --git a/blade-service/blade-openapi/Dockerfile-zh-dev b/blade-service/blade-openapi/Dockerfile-zh-dev new file mode 100644 index 0000000..1e4571b --- /dev/null +++ b/blade-service/blade-openapi/Dockerfile-zh-dev @@ -0,0 +1,19 @@ +FROM bladex/alpine-java:openjdk17_cn_slim +ARG ACTIVE + +LABEL maintainer="bladejava@qq.com" + +# 设置时区为上海 +ENV TZ=Asia/Shanghai +# 设置时区信息 +RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone + +COPY ./target/blade-openapi.jar /opt/app.jar + +WORKDIR /opt + +EXPOSE 8108 + +ENTRYPOINT ["java", "--add-opens", "java.base/java.lang=ALL-UNNAMED", "--add-opens", "java.base/java.lang.reflect=ALL-UNNAMED", "-Djava.security.egd=file:/dev/./urandom", "-XX:+HeapDumpOnOutOfMemoryError", "-XX:HeapDumpPath=/opt/logs", "-XX:ErrorFile=/opt/logs/hs_err_pid%p.log", "-Xlog:gc*:file=/opt/logs/gc.log:time,uptime,level,tags:filecount=5,filesize=50M", "-XX:MaxDirectMemorySize=512m", "-Xms1024m", "-Xmx1024m", "-jar", "app.jar"] + +CMD ["--spring.profiles.active=${ACTIVE}","--logging.path=/opt/logs"] diff --git a/blade-service/blade-openapi/pom.xml b/blade-service/blade-openapi/pom.xml new file mode 100644 index 0000000..08e98ac --- /dev/null +++ b/blade-service/blade-openapi/pom.xml @@ -0,0 +1,94 @@ + + + 4.0.0 + + blade-service + org.springblade + ${revision} + + + blade-openapi + + + + 1.18.30 + 1.5.5.Final + + + + + org.springblade + blade-core-boot + + + org.springblade + blade-starter-swagger + + + org.springblade + blade-open-api + + + org.springblade + blade-file-api + + + org.springblade + blade-mk-api + + + + org.mapstruct + mapstruct + ${org.mapstruct.version} + + + + + + + + io.fabric8 + docker-maven-plugin + + ${docker.fabric.skip} + + + + org.apache.maven.plugins + maven-antrun-plugin + + + + org.apache.maven.plugins + maven-compiler-plugin + ${maven.plugin.version} + + ${java.version} + ${java.version} + UTF-8 + + -parameters + + + + org.projectlombok + lombok + ${lombok.version} + + + org.mapstruct + mapstruct-processor + ${org.mapstruct.version} + + + + + + + + + + diff --git a/blade-service/blade-openapi/src/main/java/org/springblade/openapi/OpenApiApplication.java b/blade-service/blade-openapi/src/main/java/org/springblade/openapi/OpenApiApplication.java new file mode 100644 index 0000000..a41c68e --- /dev/null +++ b/blade-service/blade-openapi/src/main/java/org/springblade/openapi/OpenApiApplication.java @@ -0,0 +1,48 @@ +/** + * BladeX Commercial License Agreement + * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved. + *

+ * Use of this software is governed by the Commercial License Agreement + * obtained after purchasing a license from BladeX. + *

+ * 1. This software is for development use only under a valid license + * from BladeX. + *

+ * 2. Redistribution of this software's source code to any third party + * without a commercial license is strictly prohibited. + *

+ * 3. Licensees may copyright their own code but cannot use segments + * from this software for such purposes. Copyright of this software + * remains with BladeX. + *

+ * Using this software signifies agreement to this License, and the software + * must not be used for illegal purposes. + *

+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is + * not liable for any claims arising from secondary or illegal development. + *

+ * Author: Chill Zhuang (bladejava@qq.com) + */ +package org.springblade.openapi; + +import org.springblade.common.utils.ObsUtil; +import org.springblade.core.cloud.client.BladeCloudApplication; +import org.springblade.core.launch.BladeApplication; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Import; + +/** + * Desk启动器 + * + * @author Chill + */ +@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); + } + +} diff --git a/blade-service/blade-openapi/src/main/java/org/springblade/openapi/resultcode/OpenApiResultCode.java b/blade-service/blade-openapi/src/main/java/org/springblade/openapi/resultcode/OpenApiResultCode.java new file mode 100644 index 0000000..3e16b4a --- /dev/null +++ b/blade-service/blade-openapi/src/main/java/org/springblade/openapi/resultcode/OpenApiResultCode.java @@ -0,0 +1,53 @@ +package org.springblade.openapi.resultcode; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.springblade.core.tool.api.IResultCode; + +/** + * + * 业务自定义返回码 + * 定义规则: + * 统一格式:[2位服务码][2位模块码][2位功能码][3位错误码] + *

+ * xx xx xx xx + * |__服务码 |__模块 |__功能 |___错误码 + * 业务自定义返回码 + * * 定义规则: + * * 统一格式:[2位服务码][2位模块码][2位功能码][3位错误码] + * *

+ * * xx xx xx xx + * * |__服务码 |__模块 |__功能 |___错误码 + * * + 服务码: + + 业务基础服务 10 + 订单服务 20 + 仓储服务 30 + 首页/通告服务 40 + 系统管理服务 50 + 开放接口服务 60 + * @author vic + * @date 2024/9/12 2:09 PM + * @description OrderResultCode + */ + +@Getter +@AllArgsConstructor +public enum OpenApiResultCode implements IResultCode { + + + + + + +; + /** + * code编码 + */ + final int code; + /** + * 中文信息描述 + */ + final String message; +} diff --git a/blade-service/blade-openapi/src/main/java/org/springblade/openapi/wps/Api4Wps.java b/blade-service/blade-openapi/src/main/java/org/springblade/openapi/wps/Api4Wps.java new file mode 100644 index 0000000..4463e87 --- /dev/null +++ b/blade-service/blade-openapi/src/main/java/org/springblade/openapi/wps/Api4Wps.java @@ -0,0 +1,134 @@ +package org.springblade.openapi.wps; + +import com.alibaba.fastjson2.JSON; +import io.swagger.v3.oas.annotations.Hidden; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springblade.common.utils.ObsUtil; +import org.springblade.core.tool.api.R; +import org.springblade.core.tool.utils.Func; +import org.springblade.file.feign.IFileClient; +import org.springblade.file.pojo.vo.AttachmentDetailVO; +import org.springblade.openapi.wps.convert.WpsFileConvert; +import org.springblade.openapi.wps.api.IApi4Wps; +import org.springblade.openapi.wps.constant.WpsConstant; +import org.springblade.openapi.wps.exception.Api4WpsException; +import org.springblade.openapi.wps.pojo.dto.Api4WpsFileInfoDTO; +import org.springblade.openapi.wps.pojo.enums.Api4WpsError; +import org.springblade.openapi.wps.util.TokenUtils; +import org.springblade.openapi.wps.pojo.vo.*; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; + +import java.util.List; +import java.util.Locale; + +/** + * @author bfhuange + * @date 2024/10/30 + */ +@Slf4j +@Hidden +@AllArgsConstructor +@RestController +public class Api4Wps implements IApi4Wps { + + /** + * 图片扩展名列表 + */ + private static final List IMAGE_EXT = List.of("bmp", "jpeg", "jpg", "png", "gif"); + + private final IFileClient fileClient; + private final WpsFileConvert convert; + + @Override + public Api4WpsFileInfoVO getFileInfo(String fileId, String token, Api4WpsFileInfoDTO param) { + log.info("wps回调查询文件信息 参数 fileId:{} token:{} param:{}", fileId, token, JSON.toJSONString(param)); + long id = Func.toLong(fileId); + if (id <= 0L) { + throw new Api4WpsException(Api4WpsError.NOT_EXISTS); + } + if (token == null) { + throw new Api4WpsException(Api4WpsError.PERMISSION_DENIED); + } + R result = fileClient.getAttachmentDetail(id); + if (result == null || !result.isSuccess() || result.getData() == null || result.getData().getId() == null) { + log.warn("wps回调查询文件信息 查询附件失败 :{}", JSON.toJSONString(result)); + throw new Api4WpsException(Api4WpsError.NOT_EXISTS); + } + // 附件 + AttachmentDetailVO attachment = result.getData(); + Api4WpsFileVO fileVO = convert.attachment2file(attachment); + // 用户信息 + Api4WpsUserVO user = TokenUtils.getWpsUser(token); + if (user == null) { + // 用户信息为空,token过期了 + throw new Api4WpsException(Api4WpsError.SESSION_EXPIRED); + } + + // 用户不为空,添加水印 + boolean isImage = isImage(attachment.getAttachmentName()); + log.info("附件名:{} 是否图片:{}", attachment.getAttachmentName(), isImage); + if (!isImage) { + // 不是图片才加水印 + Api4WpsWatermarkVO watermark = new Api4WpsWatermarkVO(); + watermark.setType(WpsConstant.YES); + watermark.setValue(user.getName()); + watermark.setHorizontal(100); + watermark.setVertical(200); + fileVO.setWatermark(watermark); + } + Api4WpsFileInfoVO fileInfoVO = new Api4WpsFileInfoVO(); + fileInfoVO.setUser(user); + fileInfoVO.setFile(fileVO); + // 缓存时间 + fileInfoVO.setAttrs(new Api4WpsAttrsVO(WpsConstant.FILE_INFO_DEFAULT_CACHE_TIME)); + log.info("wps回调查询文件信息 返回结果:{}", JSON.toJSONString(fileInfoVO)); + return fileInfoVO; + } + + @Override + public Api4WpsFileInfoVO getFileSave(String fileId, String saveType, String token, MultipartFile file, Api4WpsFileInfoDTO param) { + try { + log.info("wps回调-上传文件新版本 参数 fileId:{} token:{} param:{}", fileId, token, JSON.toJSONString(param)); + long id = Func.toLong(fileId); + if (id == 0L) { + return null; + } + R result = fileClient.getAttachmentDetail(id); + if (result == null || !result.isSuccess()) { + log.warn("wps回调-上传文件新版本 查询附件失败 :{}", JSON.toJSONString(result)); + return null; + } + // 附件 + AttachmentDetailVO attachment = result.getData(); + Api4WpsFileVO fileVO = convert.attachment2file(attachment); + fileVO.setVersion(2); + ObsUtil.putObject(attachment.getObjectKey(), file.getInputStream()); + + Api4WpsFileInfoVO fileInfoVO = new Api4WpsFileInfoVO(); + fileInfoVO.setFile(fileVO); + + return fileInfoVO; + }catch (Exception e) { + throw new RuntimeException(e); + } + } + + /** + * 是否图片类型 + * @param fileName + * @return + */ + public boolean isImage(String fileName) { + if (fileName == null) { + return false; + } + int index = fileName.lastIndexOf("."); + if (index < 0 || index +1 >= fileName.length() ) { + return false; + } + String extName = fileName.substring(index + 1).toLowerCase(Locale.getDefault()); + return IMAGE_EXT.contains(extName); + } +} diff --git a/blade-service/blade-openapi/src/main/java/org/springblade/openapi/wps/convert/WpsFileConvert.java b/blade-service/blade-openapi/src/main/java/org/springblade/openapi/wps/convert/WpsFileConvert.java new file mode 100644 index 0000000..78903fa --- /dev/null +++ b/blade-service/blade-openapi/src/main/java/org/springblade/openapi/wps/convert/WpsFileConvert.java @@ -0,0 +1,37 @@ +package org.springblade.openapi.wps.convert; + +import org.mapstruct.*; +import org.springblade.openapi.wps.pojo.vo.Api4WpsFileVO; +import org.springblade.file.pojo.vo.AttachmentDetailVO; + +import java.util.Date; + +/** + * @author bfhuange + * @date 2024/10/30 + */ +@Mapper(componentModel = MappingConstants.ComponentModel.SPRING, unmappedTargetPolicy = ReportingPolicy.IGNORE) +public interface WpsFileConvert { + + @Mapping(source = "attachmentName", target = "name") + @Mapping(source = "url", target = "downloadUrl") + @Mapping(source = "size", target = "size", qualifiedByName = "long2int") + @Mapping(source = "createUser", target = "creator") + @Mapping(source = "createTime", target = "createTime", qualifiedByName = "date2int") + @Mapping(source = "updateUser", target = "modifier") + @Mapping(source = "updateTime", target = "modifyTime", qualifiedByName = "date2int") + Api4WpsFileVO attachment2file(AttachmentDetailVO attachment); + + @Named("date2int") + default Integer date2int(Date date) { + if (date == null) { + return null; + } + return Math.toIntExact(date.getTime() / 1000); + } + + @Named("long2int") + default Integer long2int(long size) { + return Math.toIntExact(size); + } +} diff --git a/blade-service/blade-openapi/src/main/java/org/springblade/openapi/wps/exception/Api4WpsException.java b/blade-service/blade-openapi/src/main/java/org/springblade/openapi/wps/exception/Api4WpsException.java new file mode 100644 index 0000000..e0324bf --- /dev/null +++ b/blade-service/blade-openapi/src/main/java/org/springblade/openapi/wps/exception/Api4WpsException.java @@ -0,0 +1,34 @@ +package org.springblade.openapi.wps.exception; + +import lombok.Getter; +import org.springblade.openapi.wps.pojo.enums.Api4WpsError; + +/** + * wps回调接口异常 + * @author bfhuange + * @since 2024/11/26 + */ +@Getter +public class Api4WpsException extends RuntimeException { + + /** + * 错误枚举 + */ + private final Api4WpsError error; + /** + * 消息 + */ + private final String message; + + public Api4WpsException(Api4WpsError error) { + super(error.getMessage()); + this.error = error; + this.message = error.getMessage(); + } + + public Api4WpsException(Api4WpsError error, String message) { + super(message); + this.error = error; + this.message = message; + } +} diff --git a/blade-service/blade-openapi/src/main/java/org/springblade/openapi/wps/exception/Api4WpsExceptionHandler.java b/blade-service/blade-openapi/src/main/java/org/springblade/openapi/wps/exception/Api4WpsExceptionHandler.java new file mode 100644 index 0000000..335a474 --- /dev/null +++ b/blade-service/blade-openapi/src/main/java/org/springblade/openapi/wps/exception/Api4WpsExceptionHandler.java @@ -0,0 +1,27 @@ +package org.springblade.openapi.wps.exception; + +import lombok.extern.slf4j.Slf4j; +import org.springblade.openapi.wps.pojo.enums.Api4WpsError; +import org.springblade.openapi.wps.pojo.vo.Api4WpsErrorVO; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +/** + * wps 回调接口异常处理 + * @author bfhuange + * @since 2024/11/26 + */ +@Slf4j +@RestControllerAdvice +public class Api4WpsExceptionHandler { + + @ResponseStatus(HttpStatus.BAD_REQUEST) + @ExceptionHandler(Api4WpsException.class) + public Api4WpsErrorVO handleException(Api4WpsException exception) { + log.error("wps 回调接口异常:{}", exception.getMessage(), exception); + Api4WpsError error = exception.getError(); + return new Api4WpsErrorVO(error.getCode(), error.getMessage(), exception.getMessage()); + } +} diff --git a/blade-service/blade-openapi/src/main/java/org/springblade/openapi/wps/util/TokenUtils.java b/blade-service/blade-openapi/src/main/java/org/springblade/openapi/wps/util/TokenUtils.java new file mode 100644 index 0000000..313bd9a --- /dev/null +++ b/blade-service/blade-openapi/src/main/java/org/springblade/openapi/wps/util/TokenUtils.java @@ -0,0 +1,33 @@ +package org.springblade.openapi.wps.util; + +import io.jsonwebtoken.Claims; +import org.springblade.core.launch.constant.TokenConstant; +import org.springblade.core.secure.utils.AuthUtil; +import org.springblade.core.tool.utils.Func; +import org.springblade.core.tool.utils.StringUtil; +import org.springblade.openapi.wps.pojo.vo.Api4WpsUserVO; + +/** + * @author bfhuange + * @date 2024/10/30 + */ +public class TokenUtils { + + /** + * 获取wps需要的用户信息 + * @param token + * @return + */ + public static Api4WpsUserVO getWpsUser(String token) { + if (StringUtil.isBlank(token)) { + return null; + } + Claims claims = AuthUtil.parseJWT(token); + if (claims == null) { + return null; + } + String userId = Func.toStr(claims.get(TokenConstant.USER_ID)); + String nickName = Func.toStr(claims.get(TokenConstant.NICK_NAME)); + return new Api4WpsUserVO(userId, nickName); + } +} diff --git a/blade-service/blade-openapi/src/main/resources/bootstrap-dev.yml b/blade-service/blade-openapi/src/main/resources/bootstrap-dev.yml new file mode 100644 index 0000000..303fc85 --- /dev/null +++ b/blade-service/blade-openapi/src/main/resources/bootstrap-dev.yml @@ -0,0 +1,6 @@ +#spring: +# cloud: +# nacos: +# username: nacos +# password: ${NACOS_PASSWORD:gr30wIs5%Hi7keQj} +# server-addr: ${NACOS_ADDR:10.38.16.127:8848} diff --git a/blade-service/blade-openapi/src/main/resources/bootstrap-prod.yml b/blade-service/blade-openapi/src/main/resources/bootstrap-prod.yml new file mode 100644 index 0000000..673f396 --- /dev/null +++ b/blade-service/blade-openapi/src/main/resources/bootstrap-prod.yml @@ -0,0 +1,6 @@ +#spring: +# cloud: +# nacos: +# username: nacos +# password: rWrMrVTWyf%ekjuw +# server-addr: ${NACOS_ADDR:192.168.0.242:8848} diff --git a/blade-service/blade-openapi/src/main/resources/bootstrap-test.yml b/blade-service/blade-openapi/src/main/resources/bootstrap-test.yml new file mode 100644 index 0000000..d449d60 --- /dev/null +++ b/blade-service/blade-openapi/src/main/resources/bootstrap-test.yml @@ -0,0 +1,8 @@ +#server: +# port: 38108 +#spring: +# cloud: +# nacos: +# username: nacos +# password: gr30wIs5%Hi7keQj +# server-addr: ${NACOS_ADDR:10.38.16.127:8848} diff --git a/blade-service/blade-openapi/src/main/resources/bootstrap.yml b/blade-service/blade-openapi/src/main/resources/bootstrap.yml new file mode 100644 index 0000000..e97a20a --- /dev/null +++ b/blade-service/blade-openapi/src/main/resources/bootstrap.yml @@ -0,0 +1,54 @@ +server: + port: 8108 + +#数据源配置 +spring: + datasource: + url: ${blade.datasource.dev.url} + username: ${blade.datasource.dev.username} + password: ${blade.datasource.dev.password} + cloud: + nacos: + config: + shared-configs: + - data-id: third-party-api.yaml + refresh: true + +#第三方接口本地默认配置,Nacos third-party-api.yaml 可覆盖 +thirdParty: + ocr: + baseUrl: ${OCR_BASE_URL:http://127.0.0.1:8080} + baseOtherUrl: ${OCR_BASE_OTHER_URL:http://127.0.0.1:8080} + wps: + baseUrl: ${WPS_BASE_URL:http://127.0.0.1:8080} + oa: + baseUrl: ${OA_BASE_URL:http://127.0.0.1:8080} + track: + baseUrl: ${TRACK_BASE_URL:http://127.0.0.1:8080} + mk: + baseUrl: ${MK_BASE_URL:http://127.0.0.1:8080} + +#spring: +# application: +# name: blade-openapi +# cloud: +# nacos: +# discovery: +# namespace: ${spring.profiles.active} +# # 不注册到nacos +# #registerEnabled: false +# config: +# # 文件后缀名 +# file-extension: yaml +# namespace: ${spring.profiles.active} +# shared-configs: +# - data-id: blade.yaml +# refresh: true +# - data-id: blade-${spring.profiles.active}.yaml +# refresh: true +# - data-id: third-party-api.yaml +# refresh: true +# extension-configs: +# - data-id: ${spring.application.name}-dynamictp.yaml +# group: DEFAULT_GROUP +# refresh: true # 必须配置,负责自动刷新不生效 diff --git a/blade-service/blade-system/pom.xml b/blade-service/blade-system/pom.xml index 12b0c16..4ab8af9 100644 --- a/blade-service/blade-system/pom.xml +++ b/blade-service/blade-system/pom.xml @@ -13,6 +13,13 @@ ${project.artifactId} jar + + + 1.18.30 + 1.5.5.Final + 7.5.5 + + org.springblade @@ -38,6 +45,35 @@ org.springblade blade-user-api + + + org.springblade + blade-oa-api + + + org.springblade + blade-mk-api + + + + + org.mapstruct + mapstruct + ${org.mapstruct.version} + + + + org.springblade + blade-starter-powerjob + + + + + org.springblade + blade-core-oauth2 + + + @@ -53,6 +89,34 @@ org.apache.maven.plugins maven-antrun-plugin + + + org.apache.maven.plugins + maven-compiler-plugin + ${maven.plugin.version} + + ${java.version} + ${java.version} + UTF-8 + + -parameters + + + + org.projectlombok + lombok + ${lombok.version} + + + org.mapstruct + mapstruct-processor + ${org.mapstruct.version} + + + + + + diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/controller/AppVersionController.java b/blade-service/blade-system/src/main/java/org/springblade/system/controller/AppVersionController.java new file mode 100644 index 0000000..debea12 --- /dev/null +++ b/blade-service/blade-system/src/main/java/org/springblade/system/controller/AppVersionController.java @@ -0,0 +1,115 @@ +/** + * BladeX Commercial License Agreement + * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved. + *

+ * Use of this software is governed by the Commercial License Agreement + * obtained after purchasing a license from BladeX. + *

+ * 1. This software is for development use only under a valid license + * from BladeX. + *

+ * 2. Redistribution of this software's source code to any third party + * without a commercial license is strictly prohibited. + *

+ * 3. Licensees may copyright their own code but cannot use segments + * from this software for such purposes. Copyright of this software + * remains with BladeX. + *

+ * Using this software signifies agreement to this License, and the software + * must not be used for illegal purposes. + *

+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is + * not liable for any claims arising from secondary or illegal development. + *

+ * 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.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springblade.core.boot.ctrl.BladeController; +import org.springblade.core.mp.support.Query; +import org.springblade.core.tool.api.R; +import org.springblade.core.tool.utils.Func; +import org.springblade.system.pojo.entity.AppVersion; +import org.springblade.system.service.AppVersionService; +import org.springframework.web.bind.annotation.*; + + +@RestController +@AllArgsConstructor +@RequestMapping("appVersion") +@Tag(name = "APP版本管理", description = "APP版本管理") +public class AppVersionController extends BladeController { + private static final Logger logger = LoggerFactory.getLogger(AppVersionController.class); + + private final AppVersionService appVersionService; + + /** + * 分页 + */ + @GetMapping("/page") + @ApiOperationSupport(order = 1) + @Operation(summary = "分页") + public R> page(AppVersion modelVo, Query query) { + try { + return R.data(appVersionService.page(modelVo,query)); + } catch (Exception e) { + logger.error("appVersion/page:" + e.getMessage()); + return R.fail(e.getMessage()); + } + } + + /** + * 详情 + */ + @GetMapping("/detail/{id}") + @ApiOperationSupport(order = 2) + @Operation(summary = "详情") + public R detail(@PathVariable Long id) { + try { + return R.data(appVersionService.detail(id)); + } catch (Exception e) { + logger.error("appVersion/detail:" + e.getMessage()); + return R.fail(e.getMessage()); + } + } + + /** + * 发布版本/编辑 + */ + @PostMapping("/publishOrEdit") + @ApiOperationSupport(order = 3) + @Operation(summary = "发布版本/编辑") + public R publishOrEdit(@Valid @RequestBody AppVersion modelVo) { + try { + return R.data(appVersionService.publishOrEdit(modelVo)); + } catch (Exception e) { + logger.error("appVersion/publishOrEdit:" + e.getMessage()); + return R.fail(e.getMessage()); + } + } + + /** + * 删除 + */ + @PostMapping("/remove") + @ApiOperationSupport(order = 4) + @Operation(summary = "删除") + public R remove(@Parameter(description = "主键集合", required = true) @RequestParam String ids) { + try { + return R.status(appVersionService.removeByIds(Func.toLongList(ids))); + } catch (Exception e) { + logger.error("appVersion/remove:" + e.getMessage()); + return R.fail(e.getMessage()); + } + } + +} diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/controller/DeptController.java b/blade-service/blade-system/src/main/java/org/springblade/system/controller/DeptController.java index 170f915..06d1eb9 100644 --- a/blade-service/blade-system/src/main/java/org/springblade/system/controller/DeptController.java +++ b/blade-service/blade-system/src/main/java/org/springblade/system/controller/DeptController.java @@ -25,7 +25,6 @@ */ package org.springblade.system.controller; -import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; @@ -183,10 +182,11 @@ public class DeptController extends BladeController { public R> select(Long userId, String deptId) { if (Func.isNotEmpty(userId)) { User user = UserCache.getUser(userId); - deptId = user.getDeptId(); + if (Func.isNotEmpty(user)) { + deptId = user.getDeptId(); + } } - List list = deptService.list(Wrappers.lambdaQuery().in(Dept::getId, Func.toLongList(deptId))); - return R.data(list); + return R.data(deptService.selectDept(deptId)); } /** diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/controller/FeeItemController.java b/blade-service/blade-system/src/main/java/org/springblade/system/controller/FeeItemController.java index 6acbba2..76c802b 100644 --- a/blade-service/blade-system/src/main/java/org/springblade/system/controller/FeeItemController.java +++ b/blade-service/blade-system/src/main/java/org/springblade/system/controller/FeeItemController.java @@ -25,20 +25,28 @@ */ package org.springblade.system.controller; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; 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.common.excel.ImportFailureExcelUtil; 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.system.excel.FeeItemExcel; +import org.springblade.system.excel.FeeItemImportFailureExcel; import org.springblade.system.pojo.entity.FeeItem; import org.springblade.system.pojo.vo.FeeItemVO; import org.springblade.system.service.IFeeItemService; @@ -49,6 +57,10 @@ 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; /** * 费用项 控制器 @@ -118,4 +130,69 @@ public class FeeItemController extends BladeController { return R.status(feeItemService.changeStatus(id, status)); } + /** + * 导入费用项 + */ + @PostMapping("/import-fee-item") + @ApiOperationSupport(order = 6) + @Operation(summary = "导入费用项", description = "传入excel") + public R importFeeItem(MultipartFile file, HttpServletResponse response) { + List data = ExcelUtil.read(file, FeeItemExcel.class); + List failureList = feeItemService.importFeeItem(data); + if (Func.isNotEmpty(failureList)) { + ImportFailureExcelUtil.export(response, "费用项导入失败明细" + DateUtil.time(), "导入失败明细", failureList, FeeItemExcel.class); + return null; + } + return R.success("导入数据成功"); + } + + /** + * 导出费用项 + */ + @GetMapping("/export-fee-item") + @ApiOperationSupport(order = 7) + @Operation(summary = "导出费用项") + public void exportFeeItem(FeeItemVO feeItem, + @RequestParam(required = false) String ids, + HttpServletResponse response) { + List list = feeItemService.exportFeeItem(buildExportQuery(feeItem, ids)); + ExcelUtil.export(response, "费用项" + DateUtil.time(), "费用项表", list, FeeItemExcel.class); + } + + /** + * 导出模板 + */ + @GetMapping("/export-template") + @ApiOperationSupport(order = 8) + @Operation(summary = "导出模板") + public void exportTemplate(HttpServletResponse response) { + List list = new ArrayList<>(); + ExcelUtil.export(response, "费用项模板", "费用项导入模板", list, FeeItemExcel.class); + } + + private LambdaQueryWrapper buildExportQuery(FeeItemVO feeItem, String ids) { + LambdaQueryWrapper queryWrapper = Wrappers.lambdaQuery() + .eq(FeeItem::getIsDeleted, 0) + .orderByDesc(FeeItem::getCreateTime); + if (Func.isNotEmpty(ids)) { + queryWrapper.in(FeeItem::getId, Func.toLongList(ids)); + } + if (Func.isNotEmpty(feeItem.getFeeCategory())) { + queryWrapper.eq(FeeItem::getFeeCategory, feeItem.getFeeCategory()); + } + if (Func.isNotEmpty(feeItem.getName())) { + queryWrapper.like(FeeItem::getName, feeItem.getName()); + } + if (Func.isNotEmpty(feeItem.getEnglishName())) { + queryWrapper.like(FeeItem::getEnglishName, feeItem.getEnglishName()); + } + if (Func.isNotEmpty(feeItem.getStatus())) { + queryWrapper.eq(FeeItem::getStatus, feeItem.getStatus()); + } + if (Func.isNotEmpty(feeItem.getCreateDept())) { + queryWrapper.eq(FeeItem::getCreateDept, feeItem.getCreateDept()); + } + return queryWrapper; + } + } diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/controller/MKPushController.java b/blade-service/blade-system/src/main/java/org/springblade/system/controller/MKPushController.java new file mode 100644 index 0000000..42fe7ae --- /dev/null +++ b/blade-service/blade-system/src/main/java/org/springblade/system/controller/MKPushController.java @@ -0,0 +1,50 @@ +package org.springblade.system.controller; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.springblade.core.secure.annotation.PreAuth; +import org.springblade.core.tool.api.R; +import org.springblade.core.tool.constant.RoleConstant; +import org.springblade.system.service.IMKPushService; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * mk数据推送 + * @author bfhuange + * @date 2024/9/24 + */ +@Tag(name = "mk数据推送", description = "mk数据推送") +@RequestMapping("/mk") +@RestController +@RequiredArgsConstructor +public class MKPushController { + + private final IMKPushService mkPushService; + + /** + * 推送公司和部门 + * @return + */ + @Operation(summary = "推送公司和部门") + @PostMapping("/pushCompanyAndDept") + @PreAuth(RoleConstant.HAS_ROLE_ADMIN) + public R pushCompanyAndDept() { + mkPushService.pushOrgAndRecord(null); + return R.data(true); + } + + /** + * 推送人员 + * @return + */ + @Operation(summary = "推送人员") + @PostMapping("/pushPerson") + @PreAuth(RoleConstant.HAS_ROLE_ADMIN) + public R pushPerson() { + mkPushService.pushPersonAndRecord(null); + return R.data(true); + } +} diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/controller/OAController.java b/blade-service/blade-system/src/main/java/org/springblade/system/controller/OAController.java new file mode 100644 index 0000000..c862504 --- /dev/null +++ b/blade-service/blade-system/src/main/java/org/springblade/system/controller/OAController.java @@ -0,0 +1,78 @@ +package org.springblade.system.controller; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.springblade.core.log.annotation.ApiLog; +import org.springblade.core.secure.annotation.PreAuth; +import org.springblade.core.tool.api.R; +import org.springblade.system.service.IOASyncService; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * oa数据同步 + * @author bfhuange + * @date 2024/8/23 + */ +@Tag(name = "oa数据同步", description = "oa数据同步") +@RequestMapping("/oa") +@RestController +@RequiredArgsConstructor +public class OAController { + + private final IOASyncService oaService; + + /** + * 增量同步公司和部门并推送MK + * @return + */ + @ApiLog("系统管理-机构管理-增量同步") + @Operation(summary = "增量同步公司和部门并推送MK") + @PostMapping("/syncCompanyAndDeptAndPushMK") + @PreAuth("hasPermission('oasync:org:inc')") + public R syncCompanyAndDeptAndPushMK() { + oaService.syncCompanyAndDeptAndPushMK(false); + return R.data(true); + } + + /** + * 全量同步公司和部门并推送MK + * @return + */ + @ApiLog("系统管理-机构管理-全量同步") + @Operation(summary = "全量同步公司和部门并推送MK") + @PostMapping("/syncAllCompanyAndDeptAndPushMK") + @PreAuth("hasPermission('oasync:org:all')") + public R syncAllCompanyAndDeptAndPushMK() { + oaService.syncCompanyAndDeptAndPushMK(true); + return R.data(true); + } + + /** + * 增量同步人员并推送MK + * @return + */ + @ApiLog("系统管理-用户管理-增量同步") + @Operation(summary = "增量同步人员并推送MK") + @PostMapping("/syncPersonAndPushMK") + @PreAuth("hasPermission('oasync:person:inc')") + public R syncPersonAndPushMK() { + oaService.syncPersonAndPushMK(false); + return R.data(true); + } + + /** + * 全量同步人员并推送MK + * @return + */ + @ApiLog("系统管理-用户管理-全量同步") + @Operation(summary = "全量同步人员并推送MK") + @PostMapping("/syncAllPersonAndPushMK") + @PreAuth("hasPermission('oasync:person:all')") + public R syncAllPersonAndPushMK() { + oaService.syncPersonAndPushMK(true); + return R.data(true); + } +} diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/convert/DeptConvert.java b/blade-service/blade-system/src/main/java/org/springblade/system/convert/DeptConvert.java new file mode 100644 index 0000000..3199b7e --- /dev/null +++ b/blade-service/blade-system/src/main/java/org/springblade/system/convert/DeptConvert.java @@ -0,0 +1,142 @@ +package org.springblade.system.convert; + +import org.mapstruct.*; +import org.springblade.common.constant.CommonConstant; +import org.springblade.common.constant.DataStatusEnum; +import org.springblade.system.pojo.entity.Dept; +import org.springblade.system.pojo.enums.DeptCategory; +import org.springblade.system.pojo.vo.DeptListVO; +import org.springblade.thirdparty.mk.constant.MKConstant; +import org.springblade.thirdparty.mk.pojo.dto.MKPushOrgDeptDTO; +import org.springblade.thirdparty.oa.constant.OAConstant; +import org.springblade.thirdparty.oa.constant.OAConvertConstant; +import org.springblade.thirdparty.oa.pojo.response.OACompanyResponse; +import org.springblade.thirdparty.oa.pojo.response.OADepartmentResponse; +import org.springblade.thirdparty.oa.util.OAUtils; + +import java.util.Date; +import java.util.List; +import java.util.Map; + +/** + * oa公司、部门到本系统部门转换工具 + * @author bfhuange + * @date 2024/8/26 + */ +@Mapper(componentModel = MappingConstants.ComponentModel.SPRING, unmappedTargetPolicy = ReportingPolicy.IGNORE) +public interface DeptConvert { + + DeptListVO entity2vo(Dept entity); + + List entities2vos(List entities); + + @Mapping(source = "deptCode", target = "fdId") + @Mapping(source = "deptName", target = "fdName") + @Mapping(source = "deptCode", target = "fdNo") + @Mapping(source = "sort", target = "fdOrder") + @Mapping(source = "status", target = "fdIsAvailable", qualifiedByName = "fdIsAvailable") + @Mapping(source = "deptCategory", target = "fdOrgType", qualifiedByName = "fdOrgType") + @Mapping(source = "deptCategory", target = "fdSourceSystem", qualifiedByName = "fdSourceSystem") + @Mapping(source = "parentCode", target = "fdParent") + MKPushOrgDeptDTO entity2dto(Dept entity); + + List entities2dtos(List entities); + + @Mapping(source = "subcompanyname", target = "deptName") + @Mapping(source = "subcompanydesc", target = "fullName") + // 父公司id转为父部门编码,要拼接前缀 + @Mapping(source = "supsubcomid", target = "parentCode") + @Mapping(source = "created", target = "createTime", dateFormat = "yyyy-MM-dd HH:mm:ss") + @Mapping(source = "modified", target = "updateTime", dateFormat = "yyyy-MM-dd HH:mm:ss") + Dept baseConvert(OACompanyResponse company); + + @Mapping(source = "departmentname", target = "deptName") + @Mapping(source = "departmentmark", target = "fullName") + // 父部门id转为父部门编码,要拼接前缀 + @Mapping(source = "supdepid", target = "parentCode") + // 所属子公司id转为所属公司编码,要拼接前缀 + @Mapping(source = "subcompanyid1", target = "belongCompanyCode") + @Mapping(source = "created", target = "createTime", dateFormat = "yyyy-MM-dd HH:mm:ss") + @Mapping(source = "modified", target = "updateTime", dateFormat = "yyyy-MM-dd HH:mm:ss") + Dept baseConvert(OADepartmentResponse department); + + /** + * oa公司转本系统部门 + * @param company + * @return + */ + default Dept company2dept(OACompanyResponse company) { + Dept dept = this.baseConvert(company); + // 部门编码添加前缀 + dept.setDeptCode(OAConvertConstant.COMPANY_OA_PREFIX + company.getId()); + String parentCode = dept.getParentCode(); + if (!OAConstant.ROOT_COMPANY_ID.equals(parentCode)) { + // 父部门编码不是0,拼接前缀 + dept.setParentCode(OAConvertConstant.COMPANY_OA_PREFIX + parentCode); + // 父部门id后续处理 + } else { + // 父部门编码是0,父id也设置为0 + dept.setParentId(OAConvertConstant.ROOT_PARENT_ID); + } + // 公司所属公司编码就是他自己的部门编码 + dept.setBelongCompanyCode(dept.getDeptCode()); + // 显示顺序处理 + dept.setSort(OAUtils.parseInt(company.getShoworder())); + // 部门类型,公司 + dept.setDeptCategory(DeptCategory.COMPANY.getCode()); + dept.setSyncTime(new Date()); + // 是oa的 + dept.setIsOa(CommonConstant.YES); + return dept; + } + + /** + * oa部门转本系统部门 + * @param oaDept + * @param companyDeptMap 所有公司的部门编码与id map + * @return + */ + default Dept dept2dept(OADepartmentResponse oaDept, Map companyDeptMap) { + Dept dept = this.baseConvert(oaDept); + // 部门编码添加前缀 + dept.setDeptCode(OAConvertConstant.DEPARTMENT_OA_PREFIX + oaDept.getId()); + // 部门所属公司编码就是他所属公司编码加前缀 + dept.setBelongCompanyCode(OAConvertConstant.COMPANY_OA_PREFIX + dept.getBelongCompanyCode()); + String parentCode = dept.getParentCode(); + if (!OAConstant.ROOT_COMPANY_ID.equals(parentCode)) { + // 父部门编码不是0,拼接前缀 + dept.setParentCode(OAConvertConstant.DEPARTMENT_OA_PREFIX + parentCode); + // 父部门id后续处理 + } else { + // 父部门编码是0,设置为公司编码 + dept.setParentCode(dept.getBelongCompanyCode()); + // 根据父部门编码获取对应的id + dept.setParentId(companyDeptMap.get(dept.getParentCode())); + } + + // 显示顺序处理 + dept.setSort(OAUtils.parseInt(oaDept.getShoworder())); + // 部门类型,部门 + dept.setDeptCategory(DeptCategory.DEPT.getCode()); + dept.setSyncTime(new Date()); + // 是oa的 + dept.setIsOa(CommonConstant.YES); + return dept; + } + + @Named("fdIsAvailable") + default Boolean fdIsAvailable(Integer status) { + return DataStatusEnum.ENABLE.getCode().equals(status); + } + + @Named("fdOrgType") + default String fdOrgType(Integer deptCategory) { + return DeptCategory.COMPANY.getCode().equals(deptCategory) ? MKConstant.ORG_TYPE_ORG : MKConstant.ORG_TYPE_DEPT; + } + + @Named("fdSourceSystem") + default String fdSourceSystem(Integer deptCategory) { + return CommonConstant.SYSTEM_CODE; + } + +} diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/convert/UserConvert.java b/blade-service/blade-system/src/main/java/org/springblade/system/convert/UserConvert.java new file mode 100644 index 0000000..924daee --- /dev/null +++ b/blade-service/blade-system/src/main/java/org/springblade/system/convert/UserConvert.java @@ -0,0 +1,200 @@ +package org.springblade.system.convert; + +import org.apache.commons.lang3.StringUtils; +import org.mapstruct.*; +import org.springblade.common.constant.CommonConstant; +import org.springblade.common.constant.DataStatusEnum; +import org.springblade.core.tool.utils.CollectionUtil; +import org.springblade.core.tool.utils.Func; +import org.springblade.system.pojo.entity.User; +import org.springblade.system.pojo.entity.UserDept; +import org.springblade.system.pojo.enums.UserSex; +import org.springblade.system.pojo.enums.UserType; +import org.springblade.system.pojo.vo.UserListVO; +import org.springblade.thirdparty.mk.constant.MKConstant; +import org.springblade.thirdparty.mk.pojo.dto.MKAuxiliaryDeptDTO; +import org.springblade.thirdparty.mk.pojo.dto.MKPushPersonDTO; +import org.springblade.thirdparty.oa.constant.OAConvertConstant; +import org.springblade.thirdparty.oa.pojo.response.OAPersonResponse; +import org.springblade.thirdparty.oa.util.OAUtils; + +import java.util.*; + +/** + * @author bfhuange + * @date 2024/8/26 + */ +@Mapper(componentModel = MappingConstants.ComponentModel.SPRING, unmappedTargetPolicy = ReportingPolicy.IGNORE) +public interface UserConvert { + + List entities2vos(List list); + + default List entities2dtos(List records, String defaultPassword) { + if (CollectionUtil.isEmpty(records)) { + return Collections.emptyList(); + } + return records.stream() + .map(user -> { + MKPushPersonDTO personDTO = entity2dto(user); + if (personDTO == null) { + return null; + } + if (CommonConstant.NO.equals(user.getIsPushMk())) { + // 未推送mk的才设置默认密码 + personDTO.setFdPassword(defaultPassword); + } + return personDTO; + }).toList(); + } + + @Mapping(source = "account", target = "fdId") + @Mapping(source = "account", target = "fdLoginName") + @Mapping(source = "name", target = "fdName") + @Mapping(source = "phone", target = "fdMobileNo") + @Mapping(source = "code", target = "fdNo") + @Mapping(source = "sex", target = "fdSex", qualifiedByName = "fdSex") + @Mapping(source = "status", target = "fdIsAvailable", qualifiedByName = "fdIsAvailable") + @Mapping(source = "account", target = "fdSourceSystem", qualifiedByName = "fdSourceSystem") + @Mapping(source = "deptCodes", target = "fdParent", qualifiedByName = "fdParent") + @Mapping(source = "deptCodes", target = "fdAuxiliaryDept", qualifiedByName = "fdAuxiliaryDept") + MKPushPersonDTO entity2dto(User entity); + + @Mapping(target = "id", ignore = true) + @Mapping(target = "status", ignore = true) + @Mapping(target = "password", ignore = true) + @Mapping(target = "birthday", ignore = true) + @Mapping(target = "sex", 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); + + @Mapping(source = "subcompanyname", target = "companyName") + @Mapping(source = "departmentname", target = "deptName") + @Mapping(source = "id", target = "oaId") + @Mapping(source = "loginid", target = "oaLoginId") + UserDept userDeptConvert(OAPersonResponse person); + + /** + * oa人员转本系统用户 + * @param person + * @param defaultPassword 默认密码 + * @return + */ + default User person2user(OAPersonResponse person, String defaultPassword) { + User user = baseConvert(person); + + // 密码 + user.setPassword(defaultPassword); + // 性别 + user.setSex(UserSex.getCodeByName(person.getSex())); + // 状态转换 + Integer status = getStatus(person); + user.setStatus(status); + // web平台 + user.setUserType(UserType.WEB.getCategory()); + user.setSyncTime(new Date()); + // 从oa同步的 + user.setIsOa(CommonConstant.YES); + return user; + } + + /** + * oa人员转本系统用户部门 + * @param person + * @param userMap 所有用户手机号与id map + * @return + */ + default UserDept person2userDept(OAPersonResponse person, Map userMap) { + UserDept userDept = userDeptConvert(person); + // 公司编码添加前缀 + userDept.setCompanyCode(OAConvertConstant.COMPANY_OA_PREFIX + person.getSubcompanyid1()); + // 部门编码添加前缀 + userDept.setDeptCode(OAConvertConstant.DEPARTMENT_OA_PREFIX + person.getDepartmentid()); + // 状态转换 + Integer status = getStatus(person); + userDept.setStatus(status); + // 排序 + userDept.setSort(OAUtils.parseInt(person.getDsporder())); + // 用户id + userDept.setUserId(userMap.get(person.getMobile())); + userDept.setSyncTime(new Date()); + return userDept; + } + + /** + * 转换状态 + * @param person + * @return + */ + private Integer getStatus(OAPersonResponse person) { + // 过滤状态 0-试用 1-正式 2-临时 3-试用延期 4-解聘 5-离职 6-退休 7-无效 + return switch (person.getStatus()) { + case "0", "1", "2", "3" -> DataStatusEnum.ENABLE.getCode(); + default -> DataStatusEnum.DISABLE.getCode(); + }; + } + + @Named("fdIsAvailable") + default Boolean fdIsAvailable(Integer status) { + return DataStatusEnum.ENABLE.getCode().equals(status); + } + + @Named("fdSourceSystem") + default String fdSourceSystem(String account) { + return CommonConstant.SYSTEM_CODE; + } + + @Named("fdSex") + default String fdSex(Integer sex) { + if (sex == null) { + return null; + } + if (UserSex.MAN.getCode().equals(sex)) { + return MKConstant.SEX_MAN; + } + if (UserSex.WOMAN.getCode().equals(sex)) { + return MKConstant.SEX_WOMAN; + } + return MKConstant.SEX_UNKONW; + } + + @Named("fdParent") + default String fdParent(String deptCodes) { + if (StringUtils.isBlank(deptCodes)) { + return null; + } + // 第一个部门编码作为主部门id + return Func.toStrList(deptCodes).stream() + // 非外部组织编码 + .filter(deptCode -> !deptCode.startsWith(OAConvertConstant.COMPANY_EX_PREFIX) && !deptCode.startsWith(OAConvertConstant.DEPARTMENT_EX_PREFIX)) + .findFirst() + .orElse(null); + } + + @Named("fdAuxiliaryDept") + default List fdAuxiliaryDept(String deptCodes) { + if (StringUtils.isBlank(deptCodes)) { + return null; + } + List deptCodeList = Func.toStrList(deptCodes).stream() + // 非外部组织编码 + .filter(deptCode -> !deptCode.startsWith(OAConvertConstant.COMPANY_EX_PREFIX) && !deptCode.startsWith(OAConvertConstant.DEPARTMENT_EX_PREFIX)) + .toList(); + if (deptCodeList.size() <= 1) { + return null; + } + deptCodeList = new ArrayList<>(deptCodeList); + // 移除第一个部门编码,其他的作为辅部门 + deptCodeList.remove(0); + return deptCodeList.stream() + .map(deptCode -> { + MKAuxiliaryDeptDTO deptDTO = new MKAuxiliaryDeptDTO(); + deptDTO.setFdId(deptCode); + return deptDTO; + }).toList(); + } +} diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/excel/FeeItemExcel.java b/blade-service/blade-system/src/main/java/org/springblade/system/excel/FeeItemExcel.java new file mode 100644 index 0000000..a1a8cfc --- /dev/null +++ b/blade-service/blade-system/src/main/java/org/springblade/system/excel/FeeItemExcel.java @@ -0,0 +1,66 @@ +/** + * BladeX Commercial License Agreement + * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved. + *

+ * Use of this software is governed by the Commercial License Agreement + * obtained after purchasing a license from BladeX. + *

+ * 1. This software is for development use only under a valid license + * from BladeX. + *

+ * 2. Redistribution of this software's source code to any third party + * without a commercial license is strictly prohibited. + *

+ * 3. Licensees may copyright their own code but cannot use segments + * from this software for such purposes. Copyright of this software + * remains with BladeX. + *

+ * Using this software signifies agreement to this License, and the software + * must not be used for illegal purposes. + *

+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is + * not liable for any claims arising from secondary or illegal development. + *

+ * 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.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; + +/** + * 费用项 Excel + * + * @author Chill + */ +@Data +@ColumnWidth(22) +@HeadRowHeight(20) +@ContentRowHeight(18) +public class FeeItemExcel implements Serializable { + @Serial + private static final long serialVersionUID = 1L; + + @ExcelIgnore + private Long id; + + @ExcelProperty("*费用类型") + private String feeCategory; + + @ExcelProperty("*费用项代码") + private String englishName; + + @ExcelProperty("*费用项") + private String name; + + @ExcelIgnore + private String errorMessage; + +} diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/excel/FeeItemImportFailureExcel.java b/blade-service/blade-system/src/main/java/org/springblade/system/excel/FeeItemImportFailureExcel.java new file mode 100644 index 0000000..d485b64 --- /dev/null +++ b/blade-service/blade-system/src/main/java/org/springblade/system/excel/FeeItemImportFailureExcel.java @@ -0,0 +1,62 @@ +/** + * BladeX Commercial License Agreement + * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved. + *

+ * Use of this software is governed by the Commercial License Agreement + * obtained after purchasing a license from BladeX. + *

+ * 1. This software is for development use only under a valid license + * from BladeX. + *

+ * 2. Redistribution of this software's source code to any third party + * without a commercial license is strictly prohibited. + *

+ * 3. Licensees may copyright their own code but cannot use segments + * from this software for such purposes. Copyright of this software + * remains with BladeX. + *

+ * Using this software signifies agreement to this License, and the software + * must not be used for illegal purposes. + *

+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is + * not liable for any claims arising from secondary or illegal development. + *

+ * Author: Chill Zhuang (bladejava@qq.com) + */ +package org.springblade.system.excel; + +import cn.idev.excel.annotation.ExcelProperty; +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; + +/** + * 费用项导入失败 Excel + * + * @author Chill + */ +@Data +@ColumnWidth(22) +@HeadRowHeight(20) +@ContentRowHeight(18) +public class FeeItemImportFailureExcel implements Serializable { + @Serial + private static final long serialVersionUID = 1L; + + @ExcelProperty("*费用类型") + private String feeCategory; + + @ExcelProperty("*费用项代码") + private String englishName; + + @ExcelProperty("*费用项") + private String name; + + @ExcelProperty("导入失败原因") + private String failureReason; + +} diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/log/ComposeLogUtil.java b/blade-service/blade-system/src/main/java/org/springblade/system/log/ComposeLogUtil.java new file mode 100644 index 0000000..92f9eb2 --- /dev/null +++ b/blade-service/blade-system/src/main/java/org/springblade/system/log/ComposeLogUtil.java @@ -0,0 +1,89 @@ +package org.springblade.system.log; + +import lombok.extern.slf4j.Slf4j; +import org.slf4j.Logger; +import tech.powerjob.worker.log.OmsLogger; + +import java.util.LinkedList; + +/** + * 日志工具类 + * @author bfhuange + * @date 2024/9/25 + */ +@Slf4j +public class ComposeLogUtil { + /** + * oms logger + */ + private static final ThreadLocal OMS_LOGGER_CONTEXT = new ThreadLocal<>(); + /** + * 组合 logger + */ + private static final ThreadLocal> CONTEXT = new ThreadLocal<>(); + /** + * 默认日志记录器 + */ + private static final ComposeLogger DEFAULT = new ComposeLogger(log, null); + private ComposeLogUtil() {} + + /** + * 设置oms logger + * @param omsLogger + */ + public static void setOmsLogger(OmsLogger omsLogger) { + OMS_LOGGER_CONTEXT.set(omsLogger); + } + + /** + * 获取oms logger + * @return + */ + public static OmsLogger getOmsLogger() { + return OMS_LOGGER_CONTEXT.get(); + } + + /** + * 移除oms logger + */ + public static void removeOmsLogger() { + OMS_LOGGER_CONTEXT.remove(); + } + + /** + * 添加日志记录器 + * @param log + */ + public static void addLog(Logger log) { + LinkedList list = CONTEXT.get(); + if (list == null) { + list = new LinkedList<>(); + CONTEXT.set(list); + } + ComposeLogger omsLog = new ComposeLogger(log, ComposeLogUtil.getOmsLogger()); + list.addLast(omsLog); + } + + /** + * 获取最后一个日志记录器 + * @return + */ + public static ComposeLogger getLastLog() { + LinkedList list = CONTEXT.get(); + if (list == null || list.isEmpty()) { + return DEFAULT; + } + return list.getLast(); + } + + /** + * 清除最后一个日志记录器 + */ + public static void removeLastLog() { + LinkedList list = CONTEXT.get(); + if (list == null || list.isEmpty()) { + return; + } + list.removeLast(); + } +} diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/log/ComposeLogger.java b/blade-service/blade-system/src/main/java/org/springblade/system/log/ComposeLogger.java new file mode 100644 index 0000000..b822e81 --- /dev/null +++ b/blade-service/blade-system/src/main/java/org/springblade/system/log/ComposeLogger.java @@ -0,0 +1,48 @@ +package org.springblade.system.log; + +import lombok.RequiredArgsConstructor; +import org.slf4j.Logger; +import tech.powerjob.worker.log.OmsLogger; + +/** + * 组合日志,兼容 powerjob 日志 + * @author bfhuange + * @date 2024/8/30 + */ +@RequiredArgsConstructor +public class ComposeLogger implements OmsLogger { + private final Logger log; + private final OmsLogger omsLog; + + @Override + public void debug(String s, Object... objects) { + log.debug(s, objects); + if (omsLog != null) { + omsLog.debug(s, objects); + } + } + + @Override + public void info(String s, Object... objects) { + log.info(s, objects); + if (omsLog != null) { + omsLog.info(s, objects); + } + } + + @Override + public void warn(String s, Object... objects) { + log.warn(s, objects); + if (omsLog != null) { + omsLog.warn(s, objects); + } + } + + @Override + public void error(String s, Object... objects) { + log.error(s, objects); + if (omsLog != null) { + omsLog.error(s, objects); + } + } +} diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/mapper/AppVersionMapper.java b/blade-service/blade-system/src/main/java/org/springblade/system/mapper/AppVersionMapper.java new file mode 100644 index 0000000..571b8ce --- /dev/null +++ b/blade-service/blade-system/src/main/java/org/springblade/system/mapper/AppVersionMapper.java @@ -0,0 +1,36 @@ +/** + * BladeX Commercial License Agreement + * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved. + *

+ * Use of this software is governed by the Commercial License Agreement + * obtained after purchasing a license from BladeX. + *

+ * 1. This software is for development use only under a valid license + * from BladeX. + *

+ * 2. Redistribution of this software's source code to any third party + * without a commercial license is strictly prohibited. + *

+ * 3. Licensees may copyright their own code but cannot use segments + * from this software for such purposes. Copyright of this software + * remains with BladeX. + *

+ * Using this software signifies agreement to this License, and the software + * must not be used for illegal purposes. + *

+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is + * not liable for any claims arising from secondary or illegal development. + *

+ * Author: Chill Zhuang (bladejava@qq.com) + */ +package org.springblade.system.mapper; + + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.springblade.system.pojo.entity.AppVersion; + + +public interface AppVersionMapper extends BaseMapper { + + +} diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/mapper/DataSyncRecordMapper.java b/blade-service/blade-system/src/main/java/org/springblade/system/mapper/DataSyncRecordMapper.java new file mode 100644 index 0000000..ba8e43e --- /dev/null +++ b/blade-service/blade-system/src/main/java/org/springblade/system/mapper/DataSyncRecordMapper.java @@ -0,0 +1,13 @@ +package org.springblade.system.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.springblade.system.pojo.entity.DataSyncRecord; + +/** + * 数据同步记录表 Mapper 接口 + * + * @author BladeX + * @since 2024-09-27 + */ +public interface DataSyncRecordMapper extends BaseMapper { +} diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/mapper/DataSyncRecordMapper.xml b/blade-service/blade-system/src/main/java/org/springblade/system/mapper/DataSyncRecordMapper.xml new file mode 100644 index 0000000..1293836 --- /dev/null +++ b/blade-service/blade-system/src/main/java/org/springblade/system/mapper/DataSyncRecordMapper.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/mapper/FeeItemMapper.xml b/blade-service/blade-system/src/main/java/org/springblade/system/mapper/FeeItemMapper.xml index 60cdb64..a7c051e 100644 --- a/blade-service/blade-system/src/main/java/org/springblade/system/mapper/FeeItemMapper.xml +++ b/blade-service/blade-system/src/main/java/org/springblade/system/mapper/FeeItemMapper.xml @@ -37,6 +37,9 @@ AND status = #{feeItem.status} + + AND create_dept = #{feeItem.createDept} + ORDER BY create_time DESC diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/mapper/UserDeptMapper.java b/blade-service/blade-system/src/main/java/org/springblade/system/mapper/UserDeptMapper.java index 14e6eec..287c9e2 100644 --- a/blade-service/blade-system/src/main/java/org/springblade/system/mapper/UserDeptMapper.java +++ b/blade-service/blade-system/src/main/java/org/springblade/system/mapper/UserDeptMapper.java @@ -26,7 +26,12 @@ package org.springblade.system.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.apache.ibatis.annotations.Param; import org.springblade.system.pojo.entity.UserDept; +import org.springblade.system.pojo.vo.UserDeptIdsVO; + +import java.util.Collection; +import java.util.List; /** * Mapper 接口 @@ -35,5 +40,11 @@ import org.springblade.system.pojo.entity.UserDept; */ public interface UserDeptMapper extends BaseMapper { + /** + * 根据用户id列表查询对应的部门id列表拼接的字符串 + * @param userIds + * @return + */ + List queryUserDeptIds(@Param("userIds") Collection userIds); } diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/mapper/UserDeptMapper.xml b/blade-service/blade-system/src/main/java/org/springblade/system/mapper/UserDeptMapper.xml index 843e083..03182d8 100644 --- a/blade-service/blade-system/src/main/java/org/springblade/system/mapper/UserDeptMapper.xml +++ b/blade-service/blade-system/src/main/java/org/springblade/system/mapper/UserDeptMapper.xml @@ -2,4 +2,19 @@ + + diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/service/AppVersionService.java b/blade-service/blade-system/src/main/java/org/springblade/system/service/AppVersionService.java new file mode 100644 index 0000000..d9771a4 --- /dev/null +++ b/blade-service/blade-system/src/main/java/org/springblade/system/service/AppVersionService.java @@ -0,0 +1,56 @@ +package org.springblade.system.service; + +import cn.hutool.core.util.ObjectUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import lombok.AllArgsConstructor; +import org.springblade.core.log.exception.ServiceException; +import org.springblade.core.mp.support.Condition; +import org.springblade.core.mp.support.Query; +import org.springblade.system.mapper.AppVersionMapper; +import org.springblade.system.pojo.entity.AppVersion; +import org.springframework.stereotype.Service; + +@Service +@AllArgsConstructor +public class AppVersionService extends ServiceImpl { + + /** + * 分页 + */ + public IPage page(AppVersion modelVo, Query query){ + query.setDescs("create_time"); + IPage page = this.page(Condition.getPage(query), Condition.getQueryWrapper(modelVo)); + return page; + } + + /** + * 详情 + */ + public AppVersion detail(Long id){ + if (ObjectUtil.isEmpty(id)) { + throw new ServiceException("id不能为空"); + } + AppVersion model = this.getById(id); + return model; + } + + /** + * 发布版本/编辑 + */ + public AppVersion publishOrEdit(AppVersion modelVo) { + //版本号没有重复校验 + AppVersion appVersion=this.getOne(new LambdaQueryWrapper().eq(AppVersion::getUpdateType, modelVo.getUpdateType()) + .eq(AppVersion::getVersion, modelVo.getVersion()) + .eq(AppVersion::getSubVersion, modelVo.getSubVersion()) + ); + if(ObjectUtil.isNotEmpty(appVersion)){ + if(ObjectUtil.isEmpty(modelVo.getId())||(ObjectUtil.isNotEmpty(modelVo.getId())&&!modelVo.getId().equals(appVersion.getId()))){ + throw new ServiceException("该更新类型的版本号重复"); + } + } + this.saveOrUpdate(modelVo); + return modelVo; + } +} diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/service/IDataSyncRecordService.java b/blade-service/blade-system/src/main/java/org/springblade/system/service/IDataSyncRecordService.java new file mode 100644 index 0000000..92b5f4c --- /dev/null +++ b/blade-service/blade-system/src/main/java/org/springblade/system/service/IDataSyncRecordService.java @@ -0,0 +1,26 @@ +package org.springblade.system.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import org.springblade.system.pojo.entity.DataSyncRecord; + +/** + * 数据同步记录表 服务类 + * + * @author BladeX + * @since 2024-09-27 + */ +public interface IDataSyncRecordService extends IService { + + /** + * 使用新事物提交 + * @param entity + */ + void saveWithNewTransaction(DataSyncRecord entity); + + /** + * 查询最后一次同步成功的记录 + * @param param + * @return + */ + DataSyncRecord getLastSuccessRecord(DataSyncRecord param); +} diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/service/IDeptService.java b/blade-service/blade-system/src/main/java/org/springblade/system/service/IDeptService.java index 022c6dc..22b05cd 100644 --- a/blade-service/blade-system/src/main/java/org/springblade/system/service/IDeptService.java +++ b/blade-service/blade-system/src/main/java/org/springblade/system/service/IDeptService.java @@ -30,6 +30,7 @@ import org.springblade.system.pojo.entity.Dept; import org.springblade.system.pojo.vo.DeptVO; import org.springblade.system.pojo.vo.UserVO; +import java.util.Date; import java.util.List; import java.util.Map; @@ -88,6 +89,14 @@ public interface IDeptService extends IService { */ List lazyTree(String tenantId, Long parentId); + /** + * 部门下拉数据源 + * + * @param deptId 部门ID集合(逗号分隔),为空时返回当前租户可选部门 + * @return 部门列表 + */ + List selectDept(String deptId); + /** * 根据部门名称精确匹配获取部门ID集合 * @@ -157,4 +166,11 @@ public interface IDeptService extends IService { */ List deptLeaderInfo(Long deptId); + + /** + * 更新部门祖级列表 + * @param startTime 要更新的部门的同步时间或更新时间起始 + */ + void updateAncestors(Date startTime); + } diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/service/IFeeItemService.java b/blade-service/blade-system/src/main/java/org/springblade/system/service/IFeeItemService.java index 057f432..a0f018f 100644 --- a/blade-service/blade-system/src/main/java/org/springblade/system/service/IFeeItemService.java +++ b/blade-service/blade-system/src/main/java/org/springblade/system/service/IFeeItemService.java @@ -25,11 +25,16 @@ */ package org.springblade.system.service; +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.FeeItemImportFailureExcel; import org.springblade.system.pojo.entity.FeeItem; import org.springblade.system.pojo.vo.FeeItemVO; +import java.util.List; + /** * 费用项 服务类 * @@ -63,4 +68,20 @@ public interface IFeeItemService extends BaseService { */ boolean changeStatus(Long id, Integer status); + /** + * 导入费用项 + * + * @param data 导入数据 + * @return 导入失败明细 + */ + List importFeeItem(List data); + + /** + * 导出费用项 + * + * @param queryWrapper 查询条件 + * @return 导出数据 + */ + List exportFeeItem(Wrapper queryWrapper); + } diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/service/IMKPushService.java b/blade-service/blade-system/src/main/java/org/springblade/system/service/IMKPushService.java new file mode 100644 index 0000000..cafb90a --- /dev/null +++ b/blade-service/blade-system/src/main/java/org/springblade/system/service/IMKPushService.java @@ -0,0 +1,24 @@ +package org.springblade.system.service; + +import java.util.Date; + +/** + * mk 推送接口 + * @author bfhuange + * @date 2024/9/24 + */ +public interface IMKPushService { + /** + * 推送所有公司和部门并记录 + * + * @param oaStartTime oa同步查询开始时间 + */ + void pushOrgAndRecord(Date oaStartTime); + + /** + * 推送人员并记录 + * + * @param oaStartTime oa同步查询开始时间 + */ + void pushPersonAndRecord(Date oaStartTime); +} diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/service/IOASyncService.java b/blade-service/blade-system/src/main/java/org/springblade/system/service/IOASyncService.java new file mode 100644 index 0000000..1c7c1d9 --- /dev/null +++ b/blade-service/blade-system/src/main/java/org/springblade/system/service/IOASyncService.java @@ -0,0 +1,21 @@ +package org.springblade.system.service; + +/** + * oa同步接口 + * @author bfhuange + * @date 2024/8/23 + */ +public interface IOASyncService { + + /** + * 从oa同步公司和部门并且推送到mk + * @param syncAll 是否同步所有 + */ + void syncCompanyAndDeptAndPushMK(boolean syncAll); + + /** + * 从oa同步人员并且推送到mk + * @param syncAll 是否同步所有 + */ + void syncPersonAndPushMK(boolean syncAll); +} diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/service/IUserDeptService.java b/blade-service/blade-system/src/main/java/org/springblade/system/service/IUserDeptService.java index 87593ce..e3628cf 100644 --- a/blade-service/blade-system/src/main/java/org/springblade/system/service/IUserDeptService.java +++ b/blade-service/blade-system/src/main/java/org/springblade/system/service/IUserDeptService.java @@ -28,6 +28,10 @@ package org.springblade.system.service; import com.baomidou.mybatisplus.extension.service.IService; import org.springblade.system.pojo.entity.UserDept; +import org.springblade.system.pojo.vo.UserDeptIdsVO; + +import java.util.Collection; +import java.util.List; /** * 服务类 @@ -36,4 +40,11 @@ import org.springblade.system.pojo.entity.UserDept; */ public interface IUserDeptService extends IService { + /** + * 根据用户id列表查询对应的部门id列表拼接的字符串 + * @param userIds + * @return + */ + List queryUserDeptIds(Collection userIds); + } diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/service/IUserService.java b/blade-service/blade-system/src/main/java/org/springblade/system/service/IUserService.java index c6cc71a..db389fa 100644 --- a/blade-service/blade-system/src/main/java/org/springblade/system/service/IUserService.java +++ b/blade-service/blade-system/src/main/java/org/springblade/system/service/IUserService.java @@ -30,6 +30,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.core.mp.support.Query; +import org.springblade.system.pojo.context.IPhone; import org.springblade.system.pojo.entity.User; import org.springblade.system.pojo.entity.UserInfo; import org.springblade.system.pojo.entity.UserOauth; @@ -346,4 +347,18 @@ public interface IUserService extends BaseService { * @return 用户分页(UserVO 包含角色、部门、岗位名称) */ IPage userPage(IPage page, String account, String name); + + /** + * 解密手机号 + * + * @param user 用户对象 + */ + void decryptPhone(IPhone user); + + /** + * 加密手机号 + * + * @param user 用户对象 + */ + void encryptPhone(IPhone user); } diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/DataSyncRecordServiceImpl.java b/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/DataSyncRecordServiceImpl.java new file mode 100644 index 0000000..48ae208 --- /dev/null +++ b/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/DataSyncRecordServiceImpl.java @@ -0,0 +1,42 @@ +package org.springblade.system.service.impl; + +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import org.springblade.system.mapper.DataSyncRecordMapper; +import org.springblade.system.pojo.entity.DataSyncRecord; +import org.springblade.system.pojo.enums.DataSync; +import org.springblade.system.service.IDataSyncRecordService; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Propagation; +import org.springframework.transaction.annotation.Transactional; + +/** + * 数据同步记录表 服务实现类 + * + * @author BladeX + * @since 2024-09-27 + */ +@Service +public class DataSyncRecordServiceImpl extends ServiceImpl implements IDataSyncRecordService { + + @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRES_NEW) + @Override + public void saveWithNewTransaction(DataSyncRecord entity) { + this.save(entity); + } + + @Override + public DataSyncRecord getLastSuccessRecord(DataSyncRecord param) { + // 根据系统编码,数据类型,模式 查询最新一条成功的记录 + return this.getOne(Wrappers.lambdaQuery() + .eq(DataSyncRecord::getSystemCode, param.getSystemCode()) + .eq(DataSyncRecord::getDataType, param.getDataType()) + .eq(DataSyncRecord::getMode, param.getMode()) + // 只查成功的 + .eq(DataSyncRecord::getResult, DataSync.Result.SUCCESS.name()) + // 同步时间倒序排列 + .orderByDesc(DataSyncRecord::getSyncTime) + .last("limit 1") + ); + } +} diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/DeptServiceImpl.java b/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/DeptServiceImpl.java index 39a6f0b..fa81cf0 100644 --- a/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/DeptServiceImpl.java +++ b/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/DeptServiceImpl.java @@ -30,15 +30,14 @@ import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springblade.core.log.exception.ServiceException; import org.springblade.core.mp.support.Condition; import org.springblade.core.secure.utils.AuthUtil; import org.springblade.core.tenant.TenantGuard; import org.springblade.core.tool.constant.BladeConstant; import org.springblade.core.tool.node.ForestNodeMerger; -import org.springblade.core.tool.utils.Func; -import org.springblade.core.tool.utils.StringPool; -import org.springblade.core.tool.utils.StringUtil; +import org.springblade.core.tool.utils.*; import org.springblade.system.cache.SysCache; import org.springblade.system.mapper.DeptMapper; import org.springblade.system.pojo.entity.Dept; @@ -49,12 +48,12 @@ import org.springblade.system.service.IDeptService; import org.springblade.system.service.IUserService; import org.springblade.system.wrapper.DeptWrapper; import org.springblade.system.wrapper.UserWrapper; +import org.springblade.thirdparty.oa.constant.OAConvertConstant; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; +import java.util.*; +import java.util.function.Function; import java.util.stream.Collectors; import static org.springblade.core.tenant.TenantGuard.EntityType.DEPT; @@ -67,6 +66,7 @@ import static org.springblade.core.tenant.TenantGuard.EntityType.DEPT_PARENT; */ @Service @RequiredArgsConstructor +@Slf4j public class DeptServiceImpl extends ServiceImpl implements IDeptService { private static final String TENANT_ID = "tenantId"; @@ -135,6 +135,21 @@ public class DeptServiceImpl extends ServiceImpl implements ID return ForestNodeMerger.merge(baseMapper.lazyTree(resolvedTenantId, parentId)); } + @Override + public List selectDept(String deptId) { + LambdaQueryWrapper queryWrapper = Wrappers.lambdaQuery() + .orderByAsc(Dept::getSort) + .orderByAsc(Dept::getId); + if (!AuthUtil.isAdministrator()) { + queryWrapper.eq(Dept::getTenantId, AuthUtil.getTenantId()); + } + List deptIds = Func.toLongList(deptId); + if (CollectionUtil.isNotEmpty(deptIds)) { + queryWrapper.in(Dept::getId, deptIds); + } + return baseMapper.selectList(queryWrapper); + } + @Override public String getDeptIds(String tenantId, String deptNames) { List deptList = baseMapper.selectList(Wrappers.query().lambda().eq(Dept::getTenantId, tenantId).in(Dept::getDeptName, Func.toStrList(deptNames))); @@ -265,4 +280,121 @@ public class DeptServiceImpl extends ServiceImpl implements ID } } + + @Transactional(rollbackFor = Exception.class) + @Override + public void updateAncestors(Date startTime) { + // 1. 查询需要更新祖级列表的部门 + List list = this.list(Wrappers.lambdaQuery() + // 祖级列表为空 + .isNull(Dept::getAncestors) + // 开始时间不为空,查询同步时间大于开始时间或修改时间大于开始时间的数据 + .or(startTime != null, wrapper -> wrapper.ge(Dept::getSyncTime, startTime)) + .or(startTime != null, wrapper -> wrapper.ge(Dept::getUpdateTime, startTime)) + ); + if (CollectionUtil.isEmpty(list)) { + log.info("需要更新祖级列表的部门为空"); + return; + } + // 所有公司部门列表 + List allList = this.list().stream() + .map(dept -> BeanUtil.copyProperties(dept, DeptVO.class)) + .toList(); + // id对应的部门map + Map idDeptMap = allList.stream() + .collect(Collectors.toMap(DeptVO::getId, Function.identity(), (a, b) -> b)); + // 获取根节点列表 + List rootList = getRootList(allList, idDeptMap); + // 计算祖级列表 + setAncestors(rootList); + + // 3. 更新祖级列表 + List updateParams = list.stream() + .filter(dept -> { + DeptVO vo = idDeptMap.get(dept.getId()); + if (vo == null) { + return false; + } + if (vo.getAncestors() == null) { + return false; + } + // 祖级列表和数据库的不一样的才需要更新 + return !StringUtil.equals(vo.getAncestors(), dept.getAncestors()); + }) + .map(dept -> { + Dept updateParam = new Dept(); + updateParam.setId(dept.getId()); + DeptVO vo = idDeptMap.get(dept.getId()); + updateParam.setAncestors(vo.getAncestors()); + return updateParam; + }).toList(); + if (CollectionUtil.isNotEmpty(updateParams)) { + this.updateBatchById(updateParams); + } + } + + /** + * 获取根节点列表 + * @param allList + * @param idDeptMap + * @return + */ + private List getRootList(List allList, Map idDeptMap) { + if (CollectionUtil.isEmpty(allList)) { + return Collections.emptyList(); + } + // 根节点列表 + List rootList = new ArrayList<>(); + for (DeptVO vo : allList) { + Long parentId = vo.getParentId(); + if (OAConvertConstant.ROOT_PARENT_ID.equals(parentId)) { + // 父节点是根节点,添加到根节点列表 + rootList.add(vo); + continue; + } + DeptVO parent = idDeptMap.get(parentId); + if (parent != null) { + // 父节点存在,放入父节点的子节点列表中 + if (parent.getChildren() == null) { + parent.setChildren(new ArrayList<>()); + } + parent.getChildren().add(vo); + } + } + return rootList; + } + + /** + * 计算祖级列表 + * @param rootList 根节点列表 + */ + private void setAncestors(List rootList) { + if (CollectionUtil.isEmpty(rootList)) { + return; + } + // 根节点的祖级列表是 0, 最后拼接一个逗号,防止查询的时候有包含的 + String ancestors = OAConvertConstant.ROOT_PARENT_ID + ","; + setAncestors(rootList, ancestors); + } + + /** + * 设置祖级列表 + * @param list 节点列表 + * @param ancestors 祖级列表 + */ + private void setAncestors(List list, String ancestors) { + if (CollectionUtil.isEmpty(list)) { + return; + } + for (DeptVO vo : list) { + vo.setAncestors(ancestors); + if (CollectionUtil.isNotEmpty(vo.getChildren())) { + // 子节点列表不为空,设置子节点的祖级列表 + String newAncestors = ancestors + vo.getId() + ","; + setAncestors(vo.getChildren(), newAncestors); + } + } + } + + } diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/FeeItemServiceImpl.java b/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/FeeItemServiceImpl.java index ce70e14..88a3c65 100644 --- a/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/FeeItemServiceImpl.java +++ b/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/FeeItemServiceImpl.java @@ -25,19 +25,27 @@ */ package org.springblade.system.service.impl; +import com.baomidou.mybatisplus.core.conditions.Wrapper; 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.BeanUtil; import org.springblade.core.tool.utils.Func; +import org.springblade.system.cache.DictBizCache; +import org.springblade.system.excel.FeeItemExcel; +import org.springblade.system.excel.FeeItemImportFailureExcel; import org.springblade.system.mapper.FeeItemMapper; +import org.springblade.system.pojo.entity.DictBiz; import org.springblade.system.pojo.entity.FeeItem; import org.springblade.system.pojo.vo.FeeItemVO; import org.springblade.system.service.IFeeItemService; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.util.ArrayList; +import java.util.List; import java.util.Objects; /** @@ -48,6 +56,7 @@ import java.util.Objects; @Service public class FeeItemServiceImpl extends BaseServiceImpl implements IFeeItemService { + private static final String FEE_CATEGORY_CODE = "fee_category"; private static final int STATUS_ENABLED = 1; private static final int STATUS_DISABLED = 2; private static final int FEE_CATEGORY_MAX_LENGTH = 50; @@ -86,10 +95,38 @@ public class FeeItemServiceImpl extends BaseServiceImpl return updateById(update); } + @Override + @Transactional(rollbackFor = Exception.class) + public List importFeeItem(List data) { + if (Func.isEmpty(data)) { + throw new ServiceException("导入数据不能为空"); + } + List failureList = new ArrayList<>(); + for (int index = 0; index < data.size(); index++) { + FeeItemExcel excel = data.get(index); + try { + FeeItem feeItem = buildImportFeeItem(excel); + prepare(feeItem); + validate(feeItem); + save(feeItem); + } catch (Exception exception) { + String message = exception instanceof ServiceException ? exception.getMessage() : "导入失败"; + failureList.add(toImportFailureExcel(excel, "第" + (index + 2) + "行:" + message)); + } + } + return failureList; + } + + @Override + public List exportFeeItem(Wrapper queryWrapper) { + return list(queryWrapper).stream().map(this::toExcel).toList(); + } + private void prepare(FeeItem feeItem) { feeItem.setFeeCategory(trimToEmpty(feeItem.getFeeCategory())); feeItem.setName(trimToEmpty(feeItem.getName())); feeItem.setEnglishName(trimToNull(feeItem.getEnglishName())); + appendFeeCategoryPrefix(feeItem); if (Func.isEmpty(feeItem.getStatus())) { feeItem.setStatus(STATUS_ENABLED); } @@ -99,6 +136,9 @@ public class FeeItemServiceImpl extends BaseServiceImpl if (Func.isEmpty(feeItem.getFeeCategory())) { throw new ServiceException("费用类型不能为空"); } + if (!isValidFeeCategory(feeItem.getFeeCategory())) { + throw new ServiceException("费用类型不正确"); + } if (feeItem.getFeeCategory().length() > FEE_CATEGORY_MAX_LENGTH) { throw new ServiceException("费用类型不能超过50字"); } @@ -126,6 +166,70 @@ public class FeeItemServiceImpl extends BaseServiceImpl } } + private FeeItem buildImportFeeItem(FeeItemExcel excel) { + FeeItem feeItem = new FeeItem(); + feeItem.setFeeCategory(resolveFeeCategory(excel.getFeeCategory())); + feeItem.setEnglishName(trimToNull(excel.getEnglishName())); + feeItem.setName(trimToEmpty(excel.getName())); + feeItem.setStatus(STATUS_ENABLED); + return feeItem; + } + + private FeeItemExcel toExcel(FeeItem feeItem) { + FeeItemExcel excel = Objects.requireNonNull(BeanUtil.copyProperties(feeItem, FeeItemExcel.class)); + excel.setFeeCategory(formatFeeCategory(feeItem.getFeeCategory())); + return excel; + } + + private FeeItemImportFailureExcel toImportFailureExcel(FeeItemExcel excel, String failureReason) { + FeeItemImportFailureExcel failureExcel = new FeeItemImportFailureExcel(); + failureExcel.setFeeCategory(excel.getFeeCategory()); + failureExcel.setEnglishName(excel.getEnglishName()); + failureExcel.setName(excel.getName()); + failureExcel.setFailureReason(failureReason); + return failureExcel; + } + + private String resolveFeeCategory(String value) { + String category = trimToEmpty(value); + if (Func.isEmpty(category)) { + return category; + } + return feeCategoryList().stream() + .filter(dict -> Objects.equals(category, trimToEmpty(dict.getDictKey())) || Objects.equals(category, trimToEmpty(dict.getDictValue()))) + .findFirst() + .map(DictBiz::getDictKey) + .orElse(category); + } + + private String formatFeeCategory(String value) { + String category = trimToEmpty(value); + return feeCategoryList().stream() + .filter(dict -> Objects.equals(category, trimToEmpty(dict.getDictKey()))) + .findFirst() + .map(DictBiz::getDictValue) + .orElse(category); + } + + private boolean isValidFeeCategory(String value) { + String category = trimToEmpty(value); + return feeCategoryList().stream().anyMatch(dict -> Objects.equals(category, trimToEmpty(dict.getDictKey()))); + } + + private List feeCategoryList() { + List dictBizList = DictBizCache.getList(FEE_CATEGORY_CODE); + return dictBizList == null ? List.of() : dictBizList; + } + + private void appendFeeCategoryPrefix(FeeItem feeItem) { + if (Func.isEmpty(feeItem.getEnglishName()) || Func.isEmpty(feeItem.getFeeCategory())) { + return; + } + if (!feeItem.getEnglishName().startsWith(feeItem.getFeeCategory())) { + feeItem.setEnglishName(feeItem.getFeeCategory() + feeItem.getEnglishName()); + } + } + private String trimToEmpty(String value) { return value == null ? "" : value.trim(); } diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/MKPushServiceImpl.java b/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/MKPushServiceImpl.java new file mode 100644 index 0000000..f04b413 --- /dev/null +++ b/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/MKPushServiceImpl.java @@ -0,0 +1,280 @@ +package org.springblade.system.service.impl; + +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.lang.UUID; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springblade.common.constant.CommonConstant; +import org.springblade.core.tool.utils.DigestUtil; +import org.springblade.system.cache.ParamCache; +import org.springblade.system.convert.DeptConvert; +import org.springblade.system.convert.UserConvert; +import org.springblade.system.log.ComposeLogUtil; +import org.springblade.system.pojo.entity.DataSyncRecord; +import org.springblade.system.pojo.entity.Dept; +import org.springblade.system.pojo.entity.User; +import org.springblade.system.pojo.enums.DataSync; +import org.springblade.system.pojo.enums.DeptCategory; +import org.springblade.system.service.IDataSyncRecordService; +import org.springblade.system.service.IDeptService; +import org.springblade.system.service.IMKPushService; +import org.springblade.system.service.IUserService; +import org.springblade.system.util.DataSyncRecordUtils; +import org.springblade.thirdparty.mk.pojo.dto.MKPushDTO; +import org.springblade.thirdparty.mk.pojo.dto.MKPushOrgDeptDTO; +import org.springblade.thirdparty.mk.pojo.dto.MKPushPersonDTO; +import org.springblade.thirdparty.mk.service.IMKService; +import org.springframework.stereotype.Service; + +import java.util.Date; +import java.util.List; +import java.util.function.BiConsumer; +import java.util.function.Supplier; +import java.util.function.UnaryOperator; + +import static org.springblade.common.constant.CommonConstant.DEFAULT_PARAM_PASSWORD; + +/** + * mk 推送实现 + * @author bfhuange + * @date 2024/9/24 + */ +@Slf4j +@RequiredArgsConstructor +@Service +public class MKPushServiceImpl implements IMKPushService { + /** + * 分页查询每页最大大小 + */ + private static final long MAX_SIZE_PER_PAGE = 1000; + + private final IDeptService deptService; + private final IUserService userService; + private final IMKService mkService; + private final DeptConvert deptConvert; + private final UserConvert userConvert; + private final IDataSyncRecordService dataSyncRecordService; + + @Override + public void pushOrgAndRecord(Date oaStartTime) { + try { + ComposeLogUtil.addLog(log); + // 推送所有公司和部门并记录 + this.pushAndRecord(oaStartTime, DataSyncRecordUtils::createMKOrgPush, (startTime, endTime) -> { + // 1. 推送公司 + pushDept(DeptCategory.COMPANY, startTime, endTime); + // 2. 推送部门 + pushDept(DeptCategory.DEPT, startTime, endTime); + }); + } finally { + ComposeLogUtil.removeLastLog(); + } + } + + @Override + public void pushPersonAndRecord(Date oaStartTime) { + try { + ComposeLogUtil.addLog(log); + // 推送人员并记录 + this.pushAndRecord(oaStartTime, DataSyncRecordUtils::createMKPersonPush, (startTime, endTime) -> { + // 分页查询函数 + UnaryOperator> queryUser = this.getQueryUser(startTime, endTime); + // 分页推送函数 + BiConsumer, String> pushConsumer = this::pushUser; + // 公共推送 + pushCommon(queryUser, pushConsumer); + }); + } finally { + ComposeLogUtil.removeLastLog(); + } + } + + /** + * 推送并记录 + * @param oaStartTime + * @param recordSupplier + * @param pushHandler + */ + private void pushAndRecord(Date oaStartTime, Supplier recordSupplier, BiConsumer pushHandler) { + // 创建记录 + DataSyncRecord record = recordSupplier.get(); + // 查询最新一条成功记录 + DataSyncRecord lastSuccessRecord = dataSyncRecordService.getLastSuccessRecord(record); + // 获取推送数据的开始时间 + Date startTime = getStartTime(lastSuccessRecord, oaStartTime); + // 推送开始时间作为截止时间,只同步这个时间段的更新的数据 + Date endTime = new Date(); + record.setSyncTime(endTime); + try { + pushHandler.accept(startTime, endTime); + } catch (Exception e) { + // 有异常,结果改成失败 + record.setResult(DataSync.Result.FAIL.name()); + throw e; + } finally { + // 新事务保存同步记录 + dataSyncRecordService.saveWithNewTransaction(record); + } + } + + /** + * 获取推送开始时间 + * @param lastSuccessRecord 最后一条同步成功的记录 + * @param oaStartTime oa同步查询开始时间 + * @return + */ + private Date getStartTime(DataSyncRecord lastSuccessRecord, Date oaStartTime) { + if (lastSuccessRecord == null || lastSuccessRecord.getSyncTime() == null) { + // 最后一条推送成功记录为空,返回空推送全部 + return null; + } + if (oaStartTime == null) { + // oa 同步查询开始时间为空,且最后一条推送成功记录不为空 + return lastSuccessRecord.getSyncTime(); + } + // 否则取2个时间较小的那个 + Date lastSyncTime = lastSuccessRecord.getSyncTime(); + return oaStartTime.before(lastSyncTime) ? oaStartTime : lastSyncTime; + } + + /** + * 同步公司/部门 + * + * @param deptCategory 类型 + * @param startTime 数据更新开始时间 + * @param endTime 数据更新截止时间 + */ + private void pushDept(DeptCategory deptCategory, Date startTime, Date endTime) { + // 分页查询函数 + UnaryOperator> queryDept = getQueryDept(deptCategory, startTime, endTime); + // 推送函数 + BiConsumer, String> pushDept = getPushDept(deptCategory); + // 公共推送 + pushCommon(queryDept, pushDept); + } + + /** + * 获取查询部门的方法 + * + * @param deptCategory + * @param startTime + * @param endTime + * @return + */ + private UnaryOperator> getQueryDept(DeptCategory deptCategory, Date startTime, Date endTime) { + return page -> deptService.page(page, Wrappers.lambdaQuery() + .eq(Dept::getDeptCategory, deptCategory.getCode()) + .eq(Dept::getIsOa, CommonConstant.YES) + // 查询同步时间小于结束时间且修改时间小于结束时间的部门 + .lt(Dept::getSyncTime, endTime) + .lt(Dept::getUpdateTime, endTime) + // 开始时间不为空,查询同步时间大于开始时间或修改时间大于开始时间的数据 + .and(startTime != null, wrapper -> wrapper.ge(Dept::getSyncTime, startTime) + .or(w -> w.ge(Dept::getUpdateTime, startTime))) + ); + } + + /** + * 获取推送部门的方法 + * @param deptCategory + * @return + */ + private BiConsumer, String> getPushDept(DeptCategory deptCategory) { + return (page, batchId) -> { + List records = page.getRecords(); + if (CollectionUtil.isEmpty(records)) { + return; + } + // 当前页是否最后一页 + boolean lastPage = page.getCurrent() == page.getPages(); + MKPushDTO param = new MKPushDTO<>(); + param.setBatchId(batchId); + param.setFinish(lastPage); + List list = deptConvert.entities2dtos(records); + param.setMessage(list); + // 推送公司 + boolean result = mkService.pushOrgDept(param); + ComposeLogUtil.getLastLog().info("mk推送{} 批次号:{} 当前页:{} 当前页数量:{} 结果:{}", deptCategory.getName(), batchId, page.getCurrent(), page.getSize(), result); + }; + } + + + /** + * 获取查询人员的方法 + * + * @param startTime 开始时间 + * @param endTime 结束时间 + * @return + */ + private UnaryOperator> getQueryUser(Date startTime, Date endTime) { + return page -> userService.page(page, Wrappers.lambdaQuery() + .eq(User::getIsOa, CommonConstant.YES) + // 查询同步时间小于结束时间且修改时间小于结束时间的用户 + .lt(User::getSyncTime, endTime) + .lt(User::getUpdateTime, endTime) + // 开始时间不为空,查询同步时间大于更新时间或修改时间大于更新时间的数据 + .and(startTime != null, wrapper -> wrapper.ge(User::getSyncTime, startTime) + .or(w -> w.ge(User::getUpdateTime, startTime))) + ); + } + + /** + * 推送人员 + * @param page + * @param batchId + */ + private void pushUser(IPage page, String batchId) { + List records = page.getRecords(); + if (CollectionUtil.isEmpty(records)) { + return; + } + // 当前页是否最后一页 + boolean lastPage = page.getCurrent() == page.getPages(); + MKPushDTO param = new MKPushDTO<>(); + param.setBatchId(batchId); + param.setFinish(lastPage); + // 默认密码及参数转换 + String defaultPassword = ParamCache.getValue(DEFAULT_PARAM_PASSWORD); + // 解密手机号 + records.forEach(userService::decryptPhone); + List list = userConvert.entities2dtos(records, DigestUtil.md5Hex(defaultPassword)); + param.setMessage(list); + // 推送人员 + boolean result = mkService.pushPerson(param); + ComposeLogUtil.getLastLog().info("mk推送人员 批次号:{} 当前页:{} 当前页数量:{} 结果:{}", batchId, page.getCurrent(), page.getSize(), result); + if (result) { + // 推送成功,修改用户推送状态 + List updateIds = records.stream() + .map(User::getId) + .toList(); + userService.lambdaUpdate() + // 设置是否推送mk为是 + .set(User::getIsPushMk, CommonConstant.YES) + .in(User::getId, updateIds) + // 筛选未推送mk的 + .eq(User::getIsPushMk, CommonConstant.NO) + .update(); + } + } + + /** + * 公共推送 + * @param queryOperator 分页查询 + * @param pushConsumer 数据推送 + * @param + */ + private void pushCommon(UnaryOperator> queryOperator, BiConsumer, String> pushConsumer) { + IPage page = new Page<>(1, MAX_SIZE_PER_PAGE); + String batchId = UUID.fastUUID().toString(true); + do { + // 分页查询数据 + page = queryOperator.apply(page); + // 推送数据 + pushConsumer.accept(page, batchId); + page.setCurrent(page.getCurrent() + 1); + } while (page.getCurrent() <= page.getPages()); + } +} diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/OASyncServiceImpl.java b/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/OASyncServiceImpl.java new file mode 100644 index 0000000..38e7eb9 --- /dev/null +++ b/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/OASyncServiceImpl.java @@ -0,0 +1,649 @@ +package org.springblade.system.service.impl; + +import cn.hutool.core.collection.CollectionUtil; +import com.alibaba.fastjson2.JSON; +import com.baomidou.mybatisplus.core.incrementer.DefaultIdentifierGenerator; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.springblade.common.constant.DictTypeEnum; +import org.springblade.core.cache.utils.CacheUtil; +import org.springblade.core.log.exception.ServiceException; +import org.springblade.core.tool.utils.DateUtil; +import org.springblade.core.tool.utils.DigestUtil; +import org.springblade.system.cache.DictCache; +import org.springblade.system.cache.ParamCache; +import org.springblade.system.convert.DeptConvert; +import org.springblade.system.convert.UserConvert; +import org.springblade.system.log.ComposeLogUtil; +import org.springblade.system.pojo.entity.*; +import org.springblade.system.pojo.enums.DataSync; +import org.springblade.system.pojo.enums.DeptCategory; +import org.springblade.system.pojo.vo.UserDeptIdsVO; +import org.springblade.system.service.*; +import org.springblade.system.util.DataSyncRecordUtils; +import org.springblade.thirdparty.oa.constant.OAConstant; +import org.springblade.thirdparty.oa.constant.OAConvertConstant; +import org.springblade.thirdparty.oa.feign.IOAClient; +import org.springblade.thirdparty.oa.pojo.response.OACompanyResponse; +import org.springblade.thirdparty.oa.pojo.response.OADepartmentResponse; +import org.springblade.thirdparty.oa.pojo.response.OAPersonResponse; +import org.springblade.thirdparty.oa.pojo.search.OACompanySearch; +import org.springblade.thirdparty.oa.pojo.search.OADepartmentSearch; +import org.springblade.thirdparty.oa.pojo.search.OAPersonSearch; +import org.springblade.thirdparty.oa.pojo.search.OASearch; +import org.springblade.thirdparty.oa.util.OAUtils; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.*; +import java.util.function.Consumer; +import java.util.function.Supplier; +import java.util.stream.Collectors; + +import static org.springblade.common.constant.CommonConstant.*; +import static org.springblade.core.cache.constant.CacheConstant.SYS_CACHE; +import static org.springblade.core.cache.constant.CacheConstant.USER_CACHE; + +/** + * @author bfhuange + * @date 2024/8/23 + */ +@Slf4j +@RequiredArgsConstructor +@Service +public class OASyncServiceImpl implements IOASyncService { + /** + * 用户表部门id最大长度 + */ + private static final int MAX_DEPT_ID_LENGTH = 2000; + + private final IOAClient oaClient; + private final DeptConvert deptConvert; + private final IDeptService deptService; + private final IUserService userService; + private final UserConvert userConvert; + private final IUserDeptService userDeptService; + private final IRoleService roleService; + private final IMKPushService mkPushService; + private final IDataSyncRecordService dataSyncRecordService; + + @Transactional(rollbackFor = Exception.class) + @Override + public void syncCompanyAndDeptAndPushMK(boolean syncAll) { + try { + ComposeLogUtil.addLog(log); + // 1. 同步公司和部门并记录 + Date startTime = this.syncAndRecord(DataSyncRecordUtils::createOAOrgFetch, queryStartTime -> { + // 1. 同步公司 + this.syncCompany(queryStartTime); + // 2. 同步部门 + this.syncDept(queryStartTime); + // 2. 更新部门祖级列表 + deptService.updateAncestors(queryStartTime); + }, syncAll); + // 2. 推送mk + mkPushService.pushOrgAndRecord(startTime); + } finally { + ComposeLogUtil.removeLastLog(); + } + } + + @Transactional(rollbackFor = Exception.class) + @Override + public void syncPersonAndPushMK(boolean syncAll) { + try { + ComposeLogUtil.addLog(log); + // 1. 同步人员并记录 + Date startTime = this.syncAndRecord(DataSyncRecordUtils::createOAPersonFetch, this::syncPerson, syncAll); + // 2. 推送mk + mkPushService.pushPersonAndRecord(startTime); + } finally { + ComposeLogUtil.removeLastLog(); + } + } + + /** + * 同步并记录 + * + * @param recordSupplier 同步记录提供 + * @param syncHandler 同步处理器 + * @param syncAll 是否同步所有 + * @return + */ + private Date syncAndRecord(Supplier recordSupplier, Consumer syncHandler, boolean syncAll) { + // 创建记录 + DataSyncRecord record = recordSupplier.get(); + // 查询最新一条成功记录 + DataSyncRecord lastSuccessRecord = syncAll ? null : dataSyncRecordService.getLastSuccessRecord(record); + // 同步所有,开始时间设置为null,否则最新成功记录的同步时间作为查询开始时间,第一次为null同步所有 + Date startTime = syncAll ? null : Optional.ofNullable(lastSuccessRecord) + .map(DataSyncRecord::getSyncTime) + .orElse(null); + // 同步时间设置早一些,可能下次增量同步会多同步一些数据 + // 设置晚了,增量同步可能会漏同步数据 + record.setSyncTime(new Date()); + try { + // 1. 同步处理 + syncHandler.accept(startTime); + } catch (Exception e) { + // 有异常,结果改成失败 + record.setResult(DataSync.Result.FAIL.name()); + throw e; + } finally { + // 新事务保存同步记录 + dataSyncRecordService.saveWithNewTransaction(record); + } + return startTime; + } + + /** + * 同步公司 + * @param startTime 查询开始时间 + */ + private void syncCompany(Date startTime) { + // 未处理的数据 + List notHandleList = new ArrayList<>(); + // 1. 设置查询参数 + OACompanySearch companySearch = new OACompanySearch(); + companySearch.setCurPage(1); + if (startTime != null) { + // 开始时间不为空,设置修改时间参数 + companySearch.setModified(DateUtil.format(startTime, DateUtil.PATTERN_DATETIME)); + } + // 2. 分页查询并处理数据 + OAUtils.pageSyncHandler(companySearch, param -> oaClient.queryCompanyPage(new OASearch<>(param)), response -> { + ComposeLogUtil.getLastLog().error("调用OA接口查询公司信息失败 {}", JSON.toJSONString(response)); + return new ServiceException("调用OA接口查询公司信息失败"); + }, 10000, ComposeLogUtil.getLastLog()::info).accept(list -> { + // 处理数据 + List deptList = handleCompany(list); + notHandleList.addAll(deptList); + }); + // 3. 未处理的数据 + if (CollectionUtil.isNotEmpty(notHandleList)) { + ComposeLogUtil.getLastLog().warn("同步公司,还有未处理的数据:{}", JSON.toJSONString(notHandleList)); + } + // 清除部门缓存 + CacheUtil.clear(SYS_CACHE); + CacheUtil.clear(SYS_CACHE, Boolean.FALSE); + } + + /** + * 同步部门 + * @param startTime 查询开始时间 + */ + private void syncDept(Date startTime) { + String subCompanyIds = getSubCompanyIds(); + if (StringUtils.isEmpty(subCompanyIds)) { + ComposeLogUtil.getLastLog().warn("未查询到子公司查询参数"); + return; + } + // 未处理的数据 + List notHandleList = new ArrayList<>(); + // 1. 设置查询参数 + OADepartmentSearch departmentSearch = new OADepartmentSearch(); + departmentSearch.setCurPage(1); + departmentSearch.setSubcompanyid1(subCompanyIds); + if (startTime != null) { + // 开始时间不为空,设置修改时间参数 + departmentSearch.setModified(DateUtil.format(startTime, DateUtil.PATTERN_DATETIME)); + } + // 2. 分页查询并处理数据 + OAUtils.pageSyncHandler(departmentSearch, param -> oaClient.queryDepartmentPage(new OASearch<>(param)), response -> { + ComposeLogUtil.getLastLog().error("调用OA接口查询部门信息失败 {}", JSON.toJSONString(response)); + return new ServiceException("调用OA接口查询部门信息失败"); + }, 10000, ComposeLogUtil.getLastLog()::info).accept(list -> { + // 处理数据 + List deptList = handleDept(list); + notHandleList.addAll(deptList); + }); + // 3. 未处理的数据 + if (CollectionUtil.isNotEmpty(notHandleList)) { + ComposeLogUtil.getLastLog().warn("同步部门,还有未处理的数据:{}", JSON.toJSONString(notHandleList)); + } + // 清除部门缓存 + CacheUtil.clear(SYS_CACHE); + CacheUtil.clear(SYS_CACHE, Boolean.FALSE); + } + + /** + * 同步人员 + * @param startTime 查询开始时间 + */ + private void syncPerson(Date startTime) { + String subCompanyIds = getSubCompanyIds(); + if (StringUtils.isEmpty(subCompanyIds)) { + ComposeLogUtil.getLastLog().warn("未查询到子公司查询参数"); + return; + } + // 1. 设置查询参数 + OAPersonSearch personSearch = new OAPersonSearch(); + personSearch.setCurPage(1); + personSearch.setSubcompanyid1(subCompanyIds); + if (startTime != null) { + // 开始时间不为空,设置修改时间参数 + personSearch.setModified(DateUtil.format(startTime, DateUtil.PATTERN_DATETIME)); + } + // 2. 分页查询并处理数据 + OAUtils.pageSyncHandler(personSearch, param -> oaClient.queryPersonPage(new OASearch<>(param)), response -> { + ComposeLogUtil.getLastLog().error("调用OA接口查询人员信息失败 {}", JSON.toJSONString(response)); + return new ServiceException("调用OA接口查询人员信息失败"); + }, 10000, ComposeLogUtil.getLastLog()::info).accept(this::handlePerson); + // 清除用户缓存 + CacheUtil.clear(USER_CACHE); + } + + /** + * 处理oa人员 + * @param oaPersons + */ + private void handlePerson(List oaPersons) { + if (CollectionUtil.isEmpty(oaPersons)) { + // 数据为空,直接返回 + return; + } + // 根公司id + String rootCompanyId = getRootCompanyId(); + if (rootCompanyId == null) { + return; + } + // 根公司下要同步的部门id + Set rootCompanyDeptIds = getRootCompanyDeptIds(rootCompanyId); + oaPersons = oaPersons.stream() + // 公司不是根公司,或者部门在根公司下要同步的部门列表中 + .filter(oaPerson -> !rootCompanyId.equals(oaPerson.getSubcompanyid1()) || rootCompanyDeptIds.contains(oaPerson.getDepartmentid())) + .toList(); + // 根据手机号转成set + TreeSet oaPersonSet = CollectionUtil.toTreeSet(oaPersons, Comparator.comparing(OAPersonResponse::getMobile)); + // 默认密码 + String defaultPassword = DigestUtil.encrypt(ParamCache.getValue(DEFAULT_PARAM_PASSWORD)); + // 转换数据 + List users = oaPersonSet.stream() + // 只需要手机不为空的 + .filter(person -> StringUtils.isNotBlank(person.getMobile())) + .map(person -> userConvert.person2user(person, defaultPassword)) + .toList(); + List phones = users.stream() + .map(User::getPhone) + .filter(StringUtils::isNotBlank) + .distinct() + .toList(); + // 查询所有用户 + Map userMap = userService.list(Wrappers.lambdaQuery() + //.eq(User::getIsDeleted, BladeConstant.DB_NOT_DELETED) + .in(User::getPhone, phones) + ).stream() + // 解密手机号 + .peek(userService::decryptPhone) + .collect(Collectors.toMap(User::getPhone, User::getId, (a, b) -> b)); + // 数据库存在的所有用户id + Set existsUserIds = new HashSet<>(userMap.values()); + users.forEach(user -> { + if (userMap.containsKey(user.getPhone())) { + // 根据手机号获取对应的用户id + user.setId(userMap.get(user.getPhone())); + // 清空密码,不修改密码 + user.setPassword(null); + } else { + // 没有就生成一个id + user.setId(DefaultIdentifierGenerator.getInstance().nextId(null)); + userService.encryptPhone(user); + userMap.put(user.getPhone(), user.getId()); + } + }); + + // 不存在的新增 + List addUsers = users.stream() + .filter(user -> !existsUserIds.contains(user.getId())) + .toList(); + if (CollectionUtil.isNotEmpty(addUsers)) { + ComposeLogUtil.getLastLog().info("批量新增用户:{}", addUsers.size()); + userService.saveBatch(addUsers); + } + // 存在的修改 + List updateUsers = users.stream() + .filter(user -> existsUserIds.contains(user.getId())) + .toList(); + if (CollectionUtil.isNotEmpty(updateUsers)) { + ComposeLogUtil.getLastLog().info("批量修改用户:{}", updateUsers.size()); + userService.updateBatchById(updateUsers); + } + // 没有手机号的数据 = 手机号为空的数量 + long noPhoneNum = oaPersons.stream() + .map(OAPersonResponse::getMobile) + .filter(StringUtils::isBlank) + .count(); + ComposeLogUtil.getLastLog().info("没有手机号的数据:{}", noPhoneNum); + + Map userDeptMap = userDeptService.list(Wrappers.lambdaQuery() + .in(UserDept::getUserId, userMap.values()) + ).stream() + .collect(Collectors.toMap(this::getUserDeptKey, UserDept::getId, (a, b) -> b)); + // 数据库存在的所有用户部门id + Set existsUserDeptIds = new HashSet<>(userDeptMap.values()); + + List userDeptList = oaPersons.stream() + // 只要包含用户手机号的 + .filter(oaPerson -> userMap.containsKey(oaPerson.getMobile())) + .map(oaPerson -> userConvert.person2userDept(oaPerson, userMap)) + .toList(); + userDeptList.forEach(userDept -> { + String userDeptKey = getUserDeptKey(userDept); + if (userDeptMap.containsKey(userDeptKey)) { + // 根据key获取用户部门id + userDept.setId(userDeptMap.get(userDeptKey)); + } else { + // 没有就生成一个id + userDept.setId(DefaultIdentifierGenerator.getInstance().nextId(null)); + } + }); + // 不存在的新增 + List addList = userDeptList.stream() + .filter(userDept -> !existsUserDeptIds.contains(userDept.getId())) + .toList(); + if (CollectionUtil.isNotEmpty(addList)) { + ComposeLogUtil.getLastLog().info("批量新增用户部门:{}", addList.size()); + userDeptService.saveBatch(addList); + } + // 存在的修改 + List updateList = userDeptList.stream() + .filter(userDept -> existsUserDeptIds.contains(userDept.getId())) + .toList(); + if (CollectionUtil.isNotEmpty(updateList)) { + ComposeLogUtil.getLastLog().info("批量修改用户部门:{}", updateList.size()); + userDeptService.updateBatchById(updateList); + } + // 回写部门id到用户表 + Collection userIds = userMap.values(); + List list = userDeptService.queryUserDeptIds(userIds); + // 查询没有角色的用户id + Set noRoleUserIds = userService.list(Wrappers.lambdaQuery() + .in(User::getId, userIds) + .isNull(User::getRoleId) + ).stream() + .map(User::getId) + .collect(Collectors.toSet()); + // 获取默认角色id + String defaultRoleId = getDefaultRoleId(); + List updateUserParams = list.stream() + // 过滤掉空部门id及长度超长的 + .filter(userDeptIds -> StringUtils.isNotBlank(userDeptIds.getDeptIds()) && userDeptIds.getDeptIds().length() <= MAX_DEPT_ID_LENGTH) + .map(userDeptIds -> { + User user = new User(); + user.setId(userDeptIds.getUserId()); + user.setDeptId(userDeptIds.getDeptIds()); + user.setDeptCodes(userDeptIds.getDeptCodes()); + if (noRoleUserIds.contains(userDeptIds.getUserId())) { + user.setRoleId(defaultRoleId); + } + return user; + }).toList(); + userService.updateBatchById(updateUserParams); + } + + /** + * 处理oa公司 + * @param oaCompanies + * @return 未处理的数据 + */ + private List handleCompany(List oaCompanies) { + if (CollectionUtil.isEmpty(oaCompanies)) { + // 数据为空,直接返回 + return Collections.emptyList(); + } + // 获取需要的公司名称 + Set companyNames = getCompanyNames(); + // 转换数据 + List allParam = oaCompanies.stream() + // 筛选只要需要的公司 + .filter(company -> companyNames.contains(company.getSubcompanyname())) + .map(deptConvert::company2dept) + .toList(); + + // 查询数据库的部门,转换成map + Map deptMap = getAllCompanyDeptMap(); + + // 处理部门 + return handleDept(allParam, deptMap, DeptCategory.COMPANY); + } + + /** + * 处理oa部门 + * @param oaDepts + * @return 未处理的数据 + */ + private List handleDept(List oaDepts) { + if (CollectionUtil.isEmpty(oaDepts)) { + // 数据为空,直接返回 + return Collections.emptyList(); + } + // 查询所有公司的编码和id的map + Map companyDeptMap = getAllCompanyDeptMap(); + // 根公司id + String rootCompanyId = getRootCompanyId(); + if (rootCompanyId == null) { + return Collections.emptyList(); + } + // 根公司下要同步的部门名称 + Set rootCompanyDeptNames = getRootCompanyDeptNames(); + // 转换数据 + List allParam = oaDepts.stream() + // 部门的公司不是根公司,或者部门名称在根公司下需要同步的部门列表中,且是根部门 + .filter(oaDept -> !rootCompanyId.equals(oaDept.getSubcompanyid1()) || (OAConstant.ROOT_COMPANY_ID.equals(oaDept.getSupdepid()) && rootCompanyDeptNames.contains(oaDept.getDepartmentname()))) + .map(dept -> deptConvert.dept2dept(dept, companyDeptMap)) + .toList(); + if (CollectionUtil.isEmpty(allParam)) { + return Collections.emptyList(); + } + Set deptCodes = allParam.stream() + .map(Dept::getDeptCode) + .collect(Collectors.toSet()); + // 查询数据库的部门,转换成map + Map deptMap = deptService.list(Wrappers.lambdaQuery() + .eq(Dept::getDeptCategory, DeptCategory.DEPT.getCode()) + .in(Dept::getDeptCode, deptCodes) + ).stream() + .filter(dept -> StringUtils.isNotEmpty(dept.getDeptCode())) + .collect(Collectors.toMap(Dept::getDeptCode, Dept::getId, (a, b) -> b)); + // 处理部门 + return this.handleDept(allParam, deptMap, DeptCategory.DEPT); + } + + /** + * 处理部门 + * @param allParam 待处理的部门 + * @param deptMap 数据库所有部门编码和id map + * @return 未处理的数据 + */ + private List handleDept(List allParam, Map deptMap, DeptCategory deptCategory) { + // 数据库存在的所有部门id + Set existsDeptIds = new HashSet<>(deptMap.values()); + allParam.forEach(dept -> { + if (deptMap.containsKey(dept.getDeptCode())) { + // 根据部门编码获取对应的部门id + dept.setId(deptMap.get(dept.getDeptCode())); + } else { + // 没有就生成一个id + dept.setId(DefaultIdentifierGenerator.getInstance().nextId(null)); + deptMap.put(dept.getDeptCode(), dept.getId()); + } + }); + + // 重新把没有父id的数据设置一下父id + allParam.stream() + .filter(dept -> dept.getParentId() == null && deptMap.containsKey(dept.getDeptCode())) + .forEach(dept -> dept.setParentId(deptMap.get(dept.getParentCode()))); + + // 父id不为空,保存数据 + List list = allParam.stream() + .filter(dept -> dept.getParentId() != null) + .toList(); + // 不存在的新增 + List addList = list.stream() + .filter(dept -> !existsDeptIds.contains(dept.getId())) + .toList(); + if (CollectionUtil.isNotEmpty(addList)) { + ComposeLogUtil.getLastLog().info("批量新增{}:{}", deptCategory.getName(), addList.size()); + deptService.saveBatch(addList); + } + // 存在的修改 + List updateList = list.stream() + .filter(dept -> existsDeptIds.contains(dept.getId())) + .toList(); + if (CollectionUtil.isNotEmpty(updateList)) { + ComposeLogUtil.getLastLog().info("批量修改{}:{}", deptCategory.getName(), updateList.size()); + deptService.updateBatchById(updateList); + } + // 返回未处理的 + return allParam.stream() + .filter(dept -> dept.getParentId() == null) + .toList(); + } + + /** + * 获取oa查询参数,子公司id参数 + * @return + */ + private String getSubCompanyIds() { + Set companyNames = this.getCompanyNames(); + if (companyNames.isEmpty()) { + return null; + } + return deptService.list(Wrappers.lambdaQuery() + // 根据名称查询公司 + .in(Dept::getDeptName, companyNames) + .eq(Dept::getDeptCategory, DeptCategory.COMPANY.getCode()) + ).stream() + // 只要部门编码不为空,且包含了oa公司前缀的 + .filter(dept -> StringUtils.isNotBlank(dept.getDeptCode()) && dept.getDeptCode().contains(OAConvertConstant.COMPANY_OA_PREFIX)) + // 去掉oa公司前缀 + .map(dept -> dept.getDeptCode().replace(OAConvertConstant.COMPANY_OA_PREFIX, "")) + .collect(Collectors.joining(",")); + } + + /** + * 获取默认角色id + */ + private String getDefaultRoleId() { + // 从参数管理查询,没有取默认值,作为角色别名,查询角色 + String defaultRole = ParamCache.getValue(DEFAULT_PARAM_ROLE); + if (defaultRole == null) { + defaultRole = DEFAULT_ROLE; + } + List roleList = roleService.list(Wrappers.lambdaQuery() + .eq(Role::getRoleAlias, defaultRole) + ); + if (CollectionUtil.isEmpty(roleList)) { + return null; + } + return roleList.get(0).getId().toString(); + } + + /** + * 获取所有公司的编码和id的map + * @return + */ + private Map getAllCompanyDeptMap() { + return deptService.list(Wrappers.lambdaQuery() + .eq(Dept::getDeptCategory, DeptCategory.COMPANY.getCode()) + ).stream() + .filter(dept -> StringUtils.isNotEmpty(dept.getDeptCode())) + .collect(Collectors.toMap(Dept::getDeptCode, Dept::getId, (a, b) -> b)); + } + + /** + * 获取需要的公司id + * @return + */ + private Set getCompanyNames() { + List list = DictCache.getList(DictTypeEnum.SYNC_COMPANY_NAME.getType()); + if (CollectionUtil.isEmpty(list)) { + ComposeLogUtil.getLastLog().warn("未查询到要同步的公司名称"); + return Collections.emptySet(); + } + return list.stream() + .filter(Objects::nonNull) + .map(Dict::getDictValue) + .filter(StringUtils::isNotBlank) + .collect(Collectors.toSet()); + } + + /** + * 获取根公司下需要同步的部门名称 + * @return + */ + private Set getRootCompanyDeptNames() { + List list = DictCache.getList(DictTypeEnum.SYNC_ROOT_COMPANY_DEPT_NAME.getType()); + if (CollectionUtil.isEmpty(list)) { + ComposeLogUtil.getLastLog().warn("未查询到要同步的根公司下的部门名称"); + return Collections.emptySet(); + } + return list.stream() + .filter(Objects::nonNull) + .map(Dict::getDictValue) + .filter(StringUtils::isNotBlank) + .collect(Collectors.toSet()); + } + + /** + * 获取根公司下需要同步的部门id + * @param rootCompanyId + * @return + */ + private Set getRootCompanyDeptIds(String rootCompanyId) { + Set rootCompanyDeptNames = this.getRootCompanyDeptNames(); + if (CollectionUtil.isEmpty(rootCompanyDeptNames)) { + return Collections.emptySet(); + } + // 查询父部门编码是根公司的,部门类型的,根公司下部门名称的部门 + return deptService.list(Wrappers.lambdaQuery() + .in(Dept::getDeptName, rootCompanyDeptNames) + .eq(Dept::getDeptCategory, DeptCategory.DEPT.getCode()) + .eq(Dept::getParentCode, OAConvertConstant.COMPANY_OA_PREFIX + rootCompanyId) + ).stream() + .filter(dept -> StringUtils.isNotBlank(dept.getDeptCode())) + .map(dept -> dept.getDeptCode().replace(OAConvertConstant.DEPARTMENT_OA_PREFIX, "")) + .collect(Collectors.toSet()); + } + + /** + * 根公司id + * @return + */ + private String getRootCompanyId() { + String companyName = DictCache.getValue(DictTypeEnum.SYNC_COMPANY_NAME.getType(), DICT_KEY_SYNC_ROOT_COMPANY_NAME); + if (StringUtils.isBlank(companyName)) { + ComposeLogUtil.getLastLog().warn("未查询到配置的根公司名称"); + return null; + } + Dept dept = deptService.getOne(Wrappers.lambdaQuery() + .eq(Dept::getDeptName, companyName) + .eq(Dept::getDeptCategory, DeptCategory.COMPANY.getCode()) + .last("limit 1") + ); + if (dept == null) { + ComposeLogUtil.getLastLog().warn("未查询到根公司 名称:{}", companyName); + return null; + } + if (StringUtils.isBlank(dept.getDeptCode())) { + ComposeLogUtil.getLastLog().warn("根公司编码为空:{}", companyName); + return null; + } + // 部门编码去掉前缀,就是oa的id + return dept.getDeptCode().replace(OAConvertConstant.COMPANY_OA_PREFIX, ""); + } + + /** + * 获取用户部门唯一标识,用户id+公司编码+部门编码 + * @param userDept + * @return + */ + private String getUserDeptKey(UserDept userDept) { + if (userDept == null) { + return null; + } + return userDept.getUserId() + userDept.getCompanyCode() + userDept.getDeptCode(); + } +} diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/UserDeptServiceImpl.java b/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/UserDeptServiceImpl.java index b882a88..88b1773 100644 --- a/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/UserDeptServiceImpl.java +++ b/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/UserDeptServiceImpl.java @@ -26,12 +26,18 @@ package org.springblade.system.service.impl; +import cn.hutool.core.collection.CollectionUtil; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import org.springblade.system.pojo.entity.UserDept; import org.springblade.system.mapper.UserDeptMapper; +import org.springblade.system.pojo.vo.UserDeptIdsVO; import org.springblade.system.service.IUserDeptService; import org.springframework.stereotype.Service; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + /** * 服务实现类 * @@ -40,4 +46,12 @@ import org.springframework.stereotype.Service; @Service public class UserDeptServiceImpl extends ServiceImpl implements IUserDeptService { + @Override + public List queryUserDeptIds(Collection userIds) { + if (CollectionUtil.isEmpty(userIds)) { + return Collections.emptyList(); + } + return baseMapper.queryUserDeptIds(userIds); + } + } diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/UserServiceImpl.java b/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/UserServiceImpl.java index 574a5b3..6dfc133 100644 --- a/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/UserServiceImpl.java +++ b/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/UserServiceImpl.java @@ -32,6 +32,7 @@ import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import lombok.AllArgsConstructor; +import org.bouncycastle.util.encoders.Hex; import org.springblade.common.constant.ParamConstant; import org.springblade.common.constant.TenantConstant; import org.springblade.core.cache.utils.CacheUtil; @@ -40,6 +41,7 @@ import org.springblade.core.mp.base.BaseServiceImpl; import org.springblade.core.mp.enums.StatusType; import org.springblade.core.mp.support.Condition; import org.springblade.core.mp.support.Query; +import org.springblade.core.oauth2.props.OAuth2Properties; import org.springblade.core.secure.utils.AuthUtil; import org.springblade.core.tenant.BladeTenantProperties; import org.springblade.core.tenant.TenantGuard; @@ -54,6 +56,7 @@ import org.springblade.system.cache.SysCache; import org.springblade.system.cache.UserCache; import org.springblade.system.excel.UserExcel; import org.springblade.system.mapper.UserMapper; +import org.springblade.system.pojo.context.IPhone; import org.springblade.system.pojo.entity.*; import org.springblade.system.pojo.enums.DictEnum; import org.springblade.system.pojo.enums.UserType; @@ -96,6 +99,9 @@ public class UserServiceImpl extends BaseServiceImpl implement private final IRoleService roleService; private final BladeTenantProperties tenantProperties; + private final OAuth2Properties properties; + + @Override public User getDetail(User user) { QueryWrapper queryWrapper = Condition.getQueryWrapper(user); @@ -774,6 +780,27 @@ public class UserServiceImpl extends BaseServiceImpl implement } } + + @Override + public void decryptPhone(IPhone user) { +// if (user == null) { +// return; +// } +// if (user.getPhone() != null && user.getPhone().length() > 20) { +// user.setPhone(SM2Util.decrypt(user.getPhone(), properties.getPublicKey(), properties.getPrivateKey())); +// } + } + + @Override + public void encryptPhone(IPhone user) { +// if (user == null) { +// return; +// } +// if(StringUtil.isNotBlank(user.getPhone())) { +// user.setPhone(Hex.toHexString(SM2Util.encrypt(user.getPhone(),properties.getPublicKey()))); +// } + } + private String randomPassword() { StringBuilder password = new StringBuilder(RANDOM_PASSWORD_LENGTH); for (int index = 0; index < RANDOM_PASSWORD_LENGTH; index++) { diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/wrapper/FeeItemWrapper.java b/blade-service/blade-system/src/main/java/org/springblade/system/wrapper/FeeItemWrapper.java index ae9558e..2d59255 100644 --- a/blade-service/blade-system/src/main/java/org/springblade/system/wrapper/FeeItemWrapper.java +++ b/blade-service/blade-system/src/main/java/org/springblade/system/wrapper/FeeItemWrapper.java @@ -27,6 +27,9 @@ package org.springblade.system.wrapper; import org.springblade.core.mp.support.BaseEntityWrapper; import org.springblade.core.tool.utils.BeanUtil; +import org.springblade.core.tool.utils.Func; +import org.springblade.system.cache.SysCache; +import org.springblade.system.cache.UserCache; import org.springblade.system.pojo.entity.FeeItem; import org.springblade.system.pojo.vo.FeeItemVO; @@ -45,7 +48,12 @@ public class FeeItemWrapper extends BaseEntityWrapper { @Override public FeeItemVO entityVO(FeeItem feeItem) { - return Objects.requireNonNull(BeanUtil.copyProperties(feeItem, FeeItemVO.class)); + FeeItemVO feeItemVO = Objects.requireNonNull(BeanUtil.copyProperties(feeItem, FeeItemVO.class)); + feeItemVO.setCreateDeptName( + Func.isEmpty(feeItem.getCreateDept()) ? "" : SysCache.getDeptName(feeItem.getCreateDept()) + ); + feeItemVO.setUpdateUserName(UserCache.getUserRealName(feeItem.getUpdateUser())); + return feeItemVO; } } diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/ContractManageController.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/ContractManageController.java index dd05fd4..bb3e04f 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/ContractManageController.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/ContractManageController.java @@ -81,8 +81,8 @@ public class ContractManageController extends BladeController { @PostMapping("/save-draft") @ApiOperationSupport(order = 3) @Operation(summary = "保存草稿", description = "传入contractManage") - public R saveDraft(@RequestBody ContractManage contractManage) { - return R.status(contractManageService.saveDraft(contractManage)); + public R saveDraft(@RequestBody ContractManage contractManage) { + return contractManageService.saveDraft(contractManage) ? R.data(contractManage) : R.fail("保存失败"); } @PostMapping("/submit") diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/ProjectApplyController.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/ProjectApplyController.java index c9993ec..37c633f 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/ProjectApplyController.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/ProjectApplyController.java @@ -126,8 +126,22 @@ public class ProjectApplyController extends BladeController { return R.status(projectApplyService.voidProject(id, reason)); } - @PostMapping("/start-change") + @PostMapping("/save-change") @ApiOperationSupport(order = 10) + @Operation(summary = "保存变更", description = "传入projectApply") + public R saveChange(@RequestBody ProjectApplyVO projectApply) { + return R.status(projectApplyService.saveChange(projectApply)); + } + + @PostMapping("/submit-change") + @ApiOperationSupport(order = 11) + @Operation(summary = "提交变更", description = "传入projectApply") + public R submitChange(@RequestBody ProjectApplyVO projectApply) { + return R.status(projectApplyService.submitChange(projectApply)); + } + + @PostMapping("/start-change") + @ApiOperationSupport(order = 12) @Operation(summary = "发起变更", description = "传入id、changeContent和changeReason") public R startChange(@Parameter(description = "主键", required = true) @RequestParam Long id, @RequestParam String changeContent, @@ -136,14 +150,14 @@ public class ProjectApplyController extends BladeController { } @PostMapping("/remove") - @ApiOperationSupport(order = 11) + @ApiOperationSupport(order = 13) @Operation(summary = "逻辑删除", description = "传入ids") public R remove(@Parameter(description = "主键集合", required = true) @RequestParam String ids) { return R.status(projectApplyService.removeDraft(ids)); } @GetMapping("/export-project-apply") - @ApiOperationSupport(order = 12) + @ApiOperationSupport(order = 14) @Operation(summary = "导出项目立项") public void exportProjectApply(ProjectApplyVO projectApply, @RequestParam(required = false) String ids, HttpServletResponse response) { List list = projectApplyService.exportProjectApply(projectApply, ids); diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/IProjectApplyService.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/IProjectApplyService.java index 5ea89ac..ee3cd16 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/IProjectApplyService.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/IProjectApplyService.java @@ -46,6 +46,8 @@ public interface IProjectApplyService extends BaseService { boolean reject(Long id); boolean withdraw(Long id); boolean voidProject(Long id, String reason); + boolean saveChange(ProjectApplyVO projectApply); + boolean submitChange(ProjectApplyVO projectApply); boolean startChange(Long id, String changeContent, String changeReason); boolean removeDraft(String ids); List exportProjectApply(ProjectApplyVO projectApply, String ids); diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/ContractManageServiceImpl.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/ContractManageServiceImpl.java index 628e94b..d24c239 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/ContractManageServiceImpl.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/ContractManageServiceImpl.java @@ -28,6 +28,7 @@ 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.secure.utils.AuthUtil; +import org.springblade.core.tool.jackson.JsonUtil; import org.springblade.core.tool.utils.BeanUtil; import org.springblade.core.tool.utils.Func; import org.springblade.system.cache.UserCache; @@ -43,6 +44,8 @@ import org.springframework.transaction.annotation.Transactional; import java.time.LocalDate; import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Objects; @@ -65,6 +68,8 @@ public class ContractManageServiceImpl extends BaseServiceImpl selectContractManagePage(IPage page, ContractManageVO contractManage) { @@ -111,6 +116,7 @@ public class ContractManageServiceImpl extends BaseServiceImpl queryWrapper = Wrappers.lambdaQuery().eq(ContractManage::getIsDeleted, 0); if (!Objects.equals(contractManage.getAllDept(), 1)) { - queryWrapper.eq(ContractManage::getOrganizationId, TransportBusinessSupport.currentDeptId("合同管理")); + Long currentDeptId = TransportBusinessSupport.currentDeptId("合同管理"); + Long currentUserId = AuthUtil.getUserId(); + queryWrapper.and(wrapper -> wrapper.eq(ContractManage::getOrganizationId, currentDeptId) + .or().eq(ContractManage::getCreateUser, currentUserId) + .or().eq(ContractManage::getHandlerUserId, currentUserId)); } else if (Func.isNotEmpty(contractManage.getOrganizationId())) { queryWrapper.eq(ContractManage::getOrganizationId, contractManage.getOrganizationId()); } @@ -299,8 +317,10 @@ public class ContractManageServiceImpl extends BaseServiceImpl> records = parseChangeRecords(contractManage.getChangeRecordJson()); + boolean exists = records.stream().anyMatch(record -> Objects.equals(record.get("changeType"), "创建")); + if (!exists) { + appendChangeRecord(contractManage, "创建", null, STATUS_REVIEWING, "审核中"); + } + } + + private void appendChangeRecord(ContractManage contractManage, String changeType, String changeReason, String status, String statusName) { + List> records = parseChangeRecords(contractManage.getChangeRecordJson()); + Map record = new LinkedHashMap<>(); + record.put("changeDate", LocalDate.now().toString()); + record.put("handlerUserId", AuthUtil.getUserId()); + record.put("handlerUserName", AuthUtil.getUserName()); + record.put("changeType", changeType); + record.put("changeReason", TransportBusinessSupport.trimToNull(changeReason)); + record.put("status", status); + record.put("statusName", statusName); + records.add(record); + contractManage.setChangeRecordJson(JsonUtil.toJson(records)); + } + + private void updateLatestReviewingChangeRecord(ContractManage contractManage, String status, String statusName) { + List> records = parseChangeRecords(contractManage.getChangeRecordJson()); + for (int index = records.size() - 1; index >= 0; index--) { + Map record = records.get(index); + if (Objects.equals(record.get("status"), STATUS_REVIEWING) || Objects.equals(record.get("status"), STATUS_CHANGE_REVIEWING)) { + record.put("status", status); + record.put("statusName", statusName); + contractManage.setChangeRecordJson(JsonUtil.toJson(records)); + return; + } + } + } + + @SuppressWarnings("unchecked") + private List> parseChangeRecords(String value) { + if (Func.isEmpty(value)) { + return new ArrayList<>(); + } + try { + Object records = JsonUtil.parse(value, List.class); + if (records instanceof List list) { + return (List>) (List) list; + } + } catch (Exception ignored) { + } + return new ArrayList<>(); + } + private String expireScope(LocalDate endDate) { long days = java.time.temporal.ChronoUnit.DAYS.between(LocalDate.now(), endDate); if (days < 0) { @@ -386,7 +459,15 @@ public class ContractManageServiceImpl extends BaseServiceImpl queryWrapper = Wrappers.lambdaQuery().eq(ProjectApply::getIsDeleted, 0); if (!Objects.equals(projectApply.getAllDept(), 1)) { - queryWrapper.eq(ProjectApply::getUndertakeDeptId, TransportBusinessSupport.currentDeptId("项目立项")); + Long currentDeptId = TransportBusinessSupport.currentDeptId("项目立项"); + Long currentUserId = AuthUtil.getUserId(); + queryWrapper.and(wrapper -> wrapper.eq(ProjectApply::getUndertakeDeptId, currentDeptId) + .or().eq(ProjectApply::getCreateUser, currentUserId) + .or().eq(ProjectApply::getHandlerUserId, currentUserId) + .or().eq(ProjectApply::getPrincipalUserId, currentUserId)); } else if (Func.isNotEmpty(projectApply.getUndertakeDeptId())) { queryWrapper.eq(ProjectApply::getUndertakeDeptId, projectApply.getUndertakeDeptId()); } @@ -307,6 +340,36 @@ public class ProjectApplyServiceImpl extends BaseServiceImplblade-desk blade-system blade-transport + blade-file + blade-openapi diff --git a/blade-third-party-api/blade-mk-api/pom.xml b/blade-third-party-api/blade-mk-api/pom.xml new file mode 100644 index 0000000..bcde445 --- /dev/null +++ b/blade-third-party-api/blade-mk-api/pom.xml @@ -0,0 +1,29 @@ + + + 4.0.0 + + org.springblade + blade-third-party-api + ${revision} + + + blade-mk-api + ${project.artifactId} + jar + + + + org.springblade + blade-starter-redis + + + + commons-io + commons-io + 2.16.1 + + + + diff --git a/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/config/MKFeignClientConfig.java b/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/config/MKFeignClientConfig.java new file mode 100644 index 0000000..309cc4f --- /dev/null +++ b/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/config/MKFeignClientConfig.java @@ -0,0 +1,17 @@ +package org.springblade.thirdparty.mk.config; + +import feign.RequestInterceptor; +import org.springframework.context.annotation.Bean; + +/** + * @author bfhuange + * @since 2024/12/18 + */ +public class MKFeignClientConfig { + @Bean + public RequestInterceptor requestInterceptor() { + return template -> { + // 空实现屏蔽 全局拦截器 BladeFeignRequestInterceptor + }; + } +} diff --git a/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/config/MKProperties.java b/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/config/MKProperties.java new file mode 100644 index 0000000..7b884c7 --- /dev/null +++ b/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/config/MKProperties.java @@ -0,0 +1,61 @@ +package org.springblade.thirdparty.mk.config; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + * mk配置类 + * @author bfhuange + * @date 2024/9/11 + */ +@Data +@ConfigurationProperties(prefix = "third-party.mk") +public class MKProperties { + /** + * 应用key mk-集成平台-本系统API网关-账号管理 + */ + private String appKey; + /** + * 应用secret mk-集成平台-本系统API网关-账号管理 + */ + private String appSecret; + /** + * mk基础url,接口调用地址,内网地址 + */ + private String baseUrl; + /** + * mk 登录地址,外网地址 + */ + private String loginUrl; + + //======================== mk 认证相关配置================================ + + /** + * oauth 应用id mk-系统管理-安全管理-认证服务 + */ + private String oauthAppId; + /** + * oauth 应用秘钥 mk-系统管理-安全管理-认证服务 + */ + private String oauthAppSecret; + /** + * mk 流程提交标题前缀 + */ + private String subjectPrefix; + /** + * mk 流程模板编码前缀 + */ + private String templateCodePrefix; + /** + * sso登录地址 + */ + private String mkSsoLoginUrl = "/data/sys-oauth/ssoLogin"; + /** + * 校验请求登录页面的来源地址, + */ + private boolean checkReferer = true; + /** + * erp系统基础链接,用于校验referer是否是erp的,仅在 checkReferer 为true生效 + */ + private String erpBaseUrls; +} diff --git a/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/config/ThirdPartyMKAutoConfiguration.java b/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/config/ThirdPartyMKAutoConfiguration.java new file mode 100644 index 0000000..6cc5a72 --- /dev/null +++ b/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/config/ThirdPartyMKAutoConfiguration.java @@ -0,0 +1,16 @@ +package org.springblade.thirdparty.mk.config; + +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; + +/** + * 第三方api mk配置类 + * @author bfhuange + * @date 2024/9/11 + */ +@ComponentScan("org.springblade.thirdparty.mk") +@EnableConfigurationProperties(MKProperties.class) +@Configuration +public class ThirdPartyMKAutoConfiguration { +} diff --git a/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/constant/MKConstant.java b/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/constant/MKConstant.java new file mode 100644 index 0000000..7baee0d --- /dev/null +++ b/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/constant/MKConstant.java @@ -0,0 +1,73 @@ +package org.springblade.thirdparty.mk.constant; + +/** + * mk常量类 + * @author bfhuange + * @date 2024/9/11 + */ +public class MKConstant { + /** + * 获取token 成功状态码 + */ + public static final int TOKEN_SUCCESS_CODE = 0; + + /** + * mk token缓存key + */ + public static final String MK_TOKEN_KEY = "blade:third:mk:token"; + + /** + * 组织架构类型: org-机构 + */ + public static final String ORG_TYPE_ORG = "org"; + /** + * 组织架构类型: dept-部门 + */ + public static final String ORG_TYPE_DEPT = "dept"; + /** + * 性别 男性 M表示男性,F表示女性,UN-未知 + */ + public static final String SEX_MAN = "M"; + /** + * 性别 女性 M表示男性,F表示女性,UN-未知 + */ + public static final String SEX_WOMAN = "F"; + /** + * 性别 未知 M表示男性,F表示女性,UN-未知 + */ + public static final String SEX_UNKONW = "UN"; + /** + * 操作类型 起草人撤销 + */ + public static final String OPERATE_TYPE_REVOKE = "drafter_retract"; + /** + * 操作类型 起草人废弃 + */ + public static final String OPERATE_TYPE_ABANDON = "drafter_abandon"; + /** + * 起草节点id + */ + public static final String DAFTER_NODE_ID = "N2"; + /** + * 抄送节点 + */ + public static final String NODE_TYPE_SEND = "send"; + /** + * 电子签名类型 + */ + public static final String FILE_TYPE_SIGN = "electronicSign"; + + /** + * 排序 + */ + public static class Sort { + /** + * 升序 + */ + public static final String ASC = "asc"; + /** + * 降序 + */ + public static final String DESC = "desc"; + } +} diff --git a/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/constant/MKDoc.java b/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/constant/MKDoc.java new file mode 100644 index 0000000..5ca4d16 --- /dev/null +++ b/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/constant/MKDoc.java @@ -0,0 +1,56 @@ +package org.springblade.thirdparty.mk.constant; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 文档类型 + * myDraft 我的起草 + * myCreated 我发起的 + * myCreatedAndDraft 我发起的和我起草的 + * myApproving 我的待审 + * myApproved 我的已审 + * myReading 我的待阅 + * myReaded 我的已阅 + * myFocus 我关注的 + * myAll 我所有的( 含我发起的,我审批的,待我审的) + * @author bfhuange + * @since 2025/4/2 + */ +@AllArgsConstructor +@Getter +public enum MKDoc { + /** + * 我的待审 + */ + APPROVING("myApproving", "我的待审"), + /** + * 我的已审 + */ + APPROVED("myApproved", "我的已审"), + /** + * 我的待阅 + */ + READING("myReading", "我的待阅"), + /** + * 我的已阅 + */ + READ("myReaded", "我的已阅"), + /** + * 我发起的 + */ + CREATE("myCreated", "我发起的"), + /** + * 我参与的 + */ + RELATED("myRelated", "我参与的"), + ; + /** + * 编码 + */ + private final String code; + /** + * 名称 + */ + private final String name; +} diff --git a/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/exception/MKException.java b/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/exception/MKException.java new file mode 100644 index 0000000..b693ea2 --- /dev/null +++ b/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/exception/MKException.java @@ -0,0 +1,17 @@ +package org.springblade.thirdparty.mk.exception; + +/** + * mk 接口异常 + * @author bfhuange + * @date 2024/9/13 + */ +public class MKException extends RuntimeException { + + public MKException(String message) { + super(message); + } + + public MKException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/exception/MKExceptionTranslator.java b/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/exception/MKExceptionTranslator.java new file mode 100644 index 0000000..6f4280f --- /dev/null +++ b/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/exception/MKExceptionTranslator.java @@ -0,0 +1,28 @@ +package org.springblade.thirdparty.mk.exception; + +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springblade.core.tool.api.R; +import org.springblade.core.tool.api.ResultCode; +import org.springblade.core.tool.utils.Func; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +/** + * @author bfhuange + * @since 2025/3/29 + */ +@AllArgsConstructor +@Slf4j +@RestControllerAdvice +public class MKExceptionTranslator { + + @ExceptionHandler(MKException.class) + @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) + public R handleError(MKException e) { + log.error("mk异常", e); + return R.fail(ResultCode.INTERNAL_SERVER_ERROR, (Func.isEmpty(e.getMessage()) ? ResultCode.INTERNAL_SERVER_ERROR.getMessage() : e.getMessage())); + } +} diff --git a/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/feign/IMKClient.java b/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/feign/IMKClient.java new file mode 100644 index 0000000..75dbed4 --- /dev/null +++ b/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/feign/IMKClient.java @@ -0,0 +1,170 @@ +package org.springblade.thirdparty.mk.feign; + +import feign.Response; +import org.springblade.thirdparty.mk.config.MKFeignClientConfig; +import org.springblade.thirdparty.mk.pojo.dto.*; +import org.springblade.thirdparty.mk.pojo.dto.approval.MKApprovalDTO; +import org.springblade.thirdparty.mk.pojo.dto.approval.MKProcessDTO; +import org.springblade.thirdparty.mk.pojo.vo.*; +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestParam; + +import java.util.List; + +/** + * MK接口 + * @author bfhuange + * @date 2024/9/11 + */ +@FeignClient(name = "MK", url = "${thirdParty.mk.baseUrl}", configuration = MKFeignClientConfig.class) +public interface IMKClient { + + /** + * 获取mk token + * @param param + * @return + */ + @PostMapping("${thirdParty.mk.getTokenUrl:/authapi/getToken}") + MKTokenVO getToken(@RequestBody MKTokenDTO param); + + /** + * 流程提交接口 + * @param param + * @param accessToken + * @return + */ + @PostMapping("${thirdParty.mk.processSubmitUrl:/openapi/sys-lbpm/sysLbpmProcess/openSupport/submit}") + MKResultVO processSubmit(@RequestBody MKProcessCreateDTO param, @RequestParam("access_token") String accessToken); + + /** + * 流程执行接口 + * @param param + * @param accessToken + * @return + */ + @PostMapping("${thirdParty.mk.processExecuteUrl:/openapi/sys-lbpm/sysLbpmProcess/openSupport/execute}") + MKResultVO processExecute(@RequestBody MKProcessExecuteDTO param, @RequestParam("access_token") String accessToken); + + /** + * 流程删除接口 + * @param param + * @param accessToken + * @return + */ + @PostMapping("${thirdParty.mk.processDeleteUrl:/openapi/sys-lbpm/sysLbpmProcess/openSupport/delete}") + MKResultVO processDelete(@RequestBody MKProcessDeleteDTO param, @RequestParam("access_token") String accessToken); + + /** + * 获取流程当前节点详情 + * @param param + * @param accessToken + * @return + */ + @PostMapping("${thirdParty.mk.getCurrentNodesUrl:/openapi/sys-lbpm/sysLbpmProcess/openSupport/getCurNodesInfo}") + MKResultVO> getCurrentNodes(@RequestBody MKCurrentNodesDTO param, @RequestParam("access_token") String accessToken); + + /** + * 获取节点处理人信息 + * @param param + * @param accessToken + * @return + */ + @PostMapping("${thirdParty.mk.getNodeHandlersUrl:/openapi/sys-lbpm/sysLbpmProcess/openSupport/getNodeHandlerInfos}") + MKResultVO getNodeHandlers(@RequestBody MKNodeHandlersDTO param, @RequestParam("access_token") String accessToken); + + /** + * 根据流程模板获取该模板下所有的人工节点信息 + * @param param + * @param accessToken + * @return + */ + @PostMapping("${thirdParty.mk.getManualNodeUrl:/openapi/sys-lbpm/sysLbpmTemplate/openSupport/getManualNode}") + MKResultVO> getManualNode(@RequestBody MKManualNodeDTO param, @RequestParam("access_token") String accessToken); + + //===========================人员组织推送接口============================================ + + /** + * 推送公司部门 + * @param param + * @param accessToken + * @return + */ + @PostMapping("${thirdParty.mk.pushOrgDeptUrl:/openapi/sys-org/v2/push/orgDept}") + MKResultVO pushOrgDept(@RequestBody MKPushDTO param, @RequestParam("access_token") String accessToken); + + /** + * 推送人员 + * @param param + * @param accessToken + * @return + */ + @PostMapping("${thirdParty.mk.pushPersonUrl:/openapi/sys-org/v2/push/person}") + MKResultVO pushPerson(@RequestBody MKPushDTO param, @RequestParam("access_token") String accessToken); + + //===========================认证相关接口======================================== + + /** + * 获取oauth 认证code + * @param param + */ + @PostMapping("${thirdParty.mk.getOAuthCodeUrl:/openapi/sys-oauth/openOauth/getCode}") + MKResultVO getOAuthCode(@RequestBody MKOAuthCodeDTO param, @RequestParam("access_token") String accessToken); + + /** + * 获取oauth token + * @param param + */ + @PostMapping("${thirdParty.mk.getOAuthTokenUrl:/openapi/sys-oauth/openOauth/getToken}") + MKResultVO getOAuthToken(@RequestBody MKOAuthTokenDTO param, @RequestParam("access_token") String accessToken); + + /** + * 获取oauth 用户信息 + * @param param + * @return + */ + @PostMapping("${thirdParty.mk.getOAuthUserInfoUrl:/openapi/sys-oauth/openOauth/getUserInfo}") + MKResultVO getOAuthUserInfo(@RequestBody MKOAuthUserDTO param, @RequestParam("access_token") String accessToken); + + //===========================审批记录相关接口======================================== + /** + * 流程审批意见信息API - 获取审批意见接口 + * @param accessToken + * @return + */ + @PostMapping("${thirdParty.mk.queryAuditNotesUrl:/openapi/sys-lbpm/lbpmAuditNote/openSupport/listNote}") + MKResultVO> queryAuditNotes(@RequestBody MKAuditNoteDTO param, @RequestParam("access_token") String accessToken); + + /** + * 流程卡片操作API- 获取抄送人列表 + * @param accessToken + * @return + */ + @PostMapping("${thirdParty.mk.querySenderListUrl:/openapi/sys-lbpm/sysLbpmProcessCard/openSupport/getSenderList}") + MKResultVO> querySenderList(@RequestBody MKSenderDTO param, @RequestParam("access_token") String accessToken); + + /** + * 附件文件流操作开放API- 附件下载 + * @param accessToken + * @return + */ + @PostMapping("${thirdParty.mk.downloadFileUrl:/openapi/sys-attach/fileStream/download}") + Response downloadFile(@RequestBody MKFileDTO param, @RequestParam("access_token") String accessToken); + + //===========================待办相关接口======================================== + /** + * 审批中心-流程门户API-审批中心查询列表 + * @param accessToken + * @return + */ + @PostMapping("${thirdParty.mk.queryApprovalListUrl:/openapi/lbpm-approval/lbpmApproval/portal/list}") + MKResultVO> queryApprovalList(@RequestBody MKApprovalDTO param, @RequestParam("access_token") String accessToken); + /** + * 流程实例操作API-列表 + * @param accessToken + * @return + */ + @PostMapping("${thirdParty.mk.queryProcessListUrl:/openapi/sys-lbpm/sysLbpmProcess/openSupport/list}") + MKResultVO> queryProcessList(@RequestBody MKProcessDTO param, @RequestParam("access_token") String accessToken); +} diff --git a/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/pojo/dto/MKAdditionOperationParameterDTO.java b/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/pojo/dto/MKAdditionOperationParameterDTO.java new file mode 100644 index 0000000..f8deac1 --- /dev/null +++ b/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/pojo/dto/MKAdditionOperationParameterDTO.java @@ -0,0 +1,32 @@ +package org.springblade.thirdparty.mk.pojo.dto; + +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 附加操作信息 + * + * @author linbb + */ +@Data +public class MKAdditionOperationParameterDTO implements Serializable { + @Serial + private static final long serialVersionUID = 1L; + + /** + * 操作类型 + */ + private String operationType; + + /** + * 操作身份 + */ + private String operationIdentity; + + /** + * 操作参数 + */ + private String parameter; +} diff --git a/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/pojo/dto/MKAuditNoteDTO.java b/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/pojo/dto/MKAuditNoteDTO.java new file mode 100644 index 0000000..7796293 --- /dev/null +++ b/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/pojo/dto/MKAuditNoteDTO.java @@ -0,0 +1,26 @@ +package org.springblade.thirdparty.mk.pojo.dto; + +import lombok.AllArgsConstructor; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; + +/** + * @author bfhuange + * @since 2025/2/24 + */ +@AllArgsConstructor +@Data +public class MKAuditNoteDTO implements Serializable { + @Serial + private static final long serialVersionUID = 1L; + /** + * 登录账号 登录用户账号/手机号 + */ + private String loginName; + /** + * 流程实例id + */ + private String processInstanceId; +} diff --git a/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/pojo/dto/MKAuxiliaryDeptDTO.java b/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/pojo/dto/MKAuxiliaryDeptDTO.java new file mode 100644 index 0000000..a36a014 --- /dev/null +++ b/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/pojo/dto/MKAuxiliaryDeptDTO.java @@ -0,0 +1,25 @@ +package org.springblade.thirdparty.mk.pojo.dto; + +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; + +/** + * mk 辅部门参数 + * @author bfhuange + * @date 2024/9/24 + */ +@Data +public class MKAuxiliaryDeptDTO implements Serializable { + @Serial + private static final long serialVersionUID = 1L; + /** + * 对象主键 + */ + private String fdId; + /** + * 对象显示名 + */ + private String fdName; +} diff --git a/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/pojo/dto/MKCurrentNodesDTO.java b/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/pojo/dto/MKCurrentNodesDTO.java new file mode 100644 index 0000000..302ed0a --- /dev/null +++ b/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/pojo/dto/MKCurrentNodesDTO.java @@ -0,0 +1,24 @@ +package org.springblade.thirdparty.mk.pojo.dto; + +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; + +/** + * @author bfhuange + * @date 2024/9/19 + */ +@Data +public class MKCurrentNodesDTO implements Serializable { + @Serial + private static final long serialVersionUID = 1L; + /** + * 流程实例id + */ + private String processInstanceId; + /** + * 登录名 + */ + private String loginName; +} diff --git a/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/pojo/dto/MKFileDTO.java b/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/pojo/dto/MKFileDTO.java new file mode 100644 index 0000000..f6106d3 --- /dev/null +++ b/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/pojo/dto/MKFileDTO.java @@ -0,0 +1,24 @@ +package org.springblade.thirdparty.mk.pojo.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; + +/** + * @author bfhuange + * @since 2025/2/24 + */ +@AllArgsConstructor +@Data +public class MKFileDTO implements Serializable { + @Serial + private static final long serialVersionUID = 1L; + /** + * 附件id + */ + @JsonProperty("fdId") + private String fileId; +} diff --git a/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/pojo/dto/MKManualNodeDTO.java b/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/pojo/dto/MKManualNodeDTO.java new file mode 100644 index 0000000..0b8223e --- /dev/null +++ b/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/pojo/dto/MKManualNodeDTO.java @@ -0,0 +1,28 @@ +package org.springblade.thirdparty.mk.pojo.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 人工节点查询参数 + * @author bfhuange + * @since 2025/3/21 + */ +@AllArgsConstructor +@NoArgsConstructor +@Data +public class MKManualNodeDTO implements Serializable { + @Serial + private static final long serialVersionUID = 1L; + + /** + * 模板id + */ + @JsonProperty("fdId") + private String templateId; +} diff --git a/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/pojo/dto/MKNodeHandlersDTO.java b/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/pojo/dto/MKNodeHandlersDTO.java new file mode 100644 index 0000000..d099c96 --- /dev/null +++ b/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/pojo/dto/MKNodeHandlersDTO.java @@ -0,0 +1,28 @@ +package org.springblade.thirdparty.mk.pojo.dto; + +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; + +/** + * @author bfhuange + * @date 2024/9/19 + */ +@Data +public class MKNodeHandlersDTO implements Serializable { + @Serial + private static final long serialVersionUID = 1L; + /** + * 流程实例id + */ + private String processInstanceId; + /** + * 登录名 + */ + private String loginName; + /** + * 节点(定义)ID + */ + private String nodeId; +} diff --git a/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/pojo/dto/MKOAuthCodeDTO.java b/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/pojo/dto/MKOAuthCodeDTO.java new file mode 100644 index 0000000..da449cb --- /dev/null +++ b/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/pojo/dto/MKOAuthCodeDTO.java @@ -0,0 +1,29 @@ +package org.springblade.thirdparty.mk.pojo.dto; + +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 获取oauth 认证code参数 + * @author bfhuange + * @date 2024/9/27 + */ +@Data +public class MKOAuthCodeDTO implements Serializable { + @Serial + private static final long serialVersionUID = 1L; + /** + * 申请的应⽤Id 认证 + */ + private String appId; + /** + * 应⽤秘钥 + */ + private String appSecret; + /** + * 登录名 + */ + private String loginName; +} diff --git a/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/pojo/dto/MKOAuthTokenDTO.java b/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/pojo/dto/MKOAuthTokenDTO.java new file mode 100644 index 0000000..1a9976c --- /dev/null +++ b/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/pojo/dto/MKOAuthTokenDTO.java @@ -0,0 +1,29 @@ +package org.springblade.thirdparty.mk.pojo.dto; + +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 获取oauth token参数 + * @author bfhuange + * @date 2024/9/14 + */ +@Data +public class MKOAuthTokenDTO implements Serializable { + @Serial + private static final long serialVersionUID = 1L; + /** + * 申请的应⽤Id 认证 + */ + private String appId; + /** + * 应⽤秘钥 + */ + private String appSecret; + /** + * 授权code,code使⽤⼀次之后失效 + */ + private String code; +} diff --git a/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/pojo/dto/MKOAuthUserDTO.java b/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/pojo/dto/MKOAuthUserDTO.java new file mode 100644 index 0000000..5829717 --- /dev/null +++ b/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/pojo/dto/MKOAuthUserDTO.java @@ -0,0 +1,23 @@ +package org.springblade.thirdparty.mk.pojo.dto; + +import lombok.AllArgsConstructor; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; + +/** + * mk oauth 获取用户信息参数 + * @author bfhuange + * @date 2024/9/14 + */ +@AllArgsConstructor +@Data +public class MKOAuthUserDTO implements Serializable { + @Serial + private static final long serialVersionUID = 1L; + /** + * token + */ + private String token; +} diff --git a/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/pojo/dto/MKPageDTO.java b/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/pojo/dto/MKPageDTO.java new file mode 100644 index 0000000..9f1bdb4 --- /dev/null +++ b/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/pojo/dto/MKPageDTO.java @@ -0,0 +1,25 @@ +package org.springblade.thirdparty.mk.pojo.dto; + +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; + +/** + * mk 分页参数 + * @author bfhuange + * @since 2025/2/24 + */ +@Data +public class MKPageDTO implements Serializable { + @Serial + private static final long serialVersionUID = 1L; + /** + * 从第?条开始查询,起始值为0 + */ + private Integer offset = 0; + /** + * 每页?条 + */ + private Integer pageSize = 20; +} diff --git a/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/pojo/dto/MKProcessCreateDTO.java b/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/pojo/dto/MKProcessCreateDTO.java new file mode 100644 index 0000000..8f7b9c1 --- /dev/null +++ b/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/pojo/dto/MKProcessCreateDTO.java @@ -0,0 +1,47 @@ +package org.springblade.thirdparty.mk.pojo.dto; + +import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; + +/** + * mk 流程创建参数 + * @author bfhuange + * @date 2024/9/11 + */ +@Data +public class MKProcessCreateDTO implements Serializable { + @Serial + private static final long serialVersionUID = 1L; + /** + * 表单实例id,业务id + */ + private String formInstanceId; + /** + * 标题 + */ + private String subject; + /** + * 提交人 登录用户账号/手机号 + */ + private String submitIdentity; + /** + * 登录账号 登录用户账号/手机号 + */ + private String loginName; + /** + * 模板编码 ProcessTypeEnum#templateCode + */ + private String templateCode; + /** + * 默认表单值 + */ + private Object formValues; + /** + * 临时变量 + */ + @JsonInclude(JsonInclude.Include.NON_NULL) + private Object tempVarData; +} diff --git a/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/pojo/dto/MKProcessDeleteDTO.java b/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/pojo/dto/MKProcessDeleteDTO.java new file mode 100644 index 0000000..3233094 --- /dev/null +++ b/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/pojo/dto/MKProcessDeleteDTO.java @@ -0,0 +1,25 @@ +package org.springblade.thirdparty.mk.pojo.dto; + +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; + +/** + * mk 流程删除参数 + * @author bfhuange + * @since 2024/11/26 + */ +@Data +public class MKProcessDeleteDTO implements Serializable { + @Serial + private static final long serialVersionUID = 1L; + /** + * 登录账号 登录用户账号/手机号 + */ + private String loginName; + /** + * 流程实例id + */ + private String processInstanceId; +} diff --git a/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/pojo/dto/MKProcessExecuteDTO.java b/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/pojo/dto/MKProcessExecuteDTO.java new file mode 100644 index 0000000..68997b7 --- /dev/null +++ b/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/pojo/dto/MKProcessExecuteDTO.java @@ -0,0 +1,79 @@ +package org.springblade.thirdparty.mk.pojo.dto; + +import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; +import java.util.List; + +/** + * mk 流程执行参数 + * @author bfhuange + * @date 2024/9/26 + */ +@Data +public class MKProcessExecuteDTO implements Serializable { + @Serial + private static final long serialVersionUID = 1L; + /** + * 表单实例id,业务id + */ + private String formInstanceId; + /** + * 登录账号 登录用户账号/手机号 + */ + private String loginName; + /** + * 操作类型 + */ + private String operationType; + + //==========================mk 流程执行需要的参数,前端透传========================= + /** + * 流程标题 + */ + private String subject; + /** + * 任务ID + */ + private String taskId; + /** + * 任务类型 + */ + private String activityType; + /** + * 操作详细参数(json) + */ + private String parameter; + /** + * 流程实例ID + */ + private String processId; + /** + * 操作标识(相同操作类型和相同操作身份可能存在多个操作配置) + */ + private String operationId; + /** + * 操作身份 + */ + private String operationIdentity; + /** + * 附加操作参数信息 + */ + private List additionParameters; + /** + * 表单实例Model Name + */ + private String formInstanceModel; + /** + * 业务表单字段值集合 + */ + // private Map formValues; + private Object formValues; + /** + * 临时变量 + */ + @JsonInclude(JsonInclude.Include.NON_NULL) + private Object tempVarData; +} diff --git a/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/pojo/dto/MKPushDTO.java b/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/pojo/dto/MKPushDTO.java new file mode 100644 index 0000000..24f217a --- /dev/null +++ b/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/pojo/dto/MKPushDTO.java @@ -0,0 +1,30 @@ +package org.springblade.thirdparty.mk.pojo.dto; + +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; +import java.util.List; + +/** + * mk 推送参数 + * @author bfhuange + * @date 2024/9/24 + */ +@Data +public class MKPushDTO implements Serializable { + @Serial + private static final long serialVersionUID = 1L; + /** + * 如果不传,默认一次性推送,mk这边生成批次,如果一定量数据分页推送,则第三方生成批次号,批次号唯一 + */ + private String batchId; + /** + * 结束标识 : true-当前推送完毕,false-还没推送完成。一次性推送的话,就传 true + */ + private boolean finish = true; + /** + * 推送的数据主体 + */ + private List message; +} diff --git a/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/pojo/dto/MKPushOrgDeptDTO.java b/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/pojo/dto/MKPushOrgDeptDTO.java new file mode 100644 index 0000000..72657ec --- /dev/null +++ b/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/pojo/dto/MKPushOrgDeptDTO.java @@ -0,0 +1,144 @@ +package org.springblade.thirdparty.mk.pojo.dto; + +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; +import java.util.List; + +/** + * mk 推送机构部门参数 + * @author bfhuange + * @date 2024/9/24 + */ +@Data +public class MKPushOrgDeptDTO implements Serializable { + @Serial + private static final long serialVersionUID = 1L; + //=================必填项开始================ + /** + * 唯一标识 必填,不能保证唯一的情况下,建议id前面拼接上组织类型。 例:org:1111,dept:22222,person:333333,group:555555,post:6666666 进阶情况,组织来源多个不同系统,还可以拼接上来源系统唯一id + */ + private String fdId; + /** + * 是否有效,该属性决定该组织架构是否删除 必填 + */ + private Boolean fdIsAvailable; + /** + * 名称 必填 + */ + private String fdName; + /** + * 组织架构类型: org-机构,dept-部门 + */ + private String fdOrgType; + /** + * 来源系统标识:例如:EKP + */ + private String fdSourceSystem; + //=================必填项结束================ + + /** + * 所在部门-主部门,只能一个,有值情况:当type为org,dept,post,person时,为空表示在根节点 + */ + private String fdParent; + /** + * 编号 + */ + private String fdNo; + /** + * 排序号 + */ + private Integer fdOrder; + + /** + * 映射MK的fdId,用来和Mk数据进行绑定 + */ + private String fdElementId; + /** + * 指定同步到mk的哪个节点下,有parent则以parent为准,没有则以mkParent为准。只能选择机构和部门,同步对象如果是机构,则mkParent也只能是机构,机构不能挂部门下 + */ + private String mkParent; + /** + * 部门领导,有值情况:当type为org,dept,post + */ + private String fdThisLeader; + /** + * 置空属性:字段传空过来,不想Mk这边也更新为空?可以指定这个属性。包含的字段如果为空则会被置为空,不包含的如果为空则不会更新。例如:["fdName","fdNo","fdEmail"] + */ + private List nullValueProps; + + //=======================一般不用的字段开始========================= + /** + * 机构简称 + */ + private String fdAbbreviation; + /** + * 地址1 + */ + private String fdAddress1; + /** + * 地址2 + */ + private String fdAddress2; + /** + * 管理员,例:["113","43424",..] + */ + private List fdAdmins; + /** + * 营业执照编号 + */ + private String fdBusinessLicenseNo; + /** + * 群组分类id,有值情况:当type为group时 + */ + private String fdCategoryName; + /** + * 扩展属性,未定义的字段并且需要同步的可以传到此json对象中,同步前需要去后台管理增加对应key值的扩展字段。例:{"birthDate":"1900‐01‐01","education":"本科","isSocialRecruitment":"true"} + */ + private Object fdCustomProps; + /** + * 说明 + */ + private String fdDesc; + /** + * 外部组织显示位置:默认情况是内部组织都在行政组织树,外部组织都在生态组织树。组织架构开启一体化后,外部组织既可以出现在行政树下面也可以出现在生态组织树下。但行政里面的外部组织必须要有顶级机构。是否显示在生态组织目录:true/null-显示在生态组织目录,false-显示在行政组织 + */ + private Boolean fdEosShowType; + /** + * 外部组织分类code:MK有外部组织分类,需要提前在管理端建好 + */ + private String fdExternalCode; + /** + * 是否外部组织:默认不填表示内部组织,true表示外部组织,外部组织需要externalCode + */ + private Boolean fdIsExternal; + /** + * 关键字 + */ + private String fdKeyword; + /** + * 多语言字段:例:{"fdNameUs":"Tom","fdNameCn":"张三"} + */ + private Object fdLangProps; + /** + * 是否法人 + */ + private Boolean fdLegalPerson; + /** + * 上级领导,有值情况:当type为org,dept + */ + private String fdSuperLeader; + /** + * 多维分类编号,当fdOrgType为org(机构),dept(部门) 时生效,表示当前组织/部门是该多维分类下的顶层组织。注:后续不允许变更。 + */ + private String fdVirtualizeNo; + /** + * 可见性:fdVisibleType:1-仅自己 2-所在组织同级及下级 3-指定组织/人员:例: {"fdVisibleType":1,"visibleOrgList":[{"fdId":"1feiglp6sw132weiw37cucsm1kqoe29hu4w0","fdName":"刘芬"}]} + */ + private Object fdVisible; + /** + * 机构网站地址 + */ + private String fdWebAddress; +} diff --git a/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/pojo/dto/MKPushPersonDTO.java b/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/pojo/dto/MKPushPersonDTO.java new file mode 100644 index 0000000..236f5de --- /dev/null +++ b/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/pojo/dto/MKPushPersonDTO.java @@ -0,0 +1,167 @@ +package org.springblade.thirdparty.mk.pojo.dto; + +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; +import java.util.List; + +/** + * mk 推送人员参数 + * @author bfhuange + * @date 2024/9/24 + */ +@Data +public class MKPushPersonDTO implements Serializable { + @Serial + private static final long serialVersionUID = 1L; + //=================必填项开始================ + /** + * 唯一标识 必填,不能保证唯一的情况下,建议id前面拼接上组织类型。 例:org:1111,dept:22222,person:333333,group:555555,post:6666666 进阶情况,组织来源多个不同系统,还可以拼接上来源系统唯一id + */ + private String fdId; + /** + * 是否有效,该属性决定该组织架构是否删除 必填 + */ + private Boolean fdIsAvailable; + /** + * 组织类型为用户时,登录名,有此信息且必填 + */ + private String fdLoginName; + /** + * 名称 必填 + */ + private String fdName; + /** + * 来源系统标识:例如:EKP + */ + private String fdSourceSystem; + //=================必填项结束================ + + /** + * 辅部门,可以多个,只有用户才传此值,不能和主部门重复:例:[{"fdId":"222222222","fdName":"A部门"},{"fdId":"333333333","fdName":"B部门"}] + */ + private List fdAuxiliaryDept; + /** + * 映射MK的fdId,用来和Mk数据进行绑定 + */ + private String fdElementId; + /** + * 手机号 + */ + private String fdMobileNo; + /** + * 编号 + */ + private String fdNo; + /** + * 所在部门-主部门,只能一个,有值情况:当type为org,dept,post,person时,为空表示在根节点 + */ + private String fdParent; + /** + * 排序号 + */ + private Integer fdOrder; + /** + * 密码,Md5加密后的信息,可以为空 + */ + private String fdPassword; + /** + * 性别,M表示男性,F表示女性,UN-未知 + */ + private String fdSex; + /** + * 指定同步到mk的哪个节点下,有parent则以parent为准,没有则以mkParent为准。只能选择机构和部门,同步对象如果是机构,则mkParent也只能是机构,机构不能挂部门下 + */ + private String mkParent; + /** + * 置空属性:字段传空过来,不想Mk这边也更新为空?可以指定这个属性。包含的字段如果为空则会被置为空,不包含的如果为空则不会更新。例如:["fdName","fdNo","fdEmail"] + */ + private List nullValueProps; + + //=======================一般不用的字段开始========================= + /** + * 群组分类id,有值情况:当type为group时 + */ + private String fdCategoryName; + /** + * 扩展属性,未定义的字段并且需要同步的可以传到此json对象中,同步前需要去后台管理增加对应key值的扩展字段。例:{"birthDate":"1900‐01‐01","education":"本科","isSocialRecruitment":"true"} + */ + private Object fdCustomProps; + /** + * 默认语言 + */ + private String fdDefaultLang; + /** + * 说明 + */ + private String fdDesc; + /** + * 邮件地址 + */ + private String fdEmail; + /** + * 外部组织显示位置:默认情况是内部组织都在行政组织树,外部组织都在生态组织树。组织架构开启一体化后,外部组织既可以出现在行政树下面也可以出现在生态组织树下。但行政里面的外部组织必须要有顶级机构。是否显示在生态组织目录:true/null-显示在生态组织目录,false-显示在行政组织 + */ + private Boolean fdEosShowType; + /** + * 外部组织分类code:MK有外部组织分类,需要提前在管理端建好 + */ + private String fdExternalCode; + /** + * 是否外部组织:默认不填表示内部组织,true表示外部组织,外部组织需要externalCode + */ + private Boolean fdIsExternal; + /** + * 关键字 + */ + private String fdKeyword; + /** + * 多语言字段:例:{"fdNameUs":"Tom","fdNameCn":"张三"} + */ + private Object fdLangProps; + /** + * 职级名称 + */ + private String fdLevelName; + /** + * 职级oriId + */ + private String fdLevelOriId; + /** + * 主岗,只有一个 + */ + private String fdMainPost; + /** + * 昵称 + */ + private String fdNickName; + /** + * 辅岗,可以多个,例:["cbda73","bac67843"..] + */ + private List fdPosts; + /** + * 短号 + */ + private String fdShortNo; + /** + * 职务名称 + */ + private String fdStaffingLevelName; + /** + * 职务oriId + */ + private String fdStaffingLevelOriId; + /** + * 多维分类编号,当fdOrgType为org(机构),dept(部门) 时生效,表示当前组织/部门是该多维分类下的顶层组织。注:后续不允许变更。 + */ + private String fdVirtualizeNo; + /** + * 可见性:fdVisibleType:1-仅自己 2-所在组织同级及下级 3-指定组织/人员:例: {"fdVisibleType":1,"visibleOrgList":[{"fdId":"1feiglp6sw132weiw37cucsm1kqoe29hu4w0","fdName":"刘芬"}]} + */ + private Object fdVisible; + /** + * 办公电话 + */ + private String fdWorkPhone; +} diff --git a/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/pojo/dto/MKSenderDTO.java b/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/pojo/dto/MKSenderDTO.java new file mode 100644 index 0000000..b99a58d --- /dev/null +++ b/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/pojo/dto/MKSenderDTO.java @@ -0,0 +1,26 @@ +package org.springblade.thirdparty.mk.pojo.dto; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * @author bfhuange + * @since 2025/2/24 + */ +@EqualsAndHashCode(callSuper = true) +@Data +public class MKSenderDTO extends MKPageDTO { + /** + * 流程实例id + */ + private String processInstanceId; + /** + * 节点实例id + */ + private String nodeInstanceId; + + public MKSenderDTO(String processInstanceId, String nodeInstanceId) { + this.processInstanceId = processInstanceId; + this.nodeInstanceId = nodeInstanceId; + } +} diff --git a/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/pojo/dto/MKTokenDTO.java b/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/pojo/dto/MKTokenDTO.java new file mode 100644 index 0000000..306ea1a --- /dev/null +++ b/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/pojo/dto/MKTokenDTO.java @@ -0,0 +1,25 @@ +package org.springblade.thirdparty.mk.pojo.dto; + +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 获取token参数 + * @author bfhuange + * @date 2024/9/11 + */ +@Data +public class MKTokenDTO implements Serializable { + @Serial + private static final long serialVersionUID = 1L; + /** + * 应用key + */ + private String appKey; + /** + * 应用secret + */ + private String appSecret; +} diff --git a/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/pojo/dto/approval/MKApprovalConditionDTO.java b/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/pojo/dto/approval/MKApprovalConditionDTO.java new file mode 100644 index 0000000..2c12e20 --- /dev/null +++ b/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/pojo/dto/approval/MKApprovalConditionDTO.java @@ -0,0 +1,86 @@ +package org.springblade.thirdparty.mk.pojo.dto.approval; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import lombok.Data; +import org.springblade.thirdparty.mk.support.MultiKeySerializer; + +import java.io.Serial; +import java.io.Serializable; +import java.util.List; + +/** + * mk审批中心查询条件 + * @author bfhuange + * @since 2025/4/2 + */ +@Data +public class MKApprovalConditionDTO implements Serializable { + @Serial + private static final long serialVersionUID = 1L; + /** + * 单据类型 + */ + private MKConditionDTO mydoc; + /** + * 关键字 + */ + @JsonSerialize(using = MultiKeySerializer.class) + @JsonInclude(JsonInclude.Include.NON_EMPTY) + private List keyword; + /** + * 申请时间 + */ + @JsonProperty("fdStartTime") + @JsonInclude(JsonInclude.Include.NON_NULL) + private MKConditionDTO startTime; + /** + * 接收时间 + */ + @JsonProperty("fdReceiveTime") + @JsonInclude(JsonInclude.Include.NON_NULL) + private MKConditionDTO receiveTime; + /** + * 模板名称 + */ + @JsonProperty("fdTemplateName") + @JsonInclude(JsonInclude.Include.NON_NULL) + private MKConditionDTO templateName; + + //======================已处理参数================================ + /** + * 流程状态 + */ + @JsonProperty("fdStatus") + @JsonInclude(JsonInclude.Include.NON_NULL) + private MKConditionDTO status; + /** + * 任务结束时间 + */ + @JsonProperty("fdFinishTime") + @JsonInclude(JsonInclude.Include.NON_NULL) + private MKConditionDTO finishTime; + /** + * 最后处理时间(待审任务) + */ + @JsonProperty("fdLastHandleTime") + @JsonInclude(JsonInclude.Include.NON_NULL) + private MKConditionDTO lastHandleTime; + + //======================已阅参数================================ + /** + * 阅读时间 + */ + @JsonProperty("fdReadTime") + @JsonInclude(JsonInclude.Include.NON_NULL) + private MKConditionDTO readTime; + + //======================我参与的参数================================ + /** + * 创建时间 + */ + @JsonProperty("fdCreateTime") + @JsonInclude(JsonInclude.Include.NON_NULL) + private MKConditionDTO createTime; +} diff --git a/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/pojo/dto/approval/MKApprovalDTO.java b/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/pojo/dto/approval/MKApprovalDTO.java new file mode 100644 index 0000000..0d6bb94 --- /dev/null +++ b/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/pojo/dto/approval/MKApprovalDTO.java @@ -0,0 +1,70 @@ +package org.springblade.thirdparty.mk.pojo.dto.approval; + +import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.Data; +import org.springblade.thirdparty.mk.pojo.dto.sort.ISort; + +import java.io.Serial; +import java.io.Serializable; +import java.util.List; + +/** + * mk审批中心查询参数 + * @author bfhuange + * @since 2025/4/2 + */ +@Data +public class MKApprovalDTO implements Serializable { + @Serial + private static final long serialVersionUID = 1L; + /** + * 查询条件 + */ + private MKApprovalConditionDTO conditions; + /** + * 登录名 + */ + private String loginName; + /** + * 获取内容 + */ + @JsonInclude(JsonInclude.Include.NON_NULL) + private Boolean content; + /** + * 获取条目数 + */ + @JsonInclude(JsonInclude.Include.NON_NULL) + private Boolean count; + /** + * 获取总数时最大条目数 + */ + @JsonInclude(JsonInclude.Include.NON_NULL) + private Integer maxCount; + /** + * 从第?条开始查询,起始值为0 + */ + private Integer offset; + /** + * 第?页 + */ + private Integer pageNo; + /** + * 每页?条 + */ + private Integer pageSize; + /** + * 排序列 + */ + private ISort sorts; + /** + * 返回列 + */ + @JsonInclude(JsonInclude.Include.NON_EMPTY) + private List columns; + + public void setPage(Integer pageNo, Integer pageSize) { + this.pageNo = pageNo; + this.pageSize = pageSize; + this.offset = (this.pageNo -1) * this.pageSize; + } +} diff --git a/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/pojo/dto/approval/MKConditionDTO.java b/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/pojo/dto/approval/MKConditionDTO.java new file mode 100644 index 0000000..a46e4bc --- /dev/null +++ b/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/pojo/dto/approval/MKConditionDTO.java @@ -0,0 +1,50 @@ +package org.springblade.thirdparty.mk.pojo.dto.approval; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 条件 + * @author bfhuange + * @since 2025/4/2 + */ +@Builder +@AllArgsConstructor +@NoArgsConstructor +@Data +public class MKConditionDTO implements Serializable { + @Serial + private static final long serialVersionUID = 1L; + /** + * 相等 + */ + @JsonInclude(JsonInclude.Include.NON_EMPTY) + @JsonProperty("$eq") + private String eq; + /** + * 大于等于 + */ + @JsonInclude(JsonInclude.Include.NON_EMPTY) + @JsonProperty("$gte") + private String gte; + /** + * 小于等于 + */ + @JsonInclude(JsonInclude.Include.NON_EMPTY) + @JsonProperty("$lte") + private String lte; + /** + * 包含 + */ + @JsonInclude(JsonInclude.Include.NON_EMPTY) + @JsonProperty("$contains") + private String contains; + +} diff --git a/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/pojo/dto/approval/MKProcessDTO.java b/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/pojo/dto/approval/MKProcessDTO.java new file mode 100644 index 0000000..67e813e --- /dev/null +++ b/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/pojo/dto/approval/MKProcessDTO.java @@ -0,0 +1,124 @@ +package org.springblade.thirdparty.mk.pojo.dto.approval; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; + +/** + * mk流程列表查询参数 + * @author bfhuange + * @since 2025/4/2 + */ +@Data +public class MKProcessDTO implements Serializable { + @Serial + private static final long serialVersionUID = 1L; + /** + * 登录名 + */ + private String loginName; + /** + * 查询数据模型 myAll(我的所有),myCreated(我创建的),myApproving(我的待办),myApproved(我的已办),myFollowed(我跟踪的),myReading(我待阅读),myReaded(我已阅读),myTransfer(我的转办),myCommunicate(我的沟通),myAuthorization(我的授权),my,myManage(我有特权人权限的) + */ + private String mydoc; + /** + * 流程状态 + */ + private String status; + /** + * 搜索关键字 + */ + private String keyword; + /** + * 创建时间:起始 + */ + private Long createBeginTime; + /** + * 创建时间:截止 + */ + private Long createEndTime; + /** + * 处理时间筛选(开始) + */ + @JsonProperty("fdHandleStartTime") + private Long handleStartTime; + /** + * 处理时间筛选(结束) + */ + @JsonProperty("fdHandleEndTime") + private Long handleEndTime; + /** + * 阅读时间筛选(开始) + */ + @JsonProperty("fdReadStartTime") + private Long readStartTime; + /** + * 阅读时间筛选(结束) + */ + @JsonProperty("fdReadEndTime") + private Long readEndTime; + /** + * 接收时间筛选(开始) + */ + @JsonProperty("fdReceiveStartTime") + private Long receiveStartTime; + /** + * 接收时间筛选(结束) + */ + @JsonProperty("fdReceiveEndTime") + private Long receiveEndTime; + /** + * 当前处理人 ','隔开, 支持Mk系统的组织架构id和loginName + */ + private String curHandlerIds; + /** + * 当前节点名称 + */ + @JsonProperty("fdCurNodeName") + private String curNodeName; + /** + * 创建人, 支持Mk系统的组织架构id和loginName + */ + @JsonProperty("fdCreator") + private String creator; + /** + * 从第?条开始查询,起始值为0 + */ + private Integer offset; + /** + * 第?页 + */ + private Integer pageNo; + /** + * 每页?条 + */ + private Integer pageSize; + /** + * 创建时间正序(true)/倒序(false) + */ + private Boolean asc = false; + /** + * 阅读时间正序(true)/倒序(false) + */ + @JsonInclude(JsonInclude.Include.NON_NULL) + private Boolean readTimeAsc; + /** + * 接收时间正序(true)/倒序(false) + */ + @JsonInclude(JsonInclude.Include.NON_NULL) + private Boolean receiveTimeAsc; + /** + * 处理时间正序(true)/倒序(false) + */ + @JsonInclude(JsonInclude.Include.NON_NULL) + private Boolean handleTimeAsc; + + public void setPage(Integer pageNo, Integer pageSize) { + this.pageNo = pageNo; + this.pageSize = pageSize; + this.offset = (this.pageNo -1) * this.pageSize; + } +} diff --git a/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/pojo/dto/sort/ISort.java b/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/pojo/dto/sort/ISort.java new file mode 100644 index 0000000..4b48332 --- /dev/null +++ b/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/pojo/dto/sort/ISort.java @@ -0,0 +1,8 @@ +package org.springblade.thirdparty.mk.pojo.dto.sort; + +/** + * @author bfhuange + * @since 2025/4/2 + */ +public interface ISort { +} diff --git a/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/pojo/dto/sort/MKApprovedSortDTO.java b/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/pojo/dto/sort/MKApprovedSortDTO.java new file mode 100644 index 0000000..0140de5 --- /dev/null +++ b/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/pojo/dto/sort/MKApprovedSortDTO.java @@ -0,0 +1,24 @@ +package org.springblade.thirdparty.mk.pojo.dto.sort; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import org.springblade.thirdparty.mk.constant.MKConstant; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 已处理排序 + * @author bfhuange + * @since 2025/4/2 + */ +@Data +public class MKApprovedSortDTO implements Serializable, ISort { + @Serial + private static final long serialVersionUID = 1L; + /** + * 结束时间 + */ + @JsonProperty("fdFinishTime") + private String finishTime = MKConstant.Sort.DESC; +} diff --git a/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/pojo/dto/sort/MKApprovingSortDTO.java b/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/pojo/dto/sort/MKApprovingSortDTO.java new file mode 100644 index 0000000..fe256c0 --- /dev/null +++ b/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/pojo/dto/sort/MKApprovingSortDTO.java @@ -0,0 +1,29 @@ +package org.springblade.thirdparty.mk.pojo.dto.sort; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import org.springblade.thirdparty.mk.constant.MKConstant; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 待处理、待阅排序 + * @author bfhuange + * @since 2025/4/2 + */ +@Data +public class MKApprovingSortDTO implements Serializable, ISort { + @Serial + private static final long serialVersionUID = 1L; + /** + * 级别 + */ + @JsonProperty("fdLevel") + private String level = MKConstant.Sort.ASC; + /** + * 接收时间 + */ + @JsonProperty("fdReceiveTime") + private String receiveTime = MKConstant.Sort.DESC; +} diff --git a/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/pojo/dto/sort/MKCreateSortDTO.java b/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/pojo/dto/sort/MKCreateSortDTO.java new file mode 100644 index 0000000..2fe9011 --- /dev/null +++ b/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/pojo/dto/sort/MKCreateSortDTO.java @@ -0,0 +1,24 @@ +package org.springblade.thirdparty.mk.pojo.dto.sort; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import org.springblade.thirdparty.mk.constant.MKConstant; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 我发起的排序 + * @author bfhuange + * @since 2025/4/2 + */ +@Data +public class MKCreateSortDTO implements Serializable, ISort { + @Serial + private static final long serialVersionUID = 1L; + /** + * 创建时间 + */ + @JsonProperty("fdCreateTime") + private String createTime = MKConstant.Sort.DESC; +} diff --git a/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/pojo/dto/sort/MKReadSortDTO.java b/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/pojo/dto/sort/MKReadSortDTO.java new file mode 100644 index 0000000..abb3219 --- /dev/null +++ b/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/pojo/dto/sort/MKReadSortDTO.java @@ -0,0 +1,24 @@ +package org.springblade.thirdparty.mk.pojo.dto.sort; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import org.springblade.thirdparty.mk.constant.MKConstant; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 已阅排序 + * @author bfhuange + * @since 2025/4/2 + */ +@Data +public class MKReadSortDTO implements Serializable, ISort { + @Serial + private static final long serialVersionUID = 1L; + /** + * 已读时间 + */ + @JsonProperty("fdReadTime") + private String readTime = MKConstant.Sort.DESC; +} diff --git a/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/pojo/vo/MKAllHandlerVO.java b/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/pojo/vo/MKAllHandlerVO.java new file mode 100644 index 0000000..3b1e22b --- /dev/null +++ b/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/pojo/vo/MKAllHandlerVO.java @@ -0,0 +1,29 @@ +package org.springblade.thirdparty.mk.pojo.vo; + +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; +import java.util.List; + +/** + * @author bfhuange + * @date 2024/9/20 + */ +@Data +public class MKAllHandlerVO implements Serializable { + @Serial + private static final long serialVersionUID = 1L; + /** + * 已审批的处理人 + */ + private List approvedHandlers; + /** + * 审批中的处理人 + */ + private List approvingHandlers; + /** + * 解析的处理人 + */ + private List parseHandlers; +} diff --git a/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/pojo/vo/MKApprovalVO.java b/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/pojo/vo/MKApprovalVO.java new file mode 100644 index 0000000..71a07a7 --- /dev/null +++ b/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/pojo/vo/MKApprovalVO.java @@ -0,0 +1,153 @@ +package org.springblade.thirdparty.mk.pojo.vo; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; +import java.util.Date; + +/** + * @author bfhuange + * @since 2025/4/2 + */ +@Data +public class MKApprovalVO implements Serializable { + @Serial + private static final long serialVersionUID = 1L; + + /** + * id + */ + @JsonProperty("fdId") + private String id; + /** + * 流程所属应用 + */ + @JsonProperty("fdAppName") + private String appName; + /** + * 申请人名称 + */ + @JsonProperty("fdApplicantName") + private String applicantName; + /** + * 申请人登录名 + */ + @JsonProperty("fdApplicantLoginName") + private String applicantLoginName; + /** + * 发起人名称 + */ + @JsonProperty("fdCreator") + private String creator; + /** + * 处理人名称 + */ + @JsonProperty("fdHandlerName") + private String handlerName; + /** + * 当前处理人名称 + */ + @JsonProperty("fdCurrentHandler") + private String currentHandler; + /** + * 处理人登录名 + */ + @JsonProperty("fdHandlerLoginName") + private String handlerLoginName; + /** + * 节点名称 + */ + @JsonProperty("fdNodeName") + private String nodeName; + /** + * 流程id + */ + @JsonProperty("fdProcessId") + private String processId; + /** + * 创建时间 + */ + @JsonProperty("fdCreateTime") + private Date createTime; + /** + * 流程发起时间 + */ + @JsonProperty("fdStartTime") + private Date startTime; + /** + * 如果是待办:待办接收时间 如果是待阅:传阅接收时间 + */ + @JsonProperty("fdReceiveTime") + private Date receiveTime; + /** + * 任务结束时间 + */ + @JsonProperty("fdFinishTime") + private Date finishTime; + /** + * 阅读时间(待阅任务) + */ + @JsonProperty("fdReadTime") + private Date readTime; + /** + * 最后处理时间(待审任务) + */ + @JsonProperty("fdLastHandleTime") + private Date lastHandleTime; + /** + * 流程结束时间 + */ + @JsonProperty("fdProcessFinishTime") + private Date processFinishTime; + /** + * 流程状态 00 – 废弃、10 – 草稿、11 – 驳回 20 – 待审、21 – 异常、40 – 挂起 30 – 结束 + */ + @JsonProperty("fdStatus") + private String status; + /** + * 流程主题 + */ + @JsonProperty("fdSubject") + private String subject; + /** + * 模板编码 + */ + @JsonProperty("fdTemplateCode") + private String templateCode; + /** + * 任务id + */ + @JsonProperty("fdTaskId") + private String taskId; + /** + * 任务状态 20 - 激活、30 - 结束、40 - 挂起、50 - 自动跳过 + */ + @JsonProperty("fdTaskStatus") + private String taskStatus; + /** + * 任务标题 + */ + @JsonProperty("fdTaskSubject") + private String taskSubject; + /** + * 催办标记 + */ + @JsonProperty("fdUrgeTab") + private String urgeTab; + /** + * 任务类型 1待办,2待阅 + */ + @JsonProperty("fdTaskType") + private Integer taskType; + /** + * 待办优先级 + */ + @JsonProperty("fdLevel") + private Integer level; + /** + * 动态扩展属性 + */ + private MKDynamicPropsVO dynamicProps; +} diff --git a/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/pojo/vo/MKApprovedHandlerVO.java b/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/pojo/vo/MKApprovedHandlerVO.java new file mode 100644 index 0000000..f4e1943 --- /dev/null +++ b/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/pojo/vo/MKApprovedHandlerVO.java @@ -0,0 +1,33 @@ +package org.springblade.thirdparty.mk.pojo.vo; + +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; +import java.util.Date; + +/** + * @author bfhuange + * @date 2024/9/20 + */ +@Data +public class MKApprovedHandlerVO implements Serializable { + @Serial + private static final long serialVersionUID = 1L; + /** + * 名称 + */ + private String name; + /** + * 登录名 + */ + private String loginName; + /** + * 操作时间 + */ + private Date operationTime; + /** + * 操作名称 + */ + private String operationName; +} diff --git a/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/pojo/vo/MKAttachmentVO.java b/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/pojo/vo/MKAttachmentVO.java new file mode 100644 index 0000000..cda15f4 --- /dev/null +++ b/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/pojo/vo/MKAttachmentVO.java @@ -0,0 +1,32 @@ +package org.springblade.thirdparty.mk.pojo.vo; + +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; + +/** + * @author bfhuange + * @since 2025/2/24 + */ +@Data +public class MKAttachmentVO implements Serializable { + @Serial + private static final long serialVersionUID = 1L; + /** + * id + */ + private String id; + /** + * 附件名称 + */ + private String name; + /** + * 附件类型 electronicSign 电子签名,attachment 附件 + */ + private String type; + /** + * 附件ID + */ + private String fileId; +} diff --git a/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/pojo/vo/MKAuditNoteVO.java b/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/pojo/vo/MKAuditNoteVO.java new file mode 100644 index 0000000..d1233c7 --- /dev/null +++ b/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/pojo/vo/MKAuditNoteVO.java @@ -0,0 +1,102 @@ +package org.springblade.thirdparty.mk.pojo.vo; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; +import java.util.Date; +import java.util.List; + +/** + * @author bfhuange + * @since 2025/2/24 + */ +@Data +public class MKAuditNoteVO implements Serializable { + @Serial + private static final long serialVersionUID = 1L; + /** + * 记录主键 + */ + @JsonProperty("fdId") + private String id; + /** + * 处理人 + */ + @JsonProperty("fdHandler") + private String handler; + /** + * 操作 + */ + @JsonProperty("fdAction") + private String action; + /** + * 操作编码 + */ + @JsonProperty("fdActionCode") + private String actionCode; + /** + * 操作描述(系统操作) + */ + @JsonProperty("fdActionDesc") + private String actionDesc; + /** + * 操作名称 + */ + @JsonProperty("fdActionName") + private String actionName; + /** + * 处理意见 + */ + @JsonProperty("fdMessage") + private String message; + /** + * 创建时间 + */ + @JsonProperty("fdCreateTime") + private Date createTime; + /** + * 流程实例id + */ + @JsonProperty("fdProcessInstanceId") + private String processInstanceId; + /** + * 节点实例id + */ + @JsonProperty("fdNodeInstanceId") + private String nodeInstanceId; + /** + * 节点类型 + */ + @JsonProperty("fdNodeType") + private String nodeType; + /** + * 节点id + */ + @JsonProperty("fdNodeId") + private String nodeId; + /** + * 节点名称 + */ + @JsonProperty("fdNodeName") + private String nodeName; + /** + * 节点编号 + */ + @JsonProperty("fdNodeNumber") + private String nodeNumber; + /** + * 附件参数 + */ + private List attachmentParameter; + /** + * 流程附言 + */ + @JsonProperty("lbpmCommentPostscriptVOS") + private List processComments; + /** + * 机制数据 + */ + private MKMechanismsVO mechanisms; +} diff --git a/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/pojo/vo/MKDynamicPropsVO.java b/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/pojo/vo/MKDynamicPropsVO.java new file mode 100644 index 0000000..c4754f8 --- /dev/null +++ b/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/pojo/vo/MKDynamicPropsVO.java @@ -0,0 +1,23 @@ +package org.springblade.thirdparty.mk.pojo.vo; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; + +/** + * @author bfhuange + * @since 2025/4/2 + */ +@Data +public class MKDynamicPropsVO implements Serializable { + @Serial + private static final long serialVersionUID = 1L; + + /** + * 模板名称中文 + */ + @JsonProperty("fdTemplateNameCn") + private String templateNameCn; +} diff --git a/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/pojo/vo/MKErrorVO.java b/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/pojo/vo/MKErrorVO.java new file mode 100644 index 0000000..cbe48bf --- /dev/null +++ b/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/pojo/vo/MKErrorVO.java @@ -0,0 +1,30 @@ +package org.springblade.thirdparty.mk.pojo.vo; + +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; + +/** + * mk 返回结果错误堆栈 + * @author bfhuange + * @date 2024/9/24 + */ +@Data +public class MKErrorVO implements Serializable { + @Serial + private static final long serialVersionUID = 1L; + /** + * 服务名称 + */ + private String app; + /** + * 错误编码 + */ + private String code; + /** + * 错误描述 + */ + private String message; + +} diff --git a/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/pojo/vo/MKManualNodeVO.java b/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/pojo/vo/MKManualNodeVO.java new file mode 100644 index 0000000..cce3102 --- /dev/null +++ b/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/pojo/vo/MKManualNodeVO.java @@ -0,0 +1,33 @@ +package org.springblade.thirdparty.mk.pojo.vo; + +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 人工节点 + * @author bfhuange + * @since 2025/3/21 + */ +@Data +public class MKManualNodeVO implements Serializable { + @Serial + private static final long serialVersionUID = 1L; + /** + * 节点id + */ + private String nodeId; + /** + * 节点名称 + */ + private String nodeName; + /** + * 节点编号 + */ + private String nodeNumber; + /** + * 节点类型 + */ + private String nodeType; +} diff --git a/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/pojo/vo/MKMechanismsVO.java b/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/pojo/vo/MKMechanismsVO.java new file mode 100644 index 0000000..4fd5036 --- /dev/null +++ b/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/pojo/vo/MKMechanismsVO.java @@ -0,0 +1,21 @@ +package org.springblade.thirdparty.mk.pojo.vo; + +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; +import java.util.List; + +/** + * @author bfhuange + * @since 2025/2/25 + */ +@Data +public class MKMechanismsVO implements Serializable { + @Serial + private static final long serialVersionUID = 1L; + /** + * 附件信息 + */ + private List attachment; +} diff --git a/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/pojo/vo/MKMetaAttachmentVO.java b/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/pojo/vo/MKMetaAttachmentVO.java new file mode 100644 index 0000000..241664d --- /dev/null +++ b/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/pojo/vo/MKMetaAttachmentVO.java @@ -0,0 +1,31 @@ +package org.springblade.thirdparty.mk.pojo.vo; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; + +/** + * @author bfhuange + * @since 2025/2/25 + */ +@Data +public class MKMetaAttachmentVO implements Serializable { + @Serial + private static final long serialVersionUID = 1L; + /** + * id + */ + @JsonProperty("fdId") + private String id; + /** + * 附件名称 + */ + @JsonProperty("fdFileName") + private String fileName; + /** + * 附件全名 + */ + private String fullName; +} diff --git a/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/pojo/vo/MKNodeHandlerOrgVO.java b/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/pojo/vo/MKNodeHandlerOrgVO.java new file mode 100644 index 0000000..16c8aae --- /dev/null +++ b/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/pojo/vo/MKNodeHandlerOrgVO.java @@ -0,0 +1,20 @@ +package org.springblade.thirdparty.mk.pojo.vo; + +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; + +/** + * @author bfhuange + * @date 2024/9/20 + */ +@Data +public class MKNodeHandlerOrgVO implements Serializable { + @Serial + private static final long serialVersionUID = 1L; + /** + * 登录名 + */ + private String loginName; +} diff --git a/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/pojo/vo/MKNodeHandlerVO.java b/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/pojo/vo/MKNodeHandlerVO.java new file mode 100644 index 0000000..a74cd61 --- /dev/null +++ b/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/pojo/vo/MKNodeHandlerVO.java @@ -0,0 +1,29 @@ +package org.springblade.thirdparty.mk.pojo.vo; + +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; +import java.util.Date; + +/** + * @author bfhuange + * @date 2024/9/20 + */ +@Data +public class MKNodeHandlerVO implements Serializable { + @Serial + private static final long serialVersionUID = 1L; + /** + * 处理人名称 + */ + private String handlerName; + /** + * 接收时间 + */ + private Date receiveTime; + /** + * 处理人组织信息 + */ + private MKNodeHandlerOrgVO fdHandlerOrgInfo; +} diff --git a/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/pojo/vo/MKNodeVO.java b/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/pojo/vo/MKNodeVO.java new file mode 100644 index 0000000..2ef4123 --- /dev/null +++ b/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/pojo/vo/MKNodeVO.java @@ -0,0 +1,42 @@ +package org.springblade.thirdparty.mk.pojo.vo; + +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; +import java.util.List; + +/** + * mk 节点信息 + * @author bfhuange + * @date 2024/9/20 + */ +@Data +public class MKNodeVO implements Serializable { + @Serial + private static final long serialVersionUID = 1L; + /** + * 节点id + */ + private String nodeId; + /** + * 节点名称 + */ + private String nodeName; + /** + * 节点编号 + */ + private String nodeNumber; + /** + * 流程实例id + */ + private String processInstanceId; + /** + * 节点实例id + */ + private String nodeInstanceId; + /** + * 节点处理人 + */ + private List nodeHandlers; +} diff --git a/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/pojo/vo/MKOAuthCodeVO.java b/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/pojo/vo/MKOAuthCodeVO.java new file mode 100644 index 0000000..7376cf4 --- /dev/null +++ b/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/pojo/vo/MKOAuthCodeVO.java @@ -0,0 +1,21 @@ +package org.springblade.thirdparty.mk.pojo.vo; + +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; + +/** + * mk oauth 认证code响应参数 + * @author bfhuange + * @date 2024/9/27 + */ +@Data +public class MKOAuthCodeVO implements Serializable { + @Serial + private static final long serialVersionUID = 1L; + /** + * 会话code + */ + private String code; +} diff --git a/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/pojo/vo/MKOAuthTokenVO.java b/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/pojo/vo/MKOAuthTokenVO.java new file mode 100644 index 0000000..e25437d --- /dev/null +++ b/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/pojo/vo/MKOAuthTokenVO.java @@ -0,0 +1,21 @@ +package org.springblade.thirdparty.mk.pojo.vo; + +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; + +/** + * mk oauth token响应参数 + * @author bfhuange + * @date 2024/9/14 + */ +@Data +public class MKOAuthTokenVO implements Serializable { + @Serial + private static final long serialVersionUID = 1L; + /** + * token + */ + private String token; +} diff --git a/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/pojo/vo/MKOAuthUserVO.java b/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/pojo/vo/MKOAuthUserVO.java new file mode 100644 index 0000000..cccab92 --- /dev/null +++ b/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/pojo/vo/MKOAuthUserVO.java @@ -0,0 +1,33 @@ +package org.springblade.thirdparty.mk.pojo.vo; + +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; + +/** + * mk oauth 用户信息 + * @author bfhuange + * @date 2024/9/14 + */ +@Data +public class MKOAuthUserVO implements Serializable { + @Serial + private static final long serialVersionUID = 1L; + /** + * 登录名 + */ + private String loginName; + /** + * ⽤户名 + */ + private String name; + /** + * 编号信息 + */ + private String fdNo; + /** + * 部⻔信息 + */ + private String deptName; +} diff --git a/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/pojo/vo/MKPageVO.java b/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/pojo/vo/MKPageVO.java new file mode 100644 index 0000000..864653f --- /dev/null +++ b/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/pojo/vo/MKPageVO.java @@ -0,0 +1,50 @@ +package org.springblade.thirdparty.mk.pojo.vo; + +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serial; +import java.io.Serializable; +import java.util.Collections; +import java.util.List; + +/** + * @author bfhuange + * @since 2025/2/24 + */ +@NoArgsConstructor +@Data +public class MKPageVO implements Serializable { + @Serial + private static final long serialVersionUID = 1L; + /** + * 分页位移量 + */ + private Integer offset; + /** + * 每页数目 + */ + private Integer pageSize; + /** + * 总数 + */ + private Integer totalSize; + /** + * 返回内容 + */ + private List content; + + public MKPageVO(Integer totalSize) { + this.totalSize = totalSize; + this.content = Collections.emptyList(); + } + + /** + * 空的结果 + * @return + * @param + */ + public static MKPageVO empty() { + return new MKPageVO<>(0); + } +} diff --git a/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/pojo/vo/MKProcessCommentVO.java b/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/pojo/vo/MKProcessCommentVO.java new file mode 100644 index 0000000..bfc6887 --- /dev/null +++ b/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/pojo/vo/MKProcessCommentVO.java @@ -0,0 +1,51 @@ +package org.springblade.thirdparty.mk.pojo.vo; + +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; +import java.util.Date; +import java.util.List; + +/** + * 流程附言 + * @author bfhuange + * @since 2025/2/24 + */ +@Data +public class MKProcessCommentVO implements Serializable { + @Serial + private static final long serialVersionUID = 1L; + /** + * id + */ + private String id; + /** + * 附言内容 + */ + private String content; + /** + * 创建时间 + */ + private Date createTime; + /** + * 更新时间 + */ + private Date updateTime; + /** + * 用户id + */ + private String userId; + /** + * 处理人信息 + */ + private MKUserOrgVO userOrgInfo; + /** + * 附言附件 + */ + private List attachments; + /** + * 机制数据 + */ + private MKMechanismsVO mechanisms; +} diff --git a/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/pojo/vo/MKProcessVO.java b/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/pojo/vo/MKProcessVO.java new file mode 100644 index 0000000..6474d8a --- /dev/null +++ b/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/pojo/vo/MKProcessVO.java @@ -0,0 +1,94 @@ +package org.springblade.thirdparty.mk.pojo.vo; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; +import java.util.Date; + +/** + * @author bfhuange + * @since 2025/4/2 + */ +@Data +public class MKProcessVO implements Serializable { + @Serial + private static final long serialVersionUID = 1L; + + /** + * id + */ + @JsonProperty("fdId") + private String id; + /** + * 发起人名称 + */ + @JsonProperty("fdCreator") + private String creator; + /** + * 当前处理人名称 + */ + @JsonProperty("fdCurrentHandler") + private String currentHandler; + /** + * 当前节点 - 仅我参与的 + */ + @JsonProperty("fdCurrentNode") + private String currentNode; + /** + * 流程id + */ + @JsonProperty("fdProcessId") + private String processId; + /** + * 创建时间 + */ + @JsonProperty("fdCreateTime") + private Date createTime; + /** + * 如果是待办:待办接收时间 如果是待阅:传阅接收时间 + */ + @JsonProperty("fdReceiveTime") + private Date receiveTime; + /** + * 阅读时间(待阅任务) + */ + @JsonProperty("fdReadTime") + private Date readTime; + /** + * 最后处理时间(待审任务) + */ + @JsonProperty("fdLastHandleTime") + private Date lastHandleTime; + /** + * 流程结束时间 + */ + @JsonProperty("fdProcessFinishTime") + private Date processFinishTime; + /** + * 流程状态 00 – 废弃、10 – 草稿、11 – 驳回 20 – 待审、21 – 异常、40 – 挂起 30 – 结束 + */ + @JsonProperty("fdStatus") + private String status; + /** + * 流程主题 + */ + @JsonProperty("fdSubject") + private String subject; + /** + * 模板编码 + */ + @JsonProperty("fdTemplateCode") + private String templateCode; + /** + * 模板名称 - 仅我参与的 + */ + @JsonProperty("fdTemplateName") + private String templateName; + /** + * 催办标记 + */ + @JsonProperty("fdUrgeTab") + private String urgeTab; +} diff --git a/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/pojo/vo/MKResultVO.java b/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/pojo/vo/MKResultVO.java new file mode 100644 index 0000000..1b7fdff --- /dev/null +++ b/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/pojo/vo/MKResultVO.java @@ -0,0 +1,42 @@ +package org.springblade.thirdparty.mk.pojo.vo; + +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; +import java.util.List; + +/** + * mk 返回结果 + * @author bfhuange + * @date 2024/9/11 + */ +@Data +public class MKResultVO implements Serializable { + @Serial + private static final long serialVersionUID = 1L; + /** + * 是否成功 + */ + private boolean success; + /** + * 响应码 + */ + private String code; + /** + * 消息 + */ + private String msg; + /** + * traceId + */ + private String traceId; + /** + * 数据 + */ + private T data; + /** + * 错误堆栈 + */ + private List errStacks; +} diff --git a/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/pojo/vo/MKSenderVO.java b/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/pojo/vo/MKSenderVO.java new file mode 100644 index 0000000..23edbab --- /dev/null +++ b/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/pojo/vo/MKSenderVO.java @@ -0,0 +1,27 @@ +package org.springblade.thirdparty.mk.pojo.vo; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 抄送人 + * @author bfhuange + * @since 2025/2/24 + */ +@Data +public class MKSenderVO implements Serializable { + @Serial + private static final long serialVersionUID = 1L; + /** + * id + */ + private String id; + /** + * 处理人名称 + */ + @JsonProperty("fdName") + private String name; +} diff --git a/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/pojo/vo/MKTokenVO.java b/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/pojo/vo/MKTokenVO.java new file mode 100644 index 0000000..d5cefd1 --- /dev/null +++ b/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/pojo/vo/MKTokenVO.java @@ -0,0 +1,32 @@ +package org.springblade.thirdparty.mk.pojo.vo; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; + +/** + * @author bfhuange + * @date 2024/9/11 + */ +@Data +public class MKTokenVO implements Serializable { + @Serial + private static final long serialVersionUID = 1L; + /** + * token + */ + @JsonProperty("access_token") + private String accessToken; + /** + * 过期时间 + */ + @JsonProperty("expires_in") + private int expiresIn; + /** + * 错误码 + */ + @JsonProperty("errcode") + private int errCode; +} diff --git a/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/pojo/vo/MKUserOrgVO.java b/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/pojo/vo/MKUserOrgVO.java new file mode 100644 index 0000000..d2e1fcd --- /dev/null +++ b/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/pojo/vo/MKUserOrgVO.java @@ -0,0 +1,22 @@ +package org.springblade.thirdparty.mk.pojo.vo; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; + +/** + * @author bfhuange + * @since 2025/2/24 + */ +@Data +public class MKUserOrgVO implements Serializable { + @Serial + private static final long serialVersionUID = 1L; + /** + * 名称 + */ + @JsonProperty("fdName") + private String name; +} diff --git a/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/service/IMKService.java b/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/service/IMKService.java new file mode 100644 index 0000000..79c6efb --- /dev/null +++ b/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/service/IMKService.java @@ -0,0 +1,148 @@ +package org.springblade.thirdparty.mk.service; + +import jakarta.servlet.http.HttpServletResponse; +import org.springblade.thirdparty.mk.pojo.dto.*; +import org.springblade.thirdparty.mk.pojo.dto.approval.MKApprovalDTO; +import org.springblade.thirdparty.mk.pojo.dto.approval.MKProcessDTO; +import org.springblade.thirdparty.mk.pojo.vo.*; + +import java.util.List; + +/** + * @author bfhuange + * @date 2024/9/11 + */ +public interface IMKService { + /** + * 提交流程 + * @param param + * @return + */ + String processSubmit(MKProcessCreateDTO param); + + /** + * 执行流程 + * @param param + * @return + */ + boolean processExecute(MKProcessExecuteDTO param); + + /** + * 撤回流程,起草人撤回 + * @param bizId + * @param loginName MK要的是手机号,不是登录名 + * @return + */ + boolean processRevoke(String bizId, String loginName); + + /** + * 删除流程 + * @param processInstanceId 流程实例id + * @param loginName MK要的是手机号,不是登录名 + * @return + */ + boolean processDelete(String processInstanceId, String loginName); + + /** + * 废弃流程,起草人废弃 + * @param bizId 业务id + * @param loginName MK要的是手机号,不是登录名 + * @return + */ + boolean processAbandon(String bizId, String loginName); + + /** + * 根据 mk code 获取mk账号 + * @param mkCode + * @return + */ + String getMKAccount(String mkCode); + + /** + * 获取流程当前节点详情 + * @param processInstanceId + * @param loginName + * @return + */ + List getCurrentNodes(String processInstanceId, String loginName); + + /** + * 获取节点处理人信息 + * @param processInstanceId + * @param loginName + * @param nodeId + * @return + */ + MKAllHandlerVO getNodeHandlers(String processInstanceId, String loginName, String nodeId); + + /** + * 推送机构部门 + * @param param + * @return + */ + boolean pushOrgDept(MKPushDTO param); + + /** + * 推送人员 + * @param param + * @return + */ + boolean pushPerson(MKPushDTO param); + + /** + * 获取mk审批链接 + * @param processInstanceId + * @param loginName + * @return + */ + String getMKApprovalUrl(String processInstanceId, String loginName); + + /** + * 查询流程审批记录 + * @param param + * @return + */ + List queryAuditNotes(MKAuditNoteDTO param); + + /** + * 查询抄送人列表 + * @param param + * @return + */ + List querySenderList(MKSenderDTO param); + + /** + * 下载文件 + * @param response + * @param fileId + */ + void downloadFile(HttpServletResponse response, String fileId); + + /** + * 下载文件 + * @param fileId + */ + String getFileBase64(String fileId); + + /** + * 根据流程模板id和节点id查询节点编号 + * @param templateId + * @param nodeId + * @return + */ + String getNodeNumber(String templateId, String nodeId); + + /** + * 查询审批中心列表 + * @param param + * @return + */ + MKPageVO queryApprovalList(MKApprovalDTO param); + + /** + * 查询流程列表,我参与的 待办 + * @param param + * @return + */ + MKPageVO queryProcessList(MKProcessDTO param); +} diff --git a/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/service/impl/MKServiceImpl.java b/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/service/impl/MKServiceImpl.java new file mode 100644 index 0000000..f0f3fb5 --- /dev/null +++ b/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/service/impl/MKServiceImpl.java @@ -0,0 +1,508 @@ +package org.springblade.thirdparty.mk.service.impl; + +import cn.hutool.json.JSONUtil; +import feign.Response; +import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.io.IOUtils; +import org.springblade.core.redis.cache.BladeRedis; +import org.springblade.core.tool.utils.CollectionUtil; +import org.springblade.core.tool.utils.StringPool; +import org.springblade.core.tool.utils.StringUtil; +import org.springblade.core.tool.utils.UrlUtil; +import org.springblade.thirdparty.mk.config.MKProperties; +import org.springblade.thirdparty.mk.constant.MKConstant; +import org.springblade.thirdparty.mk.exception.MKException; +import org.springblade.thirdparty.mk.feign.IMKClient; +import org.springblade.thirdparty.mk.pojo.dto.*; +import org.springblade.thirdparty.mk.pojo.dto.approval.MKApprovalDTO; +import org.springblade.thirdparty.mk.pojo.dto.approval.MKProcessDTO; +import org.springblade.thirdparty.mk.pojo.vo.*; +import org.springblade.thirdparty.mk.service.IMKService; +import org.springblade.thirdparty.mk.util.AssertUtils; +import org.springframework.http.MediaType; +import org.springframework.stereotype.Service; + +import java.io.*; +import java.nio.charset.StandardCharsets; +import java.time.Duration; +import java.util.*; +import java.util.function.Function; +import java.util.stream.Collectors; + +/** + * @author bfhuange + * @date 2024/9/11 + */ +@Slf4j +@RequiredArgsConstructor +@Service +public class MKServiceImpl implements IMKService { + + /** + * mk审批页面链接 + */ + private static final String APPROVAL_URL_FORMAT = "%s/web//#/current/sys-lbpm/lbpmIntegrate/formInFlow/view/%s?processInstanceId=%s"; + /** + * mk sso回调地址 + */ + private static final String SSO_CALLBACK_URL_FORMAT = "%s/data/sys-auth/ssoCallBack?mkCode=%s&redirectUrl=%s"; + + private final MKProperties properties; + private final IMKClient client; + private final BladeRedis bladeRedis; + + @Override + public String processSubmit(MKProcessCreateDTO param) { + if (param == null) { + log.warn("调用mk接口 创建并提交流程参数为空"); + return null; + } + return commonCall("创建并提交流程", token -> { + // 设置标题 + String actualSubject = getActualSubject(param.getSubject()); + param.setSubject(actualSubject); + log.info("调用mk提交流程开始"); + long start = System.currentTimeMillis(); + MKResultVO result = client.processSubmit(param, token); + long end = System.currentTimeMillis(); + log.info("调用mk提交流程结束 耗时:{}", end - start); + return result; + }, result -> Optional.ofNullable(result) + .map(MKResultVO::getData) + .orElse(null) + ); + } + + @Override + public boolean processExecute(MKProcessExecuteDTO param) { + if (param == null) { + log.warn("调用mk接口 执行流程参数为空"); + return false; + } + return commonCall("执行流程", token -> client.processExecute(param, token)); + } + + @Override + public boolean processRevoke(String bizId, String loginName) { + if (StringUtil.isBlank(bizId) || StringUtil.isBlank(loginName)) { + log.warn("调用mk接口 撤回流程参数为空"); + return false; + } + return commonCall("撤回流程", token -> { + MKProcessExecuteDTO param = new MKProcessExecuteDTO(); + param.setFormInstanceId(bizId); + param.setLoginName(loginName); + param.setOperationType(MKConstant.OPERATE_TYPE_REVOKE); + return client.processExecute(param, token); + }); + } + + @Override + public boolean processDelete(String processInstanceId, String loginName) { + if (StringUtil.isBlank(processInstanceId) || StringUtil.isBlank(loginName)) { + log.warn("调用mk接口 删除流程参数为空"); + return false; + } + return commonCall("删除流程", token -> { + MKProcessDeleteDTO param = new MKProcessDeleteDTO(); + param.setProcessInstanceId(processInstanceId); + param.setLoginName(loginName); + return client.processDelete(param, token); + }); + } + + @Override + public boolean processAbandon(String bizId, String loginName) { + if (StringUtil.isBlank(bizId) || StringUtil.isBlank(loginName)) { + log.warn("调用mk接口 废弃流程参数为空"); + return false; + } + return commonCall("废弃流程", token -> { + MKProcessExecuteDTO param = new MKProcessExecuteDTO(); + param.setFormInstanceId(bizId); + param.setLoginName(loginName); + param.setOperationType(MKConstant.OPERATE_TYPE_ABANDON); + return client.processExecute(param, token); + }); + } + + /** + * 公共调用方法 + * @param type + * @param function + * @return + */ + private boolean commonCall(String type, Function> function) { + return commonCall(type, function, Objects::nonNull); + } + + /** + * 公共调用方法 + * @param type + * @param function + * @return + */ + private R commonCall(String type, Function> function, Function, R> resultMapper) { + if (function == null) { + log.warn("实际调用接口为空"); + return resultMapper.apply(null); + } + String token = this.getToken(); + if (token == null) { + return resultMapper.apply(null); + } + try { + MKResultVO result = function.apply(token); + if (result != null && result.isSuccess()) { + return resultMapper.apply(result); + } + log.error("调用mk接口 {}异常响应参数:{}", type, JSONUtil.toJsonStr(result)); + String message = getMessage(result); + throw new MKException("调用mk接口" + type + "异常 " + message); + } catch (MKException e) { + throw e; + } catch (Exception e) { + log.error("调用mk接口 {}异常", type, e); + throw new MKException("调用mk接口" + type + "异常"); + } + } + + /** + * 获取消息 + * @param result + * @return + */ + private String getMessage(MKResultVO result) { + return result == null ? "" : result.getMsg(); + } + + /** + * 获取实际的标题,拼接配置的前缀 + * @param subject + * @return + */ + private String getActualSubject(String subject) { + if (StringUtil.isBlank(subject)) { + log.warn("调用mk接口 创建并提交流程标题为空"); + return subject; + } + String subjectPrefix = properties.getSubjectPrefix(); + if (StringUtil.isBlank(subjectPrefix)) { + return subject; + } + return subjectPrefix + subject; + } + + @Override + public String getMKAccount(String mkCode) { + if (!StringUtil.hasText(mkCode)) { + log.warn("获取mk账号参数为空"); + return null; + } + String accessToken = getToken(); + MKOAuthTokenDTO tokenParam = new MKOAuthTokenDTO(); + tokenParam.setAppId(properties.getOauthAppId()); + tokenParam.setAppSecret(properties.getOauthAppSecret()); + tokenParam.setCode(mkCode); + MKResultVO tokenResult = client.getOAuthToken(tokenParam, accessToken); + if (tokenResult == null || !tokenResult.isSuccess()) { + log.error("调用mk接口 获取oauth token 异常:{}", JSONUtil.toJsonStr(tokenResult)); + return null; + } + MKOAuthTokenVO tokenVO = tokenResult.getData(); + if (tokenVO == null || tokenVO.getToken() == null) { + log.error("获取oauth token 为空:{}", JSONUtil.toJsonStr(tokenResult)); + return null; + } + + String token = tokenVO.getToken(); + MKResultVO userResult = client.getOAuthUserInfo(new MKOAuthUserDTO(token), accessToken); + if (userResult == null || !userResult.isSuccess()) { + log.error("调用mk接口 获取oauth 用户信息异常:{}", JSONUtil.toJsonStr(userResult)); + return null; + } + MKOAuthUserVO userVO = userResult.getData(); + if (userVO != null) { + return userVO.getLoginName(); + } + log.error("获取oauth 用户信息为空:{}", JSONUtil.toJsonStr(userResult)); + return null; + } + + @Override + public List getCurrentNodes(String processInstanceId, String loginName) { + String accessToken = getToken(); + MKCurrentNodesDTO param = new MKCurrentNodesDTO(); + param.setProcessInstanceId(processInstanceId); + param.setLoginName(loginName); + MKResultVO> nodesResult = client.getCurrentNodes(param, accessToken); + log.info("调用mk接口 获取流程当前节点详情 :{}", JSONUtil.toJsonStr(nodesResult)); + if (nodesResult == null || !nodesResult.isSuccess()) { + log.error("调用mk接口 获取流程当前节点详情异常:{}", JSONUtil.toJsonStr(nodesResult)); + return null; + } + return nodesResult.getData(); + } + + @Override + public MKAllHandlerVO getNodeHandlers(String processInstanceId, String loginName, String nodeId) { + String accessToken = getToken(); + MKNodeHandlersDTO param = new MKNodeHandlersDTO(); + param.setProcessInstanceId(processInstanceId); + param.setLoginName(loginName); + param.setNodeId(nodeId); + MKResultVO nodeHandlers = client.getNodeHandlers(param, accessToken); + log.info("调用mk接口 获取节点处理人详情 :{}", JSONUtil.toJsonStr(nodeHandlers)); + if (nodeHandlers == null || !nodeHandlers.isSuccess()) { + log.error("调用mk接口 获取节点处理人详情异常:{}", JSONUtil.toJsonStr(nodeHandlers)); + return null; + } + return nodeHandlers.getData(); + } + + @Override + public boolean pushOrgDept(MKPushDTO param) { + String accessToken = getToken(); + MKResultVO resultVO = client.pushOrgDept(param, accessToken); + log.info("调用mk接口 推送机构部门结果 :{}", JSONUtil.toJsonStr(resultVO)); + if (resultVO == null || !resultVO.isSuccess()) { + log.error("调用mk接口 推送机构部门异常:{}", JSONUtil.toJsonStr(resultVO)); + return false; + } + return true; + } + + @Override + public boolean pushPerson(MKPushDTO param) { + String accessToken = getToken(); + MKResultVO resultVO = client.pushPerson(param, accessToken); + log.info("调用mk接口 推送人员结果 :{}", JSONUtil.toJsonStr(resultVO)); + if (resultVO == null || !resultVO.isSuccess()) { + log.error("调用mk接口 推送人员异常:{}", JSONUtil.toJsonStr(resultVO)); + return false; + } + return true; + } + + @Override + public String getMKApprovalUrl(String processInstanceId, String loginName) { + String accessToken = getToken(); + MKOAuthCodeDTO codeParam = new MKOAuthCodeDTO(); + codeParam.setAppId(properties.getOauthAppId()); + codeParam.setAppSecret(properties.getOauthAppSecret()); + codeParam.setLoginName(loginName); + MKResultVO codeResult = client.getOAuthCode(codeParam, accessToken); + if (codeResult == null || !codeResult.isSuccess()) { + log.error("调用mk接口 获取oauth 认证code 异常:{}", JSONUtil.toJsonStr(codeResult)); + String message = Optional.ofNullable(codeResult) + // 这个接口错误消息在 code,没有 msg + .map(MKResultVO::getCode) + .orElse(StringPool.EMPTY); + throw new MKException(message); + } + MKOAuthCodeVO codeVO = codeResult.getData(); + if (codeVO == null || codeVO.getCode() == null) { + log.error("获取oauth 认证code 为空:{}", JSONUtil.toJsonStr(codeVO)); + return null; + } + String code = codeVO.getCode(); + // 审批页面地址 + String approvalUrl = String.format(APPROVAL_URL_FORMAT, properties.getLoginUrl(), processInstanceId, processInstanceId); + // sso回调地址 + return String.format(SSO_CALLBACK_URL_FORMAT, properties.getLoginUrl(), code, UrlUtil.encode(approvalUrl)); + } + + @Override + public List queryAuditNotes(MKAuditNoteDTO param) { + AssertUtils.notNull(param, "参数不能为空"); + AssertUtils.notBlank(param.getLoginName(),"登录名不能为空"); + AssertUtils.notBlank(param.getProcessInstanceId(),"流程实例id不能为空"); + List notes = commonCall("查询审批记录", + token -> client.queryAuditNotes(param, token), + result -> Optional.ofNullable(result) + .map(MKResultVO::getData) + .orElse(null)); + if (CollectionUtil.isEmpty(notes)) { + return notes; + } + for (MKAuditNoteVO note : notes) { + // 处理审批意见的附件全名 + handleFullName(note.getAttachmentParameter(), note.getMechanisms()); + if (CollectionUtil.isNotEmpty(note.getProcessComments())) { + for (MKProcessCommentVO processComment : note.getProcessComments()) { + // 处理流程附言的附件全名 + handleFullName(processComment.getAttachments(), processComment.getMechanisms()); + } + } + } + return notes; + } + + /** + * 处理附件全名 + * @param attachments + * @param mechanisms + */ + private void handleFullName(List attachments, MKMechanismsVO mechanisms) { + if (CollectionUtil.isNotEmpty(attachments) && mechanisms != null && CollectionUtil.isNotEmpty(mechanisms.getAttachment())) { + Map fileIdFullNameMap = mechanisms.getAttachment().stream() + .collect(Collectors.toMap(MKMetaAttachmentVO::getId, MKMetaAttachmentVO::getFullName, (a, b) -> a)); + for (MKAttachmentVO attachment : attachments) { + String fullName = fileIdFullNameMap.get(attachment.getFileId()); + if (StringUtil.isNotBlank(fullName)) { + // 附件名替换为全名 + attachment.setName(fullName); + } + } + } + } + + @Override + public List querySenderList(MKSenderDTO param) { + AssertUtils.notNull(param, "参数不能为空"); + AssertUtils.notBlank(param.getNodeInstanceId(),"节点实例id不能为空"); + AssertUtils.notBlank(param.getProcessInstanceId(),"流程实例id不能为空"); + return commonCall("查询抄送人列表", token -> { + // 查询100,一次都查完 + param.setPageSize(100); + return client.querySenderList(param, token); + }, result -> Optional.ofNullable(result) + .map(MKResultVO::getData) + .map(MKPageVO::getContent) + .orElse(Collections.emptyList())); + } + + @Override + public void downloadFile(HttpServletResponse response, String fileId) { + AssertUtils.notBlank(fileId,"附件id不能为空"); + String accessToken = getToken(); + try (OutputStream outputStream = response.getOutputStream();) { + try (Response feignResponse = client.downloadFile(new MKFileDTO(fileId), accessToken);) { + InputStream inputStream = feignResponse.body().asInputStream(); + Map> responseHeaders = feignResponse.headers(); + response.setHeader("Content-disposition", getHeader(responseHeaders, "Content-disposition")); + // response.setHeader("Content-Type", getHeader(responseHeaders, "Content-Type")); + response.setHeader("Content-Type", "image/png"); + IOUtils.copy(inputStream, outputStream); + } catch (MKException e) { + response.setHeader("Content-Type", MediaType.APPLICATION_JSON_VALUE); + outputStream.write(Optional.ofNullable(e.getMessage()).orElse("调用mk接口 下载附件异常").getBytes(StandardCharsets.UTF_8)); + } catch (Exception e) { + log.error("调用mk接口 下载附件异常", e); + response.setHeader("Content-Type", MediaType.APPLICATION_JSON_VALUE); + outputStream.write("调用mk接口 下载附件异常".getBytes(StandardCharsets.UTF_8)); + } + } catch (Exception e) { + log.error("下载mk附件异常", e); + } + + } + + @Override + public String getFileBase64(String fileId) { + AssertUtils.notBlank(fileId,"附件id不能为空"); + String accessToken = getToken(); + try (Response feignResponse = client.downloadFile(new MKFileDTO(fileId), accessToken);) { + InputStream inputStream = feignResponse.body().asInputStream(); + byte[] bytes = IOUtils.toByteArray(inputStream); + return Base64.getEncoder().encodeToString(bytes); + } catch (MKException e) { + throw e; + } catch (Exception e) { + log.error("调用mk接口 下载附件异常", e); + } + return null; + } + + @Override + public String getNodeNumber(String templateId, String nodeId) { + if (StringUtil.isBlank(templateId) || StringUtil.isBlank(nodeId)) { + log.warn("查询节点编号模板id或节点id为空 模板id:{} 节点id:{}", templateId, nodeId); + return null; + } + return commonCall("查询模板节点信息", token -> { + return client.getManualNode(new MKManualNodeDTO(templateId), token); + }, result -> Optional.ofNullable(result) + .map(MKResultVO::getData) + .flatMap(nodes -> nodes.stream() + .filter(node -> StringUtil.equals(node.getNodeId(), nodeId)) + .findFirst() + .map(MKManualNodeVO::getNodeNumber) + ).orElse(null)); + } + + @Override + public MKPageVO queryApprovalList(MKApprovalDTO param) { + AssertUtils.notNull(param, "参数不能为空"); + AssertUtils.notNull(param.getConditions(),"条件不能为空"); + AssertUtils.notNull(param.getConditions().getMydoc(),"类型不能为空"); + return commonCall("查询审批中心列表", token -> client.queryApprovalList(param, token), + result -> Optional.ofNullable(result) + .map(MKResultVO::getData) + .orElse(MKPageVO.empty())); + } + + @Override + public MKPageVO queryProcessList(MKProcessDTO param) { + AssertUtils.notNull(param, "参数不能为空"); + AssertUtils.notBlank(param.getLoginName(),"登录名不能为空"); + AssertUtils.notNull(param.getMydoc(),"类型不能为空"); + return commonCall("查询流程列表", token -> client.queryProcessList(param, token), + result -> Optional.ofNullable(result) + .map(MKResultVO::getData) + .orElse(MKPageVO.empty())); + } + + /** + * 获取响应头 + * @param responseHeaders + * @param header + * @return + */ + private String getHeader(Map> responseHeaders, String header) { + Collection collection = responseHeaders.get(header); + if (CollectionUtil.isEmpty(collection)) { + return null; + } + return collection.stream().findFirst().orElse(null); + } + + /** + * 获取token + * + * @return + */ + private String getToken() { + String token = bladeRedis.get(MKConstant.MK_TOKEN_KEY); + if (token != null) { + return token; + } + try { + MKTokenDTO param = new MKTokenDTO(); + param.setAppKey(properties.getAppKey()); + param.setAppSecret(properties.getAppSecret()); + long start = System.currentTimeMillis(); + log.info("调用mk获取token开始"); + MKTokenVO tokenResult = client.getToken(param); + long end = System.currentTimeMillis(); + log.info("调用mk获取token结束 耗时:{}", end - start); + if (tokenResult != null && tokenResult.getErrCode() == MKConstant.TOKEN_SUCCESS_CODE) { + // 成功 + String accessToken = tokenResult.getAccessToken(); + int expiresIn = tokenResult.getExpiresIn(); + // 过期时间 - 100秒,防止刚好过期 + bladeRedis.getValueOps().setIfAbsent(MKConstant.MK_TOKEN_KEY, accessToken, Duration.ofSeconds(expiresIn - 100)); + return accessToken; + } + log.error("调用mk接口 获取token 异常响应参数:{}", JSONUtil.toJsonStr(tokenResult)); + throw new MKException("调用mk接口获取token异常"); + } catch (MKException e) { + throw e; + } catch (Exception e) { + log.error("调用mk接口 获取token 异常", e); + throw new MKException("调用mk接口获取token异常"); + } + } +} diff --git a/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/support/MultiKeySerializer.java b/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/support/MultiKeySerializer.java new file mode 100644 index 0000000..72fb7ba --- /dev/null +++ b/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/support/MultiKeySerializer.java @@ -0,0 +1,37 @@ +package org.springblade.thirdparty.mk.support; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; +import org.springblade.core.tool.utils.CollectionUtil; +import org.springblade.thirdparty.mk.pojo.dto.approval.MKConditionDTO; + +import java.io.IOException; +import java.util.List; +import java.util.Objects; + +/** + * @author bfhuange + * @since 2025/5/21 + */ +public class MultiKeySerializer extends JsonSerializer> { + @Override + public void serialize(List values, JsonGenerator gen, SerializerProvider serializers) throws IOException { + values = values.stream() + .filter(Objects::nonNull) + .toList(); + if (CollectionUtil.isNotEmpty(values)) { + // 获取当前字段名 + String fieldName = gen.getOutputContext().getCurrentName(); + boolean writeValue = false; + for (MKConditionDTO value : values) { + if (writeValue) { + // 写过值了,要再添加一个字段名 + gen.writeFieldName(fieldName); + } + gen.writeObject(value); + writeValue = true; + } + } + } +} diff --git a/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/util/AssertUtils.java b/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/util/AssertUtils.java new file mode 100644 index 0000000..5b640f1 --- /dev/null +++ b/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/util/AssertUtils.java @@ -0,0 +1,266 @@ +package org.springblade.thirdparty.mk.util; + +import org.springblade.core.tool.utils.StringUtil; +import org.springblade.thirdparty.mk.exception.MKException; +import org.springframework.util.CollectionUtils; +import org.springframework.util.ObjectUtils; + +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * @author bfhuange + * @since 2025/2/24 + */ +public class AssertUtils { + + /** + * 对象非空 + * @param object + * @param message + */ + public static void notNull(Object object, String message) { + if (object == null) { + throw new MKException(message); + } + } + + /** + * Map非空 + * @param map + * @param message + */ + public static void notEmpty(Map map, String message) { + if (CollectionUtils.isEmpty(map)) { + throw new MKException(message); + } + } + + /** + * 列表非空 + * @param collection + * @param message + */ + public static void notEmpty(Collection collection, String message) { + if (CollectionUtils.isEmpty(collection)) { + throw new MKException(message); + } + } + + /** + * 数组非空 + * @param array + * @param message + */ + public static void notEmpty(Object[] array, String message) { + if (ObjectUtils.isEmpty(array)) { + throw new MKException(message); + } + } + + /** + * 字符串非空 + * @param value + * @param message + */ + public static void notEmpty(String value, String message) { + if (StringUtil.hasLength(value)) { + throw new MKException(message); + } + } + + /** + * 字符串去除空格非空 + * @param value + * @param message + */ + public static void notBlank(String value, String message) { + if (StringUtil.isBlank(value)) { + throw new MKException(message); + } + } + + /** + * 获取重复数据 + * @param dataList + * @return + * @throws MKException + */ + public static List getRepeatData(List dataList) { + if(CollectionUtils.isEmpty(dataList)) { + return null; + } + //判断是否有重复数据 + List distinctDataList = dataList.stream().distinct().toList(); + if(dataList.size() == distinctDataList.size()) { + return null; + } + List repeatList = new ArrayList<>(); + Map dataMap = dataList.stream().collect(Collectors.groupingBy(p -> p,Collectors.counting())); + //处理重复数据 + for(Map.Entry entry:dataMap.entrySet()) { + if(entry.getValue() > 1) { + repeatList.add(entry.getKey()); + } + } + return repeatList; + } + + /** + * 数据不允许重复 + * @param dataList + * @param message + * @throws MKException + */ + public static void notRepeat(List dataList,String message) { + //获取重复数据 + List repeatList = getRepeatData(dataList); + //若有重复数据,则校验不通过处理 + if(!CollectionUtils.isEmpty(repeatList)) { + String repeatData = String.join(",",repeatList); + throw new MKException(String.format(message,repeatData)); + } + } + + /** + * 字符串相等校验 + * @param str1 + * @param str2 + * @param message + * @throws MKException + */ + public static void equals(String str1, String str2, String message) { + if (!StringUtil.equals(str1,str2)) { + throw new MKException(message); + } + } + + /** + * 数字相等校验 + * @param num1 + * @param num2 + * @param message + * @throws MKException + */ + public static void equals(BigDecimal num1, BigDecimal num2, String message) { + if (num1.compareTo(num2) != 0) { + throw new MKException(message); + } + } + + /** + * 数字不大于校验 + * @param num1 + * @param num2 + * @param message + * @throws MKException + */ + public static void notGreaterThan(BigDecimal num1, BigDecimal num2, String message) { + if(num1.compareTo(num2) > 0) { + throw new MKException(message); + } + } + + /** + * 数字不大于校验 + * @param num1 + * @param num2 + * @param message + * @throws MKException + */ + public static void notGreaterThan(Integer num1, Integer num2, String message) { + if(num1 > num2) { + throw new MKException(message); + } + } + + /** + * 数字不小于校验 + * @param num1 + * @param num2 + * @param message + * @throws MKException + */ + public static void notLessThan(BigDecimal num1, BigDecimal num2, String message) { + if(num1.compareTo(num2) < 0) { + throw new MKException(message); + } + } + + /** + * 表达式为真判断校验 + * @param expression + * @param message + * @throws MKException + */ + public static void isTrue(boolean expression, String message) { + if(!expression) { + throw new MKException(message); + } + } + + /** + * 表达式判断为假校验 + * @param expression + * @param message + * @throws MKException + */ + public static void isFalse(boolean expression, String message) { + if(expression) { + throw new MKException(message); + } + } + + /** + * 字符串不相等校验 + * @param str1 + * @param str2 + * @param message + * @throws MKException + */ + public static void notEquals(String str1, String str2, String message) { + if (StringUtil.equals(str1,str2)) { + throw new MKException(message); + } + } + + /** + * 对象为空 + * @param object + * @param message + */ + public static void isNull(Object object, String message) { + if (object != null) { + throw new MKException(message); + } + } + + /** + * 对象为数值 + * @param value + * @param message + * @throws MKException + */ + public static void isInteger(String value,String message) { + try { + Integer.valueOf(value); + } catch (NumberFormatException e) { + throw new MKException(message); + } + } + + /** + * 列表为空 + * @param collection + * @param message + */ + public static void isEmpty(Collection collection, String message) { + if (!CollectionUtils.isEmpty(collection)) { + throw new MKException(message); + } + } +} diff --git a/blade-third-party-api/blade-mk-api/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/blade-third-party-api/blade-mk-api/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 0000000..cd2c109 --- /dev/null +++ b/blade-third-party-api/blade-mk-api/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1 @@ +org.springblade.thirdparty.mk.config.ThirdPartyMKAutoConfiguration \ No newline at end of file diff --git a/blade-third-party-api/blade-oa-api/pom.xml b/blade-third-party-api/blade-oa-api/pom.xml new file mode 100644 index 0000000..857e2bb --- /dev/null +++ b/blade-third-party-api/blade-oa-api/pom.xml @@ -0,0 +1,22 @@ + + + 4.0.0 + + org.springblade + blade-third-party-api + ${revision} + + + blade-oa-api + ${project.artifactId} + jar + + + + org.springblade + blade-core-tool + + + \ No newline at end of file diff --git a/blade-third-party-api/blade-oa-api/src/main/java/org/springblade/thirdparty/oa/config/OAFeignClientConfig.java b/blade-third-party-api/blade-oa-api/src/main/java/org/springblade/thirdparty/oa/config/OAFeignClientConfig.java new file mode 100644 index 0000000..c0b9cf4 --- /dev/null +++ b/blade-third-party-api/blade-oa-api/src/main/java/org/springblade/thirdparty/oa/config/OAFeignClientConfig.java @@ -0,0 +1,23 @@ +package org.springblade.thirdparty.oa.config; + +import feign.RequestInterceptor; +import jakarta.annotation.Resource; +import org.springframework.context.annotation.Bean; + +/** + * @author bfhuange + * @since 2024/12/18 + */ +public class OAFeignClientConfig { + @Resource + OAProperties oaProperties; + + @Bean + public RequestInterceptor requestInterceptor() { + return template -> { + // 空实现屏蔽 全局拦截器 BladeFeignRequestInterceptor + String authorization=oaProperties.getAuthorization(); + template.header("Authorization",authorization); + }; + } +} diff --git a/blade-third-party-api/blade-oa-api/src/main/java/org/springblade/thirdparty/oa/config/OAProperties.java b/blade-third-party-api/blade-oa-api/src/main/java/org/springblade/thirdparty/oa/config/OAProperties.java new file mode 100644 index 0000000..bfcbae1 --- /dev/null +++ b/blade-third-party-api/blade-oa-api/src/main/java/org/springblade/thirdparty/oa/config/OAProperties.java @@ -0,0 +1,23 @@ +package org.springblade.thirdparty.oa.config; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + * oa配置类 + * @author bfhuange + * @date 2024/8/22 + */ +@Data +@ConfigurationProperties(prefix = "third-party.oa") +public class OAProperties { + /** + * oa基础url + */ + private String baseUrl; + + /** + * authorization + */ + private String authorization; +} diff --git a/blade-third-party-api/blade-oa-api/src/main/java/org/springblade/thirdparty/oa/config/ThirdPartyOAAutoConfiguration.java b/blade-third-party-api/blade-oa-api/src/main/java/org/springblade/thirdparty/oa/config/ThirdPartyOAAutoConfiguration.java new file mode 100644 index 0000000..d79e5d2 --- /dev/null +++ b/blade-third-party-api/blade-oa-api/src/main/java/org/springblade/thirdparty/oa/config/ThirdPartyOAAutoConfiguration.java @@ -0,0 +1,14 @@ +package org.springblade.thirdparty.oa.config; + +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +/** + * 第三方api oa配置类 + * @author bfhuange + * @date 2024/8/27 + */ +@EnableConfigurationProperties(OAProperties.class) +@Configuration +public class ThirdPartyOAAutoConfiguration { +} diff --git a/blade-third-party-api/blade-oa-api/src/main/java/org/springblade/thirdparty/oa/constant/OAConstant.java b/blade-third-party-api/blade-oa-api/src/main/java/org/springblade/thirdparty/oa/constant/OAConstant.java new file mode 100644 index 0000000..18579fb --- /dev/null +++ b/blade-third-party-api/blade-oa-api/src/main/java/org/springblade/thirdparty/oa/constant/OAConstant.java @@ -0,0 +1,17 @@ +package org.springblade.thirdparty.oa.constant; + +/** + * oa常量类 + * @author bfhuange + * @date 2024/8/22 + */ +public class OAConstant { + /** + * oa成功响应码 + */ + public static final String OA_SUCCESS_CODE = "1"; + /** + * 根公司或根部门id + */ + public static final String ROOT_COMPANY_ID = "0"; +} diff --git a/blade-third-party-api/blade-oa-api/src/main/java/org/springblade/thirdparty/oa/constant/OAConvertConstant.java b/blade-third-party-api/blade-oa-api/src/main/java/org/springblade/thirdparty/oa/constant/OAConvertConstant.java new file mode 100644 index 0000000..2182784 --- /dev/null +++ b/blade-third-party-api/blade-oa-api/src/main/java/org/springblade/thirdparty/oa/constant/OAConvertConstant.java @@ -0,0 +1,33 @@ +package org.springblade.thirdparty.oa.constant; + +/** + * OA转换常量类 + * @author bfhuange + * @date 2024/8/27 + */ +public class OAConvertConstant { + /** + * oa公司转部门编码前缀 + */ + public static final String COMPANY_OA_PREFIX = "OAC"; + /** + * oa部门转部门编码前缀 + */ + public static final String DEPARTMENT_OA_PREFIX = "OAD"; + /** + * 外部公司部门编码前缀 + */ + public static final String COMPANY_EX_PREFIX = "EXC"; + /** + * 外部部门编码前缀 + */ + public static final String DEPARTMENT_EX_PREFIX = "EXD"; + /** + * oa人员转用户编码前缀 + */ + public static final String PERSON_OA_PREFIX = "OAP"; + /** + * 根公司父id + */ + public static final Long ROOT_PARENT_ID = 0L; +} diff --git a/blade-third-party-api/blade-oa-api/src/main/java/org/springblade/thirdparty/oa/feign/IOAClient.java b/blade-third-party-api/blade-oa-api/src/main/java/org/springblade/thirdparty/oa/feign/IOAClient.java new file mode 100644 index 0000000..b571416 --- /dev/null +++ b/blade-third-party-api/blade-oa-api/src/main/java/org/springblade/thirdparty/oa/feign/IOAClient.java @@ -0,0 +1,47 @@ +package org.springblade.thirdparty.oa.feign; + +import org.springblade.thirdparty.oa.config.OAFeignClientConfig; +import org.springblade.thirdparty.oa.pojo.response.OACompanyResponse; +import org.springblade.thirdparty.oa.pojo.response.OADepartmentResponse; +import org.springblade.thirdparty.oa.pojo.response.OAPersonResponse; +import org.springblade.thirdparty.oa.pojo.response.OAResponse; +import org.springblade.thirdparty.oa.pojo.search.OACompanySearch; +import org.springblade.thirdparty.oa.pojo.search.OADepartmentSearch; +import org.springblade.thirdparty.oa.pojo.search.OAPersonSearch; +import org.springblade.thirdparty.oa.pojo.search.OASearch; +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; + +/** + * oa接口 + * @author bfhuange + * @date 2024/8/22 + */ +@FeignClient(name = "OA", url = "${thirdParty.oa.baseUrl}", configuration = OAFeignClientConfig.class) +public interface IOAClient { + + /** + * 查询公司分页 + * @param param + * @return + */ + @PostMapping("${thirdParty.oa.queryCompanyPageUrl:/api/hrm/resful/getHrmsubcompanyWithPage}") + OAResponse queryCompanyPage(@RequestBody OASearch param); + + /** + * 查询部门分页 + * @param param + * @return + */ + @PostMapping("${thirdParty.oa.queryDepartmentPage:/api/hrm/resful/getHrmdepartmentWithPage}") + OAResponse queryDepartmentPage(@RequestBody OASearch param); + + /** + * 查询人员分页,用户和公司、部门的关系,存在同一个用户,多条不同公司、部门数据 + * @param param + * @return + */ + @PostMapping("${thirdParty.oa.queryPersonPageUrl:/api/hrm/resful/getHrmUserInfoWithPage}") + OAResponse queryPersonPage(@RequestBody OASearch param); +} diff --git a/blade-third-party-api/blade-oa-api/src/main/java/org/springblade/thirdparty/oa/pojo/response/OACompanyResponse.java b/blade-third-party-api/blade-oa-api/src/main/java/org/springblade/thirdparty/oa/pojo/response/OACompanyResponse.java new file mode 100644 index 0000000..785b06f --- /dev/null +++ b/blade-third-party-api/blade-oa-api/src/main/java/org/springblade/thirdparty/oa/pojo/response/OACompanyResponse.java @@ -0,0 +1,28 @@ +package org.springblade.thirdparty.oa.pojo.response; + +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; + +/** + * @Author alexbzhang + * @Date 25/11/2022 14 34 + * @Version 1.0 + */ +@Data +public class OACompanyResponse implements Serializable { + @Serial + private static final long serialVersionUID = 1L; + + private String subcompanydesc; + private String canceled; + private String subcompanycode; + private String supsubcomid; + private String created; + private String modified; + private String id; + private String subcompanyname; + private String url; + private String showorder; +} diff --git a/blade-third-party-api/blade-oa-api/src/main/java/org/springblade/thirdparty/oa/pojo/response/OADepartmentResponse.java b/blade-third-party-api/blade-oa-api/src/main/java/org/springblade/thirdparty/oa/pojo/response/OADepartmentResponse.java new file mode 100644 index 0000000..709be7f --- /dev/null +++ b/blade-third-party-api/blade-oa-api/src/main/java/org/springblade/thirdparty/oa/pojo/response/OADepartmentResponse.java @@ -0,0 +1,29 @@ +package org.springblade.thirdparty.oa.pojo.response; + +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; + +/** + * @Author alexbzhang + * @Date 25/11/2022 14 40 + * @Version 1.0 + */ +@Data +public class OADepartmentResponse implements Serializable { + @Serial + private static final long serialVersionUID = 1L; + + private String canceled; + private String supdepid; + private String departmentmark; + private String departmentname; + private String created; + private String departmentcode; + private String modified; + private String id; + private String subcompanyid1; + private String showorder; + +} diff --git a/blade-third-party-api/blade-oa-api/src/main/java/org/springblade/thirdparty/oa/pojo/response/OAPersonResponse.java b/blade-third-party-api/blade-oa-api/src/main/java/org/springblade/thirdparty/oa/pojo/response/OAPersonResponse.java new file mode 100644 index 0000000..5a2fb5c --- /dev/null +++ b/blade-third-party-api/blade-oa-api/src/main/java/org/springblade/thirdparty/oa/pojo/response/OAPersonResponse.java @@ -0,0 +1,76 @@ +package org.springblade.thirdparty.oa.pojo.response; + +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; + +/** + * @Author alexbzhang + * @Date 25/11/2022 14:20 + * @Version 1.0 + */ +@Data +public class OAPersonResponse implements Serializable { + @Serial + private static final long serialVersionUID = 1L; + + private String companystartdate; + private String tempresidentnumber; + private String createdate; + private String language; + private String workstartdate; + private String subcompanyid1; + private String subcompanyname; + private String joblevel; + private String startdate; + private String password; + private String subcompanycode; + private String jobactivitydesc; + private String bememberdate; + private String modified; + private String id; + private String mobilecall; + private String nativeplace; + private String certificatenum; + private String height; + private String loginid; + private String created; + private String degree; + private String bepartydate; + private String weight; + private String telephone; + private String residentplace; + private String lastname; + private String healthinfo; + private String enddate; + private String maritalstatus; + private String departmentname; + private String folk; + /** + * 状态 :0:试用1:正式2:临时3:试用延期4:解聘5:离职6:退休7:无效 + */ + private String status; + private String birthday; + private String accounttype; + private String jobcall; + private String managerid; + private String assistantid; + private String departmentcode; + private String belongto; + private String email; + private String seclevel; + private String policy; + private String jobtitle; + private String workcode; + private String sex; + private String departmentid; + private String homeaddress; + private String mobile; + private String lastmoddate; + private String educationlevel; + private String islabouunion; + private String locationid; + private String regresidentplace; + private String dsporder; +} diff --git a/blade-third-party-api/blade-oa-api/src/main/java/org/springblade/thirdparty/oa/pojo/response/OAResponse.java b/blade-third-party-api/blade-oa-api/src/main/java/org/springblade/thirdparty/oa/pojo/response/OAResponse.java new file mode 100644 index 0000000..911275e --- /dev/null +++ b/blade-third-party-api/blade-oa-api/src/main/java/org/springblade/thirdparty/oa/pojo/response/OAResponse.java @@ -0,0 +1,21 @@ +package org.springblade.thirdparty.oa.pojo.response; + +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; + +/** + * @Author alexbzhang + * @Date 25/11/2022 13:52 + * @Version 1.0 + */ +@Data +public class OAResponse implements Serializable { + @Serial + private static final long serialVersionUID = 1L; + + private String code; + + private OAResponseData data; +} diff --git a/blade-third-party-api/blade-oa-api/src/main/java/org/springblade/thirdparty/oa/pojo/response/OAResponseData.java b/blade-third-party-api/blade-oa-api/src/main/java/org/springblade/thirdparty/oa/pojo/response/OAResponseData.java new file mode 100644 index 0000000..4e3ed79 --- /dev/null +++ b/blade-third-party-api/blade-oa-api/src/main/java/org/springblade/thirdparty/oa/pojo/response/OAResponseData.java @@ -0,0 +1,26 @@ +package org.springblade.thirdparty.oa.pojo.response; + +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; +import java.util.List; + +/** + * @Author alexbzhang + * @Date 25/11/2022 13:58 + * @Version 1.0 + */ +@Data +public class OAResponseData implements Serializable { + @Serial + private static final long serialVersionUID = 1L; + + private Long totalSize; + + private List dataList; + + private Integer pageSize; + + private Integer page; +} diff --git a/blade-third-party-api/blade-oa-api/src/main/java/org/springblade/thirdparty/oa/pojo/search/OACompanySearch.java b/blade-third-party-api/blade-oa-api/src/main/java/org/springblade/thirdparty/oa/pojo/search/OACompanySearch.java new file mode 100644 index 0000000..cb9c9e2 --- /dev/null +++ b/blade-third-party-api/blade-oa-api/src/main/java/org/springblade/thirdparty/oa/pojo/search/OACompanySearch.java @@ -0,0 +1,26 @@ +package org.springblade.thirdparty.oa.pojo.search; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.io.Serial; + +/** + * oa公司分页查询参数 + * @author bfhuange + * @date 2024/8/23 + */ +@EqualsAndHashCode(callSuper = true) +@Data +public class OACompanySearch extends OAPageSearch { + @Serial + private static final long serialVersionUID = 1L; + + private String subcompanyname; + private String subcompanycode; + private String created; + private String modified; + private String canceled; + private String id; + +} diff --git a/blade-third-party-api/blade-oa-api/src/main/java/org/springblade/thirdparty/oa/pojo/search/OADepartmentSearch.java b/blade-third-party-api/blade-oa-api/src/main/java/org/springblade/thirdparty/oa/pojo/search/OADepartmentSearch.java new file mode 100644 index 0000000..6e061b2 --- /dev/null +++ b/blade-third-party-api/blade-oa-api/src/main/java/org/springblade/thirdparty/oa/pojo/search/OADepartmentSearch.java @@ -0,0 +1,27 @@ +package org.springblade.thirdparty.oa.pojo.search; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.io.Serial; + +/** + * OA部门分页查询参数 + * @author bfhuange + * @date 2024/8/23 + */ +@EqualsAndHashCode(callSuper = true) +@Data +public class OADepartmentSearch extends OAPageSearch { + @Serial + private static final long serialVersionUID = 1L; + + private String departmentname; + private String departmentcode; + private String subcompanyid1; + private String created; + private String modified; + private String canceled; + private String id; + +} diff --git a/blade-third-party-api/blade-oa-api/src/main/java/org/springblade/thirdparty/oa/pojo/search/OAPageSearch.java b/blade-third-party-api/blade-oa-api/src/main/java/org/springblade/thirdparty/oa/pojo/search/OAPageSearch.java new file mode 100644 index 0000000..a5190be --- /dev/null +++ b/blade-third-party-api/blade-oa-api/src/main/java/org/springblade/thirdparty/oa/pojo/search/OAPageSearch.java @@ -0,0 +1,29 @@ +package org.springblade.thirdparty.oa.pojo.search; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; + +/** + * oa分页查询参数 + * @author bfhuange + * @date 2024/8/23 + */ +@Data +public class OAPageSearch implements Serializable { + @Serial + private static final long serialVersionUID = 1L; + + /** + * 当前页,必填 + */ + @JsonProperty("curpage") + protected Integer curPage = 1; + /** + * 每页大小,必填 + */ + @JsonProperty("pagesize") + protected Integer pageSize = 10000; +} diff --git a/blade-third-party-api/blade-oa-api/src/main/java/org/springblade/thirdparty/oa/pojo/search/OAPersonSearch.java b/blade-third-party-api/blade-oa-api/src/main/java/org/springblade/thirdparty/oa/pojo/search/OAPersonSearch.java new file mode 100644 index 0000000..d725c57 --- /dev/null +++ b/blade-third-party-api/blade-oa-api/src/main/java/org/springblade/thirdparty/oa/pojo/search/OAPersonSearch.java @@ -0,0 +1,30 @@ +package org.springblade.thirdparty.oa.pojo.search; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.io.Serial; + +/** + * OA人员分页查询参数 + * @author bfhuange + * @date 2024/8/23 + */ +@EqualsAndHashCode(callSuper = true) +@Data +public class OAPersonSearch extends OAPageSearch { + @Serial + private static final long serialVersionUID = 1L; + + private String created; + private String modified; + private String workcode; + private String subcompanyid1; + private String departmentid; + private String jobtitleid; + private String id; + private String loginid; + + private String isadaccount; + +} diff --git a/blade-third-party-api/blade-oa-api/src/main/java/org/springblade/thirdparty/oa/pojo/search/OASearch.java b/blade-third-party-api/blade-oa-api/src/main/java/org/springblade/thirdparty/oa/pojo/search/OASearch.java new file mode 100644 index 0000000..75b4892 --- /dev/null +++ b/blade-third-party-api/blade-oa-api/src/main/java/org/springblade/thirdparty/oa/pojo/search/OASearch.java @@ -0,0 +1,23 @@ +package org.springblade.thirdparty.oa.pojo.search; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serial; +import java.io.Serializable; + +/** + * oa查询参数 + * @author bfhuange + * @date 2024/8/22 + */ +@AllArgsConstructor +@NoArgsConstructor +@Data +public class OASearch implements Serializable { + @Serial + private static final long serialVersionUID = 1L; + + private T params; +} diff --git a/blade-third-party-api/blade-oa-api/src/main/java/org/springblade/thirdparty/oa/util/OAUtils.java b/blade-third-party-api/blade-oa-api/src/main/java/org/springblade/thirdparty/oa/util/OAUtils.java new file mode 100644 index 0000000..14fd1c3 --- /dev/null +++ b/blade-third-party-api/blade-oa-api/src/main/java/org/springblade/thirdparty/oa/util/OAUtils.java @@ -0,0 +1,168 @@ +package org.springblade.thirdparty.oa.util; + +import lombok.extern.slf4j.Slf4j; +import org.springblade.thirdparty.oa.constant.OAConstant; +import org.springblade.thirdparty.oa.pojo.response.OAResponse; +import org.springblade.thirdparty.oa.pojo.response.OAResponseData; +import org.springblade.thirdparty.oa.pojo.search.OAPageSearch; +import org.springframework.util.CollectionUtils; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.function.Function; + +/** + * oa工具类 + * @author bfhuange + * @date 2024/8/23 + */ +@Slf4j +public class OAUtils { + private OAUtils() {} + + /** + * 将浮点型字符串转换成整数,字符串为空则转为1,否则截取字符串小数点前面的部门解析 + * @param doubleString 浮点类型字符串 + * @return + */ + public static int parseInt(String doubleString) { + if (doubleString == null || doubleString.isEmpty()) { + return 1; + } + try { + return Integer.parseInt(doubleString.split("\\.")[0]); + } catch (Exception e) { + log.error("解析数值字符串[{}]异常", doubleString, e); + return 1; + } + } + + /** + * 分页同步所有数据接口,返回所有数据无限制 + * @param param 接口分页参数 + * @param function 接口 + * @param exceptionFunction 异常方法 + * @return + * @param + * @param + */ + public static List pageSyncAll(T param, Function> function, Function, RuntimeException> exceptionFunction) { + return OAUtils.pageSync(param, function, exceptionFunction, Integer.MAX_VALUE); + } + + /** + * 分页同步数据接口 + * @param param 接口分页参数 + * @param function 接口 + * @param exceptionFunction 异常方法 + * @param threshold 阈值,当数据总数超过该值时,不再调用接口,返回数据 + * @return 参数是处理数据的方法的方法 + * @param + * @param + */ + public static Consumer>> pageSyncHandler(T param, Function> function, Function, RuntimeException> exceptionFunction, int threshold) { + return OAUtils.pageSyncHandler(param, function, exceptionFunction, threshold, log::info); + } + + /** + * 分页同步数据接口 + * @param param 接口分页参数 + * @param function 接口 + * @param exceptionFunction 异常方法 + * @param threshold 阈值,当数据总数超过该值时,不再调用接口,返回数据 + * @return 参数是处理数据的方法的方法 + * @param + * @param + */ + public static Consumer>> pageSyncHandler(T param, Function> function, Function, RuntimeException> exceptionFunction, int threshold, BiConsumer logMethod) { + return consumer -> { + int i = 0; + while (true) { + // 循环调用接口直到阈值或最后一页 + List data = OAUtils.pageSync(param, function, exceptionFunction, threshold, logMethod); + if (data.isEmpty()) { + // 数据为空,结束 + return; + } + logMethod.accept("批量处理OA同步数据 第{}次,{}条", new Object[]{++i, data.size()}); + // 数据不为空,处理数据 + consumer.accept(data); + if (data.size() < threshold) { + // 数据比阈值小,说明到最后一页了,结束 + return; + } + } + }; + } + + /** + * 分页同步数据接口 + * @param param 接口分页参数 + * @param function 接口 + * @param exceptionFunction 异常方法 + * @param threshold 阈值,当数据总数超过该值时,不再调用接口,返回数据 + * @return + * @param + * @param + */ + public static List pageSync(T param, Function> function, Function, RuntimeException> exceptionFunction, int threshold) { + return OAUtils.pageSync(param, function, exceptionFunction, threshold, log::info); + } + + /** + * 分页同步数据接口 + * @param param 接口分页参数 + * @param function 接口 + * @param exceptionFunction 异常方法 + * @param threshold 阈值,当数据总数超过该值时,不再调用接口,返回数据 + * @param logMethod 日志输出 + * @return + * @param + * @param + */ + public static List pageSync(T param, Function> function, Function, RuntimeException> exceptionFunction, int threshold, BiConsumer logMethod) { + long totalPage = -1; + List result = new ArrayList<>(); + do { + // 调用接口 + OAResponse oaResponse = function.apply(param); + if (oaResponse== null || !OAConstant.OA_SUCCESS_CODE.equals(oaResponse.getCode()) || oaResponse.getData() == null) { + // 状态不是成功,抛出异常 + throw exceptionFunction.apply(oaResponse); + } + OAResponseData data = oaResponse.getData(); + if (!CollectionUtils.isEmpty(data.getDataList())) { + result.addAll(data.getDataList()); + } + // 计算总页数 + totalPage = calcTotalPage(data); + logMethod.accept("OA分页同步数据完成 {}/{}页", new Object[]{param.getCurPage(), totalPage}); + // 当前页 + 1 + param.setCurPage(param.getCurPage() + 1); + // 数据量小于阈值且当前页(下一次)小于等于总页数,循环调用接口 + } while (result.size() < threshold && param.getCurPage() <= totalPage); + return result; + } + + /** + * 计算总页数 + * @param data + * @return + * @param + */ + private static long calcTotalPage(OAResponseData data) { + // 总数 + Long totalSize = data.getTotalSize(); + // 每页大小 + Integer pageSize = data.getPageSize(); + // 总页数 = 总数 / 每页大小,这里会舍去余数 + long totalPage = totalSize / pageSize; + if (totalSize % pageSize != 0) { + // 有余数总页数 +1 + totalPage += 1; + } + return totalPage; + } +} diff --git a/blade-third-party-api/blade-oa-api/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/blade-third-party-api/blade-oa-api/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 0000000..48aa73d --- /dev/null +++ b/blade-third-party-api/blade-oa-api/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1 @@ +org.springblade.thirdparty.oa.config.ThirdPartyOAAutoConfiguration \ No newline at end of file diff --git a/blade-third-party-api/blade-ocr-api/pom.xml b/blade-third-party-api/blade-ocr-api/pom.xml new file mode 100644 index 0000000..28cda3f --- /dev/null +++ b/blade-third-party-api/blade-ocr-api/pom.xml @@ -0,0 +1,27 @@ + + + 4.0.0 + + org.springblade + blade-third-party-api + ${revision} + + + blade-ocr-api + ${project.artifactId} + jar + + + + org.springblade + blade-core-tool + + + org.springblade + blade-starter-log + true + + + \ No newline at end of file diff --git a/blade-third-party-api/blade-ocr-api/src/main/java/org/springblade/thirdparty/ocr/config/OCRFeignClientConfig.java b/blade-third-party-api/blade-ocr-api/src/main/java/org/springblade/thirdparty/ocr/config/OCRFeignClientConfig.java new file mode 100644 index 0000000..194f13f --- /dev/null +++ b/blade-third-party-api/blade-ocr-api/src/main/java/org/springblade/thirdparty/ocr/config/OCRFeignClientConfig.java @@ -0,0 +1,24 @@ +package org.springblade.thirdparty.ocr.config; + +import feign.Request; +import feign.RequestInterceptor; +import org.springblade.thirdparty.ocr.interceptor.OCRRequestInterceptor; +import org.springframework.context.annotation.Bean; + +import java.util.concurrent.TimeUnit; + +/** + * @author bfhuange + * @since 2024/12/18 + */ +public class OCRFeignClientConfig { + @Bean + public RequestInterceptor requestInterceptor() { + return new OCRRequestInterceptor(); + } + + @Bean + public Request.Options options() { + return new Request.Options(10, TimeUnit.SECONDS, 60, TimeUnit.SECONDS, true); + } +} diff --git a/blade-third-party-api/blade-ocr-api/src/main/java/org/springblade/thirdparty/ocr/config/ThirdPartyOCRAutoConfiguration.java b/blade-third-party-api/blade-ocr-api/src/main/java/org/springblade/thirdparty/ocr/config/ThirdPartyOCRAutoConfiguration.java new file mode 100644 index 0000000..7a721a4 --- /dev/null +++ b/blade-third-party-api/blade-ocr-api/src/main/java/org/springblade/thirdparty/ocr/config/ThirdPartyOCRAutoConfiguration.java @@ -0,0 +1,14 @@ +package org.springblade.thirdparty.ocr.config; + +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; + +/** + * 第三方api ocr配置类 + * @author bfhuange + * @date 2024/12/18 + */ +@ComponentScan("org.springblade.thirdparty.ocr") +@Configuration +public class ThirdPartyOCRAutoConfiguration { +} diff --git a/blade-third-party-api/blade-ocr-api/src/main/java/org/springblade/thirdparty/ocr/constant/OCRConstant.java b/blade-third-party-api/blade-ocr-api/src/main/java/org/springblade/thirdparty/ocr/constant/OCRConstant.java new file mode 100644 index 0000000..2da10f5 --- /dev/null +++ b/blade-third-party-api/blade-ocr-api/src/main/java/org/springblade/thirdparty/ocr/constant/OCRConstant.java @@ -0,0 +1,38 @@ +package org.springblade.thirdparty.ocr.constant; + +/** + * ocr 常量类 + * @author bfhuange + * @date 2024/11/4 + */ +public class OCRConstant { + /** + * 成功状态码 + */ + public static final Integer SUCCESS_CODE = 200; + + /** + * 重量单位 kg + */ + public static final String UNIT_KG = "kg"; + + /** + * 重量大卫 t + */ + public static final String UNIT_T = "t"; + + /** + * 重量单位 吨 + */ + public static final String UNIT_T_ZH = "吨"; + + /** + * 请求头,请求时间 + */ + public static final String REQUEST_HEADER_TIME = "request_time"; + /** + * 请求头,trace id + */ + public static final String REQUEST_HEADER_TRACE_ID = "trace_id"; + +} diff --git a/blade-third-party-api/blade-ocr-api/src/main/java/org/springblade/thirdparty/ocr/constant/OCRImageType.java b/blade-third-party-api/blade-ocr-api/src/main/java/org/springblade/thirdparty/ocr/constant/OCRImageType.java new file mode 100644 index 0000000..b0891ef --- /dev/null +++ b/blade-third-party-api/blade-ocr-api/src/main/java/org/springblade/thirdparty/ocr/constant/OCRImageType.java @@ -0,0 +1,105 @@ +package org.springblade.thirdparty.ocr.constant; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.Collections; +import java.util.List; + +/** + * ocr 图像类型枚举 + * @author bfhuange + * @date 2024/11/4 + */ +@AllArgsConstructor +@Getter +public enum OCRImageType { + /** + * 身份证-正⻚ + */ + ID_CARD_MAIN("IDCard-Main", "身份证-正⻚", OCRKey.ID_CARD_MAIN_KEYS), + /** + * 身份证-副⻚ + */ + ID_CARD_SIDE("IDCard-Side", "身份证-副⻚", OCRKey.ID_CARD_SIDE_KEYS), + /** + * 驾驶证-电子版 + */ + DRIVER_LICENSE_ELEC("DriverLicense-Elec", "驾驶证-电子版", OCRKey.DRIVER_LICENSE_MAIN_KEYS), + /** + * 驾驶证-正⻚ + */ + DRIVER_LICENSE_MAIN("DriverLicense-Main", "驾驶证-正⻚", OCRKey.DRIVER_LICENSE_MAIN_KEYS), + /** + * 驾驶证-副⻚ + */ + DRIVER_LICENSE_SIDE("DriverLicense-Side", "驾驶证-副⻚", OCRKey.DRIVER_LICENSE_SIDE_KEYS), + /** + * ⾏驶证-正⻚-挂车 + */ + VEHICLE_LICENSE_MAIN_GUA("VehicleLicense-Main-Gua", "⾏驶证-正⻚-挂车", OCRKey.VEHICLE_LICENSE_MAIN_KEYS), + /** + * ⾏驶证-正⻚-其他 + */ + VEHICLE_LICENSE_MAIN_OTHERS("VehicleLicense-Main-Others", "⾏驶证-正⻚-其他", OCRKey.VEHICLE_LICENSE_MAIN_KEYS), + /** + * ⾏驶证-正⻚ + */ + VEHICLE_LICENSE_MAIN("VehicleLicense-Main", "⾏驶证-正⻚", OCRKey.VEHICLE_LICENSE_MAIN_KEYS), + /** + * ⾏驶证-副⻚ + */ + VEHICLE_LICENSE_SIDE("VehicleLicense-Side", "⾏驶证-副⻚", OCRKey.VEHICLE_LICENSE_SIDE_KEYS), + /** + * ⻋头 + */ + CAR_FRONT("Car-Front", "⻋头", OCRKey.CAR_FRONT_KEYS), + /** + * ⻋尾 + */ + CAR_BACK("Car-Back", "⻋尾", OCRKey.CAR_BACK_KEYS), + // =======================下面是非erp识别所需信息,录入配置非磅单类型==================== + /** + * 未知 + */ + UNKNOWN("UNK", "未知"), + /** + * 其他 + */ + OTHERS("Others", "其他"), + /** + * 证件 + */ + CARD("CARD", "证件"), + /** + * 碎片 + */ + SCRAP("Scrap", "碎片"), + /** + * 有⻋辆没有检测到⻋牌 + */ + NO_PLATE("NoPlate", "有⻋辆没有检测到⻋牌"), + /** + * ⽆⻋ + */ + NO_CAR("NoCar", "⽆⻋"), + ; + /** + * 类型标识 + */ + private final String id; + /** + * 类型 + */ + private final String type; + /** + * key列表 + */ + private final List keys; + + OCRImageType(String id, String type) { + this.id = id; + this.type = type; + this.keys = Collections.emptyList(); + } +} diff --git a/blade-third-party-api/blade-ocr-api/src/main/java/org/springblade/thirdparty/ocr/constant/OCRKey.java b/blade-third-party-api/blade-ocr-api/src/main/java/org/springblade/thirdparty/ocr/constant/OCRKey.java new file mode 100644 index 0000000..1ea4926 --- /dev/null +++ b/blade-third-party-api/blade-ocr-api/src/main/java/org/springblade/thirdparty/ocr/constant/OCRKey.java @@ -0,0 +1,331 @@ +package org.springblade.thirdparty.ocr.constant; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.List; + +/** + * ocr 各类型字段枚举 + * @author bfhuange + * @date 2024/11/4 + */ +public class OCRKey { + /** + * 驾驶证-正⻚ 字段列表 + */ + public static final List DRIVER_LICENSE_MAIN_KEYS = List.of( + // 证号 + KeyInfo.ID_NO, + // 姓名 + KeyInfo.NAME, + // 性别 + KeyInfo.SEX, + // 出⽣⽇期 + KeyInfo.DATE_BIRTH, + // 初次领证⽇期 + KeyInfo.DATE_FIRST_ISSUE, + // 准驾⻋型 + KeyInfo.CLASS, + // 有效期限(起始时间) + // KeyInfo.DATE_VALID_START, + // 有效期限(终⽌时间) + // KeyInfo.DATE_VALID_END, + // 有效期限 + KeyInfo.DATE_VALID, + // 住址 + KeyInfo.ADDRESS + ); + /** + * 驾驶证-副⻚ 字段列表 + */ + public static final List DRIVER_LICENSE_SIDE_KEYS = List.of( + // 档案编号 + KeyInfo.FILE_NO + ); + /** + * ⾏驶证-正⻚ 字段列表 + */ + public static final List VEHICLE_LICENSE_MAIN_KEYS = List.of( + // 车牌号 + KeyInfo.PLATE_NO, + // ⻋辆类型 + KeyInfo.VEHICLE_TYPE, + // 姓名 + KeyInfo.NAME, + // 使⽤性质 + KeyInfo.USE_CHARACTER, + // 品牌型号 + KeyInfo.MODEL, + // ⻋架号 + KeyInfo.VIN, + // 发动机号码 + KeyInfo.ENGINE_NO, + // 注册⽇期 + KeyInfo.DATE_REGISTER, + // 发证⽇期 + KeyInfo.DATE_ISSUE, + // 住址 + KeyInfo.ADDRESS + ); + /** + * ⾏驶证-副⻚ 字段列表 + */ + public static final List VEHICLE_LICENSE_SIDE_KEYS = List.of( + // 档案编号 + KeyInfo.FILE_NO, + // 检验有效期 + KeyInfo.DATE_VALID_INSPECTION + ); + /** + * ⻋头 字段列表 + */ + public static final List CAR_FRONT_KEYS = List.of( + // ⻋牌号 + KeyInfo.PLATE_NO + ); + /** + * ⻋尾 字段列表 + */ + public static final List CAR_BACK_KEYS = List.of( + // ⻋牌号 + KeyInfo.PLATE_NO + ); + /** + * 过磅单 字段列表 + */ + public static final List POUND_KEYS = List.of( + // ⽇期 + KeyInfo.DATE, + // 单位 + KeyInfo.UNIT_MEASURE, + // ⻋牌号 + KeyInfo.PLATE_NO, + // 货品 + KeyInfo.NAME_GOODS, + // 收货单位 + KeyInfo.COMPANY_RECEIPT, + // 发货单位 + KeyInfo.COMPANY_SHIP, + // ⽑重 + KeyInfo.WEIGHT_GROSS, + // ⽪重 + KeyInfo.WEIGHT_TARE, + // 净重 + KeyInfo.WEIGHT_NET, + // 扣重 + KeyInfo.WEIGHT_DEDUCT, + // 实际净重 + KeyInfo.WEIGHT_RECEIVED + ); + /** + * 身份证-正⻚ 字段列表 + */ + public static final List ID_CARD_MAIN_KEYS = List.of( + // 姓名 + KeyInfo.NAME, + // 公民身份号码 + KeyInfo.ID_NO, + // 性别 + KeyInfo.ID_CARD_MAIN_GENDER, + // 民族 + KeyInfo.ID_CARD_MAIN_ETHNIC_GROUP, + // 出生 + KeyInfo.DATE, + // 住址 + KeyInfo.ID_CARD_MAIN_ADD + ); + /** + * 身份证-副⻚ 字段列表 + */ + public static final List ID_CARD_SIDE_KEYS = List.of( + // 签发机关 + KeyInfo.ISSUING_AUTHORITY, + // 有效期限 + KeyInfo.DATE_VALID + ); + + /** + * 所有类型 + */ + @AllArgsConstructor + @Getter + public enum KeyInfo { + /** + * 住址 + */ + ADDRESS("address", "住址"), + /** + * 证号 + */ + ID_NO("ID_no", "证号"), + /** + * 发动机号码 + */ + ENGINE_NO("engine_no", "发动机号码"), + /** + * 档案编号 + */ + FILE_NO("file_no", "档案编号"), + /** + * 供货单位、客户名称、发货单位 + */ + COMPANY_SHIP("company_ship", "供货单位、客户名称、发货单位"), + /** + * 收货单位 + */ + COMPANY_RECEIPT("company_receipt", "收货单位"), + /** + * ⽇期 + */ + DATE("date", "⽇期"), + /** + * 出⽣⽇期 + */ + DATE_BIRTH("date_birth", "出⽣⽇期"), + /** + * 注册⽇期 + */ + DATE_REGISTER("date_register", "注册⽇期"), + /** + * 初次领证⽇期 + */ + DATE_FIRST_ISSUE("date_first_issue", "初次领证⽇期"), + /** + * 注册⽇期 + */ + DATE_ISSUE("date_issue", "发证⽇期"), + /** + * 有效期限(起始时间) + */ + DATE_VALID_START("date_vaild_start", "有效期限(起始时间)"), + /** + * 有效期限(终⽌时间) + */ + DATE_VALID_END("date_vaild_end", "有效期限(终⽌时间)"), + /** + * ⾏驶证副⻚-备注 -> 检验有效期 + */ + DATE_VALID_INSPECTION("date_vaild_inspection", "⾏驶证副⻚-备注 -> 检验有效期"), + /** + * 性别 + */ + SEX("six", "性别"), + /** + * 国籍 + */ + NATIONALITY("nationality", "国籍"), + /** + * ⺠族 + */ + NATION("nation", "⺠族"), + /** + * 姓名、所有⼈ + */ + NAME("name", "姓名、所有⼈"), + /** + * 货物名称、货品名称 + */ + NAME_GOODS("name_goods", "货物名称、货品名称"), + /** + * 物料 + */ + NAME_MATERIAL("name_material", "物料"), + /** + * ⻋辆类型 + */ + VEHICLE_TYPE("vehicle_type", "⻋辆类型"), + /** + * 使⽤性质 + */ + USE_CHARACTER("use_character", "使⽤性质"), + /** + * 品牌型号 + */ + MODEL("model", "品牌型号"), + /** + * 准驾⻋型 + */ + CLASS("class", "准驾⻋型"), + /** + * ⻋架号 + */ + VIN("vin", "⻋架号"), + /** + * ⻋牌号、号牌号码、⻋号 + */ + PLATE_NO("plate_no", "⻋牌号、号牌号码、⻋号"), + /** + * 扣重、扣杂 + */ + WEIGHT_DEDUCT("weight_deduct", "扣重、扣杂"), + /** + * ⽑重、总重 + */ + WEIGHT_GROSS("weight_gross", "⽑重、总重"), + /** + * ⽪重、空重 + */ + WEIGHT_TARE("weight_tare", "⽪重、空重"), + /** + * 净重 + */ + WEIGHT_NET("weight_net", "净重"), + /** + * 实际净重 + */ + WEIGHT_RECEIVED("weight_received", "实际净重"), + /** + * 杂质 + */ + WEIGHT_IMPURITY("weight_impurity", "杂质"), + /** + * 粉末 + */ + WEIGHT_POWDER("weight_powder", "粉末"), + /** + * 明⽔ + */ + WEIGHT_CWATER("weight_cwater", "明⽔"), + /** + * 单位 + */ + UNIT_MEASURE("unit_measure", "单位"), + /** + * 识别了但是还未⽀持整合的结构化字段 + */ + UN_SUPPORT("unsupport", "识别了但是还未⽀持整合的结构化字段"), + + //===================身份证的有些字段不一样============================== + /** + * 身份证-性别 + */ + ID_CARD_MAIN_GENDER("gender", "性别"), + /** + * 身份证-民族 + */ + ID_CARD_MAIN_ETHNIC_GROUP("ethnic_group", "民族"), + /** + * 身份证-住址 + */ + ID_CARD_MAIN_ADD("add", "住址"), + /** + * 签发机关 + */ + ISSUING_AUTHORITY("issuing_authority", "签发机关"), + /** + * 有效期限 + */ + DATE_VALID("date_vaild", "有效期限"), + ; + /** + * 字段编码 + */ + private final String key; + /** + * 字段描述 + */ + private final String description; + } + +} diff --git a/blade-third-party-api/blade-ocr-api/src/main/java/org/springblade/thirdparty/ocr/convert/AttachmentIndexConvert.java b/blade-third-party-api/blade-ocr-api/src/main/java/org/springblade/thirdparty/ocr/convert/AttachmentIndexConvert.java new file mode 100644 index 0000000..cc0b63c --- /dev/null +++ b/blade-third-party-api/blade-ocr-api/src/main/java/org/springblade/thirdparty/ocr/convert/AttachmentIndexConvert.java @@ -0,0 +1,141 @@ +package org.springblade.thirdparty.ocr.convert; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import org.springblade.thirdparty.ocr.constant.OCRImageType; +import org.springblade.thirdparty.ocr.exception.OCRException; +import org.springblade.thirdparty.ocr.pojo.vo.TransportCertificateVO; + +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * 附件索引转换器 + * @author bfhuange + * @date 2024/11/8 + */ +@Slf4j +@AllArgsConstructor +@Getter +public enum AttachmentIndexConvert { + /** + * 车头 + */ + CAR_FRONT(List.of(OCRImageType.CAR_FRONT), TransportCertificateVO::getCarFrontIndexes, TransportCertificateVO::getOtherIndexes), + /** + * 车尾 + */ + CAR_BACK(List.of(OCRImageType.CAR_BACK), TransportCertificateVO::getCarBackIndexes, TransportCertificateVO::getOtherIndexes), + /** + * 行驶证 只从 VEHICLE_LICENSE_MAIN、VEHICLE_LICENSE_MAIN_OTHERS 取车牌号,VEHICLE_LICENSE_SIDE、VEHICLE_LICENSE_MAIN_GUA,没有车牌,但是要分类成行驶证 + */ + VEHICLE_LICENSE(List.of(OCRImageType.VEHICLE_LICENSE_MAIN, OCRImageType.VEHICLE_LICENSE_MAIN_OTHERS, OCRImageType.VEHICLE_LICENSE_SIDE, OCRImageType.VEHICLE_LICENSE_MAIN_GUA), TransportCertificateVO::getVehicleLicenseIndexes, TransportCertificateVO::getOtherIndexes, false), + /** + * 驾驶证 只从 DRIVER_LICENSE_MAIN、DRIVER_LICENSE_ELEC 取有效期,DRIVER_LICENSE_SIDE 没有有效期,但是要分类驾驶证 + */ + DRIVER_LICENSE(List.of(OCRImageType.DRIVER_LICENSE_MAIN, OCRImageType.DRIVER_LICENSE_ELEC, OCRImageType.DRIVER_LICENSE_SIDE), TransportCertificateVO::getDriverLicenseIndexes, TransportCertificateVO::getOtherIndexes, true), + /** + * 其他 + */ + OTHER(List.of(OCRImageType.ID_CARD_MAIN, OCRImageType.ID_CARD_SIDE, OCRImageType.UNKNOWN, OCRImageType.OTHERS, OCRImageType.CARD, + OCRImageType.SCRAP, OCRImageType.NO_PLATE, OCRImageType.NO_CAR), TransportCertificateVO::getOtherIndexes, TransportCertificateVO::getOtherIndexes, true), + /** + * 磅单 + */ + POUND(Collections.emptyList(), TransportCertificateVO::getPoundIndexes, TransportCertificateVO::getOtherIndexes), + ; + + /** + * 图像类型列表 + */ + private final List imageTypes; + /** + * 是否多个,除了其他,都是单个 + */ + private final boolean multi; + /** + * 默认附件索引列表 + */ + private final Function> defaultListGetter; + /** + * 单个的情况下,其他的附件放的索引列表 + */ + private final Function> otherListGetter; + /** + * 多个的情况下,是否设置多次,后面的会覆盖前面的 + */ + private final boolean multiSet; + + AttachmentIndexConvert(List imageTypes, Function> defaultListGetter, Function> otherListGetter) { + this(imageTypes, false, defaultListGetter, otherListGetter, true); + } + + AttachmentIndexConvert(List imageTypes, Function> defaultListGetter, Function> otherListGetter, boolean multiSet) { + this(imageTypes, true, defaultListGetter, otherListGetter, multiSet); + } + + /** + * 添加索引到list + * @param certificate 凭证 + * @param index 索引 + * @return + */ + public boolean addIndex2list(TransportCertificateVO certificate, Integer index) { + List list = defaultListGetter.apply(certificate); + // 是否放入默认list + boolean defaultList = true; + if (!multi && !list.isEmpty() && !list.contains(index)) { + // 单个且默认list已经有数据了且待放入的数据不在默认list,放入其他 + // 处理同一张图包含能取字段的驾驶证或行驶证以及不能取字段的驾驶证或行驶证时,不能取字段的驾驶证或行驶证类型先识别,导致后续把能取字段的驾驶证或行驶证类型放到其他 + list = otherListGetter.apply(certificate); + defaultList = false; + } + if (list.contains(index)) { + // 如果已经包含了图片,设置多个,返回true,不设置多个,返回false + return multiSet; + } + // 没有当前索引才添加,防止一张图片识别出多个结果重复添加索引 + list.add(index); + return defaultList; + } + + /** + * 图像类型对应的附件转换 + */ + private static final Map CONVERT_MAP = initConvertMap(); + + /** + * 初始化 图像类型对应的附件转换 + * @return + */ + private static Map initConvertMap() { + return Stream.of(values()) + .flatMap(convert -> convert.imageTypes.stream() + .collect(Collectors.toMap(OCRImageType::getId, e -> convert)) + .entrySet().stream() + ).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (a, b) -> { + throw new OCRException("图像类型对应的附件转换重复"); + })); + } + + /** + * 根据typeId 查询转换器,没找到的是磅单 + * @param typeId + * @return + */ + public static AttachmentIndexConvert getConvertByTypeId(String typeId) { + // 磅单的typeId过多,还在新增,所以没有配置的都认为是磅单 + return CONVERT_MAP.getOrDefault(typeId, AttachmentIndexConvert.POUND); + } + + public List getImageTypeIds() { + return imageTypes.stream() + .map(OCRImageType::getId) + .collect(Collectors.toList()); + } +} diff --git a/blade-third-party-api/blade-ocr-api/src/main/java/org/springblade/thirdparty/ocr/convert/IDCardConvert.java b/blade-third-party-api/blade-ocr-api/src/main/java/org/springblade/thirdparty/ocr/convert/IDCardConvert.java new file mode 100644 index 0000000..02bdf77 --- /dev/null +++ b/blade-third-party-api/blade-ocr-api/src/main/java/org/springblade/thirdparty/ocr/convert/IDCardConvert.java @@ -0,0 +1,62 @@ +package org.springblade.thirdparty.ocr.convert; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import org.springblade.thirdparty.ocr.constant.OCRImageType; +import org.springblade.thirdparty.ocr.constant.OCRKey; +import org.springblade.thirdparty.ocr.pojo.vo.IDCardVO; +import org.springblade.thirdparty.ocr.util.ConvertUtil; + +import java.util.function.BiConsumer; + +/** + * 身份证转换 + * @author bfhuange + * @date 2024/11/6 + */ +@Slf4j +@AllArgsConstructor +@Getter +public enum IDCardConvert { + /** + * 姓名 + */ + NAME(OCRImageType.ID_CARD_MAIN, OCRKey.KeyInfo.NAME, IDCardVO::setName), + /** + * 身份证号 + */ + ID_NO(OCRImageType.ID_CARD_MAIN, OCRKey.KeyInfo.ID_NO, IDCardVO::setIdNo), + /** + * 性别 + */ + GENDER(OCRImageType.ID_CARD_MAIN, OCRKey.KeyInfo.ID_CARD_MAIN_GENDER, IDCardVO::setGender), + /** + * 民族 + */ + NATION(OCRImageType.ID_CARD_MAIN, OCRKey.KeyInfo.ID_CARD_MAIN_ETHNIC_GROUP, IDCardVO::setNation), + /** + * 出生年月日 + */ + BIRTHDAY(OCRImageType.ID_CARD_MAIN, OCRKey.KeyInfo.DATE, (idCard, dateString) -> + ConvertUtil.convertDate(dateString).accept(idCard::setBirthday)), + /** + * 有效期止 + */ + VALID_DATE_END(OCRImageType.ID_CARD_SIDE, OCRKey.KeyInfo.DATE_VALID, (idCard, dateString) -> ConvertUtil.extractEndDate(dateString) + .accept(idCard::setValidDateEnd)), + ; + /** + * 图像类型列表 + */ + private final OCRImageType imageType; + /** + * 字段配置 + */ + private final OCRKey.KeyInfo keyInfo; + /** + * setter方法 + */ + private final BiConsumer setter; + +} diff --git a/blade-third-party-api/blade-ocr-api/src/main/java/org/springblade/thirdparty/ocr/convert/TransportCertificateConvert.java b/blade-third-party-api/blade-ocr-api/src/main/java/org/springblade/thirdparty/ocr/convert/TransportCertificateConvert.java new file mode 100644 index 0000000..b80e3e1 --- /dev/null +++ b/blade-third-party-api/blade-ocr-api/src/main/java/org/springblade/thirdparty/ocr/convert/TransportCertificateConvert.java @@ -0,0 +1,190 @@ +package org.springblade.thirdparty.ocr.convert; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import org.springblade.thirdparty.ocr.constant.OCRImageType; +import org.springblade.thirdparty.ocr.constant.OCRKey; +import org.springblade.thirdparty.ocr.pojo.vo.TransportCertificateVO; + +import java.util.Collections; +import java.util.List; +import java.util.function.BiConsumer; + +import static org.springblade.thirdparty.ocr.util.ConvertUtil.*; + +/** + * 车辆运输凭证转换 + * @author bfhuange + * @date 2024/11/6 + */ +@Slf4j +@AllArgsConstructor +@Getter +public enum TransportCertificateConvert { + /** + * 车头车牌号 + */ + CAR_FRONT_PLATE_NO(OCRImageType.CAR_FRONT, OCRKey.KeyInfo.PLATE_NO, TransportCertificateVO::setCarFrontPlateNo), + /** + * 车尾车牌号 + */ + CAR_BACK_PLATE_NO(OCRImageType.CAR_BACK, OCRKey.KeyInfo.PLATE_NO, TransportCertificateVO::setCarBackPlateNo), + /** + * 行驶证上的车牌号 + */ + DRIVING_PLATE_NO(List.of(OCRImageType.VEHICLE_LICENSE_MAIN, OCRImageType.VEHICLE_LICENSE_MAIN_OTHERS), OCRKey.KeyInfo.PLATE_NO, (transportCertificate, plateNo) -> { + transportCertificate.setDrivingPlateNo(plateNo); + transportCertificate.setVehicleLicensePlateNo(plateNo); + }), + /** + * 行驶证车辆类型 + */ + VEHICLE_LICENSE_VEHICLE_TYPE(List.of(OCRImageType.VEHICLE_LICENSE_MAIN, OCRImageType.VEHICLE_LICENSE_MAIN_OTHERS), OCRKey.KeyInfo.VEHICLE_TYPE, TransportCertificateVO::setVehicleLicenseVehicleType), + /** + * 行驶证所有人 + */ + VEHICLE_LICENSE_OWNER_NAME(List.of(OCRImageType.VEHICLE_LICENSE_MAIN, OCRImageType.VEHICLE_LICENSE_MAIN_OTHERS), OCRKey.KeyInfo.NAME, TransportCertificateVO::setVehicleLicenseOwnerName), + /** + * 行驶证使用性质 + */ + VEHICLE_LICENSE_USE_CHARACTER(List.of(OCRImageType.VEHICLE_LICENSE_MAIN, OCRImageType.VEHICLE_LICENSE_MAIN_OTHERS), OCRKey.KeyInfo.USE_CHARACTER, TransportCertificateVO::setVehicleLicenseUseCharacter), + /** + * 行驶证品牌型号 + */ + VEHICLE_LICENSE_MODEL(List.of(OCRImageType.VEHICLE_LICENSE_MAIN, OCRImageType.VEHICLE_LICENSE_MAIN_OTHERS), OCRKey.KeyInfo.MODEL, TransportCertificateVO::setVehicleLicenseModel), + /** + * 行驶证车辆识别代号 + */ + VEHICLE_LICENSE_VIN(List.of(OCRImageType.VEHICLE_LICENSE_MAIN, OCRImageType.VEHICLE_LICENSE_MAIN_OTHERS), OCRKey.KeyInfo.VIN, TransportCertificateVO::setVehicleLicenseVin), + /** + * 行驶证发动机号码 + */ + VEHICLE_LICENSE_ENGINE_NO(List.of(OCRImageType.VEHICLE_LICENSE_MAIN, OCRImageType.VEHICLE_LICENSE_MAIN_OTHERS), OCRKey.KeyInfo.ENGINE_NO, TransportCertificateVO::setVehicleLicenseEngineNo), + /** + * 行驶证注册日期 + */ + VEHICLE_LICENSE_REGISTER_DATE(List.of(OCRImageType.VEHICLE_LICENSE_MAIN, OCRImageType.VEHICLE_LICENSE_MAIN_OTHERS), OCRKey.KeyInfo.DATE_REGISTER, (transportCertificate, dateString) -> convertDate(dateString) + .accept(transportCertificate::setVehicleLicenseRegisterDate)), + /** + * 行驶证发证日期 + */ + VEHICLE_LICENSE_ISSUE_DATE(List.of(OCRImageType.VEHICLE_LICENSE_MAIN, OCRImageType.VEHICLE_LICENSE_MAIN_OTHERS), OCRKey.KeyInfo.DATE_ISSUE, (transportCertificate, dateString) -> convertDate(dateString) + .accept(transportCertificate::setVehicleLicenseIssueDate)), + /** + * 行驶证住址 + */ + VEHICLE_LICENSE_ADDRESS(List.of(OCRImageType.VEHICLE_LICENSE_MAIN, OCRImageType.VEHICLE_LICENSE_MAIN_OTHERS), OCRKey.KeyInfo.ADDRESS, TransportCertificateVO::setVehicleLicenseAddress), + /** + * 行驶证档案编号 + */ + VEHICLE_LICENSE_FILE_NO(OCRImageType.VEHICLE_LICENSE_SIDE, OCRKey.KeyInfo.FILE_NO, TransportCertificateVO::setVehicleLicenseFileNo), + /** + * 行驶证检验有效期 + */ + VEHICLE_LICENSE_INSPECTION_VALID_DATE(OCRImageType.VEHICLE_LICENSE_SIDE, OCRKey.KeyInfo.DATE_VALID_INSPECTION, (transportCertificate, dateString) -> extractEndDate(dateString) + .accept(transportCertificate::setVehicleLicenseInspectionValidDate)), + /** + * 驾驶证有效期限(终⽌时间) + */ + DRIVER_LICENSE_VALID_DATE_END(List.of(OCRImageType.DRIVER_LICENSE_MAIN, OCRImageType.DRIVER_LICENSE_ELEC), OCRKey.KeyInfo.DATE_VALID, (transportCertificate, dateString) -> convertDateValid(dateString) + .accept(transportCertificate::setDriverLicenseValidDateEnd)), + /** + * 驾驶证姓名 + */ + DRIVER_LICENSE_NAME(List.of(OCRImageType.DRIVER_LICENSE_MAIN, OCRImageType.DRIVER_LICENSE_ELEC), OCRKey.KeyInfo.NAME, TransportCertificateVO::setDriverLicenseName), + /** + * 驾驶证证号 + */ + DRIVER_LICENSE_ID_NO(List.of(OCRImageType.DRIVER_LICENSE_MAIN, OCRImageType.DRIVER_LICENSE_ELEC), OCRKey.KeyInfo.ID_NO, TransportCertificateVO::setDriverLicenseIdNo), + /** + * 驾驶证档案编号 + */ + DRIVER_LICENSE_FILE_NO(OCRImageType.DRIVER_LICENSE_SIDE, OCRKey.KeyInfo.FILE_NO, TransportCertificateVO::setDriverLicenseFileNo), + /** + * 准驾车型 + */ + DRIVER_LICENSE_TYPE(List.of(OCRImageType.DRIVER_LICENSE_MAIN, OCRImageType.DRIVER_LICENSE_ELEC), OCRKey.KeyInfo.CLASS, TransportCertificateVO::setDriverLicenseType), + /** + * 驾驶证有效期限(起始时间) + */ + DRIVER_LICENSE_VALID_DATE_START(List.of(OCRImageType.DRIVER_LICENSE_MAIN, OCRImageType.DRIVER_LICENSE_ELEC), OCRKey.KeyInfo.DATE_VALID, (transportCertificate, dateString) -> extractStartDate(dateString) + .accept(transportCertificate::setDriverLicenseValidDateStart)), + /** + * 驾驶证有效期限(起始时间) + */ + DRIVER_LICENSE_VALID_DATE_START_VALUE(List.of(OCRImageType.DRIVER_LICENSE_MAIN, OCRImageType.DRIVER_LICENSE_ELEC), OCRKey.KeyInfo.DATE_VALID_START, (transportCertificate, dateString) -> convertDate(dateString) + .accept(transportCertificate::setDriverLicenseValidDateStart)), + /** + * 驾驶证有效期限(终⽌时间) + */ + DRIVER_LICENSE_VALID_DATE_END_VALUE(List.of(OCRImageType.DRIVER_LICENSE_MAIN, OCRImageType.DRIVER_LICENSE_ELEC), OCRKey.KeyInfo.DATE_VALID_END, (transportCertificate, dateString) -> convertDate(dateString) + .accept(transportCertificate::setDriverLicenseValidDateEnd)), + /** + * 磅单车牌号 + */ + POUND_PLATE_NO(Collections.emptyList(), OCRKey.KeyInfo.PLATE_NO, TransportCertificateVO::setPoundPlateNo), + /** + * 磅单供货日期 + */ + POUND_DATE(Collections.emptyList(), OCRKey.KeyInfo.DATE, (transportCertificate, dateString) -> convertDate(dateString) + .accept(transportCertificate::setSupplyMaterialDate)), + /** + * 单位 + */ + UNIT_MEASURE(Collections.emptyList(), OCRKey.KeyInfo.UNIT_MEASURE, TransportCertificateVO::setUnitMeasure), + /** + * 货品名称 + */ + NAME_GOODS(Collections.emptyList(), OCRKey.KeyInfo.NAME_GOODS, TransportCertificateVO::setProductName), + /** + * 物料规格 + */ + NAME_MATERIAL(Collections.emptyList(), OCRKey.KeyInfo.NAME_MATERIAL, TransportCertificateVO::setSpecificationMaterial), + /** + * ⽑重 + */ + GROSS_WEIGHT(Collections.emptyList(), OCRKey.KeyInfo.WEIGHT_GROSS, (transportCertificate, string) -> convertWeightBigDecimal(string) + .accept(transportCertificate::setGrossWeight)), + /** + * ⽪重 + */ + TARE_WEIGHT(Collections.emptyList(), OCRKey.KeyInfo.WEIGHT_TARE, (transportCertificate, string) -> convertWeightBigDecimal(string) + .accept(transportCertificate::setSkinWeight)), + /** + * 净重 + */ + NET_WEIGHT(Collections.emptyList(), OCRKey.KeyInfo.WEIGHT_NET, (transportCertificate, string) -> convertWeightBigDecimal(string) + .accept(transportCertificate::setNetWeight)), + /** + * 扣重 + */ + DEDUCT_WEIGHT(Collections.emptyList(), OCRKey.KeyInfo.WEIGHT_DEDUCT, (transportCertificate, string) -> convertWeightBigDecimal(string) + .accept(transportCertificate::setMiscellaneousWeight)), + /** + * 实际净重 + */ + RECEIVED_WEIGHT(Collections.emptyList(), OCRKey.KeyInfo.WEIGHT_RECEIVED, (transportCertificate, string) -> convertWeightBigDecimal(string) + .accept(transportCertificate::setRealityWeight)), + + ; + /** + * 图像类型列表 + */ + private final List imageTypes; + /** + * 字段配置 + */ + private final OCRKey.KeyInfo keyInfo; + /** + * setter方法 + */ + private final BiConsumer setter; + + TransportCertificateConvert(OCRImageType imageType, OCRKey.KeyInfo keyInfo, BiConsumer setter) { + this.imageTypes = List.of(imageType); + this.keyInfo = keyInfo; + this.setter = setter; + } + +} diff --git a/blade-third-party-api/blade-ocr-api/src/main/java/org/springblade/thirdparty/ocr/exception/OCRException.java b/blade-third-party-api/blade-ocr-api/src/main/java/org/springblade/thirdparty/ocr/exception/OCRException.java new file mode 100644 index 0000000..e635ae3 --- /dev/null +++ b/blade-third-party-api/blade-ocr-api/src/main/java/org/springblade/thirdparty/ocr/exception/OCRException.java @@ -0,0 +1,16 @@ +package org.springblade.thirdparty.ocr.exception; + +/** + * ocr 接口异常 + * @author bfhuange + * @date 2024/11/6 + */ +public class OCRException extends RuntimeException { + public OCRException(String message) { + super(message); + } + + public OCRException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/blade-third-party-api/blade-ocr-api/src/main/java/org/springblade/thirdparty/ocr/exception/OCRExceptionTranslator.java b/blade-third-party-api/blade-ocr-api/src/main/java/org/springblade/thirdparty/ocr/exception/OCRExceptionTranslator.java new file mode 100644 index 0000000..a3d0790 --- /dev/null +++ b/blade-third-party-api/blade-ocr-api/src/main/java/org/springblade/thirdparty/ocr/exception/OCRExceptionTranslator.java @@ -0,0 +1,28 @@ +package org.springblade.thirdparty.ocr.exception; + +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springblade.core.tool.api.R; +import org.springblade.core.tool.api.ResultCode; +import org.springblade.core.tool.utils.Func; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +/** + * @author bfhuange + * @since 2025/3/29 + */ +@AllArgsConstructor +@Slf4j +@RestControllerAdvice +public class OCRExceptionTranslator { + + @ExceptionHandler(OCRException.class) + @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) + public R handleError(OCRException e) { + log.error("ocr异常", e); + return R.fail(ResultCode.INTERNAL_SERVER_ERROR, (Func.isEmpty(e.getMessage()) ? ResultCode.INTERNAL_SERVER_ERROR.getMessage() : e.getMessage())); + } +} diff --git a/blade-third-party-api/blade-ocr-api/src/main/java/org/springblade/thirdparty/ocr/feign/IOCRClient.java b/blade-third-party-api/blade-ocr-api/src/main/java/org/springblade/thirdparty/ocr/feign/IOCRClient.java new file mode 100644 index 0000000..216a6d7 --- /dev/null +++ b/blade-third-party-api/blade-ocr-api/src/main/java/org/springblade/thirdparty/ocr/feign/IOCRClient.java @@ -0,0 +1,53 @@ +package org.springblade.thirdparty.ocr.feign; + +import org.springblade.thirdparty.ocr.config.OCRFeignClientConfig; +import org.springblade.thirdparty.ocr.pojo.dto.OCRBigCommonCardBatchDTO; +import org.springblade.thirdparty.ocr.pojo.dto.OCRBigCommonCardDTO; +import org.springblade.thirdparty.ocr.pojo.dto.OCRSingleDTO; +import org.springblade.thirdparty.ocr.pojo.vo.OCRBigCommonCardVO; +import org.springblade.thirdparty.ocr.pojo.vo.OCRResultVO; +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestHeader; + +import java.util.List; + +/** + * ocr接口 磅单接口 + * @author bfhuange + * @date 2024/11/4 + */ +@FeignClient(name = "OCR", url = "${thirdParty.ocr.baseUrl}", configuration = OCRFeignClientConfig.class) +public interface IOCRClient { + + /** + * 批量磅单卡证识别(⼀个⽂件夹的数据)功能接⼝说明 + * @param param + * @param code + */ + @PostMapping("/BigCommonCardBatch") + OCRResultVO>> batchRecognition(@RequestBody OCRBigCommonCardBatchDTO param, @RequestHeader(value = "code", required = false) String code); + + /** + * 磅单卡证识别功能接⼝ + * @param param + */ + @PostMapping("/BigCommonCard") + OCRResultVO singleRecognition(@RequestBody OCRBigCommonCardDTO param, @RequestHeader(value = "code", required = false) String code); + + /** + * 磅单单独识别功能接⼝ + * @param param + */ + @PostMapping("/logisticRec") + OCRResultVO poundRecognition(@RequestBody OCRSingleDTO param, @RequestHeader(value = "code", required = false) String code); + + /** + * ⻋牌识别单独接⼝ + * @param param + */ + @PostMapping("/CLPR") + OCRResultVO plateNoRecognition(@RequestBody OCRSingleDTO param, @RequestHeader(value = "code", required = false) String code); + +} diff --git a/blade-third-party-api/blade-ocr-api/src/main/java/org/springblade/thirdparty/ocr/feign/IOCROtherClient.java b/blade-third-party-api/blade-ocr-api/src/main/java/org/springblade/thirdparty/ocr/feign/IOCROtherClient.java new file mode 100644 index 0000000..4dcdda6 --- /dev/null +++ b/blade-third-party-api/blade-ocr-api/src/main/java/org/springblade/thirdparty/ocr/feign/IOCROtherClient.java @@ -0,0 +1,28 @@ +package org.springblade.thirdparty.ocr.feign; + +import org.springblade.thirdparty.ocr.config.OCRFeignClientConfig; +import org.springblade.thirdparty.ocr.pojo.dto.OCRCardBankDetRecDTO; +import org.springblade.thirdparty.ocr.pojo.vo.OCRBigCommonCardVO; +import org.springblade.thirdparty.ocr.pojo.vo.OCRResultVO; +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; + +import java.util.List; + +/** + * ocr接口 证件接口,因为端口不同,要新建一个 + * @author bfhuange + * @date 2024/11/4 + */ +@FeignClient(name = "OCRO", url = "${thirdParty.ocr.baseOtherUrl}", configuration = OCRFeignClientConfig.class) +public interface IOCROtherClient { + + /** + * 磅单业务-证件定位+结构化信息提取接⼝ + * @param param + * @return imageInfos 没有 + */ + @PostMapping("/CardBankDetRec") + OCRResultVO cardBankDetRec(@RequestBody OCRCardBankDetRecDTO param); +} diff --git a/blade-third-party-api/blade-ocr-api/src/main/java/org/springblade/thirdparty/ocr/interceptor/OCRRequestInterceptor.java b/blade-third-party-api/blade-ocr-api/src/main/java/org/springblade/thirdparty/ocr/interceptor/OCRRequestInterceptor.java new file mode 100644 index 0000000..2f0a2a1 --- /dev/null +++ b/blade-third-party-api/blade-ocr-api/src/main/java/org/springblade/thirdparty/ocr/interceptor/OCRRequestInterceptor.java @@ -0,0 +1,33 @@ +package org.springblade.thirdparty.ocr.interceptor; + +import feign.RequestInterceptor; +import feign.RequestTemplate; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.slf4j.MDC; +import org.springblade.core.log.utils.LogTraceUtil; +import org.springblade.core.tool.utils.DateUtil; +import org.springblade.core.tool.utils.StringUtil; +import org.springblade.thirdparty.ocr.constant.OCRConstant; + +import java.util.Date; + +/** + * @author bfhuange + * @since 2024/12/26 + */ +@Slf4j +@AllArgsConstructor +public class OCRRequestInterceptor implements RequestInterceptor { + @Override + public void apply(RequestTemplate template) { + // 请求时间 + template.header(OCRConstant.REQUEST_HEADER_TIME, DateUtil.formatDateTime(new Date())); + String traceId = MDC.get("traceId"); + if (StringUtil.isBlank(traceId)) { + traceId = LogTraceUtil.getTraceId(); + } + // traceId + template.header(OCRConstant.REQUEST_HEADER_TRACE_ID, traceId); + } +} diff --git a/blade-third-party-api/blade-ocr-api/src/main/java/org/springblade/thirdparty/ocr/pojo/dto/CertificateBatchRecognitionDTO.java b/blade-third-party-api/blade-ocr-api/src/main/java/org/springblade/thirdparty/ocr/pojo/dto/CertificateBatchRecognitionDTO.java new file mode 100644 index 0000000..54eb4f9 --- /dev/null +++ b/blade-third-party-api/blade-ocr-api/src/main/java/org/springblade/thirdparty/ocr/pojo/dto/CertificateBatchRecognitionDTO.java @@ -0,0 +1,34 @@ +package org.springblade.thirdparty.ocr.pojo.dto; + +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; +import java.util.List; + +/** + * 凭证批量识别参数,所有图像链接统一识别,不分类 + * @author bfhuange + * @since 2025/3/13 + */ +@Data +public class CertificateBatchRecognitionDTO implements Serializable { + @Serial + private static final long serialVersionUID = 1L; + /** + * 所有图像链接列表 + */ + private List urls; + /** + * 项目简称 + */ + private String projectAbbreviation; + /** + * 凭证编码 + */ + private String code; + /** + * 目录名称 + */ + private String dirName; +} diff --git a/blade-third-party-api/blade-ocr-api/src/main/java/org/springblade/thirdparty/ocr/pojo/dto/CertificateRecognitionDTO.java b/blade-third-party-api/blade-ocr-api/src/main/java/org/springblade/thirdparty/ocr/pojo/dto/CertificateRecognitionDTO.java new file mode 100644 index 0000000..331fbb1 --- /dev/null +++ b/blade-third-party-api/blade-ocr-api/src/main/java/org/springblade/thirdparty/ocr/pojo/dto/CertificateRecognitionDTO.java @@ -0,0 +1,37 @@ +package org.springblade.thirdparty.ocr.pojo.dto; + +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 凭证识别参数,依次调用单张识别 + * @author bfhuange + * @since 2024/12/13 + */ +@Data +public class CertificateRecognitionDTO implements Serializable { + @Serial + private static final long serialVersionUID = 1L; + /** + * 磅单url + */ + private String poundUrl; + /** + * 车头url + */ + private String carFrontUrl; + /** + * 驾驶证url + */ + private String driverLicenseUrl; + /** + * 行驶证url + */ + private String vehicleLicenseUrl; + /** + * 项目简称 + */ + private String name; +} diff --git a/blade-third-party-api/blade-ocr-api/src/main/java/org/springblade/thirdparty/ocr/pojo/dto/OCRBigCommonCardBatchDTO.java b/blade-third-party-api/blade-ocr-api/src/main/java/org/springblade/thirdparty/ocr/pojo/dto/OCRBigCommonCardBatchDTO.java new file mode 100644 index 0000000..3b9acde --- /dev/null +++ b/blade-third-party-api/blade-ocr-api/src/main/java/org/springblade/thirdparty/ocr/pojo/dto/OCRBigCommonCardBatchDTO.java @@ -0,0 +1,39 @@ +package org.springblade.thirdparty.ocr.pojo.dto; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serial; +import java.io.Serializable; +import java.util.List; + +/** + * ocr批量磅单卡证识别参数 + * @author bfhuange + * @date 2024/11/4 + */ +@NoArgsConstructor +@AllArgsConstructor +@Data +public class OCRBigCommonCardBatchDTO implements Serializable { + @Serial + private static final long serialVersionUID = 1L; + /** + * 图像本地路径或者远程服务器地址列表 + */ + @JsonProperty("img_path") + private List imgPaths; + /** + * 项目简称 + */ + @JsonInclude(JsonInclude.Include.NON_EMPTY) + private String name; + /** + * 目录名称 + */ + @JsonInclude(JsonInclude.Include.NON_EMPTY) + private String dirName; +} diff --git a/blade-third-party-api/blade-ocr-api/src/main/java/org/springblade/thirdparty/ocr/pojo/dto/OCRBigCommonCardDTO.java b/blade-third-party-api/blade-ocr-api/src/main/java/org/springblade/thirdparty/ocr/pojo/dto/OCRBigCommonCardDTO.java new file mode 100644 index 0000000..1066136 --- /dev/null +++ b/blade-third-party-api/blade-ocr-api/src/main/java/org/springblade/thirdparty/ocr/pojo/dto/OCRBigCommonCardDTO.java @@ -0,0 +1,54 @@ +package org.springblade.thirdparty.ocr.pojo.dto; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import java.io.Serial; +import java.util.List; + +/** + * ocr批量磅单卡证识别参数 + * @author bfhuange + * @date 2024/11/4 + */ +@JsonInclude(JsonInclude.Include.NON_NULL) +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +@Data +public class OCRBigCommonCardDTO extends OCRSingleDTO { + @Serial + private static final long serialVersionUID = 1L; + /** + * 非必填 + * 可选值 -1, 0, 1, 2, 3 + * 输⼊图⽚的⽂本⽅向, 如果需要系统识别则为默认的-1(可不传), 0: 正⽅向, 1:顺时针90度, 2: 180, 3: -90 + */ + @JsonProperty("direction_id") + private Integer directionId; + /** + * 非必填 + * 可选值 True, False + * 是否是区域图, 如果True: 表⽰不需要调⽤证件定位模型获取区域 + */ + @JsonProperty("is_rect") + private String isRect; + /** + * 非必填 + * ["VehicleLicense-Main", "IDCard-Main", "IDCard-Side", "DriverLicense-Main"] + * 需要提取信息的ID列表, 不填写表⽰都要提取, 如果填写则只返回所需要列表中的信息, 返回结构不变 + */ + @JsonProperty("need_data_type") + private List needDataTypes; + + public OCRBigCommonCardDTO(String imgPath, String name) { + super(imgPath, name); + } + + public OCRBigCommonCardDTO(String imgPath, String name, List needDataTypes) { + super(imgPath, name); + this.needDataTypes = needDataTypes; + } +} diff --git a/blade-third-party-api/blade-ocr-api/src/main/java/org/springblade/thirdparty/ocr/pojo/dto/OCRCardBankDetRecDTO.java b/blade-third-party-api/blade-ocr-api/src/main/java/org/springblade/thirdparty/ocr/pojo/dto/OCRCardBankDetRecDTO.java new file mode 100644 index 0000000..5c1695a --- /dev/null +++ b/blade-third-party-api/blade-ocr-api/src/main/java/org/springblade/thirdparty/ocr/pojo/dto/OCRCardBankDetRecDTO.java @@ -0,0 +1,39 @@ +package org.springblade.thirdparty.ocr.pojo.dto; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.databind.ser.std.NullSerializer; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serial; +import java.io.Serializable; + +/** + * ocr 证件识别参数 + * @author bfhuange + * @date 2024/11/6 + */ +@NoArgsConstructor +@Data +public class OCRCardBankDetRecDTO implements Serializable { + @Serial + private static final long serialVersionUID = 1L; + /** + * 图像本地路径或者远程服务器地址,必填 + */ + @JsonProperty("img_path") + private String imgPath; + /** + * 图像内容类型,位置为"", ["", "BankCard-Main","BankCard-Side", "IDCard-Main", "IDCard-Side"] 其他依次对应: 银⾏卡-正⾯、银⾏卡-背⾯、身份证-信息⻚、身份证-国徽⻚,非必填 + */ + @JsonInclude(JsonInclude.Include.NON_NULL) + @JsonProperty("data_type") + private String dataType; + + public OCRCardBankDetRecDTO(String imgPath) { + this.imgPath = imgPath; + } +} diff --git a/blade-third-party-api/blade-ocr-api/src/main/java/org/springblade/thirdparty/ocr/pojo/dto/OCRSingleDTO.java b/blade-third-party-api/blade-ocr-api/src/main/java/org/springblade/thirdparty/ocr/pojo/dto/OCRSingleDTO.java new file mode 100644 index 0000000..05b9dd7 --- /dev/null +++ b/blade-third-party-api/blade-ocr-api/src/main/java/org/springblade/thirdparty/ocr/pojo/dto/OCRSingleDTO.java @@ -0,0 +1,33 @@ +package org.springblade.thirdparty.ocr.pojo.dto; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serial; +import java.io.Serializable; + +/** + * ocr单张识别参数 + * @author bfhuange + * @date 2024/12/13 + */ +@NoArgsConstructor +@AllArgsConstructor +@Data +public class OCRSingleDTO implements Serializable { + @Serial + private static final long serialVersionUID = 1L; + /** + * 图像本地路径或者远程服务器地址 + */ + @JsonProperty("img_path") + private String imgPath; + /** + * 项目简称 + */ + @JsonInclude(JsonInclude.Include.NON_EMPTY) + private String name; +} diff --git a/blade-third-party-api/blade-ocr-api/src/main/java/org/springblade/thirdparty/ocr/pojo/vo/IDCardVO.java b/blade-third-party-api/blade-ocr-api/src/main/java/org/springblade/thirdparty/ocr/pojo/vo/IDCardVO.java new file mode 100644 index 0000000..0098b2b --- /dev/null +++ b/blade-third-party-api/blade-ocr-api/src/main/java/org/springblade/thirdparty/ocr/pojo/vo/IDCardVO.java @@ -0,0 +1,46 @@ +package org.springblade.thirdparty.ocr.pojo.vo; + +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.Data; +import org.springblade.core.tool.utils.DateUtil; + +import java.io.Serial; +import java.io.Serializable; +import java.util.Date; + +/** + * 身份证识别结果 + * @author bfhuange + * @date 2024/11/6 + */ +@Data +public class IDCardVO implements Serializable { + @Serial + private static final long serialVersionUID = 1L; + /** + * 姓名 + */ + private String name; + /** + * 身份证号 + */ + private String idNo; + /** + * 性别 + */ + private String gender; + /** + * 民族 + */ + private String nation; + /** + * 出生年月日 + */ + @JsonFormat(pattern = DateUtil.PATTERN_DATE, timezone = "GMT+8") + private Date birthday; + /** + * 有效期止 + */ + @JsonFormat(pattern = DateUtil.PATTERN_DATE, timezone = "GMT+8") + private Date validDateEnd; +} diff --git a/blade-third-party-api/blade-ocr-api/src/main/java/org/springblade/thirdparty/ocr/pojo/vo/OCRBigCommonCardVO.java b/blade-third-party-api/blade-ocr-api/src/main/java/org/springblade/thirdparty/ocr/pojo/vo/OCRBigCommonCardVO.java new file mode 100644 index 0000000..b351934 --- /dev/null +++ b/blade-third-party-api/blade-ocr-api/src/main/java/org/springblade/thirdparty/ocr/pojo/vo/OCRBigCommonCardVO.java @@ -0,0 +1,29 @@ +package org.springblade.thirdparty.ocr.pojo.vo; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; +import java.util.List; + +/** + * ocr 磅单卡证识别响应参数 + * @author bfhuange + * @date 2024/11/4 + */ +@Data +public class OCRBigCommonCardVO implements Serializable { + @Serial + private static final long serialVersionUID = 1L; + /** + * 原始图像的识别信息-图像相关 + */ + // ocr返回的结构有点问题,先注释掉,这个对象没用到 + //@JsonProperty("image_info") + //private OCRImageInfoSummaryVO imageInfos; + /** + * 每⼀个证件⽬标的识别结果,列表顺序对应type_descriptions中的顺序 + */ + private List objects; +} diff --git a/blade-third-party-api/blade-ocr-api/src/main/java/org/springblade/thirdparty/ocr/pojo/vo/OCRImageHashVO.java b/blade-third-party-api/blade-ocr-api/src/main/java/org/springblade/thirdparty/ocr/pojo/vo/OCRImageHashVO.java new file mode 100644 index 0000000..d7fc541 --- /dev/null +++ b/blade-third-party-api/blade-ocr-api/src/main/java/org/springblade/thirdparty/ocr/pojo/vo/OCRImageHashVO.java @@ -0,0 +1,23 @@ +package org.springblade.thirdparty.ocr.pojo.vo; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; + +/** + * ocr 磅单卡证识别响应参数 图⽚hash值 + * @author bfhuange + * @date 2024/11/4 + */ +@Data +public class OCRImageHashVO implements Serializable { + @Serial + private static final long serialVersionUID = 1L; + /** + * 感知hash,⽀持轻微旋转移动和轻微质量丢失 + */ + @JsonProperty("phash") + private String phash; +} diff --git a/blade-third-party-api/blade-ocr-api/src/main/java/org/springblade/thirdparty/ocr/pojo/vo/OCRImageInfoSummaryVO.java b/blade-third-party-api/blade-ocr-api/src/main/java/org/springblade/thirdparty/ocr/pojo/vo/OCRImageInfoSummaryVO.java new file mode 100644 index 0000000..9a0005d --- /dev/null +++ b/blade-third-party-api/blade-ocr-api/src/main/java/org/springblade/thirdparty/ocr/pojo/vo/OCRImageInfoSummaryVO.java @@ -0,0 +1,39 @@ +package org.springblade.thirdparty.ocr.pojo.vo; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; +import java.util.List; + +/** + * ocr 磅单卡证识别响应参数 原始图像的识别信息-图像相关 + * @author bfhuange + * @date 2024/11/4 + */ +@Data +public class OCRImageInfoSummaryVO implements Serializable { + @Serial + private static final long serialVersionUID = 1L; + /** + * 图像评分,⽬前只需要取list中的第⼀位即可,1: 正常图像、0: 图像 质量差, 其他值: 算法还需要进⼀步判断 + */ + @JsonProperty("QA_scores") + private List qaScores; + /** + * 图像中包含的数据类型(这⾥的顺序对应下⾯结构化结果的顺序) + */ + @JsonProperty("type_descriptions") + private List typeDescriptions; + /** + * 图像中包含的数据类型id(这⾥的顺序对应下⾯结构化结果的顺序) + */ + @JsonProperty("type_ids") + private List typeIds; + /** + * 图⽚hash值 + */ + @JsonProperty("img_hash") + private OCRImageHashVO imageHash; +} diff --git a/blade-third-party-api/blade-ocr-api/src/main/java/org/springblade/thirdparty/ocr/pojo/vo/OCRKeyInfoVO.java b/blade-third-party-api/blade-ocr-api/src/main/java/org/springblade/thirdparty/ocr/pojo/vo/OCRKeyInfoVO.java new file mode 100644 index 0000000..87f42d4 --- /dev/null +++ b/blade-third-party-api/blade-ocr-api/src/main/java/org/springblade/thirdparty/ocr/pojo/vo/OCRKeyInfoVO.java @@ -0,0 +1,39 @@ +package org.springblade.thirdparty.ocr.pojo.vo; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; +import java.math.BigDecimal; + +/** + * ocr 磅单卡证识别响应参数 结构化提取后的key-value-description信息 + * @author bfhuange + * @date 2024/11/4 + */ +@Data +public class OCRKeyInfoVO implements Serializable { + @Serial + private static final long serialVersionUID = 1L; + /** + * ?字段编码 + */ + @JsonProperty("key") + private String key; + /** + * ?字段说明 + */ + @JsonProperty("description") + private String description; + /** + * ?字段值 + */ + @JsonProperty("value") + private String value; + /** + * ?匹配度,有些没有 + */ + @JsonProperty("rec_score") + private BigDecimal recScore; +} diff --git a/blade-third-party-api/blade-ocr-api/src/main/java/org/springblade/thirdparty/ocr/pojo/vo/OCRLineInfoVO.java b/blade-third-party-api/blade-ocr-api/src/main/java/org/springblade/thirdparty/ocr/pojo/vo/OCRLineInfoVO.java new file mode 100644 index 0000000..f66ea53 --- /dev/null +++ b/blade-third-party-api/blade-ocr-api/src/main/java/org/springblade/thirdparty/ocr/pojo/vo/OCRLineInfoVO.java @@ -0,0 +1,46 @@ +package org.springblade.thirdparty.ocr.pojo.vo; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; +import java.math.BigDecimal; +import java.util.List; + +/** + * ocr 磅单卡证识别响应参数 行信息 + * @author bfhuange + * @date 2024/11/4 + */ +@Data +public class OCRLineInfoVO implements Serializable { + @Serial + private static final long serialVersionUID = 1L; + /** + * ?? 区域坐标 + */ + @JsonProperty("bbox") + private List bBoxes; + /** + * ?? + */ + private String direct; + /** + * 匹配度 ? + */ + private BigDecimal score; + /** + * 文本? + */ + private String text; + /** + * ?? 有些没有 + */ + private List> polygons; + /** + * ?? 有些没有 + */ + @JsonProperty("position") + private List positions; +} diff --git a/blade-third-party-api/blade-ocr-api/src/main/java/org/springblade/thirdparty/ocr/pojo/vo/OCRRecognitionImageInfoVO.java b/blade-third-party-api/blade-ocr-api/src/main/java/org/springblade/thirdparty/ocr/pojo/vo/OCRRecognitionImageInfoVO.java new file mode 100644 index 0000000..dbf5001 --- /dev/null +++ b/blade-third-party-api/blade-ocr-api/src/main/java/org/springblade/thirdparty/ocr/pojo/vo/OCRRecognitionImageInfoVO.java @@ -0,0 +1,39 @@ +package org.springblade.thirdparty.ocr.pojo.vo; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; +import java.util.List; + +/** + * ocr 磅单卡证识别响应参数 ⼦⽬标证件的基本识别信息 + * @author bfhuange + * @date 2024/11/4 + */ +@Data +public class OCRRecognitionImageInfoVO implements Serializable { + @Serial + private static final long serialVersionUID = 1L; + /** + * 证件⽬标对于原始图像的坐标位置[x, y, w, h] + */ + // @JsonProperty("bbox") + // private List bBoxes; + /** + * 证件⽬标在原始图像中的⻆度 + */ + // @JsonProperty("image_angle") + // private Integer imageAngle; + /** + * 证件⽬标的证件类型 + */ + // @JsonProperty("type_description") + // private String typeDescription; + /** + * 磅单对应的ID号(⽬前只有磅单返回) + */ + @JsonProperty("type_id") + private String typeId; +} diff --git a/blade-third-party-api/blade-ocr-api/src/main/java/org/springblade/thirdparty/ocr/pojo/vo/OCRRecognitionResultVO.java b/blade-third-party-api/blade-ocr-api/src/main/java/org/springblade/thirdparty/ocr/pojo/vo/OCRRecognitionResultVO.java new file mode 100644 index 0000000..136b77e --- /dev/null +++ b/blade-third-party-api/blade-ocr-api/src/main/java/org/springblade/thirdparty/ocr/pojo/vo/OCRRecognitionResultVO.java @@ -0,0 +1,34 @@ +package org.springblade.thirdparty.ocr.pojo.vo; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; +import java.util.List; + +/** + * ocr 磅单卡证识别响应参数 证件⽬标的识别结果 + * @author bfhuange + * @date 2024/11/4 + */ +@Data +public class OCRRecognitionResultVO implements Serializable { + @Serial + private static final long serialVersionUID = 1L; + /** + * 证件⽬标结构化结果(系统调⽤主要使⽤的信息) + */ + @JsonProperty("structured") + private OCRStructureVO structured; + /** + * ⼦⽬标证件的基本识别信息 + */ + @JsonProperty("image_info") + private OCRRecognitionImageInfoVO imageInfo; + /** + * 所有的识别结果,非必须 + */ + //@JsonProperty("lines") + //private List lines; +} diff --git a/blade-third-party-api/blade-ocr-api/src/main/java/org/springblade/thirdparty/ocr/pojo/vo/OCRResultVO.java b/blade-third-party-api/blade-ocr-api/src/main/java/org/springblade/thirdparty/ocr/pojo/vo/OCRResultVO.java new file mode 100644 index 0000000..de419be --- /dev/null +++ b/blade-third-party-api/blade-ocr-api/src/main/java/org/springblade/thirdparty/ocr/pojo/vo/OCRResultVO.java @@ -0,0 +1,38 @@ +package org.springblade.thirdparty.ocr.pojo.vo; + +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; + +/** + * ocr 返回结果 + * @author bfhuange + * @date 2024/11/4 + */ +@Data +public class OCRResultVO implements Serializable { + @Serial + private static final long serialVersionUID = 1L; + /** + * 程序执⾏状态(200: 成功且为卡证数据, + * -1:图像是⻋牌,但质量有问题, + * -2: 没有检测出⻋牌或证件⽬标, + * -3: 输⼊的img_bas64有问题, + * -4: 没有检测出证件, + * -5: 输⼊的数据类型不⽀持(⽀持img_bas64/img_path), + * -6: C++接⼝问题, + * -7: 图像质量问题,有⻋辆没有⻋牌, + * -8: 运⾏成功但程序识别内容为空, + * -9: json数据异常) + */ + private Integer code; + /** + * code的解释说明 + */ + private String message; + /** + * 返回的识别结果 + */ + private T result; +} diff --git a/blade-third-party-api/blade-ocr-api/src/main/java/org/springblade/thirdparty/ocr/pojo/vo/OCRStructureVO.java b/blade-third-party-api/blade-ocr-api/src/main/java/org/springblade/thirdparty/ocr/pojo/vo/OCRStructureVO.java new file mode 100644 index 0000000..fe8a254 --- /dev/null +++ b/blade-third-party-api/blade-ocr-api/src/main/java/org/springblade/thirdparty/ocr/pojo/vo/OCRStructureVO.java @@ -0,0 +1,39 @@ +package org.springblade.thirdparty.ocr.pojo.vo; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; +import java.util.List; + +/** + * ocr 磅单卡证识别响应参数 证件⽬标结构化结果 + * @author bfhuange + * @date 2024/11/4 + */ +@Data +public class OCRStructureVO implements Serializable { + @Serial + private static final long serialVersionUID = 1L; + /** + * 磅单对应的ID号(⽬前只有磅单返回) + */ + @JsonProperty("type_id") + private String typeId; + /** + * 证件⽬标的证件类型 + */ + @JsonProperty("type_description") + private String typeDescription; + /** + * 磅单对应的ID号的单位(⽬前只有磅单返回), 当未知单位的时候返回"", ⽬前会返回的字段有: t, T, 吨, kg, 公⽄, 千克 + */ + @JsonProperty("unit_measure") + private String unitMeasure; + /** + * 结构化提取后的key-value-description信息 + */ + @JsonProperty("key_info") + private List keyInfos; +} diff --git a/blade-third-party-api/blade-ocr-api/src/main/java/org/springblade/thirdparty/ocr/pojo/vo/TransportCertificateVO.java b/blade-third-party-api/blade-ocr-api/src/main/java/org/springblade/thirdparty/ocr/pojo/vo/TransportCertificateVO.java new file mode 100644 index 0000000..5151789 --- /dev/null +++ b/blade-third-party-api/blade-ocr-api/src/main/java/org/springblade/thirdparty/ocr/pojo/vo/TransportCertificateVO.java @@ -0,0 +1,224 @@ +package org.springblade.thirdparty.ocr.pojo.vo; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.databind.ser.std.NullSerializer; +import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import org.springblade.core.tool.utils.DateUtil; + +import java.io.Serial; +import java.io.Serializable; +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +/** + * 车辆运输凭证 + * @author bfhuange + * @date 2024/11/6 + */ +@Data +@Schema(description = "车辆运输凭证识别结果") +public class TransportCertificateVO implements Serializable { + @Serial + private static final long serialVersionUID = 1L; + /** + * 车头车牌号 + */ + @Schema(description = "车头车牌号") + private String carFrontPlateNo; + /** + * 车尾车牌号 + */ + @Schema(description = "车尾车牌号") + private String carBackPlateNo; + /** + * 行驶证上的车牌号 + */ + @Schema(description = "行驶证上的车牌号") + private String drivingPlateNo; + /** + * 行驶证车牌号 + */ + @Schema(description = "行驶证车牌号") + private String vehicleLicensePlateNo; + /** + * 行驶证车辆类型 + */ + @Schema(description = "行驶证车辆类型") + private String vehicleLicenseVehicleType; + /** + * 行驶证所有人 + */ + @Schema(description = "行驶证所有人") + private String vehicleLicenseOwnerName; + /** + * 行驶证使用性质 + */ + @Schema(description = "行驶证使用性质") + private String vehicleLicenseUseCharacter; + /** + * 行驶证品牌型号 + */ + @Schema(description = "行驶证品牌型号") + private String vehicleLicenseModel; + /** + * 行驶证车辆识别代号 + */ + @Schema(description = "行驶证车辆识别代号") + private String vehicleLicenseVin; + /** + * 行驶证发动机号码 + */ + @Schema(description = "行驶证发动机号码") + private String vehicleLicenseEngineNo; + /** + * 行驶证注册日期 + */ + @JsonFormat(pattern = DateUtil.PATTERN_DATE,timezone = "GMT+8") + @Schema(description = "行驶证注册日期") + private Date vehicleLicenseRegisterDate; + /** + * 行驶证发证日期 + */ + @JsonFormat(pattern = DateUtil.PATTERN_DATE,timezone = "GMT+8") + @Schema(description = "行驶证发证日期") + private Date vehicleLicenseIssueDate; + /** + * 行驶证住址 + */ + @Schema(description = "行驶证住址") + private String vehicleLicenseAddress; + /** + * 行驶证档案编号 + */ + @Schema(description = "行驶证档案编号") + private String vehicleLicenseFileNo; + /** + * 行驶证检验有效期 + */ + @JsonFormat(pattern = DateUtil.PATTERN_DATE,timezone = "GMT+8") + @Schema(description = "行驶证检验有效期") + private Date vehicleLicenseInspectionValidDate; + /** + * 驾驶证有效期限(终⽌时间) + */ + @JsonFormat(pattern = DateUtil.PATTERN_DATE,timezone = "GMT+8") + @Schema(description = "驾驶证有效期限(终⽌时间)") + private Date driverLicenseValidDateEnd; + /** + * 驾驶证姓名 + */ + @Schema(description = "驾驶证姓名") + private String driverLicenseName; + /** + * 驾驶证证号 + */ + @Schema(description = "驾驶证证号") + private String driverLicenseIdNo; + /** + * 驾驶证档案编号 + */ + @Schema(description = "驾驶证档案编号") + private String driverLicenseFileNo; + /** + * 准驾车型 + */ + @Schema(description = "准驾车型") + private String driverLicenseType; + /** + * 驾驶证有效期限(起始时间) + */ + @JsonFormat(pattern = DateUtil.PATTERN_DATE,timezone = "GMT+8") + @Schema(description = "驾驶证有效期限(起始时间)") + private Date driverLicenseValidDateStart; + /** + * 磅单车牌号 + */ + @Schema(description = "磅单车牌号") + private String poundPlateNo; + /** + * 磅单供货日期 + */ + @JsonFormat(pattern = DateUtil.PATTERN_DATE,timezone = "GMT+8") + @Schema(description = "磅单供货日期") + private Date supplyMaterialDate; + /** + * 货物名称 + */ + @Schema(description = "货物名称") + private String productName; + /** + * 规格料型 + */ + @Schema(description = "规格料型") + private String specificationMaterial; + /** + * 单位 + */ + @Schema(description = "单位") + private String unitMeasure; + /** + * ⽑重 + */ + @JsonSerialize(using = ToStringSerializer.class, nullsUsing = NullSerializer.class) + @Schema(description = "⽑重") + private BigDecimal grossWeight; + /** + * ⽪重 + */ + @JsonSerialize(using = ToStringSerializer.class, nullsUsing = NullSerializer.class) + @Schema(description = "⽪重") + private BigDecimal skinWeight; + /** + * 净重 + */ + @JsonSerialize(using = ToStringSerializer.class, nullsUsing = NullSerializer.class) + @Schema(description = "净重") + private BigDecimal netWeight; + /** + * 扣重 + */ + @JsonSerialize(using = ToStringSerializer.class, nullsUsing = NullSerializer.class) + @Schema(description = "扣重") + private BigDecimal miscellaneousWeight; + /** + * 实际净重 + */ + @JsonSerialize(using = ToStringSerializer.class, nullsUsing = NullSerializer.class) + @Schema(description = "实际净重") + private BigDecimal realityWeight; + /** + * 磅单附件索引,只取一个,其余的丢弃 + */ + @Schema(description = "磅单附件索引,只取一个,其余的丢弃") + private List poundIndexes = new ArrayList<>(); + /** + * 驾驶证附件索引,只取一个,其余的丢弃 + */ + @Schema(description = "驾驶证附件索引,只取一个,其余的丢弃") + private List driverLicenseIndexes = new ArrayList<>(); + /** + * 行驶证附件索引,只取一个,其余的丢弃 + */ + @Schema(description = "行驶证附件索引,只取一个,其余的丢弃") + private List vehicleLicenseIndexes = new ArrayList<>(); + /** + * 车头附件索引,只取一个,其余的丢弃 + */ + @Schema(description = "车头附件索引,只取一个,其余的丢弃") + private List carFrontIndexes = new ArrayList<>(); + /** + * 车尾附件索引,只取一个,其余的丢弃 + */ + @Schema(description = "车尾附件索引,只取一个,其余的丢弃") + private List carBackIndexes = new ArrayList<>(); + /** + * 其他附件索引 + */ + @Schema(description = "其他附件索引") + private List otherIndexes = new ArrayList<>(); +} diff --git a/blade-third-party-api/blade-ocr-api/src/main/java/org/springblade/thirdparty/ocr/service/IOCRService.java b/blade-third-party-api/blade-ocr-api/src/main/java/org/springblade/thirdparty/ocr/service/IOCRService.java new file mode 100644 index 0000000..7cf74af --- /dev/null +++ b/blade-third-party-api/blade-ocr-api/src/main/java/org/springblade/thirdparty/ocr/service/IOCRService.java @@ -0,0 +1,39 @@ +package org.springblade.thirdparty.ocr.service; + +import org.springblade.thirdparty.ocr.pojo.dto.CertificateBatchRecognitionDTO; +import org.springblade.thirdparty.ocr.pojo.dto.CertificateRecognitionDTO; +import org.springblade.thirdparty.ocr.pojo.vo.IDCardVO; +import org.springblade.thirdparty.ocr.pojo.vo.TransportCertificateVO; + +import java.util.List; + +/** + * ocr 接口 + * @author bfhuange + * @date 2024/11/6 + */ +public interface IOCRService { + + /** + * 识别车辆运输凭证,同一个车辆的图片地址批量传入,不知道每张图片类型 + * @param param 参数 + * @return + */ + TransportCertificateVO recognitionTransportCertificate(CertificateBatchRecognitionDTO param); + + /** + * 识别车辆运输凭证,依次调用单独识别接口,知道每张图片类型 + * + * @param param 各个类型图片链接 + * @param code + * @return + */ + TransportCertificateVO recognitionTransportCertificate(CertificateRecognitionDTO param, String code); + + /** + * 识别身份证,支持同时传同一身份证的正反面,不同身份证的会覆盖 + * @param urls 图片链接列表 + * @return + */ + IDCardVO recognitionIDCard(List urls); +} diff --git a/blade-third-party-api/blade-ocr-api/src/main/java/org/springblade/thirdparty/ocr/service/impl/OCRServiceImpl.java b/blade-third-party-api/blade-ocr-api/src/main/java/org/springblade/thirdparty/ocr/service/impl/OCRServiceImpl.java new file mode 100644 index 0000000..1efee09 --- /dev/null +++ b/blade-third-party-api/blade-ocr-api/src/main/java/org/springblade/thirdparty/ocr/service/impl/OCRServiceImpl.java @@ -0,0 +1,370 @@ +package org.springblade.thirdparty.ocr.service.impl; + +import cn.hutool.core.lang.TypeReference; +import cn.hutool.json.JSONUtil; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.jetbrains.annotations.Nullable; +import org.springblade.core.tool.utils.CollectionUtil; +import org.springblade.core.tool.utils.ObjectUtil; +import org.springblade.core.tool.utils.StringUtil; +import org.springblade.thirdparty.ocr.constant.OCRConstant; +import org.springblade.thirdparty.ocr.constant.OCRImageType; +import org.springblade.thirdparty.ocr.convert.AttachmentIndexConvert; +import org.springblade.thirdparty.ocr.exception.OCRException; +import org.springblade.thirdparty.ocr.feign.IOCRClient; +import org.springblade.thirdparty.ocr.feign.IOCROtherClient; +import org.springblade.thirdparty.ocr.pojo.dto.*; +import org.springblade.thirdparty.ocr.pojo.vo.*; +import org.springblade.thirdparty.ocr.service.IOCRService; +import org.springblade.thirdparty.ocr.util.ConvertUtil; +import org.springframework.stereotype.Service; +import org.springframework.util.CollectionUtils; + +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.util.*; +import java.util.function.BiConsumer; +import java.util.function.BiFunction; +import java.util.function.Supplier; + +/** + * @author bfhuange + * @date 2024/11/6 + */ +@Slf4j +@RequiredArgsConstructor +@Service +public class OCRServiceImpl implements IOCRService { + /** + * 限制 200,超过转为kg,否则为t + */ + private static final BigDecimal WEIGHT_LIMIT = BigDecimal.valueOf(200); + + private final IOCRClient client; + private final IOCROtherClient otherClient; + + @Override + public TransportCertificateVO recognitionTransportCertificate(CertificateBatchRecognitionDTO param) { + log.info("ocr 批量识别参数:{}", JSONUtil.toJsonStr(param)); + if (param == null || CollectionUtils.isEmpty(param.getUrls())) { + throw new OCRException("图像链接不能为空"); + } + List> result = this.getCardResult(() -> client.batchRecognition(new OCRBigCommonCardBatchDTO(param.getUrls(), param.getProjectAbbreviation(), param.getDirName()), param.getCode()), "批量磅单卡证识别"); + List cards = result.stream() + .map(OCRResultVO::getResult) + .toList(); + return convertTransportCertificate(cards); + } + + @Override + public TransportCertificateVO recognitionTransportCertificate(CertificateRecognitionDTO param, String code) { + log.info("ocr 单张识别参数:{}", JSONUtil.toJsonStr(param)); + if (param == null || ObjectUtil.isEmpty(param)) { + throw new OCRException("凭证识别参数不能为空"); + } + List cards = new ArrayList<>(4); + if (StringUtil.isNotBlank(param.getPoundUrl())) { + // 磅单 + OCRBigCommonCardVO card = this.getCardResult(() -> client.poundRecognition(new OCRSingleDTO(param.getPoundUrl(), param.getName()), code), "磅单单独识别"); + if (card != null) { + cards.add(card); + } + } + if (StringUtil.isNotBlank(param.getDriverLicenseUrl())) { + // 驾驶证 + OCRBigCommonCardVO card = this.getCardResult(() -> client.singleRecognition(new OCRBigCommonCardDTO(param.getDriverLicenseUrl(), param.getName(), + // 驾驶证-正页 + List.of(OCRImageType.DRIVER_LICENSE_MAIN.getId(), + // 驾驶证-副页 + OCRImageType.DRIVER_LICENSE_SIDE.getId(), + // 驾驶证-电子版 + OCRImageType.DRIVER_LICENSE_ELEC.getId())), code + ), "驾驶证识别"); + if (card != null) { + cards.add(card); + } + } + if (StringUtil.isNotBlank(param.getVehicleLicenseUrl())) { + // 行驶证 + OCRBigCommonCardVO card = this.getCardResult(() -> client.singleRecognition(new OCRBigCommonCardDTO(param.getVehicleLicenseUrl(), param.getName(), + // ⾏驶证-正⻚-挂车 + List.of(OCRImageType.VEHICLE_LICENSE_MAIN_GUA.getId(), + // ⾏驶证-正⻚ + OCRImageType.VEHICLE_LICENSE_MAIN.getId(), + // ⾏驶证-副⻚ + OCRImageType.VEHICLE_LICENSE_SIDE.getId())), code + ), "行驶证识别"); + if (card != null) { + cards.add(card); + } + } + if (StringUtil.isNotBlank(param.getCarFrontUrl())) { + // 车头 + OCRBigCommonCardVO card = this.getCardResult(() -> client.plateNoRecognition(new OCRSingleDTO(param.getCarFrontUrl(), param.getName()), code), "⻋牌单独识别"); + if (card != null) { + cards.add(card); + } + } + return CollectionUtil.isEmpty(cards) ? null : convertTransportCertificate(cards); + } + + /** + * 获取卡证结果 + * @param resultSupplier + * @param name + * @return + */ + private T getCardResult(Supplier> resultSupplier, String name) { + try { + OCRResultVO resultVO = resultSupplier.get(); + log.info("调用ocr接口 {} 响应参数:{}", name, JSONUtil.toJsonStr(resultVO)); + if (resultVO != null && OCRConstant.SUCCESS_CODE.equals(resultVO.getCode())) { + // 成功 + return resultVO.getResult(); + } + log.error("调用ocr接口 {} 异常响应参数:{}", name, JSONUtil.toJsonStr(resultVO)); + String message = getMessage(resultVO); + throw new OCRException("调用ocr接口" + name + "异常 " + message); + } catch (OCRException e) { + throw e; + } catch (Exception e) { + log.error("调用ocr接口 {} 异常", name, e); + throw new OCRException("调用ocr接口" + name + "异常"); + } + } + + /** + * 转换车辆凭证 + * @param cards + * @return + */ + private static TransportCertificateVO convertTransportCertificate(List cards) { + TransportCertificateVO result = new TransportCertificateVO(); + if (CollectionUtils.isEmpty(cards)) { + return result; + } + OCRBigCommonCardVO card = null; + for (int index = 0; index < cards.size(); index++) { + card = cards.get(index); + convert(result, card, ConvertUtil::getTransportCertificateSetter, index); + } + // 处理重量单位 + handleWeightUnit(result); + return result; + } + + /** + * 处理重量单位 + * @param result + */ + private static void handleWeightUnit(TransportCertificateVO result) { + if (result == null) { + return; + } + // 重量超过200,默认kg,否则t + if (overLimit(result.getGrossWeight()) || overLimit(result.getSkinWeight()) || overLimit(result.getNetWeight())) { + // 毛重、皮重、净重 超过限制,是kg + result.setGrossWeight(divideThousand(result.getGrossWeight())); + result.setSkinWeight(divideThousand(result.getSkinWeight())); + result.setNetWeight(divideThousand(result.getNetWeight())); + result.setMiscellaneousWeight(divideThousand(result.getMiscellaneousWeight())); + result.setRealityWeight(divideThousand(result.getRealityWeight())); + return; + } + String unitMeasure = result.getUnitMeasure(); + if (OCRConstant.UNIT_T.equalsIgnoreCase(unitMeasure) || OCRConstant.UNIT_T_ZH.equals(unitMeasure)) { + // t或者吨,无需处理 + return; + } + if (OCRConstant.UNIT_KG.equalsIgnoreCase(unitMeasure)) { + // kg,转成吨保留5位小数 + result.setGrossWeight(divideThousand(result.getGrossWeight())); + result.setSkinWeight(divideThousand(result.getSkinWeight())); + result.setNetWeight(divideThousand(result.getNetWeight())); + result.setMiscellaneousWeight(divideThousand(result.getMiscellaneousWeight())); + result.setRealityWeight(divideThousand(result.getRealityWeight())); + return; + } + if (StringUtil.isNotBlank(unitMeasure)) { + log.warn("未知的单位类型:{}", unitMeasure); + } + } + + /** + * 是否超过限制 + * @param weight + * @return + */ + private static boolean overLimit(BigDecimal weight) { + return weight != null && weight.compareTo(WEIGHT_LIMIT) > 0; + } + + /** + * 除以1000保留5位小数 + * @param num + * @return + */ + private static BigDecimal divideThousand(BigDecimal num) { + if (num == null) { + return null; + } + return num.divide(BigDecimal.valueOf(1000), 5, RoundingMode.HALF_UP); + } + + @Override + public IDCardVO recognitionIDCard(List urls) { + if (CollectionUtils.isEmpty(urls)) { + throw new OCRException("图像链接不能为空"); + } + IDCardVO result = new IDCardVO(); + try { + for (String url : urls) { + OCRCardBankDetRecDTO param = new OCRCardBankDetRecDTO(url); +// param.setDataType("IDCard-Main"); + OCRResultVO resultVO = otherClient.cardBankDetRec(param); + if (resultVO != null && OCRConstant.SUCCESS_CODE.equals(resultVO.getCode())) { + // 成功 + OCRBigCommonCardVO card = resultVO.getResult(); + convertIDCard(result, card); + continue; + } + log.error("调用ocr接口证件定位+结构化信息提取异常响应参数:{}", JSONUtil.toJsonStr(resultVO)); + String message = getMessage(resultVO); + throw new OCRException("调用ocr接口证件定位+结构化信息提取异常 " + message); + } + } catch (OCRException e) { + throw e; + } catch (Exception e) { + log.error("调用ocr接口证件定位+结构化信息提取异常", e); + throw new OCRException("调用ocr接口证件定位+结构化信息提取异常"); + } + return result; + } + + /** + * 转换身份证 + * @param result + * @param card + * @return + */ + private void convertIDCard(IDCardVO result, OCRBigCommonCardVO card) { + convert(result, card, ConvertUtil::getIDCardSetter, null); + } + + /** + * 转换 + * + * @param + * @param result + * @param card + * @param setterGetter + * @param index + */ + private static void convert(T result, OCRBigCommonCardVO card, BiFunction> setterGetter, Integer index) { + if (result == null || card == null || setterGetter == null) { + return; + } + + List objects = card.getObjects(); + if (CollectionUtils.isEmpty(objects)) { + return; + } + // 排序类型 + sortByTypeId(objects); + for (OCRRecognitionResultVO object : objects) { + OCRStructureVO structured = object.getStructured(); + String typeId = getTypeId(object); + if (typeId == null) { + continue; + } + // 是否处理数据 + boolean handleData = true; + if (index != null && result instanceof TransportCertificateVO transportCertificate) { + // 车辆凭证类型,添加索引到list + // 添加到默认list要处理数据,添加到其他list,不处理数据 + handleData = AttachmentIndexConvert.getConvertByTypeId(typeId) + .addIndex2list(transportCertificate, index); + } + if (structured == null || !handleData) { + // 提取的数据为空或添加到了其他附件,不处理数据了,保持图片和数据一致 + continue; + } + List keyInfos = structured.getKeyInfos(); + if (CollectionUtils.isEmpty(keyInfos)) { + continue; + } + for (OCRKeyInfoVO keyInfo : keyInfos) { + // 根据typeId和key查找setter,找到了执行 + Optional.ofNullable(setterGetter.apply(typeId, keyInfo.getKey())) + .ifPresent(consumer -> consumer.accept(result, keyInfo.getValue())); + } + } + } + + /** + * 按类型排序 + * @param objects + */ + private static void sortByTypeId(List objects) { + // 行驶证类型的,把 VehicleLicense-Main 放到第一个,优先取它的数据 + List imageTypeIds = AttachmentIndexConvert.VEHICLE_LICENSE.getImageTypeIds(); + boolean vehicleLicense = objects.stream() + .map(OCRServiceImpl::getTypeId) + .filter(Objects::nonNull) + .anyMatch(imageTypeIds::contains); + if (vehicleLicense) { + objects.sort(Comparator.comparing(OCRServiceImpl::getTypeId, Comparator.comparing(typeId -> { + if (typeId == null) { + return Integer.MAX_VALUE; + } + int index = imageTypeIds.indexOf(typeId); + if (index < 0) { + return Integer.MAX_VALUE - 1; + } + return index; + }))); + } + } + + @Nullable + private static String getTypeId(OCRRecognitionResultVO object) { + OCRStructureVO structured = object.getStructured(); + String typeId = Optional.ofNullable(structured) + .map(OCRStructureVO::getTypeId) + .orElse(null); + if (typeId == null) { + // typeId为空,从 imageInfo 中取 + OCRRecognitionImageInfoVO imageInfo = object.getImageInfo(); + typeId = Optional.ofNullable(imageInfo) + .map(OCRRecognitionImageInfoVO::getTypeId) + .orElse(null); + } + return typeId; + } + + /** + * 获取错误消息 + * @param resultVO + * @return + * @param + */ + private String getMessage(OCRResultVO resultVO) { + return resultVO == null ? "" : resultVO.getMessage(); + } + + public static void main(String[] args) { + String json = ""; + OCRResultVO>> resultVO = JSONUtil.toBean(json, new TypeReference>>>() { + }, true); + if (resultVO != null && OCRConstant.SUCCESS_CODE.equals(resultVO.getCode())) { + // 成功 + List> result = resultVO.getResult(); + List cards = result.stream() + .map(OCRResultVO::getResult) + .toList(); + TransportCertificateVO transportCertificateVO = convertTransportCertificate(cards); + System.out.println(JSONUtil.toJsonStr(transportCertificateVO)); + } + } +} diff --git a/blade-third-party-api/blade-ocr-api/src/main/java/org/springblade/thirdparty/ocr/util/ConvertUtil.java b/blade-third-party-api/blade-ocr-api/src/main/java/org/springblade/thirdparty/ocr/util/ConvertUtil.java new file mode 100644 index 0000000..cf231b2 --- /dev/null +++ b/blade-third-party-api/blade-ocr-api/src/main/java/org/springblade/thirdparty/ocr/util/ConvertUtil.java @@ -0,0 +1,319 @@ +package org.springblade.thirdparty.ocr.util; + +import lombok.extern.slf4j.Slf4j; +import org.springblade.core.tool.utils.DateUtil; +import org.springblade.core.tool.utils.StringUtil; +import org.springblade.thirdparty.ocr.constant.OCRImageType; +import org.springblade.thirdparty.ocr.convert.IDCardConvert; +import org.springblade.thirdparty.ocr.convert.TransportCertificateConvert; +import org.springblade.thirdparty.ocr.pojo.vo.IDCardVO; +import org.springblade.thirdparty.ocr.pojo.vo.TransportCertificateVO; + +import java.math.BigDecimal; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * 转换工具类 + * @author bfhuange + * @date 2024/11/7 + */ +@Slf4j +public class ConvertUtil { + private ConvertUtil() {} + + /** + * 有效期分隔符 + */ + private static final String DATE_VALID_SPLIT = "至"; + /** + * 有效期至 长期 + */ + private static final String DATE_VALID_END_LONG_TERM = "长期"; + /** + * 日期格式 + */ + private static final Pattern DATE_PATTERN = Pattern.compile("(\\d{4})[-./年](\\d{1,2})[-./月](\\d{1,2})日?"); + + /** + * 非磅单类型set + */ + private static final Set NONE_POUND_TYPE_SET = initNonePoundTypeSet(); + /** + * 车辆凭证setter map + */ + private static final Map> TRANSPORT_CERTIFICATE_SETTER_MAP = initTransportCertificateSetterMap(); + /** + * 身份证setter map + */ + private static final Map>> ID_CARD_SETTER_MAP = initIDCardSetterMap(); + + /** + * 获取车辆凭证setter + * @param typeId + * @param key + * @return + */ + public static BiConsumer getTransportCertificateSetter(String typeId, String key) { + String mapKey = ConvertUtil.generateKey(typeId, key); + return TRANSPORT_CERTIFICATE_SETTER_MAP.get(mapKey); + } + + /** + * 获取身份证setter + * @param typeId + * @param key + * @return + */ + public static BiConsumer getIDCardSetter(String typeId, String key) { + Map> setterMap = ID_CARD_SETTER_MAP.get(typeId); + if (setterMap == null) { + return null; + } + BiConsumer setter = setterMap.get(key); + if (setter == null) { + return null; + } + return setter; + } + + /** + * 转换日期 + * @param dateString + * @return + */ + public static Consumer> convertDate(String dateString) { + Date date = ConvertUtil.convert2date(dateString); + return consumer -> consumer.accept(date); + } + + /** + * 转换有效期 + * @param dateString + * @return + */ + public static Consumer> convertDateValid(String dateString) { + Date date = ConvertUtil.convert2dateValid(dateString); + return consumer -> consumer.accept(date); + } + + /** + * 转换有效期 + * @param dateString 日期字符串,2015-05-31至2025-05-31 + * @return + */ + private static Date convert2dateValid(String dateString) { + if (StringUtil.isBlank(dateString)) { + return null; + } + if (dateString.contains(DATE_VALID_END_LONG_TERM)) { + // 如果有效期至是长期,设置为10年后 + return DateUtil.plusYears(new Date(), 10); + } + Date rangeEndDate = ConvertUtil.parseDateRange(dateString, true); + if (rangeEndDate != null) { + return rangeEndDate; + } + // 按至分隔,解析最后一截日期 + String[] splitDate = dateString.split(DATE_VALID_SPLIT); + return ConvertUtil.convert2date(splitDate[splitDate.length -1]); + } + + /** + * 截取有效期止 + * @param dateString + * @return + */ + public static Consumer> extractEndDate(String dateString) { + Date date = ConvertUtil.parseEndDate(dateString); + return consumer -> consumer.accept(date); + } + + /** + * 截取有效期起 + * @param dateString + * @return + */ + public static Consumer> extractStartDate(String dateString) { + Date date = ConvertUtil.parseDateRange(dateString, false); + return consumer -> consumer.accept(date); + } + + /** + * 解析有效期止 + * @param dateString 日期字符串,格式 2020.12.15-2030.12.15 + * @return + */ + private static Date parseEndDate(String dateString) { + Date date = ConvertUtil.parseDateRange(dateString, true); + if (date != null) { + return date; + } + if (dateString == null || dateString.isBlank()) { + return null; + } + dateString = dateString.substring(dateString.lastIndexOf("-") + 1); + return DateUtil.parse(dateString, "yyyy.MM.dd"); + } + + /** + * 解析日期区间 + * @param dateString + * @param end + * @return + */ + private static Date parseDateRange(String dateString, boolean end) { + if (dateString == null || dateString.isBlank()) { + return null; + } + Matcher matcher = DATE_PATTERN.matcher(dateString); + String dateValue = null; + while (matcher.find()) { + dateValue = matcher.group(); + if (!end) { + break; + } + } + return dateValue == null ? null : ConvertUtil.convert2date(dateValue); + } + + /** + * 转换重量数值 + * + * @param numString 重量字符串 + * @return + */ + public static Consumer> convertWeightBigDecimal(String numString) { + BigDecimal bigDecimal = ConvertUtil.convert2WeightBigDecimal(numString); + return consumer -> consumer.accept(bigDecimal); + } + + /** + * 字符串转日期 + * @param dateString + * @return + */ + private static Date convert2date(String dateString) { + if (dateString == null || dateString.isBlank()) { + return null; + } + try { + return DateUtil.parse(ConvertUtil.normalizeDateString(dateString), "yyyy-MM-dd"); + } catch (Exception e) { + log.error("解析日期异常", e); + return null; + } + } + + /** + * 归一化日期字符串 + * @param dateString + * @return + */ + private static String normalizeDateString(String dateString) { + String value = dateString.trim() + .replace("年", "-") + .replace("月", "-") + .replace("日", "") + .replace("/", "-") + .replace(".", "-"); + String[] parts = value.split("-"); + if (parts.length != 3) { + return value; + } + return parts[0] + "-" + ConvertUtil.normalizeDatePart(parts[1]) + "-" + + ConvertUtil.normalizeDatePart(parts[2]); + } + + /** + * 归一化月日 + * @param value + * @return + */ + private static String normalizeDatePart(String value) { + String part = value.strip(); + return part.length() == 1 ? "0" + part : part; + } + + /** + * 字符串转数值 + * + * @param numString + * @return + */ + public static BigDecimal convert2WeightBigDecimal(String numString) { + if (numString == null || numString.isBlank()) { + return null; + } + try { + return new BigDecimal(numString); + } catch (Exception e) { + log.error("解析数值异常", e); + return null; + } + } + + /** + * 初始化 非磅单类型set + * @return + */ + private static Set initNonePoundTypeSet() { + return Stream.of(OCRImageType.values()) + .map(OCRImageType::getId) + .collect(Collectors.toSet()); + } + + /** + * 初始化setter map + * @return + */ + private static Map> initTransportCertificateSetterMap() { + Map> setterMap = new HashMap<>(); + for (TransportCertificateConvert convert : TransportCertificateConvert.values()) { + if (convert.getImageTypes().isEmpty()) { + // 图像类型列表为空,设置类型id为空,只用key + String key = ConvertUtil.generateKey(null, convert.getKeyInfo().getKey()); + setterMap.put(key, convert.getSetter()); + } else { + for (OCRImageType imageType : convert.getImageTypes()) { + // 图像类型列表不为空,每个类型id和key重新生成key + String key = ConvertUtil.generateKey(imageType.getId(), convert.getKeyInfo().getKey()); + setterMap.put(key, convert.getSetter()); + } + } + } + return setterMap; + } + + /** + * 初始化 身份证 setter map + * @return + */ + private static Map>> initIDCardSetterMap() { + return Stream.of(IDCardConvert.values()) + // 先按类型id分组 + .collect(Collectors.groupingBy(convert -> convert.getImageType().getId(), + // 再按key分组 + Collectors.toMap(convert -> convert.getKeyInfo().getKey(), IDCardConvert::getSetter, (a, b) -> a) + )); + } + + /** + * 生成key,typeId + - + key + * @param typeId + * @param key + * @return + */ + private static String generateKey(String typeId, String key) { + return typeId == null || !NONE_POUND_TYPE_SET.contains(typeId)? key : typeId + "-" + key; + } + +} diff --git a/blade-third-party-api/blade-ocr-api/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/blade-third-party-api/blade-ocr-api/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 0000000..2c6637b --- /dev/null +++ b/blade-third-party-api/blade-ocr-api/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1 @@ +org.springblade.thirdparty.ocr.config.ThirdPartyOCRAutoConfiguration \ No newline at end of file diff --git a/blade-third-party-api/blade-track-api/pom.xml b/blade-third-party-api/blade-track-api/pom.xml new file mode 100644 index 0000000..e2a07cf --- /dev/null +++ b/blade-third-party-api/blade-track-api/pom.xml @@ -0,0 +1,22 @@ + + + 4.0.0 + + org.springblade + blade-third-party-api + ${revision} + + + blade-track-api + ${project.artifactId} + jar + + + + org.springblade + blade-core-tool + + + \ No newline at end of file diff --git a/blade-third-party-api/blade-track-api/src/main/java/org/springblade/thirdparty/track/config/ThirdPartyTrackAutoConfiguration.java b/blade-third-party-api/blade-track-api/src/main/java/org/springblade/thirdparty/track/config/ThirdPartyTrackAutoConfiguration.java new file mode 100644 index 0000000..d90e722 --- /dev/null +++ b/blade-third-party-api/blade-track-api/src/main/java/org/springblade/thirdparty/track/config/ThirdPartyTrackAutoConfiguration.java @@ -0,0 +1,16 @@ +package org.springblade.thirdparty.track.config; + +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; + +/** + * 第三方api 轨迹配置类 + * @author bfhuange + * @date 2024/12/18 + */ +@ComponentScan("org.springblade.thirdparty.track") +@EnableConfigurationProperties(TrackProperties.class) +@Configuration +public class ThirdPartyTrackAutoConfiguration { +} diff --git a/blade-third-party-api/blade-track-api/src/main/java/org/springblade/thirdparty/track/config/TrackFeignClientConfig.java b/blade-third-party-api/blade-track-api/src/main/java/org/springblade/thirdparty/track/config/TrackFeignClientConfig.java new file mode 100644 index 0000000..ecb5b1a --- /dev/null +++ b/blade-third-party-api/blade-track-api/src/main/java/org/springblade/thirdparty/track/config/TrackFeignClientConfig.java @@ -0,0 +1,17 @@ +package org.springblade.thirdparty.track.config; + +import feign.RequestInterceptor; +import org.springframework.context.annotation.Bean; + +/** + * @author bfhuange + * @since 2024/12/18 + */ +public class TrackFeignClientConfig { + @Bean + public RequestInterceptor requestInterceptor() { + return template -> { + // 空实现屏蔽 全局拦截器 BladeFeignRequestInterceptor + }; + } +} diff --git a/blade-third-party-api/blade-track-api/src/main/java/org/springblade/thirdparty/track/config/TrackProperties.java b/blade-third-party-api/blade-track-api/src/main/java/org/springblade/thirdparty/track/config/TrackProperties.java new file mode 100644 index 0000000..8cd8a8b --- /dev/null +++ b/blade-third-party-api/blade-track-api/src/main/java/org/springblade/thirdparty/track/config/TrackProperties.java @@ -0,0 +1,31 @@ +package org.springblade.thirdparty.track.config; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +/** + * 轨迹 配置 + * @author bfhuange + * @since 2024/12/16 + */ +@Data +@ConfigurationProperties(prefix = "third-party.track") +public class TrackProperties { + /** + * 是否启用推送到轨迹系统 + */ + private boolean enabled = false; + /** + * 轨迹基础路径 + */ + private String baseUrl; + /** + * 系统标识 + */ + private Integer bpsType; + /** + * 轨迹url + */ + private String trackUrlFormat = "/#/home/location-path?id=%s"; +} diff --git a/blade-third-party-api/blade-track-api/src/main/java/org/springblade/thirdparty/track/constant/TrackConstant.java b/blade-third-party-api/blade-track-api/src/main/java/org/springblade/thirdparty/track/constant/TrackConstant.java new file mode 100644 index 0000000..75f3957 --- /dev/null +++ b/blade-third-party-api/blade-track-api/src/main/java/org/springblade/thirdparty/track/constant/TrackConstant.java @@ -0,0 +1,13 @@ +package org.springblade.thirdparty.track.constant; + +/** + * 轨迹常量 + * @author bfhuange + * @since 2024/12/16 + */ +public class TrackConstant { + /** + * 响应成功状态码 + */ + public static final Integer SUCCESS_CODE = 200; +} diff --git a/blade-third-party-api/blade-track-api/src/main/java/org/springblade/thirdparty/track/exception/TrackException.java b/blade-third-party-api/blade-track-api/src/main/java/org/springblade/thirdparty/track/exception/TrackException.java new file mode 100644 index 0000000..17d1655 --- /dev/null +++ b/blade-third-party-api/blade-track-api/src/main/java/org/springblade/thirdparty/track/exception/TrackException.java @@ -0,0 +1,16 @@ +package org.springblade.thirdparty.track.exception; + +/** + * 轨迹异常 + * @author bfhuange + * @since 2024/12/16 + */ +public class TrackException extends RuntimeException { + public TrackException(String message) { + super(message); + } + + public TrackException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/blade-third-party-api/blade-track-api/src/main/java/org/springblade/thirdparty/track/exception/TrackExceptionTranslator.java b/blade-third-party-api/blade-track-api/src/main/java/org/springblade/thirdparty/track/exception/TrackExceptionTranslator.java new file mode 100644 index 0000000..958246a --- /dev/null +++ b/blade-third-party-api/blade-track-api/src/main/java/org/springblade/thirdparty/track/exception/TrackExceptionTranslator.java @@ -0,0 +1,28 @@ +package org.springblade.thirdparty.track.exception; + +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springblade.core.tool.api.R; +import org.springblade.core.tool.api.ResultCode; +import org.springblade.core.tool.utils.Func; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +/** + * @author bfhuange + * @since 2025/3/29 + */ +@AllArgsConstructor +@Slf4j +@RestControllerAdvice +public class TrackExceptionTranslator { + + @ExceptionHandler(TrackException.class) + @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) + public R handleError(TrackException e) { + log.error("track异常", e); + return R.fail(ResultCode.INTERNAL_SERVER_ERROR, (Func.isEmpty(e.getMessage()) ? ResultCode.INTERNAL_SERVER_ERROR.getMessage() : e.getMessage())); + } +} diff --git a/blade-third-party-api/blade-track-api/src/main/java/org/springblade/thirdparty/track/feign/ITrackClient.java b/blade-third-party-api/blade-track-api/src/main/java/org/springblade/thirdparty/track/feign/ITrackClient.java new file mode 100644 index 0000000..6c74e95 --- /dev/null +++ b/blade-third-party-api/blade-track-api/src/main/java/org/springblade/thirdparty/track/feign/ITrackClient.java @@ -0,0 +1,25 @@ +package org.springblade.thirdparty.track.feign; + +import org.springblade.thirdparty.track.config.TrackFeignClientConfig; +import org.springblade.thirdparty.track.pojo.dto.TrackWaybillSendDTO; +import org.springblade.thirdparty.track.pojo.vo.TrackResultVO; +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; + +/** + * 轨迹接口 + * @author bfhuange + * @date 2024/12/16 + */ +@FeignClient(name = "TRACK", url = "${thirdParty.track.baseUrl}", configuration = TrackFeignClientConfig.class) +public interface ITrackClient { + + /** + * 发送运单 + * @param param + * @return + */ + @PostMapping("/baseapi/nnl_waybill/send") + TrackResultVO sendWaybills(@RequestBody TrackWaybillSendDTO param); +} diff --git a/blade-third-party-api/blade-track-api/src/main/java/org/springblade/thirdparty/track/pojo/dto/TrackWaybillSendDTO.java b/blade-third-party-api/blade-track-api/src/main/java/org/springblade/thirdparty/track/pojo/dto/TrackWaybillSendDTO.java new file mode 100644 index 0000000..4ce9f49 --- /dev/null +++ b/blade-third-party-api/blade-track-api/src/main/java/org/springblade/thirdparty/track/pojo/dto/TrackWaybillSendDTO.java @@ -0,0 +1,32 @@ +package org.springblade.thirdparty.track.pojo.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serial; +import java.io.Serializable; +import java.util.List; + +/** + * 运单发送参数 + * @author bfhuange + * @since 2024/12/16 + */ +@AllArgsConstructor +@NoArgsConstructor +@Data +public class TrackWaybillSendDTO implements Serializable { + @Serial + private static final long serialVersionUID = 1L; + /** + * 历史运单id列表 + */ + private List ids; + /** + * 系统标识 + */ + @JsonProperty("ibps_type") + private Integer bpsType; +} diff --git a/blade-third-party-api/blade-track-api/src/main/java/org/springblade/thirdparty/track/pojo/vo/TrackResultVO.java b/blade-third-party-api/blade-track-api/src/main/java/org/springblade/thirdparty/track/pojo/vo/TrackResultVO.java new file mode 100644 index 0000000..4f04636 --- /dev/null +++ b/blade-third-party-api/blade-track-api/src/main/java/org/springblade/thirdparty/track/pojo/vo/TrackResultVO.java @@ -0,0 +1,25 @@ +package org.springblade.thirdparty.track.pojo.vo; + +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 轨迹响应结果 + * @author bfhuange + * @date 2024/12/16 + */ +@Data +public class TrackResultVO implements Serializable { + @Serial + private static final long serialVersionUID = 1L; + /** + * 响应代码。200为正确 + */ + private Integer code; + /** + * 对应的提示信息 + */ + private String msg; +} diff --git a/blade-third-party-api/blade-track-api/src/main/java/org/springblade/thirdparty/track/service/ITrackService.java b/blade-third-party-api/blade-track-api/src/main/java/org/springblade/thirdparty/track/service/ITrackService.java new file mode 100644 index 0000000..9d7bed1 --- /dev/null +++ b/blade-third-party-api/blade-track-api/src/main/java/org/springblade/thirdparty/track/service/ITrackService.java @@ -0,0 +1,30 @@ +package org.springblade.thirdparty.track.service; + +import java.util.List; + +/** + * 轨迹 接口 + * @author bfhuange + * @since 2024/12/16 + */ +public interface ITrackService { + + /** + * 发送运单 + * @param ids + */ + void sendWaybills(List ids); + + /** + * 获取轨迹url + * @param id 轨迹id + * @return + */ + String getTrackUrl(String id); + + /** + * 获取轨迹url前缀,后面拼接id就是正常url + * @return + */ + String getTrackUrlPrefix(); +} diff --git a/blade-third-party-api/blade-track-api/src/main/java/org/springblade/thirdparty/track/service/impl/TrackServiceImpl.java b/blade-third-party-api/blade-track-api/src/main/java/org/springblade/thirdparty/track/service/impl/TrackServiceImpl.java new file mode 100644 index 0000000..49be639 --- /dev/null +++ b/blade-third-party-api/blade-track-api/src/main/java/org/springblade/thirdparty/track/service/impl/TrackServiceImpl.java @@ -0,0 +1,84 @@ +package org.springblade.thirdparty.track.service.impl; + +import cn.hutool.json.JSONUtil; +import com.google.common.collect.Lists; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springblade.core.tool.utils.CollectionUtil; +import org.springblade.core.tool.utils.StringPool; +import org.springblade.thirdparty.track.config.TrackProperties; +import org.springblade.thirdparty.track.constant.TrackConstant; +import org.springblade.thirdparty.track.exception.TrackException; +import org.springblade.thirdparty.track.feign.ITrackClient; +import org.springblade.thirdparty.track.pojo.dto.TrackWaybillSendDTO; +import org.springblade.thirdparty.track.pojo.vo.TrackResultVO; +import org.springblade.thirdparty.track.service.ITrackService; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.Objects; +import java.util.Optional; + +/** + * @author bfhuange + * @since 2024/12/16 + */ +@Slf4j +@RequiredArgsConstructor +@Service +public class TrackServiceImpl implements ITrackService { + + private final TrackProperties trackProperties; + private final ITrackClient client; + + @Override + public void sendWaybills(List ids) { + if (!trackProperties.isEnabled()) { + log.warn("未启用轨迹系统"); + return; + } + log.info("推送运单到轨迹,参数:{}", JSONUtil.toJsonStr(ids)); + if (CollectionUtil.isEmpty(ids)) { + log.warn("推送运单到轨迹 参数为空"); + return; + } + List list = ids.stream() + .filter(Objects::nonNull) + .distinct() + .map(String::valueOf) + .toList(); + if (CollectionUtil.isEmpty(ids)) { + log.warn("推送运单到轨迹 参数为空"); + return; + } + TrackResultVO result = null; + try { + for (List subList : Lists.partition(list, 100)) { + result = client.sendWaybills(new TrackWaybillSendDTO(subList, trackProperties.getBpsType())); + if (result == null || !TrackConstant.SUCCESS_CODE.equals(result.getCode())) { + // 结果为空,或不是成功,抛出异常 + log.error("推送运单到轨迹异常 响应参数:{}", JSONUtil.toJsonStr(result)); + String message = Optional.ofNullable(result) + .map(TrackResultVO::getMsg) + .orElse(StringPool.EMPTY); + throw new TrackException("推送运单到轨迹异常 " + message); + } + } + } catch (TrackException e) { + throw e; + } catch (Exception e) { + log.error("推送运单到轨迹 异常", e); + throw new TrackException("推送运单到轨迹异常"); + } + } + + @Override + public String getTrackUrl(String id) { + return trackProperties.getBaseUrl() + trackProperties.getTrackUrlFormat().formatted(id); + } + + @Override + public String getTrackUrlPrefix() { + return trackProperties.getBaseUrl() + trackProperties.getTrackUrlFormat().replace("%s", ""); + } +} diff --git a/blade-third-party-api/blade-track-api/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/blade-third-party-api/blade-track-api/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 0000000..13ef128 --- /dev/null +++ b/blade-third-party-api/blade-track-api/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1 @@ +org.springblade.thirdparty.track.config.ThirdPartyTrackAutoConfiguration \ No newline at end of file diff --git a/blade-third-party-api/blade-wps-api/pom.xml b/blade-third-party-api/blade-wps-api/pom.xml new file mode 100644 index 0000000..c66fb0b --- /dev/null +++ b/blade-third-party-api/blade-wps-api/pom.xml @@ -0,0 +1,22 @@ + + + 4.0.0 + + org.springblade + blade-third-party-api + ${revision} + + + blade-wps-api + ${project.artifactId} + jar + + + + org.springblade + blade-core-tool + + + \ No newline at end of file diff --git a/blade-third-party-api/blade-wps-api/src/main/java/org/springblade/thirdparty/wps/config/ThirdPartyWpsAutoConfiguration.java b/blade-third-party-api/blade-wps-api/src/main/java/org/springblade/thirdparty/wps/config/ThirdPartyWpsAutoConfiguration.java new file mode 100644 index 0000000..364edf3 --- /dev/null +++ b/blade-third-party-api/blade-wps-api/src/main/java/org/springblade/thirdparty/wps/config/ThirdPartyWpsAutoConfiguration.java @@ -0,0 +1,17 @@ +package org.springblade.thirdparty.wps.config; + +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; + +/** + * 第三方api wps配置类 + * @author bfhuange + * @date 2024/10/31 + */ +@ComponentScan("org.springblade.thirdparty.wps") +@EnableConfigurationProperties(WpsProperties.class) +@Configuration +public class ThirdPartyWpsAutoConfiguration { + +} diff --git a/blade-third-party-api/blade-wps-api/src/main/java/org/springblade/thirdparty/wps/config/WpsFeignClientConfig.java b/blade-third-party-api/blade-wps-api/src/main/java/org/springblade/thirdparty/wps/config/WpsFeignClientConfig.java new file mode 100644 index 0000000..99fa4a0 --- /dev/null +++ b/blade-third-party-api/blade-wps-api/src/main/java/org/springblade/thirdparty/wps/config/WpsFeignClientConfig.java @@ -0,0 +1,15 @@ +package org.springblade.thirdparty.wps.config; + +import org.springblade.thirdparty.wps.interceptor.WpsRequestInterceptor; +import org.springframework.context.annotation.Bean; + +/** + * @author bfhuange + * @since 2024/12/18 + */ +public class WpsFeignClientConfig { + @Bean + public WpsRequestInterceptor requestInterceptor(WpsProperties wpsProperties) { + return new WpsRequestInterceptor(wpsProperties); + } +} diff --git a/blade-third-party-api/blade-wps-api/src/main/java/org/springblade/thirdparty/wps/config/WpsProperties.java b/blade-third-party-api/blade-wps-api/src/main/java/org/springblade/thirdparty/wps/config/WpsProperties.java new file mode 100644 index 0000000..fd2db56 --- /dev/null +++ b/blade-third-party-api/blade-wps-api/src/main/java/org/springblade/thirdparty/wps/config/WpsProperties.java @@ -0,0 +1,26 @@ +package org.springblade.thirdparty.wps.config; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + * wps 配置类 + * @author bfhuange + * @date 2024/10/31 + */ +@Data +@ConfigurationProperties(prefix = "third-party.wps") +public class WpsProperties { + /** + * wps基础路径 + */ + private String baseUrl; + /** + * ak + */ + private String accessKey; + /** + * sk + */ + private String secretKey; +} diff --git a/blade-third-party-api/blade-wps-api/src/main/java/org/springblade/thirdparty/wps/constant/WpsConstant.java b/blade-third-party-api/blade-wps-api/src/main/java/org/springblade/thirdparty/wps/constant/WpsConstant.java new file mode 100644 index 0000000..78d58bb --- /dev/null +++ b/blade-third-party-api/blade-wps-api/src/main/java/org/springblade/thirdparty/wps/constant/WpsConstant.java @@ -0,0 +1,33 @@ +package org.springblade.thirdparty.wps.constant; + +/** + * wps 常量类 + * @author bfhuange + * @date 2024/10/30 + */ +public class WpsConstant { + /** + * 签名方法 + */ + public static final String SIGN_METHOD = "WPS-4"; + /** + * 签名请求头 + */ + public static final String AUTHORIZATION_HEADER = "Wps-Docs-Authorization"; + /** + * 日期请求头 + */ + public static final String DATE_HEADER = "Wps-Docs-Date"; + /** + * 成功状态码 + */ + public static final Integer SUCCESS_CODE = 200; + /** + * 透传token + */ + public static final int TOKEN_TYPE_YES = 1; + /** + * 简单模式参数 + */ + public static final String MODE_PARAM = "&simple"; +} diff --git a/blade-third-party-api/blade-wps-api/src/main/java/org/springblade/thirdparty/wps/exception/WpsException.java b/blade-third-party-api/blade-wps-api/src/main/java/org/springblade/thirdparty/wps/exception/WpsException.java new file mode 100644 index 0000000..0a7d581 --- /dev/null +++ b/blade-third-party-api/blade-wps-api/src/main/java/org/springblade/thirdparty/wps/exception/WpsException.java @@ -0,0 +1,17 @@ +package org.springblade.thirdparty.wps.exception; + +/** + * wps 异常 + * @author bfhuange + * @date 2024/10/30 + */ +public class WpsException extends RuntimeException { + + public WpsException(String message) { + super(message); + } + + public WpsException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/blade-third-party-api/blade-wps-api/src/main/java/org/springblade/thirdparty/wps/exception/WpsExceptionTranslator.java b/blade-third-party-api/blade-wps-api/src/main/java/org/springblade/thirdparty/wps/exception/WpsExceptionTranslator.java new file mode 100644 index 0000000..cb9ecd6 --- /dev/null +++ b/blade-third-party-api/blade-wps-api/src/main/java/org/springblade/thirdparty/wps/exception/WpsExceptionTranslator.java @@ -0,0 +1,28 @@ +package org.springblade.thirdparty.wps.exception; + +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springblade.core.tool.api.R; +import org.springblade.core.tool.api.ResultCode; +import org.springblade.core.tool.utils.Func; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +/** + * @author bfhuange + * @since 2025/3/29 + */ +@AllArgsConstructor +@Slf4j +@RestControllerAdvice +public class WpsExceptionTranslator { + + @ExceptionHandler(WpsException.class) + @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) + public R handleError(WpsException e) { + log.error("wps异常", e); + return R.fail(ResultCode.INTERNAL_SERVER_ERROR, (Func.isEmpty(e.getMessage()) ? ResultCode.INTERNAL_SERVER_ERROR.getMessage() : e.getMessage())); + } +} diff --git a/blade-third-party-api/blade-wps-api/src/main/java/org/springblade/thirdparty/wps/feign/IWpsClient.java b/blade-third-party-api/blade-wps-api/src/main/java/org/springblade/thirdparty/wps/feign/IWpsClient.java new file mode 100644 index 0000000..a0742c0 --- /dev/null +++ b/blade-third-party-api/blade-wps-api/src/main/java/org/springblade/thirdparty/wps/feign/IWpsClient.java @@ -0,0 +1,42 @@ +package org.springblade.thirdparty.wps.feign; + +import org.springblade.thirdparty.wps.config.WpsFeignClientConfig; +import org.springblade.thirdparty.wps.pojo.dto.WpsFilePreviewDTO; +import org.springblade.thirdparty.wps.pojo.vo.WpsFilePreviewVO; +import org.springblade.thirdparty.wps.pojo.vo.WpsResultVO; +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.cloud.openfeign.SpringQueryMap; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestParam; + +/** + * wps接口 + * @author bfhuange + * @date 2024/10/30 + */ +@FeignClient(name = "WPS", url = "${thirdParty.wps.baseUrl}", configuration = WpsFeignClientConfig.class) +public interface IWpsClient { + + /** + * 获取文件预览链接 + * @param fileId + * @return + */ + @GetMapping(value = "/open/api/preview/v1/files/{fileId}/link", headers = "Content-Type=application/json") + WpsResultVO getFilePreviewUrl(@PathVariable("fileId") String fileId, + @RequestParam("preview_mode") String previewMode, + @RequestParam("_w_tokentype") int tokenType, + @SpringQueryMap WpsFilePreviewDTO param); + + /** + * 获取文件编辑链接 + * @param fileId + * @return + */ + @GetMapping(value = "/open/api/edit/v1/files/{fileId}/link", headers = "Content-Type=application/json") + WpsResultVO getFileEditUrl(@PathVariable("fileId") String fileId, + @RequestParam("preview_mode") String previewMode, + @RequestParam("_w_tokentype") int tokenType, + @SpringQueryMap WpsFilePreviewDTO param); +} diff --git a/blade-third-party-api/blade-wps-api/src/main/java/org/springblade/thirdparty/wps/interceptor/WpsRequestInterceptor.java b/blade-third-party-api/blade-wps-api/src/main/java/org/springblade/thirdparty/wps/interceptor/WpsRequestInterceptor.java new file mode 100644 index 0000000..bbcec37 --- /dev/null +++ b/blade-third-party-api/blade-wps-api/src/main/java/org/springblade/thirdparty/wps/interceptor/WpsRequestInterceptor.java @@ -0,0 +1,116 @@ +package org.springblade.thirdparty.wps.interceptor; + +import feign.RequestInterceptor; +import feign.RequestTemplate; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springblade.core.tool.utils.CollectionUtil; +import org.springblade.core.tool.utils.DigestUtil; +import org.springblade.thirdparty.wps.config.WpsProperties; +import org.springblade.thirdparty.wps.constant.WpsConstant; +import org.springblade.thirdparty.wps.exception.WpsException; +import org.springframework.util.StringUtils; + +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Collection; +import java.util.Date; +import java.util.Locale; +import java.util.TimeZone; + +/** + * wps请求拦截器 + * @author bfhuange + * @date 2024/10/30 + */ +@Slf4j +@AllArgsConstructor +public class WpsRequestInterceptor implements RequestInterceptor { + + private final WpsProperties wpsProperties; + + @Override + public void apply(RequestTemplate template) { + //获取uri路径 + String path = template.path(); + if(StringUtils.hasText(template.queryLine())) { + path = path + template.queryLine(); + } + + //获取Content-type,“application/json" + Collection contentTypes = template.headers().get("Content-Type"); + String contentType = CollectionUtil.isEmpty(contentTypes) ? "" : contentTypes.iterator().next(); + + //日期格式化 + DateFormat dateFormat = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss 'GMT'", Locale.US); + dateFormat.setTimeZone(TimeZone.getTimeZone("GMT")); + String date = dateFormat.format(new Date()); + + //open不参与签名,做替换处理 + if (path.startsWith("/open")) { + path = path.replace("/open", ""); + } + + String sha256body; + + //body为空则为空,否则返回sha256(body) + byte[] body = template.body(); + if (body != null && body.length > 0) { + sha256body = this.getSHA256StrJava(body); + } else { + sha256body = ""; + } + + //hmac-sha256(secret_key, Ver + HttpMethod + URI + Content-Type + Wps-Date + sha256(HttpBody)) + String signature = null; + try { + signature = DigestUtil.hmacSha256Hex(WpsConstant.SIGN_METHOD + template.method().toUpperCase(Locale.ROOT) + path + contentType + date + sha256body, wpsProperties.getSecretKey()); + } catch (Exception e) { + log.error("签名异常", e); + throw new WpsException("请求签名异常"); + } + + template.header(WpsConstant.DATE_HEADER, date); + template.header(WpsConstant.AUTHORIZATION_HEADER, String.format(WpsConstant.SIGN_METHOD + " %s:%s", wpsProperties.getAccessKey(), signature)); + } + + /** + * 利用java原生的摘要实现SHA256加密 + * @param str 加密后的报文 + * @return + */ + public String getSHA256StrJava(byte[] str){ + MessageDigest messageDigest; + String encodeStr = ""; + try { + messageDigest = MessageDigest.getInstance("SHA-256"); + messageDigest.update(str); + encodeStr = byte2Hex(messageDigest.digest()); + } catch (NoSuchAlgorithmException e) { + log.error("加密异常", e); + throw new WpsException("加密异常"); + } + return encodeStr; + } + + /** + * 将byte转为16进制 + * @param bytes + * @return + */ + private static String byte2Hex(byte[] bytes){ + StringBuilder builder = new StringBuilder(); + String temp = null; + for (byte aByte : bytes) { + temp = Integer.toHexString(aByte & 0xFF); + if (temp.length() == 1) { + //1得到一位的进行补0操作 + builder.append("0"); + } + builder.append(temp); + } + return builder.toString(); + } +} diff --git a/blade-third-party-api/blade-wps-api/src/main/java/org/springblade/thirdparty/wps/pojo/dto/WpsFilePreviewDTO.java b/blade-third-party-api/blade-wps-api/src/main/java/org/springblade/thirdparty/wps/pojo/dto/WpsFilePreviewDTO.java new file mode 100644 index 0000000..e958894 --- /dev/null +++ b/blade-third-party-api/blade-wps-api/src/main/java/org/springblade/thirdparty/wps/pojo/dto/WpsFilePreviewDTO.java @@ -0,0 +1,58 @@ +package org.springblade.thirdparty.wps.pojo.dto; + +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; + +/** + * wps文件预览参数 + * @author bfhuange + * @date 2024/10/30 + */ +@Data +public class WpsFilePreviewDTO implements Serializable { + @Serial + private static final long serialVersionUID = 1L; + /** + * 文件格式,可选值: + * w:文字文件 + * s:表格文件 + * p:演示文件 + * f:PDF文件 + * x:文件类型(图片、压缩包、md、eml、代码、其他),传空时默认为该类型 ,必填 + */ + private String type; + /** + * 适用版本 >= v6.0.2207.20220729 + * 预览模式,可选值: + * high_definition:高清预览 + * ordinary:普通预览 + * cache:缓存预览 + * official:公文极速预览(>=v6.1.2302.20230220)不传默认official,非必填 + */ + //@JsonProperty("preview_mode") + //private String previewMode; + /** + * 高清预览支持控制修订痕迹、评论是否显示等参数。高清预览参数wpsPreview=1111111从左到右依次代表: + * 第0位:格式修订:0不显示;1:显示 + * 第1位:插入和删除:0不显示;1:显示 + * 第2位:评论 0不显示;1:显示 + * 第3位:以嵌入模式显示修订 + * 第4位:以气泡模式显示修订 + * 第5位:0不显示标记;1:显示标记 + * 第6位:0最终状态;1:原始状态 + * 注:(第3、4位组合:01及11为批注框方式;10是嵌入方式 00是批注框显示修订者),非必填 + */ + private String wpsPreview; + /** + * 适用版本>=v7.0.2309a.20230907 + * 是否透传token,传1时透传 + */ + //@JsonProperty("_w_tokentype") + //private int tokenType = 1; + /* + 合作方自定义参数,要求以 _w_third_ 作为前缀,回调时会一并返回给开发者。 + _w_third_appid 、 _w_third_file_id 字段已被使用,对接方请合理避开。 + */ +} diff --git a/blade-third-party-api/blade-wps-api/src/main/java/org/springblade/thirdparty/wps/pojo/enums/WpsFile.java b/blade-third-party-api/blade-wps-api/src/main/java/org/springblade/thirdparty/wps/pojo/enums/WpsFile.java new file mode 100644 index 0000000..1026a39 --- /dev/null +++ b/blade-third-party-api/blade-wps-api/src/main/java/org/springblade/thirdparty/wps/pojo/enums/WpsFile.java @@ -0,0 +1,114 @@ +package org.springblade.thirdparty.wps.pojo.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.springframework.util.StringUtils; + +import java.util.Collections; +import java.util.List; +import java.util.Locale; +import java.util.stream.Stream; + +/** + * wps 文件枚举 + * @author bfhuange + * @date 2024/10/30 + */ +public class WpsFile { + /** + * 预览模式 + */ + @AllArgsConstructor + @Getter + public enum PreviewMode { + /** + * 高清预览 + */ + HIGH_DEFINITION("high_definition", "高清预览"), + /** + * 普通预览 + */ + ORDINARY("ordinary", "普通预览"), + /** + * 缓存预览 + */ + CACHE("cache", "缓存预览"), + /** + * 公文极速预览 + */ + OFFICIAL("official", "公文极速预览"), + ; + /** + * 编码 + */ + private final String code; + /** + * 名称 + */ + private final String name; + } + /** + * 类型枚举 + */ + @AllArgsConstructor + @Getter + public enum Type { + /** + * 文字文件 高清模式 + */ + WORD("w", "文字文件", PreviewMode.HIGH_DEFINITION.getCode(), List.of("doc,dot,wps,wpt,docx,dotx,docm,dotm,rtf,txt,mht,mhtml,htm,html,uot3".split(","))), + /** + * 表格文件 普通模式 + */ + EXCEL("s", "表格文件", PreviewMode.ORDINARY.getCode(), List.of("xls,xlt,et,xlsx,xltx,csv,xlsm,xltm,ett".split(","))), + /** + * 演示文件 普通模式 + */ + PPT("p", "演示文件", PreviewMode.ORDINARY.getCode(), List.of("ppt,pptx,pptm,ppsx,ppsm,pps,potx,potm,dpt,dps,pot".split(","))), + /** + * PDF文件 普通模式 + */ + PDF("f", "PDF文件", PreviewMode.ORDINARY.getCode(), List.of("pdf,ofd".split(","))), + /** + * 文件类型(图片、压缩包、md、eml、代码、其他)普通模式 + */ + OTHER("x", "文件类型(图片、压缩包、md、eml、代码、其他)", PreviewMode.ORDINARY.getCode(), Collections.emptyList()), + ; + + /** + * 编码 + */ + private final String code; + /** + * 名称 + */ + private final String name; + /** + * 预览模式 + */ + private final String previewMode; + /** + * 文件扩展名列表 + */ + private final List exts; + + /** + * 根据文件名获取类型 + * @param fileName + * @return + */ + public static Type getByFileName(String fileName) { + if (!StringUtils.hasText(fileName) || !fileName.contains(".")) { + // 文件名为空,或文件名不包含. 返回null + return OTHER; + } + // 获取文件扩展名 + String ext = fileName.substring(fileName.lastIndexOf(".") + 1).toLowerCase(Locale.ROOT); + return Stream.of(values()) + // 扩展名包含在内的 + .filter(type -> type.exts.contains(ext)) + .findFirst() + .orElse(OTHER); + } + } +} diff --git a/blade-third-party-api/blade-wps-api/src/main/java/org/springblade/thirdparty/wps/pojo/vo/WpsFilePreviewVO.java b/blade-third-party-api/blade-wps-api/src/main/java/org/springblade/thirdparty/wps/pojo/vo/WpsFilePreviewVO.java new file mode 100644 index 0000000..2c880a9 --- /dev/null +++ b/blade-third-party-api/blade-wps-api/src/main/java/org/springblade/thirdparty/wps/pojo/vo/WpsFilePreviewVO.java @@ -0,0 +1,21 @@ +package org.springblade.thirdparty.wps.pojo.vo; + +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; + +/** + * wps 文件预览链接返回结果 + * @author bfhuange + * @date 2024/10/30 + */ +@Data +public class WpsFilePreviewVO implements Serializable { + @Serial + private static final long serialVersionUID = 1L; + /** + * 文件在线预览链接,示例:配置域名+/weboffice/office/{file_type}/{file_id}?_w_appid=11&用户自定义参数 +&wpsPreview=1111111&simple&hidecmb + */ + private String link; +} diff --git a/blade-third-party-api/blade-wps-api/src/main/java/org/springblade/thirdparty/wps/pojo/vo/WpsResultVO.java b/blade-third-party-api/blade-wps-api/src/main/java/org/springblade/thirdparty/wps/pojo/vo/WpsResultVO.java new file mode 100644 index 0000000..3bf7d97 --- /dev/null +++ b/blade-third-party-api/blade-wps-api/src/main/java/org/springblade/thirdparty/wps/pojo/vo/WpsResultVO.java @@ -0,0 +1,45 @@ +package org.springblade.thirdparty.wps.pojo.vo; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; + +/** + * wps响应结果 + * @author bfhuange + * @date 2024/10/30 + */ +@Data +public class WpsResultVO implements Serializable { + @Serial + private static final long serialVersionUID = 1L; + /** + * 请求标识id + */ + @JsonProperty("request_id") + private String requestId; + /** + * 错误码 + */ + private Integer code; + /** + * 状态信息 + */ + private String msg; + /** + * 请求时间 + */ + @JsonProperty("request_time") + private long requestTime; + /** + * 响应时间 + */ + @JsonProperty("response_time") + private long responseTime; + /** + * 参数 + */ + private T data; +} diff --git a/blade-third-party-api/blade-wps-api/src/main/java/org/springblade/thirdparty/wps/service/IWpsService.java b/blade-third-party-api/blade-wps-api/src/main/java/org/springblade/thirdparty/wps/service/IWpsService.java new file mode 100644 index 0000000..c8db6fb --- /dev/null +++ b/blade-third-party-api/blade-wps-api/src/main/java/org/springblade/thirdparty/wps/service/IWpsService.java @@ -0,0 +1,23 @@ +package org.springblade.thirdparty.wps.service; + +/** + * @author bfhuange + * @date 2024/10/30 + */ +public interface IWpsService { + /** + * 根据文件id获取文件预览链接 + * @param fileId + * @param fileName + * @return + */ + String getFilePreviewUrl(String fileId, String fileName); + + /** + * 根据文件id获取文件编辑链接 + * @param fileId + * @param fileName + * @return + */ + String getWpsFileEditUrl(String fileId, String fileName); +} diff --git a/blade-third-party-api/blade-wps-api/src/main/java/org/springblade/thirdparty/wps/service/impl/WpsServiceImpl.java b/blade-third-party-api/blade-wps-api/src/main/java/org/springblade/thirdparty/wps/service/impl/WpsServiceImpl.java new file mode 100644 index 0000000..49f4538 --- /dev/null +++ b/blade-third-party-api/blade-wps-api/src/main/java/org/springblade/thirdparty/wps/service/impl/WpsServiceImpl.java @@ -0,0 +1,86 @@ +package org.springblade.thirdparty.wps.service.impl; + +import cn.hutool.json.JSONUtil; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springblade.core.tool.utils.StringUtil; +import org.springblade.thirdparty.wps.constant.WpsConstant; +import org.springblade.thirdparty.wps.exception.WpsException; +import org.springblade.thirdparty.wps.feign.IWpsClient; +import org.springblade.thirdparty.wps.pojo.dto.WpsFilePreviewDTO; +import org.springblade.thirdparty.wps.pojo.enums.WpsFile; +import org.springblade.thirdparty.wps.pojo.vo.WpsFilePreviewVO; +import org.springblade.thirdparty.wps.pojo.vo.WpsResultVO; +import org.springblade.thirdparty.wps.service.IWpsService; +import org.springframework.stereotype.Service; + +/** + * @author bfhuange + * @date 2024/10/30 + */ +@Slf4j +@RequiredArgsConstructor +@Service +public class WpsServiceImpl implements IWpsService { + + private final IWpsClient client; + + @Override + public String getFilePreviewUrl(String fileId, String fileName) { + if (StringUtil.isBlank(fileId) || StringUtil.isBlank(fileName)) { + log.info("获取wps预览链接 参数 fileId:{} fileName:{}", fileId, fileName); + throw new WpsException("参数不能为空"); + } + WpsFilePreviewDTO previewDTO = new WpsFilePreviewDTO(); + // 根据文件名获取文件格式和预览模式 + WpsFile.Type type = WpsFile.Type.getByFileName(fileName); + previewDTO.setType(type.getCode()); + try { + // get请求,对象参数不支持别名,需要提取出来 + WpsResultVO resultVO = client.getFilePreviewUrl(fileId, type.getPreviewMode(), WpsConstant.TOKEN_TYPE_YES, previewDTO); + if (resultVO != null && resultVO.getData() != null && WpsConstant.SUCCESS_CODE.equals(resultVO.getCode())) { + // 成功,返回预览链接 + String link = resultVO.getData().getLink(); + if (WpsFile.Type.EXCEL.equals(type)) { + // excel,去掉simple参数,以便查看单元格的公式 + return link.replace(WpsConstant.MODE_PARAM, ""); + } + return link; + } + log.error("获取wps预览链接异常 {}", JSONUtil.toJsonStr(resultVO)); + throw new WpsException("获取wps预览链接异常"); + } catch (WpsException e) { + throw e; + } catch (Exception e) { + log.error("获取wps预览链接异常", e); + throw new WpsException("获取wps预览链接异常"); + } + } + + @Override + public String getWpsFileEditUrl(String fileId, String fileName) { + if (StringUtil.isBlank(fileId) || StringUtil.isBlank(fileName)) { + log.info("获取wps编辑链接 参数 fileId:{} fileName:{}", fileId, fileName); + throw new WpsException("参数不能为空"); + } + WpsFilePreviewDTO previewDTO = new WpsFilePreviewDTO(); + // 根据文件名获取文件格式和预览模式 + WpsFile.Type type = WpsFile.Type.getByFileName(fileName); + previewDTO.setType(type.getCode()); + try { + // get请求,对象参数不支持别名,需要提取出来 + WpsResultVO resultVO = client.getFileEditUrl(fileId, type.getPreviewMode(), WpsConstant.TOKEN_TYPE_YES, previewDTO); + if (resultVO != null && resultVO.getData() != null && WpsConstant.SUCCESS_CODE.equals(resultVO.getCode())) { + // 成功,返回预览链接 + return resultVO.getData().getLink(); + } + log.error("获取wps编辑链接异常 {}", JSONUtil.toJsonStr(resultVO)); + throw new WpsException("获取wps编辑链接异常:"+JSONUtil.toJsonStr(resultVO)); + } catch (WpsException e) { + throw e; + } catch (Exception e) { + log.error("获取wps编辑链接异常", e); + throw new WpsException("获取wps编辑链接异常"); + } + } +} diff --git a/blade-third-party-api/blade-wps-api/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/blade-third-party-api/blade-wps-api/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 0000000..ef53fc2 --- /dev/null +++ b/blade-third-party-api/blade-wps-api/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1 @@ +org.springblade.thirdparty.wps.config.ThirdPartyWpsAutoConfiguration \ No newline at end of file diff --git a/blade-third-party-api/pom.xml b/blade-third-party-api/pom.xml new file mode 100644 index 0000000..31f02f5 --- /dev/null +++ b/blade-third-party-api/pom.xml @@ -0,0 +1,51 @@ + + + 4.0.0 + + org.springblade + BladeX + ${revision} + + + blade-third-party-api + ${project.artifactId} + + blade-oa-api + blade-mk-api + blade-wps-api + blade-ocr-api + blade-track-api + + pom + BladeX 第三方API集合 + + + + org.springframework.cloud + spring-cloud-starter-openfeign + + + com.fasterxml.jackson.core + jackson-annotations + + + cn.hutool + hutool-all + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + true + ${project.name} + + + + + diff --git a/doc/nacos/blade-dev.yaml b/doc/nacos/blade-dev.yaml index d267f05..ed1dfe1 100644 --- a/doc/nacos/blade-dev.yaml +++ b/doc/nacos/blade-dev.yaml @@ -73,3 +73,29 @@ blade: #url: jdbc:kingbase8://localhost:4321/bladex #username: kingbase #password: 123456 + +#第三方接口配置 +thirdParty: + ocr: + # OCR磅单、车牌识别接口地址 + baseUrl: http://127.0.0.1:8080 + # OCR证件识别接口地址 + baseOtherUrl: http://127.0.0.1:8080 + wps: + # WPS开放接口地址 + baseUrl: http://127.0.0.1:8080 + oa: + # OA开放接口地址 + baseUrl: http://127.0.0.1:8080 + track: + # 轨迹开放接口地址 + baseUrl: http://127.0.0.1:8080 + mk: + # MK开放接口地址 + baseUrl: http://127.0.0.1:8080 + + +powerjob: + worker: + enabled: false + server-address: 172.16.203.228:7700 diff --git a/doc/nacos/blade-prod.yaml b/doc/nacos/blade-prod.yaml index 09d53e8..5fb7e2f 100644 --- a/doc/nacos/blade-prod.yaml +++ b/doc/nacos/blade-prod.yaml @@ -45,3 +45,27 @@ blade: url: jdbc:mysql://172.16.203.228:3306/transport?useSSL=false&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&transformedBitIsBoolean=true&tinyInt1isBit=false&allowMultiQueries=true&serverTimezone=GMT%2B8&allowPublicKeyRetrieval=true username: transport password: ab3acJYSC8A4ySpk + +#第三方接口配置 +thirdParty: + ocr: + # OCR磅单、车牌识别接口地址 + baseUrl: http://127.0.0.1:8080 + # OCR证件识别接口地址 + baseOtherUrl: http://127.0.0.1:8080 + wps: + # WPS开放接口地址 + baseUrl: http://127.0.0.1:8080 + oa: + # OA开放接口地址 + baseUrl: http://127.0.0.1:8080 + track: + # 轨迹开放接口地址 + baseUrl: http://127.0.0.1:8080 + mk: + # MK开放接口地址 + baseUrl: http://127.0.0.1:8080 + +powerjob: + worker: + server-address: 172.16.203.228:7700 diff --git a/doc/nacos/third-party-api.yaml b/doc/nacos/third-party-api.yaml new file mode 100644 index 0000000..8b6b40d --- /dev/null +++ b/doc/nacos/third-party-api.yaml @@ -0,0 +1,19 @@ +#第三方接口配置 +thirdParty: + ocr: + # OCR磅单、车牌识别接口地址 + baseUrl: ${OCR_BASE_URL:http://127.0.0.1:8080} + # OCR证件识别接口地址 + baseOtherUrl: ${OCR_BASE_OTHER_URL:http://127.0.0.1:8080} + wps: + # WPS开放接口地址 + baseUrl: ${WPS_BASE_URL:http://127.0.0.1:8080} + oa: + # OA开放接口地址 + baseUrl: ${OA_BASE_URL:http://127.0.0.1:8080} + track: + # 轨迹开放接口地址 + baseUrl: ${TRACK_BASE_URL:http://127.0.0.1:8080} + mk: + # MK开放接口地址 + baseUrl: ${MK_BASE_URL:http://127.0.0.1:8080} diff --git a/doc/sql/bladex/bladex.dameng.all.create.sql b/doc/sql/bladex/bladex.dameng.all.create.sql index af207e7..2eefffb 100644 --- a/doc/sql/bladex/bladex.dameng.all.create.sql +++ b/doc/sql/bladex/bladex.dameng.all.create.sql @@ -6536,9 +6536,9 @@ CREATE INDEX "IDX_FEE_ITEM_STATUS" ON "BLADEX"."BLADE_FEE_ITEM" ("STATUS"); -- ---------------------------- -- Records of BLADE_MENU for fee item -- ---------------------------- -INSERT INTO "BLADEX"."BLADE_MENU" ("ID","PARENT_ID","CODE","NAME","ALIAS","PATH","SOURCE","SORT","CATEGORY","ACTION","IS_OPEN","COMPONENT","REMARK","IS_DELETED") VALUES (1980000000000000070, 1164733399668962201, 'fee_item', '费用项', 'menu', '/base/fee-item', 'iconfont icon-shoucang', 7, 1, 0, 1, '', NULL, 0); -INSERT INTO "BLADEX"."BLADE_MENU" ("ID","PARENT_ID","CODE","NAME","ALIAS","PATH","SOURCE","SORT","CATEGORY","ACTION","IS_OPEN","COMPONENT","REMARK","IS_DELETED") VALUES (1980000000000000071, 1980000000000000070, 'fee_item_add', '新增', 'add', '/base/fee-item/add', 'plus', 1, 2, 1, 1, '', NULL, 0); -INSERT INTO "BLADEX"."BLADE_MENU" ("ID","PARENT_ID","CODE","NAME","ALIAS","PATH","SOURCE","SORT","CATEGORY","ACTION","IS_OPEN","COMPONENT","REMARK","IS_DELETED") VALUES (1980000000000000072, 1980000000000000070, 'fee_item_edit', '修改', 'edit', '/base/fee-item/edit', 'form', 2, 2, 2, 1, '', NULL, 0); -INSERT INTO "BLADEX"."BLADE_MENU" ("ID","PARENT_ID","CODE","NAME","ALIAS","PATH","SOURCE","SORT","CATEGORY","ACTION","IS_OPEN","COMPONENT","REMARK","IS_DELETED") VALUES (1980000000000000073, 1980000000000000070, 'fee_item_delete', '删除', 'delete', '/api/blade-system/fee-item/remove', 'delete', 3, 2, 3, 1, '', NULL, 0); -INSERT INTO "BLADEX"."BLADE_MENU" ("ID","PARENT_ID","CODE","NAME","ALIAS","PATH","SOURCE","SORT","CATEGORY","ACTION","IS_OPEN","COMPONENT","REMARK","IS_DELETED") VALUES (1980000000000000074, 1980000000000000070, 'fee_item_view', '查看', 'view', '/base/fee-item/view', 'file-text', 4, 2, 2, 1, '', NULL, 0); -INSERT INTO "BLADEX"."BLADE_MENU" ("ID","PARENT_ID","CODE","NAME","ALIAS","PATH","SOURCE","SORT","CATEGORY","ACTION","IS_OPEN","COMPONENT","REMARK","IS_DELETED") VALUES (1980000000000000075, 1980000000000000070, 'fee_item_status', '启停', 'status', '/api/blade-system/fee-item/status', 'key', 5, 2, 2, 1, '', NULL, 0); +INSERT INTO "BLADEX"."BLADE_MENU" ("ID","PARENT_ID","CODE","NAME","ALIAS","PATH","SOURCE","SORT","CATEGORY","ACTION","IS_OPEN","COMPONENT","REMARK","IS_DELETED") VALUES (2075449200000000001, 1164733399668962201, 'fee_item', '费用项', 'menu', '/base/fee-item', 'iconfont icon-shoucang', 7, 1, 0, 1, '', NULL, 0); +INSERT INTO "BLADEX"."BLADE_MENU" ("ID","PARENT_ID","CODE","NAME","ALIAS","PATH","SOURCE","SORT","CATEGORY","ACTION","IS_OPEN","COMPONENT","REMARK","IS_DELETED") VALUES (1980000000000000071, 2075449200000000001, 'fee_item_add', '新增', 'add', '/base/fee-item/add', 'plus', 1, 2, 1, 1, '', NULL, 0); +INSERT INTO "BLADEX"."BLADE_MENU" ("ID","PARENT_ID","CODE","NAME","ALIAS","PATH","SOURCE","SORT","CATEGORY","ACTION","IS_OPEN","COMPONENT","REMARK","IS_DELETED") VALUES (1980000000000000072, 2075449200000000001, 'fee_item_edit', '修改', 'edit', '/base/fee-item/edit', 'form', 2, 2, 2, 1, '', NULL, 0); +INSERT INTO "BLADEX"."BLADE_MENU" ("ID","PARENT_ID","CODE","NAME","ALIAS","PATH","SOURCE","SORT","CATEGORY","ACTION","IS_OPEN","COMPONENT","REMARK","IS_DELETED") VALUES (1980000000000000073, 2075449200000000001, 'fee_item_delete', '删除', 'delete', '/api/blade-system/fee-item/remove', 'delete', 3, 2, 3, 1, '', NULL, 0); +INSERT INTO "BLADEX"."BLADE_MENU" ("ID","PARENT_ID","CODE","NAME","ALIAS","PATH","SOURCE","SORT","CATEGORY","ACTION","IS_OPEN","COMPONENT","REMARK","IS_DELETED") VALUES (1980000000000000074, 2075449200000000001, 'fee_item_view', '查看', 'view', '/base/fee-item/view', 'file-text', 4, 2, 2, 1, '', NULL, 0); +INSERT INTO "BLADEX"."BLADE_MENU" ("ID","PARENT_ID","CODE","NAME","ALIAS","PATH","SOURCE","SORT","CATEGORY","ACTION","IS_OPEN","COMPONENT","REMARK","IS_DELETED") VALUES (1980000000000000075, 2075449200000000001, 'fee_item_status', '启停', 'status', '/api/blade-system/fee-item/status', 'key', 5, 2, 2, 1, '', NULL, 0); diff --git a/doc/sql/bladex/bladex.kingbase.all.create.sql b/doc/sql/bladex/bladex.kingbase.all.create.sql index a49ca55..1f4104f 100644 --- a/doc/sql/bladex/bladex.kingbase.all.create.sql +++ b/doc/sql/bladex/bladex.kingbase.all.create.sql @@ -2562,6 +2562,7 @@ CREATE INDEX "idx_customer_type_status" ON "blade_customer_type" ("status"); DROP TABLE IF EXISTS "blade_fee_item"; CREATE TABLE "blade_fee_item" ( "id" int8 NOT NULL, + "fee_category" varchar(50) NOT NULL, "name" varchar(50) NOT NULL, "english_name" varchar(100), "create_user" int8, @@ -2574,15 +2575,19 @@ CREATE TABLE "blade_fee_item" ( CONSTRAINT "blade_fee_item_pkey" PRIMARY KEY ("id"), CONSTRAINT "uk_fee_item_name" UNIQUE ("name") ); +CREATE INDEX "idx_fee_item_category" ON "blade_fee_item" ("fee_category"); CREATE INDEX "idx_fee_item_status" ON "blade_fee_item" ("status"); -- ---------------------------- -- Records of blade_menu for fee item -- ---------------------------- INSERT INTO "blade_menu" ("id", "parent_id", "code", "name", "alias", "path", "source", "sort", "category", "action", "is_open", "component", "remark", "is_deleted") VALUES -(1980000000000000070, 1164733399668962201, 'fee_item', '费用项', 'menu', '/base/fee-item', 'iconfont icon-shoucang', 7, 1, 0, 1, '', NULL, 0), -(1980000000000000071, 1980000000000000070, 'fee_item_add', '新增', 'add', '/base/fee-item/add', 'plus', 1, 2, 1, 1, '', NULL, 0), -(1980000000000000072, 1980000000000000070, 'fee_item_edit', '修改', 'edit', '/base/fee-item/edit', 'form', 2, 2, 2, 1, '', NULL, 0), -(1980000000000000073, 1980000000000000070, 'fee_item_delete', '删除', 'delete', '/api/blade-system/fee-item/remove', 'delete', 3, 2, 3, 1, '', NULL, 0), -(1980000000000000074, 1980000000000000070, 'fee_item_view', '查看', 'view', '/base/fee-item/view', 'file-text', 4, 2, 2, 1, '', NULL, 0), -(1980000000000000075, 1980000000000000070, 'fee_item_status', '启停', 'status', '/api/blade-system/fee-item/status', 'key', 5, 2, 2, 1, '', NULL, 0); +(2075449200000000001, 1164733399668962201, 'fee_item', '费用项', 'menu', '/base/fee-item', 'iconfont icon-shoucang', 7, 1, 0, 1, '', NULL, 0), +(1980000000000000071, 2075449200000000001, 'fee_item_add', '新增', 'add', '/base/fee-item/add', 'plus', 1, 2, 1, 1, '', NULL, 0), +(1980000000000000072, 2075449200000000001, 'fee_item_edit', '修改', 'edit', '/base/fee-item/edit', 'form', 2, 2, 2, 1, '', NULL, 0), +(1980000000000000073, 2075449200000000001, 'fee_item_delete', '删除', 'delete', '/api/blade-system/fee-item/remove', 'delete', 3, 2, 3, 1, '', NULL, 0), +(1980000000000000074, 2075449200000000001, 'fee_item_view', '查看', 'view', '/base/fee-item/view', 'file-text', 4, 2, 2, 1, '', NULL, 0), +(1980000000000000075, 2075449200000000001, 'fee_item_status', '启停', 'status', '/api/blade-system/fee-item/status', 'key', 5, 2, 2, 1, '', NULL, 0), +(1980000000000000076, 2075449200000000001, 'fee_item_import', '批量导入', 'import', '/api/blade-system/fee-item/import-fee-item', 'upload', 6, 2, 3, 1, '', NULL, 0), +(1980000000000000077, 2075449200000000001, 'fee_item_template', '模板下载', 'template', '/api/blade-system/fee-item/export-template', 'download', 7, 2, 2, 1, '', NULL, 0), +(1980000000000000078, 2075449200000000001, 'fee_item_export', '批量导出', 'export', '/api/blade-system/fee-item/export-fee-item', 'download', 8, 2, 2, 1, '', NULL, 0); diff --git a/doc/sql/bladex/bladex.mysql.all.create.sql b/doc/sql/bladex/bladex.mysql.all.create.sql index 2884607..9b21660 100644 --- a/doc/sql/bladex/bladex.mysql.all.create.sql +++ b/doc/sql/bladex/bladex.mysql.all.create.sql @@ -1697,8 +1697,9 @@ INSERT INTO `blade_menu` (`id`, `parent_id`, `code`, `name`, `alias`, `path`, `s DROP TABLE IF EXISTS `blade_fee_item`; CREATE TABLE `blade_fee_item` ( `id` bigint NOT NULL COMMENT '主键', - `name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '中文名称', - `english_name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '英文名称', + `fee_category` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '费用类型', + `name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '费用项', + `english_name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '费用项代码', `create_user` bigint NULL DEFAULT NULL COMMENT '创建人', `create_dept` bigint NULL DEFAULT NULL COMMENT '创建部门', `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间', @@ -1708,6 +1709,7 @@ CREATE TABLE `blade_fee_item` ( `is_deleted` int NULL DEFAULT 0 COMMENT '是否已删除', PRIMARY KEY (`id`) USING BTREE, UNIQUE INDEX `uk_fee_item_name`(`name`) USING BTREE, + INDEX `idx_fee_item_category`(`fee_category`) USING BTREE, INDEX `idx_fee_item_status`(`status`) USING BTREE ) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '费用项'; @@ -1715,11 +1717,14 @@ CREATE TABLE `blade_fee_item` ( -- Records of blade_menu for fee item -- ---------------------------- INSERT INTO `blade_menu` (`id`, `parent_id`, `code`, `name`, `alias`, `path`, `source`, `sort`, `category`, `action`, `is_open`, `component`, `remark`, `is_deleted`) VALUES -(1980000000000000070, 1164733399668962201, 'fee_item', '费用项', 'menu', '/base/fee-item', 'iconfont icon-shoucang', 7, 1, 0, 1, '', NULL, 0), -(1980000000000000071, 1980000000000000070, 'fee_item_add', '新增', 'add', '/base/fee-item/add', 'plus', 1, 2, 1, 1, '', NULL, 0), -(1980000000000000072, 1980000000000000070, 'fee_item_edit', '修改', 'edit', '/base/fee-item/edit', 'form', 2, 2, 2, 1, '', NULL, 0), -(1980000000000000073, 1980000000000000070, 'fee_item_delete', '删除', 'delete', '/api/blade-system/fee-item/remove', 'delete', 3, 2, 3, 1, '', NULL, 0), -(1980000000000000074, 1980000000000000070, 'fee_item_view', '查看', 'view', '/base/fee-item/view', 'file-text', 4, 2, 2, 1, '', NULL, 0), -(1980000000000000075, 1980000000000000070, 'fee_item_status', '启停', 'status', '/api/blade-system/fee-item/status', 'key', 5, 2, 2, 1, '', NULL, 0); +(2075449200000000001, 1164733399668962201, 'fee_item', '费用项', 'menu', '/base/fee-item', 'iconfont icon-shoucang', 7, 1, 0, 1, '', NULL, 0), +(1980000000000000071, 2075449200000000001, 'fee_item_add', '新增', 'add', '/base/fee-item/add', 'plus', 1, 2, 1, 1, '', NULL, 0), +(1980000000000000072, 2075449200000000001, 'fee_item_edit', '修改', 'edit', '/base/fee-item/edit', 'form', 2, 2, 2, 1, '', NULL, 0), +(1980000000000000073, 2075449200000000001, 'fee_item_delete', '删除', 'delete', '/api/blade-system/fee-item/remove', 'delete', 3, 2, 3, 1, '', NULL, 0), +(1980000000000000074, 2075449200000000001, 'fee_item_view', '查看', 'view', '/base/fee-item/view', 'file-text', 4, 2, 2, 1, '', NULL, 0), +(1980000000000000075, 2075449200000000001, 'fee_item_status', '启停', 'status', '/api/blade-system/fee-item/status', 'key', 5, 2, 2, 1, '', NULL, 0), +(1980000000000000076, 2075449200000000001, 'fee_item_import', '批量导入', 'import', '/api/blade-system/fee-item/import-fee-item', 'upload', 6, 2, 3, 1, '', NULL, 0), +(1980000000000000077, 2075449200000000001, 'fee_item_template', '模板下载', 'template', '/api/blade-system/fee-item/export-template', 'download', 7, 2, 2, 1, '', NULL, 0), +(1980000000000000078, 2075449200000000001, 'fee_item_export', '批量导出', 'export', '/api/blade-system/fee-item/export-fee-item', 'download', 8, 2, 2, 1, '', NULL, 0); SET FOREIGN_KEY_CHECKS = 1; diff --git a/doc/sql/bladex/bladex.oracle.all.create.sql b/doc/sql/bladex/bladex.oracle.all.create.sql index 99af693..97b9efd 100644 --- a/doc/sql/bladex/bladex.oracle.all.create.sql +++ b/doc/sql/bladex/bladex.oracle.all.create.sql @@ -6770,9 +6770,9 @@ CREATE INDEX "IDX_FEE_ITEM_STATUS" ON "BLADE_FEE_ITEM" ("STATUS"); -- ---------------------------- -- Records of BLADE_MENU for fee item -- ---------------------------- -INSERT INTO "BLADE_MENU" ("ID","PARENT_ID","CODE","NAME","ALIAS","PATH","SOURCE","SORT","CATEGORY","ACTION","IS_OPEN","COMPONENT","REMARK","IS_DELETED") VALUES (1980000000000000070, 1164733399668962201, 'fee_item', '费用项', 'menu', '/base/fee-item', 'iconfont icon-shoucang', 7, 1, 0, 1, '', NULL, 0); -INSERT INTO "BLADE_MENU" ("ID","PARENT_ID","CODE","NAME","ALIAS","PATH","SOURCE","SORT","CATEGORY","ACTION","IS_OPEN","COMPONENT","REMARK","IS_DELETED") VALUES (1980000000000000071, 1980000000000000070, 'fee_item_add', '新增', 'add', '/base/fee-item/add', 'plus', 1, 2, 1, 1, '', NULL, 0); -INSERT INTO "BLADE_MENU" ("ID","PARENT_ID","CODE","NAME","ALIAS","PATH","SOURCE","SORT","CATEGORY","ACTION","IS_OPEN","COMPONENT","REMARK","IS_DELETED") VALUES (1980000000000000072, 1980000000000000070, 'fee_item_edit', '修改', 'edit', '/base/fee-item/edit', 'form', 2, 2, 2, 1, '', NULL, 0); -INSERT INTO "BLADE_MENU" ("ID","PARENT_ID","CODE","NAME","ALIAS","PATH","SOURCE","SORT","CATEGORY","ACTION","IS_OPEN","COMPONENT","REMARK","IS_DELETED") VALUES (1980000000000000073, 1980000000000000070, 'fee_item_delete', '删除', 'delete', '/api/blade-system/fee-item/remove', 'delete', 3, 2, 3, 1, '', NULL, 0); -INSERT INTO "BLADE_MENU" ("ID","PARENT_ID","CODE","NAME","ALIAS","PATH","SOURCE","SORT","CATEGORY","ACTION","IS_OPEN","COMPONENT","REMARK","IS_DELETED") VALUES (1980000000000000074, 1980000000000000070, 'fee_item_view', '查看', 'view', '/base/fee-item/view', 'file-text', 4, 2, 2, 1, '', NULL, 0); -INSERT INTO "BLADE_MENU" ("ID","PARENT_ID","CODE","NAME","ALIAS","PATH","SOURCE","SORT","CATEGORY","ACTION","IS_OPEN","COMPONENT","REMARK","IS_DELETED") VALUES (1980000000000000075, 1980000000000000070, 'fee_item_status', '启停', 'status', '/api/blade-system/fee-item/status', 'key', 5, 2, 2, 1, '', NULL, 0); +INSERT INTO "BLADE_MENU" ("ID","PARENT_ID","CODE","NAME","ALIAS","PATH","SOURCE","SORT","CATEGORY","ACTION","IS_OPEN","COMPONENT","REMARK","IS_DELETED") VALUES (2075449200000000001, 1164733399668962201, 'fee_item', '费用项', 'menu', '/base/fee-item', 'iconfont icon-shoucang', 7, 1, 0, 1, '', NULL, 0); +INSERT INTO "BLADE_MENU" ("ID","PARENT_ID","CODE","NAME","ALIAS","PATH","SOURCE","SORT","CATEGORY","ACTION","IS_OPEN","COMPONENT","REMARK","IS_DELETED") VALUES (1980000000000000071, 2075449200000000001, 'fee_item_add', '新增', 'add', '/base/fee-item/add', 'plus', 1, 2, 1, 1, '', NULL, 0); +INSERT INTO "BLADE_MENU" ("ID","PARENT_ID","CODE","NAME","ALIAS","PATH","SOURCE","SORT","CATEGORY","ACTION","IS_OPEN","COMPONENT","REMARK","IS_DELETED") VALUES (1980000000000000072, 2075449200000000001, 'fee_item_edit', '修改', 'edit', '/base/fee-item/edit', 'form', 2, 2, 2, 1, '', NULL, 0); +INSERT INTO "BLADE_MENU" ("ID","PARENT_ID","CODE","NAME","ALIAS","PATH","SOURCE","SORT","CATEGORY","ACTION","IS_OPEN","COMPONENT","REMARK","IS_DELETED") VALUES (1980000000000000073, 2075449200000000001, 'fee_item_delete', '删除', 'delete', '/api/blade-system/fee-item/remove', 'delete', 3, 2, 3, 1, '', NULL, 0); +INSERT INTO "BLADE_MENU" ("ID","PARENT_ID","CODE","NAME","ALIAS","PATH","SOURCE","SORT","CATEGORY","ACTION","IS_OPEN","COMPONENT","REMARK","IS_DELETED") VALUES (1980000000000000074, 2075449200000000001, 'fee_item_view', '查看', 'view', '/base/fee-item/view', 'file-text', 4, 2, 2, 1, '', NULL, 0); +INSERT INTO "BLADE_MENU" ("ID","PARENT_ID","CODE","NAME","ALIAS","PATH","SOURCE","SORT","CATEGORY","ACTION","IS_OPEN","COMPONENT","REMARK","IS_DELETED") VALUES (1980000000000000075, 2075449200000000001, 'fee_item_status', '启停', 'status', '/api/blade-system/fee-item/status', 'key', 5, 2, 2, 1, '', NULL, 0); diff --git a/doc/sql/bladex/bladex.postgres.all.create.sql b/doc/sql/bladex/bladex.postgres.all.create.sql index a49ca55..1f4104f 100644 --- a/doc/sql/bladex/bladex.postgres.all.create.sql +++ b/doc/sql/bladex/bladex.postgres.all.create.sql @@ -2562,6 +2562,7 @@ CREATE INDEX "idx_customer_type_status" ON "blade_customer_type" ("status"); DROP TABLE IF EXISTS "blade_fee_item"; CREATE TABLE "blade_fee_item" ( "id" int8 NOT NULL, + "fee_category" varchar(50) NOT NULL, "name" varchar(50) NOT NULL, "english_name" varchar(100), "create_user" int8, @@ -2574,15 +2575,19 @@ CREATE TABLE "blade_fee_item" ( CONSTRAINT "blade_fee_item_pkey" PRIMARY KEY ("id"), CONSTRAINT "uk_fee_item_name" UNIQUE ("name") ); +CREATE INDEX "idx_fee_item_category" ON "blade_fee_item" ("fee_category"); CREATE INDEX "idx_fee_item_status" ON "blade_fee_item" ("status"); -- ---------------------------- -- Records of blade_menu for fee item -- ---------------------------- INSERT INTO "blade_menu" ("id", "parent_id", "code", "name", "alias", "path", "source", "sort", "category", "action", "is_open", "component", "remark", "is_deleted") VALUES -(1980000000000000070, 1164733399668962201, 'fee_item', '费用项', 'menu', '/base/fee-item', 'iconfont icon-shoucang', 7, 1, 0, 1, '', NULL, 0), -(1980000000000000071, 1980000000000000070, 'fee_item_add', '新增', 'add', '/base/fee-item/add', 'plus', 1, 2, 1, 1, '', NULL, 0), -(1980000000000000072, 1980000000000000070, 'fee_item_edit', '修改', 'edit', '/base/fee-item/edit', 'form', 2, 2, 2, 1, '', NULL, 0), -(1980000000000000073, 1980000000000000070, 'fee_item_delete', '删除', 'delete', '/api/blade-system/fee-item/remove', 'delete', 3, 2, 3, 1, '', NULL, 0), -(1980000000000000074, 1980000000000000070, 'fee_item_view', '查看', 'view', '/base/fee-item/view', 'file-text', 4, 2, 2, 1, '', NULL, 0), -(1980000000000000075, 1980000000000000070, 'fee_item_status', '启停', 'status', '/api/blade-system/fee-item/status', 'key', 5, 2, 2, 1, '', NULL, 0); +(2075449200000000001, 1164733399668962201, 'fee_item', '费用项', 'menu', '/base/fee-item', 'iconfont icon-shoucang', 7, 1, 0, 1, '', NULL, 0), +(1980000000000000071, 2075449200000000001, 'fee_item_add', '新增', 'add', '/base/fee-item/add', 'plus', 1, 2, 1, 1, '', NULL, 0), +(1980000000000000072, 2075449200000000001, 'fee_item_edit', '修改', 'edit', '/base/fee-item/edit', 'form', 2, 2, 2, 1, '', NULL, 0), +(1980000000000000073, 2075449200000000001, 'fee_item_delete', '删除', 'delete', '/api/blade-system/fee-item/remove', 'delete', 3, 2, 3, 1, '', NULL, 0), +(1980000000000000074, 2075449200000000001, 'fee_item_view', '查看', 'view', '/base/fee-item/view', 'file-text', 4, 2, 2, 1, '', NULL, 0), +(1980000000000000075, 2075449200000000001, 'fee_item_status', '启停', 'status', '/api/blade-system/fee-item/status', 'key', 5, 2, 2, 1, '', NULL, 0), +(1980000000000000076, 2075449200000000001, 'fee_item_import', '批量导入', 'import', '/api/blade-system/fee-item/import-fee-item', 'upload', 6, 2, 3, 1, '', NULL, 0), +(1980000000000000077, 2075449200000000001, 'fee_item_template', '模板下载', 'template', '/api/blade-system/fee-item/export-template', 'download', 7, 2, 2, 1, '', NULL, 0), +(1980000000000000078, 2075449200000000001, 'fee_item_export', '批量导出', 'export', '/api/blade-system/fee-item/export-fee-item', 'download', 8, 2, 2, 1, '', NULL, 0); diff --git a/doc/sql/bladex/bladex.sqlserver.all.create.sql b/doc/sql/bladex/bladex.sqlserver.all.create.sql index 0706b85..8c64a8a 100644 --- a/doc/sql/bladex/bladex.sqlserver.all.create.sql +++ b/doc/sql/bladex/bladex.sqlserver.all.create.sql @@ -7727,6 +7727,7 @@ IF EXISTS (SELECT * FROM sys.all_objects WHERE object_id = OBJECT_ID(N'[dbo].[bl GO CREATE TABLE [dbo].[blade_fee_item] ( [id] bigint NOT NULL, + [fee_category] nvarchar(50) NOT NULL, [name] nvarchar(50) NOT NULL, [english_name] nvarchar(100) NULL, [create_user] bigint NULL, @@ -7740,11 +7741,13 @@ CREATE TABLE [dbo].[blade_fee_item] ( CONSTRAINT [uk_fee_item_name] UNIQUE ([name]) ) GO +CREATE INDEX [idx_fee_item_category] ON [dbo].[blade_fee_item] ([fee_category]) +GO CREATE INDEX [idx_fee_item_status] ON [dbo].[blade_fee_item] ([status]) GO -- ---------------------------- -- Records of blade_menu for fee item -- ---------------------------- -INSERT INTO [dbo].[blade_menu] ([id], [parent_id], [code], [name], [alias], [path], [source], [sort], [category], [action], [is_open], [component], [remark], [is_deleted]) VALUES (N'1980000000000000070', N'1164733399668962201', N'fee_item', N'费用项', N'menu', N'/base/fee-item', N'iconfont icon-shoucang', N'7', N'1', N'0', N'1', N'', NULL, N'0'), (N'1980000000000000071', N'1980000000000000070', N'fee_item_add', N'新增', N'add', N'/base/fee-item/add', N'plus', N'1', N'2', N'1', N'1', N'', NULL, N'0'), (N'1980000000000000072', N'1980000000000000070', N'fee_item_edit', N'修改', N'edit', N'/base/fee-item/edit', N'form', N'2', N'2', N'2', N'1', N'', NULL, N'0'), (N'1980000000000000073', N'1980000000000000070', N'fee_item_delete', N'删除', N'delete', N'/api/blade-system/fee-item/remove', N'delete', N'3', N'2', N'3', N'1', N'', NULL, N'0'), (N'1980000000000000074', N'1980000000000000070', N'fee_item_view', N'查看', N'view', N'/base/fee-item/view', N'file-text', N'4', N'2', N'2', N'1', N'', NULL, N'0'), (N'1980000000000000075', N'1980000000000000070', N'fee_item_status', N'启停', N'status', N'/api/blade-system/fee-item/status', N'key', N'5', N'2', N'2', N'1', N'', NULL, N'0') +INSERT INTO [dbo].[blade_menu] ([id], [parent_id], [code], [name], [alias], [path], [source], [sort], [category], [action], [is_open], [component], [remark], [is_deleted]) VALUES (N'2075449200000000001', N'1164733399668962201', N'fee_item', N'费用项', N'menu', N'/base/fee-item', N'iconfont icon-shoucang', N'7', N'1', N'0', N'1', N'', NULL, N'0'), (N'1980000000000000071', N'2075449200000000001', N'fee_item_add', N'新增', N'add', N'/base/fee-item/add', N'plus', N'1', N'2', N'1', N'1', N'', NULL, N'0'), (N'1980000000000000072', N'2075449200000000001', N'fee_item_edit', N'修改', N'edit', N'/base/fee-item/edit', N'form', N'2', N'2', N'2', N'1', N'', NULL, N'0'), (N'1980000000000000073', N'2075449200000000001', N'fee_item_delete', N'删除', N'delete', N'/api/blade-system/fee-item/remove', N'delete', N'3', N'2', N'3', N'1', N'', NULL, N'0'), (N'1980000000000000074', N'2075449200000000001', N'fee_item_view', N'查看', N'view', N'/base/fee-item/view', N'file-text', N'4', N'2', N'2', N'1', N'', NULL, N'0'), (N'1980000000000000075', N'2075449200000000001', N'fee_item_status', N'启停', N'status', N'/api/blade-system/fee-item/status', N'key', N'5', N'2', N'2', N'1', N'', NULL, N'0'), (N'1980000000000000076', N'2075449200000000001', N'fee_item_import', N'批量导入', N'import', N'/api/blade-system/fee-item/import-fee-item', N'upload', N'6', N'2', N'3', N'1', N'', NULL, N'0'), (N'1980000000000000077', N'2075449200000000001', N'fee_item_template', N'模板下载', N'template', N'/api/blade-system/fee-item/export-template', N'download', N'7', N'2', N'2', N'1', N'', NULL, N'0'), (N'1980000000000000078', N'2075449200000000001', N'fee_item_export', N'批量导出', N'export', N'/api/blade-system/fee-item/export-fee-item', N'download', N'8', N'2', N'2', N'1', N'', NULL, N'0') GO diff --git a/doc/sql/bladex/bladex.yashan.all.create.sql b/doc/sql/bladex/bladex.yashan.all.create.sql index 827a8b7..c40d914 100644 --- a/doc/sql/bladex/bladex.yashan.all.create.sql +++ b/doc/sql/bladex/bladex.yashan.all.create.sql @@ -28660,9 +28660,9 @@ CREATE INDEX "IDX_FEE_ITEM_STATUS" ON "BLADE_FEE_ITEM" ("STATUS"); -- ---------------------------- -- Records of BLADE_MENU for fee item -- ---------------------------- -INSERT INTO "BLADE_MENU" ("ID","PARENT_ID","CODE","NAME","ALIAS","PATH","SOURCE","SORT","CATEGORY","ACTION","IS_OPEN","COMPONENT","REMARK","IS_DELETED") VALUES (1980000000000000070, 1164733399668962201, 'fee_item', '费用项', 'menu', '/base/fee-item', 'iconfont icon-shoucang', 7, 1, 0, 1, '', NULL, 0); -INSERT INTO "BLADE_MENU" ("ID","PARENT_ID","CODE","NAME","ALIAS","PATH","SOURCE","SORT","CATEGORY","ACTION","IS_OPEN","COMPONENT","REMARK","IS_DELETED") VALUES (1980000000000000071, 1980000000000000070, 'fee_item_add', '新增', 'add', '/base/fee-item/add', 'plus', 1, 2, 1, 1, '', NULL, 0); -INSERT INTO "BLADE_MENU" ("ID","PARENT_ID","CODE","NAME","ALIAS","PATH","SOURCE","SORT","CATEGORY","ACTION","IS_OPEN","COMPONENT","REMARK","IS_DELETED") VALUES (1980000000000000072, 1980000000000000070, 'fee_item_edit', '修改', 'edit', '/base/fee-item/edit', 'form', 2, 2, 2, 1, '', NULL, 0); -INSERT INTO "BLADE_MENU" ("ID","PARENT_ID","CODE","NAME","ALIAS","PATH","SOURCE","SORT","CATEGORY","ACTION","IS_OPEN","COMPONENT","REMARK","IS_DELETED") VALUES (1980000000000000073, 1980000000000000070, 'fee_item_delete', '删除', 'delete', '/api/blade-system/fee-item/remove', 'delete', 3, 2, 3, 1, '', NULL, 0); -INSERT INTO "BLADE_MENU" ("ID","PARENT_ID","CODE","NAME","ALIAS","PATH","SOURCE","SORT","CATEGORY","ACTION","IS_OPEN","COMPONENT","REMARK","IS_DELETED") VALUES (1980000000000000074, 1980000000000000070, 'fee_item_view', '查看', 'view', '/base/fee-item/view', 'file-text', 4, 2, 2, 1, '', NULL, 0); -INSERT INTO "BLADE_MENU" ("ID","PARENT_ID","CODE","NAME","ALIAS","PATH","SOURCE","SORT","CATEGORY","ACTION","IS_OPEN","COMPONENT","REMARK","IS_DELETED") VALUES (1980000000000000075, 1980000000000000070, 'fee_item_status', '启停', 'status', '/api/blade-system/fee-item/status', 'key', 5, 2, 2, 1, '', NULL, 0); +INSERT INTO "BLADE_MENU" ("ID","PARENT_ID","CODE","NAME","ALIAS","PATH","SOURCE","SORT","CATEGORY","ACTION","IS_OPEN","COMPONENT","REMARK","IS_DELETED") VALUES (2075449200000000001, 1164733399668962201, 'fee_item', '费用项', 'menu', '/base/fee-item', 'iconfont icon-shoucang', 7, 1, 0, 1, '', NULL, 0); +INSERT INTO "BLADE_MENU" ("ID","PARENT_ID","CODE","NAME","ALIAS","PATH","SOURCE","SORT","CATEGORY","ACTION","IS_OPEN","COMPONENT","REMARK","IS_DELETED") VALUES (1980000000000000071, 2075449200000000001, 'fee_item_add', '新增', 'add', '/base/fee-item/add', 'plus', 1, 2, 1, 1, '', NULL, 0); +INSERT INTO "BLADE_MENU" ("ID","PARENT_ID","CODE","NAME","ALIAS","PATH","SOURCE","SORT","CATEGORY","ACTION","IS_OPEN","COMPONENT","REMARK","IS_DELETED") VALUES (1980000000000000072, 2075449200000000001, 'fee_item_edit', '修改', 'edit', '/base/fee-item/edit', 'form', 2, 2, 2, 1, '', NULL, 0); +INSERT INTO "BLADE_MENU" ("ID","PARENT_ID","CODE","NAME","ALIAS","PATH","SOURCE","SORT","CATEGORY","ACTION","IS_OPEN","COMPONENT","REMARK","IS_DELETED") VALUES (1980000000000000073, 2075449200000000001, 'fee_item_delete', '删除', 'delete', '/api/blade-system/fee-item/remove', 'delete', 3, 2, 3, 1, '', NULL, 0); +INSERT INTO "BLADE_MENU" ("ID","PARENT_ID","CODE","NAME","ALIAS","PATH","SOURCE","SORT","CATEGORY","ACTION","IS_OPEN","COMPONENT","REMARK","IS_DELETED") VALUES (1980000000000000074, 2075449200000000001, 'fee_item_view', '查看', 'view', '/base/fee-item/view', 'file-text', 4, 2, 2, 1, '', NULL, 0); +INSERT INTO "BLADE_MENU" ("ID","PARENT_ID","CODE","NAME","ALIAS","PATH","SOURCE","SORT","CATEGORY","ACTION","IS_OPEN","COMPONENT","REMARK","IS_DELETED") VALUES (1980000000000000075, 2075449200000000001, 'fee_item_status', '启停', 'status', '/api/blade-system/fee-item/status', 'key', 5, 2, 2, 1, '', NULL, 0); diff --git a/doc/sql/transport/blade_fee_item.sql b/doc/sql/transport/blade_fee_item.sql index 7e5976a..5352b38 100644 --- a/doc/sql/transport/blade_fee_item.sql +++ b/doc/sql/transport/blade_fee_item.sql @@ -28,3 +28,6 @@ INSERT INTO `blade_menu` (`id`, `parent_id`, `code`, `name`, `alias`, `path`, `s INSERT INTO `blade_menu` (`id`, `parent_id`, `code`, `name`, `alias`, `path`, `source`, `sort`, `category`, `action`, `is_open`, `component`, `remark`, `is_deleted`) VALUES (2075449200000000003, 2075449200000000001, 'fee_item_edit', '编辑', 'fee_item_edit', '', '', 1, 2, 0, 1, NULL, '', 0); INSERT INTO `blade_menu` (`id`, `parent_id`, `code`, `name`, `alias`, `path`, `source`, `sort`, `category`, `action`, `is_open`, `component`, `remark`, `is_deleted`) VALUES (2075449200000000004, 2075449200000000001, 'fee_item_status', '修改状态', 'fee_item_status', '', '', 1, 2, 0, 1, NULL, '', 0); INSERT INTO `blade_menu` (`id`, `parent_id`, `code`, `name`, `alias`, `path`, `source`, `sort`, `category`, `action`, `is_open`, `component`, `remark`, `is_deleted`) VALUES (2075449200000000005, 2075449200000000001, 'fee_item_add', '新增', 'fee_item_add', '', '', 1, 2, 0, 1, NULL, '', 0); +INSERT INTO `blade_menu` (`id`, `parent_id`, `code`, `name`, `alias`, `path`, `source`, `sort`, `category`, `action`, `is_open`, `component`, `remark`, `is_deleted`) VALUES (2075449200000000006, 2075449200000000001, 'fee_item_import', '批量导入', 'fee_item_import', '/api/blade-system/fee-item/import-fee-item', 'upload', 5, 2, 3, 1, NULL, '', 0); +INSERT INTO `blade_menu` (`id`, `parent_id`, `code`, `name`, `alias`, `path`, `source`, `sort`, `category`, `action`, `is_open`, `component`, `remark`, `is_deleted`) VALUES (2075449200000000007, 2075449200000000001, 'fee_item_template', '下载模板', 'fee_item_template', '/api/blade-system/fee-item/export-template', 'download', 6, 2, 2, 1, NULL, '', 0); +INSERT INTO `blade_menu` (`id`, `parent_id`, `code`, `name`, `alias`, `path`, `source`, `sort`, `category`, `action`, `is_open`, `component`, `remark`, `is_deleted`) VALUES (2075449200000000008, 2075449200000000001, 'fee_item_export', '批量导出', 'fee_item_export', '/api/blade-system/fee-item/export-fee-item', 'download', 7, 2, 2, 1, NULL, '', 0); diff --git a/doc/sql/transport/blade_fee_item_import_export_permission_patch_20260803.sql b/doc/sql/transport/blade_fee_item_import_export_permission_patch_20260803.sql new file mode 100644 index 0000000..d0bc840 --- /dev/null +++ b/doc/sql/transport/blade_fee_item_import_export_permission_patch_20260803.sql @@ -0,0 +1,19 @@ +-- 费用项批量导入、模板下载、批量导出菜单权限 +INSERT INTO `blade_menu` (`id`, `parent_id`, `code`, `name`, `alias`, `path`, `source`, `sort`, `category`, `action`, `is_open`, `component`, `remark`, `is_deleted`) VALUES +(1980000000000000076, 2075449200000000001, 'fee_item_import', '批量导入', 'import', '/api/blade-system/fee-item/import-fee-item', 'upload', 6, 2, 3, 1, '', NULL, 0), +(1980000000000000077, 2075449200000000001, 'fee_item_template', '模板下载', 'template', '/api/blade-system/fee-item/export-template', 'download', 7, 2, 2, 1, '', NULL, 0), +(1980000000000000078, 2075449200000000001, 'fee_item_export', '批量导出', 'export', '/api/blade-system/fee-item/export-fee-item', 'download', 8, 2, 2, 1, '', NULL, 0) +ON DUPLICATE KEY UPDATE + `parent_id` = VALUES(`parent_id`), + `code` = VALUES(`code`), + `name` = VALUES(`name`), + `alias` = VALUES(`alias`), + `path` = VALUES(`path`), + `source` = VALUES(`source`), + `sort` = VALUES(`sort`), + `category` = VALUES(`category`), + `action` = VALUES(`action`), + `is_open` = VALUES(`is_open`), + `component` = VALUES(`component`), + `remark` = VALUES(`remark`), + `is_deleted` = VALUES(`is_deleted`); diff --git a/doc/sql/transport/transport.sql b/doc/sql/transport/transport.sql index 72c1865..31df5a9 100644 --- a/doc/sql/transport/transport.sql +++ b/doc/sql/transport/transport.sql @@ -1026,6 +1026,9 @@ INSERT INTO `blade_menu` (`id`, `parent_id`, `code`, `name`, `alias`, `path`, `s INSERT INTO `blade_menu` (`id`, `parent_id`, `code`, `name`, `alias`, `path`, `source`, `sort`, `category`, `action`, `is_open`, `component`, `remark`, `is_deleted`) VALUES (2075449200000000003, 2075449200000000001, 'fee_item_edit', '编辑', 'fee_item_edit', '', '', 1, 2, 0, 1, NULL, '', 0); INSERT INTO `blade_menu` (`id`, `parent_id`, `code`, `name`, `alias`, `path`, `source`, `sort`, `category`, `action`, `is_open`, `component`, `remark`, `is_deleted`) VALUES (2075449200000000004, 2075449200000000001, 'fee_item_status', '修改状态', 'fee_item_status', '', '', 1, 2, 0, 1, NULL, '', 0); INSERT INTO `blade_menu` (`id`, `parent_id`, `code`, `name`, `alias`, `path`, `source`, `sort`, `category`, `action`, `is_open`, `component`, `remark`, `is_deleted`) VALUES (2075449200000000005, 2075449200000000001, 'fee_item_add', '新增', 'fee_item_add', '', '', 1, 2, 0, 1, NULL, '', 0); +INSERT INTO `blade_menu` (`id`, `parent_id`, `code`, `name`, `alias`, `path`, `source`, `sort`, `category`, `action`, `is_open`, `component`, `remark`, `is_deleted`) VALUES (2075449200000000006, 2075449200000000001, 'fee_item_import', '批量导入', 'fee_item_import', '/api/blade-system/fee-item/import-fee-item', 'upload', 5, 2, 3, 1, NULL, '', 0); +INSERT INTO `blade_menu` (`id`, `parent_id`, `code`, `name`, `alias`, `path`, `source`, `sort`, `category`, `action`, `is_open`, `component`, `remark`, `is_deleted`) VALUES (2075449200000000007, 2075449200000000001, 'fee_item_template', '下载模板', 'fee_item_template', '/api/blade-system/fee-item/export-template', 'download', 6, 2, 2, 1, NULL, '', 0); +INSERT INTO `blade_menu` (`id`, `parent_id`, `code`, `name`, `alias`, `path`, `source`, `sort`, `category`, `action`, `is_open`, `component`, `remark`, `is_deleted`) VALUES (2075449200000000008, 2075449200000000001, 'fee_item_export', '批量导出', 'fee_item_export', '/api/blade-system/fee-item/export-fee-item', 'download', 7, 2, 2, 1, NULL, '', 0); INSERT INTO `blade_menu` (`id`, `parent_id`, `code`, `name`, `alias`, `path`, `source`, `sort`, `category`, `action`, `is_open`, `component`, `remark`, `is_deleted`) VALUES (2075436235826896898, 0, 'blade-transport', '车/船务管理', 'blade-transport', '/vehicle', 'iconfont icon-shebeiguanli', 10, 1, 0, 1, '', '', 0); INSERT INTO `blade_menu` (`id`, `parent_id`, `code`, `name`, `alias`, `path`, `source`, `sort`, `category`, `action`, `is_open`, `component`, `remark`, `is_deleted`) VALUES (2075437082342301697, 2075436235826896898, 'maintenance_record', '维修记录', 'maintenance_record', '/vehicle/maintenance-record', 'iconfont icon-wxbgongju', 20, 1, 0, 1, '', '', 0); INSERT INTO `blade_menu` (`id`, `parent_id`, `code`, `name`, `alias`, `path`, `source`, `sort`, `category`, `action`, `is_open`, `component`, `remark`, `is_deleted`) VALUES (2075437237678350338, 2075436235826896898, 'maintenance_plan', '保养记录', 'maintenance_plan', '/vehicle/maintenance-plan', 'iconfont iconicon_exchange', 30, 1, 0, 1, '', '', 0); diff --git a/pom.xml b/pom.xml index 6e6e0a6..a73a952 100644 --- a/pom.xml +++ b/pom.xml @@ -19,11 +19,8 @@ UTF-8 - 172.16.203.228:8081 admin - Harbor12345TMS blade - ${docker.registry.url}/${docker.namespace}/alpine-java:openjdk17_cn_slim false 0.42.0 @@ -38,6 +35,7 @@ blade-plugin-api blade-service blade-service-api + blade-third-party-api @@ -115,6 +113,47 @@ blade-transport-api ${revision} + + org.springblade + blade-oa-api + ${revision} + + + org.springblade + blade-open-api + ${revision} + + + org.springblade + blade-mk-api + ${revision} + + + org.springblade + blade-wps-api + ${revision} + + + org.springblade + blade-ocr-api + ${revision} + + + org.springblade + blade-track-api + ${revision} + + + + org.springblade + blade-file-api + ${revision} + + + org.springblade + blade-starter-threadpool + ${revision} + diff --git a/script/docker/app/docker-compose.yml b/script/docker/app/docker-compose.yml index e1ffd1e..a5e6c79 100644 --- a/script/docker/app/docker-compose.yml +++ b/script/docker/app/docker-compose.yml @@ -242,6 +242,28 @@ services: restart: always networks: - blade_net + + blade-file: + image: "${REGISTER}/blade-file:${TAG}" + environment: + - TZ=Asia/Shanghai + privileged: true + restart: always + ports: + - 8107:8107 + networks: + - blade_net + + blade-openapi: + image: "${REGISTER}/blade-openapi:${TAG}" + environment: + - TZ=Asia/Shanghai + privileged: true + restart: always + ports: + - 8109:8108 + networks: + - blade_net #################################################################################################### ###=============================== 以下为Prometheus监控模块 =====================================### ####################################################################################################