综合案例

课程介绍

案例演示

核心知识点

  • 服务端开发
    • 服务端
    • Express
    • 数据库
    • HTTP
  • Ajax
  • 前后端交互开发

学习目标

核心:能掌握基本的网站前后端开发(博客系统)

  • 能掌握使用 express 开放静态资源
  • 能掌握模板引擎中提取母版页和模板继承的使用

  • 能理解路由模块的提取

  • 能理解数据库操作模块的封装
  • 能完成分类列表异步加载的前后端实现
  • 能完成删除分类功能
  • 能完成添加分类功能
  • 能完成编辑分类功能
  • 能完成用户列表功能
  • 能完成添加用户功能
  • 能完成删除用户功能
  • 能完成编辑用户功能
  • 能完成用户登录功能
  • 能完成用户退出功能
  • 能完成添加文章功能
  • 能完成文章列表功能
  • 能完成删除文章功能
  • 能完成编辑文章功能
  • 能完成添加广告图功能
  • 能完成广告图列表功能
  • 能完成删除广告图功能
  • 能完成编辑广告图功能
  • 能完成网站设置功能
  • 能完成个人中心功能
  • 能完成修改密码功能
  • 能根据文档使用 jquery-validation 验证插件
  • 能根据文档使用富文本编辑器插件
  • 能根据文档使用 ajv 验证插件
  • 能理解分页接口的实现
  • 能根据文档使用客户端分页插件
  • 能够理解 MVC 模式在项目中的意义
  • 分类管理
  • 用户管理
    • 能够使用Ajax方式添加管理员
    • 能够使用Ajax方式展示管理员列表
    • 能够使用Ajax方式完成编辑管理员
    • 能够使用Ajax方式完成删除管理员
  • 用户登录
    • 能够使用传统方式完成用户登录
    • 能够使用Ajax方式完成用户登录
  • 文章管理
    • 能够使用Ajax方式完成发布新文章
    • 能够通过查看文档掌握富文本编辑器的使用
    • 能够理解分页技术的交互过程
    • 能够通过查看文档使用客户端分页插件
    • 能够使用Ajax方式完成展示文章列表
    • 能够使用Ajax方式完成编辑文章
    • 能够使用Ajax方式完成删除文章
  • 评论管理
    • 能够使用Ajax+分页方式展示评论列表
    • 能够使用Ajax方式删除评论
    • 能够使用Ajax方式操作评论的通过状态
  • 网站设置
    • 能够掌握传统方式的表单文件提交前后端处理流程
    • 能够掌握Ajax异步表单文件提交前后端处理流程
    • 能够使用Ajax方式完成网站基本信息设置
  • 图片轮播管理
    • 能够使用Ajax方式完成添加轮播项
    • 能够使用Ajax方式展示轮播列表
    • 能够使用Ajax方式编辑轮播项
    • 能够使用Ajax方式删除轮播项
  • 菜单管理
    • 能够使用Ajax方式完成添加导航菜单项
    • 能够使用Ajax方式展示导航菜单列表
    • 能够使用Ajax方式编辑导航菜单项
    • 能够使用Ajax方式删除导航菜单项
  • 客户端前台
    • 能够使用Ajax方式加载轮播图列表
    • 能够使用Ajax+分页方式加载内容列表
    • 能够使用动态路由导航方式加载内容详情
    • 能够使用Ajax方式完成发布评论
    • 能够使用Ajax+异步分页(加载更多)方式完成评论列表展示

起步

初始化目录结构

  1. .
  2. ├── node_modules 第三方包存储目录(使用npm装包默认生成)
  3. ├── controllers 控制器
  4. ├── models 模型
  5. ├── public 静态资源
  6. ├── views 视图(存储HTML视图文件)
  7. ├── app.js 应用程序启动入口(加载Express,启动HTTP服务。。。)
  8. ├── config.js 应用配置文件
  9. ├── utils 存储工具模块(例如用来操作数据库的模块)
  10. ├── middlewares 自定义中间件
  11. ├── routes 存储路由相关模块
  12. ├── package.json 项目包说明文件,用来存储项目名称,第三方包依赖等信息(通过 npm init初始化)
  13. ├── package-lock.json npm产生的包说明文件(由npm装包自动产生)
  14. └── README.md 项目说明文件

