美文网首页
9.CRUD 系统设计

9.CRUD 系统设计

作者: 璎珞纨澜 | 来源:发表于2019-04-08 16:42 被阅读0次

    初始化项目与安装插件

    本次设计的CRUD项目开发选用 express 框架;模板引擎选择 art-template;网页 css 样式选择 bootstrap; 获取表单 POST 请求体的API:body-parser。


    1.初始化package.json 2.安装express 3.安装art-template 4.安装bootstrap 5.安装body-parser

    路由设计

    请求方法 请求路径 get参数 post参数 作用
    GET /student 渲染首页
    GET /student/new 渲染添加学生页面
    POST /student/new name、age、gender、major 处理添加学生请求
    GET /student/edit id 渲染编辑页面
    POST /students/edit id、name、age、gender、major 处理编辑请求
    GET /student/delete id 处理删除请求

    提取路由模块

    路由模块

    • 职责:
      • 处理路由
      • 根据不同的请求方法和请求路径设置具体的请求处理函数
    • 模块职责要单一,不要乱写
    • 划分模块的目的就是为了增强项目代码的可维护性,提升开发效率
      router.js
    var express = require('express')
    
    // 1. 创建一个路由容器
    var router = express.router()
    
    // 2. 把路由都挂载到 router 路由容器中
    router.get('/students', function (req, res) {
    })
    router.get('/students/new', function (req, res) {
    })
    router.post('/students/new', function (req, res) {
    })
    router.get('/student/edit', function (req, res) {
    })
    router.post('/student/edit', function (req, res) {
    })
    router.get('/students/delete', function (req, res) {
    })
    
    // 3. 把 router 导出 
    module.exports = router
    

    app.js

    var router = require('./router')
    
    // 挂载路由
    app.use(router)
    

    设计 html 页面

    在 bootstrap 官网找个css模板仿照着写:
    bootstrap官网dashboard模板
    bootstrap表单

    从文件中读取数据

    为了将数据持久化,我们设计将数据存到 json 文件中,通过读取 json 文件得到学生对象,进而渲染整个学生列表页面。简单的代码示例如下:
    db.json

    {
        "students":[
            {"id":1, "name": "张三", "gender": 0, "age": 20, "major": "英语"},
            {"id":2, "name": "李四", "gender": 0, "age": 20, "major": "英语"},
            {"id":3, "name": "小红", "gender": 1, "age": 20, "major": "数学"},
            {"id":4, "name": "哈哈", "gender": 1, "age": 20, "major": "英语"},
            {"id":5, "name": "嘻嘻", "gender": 0, "age": 20, "major": "数学"},
            {"id":6, "name": "小明", "gender": 0, "age": 20, "major": "英语"}
        ]
    }
    

    app.js

    var express = require('express')
    var fs = require('fs')
    
    var app = express()
    
    app.use('/node_modules', express.static('node_modules'))
    app.use('/public', express.static('public'))
    
    app.engine('html',require('express-art-template'))
    
    app.get('/students', function (req, res){
        // readFile的第二个参数是可选的, 传入 utf8 就是把读取到的文件直接按照 utf8编码转换
        // 除了这样来转换之外,也可以通过 data.toString 的方式
        fs.readFile('./db.json', 'utf8', function (err, data){
            if (err) {
                return res.status(500).send('Server error')
            }
            //从文件读取到的数据一定是字符串,所以一定要手动转成对象才能进行渲染
            var students = JSON.parse(data).students
            res.render('new.html', {
                students: students
            })
        })
    })
    app.listen(3000, function () {
        console.log('running 3000...')
    })
    

    设计操作数据的 API 文件模块

    • 异步封装 API
      由于我们的业务操作涉及到增删改查,需要处理文件数据,因此考虑把这部分操作封装成通用方法。而我们操作文件数据的时候调用的方法都是异步的方法,因此封装 API 的时候要考虑异步封装。在这里举个简单的异步封装例子:
    // 封装API,函数功能为延时1s,函数参数为回调函数
    function delay(callback) {
      setTimeout(function () {
        var data = 'hello'
        callback(data)
      }, 1000)
    }
    // 调用API,传入的参数(回调函数)是用户自定义的函数,比如这里打印log
    delay(function (data) {
      console.log(data)
    })
    

    代码

    思路大体是这样,详细内容见Code:
    app.js

    var express = require('express')
    var router = require('./router')
    var bodyParser = require('body-parser')
    
    var app = express()
    
    app.use('/node_modules', express.static('node_modules'))
    app.use('/public', express.static('public'))
    
    app.engine('html',require('express-art-template'))
    
    app.use(bodyParser.urlencoded({ extended: false }))
    app.use(bodyParser.json())
    
    app.use(router)
    
    app.listen(3000, function () {
        console.log('running 3000...')
    })
    

    router.js

    var fs = require('fs')
    var crud = require('./crud')
    
    var express = require('express')
    var router = express.Router()
    
    router.get('/students', function (req, res) {
        crud.find(function (err, students) {
            if (err) {
                return res.status(500).send('Server error.')
            }
            res.render('index.html', {
                students: students
            })
        })
    })
    
    router.get('/students/new', function (req, res) {
        res.render('new.html')
    })
    
    router.post('/students/new', function (req, res) {
        crud.save(req.body, function (err) {
            if(err) {
                return res.status(500).send('Server error.')
            }
            res.redirect('/students')
        })
    })
    
    router.get('/students/edit', function (req, res) {
        crud.findById(req.query.id, function (err, student) {
            if(err) {
                return res.status(500).send('Server error.')
            }
            res.render('edit.html', {
                student: student
            })
        })
    })
    
    router.post('/students/edit', function (req, res) {
        crud.updateById(req.body, function (err) {
            if(err) {
                return res.status(500).send('Server error.')
            }
            res.redirect('/students')
        })
    })
    
    router.get('/students/delete', function (req, res) {
        crud.deleteById(req.query.id, function (err) {
            if(err) {
                return res.status(500).send('Server error.')
            }
            res.redirect('/students')
        })
    })
    
    module.exports = router
    

    crud.js

    
    var fs = require('fs')
    
    var dbPath = './db.json'
    
    /**
     * 获取学生列表
     * @param  {Function} callback 回调函数
     */
    exports.find = function (callback) {
        fs.readFile(dbPath, 'utf8', function (err, data) {
            if (err) {
                return callback(err)
            }
            callback(null, JSON.parse(data).students)
        })
    }
    
    /**
     * 根据学号获取学生信息对象
     * @param  {Number} id 学生学号
     * @param  {Function} callback 回调函数 
     */
    exports.findById = function (id, callback) {
        fs.readFile(dbPath, 'utf8', function (err, data) {
            if(err) {
                return callback(err)
            }
            var students = JSON.parse(data).students
            var ret = students.find(function (item) {
                return item.id === parseInt(id)
            })
            callback(null, ret)
        })
    }
    
    /**
     * 添加保存学生
     * @param  {Object} student 学生对象
     * @param  {Function} callback 回调函数
     */
    exports.save = function (student, callback) {
        fs.readFile(dbPath, 'utf8', function (err, data) {
            if (err) {
                return callback(err)
            }
            var students = JSON.parse(data).students
    
            student.id = students[students.length - 1].id + 1
    
            students.push(student)
    
            var fileData = JSON.stringify({
                students: students
            })
    
            fs.writeFile(dbPath, fileData, function (err) {
                if (err) {
                    return callback(err)
                }
                callback(null)
            })
        })
    }
    
    /**
     * 更新学生
     * @param  {Object} student 学生对象
     * @param  {Function} callback 回调函数
     */
    exports.updateById = function (student, callback) {
            fs.readFile(dbPath, 'utf8', function (err, data) {
            if (err) {
                return callback(err)
            }
            var students = JSON.parse(data).students
    
            // 注意:这里记得把 id 统一转换为数字类型
            student.id = parseInt(student.id)
            // EcmaScript 6 中的一个数组方法:find
            // 需要接收一个函数作为参数
            // 当某个遍历项符合 item.id === student.id 条件的时候,find 会终止遍历,同时返回遍历项
            var stu = students.find(function (item) {
                return item.id === student.id
            })
            for (var key in student) {
                stu[key] = student[key]
            }
            var fileData = JSON.stringify({
                students: students
            })
            fs.writeFile(dbPath, fileData, function (err) {
                if (err) {
                    return callback(err)
                }
                callback(null)
            })
        })
    }
    
    /**
     * 删除学生
     * @param  {Number} id 学生学号
     * @param  {Function} callback 回调函数 
     */
    exports.deleteById = function (id, callback) {
        fs.readFile(dbPath, 'utf8', function (err, data) {
            if(err) {
                return callback(err)
            }
            var students = JSON.parse(data).students
    
            var deleteId = students.findIndex(function (item) {
                return item.id === parseInt(id)
            })
    
            students.splice(deleteId, 1)
    
            var fileData = JSON.stringify({
                students: students
            })
            
            fs.writeFile(dbPath, fileData, function (err) {
                if(err) {
                    return callback(err)
                }
                callback(null)
            })
        })
    }
    

    index.html

    <!DOCTYPE html>
    <html lang="zh-CN">
      <head>
        <meta charset="utf-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <!-- 上述3个meta标签*必须*放在最前面,任何其他内容都*必须*跟随其后! -->
        <meta name="description" content="">
        <meta name="author" content="">
        <link rel="icon" href="../../favicon.ico">
    
        <title>Dashboard Template for Bootstrap</title>
    
        <!-- Bootstrap core CSS -->
        <link href="/node_modules/bootstrap/dist/css/bootstrap.min.css" rel="stylesheet">
    
        <!-- Custom styles for this template -->
        <link href="/public/cssdashboard.css" rel="stylesheet">
    
      </head>
    
      <body>
        <div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main">
          <h2 class="sub-header">学生名单</h2>
          <a class="btn btn-success" href="/students/new">添加学生</a>
          
          <div class="table-responsive">
            <table class="table table-striped">
              <thead>
                <tr>
                  <th>学号</th>
                  <th>姓名</th>
                  <th>性别</th>
                  <th>年龄</th>
                  <th>专业</th>
                </tr>
              </thead>
              <tbody>
                {{ each students }}
                <tr>
                  <td>{{ $value.id }}</td>
                  <td>{{ $value.name }}</td>
                  <td>{{ $value.gender }}</td>
                  <td>{{ $value.age }}</td>
                  <td>{{ $value.major }}</td>
                  <td>
                    <a href="/students/edit?id={{ $value.id }}">编辑</a>
                    <a href="/students/delete?id={{ $value.id }}">删除</a>
                  </td>
                </tr>
                {{ /each }}
              </tbody>
            </table>
          </div>
        </div>
      </body>
    </html>
    

    new.html

    <!DOCTYPE html>
    <html lang="zh-CN">
      <head>
        <meta charset="utf-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <!-- 上述3个meta标签*必须*放在最前面,任何其他内容都*必须*跟随其后! -->
        <meta name="description" content="">
        <meta name="author" content="">
        <link rel="icon" href="../../favicon.ico">
    
        <title>Dashboard Template for Bootstrap</title>
    
        <!-- Bootstrap core CSS -->
        <link href="/node_modules/bootstrap/dist/css/bootstrap.min.css" rel="stylesheet">
    
        <!-- Custom styles for this template -->
        <link href="/public/cssdashboard.css" rel="stylesheet">
    
      </head>
    
      <body>
        <div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main">
          <h2 class="sub-header">添加学生</h2>
          <form action="/students/new" method="post">
            <div class="form-group">
              <label for="">姓名</label>
              <input class="form-control" type="text" id="" name="name" required minlength="2" maxlength="10">
            </div>
            <div class="form-group">
              <label for="">性别</label>
              <div class="radio">
                <label>
                  <input type="radio" name="gender" id="optionsRadios1" value="0" checked> 男
                </label>
              </div>
              <div class="radio">
                <label>
                  <input type="radio" name="gender" id="optionsRadios2" value="1"> 女
                </label>
              </div>
            </div>
            <div class="form-group">
              <label for="">年龄</label>
              <input class="form-control" type="number" id="" name="age" required min="1" max="150">
            </div>
            <div class="form-group">
              <label for="">专业</label>
              <input class="form-control" type="text" id="" name="major">
            </div>
            <button type="submit" class="btn btn-success">Submit</button>
          </form>
        </div>
      </body>
    </html>
    

    edit.html

    <!DOCTYPE html>
    <html lang="zh-CN">
      <head>
        <meta charset="utf-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <!-- 上述3个meta标签*必须*放在最前面,任何其他内容都*必须*跟随其后! -->
        <meta name="description" content="">
        <meta name="author" content="">
        <link rel="icon" href="../../favicon.ico">
    
        <title>Dashboard Template for Bootstrap</title>
    
        <!-- Bootstrap core CSS -->
        <link href="/node_modules/bootstrap/dist/css/bootstrap.min.css" rel="stylesheet">
    
        <!-- Custom styles for this template -->
        <link href="/public/cssdashboard.css" rel="stylesheet">
    
      </head>
    
      <body>
        <div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main">
          <h2 class="sub-header">编辑学生</h2>
          <form action="/students/edit" method="post">
            <input type="hidden" name="id" value="{{ student.id }}">
            <div class="form-group">
              <label for="">姓名</label>
              <input class="form-control" type="text" id="" name="name" required minlength="2" maxlength="10" value="{{ student.name }}">
            </div>
            <div class="form-group">
              <label for="">性别</label>
              <div class="radio">
                <label>
                  <input type="radio" name="gender" id="optionsRadios1" value="0" checked="true"> 男
                </label>
              </div>
              <div class="radio">
                <label>
                  <input type="radio" name="gender" id="optionsRadios2" value="1"> 女
                </label>
              </div>
            </div>
            <div class="form-group">
              <label for="">年龄</label>
              <input class="form-control" type="number" id="" name="age" required min="1" max="150" value="{{ student.age }}">
            </div>
            <div class="form-group">
              <label for="">专业</label>
              <input class="form-control" type="text" id="" name="major" value="{{ student.major }}">
            </div>
            <button type="submit" class="btn btn-success">Submit</button>
          </form>
        </div>
      </body>
    </html>
    

    总结

    • 处理模板
    • 配置开放静态资源
    • 配置模板引擎
    • 简单路由: /students 渲染静态页面
    • 路由设计
    • 提取路由模块
    • 由于接下来一些业务操作都需要处理文件数据,所以我们需要异步封装 API crud.js
    • crud.js 文件结构
      • 查询所有学生列表数据: find
      • 通过学生id查询学生列表数据:findById
      • 保存学生列表数据:save
      • 通过学生id更新学生列表数据:updateById
      • 通过学生id删除学生列表数据:deleteById
    • 实现具体功能
      • 通过路由收到请求
      • 接收请求中的数据 (get、post)
        • req.query
        • req.body
      • 调用数据操作 API 处理数据
      • 根据操作结果给客户端发送响应
        • 渲染页面
        • 重定向
    • 业务功能顺序:
      • 列表
      • 添加
      • 编辑
      • 删除

    相关文章

      网友评论

          本文标题:9.CRUD 系统设计

          本文链接:https://www.haomeiwen.com/subject/ltpnbqtx.html