修改:1、qtask.call 服务
2、其他界面样式优化
This commit is contained in:
@@ -2,11 +2,17 @@ package dev.zhub.mk.qtask;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import net.tccn.base.Kv;
|
||||
import net.tccn.base.Utils;
|
||||
import net.tccn.qtask.Task;
|
||||
import org.redkale.annotation.Comment;
|
||||
import org.redkale.convert.json.JsonConvert;
|
||||
import org.redkale.persistence.Column;
|
||||
import org.redkale.persistence.Entity;
|
||||
import org.redkale.persistence.Id;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@Entity
|
||||
@@ -44,4 +50,27 @@ public class QTask {
|
||||
|
||||
@Comment("[状态]10正常,80删除")
|
||||
private int status = 10;
|
||||
|
||||
public Task createTask(Kv<String, String> para) {
|
||||
// TODO : QTask 作为 Task 的引用值,省去创建重复 Task 属性
|
||||
Task task = new Task();
|
||||
task.setName(name);
|
||||
task.setTitle(title);
|
||||
task.setContent(content);
|
||||
task.setDbPlatId(dbplatid);
|
||||
task.setCatalog(catalog);
|
||||
|
||||
Kv _para = Kv.of();
|
||||
if (!Utils.isEmpty(this.para)) {
|
||||
try {
|
||||
Map<String, String> map = JsonConvert.root().convertFrom(JsonConvert.TYPE_MAP_STRING_STRING, this.para);
|
||||
_para.putAll(map);
|
||||
} catch (Exception e) {
|
||||
new IllegalArgumentException(String.format("fromJson error:[%s]", this.para), e);
|
||||
}
|
||||
}
|
||||
_para.putAll(para); // 填充传入的参数,优先级最高
|
||||
task.setPara(_para);
|
||||
return task;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,6 +35,10 @@ public class BaseService implements Service {
|
||||
protected File APP_HOME;
|
||||
@Resource(name = "meta")
|
||||
protected DataSource metaSource;
|
||||
@Resource
|
||||
protected MetaKit metaKit;
|
||||
|
||||
|
||||
protected static Properties prop = new Properties();
|
||||
protected static TplKit tplKit = TplKit.use(true);
|
||||
private static boolean tplInit = false;
|
||||
|
||||
@@ -10,7 +10,6 @@ import net.tccn.dict.Dict;
|
||||
import net.tccn.meta.*;
|
||||
import net.tccn.plat.MetaDb;
|
||||
import net.tccn.plat.MetaPlat;
|
||||
import net.tccn.qtask.DbTask;
|
||||
import net.tccn.qtask.TaskKit;
|
||||
import net.tccn.user.MetaUser;
|
||||
import org.redkale.annotation.Resource;
|
||||
@@ -49,7 +48,7 @@ public class MetaKit extends AbstractService {
|
||||
@Getter
|
||||
private static List<MetaUser> users;
|
||||
@Getter
|
||||
private static List<QTask> taskEntities;
|
||||
private static List<QTask> qTasks;
|
||||
@Getter
|
||||
private static List<Dict> dicts;
|
||||
|
||||
@@ -60,10 +59,16 @@ public class MetaKit extends AbstractService {
|
||||
@Resource(name = "meta")
|
||||
protected DataSource metaSource;
|
||||
|
||||
public <T> void reloadxAsync(Class<T> clazz) {
|
||||
if (clazz == QTask.class) {
|
||||
qTasks = metaSource.queryList(QTask.class);
|
||||
TaskKit.init(qTasks);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(AnyValue config) {
|
||||
taskEntities = metaSource.queryList(QTask.class);
|
||||
TaskKit.init();
|
||||
reloadxAsync(QTask.class);
|
||||
}
|
||||
|
||||
// -----------------------------------
|
||||
@@ -77,17 +82,6 @@ public class MetaKit extends AbstractService {
|
||||
// reload(DbTask.class);
|
||||
reload(Dict.class);
|
||||
|
||||
new Thread(() -> {
|
||||
while (true) {
|
||||
try {
|
||||
reload(MetaUser.class);
|
||||
Thread.sleep(15000);
|
||||
} catch (InterruptedException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}).start();
|
||||
|
||||
/*
|
||||
同步 本地文件配置数据到 数据库
|
||||
List<List<? extends Doc>> list = asList(metaTables, metaLinks, metaServices, dbPlats, sysPlats, users, taskEntities, dicts);
|
||||
@@ -147,11 +141,11 @@ public class MetaKit extends AbstractService {
|
||||
Type type = new TypeToken<List<MetaUser>>() {
|
||||
}.getType();
|
||||
users = FileKit.readAs(file, type);;
|
||||
} else if (DbTask.class == clazz) {
|
||||
} /*else if (DbTask.class == clazz) {
|
||||
Type type = new TypeToken<List<DbTask>>() {
|
||||
}.getType();
|
||||
taskEntities = FileKit.readAs(file, type);;
|
||||
} else if (Dict.class == clazz) {
|
||||
} */ else if (Dict.class == clazz) {
|
||||
Type type = new TypeToken<List<Dict>>() {
|
||||
}.getType();
|
||||
dicts = FileKit.readAs(file, type);;
|
||||
@@ -164,10 +158,11 @@ public class MetaKit extends AbstractService {
|
||||
else if (MetaDb.class == clazz) dbPlats = DbAccount.dao.find();
|
||||
else if (MetaPlat.class == clazz) sysPlats = MetaPlat.dao.find();
|
||||
else if (MetaUser.class == clazz) users = MetaUser.dao.find();
|
||||
else if (DbTask.class == clazz) {
|
||||
/*else if (DbTask.class == clazz) {
|
||||
// taskEntities = DbTask.dao.find();
|
||||
// TaskKit.init();
|
||||
} else if (Dict.class == clazz) {
|
||||
}*/
|
||||
else if (Dict.class == clazz) {
|
||||
dicts = Dict.dao.find();
|
||||
}
|
||||
}
|
||||
@@ -184,7 +179,7 @@ public class MetaKit extends AbstractService {
|
||||
cacheSave(DbAccount.class);
|
||||
cacheSave(MetaPlat.class);
|
||||
cacheSave(MetaUser.class);
|
||||
cacheSave(DbTask.class);
|
||||
//cacheSave(DbTask.class);
|
||||
cacheSave(Dict.class);
|
||||
}
|
||||
|
||||
@@ -196,7 +191,7 @@ public class MetaKit extends AbstractService {
|
||||
else if (DbAccount.class == clazz) list = dbPlats;
|
||||
else if (MetaPlat.class == clazz) list = sysPlats;
|
||||
else if (MetaUser.class == clazz) list = users;
|
||||
else if (DbTask.class == clazz) list = taskEntities;
|
||||
// else if (DbTask.class == clazz) list = taskEntities;
|
||||
else if (Dict.class == clazz) list = dicts;
|
||||
|
||||
if (list == null || list.size() == 0) return;
|
||||
|
||||
@@ -243,6 +243,11 @@ public class Utils {
|
||||
FromItem fromItem = plainSelect.getFromItem();
|
||||
List<Join> joins = plainSelect.getJoins();
|
||||
Expression where = plainSelect.getWhere();
|
||||
GroupByElement group = plainSelect.getGroupBy();
|
||||
|
||||
if (fromItem == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
sqlCount += " FROM " + fromItem;
|
||||
if (joins != null) {
|
||||
@@ -253,6 +258,11 @@ public class Utils {
|
||||
if (!Utils.isEmpty(where)) {
|
||||
sqlCount += " WHERE " + where;
|
||||
}
|
||||
if (!Utils.isEmpty(group)) {
|
||||
sqlCount += " " + group;
|
||||
|
||||
sqlCount = "SELECT COUNT(1) FROM (" + sqlCount + ") _";
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
@@ -89,6 +89,11 @@ public class DbKit implements DbSource {
|
||||
return dbSource.exetute(sql);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int exetuteUpdate(String sql) {
|
||||
return dbSource.exetuteUpdate(sql);
|
||||
}
|
||||
|
||||
// -----------------------------------------
|
||||
public <T> CompletableFuture<T> findAsync(String sql, Class<T> type) {
|
||||
return CompletableFuture.supplyAsync(() -> find(sql, type));
|
||||
|
||||
@@ -42,4 +42,6 @@ public interface DbSource extends IService {
|
||||
void dropTable(String tableName);
|
||||
|
||||
boolean exetute(String sql);
|
||||
|
||||
int exetuteUpdate(String sql);
|
||||
}
|
||||
|
||||
@@ -5,9 +5,7 @@ import net.tccn.base.Kv;
|
||||
|
||||
import java.sql.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
@@ -108,7 +106,7 @@ public class DbSourceMysql implements DbSource {
|
||||
ResultSetMetaData metaData = rs.getMetaData();
|
||||
int count = metaData.getColumnCount();
|
||||
while (rs.next()) {
|
||||
Map row = new HashMap();
|
||||
Kv row = Kv.of();
|
||||
for (int i = 1; i <= count; i++) {
|
||||
String columnTypeName = metaData.getColumnTypeName(i);
|
||||
//String columnName = metaData.getColumnName(i);
|
||||
@@ -117,8 +115,12 @@ public class DbSourceMysql implements DbSource {
|
||||
|
||||
if (rs.getObject(i) != null) {
|
||||
switch (columnTypeName) {
|
||||
case "DATETIME", "TIMESTAMP", "DATE" -> {
|
||||
row.put(columnLabel, rs.getTimestamp(i).getTime());
|
||||
case "DATETIME", "TIMESTAMP" -> {
|
||||
// row.put(columnLabel, rs.getTimestamp(i).getTime());
|
||||
row.put(columnLabel, rs.getTimestamp(i));
|
||||
}
|
||||
case "DATE" -> {
|
||||
row.put(columnLabel, rs.getDate(i));
|
||||
}
|
||||
default -> {
|
||||
row.put(columnLabel, rs.getObject(i));
|
||||
@@ -175,6 +177,7 @@ public class DbSourceMysql implements DbSource {
|
||||
|
||||
return Kv.toAs(v, type);
|
||||
} catch (SQLException e) {
|
||||
System.out.println("sql execute error: " + sql);
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
} finally {
|
||||
@@ -207,6 +210,20 @@ public class DbSourceMysql implements DbSource {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int exetuteUpdate(String sql) {
|
||||
Connection connection = connection();
|
||||
try (
|
||||
PreparedStatement ps = connection.prepareStatement(sql)
|
||||
) {
|
||||
return ps.executeUpdate();
|
||||
} catch (SQLException e) {
|
||||
throw new CfgException("SQL 执行失败:", sql);
|
||||
} finally {
|
||||
release(connection);
|
||||
}
|
||||
}
|
||||
|
||||
//fixme: lxy 处理连接超过8小时失效问题
|
||||
private Connection connection() {
|
||||
Connection connection = null;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/*
|
||||
package net.tccn.base.dbq.qtask;
|
||||
|
||||
import lombok.Getter;
|
||||
@@ -6,9 +7,11 @@ import net.tccn.base.arango.Doc;
|
||||
|
||||
import javax.persistence.Table;
|
||||
|
||||
*/
|
||||
/**
|
||||
* @author: liangxianyou at 2018/11/13 14:59.
|
||||
*/
|
||||
*//*
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@Table(name = "DbTask", catalog = "db_meta")
|
||||
@@ -29,3 +32,4 @@ public class DbTask extends Doc<DbTask> {
|
||||
//-------------------------------------
|
||||
|
||||
}
|
||||
*/
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/*
|
||||
package net.tccn.qtask;
|
||||
|
||||
import lombok.Getter;
|
||||
@@ -6,9 +7,11 @@ import net.tccn.base.arango.Doc;
|
||||
|
||||
import javax.persistence.Table;
|
||||
|
||||
*/
|
||||
/**
|
||||
* Created by liangxianyou at 2019/4/20 20:04.
|
||||
*/
|
||||
*//*
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@Table(name = "DbTask", catalog = "db_meta")
|
||||
@@ -28,3 +31,4 @@ public class DbTask extends Doc<DbTask> {
|
||||
|
||||
// ---------------------
|
||||
}
|
||||
*/
|
||||
|
||||
@@ -1,33 +1,25 @@
|
||||
package net.tccn.qtask;
|
||||
|
||||
import net.tccn.base.BaseService;
|
||||
import net.tccn.base.JBean;
|
||||
import net.tccn.base.Kv;
|
||||
import org.redkale.net.http.RestMapping;
|
||||
import org.redkale.net.http.RestParam;
|
||||
import org.redkale.net.http.RestService;
|
||||
|
||||
import java.util.Map;
|
||||
import org.redkale.service.RetResult;
|
||||
|
||||
/**
|
||||
* @author: liangxianyou at 2018/11/13 18:14.
|
||||
*/
|
||||
@RestService(automapping = true, comment = "qtask查询服务")
|
||||
@RestService(name = "qtask", automapping = true, comment = "qtask查询服务")
|
||||
public class QtaskService extends BaseService {
|
||||
|
||||
// 调用示例: http://qtask_service_addr_xxxxxx/qtask/call?name=abxx&platToken=3421432¶={h:1}
|
||||
@RestMapping(name = "call", auth = false)
|
||||
public JBean call(String name, Map<String, String> para, @RestParam(name = "platToken") String token) {
|
||||
JBean jBean = new JBean();
|
||||
|
||||
Kv kv = Kv.of();
|
||||
if (para != null) {
|
||||
para.forEach((k, v) -> kv.put(k, v));
|
||||
public RetResult<Object> call(String name, Kv<String, String> para, @RestParam(name = "platToken") String platToken) {
|
||||
if (para == null) {
|
||||
para = Kv.of();
|
||||
}
|
||||
|
||||
jBean.setBody(TaskKit.taskRun(name, token, kv));
|
||||
return jBean;
|
||||
|
||||
return RetResult.success(TaskKit.taskRun(name, platToken, para));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -3,10 +3,8 @@ package net.tccn.qtask;
|
||||
import dev.zhub.mk.qtask.QTask;
|
||||
import net.tccn.base.Kv;
|
||||
import net.tccn.base.MetaKit;
|
||||
import org.redkale.convert.json.JsonConvert;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
|
||||
@@ -14,61 +12,40 @@ import java.util.Optional;
|
||||
* Created by liangxianyou at 2019/4/20 19:59.
|
||||
*/
|
||||
public class TaskKit {
|
||||
static final JsonConvert convert = JsonConvert.root();
|
||||
private static List<QTask> taskEntities;
|
||||
private static List<QTask> qTasks;
|
||||
|
||||
static {
|
||||
init();
|
||||
public static void init(List<QTask> tasks) {
|
||||
qTasks = tasks;
|
||||
}
|
||||
|
||||
public static void init() {
|
||||
taskEntities = MetaKit.getTaskEntities();
|
||||
}
|
||||
|
||||
public static Task buildTask(String name, String platToken, Kv para) {
|
||||
QTask taskEntity = getTaskEntity(name, platToken);
|
||||
public static Task buildTask(String name, String plattoken, Kv para) {
|
||||
QTask taskEntity = getTaskEntity(name, plattoken);
|
||||
return buildTask(taskEntity, para);
|
||||
}
|
||||
|
||||
public static Task buildTask(dev.zhub.mk.qtask.QTask taskEntity, Kv para) {
|
||||
Task task = new Task();
|
||||
task.setName(taskEntity.getName());
|
||||
task.setTitle(taskEntity.getTitle());
|
||||
task.setContent(taskEntity.getContent());
|
||||
task.setDbPlatId(taskEntity.getDbplatid());
|
||||
task.setCatalog(taskEntity.getCatalog());
|
||||
|
||||
Kv _para = Kv.of().putAll(para);
|
||||
if (taskEntity.getPara() != null) {
|
||||
try {
|
||||
Map<String, String> map = convert.convertFrom(JsonConvert.TYPE_MAP_STRING_STRING, taskEntity.getPara());
|
||||
map.forEach((k,v) -> _para.put(k, v));
|
||||
} catch (Exception e) {
|
||||
new IllegalArgumentException(String.format("fromJson error:[%s]",taskEntity.getPara()), e);
|
||||
}
|
||||
}
|
||||
task.setPara(_para);
|
||||
task.setDbAccount(MetaKit.getDbPlat(taskEntity.getDbplatid()));
|
||||
public static Task buildTask(dev.zhub.mk.qtask.QTask qTask, Kv para) {
|
||||
Task task = qTask.createTask(para);
|
||||
task.setDbAccount(MetaKit.getDbPlat(qTask.getDbplatid()));
|
||||
return task;
|
||||
}
|
||||
|
||||
public static QTask getTaskEntity(String name, String platToken) {
|
||||
public static QTask getTaskEntity(String name, String platid) {
|
||||
Objects.requireNonNull(name);
|
||||
Objects.requireNonNull(platToken);
|
||||
Objects.requireNonNull(platid);
|
||||
|
||||
Optional<QTask> any = taskEntities.stream()
|
||||
.filter(x -> name.equals(x.getName()) && MetaKit.getPlatId(platToken).equals(x.getPlatid()))
|
||||
Optional<QTask> any = qTasks.stream()
|
||||
.filter(x -> name.equals(x.getName()) && MetaKit.getPlatId(platid).equals(x.getPlatid()))
|
||||
.findAny();
|
||||
return any.get();
|
||||
}
|
||||
|
||||
public static Object taskRun(String name, String platToken, Kv para) {
|
||||
Task task = buildTask(name, platToken, para);
|
||||
public static Object taskRun(String name, String plattoken, Kv para) {
|
||||
Task task = buildTask(name, plattoken, para);
|
||||
return QRuner.query(task);
|
||||
}
|
||||
|
||||
public static Object taskRun(QTask entity) {
|
||||
Task task = buildTask(entity, Kv.of());
|
||||
public static Object taskRun(QTask qTask) {
|
||||
Task task = buildTask(qTask, Kv.of());
|
||||
return QRuner.query(task);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ package net.tccn.qtask;
|
||||
import dev.zhub.mk.qtask.QTask;
|
||||
import net.tccn.base.BaseService;
|
||||
import net.tccn.base.JBean;
|
||||
import net.tccn.base.MetaKit;
|
||||
import org.redkale.net.http.RestBody;
|
||||
import org.redkale.net.http.RestHeader;
|
||||
import org.redkale.net.http.RestParam;
|
||||
@@ -44,6 +43,9 @@ public class _QtaskService extends BaseService {
|
||||
task.setPlatid(platId(token));
|
||||
|
||||
metaSource.insert(task);
|
||||
|
||||
// 刷新缓存
|
||||
metaKit.reloadxAsync(QTask.class);
|
||||
return render();
|
||||
}
|
||||
|
||||
@@ -51,6 +53,9 @@ public class _QtaskService extends BaseService {
|
||||
@RestBody QTask task) {
|
||||
|
||||
metaSource.update(task);
|
||||
|
||||
// 刷新缓存
|
||||
metaKit.reloadxAsync(QTask.class);
|
||||
return render();
|
||||
}
|
||||
|
||||
@@ -58,12 +63,15 @@ public class _QtaskService extends BaseService {
|
||||
@RestBody QTask task) {
|
||||
|
||||
metaSource.updateColumn(QTask.class, task.getQtaskid(), ColumnValue.create("status", 80));
|
||||
|
||||
// 刷新缓存
|
||||
metaKit.reloadxAsync(QTask.class);
|
||||
return render();
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Comment("qtask保存")
|
||||
/*@Comment("qtask保存")
|
||||
public JBean save(DbTask task, @RestParam(name = "platToken") String token) {
|
||||
JBean jBean = new JBean();
|
||||
|
||||
@@ -96,7 +104,7 @@ public class _QtaskService extends BaseService {
|
||||
|
||||
|
||||
return jBean;
|
||||
}
|
||||
}*/
|
||||
|
||||
@Comment("debug调试接口")
|
||||
public JBean debug(QTask task, @RestParam(name = "platToken") String token) {
|
||||
|
||||
@@ -10,7 +10,9 @@ import net.tccn.base.dbq.jdbc.api.DbKit;
|
||||
import net.tccn.qtask.QTask;
|
||||
import net.tccn.qtask.Task;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
||||
public class QTaskMysql extends QTaskAbs implements QTask {
|
||||
|
||||
@@ -34,7 +36,11 @@ public class QTaskMysql extends QTaskAbs implements QTask {
|
||||
String sql = tpl.renderToString(getTask().getPara()).replaceAll("[\\s]+", " ");
|
||||
|
||||
// 聚合统计返回统计结果 TODO 待完善
|
||||
if ((sql.startsWith("SELECT COUNT") || sql.startsWith("select count") || sql.startsWith("SELECT count") || sql.startsWith("select COUNT"))) {
|
||||
if (sql.startsWith("UPDATE ") || sql.startsWith("update ")) {
|
||||
return dbKit.exetuteUpdate(sql);
|
||||
}
|
||||
String countSql = Utils.parserCountSql(sql);
|
||||
if (countSql == null || (sql.startsWith("SELECT COUNT") || sql.startsWith("select count") || sql.startsWith("SELECT count") || sql.startsWith("select COUNT"))) {
|
||||
return dbKit.find(sql, Kv.class);
|
||||
}
|
||||
|
||||
@@ -69,8 +75,18 @@ public class QTaskMysql extends QTaskAbs implements QTask {
|
||||
sql = Utils.parserSql(sql);
|
||||
|
||||
Kv kv = Kv.of();
|
||||
kv.set("count", dbKit.findLong(Utils.parserCountSql(sql)));
|
||||
kv.set("list", dbKit.queryList(sql, Map.class));
|
||||
|
||||
CompletableFuture<Long> countFuture = dbKit.findColumnAsync(countSql, long.class);
|
||||
CompletableFuture<List<Kv>> listFuture = dbKit.queryListAsync(sql, Kv.class);
|
||||
|
||||
try {
|
||||
kv.set("count", countFuture.get());
|
||||
kv.set("list", listFuture.get());
|
||||
} catch (InterruptedException e) {
|
||||
throw new RuntimeException(e);
|
||||
} catch (ExecutionException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
return kv;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import dev.zhub.mk.qtask.QTask;
|
||||
import net.tccn.base.*;
|
||||
import net.tccn.base.dbq.fbean.FBean;
|
||||
import net.tccn.base.dbq.jdbc.api.DbAccount;
|
||||
@@ -8,8 +7,6 @@ import net.tccn.dict.Dict;
|
||||
import net.tccn.dict.DictKit;
|
||||
import net.tccn.meta.MetaService;
|
||||
import net.tccn.meta.MetaTable;
|
||||
import net.tccn.qtask.DbTask;
|
||||
import net.tccn.qtask.TaskKit;
|
||||
import net.tccn.user.MetaUser;
|
||||
import org.apache.poi.ss.usermodel.Workbook;
|
||||
import org.junit.Test;
|
||||
|
||||
Reference in New Issue
Block a user