是否为测试模式。
0-9间的调试等级。
应用程序的主目录,用于写文件。默认为 {user.home}/jd-data/{project}.
在CentOS下运行tomcat时,往往默认位置为 /usr/share/tomcat/jd-data/{project}
如果在web.properties中设置了
baseDir=.
表示以项目部署目录为基本目录。
@key web.properties
@key jdcloud-config
配置选项。从WEB-INF/web.properties中加载,也可以在env.onApiInit回调函数中修改。
作为WebApi应用程序入口。
所有的接口实现都应与该类在同一个包中。
示例:连接mysql, 使用库mysql-connector-java-5.1.34.jar
P_DBTYPE=mysql
P_DB_DRIVER=com.mysql.jdbc.Driver
P_DB=jdbc:mysql://localhost:3306/jdcloud?characterEncoding=utf8
P_DBCRED=demo:demo123
示例:连接sqlserver(mssql),使用库sqljdbc42.jar
P_DBTYPE=mssql
P_DB_DRIVER=com.microsoft.sqlserver.jdbc.SQLServerDriver
P_DB=jdbc:sqlserver://localhost:1433;instanceName=MSSQL1;databaseName=jdcloud;integratedSecurity=false;
P_DBCRED=sa:demo123
示例:连接mysql, 且使用连接池,在web.properties中设置:
P_DBTYPE=mysql
P_DB_DRIVER=DataSource
P_DB=jdbc/jdcloud
在web.xml的<web-app>标签中增加:
<resource-ref>
<res-ref-name>jdbc/jdcloud</res-ref-name>
<res-type>javax.sql.DataSource</res-type>
<res-auth>Container</res-auth>
</resource-ref>
在<Context>标签中增加Resource定义:
<Resource name="jdbc/jdcloud" auth="Container" driverClassName="com.mysql.jdbc.Driver" factory="org.apache.tomcat.jdbc.pool.DataSourceFactory"
maxActive="100" username="demo" password="demo123" type="javax.sql.DataSource" url="jdbc:mysql://server-pc:3306/jdcloud?characterEncoding=utf8"/>
P_DEBUG: Integer. 0-9之间,默认为0. 调试等级。为9时输出SQL日志。可用env.debugLevel获取。
@fn callSvc (ac, param, postParam, opt={backupEnv, isCleanCall, asAdmin})
调用接口,获得返回值。
如果指定了非空的param, postParam参数,则会并入当前环境的GET, POST参数中。
通过opt参数可调整行为。
@param opt.backupEnv 如果为true, 调用完成后恢复原先的GET, POST参数等。
@param opt.isCleanCall 如果为true,不使用原先环境,只用param, postParam作为GET/POST环境。
@param opt.asAdmin TODO: 以超级管理员权限调用。
JsObject rv = (JsObject)callSvc("User.get");
它不额外处理事务、不写ApiLog。
对于函数型调用,返回接口实现类(应继承JDApiBase)列表。默认为"Global":
return new String[] { "Global" };
也可以从多个类加载,常用于添加插件,如:
return new String[] { "Global", "JDLogin", "JDUpload" };
对于对象型调用,根据对象名(table)返回一个类名数组,用于绑定权限与AC类。注意类名不带包名。
默认逻辑作为示例:
if (hasPerm(AUTH_USER)) {
return new String[] { "AC1_" + table, "AC_" + table };
}
else if (hasPerm(AUTH_EMP)) {
return new String[] { "AC2_" + table };
}
else if (hasPerm(AUTH_ADMIN)) {
return new String[] { "AC0_" + table, "AccessControl" };
}
return new String[] {"AC_" + table};
它表示:
返回权限集合。一般根据session来设置。默认检查uid, empId, adminId三个session变量,如果存在则认为具有用户、员工、超级管理员登录权限。
int perms = 0;
if (_SESSION("uid") != null) {
perms |= AUTH_USER;
}
else if (_SESSION("empId") != null) {
perms |= AUTH_EMP;
}
else if (_SESSION("adminId") != null) {
perms |= AUTH_ADMIN;
}
return perms;
API调用前的回调函数。例如设置选项、检查客户版本等。
示例:关闭ApiLog
this.props.setProperty("enableApiLog", "0");
连接数据库后回调,用于设置连接选项,或切换数据库。示例:
@Override
protected void onDbconn() throws Exception {
String project = this.ctx.getContextPath();
if (project.indexOf("-hg") > 0) {
this.conn.setCatalog("pdi_hg");
}
}
如果想忽略输出一条SQL日志,可以在调用SQL查询前设置skipLogCnt,如:
++ env.skipLogCnt; // 若要忽略两条就用 env.skipLogCnt+=2;
execOne(...);
执行查询语句,只返回一行数据,如果行中只有一列,则直接返回该列数值。
如果查询不到,返回false,可以用Objects.equals(rv, false)来判断。注意返回值可能为null,不要用rv.equals(false)判断。
示例:查询用户姓名与电话,默认返回值数组(JsArray):
Object rv = queryOne("SELECT name,phone FROM User WHERE id=" + id);
if (Objects.equals(rv, false))
throw new MyException(E_PARAM, "bad user id");
JsArray row = (JsArray)rv;
// row = ["John", "13712345678"]
指定参数assoc=true时,返回关联数组:
Object rv = queryOne("SELECT name,phone FROM User WHERE id=" + id, true);
if (Objects.equals(rv, false))
throw new MyException(E_PARAM, "bad user id");
JsObject row = (JsObject)rv;
// row = {"name": "John", "phone":"13712345678"}
当查询结果只有一列且参数assoc=false时,直接返回该数值。
Object phone = queryOne("SELECT phone FROM User WHERE id="+id);
if (Objects.equals(phone, false))
throw new MyException(E_PARAM, "bad user id");
// phone = "13712345678"
@see queryAll
@param getInsertId ?=false 取INSERT语句执行后得到的id. 仅用于INSERT语句。
执行SQL语句,如INSERT, UPDATE等。执行SELECT语句请使用queryOne/queryAll.
String token = (String)mparam("token");
execOne("UPDATE Cinf SET appleDeviceToken=" . Q(token));
注意:在拼接SQL语句时,对于传入的String类型参数,应使用Q函数进行转义,避免SQL注入攻击。
对于INSERT语句,当设置参数getInsertId=true时, 可返回新加入数据行的id. 例:
String sql = String.format("INSERT INTO Hongbao (userId, createTm, src, expireTm, vdays) VALUES (%s, '%s', '%s', '%s', %s)",
userId, createTm, src, expireTm, vdays);
int hongbaoId = execOne(sql, true);
字段值可以是数值、日期、字符串等类型的变量。
如果值为null或空串会被忽略。
e.g.
int orderId = dbInsert("Ordr", new JsObject(
"tm", new Date(), // 支持Date类型
"tm1", dbExpr("now()"), // 使用dbExpr直接提供SQL表达式
"amount", 100,
"dscr", null // null字段会被忽略
));
@param id_or_cond 查询条件,如果是数值比如100或"100",则当作条件"id=100"处理;否则直接作为查询表达式,比如"qty<0";如果未指定则无查询条件。
e.g.
// UPDATE Ordr SET ... WHERE id=100
int cnt = dbUpdate("Ordr", new JsObject(
"amount", 30,
"dscr", "test dscr",
"tm", "null", // 用""或"null"对字段置空;用"empty"对字段置空串。
"tm1", null // null会被忽略
), 100);
// UPDATE Ordr SET tm=now() WHERE tm IS NULL
int cnt = dbUpdate("Ordr", new JsObject(
"tm", dbExpr("now()") // 使用dbExpr,表示是SQL表达式
), "tm IS NULL);
用于在dbInsert/dbUpdate(插入或更新数据库)时,使用表达式:
int id = dbInsert("Ordr", asMap(
"tm", dbExpr("now()") // 使用dbExpr直接提供SQL表达式
));
记录日志到文件。type参数指定日志文件名,例如默认为trace,则日志写到trace.log。
val支持多种类型。
示例:
boolean forTest = param("test/b", false);
boolean isTestMode = parseBoolean(getenv("P_TEST_MODE", "0")); // 仅用作示例,可以直接用 env.isTestMode
支持以下日期格式:
"2010/1/1 10:10", "2011-2-1 8:8:8", "2010.3.4", "2011-02-01T10:10:10Z"
如果格式无法解析,返回null。
如果onlyDatePart=true,则只取日期部分,忽略时间部分。
java.util.Date tm = parseDate("20180101121030", "yyyyMMddHHmmss");
if (tm == null) // fail to parse
return;
String tmstr = date("yyyy-MM-dd HH:mm:ss", tm); // "2018-01-01 12:10:30"
如果格式无法解析,返回null。
@param col null - (默认)先取URL参数(env._GET)再取POST参数(api._POST),"G" - 从env.GET中取; "P" - 从env.POST中取
获取名为name的参数。
name中可以指定类型,返回值根据类型确定。如果该参数未定义或是空串,直接返回缺省值defVal。
name中指定类型的方式如下:
示例:
Integer id = (Integer)param("id");
Integer svcId = (Integer)param("svcId/i", 99);
Boolean wantArray = (Boolean)param("wantArray/b", false);
Date startTm = (Date)param("startTm/dt", new Date());
List<Integer> idList = (List<Integer>)param("idList/i+");
List类型示例。假设参数"items"类型在文档中定义为
items
: list(id/Integer, qty/Double, dscr/String)
这样取该参数,返回值为JsArray类型,数组中每一项又是JsArray类型(下面用中括号表示JsArray):
JsArray items = (JsArray)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=>"打蜡"]
]
必填参数(mandatory param)
参考param函数,查看name如何支持各种类型。
示例:
String svcId = (String)mparam("svcId");
Integer svcId = (Integer)mparam("svcId/i");
List<Integer> itts = (List<Integer>)mparam("itts/i+");
name也可以是一个数组,表示至少有一个参数有值,这时返回每个参数的值。
JsArray rv = mparam(new String[] {"svcId/i", "itts/i+"}); // require one of the 2 params
Integer svcId = rv.get(0);
List<Integer> itts = rv.get(1);
几个参数,必填其一(mandatory param)
names是一个数组,表示至少有一个参数有值,返回JsArray,包含每个参数的值,其中有且只有一个非null。
JsArray rv = mparam(new String[] {"svcId/i", "itts/i+"}); // require one of the 2 params
Integer svcId = rv.get(0);
List<Integer> itts = rv.get(1);
if (svcId != null) {
}
else if (itts != null) {
}
将table格式转为 objarr, 即前端的rs2Array, 如:
table2objarr(
[
"h"=>["id", "name"],
"d"=>[
[100,"A"],
[101,"B"]
]
]
) -> [ ["id"=>100, "name"=>"A"], ["id"=>101, "name"=>"B"] ]
将值数组类型 d (仅有值的二维数组, elem=[$col1, $col2] ) 转为对象数组objarr, elem={col1=>cell1, col2=>cell2})
例:
varr2objarr(
[ [100, "A"], [101, "B"] ],
["id", "name"] )
-> [ ["id"=>100, "name"=>"A"], ["id"=>101, "name"=>"B"] ]
将字符串代表的压缩表("v1:v2:v3,...")转成值数组。
e.g.
$users = "101:andy,102:beddy";
$varr = list2varr($users);
// $varr = [["101", "andy"], ["102", "beddy"]];
$cmts = "101\thello\n102\tgood";
$varr = list2varr($cmts, "\t", "\n");
// $varr=[["101", "hello"], ["102", "good"]]
取全局设置。
String cred = getenv("P_ADMIN_CRED");
boolean isTestMode = parseBoolean(getenv("P_TEST_MODE", "0")); // 仅用作示例,可以直接用 env.isTestMode
int debugLevel = Integer.parseInt(getenv("P_DEBUG", "0")); // 仅用作示例,可以直接用 env.debugLevel
对字符串data加密,生成base64编码的字符串。
或者反过来,对base64编辑的data解密返回原文。
出错返回null。
算法:DES加密。
@param op "E"-加密(encrypt); "D"-解密(decrypt)
cred为"{user}:{pwd}"格式,支持使用base64编码。
返回2元素字符数组,表示user和pwd。
出错时返回null。
示例:
String[] cred = getCred(getenv("P_ADMIN_CRED"));
if (cred == null) {
// 未设置用户名密码
}
String user = cred[0], pwd = cred[1];
对密码进行加密处理。如果已加密过,则直接返回。
用于获得以"/"结尾或不以"/"结尾的路径。
String path = getPath("dir1/dir2/", true); // "dir1/dir2/"
String path = getPath("dir1/dir2", true); // "dir1/dir2/"
String path = getPath("dir1/dir2", false); // "dir1/dir2"
String path = getPath("dir1/dir2/", false); // "dir1/dir2"
返回项目的URL,以"/"结尾。
String url = getBaseUrl(true); // "http://myserver/myapp/"
String url1 = getBaseUrl(false); // "/myapp/"
立即返回,不再处理,不自动输出任何内容。
如果想输出返回数据,请自行用echo输出后再调用exit,或直接用DirectReturn(val)返回[0,val]
标准格式.
@see DirectReturn
为查询添加时间维度单位: y,q,m,w,d,wd,h (年,季度,月,周,日,周几,时)。
示例:
this.vcolDefs = asList(
new VcolDef().res(tmCols())
);
this.vcolDefs = asList(
new VcolDef().res(tmCols("log_cr.tm")).require("createTm")
);
判断POST内容中是否对该字段设置值,示例:
(在onValidate回调中)
if (issetval("pwd")) {
String pwd = (String)env._POST.get("pwd");
env._POST.put("pwd", hashPwd(pwd));
}
解析key-value列表字符串。如果出错抛出异常。
注意:sep, sep2为行、列分隔符的正则式。
示例:
Map<String, Object> map = parseKvList("CR:新创建;PA:已付款", ";", ":");
// map: {"CR": "新创建", "PA":"已付款"}
postParams非空使用POST请求,否则使用GET请求。
postParams可以是字符串、map或list等数据结构。默认contentType为"x-www-form-urlencoded"格式。如果postParams为list等结构,则使用"json"格式。
如果要明确指定格式,可以设置opt.contentType参数,如
String rv = httpCall(baseUrl, urlParams, postParams, asMap("contentType", "application/json"));
e.g.
String baseUrl = "http://oliveche.com/echo.php";
// 常用asMap或new JsObject
Map<String, Object> urlParams = asMap("intval", 100, "floatval", 12.345, "strval", "hello");
JsObject postParams = new JsObject("postintval", 100, "poststrval", "中文");
String rv = httpCall(baseUrl, urlParams, postParams, null);
@alias enumFields (fieldName, EnumFieldFn)
(版本v1.1)
格式为{field => map/fn(val, row) }
作为比onHandleRow/onAfterActions等更易用的工具,enumFields可对返回字段做修正。例如,想要对返回的status字段做修正,如"CR"显示为"Created",可设置:
Map<String, Object> map = asMap("CR","Created", "CA","Cancelled");
this.enumFields("status", map)
也可以设置为自定义函数,如:
this.enumFields("status", (v, row) -> {
if (map.containsKey(v))
return String.format("%s-%s", v, map.get(v));
return v;
});
此外,枚举字段可直接由请求方通过res参数指定描述值,如:
Ordr.query(res="id, status =CR:Created;CA:Cancelled")
或指定alias:
Ordr.query(res="id 编号, status 状态=CR:Created;CA:Cancelled")
更多地,设置enumFields也支持逗号分隔的枚举列表,比如字段值为"CR,CA",实际可返回"Created,Cancelled"。
如果设置该字段(例如设置为disableFlag字段),则把删除动作当作是设置该字段为1,且在查询接口中跟踪此字段增加过滤。
必须是flag字段(0/1值)。
示例:
// onInit中:
this.delField = "disableFlag"
直接调用指定类的接口,如内部直接调用"PdiRecord.query"方法:
// 假如当前是AC2权限,对应的AC类为AC2_PdiRecord:
AccessControl acObj = new AC2_PdiRecord();
acObj.env = env; // 别忘记指定env
acObj.callSvc("PdiRecord", "query");
这相当于调用callSvc("PdiRecord.query")
。
区别是,用本方法可自由指定任意AC类,无须根据当前权限自动匹配类。
例如,"PdiRecord.query"接口不对外开放,只对内开放,我们就可以只定义class PdiRecord extends AccessControl
(无AC前缀,外界无法访问),在内部访问它的query接口:
AccessControl acObj = new PdiRecord();
acObj.env = env;
acObj.callSvc("PdiRecord", "query");
如果未指定param/postParam,则使用当前GET/POST环境参数执行,否则使用指定环境执行,并在执行后恢复当前环境。
也适用于AC类内的调用,这时可不传table,例如调用当前类的add接口:
Object rv = this.callSvc(null, "add", null, postParam);
示例:通过手机号发优惠券时,支持批量发量,用逗号分隔的多个手机号,接口:
手机号userPhone只有一个时:
Coupon.add()(userPhone, ...) -> id
如果userPhone包含多个手机号:(用逗号隔开,支持中文逗号,支持有空格)
Coupon.add()(userPhone, ...) -> {cnt, idList}
重载add接口,如果是批量添加则通过callSvc再调用add接口:
public Object api_add() {
if (this._POST.containsKey("userPhone")) {
String userPhone = (String)this._POST.get("userPhone");
String[] arr = userPhone.split("(?U)[,,]"); // 支持中文逗号
if (arr.length > 1) {
List idList = new ArrayList();
JsObject postParam = new JsObject();
postParam.putAll(this._POST);
for (String e: arr) {
postParam.put("userPhone", e.trim());
idList.add(this.callSvc(null, "add", null, postParam));
}
setRet(0, asMap(
"cnt", idList.size(),
"idList", idList
);
throw new DirectReturn();
}
}
return super.api_add();
}
框架自带的批量添加接口api_batch也是类似调用。
@see callSvc
@see callSvcSafe
"set"/"setIf"接口中,限定可设置的字段。
不可设置的字段用readonlyFields/readonlyFields2来设置。
e.g.
void onValidate()
{
if (this.ac.equals("set"))
checkSetFields(asList("status", "cmt"));
}
批量更新。
setIf接口会检测readonlyFields及readonlyFields2中定义的字段不可更新。
也可以直接用checkSetFields指定哪些字段允许更新。
返回更新记录数。
示例:
class AC2_Ordr extends AccessControl {
@Override
public Object api_setIf() throws Exception {
checkAuth(App.PERM_MGR);
this.checkSetFields(asList("dscr", "amount"));
Object empId = getSession("empId");
addCond("t0.empId=" + empId);
// addJoin("...");
return super.api_setIf();
}
}
批量删除。返回删除记录数。
示例:
class AC2_Ordr extends AccessControl {
@Override
public Object api_delIf() throws Exception {
checkAuth(App.PERM_MGR);
return super.api_delIf();
}
}
由于cond参数的特殊性,不宜用param("cond")来取,可以使用:
String cond = getCondParam("cond");
支持GET/POST中各有一个cond/gcond条件。而且支持其中含有">","<"等特殊字符。
没有cond则返回null
处理objArr,按照fmt参数指定的格式返回,与query接口返回相同。例如,默认的h-d
表格式, list
格式,excel
等。
模糊查询
示例接口:
Obj.query(q) -> 同query接口返回
查询匹配参数q的内容(比如查询name, label等字段)。
参数q是一个字符串,或多个以空格分隔的字符串。例如"aa bb"表示字段包含"aa"且包含"bb"。
每个字符串中可以用通配符"",如"a"表示以a开头,"a"表示以a结尾,而"a*"和"a"是效果相同的。
实现:
protected function onQuery() {
this.qsearch(asList("name", "label", "content"), param("q"));
}
@param prepend 为true时将条件排到前面。
调用多次addCond时,多个条件会依次用"AND"连接起来。
添加查询条件。
示例:假如设计有接口:
Ordr.query(q?) . tbl(..., payTm?)
参数:
q:: 查询条件,值为"paid"时,查询10天内已付款的订单。且结果会多返回payTm/付款时间字段。
实现时,在onQuery中检查参数"q"并定制查询条件:
protected void onQuery()
{
// 限制只能看用户自己的订单
uid = _SESSION["uid"];
this.addCond("t0.userId=uid");
q = param("q");
if (isset(q) && q.equals("paid")) {
validDate = date("Y-m-d", strtotime("-9 day"));
this.addRes("olpay.tm payTm");
this.addJoin("INNER JOIN OrderLog olpay ON olpay.orderId=t0.id");
this.addCond("olpay.action='PA' AND olpay.tm>'validDate'");
}
}
根据列名找到vcolMap中的一项,添加到最终查询语句中.
vcolMap是分析vcolDef后的结果,每一列都对应一项;而在一项vcolDef中可以包含多列。
@param col 必须是一个英文词, 不允许"col as col1"形式; 该列必须在 vcolDefs 中已定义.
@param alias 列的别名。可以中文. 特殊字符"-"表示不加到最终res中(只添加join/cond等定义), 由addVColDef内部调用时使用.
@return Boolean T/F
用于AccessControl子类添加已在vcolDefs中定义的vcol. 一般应先考虑调用addRes(col)函数.
返回是否为导出文件请求。
批量添加(导入)。返回导入记录数cnt及编号列表idList
Obj.batchAdd(title?)(...) -> {cnt, @idList}
在一个事务中执行,一行出错后立即失败返回,该行前面已导入的内容也会被取消(回滚)。
支持三种方式上传:
直接在HTTP POST中传输内容,数据格式为:首行为标题行(即字段名列表),之后为实际数据行。
行使用"\n"分隔, 列使用"\t"分隔.
接口为:
{Obj}.batchAdd(title?)(标题行,数据行)
(Content-Type=text/plain)
前端JS调用示例:
var data = "name\taddr\n" + "门店1\t地址1\n门店2\t地址2\n";
callSvr("Store.batchAdd", function (ret) {
app_alert("成功导入" + ret.cnt + "条数据!");
}, data, {contentType:"text/plain"});
或指定title参数:
var data = "门店名\t地址\n" + "门店1\t地址1\n门店2\t地址2\n";
callSvr("Store.batchAdd", {title: "name,addr"}, function (ret) {
app_alert("成功导入" + ret.cnt + "条数据!");
}, data, {contentType:"text/plain"});
示例: 在chrome console中导入数据
callSvr("Vendor.batchAdd", {title: "-,name, tel, idCard, addr, email, legalAddr, weixin, qq, area, picId"}, $.noop, `编号 姓名 手机号码 身份证号 通讯地址 邮箱 户籍地址 微信号 QQ号 负责安装的区域 身份证图
112 郭志强 15384813214 150221199211215000 内蒙古呼和浩特赛罕区丰州路法院小区二号楼 815060695@qq.com 内蒙古包头市 15384813214 815060695 内蒙古 532
111 高长平 18375998418 500226198312065000 重庆市南岸区丁香路同景国际W组 1119780700@qq.com 荣昌 18375998418 1119780700 重庆 534
`, {contentType:"text/plain"});
上传的文件首行当作标题列,如果这一行不是后台要求的标题名称,可通过URL参数title重新定义。
一般使用excel csv文件(编码一般为gbk),或txt文件(以"\t"分隔列)。
接口为:
{Obj}.batchAdd(title?)(csv/txt文件)
(Content-Type=multipart/form-data, 即html form默认传文件的格式)
后端处理时, 将自动判断文本编码(utf-8或gbk).
前端HTML:
<input type="file" name="f" accept=".csv,.txt">
前端JS示例:
var fd = new FormData();
fd.append("file", frm.f.files[0]);
callSvr("Store.batchAdd", {title: "name,addr"}, function (ret) {
app_alert("成功导入" + ret.cnt + "条数据!");
}, fd);
或者使用curl等工具导入:
从excel中将数据全选复制到1.txt中(包含标题行,也可另存为csv格式文件),然后导入。
下面示例用curl工具调用VendorA.batchAdd导入:
#/bin/sh
baseUrl=http://localhost/p/anzhuang/api.php
param=title=name,phone,idCard,addr,email,legalAddr,weixin,qq,area
curl -v -F "file=@1.txt" "$baseUrl/VendorA.batchAdd?$param"
如果要调试(php/xdebug),可加URL参数XDEBUG_SESSION_START=1
或Cookie中加XDEBUG_SESSION=1
传入对象数组
格式为 {list: [...]}
var data = {
list: [
{name: "郭志强", tel: "15384813214"},
{name: "高长平", tel: "18375998418"}
]
};
callSvr("Store.batchAdd", function (ret) {
app_alert("成功导入" + ret.cnt + "条数据!");
}, data, {contentType:"application/json"});
用于定制批量导入行为。
示例,实现接口:
Task.batchAdd(orderId, task1)(city, brand, vendorName, storeName)
其中vendorName和storeName字段需要通过查阅修正为vendorId和storeId字段。
class TaskBatchAddLogic extends BatchAddLogic
{
protected $vendorCache = [];
function __construct () {
// 每个对象添加时都会用的字段,加在$this->params数组中
$this->params["orderId"] = mparam("orderId", "G"); // mparam要求必须指定该字段
$this->params["task1"] = param("task1", null, "G");
}
// $params为待添加数据,可在此修改,如用`$params["k1"]=val1`添加或更新字段,用unset($params["k1"])删除字段。
// $row为原始行数据数组。
function beforeAdd(&$params, $row) {
// vendorName -> vendorId
// 如果会大量重复查询vendorName,可以将结果加入cache来优化性能
if (! $this->vendorCache)
$this->vendorCache = new SimpleCache(); // name=>vendorId
$vendorId = $this->vendorCache->get($params["vendorName"], function () use ($params) {
$id = queryOne("SELECT id FROM Vendor", false, ["name" => $params["vendorName"]] );
if (!$id) {
// throw new MyException(E_PARAM, "请添加供应商", "供应商未注册: " . $params["vendorName"]);
// 自动添加
$id = callSvcInt("Vendor.add", null, [
"name" => $params["vendorName"],
"tel" => $params["vendorPhone"]
]);
}
return $id;
});
$params["vendorId"] = $vendorId;
unset($params["vendorName"]);
unset($params["vendorPhone"]);
// storeName -> storeId 类似处理 ...
}
// 处理原始标题行数据, $row1是通过title参数传入的标题数组,可能为空
function onGetTitleRow($row, $row1) {
}
}
class AC2_Task extends AC0_Task
{
function api_batchAdd() {
$this->batchAddLogic = new TaskBatchAddLogic();
return parent::api_batchAdd();
}
}
@see api_batchAdd