使用 Express 创建 Web 服务

  1. 安装 Express
  1. npm i express
  1. app.js 中写入以下内容
  1. const express = require('express')
  2. const app = express()
  3. app.get('/', (req, res) => res.send('Hello World!'))
  4. app.listen(3000, () => console.log('Serve listening http://127.0.0.1:3000/'))
  1. 使用 nodemon 启动开发模式
  1. nodemon app.js
  1. 在浏览器中访问 http://127.0.0.1:3000/

导入并开放静态资源

  1. 将模板中的 html 静态文件放到项目的 views 目录中
  2. 将模板中的静态资源放到 public 目录中

  3. 在 Web 服务中把 public 目录开放出来

  1. ...
  2. const path = require('path')
  3. app.use('/public', express.static(path.join(__dirname, './public')))
  4. ...
  1. 测试访问 public 中的资源

使用模板引擎渲染页面

在 Node 中,不仅仅有 art-template 这个,还有很多别的。

  • ejs
  • pug
  • handlebars
  • nunjucks

参考文档:

  1. 安装
  1. npm i art-template express-art-template
  1. 配置
  1. ...
  2. // res.render() 的时候默认去 views 中查找模板文件
  3. // 如果想要修改,可以使用下面的方式
  4. app.set('views', '模板文件存储路径')
  5. // express-art-template 内部依赖了 art-template
  6. app.engine('html', require('express-art-template'));
  7. app.set('view options', {
  8. debug: process.env.NODE_ENV !== 'production'
  9. })
  10. ...
  1. 使用
  1. app.get('/', (req, res, next) => {
  2. res.render('index.html')
  3. })
  1. 修改页面中的静态资源引用路径让页面中的资源正常加载

提取路由模块

  • 简单应用提取一个路由文件模块

  • 将来路由越来越多,所以按照不同的业务分门别类的创建了多个路由文件模块放到了 routes 目录中,好管理和维护。

提取路由模块操作步骤:

  1. 创建路由文件

  2. 写入以下基本内容

  1. const express = require('express')
  2. const router = express.Router()
  3. // 自定义路由内容
  4. // router.get
  5. // router.get
  6. // router.post
  7. // ...
  8. module.exports = router
  1. app.js 中挂载路由模块
  1. ...
  2. // 加载路由模块
  3. const 路由模块 = require('路由模块路径')
  4. ...
  5. // 挂载路由模块到 app 上
  6. app.use(路由模块)
  7. ...
  1. 打开浏览器访问路由路径进行测试。

提取模板页

参考文档:

走通页面路由导航

路由表:

导入数据库

  • 新建一个数据库命名为 alishow
  • alishow 数据库中执行下发的数据库文件 ali.sql

封装数据库操作模块

参考文档:

  1. 安装
  1. npm i mysql
  1. 基本使用
  1. var mysql = require('mysql');
  2. var connection = mysql.createConnection({
  3. host : 'localhost',
  4. user : 'me',
  5. password : 'secret',
  6. database : 'my_db'
  7. });
  8. connection.connect();
  9. connection.query('SELECT 1 + 1 AS solution', function (error, results, fields) {
  10. if (error) throw error;
  11. console.log('The solution is: ', results[0].solution);
  12. });
  13. connection.end();
  1. 上面的方式是创建了单个连接,不靠谱,一旦这个连接挂掉,就无法操作数据库。我们推荐使用连接池的方式来操作数据库,所以将单个连接的方式改为如下连接池的方式。
  1. var mysql = require('mysql');
  2. var pool = mysql.createPool({
  3. connectionLimit : 10,
  4. host : 'example.org',
  5. user : 'bob',
  6. password : 'secret',
  7. database : 'my_db'
  8. });
  9. pool.query('SELECT 1 + 1 AS solution', function (error, results, fields) {
  10. if (error) throw error;
  11. console.log('The solution is: ', results[0].solution);
  12. });
  1. 我们在项目的很多地方都要操作数据库,所以为了方便,我们将数据库操作封装为了一个单独的工具模块放到了 utils/db.js 中,哪里使用就在哪里加载。
  1. const mysql = require('mysql')
  2. // 创建一个连接池
  3. // 连接池中创建了多个连接
  4. const pool = mysql.createPool({
  5. connectionLimit: 10, // 连接池的限制大小
  6. host: 'localhost',
  7. user: 'root',
  8. password: '123456',
  9. database: 'alishow63'
  10. })
  11. // 把连接池导出
  12. // 谁要操作数据库,谁就加载 db.js 模块,拿到 poll,点儿出 query 方法操作
  13. module.exports = pool
  1. 例如在 xxx 模块中需要操作数据库,则可以直接
  1. const db = require('db模块路径')
  2. // 执行数据库操作
  3. db.query()...

