Laravel框架封装Api快速增删改查数据库的方法

2020-03-2811:04:40后端程序开发Comments2,476 views字数 8932阅读模式

Laravel的查询构造器写起来已经很舒服了,但是仍然避免不了要写大量的重复代码,比如我们要实现一个最基本的用户模块的管理功能,起码要写下面这么多接口文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/17941.html

  • 用户列表接口(分页)
  • 用户添加接口可选选项 用户可选的附加属性,例如用户组,权限等
  • 用户提交添加接口 // 保存新增的用户
  • 用户编辑接口 // 这里需要获取用户的数据,以及附属信息
  • 用户提交编辑接口 // 提交用户修改的数据
  • 删除用户接口 // 删除某个特定用户
Laravel内置Route::resource可以直接创建restful风格的接口,直接针对资源增删改查,非常的语义化,但是在工作中用起来,有下面几个问题
  • 一个是要写的函数非常之多,要实现七个接口方法,大量重复
  • 通过HTTP方法来区分动作,前端对接起来也麻烦,PUT,POST,.GET,DELETE,PUT,PATCH等
  • 不支持批量操作,我要删除一百个用户,难道我要循环发送DELETE请求{user_id} 吗?虽然有办法解决,但是就比较麻烦

就简单的一个增删改查的逻辑,就要实现下面这七个接口
Laravel框架封装Api快速增删改查数据库的方法
好,先不考虑RESTfulApi风格了,为了工作简单省事儿,多偷点懒,还是用最传统的GET,POST方式来和服务端交互
在这里可以简单的分析一下,我们要写的这七个接口里面,列表接口和删除接口相对来说是单独的,但是 新增页面的数据,提交新增,编辑页面的数据,提交编辑 这四个接口很大程度上是相似的,所以可以单独抽出来封装成方法,下面直接贴代码,下面封装的方法是基于的,当然也可以用,手动狗头文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/17941.html

primaryKey: 这个主键,默认是ID,如果前端不给你传id,一定要给你传user_id,那你可以修改这里的值为user_id
attributes : 这个字段是我们要处理的属性,比如我只想保存username,password这两个属性,我可以写['username', 'password'], 其他参数无法处理,其实跟模型上的fillable属性一样了,只不过这样写更加灵活一点
beforeSave: 这个方法用户保存之前的处理,校验参数啦,开启事务啦等等
afterSave 比如用户注册以后我查看他的分销关系树,给他的上级发放佣金啦,等等
loadParams 这个最简单,进入页面的时候我需要知道用户可选的用户组,可选角色是什么,这时候载入
onload 数据库里直接查出来的数据不是前端想要的,我可以放到这个回调里面处理,比如我数据库里用户等级分别存储了1,2,3分别对应,青铜,白银,黄金,那么取出来的时候我可以在这个回调函数里面做处理文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/17941.html

publicstaticfunctionsimpleEdit(array$options=[]){$model=newstatic;$result=[];$method=request()->method();$request=request();if(!in_array($method,['GET','POST'])){return$model->error("不支持的请求类型");}$options=array_merge(['isEdit'=>true,'primaryKey'=>null,'attributes'=>null,'beforeSave'=>null,'afterSave'=>null,'loadParams'=>[],'validator'=>null,'onload'=>null,],$options);$primaryKey=$options['primaryKey']??$model->getKeyName();$primaryKeyValue=(int)$request->input($primaryKey,null);if($options['isEdit']&&empty($primaryKeyValue)){return$model->error('请求参数错误,hint:没有ID');}if($options['isEdit']){$model=$model->findOrFail($primaryKeyValue);}if($method=="POST"){$model=$model->fill($options['attributes']??$request->all());if($options['beforeSave']instanceofClosure){$result=$options['beforeSave']($model);if($result)return$result;}if(!$model->save()){return$model->error("保存失败");}if($options['afterSave']instanceofClosure){$result=$options['afterSave']($model);if($result)return$result;}return[$primaryKey=>$model->id];}if($options['onload']instanceofClosure){$options['onload']($model);}$data=$model->toArray();if(!empty($data)){$result['data']=$data;}$result=array_merge($result,$options['loadParams']);return$result;}publicstaticfunctionsimpleCreate(array$options=[]){$options['isEdit']=false;returnstatic::simpleEdit($options);}

对于GET和POST请求呢,简单做了一下区分
GET只用于获取数据,POST用于提交数据
那么相应的
GET /user/create 是加载用户的相关的关联信息(用户组,权限之类的数据,上文已经提到过了)
POST /user/create 是保存用户当前数据(把用户提交的数据处理之后保存到数据库中)
GET /user/update?id=3 是获取ID为3的用户的所有信息
POST /user/update?id=3 是提交用户ID为3的的编辑请求
这里做一下展示,首先我们用一个方法完成两个接口文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/17941.html

publicfunctioncreate(Request $request){return$this->success(
            User::simpleCreate(['loadParams'=>['groups'=> UserGroup::all(),'roles'=> UserRole::all(),],'attributes'=>$request->only(['nickname','mobile','group_id']),'beforeSave'=>function(User $user){if($user->group_id<0){return$this->error("不合法的组ID");}$user->password=bcrypt(md5(uniqid()));},'afterSave'=>function(User $user){
                    Log::create(['content'=>"当前时间".now().",用户{$user->nickname}注册成功"]);}]));}

