chili 默默学编程

AJAX 前后端实践(网站点赞功能)


本文目录

  • 为什么要使用 AJAX
  • AJAX 的原理
  • AJAX 前后端实践(点赞功能的实现代码)
  • AJAX 后端 API 设计原则总结(RESTful API 设计原则)

为什么要使用 AJAX

AJAX 是一种在无需重新加载整个网页的情况下,能够更新部分网页的技术。 

加载整个网页的情况:互联网发展早期,访问一个网站,需要传输整个网页,过程如下:

- 客户端发送路由(route)请求(也就是用户在浏览器输入网址、或者提交 form 等请求)

- 服务器收到 route ,发送对应的 html 网页到客户端

- 浏览器收到 html 文件,渲染展示

这种情况下的后端程序是这样的(以 python flask 为例):

@main.route("/")
def index():
    return render_template("index.html")

其中 “/”,就是指主页,如本网站就是 http://shenkeyang.cc/

服务器端接收到 “/” 的路由请求后,会将 “index.html” 文件发送至用户的浏览器


上述方式存在的问题:数据传输效率低,数据处理不方便

每次都传输整个 html 文件时,绝大部分内容是重复的。

AJAX 技术能解决这个问题,解决的办法就是只交换少量数据。


AJAX 的原理

AJAX 通过 JSON 数据格式,实现前后端的数据交换。过程如下:

- 客户端发送 AJAX 请求

- 服务器处理请求,发送 JSON 格式的数据

- 客户端收到 JSON 数据,处理并渲染到当前页面

后端 API 如下:

@main.route('/article/like/<int:id>', methods=['POST'])
def article_like(id):
    // 处理数据
    result = {
        'likes': likes,
    }
    return json.dumps(result)

前端用 AJAX 的方式,访问 '/article/like/<int:id>'路由的时候,后端返回的不是一个 HTML 文件,而是一个 JSON 格式的数据(python 写法):json.dumps(data)


前端 AJAX 请求如下:

var url = '{{ url_for('.article_like', id=article.id) }}'
var data = {
    'task': 'add',
}
ajax('POST', url, null, data, function(r){
    var t = JSON.parse(r.response)
    document.querySelector('#id-span-like-num').innerText = t.num
})

其中 ajax('POST', url, null, data, function(r){ ... }) 就是一个 AJAX 请求,参数解释如下:

  • 'POST':请求的方法,按后端 API 要求来
  • url:请求的路由,按后端 API 要求来
  • data:要发送的数据
  • function(r){ ... }:后端处理成功后,前端要做的渲染、更新等动作

AJAX 前后端实践(点赞功能代码)

点赞样式如下:

点击前:

Image

点击后:

Image


点赞功能的逻辑如下:

- 打开页面的逻辑:检查是否已经点过赞,根据不同情况,渲染不同页面

- 点击按钮的逻辑:若之前没有点过赞,那么点击按钮就是增加点赞数量,否则取消之前的点赞记录


AJAX 的应用:

由于点赞功能对页面的修改非常小,仅仅改变点赞按钮的数量和状态,所以应该使用 AJAX 来实现。

步骤一:前端点击按钮发送 AJAX 请求

首先,封装一个 ajax 函数(当然也可以使用 jQuery ,那就更简单了,我这里使用 JavaScript 原生功能):

var ajax = function(method, url, headers, data, callback) {
    // 新建一个 XMLHttpRequest 对象
    var r = new XMLHttpRequest()
    // 设置请求的方法和路径
    r.open(method, url, true)
    // 设置发送的数据的格式:json
    r.setRequestHeader('Content-Type', 'application/json')
    // 设置请求成功后的处理函数(异步回调)
    r.onreadystatechange = function() {
        if(r.readyState === 4) {
            callback(r)
        }
    }
    r.send(data)
}

然后,给按钮绑定监听 click 事件:

// 选择按钮
var likeButton = document.querySelector('.button-like')
绑定事件
likeButton.addEventListener('click', function(){
    // 设置 url
    var url = '{{ url_for('.article_like', id=article.id) }}'
    // 设置 data
    var data = {
        'task': 'add',
    }
    if (likeTip.innerText == 'yes') {
        data['task'] = 'remove'
    }
    data = JSON.stringify(data)
    // 发送 AJAX 请求
    ajax('POST', url, null, data, function(r){
        if (likeTip.innerText == 'yes') {
            likeTip.innerText = 'no'
        } else if (likeTip.innerText == 'no') {
            likeTip.innerText = 'yes'
        }
        likeInit()
        var t = JSON.parse(r.response)
        document.querySelector('#id-span-like-num').innerText = t.num
    })
})

