소스 검색

bump: 发布2.3.1

details see: https://helio.uncarbon.cc/#/i18n/zh-CN/appendix/change-log
Uncarbon 1 년 전
부모
커밋
73659febc0

+ 7 - 1
.gitignore

@@ -29,4 +29,10 @@ hs_err_pid*
 .idea
 *.iws
 *.iml
-*.ipr
+*.ipr
+
+# VSCode
+.vscode
+
+# VSCode 不会自动忽略 **/target 目录
+target/

+ 2 - 2
Dockerfile

@@ -1,5 +1,5 @@
-# https://hub.docker.com/_/eclipse-temurin/tags?page=1&name=alpine
-FROM eclipse-temurin:17.0.7_7-jre-alpine
+# https://hub.docker.com/_/eclipse-temurin/tags
+FROM eclipse-temurin:17-jre
 COPY ./target/*.jar ./app.jar
 COPY entrypoint.sh /
 RUN chmod +x ./entrypoint.sh && \

+ 1 - 0
README.md

@@ -55,6 +55,7 @@ JDK compatibility: 17 - 21
     │  │              ├─appapi       app-api,用于C端的HTTP-API【只是一个骨架,并没有业务实现】
     │  │              │  ├─constant  常量
     │  │              │  └─web       即:Controller
+    │  │              ├─bizcommon       biz-common,业务开发公共包
     │  │              ├─oss          对象存储服务
     │  │              │  ├─config    配置类
     │  │              │  ├─constant  常量

+ 2 - 2
attachments/db/PostgreSQL/helio_boot.sql

@@ -477,9 +477,9 @@ ON COLUMN "sys_role"."updated_at" IS '更新时刻';
 COMMENT
 ON COLUMN "sys_role"."updated_by" IS '更新者';
 COMMENT
-ON COLUMN "sys_role"."title" IS '名';
+ON COLUMN "sys_role"."title" IS '角色名';
 COMMENT
-ON COLUMN "sys_role"."value" IS '';
+ON COLUMN "sys_role"."value" IS '角色编码';
 COMMENT
 ON TABLE "sys_role" IS '后台角色';
 

+ 6 - 3
pom.xml

@@ -8,16 +8,19 @@
     <parent>
         <groupId>cc.uncarbon.framework</groupId>
         <artifactId>helio-starters</artifactId>
-        <version>2.2.0</version>
+        <version>2.3.1</version>
     </parent>
 
     <artifactId>helio-boot</artifactId>
     <description>Helio 单模块版单体脚手架</description>
     <!-- 制品版本号,建议每次打包发布时都调整以作区分 -->
-    <version>2.2.0</version>
+    <version>2.3.1</version>
 
     <properties>
-        <!-- 这里写其他第三方依赖 -->
+        <!-- 如果实际开发使用的是JDK 21,则下面改成'21' -->
+        <java.version>17</java.version>
+
+        <!-- 这里管理全局第三方依赖版本,避免冲突 -->
         <!-- 文件上传 -->
         <x-file-storage.version>2.2.1</x-file-storage.version>
         <minio.version>8.5.8</minio.version>

+ 14 - 2
src/main/java/cc/uncarbon/aspect/SysLogAspect.java

@@ -23,6 +23,7 @@ import cn.hutool.core.util.ReflectUtil;
 import cn.hutool.core.util.StrUtil;
 import cn.hutool.json.JSONConfig;
 import cn.hutool.json.JSONUtil;
+import jakarta.servlet.http.HttpServletRequest;
 import lombok.Getter;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
@@ -33,11 +34,13 @@ import org.aspectj.lang.annotation.Aspect;
 import org.springframework.http.HttpHeaders;
 import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
 import org.springframework.stereotype.Component;
+import org.springframework.web.multipart.MultipartFile;
+import org.springframework.web.multipart.MultipartRequest;
 
-import jakarta.servlet.http.HttpServletRequest;
 import java.util.Arrays;
 import java.util.LinkedHashMap;
 import java.util.Map;
+import java.util.Objects;
 import java.util.stream.Collectors;
 
 /**
@@ -268,7 +271,16 @@ public class SysLogAspect {
         Map<Object, Object> afterMasked = new LinkedHashMap<>(32, 1);
         String params = Arrays.stream(joinPoint.getArgs()).map(
                 item -> {
-                    if (ClassUtil.isBasicType(item.getClass())) {
+                    if (Objects.isNull(item)) {
+                        return CharSequenceUtil.EMPTY;
+                    } else if (item instanceof MultipartRequest multipartRequest) {
+                        return String.format("上传文件(%s)",
+                                CharSequenceUtil.join(StrPool.COMMA,
+                                        multipartRequest.getFileMap().values().stream().map(MultipartFile::getOriginalFilename).toList()
+                                ));
+                    } else if (item instanceof MultipartFile multipartFile) {
+                        return String.format("上传文件(%s)", multipartFile.getOriginalFilename());
+                    } else if (ClassUtil.isBasicType(item.getClass())) {
                         // 基元类型 OR 其包装类型,且拿不到参数名,保存在DB时保持原样
                         return StrUtil.toStringOrNull(item);
                     }

+ 19 - 17
src/main/java/cc/uncarbon/helper/RolePermissionCacheHelper.java

@@ -18,7 +18,7 @@ import java.util.*;
 @RequiredArgsConstructor
 public class RolePermissionCacheHelper {
 
-    private final RedisTemplate<String, Set<String>> stringSetRedisTemplate;
+    private final RedisTemplate<String, Collection<String>> stringSetRedisTemplate;
 
     private static final String CACHE_KEY_ROLE_PERMISSIONS = "Authorization:rolePermissions:roleId_%s";
 
@@ -30,17 +30,20 @@ public class RolePermissionCacheHelper {
      */
     public List<String> getUserPermissions() {
         Set<Long> rolesIds = UserContextHolder.getUserContext().getRolesIds();
-        // aka * 64
-        List<String> ret = new ArrayList<>(rolesIds.size() << 6);
+        if (CollUtil.isEmpty(rolesIds)) {
+            return Collections.emptyList();
+        }
 
-        rolesIds.forEach(
-                roleId -> {
-                    String cacheKey = String.format(CACHE_KEY_ROLE_PERMISSIONS, roleId);
-                    ret.addAll(CollUtil.emptyIfNull(stringSetRedisTemplate.opsForValue().get(cacheKey)));
-                }
-        );
+        // 批量查询缓存
+        List<String> cacheKeys = rolesIds.stream()
+                .map(roleId -> String.format(CACHE_KEY_ROLE_PERMISSIONS, roleId))
+                .toList();
+        List<Collection<String>> cacheValues = stringSetRedisTemplate.opsForValue().multiGet(cacheKeys);
+        if (CollUtil.isEmpty(cacheValues)) {
+            return Collections.emptyList();
+        }
 
-        return ret;
+        return cacheValues.stream().flatMap(Collection::stream).toList();
     }
 
     /**
@@ -61,7 +64,7 @@ public class RolePermissionCacheHelper {
      * @param roleId 角色ID
      * @param newPermissions 新权限名集合
      */
