diff --git a/.qoder/quests/clean-src-directory.md b/.qoder/quests/clean-src-directory.md new file mode 100644 index 0000000..4f62a59 --- /dev/null +++ b/.qoder/quests/clean-src-directory.md @@ -0,0 +1,330 @@ +# src 目录整理设计 + +## 概述 + +本设计旨在优化 koa3-demo 项目的 src 目录结构,在保持现有 MVC 架构不变的前提下,通过最小化的代码改动来提升代码组织的清晰度和可维护性。 + +## 当前目录结构分析 + +### 现状问题 +- 目录层级较为扁平,业务模块分散 +- controllers 按类型(Api/Page)分组,但缺乏按业务领域组织 +- services 和 models 分离在不同的顶级目录 +- 缺乏统一的模块组织规范 + +### 优势保持 +- MVC 架构清晰,分层明确 +- 中间件系统完善 +- 路由自动发现机制完整 +- 数据库操作层设计合理 + +## 整理方案 + +### 目标架构 + +采用**领域驱动的模块化架构**,在保持 MVC 分层的同时,按业务领域组织代码: + +```mermaid +graph TB + subgraph "src 目录结构" + A[src/] --> B[app/] + A --> C[modules/] + A --> D[shared/] + A --> E[presentation/] + + B --> B1[config/] + B --> B2[providers/] + B --> B3[bootstrap/] + + C --> C1[auth/] + C --> C2[article/] + C --> C3[user/] + C --> C4[site-config/] + + D --> D1[utils/] + D --> D2[constants/] + D --> D3[helpers/] + + E --> E1[middlewares/] + E --> E2[routes/] + E --> E3[views/] + + subgraph "模块内部结构" + C1 --> C1A[controllers/] + C1 --> C1B[services/] + C1 --> C1C[models/] + C1 --> C1D[validators/] + end + end +``` + +### 详细目录规划 + +#### 1. app/ - 应用核心 +``` +app/ +├── config/ # 配置管理 +│ ├── index.js # 当前 src/config/index.js +│ └── database.js # 数据库配置(从 db/ 提取) +├── providers/ # 服务提供者 +│ ├── DatabaseProvider.js # 数据库连接管理 +│ └── index.js # 提供者注册 +└── bootstrap/ # 应用引导 + ├── app.js # 应用实例(当前 global.js) + ├── logger.js # 日志配置 + └── index.js # 引导入口 +``` + +#### 2. modules/ - 业务模块 +``` +modules/ +├── auth/ # 认证模块 +│ ├── controllers/ +│ │ ├── AuthController.js # API 控制器 +│ │ └── AuthPageController.js # 页面控制器 +│ ├── services/ +│ │ └── AuthService.js # 从 userService.js 提取认证相关 +│ ├── models/ +│ │ └── UserModel.js # 用户模型 +│ └── validators/ +│ └── AuthValidator.js # 认证验证规则 +├── user/ # 用户管理模块 +│ ├── controllers/ +│ │ └── ProfileController.js # 用户资料控制器 +│ ├── services/ +│ │ └── UserService.js # 用户业务服务 +│ └── models/ +│ └── UserModel.js # 用户模型(共享) +├── article/ # 文章管理模块 +│ ├── controllers/ +│ │ └── ArticleController.js +│ ├── services/ +│ │ └── ArticleService.js +│ └── models/ +│ └── ArticleModel.js +├── bookmark/ # 书签管理模块 +│ ├── services/ +│ │ └── BookmarkService.js +│ └── models/ +│ └── BookmarkModel.js +├── site-config/ # 站点配置模块 +│ ├── services/ +│ │ └── SiteConfigService.js +│ └── models/ +│ └── SiteConfigModel.js +└── common/ # 通用模块 + ├── controllers/ + │ ├── ApiController.js + │ ├── StatusController.js + │ └── UploadController.js + └── services/ + └── JobService.js +``` + +#### 3. shared/ - 共享资源 +``` +shared/ +├── utils/ # 工具函数 +│ ├── error/ +│ ├── router/ +│ ├── bcrypt.js +│ ├── helper.js +│ └── scheduler.js +├── constants/ # 常量定义 +│ ├── errors.js +│ └── status.js +└── base/ # 基础类 + ├── BaseController.js + ├── BaseService.js + └── BaseModel.js +``` + +#### 4. presentation/ - 表现层 +``` +presentation/ +├── middlewares/ # 中间件(当前 middlewares/) +├── routes/ # 路由管理 +│ ├── api.js # API 路由 +│ ├── web.js # 页面路由 +│ └── index.js # 路由注册 +└── views/ # 视图模板(当前 views/) +``` + +#### 5. infrastructure/ - 基础设施 +``` +infrastructure/ +├── database/ # 数据库相关 +│ ├── migrations/ # 迁移文件 +│ ├── seeds/ # 种子数据 +│ ├── docs/ # 数据库文档 +│ └── index.js # 数据库连接 +└── jobs/ # 定时任务 + ├── exampleJob.js + └── index.js +``` + +## 迁移策略 + +### 阶段一:创建新目录结构 +1. 创建 `app/`, `modules/`, `shared/`, `presentation/`, `infrastructure/` 目录 +2. 建立各模块的子目录结构 + +### 阶段二:文件迁移与重组 +1. **配置文件迁移** + - `config/` → `app/config/` + - 从 `db/index.js` 提取数据库配置到 `app/config/database.js` + +2. **核心文件重组** + - `global.js` → `app/bootstrap/app.js` + - `logger.js` → `app/bootstrap/logger.js` + - 创建 `app/bootstrap/index.js` 统一引导 + +3. **业务模块化** + - 按业务领域创建模块目录 + - 将相关的 controllers, services, models 组织到对应模块 + +4. **基础设施迁移** + - `db/` → `infrastructure/database/` + - `jobs/` → `infrastructure/jobs/` + - `middlewares/` → `presentation/middlewares/` + - `views/` → `presentation/views/` + +### 阶段三:更新引用路径 +1. 更新 `main.js` 中的导入路径 +2. 更新中间件注册路径 +3. 更新服务间的依赖引用 +4. 更新路由自动发现配置 + +## 实现细节 + +### 模块内 MVC 组织 + +每个业务模块内部采用标准的 MVC 分层: + +```mermaid +graph LR + subgraph "auth 模块" + AC[AuthController] --> AS[AuthService] + AS --> UM[UserModel] + APC[AuthPageController] --> AS + end + + subgraph "共享层" + BC[BaseController] + BS[BaseService] + BM[BaseModel] + end + + AC -.-> BC + AS -.-> BS + UM -.-> BM +``` + +### 路径映射表 + +| 当前路径 | 新路径 | 说明 | +|---------|-------|------| +| `src/config/` | `src/app/config/` | 配置管理 | +| `src/global.js` | `src/app/bootstrap/app.js` | 应用实例 | +| `src/logger.js` | `src/app/bootstrap/logger.js` | 日志配置 | +| `src/controllers/Api/AuthController.js` | `src/modules/auth/controllers/AuthController.js` | 认证API控制器 | +| `src/controllers/Page/AuthPageController.js` | `src/modules/auth/controllers/AuthPageController.js` | 认证页面控制器 | +| `src/services/userService.js` | `src/modules/auth/services/AuthService.js` + `src/modules/user/services/UserService.js` | 拆分认证和用户服务 | +| `src/db/models/UserModel.js` | `src/modules/auth/models/UserModel.js` | 用户模型 | +| `src/middlewares/` | `src/presentation/middlewares/` | 中间件 | +| `src/views/` | `src/presentation/views/` | 视图模板 | +| `src/db/migrations/` | `src/infrastructure/database/migrations/` | 数据库迁移 | +| `src/jobs/` | `src/infrastructure/jobs/` | 定时任务 | + +### 导入路径更新 + +#### main.js 更新示例 +```javascript +// 原始导入 +import { app } from "./global" +import { logger } from "./logger.js" +import "./jobs/index.js" +import LoadMiddlewares from "./middlewares/install.js" + +// 更新后导入 +import { app } from "./app/bootstrap/app.js" +import { logger } from "./app/bootstrap/logger.js" +import "./infrastructure/jobs/index.js" +import LoadMiddlewares from "./presentation/middlewares/install.js" +``` + +### 自动路由发现适配 + +更新路由扫描配置以适配新的模块结构: + +```javascript +// 扫描路径配置 +const scanPaths = [ + 'src/modules/*/controllers/**/*.js', + 'src/modules/common/controllers/**/*.js' +]; +``` + +## 文件操作清单 + +### 需要移动的文件 +- [ ] `src/config/` → `src/app/config/` +- [ ] `src/global.js` → `src/app/bootstrap/app.js` +- [ ] `src/logger.js` → `src/app/bootstrap/logger.js` +- [ ] `src/db/` → `src/infrastructure/database/` +- [ ] `src/jobs/` → `src/infrastructure/jobs/` +- [ ] `src/middlewares/` → `src/presentation/middlewares/` +- [ ] `src/views/` → `src/presentation/views/` +- [ ] `src/utils/` → `src/shared/utils/` + +### 需要按模块重组的文件 +- [ ] 认证相关:`AuthController.js`, `AuthPageController.js`, `userService.js`(部分), `UserModel.js` +- [ ] 用户管理:`ProfileController.js`, `userService.js`(部分) +- [ ] 文章管理:`ArticleController.js`, `ArticleService.js`, `ArticleModel.js` +- [ ] 书签管理:`BookmarkService.js`, `BookmarkModel.js` +- [ ] 站点配置:`SiteConfigService.js`, `SiteConfigModel.js` +- [ ] 通用功能:`ApiController.js`, `StatusController.js`, `UploadController.js`, `JobController.js` + +### 需要更新导入的文件 +- [ ] `src/main.js` +- [ ] `src/presentation/middlewares/install.js` +- [ ] 各个控制器文件中的服务导入 +- [ ] 各个服务文件中的模型导入 + +## 预期效果 + +### 代码组织改善 +1. **业务内聚性**:相关的 controller、service、model 集中在同一模块 +2. **职责清晰**:每个模块负责特定的业务领域 +3. **依赖明确**:模块间依赖关系更加清晰可见 + +### 开发体验提升 +1. **文件查找**:按业务功能快速定位相关文件 +2. **代码维护**:修改某个功能时,相关文件集中在一个目录 +3. **团队协作**:不同开发者可以专注于不同的业务模块 + +### 架构扩展性 +1. **新增模块**:按照统一规范快速创建新的业务模块 +2. **功能拆分**:大模块可以进一步拆分成子模块 +3. **微服务准备**:为将来可能的微服务拆分做好准备 + +## 风险评估与注意事项 + +### 低风险操作 +- 目录创建和文件移动 +- 路径引用更新 +- 配置文件调整 + +### 需要谨慎的操作 +- 路由自动发现逻辑调整 +- 中间件注册顺序维护 +- 数据库连接管理迁移 + +### 测试验证点 +1. 应用启动正常 +2. 路由注册成功 +3. 数据库连接正常 +4. 中间件功能完整 +5. 各业务模块功能正常 + +本方案通过最小化改动实现目录结构优化,保持原有架构优势的同时提升代码组织质量,为项目的长期维护和扩展奠定良好基础。 \ No newline at end of file diff --git a/src/app/bootstrap/app.js b/src/app/bootstrap/app.js new file mode 100644 index 0000000..0d9b2ab --- /dev/null +++ b/src/app/bootstrap/app.js @@ -0,0 +1,27 @@ +/** + * 应用实例 + * + * 创建和配置 Koa 应用实例 + */ + +import Koa from "koa"; +import { logger } from "./logger.js"; +import { validateEnvironment } from "../../shared/utils/envValidator.js"; + +// 启动前验证环境变量 +if (!validateEnvironment()) { + logger.error("环境变量验证失败,应用退出"); + process.exit(1); +} + +const app = new Koa({ asyncLocalStorage: true }); + +app.keys = []; + +// SESSION_SECRET 已通过环境变量验证确保存在 +process.env.SESSION_SECRET.split(",").forEach(secret => { + app.keys.push(secret.trim()); +}); + +export { app }; +export default app; \ No newline at end of file diff --git a/src/app/bootstrap/index.js b/src/app/bootstrap/index.js new file mode 100644 index 0000000..25ee3da --- /dev/null +++ b/src/app/bootstrap/index.js @@ -0,0 +1,22 @@ +/** + * 应用引导索引文件 + * + * 统一导出应用引导相关模块 + */ + +import app from "./app.js"; +import { logger, jobLogger, errorLogger } from "./logger.js"; + +export { + app, + logger, + jobLogger, + errorLogger +}; + +export default { + app, + logger, + jobLogger, + errorLogger +}; \ No newline at end of file diff --git a/src/logger.js b/src/app/bootstrap/logger.js similarity index 94% rename from src/logger.js rename to src/app/bootstrap/logger.js index 06392df..c0523f6 100644 --- a/src/logger.js +++ b/src/app/bootstrap/logger.js @@ -1,3 +1,8 @@ +/** + * 日志配置 + * + * 配置和导出日志记录器 + */ import log4js from "log4js"; @@ -60,4 +65,4 @@ log4js.configure({ // 导出常用 logger 实例,便于直接引用 export const logger = log4js.getLogger(); // default export const jobLogger = log4js.getLogger('jobs'); -export const errorLogger = log4js.getLogger('error'); +export const errorLogger = log4js.getLogger('error'); \ No newline at end of file diff --git a/src/app/config/database.js b/src/app/config/database.js new file mode 100644 index 0000000..ac3fcc8 --- /dev/null +++ b/src/app/config/database.js @@ -0,0 +1,55 @@ +/** + * 数据库配置文件 + * + * 提供数据库连接配置和相关设置 + */ + +const environment = process.env.NODE_ENV || "development"; + +// 数据库配置映射 +const databaseConfig = { + development: { + client: "sqlite3", + connection: { + filename: "./database/development.sqlite3" + }, + useNullAsDefault: true, + migrations: { + directory: "./src/infrastructure/database/migrations" + }, + seeds: { + directory: "./src/infrastructure/database/seeds" + } + }, + + test: { + client: "sqlite3", + connection: { + filename: ":memory:" + }, + useNullAsDefault: true, + migrations: { + directory: "./src/infrastructure/database/migrations" + }, + seeds: { + directory: "./src/infrastructure/database/seeds" + } + }, + + production: { + client: "sqlite3", + connection: { + filename: process.env.DATABASE_URL || "./database/production.sqlite3" + }, + useNullAsDefault: true, + migrations: { + directory: "./src/infrastructure/database/migrations" + }, + seeds: { + directory: "./src/infrastructure/database/seeds" + } + } +}; + +export default databaseConfig[environment]; +export { databaseConfig }; \ No newline at end of file diff --git a/src/config/index.js b/src/app/config/index.js similarity index 100% rename from src/config/index.js rename to src/app/config/index.js diff --git a/src/app/providers/DatabaseProvider.js b/src/app/providers/DatabaseProvider.js new file mode 100644 index 0000000..7a7b953 --- /dev/null +++ b/src/app/providers/DatabaseProvider.js @@ -0,0 +1,216 @@ +/** + * 数据库服务提供者 + * + * 负责数据库连接管理、迁移管理和查询缓存 + */ + +import buildKnex from "knex"; +import databaseConfig from "../config/database.js"; + +// 简单内存缓存(支持 TTL 与按前缀清理) +const queryCache = new Map(); + +const getNow = () => Date.now(); + +const computeExpiresAt = (ttlMs) => { + if (!ttlMs || ttlMs <= 0) return null; + return getNow() + ttlMs; +}; + +const isExpired = (entry) => { + if (!entry) return true; + if (entry.expiresAt == null) return false; + return entry.expiresAt <= getNow(); +}; + +const getCacheKeyForBuilder = (builder) => { + if (builder._customCacheKey) return String(builder._customCacheKey); + return builder.toString(); +}; + +// 全局工具,便于在 QL 外部操作缓存 +export const DbQueryCache = { + get(key) { + const entry = queryCache.get(String(key)); + if (!entry) return undefined; + if (isExpired(entry)) { + queryCache.delete(String(key)); + return undefined; + } + return entry.value; + }, + set(key, value, ttlMs) { + const expiresAt = computeExpiresAt(ttlMs); + queryCache.set(String(key), { value, expiresAt }); + return value; + }, + has(key) { + const entry = queryCache.get(String(key)); + return !!entry && !isExpired(entry); + }, + delete(key) { + return queryCache.delete(String(key)); + }, + clear() { + queryCache.clear(); + }, + clearByPrefix(prefix) { + const p = String(prefix); + for (const k of queryCache.keys()) { + if (k.startsWith(p)) queryCache.delete(k); + } + }, + stats() { + let valid = 0; + let expired = 0; + for (const [k, entry] of queryCache.entries()) { + if (isExpired(entry)) expired++; + else valid++; + } + return { size: queryCache.size, valid, expired }; + } +}; + +class DatabaseProvider { + constructor() { + this.db = null; + this._isExtended = false; + } + + /** + * 注册数据库连接 + */ + register() { + if (this.db) { + return this.db; + } + + // 创建 Knex 实例 + this.db = buildKnex(databaseConfig); + + // 扩展 QueryBuilder 缓存功能(只在第一次初始化时扩展) + if (!this._isExtended) { + this._extendQueryBuilder(); + this._isExtended = true; + } + + return this.db; + } + + /** + * 扩展 QueryBuilder 的缓存功能 + */ + _extendQueryBuilder() { + // 1) cache(ttlMs?): 读取缓存,不存在则执行并写入 + buildKnex.QueryBuilder.extend("cache", async function (ttlMs) { + const key = getCacheKeyForBuilder(this); + const entry = queryCache.get(key); + if (entry && !isExpired(entry)) { + return entry.value; + } + const data = await this; + queryCache.set(key, { value: data, expiresAt: computeExpiresAt(ttlMs) }); + return data; + }); + + // 2) cacheAs(customKey): 设置自定义 key + buildKnex.QueryBuilder.extend("cacheAs", function (customKey) { + this._customCacheKey = String(customKey); + return this; + }); + + // 3) cacheSet(value, ttlMs?): 手动设置当前查询 key 的缓存 + buildKnex.QueryBuilder.extend("cacheSet", function (value, ttlMs) { + const key = getCacheKeyForBuilder(this); + queryCache.set(key, { value, expiresAt: computeExpiresAt(ttlMs) }); + return value; + }); + + // 4) cacheGet(): 仅从缓存读取当前查询 key 的值 + buildKnex.QueryBuilder.extend("cacheGet", function () { + const key = getCacheKeyForBuilder(this); + const entry = queryCache.get(key); + if (!entry || isExpired(entry)) return undefined; + return entry.value; + }); + + // 5) cacheInvalidate(): 使当前查询 key 的缓存失效 + buildKnex.QueryBuilder.extend("cacheInvalidate", function () { + const key = getCacheKeyForBuilder(this); + queryCache.delete(key); + return this; + }); + + // 6) cacheInvalidateByPrefix(prefix): 按前缀清理 + buildKnex.QueryBuilder.extend("cacheInvalidateByPrefix", function (prefix) { + const p = String(prefix); + for (const k of queryCache.keys()) { + if (k.startsWith(p)) queryCache.delete(k); + } + return this; + }); + } + + /** + * 运行数据库迁移 + */ + async runMigrations() { + if (!this.db) { + throw new Error("Database not initialized. Call register() first."); + } + + try { + const [batch, log] = await this.db.migrate.latest(); + if (log.length === 0) { + console.log("Database is already up to date"); + } else { + console.log(`Migrated ${log.length} files:`, log); + } + return { batch, log }; + } catch (error) { + console.error("Migration failed:", error); + throw error; + } + } + + /** + * 运行种子数据 + */ + async runSeeds() { + if (!this.db) { + throw new Error("Database not initialized. Call register() first."); + } + + try { + const [log] = await this.db.seed.run(); + console.log("Seeds completed:", log); + return log; + } catch (error) { + console.error("Seeding failed:", error); + throw error; + } + } + + /** + * 获取数据库实例 + */ + getDatabase() { + return this.db; + } + + /** + * 关闭数据库连接 + */ + async close() { + if (this.db) { + await this.db.destroy(); + this.db = null; + } + } +} + +// 导出单例实例 +const databaseProvider = new DatabaseProvider(); + +export default databaseProvider; +export { DatabaseProvider }; \ No newline at end of file diff --git a/src/app/providers/index.js b/src/app/providers/index.js new file mode 100644 index 0000000..15ad712 --- /dev/null +++ b/src/app/providers/index.js @@ -0,0 +1,15 @@ +/** + * 服务提供者索引文件 + * + * 统一导出所有服务提供者 + */ + +import DatabaseProvider from "./DatabaseProvider.js"; + +export { + DatabaseProvider +}; + +export default { + DatabaseProvider +}; \ No newline at end of file diff --git a/src/controllers/Page/BasePageController.js b/src/controllers/Page/BasePageController.js deleted file mode 100644 index 411a431..0000000 --- a/src/controllers/Page/BasePageController.js +++ /dev/null @@ -1,102 +0,0 @@ -import Router from "utils/router.js" -import ArticleService from "services/ArticleService.js" -import { logger } from "@/logger.js" - -/** - * 基础页面控制器 - * 负责处理首页、静态页面、联系表单等基础功能 - */ -class BasePageController { - constructor() { - this.articleService = new ArticleService() - } - - // 首页 - async indexGet(ctx) { - const blogs = await this.articleService.getPublishedArticles() - return await ctx.render( - "page/index/index", - { - apiList: [ - { - name: "随机图片", - desc: "随机图片,点击查看。
右键可复制链接", - url: "https://pic.xieyaxin.top/random.php", - }, - ], - blogs: blogs.slice(0, 4), - }, - { includeSite: true, includeUser: true } - ) - } - - // 处理联系表单提交 - async contactPost(ctx) { - const { name, email, subject, message } = ctx.request.body - - // 简单的表单验证 - if (!name || !email || !subject || !message) { - ctx.status = 400 - ctx.body = { success: false, message: "请填写所有必填字段" } - return - } - - // 这里可以添加邮件发送逻辑或数据库存储逻辑 - // 目前只是简单的成功响应 - logger.info(`收到联系表单: ${name} (${email}) - ${subject}: ${message}`) - - ctx.body = { - success: true, - message: "感谢您的留言,我们会尽快回复您!", - } - } - - /** - * 通用页面渲染方法 - * @param {string} name - 模板名称 - * @param {Object} data - 页面数据 - * @returns {Function} 页面渲染函数 - */ - pageGet(name, data) { - return async ctx => { - return await ctx.render( - name, - { - ...(data || {}), - }, - { includeSite: true, includeUser: true } - ) - } - } - - /** - * 创建基础页面相关路由 - * @returns {Router} 路由实例 - */ - static createRoutes() { - const controller = new BasePageController() - const router = new Router({ auth: "try" }) - - // 首页 - router.get("/", controller.indexGet.bind(controller), { auth: false }) - - // 静态页面 - router.get("/about", controller.pageGet("page/about/index"), { auth: false }) - router.get("/terms", controller.pageGet("page/extra/terms"), { auth: false }) - router.get("/privacy", controller.pageGet("page/extra/privacy"), { auth: false }) - router.get("/faq", controller.pageGet("page/extra/faq"), { auth: false }) - router.get("/feedback", controller.pageGet("page/extra/feedback"), { auth: false }) - router.get("/help", controller.pageGet("page/extra/help"), { auth: false }) - router.get("/contact", controller.pageGet("page/extra/contact"), { auth: false }) - - // 需要登录的页面 - router.get("/notice", controller.pageGet("page/notice/index"), { auth: true }) - - // 联系表单处理 - router.post("/contact", controller.contactPost.bind(controller), { auth: false }) - - return router - } -} - -export default BasePageController \ No newline at end of file diff --git a/src/controllers/Page/_Demo/HtmxController.js b/src/controllers/Page/_Demo/HtmxController.js deleted file mode 100644 index 9908a22..0000000 --- a/src/controllers/Page/_Demo/HtmxController.js +++ /dev/null @@ -1,63 +0,0 @@ -import Router from "utils/router.js" - -class HtmxController { - async index(ctx) { - return await ctx.render("index", { name: "bluescurry" }) - } - - page(name, data) { - return async ctx => { - return await ctx.render(name, data) - } - } - - static createRoutes() { - const controller = new HtmxController() - const router = new Router({ auth: "try" }) - router.get("/htmx/timeline", async ctx => { - return await ctx.render("htmx/timeline", { - timeLine: [ - { - icon: "第一份工作", - title: "???", - desc: `做游戏的。`, - }, - { - icon: "大学毕业", - title: "2014年09月", - desc: `我从江西师范大学毕业, - 获得了软件工程(虚拟现实与技术)专业的学士学位。`, - }, - { - icon: "高中", - title: "???", - desc: `宜春中学`, - }, - { - icon: "初中", - title: "???", - desc: `宜春实验中学`, - }, - { - icon: "小学(4-6年级)", - title: "???", - desc: `宜春二小`, - }, - { - icon: "小学(1-3年级)", - title: "???", - desc: `丰城市泉港镇小学`, - }, - { - icon: "出生", - title: "1996年06月", - desc: `我出生于江西省丰城市泉港镇`, - }, - ], - }) - }) - return router - } -} - -export default HtmxController diff --git a/src/db/index.js b/src/db/index.js deleted file mode 100644 index fcab69a..0000000 --- a/src/db/index.js +++ /dev/null @@ -1,149 +0,0 @@ -import buildKnex from "knex" -import knexConfig from "../../knexfile.mjs" - -// 简单内存缓存(支持 TTL 与按前缀清理) -const queryCache = new Map() - -const getNow = () => Date.now() - -const computeExpiresAt = (ttlMs) => { - if (!ttlMs || ttlMs <= 0) return null - return getNow() + ttlMs -} - -const isExpired = (entry) => { - if (!entry) return true - if (entry.expiresAt == null) return false - return entry.expiresAt <= getNow() -} - -const getCacheKeyForBuilder = (builder) => { - if (builder._customCacheKey) return String(builder._customCacheKey) - return builder.toString() -} - -// 全局工具,便于在 QL 外部操作缓存 -export const DbQueryCache = { - get(key) { - const entry = queryCache.get(String(key)) - if (!entry) return undefined - if (isExpired(entry)) { - queryCache.delete(String(key)) - return undefined - } - return entry.value - }, - set(key, value, ttlMs) { - const expiresAt = computeExpiresAt(ttlMs) - queryCache.set(String(key), { value, expiresAt }) - return value - }, - has(key) { - const entry = queryCache.get(String(key)) - return !!entry && !isExpired(entry) - }, - delete(key) { - return queryCache.delete(String(key)) - }, - clear() { - queryCache.clear() - }, - clearByPrefix(prefix) { - const p = String(prefix) - for (const k of queryCache.keys()) { - if (k.startsWith(p)) queryCache.delete(k) - } - }, - stats() { - let valid = 0 - let expired = 0 - for (const [k, entry] of queryCache.entries()) { - if (isExpired(entry)) expired++ - else valid++ - } - return { size: queryCache.size, valid, expired } - } -} - -// QueryBuilder 扩展 -// 1) cache(ttlMs?): 读取缓存,不存在则执行并写入 -buildKnex.QueryBuilder.extend("cache", async function (ttlMs) { - const key = getCacheKeyForBuilder(this) - const entry = queryCache.get(key) - if (entry && !isExpired(entry)) { - return entry.value - } - const data = await this - queryCache.set(key, { value: data, expiresAt: computeExpiresAt(ttlMs) }) - return data -}) - -// 2) cacheAs(customKey): 设置自定义 key -buildKnex.QueryBuilder.extend("cacheAs", function (customKey) { - this._customCacheKey = String(customKey) - return this -}) - -// 3) cacheSet(value, ttlMs?): 手动设置当前查询 key 的缓存 -buildKnex.QueryBuilder.extend("cacheSet", function (value, ttlMs) { - const key = getCacheKeyForBuilder(this) - queryCache.set(key, { value, expiresAt: computeExpiresAt(ttlMs) }) - return value -}) - -// 4) cacheGet(): 仅从缓存读取当前查询 key 的值 -buildKnex.QueryBuilder.extend("cacheGet", function () { - const key = getCacheKeyForBuilder(this) - const entry = queryCache.get(key) - if (!entry || isExpired(entry)) return undefined - return entry.value -}) - -// 5) cacheInvalidate(): 使当前查询 key 的缓存失效 -buildKnex.QueryBuilder.extend("cacheInvalidate", function () { - const key = getCacheKeyForBuilder(this) - queryCache.delete(key) - return this -}) - -// 6) cacheInvalidateByPrefix(prefix): 按前缀清理 -buildKnex.QueryBuilder.extend("cacheInvalidateByPrefix", function (prefix) { - const p = String(prefix) - for (const k of queryCache.keys()) { - if (k.startsWith(p)) queryCache.delete(k) - } - return this -}) - -const environment = process.env.NODE_ENV || "development" -const db = buildKnex(knexConfig[environment]) - -export default db - -// async function createDatabase() { -// try { -// // SQLite会自动创建数据库文件,只需验证连接 -// await db.raw("SELECT 1") -// console.log("SQLite数据库连接成功") - -// // 检查users表是否存在(示例) -// const [tableExists] = await db.raw(` -// SELECT name -// FROM sqlite_master -// WHERE type='table' AND name='users' -// `) - -// if (tableExists) { -// console.log("表 users 已存在") -// } else { -// console.log("表 users 不存在,需要创建(通过迁移)") -// } - -// await db.destroy() -// } catch (error) { -// console.error("数据库操作失败:", error) -// process.exit(1) -// } -// } - -// createDatabase() diff --git a/src/global.js b/src/global.js deleted file mode 100644 index c5274e9..0000000 --- a/src/global.js +++ /dev/null @@ -1,21 +0,0 @@ -import Koa from "koa" -import { logger } from "./logger.js" -import { validateEnvironment } from "./utils/envValidator.js" - -// 启动前验证环境变量 -if (!validateEnvironment()) { - logger.error("环境变量验证失败,应用退出") - process.exit(1) -} - -const app = new Koa({ asyncLocalStorage: true }) - -app.keys = [] - -// SESSION_SECRET 已通过环境变量验证确保存在 -process.env.SESSION_SECRET.split(",").forEach(secret => { - app.keys.push(secret.trim()) -}) - -export { app } -export default app \ No newline at end of file diff --git a/src/db/docs/ArticleModel.md b/src/infrastructure/database/docs/ArticleModel.md similarity index 100% rename from src/db/docs/ArticleModel.md rename to src/infrastructure/database/docs/ArticleModel.md diff --git a/src/db/docs/BookmarkModel.md b/src/infrastructure/database/docs/BookmarkModel.md similarity index 100% rename from src/db/docs/BookmarkModel.md rename to src/infrastructure/database/docs/BookmarkModel.md diff --git a/src/db/docs/README.md b/src/infrastructure/database/docs/README.md similarity index 100% rename from src/db/docs/README.md rename to src/infrastructure/database/docs/README.md diff --git a/src/db/docs/SiteConfigModel.md b/src/infrastructure/database/docs/SiteConfigModel.md similarity index 100% rename from src/db/docs/SiteConfigModel.md rename to src/infrastructure/database/docs/SiteConfigModel.md diff --git a/src/db/docs/UserModel.md b/src/infrastructure/database/docs/UserModel.md similarity index 100% rename from src/db/docs/UserModel.md rename to src/infrastructure/database/docs/UserModel.md diff --git a/src/infrastructure/database/index.js b/src/infrastructure/database/index.js new file mode 100644 index 0000000..0d884be --- /dev/null +++ b/src/infrastructure/database/index.js @@ -0,0 +1,13 @@ +/** + * 数据库基础设施 + * + * 统一导出数据库相关功能 + */ + +import DatabaseProvider from "../../app/providers/DatabaseProvider.js"; + +// 初始化数据库连接 +const db = DatabaseProvider.register(); + +export default db; +export { DatabaseProvider }; \ No newline at end of file diff --git a/src/db/migrations/20250616065041_create_users_table.mjs b/src/infrastructure/database/migrations/20250616065041_create_users_table.mjs similarity index 100% rename from src/db/migrations/20250616065041_create_users_table.mjs rename to src/infrastructure/database/migrations/20250616065041_create_users_table.mjs diff --git a/src/db/migrations/20250621013128_site_config.mjs b/src/infrastructure/database/migrations/20250621013128_site_config.mjs similarity index 100% rename from src/db/migrations/20250621013128_site_config.mjs rename to src/infrastructure/database/migrations/20250621013128_site_config.mjs diff --git a/src/db/migrations/20250830014825_create_articles_table.mjs b/src/infrastructure/database/migrations/20250830014825_create_articles_table.mjs similarity index 100% rename from src/db/migrations/20250830014825_create_articles_table.mjs rename to src/infrastructure/database/migrations/20250830014825_create_articles_table.mjs diff --git a/src/db/migrations/20250830015422_create_bookmarks_table.mjs b/src/infrastructure/database/migrations/20250830015422_create_bookmarks_table.mjs similarity index 100% rename from src/db/migrations/20250830015422_create_bookmarks_table.mjs rename to src/infrastructure/database/migrations/20250830015422_create_bookmarks_table.mjs diff --git a/src/db/migrations/20250830020000_add_article_fields.mjs b/src/infrastructure/database/migrations/20250830020000_add_article_fields.mjs similarity index 100% rename from src/db/migrations/20250830020000_add_article_fields.mjs rename to src/infrastructure/database/migrations/20250830020000_add_article_fields.mjs diff --git a/src/db/migrations/20250901000000_add_profile_fields.mjs b/src/infrastructure/database/migrations/20250901000000_add_profile_fields.mjs similarity index 100% rename from src/db/migrations/20250901000000_add_profile_fields.mjs rename to src/infrastructure/database/migrations/20250901000000_add_profile_fields.mjs diff --git a/src/db/seeds/20250616071157_users_seed.mjs b/src/infrastructure/database/seeds/20250616071157_users_seed.mjs similarity index 100% rename from src/db/seeds/20250616071157_users_seed.mjs rename to src/infrastructure/database/seeds/20250616071157_users_seed.mjs diff --git a/src/db/seeds/20250621013324_site_config_seed.mjs b/src/infrastructure/database/seeds/20250621013324_site_config_seed.mjs similarity index 100% rename from src/db/seeds/20250621013324_site_config_seed.mjs rename to src/infrastructure/database/seeds/20250621013324_site_config_seed.mjs diff --git a/src/db/seeds/20250830020000_articles_seed.mjs b/src/infrastructure/database/seeds/20250830020000_articles_seed.mjs similarity index 100% rename from src/db/seeds/20250830020000_articles_seed.mjs rename to src/infrastructure/database/seeds/20250830020000_articles_seed.mjs diff --git a/src/jobs/exampleJob.js b/src/infrastructure/jobs/exampleJob.js similarity index 78% rename from src/jobs/exampleJob.js rename to src/infrastructure/jobs/exampleJob.js index 4e0387c..ca4976d 100644 --- a/src/jobs/exampleJob.js +++ b/src/infrastructure/jobs/exampleJob.js @@ -1,4 +1,4 @@ -import { jobLogger } from "@/logger" +import { jobLogger } from "../../app/bootstrap/logger.js" export default { id: "example", diff --git a/src/infrastructure/jobs/index.js b/src/infrastructure/jobs/index.js new file mode 100644 index 0000000..c288f07 --- /dev/null +++ b/src/infrastructure/jobs/index.js @@ -0,0 +1,62 @@ +import fs from 'fs'; +import path from 'path'; +import { fileURLToPath } from 'url'; +import scheduler from '../../shared/utils/scheduler.js'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); +const jobsDir = __dirname; +const jobModules = {}; + +// 异步加载所有 job 文件 +async function loadJobs() { + const files = fs.readdirSync(jobsDir); + for (const file of files) { + if (file === 'index.js' || !file.endsWith('Job.js')) continue; + try { + const jobModule = await import(`file://${path.join(jobsDir, file)}`); + const job = jobModule.default || jobModule; + if (job && job.id && job.cronTime && typeof job.task === 'function') { + jobModules[job.id] = job; + scheduler.add(job.id, job.cronTime, job.task, job.options); + if (job.autoStart) scheduler.start(job.id); + } + } catch (error) { + console.error(`[Jobs] 加载任务文件 ${file} 失败:`, error); + } + } +} + +// 立即加载所有任务 +loadJobs().catch(console.error); + +function callHook(id, hookName) { + const job = jobModules[id]; + if (job && typeof job[hookName] === 'function') { + try { + job[hookName](); + } catch (e) { + console.error(`[Job:${id}] ${hookName} 执行异常:`, e); + } + } +} + +export default { + start: id => { + callHook(id, 'beforeStart'); + scheduler.start(id); + }, + stop: id => { + scheduler.stop(id); + callHook(id, 'afterStop'); + }, + updateCronTime: (id, cronTime) => scheduler.updateCronTime(id, cronTime), + list: () => scheduler.list(), + reload: id => { + const job = jobModules[id]; + if (job) { + scheduler.remove(id); + scheduler.add(job.id, job.cronTime, job.task, job.options); + } + } +}; diff --git a/src/jobs/index.js b/src/jobs/index.js deleted file mode 100644 index bf8006c..0000000 --- a/src/jobs/index.js +++ /dev/null @@ -1,48 +0,0 @@ -import fs from 'fs'; -import path from 'path'; -import scheduler from 'utils/scheduler.js'; - -const jobsDir = __dirname; -const jobModules = {}; - -fs.readdirSync(jobsDir).forEach(file => { - if (file === 'index.js' || !file.endsWith('Job.js')) return; - const jobModule = require(path.join(jobsDir, file)); - const job = jobModule.default || jobModule; - if (job && job.id && job.cronTime && typeof job.task === 'function') { - jobModules[job.id] = job; - scheduler.add(job.id, job.cronTime, job.task, job.options); - if (job.autoStart) scheduler.start(job.id); - } -}); - -function callHook(id, hookName) { - const job = jobModules[id]; - if (job && typeof job[hookName] === 'function') { - try { - job[hookName](); - } catch (e) { - console.error(`[Job:${id}] ${hookName} 执行异常:`, e); - } - } -} - -export default { - start: id => { - callHook(id, 'beforeStart'); - scheduler.start(id); - }, - stop: id => { - scheduler.stop(id); - callHook(id, 'afterStop'); - }, - updateCronTime: (id, cronTime) => scheduler.updateCronTime(id, cronTime), - list: () => scheduler.list(), - reload: id => { - const job = jobModules[id]; - if (job) { - scheduler.remove(id); - scheduler.add(job.id, job.cronTime, job.task, job.options); - } - } -}; diff --git a/src/main.js b/src/main.js index 7f27c89..39de094 100644 --- a/src/main.js +++ b/src/main.js @@ -1,41 +1,52 @@ -import { app } from "./global" +import { app } from "./app/bootstrap/app.js" // 日志、全局插件、定时任务等基础设施 -import { logger } from "./logger.js" -import "./jobs/index.js" +import { logger } from "./app/bootstrap/logger.js" +import "./infrastructure/jobs/index.js" // 第三方依赖 import os from "os" // 应用插件与自动路由 -import LoadMiddlewares from "./middlewares/install.js" +import LoadMiddlewares from "./presentation/middlewares/install.js" -// 注册插件 -LoadMiddlewares(app) +// 异步启动函数 +async function startServer() { + // 注册插件 + await LoadMiddlewares(app) -const PORT = process.env.PORT || 3000 + const PORT = process.env.PORT || 3000 -const server = app.listen(PORT, () => { - const port = server.address().port - // 获取本地 IP - const getLocalIP = () => { - const interfaces = os.networkInterfaces() - for (const name of Object.keys(interfaces)) { - for (const iface of interfaces[name]) { - if (iface.family === "IPv4" && !iface.internal) { - return iface.address + const server = app.listen(PORT, () => { + const port = server.address().port + // 获取本地 IP + const getLocalIP = () => { + const interfaces = os.networkInterfaces() + for (const name of Object.keys(interfaces)) { + for (const iface of interfaces[name]) { + if (iface.family === "IPv4" && !iface.internal) { + return iface.address + } } } + return "localhost" } - return "localhost" - } - const localIP = getLocalIP() - logger.trace(`──────────────────── 服务器已启动 ────────────────────`) - logger.trace(` `) - logger.trace(` 本地访问: http://localhost:${port} `) - logger.trace(` 局域网: http://${localIP}:${port} `) - logger.trace(` `) - logger.trace(` 服务启动时间: ${new Date().toLocaleString()} `) - logger.trace(`──────────────────────────────────────────────────────\n`) + const localIP = getLocalIP() + logger.trace(`──────────────────── 服务器已启动 ────────────────────`) + logger.trace(` `) + logger.trace(` 本地访问: http://localhost:${port} `) + logger.trace(` 局域网: http://${localIP}:${port} `) + logger.trace(` `) + logger.trace(` 服务启动时间: ${new Date().toLocaleString()} `) + logger.trace(`──────────────────────────────────────────────────\n`) + }) + + return server +} + +// 启动服务器 +startServer().catch(error => { + logger.error('服务器启动失败:', error) + process.exit(1) }) -export default app +export default app \ No newline at end of file diff --git a/src/middlewares/ErrorHandler/index.js b/src/middlewares/ErrorHandler/index.js deleted file mode 100644 index 816dce4..0000000 --- a/src/middlewares/ErrorHandler/index.js +++ /dev/null @@ -1,43 +0,0 @@ -import { logger } from "@/logger" -// src/plugins/errorHandler.js -// 错误处理中间件插件 - -async function formatError(ctx, status, message, stack) { - const accept = ctx.accepts("json", "html", "text") - const isDev = process.env.NODE_ENV === "development" - if (accept === "json") { - ctx.type = "application/json" - ctx.body = isDev && stack ? { success: false, error: message, stack } : { success: false, error: message } - } else if (accept === "html") { - ctx.type = "html" - await ctx.render("error/index", { status, message, stack, isDev }) - } else { - ctx.type = "text" - ctx.body = isDev && stack ? `${status} - ${message}\n${stack}` : `${status} - ${message}` - } - ctx.status = status -} - -export default function errorHandler() { - return async (ctx, next) => { - // 拦截 Chrome DevTools 探测请求,直接返回 204 - if (ctx.path === "/.well-known/appspecific/com.chrome.devtools.json") { - ctx.status = 204 - ctx.body = "" - return - } - try { - await next() - if (ctx.status === 404) { - await formatError(ctx, 404, "Resource not found") - } - } catch (err) { - logger.error(err) - const isDev = process.env.NODE_ENV === "development" - if (isDev && err.stack) { - console.error(err.stack) - } - await formatError(ctx, err.statusCode || 500, err.message || err || "Internal server error", isDev ? err.stack : undefined) - } - } -} diff --git a/src/controllers/Page/ArticleController.js b/src/modules/article/controllers/ArticleController.js similarity index 97% rename from src/controllers/Page/ArticleController.js rename to src/modules/article/controllers/ArticleController.js index 8809814..94f5bd4 100644 --- a/src/controllers/Page/ArticleController.js +++ b/src/modules/article/controllers/ArticleController.js @@ -1,5 +1,5 @@ -import { ArticleModel } from "../../db/models/ArticleModel.js" -import Router from "utils/router.js" +import { ArticleModel } from "../models/ArticleModel.js" +import Router from "../../../shared/utils/router.js" import { marked } from "marked" class ArticleController { diff --git a/src/db/models/ArticleModel.js b/src/modules/article/models/ArticleModel.js similarity index 99% rename from src/db/models/ArticleModel.js rename to src/modules/article/models/ArticleModel.js index 4bf5fa9..a635334 100644 --- a/src/db/models/ArticleModel.js +++ b/src/modules/article/models/ArticleModel.js @@ -1,4 +1,4 @@ -import db from "../index.js" +import db from "../../../infrastructure/database/index.js" class ArticleModel { static async findAll() { diff --git a/src/services/ArticleService.js b/src/modules/article/services/ArticleService.js similarity index 98% rename from src/services/ArticleService.js rename to src/modules/article/services/ArticleService.js index 1364348..df96e91 100644 --- a/src/services/ArticleService.js +++ b/src/modules/article/services/ArticleService.js @@ -1,5 +1,5 @@ -import ArticleModel from "db/models/ArticleModel.js" -import CommonError from "utils/error/CommonError.js" +import ArticleModel from "../models/ArticleModel.js" +import CommonError from "../../../shared/utils/error/CommonError.js" class ArticleService { // 获取所有文章 diff --git a/src/controllers/Api/AuthController.js b/src/modules/auth/controllers/AuthController.js similarity index 88% rename from src/controllers/Api/AuthController.js rename to src/modules/auth/controllers/AuthController.js index 4c4e5cd..1e72e3b 100644 --- a/src/controllers/Api/AuthController.js +++ b/src/modules/auth/controllers/AuthController.js @@ -1,6 +1,6 @@ -import UserService from "services/userService.js" -import { R } from "utils/helper.js" -import Router from "utils/router.js" +import UserService from "../services/userService.js" +import { R } from "../../../shared/utils/helper.js" +import Router from "../../../shared/utils/router.js" class AuthController { constructor() { diff --git a/src/controllers/Page/AuthPageController.js b/src/modules/auth/controllers/AuthPageController.js similarity index 93% rename from src/controllers/Page/AuthPageController.js rename to src/modules/auth/controllers/AuthPageController.js index 1fd68b0..4b9780b 100644 --- a/src/controllers/Page/AuthPageController.js +++ b/src/modules/auth/controllers/AuthPageController.js @@ -1,8 +1,8 @@ -import Router from "utils/router.js" -import UserService from "services/userService.js" +import Router from "../../../shared/utils/router.js" +import UserService from "../services/userService.js" import svgCaptcha from "svg-captcha" -import CommonError from "@/utils/error/CommonError" -import { logger } from "@/logger.js" +import CommonError from "../../../shared/utils/error/CommonError.js" +import { logger } from "../../../app/bootstrap/logger.js" /** * 认证相关页面控制器 diff --git a/src/db/models/UserModel.js b/src/modules/auth/models/UserModel.js similarity index 93% rename from src/db/models/UserModel.js rename to src/modules/auth/models/UserModel.js index bf9fc03..ef8131c 100644 --- a/src/db/models/UserModel.js +++ b/src/modules/auth/models/UserModel.js @@ -1,4 +1,4 @@ -import db from "../index.js" +import db from "../../../infrastructure/database/index.js" class UserModel { static async findAll() { diff --git a/src/services/userService.js b/src/modules/auth/services/userService.js similarity index 97% rename from src/services/userService.js rename to src/modules/auth/services/userService.js index edd9981..de8d4c6 100644 --- a/src/services/userService.js +++ b/src/modules/auth/services/userService.js @@ -1,8 +1,8 @@ -import UserModel from "db/models/UserModel.js" -import { hashPassword, comparePassword } from "utils/bcrypt.js" -import CommonError from "utils/error/CommonError.js" -import { JWT_SECRET } from "@/middlewares/Auth/auth.js" -import jwt from "@/middlewares/Auth/jwt.js" +import UserModel from "../models/UserModel.js" +import { hashPassword, comparePassword } from "../../../shared/utils/bcrypt.js" +import CommonError from "../../../shared/utils/error/CommonError.js" +import { JWT_SECRET } from "../../../presentation/middlewares/Auth/auth.js" +import jwt from "../../../presentation/middlewares/Auth/jwt.js" class UserService { // 根据ID获取用户 diff --git a/src/db/models/BookmarkModel.js b/src/modules/bookmark/models/BookmarkModel.js similarity index 97% rename from src/db/models/BookmarkModel.js rename to src/modules/bookmark/models/BookmarkModel.js index 3fb6968..268bdc3 100644 --- a/src/db/models/BookmarkModel.js +++ b/src/modules/bookmark/models/BookmarkModel.js @@ -1,4 +1,4 @@ -import db from "../index.js" +import db from "../../../infrastructure/database/index.js" class BookmarkModel { static async findAllByUser(userId) { diff --git a/src/services/BookmarkService.js b/src/modules/bookmark/services/BookmarkService.js similarity index 98% rename from src/services/BookmarkService.js rename to src/modules/bookmark/services/BookmarkService.js index 249591c..b95e949 100644 --- a/src/services/BookmarkService.js +++ b/src/modules/bookmark/services/BookmarkService.js @@ -1,5 +1,5 @@ -import BookmarkModel from "db/models/BookmarkModel.js" -import CommonError from "utils/error/CommonError.js" +import BookmarkModel from "../models/BookmarkModel.js" +import CommonError from "../../../shared/utils/error/CommonError.js" class BookmarkService { // 获取用户的所有书签 diff --git a/src/controllers/Api/ApiController.js b/src/modules/common/controllers/ApiController.js similarity index 94% rename from src/controllers/Api/ApiController.js rename to src/modules/common/controllers/ApiController.js index 602e56e..cd64808 100644 --- a/src/controllers/Api/ApiController.js +++ b/src/modules/common/controllers/ApiController.js @@ -1,5 +1,5 @@ -import { R } from "utils/helper.js" -import Router from "utils/router.js" +import { R } from "../../../shared/utils/helper.js" +import Router from "../../../shared/utils/router.js" class AuthController { constructor() {} diff --git a/src/controllers/Api/JobController.js b/src/modules/common/controllers/JobController.js similarity index 89% rename from src/controllers/Api/JobController.js rename to src/modules/common/controllers/JobController.js index 719fddf..722a362 100644 --- a/src/controllers/Api/JobController.js +++ b/src/modules/common/controllers/JobController.js @@ -1,7 +1,7 @@ // Job Controller 示例:如何调用 service 层动态控制和查询定时任务 -import JobService from "services/JobService.js" -import { R } from "utils/helper.js" -import Router from "utils/router.js" +import JobService from "../services/JobService.js" +import { R } from "../../../shared/utils/helper.js" +import Router from "../../../shared/utils/router.js" class JobController { constructor() { diff --git a/src/modules/common/controllers/PageController.js b/src/modules/common/controllers/PageController.js new file mode 100644 index 0000000..23332db --- /dev/null +++ b/src/modules/common/controllers/PageController.js @@ -0,0 +1,25 @@ +import { R } from "../../../shared/utils/helper.js" +import Router from "../../../shared/utils/router.js" + +class Pageontroller { + constructor() {} + + async indexGet () { + console.log(234); + + return R.ResponseSuccess(Math.random()) + } + + /** + * 路由注册 + */ + static createRoutes() { + const controller = new Pageontroller() + const router = new Router() + router.get("", controller.indexGet.bind(controller), { auth: "try" }) + router.get("/", controller.indexGet.bind(controller), { auth: "try" }) + return router + } +} + +export default Pageontroller diff --git a/src/controllers/Api/StatusController.js b/src/modules/common/controllers/StatusController.js similarity index 89% rename from src/controllers/Api/StatusController.js rename to src/modules/common/controllers/StatusController.js index d9cef1c..b31a490 100644 --- a/src/controllers/Api/StatusController.js +++ b/src/modules/common/controllers/StatusController.js @@ -1,4 +1,4 @@ -import Router from "utils/router.js" +import Router from "../../../shared/utils/router.js" class StatusController { async status(ctx) { diff --git a/src/controllers/Page/UploadController.js b/src/modules/common/controllers/UploadController.js similarity index 97% rename from src/controllers/Page/UploadController.js rename to src/modules/common/controllers/UploadController.js index e172b7f..5e33b4e 100644 --- a/src/controllers/Page/UploadController.js +++ b/src/modules/common/controllers/UploadController.js @@ -1,10 +1,10 @@ -import Router from "utils/router.js" +import Router from "../../../shared/utils/router.js" import formidable from "formidable" import fs from "fs/promises" import path from "path" import { fileURLToPath } from "url" -import { logger } from "@/logger.js" -import { R } from "@/utils/helper" +import { logger } from "../../../app/bootstrap/logger.js" +import { R } from "../../../shared/utils/helper.js" /** * 文件上传控制器 diff --git a/src/services/JobService.js b/src/modules/common/services/JobService.js similarity index 83% rename from src/services/JobService.js rename to src/modules/common/services/JobService.js index 35a04a3..b6209b9 100644 --- a/src/services/JobService.js +++ b/src/modules/common/services/JobService.js @@ -1,4 +1,4 @@ -import jobs from "../jobs" +import jobs from "../../../infrastructure/jobs/index.js" class JobService { startJob(id) { diff --git a/src/db/models/SiteConfigModel.js b/src/modules/site-config/models/SiteConfigModel.js similarity index 93% rename from src/db/models/SiteConfigModel.js rename to src/modules/site-config/models/SiteConfigModel.js index 7e69fe0..1981720 100644 --- a/src/db/models/SiteConfigModel.js +++ b/src/modules/site-config/models/SiteConfigModel.js @@ -1,4 +1,4 @@ -import db from "../index.js" +import db from "../../../infrastructure/database/index.js" class SiteConfigModel { // 获取指定key的配置 diff --git a/src/services/SiteConfigService.js b/src/modules/site-config/services/SiteConfigService.js similarity index 98% rename from src/services/SiteConfigService.js rename to src/modules/site-config/services/SiteConfigService.js index 59537fd..9355fd5 100644 --- a/src/services/SiteConfigService.js +++ b/src/modules/site-config/services/SiteConfigService.js @@ -1,5 +1,5 @@ -import SiteConfigModel from "../db/models/SiteConfigModel.js" -import CommonError from "utils/error/CommonError.js" +import SiteConfigModel from "../models/SiteConfigModel.js" +import CommonError from "../../../shared/utils/error/CommonError.js" class SiteConfigService { // 获取指定key的配置 diff --git a/src/controllers/Page/ProfileController.js b/src/modules/user/controllers/ProfileController.js similarity index 96% rename from src/controllers/Page/ProfileController.js rename to src/modules/user/controllers/ProfileController.js index 3a3678c..266a683 100644 --- a/src/controllers/Page/ProfileController.js +++ b/src/modules/user/controllers/ProfileController.js @@ -1,11 +1,11 @@ -import Router from "utils/router.js" -import UserService from "services/userService.js" +import Router from "../../../shared/utils/router.js" +import UserService from "../../auth/services/userService.js" import formidable from "formidable" import fs from "fs/promises" import path from "path" import { fileURLToPath } from "url" -import CommonError from "@/utils/error/CommonError" -import { logger } from "@/logger.js" +import CommonError from "../../../shared/utils/error/CommonError.js" +import { logger } from "../../../app/bootstrap/logger.js" import imageThumbnail from "image-thumbnail" /** diff --git a/src/middlewares/Auth/auth.js b/src/presentation/middlewares/Auth/auth.js similarity index 97% rename from src/middlewares/Auth/auth.js rename to src/presentation/middlewares/Auth/auth.js index 81bfc70..9b77215 100644 --- a/src/middlewares/Auth/auth.js +++ b/src/presentation/middlewares/Auth/auth.js @@ -1,4 +1,4 @@ -import { logger } from "@/logger" +import { logger } from "../../../app/bootstrap/logger.js" import jwt from "./jwt" import { minimatch } from "minimatch" diff --git a/src/middlewares/Auth/index.js b/src/presentation/middlewares/Auth/index.js similarity index 100% rename from src/middlewares/Auth/index.js rename to src/presentation/middlewares/Auth/index.js diff --git a/src/middlewares/Auth/jwt.js b/src/presentation/middlewares/Auth/jwt.js similarity index 100% rename from src/middlewares/Auth/jwt.js rename to src/presentation/middlewares/Auth/jwt.js diff --git a/src/middlewares/errorHandler/index.js b/src/presentation/middlewares/ErrorHandler/index.js similarity index 96% rename from src/middlewares/errorHandler/index.js rename to src/presentation/middlewares/ErrorHandler/index.js index 816dce4..90ac80c 100644 --- a/src/middlewares/errorHandler/index.js +++ b/src/presentation/middlewares/ErrorHandler/index.js @@ -1,4 +1,4 @@ -import { logger } from "@/logger" +import { logger } from "../../../app/bootstrap/logger.js" // src/plugins/errorHandler.js // 错误处理中间件插件 diff --git a/src/middlewares/ResponseTime/index.js b/src/presentation/middlewares/ResponseTime/index.js similarity index 97% rename from src/middlewares/ResponseTime/index.js rename to src/presentation/middlewares/ResponseTime/index.js index 8312814..86e5ab5 100644 --- a/src/middlewares/ResponseTime/index.js +++ b/src/presentation/middlewares/ResponseTime/index.js @@ -1,4 +1,4 @@ -import { logger } from "@/logger" +import { logger } from "../../../app/bootstrap/logger.js" // 静态资源扩展名列表 const staticExts = [".css", ".js", ".png", ".jpg", ".jpeg", ".gif", ".ico", ".svg", ".map", ".woff", ".woff2", ".ttf", ".eot"] diff --git a/src/middlewares/Send/index.js b/src/presentation/middlewares/Send/index.js similarity index 100% rename from src/middlewares/Send/index.js rename to src/presentation/middlewares/Send/index.js diff --git a/src/middlewares/Send/resolve-path.js b/src/presentation/middlewares/Send/resolve-path.js similarity index 100% rename from src/middlewares/Send/resolve-path.js rename to src/presentation/middlewares/Send/resolve-path.js diff --git a/src/middlewares/Session/index.js b/src/presentation/middlewares/Session/index.js similarity index 100% rename from src/middlewares/Session/index.js rename to src/presentation/middlewares/Session/index.js diff --git a/src/middlewares/Toast/index.js b/src/presentation/middlewares/Toast/index.js similarity index 100% rename from src/middlewares/Toast/index.js rename to src/presentation/middlewares/Toast/index.js diff --git a/src/middlewares/Views/index.js b/src/presentation/middlewares/Views/index.js similarity index 92% rename from src/middlewares/Views/index.js rename to src/presentation/middlewares/Views/index.js index 8250bf6..82861af 100644 --- a/src/middlewares/Views/index.js +++ b/src/presentation/middlewares/Views/index.js @@ -1,13 +1,13 @@ import { resolve } from "path" -import { app } from "@/global" +import { app } from "../../../app/bootstrap/app.js" import consolidate from "consolidate" import send from "../Send" import getPaths from "get-paths" // import pretty from "pretty" -import { logger } from "@/logger" -import SiteConfigService from "services/SiteConfigService.js" +import { logger } from "../../../app/bootstrap/logger.js" +import SiteConfigService from "../../../modules/site-config/services/SiteConfigService.js" import assign from "lodash/assign" -import config from "config/index.js" +import config from "../../../app/config/index.js" export default viewsMiddleware diff --git a/src/middlewares/install.js b/src/presentation/middlewares/install.js similarity index 86% rename from src/middlewares/install.js rename to src/presentation/middlewares/install.js index 0f90e83..aa9ccdd 100644 --- a/src/middlewares/install.js +++ b/src/presentation/middlewares/install.js @@ -10,12 +10,12 @@ import Views from "./Views" import Session from "./Session" import etag from "@koa/etag" import conditional from "koa-conditional-get" -import { autoRegisterControllers } from "@/utils/ForRegister.js" +import { autoRegisterControllers } from "../../shared/utils/ForRegister.js" const __dirname = path.dirname(fileURLToPath(import.meta.url)) -const publicPath = resolve(__dirname, "../../public") +const publicPath = resolve(__dirname, "../../../public") -export default app => { +export default async app => { // 错误处理 app.use(ErrorHandler()) // 响应时间 @@ -50,9 +50,10 @@ export default app => { // 请求体解析 app.use(bodyParser()) // 自动注册控制器 - autoRegisterControllers(app, path.resolve(__dirname, "../controllers")) + await autoRegisterControllers(app, path.resolve(__dirname, "../../modules")) // 注册完成之后静态资源设置 app.use(async (ctx, next) => { + console.log(11, ctx.body); if (ctx.body) return await next() if (ctx.status === 200) return await next() if (ctx.method.toLowerCase() === "get") { diff --git a/src/views/error/index.pug b/src/presentation/views/error/index.pug similarity index 100% rename from src/views/error/index.pug rename to src/presentation/views/error/index.pug diff --git a/src/views/htmx/footer.pug b/src/presentation/views/htmx/footer.pug similarity index 100% rename from src/views/htmx/footer.pug rename to src/presentation/views/htmx/footer.pug diff --git a/src/views/htmx/login.pug b/src/presentation/views/htmx/login.pug similarity index 100% rename from src/views/htmx/login.pug rename to src/presentation/views/htmx/login.pug diff --git a/src/views/htmx/navbar.pug b/src/presentation/views/htmx/navbar.pug similarity index 100% rename from src/views/htmx/navbar.pug rename to src/presentation/views/htmx/navbar.pug diff --git a/src/views/htmx/timeline.pug b/src/presentation/views/htmx/timeline.pug similarity index 100% rename from src/views/htmx/timeline.pug rename to src/presentation/views/htmx/timeline.pug diff --git a/src/views/layouts/base.pug b/src/presentation/views/layouts/base.pug similarity index 100% rename from src/views/layouts/base.pug rename to src/presentation/views/layouts/base.pug diff --git a/src/views/layouts/bg-page.pug b/src/presentation/views/layouts/bg-page.pug similarity index 100% rename from src/views/layouts/bg-page.pug rename to src/presentation/views/layouts/bg-page.pug diff --git a/src/views/layouts/empty.pug b/src/presentation/views/layouts/empty.pug similarity index 100% rename from src/views/layouts/empty.pug rename to src/presentation/views/layouts/empty.pug diff --git a/src/views/layouts/page.pug b/src/presentation/views/layouts/page.pug similarity index 100% rename from src/views/layouts/page.pug rename to src/presentation/views/layouts/page.pug diff --git a/src/views/layouts/pure.pug b/src/presentation/views/layouts/pure.pug similarity index 100% rename from src/views/layouts/pure.pug rename to src/presentation/views/layouts/pure.pug diff --git a/src/views/layouts/root.pug b/src/presentation/views/layouts/root.pug similarity index 100% rename from src/views/layouts/root.pug rename to src/presentation/views/layouts/root.pug diff --git a/src/views/layouts/utils.pug b/src/presentation/views/layouts/utils.pug similarity index 100% rename from src/views/layouts/utils.pug rename to src/presentation/views/layouts/utils.pug diff --git a/src/views/page/about/index.pug b/src/presentation/views/page/about/index.pug similarity index 100% rename from src/views/page/about/index.pug rename to src/presentation/views/page/about/index.pug diff --git a/src/views/page/articles/article.pug b/src/presentation/views/page/articles/article.pug similarity index 100% rename from src/views/page/articles/article.pug rename to src/presentation/views/page/articles/article.pug diff --git a/src/views/page/articles/category.pug b/src/presentation/views/page/articles/category.pug similarity index 100% rename from src/views/page/articles/category.pug rename to src/presentation/views/page/articles/category.pug diff --git a/src/views/page/articles/index.pug b/src/presentation/views/page/articles/index.pug similarity index 100% rename from src/views/page/articles/index.pug rename to src/presentation/views/page/articles/index.pug diff --git a/src/views/page/articles/search.pug b/src/presentation/views/page/articles/search.pug similarity index 100% rename from src/views/page/articles/search.pug rename to src/presentation/views/page/articles/search.pug diff --git a/src/views/page/articles/tag.pug b/src/presentation/views/page/articles/tag.pug similarity index 100% rename from src/views/page/articles/tag.pug rename to src/presentation/views/page/articles/tag.pug diff --git a/src/views/page/auth/no-auth.pug b/src/presentation/views/page/auth/no-auth.pug similarity index 100% rename from src/views/page/auth/no-auth.pug rename to src/presentation/views/page/auth/no-auth.pug diff --git a/src/views/page/extra/contact.pug b/src/presentation/views/page/extra/contact.pug similarity index 100% rename from src/views/page/extra/contact.pug rename to src/presentation/views/page/extra/contact.pug diff --git a/src/views/page/extra/faq.pug b/src/presentation/views/page/extra/faq.pug similarity index 100% rename from src/views/page/extra/faq.pug rename to src/presentation/views/page/extra/faq.pug diff --git a/src/views/page/extra/feedback.pug b/src/presentation/views/page/extra/feedback.pug similarity index 100% rename from src/views/page/extra/feedback.pug rename to src/presentation/views/page/extra/feedback.pug diff --git a/src/views/page/extra/help.pug b/src/presentation/views/page/extra/help.pug similarity index 100% rename from src/views/page/extra/help.pug rename to src/presentation/views/page/extra/help.pug diff --git a/src/views/page/extra/privacy.pug b/src/presentation/views/page/extra/privacy.pug similarity index 100% rename from src/views/page/extra/privacy.pug rename to src/presentation/views/page/extra/privacy.pug diff --git a/src/views/page/extra/terms.pug b/src/presentation/views/page/extra/terms.pug similarity index 100% rename from src/views/page/extra/terms.pug rename to src/presentation/views/page/extra/terms.pug diff --git a/src/views/page/index copy/index.pug b/src/presentation/views/page/index copy/index.pug similarity index 100% rename from src/views/page/index copy/index.pug rename to src/presentation/views/page/index copy/index.pug diff --git a/src/views/page/index/index copy 2.pug b/src/presentation/views/page/index/index copy 2.pug similarity index 100% rename from src/views/page/index/index copy 2.pug rename to src/presentation/views/page/index/index copy 2.pug diff --git a/src/views/page/index/index copy.pug b/src/presentation/views/page/index/index copy.pug similarity index 100% rename from src/views/page/index/index copy.pug rename to src/presentation/views/page/index/index copy.pug diff --git a/src/views/page/index/index.pug b/src/presentation/views/page/index/index.pug similarity index 100% rename from src/views/page/index/index.pug rename to src/presentation/views/page/index/index.pug diff --git a/src/views/page/index/person.pug b/src/presentation/views/page/index/person.pug similarity index 100% rename from src/views/page/index/person.pug rename to src/presentation/views/page/index/person.pug diff --git a/src/views/page/login/index.pug b/src/presentation/views/page/login/index.pug similarity index 100% rename from src/views/page/login/index.pug rename to src/presentation/views/page/login/index.pug diff --git a/src/views/page/notice/index.pug b/src/presentation/views/page/notice/index.pug similarity index 100% rename from src/views/page/notice/index.pug rename to src/presentation/views/page/notice/index.pug diff --git a/src/views/page/profile/index.pug b/src/presentation/views/page/profile/index.pug similarity index 100% rename from src/views/page/profile/index.pug rename to src/presentation/views/page/profile/index.pug diff --git a/src/views/page/register/index.pug b/src/presentation/views/page/register/index.pug similarity index 100% rename from src/views/page/register/index.pug rename to src/presentation/views/page/register/index.pug diff --git a/src/services/README.md b/src/services/README.md deleted file mode 100644 index a9b4f8f..0000000 --- a/src/services/README.md +++ /dev/null @@ -1,222 +0,0 @@ -# 服务层 (Services) - -本目录包含了应用的所有业务逻辑服务层,负责处理业务规则、数据验证和错误处理。 - -## 服务列表 - -### 1. UserService - 用户服务 -处理用户相关的所有业务逻辑,包括用户注册、登录、密码管理等。 - -**主要功能:** -- 用户注册和登录 -- 用户信息管理(增删改查) -- 密码加密和验证 -- 用户统计和搜索 -- 批量操作支持 - -**使用示例:** -```javascript -import { userService } from '../services/index.js' - -// 用户注册 -const newUser = await userService.register({ - username: 'testuser', - email: 'test@example.com', - password: 'password123' -}) - -// 用户登录 -const loginResult = await userService.login({ - username: 'testuser', - password: 'password123' -}) -``` - -### 2. ArticleService - 文章服务 -处理文章相关的所有业务逻辑,包括文章的发布、编辑、搜索等。 - -**主要功能:** -- 文章的增删改查 -- 文章状态管理(草稿/发布) -- 文章搜索和分类 -- 阅读量统计 -- 相关文章推荐 -- 分页支持 - -**使用示例:** -```javascript -import { articleService } from '../services/index.js' - -// 创建文章 -const article = await articleService.createArticle({ - title: '测试文章', - content: '文章内容...', - category: '技术', - tags: 'JavaScript,Node.js' -}) - -// 获取已发布文章 -const publishedArticles = await articleService.getPublishedArticles() - -// 搜索文章 -const searchResults = await articleService.searchArticles('JavaScript') -``` - -### 3. BookmarkService - 书签服务 -处理用户书签的管理,包括添加、编辑、删除和搜索书签。 - -**主要功能:** -- 书签的增删改查 -- URL格式验证 -- 批量操作支持 -- 书签统计和搜索 -- 分页支持 - -**使用示例:** -```javascript -import { bookmarkService } from '../services/index.js' - -// 添加书签 -const bookmark = await bookmarkService.createBookmark({ - user_id: 1, - title: 'Google', - url: 'https://www.google.com', - description: '搜索引擎' -}) - -// 获取用户书签 -const userBookmarks = await bookmarkService.getUserBookmarks(1) - -// 搜索书签 -const searchResults = await bookmarkService.searchUserBookmarks(1, 'Google') -``` - -### 4. SiteConfigService - 站点配置服务 -管理站点的各种配置信息,如站点名称、描述、主题等。 - -**主要功能:** -- 配置的增删改查 -- 配置值验证 -- 批量操作支持 -- 默认配置初始化 -- 配置统计和搜索 - -**使用示例:** -```javascript -import { siteConfigService } from '../services/index.js' - -// 获取配置 -const siteName = await siteConfigService.get('site_name') - -// 设置配置 -await siteConfigService.set('site_name', '我的新网站') - -// 批量设置配置 -await siteConfigService.setMany({ - 'site_description': '网站描述', - 'posts_per_page': 20 -}) - -// 初始化默认配置 -await siteConfigService.initializeDefaultConfigs() -``` - -### 5. JobService - 任务服务 -处理后台任务和定时任务的管理。 - -**主要功能:** -- 任务调度和管理 -- 任务状态监控 -- 任务日志记录 - -## 错误处理 - -所有服务都使用统一的错误处理机制: - -```javascript -import CommonError from 'utils/error/CommonError.js' - -try { - const result = await userService.getUserById(1) -} catch (error) { - if (error instanceof CommonError) { - // 业务逻辑错误 - console.error(error.message) - } else { - // 系统错误 - console.error('系统错误:', error.message) - } -} -``` - -## 数据验证 - -服务层负责数据验证,确保数据的完整性和正确性: - -- **输入验证**:检查必填字段、格式验证等 -- **业务验证**:检查业务规则,如用户名唯一性 -- **权限验证**:确保用户只能操作自己的数据 - -## 事务支持 - -对于涉及多个数据库操作的方法,服务层支持事务处理: - -```javascript -// 在需要事务的方法中使用 -async createUserWithProfile(userData, profileData) { - // 这里可以添加事务支持 - const user = await this.createUser(userData) - // 创建用户档案... - return user -} -``` - -## 缓存策略 - -服务层可以集成缓存机制来提高性能: - -```javascript -// 示例:缓存用户信息 -async getUserById(id) { - const cacheKey = `user:${id}` - let user = await cache.get(cacheKey) - - if (!user) { - user = await UserModel.findById(id) - await cache.set(cacheKey, user, 3600) // 缓存1小时 - } - - return user -} -``` - -## 使用建议 - -1. **控制器层调用服务**:控制器应该调用服务层方法,而不是直接操作模型 -2. **错误处理**:在控制器中捕获服务层抛出的错误并返回适当的HTTP响应 -3. **数据转换**:服务层负责数据格式转换,控制器负责HTTP响应格式 -4. **业务逻辑**:复杂的业务逻辑应该放在服务层,保持控制器的简洁性 - -## 扩展指南 - -添加新的服务: - -1. 创建新的服务文件(如 `NewService.js`) -2. 继承或实现基础服务接口 -3. 在 `index.js` 中导出新服务 -4. 添加相应的测试用例 -5. 更新文档 - -```javascript -// 新服务示例 -class NewService { - async doSomething(data) { - try { - // 业务逻辑 - return result - } catch (error) { - throw new CommonError(`操作失败: ${error.message}`) - } - } -} -``` diff --git a/src/services/index.js b/src/services/index.js deleted file mode 100644 index db42d64..0000000 --- a/src/services/index.js +++ /dev/null @@ -1,36 +0,0 @@ -// 服务层统一导出 -import UserService from "./UserService.js" -import ArticleService from "./ArticleService.js" -import BookmarkService from "./BookmarkService.js" -import SiteConfigService from "./SiteConfigService.js" -import JobService from "./JobService.js" - -// 导出所有服务类 -export { - UserService, - ArticleService, - BookmarkService, - SiteConfigService, - JobService -} - -// 导出默认实例(单例模式) -export const userService = new UserService() -export const articleService = new ArticleService() -export const bookmarkService = new BookmarkService() -export const siteConfigService = new SiteConfigService() -export const jobService = new JobService() - -// 默认导出 -export default { - UserService, - ArticleService, - BookmarkService, - SiteConfigService, - JobService, - userService, - articleService, - bookmarkService, - siteConfigService, - jobService -} diff --git a/src/utils/BaseSingleton.js b/src/shared/utils/BaseSingleton.js similarity index 100% rename from src/utils/BaseSingleton.js rename to src/shared/utils/BaseSingleton.js diff --git a/src/utils/ForRegister.js b/src/shared/utils/ForRegister.js similarity index 92% rename from src/utils/ForRegister.js rename to src/shared/utils/ForRegister.js index 39b1b70..635d30c 100644 --- a/src/utils/ForRegister.js +++ b/src/shared/utils/ForRegister.js @@ -2,7 +2,7 @@ // 兼容传统 routes 方式和自动注册 controller 方式 import fs from "fs" import path from "path" -import { logger } from "@/logger.js" +import { logger } from "../../app/bootstrap/logger.js" // 保证不会被摇树(tree-shaking),即使在生产环境也会被打包 if (import.meta.env.PROD) { @@ -20,10 +20,10 @@ if (import.meta.env.PROD) { * @param {string} prefix - 路由前缀 * @param {Set} [manualControllers] - 可选,手动传入已注册 controller 文件名集合,优先于自动扫描 */ -export function autoRegisterControllers(app, controllersDir) { +export async function autoRegisterControllers(app, controllersDir) { let allRouter = [] - function scan(dir, routePrefix = "") { + async function scan(dir, routePrefix = "") { try { for (const file of fs.readdirSync(dir)) { const fullPath = path.join(dir, file) @@ -31,12 +31,12 @@ export function autoRegisterControllers(app, controllersDir) { if (stat.isDirectory()) { if (!file.startsWith("_")) { - scan(fullPath, routePrefix + "/" + file) + await scan(fullPath, routePrefix + "/" + file) } } else if (file.endsWith("Controller.js") && !file.startsWith("_")) { try { - // 使用同步的import方式,确保ES模块兼容性 - const controllerModule = require(fullPath) + // 使用动态import方式,确保ES模块兼容性 + const controllerModule = await import(`file://${fullPath}`) const controller = controllerModule.default || controllerModule if (!controller) { @@ -72,7 +72,7 @@ export function autoRegisterControllers(app, controllersDir) { } try { - scan(controllersDir) + await scan(controllersDir) if (allRouter.length === 0) { logger.warn("[路由注册] ⚠️ 未发现任何可注册的控制器") diff --git a/src/utils/bcrypt.js b/src/shared/utils/bcrypt.js similarity index 100% rename from src/utils/bcrypt.js rename to src/shared/utils/bcrypt.js diff --git a/src/utils/envValidator.js b/src/shared/utils/envValidator.js similarity index 98% rename from src/utils/envValidator.js rename to src/shared/utils/envValidator.js index fc9fb03..590c829 100644 --- a/src/utils/envValidator.js +++ b/src/shared/utils/envValidator.js @@ -1,4 +1,4 @@ -import { logger } from "@/logger.js" +import { logger } from "../../app/bootstrap/logger.js" /** * 环境变量验证配置 diff --git a/src/utils/error/CommonError.js b/src/shared/utils/error/CommonError.js similarity index 100% rename from src/utils/error/CommonError.js rename to src/shared/utils/error/CommonError.js diff --git a/src/utils/helper.js b/src/shared/utils/helper.js similarity index 91% rename from src/utils/helper.js rename to src/shared/utils/helper.js index ffa829b..d3333cb 100644 --- a/src/utils/helper.js +++ b/src/shared/utils/helper.js @@ -1,4 +1,4 @@ -import { app } from "@/global" +import { app } from "../../app/bootstrap/app.js" function ResponseSuccess(data = null, message = null) { return { success: true, error: message, data } diff --git a/src/utils/router.js b/src/shared/utils/router.js similarity index 100% rename from src/utils/router.js rename to src/shared/utils/router.js diff --git a/src/utils/router/RouteAuth.js b/src/shared/utils/router/RouteAuth.js similarity index 90% rename from src/utils/router/RouteAuth.js rename to src/shared/utils/router/RouteAuth.js index d1a4e83..bbe6f4c 100644 --- a/src/utils/router/RouteAuth.js +++ b/src/shared/utils/router/RouteAuth.js @@ -1,5 +1,5 @@ -import jwt from "@/middlewares/Auth/jwt.js" -import { JWT_SECRET } from "@/middlewares/Auth/auth.js" +import jwt from "../../../presentation/middlewares/Auth/jwt.js" +import { JWT_SECRET } from "../../../presentation/middlewares/Auth/auth.js" /** * 路由级权限中间件 diff --git a/src/utils/scheduler.js b/src/shared/utils/scheduler.js similarity index 100% rename from src/utils/scheduler.js rename to src/shared/utils/scheduler.js