import { Provide, Scope, ScopeEnum } from '@midwayjs/core'; import { InjectEntityModel } from '@midwayjs/typeorm'; import { Repository, Like, In } from 'typeorm'; import { Site } from '../entity/site.entity'; import { WpSite } from '../interface'; import { CreateSiteDTO, UpdateSiteDTO } from '../dto/site.dto'; import { Area } from '../entity/area.entity'; @Provide() @Scope(ScopeEnum.Singleton) export class SiteService { @InjectEntityModel(Site) siteModel: Repository; @InjectEntityModel(Area) areaModel: Repository; async syncFromConfig(sites: WpSite[] = []) { // 将配置中的 WpSite 同步到数据库 Site 表(用于一次性导入或初始化) for (const siteConfig of sites) { // 按站点名称查询是否已存在记录 const exist = await this.siteModel.findOne({ where: { name: siteConfig.name }, }); // 将 WpSite 字段映射为 Site 实体字段 const payload: Partial = { name: siteConfig.name, apiUrl: (siteConfig as any).wpApiUrl, consumerKey: (siteConfig as any).consumerKey, consumerSecret: (siteConfig as any).consumerSecret, type: 'woocommerce', }; // 存在则更新,不存在则插入新记录 if (exist) { await this.siteModel.update({ id: exist.id }, payload); } else { await this.siteModel.insert(payload as Site); } } } async create(data: CreateSiteDTO) { // 从 DTO 中分离出区域代码和其他站点数据 const { areas: areaCodes, ...restData } = data; const newSite = new Site(); Object.assign(newSite, restData); // 如果传入了区域代码,则查询并关联 Area 实体 if (areaCodes && areaCodes.length > 0) { const areas = await this.areaModel.findBy({ code: In(areaCodes), }); newSite.areas = areas; } else { // 如果没有传入区域,则关联一个空数组,代表"全局" newSite.areas = []; } // 使用 save 方法保存实体及其关联关系 await this.siteModel.save(newSite); return true; } async update(id: string | number, data: UpdateSiteDTO) { // 从 DTO 中分离出区域代码和其他站点数据 const { areas: areaCodes, ...restData } = data; // 首先,根据 ID 查找要更新的站点实体 const siteToUpdate = await this.siteModel.findOne({ where: { id: Number(id) }, }); if (!siteToUpdate) { // 如果找不到站点,则操作失败 return false; } // 更新站点的基本字段 const payload: Partial = { ...restData, isDisabled: data.isDisabled === undefined ? undefined : data.isDisabled ? 1 : 0, } as any; Object.assign(siteToUpdate, payload); // 如果 DTO 中传入了 areas 字段(即使是空数组),也要更新关联关系 if (areaCodes !== undefined) { if (areaCodes.length > 0) { // 如果区域代码数组不为空,则查找并更新关联 const areas = await this.areaModel.findBy({ code: In(areaCodes), }); siteToUpdate.areas = areas; } else { // 如果传入空数组,则清空所有关联,代表"全局" siteToUpdate.areas = []; } } // 使用 save 方法保存实体及其更新后的关联关系 await this.siteModel.save(siteToUpdate); return true; } async get(id: string | number, includeSecret = false) { // 根据主键获取站点,并使用 relations 加载关联的 areas const site = await this.siteModel.findOne({ where: { id: Number(id) }, relations: ['areas'], }); if (!site) { return null; } // 如果需要包含密钥,则直接返回 if (includeSecret) { return site; } // 默认不返回密钥,进行字段脱敏 const { consumerKey, consumerSecret, ...rest } = site; return rest; } 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 where: any = {}; // 按名称模糊查询 if (keyword) { where.name = Like(`%${keyword}%`); } // 按禁用状态过滤(布尔转数值) if (typeof isDisabled === 'boolean') { where.isDisabled = isDisabled ? 1 : 0; } if (ids) { // 解析逗号分隔的 ID 字符串为数字数组,并过滤非法值 const numIds = String(ids) .split(',') .filter(Boolean) .map(i => Number(i)) .filter(v => !Number.isNaN(v)); if (numIds.length > 0) { where.id = In(numIds); } } // 进行分页查询,并使用 relations 加载关联的 areas const [items, total] = await this.siteModel.findAndCount({ where, skip: (current - 1) * pageSize, take: pageSize, relations: ['areas'], }); // 根据 includeSecret 决定是否脱敏返回密钥字段 const data = includeSecret ? items : items.map((item: any) => { const { consumerKey, consumerSecret, ...rest } = item; return rest; }); return { items: data, total, current, pageSize }; } async disable(id: string | number, disabled: boolean) { // 设置站点禁用状态(true -> 1, false -> 0) await this.siteModel.update({ id: Number(id) }, { isDisabled: disabled }); return true; } }