@sisin/egg-client 1.0.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 (57) hide show
  1. package/README.md +267 -0
  2. package/app/constants/code.js +9 -0
  3. package/app/constants/crypt.js +10 -0
  4. package/app/constants/error.js +20 -0
  5. package/app/constants/redis.js +17 -0
  6. package/app/controller/authSystem/auth.js +119 -0
  7. package/app/controller/common/health.js +15 -0
  8. package/app/controller/home.js +10 -0
  9. package/app/core/database/database-health.js +39 -0
  10. package/app/core/database/database-manager.js +43 -0
  11. package/app/core/database/index.js +19 -0
  12. package/app/core/database/prisma-logging.js +38 -0
  13. package/app/core/database/transaction-manager.js +9 -0
  14. package/app/core/repository/auth/user_repository.js +14 -0
  15. package/app/core/repository/base-repository.js +26 -0
  16. package/app/extend/context.js +57 -0
  17. package/app/extend/helper.js +24 -0
  18. package/app/middleware/auth.js +16 -0
  19. package/app/middleware/check_ready.js +11 -0
  20. package/app/middleware/error_handler.js +28 -0
  21. package/app/middleware/jwt_auth.js +27 -0
  22. package/app/middleware/login_limit.js +4 -0
  23. package/app/middleware/permission.js +8 -0
  24. package/app/middleware/request_log.js +28 -0
  25. package/app/middleware/upload_limit.js +17 -0
  26. package/app/model/redis/redis-auth.js +42 -0
  27. package/app/redis/index.js +25 -0
  28. package/app/redis/redis-manager.js +46 -0
  29. package/app/router/auth.js +17 -0
  30. package/app/router/common.js +11 -0
  31. package/app/router/index.js +13 -0
  32. package/app/router.js +11 -0
  33. package/app/service/authSystem/auth.js +144 -0
  34. package/app/service/authSystem/permission.js +4 -0
  35. package/app/service/authSystem/token.js +4 -0
  36. package/app/service/redis/auth.js +4 -0
  37. package/app/utils/common.js +58 -0
  38. package/app/utils/encrypt.js +33 -0
  39. package/app/utils/jwt.js +23 -0
  40. package/app/utils/logger.js +209 -0
  41. package/app/utils/permission.js +2 -0
  42. package/app/utils/prisma-manager.js +127 -0
  43. package/app/utils/prisma.js +21 -0
  44. package/app/validate/user.js +6 -0
  45. package/app.js +21 -0
  46. package/config/config.default.js +137 -0
  47. package/config/logging.js +26 -0
  48. package/config/plugin.default.js +3 -0
  49. package/index.js +22 -0
  50. package/init/index.js +39 -0
  51. package/init/init-auth.js +37 -0
  52. package/init/init-database.js +59 -0
  53. package/init/init-logger.js +18 -0
  54. package/init/init-redis.js +26 -0
  55. package/init/init-websocket.js +10 -0
  56. package/init/ready.js +8 -0
  57. package/package.json +60 -0