这个例子中我们使用这几个配置实现了一个简单的用户注册,下面说明
GET /api/user/create
结果如下文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/17941.html

{"error":0,"groups":[{"id":1,"name":"会员"},{"id":2,"name":"非会员"},{"id":3,"name":"超级会员"}],"roles":[{"id":1,"name":"管理员"},{"id":2,"name":"开发"},{"id":3,"name":"运营"},{"id":4,"name":"财务"},{"id":5,"name":"行政"},{"id":6,"name":"产品"}]}

符合预期,我们希望用户进入新增页面的时候可以选择所属的角色,以及groups文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/17941.html

下面请求提交新增接口
POST /api/user/create文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/17941.html

{"error":0,"id":51}

结果符合预期,返回了我们创建最新的一条记录的ID,方便前端跳转或者其他用途文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/17941.html

有的接口会非常简单,新增的时候不需要选择乱七八糟的东西,可能就是个表单,上传保存就行了,那么我就可以不添加任何参数,如下所示,这样一个添加接口就写完了,是不是非常简单文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/17941.html

return$this->success(User::simpleCreate());

新增的接口说完了,下面来说下编辑接口,也就是
GETPOST 的/api/user/edit接口,不同于新增接口的是,每次请求编辑接口的是,每次请求都需要定位资源,需要传递id以定位资源,代码如下文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/17941.html

publicfunctionedit(Request $request){return$this->success(
            User::simpleEdit(['loadParams'=>['groups'=> UserGroup::all(),'roles'=> UserRole::all(),],'attributes'=>$request->all(),'beforeSave'=>function(User $user){if($user->where('nickname',$user->nickname)->exists()){return$this->error("昵称已存在,请选择别的昵称吧");}},'afterSave'=>function(User $user, User $old){
                    Log::create(['content'=>"当前时间".now().",用户名称由{$old->nickname}修改为{$user->nickname}"]);},'onload'=>function(User $user){$user['group']=$user->group()->first();$user->makeHidden(['created_at','updated_at']);$user->mobile=str_replace(substr($user->mobile,3,4),'****',$user->mobile);}]));}

我们访问接口
GET /api/user/edit?id=51文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/17941.html

{"error":0,"data":{"id":51,"nickname":"vencenty","mobile":"155****8899","password":"$2y$10$","is_admin":0,"group_id":2,"group":{"id":2,"name":"非会员"}},"groups":[{"id":1,"name":"会员"},{"id":2,"name":"非会员"},{"id":3,"name":"超级会员"}],"roles":[{"id":1,"name":"管理员"},{"id":2,"name":"开发"},{"id":3,"name":"运营"},{"id":4,"name":"财务"},{"id":5,"name":"行政"},{"id":6,"name":"产品"}]}

和 GET /api/user/create稍微不同的是,多了一个data字段,保存着我们需要的用户所的实体信息文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/17941.html

接下来请求
POST /api/user/edit?id=51文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/17941.html

{"error":-10000,"message":"昵称已存在,请选择别的昵称吧"}

验证不通过,所以符合预期,修正一下请求参数
Laravel框架封装Api快速增删改查数据库的方法文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/17941.html

{"error":0,"id":51}

好,去数据库看下
Laravel框架封装Api快速增删改查数据库的方法
是OK的文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/17941.html


下面说下删除文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/17941.html

publicstaticfunctionsimpleDelete(array$options=[]){$request=request();$model=newstatic;if(!$request->isMethod('POST')){return$model->error("错误的请求方式");}$options=array_merge(['beforeDelete'=>null,'afterDelete'=>null,'primaryKey'=>null,],$options);if($options['beforeDelete']instanceofClosure){static::deleting($options['beforeDelete']);}if($options['afterDelete']instanceofClosure){static::deleted($options['afterDelete']);}$primaryKey=$options['primaryKey']??$model->getKeyName();$waitDeleteIdCollection=(array)$request->post($primaryKey,null);if(empty($waitDeleteIdCollection)){return$model->error("参数错误, hint:没有{$primaryKey}");}$affectRows=static::destroy($waitDeleteIdCollection);return['affectRows'=>$affectRows];}

下面例如我要删除3个用户,只需要传递给我一个数组Id即可
Laravel框架封装Api快速增删改查数据库的方法
结果如下文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/17941.html

{"error":0,"affectRows":3}

好,接下来是比较复杂的列表接口,下面贴出实现文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/17941.html

