import { Inject, Provide } from '@midwayjs/core'; import { InjectEntityModel } from '@midwayjs/typeorm'; import { Repository } from 'typeorm'; import { SyncOperationResult, UnifiedPaginationDTO, UnifiedSearchParamsDTO, BatchOperationResult } from '../dto/api.dto'; import { UnifiedCustomerDTO } from '../dto/site-api.dto'; import { Customer } from '../entity/customer.entity'; import { CustomerTag } from '../entity/customer_tag.entity'; import { Order } from '../entity/order.entity'; import { SiteApiService } from './site-api.service'; import { CreateCustomerDTO, CustomerDTO, CustomerStatisticDTO, CustomerStatisticQueryParamsDTO } from '../dto/customer.dto'; @Provide() export class CustomerService { @InjectEntityModel(Order) orderModel: Repository; @InjectEntityModel(CustomerTag) customerTagModel: Repository; @InjectEntityModel(Customer) customerModel: Repository; @Inject() siteApiService: SiteApiService; /** * 根据邮箱查找客户 */ async findCustomerByEmail(email: string): Promise { return await this.customerModel.findOne({ where: { email } }); } /** * 将站点客户数据映射为本地客户实体数据 * 处理字段映射和数据转换,确保所有字段正确同步 */ private mapSiteCustomerToCustomer(siteCustomer: UnifiedCustomerDTO, siteId: number): Partial { return { site_id: siteId, // 使用站点ID而不是客户ID site_created_at: this.parseDate(siteCustomer.date_created), site_updated_at: this.parseDate(siteCustomer.date_modified), origin_id: Number(siteCustomer.id), email: siteCustomer.email, first_name: siteCustomer.first_name, last_name: siteCustomer.last_name, fullname: siteCustomer.fullname || `${siteCustomer.first_name || ''} ${siteCustomer.last_name || ''}`.trim(), username: siteCustomer.username || '', phone: siteCustomer.phone || '', avatar: siteCustomer.avatar, billing: siteCustomer.billing, shipping: siteCustomer.shipping, raw: siteCustomer.raw || siteCustomer, }; } /** * 解析日期字符串或时间戳 */ private parseDate(dateValue: any): Date | null { if (!dateValue) return null; if (dateValue instanceof Date) { return dateValue; } if (typeof dateValue === 'number') { // 处理Unix时间戳(秒或毫秒) return new Date(dateValue > 9999999999 ? dateValue : dateValue * 1000); } if (typeof dateValue === 'string') { const date = new Date(dateValue); return isNaN(date.getTime()) ? null : date; } return null; } /** * 创建新客户 */ async createCustomer(customerData: Partial): Promise { const customer = this.customerModel.create(customerData); return await this.customerModel.save(customer); } /** * 更新客户信息 */ async updateCustomer(id: number, customerData: Partial): Promise { await this.customerModel.update(id, customerData); return await this.customerModel.findOne({ where: { id } }); } /** * 创建或更新客户(upsert) * 如果客户存在则更新,不存在则创建 */ async upsertCustomer( customerData: Partial, ): Promise<{ customer: Customer; isCreated: boolean }> { if(!customerData.email) throw new Error("客户邮箱不能为空"); // 首先尝试根据邮箱查找现有客户 const existingCustomer = await this.findCustomerByEmail(customerData.email); if (existingCustomer) { // 如果客户存在,更新客户信息 const updatedCustomer = await this.updateCustomer(existingCustomer.id, customerData); return { customer: updatedCustomer, isCreated: false }; } else { // 如果客户不存在,创建新客户 const newCustomer = await this.createCustomer(customerData); return { customer: newCustomer, isCreated: true }; } } /** * 批量创建或更新客户 * 使用事务确保数据一致性 */ async upsertManyCustomers( customersData: Array> ): Promise { const results = { total: customersData.length, processed: 0, created: 0, updated: 0, errors: [] }; // 批量处理每个客户 for (const customerData of customersData) { try { const result = await this.upsertCustomer(customerData); if (result.isCreated) { results.created++; } else { results.updated++; } results.processed++; } catch (error) { // 记录错误但不中断整个批量操作 results.errors.push({ identifier: customerData.email || String(customerData.id) || 'unknown', error: error.message }); results.processed++; } } return results; } /** * 从站点同步客户数据 * 第一步:调用adapter获取站点客户数据 * 第二步:通过upsertManyCustomers保存这些客户 */ async syncCustomersFromSite( siteId: number, params?: UnifiedSearchParamsDTO ): Promise { try { // 第一步:获取适配器并从站点获取客户数据 const adapter = await this.siteApiService.getAdapter(siteId); const siteCustomers = await adapter.getAllCustomers(params || {}); // 第二步:将站点客户数据转换为客户实体数据 const customersData = siteCustomers.map(siteCustomer => { return this.mapSiteCustomerToCustomer(siteCustomer, siteId); }).map(customer => ({ ...customer, origin_id: String(customer.origin_id), })); // 第三步:批量upsert客户数据 const upsertResult = await this.upsertManyCustomers(customersData); return { total: siteCustomers.length, processed: upsertResult.processed, synced: upsertResult.processed, updated: upsertResult.updated, created: upsertResult.created, errors: upsertResult.errors }; } catch (error) { // 如果获取适配器或站点数据失败,抛出错误 throw new Error(`同步客户数据失败: ${error.message}`); } } /** * 获取客户统计列表(包含订单统计信息) * 支持分页、搜索和排序功能 * 使用原生SQL查询实现复杂的统计逻辑 */ async getCustomerStatisticList(param: CustomerStatisticQueryParamsDTO): Promise<{ items: CustomerStatisticDTO[]; total: number; current: number; pageSize: number; }> { const { page = 1, per_page = 10, search, where, orderBy, } = param; // 将page和per_page转换为current和pageSize const current = page; const pageSize = per_page; const whereConds: string[] = []; const havingConds: string[] = []; // 全局搜索关键词 if (search) { whereConds.push(`o.customer_email LIKE '%${search}%'`); } // where条件过滤 if (where) { // 邮箱搜索 if (where.email) { whereConds.push(`o.customer_email LIKE '%${where.email}%'`); } // customerId 过滤 if (where.customerId) { whereConds.push(`c.id = ${Number(where.customerId)}`); } // rate 过滤 if (where.rate) { whereConds.push(`c.rate = ${Number(where.rate)}`); } // tags 过滤 if (where.tags) { const tagList = where.tags .split(',') .map(tag => `'${tag.trim()}'`) .join(','); havingConds.push(` EXISTS ( SELECT 1 FROM customer_tag ct WHERE ct.email = o.customer_email AND ct.tag IN (${tagList}) ) `); } // 首次购买时间过滤 if (where.first_purchase_date) { havingConds.push( `DATE_FORMAT(MIN(o.date_paid), '%Y-%m') = '${where.first_purchase_date}'` ); } } // 公用过滤 const baseQuery = ` ${whereConds.length ? `WHERE ${whereConds.join(' AND ')}` : ''} GROUP BY o.customer_email ${havingConds.length ? `HAVING ${havingConds.join(' AND ')}` : ''} `; // 排序处理 let orderByClause = ''; if (orderBy) { if (typeof orderBy === 'string') { const [field, direction] = orderBy.split(':'); orderByClause = `ORDER BY ${field} ${direction === 'desc' ? 'DESC' : 'ASC'}`; } else if (typeof orderBy === 'object') { const orderClauses = Object.entries(orderBy).map(([field, direction]) => `${field} ${direction === 'desc' ? 'DESC' : 'ASC'}` ); orderByClause = `ORDER BY ${orderClauses.join(', ')}`; } } else { orderByClause = 'ORDER BY orders ASC, yoone_total DESC'; } // 主查询 const sql = ` SELECT o.customer_email AS email, MIN(o.date_created) AS date_created, MIN(o.date_paid) AS first_purchase_date, MAX(o.date_paid) AS last_purchase_date, COUNT(DISTINCT o.id) AS orders, SUM(o.total) AS total, ANY_VALUE(o.shipping) AS shipping, ANY_VALUE(o.billing) AS billing, ( SELECT JSON_ARRAYAGG(tag) FROM customer_tag ct WHERE ct.email = o.customer_email ) AS tags, c.id AS customerId, c.rate AS rate, yoone_stats.yoone_orders, yoone_stats.yoone_total FROM \`order\` o LEFT JOIN customer c ON o.customer_email = c.email LEFT JOIN ( SELECT oo.customer_email, COUNT(DISTINCT oi.orderId) AS yoone_orders, SUM(oi.total) AS yoone_total FROM order_item oi JOIN \`order\` oo ON oi.orderId = oo.id WHERE oi.name LIKE '%yoone%' GROUP BY oo.customer_email ) yoone_stats ON yoone_stats.customer_email = o.customer_email ${baseQuery} ${orderByClause} LIMIT ${pageSize} OFFSET ${(current - 1) * pageSize} `; // 统计总数 const countSql = ` SELECT COUNT(*) AS total FROM ( SELECT o.customer_email FROM \`order\` o LEFT JOIN customer c ON o.customer_email = c.email ${baseQuery} ) AS sub `; const [items, countResult] = await Promise.all([ this.orderModel.query(sql), this.orderModel.query(countSql), ]); const total = countResult[0]?.total || 0; // 处理tags字段,将JSON字符串转换为数组 const processedItems = items.map(item => { if (item.tags) { try { item.tags = JSON.parse(item.tags); } catch (e) { item.tags = []; } } else { item.tags = []; } return item; }); return { items: processedItems, total, current, pageSize, }; } /** * 获取纯粹的客户列表(不包含订单统计信息) * 支持基本的分页、搜索和排序功能 * 使用TypeORM查询构建器实现 */ async getCustomerList(params: UnifiedSearchParamsDTO): Promise>{ const { page = 1, per_page = 20, where ={}, } = params; // 查询客户列表和总数 const [customers, total] = await this.customerModel.findAndCount({ where, // order: orderBy, skip: (page - 1) * per_page, take: per_page, }); // 获取所有客户的邮箱列表 const emailList = customers.map(customer => customer.email); // 查询所有客户的标签 let customerTagsMap: Record = {}; if (emailList.length > 0) { const customerTags = await this.customerTagModel .createQueryBuilder('tag') .select('tag.email', 'email') .addSelect('tag.tag', 'tag') .where('tag.email IN (:...emailList)', { emailList }) .getRawMany(); // 将标签按邮箱分组 customerTagsMap = customerTags.reduce((acc, item) => { if (!acc[item.email]) { acc[item.email] = []; } acc[item.email].push(item.tag); return acc; }, {} as Record); } // 将标签合并到客户数据中 const customersWithTags = customers.map(customer => ({ ...customer, tags: customerTagsMap[customer.email] || [] })); return { items: customersWithTags, total, page, per_page, totalPages: Math.ceil(total / per_page), }; } async addTag(email: string, tag: string) { const isExist = await this.customerTagModel.findOneBy({ email, tag }); if (isExist) throw new Error(`${tag}已存在`); return await this.customerTagModel.save({ email, tag }); } async delTag(email: string, tag: string) { const isExist = await this.customerTagModel.findOneBy({ email, tag }); if (!isExist) throw new Error(`${tag}不存在`); return await this.customerTagModel.delete({ email, tag }); } async getTags() { const tags = await this.customerTagModel .createQueryBuilder('tag') .select('DISTINCT tag.tag', 'tag') .getRawMany(); return tags.map(t => t.tag); } async setRate(params: { id: number; rate: number }) { return await this.customerModel.update(params.id, { rate: params.rate }); } /** * 批量更新客户 * 每个客户可以有独立的更新字段 * 支持对多个客户进行统一化修改或分别更新 */ async batchUpdateCustomers( updateItems: Array<{ id: number; update_data: Partial }> ): Promise { const results = { total: updateItems.length, processed: 0, updated: 0, errors: [] }; // 批量处理每个客户的更新 for (const item of updateItems) { try { // 检查客户是否存在 const existingCustomer = await this.customerModel.findOne({ where: { id: item.id } }); if (!existingCustomer) { throw new Error(`客户ID ${item.id} 不存在`); } // 更新客户信息 await this.updateCustomer(item.id, item.update_data); results.updated++; results.processed++; } catch (error) { // 记录错误但不中断整个批量操作 results.errors.push({ identifier: String(item.id), error: error.message }); results.processed++; } } return results; } /** * 批量删除客户 * 支持对多个客户进行批量删除操作 * 返回操作结果,包括成功和失败的数量 */ async batchDeleteCustomers(ids: number[]): Promise { const results = { total: ids.length, processed: 0, updated: 0, errors: [] }; // 批量处理每个客户的删除 for (const id of ids) { try { // 检查客户是否存在 const existingCustomer = await this.customerModel.findOne({ where: { id } }); if (!existingCustomer) { throw new Error(`客户ID ${id} 不存在`); } // 删除客户 await this.customerModel.delete(id); results.updated++; results.processed++; } catch (error) { // 记录错误但不中断整个批量操作 results.errors.push({ identifier: String(id), error: error.message }); results.processed++; } } return results; } }