步骤二:后端提供 API

@main.route('/article/like/<int:id>', methods=['POST'])
def article_like(id):
    # 解析前端发来的数据
    form = request.get_json()
    ip = request.remote_addr
    # 处理数据
    article = Article.find_by(id=id)
    result = {
        'num': article.like(form, ip),
    }
    # 返回数据
    return json.dumps(result)

AJAX 后端 API 设计原则总结(RESTful API 设计原则)

我上面的设计很多不符合规范。。这是事后继续搜索学习才得知的。

阮一峰总结链接:http://www.ruanyifeng.com/blog/2014/05/restful_api.html

详细情况如下:

- 协议

API与用户的通信协议,总是使用HTTPs协议

- 域名

API 应该部署在专用域名之下,如:

https://api.example.com
https://example.com/api/

- 版本

应该将API的版本号放入URL,如:

https://api.example.com/v1/

- 路径

每个网址都代表一个资源,所以应使用名次,不能使用动词

同时,每个资源在数据库中对应一个集合,所以要用复数

https://api.example.com/v1/zoos
https://api.example.com/v1/animals
https://api.example.com/v1/employees

- HTTP动词

常用 5 个动词

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

使用的例子如下:

GET /zoos:列出所有动物园
POST /zoos:新建一个动物园
GET /zoos/ID:获取某个指定动物园的信息
PUT /zoos/ID:更新某个指定动物园的信息(提供该动物园的全部信息)
PATCH /zoos/ID:更新某个指定动物园的信息(提供该动物园的部分信息)
DELETE /zoos/ID:删除某个动物园
GET /zoos/ID/animals:列出某个指定动物园的所有动物
DELETE /zoos/ID/animals/ID:删除某个指定动物园的指定动物

- 过滤信息

API 应该提供参数,以过滤资源获取信息

?limit=10:指定返回记录的数量
?offset=10:指定返回记录的开始位置。
?page=2&per_page=100:指定第几页,以及每页的记录数。
?sortby=name&order=asc:指定返回结果按照哪个属性排序,以及排序顺序。
?animal_type_id=1:指定筛选条件

- 返回的状态码含义

详细见链接:https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html

常见如下:

200 OK - [GET]:服务器成功返回用户请求的数据,该操作是幂等的(Idempotent)。
201 CREATED - [POST/PUT/PATCH]:用户新建或修改数据成功。
202 Accepted - [*]:表示一个请求已经进入后台排队(异步任务)
204 NO CONTENT - [DELETE]:用户删除数据成功。
400 INVALID REQUEST - [POST/PUT/PATCH]:用户发出的请求有错误,服务器没有进行新建或修改数据的操作,该操作是幂等的。
401 Unauthorized - [*]:表示用户没有权限(令牌、用户名、密码错误)。
403 Forbidden - [*] 表示用户得到授权(与401错误相对),但是访问是被禁止的。
404 NOT FOUND - [*]:用户发出的请求针对的是不存在的记录,服务器没有进行操作,该操作是幂等的。
406 Not Acceptable - [GET]:用户请求的格式不可得(比如用户请求JSON格式,但是只有XML格式)。
410 Gone -[GET]:用户请求的资源被永久删除,且不会再得到的。
422 Unprocesable entity - [POST/PUT/PATCH] 当创建一个对象时,发生一个验证错误。
500 INTERNAL SERVER ERROR - [*]:服务器发生错误,用户将无法判断发出的请求是否成功。

- 返回结果

返回结果应该按照以下规范

GET /collection:返回资源对象的列表(数组)
GET /collection/resource:返回单个资源对象
POST /collection:返回新生成的资源对象
PUT /collection/resource:返回完整的资源对象
PATCH /collection/resource:返回完整的资源对象
DELETE /collection/resource:返回一个空文档

- Hypermedia API

RESTful API最好做到Hypermedia,即返回结果中提供链接,连向其他API方法,使得用户不查文档,也知道下一步应该做什么。


reply ( 3 )

yin@2018-08-30 11:08:50

0.0

usen@2018-08-30 11:50:03

很是赞的哈,来学习下

winshu@2018-08-30 17:55:06

测试一下