2022-09-26
APIJSON
APIJSON 是一种专为 API 而生的 JSON 网络传输协议 以及 基于这套协议实现的 ORM 库。 为各种增删改查提供了完全自动化的万能通用接口,零代码实时满足千变万化的各种新增和变更需求。 能大幅降低开发和沟通成本,简化开发流程,缩短开发周期。
in short 封装对数据库 增 删 改 查 的ORM库
关键对象
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 {
}操作方法
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
安全/私密获取数据, 用于获取钱包等 对安全性要求高的数据
HEAD
普通获取数量,
HEADS
安全/私密获取数量, 用于获取银行卡数量等 对安全性要求高的数据总数
POST
新增数据
PUT
修改数据, 只修改所传的字段
DELETE
删除数据
源码流程
自定义验证权限
关闭验证 的话可以重写 Parser 中的 isNeedVerifyLogin, isNeedVerifyRole, isNeedVerifyContent 来指定是否开关某个方面的校验;
若想自定义验证的话 重写 apijson.orm.AbstractVerifier 的 verifyAccess(SQLConfig config),SQLConfig 是sql参数(id/where/分页),理论是可以实现手动实现行级权限
验证登陆 流程
关系对象 apijson.orm.Verifier
- 在 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);
}
}
- 在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.AbstractParser 和 apijson.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
整合源码
对外开放的问题
- 几乎对数据库无限制的 表访问和join查询权限;
- 没办法控制隐私字段, 例 User的password字段;
- 无法限制响应结构, API版本;
- 怎么验证前端传参?;
整合对外和路由
https://github.com/APIJSON/apijson-router
解决对外开放的问题, 需要用他的原本 request 表的内容 验证这一块
so, 还是要建一张 apijson 的 request 表, 管理api结构, 版本..
解决表访问权限
在 Access 表配置校验规则,默认不允许访问,需要对 每张表、每种角色、每种操作 做相应的配置,粒度细分到行级
对应初始化方法是: apijson.framework.APIJSONVerifier#initAccess
- 将
MyVerifier继承APIJSONVerifier
public class MyVerifier extends APIJSONVerifier {
//
}-
创建 Access 表
-
启动前初始化;
有几个重载方法
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
- 同样是修改 Verifier, 将
MyVerifier继承APIJSONVerifier
public class MyVerifier extends APIJSONVerifier {
}-
创建 request 表
-
启动前初始化;
有几个重载方法
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;
}表名/列名 映射插件
- 初始化 全局 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();- 在你项目继承 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()));
}- 在你项目继承 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
}
}关键字符号
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/api 或 http://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