APIJSON(十三:AbstractVerifier源码阅读(4))
2021SC@SDUSC
verifyRepeat
此方法是验证是否重复
public void verifyRepeat(String table, String key, Object value, long exceptId) throws Exception
会发现该方法传入的参数有table、key、value、exceptId。
首先会验证key和value是否同时为空
if (key == null || value == null) {
Log.e(TAG, "verifyRepeat key == null || value == null >> return;");
return;
}
都为空就会报错
之后是查看value值的类型
if (value instanceof JSON) {
throw new UnsupportedDataTypeException(key + ":value 中value的类型不能为JSON!");
}
value的类型不能为JSON
之后会根据传入的key、value新建一个JSONRequest类。
JSONRequest request = new JSONRequest(key, value);
if (exceptId > 0) {//允许修改自己的属性为该属性原来的值
request.put(JSONRequest.KEY_ID + "!", exceptId); // FIXME 这里 id 写死了,不支持自定义
}
同时如果exceptId > 0,就会将JSONRequest.KEY_ID + “!”,exceptId插入request中(如果存在该key就会进行覆盖)
之后会用对应的方法创建JSONRequest类
JSONObject repeat = createParser().setMethod(HEAD).setNeedVerify(true).parseResponse(
new JSONRequest(table, request)
);
如果repeat为空或者repeat已重复,就会报错
repeat = repeat == null ? null : repeat.getJSONObject(table);
if (repeat == null) {
throw new Exception("服务器内部错误 verifyRepeat repeat == null");
}
if (repeat.getIntValue(JSONResponse.KEY_COUNT) > 0) {
throw new ConflictException(key + ": " + value + " 已经存在,不能重复!");
}
verifyRequest
之后是大量的从request提取target指定的内容的verifyRequest方法,及对其的重载。
@Override
public JSONObject verifyRequest(@NotNull final RequestMethod method, final String name
, final JSONObject target, final JSONObject request, final int maxUpdateCount
, final String database, final String schema, final SQLCreator creator) throws Exception {
return verifyRequest(method, name, target, request, maxUpdateCount, database, schema, this, creator);
}
public static JSONObject verifyRequest(@NotNull final RequestMethod method, final String name
, final JSONObject target, final JSONObject request, final SQLCreator creator) throws Exception {
return verifyRequest(method, name, target, request, Parser.MAX_UPDATE_COUNT, creator);
}
public static JSONObject verifyRequest(@NotNull final RequestMethod method, final String name
, final JSONObject target, final JSONObject request, final int maxUpdateCount, final SQLCreator creator) throws Exception {
return verifyRequest(method, name, target, request, maxUpdateCount, null, null, null, creator);
}
public static JSONObject verifyRequest(@NotNull final RequestMethod method, final String name
, final JSONObject target, final JSONObject request, final int maxUpdateCount
, final String database, final String schema, final IdCallback idCallback, final SQLCreator creator) throws Exception {
return verifyRequest(method, name, target, request, maxUpdateCount, database, schema, null, idCallback, creator);
}
public static JSONObject verifyRequest(@NotNull final RequestMethod method, final String name
, final JSONObject target, final JSONObject request, final int maxUpdateCount
, final String database, final String schema, final String datasource, final IdCallback idCallback, final SQLCreator creator) throws Exception {
Log.i(TAG, "verifyRequest method = " + method + "; name = " + name
+ "; target = \n" + JSON.toJSONString(target)
+ "\n request = \n" + JSON.toJSONString(request));
if (target == null || request == null) {// || request.isEmpty()) {
Log.i(TAG, "verifyRequest target == null || request == null >> return null;");
return null;
}
//解析
return parse(method, name, target, request, database, schema, idCallback, creator, new OnParseCallback() {
@Override
public JSONObject onParseJSONObject(String key, JSONObject tobj, JSONObject robj) throws Exception {
// Log.i(TAG, "verifyRequest.parse.onParseJSONObject key = " + key + "; robj = " + robj);
if (robj == null) {
if (tobj != null) {//不允许不传Target中指定的Table
throw new IllegalArgumentException(method + "请求,请在 " + name + " 内传 " + key + ":{} !");
}
} else if (apijson.JSONObject.isTableKey(key)) {
String db = request.getString(apijson.JSONObject.KEY_DATABASE);
String sh = request.getString(apijson.JSONObject.KEY_SCHEMA);
String ds = request.getString(apijson.JSONObject.KEY_DATASOURCE);
if (StringUtil.isEmpty(db, false)) {
db = database;
}
if (StringUtil.isEmpty(sh, false)) {
sh = schema;
}
if (StringUtil.isEmpty(ds, false)) {
ds = datasource;
}
String idKey = idCallback == null ? null : idCallback.getIdKey(db, sh, ds, key);
String finalIdKey = StringUtil.isEmpty(idKey, false) ? apijson.JSONObject.KEY_ID : idKey;
if (method == RequestMethod.POST) {
if (robj.containsKey(finalIdKey)) {
throw new IllegalArgumentException(method + "请求," + name + "/" + key + " 不能传 " + finalIdKey + " !");
}
} else {
if (RequestMethod.isQueryMethod(method) == false) {
verifyId(method.name(), name, key, robj, finalIdKey, maxUpdateCount, true);
String userIdKey = idCallback == null ? null : idCallback.getUserIdKey(db, sh, ds, key);
String finalUserIdKey = StringUtil.isEmpty(userIdKey, false) ? apijson.JSONObject.KEY_USER_ID : userIdKey;
verifyId(method.name(), name, key, robj, finalUserIdKey, maxUpdateCount, false);
}
}
}
return verifyRequest(method, key, tobj, robj, maxUpdateCount, database, schema, idCallback, creator);
}
@Override
protected JSONArray onParseJSONArray(String key, JSONArray tarray, JSONArray rarray) throws Exception {
if ((method == RequestMethod.POST || method == RequestMethod.PUT) && JSONRequest.isArrayKey(key)) {
if (rarray == null || rarray.isEmpty()) {
throw new IllegalArgumentException(method + "请求,请在 " + name + " 内传 " + key + ":[{ ... }] "
+ ",批量新增 Table[]:value 中 value 必须是包含表对象的非空数组!其中每个子项 { ... } 都是"
+ " tag:" + key.substring(0, key.length() - 2) + " 对应单个新增的 structure !");
}
if (rarray.size() > maxUpdateCount) {
throw new IllegalArgumentException(method + "请求," + name + "/" + key
+ " 里面的 " + key + ":[{ ... }] 中 [] 的长度不能超过 " + maxUpdateCount + " !");
}
}
return super.onParseJSONArray(key, tarray, rarray);
}
});
}
verifyId
接下来是对修改或删除操作的id验证
private static void verifyId(@NotNull String method, @NotNull String name, @NotNull String key
, @NotNull JSONObject robj, @NotNull String idKey, final int maxUpdateCount, boolean atLeastOne) {
//单个修改或删除
Object id = robj.get(idKey); //如果必须传 id ,可在Request表中配置NECESSARY
if (id != null && id instanceof Number == false && id instanceof String == false) {
throw new IllegalArgumentException(method + "请求," + name + "/" + key
+ " 里面的 " + idKey + ":value 中value的类型只能是 Long 或 String !");
}
//批量修改或删除
String idInKey = idKey + "{}";
JSONArray idIn = null;
try {
idIn = robj.getJSONArray(idInKey); //如果必须传 id{} ,可在Request表中配置NECESSARY
} catch (Exception e) {
throw new IllegalArgumentException(method + "请求," + name + "/" + key
+ " 里面的 " + idInKey + ":value 中value的类型只能是 [Long] !");
}
if (idIn == null) {
if (atLeastOne && id == null) {
throw new IllegalArgumentException(method + "请求," + name + "/" + key
+ " 里面 " + idKey + " 和 " + idInKey + " 至少传其中一个!");
}
} else {
if (idIn.size() > maxUpdateCount) { //不允许一次操作 maxUpdateCount 条以上记录
throw new IllegalArgumentException(method + "请求," + name + "/" + key
+ " 里面的 " + idInKey + ":[] 中[]的长度不能超过 " + maxUpdateCount + " !");
}
//解决 id{}: ["1' OR 1='1'))--"] 绕过id{}限制
//new ArrayList<Long>(idIn) 不能检查类型,Java泛型擦除问题,居然能把 ["a"] 赋值进去还不报错
for (int i = 0; i < idIn.size(); i++) {
Object o = idIn.get(i);
if (o == null) {
throw new IllegalArgumentException(method + "请求," + name + "/" + key
+ " 里面的 " + idInKey + ":[] 中所有项都不能为 [ null, <= 0 的数字, 空字符串 \"\" ] 中任何一个 !");
}
if (o instanceof Number) {
//解决 Windows mysql-5.6.26-winx64 等低于 5.7 的 MySQL 可能 id{}: [0] 生成 id IN(0) 触发 MySQL bug 导致忽略 IN 条件
//例如 UPDATE `apijson`.`TestRecord` SET `testAccountId` = -1 WHERE ( (`id` IN (0)) AND (`userId`= 82001) )
if (((Number) o).longValue() <= 0) {
throw new IllegalArgumentException(method + "请求," + name + "/" + key
+ " 里面的 " + idInKey + ":[] 中所有项都不能为 [ null, <= 0 的数字, 空字符串 \"\" ] 中任何一个 !");
}
}
else if (o instanceof String) {
if (StringUtil.isEmpty(o, true)) {
throw new IllegalArgumentException(method + "请求," + name + "/" + key
+ " 里面的 " + idInKey + ":[] 中所有项都不能为 [ null, <= 0 的数字, 空字符串 \"\" ] 中任何一个 !");
}
}
else {
throw new IllegalArgumentException(method + "请求," + name + "/" + key
+ " 里面的 " + idInKey + ":[] 中所有项的类型都只能是 Long 或 String !");
}
}
}
}
verifyResponse
verifyResponse方法是校验并将response转换为指定的内容和结构
@Override
public JSONObject verifyResponse(@NotNull final RequestMethod method, final String name
, final JSONObject target, final JSONObject response, final String database, final String schema
, SQLCreator creator, OnParseCallback callback) throws Exception {
return verifyResponse(method, name, target, response, database, schema, this, creator, callback);
}
public static JSONObject verifyResponse(@NotNull final RequestMethod method, final String name
, final JSONObject target, final JSONObject response, SQLCreator creator, OnParseCallback callback) throws Exception {
return verifyResponse(method, name, target, response, null, null, null, creator, callback);
}
public static JSONObject verifyResponse(@NotNull final RequestMethod method, final String name
, final JSONObject target, final JSONObject response, final String database, final String schema
, final IdCallback idKeyCallback, SQLCreator creator, OnParseCallback callback) throws Exception {
Log.i(TAG, "verifyResponse method = " + method + "; name = " + name
+ "; target = \n" + JSON.toJSONString(target)
+ "\n response = \n" + JSON.toJSONString(response));
if (target == null || response == null) {// || target.isEmpty() {
Log.i(TAG, "verifyResponse target == null || response == null >> return response;");
return response;
}
//解析
return parse(method, name, target, response, database, schema, idKeyCallback, creator, callback != null ? callback : new OnParseCallback() {
@Override
protected JSONObject onParseJSONObject(String key, JSONObject tobj, JSONObject robj) throws Exception {
return verifyResponse(method, key, tobj, robj, database, schema, idKeyCallback, creator, callback);
}
});
}
parse
parse方法——对request和response不同的解析用callback返回
public static JSONObject parse(@NotNull final RequestMethod method, String name, JSONObject target, JSONObject real
, SQLCreator creator, @NotNull OnParseCallback callback) throws Exception {
return parse(method, name, target, real, null, null, null, creator, callback);
}
public static JSONObject parse(@NotNull final RequestMethod method, String name, JSONObject target, JSONObject real
, final String database, final String schema, final IdCallback idCallback, SQLCreator creator, @NotNull OnParseCallback callback) throws Exception {
return parse(method, name, target, real, database, schema, null, idCallback, creator, callback);
}
public static JSONObject parse(@NotNull final RequestMethod method, String name, JSONObject target, JSONObject real
, final String database, final String schema, final String datasource, final IdCallback idCallback, SQLCreator creator, @NotNull OnParseCallback callback) throws Exception {
if (target == null) {
return null;
}
// 获取配置<<<<<<<<<<<<<<<<<<<<<<<<<<<<
JSONObject type = target.getJSONObject(TYPE.name());
JSONObject verify = target.getJSONObject(VERIFY.name());
JSONObject insert = target.getJSONObject(INSERT.name());
JSONObject update = target.getJSONObject(UPDATE.name());
JSONObject replace = target.getJSONObject(REPLACE.name());
String exist = StringUtil.getNoBlankString(target.getString(EXIST.name()));
String unique = StringUtil.getNoBlankString(target.getString(UNIQUE.name()));
String remove = StringUtil.getNoBlankString(target.getString(REMOVE.name()));
String must = StringUtil.getNoBlankString(target.getString(MUST.name()));
String refuse = StringUtil.getNoBlankString(target.getString(REFUSE.name()));
// 移除字段<<<<<<<<<<<<<<<<<<<
String[] removes = StringUtil.split(remove);
if (removes != null && removes.length > 0) {
for (String r : removes) {
real.remove(r);
}
}
// 移除字段>>>>>>>>>>>>>>>>>>>
// 判断必要字段是否都有<<<<<<<<<<<<<<<<<<<
String[] musts = StringUtil.split(must);
List<String> mustList = musts == null ? new ArrayList<String>() : Arrays.asList(musts);
for (String s : mustList) {
if (real.get(s) == null) { // 可能传null进来,这里还会通过 real.containsKey(s) == false) {
throw new IllegalArgumentException(method + "请求," + name
+ " 里面不能缺少 " + s + " 等[" + must + "]内的任何字段!");
}
}
//判断必要字段是否都有>>>>>>>>>>>>>>>>>>>
Set<String> objKeySet = new HashSet<String>(); //不能用tableKeySet,仅判断 Table:{} 会导致 key:{ Table:{} } 绕过判断
//解析内容<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
Set<Map.Entry<String, Object>> set = new LinkedHashSet<>(target.entrySet());
if (set.isEmpty() == false) {
String key;
Object tvalue;
Object rvalue;
for (Map.Entry<String, Object> entry : set) {
key = entry == null ? null : entry.getKey();
if (key == null || OPERATION_KEY_LIST.contains(key)) {
continue;
}
tvalue = entry.getValue();
rvalue = real.get(key);
if (callback.onParse(key, tvalue, rvalue) == false) {
continue;
}
if (tvalue instanceof JSONObject) { //JSONObject,往下一级提取
if (rvalue != null && rvalue instanceof JSONObject == false) {
throw new UnsupportedDataTypeException(key + ":value 的value不合法!类型必须是 OBJECT ,结构为 {} !");
}
tvalue = callback.onParseJSONObject(key, (JSONObject) tvalue, (JSONObject) rvalue);
objKeySet.add(key);
} else if (tvalue instanceof JSONArray) { //JSONArray
if (rvalue != null && rvalue instanceof JSONArray == false) {
throw new UnsupportedDataTypeException(key + ":value 的value不合法!类型必须是 ARRAY ,结构为 [] !");
}
tvalue = callback.onParseJSONArray(key, (JSONArray) tvalue, (JSONArray) rvalue);
if ((method == RequestMethod.POST || method == RequestMethod.PUT) && JSONRequest.isArrayKey(key)) {
objKeySet.add(key);
}
} else {//其它Object
tvalue = callback.onParseObject(key, tvalue, rvalue);
}
if (tvalue != null) {//可以在target中加上一些不需要客户端传的键值对
real.put(key, tvalue);
}
}
}
//解析内容>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
Set<String> rkset = real.keySet(); //解析内容并没有改变rkset
//解析不允许的字段<<<<<<<<<<<<<<<<<<<
List<String> refuseList = new ArrayList<String>();
if ("!".equals(refuse)) {//所有非 must,改成 !must 更好
for (String key : rkset) {//对@key放行,@role,@column,自定义@position等
if (key != null && key.startsWith("@") == false
&& mustList.contains(key) == false && objKeySet.contains(key) == false) {
refuseList.add(key);
}
}
} else {
String[] refuses = StringUtil.split(refuse);
if (refuses != null && refuses.length > 0) {
refuseList.addAll(Arrays.asList(refuses));
}
}
//解析不允许的字段>>>>>>>>>>>>>>>>>>>
//判断不允许传的key<<<<<<<<<<<<<<<<<<<<<<<<<
for (String rk : rkset) {
if (refuseList.contains(rk)) { //不允许的字段
throw new IllegalArgumentException(method + "请求," + name
+ " 里面不允许传 " + rk + " 等" + StringUtil.getString(refuseList) + "内的任何字段!");
}
if (rk == null) { //无效的key
real.remove(rk);
continue;
}
Object rv = real.get(rk);
//不允许传远程函数,只能后端配置
if (rk.endsWith("()") && rv instanceof String) {
throw new UnsupportedOperationException(method + " 请求," + rk + " 不合法!非开放请求不允许传远程函数 key():\"fun()\" !");
}
//不在target内的 key:{}
if (rk.startsWith("@") == false && objKeySet.contains(rk) == false) {
if (rv instanceof JSONObject) {
throw new UnsupportedOperationException(method + " 请求," +name + " 里面不允许传 " + rk + ":{} !");
}
if ((method == RequestMethod.POST || method == RequestMethod.PUT) && rv instanceof JSONArray && JSONRequest.isArrayKey(rk)) {
throw new UnsupportedOperationException(method + " 请求," + name + " 里面不允许 " + rk + ":[] 等未定义的 Table[]:[{}] 批量操作键值对!");
}
}
}
//判断不允许传的key>>>>>>>>>>>>>>>>>>>>>>>>>
//校验与修改Request<<<<<<<<<<<<<<<<<
//在tableKeySet校验后操作,避免 导致put/add进去的Table 被当成原Request的内容
real = operate(TYPE, type, real, creator);
real = operate(VERIFY, verify, real, creator);
real = operate(INSERT, insert, real, creator);
real = operate(UPDATE, update, real, creator);
real = operate(REPLACE, replace, real, creator);
//校验与修改Request>>>>>>>>>>>>>>>>>
String db = real.getString(apijson.JSONObject.KEY_DATABASE);
String sh = real.getString(apijson.JSONObject.KEY_SCHEMA);
String ds = real.getString(apijson.JSONObject.KEY_DATASOURCE);
if (StringUtil.isEmpty(db, false)) {
db = database;
}
if (StringUtil.isEmpty(sh, false)) {
sh = schema;
}
if (StringUtil.isEmpty(ds, false)) {
ds = datasource;
}
String idKey = idCallback == null ? null : idCallback.getIdKey(db, sh, ds, name);
String finalIdKey = StringUtil.isEmpty(idKey, false) ? apijson.JSONObject.KEY_ID : idKey;
//TODO放在operate前?考虑性能、operate修改后再验证的值是否和原来一样
//校验存在<<<<<<<<<<<<<<<<<<< TODO 格式改为 id;version,tag 兼容多个字段联合主键
String[] exists = StringUtil.split(exist);
if (exists != null && exists.length > 0) {
long exceptId = real.getLongValue(finalIdKey);
for (String e : exists) {
verifyExist(name, e, real.get(e), exceptId, creator);
}
}
//校验存在>>>>>>>>>>>>>>>>>>>
//TODO放在operate前?考虑性能、operate修改后再验证的值是否和原来一样
//校验重复<<<<<<<<<<<<<<<<<<< TODO 格式改为 id;version,tag 兼容多个字段联合主键
String[] uniques = StringUtil.split(unique);
if (uniques != null && uniques.length > 0) {
long exceptId = real.getLongValue(finalIdKey);
for (String u : uniques) {
verifyRepeat(name, u, real.get(u), exceptId, finalIdKey, creator);
}
}
//校验重复>>>>>>>>>>>>>>>>>>>
Log.i(TAG, "parse return real = " + JSON.toJSONString(real));
return real;
}
虑性能、operate修改后再验证的值是否和原来一样
//校验重复<<<<<<<<<<<<<<<<<<< TODO 格式改为 id;version,tag 兼容多个字段联合主键
String[] uniques = StringUtil.split(unique);
if (uniques != null && uniques.length > 0) {
long exceptId = real.getLongValue(finalIdKey);
for (String u : uniques) {
verifyRepeat(name, u, real.get(u), exceptId, finalIdKey, creator);
}
}
//校验重复>>>>>>>>>>>>>>>>>>>
Log.i(TAG, "parse return real = " + JSON.toJSONString(real));
return real;
}