Compare commits
No commits in common. "a59d5a7b7548118ca7aef39a80b630cd37b29b0e" and "c7480ccc8a5a650c2d623c77fd913ddfb49f2f86" have entirely different histories.
a59d5a7b75
...
c7480ccc8a
|
|
@ -52,7 +52,7 @@ export class WebhookController {
|
||||||
// 中文注释:从数据库获取站点配置
|
// 中文注释:从数据库获取站点配置
|
||||||
const site = await this.siteService.get(Number(siteId), true);
|
const site = await this.siteService.get(Number(siteId), true);
|
||||||
|
|
||||||
if (!site || !source.includes(site.apiUrl)) {
|
if (!site || !source.includes(site.wpApiUrl)) {
|
||||||
console.log('domain not match');
|
console.log('domain not match');
|
||||||
return {
|
return {
|
||||||
code: HttpStatus.BAD_REQUEST,
|
code: HttpStatus.BAD_REQUEST,
|
||||||
|
|
@ -97,13 +97,13 @@ export class WebhookController {
|
||||||
? await this.wpApiService.getVariations(site, body.id)
|
? await this.wpApiService.getVariations(site, body.id)
|
||||||
: [];
|
: [];
|
||||||
await this.wpProductService.syncProductAndVariations(
|
await this.wpProductService.syncProductAndVariations(
|
||||||
String(site.id),
|
site.id,
|
||||||
body,
|
body,
|
||||||
variations
|
variations
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
case 'product.deleted':
|
case 'product.deleted':
|
||||||
await this.wpProductService.delWpProduct(String(site.id), body.id);
|
await this.wpProductService.delWpProduct(site.id, body.id);
|
||||||
break;
|
break;
|
||||||
case 'order.created':
|
case 'order.created':
|
||||||
case 'order.updated':
|
case 'order.updated':
|
||||||
|
|
|
||||||
|
|
@ -1351,8 +1351,7 @@ export class OrderService {
|
||||||
return {
|
return {
|
||||||
...order,
|
...order,
|
||||||
siteName: site?.siteName,
|
siteName: site?.siteName,
|
||||||
// 中文注释:Site 实体无邮箱字段,这里返回空字符串保持兼容
|
email: site?.email,
|
||||||
email: '',
|
|
||||||
items,
|
items,
|
||||||
sales,
|
sales,
|
||||||
refundItems,
|
refundItems,
|
||||||
|
|
|
||||||
|
|
@ -12,38 +12,32 @@ export class SiteService {
|
||||||
siteModel: Repository<Site>;
|
siteModel: Repository<Site>;
|
||||||
|
|
||||||
async syncFromConfig(sites: WpSite[] = []) {
|
async syncFromConfig(sites: WpSite[] = []) {
|
||||||
// 将配置中的 WpSite 同步到数据库 Site 表(用于一次性导入或初始化)
|
for (const s of sites) {
|
||||||
for (const siteConfig of sites) {
|
const exist = await this.siteModel.findOne({ where: { siteName: s.siteName } });
|
||||||
// 按站点名称查询是否已存在记录
|
|
||||||
const exist = await this.siteModel.findOne({ where: { siteName: siteConfig.siteName } });
|
|
||||||
// 将 WpSite 字段映射为 Site 实体字段
|
|
||||||
const payload: Partial<Site> = {
|
const payload: Partial<Site> = {
|
||||||
siteName: siteConfig.siteName,
|
siteName: s.siteName,
|
||||||
apiUrl: (siteConfig as any).wpApiUrl,
|
apiUrl: (s as any).wpApiUrl,
|
||||||
consumerKey: (siteConfig as any).consumerKey,
|
consumerKey: (s as any).consumerKey,
|
||||||
consumerSecret: (siteConfig as any).consumerSecret,
|
consumerSecret: (s as any).consumerSecret,
|
||||||
type: 'woocommerce',
|
type: 'woocommerce',
|
||||||
};
|
};
|
||||||
// 存在则更新,不存在则插入新记录
|
|
||||||
if (exist) await this.siteModel.update({ id: exist.id }, payload);
|
if (exist) await this.siteModel.update({ id: exist.id }, payload);
|
||||||
else await this.siteModel.insert(payload as Site);
|
else await this.siteModel.insert(payload as Site);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async create(data: Partial<Site>) {
|
async create(data: Partial<Site>) {
|
||||||
// 创建新的站点记录
|
|
||||||
await this.siteModel.insert(data as Site);
|
await this.siteModel.insert(data as Site);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
async update(id: string | number, data: UpdateSiteDTO) {
|
async update(id: string | number, data: UpdateSiteDTO) {
|
||||||
// 更新指定站点记录,将布尔 isDisabled 转换为数值 0/1
|
|
||||||
const payload: Partial<Site> = {
|
const payload: Partial<Site> = {
|
||||||
...data,
|
...data,
|
||||||
isDisabled:
|
isDisabled:
|
||||||
data.isDisabled === undefined // 未传入则不更新该字段
|
data.isDisabled === undefined
|
||||||
? undefined
|
? undefined
|
||||||
: data.isDisabled // true -> 1, false -> 0
|
: data.isDisabled
|
||||||
? 1
|
? 1
|
||||||
: 0,
|
: 0,
|
||||||
} as any;
|
} as any;
|
||||||
|
|
@ -52,25 +46,19 @@ export class SiteService {
|
||||||
}
|
}
|
||||||
|
|
||||||
async get(id: string | number, includeSecret = false) {
|
async get(id: string | number, includeSecret = false) {
|
||||||
// 根据主键获取站点;includeSecret 为 true 时返回密钥字段
|
const s = await this.siteModel.findOne({ where: { id: Number(id) } });
|
||||||
const site = await this.siteModel.findOne({ where: { id: Number(id) } });
|
if (!s) return null;
|
||||||
if (!site) return null;
|
if (includeSecret) return s;
|
||||||
if (includeSecret) return site;
|
const { consumerKey, consumerSecret, emailPswd, ...rest } = s as any;
|
||||||
// 默认不返回密钥,进行字段脱敏
|
|
||||||
const { consumerKey, consumerSecret, ...rest } = site;
|
|
||||||
return rest;
|
return rest;
|
||||||
}
|
}
|
||||||
|
|
||||||
async list(param: { current?: number; pageSize?: number; keyword?: string; isDisabled?: boolean; ids?: string }, includeSecret = false) {
|
async list(param: { current?: number; pageSize?: number; keyword?: string; isDisabled?: boolean; ids?: string }, includeSecret = false) {
|
||||||
// 分页查询站点列表,支持关键字、禁用状态与 ID 列表过滤
|
|
||||||
const { current = 1, pageSize = 10, keyword, isDisabled, ids } = (param || {}) as any;
|
const { current = 1, pageSize = 10, keyword, isDisabled, ids } = (param || {}) as any;
|
||||||
const where: any = {};
|
const where: any = {};
|
||||||
// 按名称模糊查询
|
|
||||||
if (keyword) where.siteName = Like(`%${keyword}%`);
|
if (keyword) where.siteName = Like(`%${keyword}%`);
|
||||||
// 按禁用状态过滤(布尔转数值)
|
|
||||||
if (typeof isDisabled === 'boolean') where.isDisabled = isDisabled ? 1 : 0;
|
if (typeof isDisabled === 'boolean') where.isDisabled = isDisabled ? 1 : 0;
|
||||||
if (ids) {
|
if (ids) {
|
||||||
// 解析逗号分隔的 ID 字符串为数字数组,并过滤非法值
|
|
||||||
const numIds = String(ids)
|
const numIds = String(ids)
|
||||||
.split(',')
|
.split(',')
|
||||||
.filter(Boolean)
|
.filter(Boolean)
|
||||||
|
|
@ -78,18 +66,15 @@ export class SiteService {
|
||||||
.filter((v) => !Number.isNaN(v));
|
.filter((v) => !Number.isNaN(v));
|
||||||
if (numIds.length > 0) where.id = In(numIds);
|
if (numIds.length > 0) where.id = In(numIds);
|
||||||
}
|
}
|
||||||
// 进行分页查询(skip/take)并返回总条数
|
|
||||||
const [items, total] = await this.siteModel.findAndCount({ where, skip: (current - 1) * pageSize, take: pageSize });
|
const [items, total] = await this.siteModel.findAndCount({ where, skip: (current - 1) * pageSize, take: pageSize });
|
||||||
// 根据 includeSecret 决定是否脱敏返回密钥字段
|
const data = includeSecret ? items : items.map((s: any) => {
|
||||||
const data = includeSecret ? items : items.map((item: any) => {
|
const { consumerKey, consumerSecret, ...rest } = s;
|
||||||
const { consumerKey, consumerSecret, ...rest } = item;
|
|
||||||
return rest;
|
return rest;
|
||||||
});
|
});
|
||||||
return { items: data, total, current, pageSize };
|
return { items: data, total, current, pageSize };
|
||||||
}
|
}
|
||||||
|
|
||||||
async disable(id: string | number, disabled: boolean) {
|
async disable(id: string | number, disabled: boolean) {
|
||||||
// 设置站点禁用状态(true -> 1, false -> 0)
|
|
||||||
await this.siteModel.update({ id: Number(id) }, { isDisabled: disabled ? 1 : 0 });
|
await this.siteModel.update({ id: Number(id) }, { isDisabled: disabled ? 1 : 0 });
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -36,8 +36,9 @@ export class WPService {
|
||||||
* @param namespace API 命名空间,默认 wc/v3;订阅推荐 wcs/v1
|
* @param namespace API 命名空间,默认 wc/v3;订阅推荐 wcs/v1
|
||||||
*/
|
*/
|
||||||
private createApi(site: any, namespace: WooCommerceRestApiVersion = 'wc/v3') {
|
private createApi(site: any, namespace: WooCommerceRestApiVersion = 'wc/v3') {
|
||||||
|
const url = site.wpApiUrl ?? site.apiUrl; // 中文注释:兼容数据库 Site 与 WpSite 结构
|
||||||
return new WooCommerceRestApi({
|
return new WooCommerceRestApi({
|
||||||
url: site.apiUrl,
|
url,
|
||||||
consumerKey: site.consumerKey,
|
consumerKey: site.consumerKey,
|
||||||
consumerSecret: site.consumerSecret,
|
consumerSecret: site.consumerSecret,
|
||||||
version: namespace,
|
version: namespace,
|
||||||
|
|
@ -51,19 +52,15 @@ export class WPService {
|
||||||
const page = params.page ?? 1;
|
const page = params.page ?? 1;
|
||||||
const per_page = params.per_page ?? 100;
|
const per_page = params.per_page ?? 100;
|
||||||
const res = await api.get(resource.replace(/^\/+/, ''), { ...params, page, per_page });
|
const res = await api.get(resource.replace(/^\/+/, ''), { ...params, page, per_page });
|
||||||
if (res?.headers?.['content-type']?.includes('text/html')) {
|
|
||||||
throw new Error('接口返回了 text/html,可能为 WordPress 登录页或错误页,请检查站点配置或权限');
|
|
||||||
}
|
|
||||||
const data = res.data as T[];
|
const data = res.data as T[];
|
||||||
const totalPages = Number(res.headers?.['x-wp-totalpages'] ?? 1);
|
const totalPages = Number(res.headers?.['x-wp-totalpages'] ?? 1);
|
||||||
const total = Number(res.headers?.['x-wp-total']?? 1)
|
return { items: data, totalPages, page, per_page };
|
||||||
return { items: data, total, totalPages, page, per_page };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 通过 SDK 聚合分页数据,返回全部数据
|
* 通过 SDK 聚合分页数据,返回全部数据
|
||||||
*/
|
*/
|
||||||
private async sdkGetAll<T>(api: WooCommerceRestApi, resource: string, params: Record<string, any> = {}, maxPages: number = 50): Promise<T[]> {
|
private async sdkGetAll<T>(api: any, resource: string, params: Record<string, any> = {}, maxPages: number = 50): Promise<T[]> {
|
||||||
const result: T[] = [];
|
const result: T[] = [];
|
||||||
for (let page = 1; page <= maxPages; page++) {
|
for (let page = 1; page <= maxPages; page++) {
|
||||||
const { items, totalPages } = await this.sdkGetPage<T>(api, resource, { ...params, page });
|
const { items, totalPages } = await this.sdkGetPage<T>(api, resource, { ...params, page });
|
||||||
|
|
@ -89,7 +86,7 @@ export class WPService {
|
||||||
param: Record<string, any> = {}
|
param: Record<string, any> = {}
|
||||||
): Promise<T> {
|
): Promise<T> {
|
||||||
try {
|
try {
|
||||||
const apiUrl = site.apiUrl;
|
const apiUrl = site.wpApiUrl ?? site.apiUrl;
|
||||||
const { consumerKey, consumerSecret } = site;
|
const { consumerKey, consumerSecret } = site;
|
||||||
// 构建 URL,规避多/或少/问题
|
// 构建 URL,规避多/或少/问题
|
||||||
const url = this.buildURL(apiUrl, '/wp-json', endpoint);
|
const url = this.buildURL(apiUrl, '/wp-json', endpoint);
|
||||||
|
|
@ -250,7 +247,7 @@ export class WPService {
|
||||||
site: any,
|
site: any,
|
||||||
data: Record<string, any>
|
data: Record<string, any>
|
||||||
): Promise<Boolean> {
|
): Promise<Boolean> {
|
||||||
const apiUrl = site.apiUrl;
|
const apiUrl = site.wpApiUrl ?? site.apiUrl;
|
||||||
const { consumerKey, consumerSecret } = site;
|
const { consumerKey, consumerSecret } = site;
|
||||||
const auth = Buffer.from(`${consumerKey}:${consumerSecret}`).toString(
|
const auth = Buffer.from(`${consumerKey}:${consumerSecret}`).toString(
|
||||||
'base64'
|
'base64'
|
||||||
|
|
@ -350,7 +347,7 @@ export class WPService {
|
||||||
orderId: string,
|
orderId: string,
|
||||||
data: Record<string, any>
|
data: Record<string, any>
|
||||||
) {
|
) {
|
||||||
const apiUrl = site.apiUrl;
|
const apiUrl = site.wpApiUrl ?? site.apiUrl;
|
||||||
const { consumerKey, consumerSecret } = site;
|
const { consumerKey, consumerSecret } = site;
|
||||||
const auth = Buffer.from(`${consumerKey}:${consumerSecret}`).toString(
|
const auth = Buffer.from(`${consumerKey}:${consumerSecret}`).toString(
|
||||||
'base64'
|
'base64'
|
||||||
|
|
@ -378,7 +375,7 @@ export class WPService {
|
||||||
orderId: string,
|
orderId: string,
|
||||||
trackingId: string,
|
trackingId: string,
|
||||||
): Promise<Boolean> {
|
): Promise<Boolean> {
|
||||||
const apiUrl = site.apiUrl;
|
const apiUrl = site.wpApiUrl ?? site.apiUrl;
|
||||||
const { consumerKey, consumerSecret } = site;
|
const { consumerKey, consumerSecret } = site;
|
||||||
const auth = Buffer.from(`${consumerKey}:${consumerSecret}`).toString(
|
const auth = Buffer.from(`${consumerKey}:${consumerSecret}`).toString(
|
||||||
'base64'
|
'base64'
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import { Product } from '../entity/product.entity';
|
import { Product } from '../entity/product.entity';
|
||||||
import { Inject, Provide } from '@midwayjs/core';
|
import { Inject, Provide } from '@midwayjs/core';
|
||||||
import { WPService } from './wp.service';
|
import { WPService } from './wp.service';
|
||||||
|
import { WpSite } from '../interface';
|
||||||
import { WpProduct } from '../entity/wp_product.entity';
|
import { WpProduct } from '../entity/wp_product.entity';
|
||||||
import { InjectEntityModel } from '@midwayjs/typeorm';
|
import { InjectEntityModel } from '@midwayjs/typeorm';
|
||||||
import { And, Like, Not, Repository } from 'typeorm';
|
import { And, Like, Not, Repository } from 'typeorm';
|
||||||
|
|
@ -32,19 +33,28 @@ export class WpProductService {
|
||||||
|
|
||||||
async syncAllSites() {
|
async syncAllSites() {
|
||||||
// 中文注释:从数据库获取所有启用的站点,并逐站点同步产品与变体
|
// 中文注释:从数据库获取所有启用的站点,并逐站点同步产品与变体
|
||||||
const { items: sites } = await this.siteService.list({ current: 1, pageSize: Infinity, isDisabled: false }, true);
|
const { items } = await this.siteService.list({ current: 1, pageSize: 1000, isDisabled: false }, true);
|
||||||
for (const site of sites) {
|
for (const s of items as any[]) {
|
||||||
|
const site: WpSite = {
|
||||||
|
id: String(s.id),
|
||||||
|
wpApiUrl: s.apiUrl,
|
||||||
|
consumerKey: s.consumerKey,
|
||||||
|
consumerSecret: s.consumerSecret,
|
||||||
|
siteName: s.siteName,
|
||||||
|
email: '',
|
||||||
|
emailPswd: '',
|
||||||
|
};
|
||||||
const products = await this.wpApiService.getProducts(site);
|
const products = await this.wpApiService.getProducts(site);
|
||||||
for (const product of products) {
|
for (const product of products) {
|
||||||
const variations =
|
const variations =
|
||||||
product.type === 'variable'
|
product.type === 'variable'
|
||||||
? await this.wpApiService.getVariations(site, product.id)
|
? await this.wpApiService.getVariations(site, product.id)
|
||||||
: [];
|
: [];
|
||||||
await this.syncProductAndVariations(String(site.id), product, variations);
|
await this.syncProductAndVariations(site.id, product, variations);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 同步一个网站
|
|
||||||
async syncSite(siteId: string) {
|
async syncSite(siteId: string) {
|
||||||
// 中文注释:通过数据库获取站点并转换为 WpSite,用于后续 WooCommerce 同步
|
// 中文注释:通过数据库获取站点并转换为 WpSite,用于后续 WooCommerce 同步
|
||||||
const site = await this.siteService.get(Number(siteId), true);
|
const site = await this.siteService.get(Number(siteId), true);
|
||||||
|
|
@ -70,7 +80,7 @@ export class WpProductService {
|
||||||
? await this.wpApiService.getVariations(site, product.id)
|
? await this.wpApiService.getVariations(site, product.id)
|
||||||
: [];
|
: [];
|
||||||
|
|
||||||
await this.syncProductAndVariations(String(site.id), product, variations);
|
await this.syncProductAndVariations(site.id, product, variations);
|
||||||
}
|
}
|
||||||
|
|
||||||
const filteredIds = externalIds.filter(id => !excludeValues.includes(id));
|
const filteredIds = externalIds.filter(id => !excludeValues.includes(id));
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue