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.exception;
import org.ssssssss.magicapi.core.model.JsonCode;
/**
* 接口验证异常
*
* @author mxd
*/
public class ValidateException extends RuntimeException {
private final JsonCode jsonCode;
public ValidateException(JsonCode jsonCode, String message) {
super(message);
this.jsonCode = jsonCode;
}
public JsonCode getJsonCode() {
return jsonCode;
}
}
package org.ssssssss.magicapi.core.handler;
import org.ssssssss.magicapi.core.annotation.Message;
import org.ssssssss.magicapi.core.config.MessageType;
import org.ssssssss.magicapi.core.config.WebSocketSessionManager;
import org.ssssssss.magicapi.core.config.Constants;
import org.ssssssss.magicapi.core.context.MagicConsoleSession;
public class MagicCoordinationHandler {
@Message(MessageType.SET_FILE_ID)
public void setFileId(MagicConsoleSession session, String fileId) {
session.setAttribute(Constants.WEBSOCKET_ATTRIBUTE_FILE_ID, fileId);
WebSocketSessionManager.sendToOther(session.getClientId(), MessageType.INTO_FILE_ID, session.getAttribute(Constants.WEBSOCKET_ATTRIBUTE_CLIENT_ID), fileId);
}
}
package org.ssssssss.magicapi.core.handler;
import org.apache.commons.lang3.StringUtils;
import org.ssssssss.magicapi.core.annotation.Message;
import org.ssssssss.magicapi.core.config.MessageType;
import org.ssssssss.magicapi.core.config.WebSocketSessionManager;
import org.ssssssss.magicapi.core.context.MagicConsoleSession;
import org.ssssssss.script.MagicScriptDebugContext;
import java.util.Collections;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* WebSocket Debug 处理器
*
* @author mxd
*/
public class MagicDebugHandler {
/**
* 设置断点
* 当本机没有该Session时,通知其他机器处理
*/
@Message(MessageType.SET_BREAKPOINT)
public boolean setBreakPoint(MagicConsoleSession session, String scriptId, String breakpoints) {
MagicScriptDebugContext context = WebSocketSessionManager.findMagicScriptContext(session.getClientId() + scriptId);
if (context != null) {
context.setBreakpoints(Stream.of(breakpoints.split(",")).map(Integer::valueOf).collect(Collectors.toList()));
return true;
}
return false;
}
/**
* 恢复断点
* 当本机没有该Session时,通知其他机器处理
*/
@Message(MessageType.RESUME_BREAKPOINT)
public boolean resumeBreakpoint(MagicConsoleSession session, String scriptId, String stepInto, String breakpoints) {
MagicScriptDebugContext context = WebSocketSessionManager.findMagicScriptContext(session.getClientId() + scriptId);
if (context != null) {
context.setStepInto("1".equals(stepInto));
if (StringUtils.isNotBlank(breakpoints)) {
context.setBreakpoints(Stream.of(breakpoints.split("\\|")).map(Integer::valueOf).collect(Collectors.toList()));
} else {
context.setBreakpoints(Collections.emptyList());
}
try {
context.singal();
} catch (InterruptedException ignored) {
}
return true;
}
return false;
}
}
package org.ssssssss.magicapi.core.handler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;
import org.ssssssss.magicapi.core.annotation.Message;
import org.ssssssss.magicapi.core.config.MessageType;
import org.ssssssss.magicapi.core.config.WebSocketSessionManager;
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.service.MagicNotifyService;
import org.ssssssss.magicapi.utils.JsonUtils;
import org.ssssssss.script.reflection.MethodInvoker;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Stream;
import static org.ssssssss.magicapi.core.config.Constants.EMPTY_OBJECT_ARRAY;
/**
* WebSocket 分发器
*
* @author mxd
*/
public class MagicWebSocketDispatcher extends TextWebSocketHandler {
private static final Logger logger = LoggerFactory.getLogger(MagicWebSocketDispatcher.class);
private static final Map<String, MethodInvoker> HANDLERS = new HashMap<>();
private final String instanceId;
private final MagicNotifyService magicNotifyService;
public MagicWebSocketDispatcher(String instanceId, MagicNotifyService magicNotifyService, List<Object> websocketMessageHandlers) {
this.instanceId = instanceId;
this.magicNotifyService = magicNotifyService;
WebSocketSessionManager.setMagicNotifyService(magicNotifyService);
WebSocketSessionManager.setInstanceId(instanceId);
websocketMessageHandlers.forEach(websocketMessageHandler ->
Stream.of(websocketMessageHandler.getClass().getDeclaredMethods())
.filter(it -> it.getAnnotation(Message.class) != null)
.forEach(method -> HANDLERS.put(method.getAnnotation(Message.class).value().name().toLowerCase(), new MethodInvoker(method, websocketMessageHandler)))
);
}
private static Object findHandleAndInvoke(MagicConsoleSession session, String payload) {
// messageType[, data][,data]
int index = payload.indexOf(",");
String msgType = index == -1 ? payload : payload.substring(0, index);
MethodInvoker invoker = HANDLERS.get(msgType);
if (invoker != null) {
Object returnValue;
try {
Class<?>[] pTypes = invoker.getParameterTypes();
int pCount = pTypes.length;
if (pCount == 0) {
returnValue = invoker.invoke0(invoker.getDefaultTarget(), null, EMPTY_OBJECT_ARRAY);
} else {
Object[] pValues = new Object[pCount];
for (int i = 0; i < pCount; i++) {
Class<?> pType = pTypes[i];
if (pType == MagicConsoleSession.class) {
pValues[i] = session;
} else if (pType == String.class) {
int subIndex = payload.indexOf(",", index + 1);
if (subIndex > -1) {
pValues[i] = payload.substring(index + 1, index = subIndex);
} else if (index > -1) {
pValues[i] = payload.substring(index + 1);
}
} else {
pValues[i] = JsonUtils.readValue(payload, pType);
}
}
returnValue = invoker.invoke0(invoker.getDefaultTarget(), null, pValues);
}
return returnValue;
} catch (Throwable e) {
logger.error("WebSocket消息处理出错", e);
}
}
return null;
}
public static void processMessageReceived(String clientId, String payload) {
MagicConsoleSession session = WebSocketSessionManager.findSession(clientId);
if (session != null) {
findHandleAndInvoke(session, payload);
}
}
public static void processWebSocketEventMessage(String payload) {
findHandleAndInvoke(null, payload);
}
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) {
MagicConsoleSession mcsession = MagicConsoleSession.from(session);
WebSocketSessionManager.remove(mcsession);
MagicConsoleSession.remove(session);
if(mcsession.getClientId() != null && mcsession.getAttributes() != null && !mcsession.getAttributes().isEmpty()){
WebSocketSessionManager.sendToAll(MessageType.USER_LOGOUT, mcsession.getAttributes());
}
}
@Override
protected void handleTextMessage(WebSocketSession session, TextMessage message) {
MagicConsoleSession consoleSession = MagicConsoleSession.from(session);
Object returnValue = findHandleAndInvoke(consoleSession, message.getPayload());
// 如果未成功处理消息,则通知其他机器去处理消息
if (Boolean.FALSE.equals(returnValue)) {
magicNotifyService.sendNotify(new MagicNotify(instanceId, EventAction.WS_C_S, consoleSession.getClientId(), message.getPayload()));
}
}
}
package org.ssssssss.magicapi.core.handler;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpHeaders;
import org.ssssssss.magicapi.core.annotation.Message;
import org.ssssssss.magicapi.core.config.Constants;
import org.ssssssss.magicapi.core.config.MessageType;
import org.ssssssss.magicapi.core.config.WebSocketSessionManager;
import org.ssssssss.magicapi.core.context.MagicConsoleSession;
import org.ssssssss.magicapi.core.context.MagicUser;
import org.ssssssss.magicapi.core.interceptor.AuthorizationInterceptor;
import org.ssssssss.magicapi.utils.IpUtils;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;
/**
* UI上其它操作处理
*
* @author mxd
*/
public class MagicWorkbenchHandler {
private final AuthorizationInterceptor authorizationInterceptor;
private final static MagicUser guest = new MagicUser("guest","游客", "unauthorization");
private final static Logger logger = LoggerFactory.getLogger(MagicWorkbenchHandler.class);
public MagicWorkbenchHandler(AuthorizationInterceptor authorizationInterceptor) {
this.authorizationInterceptor = authorizationInterceptor;
}
@Message(MessageType.LOGIN)
public void onLogin(MagicConsoleSession session, String token, String clientId) {
session.setClientId(clientId);
MagicUser user = null;
try {
user = authorizationInterceptor.getUserByToken(token);
} catch (Exception e) {
if(!authorizationInterceptor.requireLogin()){
user = guest;
}
}
if (user != null) {
String ip = Optional.ofNullable(session.getWebSocketSession().getRemoteAddress()).map(it -> it.getAddress().getHostAddress()).orElse("unknown");
HttpHeaders headers = session.getWebSocketSession().getHandshakeHeaders();
ip = IpUtils.getRealIP(ip, headers::getFirst, null);
if (user.getTimeout() > 0) {
session.setUser(user);
session.setTimeout(user.getTimeout() * 1000 + System.currentTimeMillis());
}
session.setAttribute(Constants.WEBSOCKET_ATTRIBUTE_USER_ID, user.getId());
session.setAttribute(Constants.WEBSOCKET_ATTRIBUTE_USER_IP, StringUtils.defaultIfBlank(ip, "unknown"));
session.setAttribute(Constants.WEBSOCKET_ATTRIBUTE_USER_NAME, user.getUsername());
session.setActivateTime(System.currentTimeMillis());
synchronized (MagicWorkbenchHandler.class){
if(WebSocketSessionManager.getConsoleSession(clientId) != null){
WebSocketSessionManager.sendBySession(session, WebSocketSessionManager.buildMessage(MessageType.LOGIN_RESPONSE, "-1"));
return;
}
WebSocketSessionManager.add(session);
}
WebSocketSessionManager.sendBySession(session, WebSocketSessionManager.buildMessage(MessageType.LOGIN_RESPONSE, "1", session.getAttributes()));
List<Map<String, Object>> messages = getOnlineUsers();
if(!messages.isEmpty()){
WebSocketSessionManager.sendByClientId(session.getClientId(), WebSocketSessionManager.buildMessage(MessageType.ONLINE_USERS, messages));
}
WebSocketSessionManager.sendToMachine(MessageType.SEND_ONLINE, session.getClientId());
WebSocketSessionManager.sendToOther(session.getClientId(), MessageType.USER_LOGIN, session.getAttributes());
} else {
WebSocketSessionManager.sendBySession(session, WebSocketSessionManager.buildMessage(MessageType.LOGIN_RESPONSE, "0"));
}
}
@Message(MessageType.SEND_ONLINE)
public void sendOnline(String clientId){
List<Map<String, Object>> messages = getOnlineUsers();
if(!messages.isEmpty()){
WebSocketSessionManager.sendToMachineByClientId(clientId, WebSocketSessionManager.buildMessage(MessageType.ONLINE_USERS, messages));
}
}
@Message(MessageType.PONG)
public String pong(MagicConsoleSession session){
session.setActivateTime(System.currentTimeMillis());
MagicUser user = session.getUser();
if (user != null && session.getTimeout() - System.currentTimeMillis() < 60 * 1000){
String oldToken = user.getToken();
authorizationInterceptor.refreshToken(user);
String newToken = user.getToken();
session.setTimeout(System.currentTimeMillis() + user.getTimeout() * 1000);
if (!Objects.equals(newToken, oldToken)) {
WebSocketSessionManager.sendBySession(session, WebSocketSessionManager.buildMessage(MessageType.REFRESH_TOKEN, newToken));
}
}
return null;
}
private List<Map<String, Object>> getOnlineUsers(){
return WebSocketSessionManager.getSessions().stream()
.map(MagicConsoleSession::getAttributes)
.collect(Collectors.toList());
}
}
package org.ssssssss.magicapi.core.interceptor;
/**
* 鉴权类型枚举
*
* @author mxd
*/
public enum Authorization {
/**
* 无实际意义
*/
NONE,
/**
* 执行保存动作
*/
SAVE,
/**
* 执行查看详情、列表动作
*/
VIEW,
/**
* 执行删除动作
*/
DELETE,
/**
* 执行导出动作
*/
DOWNLOAD,
/**
* 执行上传动作
*/
UPLOAD,
/**
* 执行推送动作
*/
PUSH,
/**
* 锁定动作
*/
LOCK,
/**
* 解锁动作
*/
UNLOCK,
/**
* 重新加载
*/
RELOAD
}
package org.ssssssss.magicapi.core.interceptor;
import org.ssssssss.magicapi.core.context.MagicUser;
import org.ssssssss.magicapi.core.exception.MagicLoginException;
import org.ssssssss.magicapi.core.model.Group;
import org.ssssssss.magicapi.core.model.MagicEntity;
import org.ssssssss.magicapi.core.servlet.MagicHttpServletRequest;
/**
* UI权限拦截器
*
* @author mxd
*/
public interface AuthorizationInterceptor {
/**
* 是否需要登录
*
* @return true 需要登录, false 不需要登录
*/
default boolean requireLogin() {
return false;
}
/**
* 根据Token获取User对象
*
* @param token token值
* @return 登录成功后返回MagicUser对象
* @throws MagicLoginException 登录失败抛出
*/
default MagicUser getUserByToken(String token) throws MagicLoginException {
return null;
}
/**
* 根据用户名,密码登录
*
* @param username 用户名
* @param password 密码
* @return 登录成功后返回MagicUser对象
* @throws MagicLoginException 登录失败抛出
*/
default MagicUser login(String username, String password) throws MagicLoginException {
return null;
}
/**
* 退出登录
*
* @param token token值
*/
default void logout(String token) {
}
/**
* 是否拥有页面按钮的权限
*
* @param magicUser 登录的用户对象
* @param request HttpServletRequest
* @param authorization 鉴权方法
* @return true 有权限访问, false 无权限访问
*/
default boolean allowVisit(MagicUser magicUser, MagicHttpServletRequest request, Authorization authorization) {
return true;
}
/**
* 是否拥有对该接口的增删改权限
*
* @param magicUser 登录的用户对象
* @param request HttpServletRequest
* @param authorization 鉴权方法
* @param entity 接口、函数、数据源信息
* @return true 有权限访问, false 无权限访问
*/
default boolean allowVisit(MagicUser magicUser, MagicHttpServletRequest request, Authorization authorization, MagicEntity entity) {
return allowVisit(magicUser, request, authorization);
}
/**
* 是否拥有对该分组的增删改权限
*
* @param magicUser 登录的用户对象
* @param request HttpServletRequest
* @param authorization 鉴权方法
* @param group 分组信息
* @return true 有权限访问, false 无权限访问
*/
default boolean allowVisit(MagicUser magicUser, MagicHttpServletRequest request, Authorization authorization, Group group) {
return allowVisit(magicUser, request, authorization);
}
/**
* 刷新 token, 重新赋值对象内的token和timeout
* @param user
* @return
*/
default void refreshToken(MagicUser user) {
}
}
package org.ssssssss.magicapi.core.interceptor;
import org.ssssssss.magicapi.core.context.MagicUser;
import org.ssssssss.magicapi.core.exception.MagicLoginException;
import org.ssssssss.magicapi.utils.MD5Utils;
import java.util.Objects;
/**
* 默认UI鉴权实现
*
* @author mxd
*/
public class DefaultAuthorizationInterceptor implements AuthorizationInterceptor {
private final boolean requireLogin;
private String validToken;
private MagicUser configMagicUser;
public DefaultAuthorizationInterceptor(String username, String password) {
if (this.requireLogin = username != null && password != null) {
this.validToken = MD5Utils.encrypt(String.format("%s||%s", username, password));
this.configMagicUser = new MagicUser(username, username, this.validToken);
}
}
@Override
public boolean requireLogin() {
return this.requireLogin;
}
@Override
public MagicUser getUserByToken(String token) throws MagicLoginException {
if (requireLogin && Objects.equals(validToken, token)) {
return configMagicUser;
}
throw new MagicLoginException("token无效");
}
@Override
public MagicUser login(String username, String password) throws MagicLoginException {
if (requireLogin && Objects.equals(MD5Utils.encrypt(String.format("%s||%s", username, password)), this.validToken)) {
return configMagicUser;
}
throw new MagicLoginException("用户名或密码不正确");
}
}
package org.ssssssss.magicapi.core.interceptor;
import org.ssssssss.magicapi.core.model.JsonBean;
import org.ssssssss.magicapi.core.context.RequestEntity;
import org.ssssssss.magicapi.utils.ScriptManager;
import org.ssssssss.script.MagicScriptContext;
/**
* 默认结果封装实现
*
* @author mxd
*/
public class DefaultResultProvider implements ResultProvider {
private final String responseScript;
public DefaultResultProvider(String responseScript) {
this.responseScript = responseScript;
}
@Override
public Object buildResult(RequestEntity requestEntity, int code, String message, Object data) {
long timestamp = System.currentTimeMillis();
if (this.responseScript != null) {
MagicScriptContext context = new MagicScriptContext();
context.setScriptName(requestEntity.getMagicScriptContext().getScriptName());
context.set("code", code);
context.set("message", message);
context.set("data", data);
context.set("apiInfo", requestEntity.getApiInfo());
context.set("request", requestEntity.getRequest());
context.set("response", requestEntity.getResponse());
context.set("timestamp", timestamp);
context.set("requestTime", requestEntity.getRequestTime());
context.set("executeTime", timestamp - requestEntity.getRequestTime());
return ScriptManager.executeExpression(responseScript, context);
} else {
return new JsonBean<>(code, message, data, (int) (timestamp - requestEntity.getRequestTime()));
}
}
}
package org.ssssssss.magicapi.core.interceptor;
import org.springframework.web.method.HandlerMethod;
import org.ssssssss.magicapi.core.annotation.Valid;
import org.ssssssss.magicapi.core.config.Constants;
import org.ssssssss.magicapi.core.config.MagicCorsFilter;
import org.ssssssss.magicapi.core.exception.MagicLoginException;
import org.ssssssss.magicapi.core.servlet.MagicHttpServletRequest;
import org.ssssssss.magicapi.core.servlet.MagicHttpServletResponse;
import org.ssssssss.magicapi.core.web.MagicController;
public abstract class MagicWebRequestInterceptor {
private final MagicCorsFilter magicCorsFilter;
private final AuthorizationInterceptor authorizationInterceptor;
public MagicWebRequestInterceptor(MagicCorsFilter magicCorsFilter, AuthorizationInterceptor authorizationInterceptor) {
this.magicCorsFilter = magicCorsFilter;
this.authorizationInterceptor = authorizationInterceptor;
}
public void handle(Object handler, MagicHttpServletRequest request, MagicHttpServletResponse response) throws MagicLoginException {
HandlerMethod handlerMethod;
if (handler instanceof HandlerMethod) {
handlerMethod = (HandlerMethod) handler;
handler = handlerMethod.getBean();
if (handler instanceof MagicController) {
if (magicCorsFilter != null) {
magicCorsFilter.process(request, response);
}
Valid valid = handlerMethod.getMethodAnnotation(Valid.class);
boolean requiredLogin = authorizationInterceptor.requireLogin();
boolean validRequiredLogin = (valid == null || valid.requireLogin());
if (requiredLogin && validRequiredLogin) {
request.setAttribute(Constants.ATTRIBUTE_MAGIC_USER, authorizationInterceptor.getUserByToken(request.getHeader(Constants.MAGIC_TOKEN_HEADER)));
}
((MagicController) handler).doValid(request, valid);
}
}
}
}
package org.ssssssss.magicapi.core.interceptor;
import org.ssssssss.magicapi.core.context.RequestEntity;
import org.ssssssss.magicapi.core.model.ApiInfo;
import org.ssssssss.magicapi.core.servlet.MagicHttpServletRequest;
import org.ssssssss.magicapi.core.servlet.MagicHttpServletResponse;
import org.ssssssss.script.MagicScriptContext;
/**
* 请求拦截器
*
* @author mxd
*/
public interface RequestInterceptor {
/**
* 请求之前执行
*
* @param requestEntity 请求对象
* @return 当返回对象时,直接将此对象返回到页面,返回null时,继续执行后续操作
*/
default Object preHandle(RequestEntity requestEntity) throws Exception {
return preHandle(requestEntity.getApiInfo(), requestEntity.getMagicScriptContext(), requestEntity.getRequest(), requestEntity.getResponse());
}
/**
* 请求之前执行
*
* @param info 接口信息
* @param context 脚本上下文
* @param request HttpServletRequest
* @param response HttpServletResponse
* @return 当返回对象时,直接将此对象返回到页面,返回null时,继续执行后续操作
* @throws Exception 处理失败时抛出的异常
*/
default Object preHandle(ApiInfo info, MagicScriptContext context, MagicHttpServletRequest request, MagicHttpServletResponse response) throws Exception {
return null;
}
/**
* 执行完毕之后执行
*
* @param info 接口信息
* @param context 脚本上下文
* @param returnValue 即将要返回到页面的值
* @param request HttpServletRequest
* @param response HttpServletResponse
* @return 返回到页面的对象, 当返回null时执行后续拦截器,否则直接返回该值,不执行后续拦截器
* @throws Exception 处理失败时抛出的异常
*/
default Object postHandle(ApiInfo info, MagicScriptContext context, Object returnValue, MagicHttpServletRequest request, MagicHttpServletResponse response) throws Exception {
return null;
}
/**
* 执行完毕之后执行
*
* @param requestEntity 请求对象
* @param returnValue 即将要返回到页面的值
* @return 返回到页面的对象, 当返回null时执行后续拦截器,否则直接返回该值,不执行后续拦截器
*/
default Object postHandle(RequestEntity requestEntity, Object returnValue) throws Exception {
return postHandle(requestEntity.getApiInfo(), requestEntity.getMagicScriptContext(), returnValue, requestEntity.getRequest(), requestEntity.getResponse());
}
/**
* 接口执行完毕之后执行
*
* @param requestEntity 请求对象
* @param returnValue 即将要返回到页面的值
* @param throwable 异常对象
*/
default void afterCompletion(RequestEntity requestEntity, Object returnValue, Throwable throwable) {
afterCompletion(requestEntity.getApiInfo(), requestEntity.getMagicScriptContext(), returnValue, requestEntity.getRequest(), requestEntity.getResponse(), throwable);
}
/**
* 接口执行完毕之后执行
*
* @param info 接口信息
* @param context 脚本上下文
* @param returnValue 即将要返回到页面的值
* @param request HttpServletRequest
* @param response HttpServletResponse
* @param throwable 异常对象
*/
default void afterCompletion(ApiInfo info, MagicScriptContext context, Object returnValue, MagicHttpServletRequest request, MagicHttpServletResponse response, Throwable throwable) {
}
}
package org.ssssssss.magicapi.core.interceptor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.ssssssss.magicapi.modules.db.model.Page;
import org.ssssssss.magicapi.modules.db.model.PageResult;
import org.ssssssss.magicapi.core.context.RequestEntity;
import org.ssssssss.script.exception.MagicScriptAssertException;
import org.ssssssss.script.exception.MagicScriptException;
import org.ssssssss.script.functions.ObjectConvertExtension;
import org.ssssssss.script.runtime.ExitValue;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import static org.ssssssss.magicapi.core.config.Constants.*;
/**
* 结果构建接口
*
* @author mxd
*/
public interface ResultProvider {
Logger logger = LoggerFactory.getLogger(ResultProvider.class);
/**
* 根据异常内容构建结果
*
* @param requestEntity 请求信息
* @param root 异常对象
*/
default Object buildResult(RequestEntity requestEntity, Throwable root) {
MagicScriptException se = null;
Throwable parent = root;
do {
if (parent instanceof MagicScriptAssertException) {
MagicScriptAssertException sae = (MagicScriptAssertException) parent;
return buildResult(requestEntity, sae.getCode(), sae.getMessage());
}
if (parent instanceof MagicScriptException) {
se = (MagicScriptException) parent;
}
} while ((parent = parent.getCause()) != null);
logger.error("调用接口出错", root);
if (se != null) {
return buildException(requestEntity, se);
}
return buildException(requestEntity, root);
}
/**
* 构建JSON返回结果,code和message 默认为 1 success
*
* @param requestEntity 请求相关信息
* @param data 返回内容
*/
default Object buildResult(RequestEntity requestEntity, Object data) {
if (data instanceof ExitValue) {
ExitValue exitValue = (ExitValue) data;
Object[] values = exitValue.getValues();
int code = values.length > 0 ? ObjectConvertExtension.asInt(values[0], RESPONSE_CODE_SUCCESS) : RESPONSE_CODE_SUCCESS;
String message = values.length > 1 ? Objects.toString(values[1], RESPONSE_MESSAGE_SUCCESS) : RESPONSE_MESSAGE_SUCCESS;
return buildResult(requestEntity, code, message, values.length > 2 ? values[2] : null);
}
return buildResult(requestEntity, RESPONSE_CODE_SUCCESS, RESPONSE_MESSAGE_SUCCESS, data);
}
/**
* 构建JSON返回结果
*
* @param requestEntity 请求相关信息
* @param code 状态码
* @param message 状态说明
*/
default Object buildResult(RequestEntity requestEntity, int code, String message) {
return buildResult(requestEntity, code, message, null);
}
/**
* 构建异常返回结果
*
* @param requestEntity 请求相关信息
* @param throwable 异常信息
* @since 1.2.2
*/
default Object buildException(RequestEntity requestEntity, Throwable throwable) {
return buildResult(requestEntity, RESPONSE_CODE_EXCEPTION, "系统内部出现错误");
}
/**
* 构建JSON返回结果
*
* @param requestEntity 请求相关信息
* @param code 状态码
* @param message 状态说明
* @param data 数据内容,可以通过data的类型判断是否是分页结果进行区分普通结果集和分页结果集
*/
Object buildResult(RequestEntity requestEntity, int code, String message, Object data);
/**
* @param requestEntity 请求相关信息
* @param page 分页对象
* @param total 总数
* @param data 数据内容
*/
default Object buildPageResult(RequestEntity requestEntity, Page page, long total, List<Map<String, Object>> data) {
return new PageResult<>(total, data);
}
}
package org.ssssssss.magicapi.core.logging;
import ch.qos.logback.classic.ClassicConstants;
import ch.qos.logback.core.CoreConstants;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.text.SimpleDateFormat;
import java.util.Date;
public class Formatter {
private final static String[] SPACES = {" ", " ", " ", " ", // 1,2,4,8
// spaces
" ", // 16 spaces
" "}; // 32 spaces
private final static CachingDateFormatter CACHING_DATE_FORMATTER = new CachingDateFormatter("yyyy-MM-dd HH:mm:ss.SSS");
private final static TargetLengthBasedClassNameAbbreviator ABBREVIATOR = new TargetLengthBasedClassNameAbbreviator(39);
private StringBuilder buf = new StringBuilder();
private Formatter() {
}
public static Formatter create() {
return new Formatter();
}
private static void leftPad(StringBuilder buf, String s, int desiredLength) {
int actualLen = 0;
if (s != null) {
actualLen = s.length();
}
if (actualLen < desiredLength) {
spacePad(buf, desiredLength - actualLen);
}
if (s != null) {
buf.append(s);
}
}
private static void rightPad(StringBuilder buf, String s, int desiredLength) {
int actualLen = 0;
if (s != null) {
actualLen = s.length();
}
if (s != null) {
buf.append(s);
}
if (actualLen < desiredLength) {
spacePad(buf, desiredLength - actualLen);
}
}
/**
* Fast space padding method.
*/
private static void spacePad(StringBuilder sbuf, int length) {
while (length >= 32) {
sbuf.append(SPACES[5]);
length -= 32;
}
for (int i = 4; i >= 0; i--) {
if ((length & (1 << i)) != 0) {
sbuf.append(SPACES[i]);
}
}
}
public Formatter timestamp(long timestamp) {
buf.append(CACHING_DATE_FORMATTER.format(timestamp));
return this;
}
public Formatter space() {
buf.append(SPACES[0]);
return this;
}
public Formatter value(String value) {
buf.append(value);
return this;
}
public Formatter newline() {
buf.append("\n");
return this;
}
public Formatter thread(String value) {
return alignment(value, 15, 15, true, true);
}
public Formatter level(String value) {
return alignment(value, 5, 2147483647, true, true);
}
public Formatter loggerName(String value) {
return alignment(ABBREVIATOR.abbreviate(value), 40, 40, true, false);
}
@Override
public String toString() {
return buf.toString();
}
public Formatter throwable(Throwable throwable) {
if (throwable != null) {
this.newline();
StringWriter sw = new StringWriter(1024);
PrintWriter writer = new PrintWriter(sw);
throwable.printStackTrace(writer);
writer.close();
buf.append(sw.getBuffer());
this.newline();
}
return this;
}
private Formatter alignment(String value, int min, int max, boolean leftTruncate, boolean leftPad) {
if (value == null) {
if (0 < min) {
spacePad(buf, min);
}
} else {
int len = value.length();
if (len > max) {
if (leftTruncate) {
buf.append(value.substring(len - max));
} else {
buf.append(value, 0, max);
}
} else if (len < min) {
if (leftPad) {
leftPad(buf, value, min);
} else {
rightPad(buf, value, min);
}
} else {
buf.append(value);
}
}
return this;
}
private static class CachingDateFormatter {
final SimpleDateFormat sdf;
long lastTimestamp = -1;
String cachedStr = null;
public CachingDateFormatter(String pattern) {
sdf = new SimpleDateFormat(pattern);
}
public final String format(long now) {
// SimpleDateFormat is not thread safe.
// See also the discussion in http://jira.qos.ch/browse/LBCLASSIC-36
// DateFormattingThreadedThroughputCalculator and SelectiveDateFormattingRunnable
// are also noteworthy
// The now == lastTimestamp guard minimizes synchronization
synchronized (this) {
if (now != lastTimestamp) {
lastTimestamp = now;
cachedStr = sdf.format(new Date(now));
}
return cachedStr;
}
}
}
private static class TargetLengthBasedClassNameAbbreviator {
final int targetLength;
public TargetLengthBasedClassNameAbbreviator(int targetLength) {
this.targetLength = targetLength;
}
public String abbreviate(String fqClassName) {
StringBuilder buf = new StringBuilder(targetLength);
if (fqClassName == null) {
throw new IllegalArgumentException("Class name may not be null");
}
int inLen = fqClassName.length();
if (inLen < targetLength) {
return fqClassName;
}
int[] dotIndexesArray = new int[ClassicConstants.MAX_DOTS];
// a.b.c contains 2 dots but 2+1 parts.
// see also http://jira.qos.ch/browse/LBCLASSIC-110
int[] lengthArray = new int[ClassicConstants.MAX_DOTS + 1];
int dotCount = computeDotIndexes(fqClassName, dotIndexesArray);
// System.out.println();
// System.out.println("Dot count for [" + className + "] is " + dotCount);
// if there are not dots than abbreviation is not possible
if (dotCount == 0) {
return fqClassName;
}
// printArray("dotArray: ", dotArray);
computeLengthArray(fqClassName, dotIndexesArray, lengthArray, dotCount);
// printArray("lengthArray: ", lengthArray);
for (int i = 0; i <= dotCount; i++) {
if (i == 0) {
buf.append(fqClassName.substring(0, lengthArray[i] - 1));
} else {
buf.append(fqClassName.substring(dotIndexesArray[i - 1], dotIndexesArray[i - 1] + lengthArray[i]));
}
// System.out.println("i=" + i + ", buf=" + buf);
}
return buf.toString();
}
int computeDotIndexes(final String className, int[] dotArray) {
int dotCount = 0;
int k = 0;
while (true) {
// ignore the $ separator in our computations. This is both convenient
// and sensible.
k = className.indexOf(CoreConstants.DOT, k);
if (k != -1 && dotCount < ClassicConstants.MAX_DOTS) {
dotArray[dotCount] = k;
dotCount++;
k++;
} else {
break;
}
}
return dotCount;
}
void computeLengthArray(final String className, int[] dotArray, int[] lengthArray, int dotCount) {
int toTrim = className.length() - targetLength;
// System.out.println("toTrim=" + toTrim);
// int toTrimAvarage = 0;
int len;
for (int i = 0; i < dotCount; i++) {
int previousDotPosition = -1;
if (i > 0) {
previousDotPosition = dotArray[i - 1];
}
int available = dotArray[i] - previousDotPosition - 1;
// System.out.println("i=" + i + ", available = " + available);
len = (available < 1) ? available : 1;
// System.out.println("i=" + i + ", toTrim = " + toTrim);
if (toTrim > 0) {
len = (available < 1) ? available : 1;
} else {
len = available;
}
toTrim -= (available - len);
lengthArray[i] = len + 1;
}
int lastDotIndex = dotCount - 1;
lengthArray[dotCount] = className.length() - dotArray[lastDotIndex];
}
}
}
package org.ssssssss.magicapi.core.logging;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.core.Filter;
import org.apache.logging.log4j.core.Layout;
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.LoggerContext;
import org.apache.logging.log4j.core.appender.AbstractAppender;
import org.apache.logging.log4j.core.config.Configuration;
import org.apache.logging.log4j.core.config.LoggerConfig;
import org.apache.logging.log4j.core.config.Property;
import org.apache.logging.log4j.core.layout.PatternLayout;
import java.nio.charset.StandardCharsets;
/**
* 对接Log4j2
*
* @author mxd
*/
public class Log4j2LoggerContext implements MagicLoggerContext {
@Override
public void generateAppender() {
LoggerContext context = (LoggerContext) LogManager.getContext(false);
Configuration configuration = context.getConfiguration();
LoggerConfig logger = configuration.getRootLogger();
PatternLayout layout = PatternLayout.newBuilder()
.withCharset(StandardCharsets.UTF_8)
.withConfiguration(configuration)
.withPattern("%d %t %p %X{TracingMsg} %c - %m%n")
.build();
MagicLog4j2Appender appender = new MagicLog4j2Appender("Magic", logger.getFilter(), layout);
appender.start();
configuration.addAppender(appender);
logger.addAppender(appender, logger.getLevel(), logger.getFilter());
context.updateLoggers(configuration);
}
static class MagicLog4j2Appender extends AbstractAppender {
MagicLog4j2Appender(String name, Filter filter, Layout<String> layout) {
super(name, filter, layout, true, Property.EMPTY_ARRAY);
}
@Override
public void append(LogEvent event) {
String message = Formatter.create()
.timestamp(event.getTimeMillis())
.space()
.level(event.getLevel().toString())
.value(" --- [")
.thread(event.getThreadName())
.value("] ")
.loggerName(event.getLoggerName())
.value(": ")
.value(event.getMessage().getFormattedMessage())
.newline()
.throwable(event.getThrown())
.toString();
MagicLoggerContext.println(message);
}
}
}
package org.ssssssss.magicapi.core.logging;
import org.apache.log4j.AppenderSkeleton;
import org.apache.log4j.LogManager;
import org.apache.log4j.PatternLayout;
import org.apache.log4j.spi.LoggingEvent;
import org.apache.log4j.spi.RootLogger;
/**
* 对接Log4j
*
* @author mxd
*/
public class Log4jLoggerContext implements MagicLoggerContext {
@Override
public void generateAppender() {
RootLogger logger = (RootLogger) LogManager.getRootLogger();
PatternLayout patternLayout = new PatternLayout("%d %p [%c] - %m%n");
MagicLog4jAppender magicLog4jAppender = new MagicLog4jAppender();
magicLog4jAppender.setLayout(patternLayout);
logger.addAppender(magicLog4jAppender);
}
static class MagicLog4jAppender extends AppenderSkeleton {
@Override
protected void append(LoggingEvent event) {
String message = Formatter.create()
.timestamp(event.getTimeStamp())
.space()
.level(event.getLevel().toString())
.value(" --- [")
.thread(event.getThreadName())
.value("] ")
.loggerName(event.getLoggerName())
.value(": ")
.value(event.getRenderedMessage())
.newline()
.throwable(event.getThrowableInformation() == null ? null : event.getThrowableInformation().getThrowable())
.toString();
MagicLoggerContext.println(message);
}
@Override
public void close() {
}
@Override
public boolean requiresLayout() {
return false;
}
}
}
package org.ssssssss.magicapi.core.logging;
import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.classic.spi.IThrowableProxy;
import ch.qos.logback.classic.spi.ThrowableProxy;
import ch.qos.logback.core.UnsynchronizedAppenderBase;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* 对接Logback
*
* @author mxd
*/
public class LogbackLoggerContext implements MagicLoggerContext {
@Override
public void generateAppender() {
LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory();
ch.qos.logback.classic.Logger logger = context.getLogger(Logger.ROOT_LOGGER_NAME);
MagicLogbackAppender appender = new MagicLogbackAppender();
appender.setContext(context);
appender.setName(LOGGER_NAME);
appender.start();
logger.addAppender(appender);
}
static class MagicLogbackAppender extends UnsynchronizedAppenderBase<ILoggingEvent> {
@Override
protected void append(ILoggingEvent event) {
Formatter formatter = Formatter.create()
.timestamp(event.getTimeStamp())
.space()
.level(event.getLevel().toString())
.value(" --- [")
.thread(event.getThreadName())
.value("] ")
.loggerName(event.getLoggerName())
.value(": ")
.value(event.getFormattedMessage())
.newline();
IThrowableProxy proxy = event.getThrowableProxy();
if (proxy instanceof ThrowableProxy) {
formatter.throwable(((ThrowableProxy) proxy).getThrowable());
}
MagicLoggerContext.println(formatter.toString());
}
}
}
package org.ssssssss.magicapi.core.logging;
import org.slf4j.ILoggerFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* 日志管理
*
* @author mxd
*/
public class LoggerManager {
private static final Logger logger = LoggerFactory.getLogger(LoggerManager.class);
/**
* 创建一个新的appender至项目中,用于UI界面
*/
public static void createMagicAppender() {
ILoggerFactory loggerFactory = LoggerFactory.getILoggerFactory();
String loggerFactoryClassName = loggerFactory.getClass().getName();
MagicLoggerContext magicLoggerContext = null;
// logback
if ("ch.qos.logback.classic.LoggerContext".equalsIgnoreCase(loggerFactoryClassName)) {
magicLoggerContext = new LogbackLoggerContext();
} else if ("org.apache.logging.slf4j.Log4jLoggerFactory".equalsIgnoreCase(loggerFactoryClassName)) {
// log4j2
magicLoggerContext = new Log4j2LoggerContext();
} else if ("org.slf4j.impl.Log4jLoggerFactory".equalsIgnoreCase(loggerFactoryClassName)) {
// log4j 1
magicLoggerContext = new Log4jLoggerContext();
}
if (magicLoggerContext == null) {
logger.error("无法识别LoggerContext:{}", loggerFactoryClassName);
} else {
magicLoggerContext.generateAppender();
}
}
}
package org.ssssssss.magicapi.core.logging;
import org.ssssssss.magicapi.core.config.WebSocketSessionManager;
/**
* 日志上下文
*
* @author mxd
*/
public interface MagicLoggerContext {
String LOGGER_NAME = "magic";
ThreadLocal<String> SESSION = new InheritableThreadLocal<>();
/**
* 打印日志
* re
*
* @param logInfo 日志信息
*/
static void println(String logInfo) {
// 获取SessionId
String sessionId = SESSION.get();
if (sessionId != null) {
WebSocketSessionManager.sendLogs(sessionId, logInfo);
}
}
/**
* 移除ThreadLocal中的sessionId
*/
static void remove() {
SESSION.remove();
}
/**
* 生成appender
*/
void generateAppender();
}
package org.ssssssss.magicapi.core.model;
import com.fasterxml.jackson.core.type.TypeReference;
import org.ssssssss.magicapi.core.config.MagicConfiguration;
import org.ssssssss.magicapi.utils.JsonUtils;
import java.util.*;
import java.util.stream.Collectors;
/**
* 接口信息
*/
public class ApiInfo extends PathMagicEntity {
/**
* 请求方法
*/
private String method = "GET";
/**
* 设置的请求参数
*/
private List<Parameter> parameters = Collections.emptyList();
/**
* 设置的接口选项
*/
private List<Option> options = new ArrayList<>();
/**
* 请求体
*/
private String requestBody;
/**
* 请求头
*/
private List<Header> headers = Collections.emptyList();
/**
* 路径变量
*/
private List<Path> paths = Collections.emptyList();
/**
* 输出结果
*/
private String responseBody;
/**
* 接口描述
*/
private String description;
/**
* 请求体属性
*/
private BaseDefinition requestBodyDefinition;
/**
* 输出结果属性
*/
private BaseDefinition responseBodyDefinition;
public String getMethod() {
return method;
}
public void setMethod(String method) {
this.method = method;
}
public String getResponseBody() {
return responseBody;
}
public void setResponseBody(String responseBody) {
this.responseBody = responseBody;
}
public String getRequestBody() {
return requestBody;
}
public void setRequestBody(String requestBody) {
this.requestBody = requestBody;
}
public List<Path> getPaths() {
return paths;
}
public void setPaths(List<Path> paths) {
this.paths = paths;
}
public Map<String, String> options() {
Map<String, String> map = this.options.stream()
.collect(Collectors.toMap(BaseDefinition::getName, it -> String.valueOf(it.getValue()), (o, n) -> n));
MagicConfiguration.getMagicResourceService().getGroupsByFileId(this.id)
.stream()
.flatMap(it -> it.getOptions().stream())
.forEach(option -> {
if (!map.containsKey(option.getName())) {
map.put(option.getName(), String.valueOf(option.getValue()));
}
});
return map;
}
// 兼容1.x处理。
public void setOptionMap(Map<String, Object> optionMap) {
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public List<Option> getOptions() {
return options;
}
public void setOption(List<Option> options) {
this.options = options;
}
public void setOption(String json) {
this.options = JsonUtils.readValue(Objects.toString(json, "[]"), new TypeReference<List<Option>>() {
});
}
public List<Parameter> getParameters() {
return parameters;
}
public void setParameters(List<Parameter> parameters) {
this.parameters = parameters;
}
public List<Header> getHeaders() {
return headers;
}
public void setHeaders(List<Header> headers) {
this.headers = headers;
}
public String getOptionValue(Options options) {
return getOptionValue(options.getValue());
}
public String getOptionValue(String key) {
return this.options.stream()
.filter(it -> key.equals(it.getName()))
.findFirst()
.map(it -> Objects.toString(it.getValue(), null))
.orElseGet(() -> MagicConfiguration.getMagicResourceService().getGroupsByFileId(this.id)
.stream()
.flatMap(it -> it.getOptions().stream())
.filter(it -> key.equals(it.getName()))
.findFirst()
.map(it -> Objects.toString(it.getValue(), null)).orElse(null)
);
}
public BaseDefinition getRequestBodyDefinition() {
return requestBodyDefinition;
}
public void setRequestBodyDefinition(BaseDefinition requestBodyDefinition) {
this.requestBodyDefinition = requestBodyDefinition;
}
public BaseDefinition getResponseBodyDefinition() {
return responseBodyDefinition;
}
public void setResponseBodyDefinition(BaseDefinition responseBodyDefinition) {
this.responseBodyDefinition = responseBodyDefinition;
}
public ApiInfo simple() {
ApiInfo target = new ApiInfo();
super.simple(target);
target.setMethod(this.getMethod());
return target;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ApiInfo apiInfo = (ApiInfo) o;
return Objects.equals(id, apiInfo.id) &&
Objects.equals(method, apiInfo.method) &&
Objects.equals(path, apiInfo.path) &&
Objects.equals(script, apiInfo.script) &&
Objects.equals(name, apiInfo.name) &&
Objects.equals(paths, apiInfo.paths) &&
Objects.equals(groupId, apiInfo.groupId) &&
Objects.equals(parameters, apiInfo.parameters) &&
Objects.equals(options, apiInfo.options) &&
Objects.equals(requestBody, apiInfo.requestBody) &&
Objects.equals(headers, apiInfo.headers) &&
Objects.equals(description, apiInfo.description) &&
Objects.equals(requestBodyDefinition, apiInfo.requestBodyDefinition) &&
Objects.equals(responseBodyDefinition, apiInfo.responseBodyDefinition);
}
@Override
public int hashCode() {
return Objects.hash(id, method, path, script, name, groupId, parameters, options, requestBody, headers, description, requestBodyDefinition, responseBodyDefinition);
}
@Override
public ApiInfo copy() {
ApiInfo info = new ApiInfo();
copyTo(info);
info.setMethod(this.method);
info.setParameters(this.parameters);
info.setRequestBody(this.requestBody);
info.setOption(this.options);
info.setHeaders(this.headers);
info.setResponseBody(this.responseBody);
info.setDescription(this.description);
info.setPaths(this.paths);
info.setRequestBodyDefinition(this.requestBodyDefinition);
info.setResponseBodyDefinition(this.responseBodyDefinition);
return info;
}
}
package org.ssssssss.magicapi.core.model;
import java.beans.Transient;
import java.util.HashMap;
import java.util.Map;
/**
* 属性信息
*
* @param <T>
* @author mxd
*/
public class Attributes<T> {
protected Map<String, T> properties = new HashMap<>();
/**
* 设置属性
*
* @param key key
* @param value value
*/
public void setAttribute(String key, T value) {
properties.put(key, value);
}
/**
* 获取属性
*
* @param key key
*/
public Object getAttribute(String key) {
return properties.get(key);
}
public Map<String, T> getProperties() {
return properties;
}
public void setProperties(Map<String, T> properties) {
this.properties = properties;
}
}
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