2022-09-26

Java

APIJSON

APIJSON

APIJSON 是一种专为 API 而生的 JSON 网络传输协议 以及 基于这套协议实现的 ORM 库。 为各种增删改查提供了完全自动化的万能通用接口,零代码实时满足千变万化的各种新增和变更需求。 能大幅降低开发和沟通成本,简化开发流程,缩短开发周期。

in short 封装对数据库 增 删 改 查 的ORM库

文档 Q&A 终于算是有文档了

关键对象

apijson.orm.Parser

apijson.orm.AbstractParser (核心中的核心类) 负责解析’请求’, 执行SQL, 返回结果集

在这里需要注意的是这个角色判断必须在 parseCorrectRequest 后面,因为 parseCorrectRequest 可能会添加 @role)

关键代码apijson.orm.AbstractParser#parseResponse(com.alibaba.fastjson.JSONObject)

@NotNull
@Override
public JSONObject parseResponse(JSONObject request) {
    long startTime = System.currentTimeMillis();
    Log.d(TAG, "parseResponse  startTime = " + startTime
            + "<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n\n\n ");
    requestObject = request;
    //回调创建验证器 (访问者模式)
    verifier = createVerifier().setVisitor(getVisitor());
 
    //是否为开放(不限制请求的结构或内容;明文,浏览器能直接访问及查看)的请求方法;
    // 即: GET | HEAD 方法会进入该分支
    if (RequestMethod.isPublicMethod(requestMethod) == false) {
        try {
            if (isNeedVerifyLogin()) {
                onVerifyLogin();
            }
            if (isNeedVerifyContent()) {
                onVerifyContent();//调了 parseCorrectRequest
            }
        } catch (Exception e) {
            return extendErrorResult(requestObject, e, requestMethod, getRequestURL(), isRoot);
        }
    }
    
    //从请求对象里面拿, 验证, 配置元信息等
    if (isNeedVerifyRole() && globalRole == null) {
        try {
            //<!> 从请求对象里面拿的角色
            setGlobalRole(requestObject.getString(JSONRequest.KEY_ROLE));
            requestObject.remove(JSONRequest.KEY_ROLE);
        } catch (Exception e) {
            return extendErrorResult(requestObject, e, requestMethod, getRequestURL(), isRoot);
        }
    }
    
    try {
        setGlobalFormat(requestObject.getBoolean(JSONRequest.KEY_FORMAT));
        setGlobalDatabase(requestObject.getString(JSONRequest.KEY_DATABASE));
        setGlobalSchema(requestObject.getString(JSONRequest.KEY_SCHEMA));
        setGlobalDatasource(requestObject.getString(JSONRequest.KEY_DATASOURCE));
        setGlobalExplain(requestObject.getBoolean(JSONRequest.KEY_EXPLAIN));
        setGlobalCache(requestObject.getString(JSONRequest.KEY_CACHE));
 
        requestObject.remove(JSONRequest.KEY_FORMAT);
        requestObject.remove(JSONRequest.KEY_DATABASE);
        requestObject.remove(JSONRequest.KEY_SCHEMA);
        requestObject.remove(JSONRequest.KEY_DATASOURCE);
        requestObject.remove(JSONRequest.KEY_EXPLAIN);
        requestObject.remove(JSONRequest.KEY_CACHE);
    } catch (Exception e) {
        return extendErrorResult(requestObject, e, requestMethod, getRequestURL(), isRoot);
    }
    ...
    
    ObjectParser op = null;
    if (isReuse) {  // 数组主表使用专门的缓存数据
        op = arrayObjectParserCacheMap.get(parentPath.substring(0, parentPath.lastIndexOf("[]") + 2));
        op.setParentPath(parentPath);
    }
 
    if (op == null) {
        // 回调创建了 ObjectParser 对象
        op = createObjectParser(request, parentPath, arrayConfig, isSubquery, isTable, isArrayMainTable);
    }
    op = op.parse(name, isReuse);
    
}

去先进行角色的核验,然后核验该角色的对应权限,然后根据 request 与response的不同情况,来解析得到不同的返回结果。

apijson.orm.Verifier

apijson.orm.AbstractVerifier 校验器(权限、请求参数、返回结果等)

public class MyVerifier extends AbstractVerifier {
    private static final Logger log = LoggerFactory.getLogger(MyVerifier.class);
 
    @Override
    public Parser createParser() {
        //验证器使用的 Parser
        MyParser parser = new MyParser();
        return parser;
    }
}
 
 
 

apijson.orm.SQLConfig

apijson.orm.AbstractSQLConfig 对表一个SQL语句的配置, 包括数据库配置, 角色配置;

调用 Verifier#verifyAccess(SQLConfig config)验证权限时会传;

apijson.orm.AbstractObjectParser

每次 Parser 解析, 都会调用其 newSQLConfig 创建 SQLConfig 配置

  @Override
    public SQLConfig newSQLConfig(RequestMethod method, String table, String alias, JSONObject request,
        List<Join> joinList, boolean isProcedure) throws Exception {
    }

操作方法

查询功能符号 APIJSON -操作方法

GET

普通获取数据,

分页查询

{
    "TestBook[]":{// "key[]":{},key可省略. 当key和里面的Table名相同时,Table会被提取出来
        "TestBook": {"name$":"%java%"}, //模糊搜索 "key$":"SQL搜索表达式"
        //数组关键词 (分页查询)
        "count":2,//Integer,查询数量,0 表示最大值,默认最大值为100(分页查询: size)
        "page":0,//Integer,查询页码,从0开始,默认为100,一般和count一起用 (分页查询: page)
        //查询内容
        // 0-对象,1-总数和分页详情,2-数据、总数和分页详情
        // 总数关键词为 total,分页详情关键词为 info,它们都和 query 同级
        // 通过引用赋值得到,例如 "total@":"/[]/total", "info@":"/[]/info"
        "query":2
    },
    "total@":"/TestBook[]/total",//引用赋值,"key@":"key0/key1/.../refKey",引用路径为用/分隔的字符串。
    "info@":"/TestBook[]/info"//这两个跟上 query 的值类型有关
}

响应数据

"TestBook[]": [
    {
        "id": "40286981706ad91001706adaba430006",
        "name": "java IT运维之道 第2版 无部门2",
        "creater": "7",
    },
    ....
],
"total":139, //总数
"info":{ //分页详情
   "total":139, //总数
   "count":5, //每页数量
   "page":0, //当前页码
   "max":27, //最大页码
   "more":true, //是否还有更多
   "first":true, //是否为首页
   "last":false //是否为尾页
}
 

join 查询

"join":"&/Table0,</Table1/key1@" 
 

