landing-domain-pool.service.ts 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386
  1. import { Repository, Like, In } from 'typeorm'
  2. import { FastifyInstance } from 'fastify'
  3. import { LandingDomainPool, DomainType } from '../entities/landing-domain-pool.entity'
  4. import { TeamDomain } from '../entities/team-domain.entity'
  5. import { TeamMembers } from '../entities/team-members.entity'
  6. import { PaginationResponse } from '../dto/common.dto'
  7. import { CreateLandingDomainPoolBody, UpdateLandingDomainPoolBody, ListLandingDomainPoolQuery } from '../dto/landing-domain-pool.dto'
  8. import { TeamService } from './team.service'
  9. import { UserService } from './user.service'
  10. export class LandingDomainPoolService {
  11. private landingDomainPoolRepository: Repository<LandingDomainPool>
  12. private teamDomainRepository: Repository<TeamDomain>
  13. private teamMembersRepository: Repository<TeamMembers>
  14. private teamService: TeamService
  15. private userService: UserService
  16. constructor(app: FastifyInstance) {
  17. this.landingDomainPoolRepository = app.dataSource.getRepository(LandingDomainPool)
  18. this.teamDomainRepository = app.dataSource.getRepository(TeamDomain)
  19. this.teamMembersRepository = app.dataSource.getRepository(TeamMembers)
  20. this.teamService = new TeamService(app)
  21. this.userService = new UserService(app)
  22. }
  23. async create(data: CreateLandingDomainPoolBody): Promise<LandingDomainPool> {
  24. await this.teamService.findById(data.teamId)
  25. // 如果提供了 userId,可能是 TeamMembers 的 id,需要转换为 user 的 id
  26. let actualUserId = data.userId
  27. if (data.userId !== undefined && data.userId !== null) {
  28. // 先尝试作为 TeamMembers 的 id 查找
  29. const teamMember = await this.teamMembersRepository.findOne({
  30. where: { id: data.userId }
  31. })
  32. if (teamMember) {
  33. // 如果找到 TeamMembers,使用其 userId(即 user 的 id)
  34. actualUserId = teamMember.userId
  35. }
  36. // 如果找不到 TeamMembers,则假设传入的已经是 user 的 id,验证用户是否存在
  37. if (actualUserId !== null && actualUserId !== undefined) {
  38. await this.userService.findById(actualUserId)
  39. }
  40. }
  41. const existingDomain = await this.landingDomainPoolRepository.findOne({
  42. where: { domain: data.domain }
  43. })
  44. if (existingDomain) {
  45. throw new Error('域名已存在')
  46. }
  47. const landingDomainPool = this.landingDomainPoolRepository.create({
  48. ...data,
  49. userId: actualUserId,
  50. domainType: data.domainType || DomainType.LANDING
  51. })
  52. return this.landingDomainPoolRepository.save(landingDomainPool)
  53. }
  54. async createBatch(
  55. data: CreateLandingDomainPoolBody
  56. ): Promise<{ success: LandingDomainPool[]; failed: { domain: string; error: string }[] }> {
  57. await this.teamService.findById(data.teamId)
  58. // 如果提供了 userId,可能是 TeamMembers 的 id,需要转换为 user 的 id
  59. let actualUserId = data.userId
  60. if (data.userId !== undefined && data.userId !== null) {
  61. // 先尝试作为 TeamMembers 的 id 查找
  62. const teamMember = await this.teamMembersRepository.findOne({
  63. where: { id: data.userId }
  64. })
  65. if (teamMember) {
  66. // 如果找到 TeamMembers,使用其 userId(即 user 的 id)
  67. actualUserId = teamMember.userId
  68. }
  69. // 如果找不到 TeamMembers,则假设传入的已经是 user 的 id,保持不变
  70. }
  71. // 解析域名字符串,支持逗号和换行分隔
  72. const domains = this.parseDomains(data.domain)
  73. if (domains.length === 0) {
  74. throw new Error('没有有效的域名')
  75. }
  76. const success: LandingDomainPool[] = []
  77. const failed: { domain: string; error: string }[] = []
  78. // 检查已存在的域名
  79. const existingDomains = await this.landingDomainPoolRepository.find({
  80. where: { domain: In(domains) }
  81. })
  82. const existingDomainSet = new Set(existingDomains.map(d => d.domain))
  83. // 批量创建域名
  84. const domainsToCreate = domains.filter(domain => !existingDomainSet.has(domain))
  85. if (domainsToCreate.length > 0) {
  86. const landingDomainPools = domainsToCreate.map(domain =>
  87. this.landingDomainPoolRepository.create({
  88. teamId: data.teamId,
  89. domain: domain.trim(),
  90. description: data.description,
  91. userId: actualUserId,
  92. domainType: data.domainType || DomainType.LANDING
  93. })
  94. )
  95. const savedDomains = await this.landingDomainPoolRepository.save(landingDomainPools)
  96. success.push(...savedDomains)
  97. }
  98. // 记录失败的域名(已存在的域名)
  99. domains.forEach(domain => {
  100. if (existingDomainSet.has(domain)) {
  101. failed.push({ domain, error: '域名已存在' })
  102. }
  103. })
  104. return { success, failed }
  105. }
  106. private parseDomains(domainString: string): string[] {
  107. // 支持中英文逗号、分号和换行分隔,去除空白字符
  108. return domainString
  109. .split(/[,;\n\r]+/)
  110. .map(domain => domain.trim())
  111. .filter(domain => domain.length > 0)
  112. }
  113. async findById(id: number): Promise<LandingDomainPool> {
  114. return this.landingDomainPoolRepository.findOneOrFail({ where: { id } })
  115. }
  116. async findAll(query: ListLandingDomainPoolQuery): Promise<PaginationResponse<LandingDomainPool>> {
  117. const { page, size, id, teamId, domain, userId, domainType } = query
  118. const where: any = {}
  119. if (id) {
  120. where.id = id
  121. }
  122. if (teamId) {
  123. where.teamId = teamId
  124. }
  125. if (domain) {
  126. where.domain = Like(`%${domain}%`)
  127. }
  128. if (userId !== undefined && userId !== null) {
  129. where.userId = userId
  130. }
  131. if (domainType !== undefined) {
  132. where.domainType = domainType
  133. }
  134. const [landingDomainPools, total] = await this.landingDomainPoolRepository.findAndCount({
  135. where,
  136. skip: (Number(page) || 0) * (Number(size) || 20),
  137. take: Number(size) || 20,
  138. order: { createdAt: 'DESC' }
  139. })
  140. return {
  141. content: landingDomainPools,
  142. metadata: {
  143. total: Number(total),
  144. page: Number(page) || 0,
  145. size: Number(size) || 20
  146. }
  147. }
  148. }
  149. async update(data: UpdateLandingDomainPoolBody): Promise<LandingDomainPool> {
  150. const { id, ...updateData } = data
  151. // 先获取现有记录
  152. const existingDomain = await this.findById(id)
  153. // 如果要更新域名,检查是否重复
  154. if (updateData.domain && updateData.domain !== existingDomain.domain) {
  155. const duplicateDomain = await this.landingDomainPoolRepository.findOne({
  156. where: { domain: updateData.domain }
  157. })
  158. if (duplicateDomain) {
  159. throw new Error('域名已存在')
  160. }
  161. }
  162. // 如果要更新团队ID,验证团队是否存在
  163. if (updateData.teamId) {
  164. await this.teamService.findById(updateData.teamId)
  165. }
  166. // 如果要更新 userId,可能是 TeamMembers 的 id,需要转换为 user 的 id
  167. let actualUserId = updateData.userId
  168. if (updateData.userId !== undefined && updateData.userId !== null) {
  169. // 先尝试作为 TeamMembers 的 id 查找
  170. const teamMember = await this.teamMembersRepository.findOne({
  171. where: { id: updateData.userId }
  172. })
  173. if (teamMember) {
  174. // 如果找到 TeamMembers,使用其 userId(即 user 的 id)
  175. actualUserId = teamMember.userId
  176. }
  177. // 如果找不到 TeamMembers,则假设传入的已经是 user 的 id,保持不变
  178. // 验证用户是否存在
  179. if (actualUserId !== null && actualUserId !== undefined) {
  180. await this.userService.findById(actualUserId)
  181. }
  182. }
  183. // 构建符合 TypeORM 要求的更新对象
  184. const updateEntity: Partial<LandingDomainPool> = {}
  185. if (updateData.domain !== undefined) {
  186. updateEntity.domain = updateData.domain
  187. }
  188. if (updateData.description !== undefined) {
  189. updateEntity.description = updateData.description
  190. }
  191. if (updateData.teamId !== undefined) {
  192. updateEntity.teamId = updateData.teamId
  193. }
  194. if (updateData.userId !== undefined) {
  195. updateEntity.userId = actualUserId
  196. }
  197. if (updateData.domainType !== undefined) {
  198. updateEntity.domainType = updateData.domainType
  199. }
  200. await this.landingDomainPoolRepository.update(id, updateEntity)
  201. return this.findById(id)
  202. }
  203. async delete(id: number): Promise<void> {
  204. await this.landingDomainPoolRepository.delete(id)
  205. }
  206. async findByTeamId(teamId: number): Promise<LandingDomainPool[]> {
  207. return await this.landingDomainPoolRepository.find({
  208. where: { teamId },
  209. order: { createdAt: 'DESC' }
  210. })
  211. }
  212. async findAllGroupedByTeam(query?: ListLandingDomainPoolQuery): Promise<Record<number, LandingDomainPool[]>> {
  213. const { id, teamId, domain, userId, domainType } = query || {}
  214. const where: any = {}
  215. if (id) {
  216. where.id = id
  217. }
  218. if (teamId) {
  219. where.teamId = teamId
  220. }
  221. if (domain) {
  222. where.domain = Like(`%${domain}%`)
  223. }
  224. if (userId !== undefined && userId !== null) {
  225. where.userId = userId
  226. }
  227. if (domainType !== undefined) {
  228. where.domainType = domainType
  229. }
  230. const landingDomainPools = await this.landingDomainPoolRepository.find({
  231. where,
  232. order: { teamId: 'ASC', createdAt: 'DESC' }
  233. })
  234. // 按 teamId 分组
  235. const groupedData: Record<number, LandingDomainPool[]> = {}
  236. landingDomainPools.forEach(domain => {
  237. if (!groupedData[domain.teamId]) {
  238. groupedData[domain.teamId] = []
  239. }
  240. groupedData[domain.teamId].push(domain)
  241. })
  242. return groupedData
  243. }
  244. /**
  245. * 根据域名(team-domain)获取落地域名池列表
  246. * 如果域名绑定到个人(teamMemberId不为空),返回该个人的所有落地域名
  247. * 如果域名绑定到团队(teamMemberId为空),返回该团队的所有落地域名
  248. * @param domain team-domain中的域名
  249. * @returns 落地域名池列表
  250. */
  251. async findByTeamDomain(domain: string): Promise<LandingDomainPool[]> {
  252. // 根据域名查找team-domain记录
  253. const teamDomain = await this.teamDomainRepository.findOne({
  254. where: { domain }
  255. })
  256. if (!teamDomain) {
  257. throw new Error('域名不存在')
  258. }
  259. // 如果域名绑定到个人(teamMemberId不为空)
  260. if (teamDomain.teamMemberId !== null && teamDomain.teamMemberId !== undefined) {
  261. // 查找团队成员信息
  262. const teamMember = await this.teamMembersRepository.findOne({
  263. where: { id: teamDomain.teamMemberId }
  264. })
  265. if (!teamMember) {
  266. throw new Error('团队成员不存在')
  267. }
  268. // 返回该个人(userId)的所有落地域名
  269. return await this.landingDomainPoolRepository.find({
  270. where: { userId: teamMember.userId },
  271. order: { createdAt: 'DESC' }
  272. })
  273. } else {
  274. // 如果域名绑定到团队(teamMemberId为空),返回该团队的所有落地域名
  275. return await this.findByTeamId(teamDomain.teamId)
  276. }
  277. }
  278. /**
  279. * 根据当前落地域名获取留存域名
  280. * 根据传入的落地域名,查找对应的落地域名池记录,然后返回相同团队和用户的留存域名
  281. * 如果没有找到留存域名,则返回落地域名本身
  282. * 如果没有找到落地域名,则返回输入的域名(作为临时落地域名对象)
  283. * @param domain 落地域名
  284. * @returns 留存域名列表,如果没有留存域名则返回落地域名,如果没有落地域名则返回输入的域名
  285. */
  286. async getRetentionDomainsByLandingDomain(domain: string): Promise<LandingDomainPool[]> {
  287. // 根据域名查找落地域名池记录(domainType = 'landing')
  288. const landingDomain = await this.landingDomainPoolRepository.findOne({
  289. where: { domain, domainType: DomainType.LANDING }
  290. })
  291. // 如果没有找到落地域名,返回输入的域名(作为临时落地域名对象)
  292. if (!landingDomain) {
  293. const fallbackDomain = this.landingDomainPoolRepository.create({
  294. domain: domain,
  295. domainType: DomainType.LANDING,
  296. teamId: 0,
  297. userId: undefined,
  298. description: undefined,
  299. createdAt: new Date(),
  300. updatedAt: new Date()
  301. })
  302. return [fallbackDomain]
  303. }
  304. // 构建查询条件:相同的 teamId 和 userId,且 domainType = 'retention'
  305. const where: any = {
  306. teamId: landingDomain.teamId,
  307. domainType: DomainType.RETENTION
  308. }
  309. // 如果落地域名绑定了用户,则只返回该用户的留存域名
  310. // 如果没有绑定用户,则返回该团队的所有留存域名
  311. if (landingDomain.userId !== null && landingDomain.userId !== undefined) {
  312. where.userId = landingDomain.userId
  313. }
  314. // 查询留存域名列表
  315. const retentionDomains = await this.landingDomainPoolRepository.find({
  316. where,
  317. order: { createdAt: 'DESC' }
  318. })
  319. // 如果没有找到留存域名,返回落地域名本身
  320. if (retentionDomains.length === 0) {
  321. return [landingDomain]
  322. }
  323. // 返回留存域名列表
  324. return retentionDomains
  325. }
  326. }