Merge branch 'test' into test-dsc

* test:
  修复BUG710,添加文件下载次数统计
  修改私服地址,把 seata-dm 项目从 dsc挪过来
  feat(gateway): 添加API客户端凭证加密功能支持
  bmp 已挪到 ztcloud-dist 仓库
  修改发布信息
  增加快照仓库
  恢复 erp 模块数据权限
  从maven模块中移除 zt-server
  修改版本号
  feat:登陆用户的部门数据权限接口增加角色参数;获取当前用户可访问的顶级部门列表不校验数据权限
  no message
This commit is contained in:
ranke
2026-01-15 14:51:00 +08:00
26 changed files with 156 additions and 24 deletions

15
pom.xml
View File

@@ -25,6 +25,7 @@
<module>zt-module-databus</module>
<!-- <module>zt-module-rule</module>-->
<!-- <module>zt-module-html2pdf</module>-->
<!-- <module>zt-server</module>-->
</modules>
<name>${project.artifactId}</name>
@@ -32,7 +33,7 @@
<url>https://github.com/YunaiV/ruoyi-vue-pro</url>
<properties>
<revision>3.0.46</revision>
<revision>3.0.47-SNAPSHOT</revision>
<!-- Maven 相关 -->
<java.version>17</java.version>
<maven.compiler.source>${java.version}</maven.compiler.source>
@@ -203,7 +204,7 @@
<repository>
<id>ZT</id>
<name>中铜 ZStack 私服</name>
<url>http://172.16.46.63:30708/repository/test/</url>
<url>http://172.16.46.63:30708/repository/zt-cloud/</url>
<releases>
<updatePolicy>always</updatePolicy>
<checksumPolicy>warn</checksumPolicy>
@@ -221,11 +222,11 @@
<name>中铜 ZStack 私服</name>
<url>http://172.16.46.63:30708/repository/test/</url>
</repository>
<!-- <snapshotRepository>-->
<!-- <id>ZT</id>-->
<!-- <name>中铜 ZStack 私服</name>-->
<!-- <url>https://your-nexus.example.com/repository/maven-snapshots/</url>-->
<!-- </snapshotRepository>-->
<snapshotRepository>
<id>ZT-snap</id>
<name>中铜 ZStack 私服</name>
<url>http://172.16.46.63:30708/repository/test-snap/</url>
</snapshotRepository>
</distributionManagement>
<profiles>

View File

@@ -0,0 +1,7 @@
-- 为 API 客户端凭证表添加"是否启用加密"字段
-- 2026-01-14
ALTER TABLE databus_api_client_credential
ADD enable_encryption BIT DEFAULT '1' NOT NULL;
COMMENT ON COLUMN databus_api_client_credential.enable_encryption IS '是否启用加密传输';

View File

@@ -339,7 +339,8 @@ CREATE TABLE infra_file (
create_time datetime DEFAULT CURRENT_TIMESTAMP NOT NULL,
updater varchar(64) DEFAULT '' NULL,
update_time datetime DEFAULT CURRENT_TIMESTAMP NOT NULL,
deleted bit DEFAULT '0' NOT NULL
deleted bit DEFAULT '0' NOT NULL,
DOWNLOAD_COUNT INT DEFAULT 0 NOT NULL
);
COMMENT ON COLUMN infra_file.id IS '文件编号';
@@ -356,6 +357,7 @@ COMMENT ON COLUMN infra_file.create_time IS '创建时间';
COMMENT ON COLUMN infra_file.updater IS '更新者';
COMMENT ON COLUMN infra_file.update_time IS '更新时间';
COMMENT ON COLUMN infra_file.deleted IS '是否删除';
COMMENT ON COLUMN INFRA_FILE.DOWNLOAD_COUNT IS '下载次数';
COMMENT ON TABLE infra_file IS '文件表';
CREATE INDEX idx_infra_file_hash ON infra_file(hash);

View File

@@ -0,0 +1,5 @@
-- 添加文件下载次数统计字段
ALTER TABLE JYGK_TEST.INFRA_FILE
ADD DOWNLOAD_COUNT INT DEFAULT 0 NOT NULL;
COMMENT ON COLUMN JYGK_TEST.INFRA_FILE.DOWNLOAD_COUNT IS '下载次数';

View File

