fix(bom-costing): read stored images through object storage
This commit is contained in:
parent
c038bcedbe
commit
f08d4ff5c4
@ -52,8 +52,11 @@ describe('Alibaba 1688 CSK signing', () => {
|
||||
})
|
||||
|
||||
it('turns only the public same-origin storage route into an absolute image URL', () => {
|
||||
expect(resolveBomCostingImageSearchSourceUrl('/api/storage/object?key=prod%2Fsku.webp')).toEqual(
|
||||
new URL('https://cp.jiansun.vip/api/storage/object?key=prod%2Fsku.webp'),
|
||||
expect(resolveBomCostingImageSearchSourceUrl(
|
||||
'/api/storage/object?key=prod%2Fsku.webp',
|
||||
(key) => `https://objects.example.com/${key}`,
|
||||
)).toEqual(
|
||||
new URL('https://objects.example.com/prod/sku.webp'),
|
||||
)
|
||||
expect(() => resolveBomCostingImageSearchSourceUrl('/api/auth/me')).toThrow('竞品链接格式不正确')
|
||||
})
|
||||
@ -157,6 +160,7 @@ describe('BomCosting1688ImageSearchService', () => {
|
||||
fetch,
|
||||
now: () => new Date('2026-08-16T10:00:00.000Z'),
|
||||
randomBytes: () => Buffer.from([1, 2, 3, 4]),
|
||||
createStoredObjectReadUrl: (key) => `https://objects.example.com/${key}`,
|
||||
})
|
||||
|
||||
const quote = await service.findRealtimeQuote({
|
||||
@ -194,7 +198,7 @@ describe('BomCosting1688ImageSearchService', () => {
|
||||
expect(JSON.parse(request?.body ?? '{}')).not.toHaveProperty('imageUrl')
|
||||
})
|
||||
|
||||
it('fetches a stored same-origin product image through the public application URL', async () => {
|
||||
it('fetches a stored same-origin product image through its backing object storage URL', async () => {
|
||||
const imageResponse = {
|
||||
ok: true,
|
||||
status: 200,
|
||||
@ -214,13 +218,14 @@ describe('BomCosting1688ImageSearchService', () => {
|
||||
fetch,
|
||||
now: () => new Date('2026-08-16T10:00:00.000Z'),
|
||||
randomBytes: () => Buffer.from([1, 2, 3, 4]),
|
||||
createStoredObjectReadUrl: (key) => `https://objects.example.com/${key}`,
|
||||
})
|
||||
|
||||
await expect(service.findRealtimeQuote({
|
||||
imageUrl: '/api/storage/object?key=prod%2Fbom-costing%2Fsku.webp',
|
||||
})).resolves.toMatchObject({ status: 'NO_COMPARABLE_OFFER' })
|
||||
expect(fetch.mock.calls[0]?.[0]).toEqual(
|
||||
new URL('https://cp.jiansun.vip/api/storage/object?key=prod%2Fbom-costing%2Fsku.webp'),
|
||||
new URL('https://objects.example.com/prod/bom-costing/sku.webp'),
|
||||
)
|
||||
})
|
||||
|
||||
|
||||
@ -3,6 +3,7 @@ import 'server-only'
|
||||
import { createHash, createHmac, randomBytes } from 'node:crypto'
|
||||
import { z } from 'zod'
|
||||
import { parseSafeCompetitorUrl } from '@/lib/bom-costing/url-safety'
|
||||
import { createObjectReadUrl, resolveObjectStorageUrlForServerAccess } from '@/lib/storage'
|
||||
import type {
|
||||
DynamicBomRealtimeMarketOffer,
|
||||
DynamicBomRealtimeMarketQuote,
|
||||
@ -10,7 +11,6 @@ import type {
|
||||
|
||||
const OFFICIAL_IMAGE_SEARCH_ENDPOINT = 'https://skills-gateway.1688.com/api/find_product/1.0.0'
|
||||
const IMAGE_SEARCH_PATH = '/api/find_product/1.0.0'
|
||||
const DEFAULT_APPLICATION_ORIGIN = 'https://cp.jiansun.vip'
|
||||
const CSK_SIGNATURE_VERSION = '1.7.0'
|
||||
const DEFAULT_IMAGE_MAX_BYTES = 5 * 1024 * 1024
|
||||
const DEFAULT_TIMEOUT_MS = 45_000
|
||||
@ -67,6 +67,7 @@ export interface BomCosting1688ImageSearchGateway {
|
||||
fetch(input: RequestInfo | URL, init?: RequestInit): Promise<Response>
|
||||
now(): Date
|
||||
randomBytes(size: number): Buffer
|
||||
createStoredObjectReadUrl?: (key: string) => string
|
||||
}
|
||||
|
||||
function boundedPositiveInteger(
|
||||
@ -170,21 +171,23 @@ export function getBomCosting1688ImageSearchConfig(): BomCosting1688ImageSearchC
|
||||
|
||||
/**
|
||||
* Source snapshots keep object-storage links as same-origin relative paths.
|
||||
* The server-side image-search request needs an absolute URL, but must never
|
||||
* turn arbitrary relative input into an internal-network request. Only the
|
||||
* public storage-object route is eligible for this conversion.
|
||||
* The server-side image-search request must read the backing object directly:
|
||||
* the browser proxy correctly requires a login cookie and therefore cannot be
|
||||
* used by a background pricing job. Only its storage-object route is eligible
|
||||
* for this conversion.
|
||||
*/
|
||||
export function resolveBomCostingImageSearchSourceUrl(rawValue: string): URL {
|
||||
export function resolveBomCostingImageSearchSourceUrl(
|
||||
rawValue: string,
|
||||
buildStoredObjectReadUrl: (key: string) => string = createObjectReadUrl,
|
||||
): URL {
|
||||
const trimmed = rawValue.trim()
|
||||
if (trimmed.startsWith('/api/storage/object?')) {
|
||||
const configuredOrigin = (
|
||||
process.env.NEXT_PUBLIC_APP_URL ??
|
||||
process.env.NEXTAUTH_URL ??
|
||||
DEFAULT_APPLICATION_ORIGIN
|
||||
).trim()
|
||||
return parseSafeCompetitorUrl(new URL(trimmed, configuredOrigin).toString())
|
||||
const parsed = new URL(trimmed, 'https://bom-costing.invalid')
|
||||
const objectKey = parsed.searchParams.get('key')?.trim()
|
||||
if (!objectKey) throw new Error('SKU 主图缺少对象存储 key')
|
||||
return parseSafeCompetitorUrl(buildStoredObjectReadUrl(objectKey))
|
||||
}
|
||||
return parseSafeCompetitorUrl(trimmed)
|
||||
return parseSafeCompetitorUrl(resolveObjectStorageUrlForServerAccess(trimmed))
|
||||
}
|
||||
|
||||
const defaultGateway: BomCosting1688ImageSearchGateway = {
|
||||
@ -192,6 +195,7 @@ const defaultGateway: BomCosting1688ImageSearchGateway = {
|
||||
fetch: (...args) => fetch(...args),
|
||||
now: () => new Date(),
|
||||
randomBytes: (size) => randomBytes(size),
|
||||
createStoredObjectReadUrl: createObjectReadUrl,
|
||||
}
|
||||
|
||||
interface Alibaba1688AkKeys {
|
||||
@ -522,7 +526,10 @@ export class BomCosting1688ImageSearchService {
|
||||
}
|
||||
|
||||
try {
|
||||
const imageUrl = resolveBomCostingImageSearchSourceUrl(sourceImageUrl)
|
||||
const imageUrl = resolveBomCostingImageSearchSourceUrl(
|
||||
sourceImageUrl,
|
||||
this.gateway.createStoredObjectReadUrl,
|
||||
)
|
||||
const imageResponse = await this.gateway.fetch(imageUrl, {
|
||||
method: 'GET',
|
||||
headers: { Accept: 'image/avif,image/webp,image/png,image/jpeg,image/*;q=0.8' },
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user