create-skweb 3.1.1 → 3.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/.rollup.cache/Users/stayknight/projects/skweb-mono/tools/create-skweb/dist/index.js +14 -1
  2. package/.rollup.cache/Users/stayknight/projects/skweb-mono/tools/create-skweb/tsconfig.tsbuildinfo +1 -1
  3. package/.turbo/turbo-build.log +16 -17
  4. package/.turbo/turbo-lint.log +11 -12
  5. package/.turbo/turbo-test.log +66 -1
  6. package/CHANGELOG.md +49 -0
  7. package/dist/cli.js +14 -1
  8. package/dist/index.js +14 -1
  9. package/package.json +1 -1
  10. package/src/index.ts +21 -1
  11. package/templates/cjs/.env.example +5 -9
  12. package/templates/cjs/ecosystem.config.js +2 -2
  13. package/templates/cjs/package.json +5 -5
  14. package/templates/cjs/src/controllers/user.js +2 -2
  15. package/templates/cjs/src/cron-app.js +40 -0
  16. package/templates/cjs/src/cron-framework.js +44 -0
  17. package/templates/cjs/src/swagger.js +13 -18
  18. package/templates/cjs/src/utils/config.js +33 -13
  19. package/templates/cjs/src/{app.js → web-app.js} +41 -53
  20. package/templates/cjs/src/web-framework.js +23 -0
  21. package/templates/mjs/.env.example +5 -9
  22. package/templates/mjs/ecosystem.config.js +2 -2
  23. package/templates/mjs/package.json +5 -5
  24. package/templates/mjs/src/controllers/user.js +2 -2
  25. package/templates/mjs/src/cron-app.js +45 -0
  26. package/templates/mjs/src/cron-framework.js +44 -0
  27. package/templates/mjs/src/swagger.js +13 -18
  28. package/templates/mjs/src/utils/config.js +29 -13
  29. package/templates/mjs/src/{app.js → web-app.js} +40 -56
  30. package/templates/mjs/src/web-framework.js +23 -0
  31. package/templates/ts/.env.example +5 -9
  32. package/templates/ts/ecosystem.config.js +2 -2
  33. package/templates/ts/package.json +5 -5
  34. package/templates/ts/src/controllers/user.ts +2 -2
  35. package/templates/ts/src/cron-app.ts +45 -0
  36. package/templates/ts/src/cron-framework.ts +44 -0
  37. package/templates/ts/src/swagger.ts +13 -19
  38. package/templates/ts/src/utils/config.ts +29 -13
  39. package/templates/ts/src/{app.ts → web-app.ts} +40 -63
  40. package/templates/ts/src/web-framework.ts +23 -0
  41. package/tsconfig.tsbuildinfo +1 -1
  42. package/templates/cjs/src/cronWorker.js +0 -80
  43. package/templates/cjs/src/index.js +0 -7
  44. package/templates/cjs/src/services/database.js +0 -15
  45. package/templates/mjs/src/cronWorker.js +0 -86
  46. package/templates/mjs/src/index.js +0 -8
  47. package/templates/mjs/src/services/database.js +0 -16
  48. package/templates/ts/src/cronWorker.ts +0 -85
  49. package/templates/ts/src/index.ts +0 -8
  50. package/templates/ts/src/services/database.ts +0 -16
@@ -1,42 +1,21 @@
1
+ // @ts-nocheck
1
2
  require('dotenv/config')
2
3
 
3
4
  const path = require('path')
4
- // skweb-express / skweb-express-logger 的 CJS 产物形态是
5
- // `{ default: fn, errorHandler, ... }`。直接 `require('skweb-express')` 拿到的是
6
- // 命名空间对象,`express()` 会 TypeError。这里走 .default。
7
5
  const express = require('skweb-express').default
8
6
  const expressLogger = require('skweb-express-logger').default
9
7
  const expressErrorHandler = require('skweb-express-logger').errorHandler
10
8
  const swaggerUi = require('swagger-ui-express')
11
- const { WebFramework } = require('skweb-framework')
9
+ const { framework } = require('./web-framework.js')
12
10
  const { logger } = require('./utils/logger.js')
