2018-04-18 JS进阶实战21-27

又开始做练习了啊,探索编程世界的奥秘又开始了,升级打怪,哈哈

1、有些小虐的21-27 的练习题来了

对于即将要做的练习题目,先全部浏览了一下,接下来真真正正的是要有一场硬仗需要打,老师里面写的代码的判断,自己要花时间细细的品味,才能对于项目的一点点的演进有一个深刻的理解。这一部分搞定之后,练习题这块剩余的几个章节将不再是太大的问题。按照这个方式,一点点的啃也就解决了,中间老师对于Express的学习提出了一些问题,需要我们各自思考,认真的作答。

2、更好的API能力

开发一个webapp项目,项目能力全部体现在路由上,路由可以成为webapp提供给外界的API。

前面我们将获取页面的路由 router.page.js 和获取数据的路由 router.api.js 分开写在两个不同的 js 文件中。这是一个很好的习惯,不仅在业务功能上区分开,而且可以由不同的工程师来分布维护。

在讨论设计路由的API时,提供数据的 router.api.js 非常重要,而提供页面的 route.page.js 相对宽松一些。原因是,提供数据的路由API 不仅会给前端用,还可能给 Android 应用,ios应用、小程序等各式各样的客户端使用。如果不能采用一种标准化的设计方式,就会乱掉。

既有的 API 能力

路由 功能
[GET] /api/users 创建项目时自带的功能(暂时留着)
[GET] /api/posts 获取文章列表
[POST] /api/posts/create 提交新文章
[POST] /api/posts/edit 提交文章修改内容
[GET] /api/posts/one 获取特定文章的内容

增加 API 版本控制
有些时候我们还可以对API进行版本分类,以更好的升级 API。

[GET]    /api/v1/users
[GET]     /api/v1/posts
[POST]  /api/v1/posts/create
[POST]  /api/v1/posts/edit
[GET]     /api/v1/posts/one

在未来,你可以针对某个路由进行升级。比如下面单独升级 /api/v1/posts/one 为 v2。

[GET]    /api/v1/users
[GET]     /api/v1/posts
[POST]  /api/v1/posts/create
[POST]  /api/v1/posts/edit
[GET]     /api/v2/posts/one

尽可能用 http 自身的办法来区分路由功能
HTTP 提供了很多方法,和数据有关的有以下的几类。

GET(SELECT): 从服务器取出资源(一项或者是多项)。
POST(CREATE):在服务器新建一个资源。
PUT(UPDATE):在服务器更新资源(客户端提供改变后的完整资源)。
PATCH(UPDATE):在服务器更新资源(客户端提供改变的属性)。
DELETE(DELETE):从服务器删除资源。

依据这个原则,我们把 API 调整如下。

[GET]       /api/v1/users
[GET]       /api/v1/posts
[POST]    /api/v1/posts/create
[PATCH]  /api/v1/posts/edit
[GET]      /api/v1/posts/one

避免使用动词来区分路由

在上面用了create 、edit,这类动词看上去直观的表达了路由的意思,但是动词多了后就容易产生歧义,路由尽量避免。

[GET]       /api/v1/users
[GET]       /api/v1/posts
[POST]    /api/v1/posts   //去掉动词
[PATCH]  /api/v1/posts  //去掉动词
[GET]      /api/v1/posts/one

对于 posts 这个资源,通过 HTTP 的GET、POST、PUT 三个方法就能区分是什么事情,不需要多余动词加以区分。

用资源 ID 而不是单词区分资源

[GET]      /api/v1/posts            // 获取文章列表
[GET]      /api/v1/posts/one     // 获取某一个文章列表

改成

[GET]      /api/v1/posts            // 获取文章列表
[GET]      /api/v1/posts/:id      // 获取某一个文章

最终的路由

[GET]        /api/v1/users
[GET]        /api/v1/posts
[POST]     /api/v1/posts  
[PATCH]   /api/v1/posts/:id 
[GET]        /api/v1/posts/:id 
总结
1、尽量用 HTTP 的方法来区分功能
2、避免在路上使用动词区分功能
3、针对某个资源,用和该资源相关的 id (或唯一值)

以上我们整理了和数据相关的 API ,数据 API 属于服务端的范畴,页面只能用唯一的 HTTP 的 GET 方法,而且页面是属于客户端范畴。因此,页面的路由设计相对宽容一些。

最后,还要修改 app.js

app.use ('/api', api);

修改为

app.use('/api/v1', api );

最后一部分开始在编辑器里面实战。


21 章节练习的时候,又遇到了相对路径的问题,最终还是调试完成了。

3、更好的结果

在router.api.js 中,我们利用 res.json()方法来响应请求 并 返回结果数据。

res.json({ success: false });
res.json({ success: true });
res.json({ success: true, postsList: posts });

