Tencent / APIJSON

🏆 实时 零代码、全功能、强安全 ORM 库 🚀 后端接口和文档零代码,前端(客户端) 定制返回 JSON 的数据和结构 🏆 Real-Time coding-free, powerful and secure ORM 🚀 providing APIs and Docs without coding by Backend, and the returned JSON of API can be customized by Frontend(Client) users
http://apijson.cn
Other
17.27k stars 2.16k forks source link

crud复杂语句catch删除脏数据,请问有好的解决方案吗? #658

Open cloudAndMonkey opened 10 months ago

cloudAndMonkey commented 10 months ago

Description

try {
    crud(db,redis等)
} catch(){
    //TODO 删除redis key
} finally{

}

请问实现上面的效果,有好的解决方案吗? 前置/后置函数,加一个异常执行函数?

TommyLemon commented 10 months ago

可以通过 @try + @catch + @finally 实现

@try: true,
@catch: "catchFun(id)" // 对应调用 DemoFunctionParser 中的 public Object catchFun(JSONObject curObj, Throwable e, String idKey)
@finally: "delRedisKey('User-82001')" // 对应调用 DemoFunctionParser 中的 public void delRedisKey(JSONObject curObj, String key) // String k = getArgVal(key) 自动根据单引号判断为值,根据 User/id 判断为路径来按路径取值等

目前仅有 @try 已实现,其它两个你试试 https://github.com/Tencent/APIJSON/blob/master/APIJSONORM/src/main/java/apijson/JSONObject.java#L132-L136

image
cloudAndMonkey commented 10 months ago

可以通过 @Try + @catch + @finally 实现

@try: true,
@catch: "catchFun(id)" // 对应调用 DemoFunctionParser 中的 public Object catchFun(JSONObject curObj, Throwable e, String idKey)
@finally: "delRedisKey('User-82001')" // 对应调用 DemoFunctionParser 中的 public void delRedisKey(JSONObject curObj, String key) // String k = getArgVal(key) 自动根据单引号判断为值,根据 User/id 判断为路径来按路径取值等

目前仅有 @Try 已实现,其它两个你试试 https://github.com/Tencent/APIJSON/blob/master/APIJSONORM/src/main/java/apijson/JSONObject.java#L132-L136

image

其他两个没有实现,哈哈

cloudAndMonkey commented 10 months ago

@TommyLemon 这里执行 @catch: "catchFun(id)" , 对吧? https://github.com/Tencent/APIJSON/blob/master/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java#L299-L300

TommyLemon commented 10 months ago

对,先提前 get 出来 @catch 和 @finally 的值 https://github.com/Tencent/APIJSON/blob/master/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java#L95-L105

image

然后在 AbstractObjectParser 这里 if tri 判断后调用 parseFunction https://github.com/Tencent/APIJSON/blob/master/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java#L299-L304

image
cloudAndMonkey commented 10 months ago

@TommyLemon

} catch (Exception e) {
    if (tri == false) {
        if(StringUtil.isNotEmpty(isCatch)) {
            String catchKey = JSONRequest.KEY_CATCH.substring(1);
            onParse(catchKey + "()", isCatch);
            parseFunction(catchKey + "()", catchKey, isCatch, parentPath, this.name, request, false);
        }
        throw CommonException.wrap(e, sqlConfig);  // 不忽略错误,抛异常
    }
    invalidate();  // 忽略错误,还原request
} 

onParse, parseFunction 两种方式调用执行流程是一样的,你认为那种方式合适呢?

cloudAndMonkey commented 10 months ago

https://github.com/Tencent/APIJSON/blob/master/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java#L299-L304 @finally 考虑到一个问题 AbstractObjectParser catch 后添加 finally, 是无法获取执行结果(正确和异常). response执行结果需要在外层获取 https://github.com/Tencent/APIJSON/blob/master/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java#L540

TommyLemon commented 10 months ago