13
- const { checkRequiredEnv } = require('./utils/config.js')
11
+ const { checkRequiredEnv{{#if_eq auth "static"}}, checkJwtSecret{{/if_eq}}{{#if_eq auth "jwks"}}, checkOIDCConfig{{/if_eq}} } = require('./utils/config.js')
14
12
  const { buildOpenApiSpec } = require('./swagger.js')
15
13
 
14
+ {{#if_eq auth "static"}}
16
15
  /**
17
- * 构建 JWT 配置,支持两种模式:
18
- * - 'static' (默认):使用共享密钥(HS256/HS384/HS512),适用于自签 token
19
- * - 'jwks':通过 JWKS 端点动态获取公钥(RS256 等),适用于 Keycloak/Auth0 等 IdP
16
+ * JWT 配置(static 模式,HS256 共享密钥)。
20
17
  */
21
18
  function buildJwtOptions() {
22
- const provider = process.env.AUTH_PROVIDER || 'static'
23
-
24
- if (provider === 'jwks') {
25
- const issuer = process.env.OIDC_ISSUER
26
- const jwksUri = process.env.OIDC_JWKS_URI
27
- const audience = process.env.OIDC_AUDIENCE
28
- if (!issuer && !jwksUri) {
29
- throw new Error('AUTH_PROVIDER=jwks requires OIDC_ISSUER or OIDC_JWKS_URI')
30
- }
31
- return {
32
- provider: 'jwks',
33
- algorithms: ['RS256'],
34
- jwks: { jwksUri, issuer, audience },
35
- verify: { issuer, audience },
36
- requestProperty: 'resolvedToken'
37
- }
38
- }
39
-
40
19
  return {
41
20
  provider: 'static',
42
21
  secret: process.env.JWT_SECRET || 'please-change-this-in-production',
@@ -45,6 +24,26 @@ function buildJwtOptions() {
45
24
  requestProperty: 'resolvedToken'
46
25
  }
47
26
  }
27
+ {{else}}
28
+ /**
29
+ * JWT 配置(jwks 模式,通过 OIDC 兼容的 JWKS 端点验证 RS256 签名)。
30
+ */
31
+ function buildJwtOptions() {
32
+ const issuer = process.env.OIDC_ISSUER
33
+ const jwksUri = process.env.OIDC_JWKS_URI
34
+ const audience = process.env.OIDC_AUDIENCE
35
+ if (!issuer && !jwksUri) {
36
+ throw new Error('jwks mode requires OIDC_ISSUER or OIDC_JWKS_URI')
37
+ }
38
+ return {
39
+ provider: 'jwks',
40
+ algorithms: ['RS256'],
41
+ jwks: { jwksUri, issuer, audience },
42
+ verify: { issuer, audience },
43
+ requestProperty: 'resolvedToken'
44
+ }
45
+ }
46
+ {{/if_eq}}
48
47
 
49
48
  /**
50
49
  * Swagger UI 仅在 NODE_ENV 为 development / test,且 SWAGGER_ENABLED !== 'false' 时启用。
@@ -64,25 +63,18 @@ async function bootstrap() {
64
63
  logger.error('[app] Configuration validation failed, exiting...')
65
64
  process.exit(1)
66
65
  }
66
+ {{#if_eq auth "static"}}
67
+ if (!checkJwtSecret()) {
68
+ logger.error('[app] JWT configuration validation failed, exiting...')
69
+ process.exit(1)
70
+ }
71
+ {{else}}
72
+ if (!checkOIDCConfig()) {
73
+ logger.error('[app] OIDC configuration validation failed, exiting...')
74
+ process.exit(1)
75
+ }
76
+ {{/if_eq}}
67
77
 
68
- const framework = new WebFramework({
69
- db: {
70
- host: process.env.DB_HOST || '127.0.0.1',
71
- port: Number(process.env.DB_PORT) || 3306,
72
- username: process.env.DB_USER || 'root',
73
- password: process.env.DB_PASS || '',
74
- database: process.env.DB_NAME || 'test-project',
75
- dialect: process.env.DB_DIALECT || 'mysql',
76
- charset: process.env.DB_CHARSET || 'utf8mb4',
77
- underscored: true,
78
- logging: process.env.DEBUG_DB === 'true' ? (msg) => logger.debug(msg) : false
79
- },
80
- redis: {
81
- host: process.env.REDIS_HOST || '127.0.0.1',
82
- port: Number(process.env.REDIS_PORT) || 6379,
83
- password: process.env.REDIS_PASSWORD || undefined
84
- }
85
- })
86
78
  const app = framework.createApp()
87
79
 
88
80
  await framework.setup({
@@ -99,21 +91,19 @@ async function bootstrap() {
99
91
  app.setOkCode(0)
100
92
  app.setOkMessage('success')
101
93
 
102
- // JWT 配置(按 AUTH_PROVIDER 选择 static 或 jwks 模式)
103
94
  const jwtOptions = buildJwtOptions()
104
95
  app.setJWT('default', jwtOptions)
105
96
  app.setJwtHintHeader('JwtHint')
106
97
 
107
- if (jwtOptions.provider === 'jwks') {
108
- logger.info(`[app] JWT auth provider: jwks (issuer: ${jwtOptions.jwks.issuer || jwtOptions.jwks.jwksUri})`)
109
- } else {
110
- logger.info('[app] JWT auth provider: static (HS256)')
111
- }
98
+ {{#if_eq auth "static"}}
99
+ logger.info('[app] JWT auth provider: static (HS256)')
100
+ {{else}}
101
+ logger.info(`[app] JWT auth provider: jwks (issuer: ${jwtOptions.jwks?.issuer || jwtOptions.jwks?.jwksUri})`)
102
+ {{/if_eq}}
112
103
 
113
104
  app.use(expressLogger())
114
105
  app.use(express.static(path.join(__dirname, '..', 'public')))
115
106
 
116
- // 仅在 dev / test 环境挂载 Swagger UI(默认关闭,需显式 SWAGGER_ENABLED=true)
117
107
  if (shouldEnableSwagger()) {
118
108
  const openapiSpec = buildOpenApiSpec()
119
109
  app.get('/api-docs.json', (_req, res) => res.json(openapiSpec))
@@ -128,14 +118,12 @@ async function bootstrap() {
128
118
 
129
119
  await framework.loadControllers()
130
120
 
131
- // 错误日志中间件:挂在所有路由之后,记录业务错误并继续传递给默认错误处理。
132
121
  app.use(expressErrorHandler())
133
122
 
134
123
  framework.start(Number(process.env.PORT) || 3000)
135
124
 
136
125
  logger.info('[app] Initialization complete (web process)')
137
126
 
138
- // 未捕获异常:打印堆栈后直接退出。OS / 进程管理器负责回收资源。
139
127
  process.on('uncaughtException', (err) => {
140
128
  logger.error(`[app] uncaughtException: ${err.stack || err.message}`)
141
129
  process.exit(1)
@@ -0,0 +1,23 @@
1
+ // @ts-nocheck
2
+ const { WebFramework } = require('skweb-framework')
3
+
4
+ const framework = new WebFramework({
5
+ db: {
6
+ host: process.env.DB_HOST || '127.0.0.1',
7
+ port: Number(process.env.DB_PORT) || 3306,
8
+ username: process.env.DB_USER || 'root',
9
+ password: process.env.DB_PASS || '',
10
+ database: process.env.DB_NAME || 'test-project',
11
+ dialect: process.env.DB_DIALECT || 'mysql',
12
+ charset: process.env.DB_CHARSET || 'utf8mb4',
13
+ underscored: true,
14
+ logging: process.env.DEBUG_DB === 'true'
15
+ },
16
+ redis: {
17
+ host: process.env.REDIS_HOST || '127.0.0.1',
18
+ port: Number(process.env.REDIS_PORT) || 6379,
19
+ password: process.env.REDIS_PASSWORD || undefined
20
+ }
21
+ })
22
+
23
+ module.exports = { framework }
@@ -38,29 +38,25 @@ REDIS_DB=0
38
38
  # (Production is always disabled to avoid exposing internal API details.)
39
39
  SWAGGER_ENABLED=true
40
40
 
41
- # ---------- Auth Provider ----------
42
- # 'static' (default): use a shared secret (HS256)
43
- # 'jwks': verify via JWKS endpoint (Keycloak / Auth0 / generic OIDC)
44
- AUTH_PROVIDER={{auth}}
45
-
46
- # ---------- JWT (static mode) ----------
41
+ {{#if_eq auth "static"}}
42
+ # ---------- Auth: static (HS256 shared secret) ----------
47
43
  # [REQUIRED in production] Use a strong random string, e.g. `openssl rand -hex 32`
48
44
  JWT_SECRET=please-change-this-in-production
49
45
  JWT_EXPIRES_IN=7d
50
-
51
- # ---------- JWT (jwks mode, OIDC compatible) ----------
46
+ {{else}}
47
+ # ---------- Auth: jwks (OIDC compatible) ----------
52
48
  # Works with any OIDC-compliant IdP. Common issuer formats:
53
49
  # Keycloak: https://kc.example.com/realms/myrealm
54
50
  # Auth0: https://your-tenant.auth0.com/
55
51
  # Okta: https://your-domain.okta.com/oauth2/default
56
52
  # Authing: https://your-app.authing.cn/oauth2/default
57
- # Keycloak Realm: https://kc.example.com/auth/realms/myrealm
58
53
  # `audience` is the client id of this API in the IdP (recommended).
59
54
  # JWKS URI is auto-derived as ${OIDC_ISSUER}/.well-known/jwks.json
60
55
  # unless OIDC_JWKS_URI is set explicitly.
61
56
  OIDC_ISSUER=
62
57
  OIDC_AUDIENCE=
63
58
  OIDC_JWKS_URI=
59
+ {{/if_eq}}
64
60
 
65
61
  # ---------- Logging ----------
66
62
  # error | warn | info | http | verbose | debug | silly
@@ -2,7 +2,7 @@ module.exports = {
2
2
  apps: [
3
3
  {
4
4
  name: '{{name}}-web',
5
- script: 'src/index.js',
5
+ script: 'src/web-app.js',
6
6
  instances: 'max',
7
7
  exec_mode: 'cluster',
8
8
  env: {
@@ -16,7 +16,7 @@ module.exports = {
16
16
  },
17
17
  {
18
18
  name: '{{name}}-cron',
19
- script: 'src/cronWorker.js',
19
+ script: 'src/cron-app.js',
20
20
  instances: 1,
21
21
  exec_mode: 'fork',
22
22
  autorestart: true,
@@ -9,8 +9,8 @@
9
9
  "restart": "pm2 restart all",
10
10
  "delete": "pm2 delete all",
11
11
  "logs": "pm2 logs",
12
- "web:dev": "npx dotenvx run -- node --watch src/index.js",
13
- "cron:dev": "npx dotenvx run -- node --watch src/cronWorker.js",
12
+ "web:dev": "npx dotenvx run -- node --watch src/web-app.js",
13
+ "cron:dev": "npx dotenvx run -- node --watch src/cron-app.js",
14
14
  "seed": "npx dotenvx run -- node src/commands/seed.js",
15
15
  "migrate": "npx dotenvx run -- node src/commands/migrate.js",
16
16
  "build": "cp -r src dist",
@@ -22,14 +22,14 @@
22
22
  "dependencies": {
23
23
  "@dotenvx/dotenvx": "^1.75.1",
24
24
  "http-errors": "^2.0.1",
25
- "skweb-express": "^2.0.3",
25
+ "skweb-express": "^2.0.4",
26
26
  "skweb-express-jwt": "^3.0.0",
27
27
  "skweb-express-logger": "^2.0.0",
28
28
  "skweb-sequelize": "^2.0.0",
29
29
  "skweb-redis": "^2.0.0",
30
30
  "skweb-cron": "^2.0.0",
31
- "skweb-framework": "^2.1.0",
32
- "skweb-plugin": "^2.1.0",
31
+ "skweb-framework": "^2.1.1",
32
+ "skweb-plugin": "^2.1.1",
33
33
  "skweb-util": "^2.0.0",
34
34
  "winston": "^3.19.0",
35
35
  "sequelize": "^6.37.8",
@@ -1,7 +1,7 @@
1
1
  import createHttpError from 'http-errors'
2
- import { db } from '../services/database.js'
2
+ import { framework } from '../web-framework.js'
3
3
 
4
- const { SysUser } = db.sequelize.models
4
+ const { SysUser } = framework.models
5
5
 
6
6
  /**
7
7
  * @swagger
@@ -0,0 +1,45 @@
1
+ import 'dotenv/config'
2
+
3
+ import path from 'path'
4
+ import { fileURLToPath } from 'url'
5
+ import { framework } from './cron-framework.js'
6
+ import logger from './utils/logger.js'
7
+ import { checkRequiredEnv } from './utils/config.js'
8
+
9
+ const __filename = fileURLToPath(import.meta.url)
10
+ const __dirname = path.dirname(__filename)
11
+
12
+ async function startCronWorker() {
13
+ logger.info('[cron-worker] Starting cron worker process...')
14
+
15
+ if (!checkRequiredEnv()) {
16
+ logger.error('[cron-worker] Configuration validation failed, exiting...')
17
+ process.exit(1)
18
+ }
19
+
20
+ await framework.setup({
21
+ modelsDir: path.join(__dirname, './models'),
22
+ cronActionsDir: path.join(__dirname, './cronActions')
23
+ })
24
+ await framework.start()
25
+ logger.info('[cron-worker] Cron worker started')
26
+
27
+ process.on('uncaughtException', (err) => {
28
+ logger.error(`[cron-worker] uncaughtException: ${err.stack || err.message}`)
29
+ process.exit(1)
30
+ })
31
+ process.on('unhandledRejection', (reason) => {
32
+ logger.error(`[cron-worker] unhandledRejection: ${reason instanceof Error ? reason.stack : String(reason)}`)
33
+ process.exit(1)
34
+ })
35
+ }
36
+
37
+ const isMainModule = import.meta.url === `file://${process.argv[1]}`
38
+ if (isMainModule) {
39
+ startCronWorker().catch((err) => {
40
+ logger.error('[cron-worker] Fatal error:', err)
41
+ process.exit(1)
42
+ })
43
+ }
44
+
45
+ export { startCronWorker }
@@ -0,0 +1,44 @@
1
+ // @ts-nocheck
2
+ import { CronFramework } from 'skweb-framework'
3
+ import logger from './utils/logger.js'
4
+
5
+ const framework = new CronFramework({
6
+ db: {
7
+ host: process.env.DB_HOST || '127.0.0.1',
8
+ port: Number(process.env.DB_PORT) || 3306,
9
+ username: process.env.DB_USER || 'root',
10
+ password: process.env.DB_PASS || '',
11
+ database: process.env.DB_NAME || 'test-project',
12
+ dialect: process.env.DB_DIALECT || 'mysql',
13
+ charset: process.env.DB_CHARSET || 'utf8mb4',
14
+ underscored: true,
15
+ logging: process.env.DEBUG_DB === 'true'
16
+ },
17
+ redis: {
18
+ host: process.env.REDIS_HOST || '127.0.0.1',
19
+ port: Number(process.env.REDIS_PORT) || 6379,
20
+ password: process.env.REDIS_PASSWORD || undefined
21
+ },
22
+ channel: 'cron:task:cmd',
23
+ logger,
24
+ onSaveTask: async (taskData) => {
25
+ const SysCronTask = framework.db?.sequelize?.models.SysCronTask
26
+ if (SysCronTask) {
27
+ await SysCronTask.update(
28
+ { cache: taskData.cache, data: taskData.data },
29
+ { where: { id: taskData.taskId } }
30
+ )
31
+ }
32
+ },
33
+ loadTasks: async () => {
34
+ const SysCronTask = framework.db?.sequelize?.models.SysCronTask
35
+ if (!SysCronTask) return []
36
+ return (await SysCronTask.findAll()).map((t) => ({
37
+ id: String(t.id),
38
+ cronTime: t.cronTime,
39
+ method: t.method
40
+ }))
41
+ }
42
+ })
43
+
44
+ export { framework }
@@ -1,3 +1,4 @@
1
+ // @ts-nocheck
1
2
  import path from 'path'
2
3
  import { fileURLToPath } from 'url'
3
4
  import swaggerJSDoc from 'swagger-jsdoc'
@@ -5,22 +6,6 @@ import swaggerJSDoc from 'swagger-jsdoc'
5
6
  const __filename = fileURLToPath(import.meta.url)
6
7
  const __dirname = path.dirname(__filename)
7
8
 
8
- /**
9
- * 构建 OpenAPI 3.0 规范对象。
10
- *
11
- * 控制器中应使用 @swagger JSDoc 注释描述端点,例如:
12
- *
13
- * /**
14
- * * @swagger
15
- * * /api/users:
16
- * * get:
17
- * * summary: 获取用户列表
18
- * * tags: [Users]
19
- * * responses:
20
- * * 200:
21
- * * description: OK
22
- * *\/
23
- */
24
9
  export function buildOpenApiSpec() {
25
10
  return swaggerJSDoc({
26
11
  definition: {
@@ -36,12 +21,22 @@ export function buildOpenApiSpec() {
36
21
  ],
37
22
  components: {
38
23
  securitySchemes: {
24
+ {{#if_eq auth "static"}}
39
25
  bearerAuth: {
40
26
  type: 'http',
41
27
  scheme: 'bearer',
42
- bearerFormat: 'JWT',
43
- description: 'Paste a JWT token here (only needed when AUTH_PROVIDER=static)'
28
+ bearerFormat: 'JWT'
44
29
  }
30
+ {{else}}
31
+ oidcAuth: {
32
+ type: 'openIdConnect',
33
+ openIdConnectUrl: process.env.OIDC_JWKS_URI
34
+ ? process.env.OIDC_JWKS_URI.replace(/\/jwks\.json$/, '/.well-known/openid-configuration')
35
+ : (process.env.OIDC_ISSUER
36
+ ? `${process.env.OIDC_ISSUER.replace(/\/$/, '')}/.well-known/openid-configuration`
37
+ : undefined)
38
+ }
39
+ {{/if_eq}}
45
40
  }
46
41
  }
47
42
  },
@@ -1,3 +1,4 @@
1
+ // @ts-nocheck
1
2
  import logger from './logger.js'
2
3
 
3
4
  export function validateConfig() {
@@ -13,10 +14,6 @@ export function validateConfig() {
13
14
  logger.warn('[config] DB_USER not set, using default: root')
14
15
  }
15
16
 
16
- if (process.env.JWT_SECRET === 'please-change-this-in-production') {
17
- logger.warn('[config] WARNING: JWT_SECRET is using default value, please change in production!')
18
- }
19
-
20
17
  if (!process.env.REDIS_HOST) {
21
18
  logger.warn('[config] REDIS_HOST not set, using default: 127.0.0.1')
22
19
  }
@@ -42,22 +39,41 @@ export function validateConfig() {
42
39
  }
43
40
  }
44
41
 
42
+ {{#if_eq auth "static"}}
43
+ export function checkJwtSecret() {
44
+ if (process.env.JWT_SECRET === 'please-change-this-in-production' || !process.env.JWT_SECRET) {
45
+ if (process.env.NODE_ENV === 'production') {
46
+ logger.error('[config] JWT_SECRET must be set in production!')
47
+ return false
48
+ }
49
+ logger.warn('[config] WARNING: JWT_SECRET is using default value, please change in production!')
50
+ }
51
+ return true
52
+ }
53
+ {{else}}
54
+ export function checkOIDCConfig() {
55
+ const issuer = process.env.OIDC_ISSUER
56
+ const jwksUri = process.env.OIDC_JWKS_URI
57
+ if (!issuer && !jwksUri) {
58
+ logger.error('[config] OIDC_ISSUER or OIDC_JWKS_URI must be set for jwks auth')
59
+ return false
60
+ }
61
+ if (process.env.NODE_ENV === 'production' && !process.env.OIDC_AUDIENCE) {
62
+ logger.warn('[config] OIDC_AUDIENCE is not set; audience verification will be skipped')
63
+ }
64
+ return true
65
+ }
66
+ {{/if_eq}}
67
+
45
68
  export function checkRequiredEnv() {
46
69
  const result = validateConfig()
47
-
70
+
48
71
  if (result.errors.length > 0) {
49
72
  logger.error('[config] Configuration validation failed:')
50
73
  result.errors.forEach((err) => logger.error(` - ${err}`))
51
74
  return false
52
75
  }
53
76
 
54
- if (process.env.NODE_ENV === 'production') {
55
- if (!process.env.JWT_SECRET || process.env.JWT_SECRET === 'please-change-this-in-production') {
56
- logger.error('[config] JWT_SECRET must be set in production!')
57
- return false
58
- }
59
- }
60
-
61
77
  logger.info('[config] Configuration validation passed')
62
78
  return true
63
- }
79
+ }
@@ -1,3 +1,4 @@
1
+ // @ts-nocheck
1
2
  import 'dotenv/config'
2
3
 
3
4
  import path from 'path'
@@ -5,37 +6,19 @@ import { fileURLToPath } from 'url'
5
6
  import express from 'skweb-express'
6
7
  import expressLogger, { errorHandler as expressErrorHandler } from 'skweb-express-logger'
7
8
  import swaggerUi from 'swagger-ui-express'
9
+ import { framework } from './web-framework.js'
8
10
  import logger from './utils/logger.js'
9
- import { checkRequiredEnv } from './utils/config.js'
11
+ import { checkRequiredEnv{{#if_eq auth "static"}}, checkJwtSecret{{/if_eq}}{{#if_eq auth "jwks"}}, checkOIDCConfig{{/if_eq}} } from './utils/config.js'
10
12
  import { buildOpenApiSpec } from './swagger.js'
11
13
 
12
14
  const __filename = fileURLToPath(import.meta.url)
13
15
  const __dirname = path.dirname(__filename)
14
16
 
17
+ {{#if_eq auth "static"}}
15
18
  /**
16
- * 构建 JWT 配置,支持两种模式:
17
- * - 'static' (默认):使用共享密钥(HS256/HS384/HS512),适用于自签 token
18
- * - 'jwks':通过 JWKS 端点动态获取公钥(RS256 等),适用于 Keycloak/Auth0 等 IdP
19
+ * JWT 配置(static 模式,HS256 共享密钥)。
19
20
  */
20
21
  function buildJwtOptions() {
21
- const provider = process.env.AUTH_PROVIDER || 'static'
22
-
23
- if (provider === 'jwks') {
24
- const issuer = process.env.OIDC_ISSUER
25
- const jwksUri = process.env.OIDC_JWKS_URI
26
- const audience = process.env.OIDC_AUDIENCE
27
- if (!issuer && !jwksUri) {
28
- throw new Error('AUTH_PROVIDER=jwks requires OIDC_ISSUER or OIDC_JWKS_URI')
29
- }
30
- return {
31
- provider: 'jwks',
32
- algorithms: ['RS256'],
33
- jwks: { jwksUri, issuer, audience },
34
- verify: { issuer, audience },
35
- requestProperty: 'resolvedToken'
36
- }
37
- }
38
-
39
22
  return {
40
23
  provider: 'static',
41
24
  secret: process.env.JWT_SECRET || 'please-change-this-in-production',
@@ -43,12 +26,27 @@ function buildJwtOptions() {
43
26
  sign: { expiresIn: process.env.JWT_EXPIRES_IN || '7d' },
44
27
  requestProperty: 'resolvedToken'
45
28
  }
46
- }
47
-
29
+ }{{else}}
48
30
  /**
49
- * Swagger UI 仅在 NODE_ENV development / test,且 SWAGGER_ENABLED !== 'false' 时启用。
50
- * 生产环境(production / unset)默认禁用,避免暴露内部 API 细节。
31
+ * JWT 配置(jwks 模式,通过 OIDC 兼容的 JWKS 端点验证 RS256 签名)。
51
32
  */
33
+ function buildJwtOptions() {
34
+ const issuer = process.env.OIDC_ISSUER
35
+ const jwksUri = process.env.OIDC_JWKS_URI
36
+ const audience = process.env.OIDC_AUDIENCE
37
+ if (!issuer && !jwksUri) {
38
+ throw new Error('jwks mode requires OIDC_ISSUER or OIDC_JWKS_URI')
39
+ }
40
+ return {
41
+ provider: 'jwks',
42
+ algorithms: ['RS256'],
43
+ jwks: { jwksUri, issuer, audience },
44
+ verify: { issuer, audience },
45
+ requestProperty: 'resolvedToken'
46
+ }
47
+ }
48
+ {{/if_eq}}
49
+
52
50
  function shouldEnableSwagger() {
53
51
  const env = (process.env.NODE_ENV || '').toLowerCase()
54
52
  if (env !== 'development' && env !== 'test') return false
@@ -63,27 +61,17 @@ async function bootstrap() {
63
61
  logger.error('[app] Configuration validation failed, exiting...')
64
62
  process.exit(1)
65
63
  }
66
-
67
- const { WebFramework } = await import('skweb-framework')
68
-
69
- const framework = new WebFramework({
70
- db: {
71
- host: process.env.DB_HOST || '127.0.0.1',
72
- port: Number(process.env.DB_PORT) || 3306,
73
- username: process.env.DB_USER || 'root',
74
- password: process.env.DB_PASS || '',
75
- database: process.env.DB_NAME || 'test-project',
76
- dialect: process.env.DB_DIALECT || 'mysql',
77
- charset: process.env.DB_CHARSET || 'utf8mb4',
78
- underscored: true,
79
- logging: process.env.DEBUG_DB === 'true' ? (msg) => logger.debug(msg) : false
80
- },
81
- redis: {
82
- host: process.env.REDIS_HOST || '127.0.0.1',
83
- port: Number(process.env.REDIS_PORT) || 6379,
84
- password: process.env.REDIS_PASSWORD || undefined
85
- }
86
- })
64
+ {{#if_eq auth "static"}}
65
+ if (!checkJwtSecret()) {
66
+ logger.error('[app] JWT configuration validation failed, exiting...')
67
+ process.exit(1)
68
+ }
69
+ {{else}}
70
+ if (!checkOIDCConfig()) {
71
+ logger.error('[app] OIDC configuration validation failed, exiting...')
72
+ process.exit(1)
73
+ }
74
+ {{/if_eq}}
87
75
 
88
76
  const app = framework.createApp()
89
77
 
@@ -101,21 +89,19 @@ async function bootstrap() {
101
89
  app.setOkCode(0)
102
90
  app.setOkMessage('success')
103
91
 
104
- // JWT 配置(按 AUTH_PROVIDER 选择 static 或 jwks 模式)
105
92
  const jwtOptions = buildJwtOptions()
106
93
  app.setJWT('default', jwtOptions)
107
94
  app.setJwtHintHeader('JwtHint')
108
95
 
109
- if (jwtOptions.provider === 'jwks') {
110
- logger.info(`[app] JWT auth provider: jwks (issuer: ${jwtOptions.jwks.issuer || jwtOptions.jwks.jwksUri})`)
111
- } else {
112
- logger.info('[app] JWT auth provider: static (HS256)')
113
- }
96
+ {{#if_eq auth "static"}}
97
+ logger.info('[app] JWT auth provider: static (HS256)')
98
+ {{else}}
99
+ logger.info(`[app] JWT auth provider: jwks (issuer: ${jwtOptions.jwks?.issuer || jwtOptions.jwks?.jwksUri})`)
100
+ {{/if_eq}}
114
101
 
115
102
  app.use(expressLogger())
116
103
  app.use(express.static(path.join(__dirname, '..', 'public')))
117
104
 
118
- // 仅在 dev / test 环境挂载 Swagger UI(默认关闭,需显式 SWAGGER_ENABLED=true)
119
105
  if (shouldEnableSwagger()) {
120
106
  const openapiSpec = buildOpenApiSpec()
121
107
  app.get('/api-docs.json', (_req, res) => res.json(openapiSpec))
@@ -130,14 +116,12 @@ async function bootstrap() {
130
116
 
131
117
  await framework.loadControllers()
132
118
 
133
- // 错误日志中间件:挂在所有路由之后,记录业务错误并继续传递给默认错误处理。
134
119
  app.use(expressErrorHandler())
135
120
 
136
121
  framework.start(Number(process.env.PORT) || 3000)
137
122
 
138
123
  logger.info('[app] Initialization complete (web process)')
139
124
 
140
- // 未捕获异常:打印堆栈后直接退出。OS / 进程管理器负责回收资源。
141
125
  process.on('uncaughtException', (err) => {
142
126
  logger.error(`[app] uncaughtException: ${err.stack || err.message}`)
143
127
  process.exit(1)
@@ -0,0 +1,23 @@
1
+ // @ts-nocheck
2
+ import { WebFramework } from 'skweb-framework'
3
+
4
+ const framework = new WebFramework({
5
+ db: {
6
+ host: process.env.DB_HOST || '127.0.0.1',
7
+ port: Number(process.env.DB_PORT) || 3306,
8
+ username: process.env.DB_USER || 'root',
9
+ password: process.env.DB_PASS || '',
10
+ database: process.env.DB_NAME || 'test-project',
11
+ dialect: process.env.DB_DIALECT || 'mysql',
12
+ charset: process.env.DB_CHARSET || 'utf8mb4',
13
+ underscored: true,
14
+ logging: process.env.DEBUG_DB === 'true'
15
+ },
16
+ redis: {
17
+ host: process.env.REDIS_HOST || '127.0.0.1',
18
+ port: Number(process.env.REDIS_PORT) || 6379,
19
+ password: process.env.REDIS_PASSWORD || undefined
20
+ }
21
+ })
22
+
23
+ export { framework }