在处理结果时,我们将错误分为两类

  1. WebAPP 不提供的能力
  2. WebAPP 自身的逻辑错误 (或数据库发生的错误)

WebAPP 不提供的能力

WebAPP 提供页面 和 数据的能力是有限的。当客户端发起了一个不存在的数据路由访问,势必拿不到页面或者是数据。

在前面的小结中,我们知道在 app.js 中有统一的错误处理。

// filepath : app.js
// catch 404 and forward to error handler

app.use(function (req ,res ,next){
    var err = new Error('Not Found');
    err.status = 400;
    next(err);
});


// error handler
app.use ( function (err, req ,res ,next ){
    //set locals , only providing error in development

    res.locals.message = err.message;
    res.locals.error = req.app.get('env') === 'development' ? err :{};

    // render the error page
    res.status( err.status || 500);
    res.render('error');

});

修改数据返回的错误
通过 { success: false } 或者是 { success: true} 来区分是否存在错误逻辑似乎不是特别的好。

在 HTTP 请求是,响应中都会默认有一个 status 参数,成为状态码 ,如果状态码不为 2XX,就表明错误处理错误。为什么不用 res. status 返回500的方式呢?

我们可以借助这个统一的错误中枢来统一处理所有的错误。

// filepath: route.api.js

/* GET posts lists */
router.get('/posts', function(req, res, next) {
  PostModel.find({}, {}, function(err, posts) {
    if (err) {
      res.json({ success: false });
    } else {
      res.json({ success: true, postsList: posts });
    }
  });
});

修改成:

// filepath: route.api.js

/* GET posts lists */
router.get('/posts', function(req, res, next) {
  PostModel.find({}, {}, function(err, posts) {
    if (err) {
      err.status = 500;
      next(err);
    } else {
      res.json({ postsList: posts });
    }
  });
});

我们在err对象中加入 status = 500,并把错误送给错误中枢统一返回给用户。

其实在 app.js 中的错误处理,如果你不主动设置 err.status = 500, 它会主动帮默认设置为 500。

// filepath: app.js

// catch 404 and forward to error handler
app.use(function(req, res, next) {
  var err = new Error('Not Found');
  err.status = 404;
  next(err);
});

// error handler
app.use(function(err, req, res, next) {
  // set locals, only providing error in development
  res.locals.message = err.message;
  res.locals.error = req.app.get('env') === 'development' ? err : {};

  // render the error page
  // 如果不设置err.status,或默认设置为500
  res.status(err.status || 500);
  res.render('error');
});

因此,可以省略状态码的设置,直接调用 next(err)。

// filepath: route.api.js

/* GET posts lists */
router.get('/posts', function(req, res, next) {
  PostModel.find({}, {}, function(err, posts) {
    if (err) {
      next(err);
    } else {
      res.json({ postsList: posts });
    }
  });
});

其他路由修改参考上面的样子。

修改客户端(接受结果)的处理
在此前,posts.ejs 中获取数据是如下处理

fetchData () {
  axios.get('/api/v1/posts')
    .then(function(response) {
      vm.postsList = response.data.postsList;
      vm.postsList.forEach((element) => element.url = '/posts/show?id=' + element._id);
    })
}

这个处理并没有进行错误检查。上面我们知道,当服务端出现错误会返回状态码500。因此我们要接住这个错误才能保证逻辑正确处理。

fetchData () {
  axios.get('/api/v1/posts')
    .then(function(response) {
      return response.data;
    })
    .then(function(data) {
      vm.postsList = data.postsList;
      vm.postsList.forEach((element) => element.url = '/posts/show?id=' + element._id);
    })
    .catch(function(err) {
      alert(err);
    })
}

axios.get() 发起一个 get 请求,如果返回的状态码不是 2xx,就会抛出一个异常并执行 catch() 函数。

总结

  1. 尽量通过统一的错误处理逻辑。
  2. 尽量使用状态码,而不是自己设计一个错误
    值。例如:success:false/true。
  3. 客户端页面要对错误进行错误处理以保证健壮性。

创建和修改数据时,该不该返回数据?(特别提醒)
在 route.api.js中创建post的路由处理中,可以把写入到数据库的数据重新返回给客户端。

/* POST create post */
router.post('/posts', function(req, res, next) {
  var title = req.body.title;
  var content = req.body.content;

  var post = new PostModel();
  post.title = title;
  post.content = content;
  post.save(function(err, doc) {
    if (err) {
      next(err);
    } else {
      res.json({post: doc}); // 注意这里
    }
  });
});

一般情况下,新创建的数据需要返回给客户端一份,为什么呢?因为写入到数据库后会给这条数据产生一个唯一的id,而客户端拿到这个id可以进行一些交互操作。

