├── index.js ├── config ├── plugin.js └── config.default.js ├── test ├── fixtures │ └── example │ │ ├── config │ │ └── config.unittest.js │ │ ├── package.json │ │ └── app │ │ ├── service │ │ ├── user.js │ │ ├── schema.js │ │ └── mixin.js │ │ ├── schema │ │ └── user.js │ │ ├── controller │ │ ├── home.js │ │ └── mixin.js │ │ └── router.js └── framework.test.js ├── .gitignore ├── app ├── service │ └── egg-mongoose-base-service.js └── controller │ └── egg-mongoose-base-controller.js ├── .travis.yml ├── .vscode └── settings.json ├── appveyor.yml ├── prettier.config.mjs ├── eslint.config.mjs ├── package.json ├── README.md └── lib └── framework.js /index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = require('./lib/framework.js'); 4 | -------------------------------------------------------------------------------- /config/plugin.js: -------------------------------------------------------------------------------- 1 | exports.mongoose = { 2 | enable: true, 3 | package: 'egg-mongoose', 4 | }; 5 | -------------------------------------------------------------------------------- /test/fixtures/example/config/config.unittest.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | exports.keys = '123456'; 4 | -------------------------------------------------------------------------------- /test/fixtures/example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "framework-example", 3 | "version": "1.0.0" 4 | } -------------------------------------------------------------------------------- /test/fixtures/example/app/service/user.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | getUsername() { 3 | return 'test123'; 4 | }, 5 | }; 6 | -------------------------------------------------------------------------------- /test/fixtures/example/app/schema/user.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | username: { 3 | type: String, 4 | name: '用户名', 5 | }, 6 | }; 7 | -------------------------------------------------------------------------------- /test/fixtures/example/app/controller/home.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | async index() { 3 | this.ctx.body = 'hello world.'; 4 | }, 5 | }; 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | logs/ 2 | npm-debug.log 3 | node_modules/ 4 | coverage/ 5 | .idea/ 6 | run/ 7 | .DS_Store 8 | *.swp 9 | package-lock.json 10 | 11 | -------------------------------------------------------------------------------- /app/service/egg-mongoose-base-service.js: -------------------------------------------------------------------------------- 1 | const { Service } = require('egg'); 2 | 3 | class BaseService extends Service {} 4 | 5 | module.exports = BaseService; 6 | -------------------------------------------------------------------------------- /app/controller/egg-mongoose-base-controller.js: -------------------------------------------------------------------------------- 1 | const { Controller } = require('egg'); 2 | 3 | class BaseController extends Controller {} 4 | 5 | module.exports = BaseController; 6 | -------------------------------------------------------------------------------- /test/fixtures/example/app/router.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = (app) => { 4 | const { router, controller } = app; 5 | 6 | router.get('/', controller.home.index); 7 | router.resources('user', '/api/user', controller.user); 8 | }; 9 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | 2 | language: node_js 3 | node_js: 4 | - '8' 5 | - '9' 6 | before_install: 7 | - npm i npminstall -g 8 | install: 9 | - npminstall 10 | script: 11 | - npm run ci 12 | after_script: 13 | - npminstall codecov && codecov 14 | -------------------------------------------------------------------------------- /config/config.default.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = () => { 4 | const config = {}; 5 | 6 | config.schema = { 7 | service: true, // 是否自动生成 service 8 | controller: true, // 是否自动生成 controller 9 | }; 10 | 11 | return config; 12 | }; 13 | -------------------------------------------------------------------------------- /test/fixtures/example/app/service/schema.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | customMethod() { 3 | return 'sync'; 4 | }, 5 | async asyncCustomMethod() { 6 | return 'async'; 7 | }, 8 | thisCustomMethod() { 9 | return this.customMethod(); 10 | }, 11 | }; 12 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.formatOnSave": true, // 优先使用 prettier-eslint 3 | 4 | // eslint 插件配置部分 5 | "editor.codeActionsOnSave": { 6 | "source.fixAll.eslint": "explicit" 7 | }, 8 | "html.format.indentHandlebars": true, 9 | "html.format.indentInnerHtml": true, 10 | "vetur.format.defaultFormatter.html": "none" 11 | } 12 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | environment: 2 | matrix: 3 | - nodejs_version: '8' 4 | - nodejs_version: '9' 5 | 6 | install: 7 | - ps: Install-Product node $env:nodejs_version 8 | - npm i npminstall && node_modules\.bin\npminstall 9 | 10 | test_script: 11 | - node --version 12 | - npm --version 13 | - npm run test 14 | 15 | build: off 16 | -------------------------------------------------------------------------------- /prettier.config.mjs: -------------------------------------------------------------------------------- 1 | /** 2 | * @type {import('prettier').Config} 3 | * @see https://www.prettier.cn/docs/options.html 4 | */ 5 | export default { 6 | trailingComma: 'all', // 在多行逗号分隔的语法结构中,尽可能打印尾随逗号。 7 | singleQuote: true, // 使用单引号代替双引号。 8 | semi: true, // 在语句末尾打印分号。 9 | printWidth: 80, // 指定打印换行的行长度。 10 | arrowParens: 'always', // 在唯一的箭头函数参数周围加上括号。 11 | proseWrap: 'always', // 如果文本超出打印宽度,则换行。 12 | endOfLine: 'lf', // 使用 \n 作为换行符。 13 | experimentalTernaries: false, // 使用好奇三元组,在条件后面加上问号。 14 | tabWidth: 2, // 指定每个缩进级别的空格数。 15 | useTabs: false, // 使用制表符而不是空格来缩进行。 16 | quoteProps: 'consistent', // 如果对象中至少有一个属性需要引号,则将所有属性都加引号。 17 | jsxSingleQuote: false, // 在 JSX 中使用单引号而不是双引号。 18 | bracketSpacing: true, // 打印对象文字中括号之间的空格。 19 | bracketSameLine: false, // 将>多行 HTML(HTML、JSX、Vue、Angular)元素的放在最后一行的末尾,而不是单独放在下一行(不适用于自闭合元素)。 20 | singleAttributePerLine: false, // 在 HTML、Vue 和 JSX 中强制每行使用单个属性。 21 | embeddedLanguageFormatting: 'auto', // 控制 Prettier 是否格式化文件中嵌入的引用代码。 22 | }; 23 | -------------------------------------------------------------------------------- /test/fixtures/example/app/service/mixin.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | async create(payload) { 3 | await this.model.create(payload); 4 | }, 5 | async update(id, payload) { 6 | return await this.model.findOneAndUpdate(id, payload, { 7 | new: true, 8 | }); 9 | }, 10 | async destroy(id) { 11 | await this.model.findByIdAndRemove(id); 12 | }, 13 | async index(payload = {}) { 14 | let { isPaging, pageSize = 10, current = 1, search = {} } = payload || {}; 15 | pageSize = Number(pageSize) || 10; 16 | current = Number(current) || 1; 17 | const skip = (current - 1) * pageSize; 18 | let query = this.model.find(search); 19 | if (isPaging) { 20 | query = query.skip(skip).limit(pageSize).sort({ _id: -1 }); 21 | } 22 | const res = await query.exec(); 23 | const total = await this.model.countDocuments(search).exec(); 24 | return { 25 | list: res.map((e) => { 26 | return { key: e._doc._id, ...e._doc }; 27 | }), 28 | pagination: isPaging ? { total, pageSize, current } : { total }, 29 | }; 30 | }, 31 | }; 32 | -------------------------------------------------------------------------------- /eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import globals from 'globals'; 2 | import pluginJs from '@eslint/js'; 3 | import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended'; 4 | 5 | export default [ 6 | { files: ['**/*.js'], languageOptions: { sourceType: 'commonjs' } }, 7 | { 8 | languageOptions: { 9 | globals: { 10 | ...globals.browser, 11 | ...globals.node, // node 环境全局变量 12 | ...globals.mocha, // mocha 测试框架全局变量 13 | /** 追加一些其他自定义全局规则 */ 14 | // wx: true, 15 | }, 16 | }, 17 | }, 18 | pluginJs.configs.recommended, 19 | eslintPluginPrettierRecommended, 20 | { 21 | files: ['**/*.{js,mjs,cjs,vue}'], 22 | rules: { 23 | 'no-console': 0, // 禁用 console 24 | 'no-unused-vars': 0, // 禁止出现未使用过的变量 25 | 'no-async-promise-executor': 0, // 禁止使用异步函数作为 Promise executor 26 | 'prefer-template': 'error', // 强制使用模板字符串而不是字符串拼接 27 | 'template-curly-spacing': ['error', 'never'], // 强制在模板字符串中使用一致的间距 28 | 'quotes': ['error', 'single'], // 强制使用单引号 29 | }, 30 | }, 31 | { 32 | ignores: ['node_modules'], 33 | }, 34 | // ...其他配置 35 | ]; 36 | -------------------------------------------------------------------------------- /test/framework.test.js: -------------------------------------------------------------------------------- 1 | const mock = require('egg-mock'); 2 | const assert = require('assert'); 3 | describe('test/framework.test.js', () => { 4 | let app; 5 | before(() => { 6 | app = mock.app({ 7 | baseDir: 'example', 8 | framework: true, 9 | }); 10 | return app.ready(); 11 | }); 12 | 13 | after(() => app.close()); 14 | 15 | afterEach(mock.restore); 16 | 17 | it('should GET /', () => { 18 | return app.httpRequest().get('/').expect('hello world.'); 19 | }); 20 | 21 | it('schame', () => { 22 | const ctx = app.mockContext({}); 23 | assert.equal(typeof ctx.service.schema.customMethod, 'function'); 24 | assert.equal(typeof ctx.service.schema.asyncCustomMethod, 'function'); 25 | assert.equal(ctx.service.schema.thisCustomMethod(), 'sync'); 26 | }); 27 | 28 | it('user', async () => { 29 | const ctx = app.mockContext({}); 30 | 31 | app.httpRequest().get('/api/user').expect(200); 32 | 33 | assert.equal(typeof ctx.service.user.index, 'function'); 34 | assert.equal(ctx.service.user.getUsername(), 'test123'); 35 | assert.equal(ctx.service.user.name, 'user'); 36 | }); 37 | }); 38 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "egg-framework-mongoose", 3 | "version": "1.1.2", 4 | "description": "", 5 | "dependencies": { 6 | "egg": "^3.28.0", 7 | "egg-mongoose": "^4.0.1" 8 | }, 9 | "devDependencies": { 10 | "egg-bin": "^6.10.0", 11 | "egg-mock": "^5.12.5", 12 | "eslint": "^9.10.0", 13 | "eslint-config-prettier": "^9.1.0", 14 | "eslint-plugin-prettier": "^5.2.1", 15 | "globals": "^15.9.0", 16 | "prettier": "^3.3.3", 17 | "@eslint/js": "^9.10.0" 18 | }, 19 | "engines": { 20 | "node": ">=18.0.0" 21 | }, 22 | "scripts": { 23 | "test": "npm run lint -- --fix && npm run test-local", 24 | "test-local": "egg-bin test", 25 | "cov": "egg-bin cov", 26 | "lint": "eslint .", 27 | "ci": "npm run lint && npm run cov" 28 | }, 29 | "repository": { 30 | "type": "git", 31 | "url": "git+https://github.com/yisbug/egg-mongoose-framework.git" 32 | }, 33 | "keywords": [ 34 | "egg", 35 | "egg-framework" 36 | ], 37 | "author": "yisbug", 38 | "files": [ 39 | "index.js", 40 | "lib", 41 | "app", 42 | "config" 43 | ], 44 | "eslintIgnore": [ 45 | "coverage", 46 | "dist" 47 | ], 48 | "license": "MIT" 49 | } 50 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # egg-framework-mongoose 2 | 3 | 项目重新命名为 egg-framework-mongoose 4 | 5 | - 20240921 更新最新版本,原 npm 账号丢失,更换项目名称 6 | 7 | 使用示例:https://github.com/yisbug/egg-mongoose-curd 8 | 9 | ### 特性 10 | 11 | - 自动加载 app/schema 目录中,按照 mongoose 的 schema 规范定义的 schema 文件。 12 | - 根据 app/schema 中的配置自动生成同名 service 和 controller,并可直接在 13 | router.js 中使用对应的 controller。 14 | - 按照约定,controller 和 service 目录中,可直接使用 module.exports = {} 定义, 15 | 不再需要继承 egg.Service 和 egg.Controller。 16 | - 可自定义 app/service/mixin.js 和 app/controller/mixin.js,框架将自动挂载对应的 17 | 方法到根据 schema 生成的 service 和 controller 中,可以非常方便的实现对应 18 | schema 的 RESTful 接口。 19 | 20 | ### 配置 21 | 22 | ```js 23 | config.schema = { 24 | service: true, // 是否自动生成 service 25 | controller: true, // 是否自动生成 controller 26 | }; 27 | ``` 28 | 29 | ### 使用教程 30 | 31 | 1. npm install egg-mongoose-framework 32 | 2. 在项目 package.json 中定义 egg 字段,指定 framework。 33 | 34 | package.json: 35 | 36 | ``` 37 | { 38 | "egg": { 39 | "framework": "egg-mongoose-framework" 40 | }, 41 | } 42 | 43 | ``` 44 | 45 | 3. 定义 app/schema 文件,例如:user.js 46 | 4. 定义 app/controller/mixin.js,其中定义 create、destroy、update、show、index 5 47 | 个方法 48 | 5. router.js 中定义路由: 49 | 50 | ``` 51 | router.resources('user', '/api/user', controller.user); 52 | ``` 53 | 54 | 6. 此时已可使用 `/api/user` 这个路由调用对应的 RESTful 接口, 55 | -------------------------------------------------------------------------------- /test/fixtures/example/app/controller/mixin.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | getService() { 3 | const service = this.service[this.name]; 4 | if (!service) throw new Error(`没有找到对应的service:${this.name}`); 5 | return service; 6 | }, 7 | 8 | // 创建 9 | async create() { 10 | const { ctx } = this; 11 | if (Object.keys(this.createRule).length) { 12 | ctx.validate(this.createRule); 13 | } 14 | const payload = ctx.request.body || {}; 15 | const res = await this.getService().create(payload); 16 | ctx.success(res); 17 | }, 18 | 19 | // 删除单个 20 | async destroy() { 21 | const { ctx } = this; 22 | const { id } = ctx.params; 23 | await this.getService().destroy(id); 24 | ctx.success(); 25 | }, 26 | 27 | // 修改 28 | async update() { 29 | const { ctx } = this; 30 | if (Object.keys(this.createRule).length) { 31 | ctx.validate(this.createRule); 32 | } 33 | const { id } = ctx.params; 34 | const payload = ctx.request.body || {}; 35 | const doc = await this.getService().update(id, payload); 36 | ctx.success(doc); 37 | }, 38 | 39 | // 获取单个 40 | async show() { 41 | const { ctx } = this; 42 | const { id } = ctx.params; 43 | const doc = await this.getService().show(id); 44 | ctx.success(doc); 45 | }, 46 | 47 | // 获取所有(分页/模糊) 48 | async index() { 49 | const { ctx } = this; 50 | // 组装参数 51 | const { pageSize = 10, current = 1, ...query } = ctx.query; 52 | const isPaging = ctx.query.isPaging !== 'false'; 53 | delete query.isPaging; 54 | const search = this.getSearchObject(query); 55 | const res = await this.getService().index({ 56 | isPaging, 57 | pageSize, 58 | current, 59 | search, 60 | }); 61 | ctx.success(res); 62 | }, 63 | getSearchObject(query) { 64 | const { ctx } = this; 65 | const search = {}; 66 | Object.keys(query).forEach((key) => { 67 | const value = query[key]; 68 | if (typeof value === 'undefined') return; 69 | const prefix = key[0]; 70 | if (prefix === '$') { 71 | key = key.substr(1); 72 | } 73 | const schema = this.getService().schema[key]; 74 | if (!schema) return; 75 | // if (!schema) ctx.error(500, `controller.${this.name}中找不到字段${key}对应的schema`); 76 | const type = schema.type || schema; 77 | if (prefix === '$') { 78 | if (type === String) { 79 | search[key] = { $regex: new RegExp(value, 'gi') }; 80 | } else { 81 | try { 82 | const searchObj = JSON.parse(value); 83 | search[key] = {}; 84 | const arr = ['undefined', 'object']; 85 | if (!arr.includes(typeof searchObj.$gt)) { 86 | search[key].$gt = searchObj.$gt; 87 | } 88 | if (!arr.includes(typeof searchObj.$gte)) { 89 | search[key].$gte = searchObj.$gte; 90 | } 91 | if (!arr.includes(typeof searchObj.$lt)) { 92 | search[key].$lt = searchObj.$lt; 93 | } 94 | if (!arr.includes(typeof searchObj.$lte)) { 95 | search[key].$lte = searchObj.$lte; 96 | } 97 | if (type === Date) { 98 | search[key] = Object.keys(search[key]).reduce((o, item) => { 99 | o[item] = new Date(search[key][item]); 100 | return o; 101 | }, {}); 102 | } 103 | } catch (e) { 104 | ctx.error(400, `搜索参数格式错误:${value}`); 105 | } 106 | } 107 | } else if (type instanceof Date) { 108 | search[key] = new Date(value); 109 | } else if (type instanceof String) { 110 | search[key] = String(value); 111 | } else { 112 | search[key] = value; 113 | } 114 | }); 115 | return search; 116 | }, 117 | }; 118 | -------------------------------------------------------------------------------- /lib/framework.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const egg = require('egg'); 3 | 4 | const EGG_PATH = Symbol.for('egg#eggPath'); 5 | const EGG_LOADER = Symbol.for('egg#loader'); 6 | 7 | function callFn(fn, args, ctx) { 8 | args = args || []; 9 | return ctx ? fn.call(ctx, ...args) : fn(...args); 10 | } 11 | 12 | // wrap the class, yield a object with middlewares 13 | function wrapClass(Controller) { 14 | let proto = Controller.prototype; 15 | const ret = {}; 16 | // tracing the prototype chain 17 | while (proto !== Object.prototype) { 18 | const keys = Object.getOwnPropertyNames(proto); 19 | for (const key of keys) { 20 | // getOwnPropertyNames will return constructor 21 | // that should be ignored 22 | if (key === 'constructor') { 23 | continue; 24 | } 25 | // skip getter, setter & non-function properties 26 | const d = Object.getOwnPropertyDescriptor(proto, key); 27 | // prevent to override sub method 28 | // eslint-disable-next-line 29 | if (typeof d.value === 'function' && !ret.hasOwnProperty(key)) { 30 | ret[key] = methodToMiddleware(Controller, key); 31 | } 32 | } 33 | proto = Object.getPrototypeOf(proto); 34 | } 35 | return ret; 36 | 37 | function methodToMiddleware(Controller, key) { 38 | return function classControllerMiddleware(...args) { 39 | const controller = new Controller(this); 40 | if ( 41 | !this.app.config.controller || 42 | !this.app.config.controller.supportParams 43 | ) { 44 | args = [this]; 45 | } 46 | return callFn(controller[key], args, controller); 47 | }; 48 | } 49 | } 50 | 51 | // loader 加载顺序: 52 | // package.json -> config/plugin.* -> config/config.* -> app/extend/application 53 | // -> app/extend/request -> app/extend/response -> app/extend/context -> app/extend/helper 54 | // -> agent.js -> app.js -> app/service -> app/middleware -> app/controller 55 | // -> router.js 56 | 57 | // 文件顺序 58 | // 上面已经列出了默认会加载的文件,Egg 会按如下文件顺序加载,每个文件或目录再根据 loadUnit 的顺序去加载(应用、框架、插件各有不同)。 59 | 60 | // 加载 plugin,找到应用和框架,加载 config/plugin.js 61 | // 加载 config,遍历 loadUnit 加载 config/config.{env}.js 62 | // 加载 extend,遍历 loadUnit 加载 app/extend/xx.js 63 | // 自定义初始化,遍历 loadUnit 加载 app.js 和 agent.js 64 | // 加载 service,遍历 loadUnit 加载 app/service 目录 65 | // 加载 middleware,遍历 loadUnit 加载 app/middleware 目录 66 | // 加载 controller,加载应用的 app/controller 目录 。 =======注: 只加载应用的controller 67 | // 加载 router,加载应用的 app/router.js 。 =======注: 只加载应用的router 68 | 69 | // 所以这里应该在加载controller结束后再去加载schema,并注入到service,controller 70 | 71 | // app > plugin > core 72 | // this.loadApplicationExtend(); 73 | // this.loadRequestExtend(); 74 | // this.loadResponseExtend(); 75 | // this.loadContextExtend(); 76 | // this.loadHelperExtend(); 77 | 78 | // this.loadCustomLoader(); 79 | 80 | // // app > plugin 81 | // this.loadCustomApp(); 82 | // // app > plugin 83 | // this.loadService(); 84 | // // app > plugin > core 85 | // this.loadMiddleware(); 86 | // // app 87 | // this.loadController(); 88 | // // app 89 | // this.loadRouter(); // Dependent on controllers 90 | 91 | class AppWorkerLoader extends egg.AppWorkerLoader { 92 | load() { 93 | this.loadSchema(); 94 | super.load(); 95 | // 自己扩展 96 | } 97 | loadSchema() { 98 | const { app } = this; 99 | // 处理 schema 100 | const schemaPaths = this.getLoadUnits().map((unit) => 101 | path.join(unit.path, 'app/schema'), 102 | ); 103 | // 先加载schema 104 | if (this.app.config.schema) { 105 | this.loadToApp(schemaPaths, 'schema'); 106 | } 107 | // 根据schema生成对应的service和controller 108 | Object.keys(app.schema).forEach((name) => { 109 | // 添加基础字段 110 | if (app.config.schema.extendBaseFields) { 111 | Object.assign(app.schema[name], app.config.schema.baseFields); 112 | } 113 | 114 | // 扩展字段 115 | if (app.config.schema.extend && app.config.schema.extend[name]) { 116 | Object.assign(app.schema[name], app.config.schema.extend[name]); 117 | } 118 | }); 119 | } 120 | loadService(opt) { 121 | const { app } = this; 122 | super.loadService(opt); 123 | app.serviceObjects = {}; 124 | Object.keys(app.serviceClasses).forEach((key) => { 125 | app.serviceObjects[key] = 126 | app.serviceObjects[key] || app.serviceClasses[key]; 127 | }); 128 | // 处理 schema 定义的 service 129 | Object.keys(app.schema).forEach((name) => { 130 | if (!app.config.schema.service) return; // 是否开启自动生成 service,默认开启 131 | app.serviceClasses[name] = this.createSchemaService(name); 132 | Object.assign( 133 | app.serviceClasses[name].prototype, 134 | app.serviceObjects.mixin, // 集成 mixin 135 | app.serviceObjects[name], // 集成自定义方法 136 | ); 137 | }); 138 | // 处理剩余 service,支持 module.exports = {} 的写法 139 | Object.keys(app.serviceObjects).forEach((name) => { 140 | if (name === 'mixin') return; 141 | if (app.schema[name]) return; 142 | const service = app.serviceClasses[name]; 143 | if (typeof service === 'function') { 144 | app.serviceClasses[name] = service; 145 | } else { 146 | app.serviceClasses[name] = this.createEmptyService(); 147 | Object.assign(app.serviceClasses[name].prototype, service); 148 | } 149 | }); 150 | delete app.serviceClasses.mixin; // 删除 mixin 151 | } 152 | 153 | loadController(opt) { 154 | // egg框架内使用逻辑见: egg/lib/loader line:41 没有参数,加载默认app/controller逻辑 155 | // 有opt参数,用于其他地方(eg:插件内)加载,调用原方法 156 | if (opt) { 157 | return super.loadController(opt); 158 | } 159 | const { app } = this; 160 | // 处理 controller,先把原始controller存放到 controllerObjects 161 | const controllerPaths = this.getLoadUnits().map((unit) => 162 | path.join(unit.path, 'app/controller'), 163 | ); 164 | this.loadToApp(controllerPaths, 'controllerObjects'); 165 | app.controllerObjects = app.controllerObjects || {}; 166 | 167 | app.controller = app.controller || {}; 168 | // 遍历 schema 169 | Object.keys(app.schema).forEach((name) => { 170 | // 处理 schema 定义的 controller 171 | if (!app.config.schema.controller) return; // 是否开启自动生成 service,默认开启 172 | const Controller = this.createSchemaController(name); 173 | // 集成 mixin 和 自定义方法 174 | Object.assign( 175 | Controller.prototype, 176 | app.controllerObjects.mixin, 177 | app.controllerObjects[name], 178 | ); 179 | app.controller[name] = wrapClass(Controller); 180 | }); 181 | // 处理剩余的 controller 182 | Object.keys(app.controllerObjects).forEach((name) => { 183 | if (app.controller[name]) return; 184 | if (name === 'eggMongooseBaseController') return; 185 | if (name === 'mixin') return; 186 | // 使用 class 定义的 controller 187 | if (typeof app.controllerObjects[name] === 'function') { 188 | app.controller[name] = wrapClass(app.controllerObjects[name]); 189 | } else { 190 | // 使用 module.exports 定义的 controller 191 | const Controller = this.createEmptyController(); 192 | Object.assign(Controller.prototype, app.controllerObjects[name]); 193 | app.controller[name] = wrapClass(Controller); 194 | } 195 | }); 196 | } 197 | createSchemaService(name) { 198 | class NewService extends this.app.serviceClasses.eggMongooseBaseService { 199 | get name() { 200 | return name; 201 | } 202 | get model() { 203 | const { app } = this; 204 | if (app.model[this.name]) return app.model[this.name]; 205 | const model = app.mongoose.model( 206 | this.name, 207 | new app.mongoose.Schema(app.schema[this.name]), 208 | ); 209 | if (!model) throw new Error(`没有找到对应的model:${this.name}`); 210 | app.model[this.name] = model; 211 | return app.model[this.name]; 212 | } 213 | 214 | get modelback() { 215 | const { app } = this; 216 | const name = `${this.name}_back`; 217 | console.log('app', app, this, this.app, this.ctx); 218 | if (app.model[name]) return app.model[name]; 219 | const model = app.mongoose.model( 220 | name, 221 | new app.mongoose.Schema(app.schema[this.name]), 222 | ); 223 | if (!model) throw new Error(`没有找到对应的model:${this.name}`); 224 | app.model[name] = model; 225 | return app.model[name]; 226 | } 227 | 228 | get schema() { 229 | return this.app.schema[name]; 230 | } 231 | } 232 | return NewService; 233 | } 234 | createEmptyService() { 235 | class NewService extends this.app.serviceClasses.eggMongooseBaseService {} 236 | return NewService; 237 | } 238 | createSchemaController(name) { 239 | class NewController extends this.app.controllerObjects 240 | .eggMongooseBaseController { 241 | get name() { 242 | return name; 243 | } 244 | get $service() { 245 | return this.service[name]; 246 | } 247 | get myservice() { 248 | return this.service[name]; 249 | } 250 | } 251 | return NewController; 252 | } 253 | createEmptyController() { 254 | class NewController extends this.app.controllerObjects 255 | .eggMongooseBaseController {} 256 | return NewController; 257 | } 258 | } 259 | 260 | class Application extends egg.Application { 261 | get [EGG_PATH]() { 262 | return path.dirname(__dirname); 263 | } 264 | // 覆盖 Egg 的 Loader,启动时使用这个 Loader 265 | get [EGG_LOADER]() { 266 | return AppWorkerLoader; 267 | } 268 | } 269 | 270 | class Agent extends egg.Agent { 271 | get [EGG_PATH]() { 272 | return path.dirname(__dirname); 273 | } 274 | } 275 | 276 | module.exports = Object.assign(egg, { 277 | Application, 278 | Agent, 279 | // 自定义的 Loader 也需要 export,上层框架需要基于这个扩展 280 | AppWorkerLoader, 281 | }); 282 | --------------------------------------------------------------------------------