chanjs 1.0.45 → 1.1.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/core/config.js +25 -17
- package/core/helper.js +4 -4
- package/core/lib/cache.js +1 -1
- package/core/lib/controller.js +1 -1
- package/core/lib/extend.js +5 -3
- package/core/lib/index.js +11 -10
- package/core/lib/service.js +38 -2
- package/core/lib/view.js +7 -4
- package/index.js +73 -78
- package/package.json +3 -1
package/core/config.js
CHANGED
@@ -1,33 +1,40 @@
|
|
1
|
-
|
1
|
+
import path from 'path';
|
2
|
+
import { createRequire } from 'module';
|
3
|
+
import { pathToFileURL } from 'url';
|
2
4
|
|
3
5
|
/**
|
4
|
-
* @description
|
6
|
+
* @description 根目录(ESM 兼容写法)
|
5
7
|
*/
|
8
|
+
const __dirname = path.dirname(new URL(import.meta.url).pathname);
|
9
|
+
console.log(__dirname)
|
6
10
|
const ROOT_PATH = process.cwd();
|
7
|
-
|
8
|
-
|
11
|
+
|
12
|
+
// 异步读取 package.json(ESM 标准方式)
|
13
|
+
const packageUrl = pathToFileURL(path.join(ROOT_PATH, 'package.json')).href;
|
14
|
+
const { default: pkg } = await import(packageUrl,{ assert: { type: 'json' } });
|
15
|
+
const { version = '1.0.0', author = '明空' } = pkg;
|
9
16
|
|
10
17
|
/**
|
11
18
|
* @description 程序目录
|
12
19
|
*/
|
13
20
|
const APP_PATH = path.join(ROOT_PATH, 'app');
|
14
21
|
|
15
|
-
|
16
|
-
JSON_LIMIT:"100kb",
|
17
|
-
logger
|
22
|
+
const baseConfig = {
|
23
|
+
JSON_LIMIT: "100kb",
|
24
|
+
logger: {
|
18
25
|
level: 'dev',
|
19
26
|
},
|
20
27
|
version,
|
21
28
|
author,
|
22
|
-
env:'dev',
|
23
|
-
template:'default',
|
24
|
-
views:[],
|
25
|
-
static:[{
|
29
|
+
env: 'dev',
|
30
|
+
template: 'default',
|
31
|
+
views: [],
|
32
|
+
static: [{
|
26
33
|
prefix: "/public/",
|
27
34
|
dir: ["app/public"],
|
28
35
|
maxAge: 0,
|
29
36
|
}],
|
30
|
-
database:{
|
37
|
+
database: {
|
31
38
|
client: "mysql2",
|
32
39
|
host: "localhost",
|
33
40
|
port: "3306",
|
@@ -36,10 +43,11 @@ let config = {
|
|
36
43
|
database: "chancms",
|
37
44
|
charset: "utf8mb4",
|
38
45
|
}
|
39
|
-
}
|
46
|
+
};
|
40
47
|
|
41
|
-
|
48
|
+
// 导出配置对象
|
49
|
+
export default {
|
42
50
|
ROOT_PATH,
|
43
|
-
APP_PATH,
|
44
|
-
...
|
45
|
-
}
|
51
|
+
APP_PATH,
|
52
|
+
...baseConfig
|
53
|
+
};
|
package/core/helper.js
CHANGED
@@ -1,11 +1,11 @@
|
|
1
|
-
|
1
|
+
import knex from 'knex';
|
2
2
|
|
3
3
|
/**
|
4
4
|
* @description 实例化一个类,并将该类的所有方法绑定到一个新的对象上。
|
5
5
|
* @param {Function} className - 需要实例化的类。
|
6
6
|
*@returns {Object} 包含绑定方法的对象。
|
7
7
|
*/
|
8
|
-
|
8
|
+
export const bindClass = function(className) {
|
9
9
|
let obj = {};
|
10
10
|
const cls = new className();
|
11
11
|
Object.getOwnPropertyNames(cls.constructor.prototype).forEach(
|
@@ -22,7 +22,7 @@ exports.bindClass = function(className) {
|
|
22
22
|
}
|
23
23
|
|
24
24
|
|
25
|
-
|
25
|
+
export const createKnex = function(opt) {
|
26
26
|
let config = {
|
27
27
|
host:"localhost",
|
28
28
|
port:"3306",
|
@@ -63,7 +63,7 @@ exports.createKnex = function(opt) {
|
|
63
63
|
* @returns Array
|
64
64
|
* @description 将web模块放到最后加载
|
65
65
|
*/
|
66
|
-
|
66
|
+
export const loadWebToEnd = function(module=[]){
|
67
67
|
const index = module.indexOf('web');
|
68
68
|
if (index !== -1) {
|
69
69
|
const web = module.splice(index, 1);
|
package/core/lib/cache.js
CHANGED
package/core/lib/controller.js
CHANGED
package/core/lib/extend.js
CHANGED
@@ -1,8 +1,10 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
import express from "express";
|
2
|
+
export default (app)=>{
|
3
3
|
app.plus = {
|
4
4
|
setStatic:function ({prefix,dir,maxAge}) {
|
5
5
|
app.use(prefix, express.static(dir, { maxAge: maxAge || 0 }));
|
6
6
|
}
|
7
7
|
}
|
8
|
-
}
|
8
|
+
}
|
9
|
+
|
10
|
+
|
package/core/lib/index.js
CHANGED
@@ -1,11 +1,12 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
1
|
+
import express from "express";
|
2
|
+
import cookieParser from "cookie-parser";
|
3
|
+
import favicon from "serve-favicon";
|
4
|
+
|
5
|
+
import morgan from "morgan";
|
6
|
+
import path from "path";
|
7
|
+
import view from "./view.js";
|
8
|
+
export default async function (app, config) {
|
9
|
+
const { logger, APP_PATH, cookieKey, statics, JSON_LIMIT, appName, version } =
|
9
10
|
config;
|
10
11
|
|
11
12
|
app.use(morgan(logger.level));
|
@@ -14,8 +15,8 @@ module.exports = async function (app, config) {
|
|
14
15
|
app.use(express.json({ limit: JSON_LIMIT }));
|
15
16
|
app.use(express.urlencoded({ extended: false }));
|
16
17
|
view(app, config);
|
17
|
-
if (
|
18
|
-
|
18
|
+
if (statics.length > 0) {
|
19
|
+
statics.forEach((item) => {
|
19
20
|
const { prefix, dir, maxAge } = item;
|
20
21
|
app.use(prefix, express.static(dir, { maxAge: maxAge || 0 }));
|
21
22
|
});
|
package/core/lib/service.js
CHANGED
@@ -105,6 +105,43 @@ async update({query, params} = {}) {
|
|
105
105
|
}
|
106
106
|
|
107
107
|
|
108
|
+
/**
|
109
|
+
* @description 批量更新多条记录(基于事务保证原子性)
|
110
|
+
* @param {Array<{query: Object, params: Object}>} updates - 更新操作数组,每个元素包含查询条件和更新内容
|
111
|
+
* @returns {Promise<{ affectedRows: number[] }>} 返回每个操作影响的行数数组
|
112
|
+
*/
|
113
|
+
async updateMany(updates = []) {
|
114
|
+
// 参数合法性校验
|
115
|
+
if (!Array.isArray(updates)) {
|
116
|
+
throw new Error('参数必须为数组格式');
|
117
|
+
}
|
118
|
+
|
119
|
+
// 获取事务对象
|
120
|
+
const trx = await this.knex.transaction();
|
121
|
+
|
122
|
+
try {
|
123
|
+
const affectedRows = [];
|
124
|
+
// 循环处理每个更新操作
|
125
|
+
for (const { query, params } of updates) {
|
126
|
+
// 执行单个更新操作,使用事务对象替换原有knex实例
|
127
|
+
const result = await trx(this.model)
|
128
|
+
.where(query)
|
129
|
+
.update(params);
|
130
|
+
affectedRows.push(result);
|
131
|
+
}
|
132
|
+
// 提交事务
|
133
|
+
await trx.commit();
|
134
|
+
// 返回影响行数数组(与入参顺序一致)
|
135
|
+
return { affectedRows };
|
136
|
+
} catch (err) {
|
137
|
+
// 回滚事务
|
138
|
+
await trx.rollback();
|
139
|
+
// 错误向上抛出,由调用者处理
|
140
|
+
throw err;
|
141
|
+
}
|
142
|
+
}
|
143
|
+
|
144
|
+
|
108
145
|
|
109
146
|
|
110
147
|
/**
|
@@ -159,5 +196,4 @@ async update({query, params} = {}) {
|
|
159
196
|
}
|
160
197
|
|
161
198
|
}
|
162
|
-
|
163
|
-
module.exports = BaseService;
|
199
|
+
export default BaseService;
|
package/core/lib/view.js
CHANGED
@@ -1,7 +1,10 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
1
|
+
import path from "path";
|
2
|
+
import dayjs from 'dayjs';
|
3
|
+
import { createRequire } from 'module';
|
4
|
+
|
5
|
+
const require = createRequire(import.meta.url);
|
4
6
|
|
7
|
+
const template = require("art-template");
|
5
8
|
// 注册 dateFormat 函数
|
6
9
|
template.defaults.imports.dateFormat = function (date, format) {
|
7
10
|
if (!date) {
|
@@ -20,7 +23,7 @@ template.defaults.imports.dateFormat = function (date, format) {
|
|
20
23
|
return date.format(format);
|
21
24
|
};
|
22
25
|
|
23
|
-
|
26
|
+
export default (app, config) => {
|
24
27
|
const { APP_PATH, views, env } = config;
|
25
28
|
//合并插件中的view
|
26
29
|
const all = [...views, 'app/modules/web/view'];
|
package/index.js
CHANGED
@@ -1,14 +1,17 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
1
|
+
import express from "express";
|
2
|
+
import path from "path";
|
3
|
+
import fs from "fs";
|
4
|
+
import cors from "cors";
|
5
|
+
import { pathToFileURL } from 'url'; // 新增顶部导入
|
6
|
+
import config from "./core/config.js";
|
7
|
+
import {bindClass,createKnex,loadWebToEnd} from "./core/helper.js";
|
8
|
+
|
9
|
+
import Controller from "./core/lib/controller.js";
|
10
|
+
|
11
|
+
import Service from "./core/lib/service.js";
|
12
|
+
import cache from './core/lib/cache.js';
|
13
|
+
import core from "./core/lib/index.js";
|
14
|
+
import extend from "./core/lib/extend.js";
|
12
15
|
/**
|
13
16
|
* @description 基于express封装的mvc框架,遵循约定优于配置原则
|
14
17
|
*/
|
@@ -21,45 +24,38 @@ class Chan {
|
|
21
24
|
static Controller = Controller;
|
22
25
|
|
23
26
|
constructor() {
|
24
|
-
this.
|
27
|
+
this.app = express();
|
28
|
+
this.router = express.Router();
|
25
29
|
}
|
26
30
|
|
27
|
-
init() {
|
28
|
-
const startTime = performance.now();
|
29
|
-
this.app = express();
|
31
|
+
async init() {
|
30
32
|
extend(this.app);
|
31
|
-
this.
|
32
|
-
this.
|
33
|
-
this.loadExtends();
|
33
|
+
await this.loadConfig();
|
34
|
+
await this.loadExtends();
|
34
35
|
this.loadCore();
|
35
36
|
this.loadKnex();
|
36
37
|
this.loadCors();
|
37
|
-
// 记录结束时间
|
38
|
-
const endTime = performance.now();
|
39
|
-
console.log(`Chanjs init: ${endTime - startTime} ms`);
|
40
38
|
}
|
41
39
|
|
42
40
|
|
43
|
-
|
44
|
-
loadConfig() {
|
41
|
+
async loadConfig() {
|
45
42
|
const configPath = path.join(Chan.config.APP_PATH, "config/index.js");
|
46
43
|
if (fs.existsSync(configPath)) {
|
47
|
-
const
|
48
|
-
|
44
|
+
const configUrl = pathToFileURL(configPath).href; // 新增转换
|
45
|
+
const configModule = await import(configUrl); // 使用 URL 导入
|
46
|
+
Chan.config = { ...Chan.config, ...configModule.default }; // 注意 default
|
49
47
|
}
|
50
48
|
}
|
51
49
|
|
52
|
-
loadExtends() {
|
50
|
+
async loadExtends() {
|
53
51
|
const extendPath = path.join(Chan.config.APP_PATH, "extend");
|
54
52
|
if (fs.existsSync(extendPath)) {
|
55
|
-
|
56
|
-
|
57
|
-
.
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
const fileName = file.replace(".js", "");
|
62
|
-
Chan.helper[fileName] = helper;
|
53
|
+
const files = fs.readdirSync(extendPath).filter(file => file.endsWith(".js"));
|
54
|
+
for (const file of files) {
|
55
|
+
const filePath = path.join(extendPath, file);
|
56
|
+
const fileUrl = pathToFileURL(filePath).href; // 转换路径
|
57
|
+
const helperModule = await import(fileUrl); // 使用 URL 导入
|
58
|
+
Chan.helper[file.replace(".js", "")] = helperModule?.default || helperModule; // 注意 default
|
63
59
|
}
|
64
60
|
}
|
65
61
|
}
|
@@ -90,14 +86,10 @@ class Chan {
|
|
90
86
|
cb && cb();
|
91
87
|
}
|
92
88
|
//启动
|
93
|
-
start(cb) {
|
94
|
-
|
95
|
-
this.loadModules();
|
96
|
-
this.
|
97
|
-
this.loadCommonRouter();
|
98
|
-
// 记录结束时间
|
99
|
-
const endTime = performance.now();
|
100
|
-
console.log(`Chanjs load modules: ${endTime - startTime} ms`);
|
89
|
+
async start(cb) {
|
90
|
+
await this.loadPlugins();
|
91
|
+
await this.loadModules();
|
92
|
+
await this.loadCommonRouter();
|
101
93
|
cb && cb();
|
102
94
|
}
|
103
95
|
|
@@ -107,63 +99,69 @@ class Chan {
|
|
107
99
|
}
|
108
100
|
|
109
101
|
// 加载插件
|
110
|
-
loadPlugins() {
|
111
|
-
|
102
|
+
async loadPlugins() {
|
103
|
+
await this.loadModules("plugins");
|
112
104
|
}
|
113
105
|
|
114
106
|
/**
|
115
107
|
* @description 模块加载入口(路由&控制器& 服务)
|
116
108
|
*/
|
117
|
-
loadModules(modules = "modules") {
|
109
|
+
async loadModules(modules = "modules") {
|
118
110
|
const configPath = path.join(Chan.config.APP_PATH, modules);
|
111
|
+
console.log('configPath',configPath);
|
119
112
|
if (fs.existsSync(configPath)) {
|
120
|
-
//模块名称
|
121
113
|
const dirs = loadWebToEnd(Chan.config[modules]);
|
114
|
+
console.log('dirs',dirs);
|
122
115
|
Chan[modules] = {};
|
116
|
+
|
123
117
|
// 先加载所有服务
|
124
|
-
|
118
|
+
for (const item of dirs) {
|
125
119
|
Chan[modules][item] = {
|
126
120
|
service: {},
|
127
121
|
controller: {},
|
128
122
|
};
|
129
|
-
this.loadServices(modules, item);
|
130
|
-
}
|
131
|
-
|
123
|
+
await this.loadServices(modules, item); // 确保每个模块的服务加载完成
|
124
|
+
}
|
125
|
+
|
132
126
|
// 加载控制器和路由
|
133
|
-
|
134
|
-
this.loadModule(modules, item);
|
135
|
-
}
|
127
|
+
for (const item of dirs) {
|
128
|
+
await this.loadModule(modules, item); // 确保每个模块的加载完成
|
129
|
+
}
|
136
130
|
}
|
137
131
|
}
|
132
|
+
|
138
133
|
|
139
134
|
/**
|
140
135
|
* @description 加载模块,包括 controller service router
|
141
136
|
* @param {String} moduleName 模块名称
|
142
137
|
*/
|
143
|
-
loadModule(modules, moduleName) {
|
144
|
-
this.loadControllers(modules, moduleName);
|
145
|
-
this.loadRoutes(modules, moduleName);
|
138
|
+
async loadModule(modules, moduleName) {
|
139
|
+
await this.loadControllers(modules, moduleName); // 确保控制器加载完成
|
140
|
+
await this.loadRoutes(modules, moduleName); // 然后加载路由
|
146
141
|
}
|
147
142
|
|
148
|
-
loadFiles(modules, moduleName, type) {
|
143
|
+
async loadFiles(modules, moduleName, type) {
|
149
144
|
const dir = path.join(Chan.config.APP_PATH, modules, moduleName, type);
|
150
145
|
if (fs.existsSync(dir)) {
|
151
146
|
const files = fs.readdirSync(dir).filter(file => file.endsWith(".js"));
|
152
|
-
|
153
|
-
const
|
147
|
+
for (const file of files) { // 使用 for...of 确保异步操作顺序
|
148
|
+
const filePath = path.join(dir, file);
|
149
|
+
const fileUrl = pathToFileURL(filePath).href;
|
150
|
+
const module = await import(fileUrl);
|
154
151
|
const name = file.replace(".js", "");
|
155
|
-
Chan[modules][moduleName][type][name] = { ...bindClass(module) };
|
156
|
-
}
|
152
|
+
Chan[modules][moduleName][type][name] = { ...bindClass(module.default) };
|
153
|
+
}
|
157
154
|
}
|
158
155
|
}
|
156
|
+
|
159
157
|
|
160
158
|
/**
|
161
159
|
* @description 扫描模块下所有service
|
162
160
|
* @param {*} moduleDir 模块路径
|
163
161
|
* @param {*} moduleName 模块名称
|
164
162
|
*/
|
165
|
-
loadServices(modules, moduleName) {
|
166
|
-
|
163
|
+
async loadServices(modules, moduleName) {
|
164
|
+
await this.loadFiles(modules, moduleName, "service");
|
167
165
|
}
|
168
166
|
|
169
167
|
/**
|
@@ -171,8 +169,8 @@ class Chan {
|
|
171
169
|
* @param {*} moduleDir 模块路径
|
172
170
|
* @param {*} moduleName 模块名称
|
173
171
|
*/
|
174
|
-
loadControllers(modules, moduleName) {
|
175
|
-
this.loadFiles(modules, moduleName, "controller");
|
172
|
+
async loadControllers(modules, moduleName) {
|
173
|
+
await this.loadFiles(modules, moduleName, "controller");
|
176
174
|
}
|
177
175
|
|
178
176
|
/**
|
@@ -180,26 +178,23 @@ class Chan {
|
|
180
178
|
* @param {*} moduleDir 模块路径
|
181
179
|
* @param {*} moduleName 模块名称
|
182
180
|
*/
|
183
|
-
loadRoutes(modules, moduleName) {
|
184
|
-
const routersDir = path.join(
|
185
|
-
Chan.config.APP_PATH,
|
186
|
-
modules,
|
187
|
-
moduleName,
|
188
|
-
"router.js"
|
189
|
-
);
|
181
|
+
async loadRoutes(modules, moduleName) {
|
182
|
+
const routersDir = path.join(Chan.config.APP_PATH, modules, moduleName, "router.js");
|
190
183
|
if (fs.existsSync(routersDir)) {
|
191
|
-
const
|
192
|
-
routes
|
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 });
|
193
187
|
}
|
194
188
|
}
|
195
189
|
|
196
190
|
//通用路由,加载错误处理和500路由和爬虫处理
|
197
|
-
loadCommonRouter() {
|
191
|
+
async loadCommonRouter() {
|
198
192
|
try {
|
199
193
|
const baseRouterPath = path.join(Chan.config.APP_PATH, "router.js");
|
200
194
|
if (fs.existsSync(baseRouterPath)) {
|
201
|
-
const
|
202
|
-
_router
|
195
|
+
const routerUrl = pathToFileURL(baseRouterPath).href; // 转换路径
|
196
|
+
const _router = await import(routerUrl); // 使用 URL 导入
|
197
|
+
_router.default(this.app, this.router, Chan.config); // 注意 default
|
203
198
|
}
|
204
199
|
} catch (error) {
|
205
200
|
console.log(error);
|
@@ -214,4 +209,4 @@ class Chan {
|
|
214
209
|
}
|
215
210
|
}
|
216
211
|
global.Chan = Chan;
|
217
|
-
|
212
|
+
export default Chan;
|
package/package.json
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
{
|
2
|
+
"type": "module",
|
2
3
|
"name": "chanjs",
|
3
|
-
"version": "1.0
|
4
|
+
"version": "1.1.0",
|
4
5
|
"description": "chanjs基于express 纯js研发的轻量级mvc框架。",
|
5
6
|
"main": "index.js",
|
6
7
|
"module": "index.js",
|
@@ -12,6 +13,7 @@
|
|
12
13
|
"chancms",
|
13
14
|
"nodejs"
|
14
15
|
],
|
16
|
+
"engines": { "node": ">=20.0.0" },
|
15
17
|
"author": "明空",
|
16
18
|
"license": "ISC",
|
17
19
|
"dependencies": {
|