"join":{
   "&/Table0":{},     // 支持 ON 多个字段关联,
   "</Table1/key1@":{ // ON 只允许指定的 key1 关联
     "key0":value0,   // 其它ON条件
     "key2":value2,
     ...
     "@combine":"...",//其它ON条件的组合方式
     "@column":"...",// 外层 SELECT
     "@group":"...", // 外层 GROUP BY
     "@having":"..." // 外层 HAVING
   }
}
 
 
多表连接方式:
"@" - APP JOIN 
"<" - LEFT JOIN
">" - RIGHT JOIN
"&" - INNER JOIN
"|" - FULL JOIN
"!" - OUTER JOIN
"*" - CROSS JOIN
"^" - SIDE JOIN
"(" - ANTI JOIN
")" - FOREIGN JOIN

其中 @ APP JOIN 应用层连表,会从已查出的主表里取得所有副表 key@ 关联的主表内的 refKey 作为一个数组 refKeys: [value0, value1…],然后把原来副表 count 次查询 其它 JOIN 都是 SQL JOIN

例如

{
    "[]": {
        "TestBook": {
            "@column": "id,name,creater",//creater 必须选择, 否则会忽略掉 User 的join查询
            "name$": "%java%",
            "id":"40286981706ad91001706adaba430006"
        },
        "User": {
            "id@": "/TestBook/creater",
            "@column": "id,name"
        }
    }
}

子查询

{
    "[]": {//另: 列表若要TestBookType数据,则必须是 [] , 不能是TestBook[]
        "join": {//或简写: "</TestBookType/id@"
            "</TestBookType/id@":{
               "@column":"test_name", //出现在 SELECT 的列 
            }
        },
        "TestBook": {
            "name$": "%java%",
            "@column":"id,name,test_book_type_id",//出现在 SELECT 的列; 它是主表; 另: 列表若要TestBookType数据,则必须出现 test_book_type_id ref字段
        },
         "TestBookType":{
            "@column":"id,test_name",//出现在 子查询的列; 上面引用 </TestBookType/id, 所以这里必须要有id
            "id@": "/TestBook/test_book_type_id"
        },
        "count": 2,
        "page": 0,
        "query": 2
    },
    "total@": "/TestBook[]/total",
    "info@": "/TestBook[]/info"
}
 

此配置会被翻译为:

SELECT `TestBook`.`id`,`TestBook`.`name`, `TestBookType`.* FROM `sny-cloud-demo`.`test_book` AS `TestBook`  
   LEFT JOIN ( SELECT `id`,`test_name` FROM `sny-cloud-demo`.`test_book_type` ) AS `TestBookType` ON `TestBookType`.`id` = `TestBook`.`test_book_type_id`  
WHERE  (  (`TestBook`.`name` LIKE '%java%')  )  LIMIT 2

在线测试: http://apijson.cn/api/ 唉, 文档稀烂

GETS

安全/私密获取数据, 用于获取钱包等 对安全性要求高的数据

普通获取数量,

HEADS

安全/私密获取数量, 用于获取银行卡数量等 对安全性要求高的数据总数

POST

新增数据

PUT

修改数据, 只修改所传的字段

DELETE

删除数据

源码流程

自定义验证权限

关闭验证 的话可以重写 Parser 中的 isNeedVerifyLogin, isNeedVerifyRole, isNeedVerifyContent 来指定是否开关某个方面的校验;

若想自定义验证的话 重写 apijson.orm.AbstractVerifierverifyAccess(SQLConfig config),SQLConfig 是sql参数(id/where/分页),理论是可以实现手动实现行级权限

验证登陆 流程

关系对象 apijson.orm.Verifier

  1. 在 Parser apijson.orm.VerifierCreator#createVerifier 创建, 使用观察者模式, 设置 Visitor;

apijson.orm.AbstractParser#getVerifier

@Override
public Verifier<T> getVerifier() {
    if (verifier == null) {
        verifier = createVerifier().setVisitor(getVisitor());
    }
    return verifier;
}
 
