From 4c0d5dbd75618792f994b32f1721f6e16e58660d Mon Sep 17 00:00:00 2001 From: zw <12345678> Date: Fri, 1 Aug 2025 13:59:30 +0800 Subject: [PATCH] =?UTF-8?q?=E5=88=9B=E5=BB=BA=E4=BB=93=E5=BA=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .idea/.gitignore | 8 + .idea/compiler.xml | 14 + .idea/dataSources.xml | 12 + .idea/encodings.xml | 7 + .idea/inspectionProfiles/Project_Default.xml | 14 + .../inspectionProfiles/profiles_settings.xml | 6 + .idea/jarRepositories.xml | 30 ++ ...ogle_android_json_0_0_20131108_vaadin1.xml | 13 + ...ctivation_jakarta_activation_api_1_2_2.xml | 13 + ...nnotation_jakarta_annotation_api_1_3_5.xml | 13 + ...ta_xml_bind_jakarta_xml_bind_api_2_3_3.xml | 13 + ...he_logging_log4j_log4j_to_slf4j_2_17_2.xml | 13 + ..._org_apiguardian_apiguardian_api_1_1_2.xml | 13 + .../Maven__org_hamcrest_hamcrest_2_2.xml | 13 + ..._org_junit_jupiter_junit_jupiter_5_8_2.xml | 13 + ..._junit_jupiter_junit_jupiter_api_5_8_2.xml | 13 + ...nit_jupiter_junit_jupiter_engine_5_8_2.xml | 13 + ...nit_jupiter_junit_jupiter_params_5_8_2.xml | 13 + ..._platform_junit_platform_commons_1_8_2.xml | 13 + ...t_platform_junit_platform_engine_1_8_2.xml | 13 + .../Maven__org_objenesis_objenesis_3_2.xml | 13 + ...Maven__org_opentest4j_opentest4j_1_2_0.xml | 13 + ...aven__org_skyscreamer_jsonassert_1_5_1.xml | 13 + .../Maven__org_slf4j_jul_to_slf4j_1_7_36.xml | 13 + .../Maven__org_slf4j_slf4j_api_1_7_36.xml | 13 + .idea/misc.xml | 12 + .idea/uiDesigner.xml | 124 ++++++ .idea/vvPkAssistant.iml | 8 + HELP.md | 20 + pom.xml | 150 ++++++++ .../java/vvpkassistant/Anchors/AnchorDao.java | 22 ++ .../vvpkassistant/Anchors/AnchorModel.java | 18 + .../Anchors/AnchorsController.java | 78 ++++ src/main/java/vvpkassistant/Application.java | 12 + .../CoinRecords/CoinRecords.java | 31 ++ .../CoinRecords/CoinRecordsDao.java | 14 + .../java/vvpkassistant/Data/ResponseData.java | 36 ++ .../java/vvpkassistant/Data/ResponseInfo.java | 26 ++ .../java/vvpkassistant/Data/WxChatParam.java | 15 + src/main/java/vvpkassistant/Data/WxParam.java | 8 + .../FunctionConfigController.java | 57 +++ .../FunctionConfig/FunctionConfigHolder.java | 42 ++ .../FunctionConfig/FunctionConfigMapper.java | 10 + .../FunctionConfig/FunctionConfigModel.java | 15 + .../SystemMessage/SystemMessage.java | 14 + .../SystemMessageController.java | 49 +++ .../SystemMessage/SystemMessageDao.java | 18 + .../java/vvpkassistant/Tools/COSSigner.java | 276 +++++++++++++ .../java/vvpkassistant/Tools/LogUtil.java | 38 ++ .../java/vvpkassistant/Tools/OkHttpUtils.java | 151 ++++++++ .../java/vvpkassistant/Tools/VVRequester.java | 194 ++++++++++ .../java/vvpkassistant/Tools/VVTools.java | 128 ++++++ .../vvpkassistant/Tools/WebMvcConfig.java | 17 + .../vvpkassistant/User/UserController.java | 363 ++++++++++++++++++ src/main/java/vvpkassistant/User/UserDao.java | 34 ++ .../java/vvpkassistant/User/UserModel.java | 23 ++ .../vvpkassistant/chat/ChatController.java | 43 +++ src/main/java/vvpkassistant/chat/ChatDao.java | 16 + .../java/vvpkassistant/chat/ChatModel.java | 17 + .../java/vvpkassistant/pk/PkController.java | 326 ++++++++++++++++ src/main/java/vvpkassistant/pk/PkInfoDao.java | 77 ++++ .../java/vvpkassistant/pk/PkInfoModel.java | 31 ++ src/main/java/vvpkassistant/pk/PkRecord.java | 27 ++ .../java/vvpkassistant/pk/PkRecordDao.java | 38 ++ .../java/vvpkassistant/pk/PkRecordDetail.java | 21 + .../vvpkassistant/pk/PkRecordDetailDao.java | 17 + src/main/resources/application-dev.yml | 14 + src/main/resources/application-local.yml | 13 + src/main/resources/application-prd.yml | 13 + src/main/resources/application.yml | 9 + src/main/resources/log4j2.xml | 19 + .../java/vvpkassistant/ApplicationTests.java | 22 ++ .../spring-configuration-metadata.json | 22 ++ target/classes/application-dev.yml | 14 + target/classes/application-local.yml | 13 + target/classes/application-prd.yml | 13 + target/classes/application.yml | 9 + target/classes/log4j2.xml | 19 + target/maven-archiver/pom.properties | 5 + .../compile/default-compile/createdFiles.lst | 38 ++ .../compile/default-compile/inputFiles.lst | 36 ++ .../default-testCompile/createdFiles.lst | 1 + .../default-testCompile/inputFiles.lst | 1 + target/vvPkAssistant-0.0.1.jar.original | Bin 0 -> 70448 bytes vvPkAssistant.iml | 17 + 85 files changed, 3219 insertions(+) create mode 100644 .idea/.gitignore create mode 100644 .idea/compiler.xml create mode 100644 .idea/dataSources.xml create mode 100644 .idea/encodings.xml create mode 100644 .idea/inspectionProfiles/Project_Default.xml create mode 100644 .idea/inspectionProfiles/profiles_settings.xml create mode 100644 .idea/jarRepositories.xml create mode 100644 .idea/libraries/Maven__com_vaadin_external_google_android_json_0_0_20131108_vaadin1.xml create mode 100644 .idea/libraries/Maven__jakarta_activation_jakarta_activation_api_1_2_2.xml create mode 100644 .idea/libraries/Maven__jakarta_annotation_jakarta_annotation_api_1_3_5.xml create mode 100644 .idea/libraries/Maven__jakarta_xml_bind_jakarta_xml_bind_api_2_3_3.xml create mode 100644 .idea/libraries/Maven__org_apache_logging_log4j_log4j_to_slf4j_2_17_2.xml create mode 100644 .idea/libraries/Maven__org_apiguardian_apiguardian_api_1_1_2.xml create mode 100644 .idea/libraries/Maven__org_hamcrest_hamcrest_2_2.xml create mode 100644 .idea/libraries/Maven__org_junit_jupiter_junit_jupiter_5_8_2.xml create mode 100644 .idea/libraries/Maven__org_junit_jupiter_junit_jupiter_api_5_8_2.xml create mode 100644 .idea/libraries/Maven__org_junit_jupiter_junit_jupiter_engine_5_8_2.xml create mode 100644 .idea/libraries/Maven__org_junit_jupiter_junit_jupiter_params_5_8_2.xml create mode 100644 .idea/libraries/Maven__org_junit_platform_junit_platform_commons_1_8_2.xml create mode 100644 .idea/libraries/Maven__org_junit_platform_junit_platform_engine_1_8_2.xml create mode 100644 .idea/libraries/Maven__org_objenesis_objenesis_3_2.xml create mode 100644 .idea/libraries/Maven__org_opentest4j_opentest4j_1_2_0.xml create mode 100644 .idea/libraries/Maven__org_skyscreamer_jsonassert_1_5_1.xml create mode 100644 .idea/libraries/Maven__org_slf4j_jul_to_slf4j_1_7_36.xml create mode 100644 .idea/libraries/Maven__org_slf4j_slf4j_api_1_7_36.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/uiDesigner.xml create mode 100644 .idea/vvPkAssistant.iml create mode 100644 HELP.md create mode 100644 pom.xml create mode 100644 src/main/java/vvpkassistant/Anchors/AnchorDao.java create mode 100644 src/main/java/vvpkassistant/Anchors/AnchorModel.java create mode 100644 src/main/java/vvpkassistant/Anchors/AnchorsController.java create mode 100644 src/main/java/vvpkassistant/Application.java create mode 100644 src/main/java/vvpkassistant/CoinRecords/CoinRecords.java create mode 100644 src/main/java/vvpkassistant/CoinRecords/CoinRecordsDao.java create mode 100644 src/main/java/vvpkassistant/Data/ResponseData.java create mode 100644 src/main/java/vvpkassistant/Data/ResponseInfo.java create mode 100644 src/main/java/vvpkassistant/Data/WxChatParam.java create mode 100644 src/main/java/vvpkassistant/Data/WxParam.java create mode 100644 src/main/java/vvpkassistant/FunctionConfig/FunctionConfigController.java create mode 100644 src/main/java/vvpkassistant/FunctionConfig/FunctionConfigHolder.java create mode 100644 src/main/java/vvpkassistant/FunctionConfig/FunctionConfigMapper.java create mode 100644 src/main/java/vvpkassistant/FunctionConfig/FunctionConfigModel.java create mode 100644 src/main/java/vvpkassistant/SystemMessage/SystemMessage.java create mode 100644 src/main/java/vvpkassistant/SystemMessage/SystemMessageController.java create mode 100644 src/main/java/vvpkassistant/SystemMessage/SystemMessageDao.java create mode 100644 src/main/java/vvpkassistant/Tools/COSSigner.java create mode 100644 src/main/java/vvpkassistant/Tools/LogUtil.java create mode 100644 src/main/java/vvpkassistant/Tools/OkHttpUtils.java create mode 100644 src/main/java/vvpkassistant/Tools/VVRequester.java create mode 100644 src/main/java/vvpkassistant/Tools/VVTools.java create mode 100644 src/main/java/vvpkassistant/Tools/WebMvcConfig.java create mode 100644 src/main/java/vvpkassistant/User/UserController.java create mode 100644 src/main/java/vvpkassistant/User/UserDao.java create mode 100644 src/main/java/vvpkassistant/User/UserModel.java create mode 100644 src/main/java/vvpkassistant/chat/ChatController.java create mode 100644 src/main/java/vvpkassistant/chat/ChatDao.java create mode 100644 src/main/java/vvpkassistant/chat/ChatModel.java create mode 100644 src/main/java/vvpkassistant/pk/PkController.java create mode 100644 src/main/java/vvpkassistant/pk/PkInfoDao.java create mode 100644 src/main/java/vvpkassistant/pk/PkInfoModel.java create mode 100644 src/main/java/vvpkassistant/pk/PkRecord.java create mode 100644 src/main/java/vvpkassistant/pk/PkRecordDao.java create mode 100644 src/main/java/vvpkassistant/pk/PkRecordDetail.java create mode 100644 src/main/java/vvpkassistant/pk/PkRecordDetailDao.java create mode 100644 src/main/resources/application-dev.yml create mode 100644 src/main/resources/application-local.yml create mode 100644 src/main/resources/application-prd.yml create mode 100644 src/main/resources/application.yml create mode 100644 src/main/resources/log4j2.xml create mode 100644 src/test/java/vvpkassistant/ApplicationTests.java create mode 100644 target/classes/META-INF/spring-configuration-metadata.json create mode 100644 target/classes/application-dev.yml create mode 100644 target/classes/application-local.yml create mode 100644 target/classes/application-prd.yml create mode 100644 target/classes/application.yml create mode 100644 target/classes/log4j2.xml create mode 100644 target/maven-archiver/pom.properties create mode 100644 target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst create mode 100644 target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst create mode 100644 target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/createdFiles.lst create mode 100644 target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/inputFiles.lst create mode 100644 target/vvPkAssistant-0.0.1.jar.original create mode 100644 vvPkAssistant.iml diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..13566b8 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/compiler.xml b/.idea/compiler.xml new file mode 100644 index 0000000..a39dcad --- /dev/null +++ b/.idea/compiler.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/dataSources.xml b/.idea/dataSources.xml new file mode 100644 index 0000000..2f8893c --- /dev/null +++ b/.idea/dataSources.xml @@ -0,0 +1,12 @@ + + + + + mysql.8 + true + com.mysql.cj.jdbc.Driver + jdbc:mysql://120.26.251.180:3326/vv_assistant + $ProjectFileDir$ + + + \ No newline at end of file diff --git a/.idea/encodings.xml b/.idea/encodings.xml new file mode 100644 index 0000000..aa00ffa --- /dev/null +++ b/.idea/encodings.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..1030d73 --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,14 @@ + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 0000000..105ce2d --- /dev/null +++ b/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/jarRepositories.xml b/.idea/jarRepositories.xml new file mode 100644 index 0000000..42f83d9 --- /dev/null +++ b/.idea/jarRepositories.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Maven__com_vaadin_external_google_android_json_0_0_20131108_vaadin1.xml b/.idea/libraries/Maven__com_vaadin_external_google_android_json_0_0_20131108_vaadin1.xml new file mode 100644 index 0000000..b8581a6 --- /dev/null +++ b/.idea/libraries/Maven__com_vaadin_external_google_android_json_0_0_20131108_vaadin1.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Maven__jakarta_activation_jakarta_activation_api_1_2_2.xml b/.idea/libraries/Maven__jakarta_activation_jakarta_activation_api_1_2_2.xml new file mode 100644 index 0000000..be90656 --- /dev/null +++ b/.idea/libraries/Maven__jakarta_activation_jakarta_activation_api_1_2_2.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Maven__jakarta_annotation_jakarta_annotation_api_1_3_5.xml b/.idea/libraries/Maven__jakarta_annotation_jakarta_annotation_api_1_3_5.xml new file mode 100644 index 0000000..cba9dd2 --- /dev/null +++ b/.idea/libraries/Maven__jakarta_annotation_jakarta_annotation_api_1_3_5.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Maven__jakarta_xml_bind_jakarta_xml_bind_api_2_3_3.xml b/.idea/libraries/Maven__jakarta_xml_bind_jakarta_xml_bind_api_2_3_3.xml new file mode 100644 index 0000000..04213f7 --- /dev/null +++ b/.idea/libraries/Maven__jakarta_xml_bind_jakarta_xml_bind_api_2_3_3.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Maven__org_apache_logging_log4j_log4j_to_slf4j_2_17_2.xml b/.idea/libraries/Maven__org_apache_logging_log4j_log4j_to_slf4j_2_17_2.xml new file mode 100644 index 0000000..a4f1650 --- /dev/null +++ b/.idea/libraries/Maven__org_apache_logging_log4j_log4j_to_slf4j_2_17_2.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Maven__org_apiguardian_apiguardian_api_1_1_2.xml b/.idea/libraries/Maven__org_apiguardian_apiguardian_api_1_1_2.xml new file mode 100644 index 0000000..6ac1c42 --- /dev/null +++ b/.idea/libraries/Maven__org_apiguardian_apiguardian_api_1_1_2.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Maven__org_hamcrest_hamcrest_2_2.xml b/.idea/libraries/Maven__org_hamcrest_hamcrest_2_2.xml new file mode 100644 index 0000000..6b5496f --- /dev/null +++ b/.idea/libraries/Maven__org_hamcrest_hamcrest_2_2.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Maven__org_junit_jupiter_junit_jupiter_5_8_2.xml b/.idea/libraries/Maven__org_junit_jupiter_junit_jupiter_5_8_2.xml new file mode 100644 index 0000000..4f367e7 --- /dev/null +++ b/.idea/libraries/Maven__org_junit_jupiter_junit_jupiter_5_8_2.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Maven__org_junit_jupiter_junit_jupiter_api_5_8_2.xml b/.idea/libraries/Maven__org_junit_jupiter_junit_jupiter_api_5_8_2.xml new file mode 100644 index 0000000..8e50783 --- /dev/null +++ b/.idea/libraries/Maven__org_junit_jupiter_junit_jupiter_api_5_8_2.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Maven__org_junit_jupiter_junit_jupiter_engine_5_8_2.xml b/.idea/libraries/Maven__org_junit_jupiter_junit_jupiter_engine_5_8_2.xml new file mode 100644 index 0000000..fc8291b --- /dev/null +++ b/.idea/libraries/Maven__org_junit_jupiter_junit_jupiter_engine_5_8_2.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Maven__org_junit_jupiter_junit_jupiter_params_5_8_2.xml b/.idea/libraries/Maven__org_junit_jupiter_junit_jupiter_params_5_8_2.xml new file mode 100644 index 0000000..4b3ffb1 --- /dev/null +++ b/.idea/libraries/Maven__org_junit_jupiter_junit_jupiter_params_5_8_2.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Maven__org_junit_platform_junit_platform_commons_1_8_2.xml b/.idea/libraries/Maven__org_junit_platform_junit_platform_commons_1_8_2.xml new file mode 100644 index 0000000..181d095 --- /dev/null +++ b/.idea/libraries/Maven__org_junit_platform_junit_platform_commons_1_8_2.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Maven__org_junit_platform_junit_platform_engine_1_8_2.xml b/.idea/libraries/Maven__org_junit_platform_junit_platform_engine_1_8_2.xml new file mode 100644 index 0000000..5f3aebe --- /dev/null +++ b/.idea/libraries/Maven__org_junit_platform_junit_platform_engine_1_8_2.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Maven__org_objenesis_objenesis_3_2.xml b/.idea/libraries/Maven__org_objenesis_objenesis_3_2.xml new file mode 100644 index 0000000..6613def --- /dev/null +++ b/.idea/libraries/Maven__org_objenesis_objenesis_3_2.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Maven__org_opentest4j_opentest4j_1_2_0.xml b/.idea/libraries/Maven__org_opentest4j_opentest4j_1_2_0.xml new file mode 100644 index 0000000..fbc1b16 --- /dev/null +++ b/.idea/libraries/Maven__org_opentest4j_opentest4j_1_2_0.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Maven__org_skyscreamer_jsonassert_1_5_1.xml b/.idea/libraries/Maven__org_skyscreamer_jsonassert_1_5_1.xml new file mode 100644 index 0000000..56582d6 --- /dev/null +++ b/.idea/libraries/Maven__org_skyscreamer_jsonassert_1_5_1.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Maven__org_slf4j_jul_to_slf4j_1_7_36.xml b/.idea/libraries/Maven__org_slf4j_jul_to_slf4j_1_7_36.xml new file mode 100644 index 0000000..5d5c14b --- /dev/null +++ b/.idea/libraries/Maven__org_slf4j_jul_to_slf4j_1_7_36.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Maven__org_slf4j_slf4j_api_1_7_36.xml b/.idea/libraries/Maven__org_slf4j_slf4j_api_1_7_36.xml new file mode 100644 index 0000000..2d759c1 --- /dev/null +++ b/.idea/libraries/Maven__org_slf4j_slf4j_api_1_7_36.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..c6f03cc --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,12 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/uiDesigner.xml b/.idea/uiDesigner.xml new file mode 100644 index 0000000..2b63946 --- /dev/null +++ b/.idea/uiDesigner.xml @@ -0,0 +1,124 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/vvPkAssistant.iml b/.idea/vvPkAssistant.iml new file mode 100644 index 0000000..d0876a7 --- /dev/null +++ b/.idea/vvPkAssistant.iml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/HELP.md b/HELP.md new file mode 100644 index 0000000..0e0c676 --- /dev/null +++ b/HELP.md @@ -0,0 +1,20 @@ +# Getting Started + +### Reference Documentation + +For further reference, please consider the following sections: + +* [Official Apache Maven documentation](https://maven.apache.org/guides/index.html) +* [Spring Boot Maven Plugin Reference Guide](https://docs.spring.io/spring-boot/docs/2.6.13/maven-plugin/reference/html/) +* [Create an OCI image](https://docs.spring.io/spring-boot/docs/2.6.13/maven-plugin/reference/html/#build-image) +* [Spring Web](https://docs.spring.io/spring-boot/docs/2.6.13/reference/htmlsingle/#web) + +### Guides + +The following guides illustrate how to use some features concretely: + +* [Building a RESTful Web Service](https://spring.io/guides/gs/rest-service/) +* [Serving Web Content with Spring MVC](https://spring.io/guides/gs/serving-web-content/) +* [Building REST services with Spring](https://spring.io/guides/tutorials/rest/) +* [Accessing data with MySQL](https://spring.io/guides/gs/accessing-data-mysql/) + diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..63cf238 --- /dev/null +++ b/pom.xml @@ -0,0 +1,150 @@ + + + 4.0.0 + com.vv.pk.assistant + vvPkAssistant + 0.0.1 + vvPkAssistant + vvPkAssistant + + 1.8 + UTF-8 + UTF-8 + 2.7.12 + + + + org.springframework.boot + spring-boot-starter-web + + + + com.mysql + mysql-connector-j + runtime + + + + org.projectlombok + lombok + true + + + + org.springframework.boot + spring-boot-starter-test + test + + + + ch.qos.logback + logback-classic + 1.2.13 + + + + ch.qos.logback + logback-core + 1.2.13 + + + + + com.baomidou + mybatis-plus-boot-starter + 3.5.6 + + + + org.springframework.boot + spring-boot-configuration-processor + true + + + + com.fasterxml.jackson.core + jackson-core + 2.15.0 + + + + + com.squareup.okhttp3 + okhttp + 4.9.3 + + + + + com.google.code.gson + gson + 2.8.9 + + + + + org.apache.logging.log4j + log4j-api + 2.20.0 + + + + org.apache.logging.log4j + log4j-core + 2.20.0 + + + + + com.qcloud + cos_api + 5.6.227 + + + + + + + org.springframework.boot + spring-boot-dependencies + ${spring-boot.version} + pom + import + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.1 + + 1.8 + 1.8 + UTF-8 + + + + org.springframework.boot + spring-boot-maven-plugin + ${spring-boot.version} + + vvpkassistant.Application + false + + + + repackage + + repackage + + + + + + + + diff --git a/src/main/java/vvpkassistant/Anchors/AnchorDao.java b/src/main/java/vvpkassistant/Anchors/AnchorDao.java new file mode 100644 index 0000000..cfa0d9f --- /dev/null +++ b/src/main/java/vvpkassistant/Anchors/AnchorDao.java @@ -0,0 +1,22 @@ +package vvpkassistant.Anchors; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; +import org.apache.ibatis.annotations.Select; + +import java.util.List; + +@Mapper +public interface AnchorDao extends BaseMapper { + + // 根据用户id和主播id查询数据 + @Select("select count(*) from anchors where anchor_id = #{anchorId} and create_user_id = #{userId}") + int selectAnchorWithAnchorIdAndUserId(@Param("anchorId") String anchorId, @Param("userId") int userId); + + // 查询属于我的主播列表 + @Select("select * from anchors where create_user_id = #{userId} order by id desc") + List selectMyAnchor(@Param("userId") Integer userId); + + +} diff --git a/src/main/java/vvpkassistant/Anchors/AnchorModel.java b/src/main/java/vvpkassistant/Anchors/AnchorModel.java new file mode 100644 index 0000000..9c0f8ef --- /dev/null +++ b/src/main/java/vvpkassistant/Anchors/AnchorModel.java @@ -0,0 +1,18 @@ +package vvpkassistant.Anchors; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +@Data +@TableName("anchors") +public class AnchorModel { + @TableId(type = IdType.AUTO) + private Integer id; + private String anchorId; // 主播id + private String headerIcon; // 主播头像 + private Integer gender; // 主播性别 + private String country; // 主播国家 + private Integer createUserId; // 创建者的用户id +} diff --git a/src/main/java/vvpkassistant/Anchors/AnchorsController.java b/src/main/java/vvpkassistant/Anchors/AnchorsController.java new file mode 100644 index 0000000..83ca82b --- /dev/null +++ b/src/main/java/vvpkassistant/Anchors/AnchorsController.java @@ -0,0 +1,78 @@ +package vvpkassistant.Anchors; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import vvpkassistant.Data.ResponseData; +import vvpkassistant.Data.ResponseInfo; +import vvpkassistant.pk.PkRecordDao; + +import java.util.List; +import java.util.Map; + +@RestController +@RequestMapping("anchor") +public class AnchorsController { + + @Autowired + private AnchorDao anchorDao; + + @Autowired + private PkRecordDao recordDao; + + // 添加新主播 + @PostMapping("add") + public ResponseData addNewAnchor(@RequestBody AnchorModel model) { + //查询是否存在重复主播 + int r = anchorDao.selectAnchorWithAnchorIdAndUserId(model.getAnchorId(), model.getCreateUserId()); + if (r != 0) { + return ResponseData.error(ResponseInfo.ERROR,"该主播已存在"); + } + int insert = anchorDao.insert(model); + return insert == 1 ? ResponseData.success("") : ResponseData.error(ResponseInfo.ERROR,"添加失败"); + } + + // 查询我的主播列表 + @PostMapping("list") + public ResponseData myAnchorList(@RequestBody Map map) { + Integer userId = map.get("id"); + List anchorModels = anchorDao.selectMyAnchor(userId); + return ResponseData.success(anchorModels); + } + + // 删除我的主播 + @PostMapping("deleteMyAnchor") + public ResponseData deleteMyAnchor(@RequestBody Map map) { + Integer id = map.get("id"); + AnchorModel anchorModel = anchorDao.selectById(id); + try { + String anchorId = anchorModel.getAnchorId(); + // 根据主播id查询该主播是否存在pk记录 + int i = recordDao.existsPkRecordByAnchor(anchorId); + if (i > 0) { + return ResponseData.error(ResponseInfo.ERROR,"该主播已有pk记录。无法删除"); + } + int r = anchorDao.deleteById(id); + return r == 1 ? ResponseData.success("") : ResponseData.error(ResponseInfo.ERROR,null); + } catch (Exception e) { + return ResponseData.error(ResponseInfo.ERROR,"非法数据,操作失败"); + } + } + + // 更新主播信息 + @PostMapping("updateAnchorInfo") + public ResponseData updateAnchorInfo(@RequestBody AnchorModel model) { + // 查询该主播是否存在记录 + int i = recordDao.existsPkRecordByAnchor(model.getAnchorId()); + if (i > 0) { + return ResponseData.error(ResponseInfo.ERROR,"该主播已有pk记录。无法修改信息"); + } + int r = anchorDao.updateById(model); + return r == 1 ? ResponseData.success("") : ResponseData.error(ResponseInfo.ERROR,null); + } + + + +} diff --git a/src/main/java/vvpkassistant/Application.java b/src/main/java/vvpkassistant/Application.java new file mode 100644 index 0000000..8336a0b --- /dev/null +++ b/src/main/java/vvpkassistant/Application.java @@ -0,0 +1,12 @@ +package vvpkassistant; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class Application { + + public static void main(String[] args) { + SpringApplication.run(Application.class, args); + } + +} diff --git a/src/main/java/vvpkassistant/CoinRecords/CoinRecords.java b/src/main/java/vvpkassistant/CoinRecords/CoinRecords.java new file mode 100644 index 0000000..854888e --- /dev/null +++ b/src/main/java/vvpkassistant/CoinRecords/CoinRecords.java @@ -0,0 +1,31 @@ +package vvpkassistant.CoinRecords; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +@Data +@TableName("coin_records") +public class CoinRecords { + + @TableId(type = IdType.AUTO) + private Integer id; // 主键 + private String info; // 具体信息 + private Integer userId; // 用户id + private int time; // 时间 + private Integer number; // 变更数量 + private Integer status; // 0 减少 1 增加 + + + public CoinRecords() {} + + // 遍历构造 + public CoinRecords(String info, Integer userId, Integer number, Integer time, Integer status) { + this.info = info; + this.userId = userId; + this.number = number; + this.time = time; + this.status = status; + } + +} diff --git a/src/main/java/vvpkassistant/CoinRecords/CoinRecordsDao.java b/src/main/java/vvpkassistant/CoinRecords/CoinRecordsDao.java new file mode 100644 index 0000000..8f5a327 --- /dev/null +++ b/src/main/java/vvpkassistant/CoinRecords/CoinRecordsDao.java @@ -0,0 +1,14 @@ +package vvpkassistant.CoinRecords; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; +import org.apache.ibatis.annotations.Select; +import java.util.List; + +@Mapper +public interface CoinRecordsDao extends BaseMapper { + + @Select("select * from coin_records where user_id = #{userId} order by id desc limit #{page} , #{size}") + List fetchMyPointsData(@Param("userId") Integer userId, @Param("page") Integer page, @Param("size") Integer size); +} diff --git a/src/main/java/vvpkassistant/Data/ResponseData.java b/src/main/java/vvpkassistant/Data/ResponseData.java new file mode 100644 index 0000000..a4b1c06 --- /dev/null +++ b/src/main/java/vvpkassistant/Data/ResponseData.java @@ -0,0 +1,36 @@ +package vvpkassistant.Data; +import lombok.Data; +import org.springframework.lang.Nullable; + +@Data +public class ResponseData { + + // 状态码 + T data = null; + public String msg; + public Integer code; + + ResponseData() {} + + // 成功的返回方法 + public static ResponseData success(@Nullable T param) { + ResponseInfo info = ResponseInfo.SUCCESS; + ResponseData data = new ResponseData<>(); + data.setData(param); + data.setCode(info.getCode()); + data.setMsg(info.getDesc()); + return data; + } + + // 返回错误的方法 + public static ResponseData error(ResponseInfo info, @Nullable String msg) { + ResponseData data = new ResponseData<>(); + data.msg = msg != null ? msg : info.getDesc(); + data.code = info.getCode(); + data.data = null; + return data; + } + + + +} diff --git a/src/main/java/vvpkassistant/Data/ResponseInfo.java b/src/main/java/vvpkassistant/Data/ResponseInfo.java new file mode 100644 index 0000000..b86a0cc --- /dev/null +++ b/src/main/java/vvpkassistant/Data/ResponseInfo.java @@ -0,0 +1,26 @@ +package vvpkassistant.Data; + +public enum ResponseInfo { + + // 成功 + SUCCESS(200, "操作成功"), + ERROR(1001, "操作失败"); + + private final int code; // 状态码 + private final String desc; // 描述 + + // 构造方法(默认是private) + ResponseInfo(int code, String desc) { + this.code = code; + this.desc = desc; + } + + // Getter方法 + public int getCode() { + return code; + } + + public String getDesc() { + return desc; + } +} diff --git a/src/main/java/vvpkassistant/Data/WxChatParam.java b/src/main/java/vvpkassistant/Data/WxChatParam.java new file mode 100644 index 0000000..6503c85 --- /dev/null +++ b/src/main/java/vvpkassistant/Data/WxChatParam.java @@ -0,0 +1,15 @@ +package vvpkassistant.Data; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +@Data +@Component +@ConfigurationProperties("chat") +public class WxChatParam { + private String appId; + private String appKey; +} + + diff --git a/src/main/java/vvpkassistant/Data/WxParam.java b/src/main/java/vvpkassistant/Data/WxParam.java new file mode 100644 index 0000000..8a9c762 --- /dev/null +++ b/src/main/java/vvpkassistant/Data/WxParam.java @@ -0,0 +1,8 @@ +package vvpkassistant.Data; +import lombok.Data; + +@Data +public class WxParam { + public String appId = "wx0af70c44ad1939e9"; + public String secret = "3c9a1d2cadbb402a5ec935d090695be9"; +} diff --git a/src/main/java/vvpkassistant/FunctionConfig/FunctionConfigController.java b/src/main/java/vvpkassistant/FunctionConfig/FunctionConfigController.java new file mode 100644 index 0000000..b8babc4 --- /dev/null +++ b/src/main/java/vvpkassistant/FunctionConfig/FunctionConfigController.java @@ -0,0 +1,57 @@ +package vvpkassistant.FunctionConfig; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; +import vvpkassistant.Data.ResponseData; +import vvpkassistant.Data.ResponseInfo; + +@RestController +@RequestMapping("config") +public class FunctionConfigController { + + @Autowired + private FunctionConfigMapper configMapper; + + // 获取所有配置 + @GetMapping("getAllConfig") + public ResponseData getAllConfig() { + return ResponseData.success(FunctionConfigHolder.CONFIGS); + } + + // 更新配置项内容 + @PostMapping("updateConfigValue") + public ResponseData updateConfigValue(@RequestBody FunctionConfigModel model) { + // 1. 更新数据库 + configMapper.updateById(model); + // 2. 更新内存 + FunctionConfigHolder.CONFIGS.removeIf(c -> model.getFunctionName().equals(c.getFunctionName())); + FunctionConfigHolder.CONFIGS.add(model); + return ResponseData.success(""); + } + + @PostMapping("add") + public ResponseData addNewConfig(@RequestBody FunctionConfigModel newModel) { + String name = newModel.getFunctionName(); + boolean isDuplicate = FunctionConfigHolder.CONFIGS.stream() + .anyMatch(config -> name.equals(config.getFunctionName())); + if (isDuplicate) { + return ResponseData.error(ResponseInfo.ERROR,"配置名称重复"); + }else { + configMapper.insert(newModel); + FunctionConfigHolder.CONFIGS.add(newModel); + return ResponseData.success(""); + } + } + + @PostMapping("deleteConfigById") + public ResponseData deleteConfigById(@RequestBody FunctionConfigModel model) { + int i = configMapper.deleteById(model); + if (i == 1) { + FunctionConfigHolder.CONFIGS.removeIf(c -> model.getId().equals(c.getId())); + return ResponseData.success(""); + }else { + return ResponseData.error(ResponseInfo.ERROR,null); + } + } + + +} diff --git a/src/main/java/vvpkassistant/FunctionConfig/FunctionConfigHolder.java b/src/main/java/vvpkassistant/FunctionConfig/FunctionConfigHolder.java new file mode 100644 index 0000000..8aabfe7 --- /dev/null +++ b/src/main/java/vvpkassistant/FunctionConfig/FunctionConfigHolder.java @@ -0,0 +1,42 @@ +package vvpkassistant.FunctionConfig; +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; + +import javax.annotation.PostConstruct; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; + +@Component +@RequiredArgsConstructor +public class FunctionConfigHolder { + // 线程安全的全局配置容器 + static final List CONFIGS = new CopyOnWriteArrayList<>(); + + @Autowired + private FunctionConfigMapper configMapper; + + /** + * 启动时加载所有配置到内存 + */ + @PostConstruct + public void init() { + List dbConfigs = configMapper.selectList(null); + CONFIGS.clear(); + CONFIGS.addAll(dbConfigs); + System.out.println("已加载 "+CONFIGS.size()+" 条功能配置"); + } + + /** + * 按功能名获取配置值 + */ + public static String getValue(String functionName) { + return CONFIGS.stream() + .filter(c -> functionName.equals(c.getFunctionName())) + .findFirst() + .map(FunctionConfigModel::getFunctionValue) + .orElse(null); + } + +} diff --git a/src/main/java/vvpkassistant/FunctionConfig/FunctionConfigMapper.java b/src/main/java/vvpkassistant/FunctionConfig/FunctionConfigMapper.java new file mode 100644 index 0000000..e5e6401 --- /dev/null +++ b/src/main/java/vvpkassistant/FunctionConfig/FunctionConfigMapper.java @@ -0,0 +1,10 @@ +package vvpkassistant.FunctionConfig; + + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface FunctionConfigMapper extends BaseMapper { + +} diff --git a/src/main/java/vvpkassistant/FunctionConfig/FunctionConfigModel.java b/src/main/java/vvpkassistant/FunctionConfig/FunctionConfigModel.java new file mode 100644 index 0000000..925a7fc --- /dev/null +++ b/src/main/java/vvpkassistant/FunctionConfig/FunctionConfigModel.java @@ -0,0 +1,15 @@ +package vvpkassistant.FunctionConfig; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +@Data +@TableName("function_configuration") +public class FunctionConfigModel { + @TableId(type = IdType.AUTO) + private Integer id; // 主键 + private String functionName; // 功能名称 + private String functionValue; // 设置的值 +} diff --git a/src/main/java/vvpkassistant/SystemMessage/SystemMessage.java b/src/main/java/vvpkassistant/SystemMessage/SystemMessage.java new file mode 100644 index 0000000..4dd7082 --- /dev/null +++ b/src/main/java/vvpkassistant/SystemMessage/SystemMessage.java @@ -0,0 +1,14 @@ +package vvpkassistant.SystemMessage; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import lombok.Data; + +@Data +public class SystemMessage { + @TableId(type = IdType.AUTO) + private Integer id; // 主键 + private String content; // 内容 + private String title; // 标题 + private String time; // 创建时间 +} diff --git a/src/main/java/vvpkassistant/SystemMessage/SystemMessageController.java b/src/main/java/vvpkassistant/SystemMessage/SystemMessageController.java new file mode 100644 index 0000000..7e90e3e --- /dev/null +++ b/src/main/java/vvpkassistant/SystemMessage/SystemMessageController.java @@ -0,0 +1,49 @@ +package vvpkassistant.SystemMessage; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import vvpkassistant.Data.ResponseData; +import vvpkassistant.Tools.VVTools; + +import java.util.List; +import java.util.Map; + +@RestController +@RequestMapping("systemMessage") +public class SystemMessageController { + + @Autowired + private SystemMessageDao messageDao; + + // 获取列表 + @PostMapping("list") + public ResponseData messageList(@RequestBody Map map) { + Integer page = map.get("page"); + Integer size = map.get("size"); + List systemMessages = messageDao.messageList(page * size, size); + return ResponseData.success(systemMessages); + } + + // 增加 + @PostMapping("add") + public ResponseData createNewMessage(@RequestBody SystemMessage message) { + message.setTime(VVTools.getCurrentTime("yyyy-MM-dd HH:mm")); + return ResponseData.success(messageDao.insert(message)); + } + + // 修改 + @PostMapping("update") + public ResponseData updateMessage(@RequestBody SystemMessage message) { + return ResponseData.success(messageDao.updateById(message)); + } + + // 删除 + @PostMapping("delete") + public ResponseData delete(@RequestBody SystemMessage message) { + return ResponseData.success(messageDao.deleteById(message)); + } + +} diff --git a/src/main/java/vvpkassistant/SystemMessage/SystemMessageDao.java b/src/main/java/vvpkassistant/SystemMessage/SystemMessageDao.java new file mode 100644 index 0000000..73e559c --- /dev/null +++ b/src/main/java/vvpkassistant/SystemMessage/SystemMessageDao.java @@ -0,0 +1,18 @@ +package vvpkassistant.SystemMessage; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; +import org.apache.ibatis.annotations.Select; + +import java.util.List; + +@Mapper +public interface SystemMessageDao extends BaseMapper { + + // 查询消息列表 + @Select("select * from system_message order by id desc limit #{page} , #{size}") + List messageList(@Param("page")Integer page, @Param("size") Integer size); + + +} diff --git a/src/main/java/vvpkassistant/Tools/COSSigner.java b/src/main/java/vvpkassistant/Tools/COSSigner.java new file mode 100644 index 0000000..bfb19f3 --- /dev/null +++ b/src/main/java/vvpkassistant/Tools/COSSigner.java @@ -0,0 +1,276 @@ +package vvpkassistant.Tools;/* + * Copyright 2010-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + + * According to cos feature, we modify some class,comment, field name, etc. + */ + + +import static com.qcloud.cos.auth.COSSignerConstants.LINE_SEPARATOR; +import static com.qcloud.cos.auth.COSSignerConstants.Q_AK; +import static com.qcloud.cos.auth.COSSignerConstants.Q_HEADER_LIST; +import static com.qcloud.cos.auth.COSSignerConstants.Q_KEY_TIME; +import static com.qcloud.cos.auth.COSSignerConstants.Q_SIGNATURE; +import static com.qcloud.cos.auth.COSSignerConstants.Q_SIGN_ALGORITHM_KEY; +import static com.qcloud.cos.auth.COSSignerConstants.Q_SIGN_ALGORITHM_VALUE; +import static com.qcloud.cos.auth.COSSignerConstants.Q_SIGN_TIME; +import static com.qcloud.cos.auth.COSSignerConstants.Q_URL_PARAM_LIST; + +import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.TreeMap; + +import com.qcloud.cos.Headers; +import com.qcloud.cos.auth.AnonymousCOSCredentials; +import com.qcloud.cos.auth.COSCredentials; +import com.qcloud.cos.auth.COSSessionCredentials; +import com.qcloud.cos.exception.CosClientException; +import com.qcloud.cos.http.CosHttpRequest; +import com.qcloud.cos.http.HttpMethodName; +import com.qcloud.cos.internal.CosServiceRequest; +import com.qcloud.cos.utils.UrlEncoderUtils; + +import org.apache.commons.codec.digest.DigestUtils; +import org.apache.commons.codec.digest.HmacUtils; + +public class COSSigner { + private static Set needSignedHeaderSet = new HashSet<>(); + private Boolean isCIWorkflowRequest = false; + // Time offset between local and server + private int localTimeDelta = 0; + static { + needSignedHeaderSet.add("cache-control"); + needSignedHeaderSet.add("content-disposition"); + needSignedHeaderSet.add("content-encoding"); + needSignedHeaderSet.add("content-length"); + needSignedHeaderSet.add("content-md5"); + needSignedHeaderSet.add("content-type"); + needSignedHeaderSet.add("expect"); + needSignedHeaderSet.add("expires"); + needSignedHeaderSet.add("host"); + needSignedHeaderSet.add("if-match"); + needSignedHeaderSet.add("if-modified-since"); + needSignedHeaderSet.add("if-none-match"); + needSignedHeaderSet.add("if-unmodified-since"); + needSignedHeaderSet.add("origin"); + needSignedHeaderSet.add("range"); + needSignedHeaderSet.add("transfer-encoding"); + needSignedHeaderSet.add(Headers.PIC_OPERATIONS.toLowerCase()); + } + + private boolean isAnonymous(COSCredentials cred) { + return cred instanceof AnonymousCOSCredentials; + } + + public void sign(CosHttpRequest request, COSCredentials cred, Date expiredTime) { + if (isAnonymous(cred)) { + return; + } + + String authoriationStr = + buildAuthorizationStr(request.getHttpMethod(), request.getResourcePath(), + request.getHeaders(), request.getParameters(), cred, expiredTime, true); + + request.addHeader(Headers.COS_AUTHORIZATION, authoriationStr); + if (cred instanceof COSSessionCredentials) { + request.addHeader(Headers.SECURITY_TOKEN, + ((COSSessionCredentials) cred).getSessionToken()); + } + } + + public String buildPostObjectSignature(String secretKey, String keyTime, String policy) { + String signKey = HmacUtils.hmacSha1Hex(secretKey, keyTime); + String stringToSign = DigestUtils.sha1Hex(policy); + return HmacUtils.hmacSha1Hex(signKey, stringToSign); + } + + public String buildAuthorizationStr(HttpMethodName methodName, String resouce_path, + COSCredentials cred, + Date expiredTime) { + Date startTime = new Date(); + return buildAuthorizationStr(methodName, resouce_path, new HashMap<>(), new HashMap<>(), cred, startTime, expiredTime, true); + } + + public String buildAuthorizationStr(HttpMethodName methodName, String resouce_path, + Map headerMap, Map paramMap, COSCredentials cred, + Date expiredTime) { + Date startTime = new Date(); + return buildAuthorizationStr(methodName, resouce_path, headerMap, paramMap, + cred, startTime, expiredTime,true); + } + + public String buildAuthorizationStr(HttpMethodName methodName, String resouce_path, + Map headerMap, Map paramMap, COSCredentials cred, + Date expiredTime, Boolean signHost) { + Date startTime = new Date(); + return buildAuthorizationStr(methodName, resouce_path, headerMap, paramMap, + cred, startTime, expiredTime, signHost); + } + + public String buildAuthorizationStr(HttpMethodName methodName, String resouce_path, + Map headerMap, Map paramMap, COSCredentials cred, + Date startTime, Date expiredTime, Boolean signHost) { + if (isAnonymous(cred)) { + return null; + } + //万象工作流接口会出现uri带问号的情况 例如 /workflow/xxxxxx?active 这种情况?后面的参数不参与鉴权 + if (isCIWorkflowRequest){ + resouce_path = resouce_path.split("\\?")[0]; + } + + Map signHeaders = buildSignHeaders(headerMap, signHost); + // 签名中的参数和http 头部 都要进行字符串排序 + //对请求中的参数和http头部进行处理:对key先urlencode再小写处理,对value进行urlencode处理; + //生成 key 到 value 的映射 Map,根据key按照字典序排序 + TreeMap encodedSortedSignHeaders = buildEncodeSortedMemberMap(signHeaders); + TreeMap encodedSortedParams = buildEncodeSortedMemberMap(paramMap); + + //生成keylist + String qHeaderListStr = buildSignMemberStr(encodedSortedSignHeaders); + String qUrlParamListStr = buildSignMemberStr(encodedSortedParams); + + String qKeyTimeStr, qSignTimeStr; + qKeyTimeStr = qSignTimeStr = buildTimeStr(startTime, expiredTime); + String signKey = HmacUtils.hmacSha1Hex(cred.getCOSSecretKey(), qKeyTimeStr); + String formatMethod = methodName.toString().toLowerCase(); + String formatUri = resouce_path; + String formatParameters = formatMapToStr(encodedSortedParams); + String formatHeaders = formatMapToStr(encodedSortedSignHeaders); + + String formatStr = new StringBuilder().append(formatMethod).append(LINE_SEPARATOR) + .append(formatUri).append(LINE_SEPARATOR).append(formatParameters) + .append(LINE_SEPARATOR).append(formatHeaders).append(LINE_SEPARATOR).toString(); + String hashFormatStr = DigestUtils.sha1Hex(formatStr); + String stringToSign = new StringBuilder().append(Q_SIGN_ALGORITHM_VALUE) + .append(LINE_SEPARATOR).append(qSignTimeStr).append(LINE_SEPARATOR) + .append(hashFormatStr).append(LINE_SEPARATOR).toString(); + String signature = HmacUtils.hmacSha1Hex(signKey, stringToSign); + + String authoriationStr = new StringBuilder().append(Q_SIGN_ALGORITHM_KEY).append("=") + .append(Q_SIGN_ALGORITHM_VALUE).append("&").append(Q_AK).append("=") + .append(cred.getCOSAccessKeyId()).append("&").append(Q_SIGN_TIME).append("=") + .append(qSignTimeStr).append("&").append(Q_KEY_TIME).append("=").append(qKeyTimeStr) + .append("&").append(Q_HEADER_LIST).append("=").append(qHeaderListStr).append("&") + .append(Q_URL_PARAM_LIST).append("=").append(qUrlParamListStr).append("&") + .append(Q_SIGNATURE).append("=").append(signature).toString(); + return authoriationStr; + } + + public boolean needSignedHeader(String header) { + return needSignedHeaderSet.contains(header) || header.startsWith("x-cos-"); + } + + private Map buildSignHeaders(Map originHeaders, Boolean signHost) { + Boolean hasHost = false; + Map signHeaders = new HashMap<>(); + for (Entry headerEntry : originHeaders.entrySet()) { + String key = headerEntry.getKey().toLowerCase(); + + if (key.equals("host")) { + hasHost = true; + } + + if(needSignedHeader(key)) { + String value = headerEntry.getValue(); + signHeaders.put(key, value); + } + } + + if (!hasHost && signHost) { + String msg = String.format("buildAuthorization missing header: host. %s", originHeaders); + throw new CosClientException(msg); + } + + return signHeaders; + } + + private TreeMap buildEncodeSortedMemberMap(Map signElements){ + TreeMap encodeSortedSignElements = new TreeMap<>(); + + for (Entry header : signElements.entrySet()) { + if (header.getKey() == null) { + continue; + } + String encodeLowerKey = UrlEncoderUtils.encode(header.getKey().trim()).toLowerCase(); + String value = ""; + if (header.getValue()!=null){ + value = header.getValue().trim(); + } + String encodeValue = UrlEncoderUtils.encode(value); + encodeSortedSignElements.put(encodeLowerKey, encodeValue); + } + return encodeSortedSignElements; + } + + private String buildSignMemberStr(Map signHeaders) { + StringBuilder strBuilder = new StringBuilder(); + boolean seenOne = false; + for (String key : signHeaders.keySet()) { + if (!seenOne) { + seenOne = true; + } else { + strBuilder.append(";"); + } + strBuilder.append(key); + } + return strBuilder.toString(); + } + + private String formatMapToStr(Map kVMap) { + StringBuilder strBuilder = new StringBuilder(); + boolean seeOne = false; + for (Entry entry : kVMap.entrySet()) { + String key = entry.getKey(); + String value = entry.getValue(); + if (!seeOne) { + seeOne = true; + } else { + strBuilder.append("&"); + } + strBuilder.append(key).append("=").append(value); + } + return strBuilder.toString(); + } + + private String buildTimeStr(Date startTime, Date endTime) { + StringBuilder strBuilder = new StringBuilder(); + long startTimestamp = startTime.getTime() / 1000 + localTimeDelta; + long endTimestamp = endTime.getTime() / 1000 + localTimeDelta; + strBuilder.append(startTimestamp).append(";").append(endTimestamp); + return strBuilder.toString(); + } + + public static Set getNeedSignedHeaderSet() { + return needSignedHeaderSet; + } + + public static void setNeedSignedHeaderSet(Set needSignedHeaderSet) { + COSSigner.needSignedHeaderSet = needSignedHeaderSet; + } + + public void setCIWorkflowRequest(Boolean CIRequest) { + isCIWorkflowRequest = CIRequest; + } + + public int getLocalTimeDelta() { + return localTimeDelta; + } + + public void setLocalTimeDelta(int localTimeDelta) { + this.localTimeDelta = localTimeDelta; + } +} \ No newline at end of file diff --git a/src/main/java/vvpkassistant/Tools/LogUtil.java b/src/main/java/vvpkassistant/Tools/LogUtil.java new file mode 100644 index 0000000..b6cad23 --- /dev/null +++ b/src/main/java/vvpkassistant/Tools/LogUtil.java @@ -0,0 +1,38 @@ +package vvpkassistant.Tools; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +public class LogUtil { + // 获取 Logger 实例 + private static final Logger logger = LogManager.getLogger(LogUtil.class); + + // 封装不同级别的日志方法 + public static void trace(String message) { + logger.trace(message); + } + + public static void debug(String message) { + logger.debug(message); + } + + public static void info(String message) { + logger.info(message); + } + + public static void warn(String message) { + logger.warn(message); + } + + public static void error(String message) { + logger.error(message); + } + + public static void fatal(String message) { + logger.fatal(message); + } + + // 记录异常信息 + public static void error(String message, Throwable throwable) { + logger.error(message, throwable); + } +} diff --git a/src/main/java/vvpkassistant/Tools/OkHttpUtils.java b/src/main/java/vvpkassistant/Tools/OkHttpUtils.java new file mode 100644 index 0000000..87148f5 --- /dev/null +++ b/src/main/java/vvpkassistant/Tools/OkHttpUtils.java @@ -0,0 +1,151 @@ +package vvpkassistant.Tools; + +import com.google.gson.Gson; +import okhttp3.*; +import org.springframework.lang.Nullable; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +/** + * OkHttp + Gson 封装工具类 + * 功能: + * 1. 支持 GET/POST 请求 + * 2. 自动处理 JSON 转换(请求和响应) + * 3. 支持 Form 表单和 JSON Body 传参 + * 4. 可自定义超时时间和请求头 + */ +public class OkHttpUtils { + private static final OkHttpClient client; + private static final Gson gson = new Gson(); + private static final MediaType JSON = MediaType.parse("application/json; charset=utf-8"); + + // 初始化 OkHttpClient(可自定义超时时间) + static { + client = new OkHttpClient.Builder() + .connectTimeout(10, TimeUnit.SECONDS) // 连接超时 + .readTimeout(10, TimeUnit.SECONDS) // 读取超时 + .build(); + } + + // ------------------- GET 请求 ------------------- + /** + * GET 请求(返回字符串) + */ + public static String get(String url, Map params, Map headers) throws IOException { + Request request = buildGetRequest(url, params, headers); + return executeRequest(request); + } + + /** + * GET 请求(直接返回 Java 对象) + */ + public static T get(String url, Map params, Map headers, @Nullable Class clazz) throws IOException { + String json = get(url, params, headers); + return gson.fromJson(json, clazz); + } + + // ------------------- POST 请求 ------------------- + /** + * POST 请求(支持 Form 表单或 JSON Body,返回字符串) + * + * @param url 请求地址 + * @param data 请求数据(Map 或其他对象) + * @param headers 请求头 + * @param isForm 是否为 Form 表单提交 + * @return 响应字符串 + */ + public static String post(String url, Object data, Map headers, boolean isForm) throws IOException { + Request request = buildPostRequest(url, data, headers, isForm); + return executeRequest(request); + } + + /** + * POST 请求(直接返回 Java 对象) + * + * @param url 请求地址 + * @param data 请求数据(Map 或其他对象) + * @param headers 请求头 + * @param isForm 是否为 Form 表单提交 + * @param clazz 返回的对象类型 + * @return 响应的 Java 对象 + */ + public static T post(String url, Object data, Map headers, boolean isForm, Class clazz) throws IOException { + String json = post(url, data, headers, isForm); + return gson.fromJson(json, clazz); + } + + // ------------------- 私有方法 ------------------- + // 构建 GET 请求 + private static Request buildGetRequest(String url, Map params, Map headers) { + HttpUrl.Builder urlBuilder = HttpUrl.parse(url).newBuilder(); + if (params != null) { + params.forEach(urlBuilder::addQueryParameter); + } + Request.Builder builder = new Request.Builder().url(urlBuilder.build()); + addHeaders(builder, headers); + return builder.build(); + } + + // 构建 POST 请求 + private static Request buildPostRequest(String url, Object data, Map headers, boolean isForm) { + RequestBody requestBody; + + if (isForm) { + // Form 表单提交 + if (!(data instanceof Map)) { + throw new IllegalArgumentException("Form data must be of type Map"); + } + FormBody.Builder formBuilder = new FormBody.Builder(); + ((Map) data).forEach(formBuilder::add); + requestBody = formBuilder.build(); + + // 设置默认的 Content-Type 为 application/x-www-form-urlencoded + if (headers == null) { + headers = new HashMap<>(); // 初始化为空的 HashMap + headers.put("Content-Type", "application/x-www-form-urlencoded"); + } else if (!headers.containsKey("Content-Type")) { + headers.put("Content-Type", "application/x-www-form-urlencoded"); + } + } else { + // JSON Body 提交 + String json = gson.toJson(data); + requestBody = RequestBody.create(json, JSON); + + // 设置默认的 Content-Type 为 application/json + if (headers == null) { + headers = new HashMap<>(); // 初始化为空的 HashMap + headers.put("Content-Type", "application/json"); + } else if (!headers.containsKey("Content-Type")) { + headers.put("Content-Type", "application/json"); + } + } + + Request.Builder builder = new Request.Builder().url(url).post(requestBody); + addHeaders(builder, headers); + return builder.build(); + } + + // 添加请求头 + private static void addHeaders(Request.Builder builder, Map headers) { + if (headers != null) { + headers.forEach(builder::addHeader); + } + } + + // 执行请求并返回响应字符串 + private static String executeRequest(Request request) throws IOException { + try (Response response = client.newCall(request).execute()) { + if (!response.isSuccessful()) { + throw new IOException("Request failed: " + response.code() + ", " + response.message()); + } + ResponseBody responseBody = response.body(); + if (responseBody == null) { + throw new IOException("Response body is null"); + } + return responseBody.string(); + } + } +} diff --git a/src/main/java/vvpkassistant/Tools/VVRequester.java b/src/main/java/vvpkassistant/Tools/VVRequester.java new file mode 100644 index 0000000..47a644d --- /dev/null +++ b/src/main/java/vvpkassistant/Tools/VVRequester.java @@ -0,0 +1,194 @@ +package vvpkassistant.Tools; + +import com.google.gson.Gson; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import com.google.gson.reflect.TypeToken; +import lombok.SneakyThrows; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import vvpkassistant.Data.WxChatParam; +import vvpkassistant.Data.WxParam; +import vvpkassistant.User.UserModel; + +import java.io.BufferedReader; +import java.io.DataOutputStream; +import java.io.InputStreamReader; +import java.lang.reflect.Type; +import java.net.HttpURLConnection; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.util.*; +import java.util.Map; + +// 请求接口类 +@Component +public class VVRequester { + +// @Autowired +// private WxChatParam wxChatParam; + + // 登录小程序 + @SneakyThrows + public Map loginApp(String code) { + WxParam wx = new WxParam(); + Map requestMap = new HashMap<>(); + requestMap.put("appid",wx.getAppId()); + requestMap.put("secret", wx.getSecret()); + requestMap.put("js_code", code); + requestMap.put("grant_type", "authorization_code"); + Map data = OkHttpUtils.get("https://api.weixin.qq.com/sns/jscode2session",requestMap, null, Map.class); + return data; + } + + // 获取accessToken + @SneakyThrows + private String getAccessToken() { + WxParam wx = new WxParam(); + String requestUrl = "https://api.weixin.qq.com/cgi-bin/token"; + Map requestParam = new HashMap<>(); + requestParam.put("grant_type","client_credential"); + requestParam.put("appid",wx.getAppId()); + requestParam.put("secret",wx.getSecret()); + + Map result = OkHttpUtils.get(requestUrl, requestParam, null, Map.class); + String access_token = result.get("access_token").toString(); + return access_token; + } + + // 查询手机号 + @SneakyThrows + public String queryPhoneNumber(String code) { + + String requestUrl = "https://api.weixin.qq.com/wxa/business/getuserphonenumber?access_token=" + getAccessToken(); + Map param = new HashMap<>(); + param.put("code",code); + + // 将 Map 转换为 JSON 字符串 + Gson gson = new Gson(); + String jsonParam = gson.toJson(param); + + // 使用 OkHttpUtils 发送 POST 请求 + Map result = requestWeChatApi(requestUrl,"POST", jsonParam); + String phoneNumber = ""; + if (result.containsKey("phone_info")) { + String jsonString = new Gson().toJson(result); + JsonObject jsonObject = JsonParser.parseString(jsonString).getAsJsonObject(); + phoneNumber = jsonObject.getAsJsonObject("phone_info").get("phoneNumber").getAsString(); + } + return phoneNumber; + } + + + // 创建聊天账号 +// @SneakyThrows +// public Integer createChatAccount(UserModel userModel , String usersig) { +// long number = randomNumber(); +// +// String requestUrl = "https://console.tim.qq.com/v4/im_open_login_svc/account_import?sdkappid=" + wxChatParam.getAppId() + "&identifier=administrator&usersig=" + usersig + "&random=" + number + "&contenttype=json"; +// +// // 设置头像 +// String icon = "https://vv-1317974657.cos.ap-shanghai.myqcloud.com/headerIcon/" + userModel.getHeaderIcon(); +// +// Map requestParam = new HashMap<>(); +// requestParam.put("UserID",String.valueOf(userModel.getId())); +// requestParam.put("Nick",userModel.getNickName()); +// requestParam.put("FaceUrl",icon); +// +// Map result = OkHttpUtils.post(requestUrl,requestParam,null,false, Map.class); +// Integer errorCode = Integer.valueOf(result.get("ErrorCode").toString().split("\\.")[0]); +// System.out.println(result); +// return errorCode; +// } + + // 更新聊天账号的昵称和头像 +// @SneakyThrows +// public Integer updateChatAccountInfo(Map map) { +// +// Map param = new HashMap<>(); +// param.put("From_Account",map.get("id").toString()); +// List> list = new ArrayList<>(); +// +// Map nickNameMap = new HashMap<>(); +// nickNameMap.put("Tag","Tag_Profile_IM_Nick"); +// nickNameMap.put("value", map.get("nickName").toString()); +// +// Map imageMap = new HashMap<>(); +// imageMap.put("Tag","Tag_Profile_IM_Image"); +// imageMap.put("value", map.get("icon").toString()); +// +// list.add(nickNameMap); +// list.add(imageMap); +// param.put("ProfileItem", list); +// +// String userSig = map.get("usersig").toString(); +// +// long number = randomNumber(); +// +// String requestUrl = "https://console.tim.qq.com/v4/profile/portrait_set?sdkappid=" + wxChatParam.getAppId() + "&identifier=administrator&usersig=" + userSig + "&random=" + number + "&contenttype=json"; +// +// Map result = OkHttpUtils.post(requestUrl, param, null, false, Map.class); +// +// Integer errorCode = Integer.valueOf(result.get("ErrorCode").toString().split("\\.")[0]); +// return errorCode; +// } + + // 获取一个32位的无符号整数 + private long randomNumber() { + return (long) (Math.random() * (4294967295L + 1)); // 生成 0 到 4294967295 范围的随机数 + } + + + //请求微信API + public Map requestWeChatApi(String requestUrl, String method, String postParams) { + try { + URL url = new URL(requestUrl); + HttpURLConnection conn = (HttpURLConnection) url.openConnection(); + + // 根据方法类型设置属性 + if ("POST".equalsIgnoreCase(method)) { + conn.setRequestMethod("POST"); + // 设置请求头,假设我们发送的是表单数据 + conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded; application/json;"); + conn.setDoOutput(true); // 需要输出 + + // 写入参数 + try (DataOutputStream wr = new DataOutputStream(conn.getOutputStream())) { + if (postParams != null) { + wr.write(postParams.getBytes(StandardCharsets.UTF_8)); + wr.flush(); + } + } + } else { // 对于其他请求方法,如GET + conn.setRequestMethod(method); + } + + int responseCode = conn.getResponseCode(); + + if (responseCode == HttpURLConnection.HTTP_OK) { + BufferedReader in = new BufferedReader(new InputStreamReader(conn.getInputStream())); + String inputLine; + StringBuilder response = new StringBuilder(); + + while ((inputLine = in.readLine()) != null) { + response.append(inputLine); + } + in.close(); + + String keyValuePairs = response.toString(); + Gson gson = new Gson(); + JsonObject jsonObject = gson.fromJson(keyValuePairs, JsonObject.class); + + Type mapType = new TypeToken>(){}.getType(); + Map map = gson.fromJson(jsonObject, mapType); + return map; + } else { + System.out.println(method + " request not worked"); + } + } catch (Exception e) { + e.printStackTrace(); + } + return new HashMap<>(); + } + +} diff --git a/src/main/java/vvpkassistant/Tools/VVTools.java b/src/main/java/vvpkassistant/Tools/VVTools.java new file mode 100644 index 0000000..db45ec7 --- /dev/null +++ b/src/main/java/vvpkassistant/Tools/VVTools.java @@ -0,0 +1,128 @@ +package vvpkassistant.Tools; + +import lombok.Data; + +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; +import java.util.Arrays; +import java.util.Calendar; +import java.util.HashMap; +import java.util.Map; + +/************************ + * 工具类 * + ************************/ +public class VVTools { + + // 获取当前时间戳 + public static long currentTimeStamp() { + long timeStamp = Calendar.getInstance().getTimeInMillis() / 1000; + return timeStamp; + } + + // 查询当天所有的 + public static Map startAndEndTimeStampForToday() { + // 获取当前日期(不含时间) + LocalDate today = LocalDate.now(); + + // 当天的 00:00:00 + LocalDateTime startOfDay = today.atStartOfDay(); + long startTimestamp = startOfDay.atZone(ZoneId.systemDefault()).toEpochSecond(); + + // 当天的 23:59:59 + LocalDateTime endOfDay = today.atTime(23, 59, 59); + long endTimestamp = endOfDay.atZone(ZoneId.systemDefault()).toEpochSecond(); + + Map result = new HashMap<>(); + result.put("start",startTimestamp); + result.put("end", endTimestamp); + return result; + } + + /** + * 获取当前时间字符串 + * @param pattern 时间格式 + * @return 返回当前时间 + */ + public static String getCurrentTime(String pattern) { + DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern); + return LocalDateTime.now().format(formatter); + } + + /** + * 替换替换字符串 + * @param wordA 原文本 + * @param wordB 需要替换的文本 + */ + public static String replaceChar(String wordA, char wordB) { + + if (wordA == null || wordA.length() <= 2) { + return wordA; // 如果字符串太短(<=2个字符),直接返回 + } + + char[] chars = wordA.toCharArray(); + Arrays.fill(chars, 1, chars.length - 1, wordB); + return new String(chars); + } + + + /** + * 判断时间戳是否在当天内(基于系统默认时区) + * @param timestamp 毫秒级时间戳 + * @return true-在当天内,false-不在当天内 + */ + public static boolean isTimestampInToday(long timestamp) { + // 1. 获取当前时间戳对应的日期(系统默认时区) + LocalDate today = LocalDate.now(); + + // 2. 将目标时间戳转为日期(同一时区) + LocalDate targetDate = Instant.ofEpochMilli(timestamp) + .atZone(ZoneId.systemDefault()) + .toLocalDate(); + + // 3. 比较日期是否相同 + return targetDate.isEqual(today); + } + + // 根据传入的时间戳。计算出当天的开始时间和结束时间的时间戳 + public static Map getDayStartAndEndTimestamp(long timestamp) { + // 将10位时间戳转换为LocalDateTime + Instant instant = Instant.ofEpochSecond(timestamp); + LocalDateTime dateTime = LocalDateTime.ofInstant(instant, ZoneId.systemDefault()); + + // 获取当天的LocalDate + LocalDate date = dateTime.toLocalDate(); + + // 计算当天的开始时间(00:00:00) + LocalDateTime startOfDay = date.atStartOfDay(); + long startTimestamp = startOfDay.atZone(ZoneId.systemDefault()).toEpochSecond(); + + // 计算当天的结束时间(23:59:59) + LocalDateTime endOfDay = date.atTime(23, 59, 59); + long endTimestamp = endOfDay.atZone(ZoneId.systemDefault()).toEpochSecond(); + + // 创建并返回Map + Map result = new HashMap<>(); + result.put("start", startTimestamp); + result.put("end", endTimestamp); + + return result; + } + + + // 计算两个时间戳之间的小时数量 + public static long calculateHoursRound(long expireTime, long currentTime) { + if (expireTime <= currentTime) return 0; + long diffSeconds = expireTime - currentTime; + return diffSeconds / 3600; + } + + // 返还积分用(不足1小时忽略) + public static long calculateHoursFloor(long expireTime, long currentTime) { + if (expireTime <= currentTime) return 0; + return (expireTime - currentTime) / 3600; + } +} diff --git a/src/main/java/vvpkassistant/Tools/WebMvcConfig.java b/src/main/java/vvpkassistant/Tools/WebMvcConfig.java new file mode 100644 index 0000000..9ec2cc5 --- /dev/null +++ b/src/main/java/vvpkassistant/Tools/WebMvcConfig.java @@ -0,0 +1,17 @@ +package vvpkassistant.Tools; + +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.CorsRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +@Configuration +public class WebMvcConfig implements WebMvcConfigurer { + @Override + public void addCorsMappings(CorsRegistry registry) { + registry.addMapping("/**") // 所有接口 + .allowedOrigins("*") // 允许所有来源 + .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") // 允许的HTTP方法 + .allowedHeaders("*") // 允许所有头 + .maxAge(3600); // 预检请求缓存时间 + } +} \ No newline at end of file diff --git a/src/main/java/vvpkassistant/User/UserController.java b/src/main/java/vvpkassistant/User/UserController.java new file mode 100644 index 0000000..52f1336 --- /dev/null +++ b/src/main/java/vvpkassistant/User/UserController.java @@ -0,0 +1,363 @@ +package vvpkassistant.User; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; +import vvpkassistant.CoinRecords.CoinRecords; +import vvpkassistant.CoinRecords.CoinRecordsDao; +import vvpkassistant.Data.ResponseData; +import vvpkassistant.Data.ResponseInfo; +import vvpkassistant.Data.WxChatParam; +import vvpkassistant.FunctionConfig.FunctionConfigHolder; +import vvpkassistant.Tools.VVRequester; +import vvpkassistant.Tools.VVTools; +import vvpkassistant.pk.PkInfoDao; +import vvpkassistant.pk.PkInfoModel; +import vvpkassistant.pk.PkRecordDetail; +import vvpkassistant.pk.PkRecordDetailDao; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@RestController +@RequestMapping("user") +public class UserController { + + @Autowired + private UserDao userDao; + + @Autowired + private PkInfoDao pkInfoDao; + + @Autowired + private PkRecordDetailDao detailDao; + + @Autowired + private CoinRecordsDao coinRecordsDao; + + @Autowired + private WxChatParam wxChatParam; + + @Autowired + private VVRequester vvRequester; + + // 配置用户信息 + @PostMapping("inputUserInfo") + public ResponseData inputUserInfo(@RequestBody Map param) { + + if (!param.containsKey("code")) { + return ResponseData.error(ResponseInfo.ERROR, "code不能为空"); + } + + if (!param.containsKey("id")) { + return ResponseData.error(ResponseInfo.ERROR, "id不能为空"); + } + + //获取前端传递过来的code + String code = param.get("code").toString(); + + // 调用微信获取openid接口 + Map wx_result = vvRequester.loginApp(code); + + // 错误处理 + if (wx_result.containsKey("errcode")) { + Integer errcode = Integer.valueOf(wx_result.get("errcode").toString()); + + if (errcode == 45011) { + return ResponseData.error(ResponseInfo.ERROR, "API 调用太频繁,请稍候再试。"); + } + + if (errcode == 40029) { + return ResponseData.error(ResponseInfo.ERROR, "js_code 无效"); + } + + if (errcode == 40226) { + return ResponseData.error(ResponseInfo.ERROR, "该账号为风险用户。禁止登录小程序"); + } + + if (errcode == -1) { + return ResponseData.error(ResponseInfo.ERROR, "系统繁忙"); + } + + if (!wx_result.containsKey("openid")) { + return ResponseData.error(ResponseInfo.ERROR, "获取用户信息失败,请稍后再试。"); + } + } + + //查询用户 + String openId = wx_result.get("openid").toString(); + String sessionKey = wx_result.get("session_key").toString(); + + // 创建一个临时model + UserModel tempModel = new UserModel(); + tempModel.setId(Integer.valueOf(param.get("id").toString())); + tempModel.setOpenid(openId); + tempModel.setHeaderIcon(param.get("headerIcon").toString()); + tempModel.setNickName(param.get("nickName").toString()); + tempModel.setSessionKey(sessionKey); + tempModel.setUserChatId(openId); + int i = userDao.updateById(tempModel); + if (i == 1) { +// String usersig = param.get("usersig").toString(); +// // 注册聊天账号 +// Integer createAccountResult = vvRequester.createChatAccount(tempModel, usersig); +// if (createAccountResult != 0) { +// return ResponseData.error(ResponseInfo.ERROR,"创建聊天账号失败,请稍后再试"); +// } + Map result = new HashMap<>(); + result.put("info", tempModel); + result.put("newAccount",false); + //否则直接返回用户 + return ResponseData.success(result); + } + + return ResponseData.error(ResponseInfo.ERROR,"未知错误"); + } + + // 手机号登录 / 注册 + @PostMapping("loginWithPhoneNumber") + public ResponseData loginWithPhoneNumber(@RequestBody Map param) { + + if (!param.containsKey("code")) { + return ResponseData.error(ResponseInfo.ERROR,"code不能为空"); + } + + String code = param.get("code").toString(); + String phoneNumber = vvRequester.queryPhoneNumber(code); + + if (phoneNumber.isEmpty()) { + return ResponseData.error(ResponseInfo.ERROR,"手机号码无法查询"); + } + + // 查询是否存在用户。如果用户存在直接返回 如果用户不存在则新建用户 + UserModel model = userDao.queryWithPhoneNumber(phoneNumber); + Map result = new HashMap<>(); + if (model != null) { // 老用户 + result.put("info",model); + result.put("newAccount", false); + result.put("chatInfo",wxChatParam); + return ResponseData.success(result); + }else{ // 新用户 + UserModel tempModel = new UserModel(); + tempModel.setPhoneNumber(phoneNumber); + tempModel.setCreateTime(VVTools.currentTimeStamp()); + //设置状态为正常 + tempModel.setStatus(0); + //设置积分为0 + tempModel.setPoints(0); + userDao.insert(tempModel); + + // 判断用户是否为邀请用户 + if (param.containsKey("inviterId")) { + int inviterId = (int) param.get("inviterId"); + // 查询用户增加积分 + UserModel oldUser = userDao.selectById(inviterId); + oldUser.setPoints(oldUser.getPoints() + 10); + userDao.updateById(oldUser); + } + + result.put("info",tempModel); + result.put("newAccount",true); + result.put("chatInfo",wxChatParam); + return ResponseData.success(result); + + } + + } + + // 修改用户信息 + @PostMapping("updateUserInfo") + public ResponseData updateUserInfo(@RequestBody Map map) { + UserModel userModel = new UserModel(); + //设置用户id + userModel.setId(Integer.valueOf(map.get("id").toString())); + //设置用户头像 + userModel.setHeaderIcon(map.get("headerIcon").toString()); + //设置用户昵称 + userModel.setNickName(map.get("nickName").toString()); + + int i = userDao.updateById(userModel); + // 返回结果 + Map result = new HashMap<>(); + result.put("info", userDao.selectById(map.get("id").toString())); + result.put("newAccount", false); + return i == 1 ? ResponseData.success(result) : ResponseData.error(ResponseInfo.ERROR, ""); + } + + // 获取用户信息 + @PostMapping("getUserInfo") + public ResponseData getUserInfo(@RequestBody Map map) { + UserModel userModel = userDao.selectById(map.get("id")); + return ResponseData.success(userModel); + } + + // 查询用户所有pk数据 + @PostMapping("queryMyAllPkData") + public ResponseData queryMyAllPkData(@RequestBody Map map) { + Integer userId = map.get("userId"); + Integer page = map.get("page"); + Integer size = map.get("size"); + List pkInfoModels = pkInfoDao.queryAllPkData(userId, page * size, size); + long currentTimeStamp = VVTools.currentTimeStamp(); + // 查找置顶的数据 + for (PkInfoModel pkInfoModel : pkInfoModels) { + pkInfoModel.setIsPin(pkInfoModel.getPinExpireTime() > currentTimeStamp); + } + return ResponseData.success(pkInfoModels); + } + + // 查询我的pk记录 列表 + @PostMapping("handlePkInfo") + public ResponseData handlePkInfo(@RequestBody Map map) { + Integer type = map.get("type"); + Integer id = map.get("userId"); + Integer page = map.get("page"); + Integer size = map.get("size"); + + // 我发起的pk数据 + if (type == 1) { + return ResponseData.success(userDao.findCreatedPk(id, page * size, size)); + }else if (type == 2){ + // 别人邀请我的pk数据 + return ResponseData.success(userDao.getMyGuestPkList(id, page * size, size)); + } + return ResponseData.error(ResponseInfo.ERROR,"未知错误"); + } + + + // 查詢单条pk记录详情 + @PostMapping("pkRecordDetail") + public ResponseData pkRecordDetail(@RequestBody Map map) { + Integer id = map.get("id"); + List pkRecordDetails = detailDao.queryDetail(id); + return ResponseData.success(pkRecordDetails); + } + + // 签到 + @PostMapping("signIn") + public ResponseData signIn(@RequestBody Map map) { + Integer userId = map.get("userId"); + int i = userDao.checkSignStatus(userId); + if (i != 0) { + return ResponseData.error(ResponseInfo.ERROR,"当天已签到"); + } + + int result = userDao.signIn(userId); + UserModel userModel = userDao.selectById(userId); + int count = Integer.parseInt(FunctionConfigHolder.getValue("签到增加积分")); + + // 增加积分 + userModel.setPoints(userModel.getPoints() + count); + + // 入库 + userDao.updateById(userModel); + + if (result == 1) { + // 增加记录 + CoinRecords coinRecords = new CoinRecords("签到增加积分",userId,count, (int) VVTools.currentTimeStamp(),1); + coinRecordsDao.insert(coinRecords); + return ResponseData.success(null); + }else { + return ResponseData.error(ResponseInfo.ERROR,null); + } + } + + // 查询用户当天签到状态 + @GetMapping("checkSignStatus") + public ResponseData checkSignStatus(Integer userId) { + int i = userDao.checkSignStatus(userId); + return i == 0 ? ResponseData.success(true) : ResponseData.success(false); + } + + // 置顶文章 + @PostMapping("pinToTop") + public ResponseData pinToTop(@RequestBody Map map) { + // 文章id + Integer articleId = map.get("articleId"); + PkInfoModel pkInfoModel = pkInfoDao.selectById(articleId); + Integer userId = pkInfoModel.getSenderId(); + // 到期时间戳 + Integer pinExpireTime = map.get("pinExpireTime"); + + long currentTimeStamp = VVTools.currentTimeStamp(); + long hour = VVTools.calculateHoursRound(pinExpireTime, currentTimeStamp); + String coin = FunctionConfigHolder.getValue("置顶扣除积分"); + int totalCoin = (int) (Integer.parseInt(coin) * hour); + + UserModel userModel = userDao.selectById(userId); + if (userModel != null) { + // 扣除积分 更新数据 + Integer points = userModel.getPoints(); + if (points - totalCoin > 0) { + userModel.setPoints(userModel.getPoints() - totalCoin); + userDao.updateById(userModel); + // 设置置顶到期时间 + pkInfoModel.setPinExpireTime(pinExpireTime); + // 设置创建置顶的时间 + pkInfoModel.setPinCreateTime((int) VVTools.currentTimeStamp()); + // 更新pk文章数据 + int i = pkInfoDao.updateById(pkInfoModel); + if (i == 1) { + String info = String.format("置顶成功,扣除%d积分",totalCoin); + // 增加积分变动记录 + CoinRecords coinRecords = new CoinRecords("置顶扣除积分",userId,totalCoin, (int) VVTools.currentTimeStamp(),0); + coinRecordsDao.insert(coinRecords); + // 返回给前端数据 + return ResponseData.success(info); + }else { + return ResponseData.error(ResponseInfo.ERROR,null); + } + }else { + return ResponseData.error(ResponseInfo.ERROR,String.format("积分不足,需要%d积分",totalCoin)); + } + }else { + return ResponseData.error(ResponseInfo.ERROR,"用户不存在"); + } + } + + // 取消置顶 + @PostMapping("cancelPin") + public ResponseData cancelPin(@RequestBody Map map) { + Integer articleId = map.get("articleId"); + PkInfoModel pkInfoModel = pkInfoDao.selectById(articleId); + Integer pinExpireTime = pkInfoModel.getPinExpireTime(); + long hour = VVTools.calculateHoursFloor(pinExpireTime, VVTools.currentTimeStamp()); + + String coin = FunctionConfigHolder.getValue("置顶扣除积分"); + // 计算总积分。用于返还给用户 + int totalCoin = (int) (Integer.parseInt(coin) * hour); + + // 获取用户对象 + UserModel userModel = userDao.selectById(pkInfoModel.getSenderId()); + Integer points = userModel.getPoints(); + // 返还用户积分 + userModel.setPoints(points + totalCoin); + // 更新数据库 + userDao.updateById(userModel); + + // 重置置顶时间 + pkInfoModel.setPinExpireTime(0); + pkInfoModel.setPinCreateTime(0); + int i = pkInfoDao.updateById(pkInfoModel); + if (i == 1) { + // 添加积分更变相关记录 + CoinRecords coinRecords = new CoinRecords("取消置顶返还积分",pkInfoModel.getSenderId(),totalCoin, (int) VVTools.currentTimeStamp(),1); + coinRecordsDao.insert(coinRecords); + return ResponseData.success(String.format("操作成功,返还%d积分",totalCoin)); + }else { + return ResponseData.error(ResponseInfo.ERROR,null); + } + + } + + + // 获取积分明细 + @PostMapping("pointsDetail") + public ResponseData pointsDetail(@RequestBody Map map) { + Integer userId = map.get("userId"); + Integer page = map.get("page"); + Integer size = map.get("size"); + List coinRecords = coinRecordsDao.fetchMyPointsData(userId, page * size, size); + return ResponseData.success(coinRecords); + } + + +} diff --git a/src/main/java/vvpkassistant/User/UserDao.java b/src/main/java/vvpkassistant/User/UserDao.java new file mode 100644 index 0000000..a349b18 --- /dev/null +++ b/src/main/java/vvpkassistant/User/UserDao.java @@ -0,0 +1,34 @@ +package vvpkassistant.User; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.apache.ibatis.annotations.Insert; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; +import org.apache.ibatis.annotations.Select; +import vvpkassistant.pk.PkRecord; +import java.util.List; + +@Mapper +public interface UserDao extends BaseMapper { + + // 根据用户的手机号查询用户 + @Select("SELECT * FROM `user` WHERE phone_number = #{phoneNumber}") + UserModel queryWithPhoneNumber(@Param("phoneNumber") String phoneNumber); + + // 我邀请的pk数据 + @Select("SELECT * FROM pk_record WHERE user_id_b = #{userId} ORDER BY id DESC LIMIT #{page}, #{size};") + List getMyGuestPkList(@Param("userId") Integer userId , @Param("page") Integer page, @Param("size") Integer size); + + // 我发起的pk数据 + @Select("SELECT * FROM pk_record WHERE user_id_a = #{userId} ORDER BY id DESC LIMIT #{page}, #{size};") + List findCreatedPk(@Param("userId") Integer userId , @Param("page") Integer page, @Param("size") Integer size); + + // 签到 + @Insert("insert into `sign_in_records` set user_id = #{userId} , time = replace(current_date, '-', '')") + int signIn(@Param("userId") int userId); + + // 查询当天签到状态 + @Select("SELECT COUNT(*) FROM `sign_in_records` WHERE user_id = #{userId} AND time = REPLACE(CURDATE(), '-', '')") + int checkSignStatus(@Param("userId") int userId); + +} diff --git a/src/main/java/vvpkassistant/User/UserModel.java b/src/main/java/vvpkassistant/User/UserModel.java new file mode 100644 index 0000000..71fa030 --- /dev/null +++ b/src/main/java/vvpkassistant/User/UserModel.java @@ -0,0 +1,23 @@ +package vvpkassistant.User; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +@Data +@TableName("user") +public class UserModel { + @TableId(type = IdType.AUTO) + private Integer id; // 主键 + private String nickName; // 昵称 + private String phoneNumber; // 手机号码 + private String headerIcon; // 头像 + private String openid; // openid + private String sessionKey; // session key + private Integer status; // 用户状态 0 正常 其他业务逻辑待定 + private Long createTime; // 创建时间 + private String userChatId; // 聊天使用的id,使用微信的openid作为标识 + private Integer points; // 用户积分 + private Integer inviterId; // 邀请人id +} diff --git a/src/main/java/vvpkassistant/chat/ChatController.java b/src/main/java/vvpkassistant/chat/ChatController.java new file mode 100644 index 0000000..47fb51d --- /dev/null +++ b/src/main/java/vvpkassistant/chat/ChatController.java @@ -0,0 +1,43 @@ +package vvpkassistant.chat; + + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; +import vvpkassistant.Data.ResponseData; +import vvpkassistant.Data.ResponseInfo; + +import java.util.HashMap; +import java.util.Map; + +@RestController +@RequestMapping("chat") +public class ChatController { + + @Autowired + private ChatDao chatDao; + + //插入数据 + @PostMapping("add") + public ResponseData add(@RequestBody ChatModel model) { + int insert = chatDao.insert(model); + return insert == 1 ? ResponseData.success("") : ResponseData.error(ResponseInfo.ERROR,null); + } + + //根据ip查询数据 + @PostMapping("fetchWithKeyId") + public ResponseData query(@RequestBody ChatModel model) { + ChatModel chatModel = chatDao.selectModelWithKeyId(model.getKeyId()); + return ResponseData.success(chatModel); + } + + //接收im消息 + @PostMapping("receiveImMessage") + public Map receiveImMessage(@RequestBody Map data) { + System.out.println(data); + Map result = new HashMap<>(); + result.put("code",200); + result.put("content","success"); + return result; + } + +} diff --git a/src/main/java/vvpkassistant/chat/ChatDao.java b/src/main/java/vvpkassistant/chat/ChatDao.java new file mode 100644 index 0000000..638a983 --- /dev/null +++ b/src/main/java/vvpkassistant/chat/ChatDao.java @@ -0,0 +1,16 @@ +package vvpkassistant.chat; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; +import org.apache.ibatis.annotations.Select; + +@Mapper +public interface ChatDao extends BaseMapper { + + // 根据key_id 查询数据 + @Select("select * from pk_chat where key_id = #{keyId}") + ChatModel selectModelWithKeyId(@Param("keyId") String keyId); + + +} diff --git a/src/main/java/vvpkassistant/chat/ChatModel.java b/src/main/java/vvpkassistant/chat/ChatModel.java new file mode 100644 index 0000000..935ab5a --- /dev/null +++ b/src/main/java/vvpkassistant/chat/ChatModel.java @@ -0,0 +1,17 @@ +package vvpkassistant.chat; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +@Data +@TableName("pk_chat") +public class ChatModel { + + @TableId(type = IdType.AUTO) + private Integer id; // 主键 + private String messageInfo; // 消息内容 + private String keyId; // 消息唯一id + +} diff --git a/src/main/java/vvpkassistant/pk/PkController.java b/src/main/java/vvpkassistant/pk/PkController.java new file mode 100644 index 0000000..c1a2c49 --- /dev/null +++ b/src/main/java/vvpkassistant/pk/PkController.java @@ -0,0 +1,326 @@ +package vvpkassistant.pk; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; +import vvpkassistant.CoinRecords.CoinRecords; +import vvpkassistant.CoinRecords.CoinRecordsDao; +import vvpkassistant.Data.ResponseData; +import vvpkassistant.Data.ResponseInfo; +import vvpkassistant.FunctionConfig.FunctionConfigHolder; +import vvpkassistant.Tools.VVTools; +import vvpkassistant.User.UserDao; +import vvpkassistant.User.UserModel; + +import java.util.List; +import java.util.Map; + + +@RestController +@RequestMapping("pk") +public class PkController { + + @Autowired + private PkInfoDao pkDao; + + @Autowired + private PkRecordDao recordDao; + + @Autowired + private PkRecordDetailDao detailDao; + + @Autowired + private UserDao userDao; + + @Autowired + private CoinRecordsDao coinRecordsDao; + + // 创建pk数据 + @PostMapping("addPkData") + public ResponseData addPkData(@RequestBody PkInfoModel pkModel) { + + // 初始可邀请状态为0 + pkModel.setInviteStatus(0); + pkModel.setPinExpireTime(0); + + // 获取主播id + String anchorId = pkModel.getAnchorId(); + // 查询当天是否存在该主播发布的pk信息。 + Integer pkTime = pkModel.getPkTime(); + + // 根据设置的pk时间。查询出当天的开始时间和结束时间 + Map dayStartAndEndTimestamp = VVTools.getDayStartAndEndTimestamp(pkTime); + + Long start = dayStartAndEndTimestamp.get("start"); + Long end = dayStartAndEndTimestamp.get("end"); + + // 查询主播在当天是否有发布过pk信息 + List pkInfoModels = pkDao.selectDataWithAnchorIdAndTime(anchorId, start, end); + + // 判断该主播在当日是否已发布过pk信息 + if (pkInfoModels.size() > 0) { + return ResponseData.error(ResponseInfo.ERROR,"该主播当日已有pk信息"); + } + + int insert = pkDao.insert(pkModel); + return insert == 1 ? ResponseData.success(pkModel) : ResponseData.error(ResponseInfo.ERROR,null); + } + + // 更新pk信息 + @PostMapping("updatePkStatus") + public ResponseData updatePkStatus(@RequestBody PkRecord recordModel) { + + //如果是同意了pk邀请、则更新已发布的主播pk可邀请状态 + if (recordModel.getPkStatus() == 1) { + + // 查询出记录信息 + PkRecord pkRecordById = recordDao.singleRecord(recordModel.getId()); + + // 更新可邀请状态 + PkInfoModel pkInfoModelA = pkDao.selectById(pkRecordById.getPkIdA()); + pkInfoModelA.setInviteStatus(1); + + PkInfoModel pkInfoModelB = pkDao.selectById(pkRecordById.getPkIdB()); + pkInfoModelB.setInviteStatus(1); + + pkDao.updateById(pkInfoModelA); + pkDao.updateById(pkInfoModelB); + + // 如果有置顶的状态。需要取消置顶 + if (pkInfoModelA.getPinExpireTime() > VVTools.currentTimeStamp()) { + long hour = VVTools.calculateHoursFloor(pkInfoModelA.getPinExpireTime(),VVTools.currentTimeStamp()); + int coin = Integer.parseInt(FunctionConfigHolder.getValue("置顶扣除积分")); + int totalCoin = (int) (coin * hour); + + // 插入记录 + CoinRecords coinRecords = new CoinRecords("成功预约pk,自动取消置顶", pkInfoModelA.getSenderId(),totalCoin, (int) VVTools.currentTimeStamp(),1); + coinRecordsDao.insert(coinRecords); + + // 更新积分 + UserModel userModel = userDao.selectById(pkRecordById.getUserIdA()); + Integer points = userModel.getPoints(); + userModel.setPoints(points + totalCoin); + } + + if (pkInfoModelB.getPinExpireTime() > VVTools.currentTimeStamp()) { + long hour = VVTools.calculateHoursFloor(pkInfoModelB.getPinExpireTime(),VVTools.currentTimeStamp()); + int coin = Integer.parseInt(FunctionConfigHolder.getValue("置顶扣除积分")); + int totalCoin = (int) (coin * hour); + + // 插入记录 + CoinRecords coinRecords = new CoinRecords("成功预约pk,自动取消置顶", pkInfoModelB.getSenderId(),totalCoin, (int) VVTools.currentTimeStamp(),1); + coinRecordsDao.insert(coinRecords); + + // 更新积分 + UserModel userModel = userDao.selectById(pkRecordById.getUserIdB()); + Integer points = userModel.getPoints(); + userModel.setPoints(points + totalCoin); + } + } + + // 更新pk邀请记录 + int update = recordDao.updateById(recordModel); + return update == 1 ? ResponseData.success("") : ResponseData.error(ResponseInfo.ERROR,null); + } + + // 创建PK记录 + @PostMapping("createPkRecord") + public ResponseData createPkRecord(@RequestBody PkRecord record) { + + // 如果这两个邀约直播之间存在未处理的邀请记录。那就不允许继续发送pk邀请 + String anchorIdA = record.getAnchorIdA(); + String anchorIdB = record.getAnchorIdB(); + Integer dataCount = recordDao.getPendingInvitations(anchorIdA, anchorIdB); + + if (dataCount > 0) { + return ResponseData.error(ResponseInfo.ERROR,"已存在一条未处理的pk申请,不能重复发送,"); + } + + record.setPkStatus(0); + int insert = recordDao.insert(record); + return insert == 1 ? ResponseData.success(record) : ResponseData.error(ResponseInfo.ERROR,null); + } + + // pk列表 + @PostMapping("pkList") + public ResponseData pkList(@RequestBody Map map) { + + Integer page = (Integer) map.get("page"); + Integer size = (Integer) map.get("size"); + Map condition = (Map) map.get("condition"); + + Map todayTimeStampMap = VVTools.startAndEndTimeStampForToday(); + Long start = VVTools.currentTimeStamp(); + Long end = todayTimeStampMap.get("end"); + + List pkModels = pkDao.selectPkInfoByCondition(page * size, size, condition, start, end); + + long currentTimeStamp = VVTools.currentTimeStamp(); + + //如果传了用户id + if (map.containsKey("userId")) { + Long begin = VVTools.currentTimeStamp(); + Integer userId = Integer.valueOf(map.get("userId").toString()); + // 查询出当前用户大于等于今天的已接受邀请的pk数据 + List pkRecords = recordDao.fetchDataFromTodayWithUserId(userId, begin); + // 遍历查询出的数据。如果文章的id相同。就显示完整的主播名称, + for (PkInfoModel pkModel : pkModels) { + pkModel.setDisPlayId(pkModel.getAnchorId()); + // 设置是否为置顶 + pkModel.setIsPin(pkModel.getPinExpireTime() > currentTimeStamp); + // 如果文章的发布者不是当前登录主播。 + if (pkModel.getSenderId().intValue() != userId) { + boolean showId = false; + // 查找是否和当前登录的账号有pk关系。 + for (PkRecord pkRecord : pkRecords) { + // 如果当前用户是邀请pk的,并且对方已接受pk,也可以看到主播id + if (pkRecord.getUserIdB().intValue() == userId && + pkRecord.getPkIdA().intValue() == pkModel.getId().intValue() && + pkRecord.getPkStatus() == 1) { + showId = true; + break; + } + } + // 条件不满足则隐藏主播id' + if (!showId) { + pkModel.setDisPlayId(VVTools.replaceChar(pkModel.getAnchorId(), '*')); + } + } + } + }else{ + for (PkInfoModel pkModel : pkModels) { + // 设置是否为置顶 + pkModel.setIsPin(pkModel.getPinExpireTime() > currentTimeStamp); + pkModel.setDisPlayId(VVTools.replaceChar(pkModel.getAnchorId(), '*')); + } + } + + return ResponseData.success(pkModels); + } + + // 查询用户发布的大于当前时间的pk数据 + @PostMapping("queryMyCanUsePkData") + public ResponseData queryMyCanUsePkData(@RequestBody Map map) { + Long time = VVTools.currentTimeStamp(); + Integer userId = (Integer) map.get("userId"); + List pkModels = pkDao.queryCanUseData(userId, time); + return ResponseData.success(pkModels); + } + + //pk文章详情 + @PostMapping("pkInfoDetail") + public ResponseData pkInfoDetail(@RequestBody Map map) { + Integer id = map.get("id"); + Integer userId = map.get("userId"); + Integer from = map.get("from"); // 1 首页 2 聊天 + + PkInfoModel pkInfoModel = pkDao.selectById(id); + if (pkInfoModel == null) { + return ResponseData.error(ResponseInfo.ERROR,"该信息不存在"); + } + + if (from == 1) { + if (pkInfoModel.getPkTime() > VVTools.currentTimeStamp()) { + // 判断是否是自己发布的数据 如果不是,就隐藏主播id + if (pkInfoModel.getSenderId().equals(userId)) { + pkInfoModel.setDisPlayId(pkInfoModel.getAnchorId()); + } else { + // 查询是否存在未完成的pk记录 + Integer isHave = pkDao.checkIfUnfinishedPKExistsWithAnchor(userId, pkInfoModel.getAnchorId()); + if (isHave > 0) { + pkInfoModel.setDisPlayId(pkInfoModel.getAnchorId()); + }else { + pkInfoModel.setDisPlayId(VVTools.replaceChar(pkInfoModel.getAnchorId(), '*')); + } + } + return ResponseData.success(pkInfoModel); + }else { + return ResponseData.error(ResponseInfo.ERROR,"当前信息已无效"); + } + }else{ + Integer isHave = pkDao.checkIfUnfinishedPKExistsWithAnchor(userId, pkInfoModel.getAnchorId()); + if (isHave > 0) { + pkInfoModel.setDisPlayId(pkInfoModel.getAnchorId()); + }else { + pkInfoModel.setDisPlayId(VVTools.replaceChar(pkInfoModel.getAnchorId(), '*')); + } + return ResponseData.success(pkInfoModel); + } + } + + //删除自己的pk数据 (单个) + @PostMapping("deletePkDataWithId") + public ResponseData deletePkDataWithId(@RequestBody Map map) { + Integer id = map.get("id"); + PkInfoModel pkInfoModel = pkDao.selectById(id); + + if (pkInfoModel.getPinExpireTime() > VVTools.currentTimeStamp()) { + return ResponseData.error(ResponseInfo.ERROR,"该信息在置顶中。如要删除清先取消置顶"); + } + + Integer result = pkDao.deletePkDataWithId(id); + return result == 1 ? ResponseData.success("") : ResponseData.error(ResponseInfo.ERROR,"删除数据失败"); + } + + //修改pk信息内容 + @PostMapping("updatePkInfoById") + public ResponseData updatePkInfoById(@RequestBody PkInfoModel infoModel) { + int result = pkDao.updateById(infoModel); + return result == 1 ? ResponseData.success("") : ResponseData.error(ResponseInfo.ERROR,null); + } + + // 根据id查询pk记录 + @PostMapping("singleRecord") + public ResponseData singleRecord(@RequestBody PkRecord record) { + PkRecord data = recordDao.singleRecord(record.getId()); + return ResponseData.success(data); + } + + // 查询pk中每个场次的详细数据 + @PostMapping("fetchDetailPkDataWithId") + public ResponseData fetchDetailPkDataWithId(@RequestBody Map map) { + Integer id = map.get("id"); + List pkRecordDetails = recordDao.fetchDetailPkDataWithId(id); + return ResponseData.success(pkRecordDetails); + } + + // 根据用户id查询该用户已发布的未被邀请的主播列表 + @PostMapping("listUninvitedPublishedAnchorsByUserId") + public ResponseData listUninvitedPublishedAnchorsByUserId(@RequestBody Map map) { + Integer userId = map.get("userId"); + List pkInfoModels = pkDao.listUninvitedPublishedAnchorsByUserId(userId); + for (PkInfoModel pkInfoModel : pkInfoModels) { + pkInfoModel.setDisPlayId(VVTools.replaceChar(pkInfoModel.getAnchorId(),'*')); + } + return ResponseData.success(pkInfoModels); + } + + + /************************************************************************************************************************************/ + /** Python 相关接口 */ + /************************************************************************************************************************************/ + // Python那边需要的pk数据 + @PostMapping("pkListForPython") + public ResponseData pkDataListForPython() { + // 获取当日的开始时间和结束时间 + Map timeMap = VVTools.startAndEndTimeStampForToday(); + long start = timeMap.get("start"); + long end = timeMap.get("end"); + List result = recordDao.pkListForToday(start, end); + return ResponseData.success(result); + } + + // 更新pk结果信息 + @PostMapping("updatePkRecordInfo") + public ResponseData updatePkRecordInfo(@RequestBody PkRecord record) { + int i = recordDao.updateById(record); + return i == 1 ? ResponseData.success("") : ResponseData.error(ResponseInfo.ERROR,null); + } + + // 插入pk明细表数据 + @PostMapping("insertPkDetail") + public ResponseData insert(@RequestBody PkRecordDetail detail) { + detail.setCreateTime(VVTools.currentTimeStamp()); + int insert = detailDao.insert(detail); + return insert == 1 ? ResponseData.success("") : ResponseData.error(ResponseInfo.ERROR,null); + } + +} diff --git a/src/main/java/vvpkassistant/pk/PkInfoDao.java b/src/main/java/vvpkassistant/pk/PkInfoDao.java new file mode 100644 index 0000000..768f1ba --- /dev/null +++ b/src/main/java/vvpkassistant/pk/PkInfoDao.java @@ -0,0 +1,77 @@ +package vvpkassistant.pk; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.apache.ibatis.annotations.*; +import org.jetbrains.annotations.Nullable; + +import java.util.List; +import java.util.Map; + +@Mapper +public interface PkInfoDao extends BaseMapper { + + //根据条件筛选pk列表数据 + @Select("") + List selectPkInfoByCondition( + @Param("page") int page, + @Param("size") int size, + @Param("condition") Map condition, + @Param("todayStart") long todayStart, // 当天开始时间戳(00:00:00) + @Param("todayEnd") long todayEnd // 当天结束时间戳(23:59:59) + ); + + // 查询用户发布的大于当前时间的pk数据 + @Select("select * from pk_info where #{userId} = sender_id and #{time} <= pk_time and invite_status = 0;") + List queryCanUseData(@Param("userId") Integer userId , @Param("time") Long time); + + // 查询用户发布的所有pk数据 + @Select("select * from pk_info where sender_id = #{userId} order by id desc limit #{page}, #{size};") + List queryAllPkData(@Param("userId") Integer userId, @Param("page") Integer page, @Param("size") Integer size); + + // 根据id删除pk信息 + @Delete("delete from pk_info where id = #{id}") + Integer deletePkDataWithId(@Param("id") Integer id); + + //查询制定时间范围,制定主播的pk信息 + @Select("select * from pk_info where anchor_id = #{anchorId} and pk_time between #{startTime} and #{endTime}") + List selectDataWithAnchorIdAndTime(@Param("anchorId") String anchorId, @Param("startTime") long startTime , @Param("endTime") long endTime); + + // 置顶和取消置顶 + @Update("update pk_info set pin_expire_time = #{time} where id = #{id}") + int setPinTime(@Param("id") Integer id, @Param("time") @Nullable Integer time); + + // 根据用户id查询该用户已发布的未被邀请的主播列表 + @Select("select * from pk_info where sender_id = #{userId} and invite_status = 0 and pk_time > UNIX_TIMESTAMP();") + List listUninvitedPublishedAnchorsByUserId(@Param("userId") Integer userId); + + // 查询当前用户与该主播是否存在未完成的pk记录 + @Select("SELECT COUNT(*) FROM `pk_record`\n" + + "WHERE (user_id_a = #{userId} OR user_id_b = #{userId})\n" + + "AND (anchor_id_a = #{anchorId} OR anchor_id_b = #{anchorId})\n" + + "AND pk_status = 1\n" + + "AND pk_time > UNIX_TIMESTAMP()") + Integer checkIfUnfinishedPKExistsWithAnchor(@Param("userId") Integer userId, @Param("anchorId") String anchorId); + + +} diff --git a/src/main/java/vvpkassistant/pk/PkInfoModel.java b/src/main/java/vvpkassistant/pk/PkInfoModel.java new file mode 100644 index 0000000..962f715 --- /dev/null +++ b/src/main/java/vvpkassistant/pk/PkInfoModel.java @@ -0,0 +1,31 @@ +package vvpkassistant.pk; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +@Data +@TableName("pk_info") +public class PkInfoModel { + @TableId(type = IdType.AUTO) + private Integer id; // 主键,自动递增 + private String anchorId; // 主播ID + private Integer pkTime; // PK时间 + private String sex; // 性别 1 男 2 女 + private String country; // 国家 + private Integer coin; // 金币 + private String remark; // 描述 + private Integer senderId; // 发布人id + private Integer pkNumber; // pk场次 + private String anchorIcon; // 主播头像 + private Integer inviteStatus; // 可邀请状态 0 未邀请 1 已邀请 + private Integer pinExpireTime; // 文章置顶到期时间 + private Integer pinCreateTime; // 置顶创建时间 + @TableField(exist = false) + private String disPlayId; // 页面上显示的id + @TableField(exist = false) + private Boolean isPin; // 是否在置顶 + +} diff --git a/src/main/java/vvpkassistant/pk/PkRecord.java b/src/main/java/vvpkassistant/pk/PkRecord.java new file mode 100644 index 0000000..2ffa951 --- /dev/null +++ b/src/main/java/vvpkassistant/pk/PkRecord.java @@ -0,0 +1,27 @@ +package vvpkassistant.pk; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import lombok.Data; + +@Data +public class PkRecord { + + @TableId(type = IdType.AUTO) + private Integer id; // 数据库主键 + private Integer pkIdA; // 发起方的pk表id + private Integer pkIdB; // 被邀请方的pk表id + private Integer userIdA; // 发起人的小程序id + private Integer userIdB; // 邀请人的小程序id + private String anchorIdA; // 主播A的id + private String anchorIdB; // 主播B的id + private String anchorIconA; // 主播A的头像 + private String anchorIconB; // 主播B的头像 + private Integer userACoins; // 用户A的金币数量 + private String pkTime; // pk时间 + private Integer userBCoins; // 用户B的金币数量 + private Integer pkNumber; // pk场次 + private Integer winnerId; // 获胜的userid + private String winnerAnchorId; // 获胜主播的Tik Tok id + private Integer pkStatus; // 状态 0 未处理 1 同意 2 拒绝 + +} diff --git a/src/main/java/vvpkassistant/pk/PkRecordDao.java b/src/main/java/vvpkassistant/pk/PkRecordDao.java new file mode 100644 index 0000000..cc2a858 --- /dev/null +++ b/src/main/java/vvpkassistant/pk/PkRecordDao.java @@ -0,0 +1,38 @@ +package vvpkassistant.pk; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; +import org.apache.ibatis.annotations.Select; +import java.util.List; + + +@Mapper +public interface PkRecordDao extends BaseMapper { + + // 查询当天所有的pk数据 Python使用 + @Select("SELECT * FROM pk_record WHERE pk_time BETWEEN #{startTime} AND #{endTime} and pk_status = 1;") + List pkListForToday(@Param("startTime") long start, @Param("endTime") long end); + + // 查询大于等于今天的PK记录数据 + @Select("select * from pk_record where (user_id_a = #{userId} or user_id_b = #{userId}) and pk_time >= #{fromTime}") + List fetchDataFromTodayWithUserId(@Param("userId") Integer userId, @Param("fromTime") Long fromTime); + + // 查询是否存在未处理的邀请数据 + @Select("select count(*) from pk_record where anchor_id_a = #{anchorA} and anchor_id_b = #{anchorB} and pk_status = 0") + Integer getPendingInvitations(@Param("anchorA") String anchorA , @Param("anchorB") String anchorB); + + // 根据id查询记录详情 + @Select("select * from pk_record where id = #{id}") + PkRecord singleRecord(@Param("id") Integer id); + + + // 查询每场pk的详细数据 + @Select("select * from pk_record_detail where pk_record_id = #{id}") + List fetchDetailPkDataWithId(@Param("id") Integer id); + + // 查询主播是否存在pk记录 + @Select("select count(*) from pk_record where anchor_id_a = #{id} or anchor_id_b = #{id}") + int existsPkRecordByAnchor(@Param("id") String id); + +} diff --git a/src/main/java/vvpkassistant/pk/PkRecordDetail.java b/src/main/java/vvpkassistant/pk/PkRecordDetail.java new file mode 100644 index 0000000..b7f1f05 --- /dev/null +++ b/src/main/java/vvpkassistant/pk/PkRecordDetail.java @@ -0,0 +1,21 @@ +package vvpkassistant.pk; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import lombok.Data; + +@Data +public class PkRecordDetail { + + @TableId(type = IdType.AUTO) + private Integer id; + private Integer pkRecordId; // 记录表主id + private String winnerAnchorId; // 获胜主播ID + private Integer winnerId; // 胜利方用户id + private String anchorIdA; // 主播A + private Integer anchorCoinA; // 主播A的金币 + private Integer anchorCoinB; + private String anchorIdB; // 主播B + // 主播B的金币 + private Long createTime; // 数据创建时间 + +} diff --git a/src/main/java/vvpkassistant/pk/PkRecordDetailDao.java b/src/main/java/vvpkassistant/pk/PkRecordDetailDao.java new file mode 100644 index 0000000..9aea234 --- /dev/null +++ b/src/main/java/vvpkassistant/pk/PkRecordDetailDao.java @@ -0,0 +1,17 @@ +package vvpkassistant.pk; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; +import org.apache.ibatis.annotations.Select; + +import java.util.List; + +@Mapper +public interface PkRecordDetailDao extends BaseMapper { + + // 根据id查询对应的明细数据 + @Select("select * from pk_record_detail where pk_record_id = #{id}") + List queryDetail(@Param("id") Integer id); + +} diff --git a/src/main/resources/application-dev.yml b/src/main/resources/application-dev.yml new file mode 100644 index 0000000..8822912 --- /dev/null +++ b/src/main/resources/application-dev.yml @@ -0,0 +1,14 @@ + + +spring: + datasource: + driver-class-name: com.mysql.cj.jdbc.Driver + username: root + password: wfn53400 + url: jdbc:mysql://mysql.text.zhukeping.com:3326/vv_assistant?allowMultiQueries=true + + + +chat: + appId: 1600092688 + appKey: 9bb6df04907a8cff9292eee8d6b32158d008350198acba11607068d91cb65f66 \ No newline at end of file diff --git a/src/main/resources/application-local.yml b/src/main/resources/application-local.yml new file mode 100644 index 0000000..4b3c730 --- /dev/null +++ b/src/main/resources/application-local.yml @@ -0,0 +1,13 @@ + + +spring: + datasource: + driver-class-name: com.mysql.cj.jdbc.Driver + username: root + password: wfn53400 + url: jdbc:mysql://mysql.text.zhukeping.com:3326/vv_assistant?allowMultiQueries=true + + +chat: + appId: 1600092688 + appKey: 9bb6df04907a8cff9292eee8d6b32158d008350198acba11607068d91cb65f66 \ No newline at end of file diff --git a/src/main/resources/application-prd.yml b/src/main/resources/application-prd.yml new file mode 100644 index 0000000..89e0694 --- /dev/null +++ b/src/main/resources/application-prd.yml @@ -0,0 +1,13 @@ + + +spring: + datasource: + driver-class-name: com.mysql.cj.jdbc.Driver + username: root + password: niu995228 + url: jdbc:mysql://49.235.115.212:3336/vv_assistant + + +chat: + appId: 1600086767 + appKey: 4f240bf11de12b5d8fc596556953f4f732416456ba5bcdc30c689913def0fc35 \ No newline at end of file diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml new file mode 100644 index 0000000..5a53e53 --- /dev/null +++ b/src/main/resources/application.yml @@ -0,0 +1,9 @@ +server: + port: 8086 + +spring: + profiles: + active: dev + + + diff --git a/src/main/resources/log4j2.xml b/src/main/resources/log4j2.xml new file mode 100644 index 0000000..c537b9f --- /dev/null +++ b/src/main/resources/log4j2.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/src/test/java/vvpkassistant/ApplicationTests.java b/src/test/java/vvpkassistant/ApplicationTests.java new file mode 100644 index 0000000..a464631 --- /dev/null +++ b/src/test/java/vvpkassistant/ApplicationTests.java @@ -0,0 +1,22 @@ +package vvpkassistant; +import lombok.SneakyThrows; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import vvpkassistant.Data.WxChatParam; +import vvpkassistant.Tools.VVRequester; + + +@SpringBootTest +class ApplicationTests { + + @Autowired + private WxChatParam wxChatParam; + + @Test + @SneakyThrows + void contextLoads() { + System.out.println(wxChatParam); + } + +} diff --git a/target/classes/META-INF/spring-configuration-metadata.json b/target/classes/META-INF/spring-configuration-metadata.json new file mode 100644 index 0000000..a6de442 --- /dev/null +++ b/target/classes/META-INF/spring-configuration-metadata.json @@ -0,0 +1,22 @@ +{ + "groups": [ + { + "name": "chat", + "type": "vvpkassistant.Data.WxChatParam", + "sourceType": "vvpkassistant.Data.WxChatParam" + } + ], + "properties": [ + { + "name": "chat.app-id", + "type": "java.lang.String", + "sourceType": "vvpkassistant.Data.WxChatParam" + }, + { + "name": "chat.app-key", + "type": "java.lang.String", + "sourceType": "vvpkassistant.Data.WxChatParam" + } + ], + "hints": [] +} \ No newline at end of file diff --git a/target/classes/application-dev.yml b/target/classes/application-dev.yml new file mode 100644 index 0000000..8822912 --- /dev/null +++ b/target/classes/application-dev.yml @@ -0,0 +1,14 @@ + + +spring: + datasource: + driver-class-name: com.mysql.cj.jdbc.Driver + username: root + password: wfn53400 + url: jdbc:mysql://mysql.text.zhukeping.com:3326/vv_assistant?allowMultiQueries=true + + + +chat: + appId: 1600092688 + appKey: 9bb6df04907a8cff9292eee8d6b32158d008350198acba11607068d91cb65f66 \ No newline at end of file diff --git a/target/classes/application-local.yml b/target/classes/application-local.yml new file mode 100644 index 0000000..4b3c730 --- /dev/null +++ b/target/classes/application-local.yml @@ -0,0 +1,13 @@ + + +spring: + datasource: + driver-class-name: com.mysql.cj.jdbc.Driver + username: root + password: wfn53400 + url: jdbc:mysql://mysql.text.zhukeping.com:3326/vv_assistant?allowMultiQueries=true + + +chat: + appId: 1600092688 + appKey: 9bb6df04907a8cff9292eee8d6b32158d008350198acba11607068d91cb65f66 \ No newline at end of file diff --git a/target/classes/application-prd.yml b/target/classes/application-prd.yml new file mode 100644 index 0000000..89e0694 --- /dev/null +++ b/target/classes/application-prd.yml @@ -0,0 +1,13 @@ + + +spring: + datasource: + driver-class-name: com.mysql.cj.jdbc.Driver + username: root + password: niu995228 + url: jdbc:mysql://49.235.115.212:3336/vv_assistant + + +chat: + appId: 1600086767 + appKey: 4f240bf11de12b5d8fc596556953f4f732416456ba5bcdc30c689913def0fc35 \ No newline at end of file diff --git a/target/classes/application.yml b/target/classes/application.yml new file mode 100644 index 0000000..5a53e53 --- /dev/null +++ b/target/classes/application.yml @@ -0,0 +1,9 @@ +server: + port: 8086 + +spring: + profiles: + active: dev + + + diff --git a/target/classes/log4j2.xml b/target/classes/log4j2.xml new file mode 100644 index 0000000..c537b9f --- /dev/null +++ b/target/classes/log4j2.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/target/maven-archiver/pom.properties b/target/maven-archiver/pom.properties new file mode 100644 index 0000000..fa8265c --- /dev/null +++ b/target/maven-archiver/pom.properties @@ -0,0 +1,5 @@ +#Generated by Maven +#Wed Jul 30 13:17:57 CST 2025 +version=0.0.1 +groupId=com.vv.pk.assistant +artifactId=vvPkAssistant diff --git a/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst b/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst new file mode 100644 index 0000000..fbfbf6f --- /dev/null +++ b/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst @@ -0,0 +1,38 @@ +vvpkassistant\FunctionConfig\FunctionConfigHolder.class +vvpkassistant\FunctionConfig\FunctionConfigMapper.class +vvpkassistant\User\UserModel.class +vvpkassistant\pk\PkInfoDao.class +vvpkassistant\pk\PkRecordDetail.class +META-INF\spring-configuration-metadata.json +vvpkassistant\Anchors\AnchorModel.class +vvpkassistant\CoinRecords\CoinRecordsDao.class +vvpkassistant\Data\ResponseData.class +vvpkassistant\SystemMessage\SystemMessageDao.class +vvpkassistant\SystemMessage\SystemMessage.class +vvpkassistant\Tools\VVRequester.class +vvpkassistant\Anchors\AnchorDao.class +vvpkassistant\FunctionConfig\FunctionConfigController.class +vvpkassistant\pk\PkRecord.class +vvpkassistant\Tools\OkHttpUtils.class +vvpkassistant\Tools\COSSigner.class +vvpkassistant\chat\ChatModel.class +vvpkassistant\pk\PkRecordDetailDao.class +vvpkassistant\Tools\VVRequester$1.class +vvpkassistant\Data\WxChatParam.class +vvpkassistant\chat\ChatDao.class +vvpkassistant\pk\PkInfoModel.class +vvpkassistant\SystemMessage\SystemMessageController.class +vvpkassistant\Data\ResponseInfo.class +vvpkassistant\chat\ChatController.class +vvpkassistant\FunctionConfig\FunctionConfigModel.class +vvpkassistant\User\UserDao.class +vvpkassistant\pk\PkController.class +vvpkassistant\Anchors\AnchorsController.class +vvpkassistant\Application.class +vvpkassistant\pk\PkRecordDao.class +vvpkassistant\CoinRecords\CoinRecords.class +vvpkassistant\Tools\VVTools.class +vvpkassistant\Tools\LogUtil.class +vvpkassistant\User\UserController.class +vvpkassistant\Tools\WebMvcConfig.class +vvpkassistant\Data\WxParam.class diff --git a/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst b/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst new file mode 100644 index 0000000..c866d4f --- /dev/null +++ b/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst @@ -0,0 +1,36 @@ +E:\Code\java\vvPkAssistant\src\main\java\vvpkassistant\chat\ChatModel.java +E:\Code\java\vvPkAssistant\src\main\java\vvpkassistant\Application.java +E:\Code\java\vvPkAssistant\src\main\java\vvpkassistant\chat\ChatDao.java +E:\Code\java\vvPkAssistant\src\main\java\vvpkassistant\Data\ResponseInfo.java +E:\Code\java\vvPkAssistant\src\main\java\vvpkassistant\FunctionConfig\FunctionConfigController.java +E:\Code\java\vvPkAssistant\src\main\java\vvpkassistant\FunctionConfig\FunctionConfigModel.java +E:\Code\java\vvPkAssistant\src\main\java\vvpkassistant\CoinRecords\CoinRecordsDao.java +E:\Code\java\vvPkAssistant\src\main\java\vvpkassistant\Tools\OkHttpUtils.java +E:\Code\java\vvPkAssistant\src\main\java\vvpkassistant\Tools\LogUtil.java +E:\Code\java\vvPkAssistant\src\main\java\vvpkassistant\Anchors\AnchorDao.java +E:\Code\java\vvPkAssistant\src\main\java\vvpkassistant\CoinRecords\CoinRecords.java +E:\Code\java\vvPkAssistant\src\main\java\vvpkassistant\pk\PkController.java +E:\Code\java\vvPkAssistant\src\main\java\vvpkassistant\Data\WxChatParam.java +E:\Code\java\vvPkAssistant\src\main\java\vvpkassistant\Anchors\AnchorModel.java +E:\Code\java\vvPkAssistant\src\main\java\vvpkassistant\FunctionConfig\FunctionConfigHolder.java +E:\Code\java\vvPkAssistant\src\main\java\vvpkassistant\SystemMessage\SystemMessage.java +E:\Code\java\vvPkAssistant\src\main\java\vvpkassistant\SystemMessage\SystemMessageDao.java +E:\Code\java\vvPkAssistant\src\main\java\vvpkassistant\pk\PkInfoModel.java +E:\Code\java\vvPkAssistant\src\main\java\vvpkassistant\pk\PkRecordDao.java +E:\Code\java\vvPkAssistant\src\main\java\vvpkassistant\SystemMessage\SystemMessageController.java +E:\Code\java\vvPkAssistant\src\main\java\vvpkassistant\Data\WxParam.java +E:\Code\java\vvPkAssistant\src\main\java\vvpkassistant\pk\PkRecord.java +E:\Code\java\vvPkAssistant\src\main\java\vvpkassistant\Data\ResponseData.java +E:\Code\java\vvPkAssistant\src\main\java\vvpkassistant\Anchors\AnchorsController.java +E:\Code\java\vvPkAssistant\src\main\java\vvpkassistant\pk\PkInfoDao.java +E:\Code\java\vvPkAssistant\src\main\java\vvpkassistant\pk\PkRecordDetail.java +E:\Code\java\vvPkAssistant\src\main\java\vvpkassistant\Tools\COSSigner.java +E:\Code\java\vvPkAssistant\src\main\java\vvpkassistant\FunctionConfig\FunctionConfigMapper.java +E:\Code\java\vvPkAssistant\src\main\java\vvpkassistant\Tools\WebMvcConfig.java +E:\Code\java\vvPkAssistant\src\main\java\vvpkassistant\pk\PkRecordDetailDao.java +E:\Code\java\vvPkAssistant\src\main\java\vvpkassistant\chat\ChatController.java +E:\Code\java\vvPkAssistant\src\main\java\vvpkassistant\Tools\VVTools.java +E:\Code\java\vvPkAssistant\src\main\java\vvpkassistant\User\UserDao.java +E:\Code\java\vvPkAssistant\src\main\java\vvpkassistant\Tools\VVRequester.java +E:\Code\java\vvPkAssistant\src\main\java\vvpkassistant\User\UserController.java +E:\Code\java\vvPkAssistant\src\main\java\vvpkassistant\User\UserModel.java diff --git a/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/createdFiles.lst b/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/createdFiles.lst new file mode 100644 index 0000000..1e60b66 --- /dev/null +++ b/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/createdFiles.lst @@ -0,0 +1 @@ +vvpkassistant\ApplicationTests.class diff --git a/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/inputFiles.lst b/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/inputFiles.lst new file mode 100644 index 0000000..9df5bb8 --- /dev/null +++ b/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/inputFiles.lst @@ -0,0 +1 @@ +E:\Code\java\vvPkAssistant\src\test\java\vvpkassistant\ApplicationTests.java diff --git a/target/vvPkAssistant-0.0.1.jar.original b/target/vvPkAssistant-0.0.1.jar.original new file mode 100644 index 0000000000000000000000000000000000000000..cc5ba398e2700f3d2eef59a7f46c30fe198bee87 GIT binary patch literal 70448 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 literal 0 HcmV?d00001 diff --git a/vvPkAssistant.iml b/vvPkAssistant.iml new file mode 100644 index 0000000..6cf5908 --- /dev/null +++ b/vvPkAssistant.iml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file