1. 新增 api 调用日志记录,历史版本回滚

2. 新增用户角色权限监督功能
This commit is contained in:
chenbowen
2025-10-31 09:28:59 +08:00
parent b618f833d1
commit ddee4da72a
43 changed files with 2454 additions and 65 deletions

View File

@@ -205,8 +205,13 @@
<name>中铜 ZStack 私服</name> <name>中铜 ZStack 私服</name>
<url>http://172.16.46.63:30708/repository/test/</url> <url>http://172.16.46.63:30708/repository/test/</url>
<releases> <releases>
<enabled>true</enabled> <updatePolicy>always</updatePolicy>
<checksumPolicy>warn</checksumPolicy>
</releases> </releases>
<snapshots>
<enabled>true</enabled>
<updatePolicy>always</updatePolicy>
</snapshots>
</repository> </repository>
</repositories> </repositories>

View File

@@ -0,0 +1,33 @@
-- =============================================
-- 数据总线 API 版本历史菜单权限(备忘录模式)
-- 功能说明:
-- 1. 查看版本历史
-- 2. 查看版本详情
-- 3. 版本回滚
-- 4. 版本对比
-- =============================================
-- 删除旧的版本管理菜单权限
DELETE FROM system_menu WHERE id IN (650107, 650108, 650109, 650110);
-- 插入新的版本历史管理权限
INSERT INTO system_menu (id, name, permission, type, sort, parent_id, path, icon, component, component_name,
status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted)
VALUES
-- 查询版本历史列表
(650107, 'API版本历史', 'databus:gateway:version:query', 3, 7, 6501, '', '', '', '',
0, '1', '1', '1', 'admin', CURRENT_TIMESTAMP, 'admin', CURRENT_TIMESTAMP, '0'),
-- 查看版本详情
(650108, 'API版本详情', 'databus:gateway:version:detail', 3, 8, 6501, '', '', '', '',
0, '1', '1', '1', 'admin', CURRENT_TIMESTAMP, 'admin', CURRENT_TIMESTAMP, '0'),
-- 版本回滚
(650109, 'API版本回滚', 'databus:gateway:version:rollback', 3, 9, 6501, '', '', '', '',
0, '1', '1', '1', 'admin', CURRENT_TIMESTAMP, 'admin', CURRENT_TIMESTAMP, '0'),
-- 版本对比
(650110, 'API版本对比', 'databus:gateway:version:compare', 3, 10, 6501, '', '', '', '',
0, '1', '1', '1', 'admin', CURRENT_TIMESTAMP, 'admin', CURRENT_TIMESTAMP, '0');
-- 说明
-- 1. 不再需要"创建版本"权限,因为系统自动创建
-- 2. 不再需要"删除版本"权限,版本历史不可删除
-- 3. 保留查询、详情、回滚、对比四个核心功能

View File

@@ -0,0 +1,52 @@
-- =============================================
-- 数据总线 API 版本历史表(备忘录模式)
-- 功能说明:
-- 1. 每次保存 API 配置时自动创建版本记录
-- 2. 版本号自动递增v1, v2, v3...
-- 3. 保留完整历史链,不可删除
-- 4. 支持一键回滚到任意历史版本
-- 5. 支持版本对比功能
-- =============================================
-- 如果表已存在则删除
DROP TABLE IF EXISTS databus_api_version;
-- 创建版本历史表DM8 语法)
CREATE TABLE databus_api_version (
id BIGINT NOT NULL,
api_id BIGINT NOT NULL,
version_number INTEGER NOT NULL,
snapshot_data CLOB NOT NULL,
description VARCHAR(500),
is_current NUMBER(1) DEFAULT 0 NOT NULL,
operator VARCHAR(64),
creator VARCHAR(64) DEFAULT '' NOT NULL,
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
updater VARCHAR(64) DEFAULT '' NOT NULL,
update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
deleted NUMBER(1) DEFAULT 0 NOT NULL,
tenant_id BIGINT DEFAULT 0 NOT NULL,
CONSTRAINT pk_databus_api_version PRIMARY KEY (id)
);
-- 创建索引
CREATE INDEX idx_databus_api_version_api_id ON databus_api_version (api_id);
CREATE INDEX idx_databus_api_version_version_number ON databus_api_version (api_id, version_number);
CREATE INDEX idx_databus_api_version_is_current ON databus_api_version (api_id, is_current);
CREATE INDEX idx_databus_api_version_create_time ON databus_api_version (create_time);
CREATE INDEX idx_databus_api_version_operator ON databus_api_version (operator);
COMMENT ON TABLE databus_api_version IS '数据总线API版本历史表采用备忘录模式每次保存API时自动创建版本快照支持完整的版本历史追溯和回滚';
COMMENT ON COLUMN databus_api_version.id IS '主键ID';
COMMENT ON COLUMN databus_api_version.api_id IS 'API定义ID关联databus_api_definition表';
COMMENT ON COLUMN databus_api_version.version_number IS '版本号同一API下自动递增1,2,3...';
COMMENT ON COLUMN databus_api_version.snapshot_data IS 'API完整配置快照JSON格式包含definition、steps、transforms等所有信息';
COMMENT ON COLUMN databus_api_version.description IS '变更说明,记录本次修改的内容';
COMMENT ON COLUMN databus_api_version.is_current IS '是否为当前版本1=是0=否同一API只有一个当前版本';
COMMENT ON COLUMN databus_api_version.operator IS '操作人,记录谁创建了这个版本';
COMMENT ON COLUMN databus_api_version.creator IS '创建者';
COMMENT ON COLUMN databus_api_version.create_time IS '创建时间(版本创建时间)';
COMMENT ON COLUMN databus_api_version.updater IS '更新者';
COMMENT ON COLUMN databus_api_version.update_time IS '更新时间';
COMMENT ON COLUMN databus_api_version.deleted IS '是否删除(逻辑删除,实际不删除版本历史)';
COMMENT ON COLUMN databus_api_version.tenant_id IS '租户ID';

