diff --git a/frontend-vue/src/shared/api/url.ts b/frontend-vue/src/shared/api/url.ts new file mode 100644 index 00000000..96e6673d --- /dev/null +++ b/frontend-vue/src/shared/api/url.ts @@ -0,0 +1,25 @@ +/** Java 主后端 URL 构造:常量表值已含 /api 前缀,此处只加 /newApi 网关前缀 */ +export const JAVA_API_PREFIX = '/newApi/api' + +function buildQueryString(query?: Record): string { + if (!query) return '' + const params = new URLSearchParams() + for (const [key, value] of Object.entries(query)) { + if (value !== undefined && value !== null) { + params.append(key, String(value)) + } + } + const text = params.toString().replace(/\+/g, '%20') + return text ? `?${text}` : '' +} + +export function buildJavaUrl( + endpoint: string, + query?: Record, +): string { + const normalized = endpoint.endsWith('/') ? endpoint.slice(0, -1) : endpoint + const prefix = normalized.startsWith('/api') + ? '/newApi' + : JAVA_API_PREFIX + return `${prefix}${normalized}${buildQueryString(query)}` +} diff --git a/frontend-vue/tests/url.test.ts b/frontend-vue/tests/url.test.ts new file mode 100644 index 00000000..5e6149c1 --- /dev/null +++ b/frontend-vue/tests/url.test.ts @@ -0,0 +1,51 @@ +import { test } from 'node:test' +import assert from 'node:assert/strict' +import { buildJavaUrl } from '../src/shared/api/url.ts' + +test('test_build_url_prefixed', () => { + assert.equal(buildJavaUrl('/api/dedupe/run'), '/newApi/api/dedupe/run') + assert.equal(buildJavaUrl('/api/similar-asin/parse'), '/newApi/api/similar-asin/parse') +}) + +test('test_build_url_with_query', () => { + const url = buildJavaUrl('/api/dedupe/history', { resultId: 42 }) + assert.equal(url, '/newApi/api/dedupe/history?resultId=42') +}) + +test('test_build_url_no_query', () => { + const url = buildJavaUrl('/api/publish/dashboard') + assert.ok(!url.includes('?'), '无 query 时不应追加 ?') + assert.equal(url, '/newApi/api/publish/dashboard') +}) + +test('test_build_url_query_encoding', () => { + const url = buildJavaUrl('/api/query-asin/tasks', { + shopName: 'a b&c', + keyword: '中文', + }) + const encoded = encodeURIComponent('a b&c') + assert.equal(url, `/newApi/api/query-asin/tasks?shopName=${encoded}&keyword=${encodeURIComponent('中文')}`) +}) + +test('test_build_url_empty_query_object', () => { + const url = buildJavaUrl('/api/withdraw/candidates', {}) + assert.ok(!url.includes('?'), '空对象不应追加 ?') + assert.equal(url, '/newApi/api/withdraw/candidates') +}) + +test('test_build_url_trailing_slash_endpoint', () => { + const url = buildJavaUrl('/api/split/run/') + assert.equal(url, '/newApi/api/split/run', 'endpoint 尾斜杠应被规范化') +}) + +test('test_build_url_undefined_query', () => { + const url = buildJavaUrl('/api/files/upload', undefined) + assert.ok(!url.includes('?'), 'undefined query 不应追加 ?') + assert.equal(url, '/newApi/api/files/upload') +}) + +test('test_build_url_join_no_duplicate_slash', () => { + assert.equal(buildJavaUrl('/api/dedupe/run'), '/newApi/api/dedupe/run') + assert.ok(!buildJavaUrl('/api/dedupe/run').includes('/newApi//api')) + assert.ok(!buildJavaUrl('/api/dedupe/run').includes('//api/')) +})