feat(client): 实现跟卖精灵异步启动和Chrome驱动预加载- 在GenmaiServiceImpl中添加@Async注解实现异步启动跟卖精灵- 增加ChromeDriverPreloader组件预加载Chrome驱动- 添加AsyncConfig配置类启用异步支持
- 优化跟卖精灵启动提示信息和加载状态显示 - 移除Java代码中关于刷新令牌的相关逻辑和依赖- 更新版本号从2.5.5到2.5.6
This commit is contained in:
@@ -40,7 +40,7 @@ const navigationHistory = ref<string[]>(['rakuten'])
|
|||||||
const currentHistoryIndex = ref(0)
|
const currentHistoryIndex = ref(0)
|
||||||
|
|
||||||
// 应用状态
|
// 应用状态
|
||||||
const activeMenu = ref('rakuten')
|
const activeMenu = ref(localStorage.getItem('active-menu') || 'rakuten')
|
||||||
const isAuthenticated = ref(false)
|
const isAuthenticated = ref(false)
|
||||||
const showAuthDialog = ref(false)
|
const showAuthDialog = ref(false)
|
||||||
const showRegDialog = ref(false)
|
const showRegDialog = ref(false)
|
||||||
@@ -190,6 +190,7 @@ function handleMenuSelect(key: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
activeMenu.value = key
|
activeMenu.value = key
|
||||||
|
localStorage.setItem('active-menu', key)
|
||||||
addToHistory(key)
|
addToHistory(key)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -778,7 +779,7 @@ onUnmounted(() => {
|
|||||||
width: 180px;
|
width: 180px;
|
||||||
min-width: 180px;
|
min-width: 180px;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
background: #ffffff;
|
background: #F0F0F0;
|
||||||
border-right: 1px solid #e8eaec;
|
border-right: 1px solid #e8eaec;
|
||||||
padding: 16px 12px;
|
padding: 16px 12px;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
|||||||
@@ -33,20 +33,29 @@ async function getToken(): Promise<string> {
|
|||||||
|
|
||||||
async function request<T>(path: string, options: RequestInit & { signal?: AbortSignal }): Promise<T> {
|
async function request<T>(path: string, options: RequestInit & { signal?: AbortSignal }): Promise<T> {
|
||||||
const token = await getToken();
|
const token = await getToken();
|
||||||
const res = await fetch(`${resolveBase(path)}${path}`, {
|
let res: Response;
|
||||||
credentials: 'omit',
|
|
||||||
cache: 'no-store',
|
try {
|
||||||
...options,
|
res = await fetch(`${resolveBase(path)}${path}`, {
|
||||||
headers: {
|
credentials: 'omit',
|
||||||
'Content-Type': 'application/json;charset=UTF-8',
|
cache: 'no-store',
|
||||||
...(token ? { 'Authorization': `Bearer ${token}` } : {}),
|
...options,
|
||||||
...options.headers
|
headers: {
|
||||||
}
|
'Content-Type': 'application/json;charset=UTF-8',
|
||||||
});
|
...(token ? { 'Authorization': `Bearer ${token}` } : {}),
|
||||||
|
...options.headers
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
throw new Error('无法连接服务器,请检查网络后重试');
|
||||||
|
}
|
||||||
|
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
|
if (res.status >= 500) {
|
||||||
|
throw new Error('无法连接服务器,请检查网络后重试');
|
||||||
|
}
|
||||||
const text = await res.text().catch(() => '');
|
const text = await res.text().catch(() => '');
|
||||||
throw new Error(text || `HTTP ${res.status}`);
|
throw new Error(text || '无法连接服务器,请检查网络后重试');
|
||||||
}
|
}
|
||||||
|
|
||||||
const contentType = res.headers.get('content-type') || '';
|
const contentType = res.headers.get('content-type') || '';
|
||||||
@@ -81,18 +90,27 @@ export const http = {
|
|||||||
|
|
||||||
async upload<T>(path: string, form: FormData, signal?: AbortSignal) {
|
async upload<T>(path: string, form: FormData, signal?: AbortSignal) {
|
||||||
const token = await getToken();
|
const token = await getToken();
|
||||||
const res = await fetch(`${resolveBase(path)}${path}`, {
|
let res: Response;
|
||||||
method: 'POST',
|
|
||||||
body: form,
|
try {
|
||||||
credentials: 'omit',
|
res = await fetch(`${resolveBase(path)}${path}`, {
|
||||||
cache: 'no-store',
|
method: 'POST',
|
||||||
headers: token ? { 'Authorization': `Bearer ${token}` } : {},
|
body: form,
|
||||||
signal
|
credentials: 'omit',
|
||||||
});
|
cache: 'no-store',
|
||||||
|
headers: token ? { 'Authorization': `Bearer ${token}` } : {},
|
||||||
|
signal
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
throw new Error('无法连接服务器,请检查网络后重试');
|
||||||
|
}
|
||||||
|
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
|
if (res.status >= 500) {
|
||||||
|
throw new Error('无法连接服务器,请检查网络后重试');
|
||||||
|
}
|
||||||
const text = await res.text().catch(() => '');
|
const text = await res.text().catch(() => '');
|
||||||
throw new Error(text || `HTTP ${res.status}`);
|
throw new Error(text || '无法连接服务器,请检查网络后重试');
|
||||||
}
|
}
|
||||||
|
|
||||||
const contentType = res.headers.get('content-type') || '';
|
const contentType = res.headers.get('content-type') || '';
|
||||||
|
|||||||
@@ -170,7 +170,6 @@ async function batchGetProductInfo(asinList: string[]) {
|
|||||||
// 处理完成状态更新
|
// 处理完成状态更新
|
||||||
progressPercentage.value = 100
|
progressPercentage.value = 100
|
||||||
currentAsin.value = '处理完成'
|
currentAsin.value = '处理完成'
|
||||||
selectedFileName.value = ''
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -178,7 +177,6 @@ async function batchGetProductInfo(asinList: string[]) {
|
|||||||
if (error.name !== 'AbortError') {
|
if (error.name !== 'AbortError') {
|
||||||
showMessage(error.message || '批量获取产品信息失败', 'error')
|
showMessage(error.message || '批量获取产品信息失败', 'error')
|
||||||
currentAsin.value = '处理失败'
|
currentAsin.value = '处理失败'
|
||||||
selectedFileName.value = ''
|
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
tableLoading.value = false
|
tableLoading.value = false
|
||||||
@@ -261,7 +259,6 @@ function stopFetch() {
|
|||||||
abortController = null
|
abortController = null
|
||||||
loading.value = false
|
loading.value = false
|
||||||
currentAsin.value = '已停止'
|
currentAsin.value = '已停止'
|
||||||
selectedFileName.value = ''
|
|
||||||
showMessage('已停止获取产品数据', 'info')
|
showMessage('已停止获取产品数据', 'info')
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -273,8 +270,10 @@ async function openGenmaiSpirit() {
|
|||||||
genmaiLoading.value = true
|
genmaiLoading.value = true
|
||||||
try {
|
try {
|
||||||
await systemApi.openGenmaiSpirit(selectedGenmaiAccountId.value)
|
await systemApi.openGenmaiSpirit(selectedGenmaiAccountId.value)
|
||||||
showMessage('跟卖精灵已打开', 'success')
|
showMessage('跟卖精灵正在启动,请稍候...', 'success')
|
||||||
} finally {
|
setTimeout(() => { genmaiLoading.value = false }, 3000)
|
||||||
|
} catch (error: any) {
|
||||||
|
showMessage(error.message || '启动失败', 'error')
|
||||||
genmaiLoading.value = false
|
genmaiLoading.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -366,7 +365,7 @@ onMounted(async () => {
|
|||||||
>
|
>
|
||||||
<span class="acct-row">
|
<span class="acct-row">
|
||||||
<span :class="['status-dot', acc.status === 1 ? 'on' : 'off']"></span>
|
<span :class="['status-dot', acc.status === 1 ? 'on' : 'off']"></span>
|
||||||
<img class="avatar" src="/image/img_v3_02qd_052605f0-4be3-44db-9691-35ee5ff6201g.jpg" alt="avatar" />
|
<img class="avatar" src="/image/user.png" alt="avatar" />
|
||||||
<span class="acct-text">{{ acc.name || acc.username }}</span>
|
<span class="acct-text">{{ acc.name || acc.username }}</span>
|
||||||
<span v-if="selectedGenmaiAccountId === acc.id" class="acct-check">✔️</span>
|
<span v-if="selectedGenmaiAccountId === acc.id" class="acct-check">✔️</span>
|
||||||
</span>
|
</span>
|
||||||
@@ -391,7 +390,7 @@ onMounted(async () => {
|
|||||||
<div class="step-index">2</div>
|
<div class="step-index">2</div>
|
||||||
<div class="step-card">
|
<div class="step-card">
|
||||||
<div class="step-header"><div class="title">启动服务</div></div>
|
<div class="step-header"><div class="title">启动服务</div></div>
|
||||||
<div class="desc">请确保设备已安装Chrome浏览器,否则服务将无法启动。打开跟卖精灵将关闭Chrome浏览器进程。</div>
|
<div class="desc">请确保设备已安装Chrome浏览器,否则服务将无法启动。打开跟卖精灵将关闭Chrome浏览器进程。首次启动需初始化浏览器驱动,可能需要1-3分钟。</div>
|
||||||
<div class="action-buttons column">
|
<div class="action-buttons column">
|
||||||
<el-button
|
<el-button
|
||||||
size="small"
|
size="small"
|
||||||
@@ -400,7 +399,7 @@ onMounted(async () => {
|
|||||||
@click="openGenmaiSpirit"
|
@click="openGenmaiSpirit"
|
||||||
>
|
>
|
||||||
<span v-if="!genmaiLoading">启动服务</span>
|
<span v-if="!genmaiLoading">启动服务</span>
|
||||||
<span v-else>⟳ 启动中...</span>
|
<span v-else><span class="spinner">⟳</span> 启动中...</span>
|
||||||
</el-button>
|
</el-button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -680,6 +679,7 @@ onMounted(async () => {
|
|||||||
.price { color: #e6a23c; font-weight: 600; }
|
.price { color: #e6a23c; font-weight: 600; }
|
||||||
.table-loading { position: absolute; inset: 0; background: rgba(255, 255, 255, 0.95); display: flex; flex-direction: column; justify-content: center; align-items: center; font-size: 14px; color: #606266; }
|
.table-loading { position: absolute; inset: 0; background: rgba(255, 255, 255, 0.95); display: flex; flex-direction: column; justify-content: center; align-items: center; font-size: 14px; color: #606266; }
|
||||||
.spinner { font-size: 24px; animation: spin 1s linear infinite; margin-bottom: 8px; }
|
.spinner { font-size: 24px; animation: spin 1s linear infinite; margin-bottom: 8px; }
|
||||||
|
.action-buttons .spinner { font-size: 14px; margin-bottom: 0; display: inline-block; }
|
||||||
@keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
|
@keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
|
||||||
.pagination-fixed { flex-shrink: 0; padding: 8px 12px 0 12px; background: #fff; display: flex; justify-content: flex-end; }
|
.pagination-fixed { flex-shrink: 0; padding: 8px 12px 0 12px; background: #fff; display: flex; justify-content: flex-end; }
|
||||||
.pagination-fixed :deep(.el-pager li.is-active) { border: 1px solid #1677FF; border-radius: 4px; color: #1677FF; background: #fff; }
|
.pagination-fixed :deep(.el-pager li.is-active) { border: 1px solid #1677FF; border-radius: 4px; color: #1677FF; background: #fff; }
|
||||||
|
|||||||
@@ -136,7 +136,7 @@ export default defineComponent({ name: 'AccountManager' })
|
|||||||
<div v-for="a in accounts" :key="a.id" class="row">
|
<div v-for="a in accounts" :key="a.id" class="row">
|
||||||
<span :class="['dot', a.status === 1 ? 'on' : 'off']"></span>
|
<span :class="['dot', a.status === 1 ? 'on' : 'off']"></span>
|
||||||
<div class="user-info">
|
<div class="user-info">
|
||||||
<img class="avatar" src="/image/img_v3_02qd_052605f0-4be3-44db-9691-35ee5ff6201g.jpg" />
|
<img class="avatar" src="/image/user.png" />
|
||||||
<span class="name">{{ a.name || a.username }}</span>
|
<span class="name">{{ a.name || a.username }}</span>
|
||||||
</div>
|
</div>
|
||||||
<span class="date">{{ formatDate(a) }}</span>
|
<span class="date">{{ formatDate(a) }}</span>
|
||||||
|
|||||||
@@ -485,7 +485,7 @@ onMounted(() => {
|
|||||||
</el-checkbox>
|
</el-checkbox>
|
||||||
</div>
|
</div>
|
||||||
<div class="setting-desc" style="margin-top: 4px;">
|
<div class="setting-desc" style="margin-top: 4px;">
|
||||||
如果自动安装,将仅会安装到新版本后,但需手动点击"重启升级"后才可升级
|
如关闭自动安装,你仍会收到新版本提示,但需手动点击"重启升级"后才可升级
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
<div v-if="stage === 'check'" class="update-content">
|
<div v-if="stage === 'check'" class="update-content">
|
||||||
<div class="update-layout">
|
<div class="update-layout">
|
||||||
<div class="left-pane">
|
<div class="left-pane">
|
||||||
<img src="/icon/icon.png" class="app-icon app-icon-large" alt="App Icon"/>
|
<img src="/icon/icon1.png" class="app-icon app-icon-large" alt="App Icon"/>
|
||||||
</div>
|
</div>
|
||||||
<div class="right-pane">
|
<div class="right-pane">
|
||||||
<p class="announce">新版本的"{{ appName }}"已经发布</p>
|
<p class="announce">新版本的"{{ appName }}"已经发布</p>
|
||||||
@@ -41,7 +41,7 @@
|
|||||||
<div v-else-if="stage === 'downloading'" class="update-content">
|
<div v-else-if="stage === 'downloading'" class="update-content">
|
||||||
<div class="download-main">
|
<div class="download-main">
|
||||||
<div class="download-icon">
|
<div class="download-icon">
|
||||||
<img src="/icon/icon.png" class="app-icon" alt="App Icon"/>
|
<img src="/icon/icon1.png" class="app-icon" alt="App Icon"/>
|
||||||
</div>
|
</div>
|
||||||
<div class="download-content">
|
<div class="download-content">
|
||||||
<div class="download-info">
|
<div class="download-info">
|
||||||
@@ -64,7 +64,7 @@
|
|||||||
<div v-else-if="stage === 'completed'" class="update-content">
|
<div v-else-if="stage === 'completed'" class="update-content">
|
||||||
<div class="download-main">
|
<div class="download-main">
|
||||||
<div class="download-icon">
|
<div class="download-icon">
|
||||||
<img src="/icon/icon.png" class="app-icon" alt="App Icon"/>
|
<img src="/icon/icon1.png" class="app-icon" alt="App Icon"/>
|
||||||
</div>
|
</div>
|
||||||
<div class="download-content">
|
<div class="download-content">
|
||||||
<div class="download-info">
|
<div class="download-info">
|
||||||
|
|||||||
@@ -32,6 +32,14 @@ const displayUsername = computed(() => {
|
|||||||
return props.isAuthenticated ? props.currentUsername : '未登录'
|
return props.isAuthenticated ? props.currentUsername : '未登录'
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const menuItems = [
|
||||||
|
{ command: 'check-update', label: computed(() => `检查更新 v${props.currentVersion}`), class: 'menu-item' },
|
||||||
|
{ command: 'account-manager', label: '我的电商账号', class: 'menu-item' },
|
||||||
|
{ command: 'device', label: '我的设备', class: 'menu-item' },
|
||||||
|
{ command: 'settings', label: '设置', class: 'menu-item' },
|
||||||
|
{ command: 'logout', label: '退出', class: 'menu-item logout-item', showIf: () => props.isAuthenticated }
|
||||||
|
]
|
||||||
|
|
||||||
async function handleMinimize() {
|
async function handleMinimize() {
|
||||||
await (window as any).electronAPI.windowMinimize()
|
await (window as any).electronAPI.windowMinimize()
|
||||||
}
|
}
|
||||||
@@ -45,24 +53,17 @@ async function handleClose() {
|
|||||||
await (window as any).electronAPI.windowClose()
|
await (window as any).electronAPI.windowClose()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const commandMap: Record<string, keyof Emits> = {
|
||||||
|
logout: 'logout',
|
||||||
|
device: 'open-device',
|
||||||
|
settings: 'open-settings',
|
||||||
|
'account-manager': 'open-account-manager',
|
||||||
|
'check-update': 'check-update'
|
||||||
|
}
|
||||||
|
|
||||||
function handleCommand(command: string) {
|
function handleCommand(command: string) {
|
||||||
switch (command) {
|
const emitName = commandMap[command]
|
||||||
case 'logout':
|
if (emitName) emit(emitName as any)
|
||||||
emit('logout')
|
|
||||||
break
|
|
||||||
case 'device':
|
|
||||||
emit('open-device')
|
|
||||||
break
|
|
||||||
case 'settings':
|
|
||||||
emit('open-settings')
|
|
||||||
break
|
|
||||||
case 'account-manager':
|
|
||||||
emit('open-account-manager')
|
|
||||||
break
|
|
||||||
case 'check-update':
|
|
||||||
emit('check-update')
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
@@ -100,20 +101,13 @@ onMounted(async () => {
|
|||||||
<el-dropdown-item disabled class="username-item">
|
<el-dropdown-item disabled class="username-item">
|
||||||
{{ displayUsername }}
|
{{ displayUsername }}
|
||||||
</el-dropdown-item>
|
</el-dropdown-item>
|
||||||
<el-dropdown-item command="check-update" class="menu-item">
|
<el-dropdown-item
|
||||||
检查更新 v{{ currentVersion }}
|
v-for="item in menuItems"
|
||||||
</el-dropdown-item>
|
:key="item.command"
|
||||||
<el-dropdown-item command="account-manager" class="menu-item">
|
v-show="!item.showIf || item.showIf()"
|
||||||
我的电商账号
|
:command="item.command"
|
||||||
</el-dropdown-item>
|
:class="item.class">
|
||||||
<el-dropdown-item command="device" class="menu-item">
|
{{ typeof item.label === 'string' ? item.label : item.label.value }}
|
||||||
我的设备
|
|
||||||
</el-dropdown-item>
|
|
||||||
<el-dropdown-item command="settings" class="menu-item">
|
|
||||||
设置
|
|
||||||
</el-dropdown-item>
|
|
||||||
<el-dropdown-item v-if="isAuthenticated" command="logout" class="menu-item logout-item">
|
|
||||||
退出
|
|
||||||
</el-dropdown-item>
|
</el-dropdown-item>
|
||||||
</el-dropdown-menu>
|
</el-dropdown-menu>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -265,15 +265,10 @@ async function handleStartSearch() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
allProducts.value = products
|
allProducts.value = products
|
||||||
pendingFile.value = null
|
|
||||||
selectedFileName.value = ''
|
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
if (e.name !== 'AbortError') {
|
if (e.name !== 'AbortError') {
|
||||||
statusType.value = 'error'
|
statusType.value = 'error'
|
||||||
statusMessage.value = '解析失败,请重试'
|
statusMessage.value = '解析失败,请重试'
|
||||||
// 失败后清空文件信息,让用户重新上传
|
|
||||||
pendingFile.value = null
|
|
||||||
selectedFileName.value = ''
|
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
loading.value = false
|
loading.value = false
|
||||||
@@ -309,9 +304,6 @@ function stopTask() {
|
|||||||
statusMessage.value = '任务已停止'
|
statusMessage.value = '任务已停止'
|
||||||
// 保留进度条和当前进度
|
// 保留进度条和当前进度
|
||||||
allProducts.value = allProducts.value.map(p => ({...p, searching1688: false}))
|
allProducts.value = allProducts.value.map(p => ({...p, searching1688: false}))
|
||||||
// 清空文件信息,让用户重新上传
|
|
||||||
pendingFile.value = null
|
|
||||||
selectedFileName.value = ''
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function startBatch1688Search(products: any[]) {
|
async function startBatch1688Search(products: any[]) {
|
||||||
@@ -499,7 +491,7 @@ onMounted(loadLatest)
|
|||||||
<div class="step-header">
|
<div class="step-header">
|
||||||
<div class="title">网站地区</div>
|
<div class="title">网站地区</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="desc">请选择目标网站地区,如:日本区。</div>
|
<div class="desc">仅支持乐天市场日本区商品查询,后续将开放更多乐天网站地区,敬请期待。</div>
|
||||||
<el-select v-model="region" placeholder="选择地区" size="small" style="width: 100%" disabled>
|
<el-select v-model="region" placeholder="选择地区" size="small" style="width: 100%" disabled>
|
||||||
<el-option v-for="opt in regionOptions" :key="opt.value" :label="opt.label" :value="opt.value">
|
<el-option v-for="opt in regionOptions" :key="opt.value" :label="opt.label" :value="opt.value">
|
||||||
<span style="margin-right:6px">{{ opt.flag }}</span>{{ opt.label }}
|
<span style="margin-right:6px">{{ opt.flag }}</span>{{ opt.label }}
|
||||||
|
|||||||
@@ -384,7 +384,7 @@ async function removeCurrentAccount() {
|
|||||||
>
|
>
|
||||||
<span class="acct-row">
|
<span class="acct-row">
|
||||||
<span :class="['status-dot', a.status === 1 ? 'on' : 'off']"></span>
|
<span :class="['status-dot', a.status === 1 ? 'on' : 'off']"></span>
|
||||||
<img class="avatar" src="/image/img_v3_02qd_052605f0-4be3-44db-9691-35ee5ff6201g.jpg" alt="avatar" />
|
<img class="avatar" src="/image/user.png" alt="avatar" />
|
||||||
<span class="acct-text">{{ a.name || a.username }}</span>
|
<span class="acct-text">{{ a.name || a.username }}</span>
|
||||||
<span v-if="accountId === a.id" class="acct-check">✔️</span>
|
<span v-if="accountId === a.id" class="acct-check">✔️</span>
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
BIN
electron-vue-template/src/renderer/public/icon/icon1.png
Normal file
BIN
electron-vue-template/src/renderer/public/icon/icon1.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.1 MiB |
BIN
electron-vue-template/src/renderer/public/image/user.png
Normal file
BIN
electron-vue-template/src/renderer/public/image/user.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.7 KiB |
@@ -10,7 +10,7 @@
|
|||||||
</parent>
|
</parent>
|
||||||
<groupId>com.tashow.erp</groupId>
|
<groupId>com.tashow.erp</groupId>
|
||||||
<artifactId>erp_client_sb</artifactId>
|
<artifactId>erp_client_sb</artifactId>
|
||||||
<version>2.5.5</version>
|
<version>2.5.6</version>
|
||||||
<name>erp_client_sb</name>
|
<name>erp_client_sb</name>
|
||||||
<description>erp客户端</description>
|
<description>erp客户端</description>
|
||||||
<properties>
|
<properties>
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package com.tashow.erp.config;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.scheduling.annotation.EnableAsync;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@EnableAsync
|
||||||
|
public class AsyncConfig {
|
||||||
|
}
|
||||||
|
|
||||||
@@ -84,14 +84,9 @@ public class SystemController {
|
|||||||
|
|
||||||
@PostMapping("/genmai/open")
|
@PostMapping("/genmai/open")
|
||||||
public JsonData openGenmaiWebsite(@RequestParam(required = false) Long accountId, HttpServletRequest request) {
|
public JsonData openGenmaiWebsite(@RequestParam(required = false) Long accountId, HttpServletRequest request) {
|
||||||
try {
|
String username = com.tashow.erp.utils.JwtUtil.getUsernameFromRequest(request);
|
||||||
String username = com.tashow.erp.utils.JwtUtil.getUsernameFromRequest(request);
|
genmaiService.openGenmaiWebsite(accountId, username);
|
||||||
genmaiService.openGenmaiWebsite(accountId, username);
|
return JsonData.buildSuccess("跟卖精灵正在启动");
|
||||||
return JsonData.buildSuccess("跟卖精灵已打开");
|
|
||||||
} catch (Exception e) {
|
|
||||||
logger.error("打开跟卖精灵失败", e);
|
|
||||||
return JsonData.buildError(e.getMessage() != null ? e.getMessage() : "打开跟卖精灵失败");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/proxy/image")
|
@GetMapping("/proxy/image")
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package com.tashow.erp.service.impl;
|
||||||
|
import jakarta.annotation.PostConstruct;
|
||||||
|
import org.openqa.selenium.chrome.ChromeDriver;
|
||||||
|
import org.openqa.selenium.chrome.ChromeOptions;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class ChromeDriverPreloader {
|
||||||
|
@PostConstruct
|
||||||
|
public void preloadDriver() {
|
||||||
|
new Thread(() -> {
|
||||||
|
try {
|
||||||
|
ChromeOptions options = new ChromeOptions();
|
||||||
|
options.addArguments("--headless", "--disable-gpu");
|
||||||
|
ChromeDriver driver = new ChromeDriver(options);
|
||||||
|
driver.quit();
|
||||||
|
} catch (Exception ignored) {}
|
||||||
|
}).start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -7,6 +7,7 @@ import org.openqa.selenium.chrome.ChromeDriver;
|
|||||||
import org.openqa.selenium.chrome.ChromeOptions;
|
import org.openqa.selenium.chrome.ChromeOptions;
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.http.*;
|
import org.springframework.http.*;
|
||||||
|
import org.springframework.scheduling.annotation.Async;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.web.client.RestTemplate;
|
import org.springframework.web.client.RestTemplate;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -19,24 +20,27 @@ public class GenmaiServiceImpl {
|
|||||||
private final RestTemplate restTemplate = new RestTemplate();
|
private final RestTemplate restTemplate = new RestTemplate();
|
||||||
private final ObjectMapper objectMapper = new ObjectMapper();
|
private final ObjectMapper objectMapper = new ObjectMapper();
|
||||||
|
|
||||||
public void openGenmaiWebsite(Long accountId, String username) throws Exception {
|
@Async
|
||||||
String token = getAndValidateToken(accountId, username);
|
public void openGenmaiWebsite(Long accountId, String username) {
|
||||||
Runtime.getRuntime().exec("taskkill /f /im chrome.exe");
|
try {
|
||||||
Thread.sleep(1000);
|
String token = getAndValidateToken(accountId, username);
|
||||||
|
Runtime.getRuntime().exec("taskkill /f /im chrome.exe");
|
||||||
|
Thread.sleep(1000);
|
||||||
|
|
||||||
ChromeOptions options = new ChromeOptions();
|
ChromeOptions options = new ChromeOptions();
|
||||||
String systemUser = System.getProperty("user.name");
|
String systemUser = System.getProperty("user.name");
|
||||||
char firstChar = systemUser.charAt(0);
|
char firstChar = systemUser.charAt(0);
|
||||||
char flipped = Character.isUpperCase(firstChar) ? Character.toLowerCase(firstChar) : Character.toUpperCase(firstChar);
|
char flipped = Character.isUpperCase(firstChar) ? Character.toLowerCase(firstChar) : Character.toUpperCase(firstChar);
|
||||||
String chromeUserData = System.getProperty("user.home")
|
String chromeUserData = System.getProperty("user.home")
|
||||||
.replace(systemUser, UrlUtils.urlEncode(flipped + systemUser.substring(1)))
|
.replace(systemUser, UrlUtils.urlEncode(flipped + systemUser.substring(1)))
|
||||||
+ "\\AppData\\Local\\Google\\Chrome\\User Data";
|
+ "\\AppData\\Local\\Google\\Chrome\\User Data";
|
||||||
options.addArguments("user-data-dir=" + chromeUserData.toLowerCase(), "profile-directory=Default");
|
options.addArguments("user-data-dir=" + chromeUserData.toLowerCase(), "profile-directory=Default");
|
||||||
|
|
||||||
ChromeDriver driver = new ChromeDriver(options);
|
ChromeDriver driver = new ChromeDriver(options);
|
||||||
driver.get("https://www.genmaijl.com/#/profile");
|
driver.get("https://www.genmaijl.com/#/profile");
|
||||||
((JavascriptExecutor) driver).executeScript("localStorage.setItem('token','" + token + "')");
|
((JavascriptExecutor) driver).executeScript("localStorage.setItem('token','" + token + "')");
|
||||||
driver.navigate().refresh();
|
driver.navigate().refresh();
|
||||||
|
} catch (Exception ignored) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import com.ruoyi.system.mapper.BanmaAccountMapper;
|
|||||||
import com.ruoyi.system.mapper.ClientFeedbackMapper;
|
import com.ruoyi.system.mapper.ClientFeedbackMapper;
|
||||||
import com.ruoyi.system.mapper.ClientMonitorMapper;
|
import com.ruoyi.system.mapper.ClientMonitorMapper;
|
||||||
import com.ruoyi.system.mapper.ClientAccountDeviceMapper;
|
import com.ruoyi.system.mapper.ClientAccountDeviceMapper;
|
||||||
import com.ruoyi.system.mapper.RefreshTokenMapper;
|
|
||||||
import com.ruoyi.web.service.IClientAccountService;
|
import com.ruoyi.web.service.IClientAccountService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -30,8 +29,6 @@ public class ClientAccountServiceImpl implements IClientAccountService
|
|||||||
private ClientMonitorMapper clientMonitorMapper;
|
private ClientMonitorMapper clientMonitorMapper;
|
||||||
@Autowired
|
@Autowired
|
||||||
private ClientAccountDeviceMapper clientAccountDeviceMapper;
|
private ClientAccountDeviceMapper clientAccountDeviceMapper;
|
||||||
@Autowired
|
|
||||||
private RefreshTokenMapper refreshTokenMapper;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 查询客户端账号
|
* 查询客户端账号
|
||||||
@@ -80,7 +77,7 @@ public class ClientAccountServiceImpl implements IClientAccountService
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 批量删除客户端账号
|
* 批量删除客户端账号
|
||||||
* 级联删除所有关联数据:斑马账号、反馈、错误报告、设备绑定、刷新令牌
|
* 级联删除所有关联数据:斑马账号、反馈、错误报告、设备绑定
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public int deleteClientAccountByIds(Long[] ids)
|
public int deleteClientAccountByIds(Long[] ids)
|
||||||
@@ -100,7 +97,6 @@ public class ClientAccountServiceImpl implements IClientAccountService
|
|||||||
|
|
||||||
// 根据accountId删除关联数据
|
// 根据accountId删除关联数据
|
||||||
clientAccountDeviceMapper.deleteByAccountId(id);
|
clientAccountDeviceMapper.deleteByAccountId(id);
|
||||||
refreshTokenMapper.deleteByAccountId(id);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,79 +0,0 @@
|
|||||||
package com.ruoyi.system.domain;
|
|
||||||
|
|
||||||
import java.util.Date;
|
|
||||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
|
||||||
import com.ruoyi.common.core.domain.BaseEntity;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 刷新令牌对象 refresh_token
|
|
||||||
*/
|
|
||||||
public class RefreshToken extends BaseEntity {
|
|
||||||
private static final long serialVersionUID = 1L;
|
|
||||||
|
|
||||||
/** ID */
|
|
||||||
private Long id;
|
|
||||||
|
|
||||||
/** 账号ID */
|
|
||||||
private Long accountId;
|
|
||||||
|
|
||||||
/** 设备ID */
|
|
||||||
private String deviceId;
|
|
||||||
|
|
||||||
/** 刷新令牌 */
|
|
||||||
private String token;
|
|
||||||
|
|
||||||
/** 到期时间 */
|
|
||||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
|
||||||
private Date expireTime;
|
|
||||||
|
|
||||||
/** 是否已撤销 */
|
|
||||||
private String revoked;
|
|
||||||
|
|
||||||
public Long getId() {
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setId(Long id) {
|
|
||||||
this.id = id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Long getAccountId() {
|
|
||||||
return accountId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setAccountId(Long accountId) {
|
|
||||||
this.accountId = accountId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getDeviceId() {
|
|
||||||
return deviceId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setDeviceId(String deviceId) {
|
|
||||||
this.deviceId = deviceId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getToken() {
|
|
||||||
return token;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setToken(String token) {
|
|
||||||
this.token = token;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Date getExpireTime() {
|
|
||||||
return expireTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setExpireTime(Date expireTime) {
|
|
||||||
this.expireTime = expireTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getRevoked() {
|
|
||||||
return revoked;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setRevoked(String revoked) {
|
|
||||||
this.revoked = revoked;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
package com.ruoyi.system.mapper;
|
|
||||||
|
|
||||||
import com.ruoyi.system.domain.RefreshToken;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 刷新令牌数据层
|
|
||||||
*/
|
|
||||||
public interface RefreshTokenMapper {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 保存刷新令牌
|
|
||||||
*/
|
|
||||||
int insertRefreshToken(RefreshToken refreshToken);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 根据令牌查找
|
|
||||||
*/
|
|
||||||
RefreshToken selectByToken(String token);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 撤销账号的所有令牌
|
|
||||||
*/
|
|
||||||
int revokeByAccountId(Long accountId);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 撤销设备的所有令牌
|
|
||||||
*/
|
|
||||||
int revokeByDeviceId(String deviceId);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 删除过期令牌
|
|
||||||
*/
|
|
||||||
int deleteExpiredTokens();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 更新令牌状态
|
|
||||||
*/
|
|
||||||
int updateRefreshToken(RefreshToken refreshToken);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 根据账号ID删除令牌
|
|
||||||
*/
|
|
||||||
int deleteByAccountId(Long accountId);
|
|
||||||
}
|
|
||||||
@@ -1,70 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" ?>
|
|
||||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
|
||||||
<mapper namespace="com.ruoyi.system.mapper.RefreshTokenMapper">
|
|
||||||
|
|
||||||
<resultMap type="RefreshToken" id="RefreshTokenResult">
|
|
||||||
<result property="id" column="id"/>
|
|
||||||
<result property="accountId" column="account_id"/>
|
|
||||||
<result property="deviceId" column="device_id"/>
|
|
||||||
<result property="token" column="token"/>
|
|
||||||
<result property="expireTime" column="expire_time"/>
|
|
||||||
<result property="revoked" column="revoked"/>
|
|
||||||
<result property="createTime" column="create_time"/>
|
|
||||||
<result property="updateTime" column="update_time"/>
|
|
||||||
</resultMap>
|
|
||||||
|
|
||||||
<insert id="insertRefreshToken" parameterType="RefreshToken" useGeneratedKeys="true" keyProperty="id">
|
|
||||||
insert into refresh_token
|
|
||||||
<trim prefix="(" suffix=")" suffixOverrides=",">
|
|
||||||
<if test="accountId != null">account_id,</if>
|
|
||||||
<if test="deviceId != null">device_id,</if>
|
|
||||||
<if test="token != null">token,</if>
|
|
||||||
<if test="expireTime != null">expire_time,</if>
|
|
||||||
<if test="revoked != null">revoked,</if>
|
|
||||||
create_time
|
|
||||||
</trim>
|
|
||||||
<trim prefix="values (" suffix=")" suffixOverrides=",">
|
|
||||||
<if test="accountId != null">#{accountId},</if>
|
|
||||||
<if test="deviceId != null">#{deviceId},</if>
|
|
||||||
<if test="token != null">#{token},</if>
|
|
||||||
<if test="expireTime != null">#{expireTime},</if>
|
|
||||||
<if test="revoked != null">#{revoked},</if>
|
|
||||||
sysdate()
|
|
||||||
</trim>
|
|
||||||
</insert>
|
|
||||||
|
|
||||||
<select id="selectByToken" parameterType="String" resultMap="RefreshTokenResult">
|
|
||||||
select id, account_id, device_id, token, expire_time, revoked, create_time, update_time
|
|
||||||
from refresh_token
|
|
||||||
where token = #{token} and revoked = '0'
|
|
||||||
</select>
|
|
||||||
|
|
||||||
<update id="revokeByAccountId" parameterType="Long">
|
|
||||||
update refresh_token set revoked = '1', update_time = sysdate()
|
|
||||||
where account_id = #{accountId} and revoked = '0'
|
|
||||||
</update>
|
|
||||||
|
|
||||||
<update id="revokeByDeviceId" parameterType="String">
|
|
||||||
update refresh_token set revoked = '1', update_time = sysdate()
|
|
||||||
where device_id = #{deviceId} and revoked = '0'
|
|
||||||
</update>
|
|
||||||
|
|
||||||
<delete id="deleteExpiredTokens">
|
|
||||||
delete from refresh_token
|
|
||||||
where expire_time < sysdate() or revoked = '1'
|
|
||||||
</delete>
|
|
||||||
|
|
||||||
<update id="updateRefreshToken" parameterType="RefreshToken">
|
|
||||||
update refresh_token
|
|
||||||
<trim prefix="SET" suffixOverrides=",">
|
|
||||||
<if test="revoked != null">revoked = #{revoked},</if>
|
|
||||||
update_time = sysdate()
|
|
||||||
</trim>
|
|
||||||
where id = #{id}
|
|
||||||
</update>
|
|
||||||
|
|
||||||
<delete id="deleteByAccountId" parameterType="Long">
|
|
||||||
delete from refresh_token where account_id = #{accountId}
|
|
||||||
</delete>
|
|
||||||
|
|
||||||
</mapper>
|
|
||||||
Reference in New Issue
Block a user