parseFunction 更简单直接,应该放到 if (tri == false) {} 后面,只在 try 时生效。 或者只要 @catch/@finally 传了有效值,不需要传 @try: true 就生效?这样使用简单一些

TommyLemon commented 10 months ago

@finally 需要在 catch (Exception e) 和 正常走完流程后都执行,最好找一个同时满足两者的地方,写一处代码就行,实在不行就只能分开写两处了

TommyLemon commented 10 months ago

只要内部抛了异常,肯定不能拿到正常流程的结果,只能拿到 catch 异常后的结果,可以考虑 @default 返回默认值

cloudAndMonkey commented 10 months ago

parseFunction 更简单直接,应该放到 if (tri == false) {} 后面,只在 try 时生效。 或者只要 @catch/@finally 传了有效值,不需要传 @Try: true 就生效?这样使用简单一些

不需要传 @Try: true 就生效,这样简单.

} catch (Exception e) {
    if (tri == false) {
        throw CommonException.wrap(e, sqlConfig);  // 不忽略错误,抛异常
    }
    if(StringUtil.isNotEmpty(isCatch)) {
        String catchKey = JSONRequest.KEY_CATCH.substring(1);
        parseFunction(catchKey + "()", catchKey, isCatch, parentPath, this.name, request, false);
    }
    invalidate();  // 忽略错误,还原request
}
cloudAndMonkey commented 10 months ago

@TommyLemon 场景: 查询数据,先从缓存查询,缓存存在直接返回.缓存不存在查询数据库. 可以控制某一步直接返回 请问有推荐的解决方案吗? 比如申明 "@block":true

image
TommyLemon commented 10 months ago

@catch: "fun(args)" 调用的函数应该也支持返回值,也就是最后再返回 "@catch": 1

"@catch": { "id": 1, "content": "abc" }

这样的任意值,除非调用的远程函数返回类型是 void 或 return null 才不返回,这样功能更完善一些。 @finally 也一样。

TommyLemon commented 10 months ago

加 Redis 等内存缓存,直接在 DemoSQLExecutor 重写 putCache, getCache, removeCache 就行了,没必要前端新增传参 https://github.com/APIJSON/APIJSON-Demo/blob/master/APIJSON-Java-Server/APIJSONBoot-MultiDataSource/src/main/java/apijson/demo/DemoSQLExecutor.java#L68-L130

image

控制某一步直接返回的目的是啥?

cloudAndMonkey commented 10 months ago

@catch: "fun(args)" 调用的函数应该也支持返回值,也就是最后再返回 "@catch": 1

"@catch": { "id": 1, "content": "abc" }

这样的任意值,除非调用的远程函数返回类型是 void 或 return null 才不返回,这样功能更完善一些。 @finally 也一样。

收到

cloudAndMonkey commented 10 months ago

@TommyLemon 分页支持从0, 1开始,添加一个开关即可,请问需要支持吗? https://github.com/Tencent/APIJSON/blob/master/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java#L1295

if(AbstractParser.DEFAULT_QUERY_PAGE == false) {
   page2 = page2 > 0 ? page2 -1 : page2;
}
TommyLemon commented 10 months ago

这个需要的,DEFAULT_QUERY_PAGE 要改成更直观的名字,例如 IS_PAGE_START_FROM_ONE 这种

cloudAndMonkey commented 10 months ago

@TommyLemon 最近有点忙,不好意思 @catch 返回值已经支持

image

问题: 1、catch 是否放在 try 前面 ?

} catch (Exception e) {
    if(isCatch != null) {
        processCallFunction(KEY_CATCH, isCatch);
    }
    if (tri == false) {
        throw CommonException.wrap(e, sqlConfig);  // 不忽略错误,抛异常
    }
    invalidate();  // 忽略错误,还原request
} finally {
    if(isFinally != null) {
        processCallFunction(KEY_FINALLY, isFinally);
    }
}

