icekson / remote-services
构建服务器API的远程服务
Requires
- php: >=5.4
- doctrine/annotations: ~1.4
- monolog/monolog: ~1.12
- psr/container: ^1.0
- psr/log: 1.0.*
- zendframework/zend-cache: ~2.5
- zfr/rbac: ~1.2
Requires (Dev)
- phpunit/phpunit: ~6.0
- phpunit/phpunit-mock-objects: ^4.0
This package is not auto-updated.
Last update: 2024-09-14 18:20:43 UTC
README
Api services是以一种框架独立的方式开发的,可以与任何PHP框架一起工作;
用于派发的路由是/api/v:version/:serviceName/:actionName,其中
* version : version of api (for example v1)
* serviceName - service's name
* serviceAction - action's name
每个服务都应该实现\Api\Service\RemoteServiceInterface。为了确定应该派发哪些服务和动作,我使用了注解(使用Doctrine\Common\Annotations作为解析注解的引擎)。
示例
namespace Service; use Api\Service\RemoteServiceInterface /** * Class AdvertiserStatsService * @Service(name = "advertiser") */ class AdvertiserStatsService implements RemoteServiceInterface { /** * @ServiceAction(name="GetOffers") * * */ public function getOffers() { } }
为了方便,已实现抽象类Api\Service\BaseService,它已经实现了RemoteServiceInterface接口,可以用作服务的基类;
namespace Service; use Api\Service\BaseService /** * Class AdvertiserStatsService * @Service(name = "advertiser") */ class AdvertiserStatsService extends BaseService { /** * @ServiceAction(name="GetOffers") * * */ public function getOffers() { } }
顺便说一句,如果您想将您的服务隐藏起来(通过token授权、权限检查等),您必须实现两个额外的接口Api\Service\SecurityServiceInterface和Api\Service\SecurityOwnerPermissionInterface
下面是服务示例的完整版本
namespace Service; use Api\Service\BaseService; use Api\Service\SecurityOwnerPermissionInterface; use Api\Service\SecurityServiceInterface; use Api\Service\IdentityInterface; /** * Class AdvertiserStatsService * @Service(name = "advertiser") */ class AdvertiserStatsService extends BaseService implements SecurityServiceInterface, SecurityOwnerPermissionInterface { /** * @ServiceAction(name="GetOffers") * * */ public function getOffers() { } public function isPermitted($token){} /** * @param Properties $params * @return IdentityInterface|null * @throw NoTokenException */ public function getIdentity(Properties $params = null){} public function checkOwnPermission(); }
如代码所示,服务的名称由相应的注解@Service(name = "advertiser")定义,动作的名称由@ServiceAction(name="GetOffers")定义,这与URL: /api/v1/advertiser/GetOffers相匹配
请求派发
工作流程如下
1 从路由中检索服务和动作的名称 2 创建新的\Api\Dispatcher实例 3 将包含服务实现的路径全部注册到派发器中 4 调用相应的服务
示例
$version = "1"; $serviceName = "advertiser"; $serviceAction = "GetOffers"; $params = []; // some GET or POST params $responseBuilder = new \Api\Service\Response\JsonBuilder(); // also it can be XmlBuilder or HtmlBuilder $sm = new \Zend\ServiceManager\ServiceManager($conf); $dispatcher = new \Api\Dispatcher(); $dispatcher->registerServicesPath(API_ROOT . "v".$version . "/services/Service/"); // this folder contains implementations of Services $jsonResp = $dispatcher->dispatch($serviceName, $serviceAction, $params, $responseBuilder, $sm);
权限设置
如果您的服务实现了Api\Service\SecurityServiceInterface,那么您应该在每次请求中发送access_token参数,您可以将所有token保存在数据库或其他地方。您必须在SecurityServiceInterface::isPermitted($token)方法中实现token的验证
基于角色的权限
角色和相关权限的列表放在文件api/config/permissions.php中。在这里我们应该描述角色。如果角色没有明确定义,则拒绝访问。可以定义适当的服务的动作(serviceName.serviceAction)以及服务中的所有动作(serviceName.*)的访问权限
您还可以使用角色的继承,通过在任意角色中使用键'extends'来列出父角色,在这种情况下,该角色将继承所有父角色的权限;
api/config/permissions.php的示例
return array( 'roles' => array( 'developer' => array( 'permissions' => array( 'advertiser.*' ), 'extends' => 'test' ), 'affiliate' => array( 'permissions' => array( 'test.*', ) ), 'admin' => array( 'extends' => array( 'publisher', 'developer' ) ), 'test' => array( 'permissions' => array( 'test.GetGroupedData', ) ) ) );
列、筛选和分组的设置
对于任何动作,我们都可以使用可用的列、筛选和分组。这里我们同样使用注解
@AcceptableColumns() @AcceptableFilters() @AceptableGroupings()
一些示例
// 1 example /** * @ServiceAction(name="GetTerritoryStatistics") * * @AcceptableColumns({ * AdvertiserFilter::FIELD_OFFER_NAME, * AdvertiserFilter::FIELD_TERRITORY, * AdvertiserFilter::FIELD_CLICKS, * AdvertiserFilter::FIELD_CONVERSIONS, * AdvertiserFilter::FIELD_CR, * AdvertiserFilter::FIELD_CR_PERSENTS, * AdvertiserFilter::FIELD_SPENT, * AdvertiserFilter::FIELD_CURRENCY * }) * * @AcceptableFilters({ * AdvertiserFilter::DATE_FROM, * AdvertiserFilter::DATE_TO, * AdvertiserFilter::FIELD_OFFER_ID, * AdvertiserFilter::FIELD_OFFER_DETAILS_ID * * }) * * @AcceptableGroupings({ * AdvertiserFilter::FIELD_CPA * }) */ public function getTerritoryStatistics() {} // 2 example /** * @ServiceAction(name="GetDailyStatistics") * @AcceptableColumns({ * AdvertiserFilter::FIELD_OFFER_NAME, * AdvertiserFilter::FIELD_DATE, * AdvertiserFilter::FIELD_CLICKS, * AdvertiserFilter::FIELD_CONVERSIONS, * AdvertiserFilter::FIELD_CR, * AdvertiserFilter::FIELD_CR_PERSENTS * }) * @AcceptableColumns(role = "admin", extendDefault = true, value = { * AdvertiserFilter::FIELD_BD_MANAGER_ID * }) * * @AcceptableFilters({ * AdvertiserFilter::DATE_FROM, * AdvertiserFilter::DATE_TO, * AdvertiserFilter::FIELD_OFFER_ID * }) */ public function getDailyStatistics() {}
如我们所见,我们可以在同一个地方多次放置相同的注解(@AcceptableColumns),在这种情况下,将选择最合适的用户角色,默认情况下将选择没有角色的注解。同样,您可以使用'extendDefault = true',所有列都将从默认注解扩展
AcceptableColumns
列可以定义为字符串或数组
api/v1/someService/someAction?columns=all api/v1/someService/someAction?columns=name,id,date api/v1/someService/someAction?columns[]=name&columns[]=id&columns[]=date
如果columns参数为空,则使用'columns=all'。如果您给出不正确的列,您将收到一个包含可用列的错误列表;
AcceptableFilters
api/v1/someService/someAction?filters[date_from]=2011-01-01&filters[date_to]=2014-01-01
AcceptableGroupings
格式与列相同
api/v1/someService/someAction?group=name,id,date api/v1/someService/someAction?group[]=name&group[]=id&group[]=date