mirror of
https://github.com/201206030/novel.git
synced 2025-04-27 07:30:50 +00:00
style: 代码格式化
This commit is contained in:
parent
6d2fa74237
commit
9b93d90270
6
doc/sql/README.md
Normal file
6
doc/sql/README.md
Normal file
@ -0,0 +1,6 @@
|
||||
1. 初始状态下,MySQL 只需要执行 `novel.sql` 文件即可正常运行本系统
|
||||
2. 代码更新后再执行以日期命名的增量 SQL 文件
|
||||
3. 只有开启 XXL-JOB 的功能,才需要执行 `xxl-job.sql` 和以 xxl-job 开头日期结尾的增量 SQL 文件
|
||||
4. 只有开启 ShardingSphere-JDBC 的功能,才需要执行 `shardingsphere-jdbc.sql` 和以 shardingsphere-jdbc 开头日期结尾的增量 SQL
|
||||
文件
|
||||
|
@ -1,5 +0,0 @@
|
||||
1. 初始状态下,MYSQL 只需要执行 novel.sql 文件即可正常运行本系统
|
||||
2. 代码更新后再执行以日期命名的增量 SQL 文件
|
||||
3. 只有开启 XXL-JOB 的功能,才需要执行 xxl-job.sql 和以 xxl-job 开头日期结尾的增量 SQL 文件
|
||||
4. 只有开启 ShardingSphere-JDBC 的功能,才需要执行 shardingsphere-jdbc.sql 和以 shardingsphere-jdbc 开头日期结尾的增量 SQL 文件
|
||||
|
3
doc/style/README.md
Normal file
3
doc/style/README.md
Normal file
@ -0,0 +1,3 @@
|
||||
IntelliJ IDEA 中导入 `intellij-java-google-style.xml` 文件:
|
||||
|
||||
`Preferences` => `Editor` => `Code Style` => `Java` => `Schema` => `Import Schema`
|
598
doc/style/intellij-java-google-style.xml
Normal file
598
doc/style/intellij-java-google-style.xml
Normal file
@ -0,0 +1,598 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<code_scheme name="GoogleStyle">
|
||||
<option name="OTHER_INDENT_OPTIONS">
|
||||
<value>
|
||||
<option name="INDENT_SIZE" value="2" />
|
||||
<option name="CONTINUATION_INDENT_SIZE" value="4" />
|
||||
<option name="TAB_SIZE" value="2" />
|
||||
<option name="USE_TAB_CHARACTER" value="false" />
|
||||
<option name="SMART_TABS" value="false" />
|
||||
<option name="LABEL_INDENT_SIZE" value="0" />
|
||||
<option name="LABEL_INDENT_ABSOLUTE" value="false" />
|
||||
<option name="USE_RELATIVE_INDENTS" value="false" />
|
||||
</value>
|
||||
</option>
|
||||
<option name="INSERT_INNER_CLASS_IMPORTS" value="true" />
|
||||
<option name="CLASS_COUNT_TO_USE_IMPORT_ON_DEMAND" value="999" />
|
||||
<option name="NAMES_COUNT_TO_USE_IMPORT_ON_DEMAND" value="999" />
|
||||
<option name="PACKAGES_TO_USE_IMPORT_ON_DEMAND">
|
||||
<value />
|
||||
</option>
|
||||
<option name="IMPORT_LAYOUT_TABLE">
|
||||
<value>
|
||||
<package name="" withSubpackages="true" static="true" />
|
||||
<emptyLine />
|
||||
<package name="" withSubpackages="true" static="false" />
|
||||
</value>
|
||||
</option>
|
||||
<option name="RIGHT_MARGIN" value="100" />
|
||||
<option name="JD_ALIGN_PARAM_COMMENTS" value="false" />
|
||||
<option name="JD_ALIGN_EXCEPTION_COMMENTS" value="false" />
|
||||
<option name="JD_P_AT_EMPTY_LINES" value="false" />
|
||||
<option name="JD_KEEP_EMPTY_PARAMETER" value="false" />
|
||||
<option name="JD_KEEP_EMPTY_EXCEPTION" value="false" />
|
||||
<option name="JD_KEEP_EMPTY_RETURN" value="false" />
|
||||
<option name="KEEP_CONTROL_STATEMENT_IN_ONE_LINE" value="false" />
|
||||
<option name="KEEP_BLANK_LINES_BEFORE_RBRACE" value="0" />
|
||||
<option name="KEEP_BLANK_LINES_IN_CODE" value="1" />
|
||||
<option name="BLANK_LINES_AFTER_CLASS_HEADER" value="0" />
|
||||
<option name="ALIGN_MULTILINE_PARAMETERS" value="false" />
|
||||
<option name="ALIGN_MULTILINE_FOR" value="false" />
|
||||
<option name="CALL_PARAMETERS_WRAP" value="1" />
|
||||
<option name="METHOD_PARAMETERS_WRAP" value="1" />
|
||||
<option name="EXTENDS_LIST_WRAP" value="1" />
|
||||
<option name="THROWS_KEYWORD_WRAP" value="1" />
|
||||
<option name="METHOD_CALL_CHAIN_WRAP" value="1" />
|
||||
<option name="BINARY_OPERATION_WRAP" value="1" />
|
||||
<option name="BINARY_OPERATION_SIGN_ON_NEXT_LINE" value="true" />
|
||||
<option name="TERNARY_OPERATION_WRAP" value="1" />
|
||||
<option name="TERNARY_OPERATION_SIGNS_ON_NEXT_LINE" value="true" />
|
||||
<option name="FOR_STATEMENT_WRAP" value="1" />
|
||||
<option name="ARRAY_INITIALIZER_WRAP" value="1" />
|
||||
<option name="WRAP_COMMENTS" value="true" />
|
||||
<option name="IF_BRACE_FORCE" value="3" />
|
||||
<option name="DOWHILE_BRACE_FORCE" value="3" />
|
||||
<option name="WHILE_BRACE_FORCE" value="3" />
|
||||
<option name="FOR_BRACE_FORCE" value="3" />
|
||||
<option name="SPACE_BEFORE_ARRAY_INITIALIZER_LBRACE" value="true" />
|
||||
<AndroidXmlCodeStyleSettings>
|
||||
<option name="USE_CUSTOM_SETTINGS" value="true" />
|
||||
<option name="LAYOUT_SETTINGS">
|
||||
<value>
|
||||
<option name="INSERT_BLANK_LINE_BEFORE_TAG" value="false" />
|
||||
</value>
|
||||
</option>
|
||||
</AndroidXmlCodeStyleSettings>
|
||||
<JSCodeStyleSettings>
|
||||
<option name="INDENT_CHAINED_CALLS" value="false" />
|
||||
</JSCodeStyleSettings>
|
||||
<Python>
|
||||
<option name="USE_CONTINUATION_INDENT_FOR_ARGUMENTS" value="true" />
|
||||
</Python>
|
||||
<TypeScriptCodeStyleSettings>
|
||||
<option name="INDENT_CHAINED_CALLS" value="false" />
|
||||
</TypeScriptCodeStyleSettings>
|
||||
<XML>
|
||||
<option name="XML_ALIGN_ATTRIBUTES" value="false" />
|
||||
<option name="XML_LEGACY_SETTINGS_IMPORTED" value="true" />
|
||||
</XML>
|
||||
<codeStyleSettings language="CSS">
|
||||
<indentOptions>
|
||||
<option name="INDENT_SIZE" value="2" />
|
||||
<option name="CONTINUATION_INDENT_SIZE" value="4" />
|
||||
<option name="TAB_SIZE" value="2" />
|
||||
</indentOptions>
|
||||
</codeStyleSettings>
|
||||
<codeStyleSettings language="ECMA Script Level 4">
|
||||
<option name="KEEP_BLANK_LINES_IN_CODE" value="1" />
|
||||
<option name="ALIGN_MULTILINE_PARAMETERS" value="false" />
|
||||
<option name="ALIGN_MULTILINE_FOR" value="false" />
|
||||
<option name="CALL_PARAMETERS_WRAP" value="1" />
|
||||
<option name="METHOD_PARAMETERS_WRAP" value="1" />
|
||||
<option name="EXTENDS_LIST_WRAP" value="1" />
|
||||
<option name="BINARY_OPERATION_WRAP" value="1" />
|
||||
<option name="BINARY_OPERATION_SIGN_ON_NEXT_LINE" value="true" />
|
||||
<option name="TERNARY_OPERATION_WRAP" value="1" />
|
||||
<option name="TERNARY_OPERATION_SIGNS_ON_NEXT_LINE" value="true" />
|
||||
<option name="FOR_STATEMENT_WRAP" value="1" />
|
||||
<option name="ARRAY_INITIALIZER_WRAP" value="1" />
|
||||
<option name="IF_BRACE_FORCE" value="3" />
|
||||
<option name="DOWHILE_BRACE_FORCE" value="3" />
|
||||
<option name="WHILE_BRACE_FORCE" value="3" />
|
||||
<option name="FOR_BRACE_FORCE" value="3" />
|
||||
<option name="PARENT_SETTINGS_INSTALLED" value="true" />
|
||||
</codeStyleSettings>
|
||||
<codeStyleSettings language="HTML">
|
||||
<indentOptions>
|
||||
<option name="INDENT_SIZE" value="2" />
|
||||
<option name="CONTINUATION_INDENT_SIZE" value="4" />
|
||||
<option name="TAB_SIZE" value="2" />
|
||||
</indentOptions>
|
||||
</codeStyleSettings>
|
||||
<codeStyleSettings language="JAVA">
|
||||
<option name="KEEP_CONTROL_STATEMENT_IN_ONE_LINE" value="false" />
|
||||
<option name="KEEP_BLANK_LINES_IN_CODE" value="1" />
|
||||
<option name="BLANK_LINES_AFTER_CLASS_HEADER" value="1" />
|
||||
<option name="ALIGN_MULTILINE_PARAMETERS" value="false" />
|
||||
<option name="ALIGN_MULTILINE_RESOURCES" value="false" />
|
||||
<option name="ALIGN_MULTILINE_FOR" value="false" />
|
||||
<option name="CALL_PARAMETERS_WRAP" value="1" />
|
||||
<option name="METHOD_PARAMETERS_WRAP" value="1" />
|
||||
<option name="EXTENDS_LIST_WRAP" value="1" />
|
||||
<option name="THROWS_KEYWORD_WRAP" value="1" />
|
||||
<option name="METHOD_CALL_CHAIN_WRAP" value="1" />
|
||||
<option name="BINARY_OPERATION_WRAP" value="1" />
|
||||
<option name="BINARY_OPERATION_SIGN_ON_NEXT_LINE" value="true" />
|
||||
<option name="TERNARY_OPERATION_WRAP" value="1" />
|
||||
<option name="TERNARY_OPERATION_SIGNS_ON_NEXT_LINE" value="true" />
|
||||
<option name="FOR_STATEMENT_WRAP" value="1" />
|
||||
<option name="ARRAY_INITIALIZER_WRAP" value="1" />
|
||||
<option name="WRAP_COMMENTS" value="true" />
|
||||
<option name="IF_BRACE_FORCE" value="3" />
|
||||
<option name="DOWHILE_BRACE_FORCE" value="3" />
|
||||
<option name="WHILE_BRACE_FORCE" value="3" />
|
||||
<option name="FOR_BRACE_FORCE" value="3" />
|
||||
<option name="PARENT_SETTINGS_INSTALLED" value="true" />
|
||||
<indentOptions>
|
||||
<option name="INDENT_SIZE" value="4" />
|
||||
<option name="CONTINUATION_INDENT_SIZE" value="4" />
|
||||
<option name="TAB_SIZE" value="4" />
|
||||
</indentOptions>
|
||||
</codeStyleSettings>
|
||||
<codeStyleSettings language="JSON">
|
||||
<indentOptions>
|
||||
<option name="CONTINUATION_INDENT_SIZE" value="4" />
|
||||
<option name="TAB_SIZE" value="2" />
|
||||
</indentOptions>
|
||||
</codeStyleSettings>
|
||||
<codeStyleSettings language="JavaScript">
|
||||
<option name="RIGHT_MARGIN" value="80" />
|
||||
<option name="KEEP_BLANK_LINES_IN_CODE" value="1" />
|
||||
<option name="ALIGN_MULTILINE_PARAMETERS" value="false" />
|
||||
<option name="ALIGN_MULTILINE_FOR" value="false" />
|
||||
<option name="CALL_PARAMETERS_WRAP" value="1" />
|
||||
<option name="METHOD_PARAMETERS_WRAP" value="1" />
|
||||
<option name="BINARY_OPERATION_WRAP" value="1" />
|
||||
<option name="BINARY_OPERATION_SIGN_ON_NEXT_LINE" value="true" />
|
||||
<option name="TERNARY_OPERATION_WRAP" value="1" />
|
||||
<option name="TERNARY_OPERATION_SIGNS_ON_NEXT_LINE" value="true" />
|
||||
<option name="FOR_STATEMENT_WRAP" value="1" />
|
||||
<option name="ARRAY_INITIALIZER_WRAP" value="1" />
|
||||
<option name="IF_BRACE_FORCE" value="3" />
|
||||
<option name="DOWHILE_BRACE_FORCE" value="3" />
|
||||
<option name="WHILE_BRACE_FORCE" value="3" />
|
||||
<option name="FOR_BRACE_FORCE" value="3" />
|
||||
<option name="PARENT_SETTINGS_INSTALLED" value="true" />
|
||||
<indentOptions>
|
||||
<option name="INDENT_SIZE" value="2" />
|
||||
<option name="TAB_SIZE" value="2" />
|
||||
</indentOptions>
|
||||
</codeStyleSettings>
|
||||
<codeStyleSettings language="PROTO">
|
||||
<option name="RIGHT_MARGIN" value="80" />
|
||||
<indentOptions>
|
||||
<option name="INDENT_SIZE" value="2" />
|
||||
<option name="CONTINUATION_INDENT_SIZE" value="2" />
|
||||
<option name="TAB_SIZE" value="2" />
|
||||
</indentOptions>
|
||||
</codeStyleSettings>
|
||||
<codeStyleSettings language="protobuf">
|
||||
<option name="RIGHT_MARGIN" value="80" />
|
||||
<indentOptions>
|
||||
<option name="INDENT_SIZE" value="2" />
|
||||
<option name="CONTINUATION_INDENT_SIZE" value="2" />
|
||||
<option name="TAB_SIZE" value="2" />
|
||||
</indentOptions>
|
||||
</codeStyleSettings>
|
||||
<codeStyleSettings language="Python">
|
||||
<option name="KEEP_BLANK_LINES_IN_CODE" value="1" />
|
||||
<option name="RIGHT_MARGIN" value="80" />
|
||||
<option name="ALIGN_MULTILINE_PARAMETERS" value="false" />
|
||||
<option name="PARENT_SETTINGS_INSTALLED" value="true" />
|
||||
<indentOptions>
|
||||
<option name="INDENT_SIZE" value="2" />
|
||||
<option name="CONTINUATION_INDENT_SIZE" value="4" />
|
||||
<option name="TAB_SIZE" value="2" />
|
||||
</indentOptions>
|
||||
</codeStyleSettings>
|
||||
<codeStyleSettings language="SASS">
|
||||
<indentOptions>
|
||||
<option name="CONTINUATION_INDENT_SIZE" value="4" />
|
||||
<option name="TAB_SIZE" value="2" />
|
||||
</indentOptions>
|
||||
</codeStyleSettings>
|
||||
<codeStyleSettings language="SCSS">
|
||||
<indentOptions>
|
||||
<option name="CONTINUATION_INDENT_SIZE" value="4" />
|
||||
<option name="TAB_SIZE" value="2" />
|
||||
</indentOptions>
|
||||
</codeStyleSettings>
|
||||
<codeStyleSettings language="TypeScript">
|
||||
<indentOptions>
|
||||
<option name="INDENT_SIZE" value="2" />
|
||||
<option name="TAB_SIZE" value="2" />
|
||||
</indentOptions>
|
||||
</codeStyleSettings>
|
||||
<codeStyleSettings language="XML">
|
||||
<indentOptions>
|
||||
<option name="INDENT_SIZE" value="2" />
|
||||
<option name="CONTINUATION_INDENT_SIZE" value="2" />
|
||||
<option name="TAB_SIZE" value="2" />
|
||||
</indentOptions>
|
||||
<arrangement>
|
||||
<rules>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>xmlns:android</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>xmlns:.*</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
<order>BY_NAME</order>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*:id</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>style</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
<order>BY_NAME</order>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*:.*Style</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
<order>BY_NAME</order>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*:layout_width</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*:layout_height</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*:layout_weight</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*:layout_margin</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*:layout_marginTop</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*:layout_marginBottom</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*:layout_marginStart</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*:layout_marginEnd</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*:layout_marginLeft</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*:layout_marginRight</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*:layout_.*</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
<order>BY_NAME</order>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*:padding</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*:paddingTop</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*:paddingBottom</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*:paddingStart</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*:paddingEnd</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*:paddingLeft</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*:paddingRight</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*</NAME>
|
||||
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
<order>BY_NAME</order>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*</NAME>
|
||||
<XML_NAMESPACE>http://schemas.android.com/apk/res-auto</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
<order>BY_NAME</order>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*</NAME>
|
||||
<XML_NAMESPACE>http://schemas.android.com/tools</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
<order>BY_NAME</order>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*</NAME>
|
||||
<XML_NAMESPACE>.*</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
<order>BY_NAME</order>
|
||||
</rule>
|
||||
</section>
|
||||
</rules>
|
||||
</arrangement>
|
||||
</codeStyleSettings>
|
||||
<Objective-C>
|
||||
<option name="INDENT_NAMESPACE_MEMBERS" value="0" />
|
||||
<option name="INDENT_C_STRUCT_MEMBERS" value="2" />
|
||||
<option name="INDENT_CLASS_MEMBERS" value="2" />
|
||||
<option name="INDENT_VISIBILITY_KEYWORDS" value="1" />
|
||||
<option name="INDENT_INSIDE_CODE_BLOCK" value="2" />
|
||||
<option name="KEEP_STRUCTURES_IN_ONE_LINE" value="true" />
|
||||
<option name="FUNCTION_PARAMETERS_WRAP" value="5" />
|
||||
<option name="FUNCTION_CALL_ARGUMENTS_WRAP" value="5" />
|
||||
<option name="TEMPLATE_CALL_ARGUMENTS_WRAP" value="5" />
|
||||
<option name="TEMPLATE_CALL_ARGUMENTS_ALIGN_MULTILINE" value="true" />
|
||||
<option name="ALIGN_INIT_LIST_IN_COLUMNS" value="false" />
|
||||
<option name="SPACE_BEFORE_SUPERCLASS_COLON" value="false" />
|
||||
</Objective-C>
|
||||
<Objective-C-extensions>
|
||||
<option name="GENERATE_INSTANCE_VARIABLES_FOR_PROPERTIES" value="ASK" />
|
||||
<option name="RELEASE_STYLE" value="IVAR" />
|
||||
<option name="TYPE_QUALIFIERS_PLACEMENT" value="BEFORE" />
|
||||
<file>
|
||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Import" />
|
||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Macro" />
|
||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Typedef" />
|
||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Enum" />
|
||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Constant" />
|
||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Global" />
|
||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Struct" />
|
||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="FunctionPredecl" />
|
||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Function" />
|
||||
</file>
|
||||
<class>
|
||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Property" />
|
||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Synthesize" />
|
||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="InitMethod" />
|
||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="StaticMethod" />
|
||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="InstanceMethod" />
|
||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="DeallocMethod" />
|
||||
</class>
|
||||
<extensions>
|
||||
<pair source="cc" header="h" />
|
||||
<pair source="c" header="h" />
|
||||
</extensions>
|
||||
</Objective-C-extensions>
|
||||
<codeStyleSettings language="ObjectiveC">
|
||||
<option name="RIGHT_MARGIN" value="80" />
|
||||
<option name="KEEP_BLANK_LINES_BEFORE_RBRACE" value="1" />
|
||||
<option name="BLANK_LINES_BEFORE_IMPORTS" value="0" />
|
||||
<option name="BLANK_LINES_AFTER_IMPORTS" value="0" />
|
||||
<option name="BLANK_LINES_AROUND_CLASS" value="0" />
|
||||
<option name="BLANK_LINES_AROUND_METHOD" value="0" />
|
||||
<option name="BLANK_LINES_AROUND_METHOD_IN_INTERFACE" value="0" />
|
||||
<option name="ALIGN_MULTILINE_BINARY_OPERATION" value="false" />
|
||||
<option name="BINARY_OPERATION_SIGN_ON_NEXT_LINE" value="true" />
|
||||
<option name="FOR_STATEMENT_WRAP" value="1" />
|
||||
<option name="ASSIGNMENT_WRAP" value="1" />
|
||||
<indentOptions>
|
||||
<option name="INDENT_SIZE" value="2" />
|
||||
<option name="CONTINUATION_INDENT_SIZE" value="4" />
|
||||
</indentOptions>
|
||||
</codeStyleSettings>
|
||||
</code_scheme>
|
@ -7,6 +7,7 @@ import io.swagger.v3.oas.annotations.enums.SecuritySchemeType;
|
||||
import io.swagger.v3.oas.annotations.info.Info;
|
||||
import io.swagger.v3.oas.annotations.info.License;
|
||||
import io.swagger.v3.oas.annotations.security.SecurityScheme;
|
||||
import java.util.Map;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.mybatis.spring.annotation.MapperScan;
|
||||
import org.springframework.boot.CommandLineRunner;
|
||||
@ -21,8 +22,6 @@ import org.springframework.scheduling.annotation.EnableScheduling;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@OpenAPIDefinition(info = @Info(title = "novel 项目接口文档", version = "v3.2.0", license = @License(name = "Apache 2.0", url = "https://www.apache.org/licenses/LICENSE-2.0")))
|
||||
@SecurityScheme(type = SecuritySchemeType.APIKEY, in = SecuritySchemeIn.HEADER, name = SystemConfigConsts.HTTP_AUTH_HEADER_NAME, description = "登录 token")
|
||||
@SpringBootApplication
|
||||
@ -52,8 +51,8 @@ public class NovelApplication {
|
||||
@Bean
|
||||
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
||||
http.csrf().disable()
|
||||
.requestMatcher(EndpointRequest.toAnyEndpoint())
|
||||
.authorizeRequests(requests -> requests.anyRequest().hasRole("ENDPOINT_ADMIN"));
|
||||
.requestMatcher(EndpointRequest.toAnyEndpoint())
|
||||
.authorizeRequests(requests -> requests.anyRequest().hasRole("ENDPOINT_ADMIN"));
|
||||
http.httpBasic();
|
||||
return http.build();
|
||||
}
|
||||
|
@ -20,7 +20,12 @@ import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.validation.Valid;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springdoc.core.annotations.ParameterObject;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
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;
|
||||
|
||||
/**
|
||||
* 作家后台-作家模块 API 控制器
|
||||
@ -81,7 +86,9 @@ public class AuthorController {
|
||||
*/
|
||||
@Operation(summary = "小说章节发布接口")
|
||||
@PostMapping("book/chapter/{bookId}")
|
||||
public RestResp<Void> publishBookChapter(@Parameter(description = "小说ID") @PathVariable("bookId") Long bookId, @Valid @RequestBody ChapterAddReqDto dto) {
|
||||
public RestResp<Void> publishBookChapter(
|
||||
@Parameter(description = "小说ID") @PathVariable("bookId") Long bookId,
|
||||
@Valid @RequestBody ChapterAddReqDto dto) {
|
||||
dto.setBookId(bookId);
|
||||
return bookService.saveBookChapter(dto);
|
||||
}
|
||||
@ -91,7 +98,9 @@ public class AuthorController {
|
||||
*/
|
||||
@Operation(summary = "小说章节发布列表查询接口")
|
||||
@GetMapping("book/chapters/{bookId}")
|
||||
public RestResp<PageRespDto<BookChapterRespDto>> listBookChapters(@Parameter(description = "小说ID") @PathVariable("bookId") Long bookId,@ParameterObject PageReqDto dto) {
|
||||
public RestResp<PageRespDto<BookChapterRespDto>> listBookChapters(
|
||||
@Parameter(description = "小说ID") @PathVariable("bookId") Long bookId,
|
||||
@ParameterObject PageReqDto dto) {
|
||||
return bookService.listBookChapters(bookId, dto);
|
||||
}
|
||||
|
||||
|
@ -2,16 +2,25 @@ package io.github.xxyopen.novel.controller.front;
|
||||
|
||||
import io.github.xxyopen.novel.core.common.resp.RestResp;
|
||||
import io.github.xxyopen.novel.core.constant.ApiRouterConsts;
|
||||
import io.github.xxyopen.novel.dto.resp.*;
|
||||
import io.github.xxyopen.novel.dto.resp.BookCategoryRespDto;
|
||||
import io.github.xxyopen.novel.dto.resp.BookChapterAboutRespDto;
|
||||
import io.github.xxyopen.novel.dto.resp.BookChapterRespDto;
|
||||
import io.github.xxyopen.novel.dto.resp.BookCommentRespDto;
|
||||
import io.github.xxyopen.novel.dto.resp.BookContentAboutRespDto;
|
||||
import io.github.xxyopen.novel.dto.resp.BookInfoRespDto;
|
||||
import io.github.xxyopen.novel.dto.resp.BookRankRespDto;
|
||||
import io.github.xxyopen.novel.service.BookService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.List;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/**
|
||||
* 前台门户-小说模块 API 控制器
|
||||
@ -32,7 +41,8 @@ public class BookController {
|
||||
*/
|
||||
@Operation(summary = "小说分类列表查询接口")
|
||||
@GetMapping("category/list")
|
||||
public RestResp<List<BookCategoryRespDto>> listCategory(@Parameter(description = "作品方向",required = true) Integer workDirection) {
|
||||
public RestResp<List<BookCategoryRespDto>> listCategory(
|
||||
@Parameter(description = "作品方向", required = true) Integer workDirection) {
|
||||
return bookService.listCategory(workDirection);
|
||||
}
|
||||
|
||||
@ -41,7 +51,8 @@ public class BookController {
|
||||
*/
|
||||
@Operation(summary = "小说信息查询接口")
|
||||
@GetMapping("{id}")
|
||||
public RestResp<BookInfoRespDto> getBookById(@Parameter(description = "小说 ID") @PathVariable("id") Long bookId) {
|
||||
public RestResp<BookInfoRespDto> getBookById(
|
||||
@Parameter(description = "小说 ID") @PathVariable("id") Long bookId) {
|
||||
return bookService.getBookById(bookId);
|
||||
}
|
||||
|
||||
@ -59,7 +70,8 @@ public class BookController {
|
||||
*/
|
||||
@Operation(summary = "小说最新章节相关信息查询接口")
|
||||
@GetMapping("last_chapter/about")
|
||||
public RestResp<BookChapterAboutRespDto> getLastChapterAbout(@Parameter(description = "小说ID") Long bookId) {
|
||||
public RestResp<BookChapterAboutRespDto> getLastChapterAbout(
|
||||
@Parameter(description = "小说ID") Long bookId) {
|
||||
return bookService.getLastChapterAbout(bookId);
|
||||
}
|
||||
|
||||
@ -68,7 +80,8 @@ public class BookController {
|
||||
*/
|
||||
@Operation(summary = "小说推荐列表查询接口")
|
||||
@GetMapping("rec_list")
|
||||
public RestResp<List<BookInfoRespDto>> listRecBooks(@Parameter(description = "小说ID") Long bookId) throws NoSuchAlgorithmException {
|
||||
public RestResp<List<BookInfoRespDto>> listRecBooks(
|
||||
@Parameter(description = "小说ID") Long bookId) throws NoSuchAlgorithmException {
|
||||
return bookService.listRecBooks(bookId);
|
||||
}
|
||||
|
||||
@ -77,7 +90,8 @@ public class BookController {
|
||||
*/
|
||||
@Operation(summary = "小说章节列表查询接口")
|
||||
@GetMapping("chapter/list")
|
||||
public RestResp<List<BookChapterRespDto>> listChapters(@Parameter(description = "小说ID") Long bookId) {
|
||||
public RestResp<List<BookChapterRespDto>> listChapters(
|
||||
@Parameter(description = "小说ID") Long bookId) {
|
||||
return bookService.listChapters(bookId);
|
||||
}
|
||||
|
||||
@ -86,7 +100,8 @@ public class BookController {
|
||||
*/
|
||||
@Operation(summary = "小说内容相关信息查询接口")
|
||||
@GetMapping("content/{chapterId}")
|
||||
public RestResp<BookContentAboutRespDto> getBookContentAbout(@Parameter(description = "章节ID") @PathVariable("chapterId") Long chapterId) {
|
||||
public RestResp<BookContentAboutRespDto> getBookContentAbout(
|
||||
@Parameter(description = "章节ID") @PathVariable("chapterId") Long chapterId) {
|
||||
return bookService.getBookContentAbout(chapterId);
|
||||
}
|
||||
|
||||
@ -95,7 +110,8 @@ public class BookController {
|
||||
*/
|
||||
@Operation(summary = "获取上一章节ID接口")
|
||||
@GetMapping("pre_chapter_id/{chapterId}")
|
||||
public RestResp<Long> getPreChapterId(@Parameter(description = "章节ID") @PathVariable("chapterId") Long chapterId) {
|
||||
public RestResp<Long> getPreChapterId(
|
||||
@Parameter(description = "章节ID") @PathVariable("chapterId") Long chapterId) {
|
||||
return bookService.getPreChapterId(chapterId);
|
||||
}
|
||||
|
||||
@ -104,7 +120,8 @@ public class BookController {
|
||||
*/
|
||||
@Operation(summary = "获取下一章节ID接口")
|
||||
@GetMapping("next_chapter_id/{chapterId}")
|
||||
public RestResp<Long> getNextChapterId(@Parameter(description = "章节ID") @PathVariable("chapterId") Long chapterId) {
|
||||
public RestResp<Long> getNextChapterId(
|
||||
@Parameter(description = "章节ID") @PathVariable("chapterId") Long chapterId) {
|
||||
return bookService.getNextChapterId(chapterId);
|
||||
}
|
||||
|
||||
@ -140,7 +157,8 @@ public class BookController {
|
||||
*/
|
||||
@Operation(summary = "小说最新评论查询接口")
|
||||
@GetMapping("comment/newest_list")
|
||||
public RestResp<BookCommentRespDto> listNewestComments(@Parameter(description = "小说ID") Long bookId) {
|
||||
public RestResp<BookCommentRespDto> listNewestComments(
|
||||
@Parameter(description = "小说ID") Long bookId) {
|
||||
return bookService.listNewestComments(bookId);
|
||||
}
|
||||
|
||||
|
@ -1,20 +1,19 @@
|
||||
package io.github.xxyopen.novel.controller.front;
|
||||
|
||||
import io.github.xxyopen.novel.core.constant.ApiRouterConsts;
|
||||
import io.github.xxyopen.novel.core.common.resp.RestResp;
|
||||
import io.github.xxyopen.novel.core.constant.ApiRouterConsts;
|
||||
import io.github.xxyopen.novel.dto.resp.NewsInfoRespDto;
|
||||
import io.github.xxyopen.novel.service.NewsService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import java.util.List;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 前台门户-新闻模块 API 控制器
|
||||
*
|
||||
@ -43,7 +42,8 @@ public class NewsController {
|
||||
*/
|
||||
@Operation(summary = "新闻信息查询接口")
|
||||
@GetMapping("{id}")
|
||||
public RestResp<NewsInfoRespDto> getNews(@Parameter(description = "新闻ID") @PathVariable Long id) {
|
||||
public RestResp<NewsInfoRespDto> getNews(
|
||||
@Parameter(description = "新闻ID") @PathVariable Long id) {
|
||||
return newsService.getNews(id);
|
||||
}
|
||||
}
|
||||
|
@ -7,11 +7,14 @@ import io.github.xxyopen.novel.service.ResourceService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.io.IOException;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
/**
|
||||
* 前台门户-资源(图片/视频/文档)模块 API 控制器
|
||||
@ -38,10 +41,11 @@ public class ResourceController {
|
||||
|
||||
/**
|
||||
* 图片上传接口
|
||||
* */
|
||||
*/
|
||||
@Operation(summary = "图片上传接口")
|
||||
@PostMapping("/image")
|
||||
RestResp<String> uploadImage(@Parameter(description = "上传文件") @RequestParam("file") MultipartFile file) {
|
||||
RestResp<String> uploadImage(
|
||||
@Parameter(description = "上传文件") @RequestParam("file") MultipartFile file) {
|
||||
return resourceService.uploadImage(file);
|
||||
}
|
||||
|
||||
|
@ -33,7 +33,8 @@ public class SearchController {
|
||||
*/
|
||||
@Operation(summary = "小说搜索接口")
|
||||
@GetMapping("books")
|
||||
public RestResp<PageRespDto<BookInfoRespDto>> searchBooks(@ParameterObject BookSearchReqDto condition) {
|
||||
public RestResp<PageRespDto<BookInfoRespDto>> searchBooks(
|
||||
@ParameterObject BookSearchReqDto condition) {
|
||||
return searchService.searchBooks(condition);
|
||||
}
|
||||
|
||||
|
@ -19,7 +19,14 @@ import io.swagger.v3.oas.annotations.security.SecurityRequirement;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.validation.Valid;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.PutMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/**
|
||||
* 前台门户-会员模块 API 控制器
|
||||
@ -108,7 +115,8 @@ public class UserController {
|
||||
*/
|
||||
@Operation(summary = "修改评论接口")
|
||||
@PutMapping("comment/{id}")
|
||||
public RestResp<Void> updateComment(@Parameter(description = "评论ID") @PathVariable Long id, String content) {
|
||||
public RestResp<Void> updateComment(@Parameter(description = "评论ID") @PathVariable Long id,
|
||||
String content) {
|
||||
return bookService.updateComment(UserHolder.getUserId(), id, content);
|
||||
}
|
||||
|
||||
@ -122,9 +130,7 @@ public class UserController {
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询书架状态接口
|
||||
* 0-不在书架
|
||||
* 1-已在书架
|
||||
* 查询书架状态接口 0-不在书架 1-已在书架
|
||||
*/
|
||||
@Operation(summary = "查询书架状态接口")
|
||||
@GetMapping("bookshelf_status")
|
||||
|
@ -1,12 +1,12 @@
|
||||
package io.github.xxyopen.novel.core.annotation;
|
||||
|
||||
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
||||
|
||||
/**
|
||||
* 分布式锁-Key 注解
|
||||
*
|
||||
@ -17,5 +17,7 @@ import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
||||
@Retention(RUNTIME)
|
||||
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER})
|
||||
public @interface Key {
|
||||
|
||||
String expr() default "";
|
||||
|
||||
}
|
||||
|
@ -3,6 +3,9 @@ package io.github.xxyopen.novel.core.aspect;
|
||||
import io.github.xxyopen.novel.core.annotation.Key;
|
||||
import io.github.xxyopen.novel.core.annotation.Lock;
|
||||
import io.github.xxyopen.novel.core.common.exception.BusinessException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Parameter;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import lombok.SneakyThrows;
|
||||
import org.aspectj.lang.ProceedingJoinPoint;
|
||||
import org.aspectj.lang.annotation.Around;
|
||||
@ -17,10 +20,6 @@ import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Parameter;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* 分布式锁 切面
|
||||
*
|
||||
@ -42,7 +41,7 @@ public record LockAspect(RedissonClient redissonClient) {
|
||||
Method targetMethod = methodSignature.getMethod();
|
||||
Lock lock = targetMethod.getAnnotation(Lock.class);
|
||||
String lockKey = KEY_PREFIX + buildLockKey(lock.prefix(), targetMethod,
|
||||
joinPoint.getArgs());
|
||||
joinPoint.getArgs());
|
||||
RLock rLock = redissonClient.getLock(lockKey);
|
||||
if (lock.isWait() ? rLock.tryLock(lock.waitTime(), TimeUnit.SECONDS) : rLock.tryLock()) {
|
||||
try {
|
||||
|
@ -6,9 +6,8 @@ import io.github.xxyopen.novel.core.constant.SystemConfigConsts;
|
||||
import io.github.xxyopen.novel.core.util.JwtUtils;
|
||||
import io.github.xxyopen.novel.dto.UserInfoDto;
|
||||
import io.github.xxyopen.novel.manager.cache.UserInfoCacheManager;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.util.Objects;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* 策略模式实现用户认证授权功能
|
||||
@ -21,7 +20,7 @@ public interface AuthStrategy {
|
||||
/**
|
||||
* 请求用户认证
|
||||
*
|
||||
* @param token 登录 token
|
||||
* @param token 登录 token
|
||||
* @param requestUri 请求的 URI
|
||||
* @throws BusinessException 认证失败则抛出业务异常
|
||||
*/
|
||||
@ -35,7 +34,8 @@ public interface AuthStrategy {
|
||||
* @param token token 登录 token
|
||||
* @return 用户ID
|
||||
*/
|
||||
default Long authSSO(JwtUtils jwtUtils, UserInfoCacheManager userInfoCacheManager, String token) {
|
||||
default Long authSSO(JwtUtils jwtUtils, UserInfoCacheManager userInfoCacheManager,
|
||||
String token) {
|
||||
if (!StringUtils.hasText(token)) {
|
||||
// token 为空
|
||||
throw new BusinessException(ErrorCodeEnum.USER_LOGIN_EXPIRED);
|
||||
|
@ -7,11 +7,10 @@ import io.github.xxyopen.novel.core.util.JwtUtils;
|
||||
import io.github.xxyopen.novel.dto.AuthorInfoDto;
|
||||
import io.github.xxyopen.novel.manager.cache.AuthorInfoCacheManager;
|
||||
import io.github.xxyopen.novel.manager.cache.UserInfoCacheManager;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* 作家后台管理系统 认证策略
|
||||
@ -31,17 +30,17 @@ public class AuthorAuthStrategy implements AuthStrategy {
|
||||
|
||||
/**
|
||||
* 不需要进行作家权限认证的 URI
|
||||
* */
|
||||
*/
|
||||
private static final List<String> EXCLUDE_URI = List.of(
|
||||
ApiRouterConsts.API_AUTHOR_URL_PREFIX + "/register",
|
||||
ApiRouterConsts.API_AUTHOR_URL_PREFIX +"/status"
|
||||
ApiRouterConsts.API_AUTHOR_URL_PREFIX + "/register",
|
||||
ApiRouterConsts.API_AUTHOR_URL_PREFIX + "/status"
|
||||
);
|
||||
|
||||
@Override
|
||||
public void auth(String token, String requestUri) throws BusinessException {
|
||||
// 统一账号认证
|
||||
Long userId = authSSO(jwtUtils, userInfoCacheManager, token);
|
||||
if(EXCLUDE_URI.contains(requestUri)){
|
||||
if (EXCLUDE_URI.contains(requestUri)) {
|
||||
// 该请求不需要进行作家权限认证
|
||||
return;
|
||||
}
|
||||
|
@ -23,6 +23,6 @@ public class FrontAuthStrategy implements AuthStrategy {
|
||||
@Override
|
||||
public void auth(String token, String requestUri) throws BusinessException {
|
||||
// 统一账号认证
|
||||
authSSO(jwtUtils,userInfoCacheManager,token);
|
||||
authSSO(jwtUtils, userInfoCacheManager, token);
|
||||
}
|
||||
}
|
@ -13,12 +13,12 @@ public class UserHolder {
|
||||
|
||||
/**
|
||||
* 当前线程用户ID
|
||||
* */
|
||||
*/
|
||||
private static final ThreadLocal<Long> userIdTL = new ThreadLocal<>();
|
||||
|
||||
/**
|
||||
* 当前线程作家ID
|
||||
* */
|
||||
*/
|
||||
private static final ThreadLocal<Long> authorIdTL = new ThreadLocal<>();
|
||||
|
||||
public void setUserId(Long userId) {
|
||||
@ -37,7 +37,7 @@ public class UserHolder {
|
||||
return authorIdTL.get();
|
||||
}
|
||||
|
||||
public void clear(){
|
||||
public void clear() {
|
||||
userIdTL.remove();
|
||||
authorIdTL.remove();
|
||||
}
|
||||
|
@ -11,33 +11,33 @@ public class CommonConsts {
|
||||
|
||||
/**
|
||||
* 是
|
||||
* */
|
||||
*/
|
||||
public static final Integer YES = 1;
|
||||
public static final String TRUE = "true";
|
||||
|
||||
|
||||
/**
|
||||
* 否
|
||||
* */
|
||||
*/
|
||||
public static final Integer NO = 0;
|
||||
public static final String FALSE = "false";
|
||||
|
||||
/**
|
||||
* 性别常量
|
||||
* */
|
||||
public enum SexEnum{
|
||||
*/
|
||||
public enum SexEnum {
|
||||
|
||||
/**
|
||||
* 男
|
||||
* */
|
||||
MALE(0,"男"),
|
||||
*/
|
||||
MALE(0, "男"),
|
||||
|
||||
/**
|
||||
* 女
|
||||
* */
|
||||
FEMALE(1,"女");
|
||||
*/
|
||||
FEMALE(1, "女");
|
||||
|
||||
SexEnum(int code,String desc){
|
||||
SexEnum(int code, String desc) {
|
||||
this.code = code;
|
||||
this.desc = desc;
|
||||
}
|
||||
|
@ -5,15 +5,12 @@ import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 错误码枚举类。
|
||||
*
|
||||
* 错误码为字符串类型,共 5 位,分成两个部分:错误产生来源+四位数字编号。
|
||||
* 错误产生来源分为 A/B/C, A 表示错误来源于用户,比如参数错误,用户安装版本过低,用户支付
|
||||
* 超时等问题; B 表示错误来源于当前系统,往往是业务逻辑出错,或程序健壮性差等问题; C 表示错误来源
|
||||
* 于第三方服务,比如 CDN 服务出错,消息投递超时等问题;四位数字编号从 0001 到 9999,大类之间的
|
||||
* <p>
|
||||
* 错误码为字符串类型,共 5 位,分成两个部分:错误产生来源+四位数字编号。 错误产生来源分为 A/B/C, A 表示错误来源于用户,比如参数错误,用户安装版本过低,用户支付 超时等问题; B
|
||||
* 表示错误来源于当前系统,往往是业务逻辑出错,或程序健壮性差等问题; C 表示错误来源 于第三方服务,比如 CDN 服务出错,消息投递超时等问题;四位数字编号从 0001 到 9999,大类之间的
|
||||
* 步长间距预留 100。
|
||||
*
|
||||
* 错误码分为一级宏观错误码、二级宏观错误码、三级宏观错误码。
|
||||
* 在无法更加具体确定的错误场景中,可以直接使用一级宏观错误码。
|
||||
* <p>
|
||||
* 错误码分为一级宏观错误码、二级宏观错误码、三级宏观错误码。 在无法更加具体确定的错误场景中,可以直接使用一级宏观错误码。
|
||||
*
|
||||
* @author xiongxiaoyang
|
||||
* @date 2022/5/11
|
||||
@ -24,134 +21,132 @@ public enum ErrorCodeEnum {
|
||||
|
||||
/**
|
||||
* 正确执行后的返回
|
||||
* */
|
||||
OK("00000","一切 ok"),
|
||||
*/
|
||||
OK("00000", "一切 ok"),
|
||||
|
||||
/**
|
||||
* 一级宏观错误码,用户端错误
|
||||
* */
|
||||
USER_ERROR("A0001","用户端错误"),
|
||||
*/
|
||||
USER_ERROR("A0001", "用户端错误"),
|
||||
|
||||
/**
|
||||
* 二级宏观错误码,用户注册错误
|
||||
* */
|
||||
USER_REGISTER_ERROR("A0100","用户注册错误"),
|
||||
*/
|
||||
USER_REGISTER_ERROR("A0100", "用户注册错误"),
|
||||
|
||||
/**
|
||||
* 用户未同意隐私协议
|
||||
* */
|
||||
USER_NO_AGREE_PRIVATE_ERROR("A0101","用户未同意隐私协议"),
|
||||
*/
|
||||
USER_NO_AGREE_PRIVATE_ERROR("A0101", "用户未同意隐私协议"),
|
||||
|
||||
/**
|
||||
* 注册国家或地区受限
|
||||
* */
|
||||
USER_REGISTER_AREA_LIMIT_ERROR("A0102","注册国家或地区受限"),
|
||||
*/
|
||||
USER_REGISTER_AREA_LIMIT_ERROR("A0102", "注册国家或地区受限"),
|
||||
|
||||
/**
|
||||
* 用户验证码错误
|
||||
* */
|
||||
USER_VERIFY_CODE_ERROR("A0240","用户验证码错误"),
|
||||
*/
|
||||
USER_VERIFY_CODE_ERROR("A0240", "用户验证码错误"),
|
||||
|
||||
/**
|
||||
* 用户名已存在
|
||||
* */
|
||||
USER_NAME_EXIST("A0111","用户名已存在"),
|
||||
*/
|
||||
USER_NAME_EXIST("A0111", "用户名已存在"),
|
||||
|
||||
/**
|
||||
* 用户账号不存在
|
||||
* */
|
||||
USER_ACCOUNT_NOT_EXIST("A0201","用户账号不存在"),
|
||||
*/
|
||||
USER_ACCOUNT_NOT_EXIST("A0201", "用户账号不存在"),
|
||||
|
||||
/**
|
||||
* 用户密码错误
|
||||
* */
|
||||
USER_PASSWORD_ERROR("A0210","用户密码错误"),
|
||||
*/
|
||||
USER_PASSWORD_ERROR("A0210", "用户密码错误"),
|
||||
|
||||
/**
|
||||
* 二级宏观错误码,用户请求参数错误
|
||||
* */
|
||||
USER_REQUEST_PARAM_ERROR("A0400","用户请求参数错误"),
|
||||
*/
|
||||
USER_REQUEST_PARAM_ERROR("A0400", "用户请求参数错误"),
|
||||
|
||||
/**
|
||||
* 用户登录已过期
|
||||
* */
|
||||
USER_LOGIN_EXPIRED("A0230","用户登录已过期"),
|
||||
*/
|
||||
USER_LOGIN_EXPIRED("A0230", "用户登录已过期"),
|
||||
|
||||
/**
|
||||
* 访问未授权
|
||||
* */
|
||||
USER_UN_AUTH("A0301","访问未授权"),
|
||||
*/
|
||||
USER_UN_AUTH("A0301", "访问未授权"),
|
||||
|
||||
/**
|
||||
* 用户请求服务异常
|
||||
* */
|
||||
USER_REQ_EXCEPTION("A0500","用户请求服务异常"),
|
||||
*/
|
||||
USER_REQ_EXCEPTION("A0500", "用户请求服务异常"),
|
||||
|
||||
/**
|
||||
* 请求超出限制
|
||||
* */
|
||||
USER_REQ_MANY("A0501","请求超出限制"),
|
||||
*/
|
||||
USER_REQ_MANY("A0501", "请求超出限制"),
|
||||
|
||||
/**
|
||||
* 用户评论异常
|
||||
* */
|
||||
USER_COMMENT("A2000","用户评论异常"),
|
||||
*/
|
||||
USER_COMMENT("A2000", "用户评论异常"),
|
||||
|
||||
/**
|
||||
* 用户评论异常
|
||||
* */
|
||||
USER_COMMENTED("A2001","用户已发表评论"),
|
||||
*/
|
||||
USER_COMMENTED("A2001", "用户已发表评论"),
|
||||
|
||||
/**
|
||||
* 作家发布异常
|
||||
* */
|
||||
AUTHOR_PUBLISH("A3000","作家发布异常"),
|
||||
*/
|
||||
AUTHOR_PUBLISH("A3000", "作家发布异常"),
|
||||
|
||||
/**
|
||||
* 小说名已存在
|
||||
* */
|
||||
AUTHOR_BOOK_NAME_EXIST("A3001","小说名已存在"),
|
||||
|
||||
*/
|
||||
AUTHOR_BOOK_NAME_EXIST("A3001", "小说名已存在"),
|
||||
|
||||
/**
|
||||
* 用户上传文件异常
|
||||
* */
|
||||
USER_UPLOAD_FILE_ERROR("A0700","用户上传文件异常"),
|
||||
*/
|
||||
USER_UPLOAD_FILE_ERROR("A0700", "用户上传文件异常"),
|
||||
|
||||
/**
|
||||
* 用户上传文件类型不匹配
|
||||
* */
|
||||
USER_UPLOAD_FILE_TYPE_NOT_MATCH("A0701","用户上传文件类型不匹配"),
|
||||
*/
|
||||
USER_UPLOAD_FILE_TYPE_NOT_MATCH("A0701", "用户上传文件类型不匹配"),
|
||||
|
||||
/**
|
||||
* 一级宏观错误码,系统执行出错
|
||||
* */
|
||||
SYSTEM_ERROR("B0001","系统执行出错"),
|
||||
*/
|
||||
SYSTEM_ERROR("B0001", "系统执行出错"),
|
||||
|
||||
/**
|
||||
* 二级宏观错误码,系统执行超时
|
||||
* */
|
||||
SYSTEM_TIMEOUT_ERROR("B0100","系统执行超时"),
|
||||
*/
|
||||
SYSTEM_TIMEOUT_ERROR("B0100", "系统执行超时"),
|
||||
|
||||
/**
|
||||
* 一级宏观错误码,调用第三方服务出错
|
||||
* */
|
||||
THIRD_SERVICE_ERROR("C0001","调用第三方服务出错"),
|
||||
*/
|
||||
THIRD_SERVICE_ERROR("C0001", "调用第三方服务出错"),
|
||||
|
||||
/**
|
||||
* 一级宏观错误码,中间件服务出错
|
||||
* */
|
||||
MIDDLEWARE_SERVICE_ERROR("C0100","中间件服务出错")
|
||||
;
|
||||
*/
|
||||
MIDDLEWARE_SERVICE_ERROR("C0100", "中间件服务出错");
|
||||
|
||||
/**
|
||||
* 错误码
|
||||
* */
|
||||
*/
|
||||
private final String code;
|
||||
|
||||
/**
|
||||
* 中文描述
|
||||
* */
|
||||
*/
|
||||
private final String message;
|
||||
|
||||
}
|
||||
|
@ -19,28 +19,28 @@ public class CommonExceptionHandler {
|
||||
|
||||
/**
|
||||
* 处理数据校验异常
|
||||
* */
|
||||
*/
|
||||
@ExceptionHandler(BindException.class)
|
||||
public RestResp<Void> handlerBindException(BindException e){
|
||||
log.error(e.getMessage(),e);
|
||||
public RestResp<Void> handlerBindException(BindException e) {
|
||||
log.error(e.getMessage(), e);
|
||||
return RestResp.fail(ErrorCodeEnum.USER_REQUEST_PARAM_ERROR);
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理业务异常
|
||||
* */
|
||||
*/
|
||||
@ExceptionHandler(BusinessException.class)
|
||||
public RestResp<Void> handlerBusinessException(BusinessException e){
|
||||
log.error(e.getMessage(),e);
|
||||
public RestResp<Void> handlerBusinessException(BusinessException e) {
|
||||
log.error(e.getMessage(), e);
|
||||
return RestResp.fail(e.getErrorCodeEnum());
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理系统异常
|
||||
* */
|
||||
*/
|
||||
@ExceptionHandler(Exception.class)
|
||||
public RestResp<Void> handlerException(Exception e){
|
||||
log.error(e.getMessage(),e);
|
||||
public RestResp<Void> handlerException(Exception e) {
|
||||
log.error(e.getMessage(), e);
|
||||
return RestResp.error();
|
||||
}
|
||||
|
||||
|
@ -14,20 +14,19 @@ public class PageReqDto {
|
||||
|
||||
/**
|
||||
* 请求页码,默认第 1 页
|
||||
* */
|
||||
*/
|
||||
@Parameter(description = "请求页码,默认第 1 页")
|
||||
private int pageNum = 1;
|
||||
|
||||
/**
|
||||
* 每页大小,默认每页 10 条
|
||||
* */
|
||||
*/
|
||||
@Parameter(description = "每页大小,默认每页 10 条")
|
||||
private int pageSize = 10;
|
||||
|
||||
/**
|
||||
* 是否查询所有,默认不查所有
|
||||
* 为 true 时,pageNum 和 pageSize 无效
|
||||
* */
|
||||
* 是否查询所有,默认不查所有 为 true 时,pageNum 和 pageSize 无效
|
||||
*/
|
||||
@Parameter(hidden = true)
|
||||
private boolean fetchAll = false;
|
||||
|
||||
|
@ -1,8 +1,7 @@
|
||||
package io.github.xxyopen.novel.core.common.resp;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
import java.util.List;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 分页响应数据格式封装
|
||||
@ -34,8 +33,7 @@ public class PageRespDto<T> {
|
||||
private final List<? extends T> list;
|
||||
|
||||
/**
|
||||
* 该构造函数用于通用分页查询的场景
|
||||
* 接收普通分页数据和普通集合
|
||||
* 该构造函数用于通用分页查询的场景 接收普通分页数据和普通集合
|
||||
*/
|
||||
public PageRespDto(long pageNum, long pageSize, long total, List<T> list) {
|
||||
this.pageNum = pageNum;
|
||||
@ -50,7 +48,7 @@ public class PageRespDto<T> {
|
||||
|
||||
/**
|
||||
* 获取分页数
|
||||
* */
|
||||
*/
|
||||
public long getPages() {
|
||||
if (this.pageSize == 0L) {
|
||||
return 0L;
|
||||
@ -59,7 +57,6 @@ public class PageRespDto<T> {
|
||||
if (this.total % this.pageSize != 0L) {
|
||||
++pages;
|
||||
}
|
||||
|
||||
return pages;
|
||||
}
|
||||
}
|
||||
|
@ -1,14 +1,15 @@
|
||||
package io.github.xxyopen.novel.core.common.util;
|
||||
|
||||
import lombok.experimental.UtilityClass;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import java.awt.*;
|
||||
import java.awt.Color;
|
||||
import java.awt.Font;
|
||||
import java.awt.Graphics;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.Base64;
|
||||
import java.util.Random;
|
||||
import javax.imageio.ImageIO;
|
||||
import lombok.experimental.UtilityClass;
|
||||
|
||||
/**
|
||||
* 图片验证码工具类
|
||||
@ -79,7 +80,7 @@ public class ImgVerifyCodeUtils {
|
||||
for (int i = 1; i <= verifyCode.length(); i++) {
|
||||
g.setFont(getFont());
|
||||
g.setColor(new Color(random.nextInt(101), random.nextInt(111), random
|
||||
.nextInt(121)));
|
||||
.nextInt(121)));
|
||||
g.translate(random.nextInt(3), random.nextInt(3));
|
||||
g.drawString(String.valueOf(verifyCode.charAt(i - 1)), 13 * i, 23);
|
||||
}
|
||||
|
@ -16,6 +16,7 @@ public class IpUtils {
|
||||
|
||||
/**
|
||||
* 获取真实IP
|
||||
*
|
||||
* @return 真实IP
|
||||
*/
|
||||
public String getRealIp(HttpServletRequest request) {
|
||||
|
@ -2,6 +2,11 @@ package io.github.xxyopen.novel.core.config;
|
||||
|
||||
import com.github.benmanes.caffeine.cache.Caffeine;
|
||||
import io.github.xxyopen.novel.core.constant.CacheConsts;
|
||||
import java.time.Duration;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import org.springframework.cache.CacheManager;
|
||||
import org.springframework.cache.caffeine.CaffeineCache;
|
||||
import org.springframework.cache.support.SimpleCacheManager;
|
||||
@ -13,12 +18,6 @@ import org.springframework.data.redis.cache.RedisCacheManager;
|
||||
import org.springframework.data.redis.cache.RedisCacheWriter;
|
||||
import org.springframework.data.redis.connection.RedisConnectionFactory;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 缓存配置类
|
||||
*
|
||||
@ -40,7 +39,8 @@ public class CacheConfig {
|
||||
// 类型推断 var 非常适合 for 循环,JDK 10 引入,JDK 11 改进
|
||||
for (var c : CacheConsts.CacheEnum.values()) {
|
||||
if (c.isLocal()) {
|
||||
Caffeine<Object, Object> caffeine = Caffeine.newBuilder().recordStats().maximumSize(c.getMaxSize());
|
||||
Caffeine<Object, Object> caffeine = Caffeine.newBuilder().recordStats()
|
||||
.maximumSize(c.getMaxSize());
|
||||
if (c.getTtl() > 0) {
|
||||
caffeine.expireAfterWrite(Duration.ofSeconds(c.getTtl()));
|
||||
}
|
||||
@ -57,26 +57,32 @@ public class CacheConfig {
|
||||
*/
|
||||
@Bean
|
||||
public CacheManager redisCacheManager(RedisConnectionFactory connectionFactory) {
|
||||
RedisCacheWriter redisCacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(connectionFactory);
|
||||
RedisCacheWriter redisCacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(
|
||||
connectionFactory);
|
||||
|
||||
RedisCacheConfiguration defaultCacheConfig = RedisCacheConfiguration.defaultCacheConfig()
|
||||
.disableCachingNullValues().prefixCacheNameWith(CacheConsts.REDIS_CACHE_PREFIX);
|
||||
.disableCachingNullValues().prefixCacheNameWith(CacheConsts.REDIS_CACHE_PREFIX);
|
||||
|
||||
Map<String, RedisCacheConfiguration> cacheMap = new LinkedHashMap<>(CacheConsts.CacheEnum.values().length);
|
||||
Map<String, RedisCacheConfiguration> cacheMap = new LinkedHashMap<>(
|
||||
CacheConsts.CacheEnum.values().length);
|
||||
// 类型推断 var 非常适合 for 循环,JDK 10 引入,JDK 11 改进
|
||||
for (var c : CacheConsts.CacheEnum.values()) {
|
||||
if (c.isRemote()) {
|
||||
if (c.getTtl() > 0) {
|
||||
cacheMap.put(c.getName(), RedisCacheConfiguration.defaultCacheConfig().disableCachingNullValues()
|
||||
.prefixCacheNameWith(CacheConsts.REDIS_CACHE_PREFIX).entryTtl(Duration.ofSeconds(c.getTtl())));
|
||||
cacheMap.put(c.getName(),
|
||||
RedisCacheConfiguration.defaultCacheConfig().disableCachingNullValues()
|
||||
.prefixCacheNameWith(CacheConsts.REDIS_CACHE_PREFIX)
|
||||
.entryTtl(Duration.ofSeconds(c.getTtl())));
|
||||
} else {
|
||||
cacheMap.put(c.getName(), RedisCacheConfiguration.defaultCacheConfig().disableCachingNullValues()
|
||||
cacheMap.put(c.getName(),
|
||||
RedisCacheConfiguration.defaultCacheConfig().disableCachingNullValues()
|
||||
.prefixCacheNameWith(CacheConsts.REDIS_CACHE_PREFIX));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RedisCacheManager redisCacheManager = new RedisCacheManager(redisCacheWriter, defaultCacheConfig, cacheMap);
|
||||
RedisCacheManager redisCacheManager = new RedisCacheManager(redisCacheWriter,
|
||||
defaultCacheConfig, cacheMap);
|
||||
redisCacheManager.setTransactionAware(true);
|
||||
redisCacheManager.initializeCaches();
|
||||
return redisCacheManager;
|
||||
|
@ -37,7 +37,7 @@ public class CorsConfig {
|
||||
|
||||
UrlBasedCorsConfigurationSource configurationSource = new UrlBasedCorsConfigurationSource();
|
||||
// 添加映射路径,拦截一切请求
|
||||
configurationSource.registerCorsConfiguration("/**",config);
|
||||
configurationSource.registerCorsConfiguration("/**", config);
|
||||
return new CorsFilter(configurationSource);
|
||||
}
|
||||
|
||||
|
@ -26,7 +26,7 @@ public class EsConfig {
|
||||
|
||||
// Create the transport with a Jackson mapper
|
||||
ElasticsearchTransport transport = new RestClientTransport(
|
||||
restClient, new JacksonJsonpMapper());
|
||||
restClient, new JacksonJsonpMapper());
|
||||
|
||||
// And create the API client
|
||||
return new ElasticsearchClient(transport);
|
||||
|
@ -18,7 +18,7 @@ public class RedissonConfig {
|
||||
|
||||
@Bean
|
||||
@SneakyThrows
|
||||
public RedissonClient redissonClient(){
|
||||
public RedissonClient redissonClient() {
|
||||
Config config = Config.fromYAML(getClass().getResource("/redisson.yml"));
|
||||
return Redisson.create(config);
|
||||
}
|
||||
|
@ -12,9 +12,8 @@ import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
|
||||
/**
|
||||
* Spring Web Mvc 相关配置
|
||||
* 不要加 @EnableWebMvc 注解,否则会导致 jackson 的全局配置失效
|
||||
* 类上添加 @EnableWebMvc 会导致 WebMvcAutoConfiguration 中的自动配置全部失效
|
||||
* Spring Web Mvc 相关配置不要加 @EnableWebMvc 注解,否则会导致 jackson 的全局配置失效。因为 @EnableWebMvc 注解会导致
|
||||
* WebMvcAutoConfiguration 自动配置失效
|
||||
*
|
||||
* @author xiongxiaoyang
|
||||
* @date 2022/5/18
|
||||
@ -36,33 +35,33 @@ public class WebConfig implements WebMvcConfigurer {
|
||||
|
||||
// 流量限制拦截器
|
||||
registry.addInterceptor(flowLimitInterceptor)
|
||||
.addPathPatterns("/**")
|
||||
.order(0);
|
||||
.addPathPatterns("/**")
|
||||
.order(0);
|
||||
|
||||
// 文件访问拦截
|
||||
registry.addInterceptor(fileInterceptor)
|
||||
.addPathPatterns(SystemConfigConsts.IMAGE_UPLOAD_DIRECTORY + "**")
|
||||
.order(1);
|
||||
.addPathPatterns(SystemConfigConsts.IMAGE_UPLOAD_DIRECTORY + "**")
|
||||
.order(1);
|
||||
|
||||
// 权限认证拦截
|
||||
registry.addInterceptor(authInterceptor)
|
||||
// 拦截会员中心相关请求接口
|
||||
.addPathPatterns(ApiRouterConsts.API_FRONT_USER_URL_PREFIX + "/**",
|
||||
// 拦截作家后台相关请求接口
|
||||
ApiRouterConsts.API_AUTHOR_URL_PREFIX + "/**",
|
||||
// 拦截平台后台相关请求接口
|
||||
ApiRouterConsts.API_ADMIN_URL_PREFIX + "/**")
|
||||
// 放行登录注册相关请求接口
|
||||
.excludePathPatterns(ApiRouterConsts.API_FRONT_USER_URL_PREFIX + "/register",
|
||||
ApiRouterConsts.API_FRONT_USER_URL_PREFIX + "/login",
|
||||
ApiRouterConsts.API_ADMIN_URL_PREFIX + "/login")
|
||||
.order(2);
|
||||
// 拦截会员中心相关请求接口
|
||||
.addPathPatterns(ApiRouterConsts.API_FRONT_USER_URL_PREFIX + "/**",
|
||||
// 拦截作家后台相关请求接口
|
||||
ApiRouterConsts.API_AUTHOR_URL_PREFIX + "/**",
|
||||
// 拦截平台后台相关请求接口
|
||||
ApiRouterConsts.API_ADMIN_URL_PREFIX + "/**")
|
||||
// 放行登录注册相关请求接口
|
||||
.excludePathPatterns(ApiRouterConsts.API_FRONT_USER_URL_PREFIX + "/register",
|
||||
ApiRouterConsts.API_FRONT_USER_URL_PREFIX + "/login",
|
||||
ApiRouterConsts.API_ADMIN_URL_PREFIX + "/login")
|
||||
.order(2);
|
||||
|
||||
// Token 解析拦截器
|
||||
registry.addInterceptor(tokenParseInterceptor)
|
||||
// 拦截小说内容查询接口,需要解析 token 以判断该用户是否有权阅读该章节(付费章节是否已购买)
|
||||
.addPathPatterns(ApiRouterConsts.API_FRONT_BOOK_URL_PREFIX + "/content/*")
|
||||
.order(3);
|
||||
// 拦截小说内容查询接口,需要解析 token 以判断该用户是否有权阅读该章节(付费章节是否已购买)
|
||||
.addPathPatterns(ApiRouterConsts.API_FRONT_BOOK_URL_PREFIX + "/content/*")
|
||||
.order(3);
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,7 @@
|
||||
package io.github.xxyopen.novel.core.config;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
import java.util.List;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
/**
|
||||
* Xss 过滤配置属性
|
||||
@ -11,6 +10,6 @@ import java.util.List;
|
||||
* @date 2022/5/17
|
||||
*/
|
||||
@ConfigurationProperties(prefix = "novel.xss")
|
||||
public record XssProperties(Boolean enabled,List<String> excludes) {
|
||||
public record XssProperties(Boolean enabled, List<String> excludes) {
|
||||
|
||||
}
|
||||
|
@ -10,22 +10,22 @@ public class AmqpConsts {
|
||||
|
||||
/**
|
||||
* 小说信息改变 MQ
|
||||
* */
|
||||
public static class BookChangeMq{
|
||||
*/
|
||||
public static class BookChangeMq {
|
||||
|
||||
/**
|
||||
* 小说信息改变交换机
|
||||
* */
|
||||
*/
|
||||
public static final String EXCHANGE_NAME = "EXCHANGE-BOOK-CHANGE";
|
||||
|
||||
/**
|
||||
* Elasticsearch book 索引更新的队列
|
||||
* */
|
||||
*/
|
||||
public static final String QUEUE_ES_UPDATE = "QUEUE-ES-BOOK-UPDATE";
|
||||
|
||||
/**
|
||||
* Redis book 缓存更新的队列
|
||||
* */
|
||||
*/
|
||||
public static final String QUEUE_REDIS_UPDATE = "QUEUE-REDIS-BOOK-UPDATE";
|
||||
|
||||
}
|
||||
|
@ -34,32 +34,32 @@ public class ApiRouterConsts {
|
||||
|
||||
/**
|
||||
* 首页模块请求路径前缀
|
||||
* */
|
||||
*/
|
||||
public static final String HOME_URL_PREFIX = "/home";
|
||||
|
||||
/**
|
||||
* 首页模块请求路径前缀
|
||||
* */
|
||||
*/
|
||||
public static final String NEWS_URL_PREFIX = "/news";
|
||||
|
||||
/**
|
||||
* 小说模块请求路径前缀
|
||||
* */
|
||||
*/
|
||||
public static final String BOOK_URL_PREFIX = "/book";
|
||||
|
||||
/**
|
||||
* 会员模块请求路径前缀
|
||||
* */
|
||||
*/
|
||||
public static final String USER_URL_PREFIX = "/user";
|
||||
|
||||
/**
|
||||
* 资源(图片/视频/文档)模块请求路径前缀
|
||||
* */
|
||||
*/
|
||||
public static final String RESOURCE_URL_PREFIX = "/resource";
|
||||
|
||||
/**
|
||||
* 搜索模块请求路径前缀
|
||||
* */
|
||||
*/
|
||||
public static final String SEARCH_URL_PREFIX = "/search";
|
||||
|
||||
/**
|
||||
@ -85,11 +85,13 @@ public class ApiRouterConsts {
|
||||
/**
|
||||
* 前台门户资源(图片/视频/文档)相关API请求路径前缀
|
||||
*/
|
||||
public static final String API_FRONT_RESOURCE_URL_PREFIX = API_FRONT_URL_PREFIX + RESOURCE_URL_PREFIX;
|
||||
public static final String API_FRONT_RESOURCE_URL_PREFIX =
|
||||
API_FRONT_URL_PREFIX + RESOURCE_URL_PREFIX;
|
||||
|
||||
/**
|
||||
* 前台门户搜索相关API请求路径前缀
|
||||
* */
|
||||
public static final String API_FRONT_SEARCH_URL_PREFIX = API_FRONT_URL_PREFIX + SEARCH_URL_PREFIX;
|
||||
*/
|
||||
public static final String API_FRONT_SEARCH_URL_PREFIX =
|
||||
API_FRONT_URL_PREFIX + SEARCH_URL_PREFIX;
|
||||
|
||||
}
|
||||
|
@ -56,7 +56,7 @@ public class CacheConsts {
|
||||
|
||||
/**
|
||||
* 小说分类列表缓存
|
||||
* */
|
||||
*/
|
||||
public static final String BOOK_CATEGORY_LIST_CACHE_NAME = "bookCategoryListCache";
|
||||
|
||||
/**
|
||||
@ -76,13 +76,14 @@ public class CacheConsts {
|
||||
|
||||
/**
|
||||
* 最近更新小说ID列表缓存
|
||||
* */
|
||||
*/
|
||||
public static final String LAST_UPDATE_BOOK_ID_LIST_CACHE_NAME = "lastUpdateBookIdListCache";
|
||||
|
||||
/**
|
||||
* 图片验证码缓存 KEY
|
||||
* */
|
||||
public static final String IMG_VERIFY_CODE_CACHE_KEY = REDIS_CACHE_PREFIX + "imgVerifyCodeCache::";
|
||||
*/
|
||||
public static final String IMG_VERIFY_CODE_CACHE_KEY =
|
||||
REDIS_CACHE_PREFIX + "imgVerifyCodeCache::";
|
||||
|
||||
/**
|
||||
* 用户信息缓存
|
||||
@ -111,19 +112,19 @@ public class CacheConsts {
|
||||
|
||||
HOME_FRIEND_LINK_CACHE(2, HOME_FRIEND_LINK_CACHE_NAME, 0, 1),
|
||||
|
||||
BOOK_CATEGORY_LIST_CACHE(0,BOOK_CATEGORY_LIST_CACHE_NAME,0,2),
|
||||
BOOK_CATEGORY_LIST_CACHE(0, BOOK_CATEGORY_LIST_CACHE_NAME, 0, 2),
|
||||
|
||||
BOOK_INFO_CACHE(0, BOOK_INFO_CACHE_NAME, 60 * 60 * 18, 500),
|
||||
|
||||
BOOK_CHAPTER_CACHE(0,BOOK_CHAPTER_CACHE_NAME,60 * 60 * 6,5000),
|
||||
BOOK_CHAPTER_CACHE(0, BOOK_CHAPTER_CACHE_NAME, 60 * 60 * 6, 5000),
|
||||
|
||||
BOOK_CONTENT_CACHE(2, BOOK_CONTENT_CACHE_NAME, 60 * 60 * 12, 3000),
|
||||
|
||||
LAST_UPDATE_BOOK_ID_LIST_CACHE(0,LAST_UPDATE_BOOK_ID_LIST_CACHE_NAME,60 * 60, 10),
|
||||
LAST_UPDATE_BOOK_ID_LIST_CACHE(0, LAST_UPDATE_BOOK_ID_LIST_CACHE_NAME, 60 * 60, 10),
|
||||
|
||||
USER_INFO_CACHE(2,USER_INFO_CACHE_NAME,60 * 60 * 24, 10000),
|
||||
USER_INFO_CACHE(2, USER_INFO_CACHE_NAME, 60 * 60 * 24, 10000),
|
||||
|
||||
AUTHOR_INFO_CACHE(2,AUTHOR_INFO_CACHE_NAME,60 * 60 * 48, 1000);
|
||||
AUTHOR_INFO_CACHE(2, AUTHOR_INFO_CACHE_NAME, 60 * 60 * 48, 1000);
|
||||
|
||||
/**
|
||||
* 缓存类型 0-本地 1-本地和远程 2-远程
|
||||
|
@ -10,7 +10,6 @@ import lombok.Getter;
|
||||
*/
|
||||
public class DatabaseConsts {
|
||||
|
||||
|
||||
/**
|
||||
* 用户信息表
|
||||
*/
|
||||
|
@ -14,8 +14,8 @@ public class EsConsts {
|
||||
|
||||
/**
|
||||
* 小说索引
|
||||
* */
|
||||
public static class BookIndex{
|
||||
*/
|
||||
public static class BookIndex {
|
||||
|
||||
private BookIndex() {
|
||||
throw new IllegalStateException(SystemConfigConsts.CONST_INSTANCE_EXCEPTION_MSG);
|
||||
@ -23,18 +23,18 @@ public class EsConsts {
|
||||
|
||||
/**
|
||||
* 索引名
|
||||
* */
|
||||
*/
|
||||
public static final String INDEX_NAME = "book";
|
||||
|
||||
/**
|
||||
* id
|
||||
*/
|
||||
public static final String FIELD_ID = "id";
|
||||
public static final String FIELD_ID = "id";
|
||||
|
||||
/**
|
||||
* 作品方向;0-男频 1-女频
|
||||
*/
|
||||
public static final String FIELD_WORK_DIRECTION = "workDirection";
|
||||
public static final String FIELD_WORK_DIRECTION = "workDirection";
|
||||
|
||||
/**
|
||||
* 类别ID
|
||||
@ -110,7 +110,7 @@ public class EsConsts {
|
||||
* 是否收费;1-收费 0-免费
|
||||
*/
|
||||
public static final String FIELD_IS_VIP = "isVip";
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -14,32 +14,32 @@ public class SystemConfigConsts {
|
||||
|
||||
/**
|
||||
* Http 请求认证 Header
|
||||
* */
|
||||
*/
|
||||
public static final String HTTP_AUTH_HEADER_NAME = "Authorization";
|
||||
|
||||
/**
|
||||
* 前台门户系统标识
|
||||
* */
|
||||
*/
|
||||
public static final String NOVEL_FRONT_KEY = "front";
|
||||
|
||||
/**
|
||||
* 作家管理系统标识
|
||||
* */
|
||||
*/
|
||||
public static final String NOVEL_AUTHOR_KEY = "author";
|
||||
|
||||
/**
|
||||
* 后台管理系统标识
|
||||
* */
|
||||
*/
|
||||
public static final String NOVEL_ADMIN_KEY = "admin";
|
||||
|
||||
/**
|
||||
* 图片上传目录
|
||||
* */
|
||||
*/
|
||||
public static final String IMAGE_UPLOAD_DIRECTORY = "/image/";
|
||||
|
||||
/**
|
||||
* 常量类实例化异常信息
|
||||
* */
|
||||
*/
|
||||
public static final String CONST_INSTANCE_EXCEPTION_MSG = "Constant class";
|
||||
|
||||
}
|
||||
|
@ -3,18 +3,22 @@ package io.github.xxyopen.novel.core.filter;
|
||||
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
|
||||
import io.github.xxyopen.novel.core.config.XssProperties;
|
||||
import io.github.xxyopen.novel.core.wrapper.XssHttpServletRequestWrapper;
|
||||
import jakarta.servlet.*;
|
||||
import jakarta.servlet.Filter;
|
||||
import jakarta.servlet.FilterChain;
|
||||
import jakarta.servlet.FilterConfig;
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.ServletRequest;
|
||||
import jakarta.servlet.ServletResponse;
|
||||
import jakarta.servlet.annotation.WebFilter;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import java.io.IOException;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* 防止 XSS 攻击的过滤器
|
||||
*
|
||||
@ -22,13 +26,12 @@ import java.util.regex.Pattern;
|
||||
* @date 2022/5/17
|
||||
*/
|
||||
@Component
|
||||
@ConditionalOnProperty(value = "novel.xss.enabled",havingValue = "true")
|
||||
@ConditionalOnProperty(value = "novel.xss.enabled", havingValue = "true")
|
||||
@WebFilter(urlPatterns = "/*", filterName = "xssFilter")
|
||||
@EnableConfigurationProperties(value = {XssProperties.class})
|
||||
@RequiredArgsConstructor
|
||||
public class XssFilter implements Filter {
|
||||
|
||||
|
||||
private final XssProperties xssProperties;
|
||||
|
||||
@Override
|
||||
@ -37,13 +40,15 @@ public class XssFilter implements Filter {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
|
||||
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse,
|
||||
FilterChain filterChain) throws IOException, ServletException {
|
||||
HttpServletRequest req = (HttpServletRequest) servletRequest;
|
||||
if (handleExcludeUrl(req)) {
|
||||
filterChain.doFilter(servletRequest, servletResponse);
|
||||
return;
|
||||
}
|
||||
XssHttpServletRequestWrapper xssRequest = new XssHttpServletRequestWrapper((HttpServletRequest) servletRequest);
|
||||
XssHttpServletRequestWrapper xssRequest = new XssHttpServletRequestWrapper(
|
||||
(HttpServletRequest) servletRequest);
|
||||
filterChain.doFilter(xssRequest, servletResponse);
|
||||
}
|
||||
|
||||
|
@ -9,18 +9,16 @@ import io.github.xxyopen.novel.core.constant.ApiRouterConsts;
|
||||
import io.github.xxyopen.novel.core.constant.SystemConfigConsts;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Map;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.servlet.HandlerInterceptor;
|
||||
import org.springframework.web.servlet.ModelAndView;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 认证 拦截器
|
||||
* 为了注入其它的 Spring beans,需要通过 @Component 注解将该拦截器注册到 Spring 上下文
|
||||
* 认证 拦截器:为了注入其它的 Spring beans,需要通过 @Component 注解将该拦截器注册到 Spring 上下文
|
||||
*
|
||||
* @author xiongxiaoyang
|
||||
* @date 2022/5/18
|
||||
@ -29,13 +27,14 @@ import java.util.Map;
|
||||
@RequiredArgsConstructor
|
||||
public class AuthInterceptor implements HandlerInterceptor {
|
||||
|
||||
private final Map<String,AuthStrategy> authStrategy;
|
||||
private final Map<String, AuthStrategy> authStrategy;
|
||||
|
||||
private final ObjectMapper objectMapper;
|
||||
|
||||
@SuppressWarnings("NullableProblems")
|
||||
@Override
|
||||
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
|
||||
public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
|
||||
Object handler) throws Exception {
|
||||
// 获取登录 JWT
|
||||
String token = request.getHeader(SystemConfigConsts.HTTP_AUTH_HEADER_NAME);
|
||||
|
||||
@ -44,25 +43,27 @@ public class AuthInterceptor implements HandlerInterceptor {
|
||||
|
||||
// 根据请求的 URI 得到认证策略
|
||||
String subUri = requestUri.substring(ApiRouterConsts.API_URL_PREFIX.length() + 1);
|
||||
String systemName = subUri.substring(0,subUri.indexOf("/"));
|
||||
String authStrategyName = String.format("%sAuthStrategy",systemName);
|
||||
String systemName = subUri.substring(0, subUri.indexOf("/"));
|
||||
String authStrategyName = String.format("%sAuthStrategy", systemName);
|
||||
|
||||
// 开始认证
|
||||
try {
|
||||
authStrategy.get(authStrategyName).auth(token,requestUri);
|
||||
authStrategy.get(authStrategyName).auth(token, requestUri);
|
||||
return HandlerInterceptor.super.preHandle(request, response, handler);
|
||||
}catch (BusinessException exception){
|
||||
} catch (BusinessException exception) {
|
||||
// 认证失败
|
||||
response.setCharacterEncoding(StandardCharsets.UTF_8.name());
|
||||
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
|
||||
response.getWriter().write(objectMapper.writeValueAsString(RestResp.fail(exception.getErrorCodeEnum())));
|
||||
response.getWriter().write(
|
||||
objectMapper.writeValueAsString(RestResp.fail(exception.getErrorCodeEnum())));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("NullableProblems")
|
||||
@Override
|
||||
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
|
||||
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
|
||||
ModelAndView modelAndView) throws Exception {
|
||||
// 清理当前线程保存的用户数据
|
||||
UserHolder.clear();
|
||||
HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
|
||||
|
@ -2,15 +2,14 @@ package io.github.xxyopen.novel.core.interceptor;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.servlet.HandlerInterceptor;
|
||||
|
||||
import java.io.FileInputStream;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
|
||||
/**
|
||||
* 文件 拦截器
|
||||
*
|
||||
@ -26,12 +25,14 @@ public class FileInterceptor implements HandlerInterceptor {
|
||||
|
||||
@SuppressWarnings("NullableProblems")
|
||||
@Override
|
||||
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
|
||||
public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
|
||||
Object handler) throws Exception {
|
||||
// 获取请求的 URI
|
||||
String requestUri = request.getRequestURI();
|
||||
// 缓存10天
|
||||
response.setDateHeader("expires", System.currentTimeMillis() + 60 * 60 * 24 * 10 * 1000);
|
||||
try (OutputStream out = response.getOutputStream(); InputStream input = new FileInputStream(fileUploadPath + requestUri)) {
|
||||
try (OutputStream out = response.getOutputStream(); InputStream input = new FileInputStream(
|
||||
fileUploadPath + requestUri)) {
|
||||
byte[] b = new byte[4096];
|
||||
for (int n; (n = input.read(b)) != -1; ) {
|
||||
out.write(b, 0, n);
|
||||
|
@ -15,20 +15,18 @@ import io.github.xxyopen.novel.core.common.resp.RestResp;
|
||||
import io.github.xxyopen.novel.core.common.util.IpUtils;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.servlet.HandlerInterceptor;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 流量限制 拦截器
|
||||
* 实现接口防刷和限流
|
||||
* 流量限制 拦截器:实现接口防刷和限流
|
||||
*
|
||||
* @author xiongxiaoyang
|
||||
* @date 2022/6/1
|
||||
@ -59,18 +57,19 @@ public class FlowLimitInterceptor implements HandlerInterceptor {
|
||||
|
||||
// 接口防刷规则 1:所有的请求,限制每个 IP 每秒最多只能通过 50 个,超出限制直接拒绝
|
||||
ParamFlowRule rule2 = new ParamFlowRule(NOVEL_RESOURCE)
|
||||
.setParamIdx(0)
|
||||
.setCount(50);
|
||||
.setParamIdx(0)
|
||||
.setCount(50);
|
||||
// 接口防刷规则 2:所有的请求,限制每个 IP 每分钟最多只能通过 1000 个,超出限制直接拒绝
|
||||
ParamFlowRule rule3 = new ParamFlowRule(NOVEL_RESOURCE)
|
||||
.setParamIdx(0)
|
||||
.setCount(1000)
|
||||
.setDurationInSec(60);
|
||||
.setParamIdx(0)
|
||||
.setCount(1000)
|
||||
.setDurationInSec(60);
|
||||
ParamFlowRuleManager.loadRules(Arrays.asList(rule2, rule3));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
|
||||
public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
|
||||
Object handler) throws Exception {
|
||||
String ip = IpUtils.getRealIp(request);
|
||||
Entry entry = null;
|
||||
try {
|
||||
@ -85,7 +84,8 @@ public class FlowLimitInterceptor implements HandlerInterceptor {
|
||||
log.info("IP:{}被限流了!", ip);
|
||||
response.setCharacterEncoding(StandardCharsets.UTF_8.name());
|
||||
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
|
||||
response.getWriter().write(objectMapper.writeValueAsString(RestResp.fail(ErrorCodeEnum.USER_REQ_MANY)));
|
||||
response.getWriter()
|
||||
.write(objectMapper.writeValueAsString(RestResp.fail(ErrorCodeEnum.USER_REQ_MANY)));
|
||||
} finally {
|
||||
// 注意:exit 的时候也一定要带上对应的参数,否则可能会有统计错误。
|
||||
if (entry != null) {
|
||||
|
@ -24,7 +24,8 @@ public class TokenParseInterceptor implements HandlerInterceptor {
|
||||
private final JwtUtils jwtUtils;
|
||||
|
||||
@Override
|
||||
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
|
||||
public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
|
||||
Object handler) throws Exception {
|
||||
// 获取登录 JWT
|
||||
String token = request.getHeader(SystemConfigConsts.HTTP_AUTH_HEADER_NAME);
|
||||
if (StringUtils.hasText(token)) {
|
||||
@ -35,7 +36,8 @@ public class TokenParseInterceptor implements HandlerInterceptor {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
|
||||
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
|
||||
ModelAndView modelAndView) throws Exception {
|
||||
// 清理当前线程保存的用户数据
|
||||
UserHolder.clear();
|
||||
HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
|
||||
|
@ -3,9 +3,8 @@ package io.github.xxyopen.novel.core.json.deserializer;
|
||||
import com.fasterxml.jackson.core.JsonParser;
|
||||
import com.fasterxml.jackson.databind.DeserializationContext;
|
||||
import com.fasterxml.jackson.databind.JsonDeserializer;
|
||||
import org.springframework.boot.jackson.JsonComponent;
|
||||
|
||||
import java.io.IOException;
|
||||
import org.springframework.boot.jackson.JsonComponent;
|
||||
|
||||
|
||||
/**
|
||||
@ -18,16 +17,16 @@ import java.io.IOException;
|
||||
public class GlobalJsonDeserializer {
|
||||
|
||||
/**
|
||||
* 字符串反序列化器
|
||||
* 过滤特殊字符,解决 XSS 攻击
|
||||
* 字符串反序列化器:过滤特殊字符,解决 XSS 攻击
|
||||
*/
|
||||
public static class StringDeserializer extends JsonDeserializer<String> {
|
||||
|
||||
@Override
|
||||
public String deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException {
|
||||
public String deserialize(JsonParser jsonParser,
|
||||
DeserializationContext deserializationContext) throws IOException {
|
||||
return jsonParser.getValueAsString()
|
||||
.replace("<", "<")
|
||||
.replace(">", ">");
|
||||
.replace("<", "<")
|
||||
.replace(">", ">");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,6 @@ package io.github.xxyopen.novel.core.json.serializer;
|
||||
import com.fasterxml.jackson.core.JsonGenerator;
|
||||
import com.fasterxml.jackson.databind.JsonSerializer;
|
||||
import com.fasterxml.jackson.databind.SerializerProvider;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
@ -15,8 +14,9 @@ import java.io.IOException;
|
||||
public class UsernameSerializer extends JsonSerializer<String> {
|
||||
|
||||
@Override
|
||||
public void serialize(String s, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
|
||||
jsonGenerator.writeString(s.substring(0,4) + "****" + s.substring(8));
|
||||
public void serialize(String s, JsonGenerator jsonGenerator,
|
||||
SerializerProvider serializerProvider) throws IOException {
|
||||
jsonGenerator.writeString(s.substring(0, 4) + "****" + s.substring(8));
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -21,7 +21,8 @@ import org.springframework.stereotype.Component;
|
||||
* @date 2022/5/25
|
||||
*/
|
||||
@Component
|
||||
@ConditionalOnProperty(prefix = "spring", name = {"elasticsearch.enable","amqp.enable"}, havingValue = "true")
|
||||
@ConditionalOnProperty(prefix = "spring", name = {"elasticsearch.enable",
|
||||
"amqp.enable"}, havingValue = "true")
|
||||
@RequiredArgsConstructor
|
||||
@Slf4j
|
||||
public class RabbitQueueListener {
|
||||
@ -32,15 +33,15 @@ public class RabbitQueueListener {
|
||||
|
||||
/**
|
||||
* 监听小说信息改变的 ES 更新队列,更新最新小说信息到 ES
|
||||
* */
|
||||
*/
|
||||
@RabbitListener(queues = AmqpConsts.BookChangeMq.QUEUE_ES_UPDATE)
|
||||
@SneakyThrows
|
||||
public void updateEsBook(Long bookId) {
|
||||
BookInfo bookInfo = bookInfoMapper.selectById(bookId);
|
||||
IndexResponse response = esClient.index(i -> i
|
||||
.index(EsConsts.BookIndex.INDEX_NAME)
|
||||
.id(bookInfo.getId().toString())
|
||||
.document(EsBookDto.build(bookInfo))
|
||||
.index(EsConsts.BookIndex.INDEX_NAME)
|
||||
.id(bookInfo.getId().toString())
|
||||
.document(EsBookDto.build(bookInfo))
|
||||
);
|
||||
log.info("Indexed with version " + response.version());
|
||||
}
|
||||
|
@ -13,14 +13,13 @@ import io.github.xxyopen.novel.core.constant.EsConsts;
|
||||
import io.github.xxyopen.novel.dao.entity.BookInfo;
|
||||
import io.github.xxyopen.novel.dao.mapper.BookInfoMapper;
|
||||
import io.github.xxyopen.novel.dto.es.EsBookDto;
|
||||
import java.util.List;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.SneakyThrows;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 小说数据同步到 elasticsearch 任务
|
||||
*
|
||||
@ -51,10 +50,10 @@ public class BookToEsTask {
|
||||
for (; ; ) {
|
||||
queryWrapper.clear();
|
||||
queryWrapper
|
||||
.orderByAsc(DatabaseConsts.CommonColumnEnum.ID.getName())
|
||||
.gt(DatabaseConsts.CommonColumnEnum.ID.getName(), maxId)
|
||||
.gt(DatabaseConsts.BookTable.COLUMN_WORD_COUNT, 0)
|
||||
.last(DatabaseConsts.SqlEnum.LIMIT_30.getSql());
|
||||
.orderByAsc(DatabaseConsts.CommonColumnEnum.ID.getName())
|
||||
.gt(DatabaseConsts.CommonColumnEnum.ID.getName(), maxId)
|
||||
.gt(DatabaseConsts.BookTable.COLUMN_WORD_COUNT, 0)
|
||||
.last(DatabaseConsts.SqlEnum.LIMIT_30.getSql());
|
||||
bookInfos = bookInfoMapper.selectList(queryWrapper);
|
||||
if (bookInfos.isEmpty()) {
|
||||
break;
|
||||
@ -63,11 +62,11 @@ public class BookToEsTask {
|
||||
|
||||
for (BookInfo book : bookInfos) {
|
||||
br.operations(op -> op
|
||||
.index(idx -> idx
|
||||
.index(EsConsts.BookIndex.INDEX_NAME)
|
||||
.id(book.getId().toString())
|
||||
.document(EsBookDto.build(book))
|
||||
)
|
||||
.index(idx -> idx
|
||||
.index(EsConsts.BookIndex.INDEX_NAME)
|
||||
.id(book.getId().toString())
|
||||
.document(EsBookDto.build(book))
|
||||
)
|
||||
).timeout(Time.of(t -> t.time("10s")));
|
||||
maxId = book.getId();
|
||||
}
|
||||
|
@ -5,14 +5,13 @@ import io.jsonwebtoken.Jws;
|
||||
import io.jsonwebtoken.JwtException;
|
||||
import io.jsonwebtoken.Jwts;
|
||||
import io.jsonwebtoken.security.Keys;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Objects;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* JWT 工具类
|
||||
*
|
||||
@ -37,21 +36,23 @@ public class JwtUtils {
|
||||
|
||||
/**
|
||||
* 根据用户ID生成JWT
|
||||
* @param uid 用户ID
|
||||
*
|
||||
* @param uid 用户ID
|
||||
* @param systemKey 系统标识
|
||||
* @return JWT
|
||||
*/
|
||||
public String generateToken(Long uid, String systemKey) {
|
||||
return Jwts.builder()
|
||||
.setHeaderParam(HEADER_SYSTEM_KEY, systemKey)
|
||||
.setSubject(uid.toString())
|
||||
.signWith(Keys.hmacShaKeyFor(secret.getBytes(StandardCharsets.UTF_8)))
|
||||
.compact();
|
||||
.setHeaderParam(HEADER_SYSTEM_KEY, systemKey)
|
||||
.setSubject(uid.toString())
|
||||
.signWith(Keys.hmacShaKeyFor(secret.getBytes(StandardCharsets.UTF_8)))
|
||||
.compact();
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析JWT返回用户ID
|
||||
* @param token JWT
|
||||
*
|
||||
* @param token JWT
|
||||
* @param systemKey 系统标识
|
||||
* @return 用户ID
|
||||
*/
|
||||
@ -59,9 +60,9 @@ public class JwtUtils {
|
||||
Jws<Claims> claimsJws;
|
||||
try {
|
||||
claimsJws = Jwts.parserBuilder()
|
||||
.setSigningKey(Keys.hmacShaKeyFor(secret.getBytes(StandardCharsets.UTF_8)))
|
||||
.build()
|
||||
.parseClaimsJws(token);
|
||||
.setSigningKey(Keys.hmacShaKeyFor(secret.getBytes(StandardCharsets.UTF_8)))
|
||||
.build()
|
||||
.parseClaimsJws(token);
|
||||
// OK, we can trust this JWT
|
||||
// 判断该 JWT 是否属于指定系统
|
||||
if (Objects.equals(claimsJws.getHeader().get(HEADER_SYSTEM_KEY), systemKey)) {
|
||||
|
@ -2,7 +2,6 @@ package io.github.xxyopen.novel.core.wrapper;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletRequestWrapper;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@ -14,7 +13,7 @@ import java.util.Map;
|
||||
*/
|
||||
public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper {
|
||||
|
||||
private static final Map<String,String> REPLACE_RULE = new HashMap<>();
|
||||
private static final Map<String, String> REPLACE_RULE = new HashMap<>();
|
||||
|
||||
static {
|
||||
REPLACE_RULE.put("<", "<");
|
||||
@ -34,7 +33,8 @@ public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper {
|
||||
for (int i = 0; i < length; i++) {
|
||||
escapeValues[i] = values[i];
|
||||
int index = i;
|
||||
REPLACE_RULE.forEach((k, v)-> escapeValues[index] = escapeValues[index].replaceAll(k, v));
|
||||
REPLACE_RULE.forEach(
|
||||
(k, v) -> escapeValues[index] = escapeValues[index].replaceAll(k, v));
|
||||
}
|
||||
return escapeValues;
|
||||
}
|
||||
|
@ -6,13 +6,12 @@ import io.github.xxyopen.novel.core.constant.DatabaseConsts;
|
||||
import io.github.xxyopen.novel.dao.entity.AuthorInfo;
|
||||
import io.github.xxyopen.novel.dao.mapper.AuthorInfoMapper;
|
||||
import io.github.xxyopen.novel.dto.AuthorInfoDto;
|
||||
import java.util.Objects;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.cache.annotation.CacheEvict;
|
||||
import org.springframework.cache.annotation.Cacheable;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* 作家信息 缓存管理类
|
||||
*
|
||||
@ -29,24 +28,24 @@ public class AuthorInfoCacheManager {
|
||||
* 查询作家信息,并放入缓存中
|
||||
*/
|
||||
@Cacheable(cacheManager = CacheConsts.REDIS_CACHE_MANAGER,
|
||||
value = CacheConsts.AUTHOR_INFO_CACHE_NAME, unless = "#result == null")
|
||||
value = CacheConsts.AUTHOR_INFO_CACHE_NAME, unless = "#result == null")
|
||||
public AuthorInfoDto getAuthor(Long userId) {
|
||||
QueryWrapper<AuthorInfo> queryWrapper = new QueryWrapper<>();
|
||||
queryWrapper
|
||||
.eq(DatabaseConsts.AuthorInfoTable.COLUMN_USER_ID, userId)
|
||||
.last(DatabaseConsts.SqlEnum.LIMIT_1.getSql());
|
||||
.eq(DatabaseConsts.AuthorInfoTable.COLUMN_USER_ID, userId)
|
||||
.last(DatabaseConsts.SqlEnum.LIMIT_1.getSql());
|
||||
AuthorInfo authorInfo = authorInfoMapper.selectOne(queryWrapper);
|
||||
if (Objects.isNull(authorInfo)) {
|
||||
return null;
|
||||
}
|
||||
return AuthorInfoDto.builder()
|
||||
.id(authorInfo.getId())
|
||||
.penName(authorInfo.getPenName())
|
||||
.status(authorInfo.getStatus()).build();
|
||||
.id(authorInfo.getId())
|
||||
.penName(authorInfo.getPenName())
|
||||
.status(authorInfo.getStatus()).build();
|
||||
}
|
||||
|
||||
@CacheEvict(cacheManager = CacheConsts.REDIS_CACHE_MANAGER,
|
||||
value = CacheConsts.AUTHOR_INFO_CACHE_NAME)
|
||||
value = CacheConsts.AUTHOR_INFO_CACHE_NAME)
|
||||
public void evictAuthorCache() {
|
||||
// 调用此方法自动清除作家信息的缓存
|
||||
}
|
||||
|
@ -6,12 +6,11 @@ import io.github.xxyopen.novel.core.constant.DatabaseConsts;
|
||||
import io.github.xxyopen.novel.dao.entity.BookCategory;
|
||||
import io.github.xxyopen.novel.dao.mapper.BookCategoryMapper;
|
||||
import io.github.xxyopen.novel.dto.resp.BookCategoryRespDto;
|
||||
import java.util.List;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.cache.annotation.Cacheable;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 小说分类 缓存管理类
|
||||
*
|
||||
@ -28,15 +27,15 @@ public class BookCategoryCacheManager {
|
||||
* 根据作品方向查询小说分类列表,并放入缓存中
|
||||
*/
|
||||
@Cacheable(cacheManager = CacheConsts.CAFFEINE_CACHE_MANAGER,
|
||||
value = CacheConsts.BOOK_CATEGORY_LIST_CACHE_NAME)
|
||||
value = CacheConsts.BOOK_CATEGORY_LIST_CACHE_NAME)
|
||||
public List<BookCategoryRespDto> listCategory(Integer workDirection) {
|
||||
QueryWrapper<BookCategory> queryWrapper = new QueryWrapper<>();
|
||||
queryWrapper.eq(DatabaseConsts.BookCategoryTable.COLUMN_WORK_DIRECTION, workDirection);
|
||||
return bookCategoryMapper.selectList(queryWrapper).stream().map(v ->
|
||||
BookCategoryRespDto.builder()
|
||||
.id(v.getId())
|
||||
.name(v.getName())
|
||||
.build()).toList();
|
||||
BookCategoryRespDto.builder()
|
||||
.id(v.getId())
|
||||
.name(v.getName())
|
||||
.build()).toList();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -24,17 +24,17 @@ public class BookChapterCacheManager {
|
||||
* 查询小说章节信息,并放入缓存中
|
||||
*/
|
||||
@Cacheable(cacheManager = CacheConsts.CAFFEINE_CACHE_MANAGER,
|
||||
value = CacheConsts.BOOK_CHAPTER_CACHE_NAME)
|
||||
value = CacheConsts.BOOK_CHAPTER_CACHE_NAME)
|
||||
public BookChapterRespDto getChapter(Long chapterId) {
|
||||
BookChapter bookChapter = bookChapterMapper.selectById(chapterId);
|
||||
return BookChapterRespDto.builder()
|
||||
.id(chapterId)
|
||||
.bookId(bookChapter.getBookId())
|
||||
.chapterNum(bookChapter.getChapterNum())
|
||||
.chapterName(bookChapter.getChapterName())
|
||||
.chapterWordCount(bookChapter.getWordCount())
|
||||
.chapterUpdateTime(bookChapter.getUpdateTime())
|
||||
.build();
|
||||
.id(chapterId)
|
||||
.bookId(bookChapter.getBookId())
|
||||
.chapterNum(bookChapter.getChapterNum())
|
||||
.chapterName(bookChapter.getChapterName())
|
||||
.chapterWordCount(bookChapter.getWordCount())
|
||||
.chapterUpdateTime(bookChapter.getUpdateTime())
|
||||
.build();
|
||||
}
|
||||
|
||||
|
||||
|
@ -25,11 +25,11 @@ public class BookContentCacheManager {
|
||||
* 查询小说内容,并放入缓存中
|
||||
*/
|
||||
@Cacheable(cacheManager = CacheConsts.REDIS_CACHE_MANAGER,
|
||||
value = CacheConsts.BOOK_CONTENT_CACHE_NAME)
|
||||
value = CacheConsts.BOOK_CONTENT_CACHE_NAME)
|
||||
public String getBookContent(Long chapterId) {
|
||||
QueryWrapper<BookContent> contentQueryWrapper = new QueryWrapper<>();
|
||||
contentQueryWrapper.eq(DatabaseConsts.BookContentTable.COLUMN_CHAPTER_ID, chapterId)
|
||||
.last(DatabaseConsts.SqlEnum.LIMIT_1.getSql());
|
||||
.last(DatabaseConsts.SqlEnum.LIMIT_1.getSql());
|
||||
BookContent bookContent = bookContentMapper.selectOne(contentQueryWrapper);
|
||||
return bookContent.getContent();
|
||||
}
|
||||
|
@ -8,14 +8,13 @@ import io.github.xxyopen.novel.dao.entity.BookInfo;
|
||||
import io.github.xxyopen.novel.dao.mapper.BookChapterMapper;
|
||||
import io.github.xxyopen.novel.dao.mapper.BookInfoMapper;
|
||||
import io.github.xxyopen.novel.dto.resp.BookInfoRespDto;
|
||||
import java.util.List;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.cache.annotation.CacheEvict;
|
||||
import org.springframework.cache.annotation.CachePut;
|
||||
import org.springframework.cache.annotation.Cacheable;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 小说信息 缓存管理类
|
||||
*
|
||||
@ -33,8 +32,9 @@ public class BookInfoCacheManager {
|
||||
/**
|
||||
* 从缓存中查询小说信息(先判断缓存中是否已存在,存在则直接从缓存中取,否则执行方法体中的逻辑后缓存结果)
|
||||
*/
|
||||
@Cacheable(cacheManager = CacheConsts.CAFFEINE_CACHE_MANAGER,
|
||||
value = CacheConsts.BOOK_INFO_CACHE_NAME)
|
||||
@Cacheable(
|
||||
cacheManager = CacheConsts.CAFFEINE_CACHE_MANAGER,
|
||||
value = CacheConsts.BOOK_INFO_CACHE_NAME)
|
||||
public BookInfoRespDto getBookInfo(Long id) {
|
||||
return cachePutBookInfo(id);
|
||||
}
|
||||
@ -42,39 +42,41 @@ public class BookInfoCacheManager {
|
||||
/**
|
||||
* 缓存小说信息(不管缓存中是否存在都执行方法体中的逻辑,然后缓存起来)
|
||||
*/
|
||||
@CachePut(cacheManager = CacheConsts.CAFFEINE_CACHE_MANAGER,
|
||||
value = CacheConsts.BOOK_INFO_CACHE_NAME)
|
||||
@CachePut(
|
||||
cacheManager = CacheConsts.CAFFEINE_CACHE_MANAGER,
|
||||
value = CacheConsts.BOOK_INFO_CACHE_NAME)
|
||||
public BookInfoRespDto cachePutBookInfo(Long id) {
|
||||
// 查询基础信息
|
||||
BookInfo bookInfo = bookInfoMapper.selectById(id);
|
||||
// 查询首章ID
|
||||
QueryWrapper<BookChapter> queryWrapper = new QueryWrapper<>();
|
||||
queryWrapper
|
||||
.eq(DatabaseConsts.BookChapterTable.COLUMN_BOOK_ID, id)
|
||||
.orderByAsc(DatabaseConsts.BookChapterTable.COLUMN_CHAPTER_NUM)
|
||||
.last(DatabaseConsts.SqlEnum.LIMIT_1.getSql());
|
||||
.eq(DatabaseConsts.BookChapterTable.COLUMN_BOOK_ID, id)
|
||||
.orderByAsc(DatabaseConsts.BookChapterTable.COLUMN_CHAPTER_NUM)
|
||||
.last(DatabaseConsts.SqlEnum.LIMIT_1.getSql());
|
||||
BookChapter firstBookChapter = bookChapterMapper.selectOne(queryWrapper);
|
||||
// 组装响应对象
|
||||
return BookInfoRespDto.builder()
|
||||
.id(bookInfo.getId())
|
||||
.bookName(bookInfo.getBookName())
|
||||
.bookDesc(bookInfo.getBookDesc())
|
||||
.bookStatus(bookInfo.getBookStatus())
|
||||
.authorId(bookInfo.getAuthorId())
|
||||
.authorName(bookInfo.getAuthorName())
|
||||
.categoryId(bookInfo.getCategoryId())
|
||||
.categoryName(bookInfo.getCategoryName())
|
||||
.commentCount(bookInfo.getCommentCount())
|
||||
.firstChapterId(firstBookChapter.getId())
|
||||
.lastChapterId(bookInfo.getLastChapterId())
|
||||
.picUrl(bookInfo.getPicUrl())
|
||||
.visitCount(bookInfo.getVisitCount())
|
||||
.wordCount(bookInfo.getWordCount())
|
||||
.build();
|
||||
.id(bookInfo.getId())
|
||||
.bookName(bookInfo.getBookName())
|
||||
.bookDesc(bookInfo.getBookDesc())
|
||||
.bookStatus(bookInfo.getBookStatus())
|
||||
.authorId(bookInfo.getAuthorId())
|
||||
.authorName(bookInfo.getAuthorName())
|
||||
.categoryId(bookInfo.getCategoryId())
|
||||
.categoryName(bookInfo.getCategoryName())
|
||||
.commentCount(bookInfo.getCommentCount())
|
||||
.firstChapterId(firstBookChapter.getId())
|
||||
.lastChapterId(bookInfo.getLastChapterId())
|
||||
.picUrl(bookInfo.getPicUrl())
|
||||
.visitCount(bookInfo.getVisitCount())
|
||||
.wordCount(bookInfo.getWordCount())
|
||||
.build();
|
||||
}
|
||||
|
||||
@CacheEvict(cacheManager = CacheConsts.CAFFEINE_CACHE_MANAGER,
|
||||
value = CacheConsts.BOOK_INFO_CACHE_NAME)
|
||||
@CacheEvict(
|
||||
cacheManager = CacheConsts.CAFFEINE_CACHE_MANAGER,
|
||||
value = CacheConsts.BOOK_INFO_CACHE_NAME)
|
||||
public void evictBookInfoCache(Long ignoredId) {
|
||||
// 调用此方法自动清除小说信息的缓存
|
||||
}
|
||||
@ -82,15 +84,16 @@ public class BookInfoCacheManager {
|
||||
/**
|
||||
* 查询每个类别下最新更新的 500 个小说ID列表,并放入缓存中 1 个小时
|
||||
*/
|
||||
@Cacheable(cacheManager = CacheConsts.CAFFEINE_CACHE_MANAGER,
|
||||
value = CacheConsts.LAST_UPDATE_BOOK_ID_LIST_CACHE_NAME)
|
||||
@Cacheable(
|
||||
cacheManager = CacheConsts.CAFFEINE_CACHE_MANAGER,
|
||||
value = CacheConsts.LAST_UPDATE_BOOK_ID_LIST_CACHE_NAME)
|
||||
public List<Long> getLastUpdateIdList(Long categoryId) {
|
||||
QueryWrapper<BookInfo> queryWrapper = new QueryWrapper<>();
|
||||
queryWrapper.eq(DatabaseConsts.BookTable.COLUMN_CATEGORY_ID, categoryId)
|
||||
.gt(DatabaseConsts.BookTable.COLUMN_WORD_COUNT,0)
|
||||
.orderByDesc(DatabaseConsts.BookTable.COLUMN_LAST_CHAPTER_UPDATE_TIME)
|
||||
.last(DatabaseConsts.SqlEnum.LIMIT_500.getSql());
|
||||
queryWrapper
|
||||
.eq(DatabaseConsts.BookTable.COLUMN_CATEGORY_ID, categoryId)
|
||||
.gt(DatabaseConsts.BookTable.COLUMN_WORD_COUNT, 0)
|
||||
.orderByDesc(DatabaseConsts.BookTable.COLUMN_LAST_CHAPTER_UPDATE_TIME)
|
||||
.last(DatabaseConsts.SqlEnum.LIMIT_500.getSql());
|
||||
return bookInfoMapper.selectList(queryWrapper).stream().map(BookInfo::getId).toList();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -6,12 +6,11 @@ import io.github.xxyopen.novel.core.constant.DatabaseConsts;
|
||||
import io.github.xxyopen.novel.dao.entity.BookInfo;
|
||||
import io.github.xxyopen.novel.dao.mapper.BookInfoMapper;
|
||||
import io.github.xxyopen.novel.dto.resp.BookRankRespDto;
|
||||
import java.util.List;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.cache.annotation.Cacheable;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 小说排行榜 缓存管理类
|
||||
*
|
||||
@ -28,7 +27,7 @@ public class BookRankCacheManager {
|
||||
* 查询小说点击榜列表,并放入缓存中
|
||||
*/
|
||||
@Cacheable(cacheManager = CacheConsts.REDIS_CACHE_MANAGER,
|
||||
value = CacheConsts.BOOK_VISIT_RANK_CACHE_NAME)
|
||||
value = CacheConsts.BOOK_VISIT_RANK_CACHE_NAME)
|
||||
public List<BookRankRespDto> listVisitRankBooks() {
|
||||
QueryWrapper<BookInfo> bookInfoQueryWrapper = new QueryWrapper<>();
|
||||
bookInfoQueryWrapper.orderByDesc(DatabaseConsts.BookTable.COLUMN_VISIT_COUNT);
|
||||
@ -39,12 +38,12 @@ public class BookRankCacheManager {
|
||||
* 查询小说新书榜列表,并放入缓存中
|
||||
*/
|
||||
@Cacheable(cacheManager = CacheConsts.CAFFEINE_CACHE_MANAGER,
|
||||
value = CacheConsts.BOOK_NEWEST_RANK_CACHE_NAME)
|
||||
value = CacheConsts.BOOK_NEWEST_RANK_CACHE_NAME)
|
||||
public List<BookRankRespDto> listNewestRankBooks() {
|
||||
QueryWrapper<BookInfo> bookInfoQueryWrapper = new QueryWrapper<>();
|
||||
bookInfoQueryWrapper
|
||||
.gt(DatabaseConsts.BookTable.COLUMN_WORD_COUNT,0)
|
||||
.orderByDesc(DatabaseConsts.CommonColumnEnum.CREATE_TIME.getName());
|
||||
.gt(DatabaseConsts.BookTable.COLUMN_WORD_COUNT, 0)
|
||||
.orderByDesc(DatabaseConsts.CommonColumnEnum.CREATE_TIME.getName());
|
||||
return listRankBooks(bookInfoQueryWrapper);
|
||||
}
|
||||
|
||||
@ -52,19 +51,19 @@ public class BookRankCacheManager {
|
||||
* 查询小说更新榜列表,并放入缓存中
|
||||
*/
|
||||
@Cacheable(cacheManager = CacheConsts.CAFFEINE_CACHE_MANAGER,
|
||||
value = CacheConsts.BOOK_UPDATE_RANK_CACHE_NAME)
|
||||
value = CacheConsts.BOOK_UPDATE_RANK_CACHE_NAME)
|
||||
public List<BookRankRespDto> listUpdateRankBooks() {
|
||||
QueryWrapper<BookInfo> bookInfoQueryWrapper = new QueryWrapper<>();
|
||||
bookInfoQueryWrapper
|
||||
.gt(DatabaseConsts.BookTable.COLUMN_WORD_COUNT,0)
|
||||
.orderByDesc(DatabaseConsts.CommonColumnEnum.UPDATE_TIME.getName());
|
||||
.gt(DatabaseConsts.BookTable.COLUMN_WORD_COUNT, 0)
|
||||
.orderByDesc(DatabaseConsts.CommonColumnEnum.UPDATE_TIME.getName());
|
||||
return listRankBooks(bookInfoQueryWrapper);
|
||||
}
|
||||
|
||||
private List<BookRankRespDto> listRankBooks(QueryWrapper<BookInfo> bookInfoQueryWrapper) {
|
||||
bookInfoQueryWrapper
|
||||
.gt(DatabaseConsts.BookTable.COLUMN_WORD_COUNT,0)
|
||||
.last(DatabaseConsts.SqlEnum.LIMIT_30.getSql());
|
||||
.gt(DatabaseConsts.BookTable.COLUMN_WORD_COUNT, 0)
|
||||
.last(DatabaseConsts.SqlEnum.LIMIT_30.getSql());
|
||||
return bookInfoMapper.selectList(bookInfoQueryWrapper).stream().map(v -> {
|
||||
BookRankRespDto respDto = new BookRankRespDto();
|
||||
respDto.setId(v.getId());
|
||||
|
@ -6,12 +6,11 @@ import io.github.xxyopen.novel.core.constant.DatabaseConsts;
|
||||
import io.github.xxyopen.novel.dao.entity.HomeFriendLink;
|
||||
import io.github.xxyopen.novel.dao.mapper.HomeFriendLinkMapper;
|
||||
import io.github.xxyopen.novel.dto.resp.HomeFriendLinkRespDto;
|
||||
import java.util.List;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.cache.annotation.Cacheable;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 友情链接 缓存管理类
|
||||
*
|
||||
@ -28,7 +27,7 @@ public class FriendLinkCacheManager {
|
||||
* 友情链接列表查询,并放入缓存中
|
||||
*/
|
||||
@Cacheable(cacheManager = CacheConsts.REDIS_CACHE_MANAGER,
|
||||
value = CacheConsts.HOME_FRIEND_LINK_CACHE_NAME)
|
||||
value = CacheConsts.HOME_FRIEND_LINK_CACHE_NAME)
|
||||
public List<HomeFriendLinkRespDto> listFriendLinks() {
|
||||
// 从友情链接表中查询出友情链接列表
|
||||
QueryWrapper<HomeFriendLink> queryWrapper = new QueryWrapper<>();
|
||||
|
@ -8,16 +8,15 @@ import io.github.xxyopen.novel.dao.entity.HomeBook;
|
||||
import io.github.xxyopen.novel.dao.mapper.BookInfoMapper;
|
||||
import io.github.xxyopen.novel.dao.mapper.HomeBookMapper;
|
||||
import io.github.xxyopen.novel.dto.resp.HomeBookRespDto;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.cache.annotation.Cacheable;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.cache.annotation.Cacheable;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
/**
|
||||
* 首页推荐小说 缓存管理类
|
||||
@ -37,7 +36,7 @@ public class HomeBookCacheManager {
|
||||
* 查询首页小说推荐,并放入缓存中
|
||||
*/
|
||||
@Cacheable(cacheManager = CacheConsts.CAFFEINE_CACHE_MANAGER,
|
||||
value = CacheConsts.HOME_BOOK_CACHE_NAME)
|
||||
value = CacheConsts.HOME_BOOK_CACHE_NAME)
|
||||
public List<HomeBookRespDto> listHomeBooks() {
|
||||
// 从首页小说推荐表中查询出需要推荐的小说
|
||||
QueryWrapper<HomeBook> queryWrapper = new QueryWrapper<>();
|
||||
@ -47,8 +46,8 @@ public class HomeBookCacheManager {
|
||||
// 获取推荐小说ID列表
|
||||
if (!CollectionUtils.isEmpty(homeBooks)) {
|
||||
List<Long> bookIds = homeBooks.stream()
|
||||
.map(HomeBook::getBookId)
|
||||
.toList();
|
||||
.map(HomeBook::getBookId)
|
||||
.toList();
|
||||
|
||||
// 根据小说ID列表查询相关的小说信息列表
|
||||
QueryWrapper<BookInfo> bookInfoQueryWrapper = new QueryWrapper<>();
|
||||
@ -56,9 +55,9 @@ public class HomeBookCacheManager {
|
||||
List<BookInfo> bookInfos = bookInfoMapper.selectList(bookInfoQueryWrapper);
|
||||
|
||||
// 组装 HomeBookRespDto 列表数据并返回
|
||||
if(!CollectionUtils.isEmpty(bookInfos)){
|
||||
if (!CollectionUtils.isEmpty(bookInfos)) {
|
||||
Map<Long, BookInfo> bookInfoMap = bookInfos.stream()
|
||||
.collect(Collectors.toMap(BookInfo::getId, Function.identity()));
|
||||
.collect(Collectors.toMap(BookInfo::getId, Function.identity()));
|
||||
return homeBooks.stream().map(v -> {
|
||||
BookInfo bookInfo = bookInfoMap.get(v.getBookId());
|
||||
HomeBookRespDto bookRespDto = new HomeBookRespDto();
|
||||
|
@ -6,12 +6,11 @@ import io.github.xxyopen.novel.core.constant.DatabaseConsts;
|
||||
import io.github.xxyopen.novel.dao.entity.NewsInfo;
|
||||
import io.github.xxyopen.novel.dao.mapper.NewsInfoMapper;
|
||||
import io.github.xxyopen.novel.dto.resp.NewsInfoRespDto;
|
||||
import java.util.List;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.cache.annotation.Cacheable;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 新闻 缓存管理类
|
||||
*
|
||||
@ -28,20 +27,20 @@ public class NewsCacheManager {
|
||||
* 最新新闻列表查询,并放入缓存中
|
||||
*/
|
||||
@Cacheable(cacheManager = CacheConsts.CAFFEINE_CACHE_MANAGER,
|
||||
value = CacheConsts.LATEST_NEWS_CACHE_NAME)
|
||||
value = CacheConsts.LATEST_NEWS_CACHE_NAME)
|
||||
public List<NewsInfoRespDto> listLatestNews() {
|
||||
// 从新闻信息表中查询出最新发布的两条新闻
|
||||
QueryWrapper<NewsInfo> queryWrapper = new QueryWrapper<>();
|
||||
queryWrapper.orderByDesc(DatabaseConsts.CommonColumnEnum.CREATE_TIME.getName())
|
||||
.last(DatabaseConsts.SqlEnum.LIMIT_2.getSql());
|
||||
.last(DatabaseConsts.SqlEnum.LIMIT_2.getSql());
|
||||
return newsInfoMapper.selectList(queryWrapper).stream().map(v -> NewsInfoRespDto.builder()
|
||||
.id(v.getId())
|
||||
.categoryId(v.getCategoryId())
|
||||
.categoryName(v.getCategoryName())
|
||||
.title(v.getTitle())
|
||||
.sourceName(v.getSourceName())
|
||||
.updateTime(v.getUpdateTime())
|
||||
.build()).toList();
|
||||
.id(v.getId())
|
||||
.categoryId(v.getCategoryId())
|
||||
.categoryName(v.getCategoryName())
|
||||
.title(v.getTitle())
|
||||
.sourceName(v.getSourceName())
|
||||
.updateTime(v.getUpdateTime())
|
||||
.build()).toList();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -4,12 +4,11 @@ import io.github.xxyopen.novel.core.constant.CacheConsts;
|
||||
import io.github.xxyopen.novel.dao.entity.UserInfo;
|
||||
import io.github.xxyopen.novel.dao.mapper.UserInfoMapper;
|
||||
import io.github.xxyopen.novel.dto.UserInfoDto;
|
||||
import java.util.Objects;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.cache.annotation.Cacheable;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* 用户信息 缓存管理类
|
||||
*
|
||||
@ -26,15 +25,15 @@ public class UserInfoCacheManager {
|
||||
* 查询用户信息,并放入缓存中
|
||||
*/
|
||||
@Cacheable(cacheManager = CacheConsts.REDIS_CACHE_MANAGER,
|
||||
value = CacheConsts.USER_INFO_CACHE_NAME)
|
||||
value = CacheConsts.USER_INFO_CACHE_NAME)
|
||||
public UserInfoDto getUser(Long userId) {
|
||||
UserInfo userInfo = userInfoMapper.selectById(userId);
|
||||
if (Objects.isNull(userInfo)) {
|
||||
return null;
|
||||
}
|
||||
return UserInfoDto.builder()
|
||||
.id(userInfo.getId())
|
||||
.status(userInfo.getStatus()).build();
|
||||
.id(userInfo.getId())
|
||||
.status(userInfo.getStatus()).build();
|
||||
}
|
||||
|
||||
|
||||
|
@ -4,13 +4,13 @@ import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import io.github.xxyopen.novel.core.constant.DatabaseConsts;
|
||||
import io.github.xxyopen.novel.dao.entity.UserInfo;
|
||||
import io.github.xxyopen.novel.dao.mapper.UserInfoMapper;
|
||||
import java.util.List;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 用户模块 DAO管理类
|
||||
*
|
||||
* @author xiongxiaoyang
|
||||
* @date 2022/5/20
|
||||
*/
|
||||
@ -22,12 +22,13 @@ public class UserDaoManager {
|
||||
|
||||
/**
|
||||
* 根据用户ID集合批量查询用户信息列表
|
||||
*
|
||||
* @param userIds 需要查询的用户ID集合
|
||||
* @return 满足条件的用户信息列表
|
||||
* */
|
||||
public List<UserInfo> listUsers(List<Long> userIds){
|
||||
*/
|
||||
public List<UserInfo> listUsers(List<Long> userIds) {
|
||||
QueryWrapper<UserInfo> queryWrapper = new QueryWrapper<>();
|
||||
queryWrapper.in(DatabaseConsts.CommonColumnEnum.ID.getName(),userIds);
|
||||
queryWrapper.in(DatabaseConsts.CommonColumnEnum.ID.getName(), userIds);
|
||||
return userInfoMapper.selectList(queryWrapper);
|
||||
}
|
||||
|
||||
|
@ -2,6 +2,7 @@ package io.github.xxyopen.novel.manager.mq;
|
||||
|
||||
import io.github.xxyopen.novel.core.common.constant.CommonConsts;
|
||||
import io.github.xxyopen.novel.core.constant.AmqpConsts;
|
||||
import java.util.Objects;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.amqp.core.AmqpTemplate;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
@ -9,8 +10,6 @@ import org.springframework.stereotype.Component;
|
||||
import org.springframework.transaction.support.TransactionSynchronization;
|
||||
import org.springframework.transaction.support.TransactionSynchronizationManager;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* AMQP 消息管理类
|
||||
*
|
||||
@ -35,15 +34,17 @@ public class AmqpMsgManager {
|
||||
}
|
||||
}
|
||||
|
||||
private void sendAmqpMessage(AmqpTemplate amqpTemplate, String exchange, String routingKey, Object message) {
|
||||
private void sendAmqpMessage(AmqpTemplate amqpTemplate, String exchange, String routingKey,
|
||||
Object message) {
|
||||
// 如果在事务中则在事务执行完成后再发送,否则可以直接发送
|
||||
if (TransactionSynchronizationManager.isActualTransactionActive()) {
|
||||
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
|
||||
@Override
|
||||
public void afterCommit() {
|
||||
amqpTemplate.convertAndSend(exchange, routingKey, message);
|
||||
}
|
||||
});
|
||||
TransactionSynchronizationManager.registerSynchronization(
|
||||
new TransactionSynchronization() {
|
||||
@Override
|
||||
public void afterCommit() {
|
||||
amqpTemplate.convertAndSend(exchange, routingKey, message);
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
amqpTemplate.convertAndSend(exchange, routingKey, message);
|
||||
|
@ -2,15 +2,14 @@ package io.github.xxyopen.novel.manager.redis;
|
||||
|
||||
import io.github.xxyopen.novel.core.common.util.ImgVerifyCodeUtils;
|
||||
import io.github.xxyopen.novel.core.constant.CacheConsts;
|
||||
import java.io.IOException;
|
||||
import java.time.Duration;
|
||||
import java.util.Objects;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.time.Duration;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* 验证码 管理类
|
||||
*
|
||||
@ -31,7 +30,7 @@ public class VerifyCodeManager {
|
||||
String verifyCode = ImgVerifyCodeUtils.getRandomVerifyCode(4);
|
||||
String img = ImgVerifyCodeUtils.genVerifyCodeImg(verifyCode);
|
||||
stringRedisTemplate.opsForValue().set(CacheConsts.IMG_VERIFY_CODE_CACHE_KEY + sessionId,
|
||||
verifyCode, Duration.ofMinutes(5));
|
||||
verifyCode, Duration.ofMinutes(5));
|
||||
return img;
|
||||
}
|
||||
|
||||
@ -39,7 +38,8 @@ public class VerifyCodeManager {
|
||||
* 校验图形验证码
|
||||
*/
|
||||
public boolean imgVerifyCodeOk(String sessionId, String verifyCode) {
|
||||
return Objects.equals(stringRedisTemplate.opsForValue().get(CacheConsts.IMG_VERIFY_CODE_CACHE_KEY + sessionId), verifyCode);
|
||||
return Objects.equals(stringRedisTemplate.opsForValue()
|
||||
.get(CacheConsts.IMG_VERIFY_CODE_CACHE_KEY + sessionId), verifyCode);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -21,8 +21,9 @@ public interface AuthorService {
|
||||
|
||||
/**
|
||||
* 查询作家状态
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @return 作家状态
|
||||
* */
|
||||
*/
|
||||
RestResp<Integer> getStatus(Long userId);
|
||||
}
|
||||
|
@ -11,7 +11,11 @@ import io.github.xxyopen.novel.core.common.req.PageReqDto;
|
||||
import io.github.xxyopen.novel.core.common.resp.PageRespDto;
|
||||
import io.github.xxyopen.novel.core.common.resp.RestResp;
|
||||
import io.github.xxyopen.novel.core.constant.DatabaseConsts;
|
||||
import io.github.xxyopen.novel.dao.entity.*;
|
||||
import io.github.xxyopen.novel.dao.entity.BookChapter;
|
||||
import io.github.xxyopen.novel.dao.entity.BookComment;
|
||||
import io.github.xxyopen.novel.dao.entity.BookContent;
|
||||
import io.github.xxyopen.novel.dao.entity.BookInfo;
|
||||
import io.github.xxyopen.novel.dao.entity.UserInfo;
|
||||
import io.github.xxyopen.novel.dao.mapper.BookChapterMapper;
|
||||
import io.github.xxyopen.novel.dao.mapper.BookCommentMapper;
|
||||
import io.github.xxyopen.novel.dao.mapper.BookContentMapper;
|
||||
@ -20,23 +24,39 @@ import io.github.xxyopen.novel.dto.AuthorInfoDto;
|
||||
import io.github.xxyopen.novel.dto.req.BookAddReqDto;
|
||||
import io.github.xxyopen.novel.dto.req.ChapterAddReqDto;
|
||||
import io.github.xxyopen.novel.dto.req.UserCommentReqDto;
|
||||
import io.github.xxyopen.novel.dto.resp.*;
|
||||
import io.github.xxyopen.novel.manager.cache.*;
|
||||
import io.github.xxyopen.novel.dto.resp.BookCategoryRespDto;
|
||||
import io.github.xxyopen.novel.dto.resp.BookChapterAboutRespDto;
|
||||
import io.github.xxyopen.novel.dto.resp.BookChapterRespDto;
|
||||
import io.github.xxyopen.novel.dto.resp.BookCommentRespDto;
|
||||
import io.github.xxyopen.novel.dto.resp.BookContentAboutRespDto;
|
||||
import io.github.xxyopen.novel.dto.resp.BookInfoRespDto;
|
||||
import io.github.xxyopen.novel.dto.resp.BookRankRespDto;
|
||||
import io.github.xxyopen.novel.manager.cache.AuthorInfoCacheManager;
|
||||
import io.github.xxyopen.novel.manager.cache.BookCategoryCacheManager;
|
||||
import io.github.xxyopen.novel.manager.cache.BookChapterCacheManager;
|
||||
import io.github.xxyopen.novel.manager.cache.BookContentCacheManager;
|
||||
import io.github.xxyopen.novel.manager.cache.BookInfoCacheManager;
|
||||
import io.github.xxyopen.novel.manager.cache.BookRankCacheManager;
|
||||
import io.github.xxyopen.novel.manager.dao.UserDaoManager;
|
||||
import io.github.xxyopen.novel.manager.mq.AmqpMsgManager;
|
||||
import io.github.xxyopen.novel.service.BookService;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.SecureRandom;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Random;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.SecureRandom;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.*;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 小说模块 服务实现类
|
||||
*
|
||||
@ -100,7 +120,8 @@ public class BookServiceImpl implements BookService {
|
||||
BookInfoRespDto bookInfo = bookInfoCacheManager.getBookInfo(bookId);
|
||||
|
||||
// 查询最新章节信息
|
||||
BookChapterRespDto bookChapter = bookChapterCacheManager.getChapter(bookInfo.getLastChapterId());
|
||||
BookChapterRespDto bookChapter = bookChapterCacheManager.getChapter(
|
||||
bookInfo.getLastChapterId());
|
||||
|
||||
// 查询章节内容
|
||||
String content = bookContentCacheManager.getBookContent(bookInfo.getLastChapterId());
|
||||
@ -112,14 +133,15 @@ public class BookServiceImpl implements BookService {
|
||||
|
||||
// 组装数据并返回
|
||||
return RestResp.ok(BookChapterAboutRespDto.builder()
|
||||
.chapterInfo(bookChapter)
|
||||
.chapterTotal(chapterTotal)
|
||||
.contentSummary(content.substring(0, 30))
|
||||
.build());
|
||||
.chapterInfo(bookChapter)
|
||||
.chapterTotal(chapterTotal)
|
||||
.contentSummary(content.substring(0, 30))
|
||||
.build());
|
||||
}
|
||||
|
||||
@Override
|
||||
public RestResp<List<BookInfoRespDto>> listRecBooks(Long bookId) throws NoSuchAlgorithmException {
|
||||
public RestResp<List<BookInfoRespDto>> listRecBooks(Long bookId)
|
||||
throws NoSuchAlgorithmException {
|
||||
Long categoryId = bookInfoCacheManager.getBookInfo(bookId).getCategoryId();
|
||||
List<Long> lastUpdateIdList = bookInfoCacheManager.getLastUpdateIdList(categoryId);
|
||||
List<BookInfoRespDto> respDtoList = new ArrayList<>();
|
||||
@ -155,13 +177,13 @@ public class BookServiceImpl implements BookService {
|
||||
// 查询上一章信息并返回章节ID
|
||||
QueryWrapper<BookChapter> queryWrapper = new QueryWrapper<>();
|
||||
queryWrapper.eq(DatabaseConsts.BookChapterTable.COLUMN_BOOK_ID, bookId)
|
||||
.lt(DatabaseConsts.BookChapterTable.COLUMN_CHAPTER_NUM, chapterNum)
|
||||
.orderByDesc(DatabaseConsts.BookChapterTable.COLUMN_CHAPTER_NUM)
|
||||
.last(DatabaseConsts.SqlEnum.LIMIT_1.getSql());
|
||||
.lt(DatabaseConsts.BookChapterTable.COLUMN_CHAPTER_NUM, chapterNum)
|
||||
.orderByDesc(DatabaseConsts.BookChapterTable.COLUMN_CHAPTER_NUM)
|
||||
.last(DatabaseConsts.SqlEnum.LIMIT_1.getSql());
|
||||
return RestResp.ok(
|
||||
Optional.ofNullable(bookChapterMapper.selectOne(queryWrapper))
|
||||
.map(BookChapter::getId)
|
||||
.orElse(null)
|
||||
Optional.ofNullable(bookChapterMapper.selectOne(queryWrapper))
|
||||
.map(BookChapter::getId)
|
||||
.orElse(null)
|
||||
);
|
||||
}
|
||||
|
||||
@ -175,13 +197,13 @@ public class BookServiceImpl implements BookService {
|
||||
// 查询下一章信息并返回章节ID
|
||||
QueryWrapper<BookChapter> queryWrapper = new QueryWrapper<>();
|
||||
queryWrapper.eq(DatabaseConsts.BookChapterTable.COLUMN_BOOK_ID, bookId)
|
||||
.gt(DatabaseConsts.BookChapterTable.COLUMN_CHAPTER_NUM, chapterNum)
|
||||
.orderByAsc(DatabaseConsts.BookChapterTable.COLUMN_CHAPTER_NUM)
|
||||
.last(DatabaseConsts.SqlEnum.LIMIT_1.getSql());
|
||||
.gt(DatabaseConsts.BookChapterTable.COLUMN_CHAPTER_NUM, chapterNum)
|
||||
.orderByAsc(DatabaseConsts.BookChapterTable.COLUMN_CHAPTER_NUM)
|
||||
.last(DatabaseConsts.SqlEnum.LIMIT_1.getSql());
|
||||
return RestResp.ok(
|
||||
Optional.ofNullable(bookChapterMapper.selectOne(queryWrapper))
|
||||
.map(BookChapter::getId)
|
||||
.orElse(null)
|
||||
Optional.ofNullable(bookChapterMapper.selectOne(queryWrapper))
|
||||
.map(BookChapter::getId)
|
||||
.orElse(null)
|
||||
);
|
||||
}
|
||||
|
||||
@ -189,8 +211,9 @@ public class BookServiceImpl implements BookService {
|
||||
public RestResp<List<BookChapterRespDto>> listChapters(Long bookId) {
|
||||
QueryWrapper<BookChapter> queryWrapper = new QueryWrapper<>();
|
||||
queryWrapper.eq(DatabaseConsts.BookChapterTable.COLUMN_BOOK_ID, bookId)
|
||||
.orderByAsc(DatabaseConsts.BookChapterTable.COLUMN_CHAPTER_NUM);
|
||||
return RestResp.ok(bookChapterMapper.selectList(queryWrapper).stream().map(v -> BookChapterRespDto.builder()
|
||||
.orderByAsc(DatabaseConsts.BookChapterTable.COLUMN_CHAPTER_NUM);
|
||||
return RestResp.ok(bookChapterMapper.selectList(queryWrapper).stream()
|
||||
.map(v -> BookChapterRespDto.builder()
|
||||
.id(v.getId())
|
||||
.chapterName(v.getChapterName())
|
||||
.isVip(v.getIsVip())
|
||||
@ -204,11 +227,12 @@ public class BookServiceImpl implements BookService {
|
||||
|
||||
@Lock(prefix = "userComment")
|
||||
@Override
|
||||
public RestResp<Void> saveComment(@Key(expr = "#{userId + '::' + bookId}") UserCommentReqDto dto) {
|
||||
public RestResp<Void> saveComment(
|
||||
@Key(expr = "#{userId + '::' + bookId}") UserCommentReqDto dto) {
|
||||
// 校验用户是否已发表评论
|
||||
QueryWrapper<BookComment> queryWrapper = new QueryWrapper<>();
|
||||
queryWrapper.eq(DatabaseConsts.BookCommentTable.COLUMN_USER_ID, dto.getUserId())
|
||||
.eq(DatabaseConsts.BookCommentTable.COLUMN_BOOK_ID, dto.getBookId());
|
||||
.eq(DatabaseConsts.BookCommentTable.COLUMN_BOOK_ID, dto.getBookId());
|
||||
if (bookCommentMapper.selectCount(queryWrapper) > 0) {
|
||||
// 用户已发表评论
|
||||
return RestResp.fail(ErrorCodeEnum.USER_COMMENTED);
|
||||
@ -229,28 +253,30 @@ public class BookServiceImpl implements BookService {
|
||||
QueryWrapper<BookComment> commentCountQueryWrapper = new QueryWrapper<>();
|
||||
commentCountQueryWrapper.eq(DatabaseConsts.BookCommentTable.COLUMN_BOOK_ID, bookId);
|
||||
Long commentTotal = bookCommentMapper.selectCount(commentCountQueryWrapper);
|
||||
BookCommentRespDto bookCommentRespDto = BookCommentRespDto.builder().commentTotal(commentTotal).build();
|
||||
BookCommentRespDto bookCommentRespDto = BookCommentRespDto.builder()
|
||||
.commentTotal(commentTotal).build();
|
||||
if (commentTotal > 0) {
|
||||
|
||||
// 查询最新的评论列表
|
||||
QueryWrapper<BookComment> commentQueryWrapper = new QueryWrapper<>();
|
||||
commentQueryWrapper.eq(DatabaseConsts.BookCommentTable.COLUMN_BOOK_ID, bookId)
|
||||
.orderByDesc(DatabaseConsts.CommonColumnEnum.CREATE_TIME.getName())
|
||||
.last(DatabaseConsts.SqlEnum.LIMIT_5.getSql());
|
||||
.orderByDesc(DatabaseConsts.CommonColumnEnum.CREATE_TIME.getName())
|
||||
.last(DatabaseConsts.SqlEnum.LIMIT_5.getSql());
|
||||
List<BookComment> bookComments = bookCommentMapper.selectList(commentQueryWrapper);
|
||||
|
||||
// 查询评论用户信息,并设置需要返回的评论用户名
|
||||
List<Long> userIds = bookComments.stream().map(BookComment::getUserId).toList();
|
||||
List<UserInfo> userInfos = userDaoManager.listUsers(userIds);
|
||||
Map<Long, UserInfo> userInfoMap = userInfos.stream().collect(Collectors.toMap(UserInfo::getId, Function.identity()));
|
||||
Map<Long, UserInfo> userInfoMap = userInfos.stream()
|
||||
.collect(Collectors.toMap(UserInfo::getId, Function.identity()));
|
||||
List<BookCommentRespDto.CommentInfo> commentInfos = bookComments.stream()
|
||||
.map(v -> BookCommentRespDto.CommentInfo.builder()
|
||||
.id(v.getId())
|
||||
.commentUserId(v.getUserId())
|
||||
.commentUser(userInfoMap.get(v.getUserId()).getUsername())
|
||||
.commentUserPhoto(userInfoMap.get(v.getUserId()).getUserPhoto())
|
||||
.commentContent(v.getCommentContent())
|
||||
.commentTime(v.getCreateTime()).build()).toList();
|
||||
.map(v -> BookCommentRespDto.CommentInfo.builder()
|
||||
.id(v.getId())
|
||||
.commentUserId(v.getUserId())
|
||||
.commentUser(userInfoMap.get(v.getUserId()).getUsername())
|
||||
.commentUserPhoto(userInfoMap.get(v.getUserId()).getUserPhoto())
|
||||
.commentContent(v.getCommentContent())
|
||||
.commentTime(v.getCreateTime()).build()).toList();
|
||||
bookCommentRespDto.setComments(commentInfos);
|
||||
} else {
|
||||
bookCommentRespDto.setComments(Collections.emptyList());
|
||||
@ -262,7 +288,7 @@ public class BookServiceImpl implements BookService {
|
||||
public RestResp<Void> deleteComment(Long userId, Long commentId) {
|
||||
QueryWrapper<BookComment> queryWrapper = new QueryWrapper<>();
|
||||
queryWrapper.eq(DatabaseConsts.CommonColumnEnum.ID.getName(), commentId)
|
||||
.eq(DatabaseConsts.BookCommentTable.COLUMN_USER_ID, userId);
|
||||
.eq(DatabaseConsts.BookCommentTable.COLUMN_USER_ID, userId);
|
||||
bookCommentMapper.delete(queryWrapper);
|
||||
return RestResp.ok();
|
||||
}
|
||||
@ -271,7 +297,7 @@ public class BookServiceImpl implements BookService {
|
||||
public RestResp<Void> updateComment(Long userId, Long id, String content) {
|
||||
QueryWrapper<BookComment> queryWrapper = new QueryWrapper<>();
|
||||
queryWrapper.eq(DatabaseConsts.CommonColumnEnum.ID.getName(), id)
|
||||
.eq(DatabaseConsts.BookCommentTable.COLUMN_USER_ID, userId);
|
||||
.eq(DatabaseConsts.BookCommentTable.COLUMN_USER_ID, userId);
|
||||
BookComment bookComment = new BookComment();
|
||||
bookComment.setCommentContent(content);
|
||||
bookCommentMapper.update(bookComment, queryWrapper);
|
||||
@ -320,8 +346,8 @@ public class BookServiceImpl implements BookService {
|
||||
int chapterNum = 0;
|
||||
QueryWrapper<BookChapter> chapterQueryWrapper = new QueryWrapper<>();
|
||||
chapterQueryWrapper.eq(DatabaseConsts.BookChapterTable.COLUMN_BOOK_ID, dto.getBookId())
|
||||
.orderByDesc(DatabaseConsts.BookChapterTable.COLUMN_CHAPTER_NUM)
|
||||
.last(DatabaseConsts.SqlEnum.LIMIT_1.getSql());
|
||||
.orderByDesc(DatabaseConsts.BookChapterTable.COLUMN_CHAPTER_NUM)
|
||||
.last(DatabaseConsts.SqlEnum.LIMIT_1.getSql());
|
||||
BookChapter bookChapter = bookChapterMapper.selectOne(chapterQueryWrapper);
|
||||
if (Objects.nonNull(bookChapter)) {
|
||||
chapterNum = bookChapter.getChapterNum() + 1;
|
||||
@ -369,18 +395,18 @@ public class BookServiceImpl implements BookService {
|
||||
page.setSize(dto.getPageSize());
|
||||
QueryWrapper<BookInfo> queryWrapper = new QueryWrapper<>();
|
||||
queryWrapper.eq(DatabaseConsts.BookTable.AUTHOR_ID, UserHolder.getAuthorId())
|
||||
.orderByDesc(DatabaseConsts.CommonColumnEnum.CREATE_TIME.getName());
|
||||
.orderByDesc(DatabaseConsts.CommonColumnEnum.CREATE_TIME.getName());
|
||||
IPage<BookInfo> bookInfoPage = bookInfoMapper.selectPage(page, queryWrapper);
|
||||
return RestResp.ok(PageRespDto.of(dto.getPageNum(), dto.getPageSize(), page.getTotal(),
|
||||
bookInfoPage.getRecords().stream().map(v -> BookInfoRespDto.builder()
|
||||
.id(v.getId())
|
||||
.bookName(v.getBookName())
|
||||
.picUrl(v.getPicUrl())
|
||||
.categoryName(v.getCategoryName())
|
||||
.wordCount(v.getWordCount())
|
||||
.visitCount(v.getVisitCount())
|
||||
.updateTime(v.getUpdateTime())
|
||||
.build()).toList()));
|
||||
bookInfoPage.getRecords().stream().map(v -> BookInfoRespDto.builder()
|
||||
.id(v.getId())
|
||||
.bookName(v.getBookName())
|
||||
.picUrl(v.getPicUrl())
|
||||
.categoryName(v.getCategoryName())
|
||||
.wordCount(v.getWordCount())
|
||||
.visitCount(v.getVisitCount())
|
||||
.updateTime(v.getUpdateTime())
|
||||
.build()).toList()));
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -390,15 +416,15 @@ public class BookServiceImpl implements BookService {
|
||||
page.setSize(dto.getPageSize());
|
||||
QueryWrapper<BookChapter> queryWrapper = new QueryWrapper<>();
|
||||
queryWrapper.eq(DatabaseConsts.BookChapterTable.COLUMN_BOOK_ID, bookId)
|
||||
.orderByDesc(DatabaseConsts.BookChapterTable.COLUMN_CHAPTER_NUM);
|
||||
.orderByDesc(DatabaseConsts.BookChapterTable.COLUMN_CHAPTER_NUM);
|
||||
IPage<BookChapter> bookChapterPage = bookChapterMapper.selectPage(page, queryWrapper);
|
||||
return RestResp.ok(PageRespDto.of(dto.getPageNum(), dto.getPageSize(), page.getTotal(),
|
||||
bookChapterPage.getRecords().stream().map(v -> BookChapterRespDto.builder()
|
||||
.id(v.getId())
|
||||
.chapterName(v.getChapterName())
|
||||
.chapterUpdateTime(v.getUpdateTime())
|
||||
.isVip(v.getIsVip())
|
||||
.build()).toList()));
|
||||
bookChapterPage.getRecords().stream().map(v -> BookChapterRespDto.builder()
|
||||
.id(v.getId())
|
||||
.chapterName(v.getChapterName())
|
||||
.chapterUpdateTime(v.getUpdateTime())
|
||||
.isVip(v.getIsVip())
|
||||
.build()).toList()));
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -415,9 +441,9 @@ public class BookServiceImpl implements BookService {
|
||||
|
||||
// 组装数据并返回
|
||||
return RestResp.ok(BookContentAboutRespDto.builder()
|
||||
.bookInfo(bookInfo)
|
||||
.chapterInfo(bookChapter)
|
||||
.bookContent(content)
|
||||
.build());
|
||||
.bookInfo(bookInfo)
|
||||
.chapterInfo(bookChapter)
|
||||
.bookContent(content)
|
||||
.build());
|
||||
}
|
||||
}
|
||||
|
@ -8,13 +8,12 @@ import io.github.xxyopen.novel.dao.mapper.BookInfoMapper;
|
||||
import io.github.xxyopen.novel.dto.req.BookSearchReqDto;
|
||||
import io.github.xxyopen.novel.dto.resp.BookInfoRespDto;
|
||||
import io.github.xxyopen.novel.service.SearchService;
|
||||
import java.util.List;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 数据库搜索 服务实现类
|
||||
*
|
||||
@ -35,17 +34,18 @@ public class DbSearchServiceImpl implements SearchService {
|
||||
page.setCurrent(condition.getPageNum());
|
||||
page.setSize(condition.getPageSize());
|
||||
List<BookInfo> bookInfos = bookInfoMapper.searchBooks(page, condition);
|
||||
return RestResp.ok(PageRespDto.of(condition.getPageNum(), condition.getPageSize(), page.getTotal(),
|
||||
return RestResp.ok(
|
||||
PageRespDto.of(condition.getPageNum(), condition.getPageSize(), page.getTotal(),
|
||||
bookInfos.stream().map(v -> BookInfoRespDto.builder()
|
||||
.id(v.getId())
|
||||
.bookName(v.getBookName())
|
||||
.categoryId(v.getCategoryId())
|
||||
.categoryName(v.getCategoryName())
|
||||
.authorId(v.getAuthorId())
|
||||
.authorName(v.getAuthorName())
|
||||
.wordCount(v.getWordCount())
|
||||
.lastChapterName(v.getLastChapterName())
|
||||
.build()).toList()));
|
||||
.id(v.getId())
|
||||
.bookName(v.getBookName())
|
||||
.categoryId(v.getCategoryId())
|
||||
.categoryName(v.getCategoryName())
|
||||
.authorId(v.getAuthorId())
|
||||
.authorName(v.getAuthorName())
|
||||
.wordCount(v.getWordCount())
|
||||
.lastChapterName(v.getLastChapterName())
|
||||
.build()).toList()));
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -19,16 +19,15 @@ import io.github.xxyopen.novel.dto.es.EsBookDto;
|
||||
import io.github.xxyopen.novel.dto.req.BookSearchReqDto;
|
||||
import io.github.xxyopen.novel.dto.resp.BookInfoRespDto;
|
||||
import io.github.xxyopen.novel.service.SearchService;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.SneakyThrows;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Elasticsearch 搜索 服务实现类
|
||||
*
|
||||
@ -49,28 +48,28 @@ public class EsSearchServiceImpl implements SearchService {
|
||||
|
||||
SearchResponse<EsBookDto> response = esClient.search(s -> {
|
||||
|
||||
SearchRequest.Builder searchBuilder = s.index(EsConsts.BookIndex.INDEX_NAME);
|
||||
// 构建检索条件
|
||||
buildSearchCondition(condition, searchBuilder);
|
||||
// 排序
|
||||
if (!StringUtils.isBlank(condition.getSort())) {
|
||||
searchBuilder.sort(o -> o.field(f -> f
|
||||
.field(StringUtils.underlineToCamel(condition.getSort().split(" ")[0]))
|
||||
.order(SortOrder.Desc))
|
||||
);
|
||||
}
|
||||
// 分页
|
||||
searchBuilder.from((condition.getPageNum() - 1) * condition.getPageSize())
|
||||
.size(condition.getPageSize());
|
||||
// 设置高亮显示
|
||||
searchBuilder.highlight(h -> h.fields(EsConsts.BookIndex.FIELD_BOOK_NAME,
|
||||
t -> t.preTags("<em style='color:red'>").postTags("</em>"))
|
||||
.fields(EsConsts.BookIndex.FIELD_AUTHOR_NAME,
|
||||
t -> t.preTags("<em style='color:red'>").postTags("</em>")));
|
||||
SearchRequest.Builder searchBuilder = s.index(EsConsts.BookIndex.INDEX_NAME);
|
||||
// 构建检索条件
|
||||
buildSearchCondition(condition, searchBuilder);
|
||||
// 排序
|
||||
if (!StringUtils.isBlank(condition.getSort())) {
|
||||
searchBuilder.sort(o -> o.field(f -> f
|
||||
.field(StringUtils.underlineToCamel(condition.getSort().split(" ")[0]))
|
||||
.order(SortOrder.Desc))
|
||||
);
|
||||
}
|
||||
// 分页
|
||||
searchBuilder.from((condition.getPageNum() - 1) * condition.getPageSize())
|
||||
.size(condition.getPageSize());
|
||||
// 设置高亮显示
|
||||
searchBuilder.highlight(h -> h.fields(EsConsts.BookIndex.FIELD_BOOK_NAME,
|
||||
t -> t.preTags("<em style='color:red'>").postTags("</em>"))
|
||||
.fields(EsConsts.BookIndex.FIELD_AUTHOR_NAME,
|
||||
t -> t.preTags("<em style='color:red'>").postTags("</em>")));
|
||||
|
||||
return searchBuilder;
|
||||
},
|
||||
EsBookDto.class
|
||||
return searchBuilder;
|
||||
},
|
||||
EsBookDto.class
|
||||
);
|
||||
|
||||
TotalHits total = response.hits().total();
|
||||
@ -84,45 +83,49 @@ public class EsSearchServiceImpl implements SearchService {
|
||||
if (!CollectionUtils.isEmpty(hit.highlight().get(EsConsts.BookIndex.FIELD_BOOK_NAME))) {
|
||||
book.setBookName(hit.highlight().get(EsConsts.BookIndex.FIELD_BOOK_NAME).get(0));
|
||||
}
|
||||
if (!CollectionUtils.isEmpty(hit.highlight().get(EsConsts.BookIndex.FIELD_AUTHOR_NAME))) {
|
||||
book.setAuthorName(hit.highlight().get(EsConsts.BookIndex.FIELD_AUTHOR_NAME).get(0));
|
||||
if (!CollectionUtils.isEmpty(
|
||||
hit.highlight().get(EsConsts.BookIndex.FIELD_AUTHOR_NAME))) {
|
||||
book.setAuthorName(
|
||||
hit.highlight().get(EsConsts.BookIndex.FIELD_AUTHOR_NAME).get(0));
|
||||
}
|
||||
list.add(BookInfoRespDto.builder()
|
||||
.id(book.getId())
|
||||
.bookName(book.getBookName())
|
||||
.categoryId(book.getCategoryId())
|
||||
.categoryName(book.getCategoryName())
|
||||
.authorId(book.getAuthorId())
|
||||
.authorName(book.getAuthorName())
|
||||
.wordCount(book.getWordCount())
|
||||
.lastChapterName(book.getLastChapterName())
|
||||
.build());
|
||||
.id(book.getId())
|
||||
.bookName(book.getBookName())
|
||||
.categoryId(book.getCategoryId())
|
||||
.categoryName(book.getCategoryName())
|
||||
.authorId(book.getAuthorId())
|
||||
.authorName(book.getAuthorName())
|
||||
.wordCount(book.getWordCount())
|
||||
.lastChapterName(book.getLastChapterName())
|
||||
.build());
|
||||
}
|
||||
assert total != null;
|
||||
return RestResp.ok(PageRespDto.of(condition.getPageNum(), condition.getPageSize(), total.value(), list));
|
||||
return RestResp.ok(
|
||||
PageRespDto.of(condition.getPageNum(), condition.getPageSize(), total.value(), list));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建检索条件
|
||||
*/
|
||||
private void buildSearchCondition(BookSearchReqDto condition, SearchRequest.Builder searchBuilder) {
|
||||
private void buildSearchCondition(BookSearchReqDto condition,
|
||||
SearchRequest.Builder searchBuilder) {
|
||||
|
||||
BoolQuery boolQuery = BoolQuery.of(b -> {
|
||||
|
||||
// 只查有字数的小说
|
||||
b.must(RangeQuery.of(m -> m
|
||||
.field(EsConsts.BookIndex.FIELD_WORD_COUNT)
|
||||
.gt(JsonData.of(0))
|
||||
.field(EsConsts.BookIndex.FIELD_WORD_COUNT)
|
||||
.gt(JsonData.of(0))
|
||||
)._toQuery());
|
||||
|
||||
if (!StringUtils.isBlank(condition.getKeyword())) {
|
||||
// 关键词匹配
|
||||
b.must((q -> q.multiMatch(t -> t
|
||||
.fields(EsConsts.BookIndex.FIELD_BOOK_NAME + "^2",
|
||||
EsConsts.BookIndex.FIELD_AUTHOR_NAME + "^1.8",
|
||||
EsConsts.BookIndex.FIELD_BOOK_DESC + "^0.1")
|
||||
.query(condition.getKeyword())
|
||||
.fields(EsConsts.BookIndex.FIELD_BOOK_NAME + "^2",
|
||||
EsConsts.BookIndex.FIELD_AUTHOR_NAME + "^1.8",
|
||||
EsConsts.BookIndex.FIELD_BOOK_DESC + "^0.1")
|
||||
.query(condition.getKeyword())
|
||||
)
|
||||
));
|
||||
}
|
||||
@ -130,37 +133,37 @@ public class EsSearchServiceImpl implements SearchService {
|
||||
// 精确查询
|
||||
if (Objects.nonNull(condition.getWorkDirection())) {
|
||||
b.must(TermQuery.of(m -> m
|
||||
.field(EsConsts.BookIndex.FIELD_WORK_DIRECTION)
|
||||
.value(condition.getWorkDirection())
|
||||
.field(EsConsts.BookIndex.FIELD_WORK_DIRECTION)
|
||||
.value(condition.getWorkDirection())
|
||||
)._toQuery());
|
||||
}
|
||||
|
||||
if (Objects.nonNull(condition.getCategoryId())) {
|
||||
b.must(TermQuery.of(m -> m
|
||||
.field(EsConsts.BookIndex.FIELD_CATEGORY_ID)
|
||||
.value(condition.getCategoryId())
|
||||
.field(EsConsts.BookIndex.FIELD_CATEGORY_ID)
|
||||
.value(condition.getCategoryId())
|
||||
)._toQuery());
|
||||
}
|
||||
|
||||
// 范围查询
|
||||
if (Objects.nonNull(condition.getWordCountMin())) {
|
||||
b.must(RangeQuery.of(m -> m
|
||||
.field(EsConsts.BookIndex.FIELD_WORD_COUNT)
|
||||
.gte(JsonData.of(condition.getWordCountMin()))
|
||||
.field(EsConsts.BookIndex.FIELD_WORD_COUNT)
|
||||
.gte(JsonData.of(condition.getWordCountMin()))
|
||||
)._toQuery());
|
||||
}
|
||||
|
||||
if (Objects.nonNull(condition.getWordCountMax())) {
|
||||
b.must(RangeQuery.of(m -> m
|
||||
.field(EsConsts.BookIndex.FIELD_WORD_COUNT)
|
||||
.lt(JsonData.of(condition.getWordCountMax()))
|
||||
.field(EsConsts.BookIndex.FIELD_WORD_COUNT)
|
||||
.lt(JsonData.of(condition.getWordCountMax()))
|
||||
)._toQuery());
|
||||
}
|
||||
|
||||
if (Objects.nonNull(condition.getUpdateTimeMin())) {
|
||||
b.must(RangeQuery.of(m -> m
|
||||
.field(EsConsts.BookIndex.FIELD_LAST_CHAPTER_UPDATE_TIME)
|
||||
.gte(JsonData.of(condition.getUpdateTimeMin().getTime()))
|
||||
.field(EsConsts.BookIndex.FIELD_LAST_CHAPTER_UPDATE_TIME)
|
||||
.gte(JsonData.of(condition.getUpdateTimeMin().getTime()))
|
||||
)._toQuery());
|
||||
}
|
||||
|
||||
|
@ -10,11 +10,10 @@ import io.github.xxyopen.novel.dao.mapper.NewsInfoMapper;
|
||||
import io.github.xxyopen.novel.dto.resp.NewsInfoRespDto;
|
||||
import io.github.xxyopen.novel.manager.cache.NewsCacheManager;
|
||||
import io.github.xxyopen.novel.service.NewsService;
|
||||
import java.util.List;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 新闻模块 服务实现类
|
||||
*
|
||||
@ -41,13 +40,13 @@ public class NewsServiceImpl implements NewsService {
|
||||
NewsInfo newsInfo = newsInfoMapper.selectById(id);
|
||||
QueryWrapper<NewsContent> queryWrapper = new QueryWrapper<>();
|
||||
queryWrapper.eq(DatabaseConsts.NewsContentTable.COLUMN_NEWS_ID, id)
|
||||
.last(DatabaseConsts.SqlEnum.LIMIT_1.getSql());
|
||||
.last(DatabaseConsts.SqlEnum.LIMIT_1.getSql());
|
||||
NewsContent newsContent = newsContentMapper.selectOne(queryWrapper);
|
||||
return RestResp.ok(NewsInfoRespDto.builder()
|
||||
.title(newsInfo.getTitle())
|
||||
.sourceName(newsInfo.getSourceName())
|
||||
.updateTime(newsInfo.getUpdateTime())
|
||||
.content(newsContent.getContent())
|
||||
.build());
|
||||
.title(newsInfo.getTitle())
|
||||
.sourceName(newsInfo.getSourceName())
|
||||
.updateTime(newsInfo.getUpdateTime())
|
||||
.content(newsContent.getContent())
|
||||
.build());
|
||||
}
|
||||
}
|
||||
|
@ -8,20 +8,19 @@ import io.github.xxyopen.novel.core.constant.SystemConfigConsts;
|
||||
import io.github.xxyopen.novel.dto.resp.ImgVerifyCodeRespDto;
|
||||
import io.github.xxyopen.novel.manager.redis.VerifyCodeManager;
|
||||
import io.github.xxyopen.novel.service.ResourceService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.SneakyThrows;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.Objects;
|
||||
import javax.imageio.ImageIO;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.SneakyThrows;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
/**
|
||||
* 资源(图片/视频/文档)相关服务实现类
|
||||
@ -43,9 +42,9 @@ public class ResourceServiceImpl implements ResourceService {
|
||||
public RestResp<ImgVerifyCodeRespDto> getImgVerifyCode() throws IOException {
|
||||
String sessionId = IdWorker.get32UUID();
|
||||
return RestResp.ok(ImgVerifyCodeRespDto.builder()
|
||||
.sessionId(sessionId)
|
||||
.img(verifyCodeManager.genImgVerifyCode(sessionId))
|
||||
.build());
|
||||
.sessionId(sessionId)
|
||||
.img(verifyCodeManager.genImgVerifyCode(sessionId))
|
||||
.build());
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
@ -53,10 +52,10 @@ public class ResourceServiceImpl implements ResourceService {
|
||||
public RestResp<String> uploadImage(MultipartFile file) {
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
String savePath =
|
||||
SystemConfigConsts.IMAGE_UPLOAD_DIRECTORY
|
||||
+ now.format(DateTimeFormatter.ofPattern("yyyy")) + File.separator
|
||||
+ now.format(DateTimeFormatter.ofPattern("MM")) + File.separator
|
||||
+ now.format(DateTimeFormatter.ofPattern("dd"));
|
||||
SystemConfigConsts.IMAGE_UPLOAD_DIRECTORY
|
||||
+ now.format(DateTimeFormatter.ofPattern("yyyy")) + File.separator
|
||||
+ now.format(DateTimeFormatter.ofPattern("MM")) + File.separator
|
||||
+ now.format(DateTimeFormatter.ofPattern("dd"));
|
||||
String oriName = file.getOriginalFilename();
|
||||
assert oriName != null;
|
||||
String saveFileName = IdWorker.get32UUID() + oriName.substring(oriName.lastIndexOf("."));
|
||||
|
@ -22,13 +22,12 @@ import io.github.xxyopen.novel.dto.resp.UserLoginRespDto;
|
||||
import io.github.xxyopen.novel.dto.resp.UserRegisterRespDto;
|
||||
import io.github.xxyopen.novel.manager.redis.VerifyCodeManager;
|
||||
import io.github.xxyopen.novel.service.UserService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.DigestUtils;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Objects;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.DigestUtils;
|
||||
|
||||
/**
|
||||
* 会员模块 服务实现类
|
||||
@ -61,7 +60,7 @@ public class UserServiceImpl implements UserService {
|
||||
// 校验手机号是否已注册
|
||||
QueryWrapper<UserInfo> queryWrapper = new QueryWrapper<>();
|
||||
queryWrapper.eq(DatabaseConsts.UserInfoTable.COLUMN_USERNAME, dto.getUsername())
|
||||
.last(DatabaseConsts.SqlEnum.LIMIT_1.getSql());
|
||||
.last(DatabaseConsts.SqlEnum.LIMIT_1.getSql());
|
||||
if (userInfoMapper.selectCount(queryWrapper) > 0) {
|
||||
// 手机号已注册
|
||||
throw new BusinessException(ErrorCodeEnum.USER_NAME_EXIST);
|
||||
@ -69,7 +68,8 @@ public class UserServiceImpl implements UserService {
|
||||
|
||||
// 注册成功,保存用户信息
|
||||
UserInfo userInfo = new UserInfo();
|
||||
userInfo.setPassword(DigestUtils.md5DigestAsHex(dto.getPassword().getBytes(StandardCharsets.UTF_8)));
|
||||
userInfo.setPassword(
|
||||
DigestUtils.md5DigestAsHex(dto.getPassword().getBytes(StandardCharsets.UTF_8)));
|
||||
userInfo.setUsername(dto.getUsername());
|
||||
userInfo.setNickName(dto.getUsername());
|
||||
userInfo.setCreateTime(LocalDateTime.now());
|
||||
@ -82,10 +82,10 @@ public class UserServiceImpl implements UserService {
|
||||
|
||||
// 生成JWT 并返回
|
||||
return RestResp.ok(
|
||||
UserRegisterRespDto.builder()
|
||||
.token(jwtUtils.generateToken(userInfo.getId(), SystemConfigConsts.NOVEL_FRONT_KEY))
|
||||
.uid(userInfo.getId())
|
||||
.build()
|
||||
UserRegisterRespDto.builder()
|
||||
.token(jwtUtils.generateToken(userInfo.getId(), SystemConfigConsts.NOVEL_FRONT_KEY))
|
||||
.uid(userInfo.getId())
|
||||
.build()
|
||||
);
|
||||
|
||||
}
|
||||
@ -95,7 +95,7 @@ public class UserServiceImpl implements UserService {
|
||||
// 查询用户信息
|
||||
QueryWrapper<UserInfo> queryWrapper = new QueryWrapper<>();
|
||||
queryWrapper.eq(DatabaseConsts.UserInfoTable.COLUMN_USERNAME, dto.getUsername())
|
||||
.last(DatabaseConsts.SqlEnum.LIMIT_1.getSql());
|
||||
.last(DatabaseConsts.SqlEnum.LIMIT_1.getSql());
|
||||
UserInfo userInfo = userInfoMapper.selectOne(queryWrapper);
|
||||
if (Objects.isNull(userInfo)) {
|
||||
// 用户不存在
|
||||
@ -104,16 +104,16 @@ public class UserServiceImpl implements UserService {
|
||||
|
||||
// 判断密码是否正确
|
||||
if (!Objects.equals(userInfo.getPassword()
|
||||
, DigestUtils.md5DigestAsHex(dto.getPassword().getBytes(StandardCharsets.UTF_8)))) {
|
||||
, DigestUtils.md5DigestAsHex(dto.getPassword().getBytes(StandardCharsets.UTF_8)))) {
|
||||
// 密码错误
|
||||
throw new BusinessException(ErrorCodeEnum.USER_PASSWORD_ERROR);
|
||||
}
|
||||
|
||||
// 登录成功,生成JWT并返回
|
||||
return RestResp.ok(UserLoginRespDto.builder()
|
||||
.token(jwtUtils.generateToken(userInfo.getId(), SystemConfigConsts.NOVEL_FRONT_KEY))
|
||||
.uid(userInfo.getId())
|
||||
.nickName(userInfo.getNickName()).build());
|
||||
.token(jwtUtils.generateToken(userInfo.getId(), SystemConfigConsts.NOVEL_FRONT_KEY))
|
||||
.uid(userInfo.getId())
|
||||
.nickName(userInfo.getNickName()).build());
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -142,7 +142,7 @@ public class UserServiceImpl implements UserService {
|
||||
public RestResp<Void> deleteFeedback(Long userId, Long id) {
|
||||
QueryWrapper<UserFeedback> queryWrapper = new QueryWrapper<>();
|
||||
queryWrapper.eq(DatabaseConsts.CommonColumnEnum.ID.getName(), id)
|
||||
.eq(DatabaseConsts.UserFeedBackTable.COLUMN_USER_ID, userId);
|
||||
.eq(DatabaseConsts.UserFeedBackTable.COLUMN_USER_ID, userId);
|
||||
userFeedbackMapper.delete(queryWrapper);
|
||||
return RestResp.ok();
|
||||
}
|
||||
@ -151,11 +151,11 @@ public class UserServiceImpl implements UserService {
|
||||
public RestResp<Integer> getBookshelfStatus(Long userId, String bookId) {
|
||||
QueryWrapper<UserBookshelf> queryWrapper = new QueryWrapper<>();
|
||||
queryWrapper.eq(DatabaseConsts.UserBookshelfTable.COLUMN_USER_ID, userId)
|
||||
.eq(DatabaseConsts.UserBookshelfTable.COLUMN_BOOK_ID, bookId);
|
||||
.eq(DatabaseConsts.UserBookshelfTable.COLUMN_BOOK_ID, bookId);
|
||||
return RestResp.ok(
|
||||
userBookshelfMapper.selectCount(queryWrapper) > 0
|
||||
? CommonConsts.YES
|
||||
: CommonConsts.NO
|
||||
userBookshelfMapper.selectCount(queryWrapper) > 0
|
||||
? CommonConsts.YES
|
||||
: CommonConsts.NO
|
||||
);
|
||||
}
|
||||
|
||||
@ -163,9 +163,9 @@ public class UserServiceImpl implements UserService {
|
||||
public RestResp<UserInfoRespDto> getUserInfo(Long userId) {
|
||||
UserInfo userInfo = userInfoMapper.selectById(userId);
|
||||
return RestResp.ok(UserInfoRespDto.builder()
|
||||
.nickName(userInfo.getNickName())
|
||||
.userSex(userInfo.getUserSex())
|
||||
.userPhoto(userInfo.getUserPhoto())
|
||||
.build());
|
||||
.nickName(userInfo.getNickName())
|
||||
.userSex(userInfo.getUserSex())
|
||||
.userPhoto(userInfo.getUserPhoto())
|
||||
.build());
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user