public void processCallFunction(String key, String value) throws Exception {
    String funKey = key.substring(1);
    parseFunction(funKey + "()", funKey, value, parentPath, this.name, request, false);
    if(response.get(funKey) != null) {
        this.parser.requestObject.put(key, response.get(funKey));  // 设置返回值
    }
}

2、@finally 执行逻辑和 @catch保持一致吗? 3、分页变量名已经变更为: IS_PAGE_START_FROM_ONE

image
TommyLemon commented 10 months ago

赞。

tri 已经不需要了,JSONObject.KEY_TRY 可以标记 @Deprecated,以上代码对应改为

        if (catch_ == null) { // isCatch 看起来是 Boolean 类型,其实是 String 类型,前端传 @catch:"" 表示仅不抛异常,但不执行远程函数
           throw CommonException.wrap(e, sqlConfig);  // 不忽略错误,抛异常
       }

       if (StringUtil.isNotEmpty(catch_)) { // 这个判断移到 processCallFunction 更好,和 @finally 统一处理
           processCallFunction(KEY_CATCH,  catch_);
       }
       invalidate();  // 忽略错误,还原 request

同理 isFinally 命名改为 finally_ 或 finallyVal 之类的更好

cloudAndMonkey commented 10 months ago

@TommyLemon 代码已经调整,功能测试通过. 你再看看代码是否还有需要优化的点?

} catch (Exception e) {
    if (catch_ == null) { // 前端传 @catch:"" 表示仅不抛异常,但不执行远程函数
         throw CommonException.wrap(e, sqlConfig);  // 不忽略错误,抛异常
    }
    processCallFunction(KEY_CATCH, catch_);
    invalidate();  // 忽略错误,还原request
} finally {
    processCallFunction(KEY_FINALLY, finally_);
}

public void processCallFunction(String key, String value) throws Exception {
    if(StringUtil.isNotEmpty(value)) {
        String funKey = key.substring(1);
        parseFunction(funKey + "()", funKey, value, parentPath, this.name, request, false);
    }
}

返回结果统一处理: https://github.com/Tencent/APIJSON/blob/master/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java#L538

String warn = Log.DEBUG == false || error != null ? null : getWarnString();
processRequestObject(request, KEY_CATCH);
processRequestObject(request, KEY_FINALLY);
requestObject = error == null ? extendSuccessResult(requestObject, warn, isRoot) : extendErrorResult(requestObject, error, requestMethod, getRequestURL(), isRoot);

public void processRequestObject(JSONObject request, String key) {
  if(request.containsKey(key)) {
      String _key = key.substring(1);
      requestObject = requestObject == null ? new JSONObject() : requestObject;
      requestObject.put(_key, this.queryResultMap.get(_key));
  }
}

测试脚本

{
    "name": "jerry",
    "@catch": "",
    //"@catch": "sayHello(name)",
    "@finally": "sayHello(name)",
    "Document_copy2:data[]": {
        "Document_copy2": {
            "@column" : "id1"
        },
        "page": 0,
        "count": 10
    },
    "@explain": true,
    "format": true
}

{
    "msg": "success",
    "code": 200,
    "finally": "Hello, jerry",
    "debug:info|help": " \n提 bug 请发请求和响应的【完整截屏】,没图的自行解决! \n开发者有限的时间和精力主要放在【维护项目源码和文档】上! \n【描述不详细】 或 【文档/常见问题 已有答案】 的问题可能会被忽略!! \n【态度 不文明/不友善】的可能会被踢出群,问题也可能不予解答!!! \n\n **环境信息**  \n系统: Mac OS X 12.4 \n数据库: DEFAULT_DATABASE = MYSQL \nJDK: 1.8.0_351 x86_64 \nAPIJSON: 6.3.0 \n   \n【常见问题】:https://github.com/Tencent/APIJSON/issues/36 \n【通用文档】:https://github.com/Tencent/APIJSON/blob/master/Document.md \n【视频教程】:https://search.bilibili.com/all?keyword=APIJSON",
    "ok": true,
    "time": 1705460225810,
    "sql:generate|cache|execute|maxExecute": "1|0|1|200",
    "depth:count|max": "3|5",
    "time:start|duration|end|parse|sql": "1705460225749|61|1705460225810|61|0"
}