public AbstractVerifier<T> setVisitor(Visitor<T> visitor) {
    this.visitor = visitor;
    this.visitorId = visitor == null ? null : visitor.getId();
    return this;
}
public JSONObject parseResponse(JSONObject request) {
    long startTime = System.currentTimeMillis();
    Log.d(TAG, "parseResponse  startTime = " + startTime
            + "<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n\n\n ");
 
    requestObject = request;
 
    verifier = createVerifier().setVisitor(getVisitor());
 
    if (RequestMethod.isPublicMethod(requestMethod) == false) {
        try {
            if (isNeedVerifyLogin()) {
                onVerifyLogin();
            }
            if (isNeedVerifyContent()) {
                onVerifyContent();
            }
        } catch (Exception e) {
            return extendErrorResult(requestObject, e, requestMethod, getRequestURL(), isRoot);
        }
    }
 
  1. 在AbstractVerifier 验证登录时, 依据 Visitor 对象属性id, 判断是否登陆; apijson.orm.AbstractVerifier#verifyLogin
@Override
	public void verifyLogin() throws Exception {
		//未登录没有权限操作
		if (visitorId == null) {
			throw new NotLoggedInException("未登录或登录过期,请登录后再操作!");
		}
        .....
	}

so Visitor 即是登陆者信息 apijson.orm.AbstractParser#getVisitor, 可以覆盖这个方法返回登陆者信息.

验证角色 流程

关键对象 apijson.orm.AbstractParserapijson.orm.AbstractObjectParser

1. 入口

apijson.orm.AbstractParser.parseResponse(JSONObject)

@NotNull
@Override
public JSONObject parseResponse(JSONObject request) {
.............
final String requestString = JSON.toJSONString(request);//request传进去解析后已经变了
queryResultMap = new HashMap<String, Object>();
Exception error = null;
sqlExecutor = createSQLExecutor();
onBegin();
try {
	queryDepth = 0;
	executedSQLDuration = 0;
	//<!> 见下
	requestObject = onObjectParse(request, null, null, null, false);
 
	onCommit();
}

注意这个 onObjectParse, ObjectParser 应该是跟业务相关(每次应该new 实例?), 每次解析都会调用, 且角色时, 处理假删除时, 就是使用 apijson.orm.ObjectParser.newSQLConfig(RequestMethod, String, String, JSONObject, List<Join>, boolean) 创建的 SQLConfig apijson.orm.AbstractParser#onObjectParse

//除了 request 其他都是否 
@Override
public JSONObject onObjectParse(final JSONObject request
        , String parentPath, String name, final SQLConfig arrayConfig, boolean isSubquery) throws Exception {
    ......
    if (arrayConfig == null) { //Common
        response = op.setSQLConfig().executeSQL().response();//见下
    }
    ....
    // 调用createObjectParser得到 AbstractObjectParser;
	if (op == null) {
        op = createObjectParser(request, parentPath, arrayConfig, isSubquery, isTable, isArrayMainTable);
    }
    // 对象 - 设置 method
    setOpMethod(request, op, name);
    // <!> ObjectParser 解析 见下[3. 设置 SQLConfig 的分页查询]
    op = op.parse(name, isReuse);
    ......
        

2. ObjectParser#newSQLConfig 创建 SQLConfig

apijson.orm.AbstractObjectParser.setSQLConfig() apijson.orm.AbstractObjectParser.setSQLConfig(int, int, int)

@Override
public AbstractObjectParser setSQLConfig(int count, int page, int position) throws Exception {
    if (isTable == false || isReuse) {
        return setPosition(position);
    }
 
    if (sqlConfig == null) {
        try {
            sqlConfig = newSQLConfig(false);//见下
        }
        catch (Exception e) {
    if (e instanceof NotExistException || (e instanceof CommonException && e.getCause() instanceof NotExistException)) {
        return this;
    }
    throw e;
        }
    }
    sqlConfig.setCount(sqlConfig.getCount() <= 0 ? count : sqlConfig.getCount()).setPage(page).setPosition(position);
 
    parser.onVerifyRole(sqlConfig);
 
    return this;
}

apijson.orm.AbstractObjectParser.newSQLConfig(boolean) apijson.framework.APIJSONObjectParser.newSQLConfig(RequestMethod, String, String, JSONObject, List<Join>, boolean)

@Override
public SQLConfig newSQLConfig(RequestMethod method, String table, String alias, JSONObject request, List<Join> joinList, boolean isProcedure) throws Exception {
    return APIJSONSQLConfig.newSQLConfig(method, table, alias, request, joinList, isProcedure);
}

APIJSONSQLConfig 的实现 apijson.orm.AbstractSQLConfig.newSQLConfig(RequestMethod, String, String, JSONObject, List<Join>, boolean, Callback<T>)

public static <T extends Object> SQLConfig newSQLConfig(RequestMethod method, String table, String alias, JSONObject request, List<Join> joinList, boolean isProcedure, Callback<T> callback) throws Exception {
if (request == null) { // User:{} 这种空内容在查询时也有效
    throw new NullPointerException(TAG + ": newSQLConfig  request == null!");
}
.......
String role = request.getString(KEY_ROLE);
String cache = request.getString(KEY_CACHE);
Subquery from = (Subquery) request.get(KEY_FROM);
String column = request.getString(KEY_COLUMN);
String nulls = request.getString(KEY_NULL);
String cast = request.getString(KEY_CAST);
String combine = request.getString(KEY_COMBINE);
String group = request.getString(KEY_GROUP);
Object having = request.get(KEY_HAVING);
String havingAnd = request.getString(KEY_HAVING_AND);
String order = request.getString(KEY_ORDER);
String raw = request.getString(KEY_RAW);
String json = request.getString(KEY_JSON);
......
 
SQLConfig config = callback.getSQLConfig(method, database, schema, datasource, table);
config.setAlias(alias);
 
........
config.setRole(role);
config.setId(id);
config.setIdIn(idIn);
config.setUserId(userId);
config.setUserIdIn(userIdIn);
 

默认实现是 开放请求由前端传 @role; 非开放请求一般是后端 Request 表里 UPDATE 或 INSERT 进去一个 @role。 不传的话根据 是否登录 默认分配 LOGIN/UNKNOWN 角色

in short 就是从 request 里面拿到的角色, 前端传啥就是啥了; 不可信, 见下[6. 各种角色的验证]

3. 设置 SQLConfig 的分页查询

apijson.orm.AbstractObjectParser.setSQLConfig(int, int, int)

@Override
public AbstractObjectParser setSQLConfig(int count, int page, int position) throws Exception {
    if (isTable == false || isReuse) {
        return setPosition(position);
    }
 
    if (sqlConfig == null) {
        try {
            sqlConfig = newSQLConfig(false);
        }
        catch (Exception e) {
    if (e instanceof NotExistException || (e instanceof CommonException && e.getCause() instanceof NotExistException)) {
        return this;
    }
    throw e;
        }
    }
    sqlConfig.setCount(sqlConfig.getCount() <= 0 ? count : sqlConfig.getCount()).setPage(page).setPosition(position);
    //<!> 验证角色
    parser.onVerifyRole(sqlConfig);
 
    return this;
}

所以这个 SQLConfig, 不是Parser#createSQLConfig 创建的, 而是 ObjectParser#newSQLConfig 创建的

4. 自动处理登陆/未登陆角色

读取设置在 SQLConfig 的角色, 验证访问, apijson.orm.AbstractParser#onVerifyRole

 
@Override
public void onVerifyRole(@NotNull SQLConfig config) throws Exception {
    if (Log.DEBUG) {
        Log.i(TAG, "onVerifyRole  config = " + JSON.toJSONString(config));
    }
 
    if (isNeedVerifyRole()) {
        if (config.getRole() == null) {//SQLConfig 无角色信息
            if (globalRole != null) {
                config.setRole(globalRole);//读取全局的角色
            } else {
                config.setRole(getVisitor().getId() == null ? AbstractVerifier.UNKNOWN : AbstractVerifier.LOGIN);
            }
        }
        getVerifier().verifyAccess(config);
    }
 
}

5. 验证

apijson.orm.AbstractVerifier.verifyAccess(SQLConfig) 主入口

@Override
public boolean verifyAccess(SQLConfig config) throws Exception {
    if (ENABLE_VERIFY_ROLE == false) {
        throw new UnsupportedOperationException("AbstractVerifier.ENABLE_VERIFY_ROLE == false " +
                "时不支持校验角色权限!如需支持则设置 AbstractVerifier.ENABLE_VERIFY_ROLE = true !");
    }
    String table = config == null ? null : config.getTable();
    if (table == null) {
        return true;
    }
 
    String role = config.getRole();
    if (role == null) {
        role = UNKNOWN;
    }
    else { 
        if (ROLE_MAP.containsKey(role) == false) {
            Set<String> NAMES = ROLE_MAP.keySet();
            throw new IllegalArgumentException("角色 " + role + " 不存在!" +
                    "只能是[" + StringUtil.getString(NAMES.toArray()) + "]中的一种!");
        }
 
        if (role.equals(UNKNOWN) == false) { //未登录的角色
            verifyLogin();
        }
    }
 
    RequestMethod method = config.getMethod();
    verifyRole(config, table, method, role);//验证角色 见下
 
    return true;
}
 
@Override
public void verifyRole(SQLConfig config, String table, RequestMethod method, String role) throws Exception {
    verifyAllowRole(config, table, method, role); //验证允许的角色; 缓存的ACCESS_MAP 里面, 方法允不允该角色访问
    verifyUseRole(config, table, method, role); //验证使用的角色 <!> 真正验证角色核心 见下
}

6. 各种角色的验证

UNKNOWN:用户未登录时的默认角色(用户登录用的是服务端的 HttpSession,这里即 userId 0 或为 null)

LOGIN:用户登录后的默认角色(userId > 0)

OWNER:请求对象的创建者是当前用户(userId = $currentUserId)

CONTACT :请求对象的创建者在当前用户的好友列表中(userId IN( $currentContactIdList ) )

CIRCLE:请求对象的创建者在 当前 用户的好友列表 加上 当前用户自己 得到的列表中 (userId IN( $currentCircleIdList ) // currentCircleIdList = currentContactIdList.add(currentUserId))

ADMIN:管理员权限,默认不支持,需要手动重载 verifyAdmin 方法实现。

apijson.orm.AbstractVerifier.verifyUseRole(SQLConfig, String, RequestMethod, String)

public void verifyUseRole(SQLConfig config, String table, RequestMethod method, String role) throws Exception {
		Log.d(TAG, "verifyUseRole  table = " + table + "; method = " + method + "; role = " + role);
		//验证角色,假定真实强制匹配<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
 
        //<!> 他这里是要获取 数据关联用户ID列名; 
        //例如:
        //OWNER 角色则会带 等于 条件 WHERE  `${visitorIdKey}` = (1); 
        //CONTACT 角色则会带上 IN 条件  WHERE  `${visitorIdKey}` IN (1)
		String visitorIdKey = getVisitorIdKey(config);
		if (table == null) {
			table = config == null ? null : config.getTable();
		}
		if (method == null) {
			method = config == null ? GET : config.getMethod();
		}
		if (role == null) {
			role = config == null ? UNKNOWN : config.getRole();
		}
 
		Object requestId;
		switch (role) {
		case LOGIN://verifyRole通过就行
			break;
		case CONTACT:
		case CIRCLE:
			//TODO 做一个缓存contactMap<visitorId, contactArray>,提高[]:{}查询性能, removeAccessInfo时map.remove(visitorId)
			//不能在Visitor内null -> [] ! 否则会导致某些查询加上不需要的条件!
			List<Object> list = visitor.getContactIdList() == null
			? new ArrayList<Object>() : new ArrayList<Object>(visitor.getContactIdList());
			if (CIRCLE.equals(role)) {
				list.add(visitorId);
			}
 
			//key!{}:[] 或 其它没有明确id的条件 等 可以和key{}:list组合。类型错误就报错
			requestId = config.getWhere(visitorIdKey, true);//JSON里数值不能保证是Long,可能是Integer
			@SuppressWarnings("unchecked") 
			Collection<Object> requestIdArray = (Collection<Object>) config.getWhere(visitorIdKey + "{}", true);//不能是 &{}, |{} 不要传,直接{}
			if (requestId != null) {
				if (requestIdArray == null) {
					requestIdArray = new JSONArray();
				}
				requestIdArray.add(requestId);
			}
 
			if (requestIdArray == null) {//可能是@得到 || requestIdArray.isEmpty()) {//请求未声明key:id或key{}:[...]条件,自动补全
				config.putWhere(visitorIdKey+"{}", JSON.parseArray(list), true); //key{}:[]有效,SQLConfig里throw NotExistException
			} 
			else {//请求已声明key:id或key{}:[]条件,直接验证
				for (Object id : requestIdArray) {
					if (id == null) {
						continue;
					}
					if (id instanceof Number == false) {//不能准确地判断Long,可能是Integer
						throw new UnsupportedDataTypeException(table + ".id类型错误,id类型必须是Long!");
					}
					if (list.contains(Long.valueOf("" + id)) == false) {//Integer等转为Long才能正确判断。强转崩溃
						throw new IllegalAccessException(visitorIdKey + " = " + id + " 的 " + table
								+ " 不允许 " + role + " 用户的 " + method.name() + " 请求!");
					}
				}
			}
			break;
		case OWNER:
			if (config.getMethod() == RequestMethod.POST) {
				List<String> c = config.getColumn();
				List<List<Object>> ovs = config.getValues();
				if ( (c == null || c.isEmpty()) || (ovs == null || ovs.isEmpty()) ) {
					throw new IllegalArgumentException("POST 请求必须在Table内设置要保存的 key:value !");
				}
 
				int index = c.indexOf(visitorIdKey);
				if (index >= 0) {
					Object oid;
					for (List<Object> ovl : ovs) {
						oid = ovl == null || index >= ovl.size() ? null : ovl.get(index);
						if (oid == null || StringUtil.getString(oid).equals("" + visitorId) == false) {
							throw new IllegalAccessException(visitorIdKey + " = " + oid + " 的 " + table
									+ " 不允许 " + role + " 用户的 " + method.name() + " 请求!");
						}
					}
				}
				else {
					List<String> nc = new ArrayList<>(c);
					nc.add(visitorIdKey);
					config.setColumn(nc);
 
					List<List<Object>> nvs = new ArrayList<>();
					List<Object> nvl;
					for (List<Object> ovl : ovs) {
						nvl = ovl == null || ovl.isEmpty() ? new ArrayList<>() : new ArrayList<>(ovl);
						nvl.add(visitorId);
						nvs.add(nvl);
					}
 
					config.setValues(nvs);
				}
			}
			else {
				requestId = config.getWhere(visitorIdKey, true);//JSON里数值不能保证是Long,可能是Integer
				if (requestId != null && StringUtil.getString(requestId).equals(StringUtil.getString(visitorId)) == false) {
					throw new IllegalAccessException(visitorIdKey + " = " + requestId + " 的 " + table
							+ " 不允许 " + role + " 用户的 " + method.name() + " 请求!");
				}
 
				config.putWhere(visitorIdKey, visitorId, true);
			}
			break;
		case ADMIN://这里不好做,在特定接口内部判。 可以是  /get/admin + 固定秘钥  Parser#needVerify,之后全局跳过验证
			verifyAdmin();
			break;
		default://unknown,verifyRole通过就行
			break;
		}
 
		//验证角色,假定真实强制匹配>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
	}

函数解析

函数解析器,创建接口: apijson.orm.ParserCreator 函数解析器,解析接口: apijson.orm.Parser#onFunctionParse

示例源码: org.yang.test.MyParser#onFunctionParse,

@Override
    public Object onFunctionParse(String key, String function, String parentPath, String currentName, JSONObject currentObject) throws Exception {
        if (functionParser == null) {
            functionParser = createFunctionParser();
            functionParser.setMethod(getMethod());
            functionParser.setTag(getTag());
            functionParser.setVersion(getVersion());
            functionParser.setRequest(requestObject);
 
            if (functionParser instanceof APIJSONFunctionParser) {
//                ((APIJSONFunctionParser) functionParser).setSession(getSession());
            }
        }
        functionParser.setKey(key);
        functionParser.setParentPath(parentPath);
        functionParser.setCurrentName(currentName);
        functionParser.setCurrentObject(currentObject);
 
        return functionParser.invoke(function, currentObject);
    }
 

apijson.orm.AbstractFunctionParser#invoke(java.lang.String, com.alibaba.fastjson.JSONObject) apijson.orm.AbstractFunctionParser#invoke(apijson.orm.AbstractFunctionParser, java.lang.String, com.alibaba.fastjson.JSONObject)

public static Object invoke(@NotNull AbstractFunctionParser parser, @NotNull String function, @NotNull JSONObject currentObject) throws Exception {
 
		FunctionBean fb = parseFunction(function, currentObject, false);
 
		JSONObject row = FUNCTION_MAP.get(fb.getMethod());
		....
 
		try {
			return invoke(parser, fb.getMethod(), fb.getTypes(), fb.getValues()); 
		} catch (Exception e) {
 
}}

AbstractFunctionParser 的实现是, 最终是反射调用自身的方法

/**反射调用
    * @param methodName
    * @param parameterTypes
    * @param args
    * @return
    */
public static Object invoke(@NotNull AbstractFunctionParser parser, @NotNull String methodName, @NotNull Class<?>[] parameterTypes, @NotNull Object[] args) throws Exception {
    return parser.getClass().getMethod(methodName, parameterTypes).invoke(parser, args);
}

自定义解析器

其实源头是 Parser#createFunctionParser 覆盖这个方法, 返回自定义解析器

public class MyParser extends AbstractParser {
    ....
    @Override
    public FunctionParser createFunctionParser() {
        return new MyFunctionParser();
    }
 
    @Override
    public Object onFunctionParse(String key, String function, String parentPath, String currentName, JSONObject currentObject) throws Exception {
        if (functionParser == null) {
            functionParser = createFunctionParser();
            functionParser.setMethod(getMethod());
            functionParser.setTag(getTag());
            functionParser.setVersion(getVersion());
            functionParser.setRequest(requestObject);
        }
        functionParser.setKey(key);
        functionParser.setParentPath(parentPath);
        functionParser.setCurrentName(currentName);
        functionParser.setCurrentObject(currentObject);
 
        return functionParser.invoke(function, currentObject);//会调到 MyFunctionParser#invoke
    }
 
}
public class MyFunctionParser extends AbstractFunctionParser {
 
    @Override
    public Object invoke(@NotNull String function, @NotNull JSONObject currentObject) throws Exception {
        System.out.println("调用函数 "+ function+"; JSONObject = "+currentObject);
        return "hi";
    }
}
 

远程函数的返回值一般都是 Object,参数一般是一个 JSONObject 跟着 0 个到多个 String。对远程函数而言,如果返回 null,则只会调用函数,而不会在响应中显示。如果抛出异常,则会中止当前请求。如果返回一个特定的对象,则会被序列化为 JSON,并在响应中显示出来。

简单整合

抽出来, 使用它的描述结构JSON, 就可以CURD数据库, 不要它自带的表;

ObjectParser

参考: apijson.framework.APIJSONObjectParser MyObjectParser.java

/**
 * @author yangfh
 * @date 2022/9/26 10:56
 **/
public class MyObjectParser extends AbstractObjectParser {
    private static final Logger log = LoggerFactory.getLogger(MyObjectParser.class);
    public MyObjectParser(@NotNull JSONObject request, String parentPath, SQLConfig arrayConfig
            , boolean isSubquery, boolean isTable, boolean isArrayMainTable) throws Exception {
        super(request, parentPath, arrayConfig, isSubquery, isTable, isArrayMainTable);
    }
    @Override
    public MyObjectParser setMethod(RequestMethod method) {
        super.setMethod(method);
        return this;
    }
    @Override
    public MyObjectParser setParser(AbstractParser<?> parser) {
        super.setParser(parser);
        return this;
    }
    @Override
    public SQLConfig newSQLConfig(RequestMethod method, String table, String alias, JSONObject request, List<Join> joinList, boolean isProcedure) throws Exception {
        SQLConfig config = MySQLConfig.newSQLConfig(method, table, alias, request, joinList, isProcedure);
        return config;
    }
}
 

SQLConfig

参考: apijson.framework.APIJSONSQLConfig

public class MySQLConfig extends APIJSONSQLConfig {
    private static final Logger log = LoggerFactory.getLogger(MySQLConfig.class);
    public MySQLConfig() {
        this(RequestMethod.GET);
    }
    public MySQLConfig(RequestMethod method) {
        super(method);
        DEFAULT_DATABASE = DATABASE_MYSQL;  //TODO 默认数据库类型,改成你自己的
        DEFAULT_SCHEMA = "db-test";
    }
    @Override
    public String getDBVersion() {
        return "8.0.27";  // "8.0.11";  // TODO 改成你自己的 MySQL 或 PostgreSQL 数据库版本号  // MYSQL 8 和 7 使用的 JDBC 配置不一样
    }
 
    @JSONField(serialize = false)  // 不在日志打印 账号/密码 等敏感信息
    @Override
    public String getDBUri() {
        return "jdbc:mysql://192.168.2.254:3306/tny-admin?serverTimezone=Asia/Shanghai&characterEncoding=utf8&useSSL=false"; // TODO 改成你自己的,TiDB 可以当成 MySQL 使用,默认端口为 4000
    }
    @JSONField(serialize = false)  // 不在日志打印 账号/密码 等敏感信息
    @Override
    public String getDBAccount() {
        return "user_dev";  // TODO 改成你自己的
    }
 
    @JSONField(serialize = false)  // 不在日志打印 账号/密码 等敏感信息
    @Override
    public String getDBPassword() {
        return "sny321.com";  // TODO 改成你自己的,TiDB 可以当成 MySQL 使用, 默认密码为空字符串 ""
    }
	@Override
	public boolean isFakeDelete() {//是否是假删除
		return false;
	}
	@Override
	public void onFakeDelete(Map<String, Object> var1) {//在假删除的时候处理
	}
    public static Callback<? extends Object> MY_CALLBACK = new SimpleCallback<Object>() {
        @Override
        public SQLConfig getSQLConfig(RequestMethod method, String database, String schema,String datasource, String table) {
            SQLConfig config = new MySQLConfig(method);
            config.setMethod(method);
            config.setDatabase(database);
            config.setDatasource(datasource);
            config.setSchema(schema);
            config.setTable(table);
            return config;
        }
        //取消注释来实现自定义各个表的主键名
        //			@Override
        //			public String getIdKey(String database, String schema, String datasource, String table) {
        //				return StringUtil.firstCase(table + "Id");  // userId, comemntId ...
        //				//		return StringUtil.toLowerCase(t) + "_id";  // user_id, comemnt_id ...
        //				//		return StringUtil.toUpperCase(t) + "_ID";  // USER_ID, COMMENT_ID ...
        //			}
 
        @Override
        public String getUserIdKey(String database, String schema, String datasource, String table) {
            return USER_.equals(table) || PRIVACY_.equals(table) ? ID : USER_ID; // id / userId
        }
        //取消注释来实现数据库自增 id
        //			@Override
        //			public Object newId(RequestMethod method, String database, String schema, String datasource, String table) {
        //				return null; // return null 则不生成 id,一般用于数据库自增 id
        //			}
    };
    public static SQLConfig newSQLConfig(RequestMethod method, String table, String alias, JSONObject request, List<Join> joinList, boolean isProcedure) throws Exception {
        return newSQLConfig(method, table, alias, request, joinList, isProcedure, MY_CALLBACK);
    }
}
 

SQLExecutor (适配数据库连接池)

参考: apijson.framework.APIJSONSQLExecutor 适配连接池,如果这里能拿到连接池的有效 Connection,则 SQLConfig 不需要配置 dbVersion, dbUri, dbAccount, dbPassword 参考: https://github.com/APIJSON/APIJSON-Demo/blob/master/APIJSON-Java-Server/APIJSONDemo-Druid/src/main/java/apijson/demo/DemoSQLExecutor.java

public class MySQLExecutor extends AbstractSQLExecutor {
    private static final Logger log = LoggerFactory.getLogger(MySQLExecutor.class);
 
    // 适配连接池,如果这里能拿到连接池的有效 Connection,则 SQLConfig 不需要配置 dbVersion, dbUri, dbAccount, dbPassword
    @Override
    public Connection getConnection(SQLConfig config) throws Exception {
        String key = config.getDatasource() + "-" + config.getDatabase();
        Connection c = connectionMap.get(key);
        if (c == null || c.isClosed()) {
            //从Spring 拿到数据源
            DataSource ds = datasouce;
			connectionMap.put(key, ds == null ? null : ds.getConnection());
        }
 
        // 必须最后执行 super 方法,因为里面还有事务相关处理。
        // 如果这里是 return c,则会导致 增删改 多个对象时只有第一个会 commit,即只有第一个对象成功插入数据库表
        return super.getConnection(config);
    }
 
}

以上源码, 不要改.. 不然有事务, 释放连接等问题 =.=

Verifier 自定义验证权限

参考: apijson.framework.APIJSONVerifier

public class MyVerifier extends AbstractVerifier {
    private static final Logger log = LoggerFactory.getLogger(MyVerifier.class);
    @Override
    public Parser createParser() {
        //内部查数据库 用的?
        return null;
    }
    // <!> 验证 验证 验证 权限
    @Override
    public boolean verifyAccess(SQLConfig config) throws Exception {
        String table = config == null ? null : config.getTable();
        if (table == null) {
            return true;
        }
        RequestMethod method = config.getMethod();
        LoginUserVisitor visitor = (LoginUserVisitor) getVisitor();
        if (visitor.hasAccess(table, method)){
            return true;
        }
        throw new IllegalAccessException("无权限!");
    }
}

Parser

参考: apijson.framework.APIJSONParser<T>

 
public class MyParser extends AbstractParser {
    private static final Logger log = LoggerFactory.getLogger(MyParser.class);
    public MyParser() {
        super();
    }
    @Override
    public FunctionParser createFunctionParser() {
        //
        return new APIJSONFunctionParser();//覆盖它 即可自定义函数解析, 重写这个 #invoke(String, JSONObject, boolean) 优先一点
    }
    @Override
    public Verifier createVerifier() {
        MyVerifier myVerifier = new MyVerifier();
        return myVerifier;
    }
    @Override
    public SQLConfig createSQLConfig() {
        MySQLConfig apijsonsqlConfig = new MySQLConfig();
        return apijsonsqlConfig;
    }
    // 适配数据源
    @Override
    public SQLExecutor createSQLExecutor() {
        return new APIJSONSQLExecutor();//<!>这个对象可以适配数据源
        
    }
    @Override
    public AbstractObjectParser createObjectParser(JSONObject request, String parentPath, SQLConfig arrayConfig
            , boolean isSubquery, boolean isTable, boolean isArrayMainTable) throws Exception {
        AbstractObjectParser parser = new MyObjectParser(request,parentPath,arrayConfig, isSubquery, isTable, isArrayMainTable);
        parser.setMethod( getMethod());
        parser.setParser(this);
        return parser;
    }
//////////////// 执行函数的 逻辑
    @Override
	public Object onFunctionParse(String key, String function, String parentPath, String currentName, JSONObject currentObject, boolean containRaw) throws Exception {
		if (functionParser == null) {
			functionParser = createFunctionParser();
			functionParser.setMethod(getMethod());
			functionParser.setTag(getTag());
			functionParser.setVersion(getVersion());
			functionParser.setRequest(requestObject);
		}
		functionParser.setKey(key);
		functionParser.setParentPath(parentPath);
		functionParser.setCurrentName(currentName);
		functionParser.setCurrentObject(currentObject);
		
		return functionParser.invoke(function, currentObject, containRaw);//调用函数解析的方法
	}

测试

public class TestParse {
    private static final Logger log = LoggerFactory.getLogger(TestParse.class);
 
    public static void main(String[] args) throws Exception {
        /*
        Parser parser = new MyParser();
        String request = "{\n" +
                "    \"Moment\": {\n" +
                "      \"id\":123\n" +
                "    }\n" +
                "  }";
        parser.setNeedVerify(false);
        JSONObject jsonObject = parser.parseResponse(request);
        System.out.println("查询结果: =============== \n"+jsonObject );
        */
 
        String updateSta = "{\n" +
                "    \"Moment\": {\n" +
                "      \"userId\":123,\n" +
                "      \"content\":\"测试新增00321\",\n" +
                "      \"praiseUserIdList\":[12,55,63],\n" +
                "      \"pictureList\":[]\n" +
                "    }\n" +
                "  }";
        Parser upparse = new MyParser(RequestMethod.POST);
        upparse.setNeedVerify(false);
        JSONObject jup = upparse.parseResponse(updateSta);
        System.out.println("新增结果: =============== \n"+jup );
    }
}
 

参考项目: apijson-framework APIJSON-Java-Server/APIJSONBoot

整合源码

整合模块源码 bak

对外开放的问题

  1. 几乎对数据库无限制的 表访问和join查询权限;
  2. 没办法控制隐私字段, 例 User的password字段;
  3. 无法限制响应结构, API版本;
  4. 怎么验证前端传参?;

整合对外和路由

https://github.com/APIJSON/apijson-router

解决对外开放的问题, 需要用他的原本 request 表的内容 验证这一块

so, 还是要建一张 apijson 的 request 表, 管理api结构, 版本..

解决表访问权限

在 Access 表配置校验规则,默认不允许访问,需要对 每张表、每种角色、每种操作 做相应的配置,粒度细分到行级

对应初始化方法是: apijson.framework.APIJSONVerifier#initAccess

  1. MyVerifier 继承 APIJSONVerifier
public class MyVerifier extends APIJSONVerifier {
    //
}
  1. 创建 Access 表

  2. 启动前初始化;

有几个重载方法

public static <T> JSONObject initAccess(APIJSONCreator<T> creator) throws ServerException
public static <T> JSONObject initAccess(boolean shutdownWhenServerError, APIJSONCreator<T> creator) throws ServerException 
public static <T> JSONObject initAccess(boolean shutdownWhenServerError, APIJSONCreator<T> creator, JSONObject table) throws ServerException 
....

解决敏感字段

在抽取数据结果的时候, 会回调这个方法 apijson.orm.AbstractSQLExecutor.onPutColumn(SQLConfig, ResultSet, ResultSetMetaData, int, JSONObject, int, Join, Map<String, JSONObject>)

protected JSONObject onPutColumn(@NotNull SQLConfig config, @NotNull ResultSet rs, @NotNull ResultSetMetaData rsmd
			, final int tablePosition, @NotNull JSONObject table, final int columnIndex, Join join, Map<String, JSONObject> childMap) throws Exception {
		if (table == null) {  // 对应副表 viceSql 不能生成正常 SQL, 或者是 ! - Outer, ( - ANTI JOIN 的副表这种不需要缓存及返回的数据
			Log.i(TAG, "onPutColumn table == null >> return table;");
			return table;
		}
        //<!> 在这里处理了 '__' 前缀的隐藏列, 可以加入自定义处理, 隐藏敏感字段
		if (isHideColumn(config, rs, rsmd, tablePosition, table, columnIndex, childMap)) {
			Log.i(TAG, "onPutColumn isHideColumn(config, rs, rsmd, tablePosition, table, columnIndex, childMap) >> return table;");
			return table;
		}
 
		String label = getKey(config, rs, rsmd, tablePosition, table, columnIndex, childMap);
		Object value = getValue(config, rs, rsmd, tablePosition, table, columnIndex, label, childMap);
 
		// 主表必须 put 至少一个 null 进去,否则全部字段为 null 都不 put 会导致中断后续正常返回值
		if (value != null || (join == null && table.isEmpty())) {
			table.put(label, value);
		}
 
		return table;
	}
 
 
protected boolean isHideColumn(@NotNull SQLConfig config, @NotNull ResultSet rs, @NotNull ResultSetMetaData rsmd
        , final int tablePosition, @NotNull JSONObject table, final int columnIndex, Map<String, JSONObject> childMap) throws SQLException {
    return rsmd.getColumnName(columnIndex).startsWith("_");
}

解决参数验证

在 Request 表配置校验规则 structure,提供 MUST、TYPE、VERIFY 等通用方法,可通过 远程函数 来完全自定义

权限系统是现代服务中绕不过的一个话题,APIJSON 也针对这一问题交出了自己的答卷。Access 表就是 APIJSON 的权限中心,其中定义了对于数据库中的每张表,哪些角色(RequestRole)可以调用什么请求方法。

Access 表:每一行是一张表,每一列是一个访问方法,每一格则是角色数组。只有在这个数组内的角色才能用这个访问方法访问这张表。但是具体设定起来,可能还是会有些头大。以下是一个可能的设置方法:

对应初始化方法是: apijson.framework.APIJSONVerifier#initAccess

  1. 同样是修改 Verifier, 将 MyVerifier 继承 APIJSONVerifier
public class MyVerifier extends APIJSONVerifier {
}
  1. 创建 request 表

  2. 启动前初始化;

有几个重载方法

public static JSONObject initRequest(boolean shutdownWhenServerError) throws ServerException 
public static <T> JSONObject initRequest(APIJSONCreator<T> creator) throws ServerException 
public static <T> JSONObject initRequest(boolean shutdownWhenServerError, APIJSONCreator<T> creator) throws ServerException
....

参考

参数检验, 响应结构, 版本

对应初始化方法是: apijson.framework.APIJSONVerifier#initRequest

"VERIFY":{
  "type{}":[0,1,2]
}
//就能校验 type 的值是不是 0,1,2中的一个。
"VERIFY": { "money&{}":">0,<=10000" }              //自动验证是否 money>0 & money<=10000
"TYPE": { "balance": "DECIMAL" }                   //自动验证balance类型是否为BigDecimal
"UNIQUE": "phone"                                  //强制phone的值为数据库中没有的
"MUST": "id,name"                                  //强制传id,name两个字段
"REFUSE": "balance"                                //禁止传balance字段
"INSERT": { "@role": "OWNER" }                     //如果没传@role就自动添加
"UPDATE": { "id@": "User/id" }                     //强制放入键值对

全部操作符见 Operation.java 的注释

URL 路由

基本没有独立业务的东西, 也不知道路由个啥 https://github.com/APIJSON/apijson-router

业务相关

覆盖表名映射

接口方法 apijson.orm.SQLConfig#getSQLTable 返回 sql的表名, 重写之即可

    @JSONField(serialize = false)
	public String getSQLTable() {
    	String table = super.getTable();
    	// 如果有配有别名
    	String mappTable = (String) TABLE_KEY_MAP.get(table);
    	if(mappTable != null) {
    		return mappTable;
    	}
    	mappTable = StringUtils.toUnderScoreCase(table);//返回真实表名
    	return mappTable;
	}

表名/列名 映射插件

apijson-column

  1. 初始化 全局 ColumnUtil 内部的列名与实体属性名的映射
MetamodelImplementor  metamodel= (MetamodelImplementor) sessionFactory.getMetamodel();//具体实现
Set<EntityType<?>> entitySet = metamodel.getEntities();
List<String> mapptables = new ArrayList<String>(30);
//dbMapper 数据库映射 {表名(其实是实体名,首字母大写的), [列名1,列名2]}
Map<String, List<String>> tableColumn = new HashMap<>(30);
//keyMapper 实体映射  {实体名, {属性名1, 列名1, 属性名2, 列名2} }
Map<String, Map<String, String>> keyColumnMap = new HashMap<>(30);
for (EntityType<?> et: entitySet) {
    Class<?> entityClass = et.getJavaType();
    if(isNeedMapper(entityClass)) {
        EntityDescDTO desc = EntityMetadataUtils.getEntityDesc(entityClass, sessionFactory);
        putDbMapper(desc, tableColumn);
        putKeyMapper(desc, keyColumnMap);
        mapptables.add(desc.getEntityName());
    }
}
log.info("API JSON 处理映射的实体 {} ",mapptables);
ColumnUtil.VERSIONED_TABLE_COLUMN_MAP.put(null, tableColumn);
ColumnUtil.VERSIONED_KEY_COLUMN_MAP.put(null, keyColumnMap);
ColumnUtil.init();
  1. 在你项目继承 AbstractSQLConfig 的子类重写方法 setColumn, getKey
@Override
public AbstractSQLConfig setColumn(List<String> column) {
    return super.setColumn(ColumnUtil.compatInputColumn(column, getTable(), getMethod()));
}
@Override
public String getKey(String key) {
    return super.getKey(ColumnUtil.compatInputKey(key, getTable(), getMethod()));
}
  1. 在你项目继承 AbstractSQLExecutor 的子类重写方法 getKey
@Override
protected String getKey(SQLConfig config, ResultSet rs, ResultSetMetaData rsmd, int tablePosition, JSONObject table,
        int columnIndex, Map<String, JSONObject> childMap) throws Exception {
    return ColumnUtil.compatOutputKey(super.getKey(config, rs, rsmd, tablePosition, table, columnIndex, childMap), config.getTable(), config.getMethod());
}

自定义ID

生成规则默认是当前时间毫秒值,可以自定义 AbstractSQLConfig$SimpleCallback#newId 在 DemoSQLConfig.SIMPLE_CALLBACK 内部重写 newId 方法,可以改成你自己想要的 UUID 等值,支持 Long 和 String 类型。

Request 表和 tag 参数

“tag”:tag 后面的tag是非GET、HEAD请求中匹配请求的JSON结构的标识,一般是要查询的Table的名称,由后端 request 表中指定。 GET、HEAD请求是开放请求,可任意组合任意嵌套。 其它请求为受限制的安全/私密请求,对应的 方法(method), 标识(tag), 版本(version), 结构(structure) 都必须和 后端Request表中所指定的 一一对应,否则请求将不被通过。 version 不传、为 null 或 0 都会使用最高版本,传了其它有效值则会使用最接近它的最低版本。

Request 表说明

请求参数校验配置(必须)。 最好编辑完后删除主键,这样就是只读状态,不能随意更改。需要更改就重新加上主键。 id:唯一标识 debug:是否为 DEBUG 调试数据,只允许在开发环境使用,测试和线上环境禁用:0-否,1-是。 version: GET,HEAD可用任意结构访问任意开放内容,不需要这个字段。 其它的操作因为写入了结构和内容,所以都需要,按照不同的version选择对应的structure。 自动化版本管理: Request JSON最外层可以传 “version”:Integer 。 1.未传或 0,用最新版。 “@order”:”version-“ 2.已传且 > 0,用version以上的可用版本的最低版本。 “@order”:”version+”, “version{}”:”>={version}”

method:只限于GET,HEAD 外的操作方法。 tag:标签 structure:“结构。TODO 里面的 PUT 改为 UPDATE,避免和请求 PUT 搞混。” detail:详细说明 date:创建日期

tag 参数

“tag”:tag 后面的tag是非GET、HEAD请求中匹配请求的JSON结构的标识,一般是要查询的Table的名称,由后端 request 表中指定。

会查 request 表, 大概作用是根据 tag,version 返回不同的结构, 参数验证也是在这张表里面定义apijson.orm.AbstractParser#parseCorrectRequest 处理了验证

@Override
public JSONObject parseCorrectRequest(RequestMethod method, String tag, int version, String name, @NotNull JSONObject request
        , int maxUpdateCount, SQLCreator creator) throws Exception {
 
    if (RequestMethod.isPublicMethod(method)) {
        return request;//需要指定JSON结构的get请求可以改为post请求。一般只有对安全性要求高的才会指定,而这种情况用明文的GET方式几乎肯定不安全
    }
 
    if (StringUtil.isEmpty(tag, true)) {
        throw new IllegalArgumentException("请在最外层传 tag !一般是 Table 名,例如 \"tag\": \"User\" ");
    }
    //获取指定的JSON结构 <<<<<<<<<<<<
    JSONObject object = null;
    String error = "";
    try {
        //最终是会查 Request 表的
        object = getStructure("Request", method.name(), tag, version);
    } catch (Exception e) {
        error = e.getMessage();
    }
    ....
}

structure 结构

{
    "MUST": "Moment[].page",  // 必传 Moment[].page
    "REFUSE": "!Moment[].count,!format,!",  // 不禁传 Moment[].count 和 format,禁传 MUST 之外的其它所有 key
    "TYPE": {
        "format": "BOOLEAN",  // format 类型必须是布尔 Boolean
        "Moment[].page": "NUMBER",  // Moment[].page 类型必须是整数 Integer
        "Moment[].count": "NUMBER"  // Moment[].count 类型必须是整数 Integer
    }
}

关键字符号

JSONObject

public static final String KEY_TRY = "@try"; //尝试,忽略异常
public static final String KEY_CATCH = "@catch"; //TODO 捕捉到异常后,处理方式  null-不处理;DEFAULT-返回默认值;ORIGIN-返回请求里的原始值
public static final String KEY_DROP = "@drop"; //丢弃,不返回,TODO 应该通过 fastjson 的 ignore 之类的机制来处理,避免导致下面的对象也不返回
//	public static final String KEY_KEEP = "@keep"; //一定会返回,为 null 或 空对象时,会使用默认值(非空),解决其它对象因为不关联的第一个对为空导致也不返回
public static final String KEY_DEFULT = "@default"; //TODO 自定义默认值 { "@default":true },@default 可完全替代 @keep
public static final String KEY_NULL = "@null"; //TODO 值为 null 的键值对 "@null":"tag,pictureList",允许 is NULL 条件判断, SET tag = NULL 修改值为 NULL 等
public static final String KEY_CAST = "@cast"; //TODO 类型转换 cast(date AS DATE)
 
public static final String KEY_ROLE = "@role"; //角色,拥有对某些数据的某些操作的权限
public static final String KEY_DATABASE = "@database"; //数据库类型,默认为MySQL
public static final String KEY_SCHEMA = "@schema"; //数据库,Table在非默认schema内时需要声明
public static final String KEY_DATASOURCE = "@datasource"; //数据源
public static final String KEY_EXPLAIN = "@explain"; //分析 true/false
public static final String KEY_CACHE = "@cache"; //缓存 RAM/ROM/ALL
public static final String KEY_COLUMN = "@column"; //查询的Table字段或SQL函数
public static final String KEY_FROM = "@from"; //FROM语句
public static final String KEY_COMBINE = "@combine"; //条件组合,每个条件key前面可以放&,|,!逻辑关系  "id!{},&sex,!name&$"
public static final String KEY_GROUP = "@group"; //分组方式
public static final String KEY_HAVING = "@having"; //聚合函数条件,一般和@group一起用
public static final String KEY_HAVING_AND = "@having&"; //聚合函数条件,一般和@group一起用
public static final String KEY_ORDER = "@order"; //排序方式
public static final String KEY_RAW = "@raw"; // 自定义原始 SQL 片段
public static final String KEY_JSON = "@json"; //SQL Server 把字段转为 JSON 输出

文档生成

https://github.com/TommyLemon/APIAuto

本项目是纯静态 SPA 网页,下载源码解压后: 可以用浏览器打开 index.html,建议用 Chrome 或 Firefox (Safari、Edge、IE 等可能有兼容问题),注意此方法不显示 svg 图标。 也可以用 IntelliJ Webstorm, IntelliJ IDEA, Eclipse 等 IDE 来打开。 也可以部署到服务器并用 Nginx 或 Node 反向代理,或者 把源码放到 SpringBoot 项目的 resources/static 目录。 还可以直接访问官方网站 http://apijson.cn/apihttp://apijson.cn:8080

把左侧 URL 输入框内基地址改为你主机的地址(例如 http://localhost:8080 ), 然后在右上角 设置 下拉菜单内修改 数据库类型Database、数据库模式Schema。

右上角登录的默认管理员账号为 13000082001 密码为 123456, 右侧上方中间 3 个标签是默认的测试用户账号,点击登录/退出,左侧 - 删除,右侧 + 新增。

自动生成文档、自动管理测试用例 这两个功能 需要部署 APIJSON 后端,建议用 APIJSONBoot 系列之一 Demo,见 https://github.com/APIJSON/APIJSON-Demo/tree/master/APIJSON-Java-Server