View File

@@ -0,0 +1,13 @@
-- 数据总线 API 访问日志菜单权限初始化DM8
-- 创建访问日志页面及查询按钮权限。如已存在将先行移除再新增。
DELETE FROM system_menu WHERE id IN (6504, 650401);
INSERT INTO system_menu (id, name, permission, type, sort, parent_id, path, icon, component, component_name,
status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted)
VALUES (6504, '访问日志', 'databus:gateway:access-log:query', 2, 40, 6500, 'access-log', 'ep:document', 'databus/accesslog/index', 'DatabusAccessLog',
0, '1', '1', '1', 'admin', CURRENT_TIMESTAMP, 'admin', CURRENT_TIMESTAMP, '0');
INSERT INTO system_menu (id, name, permission, type, sort, parent_id, path, icon, component, component_name,
status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted)
VALUES (650401, '访问日志查询', 'databus:gateway:access-log:query', 3, 1, 6504, '', '', '', '',
0, '1', '1', '1', 'admin', CURRENT_TIMESTAMP, 'admin', CURRENT_TIMESTAMP, '0');

View File

@@ -0,0 +1,52 @@
-- 权限监督按钮及接口权限DM8 专用)
-- 执行前请确认未占用 1068 主键
DELETE FROM system_menu WHERE id = 1068;
INSERT INTO system_menu (
id,
name,
permission,
type,
sort,
parent_id,
path,
icon,
component,
component_name,
status,
visible,
keep_alive,
always_show,
creator,
create_time,
updater,
update_time,
deleted
)
SELECT
1068,
'权限监督',
'system:permission:user-permission-supervision',
3,
9,
101,
'',
'',
'',
NULL,
0,
'1',
'1',
'1',
'admin',
'2025-10-29 00:00:00',
'',
'2025-10-29 00:00:00',
'0'
FROM dual
WHERE NOT EXISTS (
SELECT 1
FROM system_menu
WHERE id = 1068
);

View File

@@ -31,6 +31,11 @@
<artifactId>zt-spring-boot-starter-biz-data-permission</artifactId> <artifactId>zt-spring-boot-starter-biz-data-permission</artifactId>
<version>${revision}</version> <version>${revision}</version>
</dependency> </dependency>
<dependency>
<groupId>com.zt.plat</groupId>
<artifactId>zt-spring-boot-starter-biz-tenant</artifactId>
<version>${revision}</version>
</dependency>
<!-- Test 测试相关 --> <!-- Test 测试相关 -->
<dependency> <dependency>
<groupId>com.zt.plat</groupId> <groupId>com.zt.plat</groupId>

View File