{
    "name": "jerry",
    //"@catch": "",
    "@catch": "sayHello(name)",
    "@finally": "sayHello(name)",
    "User_logback[]": [
        {
            "id": "0430f00c-a895-4f25-b096-40382bd85a11",
            "name": "角色"
        },
        {
            "id": "0430f00c-a895-4f25-b096-40382bd85a12",
            "name": "角色3"
        }
    ],
    "tag": "User_logback[]",
    "@explain": true,
    "format": true
}

{
    "msg": "success",
    "code": 200,
    "finally": "Hello, jerry",
    "catch": "Hello, jerry",
    "debug:info|help": " \n提 bug 请发请求和响应的【完整截屏】,没图的自行解决! \n开发者有限的时间和精力主要放在【维护项目源码和文档】上! \n【描述不详细】 或 【文档/常见问题 已有答案】 的问题可能会被忽略!! \n【态度 不文明/不友善】的可能会被踢出群,问题也可能不予解答!!! \n\n **环境信息**  \n系统: Mac OS X 12.4 \n数据库: DEFAULT_DATABASE = MYSQL \nJDK: 1.8.0_351 x86_64 \nAPIJSON: 6.3.0 \n   \n【常见问题】:https://github.com/Tencent/APIJSON/issues/36 \n【通用文档】:https://github.com/Tencent/APIJSON/blob/master/Document.md \n【视频教程】:https://search.bilibili.com/all?keyword=APIJSON",
    "ok": true,
    "time": 1705460325505,
    "sql:generate|cache|execute|maxExecute": "2|0|2|200",
    "depth:count|max": "2|5",
    "time:start|duration|end|parse|sql": "1705460325418|87|1705460325505|57|30"
}
TommyLemon commented 10 months ago

赞,关于返回结果统一处理,可以考虑移到 AbstractObjectParser 单独处理,因为这样 @catch 和 @finally 才能支持在多个不同对象中使用。

AbstractObjectParser.parseFunction 是有 response.put(key, returnVal) 的, https://github.com/Tencent/APIJSON/blob/master/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java#L1015C14-L1055

应该 setSQLConfig, executeSQL, onFunctionResponse, onChildResponse, onComplete 最前面加上

if (isInvalidate()) {
    return this; // 或对 void 方法 return;
} 

就会自动写入最终结果了

TommyLemon commented 10 months ago

目前看起来直接在 AbstractParser.onObjectParse 对 op 执行方法整体 try-catch-finally 实现简单很多 https://github.com/Tencent/APIJSON/blob/master/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java#L1125-L1131

只是要想想一个对象本身解析抛异常后,是否仍然继续解析它的内部子对象、剩下未执行的关键词、远程函数等?看起来没必要也不应该,避免继续错下去。 其实我当时想加 @try 的原因是: 有时需要同时查多个对象,它们之前没有依赖关系,有的甚至是次要的信息,没查到也没关系,希望报错不影响核心对象查询。

TommyLemon commented 10 months ago

Description

try {
  crud(db,redis等)
} catch(){
  //TODO 删除redis key
} finally{

}

请问实现上面的效果,有好的解决方案吗? 前置/后置函数,加一个异常执行函数?

如果是你的这个需求,那就需要在 @catch 和 @finally 解析过程或之后继续抛异常。 决定权可以给 catchFun 和 delKey 这两个函数,内部自己决定是否抛异常; 也可以把 @try 利用上,true 则不自动抛异常,false 则继续抛原来的异常,@catch 和 @finally 仅仅作为不同流程点的拦截处理,似乎这样更好些

TommyLemon commented 8 months ago

@cloudAndMonkey 可以先发 PR 提交代码,我再看看怎么调整下