├── .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 | 2 | 3 | $PROJECT_DIR$/target 4 | 5 | 6 | false 7 | picuang 8 | war 9 | false 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /.idea/artifacts/picuang_war_exploded.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | $PROJECT_DIR$/target/picuang-0.0.1-SNAPSHOT 4 | 5 | 6 | true 7 | picuang 8 | war 9 | false 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 20 | 21 | -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /.idea/jarRepositories.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 10 | 14 | 15 | 19 | 20 | 24 | 25 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__ch_qos_logback_logback_classic_1_2_3.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__ch_qos_logback_logback_core_1_2_3.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__com_fasterxml_classmate_1_4_0.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__com_fasterxml_jackson_core_jackson_annotations_2_9_0.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__com_fasterxml_jackson_core_jackson_core_2_9_9.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__com_fasterxml_jackson_core_jackson_databind_2_9_9.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__com_fasterxml_jackson_datatype_jackson_datatype_jdk8_2_9_9.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__com_fasterxml_jackson_datatype_jackson_datatype_jsr310_2_9_9.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__com_fasterxml_jackson_module_jackson_module_parameter_names_2_9_9.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__com_jayway_jsonpath_json_path_2_4_0.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__com_vaadin_external_google_android_json_0_0_20131108_vaadin1.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__javax_activation_javax_activation_api_1_2_0.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__javax_annotation_javax_annotation_api_1_3_2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__javax_validation_validation_api_2_0_1_Final.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__javax_xml_bind_jaxb_api_2_3_1.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__junit_junit_4_12.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__net_bytebuddy_byte_buddy_1_9_13.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__net_bytebuddy_byte_buddy_agent_1_9_13.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__net_minidev_accessors_smart_1_2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__net_minidev_json_smart_2_3.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_apache_logging_log4j_log4j_api_2_11_2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_apache_logging_log4j_log4j_to_slf4j_2_11_2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_apache_tomcat_embed_tomcat_embed_core_9_0_21.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_apache_tomcat_embed_tomcat_embed_el_9_0_21.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_apache_tomcat_embed_tomcat_embed_websocket_9_0_21.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_assertj_assertj_core_3_11_1.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_attoparser_attoparser_2_0_5_RELEASE.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_hamcrest_hamcrest_core_1_3.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_hamcrest_hamcrest_library_1_3.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_hibernate_validator_hibernate_validator_6_0_17_Final.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_jboss_logging_jboss_logging_3_3_2_Final.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_mockito_mockito_core_2_23_4.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_objenesis_objenesis_2_6.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_ow2_asm_asm_5_0_4.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_skyscreamer_jsonassert_1_5_0.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_slf4j_jul_to_slf4j_1_7_26.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_slf4j_slf4j_api_1_7_26.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_springframework_boot_spring_boot_2_1_6_RELEASE.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_springframework_boot_spring_boot_autoconfigure_2_1_6_RELEASE.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_springframework_boot_spring_boot_devtools_2_1_6_RELEASE.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_springframework_boot_spring_boot_starter_2_1_6_RELEASE.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_springframework_boot_spring_boot_starter_json_2_1_6_RELEASE.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_springframework_boot_spring_boot_starter_logging_2_1_6_RELEASE.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_springframework_boot_spring_boot_starter_test_2_1_6_RELEASE.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_springframework_boot_spring_boot_starter_thymeleaf_2_1_6_RELEASE.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_springframework_boot_spring_boot_starter_tomcat_2_1_6_RELEASE.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_springframework_boot_spring_boot_starter_web_2_1_6_RELEASE.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_springframework_boot_spring_boot_test_2_1_6_RELEASE.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_springframework_boot_spring_boot_test_autoconfigure_2_1_6_RELEASE.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_springframework_spring_aop_5_1_8_RELEASE.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_springframework_spring_beans_5_1_8_RELEASE.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_springframework_spring_context_5_1_8_RELEASE.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_springframework_spring_core_5_1_8_RELEASE.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_springframework_spring_expression_5_1_8_RELEASE.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_springframework_spring_jcl_5_1_8_RELEASE.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_springframework_spring_test_5_1_8_RELEASE.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_springframework_spring_web_5_1_8_RELEASE.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_springframework_spring_webmvc_5_1_8_RELEASE.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_thymeleaf_extras_thymeleaf_extras_java8time_3_0_4_RELEASE.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_thymeleaf_thymeleaf_3_0_11_RELEASE.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_thymeleaf_thymeleaf_spring5_3_0_11_RELEASE.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_unbescape_unbescape_1_1_6_RELEASE.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_xmlunit_xmlunit_core_2_6_2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_yaml_snakeyaml_1_23.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/uiDesigner.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /Picuang_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adlered/Picuang/8c361437de5fe56f45ff6cf6785a033a8e267d2a/Picuang_logo.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Picuang ![](https://img.shields.io/badge/license-Apache2.0-orange.svg?style=flat-square) ![](https://img.shields.io/github/downloads/adlered/Picuang/total?style=flat-square) ![](https://img.shields.io/github/v/release/adlered/Picuang?style=flat-square) 2 | 3 | ![](/Picuang_logo.png) 4 | 5 | :pushpin: 轻量本地图床,使用SpringBoot开发,面向用户的网络图床服务。https://pic.stackoverflow.wiki 6 | :wrench: 如果你遇到任何问题,都可以通过我个人签名中的联系方式与我沟通! 7 | :bookmark: Picuang使用Apache 2.0协议,您可以自由进行个人/商业使用,但因各种原因造成的后果~~雨我无瓜~~请自行承担。 8 | 9 | ## :art: 介绍 10 | 11 | Picuang是一款`根图床`WEB程序。用户可以向Picuang中上传`jpg`/`jpeg`/`png`/`svg`/`gif`/`bmp`/`ico`/`tiff`图片,Picuang会自动将图片上传至Picuang的运行目录中。 12 | 13 | Picuang会将用户上传的图片保存到`本地`,而非将图片上传到`其它公共容器`中(例如七牛、新浪等),满足`搭建一个提供图床存储、读取服务`的需求。 14 | 15 | ## :sparkles: 功能 16 | 17 | - [x] 选择、拖拽或粘贴图片,自动上传至Picuang服务器中 18 | - [x] 自动生成图片对应的`URL格式链接`、`HTML标签格式链接`、`Markdown格式链接` 19 | - [x] 图片链接克隆(转存)功能,可输入图片的URL,Picuang会自动下载并保存到Picuang服务器中 20 | - [x] 单IP上传自动阻流器,上传过快会排队上传,减轻服务器压力,防止恶意上传/克隆攻击 21 | - [x] 历史记录功能(按IP地址读取,所以更换IP地址后无法查询) 22 | - [x] Picuang管理员后台设置界面(基于配置文件存储,不依赖数据库) 23 | - [x] 仅管理员可上传功能(默认关闭,必须在后台登录后才能上传) 24 | 25 | ## :globe_with_meridians: 使用技术 26 | 27 | 开发: 28 | `Intellij IDEA` 29 | 30 | 后端: 31 | `Thymeleaf` 32 | `Spring Boot` 33 | 34 | 前端: 35 | `JQuery` 36 | `Bootstrap` 37 | `Axios` 38 | 39 | ## :mag: 体验 40 | 41 | [可以来这里直接体验哦~](https://pic.stackoverflow.wiki/) 42 | 43 | ### 管理界面: 44 | 45 | 支持热重载,丰富的自定义功能 46 | 47 | ![屏幕快照 2019-11-24 下午10.58.27.png](https://pic.stackoverflow.wiki/uploadImages/221/222/10/75/2019/11/24/23/02/181d3c20-3b7e-4b28-ac0f-66dba0a6e823.png) 48 | 49 | ### 主界面: 50 | 51 | ![屏幕快照 2019-11-24 下午10.58.31.png](https://pic.stackoverflow.wiki/uploadImages/221/222/10/75/2019/11/24/23/02/1809da36-d1d6-4032-aac1-98509e9eabf3.png) 52 | 53 | ### 历史记录: 54 | 55 | 时间线式展示,清晰明了全面 56 | 57 | ![屏幕快照 2019-11-24 下午10.59.55.png](https://pic.stackoverflow.wiki/uploadImages/221/222/10/75/2019/11/24/23/02/16f08bf0-b296-4f47-ae57-4884b9115013.png) 58 | 59 | ## :page_facing_up: 使用事项 60 | 61 | Picuang不需要配置数据库,如果你使用IDEA直接运行本项目或是用Maven打包为war包,它会自动将图片上传到网站根目录中的`uploadImages`目录中。你可以在UploadController.java中找到适配代码: 62 | 63 | ``` 64 | String path = ClassUtils.getDefaultClassLoader().getResource("").getPath() + "static/uploadImages/"; 65 | ``` 66 | 67 | Picuang读取了当前网站的根目录。由于项目使用了Thymeleaf,所以`static`是存储静态资源的根目录。 68 | 69 | ### :white_check_mark: 安装 / 二次开发 70 | 71 | * Picuang可在Tomcat上运行,[在这里下载已经打包好的war包,部署到Tomcat中](https://github.com/AdlerED/Picuang/releases) 72 | * 如果你想自己修改Picuang的源码,Clone后在Intellij IDEA中运行,使用Maven - package打包新的war包,新的war包位置在一般在`target`目录中。如图所示: 73 | 74 | ![image.png](https://pic.stackoverflow.wiki/uploadImages/bce0a4b4-bd34-4e63-a3a5-74d898a9dd63.png) 75 | 76 | ### :arrow_up: 版本更新 77 | 78 | * 在版本更新之前,请备份您的 `uploadImages` 文件夹(图床文件存储位置),并备份 `config.ini` 文件。 79 | * 清空旧版本 Picuang 所在目录,并将新版本部署。 80 | * 将备份的文件放回原位。 81 | 82 | ### :heavy_plus_sign: 调整上传文件大小限制 83 | 84 | 在使用前,你可以在`application.properties`文件中调整文件的限制: 85 | 86 | ``` 87 | #重要!Picuang图床设置 88 | //单个文件传输文件大小限制 89 | spring.servlet.multipart.max-file-size=20MB 90 | //单次传输文件大小限制 91 | spring.servlet.multipart.max-request-size=20MB 92 | #Picuang图床设置结束 93 | ``` 94 | 95 | 同时,你设置的限制大小会自动同步到前端的标题当中,用户可以直观地看到文件上传的大小限制。 96 | 97 | ### :rotating_light: 注意事项 98 | 99 | 如果你使用了Tomcat 或 Tomcat和Nginx搭载了Picuang,你可能会遇到上传失败的情况。请按照下方的几个解决办法尝试: 100 | 101 | 1. Tomcat:context.xml 102 | 103 | 修改`conf/context.xml`文件,在``之前添加一行: 104 | 105 | ``` 106 | 107 | ``` 108 | 109 | 2. Tomcat:server.xml 110 | 111 | 修改`conf/server.xml`文件,在你使用端口的`Connector`配置中添加一条: 112 | 113 | ``` 114 | maxPostSize="209715200" 115 | ``` 116 | 117 | 3. Nginx 118 | 119 | 在你的`location / {`下添加一行: 120 | 121 | ``` 122 | client_max_body_size 100m; 123 | ``` 124 | 125 | ### :green_heart: 轻量说明 126 | 127 | ``` 128 | Picuang 是一款轻量图床,适用于个人或小规模使用。 129 | Picuang 没有数据库,图片日期通过文件夹进行排列存储,配置通过本地配置文件进行存储。 130 | 如有问题,欢迎联系我们或直接提交PR。 131 | ``` 132 | -------------------------------------------------------------------------------- /mvnw.cmd: -------------------------------------------------------------------------------- 1 | @REM ---------------------------------------------------------------------------- 2 | @REM Licensed to the Apache Software Foundation (ASF) under one 3 | @REM or more contributor license agreements. See the NOTICE file 4 | @REM distributed with this work for additional information 5 | @REM regarding copyright ownership. The ASF licenses this file 6 | @REM to you under the Apache License, Version 2.0 (the 7 | @REM "License"); you may not use this file except in compliance 8 | @REM with the License. You may obtain a copy of the License at 9 | @REM 10 | @REM https://www.apache.org/licenses/LICENSE-2.0 11 | @REM 12 | @REM Unless required by applicable law or agreed to in writing, 13 | @REM software distributed under the License is distributed on an 14 | @REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | @REM KIND, either express or implied. See the License for the 16 | @REM specific language governing permissions and limitations 17 | @REM under the License. 18 | @REM ---------------------------------------------------------------------------- 19 | 20 | @REM ---------------------------------------------------------------------------- 21 | @REM Maven2 Start Up Batch script 22 | @REM 23 | @REM Required ENV vars: 24 | @REM JAVA_HOME - location of a JDK home dir 25 | @REM 26 | @REM Optional ENV vars 27 | @REM M2_HOME - location of maven2's installed home dir 28 | @REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands 29 | @REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending 30 | @REM MAVEN_OPTS - parameters passed to the Java VM when running Maven 31 | @REM e.g. to debug Maven itself, use 32 | @REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 33 | @REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files 34 | @REM ---------------------------------------------------------------------------- 35 | 36 | @REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' 37 | @echo off 38 | @REM set title of command window 39 | title %0 40 | @REM enable echoing my setting MAVEN_BATCH_ECHO to 'on' 41 | @if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% 42 | 43 | @REM set %HOME% to equivalent of $HOME 44 | if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") 45 | 46 | @REM Execute a user defined script before this one 47 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre 48 | @REM check for pre script, once with legacy .bat ending and once with .cmd ending 49 | if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" 50 | if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" 51 | :skipRcPre 52 | 53 | @setlocal 54 | 55 | set ERROR_CODE=0 56 | 57 | @REM To isolate internal variables from possible post scripts, we use another setlocal 58 | @setlocal 59 | 60 | @REM ==== START VALIDATION ==== 61 | if not "%JAVA_HOME%" == "" goto OkJHome 62 | 63 | echo. 64 | echo Error: JAVA_HOME not found in your environment. >&2 65 | echo Please set the JAVA_HOME variable in your environment to match the >&2 66 | echo location of your Java installation. >&2 67 | echo. 68 | goto error 69 | 70 | :OkJHome 71 | if exist "%JAVA_HOME%\bin\java.exe" goto init 72 | 73 | echo. 74 | echo Error: JAVA_HOME is set to an invalid directory. >&2 75 | echo JAVA_HOME = "%JAVA_HOME%" >&2 76 | echo Please set the JAVA_HOME variable in your environment to match the >&2 77 | echo location of your Java installation. >&2 78 | echo. 79 | goto error 80 | 81 | @REM ==== END VALIDATION ==== 82 | 83 | :init 84 | 85 | @REM Find the project base dir, i.e. the directory that contains the folder ".mvn". 86 | @REM Fallback to current working directory if not found. 87 | 88 | set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% 89 | IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir 90 | 91 | set EXEC_DIR=%CD% 92 | set WDIR=%EXEC_DIR% 93 | :findBaseDir 94 | IF EXIST "%WDIR%"\.mvn goto baseDirFound 95 | cd .. 96 | IF "%WDIR%"=="%CD%" goto baseDirNotFound 97 | set WDIR=%CD% 98 | goto findBaseDir 99 | 100 | :baseDirFound 101 | set MAVEN_PROJECTBASEDIR=%WDIR% 102 | cd "%EXEC_DIR%" 103 | goto endDetectBaseDir 104 | 105 | :baseDirNotFound 106 | set MAVEN_PROJECTBASEDIR=%EXEC_DIR% 107 | cd "%EXEC_DIR%" 108 | 109 | :endDetectBaseDir 110 | 111 | IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig 112 | 113 | @setlocal EnableExtensions EnableDelayedExpansion 114 | for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a 115 | @endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% 116 | 117 | :endReadAdditionalConfig 118 | 119 | SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" 120 | set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" 121 | set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 122 | 123 | set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar" 124 | FOR /F "tokens=1,2 delims==" %%A IN (%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties) DO ( 125 | IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B 126 | ) 127 | 128 | @REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central 129 | @REM This allows using the maven wrapper in projects that prohibit checking in binary data. 130 | if exist %WRAPPER_JAR% ( 131 | echo Found %WRAPPER_JAR% 132 | ) else ( 133 | echo Couldn't find %WRAPPER_JAR%, downloading it ... 134 | echo Downloading from: %DOWNLOAD_URL% 135 | powershell -Command "(New-Object Net.WebClient).DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')" 136 | echo Finished downloading %WRAPPER_JAR% 137 | ) 138 | @REM End of extension 139 | 140 | %MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* 141 | if ERRORLEVEL 1 goto error 142 | goto end 143 | 144 | :error 145 | set ERROR_CODE=1 146 | 147 | :end 148 | @endlocal & set ERROR_CODE=%ERROR_CODE% 149 | 150 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost 151 | @REM check for post script, once with legacy .bat ending and once with .cmd ending 152 | if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" 153 | if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" 154 | :skipRcPost 155 | 156 | @REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' 157 | if "%MAVEN_BATCH_PAUSE%" == "on" pause 158 | 159 | if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% 160 | 161 | exit /B %ERROR_CODE% 162 | -------------------------------------------------------------------------------- /picuang.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.springframework.boot 7 | spring-boot-starter-parent 8 | 2.1.6.RELEASE 9 | 10 | 11 | pers.adlered.picuang 12 | picuang 13 | 0.0.1-SNAPSHOT 14 | picuang 15 | Picture Chuang XD 16 | 17 | 18 | 1.8 19 | 20 | 21 | war 22 | 23 | 24 | 25 | org.springframework.boot 26 | spring-boot-starter-thymeleaf 27 | 28 | 29 | org.springframework.boot 30 | spring-boot-starter-web 31 | 32 | 33 | 34 | org.springframework.boot 35 | spring-boot-starter-test 36 | test 37 | 38 | 39 | 40 | 41 | org.springframework.boot 42 | spring-boot-devtools 43 | true 44 | 45 | 46 | 47 | 48 | 49 | 50 | org.springframework.boot 51 | spring-boot-maven-plugin 52 | 53 | 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /src/META-INF/MANIFEST.MF: -------------------------------------------------------------------------------- 1 | Manifest-Version: 1.0 2 | Main-Class: pers.adlered.picuang.PicuangApplication 3 | 4 | -------------------------------------------------------------------------------- /src/main/java/META-INF/MANIFEST.MF: -------------------------------------------------------------------------------- 1 | Manifest-Version: 1.0 2 | Main-Class: pers.adlered.picuang.PicuangApplication 3 | 4 | -------------------------------------------------------------------------------- /src/main/java/pers/adlered/picuang/Application.java: -------------------------------------------------------------------------------- 1 | package pers.adlered.picuang; 2 | 3 | import org.springframework.boot.builder.SpringApplicationBuilder; 4 | import org.springframework.boot.web.servlet.support.SpringBootServletInitializer; 5 | 6 | public class Application extends SpringBootServletInitializer { 7 | @Override 8 | protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) { 9 | return builder.sources(PicuangApplication.class); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/pers/adlered/picuang/PicuangApplication.java: -------------------------------------------------------------------------------- 1 | package pers.adlered.picuang; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class PicuangApplication { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(PicuangApplication.class, args); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/pers/adlered/picuang/Test.java: -------------------------------------------------------------------------------- 1 | package pers.adlered.picuang; 2 | 3 | import pers.adlered.picuang.access.HttpOrHttpsAccess; 4 | 5 | import java.io.BufferedInputStream; 6 | import java.util.Map; 7 | 8 | /** 9 | *

picuang

10 | *

11 | * 12 | * @author : https://github.com/AdlerED 13 | * @date : 2019-11-06 21:37 14 | **/ 15 | public class Test { 16 | public static void main(String[] args) { 17 | try { 18 | BufferedInputStream bufferedInputStream = HttpOrHttpsAccess.post("http://de.cichic.com/pink-polka-dot-draped-off-shoulder-oversized-midi-dress.html", "", null); 19 | byte[] bytes = new byte[1024]; 20 | int len = -1; 21 | while ((len = bufferedInputStream.read(bytes)) != -1) { 22 | System.out.print(new String(bytes, 0 , len)); 23 | } 24 | } catch (Exception e) { 25 | e.printStackTrace(); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/pers/adlered/picuang/access/HttpOrHttpsAccess.java: -------------------------------------------------------------------------------- 1 | package pers.adlered.picuang.access; 2 | 3 | import javax.net.ssl.HttpsURLConnection; 4 | import javax.net.ssl.SSLContext; 5 | import javax.net.ssl.SSLSocketFactory; 6 | import javax.net.ssl.TrustManager; 7 | import java.io.*; 8 | import java.net.HttpURLConnection; 9 | import java.net.URL; 10 | import java.net.URLConnection; 11 | import java.security.GeneralSecurityException; 12 | import java.util.Map; 13 | 14 | /** 15 | *

picuang

16 | *

访问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 header) throws IOException, GeneralSecurityException { 24 | return post(path, null, header); 25 | } 26 | 27 | public static BufferedInputStream post(String path, String postData, Map header) throws IOException, GeneralSecurityException { 28 | boolean HTTPS = path.matches("^(https://).*"); 29 | // 打开连接 30 | URL url = new URL(path); 31 | URLConnection conn = null; 32 | conn = url.openConnection(); 33 | // Content 34 | conn.setRequestProperty("accept", "*/*"); 35 | conn.setRequestProperty("connection", "Keep-Alive"); 36 | conn.setRequestProperty("user-agent", 37 | "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)"); 38 | // 超时 39 | conn.setConnectTimeout(5000); 40 | conn.setReadTimeout(15000); 41 | // Header 42 | if (header != null) { 43 | for (Map.Entry entry : header.entrySet()) { 44 | conn.setRequestProperty(entry.getKey(), entry.getValue()); 45 | } 46 | } 47 | // 输入流 48 | BufferedInputStream in = null; 49 | if (path.matches("^(https://).*")) { 50 | HttpsURLConnection httpsConn = (HttpsURLConnection) conn; 51 | // 创建SSLContext对象,并使用我们指定的信任管理器初始化 52 | TrustManager[] tm = {new MyX509TrustManager()}; 53 | SSLContext sslContext = SSLContext.getInstance("SSL", "SunJSSE"); 54 | sslContext.init(null, tm, new java.security.SecureRandom()); 55 | // 从上述SSLContext对象中得到SSLSocketFactory对象 56 | SSLSocketFactory ssf = sslContext.getSocketFactory(); 57 | httpsConn.setSSLSocketFactory(ssf); 58 | httpsConn.setDoInput(true);// 打开输入流,以便从服务器获取数据 59 | httpsConn.setDoOutput(true);// 打开输出流,以便向服务器提交数据 60 | if (postData != null && !postData.isEmpty()) { 61 | PrintWriter out = new PrintWriter(httpsConn.getOutputStream()); 62 | out.print(postData); 63 | out.flush(); 64 | } 65 | in = new BufferedInputStream(httpsConn.getInputStream()); 66 | } else { 67 | HttpURLConnection httpConn = (HttpURLConnection) conn; 68 | httpConn.setDoInput(true); 69 | httpConn.setDoOutput(true); 70 | if (postData != null && !postData.isEmpty()) { 71 | PrintWriter out = new PrintWriter(httpConn.getOutputStream()); 72 | out.print(postData); 73 | out.flush(); 74 | } 75 | in = new BufferedInputStream(httpConn.getInputStream()); 76 | } 77 | return in; 78 | } 79 | } 80 | 81 | -------------------------------------------------------------------------------- /src/main/java/pers/adlered/picuang/access/MyX509TrustManager.java: -------------------------------------------------------------------------------- 1 | package pers.adlered.picuang.access; 2 | 3 | /** 4 | *

picuang

5 | *

实现不需要加载证书,访问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 | *

picuang

13 | *

管理

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 "
请注意! 您的Picuang配置文件来自旧版 (" + realVersion + ")
" + 38 | "请将旧配置文件
" + ToolBox.getINIDir() + "
备份并删除, 重启Picuang服务端或点击\"生成新配置文件\"按钮使Picuang重新生成一个新版的配置文件;
" + 39 | "再对照自动生成的新版本 (" + neededVersion + ") 配置文件, 将您备份的旧版配置文件中的数据替换 (除了version以外), 然后点击下方\"重载\"按钮.

" + 40 | "" + 41 | "
"; 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/pers/adlered/picuang/controller/admin/api/AdminAction.java: -------------------------------------------------------------------------------- 1 | package pers.adlered.picuang.controller.admin.api; 2 | 3 | import org.springframework.stereotype.Controller; 4 | import org.springframework.web.bind.annotation.RequestMapping; 5 | import org.springframework.web.bind.annotation.RequestMethod; 6 | import org.springframework.web.bind.annotation.ResponseBody; 7 | import pers.adlered.picuang.prop.Prop; 8 | import pers.adlered.picuang.result.Result; 9 | import pers.adlered.picuang.tool.ToolBox; 10 | 11 | import javax.servlet.http.HttpSession; 12 | 13 | /** 14 | *

picuang

15 | *

管理员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 | *

picuang

20 | *

配置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 | *

picuang

19 | *

查看历史记录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 List list(HttpServletRequest request, String year, String month, String day) { 29 | List list = new ArrayList<>(); 30 | File file = new File(getHome(request) + year + "/" + month + "/" + day + "/"); 31 | File[] hour = listFiles(file); 32 | for (File i : hour) { 33 | if (i.isDirectory()) { 34 | String dir = getHome(request) + year + "/" + month + "/" + day + "/" + i.getName() + "/"; 35 | File[] minute = listFiles(new File(dir)); 36 | for (File j : minute) { 37 | String filesDir = dir + j.getName() + "/"; 38 | File[] files = listFiles(new File(filesDir)); 39 | try { 40 | for (File k : files) { 41 | if (k.isFile()) { 42 | PicProp picProp = new PicProp(); 43 | picProp.setTime(i.getName() + ":" + j.getName()); 44 | picProp.setFilename(k.getName()); 45 | picProp.setPath("/uploadImages/" + IPUtil.getIpAddr(request).replaceAll("\\.", "/").replaceAll(":", "/") + "/" + year + "/" + month + "/" + day + "/" + i.getName() + "/" + j.getName() + "/" + k.getName()); 46 | picProp.setIp(IPUtil.getIpAddr(request)); 47 | list.add(picProp); 48 | } 49 | } 50 | } catch (NullPointerException NPE) { 51 | logNpe(); 52 | continue; 53 | } 54 | } 55 | } 56 | } 57 | Collections.reverse(list); 58 | return list; 59 | } 60 | 61 | @RequestMapping("/api/day") 62 | @ResponseBody 63 | public List day(HttpServletRequest request, String year, String month) { 64 | File file = new File(getHome(request) + year + "/" + month + "/"); 65 | File[] list = listFiles(file); 66 | List lists = new ArrayList<>(); 67 | try { 68 | for (File i : list) { 69 | if (i.isDirectory()) { 70 | lists.add(i.getName()); 71 | } 72 | } 73 | } catch (NullPointerException NPE) { 74 | logNpe(); 75 | } 76 | Collections.reverse(lists); 77 | return lists; 78 | } 79 | 80 | @RequestMapping("/api/month") 81 | @ResponseBody 82 | public List month(HttpServletRequest request, String year) { 83 | StringBuilder sb = new StringBuilder(); 84 | File file = new File(getHome(request) + year + "/"); 85 | File[] list = listFiles(file); 86 | List lists = new ArrayList<>(); 87 | try { 88 | for (File i : list) { 89 | if (i.isDirectory()) { 90 | lists.add(i.getName()); 91 | } 92 | } 93 | } catch (NullPointerException NPE) { 94 | } 95 | Collections.reverse(lists); 96 | return lists; 97 | } 98 | 99 | @RequestMapping("/api/year") 100 | @ResponseBody 101 | public List year(HttpServletRequest request) { 102 | File file = new File(getHome(request)); 103 | File[] list = listFiles(file); 104 | List lists = new ArrayList<>(); 105 | try { 106 | for (File i : list) { 107 | if (i.isDirectory()) { 108 | lists.add(i.getName()); 109 | } 110 | } 111 | } catch (NullPointerException NPE) { 112 | } 113 | Collections.reverse(lists); 114 | return lists; 115 | } 116 | 117 | private String getHome(HttpServletRequest request) { 118 | String addr = IPUtil.getIpAddr(request).replaceAll("\\.", "/").replaceAll(":", "/"); 119 | return ToolBox.getPicStoreDir() + addr + "/"; 120 | } 121 | 122 | private File[] listFiles(File file) { 123 | File[] files = file.listFiles(); 124 | return files == null ? new File[0] : files; 125 | } 126 | 127 | private void logNpe() { 128 | Logger.log(String.format("A null pointer exception occurred in [%s]", this.getClass().getName())); 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /src/main/java/pers/adlered/picuang/controller/api/bean/PicProp.java: -------------------------------------------------------------------------------- 1 | package pers.adlered.picuang.controller.api.bean; 2 | 3 | /** 4 | *

picuang

5 | *

List 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 | *

picuang

5 | *

日志打印

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 | *

picuang

14 | *

自动配置文件

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 { 4 | private Integer code = 404; 5 | private String msg = ""; 6 | private T data = (T) ""; 7 | 8 | public Result() { 9 | super(); 10 | } 11 | 12 | public Result(Integer code, String msg, T data) { 13 | this.code = code; 14 | this.msg = msg; 15 | this.data = data; 16 | } 17 | 18 | public Integer getCode() { 19 | return code; 20 | } 21 | 22 | public void setCode(Integer code) { 23 | this.code = code; 24 | } 25 | 26 | public String getMsg() { 27 | return msg; 28 | } 29 | 30 | public void setMsg(String msg) { 31 | this.msg = msg; 32 | } 33 | 34 | public T getData() { 35 | return data; 36 | } 37 | 38 | public void setData(T data) { 39 | this.data = data; 40 | } 41 | 42 | @Override 43 | public String toString() { 44 | return "Result{" + 45 | "code=" + code + 46 | ", msg='" + msg + "'" + 47 | ", data=" + data + 48 | '}'; 49 | } 50 | } -------------------------------------------------------------------------------- /src/main/java/pers/adlered/picuang/tool/FileUtil.java: -------------------------------------------------------------------------------- 1 | package pers.adlered.picuang.tool; 2 | 3 | import org.springframework.util.ResourceUtils; 4 | 5 | import javax.servlet.http.HttpServletResponse; 6 | import java.io.*; 7 | 8 | /** 9 | *

picuang

10 | *

下载文件类

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 | *

picuang

9 | *

获取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 | *

picuang

14 | *

工具箱

15 | * 16 | * @author : https://github.com/AdlerED 17 | * @date : 2019-11-06 11:09 18 | **/ 19 | public class ToolBox { 20 | private static final Set suffixSet; 21 | 22 | static { 23 | suffixSet = new HashSet<>(); 24 | suffixSet.add(".jpeg"); 25 | suffixSet.add(".jpg"); 26 | suffixSet.add(".png"); 27 | suffixSet.add(".gif"); 28 | suffixSet.add(".svg"); 29 | suffixSet.add(".bmp"); 30 | suffixSet.add(".ico"); 31 | suffixSet.add(".tiff"); 32 | } 33 | 34 | public static String getSuffixName(String filename) { 35 | String suffixName = filename.substring(filename.lastIndexOf(".")); 36 | suffixName = suffixName.toLowerCase(); 37 | return suffixName; 38 | } 39 | 40 | public static boolean isPic(String suffixName) { 41 | return suffixName.contains(suffixName); 42 | } 43 | 44 | public static String getPicStoreDir() { 45 | return ClassUtils.getDefaultClassLoader().getResource("").getPath() + "static/uploadImages/"; 46 | } 47 | 48 | public static File generatePicFile(String suffixName, String time, String IP) { 49 | String path = getPicStoreDir() + IP + "/" + time; 50 | String fileName = UUID.randomUUID() + suffixName; 51 | return new File(path + fileName); 52 | } 53 | 54 | public static String getDirByTime() { 55 | Date date = new Date(); 56 | SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy/MM/dd/HH/mm/"); 57 | return simpleDateFormat.format(date); 58 | } 59 | 60 | public static String getINIDir() { 61 | return new File("config.ini").getAbsolutePath(); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/pers/adlered/picuang/tool/double_keys/main/DoubleKeys.java: -------------------------------------------------------------------------------- 1 | package pers.adlered.picuang.tool.double_keys.main; 2 | 3 | import pers.adlered.picuang.tool.double_keys.storage.DoubleKeysStorage; 4 | 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | 8 | /** 9 | *

Picuang

10 | *

验证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 tempList = new ArrayList<>(); 32 | // 检查key1是否存在 33 | if (DoubleKeysStorage.keyMap.containsKey(key1)) { 34 | // key1存在 35 | tempList = DoubleKeysStorage.keyMap.get(key1); 36 | if (tempList.contains(key2)) { 37 | return false; 38 | } else { 39 | tempList.add(key2); 40 | DoubleKeysStorage.keyMap.put(key1, tempList); 41 | return true; 42 | } 43 | } else { 44 | // key1不存在,生成key1 45 | tempList.add(key2); 46 | DoubleKeysStorage.keyMap.put(key1, tempList); 47 | // 第一次生成,直接返回true 48 | return true; 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/pers/adlered/picuang/tool/double_keys/storage/DoubleKeysStorage.java: -------------------------------------------------------------------------------- 1 | package pers.adlered.picuang.tool.double_keys.storage; 2 | 3 | import java.util.ArrayList; 4 | import java.util.HashMap; 5 | import java.util.List; 6 | import java.util.Map; 7 | 8 | /** 9 | *

Picuang

10 | *

11 | * 12 | * @author : https://github.com/AdlerED 13 | * @date : 2019-11-28 11:07 14 | **/ 15 | public class DoubleKeysStorage { 16 | public static Map> keyMap = new HashMap<>(); 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/pers/adlered/simplecurrentlimiter/cache/MainCache.java: -------------------------------------------------------------------------------- 1 | package pers.adlered.simplecurrentlimiter.cache; 2 | 3 | import pers.adlered.simplecurrentlimiter.cache.pair.CachePair; 4 | 5 | import java.util.HashMap; 6 | import java.util.Map; 7 | 8 | /** 9 | *

SimpleCurrentLimiter

10 | *

存储字符串、次数信息

11 | * 12 | * @author : https://github.com/AdlerED 13 | * @date : 2019-10-10 22:49 14 | **/ 15 | public class MainCache { 16 | // 缓存 存储CachePair 17 | public Map cachePairMap = new HashMap<>(); 18 | // 过期时间(毫秒) 19 | public long expireTime = -1; 20 | // 时间单位次数 21 | public long frequencyTime = -1; 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/pers/adlered/simplecurrentlimiter/cache/pair/CachePair.java: -------------------------------------------------------------------------------- 1 | package pers.adlered.simplecurrentlimiter.cache.pair; 2 | 3 | /** 4 | *

SimpleCurrentLimiter

5 | *

字符串和次数 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 | *

SimpleCurrentLimiter

8 | *

底层数据控制

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 | *

SimpleCurrentLimiter

7 | *

简单限流器,用于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("
" + 13 | "
" + 14 | " 存储图片数量" + 15 | "" + 16 | "
"); 17 | }); 18 | axios.get('/api/admin/getConf?conf=password') 19 | .then(function (response) { 20 | $("#config-row").prepend("
" + 21 | "
" + 22 | " 后台管理密码" + 23 | "" + 24 | "
"); 25 | }); 26 | axios.get('/api/admin/getConf?conf=cloneLimit') 27 | .then(function (response) { 28 | $("#config-row").prepend("
" + 29 | "
" + 30 | " 克隆频率限制" + 31 | "" + 32 | "
"); 33 | }); 34 | axios.get('/api/admin/getConf?conf=uploadLimit') 35 | .then(function (response) { 36 | $("#config-row").prepend("
" + 37 | "
" + 38 | " 上传频率限制" + 39 | "" + 40 | "
"); 41 | }); 42 | // 二列 43 | axios.get('/api/admin/getConf?conf=adminOnly') 44 | .then(function (response) { 45 | if (response.data === "on") { 46 | adminOnly = "启用"; 47 | } else if (response.data === "off") { 48 | adminOnly = "停用"; 49 | } else { 50 | adminOnly = "未知"; 51 | } 52 | $("#adminOnlyStatus").html(adminOnly); 53 | }); 54 | $("#logout").show(300); 55 | $("#config").show(300); 56 | }, 200); 57 | } 58 | 59 | function getHelp(key) { 60 | if (key === "imageUploadedCount") { 61 | tip("存储图片数量是在主页展示的\"为用户累计永久存储图\"的数值。
" + 62 | "修改本值不会影响图片的存储,用户每上传一张图片该数值就会+1。"); 63 | } else if (key === "uploadLimit") { 64 | tip("设置上传的频率限制。
" + 65 | "冒号左侧代表\"时间\",右侧代表\"次数\"。
" + 66 | "例如\"3:1\"代表\"每三秒允许上传一张图片\"。
" + 67 | "设置过小的数值会致使上传速度变慢。
" + 68 | "上传多张图片时达到限制会自动阻塞,不会影响正常上传。"); 69 | } else if (key === "cloneLimit") { 70 | tip("设置克隆的频率限制。
" + 71 | "冒号左侧代表\"时间\",右侧代表\"次数\"。
" + 72 | "例如\"3:1\"代表\"每三秒允许克隆一张图片\"。
" + 73 | "设置过小的数值会致使克隆速度变慢。
"); 74 | } 75 | } 76 | 77 | function tip(text) { 78 | $("#helpText").html(text); 79 | $('#helpModal').modal(); 80 | } 81 | 82 | function adminOnlyToggle() { 83 | if (adminOnly === "启用") { 84 | axios.get('/api/admin/setConf?conf=adminOnly&value=off') 85 | .then(function (response) { 86 | adminOnly = "停用"; 87 | $("#adminOnlyStatus").html(adminOnly); 88 | sendNotify("已停用:仅管理员上传模式。"); 89 | } 90 | ); 91 | } else if (adminOnly === "停用") { 92 | axios.get('/api/admin/setConf?conf=adminOnly&value=on') 93 | .then(function (response) { 94 | adminOnly = "启用"; 95 | $("#adminOnlyStatus").html(adminOnly); 96 | sendNotify("已启用:仅管理员上传模式。"); 97 | } 98 | ); 99 | } else { 100 | sendNotify("无法读取管理员上传模式。请检查配置文件是否有adminOnly项,且值为on或off,修改后点击下方\"重载\"按钮后重试。"); 101 | } 102 | } 103 | 104 | function editConfig(sheet) { 105 | var value = $("#" + sheet + "-input").val(); 106 | axios.get('/api/admin/setConf?conf=' + sheet + '&value=' + value) 107 | .then(function (response) { 108 | sendInnerNotify(sheet + " 的值已成功修改为:" + value); 109 | } 110 | ); 111 | } 112 | 113 | function exportConfig() { 114 | window.open("/api/admin/export"); 115 | } 116 | 117 | function importConfig() { 118 | var file = document.getElementById("import").files[0]; 119 | uploadConfigToServer(file); 120 | } 121 | 122 | function uploadConfigToServer(file) { 123 | var param = new FormData(); 124 | param.append('file', file); 125 | var config = { 126 | headers: {'Content-Type': 'multipart/form-data'} 127 | }; 128 | axios.post('/api/admin/import', param, config) 129 | .then(function (response) { 130 | if (response.data.code === 200) { 131 | sendNotify("配置导入成功!配置现已重载并生效。请刷新页面!"); 132 | } else { 133 | sendNotify("配置导入失败!请检查你的配置文件后缀名是否是.ini,且确认其可用性。") 134 | } 135 | } 136 | ); 137 | } 138 | 139 | function reloadServer() { 140 | axios.get('/api/admin/reload') 141 | .then(function (response) { 142 | sendNotify("配置已重载!请刷新页面。"); 143 | } 144 | ); 145 | } 146 | 147 | verifyReNew = 0; 148 | function reNewConfig() { 149 | if (verifyReNew === 0) { 150 | tip("您确定要生成新的配置文件吗?在生成新的配置文件之前,你应该将旧的配置文件导出并备份!
关闭本窗口后,再点一次\"重新生成配置文件\"按钮确定生成。"); 151 | verifyReNew = 1; 152 | } else { 153 | axios.get('/api/admin/renew') 154 | .then(function (response) { 155 | sendNotify("已重新生成配置文件!请刷新页面。"); 156 | } 157 | ); 158 | verifyReNew = 0; 159 | } 160 | } -------------------------------------------------------------------------------- /src/main/resources/static/js/admin/main.js: -------------------------------------------------------------------------------- 1 | $(function () { 2 | axios.get('/api/admin/init') 3 | .then(function (response) { 4 | if (response.data.code === 500) { 5 | $("#message").html("管理密码未设置,无法进入控制台!
请编辑文件 '" + response.data.data + "'
中 'password' 的值,然后点击\"重载\"按钮或重启服务端后刷新再试。
" + 6 | ""); 7 | $("#message").fadeIn(1000); 8 | } else { 9 | axios.get('/api/admin/check') 10 | .then(function (response) { 11 | if (response.data.code === 200) { 12 | showConfig(); 13 | } else { 14 | showLogin(); 15 | } 16 | } 17 | ); 18 | } 19 | } 20 | ); 21 | }); 22 | 23 | function adminLogin() { 24 | var password = $("#admin-password").val(); 25 | $("#admin-password").val(""); 26 | var data = new FormData(); 27 | data.append("password", password); 28 | axios.post('/api/admin/login', data) 29 | .then(function (response) { 30 | if (response.data.code === 200) { 31 | $("#admin-icon").removeClass(); 32 | $("#admin-icon").addClass("glyphicon glyphicon-ok"); 33 | $("#admin-password").hide(200); 34 | $("#admin-button").hide(200); 35 | showConfig(); 36 | } else { 37 | $("#admin-password").css("border-color", "red"); 38 | $("#admin-icon").removeClass(); 39 | $("#admin-icon").addClass("glyphicon glyphicon-remove"); 40 | } 41 | } 42 | ); 43 | } 44 | 45 | function logout() { 46 | axios.get('/api/admin/logout') 47 | .then(function (response) { 48 | location.reload(); 49 | } 50 | ); 51 | } 52 | 53 | function showLogin() { 54 | $("#message").html("
"); 55 | $("#message").fadeIn(500); 56 | $("#admin-password").keyup(function (event) { 57 | if (event.keyCode === 13) { 58 | adminLogin(); 59 | } 60 | }); 61 | } -------------------------------------------------------------------------------- /src/main/resources/static/js/clipboard.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * clipboard.js v2.0.4 3 | * https://zenorocha.github.io/clipboard.js 4 | * 5 | * Licensed MIT © Zeno Rocha 6 | */ 7 | !function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.ClipboardJS=e():t.ClipboardJS=e()}(this,function(){return function(n){var o={};function r(t){if(o[t])return o[t].exports;var e=o[t]={i:t,l:!1,exports:{}};return n[t].call(e.exports,e,e.exports,r),e.l=!0,e.exports}return r.m=n,r.c=o,r.d=function(t,e,n){r.o(t,e)||Object.defineProperty(t,e,{enumerable:!0,get:n})},r.r=function(t){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})},r.t=function(e,t){if(1&t&&(e=r(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var n=Object.create(null);if(r.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)r.d(n,o,function(t){return e[t]}.bind(null,o));return n},r.n=function(t){var e=t&&t.__esModule?function(){return t.default}:function(){return t};return r.d(e,"a",e),e},r.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},r.p="",r(r.s=0)}([function(t,e,n){"use strict";var r="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t},i=function(){function o(t,e){for(var n=0;n · "+document.domain;_div_img.onerror=function(e){_div_icon.display="none";_div_content.style.left="30px";};_div.onmouseover=function(e){_close.style.display="block";};_div.onmouseout=function(e){_close.style.display="none";};_close.onclick=function(e){_div.className="d-toast-close-start";setTimeout(function(){_div.remove();if(typeof onclose=="function"){config.onclose(e);}},500);};_close.onselectstart=function(){return false;};var toast_items=document.getElementsByClassName("d-toast");var _div_bottom=(125*toast_items.length)+20;var _body_height=document.documentElement.clientHeight;if(_body_height-125<_div_bottom){for(var i=toast_items.length-1;i>=0;i--){toast_items[i].remove();}_div_bottom=20;};if(typeof config.onclick=="function"){_div.onclick=function(e){var _target=e.target;if(_target.nodeName=="SPAN"){_close.click;}else{config.onclick(toast_data);};};};_div.style.cursor="default";_div.style.width="360px";_div.style.height="105px";_div.style.position="fixed";_div.style.bottom= _div_bottom+"px";_div.style.right="20px";_div.style.backgroundColor="#474747";_div_img.style.width="50px";_div_img.style.height="50px";_div_img.style.position="absolute";_div_img.style.top="25px";_div_img.style.left="15px";_div_content.style.position="absolute";_div_content.style.left="80px";_div_content.style.top="6px";_div_ul.style.listStyle="none";_div_ul.style.paddingLeft="0px";_div_li_1.style.color="#FFFFFF";_div_li_1.style.fontWeight="bold";_div_li_1.style.fontSize="16px";_div_li_2.style.color="#ADADAD";_div_li_2.style.fontSize="16px";_div_li_3.style.color="#ADADAD";_div_li_3.style.fontSize="12px";_div_li_3.style.marginTop="-3px";_div.className="d-toast";_div_icon.className="d-toast-icon";_div_content.className="d-toast-content";_div_li_1.className="d-toast-title";_div_li_2.className="d-toast-body";_div_li_3.className="d-toast-info";_div_ul.appendChild(_div_li_1);_div_ul.appendChild(_div_li_2);_div_ul.appendChild(_div_li_3);_div_icon.appendChild(_div_img);_div_content.appendChild(_div_ul);if(typeof config.icon=="string"){_div.appendChild(_div_icon);}else{_div_content.style.left="30px";};_div.appendChild(_div_content);_div.appendChild(_close);var _d_toast_timeout=config.timeout;if(typeof _d_toast_timeout=="undefined"){_d_toast_timeout=6500;};document.body.appendChild(_div);setTimeout(function(){_div.className="d-toast-close-start";setTimeout(function(){_div.remove();},500);},_d_toast_timeout);}toast(config){var self=this;var toast_config=config;if(window.Notification && Notification.permission !== "denied" && config.inner!=true) {Notification.requestPermission(function(status) {if(status=="granted"){var _config={lang:"zh-CN",tag:"toast-"+(+new Date()),body:config.body,};if(typeof config.icon == "string"){_config.icon=config.icon;};if(typeof config.data != "undefined"){_config.icon=config.data;};if(typeof config.timeout != "undefined"){_config.timestamp=config.timeout;};const d_toast_n = new Notification(config.title, _config);var d_toast_data=config.data;d_toast_n.onclick=function(e){if(typeof toast_config.onclick=="function"){toast_config.onclick(d_toast_data);};};}else{if(config.dev==true){console.warn('请允许通知!');};self.inner(config);};});}else{if(config.dev==true){console.warn("你的浏览器不支持!\n1、被禁止通知\n2、请更换浏览器\n3、已设置成浏览器通知");};self.inner(config);};};}; -------------------------------------------------------------------------------- /src/main/resources/static/js/fantastic-progress-bar-bootstrap.js: -------------------------------------------------------------------------------- 1 | /** 2 | 利用Bootstrap的进度条叠加效果,实现三个进度条叠在一起比赛前进的效果,nice! 3 | GitHub: AdlerED 4 | 需要先插入HTML如下: 5 | 6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | 15 | 然后调用change(progress);方法即可,例如10%就是change(10); 16 | */ 17 | function change(progress) { 18 | switch(progress) { 19 | case 0: setProgress(0,0,0); break; 20 | case 1: setProgress(1,0,0); break; 21 | case 2: setProgress(1,1,0); break; 22 | case 3: setProgress(1,1,1); break; 23 | case 4: setProgress(2,1,1); break; 24 | case 5: setProgress(2,2,1); break; 25 | case 6: setProgress(2,2,2); break; 26 | case 7: setProgress(3,2,2); break; 27 | case 8: setProgress(3,3,2); break; 28 | case 9: setProgress(3,3,3); break; 29 | case 10: setProgress(4,3,3); break; 30 | case 11: setProgress(4,4,3); break; 31 | case 12: setProgress(4,4,4); break; 32 | case 13: setProgress(5,4,4); break; 33 | case 14: setProgress(5,5,4); break; 34 | case 15: setProgress(5,5,5); break; 35 | case 16: setProgress(6,5,5); break; 36 | case 17: setProgress(6,6,5); break; 37 | case 18: setProgress(6,6,6); break; 38 | case 19: setProgress(7,6,6); break; 39 | case 20: setProgress(7,7,6); break; 40 | case 21: setProgress(7,7,7); break; 41 | case 22: setProgress(8,7,7); break; 42 | case 23: setProgress(8,8,7); break; 43 | case 24: setProgress(8,8,8); break; 44 | case 25: setProgress(9,8,8); break; 45 | case 26: setProgress(9,9,8); break; 46 | case 27: setProgress(9,9,9); break; 47 | case 28: setProgress(10,9,9); break; 48 | case 29: setProgress(10,10,9); break; 49 | case 30: setProgress(10,10,10); break; 50 | case 31: setProgress(11,10,10); break; 51 | case 32: setProgress(11,11,10); break; 52 | case 33: setProgress(11,11,11); break; 53 | case 34: setProgress(12,11,11); break; 54 | case 35: setProgress(12,12,11); break; 55 | case 36: setProgress(12,12,12); break; 56 | case 37: setProgress(13,12,12); break; 57 | case 38: setProgress(13,13,12); break; 58 | case 39: setProgress(13,13,13); break; 59 | case 40: setProgress(14,13,13); break; 60 | case 41: setProgress(14,14,13); break; 61 | case 42: setProgress(14,14,14); break; 62 | case 43: setProgress(15,14,14); break; 63 | case 44: setProgress(15,15,14); break; 64 | case 45: setProgress(15,15,15); break; 65 | case 46: setProgress(16,15,15); break; 66 | case 47: setProgress(16,16,15); break; 67 | case 48: setProgress(16,16,16); break; 68 | case 49: setProgress(17,16,16); break; 69 | case 50: setProgress(17,17,16); break; 70 | case 51: setProgress(17,17,17); break; 71 | case 52: setProgress(18,17,17); break; 72 | case 53: setProgress(18,18,17); break; 73 | case 54: setProgress(18,18,18); break; 74 | case 55: setProgress(19,18,18); break; 75 | case 56: setProgress(19,19,18); break; 76 | case 57: setProgress(19,19,19); break; 77 | case 58: setProgress(20,19,19); break; 78 | case 59: setProgress(20,20,19); break; 79 | case 60: setProgress(20,20,20); break; 80 | case 61: setProgress(31,15,15); break; 81 | case 62: setProgress(33,14,15); break; 82 | case 63: setProgress(35,14,14); break; 83 | case 64: setProgress(37,13,14); break; 84 | case 65: setProgress(39,13,13); break; 85 | case 66: setProgress(41,12,13); break; 86 | case 67: setProgress(43,12,12); break; 87 | case 68: setProgress(45,11,12); break; 88 | case 69: setProgress(47,11,11); break; 89 | case 70: setProgress(49,10,11); break; 90 | case 71: setProgress(51,10,10); break; 91 | case 72: setProgress(53,9,10); break; 92 | case 73: setProgress(55,9,9); break; 93 | case 74: setProgress(57,8,9); break; 94 | case 75: setProgress(59,8,8); break; 95 | case 76: setProgress(61,7,8); break; 96 | case 77: setProgress(63,7,7); break; 97 | case 78: setProgress(65,6,7); break; 98 | case 79: setProgress(67,6,6); break; 99 | case 80: setProgress(69,5,6); break; 100 | case 81: setProgress(71,5,5); break; 101 | case 82: setProgress(73,4,5); break; 102 | case 83: setProgress(75,4,4); break; 103 | case 84: setProgress(77,3,4); break; 104 | case 85: setProgress(79,3,3); break; 105 | case 86: setProgress(81,2,3); break; 106 | case 87: setProgress(83,2,2); break; 107 | case 88: setProgress(85,1,2); break; 108 | case 89: setProgress(87,1,1); break; 109 | case 90: setProgress(89,0,1); break; 110 | case 91: setProgress(91,0,0); break; 111 | case 92: setProgress(92,0,0); break; 112 | case 93: setProgress(92,1,0); break; 113 | case 94: setProgress(92,1,1); break; 114 | case 95: setProgress(92,2,1); break; 115 | case 96: setProgress(92,2,2); break; 116 | case 97: setProgress(93,2,2); break; 117 | case 98: setProgress(94,2,2); break; 118 | case 99: setProgress(95,3,1); break; 119 | case 100: setProgress(100,0,0); break; 120 | } 121 | } 122 | 123 | function setProgress(bar1, bar2, bar3) { 124 | $("#bar1").css("width", bar1 + "%"); 125 | $("#bar2").css("width", bar2 + "%"); 126 | $("#bar3").css("width", bar3 + "%"); 127 | } -------------------------------------------------------------------------------- /src/main/resources/static/js/history/histories.js: -------------------------------------------------------------------------------- 1 | $(function () { 2 | var i = 0; 3 | axios.get('/api/year') 4 | .then(function (yearRes) { 5 | $.each(yearRes.data, function (key, data) { 6 | var year = data; 7 | $("#histories").append("" + 8 | ""); 9 | axios.get('/api/month?year=' + year) 10 | .then(function (monthRes) { 11 | $.each(monthRes.data, function (key, data) { 12 | var month = data; 13 | $("#" + year).append("" + 14 | ""); 15 | $("#" + year + "-" + month).append("

" + year + " 年 " + month + " 月

"); 16 | $("#" + year + "-" + month).append(""); 17 | axios.get('/api/day?year=' + year + '&month=' + month) 18 | .then(function (dayRes) { 19 | $.each(dayRes.data, function (key, data) { 20 | var day = data; 21 | $("#" + year + "-" + month + "-p").append("

" + day + "日

"); 22 | axios.get('/api/list?year=' + year + '&month=' + month + '&day=' + day) 23 | .then(function (listRes) { 24 | $.each(listRes.data, function (key, data) { 25 | var list = data; 26 | $("#" + year + "-" + month + "-" + day).append(""); 27 | if (i == 0) { 28 | $("#histories").prepend("" + 29 | "
历史记录根据您的IP地址(" + data.ip + ")所生成,请及时保存,IP地址更改后历史记录将丢失。
"); 30 | } 31 | ++i; 32 | $("#picCount").text(i); 33 | }); 34 | 35 | $(".lazyload").lazyload(); 36 | }); 37 | }); 38 | }); 39 | }); 40 | }); 41 | }); 42 | }); 43 | }); -------------------------------------------------------------------------------- /src/main/resources/static/js/lazyload.min.js: -------------------------------------------------------------------------------- 1 | /*! Lazy Load 2.0.0-rc.2 - MIT license - Copyright 2007-2019 Mika Tuupola */ 2 | !function(t,e){"object"==typeof exports?module.exports=e(t):"function"==typeof define&&define.amd?define([],e):t.LazyLoad=e(t)}("undefined"!=typeof global?global:this.window||this.global,function(t){"use strict";function e(t,e){this.settings=s(r,e||{}),this.images=t||document.querySelectorAll(this.settings.selector),this.observer=null,this.init()}"function"==typeof define&&define.amd&&(t=window);const r={src:"data-src",srcset:"data-srcset",selector:".lazyload",root:null,rootMargin:"0px",threshold:0},s=function(){let t={},e=!1,r=0,o=arguments.length;"[object Boolean]"===Object.prototype.toString.call(arguments[0])&&(e=arguments[0],r++);for(;r已复制至剪贴板。"); 5 | setTimeout(function () { 6 | $("#clipboardStatus").html(""); 7 | }, 1500); 8 | }); 9 | 10 | clipboard.on('error', function (e) { 11 | $("#clipboardStatus").html("
抱歉,您的浏览器不支持!请手动复制。
"); 12 | setTimeout(function () { 13 | $("#clipboardStatus").html(""); 14 | }, 1500); 15 | }); -------------------------------------------------------------------------------- /src/main/resources/static/js/user/common-queue.js: -------------------------------------------------------------------------------- 1 | queue = 0; 2 | tempCount = 0; 3 | sourceAll = undefined; 4 | 5 | function stopUploadThreads() { 6 | sourceAll.cancel('Operation canceled by the user.'); 7 | sendStatus(""); 8 | change(0); 9 | tempCount = 0; 10 | queue = 0; 11 | } 12 | 13 | function clone() { 14 | var link = $("#picURL").val(); 15 | $("#status").text("服务端正在克隆,请稍候..."); 16 | axios.get('/clone?url=' + link) 17 | .then(function (response) { 18 | if (response.data.code == 200) { 19 | sendStatus("克隆成功!"); 20 | sendNotify("图片克隆已完成。"); 21 | responseHandler(response); 22 | } else { 23 | sendStatus("克隆失败!原因:" + response.data.msg); 24 | sendNotify("图片克隆失败。"); 25 | } 26 | } 27 | ); 28 | } 29 | 30 | function upload() { 31 | if (document.getElementById("upload").files.length == 0) { 32 | $("#status").text("请选择图片!"); 33 | } else { 34 | $("#status").text("准备传输,请稍等。"); 35 | for (var i = 0; i < document.getElementById("upload").files.length; i++) { 36 | var file = document.getElementById("upload").files[i]; 37 | suffixName = file.name.split("."); 38 | suffixName = suffixName[suffixName.length - 1]; 39 | suffixName = suffixName.toLowerCase(); 40 | if (suffixName === "jpeg" || suffixName === "jpg" || suffixName === "png" || suffixName === "gif" || suffixName === "svg" || suffixName === "bmp" || suffixName === "ico" || suffixName === "tiff") { 41 | uploadToServer(file); 42 | } else { 43 | sendInnerNotify(file.name + " 格式不受支持,将跳过该图片的上传。"); 44 | } 45 | } 46 | } 47 | } 48 | 49 | function uploadToServer(file) { 50 | if (sourceAll == undefined) { 51 | console.log("Generating new Token..."); 52 | sourceAll = axios.CancelToken.source(); 53 | } 54 | size = parseInt(((file.size / 1024) / 1024).toFixed(1)); 55 | picLimit = parseInt($("#picLimit").text().replace("MB", "")); 56 | if (size <= picLimit) { 57 | var param = new FormData(); 58 | param.append('file', file); 59 | var config = { 60 | headers: {'Content-Type': 'multipart/form-data'}, 61 | onUploadProgress: function (progressEvent) { 62 | if (progressEvent.lengthComputable) { 63 | progress = progressEvent.loaded / progressEvent.total * 100 | 0; 64 | sendStatus('

多线程传输中
队列:' + queue + '
' + file.name + ':' + progress + '%'); 65 | change(progress); 66 | } 67 | }, 68 | cancelToken: sourceAll.token 69 | }; 70 | ++queue; 71 | ++tempCount; 72 | axios.post('/upload', param, config) 73 | .then(function (response) { 74 | change(0); 75 | responseHandler(response); 76 | sendStatus('

多线程传输中
队列:' + queue + '
' + file.name + ':' + progress + '%'); 77 | --queue; 78 | if (queue == 0) { 79 | if (response.data.code == 401) { 80 | sendStatus("上传失败!" + response.data.msg); 81 | } else { 82 | sendNotify(tempCount + "张图片已上传成功。"); 83 | sendStatus("" + tempCount + "张 图片已全部传输完毕。"); 84 | } 85 | tempCount = 0; 86 | } 87 | sourceAll = undefined; 88 | }) 89 | .catch(function (reason) { 90 | if (axios.isCancel(reason)) { 91 | console.log('Request canceled', reason.message); 92 | } else { 93 | change(0); 94 | sendStatus("您的图片大小超过限制:("); 95 | --queue; 96 | if (queue == 0) { 97 | sendNotify(tempCount + "张图片已上传成功,部分图片超出大小限制。"); 98 | sendStatus("" + tempCount + "张 图片部分传输成功。(部分图片大小超过限制)"); 99 | tempCount = 0; 100 | } 101 | } 102 | sourceAll = undefined; 103 | }); 104 | } else { 105 | sendInnerNotify(file.name + " 文件大小为" + size + "MB,超过限制的" + picLimit + "MB,将跳过传输!"); 106 | } 107 | } 108 | 109 | function responseHandler(response) { 110 | code = response.data.code; 111 | msg = response.data.msg; 112 | switch (code) { 113 | case 200: 114 | filename = response.data.data; 115 | fullLocation = location.origin + msg; 116 | appendLink(fullLocation, "\""", "![" + filename + "](" + fullLocation + ")"); 117 | $("#links").css("display", "block"); 118 | break; 119 | case 406: 120 | sendStatus("未选择图片,请重试。"); 121 | break; 122 | case 500: 123 | sendStatus("图片错误,服务器拒绝解析!请检查图片格式。"); 124 | break; 125 | } 126 | } 127 | 128 | var count = 0; 129 | 130 | function appendLink(link, tag, markdown) { 131 | ++count; 132 | $("#appendLinks").prepend("
" + 133 | "
\n" + 134 | "
\n" + 135 | " \n" + 136 | " \n" + 137 | " \n" + 138 | " \n" + 139 | " \n" + 140 | "
\n" + 141 | "
\n" + 142 | "
\n" + 143 | "
\n" + 144 | " \n" + 145 | " \n" + 146 | " \n" + 147 | " \n" + 148 | " \n" + 149 | "
\n" + 150 | "
\n" + 151 | "
\n" + 152 | "
\n" + 153 | " \n" + 154 | " \n" + 155 | " \n" + 156 | " \n" + 157 | " \n" + 158 | "
\n" + 159 | "
\n"); 160 | $("#entry" + count).hide(); 161 | $("#entry" + count).fadeIn('slow'); 162 | } -------------------------------------------------------------------------------- /src/main/resources/static/js/user/drag-and-drop.js: -------------------------------------------------------------------------------- 1 | $(function () { 2 | axios.get('/api/admin/getConf?conf=adminOnly') 3 | .then(function (response) { 4 | if (response.data === "on") { 5 | axios.get('/api/admin/check') 6 | .then(function (response) { 7 | if (response.data.code === 200) { 8 | bind(); 9 | } 10 | } 11 | ); 12 | } else if (response.data === "off") { 13 | bind(); 14 | } 15 | } 16 | ); 17 | }); 18 | 19 | function bind() { 20 | var oDiv = $("html").get(0); 21 | oDiv.ondragenter = function () { 22 | $("#dragToUploadInformation").show(1000); 23 | setTimeout(function () { 24 | $("#dragToUploadInformation").hide(1000); 25 | }, 2000); 26 | }; 27 | oDiv.ondragover = function (e) { 28 | e.preventDefault(); 29 | }; 30 | oDiv.ondragleave = function (e) { 31 | e.preventDefault(); 32 | }; 33 | oDiv.ondrop = function (e) { 34 | e.preventDefault(); 35 | var fs = e.dataTransfer.files; 36 | for (var i = 0; i < fs.length; i++) { 37 | suffixName = fs[i].name.split("."); 38 | suffixName = suffixName[suffixName.length - 1]; 39 | suffixName = suffixName.toLowerCase(); 40 | if (suffixName === "jpeg" || suffixName === "jpg" || suffixName === "png" || suffixName === "gif" || suffixName === "svg" || suffixName === "bmp" || suffixName === "ico" || suffixName === "tiff") { 41 | uploadToServer(fs[i]); 42 | } else { 43 | sendInnerNotify(fs[i].name + " 格式不受支持,将跳过该图片的上传。"); 44 | } 45 | } 46 | } 47 | } 48 | 49 | function dataURLtoFile(dataURL, fileName) { 50 | var arr = dataURL.split(','), 51 | mime = arr[0].match(/:(.*?);/)[1], 52 | bstr = atob(arr[1]), 53 | n = bstr.length, 54 | u8arr = new Uint8Array(n); 55 | while (n--) { 56 | u8arr[n] = bstr.charCodeAt(n); 57 | } 58 | return new File([u8arr], fileName, {type: mime}); 59 | } -------------------------------------------------------------------------------- /src/main/resources/static/js/user/notify.js: -------------------------------------------------------------------------------- 1 | $(function() { 2 | axios.get('/api/admin/getConf?conf=adminOnly') 3 | .then(function (response) { 4 | if (response.data === "on") { 5 | axios.get('/api/admin/check') 6 | .then(function (response) { 7 | if (response.data.code === 200) { 8 | $("#functions").show(); 9 | } else { 10 | $("#functions").after("
抱歉!根据管理员的设置,非管理员用户无法上传图片。
"); 11 | } 12 | } 13 | ); 14 | } else if (response.data === "off") { 15 | $("#functions").show(); 16 | } 17 | }); 18 | }) 19 | 20 | $(function () { 21 | window.Notification.requestPermission(function (status) {}); 22 | }); 23 | 24 | function sendNotify(str) { 25 | if (window.Notification.permission === "granted") { 26 | // Chrome提醒 27 | var title = "Picuang图床 - 提醒"; 28 | var option = { 29 | body: str, 30 | icon: "/favicon.png" 31 | }; 32 | var notify = new Notification(title, option); 33 | notify.onclick = function () { 34 | notify.close(); 35 | }; 36 | } 37 | sendInnerNotify(str); 38 | } 39 | 40 | function sendInnerNotify(str) { 41 | // 页内提醒 42 | var config = { 43 | title: "Picuang图床 - 提醒", 44 | body: str, 45 | inner: true, 46 | icon: "favicon.png", 47 | onclick: function (data) {} 48 | }; 49 | new dToast(config); 50 | } 51 | 52 | function sendStatus(str) { 53 | $("#status").html(str); 54 | } -------------------------------------------------------------------------------- /src/main/resources/static/js/user/on-paste.js: -------------------------------------------------------------------------------- 1 | document.getElementsByTagName('html')[0].addEventListener('paste', function (e) { 2 | if (!(e.clipboardData && e.clipboardData.items)) { 3 | return; 4 | } 5 | var file = null; 6 | var filename = null; 7 | for (var i = 0, len = e.clipboardData.items.length; i < len; i++) { 8 | var item = e.clipboardData.items[i]; 9 | if (item.kind === "string") { 10 | item.getAsString(function (str) { 11 | filename = str; 12 | }); 13 | } else if (item.kind === "file") { 14 | file = item.getAsFile(); 15 | } 16 | } 17 | setTimeout(function () { 18 | console.log("file::" + file); 19 | console.log("name::" + filename); 20 | if (filename !== null) { 21 | if (!(filename.search("[^*|\\:\"<>?/]+\\.[^*|\\:\"<>?/\u4E00-\u9FA5]+"))) { 22 | // 有文件名 23 | try { 24 | file = new File([file], filename, {type: file.type}); 25 | } catch (e) { 26 | if (file.name != null) { 27 | sendInnerNotify(file.name + " 格式不受支持,将跳过该图片的上传。"); 28 | } else { 29 | sendInnerNotify(filename + " 格式不受支持,将跳过该图片的上传。"); 30 | } 31 | } 32 | } 33 | } 34 | uploadToServer(file); 35 | }, 400); 36 | }); -------------------------------------------------------------------------------- /src/main/resources/templates/admin.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 🛏Picuang - 永久免费 | 免登录图床 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 34 | 35 | 36 |
37 |
38 |
39 |
40 |
41 |

必床 Picuang

42 | 50 |
51 |
52 | 53 |
54 |
55 |
56 | 57 | 123 |
124 |
125 | 126 |
127 |
128 |

129 | Picuang 131 |

132 |
133 |
134 |
135 |
136 |
137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | -------------------------------------------------------------------------------- /src/main/resources/templates/history.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 🛏Picuang - 永久免费 | 免登录图床 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 |
23 |
24 |
25 |
26 |

必床 Picuang

27 | 34 |
35 |
36 | 37 |
38 |
找到0条记录
39 |
40 |
41 | 42 |
43 |
44 |

45 | Picuang 47 |

48 |
49 |
50 |
51 |
52 |
53 | 54 | 55 | 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /src/main/resources/templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 🛏Picuang - 永久免费 | 免登录图床 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 |
23 |
24 |
25 |
26 |

必床 Picuang

27 | 34 |
35 |
36 | 37 |
38 |

永久免费 | 免登录图床

39 |

40 | 请将图片拖拽、粘贴或点击按钮上传
41 | 支持同时多个上传 | 支持JPG / PNG / SVG / GIF / BMP / ICO / TIFF
42 | 图片不会被压缩,不限制外链,永久保存 43 |

44 |

45 | 图片大小限制: 46 |
47 | 为用户累计永久存储图片:张 48 |
49 | 服务器剩余空间:GB 50 |

51 | 52 | 81 | 82 |
83 | 84 |
85 | 86 |
87 | 88 | 91 | 92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 | 101 | 105 |
106 | 107 |
108 |
109 |

110 | Picuang 112 |

113 |
114 |
115 |
116 |
117 |
118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | -------------------------------------------------------------------------------- /src/test/java/pers/adlered/picuang/picuang/PicuangApplicationTests.java: -------------------------------------------------------------------------------- 1 | package pers.adlered.picuang.picuang; 2 | 3 | import org.junit.Test; 4 | import org.junit.runner.RunWith; 5 | import org.springframework.boot.test.context.SpringBootTest; 6 | import org.springframework.test.context.junit4.SpringRunner; 7 | 8 | @RunWith(SpringRunner.class) 9 | @SpringBootTest 10 | public class PicuangApplicationTests { 11 | 12 | @Test 13 | public void contextLoads() { 14 | } 15 | 16 | } 17 | --------------------------------------------------------------------------------