├── .eslintrc.js ├── .gitignore ├── README.md ├── app.js ├── auto.js ├── bin ├── api └── debug ├── config ├── common.js ├── error.js ├── host.js ├── imgUrl.js ├── redis.js └── sql.js ├── controller └── base │ └── test │ ├── dbDel.js │ ├── dbFindAll.js │ ├── dbFindAndCountAll.js │ ├── dbFindOne.js │ ├── dbInsert.js │ ├── dbUpdate.js │ ├── index.js │ ├── index2.js │ ├── index3.js │ ├── index4.js │ ├── redisGet.js │ └── redisInsert.js ├── core ├── ctx.js ├── db.js ├── icefire │ ├── base.js │ ├── getParams.js │ ├── index.js │ ├── onerror.js │ └── service.js ├── logger.js ├── prototype.js ├── redis.js ├── routeEach.js └── schedule.js ├── extend └── context.js ├── lib └── timoSign │ ├── checkSign.js │ ├── getSign.js │ ├── index.js │ ├── md5.js │ ├── objToString.js │ └── package.json ├── middlewares ├── oauth.js └── sign.js ├── package.json ├── public ├── favicon.ico └── stylesheets │ └── style.css ├── schedule └── first.js ├── service ├── base.js ├── like │ └── book.js └── test.js ├── sql └── timotest.sql ├── tool ├── index.js ├── permissionList.js └── xmlTool.js └── views ├── error.pug ├── index.pug └── layout.pug /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "env": { 3 | "browser": true, 4 | "node": true, 5 | "commonjs": true, 6 | "es6": true 7 | }, 8 | "ecmaFeatures": { 9 | // lambda表达式 10 | "arrowFunctions": true, 11 | // 解构赋值 12 | "destructuring": true, 13 | // class 14 | "classes": true, 15 | // http://es6.ruanyifeng.com/#docs/function#函数参数的默认值 16 | "defaultParams": true, 17 | // 块级作用域,允许使用let const 18 | "blockBindings": true, 19 | // 允许使用模块,模块内默认严格模式 20 | "modules": true, 21 | // 允许字面量定义对象时,用表达式做属性名 22 | // http://es6.ruanyifeng.com/#docs/object#属性名表达式 23 | "objectLiteralComputedProperties": true, 24 | // 允许对象字面量方法名简写 25 | /*var o = { 26 | method() { 27 | return "Hello!"; 28 | } 29 | }; 30 | 31 | 等同于 32 | 33 | var o = { 34 | method: function() { 35 | return "Hello!"; 36 | } 37 | }; 38 | */ 39 | "objectLiteralShorthandMethods": true, 40 | /* 41 | 对象字面量属性名简写 42 | var foo = 'bar'; 43 | var baz = {foo}; 44 | baz // {foo: "bar"} 45 | 46 | // 等同于 47 | var baz = {foo: foo}; 48 | */ 49 | "objectLiteralShorthandProperties": true, 50 | // http://es6.ruanyifeng.com/#docs/function#rest参数 51 | "restParams": true, 52 | // http://es6.ruanyifeng.com/#docs/function#扩展运算符 53 | "spread": true, 54 | // http://es6.ruanyifeng.com/#docs/iterator#for---of循环 55 | "forOf": true, 56 | // http://es6.ruanyifeng.com/#docs/generator 57 | "generators": true, 58 | // http://es6.ruanyifeng.com/#docs/string#模板字符串 59 | "templateStrings": true, 60 | "superInFunctions": true, 61 | // http://es6.ruanyifeng.com/#docs/object#对象的扩展运算符 62 | "experimentalObjectRestSpread": true, //es6 63 | }, 64 | "parserOptions": { 65 | "ecmaVersion": 6, //es6写法 66 | }, 67 | "parser": "babel-eslint", 68 | "rules": { 69 | // 定义对象的set存取器属性时,强制定义get 70 | "accessor-pairs": 2, 71 | // 指定数组的元素之间要以空格隔开(,后面), never参数:[ 之前和 ] 之后不能带空格,always参数:[ 之前和 ] 之后必须带空格 72 | "array-bracket-spacing": [2, "never"], 73 | // 在块级作用域外访问块内定义的变量是否报错提示 74 | "block-scoped-var": 0, 75 | // if while function 后面的{必须与if在同一行,java风格。 76 | "brace-style": [2, "1tbs", {"allowSingleLine": true}], 77 | // 双峰驼命名格式 78 | "camelcase": 2, 79 | // 数组和对象键值对最后一个逗号, never参数:不能带末尾的逗号, always参数:必须带末尾的逗号, 80 | // always-multiline:多行模式必须带逗号,单行模式不能带逗号 81 | // "comma-dangle": [2, "never"], 82 | "comma-dangle": [0, "never"], 83 | // 控制逗号前后的空格 84 | "comma-spacing": [2, {"before": false, "after": true}], 85 | // 控制逗号在行尾出现还是在行首出现 86 | // http://eslint.org/docs/rules/comma-style 87 | "comma-style": [2, "last"], 88 | // 圈复杂度 89 | "complexity": [2, 9], 90 | // 以方括号取对象属性时,[ 后面和 ] 前面是否需要空格, 可选参数 never, always 91 | "computed-property-spacing": [2, "never"], 92 | // 强制方法必须返回值,TypeScript强类型,不配置 93 | "consistent-return": 0, 94 | // 用于指统一在回调函数中指向this的变量名,箭头函数中的this已经可以指向外层调用者,应该没卵用了 95 | // e.g [0,"that"] 指定只能 var that = this. that不能指向其他任何值,this也不能赋值给that以外的其他值 96 | "consistent-this": 0, 97 | // 强制在子类构造函数中用super()调用父类构造函数,TypeScrip的编译器也会提示 98 | "constructor-super": 0, 99 | // if else while for do后面的代码块是否需要{ }包围,参数: 100 | // multi 只有块中有多行语句时才需要{ }包围 101 | // multi-line 只有块中有多行语句时才需要{ }包围, 但是块中的执行语句只有一行时, 102 | // 块中的语句只能跟和if语句在同一行。if (foo) foo++; else doSomething(); 103 | // multi-or-nest 只有块中有多行语句时才需要{ }包围, 如果块中的执行语句只有一行,执行语句可以零另起一行也可以跟在if语句后面 104 | // [2, "multi", "consistent"] 保持前后语句的{ }一致 105 | // default: [2, "all"] 全都需要{ }包围 106 | "curly": [2, "all"], 107 | // switch语句强制default分支,也可添加 // no default 注释取消此次警告 108 | "default-case": 2, 109 | // 强制object.key 中 . 的位置,参数: 110 | // property,'.'号应与属性在同一行 111 | // object, '.' 号应与对象名在同一行 112 | "dot-location": [2, "property"], 113 | // 强制使用.号取属性 114 | // 参数: allowKeywords:true 使用保留字做属性名时,只能使用.方式取属性 115 | // false 使用保留字做属性名时, 只能使用[]方式取属性 e.g [2, {"allowKeywords": false}] 116 | // allowPattern: 当属性名匹配提供的正则表达式时,允许使用[]方式取值,否则只能用.号取值 e.g [2, {"allowPattern": "^[a-z]+(_[a-z]+)+$"}] 117 | "dot-notation": [2, {"allowKeywords": true}], 118 | // 文件末尾强制换行 119 | "eol-last": 0, 120 | // 使用 === 替代 == 121 | "eqeqeq": [0, "allow-null"], 122 | // 方法表达式是否需要命名 123 | "func-names": 0, 124 | // 方法定义风格,参数: 125 | // declaration: 强制使用方法声明的方式,function f(){} e.g [2, "declaration"] 126 | // expression:强制使用方法表达式的方式,var f = function() {} e.g [2, "expression"] 127 | // allowArrowFunctions: declaration风格中允许箭头函数。 e.g [2, "declaration", { "allowArrowFunctions": true }] 128 | "func-style": 0, 129 | "generator-star-spacing": [2, {"before": true, "after": true}], 130 | "guard-for-in": 0, 131 | "handle-callback-err": [2, "^(err|error)$"], 132 | "indent": [2, 4, {"SwitchCase": 1}], 133 | "key-spacing": [2, {"beforeColon": false, "afterColon": true}], 134 | "linebreak-style": 0, 135 | "lines-around-comment": 0, 136 | "max-nested-callbacks": 0, 137 | "new-cap": [2, {"newIsCap": true, "capIsNew": false}], 138 | "new-parens": 2, 139 | "newline-after-var": 0, 140 | "no-alert": 0, 141 | "no-array-constructor": 2, 142 | "no-caller": 2, 143 | "no-catch-shadow": 0, 144 | "no-cond-assign": 2, 145 | "no-console": 0, 146 | "no-constant-condition": 0, 147 | "no-continue": 0, 148 | "no-control-regex": 2, 149 | "no-debugger": 2, 150 | "no-delete-var": 2, 151 | "no-div-regex": 0, 152 | "no-dupe-args": 2, 153 | "no-dupe-keys": 2, 154 | "no-duplicate-case": 2, 155 | "no-else-return": 0, 156 | "no-empty": 0, 157 | "no-empty-character-class": 2, 158 | "no-empty-label": 0, //禁止使用空label 159 | "no-eq-null": 0, 160 | "no-eval": 2, 161 | "no-ex-assign": 2, 162 | "no-extend-native": 2, 163 | "no-extra-bind": 2, 164 | "no-extra-boolean-cast": 2, 165 | "no-extra-parens": 0, 166 | "no-extra-semi": 0, 167 | "no-fallthrough": 2, 168 | "no-floating-decimal": 2, 169 | "no-func-assign": 2, 170 | "no-implied-eval": 2, 171 | "no-inline-comments": 0, 172 | "no-inner-declarations": [2, "functions"], 173 | "no-invalid-regexp": 2, 174 | "no-irregular-whitespace": 2, 175 | "no-iterator": 2, 176 | "no-label-var": 2, 177 | "no-labels": 2, 178 | "no-lone-blocks": 2, 179 | "no-lonely-if": 0, 180 | "no-loop-func": 0, 181 | "no-mixed-requires": 0, 182 | "no-mixed-spaces-and-tabs": 2, 183 | "no-multi-spaces": 2, 184 | "no-multi-str": 2, 185 | "no-multiple-empty-lines": [2, {"max": 1}], 186 | "no-native-reassign": 2, 187 | "no-negated-in-lhs": 2, 188 | "no-nested-ternary": 0, 189 | "no-new": 2, 190 | "no-new-func": 0, 191 | "no-new-object": 2, 192 | "no-new-require": 2, 193 | "no-new-wrappers": 2, 194 | "no-obj-calls": 2, 195 | "no-octal": 2, 196 | "no-octal-escape": 2, 197 | "no-param-reassign": 0, 198 | "no-path-concat": 0, 199 | "no-process-env": 0, 200 | "no-process-exit": 0, 201 | "no-proto": 0, 202 | "no-redeclare": 2, 203 | "no-regex-spaces": 2, 204 | "no-restricted-modules": 0, 205 | "no-return-assign": 2, 206 | "no-script-url": 0, 207 | "no-self-compare": 2, 208 | "no-sequences": 2, 209 | "no-shadow": 0, 210 | "no-shadow-restricted-names": 2, 211 | "no-spaced-func": 2, 212 | "no-sparse-arrays": 2, 213 | "no-sync": 0, 214 | "no-ternary": 0, 215 | "no-this-before-super": 2, 216 | "no-throw-literal": 2, 217 | "no-trailing-spaces": 2, 218 | "no-undef": 2, 219 | "no-undef-init": 2, 220 | "no-undefined": 0, 221 | "no-underscore-dangle": 0, 222 | "no-unexpected-multiline": 2, 223 | "no-unneeded-ternary": 2, 224 | "no-unreachable": 2, 225 | "no-unused-expressions": 0, 226 | // "no-unused-vars": [2, {"vars": "all", "args": "none"}], 227 | "no-unused-vars": [0, {"vars": "all", "args": "after-used"}],// 不能有声明后未被使用的变量或参数 228 | "no-use-before-define": 0, 229 | "no-var": 0, 230 | "no-void": 0, 231 | "no-warning-comments": 0, 232 | "no-with": 2, 233 | "object-curly-spacing": 0, 234 | "object-shorthand": 0, 235 | // "one-var": [2, {"initialized": "never"}], 236 | "one-var": 1, // 连续声明 237 | "operator-assignment": 0, 238 | "operator-linebreak": [2, "after", {"overrides": {"?": "before", ":": "before"}}], 239 | "padded-blocks": 0, 240 | "prefer-const": 0, 241 | "quote-props": 0, 242 | "quotes": [2, "single", "avoid-escape"], 243 | "radix": 0, // parseInt必须指定第二个参数 244 | // "semi": ["error", "always"], 245 | "semi": [ 246 | "error", 247 | "always" 248 | ], 249 | "semi-spacing": 0, 250 | "sort-vars": 0, 251 | "space-after-keywords": [0, "always", "never"], 252 | "space-before-blocks": [2, "always"], 253 | "space-before-function-paren": [2, "never"], 254 | "space-in-parens": [2, "never"], 255 | "space-infix-ops": 2, 256 | "space-return-throw-case": 0, // return throw case后面要不要加空格 257 | "space-unary-ops": [2, {"words": true, "nonwords": false}], 258 | "spaced-comment": [2, "always", {"markers": ["global", "globals", "eslint", "eslint-disable", "*package", "!"]}], 259 | "strict": 0, 260 | "use-isnan": 2, 261 | "valid-jsdoc": 0, 262 | "valid-typeof": 2, 263 | "vars-on-top": 0, 264 | "wrap-iife": [2, "any"], 265 | "wrap-regex": 0, 266 | "yoda": [2, "never"] 267 | }, 268 | 269 | // 是规避制表符和空格混用的情况 以及定义一些可全局的变量不必声明的 270 | // "globals": { 271 | // "document": true, 272 | // "localStorage": true, 273 | // "window": true, 274 | // "get": true, 275 | // "insert": true, 276 | // }, 277 | }; -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | .logs 3 | logs 4 | logs/* 5 | .idea 6 | .idea/* 7 | package-lock.json 8 | yarn.lock 9 | models 10 | models/* 11 | .git 12 | .git/* 13 | .yarn.lock 14 | 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## 冰火 2 | 基于koa2封装的node后台框架,一个强大、简单、粗暴、易用、自由、便捷的node框架。 3 | 4 | #### 冰火初衷 5 | ``` 6 | 搭建一个强大、简单、粗暴、易用、自由、便捷的node 后台框架。 7 | 8 | 拥有后台通用的mvc目录结构,舍弃egg繁琐的debug方式、约束、底层封装、第三方扩展插件再次封装、部署等缺点。 9 | 10 | 如果egg是企业级,那么我这个就是专属mysql的个人级后台。 11 | ``` 12 | #### 最佳环境 13 | ``` 14 | node: v10.13.0 以上 15 | pm2: v4.2.3 以上 16 | nodemon:测试环境下,数据库分表暂且有个bug,不影响线上操作 17 | ``` 18 | 19 | #### 目录介绍 20 | ```$xslt 21 | 1、bin文件 22 | 程序启动的地方 23 | 2、config配置文件 24 | mysql、redis、图片前缀等配置 25 | 3、controller控制器(不需要注明路由,文件名对应url路由名) 26 | module.exports = { 27 | page: page, 28 | method: "get", // 请求方式 get请求,post请求,all请求(全部请求) 29 | before: [], // 存放中间件 30 | after: [] 31 | } 32 | 4、core文件夹(最好不要去更改它,本架构的最核心之处) 33 | 实现了本架构的功能 34 | 4.1、定义了ctx.tool 35 | 4.2、定义了ctx.params获取参数的方法,(icefire/getParams.js) 36 | 4.3、页面报错拦截、输出并log打印的功能(icefire/onerror.js) 37 | 4.4、控制器文件名对应路由名的功能(routeEach.js) 38 | 4.5、log日志打印输出切割功能(logger.js) 39 | 4.6、mysql数据库连接并定义到ctx.db(db.js) 40 | 4.7、redis连接功能并定义到ctx.redis(redis) 41 | 4.8、service对应service方法的实现功能 42 | 5、extend文件 43 | contenxt.js定义了一些常用的方法 支持this.ctx 44 | 6、logs日志文件 45 | 存放程序log日志 46 | 7、middlewares中间件 47 | 8、models数据库模型 48 | mysql数据库模型,通过`node auto`生成 49 | 9、public静态文件 50 | 静态图片、静态js、静态css存放的地方 51 | 10、service服务层 52 | 专门写业务代码 53 | service下不支持多个文件夹嵌套 54 | 11、sql文件 55 | 本示例代码的sql文件 56 | 12、tool工具 57 | 封装了常用的工具方法 58 | 使用方式 ctx.tool 59 | 13、views视图层 60 | 这里采用的是pug语法, 61 | 若想用ejs语法的话,可去app.js里更改成ejs语法 62 | 14、app.js 63 | 本程序的开始 64 | 15、auto.js 65 | 生成模型的方法 66 | 16、schedule文件夹 67 | 是定时任务,first.js是定时任务模板 68 | 用的是node-schedule插件 69 | ``` 70 | 71 | #### ctx方法 72 | ``` 73 | ctx.db.(数据库表名,首字母大写).findAll 采用的是sequelize库。 74 | ctx.redis.对象名.方法 redis方法 75 | ctx.tool 工具类方法 76 | ctx.success extend文件夹内定义的 77 | ctx.error extend文件夹内定义的 78 | ctx.toJSON extend文件夹内定义的 79 | ``` 80 | #### mysql操作介绍(数据库分表) 81 | ``` 82 | config文件夹下的sql.js 83 | 84 | stepCount属性:在subTable为true下有用 85 | 水平分表是当表的自增长id大于多少时则分表,默认是百万。 86 | stepCount是为了自定义每张表自增长id大于多少时分表, 87 | 注:正式开发时把user去掉。 88 | 89 | timestamp属性:这个参数为true是MySQL会自动给每条数据添加createdAt和updateAt字段。createdAt和updateAt需要我们自己在新建表时定义(sequelize的字段) 90 | 91 | paranoid属性:设置 deletedAt 字段,当删除一条记录的时候,并不是真的销毁记录,而是通过该字段来标示,即保留数据,进行假删除,默认为false 仅在timestamp启用下使用。 deletedAt需要我们自己在新建表时定义(sequelize的字段) 92 | ``` 93 | #### 跑项目之前 94 | 0、环境配置 95 | ``` 96 | 将sql文件夹下的sql文件导入到mysql的timotest表里。 97 | 配置好config文件夹下的mysql和redis文件 98 | ``` 99 | 1、下载依赖 100 | ``` 101 | yarn 102 | ``` 103 | 2、生成模型 104 | ``` 105 | yarn model 106 | ``` 107 | #### 开发环境 108 | ``` 109 | yarn dev 110 | ``` 111 | #### 部署 112 | ``` 113 | yarn model 114 | pm2 start bin/api 115 | ``` 116 | 117 | #### 请求地址 118 | ``` 119 | # 普通页面测试地址请求 120 | http://localhost:3001/api/base/test/ 121 | http://localhost:3001/api/base/test/index2 122 | http://localhost:3001/api/base/test/index3 123 | http://localhost:3001/api/base/test/index4 124 | 125 | # mysql测试页面地址请求 126 | #### 增 127 | http://localhost:3001/api/base/test/dbInsert?name=冰火&age=15&sex=1 128 | 129 | #### 查 130 | http://localhost:3001/api/base/test/dbFindAndCountAll 131 | http://localhost:3001/api/base/test/dbFindAll 132 | http://localhost:3001/api/base/test/dbFindOne?id=2 133 | 134 | #### 改 135 | http://localhost:3001/api/base/test/dbUpdate?id=2&name=冰火 136 | #### 删 137 | http://localhost:3001/api/base/test/dbDel?id=2 138 | http://localhost:3001/api/base/test/dbDel?name=%E9%BB%98%E8%AE%A4 139 | 140 | # redis请求地址 141 | http://localhost:3001/api/base/test/redisInsert 142 | http://localhost:3001/api/base/test/redisGet 143 | ``` 144 | #### API接口规范 145 | ``` 146 | /* 147 | * code 148 | * 1000 请求接口成功 149 | * 1002 提示错误信息,前端直接显示报错信息 150 | * 1003 token验证失败,前端直接跳转到登录页 151 | * 1004 权限不够,前端直接跳转到首页 152 | * */ 153 | ``` 154 | ##### 请求接口成功的code码是1000, data是数据 155 | ``` 156 | { 157 | "code": 1000, 158 | "data": "冰火,欢迎你来到我的冰火世界,你的名字,我是从中间件里获取的" 159 | } 160 | ``` 161 | ##### 请求接口失败的code码是1002, msg是错误提示 162 | ``` 163 | { 164 | "code": 1002, 165 | "msg": "500,未知错误,请复制错误码1585724000253联系我们" 166 | } 167 | ``` 168 | ##### token验证的code码是1003, msg是错误提示 169 | ``` 170 | { 171 | "code": 1003, 172 | "msg": "登录信息失效,请重新登录" 173 | } 174 | ``` 175 | ##### 权限不足的code码是1004, msg是权限不足提示 176 | ``` 177 | { 178 | "code": 1004, 179 | "msg": "你没有该权限" 180 | } 181 | ``` 182 | -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | global.__base = __dirname + '/'; // 设置全局require目录前缀 2 | require('best-require')(process.cwd()); // 添加require里的~/功能 3 | 4 | const Koa = require('koa'); 5 | const app = new Koa(); 6 | const views = require('koa-views'); 7 | // const json = require('koa-json') 8 | require('~/core/prototype'); 9 | const bodyparser = require('koa-bodyparser'); // 获取post请求的参数 10 | const {accessLogger, systemLogger, accessErrorLogger} = require('~/core/logger'); 11 | const icefire = require('~/core/icefire/'); 12 | 13 | const scheduleObj = require('~/core/schedule')(); // 定时任务 14 | const compress = require('koa-compress'); // 压缩 15 | const helmet = require('koa-helmet'); // 安全 16 | const favicon = require('koa-favicon'); // favicon 17 | // const routeEach = require(global.__base + 'core/routeEach'); 18 | const routeEach = require('~/core/routeEach'); 19 | 20 | app.use(bodyparser()); 21 | 22 | icefire(app); 23 | app.use(accessLogger()); // 中间件 24 | /* 25 | * 压缩 26 | * */ 27 | const options = {threshold: 2048}; 28 | app.use(compress(options)); 29 | 30 | app.use(helmet()); 31 | // app.use(json()) 32 | // 静态资源托管 33 | app.use(require('koa-static-server')({rootDir: __dirname + '/public', rootPath: '/public'})); 34 | 35 | // app.use(favicon(__dirname + '/public/favicon.ico')); 36 | app.use(favicon(__dirname + '/public/logo.png')); 37 | app.use(views(__dirname + '/views', { 38 | extension: 'pug' 39 | })); 40 | 41 | routeEach(app, '/api'); // 路由地址 42 | 43 | app.use((ctx, next) => { 44 | ctx.errorCode('NOTFOUND'); 45 | ctx.response.status = 404; 46 | }); 47 | 48 | // error-handling 49 | app.on('error', (err, ctx) => { 50 | systemLogger.error(err); 51 | }); 52 | // app.on('error', err => {systemLogger.error(err); }); 53 | 54 | module.exports = app; 55 | -------------------------------------------------------------------------------- /auto.js: -------------------------------------------------------------------------------- 1 | global.__base = __dirname + '/'; // 设置全局require目录前缀 2 | require('best-require')(process.cwd()); // 添加require里的~/功能 3 | const SequelizeAuto = require('sequelize-auto'); 4 | const config = require('~/config/sql'); 5 | const fs = require('fs'); 6 | 7 | delDir(global.__base + 'models'); // 先删除 8 | const auto = new SequelizeAuto( 9 | config.database, config.user, config.password, { 10 | host: config.host, 11 | dialect: 'mysql', 12 | directory: './models', // prevents the program from writing to disk 13 | port: config.port, 14 | additional: { 15 | timestamps: config.timestamp, 16 | paranoid: config.paranoid, 17 | // autoIncrement: true, 18 | // 要将表里的 deletedAt 设置为 destroyTime (注意要启用paranoid) 19 | // deletedAt: 'destroyTime', 20 | } 21 | } 22 | ); 23 | 24 | // 删除文件夹 25 | function delDir(path) { 26 | let files = []; 27 | if (fs.existsSync(path)) { 28 | files = fs.readdirSync(path); 29 | files.forEach((file, index) => { 30 | let curPath = path + '/' + file; 31 | if (fs.statSync(curPath).isDirectory()) { 32 | delDir(curPath); // 递归删除文件夹 33 | } else { 34 | fs.unlinkSync(curPath); // 删除文件 35 | } 36 | }); 37 | fs.rmdirSync(path); 38 | } 39 | } 40 | 41 | auto.run(function (err) { 42 | if (err) { 43 | throw err; 44 | } 45 | console.log(auto.tables); // table list 46 | console.log(auto.foreignKeys); // foreign key list 47 | replaceAutoIncrement(); // mysql为 5.8以上时 需要打开注释 48 | // 生成models表后,直接执行项目 49 | // require('./bin/api'); 50 | }); 51 | /* 52 | * 修复mysql8下生成模型的错误 53 | * */ 54 | function replaceAutoIncrement() { 55 | // 遍历statics文件夹, 56 | fs.readdir('./models', function(err, files) { 57 | if (err) { 58 | return err; 59 | } 60 | if (files.length != 0) { 61 | files.forEach((item) => { 62 | let path = './models/' + item; 63 | // 判断文件的状态,用于区分文件名/文件夹 64 | fs.stat(path, function(err, status) { 65 | if (err) { 66 | return err; 67 | } 68 | let isFile = status.isFile();// 是文件 69 | let isDir = status.isDirectory();// 是文件夹 70 | if (isFile) { 71 | // replaceFile(path, `id: { 72 | // \t\t\ttype: DataTypes.INTEGER(10), 73 | // \t\t\tallowNull: false, 74 | // \t\t\tprimaryKey: true 75 | // \t\t},`, `id: { 76 | // \t\t\ttype: DataTypes.INTEGER(10), 77 | // \t\t\tallowNull: false, 78 | // \t\t\tprimaryKey: true, 79 | // \t\t\tautoIncrement: true 80 | // \t\t},`); 81 | // replaceFile(path, ` id: { 82 | // type: DataTypes.INTEGER, 83 | // allowNull: false, 84 | // primaryKey: true 85 | // },`, ` id: { 86 | // type: DataTypes.INTEGER, 87 | // allowNull: false, 88 | // primaryKey: true, 89 | // autoIncrement: true 90 | // },`) 91 | 92 | replaceFile(path, `primaryKey: true`,`primaryKey: true,autoIncrement: true` ) 93 | } 94 | if (isDir) { 95 | console.log('文件夹:' + item); 96 | } 97 | }); 98 | }); 99 | } 100 | }); 101 | } 102 | 103 | // 读取文件,并且替换文件中指定的字符串 104 | let replaceFile = function(filePath, sourceRegx, targetStr) { 105 | fs.readFile(filePath, function(err, data) { 106 | if (err) { 107 | return err; 108 | } 109 | let str = data.toString(); 110 | str = str.replace(sourceRegx, targetStr); 111 | fs.writeFile(filePath, str, function(err) { 112 | if(err) return err; 113 | console.log('更改了' + filePath); 114 | }); 115 | }); 116 | } 117 | 118 | -------------------------------------------------------------------------------- /bin/api: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | 7 | var app = require('../app'); 8 | var debug = require('debug')('demo:server'); 9 | var http = require('http'); 10 | 11 | // 使用node进程 来处理负载任务 12 | const cluster = require('cluster'); 13 | const numCPUs = require('os').cpus().length; 14 | 15 | if (cluster.isMaster) { 16 | console.log(`Master ${process.pid} is running`); 17 | 18 | for (let i = 0; i < numCPUs; i++) { 19 | cluster.fork(); 20 | } 21 | 22 | cluster.on('exit', (worker, code, signal) => { 23 | console.log(`worker ${worker.process.pid} died`); 24 | }); 25 | return; 26 | } 27 | 28 | /** 29 | * Get port from environment and store in Express. 30 | */ 31 | 32 | var port = normalizePort(process.env.PORT || '3001'); 33 | console.log('开启端口3001'); 34 | // app.set('port', port); 35 | 36 | /** 37 | * Create HTTP server. 38 | */ 39 | 40 | var server = http.createServer(app.callback()); 41 | 42 | /** 43 | * Listen on provided port, on all network interfaces. 44 | */ 45 | 46 | server.listen(port); 47 | server.on('error', onError); 48 | server.on('listening', onListening); 49 | 50 | /** 51 | * Normalize a port into a number, string, or false. 52 | */ 53 | 54 | function normalizePort(val) { 55 | var port = parseInt(val, 10); 56 | if (isNaN(port)) { 57 | // named pipe 58 | return val; 59 | } 60 | 61 | if (port >= 0) { 62 | // port number 63 | return port; 64 | } 65 | 66 | return false; 67 | } 68 | 69 | /** 70 | * Event listener for HTTP server "error" event. 71 | */ 72 | 73 | function onError(error) { 74 | if (error.syscall !== 'listen') { 75 | throw error; 76 | } 77 | 78 | var bind = typeof port === 'string' 79 | ? 'Pipe ' + port 80 | : 'Port ' + port; 81 | 82 | // handle specific listen errors with friendly messages 83 | switch (error.code) { 84 | case 'EACCES': 85 | console.error(bind + ' requires elevated privileges'); 86 | process.exit(1); 87 | break; 88 | case 'EADDRINUSE': 89 | console.error(bind + ' is already in use'); 90 | process.exit(1); 91 | break; 92 | default: 93 | throw error; 94 | } 95 | } 96 | 97 | /** 98 | * Event listener for HTTP server "listening" event. 99 | */ 100 | 101 | function onListening() { 102 | var addr = server.address(); 103 | var bind = typeof addr === 'string' 104 | ? 'pipe ' + addr 105 | : 'port ' + addr.port; 106 | debug('Listening on ' + bind); 107 | } 108 | -------------------------------------------------------------------------------- /bin/debug: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | 7 | var app = require('../app'); 8 | var debug = require('debug')('demo:server'); 9 | var http = require('http'); 10 | 11 | // 使用node进程 来处理负载任务 12 | // const cluster = require('cluster'); 13 | // const numCPUs = require('os').cpus().length; 14 | // 15 | // if (cluster.isMaster) { 16 | // console.log(`Master ${process.pid} is running`); 17 | // 18 | // for (let i = 0; i < numCPUs; i++) { 19 | // cluster.fork(); 20 | // } 21 | // 22 | // cluster.on('exit', (worker, code, signal) => { 23 | // console.log(`worker ${worker.process.pid} died`); 24 | // }); 25 | // return; 26 | // } 27 | 28 | /** 29 | * Get port from environment and store in Express. 30 | */ 31 | 32 | var port = normalizePort(process.env.PORT || '3001'); 33 | console.log('开启端口3001'); 34 | // app.set('port', port); 35 | 36 | /** 37 | * Create HTTP server. 38 | */ 39 | 40 | var server = http.createServer(app.callback()); 41 | 42 | /** 43 | * Listen on provided port, on all network interfaces. 44 | */ 45 | 46 | server.listen(port); 47 | server.on('error', onError); 48 | server.on('listening', onListening); 49 | 50 | /** 51 | * Normalize a port into a number, string, or false. 52 | */ 53 | 54 | function normalizePort(val) { 55 | var port = parseInt(val, 10); 56 | if (isNaN(port)) { 57 | // named pipe 58 | return val; 59 | } 60 | 61 | if (port >= 0) { 62 | // port number 63 | return port; 64 | } 65 | 66 | return false; 67 | } 68 | 69 | /** 70 | * Event listener for HTTP server "error" event. 71 | */ 72 | 73 | function onError(error) { 74 | if (error.syscall !== 'listen') { 75 | throw error; 76 | } 77 | 78 | var bind = typeof port === 'string' 79 | ? 'Pipe ' + port 80 | : 'Port ' + port; 81 | 82 | // handle specific listen errors with friendly messages 83 | switch (error.code) { 84 | case 'EACCES': 85 | console.error(bind + ' requires elevated privileges'); 86 | process.exit(1); 87 | break; 88 | case 'EADDRINUSE': 89 | console.error(bind + ' is already in use'); 90 | process.exit(1); 91 | break; 92 | default: 93 | throw error; 94 | } 95 | } 96 | 97 | /** 98 | * Event listener for HTTP server "listening" event. 99 | */ 100 | 101 | function onListening() { 102 | var addr = server.address(); 103 | var bind = typeof addr === 'string' 104 | ? 'pipe ' + addr 105 | : 'port ' + addr.port; 106 | debug('Listening on ' + bind); 107 | } 108 | -------------------------------------------------------------------------------- /config/common.js: -------------------------------------------------------------------------------- 1 | // jwt验证 2 | module.exports.secret = '冰火、黑白共存的世界'; 3 | // signKey 4 | module.exports.pcName = '冰火世界'; 5 | module.exports.pcKey = 'icefire'; 6 | 7 | // valid 8 | module.exports.phoneReg = /^[1][3,4,5,6,7,8,9][0-9]{9}$/; 9 | module.exports.qqReg = /^\d{6,16}$/; 10 | module.exports.wxReg = /^[^\s]{6,20}$/; 11 | -------------------------------------------------------------------------------- /config/error.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = { 3 | 'NOTFOUND': { 4 | code: '10404', 5 | msg: '没有该页面' 6 | }, 7 | 'NOERROR': { 8 | code: '10500', 9 | msg: '未知错误' 10 | }, 11 | 'Illegal Buffer': { 12 | code: '10001', 13 | msg: 'encryptedData或iv失效' 14 | }, 15 | 'NOTLOGIN': { 16 | code: '1003', 17 | msg: 'token验证失败,前端直接跳转到登录页' 18 | }, 19 | 'SKUGOODSNOTARRAY': { 20 | code: '100500', 21 | msg: 'sukGoods不是一个字符串数组' 22 | }, 23 | 'NODATANOUPDATE': { 24 | code: '100312', 25 | msg: '没有该数据,修改失败' 26 | }, 27 | 'GOODSNOTARRAY': { 28 | code: '100501', 29 | msg: 'goods不是一个字符串数组' 30 | }, 31 | 'SKUJSONNOTARRAY': { 32 | code: '100502', 33 | msg: 'skuJson不是一个字符串数组' 34 | }, 35 | 'refundMoneyFAILNOMONEY': { 36 | code: '100444', 37 | msg: '确认取消失败,失败原因:余额不足' 38 | }, 39 | 'NOPHONE': { 40 | code: '10086', 41 | msg: '该用户尚未授权手机号,请授权' 42 | }, 43 | 'NOTENOUGH': { 44 | code: '100999', 45 | msg: '基本账户余额不足,请充值后重新发起' 46 | }, 47 | 'CARERROR': { 48 | code: '10005', 49 | msg: '请刷新购物车' 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /config/host.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports.pc = [ 3 | 'localhost:3001', 4 | 'localhost:10086', 5 | ]; 6 | -------------------------------------------------------------------------------- /config/imgUrl.js: -------------------------------------------------------------------------------- 1 | module.exports = 'http://localhost:3000/images/'; // 只针对h5配置小说封面地址前缀 2 | -------------------------------------------------------------------------------- /config/redis.js: -------------------------------------------------------------------------------- 1 | // 测试 2 | module.exports = { 3 | RDS_PORT: 6379, // 服务器端口 4 | RDS_HOST: '127.0.0.1', // 服务器ip 5 | RDS_PWD: '123456', // 密码 6 | RDS_OPTS: {}, // 设置值 // 是否把连接情况打印到文件里 7 | }; 8 | -------------------------------------------------------------------------------- /config/sql.js: -------------------------------------------------------------------------------- 1 | // 测试 2 | module.exports = { 3 | host: '127.0.0.1', 4 | port: '3306', 5 | user: 'root', 6 | password: '123456', 7 | database: 'timotest', 8 | multipleStatements: true, // 是否允许执行多条sql语句 9 | // insecureAuth: true, // 加入此项可解决此错误!!! //使用旧(不安全)的连接方式去连接MySQL 远程连接 10 | // debug: true, //是否把连接情况打印到文件里 11 | timestamp: true, // 这个参数为true是MySQL会自动给每条数据添加createdAt和updateAt字段 12 | paranoid: true, // 设置 deletedAt 字段,当删除一条记录的时候,并不是真的销毁记录,而是通过该字段来标示,即保留数据,进行假删除,默认为false 仅在timestamp启用下使用 13 | }; 14 | -------------------------------------------------------------------------------- /controller/base/test/dbDel.js: -------------------------------------------------------------------------------- 1 | const oauth = require('~/middlewares/oauth'); 2 | const page = async(ctx, next) => { 3 | let {name, sex, age, id} = ctx.params; // 获取参数 4 | ctx.success(await ctx.service.test.del({name, sex, age, id})); 5 | }; 6 | 7 | module.exports = { 8 | page: page, 9 | method: 'get', // 请求方式 get请求,post请求,all请求(全部请求) 默认all 10 | before: [oauth], // 存放中间件 11 | after: [] 12 | }; 13 | -------------------------------------------------------------------------------- /controller/base/test/dbFindAll.js: -------------------------------------------------------------------------------- 1 | const oauth = require('~/middlewares/oauth'); 2 | const page = async(ctx, next) => { 3 | let {name, sex, age, id} = ctx.params; // 获取参数 4 | ctx.success(await ctx.service.test.findAll({name, sex, age, id})); 5 | }; 6 | 7 | module.exports = { 8 | page: page, 9 | method: 'get', // 请求方式 get请求,post请求,all请求(全部请求) 默认all 10 | before: [oauth], // 存放中间件 11 | after: [] 12 | }; 13 | -------------------------------------------------------------------------------- /controller/base/test/dbFindAndCountAll.js: -------------------------------------------------------------------------------- 1 | const oauth = require('~/middlewares/oauth'); 2 | const page = async(ctx, next) => { 3 | let {name, sex, age, id} = ctx.params; // 获取参数 4 | ctx.success(await ctx.service.test.findAndCountAll({name, sex, age, id})); 5 | }; 6 | 7 | module.exports = { 8 | page: page, 9 | method: 'get', // 请求方式 get请求,post请求,all请求(全部请求) 默认all 10 | before: [oauth], // 存放中间件 11 | after: [] 12 | }; 13 | -------------------------------------------------------------------------------- /controller/base/test/dbFindOne.js: -------------------------------------------------------------------------------- 1 | const oauth = require('~/middlewares/oauth'); 2 | const page = async(ctx, next) => { 3 | let {name, sex, age, id} = ctx.params; // 获取参数 4 | ctx.success(await ctx.service.test.findOne({name, sex, age, id})); 5 | }; 6 | 7 | module.exports = { 8 | page: page, 9 | method: 'get', // 请求方式 get请求,post请求,all请求(全部请求) 默认all 10 | before: [oauth], // 存放中间件 11 | after: [] 12 | }; 13 | -------------------------------------------------------------------------------- /controller/base/test/dbInsert.js: -------------------------------------------------------------------------------- 1 | const oauth = require('~/middlewares/oauth'); 2 | const page = async(ctx, next) => { 3 | let {name = '默认', sex = 1, age = 20} = ctx.params; // 获取参数 4 | ctx.success(await ctx.service.test.insert({name, sex, age})); 5 | }; 6 | 7 | module.exports = { 8 | page: page, 9 | method: 'get', // 请求方式 get请求,post请求,all请求(全部请求) 默认all 10 | before: [oauth], // 存放中间件 11 | after: [] 12 | }; 13 | -------------------------------------------------------------------------------- /controller/base/test/dbUpdate.js: -------------------------------------------------------------------------------- 1 | const oauth = require('~/middlewares/oauth'); 2 | const page = async(ctx, next) => { 3 | let {name = '修改', sex = 1, age = 20, id} = ctx.params; // 获取参数 4 | ctx.success(await ctx.service.test.update({name, sex, age, id})); 5 | }; 6 | 7 | module.exports = { 8 | page: page, 9 | method: 'get', // 请求方式 get请求,post请求,all请求(全部请求) 默认all 10 | before: [oauth], // 存放中间件 11 | after: [] 12 | }; 13 | -------------------------------------------------------------------------------- /controller/base/test/index.js: -------------------------------------------------------------------------------- 1 | // const oauth = require('~/middlewares/oauth'); 2 | const oauth = require('~/middlewares/oauth'); 3 | 4 | const page = async(ctx, next) => { 5 | ctx.success(`${ ctx.name },欢迎你来到我的冰火世界,你的名字,我是从中间件里获取的`); 6 | }; 7 | 8 | module.exports = { 9 | page: page, 10 | method: 'get', // 请求方式 get请求,post请求,all请求(全部请求) 默认all 11 | before: [oauth], // 存放中间件 12 | after: [] 13 | }; 14 | -------------------------------------------------------------------------------- /controller/base/test/index2.js: -------------------------------------------------------------------------------- 1 | const page = async(ctx, next) => { 2 | ctx.toJSON(`${ ctx.name },欢迎你来到我的冰火世界`); 3 | }; 4 | 5 | module.exports = { 6 | page: page, 7 | method: 'get', // 请求方式 get请求,post请求,all请求(全部请求) 默认all 8 | before: [], // 存放中间件 9 | after: [] 10 | }; 11 | -------------------------------------------------------------------------------- /controller/base/test/index3.js: -------------------------------------------------------------------------------- 1 | const oauth = require('~/middlewares/oauth'); 2 | 3 | const page = async(ctx, next) => { 4 | ctx.body = await ctx.service.base.get(); 5 | }; 6 | 7 | module.exports = { 8 | page: page, 9 | method: 'get', // 请求方式 get请求,post请求,all请求(全部请求) 默认all 10 | before: [oauth], // 存放中间件 11 | after: [] 12 | }; 13 | -------------------------------------------------------------------------------- /controller/base/test/index4.js: -------------------------------------------------------------------------------- 1 | const oauth = require('~/middlewares/oauth'); 2 | const page = async(ctx, next) => { 3 | 4 | ctx.body = await ctx.service.like.book.get(); 5 | }; 6 | 7 | module.exports = { 8 | page: page, 9 | method: 'get', // 请求方式 get请求,post请求,all请求(全部请求) 默认all 10 | before: [oauth], // 存放中间件 11 | after: [] 12 | }; 13 | -------------------------------------------------------------------------------- /controller/base/test/redisGet.js: -------------------------------------------------------------------------------- 1 | const oauth = require('~/middlewares/oauth'); 2 | const page = async(ctx, next) => { 3 | 4 | ctx.body = await ctx.service.test.redisGet(); 5 | }; 6 | 7 | module.exports = { 8 | page: page, 9 | method: 'get', // 请求方式 get请求,post请求,all请求(全部请求) 默认all 10 | before: [oauth], // 存放中间件 11 | after: [] 12 | }; 13 | -------------------------------------------------------------------------------- /controller/base/test/redisInsert.js: -------------------------------------------------------------------------------- 1 | const oauth = require('~/middlewares/oauth'); 2 | const page = async(ctx, next) => { 3 | 4 | ctx.body = await ctx.service.test.redisInsert(); 5 | }; 6 | 7 | module.exports = { 8 | page: page, 9 | method: 'get', // 请求方式 get请求,post请求,all请求(全部请求) 默认all 10 | before: [oauth], // 存放中间件 11 | after: [] 12 | }; 13 | -------------------------------------------------------------------------------- /core/ctx.js: -------------------------------------------------------------------------------- 1 | 2 | const Sequelize = require('sequelize'); 3 | const Op = Sequelize.Op; 4 | const db = require('~/core/db'); 5 | const extend = require('~/extend/context'); 6 | const path = require('path'); 7 | const glob = require('glob'); // 读取本地的.js格式文件, 8 | 9 | let defaultOptions = { 10 | serviceRoot: path.join(process.cwd(), 'service/'), 11 | extendRoot: path.join(process.cwd(), 'extend/'), 12 | }; 13 | 14 | function selfish(target, ctx) { 15 | const cache = new WeakMap(); // 弱引用。 16 | const handler = { 17 | get(target, key) { 18 | const value = Reflect.get(target, key); 19 | if (typeof value !== 'function') { 20 | return value; 21 | } 22 | if (!cache.has(value)) { 23 | cache.set(value, value.bind({ctx})); 24 | } 25 | return cache.get(value); 26 | } 27 | }; 28 | const proxy = new Proxy(target, handler); 29 | return proxy; 30 | } 31 | /* 32 | * 将url地址转换为对象 33 | * 34 | * 比如 35 | * 将这么一段数组 36 | * [ 37 | "Book/getBook.js", 38 | "index.js", 39 | "like/like2.js", 40 | "like/like/like2.js" 41 | ]; 42 | 转成下面这种格式 43 | { 44 | Book:{ 45 | getBook:require("Book/getBook.js"), 46 | }, 47 | index:require("index.js"), 48 | like:{ 49 | like2:require("like/like2.js"), 50 | like:{ 51 | like2:require("like/like/like2.js") 52 | } 53 | } 54 | } 55 | * 56 | * serviceRoot 根目录 57 | * files url地址格式 58 | * cacheMap 将要赋值的地址 59 | * 60 | * return cacheMap; 61 | * */ 62 | const urlToObj = (serviceRoot, files) => { 63 | let cacheMap = {}; 64 | files.forEach((value, index) => { 65 | let valueArr = value.split('/'); 66 | let length = valueArr.length; 67 | valueArr[length - 1] = valueArr[length - 1].substring(0, valueArr[length - 1].length - 3); // 数组最后一个,去掉.js 68 | let cacheMapNow = cacheMap; 69 | let i = 0; 70 | for (i; i < length - 1; i++) { 71 | let value2 = valueArr[i]; 72 | if (cacheMapNow[value2] === undefined) { 73 | cacheMapNow[value2] = {}; 74 | } 75 | cacheMapNow = cacheMapNow[value2]; 76 | } 77 | let value2 = valueArr[length - 1]; 78 | cacheMapNow[value2] = require(`${serviceRoot}/${value}`); 79 | }); 80 | 81 | return cacheMap; 82 | }; 83 | 84 | let ctx = { 85 | db, 86 | ...extend, 87 | }; 88 | 89 | let service = (options) => { 90 | options = options || {}; 91 | options = Object.assign({}, defaultOptions, options); 92 | const files = glob.sync('**/*.js', {nodir: true, cwd: options.serviceRoot}); 93 | let cacheMap = urlToObj(options.serviceRoot, files); 94 | let handler = { 95 | get: function(target, name) { 96 | if (typeof target[name] == 'object') { 97 | return new Proxy(target[name], handler); 98 | } else { 99 | let service = selfish(new (target[name])(), ctx); 100 | return service; 101 | } 102 | } 103 | }; 104 | return new Proxy(cacheMap, handler); 105 | } 106 | 107 | ctx.service = service({ 108 | serviceRoot: path.join(process.cwd(), 'service/'), 109 | }); 110 | 111 | module.exports.ctx = ctx; 112 | -------------------------------------------------------------------------------- /core/db.js: -------------------------------------------------------------------------------- 1 | const Sequelize = require('sequelize'); 2 | const fs = require('fs'); 3 | const path = require('path'); 4 | const {sqlLog} = require('~/core/logger'); 5 | const config = require('~/config/sql'); 6 | 7 | const basePathG = path.join(process.cwd(), 'models'); 8 | 9 | let models = fs.readdirSync(basePathG); 10 | let stepDb = null; 11 | const stepObj = config.stepCount; 12 | 13 | dbInit(); 14 | /* 15 | * 返回首字母大写 16 | * */ 17 | const returnTableName = (str) => { 18 | return str.substring(0, 1).toUpperCase() + str.substring(1); 19 | }; 20 | 21 | /* 22 | * 检查是否有该表 23 | * 有表则不操作 24 | * 没表则新增一个表 25 | * 若isCreat为false并无该表,则直接返回fileName 26 | * */ 27 | const checkOrCreat = async({tableName, fileName, sequelize, idCount }) => { 28 | // 如果该表未定义就创建表 并且是新增模式下 29 | if (!sequelize.isDefined(tableName)) { 30 | // 获取模型的字符串 31 | let modelStr = fs.readFileSync(basePathG + `/${fileName}.js`, 'utf-8'); 32 | 33 | // 替换模型的名称和表名 34 | modelStr = modelStr.replace(`sequelize.define('${fileName}'`, `sequelize.define('${tableName}'`) 35 | .replace(`tableName: '${fileName}',`, `tableName: '${tableName}',`); 36 | 37 | // 创建模型文件 38 | await fs.writeFileSync(basePathG + `/${tableName}.js`, modelStr, 'utf-8'); 39 | 40 | // 引入并定义模型 41 | await sequelize.import(basePathG + `/${tableName}.js`); 42 | // 同步模型到数据库 43 | await sequelize.sync(); 44 | // 更改自增值 45 | await sequelize.query(`alter table ${tableName} auto_increment=${idCount};`); 46 | return tableName; 47 | } else { 48 | return tableName; 49 | } 50 | }; 51 | 52 | /* 53 | * 获取id、table 54 | * 判断并返回是第几个表 55 | * 56 | * fileName 是表名 57 | * id是表的id 58 | * 59 | * */ 60 | const getTableNum = async({fileName, sequelize, id, isCreat}) => { 61 | let tableCount = 0, setpCount = 1000000; 62 | if (!isCreat) { // 查询,修改,删除 63 | setpCount = stepObj[fileName] || 1000000; // 一个表存在多少id 64 | tableCount = Math.ceil((id) / setpCount); // 表的数量 65 | } else { // 新增表获取会用到 66 | const step = await stepDb.findOne({ 67 | attributes: ['number'], 68 | where: { 69 | tableName: fileName 70 | } 71 | }); 72 | let stepLength = step.number; // 步长数量 73 | setpCount = stepObj[fileName] || 1000000; // 一个表存在多少id 74 | tableCount = Math.ceil((stepLength + 1) / setpCount); // 表的数量 75 | } 76 | let tableName = fileName + (tableCount <= 1 ? '' : tableCount); 77 | tableName = await checkOrCreat({ 78 | tableName, 79 | fileName, 80 | sequelize, 81 | idCount: setpCount * (tableCount - 1) + 1 82 | }); 83 | 84 | return tableName; 85 | }; 86 | 87 | async function dbInit() { 88 | let sequelize = await new Sequelize( 89 | config.database, 90 | config.user, 91 | config.password, 92 | { 93 | 'query': {raw: true}, 94 | // 'create':{raw:true}, 95 | // define: { 96 | // raw: true // 设置为 true,即可返回源数据 97 | // }, 98 | 'dialect': 'mysql', // 数据库使用mysql 99 | 'host': config.host, // 数据库服务器ip 100 | 'port': config.port, // 数据库运行端口 101 | 'paranoid': config.paranoid, 102 | 'timestamp': config.timestamp, // 这个参数为true是MySQL会自动给每条数据添加createdAt和updateAt字段 103 | 'quoteIdentifiers': true, 104 | // logging: (message) => {sqlLog.info(message)} 105 | }, 106 | ); 107 | module.exports.sequelize = sequelize; 108 | 109 | if (config.subTable) { 110 | const tool = { 111 | // 查询有多少个这样的分表 112 | getTableCount: async(tableName) => { 113 | let tables = (await sequelize.query(`SELECT table_name FROM information_schema.TABLES WHERE table_name like '${tableName}%' and TABLE_SCHEMA = '${config.database}';`))[0]; 114 | let tablesArr = []; 115 | tables.forEach((value, index) => { 116 | let tableName = value.table_name; 117 | tablesArr.push(returnTableName(tableName)); // 首字母大写 118 | }); 119 | return tablesArr; 120 | }, 121 | findOne: async({ctx, tableName, options}) => { 122 | let tables = await tool.getTableCount(tableName); 123 | let result = null; 124 | for (let value of tables) { 125 | result = await ctx.db[value].findOne(options); 126 | if (result) { 127 | break; 128 | } 129 | } 130 | return result; 131 | }, 132 | findAll: async({ctx, tableName, options}) => { 133 | let tables = await tool.getTableCount(tableName); 134 | let result = []; 135 | let pArr = []; 136 | for (let value of tables) { 137 | pArr.push(ctx.db[value].findAll(options)); 138 | } 139 | let proResult = await Promise.all(pArr); 140 | proResult.forEach((value, index) => { 141 | result.push(...value); 142 | }); 143 | return result; 144 | }, 145 | findAndCountAll: async({ctx, tableName, options}) => { 146 | let tables = await tool.getTableCount(tableName); 147 | let result = { 148 | count: 0, 149 | rows: [] 150 | }; 151 | let pArr = []; 152 | for (let value of tables) { 153 | pArr.push(ctx.db[value].findAndCountAll(options)); 154 | } 155 | let proResult = await Promise.all(pArr); 156 | proResult.forEach((value, index) => { 157 | result.count += value.count; 158 | result.rows.push(...value.rows); 159 | }); 160 | return result; 161 | }, 162 | update: async({ctx, tableName, options}) => { 163 | let tables = await tool.getTableCount(tableName); 164 | let result = 0; 165 | let pArr = []; 166 | for (let value of tables) { 167 | pArr.push(ctx.db[value].update(...options)); 168 | } 169 | let proResult = await Promise.all(pArr); 170 | proResult.forEach((value, index) => { 171 | result += value[0]; 172 | }); 173 | return result; 174 | }, 175 | destroy: async({ctx, tableName, options}) => { 176 | let tables = await tool.getTableCount(tableName); 177 | let result = 0; 178 | let pArr = []; 179 | for (let value of tables) { 180 | pArr.push(ctx.db[value].destroy(options)); 181 | } 182 | let proResult = await Promise.all(pArr); 183 | proResult.forEach((value, index) => { 184 | result += value; 185 | }); 186 | return result; 187 | } 188 | }; 189 | module.exports.tool = tool; 190 | } 191 | 192 | models.forEach((item, index) => { 193 | let fileName = item.substr(0, item.length - 3); 194 | let name = returnTableName(fileName); // 首字母大写 195 | 196 | let dbName = require(basePathG + `/${item}`)(sequelize, Sequelize.DataTypes); 197 | 198 | module.exports[name] = dbName; 199 | 200 | if (item == 'step.js') { // 记录每个表的id数量 201 | stepDb = dbName; 202 | return; 203 | } 204 | if (!config.subTable) { 205 | return; 206 | } 207 | // find请求之前 208 | dbName.addHook('beforeFind', async(Instance, options, next) => { 209 | if (Instance.where && Instance.where.id) { 210 | dbName.tableName = await getTableNum({fileName, sequelize, id: Instance.where.id}); 211 | } 212 | }); 213 | dbName.addHook('afterFind', async(Instance, options) => { 214 | dbName.tableName = fileName; 215 | }); 216 | // dbName.addHook('beforeValidate', async (Instance, options) => { 217 | // dbName.tableName = await getTableNum({ fileName, sequelize }); 218 | // }); 219 | // 220 | // dbName.addHook('afterValidate', async (Instance, options) => { 221 | // }); 222 | 223 | // 修改请求之前 224 | dbName.addHook('beforeBulkUpdate', async(Instance, options) => { 225 | if (Instance.where && Instance.where.id) { 226 | dbName.tableName = await getTableNum({fileName, sequelize, id: Instance.where.id}); 227 | } 228 | }); 229 | 230 | // 修改请求之后 231 | dbName.addHook('afterBulkUpdate', async(Instance, options) => { 232 | dbName.tableName = fileName; 233 | }); 234 | 235 | // 删除请求之前 236 | dbName.addHook('beforeBulkDestroy', async(Instance, options) => { 237 | if (Instance.where && Instance.where.id) { 238 | dbName.tableName = await getTableNum({fileName, sequelize, id: Instance.where.id}); 239 | } 240 | }); 241 | 242 | // 删除请求之后 243 | dbName.addHook('afterBulkDestroy', async(Instance, options) => { 244 | dbName.tableName = fileName; 245 | }); 246 | 247 | // 新增请求之前 248 | dbName.addHook('beforeCreate', async(Instance, options) => { 249 | dbName.tableName = await getTableNum({fileName, sequelize, isCreat: true}); 250 | }); 251 | 252 | // 新增请求之后 253 | dbName.addHook('afterCreate', async(Instance, options, fn) => { 254 | let data = {}; 255 | data.number = Instance.id; 256 | await stepDb.update(data, {where: {tableName: fileName}}); // 更改步长标识 257 | 258 | // tableName改回来 259 | dbName.tableName = fileName; 260 | }); 261 | }); 262 | } 263 | -------------------------------------------------------------------------------- /core/icefire/base.js: -------------------------------------------------------------------------------- 1 | const tool = require('~/tool'); 2 | const db = require('~/core/db'); 3 | const redis = require('~/core/redis'); 4 | 5 | let base = async(ctx, next) => { 6 | ctx.db = db; 7 | ctx.redis = redis; 8 | ctx.tool = tool; 9 | await next(); 10 | }; 11 | 12 | module.exports = base; 13 | -------------------------------------------------------------------------------- /core/icefire/getParams.js: -------------------------------------------------------------------------------- 1 | let getParams = async(ctx, next) => { 2 | // ctx.body = { 3 | // url: ctx.url, 4 | // ctx_query: ctx.query, 5 | // ctx_querystring: ctx.querystring 6 | // } 7 | /* 8 | * 统一获取接口参数, 9 | * 10 | * 同时也为了防止sql注入 11 | * 12 | * 把英文双引号改成了转义的英文双引号,遇到转义符号,先处理转义符号,再处理英文双引号(以前是把英文双引号改成了英文单引号) 13 | * 14 | * sql语法尽量用双引号 15 | * 16 | * notrans 默认false 如果不想转义的话,则设为true 17 | * */ 18 | 19 | ctx.params = Object.assign(ctx.params || {}, ctx.query || {}, ctx.request.body || {}); 20 | await next(); 21 | }; 22 | 23 | module.exports = getParams; -------------------------------------------------------------------------------- /core/icefire/index.js: -------------------------------------------------------------------------------- 1 | const base = require('./base'); // db、redis、和常用中间件的 2 | const service = require('./service'); // 封装 service 和extend的中间件 3 | const getParams = require('./getParams'); // 获取参数的中间件 4 | const onerror = require('./onerror'); // 页面报错检测的中间件 5 | const path = require('path'); 6 | 7 | module.exports = (app) => { 8 | app.use(base); 9 | app.use(getParams); 10 | app.use(service({ 11 | // serviceRoot: path.join(__dirname, 'service'), 12 | // serviceRoot: path.join(global.__base, 'service') 13 | serviceRoot: path.join(process.cwd(), 'service/'), 14 | })); 15 | app.use(onerror); // 页面的错误机制 16 | }; 17 | -------------------------------------------------------------------------------- /core/icefire/onerror.js: -------------------------------------------------------------------------------- 1 | // const {accessLogger, systemLogger, accessErrorLogger, accessSimpleLogger} = require(global.__base + 'core/logger'); 2 | const {accessLogger, systemLogger, accessErrorLogger, accessSimpleLogger} = require('~/core/logger'); 3 | const error = require('~/config/error.js'); 4 | const handler = async(ctx, next) => { 5 | try { 6 | await next(); 7 | } catch (err) { 8 | let url = ctx.req.headers.host + ctx.req.url; 9 | if(error[err.message]) { 10 | ctx.response.status = 200; 11 | } else { 12 | ctx.response.status = err.statusCode || err.status || 500; 13 | } 14 | // ctx.response.body = { 15 | // message: err.message 16 | // };NOTLOGIN 17 | if(ctx.response.status == 404) { 18 | ctx.errorCode('NOTFOUND'); // 以后也可以自己渲染一个页面 19 | } else { 20 | // ctx.error(ctx.response.status + ',未知错误,请复制错误码' + new Date().getTime() + '联系我们'); 21 | ctx.errorCode(err.message); 22 | // ctx.errorFn(ctx.response.status); 23 | // if(error[err.message]) { 24 | // ctx.response.status = 200; 25 | // ctx.errorCode(err.message); 26 | // } else { 27 | // ctx.errorCode(err.message); 28 | // } 29 | } 30 | 31 | accessSimpleLogger.error(url + err); // 简单记录报错 32 | accessErrorLogger.error(err); // 报错详情 33 | 34 | // 手动释放error事件 35 | // ctx.app.emit('error', err, ctx); 36 | // ctx.app.emit('error'); 37 | ctx.app.emit(); 38 | // ctx.throw(500); 39 | } 40 | }; 41 | 42 | module.exports = handler; 43 | -------------------------------------------------------------------------------- /core/icefire/service.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const glob = require('glob'); // 读取本地的.js格式文件, 3 | 4 | let defaultOptions = { 5 | serviceRoot: path.join(process.cwd(), 'service/'), 6 | extendRoot: path.join(process.cwd(), 'extend/'), 7 | }; 8 | 9 | /* 10 | * 将url地址转换为对象 11 | * 12 | * 比如 13 | * 将这么一段数组 14 | * [ 15 | "Book/getBook.js", 16 | "index.js", 17 | "like/like2.js", 18 | "like/like/like2.js" 19 | ]; 20 | 转成下面这种格式 21 | { 22 | Book:{ 23 | getBook:require("Book/getBook.js"), 24 | }, 25 | index:require("index.js"), 26 | like:{ 27 | like2:require("like/like2.js"), 28 | like:{ 29 | like2:require("like/like/like2.js") 30 | } 31 | } 32 | } 33 | * 34 | * serviceRoot 根目录 35 | * files url地址格式 36 | * cacheMap 将要赋值的地址 37 | * 38 | * return cacheMap; 39 | * */ 40 | const urlToObj = (serviceRoot, files) => { 41 | let cacheMap = {}; 42 | files.forEach((value, index) => { 43 | let valueArr = value.split('/'); 44 | let length = valueArr.length; 45 | valueArr[length - 1] = valueArr[length - 1].substring(0, valueArr[length - 1].length - 3); // 数组最后一个,去掉.js 46 | let cacheMapNow = cacheMap; 47 | let i = 0; 48 | for (i; i < length - 1; i++) { 49 | let value2 = valueArr[i]; 50 | if (cacheMapNow[value2] === undefined) { 51 | cacheMapNow[value2] = {}; 52 | } 53 | cacheMapNow = cacheMapNow[value2]; 54 | } 55 | let value2 = valueArr[length - 1]; 56 | cacheMapNow[value2] = require(`${serviceRoot}/${value}`); 57 | }); 58 | 59 | return cacheMap; 60 | }; 61 | 62 | let service = (options) => { 63 | options = options || {}; 64 | options = Object.assign({}, defaultOptions, options); 65 | const files = glob.sync('**/*.js', {nodir: true, cwd: options.serviceRoot}); 66 | let cacheMap = urlToObj(options.serviceRoot, files); 67 | let serviceMiddleWare = async(ctx, next) => { 68 | let handler = { 69 | get: function(target, name) { 70 | if (typeof target[name] == 'object') { 71 | return new Proxy(target[name], handler); 72 | } else { 73 | let service = selfish(new (target[name])(), ctx); 74 | return service; 75 | } 76 | } 77 | }; 78 | ctx.service = new Proxy(cacheMap, handler); 79 | /* 80 | * 种下 extend里的方法start 81 | * */ 82 | let extend = require(options.extendRoot + 'context.js'); 83 | extend = selfish(extend, ctx); 84 | Object.assign(ctx, extend); 85 | /* 86 | * 种下 extend里的方法end 87 | * */ 88 | 89 | await next(); 90 | }; 91 | 92 | return serviceMiddleWare; 93 | }; 94 | 95 | function selfish(target, ctx) { 96 | const cache = new WeakMap(); // 弱引用。 97 | const handler = { 98 | get(target, key) { 99 | const value = Reflect.get(target, key); 100 | if (typeof value !== 'function') { 101 | return value; 102 | } 103 | if (!cache.has(value)) { 104 | cache.set(value, value.bind({ctx})); 105 | } 106 | return cache.get(value); 107 | } 108 | }; 109 | const proxy = new Proxy(target, handler); 110 | return proxy; 111 | } 112 | 113 | module.exports = service; 114 | -------------------------------------------------------------------------------- /core/logger.js: -------------------------------------------------------------------------------- 1 | const path = require('path');// 引入原生path模块 2 | const log4js = require('koa-log4');// 引入koa-log4 3 | 4 | log4js.configure({ 5 | appenders: { 6 | // 访问日志 7 | access: { 8 | type: 'dateFile', 9 | pattern: '-yyyy-MM-dd.log', // 通过日期来生成文件 10 | alwaysIncludePattern: true, // 文件名始终以日期区分 11 | encoding: 'utf-8', 12 | filename: path.join('logs/', 'access.log') // 生成文件路径和文件名 13 | }, 14 | // 系统日志 15 | application: { 16 | type: 'dateFile', 17 | pattern: '-yyyy-MM-dd.log', // 通过日期来生成文件 18 | alwaysIncludePattern: true, // 文件名始终以日期区分 19 | encoding: 'utf-8', 20 | filename: path.join('logs/', 'application.log') // 生成文件路径和文件名 21 | }, 22 | accessErrorLogger: { 23 | type: 'dateFile', 24 | pattern: '-yyyy-MM-dd.log', // 通过日期来生成文件 25 | alwaysIncludePattern: true, // 文件名始终以日期区分 26 | encoding: 'utf-8', 27 | filename: path.join('logs/', 'accessErrorLogger.log') // 生成文件路径和文件名 28 | }, 29 | accessSimpleLogger: { 30 | type: 'dateFile', 31 | pattern: '-yyyy-MM-dd.log', // 通过日期来生成文件 32 | alwaysIncludePattern: true, // 文件名始终以日期区分 33 | encoding: 'utf-8', 34 | filename: path.join('logs/', 'accessSimpleLogger.log') // 生成文件路径和文件名 35 | }, 36 | sqlLog: { 37 | type: 'dateFile', 38 | pattern: '-yyyy-MM-dd.log', // 通过日期来生成文件 39 | alwaysIncludePattern: true, // 文件名始终以日期区分 40 | encoding: 'utf-8', 41 | filename: path.join('logs/', 'sqlLog.log') // 生成文件路径和文件名 42 | }, 43 | out: { 44 | type: 'console' 45 | } 46 | }, 47 | categories: { 48 | default: {appenders: ['out'], level: 'info'}, 49 | access: {appenders: ['access'], level: 'info'}, 50 | application: {appenders: ['application'], level: 'WARN'}, 51 | accessErrorLogger: {appenders: ['accessErrorLogger'], level: 'WARN'}, 52 | accessSimpleLogger: {appenders: ['accessSimpleLogger'], level: 'WARN'}, 53 | sqlLog: {appenders: ['sqlLog'], level: 'info'} 54 | } 55 | }); 56 | 57 | exports.accessLogger = () => log4js.koaLogger(log4js.getLogger('access')); // 记录所有访问级别的日志 58 | exports.systemLogger = log4js.getLogger('application'); // 记录所有应用级别的日志 59 | exports.accessErrorLogger = log4js.getLogger('accessErrorLogger'); // 记录所有访问时报错的日志 60 | exports.accessSimpleLogger = log4js.getLogger('accessSimpleLogger'); // 记录所有访问时报错的日志 61 | exports.sqlLog = log4js.getLogger('sqlLog'); // 记录所有访问时报错的日志 62 | -------------------------------------------------------------------------------- /core/prototype.js: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line no-extend-native 2 | Date.prototype.Format = function(fmt) { 3 | fmt = fmt ? fmt : 'yyyy-MM-dd hh:mm:ss'; 4 | 5 | var o = { 6 | 'M+': this.getMonth() + 1, // 月份 7 | 'd+': this.getDate(), // 日 8 | 'h+': this.getHours(), // 小时 9 | 'm+': this.getMinutes(), // 分 10 | 's+': this.getSeconds(), // 秒 11 | 'q+': Math.floor((this.getMonth() + 3) / 3), // 季度 12 | 'S': this.getMilliseconds() // 毫秒 13 | }; 14 | if (/(y+)/.test(fmt)) { 15 | fmt = fmt.replace(RegExp.$1, (this.getFullYear() + '').substr(4 - RegExp.$1.length)); 16 | } 17 | for (var k in o) { 18 | if (new RegExp('(' + k + ')').test(fmt)) { 19 | fmt = fmt.replace(RegExp.$1, (RegExp.$1.length == 1) ? (o[k]) : (('00' + o[k]).substr(('' + o[k]).length))); 20 | } 21 | } 22 | return fmt; 23 | }; 24 | 25 | module.exports = {}; 26 | -------------------------------------------------------------------------------- /core/redis.js: -------------------------------------------------------------------------------- 1 | const jwt = require('jwt-simple'); 2 | const CryptoJS = require('crypto-js'); 3 | const md5 = str => CryptoJS.MD5(str).toString().toUpperCase(); 4 | const redisConfig = require('~/config/redis.js'); 5 | const {secret} = require('~/config/common'); 6 | const redis = require('redis'), 7 | RDS_PORT = redisConfig.RDS_PORT, // 服务器端口 8 | RDS_HOST = redisConfig.RDS_HOST, // 服务器ip 9 | RDS_OPTS = redisConfig.RDS_OPTS, // 设置值 10 | RDS_PWD = redisConfig.RDS_PWD, // 密码 11 | client = redis.createClient({port: RDS_PORT, host: RDS_HOST, RDS_OPTS, password: RDS_PWD, db: 1}), 12 | client2 = redis.createClient({port: RDS_PORT, host: RDS_HOST, RDS_OPTS, password: RDS_PWD, db: 2}); 13 | client3 = redis.createClient({port: RDS_PORT, host: RDS_HOST, RDS_OPTS, password: RDS_PWD, db: 3}); 14 | 15 | client.auth(RDS_PWD, function() { 16 | console.log('redis1认证通过'); 17 | }); 18 | client2.auth(RDS_PWD, function() { 19 | console.log('redis2认证通过'); 20 | }); 21 | client3.auth(RDS_PWD, function() { 22 | console.log('redis3通过认证'); 23 | }); 24 | client.on('error', function(err) { 25 | console.log('Error ' + err); 26 | }); 27 | client.on('ready', function() { 28 | console.log('redis ok'); 29 | }); 30 | // 1.lpush 31 | // 在key对应 list的头部添加字符串元素 32 | // 33 | // 2.rpush 34 | // 在key对应 list 的尾部添加字符串元素 35 | let redisFn = { 36 | common: { 37 | // 获取当前db中所有的key 38 | getdbnamelist: async() => { 39 | return new Promise((resolve, reject) => { 40 | // 相当于命令(keys *), 返回list,包含当前db所有key的名字 41 | client.keys('*', function(err, val) { 42 | if (err) { 43 | resolve(); // 报错返回null 44 | } else { 45 | resolve(val); 46 | } 47 | }); 48 | }); 49 | }, 50 | // 添加list数据 51 | insertListInDB: async(dbname, dbdata) => { 52 | return new Promise((resolve, reject) => { 53 | client.rpush(dbname, dbdata, function(err) { 54 | if (err) { 55 | resolve(); // 报错返回null 56 | } else { 57 | resolve({dbdata, dbname}); 58 | // console.log('insert[%d] 个数据 in db[%s] finished',dbdata.length,dbname); 59 | } 60 | }); 61 | }); 62 | }, 63 | // 查询指定的key中,指定位置的内容 64 | querylistdata: async(dbname) => { 65 | return new Promise((resolve, reject) => { 66 | // 0 为起始位置,-1为最后的位置 67 | client.lrange(dbname, 0, -1, function(err, val) { 68 | if (err) { 69 | resolve(); // 报错返回null 70 | } else { 71 | resolve({val}); 72 | } 73 | }); 74 | }); 75 | }, 76 | // 删除 77 | deletelistdata: async(dbname) => { 78 | return new Promise((resolve, reject) => { 79 | // 保留指定位置的内容,其他全部删除,所以从0到-1就是一个不删; 从-1到0就是数据全部删除,相当于del key 80 | client.ltrim(dbname, -1, 0, function(err, val) { 81 | if (err) { 82 | resolve(); // 报错返回null 83 | } else { 84 | resolve({val}); 85 | } 86 | }); 87 | }); 88 | 89 | }, 90 | // 更新指定位置内容 91 | updatelist: async(dbname, dbdataindex, newinfo) => { 92 | return new Promise((resolve, reject) => { 93 | client.lset(dbname, dbdataindex, newinfo, function(err, val) { 94 | if (err) { 95 | resolve(); // 报错返回null 96 | } else { 97 | resolve({val}); 98 | } 99 | }); 100 | }); 101 | }, 102 | }, 103 | token: { 104 | /* 105 | * 用user换取JwtToken 106 | * source 指来源 pc、h5、app 107 | * */ 108 | getJwtToken: async(user, source) => { 109 | user = Object.assign({}, user); 110 | source = source || 'pc'; 111 | user.source = source; 112 | let date = new Date().getTime(); 113 | let jwtToken = jwt.encode(user, secret + date) + date; 114 | return jwtToken; 115 | }, 116 | /* 117 | * 用user换取token 118 | * source 指来源 pc、h5、app 119 | * */ 120 | setToken: async(user, source) => { 121 | let jwtToken = await redisFn.token.getJwtToken(user, source); 122 | let token = md5(jwtToken); 123 | client.set(token, user.id + '-' + source); 124 | client.expire(token, 60 * 60 * 24 * 60); // 缓存60天 125 | let obj = {}; 126 | let field = source + '-' + token; 127 | obj[field] = jwtToken; 128 | client2.hmset(user.id, obj, (err, reply) => { // 异步 129 | if (err) { 130 | throw err; 131 | } else { 132 | // 顶号操作,一个来源只允许一个账号登录 133 | // eslint-disable-next-line handle-callback-err 134 | client2.hgetall(user.id, (err, obj) => { 135 | Object.keys(obj).forEach((key, index) => { 136 | let keyArr = key.split('-'); 137 | let keySource = keyArr[0]; 138 | let keyToken = keyArr[1]; 139 | if (keySource == source && key != field) { 140 | client.del(keyToken, client.print); 141 | client2.hdel(user.id, key, client2.print); 142 | } 143 | }); 144 | }); 145 | } 146 | }); 147 | return token; 148 | }, 149 | /* 150 | * 用user换取tokeon 151 | * source 指来源 pc、h5、app 152 | * */ 153 | /* 154 | * 用admin的token 155 | * 用user换取token 156 | * source 指来源 pc、h5、app 157 | * */ 158 | setAdminToken: async(user, source) => { 159 | let jwtToken = await redisFn.token.getJwtToken(user, source); 160 | let token = md5(jwtToken); 161 | client2.set(token, user.id + '-' + source); 162 | client2.expire(token, 60 * 60 * 24 * 30); // 缓存30天 163 | let obj = {}; 164 | let field = source + '-' + token; 165 | obj[field] = jwtToken; 166 | client.hmset(user.id, obj, (err, reply) => { // 异步 167 | if (err) { 168 | throw err; 169 | } else { 170 | // 顶号操作,一个来源只允许一个账号登录 171 | // eslint-disable-next-line handle-callback-err 172 | client.hgetall(user.id, (err, obj) => { 173 | Object.keys(obj).forEach((key, index) => { 174 | let keyArr = key.split('-'); 175 | let keySource = keyArr[0]; 176 | let keyToken = keyArr[1]; 177 | if (keySource == source && key != field) { 178 | client2.del(keyToken, client2.print); 179 | client.hdel(user.id, key, client.print); 180 | } 181 | }); 182 | }); 183 | } 184 | }); 185 | return token; 186 | }, 187 | /* 188 | * 用token换取admin的user 189 | * 若没有user则返回空 190 | * */ 191 | getAdminUser: async(token) => { 192 | let userId = null; 193 | let source = null; 194 | let field = null; 195 | let jwtTokenStr = await new Promise(function(resolve, reject) { 196 | // eslint-disable-next-line handle-callback-err 197 | client2.get(token, (err, reply) => { 198 | if (!reply) { 199 | resolve(''); 200 | return; 201 | } 202 | let replyArr = reply.split('-'); 203 | userId = replyArr[0]; 204 | source = replyArr[1]; 205 | field = source + '-' + token; 206 | // eslint-disable-next-line handle-callback-err 207 | client.hget(userId, field, (err, reply) => { 208 | resolve(reply ? reply : ''); 209 | }); 210 | }); 211 | }); 212 | // console.log(jwtTokenStr); 213 | if (jwtTokenStr) { 214 | let jwtToken = jwtTokenStr.substring(0, jwtTokenStr.length - 13); 215 | let date = jwtTokenStr.substring(jwtTokenStr.length - 13, jwtTokenStr.length); 216 | let user = ''; 217 | try { 218 | user = jwt.decode(jwtToken, secret + date); 219 | return user; 220 | } catch (err) { 221 | if (userId) { // 删除跟该token有关的值,得不到就删除,一般不会来到这里的 222 | // console.log('得不到就删除,这是一个很大的bug,容易被攻击'); 223 | client2.del(token, client.print); 224 | client.hdel(userId, field, client2.print); 225 | } 226 | return ''; 227 | } 228 | } else { 229 | return ''; 230 | } 231 | }, 232 | /* 233 | * 更改用户信息 234 | * 用user重置下jwtToken的值 235 | * 更新成功则返回true 236 | * */ 237 | reSetToken: async(user) => { 238 | await new Promise((resolve, reject) => { 239 | client2.hgetall(user.id, async(err, obj) => { 240 | if (err) { 241 | reject(err); 242 | } 243 | let keys = Object.keys(obj); 244 | let i = 0, length = keys.length; 245 | for (i; i < length; i++) { 246 | let key = keys[i]; 247 | let keyArr = key.split('-'); 248 | let source = keyArr[0]; 249 | let jwtToken = await redisFn.token.getJwtToken(user, source); 250 | let token = keyArr[1]; 251 | let obj = {}; 252 | let field = source + '-' + token; 253 | obj[field] = jwtToken; 254 | await new Promise((resolve2, reject2) => { 255 | client2.hmset(user.id, obj, (err, reply) => { 256 | if (err) { 257 | reject2(err); 258 | } else { 259 | resolve2(true); 260 | } 261 | }); 262 | }); 263 | 264 | } 265 | resolve(true); 266 | }); 267 | }); 268 | 269 | return true; 270 | }, 271 | /* 272 | * 用token换取user 273 | * 若没有user则返回空 274 | * */ 275 | getUser: async(token) => { 276 | let userId = null; 277 | let source = null; 278 | let field = null; 279 | let jwtTokenStr = await new Promise(function(resolve, reject) { 280 | // eslint-disable-next-line handle-callback-err 281 | client.get(token, (err, reply) => { 282 | if (!reply) { 283 | resolve(''); 284 | return; 285 | } 286 | let replyArr = reply.split('-'); 287 | userId = replyArr[0]; 288 | source = replyArr[1]; 289 | field = source + '-' + token; 290 | // eslint-disable-next-line handle-callback-err 291 | client2.hget(userId, field, (err, reply) => { 292 | resolve(reply ? reply : ''); 293 | }); 294 | }); 295 | }); 296 | // console.log(jwtTokenStr); 297 | if (jwtTokenStr) { 298 | let jwtToken = jwtTokenStr.substring(0, jwtTokenStr.length - 13); 299 | let date = jwtTokenStr.substring(jwtTokenStr.length - 13, jwtTokenStr.length); 300 | let user = ''; 301 | try { 302 | user = jwt.decode(jwtToken, secret + date); 303 | return user; 304 | } catch (err) { 305 | if (userId) { // 删除跟该token有关的值,得不到就删除,一般不会来到这里的 306 | // console.log('得不到就删除,这是一个很大的bug,容易被攻击'); 307 | client.del(token, client.print); 308 | client2.hdel(userId, field, client2.print); 309 | } 310 | return ''; 311 | } 312 | } else { 313 | return ''; 314 | } 315 | }, 316 | /* 317 | * 删除该adminid的所有token信息 318 | * 删除成功则返回true 319 | * */ 320 | removeAdminToken: async(userId) => { 321 | try { 322 | // eslint-disable-next-line handle-callback-err 323 | client.hgetall(userId, (err, obj) => { 324 | if (!obj) { 325 | obj = []; 326 | } 327 | Object.keys(obj).forEach((key, index) => { 328 | let token = key.split('-')[1]; 329 | client2.del(token, client.print); 330 | client.hdel(userId, key, client2.print); 331 | }); 332 | }); 333 | client.del(userId); 334 | return true; 335 | } catch (err) { 336 | // console.log(err); 337 | return false; 338 | } 339 | }, 340 | /* 341 | * 删除该用户id的所有token信息 342 | * 删除成功则返回true 343 | * */ 344 | removeToken: async(userId) => { 345 | try { 346 | // eslint-disable-next-line handle-callback-err 347 | client2.hgetall(userId, (err, obj) => { 348 | if (!obj) { 349 | obj = []; 350 | } 351 | Object.keys(obj).forEach((key, index) => { 352 | let token = key.split('-')[1]; 353 | client.del(token, client.print); 354 | client2.hdel(userId, key, client2.print); 355 | }); 356 | }); 357 | client2.del(userId); 358 | return true; 359 | } catch (err) { 360 | // console.log(err); 361 | return false; 362 | } 363 | }, 364 | } 365 | }; 366 | module.exports = { 367 | client, 368 | client2, 369 | client3, 370 | ...redisFn, 371 | }; 372 | 373 | -------------------------------------------------------------------------------- /core/routeEach.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | const router = require('koa-router')(); 4 | const sign = require('~/middlewares/sign'); 5 | const basePathG = path.join(process.cwd(), 'controller'); 6 | let arrG = fs.readdirSync(basePathG); 7 | let basePathStrG = ''; 8 | 9 | function routeEach(app, baseUrl, pathArr, basePathStr, basePath) { 10 | pathArr = pathArr ? pathArr : arrG; 11 | basePathStr = basePathStr ? basePathStr : basePathStrG; 12 | basePath = basePath ? basePath : basePathG; 13 | baseUrl = baseUrl || ''; 14 | 15 | let i, length = pathArr.length; 16 | for (i = 0; i < length; i++) { 17 | let pathStr = path.join(basePath, `${basePathStr}/${pathArr[i]}`); 18 | if (!isExists(pathStr)) { // 检查是否有该文件或者目录 没有就继续下一个循环 19 | continue; 20 | } 21 | if (isDir(pathStr)) { // 检查是不是文件夹 22 | let arr = fs.readdirSync(pathStr); 23 | routeEach(app, baseUrl, arr, `${basePathStr}/${pathArr[i]}`, basePath); 24 | } else { 25 | let str = ''; 26 | if (pathArr[i] == 'index.js') { 27 | str = `${basePathStr}`; 28 | } else { 29 | str = `${basePathStr}/${pathArr[i].substring(0, pathArr[i].length - 3)}`; 30 | } 31 | let initParams = { 32 | page: async(ctx, next) => { 33 | ctx.body = '空白页哦'; 34 | }, 35 | method: 'all', 36 | // before: [sign], 37 | before: [], 38 | after: [function(ctx) { 39 | ctx.throw(500); 40 | }] 41 | }; 42 | let pageParams = Object.assign({}, initParams, require(pathStr)); 43 | pageParams.before = initParams.before.concat(pageParams.before); 44 | pageParams.after = initParams.after.concat(pageParams.after); 45 | str = baseUrl + str; 46 | console.log(str); 47 | router[pageParams.method](str, ...pageParams.before, pageParams.page, ...pageParams.after); 48 | } 49 | } 50 | app.use(router.routes(), router.allowedMethods()); 51 | } 52 | 53 | function isExists(path) { // 查看是否有该文件或目录 54 | if (fs.existsSync(path)) { 55 | return true; 56 | } 57 | return; 58 | } 59 | 60 | function isDir(path) { // 查看是不是文件夹 61 | if (fs.existsSync(path) && fs.statSync(path).isDirectory()) { // 先判断存在不存在 再判断文件类型,判断是不是文件夹 62 | return true; 63 | } 64 | return false; 65 | } 66 | 67 | module.exports = routeEach; 68 | -------------------------------------------------------------------------------- /core/schedule.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const fs = require('fs'); 3 | const schedule = require('node-schedule'); 4 | 5 | // 自动扫指定目录下面的文件并且加载 6 | function scanFilesByFolder(cb) { 7 | const _folder = path.join(process.cwd(), 'schedule/'); 8 | try { 9 | const files = fs.readdirSync(_folder); 10 | files.forEach((file) => { 11 | let filename = file.replace('.js', ''); 12 | 13 | let oFileCnt = require(_folder + '/' + filename); 14 | 15 | cb && cb(filename, oFileCnt); 16 | }); 17 | 18 | } catch (error) { 19 | // console.log('文件自动加载失败...', error); 20 | } 21 | } 22 | 23 | // 加载定时任务 24 | function initSchedule() { 25 | scanFilesByFolder((filename, scheduleConf) => { 26 | // console.log(filename); 27 | // console.log(scheduleConf); 28 | schedule.scheduleJob(scheduleConf.interval, scheduleConf.handler); 29 | }); 30 | } 31 | 32 | module.exports = initSchedule; 33 | -------------------------------------------------------------------------------- /extend/context.js: -------------------------------------------------------------------------------- 1 | const error = require('~/config/error.js'); 2 | 3 | module.exports = { 4 | /** 5 | * 成功返回的格式 6 | * @param {*} data 返回的数据 7 | */ 8 | success(data) { 9 | this.ctx.body = { 10 | code: 1000, 11 | data 12 | }; 13 | }, 14 | /** 15 | * 失败返回的格式 16 | * @param {*} errorMsg 返回的错误信息 17 | */ 18 | error(errorMsg) { 19 | this.ctx.body = { 20 | code: 1002, 21 | // data: null, 22 | msg: errorMsg || '', 23 | }; 24 | }, 25 | errorCode(errorCode) { 26 | this.ctx.body = error[errorCode] || { 27 | code: '10500', 28 | msg: '未知错误,请复制错误码' + new Date().getTime() + '联系我们' 29 | }; 30 | }, 31 | toJSON(data, msg, code) { 32 | this.ctx.body = { 33 | code: code || 1000, 34 | data: data, 35 | msg: msg || '' 36 | }; 37 | /* 38 | * code 39 | * 1000 请求接口成功 40 | * 1002 提示错误信息,前端直接显示报错信息 41 | * 1003 token验证失败,前端直接跳转到登录页 42 | * 1004 权限不够,前端直接跳转到首页 43 | * */ 44 | }, 45 | /* 46 | * 专供于service返回信息 47 | * */ 48 | toData({isSuccess = false, data = null, msg = null} = {}) { 49 | return {isSuccess, data, msg}; 50 | }, 51 | /* 52 | * 专供于service返回成功信息 53 | * */ 54 | toSuccess(data) { 55 | return {isSuccess: true, data}; 56 | }, 57 | /* 58 | * 专供于service返回失败信息 59 | * */ 60 | toError(msg) { 61 | return {isSuccess: false, msg}; 62 | }, 63 | 64 | exclude: { 65 | exclude: ['createdAt', 'updatedAt', 'deletedAt'] 66 | }, 67 | /* 68 | * 下面都是时间 start 69 | * */ 70 | toMonthFirst(paramsTime) { // 获取本月1号 0:00:00 000 的时间戳 71 | let time; 72 | if (paramsTime && new Date(paramsTime)) { 73 | time = new Date(new Date(paramsTime).setDate(1)).setHours(0, 0, 0, 0); 74 | } else { 75 | time = new Date(new Date().setDate(1)).setHours(0, 0, 0, 0); 76 | } 77 | return time; 78 | }, 79 | toMonthCount(paramsTime) { // 获取本月天数 80 | var date; 81 | 82 | if (paramsTime && new Date(paramsTime)) { 83 | date = new Date(paramsTime); 84 | } else { 85 | date = new Date(); 86 | } 87 | var year = date.getFullYear(); 88 | var month = date.getMonth() + 1; 89 | var d = new Date(year, month, 0); 90 | return d.getDate(); 91 | }, 92 | toDayCount(paramsTime) { // 今天是本月的第几天 93 | if (paramsTime && new Date(paramsTime)) { 94 | return new Date().getDate(paramsTime); 95 | } 96 | return new Date().getDate(); 97 | }, 98 | BeforeSevenDayStart() { // 获取六天前的 0:00:00 000 // 六日前的0点。。比如今天是30号,也就是获取24号的0点。 99 | // 获取今日0点 100 | let time = new Date().setHours(0, 0, 0, 0); 101 | let beforeSevenTime = time - 60 * 60 * 24 * 1000 * 6; 102 | return beforeSevenTime; 103 | }, 104 | toDayStart() { // 获取今日0:00:00 000 105 | // 获取今日0点 106 | let time = new Date().setHours(0, 0, 0, 0); 107 | return time; 108 | }, 109 | toDayEnd() { // 获取今日23:59:59 999 110 | // 获取今日0点 111 | let time = new Date().setHours(0, 0, 0, 0); 112 | let endTime = time + 60 * 60 * 24 * 1000 - 1; // 今日23:59:59 113 | return endTime; 114 | }, 115 | /* 116 | * 下面都是时间 end 117 | * */ 118 | /* 119 | * 定时任务实例存储 120 | * */ 121 | schedule: { 122 | 123 | }, 124 | getClientIP(request) { 125 | const {req} = request; 126 | // return req.headers['x-forwarded-for'] || // 判断是否有反向代理 IP 127 | // req.headers['x-real-ip'] || 128 | // req.headers.referer || 129 | // req.headers.host; 130 | return req.headers['x-forwarded-for'] || // 判断是否有反向代理 IP 131 | req.connection.remoteAddress || // 判断 connection 的远程 IP 132 | req.socket.remoteAddress || // 判断后端的 socket 的 IP 133 | req.connection.socket.remoteAddress; 134 | } 135 | }; 136 | -------------------------------------------------------------------------------- /lib/timoSign/checkSign.js: -------------------------------------------------------------------------------- 1 | const md5 = require('./md5.js'); 2 | const objToString = require('./objToString'); 3 | const checkSign = ({ data, name, key }) => { 4 | let params = Object.assign({}, data); 5 | let { token, sign, timestamp } = params; 6 | delete params.token; 7 | delete params.sign; 8 | delete params.timestamp; 9 | let paramsStr = objToString(params); 10 | let checkSign = null; 11 | if (token) { 12 | checkSign = md5(`${name + key + token + timestamp + paramsStr}`); 13 | } else { 14 | checkSign = md5(`${name + key + timestamp + paramsStr}`); 15 | } 16 | return checkSign == sign; 17 | }; 18 | 19 | module.exports = checkSign; -------------------------------------------------------------------------------- /lib/timoSign/getSign.js: -------------------------------------------------------------------------------- 1 | const md5 = require('./md5.js'); 2 | const objToString = require('./objToString'); 3 | const getSign = ({ data, name, key }) => { 4 | let params = Object.assign({}, data); 5 | let {token} = params; 6 | delete params.token; 7 | let paramsStr = objToString(params); 8 | let timestamp = new Date().getTime(); 9 | let sign = null; 10 | if (token) { 11 | sign = md5(`${name + key + token + timestamp + paramsStr}`); 12 | } else { 13 | sign = md5(`${name + key + timestamp + paramsStr}`); 14 | } 15 | 16 | return Object.assign({}, data, { 17 | sign, timestamp 18 | }); 19 | }; 20 | 21 | module.exports = getSign; 22 | -------------------------------------------------------------------------------- /lib/timoSign/index.js: -------------------------------------------------------------------------------- 1 | const checkSign = require('./checkSign'); 2 | const getSign = require('./getSign'); 3 | const md5 = require('./md5'); 4 | 5 | module.exports = { 6 | checkSign, 7 | getSign, 8 | md5 9 | }; -------------------------------------------------------------------------------- /lib/timoSign/md5.js: -------------------------------------------------------------------------------- 1 | const CryptoJS = require('crypto-js'); 2 | const md5 = str => CryptoJS.MD5(str).toString().toUpperCase(); 3 | 4 | module.exports = md5; -------------------------------------------------------------------------------- /lib/timoSign/objToString.js: -------------------------------------------------------------------------------- 1 | const objToString = (params) => { 2 | let paramsStr = ''; 3 | let paramsKey = ''; 4 | let keys = Object.keys(params).sort(); 5 | keys.forEach((key, index) => { 6 | paramsKey += key; 7 | if(typeof params[key] == 'object') { 8 | paramsStr += objToString(params[key]) + ''; 9 | } else { 10 | paramsStr += params[key] + ''; 11 | } 12 | }); 13 | return paramsKey + paramsStr; 14 | } 15 | module.exports = objToString; -------------------------------------------------------------------------------- /lib/timoSign/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "timoSign", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "license": "MIT", 6 | "dependencies": { 7 | "crypto-js": "^3.1.9-1" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /middlewares/oauth.js: -------------------------------------------------------------------------------- 1 | let oauth = async(ctx, next) => { 2 | ctx.name = '冰火'; 3 | await next(); 4 | }; 5 | 6 | module.exports = oauth; 7 | -------------------------------------------------------------------------------- /middlewares/sign.js: -------------------------------------------------------------------------------- 1 | const { checkSign,getSign } = require('~/lib/timoSign/'); 2 | const { pcName, pcKey } = require('~/config/common'); 3 | const host = require('~/config/host'); 4 | let sign = async (ctx, next)=>{ 5 | let name = null; 6 | let key = null; 7 | if(host.pc.indexOf(ctx.host) != -1) { 8 | name = pcName; 9 | key = pcKey; 10 | } 11 | if(!name || !key) { 12 | ctx.status = 404; 13 | ctx.error('总有刁民想害朕'); 14 | return; 15 | } 16 | // console.log(ctx.params); 17 | // console.log(getSign({data: ctx.params,name,key})); 18 | if(checkSign({data: ctx.params, name, key})) { 19 | await next(); 20 | } else { 21 | ctx.status = 404; 22 | ctx.error('服务器驳回请求,该请求已被记录。'); 23 | } 24 | }; 25 | 26 | module.exports = sign; 27 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "icefire", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "./node_modules/.bin/nodemon bin/debug", 7 | "dev-model": "./node_modules/.bin/nodemon bin/debug -i ./models", 8 | "start-debug": "node bin/debug", 9 | "start": "node bin/rrxapi", 10 | "model": "node auto", 11 | "prd": "pm2 start bin/rrxapi" 12 | }, 13 | "dependencies": { 14 | "axios": "^0.19.2", 15 | "babel-eslint": "^10.0.3", 16 | "best-require": "^1.1.4", 17 | "busboy": "^0.3.1", 18 | "crypto-js": "^3.1.9-1", 19 | "debug": "^2.6.3", 20 | "eslint": "^3.6.0", 21 | "eslint-config-airbnb": "^11.2.0", 22 | "eslint-plugin-babel": "^3.3.0", 23 | "eslint-plugin-import": "^1.16.0", 24 | "eslint-plugin-jsx-a11y": "^2.2.2", 25 | "eslint-plugin-react": "^6.3.0", 26 | "jwt-simple": "^0.5.6", 27 | "koa": "^2.2.0", 28 | "koa-bodyparser": "^3.2.0", 29 | "koa-compress": "^3.0.0", 30 | "koa-favicon": "^2.0.1", 31 | "koa-helmet": "^4.1.0", 32 | "koa-json": "^2.0.2", 33 | "koa-log4": "^2.3.2", 34 | "koa-router": "^7.1.1", 35 | "koa-static-server": "^1.4.0", 36 | "koa-views": "^5.2.1", 37 | "mysql": "^2.16.0", 38 | "mysql2": "^1.6.5", 39 | "node-schedule": "^1.3.2", 40 | "pug": "^2.0.0-rc.1", 41 | "redis": "^2.8.0", 42 | "request": "^2.88.2", 43 | "request-promise": "^4.2.5", 44 | "sequelize": "^5.21.6", 45 | "sequelize-auto": "^0.4.29", 46 | "underscore": "^1.10.2", 47 | "xlsx": "^0.16.2", 48 | "xml2js": "^0.4.23", 49 | "xmlreader": "^0.2.3" 50 | }, 51 | "devDependencies": { 52 | "nodemon": "^2.0.3" 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IceInTheFire/icefire-node-server/3aaec2320aa87d4dfb3c66511463babdcbd75145/public/favicon.ico -------------------------------------------------------------------------------- /public/stylesheets/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding: 50px; 3 | font: 14px "Lucida Grande", Helvetica, Arial, sans-serif; 4 | } 5 | 6 | a { 7 | color: #00B7FF; 8 | } 9 | -------------------------------------------------------------------------------- /schedule/first.js: -------------------------------------------------------------------------------- 1 | const Sequelize = require('sequelize'); 2 | const Op = Sequelize.Op; 3 | const schedule = require('node-schedule'); 4 | 5 | const {ctx} = require('~/core/ctx'); 6 | 7 | let rule = new schedule.RecurrenceRule(); 8 | rule.hour = 0; // 0点 9 | rule.minute = 0; // 0分 10 | rule.second = 0; // 0秒 11 | const {accessLogger, systemLogger, accessErrorLogger, accessSimpleLogger} = require('~/core/logger'); 12 | 13 | module.exports = { 14 | interval: rule, 15 | async handler() { 16 | try { 17 | console.log('每天定时0点0分0秒打印') 18 | } catch (err) { 19 | // console.log(err); 20 | accessErrorLogger.error(err); // 报错详情 21 | } 22 | } 23 | }; 24 | -------------------------------------------------------------------------------- /service/base.js: -------------------------------------------------------------------------------- 1 | class baseService { 2 | async get() { 3 | const {ctx} = this; 4 | return '我是从service里获取的'; 5 | } 6 | } 7 | 8 | module.exports = baseService; 9 | -------------------------------------------------------------------------------- /service/like/book.js: -------------------------------------------------------------------------------- 1 | class bookService { 2 | async get() { 3 | const {ctx} = this; 4 | return `你好,我的世界不欢迎你啊,${ctx.name}`; 5 | } 6 | } 7 | 8 | module.exports = bookService; 9 | -------------------------------------------------------------------------------- /service/test.js: -------------------------------------------------------------------------------- 1 | class testService { 2 | async insert({name, age, sex}) { 3 | const {ctx} = this; 4 | let result = await ctx.db.User.create({ 5 | name, age, sex, 6 | }); 7 | return result; 8 | } 9 | 10 | async findAll({name, sex, age, id}) { 11 | const {ctx} = this; 12 | let result = null; 13 | let options = { 14 | attributes: ['name', 'id', 'age', 'sex'], 15 | where: {} 16 | }; 17 | name ? options.where.name = name : ''; 18 | sex ? options.where.sex = sex : ''; 19 | age ? options.where.age = age : ''; 20 | id ? options.where.id = id : ''; 21 | result = await ctx.db.User.findAll(options); 22 | return result; 23 | } 24 | 25 | async findAndCountAll({name, sex, age, id}) { 26 | const {ctx} = this; 27 | let result = null; 28 | let options = { 29 | attributes: ['name', 'id', 'age', 'sex'], 30 | where: {} 31 | }; 32 | name ? options.where.name = name : ''; 33 | sex ? options.where.sex = sex : ''; 34 | age ? options.where.age = age : ''; 35 | id ? options.where.id = id : ''; 36 | 37 | result = await ctx.db.User.findAndCountAll(options); 38 | 39 | return result; 40 | } 41 | 42 | async findOne({name, sex, age, id}) { 43 | const {ctx} = this; 44 | let result = null; 45 | let options = { 46 | attributes: ['name', 'id', 'age', 'sex'], 47 | where: {} 48 | }; 49 | name ? options.where.name = name : ''; 50 | sex ? options.where.sex = sex : ''; 51 | age ? options.where.age = age : ''; 52 | id ? options.where.id = id : ''; 53 | result = await ctx.db.User.findAll(options); 54 | return result; 55 | } 56 | 57 | async update({name, sex, age, id}) { 58 | const {ctx} = this; 59 | 60 | let options = [{ 61 | name, sex, age 62 | }, { 63 | where: {} 64 | }]; 65 | id ? options[1].where.id = id : ''; 66 | let result = null; 67 | result = await ctx.db.User.update(...options); 68 | return result; 69 | } 70 | 71 | async del({name, sex, age, id}) { 72 | const {ctx} = this; 73 | // let result = await ctx.db.tool.destroy({ ctx, tableName:'user', options: { 74 | // where:{ 75 | // name: '你好' 76 | // } 77 | // }}); 78 | let options = { 79 | where: {} 80 | }; 81 | name ? options.where.name = name : ''; 82 | sex ? options.where.sex = sex : ''; 83 | age ? options.where.age = age : ''; 84 | id ? options.where.id = id : ''; 85 | let result = null; 86 | result = await ctx.db.User.destroy(options); 87 | return result; 88 | } 89 | 90 | async redisInsert() { 91 | const {ctx} = this; 92 | let result = await ctx.redis.common.insertListInDB('冰火', '你好,我的伙伴'); 93 | return result; 94 | } 95 | 96 | async redisGet() { 97 | const {ctx} = this; 98 | let keys = await ctx.redis.common.getdbnamelist(); 99 | let result = []; 100 | for (let key of keys) { // for of 性能较慢, 真实项目。建议用for循环 101 | let obj = {}; 102 | obj[key] = await ctx.redis.common.querylistdata(key); 103 | result.push(obj); 104 | } 105 | return result; 106 | } 107 | } 108 | 109 | module.exports = testService; 110 | -------------------------------------------------------------------------------- /sql/timotest.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Navicat Premium Data Transfer 3 | 4 | Source Server : 宜果送 5 | Source Server Type : MySQL 6 | Source Server Version : 50726 7 | Source Host : rm-bp11sk9xkq10w58j0co.mysql.rds.aliyuncs.com:3306 8 | Source Schema : group 9 | 10 | Target Server Type : MySQL 11 | Target Server Version : 50726 12 | File Encoding : 65001 13 | 14 | Date: 20/04/2020 16:57:42 15 | */ 16 | 17 | SET NAMES utf8mb4; 18 | SET FOREIGN_KEY_CHECKS = 0; 19 | 20 | -- ---------------------------- 21 | -- Table structure for step 22 | -- ---------------------------- 23 | DROP TABLE IF EXISTS `step`; 24 | CREATE TABLE `step` ( 25 | `id` int(10) unsigned NOT NULL AUTO_INCREMENT, 26 | `tableName` varchar(100) DEFAULT NULL, 27 | `createdAt` datetime DEFAULT NULL, 28 | `updatedAt` datetime DEFAULT NULL, 29 | `deletedAt` datetime DEFAULT NULL, 30 | `number` int(100) DEFAULT NULL, 31 | PRIMARY KEY (`id`) 32 | ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4; 33 | 34 | -- ---------------------------- 35 | -- Records of step 36 | -- ---------------------------- 37 | BEGIN; 38 | INSERT INTO `step` VALUES (1, 'user', NULL, '2020-04-20 07:04:01', NULL, 0); 39 | COMMIT; 40 | 41 | -- ---------------------------- 42 | -- Table structure for user 43 | -- ---------------------------- 44 | DROP TABLE IF EXISTS `user`; 45 | CREATE TABLE `user` ( 46 | `id` int(10) NOT NULL AUTO_INCREMENT, 47 | `sex` int(10) DEFAULT NULL, 48 | `age` int(10) DEFAULT NULL, 49 | `name` varchar(255) DEFAULT NULL, 50 | `createdAt` datetime DEFAULT NULL, 51 | `updatedAt` datetime DEFAULT NULL, 52 | `deletedAt` datetime DEFAULT NULL, 53 | PRIMARY KEY (`id`) 54 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; 55 | 56 | SET FOREIGN_KEY_CHECKS = 1; 57 | -------------------------------------------------------------------------------- /tool/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const axios = require('axios'); 3 | const Qs = require('qs'); 4 | const permissionList = require('~/tool/permissionList.js'); 5 | /** 6 | * 工具大全 7 | */ 8 | module.exports = { 9 | /** 10 | * 判断是否是手机号 11 | * 是则返回true 12 | * 否则返回false 13 | */ 14 | isMobile, 15 | /** 16 | * 判断是否是obj 17 | * 判断msg是否是对象,并判断不是数组 18 | */ 19 | isObject, 20 | /** 21 | * 判断是不是整数 22 | */ 23 | isInt, 24 | /** 25 | * 删除对象里的空值 26 | */ 27 | delEmptyKey, 28 | /** 29 | * 输出正确的sql语句 30 | * 如果是字符串的话,返回多加两个双引号 31 | * 如果不是,则照样返回 32 | */ 33 | toSqlStr(str) { 34 | if (typeof str == 'string') { 35 | return `"${str}"`; 36 | } else { 37 | return str; 38 | } 39 | }, 40 | 41 | httpGet, 42 | httpPost, 43 | 44 | permissionList: permissionList.permissionList, 45 | allPermissionList: permissionList.allPermissionList 46 | }; 47 | 48 | /** 49 | * 50 | * @param {object} obj 51 | * 删除对象里的空值 52 | */ 53 | function delEmptyKey(obj) { 54 | let result = {}; 55 | /* Object.entries遍历键值对 */ 56 | // let obj = { a: 1, b: 2, c: function () { } }; 57 | // Object.entries(obj)//[['a',1],['b', 2], ['c',function]]返回对象键值对数组 58 | Object.entries(obj).forEach((value, index) => { 59 | if (value[1]) { 60 | result[value[0]] = value[1]; 61 | } 62 | }); 63 | return result; 64 | } 65 | 66 | /* 67 | * 判断是否是obj 68 | * 判断msg是否是对象,并判断不是数组 69 | * */ 70 | function isObject(obj) { 71 | return (typeof obj == 'object' && !Array.isArray(obj)); 72 | } 73 | 74 | /* 75 | * 判断是否是手机号 76 | * 是则返回true 77 | * 否则返回false 78 | */ 79 | function isMobile(mobile) { 80 | return /^[1][3,4,5,6,7,8,9][0-9]{9}$/.test(mobile); 81 | } 82 | 83 | /* 84 | * 判断是不是整数 85 | */ 86 | function isInt(str) { 87 | var patrn = /^[0-9]*$/; 88 | if (patrn.exec(str) == null || str == '') { 89 | return false; 90 | } else { 91 | return true; 92 | } 93 | } 94 | 95 | async function httpPost({url, params}) { 96 | return new Promise(async(resolve, reject) => { 97 | try { 98 | let response = await axios({ 99 | method: 'POST', 100 | headers: {'Content-Type': 'application/x-www-form-urlencoded'}, 101 | url: url, 102 | data: JSON.stringify(params), 103 | // responseType: 'arraybuffer', 104 | success(data) { 105 | console.log(data); 106 | } 107 | }); 108 | resolve(response); 109 | } catch (err) { 110 | reject(err); 111 | } 112 | }); 113 | } 114 | 115 | async function httpGet({url, params}) { 116 | return new Promise(async(resolve, reject) => { 117 | try { 118 | axios.get(url, { 119 | params: params 120 | }).then((response) => { 121 | resolve(response.data); 122 | }).catch((error) => { 123 | reject(error); 124 | }); 125 | } catch (err) { 126 | reject(err); 127 | } 128 | }); 129 | } 130 | -------------------------------------------------------------------------------- /tool/permissionList.js: -------------------------------------------------------------------------------- 1 | let permissionList = [ 2 | { 3 | expand: false, 4 | title: '页面', 5 | ispage: true, 6 | children: [ 7 | { 8 | title: '权限管理', 9 | expand: false, 10 | id: 6000, 11 | children: [ 12 | { 13 | title: '角色管理', 14 | expand: false, 15 | id: 6100, 16 | }, 17 | { 18 | title: '职员管理', 19 | expand: false, 20 | id: 6200 21 | }, 22 | ] 23 | }, 24 | { 25 | title: '用户管理', 26 | expand: false, 27 | id: 14000, 28 | children: [ 29 | { 30 | title: '用户列表', 31 | expand: false, 32 | id: 14100, 33 | } 34 | ] 35 | }, 36 | { 37 | title: '商品管理', 38 | expand: false, 39 | id: 7000, 40 | children: [ 41 | { 42 | title: '商品列表', 43 | expand: false, 44 | id: 7100, 45 | }, 46 | { 47 | title: '分类管理', 48 | expand: false, 49 | id: 7200 50 | }, 51 | ] 52 | }, 53 | { 54 | title: '优惠券列表', 55 | expand: false, 56 | id: 8000, 57 | children: [ 58 | { 59 | title: '优惠券列表', 60 | expand: false, 61 | id: 8100, 62 | }, 63 | ] 64 | }, 65 | { 66 | title: '订单列表', 67 | expand: false, 68 | id: 9000, 69 | children: [ 70 | { 71 | title: '订单列表', 72 | expand: false, 73 | id: 9100, 74 | }, 75 | { 76 | title: '每日商品配送', 77 | expand: false, 78 | id: 9200, 79 | }, 80 | { 81 | title: '商品配送列表', 82 | expand: false, 83 | id: 9300, 84 | }, 85 | ] 86 | }, 87 | { 88 | title: '门店列表', 89 | expand: false, 90 | id: 10000, 91 | children: [ 92 | { 93 | title: '门店列表', 94 | expand: false, 95 | id: 10100, 96 | }, 97 | ] 98 | }, 99 | { 100 | title: '首页配置', 101 | expand: false, 102 | id: 11000, 103 | children: [ 104 | { 105 | title: '首页配置', 106 | expand: false, 107 | id: 11100, 108 | }, 109 | ] 110 | }, 111 | { 112 | title: '比例管理', 113 | expand: false, 114 | id: 12000, 115 | children: [ 116 | { 117 | title: '金币汇率', 118 | expand: false, 119 | id: 12100, 120 | }, 121 | { 122 | title: '返佣比例', 123 | expand: false, 124 | id: 12200, 125 | }, 126 | { 127 | title: '赠送金币', 128 | expand: false, 129 | id: 12300, 130 | }, 131 | { 132 | title: '金币上限', 133 | expand: false, 134 | id: 12400, 135 | }, 136 | ] 137 | }, 138 | { 139 | title: '工具管理', 140 | expand: false, 141 | id: 13000, 142 | children: [ 143 | { 144 | title: '图片上传', 145 | expand: false, 146 | id: 13100, 147 | } 148 | ] 149 | } 150 | ] 151 | }, 152 | { 153 | expand: false, 154 | title: '接口', 155 | children: [ 156 | { 157 | title: '权限模块', 158 | expand: false, 159 | // id: 6000, 160 | children: [ 161 | { 162 | title: '角色', 163 | expand: false, 164 | // id:6100, 165 | children: [ 166 | {title: '获取角色列表', id: 6101, path: '/admin/permission/roleList'}, 167 | {title: '添加角色', id: 6102, path: '/admin/permission/addRole'}, 168 | {title: '编辑角色', id: 6103, path: '/admin/permission/editRole'}, 169 | {title: '删除角色', id: 6104, path: '/admin/permission/delRole'}, 170 | ] 171 | }, 172 | { 173 | title: '职员', 174 | expand: false, 175 | // id:6200 176 | children: [ 177 | {title: '获取职员列表', id: 6201, path: '/admin/permission/staffList'}, 178 | {title: '添加职员', id: 6202, path: '/admin/permission/addStaff'}, 179 | {title: '编辑职员', id: 6203, path: '/admin/permission/editStaff'}, 180 | {title: '删除职员', id: 6204, path: '/admin/permission/delStaff'}, 181 | ] 182 | }, 183 | {title: '获取权限列表', id: 6001, path: '/admin/permission/list'}, 184 | ] 185 | }, 186 | ] 187 | } 188 | ]; 189 | 190 | let allPermissionList = []; 191 | 192 | addAccess(permissionList[0]); 193 | addAccess(permissionList[1]); 194 | 195 | function addAccess(value) { 196 | if (value.children) { 197 | if(value.id) { 198 | allPermissionList.push(value.id); 199 | } 200 | value.children.forEach((value, index) => { 201 | addAccess(value); 202 | }); 203 | } else { 204 | allPermissionList.push(value.id); 205 | } 206 | } 207 | 208 | module.exports = { 209 | permissionList, 210 | allPermissionList, // 页面参数 211 | }; 212 | 213 | -------------------------------------------------------------------------------- /tool/xmlTool.js: -------------------------------------------------------------------------------- 1 | const xml2js = require('xml2js'); 2 | 3 | const xml = { 4 | xmlToJson: (str) => { 5 | return new Promise((resolve, reject) => { 6 | const parseString = xml2js.parseString; 7 | parseString(str, (err, result) => { 8 | if (err) { 9 | reject(err); 10 | } else { 11 | resolve(result); 12 | } 13 | }); 14 | }); 15 | }, 16 | xmlToJsonObj: (str) => { 17 | return new Promise((resolve, reject) => { 18 | const parseString = xml2js.parseString; 19 | parseString(str, { explicitArray : false, ignoreAttrs : true }, (err, result) => { 20 | if (err) { 21 | reject(err); 22 | } else { 23 | resolve(result); 24 | } 25 | }); 26 | }); 27 | }, 28 | jsonToXml: (obj) => { 29 | const builder = new xml2js.Builder(); 30 | return builder.buildObject(obj); 31 | } 32 | }; 33 | 34 | module.exports = xml; 35 | -------------------------------------------------------------------------------- /views/error.pug: -------------------------------------------------------------------------------- 1 | extends layout 2 | 3 | block content 4 | h1= message 5 | h2= error.status 6 | pre #{error.stack} 7 | -------------------------------------------------------------------------------- /views/index.pug: -------------------------------------------------------------------------------- 1 | extends layout 2 | 3 | block content 4 | h1= title 5 | p Welcome to #{title} 6 | -------------------------------------------------------------------------------- /views/layout.pug: -------------------------------------------------------------------------------- 1 | doctype html 2 | html 3 | head 4 | title= title 5 | link(rel='stylesheet', href='/stylesheets/style.css') 6 | body 7 | block content 8 | --------------------------------------------------------------------------------