| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386 |
- import { Repository, Like, In } from 'typeorm'
- import { FastifyInstance } from 'fastify'
- import { LandingDomainPool, DomainType } from '../entities/landing-domain-pool.entity'
- import { TeamDomain } from '../entities/team-domain.entity'
- import { TeamMembers } from '../entities/team-members.entity'
- import { PaginationResponse } from '../dto/common.dto'
- import { CreateLandingDomainPoolBody, UpdateLandingDomainPoolBody, ListLandingDomainPoolQuery } from '../dto/landing-domain-pool.dto'
- import { TeamService } from './team.service'
- import { UserService } from './user.service'
- export class LandingDomainPoolService {
- private landingDomainPoolRepository: Repository<LandingDomainPool>
- private teamDomainRepository: Repository<TeamDomain>
- private teamMembersRepository: Repository<TeamMembers>
- private teamService: TeamService
- private userService: UserService
- constructor(app: FastifyInstance) {
- this.landingDomainPoolRepository = app.dataSource.getRepository(LandingDomainPool)
- this.teamDomainRepository = app.dataSource.getRepository(TeamDomain)
- this.teamMembersRepository = app.dataSource.getRepository(TeamMembers)
- this.teamService = new TeamService(app)
- this.userService = new UserService(app)
- }
- async create(data: CreateLandingDomainPoolBody): Promise<LandingDomainPool> {
- await this.teamService.findById(data.teamId)
- // 如果提供了 userId,可能是 TeamMembers 的 id,需要转换为 user 的 id
- let actualUserId = data.userId
- if (data.userId !== undefined && data.userId !== null) {
- // 先尝试作为 TeamMembers 的 id 查找
- const teamMember = await this.teamMembersRepository.findOne({
- where: { id: data.userId }
- })
- if (teamMember) {
- // 如果找到 TeamMembers,使用其 userId(即 user 的 id)
- actualUserId = teamMember.userId
- }
- // 如果找不到 TeamMembers,则假设传入的已经是 user 的 id,验证用户是否存在
- if (actualUserId !== null && actualUserId !== undefined) {
- await this.userService.findById(actualUserId)
- }
- }
- const existingDomain = await this.landingDomainPoolRepository.findOne({
- where: { domain: data.domain }
- })
- if (existingDomain) {
- throw new Error('域名已存在')
- }
- const landingDomainPool = this.landingDomainPoolRepository.create({
- ...data,
- userId: actualUserId,
- domainType: data.domainType || DomainType.LANDING
- })
- return this.landingDomainPoolRepository.save(landingDomainPool)
- }
- async createBatch(
- data: CreateLandingDomainPoolBody
- ): Promise<{ success: LandingDomainPool[]; failed: { domain: string; error: string }[] }> {
- await this.teamService.findById(data.teamId)
- // 如果提供了 userId,可能是 TeamMembers 的 id,需要转换为 user 的 id
- let actualUserId = data.userId
- if (data.userId !== undefined && data.userId !== null) {
- // 先尝试作为 TeamMembers 的 id 查找
- const teamMember = await this.teamMembersRepository.findOne({
- where: { id: data.userId }
- })
- if (teamMember) {
- // 如果找到 TeamMembers,使用其 userId(即 user 的 id)
- actualUserId = teamMember.userId
- }
- // 如果找不到 TeamMembers,则假设传入的已经是 user 的 id,保持不变
- }
- // 解析域名字符串,支持逗号和换行分隔
- const domains = this.parseDomains(data.domain)
- if (domains.length === 0) {
- throw new Error('没有有效的域名')
- }
- const success: LandingDomainPool[] = []
- const failed: { domain: string; error: string }[] = []
-
- // 检查已存在的域名
- const existingDomains = await this.landingDomainPoolRepository.find({
- where: { domain: In(domains) }
- })
- const existingDomainSet = new Set(existingDomains.map(d => d.domain))
- // 批量创建域名
- const domainsToCreate = domains.filter(domain => !existingDomainSet.has(domain))
- if (domainsToCreate.length > 0) {
- const landingDomainPools = domainsToCreate.map(domain =>
- this.landingDomainPoolRepository.create({
- teamId: data.teamId,
- domain: domain.trim(),
- description: data.description,
- userId: actualUserId,
- domainType: data.domainType || DomainType.LANDING
- })
- )
- const savedDomains = await this.landingDomainPoolRepository.save(landingDomainPools)
- success.push(...savedDomains)
- }
- // 记录失败的域名(已存在的域名)
- domains.forEach(domain => {
- if (existingDomainSet.has(domain)) {
- failed.push({ domain, error: '域名已存在' })
- }
- })
- return { success, failed }
- }
- private parseDomains(domainString: string): string[] {
- // 支持中英文逗号、分号和换行分隔,去除空白字符
- return domainString
- .split(/[,;\n\r]+/)
- .map(domain => domain.trim())
- .filter(domain => domain.length > 0)
- }
- async findById(id: number): Promise<LandingDomainPool> {
- return this.landingDomainPoolRepository.findOneOrFail({ where: { id } })
- }
- async findAll(query: ListLandingDomainPoolQuery): Promise<PaginationResponse<LandingDomainPool>> {
- const { page, size, id, teamId, domain, userId, domainType } = query
- const where: any = {}
- if (id) {
- where.id = id
- }
- if (teamId) {
- where.teamId = teamId
- }
- if (domain) {
- where.domain = Like(`%${domain}%`)
- }
- if (userId !== undefined && userId !== null) {
- where.userId = userId
- }
- if (domainType !== undefined) {
- where.domainType = domainType
- }
- const [landingDomainPools, total] = await this.landingDomainPoolRepository.findAndCount({
- where,
- skip: (Number(page) || 0) * (Number(size) || 20),
- take: Number(size) || 20,
- order: { createdAt: 'DESC' }
- })
- return {
- content: landingDomainPools,
- metadata: {
- total: Number(total),
- page: Number(page) || 0,
- size: Number(size) || 20
- }
- }
- }
- async update(data: UpdateLandingDomainPoolBody): Promise<LandingDomainPool> {
- const { id, ...updateData } = data
- // 先获取现有记录
- const existingDomain = await this.findById(id)
- // 如果要更新域名,检查是否重复
- if (updateData.domain && updateData.domain !== existingDomain.domain) {
- const duplicateDomain = await this.landingDomainPoolRepository.findOne({
- where: { domain: updateData.domain }
- })
- if (duplicateDomain) {
- throw new Error('域名已存在')
- }
- }
- // 如果要更新团队ID,验证团队是否存在
- if (updateData.teamId) {
- await this.teamService.findById(updateData.teamId)
- }
- // 如果要更新 userId,可能是 TeamMembers 的 id,需要转换为 user 的 id
- let actualUserId = updateData.userId
- if (updateData.userId !== undefined && updateData.userId !== null) {
- // 先尝试作为 TeamMembers 的 id 查找
- const teamMember = await this.teamMembersRepository.findOne({
- where: { id: updateData.userId }
- })
- if (teamMember) {
- // 如果找到 TeamMembers,使用其 userId(即 user 的 id)
- actualUserId = teamMember.userId
- }
- // 如果找不到 TeamMembers,则假设传入的已经是 user 的 id,保持不变
- // 验证用户是否存在
- if (actualUserId !== null && actualUserId !== undefined) {
- await this.userService.findById(actualUserId)
- }
- }
- // 构建符合 TypeORM 要求的更新对象
- const updateEntity: Partial<LandingDomainPool> = {}
- if (updateData.domain !== undefined) {
- updateEntity.domain = updateData.domain
- }
- if (updateData.description !== undefined) {
- updateEntity.description = updateData.description
- }
- if (updateData.teamId !== undefined) {
- updateEntity.teamId = updateData.teamId
- }
- if (updateData.userId !== undefined) {
- updateEntity.userId = actualUserId
- }
- if (updateData.domainType !== undefined) {
- updateEntity.domainType = updateData.domainType
- }
- await this.landingDomainPoolRepository.update(id, updateEntity)
- return this.findById(id)
- }
- async delete(id: number): Promise<void> {
- await this.landingDomainPoolRepository.delete(id)
- }
- async findByTeamId(teamId: number): Promise<LandingDomainPool[]> {
- return await this.landingDomainPoolRepository.find({
- where: { teamId },
- order: { createdAt: 'DESC' }
- })
- }
- async findAllGroupedByTeam(query?: ListLandingDomainPoolQuery): Promise<Record<number, LandingDomainPool[]>> {
- const { id, teamId, domain, userId, domainType } = query || {}
- const where: any = {}
- if (id) {
- where.id = id
- }
- if (teamId) {
- where.teamId = teamId
- }
- if (domain) {
- where.domain = Like(`%${domain}%`)
- }
- if (userId !== undefined && userId !== null) {
- where.userId = userId
- }
- if (domainType !== undefined) {
- where.domainType = domainType
- }
- const landingDomainPools = await this.landingDomainPoolRepository.find({
- where,
- order: { teamId: 'ASC', createdAt: 'DESC' }
- })
- // 按 teamId 分组
- const groupedData: Record<number, LandingDomainPool[]> = {}
- landingDomainPools.forEach(domain => {
- if (!groupedData[domain.teamId]) {
- groupedData[domain.teamId] = []
- }
- groupedData[domain.teamId].push(domain)
- })
- return groupedData
- }
- /**
- * 根据域名(team-domain)获取落地域名池列表
- * 如果域名绑定到个人(teamMemberId不为空),返回该个人的所有落地域名
- * 如果域名绑定到团队(teamMemberId为空),返回该团队的所有落地域名
- * @param domain team-domain中的域名
- * @returns 落地域名池列表
- */
- async findByTeamDomain(domain: string): Promise<LandingDomainPool[]> {
- // 根据域名查找team-domain记录
- const teamDomain = await this.teamDomainRepository.findOne({
- where: { domain }
- })
- if (!teamDomain) {
- throw new Error('域名不存在')
- }
- // 如果域名绑定到个人(teamMemberId不为空)
- if (teamDomain.teamMemberId !== null && teamDomain.teamMemberId !== undefined) {
- // 查找团队成员信息
- const teamMember = await this.teamMembersRepository.findOne({
- where: { id: teamDomain.teamMemberId }
- })
- if (!teamMember) {
- throw new Error('团队成员不存在')
- }
- // 返回该个人(userId)的所有落地域名
- return await this.landingDomainPoolRepository.find({
- where: { userId: teamMember.userId },
- order: { createdAt: 'DESC' }
- })
- } else {
- // 如果域名绑定到团队(teamMemberId为空),返回该团队的所有落地域名
- return await this.findByTeamId(teamDomain.teamId)
- }
- }
- /**
- * 根据当前落地域名获取留存域名
- * 根据传入的落地域名,查找对应的落地域名池记录,然后返回相同团队和用户的留存域名
- * 如果没有找到留存域名,则返回落地域名本身
- * 如果没有找到落地域名,则返回输入的域名(作为临时落地域名对象)
- * @param domain 落地域名
- * @returns 留存域名列表,如果没有留存域名则返回落地域名,如果没有落地域名则返回输入的域名
- */
- async getRetentionDomainsByLandingDomain(domain: string): Promise<LandingDomainPool[]> {
- // 根据域名查找落地域名池记录(domainType = 'landing')
- const landingDomain = await this.landingDomainPoolRepository.findOne({
- where: { domain, domainType: DomainType.LANDING }
- })
- // 如果没有找到落地域名,返回输入的域名(作为临时落地域名对象)
- if (!landingDomain) {
- const fallbackDomain = this.landingDomainPoolRepository.create({
- domain: domain,
- domainType: DomainType.LANDING,
- teamId: 0,
- userId: undefined,
- description: undefined,
- createdAt: new Date(),
- updatedAt: new Date()
- })
- return [fallbackDomain]
- }
- // 构建查询条件:相同的 teamId 和 userId,且 domainType = 'retention'
- const where: any = {
- teamId: landingDomain.teamId,
- domainType: DomainType.RETENTION
- }
- // 如果落地域名绑定了用户,则只返回该用户的留存域名
- // 如果没有绑定用户,则返回该团队的所有留存域名
- if (landingDomain.userId !== null && landingDomain.userId !== undefined) {
- where.userId = landingDomain.userId
- }
- // 查询留存域名列表
- const retentionDomains = await this.landingDomainPoolRepository.find({
- where,
- order: { createdAt: 'DESC' }
- })
- // 如果没有找到留存域名,返回落地域名本身
- if (retentionDomains.length === 0) {
- return [landingDomain]
- }
- // 返回留存域名列表
- return retentionDomains
- }
- }
|