简介
当对某用户的回答做点赞或点差时,此时用户、回答两个实体之间存在多对多的关联关系,而用户、回答、投票三者组合必须是唯一的,即用户不能为回答投多票。而类似功能均可作为通用API来实现。
通用API应用场景
- 当涉及到多表查询时需用通用API
- 零碎功能点所需要的API
1.创建多对多关联关系
回答表和用户表之间隶属于多对多的关联关系,laravel中称这种中间表为轴心表。
(1)在模型中创建多对多的关联关系
app\Model\Answer.php
/*答案和用户之间隶属于多对多的关联关系*/
public function users()
{
//默认中间表仅保存关联字段,新增字段需使用withPivot注明
return $this
->belongsToMany('App\Model\User')
->withPivot('vote') //执行自定义新增字段(轴心)
->withTimestamps();//同时更新时间
}
app\Model\User.php
/*答案和用户之间隶属于多对多的关联关系*/
public function answers()
{
//默认中间表仅保存关联字段,新增字段需使用withPivot注明
return $this
->belongsToMany('App\Model\Answer')
->withPivot('vote') //执行自定义新增字段
->withTimestamps();//同时更新时间
}
(2)创建多对多中间表
php artisan make:migration create_table_answer_user --create=answer_user
注意:中间表表名不使用复数形式,必须使用单数。
(3)创建中间关联表结构
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreateTableAnswerUser extends Migration
{
public function up()
{
Schema::create('answer_user', function (Blueprint $table) {
$table->increments('id');
$table->timestamps();
//自定义字段
$table->unsignedInteger('user_id');//用户表主键
$table->unsignedInteger('answer_id');//回答表主键
$table->unsignedSmallInteger('vote');//票数
//外键
$table->foreign('user_id')->references('id')->on('user');
$table->foreign('answer_id')->references('id')->on('answer');
//唯一键设置
$table->unique(['user_id','answer_id','vote']);
});
}
public function down()
{
Schema::dropIfExists('answer_user');
}
}
# 版本2
Schema::create('answer_user', function (Blueprint $table) {
$table->increments('id');
$table->unsignedInteger('user_id')->default('0')->comment('用户');
$table->foreign('user_id')->references('id')->on('users');
$table->unsignedInteger('answer_id')->default('0')->comment('回答');
$table->foreign('answer_id')->references('id')->on('answers');
$table->unsignedTinyInteger('vote')->default('0')->comment('投票:0踩1赞');
$table->unique(['user_id','answer_id','vote']);//一个用户只能对一个问题投一次票(顶或踩)
$table->timestamps();
});
(4)生成表结构
php artisan migrate:pretend
php artisan migrate
php artisan migrate:rollback
2. 为回答投票
(1)创建路由 routes\web.php
Route::any('/api/answer/vote',function(){
return answerInstance()->vote();
});
(2)实现方法 app\Model\Answer.php
/*使用中间表为问题投票*/
public function vote()
{
//检查用户是否登录
if(!userInstance()->isLogin()){
return ['err'=>1, 'msg'=>'请先登录'];
}
//判断必填参数
if(!rq('id') || !rq('vote')){
return ['err'=>1, 'msg'=>'参数错误'];
}
//检查回答是否存在
$answer = $this->find(rq('id'));
if(!$answer){
return ['err'=>1, 'msg'=>'回答不存在'];
}
//检查当前用户是否在回答上重复投票,若中间表已存在数据则先存在后添加
$answer->users()->newPivotStatement()
->where('user_id',session('user.id'))
->where('answer_id',rq('id'))
->delete();
//向中间表添加
$vote = rq('vote')<=1 ? 1 : 2;//1赞同 2反对
$answer->users()->attach(session('user.id'),['vote'=>$vote]);
return ['err'=>0];
}
# 版本2
/*API 用户投票*/
public function vote()
{
//判断用户是否登录
if(!user()->islogin()){
return ['err'=>1, 'msg'=>'尚未登录'];
}
//判断回答编号和投票是否存在
if(!rq('id')){
return ['err'=>1, 'msg'=>'缺少回答编号'];
}
if(!Request::has('vote')){
return ['err'=>1, 'msg'=>'缺少投票'];
}
//判断投票类型
$vote = rq('vote') ? 1 : 0;//1顶0踩
//判断问题是否存在
$answer = $this->find(rq('id'));
if(!$answer){
return ['err'=>1, 'msg'=>'回答不存在'];
}
//判断用户在相同回答下是否已投票,注意 answer_user 中 answer_id+user_id+vote 必须唯一,即一个用户对一个问题只能投一票。
//获取中间表
$answer_user = $answer->user()->newPivotStatement();
//删除投票记录
$answer_user->where(['answer_id'=>rq('id'), 'user_id'=>session('user_id')])->delete();
//添加投票
$answer->user()->attach(session('user_id'), ['vote'=>$vote]);
return ['err'=>0,'msg'=>'投票成功'];
}