u
This commit is contained in:
1 parent
422d206bd6
commit
a4bd92264d
108 files changed
+16862
-35
No files matched your search
+40
-10
@@ -13,6 +13,7 @@ import java.util.Properties;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
public class DataSourceManager extends AbstractRoutingDataSource {
|
||||
|
||||
private static final String CONFIG_PATH = "dbconfigs/";
|
||||
private static final Map<String, DataSource> dataSources = new ConcurrentHashMap<>();
|
||||
|
||||
@@ -26,10 +27,16 @@ public class DataSourceManager extends AbstractRoutingDataSource {
|
||||
return DataSourceContextHolder.getDataSourceKey();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取连接(带异常保护)
|
||||
*/
|
||||
public static Connection getConnection(String orgCode) throws Exception {
|
||||
DataSource dataSource = getOrCreateDataSource(orgCode);
|
||||
|
||||
return dataSource.getConnection();
|
||||
try {
|
||||
DataSource dataSource = getOrCreateDataSource(orgCode);
|
||||
return dataSource.getConnection();
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("获取数据库连接失败: " + orgCode, e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -49,9 +56,21 @@ public class DataSourceManager extends AbstractRoutingDataSource {
|
||||
return ds;
|
||||
}
|
||||
|
||||
// 创建数据源
|
||||
ds = createDataSource(orgCode);
|
||||
dataSources.put(orgCode, ds);
|
||||
|
||||
// ===== 关键:先验证连接,防止缓存坏数据源 =====
|
||||
try (Connection conn = ds.getConnection()) {
|
||||
// 成功才缓存
|
||||
dataSources.put(orgCode, ds);
|
||||
} catch (Exception e) {
|
||||
if (ds instanceof DruidDataSource) {
|
||||
((DruidDataSource) ds).close();
|
||||
}
|
||||
throw new RuntimeException("数据库连接失败: " + orgCode, e);
|
||||
}
|
||||
|
||||
// 刷新 routing datasource
|
||||
refresh();
|
||||
|
||||
return ds;
|
||||
@@ -62,14 +81,13 @@ public class DataSourceManager extends AbstractRoutingDataSource {
|
||||
* 刷新 routing datasource
|
||||
*/
|
||||
private static void refresh() {
|
||||
|
||||
DataSourceManager routing = SpringUtils.getBean(DataSourceManager.class);
|
||||
routing.setTargetDataSources(new ConcurrentHashMap<>(dataSources));
|
||||
routing.afterPropertiesSet();
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建数据源
|
||||
* 创建数据源(已优化:防止无限重连/卡死)
|
||||
*/
|
||||
private static DataSource createDataSource(String orgCode) throws Exception {
|
||||
|
||||
@@ -93,11 +111,23 @@ public class DataSourceManager extends AbstractRoutingDataSource {
|
||||
ds.setPassword(p.getProperty("password"));
|
||||
ds.setDriverClassName(p.getProperty("driver"));
|
||||
|
||||
ds.setInitialSize(2);
|
||||
ds.setMaxActive(20);
|
||||
ds.setMinIdle(2);
|
||||
ds.setValidationQuery("SELECT 1");
|
||||
// ===== 基础连接池配置 =====
|
||||
ds.setInitialSize(1);
|
||||
ds.setMaxActive(10);
|
||||
ds.setMinIdle(1);
|
||||
|
||||
// ===== 核心:防止卡死 / 无限重试 =====
|
||||
ds.setMaxWait(3000); // 获取连接最大等待3秒
|
||||
ds.setBreakAfterAcquireFailure(true); // 获取失败直接抛异常
|
||||
ds.setConnectionErrorRetryAttempts(0); // 不重试
|
||||
ds.setFailFast(true); // 初始化时检测连接
|
||||
|
||||
// ===== 连接校验 =====
|
||||
ds.setValidationQuery("SELECT 1");
|
||||
ds.setTestOnBorrow(true);
|
||||
ds.setTestWhileIdle(false);
|
||||
|
||||
// 初始化(此时若连接失败会快速抛异常)
|
||||
ds.init();
|
||||
|
||||
return ds;
|
||||
|
||||
@@ -22,8 +22,8 @@ file:
|
||||
base-dir: D:/data/file
|
||||
|
||||
static-path: /static/file
|
||||
server-url: http://192.168.1.137:80/api
|
||||
|
||||
# server-url: http://192.168.1.137:80/api
|
||||
server-url: http://ser369.g3soft.cn:80/api
|
||||
common-dir: common
|
||||
container-dir: container
|
||||
temp-dir: temp
|
||||
@@ -1,10 +1,10 @@
|
||||
url=jdbc:sqlserver://118.89.70.199:1433;databaseName=G3BoxOCR
|
||||
#url=jdbc:sqlserver://118.89.70.199:1433;databaseName=G3BoxOCR
|
||||
#username=sa
|
||||
#password=zhang520..
|
||||
#driver=com.microsoft.sqlserver.jdbc.SQLServerDriver
|
||||
|
||||
url=jdbc:sqlserver://127.0.0.1:1433;databaseName=G3ContainerOCR
|
||||
username=sa
|
||||
password=zhang520..
|
||||
password=123456@aaa
|
||||
driver=com.microsoft.sqlserver.jdbc.SQLServerDriver
|
||||
|
||||
# url=jdbc:sqlserver://127.0.0.1:1433;databaseName=G3BoxOCR
|
||||
# username=sa
|
||||
# password=123456@aaa
|
||||
# driver=com.microsoft.sqlserver.jdbc.SQLServerDriver
|
||||
|
||||
@@ -10,7 +10,7 @@ PUBLIC_SERVER_PORT=8080
|
||||
|
||||
# API
|
||||
# 后端 API 基础地址
|
||||
# PUBLIC_API_BASE_URL=http://ser369.g3soft.cn:80/api
|
||||
PUBLIC_API_BASE_URL=http://192.168.5.6:80/api
|
||||
PUBLIC_API_BASE_URL=http://ser369.g3soft.cn:80/api
|
||||
# PUBLIC_API_BASE_URL=http://192.168.1.137:80/api
|
||||
|
||||
PUBLIC_ALIYUN_BUCKET = g3hd
|
||||
+9
@@ -18,6 +18,7 @@ declare module 'vue' {
|
||||
ElDatePicker: typeof import('element-plus/es')['ElDatePicker']
|
||||
ElDialog: typeof import('element-plus/es')['ElDialog']
|
||||
ElDivider: typeof import('element-plus/es')['ElDivider']
|
||||
ElDrawer: typeof import('element-plus/es')['ElDrawer']
|
||||
ElDropdown: typeof import('element-plus/es')['ElDropdown']
|
||||
ElDropdownItem: typeof import('element-plus/es')['ElDropdownItem']
|
||||
ElDropdownMenu: typeof import('element-plus/es')['ElDropdownMenu']
|
||||
@@ -27,15 +28,23 @@ declare module 'vue' {
|
||||
ElInput: typeof import('element-plus/es')['ElInput']
|
||||
ElMenu: typeof import('element-plus/es')['ElMenu']
|
||||
ElMenuItem: typeof import('element-plus/es')['ElMenuItem']
|
||||
ElOption: typeof import('element-plus/es')['ElOption']
|
||||
ElPagination: typeof import('element-plus/es')['ElPagination']
|
||||
ElPopover: typeof import('element-plus/es')['ElPopover']
|
||||
ElRadio: typeof import('element-plus/es')['ElRadio']
|
||||
ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup']
|
||||
ElResult: typeof import('element-plus/es')['ElResult']
|
||||
ElScrollbar: typeof import('element-plus/es')['ElScrollbar']
|
||||
ElSelect: typeof import('element-plus/es')['ElSelect']
|
||||
ElSplitter: typeof import('element-plus/es')['ElSplitter']
|
||||
ElSplitterPanel: typeof import('element-plus/es')['ElSplitterPanel']
|
||||
ElSubMenu: typeof import('element-plus/es')['ElSubMenu']
|
||||
ElSwitch: typeof import('element-plus/es')['ElSwitch']
|
||||
ElTable: typeof import('element-plus/es')['ElTable']
|
||||
ElTableColumn: typeof import('element-plus/es')['ElTableColumn']
|
||||
ElTabPane: typeof import('element-plus/es')['ElTabPane']
|
||||
ElTabs: typeof import('element-plus/es')['ElTabs']
|
||||
ElText: typeof import('element-plus/es')['ElText']
|
||||
ElTooltip: typeof import('element-plus/es')['ElTooltip']
|
||||
ElTree: typeof import('element-plus/es')['ElTree']
|
||||
G3Column: typeof import('./src/components/g3-table/g3-column.vue')['default']
|
||||
|
||||
@@ -26,8 +26,8 @@ import {
|
||||
formatFileSize,
|
||||
getAliyunFileBlob,
|
||||
getFileExtension,
|
||||
uploadFile,
|
||||
buildPublicUrl,
|
||||
extractFilePath,
|
||||
} from "@/utils/file";
|
||||
import {
|
||||
insertFileApi,
|
||||
@@ -150,7 +150,7 @@ async function handleConfirmUpdate() {
|
||||
return;
|
||||
}
|
||||
|
||||
updateLoading.value = true;
|
||||
// updateLoading.value = true;
|
||||
|
||||
const requestData = [
|
||||
{
|
||||
@@ -162,20 +162,60 @@ async function handleConfirmUpdate() {
|
||||
subid: updateFile.value.subid,
|
||||
mx_filename: updateFile.value.mx_filename,
|
||||
mx_bz: updateFile.value.mx_bz,
|
||||
mx_cate_id: updateFile.value.mx_cate_id,
|
||||
},
|
||||
],
|
||||
deletes: [],
|
||||
},
|
||||
];
|
||||
|
||||
// 检查是否设置了箱门照
|
||||
if (updateFile.value.mx_cate_id === "0") {
|
||||
requestData[0].updates[0].mx_xh = "0";
|
||||
|
||||
// 检查数据库表其它是否有文件已经设置了箱门照
|
||||
const searchCondition = `father = '${props.father}' AND mx_cate_id = '0' AND subid != '${updateFile.value.subid}'`;
|
||||
const res = await loadDataApi("bf_file", searchCondition, "mx_xh", ["subid", "mx_xh"]);
|
||||
|
||||
if (res.success && res.data.length > 0) {
|
||||
requestData.push({
|
||||
tableName: "bf_file",
|
||||
keyField: "subid",
|
||||
inserts: [],
|
||||
updates: [
|
||||
{
|
||||
subid: res.data[0].subid,
|
||||
mx_cate_id: "1",
|
||||
mx_xh: updateFile.value.mx_xh,
|
||||
},
|
||||
],
|
||||
deletes: [],
|
||||
});
|
||||
|
||||
requestData.push({
|
||||
tableName: "b_container",
|
||||
keyField: "b_id",
|
||||
inserts: [],
|
||||
updates: [
|
||||
{
|
||||
b_id: props.father,
|
||||
b_container_img: extractFilePath(updateFile.value.mx_mapfilename),
|
||||
},
|
||||
],
|
||||
deletes: [],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await saveObjectApi(requestData);
|
||||
|
||||
if (response.success) {
|
||||
ElMessage.success("文件名更新成功");
|
||||
updateFileName(updateFile.value);
|
||||
ElMessage.success("更新成功");
|
||||
|
||||
showUpdateDialog.value = false;
|
||||
|
||||
loadFiles()
|
||||
}
|
||||
} catch (error) {
|
||||
ElMessage.error("更新文件时发生错误");
|
||||
@@ -247,7 +287,7 @@ async function loadFiles() {
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await loadDataApi("bf_file", searchCondition, "mx_xh");
|
||||
const res = await loadDataApi("bf_file", searchCondition, "mx_cate_id,mx_xh");
|
||||
|
||||
if (res.success) {
|
||||
let datas = res.data.map((item) => {
|
||||
@@ -1065,6 +1105,10 @@ function handleTreeNodeClick(data) {
|
||||
<el-input v-model="updateFile.mx_filename" />
|
||||
<div style="margin: 4px 0px">备注:</div>
|
||||
<el-input v-model="updateFile.mx_bz" />
|
||||
<template v-if="updateFile._status !== 'pending'">
|
||||
<div style="margin: 4px 0px">是否为箱门照:</div>
|
||||
<el-switch v-model="updateFile.mx_cate_id" active-value="0" inactive-value="1" />
|
||||
</template>
|
||||
</div>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
|
||||
@@ -203,7 +203,6 @@ export function getFileExtension(fileName) {
|
||||
return fileExt;
|
||||
}
|
||||
|
||||
|
||||
export function downloadOssZip(files, fileName = "文件合集", callback) {
|
||||
const zip = new JSZip();
|
||||
const addedFiles = new Set();
|
||||
@@ -424,8 +423,35 @@ export function buildPublicUrl(filePath) {
|
||||
return joinUrl(baseUrl, "/static/file", filePath);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从完整 URL 中提取 filePath
|
||||
* @param fullUrl 完整访问地址
|
||||
* @returns filePath
|
||||
*/
|
||||
export function extractFilePath(fullUrl) {
|
||||
if (!fullUrl) return "";
|
||||
|
||||
const baseUrl = import.meta.env.PUBLIC_API_BASE_URL || "";
|
||||
|
||||
// 去掉 baseUrl
|
||||
let path = fullUrl.startsWith(baseUrl) ? fullUrl.slice(baseUrl.length) : fullUrl;
|
||||
|
||||
// 去掉 /static/file 前缀
|
||||
const prefix = "/static/file";
|
||||
if (path.startsWith(prefix)) {
|
||||
path = path.slice(prefix.length);
|
||||
}
|
||||
|
||||
// 去掉开头的 /
|
||||
if (path.startsWith("/")) {
|
||||
path = path.slice(1);
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
export function getFileBlob(path) {
|
||||
const fullUrl = buildPublicUrl(path)
|
||||
const fullUrl = buildPublicUrl(path);
|
||||
|
||||
return fetch(fullUrl).then((response) => {
|
||||
if (!response.ok) {
|
||||
|
||||
@@ -53,7 +53,7 @@ const pagination = reactive({
|
||||
|
||||
const fileCategories = [
|
||||
{
|
||||
label: "集装箱号",
|
||||
label: "箱门照",
|
||||
cateId: "0",
|
||||
},
|
||||
{
|
||||
@@ -718,7 +718,7 @@ function buildFileName(containerNo, index, fileName) {
|
||||
/>
|
||||
|
||||
<HighDownloadDialog v-model="showHighDownload" />
|
||||
<UpdateDialog v-model="showUpdate" :data="currentRow" />
|
||||
<UpdateDialog v-model="showUpdate" :data="currentRow" @update="loadTableDatas" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
<script lang="js" setup>
|
||||
import { ElMessage } from "element-plus";
|
||||
import { saveObjectApi } from "@/request/api";
|
||||
|
||||
const visible = defineModel(false);
|
||||
|
||||
const props = defineProps({
|
||||
@@ -8,6 +11,8 @@ const props = defineProps({
|
||||
},
|
||||
});
|
||||
|
||||
const emits = defineEmits(["update"]);
|
||||
|
||||
const loading = ref(false);
|
||||
|
||||
const form = ref({
|
||||
@@ -26,18 +31,79 @@ const title = computed(() => {
|
||||
});
|
||||
|
||||
function onOpened() {
|
||||
loading.global = true;
|
||||
console.log("对话框已打开");
|
||||
//
|
||||
form.value = {
|
||||
b_id: props.data.b_id,
|
||||
b_container_no: props.data.b_container_no,
|
||||
};
|
||||
}
|
||||
|
||||
function onClosed() {
|
||||
loading.value = true;
|
||||
|
||||
form.value = {
|
||||
b_id: "",
|
||||
b_container_no: "",
|
||||
};
|
||||
}
|
||||
|
||||
async function handleSave() {
|
||||
loading.value = true;
|
||||
|
||||
// 检查是否符合规则
|
||||
if (!validateContainerNo(form.value.b_container_no)) {
|
||||
ElMessage.error("箱号格式不正确,请输入4个字母加7个数字的格式");
|
||||
loading.value = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查是否有修改
|
||||
if (form.value.b_container_no === props.data.b_container_no) {
|
||||
ElMessage.success("保存成功");
|
||||
loading.value = false;
|
||||
visible.value = false;
|
||||
return;
|
||||
}
|
||||
|
||||
let updateObj = {
|
||||
b_id: form.value.b_id,
|
||||
b_container_no: form.value.b_container_no,
|
||||
};
|
||||
|
||||
// 保存
|
||||
await saveObjectApi([
|
||||
{
|
||||
tableName: "b_container",
|
||||
keyField: "b_id",
|
||||
inserts: [],
|
||||
updates: [updateObj],
|
||||
deletes: [],
|
||||
},
|
||||
])
|
||||
.then((res) => {
|
||||
if (res.success) {
|
||||
ElMessage.success("保存成功");
|
||||
emits("update");
|
||||
visible.value = false;
|
||||
} else {
|
||||
ElMessage.error(res.message || "保存失败");
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
}
|
||||
|
||||
// 集装箱号校验
|
||||
function validateContainerNo(containerNo) {
|
||||
// 正则表达式: 4个字母 + 7个数字
|
||||
const pattern = /^[A-Z]{4}\d{7}$/i;
|
||||
|
||||
// 使用正则进行匹配
|
||||
if (pattern.test(containerNo)) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -66,7 +132,7 @@ function onClosed() {
|
||||
</el-icon>
|
||||
取消
|
||||
</el-button>
|
||||
<el-button type="primary">
|
||||
<el-button type="primary" :loading="loading" @click="handleSave">
|
||||
<el-icon class="el-icon--left">
|
||||
<G3Icon icon="lucide:save" />
|
||||
</el-icon>
|
||||
|
||||
Reference in new issue
Block a user