前言 记录使用Node.js的Express与Sequelize开发后端接口方法。
yarn工具 1 2 3 4 5 6 7 8 9 // 全局安装; npm i -g yarn // 安装国内镜像文件,三个命令行; yarn config set registry https://registry.npm.taobao.org --global yarn config set disturl https://npm.taobao.org/dist --global yarn config set sass_binary_site http://cdn.npm.taobao.org/dist/node-sass -g // 基本命令格式 yarn add //安装 yarn global add //全局安装
Hello word项目 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 // 初始化,建立package.json文件 npm init // 注意使用yarn init好像有问题(具体不清楚原因,推荐还是npm init) // 安装express yarn add express // 建立main里面的js入口文件 // 引用express var express = require('express'); // 使用 var app = express(); // req是前端发送,res是后端返回 app.get('/', function (req, res) { res.send('Hello World!'); }); // 命令行提示 app.listen(3000, function () { console.log('成功启动'); });
1 2 3 4 5 6 // 注意,package.json文件中,启动命令是serve时; "scripts": { "serve": "node index.js" }, npm run serve // 需要加run(其他不需要) yarn serve // 直接输入
快速构建node项目 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 // 安装命令 cnpm install express-generator -g // 新建项目 express --view=ejs blog cd blog cnpm install // 启动项目 npm start // 修改端口号(app.js里面修改)可选 process.env.PORT = 2000 // 使用nodemon监听代码变动 cnpm i nodemon -S // 在package.json文件修改为 "scripts": { "start": "nodemon ./bin/www" },
安装sequelize连接数据库 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 // 安装sequelize和mysql2 cnpm install sequelize -S cnpm install mysql2 -S // 全局安装sequelize-cli(下次不要安装了) cnpm install sequelize-cli -g // 初始化项目 sequelize init // 配置数据库 "development": { "database": "blog_development", } // 使用命令行创建数据库(报错就手动,推荐手动),后面迁移不成功就输入下面的代码 sequelize db:create --charset 'utf8mb4' // 手动创建 // 使用软件创建数据库:先创建连接,后建立和config文件中名字相同的库; // 排序选择utf8mb4_general_ci; // 创建模型 sequelize model:generate --name Article --attributes title:string,content:text // 运行迁移 sequelize db:migrate
实现增删改查 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 // 在routes中创建路由,并在app.js入口文件引入和使用;(如下) var articlesRouter = require('./routes/articles'); app.use('/articles',articlesRouter) // 先引入 var models = require('../models'); // get请求 router.get('/', function (req, res, next) { models.Article.findAll().then(articles => { res.json({articles: articles}); }) }); // 第二种写法 router.get('/', async function (req, res, next) { var articles = await models.Article.findAll({ order: [['id', 'DESC']], // 这里是倒叙 }) res.json({articles: articles}); }); // 查询 router.get('/:id', async function (req, res, next) { var article = await models.Article.findByPk(req.params.id); res.json({article: article}); }); // 新增 router.post('/', async function (req, res, next) { var article = await models.Article.create(req.body) res.json({article: article}); }); // 删除 router.delete('/:id', async function (req, res, next) { var article = await models.Article.findByPk(req.params.id) article.destroy(); res.json({msg: '删除成功'}); }); // 修改 router.put('/:id', async function (req, res, next) { var article = await models.Article.findByPk(req.params.id); article.update(req.body); res.json({article: article}); });
跨域:读取不了数据 这个配置方法只在开发环境有效
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 1、vue的跨域配置 module.exports = { devServer: { proxy: { // 增加一个api的前缀(proxyTable代理服务器) '/api': { target: process.env.VUE_APP_BASE_API, // 后台接口域名 ws: true, // 如果要代理 websockets,配置这个参数 secure: false, // 如果是https接口,需要配置这个参数 changeOrigin: true, // 是否跨域 pathRewrite: { '^/api': '' //用'/api'代替target里面的地址 } } } }, } 2、CORS 通过自定义请求头来让服务器和浏览器进行沟通 3、nginx代理跨域 nginx模拟一个虚拟服务器,因为服务器与服务器之间是不存在跨域的, 发送数据时 ,客户端->nginx->服务端 返回数据时,服务端->nginx->客户端
模糊搜索和分页 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 router.get('/', async function (req, res, next) { var where = {}; var title = req.query.title; var content = req.query.content; var currentPage = parseInt(req.query.currentPage) || 1; var pageSize = parseInt(req.query.pageSize) || 2; // 查询关联id字段时,就不要使用[Op.like]了,否者查询1出现11的bug if (title) { where.title = { [Op.like]: `%${title}%` } } if (content) { where.content = { [Op.like]: `%${content}%` } } var result = await models.Article.findAndCountAll({ order: [['id', 'DESC']], where: where, offset: (currentPage - 1) * pageSize, limit: pageSize, }) res.json({ articles: result.rows, pagination: { currentPage: currentPage, pageSize: pageSize, total: result.count } }); });
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 // 创建模型 sequelize model:generate --name Category --attributes name:string,sort:integer // 在迁移文件添加默认值 allowNull: false, defaultValue: '0' // 迁移 sequelize db:migrate // 回滚 npx sequelize-cli db:migrate:undo // 或者(网上看的还未验证)sequelize db:migrate:undo // 新的模型为添加字段 sequelize-cli migration:create --name add-categoryID-to-userID // 在新的迁移文件中 // up里面 await queryInterface.addColumn('articles', 'categoryId', { type: Sequelize.INTEGER }) await queryInterface.addColumn('articles', 'userId', { type: Sequelize.INTEGER }) // down里面 await queryInterface.removeColumn('articles', 'categoryId') await queryInterface.removeColumn('articles', 'userId')
1 2 3 4 5 6 7 // 模型里面从属 models.Category.hasMany(models.Article) models.Article.belongsTo(models.Category) // articles中关联模型 include: [models.Category, models.User], // categories中使用sort排序 order: [['id', 'DESC']], // 正序ASC
在下拉读取时分类时,接口里面写分页写大,不然在读取只能读第一页
数据库密码加密 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 // 安装bcryptjs npm install bcryptjs // 在users里面引用 var bcrypt = require('bcryptjs'); // 用户新增里面 // 密码需要转化为字符串 var password = req.body.password ? req.body.password.toString().trim() : false var name = req.body.name ? req.body.name.toString().trim() : false // 判断填写的密码和用户名 if (!name) { return res.json({ code: 20000, data: {status: false,msg: "用户名必须填写"} }) } if (!password) { return res.json({ code: 20000, data: {status: false,msg: "密码必须填写"} }) } // 查询单条属性findOne var user = await models.User.findOne({ where: {name: req.body.name} }); // 判断用户名已经存在(这里可以写return,必须要在函数里面,当前if在一个函数里面) if (user) { return res.json({ code: 20000, data: { status: false, msg: "用户名已经存在" } }) } // 生成盐(加密用) var salt = bcrypt.genSaltSync(10); // 将密码加密转化 var hash = bcrypt.hashSync(password, salt); // 推到数据库(...为展开下面可以直接覆盖) var body = { ...req.body, password: hash } user = await models.User.create(body) // 返回结果 res.json({ code: 20000, data: { status: true, user: user } });
登录生成token 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 // 检查输入的密码是否正确 var password = req.body.password // 这里 var hash = user.password // 数据库保存的乱码 var check = bcrypt.compareSync(password, hash); // 安装生成token的包 yarn add jsonwebtoken // 引用jwt var jwt = require('jsonwebtoken'); // 生成token,将id和name生成token,其中有个环境变量,最后的生成有效期为7天的token; // 注意这里生成token时,如果有多个中间件,后台和前台的token都会验证成功(bug) var token = jwt.sign({ id: user.id, name: user.name }, process.env.SECRET, {expiresIn: 60 * 60 * 24 * 7}); // 返回token res.json({token:token}) // 环境变量 // 安装dotenv yarn add dotenv //安装完成后,在项目根目录建一个.env文件,里面代码 SECRET=clwy.cn // 例如 // 在根目录app.js中引用 require('dotenv').config()
中间件验证登录 1 2 3 4 5 6 7 8 // 根目录下新建/middlewares/checkAuth.js文件 // 里面代码(如下checkAuth) // 在app.js中,后台的路由中,添加上中间件 // 引用 var checkAuth = require('./middlewares/checkAuth') // 添加 app.use('/admin/categories', checkAuth(), adminCategoriesRouter);
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 // 中间件代码 var jwt = require('jsonwebtoken') module.exports = function (options) { return function (req, res, next) { // 验证是否有token var token = req.headers.token; if (!token) { return res.json({ code: 50008, message: 'Token未提供' }); } // 验证token是否正确 jwt.verify(token, process.env.SECRET, function (err, decoded) { if (err) { if (err.name == "TokenExpiredError") { return res.json({ code: 50014, message: 'Token过期' }); } if (err.name == "JsonWebTokenError") { return res.json({ code: 50008, message: 'Token错误' }); } } // decoded里面装的是解密出来的是之前加密生成的token的东西 // 解析出来的东西存入req req.decoded = decoded; next(); }) } }
时间moment 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 // 安装moment yarn add moment var moment = require('moment');// 引用 // config.json配置时区,设置成国内 "timezone": "+08:00" // 设置时间对象(当前月去i,返回一年的数据) let months = [] let data = [] moment.locale('zh-cn');// 中国时区 for (let i = 0; i < 12; i++) { let month = moment().subtract(i, 'months').format("YYYY-MM"); // 注意format使用; months.push(month) let start = moment().subtract(i, 'months').startOf('month').format("YYYY-MM-DD") + ' 00:00:00' let end = moment().subtract(i, 'months').endOf('month').format("YYYY-MM-DD") + ' 23:59:59' let res = await models.Product.count({ where: {'createdAt': {[Op.between]: [start, end]}} })// count是查询符合条件的个数 data.push(res) }
seed种子文件添加数据 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 // 创建seed文件 sequelize seed:generate --name order // 在文件seeders/xxx-order.js(可以修改后再次提交) // up里面(添加的数必须要有时间) let data= { name: 'admin', createdAt: new Date(), updatedAt: new Date() } await queryInterface.bulkInsert('Orders', data, {});// 需要大写 // down(注意这个不是使用回滚,会导致之前的表迁移返回,暂时不知道如何使用) await queryInterface.bulkDelete('Orders', null, {}); // 运行迁移(所有的seed) sequelize db:seed:all // 迁移单个文件 sequelize db:seed --seed xxx-order // 撤消最近种子 sequelize db:seed:undo sequelize db:seed:undo:all// 所有的种子
七牛云图片后台 1 2 3 4 5 6 7 8 9 10 11 12 // 安装 npm install qiniu // 引用 const qiniu = require('qiniu'); // router中 var accessKey = process.env.ACCESS_KEY;// 七牛云的密钥 var secretKey = process.env.SECRET_KEY;// 七牛云的私钥 var mac = new qiniu.auth.digest.Mac(accessKey, secretKey); var options = {scope: process.env.SCOPE,};// 后为空间名字 var putPolicy = new qiniu.rs.PutPolicy(options); var uploadToken = putPolicy.uploadToken(mac); // 最后返回uploadToken
封装res返回的success、error 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 // 新建utils/message.js文件 const success = (res, message = '', data = {}) => { return res.json({ code: 20000, message, data }) } const error = (res, message = '') => { return res.json({ code: 20000, message, }) } module.exports = { success, error } // 路由中引用 const {success, error} = require('../utils/message') // 使用 success(res, '查询成功', data)
CORS跨域处理 1 2 3 4 5 6 yarn add cors // app.js中 var cors = require('cors'); // ... 注意位置 var app = express(); app.use(cors());