-    public void putCache(Long roleId, Set<String> newPermissions) {
+    public void putCache(Long roleId, Collection<String> newPermissions) {
         String cacheKey = String.format(CACHE_KEY_ROLE_PERMISSIONS, roleId);
         stringSetRedisTemplate.opsForValue().set(cacheKey, newPermissions);
     }
@@ -72,11 +75,10 @@ public class RolePermissionCacheHelper {
      * @param roleIds 角色ID集合
      */
     public void deleteCache(Collection<Long> roleIds) {
-        roleIds.forEach(
-                roleId -> {
-                    String cacheKey = String.format(CACHE_KEY_ROLE_PERMISSIONS, roleId);
-                    stringSetRedisTemplate.delete(cacheKey);
-                }
-        );
+        if (CollUtil.isNotEmpty(roleIds)) {
+            // 批量删除缓存
+            List<String> cacheKeys = roleIds.stream().map(roleId -> String.format(CACHE_KEY_ROLE_PERMISSIONS, roleId)).toList();
+            stringSetRedisTemplate.delete(cacheKeys);
+        }
     }
 }

+ 77 - 37
src/main/java/cc/uncarbon/module/adminapi/model/response/SelectOptionItemVO.java → src/main/java/cc/uncarbon/module/adminapi/model/response/AdminSelectOptionItemVO.java

@@ -10,10 +10,8 @@ import lombok.Setter;
 import lombok.experimental.Accessors;
 
 import java.io.Serializable;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.List;
+import java.util.*;
+import java.util.function.BiConsumer;
 import java.util.function.Function;
 import java.util.function.Predicate;
 import java.util.stream.Stream;
@@ -24,14 +22,20 @@ import java.util.stream.Stream;
 @JsonInclude(JsonInclude.Include.NON_NULL)
 @Accessors(chain = true)
 @Getter
-public class SelectOptionItemVO implements Serializable {
+public class AdminSelectOptionItemVO implements Serializable {
+    /**
+     * Jackson等序列化框架,可利用此无参构造器反射生成对象
+     */
+    private AdminSelectOptionItemVO() {
+    }
 
     // ID👉名称 一对(用于关联各种实体)
     @Schema(description = "ID")
     private Number id;
     @Schema(description = "名称")
     private String name;
-    public SelectOptionItemVO(Number id, String name) {
+
+    public AdminSelectOptionItemVO(Number id, String name) {
         this.id = id;
         this.name = name;
     }
@@ -40,7 +44,8 @@ public class SelectOptionItemVO implements Serializable {
     @Schema(description = "上级ID")
     @Setter
     private Number parentId;
-    public SelectOptionItemVO(Number id, String name, Number parentId) {
+
+    public AdminSelectOptionItemVO(Number id, String name, Number parentId) {
         this.id = id;
         this.name = name;
         this.parentId = parentId;
@@ -52,7 +57,8 @@ public class SelectOptionItemVO implements Serializable {
     private Number value;
     @Schema(description = "标签")
     private String label;
-    public SelectOptionItemVO(HelioBaseEnum<? extends Number> helioBaseEnum) {
+
+    public AdminSelectOptionItemVO(HelioBaseEnum<? extends Number> helioBaseEnum) {
         this.value = helioBaseEnum.getValue();
         this.label = helioBaseEnum.getLabel();
     }
@@ -74,54 +80,79 @@ public class SelectOptionItemVO implements Serializable {
      */
 
     /**
-     * 构造List<SelectOptionItemVO>
+     * 构造List<AdminSelectOptionItemVO>
      * 将转换源集合中所有集合项
      * 无需上级ID
-     * @param source 源集合
-     * @param idGetter id getter
+     *
+     * @param source     源集合
+     * @param idGetter   id getter
      * @param nameGetter name getter
      */
-    public static <T> List<SelectOptionItemVO> listOf(
+    public static <T> List<AdminSelectOptionItemVO> listOf(
             Collection<T> source,
             @NonNull Function<T, Number> idGetter,
             @NonNull Function<T, String> nameGetter
     ) {
-        return listOf(source, idGetter, nameGetter, null, null);
+        return listOf(source, idGetter, nameGetter, null, null, null);
     }
 
     /**
-     * 构造List<SelectOptionItemVO>
+     * 构造List<AdminSelectOptionItemVO>
      * 将转换源集合中所有集合项
      * 支持上级ID
-     * @param source 源集合
-     * @param idGetter id getter
-     * @param nameGetter name getter
+     *
+     * @param source         源集合
+     * @param idGetter       id getter
+     * @param nameGetter     name getter
+     * @param parentIdGetter 上级ID getter
      */
-    public static <T> List<SelectOptionItemVO> listOf(
+    public static <T> List<AdminSelectOptionItemVO> listOf(
             Collection<T> source,
             @NonNull Function<T, Number> idGetter,
             @NonNull Function<T, String> nameGetter,
             Function<T, Number> parentIdGetter
     ) {
-        return listOf(source, idGetter, nameGetter, parentIdGetter, null);
+        return listOf(source, idGetter, nameGetter, parentIdGetter, null, null);
+    }
+
+    /**
+     * 构造List<AdminSelectOptionItemVO>
+     * 将转换源集合中所有集合项
+     * 无需上级ID
+     *
+     * @param source                   源集合
+     * @param idGetter                 id getter
+     * @param nameGetter               name getter
+     * @param postConversionProcessing (可选)转换后置处理过程,方便加入一些自定义字段,如 code、quantity 等
+     */
+    public static <T> List<AdminSelectOptionItemVO> listOf(
+            Collection<T> source,
+            @NonNull Function<T, Number> idGetter,
+            @NonNull Function<T, String> nameGetter,
+            BiConsumer<T, AdminSelectOptionItemVO> postConversionProcessing
+    ) {
+        return listOf(source, idGetter, nameGetter, null, null, postConversionProcessing);
     }
 
     /**
-     * 构造List<SelectOptionItemVO>
+     * 构造List<AdminSelectOptionItemVO>
      * 支持自定义过滤器,仅转换需要的集合项
      * 支持上级ID
-     * @param source 源集合
-     * @param idGetter id getter
-     * @param nameGetter name getter
-     * @param parentIdGetter (可选)parentId getter
-     * @param sourceItemFilter (可选)集合项过滤器
+     *
+     * @param source                   源集合
+     * @param idGetter                 id getter
+     * @param nameGetter               name getter
+     * @param parentIdGetter           (可选)parentId getter
+     * @param sourceItemFilter         (可选)集合项过滤器
+     * @param postConversionProcessing (可选)转换后置处理过程,方便加入一些自定义字段,如 code、quantity 等
      */
-    public static <T> List<SelectOptionItemVO> listOf(
+    public static <T> List<AdminSelectOptionItemVO> listOf(
             Collection<T> source,
             @NonNull Function<T, Number> idGetter,
             @NonNull Function<T, String> nameGetter,
             Function<T, Number> parentIdGetter,
-            Predicate<T> sourceItemFilter
+            Predicate<T> sourceItemFilter,
+            BiConsumer<T, AdminSelectOptionItemVO> postConversionProcessing
     ) {
         if (CollUtil.isEmpty(source)) {
             return Collections.emptyList();
@@ -130,28 +161,37 @@ public class SelectOptionItemVO implements Serializable {
         if (sourceItemFilter != null) {
             stream = stream.filter(sourceItemFilter);
         }
-        return stream.map(item ->
-                        new SelectOptionItemVO(idGetter.apply(item), nameGetter.apply(item),
-                                parentIdGetter == null ? null : parentIdGetter.apply(item)))
-                .toList();
+
+        return stream.map(sourceItem -> {
+                    AdminSelectOptionItemVO optionItem = new AdminSelectOptionItemVO(idGetter.apply(sourceItem), nameGetter.apply(sourceItem));
+                    if (Objects.nonNull(parentIdGetter)) {
+                        optionItem.setParentId(parentIdGetter.apply(sourceItem));
+                    }
+                    if (Objects.nonNull(postConversionProcessing)) {
+                        postConversionProcessing.accept(sourceItem, optionItem);
+                    }
+                    return optionItem;
+                }).toList();
     }
 
     /**
-     * 构造List<SelectOptionItemVO>
+     * 构造List<AdminSelectOptionItemVO>
      * 将转换枚举类中所有枚举常量
+     *
      * @param helioBaseEnum 实现了HelioBaseEnum的枚举类
      */
-    public static <E extends Enum<?> & HelioBaseEnum<? extends Number>> List<SelectOptionItemVO> listOf(Class<E> helioBaseEnum) {
+    public static <E extends Enum<?> & HelioBaseEnum<? extends Number>> List<AdminSelectOptionItemVO> listOf(Class<E> helioBaseEnum) {
         return listOf(helioBaseEnum, null);
     }
 
     /**
-     * 构造List<SelectOptionItemVO>
+     * 构造List<AdminSelectOptionItemVO>
      * 支持自定义过滤器,仅转换需要的枚举常量
-     * @param helioBaseEnum 实现了HelioBaseEnum的枚举类
+     *
+     * @param helioBaseEnum      实现了HelioBaseEnum的枚举类
      * @param enumConstantFilter (可选)枚举类中枚举常量过滤器
      */
-    public static <E extends Enum<?> & HelioBaseEnum<? extends Number>> List<SelectOptionItemVO> listOf(
+    public static <E extends Enum<?> & HelioBaseEnum<? extends Number>> List<AdminSelectOptionItemVO> listOf(
             Class<E> helioBaseEnum,
             Predicate<E> enumConstantFilter
     ) {
@@ -162,6 +202,6 @@ public class SelectOptionItemVO implements Serializable {
         if (enumConstantFilter != null) {
             stream = stream.filter(enumConstantFilter);
         }
-        return stream.map(SelectOptionItemVO::new).toList();
+        return stream.map(AdminSelectOptionItemVO::new).toList();
     }
 }

+ 5 - 5
src/main/java/cc/uncarbon/module/adminapi/web/common/AdminSelectOptionsController.java

@@ -3,7 +3,7 @@ package cc.uncarbon.module.adminapi.web.common;
 
 import cc.uncarbon.framework.web.model.response.ApiResult;
 import cc.uncarbon.module.adminapi.constant.AdminApiConstant;
-import cc.uncarbon.module.adminapi.model.response.SelectOptionItemVO;
+import cc.uncarbon.module.adminapi.model.response.AdminSelectOptionItemVO;
 import cc.uncarbon.module.adminapi.util.AdminStpUtil;
 import cc.uncarbon.module.sys.model.response.SysDeptBO;
 import cc.uncarbon.module.sys.model.response.SysRoleBO;
@@ -40,17 +40,17 @@ public class AdminSelectOptionsController {
 
     @Operation(summary = "后台角色下拉框")
     @GetMapping(value = "/select-options/roles")
-    public ApiResult<List<SelectOptionItemVO>> roles() {
+    public ApiResult<List<AdminSelectOptionItemVO>> roles() {
         return ApiResult.data(
-                SelectOptionItemVO.listOf(sysRoleService.adminSelectOptions(), SysRoleBO::getId, SysRoleBO::getTitle)
+                AdminSelectOptionItemVO.listOf(sysRoleService.adminSelectOptions(), SysRoleBO::getId, SysRoleBO::getTitle)
         );
     }
 
     @Operation(summary = "部门下拉框(前端负责转为树状数据)")
     @GetMapping(value = "/select-options/depts")
-    public ApiResult<List<SelectOptionItemVO>> depts() {
+    public ApiResult<List<AdminSelectOptionItemVO>> depts() {
         return ApiResult.data(
-                SelectOptionItemVO.listOf(sysDeptService.adminSelectOptions(true), SysDeptBO::getId, SysDeptBO::getTitle, SysDeptBO::getParentId)
+                AdminSelectOptionItemVO.listOf(sysDeptService.adminSelectOptions(true), SysDeptBO::getId, SysDeptBO::getTitle, SysDeptBO::getParentId)
         );
     }
 

+ 11 - 7
src/main/java/cc/uncarbon/module/adminapi/web/oss/AdminOssUploadDownloadController.java

@@ -54,8 +54,8 @@ public class AdminOssUploadDownloadController {
     ) throws IOException {
         AdminApiErrorEnum.UPLOAD_FILE_NOT_EXIST.assertNotNull(file);
         UploadFileCheckResultEnum checkResult = UploadFileChecker.check(file,
-                // 默认只能上传10MB之内的文件,且约束后缀名
-                UploadFileChecker.FILE_SIZE_1MB * 10,
+                // 默认只能上传1024MB之内的文件,且约束后缀名
+                UploadFileChecker.FILE_SIZE_1MB * 1024,
                 new String[]{"jpg", "png", "webp", "gif", "xlsx"});
         if (checkResult.isNotOK()) {
             throw new BusinessException(checkResult.getLabel());
@@ -82,7 +82,7 @@ public class AdminOssUploadDownloadController {
             bo = ossUploadDownloadFacade.upload(file.getBytes(), attr);
         }
 
-        return ApiResult.data(this.toUploadResult(bo, request.getRequestURL().toString()));
+        return ApiResult.data(this.toUploadResult(bo, request.getRequestURL().toString(), file.getOriginalFilename()));
     }
 
     @Operation(summary = "下载文件(根据文件ID)")
@@ -110,15 +110,18 @@ public class AdminOssUploadDownloadController {
     /**
      * 将 OssFileInfoBO 转换为 OssFileUploadResultVO
      */
-    private OssFileUploadResultVO toUploadResult(OssFileInfoBO ossFileInfo, String requestUrl) {
+    private OssFileUploadResultVO toUploadResult(OssFileInfoBO ossFileInfo, String requestUrl, String originalFilename) {
         OssFileUploadResultVO ret = new OssFileUploadResultVO()
                 .setFileId(ossFileInfo.getId())
-                .setFilename(ossFileInfo.getStorageFilenameFull());
+                .setFilename(ossFileInfo.getStorageFilenameFull())
+                // 返回本次上传文件的原始文件名
+                .setOriginalFilename(originalFilename);
 
         /*
         这里请根据实际业务性质调整
-        有的业务出于安全目的,上传后文件只能通过文件ID才能下载
-        有的业务没有限制,上传后文件完全可以直接通过对象存储直链下载
+        有的业务出于安全目的,不能暴露直链,只能通过服务端代理下载后,返回 byte[]
+        有的业务没有限制,上传后文件完全可以直接通过对象存储直链下载,如此还能节约服务端上传带宽
+        有的业务有安全要求,只能通过预签名地址下载
         但本地存储又没有直链,只能通过文件ID;
         默认地,此处按【本地存储or对象存储直链为空:通过文件ID下载;对象存储:通过对象存储直链下载】返回 url
          */
@@ -131,6 +134,7 @@ public class AdminOssUploadDownloadController {
                     String.format("%s/%s", requestUrl, ossFileInfo.getId())
             );
         } else {
+            // 获取预签名地址可参考 OssUploadDownloadFacadeImpl 的 downloadById 方法
             ret.setUrl(ossFileInfo.getDirectUrl());
         }
 

+ 38 - 0
src/main/java/cc/uncarbon/module/bizcommon/context/PrivilegedTenantContext.java

@@ -0,0 +1,38 @@
+package cc.uncarbon.module.bizcommon.context;
+
+import cc.uncarbon.framework.core.constant.HelioConstant;
+import cc.uncarbon.framework.core.context.TenantContext;
+
+/**
+ * 快速构造一个超级租户实例,用于临时绕过租户拦截器;同时提供一个单例对象
+ * 使用示例
+ * <p>
+ *     TenantContext originContext = TenantContextHolder.getTenantContext();
+ *     try {
+ *         TenantContextHolder.setTenantContext(PrivilegedTenantContext.SINGLETON);
+ *         // 业务代码...
+ *     } finally {
+ *         TenantContextHolder.setTenantContext(originContext);
+ *     }
+ * </p>
+ */
+public class PrivilegedTenantContext extends TenantContext {
+
+    public PrivilegedTenantContext() {
+        super(HelioConstant.Tenant.DEFAULT_PRIVILEGED_TENANT_ID, "超级租户");
+    }
+
+    public static final PrivilegedTenantContext SINGLETON = new PrivilegedTenantContext();
+
+    @Override
+    public TenantContext setTenantId(Long tenantId) {
+        // 不允许修改
+        return this;
+    }
+
+    @Override
+    public TenantContext setTenantName(String tenantName) {
+        // 不允许修改
+        return this;
+    }
+}

+ 32 - 0
src/main/java/cc/uncarbon/module/bizcommon/model/request/AdminBatchUpdateStatusDTO.java

@@ -0,0 +1,32 @@
+package cc.uncarbon.module.bizcommon.model.request;
+
+import cc.uncarbon.framework.core.enums.HelioBaseEnum;
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotEmpty;
+import jakarta.validation.constraints.NotNull;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import lombok.experimental.Accessors;
+
+import java.io.Serializable;
+import java.util.Collection;
+
+/**
+ * 批量修改状态公共类
+ */
+@Accessors(chain = true)
+@AllArgsConstructor
+@NoArgsConstructor
+@Data
+public class AdminBatchUpdateStatusDTO<I extends Serializable, E extends HelioBaseEnum<?>> implements Serializable {
+
+    @Schema(description = "主键IDs")
+    @NotEmpty(message = "ID不能为空")
+    private Collection<I> ids;
+
+    @Schema(description = "新状态")
+    @NotNull(message = "新状态不能为空")
+    private E newStatus;
+
+}

+ 7 - 0
src/main/java/cc/uncarbon/module/oss/facade/impl/OssUploadDownloadFacadeImpl.java

@@ -92,6 +92,7 @@ public class OssUploadDownloadFacadeImpl implements OssUploadDownloadFacade {
         这里请根据实际业务性质调整
         有的业务出于安全目的,不能暴露直链,只能通过服务端代理下载后,返回 byte[]
         有的业务没有限制,上传后文件完全可以直接通过对象存储直链下载,如此还能节约服务端上传带宽
+        有的业务有安全要求,只能通过预签名地址下载
         但本地存储又没有直链,只能通过文件ID;
         默认地,此处按【本地存储or对象存储直链为空:通过服务端代理下载;对象存储:通过对象存储直链下载】返回
          */
@@ -109,6 +110,12 @@ public class OssUploadDownloadFacadeImpl implements OssUploadDownloadFacade {
                 throw new BusinessException(OssErrorEnum.FILE_DOWNLOAD_FAILED);
             }
         } else {
+            /*if (fileStorageService.isSupportPresignedUrl(ossFileInfo.getStoragePlatform())) {
+                // 采用预签名地址下载
+                FileInfo fileInfo = OssFileInfoService.toFileInfo(ossFileInfo);
+                DateTime oneHourLater = DateUtil.offsetHour(DateUtil.date(), 1);
+                String preSignedUrl = fileStorageService.generatePresignedUrl(fileInfo, oneHourLater);
+                ossFileInfo.setDirectUrl(preSignedUrl);*/
             redirect2DirectUrl = true;
         }
         return OssFileDownloadReplyBO.builder()

+ 3 - 0
src/main/java/cc/uncarbon/module/oss/model/response/OssFileUploadResultVO.java

@@ -27,4 +27,7 @@ public class OssFileUploadResultVO implements Serializable {
     @Schema(description = "完整外链")
     private String url;
 
+    @Schema(description = "原始文件名")
+    private String originalFilename;
+
 }

+ 8 - 5
src/main/java/cc/uncarbon/module/sys/service/SysDeptService.java

@@ -150,12 +150,15 @@ public class SysDeptService {
      * @param queryVisibleDept 是否要进一步查询可见部门
      */
     protected UserDeptContainer getSpecifiedUserDeptContainer(Long specifiedUserId, boolean queryVisibleDept) {
-        List<Long> currentUserDeptIds = sysUserDeptRelationService.getUserDeptIds(specifiedUserId);
-        List<SysDeptEntity> currentUserDepts = Collections.emptyList();
-        if (CollUtil.isNotEmpty(currentUserDeptIds)) {
-            currentUserDepts = sysDeptMapper.selectBatchIds(currentUserDeptIds);
+        List<Long> userDeptIds = sysUserDeptRelationService.getUserDeptIds(specifiedUserId);
+        List<SysDeptEntity> userDepts = null;
+        if (CollUtil.isNotEmpty(userDeptIds)) {
+            userDepts = sysDeptMapper.selectBatchIds(userDeptIds);
         }
-        UserDeptContainer container = new UserDeptContainer(currentUserDeptIds, currentUserDepts);
+        if (CollUtil.isEmpty(userDepts)) {
+            userDepts = Collections.emptyList();
+        }
+        UserDeptContainer container = new UserDeptContainer(userDeptIds, userDepts);
 
         if (queryVisibleDept && container.hasRelatedDepts()) {
             List<SysDeptEntity> allDepts = sysDeptMapper.sortedList();

+ 2 - 0
src/main/java/cc/uncarbon/module/sys/service/SysMenuService.java

@@ -327,6 +327,8 @@ public class SysMenuService {
                         .lambda()
                         .in(SysMenuEntity::getId, ids)
                         .in(SysMenuEntity::getType, types)
+                        // 仅显示启用状态菜单
+                        .eq(SysMenuEntity::getStatus, EnabledStatusEnum.ENABLED)
                         .orderByAsc(SysMenuEntity::getSort)
         );
 

+ 8 - 5
src/main/java/cc/uncarbon/module/sys/service/SysRoleService.java

@@ -227,12 +227,15 @@ public class SysRoleService {
      * 仅内部使用
      */
     protected UserRoleContainer getSpecifiedUserRoleContainer(Long specifiedUserId) {
-        Set<Long> currentUserRoleIds = sysUserRoleRelationService.listRoleIdsByUserId(specifiedUserId);
-        List<SysRoleEntity> currentUserRoles = Collections.emptyList();
-        if (CollUtil.isNotEmpty(currentUserRoleIds)) {
-            currentUserRoles = sysRoleMapper.selectBatchIds(currentUserRoleIds);
+        Set<Long> userRoleIds = sysUserRoleRelationService.listRoleIdsByUserId(specifiedUserId);
+        List<SysRoleEntity> userRoles = null;
+        if (CollUtil.isNotEmpty(userRoleIds)) {
+            userRoles = sysRoleMapper.selectBatchIds(userRoleIds);
         }
-        return new UserRoleContainer(currentUserRoleIds, currentUserRoles);
+        if (CollUtil.isEmpty(userRoles)) {
+            userRoles = Collections.emptyList();
+        }
+        return new UserRoleContainer(userRoleIds, userRoles);
     }
 
     /**

+ 6 - 0
src/main/resources/application.yml

@@ -25,6 +25,12 @@ spring:
     execution:
       pool:
         core-size: 8
+  servlet:
+    multipart:
+      # 文件上传大小限制(两个一起配)
+      # 如果还用了 Nginx 等反向代理,需要在反向代理的配置中也放宽请求体大小限制
+      max-file-size: 1024MB
+      max-request-size: 1024MB
 
 helio:
   crud:

+ 1 - 1
src/main/resources/logback-spring.xml

@@ -23,7 +23,7 @@
     <!-- 控制台输出 -->
     <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
         <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
-            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
+            <pattern>%red(%d{yyyy-MM-dd HH:mm:ss.SSS}) %highlight(%5level) %green([%10thread] [%X{tid}]) %boldMagenta(%40.40logger{40}) [%10method,%line] : %cyan(%msg%n)</pattern>
         </encoder>
     </appender>