feat(amazon):优化商标筛查重试机制和进度显示- 添加防抖控制,避免频繁点击重试按钮
- 优化重试逻辑,增加时间间隔限制和状态检查 - 移除表格上方冗余的进度条显示 - 更新取消状态下的提示文案和操作引导- 修复品牌统计数据显示逻辑,确保准确性- 调整用户界面元素间距和样式细节 - 完善后端接口调用,支持信号中断和错误处理 -优化SSE连接管理,防止连接泄漏 - 改进任务取消机制,提升用户体验 - 更新用户信息展示,增加注册时间显示
This commit is contained in:
@@ -229,8 +229,6 @@ function startSpringBoot() {
|
|||||||
const dataDir = getDataDirectoryPath();
|
const dataDir = getDataDirectoryPath();
|
||||||
const logDir = getLogDirectoryPath();
|
const logDir = getLogDirectoryPath();
|
||||||
const logbackConfigPath = getLogbackConfigPath();
|
const logbackConfigPath = getLogbackConfigPath();
|
||||||
console.log('[Spring Boot] JAR路径:', jarPath);
|
|
||||||
console.log('[Spring Boot] Java路径:', javaPath);
|
|
||||||
if (!existsSync(jarPath)) {
|
if (!existsSync(jarPath)) {
|
||||||
dialog.showErrorBox('启动失败', `JAR 文件不存在:\n${jarPath}`);
|
dialog.showErrorBox('启动失败', `JAR 文件不存在:\n${jarPath}`);
|
||||||
app.quit();
|
app.quit();
|
||||||
@@ -475,10 +473,15 @@ app.whenReady().then(() => {
|
|||||||
splashWindow.once('ready-to-show', () => splashWindow?.show());
|
splashWindow.once('ready-to-show', () => splashWindow?.show());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 已手动启动后端
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
startSpringBoot();
|
startSpringBoot();
|
||||||
}, 200);
|
}, 200);
|
||||||
|
|
||||||
|
// setTimeout(() => {
|
||||||
|
// openAppIfNotOpened();
|
||||||
|
// }, 200);
|
||||||
|
|
||||||
app.on('activate', () => {
|
app.on('activate', () => {
|
||||||
if (mainWindow && !mainWindow.isDestroyed()) {
|
if (mainWindow && !mainWindow.isDestroyed()) {
|
||||||
mainWindow.show();
|
mainWindow.show();
|
||||||
@@ -856,15 +859,7 @@ ipcMain.handle('dev-skip-backend', () => {
|
|||||||
return { success: false, error: '仅开发模式可用' };
|
return { success: false, error: '仅开发模式可用' };
|
||||||
});
|
});
|
||||||
|
|
||||||
// 开发模式:手动启动后端
|
|
||||||
ipcMain.handle('dev-start-backend', () => {
|
|
||||||
if (isDev) {
|
|
||||||
console.log('[开发模式] 前端请求启动后端');
|
|
||||||
startSpringBoot();
|
|
||||||
return { success: true };
|
|
||||||
}
|
|
||||||
return { success: false, error: '仅开发模式可用' };
|
|
||||||
});
|
|
||||||
|
|
||||||
// 窗口控制 API
|
// 窗口控制 API
|
||||||
ipcMain.handle('window-minimize', () => {
|
ipcMain.handle('window-minimize', () => {
|
||||||
|
|||||||
@@ -51,6 +51,7 @@ const showAuthDialog = ref(false)
|
|||||||
const showRegDialog = ref(false)
|
const showRegDialog = ref(false)
|
||||||
const zhCnLocale = zhCn
|
const zhCnLocale = zhCn
|
||||||
const currentUsername = ref('')
|
const currentUsername = ref('')
|
||||||
|
const registerTime = ref('')
|
||||||
const showDeviceDialog = ref(false)
|
const showDeviceDialog = ref(false)
|
||||||
const deviceLoading = ref(false)
|
const deviceLoading = ref(false)
|
||||||
const devices = ref<DeviceItem[]>([])
|
const devices = ref<DeviceItem[]>([])
|
||||||
@@ -220,6 +221,7 @@ async function handleLoginSuccess(data: {
|
|||||||
vipExpireTime.value = data.expireTime ? new Date(data.expireTime) : null
|
vipExpireTime.value = data.expireTime ? new Date(data.expireTime) : null
|
||||||
accountType.value = data.accountType || 'trial'
|
accountType.value = data.accountType || 'trial'
|
||||||
deviceTrialExpired.value = data.deviceTrialExpired || false
|
deviceTrialExpired.value = data.deviceTrialExpired || false
|
||||||
|
registerTime.value = data.registerTime || ''
|
||||||
|
|
||||||
const deviceId = await getOrCreateDeviceId()
|
const deviceId = await getOrCreateDeviceId()
|
||||||
await deviceApi.register({
|
await deviceApi.register({
|
||||||
@@ -249,6 +251,7 @@ async function clearLocalAuth() {
|
|||||||
removeToken()
|
removeToken()
|
||||||
isAuthenticated.value = false
|
isAuthenticated.value = false
|
||||||
currentUsername.value = ''
|
currentUsername.value = ''
|
||||||
|
registerTime.value = ''
|
||||||
userPermissions.value = ''
|
userPermissions.value = ''
|
||||||
vipExpireTime.value = null
|
vipExpireTime.value = null
|
||||||
deviceTrialExpired.value = false
|
deviceTrialExpired.value = false
|
||||||
@@ -319,6 +322,7 @@ async function checkAuth() {
|
|||||||
userPermissions.value = res.data.permissions || ''
|
userPermissions.value = res.data.permissions || ''
|
||||||
deviceTrialExpired.value = res.data.deviceTrialExpired || false
|
deviceTrialExpired.value = res.data.deviceTrialExpired || false
|
||||||
accountType.value = res.data.accountType || 'trial'
|
accountType.value = res.data.accountType || 'trial'
|
||||||
|
registerTime.value = res.data.registerTime || ''
|
||||||
|
|
||||||
if (res.data.expireTime) {
|
if (res.data.expireTime) {
|
||||||
vipExpireTime.value = new Date(res.data.expireTime)
|
vipExpireTime.value = new Date(res.data.expireTime)
|
||||||
@@ -640,15 +644,8 @@ onUnmounted(() => {
|
|||||||
<div class="user-avatar-section" @click="handleUserClick">
|
<div class="user-avatar-section" @click="handleUserClick">
|
||||||
<div class="avatar-wrapper">
|
<div class="avatar-wrapper">
|
||||||
<img
|
<img
|
||||||
v-if="isAuthenticated"
|
|
||||||
src="/image/img_v3_02qd_052605f0-4be3-44db-9691-35ee5ff6201g.jpg"
|
|
||||||
alt="用户头像"
|
|
||||||
class="user-avatar-img"
|
|
||||||
/>
|
|
||||||
<img
|
|
||||||
v-else
|
|
||||||
src="/image/user.png"
|
src="/image/user.png"
|
||||||
alt="默认头像"
|
alt="用户头像"
|
||||||
class="user-avatar-img"
|
class="user-avatar-img"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -657,7 +654,7 @@ onUnmounted(() => {
|
|||||||
<span class="user-name">{{ isAuthenticated ? currentUsername : '登录/注册' }}</span>
|
<span class="user-name">{{ isAuthenticated ? currentUsername : '登录/注册' }}</span>
|
||||||
<span v-if="isAuthenticated && vipStatus.isVip" class="vip-badge">VIP {{ vipStatus.daysLeft }}天</span>
|
<span v-if="isAuthenticated && vipStatus.isVip" class="vip-badge">VIP {{ vipStatus.daysLeft }}天</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="user-action">{{ isAuthenticated ? '18659156151' : '登录账号体验完整功能' }}</div>
|
<div class="user-action">{{ isAuthenticated ? ` ${registerTime ? registerTime.replace('T', ' ').substring(0, 16) : '未知'}` : '登录账号体验完整功能' }}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -903,7 +900,6 @@ onUnmounted(() => {
|
|||||||
|
|
||||||
/* 主Logo */
|
/* 主Logo */
|
||||||
.main-logo {
|
.main-logo {
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
padding: 8px 0;
|
padding: 8px 0;
|
||||||
@@ -929,8 +925,8 @@ onUnmounted(() => {
|
|||||||
|
|
||||||
.avatar-wrapper {
|
.avatar-wrapper {
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
width: 44px;
|
width: 40px;
|
||||||
height: 44px;
|
height: 40px;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
background: #fff;
|
background: #fff;
|
||||||
@@ -958,7 +954,7 @@ onUnmounted(() => {
|
|||||||
.user-name-wrapper {
|
.user-name-wrapper {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 6px;
|
gap: 4px;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -968,15 +964,17 @@ onUnmounted(() => {
|
|||||||
color: #303133;
|
color: #303133;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
line-height: 1.2;
|
line-height: 1.2;
|
||||||
flex-shrink: 0;
|
min-width: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
|
|
||||||
.vip-badge {
|
.vip-badge {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
padding: 0px 5px;
|
padding: 0px 3px;
|
||||||
height: 16px;
|
height: 14px;
|
||||||
background: #BAE0FF;
|
background: #BAE0FF;
|
||||||
border: 1px solid rgba(22, 119, 255, 0.05);
|
border: 1px solid rgba(22, 119, 255, 0.05);
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
@@ -1021,7 +1019,8 @@ onUnmounted(() => {
|
|||||||
.brand-logo {
|
.brand-logo {
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
max-height: 100%;
|
max-height: 100%;
|
||||||
object-fit: contain;
|
object-fit: cover;
|
||||||
|
border-radius: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.menu {
|
.menu {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
export type HttpMethod = 'GET' | 'POST' | 'DELETE';
|
export type HttpMethod = 'GET' | 'POST' | 'DELETE';
|
||||||
const RUOYI_BASE = 'http://8.138.23.49:8085';
|
const RUOYI_BASE = 'http://8.138.23.49:8085';
|
||||||
// const RUOYI_BASE = 'http://192.168.1.89:8085';
|
// const RUOYI_BASE = 'http://192.168.1.89:8085';
|
||||||
export const CONFIG = {
|
export const CONFIG = {
|
||||||
CLIENT_BASE: 'http://localhost:8081',
|
CLIENT_BASE: 'http://localhost:8081',
|
||||||
RUOYI_BASE,
|
RUOYI_BASE,
|
||||||
|
|||||||
@@ -2,14 +2,14 @@ import { http } from './http'
|
|||||||
|
|
||||||
export const markApi = {
|
export const markApi = {
|
||||||
// 新建任务(调用 erp_client_sb)
|
// 新建任务(调用 erp_client_sb)
|
||||||
newTask(file: File) {
|
newTask(file: File, signal?: AbortSignal) {
|
||||||
const formData = new FormData()
|
const formData = new FormData()
|
||||||
formData.append('file', file)
|
formData.append('file', file)
|
||||||
return http.upload<{ code: number, data: any, msg: string }>('/api/trademark/newTask', formData)
|
return http.upload<{ code: number, data: any, msg: string }>('/api/trademark/newTask', formData, signal)
|
||||||
},
|
},
|
||||||
|
|
||||||
// 获取任务列表及筛选数据(调用 erp_client_sb)
|
// 获取任务列表及筛选数据(调用 erp_client_sb)
|
||||||
getTask() {
|
getTask(signal?: AbortSignal) {
|
||||||
return http.post<{
|
return http.post<{
|
||||||
code: number,
|
code: number,
|
||||||
data: {
|
data: {
|
||||||
@@ -18,12 +18,12 @@ export const markApi = {
|
|||||||
headers: string[] // 表头
|
headers: string[] // 表头
|
||||||
},
|
},
|
||||||
msg: string
|
msg: string
|
||||||
}>('/api/trademark/task')
|
}>('/api/trademark/task', undefined, signal)
|
||||||
},
|
},
|
||||||
|
|
||||||
// 品牌商标筛查
|
// 品牌商标筛查
|
||||||
brandCheck(brands: string[], taskId?: string) {
|
brandCheck(brands: string[], taskId?: string, signal?: AbortSignal) {
|
||||||
return http.post<{ code: number, data: { total: number, checked: number, registered: number, unregistered: number, failed: number, data: any[], duration: string }, msg: string }>('/api/trademark/brandCheck', { brands, taskId })
|
return http.post<{ code: number, data: { total: number, checked: number, registered: number, unregistered: number, failed: number, data: any[], duration: string }, msg: string }>('/api/trademark/brandCheck', { brands, taskId }, signal)
|
||||||
},
|
},
|
||||||
|
|
||||||
// 查询品牌筛查进度
|
// 查询品牌筛查进度
|
||||||
|
|||||||
@@ -107,10 +107,28 @@ function handleCancelTask() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const isRetrying = ref(false)
|
||||||
|
let lastRetryTime = 0
|
||||||
|
|
||||||
function handleRetryTask() {
|
function handleRetryTask() {
|
||||||
|
const now = Date.now()
|
||||||
|
if (now - lastRetryTime < 3000 || isRetrying.value || loading.value || trademarkPanelRef.value?.trademarkLoading) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
lastRetryTime = now
|
||||||
|
isRetrying.value = true
|
||||||
|
|
||||||
if (trademarkPanelRef.value && typeof trademarkPanelRef.value.startTrademarkQuery === 'function') {
|
if (trademarkPanelRef.value && typeof trademarkPanelRef.value.startTrademarkQuery === 'function') {
|
||||||
trademarkPanelRef.value.startTrademarkQuery()
|
trademarkPanelRef.value.startTrademarkQuery()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const checkInterval = setInterval(() => {
|
||||||
|
if (trademarkPanelRef.value?.trademarkLoading || Date.now() - now > 2000) {
|
||||||
|
clearInterval(checkInterval)
|
||||||
|
isRetrying.value = false
|
||||||
|
}
|
||||||
|
}, 50)
|
||||||
}
|
}
|
||||||
|
|
||||||
function openSubscribeDialog() {
|
function openSubscribeDialog() {
|
||||||
@@ -176,19 +194,6 @@ function handleExportData() {
|
|||||||
<!-- 数据显示区域 -->
|
<!-- 数据显示区域 -->
|
||||||
<div class="table-container">
|
<div class="table-container">
|
||||||
<div class="table-section">
|
<div class="table-section">
|
||||||
<!-- 表格上方进度条 -->
|
|
||||||
<div v-if="progressVisible || (currentTab === 'trademark' && progressPercentage > 0)" class="progress-head">
|
|
||||||
<div class="progress-section">
|
|
||||||
<div class="progress-box">
|
|
||||||
<div class="progress-container">
|
|
||||||
<div class="progress-bar">
|
|
||||||
<div class="progress-fill" :style="{ width: progressPercentage + '%' }"></div>
|
|
||||||
</div>
|
|
||||||
<div class="progress-text">{{ progressPercentage }}%</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="table-wrapper">
|
<div class="table-wrapper">
|
||||||
<!-- 跟卖精灵内容 -->
|
<!-- 跟卖精灵内容 -->
|
||||||
@@ -276,15 +281,14 @@ function handleExportData() {
|
|||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<div class="banner-content">
|
<div class="banner-content">
|
||||||
<div class="banner-title">已取消查询</div>
|
<div class="banner-title">筛查已取消</div>
|
||||||
<div class="banner-desc">您已取消本次查询任务</div>
|
<div class="banner-desc">点击右侧“重试”按钮可继续,或导入新的选品表格重新筛查。</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="banner-actions">
|
<div class="banner-actions">
|
||||||
<el-button size="default" @click="handleNewTask">新建任务</el-button>
|
<el-button size="default" @click="handleNewTask">新建任务</el-button>
|
||||||
<el-button type="primary" size="default" @click="handleRetryTask">重新筛查</el-button>
|
<el-button type="primary" size="default" @click="handleRetryTask" :disabled="loading || isRetrying">重新筛查</el-button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 完成/失败状态横幅 -->
|
<!-- 完成/失败状态横幅 -->
|
||||||
<div v-if="trademarkPanelRef?.queryStatus === 'done' || trademarkPanelRef?.queryStatus === 'error' || trademarkPanelRef?.queryStatus === 'networkError'" class="status-banner done-banner">
|
<div v-if="trademarkPanelRef?.queryStatus === 'done' || trademarkPanelRef?.queryStatus === 'error' || trademarkPanelRef?.queryStatus === 'networkError'" class="status-banner done-banner">
|
||||||
<div class="banner-icon">
|
<div class="banner-icon">
|
||||||
@@ -305,7 +309,7 @@ function handleExportData() {
|
|||||||
<div class="banner-actions">
|
<div class="banner-actions">
|
||||||
<el-button size="default" @click="handleNewTask">新建任务</el-button>
|
<el-button size="default" @click="handleNewTask">新建任务</el-button>
|
||||||
<el-button v-if="trademarkPanelRef.queryStatus === 'done'" type="primary" size="default" @click="handleExportData">导出数据</el-button>
|
<el-button v-if="trademarkPanelRef.queryStatus === 'done'" type="primary" size="default" @click="handleExportData">导出数据</el-button>
|
||||||
<el-button v-else type="primary" size="default" @click="handleRetryTask">重新筛查</el-button>
|
<el-button v-else type="primary" size="default" @click="handleRetryTask" :disabled="loading || isRetrying">重新筛查</el-button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -429,11 +433,11 @@ function handleExportData() {
|
|||||||
</div>
|
</div>
|
||||||
<div class="task-stat highlight">
|
<div class="task-stat highlight">
|
||||||
<span class="stat-label">未注册</span>
|
<span class="stat-label">未注册</span>
|
||||||
<span class="stat-value">{{ trademarkPanelRef?.isBrandTaskRealData ? trademarkPanelRef.taskProgress.brand.completed : '-' }}</span>
|
<span class="stat-value">{{ trademarkPanelRef?.brandStatsDisplay?.unregistered || '-' }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="task-stat">
|
<div class="task-stat">
|
||||||
<span class="stat-label">已注册</span>
|
<span class="stat-label">已注册</span>
|
||||||
<span class="stat-value">{{ trademarkPanelRef?.isBrandTaskRealData ? (trademarkPanelRef.taskProgress.brand.total - trademarkPanelRef.taskProgress.brand.completed) : '-' }}</span>
|
<span class="stat-value">{{ trademarkPanelRef?.brandStatsDisplay?.registered || '-' }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -702,13 +706,6 @@ function handleExportData() {
|
|||||||
.text:focus { border-color: #409EFF; }
|
.text:focus { border-color: #409EFF; }
|
||||||
.text:disabled { background: #f5f7fa; color: #c0c4cc; }
|
.text:disabled { background: #f5f7fa; color: #c0c4cc; }
|
||||||
.action-buttons { display: flex; gap: 10px; flex-wrap: wrap; }
|
.action-buttons { display: flex; gap: 10px; flex-wrap: wrap; }
|
||||||
.progress-section { margin: 0px 12px 0px 12px; }
|
|
||||||
.progress-head { margin-bottom: 8px; }
|
|
||||||
.progress-box { padding: 4px 0; }
|
|
||||||
.progress-container { display: flex; align-items: center; gap: 8px; }
|
|
||||||
.progress-bar { flex: 1; height: 6px; background: #e3eeff; border-radius: 999px; overflow: hidden; }
|
|
||||||
.progress-fill { height: 100%; background: #1677FF; border-radius: 999px; transition: width 0.3s ease; }
|
|
||||||
.progress-text { font-size: 13px; color: #1677FF; font-weight: 500; min-width: 44px; text-align: right; }
|
|
||||||
.current-status { font-size: 12px; color: #606266; padding-left: 2px; }
|
.current-status { font-size: 12px; color: #606266; padding-left: 2px; }
|
||||||
.table-container {
|
.table-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -970,6 +967,7 @@ function handleExportData() {
|
|||||||
width: 48px;
|
width: 48px;
|
||||||
height: 48px;
|
height: 48px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.done-banner .banner-icon {
|
.done-banner .banner-icon {
|
||||||
background: transparent;
|
background: transparent;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -365,7 +365,7 @@ defineExpose({
|
|||||||
.step-header { display: flex; align-items: center; gap: 8px; margin-bottom: 8px; }
|
.step-header { display: flex; align-items: center; gap: 8px; margin-bottom: 8px; }
|
||||||
.title { font-size: 14px; font-weight: 600; color: #303133; text-align: left; }
|
.title { font-size: 14px; font-weight: 600; color: #303133; text-align: left; }
|
||||||
.desc { font-size: 12px; color: #909399; margin-bottom: 10px; text-align: left; line-height: 1.5; }
|
.desc { font-size: 12px; color: #909399; margin-bottom: 10px; text-align: left; line-height: 1.5; }
|
||||||
.links { display: flex; align-items: center; gap: 6px; margin-bottom: 8px; }
|
.links { display: flex; align-items: center; gap: 2px; margin-bottom: 8px; }
|
||||||
.link { color: #409EFF; cursor: pointer; font-size: 12px; }
|
.link { color: #409EFF; cursor: pointer; font-size: 12px; }
|
||||||
.sep { color: #dcdfe6; }
|
.sep { color: #dcdfe6; }
|
||||||
.dropzone { border: 1px dashed #c0c4cc; border-radius: 6px; padding: 16px; text-align: center; cursor: pointer; background: #fafafa; }
|
.dropzone { border: 1px dashed #c0c4cc; border-radius: 6px; padding: 16px; text-align: center; cursor: pointer; background: #fafafa; }
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,13 +0,0 @@
|
|||||||
package com.tashow.erp.config;
|
|
||||||
|
|
||||||
import com.tashow.erp.fx.controller.JavaBridge;
|
|
||||||
import org.springframework.context.annotation.Bean;
|
|
||||||
import org.springframework.context.annotation.Configuration;
|
|
||||||
|
|
||||||
@Configuration
|
|
||||||
public class JavaBridgeConfig {
|
|
||||||
@Bean
|
|
||||||
public JavaBridge javaBridge() {
|
|
||||||
return new JavaBridge();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -9,6 +9,7 @@ import com.tashow.erp.service.IFangzhouApiService;
|
|||||||
import com.tashow.erp.utils.ExcelParseUtil;
|
import com.tashow.erp.utils.ExcelParseUtil;
|
||||||
import com.tashow.erp.utils.JsonData;
|
import com.tashow.erp.utils.JsonData;
|
||||||
import com.tashow.erp.utils.LoggerUtil;
|
import com.tashow.erp.utils.LoggerUtil;
|
||||||
|
import com.tashow.erp.utils.ProxyPool;
|
||||||
import com.tashow.erp.utils.TrademarkCheckUtil;
|
import com.tashow.erp.utils.TrademarkCheckUtil;
|
||||||
import cn.hutool.core.io.FileUtil;
|
import cn.hutool.core.io.FileUtil;
|
||||||
import cn.hutool.http.HttpUtil;
|
import cn.hutool.http.HttpUtil;
|
||||||
@@ -18,9 +19,11 @@ import org.slf4j.Logger;
|
|||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
|
||||||
|
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -34,7 +37,7 @@ public class TrademarkController {
|
|||||||
private static final ObjectMapper objectMapper = new ObjectMapper();
|
private static final ObjectMapper objectMapper = new ObjectMapper();
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private TrademarkCheckUtil util;
|
private ProxyPool proxyPool;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private BrandTrademarkCacheService cacheService;
|
private BrandTrademarkCacheService cacheService;
|
||||||
@@ -45,20 +48,40 @@ public class TrademarkController {
|
|||||||
@Autowired
|
@Autowired
|
||||||
private IFangzhouApiService fangzhouApi;
|
private IFangzhouApiService fangzhouApi;
|
||||||
|
|
||||||
// 进度追踪
|
private static final Map<String, Integer> progressMap = new ConcurrentHashMap<>();
|
||||||
private final Map<String, Integer> progressMap = new java.util.concurrent.ConcurrentHashMap<>();
|
private static final Map<String, Boolean> cancelMap = new ConcurrentHashMap<>();
|
||||||
|
private static final Map<String, SseEmitter> sseEmitters = new ConcurrentHashMap<>();
|
||||||
|
private static final Map<String, java.util.concurrent.ExecutorService> taskExecutors = new ConcurrentHashMap<>();
|
||||||
|
private static volatile String currentTaskId = null;
|
||||||
|
private static final Object taskLock = new Object();
|
||||||
|
private static volatile boolean isUploadingFile = false;
|
||||||
|
private static final Object uploadLock = new Object();
|
||||||
|
|
||||||
// 任务取消标志
|
@GetMapping("/progress/{taskId}")
|
||||||
private final Map<String, Boolean> cancelMap = new java.util.concurrent.ConcurrentHashMap<>();
|
public SseEmitter getProgress(@PathVariable String taskId) {
|
||||||
|
SseEmitter emitter = new SseEmitter(300000L);
|
||||||
|
sseEmitters.put(taskId, emitter);
|
||||||
|
emitter.onCompletion(() -> sseEmitters.remove(taskId));
|
||||||
|
emitter.onTimeout(() -> sseEmitters.remove(taskId));
|
||||||
|
emitter.onError((e) -> sseEmitters.remove(taskId));
|
||||||
|
return emitter;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 批量品牌商标筛查
|
|
||||||
*/
|
|
||||||
@PostMapping("/brandCheck")
|
@PostMapping("/brandCheck")
|
||||||
public JsonData brandCheck(@RequestBody Map<String, Object> request) {
|
public JsonData brandCheck(@RequestBody Map<String, Object> request) {
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
List<String> brands = (List<String>) request.get("brands");
|
List<String> brands = (List<String>) request.get("brands");
|
||||||
String taskId = (String) request.get("taskId");
|
String taskId = (String) request.get("taskId");
|
||||||
|
|
||||||
|
synchronized (taskLock) {
|
||||||
|
if (currentTaskId != null && !currentTaskId.equals(taskId)) {
|
||||||
|
logger.info("检测到新任务 {},终止旧任务 {}", taskId, currentTaskId);
|
||||||
|
forceTerminateTask(currentTaskId);
|
||||||
|
}
|
||||||
|
currentTaskId = taskId;
|
||||||
|
cancelMap.remove(taskId);
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
List<String> list = brands.stream()
|
List<String> list = brands.stream()
|
||||||
.filter(b -> !b.trim().isEmpty())
|
.filter(b -> !b.trim().isEmpty())
|
||||||
@@ -66,37 +89,129 @@ public class TrademarkController {
|
|||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
long start = System.currentTimeMillis();
|
long start = System.currentTimeMillis();
|
||||||
// 1. 先从全局缓存获取
|
|
||||||
Map<String, Boolean> cached = cacheService.getCached(list);
|
Map<String, Boolean> cached = cacheService.getCached(list);
|
||||||
// 2. 找出缓存未命中的品牌
|
|
||||||
List<String> toQuery = list.stream()
|
List<String> toQuery = list.stream()
|
||||||
.filter(b -> !cached.containsKey(b))
|
.filter(b -> !cached.containsKey(b))
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
Map<String, Boolean> queried = new HashMap<>();
|
|
||||||
|
Map<String, Boolean> queried = new java.util.concurrent.ConcurrentHashMap<>();
|
||||||
|
|
||||||
if (!toQuery.isEmpty()) {
|
if (!toQuery.isEmpty()) {
|
||||||
for (int i = 0; i < toQuery.size(); i++) {
|
List<List<String>> chunks = new ArrayList<>();
|
||||||
|
int totalBrands = toQuery.size();
|
||||||
|
if (totalBrands <= 100) {
|
||||||
|
chunks.add(toQuery);
|
||||||
|
} else {
|
||||||
|
int chunkSize = 100;
|
||||||
|
int numChunks = (totalBrands + chunkSize - 1) / chunkSize;
|
||||||
|
int baseSize = totalBrands / numChunks;
|
||||||
|
int remainder = totalBrands % numChunks;
|
||||||
|
|
||||||
|
int startIndex = 0;
|
||||||
|
for (int i = 0; i < numChunks; i++) {
|
||||||
|
int currentChunkSize = baseSize + (i < remainder ? 1 : 0);
|
||||||
|
chunks.add(toQuery.subList(startIndex, startIndex + currentChunkSize));
|
||||||
|
startIndex += currentChunkSize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根据实际线程数获取代理,不浪费
|
||||||
|
int proxyCount = chunks.size();
|
||||||
|
List<String> proxies = proxyPool.getProxies(proxyCount);
|
||||||
|
if (proxies.size() < chunks.size()) {
|
||||||
|
logger.warn("代理数量不足,需要{}个,实际获取{}个", chunks.size(), proxies.size());
|
||||||
|
}
|
||||||
|
logger.info("获取到{}个代理,分配给{}个线程", proxies.size(), chunks.size());
|
||||||
|
|
||||||
|
java.util.concurrent.ExecutorService executor = java.util.concurrent.Executors.newFixedThreadPool(chunks.size());
|
||||||
|
taskExecutors.put(taskId, executor);
|
||||||
|
List<java.util.concurrent.Future<Map<String, Boolean>>> futures = new ArrayList<>();
|
||||||
|
|
||||||
|
for (int i = 0; i < chunks.size(); i++) {
|
||||||
if (cancelMap.getOrDefault(taskId, false)) {
|
if (cancelMap.getOrDefault(taskId, false)) {
|
||||||
logger.info("任务 {} 已被取消,停止查询", taskId);
|
logger.info("任务 {} 已被取消", taskId);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
String brand = toQuery.get(i);
|
List<String> chunk = chunks.get(i);
|
||||||
logger.info("处理第 {} 个: {}", i + 1, brand);
|
String proxy = proxies.isEmpty() ? null : proxies.get(i % proxies.size());
|
||||||
|
|
||||||
Map<String, Boolean> results = util.batchCheck(Collections.singletonList(brand), queried);
|
final int chunkIndex = i;
|
||||||
queried.putAll(results);
|
futures.add(executor.submit(() -> {
|
||||||
|
if (cancelMap.getOrDefault(taskId, false)) {
|
||||||
if (taskId != null) progressMap.put(taskId, cached.size() + queried.size());
|
return new HashMap<String, Boolean>();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!queried.isEmpty()) cacheService.saveResults(queried);
|
logger.info("线程 {} 开始处理 {} 个品牌,使用代理: {}", chunkIndex, chunk.size(), proxy);
|
||||||
|
Map<String, Boolean> result = TrademarkCheckUtil.batchCheck(chunk, proxy, taskId, cancelMap, chunkIndex, sseEmitters);
|
||||||
|
|
||||||
|
if (cancelMap.getOrDefault(taskId, false)) {
|
||||||
|
return new HashMap<String, Boolean>();
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (java.util.concurrent.Future<Map<String, Boolean>> future : futures) {
|
||||||
|
if (cancelMap.getOrDefault(taskId, false)) {
|
||||||
|
logger.info("任务 {} 已被取消,停止收集结果", taskId);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
Map<String, Boolean> result = future.get();
|
||||||
|
if (!result.isEmpty()) {
|
||||||
|
queried.putAll(result);
|
||||||
|
}
|
||||||
|
} catch (java.util.concurrent.CancellationException e) {
|
||||||
|
logger.info("线程任务已被取消: {}", taskId);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
logger.info("线程任务被中断: {}", taskId);
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("获取线程结果失败", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
taskExecutors.remove(taskId);
|
||||||
|
executor.shutdown();
|
||||||
|
try {
|
||||||
|
if (!executor.awaitTermination(60, java.util.concurrent.TimeUnit.SECONDS)) {
|
||||||
|
logger.warn("线程池未能在60秒内正常关闭,强制关闭");
|
||||||
|
executor.shutdownNow();
|
||||||
|
if (!executor.awaitTermination(10, java.util.concurrent.TimeUnit.SECONDS)) {
|
||||||
|
logger.error("线程池强制关闭失败");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
logger.warn("等待线程池关闭时被中断,强制关闭");
|
||||||
|
executor.shutdownNow();
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!queried.isEmpty()) {
|
||||||
|
cacheService.saveResults(queried);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查任务是否已被取消
|
||||||
|
if (cancelMap.getOrDefault(taskId, false)) {
|
||||||
|
logger.info("任务 {} 已被取消,停止处理结果", taskId);
|
||||||
|
synchronized (taskLock) {
|
||||||
|
if (taskId.equals(currentTaskId)) {
|
||||||
|
currentTaskId = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
progressMap.remove(taskId);
|
||||||
|
cancelMap.remove(taskId);
|
||||||
|
return JsonData.buildError("任务已取消");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 5. 合并缓存和新查询结果
|
|
||||||
Map<String, Boolean> allResults = new HashMap<>(cached);
|
Map<String, Boolean> allResults = new HashMap<>(cached);
|
||||||
allResults.putAll(queried);
|
allResults.putAll(queried);
|
||||||
|
|
||||||
// 6. 统计结果
|
|
||||||
List<Map<String, Object>> unregistered = new ArrayList<>();
|
List<Map<String, Object>> unregistered = new ArrayList<>();
|
||||||
int registeredCount = 0;
|
int registeredCount = 0;
|
||||||
|
|
||||||
@@ -112,42 +227,37 @@ public class TrademarkController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
long t = (System.currentTimeMillis() - start) / 1000;
|
long t = (System.currentTimeMillis() - start) / 1000;
|
||||||
int checkedCount = list.size();
|
|
||||||
int failedCount = 0;
|
|
||||||
|
|
||||||
Map<String, Object> res = new HashMap<>();
|
Map<String, Object> res = new HashMap<>();
|
||||||
res.put("total", list.size());
|
res.put("total", list.size());
|
||||||
res.put("checked", checkedCount);
|
res.put("checked", list.size());
|
||||||
res.put("registered", registeredCount);
|
res.put("registered", registeredCount);
|
||||||
res.put("unregistered", unregistered.size());
|
res.put("unregistered", unregistered.size());
|
||||||
res.put("failed", failedCount);
|
res.put("failed", 0);
|
||||||
res.put("data", unregistered);
|
res.put("data", unregistered);
|
||||||
res.put("duration", t + "秒");
|
res.put("duration", t + "秒");
|
||||||
|
|
||||||
logger.info("完成: 共{}个,成功查询{}个(已注册{}个,未注册{}个),查询失败{}个,耗时{}秒",
|
logger.info("完成: 共{}个,已注册{}个,未注册{}个,耗时{}秒",
|
||||||
list.size(), checkedCount, registeredCount, unregistered.size(), failedCount, t);
|
list.size(), registeredCount, unregistered.size(), t);
|
||||||
|
|
||||||
|
synchronized (taskLock) {
|
||||||
|
if (taskId.equals(currentTaskId)) {
|
||||||
|
currentTaskId = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (taskId != null) {
|
|
||||||
new Thread(() -> {
|
|
||||||
try { Thread.sleep(30000); } catch (InterruptedException ignored) {}
|
|
||||||
progressMap.remove(taskId);
|
progressMap.remove(taskId);
|
||||||
cancelMap.remove(taskId);
|
cancelMap.remove(taskId);
|
||||||
}).start();
|
|
||||||
}
|
|
||||||
|
|
||||||
return JsonData.buildSuccess(res);
|
return JsonData.buildSuccess(res);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.error("筛查失败", e);
|
logger.error("筛查失败", e);
|
||||||
return JsonData.buildError("筛查失败: " + e.getMessage());
|
return JsonData.buildError("筛查失败: " + e.getMessage());
|
||||||
} finally {
|
} finally {
|
||||||
if (util != null && util.driver != null) util.driver.quit();
|
|
||||||
cacheService.cleanExpired();
|
cacheService.cleanExpired();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 查询品牌筛查进度
|
|
||||||
*/
|
|
||||||
@GetMapping("/brandCheckProgress")
|
@GetMapping("/brandCheckProgress")
|
||||||
public JsonData getBrandCheckProgress(@RequestParam("taskId") String taskId) {
|
public JsonData getBrandCheckProgress(@RequestParam("taskId") String taskId) {
|
||||||
Integer current = progressMap.get(taskId);
|
Integer current = progressMap.get(taskId);
|
||||||
@@ -159,23 +269,58 @@ public class TrademarkController {
|
|||||||
return JsonData.buildSuccess(result);
|
return JsonData.buildSuccess(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 取消品牌筛查任务
|
|
||||||
*/
|
|
||||||
@PostMapping("/cancelBrandCheck")
|
@PostMapping("/cancelBrandCheck")
|
||||||
public JsonData cancelBrandCheck(@RequestBody Map<String, String> request) {
|
public JsonData cancelBrandCheck(@RequestBody Map<String, String> request) {
|
||||||
String taskId = request.get("taskId");
|
String taskId = request.get("taskId");
|
||||||
if (taskId != null) {
|
if (taskId != null) {
|
||||||
cancelMap.put(taskId, true);
|
forceTerminateTask(taskId);
|
||||||
logger.info("任务 {} 已标记为取消", taskId);
|
}
|
||||||
return JsonData.buildSuccess("任务已取消");
|
return JsonData.buildSuccess("任务已取消");
|
||||||
}
|
}
|
||||||
return JsonData.buildError("缺少taskId参数");
|
|
||||||
|
private void forceTerminateTask(String taskId) {
|
||||||
|
logger.info("开始强制终止任务: {}", taskId);
|
||||||
|
|
||||||
|
cancelMap.put(taskId, true);
|
||||||
|
|
||||||
|
java.util.concurrent.ExecutorService executor = taskExecutors.remove(taskId);
|
||||||
|
if (executor != null && !executor.isShutdown()) {
|
||||||
|
logger.info("强制关闭任务 {} 的线程池", taskId);
|
||||||
|
executor.shutdownNow();
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (!executor.awaitTermination(5, java.util.concurrent.TimeUnit.SECONDS)) {
|
||||||
|
logger.warn("任务 {} 的线程池未能在5秒内关闭", taskId);
|
||||||
|
} else {
|
||||||
|
logger.info("任务 {} 的线程池已成功关闭", taskId);
|
||||||
|
}
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
logger.warn("等待线程池关闭时被中断");
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SseEmitter emitter = sseEmitters.remove(taskId);
|
||||||
|
if (emitter != null) {
|
||||||
|
try {
|
||||||
|
emitter.send(SseEmitter.event().name("cancelled").data("任务已取消"));
|
||||||
|
emitter.complete();
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.warn("关闭SSE连接失败: {}", e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
progressMap.remove(taskId);
|
||||||
|
|
||||||
|
synchronized (taskLock) {
|
||||||
|
if (taskId.equals(currentTaskId)) {
|
||||||
|
currentTaskId = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info("任务 {} 强制终止完成", taskId);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 验证Excel表头
|
|
||||||
*/
|
|
||||||
@PostMapping("/validateHeaders")
|
@PostMapping("/validateHeaders")
|
||||||
public JsonData validateHeaders(@RequestParam("file") MultipartFile file,
|
public JsonData validateHeaders(@RequestParam("file") MultipartFile file,
|
||||||
@RequestParam(value = "requiredHeaders", required = false) String requiredHeadersJson) {
|
@RequestParam(value = "requiredHeaders", required = false) String requiredHeadersJson) {
|
||||||
@@ -191,7 +336,6 @@ public class TrademarkController {
|
|||||||
Map<String, Object> result = new HashMap<>();
|
Map<String, Object> result = new HashMap<>();
|
||||||
result.put("headers", headers);
|
result.put("headers", headers);
|
||||||
|
|
||||||
// 如果提供了必需表头,进行验证
|
|
||||||
if (requiredHeadersJson != null && !requiredHeadersJson.trim().isEmpty()) {
|
if (requiredHeadersJson != null && !requiredHeadersJson.trim().isEmpty()) {
|
||||||
List<String> requiredHeaders = objectMapper.readValue(requiredHeadersJson,
|
List<String> requiredHeaders = objectMapper.readValue(requiredHeadersJson,
|
||||||
objectMapper.getTypeFactory().constructCollectionType(List.class, String.class));
|
objectMapper.getTypeFactory().constructCollectionType(List.class, String.class));
|
||||||
@@ -497,6 +641,15 @@ public class TrademarkController {
|
|||||||
*/
|
*/
|
||||||
@PostMapping("/newTask")
|
@PostMapping("/newTask")
|
||||||
public JsonData newTask(@RequestParam("file") MultipartFile file) {
|
public JsonData newTask(@RequestParam("file") MultipartFile file) {
|
||||||
|
// 防止重复上传:如果已有上传任务在进行,直接拒绝
|
||||||
|
synchronized (uploadLock) {
|
||||||
|
if (isUploadingFile) {
|
||||||
|
logger.warn("文件上传被拒绝:已有上传任务正在进行中");
|
||||||
|
return JsonData.buildError("请勿重复点击,上传任务进行中");
|
||||||
|
}
|
||||||
|
isUploadingFile = true;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 1. 获取 Token 并上传文件
|
// 1. 获取 Token 并上传文件
|
||||||
String token = fangzhouApi.getToken();
|
String token = fangzhouApi.getToken();
|
||||||
@@ -512,6 +665,11 @@ public class TrademarkController {
|
|||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.error("创建任务失败", e);
|
logger.error("创建任务失败", e);
|
||||||
return JsonData.buildError("创建任务失败: " + e.getMessage());
|
return JsonData.buildError("创建任务失败: " + e.getMessage());
|
||||||
|
} finally {
|
||||||
|
// 释放上传锁
|
||||||
|
synchronized (uploadLock) {
|
||||||
|
isUploadingFile = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,79 +0,0 @@
|
|||||||
package com.tashow.erp.fx.controller;
|
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
|
|
||||||
import java.awt.*;
|
|
||||||
import java.awt.datatransfer.Clipboard;
|
|
||||||
import java.awt.datatransfer.StringSelection;
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.FileOutputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
@Slf4j
|
|
||||||
public class JavaBridge {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 直接保存字节数组为Excel文件到桌面(纯 Spring Boot 环境,无文件对话框)
|
|
||||||
*/
|
|
||||||
public String saveExcelFileToDesktop(byte[] data, String fileName) {
|
|
||||||
try {
|
|
||||||
if (data == null || data.length == 0) {
|
|
||||||
log.warn("文件数据为空,无法保存文件");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
String userHome = System.getProperty("user.home");
|
|
||||||
File desktop = new File(userHome, "Desktop");
|
|
||||||
if (!desktop.exists()) {
|
|
||||||
// 回退到用户目录
|
|
||||||
desktop = new File(userHome);
|
|
||||||
}
|
|
||||||
|
|
||||||
File file = new File(desktop, fileName);
|
|
||||||
int counter = 1;
|
|
||||||
if (fileName != null && fileName.contains(".")) {
|
|
||||||
String baseName = fileName.substring(0, fileName.lastIndexOf('.'));
|
|
||||||
String extension = fileName.substring(fileName.lastIndexOf('.'));
|
|
||||||
while (file.exists()) {
|
|
||||||
file = new File(desktop, baseName + "_" + counter + extension);
|
|
||||||
counter++;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
while (file.exists()) {
|
|
||||||
file = new File(desktop, fileName + "_" + counter);
|
|
||||||
counter++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try (FileOutputStream fos = new FileOutputStream(file)) {
|
|
||||||
fos.write(data);
|
|
||||||
fos.flush();
|
|
||||||
}
|
|
||||||
|
|
||||||
String filePath = file.getAbsolutePath();
|
|
||||||
log.info("Excel文件已保存: {}", filePath);
|
|
||||||
return filePath;
|
|
||||||
} catch (IOException e) {
|
|
||||||
log.error("保存Excel文件失败: {}", e.getMessage(), e);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 复制文本到系统剪贴板
|
|
||||||
*/
|
|
||||||
public boolean copyToClipboard(String text) {
|
|
||||||
try {
|
|
||||||
if (text == null || text.trim().isEmpty()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
|
|
||||||
StringSelection selection = new StringSelection(text);
|
|
||||||
clipboard.setContents(selection, null);
|
|
||||||
return true;
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.error("复制到剪贴板失败: {}", e.getMessage());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -34,7 +34,7 @@ public class UsptoApiTest {
|
|||||||
TrademarkCheckUtil trademarkUtil = context.getBean(TrademarkCheckUtil.class);
|
TrademarkCheckUtil trademarkUtil = context.getBean(TrademarkCheckUtil.class);
|
||||||
|
|
||||||
// 测试单品牌查询(获取详细结果)
|
// 测试单品牌查询(获取详细结果)
|
||||||
testSingleBrandWithDetailedResults("Remorlet");
|
testSingleBrandWithDetailedResults("SummitFlare");
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
System.err.println("测试失败: " + e.getMessage());
|
System.err.println("测试失败: " + e.getMessage());
|
||||||
@@ -209,7 +209,7 @@ public class UsptoApiTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 极简输出 - 只显示最终结果
|
// 极简输出 - 只显示最终结果
|
||||||
System.out.println(isRegistered);
|
System.out.println(results);
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
System.err.println("=== 测试失败 ===");
|
System.err.println("=== 测试失败 ===");
|
||||||
|
|||||||
@@ -2,30 +2,49 @@ package com.tashow.erp.utils;
|
|||||||
import cn.hutool.http.HttpUtil;
|
import cn.hutool.http.HttpUtil;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 代理IP池
|
* 代理IP池
|
||||||
*/
|
*/
|
||||||
@Component
|
@Component
|
||||||
public class ProxyPool {
|
public class ProxyPool {
|
||||||
|
|
||||||
private static final String API_URL = "http://api.tianqiip.com/getip?secret=h6x09x0eenxuf4s7&num=1&type=txt&port=2&time=3&mr=1&sign=620719f6b7d66744b0216a4f61a6bcee";
|
private static final String API_URL = "http://api.tianqiip.com/getip?secret=y0thbcco1rgxn9e9&num=%d&type=txt&port=2&time=3&mr=1&sign=a8a42f3cd3f22a7fbf84530deb91c1d8";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取一个代理IP
|
* 获取一个代理IP
|
||||||
* @return 代理地址,格式:host:port,如 123.96.236.32:40016
|
* @return 代理地址,格式:host:port,如 123.96.236.32:40016
|
||||||
*/
|
*/
|
||||||
public String getProxy() {
|
public String getProxy() {
|
||||||
|
List<String> proxies = getProxies(1);
|
||||||
|
return proxies.isEmpty() ? null : proxies.get(0);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 批量获取代理IP
|
||||||
|
* @param num 需要获取的代理数量
|
||||||
|
* @return 代理地址列表
|
||||||
|
*/
|
||||||
|
public List<String> getProxies(int num) {
|
||||||
|
List<String> proxies = new ArrayList<>();
|
||||||
try {
|
try {
|
||||||
String response = HttpUtil.get(API_URL);
|
String url = String.format(API_URL, num);
|
||||||
|
String response = HttpUtil.get(url);
|
||||||
if (response != null && !response.trim().isEmpty()) {
|
if (response != null && !response.trim().isEmpty()) {
|
||||||
String proxy = response.trim();
|
String[] lines = response.trim().split("\n");
|
||||||
System.out.println("获取到代理: " + proxy);
|
for (String line : lines) {
|
||||||
return proxy;
|
String proxy = line.trim();
|
||||||
|
if (!proxy.isEmpty()) {
|
||||||
|
proxies.add(proxy);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
System.out.println("获取到 " + proxies.size() + " 个代理");
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
System.err.println("获取代理失败: " + e.getMessage());
|
System.err.println("获取代理失败: " + e.getMessage());
|
||||||
}
|
}
|
||||||
return null;
|
return proxies;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,55 +1,157 @@
|
|||||||
package com.tashow.erp.utils;
|
package com.tashow.erp.utils;
|
||||||
|
|
||||||
import com.tashow.erp.service.BrandTrademarkCacheService;
|
|
||||||
import jakarta.annotation.PreDestroy;
|
|
||||||
import org.openqa.selenium.JavascriptExecutor;
|
import org.openqa.selenium.JavascriptExecutor;
|
||||||
import org.openqa.selenium.chrome.ChromeDriver;
|
import org.openqa.selenium.chrome.ChromeDriver;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 商标检查工具
|
* 商标检查工具 - 无状态设计
|
||||||
* 检测到403时自动切换代理并重试
|
* 每次调用使用独立的Driver和代理
|
||||||
*/
|
*/
|
||||||
@Component
|
@Component
|
||||||
public class TrademarkCheckUtil {
|
public class TrademarkCheckUtil {
|
||||||
@Autowired
|
|
||||||
private ProxyPool proxyPool;
|
|
||||||
@Autowired
|
|
||||||
private BrandTrademarkCacheService cacheService;
|
|
||||||
public ChromeDriver driver;
|
|
||||||
private final int maxRetries = 3;
|
|
||||||
|
|
||||||
private String normalize(String name) {
|
private static String normalize(String name) {
|
||||||
return name.toLowerCase().replaceAll("[^a-z0-9]", "");
|
return name.toLowerCase().replaceAll("[^a-z0-9]", "");
|
||||||
}
|
}
|
||||||
|
|
||||||
private synchronized void ensureInit() {
|
/**
|
||||||
if (driver == null) {
|
* 批量检查商标(使用指定代理)
|
||||||
driver = SeleniumUtil.createDriver(true, proxyPool.getProxy());
|
* @param brands 品牌列表
|
||||||
|
* @param proxy 代理地址,格式:host:port
|
||||||
|
* @return 检查结果 Map<品牌, 是否已注册>
|
||||||
|
*/
|
||||||
|
public static Map<String, Boolean> batchCheck(List<String> brands, String proxy) {
|
||||||
|
return batchCheck(brands, proxy, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量检查商标(使用指定代理,支持取消检查)
|
||||||
|
* @param brands 品牌列表
|
||||||
|
* @param proxy 代理地址,格式:host:port
|
||||||
|
* @param taskId 任务ID,用于取消检查
|
||||||
|
* @param cancelMap 取消状态映射
|
||||||
|
* @return 检查结果 Map<品牌, 是否已注册>
|
||||||
|
*/
|
||||||
|
public static Map<String, Boolean> batchCheck(List<String> brands, String proxy, String taskId, java.util.Map<String, Boolean> cancelMap) {
|
||||||
|
return batchCheck(brands, proxy, taskId, cancelMap, -1, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Map<String, Boolean> batchCheck(List<String> brands, String proxy, String taskId, java.util.Map<String, Boolean> cancelMap, int processIndex) {
|
||||||
|
return batchCheck(brands, proxy, taskId, cancelMap, processIndex, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量检查商标(使用指定代理,支持取消检查和进程标识)
|
||||||
|
* @param brands 品牌列表
|
||||||
|
* @param proxy 代理地址,格式:host:port
|
||||||
|
* @param taskId 任务ID,用于取消检查
|
||||||
|
* @param cancelMap 取消状态映射
|
||||||
|
* @param processIndex 进程索引,用于日志标识
|
||||||
|
* @return 检查结果 Map<品牌, 是否已注册>
|
||||||
|
*/
|
||||||
|
public static Map<String, Boolean> batchCheck(List<String> brands, String proxy, String taskId, java.util.Map<String, Boolean> cancelMap, int processIndex, java.util.Map<String, org.springframework.web.servlet.mvc.method.annotation.SseEmitter> sseEmitters) {
|
||||||
|
Map<String, Boolean> resultMap = new HashMap<>();
|
||||||
|
String processPrefix = processIndex >= 0 ? "进程" + processIndex + ":" : "";
|
||||||
|
|
||||||
|
ChromeDriver driver = null;
|
||||||
|
String currentProxy = proxy; // 当前使用的代理
|
||||||
|
|
||||||
|
// 初始化Driver,失败时重试并换IP
|
||||||
|
int initRetryCount = 0;
|
||||||
|
while (initRetryCount < 5 && driver == null) {
|
||||||
|
try {
|
||||||
|
driver = SeleniumUtil.createDriver(true, currentProxy);
|
||||||
driver.get("https://tmsearch.uspto.gov/search/search-results");
|
driver.get("https://tmsearch.uspto.gov/search/search-results");
|
||||||
try { Thread.sleep(6000); } catch (InterruptedException ignored) {}
|
Thread.sleep(6000);
|
||||||
|
break; // 成功则跳出循环
|
||||||
|
} catch (Exception initError) {
|
||||||
|
System.err.println(processPrefix + "Driver初始化失败(" + (initRetryCount + 1) + "/5): " + initError.getMessage());
|
||||||
|
|
||||||
|
if (driver != null) {
|
||||||
|
try { driver.quit(); } catch (Exception ignored) {}
|
||||||
|
driver = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取新代理重试
|
||||||
|
if (initRetryCount < 2) {
|
||||||
|
try {
|
||||||
|
ProxyPool proxyPool = new ProxyPool();
|
||||||
|
String newProxy = proxyPool.getProxy();
|
||||||
|
if (newProxy != null) {
|
||||||
|
currentProxy = newProxy;
|
||||||
|
System.out.println(processPrefix + "初始化失败,切换到新代理: " + currentProxy);
|
||||||
|
} else {
|
||||||
|
System.err.println(processPrefix + "获取新代理失败,使用原代理重试");
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
System.err.println(processPrefix + "获取新代理异常: " + e.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized Map<String, Boolean> batchCheck(List<String> brands, Map<String, Boolean> alreadyQueried) {
|
initRetryCount++;
|
||||||
Map<String, Boolean> resultMap = new HashMap<>();
|
if (initRetryCount < 3) {
|
||||||
|
try { Thread.sleep(2000); } catch (InterruptedException ignored) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (driver == null) {
|
||||||
|
System.err.println(processPrefix + "Driver初始化失败,已重试3次,跳过该批次");
|
||||||
|
return resultMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
for (String brand : brands) {
|
for (String brand : brands) {
|
||||||
|
// 检查是否已取消
|
||||||
|
if (taskId != null && cancelMap != null && cancelMap.getOrDefault(taskId, false)) {
|
||||||
|
System.out.println("检测到任务已取消,停止处理品牌: " + brand);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
int retryCount = 0;
|
int retryCount = 0;
|
||||||
boolean success = false;
|
boolean success = false;
|
||||||
|
|
||||||
while (retryCount < 5 && !success) {
|
while (retryCount < 5 && !success) {
|
||||||
|
// 在重试循环中也检查取消状态
|
||||||
|
if (taskId != null && cancelMap != null && cancelMap.getOrDefault(taskId, false)) {
|
||||||
|
System.out.println("检测到任务已取消,停止重试品牌: " + brand);
|
||||||
|
return resultMap;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
ensureInit();
|
|
||||||
String script = "fetch('https://tmsearch.uspto.gov/prod-stage-v1-0-0/tmsearch',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({query:{bool:{must:[{bool:{should:[{match_phrase:{WM:{query:'" + brand.replace("'", "\\'")+"',boost:5}}}]}}]}},size:100})}).then(r=>{if(!r.ok){return arguments[arguments.length-1]({hits:[],error:'HTTP '+r.status+': '+r.statusText});}return r.text().then(text=>{if(text.startsWith('<!DOCTYPE')||text.startsWith('<html')){return arguments[arguments.length-1]({hits:[],error:'HTML response detected, likely blocked'});}try{const d=JSON.parse(text);return arguments[arguments.length-1]({hits:d?.hits?.hits||[],error:null});}catch(e){return arguments[arguments.length-1]({hits:[],error:'JSON parse error: '+e.message});}});}).catch(e=>arguments[arguments.length-1]({hits:[],error:e.message}));";
|
String script = "fetch('https://tmsearch.uspto.gov/prod-stage-v1-0-0/tmsearch',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({query:{bool:{must:[{bool:{should:[{match_phrase:{WM:{query:'" + brand.replace("'", "\\'")+"',boost:5}}}]}}]}},size:100})}).then(r=>{if(!r.ok){return arguments[arguments.length-1]({hits:[],error:'HTTP '+r.status+': '+r.statusText});}return r.text().then(text=>{if(text.startsWith('<!DOCTYPE')||text.startsWith('<html')){return arguments[arguments.length-1]({hits:[],error:'HTML response detected, likely blocked'});}try{const d=JSON.parse(text);return arguments[arguments.length-1]({hits:d?.hits?.hits||[],error:null});}catch(e){return arguments[arguments.length-1]({hits:[],error:'JSON parse error: '+e.message});}});}).catch(e=>arguments[arguments.length-1]({hits:[],error:e.message}));";
|
||||||
@SuppressWarnings("unchecked") Map<String, Object> result = (Map<String, Object>) ((JavascriptExecutor) driver).executeAsyncScript(script);
|
@SuppressWarnings("unchecked") Map<String, Object> result = (Map<String, Object>) ((JavascriptExecutor) driver).executeAsyncScript(script);
|
||||||
String error = (String) result.get("error");
|
String error = (String) result.get("error");
|
||||||
|
|
||||||
if (error != null && (error.contains("HTTP 403") || error.contains("Failed to fetch") || error.contains("NetworkError") || error.contains("TypeError") || error.contains("script timeout"))) {
|
if (error != null && (error.contains("HTTP 403") || error.contains("Failed to fetch") || error.contains("NetworkError") || error.contains("TypeError") || error.contains("script timeout"))) {
|
||||||
System.err.println(brand + " 查询失败(" + (retryCount + 1) + "/3): " + error + ",切换代理...");
|
System.err.println(processPrefix + brand + " 查询失败(" + (retryCount + 1) + "/5): " + error + ",切换代理...");
|
||||||
|
|
||||||
|
// 对于网络错误,获取新代理并重新创建Driver
|
||||||
|
if (error.contains("Failed to fetch") || error.contains("HTTP 403") || error.contains("NetworkError") || error.contains("ERR_CONNECTION_RESET")) {
|
||||||
|
try {
|
||||||
if (driver != null) driver.quit();
|
if (driver != null) driver.quit();
|
||||||
driver = null;
|
|
||||||
|
// 获取新的代理IP
|
||||||
|
ProxyPool proxyPool = new ProxyPool();
|
||||||
|
String newProxy = proxyPool.getProxy();
|
||||||
|
if (newProxy != null) {
|
||||||
|
currentProxy = newProxy;
|
||||||
|
System.out.println(processPrefix + "切换到新代理: " + currentProxy);
|
||||||
|
} else {
|
||||||
|
System.err.println(processPrefix + "获取新代理失败,使用原代理");
|
||||||
|
}
|
||||||
|
|
||||||
|
driver = SeleniumUtil.createDriver(true, currentProxy);
|
||||||
|
driver.get("https://tmsearch.uspto.gov/search/search-results");
|
||||||
|
Thread.sleep(3000); // 缩短等待时间
|
||||||
|
} catch (Exception e) {
|
||||||
|
System.err.println(processPrefix + "重新创建Driver失败: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
retryCount++;
|
retryCount++;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -63,38 +165,53 @@ public class TrademarkCheckUtil {
|
|||||||
@SuppressWarnings("unchecked") Map<String, Object> source = (Map<String, Object>) hit.get("source");
|
@SuppressWarnings("unchecked") Map<String, Object> source = (Map<String, Object>) hit.get("source");
|
||||||
if (source != null && input.equals(normalize((String) source.get("wordmark")))) {
|
if (source != null && input.equals(normalize((String) source.get("wordmark")))) {
|
||||||
Number code = (Number) source.get("statusCode");
|
Number code = (Number) source.get("statusCode");
|
||||||
if (code != null && (code.intValue() == 688 || code.intValue() == 700)) {
|
if (code != null && (code.intValue() == 688 || code.intValue() == 700 || code.intValue() == 686)) {
|
||||||
registered = true;
|
registered = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
resultMap.put(brand, registered);
|
resultMap.put(brand, registered);
|
||||||
System.out.println(brand + " -> " + (registered ? "✓" : "✗"));
|
System.out.println(processPrefix + brand + " -> " + (registered ? "✓" : "✗"));
|
||||||
success = true;
|
success = true;
|
||||||
|
|
||||||
|
// 推送SSE进度
|
||||||
|
if (taskId != null && sseEmitters != null) {
|
||||||
|
org.springframework.web.servlet.mvc.method.annotation.SseEmitter emitter = sseEmitters.get(taskId);
|
||||||
|
if (emitter != null) {
|
||||||
|
try {
|
||||||
|
emitter.send(org.springframework.web.servlet.mvc.method.annotation.SseEmitter.event()
|
||||||
|
.name("progress")
|
||||||
|
.data(processPrefix + brand + " -> " + (registered ? "✓" : "✗")));
|
||||||
|
} catch (Exception ignored) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
System.err.println(brand + " -> [查询失败: " + error + "]");
|
System.err.println(processPrefix + brand + " -> [查询失败: " + error + "]");
|
||||||
resultMap.put(brand, true);
|
resultMap.put(brand, true);
|
||||||
success = true;
|
success = true;
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
System.err.println(brand + " 查询异常(" + (retryCount + 1) + "/3): " + e.getMessage());
|
System.err.println(processPrefix + brand + " 查询异常(" + (retryCount + 1) + "/5): " + e.getMessage());
|
||||||
if (driver != null) driver.quit();
|
|
||||||
driver = null;
|
|
||||||
retryCount++;
|
retryCount++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!success) {
|
if (!success) {
|
||||||
System.err.println(brand + " -> [查询失败: 已重试3次]");
|
System.err.println(processPrefix + brand + " -> [查询失败: 已重试5次]");
|
||||||
resultMap.put(brand, true);
|
resultMap.put(brand, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return resultMap;
|
} catch (Exception e) {
|
||||||
|
System.err.println("Driver初始化失败: " + e.getMessage());
|
||||||
|
} finally {
|
||||||
|
if (driver != null) {
|
||||||
|
try {
|
||||||
|
driver.quit();
|
||||||
|
} catch (Exception ignored) {}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@PreDestroy
|
return resultMap;
|
||||||
public void cleanup() {
|
|
||||||
if (driver != null) driver.quit();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -203,7 +203,8 @@ public class ClientAccountController extends BaseController {
|
|||||||
"permissions", account.getPermissions(),
|
"permissions", account.getPermissions(),
|
||||||
"accountName", account.getAccountName(),
|
"accountName", account.getAccountName(),
|
||||||
"expireTime", account.getExpireTime(),
|
"expireTime", account.getExpireTime(),
|
||||||
"accountType", account.getAccountType()
|
"accountType", account.getAccountType(),
|
||||||
|
"registerTime", account.getCreateTime()
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
@@ -244,7 +245,8 @@ public class ClientAccountController extends BaseController {
|
|||||||
"accountName", account.getAccountName(),
|
"accountName", account.getAccountName(),
|
||||||
"expireTime", account.getExpireTime(),
|
"expireTime", account.getExpireTime(),
|
||||||
"accountType", account.getAccountType(),
|
"accountType", account.getAccountType(),
|
||||||
"deviceTrialExpired", deviceTrialExpired
|
"deviceTrialExpired", deviceTrialExpired,
|
||||||
|
"registerTime", account.getCreateTime()
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -309,7 +311,8 @@ public class ClientAccountController extends BaseController {
|
|||||||
"permissions", clientAccount.getPermissions(),
|
"permissions", clientAccount.getPermissions(),
|
||||||
"accountName", clientAccount.getAccountName(),
|
"accountName", clientAccount.getAccountName(),
|
||||||
"expireTime", clientAccount.getExpireTime(),
|
"expireTime", clientAccount.getExpireTime(),
|
||||||
"accountType", clientAccount.getAccountType()
|
"accountType", clientAccount.getAccountType(),
|
||||||
|
"registerTime", clientAccount.getCreateTime()
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -87,8 +87,6 @@ public class SysLoginController
|
|||||||
ajax.put("user", user);
|
ajax.put("user", user);
|
||||||
ajax.put("roles", roles);
|
ajax.put("roles", roles);
|
||||||
ajax.put("permissions", permissions);
|
ajax.put("permissions", permissions);
|
||||||
ajax.put("isDefaultModifyPwd", initPasswordIsModify(user.getPwdUpdateDate()));
|
|
||||||
ajax.put("isPasswordExpired", passwordIsExpiration(user.getPwdUpdateDate()));
|
|
||||||
return ajax;
|
return ajax;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user