├── .gitignore ├── 00-static ├── .dockerignore ├── Dockerfile ├── package.json ├── public │ └── index.html ├── src │ └── server.js └── yarn.lock ├── 01-api-and-layering ├── .dockerignore ├── Dockerfile ├── package.json ├── public │ ├── index.html │ └── index.js ├── src │ ├── controllers │ │ ├── index.js │ │ └── shop.js │ ├── server.js │ └── services │ │ └── shop.js └── yarn.lock ├── 02-validate ├── .dockerignore ├── Dockerfile ├── package.json ├── public │ ├── glue.js │ ├── index.css │ ├── index.html │ └── index.js ├── src │ ├── controllers │ │ ├── index.js │ │ └── shop.js │ ├── moulds │ │ ├── ShopForm.js │ │ └── yup.js │ ├── server.js │ └── services │ │ └── shop.js └── yarn.lock ├── 03-middleware ├── .dockerignore ├── Dockerfile ├── package.json ├── public │ ├── glue.js │ ├── index.css │ ├── index.html │ └── index.js ├── src │ ├── controllers │ │ ├── index.js │ │ └── shop.js │ ├── middlewares │ │ ├── index.js │ │ └── urlnormalize.js │ ├── moulds │ │ ├── ShopForm.js │ │ └── yup.js │ ├── server.js │ └── services │ │ └── shop.js └── yarn.lock ├── 04-exception ├── .dockerignore ├── Dockerfile ├── package.json ├── public │ ├── 500.html │ ├── glue.js │ ├── index.css │ ├── index.html │ └── index.js ├── src │ ├── controllers │ │ ├── chaos.js │ │ ├── health.js │ │ ├── index.js │ │ └── shop.js │ ├── middlewares │ │ ├── index.js │ │ └── urlnormalize.js │ ├── moulds │ │ ├── ShopForm.js │ │ └── yup.js │ ├── server.js │ ├── services │ │ └── shop.js │ └── utils │ │ └── cc.js └── yarn.lock ├── 05-database ├── .dockerignore ├── .npmrc ├── .sequelizerc ├── Dockerfile ├── database │ └── .gitkeep ├── package.json ├── public │ ├── 500.html │ ├── glue.js │ ├── index.css │ ├── index.html │ └── index.js ├── src │ ├── controllers │ │ ├── chaos.js │ │ ├── health.js │ │ ├── index.js │ │ └── shop.js │ ├── middlewares │ │ ├── index.js │ │ └── urlnormalize.js │ ├── models │ │ ├── config │ │ │ └── index.js │ │ ├── index.js │ │ ├── migrate │ │ │ └── 20200725045100-create-shop.js │ │ ├── seed │ │ │ └── 20200725050230-first-shop.js │ │ └── shop.js │ ├── moulds │ │ ├── ShopForm.js │ │ └── yup.js │ ├── server.js │ ├── services │ │ └── shop.js │ └── utils │ │ └── cc.js └── yarn.lock ├── 06-session ├── .dockerignore ├── .npmrc ├── .sequelizerc ├── Dockerfile ├── database │ └── .gitkeep ├── package.json ├── public │ ├── 500.html │ ├── glue.js │ ├── index.css │ ├── index.html │ ├── index.js │ └── login.html ├── src │ ├── controllers │ │ ├── chaos.js │ │ ├── health.js │ │ ├── index.js │ │ ├── login.js │ │ └── shop.js │ ├── middlewares │ │ ├── index.js │ │ ├── login.js │ │ ├── session.js │ │ └── urlnormalize.js │ ├── models │ │ ├── config │ │ │ └── index.js │ │ ├── index.js │ │ ├── migrate │ │ │ ├── 20200725045100-create-shop.js │ │ │ └── 20200727025727-create-session.js │ │ ├── seed │ │ │ └── 20200725050230-first-shop.js │ │ └── shop.js │ ├── moulds │ │ ├── ShopForm.js │ │ └── yup.js │ ├── server.js │ ├── services │ │ └── shop.js │ └── utils │ │ └── cc.js └── yarn.lock ├── 07-authentication ├── .dockerignore ├── .npmrc ├── .sequelizerc ├── Dockerfile ├── database │ └── .gitkeep ├── package.json ├── public │ ├── 500.html │ ├── glue.js │ ├── index.css │ ├── index.html │ ├── index.js │ └── login.html ├── src │ ├── controllers │ │ ├── chaos.js │ │ ├── health.js │ │ ├── index.js │ │ ├── login.js │ │ └── shop.js │ ├── middlewares │ │ ├── auth.js │ │ ├── index.js │ │ ├── login.js │ │ ├── session.js │ │ └── urlnormalize.js │ ├── models │ │ ├── config │ │ │ └── index.js │ │ ├── index.js │ │ ├── migrate │ │ │ ├── 20200725045100-create-shop.js │ │ │ └── 20200727025727-create-session.js │ │ ├── seed │ │ │ └── 20200725050230-first-shop.js │ │ └── shop.js │ ├── moulds │ │ ├── ShopForm.js │ │ └── yup.js │ ├── server.js │ ├── services │ │ └── shop.js │ └── utils │ │ └── cc.js └── yarn.lock ├── 08-security ├── .dockerignore ├── .npmrc ├── .sequelizerc ├── Dockerfile ├── database │ └── .gitkeep ├── package.json ├── public │ ├── 500.html │ ├── glue.js │ ├── index.css │ ├── index.html │ ├── index.js │ └── login.html ├── src │ ├── controllers │ │ ├── chaos.js │ │ ├── csrf.js │ │ ├── health.js │ │ ├── index.js │ │ ├── login.js │ │ └── shop.js │ ├── middlewares │ │ ├── auth.js │ │ ├── index.js │ │ ├── login.js │ │ ├── session.js │ │ └── urlnormalize.js │ ├── models │ │ ├── config │ │ │ └── index.js │ │ ├── index.js │ │ ├── migrate │ │ │ ├── 20200725045100-create-shop.js │ │ │ └── 20200727025727-create-session.js │ │ ├── seed │ │ │ └── 20200725050230-first-shop.js │ │ └── shop.js │ ├── moulds │ │ ├── ShopForm.js │ │ └── yup.js │ ├── server.js │ ├── services │ │ └── shop.js │ └── utils │ │ ├── cc.js │ │ └── escape-html-in-object.js └── yarn.lock ├── 09-config ├── .dockerignore ├── .env.local ├── .env.production.local ├── .npmrc ├── .sequelizerc ├── Dockerfile ├── database │ └── .gitkeep ├── package.json ├── public │ ├── 500.html │ ├── glue.js │ ├── index.css │ ├── index.html │ ├── index.js │ └── login.html ├── scripts │ └── env.js ├── src │ ├── config │ │ └── index.js │ ├── controllers │ │ ├── chaos.js │ │ ├── csrf.js │ │ ├── health.js │ │ ├── index.js │ │ ├── login.js │ │ └── shop.js │ ├── middlewares │ │ ├── auth.js │ │ ├── index.js │ │ ├── login.js │ │ ├── session.js │ │ └── urlnormalize.js │ ├── models │ │ ├── config │ │ │ └── index.js │ │ ├── index.js │ │ ├── migrate │ │ │ ├── 20200725045100-create-shop.js │ │ │ └── 20200727025727-create-session.js │ │ ├── seed │ │ │ └── 20200725050230-first-shop.js │ │ └── shop.js │ ├── moulds │ │ ├── ShopForm.js │ │ └── yup.js │ ├── server.js │ ├── services │ │ └── shop.js │ └── utils │ │ ├── cc.js │ │ └── escape-html-in-object.js └── yarn.lock ├── 10-log ├── .dockerignore ├── .env ├── .env.local ├── .env.production.local ├── .npmrc ├── .sequelizerc ├── Dockerfile ├── database │ └── .gitkeep ├── package.json ├── public │ ├── 500.html │ ├── glue.js │ ├── index.css │ ├── index.html │ ├── index.js │ └── login.html ├── scripts │ └── env.js ├── src │ ├── config │ │ └── index.js │ ├── controllers │ │ ├── chaos.js │ │ ├── csrf.js │ │ ├── health.js │ │ ├── index.js │ │ ├── login.js │ │ └── shop.js │ ├── middlewares │ │ ├── auth.js │ │ ├── index.js │ │ ├── login.js │ │ ├── session.js │ │ ├── trace.js │ │ └── urlnormalize.js │ ├── models │ │ ├── config │ │ │ └── index.js │ │ ├── index.js │ │ ├── migrate │ │ │ ├── 20200725045100-create-shop.js │ │ │ └── 20200727025727-create-session.js │ │ ├── seed │ │ │ └── 20200725050230-first-shop.js │ │ └── shop.js │ ├── moulds │ │ ├── ShopForm.js │ │ └── yup.js │ ├── server.js │ ├── services │ │ └── shop.js │ └── utils │ │ ├── cc.js │ │ ├── escape-html-in-object.js │ │ └── logger.js └── yarn.lock ├── 11-schedule ├── .dockerignore ├── .env ├── .env.local ├── .env.production.local ├── .npmrc ├── .sequelizerc ├── Dockerfile ├── database │ └── .gitkeep ├── package.json ├── public │ ├── 500.html │ ├── glue.js │ ├── index.css │ ├── index.html │ ├── index.js │ └── login.html ├── scripts │ └── env.js ├── src │ ├── config │ │ └── index.js │ ├── controllers │ │ ├── chaos.js │ │ ├── csrf.js │ │ ├── health.js │ │ ├── index.js │ │ ├── login.js │ │ └── shop.js │ ├── middlewares │ │ ├── auth.js │ │ ├── index.js │ │ ├── login.js │ │ ├── session.js │ │ ├── trace.js │ │ └── urlnormalize.js │ ├── models │ │ ├── config │ │ │ └── index.js │ │ ├── index.js │ │ ├── migrate │ │ │ ├── 20200725045100-create-shop.js │ │ │ ├── 20200727025727-create-session.js │ │ │ └── 20200801120113-create-schedule-lock.js │ │ ├── scheduleLock.js │ │ ├── seed │ │ │ └── 20200725050230-first-shop.js │ │ └── shop.js │ ├── moulds │ │ ├── ShopForm.js │ │ └── yup.js │ ├── schedules │ │ ├── index.js │ │ └── inspectAttack.js │ ├── server.js │ ├── services │ │ ├── mail.js │ │ └── shop.js │ └── utils │ │ ├── cc.js │ │ ├── escape-html-in-object.js │ │ └── logger.js └── yarn.lock ├── 12-rpc ├── .dockerignore ├── .env ├── .env.local ├── .env.production.local ├── .npmrc ├── .sequelizerc ├── Dockerfile ├── database │ └── .gitkeep ├── package.json ├── public │ ├── 500.html │ ├── glue.js │ ├── index.css │ ├── index.html │ ├── index.js │ └── login.html ├── scripts │ └── env.js ├── src │ ├── config │ │ └── index.js │ ├── controllers │ │ ├── chaos.js │ │ ├── csrf.js │ │ ├── echo.js │ │ ├── health.js │ │ ├── index.js │ │ ├── login.js │ │ └── shop.js │ ├── middlewares │ │ ├── auth.js │ │ ├── index.js │ │ ├── login.js │ │ ├── session.js │ │ ├── trace.js │ │ └── urlnormalize.js │ ├── models │ │ ├── config │ │ │ └── index.js │ │ ├── index.js │ │ ├── migrate │ │ │ ├── 20200725045100-create-shop.js │ │ │ ├── 20200727025727-create-session.js │ │ │ └── 20200801120113-create-schedule-lock.js │ │ ├── scheduleLock.js │ │ ├── seed │ │ │ └── 20200725050230-first-shop.js │ │ └── shop.js │ ├── moulds │ │ ├── ShopForm.js │ │ └── yup.js │ ├── rpc │ │ ├── echo │ │ │ ├── client.js │ │ │ ├── def.proto │ │ │ └── server.js │ │ └── index.js │ ├── schedules │ │ ├── index.js │ │ └── inspectAttack.js │ ├── server.js │ ├── services │ │ ├── mail.js │ │ └── shop.js │ └── utils │ │ ├── cc.js │ │ ├── escape-html-in-object.js │ │ └── logger.js └── yarn.lock ├── 13-debugging-and-profiling ├── .dockerignore ├── .env ├── .env.local ├── .env.production.local ├── .npmrc ├── .sequelizerc ├── .vscode │ └── launch.json ├── Dockerfile ├── database │ └── .gitkeep ├── package.json ├── public │ ├── 500.html │ ├── glue.js │ ├── index.css │ ├── index.html │ ├── index.js │ └── login.html ├── scripts │ └── env.js ├── src │ ├── config │ │ └── index.js │ ├── controllers │ │ ├── chaos.js │ │ ├── csrf.js │ │ ├── echo.js │ │ ├── health.js │ │ ├── index.js │ │ ├── login.js │ │ └── shop.js │ ├── middlewares │ │ ├── auth.js │ │ ├── index.js │ │ ├── login.js │ │ ├── session.js │ │ ├── trace.js │ │ └── urlnormalize.js │ ├── models │ │ ├── config │ │ │ └── index.js │ │ ├── index.js │ │ ├── migrate │ │ │ ├── 20200725045100-create-shop.js │ │ │ ├── 20200727025727-create-session.js │ │ │ └── 20200801120113-create-schedule-lock.js │ │ ├── scheduleLock.js │ │ ├── seed │ │ │ └── 20200725050230-first-shop.js │ │ └── shop.js │ ├── moulds │ │ ├── ShopForm.js │ │ └── yup.js │ ├── rpc │ │ ├── echo │ │ │ ├── client.js │ │ │ ├── def.proto │ │ │ └── server.js │ │ └── index.js │ ├── schedules │ │ ├── index.js │ │ └── inspectAttack.js │ ├── server.js │ ├── services │ │ ├── mail.js │ │ └── shop.js │ └── utils │ │ ├── cc.js │ │ ├── escape-html-in-object.js │ │ └── logger.js └── yarn.lock ├── 14-testing ├── .dockerignore ├── .env ├── .env.local ├── .env.production.local ├── .npmrc ├── .sequelizerc ├── .vscode │ └── launch.json ├── Dockerfile ├── database │ └── .gitkeep ├── jest.config.js ├── package.json ├── public │ ├── 500.html │ ├── glue.js │ ├── index.css │ ├── index.html │ ├── index.js │ └── login.html ├── scripts │ └── env.js ├── src │ ├── config │ │ └── index.js │ ├── controllers │ │ ├── chaos.js │ │ ├── csrf.js │ │ ├── echo.js │ │ ├── health.js │ │ ├── index.js │ │ ├── login.js │ │ ├── shop.js │ │ └── shop.test.js │ ├── middlewares │ │ ├── auth.js │ │ ├── index.js │ │ ├── login.js │ │ ├── session.js │ │ ├── trace.js │ │ └── urlnormalize.js │ ├── models │ │ ├── config │ │ │ └── index.js │ │ ├── index.js │ │ ├── migrate │ │ │ ├── 20200725045100-create-shop.js │ │ │ ├── 20200727025727-create-session.js │ │ │ └── 20200801120113-create-schedule-lock.js │ │ ├── scheduleLock.js │ │ ├── seed │ │ │ └── 20200725050230-first-shop.js │ │ └── shop.js │ ├── moulds │ │ ├── ShopForm.js │ │ └── yup.js │ ├── rpc │ │ ├── echo │ │ │ ├── client.js │ │ │ ├── def.proto │ │ │ └── server.js │ │ └── index.js │ ├── schedules │ │ ├── index.js │ │ └── inspectAttack.js │ ├── server.js │ ├── services │ │ ├── mail.js │ │ └── shop.js │ └── utils │ │ ├── cc.js │ │ ├── escape-html-in-object.js │ │ ├── escape-html-in-object.perf.js │ │ ├── escape-html-in-object.test.js │ │ └── logger.js ├── tests │ └── globalSetup.js └── yarn.lock ├── LICENSE └── README.md /00-static/.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /00-static/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:12.18.2-slim 2 | 3 | WORKDIR /usr/app/00-static 4 | COPY . . 5 | RUN yarn 6 | 7 | EXPOSE 9000 8 | CMD yarn start 9 | -------------------------------------------------------------------------------- /00-static/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "00-static", 3 | "version": "1.0.0", 4 | "scripts": { 5 | "start": "node src/server.js" 6 | }, 7 | "license": "MIT", 8 | "dependencies": { 9 | "express": "^4.17.1" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /00-static/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |

It works!

7 | 8 | 9 | -------------------------------------------------------------------------------- /00-static/src/server.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const { resolve } = require('path'); 3 | const { promisify } = require('util'); 4 | 5 | const server = express(); 6 | const port = parseInt(process.env.PORT || '9000'); 7 | const publicDir = resolve('public'); 8 | 9 | async function bootstrap() { 10 | server.use(express.static(publicDir)); 11 | await promisify(server.listen.bind(server, port))(); 12 | console.log(`> Started on port ${port}`); 13 | } 14 | 15 | bootstrap(); 16 | -------------------------------------------------------------------------------- /01-api-and-layering/.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /01-api-and-layering/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:12.18.2-slim 2 | 3 | WORKDIR /usr/app/00-static 4 | COPY . . 5 | RUN yarn 6 | 7 | EXPOSE 9000 8 | CMD yarn start 9 | -------------------------------------------------------------------------------- /01-api-and-layering/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "01-api-and-layering", 3 | "version": "1.0.0", 4 | "scripts": { 5 | "start": "node src/server.js" 6 | }, 7 | "license": "MIT", 8 | "dependencies": { 9 | "express": "^4.17.1" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /01-api-and-layering/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |
7 | 8 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /01-api-and-layering/src/controllers/index.js: -------------------------------------------------------------------------------- 1 | const { Router } = require('express'); 2 | const shopController = require('./shop'); 3 | 4 | module.exports = async function initControllers() { 5 | const router = Router(); 6 | router.use('/api/shop', await shopController()); 7 | return router; 8 | }; 9 | -------------------------------------------------------------------------------- /01-api-and-layering/src/server.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const { resolve } = require('path'); 3 | const { promisify } = require('util'); 4 | const initControllers = require('./controllers'); 5 | 6 | const server = express(); 7 | const port = parseInt(process.env.PORT || '9000'); 8 | const publicDir = resolve('public'); 9 | 10 | async function bootstrap() { 11 | server.use(express.static(publicDir)); 12 | server.use(await initControllers()); 13 | await promisify(server.listen.bind(server, port))(); 14 | console.log(`> Started on port ${port}`); 15 | } 16 | 17 | bootstrap(); 18 | -------------------------------------------------------------------------------- /02-validate/.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /02-validate/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:12.18.2-slim 2 | 3 | WORKDIR /usr/app/00-static 4 | COPY . . 5 | RUN yarn 6 | 7 | EXPOSE 9000 8 | CMD yarn start 9 | -------------------------------------------------------------------------------- /02-validate/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "02-validate", 3 | "version": "1.0.0", 4 | "scripts": { 5 | "start": "node src/server.js", 6 | "build:yup": "rollup node_modules/yup -o src/moulds/yup.js -p @rollup/plugin-node-resolve,@rollup/plugin-commonjs,rollup-plugin-terser -f umd -n 'yup'" 7 | }, 8 | "license": "MIT", 9 | "dependencies": { 10 | "express": "^4.17.1", 11 | "yup": "^0.29.1" 12 | }, 13 | "devDependencies": { 14 | "@rollup/plugin-commonjs": "^14.0.0", 15 | "@rollup/plugin-node-resolve": "^8.4.0", 16 | "rollup": "^2.22.2", 17 | "rollup-plugin-terser": "^6.1.0" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /02-validate/public/glue.js: -------------------------------------------------------------------------------- 1 | import './moulds/yup.js'; 2 | 3 | window.require = (k) => window[k]; 4 | window.exports = window.moulds = {}; 5 | -------------------------------------------------------------------------------- /02-validate/public/index.css: -------------------------------------------------------------------------------- 1 | .error { 2 | color: red; 3 | font-size: 14px; 4 | } 5 | -------------------------------------------------------------------------------- /02-validate/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /02-validate/src/controllers/index.js: -------------------------------------------------------------------------------- 1 | const { Router } = require('express'); 2 | const shopController = require('./shop'); 3 | 4 | module.exports = async function initControllers() { 5 | const router = Router(); 6 | router.use('/api/shop', await shopController()); 7 | return router; 8 | }; 9 | -------------------------------------------------------------------------------- /02-validate/src/moulds/ShopForm.js: -------------------------------------------------------------------------------- 1 | const Yup = require('yup'); 2 | 3 | exports.createShopFormSchema = () => 4 | Yup.object({ 5 | name: Yup.string() 6 | .required('店铺名不能为空') 7 | .min(3, '店铺名至少 3 个字符') 8 | .max(20, '店铺名不可超过 20 字'), 9 | }); 10 | -------------------------------------------------------------------------------- /02-validate/src/server.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const { resolve } = require('path'); 3 | const { promisify } = require('util'); 4 | const initControllers = require('./controllers'); 5 | 6 | const server = express(); 7 | const port = parseInt(process.env.PORT || '9000'); 8 | const publicDir = resolve('public'); 9 | const mouldsDir = resolve('src/moulds'); 10 | 11 | async function bootstrap() { 12 | server.use(express.static(publicDir)); 13 | server.use('/moulds', express.static(mouldsDir)); 14 | server.use(await initControllers()); 15 | await promisify(server.listen.bind(server, port))(); 16 | console.log(`> Started on port ${port}`); 17 | } 18 | 19 | bootstrap(); 20 | -------------------------------------------------------------------------------- /03-middleware/.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /03-middleware/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:12.18.2-slim 2 | 3 | WORKDIR /usr/app/00-static 4 | COPY . . 5 | RUN yarn 6 | 7 | EXPOSE 9000 8 | CMD yarn start 9 | -------------------------------------------------------------------------------- /03-middleware/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "03-middleware", 3 | "version": "1.0.0", 4 | "scripts": { 5 | "start": "node src/server.js", 6 | "build:yup": "rollup node_modules/yup -o src/moulds/yup.js -p @rollup/plugin-node-resolve,@rollup/plugin-commonjs,rollup-plugin-terser -f umd -n 'yup'" 7 | }, 8 | "license": "MIT", 9 | "dependencies": { 10 | "body-parser": "^1.19.0", 11 | "express": "^4.17.1", 12 | "yup": "^0.29.1" 13 | }, 14 | "devDependencies": { 15 | "@rollup/plugin-commonjs": "^14.0.0", 16 | "@rollup/plugin-node-resolve": "^8.4.0", 17 | "rollup": "^2.22.2", 18 | "rollup-plugin-terser": "^6.1.0" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /03-middleware/public/glue.js: -------------------------------------------------------------------------------- 1 | import './moulds/yup.js'; 2 | 3 | window.require = (k) => window[k]; 4 | window.exports = window.moulds = {}; 5 | -------------------------------------------------------------------------------- /03-middleware/public/index.css: -------------------------------------------------------------------------------- 1 | .error { 2 | color: red; 3 | font-size: 14px; 4 | } 5 | -------------------------------------------------------------------------------- /03-middleware/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /03-middleware/src/controllers/index.js: -------------------------------------------------------------------------------- 1 | const { Router } = require('express'); 2 | const shopController = require('./shop'); 3 | 4 | module.exports = async function initControllers() { 5 | const router = Router(); 6 | router.use('/api/shop', await shopController()); 7 | return router; 8 | }; 9 | -------------------------------------------------------------------------------- /03-middleware/src/middlewares/index.js: -------------------------------------------------------------------------------- 1 | const { Router } = require('express'); 2 | const urlnormalizeMiddleware = require('./urlnormalize'); 3 | 4 | module.exports = async function initMiddlewares() { 5 | const router = Router(); 6 | router.use(urlnormalizeMiddleware()); 7 | return router; 8 | }; 9 | -------------------------------------------------------------------------------- /03-middleware/src/middlewares/urlnormalize.js: -------------------------------------------------------------------------------- 1 | const { normalize } = require('path'); 2 | const { parse, format } = require('url'); 3 | 4 | module.exports = function urlnormalizeMiddleware() { 5 | return (req, res, next) => { 6 | // 解决windows、Linux系统使用normalize路径分隔符不一致的问题 7 | const pathname = normalize(req.path).split('\\').join('/'); 8 | const urlParsed = parse(req.url); 9 | 10 | let shouldRedirect = false; 11 | 12 | // 重定向不规范的路径 13 | if (req.path != pathname) { 14 | urlParsed.pathname = pathname; 15 | shouldRedirect = true; 16 | } 17 | 18 | // 执行重定向或者略过 19 | if (shouldRedirect) { 20 | res.redirect(format(urlParsed)); 21 | } else { 22 | next(); 23 | } 24 | }; 25 | }; 26 | -------------------------------------------------------------------------------- /03-middleware/src/moulds/ShopForm.js: -------------------------------------------------------------------------------- 1 | const Yup = require('yup'); 2 | 3 | exports.createShopFormSchema = () => 4 | Yup.object({ 5 | name: Yup.string() 6 | .required('店铺名不能为空') 7 | .min(3, '店铺名至少 3 个字符') 8 | .max(20, '店铺名不可超过 20 字'), 9 | }); 10 | -------------------------------------------------------------------------------- /03-middleware/src/server.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const { resolve } = require('path'); 3 | const { promisify } = require('util'); 4 | const initMiddlewares = require('./middlewares'); 5 | const initControllers = require('./controllers'); 6 | 7 | const server = express(); 8 | const port = parseInt(process.env.PORT || '9000'); 9 | const publicDir = resolve('public'); 10 | const mouldsDir = resolve('src/moulds'); 11 | 12 | async function bootstrap() { 13 | server.use(express.static(publicDir)); 14 | server.use('/moulds', express.static(mouldsDir)); 15 | server.use(await initMiddlewares()); 16 | server.use(await initControllers()); 17 | await promisify(server.listen.bind(server, port))(); 18 | console.log(`> Started on port ${port}`); 19 | } 20 | 21 | bootstrap(); 22 | -------------------------------------------------------------------------------- /04-exception/.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /04-exception/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:12.18.2-slim 2 | 3 | WORKDIR /usr/app/00-static 4 | COPY . . 5 | RUN yarn 6 | 7 | EXPOSE 9000 8 | CMD yarn start 9 | -------------------------------------------------------------------------------- /04-exception/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "04-exception", 3 | "version": "1.0.0", 4 | "scripts": { 5 | "start": "node src/server.js", 6 | "build:yup": "rollup node_modules/yup -o src/moulds/yup.js -p @rollup/plugin-node-resolve,@rollup/plugin-commonjs,rollup-plugin-terser -f umd -n 'yup'" 7 | }, 8 | "license": "MIT", 9 | "dependencies": { 10 | "body-parser": "^1.19.0", 11 | "express": "^4.17.1", 12 | "yup": "^0.29.1" 13 | }, 14 | "devDependencies": { 15 | "@rollup/plugin-commonjs": "^14.0.0", 16 | "@rollup/plugin-node-resolve": "^8.4.0", 17 | "rollup": "^2.22.2", 18 | "rollup-plugin-terser": "^6.1.0" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /04-exception/public/500.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |

系统繁忙,请您稍后再试

7 | 返回首页 8 | 9 | 10 | -------------------------------------------------------------------------------- /04-exception/public/glue.js: -------------------------------------------------------------------------------- 1 | import './moulds/yup.js'; 2 | 3 | window.require = (k) => window[k]; 4 | window.exports = window.moulds = {}; 5 | -------------------------------------------------------------------------------- /04-exception/public/index.css: -------------------------------------------------------------------------------- 1 | .error { 2 | color: red; 3 | font-size: 14px; 4 | } 5 | -------------------------------------------------------------------------------- /04-exception/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /04-exception/src/controllers/health.js: -------------------------------------------------------------------------------- 1 | const { Router } = require('express'); 2 | 3 | class HealthController { 4 | async init() { 5 | const router = Router(); 6 | router.get('/', this.get); 7 | return router; 8 | } 9 | 10 | get = (req, res) => { 11 | res.send({}); 12 | }; 13 | } 14 | 15 | module.exports = async () => { 16 | const c = new HealthController(); 17 | return await c.init(); 18 | }; 19 | -------------------------------------------------------------------------------- /04-exception/src/controllers/index.js: -------------------------------------------------------------------------------- 1 | const { Router } = require('express'); 2 | const shopController = require('./shop'); 3 | const chaosController = require('./chaos'); 4 | const healthController = require('./health'); 5 | 6 | module.exports = async function initControllers() { 7 | const router = Router(); 8 | router.use('/api/shop', await shopController()); 9 | router.use('/api/chaos', await chaosController()); 10 | router.use('/api/health', await healthController()); 11 | return router; 12 | }; 13 | -------------------------------------------------------------------------------- /04-exception/src/middlewares/index.js: -------------------------------------------------------------------------------- 1 | const { Router } = require('express'); 2 | const urlnormalizeMiddleware = require('./urlnormalize'); 3 | 4 | module.exports = async function initMiddlewares() { 5 | const router = Router(); 6 | router.use(urlnormalizeMiddleware()); 7 | return router; 8 | }; 9 | -------------------------------------------------------------------------------- /04-exception/src/middlewares/urlnormalize.js: -------------------------------------------------------------------------------- 1 | const { normalize } = require('path'); 2 | const { parse, format } = require('url'); 3 | 4 | module.exports = function urlnormalizeMiddleware() { 5 | return (req, res, next) => { 6 | // 解决windows、Linux系统使用normalize路径分隔符不一致的问题 7 | const pathname = normalize(req.path).split('\\').join('/'); 8 | const urlParsed = parse(req.url); 9 | 10 | let shouldRedirect = false; 11 | 12 | // 重定向不规范的路径 13 | if (req.path != pathname) { 14 | urlParsed.pathname = pathname; 15 | shouldRedirect = true; 16 | } 17 | 18 | // 执行重定向或者略过 19 | if (shouldRedirect) { 20 | res.redirect(format(urlParsed)); 21 | } else { 22 | next(); 23 | } 24 | }; 25 | }; 26 | -------------------------------------------------------------------------------- /04-exception/src/moulds/ShopForm.js: -------------------------------------------------------------------------------- 1 | const Yup = require('yup'); 2 | 3 | exports.createShopFormSchema = () => 4 | Yup.object({ 5 | name: Yup.string() 6 | .required('店铺名不能为空') 7 | .min(3, '店铺名至少 3 个字符') 8 | .max(20, '店铺名不可超过 20 字'), 9 | }); 10 | -------------------------------------------------------------------------------- /04-exception/src/utils/cc.js: -------------------------------------------------------------------------------- 1 | module.exports = function callbackCatch(callback) { 2 | return async (req, res, next) => { 3 | try { 4 | await callback(req, res, next); 5 | } catch (e) { 6 | next(e); 7 | } 8 | }; 9 | }; 10 | -------------------------------------------------------------------------------- /05-database/.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /05-database/.npmrc: -------------------------------------------------------------------------------- 1 | registry=http://r.cnpmjs.org/ 2 | node_sqlite3_binary_host_mirror=http://npm.taobao.org/mirrors/sqlite3/ 3 | -------------------------------------------------------------------------------- /05-database/.sequelizerc: -------------------------------------------------------------------------------- 1 | const { resolve } = require('path'); 2 | 3 | const modelsDir = resolve('src/models'); 4 | 5 | module.exports = { 6 | config: `${modelsDir}/config`, 7 | 'migrations-path': `${modelsDir}/migrate`, 8 | 'seeders-path': `${modelsDir}/seed`, 9 | 'models-path': modelsDir, 10 | }; 11 | -------------------------------------------------------------------------------- /05-database/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:12.18.2 2 | 3 | WORKDIR /usr/app/05-database 4 | COPY . . 5 | RUN yarn 6 | 7 | EXPOSE 9000 8 | CMD yarn start 9 | -------------------------------------------------------------------------------- /05-database/database/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/licg9999/nodejs-server-examples/a9a3acd1f922888fa5f95175d50ed9cfac774e82/05-database/database/.gitkeep -------------------------------------------------------------------------------- /05-database/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "05-database", 3 | "version": "1.0.0", 4 | "scripts": { 5 | "start": "node src/server.js", 6 | "build:yup": "rollup node_modules/yup -o src/moulds/yup.js -p @rollup/plugin-node-resolve,@rollup/plugin-commonjs,rollup-plugin-terser -f umd -n 'yup'" 7 | }, 8 | "license": "MIT", 9 | "dependencies": { 10 | "body-parser": "^1.19.0", 11 | "express": "^4.17.1", 12 | "sequelize": "^5.22.3", 13 | "sqlite3": "^5.0.0", 14 | "yup": "^0.29.1" 15 | }, 16 | "devDependencies": { 17 | "@rollup/plugin-commonjs": "^14.0.0", 18 | "@rollup/plugin-node-resolve": "^8.4.0", 19 | "rollup": "^2.22.2", 20 | "rollup-plugin-terser": "^6.1.0", 21 | "sequelize-cli": "^6.2.0" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /05-database/public/500.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |

系统繁忙,请您稍后再试

7 | 返回首页 8 | 9 | 10 | -------------------------------------------------------------------------------- /05-database/public/glue.js: -------------------------------------------------------------------------------- 1 | import './moulds/yup.js'; 2 | 3 | window.require = (k) => window[k]; 4 | window.exports = window.moulds = {}; 5 | -------------------------------------------------------------------------------- /05-database/public/index.css: -------------------------------------------------------------------------------- 1 | .error { 2 | color: red; 3 | font-size: 14px; 4 | } 5 | -------------------------------------------------------------------------------- /05-database/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /05-database/src/controllers/health.js: -------------------------------------------------------------------------------- 1 | const { Router } = require('express'); 2 | 3 | class HealthController { 4 | async init() { 5 | const router = Router(); 6 | router.get('/', this.get); 7 | return router; 8 | } 9 | 10 | get = (req, res) => { 11 | res.send({}); 12 | }; 13 | } 14 | 15 | module.exports = async () => { 16 | const c = new HealthController(); 17 | return await c.init(); 18 | }; 19 | -------------------------------------------------------------------------------- /05-database/src/controllers/index.js: -------------------------------------------------------------------------------- 1 | const { Router } = require('express'); 2 | const shopController = require('./shop'); 3 | const chaosController = require('./chaos'); 4 | const healthController = require('./health'); 5 | 6 | module.exports = async function initControllers() { 7 | const router = Router(); 8 | router.use('/api/shop', await shopController()); 9 | router.use('/api/chaos', await chaosController()); 10 | router.use('/api/health', await healthController()); 11 | return router; 12 | }; 13 | -------------------------------------------------------------------------------- /05-database/src/middlewares/index.js: -------------------------------------------------------------------------------- 1 | const { Router } = require('express'); 2 | const urlnormalizeMiddleware = require('./urlnormalize'); 3 | 4 | module.exports = async function initMiddlewares() { 5 | const router = Router(); 6 | router.use(urlnormalizeMiddleware()); 7 | return router; 8 | }; 9 | -------------------------------------------------------------------------------- /05-database/src/middlewares/urlnormalize.js: -------------------------------------------------------------------------------- 1 | const { normalize } = require('path'); 2 | const { parse, format } = require('url'); 3 | 4 | module.exports = function urlnormalizeMiddleware() { 5 | return (req, res, next) => { 6 | // 解决windows、Linux系统使用normalize路径分隔符不一致的问题 7 | const pathname = normalize(req.path).split('\\').join('/'); 8 | const urlParsed = parse(req.url); 9 | 10 | let shouldRedirect = false; 11 | 12 | // 重定向不规范的路径 13 | if (req.path != pathname) { 14 | urlParsed.pathname = pathname; 15 | shouldRedirect = true; 16 | } 17 | 18 | // 执行重定向或者略过 19 | if (shouldRedirect) { 20 | res.redirect(format(urlParsed)); 21 | } else { 22 | next(); 23 | } 24 | }; 25 | }; 26 | -------------------------------------------------------------------------------- /05-database/src/models/config/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | development: { 3 | dialect: 'sqlite', 4 | storage: 'database/index.db', 5 | define: { 6 | underscored: true, 7 | }, 8 | migrationStorageTableName: 'sequelize_meta', 9 | }, 10 | }; 11 | -------------------------------------------------------------------------------- /05-database/src/models/migrate/20200725045100-create-shop.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | up: async (queryInterface, Sequelize) => { 3 | await queryInterface.createTable('shop', { 4 | id: { 5 | allowNull: false, 6 | autoIncrement: true, 7 | primaryKey: true, 8 | type: Sequelize.INTEGER, 9 | }, 10 | name: { 11 | type: Sequelize.STRING, 12 | }, 13 | created_at: { 14 | allowNull: false, 15 | type: Sequelize.DATE, 16 | }, 17 | updated_at: { 18 | allowNull: false, 19 | type: Sequelize.DATE, 20 | }, 21 | }); 22 | }, 23 | down: async (queryInterface, Sequelize) => { 24 | await queryInterface.dropTable('shop'); 25 | }, 26 | }; 27 | -------------------------------------------------------------------------------- /05-database/src/models/seed/20200725050230-first-shop.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | up: async (queryInterface, Sequelize) => { 3 | await queryInterface.bulkInsert('shop', [ 4 | { name: '良品铺子', created_at: new Date(), updated_at: new Date() }, 5 | { name: '来伊份', created_at: new Date(), updated_at: new Date() }, 6 | { name: '三只松鼠', created_at: new Date(), updated_at: new Date() }, 7 | { name: '百草味', created_at: new Date(), updated_at: new Date() }, 8 | ]); 9 | }, 10 | 11 | down: async (queryInterface, Sequelize) => { 12 | await queryInterface.bulkDelete('shop', null, {}); 13 | }, 14 | }; 15 | -------------------------------------------------------------------------------- /05-database/src/models/shop.js: -------------------------------------------------------------------------------- 1 | const { Model } = require('sequelize'); 2 | 3 | module.exports = (sequelize, DataTypes) => { 4 | class Shop extends Model { 5 | /** 6 | * Helper method for defining associations. 7 | * This method is not a part of Sequelize lifecycle. 8 | * The `models/index` file will call this method automatically. 9 | */ 10 | static associate(models) { 11 | // define association here 12 | } 13 | } 14 | Shop.init( 15 | { 16 | name: DataTypes.STRING, 17 | }, 18 | { 19 | sequelize, 20 | modelName: 'Shop', 21 | tableName: 'shop', 22 | } 23 | ); 24 | return Shop; 25 | }; 26 | -------------------------------------------------------------------------------- /05-database/src/moulds/ShopForm.js: -------------------------------------------------------------------------------- 1 | const Yup = require('yup'); 2 | 3 | exports.createShopFormSchema = () => 4 | Yup.object({ 5 | name: Yup.string() 6 | .required('店铺名不能为空') 7 | .min(3, '店铺名至少 3 个字符') 8 | .max(20, '店铺名不可超过 20 字'), 9 | }); 10 | -------------------------------------------------------------------------------- /05-database/src/services/shop.js: -------------------------------------------------------------------------------- 1 | const { Shop } = require('../models'); 2 | 3 | class ShopService { 4 | async init() {} 5 | 6 | async find({ id, pageIndex = 0, pageSize = 10 }) { 7 | if (id) { 8 | return [await Shop.findByPk(id)]; 9 | } 10 | 11 | return await Shop.findAll({ 12 | offset: pageIndex * pageSize, 13 | limit: pageSize, 14 | }); 15 | } 16 | 17 | async modify({ id, values }) { 18 | const target = await Shop.findByPk(id); 19 | 20 | if (!target) { 21 | return null; 22 | } 23 | 24 | Object.assign(target, values); 25 | return await target.save(); 26 | } 27 | 28 | async remove({ id }) { 29 | const target = await Shop.findByPk(id); 30 | 31 | if (!target) { 32 | return false; 33 | } 34 | 35 | return target.destroy(); 36 | } 37 | 38 | async create({ values }) { 39 | return await Shop.create(values); 40 | } 41 | } 42 | 43 | // 单例模式 44 | let service; 45 | module.exports = async function () { 46 | if (!service) { 47 | service = new ShopService(); 48 | await service.init(); 49 | } 50 | return service; 51 | }; 52 | -------------------------------------------------------------------------------- /05-database/src/utils/cc.js: -------------------------------------------------------------------------------- 1 | module.exports = function callbackCatch(callback) { 2 | return async (req, res, next) => { 3 | try { 4 | await callback(req, res, next); 5 | } catch (e) { 6 | next(e); 7 | } 8 | }; 9 | }; 10 | -------------------------------------------------------------------------------- /06-session/.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /06-session/.npmrc: -------------------------------------------------------------------------------- 1 | registry=http://r.cnpmjs.org/ 2 | node_sqlite3_binary_host_mirror=http://npm.taobao.org/mirrors/sqlite3/ 3 | -------------------------------------------------------------------------------- /06-session/.sequelizerc: -------------------------------------------------------------------------------- 1 | const { resolve } = require('path'); 2 | 3 | const modelsDir = resolve('src/models'); 4 | 5 | module.exports = { 6 | config: `${modelsDir}/config`, 7 | 'migrations-path': `${modelsDir}/migrate`, 8 | 'seeders-path': `${modelsDir}/seed`, 9 | 'models-path': modelsDir, 10 | }; 11 | -------------------------------------------------------------------------------- /06-session/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:12.18.2 2 | 3 | WORKDIR /usr/app/05-database 4 | COPY . . 5 | RUN yarn 6 | 7 | EXPOSE 9000 8 | CMD yarn start 9 | -------------------------------------------------------------------------------- /06-session/database/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/licg9999/nodejs-server-examples/a9a3acd1f922888fa5f95175d50ed9cfac774e82/06-session/database/.gitkeep -------------------------------------------------------------------------------- /06-session/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "05-database", 3 | "version": "1.0.0", 4 | "scripts": { 5 | "start": "node src/server.js", 6 | "build:yup": "rollup node_modules/yup -o src/moulds/yup.js -p @rollup/plugin-node-resolve,@rollup/plugin-commonjs,rollup-plugin-terser -f umd -n 'yup'" 7 | }, 8 | "license": "MIT", 9 | "dependencies": { 10 | "body-parser": "^1.19.0", 11 | "connect-session-sequelize": "^6.1.1", 12 | "cookie-parser": "^1.4.5", 13 | "express": "^4.17.1", 14 | "express-session": "^1.17.1", 15 | "sequelize": "^5.22.3", 16 | "sqlite3": "^5.0.0", 17 | "yup": "^0.29.1" 18 | }, 19 | "devDependencies": { 20 | "@rollup/plugin-commonjs": "^14.0.0", 21 | "@rollup/plugin-node-resolve": "^8.4.0", 22 | "rollup": "^2.22.2", 23 | "rollup-plugin-terser": "^6.1.0", 24 | "sequelize-cli": "^6.2.0" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /06-session/public/500.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |

系统繁忙,请您稍后再试

7 | 返回首页 8 | 9 | 10 | -------------------------------------------------------------------------------- /06-session/public/glue.js: -------------------------------------------------------------------------------- 1 | import './moulds/yup.js'; 2 | 3 | window.require = (k) => window[k]; 4 | window.exports = window.moulds = {}; 5 | -------------------------------------------------------------------------------- /06-session/public/index.css: -------------------------------------------------------------------------------- 1 | .error { 2 | color: red; 3 | font-size: 14px; 4 | } 5 | -------------------------------------------------------------------------------- /06-session/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /06-session/public/login.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |
7 | 8 |
9 | 10 | 11 | -------------------------------------------------------------------------------- /06-session/src/controllers/health.js: -------------------------------------------------------------------------------- 1 | const { Router } = require('express'); 2 | 3 | class HealthController { 4 | async init() { 5 | const router = Router(); 6 | router.get('/', this.get); 7 | return router; 8 | } 9 | 10 | get = (req, res) => { 11 | res.send({}); 12 | }; 13 | } 14 | 15 | module.exports = async () => { 16 | const c = new HealthController(); 17 | return await c.init(); 18 | }; 19 | -------------------------------------------------------------------------------- /06-session/src/controllers/index.js: -------------------------------------------------------------------------------- 1 | const { Router } = require('express'); 2 | const shopController = require('./shop'); 3 | const chaosController = require('./chaos'); 4 | const healthController = require('./health'); 5 | const loginController = require('./login'); 6 | 7 | module.exports = async function initControllers() { 8 | const router = Router(); 9 | router.use('/api/shop', await shopController()); 10 | router.use('/api/chaos', await chaosController()); 11 | router.use('/api/health', await healthController()); 12 | router.use('/api/login', await loginController()); 13 | return router; 14 | }; 15 | -------------------------------------------------------------------------------- /06-session/src/controllers/login.js: -------------------------------------------------------------------------------- 1 | const { Router } = require('express'); 2 | 3 | class LoginController { 4 | async init() { 5 | const router = Router(); 6 | router.post('/', this.post); 7 | return router; 8 | } 9 | 10 | post = (req, res) => { 11 | req.session.logined = true; 12 | res.redirect('/'); 13 | }; 14 | } 15 | 16 | module.exports = async () => { 17 | const c = new LoginController(); 18 | return await c.init(); 19 | }; 20 | -------------------------------------------------------------------------------- /06-session/src/middlewares/index.js: -------------------------------------------------------------------------------- 1 | const { Router } = require('express'); 2 | const cookieParser = require('cookie-parser'); 3 | const sessionMiddleware = require('./session'); 4 | const urlnormalizeMiddleware = require('./urlnormalize'); 5 | const loginMiddleware = require('./login'); 6 | 7 | const secret = '842d918ced1888c65a650f993077c3d36b8f114d'; 8 | 9 | module.exports = async function initMiddlewares() { 10 | const router = Router(); 11 | router.use(urlnormalizeMiddleware()); 12 | router.use(cookieParser(secret)); 13 | router.use(sessionMiddleware(secret)); 14 | router.use(loginMiddleware()); 15 | return router; 16 | }; 17 | -------------------------------------------------------------------------------- /06-session/src/middlewares/login.js: -------------------------------------------------------------------------------- 1 | const { parse } = require('url'); 2 | 3 | module.exports = function loginMiddleware( 4 | homepagePath = '/', 5 | loginPath = '/login.html', 6 | whiteList = { 7 | '/500.html': ['get'], 8 | '/api/health': ['get'], 9 | '/api/login': ['post'], 10 | } 11 | ) { 12 | whiteList[loginPath] = ['get']; 13 | 14 | return (req, res, next) => { 15 | const { pathname } = parse(req.url); 16 | 17 | if (req.session.logined && pathname == loginPath) { 18 | res.redirect(homepagePath); 19 | return; 20 | } 21 | 22 | if ( 23 | req.session.logined || 24 | (whiteList[pathname] && 25 | whiteList[pathname].includes(req.method.toLowerCase())) 26 | ) { 27 | next(); 28 | return; 29 | } 30 | 31 | res.redirect(loginPath); 32 | }; 33 | }; 34 | -------------------------------------------------------------------------------- /06-session/src/middlewares/session.js: -------------------------------------------------------------------------------- 1 | const session = require('express-session'); 2 | const sessionSequelize = require('connect-session-sequelize'); 3 | const { sequelize } = require('../models'); 4 | 5 | module.exports = function sessionMiddleware(secret) { 6 | const SequelizeStore = sessionSequelize(session.Store); 7 | 8 | const store = new SequelizeStore({ 9 | db: sequelize, 10 | modelKey: 'Session', 11 | tableName: 'session', 12 | }); 13 | 14 | return session({ 15 | secret, 16 | cookie: { maxAge: 7 * 24 * 60 * 60 * 1000 }, 17 | store, 18 | resave: false, 19 | proxy: true, 20 | saveUninitialized: false, 21 | }); 22 | }; 23 | -------------------------------------------------------------------------------- /06-session/src/middlewares/urlnormalize.js: -------------------------------------------------------------------------------- 1 | const { normalize } = require('path'); 2 | const { parse, format } = require('url'); 3 | 4 | module.exports = function urlnormalizeMiddleware() { 5 | return (req, res, next) => { 6 | // 解决windows、Linux系统使用normalize路径分隔符不一致的问题 7 | const pathname = normalize(req.path).split('\\').join('/'); 8 | const urlParsed = parse(req.url); 9 | 10 | let shouldRedirect = false; 11 | 12 | // 重定向不规范的路径 13 | if (req.path != pathname) { 14 | urlParsed.pathname = pathname; 15 | shouldRedirect = true; 16 | } 17 | 18 | // 执行重定向或者略过 19 | if (shouldRedirect) { 20 | res.redirect(format(urlParsed)); 21 | } else { 22 | next(); 23 | } 24 | }; 25 | }; 26 | -------------------------------------------------------------------------------- /06-session/src/models/config/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | development: { 3 | dialect: 'sqlite', 4 | storage: 'database/index.db', 5 | define: { 6 | underscored: true, 7 | }, 8 | migrationStorageTableName: 'sequelize_meta', 9 | }, 10 | }; 11 | -------------------------------------------------------------------------------- /06-session/src/models/migrate/20200725045100-create-shop.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | up: async (queryInterface, Sequelize) => { 3 | await queryInterface.createTable('shop', { 4 | id: { 5 | allowNull: false, 6 | autoIncrement: true, 7 | primaryKey: true, 8 | type: Sequelize.INTEGER, 9 | }, 10 | name: { 11 | type: Sequelize.STRING, 12 | }, 13 | created_at: { 14 | allowNull: false, 15 | type: Sequelize.DATE, 16 | }, 17 | updated_at: { 18 | allowNull: false, 19 | type: Sequelize.DATE, 20 | }, 21 | }); 22 | }, 23 | down: async (queryInterface, Sequelize) => { 24 | await queryInterface.dropTable('shop'); 25 | }, 26 | }; 27 | -------------------------------------------------------------------------------- /06-session/src/models/migrate/20200727025727-create-session.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | up: async (queryInterface, Sequelize) => { 3 | await queryInterface.createTable('session', { 4 | sid: { 5 | type: Sequelize.STRING(36), 6 | }, 7 | expires: { 8 | type: Sequelize.DATE, 9 | }, 10 | data: { 11 | type: Sequelize.TEXT, 12 | }, 13 | 14 | created_at: { 15 | allowNull: false, 16 | type: Sequelize.DATE, 17 | }, 18 | updated_at: { 19 | allowNull: false, 20 | type: Sequelize.DATE, 21 | }, 22 | }); 23 | }, 24 | 25 | down: async (queryInterface, Sequelize) => { 26 | queryInterface.dropTable('session'); 27 | }, 28 | }; 29 | -------------------------------------------------------------------------------- /06-session/src/models/seed/20200725050230-first-shop.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | up: async (queryInterface, Sequelize) => { 3 | await queryInterface.bulkInsert('shop', [ 4 | { name: '良品铺子', created_at: new Date(), updated_at: new Date() }, 5 | { name: '来伊份', created_at: new Date(), updated_at: new Date() }, 6 | { name: '三只松鼠', created_at: new Date(), updated_at: new Date() }, 7 | { name: '百草味', created_at: new Date(), updated_at: new Date() }, 8 | ]); 9 | }, 10 | 11 | down: async (queryInterface, Sequelize) => { 12 | await queryInterface.bulkDelete('shop', null, {}); 13 | }, 14 | }; 15 | -------------------------------------------------------------------------------- /06-session/src/models/shop.js: -------------------------------------------------------------------------------- 1 | const { Model } = require('sequelize'); 2 | 3 | module.exports = (sequelize, DataTypes) => { 4 | class Shop extends Model { 5 | /** 6 | * Helper method for defining associations. 7 | * This method is not a part of Sequelize lifecycle. 8 | * The `models/index` file will call this method automatically. 9 | */ 10 | static associate(models) { 11 | // define association here 12 | } 13 | } 14 | Shop.init( 15 | { 16 | name: DataTypes.STRING, 17 | }, 18 | { 19 | sequelize, 20 | modelName: 'Shop', 21 | tableName: 'shop', 22 | } 23 | ); 24 | return Shop; 25 | }; 26 | -------------------------------------------------------------------------------- /06-session/src/moulds/ShopForm.js: -------------------------------------------------------------------------------- 1 | const Yup = require('yup'); 2 | 3 | exports.createShopFormSchema = () => 4 | Yup.object({ 5 | name: Yup.string() 6 | .required('店铺名不能为空') 7 | .min(3, '店铺名至少 3 个字符') 8 | .max(20, '店铺名不可超过 20 字'), 9 | }); 10 | -------------------------------------------------------------------------------- /06-session/src/services/shop.js: -------------------------------------------------------------------------------- 1 | const { Shop } = require('../models'); 2 | 3 | class ShopService { 4 | async init() {} 5 | 6 | async find({ id, pageIndex = 0, pageSize = 10 }) { 7 | if (id) { 8 | return [await Shop.findByPk(id)]; 9 | } 10 | 11 | return await Shop.findAll({ 12 | offset: pageIndex * pageSize, 13 | limit: pageSize, 14 | }); 15 | } 16 | 17 | async modify({ id, values }) { 18 | const target = await Shop.findByPk(id); 19 | 20 | if (!target) { 21 | return null; 22 | } 23 | 24 | Object.assign(target, values); 25 | return await target.save(); 26 | } 27 | 28 | async remove({ id }) { 29 | const target = await Shop.findByPk(id); 30 | 31 | if (!target) { 32 | return false; 33 | } 34 | 35 | return target.destroy(); 36 | } 37 | 38 | async create({ values }) { 39 | return await Shop.create(values); 40 | } 41 | } 42 | 43 | // 单例模式 44 | let service; 45 | module.exports = async function () { 46 | if (!service) { 47 | service = new ShopService(); 48 | await service.init(); 49 | } 50 | return service; 51 | }; 52 | -------------------------------------------------------------------------------- /06-session/src/utils/cc.js: -------------------------------------------------------------------------------- 1 | module.exports = function callbackCatch(callback) { 2 | return async (req, res, next) => { 3 | try { 4 | await callback(req, res, next); 5 | } catch (e) { 6 | next(e); 7 | } 8 | }; 9 | }; 10 | -------------------------------------------------------------------------------- /07-authentication/.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /07-authentication/.npmrc: -------------------------------------------------------------------------------- 1 | registry=http://r.cnpmjs.org/ 2 | node_sqlite3_binary_host_mirror=http://npm.taobao.org/mirrors/sqlite3/ 3 | -------------------------------------------------------------------------------- /07-authentication/.sequelizerc: -------------------------------------------------------------------------------- 1 | const { resolve } = require('path'); 2 | 3 | const modelsDir = resolve('src/models'); 4 | 5 | module.exports = { 6 | config: `${modelsDir}/config`, 7 | 'migrations-path': `${modelsDir}/migrate`, 8 | 'seeders-path': `${modelsDir}/seed`, 9 | 'models-path': modelsDir, 10 | }; 11 | -------------------------------------------------------------------------------- /07-authentication/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:12.18.2 2 | 3 | WORKDIR /usr/app/05-database 4 | COPY . . 5 | RUN yarn 6 | 7 | EXPOSE 9000 8 | CMD yarn start 9 | -------------------------------------------------------------------------------- /07-authentication/database/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/licg9999/nodejs-server-examples/a9a3acd1f922888fa5f95175d50ed9cfac774e82/07-authentication/database/.gitkeep -------------------------------------------------------------------------------- /07-authentication/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "05-database", 3 | "version": "1.0.0", 4 | "scripts": { 5 | "start": "node src/server.js", 6 | "build:yup": "rollup node_modules/yup -o src/moulds/yup.js -p @rollup/plugin-node-resolve,@rollup/plugin-commonjs,rollup-plugin-terser -f umd -n 'yup'" 7 | }, 8 | "license": "MIT", 9 | "dependencies": { 10 | "body-parser": "^1.19.0", 11 | "connect-session-sequelize": "^6.1.1", 12 | "cookie-parser": "^1.4.5", 13 | "express": "^4.17.1", 14 | "express-session": "^1.17.1", 15 | "passport": "^0.4.1", 16 | "passport-github": "^1.1.0", 17 | "sequelize": "^5.22.3", 18 | "sqlite3": "^5.0.0", 19 | "yup": "^0.29.1" 20 | }, 21 | "devDependencies": { 22 | "@rollup/plugin-commonjs": "^14.0.0", 23 | "@rollup/plugin-node-resolve": "^8.4.0", 24 | "rollup": "^2.22.2", 25 | "rollup-plugin-terser": "^6.1.0", 26 | "sequelize-cli": "^6.2.0" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /07-authentication/public/500.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |

系统繁忙,请您稍后再试

7 | 返回首页 8 | 9 | 10 | -------------------------------------------------------------------------------- /07-authentication/public/glue.js: -------------------------------------------------------------------------------- 1 | import './moulds/yup.js'; 2 | 3 | window.require = (k) => window[k]; 4 | window.exports = window.moulds = {}; 5 | -------------------------------------------------------------------------------- /07-authentication/public/index.css: -------------------------------------------------------------------------------- 1 | .error { 2 | color: red; 3 | font-size: 14px; 4 | } 5 | -------------------------------------------------------------------------------- /07-authentication/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /07-authentication/public/login.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |
7 | 8 |
9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /07-authentication/src/controllers/health.js: -------------------------------------------------------------------------------- 1 | const { Router } = require('express'); 2 | 3 | class HealthController { 4 | async init() { 5 | const router = Router(); 6 | router.get('/', this.get); 7 | return router; 8 | } 9 | 10 | get = (req, res) => { 11 | res.send({}); 12 | }; 13 | } 14 | 15 | module.exports = async () => { 16 | const c = new HealthController(); 17 | return await c.init(); 18 | }; 19 | -------------------------------------------------------------------------------- /07-authentication/src/controllers/index.js: -------------------------------------------------------------------------------- 1 | const { Router } = require('express'); 2 | const shopController = require('./shop'); 3 | const chaosController = require('./chaos'); 4 | const healthController = require('./health'); 5 | const loginController = require('./login'); 6 | 7 | module.exports = async function initControllers() { 8 | const router = Router(); 9 | router.use('/api/shop', await shopController()); 10 | router.use('/api/chaos', await chaosController()); 11 | router.use('/api/health', await healthController()); 12 | router.use('/api/login', await loginController()); 13 | return router; 14 | }; 15 | -------------------------------------------------------------------------------- /07-authentication/src/controllers/login.js: -------------------------------------------------------------------------------- 1 | const { Router } = require('express'); 2 | const { passport } = require('../middlewares/auth'); 3 | 4 | class LoginController { 5 | homepagePath; 6 | loginPath; 7 | 8 | async init() { 9 | const router = Router(); 10 | router.post('/', this.post); 11 | router.get( 12 | '/github', 13 | passport.authenticate('github', { scope: ['read:user'] }) 14 | ); 15 | router.get( 16 | '/github/callback', 17 | passport.authenticate('github', { 18 | failureRedirect: this.loginPath, 19 | }), 20 | this.getGithubCallback 21 | ); 22 | return router; 23 | } 24 | 25 | post = (req, res) => { 26 | req.session.logined = true; 27 | res.redirect(this.homepagePath); 28 | }; 29 | 30 | getGithubCallback = (req, res) => { 31 | req.session.logined = true; 32 | res.redirect(this.homepagePath); 33 | }; 34 | } 35 | 36 | module.exports = async (homepagePath = '/', loginPath = '/login.html') => { 37 | const c = new LoginController(); 38 | Object.assign(c, { homepagePath, loginPath }); 39 | return await c.init(); 40 | }; 41 | -------------------------------------------------------------------------------- /07-authentication/src/middlewares/auth.js: -------------------------------------------------------------------------------- 1 | const passport = require('passport'); 2 | const { Strategy: GithubStrategy } = require('passport-github'); 3 | 4 | const GITHUB_STRATEGY_OPTIONS = { 5 | clientID: 'b8ada004c6d682426cfb', 6 | clientSecret: '0b13f2ab5651f33f879a535fc2b316c6c731a041', 7 | callbackURL: 'http://localhost:9000/api/login/github/callback', 8 | }; 9 | 10 | const githubStrategy = new GithubStrategy( 11 | GITHUB_STRATEGY_OPTIONS, 12 | (accessToken, refreshToken, profile, done) => { 13 | /** 14 | * 根据 profile 查找或新建 user 信息 15 | */ 16 | const user = {}; 17 | done(null, user); 18 | } 19 | ); 20 | 21 | passport.use(githubStrategy); 22 | 23 | passport.serializeUser((user, done) => { 24 | /** 25 | * 根据 user 信息获取 userId 26 | */ 27 | const userId = '46e5'; 28 | done(null, userId); 29 | }); 30 | 31 | passport.deserializeUser((userId, done) => { 32 | /** 33 | * 根据 userId 获取 user 信息 34 | */ 35 | const user = {}; 36 | done(null, user); 37 | }); 38 | 39 | module.exports = function authMiddleware() { 40 | return [passport.initialize(), passport.session()]; 41 | }; 42 | 43 | Object.assign(module.exports, { passport }); 44 | -------------------------------------------------------------------------------- /07-authentication/src/middlewares/index.js: -------------------------------------------------------------------------------- 1 | const { Router } = require('express'); 2 | const cookieParser = require('cookie-parser'); 3 | const sessionMiddleware = require('./session'); 4 | const urlnormalizeMiddleware = require('./urlnormalize'); 5 | const loginMiddleware = require('./login'); 6 | const authMiddleware = require('./auth'); 7 | 8 | const secret = '842d918ced1888c65a650f993077c3d36b8f114d'; 9 | 10 | module.exports = async function initMiddlewares() { 11 | const router = Router(); 12 | router.use(urlnormalizeMiddleware()); 13 | router.use(cookieParser(secret)); 14 | router.use(sessionMiddleware(secret)); 15 | router.use(loginMiddleware()); 16 | router.use(authMiddleware()); 17 | return router; 18 | }; 19 | -------------------------------------------------------------------------------- /07-authentication/src/middlewares/login.js: -------------------------------------------------------------------------------- 1 | const { parse } = require('url'); 2 | 3 | module.exports = function loginMiddleware( 4 | homepagePath = '/', 5 | loginPath = '/login.html', 6 | whiteList = { 7 | '/500.html': ['get'], 8 | '/api/health': ['get'], 9 | '/api/login': ['post'], 10 | '/api/login/github': ['get'], 11 | '/api/login/github/callback': ['get'], 12 | } 13 | ) { 14 | whiteList[loginPath] = ['get']; 15 | 16 | return (req, res, next) => { 17 | const { pathname } = parse(req.url); 18 | 19 | if (req.session.logined && pathname == loginPath) { 20 | res.redirect(homepagePath); 21 | return; 22 | } 23 | 24 | if ( 25 | req.session.logined || 26 | (whiteList[pathname] && 27 | whiteList[pathname].includes(req.method.toLowerCase())) 28 | ) { 29 | next(); 30 | return; 31 | } 32 | 33 | res.redirect(loginPath); 34 | }; 35 | }; 36 | -------------------------------------------------------------------------------- /07-authentication/src/middlewares/session.js: -------------------------------------------------------------------------------- 1 | const session = require('express-session'); 2 | const sessionSequelize = require('connect-session-sequelize'); 3 | const { sequelize } = require('../models'); 4 | 5 | module.exports = function sessionMiddleware(secret) { 6 | const SequelizeStore = sessionSequelize(session.Store); 7 | 8 | const store = new SequelizeStore({ 9 | db: sequelize, 10 | modelKey: 'Session', 11 | tableName: 'session', 12 | }); 13 | 14 | return session({ 15 | secret, 16 | cookie: { maxAge: 7 * 24 * 60 * 60 * 1000 }, 17 | store, 18 | resave: false, 19 | proxy: true, 20 | saveUninitialized: false, 21 | }); 22 | }; 23 | -------------------------------------------------------------------------------- /07-authentication/src/middlewares/urlnormalize.js: -------------------------------------------------------------------------------- 1 | const { normalize } = require('path'); 2 | const { parse, format } = require('url'); 3 | 4 | module.exports = function urlnormalizeMiddleware() { 5 | return (req, res, next) => { 6 | // 解决windows、Linux系统使用normalize路径分隔符不一致的问题 7 | const pathname = normalize(req.path).split('\\').join('/'); 8 | const urlParsed = parse(req.url); 9 | 10 | let shouldRedirect = false; 11 | 12 | // 重定向不规范的路径 13 | if (req.path != pathname) { 14 | urlParsed.pathname = pathname; 15 | shouldRedirect = true; 16 | } 17 | 18 | // 执行重定向或者略过 19 | if (shouldRedirect) { 20 | res.redirect(format(urlParsed)); 21 | } else { 22 | next(); 23 | } 24 | }; 25 | }; 26 | -------------------------------------------------------------------------------- /07-authentication/src/models/config/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | development: { 3 | dialect: 'sqlite', 4 | storage: 'database/index.db', 5 | define: { 6 | underscored: true, 7 | }, 8 | migrationStorageTableName: 'sequelize_meta', 9 | }, 10 | }; 11 | -------------------------------------------------------------------------------- /07-authentication/src/models/migrate/20200725045100-create-shop.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | up: async (queryInterface, Sequelize) => { 3 | await queryInterface.createTable('shop', { 4 | id: { 5 | allowNull: false, 6 | autoIncrement: true, 7 | primaryKey: true, 8 | type: Sequelize.INTEGER, 9 | }, 10 | name: { 11 | type: Sequelize.STRING, 12 | }, 13 | created_at: { 14 | allowNull: false, 15 | type: Sequelize.DATE, 16 | }, 17 | updated_at: { 18 | allowNull: false, 19 | type: Sequelize.DATE, 20 | }, 21 | }); 22 | }, 23 | down: async (queryInterface, Sequelize) => { 24 | await queryInterface.dropTable('shop'); 25 | }, 26 | }; 27 | -------------------------------------------------------------------------------- /07-authentication/src/models/migrate/20200727025727-create-session.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | up: async (queryInterface, Sequelize) => { 3 | await queryInterface.createTable('session', { 4 | sid: { 5 | type: Sequelize.STRING(36), 6 | }, 7 | expires: { 8 | type: Sequelize.DATE, 9 | }, 10 | data: { 11 | type: Sequelize.TEXT, 12 | }, 13 | 14 | created_at: { 15 | allowNull: false, 16 | type: Sequelize.DATE, 17 | }, 18 | updated_at: { 19 | allowNull: false, 20 | type: Sequelize.DATE, 21 | }, 22 | }); 23 | }, 24 | 25 | down: async (queryInterface, Sequelize) => { 26 | queryInterface.dropTable('session'); 27 | }, 28 | }; 29 | -------------------------------------------------------------------------------- /07-authentication/src/models/seed/20200725050230-first-shop.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | up: async (queryInterface, Sequelize) => { 3 | await queryInterface.bulkInsert('shop', [ 4 | { name: '良品铺子', created_at: new Date(), updated_at: new Date() }, 5 | { name: '来伊份', created_at: new Date(), updated_at: new Date() }, 6 | { name: '三只松鼠', created_at: new Date(), updated_at: new Date() }, 7 | { name: '百草味', created_at: new Date(), updated_at: new Date() }, 8 | ]); 9 | }, 10 | 11 | down: async (queryInterface, Sequelize) => { 12 | await queryInterface.bulkDelete('shop', null, {}); 13 | }, 14 | }; 15 | -------------------------------------------------------------------------------- /07-authentication/src/models/shop.js: -------------------------------------------------------------------------------- 1 | const { Model } = require('sequelize'); 2 | 3 | module.exports = (sequelize, DataTypes) => { 4 | class Shop extends Model { 5 | /** 6 | * Helper method for defining associations. 7 | * This method is not a part of Sequelize lifecycle. 8 | * The `models/index` file will call this method automatically. 9 | */ 10 | static associate(models) { 11 | // define association here 12 | } 13 | } 14 | Shop.init( 15 | { 16 | name: DataTypes.STRING, 17 | }, 18 | { 19 | sequelize, 20 | modelName: 'Shop', 21 | tableName: 'shop', 22 | } 23 | ); 24 | return Shop; 25 | }; 26 | -------------------------------------------------------------------------------- /07-authentication/src/moulds/ShopForm.js: -------------------------------------------------------------------------------- 1 | const Yup = require('yup'); 2 | 3 | exports.createShopFormSchema = () => 4 | Yup.object({ 5 | name: Yup.string() 6 | .required('店铺名不能为空') 7 | .min(3, '店铺名至少 3 个字符') 8 | .max(20, '店铺名不可超过 20 字'), 9 | }); 10 | -------------------------------------------------------------------------------- /07-authentication/src/services/shop.js: -------------------------------------------------------------------------------- 1 | const { Shop } = require('../models'); 2 | 3 | class ShopService { 4 | async init() {} 5 | 6 | async find({ id, pageIndex = 0, pageSize = 10 }) { 7 | if (id) { 8 | return [await Shop.findByPk(id)]; 9 | } 10 | 11 | return await Shop.findAll({ 12 | offset: pageIndex * pageSize, 13 | limit: pageSize, 14 | }); 15 | } 16 | 17 | async modify({ id, values }) { 18 | const target = await Shop.findByPk(id); 19 | 20 | if (!target) { 21 | return null; 22 | } 23 | 24 | Object.assign(target, values); 25 | return await target.save(); 26 | } 27 | 28 | async remove({ id }) { 29 | const target = await Shop.findByPk(id); 30 | 31 | if (!target) { 32 | return false; 33 | } 34 | 35 | return target.destroy(); 36 | } 37 | 38 | async create({ values }) { 39 | return await Shop.create(values); 40 | } 41 | } 42 | 43 | // 单例模式 44 | let service; 45 | module.exports = async function () { 46 | if (!service) { 47 | service = new ShopService(); 48 | await service.init(); 49 | } 50 | return service; 51 | }; 52 | -------------------------------------------------------------------------------- /07-authentication/src/utils/cc.js: -------------------------------------------------------------------------------- 1 | module.exports = function callbackCatch(callback) { 2 | return async (req, res, next) => { 3 | try { 4 | await callback(req, res, next); 5 | } catch (e) { 6 | next(e); 7 | } 8 | }; 9 | }; 10 | -------------------------------------------------------------------------------- /08-security/.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /08-security/.npmrc: -------------------------------------------------------------------------------- 1 | registry=http://r.cnpmjs.org/ 2 | node_sqlite3_binary_host_mirror=http://npm.taobao.org/mirrors/sqlite3/ 3 | -------------------------------------------------------------------------------- /08-security/.sequelizerc: -------------------------------------------------------------------------------- 1 | const { resolve } = require('path'); 2 | 3 | const modelsDir = resolve('src/models'); 4 | 5 | module.exports = { 6 | config: `${modelsDir}/config`, 7 | 'migrations-path': `${modelsDir}/migrate`, 8 | 'seeders-path': `${modelsDir}/seed`, 9 | 'models-path': modelsDir, 10 | }; 11 | -------------------------------------------------------------------------------- /08-security/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:12.18.2 2 | 3 | WORKDIR /usr/app/05-database 4 | COPY . . 5 | RUN yarn 6 | 7 | EXPOSE 9000 8 | CMD yarn start 9 | -------------------------------------------------------------------------------- /08-security/database/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/licg9999/nodejs-server-examples/a9a3acd1f922888fa5f95175d50ed9cfac774e82/08-security/database/.gitkeep -------------------------------------------------------------------------------- /08-security/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "05-database", 3 | "version": "1.0.0", 4 | "scripts": { 5 | "start": "node src/server.js", 6 | "build:yup": "rollup node_modules/yup -o src/moulds/yup.js -p @rollup/plugin-node-resolve,@rollup/plugin-commonjs,rollup-plugin-terser -f umd -n 'yup'" 7 | }, 8 | "license": "MIT", 9 | "dependencies": { 10 | "body-parser": "^1.19.0", 11 | "connect-session-sequelize": "^6.1.1", 12 | "cookie-parser": "^1.4.5", 13 | "csurf": "^1.11.0", 14 | "escape-html": "^1.0.3", 15 | "express": "^4.17.1", 16 | "express-session": "^1.17.1", 17 | "helmet": "^3.23.3", 18 | "passport": "^0.4.1", 19 | "passport-github": "^1.1.0", 20 | "sequelize": "^5.22.3", 21 | "sqlite3": "^5.0.0", 22 | "yup": "^0.29.1" 23 | }, 24 | "devDependencies": { 25 | "@rollup/plugin-commonjs": "^14.0.0", 26 | "@rollup/plugin-node-resolve": "^8.4.0", 27 | "rollup": "^2.22.2", 28 | "rollup-plugin-terser": "^6.1.0", 29 | "sequelize-cli": "^6.2.0" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /08-security/public/500.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |

系统繁忙,请您稍后再试

7 | 返回首页 8 | 9 | 10 | -------------------------------------------------------------------------------- /08-security/public/glue.js: -------------------------------------------------------------------------------- 1 | import './moulds/yup.js'; 2 | 3 | window.require = (k) => window[k]; 4 | window.exports = window.moulds = {}; 5 | -------------------------------------------------------------------------------- /08-security/public/index.css: -------------------------------------------------------------------------------- 1 | .error { 2 | color: red; 3 | font-size: 14px; 4 | } 5 | -------------------------------------------------------------------------------- /08-security/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /08-security/public/login.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 13 | 14 |
15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /08-security/src/controllers/csrf.js: -------------------------------------------------------------------------------- 1 | const { Router } = require('express'); 2 | 3 | class CsrfController { 4 | async init() { 5 | const router = Router(); 6 | router.get('/script', this.getScript); 7 | return router; 8 | } 9 | 10 | getScript = (req, res) => { 11 | res.type('js'); 12 | res.send(`window.__CSRF_TOKEN__='${req.csrfToken()}';`); 13 | }; 14 | } 15 | 16 | module.exports = async () => { 17 | const c = new CsrfController(); 18 | return await c.init(); 19 | }; 20 | -------------------------------------------------------------------------------- /08-security/src/controllers/health.js: -------------------------------------------------------------------------------- 1 | const { Router } = require('express'); 2 | 3 | class HealthController { 4 | async init() { 5 | const router = Router(); 6 | router.get('/', this.get); 7 | return router; 8 | } 9 | 10 | get = (req, res) => { 11 | res.send({}); 12 | }; 13 | } 14 | 15 | module.exports = async () => { 16 | const c = new HealthController(); 17 | return await c.init(); 18 | }; 19 | -------------------------------------------------------------------------------- /08-security/src/controllers/index.js: -------------------------------------------------------------------------------- 1 | const { Router } = require('express'); 2 | const shopController = require('./shop'); 3 | const chaosController = require('./chaos'); 4 | const healthController = require('./health'); 5 | const loginController = require('./login'); 6 | const csrfController = require('./csrf'); 7 | 8 | module.exports = async function initControllers() { 9 | const router = Router(); 10 | router.use('/api/shop', await shopController()); 11 | router.use('/api/chaos', await chaosController()); 12 | router.use('/api/health', await healthController()); 13 | router.use('/api/login', await loginController()); 14 | router.use('/api/csrf', await csrfController()); 15 | return router; 16 | }; 17 | -------------------------------------------------------------------------------- /08-security/src/controllers/login.js: -------------------------------------------------------------------------------- 1 | const { Router } = require('express'); 2 | const { passport } = require('../middlewares/auth'); 3 | 4 | class LoginController { 5 | homepagePath; 6 | loginPath; 7 | 8 | async init() { 9 | const router = Router(); 10 | router.post('/', this.post); 11 | router.get( 12 | '/github', 13 | passport.authenticate('github', { scope: ['read:user'] }) 14 | ); 15 | router.get( 16 | '/github/callback', 17 | passport.authenticate('github', { 18 | failureRedirect: this.loginPath, 19 | }), 20 | this.getGithubCallback 21 | ); 22 | return router; 23 | } 24 | 25 | post = (req, res) => { 26 | req.session.logined = true; 27 | res.redirect(this.homepagePath); 28 | }; 29 | 30 | getGithubCallback = (req, res) => { 31 | req.session.logined = true; 32 | res.redirect(this.homepagePath); 33 | }; 34 | } 35 | 36 | module.exports = async (homepagePath = '/', loginPath = '/login.html') => { 37 | const c = new LoginController(); 38 | Object.assign(c, { homepagePath, loginPath }); 39 | return await c.init(); 40 | }; 41 | -------------------------------------------------------------------------------- /08-security/src/middlewares/auth.js: -------------------------------------------------------------------------------- 1 | const passport = require('passport'); 2 | const { Strategy: GithubStrategy } = require('passport-github'); 3 | 4 | const GITHUB_STRATEGY_OPTIONS = { 5 | clientID: 'b8ada004c6d682426cfb', 6 | clientSecret: '0b13f2ab5651f33f879a535fc2b316c6c731a041', 7 | callbackURL: 'http://localhost:9000/api/login/github/callback', 8 | }; 9 | 10 | const githubStrategy = new GithubStrategy( 11 | GITHUB_STRATEGY_OPTIONS, 12 | (accessToken, refreshToken, profile, done) => { 13 | /** 14 | * 根据 profile 查找或新建 user 信息 15 | */ 16 | const user = {}; 17 | done(null, user); 18 | } 19 | ); 20 | 21 | passport.use(githubStrategy); 22 | 23 | passport.serializeUser((user, done) => { 24 | /** 25 | * 根据 user 信息获取 userId 26 | */ 27 | const userId = '46e5'; 28 | done(null, userId); 29 | }); 30 | 31 | passport.deserializeUser((userId, done) => { 32 | /** 33 | * 根据 userId 获取 user 信息 34 | */ 35 | const user = {}; 36 | done(null, user); 37 | }); 38 | 39 | module.exports = function authMiddleware() { 40 | return [passport.initialize(), passport.session()]; 41 | }; 42 | 43 | Object.assign(module.exports, { passport }); 44 | -------------------------------------------------------------------------------- /08-security/src/middlewares/index.js: -------------------------------------------------------------------------------- 1 | const { Router } = require('express'); 2 | const cookieParser = require('cookie-parser'); 3 | const bodyParser = require('body-parser'); 4 | const csurf = require('csurf'); 5 | const helmet = require('helmet'); 6 | const sessionMiddleware = require('./session'); 7 | const urlnormalizeMiddleware = require('./urlnormalize'); 8 | const loginMiddleware = require('./login'); 9 | const authMiddleware = require('./auth'); 10 | 11 | const secret = '842d918ced1888c65a650f993077c3d36b8f114d'; 12 | 13 | module.exports = async function initMiddlewares() { 14 | const router = Router(); 15 | router.use(helmet()); 16 | router.use(urlnormalizeMiddleware()); 17 | router.use(cookieParser(secret)); 18 | router.use(sessionMiddleware(secret)); 19 | router.use(loginMiddleware()); 20 | router.use(authMiddleware()); 21 | router.use(bodyParser.urlencoded({ extended: false }), csurf()); 22 | return router; 23 | }; 24 | -------------------------------------------------------------------------------- /08-security/src/middlewares/login.js: -------------------------------------------------------------------------------- 1 | const { parse } = require('url'); 2 | 3 | module.exports = function loginMiddleware( 4 | homepagePath = '/', 5 | loginPath = '/login.html', 6 | whiteList = { 7 | '/500.html': ['get'], 8 | '/api/health': ['get'], 9 | '/api/csrf/script': ['get'], 10 | '/api/login': ['post'], 11 | '/api/login/github': ['get'], 12 | '/api/login/github/callback': ['get'], 13 | } 14 | ) { 15 | whiteList[loginPath] = ['get']; 16 | 17 | return (req, res, next) => { 18 | const { pathname } = parse(req.url); 19 | 20 | if (req.session.logined && pathname == loginPath) { 21 | res.redirect(homepagePath); 22 | return; 23 | } 24 | 25 | if ( 26 | req.session.logined || 27 | (whiteList[pathname] && 28 | whiteList[pathname].includes(req.method.toLowerCase())) 29 | ) { 30 | next(); 31 | return; 32 | } 33 | 34 | res.redirect(loginPath); 35 | }; 36 | }; 37 | -------------------------------------------------------------------------------- /08-security/src/middlewares/session.js: -------------------------------------------------------------------------------- 1 | const session = require('express-session'); 2 | const sessionSequelize = require('connect-session-sequelize'); 3 | const { sequelize } = require('../models'); 4 | 5 | module.exports = function sessionMiddleware(secret) { 6 | const SequelizeStore = sessionSequelize(session.Store); 7 | 8 | const store = new SequelizeStore({ 9 | db: sequelize, 10 | modelKey: 'Session', 11 | tableName: 'session', 12 | }); 13 | 14 | return session({ 15 | secret, 16 | cookie: { maxAge: 7 * 24 * 60 * 60 * 1000 }, 17 | store, 18 | resave: false, 19 | proxy: true, 20 | saveUninitialized: false, 21 | }); 22 | }; 23 | -------------------------------------------------------------------------------- /08-security/src/middlewares/urlnormalize.js: -------------------------------------------------------------------------------- 1 | const { normalize } = require('path'); 2 | const { parse, format } = require('url'); 3 | 4 | module.exports = function urlnormalizeMiddleware() { 5 | return (req, res, next) => { 6 | // 解决windows、Linux系统使用normalize路径分隔符不一致的问题 7 | const pathname = normalize(req.path).split('\\').join('/'); 8 | const urlParsed = parse(req.url); 9 | 10 | let shouldRedirect = false; 11 | 12 | // 重定向不规范的路径 13 | if (req.path != pathname) { 14 | urlParsed.pathname = pathname; 15 | shouldRedirect = true; 16 | } 17 | 18 | // 执行重定向或者略过 19 | if (shouldRedirect) { 20 | res.redirect(format(urlParsed)); 21 | } else { 22 | next(); 23 | } 24 | }; 25 | }; 26 | -------------------------------------------------------------------------------- /08-security/src/models/config/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | development: { 3 | dialect: 'sqlite', 4 | storage: 'database/index.db', 5 | define: { 6 | underscored: true, 7 | }, 8 | migrationStorageTableName: 'sequelize_meta', 9 | }, 10 | }; 11 | -------------------------------------------------------------------------------- /08-security/src/models/migrate/20200725045100-create-shop.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | up: async (queryInterface, Sequelize) => { 3 | await queryInterface.createTable('shop', { 4 | id: { 5 | allowNull: false, 6 | autoIncrement: true, 7 | primaryKey: true, 8 | type: Sequelize.INTEGER, 9 | }, 10 | name: { 11 | type: Sequelize.STRING, 12 | }, 13 | created_at: { 14 | allowNull: false, 15 | type: Sequelize.DATE, 16 | }, 17 | updated_at: { 18 | allowNull: false, 19 | type: Sequelize.DATE, 20 | }, 21 | }); 22 | }, 23 | down: async (queryInterface, Sequelize) => { 24 | await queryInterface.dropTable('shop'); 25 | }, 26 | }; 27 | -------------------------------------------------------------------------------- /08-security/src/models/migrate/20200727025727-create-session.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | up: async (queryInterface, Sequelize) => { 3 | await queryInterface.createTable('session', { 4 | sid: { 5 | type: Sequelize.STRING(36), 6 | }, 7 | expires: { 8 | type: Sequelize.DATE, 9 | }, 10 | data: { 11 | type: Sequelize.TEXT, 12 | }, 13 | 14 | created_at: { 15 | allowNull: false, 16 | type: Sequelize.DATE, 17 | }, 18 | updated_at: { 19 | allowNull: false, 20 | type: Sequelize.DATE, 21 | }, 22 | }); 23 | }, 24 | 25 | down: async (queryInterface, Sequelize) => { 26 | queryInterface.dropTable('session'); 27 | }, 28 | }; 29 | -------------------------------------------------------------------------------- /08-security/src/models/seed/20200725050230-first-shop.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | up: async (queryInterface, Sequelize) => { 3 | await queryInterface.bulkInsert('shop', [ 4 | { name: '良品铺子', created_at: new Date(), updated_at: new Date() }, 5 | { name: '来伊份', created_at: new Date(), updated_at: new Date() }, 6 | { name: '三只松鼠', created_at: new Date(), updated_at: new Date() }, 7 | { name: '百草味', created_at: new Date(), updated_at: new Date() }, 8 | ]); 9 | }, 10 | 11 | down: async (queryInterface, Sequelize) => { 12 | await queryInterface.bulkDelete('shop', null, {}); 13 | }, 14 | }; 15 | -------------------------------------------------------------------------------- /08-security/src/models/shop.js: -------------------------------------------------------------------------------- 1 | const { Model } = require('sequelize'); 2 | 3 | module.exports = (sequelize, DataTypes) => { 4 | class Shop extends Model { 5 | /** 6 | * Helper method for defining associations. 7 | * This method is not a part of Sequelize lifecycle. 8 | * The `models/index` file will call this method automatically. 9 | */ 10 | static associate(models) { 11 | // define association here 12 | } 13 | } 14 | Shop.init( 15 | { 16 | name: DataTypes.STRING, 17 | }, 18 | { 19 | sequelize, 20 | modelName: 'Shop', 21 | tableName: 'shop', 22 | } 23 | ); 24 | return Shop; 25 | }; 26 | -------------------------------------------------------------------------------- /08-security/src/moulds/ShopForm.js: -------------------------------------------------------------------------------- 1 | const Yup = require('yup'); 2 | 3 | exports.createShopFormSchema = () => 4 | Yup.object({ 5 | name: Yup.string() 6 | .required('店铺名不能为空') 7 | .min(3, '店铺名至少 3 个字符') 8 | .max(120, '店铺名不可超过 120 字'), 9 | }); 10 | -------------------------------------------------------------------------------- /08-security/src/services/shop.js: -------------------------------------------------------------------------------- 1 | const { Shop } = require('../models'); 2 | 3 | class ShopService { 4 | async init() {} 5 | 6 | async find({ id, pageIndex = 0, pageSize = 10 }) { 7 | if (id) { 8 | return [await Shop.findByPk(id)]; 9 | } 10 | 11 | return await Shop.findAll({ 12 | offset: pageIndex * pageSize, 13 | limit: pageSize, 14 | }); 15 | } 16 | 17 | async modify({ id, values }) { 18 | const target = await Shop.findByPk(id); 19 | 20 | if (!target) { 21 | return null; 22 | } 23 | 24 | Object.assign(target, values); 25 | return await target.save(); 26 | } 27 | 28 | async remove({ id }) { 29 | const target = await Shop.findByPk(id); 30 | 31 | if (!target) { 32 | return false; 33 | } 34 | 35 | return target.destroy(); 36 | } 37 | 38 | async create({ values }) { 39 | return await Shop.create(values); 40 | } 41 | } 42 | 43 | // 单例模式 44 | let service; 45 | module.exports = async function () { 46 | if (!service) { 47 | service = new ShopService(); 48 | await service.init(); 49 | } 50 | return service; 51 | }; 52 | -------------------------------------------------------------------------------- /08-security/src/utils/cc.js: -------------------------------------------------------------------------------- 1 | module.exports = function callbackCatch(callback) { 2 | return async (req, res, next) => { 3 | try { 4 | await callback(req, res, next); 5 | } catch (e) { 6 | next(e); 7 | } 8 | }; 9 | }; 10 | -------------------------------------------------------------------------------- /08-security/src/utils/escape-html-in-object.js: -------------------------------------------------------------------------------- 1 | const escapeHtml = require('escape-html'); 2 | 3 | module.exports = function escapeHtmlInObject(input) { 4 | // 尝试将 ORM 对象转化为普通对象 5 | try { 6 | input = input.toJSON(); 7 | } catch {} 8 | 9 | // 对类型为 string 的值转义处理 10 | if (Array.isArray(input)) { 11 | return input.map(escapeHtmlInObject); 12 | } else if (typeof input == 'object') { 13 | const output = {}; 14 | Object.keys(input).forEach((k) => { 15 | output[k] = escapeHtmlInObject(input[k]); 16 | }); 17 | return output; 18 | } else if (typeof input == 'string') { 19 | return escapeHtml(input); 20 | } else { 21 | return input; 22 | } 23 | }; 24 | -------------------------------------------------------------------------------- /09-config/.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /09-config/.env.local: -------------------------------------------------------------------------------- 1 | GITHUB_CLIENT_ID='b8ada004c6d682426cfb' 2 | GITHUB_CLIENT_SECRET='0b13f2ab5651f33f879a535fc2b316c6c731a041' 3 | -------------------------------------------------------------------------------- /09-config/.env.production.local: -------------------------------------------------------------------------------- 1 | GITHUB_CLIENT_ID='a8d43bbca18811dcc63a' 2 | GITHUB_CLIENT_SECRET='276b97b79c79cfef36c3fb1fceef8542f9e88aa6' 3 | -------------------------------------------------------------------------------- /09-config/.npmrc: -------------------------------------------------------------------------------- 1 | registry=http://r.cnpmjs.org/ 2 | node_sqlite3_binary_host_mirror=http://npm.taobao.org/mirrors/sqlite3/ 3 | -------------------------------------------------------------------------------- /09-config/.sequelizerc: -------------------------------------------------------------------------------- 1 | const { resolve } = require('path'); 2 | 3 | const modelsDir = resolve('src/models'); 4 | 5 | module.exports = { 6 | config: `${modelsDir}/config`, 7 | 'migrations-path': `${modelsDir}/migrate`, 8 | 'seeders-path': `${modelsDir}/seed`, 9 | 'models-path': modelsDir, 10 | }; 11 | -------------------------------------------------------------------------------- /09-config/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:12.18.2 2 | 3 | WORKDIR /usr/app/09-config 4 | COPY . . 5 | RUN yarn 6 | 7 | EXPOSE 9000 8 | CMD yarn start:prod 9 | -------------------------------------------------------------------------------- /09-config/database/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/licg9999/nodejs-server-examples/a9a3acd1f922888fa5f95175d50ed9cfac774e82/09-config/database/.gitkeep -------------------------------------------------------------------------------- /09-config/public/500.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |

系统繁忙,请您稍后再试

7 | 返回首页 8 | 9 | 10 | -------------------------------------------------------------------------------- /09-config/public/glue.js: -------------------------------------------------------------------------------- 1 | import './moulds/yup.js'; 2 | 3 | window.require = (k) => window[k]; 4 | window.exports = window.moulds = {}; 5 | -------------------------------------------------------------------------------- /09-config/public/index.css: -------------------------------------------------------------------------------- 1 | .error { 2 | color: red; 3 | font-size: 14px; 4 | } 5 | -------------------------------------------------------------------------------- /09-config/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /09-config/public/login.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 13 | 14 |
15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /09-config/scripts/env.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const { resolve } = require('path'); 3 | const dotenv = require('dotenv'); 4 | 5 | const dotenvTags = [ 6 | // 本地环境 7 | 'development', 8 | 9 | // 测试环境 10 | // 比如:单元测试 11 | 'test', 12 | 13 | // 部署环境 14 | // 比如:日常、预发、线上 15 | 'production', 16 | ]; 17 | 18 | if (!dotenvTags.includes(process.env.NODE_ENV)) { 19 | process.env.NODE_ENV = dotenvTags[0]; 20 | } 21 | 22 | const dotenvPath = resolve('.env'); 23 | 24 | const dotenvFiles = [ 25 | dotenvPath, 26 | `${dotenvPath}.local`, 27 | `${dotenvPath}.${process.env.NODE_ENV}`, 28 | `${dotenvPath}.${process.env.NODE_ENV}.local`, 29 | ].filter(fs.existsSync); 30 | 31 | dotenvFiles 32 | .reverse() 33 | .forEach((dotenvFile) => dotenv.config({ path: dotenvFile })); 34 | -------------------------------------------------------------------------------- /09-config/src/controllers/csrf.js: -------------------------------------------------------------------------------- 1 | const { Router } = require('express'); 2 | 3 | class CsrfController { 4 | async init() { 5 | const router = Router(); 6 | router.get('/script', this.getScript); 7 | return router; 8 | } 9 | 10 | getScript = (req, res) => { 11 | res.type('js'); 12 | res.send(`window.__CSRF_TOKEN__='${req.csrfToken()}';`); 13 | }; 14 | } 15 | 16 | module.exports = async () => { 17 | const c = new CsrfController(); 18 | return await c.init(); 19 | }; 20 | -------------------------------------------------------------------------------- /09-config/src/controllers/health.js: -------------------------------------------------------------------------------- 1 | const { Router } = require('express'); 2 | 3 | class HealthController { 4 | async init() { 5 | const router = Router(); 6 | router.get('/', this.get); 7 | return router; 8 | } 9 | 10 | get = (req, res) => { 11 | res.send({}); 12 | }; 13 | } 14 | 15 | module.exports = async () => { 16 | const c = new HealthController(); 17 | return await c.init(); 18 | }; 19 | -------------------------------------------------------------------------------- /09-config/src/controllers/index.js: -------------------------------------------------------------------------------- 1 | const { Router } = require('express'); 2 | const shopController = require('./shop'); 3 | const chaosController = require('./chaos'); 4 | const healthController = require('./health'); 5 | const loginController = require('./login'); 6 | const csrfController = require('./csrf'); 7 | 8 | module.exports = async function initControllers() { 9 | const router = Router(); 10 | router.use('/api/shop', await shopController()); 11 | router.use('/api/chaos', await chaosController()); 12 | router.use('/api/health', await healthController()); 13 | router.use('/api/login', await loginController()); 14 | router.use('/api/csrf', await csrfController()); 15 | return router; 16 | }; 17 | -------------------------------------------------------------------------------- /09-config/src/controllers/login.js: -------------------------------------------------------------------------------- 1 | const { Router } = require('express'); 2 | const { passport } = require('../middlewares/auth'); 3 | const { homepagePath, loginPath } = require('../config'); 4 | 5 | class LoginController { 6 | async init() { 7 | const router = Router(); 8 | router.post('/', this.post); 9 | router.get( 10 | '/github', 11 | passport.authenticate('github', { scope: ['read:user'] }) 12 | ); 13 | router.get( 14 | '/github/callback', 15 | passport.authenticate('github', { 16 | failureRedirect: loginPath, 17 | }), 18 | this.getGithubCallback 19 | ); 20 | return router; 21 | } 22 | 23 | post = (req, res) => { 24 | req.session.logined = true; 25 | res.redirect(homepagePath); 26 | }; 27 | 28 | getGithubCallback = (req, res) => { 29 | req.session.logined = true; 30 | res.redirect(homepagePath); 31 | }; 32 | } 33 | 34 | module.exports = async () => { 35 | const c = new LoginController(); 36 | return await c.init(); 37 | }; 38 | -------------------------------------------------------------------------------- /09-config/src/middlewares/auth.js: -------------------------------------------------------------------------------- 1 | const passport = require('passport'); 2 | const { Strategy: GithubStrategy } = require('passport-github'); 3 | const { githubStrategyOptions } = require('../config'); 4 | 5 | const githubStrategy = new GithubStrategy( 6 | githubStrategyOptions, 7 | (accessToken, refreshToken, profile, done) => { 8 | /** 9 | * 根据 profile 查找或新建 user 信息 10 | */ 11 | const user = {}; 12 | done(null, user); 13 | } 14 | ); 15 | 16 | passport.use(githubStrategy); 17 | 18 | passport.serializeUser((user, done) => { 19 | /** 20 | * 根据 user 信息获取 userId 21 | */ 22 | const userId = '46e5'; 23 | done(null, userId); 24 | }); 25 | 26 | passport.deserializeUser((userId, done) => { 27 | /** 28 | * 根据 userId 获取 user 信息 29 | */ 30 | const user = {}; 31 | done(null, user); 32 | }); 33 | 34 | module.exports = function authMiddleware() { 35 | return [passport.initialize(), passport.session()]; 36 | }; 37 | 38 | Object.assign(module.exports, { passport }); 39 | -------------------------------------------------------------------------------- /09-config/src/middlewares/index.js: -------------------------------------------------------------------------------- 1 | const { Router } = require('express'); 2 | const cookieParser = require('cookie-parser'); 3 | const bodyParser = require('body-parser'); 4 | const csurf = require('csurf'); 5 | const helmet = require('helmet'); 6 | const sessionMiddleware = require('./session'); 7 | const urlnormalizeMiddleware = require('./urlnormalize'); 8 | const loginMiddleware = require('./login'); 9 | const authMiddleware = require('./auth'); 10 | const { sessionCookieSecret } = require('../config'); 11 | 12 | module.exports = async function initMiddlewares() { 13 | const router = Router(); 14 | router.use(helmet()); 15 | router.use(urlnormalizeMiddleware()); 16 | router.use(cookieParser(sessionCookieSecret)); 17 | router.use(sessionMiddleware()); 18 | router.use(loginMiddleware()); 19 | router.use(authMiddleware()); 20 | router.use(bodyParser.urlencoded({ extended: false }), csurf()); 21 | return router; 22 | }; 23 | -------------------------------------------------------------------------------- /09-config/src/middlewares/login.js: -------------------------------------------------------------------------------- 1 | const { parse } = require('url'); 2 | const { homepagePath, loginPath, loginWhiteList } = require('../config'); 3 | 4 | module.exports = function loginMiddleware() { 5 | const whiteList = Object.assign({}, loginWhiteList, { 6 | [loginPath]: ['get'], 7 | }); 8 | 9 | return (req, res, next) => { 10 | const { pathname } = parse(req.url); 11 | 12 | if (req.session.logined && pathname == loginPath) { 13 | res.redirect(homepagePath); 14 | return; 15 | } 16 | 17 | if ( 18 | req.session.logined || 19 | (whiteList[pathname] && 20 | whiteList[pathname].includes(req.method.toLowerCase())) 21 | ) { 22 | next(); 23 | return; 24 | } 25 | 26 | res.redirect(loginPath); 27 | }; 28 | }; 29 | -------------------------------------------------------------------------------- /09-config/src/middlewares/session.js: -------------------------------------------------------------------------------- 1 | const session = require('express-session'); 2 | const sessionSequelize = require('connect-session-sequelize'); 3 | const { sequelize } = require('../models'); 4 | const { sessionCookieSecret, sessionCookieMaxAge } = require('../config'); 5 | 6 | module.exports = function sessionMiddleware() { 7 | const SequelizeStore = sessionSequelize(session.Store); 8 | 9 | const store = new SequelizeStore({ 10 | db: sequelize, 11 | modelKey: 'Session', 12 | tableName: 'session', 13 | }); 14 | 15 | return session({ 16 | secret: sessionCookieSecret, 17 | cookie: { maxAge: sessionCookieMaxAge }, 18 | store, 19 | resave: false, 20 | proxy: true, 21 | saveUninitialized: false, 22 | }); 23 | }; 24 | -------------------------------------------------------------------------------- /09-config/src/middlewares/urlnormalize.js: -------------------------------------------------------------------------------- 1 | const { normalize } = require('path'); 2 | const { parse, format } = require('url'); 3 | 4 | module.exports = function urlnormalizeMiddleware() { 5 | return (req, res, next) => { 6 | // 解决windows、Linux系统使用normalize路径分隔符不一致的问题 7 | const pathname = normalize(req.path).split('\\').join('/'); 8 | const urlParsed = parse(req.url); 9 | 10 | let shouldRedirect = false; 11 | 12 | // 重定向不规范的路径 13 | if (req.path != pathname) { 14 | urlParsed.pathname = pathname; 15 | shouldRedirect = true; 16 | } 17 | 18 | // 执行重定向或者略过 19 | if (shouldRedirect) { 20 | res.redirect(format(urlParsed)); 21 | } else { 22 | next(); 23 | } 24 | }; 25 | }; 26 | -------------------------------------------------------------------------------- /09-config/src/models/config/index.js: -------------------------------------------------------------------------------- 1 | const { db } = require('../../config'); 2 | 3 | module.exports = { [process.env.NODE_ENV || 'development']: db }; 4 | -------------------------------------------------------------------------------- /09-config/src/models/migrate/20200725045100-create-shop.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | up: async (queryInterface, Sequelize) => { 3 | await queryInterface.createTable('shop', { 4 | id: { 5 | allowNull: false, 6 | autoIncrement: true, 7 | primaryKey: true, 8 | type: Sequelize.INTEGER, 9 | }, 10 | name: { 11 | type: Sequelize.STRING, 12 | }, 13 | created_at: { 14 | allowNull: false, 15 | type: Sequelize.DATE, 16 | }, 17 | updated_at: { 18 | allowNull: false, 19 | type: Sequelize.DATE, 20 | }, 21 | }); 22 | }, 23 | down: async (queryInterface, Sequelize) => { 24 | await queryInterface.dropTable('shop'); 25 | }, 26 | }; 27 | -------------------------------------------------------------------------------- /09-config/src/models/migrate/20200727025727-create-session.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | up: async (queryInterface, Sequelize) => { 3 | await queryInterface.createTable('session', { 4 | sid: { 5 | type: Sequelize.STRING(36), 6 | }, 7 | expires: { 8 | type: Sequelize.DATE, 9 | }, 10 | data: { 11 | type: Sequelize.TEXT, 12 | }, 13 | 14 | created_at: { 15 | allowNull: false, 16 | type: Sequelize.DATE, 17 | }, 18 | updated_at: { 19 | allowNull: false, 20 | type: Sequelize.DATE, 21 | }, 22 | }); 23 | }, 24 | 25 | down: async (queryInterface, Sequelize) => { 26 | queryInterface.dropTable('session'); 27 | }, 28 | }; 29 | -------------------------------------------------------------------------------- /09-config/src/models/seed/20200725050230-first-shop.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | up: async (queryInterface, Sequelize) => { 3 | await queryInterface.bulkInsert('shop', [ 4 | { name: '良品铺子', created_at: new Date(), updated_at: new Date() }, 5 | { name: '来伊份', created_at: new Date(), updated_at: new Date() }, 6 | { name: '三只松鼠', created_at: new Date(), updated_at: new Date() }, 7 | { name: '百草味', created_at: new Date(), updated_at: new Date() }, 8 | ]); 9 | }, 10 | 11 | down: async (queryInterface, Sequelize) => { 12 | await queryInterface.bulkDelete('shop', null, {}); 13 | }, 14 | }; 15 | -------------------------------------------------------------------------------- /09-config/src/models/shop.js: -------------------------------------------------------------------------------- 1 | const { Model } = require('sequelize'); 2 | 3 | module.exports = (sequelize, DataTypes) => { 4 | class Shop extends Model { 5 | /** 6 | * Helper method for defining associations. 7 | * This method is not a part of Sequelize lifecycle. 8 | * The `models/index` file will call this method automatically. 9 | */ 10 | static associate(models) { 11 | // define association here 12 | } 13 | } 14 | Shop.init( 15 | { 16 | name: DataTypes.STRING, 17 | }, 18 | { 19 | sequelize, 20 | modelName: 'Shop', 21 | tableName: 'shop', 22 | } 23 | ); 24 | return Shop; 25 | }; 26 | -------------------------------------------------------------------------------- /09-config/src/moulds/ShopForm.js: -------------------------------------------------------------------------------- 1 | const Yup = require('yup'); 2 | 3 | exports.createShopFormSchema = () => 4 | Yup.object({ 5 | name: Yup.string() 6 | .required('店铺名不能为空') 7 | .min(3, '店铺名至少 3 个字符') 8 | .max(120, '店铺名不可超过 120 字'), 9 | }); 10 | -------------------------------------------------------------------------------- /09-config/src/services/shop.js: -------------------------------------------------------------------------------- 1 | const { Shop } = require('../models'); 2 | 3 | class ShopService { 4 | async init() {} 5 | 6 | async find({ id, pageIndex = 0, pageSize = 10 }) { 7 | if (id) { 8 | return [await Shop.findByPk(id)]; 9 | } 10 | 11 | return await Shop.findAll({ 12 | offset: pageIndex * pageSize, 13 | limit: pageSize, 14 | }); 15 | } 16 | 17 | async modify({ id, values }) { 18 | const target = await Shop.findByPk(id); 19 | 20 | if (!target) { 21 | return null; 22 | } 23 | 24 | Object.assign(target, values); 25 | return await target.save(); 26 | } 27 | 28 | async remove({ id }) { 29 | const target = await Shop.findByPk(id); 30 | 31 | if (!target) { 32 | return false; 33 | } 34 | 35 | return target.destroy(); 36 | } 37 | 38 | async create({ values }) { 39 | return await Shop.create(values); 40 | } 41 | } 42 | 43 | // 单例模式 44 | let service; 45 | module.exports = async function () { 46 | if (!service) { 47 | service = new ShopService(); 48 | await service.init(); 49 | } 50 | return service; 51 | }; 52 | -------------------------------------------------------------------------------- /09-config/src/utils/cc.js: -------------------------------------------------------------------------------- 1 | module.exports = function callbackCatch(callback) { 2 | return async (req, res, next) => { 3 | try { 4 | await callback(req, res, next); 5 | } catch (e) { 6 | next(e); 7 | } 8 | }; 9 | }; 10 | -------------------------------------------------------------------------------- /09-config/src/utils/escape-html-in-object.js: -------------------------------------------------------------------------------- 1 | const escapeHtml = require('escape-html'); 2 | 3 | module.exports = function escapeHtmlInObject(input) { 4 | // 尝试将 ORM 对象转化为普通对象 5 | try { 6 | input = input.toJSON(); 7 | } catch {} 8 | 9 | // 对类型为 string 的值转义处理 10 | if (Array.isArray(input)) { 11 | return input.map(escapeHtmlInObject); 12 | } else if (typeof input == 'object') { 13 | const output = {}; 14 | Object.keys(input).forEach((k) => { 15 | output[k] = escapeHtmlInObject(input[k]); 16 | }); 17 | return output; 18 | } else if (typeof input == 'string') { 19 | return escapeHtml(input); 20 | } else { 21 | return input; 22 | } 23 | }; 24 | -------------------------------------------------------------------------------- /10-log/.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /10-log/.env: -------------------------------------------------------------------------------- 1 | LOG_LEVEL='debug' 2 | -------------------------------------------------------------------------------- /10-log/.env.local: -------------------------------------------------------------------------------- 1 | GITHUB_CLIENT_ID='b8ada004c6d682426cfb' 2 | GITHUB_CLIENT_SECRET='0b13f2ab5651f33f879a535fc2b316c6c731a041' 3 | -------------------------------------------------------------------------------- /10-log/.env.production.local: -------------------------------------------------------------------------------- 1 | GITHUB_CLIENT_ID='a8d43bbca18811dcc63a' 2 | GITHUB_CLIENT_SECRET='276b97b79c79cfef36c3fb1fceef8542f9e88aa6' 3 | -------------------------------------------------------------------------------- /10-log/.npmrc: -------------------------------------------------------------------------------- 1 | registry=http://r.cnpmjs.org/ 2 | node_sqlite3_binary_host_mirror=http://npm.taobao.org/mirrors/sqlite3/ 3 | -------------------------------------------------------------------------------- /10-log/.sequelizerc: -------------------------------------------------------------------------------- 1 | const { resolve } = require('path'); 2 | 3 | const modelsDir = resolve('src/models'); 4 | 5 | module.exports = { 6 | config: `${modelsDir}/config`, 7 | 'migrations-path': `${modelsDir}/migrate`, 8 | 'seeders-path': `${modelsDir}/seed`, 9 | 'models-path': modelsDir, 10 | }; 11 | -------------------------------------------------------------------------------- /10-log/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:12.18.2 2 | 3 | WORKDIR /usr/app/09-config 4 | COPY . . 5 | RUN yarn 6 | 7 | EXPOSE 9000 8 | CMD yarn start:prod 9 | -------------------------------------------------------------------------------- /10-log/database/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/licg9999/nodejs-server-examples/a9a3acd1f922888fa5f95175d50ed9cfac774e82/10-log/database/.gitkeep -------------------------------------------------------------------------------- /10-log/public/500.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |

系统繁忙,请您稍后再试

7 | 返回首页 8 | 9 | 10 | -------------------------------------------------------------------------------- /10-log/public/glue.js: -------------------------------------------------------------------------------- 1 | import './moulds/yup.js'; 2 | 3 | window.require = (k) => window[k]; 4 | window.exports = window.moulds = {}; 5 | -------------------------------------------------------------------------------- /10-log/public/index.css: -------------------------------------------------------------------------------- 1 | .error { 2 | color: red; 3 | font-size: 14px; 4 | } 5 | -------------------------------------------------------------------------------- /10-log/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /10-log/public/login.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 13 | 14 |
15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /10-log/scripts/env.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const { resolve } = require('path'); 3 | const dotenv = require('dotenv'); 4 | 5 | const dotenvTags = [ 6 | // 本地环境 7 | 'development', 8 | 9 | // 测试环境 10 | // 比如:单元测试 11 | 'test', 12 | 13 | // 部署环境 14 | // 比如:日常、预发、线上 15 | 'production', 16 | ]; 17 | 18 | if (!dotenvTags.includes(process.env.NODE_ENV)) { 19 | process.env.NODE_ENV = dotenvTags[0]; 20 | } 21 | 22 | const dotenvPath = resolve('.env'); 23 | 24 | const dotenvFiles = [ 25 | dotenvPath, 26 | `${dotenvPath}.local`, 27 | `${dotenvPath}.${process.env.NODE_ENV}`, 28 | `${dotenvPath}.${process.env.NODE_ENV}.local`, 29 | ].filter(fs.existsSync); 30 | 31 | dotenvFiles 32 | .reverse() 33 | .forEach((dotenvFile) => dotenv.config({ path: dotenvFile })); 34 | -------------------------------------------------------------------------------- /10-log/src/controllers/csrf.js: -------------------------------------------------------------------------------- 1 | const { Router } = require('express'); 2 | 3 | class CsrfController { 4 | async init() { 5 | const router = Router(); 6 | router.get('/script', this.getScript); 7 | return router; 8 | } 9 | 10 | getScript = (req, res) => { 11 | res.type('js'); 12 | res.send(`window.__CSRF_TOKEN__='${req.csrfToken()}';`); 13 | }; 14 | } 15 | 16 | module.exports = async () => { 17 | const c = new CsrfController(); 18 | return await c.init(); 19 | }; 20 | -------------------------------------------------------------------------------- /10-log/src/controllers/health.js: -------------------------------------------------------------------------------- 1 | const { Router } = require('express'); 2 | 3 | class HealthController { 4 | async init() { 5 | const router = Router(); 6 | router.get('/', this.get); 7 | return router; 8 | } 9 | 10 | get = (req, res) => { 11 | res.send({}); 12 | }; 13 | } 14 | 15 | module.exports = async () => { 16 | const c = new HealthController(); 17 | return await c.init(); 18 | }; 19 | -------------------------------------------------------------------------------- /10-log/src/controllers/index.js: -------------------------------------------------------------------------------- 1 | const { Router } = require('express'); 2 | const shopController = require('./shop'); 3 | const chaosController = require('./chaos'); 4 | const healthController = require('./health'); 5 | const loginController = require('./login'); 6 | const csrfController = require('./csrf'); 7 | 8 | module.exports = async function initControllers() { 9 | const router = Router(); 10 | router.use('/api/shop', await shopController()); 11 | router.use('/api/chaos', await chaosController()); 12 | router.use('/api/health', await healthController()); 13 | router.use('/api/login', await loginController()); 14 | router.use('/api/csrf', await csrfController()); 15 | return router; 16 | }; 17 | -------------------------------------------------------------------------------- /10-log/src/controllers/login.js: -------------------------------------------------------------------------------- 1 | const { Router } = require('express'); 2 | const { passport } = require('../middlewares/auth'); 3 | const { homepagePath, loginPath } = require('../config'); 4 | 5 | class LoginController { 6 | async init() { 7 | const router = Router(); 8 | router.post('/', this.post); 9 | router.get( 10 | '/github', 11 | passport.authenticate('github', { scope: ['read:user'] }) 12 | ); 13 | router.get( 14 | '/github/callback', 15 | passport.authenticate('github', { 16 | failureRedirect: loginPath, 17 | }), 18 | this.getGithubCallback 19 | ); 20 | return router; 21 | } 22 | 23 | post = (req, res) => { 24 | req.session.logined = true; 25 | res.redirect(homepagePath); 26 | }; 27 | 28 | getGithubCallback = (req, res) => { 29 | req.session.logined = true; 30 | res.redirect(homepagePath); 31 | }; 32 | } 33 | 34 | module.exports = async () => { 35 | const c = new LoginController(); 36 | return await c.init(); 37 | }; 38 | -------------------------------------------------------------------------------- /10-log/src/middlewares/auth.js: -------------------------------------------------------------------------------- 1 | const passport = require('passport'); 2 | const { Strategy: GithubStrategy } = require('passport-github'); 3 | const { githubStrategyOptions } = require('../config'); 4 | 5 | const githubStrategy = new GithubStrategy( 6 | githubStrategyOptions, 7 | (accessToken, refreshToken, profile, done) => { 8 | /** 9 | * 根据 profile 查找或新建 user 信息 10 | */ 11 | const user = {}; 12 | done(null, user); 13 | } 14 | ); 15 | 16 | passport.use(githubStrategy); 17 | 18 | passport.serializeUser((user, done) => { 19 | /** 20 | * 根据 user 信息获取 userId 21 | */ 22 | const userId = '46e5'; 23 | done(null, userId); 24 | }); 25 | 26 | passport.deserializeUser((userId, done) => { 27 | /** 28 | * 根据 userId 获取 user 信息 29 | */ 30 | const user = {}; 31 | done(null, user); 32 | }); 33 | 34 | module.exports = function authMiddleware() { 35 | return [passport.initialize(), passport.session()]; 36 | }; 37 | 38 | Object.assign(module.exports, { passport }); 39 | -------------------------------------------------------------------------------- /10-log/src/middlewares/index.js: -------------------------------------------------------------------------------- 1 | const { Router } = require('express'); 2 | const cookieParser = require('cookie-parser'); 3 | const bodyParser = require('body-parser'); 4 | const csurf = require('csurf'); 5 | const helmet = require('helmet'); 6 | const sessionMiddleware = require('./session'); 7 | const urlnormalizeMiddleware = require('./urlnormalize'); 8 | const loginMiddleware = require('./login'); 9 | const authMiddleware = require('./auth'); 10 | const traceMiddleware = require('./trace'); 11 | const { sessionCookieSecret } = require('../config'); 12 | 13 | module.exports = async function initMiddlewares() { 14 | const router = Router(); 15 | router.use(traceMiddleware()); 16 | router.use(helmet()); 17 | router.use(urlnormalizeMiddleware()); 18 | router.use(cookieParser(sessionCookieSecret)); 19 | router.use(sessionMiddleware()); 20 | router.use(loginMiddleware()); 21 | router.use(authMiddleware()); 22 | router.use(bodyParser.urlencoded({ extended: false }), csurf()); 23 | return router; 24 | }; 25 | -------------------------------------------------------------------------------- /10-log/src/middlewares/login.js: -------------------------------------------------------------------------------- 1 | const { parse } = require('url'); 2 | const { homepagePath, loginPath, loginWhiteList } = require('../config'); 3 | 4 | module.exports = function loginMiddleware() { 5 | const whiteList = Object.assign({}, loginWhiteList, { 6 | [loginPath]: ['get'], 7 | }); 8 | 9 | return (req, res, next) => { 10 | const { pathname } = parse(req.url); 11 | 12 | if (req.session.logined && pathname == loginPath) { 13 | res.redirect(homepagePath); 14 | return; 15 | } 16 | 17 | if ( 18 | req.session.logined || 19 | (whiteList[pathname] && 20 | whiteList[pathname].includes(req.method.toLowerCase())) 21 | ) { 22 | next(); 23 | return; 24 | } 25 | 26 | res.redirect(loginPath); 27 | }; 28 | }; 29 | -------------------------------------------------------------------------------- /10-log/src/middlewares/session.js: -------------------------------------------------------------------------------- 1 | const session = require('express-session'); 2 | const sessionSequelize = require('connect-session-sequelize'); 3 | const { sequelize } = require('../models'); 4 | const { sessionCookieSecret, sessionCookieMaxAge } = require('../config'); 5 | 6 | module.exports = function sessionMiddleware() { 7 | const SequelizeStore = sessionSequelize(session.Store); 8 | 9 | const store = new SequelizeStore({ 10 | db: sequelize, 11 | modelKey: 'Session', 12 | tableName: 'session', 13 | }); 14 | 15 | return session({ 16 | secret: sessionCookieSecret, 17 | cookie: { maxAge: sessionCookieMaxAge }, 18 | store, 19 | resave: false, 20 | proxy: true, 21 | saveUninitialized: false, 22 | }); 23 | }; 24 | -------------------------------------------------------------------------------- /10-log/src/middlewares/urlnormalize.js: -------------------------------------------------------------------------------- 1 | const { normalize } = require('path'); 2 | const { parse, format } = require('url'); 3 | 4 | module.exports = function urlnormalizeMiddleware() { 5 | return (req, res, next) => { 6 | // 解决windows、Linux系统使用normalize路径分隔符不一致的问题 7 | const pathname = normalize(req.path).split('\\').join('/'); 8 | const urlParsed = parse(req.url); 9 | 10 | let shouldRedirect = false; 11 | 12 | // 重定向不规范的路径 13 | if (req.path != pathname) { 14 | urlParsed.pathname = pathname; 15 | shouldRedirect = true; 16 | } 17 | 18 | // 执行重定向或者略过 19 | if (shouldRedirect) { 20 | res.redirect(format(urlParsed)); 21 | } else { 22 | next(); 23 | } 24 | }; 25 | }; 26 | -------------------------------------------------------------------------------- /10-log/src/models/config/index.js: -------------------------------------------------------------------------------- 1 | const { db } = require('../../config'); 2 | 3 | module.exports = { [process.env.NODE_ENV || 'development']: db }; 4 | -------------------------------------------------------------------------------- /10-log/src/models/migrate/20200725045100-create-shop.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | up: async (queryInterface, Sequelize) => { 3 | await queryInterface.createTable('shop', { 4 | id: { 5 | allowNull: false, 6 | autoIncrement: true, 7 | primaryKey: true, 8 | type: Sequelize.INTEGER, 9 | }, 10 | name: { 11 | type: Sequelize.STRING, 12 | }, 13 | created_at: { 14 | allowNull: false, 15 | type: Sequelize.DATE, 16 | }, 17 | updated_at: { 18 | allowNull: false, 19 | type: Sequelize.DATE, 20 | }, 21 | }); 22 | }, 23 | down: async (queryInterface, Sequelize) => { 24 | await queryInterface.dropTable('shop'); 25 | }, 26 | }; 27 | -------------------------------------------------------------------------------- /10-log/src/models/migrate/20200727025727-create-session.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | up: async (queryInterface, Sequelize) => { 3 | await queryInterface.createTable('session', { 4 | sid: { 5 | type: Sequelize.STRING(36), 6 | }, 7 | expires: { 8 | type: Sequelize.DATE, 9 | }, 10 | data: { 11 | type: Sequelize.TEXT, 12 | }, 13 | 14 | created_at: { 15 | allowNull: false, 16 | type: Sequelize.DATE, 17 | }, 18 | updated_at: { 19 | allowNull: false, 20 | type: Sequelize.DATE, 21 | }, 22 | }); 23 | }, 24 | 25 | down: async (queryInterface, Sequelize) => { 26 | queryInterface.dropTable('session'); 27 | }, 28 | }; 29 | -------------------------------------------------------------------------------- /10-log/src/models/seed/20200725050230-first-shop.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | up: async (queryInterface, Sequelize) => { 3 | await queryInterface.bulkInsert('shop', [ 4 | { name: '良品铺子', created_at: new Date(), updated_at: new Date() }, 5 | { name: '来伊份', created_at: new Date(), updated_at: new Date() }, 6 | { name: '三只松鼠', created_at: new Date(), updated_at: new Date() }, 7 | { name: '百草味', created_at: new Date(), updated_at: new Date() }, 8 | ]); 9 | }, 10 | 11 | down: async (queryInterface, Sequelize) => { 12 | await queryInterface.bulkDelete('shop', null, {}); 13 | }, 14 | }; 15 | -------------------------------------------------------------------------------- /10-log/src/models/shop.js: -------------------------------------------------------------------------------- 1 | const { Model } = require('sequelize'); 2 | 3 | module.exports = (sequelize, DataTypes) => { 4 | class Shop extends Model { 5 | /** 6 | * Helper method for defining associations. 7 | * This method is not a part of Sequelize lifecycle. 8 | * The `models/index` file will call this method automatically. 9 | */ 10 | static associate(models) { 11 | // define association here 12 | } 13 | } 14 | Shop.init( 15 | { 16 | name: DataTypes.STRING, 17 | }, 18 | { 19 | sequelize, 20 | modelName: 'Shop', 21 | tableName: 'shop', 22 | } 23 | ); 24 | return Shop; 25 | }; 26 | -------------------------------------------------------------------------------- /10-log/src/moulds/ShopForm.js: -------------------------------------------------------------------------------- 1 | const Yup = require('yup'); 2 | 3 | exports.createShopFormSchema = () => 4 | Yup.object({ 5 | name: Yup.string() 6 | .required('店铺名不能为空') 7 | .min(3, '店铺名至少 3 个字符') 8 | .max(120, '店铺名不可超过 120 字'), 9 | }); 10 | -------------------------------------------------------------------------------- /10-log/src/utils/cc.js: -------------------------------------------------------------------------------- 1 | module.exports = function callbackCatch(callback) { 2 | return async (req, res, next) => { 3 | try { 4 | await callback(req, res, next); 5 | } catch (e) { 6 | next(e); 7 | } 8 | }; 9 | }; 10 | -------------------------------------------------------------------------------- /10-log/src/utils/escape-html-in-object.js: -------------------------------------------------------------------------------- 1 | const escapeHtml = require('escape-html'); 2 | 3 | module.exports = function escapeHtmlInObject(input) { 4 | // 尝试将 ORM 对象转化为普通对象 5 | try { 6 | input = input.toJSON(); 7 | } catch {} 8 | 9 | // 对类型为 string 的值转义处理 10 | if (Array.isArray(input)) { 11 | return input.map(escapeHtmlInObject); 12 | } else if (typeof input == 'object') { 13 | const output = {}; 14 | Object.keys(input).forEach((k) => { 15 | output[k] = escapeHtmlInObject(input[k]); 16 | }); 17 | return output; 18 | } else if (typeof input == 'string') { 19 | return escapeHtml(input); 20 | } else { 21 | return input; 22 | } 23 | }; 24 | -------------------------------------------------------------------------------- /10-log/src/utils/logger.js: -------------------------------------------------------------------------------- 1 | const bunyan = require('bunyan'); 2 | const { name } = require('../../package.json'); 3 | 4 | const logger = bunyan.createLogger({ 5 | name, 6 | level: (process.env.LOG_LEVEL || 'debug').toLowerCase(), 7 | }); 8 | 9 | const LOGGING_REGEXP = /^Executed\s+\((.+)\):\s+(.+)/; 10 | 11 | function logging(logger, level = 'trace') { 12 | return (m, t) => { 13 | const o = { type: 'sql' }; 14 | 15 | const match = m.match(LOGGING_REGEXP); 16 | if (match) { 17 | o.transaction = match[1]; 18 | o.statement = match[2]; 19 | } else { 20 | o.statement = m; 21 | } 22 | 23 | if (typeof t == 'number') o.elapsedTime = t; 24 | 25 | logger[level](o); 26 | }; 27 | } 28 | 29 | module.exports = logger; 30 | 31 | Object.assign(module.exports, { logging }); 32 | -------------------------------------------------------------------------------- /11-schedule/.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /11-schedule/.env: -------------------------------------------------------------------------------- 1 | LOG_LEVEL='debug' 2 | -------------------------------------------------------------------------------- /11-schedule/.env.local: -------------------------------------------------------------------------------- 1 | GITHUB_CLIENT_ID='b8ada004c6d682426cfb' 2 | GITHUB_CLIENT_SECRET='0b13f2ab5651f33f879a535fc2b316c6c731a041' 3 | 4 | MAILER_USER='ht_nse@126.com' 5 | MAILER_PASS='CAEJHSTBWNOKHRVL' 6 | -------------------------------------------------------------------------------- /11-schedule/.env.production.local: -------------------------------------------------------------------------------- 1 | GITHUB_CLIENT_ID='a8d43bbca18811dcc63a' 2 | GITHUB_CLIENT_SECRET='276b97b79c79cfef36c3fb1fceef8542f9e88aa6' 3 | -------------------------------------------------------------------------------- /11-schedule/.npmrc: -------------------------------------------------------------------------------- 1 | registry=http://r.cnpmjs.org/ 2 | node_sqlite3_binary_host_mirror=http://npm.taobao.org/mirrors/sqlite3/ 3 | -------------------------------------------------------------------------------- /11-schedule/.sequelizerc: -------------------------------------------------------------------------------- 1 | const { resolve } = require('path'); 2 | 3 | const modelsDir = resolve('src/models'); 4 | 5 | module.exports = { 6 | config: `${modelsDir}/config`, 7 | 'migrations-path': `${modelsDir}/migrate`, 8 | 'seeders-path': `${modelsDir}/seed`, 9 | 'models-path': modelsDir, 10 | }; 11 | -------------------------------------------------------------------------------- /11-schedule/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:12.18.2 2 | 3 | WORKDIR /usr/app/09-config 4 | COPY . . 5 | RUN yarn 6 | 7 | EXPOSE 9000 8 | CMD yarn start:prod 9 | -------------------------------------------------------------------------------- /11-schedule/database/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/licg9999/nodejs-server-examples/a9a3acd1f922888fa5f95175d50ed9cfac774e82/11-schedule/database/.gitkeep -------------------------------------------------------------------------------- /11-schedule/public/500.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |

系统繁忙,请您稍后再试

7 | 返回首页 8 | 9 | 10 | -------------------------------------------------------------------------------- /11-schedule/public/glue.js: -------------------------------------------------------------------------------- 1 | import './moulds/yup.js'; 2 | 3 | window.require = (k) => window[k]; 4 | window.exports = window.moulds = {}; 5 | -------------------------------------------------------------------------------- /11-schedule/public/index.css: -------------------------------------------------------------------------------- 1 | .error { 2 | color: red; 3 | font-size: 14px; 4 | } 5 | -------------------------------------------------------------------------------- /11-schedule/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /11-schedule/public/login.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 13 | 14 |
15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /11-schedule/scripts/env.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const { resolve } = require('path'); 3 | const dotenv = require('dotenv'); 4 | 5 | const dotenvTags = [ 6 | // 本地环境 7 | 'development', 8 | 9 | // 测试环境 10 | // 比如:单元测试 11 | 'test', 12 | 13 | // 部署环境 14 | // 比如:日常、预发、线上 15 | 'production', 16 | ]; 17 | 18 | if (!dotenvTags.includes(process.env.NODE_ENV)) { 19 | process.env.NODE_ENV = dotenvTags[0]; 20 | } 21 | 22 | const dotenvPath = resolve('.env'); 23 | 24 | const dotenvFiles = [ 25 | dotenvPath, 26 | `${dotenvPath}.local`, 27 | `${dotenvPath}.${process.env.NODE_ENV}`, 28 | `${dotenvPath}.${process.env.NODE_ENV}.local`, 29 | ].filter(fs.existsSync); 30 | 31 | dotenvFiles 32 | .reverse() 33 | .forEach((dotenvFile) => dotenv.config({ path: dotenvFile })); 34 | -------------------------------------------------------------------------------- /11-schedule/src/controllers/csrf.js: -------------------------------------------------------------------------------- 1 | const { Router } = require('express'); 2 | 3 | class CsrfController { 4 | async init() { 5 | const router = Router(); 6 | router.get('/script', this.getScript); 7 | return router; 8 | } 9 | 10 | getScript = (req, res) => { 11 | res.type('js'); 12 | res.send(`window.__CSRF_TOKEN__='${req.csrfToken()}';`); 13 | }; 14 | } 15 | 16 | module.exports = async () => { 17 | const c = new CsrfController(); 18 | return await c.init(); 19 | }; 20 | -------------------------------------------------------------------------------- /11-schedule/src/controllers/health.js: -------------------------------------------------------------------------------- 1 | const { Router } = require('express'); 2 | 3 | class HealthController { 4 | async init() { 5 | const router = Router(); 6 | router.get('/', this.get); 7 | return router; 8 | } 9 | 10 | get = (req, res) => { 11 | res.send({}); 12 | }; 13 | } 14 | 15 | module.exports = async () => { 16 | const c = new HealthController(); 17 | return await c.init(); 18 | }; 19 | -------------------------------------------------------------------------------- /11-schedule/src/controllers/index.js: -------------------------------------------------------------------------------- 1 | const { Router } = require('express'); 2 | const shopController = require('./shop'); 3 | const chaosController = require('./chaos'); 4 | const healthController = require('./health'); 5 | const loginController = require('./login'); 6 | const csrfController = require('./csrf'); 7 | 8 | module.exports = async function initControllers() { 9 | const router = Router(); 10 | router.use('/api/shop', await shopController()); 11 | router.use('/api/chaos', await chaosController()); 12 | router.use('/api/health', await healthController()); 13 | router.use('/api/login', await loginController()); 14 | router.use('/api/csrf', await csrfController()); 15 | return router; 16 | }; 17 | -------------------------------------------------------------------------------- /11-schedule/src/controllers/login.js: -------------------------------------------------------------------------------- 1 | const { Router } = require('express'); 2 | const { passport } = require('../middlewares/auth'); 3 | const { homepagePath, loginPath } = require('../config'); 4 | 5 | class LoginController { 6 | async init() { 7 | const router = Router(); 8 | router.post('/', this.post); 9 | router.get( 10 | '/github', 11 | passport.authenticate('github', { scope: ['read:user'] }) 12 | ); 13 | router.get( 14 | '/github/callback', 15 | passport.authenticate('github', { 16 | failureRedirect: loginPath, 17 | }), 18 | this.getGithubCallback 19 | ); 20 | return router; 21 | } 22 | 23 | post = (req, res) => { 24 | req.session.logined = true; 25 | res.redirect(homepagePath); 26 | }; 27 | 28 | getGithubCallback = (req, res) => { 29 | req.session.logined = true; 30 | res.redirect(homepagePath); 31 | }; 32 | } 33 | 34 | module.exports = async () => { 35 | const c = new LoginController(); 36 | return await c.init(); 37 | }; 38 | -------------------------------------------------------------------------------- /11-schedule/src/middlewares/auth.js: -------------------------------------------------------------------------------- 1 | const passport = require('passport'); 2 | const { Strategy: GithubStrategy } = require('passport-github'); 3 | const { githubStrategyOptions } = require('../config'); 4 | 5 | const githubStrategy = new GithubStrategy( 6 | githubStrategyOptions, 7 | (accessToken, refreshToken, profile, done) => { 8 | /** 9 | * 根据 profile 查找或新建 user 信息 10 | */ 11 | const user = {}; 12 | done(null, user); 13 | } 14 | ); 15 | 16 | passport.use(githubStrategy); 17 | 18 | passport.serializeUser((user, done) => { 19 | /** 20 | * 根据 user 信息获取 userId 21 | */ 22 | const userId = '46e5'; 23 | done(null, userId); 24 | }); 25 | 26 | passport.deserializeUser((userId, done) => { 27 | /** 28 | * 根据 userId 获取 user 信息 29 | */ 30 | const user = {}; 31 | done(null, user); 32 | }); 33 | 34 | module.exports = function authMiddleware() { 35 | return [passport.initialize(), passport.session()]; 36 | }; 37 | 38 | Object.assign(module.exports, { passport }); 39 | -------------------------------------------------------------------------------- /11-schedule/src/middlewares/index.js: -------------------------------------------------------------------------------- 1 | const { Router } = require('express'); 2 | const cookieParser = require('cookie-parser'); 3 | const bodyParser = require('body-parser'); 4 | const csurf = require('csurf'); 5 | const helmet = require('helmet'); 6 | const sessionMiddleware = require('./session'); 7 | const urlnormalizeMiddleware = require('./urlnormalize'); 8 | const loginMiddleware = require('./login'); 9 | const authMiddleware = require('./auth'); 10 | const traceMiddleware = require('./trace'); 11 | const { sessionCookieSecret } = require('../config'); 12 | 13 | module.exports = async function initMiddlewares() { 14 | const router = Router(); 15 | router.use(traceMiddleware()); 16 | router.use(helmet()); 17 | router.use(urlnormalizeMiddleware()); 18 | router.use(cookieParser(sessionCookieSecret)); 19 | router.use(sessionMiddleware()); 20 | router.use(loginMiddleware()); 21 | router.use(authMiddleware()); 22 | router.use(bodyParser.urlencoded({ extended: false }), csurf()); 23 | return router; 24 | }; 25 | -------------------------------------------------------------------------------- /11-schedule/src/middlewares/login.js: -------------------------------------------------------------------------------- 1 | const { parse } = require('url'); 2 | const { homepagePath, loginPath, loginWhiteList } = require('../config'); 3 | 4 | module.exports = function loginMiddleware() { 5 | const whiteList = Object.assign({}, loginWhiteList, { 6 | [loginPath]: ['get'], 7 | }); 8 | 9 | return (req, res, next) => { 10 | const { pathname } = parse(req.url); 11 | 12 | if (req.session.logined && pathname == loginPath) { 13 | res.redirect(homepagePath); 14 | return; 15 | } 16 | 17 | if ( 18 | req.session.logined || 19 | (whiteList[pathname] && 20 | whiteList[pathname].includes(req.method.toLowerCase())) 21 | ) { 22 | next(); 23 | return; 24 | } 25 | 26 | res.redirect(loginPath); 27 | }; 28 | }; 29 | -------------------------------------------------------------------------------- /11-schedule/src/middlewares/session.js: -------------------------------------------------------------------------------- 1 | const session = require('express-session'); 2 | const sessionSequelize = require('connect-session-sequelize'); 3 | const { sequelize } = require('../models'); 4 | const { sessionCookieSecret, sessionCookieMaxAge } = require('../config'); 5 | 6 | module.exports = function sessionMiddleware() { 7 | const SequelizeStore = sessionSequelize(session.Store); 8 | 9 | const store = new SequelizeStore({ 10 | db: sequelize, 11 | modelKey: 'Session', 12 | tableName: 'session', 13 | }); 14 | 15 | return session({ 16 | secret: sessionCookieSecret, 17 | cookie: { maxAge: sessionCookieMaxAge }, 18 | store, 19 | resave: false, 20 | proxy: true, 21 | saveUninitialized: false, 22 | }); 23 | }; 24 | -------------------------------------------------------------------------------- /11-schedule/src/middlewares/urlnormalize.js: -------------------------------------------------------------------------------- 1 | const { normalize } = require('path'); 2 | const { parse, format } = require('url'); 3 | 4 | module.exports = function urlnormalizeMiddleware() { 5 | return (req, res, next) => { 6 | // 解决windows、Linux系统使用normalize路径分隔符不一致的问题 7 | const pathname = normalize(req.path).split('\\').join('/'); 8 | const urlParsed = parse(req.url); 9 | 10 | let shouldRedirect = false; 11 | 12 | // 重定向不规范的路径 13 | if (req.path != pathname) { 14 | urlParsed.pathname = pathname; 15 | shouldRedirect = true; 16 | } 17 | 18 | // 执行重定向或者略过 19 | if (shouldRedirect) { 20 | res.redirect(format(urlParsed)); 21 | } else { 22 | next(); 23 | } 24 | }; 25 | }; 26 | -------------------------------------------------------------------------------- /11-schedule/src/models/config/index.js: -------------------------------------------------------------------------------- 1 | const { db } = require('../../config'); 2 | 3 | module.exports = { [process.env.NODE_ENV || 'development']: db }; 4 | -------------------------------------------------------------------------------- /11-schedule/src/models/migrate/20200725045100-create-shop.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | up: async (queryInterface, Sequelize) => { 3 | await queryInterface.createTable('shop', { 4 | id: { 5 | allowNull: false, 6 | autoIncrement: true, 7 | primaryKey: true, 8 | type: Sequelize.INTEGER, 9 | }, 10 | name: { 11 | type: Sequelize.STRING, 12 | }, 13 | created_at: { 14 | allowNull: false, 15 | type: Sequelize.DATE, 16 | }, 17 | updated_at: { 18 | allowNull: false, 19 | type: Sequelize.DATE, 20 | }, 21 | }); 22 | }, 23 | down: async (queryInterface, Sequelize) => { 24 | await queryInterface.dropTable('shop'); 25 | }, 26 | }; 27 | -------------------------------------------------------------------------------- /11-schedule/src/models/migrate/20200727025727-create-session.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | up: async (queryInterface, Sequelize) => { 3 | await queryInterface.createTable('session', { 4 | sid: { 5 | type: Sequelize.STRING(36), 6 | }, 7 | expires: { 8 | type: Sequelize.DATE, 9 | }, 10 | data: { 11 | type: Sequelize.TEXT, 12 | }, 13 | 14 | created_at: { 15 | allowNull: false, 16 | type: Sequelize.DATE, 17 | }, 18 | updated_at: { 19 | allowNull: false, 20 | type: Sequelize.DATE, 21 | }, 22 | }); 23 | }, 24 | 25 | down: async (queryInterface, Sequelize) => { 26 | queryInterface.dropTable('session'); 27 | }, 28 | }; 29 | -------------------------------------------------------------------------------- /11-schedule/src/models/migrate/20200801120113-create-schedule-lock.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | up: async (queryInterface, Sequelize) => { 3 | await queryInterface.createTable('schedule_lock', { 4 | id: { 5 | allowNull: false, 6 | autoIncrement: true, 7 | primaryKey: true, 8 | type: Sequelize.INTEGER, 9 | }, 10 | name: { 11 | type: Sequelize.STRING, 12 | }, 13 | counter: { 14 | type: Sequelize.INTEGER, 15 | }, 16 | created_at: { 17 | allowNull: false, 18 | type: Sequelize.DATE, 19 | }, 20 | updated_at: { 21 | allowNull: false, 22 | type: Sequelize.DATE, 23 | }, 24 | }); 25 | }, 26 | down: async (queryInterface, Sequelize) => { 27 | await queryInterface.dropTable('schedule_lock'); 28 | }, 29 | }; 30 | -------------------------------------------------------------------------------- /11-schedule/src/models/scheduleLock.js: -------------------------------------------------------------------------------- 1 | const { Model } = require('sequelize'); 2 | 3 | module.exports = (sequelize, DataTypes) => { 4 | class scheduleLock extends Model { 5 | /** 6 | * Helper method for defining associations. 7 | * This method is not a part of Sequelize lifecycle. 8 | * The `models/index` file will call this method automatically. 9 | */ 10 | static associate(models) { 11 | // define association here 12 | } 13 | } 14 | scheduleLock.init( 15 | { 16 | name: DataTypes.STRING, 17 | counter: DataTypes.INTEGER, 18 | }, 19 | { 20 | sequelize, 21 | modelName: 'ScheduleLock', 22 | tableName: 'schedule_lock', 23 | } 24 | ); 25 | return scheduleLock; 26 | }; 27 | -------------------------------------------------------------------------------- /11-schedule/src/models/seed/20200725050230-first-shop.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | up: async (queryInterface, Sequelize) => { 3 | await queryInterface.bulkInsert('shop', [ 4 | { name: '良品铺子', created_at: new Date(), updated_at: new Date() }, 5 | { name: '来伊份', created_at: new Date(), updated_at: new Date() }, 6 | { name: '三只松鼠', created_at: new Date(), updated_at: new Date() }, 7 | { name: '百草味', created_at: new Date(), updated_at: new Date() }, 8 | ]); 9 | }, 10 | 11 | down: async (queryInterface, Sequelize) => { 12 | await queryInterface.bulkDelete('shop', null, {}); 13 | }, 14 | }; 15 | -------------------------------------------------------------------------------- /11-schedule/src/models/shop.js: -------------------------------------------------------------------------------- 1 | const { Model } = require('sequelize'); 2 | 3 | module.exports = (sequelize, DataTypes) => { 4 | class Shop extends Model { 5 | /** 6 | * Helper method for defining associations. 7 | * This method is not a part of Sequelize lifecycle. 8 | * The `models/index` file will call this method automatically. 9 | */ 10 | static associate(models) { 11 | // define association here 12 | } 13 | } 14 | Shop.init( 15 | { 16 | name: DataTypes.STRING, 17 | }, 18 | { 19 | sequelize, 20 | modelName: 'Shop', 21 | tableName: 'shop', 22 | } 23 | ); 24 | return Shop; 25 | }; 26 | -------------------------------------------------------------------------------- /11-schedule/src/moulds/ShopForm.js: -------------------------------------------------------------------------------- 1 | const Yup = require('yup'); 2 | 3 | exports.createShopFormSchema = () => 4 | Yup.object({ 5 | name: Yup.string() 6 | .required('店铺名不能为空') 7 | .min(3, '店铺名至少 3 个字符') 8 | .max(120, '店铺名不可超过 120 字'), 9 | }); 10 | -------------------------------------------------------------------------------- /11-schedule/src/schedules/index.js: -------------------------------------------------------------------------------- 1 | const inspectAttackSchedule = require('./inspectAttack'); 2 | 3 | module.exports = async function initSchedules() { 4 | await inspectAttackSchedule(); 5 | }; 6 | -------------------------------------------------------------------------------- /11-schedule/src/services/mail.js: -------------------------------------------------------------------------------- 1 | const { promisify } = require('util'); 2 | const nodemailer = require('nodemailer'); 3 | const { mailerOptions } = require('../config'); 4 | 5 | class MailService { 6 | mailer; 7 | 8 | async init() { 9 | this.mailer = nodemailer.createTransport(mailerOptions); 10 | await promisify(this.mailer.verify)(); 11 | } 12 | 13 | async sendMail(params) { 14 | return await this.mailer.sendMail({ 15 | from: mailerOptions.auth.user, 16 | ...params, 17 | }); 18 | } 19 | } 20 | 21 | let service; 22 | module.exports = async () => { 23 | if (!service) { 24 | service = new MailService(); 25 | await service.init(); 26 | } 27 | return service; 28 | }; 29 | -------------------------------------------------------------------------------- /11-schedule/src/utils/cc.js: -------------------------------------------------------------------------------- 1 | module.exports = function callbackCatch(callback) { 2 | return async (req, res, next) => { 3 | try { 4 | await callback(req, res, next); 5 | } catch (e) { 6 | next(e); 7 | } 8 | }; 9 | }; 10 | -------------------------------------------------------------------------------- /11-schedule/src/utils/escape-html-in-object.js: -------------------------------------------------------------------------------- 1 | const escapeHtml = require('escape-html'); 2 | 3 | module.exports = function escapeHtmlInObject(input) { 4 | // 尝试将 ORM 对象转化为普通对象 5 | try { 6 | input = input.toJSON(); 7 | } catch {} 8 | 9 | // 对类型为 string 的值转义处理 10 | if (Array.isArray(input)) { 11 | return input.map(escapeHtmlInObject); 12 | } else if (typeof input == 'object') { 13 | const output = {}; 14 | Object.keys(input).forEach((k) => { 15 | output[k] = escapeHtmlInObject(input[k]); 16 | }); 17 | return output; 18 | } else if (typeof input == 'string') { 19 | return escapeHtml(input); 20 | } else { 21 | return input; 22 | } 23 | }; 24 | -------------------------------------------------------------------------------- /11-schedule/src/utils/logger.js: -------------------------------------------------------------------------------- 1 | const bunyan = require('bunyan'); 2 | const { name } = require('../../package.json'); 3 | 4 | const logger = bunyan.createLogger({ 5 | name, 6 | level: (process.env.LOG_LEVEL || 'debug').toLowerCase(), 7 | }); 8 | 9 | const LOGGING_REGEXP = /^Executed\s+\((.+)\):\s+(.+)/; 10 | 11 | function logging(logger, level = 'trace') { 12 | return (m, t) => { 13 | const o = { type: 'sql' }; 14 | 15 | const match = m.match(LOGGING_REGEXP); 16 | if (match) { 17 | o.transaction = match[1]; 18 | o.statement = match[2]; 19 | } else { 20 | o.statement = m; 21 | } 22 | 23 | if (typeof t == 'number') o.elapsedTime = t; 24 | 25 | logger[level](o); 26 | }; 27 | } 28 | 29 | module.exports = logger; 30 | 31 | Object.assign(module.exports, { logging }); 32 | -------------------------------------------------------------------------------- /12-rpc/.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /12-rpc/.env: -------------------------------------------------------------------------------- 1 | LOG_LEVEL='debug' 2 | 3 | GRPC_TRACE='all' 4 | GRPC_VERBOSITY='DEBUG' 5 | -------------------------------------------------------------------------------- /12-rpc/.env.local: -------------------------------------------------------------------------------- 1 | GITHUB_CLIENT_ID='b8ada004c6d682426cfb' 2 | GITHUB_CLIENT_SECRET='0b13f2ab5651f33f879a535fc2b316c6c731a041' 3 | 4 | MAILER_USER='ht_nse@126.com' 5 | MAILER_PASS='CAEJHSTBWNOKHRVL' 6 | -------------------------------------------------------------------------------- /12-rpc/.env.production.local: -------------------------------------------------------------------------------- 1 | GITHUB_CLIENT_ID='a8d43bbca18811dcc63a' 2 | GITHUB_CLIENT_SECRET='276b97b79c79cfef36c3fb1fceef8542f9e88aa6' 3 | -------------------------------------------------------------------------------- /12-rpc/.npmrc: -------------------------------------------------------------------------------- 1 | registry=http://r.cnpmjs.org/ 2 | node_sqlite3_binary_host_mirror=http://npm.taobao.org/mirrors/sqlite3/ 3 | -------------------------------------------------------------------------------- /12-rpc/.sequelizerc: -------------------------------------------------------------------------------- 1 | const { resolve } = require('path'); 2 | 3 | const modelsDir = resolve('src/models'); 4 | 5 | module.exports = { 6 | config: `${modelsDir}/config`, 7 | 'migrations-path': `${modelsDir}/migrate`, 8 | 'seeders-path': `${modelsDir}/seed`, 9 | 'models-path': modelsDir, 10 | }; 11 | -------------------------------------------------------------------------------- /12-rpc/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:12.18.2 2 | 3 | WORKDIR /usr/app/09-config 4 | COPY . . 5 | RUN yarn 6 | 7 | EXPOSE 9000 8 | CMD yarn start:prod 9 | -------------------------------------------------------------------------------- /12-rpc/database/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/licg9999/nodejs-server-examples/a9a3acd1f922888fa5f95175d50ed9cfac774e82/12-rpc/database/.gitkeep -------------------------------------------------------------------------------- /12-rpc/public/500.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |

系统繁忙,请您稍后再试

7 | 返回首页 8 | 9 | 10 | -------------------------------------------------------------------------------- /12-rpc/public/glue.js: -------------------------------------------------------------------------------- 1 | import './moulds/yup.js'; 2 | 3 | window.require = (k) => window[k]; 4 | window.exports = window.moulds = {}; 5 | -------------------------------------------------------------------------------- /12-rpc/public/index.css: -------------------------------------------------------------------------------- 1 | .error { 2 | color: red; 3 | font-size: 14px; 4 | } 5 | -------------------------------------------------------------------------------- /12-rpc/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /12-rpc/public/login.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 13 | 14 |
15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /12-rpc/scripts/env.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const { resolve } = require('path'); 3 | const dotenv = require('dotenv'); 4 | 5 | const dotenvTags = [ 6 | // 本地环境 7 | 'development', 8 | 9 | // 测试环境 10 | // 比如:单元测试 11 | 'test', 12 | 13 | // 部署环境 14 | // 比如:日常、预发、线上 15 | 'production', 16 | ]; 17 | 18 | if (!dotenvTags.includes(process.env.NODE_ENV)) { 19 | process.env.NODE_ENV = dotenvTags[0]; 20 | } 21 | 22 | const dotenvPath = resolve('.env'); 23 | 24 | const dotenvFiles = [ 25 | dotenvPath, 26 | `${dotenvPath}.local`, 27 | `${dotenvPath}.${process.env.NODE_ENV}`, 28 | `${dotenvPath}.${process.env.NODE_ENV}.local`, 29 | ].filter(fs.existsSync); 30 | 31 | dotenvFiles 32 | .reverse() 33 | .forEach((dotenvFile) => dotenv.config({ path: dotenvFile })); 34 | -------------------------------------------------------------------------------- /12-rpc/src/controllers/csrf.js: -------------------------------------------------------------------------------- 1 | const { Router } = require('express'); 2 | 3 | class CsrfController { 4 | async init() { 5 | const router = Router(); 6 | router.get('/script', this.getScript); 7 | return router; 8 | } 9 | 10 | getScript = (req, res) => { 11 | res.type('js'); 12 | res.send(`window.__CSRF_TOKEN__='${req.csrfToken()}';`); 13 | }; 14 | } 15 | 16 | module.exports = async () => { 17 | const c = new CsrfController(); 18 | return await c.init(); 19 | }; 20 | -------------------------------------------------------------------------------- /12-rpc/src/controllers/echo.js: -------------------------------------------------------------------------------- 1 | const { Router } = require('express'); 2 | const cc = require('../utils/cc'); 3 | const rpcEchoClient = require('../rpc/echo/client'); 4 | 5 | class EchoController { 6 | rpcEchoClient; 7 | 8 | async init() { 9 | this.rpcEchoClient = await rpcEchoClient(); 10 | 11 | const router = Router(); 12 | router.get('/', this.get); 13 | return router; 14 | } 15 | 16 | get = cc(async (req, res) => { 17 | const { s = '' } = req.query; 18 | const message = await this.rpcEchoClient.get({ s, logger: req.loggerRpc }); 19 | res.send({ success: true, message }); 20 | }); 21 | } 22 | 23 | module.exports = async () => { 24 | const c = new EchoController(); 25 | return await c.init(); 26 | }; 27 | -------------------------------------------------------------------------------- /12-rpc/src/controllers/health.js: -------------------------------------------------------------------------------- 1 | const { Router } = require('express'); 2 | 3 | class HealthController { 4 | async init() { 5 | const router = Router(); 6 | router.get('/', this.get); 7 | return router; 8 | } 9 | 10 | get = (req, res) => { 11 | res.send({}); 12 | }; 13 | } 14 | 15 | module.exports = async () => { 16 | const c = new HealthController(); 17 | return await c.init(); 18 | }; 19 | -------------------------------------------------------------------------------- /12-rpc/src/controllers/index.js: -------------------------------------------------------------------------------- 1 | const { Router } = require('express'); 2 | const shopController = require('./shop'); 3 | const chaosController = require('./chaos'); 4 | const healthController = require('./health'); 5 | const loginController = require('./login'); 6 | const csrfController = require('./csrf'); 7 | const echoController = require('./echo'); 8 | 9 | module.exports = async function initControllers() { 10 | const router = Router(); 11 | router.use('/api/shop', await shopController()); 12 | router.use('/api/chaos', await chaosController()); 13 | router.use('/api/health', await healthController()); 14 | router.use('/api/login', await loginController()); 15 | router.use('/api/csrf', await csrfController()); 16 | router.use('/api/echo', await echoController()); 17 | return router; 18 | }; 19 | -------------------------------------------------------------------------------- /12-rpc/src/controllers/login.js: -------------------------------------------------------------------------------- 1 | const { Router } = require('express'); 2 | const { passport } = require('../middlewares/auth'); 3 | const { homepagePath, loginPath } = require('../config'); 4 | 5 | class LoginController { 6 | async init() { 7 | const router = Router(); 8 | router.post('/', this.post); 9 | router.get( 10 | '/github', 11 | passport.authenticate('github', { scope: ['read:user'] }) 12 | ); 13 | router.get( 14 | '/github/callback', 15 | passport.authenticate('github', { 16 | failureRedirect: loginPath, 17 | }), 18 | this.getGithubCallback 19 | ); 20 | return router; 21 | } 22 | 23 | post = (req, res) => { 24 | req.session.logined = true; 25 | res.redirect(homepagePath); 26 | }; 27 | 28 | getGithubCallback = (req, res) => { 29 | req.session.logined = true; 30 | res.redirect(homepagePath); 31 | }; 32 | } 33 | 34 | module.exports = async () => { 35 | const c = new LoginController(); 36 | return await c.init(); 37 | }; 38 | -------------------------------------------------------------------------------- /12-rpc/src/middlewares/auth.js: -------------------------------------------------------------------------------- 1 | const passport = require('passport'); 2 | const { Strategy: GithubStrategy } = require('passport-github'); 3 | const { githubStrategyOptions } = require('../config'); 4 | 5 | const githubStrategy = new GithubStrategy( 6 | githubStrategyOptions, 7 | (accessToken, refreshToken, profile, done) => { 8 | /** 9 | * 根据 profile 查找或新建 user 信息 10 | */ 11 | const user = {}; 12 | done(null, user); 13 | } 14 | ); 15 | 16 | passport.use(githubStrategy); 17 | 18 | passport.serializeUser((user, done) => { 19 | /** 20 | * 根据 user 信息获取 userId 21 | */ 22 | const userId = '46e5'; 23 | done(null, userId); 24 | }); 25 | 26 | passport.deserializeUser((userId, done) => { 27 | /** 28 | * 根据 userId 获取 user 信息 29 | */ 30 | const user = {}; 31 | done(null, user); 32 | }); 33 | 34 | module.exports = function authMiddleware() { 35 | return [passport.initialize(), passport.session()]; 36 | }; 37 | 38 | Object.assign(module.exports, { passport }); 39 | -------------------------------------------------------------------------------- /12-rpc/src/middlewares/index.js: -------------------------------------------------------------------------------- 1 | const { Router } = require('express'); 2 | const cookieParser = require('cookie-parser'); 3 | const bodyParser = require('body-parser'); 4 | const csurf = require('csurf'); 5 | const helmet = require('helmet'); 6 | const sessionMiddleware = require('./session'); 7 | const urlnormalizeMiddleware = require('./urlnormalize'); 8 | const loginMiddleware = require('./login'); 9 | const authMiddleware = require('./auth'); 10 | const traceMiddleware = require('./trace'); 11 | const { sessionCookieSecret } = require('../config'); 12 | 13 | module.exports = async function initMiddlewares() { 14 | const router = Router(); 15 | router.use(traceMiddleware()); 16 | router.use(helmet()); 17 | router.use(urlnormalizeMiddleware()); 18 | router.use(cookieParser(sessionCookieSecret)); 19 | router.use(sessionMiddleware()); 20 | router.use(loginMiddleware()); 21 | router.use(authMiddleware()); 22 | router.use(bodyParser.urlencoded({ extended: false }), csurf()); 23 | return router; 24 | }; 25 | -------------------------------------------------------------------------------- /12-rpc/src/middlewares/login.js: -------------------------------------------------------------------------------- 1 | const { parse } = require('url'); 2 | const { homepagePath, loginPath, loginWhiteList } = require('../config'); 3 | 4 | module.exports = function loginMiddleware() { 5 | const whiteList = Object.assign({}, loginWhiteList, { 6 | [loginPath]: ['get'], 7 | }); 8 | 9 | return (req, res, next) => { 10 | const { pathname } = parse(req.url); 11 | 12 | if (req.session.logined && pathname == loginPath) { 13 | res.redirect(homepagePath); 14 | return; 15 | } 16 | 17 | if ( 18 | req.session.logined || 19 | (whiteList[pathname] && 20 | whiteList[pathname].includes(req.method.toLowerCase())) 21 | ) { 22 | next(); 23 | return; 24 | } 25 | 26 | res.redirect(loginPath); 27 | }; 28 | }; 29 | -------------------------------------------------------------------------------- /12-rpc/src/middlewares/session.js: -------------------------------------------------------------------------------- 1 | const session = require('express-session'); 2 | const sessionSequelize = require('connect-session-sequelize'); 3 | const { sequelize } = require('../models'); 4 | const { sessionCookieSecret, sessionCookieMaxAge } = require('../config'); 5 | 6 | module.exports = function sessionMiddleware() { 7 | const SequelizeStore = sessionSequelize(session.Store); 8 | 9 | const store = new SequelizeStore({ 10 | db: sequelize, 11 | modelKey: 'Session', 12 | tableName: 'session', 13 | }); 14 | 15 | return session({ 16 | secret: sessionCookieSecret, 17 | cookie: { maxAge: sessionCookieMaxAge }, 18 | store, 19 | resave: false, 20 | proxy: true, 21 | saveUninitialized: false, 22 | }); 23 | }; 24 | -------------------------------------------------------------------------------- /12-rpc/src/middlewares/urlnormalize.js: -------------------------------------------------------------------------------- 1 | const { normalize } = require('path'); 2 | const { parse, format } = require('url'); 3 | 4 | module.exports = function urlnormalizeMiddleware() { 5 | return (req, res, next) => { 6 | // 解决windows、Linux系统使用normalize路径分隔符不一致的问题 7 | const pathname = normalize(req.path).split('\\').join('/'); 8 | const urlParsed = parse(req.url); 9 | 10 | let shouldRedirect = false; 11 | 12 | // 重定向不规范的路径 13 | if (req.path != pathname) { 14 | urlParsed.pathname = pathname; 15 | shouldRedirect = true; 16 | } 17 | 18 | // 执行重定向或者略过 19 | if (shouldRedirect) { 20 | res.redirect(format(urlParsed)); 21 | } else { 22 | next(); 23 | } 24 | }; 25 | }; 26 | -------------------------------------------------------------------------------- /12-rpc/src/models/config/index.js: -------------------------------------------------------------------------------- 1 | const { db } = require('../../config'); 2 | 3 | module.exports = { [process.env.NODE_ENV || 'development']: db }; 4 | -------------------------------------------------------------------------------- /12-rpc/src/models/migrate/20200725045100-create-shop.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | up: async (queryInterface, Sequelize) => { 3 | await queryInterface.createTable('shop', { 4 | id: { 5 | allowNull: false, 6 | autoIncrement: true, 7 | primaryKey: true, 8 | type: Sequelize.INTEGER, 9 | }, 10 | name: { 11 | type: Sequelize.STRING, 12 | }, 13 | created_at: { 14 | allowNull: false, 15 | type: Sequelize.DATE, 16 | }, 17 | updated_at: { 18 | allowNull: false, 19 | type: Sequelize.DATE, 20 | }, 21 | }); 22 | }, 23 | down: async (queryInterface, Sequelize) => { 24 | await queryInterface.dropTable('shop'); 25 | }, 26 | }; 27 | -------------------------------------------------------------------------------- /12-rpc/src/models/migrate/20200727025727-create-session.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | up: async (queryInterface, Sequelize) => { 3 | await queryInterface.createTable('session', { 4 | sid: { 5 | type: Sequelize.STRING(36), 6 | }, 7 | expires: { 8 | type: Sequelize.DATE, 9 | }, 10 | data: { 11 | type: Sequelize.TEXT, 12 | }, 13 | 14 | created_at: { 15 | allowNull: false, 16 | type: Sequelize.DATE, 17 | }, 18 | updated_at: { 19 | allowNull: false, 20 | type: Sequelize.DATE, 21 | }, 22 | }); 23 | }, 24 | 25 | down: async (queryInterface, Sequelize) => { 26 | queryInterface.dropTable('session'); 27 | }, 28 | }; 29 | -------------------------------------------------------------------------------- /12-rpc/src/models/migrate/20200801120113-create-schedule-lock.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | up: async (queryInterface, Sequelize) => { 3 | await queryInterface.createTable('schedule_lock', { 4 | id: { 5 | allowNull: false, 6 | autoIncrement: true, 7 | primaryKey: true, 8 | type: Sequelize.INTEGER, 9 | }, 10 | name: { 11 | type: Sequelize.STRING, 12 | }, 13 | counter: { 14 | type: Sequelize.INTEGER, 15 | }, 16 | created_at: { 17 | allowNull: false, 18 | type: Sequelize.DATE, 19 | }, 20 | updated_at: { 21 | allowNull: false, 22 | type: Sequelize.DATE, 23 | }, 24 | }); 25 | }, 26 | down: async (queryInterface, Sequelize) => { 27 | await queryInterface.dropTable('schedule_lock'); 28 | }, 29 | }; 30 | -------------------------------------------------------------------------------- /12-rpc/src/models/scheduleLock.js: -------------------------------------------------------------------------------- 1 | const { Model } = require('sequelize'); 2 | 3 | module.exports = (sequelize, DataTypes) => { 4 | class scheduleLock extends Model { 5 | /** 6 | * Helper method for defining associations. 7 | * This method is not a part of Sequelize lifecycle. 8 | * The `models/index` file will call this method automatically. 9 | */ 10 | static associate(models) { 11 | // define association here 12 | } 13 | } 14 | scheduleLock.init( 15 | { 16 | name: DataTypes.STRING, 17 | counter: DataTypes.INTEGER, 18 | }, 19 | { 20 | sequelize, 21 | modelName: 'ScheduleLock', 22 | tableName: 'schedule_lock', 23 | } 24 | ); 25 | return scheduleLock; 26 | }; 27 | -------------------------------------------------------------------------------- /12-rpc/src/models/seed/20200725050230-first-shop.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | up: async (queryInterface, Sequelize) => { 3 | await queryInterface.bulkInsert('shop', [ 4 | { name: '良品铺子', created_at: new Date(), updated_at: new Date() }, 5 | { name: '来伊份', created_at: new Date(), updated_at: new Date() }, 6 | { name: '三只松鼠', created_at: new Date(), updated_at: new Date() }, 7 | { name: '百草味', created_at: new Date(), updated_at: new Date() }, 8 | ]); 9 | }, 10 | 11 | down: async (queryInterface, Sequelize) => { 12 | await queryInterface.bulkDelete('shop', null, {}); 13 | }, 14 | }; 15 | -------------------------------------------------------------------------------- /12-rpc/src/models/shop.js: -------------------------------------------------------------------------------- 1 | const { Model } = require('sequelize'); 2 | 3 | module.exports = (sequelize, DataTypes) => { 4 | class Shop extends Model { 5 | /** 6 | * Helper method for defining associations. 7 | * This method is not a part of Sequelize lifecycle. 8 | * The `models/index` file will call this method automatically. 9 | */ 10 | static associate(models) { 11 | // define association here 12 | } 13 | } 14 | Shop.init( 15 | { 16 | name: DataTypes.STRING, 17 | }, 18 | { 19 | sequelize, 20 | modelName: 'Shop', 21 | tableName: 'shop', 22 | } 23 | ); 24 | return Shop; 25 | }; 26 | -------------------------------------------------------------------------------- /12-rpc/src/moulds/ShopForm.js: -------------------------------------------------------------------------------- 1 | const Yup = require('yup'); 2 | 3 | exports.createShopFormSchema = () => 4 | Yup.object({ 5 | name: Yup.string() 6 | .required('店铺名不能为空') 7 | .min(3, '店铺名至少 3 个字符') 8 | .max(120, '店铺名不可超过 120 字'), 9 | }); 10 | -------------------------------------------------------------------------------- /12-rpc/src/rpc/echo/client.js: -------------------------------------------------------------------------------- 1 | const { resolve } = require('path'); 2 | const { promisify } = require('util'); 3 | const protoLoader = require('@grpc/proto-loader'); 4 | const grpc = require('@grpc/grpc-js'); 5 | const { rpc } = require('../../config'); 6 | 7 | class EchoClient { 8 | grpcClient; 9 | 10 | async init() { 11 | const grpcObject = grpc.loadPackageDefinition( 12 | await protoLoader.load(resolve(__dirname, 'def.proto')) 13 | ); 14 | 15 | this.grpcClient = new grpcObject.Echo( 16 | `${rpc.domain}:${rpc.port}`, 17 | grpc.credentials.createInsecure() 18 | ); 19 | } 20 | 21 | get = async ({ s, logger }) => { 22 | const { grpcClient } = this; 23 | 24 | const { message } = await promisify( 25 | grpcClient.get.bind(grpcClient, { message: s }) 26 | )(); 27 | 28 | logger.info('Echo/Get Invoked'); 29 | 30 | return { message }; 31 | }; 32 | } 33 | 34 | let client; 35 | module.exports = async () => { 36 | if (!client) { 37 | client = new EchoClient(); 38 | await client.init(); 39 | } 40 | return client; 41 | }; 42 | -------------------------------------------------------------------------------- /12-rpc/src/rpc/echo/def.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | service Echo { 4 | rpc Get(EchoRequest) returns (EchoResponse) {} 5 | } 6 | 7 | message EchoRequest { string message = 1; } 8 | 9 | message EchoResponse { string message = 1; } -------------------------------------------------------------------------------- /12-rpc/src/rpc/echo/server.js: -------------------------------------------------------------------------------- 1 | const { resolve } = require('path'); 2 | const { callbackify } = require('util'); 3 | const protoLoader = require('@grpc/proto-loader'); 4 | const grpc = require('@grpc/grpc-js'); 5 | 6 | class EchoServer { 7 | grpcServer; 8 | 9 | async init() { 10 | const grpcObject = grpc.loadPackageDefinition( 11 | await protoLoader.load(resolve(__dirname, 'def.proto')) 12 | ); 13 | 14 | this.grpcServer.addService(grpcObject.Echo.service, this); 15 | } 16 | 17 | get = callbackify(async (call) => { 18 | const { message } = call.request; 19 | return { message }; 20 | }); 21 | } 22 | 23 | let server; 24 | module.exports = async (grpcServer) => { 25 | if (!server) { 26 | server = new EchoServer(); 27 | Object.assign(server, { grpcServer }); 28 | await server.init(); 29 | } 30 | return server; 31 | }; 32 | -------------------------------------------------------------------------------- /12-rpc/src/rpc/index.js: -------------------------------------------------------------------------------- 1 | const { promisify } = require('util'); 2 | const grpc = require('@grpc/grpc-js'); 3 | const { rpc } = require('../config'); 4 | const logger = require('../utils/logger'); 5 | const echoClient = require('./echo/client'); 6 | const echoServer = require('./echo/server'); 7 | const { grpcLogger } = logger; 8 | 9 | module.exports = async function initRpc() { 10 | grpc.setLogger(grpcLogger(logger.child({ type: 'rpc' }), 'debug')); 11 | 12 | // init rpc servers 13 | const grpcServer = new grpc.Server(); 14 | await echoServer(grpcServer); 15 | 16 | await promisify(grpcServer.bindAsync.bind(grpcServer))( 17 | `0.0.0.0:${rpc.port}`, 18 | grpc.ServerCredentials.createInsecure() 19 | ); 20 | grpcServer.start(); 21 | 22 | // init rpc clients 23 | await echoClient(); 24 | }; 25 | -------------------------------------------------------------------------------- /12-rpc/src/schedules/index.js: -------------------------------------------------------------------------------- 1 | const inspectAttackSchedule = require('./inspectAttack'); 2 | 3 | module.exports = async function initSchedules() { 4 | await inspectAttackSchedule(); 5 | }; 6 | -------------------------------------------------------------------------------- /12-rpc/src/services/mail.js: -------------------------------------------------------------------------------- 1 | const { promisify } = require('util'); 2 | const nodemailer = require('nodemailer'); 3 | const { mailerOptions } = require('../config'); 4 | 5 | class MailService { 6 | mailer; 7 | 8 | async init() { 9 | this.mailer = nodemailer.createTransport(mailerOptions); 10 | await promisify(this.mailer.verify)(); 11 | } 12 | 13 | async sendMail(params) { 14 | return await this.mailer.sendMail({ 15 | from: mailerOptions.auth.user, 16 | ...params, 17 | }); 18 | } 19 | } 20 | 21 | let service; 22 | module.exports = async () => { 23 | if (!service) { 24 | service = new MailService(); 25 | await service.init(); 26 | } 27 | return service; 28 | }; 29 | -------------------------------------------------------------------------------- /12-rpc/src/utils/cc.js: -------------------------------------------------------------------------------- 1 | module.exports = function callbackCatch(callback) { 2 | return async (req, res, next) => { 3 | try { 4 | await callback(req, res, next); 5 | } catch (e) { 6 | next(e); 7 | } 8 | }; 9 | }; 10 | -------------------------------------------------------------------------------- /12-rpc/src/utils/escape-html-in-object.js: -------------------------------------------------------------------------------- 1 | const escapeHtml = require('escape-html'); 2 | 3 | module.exports = function escapeHtmlInObject(input) { 4 | // 尝试将 ORM 对象转化为普通对象 5 | try { 6 | input = input.toJSON(); 7 | } catch {} 8 | 9 | // 对类型为 string 的值转义处理 10 | if (Array.isArray(input)) { 11 | return input.map(escapeHtmlInObject); 12 | } else if (typeof input == 'object') { 13 | const output = {}; 14 | Object.keys(input).forEach((k) => { 15 | output[k] = escapeHtmlInObject(input[k]); 16 | }); 17 | return output; 18 | } else if (typeof input == 'string') { 19 | return escapeHtml(input); 20 | } else { 21 | return input; 22 | } 23 | }; 24 | -------------------------------------------------------------------------------- /13-debugging-and-profiling/.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /13-debugging-and-profiling/.env: -------------------------------------------------------------------------------- 1 | LOG_LEVEL='debug' 2 | 3 | GRPC_TRACE='all' 4 | GRPC_VERBOSITY='DEBUG' 5 | 6 | WITH_REDIS=1 7 | 8 | CLUSTERING=2 9 | -------------------------------------------------------------------------------- /13-debugging-and-profiling/.env.local: -------------------------------------------------------------------------------- 1 | GITHUB_CLIENT_ID='b8ada004c6d682426cfb' 2 | GITHUB_CLIENT_SECRET='0b13f2ab5651f33f879a535fc2b316c6c731a041' 3 | 4 | MAILER_USER='ht_nse@126.com' 5 | MAILER_PASS='CAEJHSTBWNOKHRVL' 6 | -------------------------------------------------------------------------------- /13-debugging-and-profiling/.env.production.local: -------------------------------------------------------------------------------- 1 | GITHUB_CLIENT_ID='a8d43bbca18811dcc63a' 2 | GITHUB_CLIENT_SECRET='276b97b79c79cfef36c3fb1fceef8542f9e88aa6' 3 | -------------------------------------------------------------------------------- /13-debugging-and-profiling/.npmrc: -------------------------------------------------------------------------------- 1 | registry=http://r.cnpmjs.org/ 2 | node_sqlite3_binary_host_mirror=http://npm.taobao.org/mirrors/sqlite3/ 3 | -------------------------------------------------------------------------------- /13-debugging-and-profiling/.sequelizerc: -------------------------------------------------------------------------------- 1 | const { resolve } = require('path'); 2 | 3 | const modelsDir = resolve('src/models'); 4 | 5 | module.exports = { 6 | config: `${modelsDir}/config`, 7 | 'migrations-path': `${modelsDir}/migrate`, 8 | 'seeders-path': `${modelsDir}/seed`, 9 | 'models-path': modelsDir, 10 | }; 11 | -------------------------------------------------------------------------------- /13-debugging-and-profiling/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "Attach by Process ID", 9 | "processId": "${command:PickProcess}", 10 | "request": "attach", 11 | "skipFiles": ["/**"], 12 | "type": "pwa-node" 13 | } 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /13-debugging-and-profiling/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:12.18.2 2 | 3 | WORKDIR /usr/app/09-config 4 | COPY . . 5 | RUN yarn 6 | 7 | EXPOSE 9000 8 | CMD yarn start:prod 9 | -------------------------------------------------------------------------------- /13-debugging-and-profiling/database/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/licg9999/nodejs-server-examples/a9a3acd1f922888fa5f95175d50ed9cfac774e82/13-debugging-and-profiling/database/.gitkeep -------------------------------------------------------------------------------- /13-debugging-and-profiling/public/500.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |

系统繁忙,请您稍后再试

7 | 返回首页 8 | 9 | 10 | -------------------------------------------------------------------------------- /13-debugging-and-profiling/public/glue.js: -------------------------------------------------------------------------------- 1 | import './moulds/yup.js'; 2 | 3 | window.require = (k) => window[k]; 4 | window.exports = window.moulds = {}; 5 | -------------------------------------------------------------------------------- /13-debugging-and-profiling/public/index.css: -------------------------------------------------------------------------------- 1 | .error { 2 | color: red; 3 | font-size: 14px; 4 | } 5 | -------------------------------------------------------------------------------- /13-debugging-and-profiling/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /13-debugging-and-profiling/public/login.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 13 | 14 |
15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /13-debugging-and-profiling/scripts/env.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const { resolve } = require('path'); 3 | const dotenv = require('dotenv'); 4 | 5 | const dotenvTags = [ 6 | // 本地环境 7 | 'development', 8 | 9 | // 测试环境 10 | // 比如:单元测试 11 | 'test', 12 | 13 | // 部署环境 14 | // 比如:日常、预发、线上 15 | 'production', 16 | ]; 17 | 18 | if (!dotenvTags.includes(process.env.NODE_ENV)) { 19 | process.env.NODE_ENV = dotenvTags[0]; 20 | } 21 | 22 | const dotenvPath = resolve('.env'); 23 | 24 | const dotenvFiles = [ 25 | dotenvPath, 26 | `${dotenvPath}.local`, 27 | `${dotenvPath}.${process.env.NODE_ENV}`, 28 | `${dotenvPath}.${process.env.NODE_ENV}.local`, 29 | ].filter(fs.existsSync); 30 | 31 | dotenvFiles 32 | .reverse() 33 | .forEach((dotenvFile) => dotenv.config({ path: dotenvFile })); 34 | -------------------------------------------------------------------------------- /13-debugging-and-profiling/src/controllers/csrf.js: -------------------------------------------------------------------------------- 1 | const { Router } = require('express'); 2 | 3 | class CsrfController { 4 | async init() { 5 | const router = Router(); 6 | router.get('/script', this.getScript); 7 | return router; 8 | } 9 | 10 | getScript = (req, res) => { 11 | res.type('js'); 12 | res.send(`window.__CSRF_TOKEN__='${req.csrfToken()}';`); 13 | }; 14 | } 15 | 16 | module.exports = async () => { 17 | const c = new CsrfController(); 18 | return await c.init(); 19 | }; 20 | -------------------------------------------------------------------------------- /13-debugging-and-profiling/src/controllers/echo.js: -------------------------------------------------------------------------------- 1 | const { Router } = require('express'); 2 | const cc = require('../utils/cc'); 3 | const rpcEchoClient = require('../rpc/echo/client'); 4 | 5 | class EchoController { 6 | rpcEchoClient; 7 | 8 | async init() { 9 | this.rpcEchoClient = await rpcEchoClient(); 10 | 11 | const router = Router(); 12 | router.get('/', this.get); 13 | return router; 14 | } 15 | 16 | get = cc(async (req, res) => { 17 | const { s = '' } = req.query; 18 | const message = await this.rpcEchoClient.get({ s, logger: req.loggerRpc }); 19 | res.send({ success: true, message }); 20 | }); 21 | } 22 | 23 | module.exports = async () => { 24 | const c = new EchoController(); 25 | return await c.init(); 26 | }; 27 | -------------------------------------------------------------------------------- /13-debugging-and-profiling/src/controllers/health.js: -------------------------------------------------------------------------------- 1 | const { Router } = require('express'); 2 | 3 | class HealthController { 4 | async init() { 5 | const router = Router(); 6 | router.get('/', this.get); 7 | return router; 8 | } 9 | 10 | get = (req, res) => { 11 | res.send({}); 12 | }; 13 | } 14 | 15 | module.exports = async () => { 16 | const c = new HealthController(); 17 | return await c.init(); 18 | }; 19 | -------------------------------------------------------------------------------- /13-debugging-and-profiling/src/controllers/index.js: -------------------------------------------------------------------------------- 1 | const { Router } = require('express'); 2 | const shopController = require('./shop'); 3 | const chaosController = require('./chaos'); 4 | const healthController = require('./health'); 5 | const loginController = require('./login'); 6 | const csrfController = require('./csrf'); 7 | const echoController = require('./echo'); 8 | 9 | module.exports = async function initControllers() { 10 | const router = Router(); 11 | router.use('/api/shop', await shopController()); 12 | router.use('/api/chaos', await chaosController()); 13 | router.use('/api/health', await healthController()); 14 | router.use('/api/login', await loginController()); 15 | router.use('/api/csrf', await csrfController()); 16 | router.use('/api/echo', await echoController()); 17 | return router; 18 | }; 19 | -------------------------------------------------------------------------------- /13-debugging-and-profiling/src/controllers/login.js: -------------------------------------------------------------------------------- 1 | const { Router } = require('express'); 2 | const { passport } = require('../middlewares/auth'); 3 | const { homepagePath, loginPath } = require('../config'); 4 | 5 | class LoginController { 6 | async init() { 7 | const router = Router(); 8 | router.post('/', this.post); 9 | router.get( 10 | '/github', 11 | passport.authenticate('github', { scope: ['read:user'] }) 12 | ); 13 | router.get( 14 | '/github/callback', 15 | passport.authenticate('github', { 16 | failureRedirect: loginPath, 17 | }), 18 | this.getGithubCallback 19 | ); 20 | return router; 21 | } 22 | 23 | post = (req, res) => { 24 | req.session.logined = true; 25 | res.redirect(homepagePath); 26 | }; 27 | 28 | getGithubCallback = (req, res) => { 29 | req.session.logined = true; 30 | res.redirect(homepagePath); 31 | }; 32 | } 33 | 34 | module.exports = async () => { 35 | const c = new LoginController(); 36 | return await c.init(); 37 | }; 38 | -------------------------------------------------------------------------------- /13-debugging-and-profiling/src/middlewares/auth.js: -------------------------------------------------------------------------------- 1 | const passport = require('passport'); 2 | const { Strategy: GithubStrategy } = require('passport-github'); 3 | const { githubStrategyOptions } = require('../config'); 4 | 5 | const githubStrategy = new GithubStrategy( 6 | githubStrategyOptions, 7 | (accessToken, refreshToken, profile, done) => { 8 | /** 9 | * 根据 profile 查找或新建 user 信息 10 | */ 11 | const user = {}; 12 | done(null, user); 13 | } 14 | ); 15 | 16 | passport.use(githubStrategy); 17 | 18 | passport.serializeUser((user, done) => { 19 | /** 20 | * 根据 user 信息获取 userId 21 | */ 22 | const userId = '46e5'; 23 | done(null, userId); 24 | }); 25 | 26 | passport.deserializeUser((userId, done) => { 27 | /** 28 | * 根据 userId 获取 user 信息 29 | */ 30 | const user = {}; 31 | done(null, user); 32 | }); 33 | 34 | module.exports = function authMiddleware() { 35 | return [passport.initialize(), passport.session()]; 36 | }; 37 | 38 | Object.assign(module.exports, { passport }); 39 | -------------------------------------------------------------------------------- /13-debugging-and-profiling/src/middlewares/index.js: -------------------------------------------------------------------------------- 1 | const { Router } = require('express'); 2 | const cookieParser = require('cookie-parser'); 3 | const bodyParser = require('body-parser'); 4 | const csurf = require('csurf'); 5 | const helmet = require('helmet'); 6 | const sessionMiddleware = require('./session'); 7 | const urlnormalizeMiddleware = require('./urlnormalize'); 8 | const loginMiddleware = require('./login'); 9 | const authMiddleware = require('./auth'); 10 | const traceMiddleware = require('./trace'); 11 | const { sessionCookieSecret } = require('../config'); 12 | 13 | module.exports = async function initMiddlewares() { 14 | const router = Router(); 15 | router.use(traceMiddleware()); 16 | router.use(helmet()); 17 | router.use(urlnormalizeMiddleware()); 18 | router.use(cookieParser(sessionCookieSecret)); 19 | router.use(sessionMiddleware()); 20 | router.use(loginMiddleware()); 21 | router.use(authMiddleware()); 22 | router.use(bodyParser.urlencoded({ extended: false }), csurf()); 23 | return router; 24 | }; 25 | -------------------------------------------------------------------------------- /13-debugging-and-profiling/src/middlewares/login.js: -------------------------------------------------------------------------------- 1 | const { parse } = require('url'); 2 | const { homepagePath, loginPath, loginWhiteList } = require('../config'); 3 | 4 | module.exports = function loginMiddleware() { 5 | const whiteList = Object.assign({}, loginWhiteList, { 6 | [loginPath]: ['get'], 7 | }); 8 | 9 | return (req, res, next) => { 10 | const { pathname } = parse(req.url); 11 | 12 | if (req.session.logined && pathname == loginPath) { 13 | res.redirect(homepagePath); 14 | return; 15 | } 16 | 17 | if ( 18 | req.session.logined || 19 | (whiteList[pathname] && 20 | whiteList[pathname].includes(req.method.toLowerCase())) 21 | ) { 22 | next(); 23 | return; 24 | } 25 | 26 | res.redirect(loginPath); 27 | }; 28 | }; 29 | -------------------------------------------------------------------------------- /13-debugging-and-profiling/src/middlewares/urlnormalize.js: -------------------------------------------------------------------------------- 1 | const { normalize } = require('path'); 2 | const { parse, format } = require('url'); 3 | 4 | module.exports = function urlnormalizeMiddleware() { 5 | return (req, res, next) => { 6 | // 解决windows、Linux系统使用normalize路径分隔符不一致的问题 7 | const pathname = normalize(req.path).split('\\').join('/'); 8 | const urlParsed = parse(req.url); 9 | 10 | let shouldRedirect = false; 11 | 12 | // 重定向不规范的路径 13 | if (req.path != pathname) { 14 | urlParsed.pathname = pathname; 15 | shouldRedirect = true; 16 | } 17 | 18 | // 执行重定向或者略过 19 | if (shouldRedirect) { 20 | res.redirect(format(urlParsed)); 21 | } else { 22 | next(); 23 | } 24 | }; 25 | }; 26 | -------------------------------------------------------------------------------- /13-debugging-and-profiling/src/models/config/index.js: -------------------------------------------------------------------------------- 1 | const { db } = require('../../config'); 2 | 3 | module.exports = { [process.env.NODE_ENV || 'development']: db }; 4 | -------------------------------------------------------------------------------- /13-debugging-and-profiling/src/models/migrate/20200725045100-create-shop.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | up: async (queryInterface, Sequelize) => { 3 | await queryInterface.createTable('shop', { 4 | id: { 5 | allowNull: false, 6 | autoIncrement: true, 7 | primaryKey: true, 8 | type: Sequelize.INTEGER, 9 | }, 10 | name: { 11 | type: Sequelize.STRING, 12 | }, 13 | created_at: { 14 | allowNull: false, 15 | type: Sequelize.DATE, 16 | }, 17 | updated_at: { 18 | allowNull: false, 19 | type: Sequelize.DATE, 20 | }, 21 | }); 22 | }, 23 | down: async (queryInterface, Sequelize) => { 24 | await queryInterface.dropTable('shop'); 25 | }, 26 | }; 27 | -------------------------------------------------------------------------------- /13-debugging-and-profiling/src/models/migrate/20200727025727-create-session.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | up: async (queryInterface, Sequelize) => { 3 | await queryInterface.createTable('session', { 4 | sid: { 5 | type: Sequelize.STRING(36), 6 | }, 7 | expires: { 8 | type: Sequelize.DATE, 9 | }, 10 | data: { 11 | type: Sequelize.TEXT, 12 | }, 13 | 14 | created_at: { 15 | allowNull: false, 16 | type: Sequelize.DATE, 17 | }, 18 | updated_at: { 19 | allowNull: false, 20 | type: Sequelize.DATE, 21 | }, 22 | }); 23 | }, 24 | 25 | down: async (queryInterface, Sequelize) => { 26 | queryInterface.dropTable('session'); 27 | }, 28 | }; 29 | -------------------------------------------------------------------------------- /13-debugging-and-profiling/src/models/migrate/20200801120113-create-schedule-lock.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | up: async (queryInterface, Sequelize) => { 3 | await queryInterface.createTable('schedule_lock', { 4 | id: { 5 | allowNull: false, 6 | autoIncrement: true, 7 | primaryKey: true, 8 | type: Sequelize.INTEGER, 9 | }, 10 | name: { 11 | type: Sequelize.STRING, 12 | }, 13 | counter: { 14 | type: Sequelize.INTEGER, 15 | }, 16 | created_at: { 17 | allowNull: false, 18 | type: Sequelize.DATE, 19 | }, 20 | updated_at: { 21 | allowNull: false, 22 | type: Sequelize.DATE, 23 | }, 24 | }); 25 | }, 26 | down: async (queryInterface, Sequelize) => { 27 | await queryInterface.dropTable('schedule_lock'); 28 | }, 29 | }; 30 | -------------------------------------------------------------------------------- /13-debugging-and-profiling/src/models/scheduleLock.js: -------------------------------------------------------------------------------- 1 | const { Model } = require('sequelize'); 2 | 3 | module.exports = (sequelize, DataTypes) => { 4 | class scheduleLock extends Model { 5 | /** 6 | * Helper method for defining associations. 7 | * This method is not a part of Sequelize lifecycle. 8 | * The `models/index` file will call this method automatically. 9 | */ 10 | static associate(models) { 11 | // define association here 12 | } 13 | } 14 | scheduleLock.init( 15 | { 16 | name: DataTypes.STRING, 17 | counter: DataTypes.INTEGER, 18 | }, 19 | { 20 | sequelize, 21 | modelName: 'ScheduleLock', 22 | tableName: 'schedule_lock', 23 | } 24 | ); 25 | return scheduleLock; 26 | }; 27 | -------------------------------------------------------------------------------- /13-debugging-and-profiling/src/models/seed/20200725050230-first-shop.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | up: async (queryInterface, Sequelize) => { 3 | await queryInterface.bulkInsert('shop', [ 4 | { name: '良品铺子', created_at: new Date(), updated_at: new Date() }, 5 | { name: '来伊份', created_at: new Date(), updated_at: new Date() }, 6 | { name: '三只松鼠', created_at: new Date(), updated_at: new Date() }, 7 | { name: '百草味', created_at: new Date(), updated_at: new Date() }, 8 | ]); 9 | }, 10 | 11 | down: async (queryInterface, Sequelize) => { 12 | await queryInterface.bulkDelete('shop', null, {}); 13 | }, 14 | }; 15 | -------------------------------------------------------------------------------- /13-debugging-and-profiling/src/models/shop.js: -------------------------------------------------------------------------------- 1 | const { Model } = require('sequelize'); 2 | 3 | module.exports = (sequelize, DataTypes) => { 4 | class Shop extends Model { 5 | /** 6 | * Helper method for defining associations. 7 | * This method is not a part of Sequelize lifecycle. 8 | * The `models/index` file will call this method automatically. 9 | */ 10 | static associate(models) { 11 | // define association here 12 | } 13 | } 14 | Shop.init( 15 | { 16 | name: DataTypes.STRING, 17 | }, 18 | { 19 | sequelize, 20 | modelName: 'Shop', 21 | tableName: 'shop', 22 | } 23 | ); 24 | return Shop; 25 | }; 26 | -------------------------------------------------------------------------------- /13-debugging-and-profiling/src/moulds/ShopForm.js: -------------------------------------------------------------------------------- 1 | const Yup = require('yup'); 2 | 3 | exports.createShopFormSchema = () => 4 | Yup.object({ 5 | name: Yup.string() 6 | .required('店铺名不能为空') 7 | .min(3, '店铺名至少 3 个字符') 8 | .max(120, '店铺名不可超过 120 字'), 9 | }); 10 | -------------------------------------------------------------------------------- /13-debugging-and-profiling/src/rpc/echo/client.js: -------------------------------------------------------------------------------- 1 | const { resolve } = require('path'); 2 | const { promisify } = require('util'); 3 | const protoLoader = require('@grpc/proto-loader'); 4 | const grpc = require('@grpc/grpc-js'); 5 | const { rpc } = require('../../config'); 6 | 7 | class EchoClient { 8 | grpcClient; 9 | 10 | async init() { 11 | const grpcObject = grpc.loadPackageDefinition( 12 | await protoLoader.load(resolve(__dirname, 'def.proto')) 13 | ); 14 | 15 | this.grpcClient = new grpcObject.Echo( 16 | `${rpc.domain}:${rpc.port}`, 17 | grpc.credentials.createInsecure() 18 | ); 19 | } 20 | 21 | get = async ({ s, logger }) => { 22 | const { grpcClient } = this; 23 | 24 | const { message } = await promisify( 25 | grpcClient.get.bind(grpcClient, { message: s }) 26 | )(); 27 | 28 | logger.info('Echo/Get Invoked'); 29 | 30 | return { message }; 31 | }; 32 | } 33 | 34 | let client; 35 | module.exports = async () => { 36 | if (!client) { 37 | client = new EchoClient(); 38 | await client.init(); 39 | } 40 | return client; 41 | }; 42 | -------------------------------------------------------------------------------- /13-debugging-and-profiling/src/rpc/echo/def.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | service Echo { 4 | rpc Get(EchoRequest) returns (EchoResponse) {} 5 | } 6 | 7 | message EchoRequest { string message = 1; } 8 | 9 | message EchoResponse { string message = 1; } -------------------------------------------------------------------------------- /13-debugging-and-profiling/src/rpc/echo/server.js: -------------------------------------------------------------------------------- 1 | const { resolve } = require('path'); 2 | const { callbackify } = require('util'); 3 | const protoLoader = require('@grpc/proto-loader'); 4 | const grpc = require('@grpc/grpc-js'); 5 | 6 | class EchoServer { 7 | grpcServer; 8 | 9 | async init() { 10 | const grpcObject = grpc.loadPackageDefinition( 11 | await protoLoader.load(resolve(__dirname, 'def.proto')) 12 | ); 13 | 14 | this.grpcServer.addService(grpcObject.Echo.service, this); 15 | } 16 | 17 | get = callbackify(async (call) => { 18 | const { message } = call.request; 19 | return { message }; 20 | }); 21 | } 22 | 23 | let server; 24 | module.exports = async (grpcServer) => { 25 | if (!server) { 26 | server = new EchoServer(); 27 | Object.assign(server, { grpcServer }); 28 | await server.init(); 29 | } 30 | return server; 31 | }; 32 | -------------------------------------------------------------------------------- /13-debugging-and-profiling/src/rpc/index.js: -------------------------------------------------------------------------------- 1 | const { promisify } = require('util'); 2 | const grpc = require('@grpc/grpc-js'); 3 | const { rpc } = require('../config'); 4 | const logger = require('../utils/logger'); 5 | const echoClient = require('./echo/client'); 6 | const echoServer = require('./echo/server'); 7 | const { grpcLogger } = logger; 8 | 9 | module.exports = async function initRpc() { 10 | grpc.setLogger(grpcLogger(logger.child({ type: 'rpc' }), 'debug')); 11 | 12 | // init rpc servers 13 | const grpcServer = new grpc.Server(); 14 | await echoServer(grpcServer); 15 | 16 | await promisify(grpcServer.bindAsync.bind(grpcServer))( 17 | `0.0.0.0:${rpc.port}`, 18 | grpc.ServerCredentials.createInsecure() 19 | ); 20 | grpcServer.start(); 21 | 22 | // init rpc clients 23 | await echoClient(); 24 | }; 25 | -------------------------------------------------------------------------------- /13-debugging-and-profiling/src/schedules/index.js: -------------------------------------------------------------------------------- 1 | const inspectAttackSchedule = require('./inspectAttack'); 2 | 3 | module.exports = async function initSchedules() { 4 | await inspectAttackSchedule(); 5 | }; 6 | -------------------------------------------------------------------------------- /13-debugging-and-profiling/src/services/mail.js: -------------------------------------------------------------------------------- 1 | const { promisify } = require('util'); 2 | const nodemailer = require('nodemailer'); 3 | const { mailerOptions } = require('../config'); 4 | 5 | class MailService { 6 | mailer; 7 | 8 | async init() { 9 | this.mailer = nodemailer.createTransport(mailerOptions); 10 | await promisify(this.mailer.verify)(); 11 | } 12 | 13 | async sendMail(params) { 14 | return await this.mailer.sendMail({ 15 | from: mailerOptions.auth.user, 16 | ...params, 17 | }); 18 | } 19 | } 20 | 21 | let service; 22 | module.exports = async () => { 23 | if (!service) { 24 | service = new MailService(); 25 | await service.init(); 26 | } 27 | return service; 28 | }; 29 | -------------------------------------------------------------------------------- /13-debugging-and-profiling/src/utils/cc.js: -------------------------------------------------------------------------------- 1 | module.exports = function callbackCatch(callback) { 2 | return async (req, res, next) => { 3 | try { 4 | await callback(req, res, next); 5 | } catch (e) { 6 | next(e); 7 | } 8 | }; 9 | }; 10 | -------------------------------------------------------------------------------- /13-debugging-and-profiling/src/utils/escape-html-in-object.js: -------------------------------------------------------------------------------- 1 | const escapeHtml = require('escape-html'); 2 | 3 | module.exports = function escapeHtmlInObject(input) { 4 | // 尝试将 ORM 对象转化为普通对象 5 | try { 6 | input = input.toJSON(); 7 | } catch {} 8 | 9 | // 对类型为 string 的值转义处理 10 | if (Array.isArray(input)) { 11 | return input.map(escapeHtmlInObject); 12 | } else if (typeof input == 'object') { 13 | const output = {}; 14 | Object.keys(input).forEach((k) => { 15 | output[k] = escapeHtmlInObject(input[k]); 16 | }); 17 | return output; 18 | } else if (typeof input == 'string') { 19 | return escapeHtml(input); 20 | } else { 21 | return input; 22 | } 23 | }; 24 | -------------------------------------------------------------------------------- /14-testing/.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /14-testing/.env: -------------------------------------------------------------------------------- 1 | LOG_LEVEL='debug' 2 | 3 | GRPC_TRACE='all' 4 | GRPC_VERBOSITY='DEBUG' 5 | 6 | WITH_REDIS=1 7 | 8 | CLUSTERING=2 9 | -------------------------------------------------------------------------------- /14-testing/.env.local: -------------------------------------------------------------------------------- 1 | GITHUB_CLIENT_ID='b8ada004c6d682426cfb' 2 | GITHUB_CLIENT_SECRET='0b13f2ab5651f33f879a535fc2b316c6c731a041' 3 | 4 | MAILER_USER='ht_nse@126.com' 5 | MAILER_PASS='CAEJHSTBWNOKHRVL' 6 | -------------------------------------------------------------------------------- /14-testing/.env.production.local: -------------------------------------------------------------------------------- 1 | GITHUB_CLIENT_ID='a8d43bbca18811dcc63a' 2 | GITHUB_CLIENT_SECRET='276b97b79c79cfef36c3fb1fceef8542f9e88aa6' 3 | -------------------------------------------------------------------------------- /14-testing/.npmrc: -------------------------------------------------------------------------------- 1 | registry=http://r.cnpmjs.org/ 2 | node_sqlite3_binary_host_mirror=http://npm.taobao.org/mirrors/sqlite3/ 3 | -------------------------------------------------------------------------------- /14-testing/.sequelizerc: -------------------------------------------------------------------------------- 1 | const { resolve } = require('path'); 2 | 3 | const modelsDir = resolve('src/models'); 4 | 5 | module.exports = { 6 | config: `${modelsDir}/config`, 7 | 'migrations-path': `${modelsDir}/migrate`, 8 | 'seeders-path': `${modelsDir}/seed`, 9 | 'models-path': modelsDir, 10 | }; 11 | -------------------------------------------------------------------------------- /14-testing/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "Attach by Process ID", 9 | "processId": "${command:PickProcess}", 10 | "request": "attach", 11 | "skipFiles": ["/**"], 12 | "type": "pwa-node" 13 | } 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /14-testing/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:12.18.2 2 | 3 | WORKDIR /usr/app/09-config 4 | COPY . . 5 | RUN yarn 6 | 7 | EXPOSE 9000 8 | CMD yarn start:prod 9 | -------------------------------------------------------------------------------- /14-testing/database/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/licg9999/nodejs-server-examples/a9a3acd1f922888fa5f95175d50ed9cfac774e82/14-testing/database/.gitkeep -------------------------------------------------------------------------------- /14-testing/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | globalSetup: '/tests/globalSetup.js', 3 | }; 4 | -------------------------------------------------------------------------------- /14-testing/public/500.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |

系统繁忙,请您稍后再试

7 | 返回首页 8 | 9 | 10 | -------------------------------------------------------------------------------- /14-testing/public/glue.js: -------------------------------------------------------------------------------- 1 | import './moulds/yup.js'; 2 | 3 | window.require = (k) => window[k]; 4 | window.exports = window.moulds = {}; 5 | -------------------------------------------------------------------------------- /14-testing/public/index.css: -------------------------------------------------------------------------------- 1 | .error { 2 | color: red; 3 | font-size: 14px; 4 | } 5 | -------------------------------------------------------------------------------- /14-testing/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /14-testing/public/login.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 13 | 14 |
15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /14-testing/scripts/env.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const { resolve } = require('path'); 3 | const dotenv = require('dotenv'); 4 | 5 | const dotenvTags = [ 6 | // 本地环境 7 | 'development', 8 | 9 | // 测试环境 10 | // 比如:单元测试 11 | 'test', 12 | 13 | // 部署环境 14 | // 比如:日常、预发、线上 15 | 'production', 16 | ]; 17 | 18 | if (!dotenvTags.includes(process.env.NODE_ENV)) { 19 | process.env.NODE_ENV = dotenvTags[0]; 20 | } 21 | 22 | const dotenvPath = resolve('.env'); 23 | 24 | const dotenvFiles = [ 25 | dotenvPath, 26 | `${dotenvPath}.local`, 27 | `${dotenvPath}.${process.env.NODE_ENV}`, 28 | `${dotenvPath}.${process.env.NODE_ENV}.local`, 29 | ].filter(fs.existsSync); 30 | 31 | dotenvFiles 32 | .reverse() 33 | .forEach((dotenvFile) => dotenv.config({ path: dotenvFile })); 34 | -------------------------------------------------------------------------------- /14-testing/src/controllers/csrf.js: -------------------------------------------------------------------------------- 1 | const { Router } = require('express'); 2 | 3 | class CsrfController { 4 | async init() { 5 | const router = Router(); 6 | router.get('/script', this.getScript); 7 | return router; 8 | } 9 | 10 | getScript = (req, res) => { 11 | res.type('js'); 12 | res.send(`window.__CSRF_TOKEN__='${req.csrfToken()}';`); 13 | }; 14 | } 15 | 16 | module.exports = async () => { 17 | const c = new CsrfController(); 18 | return await c.init(); 19 | }; 20 | -------------------------------------------------------------------------------- /14-testing/src/controllers/echo.js: -------------------------------------------------------------------------------- 1 | const { Router } = require('express'); 2 | const cc = require('../utils/cc'); 3 | const rpcEchoClient = require('../rpc/echo/client'); 4 | 5 | class EchoController { 6 | rpcEchoClient; 7 | 8 | async init() { 9 | this.rpcEchoClient = await rpcEchoClient(); 10 | 11 | const router = Router(); 12 | router.get('/', this.get); 13 | return router; 14 | } 15 | 16 | get = cc(async (req, res) => { 17 | const { s = '' } = req.query; 18 | const message = await this.rpcEchoClient.get({ s, logger: req.loggerRpc }); 19 | res.send({ success: true, message }); 20 | }); 21 | } 22 | 23 | module.exports = async () => { 24 | const c = new EchoController(); 25 | return await c.init(); 26 | }; 27 | -------------------------------------------------------------------------------- /14-testing/src/controllers/health.js: -------------------------------------------------------------------------------- 1 | const { Router } = require('express'); 2 | 3 | class HealthController { 4 | async init() { 5 | const router = Router(); 6 | router.get('/', this.get); 7 | return router; 8 | } 9 | 10 | get = (req, res) => { 11 | res.send({}); 12 | }; 13 | } 14 | 15 | module.exports = async () => { 16 | const c = new HealthController(); 17 | return await c.init(); 18 | }; 19 | -------------------------------------------------------------------------------- /14-testing/src/controllers/index.js: -------------------------------------------------------------------------------- 1 | const { Router } = require('express'); 2 | const shopController = require('./shop'); 3 | const chaosController = require('./chaos'); 4 | const healthController = require('./health'); 5 | const loginController = require('./login'); 6 | const csrfController = require('./csrf'); 7 | const echoController = require('./echo'); 8 | 9 | module.exports = async function initControllers() { 10 | const router = Router(); 11 | router.use('/api/shop', await shopController()); 12 | router.use('/api/chaos', await chaosController()); 13 | router.use('/api/health', await healthController()); 14 | router.use('/api/login', await loginController()); 15 | router.use('/api/csrf', await csrfController()); 16 | router.use('/api/echo', await echoController()); 17 | return router; 18 | }; 19 | -------------------------------------------------------------------------------- /14-testing/src/controllers/login.js: -------------------------------------------------------------------------------- 1 | const { Router } = require('express'); 2 | const { passport } = require('../middlewares/auth'); 3 | const { homepagePath, loginPath } = require('../config'); 4 | 5 | class LoginController { 6 | async init() { 7 | const router = Router(); 8 | router.post('/', this.post); 9 | router.get( 10 | '/github', 11 | passport.authenticate('github', { scope: ['read:user'] }) 12 | ); 13 | router.get( 14 | '/github/callback', 15 | passport.authenticate('github', { 16 | failureRedirect: loginPath, 17 | }), 18 | this.getGithubCallback 19 | ); 20 | return router; 21 | } 22 | 23 | post = (req, res) => { 24 | req.session.logined = true; 25 | res.redirect(homepagePath); 26 | }; 27 | 28 | getGithubCallback = (req, res) => { 29 | req.session.logined = true; 30 | res.redirect(homepagePath); 31 | }; 32 | } 33 | 34 | module.exports = async () => { 35 | const c = new LoginController(); 36 | return await c.init(); 37 | }; 38 | -------------------------------------------------------------------------------- /14-testing/src/middlewares/auth.js: -------------------------------------------------------------------------------- 1 | const passport = require('passport'); 2 | const { Strategy: GithubStrategy } = require('passport-github'); 3 | const { githubStrategyOptions } = require('../config'); 4 | 5 | const githubStrategy = new GithubStrategy( 6 | githubStrategyOptions, 7 | (accessToken, refreshToken, profile, done) => { 8 | /** 9 | * 根据 profile 查找或新建 user 信息 10 | */ 11 | const user = {}; 12 | done(null, user); 13 | } 14 | ); 15 | 16 | passport.use(githubStrategy); 17 | 18 | passport.serializeUser((user, done) => { 19 | /** 20 | * 根据 user 信息获取 userId 21 | */ 22 | const userId = '46e5'; 23 | done(null, userId); 24 | }); 25 | 26 | passport.deserializeUser((userId, done) => { 27 | /** 28 | * 根据 userId 获取 user 信息 29 | */ 30 | const user = {}; 31 | done(null, user); 32 | }); 33 | 34 | module.exports = function authMiddleware() { 35 | return [passport.initialize(), passport.session()]; 36 | }; 37 | 38 | Object.assign(module.exports, { passport }); 39 | -------------------------------------------------------------------------------- /14-testing/src/middlewares/index.js: -------------------------------------------------------------------------------- 1 | const { Router } = require('express'); 2 | const cookieParser = require('cookie-parser'); 3 | const bodyParser = require('body-parser'); 4 | const csurf = require('csurf'); 5 | const helmet = require('helmet'); 6 | const sessionMiddleware = require('./session'); 7 | const urlnormalizeMiddleware = require('./urlnormalize'); 8 | const loginMiddleware = require('./login'); 9 | const authMiddleware = require('./auth'); 10 | const traceMiddleware = require('./trace'); 11 | const { sessionCookieSecret } = require('../config'); 12 | 13 | module.exports = async function initMiddlewares() { 14 | const router = Router(); 15 | router.use(traceMiddleware()); 16 | router.use(helmet()); 17 | router.use(urlnormalizeMiddleware()); 18 | router.use(cookieParser(sessionCookieSecret)); 19 | router.use(sessionMiddleware()); 20 | router.use(loginMiddleware()); 21 | router.use(authMiddleware()); 22 | router.use(bodyParser.urlencoded({ extended: false }), csurf()); 23 | return router; 24 | }; 25 | -------------------------------------------------------------------------------- /14-testing/src/middlewares/login.js: -------------------------------------------------------------------------------- 1 | const { parse } = require('url'); 2 | const { homepagePath, loginPath, loginWhiteList } = require('../config'); 3 | 4 | module.exports = function loginMiddleware() { 5 | const whiteList = Object.assign({}, loginWhiteList, { 6 | [loginPath]: ['get'], 7 | }); 8 | 9 | return (req, res, next) => { 10 | const { pathname } = parse(req.url); 11 | 12 | if (req.session.logined && pathname == loginPath) { 13 | res.redirect(homepagePath); 14 | return; 15 | } 16 | 17 | if ( 18 | req.session.logined || 19 | (whiteList[pathname] && 20 | whiteList[pathname].includes(req.method.toLowerCase())) 21 | ) { 22 | next(); 23 | return; 24 | } 25 | 26 | res.redirect(loginPath); 27 | }; 28 | }; 29 | -------------------------------------------------------------------------------- /14-testing/src/middlewares/urlnormalize.js: -------------------------------------------------------------------------------- 1 | const { normalize } = require('path'); 2 | const { parse, format } = require('url'); 3 | 4 | module.exports = function urlnormalizeMiddleware() { 5 | return (req, res, next) => { 6 | // 解决windows、Linux系统使用normalize路径分隔符不一致的问题 7 | const pathname = normalize(req.path).split('\\').join('/'); 8 | const urlParsed = parse(req.url); 9 | 10 | let shouldRedirect = false; 11 | 12 | // 重定向不规范的路径 13 | if (req.path != pathname) { 14 | urlParsed.pathname = pathname; 15 | shouldRedirect = true; 16 | } 17 | 18 | // 执行重定向或者略过 19 | if (shouldRedirect) { 20 | res.redirect(format(urlParsed)); 21 | } else { 22 | next(); 23 | } 24 | }; 25 | }; 26 | -------------------------------------------------------------------------------- /14-testing/src/models/config/index.js: -------------------------------------------------------------------------------- 1 | const { db } = require('../../config'); 2 | 3 | module.exports = { [process.env.NODE_ENV || 'development']: db }; 4 | -------------------------------------------------------------------------------- /14-testing/src/models/migrate/20200725045100-create-shop.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | up: async (queryInterface, Sequelize) => { 3 | await queryInterface.createTable('shop', { 4 | id: { 5 | allowNull: false, 6 | autoIncrement: true, 7 | primaryKey: true, 8 | type: Sequelize.INTEGER, 9 | }, 10 | name: { 11 | type: Sequelize.STRING, 12 | }, 13 | created_at: { 14 | allowNull: false, 15 | type: Sequelize.DATE, 16 | }, 17 | updated_at: { 18 | allowNull: false, 19 | type: Sequelize.DATE, 20 | }, 21 | }); 22 | }, 23 | down: async (queryInterface, Sequelize) => { 24 | await queryInterface.dropTable('shop'); 25 | }, 26 | }; 27 | -------------------------------------------------------------------------------- /14-testing/src/models/migrate/20200727025727-create-session.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | up: async (queryInterface, Sequelize) => { 3 | await queryInterface.createTable('session', { 4 | sid: { 5 | type: Sequelize.STRING(36), 6 | }, 7 | expires: { 8 | type: Sequelize.DATE, 9 | }, 10 | data: { 11 | type: Sequelize.TEXT, 12 | }, 13 | 14 | created_at: { 15 | allowNull: false, 16 | type: Sequelize.DATE, 17 | }, 18 | updated_at: { 19 | allowNull: false, 20 | type: Sequelize.DATE, 21 | }, 22 | }); 23 | }, 24 | 25 | down: async (queryInterface, Sequelize) => { 26 | queryInterface.dropTable('session'); 27 | }, 28 | }; 29 | -------------------------------------------------------------------------------- /14-testing/src/models/migrate/20200801120113-create-schedule-lock.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | up: async (queryInterface, Sequelize) => { 3 | await queryInterface.createTable('schedule_lock', { 4 | id: { 5 | allowNull: false, 6 | autoIncrement: true, 7 | primaryKey: true, 8 | type: Sequelize.INTEGER, 9 | }, 10 | name: { 11 | type: Sequelize.STRING, 12 | }, 13 | counter: { 14 | type: Sequelize.INTEGER, 15 | }, 16 | created_at: { 17 | allowNull: false, 18 | type: Sequelize.DATE, 19 | }, 20 | updated_at: { 21 | allowNull: false, 22 | type: Sequelize.DATE, 23 | }, 24 | }); 25 | }, 26 | down: async (queryInterface, Sequelize) => { 27 | await queryInterface.dropTable('schedule_lock'); 28 | }, 29 | }; 30 | -------------------------------------------------------------------------------- /14-testing/src/models/scheduleLock.js: -------------------------------------------------------------------------------- 1 | const { Model } = require('sequelize'); 2 | 3 | module.exports = (sequelize, DataTypes) => { 4 | class scheduleLock extends Model { 5 | /** 6 | * Helper method for defining associations. 7 | * This method is not a part of Sequelize lifecycle. 8 | * The `models/index` file will call this method automatically. 9 | */ 10 | static associate(models) { 11 | // define association here 12 | } 13 | } 14 | scheduleLock.init( 15 | { 16 | name: DataTypes.STRING, 17 | counter: DataTypes.INTEGER, 18 | }, 19 | { 20 | sequelize, 21 | modelName: 'ScheduleLock', 22 | tableName: 'schedule_lock', 23 | } 24 | ); 25 | return scheduleLock; 26 | }; 27 | -------------------------------------------------------------------------------- /14-testing/src/models/seed/20200725050230-first-shop.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | up: async (queryInterface, Sequelize) => { 3 | await queryInterface.bulkInsert('shop', [ 4 | { name: '良品铺子', created_at: new Date(), updated_at: new Date() }, 5 | { name: '来伊份', created_at: new Date(), updated_at: new Date() }, 6 | { name: '三只松鼠', created_at: new Date(), updated_at: new Date() }, 7 | { name: '百草味', created_at: new Date(), updated_at: new Date() }, 8 | ]); 9 | }, 10 | 11 | down: async (queryInterface, Sequelize) => { 12 | await queryInterface.bulkDelete('shop', null, {}); 13 | }, 14 | }; 15 | -------------------------------------------------------------------------------- /14-testing/src/models/shop.js: -------------------------------------------------------------------------------- 1 | const { Model } = require('sequelize'); 2 | 3 | module.exports = (sequelize, DataTypes) => { 4 | class Shop extends Model { 5 | /** 6 | * Helper method for defining associations. 7 | * This method is not a part of Sequelize lifecycle. 8 | * The `models/index` file will call this method automatically. 9 | */ 10 | static associate(models) { 11 | // define association here 12 | } 13 | } 14 | Shop.init( 15 | { 16 | name: DataTypes.STRING, 17 | }, 18 | { 19 | sequelize, 20 | modelName: 'Shop', 21 | tableName: 'shop', 22 | } 23 | ); 24 | return Shop; 25 | }; 26 | -------------------------------------------------------------------------------- /14-testing/src/moulds/ShopForm.js: -------------------------------------------------------------------------------- 1 | const Yup = require('yup'); 2 | 3 | exports.createShopFormSchema = () => 4 | Yup.object({ 5 | name: Yup.string() 6 | .required('店铺名不能为空') 7 | .min(3, '店铺名至少 3 个字符') 8 | .max(120, '店铺名不可超过 120 字'), 9 | }); 10 | -------------------------------------------------------------------------------- /14-testing/src/rpc/echo/client.js: -------------------------------------------------------------------------------- 1 | const { resolve } = require('path'); 2 | const { promisify } = require('util'); 3 | const protoLoader = require('@grpc/proto-loader'); 4 | const grpc = require('@grpc/grpc-js'); 5 | const { rpc } = require('../../config'); 6 | 7 | class EchoClient { 8 | grpcClient; 9 | 10 | async init() { 11 | const grpcObject = grpc.loadPackageDefinition( 12 | await protoLoader.load(resolve(__dirname, 'def.proto')) 13 | ); 14 | 15 | this.grpcClient = new grpcObject.Echo( 16 | `${rpc.domain}:${rpc.port}`, 17 | grpc.credentials.createInsecure() 18 | ); 19 | } 20 | 21 | get = async ({ s, logger }) => { 22 | const { grpcClient } = this; 23 | 24 | const { message } = await promisify( 25 | grpcClient.get.bind(grpcClient, { message: s }) 26 | )(); 27 | 28 | logger.info('Echo/Get Invoked'); 29 | 30 | return { message }; 31 | }; 32 | } 33 | 34 | let client; 35 | module.exports = async () => { 36 | if (!client) { 37 | client = new EchoClient(); 38 | await client.init(); 39 | } 40 | return client; 41 | }; 42 | -------------------------------------------------------------------------------- /14-testing/src/rpc/echo/def.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | service Echo { 4 | rpc Get(EchoRequest) returns (EchoResponse) {} 5 | } 6 | 7 | message EchoRequest { string message = 1; } 8 | 9 | message EchoResponse { string message = 1; } -------------------------------------------------------------------------------- /14-testing/src/rpc/echo/server.js: -------------------------------------------------------------------------------- 1 | const { resolve } = require('path'); 2 | const { callbackify } = require('util'); 3 | const protoLoader = require('@grpc/proto-loader'); 4 | const grpc = require('@grpc/grpc-js'); 5 | 6 | class EchoServer { 7 | grpcServer; 8 | 9 | async init() { 10 | const grpcObject = grpc.loadPackageDefinition( 11 | await protoLoader.load(resolve(__dirname, 'def.proto')) 12 | ); 13 | 14 | this.grpcServer.addService(grpcObject.Echo.service, this); 15 | } 16 | 17 | get = callbackify(async (call) => { 18 | const { message } = call.request; 19 | return { message }; 20 | }); 21 | } 22 | 23 | let server; 24 | module.exports = async (grpcServer) => { 25 | if (!server) { 26 | server = new EchoServer(); 27 | Object.assign(server, { grpcServer }); 28 | await server.init(); 29 | } 30 | return server; 31 | }; 32 | -------------------------------------------------------------------------------- /14-testing/src/rpc/index.js: -------------------------------------------------------------------------------- 1 | const { promisify } = require('util'); 2 | const grpc = require('@grpc/grpc-js'); 3 | const { rpc } = require('../config'); 4 | const logger = require('../utils/logger'); 5 | const echoClient = require('./echo/client'); 6 | const echoServer = require('./echo/server'); 7 | const { grpcLogger } = logger; 8 | 9 | module.exports = async function initRpc() { 10 | grpc.setLogger(grpcLogger(logger.child({ type: 'rpc' }), 'debug')); 11 | 12 | // init rpc servers 13 | const grpcServer = new grpc.Server(); 14 | await echoServer(grpcServer); 15 | 16 | await promisify(grpcServer.bindAsync.bind(grpcServer))( 17 | `0.0.0.0:${rpc.port}`, 18 | grpc.ServerCredentials.createInsecure() 19 | ); 20 | grpcServer.start(); 21 | 22 | // init rpc clients 23 | await echoClient(); 24 | }; 25 | -------------------------------------------------------------------------------- /14-testing/src/schedules/index.js: -------------------------------------------------------------------------------- 1 | const inspectAttackSchedule = require('./inspectAttack'); 2 | 3 | module.exports = async function initSchedules() { 4 | await inspectAttackSchedule(); 5 | }; 6 | -------------------------------------------------------------------------------- /14-testing/src/services/mail.js: -------------------------------------------------------------------------------- 1 | const { promisify } = require('util'); 2 | const nodemailer = require('nodemailer'); 3 | const { mailerOptions } = require('../config'); 4 | 5 | class MailService { 6 | mailer; 7 | 8 | async init() { 9 | this.mailer = nodemailer.createTransport(mailerOptions); 10 | await promisify(this.mailer.verify)(); 11 | } 12 | 13 | async sendMail(params) { 14 | return await this.mailer.sendMail({ 15 | from: mailerOptions.auth.user, 16 | ...params, 17 | }); 18 | } 19 | } 20 | 21 | let service; 22 | module.exports = async () => { 23 | if (!service) { 24 | service = new MailService(); 25 | await service.init(); 26 | } 27 | return service; 28 | }; 29 | -------------------------------------------------------------------------------- /14-testing/src/utils/cc.js: -------------------------------------------------------------------------------- 1 | module.exports = function callbackCatch(callback) { 2 | return async (req, res, next) => { 3 | try { 4 | await callback(req, res, next); 5 | } catch (e) { 6 | next(e); 7 | } 8 | }; 9 | }; 10 | -------------------------------------------------------------------------------- /14-testing/src/utils/escape-html-in-object.js: -------------------------------------------------------------------------------- 1 | const escapeHtml = require('escape-html'); 2 | 3 | module.exports = function escapeHtmlInObject(input) { 4 | // 尝试将 ORM 对象转化为普通对象 5 | if (input && typeof input == 'object' && typeof input.toJSON == 'function') { 6 | input = input.toJSON(); 7 | } 8 | 9 | // 对类型为 string 的值转义处理 10 | if (Array.isArray(input)) { 11 | return input.map(escapeHtmlInObject); 12 | } else if (input && typeof input == 'object') { 13 | const output = {}; 14 | Object.keys(input).forEach((k) => { 15 | output[k] = escapeHtmlInObject(input[k]); 16 | }); 17 | return output; 18 | } else if (typeof input == 'string') { 19 | return escapeHtml(input); 20 | } else { 21 | return input; 22 | } 23 | }; 24 | -------------------------------------------------------------------------------- /14-testing/src/utils/escape-html-in-object.perf.js: -------------------------------------------------------------------------------- 1 | const { Suite } = require('benchmark'); 2 | const benchmarks = require('beautify-benchmark'); 3 | const escapeHtmlInObject = require('./escape-html-in-object'); 4 | 5 | const suite = new Suite(); 6 | 7 | suite.add('sparse special chars', () => { 8 | escapeHtmlInObject(' & '); 9 | }); 10 | 11 | suite.add('sparse special chars in object', () => { 12 | escapeHtmlInObject({ _: ' & ' }); 13 | }); 14 | 15 | suite.add('sparse special chars in array', () => { 16 | escapeHtmlInObject([' & ']); 17 | }); 18 | 19 | suite.add('dense special chars', () => { 20 | escapeHtmlInObject(`"'&<>"'&<>""''&&<<>>`); 21 | }); 22 | 23 | suite.add('dense special chars in object', () => { 24 | escapeHtmlInObject({ _: `"'&<>"'&<>""''&&<<>>` }); 25 | }); 26 | 27 | suite.add('dense special chars in object', () => { 28 | escapeHtmlInObject([`"'&<>"'&<>""''&&<<>>`]); 29 | }); 30 | 31 | suite.on('cycle', (e) => benchmarks.add(e.target)); 32 | suite.on('complete', () => benchmarks.log()); 33 | suite.run({ async: false }); 34 | -------------------------------------------------------------------------------- /14-testing/tests/globalSetup.js: -------------------------------------------------------------------------------- 1 | const { commandSync } = require('execa'); 2 | 3 | module.exports = () => { 4 | commandSync('yarn sequelize db:migrate'); 5 | }; 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Host1 Tech 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | --------------------------------------------------------------------------------