@@ -20,30 +20,68 @@ public class CompanyVisitContextInterceptor implements HandlerInterceptor {
@Override @Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
// 解析 header 并设置 visitCompanyId LoginUser loginUser = SecurityFrameworkUtils.getLoginUser();
Long companyId = WebFrameworkUtils.getCompanyId(request); Long companyId = WebFrameworkUtils.getCompanyId(request);
// 优先使用请求头上的公司信息,若缺失则回退到请求属性或当前登录用户已缓存的访问公司
if (companyId == null || companyId <= 0L) {
Long attrCompanyId = resolveLong(request.getAttribute(WebFrameworkUtils.HEADER_VISIT_COMPANY_ID));
if (attrCompanyId != null && attrCompanyId > 0L) {
companyId = attrCompanyId;
} else if (loginUser != null && loginUser.getVisitCompanyId() != null && loginUser.getVisitCompanyId() > 0L) {
companyId = loginUser.getVisitCompanyId();
}
}
String companyName = WebFrameworkUtils.getCompanyName(request); String companyName = WebFrameworkUtils.getCompanyName(request);
if (companyId <= 0L) { if (companyName == null || companyName.isEmpty()) {
// 如果没有设置 companyId则忽略 Object attrCompanyName = request.getAttribute(WebFrameworkUtils.HEADER_VISIT_COMPANY_NAME);
if (attrCompanyName instanceof String) {
companyName = (String) attrCompanyName;
} else if (loginUser != null) {
companyName = loginUser.getVisitCompanyName();
}
}
Long deptId = WebFrameworkUtils.getDeptId(request);
// 部门信息同样遵循“请求头 -> 请求属性 -> 登录缓存”的回退顺序
if (deptId == null || deptId <= 0L) {
Long attrDeptId = resolveLong(request.getAttribute(WebFrameworkUtils.HEADER_VISIT_DEPT_ID));
if (attrDeptId != null && attrDeptId > 0L) {
deptId = attrDeptId;
} else if (loginUser != null && loginUser.getVisitDeptId() != null && loginUser.getVisitDeptId() > 0L) {
deptId = loginUser.getVisitDeptId();
}
}
String deptName = WebFrameworkUtils.getDeptName(request);
if (deptName == null || deptName.isEmpty()) {
Object attrDeptName = request.getAttribute(WebFrameworkUtils.HEADER_VISIT_DEPT_NAME);
if (attrDeptName instanceof String) {
deptName = (String) attrDeptName;
} else if (loginUser != null) {
deptName = loginUser.getVisitDeptName();
}
}
if (companyId == null || companyId <= 0L) {
CompanyContextHolder.setIgnore(true); CompanyContextHolder.setIgnore(true);
return true; return true;
} }
Long deptId = WebFrameworkUtils.getDeptId(request);
String deptName = WebFrameworkUtils.getDeptName(request); CompanyContextHolder.setIgnore(false);
LoginUser loginUser = SecurityFrameworkUtils.getLoginUser(); CompanyContextHolder.setCompanyId(companyId);
if (loginUser == null) { if (loginUser == null) {
return true; return true;
} }
if (deptId > 0L) {
// 同步最新的访问公司/部门到登录用户对象,供后续数据权限及上下文读取
loginUser.setVisitCompanyId(companyId);
loginUser.setVisitCompanyName(companyName);
if (deptId != null && deptId > 0L) {
loginUser.setVisitDeptId(deptId); loginUser.setVisitDeptId(deptId);
loginUser.setVisitDeptName(deptName); loginUser.setVisitDeptName(deptName);
} }
// if (!securityFrameworkService.hasAnyPermissions(PERMISSION)) {
// throw exception0(GlobalErrorCodeConstants.FORBIDDEN.getCode(), "您无权切换部门");
// }
loginUser.setVisitCompanyId(companyId);
loginUser.setVisitCompanyName(companyName);
CompanyContextHolder.setCompanyId(companyId);
return true; return true;
} }
@@ -55,4 +93,18 @@ public class CompanyVisitContextInterceptor implements HandlerInterceptor {
loginUser.setVisitCompanyId(0L); loginUser.setVisitCompanyId(0L);
} }
} }
private Long resolveLong(Object value) {
if (value instanceof Number) {
return ((Number) value).longValue();
}
if (value instanceof String) {
try {
return Long.parseLong(((String) value).trim());
} catch (NumberFormatException ignored) {
return null;
}
}
return null;
}
} }

View File

@@ -0,0 +1,52 @@
package com.zt.plat.module.databus.controller.admin.gateway;
import com.zt.plat.framework.common.pojo.CommonResult;
import com.zt.plat.framework.common.pojo.PageResult;
import com.zt.plat.module.databus.controller.admin.gateway.convert.ApiAccessLogConvert;
import com.zt.plat.module.databus.controller.admin.gateway.vo.accesslog.ApiAccessLogPageReqVO;
import com.zt.plat.module.databus.controller.admin.gateway.vo.accesslog.ApiAccessLogRespVO;
import com.zt.plat.module.databus.dal.dataobject.gateway.ApiAccessLogDO;
import com.zt.plat.module.databus.service.gateway.ApiAccessLogService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
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 static com.zt.plat.framework.common.pojo.CommonResult.success;
/**
* Databus API 访问日志控制器。
*/
@Tag(name = "管理后台 - Databus API 访问日志")
@RestController
@RequestMapping("/databus/gateway/access-log")
@Validated
public class ApiAccessLogController {
@Resource
private ApiAccessLogService apiAccessLogService;
@GetMapping("/get")
@Operation(summary = "获取访问日志详情")
@Parameter(name = "id", description = "日志编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('databus:gateway:access-log:query')")
public CommonResult<ApiAccessLogRespVO> get(@RequestParam("id") Long id) {
ApiAccessLogDO logDO = apiAccessLogService.get(id);
return success(ApiAccessLogConvert.INSTANCE.convert(logDO));
}
@GetMapping("/page")
@Operation(summary = "分页查询访问日志")
@PreAuthorize("@ss.hasPermission('databus:gateway:access-log:query')")
public CommonResult<PageResult<ApiAccessLogRespVO>> page(@Valid ApiAccessLogPageReqVO pageReqVO) {
PageResult<ApiAccessLogDO> pageResult = apiAccessLogService.getPage(pageReqVO);
return success(ApiAccessLogConvert.INSTANCE.convertPage(pageResult));
}
}

Some files were not shown because too many files have changed in this diff Show More