小结

分类管理

分类列表

一、页面加载,发起 Ajax 请求,获取分类列表数据,等待响应

  1. $.ajax({
  2. url: '/api/categories',
  3. method: 'GET',
  4. data: {},
  5. dataType: 'json',
  6. success: function (data) {
  7. // 1. 判断数据是否正确
  8. // 2. 使用模板引擎渲染列表数据
  9. // 3. 把渲染结果替换到列表容器中
  10. console.log(data)
  11. },
  12. error: function (err) {
  13. console.log('请求失败了', err)
  14. }
  15. })

二、服务端收到请求,提供请求方法为 GET, 请求路径为 /api/categories 的路由,响应分类列表数据

  1. // 1. 添加接口路由
  2. router.get('/api/categories/list', (req, res, next) => {
  3. // 2. 操作数据库获取数据
  4. db.query('SELECT * FROM `ali_cate`', function (err, data) {
  5. if (err) {
  6. throw err
  7. }
  8. // 3. 把数据响应给客户端
  9. res.send({
  10. success: true,
  11. data
  12. })
  13. })
  14. })

三、客户端正确的收到服务端响应的数据了,使用数据结合模板引擎渲染页面内容

  1. 配置客户端模板引擎

    1. 下载
    2. 引用
  2. 准备模板字符串

  1. <script type="text/html" id="list_template">
  2. {%each listData%}
  3. <tr>
  4. <td class="text-center"><input type="checkbox"></td>
  5. <td>{% $value.cate_name %}</td>
  6. <td>{% $value.cate_slug %}</td>
  7. <td class="text-center">
  8. <a href="javascript:;" class="btn btn-info btn-xs">编辑</a>
  9. <a data-id="{% $value.cate_id %}" name="delete" href="javascript:;" class="btn btn-danger btn-xs">删除</a>
  10. </td>
  11. </tr>
  12. {%/each%}
  13. </script>
  14. <script>
  15. // template('script 节点 id')
  16. // 当前页面是由服务端渲染出来的
  17. // 服务端先先对当前页面进行模板引擎处理
  18. // 服务端处理的时候根本不关心你的内容,只关心模板语法,我要解析替换
  19. // 当你的服务端模板引擎语法和客户端模板引擎语法一样的时候,就会产生冲突
  20. // 服务端会把客户端的模板字符串页给解析掉
  21. // 这就是所谓的前后端模板语法冲突
  22. // 修改模板引擎的语法界定符
  23. template.defaults.rules[1].test = /{%([@#]?)[ \t]*(\/?)([\w\W]*?)[ \t]*%}/;
  24. </script>

后续处理:

  1. <script>
  2. loadList()
  3. /*
  4. * 加载分类列表数据
  5. */
  6. function loadList() {
  7. $.ajax({
  8. url: '/api/categories',
  9. method: 'GET',
  10. data: {},
  11. dataType: 'json',
  12. success: function (data) {
  13. // 1. 判断数据是否正确
  14. // 2. 使用模板引擎渲染列表数据
  15. // 3. 把渲染结果替换到列表容器中
  16. if (data.success) {
  17. var htmlStr = template('list_template', {
  18. listData: data.data
  19. })
  20. $('#list_container').html(htmlStr)
  21. }
  22. },
  23. error: function (err) {
  24. console.log('请求失败了', err)
  25. }
  26. })
  27. }
  28. </script>

总结:

  • 客户端发起请求,等待响应
  • 服务端收到请求
  • 服务端处理请求
  • 服务端发送响应
  • 客户端收到响应
  • 客户端根据响应结果进行后续处理

删除分类

一、通过事件委托方式为动态渲染的删除按钮添加点击事件

  • 第一种把添加事件的代码放到数据列表渲染之后
  • 第二种使用事件代理(委托)的方式
  1. ...
  2. $('#list_container').on('click', 'a[name=delete]', handleDelete)
  3. ...

二、在删除处理中发起 Ajax 请求删除操作

  1. function handleDelete() {
  2. if (!window.confirm('确认删除吗?')) {
  3. return
  4. }
  5. var id = $(this).data('id')
  6. // 点击确定,发起 Ajax 请求,执行删除操作
  7. $.ajax({
  8. url: '/api/categories/delete',
  9. method: 'GET',
  10. data: {
  11. id: id
  12. },
  13. dataType: 'json',
  14. success: function (data) {
  15. console.log(data)
  16. },
  17. error: function (err) {
  18. console.log(err)
  19. }
  20. })
  21. return false
  22. }

三、在服务端添加路由接口处理删除操作

  1. router.get('/api/categories/delete', (req, res, next) => {
  2. // 获取要删除的数据id
  3. const { id } = req.query
  4. // 操作数据库,执行删除
  5. db.query('DELETE FROM `ali_cate` WHERE `cate_id`=?', [id], (err, ret) => {
  6. if (err) {
  7. throw err
  8. }
  9. res.send({
  10. success: true,
  11. ret
  12. })
  13. })
  14. })

四、客户端收到响应结果,判断如果删除成功,重新请求加载数据列表

  1. ...
  2. success: function (data) {
  3. if (data.success) {
  4. // 删除成功,重新加载列表数据
  5. loadList()
  6. }
  7. }
  8. ...

添加分类

基本步骤:

  1. 客户端发起请求,提交表单数据,等待服务端响应
  2. 服务端收到请求,处理请求,发送响应
  3. 客户端收到响应,根据响应结果进行后续处理

一、客户端发起添加请求

  • 表单的 submit 提交事件
  • 表单内容的获取 $(表单).serialize()
  1. // 表单提交
  2. // submit 提交事件
  3. // 1. button 类型为 submit 的会触发
  4. // 2. 文本框敲回车也会触发
  5. $('#add_form').on('submit', handleAdd)
  6. function handleAdd() {
  7. // serialize 会找到表单中所有的带有 name 的表单元素,提取对应的值,拼接成 key=value&key=value... 的格式数据
  8. var formData = $('#add_form').serialize()
  9. $.ajax({
  10. url: '/api/categories/create',
  11. method: 'POST',
  12. data: formData,
  13. // Content-Type 为 application/x-www-form-urlencoded
  14. // data: { // data 为对象只是为了让你写起来方便,最终在发送给服务器的时候,$.ajax 还会把对象转换为 key=value&key=value... 的数据格式
  15. // 普通的表单 POST 提交(没有文件),必须提交格式为 key=value&key=value... 数据,放到请求体中
  16. // key: value,
  17. // key2: value2
  18. // },
  19. dataType: 'json',
  20. success: function (resData) {
  21. console.log(resData)
  22. },
  23. error: function (error) {
  24. console.log(error)
  25. }
  26. })
  27. return false
  28. }

二、服务端处理请求

  1. 在 app.js 中配置解析表单 POST 请求体
  1. ...
  2. /**
  3. * 该方法目前可以在 4.16.0 之前使用,以后可能会废弃掉,建议还是使用之前 body-parser 的方式
  4. * 配置解析表单 POST 请求体(只能解析 Content-Type 为 application/x-www-form-urlencoded 数据)
  5. * 配置好以后,我们就可以在请求处理函数中通过 req.body 获取请求体数据
  6. * express 已经内置 body-parser
  7. * express 通过 express.urlencoded 方法包装了 body-parser
  8. */
  9. app.use(express.urlencoded())
  10. ...
  1. 执行数据库操作和发送响应数据
  1. router.post('/api/categories/create', (req, res, next) => {
  2. // 1. 获取表单数据
  3. const body = req.body
  4. // 2. 验证数据的有效性(永远不要相信客户端的输入)
  5. // 3. 操作数据库,执行插入操作
  6. // { cate_name: 'xxx', cate_slug: 'xxx' }
  7. // query 方法会把对象转为 filed1==value1&filed2=value2... 格式,替换到 sql 语句中的 ?
  8. db.query('INSERT INTO `ali_cate` SET ?', body, (err, ret) => {
  9. if (err) {
  10. throw err
  11. }
  12. // 4. 发送响应给客户端
  13. res.send({
  14. success: true,
  15. data: ret
  16. })
  17. })
  18. })

三、客户端收到响应,后续处理

  • 判断响应是否正确
  • 如果正确,则重新加载最新的列表数据,清空表单内容
  1. ...
  2. success: function (resData) {
  3. if (data.success) {
  4. // 添加成功,重新加载列表数据
  5. loadList()
  6. // 清空表单内容
  7. $('#add_form').find('input[name]').val('')
  8. }
  9. }
  10. ...

编辑分类

动态显示编辑模态框

一、点击编辑,弹出模态框

  • Bootstrap 自带的 JavaScript 组件:模态框

二、发起 Ajax 请求,获取 id=xxx 的分类数据

三、服务端收到请求,获取 id,操作数据库,发送响应

四、客户端收到服务端响应,进行后续处理

提交编辑表单完成编辑操作

一、注册编辑表单的提交事件

二、在提交事件中,获取表单数据,发送 Ajax POST请求 /api/categories/update,提交的数据放到请求体中

  • 表单隐藏域的使用

三、服务端收到请求,获取查询字符串中的 id,获取请求体,执行数据库修改数据操作,发送响应

四、客户端收到响应,根据响应结果做后续处理

简单优化

自动挂载路由

  • 自动加载路由模块
    • 使用 glob 获取指定的文件路径
    • 循环路由模块文件路径挂载路由模块
  1. // 获取 routes 目录中所有的路由文件模块路径
  2. const routerFiles = glob.sync('./routes/**/*.js')
  3. // 循环路由模块路径,动态加载路由模块挂载到 app 中
  4. routerFiles.forEach(routerPath => {
  5. const router = require(routerPath)
  6. if (typeof router === 'function') {
  7. // router.prefix 是我们添加的自定义属性,作用是用来设定路由的模块的访问前缀
  8. // 当路由模块没有 prefix 的时候,我们给一个 / 作为默认值,相当于没有前缀限制。
  9. // 因为所有的请求路径都以 / 开头
  10. app.use(router.prefix || '/', router)
  11. }
  12. })

客户端表单数据验证

服务端数据验证

  • 基本数据校验
  • 业务数据校验

服务端全局错误处理

利用错误处理中间件:http://expressjs.com/en/guide/error-handling.html

  1. app.use((err, req, res, next) => {
  2. // 1. 记录错误日志
  3. // 2. 一些比较严重的错误,还应该通知网站负责人或是开发人员等
  4. // 可以通过程序调用第三方服务,发短信,发邮件
  5. // 3. 把错误消息发送到客户端 500 Server Internal Error
  6. res.status(500).send({
  7. error: err.message
  8. })
  9. })

注意:执行错误处理中间件挂载的代码必须在我们的路由执行挂载之后

然后在我们的路由处理中,如果有错误,就调用 next 函数传递错误对象,例如

  1. rouget.get('xxx', (req, res, next) => {
  2. xxx操作
  3. if (err) {
  4. // 调用 next,传递 err 错误对象
  5. return next(err)
  6. }
  7. })

客户端统一错误处理

用户管理

用户列表

  1. 添加路由,渲染 admin/users.html 页面
  2. 在 users.html 页面中套用模板页

几个小点:

  • 把 art-template 文件资源的引用放到模板页中
  • 把修改模板引擎默认语法规则的代码放到模板页中
  • 把注册的全局 Ajax 错误处理方法放到模板页中

添加用户

  • 插件表单验证
  • 异步请求验证

删除用户

编辑用户

用户登录

  • 基本登录流程处理
  • 记录用户登录状态
  • 基本的页面访问权限认真,如果用户没有登录,则让用户跳转到登录页面进行登录

    用户登录处理流程

状态保持

  • HTTP 协议本身是无状态的

  • Cookie 发橘子,往背后贴纸条

  • Session 超市存物柜,东西放到柜子里,你拿着小票

使用 Session 存储登录状态

参考文档:https://github.com/expressjs/session

  1. 安装
  1. npm i express-session
  1. 配置
  1. ...
  2. const session = require('express-session')
  3. app.use(session({
  4. // 生成密文是有一套算法的来计算生成密文,如果网站都使用默认的密文生成方式, 就会有一定的重复和被破解的概率,所以为了增加这个安全性,算法对外暴露了一个混入私钥的接口,算法在生成密文的时候会混入我们添加的自定义成分
  5. secret: 'itcast',
  6. resave: false,
  7. // 如果为 true 无论是否往 Session 中存储数据,都直接给客户端发送一个 Cookie 小票
  8. // 如果为 false,则只有在往 Session 中写入数据的时候才会下发小票
  9. // 推荐设置为 true
  10. saveUninitialized: true
  11. }))
  12. ...
  1. 使用
  1. // 存储 Session 数据
  2. // 就想操作对象一样,往 Session 中写数据
  3. req.session.名字 =
  4. // 读取 Session 中的数据
  5. // 就是读取对象成员一样,读取 Session 中的数据
  6. req.session.名字

页面访问权限控制

  1. /**
  2. * 统一控制后台管理系统的页面访问权限
  3. * 相当于为所有以 /admin/xxxxx 开头的请求设置了一道关卡
  4. *
  5. */
  6. app.use('/admin', (req, res, next) => {
  7. // 1. 如果是登录页面 /admin/login,允许通过
  8. if (req.originalUrl === '/admin/login') {
  9. // 这里 next() 就会往后匹配调用到我们的那个能处理 /admin/login 的路由
  10. return next()
  11. }
  12. // 2. 其他页面都一律验证登录状态
  13. const sessionUser = req.session.user
  14. // 如果没有登录页, 让其重定向到登录页
  15. if (!sessionUser) {
  16. return res.redirect('/admin/login')
  17. }
  18. // 如果登录了,则允许通过
  19. // 这里调用 next 就是调用与当前请求匹配的下一个中间件路由函数
  20. // 例如,当前请求是 /admin/users ,则 next 会找到我们那个匹配 /admin/users 的路由去处理
  21. // /admin/categories ,则 next 会找到我们添加的那个 /admin/categories 的路由去处理
  22. next()
  23. })

用户退出

  • 实现用户退出接口
  1. /**
  2. * 用户退出
  3. */
  4. router.get('/admin/logout', (req, res) => {
  5. // 1. 清除登录状态
  6. delete req.session.user
  7. // 2. 跳转到登录页
  8. res.redirect('/admin/login')
  9. })
  • 使用 Ajax 请求完成用户退出

展示当前登录用户信息

参考文档:http://expressjs.com/en/4x/api.html#app.locals

  • 把 Session 中的当前登录用户数据传递到页面模板中
  • app.locals 的应用

简单点就是在每一次 render 页面的时候,把 req.session.user 传到模板中去使用。

当你需要在多个模板中使用相同的模板数据的时候,每一次 render 传递就麻烦了。所以 express 提供了一种简单的方式,我们可以把模板中公用的数据放到 app.locals 中。app.locals 中的数据可以在模板中直接使用。

Session 数据持久化

参考文档:https://github.com/chill117/express-mysql-session

Session 数据持久化的目的是为了解决服务器重启或者崩溃挂掉导致的 Session 数据丢失的问题。

因为默认情况下 Session 数据是存储在内存中的,服务器一旦重启就会导致 Session 数据丢失。

所了我们为了解决这个问题,把 Session 数据存储到了数据库中。

  1. 安装
  1. npm i express-mysql-session
  1. 配置
  1. ...
  2. const session = require('express-session')
  3. /**
  4. * 配置 Session 数据持久化
  5. * 参考文档:https://github.com/chill117/express-mysql-session#readme
  6. * 该插件会自动往数据库中创建一个 sessions 表,用来存储 Session 数据
  7. */
  8. const MySQLStore = require('express-mysql-session')(session)
  9. const sessionStore = new MySQLStore({
  10. host: 'localhost',
  11. port: 3306,
  12. user: 'root',
  13. password: '123456',
  14. database: 'alishow62'
  15. })
  16. const app = express()
  17. app.use(session({
  18. secret: 'keyboard cat',
  19. resave: false,
  20. saveUninitialized: true,
  21. store: sessionStore, // 告诉 express-session 中间件,使用 sessionStore 持久化 Session 数据
  22. }))
  23. ...

记住我(*)

记住我处理流程

对称加解密:加解密使用的私钥必须一致。

加密:

  1. const crypto = require('crypto');
  2. const cipher = crypto.createCipher('aes192', '私钥');
  3. let encrypted = cipher.update('要加密的数据', 'utf8', 'hex');
  4. encrypted += cipher.final('hex');
  5. console.log(encrypted);
  6. // Prints: ca981be48e90867604588e75d04feabb63cc007a8f8ad89b10616ed84d815504

解密:

  1. const crypto = require('crypto');
  2. const decipher = crypto.createDecipher('aes192', '私钥');
  3. const encrypted =
  4. '要解密的数据';
  5. let decrypted = decipher.update(encrypted, 'hex', 'utf8');
  6. decrypted += decipher.final('utf8');
  7. console.log(decrypted);
  8. // Prints: some clear text data

文章管理

添加文章

一、客户端表单提交(带有文件的POST请求)处理

  1. function handleSubmit() {
  2. // 1. 获取表单数据
  3. // multipart/form-data
  4. var formEl = $('#new_form')
  5. var formData = new FormData(formEl.get(0))
  6. // 2. 表单提交
  7. $.ajax({
  8. url: '/api/posts/create',
  9. type: 'POST',
  10. data: formData,
  11. processData: false, // 不处理数据
  12. contentType: false, // 不设置内容类型
  13. success: function (resData) {
  14. // 3. 根据响应结果做后续处理
  15. console.log(resData)
  16. },
  17. error: function (err) {
  18. console.log(err)
  19. }
  20. })
  21. return false
  22. }

二、服务端接口处理

  1. express 本身不处理文件上传
  2. 使用 multer 处理带有文件的表单 POST 请求

基本用法:(try-try-see)

  1. 安装
  1. npm i multer
  1. 基本示例
  1. var express = require('express')
  2. var multer = require('multer')
  3. var upload = multer({ dest: 'uploads/' }) // 指定上传文件的存储路径
  4. var app = express()
  5. // /profile 是带有文件的 POST 请求,使用 multer 解析文件上传
  6. // upload.single() 需要给定一个参数:告诉multer,请求体中哪个字段是文件
  7. app.post('/profile', upload.single('avatar'), function (req, res, next) {
  8. // req.file 是 `avatar` 文件的相关信息(原本的文件名,新的唯一名称,文件保存路径,文件大小...)
  9. // req.body 是请求体中的那些普通的文本字段
  10. // 数据库中不存储文件,文件还是存储在磁盘上,数据库中存储文件在我们 Web 服务中的 url 资源路径
  11. })
  1. multer 保存的文件默认没有后缀名,如果需要的话,就需要下面这样来使用
  1. var storage = multer.diskStorage({
  2. // 可以动态处理文件的保存路径
  3. destination: function (req, file, cb) {
  4. cb(null, '/tmp/my-uploads')
  5. },
  6. // 动态的处理保存的文件名
  7. filename: function (req, file, cb) {
  8. cb(null, file.fieldname + '-' + Date.now()) // 这里的关键是这个时间戳,能保证文件名的唯一性(不严谨)
  9. }
  10. })
  11. var upload = multer({ storage: storage })
  12. app.post('/profile', upload.single('avatar'), function (req, res, next) {
  13. // req.file 是 `avatar` 文件的相关信息(原本的文件名,新的唯一名称,文件保存路径,文件大小...)
  14. // req.body 是请求体中的那些普通的文本字段
  15. // 数据库中不存储文件,文件还是存储在磁盘上,数据库中存储文件在我们 Web 服务中的 url 资源路径
  16. })
  1. 处理多文件

有多个名字都一样的 file 类型的 input

  1. app.post('/photos/upload', upload.array('photos', 12), function (req, res, next) {
  2. // req.files is array of `photos` files
  3. // req.body will contain the text fields, if there were any
  4. })

处理多个不同名字的 file 类型的 input:

  1. var cpUpload = upload.fields([{ name: 'avatar', maxCount: 1 }, { name: 'gallery', maxCount: 8 }])
  2. app.post('/cool-profile', cpUpload, function (req, res, next) {
  3. // req.files is an object (String -> Array) where fieldname is the key, and the value is array of files
  4. //
  5. // e.g.
  6. // req.files['avatar'][0] -> File
  7. // req.files['gallery'] -> Array
  8. //
  9. // req.body will contain the text fields, if there were any
  10. })

富文本编辑器 wangEditor

常见的富文本编辑器:

这里我们以使用 wangEditor 为例:

文章列表

编辑文章

删除文章

网站设置

个人中心

轮播广告管理