chanjs 1.2.6 → 2.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.
package/index.js CHANGED
@@ -1,68 +1,90 @@
1
- import express from "express";
1
+ import "./common/global.js";
2
2
  import path from "path";
3
3
  import fs from "fs";
4
- import cors from "cors";
5
- import { pathToFileURL } from 'url'; // 新增顶部导入
6
- import config from "./core/config.js";
7
- import * as helper from "./core/helper.js";
8
- import Controller from "./core/lib/controller.js";
9
- import Service from "./core/lib/service.js";
10
- import Cache from './core/lib/cache.js';
11
- import Task from "./core/lib/task.js";
12
- import core from "./core/lib/index.js";
13
- const {bindClass, createKnex, loadWebToEnd} = helper;
14
- import { z } from 'zod';
15
- /**
16
- * @description 基于express封装的mvc框架,遵循约定优于配置原则
17
- */
4
+ import {
5
+ bindClass,
6
+ db,
7
+ loaderSort,
8
+ getPackage,
9
+ loadConfig,
10
+ loadController,
11
+ } from "./utils/index.js";
12
+ import { express, z } from "./extend/import.js";
13
+ import { Controller, Service } from "./core/index.js";
14
+ import {
15
+ log,
16
+ setCookie,
17
+ setFavicon,
18
+ setBody,
19
+ setStatic,
20
+ setHeader,
21
+ setTemplate,
22
+ Cors,
23
+ validator,
24
+ } from "./middleware/index.js";
25
+
18
26
  class Chan {
19
- static helper = {...helper};
20
- static modules = {};
21
- static plugins = {};
22
- static config = config;
23
- static Controller = Controller;
24
- static Service = Service;
25
- static Cache = Cache;
26
- static Task = Task;
27
- static z = z;
27
+ static helper = {
28
+ bindClass,
29
+ getPackage,
30
+ loadController,
31
+ db,
32
+ z, //验证器
33
+ validator,
34
+ }; //工具类
35
+ static config = {}; //配置
36
+ //数据库
37
+
38
+ // static Router = express.Router; //路由
39
+ static Service = Service; //服务
40
+ static Controller = Controller; //控制器
41
+ static extend = {}; //组件扩展
42
+ static middleware = {}; //中间件
43
+
28
44
  constructor() {
29
45
  this.app = express();
30
46
  this.router = express.Router();
31
47
  }
32
- async init() {
33
- await this.loadConfig();
34
- await this.loadExtends();
35
- this.loadCore();
36
- this.loadDB();
37
- this.loadCors();
38
- }
39
- async loadConfig() {
40
- const configPath = path.join(Chan.config.APP_PATH, "config/index.js");
41
- if (fs.existsSync(configPath)) {
42
- const configUrl = pathToFileURL(configPath).href; // 新增转换
43
- const configModule = await import(configUrl); // 使用 URL 导入
44
- Chan.config = { ...Chan.config, ...configModule.default }; // 注意 default
45
- }
48
+
49
+ beforeStart(cb) {
50
+ cb && cb();
46
51
  }
47
52
 
48
- async loadExtends() {
49
- const extendPath = path.join(Chan.config.APP_PATH, "extend");
50
- if (fs.existsSync(extendPath)) {
51
- const files = fs.readdirSync(extendPath).filter(file => file.endsWith(".js"));
52
- for (const file of files) {
53
- const filePath = path.join(extendPath, file);
54
- const fileUrl = pathToFileURL(filePath).href; // 转换路径
55
- const helperModule = await import(fileUrl); // 使用 URL 导入
56
- Chan.helper[file.replace(".js", "")] = helperModule?.default || helperModule; // 注意 default
57
- }
58
- }
53
+ async init() {
54
+ await this.config();
55
+ await this.loadExtend(); //utils
56
+ this.loadMiddleware();
57
+ this.loadDB();
59
58
  }
60
59
 
61
- /**
62
- * @description app核心模块:日志、favicon 图标、cookie、json、url、模板引擎、静态资源
63
- */
64
- loadCore() {
65
- core(this.app, Chan.config);
60
+ //加载配置文件
61
+ async config() {
62
+ let config = await loadConfig();
63
+ Chan.config = config;
64
+ }
65
+
66
+ //加载中间件
67
+ async loadMiddleware() {
68
+ const {
69
+ views,
70
+ env,
71
+ appName,
72
+ version,
73
+ cookieKey,
74
+ JSON_LIMIT,
75
+ statics,
76
+ logger,
77
+ cors,
78
+ } = Chan.config;
79
+
80
+ log(this.app, logger);
81
+ setFavicon(this.app);
82
+ setCookie(this.app, cookieKey);
83
+ setBody(this.app, JSON_LIMIT);
84
+ setTemplate(this.app, { views, env });
85
+ setStatic(this.app, statics);
86
+ Cors(this.app, cors);
87
+ setHeader(this.app, { appName, version });
66
88
  }
67
89
 
68
90
  //数据库操作
@@ -70,135 +92,66 @@ class Chan {
70
92
  if (Chan.config?.db?.length > 0) {
71
93
  Chan.config.db.map((item, index) => {
72
94
  if (index == 0) {
73
- Chan.knex = createKnex(item);
95
+ Chan.knex = db(item);
74
96
  } else {
75
- Chan[`knex${index}`] = createKnex(item);
97
+ Chan[`knex${index}`] = db(item);
76
98
  }
77
- })
78
- Chan.Service = Service;
99
+ });
79
100
  }
80
101
  }
81
102
 
82
- //解决跨域
83
- loadCors() {
84
- Chan.config?.cors?.origin && this.app.use(cors(Chan.config.cors));
85
- }
86
-
87
- //开始启动
88
- beforeStart(cb) {
89
- cb && cb();
90
- }
91
- //启动
92
- async start(cb) {
93
- await this.init();
94
- await this.loadPlugins();
95
- await this.loadModules();
96
- await this.loadCommonRouter();
97
- cb && cb();
98
- }
99
-
100
- // 加载插件
101
- async loadPlugins() {
102
- await this.loadModules("plugins");
103
- }
104
-
105
- /**
106
- * @description 模块加载入口(路由&控制器& 服务)
107
- */
108
- async loadModules(modules = "modules") {
109
- const configPath = path.join(Chan.config.APP_PATH, modules);
103
+ async loadRouter() {
104
+ const configPath = path.join(APP_PATH, "modules");
110
105
  if (fs.existsSync(configPath)) {
111
- const dirs = loadWebToEnd(Chan.config[modules]);
112
- Chan[modules] = {};
113
-
114
- // 先加载所有服务
106
+ const dirs = loaderSort(Chan.config.modules);
115
107
  for (const item of dirs) {
116
- Chan[modules][item] = {
117
- service: {},
118
- controller: {},
119
- };
120
- await this.loadServices(modules, item); // 确保每个模块的服务加载完成
121
- }
122
-
123
- // 加载控制器和路由
124
- for (const item of dirs) {
125
- await this.loadModule(modules, item); // 确保每个模块的加载完成
108
+ let router = await importRootFile(`app/modules/${item}/router.js`);
109
+ router(this.app, this.router, Chan.config);
126
110
  }
127
111
  }
128
112
  }
129
113
 
130
- /**
131
- * @description 加载模块,包括 controller service router
132
- * @param {String} moduleName 模块名称
133
- */
134
- async loadModule(modules, moduleName) {
135
- await this.loadControllers(modules, moduleName); // 确保控制器加载完成
136
- await this.loadRoutes(modules, moduleName); // 然后加载路由
114
+ //通用路由,加载错误处理和500路由和爬虫处理
115
+ async loadCommonRouter() {
116
+ try {
117
+ let router = await importRootFile("app/router.js");
118
+ router(this.app, this.router, Chan.config);
119
+ } catch (error) {
120
+ console.log(error);
121
+ }
137
122
  }
138
123
 
139
- async loadFiles(modules, moduleName, type) {
140
- const dir = path.join(Chan.config.APP_PATH, modules, moduleName, type);
141
- if (fs.existsSync(dir)) {
142
- const files = fs.readdirSync(dir).filter(file => file.endsWith(".js"));
143
- await Promise.all(files.map(async (file) => {
144
- try {
145
- const filePath = path.join(dir, file);
146
- const fileUrl = pathToFileURL(filePath).href;
147
- const module = await import(fileUrl);
148
- const name = file.replace(".js", "");
149
- Chan[modules][moduleName][type][name] = { ...bindClass(module.default) };
150
- } catch (e) {
151
- console.error(`加载${type}模块失败: ${file}`, e);
152
- throw e;
153
- }
154
- }));
124
+ async loadExtend() {
125
+ if (fs.existsSync(EXTEND_PATH)) {
126
+ const files = fs
127
+ .readdirSync(EXTEND_PATH)
128
+ .filter((file) => file.endsWith(".js"));
129
+ for (const file of files) {
130
+ const filePath = path.join(EXTEND_PATH, file);
131
+ let helperModule = await importFile(filePath);
132
+ Chan.helper[file.replace(".js", "")] = helperModule;
133
+ }
155
134
  }
156
135
  }
157
136
 
158
- /**
159
- * @description 扫描模块下所有service
160
- * @param {*} moduleDir 模块路径
161
- * @param {*} moduleName 模块名称
162
- */
163
- async loadServices(modules, moduleName) {
164
- await this.loadFiles(modules, moduleName, "service");
165
- }
137
+ async start(cb) {
138
+ await this.init();
139
+ await this.loadRouter();
140
+ await this.loadCommonRouter();
166
141
 
167
- /**
168
- * @description 扫描模块下所有controller
169
- * @param {*} moduleDir 模块路径
170
- * @param {*} moduleName 模块名称
171
- */
172
- async loadControllers(modules, moduleName) {
173
- await this.loadFiles(modules, moduleName, "controller");
142
+ cb && cb();
174
143
  }
175
144
 
176
- /**
177
- * @description 扫描模块下所有router.js
178
- * @param {*} moduleDir 模块路径
179
- * @param {*} moduleName 模块名称
180
- */
181
- async loadRoutes(modules, moduleName) {
182
- const routersDir = path.join(Chan.config.APP_PATH, modules, moduleName, "router.js");
183
- if (fs.existsSync(routersDir)) {
184
- const routeUrl = pathToFileURL(routersDir).href; // 转换路径
185
- const routes = await import(routeUrl); // 使用 URL 导入
186
- routes.default({ router: this.router, modules: Chan[modules], app: this.app });
187
- }
188
- }
145
+ getAllRouter() {
146
+ // 获取所有路径
147
+ const routes = this.router.stack
148
+ .filter((r) => r.route) // 过滤掉中间件
149
+ .map((r) => ({
150
+ path: r.route.path,
151
+ methods: Object.keys(r.route.methods),
152
+ }));
189
153
 
190
- //通用路由,加载错误处理和500路由和爬虫处理
191
- async loadCommonRouter() {
192
- try {
193
- const baseRouterPath = path.join(Chan.config.APP_PATH, "router.js");
194
- if (fs.existsSync(baseRouterPath)) {
195
- const routerUrl = pathToFileURL(baseRouterPath).href; // 转换路径
196
- const _router = await import(routerUrl); // 使用 URL 导入
197
- _router.default(this.app, this.router, Chan.config); // 注意 default
198
- }
199
- } catch (error) {
200
- console.log(error);
201
- }
154
+ console.log(routes);
202
155
  }
203
156
 
204
157
  run(cb) {
@@ -208,5 +161,6 @@ class Chan {
208
161
  });
209
162
  }
210
163
  }
164
+
211
165
  global.Chan = Chan;
212
166
  export default Chan;
@@ -0,0 +1,4 @@
1
+ import cookieParser from "cookie-parser";
2
+ export const setCookie = (app,cookieKey) => {
3
+ app.use(cookieParser(cookieKey));
4
+ };
@@ -0,0 +1,4 @@
1
+ import cors from "cors";
2
+ export const Cors = (app,_cors) => {
3
+ app.use(cors(_cors));
4
+ };
@@ -0,0 +1,5 @@
1
+ import path from "path";
2
+ import favicon from "serve-favicon";
3
+ export const setFavicon = (app) => {
4
+ app.use(favicon(path.join(APP_PATH, "public/favicon.ico")));
5
+ };
@@ -0,0 +1,9 @@
1
+ export let setHeader = (app, { appName, version })=>{
2
+ app.use((req, res, next) => {
3
+ res.setHeader("Create-By", "Chanjs");
4
+ res.setHeader("X-Powered-By", "ChanCMS");
5
+ res.setHeader("ChanCMS", version);
6
+ res.setHeader("Server", appName);
7
+ next();
8
+ });
9
+ };
@@ -0,0 +1,21 @@
1
+ import {log} from "./log.js";
2
+ import {setCookie} from "./cookie.js";
3
+ import {setFavicon} from "./favicon.js";
4
+ import {setBody} from "./setBody.js";
5
+ import {setStatic} from "./static.js";
6
+ import {setHeader} from "./header.js";
7
+ import {setTemplate} from "./template.js";
8
+ import {validator} from "./validator.js";
9
+ import {Cors} from "./cors.js";
10
+
11
+ export {
12
+ log,
13
+ setCookie,
14
+ setFavicon,
15
+ setBody,
16
+ setStatic,
17
+ setHeader,
18
+ setTemplate,
19
+ Cors,
20
+ validator
21
+ }
@@ -0,0 +1,4 @@
1
+ import morgan from "morgan";
2
+ export const log = (app,logger) => {
3
+ app.use(morgan(logger.level));
4
+ };
@@ -0,0 +1,11 @@
1
+ import express from "express";
2
+
3
+
4
+ let setBody = function (app, JSON_LIMIT) {
5
+ app.use(express.json({ limit: JSON_LIMIT }));
6
+ app.use(express.urlencoded({ extended: false }));
7
+ };
8
+
9
+ export {
10
+ setBody
11
+ }
@@ -0,0 +1,10 @@
1
+ import express from "express";
2
+
3
+ export const setStatic = async function (app, statics) {
4
+ if (statics.length > 0) {
5
+ statics.forEach((item) => {
6
+ const { prefix, dir, maxAge } = item;
7
+ app.use(prefix, express.static(dir, { maxAge: maxAge || 0 }));
8
+ });
9
+ }
10
+ };
@@ -0,0 +1,14 @@
1
+ import '../extend/art-template.js'
2
+ export let setTemplate = (app, config) => {
3
+ const { views, env } = config;
4
+ //合并插件中的view
5
+ const all = [...views, 'app/modules/web/view'];
6
+ app.set("view options", {
7
+ debug: env === "dev",
8
+ cache: env === "prd",
9
+ minimize: true,
10
+ });
11
+ app.set("view engine", "html");
12
+ app.set("views", all);
13
+ app.engine(".html", requirejs("express-art-template"));
14
+ };
@@ -0,0 +1,23 @@
1
+ export const validator = (schemas) => (req, res, next) => {
2
+ // 分别验证 headers/params/query/body
3
+ const sections = {
4
+ headers: schemas.headers,
5
+ params: schemas.params,
6
+ query: schemas.query,
7
+ body: schemas.body
8
+ };
9
+
10
+ for (const [key, schema] of Object.entries(sections)) {
11
+ if (!schema) continue;
12
+
13
+ const result = schema.safeParse(req[key]);
14
+ if (!result.success) {
15
+ // 返回首个错误信息
16
+ const firstError = result.error.errors[0];
17
+ return res.status(400).json({ error: firstError.message });
18
+ }
19
+ // 将验证后的数据挂载到 req 对象
20
+ req[key] = result.data;
21
+ }
22
+ next();
23
+ };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "chanjs",
4
- "version": "1.2.6",
4
+ "version": "2.0.0",
5
5
  "description": "chanjs基于express5 纯js研发的轻量级mvc框架。",
6
6
  "main": "index.js",
7
7
  "module": "index.js",
@@ -11,7 +11,7 @@
11
11
  "express mysql",
12
12
  "express knex",
13
13
  "chancms",
14
- "nodejs"
14
+ "nodejs mvc"
15
15
  ],
16
16
  "engines": {
17
17
  "node": ">=20.0.0"
@@ -24,7 +24,6 @@
24
24
  "cookie-parser": "^1.4.7",
25
25
  "cors": "^2.8.5",
26
26
  "dayjs": "^1.11.13",
27
- "deepmerge": "^4.3.1",
28
27
  "express": "^5.1.0",
29
28
  "express-art-template": "^1.0.1",
30
29
  "knex": "^3.1.0",
package/utils/bind.js ADDED
@@ -0,0 +1,21 @@
1
+
2
+ /**
3
+ * @description 实例化一个类,并将该类的所有方法绑定到一个新的对象上。
4
+ * @param {Function} className - 需要实例化的类。
5
+ *@returns {Object} 包含绑定方法的对象。
6
+ */
7
+ export const bindClass = function(className) {
8
+ let obj = {};
9
+ const cls = new className();
10
+ Object.getOwnPropertyNames(cls.constructor.prototype).forEach(
11
+ (methodName) => {
12
+ if (
13
+ methodName !== "constructor" &&
14
+ typeof cls[methodName] === "function"
15
+ ) {
16
+ obj[methodName] = cls[methodName].bind(cls);
17
+ }
18
+ }
19
+ );
20
+ return obj;
21
+ }
package/utils/db.js ADDED
@@ -0,0 +1,75 @@
1
+ import knex from 'knex';
2
+ export const db = function({
3
+ client='mysql2',
4
+ host='127.0.0.1',
5
+ user='root',
6
+ password='123456',
7
+ database='test',
8
+ debug=true,
9
+ charset='utf8mb4',
10
+ min=0,
11
+ max=2,
12
+ } ) {
13
+
14
+ let config = {
15
+ client ,
16
+ connection: {
17
+ host ,
18
+ user ,
19
+ password,
20
+ database,
21
+ charset,
22
+ } ,
23
+ debug,
24
+ pool: { //默认为{min: 2, max: 10},连接池配置
25
+ min,
26
+ max,
27
+ },
28
+ log: {
29
+ warn(message) {
30
+ console.error("[knex warn]", message);
31
+ },
32
+ error(message) {
33
+ console.error("[knex error]", message);
34
+ },
35
+ debug(message) {
36
+ console.log("[knex debug]", message);
37
+ },
38
+ deprecate(message) {
39
+ console.warn("[knex deprecate]", message);
40
+ },
41
+ trace(message) {
42
+ console.log("[knex trace]", message);
43
+ },
44
+ log(message) {
45
+ console.log("[knex log]", message);
46
+ },
47
+ info(message) {
48
+ console.log("[knex info]", message);
49
+ },
50
+
51
+ },
52
+ }
53
+ return knex(config);
54
+ }
55
+
56
+ const errCode = {
57
+ "ECONNREFUSED": "数据库连接被拒绝,请检查数据库服务是否正常运行。",
58
+ "ER_ACCESS_DENIED_ERROR": "无权限访问,账号或密码错误。",
59
+ "ER_ROW_IS_REFERENCED_2": "无法删除或更新记录,存在关联数据。",
60
+ "ER_BAD_FIELD_ERROR": "SQL语句中包含无效字段,请检查查询条件或列名。",
61
+ "ER_DUP_ENTRY": "插入失败:数据重复,违反唯一性约束。",
62
+ "ER_NO_SUCH_TABLE": "操作失败:目标表不存在。",
63
+ "ETIMEOUT": "数据库操作超时,请稍后再试。"
64
+ }
65
+ const getDefaultErrorMessage = (error) => {
66
+ if (error.message.includes('syntax') ||
67
+ error.message.includes('SQL')) {
68
+ return '数据库语法错误,请检查您的查询语句。';
69
+ } else if (error.message.includes('Connection closed')) {
70
+ return '数据库连接已关闭,请重试。';
71
+ } else if (error.message.includes('permission')) {
72
+ return '数据库权限不足,请检查配置。';
73
+ }
74
+ return '数据库发生未知错误,请稍后重试。';
75
+ }
package/utils/index.js ADDED
@@ -0,0 +1,11 @@
1
+ import {bindClass} from './bind.js'
2
+ import {db} from './db.js'
3
+ import {loaderSort,getPackage,loadConfig,loadController} from './loader.js'
4
+ export {
5
+ bindClass,
6
+ db,
7
+ loaderSort,
8
+ getPackage,
9
+ loadConfig,
10
+ loadController
11
+ }
@@ -0,0 +1,63 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+
4
+ /**
5
+ *
6
+ * @param {*} module 模块目录
7
+ * @returns Array
8
+ * @description 将web模块放到最后加载
9
+ */
10
+ export const loaderSort = (modules=[])=>{
11
+ const index = modules.indexOf('web');
12
+ if (index !== -1) {
13
+ const web = modules.splice(index, 1);
14
+ modules.push(web[0]);
15
+ }
16
+ return modules;
17
+ }
18
+
19
+ export const getPackage = async function(){
20
+ let pkg = await importFile('package.json')
21
+ return pkg;
22
+ }
23
+
24
+ export const loadConfig = async function(){
25
+ let config = await importFile('app/config/index.js')
26
+ return config;
27
+ }
28
+
29
+
30
+ /**
31
+ * 加载指定模块名下的所有控制器文件
32
+ * @param {string} moduleName - 模块名称
33
+ * @returns {Promise<Object>} - 控制器对象
34
+ */
35
+ export const loadController = async function (moduleName) {
36
+ const controller = {};
37
+
38
+ const dir = path.join(MODULES_PATH, moduleName,'controller');
39
+
40
+ if (!fs.existsSync(dir)) {
41
+ console.warn(`模块路径不存在,跳过加载控制器: ${dir}`);
42
+ return controller;
43
+ }
44
+
45
+ const files = fs.readdirSync(dir).filter(file => file.endsWith('.js'));
46
+ for (const file of files) {
47
+ const filePath = path.join(dir, file);
48
+ const name = file.replace(/\.js$/i, ''); // 安全处理 .js 后缀
49
+ try {
50
+ const module = await importFile(filePath);
51
+ controller[name] = { ...module};
52
+ } catch (e) {
53
+ console.error(`加载控制器失败: ${filePath}`, e);
54
+ // 可选:抛出错误或继续加载其他文件
55
+ // throw e;
56
+ }
57
+ }
58
+
59
+ return controller;
60
+ };
61
+
62
+
63
+
@@ -0,0 +1,26 @@
1
+ export let success = (data) => {
2
+ return {
3
+ success: true,
4
+ msg: "操作成功",
5
+ code: 200,
6
+ data,
7
+ };
8
+ };
9
+
10
+ export let fail = (data, code = 201) => {
11
+ return {
12
+ success: false,
13
+ msg: "操作失败",
14
+ code,
15
+ data,
16
+ };
17
+ };
18
+
19
+ export let err = (data = {}, code = 500) => {
20
+ return {
21
+ success: false,
22
+ msg: "系统异常",
23
+ code,
24
+ data,
25
+ };
26
+ };