├── .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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
--------------------------------------------------------------------------------