This commit is contained in:
@@ -24,6 +24,8 @@ import org.redkale.util.*;
|
|||||||
* 详情见: https://redkale.org
|
* 详情见: https://redkale.org
|
||||||
*
|
*
|
||||||
* @author zhangjx
|
* @author zhangjx
|
||||||
|
*
|
||||||
|
* @since 2.1.0
|
||||||
*/
|
*/
|
||||||
public abstract class ClusterAgent {
|
public abstract class ClusterAgent {
|
||||||
|
|
||||||
|
|||||||
@@ -8,12 +8,13 @@ package org.redkale.mq;
|
|||||||
import org.redkale.net.http.*;
|
import org.redkale.net.http.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
|
||||||
*
|
*
|
||||||
* <p>
|
* <p>
|
||||||
* 详情见: https://redkale.org
|
* 详情见: https://redkale.org
|
||||||
*
|
*
|
||||||
* @author zhangjx
|
* @author zhangjx
|
||||||
|
*
|
||||||
|
* @since 2.1.0
|
||||||
*/
|
*/
|
||||||
public class HttpMessageRequest extends HttpRequest {
|
public class HttpMessageRequest extends HttpRequest {
|
||||||
|
|
||||||
|
|||||||
@@ -19,6 +19,8 @@ import org.redkale.util.ObjectPool;
|
|||||||
* 详情见: https://redkale.org
|
* 详情见: https://redkale.org
|
||||||
*
|
*
|
||||||
* @author zhangjx
|
* @author zhangjx
|
||||||
|
*
|
||||||
|
* @since 2.1.0
|
||||||
*/
|
*/
|
||||||
public class HttpMessageResponse extends HttpResponse {
|
public class HttpMessageResponse extends HttpResponse {
|
||||||
|
|
||||||
|
|||||||
130
src/org/redkale/mq/HttpResultCoder.java
Normal file
130
src/org/redkale/mq/HttpResultCoder.java
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
/*
|
||||||
|
* To change this license header, choose License Headers in Project Properties.
|
||||||
|
* To change this template file, choose Tools | Templates
|
||||||
|
* and open the template in the editor.
|
||||||
|
*/
|
||||||
|
package org.redkale.mq;
|
||||||
|
|
||||||
|
import java.net.HttpCookie;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
import org.redkale.convert.*;
|
||||||
|
import org.redkale.convert.json.JsonConvert;
|
||||||
|
import static org.redkale.mq.MessageCoder.*;
|
||||||
|
import org.redkale.net.http.HttpResult;
|
||||||
|
import org.redkale.util.Utility;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HttpResult的MessageCoder实现
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* 详情见: https://redkale.org
|
||||||
|
*
|
||||||
|
* @author zhangjx
|
||||||
|
*
|
||||||
|
* @since 2.1.0
|
||||||
|
*/
|
||||||
|
public class HttpResultCoder implements MessageCoder<HttpResult> {
|
||||||
|
|
||||||
|
private static final HttpResultCoder instance = new HttpResultCoder();
|
||||||
|
|
||||||
|
public static HttpResultCoder getInstance() {
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] encode(HttpResult data) {
|
||||||
|
if (data == null) return null;
|
||||||
|
byte[] contentType = MessageCoder.getBytes(data.getContentType());
|
||||||
|
byte[] headers = MessageCoder.getBytes(data.getHeaders());
|
||||||
|
byte[] cookies = getBytes(data.getCookies());
|
||||||
|
byte[] content;
|
||||||
|
if (data.getResult() == null) {
|
||||||
|
content = new byte[0]; //""
|
||||||
|
} else if (data.getResult() instanceof CharSequence) {
|
||||||
|
content = MessageCoder.getBytes(data.getResult().toString());
|
||||||
|
} else {
|
||||||
|
Convert cc = data.convert();
|
||||||
|
if (cc == null || !(cc instanceof TextConvert)) cc = JsonConvert.root();
|
||||||
|
content = cc.convertToBytes(data.getResult());
|
||||||
|
}
|
||||||
|
int count = 4 + 2 + contentType.length + headers.length + cookies.length + 4 + (content == null ? 0 : content.length);
|
||||||
|
final byte[] bs = new byte[count];
|
||||||
|
ByteBuffer buffer = ByteBuffer.wrap(bs);
|
||||||
|
buffer.putInt(data.getStatus());
|
||||||
|
buffer.putChar((char) contentType.length);
|
||||||
|
if (contentType.length > 0) buffer.put(contentType);
|
||||||
|
buffer.put(headers);
|
||||||
|
buffer.put(cookies);
|
||||||
|
if (content == null || content.length == 0) {
|
||||||
|
buffer.putInt(0);
|
||||||
|
} else {
|
||||||
|
buffer.putInt(content.length);
|
||||||
|
buffer.put(content);
|
||||||
|
}
|
||||||
|
return bs;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public HttpResult<byte[]> decode(byte[] data) {
|
||||||
|
if (data == null) return null;
|
||||||
|
ByteBuffer buffer = ByteBuffer.wrap(data);
|
||||||
|
HttpResult result = new HttpResult();
|
||||||
|
result.setStatus(buffer.getInt());
|
||||||
|
result.setContentType(MessageCoder.getShortString(buffer));
|
||||||
|
result.setHeaders(MessageCoder.getMap(buffer));
|
||||||
|
result.setCookies(getCookieList(buffer));
|
||||||
|
int len = buffer.getInt();
|
||||||
|
if (len > 0) {
|
||||||
|
byte[] bs = new byte[len];
|
||||||
|
buffer.get(bs);
|
||||||
|
result.setResult(bs);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static byte[] getBytes(final List<HttpCookie> list) {
|
||||||
|
if (list == null || list.isEmpty()) return new byte[2];
|
||||||
|
final AtomicInteger len = new AtomicInteger(2);
|
||||||
|
list.forEach(cookie -> {
|
||||||
|
len.addAndGet(2 + (cookie.getName() == null ? 0 : Utility.encodeUTF8Length(cookie.getName())));
|
||||||
|
len.addAndGet(2 + (cookie.getValue() == null ? 0 : Utility.encodeUTF8Length(cookie.getValue())));
|
||||||
|
len.addAndGet(2 + (cookie.getDomain() == null ? 0 : Utility.encodeUTF8Length(cookie.getDomain())));
|
||||||
|
len.addAndGet(2 + (cookie.getPath() == null ? 0 : Utility.encodeUTF8Length(cookie.getPath())));
|
||||||
|
len.addAndGet(2 + (cookie.getPortlist() == null ? 0 : Utility.encodeUTF8Length(cookie.getPortlist())));
|
||||||
|
len.addAndGet(4 + 1 + 1); //maxage Secure HttpOnly
|
||||||
|
});
|
||||||
|
final byte[] bs = new byte[len.get()];
|
||||||
|
final ByteBuffer buffer = ByteBuffer.wrap(bs);
|
||||||
|
buffer.putChar((char) list.size());
|
||||||
|
list.forEach(cookie -> {
|
||||||
|
putShortString(buffer, cookie.getName());
|
||||||
|
putShortString(buffer, cookie.getValue());
|
||||||
|
putShortString(buffer, cookie.getDomain());
|
||||||
|
putShortString(buffer, cookie.getPath());
|
||||||
|
putShortString(buffer, cookie.getPortlist());
|
||||||
|
buffer.putLong(cookie.getMaxAge());
|
||||||
|
buffer.put(cookie.getSecure() ? (byte) 1 : (byte) 0);
|
||||||
|
buffer.put(cookie.isHttpOnly() ? (byte) 1 : (byte) 0);
|
||||||
|
});
|
||||||
|
return bs;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<HttpCookie> getCookieList(ByteBuffer buffer) {
|
||||||
|
int len = buffer.getChar();
|
||||||
|
if (len == 0) return null;
|
||||||
|
final List<HttpCookie> list = new ArrayList<>(len);
|
||||||
|
for (int i = 0; i < len; i++) {
|
||||||
|
HttpCookie cookie = new HttpCookie(getShortString(buffer), getShortString(buffer));
|
||||||
|
cookie.setDomain(getShortString(buffer));
|
||||||
|
cookie.setPath(getShortString(buffer));
|
||||||
|
cookie.setPortlist(getShortString(buffer));
|
||||||
|
cookie.setMaxAge(buffer.getLong());
|
||||||
|
cookie.setSecure(buffer.get() == 1);
|
||||||
|
cookie.setHttpOnly(buffer.get() == 1);
|
||||||
|
list.add(cookie);
|
||||||
|
}
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -16,6 +16,8 @@ import org.redkale.net.http.HttpSimpleRequest;
|
|||||||
* 详情见: https://redkale.org
|
* 详情见: https://redkale.org
|
||||||
*
|
*
|
||||||
* @author zhangjx
|
* @author zhangjx
|
||||||
|
*
|
||||||
|
* @since 2.1.0
|
||||||
*/
|
*/
|
||||||
public class HttpSimpleRequestCoder implements MessageCoder<HttpSimpleRequest> {
|
public class HttpSimpleRequestCoder implements MessageCoder<HttpSimpleRequest> {
|
||||||
|
|
||||||
|
|||||||
@@ -21,6 +21,8 @@ import org.redkale.util.*;
|
|||||||
* 详情见: https://redkale.org
|
* 详情见: https://redkale.org
|
||||||
*
|
*
|
||||||
* @author zhangjx
|
* @author zhangjx
|
||||||
|
*
|
||||||
|
* @since 2.1.0
|
||||||
*/
|
*/
|
||||||
public abstract class MessageAgent {
|
public abstract class MessageAgent {
|
||||||
|
|
||||||
|
|||||||
@@ -19,6 +19,9 @@ import org.redkale.util.Utility;
|
|||||||
* 详情见: https://redkale.org
|
* 详情见: https://redkale.org
|
||||||
*
|
*
|
||||||
* @author zhangjx
|
* @author zhangjx
|
||||||
|
*
|
||||||
|
* @since 2.1.0
|
||||||
|
*
|
||||||
* @param <T> 泛型
|
* @param <T> 泛型
|
||||||
*/
|
*/
|
||||||
public interface MessageCoder<T> {
|
public interface MessageCoder<T> {
|
||||||
|
|||||||
@@ -14,7 +14,10 @@ import java.util.logging.Logger;
|
|||||||
* <p>
|
* <p>
|
||||||
* 详情见: https://redkale.org
|
* 详情见: https://redkale.org
|
||||||
*
|
*
|
||||||
|
*
|
||||||
* @author zhangjx
|
* @author zhangjx
|
||||||
|
*
|
||||||
|
* @since 2.1.0
|
||||||
*/
|
*/
|
||||||
public abstract class MessageConsumer extends Thread {
|
public abstract class MessageConsumer extends Thread {
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ package org.redkale.mq;
|
|||||||
* 详情见: https://redkale.org
|
* 详情见: https://redkale.org
|
||||||
*
|
*
|
||||||
* @author zhangjx
|
* @author zhangjx
|
||||||
|
*
|
||||||
|
* @since 2.1.0
|
||||||
*/
|
*/
|
||||||
public interface MessageProcessor {
|
public interface MessageProcessor {
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ import java.util.logging.Logger;
|
|||||||
* 详情见: https://redkale.org
|
* 详情见: https://redkale.org
|
||||||
*
|
*
|
||||||
* @author zhangjx
|
* @author zhangjx
|
||||||
|
*
|
||||||
|
* @since 2.1.0
|
||||||
*/
|
*/
|
||||||
public abstract class MessageProducer extends Thread {
|
public abstract class MessageProducer extends Thread {
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,8 @@ import org.redkale.util.Comment;
|
|||||||
* 详情见: https://redkale.org
|
* 详情见: https://redkale.org
|
||||||
*
|
*
|
||||||
* @author zhangjx
|
* @author zhangjx
|
||||||
|
*
|
||||||
|
* @since 2.1.0
|
||||||
*/
|
*/
|
||||||
public class MessageRecord implements Serializable {
|
public class MessageRecord implements Serializable {
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ import org.redkale.convert.ConvertType;
|
|||||||
* 详情见: https://redkale.org
|
* 详情见: https://redkale.org
|
||||||
*
|
*
|
||||||
* @author zhangjx
|
* @author zhangjx
|
||||||
|
*
|
||||||
|
* @since 2.1.0
|
||||||
*/
|
*/
|
||||||
public class MessageRecordCoder implements MessageCoder<MessageRecord> {
|
public class MessageRecordCoder implements MessageCoder<MessageRecord> {
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ package org.redkale.mq;
|
|||||||
* 详情见: https://redkale.org
|
* 详情见: https://redkale.org
|
||||||
*
|
*
|
||||||
* @author zhangjx
|
* @author zhangjx
|
||||||
|
*
|
||||||
|
* @since 2.1.0
|
||||||
*/
|
*/
|
||||||
public interface MessageResponse {
|
public interface MessageResponse {
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ import org.redkale.net.sncp.*;
|
|||||||
* 详情见: https://redkale.org
|
* 详情见: https://redkale.org
|
||||||
*
|
*
|
||||||
* @author zhangjx
|
* @author zhangjx
|
||||||
|
*
|
||||||
|
* @since 2.1.0
|
||||||
*/
|
*/
|
||||||
public class SncpMessageRequest extends SncpRequest {
|
public class SncpMessageRequest extends SncpRequest {
|
||||||
|
|
||||||
|
|||||||
@@ -19,6 +19,8 @@ import org.redkale.util.ObjectPool;
|
|||||||
* 详情见: https://redkale.org
|
* 详情见: https://redkale.org
|
||||||
*
|
*
|
||||||
* @author zhangjx
|
* @author zhangjx
|
||||||
|
*
|
||||||
|
* @since 2.1.0
|
||||||
*/
|
*/
|
||||||
public class SncpMessageResponse extends SncpResponse {
|
public class SncpMessageResponse extends SncpResponse {
|
||||||
|
|
||||||
|
|||||||
@@ -1013,7 +1013,7 @@ public class HttpResponse extends Response<HttpContext, HttpRequest> {
|
|||||||
if (defaultCookie.getDomain() != null && cookie.getDomain() == null) cookie.setDomain(defaultCookie.getDomain());
|
if (defaultCookie.getDomain() != null && cookie.getDomain() == null) cookie.setDomain(defaultCookie.getDomain());
|
||||||
if (defaultCookie.getPath() != null && cookie.getPath() == null) cookie.setPath(defaultCookie.getPath());
|
if (defaultCookie.getPath() != null && cookie.getPath() == null) cookie.setPath(defaultCookie.getPath());
|
||||||
}
|
}
|
||||||
buffer.put(("Set-Cookie: " + genString(cookie) + "\r\n").getBytes());
|
buffer.put(("Set-Cookie: " + cookieString(cookie) + "\r\n").getBytes());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
buffer.put(LINE);
|
buffer.put(LINE);
|
||||||
@@ -1022,7 +1022,7 @@ public class HttpResponse extends Response<HttpContext, HttpRequest> {
|
|||||||
return buffer;
|
return buffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
private CharSequence genString(HttpCookie cookie) {
|
private CharSequence cookieString(HttpCookie cookie) {
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
sb.append(cookie.getName()).append("=").append(cookie.getValue()).append("; Version=1");
|
sb.append(cookie.getName()).append("=").append(cookie.getValue()).append("; Version=1");
|
||||||
if (cookie.getDomain() != null) sb.append("; Domain=").append(cookie.getDomain());
|
if (cookie.getDomain() != null) sb.append("; Domain=").append(cookie.getDomain());
|
||||||
|
|||||||
@@ -23,17 +23,20 @@ public class HttpResult<T> {
|
|||||||
|
|
||||||
public static final String SESSIONID_COOKIENAME = HttpRequest.SESSIONID_NAME;
|
public static final String SESSIONID_COOKIENAME = HttpRequest.SESSIONID_NAME;
|
||||||
|
|
||||||
protected Map<String, String> headers;
|
@ConvertColumn(index = 1)
|
||||||
|
|
||||||
protected List<HttpCookie> cookies;
|
|
||||||
|
|
||||||
protected String contentType;
|
|
||||||
|
|
||||||
protected T result;
|
|
||||||
|
|
||||||
protected int status = 0; //不设置则为 200
|
protected int status = 0; //不设置则为 200
|
||||||
|
|
||||||
protected String message;
|
@ConvertColumn(index = 2)
|
||||||
|
protected String contentType;
|
||||||
|
|
||||||
|
@ConvertColumn(index = 3)
|
||||||
|
protected Map<String, String> headers;
|
||||||
|
|
||||||
|
@ConvertColumn(index = 4)
|
||||||
|
protected List<HttpCookie> cookies;
|
||||||
|
|
||||||
|
@ConvertColumn(index = 5)
|
||||||
|
protected T result;
|
||||||
|
|
||||||
protected Convert convert;
|
protected Convert convert;
|
||||||
|
|
||||||
@@ -85,11 +88,6 @@ public class HttpResult<T> {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public HttpResult<T> message(String message) {
|
|
||||||
this.message = message;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Convert convert() {
|
public Convert convert() {
|
||||||
return convert;
|
return convert;
|
||||||
}
|
}
|
||||||
@@ -138,14 +136,6 @@ public class HttpResult<T> {
|
|||||||
this.status = status;
|
this.status = status;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getMessage() {
|
|
||||||
return message;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setMessage(String message) {
|
|
||||||
this.message = message;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return JsonConvert.root().convertTo(this);
|
return JsonConvert.root().convertTo(this);
|
||||||
|
|||||||
@@ -17,6 +17,8 @@ import org.redkale.util.Comment;
|
|||||||
* 详情见: https://redkale.org
|
* 详情见: https://redkale.org
|
||||||
*
|
*
|
||||||
* @author zhangjx
|
* @author zhangjx
|
||||||
|
*
|
||||||
|
* @since 2.1.0
|
||||||
*/
|
*/
|
||||||
public class HttpSimpleRequest implements java.io.Serializable {
|
public class HttpSimpleRequest implements java.io.Serializable {
|
||||||
|
|
||||||
|
|||||||
@@ -100,6 +100,12 @@ public abstract class Sncp {
|
|||||||
return service.getClass().getAnnotation(SncpDyn.class) != null;
|
return service.getClass().getAnnotation(SncpDyn.class) != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static int getVersion(Service service) {
|
||||||
|
if (service == null) return -1;
|
||||||
|
Version ver = service.getClass().getAnnotation(Version.class);
|
||||||
|
return ver == null ? -1 : ver.value();
|
||||||
|
}
|
||||||
|
|
||||||
public static String getResourceName(Service service) {
|
public static String getResourceName(Service service) {
|
||||||
if (service == null) return null;
|
if (service == null) return null;
|
||||||
Resource res = service.getClass().getAnnotation(Resource.class);
|
Resource res = service.getClass().getAnnotation(Resource.class);
|
||||||
|
|||||||
@@ -75,12 +75,13 @@ public final class SncpClient {
|
|||||||
this.remote = remote;
|
this.remote = remote;
|
||||||
this.executor = factory.getExecutor();
|
this.executor = factory.getExecutor();
|
||||||
this.bufferSupplier = factory.getBufferSupplier();
|
this.bufferSupplier = factory.getBufferSupplier();
|
||||||
|
Class<?> tn = serviceTypeOrImplClass;
|
||||||
|
Version ver = tn.getAnnotation(Version.class);
|
||||||
this.serviceClass = serviceClass;
|
this.serviceClass = serviceClass;
|
||||||
this.serviceversion = 0;
|
this.serviceversion = ver == null ? 0 : ver.value();
|
||||||
this.clientSncpAddress = clientSncpAddress;
|
this.clientSncpAddress = clientSncpAddress;
|
||||||
this.name = serviceName;
|
this.name = serviceName;
|
||||||
Class tn = serviceTypeOrImplClass;
|
ResourceType rt = tn.getAnnotation(ResourceType.class);
|
||||||
ResourceType rt = (ResourceType) tn.getAnnotation(ResourceType.class);
|
|
||||||
if (rt != null) tn = rt.value();
|
if (rt != null) tn = rt.value();
|
||||||
this.serviceid = Sncp.hash(tn.getName() + ':' + serviceName);
|
this.serviceid = Sncp.hash(tn.getName() + ':' + serviceName);
|
||||||
final List<SncpAction> methodens = new ArrayList<>();
|
final List<SncpAction> methodens = new ArrayList<>();
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ import org.redkale.util.Attribute;
|
|||||||
* 详情见: https://redkale.org
|
* 详情见: https://redkale.org
|
||||||
*
|
*
|
||||||
* @author zhangjx
|
* @author zhangjx
|
||||||
|
*
|
||||||
|
* @since 2.1.0
|
||||||
* @param <T> 对象类型
|
* @param <T> 对象类型
|
||||||
* @param <F> 字段类型
|
* @param <F> 字段类型
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -16,6 +16,8 @@ import org.redkale.util.Attribute;
|
|||||||
* 详情见: https://redkale.org
|
* 详情见: https://redkale.org
|
||||||
*
|
*
|
||||||
* @author zhangjx
|
* @author zhangjx
|
||||||
|
*
|
||||||
|
* @since 2.1.0
|
||||||
*/
|
*/
|
||||||
public class RpcCallAttribute implements Attribute<Object, Serializable> {
|
public class RpcCallAttribute implements Attribute<Object, Serializable> {
|
||||||
|
|
||||||
|
|||||||
29
src/org/redkale/util/Version.java
Normal file
29
src/org/redkale/util/Version.java
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
/*
|
||||||
|
* To change this license header, choose License Headers in Project Properties.
|
||||||
|
* To change this template file, choose Tools | Templates
|
||||||
|
* and open the template in the editor.
|
||||||
|
*/
|
||||||
|
package org.redkale.util;
|
||||||
|
|
||||||
|
import java.lang.annotation.*;
|
||||||
|
import static java.lang.annotation.ElementType.*;
|
||||||
|
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 版本, 可用于标记Service的接口版本变化
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* 详情见: https://redkale.org
|
||||||
|
*
|
||||||
|
* @since 2.1.0
|
||||||
|
*
|
||||||
|
* @author zhangjx
|
||||||
|
*/
|
||||||
|
@Inherited
|
||||||
|
@Documented
|
||||||
|
@Target({TYPE, METHOD})
|
||||||
|
@Retention(RUNTIME)
|
||||||
|
public @interface Version {
|
||||||
|
|
||||||
|
int value();
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user