Commit 02897568 authored by liang.tang's avatar liang.tang
Browse files

magic-api

parents
Pipeline #222 failed with stages
in 0 seconds
package org.ssssssss.magicapi.core.config;
/**
* 分页配置
*
* @author mxd
*/
public class Page {
/**
* 默认page表达式
*/
private String page = "page";
/**
* 默认size表达式
*/
private String size = "size";
/**
* 默认首页
*/
private long defaultPage = 1;
/**
* 默认页大小
*/
private long defaultSize = 10;
/**
* 最大页大小, -1 为不限制
*/
private long maxPageSize = -1;
public String getPage() {
return page;
}
public void setPage(String page) {
this.page = page;
}
public String getSize() {
return size;
}
public void setSize(String size) {
this.size = size;
}
public long getDefaultPage() {
return defaultPage;
}
public void setDefaultPage(long defaultPage) {
this.defaultPage = defaultPage;
}
public long getDefaultSize() {
return defaultSize;
}
public void setDefaultSize(long defaultSize) {
this.defaultSize = defaultSize;
}
public long getMaxPageSize() {
return maxPageSize;
}
public void setMaxPageSize(long maxPageSize) {
this.maxPageSize = maxPageSize;
}
}
package org.ssssssss.magicapi.core.config;
/**
* 接口存储配置
*
* @author mxd
*/
public class Resource {
/**
* 存储类型,默认是文件
*/
private String type = "file";
/**
* 文件存储位置
*/
private String location = "/data/magic-api/";
/**
* 是否是只读模式
*/
private boolean readonly = false;
/**
* 前缀
*/
private String prefix = "magic-api";
/**
* 使用数据库存储时的表名
*/
private String tableName = "magic_api_file";
/**
* 使用数据库存储时使用的数据源
*/
private String datasource;
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getLocation() {
return location;
}
public void setLocation(String location) {
this.location = location;
}
public boolean isReadonly() {
return readonly;
}
public void setReadonly(boolean readonly) {
this.readonly = readonly;
}
public String getPrefix() {
return prefix;
}
public void setPrefix(String prefix) {
this.prefix = prefix;
}
public String getTableName() {
return tableName;
}
public void setTableName(String tableName) {
this.tableName = tableName;
}
public String getDatasource() {
return datasource;
}
public void setDatasource(String datasource) {
this.datasource = datasource;
}
}
package org.ssssssss.magicapi.core.config;
/**
* json结果code配置
*
* @author mxd
* @since 1.1.2
*/
public class ResponseCode {
/**
* 执行成功的code值
*/
private int success = 1;
/**
* 参数验证未通过的code值
*/
private int invalid = 0;
/**
* 执行出现异常的code值
*/
private int exception = -1;
public int getSuccess() {
return success;
}
public void setSuccess(int success) {
this.success = success;
}
public int getInvalid() {
return invalid;
}
public void setInvalid(int invalid) {
this.invalid = invalid;
}
public int getException() {
return exception;
}
public void setException(int exception) {
this.exception = exception;
}
}
package org.ssssssss.magicapi.core.config;
/**
* 安全配置
*
* @author mxd
* @since 0.4.0
*/
public class Security {
/**
* 登录用的用户名
*/
private String username;
/**
* 登录用的密码
*/
private String password;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
package org.ssssssss.magicapi.core.config;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.socket.TextMessage;
import org.ssssssss.magicapi.core.event.EventAction;
import org.ssssssss.magicapi.core.context.MagicConsoleSession;
import org.ssssssss.magicapi.core.model.MagicNotify;
import org.ssssssss.magicapi.core.model.Pair;
import org.ssssssss.magicapi.core.service.MagicNotifyService;
import org.ssssssss.magicapi.utils.JsonUtils;
import org.ssssssss.script.MagicScriptDebugContext;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
public class WebSocketSessionManager {
private static final Logger logger = LoggerFactory.getLogger(WebSocketSessionManager.class);
private static final Map<String, MagicConsoleSession> SESSIONS = new ConcurrentHashMap<>();
private static MagicNotifyService magicNotifyService;
private static final Map<String, MagicScriptDebugContext> CONTEXTS = new ConcurrentHashMap<>();
private static String instanceId;
private static final int CHECK_INTERVAL = 20;
private static final int KEEPALIVE_TIMEOUT = 60 * 1000;
private static final List<Pair<String, String>> MESSAGE_CACHE = new ArrayList<>(200);
public static void add(MagicConsoleSession session) {
SESSIONS.put(session.getClientId(), session);
}
public static MagicConsoleSession getConsoleSession(String clientId) {
return SESSIONS.get(clientId);
}
static {
// 1秒1次发送日志
new ScheduledThreadPoolExecutor(1, r -> new Thread(r, "magic-api-send-log-task")).scheduleAtFixedRate(WebSocketSessionManager::flushLog, 1, 1, TimeUnit.SECONDS);
// 60秒检测一次是否在线
new ScheduledThreadPoolExecutor(1, r -> new Thread(r, "magic-api-websocket-clean-task")).scheduleAtFixedRate(WebSocketSessionManager::checkSession, CHECK_INTERVAL, CHECK_INTERVAL, TimeUnit.SECONDS);
}
public static Collection<MagicConsoleSession> getSessions() {
return SESSIONS.values();
}
public static void remove(MagicConsoleSession session) {
if (session.getClientId() != null) {
remove(session.getClientId());
}
}
public static void remove(String sessionId) {
SESSIONS.remove(sessionId);
}
public static void sendToAll(MessageType messageType, Object... values) {
String content = buildMessage(messageType, values);
sendToAll(content);
}
private static void sendToAll(String content) {
getSessions().stream().filter(MagicConsoleSession::writeable).forEach(session -> sendBySession(session, content));
sendToMachineByClientId(null, content);
}
public static void sendLogs(String sessionId, String message) {
synchronized (MESSAGE_CACHE) {
MESSAGE_CACHE.add(Pair.of(sessionId, message));
if (MESSAGE_CACHE.size() >= 100) {
flushLog();
}
}
}
public static void flushLog() {
try {
Map<String, List<String>> messages;
synchronized (MESSAGE_CACHE) {
messages = MESSAGE_CACHE.stream().collect(Collectors.groupingBy(Pair::getFirst, Collectors.mapping(Pair::getSecond, Collectors.toList())));
MESSAGE_CACHE.clear();
}
messages.forEach((clientId, logs) -> sendByClientId(clientId, logs.size() > 1 ? MessageType.LOGS : MessageType.LOG, logs));
} catch (Exception e) {
logger.warn("发送日志失败", e);
}
}
public static void sendByClientId(String clientId, MessageType messageType, Object... values) {
MagicConsoleSession session = findSession(clientId);
String content = buildMessage(messageType, values);
if (session != null && session.writeable()) {
sendBySession(session, content);
} else {
sendToMachineByClientId(clientId, content);
}
}
public static void sendToOther(String excludeClientId, MessageType messageType, Object... values) {
String content = buildMessage(messageType, values);
getSessions().stream()
.filter(MagicConsoleSession::writeable)
.filter(it -> !it.getClientId().equals(excludeClientId))
.forEach(session -> sendBySession(session, content));
sendToMachineByClientId(null, content);
}
public static void sendToMachineByClientId(String clientId, String content) {
if (magicNotifyService != null) {
// 通知其他机器去发送消息
magicNotifyService.sendNotify(new MagicNotify(instanceId, EventAction.WS_S_C, clientId, content));
}
}
public static void sendToMachine(MessageType messageType, Object... args) {
if (magicNotifyService != null) {
// 通知其他机器去发送消息
magicNotifyService.sendNotify(new MagicNotify(instanceId, EventAction.WS_S_S, null, buildMessage(messageType, args)));
}
}
public static String buildMessage(MessageType messageType, Object... values) {
StringBuilder builder = new StringBuilder(messageType.name().toLowerCase());
if (values != null) {
for (int i = 0, len = values.length; i < len; i++) {
builder.append(",");
Object value = values[i];
if (i + 1 < len || value instanceof CharSequence || value instanceof Number) {
builder.append(value);
} else {
builder.append(JsonUtils.toJsonString(value));
}
}
}
return builder.toString();
}
public static void sendByClientId(String clientId, String content) {
if (clientId == null) {
getSessions().stream().filter(MagicConsoleSession::writeable).forEach(session -> sendBySession(session, content));
} else {
MagicConsoleSession session = findSession(clientId);
if (session != null) {
sendBySession(session, content);
}
}
}
public static void sendBySession(MagicConsoleSession session, String content) {
try {
if (session != null) {
synchronized (session.getClientId()) {
session.getWebSocketSession().sendMessage(new TextMessage(content));
}
}
} catch (Exception e) {
logger.warn("发送WebSocket消息失败: {}", e.getMessage());
}
}
public static MagicConsoleSession findSession(String clientId) {
return getSessions().stream()
.filter(it -> Objects.equals(clientId, it.getClientId()))
.findFirst()
.orElse(null);
}
public static void setMagicNotifyService(MagicNotifyService magicNotifyService) {
WebSocketSessionManager.magicNotifyService = magicNotifyService;
}
public static void setInstanceId(String instanceId) {
WebSocketSessionManager.instanceId = instanceId;
}
public static void addMagicScriptContext(String sessionAndScriptId, MagicScriptDebugContext context) {
CONTEXTS.put(sessionAndScriptId, context);
}
public static MagicScriptDebugContext findMagicScriptContext(String sessionAndScriptId) {
return CONTEXTS.get(sessionAndScriptId);
}
public static void removeMagicScriptContext(String sessionAndScriptId) {
CONTEXTS.remove(sessionAndScriptId);
}
private static void checkSession() {
try {
long activateTime = System.currentTimeMillis() - KEEPALIVE_TIMEOUT;
SESSIONS.entrySet().stream()
.peek(it -> WebSocketSessionManager.sendBySession(it.getValue(), WebSocketSessionManager.buildMessage(MessageType.PING)))
.filter(it -> it.getValue().getActivateTime() < activateTime)
.collect(Collectors.toList())
.forEach(entry -> {
MagicConsoleSession session = entry.getValue();
SESSIONS.remove(entry.getKey());
session.close();
sendToAll(MessageType.USER_LOGOUT, session.getAttributes());
});
} catch (Exception ignored) {
}
}
}
package org.ssssssss.magicapi.core.context;
import org.ssssssss.magicapi.core.servlet.MagicCookie;
import org.ssssssss.magicapi.core.servlet.MagicHttpServletRequest;
import java.util.HashMap;
/**
* Cookie Context 用于脚本中获取cookie信息
*
* @author mxd
*/
public class CookieContext extends HashMap<String, String> {
private final MagicCookie[] cookies;
public CookieContext(MagicHttpServletRequest request) {
this.cookies = request.getCookies();
}
@Override
public String get(Object key) {
if (cookies != null) {
for (MagicCookie cookie : cookies) {
if (cookie.getName().equalsIgnoreCase("" + key)) {
return cookie.getValue();
}
}
}
return null;
}
}
package org.ssssssss.magicapi.core.context;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.WebSocketSession;
import org.ssssssss.magicapi.core.config.Constants;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class MagicConsoleSession {
private static final Map<String, MagicConsoleSession> cached = new ConcurrentHashMap<>();
private String clientId;
private WebSocketSession webSocketSession;
private final Map<String, Object> attributes = new HashMap<>();
private long activateTime = System.currentTimeMillis();
private MagicUser user;
private long timeout;
public MagicConsoleSession(WebSocketSession webSocketSession) {
this.webSocketSession = webSocketSession;
}
public String getClientId() {
return clientId;
}
public WebSocketSession getWebSocketSession() {
return webSocketSession;
}
public boolean writeable() {
return webSocketSession != null && webSocketSession.isOpen();
}
public static MagicConsoleSession from(WebSocketSession session) {
MagicConsoleSession magicConsoleSession = cached.get(session.getId());
if (magicConsoleSession == null) {
magicConsoleSession = new MagicConsoleSession(session);
cached.put(session.getId(), magicConsoleSession);
}
return magicConsoleSession;
}
public static void remove(WebSocketSession session) {
cached.remove(session.getId());
}
public Object getAttribute(String key){
return attributes.get(key);
}
public void setAttribute(String key, Object value){
attributes.put(key, value);
}
public Map<String, Object> getAttributes(){
return attributes;
}
public void setClientId(String clientId) {
this.clientId = clientId;
setAttribute(Constants.WEBSOCKET_ATTRIBUTE_CLIENT_ID, clientId);
}
public long getActivateTime() {
return activateTime;
}
public void setActivateTime(long activateTime) {
this.activateTime = activateTime;
}
public void close(){
if(this.webSocketSession != null){
remove(this.webSocketSession);
try {
this.webSocketSession.close(CloseStatus.SESSION_NOT_RELIABLE);
} catch (Exception ignored) {
}
this.webSocketSession = null;
}
}
public MagicUser getUser() {
return user;
}
public void setUser(MagicUser user) {
this.user = user;
}
public long getTimeout() {
return timeout;
}
public void setTimeout(long timeout) {
this.timeout = timeout;
}
}
package org.ssssssss.magicapi.core.context;
/**
* magic 用户对象
*
* @author mxd
*/
public class MagicUser {
private String id;
private String username;
private String token;
/**
* token 有效期,<=0 为永不过期
*/
private long timeout = -1;
public MagicUser() {
}
public MagicUser(String id, String username, String token) {
this.id = id;
this.username = username;
this.token = token;
}
public MagicUser(String id, String username, String token, long timeout) {
this.id = id;
this.username = username;
this.token = token;
this.timeout = timeout;
}
public static MagicUser guest() {
return new MagicUser(null, "guest", null);
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getToken() {
return token;
}
public void setToken(String token) {
this.token = token;
}
public long getTimeout() {
return timeout;
}
public void setTimeout(long timeout) {
this.timeout = timeout;
}
}
package org.ssssssss.magicapi.core.context;
import org.ssssssss.magicapi.core.servlet.MagicHttpServletRequest;
import org.ssssssss.magicapi.core.servlet.MagicHttpServletResponse;
/**
* 请求上下文
*
* @author mxd
*/
public class RequestContext {
private static final ThreadLocal<RequestEntity> REQUEST_ENTITY_THREAD_LOCAL = new InheritableThreadLocal<>();
public static MagicHttpServletRequest getHttpServletRequest() {
RequestEntity requestEntity = REQUEST_ENTITY_THREAD_LOCAL.get();
return requestEntity == null ? null : requestEntity.getRequest();
}
public static MagicHttpServletResponse getHttpServletResponse() {
RequestEntity requestEntity = REQUEST_ENTITY_THREAD_LOCAL.get();
return requestEntity == null ? null : requestEntity.getResponse();
}
public static RequestEntity getRequestEntity() {
return REQUEST_ENTITY_THREAD_LOCAL.get();
}
public static void setRequestEntity(RequestEntity requestEntity) {
REQUEST_ENTITY_THREAD_LOCAL.set(requestEntity);
}
public static void remove() {
REQUEST_ENTITY_THREAD_LOCAL.remove();
}
}
package org.ssssssss.magicapi.core.context;
import org.ssssssss.magicapi.core.model.ApiInfo;
import org.ssssssss.magicapi.core.model.DebugRequest;
import org.ssssssss.magicapi.core.servlet.MagicHttpServletRequest;
import org.ssssssss.magicapi.core.servlet.MagicHttpServletResponse;
import org.ssssssss.script.MagicScriptContext;
import java.util.Map;
import java.util.UUID;
/**
* 请求信息
*
* @author mxd
*/
public class RequestEntity {
private final Long requestTime = System.currentTimeMillis();
private final String requestId = UUID.randomUUID().toString().replace("-", "");
private ApiInfo apiInfo;
private MagicHttpServletRequest request;
private MagicHttpServletResponse response;
private boolean requestedFromTest;
private Map<String, Object> parameters;
private Map<String, Object> pathVariables;
private MagicScriptContext magicScriptContext;
private Object requestBody;
private DebugRequest debugRequest;
private Map<String, Object> headers;
private RequestEntity() {
}
public static RequestEntity create() {
return new RequestEntity();
}
public ApiInfo getApiInfo() {
return apiInfo;
}
public RequestEntity info(ApiInfo apiInfo) {
this.apiInfo = apiInfo;
return this;
}
public MagicHttpServletRequest getRequest() {
return request;
}
public RequestEntity request(MagicHttpServletRequest request) {
this.request = request;
this.debugRequest = DebugRequest.create(request);
return this;
}
public MagicHttpServletResponse getResponse() {
return response;
}
public RequestEntity response(MagicHttpServletResponse response) {
this.response = response;
return this;
}
public boolean isRequestedFromTest() {
return requestedFromTest;
}
public RequestEntity requestedFromTest(boolean requestedFromTest) {
this.requestedFromTest = requestedFromTest;
return this;
}
public boolean isRequestedFromDebug() {
return requestedFromTest && !this.debugRequest.getRequestedBreakpoints().isEmpty();
}
public Map<String, Object> getParameters() {
return parameters;
}
public RequestEntity parameters(Map<String, Object> parameters) {
this.parameters = parameters;
return this;
}
public Map<String, Object> getPathVariables() {
return pathVariables;
}
public RequestEntity pathVariables(Map<String, Object> pathVariables) {
this.pathVariables = pathVariables;
return this;
}
public Long getRequestTime() {
return requestTime;
}
public MagicScriptContext getMagicScriptContext() {
return magicScriptContext;
}
public RequestEntity setMagicScriptContext(MagicScriptContext magicScriptContext) {
this.magicScriptContext = magicScriptContext;
return this;
}
public Map<String, Object> getHeaders() {
return headers;
}
public RequestEntity setHeaders(Map<String, Object> headers) {
this.headers = headers;
return this;
}
public String getRequestId() {
return requestId;
}
/**
* 获取 RequestBody
*/
public Object getRequestBody() {
return this.requestBody;
}
public RequestEntity setRequestBody(Object requestBody) {
this.requestBody = requestBody;
return this;
}
public DebugRequest getDebugRequest() {
return debugRequest;
}
}
package org.ssssssss.magicapi.core.context;
import org.ssssssss.magicapi.core.servlet.MagicHttpSession;
import java.util.HashMap;
/**
* Session Context 用于脚本中获取Session信息
*
* @author mxd
*/
public class SessionContext extends HashMap<String, Object> {
private final MagicHttpSession session;
public SessionContext(MagicHttpSession session) {
this.session = session;
}
@Override
public Object get(Object key) {
return session != null ? session.getAttribute(key.toString()) : null;
}
@Override
public Object put(String key, Object value) {
Object oldValue = session.getAttribute(key);
session.setAttribute(key, value);
return oldValue;
}
}
package org.ssssssss.magicapi.core.event;
public enum EventAction {
CREATE, SAVE, DELETE, MOVE, LOAD, WS_C_S, WS_S_C, RELOAD, CLEAR, WS_S_S
}
package org.ssssssss.magicapi.core.event;
import org.ssssssss.magicapi.core.model.MagicEntity;
public class FileEvent extends MagicEvent {
private final MagicEntity entity;
public FileEvent(String type, EventAction action, MagicEntity entity) {
super(type, action);
this.entity = entity;
}
public FileEvent(String type, EventAction action, MagicEntity entity, String source) {
super(type, action, source);
this.entity = entity;
}
public MagicEntity getEntity() {
return entity;
}
}
package org.ssssssss.magicapi.core.event;
import org.ssssssss.magicapi.core.model.Group;
import org.ssssssss.magicapi.core.model.MagicEntity;
import java.util.Collections;
import java.util.List;
public class GroupEvent extends MagicEvent {
/**
* 分组信息
*/
private Group group;
/**
* 子分组
*/
private List<MagicEntity> entities;
public GroupEvent(String type, EventAction action, Group group) {
this(type, action, group, Collections.emptyList());
}
public GroupEvent(String type, EventAction action, Group group, List<MagicEntity> entities) {
super(type, action);
this.group = group;
this.entities = entities;
}
public Group getGroup() {
return group;
}
public void setGroup(Group group) {
this.group = group;
}
public List<MagicEntity> getEntities() {
return entities;
}
public void setEntities(List<MagicEntity> entities) {
this.entities = entities;
}
}
package org.ssssssss.magicapi.core.event;
public class MagicEvent {
/**
* 消息类型
*/
private final String type;
/**
* 消息动作
*/
private final EventAction action;
/**
* 消息来源
*/
private String source;
public MagicEvent(String type, EventAction action, String source) {
this.type = type;
this.action = action;
this.source = source;
}
public MagicEvent(String type, EventAction action) {
this(type, action, null);
}
public String getType() {
return type;
}
public EventAction getAction() {
return action;
}
public String getSource() {
return source;
}
public void setSource(String source) {
this.source = source;
}
}
package org.ssssssss.magicapi.core.event;
import org.ssssssss.magicapi.core.config.Constants;
import org.ssssssss.magicapi.core.model.MagicNotify;
public class NotifyEvent extends MagicEvent {
private String id;
public NotifyEvent(MagicNotify notify) {
super(notify.getType(), notify.getAction(), Constants.EVENT_SOURCE_NOTIFY);
this.id = notify.getId();
}
public String getId() {
return id;
}
}
package org.ssssssss.magicapi.core.exception;
import org.ssssssss.magicapi.core.model.JsonCode;
/**
* 参数错误异常
*
* @author mxd
*/
public class InvalidArgumentException extends RuntimeException {
private final transient JsonCode jsonCode;
public InvalidArgumentException(JsonCode jsonCode) {
super(jsonCode.getMessage());
this.jsonCode = jsonCode;
}
public int getCode() {
return jsonCode.getCode();
}
}
package org.ssssssss.magicapi.core.exception;
/**
* magic-api异常对象
*
* @author mxd
*/
public class MagicAPIException extends RuntimeException {
public MagicAPIException(String message) {
super(message);
}
public MagicAPIException(String message, Throwable cause) {
super(message, cause);
}
}
package org.ssssssss.magicapi.core.exception;
/**
* 登录异常对象
*
* @author mxd
*/
public class MagicLoginException extends Exception {
public MagicLoginException(String message) {
super(message);
}
}
package org.ssssssss.magicapi.core.exception;
/**
* 资源未找到
*
* @author mxd
*/
public class MagicResourceNotFoundException extends RuntimeException {
public MagicResourceNotFoundException(String message) {
super(message);
}
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment