API参考 - 筋斗云服务端JAVA版

最后更新:2018-05-09

%var env.isTestMode

是否为测试模式。

%var env.debugLevel

0-9间的调试等级。

%var env.appName

@var env.appType

@key getAppType ()

获取appName, appType

%var env.baseDir

应用程序的主目录,用于写文件。默认为 {user.home}/jd-data/{project}.

%var env.props

1 应用入口 JDEnv

2 数据库连接

3 应用程序选项

@key web.properties

@key jdcloud-config

配置选项。从WEB-INF/web.properties中加载,也可以在env.onApiInit回调函数中修改。

1 应用入口 JDEnv

作为WebApi应用程序入口。
所有的接口实现都应与该类在同一个包中。

2 数据库连接

示例:连接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"/> 

3 应用程序选项

%fn callSvc(ac)

@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。

%fn env.onCreateApi() -> String[]

对于函数型调用,返回接口实现类(应继承JDApiBase)列表。默认为"Global":

return new String[] { "Global" };

也可以从多个类加载,常用于添加插件,如:

return new String[] { "Global", "JDLogin", "JDUpload" };

%fn env.onCreateAC(table) -> String[]

对于对象型调用,根据对象名(table)返回一个类名数组,用于绑定权限与AC类。注意类名不带包名。
默认逻辑作为示例:

    if (api.hasPerm(JDApiBase.AUTH_USER)) {
        return new String[] { "AC1_" + table, "AC_" + table };
    }
    else if (api.hasPerm(JDApiBase.AUTH_EMP)) {
        return new String[] { "AC2_" + table };
    }
    else if (api.hasPerm(JDApiBase.AUTH_ADMIN)) {
        return new String[] { "AC0_" + table, "AccessControl" };
    }
    return new String[] {"AC_" + table};

它表示:

%fn env.onGetPerms() -> perms/i

返回权限集合。一般根据session来设置。默认检查uid, empId, adminId三个session变量,如果存在则认为具有用户、员工、超级管理员登录权限。

    int perms = 0;
    if (api.getSession("uid") != null) {
        perms |= JDApiBase.AUTH_USER;
    }
    else if (api.getSession("empId") != null) {
        perms |= JDApiBase.AUTH_EMP;
    }
    else if (api.getSession("adminId") != null) {
        perms |= JDApiBase.AUTH_ADMIN;
    }
    return perms;

%fn env.onApiInit()

API调用前的回调函数。例如设置选项、检查客户版本等。
示例:关闭ApiLog

this.props.setProperty("enableApiLog", "0");

%fn queryOne(sql, assoc?=false)

执行查询语句,只返回一行数据,如果行中只有一列,则直接返回该列数值。
如果查询不到,返回false.

示例:查询用户姓名与电话,默认返回值数组(JsArray):

Object rv = queryOne("SELECT name,phone FROM User WHERE id=" + id);
if (rv.equals(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 (rv.equals(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 (phone.equals(false))
    throw new MyException(E_PARAM, "bad user id");
// phone = "13712345678"

@see queryAll

%fn execOne(sql, getInsertId?=false)

@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);

@fn dbInsert(table, kv) -> newId

字段值可以是数值、日期、字符串等类型的变量。
如果值为null或空串会被忽略。
e.g.

int orderId = dbInsert("Ordr", new JsObject(
    "tm", new Date(), // 支持Date类型
    "tm1", "=now()", // "="开头,表示是SQL表达式
    "amount", 100,
    "dscr", null // null字段会被忽略
));

@fn dbUpdate(table, kv, id_or_cond?) -> cnt

@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", "=now()"  // "="开头,表示是SQL表达式
), "tm IS NULL);

%fn jsonEncode(o, doFormat=false)

@param doFormat 设置为true则会对JSON输出进行格式化便于调试。

@see jsonDecode

%fn jsonDecode(json, type) -> type

Map m = jsonDecode(json, Map.class);
List m = jsonDecode(json, List.class);
User u = jsonDecode(json, User.class);

@see jsonEncode

%fn logit(str, addHeader?=true, type?="trace")

记录日志到文件。type参数指定日志文件名,例如默认为trace,则日志写到trace.log。

%fn param(name, defVal?, col?, doHtmlEscape=true)

@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=>"打蜡"]
]

%fn mparam(name, coll?=null, htmlEscape?=true)

必填参数(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);

%fn mparam(names, coll?=null)

几个参数,必填其一(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) {
}

%fn getSession(name) -> Object

@see setSession

@see unsetSession

@see destroySession

%fn setSession(name, value)

@see getSession

@see unsetSession

@see destroySession

%fn unsetSession(name)

@see setSession

@see getSession

@see destroySession

%fn destroySession()

@see getSession

@see setSession

@see unsetSession

%fn regexMatch(str, pat) -> Matcher

正则表达式匹配

String phone = "13712345678";
Matcher m = regexMatch(phone, "...(\\d{4})";
if (m.find()) { // 如果要连续匹配可用 while (m.find()) 
    // m.group(1) 为中间4位数
}

%fn regexReplace(str, pat, str1) -> String

@alias regexReplace (str, pat, fn) -> String

用正则表达式替换字符串

@param fn (Matcher m) -> String

String phone = "13712345678"; // 变成 "137****5678"
String phone1 = regexReplace(phone, "(?<=^\\d{3})\\d{4}", "****");
或者
String phone1 = regexReplace(phone, "^(\\d{3})\\d{4}", m -> { return m.group(1) + "****"; } );

%var T_SEC,T_MIN,T_HOUR,T_DAY

Date dt = new Date();
Date dt1 = parseDate(dtStr1);
long hours = (dt1.getTime() - dt.getTime()) / T_HOUR;
Date dt2 = new Date(dt1.getTime() + 4 * T_DAY);

@see time

%fn date(fmt?="yyyy-MM-dd HH:mm:ss", dt?)

生成日期字符串。

String dtStr1 = date(null, null);
Date dt1 = parseDate(dtStr1);
String dtStr2 = date("yyyy-MM-dd", dt1);

@see parseDate

%fn time()

系统当前时间(毫秒)。

long t = time();
long unixTimestamp = t / T_SEC
Date dt1 = new Date();
long diff_ms = dt1.getTime() - t;

%fn getenv(name, defVal?)

取全局设置。

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

%fn md5(s) -> String

返回md5字符串(32字符)

%fn md5Bytes(s) -> byte[]

返回md5结果(16字节)

%fn rand(from, to) -> int

生成[from, to]范围内随机整数

%fn base64Encode(s) -> String

@param s String/byte[]

%fn base64Decode(s) -> String

@fn base64DecodeBytes (s) -> byte[]

String text = base64Decode(enc);

%fn myEncrypt(data, op, key?="jdcloud") -> String

对字符串data加密,生成base64编码的字符串。
或者反过来,对base64编辑的data解密返回原文。
出错返回null。

算法:DES加密。

@param op "E"-加密(encrypt); "D"-解密(decrypt)

%fn getCred(cred) -> [user, pwd]

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];

%fn hashPwd(pwd) -> pwd1

对密码进行加密处理。如果已加密过,则直接返回。

%fn indexOf(map, fn) -> key

@param fn (key, value) -> boolean

找符合fn条件的第一个key。找不到返回null。

JsObject map = new JsObject("aa", 100, "bb", 300);
String key = indexOf(map, (k,v)->{v>200}); // key="bb"

%fn indexOf(arr, e) -> index

数组查找。返回找到的索引,找不到返回-1。

JsArray arr = new JsArray("aa", "bbb");
int idx = indexOf(arr, "bbb"); // idx =1

%fn writeFile(in, out, bufSize?)

复制输入到输出。输入、输出可以是文件或流。

@param in String/File/InputStream

@param out String/File/OutputStream

@param bufSize 指定buffer大小,设置0使用默认值(10K)

%fn getPath(path, withSep) -> path

用于获得以"/"结尾或不以"/"结尾的路径。

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"

%fn getBaseUrl(wantHost) -> String

返回项目的URL,以"/"结尾。

String url = getBaseUrl(true); // "http://myserver/myapp/"
String url1 = getBaseUrl(false); // "/myapp/"

%fn exit()

立即返回,不再处理。等价于throw new DirectReturn();
如果想退出返回数据,请直接用DirectReturn.

%fn safeClose(o)

Close without exception.

%fn tmCols(fieldName = "t0.tm")

为查询添加时间维度单位: y,m,w,d,wd,h (年,月,周,日,周几,时)。

示例:

this.vcolDefs = asList(
    new VcolDef().res(tmCols())
);

this.vcolDefs = asList(
    new VcolDef().res(tmCols("log_cr.tm")).require("createTm")
);

%fn issetval(fieldName)

判断POST内容中是否对该字段设置值,示例:
(在onValidate回调中)

if (issetval("pwd")) {
    String pwd = (String)env._POST.get("pwd");
    env._POST.put("pwd", hashPwd(pwd));
}

@fn parseKvList(kvListStr, sep, sep2)

解析key-value列表字符串。如果出错抛出异常。
注意:sep, sep2为行、列分隔符的正则式。

示例:

Map<String, Object> map = parseKvList("CR:新创建;PA:已付款", ";", ":");
// map: {"CR": "新创建", "PA":"已付款"}

@var AccessControl.enumFields 枚举支持及自定义字段处理

(版本v1.1)

格式为{field => map/fn(val) }

作为比onHandleRow/onAfterActions等更易用的工具,enumFields可对返回字段做修正。例如,想要对返回的status字段做修正,如"CR"显示为"Created",可设置:

Map<String, Object> map = asMap("CR","Created", "CA","Cancelled");
this.enumFields = asMap(
    "status", map
);

(TODO:暂不支持)也可以设置为自定义函数,如:

map = asMap(...);
this.enumFields.asMap("status", v -> {
    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"。

@fn AccessControl::addCond(cond, prepend=false)

@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'");
    }
}

@see AccessControl::addRes

@see AccessControl::addJoin

@fn AccessControl::addJoin(joinCond)

添加Join条件.

@see AccessControl::addCond 其中有示例

@fn AccessControl::addVCol(col, ignoreError=false, alias=null)

根据列名找到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)函数.

@see AccessControl::addRes

Generated by jdcloud-gendoc