From 8e5a42930e5feac8b1eeeb9ab5bea2504adf7a52 Mon Sep 17 00:00:00 2001 From: wangbing Date: Fri, 11 Oct 2019 17:54:18 +0800 Subject: [PATCH] 111 --- .../com/example/action/GlobalController.java | 42 + .../java/com/example/config/NginxConfig.java | 72 ++ .../com/example/config/SecurityConfig.java | 11 +- .../java/com/example/config/SqliteConfig.java | 9 +- .../java/com/example/config/TaskConfig.java | 23 +- .../java/com/example/frame/base/Message.java | 24 + .../com/example/frame/base/MessageType.java | 6 + .../com/example/frame/utils/FileUtil.java | 915 ++++++++++++++++++ .../java/com/example/frame/utils/ZipUtil.java | 138 +++ .../example/module/admin/ent/NginxCtrl.java | 56 ++ .../com/example/module/admin/ent/State.java | 18 + .../main/resources/application-dev.properties | 7 +- .../resources/application-prod.properties | 4 +- admin/src/main/resources/nginx-1.14.2.zip | Bin 1474785 -> 1473399 bytes .../src/main/resources/static/img/restart.png | Bin 0 -> 3980 bytes admin/src/main/resources/static/img/start.png | Bin 0 -> 2354 bytes .../src/main/resources/static/img/start_.png | Bin 0 -> 2051 bytes admin/src/main/resources/static/img/stop.png | Bin 0 -> 2174 bytes admin/src/main/resources/static/img/stop_.png | Bin 0 -> 1889 bytes .../resources/templates/screen/mapping.ftl | 72 +- 20 files changed, 1369 insertions(+), 28 deletions(-) create mode 100644 admin/src/main/java/com/example/config/NginxConfig.java create mode 100644 admin/src/main/java/com/example/frame/base/Message.java create mode 100644 admin/src/main/java/com/example/frame/base/MessageType.java create mode 100644 admin/src/main/java/com/example/frame/utils/FileUtil.java create mode 100644 admin/src/main/java/com/example/frame/utils/ZipUtil.java create mode 100644 admin/src/main/java/com/example/module/admin/ent/NginxCtrl.java create mode 100644 admin/src/main/java/com/example/module/admin/ent/State.java create mode 100644 admin/src/main/resources/static/img/restart.png create mode 100644 admin/src/main/resources/static/img/start.png create mode 100644 admin/src/main/resources/static/img/start_.png create mode 100644 admin/src/main/resources/static/img/stop.png create mode 100644 admin/src/main/resources/static/img/stop_.png diff --git a/admin/src/main/java/com/example/action/GlobalController.java b/admin/src/main/java/com/example/action/GlobalController.java index 4ccd7ed..78b74c5 100644 --- a/admin/src/main/java/com/example/action/GlobalController.java +++ b/admin/src/main/java/com/example/action/GlobalController.java @@ -6,6 +6,7 @@ import com.example.frame.base.ErrorType; import com.example.frame.base.Screen; import com.example.frame.utils.LocalData; import com.example.config.ActionConfig; +import com.example.frame.utils.MapperUtil; import org.springframework.beans.BeansException; import org.apache.commons.io.FileUtils; import org.springframework.boot.web.servlet.error.ErrorController; @@ -26,6 +27,14 @@ import javax.servlet.http.HttpServletResponse; import java.io.File; import java.io.IOException; import org.springframework.beans.factory.annotation.Value; +import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; + +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.HashMap; +import java.util.Timer; +import java.util.TimerTask; +import java.util.concurrent.ConcurrentHashMap; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -178,4 +187,37 @@ public class GlobalController implements ErrorController { return new ResponseEntity(FileUtils.readFileToByteArray(new File(file)), headers, HttpStatus.CREATED); } + + private static ConcurrentHashMap sseMap = new ConcurrentHashMap(); + + /** + * Sse推送服务,服务器向js推送自定义消息 + * Sse容器{@link GlobalController#sseMap} + * Sse批量推送{@link GlobalController#pushAll} + */ + @RequestMapping(value = "/sse/{userId}", produces = "text/event-stream;charset=UTF-8") + public SseEmitter sse(@PathVariable String userId) { + SseEmitter sseEmitter = new SseEmitter(10000000L); + if (sseMap.get(userId) != null) { + sseMap.remove(userId); + } + sseMap.put(userId, sseEmitter); + + return sseEmitter; + } + + /** + * Sse批量推送 + * + * @param data 推送对象 + */ + public void pushAll(Object data) { + try { + for (String s : sseMap.keySet()) { + sseMap.get(s).send(MapperUtil.toJson(data), MediaType.APPLICATION_JSON); + } + } catch (IOException e) { + e.printStackTrace(); + } + } } diff --git a/admin/src/main/java/com/example/config/NginxConfig.java b/admin/src/main/java/com/example/config/NginxConfig.java new file mode 100644 index 0000000..2ee2251 --- /dev/null +++ b/admin/src/main/java/com/example/config/NginxConfig.java @@ -0,0 +1,72 @@ +package com.example.config; + +import com.example.frame.utils.FileUtil; +import com.example.frame.utils.ZipUtil; +import com.example.module.admin.ent.Mapping; +import com.example.module.admin.ent.NginxCtrl; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.system.ApplicationHome; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.env.Environment; +import org.springframework.core.io.ClassPathResource; +import org.springframework.util.StringUtils; +import xyz.wbsite.wsqlite.ObjectClient; + +import javax.annotation.PostConstruct; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.sql.SQLException; +import java.util.ArrayList; + + +@Configuration +public class NginxConfig { + + + @Autowired + private Environment environment; + + @Bean + public NginxCtrl initNginx() { + String property = environment.getProperty("nginx-path"); + File nginxHome = null; + if (StringUtils.isEmpty(property)) { + ApplicationHome home = new ApplicationHome(getClass()); + // 当前运行jar文件 + File jarFile = home.getSource() != null ? home.getSource() : home.getDir(); + + //jar同目录 + nginxHome = new File(jarFile.getParent(), "nginx"); + + if (!nginxHome.exists()) { + ClassPathResource classPathResource = new ClassPathResource("nginx-1.14.2.zip"); + try { + InputStream inputStream = classPathResource.getInputStream(); + File nginxDir = new File(jarFile.getParent(), "nginx"); + File nginxZip = new File(jarFile.getParent(), "nginx.zip"); + FileUtil.inputStream2File(inputStream, nginxZip); + ZipUtil.unZip(nginxZip, nginxDir); + } catch (IOException e) { + e.printStackTrace(); + } + } + } else { + nginxHome = new File(property); + } + + if (!nginxHome.exists()) { + throw new RuntimeException("nginx home not exists!"); + } + + File exe = new File(nginxHome, "nginx.exe"); + + NginxCtrl nginxCtrl = new NginxCtrl(); + nginxCtrl.setStartCmd("start " + exe.getAbsolutePath()); + nginxCtrl.setStopCmd(exe.getAbsolutePath() + " -s quit"); + nginxCtrl.setReloadCmd(exe.getAbsolutePath() + " - s reload "); + nginxCtrl.setVersionCmd(exe.getAbsolutePath() + " -v "); + return nginxCtrl; + } +} diff --git a/admin/src/main/java/com/example/config/SecurityConfig.java b/admin/src/main/java/com/example/config/SecurityConfig.java index 744c219..41de383 100644 --- a/admin/src/main/java/com/example/config/SecurityConfig.java +++ b/admin/src/main/java/com/example/config/SecurityConfig.java @@ -17,19 +17,18 @@ import javax.servlet.http.HttpServletRequest; @Configuration @EnableGlobalMethodSecurity(securedEnabled = true) public class SecurityConfig extends WebSecurityConfigurerAdapter { - @Value("${web.url.auth.excluded}") private String[] excluded; - @Value("${web.url.auth.included}") - private String[] included; + @Value("${spring.mvc.static-path-pattern}") + private String[] staticPath; @Override protected void configure(HttpSecurity http) throws Exception { - http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) - .and() + http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and() .authorizeRequests() + .antMatchers(staticPath).permitAll() .antMatchers(excluded).permitAll() - .antMatchers(included).access("@Authorization.hasPermission(request,authentication)") + .anyRequest().access("@Authorization.hasPermission(request,authentication)") .and().cors() .and().headers().frameOptions().disable() .and().csrf().disable(); diff --git a/admin/src/main/java/com/example/config/SqliteConfig.java b/admin/src/main/java/com/example/config/SqliteConfig.java index 51dcb4d..80d7f75 100644 --- a/admin/src/main/java/com/example/config/SqliteConfig.java +++ b/admin/src/main/java/com/example/config/SqliteConfig.java @@ -1,5 +1,7 @@ package com.example.config; +import com.example.frame.utils.FileUtil; +import com.example.frame.utils.ZipUtil; import com.example.module.admin.ent.Mapping; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; @@ -7,10 +9,15 @@ import org.springframework.boot.system.ApplicationHome; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.env.Environment; +import org.springframework.core.io.ClassPathResource; +import org.springframework.core.io.Resource; import org.springframework.util.StringUtils; import xyz.wbsite.wsqlite.ObjectClient; +import javax.annotation.PostConstruct; import java.io.File; +import java.io.IOException; +import java.io.InputStream; import java.sql.SQLException; import java.util.ArrayList; @@ -34,7 +41,7 @@ public class SqliteConfig { ArrayList objects = new ArrayList<>(); objects.add(Mapping.class); - return new ObjectClient(new File(dbpath,"data.db"), objects); + return new ObjectClient(new File(dbpath, "data.db"), objects); } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (SQLException e) { diff --git a/admin/src/main/java/com/example/config/TaskConfig.java b/admin/src/main/java/com/example/config/TaskConfig.java index 7be44f2..cf4fa1f 100644 --- a/admin/src/main/java/com/example/config/TaskConfig.java +++ b/admin/src/main/java/com/example/config/TaskConfig.java @@ -1,7 +1,12 @@ package com.example.config; +import com.example.action.GlobalController; +import com.example.frame.base.Message; +import com.example.frame.base.MessageType; import com.example.frame.utils.LogUtil; import com.example.frame.utils.ProcessUtil; +import com.example.module.admin.ent.State; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Profile; import org.springframework.scheduling.annotation.EnableScheduling; @@ -17,12 +22,20 @@ import java.util.concurrent.Executors; */ @Configuration @EnableScheduling -@Profile("prod") -public class TaskConfig implements SchedulingConfigurer { +//@Profile("prod") +public class TaskConfig implements SchedulingConfigurer { - @Scheduled(cron="0/30 * * * * ? ") - public void task(){ - LogUtil.i("--------------------Task--------------------"); + @Autowired + private GlobalController globalController; + + @Scheduled(cron = "0/3 * * * * ? ") + public void task() { + Message message = new Message(); + message.setType(MessageType.NGINX_STATE); + State state = new State(); + state.setRun(true); + message.setObject(state); + globalController.pushAll(message); } /** diff --git a/admin/src/main/java/com/example/frame/base/Message.java b/admin/src/main/java/com/example/frame/base/Message.java new file mode 100644 index 0000000..f373b82 --- /dev/null +++ b/admin/src/main/java/com/example/frame/base/Message.java @@ -0,0 +1,24 @@ +package com.example.frame.base; + +public class Message { + + private MessageType type; + + private Object object; + + public MessageType getType() { + return type; + } + + public void setType(MessageType type) { + this.type = type; + } + + public Object getObject() { + return object; + } + + public void setObject(Object object) { + this.object = object; + } +} diff --git a/admin/src/main/java/com/example/frame/base/MessageType.java b/admin/src/main/java/com/example/frame/base/MessageType.java new file mode 100644 index 0000000..73406da --- /dev/null +++ b/admin/src/main/java/com/example/frame/base/MessageType.java @@ -0,0 +1,6 @@ +package com.example.frame.base; + +public enum MessageType { + + NGINX_STATE, +} \ No newline at end of file diff --git a/admin/src/main/java/com/example/frame/utils/FileUtil.java b/admin/src/main/java/com/example/frame/utils/FileUtil.java new file mode 100644 index 0000000..55dd2c8 --- /dev/null +++ b/admin/src/main/java/com/example/frame/utils/FileUtil.java @@ -0,0 +1,915 @@ +package com.example.frame.utils; + +import java.io.*; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.ArrayList; +import java.util.List; + +public class FileUtil { + /** + * 私有构造方法,防止类的实例化,因为工具类不需要实例化。 + */ + private FileUtil() { + + } + + /** + * 修改文件的最后访问时间。 + * 如果文件不存在则创建该文件。 + * 目前这个方法的行为方式还不稳定,主要是方法有些信息输出,这些信息输出是否保留还在考虑中。 + * + * @param file 需要修改最后访问时间的文件。 + * @since 1.0 + */ + public static void touch(File file) { + long currentTime = System.currentTimeMillis(); + if (!file.exists()) { + System.err.println("file not found:" + file.getName()); + System.err.println("Create a new file:" + file.getName()); + try { + if (file.createNewFile()) { + System.out.println("Succeeded!"); + } else { + System.err.println("Create file failed!"); + } + } catch (IOException e) { + System.err.println("Create file failed!"); + e.printStackTrace(); + } + } + boolean result = file.setLastModified(currentTime); + if (!result) { + System.err.println("touch failed: " + file.getName()); + } + } + + /** + * 修改文件的最后访问时间。 + * 如果文件不存在则创建该文件。 + * 目前这个方法的行为方式还不稳定,主要是方法有些信息输出,这些信息输出是否保留还在考虑中。 + * + * @param fileName 需要修改最后访问时间的文件的文件名。 + * @since 1.0 + */ + public static void touch(String fileName) { + File file = new File(fileName); + touch(file); + } + + /** + * 修改文件的最后访问时间。 + * 如果文件不存在则创建该文件。 + * 目前这个方法的行为方式还不稳定,主要是方法有些信息输出,这些信息输出是否保留还在考虑中。 + * + * @param files 需要修改最后访问时间的文件数组。 + * @since 1.0 + */ + public static void touch(File[] files) { + for (int i = 0; i < files.length; i++) { + touch(files[i]); + } + } + + /** + * 修改文件的最后访问时间。 + * 如果文件不存在则创建该文件。 + * 目前这个方法的行为方式还不稳定,主要是方法有些信息输出,这些信息输出是否保留还在考虑中。 + * + * @param fileNames 需要修改最后访问时间的文件名数组。 + * @since 1.0 + */ + public static void touch(String[] fileNames) { + File[] files = new File[fileNames.length]; + for (int i = 0; i < fileNames.length; i++) { + files[i] = new File(fileNames[i]); + } + touch(files); + } + + /** + * 判断指定的文件是否存在。 + * + * @param fileName 要判断的文件的文件名 + * @return 存在时返回true,否则返回false。 + * @since 1.0 + */ + public static boolean isFileExist(String fileName) { + return new File(fileName).isFile(); + } + + /** + * 创建指定的目录。 + * 如果指定的目录的父目录不存在则创建其目录书上所有需要的父目录。 + * 注意:可能会在返回false的时候创建部分父目录。 + * + * @param file 要创建的目录 + * @return 完全创建成功时返回true,否则返回false。 + * @since 1.0 + */ + public static boolean makeDirectory(File file) { + File parent = file.getParentFile(); + if (parent != null) { + return parent.mkdirs(); + } + return false; + } + + /** + * 创建指定的目录。 + * 如果指定的目录的父目录不存在则创建其目录书上所有需要的父目录。 + * 注意:可能会在返回false的时候创建部分父目录。 + * + * @param fileName 要创建的目录的目录名 + * @return 完全创建成功时返回true,否则返回false。 + * @since 1.0 + */ + public static boolean makeDirectory(String fileName) { + File file = new File(fileName); + return makeDirectory(file); + } + + /** + * 清空指定目录中的文件。 + * 这个方法将尽可能删除所有的文件,但是只要有一个文件没有被删除都会返回false。 + * 另外这个方法不会迭代删除,即不会删除子目录及其内容。 + * + * @param directory 要清空的目录 + * @return 目录下的所有文件都被成功删除时返回true,否则返回false. + * @since 1.0 + */ + public static boolean emptyDirectory(File directory) { + boolean result = true; + File[] entries = directory.listFiles(); + for (int i = 0; i < entries.length; i++) { + if (!entries[i].delete()) { + result = false; + } + } + return result; + } + + /** + * 清空指定目录中的文件。 + * 这个方法将尽可能删除所有的文件,但是只要有一个文件没有被删除都会返回false。 + * 另外这个方法不会迭代删除,即不会删除子目录及其内容。 + * + * @param directoryName 要清空的目录的目录名 + * @return 目录下的所有文件都被成功删除时返回true,否则返回false。 + * @since 1.0 + */ + public static boolean emptyDirectory(String directoryName) { + File dir = new File(directoryName); + return emptyDirectory(dir); + } + + /** + * 删除指定目录及其中的所有内容。 + * + * @param dirName 要删除的目录的目录名 + * @return 删除成功时返回true,否则返回false。 + * @since 1.0 + */ + public static boolean deleteDirectory(String dirName) { + return deleteDirectory(new File(dirName)); + } + + /** + * 删除指定目录及其中的所有内容。 + * + * @param dir 要删除的目录 + * @return 删除成功时返回true,否则返回false。 + * @since 1.0 + */ + public static boolean deleteDirectory(File dir) { + if ((dir == null) || !dir.isDirectory()) { + throw new IllegalArgumentException("Argument " + dir + + " is not a directory. "); + } + + File[] entries = dir.listFiles(); + int sz = entries.length; + + for (int i = 0; i < sz; i++) { + if (entries[i].isDirectory()) { + if (!deleteDirectory(entries[i])) { + return false; + } + } else { + if (!entries[i].delete()) { + return false; + } + } + } + + if (!dir.delete()) { + return false; + } + return true; + } + + /** + * 列出目录中的所有内容,包括其子目录中的内容。 + * + * @param file 要列出的目录 + * @param filter 过滤器 + * @return 目录内容的文件数组。 + * @since 1.0 + */ + public static File[] listAll(File file, + javax.swing.filechooser.FileFilter filter) { + ArrayList list = new ArrayList(); + File[] files; + if (!file.exists() || file.isFile()) { + return null; + } + list(list, file, filter); + files = new File[list.size()]; + list.toArray(files); + return files; + } + + /** + * 将目录中的内容添加到列表。 + * + * @param list 文件列表 + * @param filter 过滤器 + * @param file 目录 + */ + private static void list(ArrayList list, File file, + javax.swing.filechooser.FileFilter filter) { + if (filter.accept(file)) { + list.add(file); + if (file.isFile()) { + return; + } + } + if (file.isDirectory()) { + File files[] = file.listFiles(); + for (int i = 0; i < files.length; i++) { + list(list, files[i], filter); + } + } + + } + + /** + * 返回文件的URL地址。 + * + * @param file 文件 + * @return 文件对应的的URL地址 + * @throws MalformedURLException + * @since 1.0 + * @deprecated 在实现的时候没有注意到File类本身带一个toURL方法将文件路径转换为URL。 + * 请使用File.toURL方法。 + */ + public static URL getURL(File file) throws MalformedURLException { + String fileURL = "file:/" + file.getAbsolutePath(); + URL url = new URL(fileURL); + return url; + } + + /** + * 从文件路径得到文件名。 + * + * @param filePath 文件的路径,可以是相对路径也可以是绝对路径 + * @return 对应的文件名 + * @since 1.0 + */ + public static String getFileName(String filePath) { + File file = new File(filePath); + return file.getName(); + } + + /** + * 从文件名得到文件绝对路径。 + * + * @param fileName 文件名 + * @return 对应的文件路径 + * @since 1.0 + */ + public static String getFilePath(String fileName) { + File file = new File(fileName); + return file.getAbsolutePath(); + } + + /** + * 将DOS/Windows格式的路径转换为UNIX/Linux格式的路径。 + * 其实就是将路径中的"\"全部换为"/",因为在某些情况下我们转换为这种方式比较方便, + * 某中程度上说"/"比"\"更适合作为路径分隔符,而且DOS/Windows也将它当作路径分隔符。 + * + * @param filePath 转换前的路径 + * @return 转换后的路径 + * @since 1.0 + */ + public static String toUNIXpath(String filePath) { + return filePath.replace('\\', '/'); + } + + /** + * 从文件名得到UNIX风格的文件绝对路径。 + * + * @param fileName 文件名 + * @return 对应的UNIX风格的文件路径 + * @see #toUNIXpath(String filePath) toUNIXpath + * @since 1.0 + */ + public static String getUNIXfilePath(String fileName) { + File file = new File(fileName); + return toUNIXpath(file.getAbsolutePath()); + } + + /** + * 得到文件的类型。 + * 实际上就是得到文件名中最后一个“.”后面的部分。 + * + * @param fileName 文件名 + * @return 文件名中的类型部分 + * @since 1.0 + */ + public static String getTypePart(String fileName) { + int point = fileName.lastIndexOf('.'); + int length = fileName.length(); + if (point == -1 || point == length - 1) { + return ""; + } else { + return fileName.substring(point + 1, length); + } + } + + /** + * 得到文件的类型。 + * 实际上就是得到文件名中最后一个“.”后面的部分。 + * + * @param file 文件 + * @return 文件名中的类型部分 + * @since 1.0 + */ + public static String getFileType(File file) { + return getTypePart(file.getName()); + } + + /** + * 得到文件的名字部分。 + * 实际上就是路径中的最后一个路径分隔符后的部分。 + * + * @param fileName 文件名 + * @return 文件名中的名字部分 + * @since 1.0 + */ + public static String getNamePart(String fileName) { + int point = getPathLsatIndex(fileName); + int length = fileName.length(); + if (point == -1) { + return fileName; + } else if (point == length - 1) { + int secondPoint = getPathLsatIndex(fileName, point - 1); + if (secondPoint == -1) { + if (length == 1) { + return fileName; + } else { + return fileName.substring(0, point); + } + } else { + return fileName.substring(secondPoint + 1, point); + } + } else { + return fileName.substring(point + 1); + } + } + + /** + * 得到文件名中的父路径部分。 + * 对两种路径分隔符都有效。 + * 不存在时返回""。 + * 如果文件名是以路径分隔符结尾的则不考虑该分隔符,例如"/path/"返回""。 + * + * @param fileName 文件名 + * @return 父路径,不存在或者已经是父目录时返回"" + * @since 1.0 + */ + public static String getPathPart(String fileName) { + int point = getPathLsatIndex(fileName); + int length = fileName.length(); + if (point == -1) { + return ""; + } else if (point == length - 1) { + int secondPoint = getPathLsatIndex(fileName, point - 1); + if (secondPoint == -1) { + return ""; + } else { + return fileName.substring(0, secondPoint); + } + } else { + return fileName.substring(0, point); + } + } + + /** + * 得到路径分隔符在文件路径中首次出现的位置。 + * 对于DOS或者UNIX风格的分隔符都可以。 + * + * @param fileName 文件路径 + * @return 路径分隔符在路径中首次出现的位置,没有出现时返回-1。 + * @since 1.0 + */ + public static int getPathIndex(String fileName) { + int point = fileName.indexOf('/'); + if (point == -1) { + point = fileName.indexOf('\\'); + } + return point; + } + + /** + * 得到路径分隔符在文件路径中指定位置后首次出现的位置。 + * 对于DOS或者UNIX风格的分隔符都可以。 + * + * @param fileName 文件路径 + * @param fromIndex 开始查找的位置 + * @return 路径分隔符在路径中指定位置后首次出现的位置,没有出现时返回-1。 + * @since 1.0 + */ + public static int getPathIndex(String fileName, int fromIndex) { + int point = fileName.indexOf('/', fromIndex); + if (point == -1) { + point = fileName.indexOf('\\', fromIndex); + } + return point; + } + + /** + * 得到路径分隔符在文件路径中最后出现的位置。 + * 对于DOS或者UNIX风格的分隔符都可以。 + * + * @param fileName 文件路径 + * @return 路径分隔符在路径中最后出现的位置,没有出现时返回-1。 + * @since 1.0 + */ + public static int getPathLsatIndex(String fileName) { + int point = fileName.lastIndexOf('/'); + if (point == -1) { + point = fileName.lastIndexOf('\\'); + } + return point; + } + + /** + * 得到路径分隔符在文件路径中指定位置前最后出现的位置。 + * 对于DOS或者UNIX风格的分隔符都可以。 + * + * @param fileName 文件路径 + * @param fromIndex 开始查找的位置 + * @return 路径分隔符在路径中指定位置前最后出现的位置,没有出现时返回-1。 + * @since 1.0 + */ + public static int getPathLsatIndex(String fileName, int fromIndex) { + int point = fileName.lastIndexOf('/', fromIndex); + if (point == -1) { + point = fileName.lastIndexOf('\\', fromIndex); + } + return point; + } + + /** + * 将文件名中的类型部分去掉。 + * + * @param filename 文件名 + * @return 去掉类型部分的结果 + * @since 1.0 + */ + public static String trimType(String filename) { + int index = filename.lastIndexOf("."); + if (index != -1) { + return filename.substring(0, index); + } else { + return filename; + } + } + + /** + * 得到相对路径。 + * 文件名不是目录名的子节点时返回文件名。 + * + * @param pathName 目录名 + * @param fileName 文件名 + * @return 得到文件名相对于目录名的相对路径,目录下不存在该文件时返回文件名 + * @since 1.0 + */ + public static String getSubpath(String pathName, String fileName) { + int index = fileName.indexOf(pathName); + if (index != -1) { + return fileName.substring(index + pathName.length() + 1); + } else { + return fileName; + } + } + + /** + * 检查给定目录的存在性 + * 保证指定的路径可用,如果指定的路径不存在,那么建立该路径,可以为多级路径 + * + * @param path + * @return 真假值 + * @since 1.0 + */ + public static final boolean pathValidate(String path) { + //String path="d:/web/www/sub"; + //System.out.println(path); + //path = getUNIXfilePath(path); + + //path = ereg_replace("^\\/+", "", path); + //path = ereg_replace("\\/+$", "", path); + String[] arraypath = path.split("/"); + String tmppath = ""; + for (int i = 0; i < arraypath.length; i++) { + tmppath += "/" + arraypath[i]; + File d = new File(tmppath.substring(1)); + if (!d.exists()) { //检查Sub目录是否存在 + System.out.println(tmppath.substring(1)); + if (!d.mkdir()) { + return false; + } + } + } + return true; + } + + /** + * 读取文件的内容 + * 读取指定文件的内容 + * + * @param path 为要读取文件的绝对路径 + * @return 以行读取文件后的内容。 + * @since 1.0 + */ + public static final String getFileContent(String path) throws IOException { + String filecontent = ""; + try { + File f = new File(path); + if (f.exists()) { + FileReader fr = new FileReader(path); + BufferedReader br = new BufferedReader(fr); //建立BufferedReader对象,并实例化为br + String line = br.readLine(); //从文件读取一行字符串 + //判断读取到的字符串是否不为空 + while (line != null) { + filecontent += line + "\n"; + line = br.readLine(); //从文件中继续读取一行数据 + } + br.close(); //关闭BufferedReader对象 + fr.close(); //关闭文件 + } + + } catch (IOException e) { + throw e; + } + return filecontent; + } + + /** + * 根据内容生成文件 + * + * @param path 要生成文件的绝对路径, + * @param modulecontent 文件的内容。 + * @return 真假值 + * @since 1.0 + */ + public static final boolean genModuleTpl(String path, String modulecontent) throws IOException { + + path = getUNIXfilePath(path); + String[] patharray = path.split("\\/"); + String modulepath = ""; + for (int i = 0; i < patharray.length - 1; i++) { + modulepath += "/" + patharray[i]; + } + File d = new File(modulepath.substring(1)); + if (!d.exists()) { + if (!pathValidate(modulepath.substring(1))) { + return false; + } + } + try { + FileWriter fw = new FileWriter(path); //建立FileWriter对象,并实例化fw + //将字符串写入文件 + fw.write(modulecontent); + fw.close(); + } catch (IOException e) { + throw e; + } + return true; + } + + /** + * 获取图片文件的扩展名(发布系统专用) + * + * @param pic_path 为图片名称加上前面的路径不包括扩展名 + * @return 图片的扩展名 + * @since 1.0 + */ + public static final String getPicExtendName(String pic_path) { + pic_path = getUNIXfilePath(pic_path); + String pic_extend = ""; + if (isFileExist(pic_path + ".gif")) { + pic_extend = ".gif"; + } + if (isFileExist(pic_path + ".jpeg")) { + pic_extend = ".jpeg"; + } + if (isFileExist(pic_path + ".jpg")) { + pic_extend = ".jpg"; + } + if (isFileExist(pic_path + ".png")) { + pic_extend = ".png"; + } + return pic_extend; //返回图片扩展名 + } + + /** + * 拷贝文件 + * + * @param in 输入文件 + * @param out 输出文件 + * @return + * @throws Exception + */ + public static final boolean CopyFile(File in, File out) throws Exception { + try { + FileInputStream fis = new FileInputStream(in); + FileOutputStream fos = new FileOutputStream(out); + byte[] buf = new byte[1024]; + int i = 0; + while ((i = fis.read(buf)) != -1) { + fos.write(buf, 0, i); + } + fis.close(); + fos.close(); + return true; + } catch (IOException ie) { + ie.printStackTrace(); + return false; + } + } + + /** + * 拷贝文件 + * + * @param infile 输入字符串 + * @param outfile 输出字符串 + * @return + * @throws Exception + */ + public static final boolean CopyFile(String infile, String outfile) throws Exception { + try { + File in = new File(infile); + File out = new File(outfile); + return CopyFile(in, out); + } catch (IOException ie) { + ie.printStackTrace(); + return false; + } + + } + + /** + * 把内容content写的path文件中 + * + * @param content 输入内容 + * @param path 文件路径 + * @return + */ + public static boolean SaveFileAs(String content, String path) { + FileWriter fw = null; + try { + fw = new FileWriter(new File(path), false); + if (content != null) { + fw.write(content); + } + } catch (IOException e) { + e.printStackTrace(); + return false; + } finally { + if (fw != null) { + try { + fw.flush(); + fw.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + return true; + } + + /** + * 以字节为单位读取文件,常用于读二进制文件,如图片、声音、影像等文件。 + */ + public static byte[] readFileByBytes(String fileName) throws IOException { + FileInputStream in = new FileInputStream(fileName); + byte[] bytes = null; + try { + + ByteArrayOutputStream out = new ByteArrayOutputStream(1024); + + byte[] temp = new byte[1024]; + + int size = 0; + + while ((size = in.read(temp)) != -1) { + out.write(temp, 0, size); + } + + in.close(); + + bytes = out.toByteArray(); + + } catch (Exception e1) { + e1.printStackTrace(); + } finally { + if (in != null) { + in.close(); + } + } + return bytes; + } + + /** + * 以字符为单位读取文件,常用于读文本,数字等类型的文件 + */ + public static void readFileByChars(String fileName) { + File file = new File(fileName); + Reader reader = null; + try { + System.out.println("以字符为单位读取文件内容,一次读一个字节:"); + // 一次读一个字符 + reader = new InputStreamReader(new FileInputStream(file)); + int tempchar; + while ((tempchar = reader.read()) != -1) { + // 对于windows下,\r\n这两个字符在一起时,表示一个换行。 + // 但如果这两个字符分开显示时,会换两次行。 + // 因此,屏蔽掉\r,或者屏蔽\n。否则,将会多出很多空行。 + if (((char) tempchar) != '\r') { + System.out.print((char) tempchar); + } + } + reader.close(); + } catch (Exception e) { + e.printStackTrace(); + } + try { + System.out.println("以字符为单位读取文件内容,一次读多个字节:"); + // 一次读多个字符 + char[] tempchars = new char[30]; + int charread = 0; + reader = new InputStreamReader(new FileInputStream(fileName)); + // 读入多个字符到字符数组中,charread为一次读取字符数 + while ((charread = reader.read(tempchars)) != -1) { + // 同样屏蔽掉\r不显示 + if ((charread == tempchars.length) + && (tempchars[tempchars.length - 1] != '\r')) { + System.out.print(tempchars); + } else { + for (int i = 0; i < charread; i++) { + if (tempchars[i] == '\r') { + continue; + } else { + System.out.print(tempchars[i]); + } + } + } + } + + } catch (Exception e1) { + e1.printStackTrace(); + } finally { + if (reader != null) { + try { + reader.close(); + } catch (IOException e1) { + } + } + } + } + + /** + * 以行为单位读取文件,常用于读面向行的格式化文件 + */ + public static List readFileByLines(String fileName) { + List returnString = new ArrayList(); + File file = new File(fileName); + BufferedReader reader = null; + try { + reader = new BufferedReader(new FileReader(file)); + String tempString = null; + int line = 1; + // 一次读入一行,直到读入null为文件结束 + while ((tempString = reader.readLine()) != null) { + // 显示行号 + returnString.add(tempString); + line++; + } + reader.close(); + } catch (IOException e) { + e.printStackTrace(); + } finally { + if (reader != null) { + try { + reader.close(); + } catch (IOException e1) { + } + } + } + return returnString; + } + + /** + * 随机读取文件内容 + */ + public static void readFileByRandomAccess(String fileName) { + RandomAccessFile randomFile = null; + try { + System.out.println("随机读取一段文件内容:"); + // 打开一个随机访问文件流,按只读方式 + randomFile = new RandomAccessFile(fileName, "r"); + // 文件长度,字节数 + long fileLength = randomFile.length(); + // 读文件的起始位置 + int beginIndex = (fileLength > 4) ? 4 : 0; + // 将读文件的开始位置移到beginIndex位置。 + randomFile.seek(beginIndex); + byte[] bytes = new byte[10]; + int byteread = 0; + // 一次读10个字节,如果文件内容不足10个字节,则读剩下的字节。 + // 将一次读取的字节数赋给byteread + while ((byteread = randomFile.read(bytes)) != -1) { + System.out.write(bytes, 0, byteread); + } + } catch (IOException e) { + e.printStackTrace(); + } finally { + if (randomFile != null) { + try { + randomFile.close(); + } catch (IOException e1) { + } + } + } + } + + /** + * 读取文件内容 + * + * @param fileName + * @return + */ + public static String readFileAll(String fileName) { + String encoding = "UTF-8"; + File file = new File(fileName); + Long filelength = file.length(); + byte[] filecontent = new byte[filelength.intValue()]; + try { + FileInputStream in = new FileInputStream(file); + in.read(filecontent); + in.close(); + } catch (FileNotFoundException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } + try { + return new String(filecontent, encoding); + } catch (UnsupportedEncodingException e) { + System.err.println("The OS does not support " + encoding); + e.printStackTrace(); + return ""; + } + } + + /** + * 显示输入流中还剩的字节数 + */ + private static void showAvailableBytes(InputStream in) { + try { + System.out.println("当前字节输入流中的字节数为:" + in.available()); + } catch (IOException e) { + e.printStackTrace(); + } + } + + public static void inputStream2File(InputStream is, File target) { + try { + if (!target.exists() && !target.createNewFile()) { + throw new RuntimeException(target.getName() + "not exists"); + } + OutputStream outStream = new FileOutputStream(target); + byte[] buffer = new byte[8 * 1024]; + int bytesRead; + while ((bytesRead = is.read(buffer)) != -1) { + outStream.write(buffer, 0, bytesRead); + } + } catch (FileNotFoundException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } + } +} \ No newline at end of file diff --git a/admin/src/main/java/com/example/frame/utils/ZipUtil.java b/admin/src/main/java/com/example/frame/utils/ZipUtil.java new file mode 100644 index 0000000..536254c --- /dev/null +++ b/admin/src/main/java/com/example/frame/utils/ZipUtil.java @@ -0,0 +1,138 @@ +package com.example.frame.utils; + +import java.io.*; +import java.util.Enumeration; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; +import java.util.zip.ZipOutputStream; + +public class ZipUtil { + + /** + * 解压文件 + * + * @param zipFile 指定解压Zip文件 + * @param descDir 指定解压目录 + */ + public static void unZip(File zipFile, File descDir) { + if (!descDir.exists()) { + descDir.mkdirs(); + } + ZipFile zip = null; + try { + zip = new ZipFile(zipFile); + Enumeration entries = zip.entries(); + while (entries.hasMoreElements()) { + ZipEntry entry = (ZipEntry) entries.nextElement(); + // 如果是文件夹,就创建个文件夹 + if (entry.isDirectory()) { + File dir = new File(descDir, entry.getName()); + dir.mkdirs(); + } else { + // 如果是文件,就先创建一个文件,然后用io流把内容copy过去 + File targetFile = new File(descDir + "/" + entry.getName()); + // 保证这个文件的父文件夹必须要存在 + if (!targetFile.getParentFile().exists()) { + targetFile.getParentFile().mkdirs(); + } + targetFile.createNewFile(); + // 将压缩文件内容写入到这个文件中 + InputStream is = zip.getInputStream(entry); + FileOutputStream fos = new FileOutputStream(targetFile); + int len; + byte[] buf = new byte[1024]; + while ((len = is.read(buf)) != -1) { + fos.write(buf, 0, len); + } + // 关流顺序,先打开的后关闭 + fos.close(); + is.close(); + } + } + System.out.println("解压文件" + zipFile.getAbsolutePath() + "成功"); + } catch (Exception e) { + throw new RuntimeException("unzip error from ZipUtil", e); + } finally { + if (zip != null) { + try { + zip.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + } + + /** + * 压缩文件或文件夹 + * + * @param src 指定待压缩目录或文件 + * @param zip 指定压缩Zip文件 + */ + public static void toZip(File src, File zip) { + ZipOutputStream zipOutputStream = null; + try { + if (!src.exists()) { + System.err.println("压缩文件或目录不存在"); + return; + } + + if (!zip.exists()) { + zip.createNewFile(); + } + + zipOutputStream = new ZipOutputStream(new FileOutputStream(zip)); + compress(src, zipOutputStream, null); + zipOutputStream.close(); + System.out.println("压缩文件" + zip.getAbsolutePath() + "成功"); + } catch (IOException e) { + System.err.println("压缩失败"); + e.printStackTrace(); + } finally { + if (zipOutputStream != null) { + try { + zipOutputStream.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + } + + /** + * @param sourceFile 待压缩文件或文件夹 + * @param zos Zip输出流 + * @param parentPath 父目录结构 + * @throws IOException + */ + private static void compress(File sourceFile, ZipOutputStream zos, String parentPath) throws IOException { + byte[] buff = new byte[1024]; + + if (sourceFile.isDirectory()) { + //确保空文件夹也可以压缩进去 + zos.putNextEntry(new ZipEntry((parentPath != null ? parentPath : "") + sourceFile.getName() + "/")); + zos.closeEntry(); + //循环压缩子文件或文件夹 + for (File file : sourceFile.listFiles()) { + compress(file, zos, (parentPath != null ? parentPath : "") + sourceFile.getName() + "/"); + } + } else { + //压缩文件 + zos.putNextEntry(new ZipEntry((parentPath != null ? parentPath : "") + sourceFile.getName())); + int len; + FileInputStream in = new FileInputStream(sourceFile); + while ((len = in.read(buff)) != -1) { + zos.write(buff, 0, len); + } + zos.closeEntry(); + in.close(); + } + } + + public static void main(String[] args) { + //解压 + unZip(new File("E:\\AAA.zip"), new File("E:\\AAA")); + //压缩 + toZip(new File("E:\\AAA"), new File("E:\\AAA.zip")); + } +} diff --git a/admin/src/main/java/com/example/module/admin/ent/NginxCtrl.java b/admin/src/main/java/com/example/module/admin/ent/NginxCtrl.java new file mode 100644 index 0000000..1db8d84 --- /dev/null +++ b/admin/src/main/java/com/example/module/admin/ent/NginxCtrl.java @@ -0,0 +1,56 @@ +package com.example.module.admin.ent; + + +import com.example.frame.utils.ProcessUtil; + +public class NginxCtrl { + + private String startCmd = "start nginx"; + private String stopCmd = "nginx.exe -s quit"; + private String reloadCmd = "nginx.exe -s reload"; + private String versionCmd = "nginx -v"; + + public void doStart(){ + ProcessUtil.exec(startCmd); + } + + public void doStop(){ + ProcessUtil.exec(stopCmd); + } + + public void doReload(){ + ProcessUtil.exec(reloadCmd); + } + + public String getStartCmd() { + return startCmd; + } + + public void setStartCmd(String startCmd) { + this.startCmd = startCmd; + } + + public String getStopCmd() { + return stopCmd; + } + + public void setStopCmd(String stopCmd) { + this.stopCmd = stopCmd; + } + + public String getReloadCmd() { + return reloadCmd; + } + + public void setReloadCmd(String reloadCmd) { + this.reloadCmd = reloadCmd; + } + + public String getVersionCmd() { + return versionCmd; + } + + public void setVersionCmd(String versionCmd) { + this.versionCmd = versionCmd; + } +} diff --git a/admin/src/main/java/com/example/module/admin/ent/State.java b/admin/src/main/java/com/example/module/admin/ent/State.java new file mode 100644 index 0000000..0f19da8 --- /dev/null +++ b/admin/src/main/java/com/example/module/admin/ent/State.java @@ -0,0 +1,18 @@ +package com.example.module.admin.ent; + +/** + * 运行状态 + */ +public class State { + + // 0 为关闭,1为运行 + private boolean run; + + public boolean isRun() { + return run; + } + + public void setRun(boolean run) { + this.run = run; + } +} diff --git a/admin/src/main/resources/application-dev.properties b/admin/src/main/resources/application-dev.properties index 5db2c35..78934af 100644 --- a/admin/src/main/resources/application-dev.properties +++ b/admin/src/main/resources/application-dev.properties @@ -13,10 +13,8 @@ spring.http.encoding.enabled=true server.tomcat.uri-encoding=UTF-8 # 根路径、欢迎页 web.welcome.page=/index.htm -# 需要验证授权, 既访问时组装Token -web.url.auth.included=/** # 不需要验证授权, 或该请求有自己的验证机制 -web.url.auth.excluded=/favicon.ico,/static/**,/api,/login.htm,/mapping.htm +web.url.auth.excluded=/favicon.ico,/static/**,/api,/login.htm,/mapping.htm,/sse/** # 日志配置 logging.path=D:// logging.levels=DEBUG @@ -49,4 +47,5 @@ spring.freemarker.settings.url_escaping_charset=utf-8 spring.servlet.multipart.resolveLazily=false spring.servlet.multipart.max-file-size=100MB spring.servlet.multipart.max-request-size=100MB -dbpath= \ No newline at end of file +dbpath= +nginx-path= \ No newline at end of file diff --git a/admin/src/main/resources/application-prod.properties b/admin/src/main/resources/application-prod.properties index 30ba5e8..16791ab 100644 --- a/admin/src/main/resources/application-prod.properties +++ b/admin/src/main/resources/application-prod.properties @@ -13,8 +13,6 @@ spring.http.encoding.enabled=true server.tomcat.uri-encoding=UTF-8 # 根路径、欢迎页 web.welcome.page=/index.htm -# 需要验证授权, 既访问时组装Token -web.url.auth.included=/** # 不需要验证授权, 或该请求有自己的验证机制 web.url.auth.excluded=/favicon.ico,/static/**,/api,/login.htm # 日志配置 @@ -49,3 +47,5 @@ spring.freemarker.settings.url_escaping_charset=utf-8 spring.servlet.multipart.resolveLazily=false spring.servlet.multipart.max-file-size=100MB spring.servlet.multipart.max-request-size=100MB +dbpath= +nginx-path= \ No newline at end of file diff --git a/admin/src/main/resources/nginx-1.14.2.zip b/admin/src/main/resources/nginx-1.14.2.zip index a403aee7a4c18e008ca05d1edaa17b8d336c2cda..6dc614266bc1f0c4b55fc83643ac15ee52dd2a67 100644 GIT binary patch delta 2680 zcmZuyYitx%6rP#Rwp;p`c6WO<+wLPkTMBJ=QIQ|SEfk3g6akZpga|~CvVCo7OScqg zK?F?Dw2XKOm!B!QvE@cw1Ft781cJzX4@TLHz(hnbH4fR zx#!+{=GumZcW%GbY?n=fAhZ;_JsHdHkvMX#b_hDIW|UCm~Udj zpg!^{2W2^h`EFQqMK02jllG+kZh)MSWiz5`Vftv<#%Q(aUr2H?8x=YmM47Ol;wI{^ zF1zetZpG~~$vJFPkQtP?j}bbqW=kfXhk7$NOHKoUR@IO+^{G6&?9`u}>gK!>9#ik- zdAM6G$bXS9iOyS1tZF#lE@v<{o-$O?^Cgd|-JU$|#=zP!+PlTa%c6!WT-wrqtGIBJ zFMUhT5;4@XM7lhY2SUfUOG6T08C_aWY@wbV(quC)L&)AL9W%)ZY_jWZ(gMvnZ@=V` zxHxTTmt1lZ!l4uGQc&dbp_%VXZ;M)d=mW%AxkIiGCB@8(7Ap8y^2hQa4R=ajIUeyX zcRM9m3`~ZZV6m_`m>Cui8wE>%CBl+m$uI?$0!xKiU{;tdbhk5Y$`PKxJEg?M_lMT` ziI+CDOET^BlU%;t^iMw-&$FZ6Qj(+j{G}wW~Sx4$D2RYF|?ur38!-P|CLuq0q z8LOO}aGH~Kqy?2A8w*{Ed}%`^3QdMIvX1mHBO7aX0N0BCUWwY(_x-??b)-wHKsFDj z!M4_K2e~*vp0(J9{ciZ1ng3XTjPw1MdWMTcFuESwP}5oeTLE=-M=cRDOlFCk!S!b?f_7AqsTRj@j3pD};xPyHtRgmFZ&Ei;D1!GV^w&Uo z{3Ik(h)v2d=w#tgs|A@kGz1+ou3OWIpr)hl%_jFvzb{K~Z&$P9=$%TEM5k7hIAz=C zJvChbc|Z$JA)Lw14#jLN$@tygK4g&RQ;VF&#BAW3#c8Bdw!#Xf`9YWQSl9^E-k# z*2`D*aS>!S{@hYY)76rOBh^yH5+9gibk%k%Bs65mx3G_CUeMLJib8-ZukJirD zYl=#Tn))306u$AOfUXj0ku19b6&m<>j8pb3_=#)iNO#s_LgRIw_%6uj>PaTC`LA$t z1hon2i8hI3Bp4>RwO10BRlZW~nLlIN(?wLQCuzPFc~|+{5sXgvlQz%Hi?6m=W!!`d zXnDpA=IlcTht{B>eD_E{*U<4{JF;Ov$j$~b!uNX)ot?sm*G*nmxIxuI$3NZo5uL*p^&d=KPBYjI4BBA1!} ztF>t4KW@Fo6NzASB5Q&Z<_vzi;96m z8OXaC`I%h3a-GB>J1%)g2GbDGHW_K`0V^B;k~W_fwQQ(IR}y-mbM XS2Pm4&4dyOf*1dd7D4C-Nf7=An3>R# delta 3915 zcmZ`+3s6%>6n%LifFux}garf13m_mNsr@KpKUVGNbl;++yZdD3&QA87bMC!+ zckeF$+?#c6)$H|pA)Mnl=i(w~wZ(3=kF<`k!Bwri9!_w8pm1V-gHaTWG`KVS8--w^ zXS?+LNrf@-iVKb1OtPf6=hbi@paYfM#)8TBKla6 z5KW8t-qj}x3G{4NyZ$gQ#4*!O!!m`CL{Iw?MBr13dLfPe(v={JDq$2oEfKnA4}F7Y z8Tix16kf$Hn(w>x6d{q(3O3t>bY?coTq}b;A%}&zOMua*vt+SV^-Z@}gj7a+x+TiD z$#Pl-UtpEP7g(dZc3JDj$!sxSMw!T_+Vxu5rMv9@sI$&f_^tE!t9F*+58uG|@oXu( z8~MCYc9;04e!%ytgjhN^7`c<5DFZLv$=i6D_+fJ^FA92E#(%t(uU4=b_{Z($8x?Xo z7u%2yb0Yi?+IVd!yA%DI4!$^;orm6TK0^R>ICbsaJTfJ+AY>|J!N@|8g(4e-EDV_% znFd)nvIt~aWRb`OWP_1KA=CNWyQ9Z+%B7It03v%jV59?N7(_ZG&!Vz&TVa9QvtV%% zG&z7t&m3?JDX(x$aIxGc z!%wMu_FPar9mj@U4nx18-3<)lU_ysEaWK3HC_Ig!&S+k|a~5 zrJ1jkfF$+!@)ejl=KPEOPS=zGO|pS-dMt{fE^s*uZEkq32pH8r-CKzT;-=dB1@0gL zsz8r*lmOI}q5{3Y{4Gq~G4VM}O}|`w!n+Nv>y`+PBUd{Ioi(z=u2Nvqmi}6c6}a1z zq!I5R_|OF+gcx+Ikc6|P;AJs^NGz8D4J!k~)g9SuuaQ&WiQ1eUYcch7e%wGnV$uLpIvOqLj1o-gE`mxYm>{MN0HtkxQwC^&&liJmxWWlS z)GO=OV_#xJG7dBS{`I&)nP^8(L`yh|kkTc~`TW+2_ImyOjacRUs>FVk%zc+DuOWr2 zsAyzBO~d3=@?9WKYn<7LReI;82J#M+P|EwHnPS6s7nm$24g@NJ9x)(IImj2&2SC!o zykMb}d|75A98&>w+SJwCa2y4HM+V{`U0k5q@CNHbXQf<7xAzF`t)0zSzPvyakPoyJ zDT6m@Cc50C;Mpapys1PM2W5dHDu6)?x3*w6$ukB$%gt^Z6`&8@{&0H*c4@?O7%Fxfc$P?${T*L1IuUR{wuY2#k4#HFLG#C zaiQFyb*rbuY1d=8YAHIGGd(~vl#Io< zkteAOT?^bcc+CTJu)zaD@-B5Az#@-^++(8J@{VUpN#DvVhER^HM{gFgTlh9n(wiH3 z9lV8supVl;2qrU!i%i%(44?h{<+-g){ z@V#U+rcga&=-1yd=d@eIha T42+Gib9Qbz`tQ+;K6BiE48Pj` diff --git a/admin/src/main/resources/static/img/restart.png b/admin/src/main/resources/static/img/restart.png new file mode 100644 index 0000000000000000000000000000000000000000..410ab46d68f9ccd2a096db635c4d84bd20d68e33 GIT binary patch literal 3980 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 b;qw0ge}tBkMP14(00000NkvXXu0mjf2J1)x literal 0 HcmV?d00001 diff --git a/admin/src/main/resources/templates/screen/mapping.ftl b/admin/src/main/resources/templates/screen/mapping.ftl index 12c4fd8..00f4364 100644 --- a/admin/src/main/resources/templates/screen/mapping.ftl +++ b/admin/src/main/resources/templates/screen/mapping.ftl @@ -14,6 +14,27 @@ + + Nginx 控制中心 + + + + + + + + + + + + + + @@ -86,7 +107,8 @@ 编辑资源 编辑 - 删除 + 删除 + @@ -111,6 +133,21 @@ .box-card { margin: 10px; } + + .control a { + display: inline-block; + line-height: 50px; + height: 50px; + vertical-align: top; + } + + .control a:hover { + background: #e7e7e7; + } + + .control a:first-child:hover { + background: #ffffff; + }