基于Redux思想與RxJava的SpringMVC中Controller的代碼風格實踐
在筆者之前關于RARF的描述中,曾提及基于MVC風格的業務模塊代碼架構中存在的一些問題。彼時筆者推崇的是基于URFP的鏈式邏輯組織,換言之,一個完整的業務邏輯有數個ResourceHandler鏈接完成。但是在實踐中這種方式并不是適用于全部的情況,很多時候,從一個正常的思維角度來說,我們還是習慣于去寫 面條式 的代碼,即在一個Controller中通過調用多個Service進行一條業務邏輯線的處理,本文筆者即是在編寫這種面條式的邏輯代碼的前提下,自己思索的一些實踐。
筆者并沒有評估過本文這種實現所引起的性能損耗,大概是因為JVM調試分析的能力太渣了吧。另一方面,本文很有可能矯枉過正,所以權當一樂。
Overview
為了能有一個大概的印象,我們以一個簡單的登錄注冊邏輯作為示范,整個流程中只有兩條主要的邏輯線,大概如下圖所示:
然后我們直接看最終的Controller的寫法:
@RequestMapping("/login/{verifyCode}")
String login(HttpServletRequest request, @PathVariable("verifyCode") String verifyCode) {
//初始化上下文
SyncContext syncContext = new SyncContext();
syncContext
//判斷用戶提交的信息是否有效
.requestHandler((uniResourceBag, action) -> {
JSONObject requestData = UniResourceBag.parseRequestData(request, "username", "password");
return new Action("RequestDataReady")
.addActionData("username", requestData.getString("username"))
.addActionData("password", requestData.getString("password"));
})
.step("請求處理")
.reducer("判斷用戶是否存在", (uniResourceBag, action) -> {
//判斷接收到的類型是否為當前需要處理的類型,如果不是則忽略
if (!action.isType("RequestDataReady")) {
return new Action();
}
String username = (String) action.getActionDataOrDefault("username");
String password = (String) action.getActionDataOrDefault("password");
//如果用戶名為chevalier,則認為是存在的
if (username.equals("chevalier")) {
return new Action("doLogin").addActionData("username", username).addActionData("password", password);
} else if (username.equals("error")) {
throw new NullPointerException("error");
} else {
//否則創建新用戶
return new Action("doRegister").addActionData("username", username).addActionData("password", password);
}
})
.step("執行登錄操作或者創建新用戶")
.reducer("對于已存在用戶進行登錄操作,返回user_token", (uniResourceBag, action) -> {
if (!action.isType("doLogin")) {
return new Action();
}
String username = (String) action.getActionDataOrDefault("username");
String password = (String) action.getActionDataOrDefault("password");
//將username+password連接作為user_token登錄
uniResourceBag.setRequestDataWhenSuccess("user_token", UUID.randomUUID());
return new Action("Complete");
})
.reducer("對于新用戶判斷用戶名是否存在,返回創建好的信息與user_token", (uniResourceBag, action) -> {
if (!action.isType("doRegister")) {
return new Action();
}
//返回用戶名,用戶密碼,創建時間和用戶Token
String username = (String) action.getActionDataOrDefault("username");
String password = (String) action.getActionDataOrDefault("password");
//將username+password連接作為user_token登錄
uniResourceBag.setRequestDataWhenSuccess(
"username", username,
"password", password,
"create_time", Instant.now().toEpochMilli(),
"user_token", UUID.randomUUID());
return new Action("Complete");
})
.responseHandler((uniResourceBag, action) -> {
//進行最后的處理
return new Action();
});
return syncContext
.getUniResourceBag()
.getResponseString();
}
上面就是完成該流程的整個的代碼,可以看出整個流程由step劃分為了數個層次,這也是借鑒了軟件設計中的分層模型,一般來說,每次請求只會針對一條邏輯線,即是每一個Step中只有一個Reducer會被調用。同一個Step中的不同的Reducer往往之間是不同的條件選擇,而不同Step之間只會用Action進行溝通。但是整個流程還是以面條式的代碼實現,方便理解。我們再看下整個的請求與響應效果:
(1)正常登錄請求:login/1?requestData={"username":"chevalier","password":123}
{
"code": 0,
"subCode": 0,
"runtimeLog":
{
"Context Uptime": 2,
"steps":
[
{
"actualReducerDesc": "RequestHandler",
"stepDesc": "RequestHandler",
"stepId": 0,
"actualReducerAction":
{
"actionType": "RequestDataReady",
"actionData":
{
"password": "123",
"username": "chevalier"
}
},
"stepRunTime": 0,
"actualReducerUUID": "2608550a-6550-40da-84aa-a6a5b68bb93d"
},
{
"actualReducerDesc": "判斷用戶是否存在",
"stepDesc": "請求處理",
"stepId": 1,
"actualReducerAction":
{
"actionType": "doLogin",
"actionData":
{
"password": "123",
"username": "chevalier"
}
},
"stepRunTime": 0,
"actualReducerUUID": "92277976-bc24-4f69-b286-a4c95be993ac"
},
{
"actualReducerDesc": "對于已存在用戶進行登錄操作,返回user_token",
"stepDesc": "執行登錄操作或者創建新用戶",
"stepId": 2,
"actualReducerAction":
{
"actionType": "Complete",
"actionData":
{
}
},
"stepRunTime": 0,
"actualReducerUUID": "f21b1a80-9b86-49d7-9751-5bdc6d90c355"
}
]
},
"user_token": "499206a2-0130-484b-82e0-2b822c3b8dfa",
"desc": "Success"
}
(2)正常注冊請求:login/1?requestData={"username":"123","password":123}
{
"password": "123",
"code": 0,
"create_time": 1461944838909,
"subCode": 0,
"runtimeLog":
{
"Context Uptime": 0,
"steps":
[
{
"actualReducerDesc": "RequestHandler",
"stepDesc": "RequestHandler",
"stepId": 0,
"actualReducerAction":
{
"actionType": "RequestDataReady",
"actionData":
{
"password": "123",
"username": "123"
}
},
"stepRunTime": 0,
"actualReducerUUID": "c3e3040d-6ff4-41a7-b911-dbc31de6c6dd"
},
{
"actualReducerDesc": "判斷用戶是否存在",
"stepDesc": "請求處理",
"stepId": 1,
"actualReducerAction":
{
"actionType": "doRegister",
"actionData":
{
"password": "123",
"username": "123"
}
},
"stepRunTime": 0,
"actualReducerUUID": "a5ea22dd-41c4-46bc-9d14-f60c8f6a737d"
},
{
"actualReducerDesc": "對于新用戶判斷用戶名是否存在,返回創建好的信息與user_token",
"stepDesc": "執行登錄操作或者創建新用戶",
"stepId": 2,
"actualReducerAction":
{
"actionType": "Complete",
"actionData":
{
}
},
"stepRunTime": 0,
"actualReducerUUID": "eb06837e-03b3-4812-9c2d-ff86a4d795a0"
}
]
},
"user_token": "6866cf13-ee06-4333-831a-dde0f2114764",
"username": "123",
"desc": "Success"
}
(3)我在隨機一個地方拋出了一個異常:
{
"code": -1008,
"runtimeLog":
{
"Context Uptime": 5,
"steps":
[
{
"actualReducerDesc": "RequestHandler",
"stepDesc": "RequestHandler",
"stepId": 0,
"actualReducerAction":
{
"actionType": "RequestDataReady",
"actionData":
{
"password": "123",
"username": "error"
}
},
"stepRunTime": 0,
"actualReducerUUID": "50cb7b43-cc09-4491-8650-3294903fd6e6"
},
{
"stepDesc": "請求處理",
"stepId": 1,
"stepRunTime": -1461944917734
},
{
"stepDesc": "執行登錄操作或者創建新用戶",
"stepId": 2,
"stepRunTime": 0
}
]
},
"stackTrace": "error
wx.controller.user.LoginController.lambda$login$13(LoginController.java:67)
wx.controller.user.LoginController$$Lambda$215/1765146717.doWork(Unknown Source)
wx.rarf.context.SyncContext.lambda$null$21(SyncContext.java:304)
wx.rarf.context.SyncContext$$Lambda$220/1322867488.call(Unknown Source)
rx.Observable.unsafeSubscribe(Observable.java:7507)
rx.internal.operators.OperatorMerge$MergeSubscriber.handleNewSource(OperatorMerge.java:215)
rx.internal.operators.OperatorMerge$MergeSubscriber.onNext(OperatorMerge.java:185)
rx.internal.operators.OperatorMerge$MergeSubscriber.onNext(OperatorMerge.java:120)
rx.internal.operators.OperatorMap$1.onNext(OperatorMap.java:55)
rx.internal.operators.OperatorSingle$ParentSubscriber.onCompleted(OperatorSingle.java:124)
rx.internal.operators.OperatorTake$1.onNext(OperatorTake.java:69)
rx.internal.operators.OperatorMerge$InnerSubscriber.emit(OperatorMerge.java:676)
rx.internal.operators.OperatorMerge$InnerSubscriber.onNext(OperatorMerge.java:586)
wx.rarf.context.SyncContext.lambda$requestHandler$18(SyncContext.java:93)
wx.rarf.context.SyncContext$$Lambda$214/1827551503.call(Unknown Source)
rx.Observable.unsafeSubscribe(Observable.java:7507)
rx.internal.operators.OperatorMerge$MergeSubscriber.handleNewSource(OperatorMerge.java:215)
rx.internal.operators.OperatorMerge$MergeSubscriber.onNext(OperatorMerge.java:185)
rx.internal.operators.OperatorMerge$MergeSubscriber.onNext(OperatorMerge.java:120)
rx.internal.operators.OnSubscribeFromIterable$IterableProducer.request(OnSubscribeFromIterable.java:98)
rx.Subscriber.setProducer(Subscriber.java:177)
rx.internal.operators.OnSubscribeFromIterable.call(OnSubscribeFromIterable.java:50)
rx.internal.operators.OnSubscribeFromIterable.call(OnSubscribeFromIterable.java:33)
rx.Observable$1.call(Observable.java:144)
rx.Observable$1.call(Observable.java:136)
rx.Observable$1.call(Observable.java:144)
rx.Observable$1.call(Observable.java:136)
rx.Observable$1.call(Observable.java:144)
rx.Observable$1.call(Observable.java:136)
rx.Observable$1.call(Observable.java:144)
rx.Observable$1.call(Observable.java:136)
rx.Observable$1.call(Observable.java:144)
rx.Observable$1.call(Observable.java:136)
rx.Observable.unsafeSubscribe(Observable.java:7507)
rx.internal.operators.OperatorMerge$MergeSubscriber.handleNewSource(OperatorMerge.java:215)
rx.internal.operators.OperatorMerge$MergeSubscriber.onNext(OperatorMerge.java:185)
rx.internal.operators.OperatorMerge$MergeSubscriber.onNext(OperatorMerge.java:120)
rx.internal.operators.OnSubscribeFromIterable$IterableProducer.request(OnSubscribeFromIterable.java:98)
rx.Subscriber.setProducer(Subscriber.java:177)
rx.internal.operators.OnSubscribeFromIterable.call(OnSubscribeFromIterable.java:50)
rx.internal.operators.OnSubscribeFromIterable.call(OnSubscribeFromIterable.java:33)
rx.Observable$1.call(Observable.java:144)
rx.Observable$1.call(Observable.java:136)
rx.Observable$1.call(Observable.java:144)
rx.Observable$1.call(Observable.java:136)
rx.Observable$1.call(Observable.java:144)
rx.Observable$1.call(Observable.java:136)
rx.Observable$1.call(Observable.java:144)
rx.Observable$1.call(Observable.java:136)
rx.Observable$1.call(Observable.java:144)
rx.Observable$1.call(Observable.java:136)
rx.Observable.unsafeSubscribe(Observable.java:7507)
rx.internal.operators.OperatorMerge$MergeSubscriber.handleNewSource(OperatorMerge.java:215)
rx.internal.operators.OperatorMerge$MergeSubscriber.onNext(OperatorMerge.java:185)
rx.internal.operators.OperatorMerge$MergeSubscriber.onNext(OperatorMerge.java:120)
rx.internal.operators.OnSubscribeFromIterable$IterableProducer.request(OnSubscribeFromIterable.java:98)
rx.Subscriber.setProducer(Subscriber.java:177)
rx.internal.operators.OnSubscribeFromIterable.call(OnSubscribeFromIterable.java:50)
rx.internal.operators.OnSubscribeFromIterable.call(OnSubscribeFromIterable.java:33)
rx.Observable$1.call(Observable.java:144)
rx.Observable$1.call(Observable.java:136)
rx.Observable$1.call(Observable.java:144)
rx.Observable$1.call(Observable.java:136)
rx.Observable$1.call(Observable.java:144)
rx.Observable$1.call(Observable.java:136)
rx.Observable$1.call(Observable.java:144)
rx.Observable$1.call(Observable.java:136)
rx.Observable$1.call(Observable.java:144)
rx.Observable$1.call(Observable.java:136)
rx.Observable$1.call(Observable.java:144)
rx.Observable$1.call(Observable.java:136)
rx.Observable.subscribe(Observable.java:7597)
rx.observables.BlockingObservable.blockForSingle(BlockingObservable.java:442)
rx.observables.BlockingObservable.first(BlockingObservable.java:169)
wx.rarf.context.SyncContext.responseHandler(SyncContext.java:172)
wx.controller.user.LoginController.login(LoginController.java:115)
sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
java.lang.reflect.Method.invoke(Method.java:497)
org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:221)
org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:136)
org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:110)
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:817)
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:731)
org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:85)
org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:959)
org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:893)
org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:968)
org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:859)
javax.servlet.http.HttpServlet.service(HttpServlet.java:687)
org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:844)
javax.servlet.http.HttpServlet.service(HttpServlet.java:790)
org.eclipse.jetty.servlet.ServletHolder.handle(ServletHolder.java:812)
org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1669)
org.eclipse.jetty.websocket.server.WebSocketUpgradeFilter.doFilter(WebSocketUpgradeFilter.java:224)
org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1652)
org.springframework.boot.actuate.autoconfigure.EndpointWebMvcAutoConfiguration$ApplicationContextHeaderFilter.doFilterInternal(EndpointWebMvcAutoConfiguration.java:237)
org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1652)
wx.application.filter.SystemFilter.doFilter(SystemFilter.java:95)
org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1652)
org.springframework.boot.actuate.trace.WebRequestTraceFilter.doFilterInternal(WebRequestTraceFilter.java:112)
org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1652)
org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:99)
org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1652)
org.springframework.web.filter.HttpPutFormContentFilter.doFilterInternal(HttpPutFormContentFilter.java:87)
org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1652)
org.springframework.web.filter.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:77)
org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1652)
org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:121)
org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1652)
org.springframework.boot.actuate.autoconfigure.MetricsFilter.doFilterInternal(MetricsFilter.java:103)
org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1652)
org.eclipse.jetty.servlet.ServletHandler.doHandle(ServletHandler.java:585)
org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:143)
org.eclipse.jetty.security.SecurityHandler.handle(SecurityHandler.java:577)
org.eclipse.jetty.server.session.SessionHandler.doHandle(SessionHandler.java:223)
org.eclipse.jetty.server.handler.ContextHandler.doHandle(ContextHandler.java:1127)
org.eclipse.jetty.servlet.ServletHandler.doScope(ServletHandler.java:515)
org.eclipse.jetty.server.session.SessionHandler.doScope(SessionHandler.java:185)
org.eclipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:1061)
org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:141)
org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:97)
org.eclipse.jetty.server.Server.handle(Server.java:499)
org.eclipse.jetty.server.HttpChannel.handle(HttpChannel.java:311)
org.eclipse.jetty.server.HttpConnection.onFillable(HttpConnection.java:257)
org.eclipse.jetty.io.AbstractConnection$2.run(AbstractConnection.java:544)
org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:635)
org.eclipse.jetty.util.thread.QueuedThreadPool$3.run(QueuedThreadPool.java:555)
java.lang.Thread.run(Thread.java:745)
",
"desc": "內部錯誤"
}
Features & Motivation
首先,我們需要考慮現在的問題是什么,以及改進的目標是什么。筆者最早萌生出要做出一些改進的動因,是看到了下面這一個Controller,因為代碼過多,如果有礙閱讀可以直接拉到下面看。一個Controller寫了600多行,這絕不是一件令人驕傲的事情,這種代碼的可讀性、可維護性基本上為零。而這種代碼的產生,正如筆者在RARF中分析的,并不一定是因為程序猿的能力和整體代碼規范的問題。下面的這個代碼還算是規范的吧(筆者已經刪去了很多的注釋),但是因為在一個快速演進迭代地業務系統中,我們不可避免的要以打補丁的方式來修正邏輯。但是打補丁的可能后果之一就是因為在一條業務邏輯線中的分支過多而導致N層的內嵌選擇。最佳的方式,肯定是在系統規劃之初就把這個邏輯線再細分一下,但是PM說好的不會改需求呢?
@RequestMapping("doShareCreateWithAt")
public void doShareCreateWithAt(HttpServletRequest request, PrintWriter out)
throws Exception {
HJSONObject rtn = new HJSONObject();
int code = ErrorConfig.CODE_SUCCESS;
String desc = "返回成功";
int subCode = -1;
HJSONObject json = new HJSONObject();
String user_id = null;
String share_id = "10";//默認的分享創建成功之后
try {
//鑒權:判斷用戶Token是否有效
json = requestHandler(request, new String[]{"user_token", "share_img_path"});
String user_token = json.getString("user_token");
user_id = userTokenServiceImpl.queryUserIdByToken(user_token);
if (user_id == null) {
code = ErrorConfig.CODE_1006;
subCode = 1;
desc = "[" + code + "]+" + "[" + subCode + "],用戶鑒權失敗";
errorInfo.setErrorInfo(code, subCode, desc, request, out);
return;
} else {
//提取分享的基本信息
String share_category = json.getString("share_category", "0");
String share_description = json.getString("share_description", "");
String share_img_path = json.getString("share_img_path");
String share_city = json.getString("share_city", "0");
String share_county = json.getString("share_county", "0");
String share_lon = json.getString("share_lon", "0.0");
String share_lat = json.getString("share_lat", "0.0");
//活動IDs
String activity_ids = json.getString("activity_ids", "");
//群組IDs
String group_ids = json.getString("group_ids", "");
//用戶IDs
String user_ids = json.getString("user_ids", "");
String share_img_path_with_lables = json.getString("share_img_path_with_lables", "");
String share_img_path_with_pasters = json.getString("share_img_path_with_pasters", "");
String share_location = json.getString("share_location", "");
String sport_ids = json.getString("sport_ids", "");
//這個是抽出來的貼紙信息
String share_paster_ids = "";
HashMap<String, String> activityMap = null;
HashMap<String, String> groupMap = null;
//檢查活動的參數-Json數組-只允許@一個活動
if (StringUtil.isBlank(activity_ids) || StringUtil.isEmpty(activity_ids)) {
activity_ids = null;
} else {
if (!isJSONArray(activity_ids)) {
code = ErrorConfig.CODE_1006;
subCode = 2;
desc = "[" + code + "]+" + "[" + subCode + "],activity_ids格式不符合";
errorInfo.setErrorInfo(code, subCode, desc, request, out);
return;
//throw new RequestException(code, subCode,desc);
} else {
JSONArray activityIds = JSONArray.parseArray(activity_ids);
//獲取傳來的id的個數
int idsSize = activityIds.size();
if (idsSize != 1) {
code = ErrorConfig.CODE_1005;
subCode = 3;
desc = "[" + code + "]+" + "[" + subCode + "],一條分享只允許@一個活動";
errorInfo.setErrorInfo(code, subCode, desc, request, out);
return;
//throw new RequestException(code, subCode,desc);
} else {
//目前只讓@一個活動
//鑒別活動id
String activity_id = activityIds.getString(0);
String group_id = null;
if (StringUtil.isBlank(activity_id) || StringUtil.isEmpty(activity_id)) {
code = ErrorConfig.CODE_1005;
subCode = 4;
desc = "[" + code + "]+" + "[" + subCode + "],activity_id有誤,活動不存在";
errorInfo.setErrorInfo(code, subCode, desc, request, out);
return;
//throw new RequestException(code, subCode,desc);
} else {
Map<String, Object> groupInfo = socialActivityServiceImpl.queryActivityByIdV(activity_id);
if (groupInfo == null) {
code = ErrorConfig.CODE_1005;
subCode = 4;
desc = "[" + code + "]+" + "[" + subCode + "],activity_id有誤,活動不存在";
errorInfo.setErrorInfo(code, subCode, desc, request, out);
return;
//throw new RequestException(code, subCode,desc);
} else {
//活動存在,看看是不是官方活動?官方活動沒有群:group_id為空
if (groupInfo.get("group_id") != null)
group_id = groupInfo.get("group_id").toString();
activityMap = new HashMap<String, String>();
activityMap.put("group_id", null);//默認放個null進去
//查看用戶是否已經參加了該活動:1-已參加、-1-未參加、0-已退出
String attendState = socialActivityServiceImpl.queryUserAttendStateV(user_id, activity_id);
if (!attendState.equals("1")) {
activityMap.put("hasAttend", "0");
} else {
activityMap.put("hasAttend", "1");
}
if (StringUtil.isBlank(group_id) || StringUtil.isEmpty(group_id)) {
//官方活動,參加不參加都能@,沒參加的先參加
//activity_ids = activity_id;
activityMap.put("isOfficial", "1");
} else {
//非官方活動,看參加沒,沒參加不能@
//TODO-直接參加?
activityMap.put("isOfficial", "0");
if (!attendState.equals("1")) {
code = ErrorConfig.CODE_1006;
subCode = 5;
desc = "[" + code + "]+" + "[" + subCode + "],非官方活動,沒有參加不能@";
errorInfo.setErrorInfo(code, subCode, desc, request, out);
return;
//throw new RequestException(code, subCode,desc);
} else {
//參加了讓@
//activity_ids = activity_id;
activityMap.put("group_id", group_id);
}
}
//TODO-兩個?
activityMap.put("activity_id", activity_id);
activityMap.put("activity_ids", activity_ids);
}
}
}
}
}
//檢查群組的參數-Json-只允許@一個群組
if (StringUtil.isBlank(group_ids) || StringUtil.isEmpty(group_ids)) {
group_ids = null;
} else {
if (!isJSONArray(group_ids)) {
code = ErrorConfig.CODE_1006;
subCode = 6;
desc = "[" + code + "]+" + "[" + subCode + "],group_ids格式不符合";
errorInfo.setErrorInfo(code, subCode, desc, request, out);
return;
//throw new RequestException(code, subCode,desc);
} else {
JSONArray groupIds = JSONArray.parseArray(group_ids);
//獲取傳來的id的個數
int idsSize = groupIds.size();
if (idsSize != 1) {
code = ErrorConfig.CODE_1005;
subCode = 7;
desc = "[" + code + "]+" + "[" + subCode + "],一條分享只允許@一個群組";
errorInfo.setErrorInfo(code, subCode, desc, request, out);
return;
//throw new RequestException(code, subCode,desc);
} else {
//目前只讓@一個群組
//看看群組存在么
String group_id = groupIds.getString(0);
groupMap = new HashMap<String, String>();
if (StringUtil.isBlank(group_id) || StringUtil.isEmpty(group_id)) {
code = ErrorConfig.CODE_1005;
subCode = 8;
desc = "[" + code + "]+" + "[" + subCode + "],group_id有誤,群組不存在";
errorInfo.setErrorInfo(code, subCode, desc, request, out);
return;
//throw new RequestException(code, subCode,desc);
} else {
//根據群組ID獲取群組詳情
Map<String, String> groupInfo = socialGroupServiceImpl.queryGroupInfo(group_id);
if (groupInfo == null) {
code = ErrorConfig.CODE_1005;
subCode = 8;
desc = "[" + code + "]+" + "[" + subCode + "],group_id有誤,群組不存在";
errorInfo.setErrorInfo(code, subCode, desc, request, out);
return;
//throw new RequestException(code, subCode,desc);
} else {
//群組存在,看看參加沒有
boolean isJoin = socialGroupServiceImpl.queryIdByGroupAndUserIdV(group_id, user_id);
if (isJoin == false) {
code = ErrorConfig.CODE_1005;
subCode = 9;
desc = "[" + code + "]+" + "[" + subCode + "],@了未加入的群組";
errorInfo.setErrorInfo(code, subCode, desc, request, out);
return;
//throw new RequestException(code, subCode,desc);
} else {
//group_ids = group_id;
groupMap.put("group_ids", group_ids);//這是存的,為了不該群相冊接口,還是存一個array,以后如果可以關聯多個群組什么的也可以
groupMap.put("group_id", group_id);//這是拿到service層做比較的,不用再從array轉化一次
}
}
}
}
}
}
//@用戶
if (StringUtil.isBlank(user_ids) || StringUtil.isEmpty(user_ids)) {
user_ids = null;
} else {
if (!isJSONArray(user_ids)) {
code = ErrorConfig.CODE_1005;
subCode = 10;
desc = "[" + code + "]+" + "[" + subCode + "],user_ids不符合";
errorInfo.setErrorInfo(code, subCode, desc, request, out);
return;
//throw new RequestException(code, subCode,desc);
} else {
JSONArray userIds = JSONArray.parseArray(user_ids);
//獲取傳來的id的個數
int idsSize = userIds.size();
if (idsSize < 1 || idsSize > 3) {
code = ErrorConfig.CODE_1005;
subCode = 11;
desc = "[" + code + "]+" + "[" + subCode + "],一條分享只允許1-3個好友";
errorInfo.setErrorInfo(code, subCode, desc, request, out);
return;
//throw new RequestException(code, subCode,desc);
} else {
//看看重不重復
Set<String> userIdSet = new HashSet<String>();
for (int i = 0; i < idsSize; i++) {
userIdSet.add(userIds.getString(i));
}
if (idsSize != userIdSet.size()) {
code = ErrorConfig.CODE_1005;
subCode = 12;
desc = "[" + code + "]+" + "[" + subCode + "],一個好友只能@一次";
errorInfo.setErrorInfo(code, subCode, desc, request, out);
return;
//throw new RequestException(code, subCode,desc);
}
//TODO-批量查詢數據庫
//看看好友存在么
for (int i = 0; i < idsSize; i++) {
String userId = userIds.getString(i);
//存在性
boolean isUser = userServiceImpl.queryUserIdById(userId);
if (isUser == false) {
code = ErrorConfig.CODE_1005;
subCode = 13;
desc = "[" + code + "]+" + "[" + subCode + "],@了一個不存在的用戶";
errorInfo.setErrorInfo(code, subCode, desc, request, out);
return;
//throw new RequestException(code, subCode,desc);
}
//是否好友
boolean isAttention = socialFollowingServiceImpl.queryFollowingIdV(user_id, userId);
boolean isAttention1 = socialFollowingServiceImpl.queryFollowingIdV(userId, user_id);
if (isAttention == false || isAttention1 == false) {
code = ErrorConfig.CODE_1005;
subCode = 14;
desc = "[" + code + "]+" + "[" + subCode + "],@了一個不是好友的用戶";
errorInfo.setErrorInfo(code, subCode, desc, request, out);
return;
//throw new RequestException(code, subCode,desc);
}
}
}
}
}
//圖片標簽-"share_img_path_with_lables":[{"pic1_url":[]},{"pic2_url":[]},{"pic3_url":[]}]
//鍵key:存圖片的Url,值Value:存圖片對應的標簽
if (!"".equals(share_img_path_with_lables)) {
//判斷是否是json格式
if (!isJSONArray(share_img_path_with_lables)) {
code = ErrorConfig.CODE_1006;
subCode = 15;
desc = "[" + code + "]+" + "[" + subCode + "],請求參數格式不符,檢查share_img_path_with_lables";
errorInfo.setErrorInfo(code, subCode, desc, request, out);
return;
//throw new RequestException(code, subCode,desc);
}
//解析share_img_path_with_lables的值,真實的圖片,總共有9張圖片
JSONArray shareImgLableArr = JSONArray.parseArray(share_img_path_with_lables);
int urlSize = shareImgLableArr.size();
if (urlSize > 9) {
code = ErrorConfig.CODE_1005;
subCode = 16;
desc = "[" + code + "]+" + "[" + subCode + "],分享不能超過9張圖,檢查share_img_path_with_lables";
errorInfo.setErrorInfo(code, subCode, desc, request, out);
return;
//throw new RequestException(code, subCode,desc);
} else {
//遍歷,看看必填的參數是不是都填了。
//遍歷每個圖片附帶的標簽屬性
for (int i = 0; i < urlSize; i++) {
String shareImgLables = shareImgLableArr.getString(i);//"pic1_url":["":"","":""]
HJSONObject shareImgLablesJson = new HJSONObject(JSONObject.parseObject(shareImgLables));
//獲取第i張圖片的鍵key,存放的是pic_url的Url路徑,所以key的size為1
Set<String> keyset = shareImgLablesJson.keySet();
if (keyset.size() != 1) {
code = ErrorConfig.CODE_1006;
subCode = 15;
desc = "[" + code + "]+" + "[" + subCode + "],請求參數格式不符,檢查share_img_path_with_lables";
errorInfo.setErrorInfo(code, subCode, desc, request, out);
return;
//throw new RequestException(code, subCode,desc);
} else {
String url = keyset.iterator().next();
if (!isUrl(url)) {
code = ErrorConfig.CODE_1006;
subCode = 15;
desc = "[" + code + "]+" + "[" + subCode + "],請求參數格式不符,檢查share_img_path_with_lables";
errorInfo.setErrorInfo(code, subCode, desc, request, out);
return;
//throw new RequestException(code, subCode,desc);
}
//根據鍵key獲取對應的值value,其中存放的是圖片對應的標簽信息
String shareImgLablesStr = shareImgLablesJson.getString(url);
JSONArray shareImgLablesArr = JSONArray.parseArray(shareImgLablesStr);
int lableSize = shareImgLablesArr.size();
if (lableSize > 6) {
code = ErrorConfig.CODE_1005;
subCode = 17;
desc = "[" + code + "]+" + "[" + subCode + "],一張圖片不能超過6個標簽";
errorInfo.setErrorInfo(code, subCode, desc, request, out);
return;
//throw new RequestException(code, subCode,desc);
} else {
for (int j = 0; j < lableSize; j++) {
String shareImgLable = shareImgLablesArr.getString(j);
//標簽在圖片上的位置,還有牌子信息必填
HJSONObject shareImgLableJson = HJSONHandler(shareImgLable, new String[]{"offset_x", "offset_y", "brand", "direction"});
String offset_x = shareImgLableJson.getString("offset_x");
try {
Double.parseDouble(offset_x);
} catch (Exception e) {
code = ErrorConfig.CODE_1005;
subCode = 18;
desc = "[" + code + "]+" + "[" + subCode + "],非法的x偏移參數";
errorInfo.setErrorInfo(code, subCode, desc, request, out);
return;
//throw new RequestException(code, subCode,desc);
}
String offset_y = shareImgLableJson.getString("offset_y");
try {
Double.parseDouble(offset_y);
} catch (Exception e) {
code = ErrorConfig.CODE_1005;
subCode = 19;
desc = "[" + code + "]+" + "[" + subCode + "],非法的y偏移參數";
errorInfo.setErrorInfo(code, subCode, desc, request, out);
return;
//throw new RequestException(code, subCode,desc);
}
String direction = shareImgLableJson.getString("direction");
String brand = shareImgLableJson.getString("brand");
//這個需要一個brand數據表
String altitude = shareImgLableJson.getString("altitude", "");
String number = shareImgLableJson.getString("number", "");
String img_label_type = shareImgLableJson.getString("img_label_type", "");
shareImgLableJson.clear();
shareImgLableJson.put("offset_x", offset_x);
shareImgLableJson.put("offset_y", offset_y);
shareImgLableJson.put("brand", brand);
shareImgLableJson.put("altitude", altitude);
shareImgLableJson.put("number", number);
shareImgLableJson.put("img_label_type", img_label_type);
shareImgLableJson.put("direction", direction);
shareImgLablesArr.set(j, shareImgLableJson);
}
}
shareImgLablesJson.put(url, shareImgLablesArr);
}
shareImgLableArr.set(i, shareImgLablesJson);
share_img_path_with_lables = shareImgLableArr.toString();
}
}
}
//貼紙,類似標簽的數據結構, "share_paster_ids":[{"url1":["1","2"]},{"url2":["1","2"]}]
if (!"".equals(share_img_path_with_pasters)) {
//判斷是否是json格式
if (!isJSONArray(share_img_path_with_pasters)) {
code = ErrorConfig.CODE_1006;
subCode = 20;
desc = "[" + code + "]+" + "[" + subCode + "],請求參數格式不符,檢查share_img_path_with_pasters";
errorInfo.setErrorInfo(code, subCode, desc, request, out);
return;
//throw new RequestException(code, subCode,desc);
}
JSONArray shareImgPasterUrlsArr = JSONArray.parseArray(share_img_path_with_pasters);
Set<String> shareImgPasterIdsSet = new HashSet<String>();
int urlSize = shareImgPasterUrlsArr.size();
if (urlSize > 9) {
code = ErrorConfig.CODE_1005;
subCode = 21;
desc = "[" + code + "]+" + "[" + subCode + "],分享不能超過9張圖,檢查share_img_path_with_pasters";
errorInfo.setErrorInfo(code, subCode, desc, request, out);
return;
//throw new RequestException(code, subCode,desc);
} else {
for (int i = 0; i < urlSize; i++) {
String shareImgPasterIdsArrStr = shareImgPasterUrlsArr.getString(i);
HJSONObject shareImgPasterIdsArrJson = new HJSONObject(JSONObject.parseObject(shareImgPasterIdsArrStr));
Set<String> keyset = shareImgPasterIdsArrJson.keySet();
if (keyset.size() != 1) {
code = ErrorConfig.CODE_1006;
subCode = 20;
desc = "[" + code + "]+" + "[" + subCode + "],請求參數格式不符,檢查share_img_path_with_pasters";
errorInfo.setErrorInfo(code, subCode, desc, request, out);
return;
//throw new RequestException(code, subCode,desc);
} else {
String url = keyset.iterator().next();
if (!isUrl(url)) {
code = ErrorConfig.CODE_1006;
subCode = 20;
desc = "[" + code + "]+" + "[" + subCode + "],請求參數格式不符,檢查share_img_path_with_pasters";
errorInfo.setErrorInfo(code, subCode, desc, request, out);
return;
//throw new RequestException(code, subCode,desc);
}
String shareImgPasterIdsStr = shareImgPasterIdsArrJson.getString(url);
JSONArray shareImgPasterIdsArr = JSONArray.parseArray(shareImgPasterIdsStr);
int idsSize = shareImgPasterIdsArr.size();
if (idsSize > 5) {
code = ErrorConfig.CODE_1005;
subCode = 22;
desc = "[" + code + "]+" + "[" + subCode + "],一張圖不能超過5個貼紙";
errorInfo.setErrorInfo(code, subCode, desc, request, out);
return;
//throw new RequestException(code, subCode,desc);
} else {
for (int j = 0; j < idsSize; j++) {
String paster_id = shareImgPasterIdsArr.getString(j);
try {
int paster_id_int = Integer.parseInt(paster_id);
shareImgPasterIdsSet.add(String.valueOf(paster_id_int));
} catch (Exception e) {
e.printStackTrace();
subCode = 20;
desc = "[" + code + "]+" + "[" + subCode + "],請求參數格式不符,檢查share_img_path_with_pasters";
errorInfo.setErrorInfo(code, subCode, desc, request, out);
return;
//throw new RequestException(code, subCode,desc);
}
}
}
}
}
}
}
//運動類型 "sport_ids":["1"]-json-一個分享智能關聯一個運動類型
if (StringUtil.isBlank(sport_ids) || StringUtil.isEmpty(sport_ids)) {
sport_ids = null;
} else {
if (!isJSONArray(sport_ids)) {
code = ErrorConfig.CODE_1005;
subCode = 26;
desc = "[" + code + "]+" + "[" + subCode + "],sport_ids不符合";
errorInfo.setErrorInfo(code, subCode, desc, request, out);
return;
//throw new RequestException(code, subCode,desc);
} else {
JSONArray sportIds = JSONArray.parseArray(sport_ids);
//獲取傳來的id的個數
int idsSize = sportIds.size();
if (idsSize != 1) {
code = ErrorConfig.CODE_1005;
subCode = 27;
desc = "[" + code + "]+" + "[" + subCode + "],一條分享只關聯1個運動類型";
errorInfo.setErrorInfo(code, subCode, desc, request, out);
return;
//throw new RequestException(code, subCode,desc);
} else {
//看看運動類型存在么
for (int i = 0; i < idsSize; i++) {
String sportId = sportIds.getString(i);
//存在性
String sport_id = sportIds.getString(i);
try {
int sport_id_i = Integer.parseInt(sport_id);
Boolean is_user_sport_id_exist = userPersonServiceImpl.queryUserSportBySportId(String.valueOf(sport_id_i));
if (!is_user_sport_id_exist) {
throw new Exception();
}
sportIds.set(i, String.valueOf(sport_id_i));
} catch (Exception e) {
code = ErrorConfig.CODE_1005;
subCode = 28;
desc = "[" + code + "]+" + "[" + subCode + "],用戶運動類型有誤";
errorInfo.setErrorInfo(code, subCode, desc, request, out);
return;
//throw new RequestException(code, subCode,desc);
}
}
sport_ids = sportIds.toString();
}
}
}
if (!"".equals(share_location)) {
if (StringUtil.isBlank(share_location) || StringUtil.isEmpty(share_location)) {
code = ErrorConfig.CODE_1006;
subCode = 25;
desc = "[" + code + "]+" + "[" + subCode + "],請檢查share_location是否為空";
errorInfo.setErrorInfo(code, subCode, desc, request, out);
return;
//throw new RequestException(code, subCode,desc);
}
}
if (StringUtil.isBlank(share_img_path) || StringUtil.isEmpty(share_img_path) || Integer.parseInt(share_category) < 0 || Integer.parseInt(share_category) > 1 || !isJSONArray(share_img_path)) {
code = ErrorConfig.CODE_1006;
subCode = 23;
desc = "[" + code + "]+" + "[" + subCode + "],請求參數格式不符,檢查share_img_path和share_category";
errorInfo.setErrorInfo(code, subCode, desc, request, out);
return;
//throw new RequestException(code, subCode,desc);
} else {
if (isJsonEmpty(share_img_path)) {
code = ErrorConfig.CODE_1006;
subCode = 23;
desc = "[" + code + "]+" + "[" + subCode + "],請求參數格式不符,檢查share_img_path和share_category";
throw new RequestException(code, subCode, desc);
}
JSONArray share_img_path_json = JSONArray.parseArray(share_img_path);
//獲取傳來的id的個數
int idsSize = share_img_path_json.size();
if (idsSize > 9) {
code = ErrorConfig.CODE_1005;
subCode = 29;
desc = "[" + code + "]+" + "[" + subCode + "],一條分享最多關聯9張圖片";
errorInfo.setErrorInfo(code, subCode, desc, request, out);
return;
//throw new RequestException(code, subCode,desc);
} else {
for (int i = 0; i < idsSize; i++) {
String url = share_img_path_json.getString(i);
if (!isUrl(url)) {
code = ErrorConfig.CODE_1005;
subCode = 30;
desc = "[" + code + "]+" + "[" + subCode + "],分享圖片中有不合法的url";
errorInfo.setErrorInfo(code, subCode, desc, request, out);
return;
//throw new RequestException(code, subCode,desc);
}
}
share_img_path = share_img_path_json.toString();
}
if (share_category.equals("0")) {
try {
//調用創建分享的服務,成功則返回分享的編號
share_id = socialShareServiceImpl.insertShareWithAt(user_id, share_category, share_description,
share_img_path, share_city, share_county, share_lon, share_lat, activityMap, groupMap, user_ids,
share_img_path_with_lables, share_paster_ids, share_img_path_with_pasters, share_location, sport_ids);
} catch (Exception e) {
e.printStackTrace();
code = ErrorConfig.CODE_1006;
subCode = 24;
desc = "[" + code + "]+" + "[" + subCode + "],數據庫更新失敗";
errorInfo.setErrorInfo(code, subCode, desc, request, out);
return;
}
} else if (share_category.equals("1")) {
//承諾的實現還沒寫
try {
socialShareServiceImpl.insertPromiseWithAt(user_id, share_category, share_description,
share_img_path, share_city, share_county, share_lon, share_lat, activityMap, groupMap, user_ids, share_img_path_with_lables);
} catch (Exception e) {
code = ErrorConfig.CODE_1006;
subCode = 24;
desc = "[" + code + "]+" + "[" + subCode + "],數據庫更新失敗";
errorInfo.setErrorInfo(code, subCode, desc, request, out);
return;
}
}
}
}
} catch (ParamException e) {
code = ErrorConfig.CODE_1005;
subCode = 0;
desc = "[" + code + "]+" + "[" + subCode + "],請求參數缺失";
rtn.put("subCode", subCode);
rtn.put("desc", desc);
errorInfo.setErrorInfo(code, subCode, desc, request, out);
return;
}
rtn.put("code", code);
//Todo 添加真實的返回share_id
rtn.put("share_id", share_id);
request.setAttribute("user_id", user_id);
out.print(responseHandler(rtn, request));
out.close();
}
清晰的業務代碼邏輯
一般來說,一個Controller是進行一個業務流程的處理,而該流程可能根據輸入參數的不同、當前狀態的不同導致走不同的邏輯流線。正如上文中列舉的代碼,正是因為存在著多個可選參數導致存在著大量的if-else分支。而用面條式的、瀑布流式的代碼是最好的邏輯表現,但是一旦分支多了,if-else多了之后,就變成了一團亂麻。筆者不是否認在充分的注釋情況下可以完全理解原有代碼,但是這樣的可讀性依然很差。筆者之前一直在測試中比較喜歡BDD方式,譬如:
Scenario: 兩數相加
Given 我有一個計算器
And 我向計算器輸入50
And 我向計算器輸入70
When 我點擊累加
Then 我應該看到結果120
在OverView部分,筆者呈現出的代碼也希望能符合這個特性:
requestHandler().
step("我是本步驟的描述").
reducer("我是本步驟的第一個可能情況").
reducer("我是本步驟的第二個可能情況")
全局狀態與局部狀態的分割
在Redux中,會把所有的狀態放在Store中進行統一管理,這樣就把狀態變量和臨時變量區分了開來,即把全局狀態與局部狀態分割了開來。筆者沒有真實的大公司的工作經驗,不過在一個可能幾百行的邏輯處理中,很有可能出現大量的a,b,c,d這樣臨時的變量,然后在最后構造返回數據時,隨手把之前的某個臨時變量封裝進去。此外,整個邏輯線的不同步驟中的局部變量可能相互干擾,可能在步驟一中定義了某個臨時變量,步驟二中沒用到,然后步驟三中突然拿來進行某個條件的判斷,然后步驟四中再重新賦予其他用法。然后在維護的時候,大家都懵逼了。
可回溯性
Redux有一個非常誘人的特性叫時空旅行,即可以回溯整個應用生命周期中的所有狀態。這個特性主要是基于其狀態樹,可以回溯出每個操作之后的狀態。筆者認為可回溯性在業務邏輯開發中的表現即是全局狀態的記錄與每次業務處理中的局部狀態的記錄。之前進行Log的時候,有很大的隨意性,而有了一個全局狀態之后,可以對于輸入、輸出進行統一的記錄。而對于中間狀態,因為劃分了Step和不同Step之間統一的Action,也可以方便的記錄下來。在這種情況下,只要了解每個Step具體執行了哪個Reducer,并且每個Reducer的輸出值,可以很快定位到問題代碼所在的地方。
可容錯性
上文中已經提及,因為RARF是記錄了每個Step的情況,那么可以很快的定位到錯誤代碼。此外,由于全部的代碼是包裹在了Observable之中,那么任何的未知錯誤也都可以進行妥善處理,而不用擔心不可回溯。
并發編程與異步實現
隨著計算機硬件,筆者比較推崇響應式流這種異步模型,在本部分的實現中,筆者也是優先考慮了并發的易實現性。RARF將整個業務處理邏輯分成了數個Step,如果你發現哪個Step是IO密集型或者計算密集型,簡言之,就需要耗費較長的時間,那么完全可以放到新線程中執行,而把Main線程流出來響應新的請求。
RxJava這種鏈式調用形式的異步寫法很是清晰明了,不過需要注意的是RxJava本身并不一定是并發地,默認情況下所有的Observable與Subscriber都是在一個線程里運行,但是可以簡單的使用 subscribeOn 方法將某個Observable扔到子線程中運行。
Terminology
Context
Context即對應一個業務處理流程,包含多個邏輯線。一個Context會被劃分為輸入處理、一到多個中間過程以及最后的輸出處理。
Step
一個Step即是某個具體的業務處理塊,一個Step包含一到多個Reducer,每個Reducer表示該步驟可能的一個邏輯線。一般來說,一起請求中一個Step中只有一個Reducer會有效執行,即返回有效的Action。如果某個Step中所有Action都沒有執行,那么會返回一個錯誤代碼。
UniResourceBag
資源包即是上文描述的全局狀態存放的地方,包括輸入、輸出。
Reducer & Action
每個Reducer的執行塊都會返回一個Action對象,如果Action的isValid值為True,表示要把該Action發射到下一個Observable,否則不進行發射。每個Reducer首先會對上一步Step傳入的Action類型進行判斷,只有在是自己需要的類型的情況下才會進行執行。每個Reducer在有效執行后會發射有效地Action到下一步中。
RequestHandler:對于請求數據進行預處理
請求方式基于RARF中的URFP,即是PathVariable與RequestData混合的方式,一個典型的登錄請求為:
/login/{verifyCode}?requestData={"username":chevalier,"password":123456}
ResponseHandler:對于輸出數據進行處理
ResponseHandler會對ResourceBag中的數據進行最終的處理校驗,同時負責處理日志啊、消息推送啊等等非必須邏輯的異步實現。
來自: https://segmentfault.com/a/1190000005039478