# 如何开发接口

使用PhalApi专业版开发接口，非常简单，大致流程如下。

## 目录结构

PhalApi Pro版的目录结构如下，

```
./
├── README.md # 简介
├── bin # 脚本目录
├── config # 配置目录
│   ├── app.php # 应用配置
│   ├── dbs.php # 数据库配置
│   ├── di.php # 依赖服务配置
│   └── sys.php #系统配置
├── data # 数据库
│   └── phalapi_pro.sql # 数据库安装时的文件
├── language # 翻译包
├── pro_admin # 管理后台的前端源代码，基于iview-admin
├── pro_platform # 开放平台的前端源代码，基于iview-admin
├── public # 对外访问的目录
│   ├── admin # 管理后台访问入口（相当于pro_admin打包构建后的dist目录）
│   ├── api # 接口访问入口（内分前台API和后台API）
│   ├── docs # 离线生成的HTML接口文档
│   ├── docs.php # 在线版接口文档访问入口
│   ├── index.php
│   ├── init.php # 全局初始化文件
│   ├── install # 安装向导（成功安装后建议删除此目录）
│   ├── platform # 开放平台访问入口（相当于pro_platform打包构建后的dist目录）
│   ├── static # 静态资源
│   ├── uploads # 上传目录（需要有写入权限）
│   └── wiki # 技术文档
├── runtime # 运行目录
│   ├── _install.lock # 安装锁定文件
│   ├── cache # 文件缓存
│   └── log # 文件日志
├── sdk # SDK包
├── src # 项目源代码，非常重要
│   ├── admin # 后台接口源代码（遵循ADM模式）
│   ├── app # 开放平台接口源代码（遵循ADM模式）
│   ├── base # 基础包源代码（放置底层公共的代码，不对外直接提供接口，即不提供Api层）
│   ├── platform # 开放平台接口源代码（遵循ADM模式）
│   ├── task # 计划任务接口源代码（遵循ADM模式）
│   └── view # 页面模板目录（如接口文档）
├── tests # 单元测试
└── vendor # composer包，不需要手动修改，通过composer install/update可进行安装和更新
```

