JB Online开发笔记2:SQL与axios

SQL

新表格

为了实现美妙的课程订阅功能,需要一张新的表格,叫做course_enroll_records,点击“加入课程”就会往表里面插入一行,包括课程号、用户号和一个判断是否为管理员类型的bool变量。

处理函数

定义了一些新的路由,以API为例,分别是

Route::group(['prefix' => 'course/{course}'], function () {
  Route::post('check/student/{user?}', '[email protected]')->name('api.forum.course.check.student');
  Route::post('check/admin/{user?}', '[email protected]')->name('api.forum.course.check.admin');
  Route::post('add/student', '[email protected]')->name('api.forum.course.add.student.myself');
  Route::post('delete/user', '[email protected]')->name('api.forum.course.delete.user.myself');
  Route::group(['middleware' => 'admin'], function () {
    // Only admin can add admin, and control other users.
    Route::post('add/student/{user?}', '[email protected]')->name('api.forum.course.add.student');
    Route::post('add/admin/{user?}', '[email protected]')->name('api.forum.course.add.admin');
    Route::post('delete/user/{user?}', '[email protected]')->name('api.forum.course.delete.user');
  });
});

可以看出,addStudentdeleteUser分别有用户版和管理员两个版本,区别就是用户版只能修改自己,而管理员版可以带个参数,来修改其他人。对应的处理函数会调用模型的methods,从而实现数据库的操作。而web版的处理函数则是在CourseController中新建一个protected $courseAPI;实例,直接偷懒调用API的函数,反而写起来轻松,维护也好弄。

联合查询

然而接下来就遇到了一个难题。如何对多张表的数据进行同时查询呢?需要实现的查询需求如下:

首先从assignments表中把所有没截止的作业拿出来,然后如果issuer一列为对应的用户ID,则选中这列,或者如果对应的course_id和用户ID在course_enroll_records表中有命中,则选中。

怎么办呢?

上网一查,只需要做联合就可以了(这里不介绍了)。

那么怎么写呢?(省略半天研究+查资料)

public function scopeSubscribedByUser($query, $userID)
{
  return $query->join('course_enroll_records', function ($join) use ($userID) {
    $join->where('course_enroll_records.user_id', $userID)
      ->on(function ($join) use ($userID) {
        $join->where('assignments.issuer', $userID)
          ->orOn(function ($join) {
            $join->where('assignments.issuer', 0)
              ->on('assignments.course_id', '=', 'course_enroll_records.course_id');
          });
      });
  })->distinct('assignments.id'); // holy [email protected]!
}

这TM什么鬼?

首先,这是一个scope函数,是可以直接接到查询语句里($query->subscribedByUser($user))作为一整段查询条件(其实就是个好多层的lambda表达式)使用的。当选择assignments表后,这个过程会先会把assignments表和course_enroll_records表合并,选择满足以下条件的行联合起来:

  1. course_enroll_records中的user_id列与$userID相等。

  2. assignments中的issuer列与$userID相等,或者issuer列为0且两个表的course_id相等。

这样就可以把issuer!=0或者是用户订阅了课程的作业筛选出来啦!

Bug1:ID被覆盖

然而这样筛选出来之后通过$assignment->id获取到的编号竟然是course_enroll_records.id,也就是说作业的ID被覆盖了。

解决方法是使用SELECT assignments.*(选择作业表部分的内容),而不是SELECT *(同名列会被覆盖)。

Bug2:空行重复

其实这个bug是下午4点多才测出来的。一开始测试的时候只订阅了一个课程,但是当订阅了两个课程的时候,个人作业(issuer!=0)就会重复出现。

造成bug的原因是SQL会把连接的右边表进行遍历,结果发现两个订阅都符合条件2的前半部分(后半部分就短路了),然后就出现了重复的内容。解决方案是加一层SELECT DISTINCT *,也就是上面scope函数的最后一行。

axios

axios与AJAX

AJAX是jQuery的一部分,但是jQuery其实是一个很大的库,而且AJAX用起来并不是很方便。主要原因是,Laravel对网页有CSRF保护,POST类型必须把CSRF-Token作为一个输入放在头表里提交上去,否则就会出现419错误(耐人寻味的数字)。然而Laravel在默认的脚本中已经配好了axios的CSRF-Token头表,直接调用就可以了。

axios其实是Vue的组件,并且基于promise,符合ES6的语法标准,但是我在开发的时候完全没有用到Vue,反而是把jQuery+AJAX替换成了jQuery+axios, jQuery在改变页面内容上很方便,axios在进行交互请求方面很方便,两者一结合,boom,太爽了。(当然少不了JS各种出错让我哭)

How to use

axios其实很简单……

axios.post(url, {data})
  .then(function (response){
    // success
    })
  .error(function (error){
    // fail
    });

就这么多……axios因为使用promise,整个异步处理简单清晰,很好看懂,比AJAX的写法清爽很多。

How to implement

$('.xxBtnContainer').on('click', '.xxBtn', function() {
  let api = this.dataset.api;
  let pid = this.dataset.pid;
  axios.post(url, {})
    .then(function (response) {
        $('#.xxBtnContainer-' + pid).html(response.data.xxBtn_html);
    })
    .error(function (error) {
        $.alert(/*...*/);
    });
});

挺容易懂的,每个可按下的按钮都有一个配套的container,监听按下按钮的事件。按钮按下后首先从`data-xxx属性中取出对应的数据,然后进行一次axios访问。成功后通过获取的数据把原来的按钮更新掉。

这种container+button写法的好处是,可以直接对整个按钮进行替换,服务器端只需要直接调用属性,把新的元素整个返回即可,而客户端也不会因为旧的按钮被替换而无法监听新的事件。

发布于2019-01-15 23:48

笔记 / Laravel