APIJSON一款自动化的ORM框架,对于前后端开发,带来了很大的便利(有了它,后端不再需要写常规的业务代码,简单的,复杂的,增删改查),为什么这么说,请看下文分析。因为此文针对的是APIJSON深入应用,所以各位看官,如需了解APIJSON基本使用,请先移驾APIJSON官方网站官方文档。
深入使用APIJSON,是因为接手了一个类似12306火车的订票系统,具体业务场景,我们先来看下ER图
库表备注:
站点:opm_site(每条线路的站点,都是来自这些站点,包含一些定位,名称等)
站点列表:opm_site_list(每条线路包含的所有站点)
线路:opm_line(具体线路)
班次:opm_car_freq(线路下的具体班次,定义了票数、时间等必要信息)
业务说明:类似12306,用户选择出发、到达站、出发时间
常规增删改查,在官方文档语法&示例,已经有基础说明,这里不再赘述。根据以上业务,我们来解析下,其中涉及的复杂业务需求。
解析:我们从线路(opm_line)为分析出发点,那么线路关联的站点列表(opm_site_list)所有站点,必须包含用户搜索的出发与到达站,并且站点顺序必须是出发站在前、到达站在后(因为只是包含,不限制顺序,有可能搜索出来反方向的车次,这点应该好理解),然后根据线路,找班次,出发时间在班次上限制的。
下边我们根据以上需求,先想想sql怎么写呢(在上手APIJSON初期,一直有个不好的依赖,总是对于复杂的需求,一上来就想直接用APIJSON实现,可是自己SQL都没想好能不能实现)
我们梳理下,线路包含出发、到达站,对应班次肯定也包含,那第一步我们先要筛选包含用户出发、到达站的线路。
select * from opm_line ol
inner join opm_site_list osl on osl.opm_line_id = ol.id
where opm_site_list.opm_site_id = 1 //筛选包含某个站点?
将线路表、站点列表关联查询,这好像也不行,因为只能筛选一个站点
怎么能筛选两个站点呢?
那将【站点列表】根据线路id内联一次,不就有线路的两个站点了吗!(如果看官对这里有疑惑,请思考或补充单表内联数据关系)
select startSite.opm_line_id
from opm_site_list startSite
inner join opm_site_list arrivedSite on startSite.opm_line_id = arrivedSite.opm_line_id
where startSite.opm_site_id = 1 //出发站点id
and arrivedSite.opm_site_id = 2 //到达站点id
根据线路id内联【站点列表】后,再根据出发、到达站点id筛选只包含,出发、到达站点的线路id。
这样第一步包含搜索的站点,没问题了,但是有个问题,这样会搜索出反方向的线路,是不是?
用户只希望搜索自己搜索方向的线路,比如用户搜【北京->上海】,系统包含线路【北京->天津->上海】和【上海->天津->北京】,用户只希望搜出来【北京->天津->上海】,另外一条要过滤,怎么过滤?
别忘记我们【站点列表】有个排序字段,即线路下,每个站点的排序号。在数据录入时,该字段是这样的:
opm_site_list站点列表
id opm_site_id(站点id) opm_line_id(线路id) seq(排序)
1 1(北京站点id) 1 1
2 2(天津站点id) 1 2
3 3(上海站点id) 1 3
4 3(上海站点id) 2 1
5 2(天津站点id) 2 2
6 1(北京站点id) 2 3
根据排序字段实现只要startSite.seq
APIJSON查询
{
"sql@": { //子查询:筛选包含的用户输入出发、到达站的所有线路id
"from": "Opm_site_list",
"join": "&/Opm_site_list:to/opm_line_id@",
"Opm_site_list": {
"@column": "opm_line_id",
"opm_site_id": 1, //出发站点id
"@raw": "siteListWhereItem1" //注意:这是原始sql替换关键字,即该关键字,根据内容,在后台查找匹配的内容,直接替换。目前的版本,该字段的内容,在后台配置的(为了实现startSite.seq
下单过程一般系统设计都比较复杂,比如涉及到各种条件判断,支付发起(微信、支付宝等),超时缓存等,APIJSON怎么实现呢?目前对特别复杂的场景,要全靠APIJSON已有的自动化接口,很难实现,但是我们可以复用其功能,也能极大的简化许多工作量。
说明:对于目前APIJSON,只支持单一的新增、单一的修改、单一的删除、单一的查询,所谓单一,是指要么是新增,要么删除,自动化接口不能即删除、又新增。(目前笔者正在实现该复合接口)
什么意思呢?这种场景自己写接口,复用APIJSON的一些方法,把APIJSON的事务剥离出来,自己代码控制事务,然后在代码中,根据自己的业务场景,调用APIJSON的增删改查
具体实现方式,我是重写了AbstractParser.parseResponse方法,仅仅把该方法的事务注释了,然后,单独写了,事务开始、提交、回滚、关闭的方法,可以由代码来控制事务。如下:
import apijson.*;
import apijson.server.*;
import apijson.server.JSONRequest;
import com.alibaba.fastjson.JSONObject;
import com.smart.cloud.authentication.server.function.RemoteFunctionInstance;
import java.sql.SQLException;
import java.sql.Savepoint;
import java.util.HashMap;
/**请求解析器
* @author Lemon
*/
public class ExtendRequestParser extends AbstractParser {
public ExtendRequestParser() {
super();
}
public ExtendRequestParser(RequestMethod method) {
super(method);
}
public ExtendRequestParser(RequestMethod method, boolean noVerify) {
super(method, noVerify);
}
@Override
public Verifier createVerifier() {
return new InvalidVerifier();
}
@Override
public MYSQLConfig createSQLConfig() {
return new MYSQLConfig();
}
@Override
public MYSQLExecutor createSQLExecutor() {
return new MYSQLExecutor();
}
private RemoteFunctionInstance function;
@Override
public Object onFunctionParse(JSONObject jsonObject, String fun) throws Exception {
if (function == null) {
function = new RemoteFunctionInstance(requestMethod, tag, version);
}
return function.invoke(function,jsonObject,fun);
}
@Override
public ObjectParser createObjectParser(JSONObject request, String parentPath, String name, SQLConfig arrayConfig, boolean isSubquery) throws Exception {
return new RequestObjectParser(request, parentPath, name, arrayConfig, isSubquery) {
//TODO 删除,onPUTArrayParse改用MySQL函数JSON_ADD, JSON_REMOVE等
}.setMethod(requestMethod).setParser(this);
}
@Override
public int getMaxQueryCount() {
return 1000;
}
@Override
public int getMaxObjectCount() {
return 200;
}
/**重写(扩展)解析请求json,获取对应结果(去掉事务控制,事务控制由单独的方法调用,方便外部复用)
* @param request
* @return requestObject
*/
@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());
if (RequestMethod.isPublicMethod(requestMethod) == false) {
try {
if (noVerifyLogin == false) {
onVerifyLogin();
}
if (noVerifyContent == false) {
onVerifyContent();
}
} catch (Exception e) {
return extendErrorResult(requestObject, e);
}
}
//必须在parseCorrectRequest后面,因为parseCorrectRequest可能会添加 @role
if (noVerifyRole == false && globleRole == null) {
try {
setGlobleRole(RequestRole.get(requestObject.getString(JSONRequest.KEY_ROLE)));
requestObject.remove(JSONRequest.KEY_ROLE);
} catch (Exception e) {
return extendErrorResult(requestObject, e);
}
}
try {
setGlobleFormat(requestObject.getBoolean(JSONRequest.KEY_FORMAT));
setGlobleDatabase(requestObject.getString(JSONRequest.KEY_DATABASE));
setGlobleSchema(requestObject.getString(JSONRequest.KEY_SCHEMA));
setGlobleExplain(requestObject.getBoolean(JSONRequest.KEY_EXPLAIN));
setGlobleCache(requestObject.getString(JSONRequest.KEY_CACHE));
requestObject.remove(JSONRequest.KEY_FORMAT);
requestObject.remove(JSONRequest.KEY_DATABASE);
requestObject.remove(JSONRequest.KEY_SCHEMA);
requestObject.remove(JSONRequest.KEY_EXPLAIN);
requestObject.remove(JSONRequest.KEY_CACHE);
} catch (Exception e) {
return extendErrorResult(requestObject, e);
}
final String requestString = JSON.toJSONString(request);//request传进去解析后已经变了
queryResultMap = new HashMap();
Exception error = null;
//sqlExecutor = createSQLExecutor();
//onBegin();
try {
//queryDepth = 0;
setQueryDepth(0);
requestObject = onObjectParse(request, null, null, null, false);
//onCommit();
}
catch (Exception e) {
e.printStackTrace();
error = e;
throw new RuntimeException(e.getCause());
//onRollback();
}
requestObject = error == null ? extendSuccessResult(requestObject) : extendErrorResult(requestObject, error);
JSONObject res = (globleFormat != null && globleFormat) && JSONResponse.isSuccess(requestObject) ? new JSONResponse(requestObject) : requestObject;
long endTime = System.currentTimeMillis();
long duration = endTime - startTime;
if (Log.DEBUG) { //用 | 替代 /,避免 APIJSON ORM,APIAuto 等解析路径错误
requestObject.put("sql:generate|cache|execute|maxExecute", sqlExecutor.getGeneratedSQLCount() + "|" + sqlExecutor.getCachedSQLCount() + "|" + sqlExecutor.getExecutedSQLCount() + "|" + getMaxSQLCount());
requestObject.put("depth:count|max", getQueryDepth() + "|" + getMaxQueryDepth());
requestObject.put("time:start|duration|end", startTime + "|" + duration + "|" + endTime);
}
//onClose();
//会不会导致原来的session = null? session = null;
if (Log.DEBUG) {
Log.d(TAG, "\n\n\n\n<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n "
+ requestMethod + "/parseResponse request = \n" + requestString + "\n\n");
Log.d(TAG, "parseResponse return response = \n" + JSON.toJSONString(requestObject)
+ "\n >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> \n\n\n");
}
Log.d(TAG, "parseResponse endTime = " + endTime + "; duration = " + duration
+ ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n\n\n");
return res;
}
public void onBegin() {
sqlExecutor = createSQLExecutor();
super.onBegin();
}
public void onCommit() {
super.onCommit();
}
public void onRollback() {
super.onRollback();
}
public void onClose() {
this.close();
this.sqlExecutor = null;
if(this.queryResultMap!=null){this.queryResultMap.clear();}
this.queryResultMap = null;
}
public JSONObject extendParseResponse(JSONObject request){
JSONObject response = this.parseResponse(request);
this.clear();
return response;
}
private void clear(){
this.queryResultMap.clear();
this.queryResultMap = null;
}
}
具体怎么使用呢?
下边我们来看看【确认订单】接口
//4、创建订单
ExtendRequestParser extendRequestParser = new ExtendRequestParser(PUT,true);
extendRequestParser.onBegin(); //事务开始
try{
//4-1、减库存(减班次库存)
JSONObject updateCarFreq = new JSONObject();
//...一些json业务配置
extendRequestParser.setMethod(PUT);
//执行业务
JSONObject updateCarFreqResponse = extendRequestParser.extendParseResponse(updateCarFreq);
//4-2、创建促销信息(如果不存在)
if(spellGroup!=null){ //首先得订单参与了促销
if(orderPromotionInfoId==null){ //促销信息还未创建,则新建
JSONObject createPromotion = new JSONObject();
//...一些json业务配置
extendRequestParser.setMethod(POST);
//执行业务
JSONObject createPromotionResponse = extendRequestParser.extendParseResponse(createPromotion);
}
//减促销(拼团)库存
JSONObject updateSpellGroup = new JSONObject();
//...一些json业务配置
extendRequestParser.setMethod(PUT);
JSONObject updateSpellGroupResponse = extendRequestParser.extendParseResponse(updateSpellGroup);
}
//4-3、创建订单
Integer orderId;
JSONObject createOrder = new JSONObject();
//...一些json业务配置
extendRequestParser.setMethod(POST);
JSONObject createOrderResponse = extendRequestParser.extendParseResponse(createOrder);
//4-4、创建订单班次(如果不存在)
if(!isExistOrderCarFreq){
JSONObject createOrderCarFreq = new JSONObject();
//...一些json业务配置
extendRequestParser.setMethod(POST);
JSONObject createOrderCarFreqResponse = extendRequestParser.extendParseResponse(createOrderCarFreq);
}
//4-5、创建订单站点
JSONObject createOrderSite = new JSONObject();
//...一些json业务配置
extendRequestParser.setMethod(POST);
JSONObject createOrderSiteResponse = extendRequestParser.extendParseResponse(createOrderSite);
//4-6、创建订单乘客信息
JSONObject createPassengerInfo = new JSONObject();
//...一些json业务配置
extendRequestParser.setMethod(POST);
JSONObject createPassengerInfoResponse = extendRequestParser.extendParseResponse(createPassengerInfo);
//4-7、支付下单(第三方支付)(这不是本文的重点,忽略)
PayOrderVo payOrderVo = new PayOrderVo();
//.....
Map unifiedOrderResult= unifiedOrder(payOrderVo);
//4-8、缓存支付签名(这不是本文的重点,忽略)
SignUtil signUtil = new SignUtil();
Map paramMap = new HashMap<>();
//......
System.out.println("签名字符串==="+paySign);
paramMap.put("paySign",paySign);
long expireTime = System.currentTimeMillis()+payTimeOut;
paramMap.put("expireTime",String.valueOf(expireTime));
//redis缓存
redisCache.set("order:"+orderId,JSON.toJSONString(paramMap));
//4-9、添加定时任务执行,订单支付超时处理
CronTask.addOrderTask(orderId,expireTime);
extendRequestParser.onCommit(); //事务提交
return Result.createSuccessResult(orderId);
} catch (Exception e){
e.printStackTrace();
extendRequestParser.onRollback(); //事务回滚
System.out.println("=======================订单创建失败,执行回滚!==================");
throw new RuntimeException("订单创建失败,服务器内部错误!");
} finally {
extendRequestParser.onClose(); //连接关闭
}
该部分作者已更新了文档,更多见APIJSON操作方法
接口:delete
请求
{
"Moment": {
"id{}": [1,2],
"tag": "Moment"
}
接口:post
请求
{
"Moment[]": [{
"id": 1, //一般后台自动生成id,无需请求传
"content": "这是测试内容"
},{
"id": 2,//一般后台自动生成id,无需请求传
"content": "这是测试内容"
}],
"tag": "Moment[]"
}
接口:put
请求
{
"Moment[]": [{
"id": 1,
"content": "这是测试内容"
},{
"id": 2,
"content": "这是测试内容"
}],
"tag": "Moment[]"
}
见APIAuto
无法使用,试试APIAuto8000
对了,测试的账号有:13000082001,密码:123456
未完待续…