Merge branch 'main' of http://27.223.88.102:33000/chenx/qhmes
This commit is contained in:
@@ -1035,6 +1035,22 @@ jeecgboot-vue3/src/views/xslmes/mesXslEquipPartMapping/MesXslEquipPartMappingLis
|
||||
jeecgboot-vue3/src/views/xslmes/mesXslEquipPartMapping/MesXslEquipPartMapping.data.ts
|
||||
jeecgboot-vue3/src/views/xslmes/mesXslEquipPartMapping/MesXslEquipPartMapping.api.ts
|
||||
|
||||
-- author:GHT---date:20260616--for: 【MES上辅机】SQL Server中间库可视化配置(第三方配置页+读写开关+热刷新数据源) ---
|
||||
jeecg-boot/jeecg-module-system/jeecg-system-start/src/main/resources/flyway/sql/mysql/V3.9.2_150__mes_xsl_mcs_db_config.sql
|
||||
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/mcs/entity/MesXslMcsDbConfig.java
|
||||
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/mcs/mapper/MesXslMcsDbConfigMapper.java
|
||||
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/mcs/service/IMesXslMcsDbConfigService.java
|
||||
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/mcs/service/impl/MesXslMcsDbConfigServiceImpl.java
|
||||
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/mcs/datasource/McsDataSourceManager.java
|
||||
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/mcs/datasource/McsDataSourceGuardAspect.java
|
||||
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/mcs/config/McsDataSourceInitializer.java
|
||||
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/mcs/controller/MesXslMcsDbConfigController.java
|
||||
jeecgboot-vue3/src/views/system/appconfig/ThirdAppConfigList.vue
|
||||
jeecgboot-vue3/src/views/system/appconfig/ThirdAppMcsDbConfigForm.vue
|
||||
jeecgboot-vue3/src/views/system/appconfig/McsDbConfigModal.vue
|
||||
jeecgboot-vue3/src/views/system/appconfig/McsDbConfig.data.ts
|
||||
jeecgboot-vue3/src/views/system/appconfig/McsDbConfig.api.ts
|
||||
|
||||
-- author:jiangxh---date:20250603--for: 【MES】设备/质量管理主数据删除前引用校验(统一点检配置等下游阻断) ---
|
||||
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/service/IMesXslDeleteReferenceService.java
|
||||
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/service/impl/MesXslDeleteReferenceServiceImpl.java
|
||||
|
||||
@@ -133,24 +133,18 @@ public class ApprovalCallbackDispatcher {
|
||||
DING_LOG_TAG, callbackName, ctx.getAction(), ctx.getBizTable(), ctx.getBizDataId());
|
||||
}
|
||||
try {
|
||||
switch (ctx.getAction()) {
|
||||
case NODE_APPROVED:
|
||||
cb.onNodeApproved(ctx);
|
||||
break;
|
||||
case APPROVED:
|
||||
cb.onApproved(ctx);
|
||||
break;
|
||||
case REJECTED:
|
||||
cb.onRejected(ctx);
|
||||
break;
|
||||
//update-begin---author:GHT ---date:2026-06-08 for:【风险修复-R5】分发撤销回调-----------
|
||||
case CANCELLED:
|
||||
cb.onCancelled(ctx);
|
||||
break;
|
||||
//update-end---author:GHT ---date:2026-06-08 for:【风险修复-R5】分发撤销回调-----------
|
||||
default:
|
||||
break;
|
||||
//update-begin---author:cursor ---date:20260615 for:【XSLMES-20260615-A05】审批回调分发改用if-else,避免调试热更新缺失switch合成类-----------
|
||||
ApprovalCallbackContext.Action action = ctx.getAction();
|
||||
if (action == ApprovalCallbackContext.Action.NODE_APPROVED) {
|
||||
cb.onNodeApproved(ctx);
|
||||
} else if (action == ApprovalCallbackContext.Action.APPROVED) {
|
||||
cb.onApproved(ctx);
|
||||
} else if (action == ApprovalCallbackContext.Action.REJECTED) {
|
||||
cb.onRejected(ctx);
|
||||
} else if (action == ApprovalCallbackContext.Action.CANCELLED) {
|
||||
cb.onCancelled(ctx);
|
||||
}
|
||||
//update-end---author:cursor ---date:20260615 for:【XSLMES-20260615-A05】审批回调分发改用if-else,避免调试热更新缺失switch合成类-----------
|
||||
if (dingTalk) {
|
||||
log.info("{} 业务回调完成 {} action={} bizTable={} bizDataId={}",
|
||||
DING_LOG_TAG, callbackName, ctx.getAction(), ctx.getBizTable(), ctx.getBizDataId());
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
package org.jeecg.modules.xslmes.mcs.config;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.jeecg.modules.xslmes.mcs.datasource.McsDataSourceManager;
|
||||
import org.jeecg.modules.xslmes.mcs.entity.MesXslMcsDbConfig;
|
||||
import org.jeecg.modules.xslmes.mcs.service.IMesXslMcsDbConfigService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.ApplicationArguments;
|
||||
import org.springframework.boot.ApplicationRunner;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* 启动时加载中间库配置,覆盖 application.yml 中的 sqlserver_mcs 连接
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@Order(100)
|
||||
public class McsDataSourceInitializer implements ApplicationRunner {
|
||||
|
||||
@Autowired
|
||||
private IMesXslMcsDbConfigService mcsDbConfigService;
|
||||
|
||||
@Autowired
|
||||
private McsDataSourceManager mcsDataSourceManager;
|
||||
|
||||
//update-begin---author:GHT ---date:20260616 for:【MES上辅机】启动时加载中间库可视化配置-----------
|
||||
@Override
|
||||
public void run(ApplicationArguments args) {
|
||||
try {
|
||||
MesXslMcsDbConfig config = mcsDbConfigService.getOne(
|
||||
new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<MesXslMcsDbConfig>()
|
||||
.eq(MesXslMcsDbConfig::getTenantId, 0)
|
||||
.orderByDesc(MesXslMcsDbConfig::getUpdateTime)
|
||||
.last("LIMIT 1"),
|
||||
false);
|
||||
if (config != null) {
|
||||
mcsDataSourceManager.applyConfig(config);
|
||||
log.info("[MCS中间库] 已加载数据库中的中间库配置(含读写开关)");
|
||||
} else {
|
||||
log.info("[MCS中间库] 未找到已启用的数据库配置,继续使用 application.yml 中的 sqlserver_mcs");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warn("[MCS中间库] 启动加载配置失败,将使用 yml 默认连接: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
//update-end---author:GHT ---date:20260616 for:【MES上辅机】启动时加载中间库可视化配置-----------
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package org.jeecg.modules.xslmes.mcs.config;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.jeecg.common.exception.JeecgBootException;
|
||||
import org.jeecg.modules.xslmes.mcs.datasource.McsDataSourceManager;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.servlet.HandlerInterceptor;
|
||||
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
|
||||
/**
|
||||
* 注册 MCS 中间库 HTTP 拦截器(读取/写入开关在接口层生效)
|
||||
*/
|
||||
@Configuration
|
||||
public class McsWebMvcConfig implements WebMvcConfigurer {
|
||||
|
||||
@Autowired
|
||||
private McsDataSourceManager mcsDataSourceManager;
|
||||
|
||||
@Override
|
||||
public void addInterceptors(InterceptorRegistry registry) {
|
||||
registry.addInterceptor(mcsReadWriteInterceptor())
|
||||
.addPathPatterns("/xslmes/mcs/**")
|
||||
.excludePathPatterns("/xslmes/mcs/dbConfig/**");
|
||||
}
|
||||
|
||||
//update-begin---author:GHT ---date:20260616 for:【MES上辅机】HTTP层拦截中间库读写请求-----------
|
||||
private HandlerInterceptor mcsReadWriteInterceptor() {
|
||||
return new HandlerInterceptor() {
|
||||
@Override
|
||||
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
|
||||
if (!mcsDataSourceManager.hasSavedConfig()) {
|
||||
return true;
|
||||
}
|
||||
String uri = request.getRequestURI();
|
||||
if (uri.contains("/mcs/dbConfig")) {
|
||||
return true;
|
||||
}
|
||||
if ("OPTIONS".equalsIgnoreCase(request.getMethod())) {
|
||||
return true;
|
||||
}
|
||||
if (!mcsDataSourceManager.isReadEnabled()) {
|
||||
throw new JeecgBootException("中间库读取已关闭,请在「中间库连接配置」中开启读取开关并保存");
|
||||
}
|
||||
if (isWriteRequest(request, uri) && !mcsDataSourceManager.isWriteEnabled()) {
|
||||
throw new JeecgBootException("中间库写入已关闭,请在「中间库连接配置」中开启写入开关并保存");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean isWriteRequest(HttpServletRequest request, String uri) {
|
||||
String method = request.getMethod();
|
||||
boolean mutating = "POST".equalsIgnoreCase(method)
|
||||
|| "PUT".equalsIgnoreCase(method)
|
||||
|| "DELETE".equalsIgnoreCase(method)
|
||||
|| "PATCH".equalsIgnoreCase(method);
|
||||
return mutating && uri.toLowerCase().contains("mestomcs");
|
||||
}
|
||||
};
|
||||
}
|
||||
//update-end---author:GHT ---date:20260616 for:【MES上辅机】HTTP层拦截中间库读写请求-----------
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
package org.jeecg.modules.xslmes.mcs.controller;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import org.jeecg.common.api.vo.Result;
|
||||
import org.jeecg.common.config.TenantContext;
|
||||
import org.jeecg.common.util.oConvertUtils;
|
||||
import org.jeecg.config.mybatis.MybatisPlusSaasConfig;
|
||||
import org.jeecg.modules.xslmes.mcs.datasource.McsDataSourceManager;
|
||||
import org.jeecg.modules.xslmes.mcs.entity.MesXslMcsDbConfig;
|
||||
import org.jeecg.modules.xslmes.mcs.service.IMesXslMcsDbConfigService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* MES上辅机 SQL Server 中间库连接配置
|
||||
*/
|
||||
@Tag(name = "MES上辅机中间库配置")
|
||||
@RestController
|
||||
@RequestMapping("/xslmes/mcs/dbConfig")
|
||||
public class MesXslMcsDbConfigController {
|
||||
|
||||
@Autowired
|
||||
private IMesXslMcsDbConfigService mcsDbConfigService;
|
||||
|
||||
@Autowired
|
||||
private McsDataSourceManager mcsDataSourceManager;
|
||||
|
||||
private Integer resolveTenantId(Integer tenantId) {
|
||||
if (MybatisPlusSaasConfig.OPEN_SYSTEM_TENANT_CONTROL) {
|
||||
if (tenantId == null) {
|
||||
tenantId = oConvertUtils.getInt(TenantContext.getTenant(), -1);
|
||||
}
|
||||
} else if (tenantId == null) {
|
||||
tenantId = oConvertUtils.getInt(TenantContext.getTenant(), 0);
|
||||
}
|
||||
return tenantId;
|
||||
}
|
||||
|
||||
@Operation(summary = "获取中间库配置")
|
||||
@GetMapping("/get")
|
||||
public Result<MesXslMcsDbConfig> getConfig(@RequestParam(name = "tenantId", required = false) Integer tenantId) {
|
||||
return Result.OK(mcsDbConfigService.getCurrentConfig(resolveTenantId(tenantId)));
|
||||
}
|
||||
|
||||
@Operation(summary = "保存中间库配置")
|
||||
@PostMapping("/save")
|
||||
public Result<String> saveConfig(@RequestBody MesXslMcsDbConfig config) {
|
||||
if (config.getTenantId() == null) {
|
||||
config.setTenantId(resolveTenantId(null));
|
||||
}
|
||||
return mcsDbConfigService.saveOrUpdateConfig(config);
|
||||
}
|
||||
|
||||
@Operation(summary = "测试中间库连接")
|
||||
@GetMapping("/testConnect")
|
||||
public Result<String> testConnect(@RequestParam(name = "tenantId", required = false) Integer tenantId) {
|
||||
return mcsDbConfigService.testConnection(resolveTenantId(tenantId));
|
||||
}
|
||||
|
||||
@Operation(summary = "删除中间库配置")
|
||||
@DeleteMapping("/delete")
|
||||
public Result<String> deleteConfig(@RequestParam(name = "id") String id) {
|
||||
return mcsDbConfigService.deleteConfig(id);
|
||||
}
|
||||
|
||||
@Operation(summary = "获取中间库连接状态")
|
||||
@GetMapping("/status")
|
||||
public Result<Map<String, Object>> status() {
|
||||
Map<String, Object> data = new LinkedHashMap<>();
|
||||
MesXslMcsDbConfig cfg = mcsDataSourceManager.getCachedConfig();
|
||||
data.put("dbConfigActive", mcsDataSourceManager.isDbConfigActive());
|
||||
data.put("readEnabled", mcsDataSourceManager.isReadEnabled());
|
||||
data.put("writeEnabled", mcsDataSourceManager.isWriteEnabled());
|
||||
if (cfg != null) {
|
||||
data.put("serverHost", cfg.getServerHost());
|
||||
data.put("serverPort", cfg.getServerPort());
|
||||
data.put("dbName", cfg.getDbName());
|
||||
}
|
||||
return Result.OK(data);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package org.jeecg.modules.xslmes.mcs.datasource;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.aspectj.lang.JoinPoint;
|
||||
import org.aspectj.lang.annotation.Aspect;
|
||||
import org.aspectj.lang.annotation.Before;
|
||||
import org.jeecg.common.exception.JeecgBootException;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* MCS 中间库读写开关守卫
|
||||
* <ul>
|
||||
* <li>读取开关:拦截所有中间表查询(McsToMes + MesToMcs 列表/详情等)</li>
|
||||
* <li>写入开关:仅拦截 MES→MCS 方向的增删改</li>
|
||||
* </ul>
|
||||
*/
|
||||
@Slf4j
|
||||
@Aspect
|
||||
@Component
|
||||
@Order(1)
|
||||
public class McsDataSourceGuardAspect {
|
||||
|
||||
@Autowired
|
||||
private McsDataSourceManager mcsDataSourceManager;
|
||||
|
||||
//update-begin---author:GHT ---date:20260616 for:【MES上辅机】读取开关拦截全部中间表查询-----------
|
||||
@Before("(execution(* com.baomidou.mybatisplus.extension.service.IService+.*(..)) "
|
||||
+ "&& (target(org.jeecg.modules.xslmes.mcs.service.impl.McsToMes*) "
|
||||
+ "|| target(org.jeecg.modules.xslmes.mcs.service.impl.MesToMcs*)))")
|
||||
public void guardRead(JoinPoint joinPoint) {
|
||||
if (!mcsDataSourceManager.isReadEnabled()) {
|
||||
throw new JeecgBootException("中间库读取已关闭,请在「中间库连接配置」中开启读取开关并保存");
|
||||
}
|
||||
}
|
||||
|
||||
@Before("execution(* org.jeecg.modules.xslmes.mcs.service.impl.MesToMcs*.save*(..)) || "
|
||||
+ "execution(* org.jeecg.modules.xslmes.mcs.service.impl.MesToMcs*.update*(..)) || "
|
||||
+ "execution(* org.jeecg.modules.xslmes.mcs.service.impl.MesToMcs*.remove*(..)) || "
|
||||
+ "execution(* org.jeecg.modules.xslmes.mcs.service.impl.MesToMcs*.delete*(..))")
|
||||
public void guardWrite(JoinPoint joinPoint) {
|
||||
if (!mcsDataSourceManager.isWriteEnabled()) {
|
||||
throw new JeecgBootException("中间库写入已关闭,请在「中间库连接配置」中开启写入开关并保存");
|
||||
}
|
||||
}
|
||||
//update-end---author:GHT ---date:20260616 for:【MES上辅机】读取开关拦截全部中间表查询-----------
|
||||
}
|
||||
@@ -0,0 +1,229 @@
|
||||
package org.jeecg.modules.xslmes.mcs.datasource;
|
||||
|
||||
import com.alibaba.druid.pool.DruidDataSource;
|
||||
import com.baomidou.dynamic.datasource.DynamicRoutingDataSource;
|
||||
import com.baomidou.dynamic.datasource.creator.DataSourceProperty;
|
||||
import com.baomidou.dynamic.datasource.creator.druid.DruidDataSourceCreator;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.jeecg.common.util.dynamic.db.DataSourceCachePool;
|
||||
import org.jeecg.modules.system.util.SecurityUtil;
|
||||
import org.jeecg.modules.xslmes.mcs.entity.MesXslMcsDbConfig;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
import java.sql.Connection;
|
||||
import java.sql.DriverManager;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
/**
|
||||
* MCS SQL Server 动态数据源管理器
|
||||
* <p>配置保存后热刷新 sqlserver_mcs 数据源,无需改 application.yml</p>
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class McsDataSourceManager {
|
||||
|
||||
public static final String DS_KEY = "sqlserver_mcs";
|
||||
|
||||
private final AtomicReference<MesXslMcsDbConfig> cachedConfig = new AtomicReference<>();
|
||||
|
||||
@Autowired
|
||||
private DataSource dataSource;
|
||||
|
||||
@Autowired
|
||||
private DruidDataSourceCreator dataSourceCreator;
|
||||
|
||||
@Value("${spring.datasource.dynamic.datasource.sqlserver_mcs.url:}")
|
||||
private String ymlUrl;
|
||||
|
||||
@Value("${spring.datasource.dynamic.datasource.sqlserver_mcs.username:}")
|
||||
private String ymlUsername;
|
||||
|
||||
@Value("${spring.datasource.dynamic.datasource.sqlserver_mcs.password:}")
|
||||
private String ymlPassword;
|
||||
|
||||
@Value("${spring.datasource.dynamic.datasource.sqlserver_mcs.driver-class-name:com.microsoft.sqlserver.jdbc.SQLServerDriver}")
|
||||
private String ymlDriverClassName;
|
||||
|
||||
public MesXslMcsDbConfig getCachedConfig() {
|
||||
return cachedConfig.get();
|
||||
}
|
||||
|
||||
public boolean hasSavedConfig() {
|
||||
return cachedConfig.get() != null;
|
||||
}
|
||||
|
||||
public boolean isDbConfigActive() {
|
||||
MesXslMcsDbConfig cfg = cachedConfig.get();
|
||||
return cfg != null && Integer.valueOf(1).equals(cfg.getStatus());
|
||||
}
|
||||
|
||||
public boolean isReadEnabled() {
|
||||
MesXslMcsDbConfig cfg = cachedConfig.get();
|
||||
if (cfg == null) {
|
||||
return true;
|
||||
}
|
||||
return Integer.valueOf(1).equals(cfg.getReadEnabled());
|
||||
}
|
||||
|
||||
public boolean isWriteEnabled() {
|
||||
MesXslMcsDbConfig cfg = cachedConfig.get();
|
||||
if (cfg == null) {
|
||||
return true;
|
||||
}
|
||||
return Integer.valueOf(1).equals(cfg.getWriteEnabled());
|
||||
}
|
||||
|
||||
//update-begin---author:GHT ---date:20260616 for:【MES上辅机】SQL Server中间库可视化配置-----------
|
||||
/**
|
||||
* 将配置应用到动态数据源(启用时注册/更新,禁用时移除)
|
||||
*/
|
||||
public void applyConfig(MesXslMcsDbConfig config) {
|
||||
cachedConfig.set(config);
|
||||
DynamicRoutingDataSource routing = (DynamicRoutingDataSource) dataSource;
|
||||
closeAndRemoveDataSource(routing);
|
||||
DataSourceCachePool.removeCache(DS_KEY);
|
||||
if (config == null) {
|
||||
restoreYmlDataSource(routing);
|
||||
log.info("[MCS中间库] 已清除页面配置");
|
||||
return;
|
||||
}
|
||||
if (Integer.valueOf(1).equals(config.getStatus())) {
|
||||
try {
|
||||
String plainPassword = decryptPassword(config.getDbPassword());
|
||||
DataSourceProperty property = buildDataSourceProperty(config, plainPassword);
|
||||
DataSource mcsDs = dataSourceCreator.createDataSource(property);
|
||||
routing.addDataSource(DS_KEY, mcsDs);
|
||||
log.info("[MCS中间库] 动态数据源 {} 已热刷新(无需重启): {}:{}/{}", DS_KEY,
|
||||
config.getServerHost(), config.getServerPort(), config.getDbName());
|
||||
} catch (Exception e) {
|
||||
cachedConfig.set(config);
|
||||
log.error("[MCS中间库] 刷新动态数据源失败", e);
|
||||
throw new RuntimeException("刷新中间库数据源失败:" + e.getMessage(), e);
|
||||
}
|
||||
return;
|
||||
}
|
||||
restoreYmlDataSource(routing);
|
||||
log.info("[MCS中间库] 连接未启用,读写开关仍按页面配置生效");
|
||||
}
|
||||
|
||||
private void closeAndRemoveDataSource(DynamicRoutingDataSource routing) {
|
||||
if (!routing.getDataSources().containsKey(DS_KEY)) {
|
||||
return;
|
||||
}
|
||||
DataSource old = routing.getDataSource(DS_KEY);
|
||||
routing.removeDataSource(DS_KEY);
|
||||
closeDataSourceQuietly(old);
|
||||
}
|
||||
|
||||
private void closeDataSourceQuietly(DataSource ds) {
|
||||
if (ds instanceof DruidDataSource druid && druid.isEnable()) {
|
||||
try {
|
||||
druid.close();
|
||||
} catch (Exception e) {
|
||||
log.warn("[MCS中间库] 关闭旧连接池失败: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试 JDBC 连通性(不写入动态数据源)
|
||||
*/
|
||||
public String testConnection(MesXslMcsDbConfig config, String plainPassword) {
|
||||
if (config == null) {
|
||||
return "配置为空";
|
||||
}
|
||||
String jdbcUrl = buildJdbcUrl(config);
|
||||
try {
|
||||
Class.forName("com.microsoft.sqlserver.jdbc.SQLServerDriver");
|
||||
try (Connection conn = DriverManager.getConnection(jdbcUrl, config.getDbUsername(), plainPassword)) {
|
||||
if (conn.isValid(5)) {
|
||||
return null;
|
||||
}
|
||||
return "连接无效";
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warn("[MCS中间库] 连接测试失败: {}", e.getMessage());
|
||||
return e.getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
public String buildJdbcUrl(MesXslMcsDbConfig config) {
|
||||
int loginTimeout = config.getLoginTimeout() != null ? config.getLoginTimeout() : 120;
|
||||
int connectTimeout = config.getConnectTimeout() != null ? config.getConnectTimeout() : 120000;
|
||||
String host = resolveHost(config);
|
||||
int port = config.getServerPort() != null ? config.getServerPort() : 1433;
|
||||
String dbName = StringUtils.isNotBlank(config.getDbName()) ? config.getDbName() : "MES_ShareDB";
|
||||
return "jdbc:sqlserver://" + host + ":" + port
|
||||
+ ";DatabaseName=" + dbName
|
||||
+ ";encrypt=false;trustServerCertificate=true;SelectMethod=cursor"
|
||||
+ ";loginTimeout=" + loginTimeout
|
||||
+ ";connectTimeout=" + connectTimeout + ";";
|
||||
}
|
||||
|
||||
private DataSourceProperty buildDataSourceProperty(MesXslMcsDbConfig config, String plainPassword) {
|
||||
DataSourceProperty property = new DataSourceProperty();
|
||||
property.setPoolName(DS_KEY);
|
||||
property.setDriverClassName("com.microsoft.sqlserver.jdbc.SQLServerDriver");
|
||||
property.setUrl(buildJdbcUrl(config));
|
||||
property.setUsername(config.getDbUsername());
|
||||
property.setPassword(plainPassword);
|
||||
return property;
|
||||
}
|
||||
|
||||
private void restoreYmlDataSource(DynamicRoutingDataSource routing) {
|
||||
if (StringUtils.isBlank(ymlUrl)) {
|
||||
log.warn("[MCS中间库] yml 中未配置 sqlserver_mcs,无法恢复默认数据源");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
DataSourceProperty property = new DataSourceProperty();
|
||||
property.setPoolName(DS_KEY);
|
||||
property.setDriverClassName(ymlDriverClassName);
|
||||
property.setUrl(ymlUrl);
|
||||
property.setUsername(ymlUsername);
|
||||
property.setPassword(ymlPassword);
|
||||
routing.addDataSource(DS_KEY, dataSourceCreator.createDataSource(property));
|
||||
} catch (Exception e) {
|
||||
log.error("[MCS中间库] 恢复 yml 默认数据源失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
private String resolveHost(MesXslMcsDbConfig config) {
|
||||
String host = config.getServerHost() == null ? "" : config.getServerHost().trim();
|
||||
if (host.contains(":")) {
|
||||
String[] parts = host.split(":", 2);
|
||||
host = parts[0];
|
||||
if (config.getServerPort() == null || config.getServerPort() == 1433) {
|
||||
try {
|
||||
config.setServerPort(Integer.parseInt(parts[1]));
|
||||
} catch (NumberFormatException ignored) {
|
||||
// 端口解析失败时保留原端口
|
||||
}
|
||||
}
|
||||
}
|
||||
return host;
|
||||
}
|
||||
|
||||
public String decryptPassword(String encrypted) {
|
||||
if (StringUtils.isBlank(encrypted)) {
|
||||
return "";
|
||||
}
|
||||
try {
|
||||
return SecurityUtil.jiemi(encrypted);
|
||||
} catch (Exception e) {
|
||||
return encrypted;
|
||||
}
|
||||
}
|
||||
|
||||
public String encryptPassword(String plain) {
|
||||
if (StringUtils.isBlank(plain)) {
|
||||
return plain;
|
||||
}
|
||||
return SecurityUtil.jiami(plain);
|
||||
}
|
||||
//update-end---author:GHT ---date:20260616 for:【MES上辅机】SQL Server中间库可视化配置-----------
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
package org.jeecg.modules.xslmes.mcs.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.experimental.Accessors;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* MES上辅机 SQL Server 中间库配置
|
||||
*/
|
||||
@Data
|
||||
@TableName("mes_xsl_mcs_db_config")
|
||||
@Accessors(chain = true)
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
@Schema(description = "MES上辅机SQL Server中间库配置")
|
||||
public class MesXslMcsDbConfig implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@TableId(type = IdType.ASSIGN_ID)
|
||||
@Schema(description = "主键")
|
||||
private String id;
|
||||
|
||||
@Schema(description = "租户ID")
|
||||
private Integer tenantId;
|
||||
|
||||
@Schema(description = "服务器地址")
|
||||
private String serverHost;
|
||||
|
||||
@Schema(description = "端口")
|
||||
private Integer serverPort;
|
||||
|
||||
@Schema(description = "数据库名")
|
||||
private String dbName;
|
||||
|
||||
@Schema(description = "用户名")
|
||||
private String dbUsername;
|
||||
|
||||
@JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
|
||||
@Schema(description = "密码")
|
||||
private String dbPassword;
|
||||
|
||||
@Schema(description = "读取开关(0-关,1-开)")
|
||||
private Integer readEnabled;
|
||||
|
||||
@Schema(description = "写入开关(0-关,1-开)")
|
||||
private Integer writeEnabled;
|
||||
|
||||
@Schema(description = "启用状态(0-关,1-开)")
|
||||
private Integer status;
|
||||
|
||||
@Schema(description = "登录超时(秒)")
|
||||
private Integer loginTimeout;
|
||||
|
||||
@Schema(description = "连接超时(毫秒)")
|
||||
private Integer connectTimeout;
|
||||
|
||||
@Schema(description = "备注")
|
||||
private String remark;
|
||||
|
||||
private String createBy;
|
||||
|
||||
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date createTime;
|
||||
|
||||
private String updateBy;
|
||||
|
||||
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date updateTime;
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package org.jeecg.modules.xslmes.mcs.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.jeecg.modules.xslmes.mcs.entity.MesXslMcsDbConfig;
|
||||
|
||||
/**
|
||||
* MES上辅机 SQL Server 中间库配置 Mapper
|
||||
*/
|
||||
public interface MesXslMcsDbConfigMapper extends BaseMapper<MesXslMcsDbConfig> {
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package org.jeecg.modules.xslmes.mcs.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import org.jeecg.common.api.vo.Result;
|
||||
import org.jeecg.modules.xslmes.mcs.entity.MesXslMcsDbConfig;
|
||||
|
||||
/**
|
||||
* MES上辅机 SQL Server 中间库配置 Service
|
||||
*/
|
||||
public interface IMesXslMcsDbConfigService extends IService<MesXslMcsDbConfig> {
|
||||
|
||||
/**
|
||||
* 获取当前租户配置(无则返回 null)
|
||||
*/
|
||||
MesXslMcsDbConfig getCurrentConfig(Integer tenantId);
|
||||
|
||||
/**
|
||||
* 保存或更新配置并刷新动态数据源
|
||||
*/
|
||||
Result<String> saveOrUpdateConfig(MesXslMcsDbConfig config);
|
||||
|
||||
/**
|
||||
* 测试数据库连接
|
||||
*/
|
||||
Result<String> testConnection(Integer tenantId);
|
||||
|
||||
/**
|
||||
* 删除配置并移除动态数据源
|
||||
*/
|
||||
Result<String> deleteConfig(String id);
|
||||
}
|
||||
@@ -0,0 +1,186 @@
|
||||
package org.jeecg.modules.xslmes.mcs.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.apache.shiro.SecurityUtils;
|
||||
import org.jeecg.common.api.vo.Result;
|
||||
import org.jeecg.common.system.vo.LoginUser;
|
||||
import org.jeecg.modules.xslmes.mcs.datasource.McsDataSourceManager;
|
||||
import org.jeecg.modules.xslmes.mcs.entity.MesXslMcsDbConfig;
|
||||
import org.jeecg.modules.xslmes.mcs.mapper.MesXslMcsDbConfigMapper;
|
||||
import org.jeecg.modules.xslmes.mcs.service.IMesXslMcsDbConfigService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.transaction.support.TransactionSynchronization;
|
||||
import org.springframework.transaction.support.TransactionSynchronizationManager;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* MES上辅机 SQL Server 中间库配置 Service 实现
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
public class MesXslMcsDbConfigServiceImpl extends ServiceImpl<MesXslMcsDbConfigMapper, MesXslMcsDbConfig>
|
||||
implements IMesXslMcsDbConfigService {
|
||||
|
||||
@Autowired
|
||||
private McsDataSourceManager mcsDataSourceManager;
|
||||
|
||||
@Override
|
||||
public MesXslMcsDbConfig getCurrentConfig(Integer tenantId) {
|
||||
int tid = tenantId != null ? tenantId : 0;
|
||||
LambdaQueryWrapper<MesXslMcsDbConfig> qw = new LambdaQueryWrapper<>();
|
||||
qw.eq(MesXslMcsDbConfig::getTenantId, tid);
|
||||
qw.orderByDesc(MesXslMcsDbConfig::getUpdateTime);
|
||||
qw.last("LIMIT 1");
|
||||
MesXslMcsDbConfig config = getOne(qw, false);
|
||||
if (config != null) {
|
||||
config.setDbPassword(null);
|
||||
}
|
||||
return config;
|
||||
}
|
||||
|
||||
//update-begin---author:GHT ---date:20260616 for:【MES上辅机】SQL Server中间库可视化配置-----------
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Result<String> saveOrUpdateConfig(MesXslMcsDbConfig config) {
|
||||
if (config == null) {
|
||||
return Result.error("配置不能为空");
|
||||
}
|
||||
if (StringUtils.isBlank(config.getServerHost())) {
|
||||
return Result.error("服务器地址不能为空");
|
||||
}
|
||||
if (StringUtils.isBlank(config.getDbUsername())) {
|
||||
return Result.error("用户名不能为空");
|
||||
}
|
||||
if (config.getTenantId() == null) {
|
||||
config.setTenantId(0);
|
||||
}
|
||||
if (config.getServerPort() == null) {
|
||||
config.setServerPort(1433);
|
||||
}
|
||||
if (StringUtils.isBlank(config.getDbName())) {
|
||||
config.setDbName("MES_ShareDB");
|
||||
}
|
||||
if (config.getReadEnabled() == null) {
|
||||
config.setReadEnabled(1);
|
||||
}
|
||||
if (config.getWriteEnabled() == null) {
|
||||
config.setWriteEnabled(1);
|
||||
}
|
||||
if (config.getStatus() == null) {
|
||||
config.setStatus(0);
|
||||
}
|
||||
if (config.getLoginTimeout() == null) {
|
||||
config.setLoginTimeout(120);
|
||||
}
|
||||
if (config.getConnectTimeout() == null) {
|
||||
config.setConnectTimeout(120000);
|
||||
}
|
||||
|
||||
String plainPassword = resolvePlainPassword(config);
|
||||
if (StringUtils.isBlank(plainPassword)) {
|
||||
return Result.error("密码不能为空");
|
||||
}
|
||||
|
||||
String err = mcsDataSourceManager.testConnection(config, plainPassword);
|
||||
if (err != null && Integer.valueOf(1).equals(config.getStatus())) {
|
||||
return Result.error("连接测试失败:" + err);
|
||||
}
|
||||
|
||||
LoginUser user = (LoginUser) SecurityUtils.getSubject().getPrincipal();
|
||||
String username = user != null ? user.getUsername() : "system";
|
||||
Date now = new Date();
|
||||
|
||||
if (StringUtils.isNotBlank(config.getId())) {
|
||||
MesXslMcsDbConfig old = getById(config.getId());
|
||||
if (old == null) {
|
||||
return Result.error("配置不存在");
|
||||
}
|
||||
config.setDbPassword(mcsDataSourceManager.encryptPassword(plainPassword));
|
||||
config.setUpdateBy(username);
|
||||
config.setUpdateTime(now);
|
||||
updateById(config);
|
||||
} else {
|
||||
config.setDbPassword(mcsDataSourceManager.encryptPassword(plainPassword));
|
||||
config.setCreateBy(username);
|
||||
config.setCreateTime(now);
|
||||
config.setUpdateBy(username);
|
||||
config.setUpdateTime(now);
|
||||
save(config);
|
||||
}
|
||||
|
||||
MesXslMcsDbConfig applied = getById(config.getId());
|
||||
scheduleApplyAfterCommit(applied);
|
||||
return Result.OK("保存成功,中间库连接已热刷新,无需重启后端");
|
||||
}
|
||||
|
||||
/**
|
||||
* 事务提交后再刷新数据源,避免库未落盘就切换连接
|
||||
*/
|
||||
private void scheduleApplyAfterCommit(MesXslMcsDbConfig applied) {
|
||||
if (TransactionSynchronizationManager.isSynchronizationActive()) {
|
||||
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
|
||||
@Override
|
||||
public void afterCommit() {
|
||||
mcsDataSourceManager.applyConfig(applied);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
mcsDataSourceManager.applyConfig(applied);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<String> testConnection(Integer tenantId) {
|
||||
MesXslMcsDbConfig config = getOneByTenant(tenantId);
|
||||
if (config == null) {
|
||||
return Result.error("尚未配置中间库连接");
|
||||
}
|
||||
String plainPassword = mcsDataSourceManager.decryptPassword(config.getDbPassword());
|
||||
String err = mcsDataSourceManager.testConnection(config, plainPassword);
|
||||
if (err == null) {
|
||||
return Result.OK("连接成功");
|
||||
}
|
||||
return Result.error("连接失败:" + err);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Result<String> deleteConfig(String id) {
|
||||
MesXslMcsDbConfig config = getById(id);
|
||||
if (config == null) {
|
||||
return Result.error("配置不存在");
|
||||
}
|
||||
removeById(id);
|
||||
mcsDataSourceManager.applyConfig(null);
|
||||
return Result.OK("已删除配置,中间库连接已断开(无需重启后端)");
|
||||
}
|
||||
|
||||
private MesXslMcsDbConfig getOneByTenant(Integer tenantId) {
|
||||
int tid = tenantId != null ? tenantId : 0;
|
||||
LambdaQueryWrapper<MesXslMcsDbConfig> qw = new LambdaQueryWrapper<>();
|
||||
qw.eq(MesXslMcsDbConfig::getTenantId, tid);
|
||||
qw.orderByDesc(MesXslMcsDbConfig::getUpdateTime);
|
||||
qw.last("LIMIT 1");
|
||||
return getOne(qw, false);
|
||||
}
|
||||
|
||||
private String resolvePlainPassword(MesXslMcsDbConfig config) {
|
||||
if (StringUtils.isNotBlank(config.getDbPassword())) {
|
||||
return config.getDbPassword();
|
||||
}
|
||||
if (StringUtils.isNotBlank(config.getId())) {
|
||||
MesXslMcsDbConfig old = getById(config.getId());
|
||||
if (old != null) {
|
||||
return mcsDataSourceManager.decryptPassword(old.getDbPassword());
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
//update-end---author:GHT ---date:20260616 for:【MES上辅机】SQL Server中间库可视化配置-----------
|
||||
}
|
||||
@@ -167,21 +167,22 @@ spring:
|
||||
username: root
|
||||
password: 123456
|
||||
driver-class-name: com.mysql.cj.jdbc.Driver
|
||||
# 中间库连接已改为「第三方配置 → 上辅机中间库」页面维护,此处保留示例供参考
|
||||
#update-begin---author:geh ---date:2026-06-02 for:【MES上辅机】新增 SQL Server 中间表数据源(MES_ShareDB)-----------
|
||||
sqlserver_mcs:
|
||||
driver-class-name: com.microsoft.sqlserver.jdbc.SQLServerDriver
|
||||
# loginTimeout/connectTimeout:花生壳映射不稳定时延长 TCP/登录等待(单位:秒 / 毫秒)
|
||||
url: jdbc:sqlserver://1lo04860wn636.vicp.fun:31601;DatabaseName=MES_ShareDB;encrypt=false;trustServerCertificate=true;SelectMethod=cursor;loginTimeout=120;connectTimeout=120000;
|
||||
username: sa
|
||||
password: 123456
|
||||
druid:
|
||||
initial-size: 0
|
||||
min-idle: 0
|
||||
max-wait: 120000
|
||||
connect-timeout: 120000
|
||||
connection-error-retry-attempts: 10
|
||||
time-between-connect-error-millis: 3000
|
||||
break-after-acquire-failure: false
|
||||
# sqlserver_mcs:
|
||||
# driver-class-name: com.microsoft.sqlserver.jdbc.SQLServerDriver
|
||||
# # loginTimeout/connectTimeout:花生壳映射不稳定时延长 TCP/登录等待(单位:秒 / 毫秒)
|
||||
# url: jdbc:sqlserver://1lo04860wn636.vicp.fun:31601;DatabaseName=MES_ShareDB;encrypt=false;trustServerCertificate=true;SelectMethod=cursor;loginTimeout=120;connectTimeout=120000;
|
||||
# username: sa
|
||||
# password: 123456
|
||||
# druid:
|
||||
# initial-size: 0
|
||||
# min-idle: 0
|
||||
# max-wait: 120000
|
||||
# connect-timeout: 120000
|
||||
# connection-error-retry-attempts: 10
|
||||
# time-between-connect-error-millis: 3000
|
||||
# break-after-acquire-failure: false
|
||||
#update-end---author:geh ---date:2026-06-02 for:【MES上辅机】新增 SQL Server 中间表数据源(MES_ShareDB)-----------
|
||||
# # shardingjdbc数据源
|
||||
# sharding-db:
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
-- 【MES上辅机】SQL Server 中间库可视化配置(替代 application.yml 手工改连接)
|
||||
CREATE TABLE IF NOT EXISTS `mes_xsl_mcs_db_config` (
|
||||
`id` varchar(36) NOT NULL COMMENT '主键',
|
||||
`tenant_id` int DEFAULT 0 COMMENT '租户ID',
|
||||
`server_host` varchar(200) NOT NULL COMMENT '服务器地址(IP或域名,可含端口)',
|
||||
`server_port` int DEFAULT 1433 COMMENT '端口',
|
||||
`db_name` varchar(100) NOT NULL DEFAULT 'MES_ShareDB' COMMENT '数据库名',
|
||||
`db_username` varchar(100) NOT NULL COMMENT '用户名',
|
||||
`db_password` varchar(500) DEFAULT NULL COMMENT '密码(AES加密)',
|
||||
`read_enabled` tinyint(1) DEFAULT 1 COMMENT '读取开关(0-关,1-开) MCS→MES方向',
|
||||
`write_enabled` tinyint(1) DEFAULT 1 COMMENT '写入开关(0-关,1-开) MES→MCS方向',
|
||||
`status` tinyint(1) DEFAULT 0 COMMENT '启用状态(0-关,1-开)',
|
||||
`login_timeout` int DEFAULT 120 COMMENT '登录超时(秒)',
|
||||
`connect_timeout` int DEFAULT 120000 COMMENT '连接超时(毫秒)',
|
||||
`remark` varchar(500) DEFAULT NULL COMMENT '备注',
|
||||
`create_by` varchar(50) DEFAULT NULL COMMENT '创建人',
|
||||
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
|
||||
`update_by` varchar(50) DEFAULT NULL COMMENT '更新人',
|
||||
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_mcs_db_tenant` (`tenant_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='MES上辅机SQL Server中间库配置';
|
||||
|
||||
-- 菜单:MES上辅机数据 -> 中间库连接配置
|
||||
INSERT INTO `sys_permission` (`id`, `parent_id`, `name`, `url`, `component`, `is_route`, `component_name`, `redirect`, `menu_type`, `perms`, `perms_type`, `sort_no`, `always_show`, `icon`, `is_leaf`, `keep_alive`, `hidden`, `hide_tab`, `description`, `create_by`, `create_time`, `update_by`, `update_time`, `del_flag`, `rule_flag`, `status`, `internal_or_external`)
|
||||
SELECT '1900000000000000848', '1900000000000000830', '中间库连接配置', '/third/app?tab=mcs', 'system/appconfig/ThirdAppConfigList', 1, NULL, NULL, 1, NULL, '0', 0.50, 0, 'ant-design:database-outlined', 1, 1, 0, 0, 'SQL Server中间库连接与读写开关', 'admin', NOW(), 'admin', NOW(), 0, 0, '1', 0
|
||||
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM `sys_permission` WHERE `id` = '1900000000000000848');
|
||||
|
||||
INSERT INTO `sys_role_permission` (`id`, `role_id`, `permission_id`, `data_rule_ids`, `operate_date`, `operate_ip`)
|
||||
SELECT REPLACE(UUID(), '-', ''), r.id, '1900000000000000848', NULL, NOW(), '127.0.0.1'
|
||||
FROM `sys_role` r
|
||||
WHERE r.`role_code` = 'admin'
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM `sys_role_permission` rp
|
||||
WHERE rp.`role_id` = r.id AND rp.`permission_id` = '1900000000000000848'
|
||||
);
|
||||
19
jeecgboot-vue3/src/views/system/appconfig/McsDbConfig.api.ts
Normal file
19
jeecgboot-vue3/src/views/system/appconfig/McsDbConfig.api.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { defHttp } from '/@/utils/http/axios';
|
||||
|
||||
enum Api {
|
||||
getConfig = '/xslmes/mcs/dbConfig/get',
|
||||
saveConfig = '/xslmes/mcs/dbConfig/save',
|
||||
testConnect = '/xslmes/mcs/dbConfig/testConnect',
|
||||
deleteConfig = '/xslmes/mcs/dbConfig/delete',
|
||||
status = '/xslmes/mcs/dbConfig/status',
|
||||
}
|
||||
|
||||
export const getMcsDbConfig = (params?) => defHttp.get({ url: Api.getConfig, params });
|
||||
|
||||
export const saveMcsDbConfig = (params) => defHttp.post({ url: Api.saveConfig, params });
|
||||
|
||||
export const testMcsDbConnect = (params?) => defHttp.get({ url: Api.testConnect, params }, { isTransformResponse: false });
|
||||
|
||||
export const deleteMcsDbConfig = (params) => defHttp.delete({ url: Api.deleteConfig, params }, { joinParamsToUrl: true });
|
||||
|
||||
export const getMcsDbStatus = () => defHttp.get({ url: Api.status });
|
||||
100
jeecgboot-vue3/src/views/system/appconfig/McsDbConfig.data.ts
Normal file
100
jeecgboot-vue3/src/views/system/appconfig/McsDbConfig.data.ts
Normal file
@@ -0,0 +1,100 @@
|
||||
import { FormSchema } from '/@/components/Form';
|
||||
|
||||
export const mcsDbConfigFormSchema: FormSchema[] = [
|
||||
{ label: 'id', field: 'id', component: 'Input', show: false },
|
||||
{ label: 'tenantId', field: 'tenantId', component: 'InputNumber', show: false },
|
||||
{
|
||||
label: '服务器地址',
|
||||
field: 'serverHost',
|
||||
component: 'Input',
|
||||
required: true,
|
||||
componentProps: { placeholder: 'IP 或域名,例如 192.168.1.10 或 xxx.vicp.fun' },
|
||||
},
|
||||
{
|
||||
label: '端口',
|
||||
field: 'serverPort',
|
||||
component: 'InputNumber',
|
||||
defaultValue: 1433,
|
||||
required: true,
|
||||
componentProps: { min: 1, max: 65535, style: { width: '100%' } },
|
||||
},
|
||||
{
|
||||
label: '数据库名',
|
||||
field: 'dbName',
|
||||
component: 'Input',
|
||||
defaultValue: 'MES_ShareDB',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
label: '用户名',
|
||||
field: 'dbUsername',
|
||||
component: 'Input',
|
||||
required: true,
|
||||
componentProps: { placeholder: '例如 sa' },
|
||||
},
|
||||
{
|
||||
label: '密码',
|
||||
field: 'dbPassword',
|
||||
component: 'InputPassword',
|
||||
componentProps: { placeholder: '编辑时留空表示不修改密码' },
|
||||
},
|
||||
{
|
||||
label: '读取开关',
|
||||
field: 'readEnabled',
|
||||
component: 'Switch',
|
||||
helpMessage: '关闭后所有中间表列表/详情查询将被拦截(含 MCS→MES 与 MES→MCS 方向)',
|
||||
componentProps: {
|
||||
checkedChildren: '开启',
|
||||
checkedValue: 1,
|
||||
unCheckedChildren: '关闭',
|
||||
unCheckedValue: 0,
|
||||
},
|
||||
defaultValue: 1,
|
||||
},
|
||||
{
|
||||
label: '写入开关',
|
||||
field: 'writeEnabled',
|
||||
component: 'Switch',
|
||||
helpMessage: '关闭后 MES→MCS 方向的增删改将被拦截(不影响列表查询,查询由读取开关控制)',
|
||||
componentProps: {
|
||||
checkedChildren: '开启',
|
||||
checkedValue: 1,
|
||||
unCheckedChildren: '关闭',
|
||||
unCheckedValue: 0,
|
||||
},
|
||||
defaultValue: 1,
|
||||
},
|
||||
{
|
||||
label: '启用连接',
|
||||
field: 'status',
|
||||
component: 'Switch',
|
||||
helpMessage: '开启后立即连接中间库并热刷新数据源,无需重启后端',
|
||||
componentProps: {
|
||||
checkedChildren: '启用',
|
||||
checkedValue: 1,
|
||||
unCheckedChildren: '停用',
|
||||
unCheckedValue: 0,
|
||||
},
|
||||
defaultValue: 0,
|
||||
},
|
||||
{
|
||||
label: '登录超时(秒)',
|
||||
field: 'loginTimeout',
|
||||
component: 'InputNumber',
|
||||
defaultValue: 120,
|
||||
componentProps: { min: 10, max: 600, style: { width: '100%' } },
|
||||
},
|
||||
{
|
||||
label: '连接超时(毫秒)',
|
||||
field: 'connectTimeout',
|
||||
component: 'InputNumber',
|
||||
defaultValue: 120000,
|
||||
componentProps: { min: 5000, max: 600000, style: { width: '100%' } },
|
||||
},
|
||||
{
|
||||
label: '备注',
|
||||
field: 'remark',
|
||||
component: 'InputTextArea',
|
||||
componentProps: { rows: 2 },
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,62 @@
|
||||
<template>
|
||||
<BasicModal @register="registerModal" :width="720" title="上辅机中间库配置" @ok="handleSubmit">
|
||||
<BasicForm @register="registerForm" />
|
||||
</BasicModal>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import { BasicModal, useModalInner } from '/@/components/Modal';
|
||||
import { useForm, BasicForm } from '/@/components/Form';
|
||||
import { mcsDbConfigFormSchema } from './McsDbConfig.data';
|
||||
import { getMcsDbConfig, saveMcsDbConfig } from './McsDbConfig.api';
|
||||
import { getTenantId } from '/@/utils/auth';
|
||||
import { useMessage } from '/@/hooks/web/useMessage';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'McsDbConfigModal',
|
||||
components: { BasicModal, BasicForm },
|
||||
emits: ['success'],
|
||||
setup(_props, { emit }) {
|
||||
const { createMessage } = useMessage();
|
||||
const [registerForm, { resetFields, setFieldsValue, validate }] = useForm({
|
||||
schemas: mcsDbConfigFormSchema,
|
||||
showActionButtonGroup: false,
|
||||
labelCol: { span: 24 },
|
||||
wrapperCol: { span: 24 },
|
||||
});
|
||||
|
||||
const [registerModal, { setModalProps, closeModal }] = useModalInner(async () => {
|
||||
setModalProps({ confirmLoading: true });
|
||||
await resetFields();
|
||||
const tenantId = getTenantId();
|
||||
const values = await getMcsDbConfig({ tenantId });
|
||||
setModalProps({ confirmLoading: false });
|
||||
if (values) {
|
||||
await setFieldsValue({ ...values, dbPassword: '' });
|
||||
} else {
|
||||
await setFieldsValue({ tenantId, readEnabled: 1, writeEnabled: 1, status: 0, serverPort: 1433, dbName: 'MES_ShareDB' });
|
||||
}
|
||||
});
|
||||
|
||||
async function handleSubmit() {
|
||||
const values = await validate();
|
||||
if (!values.id && !values.dbPassword) {
|
||||
createMessage.warning('请输入数据库密码');
|
||||
return;
|
||||
}
|
||||
setModalProps({ confirmLoading: true });
|
||||
try {
|
||||
await saveMcsDbConfig(values);
|
||||
createMessage.success('保存成功,已热刷新连接,无需重启后端');
|
||||
emit('success');
|
||||
closeModal();
|
||||
} finally {
|
||||
setModalProps({ confirmLoading: false });
|
||||
}
|
||||
}
|
||||
|
||||
return { registerForm, registerModal, handleSubmit };
|
||||
},
|
||||
});
|
||||
</script>
|
||||
@@ -5,6 +5,7 @@
|
||||
<li :class="activeKey === 'ding' ? 'active' : ''" @click="dingLiClick('ding')"><a>钉钉集成</a></li>
|
||||
<li :class="activeKey === 'wechat' ? 'active' : ''" @click="dingLiClick('wechat')"><a>企业微信集成</a></li>
|
||||
<li :class="activeKey === 'kingdee' ? 'active' : ''" @click="dingLiClick('kingdee')"><a>金蝶集成</a></li>
|
||||
<li :class="activeKey === 'mcs' ? 'active' : ''" @click="dingLiClick('mcs')"><a>上辅机中间库</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div v-show="activeKey === 'ding'" class="base-collapse">
|
||||
@@ -16,14 +17,19 @@
|
||||
<div v-show="activeKey === 'kingdee'" class="base-collapse">
|
||||
<ThirdAppKingdeeConfigForm />
|
||||
</div>
|
||||
<div v-show="activeKey === 'mcs'" class="base-collapse">
|
||||
<ThirdAppMcsDbConfigForm />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref } from 'vue';
|
||||
import { defineComponent, onMounted, ref } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
import ThirdAppDingTalkConfigForm from './ThirdAppDingTalkConfigForm.vue';
|
||||
import ThirdAppWeEnterpriseConfigForm from './ThirdAppWeEnterpriseConfigForm.vue';
|
||||
import ThirdAppKingdeeConfigForm from './ThirdAppKingdeeConfigForm.vue';
|
||||
import ThirdAppMcsDbConfigForm from './ThirdAppMcsDbConfigForm.vue';
|
||||
import { useDesign } from '/@/hooks/web/useDesign';
|
||||
|
||||
export default defineComponent({
|
||||
@@ -32,13 +38,22 @@
|
||||
ThirdAppDingTalkConfigForm,
|
||||
ThirdAppWeEnterpriseConfigForm,
|
||||
ThirdAppKingdeeConfigForm,
|
||||
ThirdAppMcsDbConfigForm,
|
||||
},
|
||||
setup() {
|
||||
const { prefixCls } = useDesign('j-dd-container');
|
||||
const route = useRoute();
|
||||
|
||||
//选中的key
|
||||
const activeKey = ref<string>('ding');
|
||||
|
||||
onMounted(() => {
|
||||
const tab = route.query.tab as string;
|
||||
if (tab === 'mcs') {
|
||||
activeKey.value = 'mcs';
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* tab点击事件
|
||||
* @param key
|
||||
|
||||
@@ -0,0 +1,208 @@
|
||||
<template>
|
||||
<div class="base-collapse">
|
||||
<div class="header"> 上辅机中间库 </div>
|
||||
<a-collapse expand-icon-position="right" :bordered="false">
|
||||
<a-collapse-panel key="1">
|
||||
<template #header>
|
||||
<div style="font-size: 16px"> 1.配置说明</div>
|
||||
</template>
|
||||
<div class="base-desc">
|
||||
在此配置 SQL Server 中间库(MES_ShareDB)连接信息。<strong>保存后立即生效,无需重启后端</strong>,也无需修改 application.yml。
|
||||
读取开关控制 MCS→MES 方向数据查询;写入开关控制 MES→MCS 方向数据写入。
|
||||
</div>
|
||||
</a-collapse-panel>
|
||||
</a-collapse>
|
||||
<div class="sync-padding">
|
||||
<a-collapse expand-icon-position="right" :bordered="false">
|
||||
<a-collapse-panel key="2">
|
||||
<template #header>
|
||||
<div style="width: 100%; justify-content: space-between; display: flex">
|
||||
<div style="font-size: 16px"> 2.连接信息及开关</div>
|
||||
</div>
|
||||
</template>
|
||||
<a-alert v-if="connStatus" type="info" show-icon style="margin-bottom: 12px" message="当前运行状态" :description="connStatus" />
|
||||
<div class="flex-flow">
|
||||
<div class="base-title">服务器</div>
|
||||
<div class="base-message">
|
||||
<a-input :value="displayHost" readonly />
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-flow">
|
||||
<div class="base-title">数据库</div>
|
||||
<div class="base-message">
|
||||
<a-input :value="configData.dbName || '-'" readonly />
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-flow">
|
||||
<div class="base-title">用户名</div>
|
||||
<div class="base-message">
|
||||
<a-input :value="configData.dbUsername || '-'" readonly />
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-flow">
|
||||
<div class="base-title">读取开关</div>
|
||||
<div class="base-message" style="display: flex; align-items: center; height: 50px">
|
||||
<a-tag :color="runtimeStatus.readEnabled ? 'green' : 'default'">{{ runtimeStatus.readEnabled ? '已开启' : '已关闭' }}</a-tag>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-flow">
|
||||
<div class="base-title">写入开关</div>
|
||||
<div class="base-message" style="display: flex; align-items: center; height: 50px">
|
||||
<a-tag :color="runtimeStatus.writeEnabled ? 'green' : 'default'">{{ runtimeStatus.writeEnabled ? '已开启' : '已关闭' }}</a-tag>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-flow">
|
||||
<div class="base-title">连接状态</div>
|
||||
<div class="base-message" style="display: flex; align-items: center; height: 50px">
|
||||
<a-tag :color="runtimeStatus.dbConfigActive ? 'green' : 'orange'">{{ runtimeStatus.dbConfigActive ? '已启用数据库配置' : '使用 yml 默认配置' }}</a-tag>
|
||||
</div>
|
||||
</div>
|
||||
<div style="margin-top: 20px; width: 100%; text-align: right">
|
||||
<a-button type="primary" @click="editClick">编辑配置</a-button>
|
||||
<a-button @click="testClick" style="margin-left: 10px">连接测试</a-button>
|
||||
<a-button v-if="configData.id" @click="deleteClick" danger style="margin-left: 10px">删除配置</a-button>
|
||||
</div>
|
||||
</a-collapse-panel>
|
||||
</a-collapse>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<McsDbConfigModal @register="registerModal" @success="reload" />
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, onMounted, ref } from 'vue';
|
||||
import { Modal } from 'ant-design-vue';
|
||||
import { useModal } from '/@/components/Modal';
|
||||
import { useMessage } from '/@/hooks/web/useMessage';
|
||||
import { getTenantId } from '/@/utils/auth';
|
||||
import McsDbConfigModal from './McsDbConfigModal.vue';
|
||||
import { deleteMcsDbConfig, getMcsDbConfig, getMcsDbStatus, testMcsDbConnect } from './McsDbConfig.api';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'ThirdAppMcsDbConfigForm',
|
||||
components: { McsDbConfigModal },
|
||||
setup() {
|
||||
const { createMessage } = useMessage();
|
||||
const configData = ref<any>({});
|
||||
const runtimeStatus = ref<any>({ dbConfigActive: false, readEnabled: true, writeEnabled: true });
|
||||
const [registerModal, { openModal }] = useModal();
|
||||
|
||||
const displayHost = computed(() => {
|
||||
if (!configData.value.serverHost) return '-';
|
||||
const port = configData.value.serverPort || 1433;
|
||||
return `${configData.value.serverHost}:${port}`;
|
||||
});
|
||||
|
||||
const connStatus = computed(() => {
|
||||
const s = runtimeStatus.value;
|
||||
if (!configData.value.id) {
|
||||
return '尚未保存数据库配置,当前使用 application.yml 中的 sqlserver_mcs 默认连接。';
|
||||
}
|
||||
return `读取:${s.readEnabled ? '开' : '关'} | 写入:${s.writeEnabled ? '开' : '关'} | 连接:${s.dbConfigActive ? '已启用数据库配置' : '使用 yml 默认配置'}`;
|
||||
});
|
||||
|
||||
async function reload() {
|
||||
const tenantId = getTenantId();
|
||||
configData.value = (await getMcsDbConfig({ tenantId })) || {};
|
||||
runtimeStatus.value = (await getMcsDbStatus()) || {};
|
||||
}
|
||||
|
||||
function editClick() {
|
||||
openModal(true, {});
|
||||
}
|
||||
|
||||
async function testClick() {
|
||||
const tenantId = getTenantId();
|
||||
const res = await testMcsDbConnect({ tenantId });
|
||||
if (res.success) {
|
||||
createMessage.success(res.message || '连接成功');
|
||||
} else {
|
||||
createMessage.warning(res.message || '连接失败');
|
||||
}
|
||||
}
|
||||
|
||||
function deleteClick() {
|
||||
Modal.confirm({
|
||||
title: '删除配置',
|
||||
content: '删除后将断开数据库中的中间库配置,系统回退使用 application.yml 默认连接。确认删除?',
|
||||
okText: '确认',
|
||||
cancelText: '取消',
|
||||
onOk: async () => {
|
||||
await deleteMcsDbConfig({ id: configData.value.id });
|
||||
createMessage.success('已删除');
|
||||
await reload();
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
onMounted(reload);
|
||||
|
||||
return {
|
||||
configData,
|
||||
runtimeStatus,
|
||||
displayHost,
|
||||
connStatus,
|
||||
registerModal,
|
||||
editClick,
|
||||
testClick,
|
||||
deleteClick,
|
||||
reload,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.header {
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
height: 50px;
|
||||
justify-content: space-between;
|
||||
font-weight: 700;
|
||||
font-size: 18px;
|
||||
color: @text-color;
|
||||
}
|
||||
|
||||
.flex-flow {
|
||||
display: flex;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.sync-padding {
|
||||
padding: 12px 0 16px;
|
||||
color: @text-color;
|
||||
}
|
||||
|
||||
.base-collapse {
|
||||
margin-top: 20px;
|
||||
padding: 0 24px;
|
||||
font-size: 20px;
|
||||
|
||||
.base-desc {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.base-title {
|
||||
width: 100px;
|
||||
text-align: left;
|
||||
height: 50px;
|
||||
line-height: 50px;
|
||||
}
|
||||
|
||||
.base-message {
|
||||
width: 100%;
|
||||
height: 50px;
|
||||
line-height: 50px;
|
||||
}
|
||||
|
||||
:deep(.ant-collapse-header) {
|
||||
padding: 12px 0 16px;
|
||||
}
|
||||
|
||||
:deep(.ant-collapse-content-box) {
|
||||
padding-left: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user