diff --git a/src/controller/subscription.controller.ts b/src/controller/subscription.controller.ts index 75decc7..5506063 100644 --- a/src/controller/subscription.controller.ts +++ b/src/controller/subscription.controller.ts @@ -10,6 +10,7 @@ export class SubscriptionController { @Inject() subscriptionService: SubscriptionService; + // 同步订阅:根据站点 ID 拉取并更新本地订阅数据 @ApiOkResponse({ type: BooleanRes }) @Post('/sync/:siteId') async sync(@Param('siteId') siteId: string) { @@ -21,6 +22,7 @@ export class SubscriptionController { } } + // 订阅列表:分页 + 筛选 @ApiOkResponse({ type: SubscriptionListRes }) @Get('/list') async list(@Query() query: QuerySubscriptionDTO) { diff --git a/src/dto/reponse.dto.ts b/src/dto/reponse.dto.ts index f55fa96..93733eb 100644 --- a/src/dto/reponse.dto.ts +++ b/src/dto/reponse.dto.ts @@ -119,7 +119,7 @@ export class PaymentMethodListRes extends SuccessArrayWrapper( PaymentMethodDTO ) {} -// 订阅分页数据 +// 订阅分页数据(列表 + 总数等分页信息) export class SubscriptionPaginatedResponse extends PaginatedWrapper(Subscription) {} -// 订阅分页返回数据 +// 订阅分页返回数据(统一成功包装) export class SubscriptionListRes extends SuccessWrapper(SubscriptionPaginatedResponse) {} diff --git a/src/dto/subscription.dto.ts b/src/dto/subscription.dto.ts index e231948..79794db 100644 --- a/src/dto/subscription.dto.ts +++ b/src/dto/subscription.dto.ts @@ -2,27 +2,34 @@ import { ApiProperty } from '@midwayjs/swagger'; import { Rule, RuleType } from '@midwayjs/validate'; import { SubscriptionStatus } from '../enums/base.enum'; +// 订阅列表查询参数(分页与筛选) export class QuerySubscriptionDTO { + // 当前页码(从 1 开始) @ApiProperty({ example: 1, description: '页码' }) @Rule(RuleType.number().default(1)) current: number; + // 每页数量 @ApiProperty({ example: 10, description: '每页大小' }) @Rule(RuleType.number().default(10)) pageSize: number; + // 站点 ID(可选) @ApiProperty({ description: '站点ID' }) @Rule(RuleType.string().allow('')) siteId: string; + // 订阅状态筛选(可选),支持枚举值 @ApiProperty({ description: '订阅状态', enum: SubscriptionStatus }) @Rule(RuleType.string().valid(...Object.values(SubscriptionStatus)).allow('')) status: SubscriptionStatus | ''; + // 客户邮箱(模糊匹配,可选) @ApiProperty({ description: '客户邮箱' }) @Rule(RuleType.string().allow('')) customer_email: string; + // 关键字(订阅ID、邮箱等,模糊匹配,可选) @ApiProperty({ description: '关键字(订阅ID、邮箱等)' }) @Rule(RuleType.string().allow('')) keyword: string; diff --git a/src/entity/subscription.entity.ts b/src/entity/subscription.entity.ts index 76830e6..f4c76bb 100644 --- a/src/entity/subscription.entity.ts +++ b/src/entity/subscription.entity.ts @@ -12,96 +12,115 @@ import { SubscriptionStatus } from '../enums/base.enum'; @Entity('subscription') @Exclude() export class Subscription { + // 本地主键,自增 ID @ApiProperty() @PrimaryGeneratedColumn() @Expose() id: number; + // 站点唯一标识,用于区分不同来源站点 @ApiProperty({ description: '来源站点唯一标识' }) @Column() @Expose() siteId: string; + // WooCommerce 订阅的原始 ID(字符串化),用于幂等更新 @ApiProperty({ description: 'WooCommerce 订阅 ID' }) @Column() @Expose() externalSubscriptionId: string; + // 订阅状态(active/cancelled/on-hold 等) @ApiProperty({ type: SubscriptionStatus }) @Column({ type: 'enum', enum: SubscriptionStatus }) @Expose() status: SubscriptionStatus; + // 货币代码,例如 USD/CAD @ApiProperty() @Column({ default: '' }) @Expose() currency: string; + // 总金额,保留两位小数 @ApiProperty() @Column('decimal', { precision: 10, scale: 2, default: 0 }) @Expose() total: number; + // 计费周期(day/week/month/year) @ApiProperty({ description: '计费周期 e.g. day/week/month/year' }) @Column({ default: '' }) @Expose() billing_period: string; + // 计费周期间隔(例如 1/3/12) @ApiProperty({ description: '计费周期间隔 e.g. 1/3/12' }) @Column({ type: 'int', default: 0 }) @Expose() billing_interval: number; + // 客户 ID(WooCommerce 用户 ID) @ApiProperty() @Column({ type: 'int', default: 0 }) @Expose() customer_id: number; + // 客户邮箱(从 billing.email 或 customer_email 提取) @ApiProperty() @Column({ default: '' }) @Expose() customer_email: string; + // 父订单/订阅 ID(如有) @ApiProperty({ description: '父订单/父订阅ID(如有)' }) @Column({ type: 'int', default: 0 }) @Expose() parent_id: number; + // 订阅开始时间 @ApiProperty() @Column({ type: 'timestamp', nullable: true }) @Expose() start_date: Date; + // 试用结束时间 @ApiProperty() @Column({ type: 'timestamp', nullable: true }) @Expose() trial_end: Date; + // 下次支付时间 @ApiProperty() @Column({ type: 'timestamp', nullable: true }) @Expose() next_payment_date: Date; + // 订阅结束时间 @ApiProperty() @Column({ type: 'timestamp', nullable: true }) @Expose() end_date: Date; + // 商品项(订阅行项目) @ApiProperty() @Column({ type: 'json', nullable: true }) @Expose() line_items: any[]; + // 额外元数据(键值对) @ApiProperty() @Column({ type: 'json', nullable: true }) @Expose() meta_data: any[]; + // 创建时间(数据库自动生成) @ApiProperty({ example: '2022-12-12 11:11:11', description: '创建时间', required: true }) @CreateDateColumn() @Expose() createdAt: Date; + // 更新时间(数据库自动生成) @ApiProperty({ example: '2022-12-12 11:11:11', description: '更新时间', required: true }) @UpdateDateColumn() @Expose() diff --git a/src/service/subscription.service.ts b/src/service/subscription.service.ts index d8b2b92..2ec7597 100644 --- a/src/service/subscription.service.ts +++ b/src/service/subscription.service.ts @@ -15,6 +15,10 @@ export class SubscriptionService { @InjectEntityModel(Subscription) subscriptionModel: Repository; + /** + * 同步指定站点的订阅列表 + * - 从 WooCommerce 拉取订阅并逐条入库/更新 + */ async syncSubscriptions(siteId: string) { const subs = await this.wPService.getSubscriptions(siteId); for (const sub of subs) { @@ -22,6 +26,11 @@ export class SubscriptionService { } } + /** + * 同步单条订阅 + * - 规范化字段、设置幂等键 externalSubscriptionId + * - 已存在则更新,不存在则新增 + */ async syncSingleSubscription(siteId: string, sub: any) { const { line_items, ...raw } = sub; const entity: Partial = { @@ -44,6 +53,9 @@ export class SubscriptionService { } } + /** + * 获取订阅分页列表(支持站点、状态、邮箱与关键字筛选) + */ async getSubscriptionList({ current = 1, pageSize = 10, diff --git a/src/service/wp.service.ts b/src/service/wp.service.ts index 7021fef..7cba5a4 100644 --- a/src/service/wp.service.ts +++ b/src/service/wp.service.ts @@ -136,7 +136,8 @@ export class WPService { /** * 获取 WooCommerce Subscriptions - * 优先尝试 wc/v1/subscriptions (Subscriptions 插件提供), 如失败则回退 wc/v3/subscriptions(部分版本提供)。 + * 优先尝试 wc/v1/subscriptions(Subscriptions 插件提供),失败时回退 wc/v3/subscriptions。 + * 返回所有分页合并后的订阅数组。 */ async getSubscriptions(siteId: string): Promise[]> { const site = this.geSite(siteId);