【NodeJS】Express写接口的整体流程
前提条件
开发 Node.js,首先就必须要安装 Node.js。推荐使用 nvm
,它可以随意切换 node 版本。下载 nvm,具体可以看本人另一篇文章:nvm的作用、下载、使用、以及Mac使用时遇到commond not found:nvm如何解决。
nvm官方:https://github.com/coreybutler/nvm-windows/
1. express-generator快速生成项目目录
下载
npm i -g express-generator@4
快速生成项目目录,--no-view
表示不需要视图。
express --no-view benchu-api
进入项目目录,下载依赖包
npm i
运行命令,启动服务
npm start
访问 http://localhost:3000
看到如下页面,即为成功
2. nodemon
本文使用 node 专门搭建接口,所以在 routes/index.js
中将返回格式修改为 json
并删掉 public/index.html
文件。
刷新发现页面没有变化,需要重新 npm start
,发现正常返回了 json。
我这里下载了浏览器插件,视觉上更加美观。
频繁执行 npm start
过于繁琐,更推荐安装 nodemon
,它可以监听改动并自动重启。
下载 nodemon
npm i nodemon
修改 package.json
,将 start 命令由 node 改为 nodemon
后面再执行 npm start
时就是通过 nodemon
启动服务了,修改后自动重启服务,刷新页面即可。
3. 使用Docker运行数据库
在 Windows 与 macOS 下,整个安装和使用的流程都非常的不同,而且将来部署到服务器后,也会有一些默认配置不同。例如以下这个:
low_case_table_names=0
它是让MySQL区分数据表名是否大小写的配置。默认在 Windows、macOS 和 Linux 下全都不同。而且当数据库初始化之后,也就说成功的启动过一次后,就不能修改了。
3.1 下载 Docker 并配置中国镜像
使用 Docker
来运行 MySQL
就不会有如上问题的困惑。这样在 Windows 与 macOS 上,甚至在 Linux 服务器上,它们的运行环境都是一致的。
官网下载:https://www.docker.com/get-started/
下载完成后,设置 => Docker Emgine中添加中国镜像的配置。
"registry-mirrors": ["https://docker.rainbond.cc"
]
保存配置后,切回项目目录,在 项目根目录下 新建一个 docker-compose.yml
文件。一定要在项目根目录中,放在其他地方会找不到的。然后将下面 MySQL
的配置复制进去。
services:mysql:image: mysql:8.3.0command:--default-authentication-plugin=mysql_native_password--character-set-server=utf8mb4--collation-server=utf8mb4_general_cienvironment:- MYSQL_ROOT_PASSWORD=tianbenchu- MYSQL_LOWER_CASE_TABLE_NAMES=0ports:- "3306:3306"volumes:- ./data/mysql:/var/lib/mysql
执行如下命令,下载并启动 MySQL
docker-compose up -d
如下提示标识下载启动完毕
再次切回 Docker
,发现出现项目,以后 启动/关闭 MySQL
可以通过按钮快速实现。
3.2 下载客户端连接数据库
想要方便的操作数据库,需要下载客户端,我这里以 MacOS
举例,到 AppStore 中下载 sequel ace
即可,Windows 可以使用 Navicat。
连接数据库
密码就是配置文件中的 MYSQL_ROOT_PASSWORD
4. 创建数据库与表
4.1 创建数据库与表
继上一步连接无误后,创建新数据库。
创建表,这里以 Articles
命名,注意首字母一定要大写,不大写的话将来部署到 Linux 服务器会报错;且一定要为复数形式,即后缀s,否则 nodejs 将无法查询到这个表。
4.2 主键
表创建完毕后,继续观察,有个 key,里面写的 PRI
,它是主键的意思。一般,每个表都会设置一个 id,并把它设置成 primary key
表示它是唯一标识。
主键,还会搭配自增 auto_increment
来使用,后续追加数据时 主键 自增。
4.3 字段类型
4.3.1 数字类型
类型 | 字节大小 | 有符号范围 (Signed) | 无符号范围 (Unsigned) |
---|---|---|---|
TINYINT | 1 | -128 ~ 127 | 0 ~ 255 |
SMALLINT | 2 | -32768 ~ 32767 | 0 ~ 65535 |
MEDIUMINT | 3 | -8388608 ~ 8388607 | 0 ~ 16777215 |
INT/INTEGER | 4 | -2147483648 ~2147483647 | 0 ~ 4294967295 |
BIGINT | 8 | -9223372036854775808 ~ 9223372036854775807 | 0 ~ 18446744073709551615 |
4.3.2 字符串类型:
类型 | 说明 | 使用场景 |
---|---|---|
CHAR | 固定长度,小型固定长度的数据 | 身份证号、手机号、电话、密码 |
VARCHAR | 可变长度,小型数据 | 姓名、地址、品牌、型号、用户的评论、文章的标题 |
TEXT | 可变长度,字符个数大于 4000 | 存储文章正文 |
LONGTEXT | 可变长度,超大型文本数据 | 存储超大型文本数据 |
4.3.3 时间类型
类型 | 字节大小 | 示例 |
---|---|---|
DATE | 4 | ‘2020-01-01’ |
TIME | 3 | ‘12:29:59’ |
DATETIME | 8 | ‘2020-01-01 12:29:59’ |
YEAR | 1 | ‘2017’ |
TIMESTAMP | 4 | ‘1970-01-01 00:00:01’ UTC ~ ‘2038-01-01 00:00:01’ UTC |
4.3.4 总结 - 常用数据类型
类型 | 含义 | 说明 |
---|---|---|
int | 整数 | 需要设定长度 |
decimal | 小数 | 金额常用,需要设定长度。如 decimal(10, 2) 表示共存 10 位数,其中小数占 2 位 |
char、varchar | 字符串 | 文字类的常用,需要设定长度。例如身份证号、文章的标题使用。 |
text | 文本 | 存储大文本,无需设定长度。一般会用文字很多的时候,例如文章的正文部分。 |
date、time、datetime | 日期 | 记录时间 |
4.4 新增字段
了解了数据类型后,添加 title
和 content
。
5. 基础 sql 语句
5.1 增
INSERT INTO 表名 (列1, ...) VALUES (值1, ...)
// 多行插入
INSERT INTO 表名 (列1, ...) VALUES (值1, ...),(值1, ...)...;// 例如:
INSERT INTO `Articles` (`title`, `content`) VALUES ('行路难·其一', '长风破浪会有时,直挂云帆济沧海。');
可以看到数据已成功加入
5.2 删
DELETE FROM 表名 WHERE 条件// 例如:
DELETE FROM `ARTICLES` WHERE `id`=5;
5.3 改
UPDATE 表名 SET 列1=值1, 列2=值2, ... WHERE 条件// 例如:
UPDATE `Articles` SET `title`='黄鹤楼送孟浩然之广陵', `content`='故人西辞黄鹤楼,烟花三月下扬州。' WHERE `id`=2;
5.4 查
SELECT * FROM 表名;// 例如:
SELECT * FROM `Articles`;
* 表示所有的字段,如果只需要查询某些字段,指定即可。
SELECT `id`, `title` FROM `Articles`;
条件查询
SELECT * FROM 表名 WHERE 条件;// 想查询id大于2的文章:
SELECT * FROM `Articles` WHERE `id`>2;
排序,不添加声明,默认为 ASC
升序。
-- 查询id大于2的文章,按 id 从大到小排序,即降序
SELECT * FROM `Articles` WHERE `id`>2 ORDER BY `id` DESC;-- 查询id大于2的文章,按 id 从小到大排列,即升序
SELECT * FROM `Articles` WHERE `id`>2 ORDER BY `id` ASC;
6. 使用 Sequelize ORM 简化书写 sql 语句
手写 sql 语句过于繁琐且容易出错,可以使用一些工具简化书写逻辑,本文使用 Sequelize ORM
。
6.1 Sequelize 常用命令
先体验一下 Sequelize
简洁的语法:
如全量查询
SELECT * FROM `Articles`;// Sequelize 可以这么写
Article.findAll()
条件查询,findByPk
参数就是主键 primary key
,说白了就是通过id来查找数据。
SELECT * FROM `Articles` WHERE `id`=2;// Sequelize 可以这么写
Article.findByPk(2)
6.2 安装 Sequelize
先安装sequelize的命令行工具
npm i -g sequelize-cli
然后在项目目录中,安装当前项目所依赖的 sequelize
包和对数据库支持依赖的mysql2
。
npm i sequelize mysql2
初始化项目
sequelize init
初始化完毕后,项目目录下生成了几个文件:
6.3 新增文件的作用
初始化 sequelize
完毕后,项目目录生成了如下几个文件:
- config/config.json:该文件是 sequelize 需要的连接到数据库的配置文件。
- migrations:迁移,用于处理 新增表、修改字段、删除表 等操作,而不用直接在客户端中点点点直接操作数据库。
- models/index.js:模型文件,使用
sequelize
增删改查时,每个文件对应数据库中的一张表。 - seeders:存放需要添加到数据表的测试数据。
6.3.1 配置 config/config.json
配置 config/config.json,Node项目就会自动的,连接到数据库上了。有三组配置,分别是:development、test 和 production。分别对应开发环境、测试环境、生产环境的配置。
本文只配置 development,其他的配置也是一样的。
- password:保持与
docker-compose.yml
中一致 - database:修改数据库名,保持与
Sequel Ace
客户端中一致 - timezone:时区设置为东八区
6.3.2 migrations迁移和models模型
删除上文中通过客户端手动创建的表 Articles,通过命令新增模型
sequelize model:generate --name Article --attributes title:string,content:text
项目 migrations
迁移目录 和 models
模型目录下多了文件,稍作修改,将 content
字段设置为不可为 null。
-
up:通过
createTabel
,创建了一个叫做Articles的表。表名为复数,但是models/article.js
模型为单数。 -
down:新建表的反向操作,为
dropTable
也就是删除当前的表。这样当我们创建表,建完后,突然又发现有错误,也可以通过相关命令来删除当前表。
运行迁移命令
sequelize db:migrate
再次刷新数据库客户端,除了我们创建的 Articles
表,还自动创建了 SequelizeMeta
表,这张表里记录了当前已经跑过了哪些迁移,当再次运行 sequelize db:migrate
时,已经运行过的迁移文件,就不会重复再次执行了。
6.3.3 种子文件
上一步运行迁移后已经创建成功了 Articles
表,下一步就是要填充一些在开发中用来测试的数据了。通过如下命令添加种子文件。
sequelize seed:generate --name article
完成后,在 seeds
目录,就看到刚才命令新建的种子文件了。同样也是分为两个部分,up部分用来填充数据,down部分是反向操作,用来删除数据的。
up 和 down 中默认给了案例,仿照他的写法生成多条测试数据:
"use strict"/** @type {import('sequelize-cli').Migration} */
module.exports = {async up(queryInterface, Sequelize) {const articles = []for (let i = 1; i <= 100; i++) {const article = {title: `文章的标题 ${i}`,content: `文章的内容 ${i}`,createdAt: new Date(),updatedAt: new Date(),}articles.push(article)}await queryInterface.bulkInsert("Articles", articles, {})},async down(queryInterface, Sequelize) {await queryInterface.bulkDelete("Articles", null, {})},
}
运行种子文件,生成大量数据,注意
:命令后缀一定为该种子文件名
sequelize db:seed --seed xxx-article
如我这里的种子文件为:20241126100352-article.js
就运行下面这行命令
sequelize db:seed --seed 20241126100352-article
刷新数据库,发现数据添加成功。
6.4 Sequelize的基本使用
日常开发项目,都是采用固定的步骤:
步骤 | 命令 | 说明 |
---|---|---|
第一步 | sequelize model:generate --name Article --attributes … | 建模型和迁移文件 |
第二步 | 人工处理 | 根据需求调整迁移文件 |
第三步 | sequelize db:migrate | 运行迁移,生成数据表 |
第四步 | sequelize seed:generate --name article | 新建种子文件 |
第五步 | 人工处理 | 将种子文件修改为自己想填充的数据 |
第六步 | sequelize db:seed --seed xxx-article | 运行种子文件,将数据填充到数据表中 |
种子
文件并不是必须的,但对于一些需要一下子插入大量数据的情况来说,更推荐使用种子。
7. 使用 Sequelize 写几个接口
routes/admin/articles.js
新增路由
var express = require("express")
var router = express.Router()router.get("/", function (req, res, next) {res.json({ message: "articles api" })
})module.exports = router
app.js 中引用并use路由
npm start
启动服务,访问 http://localhost:3000/admin/articles
,看到正常返回。
7.1 写接口 - 获取文章列表
如果想读取数据库数据,就需要使用 模型
,routes/admin/articles.js
中引入模型,并通过模型读取数据库数据。
Article.findAll()
读取全部数据,读取数据是异步操作,使用 async await 等待。
var express = require("express")
var router = express.Router()
const { Article } = require("../../models")router.get("/", async function (req, res, next) {try {// 查询数据const articles = await Article.findAll()// 返回查询结果res.json({status: true,message: "查询文章列表成功",data: { articles },})} catch (err) {res.status(500).json({status: false,message: "查询文章列表失败",errors: [err.message],})}
})module.exports = router
刷新页面,数据已出现
通过控制台可以看到,Article.findAll()
最终被转为 sql 语句执行。
实际场景中,数据通常是倒序,即最后添加的数据最先展示,定义查询条件即可,Article.findAll()
时传入查询条件。
var express = require("express")
var router = express.Router()
const { Article } = require("../../models")router.get("/", async function (req, res, next) {try {// 定义查询条件const condition = {order: [["id", "DESC"]],}// 查询数据const articles = await Article.findAll(condition)// 返回查询结果,成功状态码默认为200res.json({status: true,message: "查询文章列表成功",data: { articles },})} catch (err) {res.status(500).json({status: false,message: "查询文章列表失败",errors: [err.message],})}
})module.exports = router
7.2 写接口 - 获取文章详情
routes/admin/articles.js
中添加获取详情接口,Article.findByPk(id)
通过 id 查询。
// 查询文章详情
router.get("/:id", async function (req, res, next) {try {// 获取参数 - 文章IDconst { id } = req.params// 查询数据const article = await Article.findByPk(id)// 返回查询结果if (article) {res.json({status: true,message: "查询文章详情成功",data: { article },})} else {return res.status(404).json({status: false,message: "文章不存在",})}} catch (err) {res.status(500).json({status: false,message: "查询文章详情失败",errors: [err.message],})}
})
访问 http://localhost:3000/admin/articles/66
访问 http://localhost:3000/admin/articles/666
,并不存在改文章
控制台对应的 sql 语句
7.3 使用Apifox
通过浏览器页面查看返回不方便,更推荐使用apifox,将开发环境默认前缀设置为:http://localhost:3000,创建接口并测试。
7.4 写接口 - 创建文章
首先需要验证表单数据,在 models/article.js
中添加相应配置,当前案例只对 title 参数进行校验。validate
为验证规则,其中:
- notNull: 没有传 title,就会提示:标题必须存在。
- notEmpty:用户传了 title 过来,但是却没有值,就会提示:标题不能为空。
- len:限制长度。
title: {type: DataTypes.STRING,allowNull: false,validate: {notNull: {msg: '标题必须存在。'},notEmpty: {msg: '标题不能为空。'},len: {args: [2, 45],msg: '标题长度需要在2 ~ 45个字符之间。'}}
},
Article.create()
等同于 INSERT INTO 表名 (列1, ...) VALUES (值1, ...)
// 白名单过滤 - 获取请求参数
function getBody(req){return {title: req.body.title,content: req.body.content,}
}router.post("/", async function (req, res, next) {const body = getBody(req)try {// 创建数据const article = await Article.create(body)// 返回创建结果res.status(201).json({status: true,message: "创建文章成功",data: article,})} catch (err) {res.json(err)}
})
apifox 测试,故意不传 title 查看报错信息,errors 是个列表,需要遍历所有错误信息。
修改 catch 捕获错误的相关逻辑
catch (err) {if (err.name === "SequelizeValidationError") {const errors = err.errors.map((error) => error.message)res.status(400).json({status: false,message: "创建文章失败",errors,})} else {res.status(500).json({status: false,message: "创建文章失败",errors: [err.message],})}
}
7.5 写接口 - 删除文章
Article.destroy({ where: { id } })
等同于如下 sql 语句。
DELETE FROM \`Articles\` WHERE \`id\` = '102'
// 删除文章
router.delete("/:id", async function (req, res, next) {try {// 获取参数 - 文章IDconst { id } = req.params// 查询数据const article = await Article.findByPk(id)if (article) {// 删除数据await Article.destroy({ where: { id } })// 返回删除结果res.json({status: true,message: "删除文章成功",})} else {return res.status(404).json({status: false,message: "文章不存在",})}} catch (err) {res.status(500).json({status: false,message: "删除文章失败",errors: [err.message],})}
})
7.6 写接口 - 更新文章
// 白名单过滤 - 获取请求参数
function getBody(req){return {title: req.body.title,content: req.body.content,}
}router.put("/:id", async function (req, res, next) {const body = getBody(req)try {// 获取参数 - 文章IDconst { id } = req.params// 查询数据const article = await Article.findByPk(id)if (article) {// 更新数据await article.update(body)// 返回更新结果res.json({status: true,message: "更新文章成功",})} else {return res.status(404).json({status: false,message: "文章不存在",})}} catch (err) {res.status(500).json({status: false,message: "更新文章失败",errors: [err.message],})}
})
7.7 写接口 - 模糊查询
做这种复杂的查询,要引入 Op
。然后修改 findAll
的查询条件,title: { [Op.like]: %${query.title}%
},这段代码等同于如下 sql 语句。
select * from Articles where title like '%标题 10%'`
const { Op } = require('sequelize');// 查询文章列表
router.get("/", async function (req, res, next) {try {const query = req.queryconst condition = {order: [["id", "DESC"]],}// 模糊查询if (query.title) {condition.where = {title: {[Op.like]: `%${query.title}%`,},}}const articles = await Article.findAll(condition)res.json({status: true,message: "查询文章列表成功",data: { articles },})} catch (err) {res.status(500).json({status: false,message: "查询文章列表失败",errors: [err.message],})}
})
7.8 写接口 - 分页
分页的 sql 语句如下,LIMIT
后第一个参数为从哪个索引开始查找,第二个参数为查找多少数据。
SELECT * FROM `Articles` LIMIT 0, 10;// 假如一页10条,第二页参数为 10 10,而不是 10 20
SELECT * FROM `Articles` LIMIT 10, 10;
需要注意 sequelize
中,查询条件的 offset 对应 sql 语句中第一个参数,limit 字段也就是 pageSize 对应 sql 语句中第二个参数。
将 findAll
改为 findAndCountAll
,该方法返回对象,其中 count 为数据总数,rows 为当前查询到的数据。
// 查询文章列表
router.get("/", async function (req, res, next) {try {const query = req.query// 当前页码const currentPage = Math.abs(Number(query.currentPage)) || 1// 每页显示条数const pageSize = Math.abs(Number(query.pageSize)) || 10// 计算offsetconst offset = (currentPage - 1) * pageSizeconst condition = {order: [["id", "DESC"]],limit: pageSize,offset,}if (query.title) {condition.where = {title: { [Op.like]: `%${query.title}%` },}}const { count, rows } = await Article.findAndCountAll(condition)res.json({status: true,message: "查询文章列表成功",data: {articles: rows,pagination: { total: count, currentPage, pageSize },},})} catch (err) {res.status(500).json({status: false,message: "查询文章列表失败",errors: [err.message],})}
})
8. 封装响应 优化代码结构
上面的接口代码有大量重复性代码,适当封装实现复用。思路如下:
- 如果没找到数据导致报错,通过抛出异常的方式,让catch来捕获异常,而不是直接在 try 中写 if。
- 成功和失败状态响应的封装。
- 查询、更新、删除都需要先查询当前文章,定义一个公共方法,可以直接调用这个方法。
项目根目录中新建一个 utils 目录,其中建一个 response.js,通过继承的方式,自定义一个 404 错误类,以及成功和失败状态的响应。
utils/response.js
/*** 自定义 404 错误类*/
class NotFoundError extends Error {constructor(message) {super(message)this.name = "NotFoundError"}
}/*** 请求成功* @param res* @param message* @param data* @param code*/
function success(res, message, data = {}, code = 200) {res.status(code).json({status: true,message,data,})
}/*** 请求失败* @param res* @param error*/
function failure(res, error) {if (error.name === "SequelizeValidationError") {const errors = error.errors.map((e) => e.message)return res.status(400).json({status: false,message: "请求参数错误",errors,})}if (error.name === "NotFoundError") {return res.status(404).json({status: false,message: "资源不存在",errors: [error.message],})}res.status(500).json({status: false,message: "服务器错误",errors: [error.message],})
}module.exports = {NotFoundError,success,failure,
}
现在就可以通过如下方式抛出异常
throw new NotFoundError('错误信息')
成功的响应
// 查询文章详情
success(res, '查询文章成功。', { article });// 创建文章,状态码 201
success(res, '创建文章成功。', { article }, 201);// 更新文章
success(res, '更新文章成功。', { article });// 删除文章 文章已经被删掉了,所以不需要传 data
success(res, '删除文章成功。');
失败的响应
catch (error) {failure(res, error);
}
封装查询文章的方法,查询、更新、删除中均可使用
/*** 公共方法:查询当前文章*/
async function getArticle(req) {// 获取文章 IDconst { id } = req.params// 查询当前文章const article = await Article.findByPk(id)// 没找到 - 抛出异常if (!article) {throw new NotFoundError(`ID: ${id}的文章未找到。`)}return article
}
优化后的整体代码
const express = require("express")
const router = express.Router()
const { Article } = require("../../models")
const { Op } = require("sequelize")
const { NotFoundError, success, failure } = require("../../utils/response")/*** 查询文章列表* GET /admin/articles*/
router.get("/", async function (req, res) {try {const query = req.queryconst currentPage = Math.abs(Number(query.currentPage)) || 1const pageSize = Math.abs(Number(query.pageSize)) || 10const offset = (currentPage - 1) * pageSizeconst condition = {order: [["id", "DESC"]],limit: pageSize,offset: offset,}if (query.title) {condition.where = {title: {[Op.like]: `%${query.title}%`,},}}const { count, rows } = await Article.findAndCountAll(condition)success(res, "查询文章列表成功。", {articles: rows,pagination: {total: count,currentPage,pageSize,},})} catch (error) {failure(res, error)}
})/*** 查询文章详情* GET /admin/articles/:id*/
router.get("/:id", async function (req, res) {try {const article = await getArticle(req)success(res, "查询文章成功。", { article })} catch (error) {failure(res, error)}
})/*** 创建文章* POST /admin/articles*/
router.post("/", async function (req, res) {try {const body = filterBody(req)const article = await Article.create(body)success(res, "创建文章成功。", { article }, 201)} catch (error) {failure(res, error)}
})/*** 更新文章* PUT /admin/articles/:id*/
router.put("/:id", async function (req, res) {try {const article = await getArticle(req)const body = filterBody(req)await article.update(body)success(res, "更新文章成功。", { article })} catch (error) {failure(res, error)}
})/*** 删除文章* DELETE /admin/articles/:id*/
router.delete("/:id", async function (req, res) {try {const article = await getArticle(req)await article.destroy()success(res, "删除文章成功。")} catch (error) {failure(res, error)}
})/*** 公共方法:查询当前文章*/
async function getArticle(req) {const { id } = req.paramsconst article = await Article.findByPk(id)if (!article) {throw new NotFoundError(`ID: ${id}的文章未找到。`)}return article
}/*** 公共方法:白名单过滤* @param req* @returns {{title, content: (string|string|DocumentFragment|*)}}*/
function filterBody(req) {return {title: req.body.title,content: req.body.content,}
}module.exports = router
9. 总结
9.1 创建流程
- 安装 docker 和 数据库图形化工具
- npm i -g express-generator@4:安装 express 脚手架快速搭建项目
- npm i -g sequelize-cli: 安装 sequelize 命令行工具,可以执行模型、迁移、种子相关的命令
- express --no-view clwy-api: 创建项目
- npm i:下载项目依赖
- 删除
public/index.html
,根目录下创建docker-compose.yml
并配置数据库 - npm i nodemon:安装 nodemon,将
package.json
中的启动方式修改为 nodemon - npm i sequelize mysql2:安装 sequelize 与 mysql2 依赖包,然后就可以使用 sequelize 操作 mysql
- sequelize init:初始化 sequelize,修改
config/config.json
中数据库相关配置 - npm start:启动服务
9.2 开发流程
- 创建数据库
- sequelize model:generate --name Article --attributes title:string,content:text:创建模型,
注意:
模型名为单数形式,表名为复数形式,并指定字段与类型 - 上面一步会创建迁移文件,修改迁移文件中字段的要求,如本文将 title 字段设置为不可为null
- sequelize db:migrate:运行迁移文件,创建表
- sequelize seed:generate --name article:创建种子文件
- sequelize db:seed --seed xxx-article:运行指定种子文件,–seed后为完整文件名
- sequelize db:seed:all:运行所有种子文件
- 运行完种子文件,往表中添加测试数据
9.3 req说明
- req.params:获取路由里的参数 /admin/articles/:id
- req.query:获取 URL 地址里的查询参数 /admin/articles ?title=hello¤tPage=2&pageSize=20
- req.body:获取通过 POST、PUT 发送的数据
9.4 Sequelize ORM 操作数据库常用方法
- findAll:查询所有记录
- findAndCountAll:查询所有记录,并统计数据总数
- findByPk:通过主键查询单条数据
- create:创建新数据
- update:更新数据
- destroy:删除数据
相关文章:
【NodeJS】Express写接口的整体流程
前提条件 开发 Node.js,首先就必须要安装 Node.js。推荐使用 nvm,它可以随意切换 node 版本。下载 nvm,具体可以看本人另一篇文章:nvm的作用、下载、使用、以及Mac使用时遇到commond not found:nvm如何解决。 nvm官方࿱…...
Oracle 锁表的解决方法及避免锁表问题的最佳实践
背景介绍 在 Oracle 数据库中,锁表或锁超时相信大家都不陌生,是一个常见的问题,尤其是在执行 DML(数据操作语言)语句时。当一个会话对表或行进行锁定但未提交事务时,其他会话可能会因为等待锁资源而出现超…...
关于 vue+element 日期时间选择器 限制只能选当天以及30天之前的日期
业务需求,需要实现选择当天以及30天之前的日期,于是我想到的是利用picker-options去限制可选范围 代码如下 <el-date-pickerv-model"searchData.acceptTime"type"datetimerange"value-format"yyyy-MM-dd hh:mm:ss"styl…...
租辆酷车小程序开发(二)—— 接入微服务GRPC
vscode中golang的配置 设置依赖管理 go env -w GO111MODULEon go env -w GOPROXYhttps://goproxy.cn,direct GO111MODULEauto 在$GOPATH/src 外面且根目录有go.mod 文件时,开启模块支持 GO111MODULEoff 无模块支持,go会从GOPATH 和 vendor 文件夹寻找包…...
如何在 Ubuntu 22.04 上安装 Metabase 数据可视化分析工具
简介 Metabase 提供了一个简单易用的界面,让你能够轻松地对数据进行探索和分析。通过本文的指导,你将能够在 Ubuntu 22.04 系统上安装并配置 Metabase,并通过 Nginx 进行反向代理以提高安全性。本教程假设你已经拥有了一个非 root 用户&…...
MySQL 用户与权限管理
MySQL 是一种广泛使用的关系型数据库管理系统,支持多用户访问和权限控制。在多用户环境下,数据库安全至关重要,而用户和权限管理是数据库管理中最基础也是最重要的一部分。通过合理地创建和管理用户、分配和管理权限、使用角色权限,可以有效地保护数据库,确保数据的安全性…...
【Web前端】如何构建简单HTML表单?
HTML 表单是 Web 开发中非常重要的组成部分。它们是与用户交互的主要方式,能够收集用户输入的数据。表单的灵活性使它们成为 HTML 中最复杂的结构之一,但若使用正确的结构和元素,可以确保其可用性和无障碍性。 表单的基本结构 HTML 表单使用…...
Spring Boot 3 集成 Spring Security(3)数据管理
文章目录 准备工作新建项目引入MyBatis-Plus依赖创建表结构生成基础代码 逻辑实现application.yml配置SecurityConfig 配置自定义 UserDetailsService创建测试 启动测试 在前面的文章中我们介绍了 《Spring Boot 3 集成 Spring Security(1)认证》和 《…...
书生大模型实战营第四期-入门岛-4. maas课程任务
书生大模型实战营第四期-入门岛-4. maas课程任务 任务一、模型下载 任务内容 使用Hugging Face平台、魔搭社区平台(可选)和魔乐社区平台(可选)下载文档中提到的模型(至少需要下载config.json文件、model.safetensor…...
Spring ApplicationListener监听
【JavaWeb】Spring ApplicationListener-CSDN博客 ApplicationEvent以及Listener是Spring为我们提供的一个事件监听、订阅的实现,内部实现原理是观察者设计模式,设计初衷也是为了系统业务逻辑之间的解耦,提高可扩展性以及可维护性。事件发布…...
K8s调度器扩展(scheduler)
1.K8S调度器 筛选插件扩展 为了熟悉 K8S调度器扩展步骤,目前只修改 筛选 插件 准备环境(到GitHub直接下载压缩包,然后解压,解压要在Linux系统下完成) 2. 编写调度器插件代码 在 Kubernetes 源代码目录下编写调度插件…...
IntelliJ IDEA 中,自动导包功能
在 IntelliJ IDEA 中,自动导包功能可以极大地提高开发效率,减少手动导入包所带来的繁琐和错误。以下是如何在 IntelliJ IDEA 中设置和使用自动导包功能的详细步骤: 一、设置自动导包 打开 IntelliJ IDEA: 启动 IntelliJ IDEA 并打…...
Spring事务笔记
目录 1.Spring 编程式事务 2.Transactional 3.事务隔离级别 4.Spring 事务传播机制 什么是事务? 事务是⼀组操作的集合, 是⼀个不可分割的操作. 事务会把所有的操作作为⼀个整体, ⼀起向数据库提交或者是撤销操作请求. 所以这组操作要么同时成 功, 要么同时失败 1.Spri…...
SQLite 管理工具 SQLiteStudio 3.4.5 发布
SQLiteStudio 3.4.5 版本现已发布,它带来了大量的 bug 修复,并增加了一些小功能。SQLiteStudio 是一个跨平台的 SQLite 数据库的管理工具。 具体更新内容包括: 现在可以使用 Collations Editor 窗口在数据库中注册 Extension-based collatio…...
QT 实现组织树状图
1.实现效果 在Qt中使用QGraphicsItem和QGraphicsScene实现树状图,你需要创建自定义的QGraphicsItem类来表示树的节点,并管理它们的位置和连接,以下是实现效果图。 2.实现思路 可以看见,上图所示,我们需要自定义连线类和节点类。 每个节点类Node,需要绘制矩形框体文字…...
go-学习
文章目录 简介标识符字符串的拼接,关键字数据类型声明变量常量算术运算符关系运算符逻辑运算符位运算赋值运算符其他运算符 简介 Go 语言的基础组成有以下几个部分: 1.包声明 2.引入包 3.函数 4.变量 5.语句 & 表达式 6.注释 package main import &q…...
【面试分享】主流编程语言的内存回收机制及其优缺点
以下是几种主流编程语言的内存回收机制及其优缺点: 一、Java 内存回收机制: Java 使用自动内存管理,主要通过垃圾回收器(Garbage Collector,GC)来回收不再被使用的对象所占用的内存。Java 的垃圾回收器会定…...
STM32-- 串口发送数据
while(USART_GetFlagStatus(USART2,USART_FLAG_TXE)RESET);?? 答: 这行代码: while(USART_GetFlagStatus(USART2, USART_FLAG_TXE) RESET);的作用是等待串口 USART2 的发送数据寄存器(TXE,Transmit Dat…...
数据结构 (13)串的应用举例
前言 数据结构中的串(String),也称为字符串,是一种常见且重要的数据结构,在计算机科学中被广泛应用于各种场景。 一、文本处理 文本编辑器:在文本编辑器中,字符串被用来表示和存储用户输入的文本…...
qt-- - 版本和下载介绍
qt版本很多,每个大版本都有几个版本是长期支持的(LTS),最好使用长期支持的。 例如qt5.15 qt6.2 qt6.8 都是LTS版本的。 qt在线安装需要提供账号,之前安装qt6.8因为账号问题试了很长时间,密码错了。 …...
解锁 Vue 项目中 TSX 配置与应用简单攻略
在 Vue 项目中配置 TSX 写法 在 Vue 项目中使用 TSX 可以为我们带来更灵活、高效的开发体验,特别是在处理复杂组件逻辑和动态渲染时。以下是详细的配置步骤: 一、安装相关依赖 首先,我们需要在命令行中输入以下命令来安装 vitejs/plugin-v…...
ShuffleNet:一种为移动设备设计的极致高效的卷积神经网络
摘要 https://arxiv.org/pdf/1707.01083 我们介绍了一种名为ShuffleNet的计算效率极高的卷积神经网络(CNN)架构,该架构专为计算能力非常有限的移动设备(例如10-150 MFLOPs)而设计。新架构利用两种新操作:逐…...
yum源问题的解决方案
linux课堂作业 问题描述 yum 直接安装tree的问题截图 这个错误表明你的系统没有正确注册到 Red Hat Subscription Management(这个问题不用管),也没有配置有效的 YUM 软件仓库,因此无法安装或更新软件包。 解决方案(…...
在Linux中备份msyql数据库和表的详细操作
目录 前情提要 一、备份mysql数据库 原库展示 (一)新建一个数据库 (二)在linux根目录下找个位置暂时存放 (三)临时sql还原真正存放到库中 (四)查看是否备份成功 备份库成功展示 二、备份表的操作 编辑 原表emp展示 (一)快速新建一个原结构相同的表 (二)原表所…...
实时数仓Kappa架构:从入门到实战
引言 随着大数据技术的不断发展,企业对实时数据处理和分析的需求日益增长。实时数仓(Real-Time Data Warehouse, RTDW)应运而生,其中Kappa架构作为一种简化的数据处理架构,通过统一的流处理框架,解决了传统…...
【老白学 Java】Warship v2.0(四)
Warship v2.0(四) 文章来源:《Head First Java》修炼感悟。 上一篇文章中,老白仔细分析了 v2.0 的设计思路以及实现手段,如果大家有好的设计方案也可以自行尝试。 本篇文章的主要内容是对 Warship 类进行最后的修改&a…...
LLM之学习笔记(一)
前言 记录一下自己的学习历程,也怕自己忘掉了某些知识点 Prefix LM 和 Causal LM区别是什么? Prefix LM (前缀语⾔模型)和 Causal LM(因果语言模型)是两者不同类型的语言模型,它们的区别在于生…...
C# 反射详解
反射是C#中的一个强大特性,允许程序在运行时检查和操作类型和对象的信息。 通过反射,你可以获取类型的属性、方法、构造函数等信息,并可以动态创建对象、调用方法或访问属性,甚至可以实现某些框架或库的核心功能。 反射的基本概念…...
pgadmin安装后运行不能启动界面的问题
在本人机器上安装了pgsql10后,自带的pgadmin安装后运行时能打开edge并显示数据库server和数据库的,后来又安装了pgsql17,结果安装后想打开pgadmin,结果一直在等待最后,爆出类似于下面的错误。 pgAdmin Runtime Enviro…...
跳表(Skip List)
跳表(Skip List) 跳表是一种用于快速查找、插入和删除的概率型数据结构,通常用于替代平衡二叉搜索树(如 AVL 树或红黑树)。跳表通过在有序链表的基础上增加多层索引,使得查找操作的平均时间复杂度降低&…...
如何设置网站关键词/自媒体135网站
更正:我使用这种方式制作了完整安装包9.4.3,9.4.3安装好以后更新到9.4.4没有问题,然后从9.4.4更新到这个月的9.4.5时需要安装包中的.msi文件。这可能会给IT管理员带来不便,出现此问题时需要把Adobe Reader卸载,再重新安…...
坦克大战网站开发课程设计报告/郑州网站推广哪家专业
首先需要两个服务器(也可以用一台,但不推荐) 1服务器用yum安装Apachephpphp-mysql 2服务器用yum安装mysql 1服务器 用yum安装Apache和phpphp-mysql yum install httpd -y yum install php -y yum install php -mysql 完成后必须关闭防火墙和…...
怎么在服务器做网站/班级优化大师头像
作者简介: 李中凯老师,8年前端开发,前端负责人,擅长JavaScript/Vue。 公众号:1024译站 掘金文章专栏:https://juejin.im/user/57c7cb8a0a2b58006b1b8666/posts 主要分享:Vue.js, Jav…...
数据库网站建设公司/seo经理招聘
要实现如题的效果,可以利用表格来对图片进行排版,方法分为九步,具体如下:第一步:新建或打开Word文档,插入一个两行多列的表格(表格列数取决于图片的数量),如图。第二步:全选表格-右键…...
网站里自已的微信联系如何做/seo教程seo优化
举例:将i a b * c作为源代码输入到解析器里,则广义上的解析器的工作流程如下图: 发表时间:2009-10-17 最后修改:2011-03-04 < > 猎头职位: 上海: Senior Software Engine…...
csdn 博客 wordpress/百度广告投放价格表
概述在日常工作中,我们会有时会开慢查询去记录一些执行时间比较久的SQL语句,找出这些SQL语句并不意味着完事了,我们还需要用到explain这个命令来查看一个这些SQL语句的执行计划,查看该SQL语句有没有使用上了索引,有没有…...