submit () {
  axios.post('/api/v1/posts',
    {
      title: vm.title,
      content: vm.content
    })
    .then(function(response) {
      return response.data;
    })
    .then(function(data) {
      // 注意这句
      window.location = '/posts/show?id=' + data.post._id;
    })
    .catch(function(err) {
      alert(err);
    })
  }
}

可以通过服务端返回的post数据来打开刚刚创建的文章页面。

编辑文章时,服务端不需要返回完整的数据。

/* PATCH edit post */
router.patch('/posts/:id', function(req, res, next) {
  var id = req.params.id;
  var title = req.body.title;
  var content = req.body.content;

  PostModel.findOneAndUpdate({ _id: id }, { title, content }, function(err) {
    if (err) {
      next(err);
    } else {
      res.json({}); // 不需要返回文章数据
    }
  });
});

因为编辑是对一个已经在数据库中的数据进行编辑,客户端自身有这条数据的id。 所以,在 ./views/edit.ejs中,只需要用本地已经有的id来进行页面跳转。

submit () {
  axios.patch('/api/v1/posts/' + postId,
    {
      title: vm.title,
      content: vm.content
    })
    .then(function(response) {
      return response.data;
    })
    .then(function(data) {
      // 注意这里,用的是既有的postId来跳转到文章页面
      window.location = '/posts/show?id=' + postId
    })
    .catch(function(err) {
      alert(err);
    })
}

这也说明了http的post和patch的使用区别,post可能需要服务端返回数据,patch不需要服务端返回数据。

22 小节的实战: 主要是针对于页面有错误的时候,不应该单纯的return success,而是返回相关的错误界面给到用户。后面的练习题,主要是针对于 Post

4、通过 22 个小节,我们做出了一个简单的内容发布 WebAPP。在通过进一步之前,我建议你停下来并认真复习和思考一下。
  • 开发一个 WebAPP 为什么需要 Express?Express能带给你什么好处?

  • WebAPP 中的 package.json 在整个工程起到了什么作用?

  • 在 Express 中 Router 的价值,为什么 Router 作为一个中间件会将其格外的突出?

  • 为什么要对 Router 进行分类管理,分成 route.api.js 和 route.page.js 的好处是什么?

  • 在整个数据流中,为什么要传递req、res 和 next 这三个对象?

  • app.js中的两个错误处理函数的(错误中枢)的工作原理,统一错误的价值是什么?

  • 视图引擎是必须的吗?Express和 ejs的关系是什么?使用 ejs 是必须的吗?

  • 在构建视图时,res.render 是如何定位到页面文件的?可以改变定位的路径吗?如果不用res.render() 该怎么构建页面?

  • ejs 中 <%- %> 和 <%= %> 的差异是什么?

  • mongodb中的 schema 是什么意思?

5、添加导航条

24小节的实战:就是给每个界面添加了导航条,实现起来没有多少难度

6、账号系统

25小节的实战:内容超级多,难啃的骨头来了。

  • 在WebAPP中,账号信息可以记录在浏览器的 Cookie 模块,浏览器会根据不同的域名来分配一块 Cookie 空间。

    当用户在页面输入账号和密码后,WebAPP 验证登录信息是正确的,这时服务会把账号信息塞到请求的 Cookie 中并返回给客户端,客户端拿到信息后,把账户信息存在浏览器的 Cookie 中。

下一次客户端向服务端发起请求的时候,浏览器会帮我们自动把 Cookie 附加到请求里,以供服务来判断这是一个已经登录的用户。当服务收到请求后,第一件事情应该是分析 Cookie 里的信息并判断这个用户是不是一个真实的用户。

  • 实战中遇到的问题

1、router.api.js里面没有引入相关的文件

2018-04-18 JS进阶实战21-27_第1张图片
router.api.js里面没有引入相关的文件

2、bcrypt 安装一直出现报错的问题

npm install --save bcrypt // 报错

2018-04-18 JS进阶实战21-27_第2张图片
报错截图

解决方案:
修改package.json 里面的版本号" bcrypt": "^1.0.3",与老师的保持一致

删除已安装版本
npm uninstall --save bcrypt

再次执行
npm install --save bcrypt // 即可

2018-04-18 JS进阶实战21-27_第3张图片
修改版本号

3、程序在设计的整个环节的思路,不是特别的清晰,还需要不断的揣摩

7、访问权限

26小节的实战

  • toString的函数使用在前端报错,问题没有彻底的解决,临时的解决方案是把 toString 干掉了
  • var ObjectId = Schema.ObjectId; 这个代码我并不理解其中的全部的意思
8、更好的错误处理中枢

27小节的实战:这一个小节就是对于错误页面,不是同一给出一个500页面,而是把具体的错误提示处理。

你可能感兴趣的:(2018-04-18 JS进阶实战21-27)