事件领域模型框架地址:https://gitee.com/zkpursuit/kaka-notice-lib
所依赖的其它第三方库:
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.25</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-io</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.5</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.9.5</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.9.5</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
</dependency>
一、定义一个json数据写入器,用于暂存发送到客户端的json数据。
package com.http.core;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.util.JsonUtils;
/**
* 待发送的JSON对象
*
* @author zhoukai
*/
public class HttpJsonRespWriter {
private ArrayNode array; //待发送的json数据
private StringBuilder strBuilder; //待发送的字符串数据,与array中的数据互斥,array优先级最高
private boolean forceArray = false; //是否强制以json数组形式发送数据到客户端
public HttpJsonRespWriter() {
}
public void setForceArray(boolean bool) {
this.forceArray = bool;
}
public boolean getForceArray() {
return forceArray;
}
/**
* 追加JSON对象
*
* @param json 待追加的对象
*/
private void addJsonObject(JsonNode json) {
if (array == null) {
array = JsonUtils.createJsonArray();
}
if (json instanceof ArrayNode) {
ArrayNode arr = (ArrayNode) json;
array.addAll(arr);
} else {
array.add(json);
}
}
/**
* 追加
*
* @param obj 追加的对象实例,支持Json对象、HttpResponseWriter对象、字符串
* <p>其它未支持的对象全部以字符串表示</p>
* @return 本实例
*/
HttpJsonRespWriter writeObject(Object obj) {
if (obj instanceof JsonNode) {
addJsonObject((JsonNode) obj);
} else if(obj instanceof HttpJsonRespWriter) {
HttpJsonRespWriter writer = (HttpJsonRespWriter) obj;
ArrayNode arr = writer.array;
boolean flag = true;
if(arr != null) {
int size = arr.size();
if(size > 0) {
for(int i = 0; i < size; i++) {
JsonNode node = arr.get(i);
writeObject(node);
}
flag = false;
}
}
if(flag && strBuilder != null && strBuilder.length() > 0) {
writeObject(strBuilder.toString());
}
} else {
if (strBuilder == null) {
strBuilder = new StringBuilder();
}
strBuilder.append(String.valueOf(obj));
}
return this;
}
/**
* 写入待发送的数据
*
* @param cmd 协议号
* @param sendData 等待发送的数据
*/
public void write(String cmd, Object sendData) {
ObjectNode sendDataJson = JsonUtils.createJsonObject();
if(sendData != null) {
if(sendData instanceof ObjectNode) {
ObjectNode jsonObj = (ObjectNode) sendData;
if(jsonObj.has("error") || jsonObj.has("info")) {
sendDataJson.setAll(jsonObj);
} else {
if(!jsonObj.has("data")) {
sendDataJson.set("data", jsonObj);
} else {
sendDataJson.setAll(jsonObj);
}
}
} else if(sendData instanceof ArrayNode) {
ArrayNode jsonArr = (ArrayNode) sendData;
sendDataJson.set("data", jsonArr);
} else {
sendDataJson.put("data", sendData.toString());
}
} else {
sendDataJson.put("data", "null");
}
String cmdStr = "cmd";
String systime = "systime";
if (!sendDataJson.has(cmdStr)) {
sendDataJson.put(cmdStr, cmd);
}
if (!sendDataJson.has(systime)) {
sendDataJson.put(systime, System.currentTimeMillis());
}
writeObject(sendDataJson);
}
/**
* 清除
*/
public void clear() {
if (array != null) {
array.removeAll();
}
if (strBuilder != null && strBuilder.length() > 0) {
strBuilder.delete(0, strBuilder.length());
}
}
/**
* 判断此对象的字符串表示是否为json格式
*
* @return true是json格式
*/
public boolean isJsonString() {
if (array != null) {
return true;
}
return JsonUtils.isValidJson(strBuilder.toString());
}
/**
* 是否为空
*
* @return true为空
*/
public boolean isEmpty() {
if (array != null && array.size() > 0) {
return false;
}
return !(strBuilder != null && strBuilder.length() > 0);
}
/**
* 将本对象转换为字符串
*
* @return 本对象的字符串表示
*/
@Override
public String toString() {
if (array != null) {
if (forceArray) {
return JsonUtils.toJsonString(array);
}
if (array.size() == 1) {
Object obj = array.get(0);
if (obj instanceof JsonNode) {
return JsonUtils.toJsonString(obj);
}
return obj.toString();
}
return JsonUtils.toJsonString(array);
}
if (strBuilder != null) {
return strBuilder.toString();
}
return super.toString();
}
}
二、定义一个基于json的事件消息类,具体父类继承于事件领域模型框架中的com.kaka.notice.Message类
package com.http.core;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.kaka.notice.Message;
/**
*
* @author zhoukai
*/
public class JsonMessage extends Message {
public final HttpJsonRespWriter out;
public JsonMessage(Object what, ObjectNode data, HttpJsonRespWriter out) {
super(what, data);
this.out = out;
}
}
三、定义json数据处理器(具体父类继承于事件领域模型框架中的com.kaka.notice.Command类),处理自定义数据协议并将业务结果返回给json数据写入器 HttpJsonRespWriter
package com.http.core;
import ch.qos.logback.classic.Logger;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.kaka.notice.Command;
import com.kaka.notice.Message;
import org.slf4j.LoggerFactory;
/**
* json数据处理器
*
* @author zhoukai
*/
abstract public class JsonDataHandler extends Command {
private Logger logger;
protected final String opcode; //一定要在构造方法里赋值,不然反射赋值不会成功
public JsonDataHandler() {
this.opcode = "";
}
@Override
public void execute(Message msg) {
com.kaka.util.ReflectUtils.setFieldValue(this, "opcode", this.cmd());
ObjectNode netData = (ObjectNode) msg.getBody();
HttpJsonRespWriter out = null;
if (msg instanceof JsonMessage) {
JsonMessage nm = (JsonMessage) msg;
out = nm.out;
}
try {
execute(netData, out);
} catch (Exception ex) {
getLogger().error(ex.getLocalizedMessage(), ex);
}
}
/**
* 数据包具体解析执行,业务逻辑处理
*
* @param requestJson 客户端发送的json协议请求
* @param out 返回给客户端的json数据写入器
*/
abstract public void execute(ObjectNode requestJson, HttpJsonRespWriter out);
protected Logger getLogger() {
if (logger == null) {
logger = (Logger) LoggerFactory.getLogger(this.getClass());
}
return logger;
}
}
四、重点是定义一个接口服务servlet,将整合kaka事件框架及自定义的json事件消息和json数据处理器
package com.http.core;
import ch.qos.logback.classic.Logger;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.util.JsonUtils;
import org.apache.commons.io.IOUtils;
import org.slf4j.LoggerFactory;
import javax.servlet.ServletException;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.net.URLDecoder;
import java.nio.charset.Charset;
import static com.kaka.notice.Facade.facade;
/**
*
* @author zhoukai
*/
public class JsonApiServlet extends HttpServlet {
private static final Logger logger = (Logger) LoggerFactory.getLogger(JsonApiServlet.class);
/**
* 处理请求中的数据通信协议,json格式
*
* @param request http请求
* @param requestString 请求json格式字符串,可为单个JsonObject或者包含多个JsonObject的JsonArray
* <p>也可能为经过DES加密再经过Hax编码后的字符串,但解码后仍是json格式</p>
* @param response http响应
*/
private void processJson(String requestString, HttpServletRequest request, HttpServletResponse response) {
String requestJsonString = null;
if (JsonUtils.isValidJson(requestString)) {
requestJsonString = requestString;
}
logger.info(requestJsonString);
HttpJsonRespWriter writer = new HttpJsonRespWriter();
JsonNode jn = JsonUtils.toJson(requestJsonString);
if (jn != null) {
if (jn instanceof ObjectNode) {
ObjectNode jsonObj = (ObjectNode) jn;
processJson(jsonObj, writer);
} else if (jn instanceof ArrayNode) {
ArrayNode jsonArr = (ArrayNode) jn;
int size = jsonArr.size();
for (int i = 0; i < size; i++) {
ObjectNode jsonObj = (ObjectNode) jsonArr.get(i);
processJson(jsonObj, writer);
}
}
if (writer.isEmpty()) {
ObjectNode jo = JsonUtils.createJsonObject();
jo.put("info", "module_not_open");
writer.writeObject(jo);
}
} else {
ObjectNode jo = JsonUtils.createJsonObject();
jo.put("error", "请求数据非标准json格式");
writer.writeObject(jo);
}
this.send(response, writer.toString());
}
/**
* 处理单个JsonObject数据协议</br>
* 单个处理当中发生异常不会影响其它处理结果</br>
*
* @param jsonObj json协议数据
* @param out 整合处理结果
*/
private void processJson(ObjectNode jsonObj, HttpJsonRespWriter out) {
String cmd = jsonObj.get("cmd").asText();
HttpJsonRespWriter writer = new HttpJsonRespWriter();
try {
facade.sendMessage(new JsonMessage(cmd, jsonObj, writer));
} catch (Exception ex) {
ObjectNode jo = JsonUtils.createJsonObject();
jo.put("info", "error");
writer.write(cmd, jo);
logger.error(JsonUtils.toJsonString(jsonObj));
logger.error("数据处理错误!", ex);
}
if (writer.isEmpty()) {
ObjectNode jo = JsonUtils.createJsonObject();
jo.put("info", "module_not_open");
writer.write(cmd, jo);
}
out.writeObject(writer);
}
/**
* 发送响应数据到客户端
*
* @param resp
* @param content
*/
private void send(HttpServletResponse resp, String content) {
try (PrintWriter pw = resp.getWriter()) {
pw.write(content);
pw.flush();
} catch (IOException e) {
logger.error(e.getLocalizedMessage(), e);
}
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
req.setCharacterEncoding("UTF-8");
resp.setContentType("text/html;charset=UTF-8");
String param = req.getQueryString();
param = URLDecoder.decode(param, "UTF-8");
processJson(param, req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
req.setCharacterEncoding("UTF-8");
resp.setContentType("text/html;charset=UTF-8");
ServletInputStream is = req.getInputStream();
byte[] bytes = IOUtils.toByteArray(is);
String param = new String(bytes, Charset.forName("UTF-8"));
processJson(param, req, resp);
}
}
将此servlet配置到web.xml中,如果基于servlet3.0,直接用注解也行。
<servlet>
<servlet-name>JsonApiServlet</servlet-name>
<servlet-class>com.http.core.JsonApiServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>JsonApiServlet</servlet-name>
<url-pattern>/api</url-pattern>
</servlet-mapping>
五、又是个重点了,上面写了一大堆代码,我们得具体实现一个业务了,怎么做呢,当然得继承上面自定义的那个JsonDataHandler了,客官请看:
package com.http.business;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.http.core.HttpJsonRespWriter;
import com.http.core.JsonDataHandler;
import com.kaka.notice.annotation.Handler;
import com.util.JsonUtils;
/**
* 一个简单的求和
*
* @author zhoukai
*/
@Handler(cmd = "my_first_cmd", type = String.class) //cmd一般定义一个静态类,里面集中写好协议号,在此随便定义一个字符串了
public class MyFirstJsonHandler extends JsonDataHandler {
/**
*
* @param requestJson 客户端发送的json协议请求
* @param out 返回给客户端的json数据写入器
*/
@Override
public void execute(ObjectNode requestJson, HttpJsonRespWriter out) {
int min = requestJson.get("min").asInt();
int max = requestJson.get("max").asInt();
int _min = min;
min = Math.min(_min, max);
max = Math.max(max, _min);
ObjectNode json = JsonUtils.createJsonObject();
int sum = 0;
for(int i = min; i <= max; i++) {
sum += i;
}
json.put("min", min);
json.put("max", max);
json.put("sum", sum);
out.write(this.opcode, json);
}
}
好像看着一切就绪了,运行看看,尝试在浏览器地址栏中输入http://localhost:8080/项目名/api?{%22cmd%22:%22my_first_cmd%22,%20%22min%22:1,%20%22max%22:100}后回车,怎么回事?输出的结果是模块未开启?
抱歉!抱歉!重中之中当然得调用到com.kaka.Startup实例的scan方法扫描包了,接着编写一个http监听器
package com.http.core;
import com.kaka.Startup;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
public class InitListener extends Startup implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent sce) {
this.scan("com.http.business");
}
}
然后将其注册到web.xml文件中。
<listener>
<listener-class>com.http.core.InitListener</listener-class>
</listener>
初心大意,忘记发json工具类了,海涵!
package com.util;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import java.io.IOException;
import java.util.Collection;
/**
* {@link com.fasterxml.jackson.annotation.JsonProperty}
* <p>为属性取别名的注解</p>
* {@link com.fasterxml.jackson.annotation.JsonIgnore}
* <p>忽略方法</p>
* {@link com.fasterxml.jackson.annotation.JsonAutoDetect}
* <p>类名注解,配合JsonProperty注解使用忽略方法</p>
*/
public class JsonUtils {
/**
* 线程安全,可以全局使用
*/
private static final ObjectMapper mapper = new ObjectMapper();
// static {
// mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
// //将属性字段全部转为小写
// mapper.setPropertyNamingStrategy(PropertyNamingStrategy.LOWER_CASE);
// }
/**
* 将java对象转换为json对象
* @param javaObject java对象
* @param <T> json对象的限定类型
* @return json对象
*/
public final static <T extends JsonNode> T toJsonObject(Object javaObject) {
if(javaObject instanceof JsonNode) {
return (T) javaObject;
}
//return mapper.convertValue(javaBean, JsonNode.class);
JsonNode jsonObject = mapper.valueToTree(javaObject);
return (T) jsonObject;
}
/**
* 判断字符串是否为一个有效的json格式
* @param jsonInString
* @return
*/
public final static boolean isValidJson(String jsonInString ) {
try {
mapper.readTree(jsonInString);
return true;
} catch (IOException e) {
return false;
}
}
/**
* 创建一个空的JsonObject对象
* @return
*/
public final static ObjectNode createJsonObject() {
return mapper.createObjectNode();
}
/**
* 创建一个空的JsonArray对象
* @return
*/
public final static ArrayNode createJsonArray() {
return mapper.createArrayNode();
}
/**
* 将java对象转换为json字符串
* @param value
* @return
*/
public final static String toJsonString(Object value) {
try {
return mapper.writeValueAsString(value);
} catch (JsonProcessingException e) {
return null;
}
}
/**
* 将json字符串转换为json对象
* @param json
* @param <T>
* @return
*/
public final static <T extends JsonNode> T toJson(String json) {
if(json == null || "".equals(json)) return null;
try {
return (T) mapper.readTree(json);
} catch (IOException e) {
return null;
}
}
/**
* 将json字符串转换为java对象
* @param json
* @param type
* @param <T>
* @return
*/
public final static <T>T toObject(String json, Class<T> type) {
try {
return mapper.readValue(json, type);
} catch (IOException e) {
return null;
}
}
/**
* 获取集合类型描述
* @param collectionClass 集合类型
* @param elementClasses 集合的元素类型
* @return 类型描述
*/
public final static JavaType getCollectionType(Class<?> collectionClass, Class<?>... elementClasses) {
return mapper.getTypeFactory().constructParametricType(collectionClass, elementClasses);
}
/**
* 将json字符串转换为java集合
* @param json json字符串
* @param collectionClass 集合类型
* @param elementClasses 集合的元素类型
* @param <T> 转换后的集合限定类型
* @return 集合对象
*/
public final static <T> Collection<T> toCollection(String json, Class<?> collectionClass, Class<T> elementClasses) {
JavaType javaType = getCollectionType(collectionClass, elementClasses);
try {
return (Collection<T>) mapper.readValue(json, javaType);
} catch (IOException e) {
return null;
}
}
/**
* 将json字符串转换为java对象
* @param json json字符串
* @param type 目标对象类型
* @param elementClasses 如果目标对象类型为集合,则此参数表示集合的元素类型
* @return 对象
*/
public final static Object toObject(String json, Class<?> type, Class<?>... elementClasses) {
try {
if(elementClasses.length > 0) {
JavaType javaType = getCollectionType(type, elementClasses);
return mapper.readValue(json, javaType);
}
return mapper.readValue(json, type);
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
}
再次运行,在浏览器地址栏中输入http://localhost:8080/项目名/api?{"cmd":"my_first_cmd","min":1,"max":100}后回车
哈哈!结果出来了,一切显得那么完美,不是吗?
什么?想现在看到结果,行,具体用应答模式呈现:
浏览器请求:{"cmd":"my_first_cmd","min":1,"max":100}
服务器响应:{"data":{"min":1,"max":100,"sum":5050},"cmd":"my_first_cmd","systime":1544977209943}
如想直接体验基于事件领域模型的快感,请打开浏览器直接输入http://47.101.144.30:8080/
试着用浏览器开发者模式看看请求的数据和响应的数据,是不是很方便呢?http://47.101.144.30:8080/此地址使用了guava中的令牌桶限流,请勿恶意访问,谢谢支持!
基于 https://gitee.com/zkpursuit/kaka-notice-lib
构建的一个斗地主游戏 http://47.101.144.30:8080/game.html (需在浏览器中开三个标签页体验),自定义二进制协议通信,有什么关于本事件领域模型的疑问或者简单的斗地主算法问题可以一起探讨。
申明:博文中提到的斗地主游戏(html5)完全属于一种框架使用范例,不涉及任何广告和其它经济利益,也不是一个完整健全的游戏项目,在此仅做技术探讨使用!