Laravel9  API开发,分三层,处理不同的业务 
                                                    
                        
                    
                    
  
                    
                    我的项目开发中把程序分为3层,重点是这3层都是用代码生成器直接生成的
1、控制层 controller 处理接口命名、入参检验,不处理逻辑,检验都放在一起多方便啊
2、服务层 service 处理业务逻辑、判断、对比,简单的数据库操作
3、模型层 Model 处理数据库相关、出参控制、复杂的数据库操作
- controller层 称为接口服务层,负责对客户端的请求进行响应,处理接收客户端传递的参数,进行高层决策并对领域业务层进行调度,最后将处理结果返回给客户端。 
- service层 称为领域业务层,负责对领域业务的规则处理,重点关注对数据的逻辑处理、转换和加工,封装并体现特定领域业务的规则。 
- Model层 称为数据模型层,负责技术层面上对数据信息的提取、存储、更新和删除等操作,数据可来自内存,也可以来自持久化存储媒介,甚至可以是来自外部第三方系统。 
与其他写的不同的是分了三层,控制层有getRules入参,模型层有getRules列表出参
借鉴JAVA的入参和出参分开的规则,入参控制校验和SWAGGER生成,出参控制列表输出,顺便生成SWAGGER 文件,说实在的,SWAGGER的注释太不是东西了,如果入参和出参多都要写的话,那是很遭罪的,所以我把入参的字段名称、描述、规则,放在控制层的getRules,我把出参的字段名称、描述,放在模型层的getRules,自己生成SWAGGER api-docs.json 文件
一、控制层:
<?php
namespace App\Http\Controllers;
use App\Services\DepartmentsService;
use App\Traits\ApiResponse;
use Illuminate\Http\Request;
/**
 * Departments 接口控制器
 * @desc 部门模块
 */
class DepartmentsController extends Controller
{
    use ApiResponse;
    protected $service;
    public function __construct()
    {
        $this->service = new DepartmentsService();
    }
    // 接口参数规则
    public function getRules()
    {
        return [
            'store' => [
                'parentid' => ['name' => 'parentid', 'type' => 'integer', 'required' => true, 'desc' => '父ID'],
                'title' => ['name' => 'title', 'type' => 'string', 'required' => true, 'desc' => '名称'],
                'subtitle' => ['name' => 'subtitle', 'type' => 'string', 'required' => false, 'desc' => '短标|英标|副标'],
                'userid' => ['name' => 'userid', 'type' => 'string', 'required' => true, 'default' => 0, 'desc' => '管理者ID'],
            ],
            'destroy' => [
                'id' => ['name' => 'id', 'type' => 'integer', 'required' => true, 'desc' => '主键ID'],
            ],
            'show' => [
                'id' => ['name' => 'id', 'type' => 'integer', 'required' => true, 'desc' => '主键ID'],
            ],
            'update' => [
                'id' => ['name' => 'id', 'type' => 'integer', 'required' => true, 'desc' => '主键ID'],
                'parentid' => ['name' => 'parentid', 'type' => 'integer', 'required' => false, 'desc' => '父ID'],
                'title' => ['name' => 'title', 'type' => 'string', 'required' => false, 'desc' => '名称'],
                'subtitle' => ['name' => 'subtitle', 'type' => 'string', 'required' => false, 'desc' => '短标|英标|副标'],
                'userid' => ['name' => 'userid', 'type' => 'string', 'required' => true, 'default' => 0, 'desc' => '管理者ID'],
            ],
            'list' => [
                'parentid' => ['name' => 'parentid', 'type' => 'integer', 'required' => true, 'default' => 0, 'description' => '检索父ID:数字'],
            ],
        ];
    }
    /**
     * Departments:新增单条数据
     * @desc 权限:通用
     * @return int 新增ID
     * @method POST
     */
    public function store(Request $request)
    {
        $fields = verifyInputParams($request, $this->getRules()['store']);
        $result = $this->service->store($fields);
        return $this->formatUniteResult($result);
    }
    /**
     * Departments:删除单条数据
     * @desc 权限:分配
     * @return int 影响行数
     * @method POST
     */
    public function destroy(Request $request)
    {
        $fields = verifyInputParams($request, $this->getRules()['destroy']);
        $result = $this->service->destroy($fields);
        return $this->formatUniteResult($result);
    }
    /**
     * Departments:读取单条数据
     * @desc 权限:通用
     * @return array DepartmentsModel@show
     * @method GET
     */
    public function show(Request $request)
    {
        $fields = verifyInputParams($request, $this->getRules()['show']);
        $result = $this->service->show($fields);
        return $this->formatUniteResult($result);
    }
    /**
     * Departments:更新单条数据
     * @desc 权限:通用,修改一行多字段值
     * @return int 影响行数
     * @method POST
     */
    public function update(Request $request)
    {
        $fields = verifyInputParams($request, $this->getRules()['update']);
        $result = $this->service->update($fields);
        return $this->formatUniteResult($result);
    }
    /**
     * Departments:列表记录数据
     * @desc 权限:通用,多用于下拉数据, 集合:不分页
     * @return array DepartmentsModel@list
     * @method GET
     */
    public function list(Request $request)
    {
        $fields = verifyInputParams($request, $this->getRules()['list']);
        $result = $this->service->list($fields);
        return $this->formatUniteResult($result);
    }
}说明下:
getRules方法是每个接口的入参,由代码生成器生成
verifyInputParams 检验函数, 我放在app\Helpers\function.php里,关联自定义的函数可以查看Laravel9 常用自定义函数部署应用
$this->formatUniteResult 参考Laravel9 API开发统一格式化数据
use Illuminate\Support\Facades\Validator;
use Illuminate\Support\Str;
/**
 * 过滤传入参数
 * @param $request 表单提交字段
 * @param $rules 接口允许字段
 */