@@ -10,11 +10,11 @@
<name>中铜 ZStack 私服</name>
<url>http://172.16.46.63:30708/repository/test/</url>
</repository>
<!-- <snapshotRepository>-->
<!-- <id>ZT</id>-->
<!-- <name>中铜 ZStack 私服</name>-->
<!-- <url>https://your-nexus.example.com/repository/maven-snapshots/</url>-->
<!-- </snapshotRepository>-->
<snapshotRepository>
<id>ZT-snap</id>
<name>中铜 ZStack 私服</name>
<url>http://172.16.46.63:30708/repository/test-snap/</url>
</snapshotRepository>
</distributionManagement>
<groupId>com.zt.plat</groupId>
<artifactId>zt-dependencies</artifactId>
@@ -26,7 +26,7 @@
<url>https://github.com/YunaiV/ruoyi-vue-pro</url>
<properties>
<revision>3.0.46</revision>
<revision>3.0.47-SNAPSHOT</revision>
<flatten-maven-plugin.version>1.6.0</flatten-maven-plugin.version>
<!-- 统一依赖管理 -->
<spring.boot.version>3.4.5</spring.boot.version>

View File

@@ -25,7 +25,7 @@
<module>zt-spring-boot-starter-job</module>
<module>zt-spring-boot-starter-mq</module>
<module>zt-spring-boot-starter-rpc</module>
<module>zt-spring-boot-starter-seata-dm</module>
<module>zt-spring-boot-starter-excel</module>
<module>zt-spring-boot-starter-test</module>

View File

@@ -40,4 +40,11 @@ public interface PermissionCommonApi {
@Parameter(name = "userId", description = "用户编号", example = "2", required = true)
CommonResult<DeptDataPermissionRespDTO> getDeptDataPermission(@RequestParam("userId") Long userId);
@GetMapping(PREFIX + "/get-dept-data-permission-with-roleCodes")
@Operation(summary = "获得登陆用户的部门数据权限")
@Parameters({
@Parameter(name = "userId", description = "用户编号", example = "2", required = true),
@Parameter(name = "roleCodes", description = "角色编码", example = "2", required = true)
})
CommonResult<DeptDataPermissionRespDTO> getDeptDataPermissionWithRoleCodes(@RequestParam("userId") Long userId, @RequestParam("roleCodes") String roleCodes);
}

View File

