import { Controller, Get, Inject, Param, Query, Body, Post, Put, Del } from '@midwayjs/core'; import { ApiOkResponse, ApiBody } from '@midwayjs/swagger'; import { UploadMediaDTO, UnifiedMediaPaginationDTO, UnifiedOrderDTO, UnifiedOrderPaginationDTO, UnifiedProductDTO, UnifiedProductPaginationDTO, UnifiedSearchParamsDTO, UnifiedSubscriptionPaginationDTO, UnifiedCustomerDTO, UnifiedCustomerPaginationDTO, UnifiedReviewPaginationDTO, UnifiedReviewDTO, CreateReviewDTO, UpdateReviewDTO, UnifiedWebhookDTO, CreateWebhookDTO, UpdateWebhookDTO, UnifiedPaginationDTO, ShipOrderDTO, CancelShipOrderDTO, BatchShipOrdersDTO, } from '../dto/site-api.dto'; import { BatchOperationDTO, BatchOperationResultDTO } from '../dto/batch.dto'; import { SiteApiService } from '../service/site-api.service'; import { errorResponse, successResponse } from '../utils/response.util'; import { ILogger } from '@midwayjs/core'; @Controller('/site-api') export class SiteApiController { @Inject() siteApiService: SiteApiService; @Inject() logger: ILogger; @Post('/:siteId/media/create') @ApiOkResponse({ type: UnifiedMediaPaginationDTO }) @ApiBody({ type: UploadMediaDTO }) async createMedia( @Param('siteId') siteId: number, @Body() body: UploadMediaDTO, ) { this.logger.debug(`[Site API] 上传媒体文件开始, siteId: ${siteId}`); try { const adapter = await this.siteApiService.getAdapter(siteId); const data = await adapter.createMedia(body.file); this.logger.debug(`[Site API] 上传媒体文件成功, siteId: ${siteId}`); return successResponse(data); } catch (error) { this.logger.error(`[Site API] 上传媒体文件失败, siteId: ${siteId}, 错误信息: ${error.message}`); return errorResponse(error.message); } } @Get('/:siteId/reviews') @ApiOkResponse({ type: UnifiedReviewPaginationDTO }) async getReviews( @Param('siteId') siteId: number, @Query() query: UnifiedSearchParamsDTO ) { this.logger.debug(`[Site API] 获取评论列表开始, siteId: ${siteId}, query: ${JSON.stringify(query)}`); try { const adapter = await this.siteApiService.getAdapter(siteId); const data = await adapter.getReviews(query); this.logger.debug(`[Site API] 获取评论列表成功, siteId: ${siteId}, 共获取到 ${data.total} 条评论`); return successResponse(data); } catch (error) { this.logger.error(`[Site API] 获取评论列表失败, siteId: ${siteId}, 错误信息: ${error.message}`); return errorResponse(error.message); } } @Post('/:siteId/reviews') @ApiOkResponse({ type: UnifiedReviewDTO }) async createReview( @Param('siteId') siteId: number, @Body() body: CreateReviewDTO ) { this.logger.debug(`[Site API] 创建评论开始, siteId: ${siteId}, body: ${JSON.stringify(body)}`); try { const adapter = await this.siteApiService.getAdapter(siteId); const data = await adapter.createReview(body); this.logger.debug(`[Site API] 创建评论成功, siteId: ${siteId}`); return successResponse(data); } catch (error) { this.logger.error(`[Site API] 创建评论失败, siteId: ${siteId}, 错误信息: ${error.message}`); return errorResponse(error.message); } } @Put('/:siteId/reviews/:id') @ApiOkResponse({ type: UnifiedReviewDTO }) async updateReview( @Param('siteId') siteId: number, @Param('id') id: number, @Body() body: UpdateReviewDTO ) { this.logger.debug(`[Site API] 更新评论开始, siteId: ${siteId}, id: ${id}, body: ${JSON.stringify(body)}`); try { const adapter = await this.siteApiService.getAdapter(siteId); const data = await adapter.updateReview(id, body); this.logger.debug(`[Site API] 更新评论成功, siteId: ${siteId}, id: ${id}`); return successResponse(data); } catch (error) { this.logger.error(`[Site API] 更新评论失败, siteId: ${siteId}, id: ${id}, 错误信息: ${error.message}`); return errorResponse(error.message); } } @Del('/:siteId/reviews/:id') @ApiOkResponse({ type: Boolean }) async deleteReview( @Param('siteId') siteId: number, @Param('id') id: number ) { this.logger.debug(`[Site API] 删除评论开始, siteId: ${siteId}, id: ${id}`); try { const adapter = await this.siteApiService.getAdapter(siteId); const data = await adapter.deleteReview(id); this.logger.debug(`[Site API] 删除评论成功, siteId: ${siteId}, id: ${id}`); return successResponse(data); } catch (error) { this.logger.error(`[Site API] 删除评论失败, siteId: ${siteId}, id: ${id}, 错误信息: ${error.message}`); return errorResponse(error.message); } } @Get('/:siteId/webhooks') @ApiOkResponse({ type: UnifiedPaginationDTO }) async getWebhooks( @Param('siteId') siteId: number, @Query() query: UnifiedSearchParamsDTO ) { this.logger.debug(`[Site API] 获取webhooks列表开始, siteId: ${siteId}, query: ${JSON.stringify(query)}`); try { const adapter = await this.siteApiService.getAdapter(siteId); const data = await adapter.getWebhooks(query); this.logger.debug(`[Site API] 获取webhooks列表成功, siteId: ${siteId}, 共获取到 ${data.total} 个webhooks`); return successResponse(data); } catch (error) { this.logger.error(`[Site API] 获取webhooks列表失败, siteId: ${siteId}, 错误信息: ${error.message}`); return errorResponse(error.message); } } @Get('/:siteId/webhooks/:id') @ApiOkResponse({ type: UnifiedWebhookDTO }) async getWebhook( @Param('siteId') siteId: number, @Param('id') id: string ) { this.logger.debug(`[Site API] 获取单个webhook开始, siteId: ${siteId}, id: ${id}`); try { const adapter = await this.siteApiService.getAdapter(siteId); const data = await adapter.getWebhook(id); this.logger.debug(`[Site API] 获取单个webhook成功, siteId: ${siteId}, id: ${id}`); return successResponse(data); } catch (error) { this.logger.error(`[Site API] 获取单个webhook失败, siteId: ${siteId}, id: ${id}, 错误信息: ${error.message}`); return errorResponse(error.message); } } @Post('/:siteId/webhooks') @ApiOkResponse({ type: UnifiedWebhookDTO }) @ApiBody({ type: CreateWebhookDTO }) async createWebhook( @Param('siteId') siteId: number, @Body() body: CreateWebhookDTO ) { this.logger.debug(`[Site API] 创建webhook开始, siteId: ${siteId}, body: ${JSON.stringify(body)}`); try { const adapter = await this.siteApiService.getAdapter(siteId); const data = await adapter.createWebhook(body); this.logger.debug(`[Site API] 创建webhook成功, siteId: ${siteId}`); return successResponse(data); } catch (error) { this.logger.error(`[Site API] 创建webhook失败, siteId: ${siteId}, 错误信息: ${error.message}`); return errorResponse(error.message); } } @Put('/:siteId/webhooks/:id') @ApiOkResponse({ type: UnifiedWebhookDTO }) @ApiBody({ type: UpdateWebhookDTO }) async updateWebhook( @Param('siteId') siteId: number, @Param('id') id: string, @Body() body: UpdateWebhookDTO ) { this.logger.debug(`[Site API] 更新webhook开始, siteId: ${siteId}, id: ${id}, body: ${JSON.stringify(body)}`); try { const adapter = await this.siteApiService.getAdapter(siteId); const data = await adapter.updateWebhook(id, body); this.logger.debug(`[Site API] 更新webhook成功, siteId: ${siteId}, id: ${id}`); return successResponse(data); } catch (error) { this.logger.error(`[Site API] 更新webhook失败, siteId: ${siteId}, id: ${id}, 错误信息: ${error.message}`); return errorResponse(error.message); } } @Del('/:siteId/webhooks/:id') @ApiOkResponse({ type: Boolean }) async deleteWebhook( @Param('siteId') siteId: number, @Param('id') id: string ) { this.logger.debug(`[Site API] 删除webhook开始, siteId: ${siteId}, id: ${id}`); try { const adapter = await this.siteApiService.getAdapter(siteId); const data = await adapter.deleteWebhook(id); this.logger.debug(`[Site API] 删除webhook成功, siteId: ${siteId}, id: ${id}`); return successResponse(data); } catch (error) { this.logger.error(`[Site API] 删除webhook失败, siteId: ${siteId}, id: ${id}, 错误信息: ${error.message}`); return errorResponse(error.message); } } @Get('/:siteId/products') @ApiOkResponse({ type: UnifiedProductPaginationDTO }) async getProducts( @Param('siteId') siteId: number, @Query() query: UnifiedSearchParamsDTO ) { this.logger.debug(`[Site API] 获取产品列表开始, siteId: ${siteId}, query: ${JSON.stringify(query)}`); try { const adapter = await this.siteApiService.getAdapter(siteId); const data = await adapter.getProducts(query); // 如果包含ERP产品信息,则增强商品数据 if (data && data.items && data.items.length > 0) { const enrichedItems = await this.siteApiService.enrichSiteProductsWithErpInfo(siteId, data.items); data.items = enrichedItems; } this.logger.debug(`[Site API] 获取产品列表成功, siteId: ${siteId}, 共获取到 ${data.total} 个产品`); return successResponse(data); } catch (error) { this.logger.error(`[Site API] 获取产品列表失败, siteId: ${siteId}, 错误信息: ${error.message}`); return errorResponse(error.message); } } @Get('/:siteId/products/export') async exportProducts( @Param('siteId') siteId: number, @Query() query: UnifiedSearchParamsDTO ) { try { const adapter = await this.siteApiService.getAdapter(siteId); const perPage = (query.per_page) || 100; let page = 1; const all: any[] = []; while (true) { const data = await adapter.getProducts({ ...query, page, per_page: perPage }); const items = data.items || []; all.push(...items); const totalPages = data.totalPages || Math.ceil((data.total || 0) / (data.per_page || perPage)); if (!items.length || page >= totalPages) break; page += 1; } let items = all; if (query.where?.ids) { const ids = new Set(String(query.where.ids).split(',').map(v => v.trim()).filter(Boolean)); items = items.filter(i => ids.has(String(i.id))); } const header = ['id', 'name', 'type', 'status', 'sku', 'regular_price', 'sale_price', 'price', 'stock_status', 'stock_quantity', 'image_src']; const rows = items.map((p: any) => [ p.id, p.name, p.type, p.status, p.sku, p.regular_price, p.sale_price, p.price, p.stock_status, p.stock_quantity, ((p.images && p.images[0]?.src) || ''), ]); const toCsvValue = (val: any) => { const s = String(val ?? ''); const escaped = s.replace(/"/g, '""'); return `"${escaped}"`; }; const csv = [header.map(toCsvValue).join(','), ...rows.map(r => r.map(toCsvValue).join(','))].join('\n'); return successResponse({ csv }); } catch (error) { return errorResponse(error.message); } } // 平台特性:产品导出(特殊CSV,走平台服务) @Get('/:siteId/links') async getLinks( @Param('siteId') siteId: number ) { this.logger.debug(`[Site API] 获取站点链接列表开始, siteId: ${siteId}`); try { const adapter = await this.siteApiService.getAdapter(siteId); const data = await adapter.getLinks(); this.logger.debug(`[Site API] 获取站点链接列表成功, siteId: ${siteId}`); return successResponse(data); } catch (error) { this.logger.error(`[Site API] 获取站点链接列表失败, siteId: ${siteId}, 错误信息: ${error.message}`); return errorResponse(error.message); } } @Get('/:siteId/products/export-special') async exportProductsSpecial( @Param('siteId') siteId: number, @Query() query: UnifiedSearchParamsDTO ) { try { const site = await this.siteApiService.siteService.get(siteId, true); if (site.type === 'woocommerce') { const page = query.page || 1; const perPage = (query.per_page) || 100; const res = await this.siteApiService.wpService.getProducts(site, page, perPage); const header = ['id', 'name', 'type', 'status', 'sku', 'regular_price', 'sale_price', 'stock_status', 'stock_quantity']; const rows = (res.items || []).map((p: any) => [p.id, p.name, p.type, p.status, p.sku, p.regular_price, p.sale_price, p.stock_status, p.stock_quantity]); const toCsvValue = (val: any) => { const s = String(val ?? ''); const escaped = s.replace(/"/g, '""'); return `"${escaped}"`; }; const csv = [header.map(toCsvValue).join(','), ...rows.map(r => r.map(toCsvValue).join(','))].join('\n'); return successResponse({ csv }); } if (site.type === 'shopyy') { const res = await this.siteApiService.shopyyService.getProducts(site, query.page || 1, query.per_page || 100); const header = ['id', 'name', 'type', 'status', 'sku', 'price', 'stock_status', 'stock_quantity']; const rows = (res.items || []).map((p: any) => [p.id, p.name, p.type, p.status, p.sku, p.price, p.stock_status, p.stock_quantity]); const csv = [header.join(','), ...rows.map(r => r.map(v => String(v ?? '')).join(','))].join('\n'); return successResponse({ csv }); } throw new Error('Unsupported site type for special export'); } catch (error) { return errorResponse(error.message); } } @Get('/:siteId/products/:id') @ApiOkResponse({ type: UnifiedProductDTO }) async getProduct( @Param('siteId') siteId: number, @Param('id') id: string ) { this.logger.info(`[Site API] 获取单个产品开始, siteId: ${siteId}, productId: ${id}`); try { const adapter = await this.siteApiService.getAdapter(siteId); const data = await adapter.getProduct(id); // 如果获取到商品数据,则增强ERP产品信息 if (data) { const enrichedData = await this.siteApiService.enrichSiteProductWithErpInfo(siteId, data); this.logger.info(`[Site API] 获取单个产品成功, siteId: ${siteId}, productId: ${id}`); return successResponse(enrichedData); } this.logger.info(`[Site API] 获取单个产品成功, siteId: ${siteId}, productId: ${id}`); return successResponse(data); } catch (error) { this.logger.error(`[Site API] 获取单个产品失败, siteId: ${siteId}, productId: ${id}, 错误信息: ${error.message}`); return errorResponse(error.message); } } @Post('/:siteId/products') @ApiOkResponse({ type: UnifiedProductDTO }) async createProduct( @Param('siteId') siteId: number, @Body() body: UnifiedProductDTO ) { this.logger.info(`[Site API] 创建产品开始, siteId: ${siteId}, 产品名称: ${body.name}`); try { const adapter = await this.siteApiService.getAdapter(siteId); const data = await adapter.createProduct(body); this.logger.info(`[Site API] 创建产品成功, siteId: ${siteId}, 产品ID: ${data.id}`); return successResponse(data); } catch (error) { this.logger.error(`[Site API] 创建产品失败, siteId: ${siteId}, 错误信息: ${error.message}`); return errorResponse(error.message); } } @Post('/:siteId/products/import') @ApiOkResponse({ type: Object }) async importProducts( @Param('siteId') siteId: number, @Body() body: { items?: any[]; csv?: string } ) { try { const adapter = await this.siteApiService.getAdapter(siteId); let items = body.items || []; if (!items.length && body.csv) { const lines = body.csv.split(/\r?\n/).filter(Boolean); const header = lines.shift()?.split(',') || []; items = lines.map((line) => { const cols = line.split(','); const obj: any = {}; header.forEach((h, i) => (obj[h] = cols[i])); return obj; }); } const created: any[] = []; const failed: any[] = []; for (const item of items) { try { const data = await adapter.createProduct(item); created.push(data); } catch (e) { failed.push({ item, error: (e as any).message }); } } return successResponse({ created, failed }); } catch (error) { return errorResponse(error.message); } } // 平台特性:产品导入(特殊CSV,走平台服务) @Post('/:siteId/products/import-special') @ApiOkResponse({ type: Object }) async importProductsSpecial( @Param('siteId') siteId: number, @Body() body: { csv?: string; items?: any[] } ) { try { const site = await this.siteApiService.siteService.get(siteId, true); const csvText = body.csv || ''; const items = body.items || []; const created: any[] = []; const failed: any[] = []; if (site.type === 'woocommerce') { // 解析 CSV 为对象数组(若传入 items 则优先 items) let payloads = items; if (!payloads.length && csvText) { const lines = csvText.split(/\r?\n/).filter(Boolean); const header = lines.shift()?.split(',') || []; payloads = lines.map((line) => { const cols = line.split(','); const obj: any = {}; header.forEach((h, i) => (obj[h] = cols[i])); return obj; }); } for (const item of payloads) { try { const res = await this.siteApiService.wpService.createProduct(site, item); created.push(res); } catch (e) { failed.push({ item, error: (e as any).message }); } } return successResponse({ created, failed }); } if (site.type === 'shopyy') { throw new Error('ShopYY 暂不支持特殊CSV导入'); } throw new Error('Unsupported site type for special import'); } catch (error) { return errorResponse(error.message); } } @Put('/:siteId/products/:id') @ApiOkResponse({ type: UnifiedProductDTO }) async updateProduct( @Param('siteId') siteId: number, @Param('id') id: string, @Body() body: UnifiedProductDTO ) { this.logger.info(`[Site API] 更新产品开始, siteId: ${siteId}, productId: ${id}`); try { const adapter = await this.siteApiService.getAdapter(siteId); const data = await adapter.updateProduct(id, body); this.logger.info(`[Site API] 更新产品成功, siteId: ${siteId}, productId: ${id}`); return successResponse(data); } catch (error) { this.logger.error(`[Site API] 更新产品失败, siteId: ${siteId}, productId: ${id}, 错误信息: ${error.message}`); return errorResponse(error.message); } } @Put('/:siteId/products/:productId/variations/:variationId') @ApiOkResponse({ type: Object }) async updateVariation( @Param('siteId') siteId: number, @Param('productId') productId: string, @Param('variationId') variationId: string, @Body() body: any ) { this.logger.info(`[Site API] 更新产品变体开始, siteId: ${siteId}, productId: ${productId}, variationId: ${variationId}`); try { const adapter = await this.siteApiService.getAdapter(siteId); const data = await adapter.updateVariation(productId, variationId, body); this.logger.info(`[Site API] 更新产品变体成功, siteId: ${siteId}, productId: ${productId}, variationId: ${variationId}`); return successResponse(data); } catch (error) { this.logger.error(`[Site API] 更新产品变体失败, siteId: ${siteId}, productId: ${productId}, variationId: ${variationId}, 错误信息: ${error.message}`); return errorResponse(error.message); } } @Del('/:siteId/products/:id') @ApiOkResponse({ type: Boolean }) async deleteProduct( @Param('siteId') siteId: number, @Param('id') id: string ) { this.logger.info(`[Site API] 删除产品开始, siteId: ${siteId}, productId: ${id}`); try { const adapter = await this.siteApiService.getAdapter(siteId); const success = await adapter.deleteProduct(id); this.logger.info(`[Site API] 删除产品成功, siteId: ${siteId}, productId: ${id}`); return successResponse(success); } catch (error) { this.logger.error(`[Site API] 删除产品失败, siteId: ${siteId}, productId: ${id}, 错误信息: ${error.message}`); return errorResponse(error.message); } } @Post('/:siteId/products/batch') @ApiOkResponse({ type: BatchOperationResultDTO }) async batchProducts( @Param('siteId') siteId: number, @Body() body: BatchOperationDTO ) { this.logger.info(`[Site API] 批量处理产品开始, siteId: ${siteId}`); try { const adapter = await this.siteApiService.getAdapter(siteId); if (adapter.batchProcessProducts) { const res = await adapter.batchProcessProducts(body); this.logger.info(`[Site API] 批量处理产品成功, siteId: ${siteId}`); return successResponse(res); } const created: any[] = []; const updated: any[] = []; const deleted: Array = []; const errors: Array<{identifier: string, error: string}> = []; if (body.create?.length) { for (const item of body.create) { try { const data = await adapter.createProduct(item); created.push(data); } catch (e) { errors.push({ identifier: String(item.id || item.sku || 'unknown'), error: (e as any).message }); } } } if (body.update?.length) { for (const item of body.update) { try { const id = item.id; const data = await adapter.updateProduct(id, item); updated.push(data); } catch (e) { errors.push({ identifier: String(item.id || 'unknown'), error: (e as any).message }); } } } if (body.delete?.length) { for (const id of body.delete) { try { const ok = await adapter.deleteProduct(id); if (ok) deleted.push(id); else errors.push({ identifier: String(id), error: 'delete failed' }); } catch (e) { errors.push({ identifier: String(id), error: (e as any).message }); } } } this.logger.info(`[Site API] 批量处理产品完成, siteId: ${siteId}`); return successResponse({ total: (body.create?.length || 0) + (body.update?.length || 0) + (body.delete?.length || 0), processed: created.length + updated.length + deleted.length, created: created.length, updated: updated.length, deleted: deleted.length, errors: errors }); } catch (error) { this.logger.error(`[Site API] 批量处理产品失败, siteId: ${siteId}, 错误信息: ${error.message}`); return errorResponse(error.message); } } @Get('/:siteId/orders') @ApiOkResponse({ type: UnifiedOrderPaginationDTO }) async getOrders( @Param('siteId') siteId: number, @Query() query: UnifiedSearchParamsDTO ) { this.logger.info(`[Site API] 获取订单列表开始, siteId: ${siteId}, query: ${JSON.stringify(query)}`); try { const adapter = await this.siteApiService.getAdapter(siteId); const where = { ...(query.where || {}) }; const data = await adapter.getOrders({ ...query, where }); this.logger.info(`[Site API] 获取订单列表成功, siteId: ${siteId}, 共获取到 ${data.total} 个订单`); return successResponse(data); } catch (error) { this.logger.error(`[Site API] 获取订单列表失败, siteId: ${siteId}, 错误信息: ${error.message}`); return errorResponse(error.message); } } @Get('/:siteId/customers/:customerId/orders') @ApiOkResponse({ type: UnifiedOrderPaginationDTO }) async getCustomerOrders( @Param('siteId') siteId: number, @Param('customerId') customerId: number, @Query() query: UnifiedSearchParamsDTO ) { this.logger.info(`[Site API] 获取客户订单列表开始, siteId: ${siteId}, customerId: ${customerId}, query: ${JSON.stringify(query)}`); try { const adapter = await this.siteApiService.getAdapter(siteId); const where = { ...(query.where || {}), customer: customerId }; const data = await adapter.getOrders({ ...query, where }); this.logger.info(`[Site API] 获取客户订单列表成功, siteId: ${siteId}, customerId: ${customerId}, 共获取到 ${data.total} 个订单`); return successResponse(data); } catch (error) { this.logger.error(`[Site API] 获取客户订单列表失败, siteId: ${siteId}, customerId: ${customerId}, 错误信息: ${error.message}`); return errorResponse(error.message); } } @Get('/:siteId/orders/export') async exportOrders( @Param('siteId') siteId: number, @Query() query: UnifiedSearchParamsDTO ) { try { const adapter = await this.siteApiService.getAdapter(siteId); const perPage = (query.per_page) || 100; let page = 1; const all: any[] = []; while (true) { const data = await adapter.getOrders({ ...query, page, per_page: perPage }); const items = data.items || []; all.push(...items); const totalPages = data.totalPages || Math.ceil((data.total || 0) / (data.per_page || perPage)); if (!items.length || page >= totalPages) break; page += 1; } let items = all; if (query.where?.ids) { const ids = new Set(String(query.where.ids).split(',').map(v => v.trim()).filter(Boolean)); items = items.filter(i => ids.has(String(i.id))); } const header = ['id', 'number', 'status', 'currency', 'total', 'customer_id', 'customer_name', 'email', 'payment_method', 'phone', 'billing_full_address', 'shipping_full_address', 'date_created']; const rows = items.map((o: any) => [ o.id, o.number, o.status, o.currency, o.total, o.customer_id, o.customer_name, o.email, o.payment_method, (o.shipping?.phone || o.billing?.phone || ''), (o.billing_full_address || ''), (o.shipping_full_address || ''), o.date_created, ]); const toCsvValue = (val: any) => { const s = String(val ?? ''); const escaped = s.replace(/"/g, '""'); return `"${escaped}"`; }; const csv = [header.map(toCsvValue).join(','), ...rows.map(r => r.map(toCsvValue).join(','))].join('\n'); return successResponse({ csv }); } catch (error) { return errorResponse(error.message); } } @Get('/:siteId/orders/:id') @ApiOkResponse({ type: UnifiedOrderDTO }) async getOrder( @Param('siteId') siteId: number, @Param('id') id: string ) { this.logger.info(`[Site API] 获取单个订单开始, siteId: ${siteId}, orderId: ${id}`); try { const adapter = await this.siteApiService.getAdapter(siteId); const data = await adapter.getOrder(id); this.logger.info(`[Site API] 获取单个订单成功, siteId: ${siteId}, orderId: ${id}`); return successResponse(data); } catch (error) { this.logger.error(`[Site API] 获取单个订单失败, siteId: ${siteId}, orderId: ${id}, 错误信息: ${error.message}`); return errorResponse(error.message); } } @Post('/:siteId/orders') @ApiOkResponse({ type: UnifiedOrderDTO }) async createOrder( @Param('siteId') siteId: number, @Body() body: any ) { this.logger.info(`[Site API] 创建订单开始, siteId: ${siteId}`); try { const adapter = await this.siteApiService.getAdapter(siteId); const data = await adapter.createOrder(body); this.logger.info(`[Site API] 创建订单成功, siteId: ${siteId}, orderId: ${data.id}`); return successResponse(data); } catch (error) { this.logger.error(`[Site API] 创建订单失败, siteId: ${siteId}, 错误信息: ${error.message}`); return errorResponse(error.message); } } @Post('/:siteId/orders/import') @ApiOkResponse({ type: Object }) async importOrders( @Param('siteId') siteId: number, @Body() body: { items?: any[]; csv?: string } ) { try { const adapter = await this.siteApiService.getAdapter(siteId); let items = body.items || []; if (!items.length && body.csv) { const lines = body.csv.split(/\r?\n/).filter(Boolean); const header = lines.shift()?.split(',') || []; items = lines.map((line) => { const cols = line.split(','); const obj: any = {}; header.forEach((h, i) => (obj[h] = cols[i])); return obj; }); } const created: any[] = []; const failed: any[] = []; for (const item of items) { try { const data = await adapter.createOrder(item); created.push(data); } catch (e) { failed.push({ item, error: (e as any).message }); } } return successResponse({ created, failed }); } catch (error) { return errorResponse(error.message); } } @Put('/:siteId/orders/:id') @ApiOkResponse({ type: Boolean }) async updateOrder( @Param('siteId') siteId: number, @Param('id') id: string, @Body() body: any ) { this.logger.info(`[Site API] 更新订单开始, siteId: ${siteId}, orderId: ${id}`); try { const adapter = await this.siteApiService.getAdapter(siteId); const ok = await adapter.updateOrder(id, body); this.logger.info(`[Site API] 更新订单成功, siteId: ${siteId}, orderId: ${id}`); return successResponse(ok); } catch (error) { this.logger.error(`[Site API] 更新订单失败, siteId: ${siteId}, orderId: ${id}, 错误信息: ${error.message}`); return errorResponse(error.message); } } @Del('/:siteId/orders/:id') @ApiOkResponse({ type: Boolean }) async deleteOrder( @Param('siteId') siteId: number, @Param('id') id: string ) { this.logger.info(`[Site API] 删除订单开始, siteId: ${siteId}, orderId: ${id}`); try { const adapter = await this.siteApiService.getAdapter(siteId); const ok = await adapter.deleteOrder(id); this.logger.info(`[Site API] 删除订单成功, siteId: ${siteId}, orderId: ${id}`); return successResponse(ok); } catch (error) { this.logger.error(`[Site API] 删除订单失败, siteId: ${siteId}, orderId: ${id}, 错误信息: ${error.message}`); return errorResponse(error.message); } } @Post('/:siteId/orders/batch') @ApiOkResponse({ type: BatchOperationResultDTO }) async batchOrders( @Param('siteId') siteId: number, @Body() body: BatchOperationDTO ) { this.logger.info(`[Site API] 批量处理订单开始, siteId: ${siteId}`); try { const adapter = await this.siteApiService.getAdapter(siteId); const created: any[] = []; const updated: any[] = []; const deleted: Array = []; const errors: Array<{identifier: string, error: string}> = []; if (body.create?.length) { for (const item of body.create) { try { const data = await adapter.createOrder(item); created.push(data); } catch (e) { errors.push({ identifier: String(item.id || item.order_number || 'unknown'), error: (e as any).message }); } } } if (body.update?.length) { for (const item of body.update) { try { const id = item.id; const ok = await adapter.updateOrder(id, item); if (ok) updated.push(item); else errors.push({ identifier: String(item.id || 'unknown'), error: 'update failed' }); } catch (e) { errors.push({ identifier: String(item.id || 'unknown'), error: (e as any).message }); } } } if (body.delete?.length) { for (const id of body.delete) { try { const ok = await adapter.deleteOrder(id); if (ok) deleted.push(id); else errors.push({ identifier: String(id), error: 'delete failed' }); } catch (e) { errors.push({ identifier: String(id), error: (e as any).message }); } } } this.logger.info(`[Site API] 批量处理订单完成, siteId: ${siteId}`); return successResponse({ total: (body.create?.length || 0) + (body.update?.length || 0) + (body.delete?.length || 0), processed: created.length + updated.length + deleted.length, created: created.length, updated: updated.length, deleted: deleted.length, errors: errors }); } catch (error) { this.logger.error(`[Site API] 批量处理订单失败, siteId: ${siteId}, 错误信息: ${error.message}`); return errorResponse(error.message); } } @Get('/:siteId/orders/:id/notes') @ApiOkResponse({ type: Object }) async getOrderNotes( @Param('siteId') siteId: number, @Param('id') id: string ) { this.logger.info(`[Site API] 获取订单备注开始, siteId: ${siteId}, orderId: ${id}`); try { const adapter = await this.siteApiService.getAdapter(siteId); const data = await adapter.getOrderNotes(id); this.logger.info(`[Site API] 获取订单备注成功, siteId: ${siteId}, orderId: ${id}, 共获取到 ${data.length} 条备注`); return successResponse(data); } catch (error) { this.logger.error(`[Site API] 获取订单备注失败, siteId: ${siteId}, orderId: ${id}, 错误信息: ${error.message}`); return errorResponse(error.message); } } @Post('/:siteId/orders/:id/notes') @ApiOkResponse({ type: Object }) async createOrderNote( @Param('siteId') siteId: number, @Param('id') id: string, @Body() body: any ) { this.logger.info(`[Site API] 创建订单备注开始, siteId: ${siteId}, orderId: ${id}`); try { const adapter = await this.siteApiService.getAdapter(siteId); const data = await adapter.createOrderNote(id, body); this.logger.info(`[Site API] 创建订单备注成功, siteId: ${siteId}, orderId: ${id}`); return successResponse(data); } catch (error) { this.logger.error(`[Site API] 创建订单备注失败, siteId: ${siteId}, orderId: ${id}, 错误信息: ${error.message}`); return errorResponse(error.message); } } @Post('/:siteId/orders/:id/ship') @ApiOkResponse({ type: Object }) async shipOrder( @Param('siteId') siteId: number, @Param('id') id: string, @Body() body: ShipOrderDTO ) { this.logger.info(`[Site API] 订单发货开始, siteId: ${siteId}, orderId: ${id}`); try { const adapter = await this.siteApiService.getAdapter(siteId); const data = await adapter.shipOrder(id, body); this.logger.info(`[Site API] 订单发货成功, siteId: ${siteId}, orderId: ${id}`); return successResponse(data); } catch (error) { this.logger.error(`[Site API] 订单发货失败, siteId: ${siteId}, orderId: ${id}, 错误信息: ${error.message}`); return errorResponse(error.message); } } @Post('/:siteId/orders/:id/cancel-ship') @ApiOkResponse({ type: Object }) async cancelShipOrder( @Param('siteId') siteId: number, @Param('id') id: string, @Body() body: CancelShipOrderDTO ) { this.logger.info(`[Site API] 取消订单发货开始, siteId: ${siteId}, orderId: ${id}`); try { const adapter = await this.siteApiService.getAdapter(siteId); const data = await adapter.cancelShipOrder(id, body); this.logger.info(`[Site API] 取消订单发货成功, siteId: ${siteId}, orderId: ${id}`); return successResponse(data); } catch (error) { this.logger.error(`[Site API] 取消订单发货失败, siteId: ${siteId}, orderId: ${id}, 错误信息: ${error.message}`); return errorResponse(error.message); } } @Post('/:siteId/orders/batch-ship') @ApiOkResponse({ type: Object }) async batchShipOrders( @Param('siteId') siteId: number, @Body() body: BatchShipOrdersDTO ) { this.logger.info(`[Site API] 批量订单发货开始, siteId: ${siteId}, 订单数量: ${body.orders.length}`); try { const adapter = await this.siteApiService.getAdapter(siteId); const results = await Promise.allSettled( body.orders.map(order => adapter.shipOrder(order.order_id, { tracking_number: order.tracking_number, shipping_provider: order.shipping_provider, shipping_method: order.shipping_method, items: order.items, }).catch(error => ({ order_id: order.order_id, success: false, error: error.message })) ) ); const successful = results .filter(result => result.status === 'fulfilled') .map(result => (result as PromiseFulfilledResult).value); const failed = results .filter(result => result.status === 'rejected') .map(result => (result as PromiseRejectedResult).reason); this.logger.info(`[Site API] 批量订单发货完成, siteId: ${siteId}, 成功: ${successful.length}, 失败: ${failed.length}`); return successResponse({ successful: successful.length, failed: failed.length, results: { successful, failed } }); } catch (error) { this.logger.error(`[Site API] 批量订单发货失败, siteId: ${siteId}, 错误信息: ${error.message}`); return errorResponse(error.message); } } @Get('/:siteId/subscriptions') @ApiOkResponse({ type: UnifiedSubscriptionPaginationDTO }) async getSubscriptions( @Param('siteId') siteId: number, @Query() query: UnifiedSearchParamsDTO ) { this.logger.info(`[Site API] 获取订阅列表开始, siteId: ${siteId}, query: ${JSON.stringify(query)}`); try { const adapter = await this.siteApiService.getAdapter(siteId); const data = await adapter.getSubscriptions(query); this.logger.info(`[Site API] 获取订阅列表成功, siteId: ${siteId}, 共获取到 ${data.total} 个订阅`); return successResponse(data); } catch (error) { this.logger.error(`[Site API] 获取订阅列表失败, siteId: ${siteId}, 错误信息: ${error.message}`); return errorResponse(error.message); } } @Get('/:siteId/subscriptions/export') async exportSubscriptions( @Param('siteId') siteId: number, @Query() query: UnifiedSearchParamsDTO ) { try { const adapter = await this.siteApiService.getAdapter(siteId); const perPage = (query.per_page) || 100; let page = 1; const all: any[] = []; while (true) { const data = await adapter.getSubscriptions({ ...query, page, per_page: perPage }); const items = data.items || []; all.push(...items); const totalPages = data.totalPages || Math.ceil((data.total || 0) / (data.per_page || perPage)); if (!items.length || page >= totalPages) break; page += 1; } let items = all; if (query.where?.ids) { const ids = new Set(String(query.where.ids).split(',').map(v => v.trim()).filter(Boolean)); items = items.filter(i => ids.has(String(i.id))); } const header = ['id', 'status', 'customer_id', 'billing_period', 'billing_interval', 'start_date', 'next_payment_date']; const rows = items.map((s: any) => [s.id, s.status, s.customer_id, s.billing_period, s.billing_interval, s.start_date, s.next_payment_date]); const csv = [header.join(','), ...rows.map(r => r.map(v => String(v ?? '')).join(','))].join('\n'); return successResponse({ csv }); } catch (error) { return errorResponse(error.message); } } @Get('/:siteId/media') @ApiOkResponse({ type: UnifiedMediaPaginationDTO }) async getMedia( @Param('siteId') siteId: number, @Query() query: UnifiedSearchParamsDTO ) { this.logger.info(`[Site API] 获取媒体列表开始, siteId: ${siteId}, query: ${JSON.stringify(query)}`); try { const adapter = await this.siteApiService.getAdapter(siteId); const data = await adapter.getMedia(query); this.logger.info(`[Site API] 获取媒体列表成功, siteId: ${siteId}, 共获取到 ${data.total} 个媒体`); return successResponse(data); } catch (error) { this.logger.error(`[Site API] 获取媒体列表失败, siteId: ${siteId}, 错误信息: ${error.message}`); return errorResponse(error.message); } } @Get('/:siteId/media/export') async exportMedia( @Param('siteId') siteId: number, @Query() query: UnifiedSearchParamsDTO ) { try { const adapter = await this.siteApiService.getAdapter(siteId); const perPage = (query.per_page) || 100; let page = 1; const all: any[] = []; while (true) { const data = await adapter.getMedia({ ...query, page, per_page: perPage }); const items = data.items || []; all.push(...items); const totalPages = data.totalPages || Math.ceil((data.total || 0) / (data.per_page || perPage)); if (!items.length || page >= totalPages) break; page += 1; } let items = all; if (query.where?.ids) { const ids = new Set(String(query.where.ids).split(',').map(v => v.trim()).filter(Boolean)); items = items.filter(i => ids.has(String(i.id))); } const header = ['id', 'title', 'media_type', 'mime_type', 'source_url', 'date_created']; const rows = items.map((m: any) => [m.id, m.title, m.media_type, m.mime_type, m.source_url, m.date_created]); const csv = [header.join(','), ...rows.map(r => r.map(v => String(v ?? '')).join(','))].join('\n'); return successResponse({ csv }); } catch (error) { return errorResponse(error.message); } } @Del('/:siteId/media/:id') @ApiOkResponse({ type: Boolean }) async deleteMedia( @Param('siteId') siteId: number, @Param('id') id: string ) { this.logger.info(`[Site API] 删除媒体开始, siteId: ${siteId}, mediaId: ${id}`); try { const adapter = await this.siteApiService.getAdapter(siteId); const api: any = adapter as any; if (api.deleteMedia) { const success = await api.deleteMedia(id); this.logger.info(`[Site API] 删除媒体成功, siteId: ${siteId}, mediaId: ${id}`); return successResponse(success); } throw new Error('Media delete not supported'); } catch (error) { this.logger.error(`[Site API] 删除媒体失败, siteId: ${siteId}, mediaId: ${id}, 错误信息: ${error.message}`); return errorResponse(error.message); } } @Put('/:siteId/media/:id') @ApiOkResponse({ type: Object }) async updateMedia( @Param('siteId') siteId: number, @Param('id') id: string, @Body() body: any ) { this.logger.info(`[Site API] 更新媒体开始, siteId: ${siteId}, mediaId: ${id}`); try { const adapter = await this.siteApiService.getAdapter(siteId); const api: any = adapter as any; if (api.updateMedia) { const res = await api.updateMedia(id, body); this.logger.info(`[Site API] 更新媒体成功, siteId: ${siteId}, mediaId: ${id}`); return successResponse(res); } throw new Error('Media update not supported'); } catch (error) { this.logger.error(`[Site API] 更新媒体失败, siteId: ${siteId}, mediaId: ${id}, 错误信息: ${error.message}`); return errorResponse(error.message); } } @Post('/:siteId/media/batch') @ApiOkResponse({ type: Object }) async batchMedia( @Param('siteId') siteId: number, @Body() body: { update?: any[]; delete?: Array } ) { this.logger.info(`[Site API] 批量处理媒体开始, siteId: ${siteId}`); try { const adapter = await this.siteApiService.getAdapter(siteId); const updated: any[] = []; const deleted: Array = []; const failed: any[] = []; const api: any = adapter as any; if (body.update?.length) { for (const item of body.update) { try { if (!api.updateMedia) throw new Error('Media update not supported'); const res = await api.updateMedia(item.id, item); updated.push(res); } catch (e) { failed.push({ action: 'update', item, error: (e as any).message }); } } } if (body.delete?.length) { for (const id of body.delete) { try { if (!api.deleteMedia) throw new Error('Media delete not supported'); const ok = await api.deleteMedia(id); if (ok) deleted.push(id); else failed.push({ action: 'delete', id, error: 'delete failed' }); } catch (e) { failed.push({ action: 'delete', id, error: (e as any).message }); } } } this.logger.info(`[Site API] 批量处理媒体完成, siteId: ${siteId}`); return successResponse({ updated, deleted, failed }); } catch (error) { this.logger.error(`[Site API] 批量处理媒体失败, siteId: ${siteId}, 错误信息: ${error.message}`); return errorResponse(error.message); } } @Post('/:siteId/media/convert-webp') @ApiOkResponse({ type: Object }) async convertMediaToWebp( @Param('siteId') siteId: number, @Body() body: { ids: Array } ) { this.logger.info(`[Site API] 批量转换媒体为 webp 开始, siteId: ${siteId}`); try { const adapter = await this.siteApiService.getAdapter(siteId); const api: any = adapter as any; // 条件判断 如果未提供 ids 列表则抛出错误 if (!body?.ids || body.ids.length === 0) { throw new Error('未提供需要转换的媒体ID列表'); } if (!api.convertMediaToWebp) { throw new Error('当前站点不支持媒体转换'); } const res = await api.convertMediaToWebp(body.ids); this.logger.info(`[Site API] 批量转换媒体为 webp 成功, siteId: ${siteId}`); return successResponse(res); } catch (error) { this.logger.error(`[Site API] 批量转换媒体为 webp 失败, siteId: ${siteId}, 错误信息: ${error.message}`); return errorResponse(error.message); } } @Get('/:siteId/customers') @ApiOkResponse({ type: UnifiedCustomerPaginationDTO }) async getCustomers( @Param('siteId') siteId: number, @Query() query: UnifiedSearchParamsDTO ) { this.logger.info(`[Site API] 获取客户列表开始, siteId: ${siteId}, query: ${JSON.stringify(query)}`); try { const adapter = await this.siteApiService.getAdapter(siteId); const data = await adapter.getCustomers(query); this.logger.info(`[Site API] 获取客户列表成功, siteId: ${siteId}, 共获取到 ${data.total} 个客户`); return successResponse(data); } catch (error) { this.logger.error(`[Site API] 获取客户列表失败, siteId: ${siteId}, 错误信息: ${error.message}`); return errorResponse(error.message); } } @Get('/:siteId/customers/export') async exportCustomers( @Param('siteId') siteId: number, @Query() query: UnifiedSearchParamsDTO ) { try { const adapter = await this.siteApiService.getAdapter(siteId); const perPage = (query.per_page) || 100; let page = 1; const all: any[] = []; while (true) { const data = await adapter.getCustomers({ ...query, page, per_page: perPage}); const items = data.items || []; all.push(...items); const totalPages = data.totalPages || Math.ceil((data.total || 0) / (data.per_page || perPage)); if (!items.length || page >= totalPages) break; page += 1; } let items = all; const header = ['id', 'email', 'first_name', 'last_name', 'fullname', 'username', 'phone', 'orders', 'total_spend', 'role', 'billing_full_address', 'shipping_full_address', 'date_created']; const formatAddress = (addr: any) => [ addr?.fullname, addr?.company, addr?.address_1, addr?.address_2, addr?.city, addr?.state, addr?.postcode, addr?.country, addr?.phone, ].filter(Boolean).join(', '); const rows = items.map((c: any) => [ c.id, c.email, c.first_name, c.last_name, c.fullname, (c.username || c.raw?.username || ''), (c.phone || c.billing?.phone || c.shipping?.phone || ''), c.orders, c.total_spend, (c.role || c.raw?.role || ''), formatAddress(c.billing || {}), formatAddress(c.shipping || {}), c.date_created, ]); const csv = [header.join(','), ...rows.map(r => r.map(v => String(v ?? '')).join(','))].join('\n'); return successResponse({ csv }); } catch (error) { return errorResponse(error.message); } } @Get('/:siteId/customers/:id') @ApiOkResponse({ type: UnifiedCustomerDTO }) async getCustomer( @Param('siteId') siteId: number, @Param('id') id: string ) { this.logger.info(`[Site API] 获取单个客户开始, siteId: ${siteId}, customerId: ${id}`); try { const adapter = await this.siteApiService.getAdapter(siteId); const data = await adapter.getCustomer(id); this.logger.info(`[Site API] 获取单个客户成功, siteId: ${siteId}, customerId: ${id}`); return successResponse(data); } catch (error) { this.logger.error(`[Site API] 获取单个客户失败, siteId: ${siteId}, customerId: ${id}, 错误信息: ${error.message}`); return errorResponse(error.message); } } @Post('/:siteId/customers') @ApiOkResponse({ type: UnifiedCustomerDTO }) async createCustomer( @Param('siteId') siteId: number, @Body() body: UnifiedCustomerDTO ) { this.logger.info(`[Site API] 创建客户开始, siteId: ${siteId}, 客户邮箱: ${body.email}`); try { const adapter = await this.siteApiService.getAdapter(siteId); const data = await adapter.createCustomer(body); this.logger.info(`[Site API] 创建客户成功, siteId: ${siteId}, customerId: ${data.id}`); return successResponse(data); } catch (error) { this.logger.error(`[Site API] 创建客户失败, siteId: ${siteId}, 错误信息: ${error.message}`); return errorResponse(error.message); } } @Post('/:siteId/customers/import') @ApiOkResponse({ type: Object }) async importCustomers( @Param('siteId') siteId: number, @Body() body: { items?: any[]; csv?: string } ) { try { const adapter = await this.siteApiService.getAdapter(siteId); let items = body.items || []; if (!items.length && body.csv) { const lines = body.csv.split(/\r?\n/).filter(Boolean); const header = lines.shift()?.split(',') || []; items = lines.map((line) => { const cols = line.split(','); const obj: any = {}; header.forEach((h, i) => (obj[h] = cols[i])); return obj; }); } const created: any[] = []; const failed: any[] = []; for (const item of items) { try { const data = await adapter.createCustomer(item); created.push(data); } catch (e) { failed.push({ item, error: (e as any).message }); } } return successResponse({ created, failed }); } catch (error) { return errorResponse(error.message); } } @Put('/:siteId/customers/:id') @ApiOkResponse({ type: UnifiedCustomerDTO }) async updateCustomer( @Param('siteId') siteId: number, @Param('id') id: string, @Body() body: UnifiedCustomerDTO ) { this.logger.info(`[Site API] 更新客户开始, siteId: ${siteId}, customerId: ${id}`); try { const adapter = await this.siteApiService.getAdapter(siteId); const data = await adapter.updateCustomer(id, body); this.logger.info(`[Site API] 更新客户成功, siteId: ${siteId}, customerId: ${id}`); return successResponse(data); } catch (error) { this.logger.error(`[Site API] 更新客户失败, siteId: ${siteId}, customerId: ${id}, 错误信息: ${error.message}`); return errorResponse(error.message); } } @Del('/:siteId/customers/:id') @ApiOkResponse({ type: Boolean }) async deleteCustomer( @Param('siteId') siteId: number, @Param('id') id: string ) { this.logger.info(`[Site API] 删除客户开始, siteId: ${siteId}, customerId: ${id}`); try { const adapter = await this.siteApiService.getAdapter(siteId); const success = await adapter.deleteCustomer(id); this.logger.info(`[Site API] 删除客户成功, siteId: ${siteId}, customerId: ${id}`); return successResponse(success); } catch (error) { this.logger.error(`[Site API] 删除客户失败, siteId: ${siteId}, customerId: ${id}, 错误信息: ${error.message}`); return errorResponse(error.message); } } @Post('/:siteId/customers/batch') @ApiOkResponse({ type: Object }) async batchCustomers( @Param('siteId') siteId: number, @Body() body: { create?: any[]; update?: any[]; delete?: Array } ) { this.logger.info(`[Site API] 批量处理客户开始, siteId: ${siteId}`); try { const adapter = await this.siteApiService.getAdapter(siteId); const created: any[] = []; const updated: any[] = []; const deleted: Array = []; const failed: any[] = []; if (body.create?.length) { for (const item of body.create) { try { const data = await adapter.createCustomer(item); created.push(data); } catch (e) { failed.push({ action: 'create', item, error: (e as any).message }); } } } if (body.update?.length) { for (const item of body.update) { try { const id = item.id; const data = await adapter.updateCustomer(id, item); updated.push(data); } catch (e) { failed.push({ action: 'update', item, error: (e as any).message }); } } } if (body.delete?.length) { for (const id of body.delete) { try { const ok = await adapter.deleteCustomer(id); if (ok) deleted.push(id); else failed.push({ action: 'delete', id, error: 'delete failed' }); } catch (e) { failed.push({ action: 'delete', id, error: (e as any).message }); } } } this.logger.info(`[Site API] 批量处理客户完成, siteId: ${siteId}`); return successResponse({ created, updated, deleted, failed }); } catch (error) { this.logger.error(`[Site API] 批量处理客户失败, siteId: ${siteId}, 错误信息: ${error.message}`); return errorResponse(error.message); } } }