function filterInputParams($request, $rules)
{
    $params = [];
    $key = array_keys($rules);
    foreach ($key as $k => $v) {
        if ($request->has($v)) {
            $params = array_merge($params, [$v => $request->input($v)]);
        }
    }
    return $params;
}
/**
 * 验证传入参数
 * @param $request 表单提交字段
 * @param $rules  接口允许字段
 */
function verifyInputParams(object $request, $rules)
{
    $filter = [];
    foreach ($rules as $k => $v) {
        $result = [];
        // 有默认值和没有传参
        !isset($v['require']) ?: $v['required'] = $v['require'];
        if ($v['required'] == true) {
            isset($v['default']) && !isset($request[$v['name']]) ? $request[$v['name']] = $v['default'] : '';
            $result[] = 'required';
            ($v['type'] == 'int') ? $v['type'] = 'integer' : '';
            isset($v['type']) ? $result[] = $v['type'] : $result[] = 'string';
            isset($v['max']) ? $result[] = 'max:' . $v['max'] : '';
            isset($v['min']) ? $result[] = 'min:' . $v['min'] : '';
        } else {
            if (!isset($request[$v['name']])) { //没有传入
                if (isset($v['default'])) {
                    $request[$v['name']] = $v['default'];
                } else {
                    unset($request[$v['name']]);
                }
            }
        }
        $filter[$v['name']] = $result;
    }
    // 过滤不在规则内参数,必填有默认值除外
    $params = filterInputParams($request, $filter);
    // 验证参数规则
    $validator = Validator::make($params, $filter);
    if ($validator->fails()) {
        throw new \App\Exceptions\BusinessException(422, $validator->errors());
    }
    return $params;
}throw new \App\Exceptions\BusinessException(422, $validator->errors());
参考Laravel9 自定义异常处理
当调用接口的时候,
校验入参:verifyInputParams
调用逻辑处理:$result = $this->service->list($fields);
统一输出:return $this->formatUniteResult($result);
二、业务层:
<?php
namespace App\Services;
use App\Models\Departments;
/**
 * Departments 业务逻辑
 */
class DepartmentsService
{
    protected $model;
    public function __construct()
    {
        $this->model = new Departments();
    }
    // 新增数据 Model层 $fillable要加上
    public function store($forms = [])
    {
        return $this->model::insertGetId($forms); // ID
    }
    // 删除数据 Model层 $fillable要加上delete_time | deleted_at
    public function destroy($forms = [])
    {
        $where = ['id' => $forms['id']];
        $param = ['delete_time' => time()];
        return $this->model->where($where)->update($param); // 软删除
        // return $this->model::destroy($where); // 直删除
    }
    // 读取数据
    public function show($forms = [])
    {
        $data = $this->model::findOrFail($forms['id']); // 用于ID条件
        return listArrayAllowKeys($data, $this->model->getRules()['show']);
    }
    // 更新多列单条 Model层 $fillable要存在对应的字段
    public function update($forms = [])
    {
        $where = ['id' => $forms['id']];
        return $this->model::where($where)->update($forms); // 用于多条件 // id不存在,return 0;
    }
    // 列表数据
    public function list($forms = [])
    {
        $fields = $forms;
        unset($fields['parentid']);
        $result = $this->model->list($fields);
        return listArrayAllowKeys($result, $this->model->getRules()['list']);
    }
}
listArrayAllowKeys 函数 格式话输出参数
/**
 * 获取数组中需要的键
 * @param array $array 待处理的数组,可以是一维数组或二维数组
 * @param string|array $allowKeys 待需要的键,字符串时使用英文逗号分割
 * @return array 排除key后的新数组
 */
