设计公司企业网站详情,网上营销手段,湖南长沙怎么注册工作室,淘客网站建设带app前提条件
开发 Node.js#xff0c;首先就必须要安装 Node.js。推荐使用 nvm#xff0c;它可以随意切换 node 版本。下载 nvm#xff0c;具体可以看本人另一篇文章#xff1a;nvm的作用、下载、使用、以及Mac使用时遇到commond not found:nvm如何解决。
nvm官方#xff1…前提条件
开发 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-generator4快速生成项目目录--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_names0它是让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-pluginmysql_native_password--character-set-serverutf8mb4--collation-serverutf8mb4_general_cienvironment:- MYSQL_ROOT_PASSWORDtianbenchu- MYSQL_LOWER_CASE_TABLE_NAMES0ports:- 3306:3306volumes:- ./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)TINYINT1-128 ~ 1270 ~ 255SMALLINT2-32768 ~ 327670 ~ 65535MEDIUMINT3-8388608 ~ 83886070 ~ 16777215INT/INTEGER4-2147483648 ~21474836470 ~ 4294967295BIGINT8-9223372036854775808 ~ 92233720368547758070 ~ 18446744073709551615
4.3.2 字符串类型
类型说明使用场景CHAR固定长度小型固定长度的数据身份证号、手机号、电话、密码VARCHAR可变长度小型数据姓名、地址、品牌、型号、用户的评论、文章的标题TEXT可变长度字符个数大于 4000存储文章正文LONGTEXT可变长度超大型文本数据存储超大型文本数据
4.3.3 时间类型
类型字节大小示例DATE4‘2020-01-01’TIME3‘12:29:59’DATETIME8‘2020-01-01 12:29:59’YEAR1‘2017’TIMESTAMP4‘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 id5;5.3 改
UPDATE 表名 SET 列1值1, 列2值2, ... WHERE 条件// 例如
UPDATE Articles SET title黄鹤楼送孟浩然之广陵, content故人西辞黄鹤楼烟花三月下扬州。 WHERE id2;5.4 查
SELECT * FROM 表名;// 例如
SELECT * FROM Articles;* 表示所有的字段如果只需要查询某些字段指定即可。
SELECT id, title FROM Articles;条件查询
SELECT * FROM 表名 WHERE 条件;// 想查询id大于2的文章:
SELECT * FROM Articles WHERE id2;排序不添加声明默认为 ASC 升序。
-- 查询id大于2的文章按 id 从大到小排序即降序
SELECT * FROM Articles WHERE id2 ORDER BY id DESC;-- 查询id大于2的文章按 id 从小到大排列即升序
SELECT * FROM Articles WHERE id2 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 id2;// 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.jsonNode项目就会自动的连接到数据库上了。有三组配置分别是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 routerapp.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 router7.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 router9. 总结
9.1 创建流程
安装 docker 和 数据库图形化工具npm i -g express-generator4安装 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 中的启动方式修改为 nodemonnpm i sequelize mysql2安装 sequelize 与 mysql2 依赖包然后就可以使用 sequelize 操作 mysqlsequelize init初始化 sequelize修改 config/config.json 中数据库相关配置npm start启动服务
9.2 开发流程
创建数据库sequelize model:generate --name Article --attributes title:string,content:text创建模型注意 模型名为单数形式表名为复数形式并指定字段与类型上面一步会创建迁移文件修改迁移文件中字段的要求如本文将 title 字段设置为不可为nullsequelize 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/:idreq.query获取 URL 地址里的查询参数 /admin/articles ?titlehellocurrentPage2pageSize20req.body获取通过 POST、PUT 发送的数据
9.4 Sequelize ORM 操作数据库常用方法
findAll查询所有记录findAndCountAll查询所有记录并统计数据总数findByPk通过主键查询单条数据create创建新数据update更新数据destroy删除数据