@@ -33,7 +33,7 @@ public class BusinessDataPermissionEntityScanner {
*/
private static final Set<String> EXCLUDED_PACKAGE_PREFIXES = Set.of(
"com.zt.plat.module.backendlogistics",
"com.zt.plat.module.erp",
// "com.zt.plat.module.erp",
"com.zt.plat.framework.mybatis.core.dataobject.BusinessBaseDO");
private final Set<String> basePackages;

View File

@@ -2,6 +2,8 @@ package com.zt.plat.framework.tenant.core.context;
import com.alibaba.ttl.TransmittableThreadLocal;
import java.util.List;
/**
* 部门上下文 Holder使用 {@link TransmittableThreadLocal} 支持在线程池/异步场景下的上下文传递。
*
@@ -15,6 +17,8 @@ public class DeptContextHolder {
private static final ThreadLocal<Long> COMPANY_ID = new TransmittableThreadLocal<>();
/** 是否忽略部门数据权限 */
private static final ThreadLocal<Boolean> IGNORE = new TransmittableThreadLocal<>();
/** 角色编码列表 */
private static final ThreadLocal<List<String>> ROLE_CODE_LIST = new TransmittableThreadLocal<>();
public static Long getDeptId() {
return DEPT_ID.get();
@@ -32,6 +36,12 @@ public class DeptContextHolder {
COMPANY_ID.set(companyId);
}
public static void setContext(Long deptId, Long companyId, List<String> roleCodeList) {
DEPT_ID.set(deptId);
COMPANY_ID.set(companyId);
ROLE_CODE_LIST.set(roleCodeList);
}
public static void setDeptId(Long deptId) {
DEPT_ID.set(deptId);
}
@@ -53,9 +63,20 @@ public class DeptContextHolder {
return Boolean.TRUE.equals(IGNORE.get());
}
public static void setRoleCodeList(List<String> roleCodeList) {
ROLE_CODE_LIST.set(roleCodeList);
}
public static List<String> getRoleCodeList() {
return ROLE_CODE_LIST.get();
}
public static void clearRoleCodeList(){
ROLE_CODE_LIST.remove();
}
public static void clear() {
DEPT_ID.remove();
COMPANY_ID.remove();
IGNORE.remove();
ROLE_CODE_LIST.remove();
}
}

View File

@@ -42,6 +42,9 @@ public class ApiClientCredentialRespVO {
@Schema(description = "匿名访问固定用户昵称", example = "张三")
private String anonymousUserNickname;
@Schema(description = "是否启用加密", example = "true")
private Boolean enableEncryption;
@Schema(description = "创建时间")
private LocalDateTime createTime;

View File

@@ -45,4 +45,8 @@ public class ApiClientCredentialSaveReqVO {
@Schema(description = "匿名访问固定用户 ID", example = "1024")
private Long anonymousUserId;
@Schema(description = "是否启用加密", example = "true")
@NotNull(message = "启用加密标识不能为空")
private Boolean enableEncryption;
}

View File

@@ -38,4 +38,6 @@ public class ApiClientCredentialDO extends BaseDO {
private Long anonymousUserId;
private Boolean enableEncryption;
}

View File

@@ -238,6 +238,11 @@ public class GatewaySecurityFilter extends OncePerRequestFilter {
private byte[] decryptRequestBody(byte[] originalBody,
ApiClientCredentialDO credential,
ApiGatewayProperties.Security security) {
// 检查是否启用加密,如果未启用则直接返回原文
if (credential != null && Boolean.FALSE.equals(credential.getEnableEncryption())) {
return originalBody != null ? originalBody : new byte[0];
}
if (originalBody == null || originalBody.length == 0) {
return new byte[0];
}
@@ -390,6 +395,11 @@ public class GatewaySecurityFilter extends OncePerRequestFilter {
private void encryptResponse(ContentCachingResponseWrapper responseWrapper,
ApiClientCredentialDO credential,
ApiGatewayProperties.Security security) throws IOException {
// 检查是否启用加密,如果未启用则直接返回,不加密响应
if (credential != null && Boolean.FALSE.equals(credential.getEnableEncryption())) {
return;
}
if (!security.isEncryptResponse()) {
return;
}
@@ -524,6 +534,10 @@ public class GatewaySecurityFilter extends OncePerRequestFilter {
if (security == null || credential == null) {
return false;
}
// 检查是否启用加密,如果未启用则不加密错误响应
if (Boolean.FALSE.equals(credential.getEnableEncryption())) {
return false;
}
if (!security.isEncryptResponse()) {
return false;
}

View File

@@ -34,4 +34,7 @@ public class FileRespDTO {
@Schema(description = "文件内容", requiredMode = Schema.RequiredMode.REQUIRED)
private byte[] content;
@Schema(description = "文件下载次数")
private Integer downloadCount;
}

View File

@@ -133,6 +133,10 @@ public class FileController {
response.setStatus(HttpStatus.NOT_FOUND.value());
return;
}
// 统计下载次数
fileService.incDownloadCount(configId,path);
writeAttachment(response, path, content);
}

View File

@@ -99,4 +99,7 @@ public class FileRespVO {
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
private LocalDateTime createTime;
@Schema(description = "下载次数")
private Integer downloadCount;
}

View File

@@ -65,6 +65,11 @@ public class FileDO extends BaseDO {
*/
private String aesIv;
/**
* 文件下载次数统计
*/
private Integer downloadCount;
/**
* 是否加密
* <p>

View File

@@ -6,6 +6,8 @@ import com.zt.plat.framework.mybatis.core.query.LambdaQueryWrapperX;
import com.zt.plat.module.infra.controller.admin.file.vo.file.FilePageReqVO;
import com.zt.plat.module.infra.dal.dataobject.file.FileDO;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Update;
/**
* 文件操作 Mapper
@@ -32,4 +34,7 @@ public interface FileMapper extends BaseMapperX<FileDO> {
return selectFirstOne(FileDO::getHash, hash);
}
@Update("UPDATE INFRA_FILE SET DOWNLOAD_COUNT = DOWNLOAD_COUNT + 1 WHERE CONFIG_ID = #{configId} AND PATH = #{path}")
int incDownloadCount(@Param("configId") Long configId, @Param("path") String path);
}

View File

@@ -112,4 +112,11 @@ public interface FileService {
FileDO getActiveFileById(Long fileId);
boolean verifyCode(Long fileId, Long userId, String code) throws Exception;
/**
* 更新文件下载次数
* @param configId
* @param path
*/
void incDownloadCount(Long configId, String path);
}

View File

@@ -334,4 +334,9 @@ public class FileServiceImpl implements FileService {
}
}
@Override
public void incDownloadCount(Long configId, String path) {
fileMapper.incDownloadCount(configId, path);
}
}

View File

@@ -86,4 +86,8 @@ public class PermissionApiImpl implements PermissionApi {
return success(permissionService.getDeptDataPermission(userId));
}
@Override
public CommonResult<DeptDataPermissionRespDTO> getDeptDataPermissionWithRoleCodes(Long userId, String roleCodes) {
return success(permissionService.getDeptDataPermissionWithRoleCodes(userId, roleCodes));
}
}

View File

@@ -123,7 +123,7 @@ public class DeptController {
@GetMapping("/top-level-list")
@Operation(summary = "获取当前用户可访问的顶级部门列表", description = "用于懒加载,返回当前用户所属部门的最顶层祖先部门,如果用户没有关联任何部门则返回空列表")
@PreAuthorize("@ss.hasPermission('system:dept:query')")
// @PreAuthorize("@ss.hasPermission('system:dept:query')")
public CommonResult<List<DeptRespVO>> getTopLevelDeptList() {
List<DeptDO> list = deptService.getTopLevelDeptList();
return success(BeanUtils.toBean(list, DeptRespVO.class));

View File

@@ -143,6 +143,7 @@ public interface PermissionService {
* @return 部门数据权限
*/
DeptDataPermissionRespDTO getDeptDataPermission(Long userId);
DeptDataPermissionRespDTO getDeptDataPermissionWithRoleCodes(Long userId, String roleCodes);
/**
* 获得用户的数据权限级别

View File

@@ -3,6 +3,7 @@ package com.zt.plat.module.system.service.permission;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.extra.spring.SpringUtil;
import com.baomidou.dynamic.datasource.annotation.DSTransactional;
import com.google.common.annotations.VisibleForTesting;
@@ -12,6 +13,7 @@ import com.zt.plat.framework.common.biz.system.permission.dto.DeptDataPermission
import com.zt.plat.framework.common.enums.CommonStatusEnum;
import com.zt.plat.framework.common.util.collection.CollectionUtils;
import com.zt.plat.framework.datapermission.core.annotation.DataPermission;
import com.zt.plat.framework.tenant.core.context.DeptContextHolder;
import com.zt.plat.module.system.dal.dataobject.permission.MenuDO;
import com.zt.plat.module.system.dal.dataobject.permission.RoleDO;
import com.zt.plat.module.system.dal.dataobject.permission.RoleMenuDO;
@@ -347,6 +349,12 @@ public class PermissionServiceImpl implements PermissionService {
// 获得用户的角色
List<RoleDO> roles = getEnableUserRoleListByUserIdFromCache(userId);
//使用上下文角色编码过滤
List<String> contextRoleCodes = DeptContextHolder.getRoleCodeList();
if(!CollectionUtil.isEmpty(contextRoleCodes)){
roles = roles.stream().filter(role -> contextRoleCodes.contains(role.getCode())).collect(Collectors.toList());
}
// 获得用户的部门编号的缓存,通过 Guava 的 Suppliers 惰性求值,即有且仅有第一次发起 DB 的查询
Supplier<Set<Long>> userDeptIds = Suppliers.memoize(() -> {
List<UserDeptDO> validUserDeptListByUserId = userDeptService.getValidUserDeptListByUserIds(singleton(userId));
@@ -414,6 +422,26 @@ public class PermissionServiceImpl implements PermissionService {
return result;
}
@Override
public DeptDataPermissionRespDTO getDeptDataPermissionWithRoleCodes(Long userId, String roleCodes) {
// 获得用户的角色
List<RoleDO> roles = getEnableUserRoleListByUserIdFromCache(userId);
if(ObjectUtil.isEmpty(roleCodes))
return getDeptDataPermission(userId);
List<String> roleCodesList = Arrays.asList(roleCodes.split(","));
if(CollectionUtil.isEmpty(roles))
return getDeptDataPermission(userId);
DeptContextHolder.setRoleCodeList(roleCodesList);
try{
return getDeptDataPermission(userId);
}catch (Exception e){
log.error("getDeptDataPermission-- error ", e);
}finally {
DeptContextHolder.clearRoleCodeList();
}
return getDeptDataPermission(userId);
}
@Override
@DataPermission(enable = false)
@TenantIgnore

View File

@@ -43,11 +43,11 @@
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.zt.plat</groupId>
<artifactId>zt-module-template-server</artifactId>
<version>${revision}</version>
</dependency>
<!-- <dependency>-->
<!-- <groupId>com.zt.plat</groupId>-->
<!-- <artifactId>zt-module-template-server</artifactId>-->
<!-- <version>${revision}</version>-->
<!-- </dependency>-->
<!-- 会员中心。默认注释,保证编译速度 -->
<!-- <dependency>-->

View File

@@ -62,7 +62,8 @@ spring:
host: 172.16.46.63 # 地址
port: 30379 # 端口
database: 0 # 数据库索引
# password: 123456 # 密码,建议生产环境开启
password: P@ssword25
username: zt-redis
--- #################### 定时任务相关配置 ####################
@@ -76,7 +77,7 @@ xxl:
# rocketmq 配置项,对应 RocketMQProperties 配置类
rocketmq:
name-server: 127.0.0.1:9876 # RocketMQ Namesrv
name-server: 172.16.46.63:30876 # RocketMQ Namesrv
spring:
# RabbitMQ 配置项,对应 RabbitProperties 配置类