function listArrayAllowKeys($array, $allowKeys)
{
    if (!is_array($array) && !is_object($array)) {
        throw new \App\Exceptions\BusinessException(400, '检查数据格式:JSON|ARRAY');
    }
    $is_items = false;
    $allArray = json_decode(json_encode($array), true);
    $is_data = array_key_exists('data', $allArray);
    $array = ($is_data) ? $allArray['data'] : $allArray;
    $allowKeys = array_keys($allowKeys);
    //$excludeKeys = is_array($excludeKeys) ? $excludeKeys : explode(',', $excludeKeys);
    foreach ($array as $key => $value) {
        if (is_array($value)) {
            foreach ($array[$key] as $subKey => $subValue) {
                if (!in_array($subKey, $allowKeys, true)) {
                    unset($array[$key][$subKey]);
                }
            }
        } else if (!in_array($key, $allowKeys, true)) {
            unset($array[$key]);
        }
    }
    if ($is_data) {
        $allArray['data'] = $array;
        return $allArray;
    }
    return $array;
}三、模型层
<?php
namespace App\Models;
use Eloquence\Behaviours\CamelCasing; //把 a_b 变成 aB
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\DB;
/**
 * Departments 数据模型
 */
class Departments extends Model
{
    use CamelCasing;
    protected $table = 'departments';
    protected $fillable = ['parentid', 'title', 'subtitle', 'userid', 'organizationid', 'sort', 'status', 'delete_time']; // 增改时允许赋值字段(白名单)
    protected $guarded = ['id']; // 增改时禁止赋值字段(黑名单)
    protected $hidden = []; // 列表时隐藏的字段
    protected $appends = []; // 需要追加的字段
    public $timestamps = false;
    // 列表数据
    public function list($forms = [])
    {
        // ** 传入参数处理 **
        $parentid = isset($forms['parentid']) ? $forms['parentid'] : '';
        // ** 执行SQL语句 **
        $query = self::query();
        $query->select($this->table . '.*');
        $query->where($this->table . '.delete_time', 0);
        $query->when($parentid, function ($query) use ($parentid) {
            $query->where(function ($query) use ($parentid) {
                $query->Where($this->table . '.parentid', $parentid);
                $query->orWhere($this->table . '.id', $parentid);
            });
        });
        $query->Orderby($this->table . '.sort');
        return $query->get();
    }
    // 接口参数规则
    public function getRules()
    {
        return [
            'show' => [
                'id' => ['type' => 'integer', 'description' => '主健ID'],
                'parentid' => ['type' => 'integer', 'description' => '父ID'],
                'title' => ['type' => 'string', 'description' => '名称'],
                'subtitle' => ['type' => 'string', 'description' => '短标|英标|副标'],
                'userid' => ['type' => 'string', 'description' => '管理者ID'],
                'organizationid' => ['type' => 'integer', 'description' => '组织ID'],
                'sort' => ['type' => 'integer', 'description' => '排序'],
                'status' => ['type' => 'integer', 'description' => '状态'],
                'deleteTime' => ['type' => 'integer', 'description' => '删除时间'],
            ],
            'list' => [
                'id' => ['type' => 'integer', 'description' => '主健ID'],
                'parentid' => ['type' => 'integer', 'description' => '父ID'],
                'title' => ['type' => 'string', 'description' => '名称'],
                'subtitle' => ['type' => 'string', 'description' => '短标|英标|副标'],
                'userid' => ['type' => 'string', 'description' => '管理者ID'],
                'organizationid' => ['type' => 'integer', 'description' => '组织ID'],
                'sort' => ['type' => 'integer', 'description' => '排序'],
                'status' => ['type' => 'integer', 'description' => '状态'],
                'deleteTime' => ['type' => 'integer', 'description' => '删除时间'],
                /**新增**/
                'level' => ['type' => 'integer', 'description' => '层级'],
                'master' => ['type' => 'integer', 'description' => '管理者'],
            ],
        ];
    }
}
getRules 在SERVICE层调用
$this->model->getRules()[‘show’] 单条的字段控制输出字段
$this->model->getRules()[‘list’] 列表的字段控制输出字段
读取文件类、方法、注释、参数的代码
<?php
namespace App\Traits;
use Str;
trait Swagger
{
    //const API_CATE_TYPE_API_CLASS_NAME = 0; // 按API类名分类
    //const API_CATE_TYPE_API_CLASS_TITLE = 1; // 按接口模块名称分类
    //const API_LIST_SORT_BY_API_NAME = 0; // 接口列表,根据接口名称排序
    //const API_LIST_SORT_BY_API_TITLE = 1; // 接口列表,根据接口标题排序
    /**
     * @var int $apiCateType 接口分类的方式
     */
    protected $apiCateType;
    /**
     * @var int $apiListSortBy 接口列表的排序方式
     */
    //protected $apiListSortBy;
    public function swaggerJson()
    {
        $rootPath = base_path();
        defined('D_S') || define('D_S', DIRECTORY_SEPARATOR);
        //App\Http\Controllers 及往下目录
        $psr = ['App\Http\Controllers'];
        foreach (glob(app_path('Http\Controllers') . '/*', GLOB_ONLYDIR) as $dirName) {
            $name = pathinfo($dirName, PATHINFO_FILENAME);
            $psr[] = 'App\Http\Controllers' . D_S . $name;
        }
        // 按照Api.php的类排除的隐藏和默认的方法
        $allPhalApiApiMethods = get_class_methods('\\App\\Http\\Controllers\\Api');
        $allApiS = [];
        $allModel = [];
        $allResource = [];
        // 扫描接口文件
        // $srcPath = "App\\Http\\Controllers"
        foreach ($psr as $namespace => $srcPath) {
            foreach (glob(app_path(str_replace('App\\', '', $srcPath)) . '/*.php') as $dirName) {
                $apiFileName = str_replace('.php', '', str_replace(app_path(str_replace('App\\', '', $srcPath)) . '/', '', $dirName));
                $apiClassName = $srcPath . '\\' . $apiFileName;
                if (!class_exists($apiClassName) || $apiFileName == 'Controller' || $apiFileName == 'Api' || $apiFileName == 'Swagger') {
                    continue;
                }
                $ref = new \ReflectionClass($apiClassName);
                $title = "//请检测接口服务注释($apiClassName)";
                $desc = '-';
                $isClassIgnore = false; // 是否屏蔽此接口类
                $docComment = $ref->getDocComment();
                if ($docComment !== false) {
                    $docCommentArr = explode("\n", $docComment);
                    $comment = trim($docCommentArr[1]);
                    $title = trim(substr($comment, strpos($comment, '*') + 1));
                    foreach ($docCommentArr as $comment) {
                        $pos = stripos($comment, '@desc');
                        if ($pos !== false) {
                            $desc = trim(substr($comment, $pos + 5));
                        }
                        if (stripos($comment, '@ignore') !== false) {
                            $isClassIgnore = true;
                        }
                    }
                }
                if ($isClassIgnore) {
                    continue;
                }
                $apiCateVal = $this->apiCateType == 1 ? $title : $apiFileName;
                if (!isset($allApiS[$srcPath][$apiCateVal])) {
                    $allApiS[$srcPath][$apiCateVal] = array('methods' => array());
                }
                $allApiS[$srcPath][$apiCateVal]['class'] = $apiClassName;
                $allApiS[$srcPath][$apiCateVal]['summary'] = $title;
                $allApiS[$srcPath][$apiCateVal]['description'] = $desc;
                $method = array_diff(get_class_methods($apiClassName), $allPhalApiApiMethods);
                //Set Swagger
                $rulesObject = new $apiClassName;
                $rulesArray = $rulesObject->getRules();
                //Set Swagger
                foreach ($method as $mValue) {
                    $rMethod = new \Reflectionmethod($apiClassName, $mValue);
                    if (!$rMethod->isPublic() || strpos($mValue, '__') === 0 || $mValue == 'getRules') {
                        continue;
                    }
                    $title = '//请检测函数注释';
                    $desc = '//请使用@desc注释';
                    $methods = 'GET/POST';
                    $isMethodIgnore = false;
                    $docComment = $rMethod->getDocComment();
                    if ($docComment !== false) {
                        $docCommentArr = explode("\n", $docComment);
                        $comment = trim($docCommentArr[1]);
                        $title = trim(substr($comment, strpos($comment, '*') + 1));
                        foreach ($docCommentArr as $comment) {
                            $pos = stripos($comment, '@desc');
                            if ($pos !== false) {
                                $desc = trim(substr($comment, $pos + 5));
                            }
                            if (stripos($comment, '@ignore') !== false) {
                                $isMethodIgnore = true;
                            }
                            $pos = stripos($comment, '@return');
                            if ($pos !== false) {
                                $back = trim(substr($comment, $pos + 8));
                            }
                            $pos = stripos($comment, '@method');
                            if ($pos !== false) {
                                $methods = trim(substr($comment, $pos + 8));
                                continue;
                            }
                        }
                    }
                    if ($isMethodIgnore) {
                        continue;
                    }
                    $resourcesName = str_replace('Controller', '', $apiFileName);
                    $apiResourceName = '\App\\Models\\' . $resourcesName;
                    if (class_exists($apiResourceName)) {
                        $resourceObject = new $apiResourceName;
                        if (method_exists($apiResourceName, 'getRules')) {
                            $getRulesKeys = array_keys($resourceObject->getRules());
                            foreach ($getRulesKeys as $key => $value) {
                                $allResource[$resourcesName . 'Model@' . Str::snake($value)]['type'] = 'object';
                                $allResource[$resourcesName . 'Model@' . Str::snake($value)]['properties'] = $resourceObject->getRules()[$value];
                                $allResource[$resourcesName . 'Model@' . Str::snake($value)]['title'] = $resourcesName . "Model@" . $value;
                            }
                        }
                    }
                    $service = trim($srcPath, '\\') . '\\' . $apiFileName . '@' . $mValue;
                    $parameters = isset($rulesArray[$mValue]) ? $this->handleRules($rulesArray[$mValue]) : []; //set Swagger
                    $allApiS[$srcPath][$apiCateVal]['methods'][$mValue] = array(
                        'actions' => $service,
                        'summary' => $title,
                        'description' => trim($desc),
                        'methods' => $methods,
                        'parameters' => $parameters,
                        'name' => $mValue,
                        'back' => $back,
                    );
                }
            }
        }
        return ['api' => $allApiS, 'model' => $allModel, 'resource' => $allResource];
    }
    public function handleRules($array = [])
    {
        $result = [];
        foreach ($array as $key => $value) {
            !isset($value['desc']) ?: $value['description'] = $value['desc']; // 兼容 PHALAPI
            !isset($value['require']) ?: $value['required'] = $value['require']; // 兼容 PHALAPI
            ($value['type'] == 'int') ? $value['type'] = 'integer' : ''; // 兼容 PHALAPI
            $value['schema']['type'] = $value['type'];
            !isset($value['default']) ?: $value['schema']['default'] = $value['default'];
            !isset($value['min']) ?: $value['schema']['minimum'] = $value['min'];
            !isset($value['max']) ?: $value['schema']['maximum'] = $value['max'];
            unset($value['min']);
            unset($value['max']);
            unset($value['default']);
            unset($value['type']);
            unset($value['desc']); // 兼容 PHALAPI
            unset($value['require']); // 兼容 PHALAPI
            $value['in'] = 'query';
            $result[] = $value;
        }
        return $result;
    }
}
过滤文件App\Http\Controller\Api.php
<?php
namespace App\Http\Controllers;
use App\Traits\ApiResponse;
/**
 * Api 接口控制器
 * @desc 作为过滤SWAGGER方法模版使用
 */
class Api extends Controller
{
    use ApiResponse;
    // 接口参数规则
    public function getRules()
    {
        return [];
    }
}

本作品采用《CC 协议》,转载必须注明作者和本文链接
 
           guangguijun 的个人博客
 guangguijun 的个人博客
         
             
             
             
             
                     
                     
             
            

 
             
             
           
           关于 LearnKu
                关于 LearnKu
               
                     
                     
                     粤公网安备 44030502004330号
 粤公网安备 44030502004330号 
 
推荐文章: