package tech.easyflow.starter; import com.mybatisflex.core.FlexGlobalConfig; import com.mybatisflex.core.audit.AuditMessage; import com.mybatisflex.core.audit.AuditManager; import com.mybatisflex.core.tenant.TenantManager; import com.mybatisflex.spring.boot.MyBatisFlexCustomizer; import cn.dev33.satoken.stp.StpUtil; import cn.dev33.satoken.exception.NotWebContextException; import cn.dev33.satoken.exception.SaTokenContextException; import cn.dev33.satoken.context.SaHolder; import java.math.BigInteger; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Configuration; import tech.easyflow.common.entity.LoginAccount; import tech.easyflow.common.satoken.util.SaTokenUtil; import tech.easyflow.job.execution.SysJobExecutionContextHolder; @Configuration public class MybatisConfig implements MyBatisFlexCustomizer { private static final Logger log = LoggerFactory.getLogger(MybatisConfig.class); private static final int MAX_AUDIT_SQL_LENGTH = 2_000; @Value("${easyflow.mybatis.sql-audit-enabled:false}") private boolean sqlAuditEnabled; @Override public void customize(FlexGlobalConfig flexGlobalConfig) { // SQL 审计会同步格式化并输出每条语句,高频 Worker 轮询下必须显式开启。 AuditManager.setAuditEnable(sqlAuditEnabled); // 统一使用标准 0/1 逻辑删除语义。 flexGlobalConfig.setNormalValueOfLogicDelete(0); flexGlobalConfig.setDeletedValueOfLogicDelete(1); // 登录请求自动追加 tenant_id 条件;系统任务没有登录上下文时保持显式全局语义。 TenantManager.setTenantFactory(this::currentTenantIds); //取消控制台的 Banner 打印 flexGlobalConfig.setPrintBanner(false); if (sqlAuditEnabled) { AuditManager.setMessageCollector(MybatisConfig::collectSafeAuditMessage); } } /** * 审计日志只记录参数化 SQL 模板,绝不读取 AuditMessage 中的参数或完整 SQL。 */ private static void collectSafeAuditMessage(AuditMessage message) { log.info("SQL audit: statement={}, elapsedMs={}, rows={}, sql={}", message.getStmtId(), message.getElapsedTime(), message.getQueryCount(), compactSqlTemplate(message.getQuery())); } private static String compactSqlTemplate(String sql) { if (sql == null) return null; String compact = sql.replaceAll("\\s+", " ").trim(); return compact.length() <= MAX_AUDIT_SQL_LENGTH ? compact : compact.substring(0, MAX_AUDIT_SQL_LENGTH) + "..."; } /** * 返回当前请求的租户条件;启动与调度线程没有 Sa-Token 上下文时保留系统级查询语义。 * * @return 当前租户 ID 数组;没有请求登录上下文时返回 {@code null} */ private Object[] currentTenantIds() { BigInteger scheduledTenantId = SysJobExecutionContextHolder.current() .map(context -> context.tenantId()) .orElse(null); if (scheduledTenantId != null) { return new Object[]{scheduledTenantId}; } try { if (!SaHolder.getContext().isValid() || !StpUtil.isLogin()) { return null; } LoginAccount account = SaTokenUtil.getLoginAccount(); BigInteger tenantId = account == null || account.getTenantId() == null ? BigInteger.ZERO : account.getTenantId(); return new Object[]{tenantId}; } catch (NotWebContextException | SaTokenContextException ignored) { // Bean 初始化与调度线程没有 Web 上下文,必须允许显式系统任务读取全局状态。 return null; } } }