DACA服务端提供基于HTTP的Web服务接口,实现BQP协议。
- 它提供函数调用型接口和对象调用型接口两类编程风格,并分别具有相应的权限检查方法。
- 它以数据模型为核心,提供通用对象操作接口,支持对通用接口的各种定制如只读字段、虚拟字段、子对象等,也支持为对象添加非标准接口。
- 它提供方便的参数获取、数据库调用、异常返回等函数。
- 参数 param / mparam
- 数据库 dbconn / queryOne / queryAll / execOne
- 权限 checkAuth / hasPerm / AccessControl
- 错误处理 MyException / jdRet
- 接口应用程序 ApiApp
- 日志处理 addLog / logit
获取请求参数
param(name, defVal?, coll?) -> val
mparam(name, coll?) -> val
@param $col: 默认先取$_GET再取$_POST,"G" - 从$_GET中取; "P" - 从$_POST中取
以下形式已不建议使用:
@fn param($name, $defVal?, $col?=$_REQUEST, $doHtmlEscape=true) @param $col: key-value collection
获取名为$name的参数。 $name中可以指定类型,返回值根据类型确定。如果该参数未定义或是空串,直接返回缺省值$defVal。
$name中指定类型的方式如下:
- 名为"id", 或以"Id"或"/i"结尾: int
- 以"/b"结尾: bool. 可接受的字符串值为: "1"/"true"/"on"/"yes"=>true, "0"/"false"/"off"/"no" => false
- 以"/dt"或"/tm"结尾: datetime
- 以"/n"结尾: numeric/double
- 以"/s"结尾(缺省): string. 缺省为防止XSS攻击会做html编码,如"a&b"处理成"a&b",设置参数doHtmlEscape可禁用这个功能。
- 复杂类型:以"/i+"结尾: int array
- 复杂类型:以"/js"结尾: json object
- 复杂类型:List类型(以","分隔行,以":"分隔列),类型定义如"/i:n:b:dt:tm" (列只支持简单类型,不可为复杂类型)
示例:
$id = param("id");
$svcId = param("svcId/i", 99);
$wantArray = param("wantArray/b", false);
$startTm = param("startTm/dt", time());
List类型示例。参数"items"类型在文档中定义为list(id/Integer, qty/Double, dscr/String),可用param("items/i:n:s")获取, 值如
items=100:1:洗车,101:1:打蜡
返回
[ [ 100, 1.0, "洗车"], [101, 1.0, "打蜡"] ]
如果某列可缺省,用"?"表示,如param("items/i:n?:s?")可获取值:
items=100:1,101::打蜡
返回
[ [ 100, 1.0, null], [101, null, "打蜡"] ]
TODO: 直接支持 param("items/(id,qty?/n,dscr?)"), 添加param_objarr函数,去掉parseList函数。上例将返回
[
[ "id"=>100, "qty"=>1.0, dscr=>null],
[ "id"=>101, "qty"=>null, dscr=>"打蜡"]
]
定义函数 api_{接口名}
,可实现一个函数型接口,如“查询订单列表”的接口定义为:
queryOrder() -> [{id, dscr, total}]
可实现为:
function api_queryOrder()
{
$data = [
[ "id" => 100, "dscr" => "基本套餐", "total" => 128],
[ "id" => 101, "dscr" => "高级套餐", "total" => 198],
];
// 成功返回,不关心最终协议格式。
return $data;
}
接口实现只与接口原型定义相关,不关心URL映射等通讯协议层面的实现,框架应定义URL如何被映射到接口实现函数。
函数的返回值即是接口原型中描述的当成功调用时返回的数据结构。 框架应自动完成后续的添加返回码0、序列化传输(如转成JSON字符串)等过程。
如果函数没有返回值(即接口原型中未定义返回值),则框架应保证调用成功时返回字符串"OK",JSON序列化后即[0, "OK"]
。
如果使用不支持全局函数的编程语言(如java/C#等)实现,建议将接口实现函数放在名为"Global"的类中,如java实现示例:
// Global类中的公有函数`api_xxx`为函数型接口实现类
public class Global
{
public Object api_queryOrder()
{
JsArray data = new JsArray(
new JsObject(
"id", 100,
"dscr", "基本套餐",
"total", 128
),
new JsObject(
"id", 101,
"dscr", "高级套餐",
"total", 198
)
);
return data;
}
}
其中,工具类"JsArray"和"JsObject"是可以支持通用类型(元素可以是数值、字符串、数组、对象等,一般用Object表示)、支持一行初始化数据,且可被序列化成JSON数组和对象的类。
函数型接口在实现时,通常由权限检查、参数检查、失败返回、成功返回几部分组成,大致示例如下:
function api_queryOrder()
{
// 权限检查,失败则自动返回错误数据
checkAuth(AUTH_LOGIN);
// 获取参数,可自动检查日期类型
$dt1 = param("date1/dt");
// 失败返回,不关心失败数据处理细节
if (...) throw MyException(2, "XXX失败");
// 一行查询数据库,不关心连接等细节
$data = queryAll("SELECT ...");
// 成功返回,不关心最终协议格式。
return $data;
}
接口权限控制主要通过checkAuth与hasPerm两个函数实现,直接当权限检查失败,直接由框架返回错误信息;后者则返回bool值让应用来处理。
对象型接口要求实现时定义名为{AC}_{对象名}
的类(称之为AC类),该类通过继承AccessControl基类,即可完成BQP协议中通用对象接口的全部功能,如对象增删改查、分页控制、灵活查询、统计分析、导出文件等。
示例:假如订单对象名为"Ordr"(不用"Order"以避免与同名数据库关键字冲突),它对应“订单”这个数据模型,以下代码可将其全部操作暴露:
class AC_Ordr extends AccessControl
{
}
通过简单地继承AccessControl类,使其拥有BQP协议中定义的通用对象接口,如:
Ordr.add()(要添加的字段如dscr, total) -> id
Ordr.get(id) -> {id, status, dscr, total, ...}
Ordr.set(id)(要修改的字段...)
Ordr.del(id)
Ordr.query(res?, cond?, orderby?, pagesz?, pagekey?) -> table(id, status, dscr, ...)
对象型接口的权限检查主要通过登录身份与类名前缀相关联实现,例如按惯例,前缀"AC"表示允许所有角色访问。
框架应支持名为onCreateAC
的回调函数,让编程者可以自定义什么角色访问什么名称的类,例如:
function onCreateAC($tbl)
{
$cls = null;
if (hasPerm(AUTH_USER))
{
$cls = "AC1_$tbl";
if (! class_exists($cls))
$cls = "AC_$tbl";
}
else if (hasPerm(AUTH_EMP))
{
$cls = "AC2_$tbl";
}
return $cls;
}
上述代码表示如果是用户登录(权限定义为AUTH_USER),则对象接口实现的类名为"AC1_{XXX}"(如果未定义该类则尝试"AC_{XXX}"类),如果是员工登录(权限定义为AUTH_EMP),则应调用类"AC2_{XXX}"来实现接口。
如果返回null,则默认类名为AC_{XXX}
。
下面是java实现对象访问权限控制与AC类名关联的参考,由类DacaEnvBase提供该回调函数:
public class MyServiceEnv extends DacaEnvBase
{
public String onCreateAC(String table)
{
if (hasPerm(AUTH_USER))
{
String cls = "AC1_" + table;
try {
Class.forName("com.demo." + cls);
} catch (Exception ex) {
cls = "AC_" + table;
}
return cls;
}
else if (hasPerm(AUTH_EMP))
{
return "AC2_" + table;
}
return null;
}
}
AC类除提供通用对象接口的实现,还应支持多种权限控制,下面详述。
TODO
通过appType确定认证类型。在登录后,将认证类型、用户id、用户权限存入会话中。在session中名称分别为appType, uid, perms。
开发者应定义AUTH_XXX和PERM_XXX系列权限。
实现 checkAuth/hasPerm:
- AUTH_USER : appType=='user'
- AUTH_EMP: appType == 'emp'
- PERM_MGR: appType == 'emp' && perms.contains("mgr")
- PERM_TEST_MODE: global $TEST_MODE