publicstaticfunctionsimpleList(array$params=[],$options=[]){$model=newstatic;$request=request();$options=array_merge(['pager'=>true,'page'=>null,'pageName'=>'page','pageSizeName'=>'per_page','toArray'=>false,'callback'=>null,'sort'=>null,'by'=>'desc','attachParams'=>[],],$options);$pageName=$options['pageName']??'page';$currentPage=(int)$request->get($pageName,1);$pageSize=$request->get('per_page')??$model->getPerPage();if(isset($params['queryBuilder'])&&$params['queryBuilder']instanceofClosure){$model=$params['queryBuilder']->call($model,$request);unset($params['queryBuilder']);}$sort=$request->get('sort',$options['sort']);$by=$request->get('by',$options['by']);if(!empty($sort)){$model=$model->orderBy($sort,$by);}foreach($paramsas$key=>$param){if($paraminstanceofClosure){$model=$model->{$key}($param);continue;}if(Arr::isMultipleArray($param)){foreach($paramas$condition){$model=$model->{$key}(...$condition);}continue;}$model=$model->$key(...$param);}$total=$model->count();$data=$model->offset($pageSize*($currentPage-1))->limit($pageSize)->get();$data=$options['toArray']?$data->toArray():$data->all();if($options['callback']instanceofClosure){array_walk($data,$options['callback']);}$pageTotal=ceil($total/$pageSize);returnarray_merge(['data'=>$data,'per_page'=>$pageSize,'current_page'=>$currentPage,'total'=>$total,'page_total'=>$pageTotal,],$options['attachParams']);}

Laravel有自己的paginate函数,但是总觉得不够灵活,比如我想往最外层的数组添加数据,就不太好实现,这里重新实现了一下,配置参数说明已经写了注释了,这里提供给前端几个开放的接口
per_page 一个页面多少条数据
page 当前显示第几页
sort 排序字段
by 可选参数asc|desc
这个SimpleList方法有几个点需要特殊说明一下,两个参数,左边的Params参数可以用来生成各种查询条件,options参数用于处理查询出来的数据,以及附加参数等等,我们使用Laravel框架原先写法是类似下面这种写法,但是转为数组以后怎么处理呢,这里使用了 ...拆解符 把数组拆解为一个个参数,然后通过
call_user_func_array()自己处理链式调用,所以原生Laravel类似于下面的写法的时候
User::where('id','<', 10)->limit(3)->orderByDesc('created_at')->get()
转为目前SimpleList写法要这样文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/17941.html

publicfunctionindex(){return$this->success(User::simpleList(['where'=>['id','<',10],'limit'=>3,'orderByDesc'=>'created_at']));}

原先的函数名要转为数组的key,然后参数变成数组,有几个参数就写几个数组元素,这样好像解决了问题,但是链式调用怎么处理,会经常写多Where调用啊,例如这种文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/17941.html

    User::leftJoin('group','','=','')->leftJoin('role','','=','')->where('id','<',100)->where('name','like','%张')->limit(3)->orderByDesc('created_at')->get()

那我就只能再解析一层被,如果发现数组为二维数组,再遍历进行解析,所以写法就变成下面这种了,但是对于复杂的SQL,比如嵌套括号的,各种OrderBy的,
但是前段时间被同事吐槽,说这种写法特别难用,希望有能基于原生的写法来写SQL,所以后来又预留了一个接口,查了下资料,发现闭包绑定可以做到,那么又增加了一个buildQuery配置项, buildQuery参数的闭包的$this绑定了Illuminate\Database\Eloquent\Model对象,在这里面可以通过$this实现链式调用,不了解闭包绑定类的可以搜索一下php Closure::bind Closure::bindto Closure::call函数的用法,有时间会写一个关于这个的文章,看下下面的代码示例,示例一和示例二效果是一模一样的文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/17941.html


 User::simpleList(['leftJoin'=>[['role','','=',''],['group','','=','']],'where'=>[['id','<',10],['name','like','%张']],'limit'=>3,'orderByDesc'=>'created_at'])

 User::simpleList(['buildQuery'=>function($request){return$this->leftJoin('group','','=','')->leftJoin('role','','=','')->where('id','<',100)->where('name','like','%张')->limit(3)->orderByDesc('created_at');},])

理解了以上几点以后,那么后面的$options参数就好理解的多了,具体用法都写注释里了,后期会继续补充SimpleList方法,用于封装快速实现搜索功能文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/17941.html

Github地址 里面的
这个文件
当然代码示例中我使用了 $this->success()这个函数返回结果,用的也是Git这个文件夹下的
这个Trait
要想使用这个方法,可以通过
composer require vencenty/laravel-enhance
然后控制器引入 JsonResponse 这个Trait
模型最好建立一个基类,然后导入 JsonResponseResourceCURD这个类,就可以使用了文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/17941.html

Laravel框架封装Api快速增删改查数据库的方法
Laravel框架封装Api快速增删改查数据库的方法文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/17941.html

Laravel框架封装Api快速增删改查数据库的方法文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/17941.html

我终于明白程序员为什么这么讨厌写文档了,写代码容易,写文档真是要疯啊,况且我这个也不是文档。给自己个差评!!文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/17941.html

  • 本站内容整理自互联网,仅提供信息存储空间服务,以方便学习之用。如对文章、图片、字体等版权有疑问,请在下方留言,管理员看到后,将第一时间进行处理。
  • 转载请务必保留本文链接:https://www.cainiaoxueyuan.com/bc/17941.html

Comment

匿名网友 填写信息

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定