@@ -0,0 +1,23 @@
1
+ // JWT token 工具
2
+ const jwt = require('jsonwebtoken');
3
+
4
+ /**
5
+ * 生成签名
6
+ * @param {*} payload token payload
7
+ * @param {String} SECRET 密钥
8
+ * @param {Object} options 其他选项,如 expiresIn: '1h'(1小时)、'30m'(30分钟)、'2d'(2天)
9
+ * @return {string} signed access token
10
+ */
11
+ exports.createToken = (payload, SECRET, options = {}) => {
12
+ return jwt.sign(payload, SECRET, { ...options });
13
+ };
14
+
15
+ /**
16
+ * 验证是否与密钥匹配
17
+ * @param {*} token access token
18
+ * @param {String} SECRET 密钥
19
+ * @return {Object|null} { ...payload, exp } 解密后的payload 或 null
20
+ */
21
+ exports.verifyToken = (token, SECRET) => {
22
+ return jwt.verify(token, SECRET);
23
+ };
@@ -0,0 +1,209 @@
1
+ const pino = require("pino");
2
+ const path = require("path");
3
+ const fs = require("fs");
4
+ const { loggingConfig } = require("../../config/logging");
5
+
6
+ // ── 日志级别权重 ─────────────────────────────────────
7
+ const levelRank = {
8
+ trace: 10,
9
+ debug: 20,
10
+ info: 30,
11
+ warn: 40,
12
+ error: 50,
13
+ fatal: 60,
14
+ };
15
+
16
+ // ── Time format: YYYY-MM-DD HH:mm:ss ────────────────
17
+ function formatTime() {
18
+ const d = new Date();
19
+ const pad = (n) => String(n).padStart(2, "0");
20
+ return (
21
+ `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ` +
22
+ `${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`
23
+ );
24
+ }
25
+
26
+ // ── Date-rotating file writer ───────────────────────
27
+ // Creates: {basePath}/YYYY-MM/DD.log
28
+ // Content: YYYY-MM-DD HH:mm:ss: message
29
+ class DateRotateWriter {
30
+ constructor(basePath) {
31
+ this.basePath = basePath;
32
+ this.currentDate = "";
33
+ this.stream = null;
34
+ }
35
+
36
+ _rotate() {
37
+ const now = new Date();
38
+ const pad = (n) => String(n).padStart(2, "0");
39
+ const yearMonth = `${now.getFullYear()}-${pad(now.getMonth() + 1)}`;
40
+ const day = pad(now.getDate());
41
+ const dateStr = `${yearMonth}-${day}`;
42
+
43
+ if (dateStr === this.currentDate) return;
44
+
45
+ if (this.stream) {
46
+ this.stream.destroy();
47
+ this.stream = null;
48
+ }
49
+
50
+ const dir = path.resolve(this.basePath, yearMonth);
51
+ fs.mkdirSync(dir, { recursive: true });
52
+ this.stream = fs.createWriteStream(path.join(dir, `${day}.log`), {
53
+ flags: "a",
54
+ });
55
+ this.stream.on("error", () => {
56
+ // silent — prevent crash on file write errors
57
+ });
58
+ this.currentDate = dateStr;
59
+ }
60
+
61
+ write(line) {
62
+ try {
63
+ this._rotate();
64
+ if (this.stream) {
65
+ this.stream.write(line + "\n");
66
+ }
67
+ } catch (_) {
68
+ // silent — don't crash the app over log write failures
69
+ }
70
+ }
71
+
72
+ close() {
73
+ if (this.stream) {
74
+ this.stream.end();
75
+ this.stream = null;
76
+ }
77
+ }
78
+ }
79
+
80
+ // ── Serialize extra data for plain-text file output ──
81
+ function flatData(data) {
82
+ if (data === undefined || data === null) return "";
83
+ if (typeof data === "string") return ` ${data}`;
84
+ if (data instanceof Error) {
85
+ return ` ${data.message}${data.stack ? `\n${data.stack}` : ""}`;
86
+ }
87
+ return ` ${JSON.stringify(data)}`;
88
+ }
89
+
90
+ // ── Console-only logger factory ──────────────────────
91
+ // 只在终端美化输出,不写入文件。用于初始化提示、状态消息等全局场景。
92
+ function createConsoleLogger(config) {
93
+ const level = config.level || "info";
94
+ const p = pino({
95
+ level,
96
+ transport: {
97
+ target: "pino-pretty",
98
+ options: {
99
+ colorize: true,
100
+ translateTime: "yyyy-mm-dd HH:MM:ss",
101
+ ignore: "pid,hostname",
102
+ },
103
+ },
104
+ });
105
+
106
+ const logger = {};
107
+
108
+ const stdLevels = ["trace", "debug", "info", "warn", "error", "fatal"];
109
+ for (const lvl of stdLevels) {
110
+ logger[lvl] = (message, data) => {
111
+ if (data !== undefined && data !== null) {
112
+ p[lvl](data, message);
113
+ } else {
114
+ p[lvl](message);
115
+ }
116
+ };
117
+ }
118
+
119
+ // 终端专用美化方法(不落文件)
120
+ logger.success = (message) => p.info(`√ ${message}`);
121
+ logger.init = (message) => p.info(`◆ ${message}`);
122
+ logger.warning = logger.warn;
123
+
124
+ return logger;
125
+ }
126
+
127
+ // ── File-logger factory (with optional terminal) ────
128
+ // 根据 config.terminal 决定是否同时在终端输出;根据 config.pretty 决定终端是否美化。
129
+ // 日志文件始终通过 DateRotateWriter 写入(当 filePath 非空时),但受 config.level 过滤。
130
+ function createLogger(config) {
131
+ const level = config.level || "info";
132
+ const minRank = levelRank[level] || levelRank.info;
133
+
134
+ // 终端输出 (pino + 可选 pino-pretty),仅在 config.terminal 为 true 时创建
135
+ let p = null;
136
+ if (config.terminal) {
137
+ const pinoOpts = { level };
138
+ if (config.pretty) {
139
+ pinoOpts.transport = {
140
+ target: "pino-pretty",
141
+ options: {
142
+ colorize: true,
143
+ translateTime: "yyyy-mm-dd HH:MM:ss",
144
+ ignore: "pid,hostname",
145
+ },
146
+ };
147
+ }
148
+ p = pino(pinoOpts);
149
+ }
150
+
151
+ // 文件写入器(始终创建,当 filePath 非空时)
152
+ const fw = config.filePath ? new DateRotateWriter(config.filePath) : null;
153
+
154
+ const logger = {};
155
+
156
+ const stdLevels = ["trace", "debug", "info", "warn", "error", "fatal"];
157
+ for (const lvl of stdLevels) {
158
+ logger[lvl] = (message, data) => {
159
+ // 终端输出:pino 内部自带 level 过滤
160
+ if (p) {
161
+ if (data !== undefined && data !== null) {
162
+ p[lvl](data, message);
163
+ } else {
164
+ p[lvl](message);
165
+ }
166
+ }
167
+ // 文件写入:手动检查 level 权重,低于配置级别的不落文件
168
+ if (fw && (levelRank[lvl] || 0) >= minRank) {
169
+ fw.write(`${formatTime()}: [${lvl.toUpperCase()}] ${message}${flatData(data)}`);
170
+ }
171
+ };
172
+ }
173
+
174
+ // success / init 按 info 级别处理,受 config.level 过滤
175
+ logger.success = (message) => {
176
+ if (p) p.info(`√ ${message}`);
177
+ if (fw && levelRank.info >= minRank) fw.write(`${formatTime()}: [SUCCESS] ${message}`);
178
+ };
179
+ logger.init = (message) => {
180
+ if (p) p.info(`◆ ${message}`);
181
+ if (fw && levelRank.info >= minRank) fw.write(`${formatTime()}: [INIT] ${message}`);
182
+ };
183
+ logger.warning = logger.warn;
184
+
185
+ return logger;
186
+ }
187
+
188
+ // ── Pre-configured loggers ───────────────────────────
189
+
190
+ // consoleLogger:纯终端输出,不落文件。作为默认导出,供 index.js 初始化提示、全局状态消息等场景。
191
+ const consoleLogger = createConsoleLogger(loggingConfig.console);
192
+
193
+ // appLogger / requestLogger / prismaLogger:文件存储为主,终端输出由配置控制
194
+ const appLogger = createLogger(loggingConfig.app);
195
+ const requestLogger = createLogger(loggingConfig.request);
196
+ const prismaLogger = createLogger({
197
+ level: loggingConfig.prisma.sqlLogLevel,
198
+ filePath: loggingConfig.prisma.filePath,
199
+ terminal: loggingConfig.prisma.sqlTerminal,
200
+ pretty: loggingConfig.prisma.sqlTerminal,
201
+ });
202
+
203
+ // 默认导出:纯终端美化输出,用于初始化等全局消息
204
+ module.exports = consoleLogger;
205
+ module.exports.createLogger = createLogger;
206
+ module.exports.createConsoleLogger = createConsoleLogger;
207
+ module.exports.requestLogger = requestLogger;
208
+ module.exports.prismaLogger = prismaLogger;
209
+ module.exports.appLogger = appLogger;
@@ -0,0 +1,2 @@
1
+ // 权限工具(预留)
2
+ module.exports = {};
@@ -0,0 +1,127 @@
1
+ const path = require('path');
2
+ const fs = require('fs');
3
+ const { execSync } = require('child_process');
4
+ const { buildDataBaseUrl } = require('./prisma');
5
+ const logger = require('./logger');
6
+
7
+ /**
8
+ * Prisma 生命周期管理器
9
+ * 自动构建每个数据库的 config、schema 和 generated client
10
+ *
11
+ * 对于 Dbs 中的每个 key,生成:
12
+ * - prisma.{key}.config.ts — Prisma 7 配置 (schema 路径 + 连接 URL)
13
+ * - prisma/{key}/schema.prisma — 从数据库 pull 的 schema
14
+ * - prisma/generated/{key}/ — 生成的 PrismaClient 实例
15
+ */
16
+ const CONFIG_TPL = (key, url) => `import { defineConfig } from "prisma/config";
17
+
18
+ export default defineConfig({
19
+ schema: "prisma/${key}/schema.prisma",
20
+ datasource: { url: "${url}" },
21
+ });
22
+ `;
23
+
24
+ const INITIAL_SCHEMA = (key, type = 'mysql') => `generator client {
25
+ provider = "prisma-client-js"
26
+ output = "../generated/${key}"
27
+ }
28
+
29
+ datasource db {
30
+ provider = "${type}"
31
+ }
32
+ `;
33
+
34
+ class PrismaManager {
35
+ constructor(app) {
36
+ this.app = app;
37
+ this.baseDir = app.baseDir;
38
+ this.dbConfig = app.config.Dbs || {};
39
+ }
40
+
41
+ /** 所有数据库是否就绪(generated client 存在) */
42
+ isReady() {
43
+ for (const key in this.dbConfig) {
44
+ if (!this.dbConfig[key]) continue;
45
+ try {
46
+ const mod = require(path.join(this.baseDir, 'prisma', 'generated', key));
47
+ if (!mod.PrismaClient) return false;
48
+ } catch (_) {
49
+ return false;
50
+ }
51
+ }
52
+ return true;
53
+ }
54
+
55
+ /** 为每个数据库生成 prisma.{key}.config.ts */
56
+ generateConfigs() {
57
+ for (const key in this.dbConfig) {
58
+ if (!this.dbConfig[key]) continue;
59
+ const url = buildDataBaseUrl(this.dbConfig[key]);
60
+ const configPath = path.join(this.baseDir, 'prisma.' + key + '.config.ts');
61
+ logger.info('[PrismaManager] 生成配置 ' + configPath);
62
+ fs.writeFileSync(configPath, CONFIG_TPL(key, url), 'utf-8');
63
+ }
64
+ }
65
+
66
+ /** 确保 prisma/{key}/ 目录和初始 schema 存在(generator 块,prisma pull 不会覆盖它) */
67
+ ensureInitialSchemas() {
68
+ for (const key in this.dbConfig) {
69
+ if (!this.dbConfig[key]) continue;
70
+ const schemaDir = path.join(this.baseDir, 'prisma', key);
71
+ fs.mkdirSync(schemaDir, { recursive: true });
72
+ const schemaPath = path.join(schemaDir, 'schema.prisma');
73
+ if (!fs.existsSync(schemaPath)) {
74
+ logger.info('[PrismaManager] 创建初始 schema ' + schemaPath);
75
+ fs.writeFileSync(schemaPath, INITIAL_SCHEMA(key,this.dbConfig[key]?.type || 'mysql'), 'utf-8');
76
+ }
77
+ }
78
+ }
79
+
80
+ /** 执行 npx prisma db pull --config prisma.{key}.config.ts */
81
+ pullSchemas() {
82
+ for (const key in this.dbConfig) {
83
+ if (!this.dbConfig[key]) continue;
84
+ const cfg = path.join(this.baseDir, 'prisma.' + key + '.config.ts');
85
+ if (!fs.existsSync(cfg)) {
86
+ logger.warn('[PrismaManager] 配置 ' + key + ' 不存在,跳过 pull');
87
+ continue;
88
+ }
89
+ logger.info('[PrismaManager] prisma db pull - ' + key);
90
+ execSync('npx prisma db pull --config "' + cfg + '"', {
91
+ cwd: this.baseDir,
92
+ stdio: 'inherit',
93
+ env: { ...process.env },
94
+ });
95
+ }
96
+ }
97
+
98
+ /** 执行 npx prisma generate --config prisma.{key}.config.ts */
99
+ generateClients() {
100
+ for (const key in this.dbConfig) {
101
+ if (!this.dbConfig[key]) continue;
102
+ const cfg = path.join(this.baseDir, 'prisma.' + key + '.config.ts');
103
+ if (!fs.existsSync(cfg)) {
104
+ logger.warn('[PrismaManager] 配置 ' + key + ' 不存在,跳过 generate');
105
+ continue;
106
+ }
107
+ logger.info('[PrismaManager] prisma generate - ' + key);
108
+ execSync('npx prisma generate --config "' + cfg + '"', {
109
+ cwd: this.baseDir,
110
+ stdio: 'inherit',
111
+ env: { ...process.env },
112
+ });
113
+ }
114
+ }
115
+
116
+ /** 完整同步:生成配置 → 初始 schema → db pull → generate */
117
+ sync() {
118
+ logger.info('[PrismaManager] ====== 开始 Prisma 同步 ======');
119
+ this.generateConfigs();
120
+ this.ensureInitialSchemas();
121
+ this.pullSchemas();
122
+ this.generateClients();
123
+ logger.info('[PrismaManager] ====== Prisma 同步完成 ======');
124
+ }
125
+ }
126
+
127
+ module.exports = PrismaManager;
@@ -0,0 +1,21 @@
1
+ /**
2
+ * 构建 Prisma 数据库连接 URL
3
+ *
4
+ * @param {Object} options
5
+ * @param {string} options.type - 数据库类型,如 "mysql"
6
+ * @param {number} [options.connectLimit=5] - 连接数限制
7
+ * @param {number} [options.socketTimeout=30000] - Socket 超时时间 (ms)
8
+ * @param {number} [options.poolTimeout=30000] - 连接池超时 (ms)
9
+ * @param {number} [options.connectTimeout=30000] - 连接超时 (ms)
10
+ * @param {Object} [options.rest] - 额外配置
11
+ * @returns {string} 数据库连接 URL
12
+ */
13
+ function buildDataBaseUrl({ type, user, password, host, port, database, pool = {}, ...config }) {
14
+ const { connectLimit = 5, socketTimeout = 30000, poolTimeout = 30000, connectTimeout = 30000, } = pool
15
+ if (type === "mysql") {
16
+ return `${type}://${user}:${password}@${host}:${port}/${database}?connect_limit=${connectLimit}&socket_timeout=${socketTimeout}&pool_timeout=${poolTimeout}&connect_timeout=${connectTimeout}`;
17
+ }
18
+ throw new Error(`暂不支持当前数据库类型:${type}`);
19
+ }
20
+
21
+ module.exports = { buildDataBaseUrl };
@@ -0,0 +1,6 @@
1
+ module.exports = {
2
+ login: {
3
+ username: { type: 'string', required: true, min: 3, max: 20 },
4
+ password: { type: 'string', required: true, min: 6, max: 20 },
5
+ },
6
+ };
package/app.js ADDED
@@ -0,0 +1,21 @@
1
+ // @niceguy/egg-client - Framework lifecycle entry
2
+ 'use strict';
3
+
4
+ const init = require('./init');
5
+
6
+ module.exports = (app) => {
7
+ app.initStatus = { ready: false, tasks: {}, startTime: Date.now() };
8
+
9
+ // 提供 initFramework 方法,可以从外部调用
10
+ app.initFramework = async () => {
11
+ if (app._frameworkInitDone) return;
12
+ app._frameworkInitDone = true;
13
+ await init(app);
14
+ app.initStatus.ready = true;
15
+ };
16
+
17
+ // 如果 app.js 被 Egg.js 加载,通过 beforeStart 触发初始化
18
+ app.beforeStart(async () => {
19
+ await app.initFramework();
20
+ });
21
+ };
@@ -0,0 +1,137 @@
1
+ /**
2
+ * @niceguy/egg-client - Framework-level default configuration
3
+ * 框架提供的默认配置,业务项目可通过 config.default.js 覆盖
4
+ */
5
+ 'use strict';
6
+
7
+ const path = require('path');
8
+
9
+ module.exports = (appInfo) => {
10
+ const config = {
11
+ // 集群监听
12
+ cluster: {
13
+ listen: {
14
+ path: '',
15
+ port: 7001,
16
+ },
17
+ },
18
+
19
+ // Cookie 签名密钥
20
+ keys: appInfo.name + '_niceguy_egg_client_key',
21
+
22
+ // 密码是否加密
23
+ isPwdEncrypt: true,
24
+
25
+ // JWT 配置
26
+ jwt: {
27
+ accessToken: 60 * 60 * 1000, // 有效期 1 小时
28
+ refreshToken: 60 * 60 * 24 * 7 * 1000, // 有效期 7 天
29
+ secret: 'niceguy-core',
30
+ },
31
+
32
+ // WebSocket 配置
33
+ websocket: {
34
+ enable: false,
35
+ },
36
+
37
+ // 数据库连接配置(业务项目覆盖具体连接信息)
38
+ Dbs: {
39
+ fx: { // 登录库
40
+ type: 'mysql',// 数据库类型
41
+ host: '127.0.0.1',
42
+ port: 3306,
43
+ user: 'root',
44
+ password: '123456',
45
+ database: 'db_base',
46
+ pool: {
47
+ connectLimit: 5,// 连接池大小 - 按worker
48
+ poolTimeout: 30000,// 连接池超时时间 ms
49
+ socketTimeout: 30000,// SQL执行超时时间
50
+ connectTimeout: 30000,// 建立连接超时
51
+ }
52
+ },
53
+ business: { // 业务库
54
+ type: 'mysql',// 数据库类型
55
+ host: '127.0.0.1',
56
+ port: 3306,
57
+ user: 'root',
58
+ password: '123456',
59
+ database: 'db_business',
60
+ pool: {
61
+ connectLimit: 5,// 连接池大小 - 按worker
62
+ poolTimeout: 30000,// 连接池超时时间 ms
63
+ socketTimeout: 30000,// SQL执行超时时间
64
+ connectTimeout: 30000,// 建立连接超时
65
+ }
66
+ }
67
+ },
68
+
69
+ // Redis 连接配置(业务项目覆盖具体连接信息)
70
+ redis: {
71
+ fx: {
72
+ // 登录库
73
+ host: "127.0.0.1",
74
+ port: 6379,
75
+ password: "123456",
76
+ db: 0,
77
+ },
78
+ bussiness: {
79
+ host: "127.0.0.1",
80
+ port: 6379,
81
+ password: "123456",
82
+ db: 1,
83
+ },
84
+ },
85
+
86
+ // 日志配置(框架内置,亦可被项目覆盖)
87
+ logging: {
88
+ console: {
89
+ level: 'info',
90
+ pretty: true,
91
+ },
92
+ request: {
93
+ level: 'warn',
94
+ terminal: false,
95
+ filePath: path.join(appInfo.baseDir, 'logs', 'request'),
96
+ },
97
+ app: {
98
+ level: 'warn',
99
+ pretty: true,
100
+ terminal: false,
101
+ filePath: path.join(appInfo.baseDir, 'logs', 'app'),
102
+ },
103
+ prisma: {
104
+ level: 'warn',
105
+ sqlTerminal: true,
106
+ sqlLogLevel: 'debug',
107
+ slowQueryMs: 20000,
108
+ filePath: path.join(appInfo.baseDir, 'logs', 'prisma'),
109
+ },
110
+ },
111
+
112
+ // CSRF 安全配置(API 使用 JWT Header 鉴权,不依赖 Cookie)
113
+ security: {
114
+ csrf: {
115
+ enable: false,
116
+ ignore: (ctx) => ctx.path.startsWith('/api'),
117
+ },
118
+ },
119
+
120
+ // 全局中间件
121
+ middleware: ['checkReady', 'errorHandler', 'requestLog', 'jwtAuth'],
122
+
123
+ // 文件上传限制
124
+ fileLimit: {
125
+ whiteList: ['.jpg', '.jpeg', '.png', '.gif', '.bmp', '.xlsx', '.xls', '.doc', '.docx'],
126
+ size: 10, // MB
127
+ },
128
+
129
+ // 认证配置
130
+ auth: {
131
+ whiteList: ['/api/public'], // API 白名单(前缀匹配,跳过 jwt 校验)
132
+ allowDevice: ['pc', 'h5', 'app', 'miniapp'],
133
+ },
134
+ };
135
+
136
+ return config;
137
+ };
@@ -0,0 +1,26 @@
1
+ const loggingConfig = {
2
+ console: {
3
+ level: process.env.CONSOLE_LOG_LEVEL || "info", // 控制台日志级别
4
+ pretty: true, // 始终美化终端输出
5
+ },
6
+ request: {
7
+ level: process.env.REQUEST_LOG_LEVEL || "warn", // 日志级别 (info, warn, error)
8
+ terminal: process.env.REQUEST_LOG_TERMINAL === "true", // 是否终端输出
9
+ filePath: process.env.REQUEST_LOG_FILE_PATH || "./logs/request", // 日志文件路径
10
+ },
11
+ app: {
12
+ level: process.env.APP_LOG_LEVEL || "warn", // 日志级别 (info, warn, error)
13
+ pretty: true,//process.env.APP_LOG_PRETTY !== "false", // 是否美化输出
14
+ terminal: process.env.APP_LOG_TERMINAL === "true", // 是否终端输出
15
+ filePath: process.env.APP_LOG_FILE_PATH || "./logs/app", // 日志文件路径
16
+ },
17
+ prisma: {
18
+ level: process.env.PRISMA_LOG_LEVEL || "warn", // 日志级别 (info, warn, error)
19
+ sqlTerminal: true, // process.env.PRISMA_SQL_TERMINAL !== "false", // 是否终端输出日志
20
+ sqlLogLevel: process.env.PRISMA_SQL_LOG_LEVEL || "debug", // 日志级别 (info, warn, error)
21
+ slowQueryMs: Number(process.env.PRISMA_SLOW_QUERY_MS || 20000), // 慢查询阈值
22
+ filePath: process.env.PRISMA_LOG_FILE_PATH || "./logs/prisma", // 日志文件路径
23
+ },
24
+ };
25
+
26
+ module.exports = { loggingConfig };
@@ -0,0 +1,3 @@
1
+ 'use strict';
2
+
3
+ module.exports = {};
package/index.js ADDED
@@ -0,0 +1,22 @@
1
+ 'use strict';
2
+
3
+ // 1) 重新导出 egg 框架,使 egg-bin 能找到 startCluster / Application / Controller 等
4
+ const egg = require('egg');
5
+ Object.assign(exports, egg);
6
+
7
+ // 2) 框架自定义导出(lazy getter,使用时才加载)
8
+ const customExports = {
9
+ prisma: () => require('./app/utils/prisma'),
10
+ PrismaManager:() => require('./app/utils/prisma-manager'),
11
+ BaseRepository: () => require('./app/core/repository/base-repository'),
12
+ TransactionManager:() => require('./app/core/database/transaction-manager'),
13
+ encrypt: () => require('./app/utils/encrypt'),
14
+ jwt: () => require('./app/utils/jwt'),
15
+ common: () => require('./app/utils/common'),
16
+ logger: () => require('./app/utils/logger'),
17
+ permission: () => require('./app/utils/permission'),
18
+ };
19
+
20
+ for (const [name, loader] of Object.entries(customExports)) {
21
+ Object.defineProperty(exports, name, { enumerable: true, get: loader });
22
+ }
package/init/index.js ADDED
@@ -0,0 +1,39 @@
1
+ // 框架初始化入口 — 挂载日志 → 初始化数据库 → 初始化 Redis → 初始化认证
2
+ const logger = require('../app/utils/logger');
3
+ const initDatabase = require('./init-database');
4
+ const initRedis = require('./init-redis');
5
+ const initLogger = require('./init-logger');
6
+ const initAuth = require('./init-auth');
7
+
8
+ module.exports = async (app) => {
9
+ app.logger.info('[INIT] Framework init start');
10
+ try {
11
+ // 并发执行:日志、数据库、Redis
12
+ await Promise.all([initLogger(app), initDatabase(app), initRedis(app)]);
13
+ // 依赖上述资源的后续初始化
14
+ await Promise.all([initAuth(app)]);
15
+
16
+ // 注册框架路由(此时 controller / service / middleware 均已加载完毕)
17
+ app.logger.info('[INIT] Registering framework routes...');
18
+
19
+ app.router.prefix('/api');
20
+ app.logger.info('[INIT] Prefix set, loading router files...');
21
+
22
+ // 直接注册一条测试路由(不依赖 controller/router 文件)
23
+ app.router.get('/api/test', (ctx) => { ctx.body = { test: 'ok' }; });
24
+
25
+ // 载入框架的 app/router.js(注册 controller 路由)
26
+ try {
27
+ require('../app/router')(app);
28
+ app.logger.info('[INIT] Router files loaded successfully');
29
+ } catch (subErr) {
30
+ app.logger.error('[INIT] Router file load FAILED: ' + subErr.message);
31
+ }
32
+
33
+ app.logger.info('[INIT] Routes registration complete');
34
+ } catch (err) {
35
+ app.logger.error('[INIT] Framework init FAILED: ' + err.message);
36
+ logger.error('系统初始化任务执行失败', err);
37
+ return;
38
+ }
39
+ };
@@ -0,0 +1,37 @@
1
+ // 用户初始化(密码加密检查、挂载 RedisAuth 模型)
2
+ const { isEncrypted, encryptPassword } = require('../app/utils/encrypt');
3
+ const logger = require('../app/utils/logger');
4
+ const RedisAuth = require('../app/model/redis/redis-auth');
5
+
6
+ module.exports = async (app) => {
7
+ try {
8
+ const ctx = app.createAnonymousContext();
9
+
10
+ // 挂载 modle 命名空间
11
+ app.modle = { redis: {}, prisma: {} };
12
+ app.modle.redis.redisAuth = new RedisAuth(app);
13
+
14
+ // 检查并加密现有用户密码
15
+ if (!ctx.service || !ctx.service.authSystem || !ctx.service.authSystem.auth) {
16
+ logger.warn('警告:authSystem.auth service 未找到,跳过用户初始化');
17
+ return;
18
+ }
19
+
20
+ const userList = await ctx.service.authSystem.auth.findMany();
21
+ const user = userList.find((item) => item.en_name === 'admin');
22
+ if (!user) return;
23
+
24
+ if (!isEncrypted(user.pwd)) {
25
+ const updateUser = [];
26
+ for (const o of userList) {
27
+ const pwd = await encryptPassword(o.pwd);
28
+ updateUser.push({ id: o.id, pwd });
29
+ }
30
+ await ctx.service.authSystem.auth.updateUserMany(updateUser);
31
+ logger.success('用户密码强加密');
32
+ }
33
+ } catch (err) {
34
+ logger.error('用户初始化失败', err);
35
+ throw err;
36
+ }
37
+ };