app.ts 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155
  1. import 'reflect-metadata'
  2. import { fastify, errorCodes } from 'fastify'
  3. import cors from '@fastify/cors'
  4. import jwt from '@fastify/jwt'
  5. import swagger from '@fastify/swagger'
  6. import swaggerUi from '@fastify/swagger-ui'
  7. import multipart from '@fastify/multipart'
  8. import fastifyEnv, { FastifyEnvOptions } from '@fastify/env'
  9. import { schema } from './config/env'
  10. import { createDataSource } from './config/database'
  11. import userRoutes from './routes/user.routes'
  12. import fileRoutes from './routes/file.routes'
  13. import sysConfigRoutes from './routes/sys-config.routes'
  14. import incomeRecordsRoutes from './routes/income-records.routes'
  15. import financeRoutes from './routes/finance.routes'
  16. import teamRoutes from './routes/team.routes'
  17. import teamMembersRoutes from './routes/team-members.routes'
  18. import promotionLinkRoutes from './routes/promotion-link.routes'
  19. import paymentRoutes from './routes/payment.routes'
  20. import memberRoutes from './routes/member.routes'
  21. import userShareRoutes from './routes/user-invite.routes'
  22. import teamDomainRoutes from './routes/team-domain.routes'
  23. import bannerRoutes from './routes/banner.routes'
  24. import { authenticate } from './middlewares/auth.middleware'
  25. import { createRedisClient, closeRedisClient } from './config/redis'
  26. import { BannerStatisticsScheduler } from './scheduler/banner-statistics.scheduler'
  27. const options: FastifyEnvOptions = {
  28. schema: schema,
  29. dotenv: {
  30. debug: false
  31. }
  32. }
  33. export const createApp = async () => {
  34. const app = fastify({
  35. disableRequestLogging: true,
  36. trustProxy: true,
  37. logger: {
  38. level: process.env.NODE_ENV === 'development' ? 'debug' : 'info',
  39. transport: {
  40. target: 'pino-pretty',
  41. options: {
  42. translateTime: 'yy/mm/dd HH:MM:ss Z',
  43. ignore: 'pid,hostname'
  44. }
  45. }
  46. }
  47. })
  48. await app.register(fastifyEnv, options)
  49. app.register(cors, {
  50. origin: true,
  51. methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS']
  52. })
  53. app.register(jwt, {
  54. secret: app.config.JWT_SECRET,
  55. sign: {
  56. expiresIn: app.config.JWT_EXPIRES_IN
  57. }
  58. })
  59. app.register(multipart, {
  60. limits: {
  61. fileSize: 200 * 1024 * 1024
  62. }
  63. })
  64. app.register(swagger, {
  65. swagger: {
  66. info: {
  67. title: 'Wuma API',
  68. description: 'Wuma API documentation',
  69. version: '1.0.0'
  70. },
  71. host: 'localhost',
  72. schemes: ['http'],
  73. consumes: ['application/json'],
  74. produces: ['application/json']
  75. }
  76. })
  77. if (app.config.NODE_ENV === 'development') {
  78. app.register(swaggerUi, {
  79. routePrefix: '/documentation'
  80. })
  81. }
  82. app.register(userRoutes, { prefix: '/api/users' })
  83. app.register(fileRoutes, { prefix: '/api/files' })
  84. app.register(sysConfigRoutes, { prefix: '/api/config' })
  85. app.register(incomeRecordsRoutes, { prefix: '/api/income' })
  86. app.register(financeRoutes, { prefix: '/api/finance' })
  87. app.register(teamRoutes, { prefix: '/api/teams' })
  88. app.register(teamMembersRoutes, { prefix: '/api/team-members' })
  89. app.register(teamDomainRoutes, { prefix: '/api/team-domains' })
  90. app.register(promotionLinkRoutes, { prefix: '/api/links' })
  91. app.register(paymentRoutes, { prefix: '/api/payment' })
  92. app.register(memberRoutes, { prefix: '/api/member' })
  93. app.register(userShareRoutes, { prefix: '/api/user-share' })
  94. app.register(bannerRoutes, { prefix: '/api/banners' })
  95. // 添加 /account 路由重定向到用户资料
  96. app.get('/account', { onRequest: [authenticate] }, async (request, reply) => {
  97. return reply.redirect('/api/users/profile')
  98. })
  99. const dataSource = createDataSource(app)
  100. await dataSource.initialize()
  101. app.decorate('dataSource', dataSource)
  102. // 初始化Redis(如果未配置则使用默认值 localhost:6379)
  103. try {
  104. const redis = createRedisClient({
  105. REDIS_HOST: app.config.REDIS_HOST || 'localhost',
  106. REDIS_PORT: app.config.REDIS_PORT || 6379,
  107. REDIS_PASSWORD: app.config.REDIS_PASSWORD,
  108. REDIS_DB: app.config.REDIS_DB || 0
  109. })
  110. app.decorate('redis', redis)
  111. if (app.config.REDIS_HOST) {
  112. app.log.info(`Redis connected successfully to ${app.config.REDIS_HOST}:${app.config.REDIS_PORT || 6379}`)
  113. } else {
  114. app.log.info('Redis connected successfully using default configuration (localhost:6379)')
  115. }
  116. } catch (error) {
  117. app.log.warn(`Redis connection failed, continuing without Redis: ${error instanceof Error ? error.message : String(error)}`)
  118. }
  119. // 初始化定时任务(需要Redis支持)
  120. let bannerStatisticsScheduler: BannerStatisticsScheduler | null = null
  121. if (app.redis) {
  122. try {
  123. bannerStatisticsScheduler = new BannerStatisticsScheduler(app)
  124. bannerStatisticsScheduler.start()
  125. app.decorate('bannerStatisticsScheduler', bannerStatisticsScheduler)
  126. } catch (error) {
  127. app.log.warn(`定时任务初始化失败: ${error instanceof Error ? error.message : String(error)}`)
  128. }
  129. }
  130. app.addHook('onClose', async () => {
  131. // 停止定时任务
  132. if (bannerStatisticsScheduler) {
  133. bannerStatisticsScheduler.stop()
  134. }
  135. await closeRedisClient()
  136. await dataSource.destroy()
  137. process.exit(0)
  138. })
  139. return app
  140. }