├── .DS_Store
├── .gitattributes
├── .gitignore
├── .idea
├── artifacts
│ ├── picuang_war.xml
│ └── picuang_war_exploded.xml
├── compiler.xml
├── encodings.xml
├── jarRepositories.xml
├── libraries
│ ├── Maven__ch_qos_logback_logback_classic_1_2_3.xml
│ ├── Maven__ch_qos_logback_logback_core_1_2_3.xml
│ ├── Maven__com_fasterxml_classmate_1_4_0.xml
│ ├── Maven__com_fasterxml_jackson_core_jackson_annotations_2_9_0.xml
│ ├── Maven__com_fasterxml_jackson_core_jackson_core_2_9_9.xml
│ ├── Maven__com_fasterxml_jackson_core_jackson_databind_2_9_9.xml
│ ├── Maven__com_fasterxml_jackson_datatype_jackson_datatype_jdk8_2_9_9.xml
│ ├── Maven__com_fasterxml_jackson_datatype_jackson_datatype_jsr310_2_9_9.xml
│ ├── Maven__com_fasterxml_jackson_module_jackson_module_parameter_names_2_9_9.xml
│ ├── Maven__com_jayway_jsonpath_json_path_2_4_0.xml
│ ├── Maven__com_vaadin_external_google_android_json_0_0_20131108_vaadin1.xml
│ ├── Maven__javax_activation_javax_activation_api_1_2_0.xml
│ ├── Maven__javax_annotation_javax_annotation_api_1_3_2.xml
│ ├── Maven__javax_validation_validation_api_2_0_1_Final.xml
│ ├── Maven__javax_xml_bind_jaxb_api_2_3_1.xml
│ ├── Maven__junit_junit_4_12.xml
│ ├── Maven__net_bytebuddy_byte_buddy_1_9_13.xml
│ ├── Maven__net_bytebuddy_byte_buddy_agent_1_9_13.xml
│ ├── Maven__net_minidev_accessors_smart_1_2.xml
│ ├── Maven__net_minidev_json_smart_2_3.xml
│ ├── Maven__org_apache_logging_log4j_log4j_api_2_11_2.xml
│ ├── Maven__org_apache_logging_log4j_log4j_to_slf4j_2_11_2.xml
│ ├── Maven__org_apache_tomcat_embed_tomcat_embed_core_9_0_21.xml
│ ├── Maven__org_apache_tomcat_embed_tomcat_embed_el_9_0_21.xml
│ ├── Maven__org_apache_tomcat_embed_tomcat_embed_websocket_9_0_21.xml
│ ├── Maven__org_assertj_assertj_core_3_11_1.xml
│ ├── Maven__org_attoparser_attoparser_2_0_5_RELEASE.xml
│ ├── Maven__org_hamcrest_hamcrest_core_1_3.xml
│ ├── Maven__org_hamcrest_hamcrest_library_1_3.xml
│ ├── Maven__org_hibernate_validator_hibernate_validator_6_0_17_Final.xml
│ ├── Maven__org_jboss_logging_jboss_logging_3_3_2_Final.xml
│ ├── Maven__org_mockito_mockito_core_2_23_4.xml
│ ├── Maven__org_objenesis_objenesis_2_6.xml
│ ├── Maven__org_ow2_asm_asm_5_0_4.xml
│ ├── Maven__org_skyscreamer_jsonassert_1_5_0.xml
│ ├── Maven__org_slf4j_jul_to_slf4j_1_7_26.xml
│ ├── Maven__org_slf4j_slf4j_api_1_7_26.xml
│ ├── Maven__org_springframework_boot_spring_boot_2_1_6_RELEASE.xml
│ ├── Maven__org_springframework_boot_spring_boot_autoconfigure_2_1_6_RELEASE.xml
│ ├── Maven__org_springframework_boot_spring_boot_devtools_2_1_6_RELEASE.xml
│ ├── Maven__org_springframework_boot_spring_boot_starter_2_1_6_RELEASE.xml
│ ├── Maven__org_springframework_boot_spring_boot_starter_json_2_1_6_RELEASE.xml
│ ├── Maven__org_springframework_boot_spring_boot_starter_logging_2_1_6_RELEASE.xml
│ ├── Maven__org_springframework_boot_spring_boot_starter_test_2_1_6_RELEASE.xml
│ ├── Maven__org_springframework_boot_spring_boot_starter_thymeleaf_2_1_6_RELEASE.xml
│ ├── Maven__org_springframework_boot_spring_boot_starter_tomcat_2_1_6_RELEASE.xml
│ ├── Maven__org_springframework_boot_spring_boot_starter_web_2_1_6_RELEASE.xml
│ ├── Maven__org_springframework_boot_spring_boot_test_2_1_6_RELEASE.xml
│ ├── Maven__org_springframework_boot_spring_boot_test_autoconfigure_2_1_6_RELEASE.xml
│ ├── Maven__org_springframework_spring_aop_5_1_8_RELEASE.xml
│ ├── Maven__org_springframework_spring_beans_5_1_8_RELEASE.xml
│ ├── Maven__org_springframework_spring_context_5_1_8_RELEASE.xml
│ ├── Maven__org_springframework_spring_core_5_1_8_RELEASE.xml
│ ├── Maven__org_springframework_spring_expression_5_1_8_RELEASE.xml
│ ├── Maven__org_springframework_spring_jcl_5_1_8_RELEASE.xml
│ ├── Maven__org_springframework_spring_test_5_1_8_RELEASE.xml
│ ├── Maven__org_springframework_spring_web_5_1_8_RELEASE.xml
│ ├── Maven__org_springframework_spring_webmvc_5_1_8_RELEASE.xml
│ ├── Maven__org_thymeleaf_extras_thymeleaf_extras_java8time_3_0_4_RELEASE.xml
│ ├── Maven__org_thymeleaf_thymeleaf_3_0_11_RELEASE.xml
│ ├── Maven__org_thymeleaf_thymeleaf_spring5_3_0_11_RELEASE.xml
│ ├── Maven__org_unbescape_unbescape_1_1_6_RELEASE.xml
│ ├── Maven__org_xmlunit_xmlunit_core_2_6_2.xml
│ └── Maven__org_yaml_snakeyaml_1_23.xml
├── misc.xml
├── modules.xml
├── uiDesigner.xml
├── vcs.xml
└── workspace.xml
├── LICENSE
├── Picuang_logo.png
├── README.md
├── mvnw
├── mvnw.cmd
├── picuang.iml
├── pom.xml
└── src
├── META-INF
└── MANIFEST.MF
├── main
├── java
│ ├── META-INF
│ │ └── MANIFEST.MF
│ └── pers
│ │ └── adlered
│ │ ├── picuang
│ │ ├── Application.java
│ │ ├── PicuangApplication.java
│ │ ├── Test.java
│ │ ├── access
│ │ │ ├── HttpOrHttpsAccess.java
│ │ │ └── MyX509TrustManager.java
│ │ ├── controller
│ │ │ ├── MainController.java
│ │ │ ├── UploadController.java
│ │ │ ├── admin
│ │ │ │ ├── Admin.java
│ │ │ │ └── api
│ │ │ │ │ ├── AdminAction.java
│ │ │ │ │ └── ConfigAction.java
│ │ │ └── api
│ │ │ │ ├── History.java
│ │ │ │ └── bean
│ │ │ │ └── PicProp.java
│ │ ├── log
│ │ │ └── Logger.java
│ │ ├── prop
│ │ │ └── Prop.java
│ │ ├── result
│ │ │ └── Result.java
│ │ └── tool
│ │ │ ├── FileUtil.java
│ │ │ ├── IPUtil.java
│ │ │ ├── ToolBox.java
│ │ │ └── double_keys
│ │ │ ├── main
│ │ │ └── DoubleKeys.java
│ │ │ └── storage
│ │ │ └── DoubleKeysStorage.java
│ │ └── simplecurrentlimiter
│ │ ├── cache
│ │ ├── MainCache.java
│ │ └── pair
│ │ │ └── CachePair.java
│ │ ├── control
│ │ └── MainControl.java
│ │ └── main
│ │ └── SimpleCurrentLimiter.java
└── resources
│ ├── application.properties
│ ├── static
│ ├── background
│ │ └── load.gif
│ ├── bootstrap
│ │ ├── css
│ │ │ ├── bootstrap-theme.css
│ │ │ ├── bootstrap-theme.css.map
│ │ │ ├── bootstrap-theme.min.css
│ │ │ ├── bootstrap-theme.min.css.map
│ │ │ ├── bootstrap.css
│ │ │ ├── bootstrap.css.map
│ │ │ ├── bootstrap.min.css
│ │ │ └── bootstrap.min.css.map
│ │ ├── fonts
│ │ │ ├── glyphicons-halflings-regular.eot
│ │ │ ├── glyphicons-halflings-regular.svg
│ │ │ ├── glyphicons-halflings-regular.ttf
│ │ │ ├── glyphicons-halflings-regular.woff
│ │ │ └── glyphicons-halflings-regular.woff2
│ │ └── js
│ │ │ ├── bootstrap.js
│ │ │ ├── bootstrap.min.js
│ │ │ └── npm.js
│ ├── css
│ │ └── bootstrap
│ │ │ └── cover.css
│ ├── favicon.png
│ └── js
│ │ ├── admin
│ │ ├── config.js
│ │ └── main.js
│ │ ├── axios.min.js
│ │ ├── clipboard.js
│ │ ├── d-toast.min.js
│ │ ├── fantastic-progress-bar-bootstrap.js
│ │ ├── history
│ │ └── histories.js
│ │ ├── jquery.min.js
│ │ ├── lazyload.min.js
│ │ ├── transition.min.js
│ │ ├── user
│ │ ├── click-listen.js
│ │ ├── clipboard-control.js
│ │ ├── common-queue.js
│ │ ├── drag-and-drop.js
│ │ ├── notify.js
│ │ └── on-paste.js
│ │ └── vue.js
│ └── templates
│ ├── admin.html
│ ├── history.html
│ └── index.html
└── test
└── java
└── pers
└── adlered
└── picuang
└── picuang
└── PicuangApplicationTests.java
/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/adlered/Picuang/8c361437de5fe56f45ff6cf6785a033a8e267d2a/.DS_Store
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | *.yml linguist-language=Java
2 | *.html linguist-language=Java
3 | *.js linguist-language=Java
4 | *.xml linguist-language=Java
5 | *.css linguist-language=Java
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | config.ini
2 | config.ini.backup
--------------------------------------------------------------------------------
/.idea/artifacts/picuang_war.xml:
--------------------------------------------------------------------------------
1 |
访问HTTP或HTTPS网站
17 | *当postData为空或null时,使用GET方式访问,反之使用POST方式访问
18 | * 19 | * @author : https://github.com/AdlerED 20 | * @date : 2019-11-06 09:48 21 | **/ 22 | public class HttpOrHttpsAccess { 23 | public static BufferedInputStream get(String path, Map实现不需要加载证书,访问https网站或者接口
6 | * 7 | * @author : https://github.com/AdlerED 8 | * @date : 2019-11-06 09:47 9 | **/ 10 | 11 | import javax.net.ssl.X509TrustManager; 12 | import java.security.cert.CertificateException; 13 | import java.security.cert.X509Certificate; 14 | 15 | public class MyX509TrustManager implements X509TrustManager { 16 | /* 17 | * The default X509TrustManager returned by SunX509. We'll delegate 18 | * decisions to it, and fall back to the logic in this class if the 19 | * default X509TrustManager doesn't trust it. 20 | */ 21 | 22 | /* 23 | * Delegate to the default trust manager. 24 | */ 25 | public void checkClientTrusted(X509Certificate[] chain, String authType) 26 | throws CertificateException { 27 | 28 | } 29 | 30 | /* 31 | * Delegate to the default trust manager. 32 | */ 33 | public void checkServerTrusted(X509Certificate[] chain, String authType) 34 | throws CertificateException { 35 | 36 | } 37 | 38 | /* 39 | * Merely pass this through. 40 | */ 41 | public X509Certificate[] getAcceptedIssuers() { 42 | return null; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/pers/adlered/picuang/controller/MainController.java: -------------------------------------------------------------------------------- 1 | package pers.adlered.picuang.controller; 2 | 3 | import org.springframework.beans.factory.annotation.Value; 4 | import org.springframework.stereotype.Controller; 5 | import org.springframework.util.ClassUtils; 6 | import org.springframework.web.bind.annotation.*; 7 | import org.springframework.web.servlet.ModelAndView; 8 | import pers.adlered.picuang.prop.Prop; 9 | 10 | import java.io.File; 11 | import java.text.DecimalFormat; 12 | 13 | @Controller 14 | public class MainController { 15 | @Value("${spring.servlet.multipart.max-file-size}") 16 | private String maxFileSize; 17 | 18 | @RequestMapping("/") 19 | @ResponseBody 20 | public ModelAndView index() { 21 | ModelAndView modelAndView = new ModelAndView("index"); 22 | // 图片总数计算 23 | modelAndView.addObject("files", Prop.get("imageUploadedCount")); 24 | // 剩余空间计算 25 | File diskPartition = new File("/"); 26 | String freePartitionSpace = new DecimalFormat("#.00").format(diskPartition.getFreeSpace() / 1073741824); 27 | modelAndView.addObject("free", freePartitionSpace); 28 | // 限制文件大小 29 | modelAndView.addObject("limit", maxFileSize); 30 | // 版本 31 | modelAndView.addObject("version", Prop.getVersion()); 32 | return modelAndView; 33 | } 34 | 35 | @RequestMapping("/history") 36 | @ResponseBody 37 | public ModelAndView history() { 38 | ModelAndView modelAndView = new ModelAndView("history"); 39 | modelAndView.addObject("version", Prop.getVersion()); 40 | return modelAndView; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/pers/adlered/picuang/controller/UploadController.java: -------------------------------------------------------------------------------- 1 | package pers.adlered.picuang.controller; 2 | 3 | import org.springframework.stereotype.Controller; 4 | import org.springframework.web.bind.annotation.PathVariable; 5 | import org.springframework.web.bind.annotation.RequestMapping; 6 | import org.springframework.web.bind.annotation.ResponseBody; 7 | import org.springframework.web.multipart.MultipartFile; 8 | import pers.adlered.picuang.access.HttpOrHttpsAccess; 9 | import pers.adlered.picuang.log.Logger; 10 | import pers.adlered.picuang.prop.Prop; 11 | import pers.adlered.picuang.result.Result; 12 | import pers.adlered.picuang.tool.IPUtil; 13 | import pers.adlered.picuang.tool.ToolBox; 14 | import pers.adlered.simplecurrentlimiter.main.SimpleCurrentLimiter; 15 | 16 | import javax.servlet.http.HttpServletRequest; 17 | import javax.servlet.http.HttpSession; 18 | import java.io.BufferedInputStream; 19 | import java.io.File; 20 | import java.io.FileOutputStream; 21 | import java.io.IOException; 22 | import java.util.regex.Matcher; 23 | import java.util.regex.Pattern; 24 | 25 | @Controller 26 | public class UploadController { 27 | public static SimpleCurrentLimiter uploadLimiter = new SimpleCurrentLimiter(1, 1); 28 | public static SimpleCurrentLimiter cloneLimiter = new SimpleCurrentLimiter(3, 1); 29 | 30 | /** 31 | * 携带管理员密码进行图片上传 32 | * @param file 33 | * @param request 34 | * @param password 35 | * @return 36 | */ 37 | @RequestMapping("/upload/auth") 38 | @ResponseBody 39 | public Result uploadInAuth(@PathVariable MultipartFile file, HttpServletRequest request, HttpSession session, String password) { 40 | String truePassword = Prop.get("password"); 41 | Result result = new Result(); 42 | if (!adminOnly(session, result)) { 43 | return upload(file, request); 44 | } else { 45 | if (truePassword.equals(password)) { 46 | return upload(file, request); 47 | } 48 | } 49 | result.setCode(500); 50 | result.setMsg("Invalid password."); 51 | return result; 52 | } 53 | 54 | @RequestMapping("/upload") 55 | @ResponseBody 56 | public Result upload(@PathVariable MultipartFile file, HttpServletRequest request, HttpSession session) { 57 | Result result = new Result(); 58 | if (adminOnly(session, result)) { 59 | result.setCode(500); 60 | result.setMsg("Admin only upload."); 61 | return result; 62 | } 63 | return upload(file, request); 64 | } 65 | 66 | public Result upload(@PathVariable MultipartFile file, HttpServletRequest request) { 67 | synchronized (this) { 68 | uploadLimiter.setExpireTimeMilli(500); 69 | String addr = IPUtil.getIpAddr(request).replaceAll("\\.", "/").replaceAll(":", "/"); 70 | boolean allowed = uploadLimiter.access(addr); 71 | try { 72 | while (!allowed) { 73 | allowed = uploadLimiter.access(addr); 74 | System.out.print("."); 75 | Thread.sleep(100); 76 | } 77 | } catch (InterruptedException IE) { 78 | } 79 | Result result = new Result(); 80 | //是否上传了文件 81 | if (file.isEmpty()) { 82 | result.setCode(406); 83 | return result; 84 | } 85 | //是否是图片格式 86 | String filename = file.getOriginalFilename(); 87 | String suffixName = ToolBox.getSuffixName(filename); 88 | Logger.log("SuffixName: " + suffixName); 89 | if (ToolBox.isPic(suffixName)) { 90 | String time = ToolBox.getDirByTime(); 91 | File dest = ToolBox.generatePicFile(suffixName, time, addr); 92 | result.setData(filename); 93 | filename = dest.getName(); 94 | Logger.log("Saving into " + dest.getAbsolutePath()); 95 | if (!dest.getParentFile().exists()) { 96 | dest.getParentFile().mkdirs(); 97 | } 98 | try { 99 | file.transferTo(dest); 100 | String url = "/uploadImages/" + addr + "/" + time + filename; 101 | result.setCode(200); 102 | result.setMsg(url); 103 | int count = Integer.parseInt(Prop.get("imageUploadedCount")); 104 | ++count; 105 | Prop.set("imageUploadedCount", String.valueOf(count)); 106 | return result; 107 | } catch (IOException e) { 108 | e.printStackTrace(); 109 | } 110 | } else { 111 | result.setCode(500); 112 | result.setMsg("不是jpg/jpeg/png/svg/gif图片!"); 113 | return result; 114 | } 115 | result.setCode(500); 116 | result.setMsg("未知错误。"); 117 | return result; 118 | } 119 | } 120 | 121 | @RequestMapping("/clone") 122 | @ResponseBody 123 | public Result clone(String url, HttpServletRequest request, HttpSession session) { 124 | synchronized (this) { 125 | String addr = IPUtil.getIpAddr(request).replaceAll("\\.", "/").replaceAll(":", "/"); 126 | boolean allowed = cloneLimiter.access(addr); 127 | try { 128 | while (!allowed) { 129 | allowed = cloneLimiter.access(addr); 130 | System.out.print("."); 131 | Thread.sleep(100); 132 | } 133 | } catch (InterruptedException IE) { 134 | } 135 | Result result = new Result(); 136 | if (adminOnly(session, result)) { 137 | return result; 138 | } 139 | try { 140 | String suffixName = ToolBox.getSuffixName(url); 141 | Logger.log("SuffixName: " + suffixName); 142 | String time = ToolBox.getDirByTime(); 143 | File dest; 144 | if (ToolBox.isPic(suffixName)) { 145 | dest = ToolBox.generatePicFile(suffixName, time, addr); 146 | } else { 147 | dest = ToolBox.generatePicFile(".png", time, addr); 148 | } 149 | Logger.log("Saving into " + dest.getAbsolutePath()); 150 | if (!dest.getParentFile().exists()) { 151 | dest.getParentFile().mkdirs(); 152 | } 153 | FileOutputStream fileOutputStream = new FileOutputStream(dest); 154 | BufferedInputStream bufferedInputStream = HttpOrHttpsAccess.post(url, 155 | "", 156 | null); 157 | byte[] bytes = new byte[1024]; 158 | int len; 159 | while ((len = bufferedInputStream.read(bytes)) != -1) { 160 | fileOutputStream.write(bytes, 0, len); 161 | } 162 | fileOutputStream.flush(); 163 | fileOutputStream.close(); 164 | bufferedInputStream.close(); 165 | Pattern p = Pattern.compile("(?<=http://|\\.)[^.]*?\\.(com|cn|net|org|biz|info|cc|tv)", Pattern.CASE_INSENSITIVE); 166 | Matcher matcher = p.matcher(url); 167 | matcher.find(); 168 | result.setData("From " + matcher.group()); 169 | result.setCode(200); 170 | result.setMsg("/uploadImages/" + addr + "/" + time + dest.getName()); 171 | int count = Integer.parseInt(Prop.get("imageUploadedCount")); 172 | ++count; 173 | Prop.set("imageUploadedCount", String.valueOf(count)); 174 | return result; 175 | } catch (Exception e) { 176 | result.setCode(500); 177 | result.setMsg(e.getClass().toGenericString()); 178 | return result; 179 | } 180 | } 181 | } 182 | 183 | private boolean adminOnly(HttpSession session, Result result) { 184 | if (Prop.get("adminOnly").equals("on")) { 185 | Logger.log("AdminOnly mode is on! Checking user's permission..."); 186 | if (!logged(session)) { 187 | Logger.log("User not logged! Uploading terminated."); 188 | result.setCode(401); 189 | result.setMsg("管理员禁止了普通用户上传文件!"); 190 | return true; 191 | } 192 | Logger.log("Admin is uploading..."); 193 | } 194 | return false; 195 | } 196 | 197 | /** 198 | * 检查管理员是否已登录 199 | * 200 | * @param session 201 | * @return 202 | */ 203 | private boolean logged(HttpSession session) { 204 | boolean logged = false; 205 | try { 206 | logged = Boolean.parseBoolean(session.getAttribute("admin").toString()); 207 | } catch (NullPointerException NPE) { 208 | } 209 | return logged; 210 | } 211 | } 212 | -------------------------------------------------------------------------------- /src/main/java/pers/adlered/picuang/controller/admin/Admin.java: -------------------------------------------------------------------------------- 1 | package pers.adlered.picuang.controller.admin; 2 | 3 | import org.springframework.stereotype.Controller; 4 | import org.springframework.util.ClassUtils; 5 | import org.springframework.web.bind.annotation.RequestMapping; 6 | import org.springframework.web.bind.annotation.ResponseBody; 7 | import org.springframework.web.servlet.ModelAndView; 8 | import pers.adlered.picuang.prop.Prop; 9 | import pers.adlered.picuang.tool.ToolBox; 10 | 11 | /** 12 | *管理
14 | * 15 | * @author : https://github.com/AdlerED 16 | * @date : 2019-11-07 17:05 17 | **/ 18 | 19 | @Controller 20 | public class Admin { 21 | @RequestMapping("/admin") 22 | @ResponseBody 23 | public ModelAndView admin() { 24 | ModelAndView modelAndView = new ModelAndView("admin"); 25 | modelAndView.addObject("appConfLocation", ClassUtils.getDefaultClassLoader().getResource("").getPath() + "application.properties"); 26 | modelAndView.addObject("version", Prop.getVersion()); 27 | modelAndView.addObject("checkVersion", checkVersion()); 28 | return modelAndView; 29 | } 30 | 31 | private String checkVersion() { 32 | String neededVersion = Prop.getVersion(); 33 | String realVersion = Prop.get("version"); 34 | if (neededVersion.equals(realVersion)) { 35 | return ""; 36 | } else { 37 | return "管理员API
16 | * 17 | * @author : https://github.com/AdlerED 18 | * @date : 2019-11-07 17:16 19 | **/ 20 | @Controller 21 | public class AdminAction { 22 | /** 23 | * 检测管理员密码是否已经设定 24 | * @return 25 | * 500:管理员密码未设定,需要在Data中指定的文件设置密码 26 | * 200:管理员密码已设定,可以登录 27 | */ 28 | @RequestMapping("/api/admin/init") 29 | @ResponseBody 30 | public Result init() { 31 | Result result = new Result(); 32 | try { 33 | if (Prop.get("password").isEmpty()) { 34 | result.setCode(500); 35 | result.setData(ToolBox.getINIDir()); 36 | } else { 37 | result.setCode(200); 38 | } 39 | } catch (NullPointerException NPE) { 40 | result.setCode(500); 41 | result.setData(ToolBox.getINIDir()); 42 | } 43 | return result; 44 | } 45 | 46 | /** 47 | * 检查管理员是否已登录 48 | * @param session 49 | * @return 50 | */ 51 | @RequestMapping("/api/admin/check") 52 | @ResponseBody 53 | public Result check(HttpSession session) { 54 | Result result = new Result(); 55 | boolean logged = false; 56 | try { 57 | String admin = session.getAttribute("admin").toString(); 58 | logged = Boolean.parseBoolean(admin); 59 | } catch (NullPointerException NPE) { 60 | } 61 | if (logged) { 62 | result.setCode(200); 63 | } else { 64 | result.setCode(500); 65 | } 66 | return result; 67 | } 68 | 69 | /** 70 | * 管理员登录 71 | * 管理员登录为了安全考虑,必须使用POST方法传输 72 | * @param session 73 | * @param password 74 | * @return 75 | * 500:登录失败 76 | * 200:登录成功 77 | */ 78 | @RequestMapping(value = "/api/admin/login", method = RequestMethod.POST) 79 | @ResponseBody 80 | public Result login(HttpSession session, String password) { 81 | Result result = new Result(); 82 | if (password.isEmpty()) { 83 | result.setCode(500); 84 | return result; 85 | } 86 | String truePassword = Prop.get("password"); 87 | if (truePassword.equals(password)) { 88 | session.setAttribute("admin", "true"); 89 | result.setCode(200); 90 | } else { 91 | result.setCode(500); 92 | } 93 | return result; 94 | } 95 | 96 | /** 97 | * 管理员注销 98 | * @param session 99 | * @return 100 | * 200:注销成功 101 | */ 102 | @RequestMapping("/api/admin/logout") 103 | @ResponseBody 104 | public Result logout(HttpSession session) { 105 | session.invalidate(); 106 | Result result = new Result(); 107 | result.setCode(200); 108 | return result; 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/main/java/pers/adlered/picuang/controller/admin/api/ConfigAction.java: -------------------------------------------------------------------------------- 1 | package pers.adlered.picuang.controller.admin.api; 2 | 3 | import org.springframework.stereotype.Controller; 4 | import org.springframework.web.bind.annotation.PathVariable; 5 | import org.springframework.web.bind.annotation.RequestMapping; 6 | import org.springframework.web.bind.annotation.ResponseBody; 7 | import org.springframework.web.multipart.MultipartFile; 8 | import pers.adlered.picuang.log.Logger; 9 | import pers.adlered.picuang.prop.Prop; 10 | import pers.adlered.picuang.result.Result; 11 | import pers.adlered.picuang.tool.FileUtil; 12 | import pers.adlered.picuang.tool.ToolBox; 13 | 14 | import javax.servlet.http.HttpServletResponse; 15 | import javax.servlet.http.HttpSession; 16 | import java.io.File; 17 | 18 | /** 19 | *配置API
21 | * 22 | * @author : https://github.com/AdlerED 23 | * @date : 2019-11-07 17:31 24 | **/ 25 | @Controller 26 | public class ConfigAction { 27 | /** 28 | * 检查管理员是否已登录 29 | * 30 | * @param session 31 | * @return 32 | */ 33 | public boolean logged(HttpSession session) { 34 | boolean logged = false; 35 | try { 36 | logged = Boolean.parseBoolean(session.getAttribute("admin").toString()); 37 | } catch (NullPointerException NPE) { 38 | } 39 | return logged; 40 | } 41 | 42 | @RequestMapping("/api/admin/getConf") 43 | @ResponseBody 44 | public String getConf(HttpSession session, String conf) { 45 | if (logged(session) || conf.equals("adminOnly")) { 46 | String result = Prop.get(conf); 47 | if (result != null) { 48 | return result; 49 | } else { 50 | return "找不到配置!如果你更新过Picuang,请备份并删除当前的config.ini文件(位于 " + ToolBox.getINIDir() + " ),然后重启服务端或点击\"生成新配置文件\"按钮,使Picuang重新生成新的配置文件。"; 51 | } 52 | } else { 53 | return "Permission denied"; 54 | } 55 | } 56 | 57 | @RequestMapping("/api/admin/setConf") 58 | @ResponseBody 59 | public Result setConf(HttpSession session, String conf, String value) { 60 | Result result = new Result(); 61 | if (logged(session)) { 62 | Prop.set(conf, value); 63 | result.setCode(200); 64 | } else { 65 | result.setCode(500); 66 | } 67 | return result; 68 | } 69 | 70 | @RequestMapping("/api/admin/export") 71 | @ResponseBody 72 | public void exportConfig(HttpServletResponse response, HttpSession session) { 73 | if (logged(session)) { 74 | String fileName = "config.ini"; 75 | FileUtil.downloadFile(response, fileName); 76 | } 77 | } 78 | 79 | @RequestMapping("/api/admin/import") 80 | @ResponseBody 81 | public Result importConfig(@PathVariable MultipartFile file, HttpSession session) { 82 | Result result = new Result(); 83 | try { 84 | String filename = file.getOriginalFilename(); 85 | // 如果已登录 && 文件不为空 && 是ini文件 86 | if (logged(session) && (!file.isEmpty()) && filename.matches(".*(\\.ini)$")) { 87 | File config = new File("config.ini"); 88 | config.renameTo(new File("config.ini.backup")); 89 | File newConfig = new File(config.getAbsolutePath()); 90 | file.transferTo(newConfig); 91 | Logger.log(newConfig.getPath()); 92 | Prop.reload(); 93 | result.setCode(200); 94 | } else { 95 | result.setCode(500); 96 | } 97 | } catch (Exception e) { 98 | e.printStackTrace(); 99 | result.setCode(500); 100 | } 101 | return result; 102 | } 103 | 104 | /** 105 | * 重载功能 106 | * 不验证管理员是否已经登录,因为需要重载初始化后的密码 107 | * 108 | * @return 109 | */ 110 | @RequestMapping("/api/admin/reload") 111 | @ResponseBody 112 | public Result reloadConfig() { 113 | Result result = new Result(); 114 | Prop.reload(); 115 | result.setCode(200); 116 | return result; 117 | } 118 | 119 | @RequestMapping("/api/admin/renew") 120 | @ResponseBody 121 | public Result renewConfig(HttpSession session) { 122 | Result result = new Result(); 123 | if (logged(session)) { 124 | Prop.renew(); 125 | result.setCode(200); 126 | } else { 127 | result.setCode(500); 128 | } 129 | return result; 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /src/main/java/pers/adlered/picuang/controller/api/History.java: -------------------------------------------------------------------------------- 1 | package pers.adlered.picuang.controller.api; 2 | 3 | import org.springframework.stereotype.Controller; 4 | import org.springframework.web.bind.annotation.RequestMapping; 5 | import org.springframework.web.bind.annotation.ResponseBody; 6 | import pers.adlered.picuang.controller.api.bean.PicProp; 7 | import pers.adlered.picuang.log.Logger; 8 | import pers.adlered.picuang.tool.IPUtil; 9 | import pers.adlered.picuang.tool.ToolBox; 10 | 11 | import javax.servlet.http.HttpServletRequest; 12 | import java.io.File; 13 | import java.util.ArrayList; 14 | import java.util.Collections; 15 | import java.util.List; 16 | 17 | /** 18 | *查看历史记录API
20 | * 21 | * @author : https://github.com/AdlerED 22 | * @date : 2019-11-06 16:24 23 | **/ 24 | @Controller 25 | public class History { 26 | @RequestMapping("/api/list") 27 | @ResponseBody 28 | public ListList Bean
6 | * 7 | * @author : https://github.com/AdlerED 8 | * @date : 2019-11-06 16:54 9 | **/ 10 | public class PicProp { 11 | private String time; 12 | private String filename; 13 | private String path; 14 | private String ip; 15 | 16 | public String getTime() { 17 | return time; 18 | } 19 | 20 | public void setTime(String time) { 21 | this.time = time; 22 | } 23 | 24 | public String getFilename() { 25 | return filename; 26 | } 27 | 28 | public void setFilename(String filename) { 29 | this.filename = filename; 30 | } 31 | 32 | public String getPath() { 33 | return path; 34 | } 35 | 36 | public void setPath(String path) { 37 | this.path = path; 38 | } 39 | 40 | public String getIp() { 41 | return ip; 42 | } 43 | 44 | public void setIp(String ip) { 45 | this.ip = ip; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/pers/adlered/picuang/log/Logger.java: -------------------------------------------------------------------------------- 1 | package pers.adlered.picuang.log; 2 | 3 | /** 4 | *日志打印
6 | * 7 | * @author : https://github.com/AdlerED 8 | * @date : 2019-11-08 14:07 9 | **/ 10 | public class Logger { 11 | public static void log(String log) { 12 | System.out.println("[Picuang] " + log); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/pers/adlered/picuang/prop/Prop.java: -------------------------------------------------------------------------------- 1 | package pers.adlered.picuang.prop; 2 | 3 | import org.springframework.stereotype.Component; 4 | import pers.adlered.picuang.controller.UploadController; 5 | import pers.adlered.picuang.log.Logger; 6 | import pers.adlered.simplecurrentlimiter.main.SimpleCurrentLimiter; 7 | 8 | import java.io.*; 9 | import java.util.Properties; 10 | import java.util.Set; 11 | 12 | /** 13 | *自动配置文件
15 | * 16 | * @author : https://github.com/AdlerED 17 | * @date : 2019-11-06 21:29 18 | **/ 19 | @Component 20 | public class Prop { 21 | // 版本号 22 | private static final String version = "V2.4"; 23 | 24 | private static Properties properties = new Properties(); 25 | 26 | static { 27 | put(); 28 | Logger.log("Properties loaded."); 29 | reload(); 30 | } 31 | 32 | public static void del() { 33 | File file = new File("config.ini"); 34 | file.delete(); 35 | } 36 | 37 | public static void put() { 38 | try { 39 | properties.load(new BufferedInputStream(new FileInputStream("config.ini"))); 40 | } catch (FileNotFoundException e) { 41 | Logger.log("Generating new profile..."); 42 | properties.put("imageUploadedCount", "0"); 43 | properties.put("version", version); 44 | properties.put("password", ""); 45 | properties.put("adminOnly", "off"); 46 | properties.put("uploadLimit", "1:1"); 47 | properties.put("cloneLimit", "3:1"); 48 | try { 49 | properties.store(new BufferedOutputStream(new FileOutputStream("config.ini")), "Save Configs File."); 50 | } catch (FileNotFoundException FNFE) { 51 | FNFE.printStackTrace(); 52 | } catch (IOException IOE) { 53 | IOE.printStackTrace(); 54 | } 55 | } catch (IOException IOE) { 56 | IOE.printStackTrace(); 57 | } 58 | } 59 | 60 | public static String get(String key) { 61 | return properties.getProperty(key); 62 | } 63 | 64 | public static void set(String key, String value) { 65 | try { 66 | properties.setProperty(key, value); 67 | Logger.log("[Prop] Set key '" + key + "' to value '" + value + "'"); 68 | PrintWriter printWriter = new PrintWriter(new FileWriter("config.ini"), true); 69 | Set set = properties.keySet(); 70 | for (Object object : set) { 71 | String k = (String) object; 72 | String v = properties.getProperty(k); 73 | printWriter.println(k + "=" + v); 74 | } 75 | } catch (Exception e) { 76 | e.printStackTrace(); 77 | } 78 | if (!key.equals("imageUploadedCount")) { 79 | reload(); 80 | } 81 | } 82 | 83 | public static String getVersion() { 84 | return version; 85 | } 86 | 87 | public static void reload() { 88 | Logger.log("Reloading profile..."); 89 | // Reload properties 90 | try { 91 | properties.load(new BufferedInputStream(new FileInputStream("config.ini"))); 92 | } catch (Exception e) {} 93 | // Upload limit 94 | try { 95 | String uploadLimitMaster = get("uploadLimit"); 96 | if (uploadLimitMaster.contains(":")) { 97 | int uploadLimitTime = Integer.parseInt(uploadLimitMaster.split(":")[0]); 98 | int uploadLimitTimes = Integer.parseInt(uploadLimitMaster.split(":")[1]); 99 | UploadController.uploadLimiter = new SimpleCurrentLimiter(uploadLimitTime, uploadLimitTimes); 100 | Logger.log("Upload limit custom setting loaded (" + uploadLimitTimes + " times in " + uploadLimitTime + " second) ."); 101 | } 102 | } catch (Exception e) {} 103 | // Clone limit 104 | try { 105 | String cloneLimitMaster = get("cloneLimit"); 106 | if (cloneLimitMaster.contains(":")) { 107 | int cloneLimitTime = Integer.parseInt(cloneLimitMaster.split(":")[0]); 108 | int cloneLimitTimes = Integer.parseInt(cloneLimitMaster.split(":")[1]); 109 | UploadController.cloneLimiter = new SimpleCurrentLimiter(cloneLimitTime, cloneLimitTimes); 110 | Logger.log("Clone limit custom setting loaded (" + cloneLimitTimes + " times in " + cloneLimitTime + " second) ."); 111 | } 112 | } catch (Exception e) {} 113 | } 114 | 115 | public static void renew() { 116 | del(); 117 | put(); 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /src/main/java/pers/adlered/picuang/result/Result.java: -------------------------------------------------------------------------------- 1 | package pers.adlered.picuang.result; 2 | 3 | public class Result下载文件类
11 | * 12 | * @author : https://github.com/AdlerED 13 | * @date : 2019-11-07 23:24 14 | **/ 15 | public class FileUtil { 16 | /** 17 | * 下载项目根目录下doc下的文件 18 | * 19 | * @param response response 20 | * @param fileName 文件名 21 | * @return 返回结果 成功或者文件不存在 22 | */ 23 | public static void downloadFile(HttpServletResponse response, String fileName) { 24 | response.setHeader("content-type", "application/octet-stream"); 25 | response.setContentType("application/octet-stream"); 26 | try { 27 | response.setHeader("Content-Disposition", "attachment;filename=" + java.net.URLEncoder.encode(fileName, "UTF-8")); 28 | } catch (UnsupportedEncodingException e2) { 29 | e2.printStackTrace(); 30 | } 31 | byte[] buff = new byte[1024]; 32 | BufferedInputStream bis = null; 33 | OutputStream os = null; 34 | try { 35 | os = response.getOutputStream(); 36 | bis = new BufferedInputStream(new FileInputStream(new File(fileName))); 37 | int i; 38 | while ((i = bis.read(buff)) != -1) { 39 | os.write(buff, 0, i); 40 | os.flush(); 41 | } 42 | } catch (FileNotFoundException FNFE) { 43 | FNFE.printStackTrace(); 44 | } catch (IOException IOE) { 45 | IOE.printStackTrace(); 46 | } finally { 47 | if (bis != null) { 48 | try { 49 | bis.close(); 50 | } catch (IOException IOE) { 51 | IOE.printStackTrace(); 52 | } 53 | } 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/main/java/pers/adlered/picuang/tool/IPUtil.java: -------------------------------------------------------------------------------- 1 | package pers.adlered.picuang.tool; 2 | 3 | import javax.servlet.http.HttpServletRequest; 4 | import java.net.InetAddress; 5 | import java.net.UnknownHostException; 6 | 7 | /** 8 | *获取IP地址
10 | * 11 | * @author : https://github.com/AdlerED 12 | * @date : 2019-11-06 21:17 13 | **/ 14 | public class IPUtil { 15 | public static String getIpAddr(HttpServletRequest request) { 16 | String ipAddress = null; 17 | try { 18 | ipAddress = request.getHeader("x-forwarded-for"); 19 | if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) { 20 | ipAddress = request.getHeader("Proxy-Client-IP"); 21 | } 22 | if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) { 23 | ipAddress = request.getHeader("WL-Proxy-Client-IP"); 24 | } 25 | if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) { 26 | ipAddress = request.getRemoteAddr(); 27 | if (ipAddress.equals("127.0.0.1")) { 28 | InetAddress inet = null; 29 | try { 30 | inet = InetAddress.getLocalHost(); 31 | } catch (UnknownHostException e) { 32 | e.printStackTrace(); 33 | } 34 | ipAddress = inet.getHostAddress(); 35 | } 36 | } 37 | if (ipAddress != null && ipAddress.length() > 15) { 38 | if (ipAddress.indexOf(",") > 0) { 39 | ipAddress = ipAddress.substring(0, ipAddress.indexOf(",")); 40 | } 41 | } 42 | } catch (Exception e) { 43 | ipAddress = ""; 44 | } 45 | return ipAddress; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/pers/adlered/picuang/tool/ToolBox.java: -------------------------------------------------------------------------------- 1 | package pers.adlered.picuang.tool; 2 | 3 | import org.springframework.util.ClassUtils; 4 | 5 | import java.io.File; 6 | import java.text.SimpleDateFormat; 7 | import java.util.Date; 8 | import java.util.HashSet; 9 | import java.util.Set; 10 | import java.util.UUID; 11 | 12 | /** 13 | *工具箱
15 | * 16 | * @author : https://github.com/AdlerED 17 | * @date : 2019-11-06 11:09 18 | **/ 19 | public class ToolBox { 20 | private static final Set验证Key1中的key2在key1中是否已经存在,存在返回false
11 | *用于检测某个用户名是否已经发过相同的一句话
12 | * 13 | * @author : https://github.com/AdlerED 14 | * @date : 2019-11-28 11:06 15 | **/ 16 | public class DoubleKeys { 17 | public static void main(String[] args) { 18 | c("adler", "1"); 19 | c("adler", "1"); 20 | c("hello", "1"); 21 | c("adler", "2"); 22 | c("hello", "1"); 23 | c("adler", "2"); 24 | } 25 | 26 | public static void c(String key1, String key2) { 27 | System.out.println(check(key1, key2)); 28 | } 29 | 30 | public static boolean check(String key1, String key2) { 31 | List存储字符串、次数信息
11 | * 12 | * @author : https://github.com/AdlerED 13 | * @date : 2019-10-10 22:49 14 | **/ 15 | public class MainCache { 16 | // 缓存 存储CachePair 17 | public Map字符串和次数 Bean
6 | * 7 | * @author : https://github.com/AdlerED 8 | * @date : 2019-10-10 22:52 9 | **/ 10 | public class CachePair { 11 | private long frequency; 12 | private long timeStamp; 13 | 14 | public long getFrequency() { 15 | return frequency; 16 | } 17 | 18 | public void setFrequency(long frequency) { 19 | this.frequency = frequency; 20 | } 21 | 22 | public long getTimeStamp() { 23 | return timeStamp; 24 | } 25 | 26 | public void setTimeStamp(long timeStamp) { 27 | this.timeStamp = timeStamp; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/pers/adlered/simplecurrentlimiter/control/MainControl.java: -------------------------------------------------------------------------------- 1 | package pers.adlered.simplecurrentlimiter.control; 2 | 3 | import pers.adlered.simplecurrentlimiter.cache.MainCache; 4 | import pers.adlered.simplecurrentlimiter.cache.pair.CachePair; 5 | 6 | /** 7 | *底层数据控制
9 | * 10 | * @author : https://github.com/AdlerED 11 | * @date : 2019-10-10 22:55 12 | **/ 13 | public class MainControl extends MainCache { 14 | public boolean write(String str) { 15 | boolean isOK = true; 16 | // 先读取查看是否已经存在 17 | long frequency; 18 | long timeStamp; 19 | long currentTimeStamp = System.currentTimeMillis(); 20 | CachePair verifyPair = this.cachePairMap.get(str); 21 | if (verifyPair == null) { 22 | frequency = 1L; 23 | timeStamp = currentTimeStamp; 24 | } else { 25 | frequency = verifyPair.getFrequency() + 1L; 26 | timeStamp = verifyPair.getTimeStamp(); 27 | // 超时刷新 28 | if (this.expireTime != -1) { 29 | if ((currentTimeStamp - timeStamp) > this.expireTime) { 30 | frequency = 1L; 31 | timeStamp = currentTimeStamp; 32 | } 33 | } 34 | } 35 | if (frequency > this.frequencyTime) { 36 | isOK = false; 37 | } 38 | CachePair newPair = new CachePair(); 39 | newPair.setFrequency(frequency); 40 | newPair.setTimeStamp(timeStamp); 41 | this.cachePairMap.put(str, newPair); 42 | return isOK; 43 | } 44 | 45 | public void setFrequencyTime(long frequencyTime) { 46 | this.frequencyTime = frequencyTime; 47 | } 48 | 49 | public void setExpireTimeMilli(long timeMilli) { 50 | this.expireTime = timeMilli; 51 | } 52 | 53 | public void setExpireTimeSecond(long timeSecond) { 54 | this.expireTime = timeSecond * 1000; 55 | } 56 | 57 | public void setExpireTimeMin(long timeMin) { 58 | this.expireTime = timeMin * 60000; 59 | } 60 | 61 | public void setExpireTimeHour(long timeHour) { 62 | this.expireTime = timeHour * 3600000; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/main/java/pers/adlered/simplecurrentlimiter/main/SimpleCurrentLimiter.java: -------------------------------------------------------------------------------- 1 | package pers.adlered.simplecurrentlimiter.main; 2 | 3 | import pers.adlered.simplecurrentlimiter.control.MainControl; 4 | 5 | /** 6 | *简单限流器,用于IP地址访问次数验证,其它字符串访问次数验证
8 | * 9 | * @author : https://github.com/AdlerED 10 | * @date : 2019-10-10 22:45 11 | **/ 12 | public class SimpleCurrentLimiter { 13 | MainControl mainControl = null; 14 | 15 | private MainControl getMainControl() { 16 | if (mainControl == null) { 17 | mainControl = new MainControl(); 18 | } 19 | return mainControl; 20 | } 21 | 22 | public SimpleCurrentLimiter(long expireTimeSecond, long frequencyTime) { 23 | MainControl mainControl = getMainControl(); 24 | mainControl.setExpireTimeSecond(expireTimeSecond); 25 | mainControl.setFrequencyTime(frequencyTime); 26 | } 27 | 28 | /** 29 | * 当用户访问时,特征将传入此方法 30 | * @return 用户令牌 31 | */ 32 | public boolean access(String str) { 33 | MainControl mainControl = getMainControl(); 34 | return mainControl.write(str); 35 | } 36 | 37 | public void setExpireTimeMilli(long timeMilli) { 38 | MainControl mainControl = getMainControl(); 39 | mainControl.setExpireTimeMilli(timeMilli); 40 | } 41 | 42 | public void setExpireTimeSecond(long timeSecond) { 43 | MainControl mainControl = getMainControl(); 44 | mainControl.setExpireTimeSecond(timeSecond); 45 | } 46 | 47 | public void setExpireTimeMin(long timeMin) { 48 | MainControl mainControl = getMainControl(); 49 | mainControl.setExpireTimeMin(timeMin); 50 | } 51 | 52 | public void setExpireTimeHour(long timeHour) { 53 | MainControl mainControl = getMainControl(); 54 | mainControl.setExpireTimeHour(timeHour); 55 | } 56 | 57 | public void setFrequencyTime(long frequencyTime) { 58 | MainControl mainControl = getMainControl(); 59 | mainControl.setFrequencyTime(frequencyTime); 60 | } 61 | } -------------------------------------------------------------------------------- /src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | #重要!Picuang图床设置 2 | //单个文件传输文件大小限制 3 | spring.servlet.multipart.max-file-size=5MB 4 | //单次传输文件大小限制 5 | spring.servlet.multipart.max-request-size=5MB 6 | #Picuang图床设置结束 7 | 8 | #SpringBoot配置文件 9 | //让SpringBoot支持热部署 10 | spring.devtools.restart.enabled=true 11 | #SpringBoot配置文件结束 12 | 13 | #Thymeleaf配置文件 14 | //关闭Thymeleaf的缓存 15 | spring.thymeleaf.cache=false 16 | //设置Thymeleaf的类型 17 | spring.thymeleaf.servlet.content-type=text/html 18 | //启用Thymeleaf 19 | spring.thymeleaf.enabled=true 20 | //设置Thymeleaf的编码 21 | spring.thymeleaf.encoding=UTF-8 22 | //设置Thymeleaf的模式 23 | spring.thymeleaf.mode=HTML5 24 | //设置Thymeleaf的目录前缀 25 | spring.thymeleaf.prefix=classpath:/templates/ 26 | //设置Thymeleaf的目录后缀 27 | spring.thymeleaf.suffix=.html 28 | #Thymeleaf配置文件结束 -------------------------------------------------------------------------------- /src/main/resources/static/background/load.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adlered/Picuang/8c361437de5fe56f45ff6cf6785a033a8e267d2a/src/main/resources/static/background/load.gif -------------------------------------------------------------------------------- /src/main/resources/static/bootstrap/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adlered/Picuang/8c361437de5fe56f45ff6cf6785a033a8e267d2a/src/main/resources/static/bootstrap/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /src/main/resources/static/bootstrap/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adlered/Picuang/8c361437de5fe56f45ff6cf6785a033a8e267d2a/src/main/resources/static/bootstrap/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /src/main/resources/static/bootstrap/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adlered/Picuang/8c361437de5fe56f45ff6cf6785a033a8e267d2a/src/main/resources/static/bootstrap/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /src/main/resources/static/bootstrap/fonts/glyphicons-halflings-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adlered/Picuang/8c361437de5fe56f45ff6cf6785a033a8e267d2a/src/main/resources/static/bootstrap/fonts/glyphicons-halflings-regular.woff2 -------------------------------------------------------------------------------- /src/main/resources/static/bootstrap/js/npm.js: -------------------------------------------------------------------------------- 1 | // This file is autogenerated via the `commonjs` Grunt task. You can require() this file in a CommonJS environment. 2 | require('../../js/transition.js') 3 | require('../../js/alert.js') 4 | require('../../js/button.js') 5 | require('../../js/carousel.js') 6 | require('../../js/collapse.js') 7 | require('../../js/dropdown.js') 8 | require('../../js/modal.js') 9 | require('../../js/tooltip.js') 10 | require('../../js/popover.js') 11 | require('../../js/scrollspy.js') 12 | require('../../js/tab.js') 13 | require('../../js/affix.js') -------------------------------------------------------------------------------- /src/main/resources/static/css/bootstrap/cover.css: -------------------------------------------------------------------------------- 1 | /* 2 | * Globals 3 | */ 4 | 5 | /* Links */ 6 | a, 7 | a:focus, 8 | a:hover { 9 | color: #fff; 10 | } 11 | 12 | /* Custom default button */ 13 | .btn-default, 14 | .btn-default:hover, 15 | .btn-default:focus { 16 | color: #333; 17 | text-shadow: none; /* Prevent inheritance from `body` */ 18 | background-color: #fff; 19 | border: 1px solid #fff; 20 | } 21 | 22 | 23 | /* 24 | * Base structure 25 | */ 26 | 27 | html, 28 | body { 29 | height: 100%; 30 | background-color: #333; 31 | } 32 | body { 33 | color: #fff; 34 | text-align: center; 35 | text-shadow: 0 1px 3px rgba(0,0,0,.5); 36 | } 37 | 38 | /* Extra markup and styles for table-esque vertical and horizontal centering */ 39 | .site-wrapper { 40 | display: table; 41 | width: 100%; 42 | height: 100%; /* For at least Firefox */ 43 | min-height: 100%; 44 | -webkit-box-shadow: inset 0 0 100px rgba(0,0,0,.5); 45 | box-shadow: inset 0 0 100px rgba(0,0,0,.5); 46 | } 47 | .site-wrapper-inner { 48 | display: table-cell; 49 | vertical-align: top; 50 | } 51 | .cover-container { 52 | margin-right: auto; 53 | margin-left: auto; 54 | } 55 | 56 | /* Padding for spacing */ 57 | .inner { 58 | padding: 5px; 59 | } 60 | 61 | /* 62 | * Header 63 | */ 64 | .masthead-brand { 65 | margin-top: 10px; 66 | margin-bottom: 10px; 67 | } 68 | 69 | .masthead-nav > li { 70 | display: inline-block; 71 | } 72 | .masthead-nav > li + li { 73 | margin-left: 20px; 74 | } 75 | .masthead-nav > li > a { 76 | padding-right: 0; 77 | padding-left: 0; 78 | font-size: 16px; 79 | font-weight: bold; 80 | color: #fff; /* IE8 proofing */ 81 | color: rgba(255,255,255,.75); 82 | border-bottom: 2px solid transparent; 83 | } 84 | .masthead-nav > li > a:hover, 85 | .masthead-nav > li > a:focus { 86 | background-color: transparent; 87 | border-bottom-color: #a9a9a9; 88 | border-bottom-color: rgba(255,255,255,.25); 89 | } 90 | .masthead-nav > .active > a, 91 | .masthead-nav > .active > a:hover, 92 | .masthead-nav > .active > a:focus { 93 | color: #fff; 94 | border-bottom-color: #fff; 95 | } 96 | 97 | @media (min-width: 768px) { 98 | .masthead-brand { 99 | float: left; 100 | } 101 | .masthead-nav { 102 | float: right; 103 | } 104 | } 105 | 106 | 107 | /* 108 | * Cover 109 | */ 110 | 111 | .cover { 112 | padding: 0 20px; 113 | } 114 | .cover .btn-lg { 115 | padding: 10px 20px; 116 | font-weight: bold; 117 | } 118 | 119 | 120 | /* 121 | * Footer 122 | */ 123 | 124 | .mastfoot { 125 | color: #999; /* IE8 proofing */ 126 | color: rgba(255,255,255,.5); 127 | } 128 | 129 | 130 | /* 131 | * Affix and center 132 | */ 133 | 134 | @media (min-width: 768px) { 135 | /* Pull out the header and footer */ 136 | .masthead { 137 | /*position: fixed;*/ 138 | top: 0; 139 | } 140 | .mastfoot { 141 | /*position: fixed;*/ 142 | bottom: 0; 143 | } 144 | /* Start the vertical centering */ 145 | .site-wrapper-inner { 146 | vertical-align: middle; 147 | } 148 | /* Handle the widths */ 149 | .masthead, 150 | .mastfoot, 151 | .cover-container { 152 | width: 100%; /* Must be percentage or pixels for horizontal alignment */ 153 | } 154 | } 155 | 156 | @media (min-width: 992px) { 157 | .masthead, 158 | .mastfoot, 159 | .cover-container { 160 | width: 700px; 161 | } 162 | } -------------------------------------------------------------------------------- /src/main/resources/static/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adlered/Picuang/8c361437de5fe56f45ff6cf6785a033a8e267d2a/src/main/resources/static/favicon.png -------------------------------------------------------------------------------- /src/main/resources/static/js/admin/config.js: -------------------------------------------------------------------------------- 1 | var adminOnly; 2 | 3 | $(function () { 4 | $("[data-toggle='popover']").popover(); 5 | }); 6 | 7 | function showConfig() { 8 | setTimeout(function () { 9 | // 一列 10 | axios.get('/api/admin/getConf?conf=imageUploadedCount') 11 | .then(function (response) { 12 | $("#config-row").prepend("129 | Picuang 131 |
132 |45 | Picuang 47 |
48 |
40 | 请将图片拖拽、粘贴或点击按钮上传
41 | 支持同时多个上传 | 支持JPG / PNG / SVG / GIF / BMP / ICO / TIFF
42 | 图片不会被压缩,不限制外链,永久保存
43 |
45 | 图片大小限制:
46 |
47 | 为用户累计永久存储图片:张
48 |
49 | 服务器剩余空间:GB
50 |
110 | Picuang 112 |
113 |