PHP代码层次结构如下：  
![](http://cdn7.okayapi.com/yesyesapi_20200416113656_6c74c2813bed5ce7f552c5ed2a1007cc.png)

## 编写Api接口层

如果需要编写开放接口，可以在./src/app/Api目录下新增一个PHP文件，类名和文件名一样（需要区分大小写）。并继承App\Common\Api基类即可。  

例如开放接口的Hello World示例，接口文件是：./src/app/Api/HelloWorld.php，类名是：App\Api\HelloWorld，对接的接口服务名称是：App.HelloWorld.Say。  

```php
<?php
namespace App\Api;
use App\Common\Api;

/**
 * Hello World示例
 */
class HelloWorld extends Api {
    // 接口参数配置
    public function getRules() {
        return array(
            'say' => array(
                'nickname' => array('name' => 'nickname', 'desc' => '昵称'),
            ),
        );
    }

    /**
     * 示例
     * @desc 第一个前台接口示例
     */
    public function say() {
        $nickname = $this->nickname;
        return array('content' => 'Hello world!');
    }
}
```

保存后，访问接口文档列表，可以看到以下新接口：  
![](http://cdn7.okayapi.com/yesyesapi_20191231095424_6cdab6e1dbac43fd95726fa2033412e0.png)

同时在接口详情文档可以查看到相应的详细接口文档：  
![](http://cdn7.okayapi.com/yesyesapi_20191231100511_05d90cae0288de0649d1476772142a15.png)

如果需要编写后台接口，则需要放置在./src/admin/Api目录下，并继承Admin\Common\Api基类。其他开发要求类似。

> 温馨提示：以下方法是系统保留的函数名，不以用于接口函数名称，否则会影响接口正常运行。系统保留接口函数名称有：tryToGetUid()、checkUserLogin()、tryToGetAppKey()、checkAppOnline()、getCurContext()、init()、createMemberValue()、getApiRules()、getApiCommonRules()、getRules()、filterCheck()、userCheck()、isServiceWhitelist()、equalOrIngore()。

## 如何取消接口令牌验证？

默认情况下，前台接口需要进行```access_token```令牌验证，以保护接口不被非法请求。如果不需要对指定的接口进行验证，可以在配置文件./config/app.php中的service_whitelist白名单中添加接口。例如上面的HelloWorld接口不需要接口验证，可以在最后追加配置：  

```php
'service_whitelist' => array(
        'Site.Index',
        'HelloWorld.Say', // 追加Hello World示例白名单
),
```

接口白名单配置，会取消过滤器，不进行任何校验和判断，此时不会相应调整应用的接口权限。如果需要让接口权限在界面上显示保持一致，可以配置接口权限规则。  

> 温馨提示：配置接口白名单，开放平台和管理后台的接口权限显示不会影响。  

## 如何取消接口权限判断？

+ 如何取消全部接口权限判断？  
可以修改./config/app.php里面的default_app_api_rigths_is_allow配置项为true，即可让开放接口默认拥有权限，相当于取消全部接口权限判断。  

+ 如何取消某个接口类的接口权限判断？

在你的接口具体子类中，重载```\App\Common\Api::userCheck()```方法，不进行任何操作即可。  
```php
/**
 * 平台接口基类
 */
class Api extends \App\Common\Api {
    
    /**
     * 进行接口权限判断
     * @throws BadRequestException
     */
    protected function userCheck() {
        // 不需要
    }
}

```
> 温馨提示：通过代码取消接口权限判断，开放平台和管理后台的接口权限显示不会影响。  

+ 如何取消一个接口的接口权限判断？  

在你的接口具体子类中，重载```\App\Common\Api::userCheckActionWhitelist()```方法，返回不需要进行接口权限判断的接口白名单。  
```php
<?php
namespace App\Api;
use App\Common\Api;

/**
 * Hello World示例
 */
class HelloWorld extends Api {
    
    protected function userCheckActionWhitelist() {
        return array('hiApp', 'hiMember');
    }
}

```
> 温馨提示：通过代码取消接口权限判断，开放平台和管理后台的接口权限显示不会影响。  

## 如何获取当前上下文信息？
如何获取当前上下文、登录会员ID和app_key？  

在App\Common\Api接口基类中，已经封装了针对于当前上下文、登录会员ID和app_key等接口，方便项目快速开发。  

以下是使用代码和相关说明。  

```php
class HelloWorld extends Api {
    public function say() {
        // 获取会员ID，未登录时异常返回
        $uid = $this->tryToGetUid();
        
        // 获取会员ID，未登录时返回0
        $uid = $this->tryToGetUid(false);
        
        // 检测会员是否已登录，未登录时异常返回
        $this->checkUserLogin();
        
        // 获取app_key，未指定时异常返回
        $appKey = $this->tryToGetAppKey();
        
        // 获取app_key，未指定时返回空字符串
        $appKey = $this->tryToGetAppKey(false);
        
        // 检测是否已指定app_key
        $this->checkAppOnline();
        
        // 获取当前上下文
        $context = $this->getCurContext();
        var_dump($context->getUid()); // 会员ID
        var_dump($context->getAppKey()); // app_key
    }
}
````

## 如何隐藏access_token参数？

如果不需要在接口文档上显示```access_token```参数，可以在接口参数规则里这样配置（设置is_doc_hide为true即可）。  
```php
class HelloWorld extends Api {
    public function getRules() {
        return array(
            'say' => array(
                'accessToken' => array('name' => 'access_token', 'is_doc_hide' => true),
            ),
        )
    }
}
```

## 编写Domain领域层

Domain领域层主要用于封装复杂的业务逻辑、规则和算法。此部分PHP代码放置在./src/app/Domain目录下。此部分根据不同项目的业务需求，具体开发即可。

## 编写Model数据层

Model数据层主要用于操作MySQL数据库，全部的Model子类可继承Base\Model\Base基类，此基类封装了很多实用的方法和接口，极大减少了数据库封装的代码。例如对应配置表的配置Model类代码如下：  

```php
<?php
namespace App\Model;

class Config extends Base {
}
```
对应源代码文件是：./src/app/Model/Config.php。  

在继承```Base\Model\Base```数据库基类后，可以很方便进行数据库的操作和查询。  

更多关于数据库的连接、操作、查询、多数据库使用等，请参考[DataModel数据模型 - PhalApi 2.x 开发文档](http://docs.phalapi.net/#/v2.0/database-datamodel)。

## 如何新增API接口命名空间？  

默认情况，推荐将接口统一放置在App命名空间，即src/app目录下。如果项目有需要，可以新增自己的接口命名空间。  

首先，参考开源版文档[如何增加一个顶级命名空间？](http://docs.phalapi.net/#/v2.0/autoload?id=%e5%a6%82%e4%bd%95%e5%a2%9e%e5%8a%a0%e4%b8%80%e4%b8%aa%e9%a1%b6%e7%ba%a7%e5%91%bd%e5%90%8d%e7%a9%ba%e9%97%b4%ef%bc%9f)，增加一个新的顶级命名空间。  

然后，修改```./config/app.php```配置文件里的open_api_namespaces配置，追加你的命名空间。
```php
        // 开放接口的命名空间，配置后可提供接口权限分配，可配置多个
        'open_api_namespaces' => array('App', '新的顶级命名空间'),
```

例如，加了Task命名空间后： 
```php
        // 开放接口的命名空间，配置后可提供接口权限分配，可配置多个
        'open_api_namespaces' => array('App', 'Task'),
```

在管理后台可以自动管理该命名空间下的接口权限。  
接口权限分配：  
![](http://cdn7.okayapi.com/yesyesapi_20200416114254_3c9a241aa03b33b0b66911583c8bd568.jpg)  
接口服务列表：  
![](http://cdn7.okayapi.com/yesyesapi_20200416114450_1d98c314d1c0eef0f34aad607a4ce076.jpg)  

在开放平台，开发者也可自动看到新增命名空间下的接口。  
![](http://cdn7.okayapi.com/yesyesapi_20200416114634_7ac7f4d70c1c4e138eac7206c2134e46.png)  

## 如何隐藏接口？

和开源版一样，在接口类或方法中添加```@ignore```注释。  

如：  
```php
<?php
namespace App\Api;
use App\Common\Api;

/**
 * Hello World示例
 * @ignore
 */
class HelloWorld extends Api {
    
    /**
     * 接口示例
     * @desc 一个开放接口示例，可直接访问，不需要任何验证，因为配置了service_whitelist白名单。熟悉后可删除此示例接口。
     * @ignore
     * @return string content 一句话
     */
    public function say() {
        return array('content' => 'Hello PhalApi Pro!');
    }
 }
```

## 如何开启接口参数加密传输？

为了保护客户端传递的参数不被外界非法获取，除了使用HTTPS协议外，也可以通过代码方式来增加。  

在PhalApi专业版，你可以：
  
** 客户端RSA公钥加密传输API接口参数 + 服务端RSA私钥解密API接口参数。**   

### 重要配置

首先，是RSA私钥和公钥的文件，分别是：  
 + rsa私钥文件：./config/phalapi_pro_rsa.pri
 + rsa公钥文件：./config/phalapi_pro_rsa.pub
 
在项目启动或开始时，你可以更换以上RSA配置文件，项目开始使用后，不建议再更换。   

其次，还可以开启./config/app.php配置文件中的encrypt_data公共参数，方便客户端查看可以传递此参数以及其格式要求说明。  

```php

    /**
     * 应用接口层的统一参数
     */
    'apiCommonRules' => array(
        'accessToken' => array('name' => 'access_token', 'default' => '', 'desc' => '访问令牌，仅当开启签名验证时需要传递，生成令牌可使用App.Auth.ApplyToken接口'),

        /** ----- 如果你需要使用第二套加密算法，请开启以下参数规则 ----- **/

        // 'app_key' => array('name' => 'app_key', 'default' => '', 'desc' => 'app_key，用于区分客户端应用，首次接入需要创建应用并等待管理员审核通过'),
        // 'sign' => array('name' => 'sign', 'desc' => '动态签名，签名算法是：<br/><ul><li>1、全部参数（排除sign），按key进行字典排序</li><li>2、全部参数值，把原始值按字符串进行拼接，并在最后加上app_secret密钥</li><li>3、对第2步结果，拼接密钥后，进行MD5加密</li><li>4、对第3步结果，转成大写，得到sign签名（32位）</li></ul>'),
        // 'uid' => array('name' => 'uid', 'type' => 'int', 'default' => 0, 'desc' => ''),
        // 'accessToken' => array('name' => 'access_token', 'default' => '', 'desc' => '访问令牌，保留使用但不需要在文档上展示', 'is_doc_hide' => true),
        
        'encryptData' => array('name' => 'encrypt_data', 'desc' => '客户端加密的接口，格式是：RSA公钥加密(base64编码(JSON原始数据))。开启后，同时支持原来普通的参数传递方式。'),
    ),
```

### 客户端RSA公钥加密传输API接口参数

将rsa公钥文件：./config/phalapi_pro_rsa.pub提供给受信任的客户端开发人员，其中待加密的encrypt_data参数格式是：  

encrypt_data = RSA公钥加密(base64编码(JSON原始数据))  

也就是：
 + 第1步、把全部的原始参数通过JSON格式封装
 + 第2步、对第1步结果进行base64编码
 + 第3步、使用RSA公钥进行加密
 + 第4步、得到encrypt_data

以下是PHP作为客户端编写的代码示例：  
```php
    // 公钥加密-由客户端完成
    $public_key = openssl_pkey_get_public(file_get_contents('./phalapi_pro_rsa.pub'));
    openssl_public_encrypt('{"name":"phalapi pro"}', $crypted, $public_key);
    $_REQUEST['encrypt_data'] = base64_encode($crypted);
    // K07LIy/V+cfZqfHgZpIPnmdkwlkFbTkyRXVXx2JfQrF3YFAIsFcHnC9TjGTezzyup2f0V24nYH71Uf3oXVIqz/X9wgPXW0AGAbJw4kDOIq9Jao5L0mG7t5FV/2DLzJ14qO6fvANv6e/Hy2pFBcKvHnQ8uRJ/wyAV+RpUAa21wCY6zzuo9OhS89NPZg4B4CUORR8SIuIqWTUlXHB0woFIfRiO/AKCGltc9oDkyzJFYVvgI0LwijkQUV9RoruCEx6EvmZY7OVLB5+AXwfnfFKKtCw3jucqHyclzXwCQoif8FXN1NzCNpYvwj7DbzqU/WzRgxnPgXQSyjbCDlw19BrWoQ==
```
以上，原始的参数是：```{"name":"phalapi pro"}```，最后加密后的结果如上。  

> 温馨提示：客户端可以把需要加密的参数放到encrypt_data，同时服务端接口也会继续支持原来原始参数的传递。两者重复时以加密的参数为准。  

### 服务端RSA私钥解密API接口参数

此部分由后端API接口自动完成。不影响原有的接口参数传递方式和接口参数规则配置。   

## composer的使用

PhalApi专业版和PhalApi 2.x开源一样，都是使用了composer，如果对PHP composer不熟悉，可以查看[Composer 中文网 / Packagist 中国全量镜像](https://www.phpcomposer.com/)，简单来说，composer是 PHP 用来管理依赖（dependency）关系的工具。  

由于国外镜像通常会很慢，本地使用时可以切换到中国镜像。执行以下命令：  
```
$ composer config -g repo.packagist composer https://packagist.phpcomposer.com
```

## 扩展阅读
更多内容请参考[PhalApi 2.x 开发文档](http://docs.phalapi.net/#/)。


