Compare commits
86 Commits
04b891e73d
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 94729e5170 | |||
| 6e0f226b42 | |||
| e0cf96edbf | |||
| d9a960e265 | |||
| 6985431236 | |||
| 85e0238857 | |||
| 93ca81a15b | |||
| f74397ed1e | |||
| f90e36f7d6 | |||
| a8d5c7f9b7 | |||
| 51cebf6215 | |||
| 8be6719be8 | |||
| e3f9b6a5b5 | |||
| 6f22c2b50d | |||
| bd775b805c | |||
| 85e6bab079 | |||
| 6fbe8eed25 | |||
| fdee4dc2b4 | |||
| 2b06ca0300 | |||
| 29cf889dd7 | |||
| c454e1832c | |||
| 9ec330216f | |||
| 63495b4938 | |||
| f49d0e6b76 | |||
| a37e76c87c | |||
| c37b16ff42 | |||
| eb19d52fcb | |||
| 226c28885b | |||
| 5eb3c7b58e | |||
| 2b2cd2be70 | |||
| 218f4c6974 | |||
| 268a009c9b | |||
| 678c8ab8eb | |||
| 39d2eb1063 | |||
| 96b4127873 | |||
| 7c62f1dcf9 | |||
| 1335582827 | |||
| 34c7092abc | |||
| 3d33a73462 | |||
| 4b13e52a29 | |||
| 4e00542371 | |||
| 3437c2bff4 | |||
| c216ca4c63 | |||
| 6cc5c06879 | |||
| d1f756d5c8 | |||
| ac65664dfe | |||
| 17123657f4 | |||
| 5c4d8862a2 | |||
| a6f4d437d2 | |||
| c1c0590cce | |||
| 8a1681e590 | |||
| f217b8133a | |||
| efd2e51d24 | |||
| 023d0c0926 | |||
| 7fc24e1e2a | |||
| 7b1df60c05 | |||
| 564eefa7bc | |||
| c036fadbff | |||
| 61cfbd6b81 | |||
| 1ac61d1b06 | |||
| 65b089de70 | |||
| 54c34706fb | |||
| cfcd12be0d | |||
| 90bd9a5a5d | |||
| bb44cd3d23 | |||
| 7c92bd91f6 | |||
| 648c621fbf | |||
| d59acad051 | |||
| c6ac7193c1 | |||
| 31ab7c3d86 | |||
| e17ab857b9 | |||
| ee99654e7c | |||
| 3904e8510e | |||
| 893f52e5aa | |||
| 84d6914b1c | |||
| 9e3c35043e | |||
| 5e4f9b1203 | |||
| aca7c657fa | |||
| 1e350a4af5 | |||
| b70d9073d8 | |||
| f0afb23a73 | |||
| 362c32cbd6 | |||
| 5a7564d504 | |||
| 3c8dc9e4af | |||
| 7942a46592 | |||
| 16ab8a13d2 |
80
.gitignore
vendored
80
.gitignore
vendored
@@ -1,13 +1,31 @@
|
|||||||
target/
|
# -----------------------------------------------------------------------------
|
||||||
!.mvn/wrapper/maven-wrapper.jar
|
# 构建产物和依赖
|
||||||
!**/src/main/**/target/
|
# -----------------------------------------------------------------------------
|
||||||
!**/src/test/**/target/
|
target/ # Maven 默认的编译输出目录
|
||||||
.kotlin
|
**/target/
|
||||||
|
!.mvn/wrapper/maven-wrapper.jar # 保留 Maven Wrapper jar 包
|
||||||
|
!**/src/main/**/target/ # 排除 src/main 下的 target 目录,但如果上面的 target/ 已经生效,这行可能冗余,但安全起见保留
|
||||||
|
!**/src/test/**/target/ # 排除 src/test 下的 target 目录
|
||||||
|
|
||||||
|
/build/ # Gradle 默认的编译输出目录
|
||||||
|
!**/src/main/**/build/ # 排除 src/main 下的 build 目录
|
||||||
|
!**/src/test/**/build/ # 排除 src/test 下的 build 目录
|
||||||
|
/dist/ # NetBeans / 通用分发目录
|
||||||
|
/nbbuild/
|
||||||
|
/nbdist/
|
||||||
|
/.nb-gradle/
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# IDE 配置文件
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
### IntelliJ IDEA ###
|
### IntelliJ IDEA ###
|
||||||
.idea/modules.xml
|
.idea/ # IntelliJ IDEA 配置目录
|
||||||
.idea/jarRepositories.xml
|
# 保留某些重要的 IDEA 文件 (如果需要,但通常 .idea/ 排除足够)
|
||||||
.idea/compiler.xml
|
!.idea/modules.xml
|
||||||
|
!.idea/jarRepositories.xml
|
||||||
|
!.idea/compiler.xml
|
||||||
|
# 排除掉不需要的版本控制的文件
|
||||||
.idea/libraries/
|
.idea/libraries/
|
||||||
*.iws
|
*.iws
|
||||||
*.iml
|
*.iml
|
||||||
@@ -24,29 +42,31 @@ target/
|
|||||||
|
|
||||||
### NetBeans ###
|
### NetBeans ###
|
||||||
/nbproject/private/
|
/nbproject/private/
|
||||||
/nbbuild/
|
|
||||||
/dist/
|
|
||||||
/nbdist/
|
|
||||||
/.nb-gradle/
|
|
||||||
build/
|
|
||||||
!**/src/main/**/build/
|
|
||||||
!**/src/test/**/build/
|
|
||||||
|
|
||||||
### VS Code ###
|
### VS Code ###
|
||||||
.vscode/
|
.vscode/
|
||||||
|
|
||||||
### Mac OS ###
|
# -----------------------------------------------------------------------------
|
||||||
.DS_Store
|
# OS/平台文件
|
||||||
/.idea/
|
# -----------------------------------------------------------------------------
|
||||||
/han-note-auth/src/main/resources/application-dev.yml
|
.DS_Store # Mac OS X
|
||||||
/han-note-auth/src/main/resources/application-prod.yml
|
# .idea/ (已在 IDEA 部分排除,但 Mac 用户有时会在项目根目录生成一个隐藏的 .idea 文件夹)
|
||||||
/han-note-auth/logs/
|
|
||||||
/logs/
|
# -----------------------------------------------------------------------------
|
||||||
/han-note-oss/han-note-oss-biz/src/main/resources/application-dev.yml
|
# 语言特定文件
|
||||||
/han-note-user/han-note-user-biz/src/main/resources/application-dev.yml
|
# -----------------------------------------------------------------------------
|
||||||
/han-note-user/han-note-user-biz/logs/
|
.kotlin # Kotlin 缓存文件
|
||||||
/han-note-kv/han-note-kv-biz/src/main/resources/application-dev.yml
|
|
||||||
/han-note-kv/han-note-kv-biz/src/main/resources/application-prod.yml
|
# -----------------------------------------------------------------------------
|
||||||
/han-note-kv/han-note-kv-biz/logs/
|
# 自定义应用配置文件和日志 (重点优化部分)
|
||||||
/han-note-note/han-note-note-biz/src/main/resources/application-dev.yml
|
# -----------------------------------------------------------------------------
|
||||||
/han-note-user-relation/han-note-user-relation-biz/src/main/resources/application-dev.yml
|
|
||||||
|
# 排除所有模块的 logs/ 目录
|
||||||
|
*/logs/
|
||||||
|
# 统一排除所有模块的日志目录(如果上面的 *\/logs/ 不够全面)
|
||||||
|
logs/
|
||||||
|
|
||||||
|
# 排除所有 application-dev.yml 和 application-prod.yml
|
||||||
|
# 这种方式更简洁,排除所有环境的本地配置,统一管理
|
||||||
|
application-dev.yml
|
||||||
|
application-prod.yml
|
||||||
|
|||||||
434
.idea/MyBatisCodeHelperDatasource.xml
generated
Normal file
434
.idea/MyBatisCodeHelperDatasource.xml
generated
Normal file
@@ -0,0 +1,434 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="MyBatisCodeHelperDatasource">
|
||||||
|
<option name="projectProfile">
|
||||||
|
<ProjectProfile>
|
||||||
|
<option name="controllerTemplateString" value=" #* @vtlvariable name="tableName" type="java.lang.String" *# #* @vtlvariable name="entityPackageName" type="java.lang.String" *# #* @vtlvariable name="entityClassName" type="java.lang.String" *# #* @vtlvariable name="servicePackageName" type="java.lang.String" *# #* @vtlvariable name="serviceInterfacePackage" type="java.lang.String" *# #* @vtlvariable name="serviceClassName" type="java.lang.String" *# #* @vtlvariable name="serviceInterfaceClassName" type="java.lang.String" *# #* @vtlvariable name="mapperPackageName" type="java.lang.String" *# #* @vtlvariable name="mapperClassName" type="java.lang.String" *# #* @vtlvariable name="controllerPackage" type="java.lang.String" *# #* @vtlvariable name="tableRemark" type="java.lang.String" *# #* @vtlvariable name="myDate" type="java.util.Date" *# #* @vtlvariable name="simpleDateFormat" type="java.text.SimpleDateFormat" *# package $!{controllerPackage}; import $!{entityPackageName}.$!{entityClassName}; ###set($realServiceName = $!{serviceClassName}+'Impl') import $!{servicePackageName}.$!{serviceClassName}; import org.springframework.web.bind.annotation.*; #set($serviceFirstLower = $!{serviceClassName.substring(0,1).toLowerCase()}+$!{serviceClassName.substring(1,$!{serviceClassName.length()})}) import org.springframework.beans.factory.annotation.Autowired; /** * $!{tableRemark}($!{tableName})表控制层 * * @author xxxxx */ @RestController @RequestMapping("/$!{tableName}") public class $!{entityClassName}Controller { /** * 服务对象 */ @Autowired private $!{serviceClassName} $!{serviceFirstLower}; /** * 通过主键查询单条数据 * * @param id 主键 * @return 单条数据 */ @GetMapping("selectOne") public $!{entityClassName} selectOne(Integer id) { return $!{serviceFirstLower}.selectByPrimaryKey(id); } }" />
|
||||||
|
<option name="customizedLombokAnnotation" value="true" />
|
||||||
|
<option name="customizedLombokValue" value="@lombok.Builder" />
|
||||||
|
<option name="deleteByPrimayKeyEnabled" value="false" />
|
||||||
|
<option name="insertMethodEnabled" value="false" />
|
||||||
|
<option name="insertSelectiveMethodEnabled" value="false" />
|
||||||
|
<option name="javaMapperPackage" value="com.hanserwei.hannote.comment.biz.domain.mapper" />
|
||||||
|
<option name="javaMapperPath" value="$PROJECT_DIR$/han-note-comment/han-note-comment-biz/src/main/java" />
|
||||||
|
<option name="javaModelPackage" value="com.hanserwei.hannote.comment.biz.domain.dataobject" />
|
||||||
|
<option name="javaModelPath" value="$PROJECT_DIR$/han-note-comment/han-note-comment-biz/src/main/java" />
|
||||||
|
<option name="lastDatabaseCrudChooseModuleName" value="han-note-comment-biz" />
|
||||||
|
<option name="lombokAllArgConstructor" value="true" />
|
||||||
|
<option name="lombokDataAnnotation" value="true" />
|
||||||
|
<option name="lombokNoArgsConstructor" value="true" />
|
||||||
|
<option name="mapperAnnotaion" value="true" />
|
||||||
|
<option name="mapperFilesFolder" value="$PROJECT_DIR$/han-note-search/src/main/resources/mapperxml" />
|
||||||
|
<option name="mapperFilesFolderList">
|
||||||
|
<list>
|
||||||
|
<option value="$PROJECT_DIR$/han-note-auth/src/main/resources/mapperxml" />
|
||||||
|
<option value="$PROJECT_DIR$/han-note-data-align/src/main/resources/mapperxml" />
|
||||||
|
<option value="$PROJECT_DIR$/han-note-search/src/main/resources/mapperxml" />
|
||||||
|
</list>
|
||||||
|
</option>
|
||||||
|
<option name="moduleNameToPackageAndPathMap">
|
||||||
|
<map>
|
||||||
|
<entry key="han-note-auth">
|
||||||
|
<value>
|
||||||
|
<UserPackageAndPathInfoByModule>
|
||||||
|
<option name="javaMapperPackage" value="com.hanserwei.hannote.count.biz.domain.mapper" />
|
||||||
|
<option name="javaMapperPath" value="$PROJECT_DIR$/han-note-count/han-note-count-biz/src/main/java" />
|
||||||
|
<option name="javaModelPacakge" value="com.hanserwei.hannote.count.biz.domain.dataobject" />
|
||||||
|
<option name="javaModelPath" value="$PROJECT_DIR$/han-note-count/han-note-count-biz/src/main/java" />
|
||||||
|
<option name="javaServiceInterfacePackage" value="com.hanserwei.hannote.count.biz.service" />
|
||||||
|
<option name="javaServiceInterfacePath" value="$PROJECT_DIR$/han-note-count/han-note-count-biz/src/main/java" />
|
||||||
|
<option name="javaServicePackage" value="com.hanserwei.hannote.count.biz.service.impl" />
|
||||||
|
<option name="javaServicePath" value="$PROJECT_DIR$/han-note-count/han-note-count-biz/src/main/java" />
|
||||||
|
<option name="xmlPackage" value="mapperxml" />
|
||||||
|
<option name="xmlPath" value="$PROJECT_DIR$/han-note-count/han-note-count-biz/src/main/resources" />
|
||||||
|
</UserPackageAndPathInfoByModule>
|
||||||
|
</value>
|
||||||
|
</entry>
|
||||||
|
<entry key="han-note-comment-biz">
|
||||||
|
<value>
|
||||||
|
<UserPackageAndPathInfoByModule>
|
||||||
|
<option name="javaMapperPackage" value="com.hanserwei.hannote.comment.biz.domain.mapper" />
|
||||||
|
<option name="javaMapperPath" value="$PROJECT_DIR$/han-note-comment/han-note-comment-biz/src/main/java" />
|
||||||
|
<option name="javaModelPacakge" value="com.hanserwei.hannote.comment.biz.domain.dataobject" />
|
||||||
|
<option name="javaModelPath" value="$PROJECT_DIR$/han-note-comment/han-note-comment-biz/src/main/java" />
|
||||||
|
<option name="javaServiceInterfacePackage" value="com.hanserwei.hannote.comment.biz.service" />
|
||||||
|
<option name="javaServiceInterfacePath" value="$PROJECT_DIR$/han-note-comment/han-note-comment-biz/src/main/java" />
|
||||||
|
<option name="javaServicePackage" value="com.hanserwei.hannote.comment.biz.service.impl" />
|
||||||
|
<option name="javaServicePath" value="$PROJECT_DIR$/han-note-comment/han-note-comment-biz/src/main/java" />
|
||||||
|
<option name="xmlPackage" value="mapperxml" />
|
||||||
|
<option name="xmlPath" value="$PROJECT_DIR$/han-note-comment/han-note-comment-biz/src/main/resources" />
|
||||||
|
</UserPackageAndPathInfoByModule>
|
||||||
|
</value>
|
||||||
|
</entry>
|
||||||
|
<entry key="han-note-count-biz">
|
||||||
|
<value>
|
||||||
|
<UserPackageAndPathInfoByModule>
|
||||||
|
<option name="javaMapperPackage" value="com.hanserwei.hannote.count.biz.domain.mapper" />
|
||||||
|
<option name="javaMapperPath" value="$PROJECT_DIR$/han-note-count/han-note-count-biz/src/main/java" />
|
||||||
|
<option name="javaModelPacakge" value="com.hanserwei.hannote.count.biz.domain.dataobject" />
|
||||||
|
<option name="javaModelPath" value="$PROJECT_DIR$/han-note-count/han-note-count-biz/src/main/java" />
|
||||||
|
<option name="javaServiceInterfacePackage" value="com.hanserwei.hannote.count.biz.service" />
|
||||||
|
<option name="javaServiceInterfacePath" value="$PROJECT_DIR$/han-note-count/han-note-count-biz/src/main/java" />
|
||||||
|
<option name="javaServicePackage" value="com.hanserwei.hannote.count.biz.service.impl" />
|
||||||
|
<option name="javaServicePath" value="$PROJECT_DIR$/han-note-count/han-note-count-biz/src/main/java" />
|
||||||
|
<option name="xmlPackage" value="mapperxml" />
|
||||||
|
<option name="xmlPath" value="$PROJECT_DIR$/han-note-count/han-note-count-biz/src/main/resources" />
|
||||||
|
</UserPackageAndPathInfoByModule>
|
||||||
|
</value>
|
||||||
|
</entry>
|
||||||
|
<entry key="han-note-note-biz">
|
||||||
|
<value>
|
||||||
|
<UserPackageAndPathInfoByModule>
|
||||||
|
<option name="javaMapperPackage" value="com.hanserwei.hannote.note.biz.domain.mapper" />
|
||||||
|
<option name="javaMapperPath" value="$PROJECT_DIR$/han-note-note/han-note-note-biz/src/main/java" />
|
||||||
|
<option name="javaModelPacakge" value="com.hanserwei.hannote.note.biz.domain.dataobject" />
|
||||||
|
<option name="javaModelPath" value="$PROJECT_DIR$/han-note-note/han-note-note-biz/src/main/java" />
|
||||||
|
<option name="javaServiceInterfacePackage" value="com.hanserwei.hannote.note.biz.service" />
|
||||||
|
<option name="javaServiceInterfacePath" value="$PROJECT_DIR$/han-note-note/han-note-note-biz/src/main/java" />
|
||||||
|
<option name="javaServicePackage" value="com.hanserwei.hannote.note.biz.service.impl" />
|
||||||
|
<option name="javaServicePath" value="$PROJECT_DIR$/han-note-note/han-note-note-biz/src/main/java" />
|
||||||
|
<option name="xmlPackage" value="mapperxml" />
|
||||||
|
<option name="xmlPath" value="$PROJECT_DIR$/han-note-note/han-note-note-biz/src/main/resources" />
|
||||||
|
</UserPackageAndPathInfoByModule>
|
||||||
|
</value>
|
||||||
|
</entry>
|
||||||
|
</map>
|
||||||
|
</option>
|
||||||
|
<option name="mybatisPlusIdType" value="ASSIGN_ID" />
|
||||||
|
<option name="selectByPrimaryKeyEnabled" value="false" />
|
||||||
|
<option name="tableGenerateConfigs">
|
||||||
|
<map>
|
||||||
|
<entry key="han_note:t_channel">
|
||||||
|
<value>
|
||||||
|
<TableGenerateConfig>
|
||||||
|
<option name="deleteByPrimayKeyEnabled" value="false" />
|
||||||
|
<option name="generatedKey" value="id" />
|
||||||
|
<option name="insertMethodEnabled" value="false" />
|
||||||
|
<option name="insertSelectiveMethodEnabled" value="false" />
|
||||||
|
<option name="javaModelName" value="ChannelDO" />
|
||||||
|
<option name="moduleName" value="han-note-note-biz" />
|
||||||
|
<option name="mybatisplusIdType" value="ASSIGN_ID" />
|
||||||
|
<option name="selectByPrimaryKeyEnabled" value="false" />
|
||||||
|
<option name="sequenceColumn" value="" />
|
||||||
|
<option name="sequenceId" value="" />
|
||||||
|
<option name="updateByPrimaryKeySelectiveEnabled" value="false" />
|
||||||
|
<option name="updateByPrimaykeyEnabled" value="false" />
|
||||||
|
<option name="useActualColumnName" value="false" />
|
||||||
|
</TableGenerateConfig>
|
||||||
|
</value>
|
||||||
|
</entry>
|
||||||
|
<entry key="han_note:t_channel_topic_rel">
|
||||||
|
<value>
|
||||||
|
<TableGenerateConfig>
|
||||||
|
<option name="deleteByPrimayKeyEnabled" value="false" />
|
||||||
|
<option name="generatedKey" value="id" />
|
||||||
|
<option name="insertMethodEnabled" value="false" />
|
||||||
|
<option name="insertSelectiveMethodEnabled" value="false" />
|
||||||
|
<option name="javaModelName" value="ChannelTopicRelDO" />
|
||||||
|
<option name="moduleName" value="han-note-auth" />
|
||||||
|
<option name="mybatisplusIdType" value="AUTO" />
|
||||||
|
<option name="selectByPrimaryKeyEnabled" value="false" />
|
||||||
|
<option name="sequenceColumn" value="" />
|
||||||
|
<option name="sequenceId" value="" />
|
||||||
|
<option name="updateByPrimaryKeySelectiveEnabled" value="false" />
|
||||||
|
<option name="updateByPrimaykeyEnabled" value="false" />
|
||||||
|
<option name="useActualColumnName" value="false" />
|
||||||
|
</TableGenerateConfig>
|
||||||
|
</value>
|
||||||
|
</entry>
|
||||||
|
<entry key="han_note:t_comment">
|
||||||
|
<value>
|
||||||
|
<TableGenerateConfig>
|
||||||
|
<option name="deleteByPrimayKeyEnabled" value="false" />
|
||||||
|
<option name="generatedKey" value="id" />
|
||||||
|
<option name="insertMethodEnabled" value="false" />
|
||||||
|
<option name="insertSelectiveMethodEnabled" value="false" />
|
||||||
|
<option name="javaModelName" value="CommentDO" />
|
||||||
|
<option name="moduleName" value="han-note-comment-biz" />
|
||||||
|
<option name="mybatisplusIdType" value="AUTO" />
|
||||||
|
<option name="selectByPrimaryKeyEnabled" value="false" />
|
||||||
|
<option name="sequenceColumn" value="" />
|
||||||
|
<option name="sequenceId" value="" />
|
||||||
|
<option name="updateByPrimaryKeySelectiveEnabled" value="false" />
|
||||||
|
<option name="updateByPrimaykeyEnabled" value="false" />
|
||||||
|
<option name="useActualColumnName" value="false" />
|
||||||
|
</TableGenerateConfig>
|
||||||
|
</value>
|
||||||
|
</entry>
|
||||||
|
<entry key="han_note:t_comment_like">
|
||||||
|
<value>
|
||||||
|
<TableGenerateConfig>
|
||||||
|
<option name="deleteByPrimayKeyEnabled" value="false" />
|
||||||
|
<option name="generatedKey" value="id" />
|
||||||
|
<option name="insertMethodEnabled" value="false" />
|
||||||
|
<option name="insertSelectiveMethodEnabled" value="false" />
|
||||||
|
<option name="javaModelName" value="CommentLikeDO" />
|
||||||
|
<option name="moduleName" value="han-note-comment-biz" />
|
||||||
|
<option name="mybatisplusIdType" value="AUTO" />
|
||||||
|
<option name="selectByPrimaryKeyEnabled" value="false" />
|
||||||
|
<option name="sequenceColumn" value="" />
|
||||||
|
<option name="sequenceId" value="" />
|
||||||
|
<option name="updateByPrimaryKeySelectiveEnabled" value="false" />
|
||||||
|
<option name="updateByPrimaykeyEnabled" value="false" />
|
||||||
|
<option name="useActualColumnName" value="false" />
|
||||||
|
</TableGenerateConfig>
|
||||||
|
</value>
|
||||||
|
</entry>
|
||||||
|
<entry key="han_note:t_fans">
|
||||||
|
<value>
|
||||||
|
<TableGenerateConfig>
|
||||||
|
<option name="deleteByPrimayKeyEnabled" value="false" />
|
||||||
|
<option name="generatedKey" value="id" />
|
||||||
|
<option name="insertMethodEnabled" value="false" />
|
||||||
|
<option name="insertSelectiveMethodEnabled" value="false" />
|
||||||
|
<option name="javaModelName" value="FansDO" />
|
||||||
|
<option name="moduleName" value="han-note-auth" />
|
||||||
|
<option name="mybatisplusIdType" value="AUTO" />
|
||||||
|
<option name="selectByPrimaryKeyEnabled" value="false" />
|
||||||
|
<option name="sequenceColumn" value="" />
|
||||||
|
<option name="sequenceId" value="" />
|
||||||
|
<option name="updateByPrimaryKeySelectiveEnabled" value="false" />
|
||||||
|
<option name="updateByPrimaykeyEnabled" value="false" />
|
||||||
|
<option name="useActualColumnName" value="false" />
|
||||||
|
</TableGenerateConfig>
|
||||||
|
</value>
|
||||||
|
</entry>
|
||||||
|
<entry key="han_note:t_following">
|
||||||
|
<value>
|
||||||
|
<TableGenerateConfig>
|
||||||
|
<option name="deleteByPrimayKeyEnabled" value="false" />
|
||||||
|
<option name="generatedKey" value="id" />
|
||||||
|
<option name="insertMethodEnabled" value="false" />
|
||||||
|
<option name="insertSelectiveMethodEnabled" value="false" />
|
||||||
|
<option name="javaModelName" value="FollowingDO" />
|
||||||
|
<option name="moduleName" value="han-note-auth" />
|
||||||
|
<option name="mybatisplusIdType" value="AUTO" />
|
||||||
|
<option name="selectByPrimaryKeyEnabled" value="false" />
|
||||||
|
<option name="sequenceColumn" value="" />
|
||||||
|
<option name="sequenceId" value="" />
|
||||||
|
<option name="updateByPrimaryKeySelectiveEnabled" value="false" />
|
||||||
|
<option name="updateByPrimaykeyEnabled" value="false" />
|
||||||
|
<option name="useActualColumnName" value="false" />
|
||||||
|
</TableGenerateConfig>
|
||||||
|
</value>
|
||||||
|
</entry>
|
||||||
|
<entry key="han_note:t_note">
|
||||||
|
<value>
|
||||||
|
<TableGenerateConfig>
|
||||||
|
<option name="deleteByPrimayKeyEnabled" value="false" />
|
||||||
|
<option name="generatedKey" value="" />
|
||||||
|
<option name="insertMethodEnabled" value="false" />
|
||||||
|
<option name="insertSelectiveMethodEnabled" value="false" />
|
||||||
|
<option name="javaModelName" value="NoteDO" />
|
||||||
|
<option name="moduleName" value="han-note-auth" />
|
||||||
|
<option name="mybatisplusIdType" value="AUTO" />
|
||||||
|
<option name="selectByPrimaryKeyEnabled" value="false" />
|
||||||
|
<option name="sequenceColumn" value="" />
|
||||||
|
<option name="sequenceId" value="" />
|
||||||
|
<option name="updateByPrimaryKeySelectiveEnabled" value="false" />
|
||||||
|
<option name="updateByPrimaykeyEnabled" value="false" />
|
||||||
|
<option name="useActualColumnName" value="false" />
|
||||||
|
</TableGenerateConfig>
|
||||||
|
</value>
|
||||||
|
</entry>
|
||||||
|
<entry key="han_note:t_note_collection">
|
||||||
|
<value>
|
||||||
|
<TableGenerateConfig>
|
||||||
|
<option name="deleteByPrimayKeyEnabled" value="false" />
|
||||||
|
<option name="generatedKey" value="id" />
|
||||||
|
<option name="insertMethodEnabled" value="false" />
|
||||||
|
<option name="insertSelectiveMethodEnabled" value="false" />
|
||||||
|
<option name="javaModelName" value="NoteCollectionDO" />
|
||||||
|
<option name="moduleName" value="han-note-note-biz" />
|
||||||
|
<option name="mybatisplusIdType" value="ASSIGN_ID" />
|
||||||
|
<option name="selectByPrimaryKeyEnabled" value="false" />
|
||||||
|
<option name="sequenceColumn" value="" />
|
||||||
|
<option name="sequenceId" value="" />
|
||||||
|
<option name="updateByPrimaryKeySelectiveEnabled" value="false" />
|
||||||
|
<option name="updateByPrimaykeyEnabled" value="false" />
|
||||||
|
<option name="useActualColumnName" value="false" />
|
||||||
|
</TableGenerateConfig>
|
||||||
|
</value>
|
||||||
|
</entry>
|
||||||
|
<entry key="han_note:t_note_count">
|
||||||
|
<value>
|
||||||
|
<TableGenerateConfig>
|
||||||
|
<option name="deleteByPrimayKeyEnabled" value="false" />
|
||||||
|
<option name="generatedKey" value="id" />
|
||||||
|
<option name="insertMethodEnabled" value="false" />
|
||||||
|
<option name="insertSelectiveMethodEnabled" value="false" />
|
||||||
|
<option name="javaModelName" value="NoteCountDO" />
|
||||||
|
<option name="moduleName" value="han-note-comment-biz" />
|
||||||
|
<option name="mybatisplusIdType" value="ASSIGN_ID" />
|
||||||
|
<option name="selectByPrimaryKeyEnabled" value="false" />
|
||||||
|
<option name="sequenceColumn" value="" />
|
||||||
|
<option name="sequenceId" value="" />
|
||||||
|
<option name="updateByPrimaryKeySelectiveEnabled" value="false" />
|
||||||
|
<option name="updateByPrimaykeyEnabled" value="false" />
|
||||||
|
<option name="useActualColumnName" value="false" />
|
||||||
|
</TableGenerateConfig>
|
||||||
|
</value>
|
||||||
|
</entry>
|
||||||
|
<entry key="han_note:t_note_like">
|
||||||
|
<value>
|
||||||
|
<TableGenerateConfig>
|
||||||
|
<option name="deleteByPrimayKeyEnabled" value="false" />
|
||||||
|
<option name="generatedKey" value="id" />
|
||||||
|
<option name="insertMethodEnabled" value="false" />
|
||||||
|
<option name="insertSelectiveMethodEnabled" value="false" />
|
||||||
|
<option name="javaModelName" value="NoteLikeDO" />
|
||||||
|
<option name="moduleName" value="han-note-note-biz" />
|
||||||
|
<option name="mybatisplusIdType" value="AUTO" />
|
||||||
|
<option name="selectByPrimaryKeyEnabled" value="false" />
|
||||||
|
<option name="sequenceColumn" value="" />
|
||||||
|
<option name="sequenceId" value="" />
|
||||||
|
<option name="updateByPrimaryKeySelectiveEnabled" value="false" />
|
||||||
|
<option name="updateByPrimaykeyEnabled" value="false" />
|
||||||
|
<option name="useActualColumnName" value="false" />
|
||||||
|
</TableGenerateConfig>
|
||||||
|
</value>
|
||||||
|
</entry>
|
||||||
|
<entry key="han_note:t_permission">
|
||||||
|
<value>
|
||||||
|
<TableGenerateConfig>
|
||||||
|
<option name="deleteByPrimayKeyEnabled" value="false" />
|
||||||
|
<option name="generatedKey" value="id" />
|
||||||
|
<option name="insertMethodEnabled" value="false" />
|
||||||
|
<option name="insertSelectiveMethodEnabled" value="false" />
|
||||||
|
<option name="javaModelName" value="PermissionDO" />
|
||||||
|
<option name="moduleName" value="han-note-auth" />
|
||||||
|
<option name="mybatisplusIdType" value="AUTO" />
|
||||||
|
<option name="selectByPrimaryKeyEnabled" value="false" />
|
||||||
|
<option name="sequenceColumn" value="" />
|
||||||
|
<option name="sequenceId" value="" />
|
||||||
|
<option name="updateByPrimaryKeySelectiveEnabled" value="false" />
|
||||||
|
<option name="updateByPrimaykeyEnabled" value="false" />
|
||||||
|
<option name="useActualColumnName" value="false" />
|
||||||
|
</TableGenerateConfig>
|
||||||
|
</value>
|
||||||
|
</entry>
|
||||||
|
<entry key="han_note:t_role">
|
||||||
|
<value>
|
||||||
|
<TableGenerateConfig>
|
||||||
|
<option name="deleteByPrimayKeyEnabled" value="false" />
|
||||||
|
<option name="generatedKey" value="id" />
|
||||||
|
<option name="insertMethodEnabled" value="false" />
|
||||||
|
<option name="insertSelectiveMethodEnabled" value="false" />
|
||||||
|
<option name="javaModelName" value="RoleDO" />
|
||||||
|
<option name="moduleName" value="han-note-auth" />
|
||||||
|
<option name="mybatisplusIdType" value="AUTO" />
|
||||||
|
<option name="selectByPrimaryKeyEnabled" value="false" />
|
||||||
|
<option name="sequenceColumn" value="" />
|
||||||
|
<option name="sequenceId" value="" />
|
||||||
|
<option name="updateByPrimaryKeySelectiveEnabled" value="false" />
|
||||||
|
<option name="updateByPrimaykeyEnabled" value="false" />
|
||||||
|
<option name="useActualColumnName" value="false" />
|
||||||
|
</TableGenerateConfig>
|
||||||
|
</value>
|
||||||
|
</entry>
|
||||||
|
<entry key="han_note:t_role_permission_rel">
|
||||||
|
<value>
|
||||||
|
<TableGenerateConfig>
|
||||||
|
<option name="deleteByPrimayKeyEnabled" value="false" />
|
||||||
|
<option name="generatedKey" value="id" />
|
||||||
|
<option name="insertMethodEnabled" value="false" />
|
||||||
|
<option name="insertSelectiveMethodEnabled" value="false" />
|
||||||
|
<option name="javaModelName" value="RolePermissionDO" />
|
||||||
|
<option name="moduleName" value="han-note-auth" />
|
||||||
|
<option name="mybatisplusIdType" value="AUTO" />
|
||||||
|
<option name="selectByPrimaryKeyEnabled" value="false" />
|
||||||
|
<option name="sequenceColumn" value="" />
|
||||||
|
<option name="sequenceId" value="" />
|
||||||
|
<option name="updateByPrimaryKeySelectiveEnabled" value="false" />
|
||||||
|
<option name="updateByPrimaykeyEnabled" value="false" />
|
||||||
|
<option name="useActualColumnName" value="false" />
|
||||||
|
</TableGenerateConfig>
|
||||||
|
</value>
|
||||||
|
</entry>
|
||||||
|
<entry key="han_note:t_topic">
|
||||||
|
<value>
|
||||||
|
<TableGenerateConfig>
|
||||||
|
<option name="deleteByPrimayKeyEnabled" value="false" />
|
||||||
|
<option name="generatedKey" value="id" />
|
||||||
|
<option name="insertMethodEnabled" value="false" />
|
||||||
|
<option name="insertSelectiveMethodEnabled" value="false" />
|
||||||
|
<option name="javaModelName" value="TopicDO" />
|
||||||
|
<option name="moduleName" value="han-note-auth" />
|
||||||
|
<option name="mybatisplusIdType" value="AUTO" />
|
||||||
|
<option name="selectByPrimaryKeyEnabled" value="false" />
|
||||||
|
<option name="sequenceColumn" value="" />
|
||||||
|
<option name="sequenceId" value="" />
|
||||||
|
<option name="updateByPrimaryKeySelectiveEnabled" value="false" />
|
||||||
|
<option name="updateByPrimaykeyEnabled" value="false" />
|
||||||
|
<option name="useActualColumnName" value="false" />
|
||||||
|
</TableGenerateConfig>
|
||||||
|
</value>
|
||||||
|
</entry>
|
||||||
|
<entry key="han_note:t_user">
|
||||||
|
<value>
|
||||||
|
<TableGenerateConfig>
|
||||||
|
<option name="deleteByPrimayKeyEnabled" value="false" />
|
||||||
|
<option name="generatedKey" value="id" />
|
||||||
|
<option name="insertMethodEnabled" value="false" />
|
||||||
|
<option name="insertSelectiveMethodEnabled" value="false" />
|
||||||
|
<option name="javaModelName" value="UserDO" />
|
||||||
|
<option name="moduleName" value="han-note-auth" />
|
||||||
|
<option name="mybatisplusIdType" value="ASSIGN_ID" />
|
||||||
|
<option name="selectByPrimaryKeyEnabled" value="false" />
|
||||||
|
<option name="sequenceColumn" value="" />
|
||||||
|
<option name="sequenceId" value="" />
|
||||||
|
<option name="updateByPrimaryKeySelectiveEnabled" value="false" />
|
||||||
|
<option name="updateByPrimaykeyEnabled" value="false" />
|
||||||
|
<option name="useActualColumnName" value="false" />
|
||||||
|
</TableGenerateConfig>
|
||||||
|
</value>
|
||||||
|
</entry>
|
||||||
|
<entry key="han_note:t_user_count">
|
||||||
|
<value>
|
||||||
|
<TableGenerateConfig>
|
||||||
|
<option name="deleteByPrimayKeyEnabled" value="false" />
|
||||||
|
<option name="generatedKey" value="id" />
|
||||||
|
<option name="insertMethodEnabled" value="false" />
|
||||||
|
<option name="insertSelectiveMethodEnabled" value="false" />
|
||||||
|
<option name="javaModelName" value="UserCountDO" />
|
||||||
|
<option name="moduleName" value="han-note-count-biz" />
|
||||||
|
<option name="mybatisplusIdType" value="AUTO" />
|
||||||
|
<option name="selectByPrimaryKeyEnabled" value="false" />
|
||||||
|
<option name="sequenceColumn" value="" />
|
||||||
|
<option name="sequenceId" value="" />
|
||||||
|
<option name="updateByPrimaryKeySelectiveEnabled" value="false" />
|
||||||
|
<option name="updateByPrimaykeyEnabled" value="false" />
|
||||||
|
<option name="useActualColumnName" value="false" />
|
||||||
|
</TableGenerateConfig>
|
||||||
|
</value>
|
||||||
|
</entry>
|
||||||
|
<entry key="han_note:t_user_role_rel">
|
||||||
|
<value>
|
||||||
|
<TableGenerateConfig>
|
||||||
|
<option name="deleteByPrimayKeyEnabled" value="false" />
|
||||||
|
<option name="generatedKey" value="id" />
|
||||||
|
<option name="insertMethodEnabled" value="false" />
|
||||||
|
<option name="insertSelectiveMethodEnabled" value="false" />
|
||||||
|
<option name="javaModelName" value="UserRoleDO" />
|
||||||
|
<option name="moduleName" value="han-note-auth" />
|
||||||
|
<option name="mybatisplusIdType" value="AUTO" />
|
||||||
|
<option name="selectByPrimaryKeyEnabled" value="false" />
|
||||||
|
<option name="sequenceColumn" value="" />
|
||||||
|
<option name="sequenceId" value="" />
|
||||||
|
<option name="updateByPrimaryKeySelectiveEnabled" value="false" />
|
||||||
|
<option name="updateByPrimaykeyEnabled" value="false" />
|
||||||
|
<option name="useActualColumnName" value="false" />
|
||||||
|
</TableGenerateConfig>
|
||||||
|
</value>
|
||||||
|
</entry>
|
||||||
|
</map>
|
||||||
|
</option>
|
||||||
|
<option name="updateByPrimaryKeySelectiveEnabled" value="false" />
|
||||||
|
<option name="updateByPrimaykeyEnabled" value="false" />
|
||||||
|
<option name="userMybatisPlus" value="true" />
|
||||||
|
<option name="xmlMapperPackage" value="mapperxml" />
|
||||||
|
<option name="xmlMapperPath" value="$PROJECT_DIR$/han-note-comment/han-note-comment-biz/src/main/resources" />
|
||||||
|
</ProjectProfile>
|
||||||
|
</option>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
54
.idea/compiler.xml
generated
Normal file
54
.idea/compiler.xml
generated
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="CompilerConfiguration">
|
||||||
|
<annotationProcessing>
|
||||||
|
<profile default="true" name="Default" enabled="true" />
|
||||||
|
<profile name="Maven default annotation processors profile" enabled="true">
|
||||||
|
<sourceOutputDir name="target/generated-sources/annotations" />
|
||||||
|
<sourceTestOutputDir name="target/generated-test-sources/test-annotations" />
|
||||||
|
<outputRelativeToContentRoot value="true" />
|
||||||
|
</profile>
|
||||||
|
<profile name="Annotation profile for han-note" enabled="true">
|
||||||
|
<sourceOutputDir name="target/generated-sources/annotations" />
|
||||||
|
<sourceTestOutputDir name="target/generated-test-sources/test-annotations" />
|
||||||
|
<outputRelativeToContentRoot value="true" />
|
||||||
|
<processorPath useClasspath="false">
|
||||||
|
<entry name="$MAVEN_REPOSITORY$/org/projectlombok/lombok/1.18.30/lombok-1.18.30.jar" />
|
||||||
|
</processorPath>
|
||||||
|
<module name="han-note-distributed-id-generator-api" />
|
||||||
|
<module name="han-note-data-align" />
|
||||||
|
<module name="han-note-note-biz" />
|
||||||
|
<module name="han-note-user-relation-api" />
|
||||||
|
<module name="han-note-user-api" />
|
||||||
|
<module name="han-note-user-biz" />
|
||||||
|
<module name="han-note-distributed-id-generator-biz" />
|
||||||
|
<module name="han-note-user-relation-biz" />
|
||||||
|
<module name="han-note-oss-biz" />
|
||||||
|
<module name="han-note-comment-biz" />
|
||||||
|
<module name="han-note-count-api" />
|
||||||
|
<module name="han-note-oss-api" />
|
||||||
|
<module name="han-note-count-biz" />
|
||||||
|
<module name="han-note-comment-api" />
|
||||||
|
<module name="hanserwei-spring-boot-starter-biz-operationlog" />
|
||||||
|
<module name="han-note-gateway" />
|
||||||
|
<module name="han-note-note-api" />
|
||||||
|
<module name="hanserwei-spring-boot-starter-jackson" />
|
||||||
|
<module name="han-note-kv-api" />
|
||||||
|
<module name="han-note-search-api" />
|
||||||
|
<module name="han-note-kv-biz" />
|
||||||
|
<module name="hanserwei-common" />
|
||||||
|
<module name="han-note-auth" />
|
||||||
|
<module name="han-note-search-biz" />
|
||||||
|
<module name="hanserwei-spring-boot-starter-biz-context" />
|
||||||
|
</profile>
|
||||||
|
</annotationProcessing>
|
||||||
|
<bytecodeTargetLevel>
|
||||||
|
<module name="hanserwei-spring-starter-biz-context" target="21" />
|
||||||
|
</bytecodeTargetLevel>
|
||||||
|
</component>
|
||||||
|
<component name="JavacSettings">
|
||||||
|
<option name="ADDITIONAL_OPTIONS_OVERRIDE">
|
||||||
|
<module name="han-note-auth" options="" />
|
||||||
|
</option>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
57
.idea/dataSources.xml
generated
Normal file
57
.idea/dataSources.xml
generated
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="DataSourceManagerImpl" format="xml" multifile-model="true">
|
||||||
|
<data-source source="LOCAL" name="han_note@127.0.0.1" uuid="f2474a4a-e4f1-4afa-bd43-7ae7738b47c5">
|
||||||
|
<driver-ref>mysql.8</driver-ref>
|
||||||
|
<synchronize>true</synchronize>
|
||||||
|
<imported>true</imported>
|
||||||
|
<jdbc-driver>com.mysql.cj.jdbc.Driver</jdbc-driver>
|
||||||
|
<jdbc-url>jdbc:mysql://127.0.0.1:3306/han_note?useUnicode=true&characterEncoding=utf-8&autoReconnect=true&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true</jdbc-url>
|
||||||
|
<jdbc-additional-properties>
|
||||||
|
<property name="com.intellij.clouds.kubernetes.db.host.port" />
|
||||||
|
<property name="com.intellij.clouds.kubernetes.db.enabled" value="false" />
|
||||||
|
<property name="com.intellij.clouds.kubernetes.db.container.port" />
|
||||||
|
</jdbc-additional-properties>
|
||||||
|
<working-dir>$ProjectFileDir$</working-dir>
|
||||||
|
</data-source>
|
||||||
|
<data-source source="LOCAL" name="5@127.0.0.1" uuid="5b969fbe-0f66-42be-8d30-ff21036ab8a4">
|
||||||
|
<driver-ref>redis</driver-ref>
|
||||||
|
<synchronize>true</synchronize>
|
||||||
|
<imported>true</imported>
|
||||||
|
<jdbc-driver>jdbc.RedisDriver</jdbc-driver>
|
||||||
|
<jdbc-url>jdbc:redis://127.0.0.1:6379/5</jdbc-url>
|
||||||
|
<jdbc-additional-properties>
|
||||||
|
<property name="com.intellij.clouds.kubernetes.db.host.port" />
|
||||||
|
<property name="com.intellij.clouds.kubernetes.db.enabled" value="false" />
|
||||||
|
<property name="com.intellij.clouds.kubernetes.db.container.port" />
|
||||||
|
</jdbc-additional-properties>
|
||||||
|
<working-dir>$ProjectFileDir$</working-dir>
|
||||||
|
</data-source>
|
||||||
|
<data-source source="LOCAL" name="hannote@127.0.0.1" uuid="19b59a4f-95b9-451e-9051-bb3d5ce210ee">
|
||||||
|
<driver-ref>cassandra</driver-ref>
|
||||||
|
<synchronize>true</synchronize>
|
||||||
|
<imported>true</imported>
|
||||||
|
<jdbc-driver>com.ing.data.cassandra.jdbc.CassandraDriver</jdbc-driver>
|
||||||
|
<jdbc-url>jdbc:cassandra://127.0.0.1:9042/hannote</jdbc-url>
|
||||||
|
<jdbc-additional-properties>
|
||||||
|
<property name="com.intellij.clouds.kubernetes.db.host.port" />
|
||||||
|
<property name="com.intellij.clouds.kubernetes.db.enabled" value="false" />
|
||||||
|
<property name="com.intellij.clouds.kubernetes.db.container.port" />
|
||||||
|
</jdbc-additional-properties>
|
||||||
|
<working-dir>$ProjectFileDir$</working-dir>
|
||||||
|
</data-source>
|
||||||
|
<data-source source="LOCAL" name="leaf@127.0.0.1" uuid="c4c1f1dc-816f-4113-88d6-9ebd7677af82">
|
||||||
|
<driver-ref>mysql.8</driver-ref>
|
||||||
|
<synchronize>true</synchronize>
|
||||||
|
<imported>true</imported>
|
||||||
|
<jdbc-driver>com.mysql.cj.jdbc.Driver</jdbc-driver>
|
||||||
|
<jdbc-url>jdbc:mysql://127.0.0.1:3306/leaf?useUnicode=true&characterEncoding=utf-8&autoReconnect=true&useSSL=false&serverTimezone=Asia/Shanghai</jdbc-url>
|
||||||
|
<jdbc-additional-properties>
|
||||||
|
<property name="com.intellij.clouds.kubernetes.db.host.port" />
|
||||||
|
<property name="com.intellij.clouds.kubernetes.db.enabled" value="false" />
|
||||||
|
<property name="com.intellij.clouds.kubernetes.db.container.port" />
|
||||||
|
</jdbc-additional-properties>
|
||||||
|
<working-dir>$ProjectFileDir$</working-dir>
|
||||||
|
</data-source>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
13
.idea/data_source_mapping.xml
generated
Normal file
13
.idea/data_source_mapping.xml
generated
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="DataSourcePerFileMappings">
|
||||||
|
<file url="file://$APPLICATION_CONFIG_DIR$/consoles/db/19b59a4f-95b9-451e-9051-bb3d5ce210ee/console.sql" value="19b59a4f-95b9-451e-9051-bb3d5ce210ee" />
|
||||||
|
<file url="file://$APPLICATION_CONFIG_DIR$/consoles/db/5b969fbe-0f66-42be-8d30-ff21036ab8a4/console.sql" value="5b969fbe-0f66-42be-8d30-ff21036ab8a4" />
|
||||||
|
<file url="file://$APPLICATION_CONFIG_DIR$/consoles/db/f2474a4a-e4f1-4afa-bd43-7ae7738b47c5/console.sql" value="f2474a4a-e4f1-4afa-bd43-7ae7738b47c5" />
|
||||||
|
<file url="file://$PROJECT_DIR$/han-note-data-align/src/main/resources/mapperxml/InsertMapper.xml" value="f2474a4a-e4f1-4afa-bd43-7ae7738b47c5" />
|
||||||
|
<file url="file://$PROJECT_DIR$/han-note-data-align/src/main/resources/mapperxml/SelectRecordMapper.xml" value="f2474a4a-e4f1-4afa-bd43-7ae7738b47c5" />
|
||||||
|
<file url="file://$PROJECT_DIR$/sql/createData.sql" value="f2474a4a-e4f1-4afa-bd43-7ae7738b47c5" />
|
||||||
|
<file url="file://$PROJECT_DIR$/sql/createTable.sql" value="f2474a4a-e4f1-4afa-bd43-7ae7738b47c5" />
|
||||||
|
<file url="file://$PROJECT_DIR$/sql/leafcreatetable.sql" value="c4c1f1dc-816f-4113-88d6-9ebd7677af82" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
19
.idea/dictionaries/project.xml
generated
Normal file
19
.idea/dictionaries/project.xml
generated
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
<component name="ProjectDictionaryState">
|
||||||
|
<dictionary name="project">
|
||||||
|
<words>
|
||||||
|
<w>asyn</w>
|
||||||
|
<w>entrys</w>
|
||||||
|
<w>hannote</w>
|
||||||
|
<w>hanserwei</w>
|
||||||
|
<w>jobhandler</w>
|
||||||
|
<w>mget</w>
|
||||||
|
<w>nacos</w>
|
||||||
|
<w>operationlog</w>
|
||||||
|
<w>rbitmap</w>
|
||||||
|
<w>rustfs</w>
|
||||||
|
<w>zadd</w>
|
||||||
|
<w>zrevrangebyscore</w>
|
||||||
|
<w>zset</w>
|
||||||
|
</words>
|
||||||
|
</dictionary>
|
||||||
|
</component>
|
||||||
20
.idea/encodings.xml
generated
20
.idea/encodings.xml
generated
@@ -3,6 +3,20 @@
|
|||||||
<component name="Encoding" defaultCharsetForPropertiesFiles="UTF-8">
|
<component name="Encoding" defaultCharsetForPropertiesFiles="UTF-8">
|
||||||
<file url="file://$PROJECT_DIR$/han-note-auth/src/main/java" charset="UTF-8" />
|
<file url="file://$PROJECT_DIR$/han-note-auth/src/main/java" charset="UTF-8" />
|
||||||
<file url="file://$PROJECT_DIR$/han-note-auth/src/main/resources" charset="UTF-8" />
|
<file url="file://$PROJECT_DIR$/han-note-auth/src/main/resources" charset="UTF-8" />
|
||||||
|
<file url="file://$PROJECT_DIR$/han-note-comment/han-note-comment-api/src/main/java" charset="UTF-8" />
|
||||||
|
<file url="file://$PROJECT_DIR$/han-note-comment/han-note-comment-api/src/main/resources" charset="UTF-8" />
|
||||||
|
<file url="file://$PROJECT_DIR$/han-note-comment/han-note-comment-biz/src/main/java" charset="UTF-8" />
|
||||||
|
<file url="file://$PROJECT_DIR$/han-note-comment/han-note-comment-biz/src/main/resources" charset="UTF-8" />
|
||||||
|
<file url="file://$PROJECT_DIR$/han-note-comment/src/main/java" charset="UTF-8" />
|
||||||
|
<file url="file://$PROJECT_DIR$/han-note-comment/src/main/resources" charset="UTF-8" />
|
||||||
|
<file url="file://$PROJECT_DIR$/han-note-count/han-note-count-api/src/main/java" charset="UTF-8" />
|
||||||
|
<file url="file://$PROJECT_DIR$/han-note-count/han-note-count-api/src/main/resources" charset="UTF-8" />
|
||||||
|
<file url="file://$PROJECT_DIR$/han-note-count/han-note-count-biz/src/main/java" charset="UTF-8" />
|
||||||
|
<file url="file://$PROJECT_DIR$/han-note-count/han-note-count-biz/src/main/resources" charset="UTF-8" />
|
||||||
|
<file url="file://$PROJECT_DIR$/han-note-count/src/main/java" charset="UTF-8" />
|
||||||
|
<file url="file://$PROJECT_DIR$/han-note-count/src/main/resources" charset="UTF-8" />
|
||||||
|
<file url="file://$PROJECT_DIR$/han-note-data-align/src/main/java" charset="UTF-8" />
|
||||||
|
<file url="file://$PROJECT_DIR$/han-note-data-align/src/main/resources" charset="UTF-8" />
|
||||||
<file url="file://$PROJECT_DIR$/han-note-distributed-id-generator/han-note-distributed-id-generator-api/src/main/java" charset="UTF-8" />
|
<file url="file://$PROJECT_DIR$/han-note-distributed-id-generator/han-note-distributed-id-generator-api/src/main/java" charset="UTF-8" />
|
||||||
<file url="file://$PROJECT_DIR$/han-note-distributed-id-generator/han-note-distributed-id-generator-api/src/main/resources" charset="UTF-8" />
|
<file url="file://$PROJECT_DIR$/han-note-distributed-id-generator/han-note-distributed-id-generator-api/src/main/resources" charset="UTF-8" />
|
||||||
<file url="file://$PROJECT_DIR$/han-note-distributed-id-generator/han-note-distributed-id-generator-biz/src/main/java" charset="UTF-8" />
|
<file url="file://$PROJECT_DIR$/han-note-distributed-id-generator/han-note-distributed-id-generator-biz/src/main/java" charset="UTF-8" />
|
||||||
@@ -29,6 +43,12 @@
|
|||||||
<file url="file://$PROJECT_DIR$/han-note-oss/han-note-oss-biz/src/main/resources" charset="UTF-8" />
|
<file url="file://$PROJECT_DIR$/han-note-oss/han-note-oss-biz/src/main/resources" charset="UTF-8" />
|
||||||
<file url="file://$PROJECT_DIR$/han-note-oss/src/main/java" charset="UTF-8" />
|
<file url="file://$PROJECT_DIR$/han-note-oss/src/main/java" charset="UTF-8" />
|
||||||
<file url="file://$PROJECT_DIR$/han-note-oss/src/main/resources" charset="UTF-8" />
|
<file url="file://$PROJECT_DIR$/han-note-oss/src/main/resources" charset="UTF-8" />
|
||||||
|
<file url="file://$PROJECT_DIR$/han-note-search/han-note-search-api/src/main/java" charset="UTF-8" />
|
||||||
|
<file url="file://$PROJECT_DIR$/han-note-search/han-note-search-api/src/main/resources" charset="UTF-8" />
|
||||||
|
<file url="file://$PROJECT_DIR$/han-note-search/han-note-search-biz/src/main/java" charset="UTF-8" />
|
||||||
|
<file url="file://$PROJECT_DIR$/han-note-search/han-note-search-biz/src/main/resources" charset="UTF-8" />
|
||||||
|
<file url="file://$PROJECT_DIR$/han-note-search/src/main/java" charset="UTF-8" />
|
||||||
|
<file url="file://$PROJECT_DIR$/han-note-search/src/main/resources" charset="UTF-8" />
|
||||||
<file url="file://$PROJECT_DIR$/han-note-user-relation/han-note-user-relation-api/src/main/java" charset="UTF-8" />
|
<file url="file://$PROJECT_DIR$/han-note-user-relation/han-note-user-relation-api/src/main/java" charset="UTF-8" />
|
||||||
<file url="file://$PROJECT_DIR$/han-note-user-relation/han-note-user-relation-api/src/main/resources" charset="UTF-8" />
|
<file url="file://$PROJECT_DIR$/han-note-user-relation/han-note-user-relation-api/src/main/resources" charset="UTF-8" />
|
||||||
<file url="file://$PROJECT_DIR$/han-note-user-relation/han-note-user-relation-biz/src/main/java" charset="UTF-8" />
|
<file url="file://$PROJECT_DIR$/han-note-user-relation/han-note-user-relation-biz/src/main/java" charset="UTF-8" />
|
||||||
|
|||||||
12
.idea/inspectionProfiles/Project_Default.xml
generated
12
.idea/inspectionProfiles/Project_Default.xml
generated
@@ -1,6 +1,14 @@
|
|||||||
<component name="InspectionProjectProfileManager">
|
<component name="InspectionProjectProfileManager">
|
||||||
<profile version="1.0">
|
<profile version="1.0">
|
||||||
<option name="myName" value="Project Default" />
|
<option name="myName" value="Project Default" />
|
||||||
|
<inspection_tool class="ConstantValue" enabled="true" level="WARNING" enabled_by_default="true">
|
||||||
|
<option name="REPORT_CONSTANT_REFERENCE_VALUES" value="false" />
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="DuplicatedCode" enabled="true" level="WEAK WARNING" enabled_by_default="true">
|
||||||
|
<Languages>
|
||||||
|
<language minSize="56" name="Java" />
|
||||||
|
</Languages>
|
||||||
|
</inspection_tool>
|
||||||
<inspection_tool class="IncorrectHttpHeaderInspection" enabled="true" level="WARNING" enabled_by_default="true">
|
<inspection_tool class="IncorrectHttpHeaderInspection" enabled="true" level="WARNING" enabled_by_default="true">
|
||||||
<option name="customHeaders">
|
<option name="customHeaders">
|
||||||
<set>
|
<set>
|
||||||
@@ -15,6 +23,10 @@
|
|||||||
</list>
|
</list>
|
||||||
</option>
|
</option>
|
||||||
</inspection_tool>
|
</inspection_tool>
|
||||||
|
<inspection_tool class="SpringBootConfigYamlInspection" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||||
<inspection_tool class="SqlNoDataSourceInspection" enabled="false" level="WARNING" enabled_by_default="false" />
|
<inspection_tool class="SqlNoDataSourceInspection" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||||
|
<inspection_tool class="SqlResolveInspection" enabled="true" level="ERROR" enabled_by_default="true">
|
||||||
|
<option name="suppressForPossibleStringLiterals" value="true" />
|
||||||
|
</inspection_tool>
|
||||||
</profile>
|
</profile>
|
||||||
</component>
|
</component>
|
||||||
20
.idea/jarRepositories.xml
generated
Normal file
20
.idea/jarRepositories.xml
generated
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="RemoteRepositoriesConfiguration">
|
||||||
|
<remote-repository>
|
||||||
|
<option name="id" value="central" />
|
||||||
|
<option name="name" value="Central Repository" />
|
||||||
|
<option name="url" value="https://repo.maven.apache.org/maven2" />
|
||||||
|
</remote-repository>
|
||||||
|
<remote-repository>
|
||||||
|
<option name="id" value="central" />
|
||||||
|
<option name="name" value="Maven Central repository" />
|
||||||
|
<option name="url" value="https://repo1.maven.org/maven2" />
|
||||||
|
</remote-repository>
|
||||||
|
<remote-repository>
|
||||||
|
<option name="id" value="jboss.community" />
|
||||||
|
<option name="name" value="JBoss Community repository" />
|
||||||
|
<option name="url" value="https://repository.jboss.org/nexus/content/repositories/public/" />
|
||||||
|
</remote-repository>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
9
.idea/sqldialects.xml
generated
Normal file
9
.idea/sqldialects.xml
generated
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="SqlDialectMappings">
|
||||||
|
<file url="file://$PROJECT_DIR$/sql/createData.sql" dialect="MySQL" />
|
||||||
|
<file url="file://$PROJECT_DIR$/sql/createTable.sql" dialect="MySQL" />
|
||||||
|
<file url="file://$PROJECT_DIR$/sql/leafcreatetable.sql" dialect="MySQL" />
|
||||||
|
<file url="PROJECT" dialect="MySQL" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
6
.idea/swagger-settings.xml
generated
Normal file
6
.idea/swagger-settings.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="SwaggerSettings">
|
||||||
|
<option name="defaultPreviewType" value="REDOC" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
24
han-note-comment/han-note-comment-api/pom.xml
Normal file
24
han-note-comment/han-note-comment-api/pom.xml
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<!-- 指定父项目 -->
|
||||||
|
<parent>
|
||||||
|
<groupId>com.hanserwei</groupId>
|
||||||
|
<artifactId>han-note-comment</artifactId>
|
||||||
|
<version>${revision}</version>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<!-- 打包方式 -->
|
||||||
|
<packaging>jar</packaging>
|
||||||
|
|
||||||
|
<artifactId>han-note-comment-api</artifactId>
|
||||||
|
<name>${project.artifactId}</name>
|
||||||
|
<description>RPC层, 供其他服务调用</description>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.hanserwei</groupId>
|
||||||
|
<artifactId>hanserwei-common</artifactId>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
</project>
|
||||||
150
han-note-comment/han-note-comment-biz/pom.xml
Normal file
150
han-note-comment/han-note-comment-biz/pom.xml
Normal file
@@ -0,0 +1,150 @@
|
|||||||
|
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<!-- 指定父项目 -->
|
||||||
|
<parent>
|
||||||
|
<groupId>com.hanserwei</groupId>
|
||||||
|
<artifactId>han-note-comment</artifactId>
|
||||||
|
<version>${revision}</version>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<!-- 打包方式 -->
|
||||||
|
<packaging>jar</packaging>
|
||||||
|
|
||||||
|
<artifactId>han-note-comment-biz</artifactId>
|
||||||
|
<name>${project.artifactId}</name>
|
||||||
|
<description>评论服务业务层</description>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.hanserwei</groupId>
|
||||||
|
<artifactId>hanserwei-common</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- 业务接口日志组件 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.hanserwei</groupId>
|
||||||
|
<artifactId>hanserwei-spring-boot-starter-biz-operationlog</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- 上下文组件 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.hanserwei</groupId>
|
||||||
|
<artifactId>hanserwei-spring-boot-starter-biz-context</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Jackson 组件 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.hanserwei</groupId>
|
||||||
|
<artifactId>hanserwei-spring-boot-starter-jackson</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-web</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-test</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Mybatis-plus -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.baomidou</groupId>
|
||||||
|
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- MySQL 驱动 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.mysql</groupId>
|
||||||
|
<artifactId>mysql-connector-j</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Druid 数据库连接池 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.alibaba</groupId>
|
||||||
|
<artifactId>druid-spring-boot-3-starter</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.cloud</groupId>
|
||||||
|
<artifactId>spring-cloud-starter-bootstrap</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- 服务发现 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.alibaba.cloud</groupId>
|
||||||
|
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Rocket MQ -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.rocketmq</groupId>
|
||||||
|
<artifactId>rocketmq-spring-boot-starter</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Spring Retry 重试框架 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.retry</groupId>
|
||||||
|
<artifactId>spring-retry</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- AOP 切面(Spring Retry 重试框架需要) -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-aop</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Rocket MQ 客户端 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.rocketmq</groupId>
|
||||||
|
<artifactId>rocketmq-client</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.hanserwei</groupId>
|
||||||
|
<artifactId>han-note-distributed-id-generator-api</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.hanserwei</groupId>
|
||||||
|
<artifactId>han-note-kv-api</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- 快手 Buffer Trigger -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.github.phantomthief</groupId>
|
||||||
|
<artifactId>buffer-trigger</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Redis -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-data-redis</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.hanserwei</groupId>
|
||||||
|
<artifactId>han-note-user-api</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Caffeine 本地缓存 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.github.ben-manes.caffeine</groupId>
|
||||||
|
<artifactId>caffeine</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
</project>
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package com.hanserwei.hannote.comment.biz;
|
||||||
|
|
||||||
|
import org.mybatis.spring.annotation.MapperScan;
|
||||||
|
import org.springframework.boot.SpringApplication;
|
||||||
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
import org.springframework.cloud.openfeign.EnableFeignClients;
|
||||||
|
import org.springframework.retry.annotation.EnableRetry;
|
||||||
|
|
||||||
|
@SpringBootApplication
|
||||||
|
@MapperScan("com.hanserwei.hannote.comment.biz.domain.mapper")
|
||||||
|
@EnableFeignClients("com.hanserwei.hannote")
|
||||||
|
@EnableRetry
|
||||||
|
public class HannoteCommentApplication {
|
||||||
|
public static void main(String[] args) {
|
||||||
|
SpringApplication.run(HannoteCommentApplication.class, args);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
package com.hanserwei.hannote.comment.biz.config;
|
||||||
|
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.data.redis.connection.RedisConnectionFactory;
|
||||||
|
import org.springframework.data.redis.core.RedisTemplate;
|
||||||
|
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
|
||||||
|
import org.springframework.data.redis.serializer.StringRedisSerializer;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
public class RedisTemplateConfig {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
|
||||||
|
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
|
||||||
|
// 设置 RedisTemplate 的连接工厂
|
||||||
|
redisTemplate.setConnectionFactory(connectionFactory);
|
||||||
|
|
||||||
|
// 使用 StringRedisSerializer 来序列化和反序列化 redis 的 key 值,确保 key 是可读的字符串
|
||||||
|
redisTemplate.setKeySerializer(new StringRedisSerializer());
|
||||||
|
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
|
||||||
|
|
||||||
|
// 使用 Jackson2JsonRedisSerializer 来序列化和反序列化 redis 的 value 值, 确保存储的是 JSON 格式
|
||||||
|
Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer<>(Object.class);
|
||||||
|
redisTemplate.setValueSerializer(serializer);
|
||||||
|
redisTemplate.setHashValueSerializer(serializer);
|
||||||
|
|
||||||
|
redisTemplate.afterPropertiesSet();
|
||||||
|
return redisTemplate;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
package com.hanserwei.hannote.comment.biz.config;
|
||||||
|
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.retry.backoff.ExponentialBackOffPolicy;
|
||||||
|
import org.springframework.retry.policy.SimpleRetryPolicy;
|
||||||
|
import org.springframework.retry.support.RetryTemplate;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
public class RetryConfig {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private RetryProperties retryProperties;
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public RetryTemplate retryTemplate() {
|
||||||
|
RetryTemplate retryTemplate = new RetryTemplate();
|
||||||
|
|
||||||
|
// 定义重试策略
|
||||||
|
SimpleRetryPolicy retryPolicy = new SimpleRetryPolicy();
|
||||||
|
retryPolicy.setMaxAttempts(retryProperties.getMaxAttempts());// 最大重试次数
|
||||||
|
|
||||||
|
// 定义间隔策略
|
||||||
|
ExponentialBackOffPolicy backOffPolicy = new ExponentialBackOffPolicy();
|
||||||
|
backOffPolicy.setInitialInterval(retryProperties.getInitInterval()); // 初始间隔时间
|
||||||
|
backOffPolicy.setMultiplier(retryProperties.getMultiplier()); // 延迟倍数,默认为2
|
||||||
|
|
||||||
|
retryTemplate.setRetryPolicy(retryPolicy);
|
||||||
|
retryTemplate.setBackOffPolicy(backOffPolicy);
|
||||||
|
|
||||||
|
return retryTemplate;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
package com.hanserwei.hannote.comment.biz.config;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
@ConfigurationProperties(prefix = RetryProperties.PREFIX)
|
||||||
|
@Component
|
||||||
|
@Data
|
||||||
|
public class RetryProperties {
|
||||||
|
|
||||||
|
public static final String PREFIX = "retry";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 最大重试次数
|
||||||
|
*/
|
||||||
|
private Integer maxAttempts = 3;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始间隔时间,单位 ms
|
||||||
|
*/
|
||||||
|
private Integer initInterval = 1000;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 乘积(每次乘以 2)
|
||||||
|
*/
|
||||||
|
private Double multiplier = 2.0;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package com.hanserwei.hannote.comment.biz.config;
|
||||||
|
|
||||||
|
import org.apache.rocketmq.spring.autoconfigure.RocketMQAutoConfiguration;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.context.annotation.Import;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@Import(RocketMQAutoConfiguration.class)
|
||||||
|
public class RocketMQConfig {
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
package com.hanserwei.hannote.comment.biz.config;
|
||||||
|
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
||||||
|
|
||||||
|
import java.util.concurrent.ThreadPoolExecutor;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
public class ThreadPoolConfig {
|
||||||
|
|
||||||
|
@Bean(name = "taskExecutor")
|
||||||
|
public ThreadPoolTaskExecutor taskExecutor() {
|
||||||
|
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
|
||||||
|
// 核心线程数
|
||||||
|
executor.setCorePoolSize(10);
|
||||||
|
// 最大线程数
|
||||||
|
executor.setMaxPoolSize(50);
|
||||||
|
// 队列容量
|
||||||
|
executor.setQueueCapacity(200);
|
||||||
|
// 线程活跃时间(秒)
|
||||||
|
executor.setKeepAliveSeconds(30);
|
||||||
|
// 线程名前缀
|
||||||
|
executor.setThreadNamePrefix("NoteExecutor-");
|
||||||
|
|
||||||
|
// 拒绝策略:由调用线程处理(一般为主线程)
|
||||||
|
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
|
||||||
|
|
||||||
|
// 等待所有任务结束后再关闭线程池
|
||||||
|
executor.setWaitForTasksToCompleteOnShutdown(true);
|
||||||
|
// 设置等待时间,如果超过这个时间还没有销毁就强制销毁,以确保应用最后能够被关闭,而不是被没有完成的任务阻塞
|
||||||
|
executor.setAwaitTerminationSeconds(60);
|
||||||
|
|
||||||
|
executor.initialize();
|
||||||
|
return executor;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
package com.hanserwei.hannote.comment.biz.constants;
|
||||||
|
|
||||||
|
public interface MQConstants {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Topic: 评论发布
|
||||||
|
*/
|
||||||
|
String TOPIC_PUBLISH_COMMENT = "PublishCommentTopic";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Topic: 笔记评论总数计数
|
||||||
|
*/
|
||||||
|
String TOPIC_COUNT_NOTE_COMMENT = "CountNoteCommentTopic";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Topic: 评论热度值更新
|
||||||
|
*/
|
||||||
|
String TOPIC_COMMENT_HEAT_UPDATE = "CommentHeatUpdateTopic";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Topic: 评论点赞、取消点赞共用一个 Topic
|
||||||
|
*/
|
||||||
|
String TOPIC_COMMENT_LIKE_OR_UNLIKE = "CommentLikeUnlikeTopic";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Topic: 删除本地缓存 —— 评论详情
|
||||||
|
*/
|
||||||
|
String TOPIC_DELETE_COMMENT_LOCAL_CACHE = "DeleteCommentDetailLocalCacheTopic";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Topic: 删除评论
|
||||||
|
*/
|
||||||
|
String TOPIC_DELETE_COMMENT = "DeleteCommentTopic";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tag 标签:点赞
|
||||||
|
*/
|
||||||
|
String TAG_LIKE = "Like";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tag 标签:取消点赞
|
||||||
|
*/
|
||||||
|
String TAG_UNLIKE = "UnLike";
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,123 @@
|
|||||||
|
package com.hanserwei.hannote.comment.biz.constants;
|
||||||
|
|
||||||
|
public class RedisKeyConstants {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Key 前缀:一级评论的 first_reply_comment_id 字段值是否更新标识
|
||||||
|
*/
|
||||||
|
private static final String HAVE_FIRST_REPLY_COMMENT_KEY_PREFIX = "comment:havaFirstReplyCommentId:";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Key 前缀:布隆过滤器 - 用户点赞的评论
|
||||||
|
*/
|
||||||
|
private static final String BLOOM_COMMENT_LIKES_KEY_PREFIX = "bloom:comment:likes:";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Key 前缀:二级评论分页 ZSET
|
||||||
|
*/
|
||||||
|
private static final String CHILD_COMMENT_LIST_KEY_PREFIX = "comment:childList:";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hash Field: 子评论总数
|
||||||
|
*/
|
||||||
|
public static final String FIELD_CHILD_COMMENT_TOTAL = "childCommentTotal";
|
||||||
|
/**
|
||||||
|
* Hash Field: 点赞总数
|
||||||
|
*/
|
||||||
|
public static final String FIELD_LIKE_TOTAL = "likeTotal";
|
||||||
|
/**
|
||||||
|
* 评论维度计数 Key 前缀
|
||||||
|
*/
|
||||||
|
private static final String COUNT_COMMENT_KEY_PREFIX = "count:comment:";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hash Field 键:评论总数
|
||||||
|
*/
|
||||||
|
public static final String FIELD_COMMENT_TOTAL = "commentTotal";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Key 前缀:笔记评论总数
|
||||||
|
*/
|
||||||
|
private static final String COUNT_COMMENT_TOTAL_KEY_PREFIX = "count:note:";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Key 前缀:评论分页 ZSET
|
||||||
|
*/
|
||||||
|
private static final String COMMENT_LIST_KEY_PREFIX = "comment:list:";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Key 前缀:评论详情 JSON
|
||||||
|
*/
|
||||||
|
private static final String COMMENT_DETAIL_KEY_PREFIX = "comment:detail:";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构建 布隆过滤器 - 用户点赞的评论 完整 KEY
|
||||||
|
*
|
||||||
|
* @param userId 用户 ID
|
||||||
|
* @return 布隆过滤器 - 用户点赞的评论 完整 KEY
|
||||||
|
*/
|
||||||
|
public static String buildBloomCommentLikesKey(Long userId) {
|
||||||
|
return BLOOM_COMMENT_LIKES_KEY_PREFIX + userId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构建子评论分页 ZSET 完整 KEY
|
||||||
|
*
|
||||||
|
* @param commentId 一级评论 ID
|
||||||
|
* @return 子评论分页 ZSET 完整 KEY
|
||||||
|
*/
|
||||||
|
public static String buildChildCommentListKey(Long commentId) {
|
||||||
|
return CHILD_COMMENT_LIST_KEY_PREFIX + commentId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构建评论维度计数 Key
|
||||||
|
*
|
||||||
|
* @param commentId 评论 ID
|
||||||
|
* @return 评论维度计数 Key
|
||||||
|
*/
|
||||||
|
public static String buildCountCommentKey(Long commentId) {
|
||||||
|
return COUNT_COMMENT_KEY_PREFIX + commentId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构建完整 KEY
|
||||||
|
*
|
||||||
|
* @param commentId 一级评论 ID
|
||||||
|
* @return 完整 KEY
|
||||||
|
*/
|
||||||
|
public static String buildHaveFirstReplyCommentKey(Long commentId) {
|
||||||
|
return HAVE_FIRST_REPLY_COMMENT_KEY_PREFIX + commentId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构建笔记评论总数完整 KEY
|
||||||
|
*
|
||||||
|
* @param noteId 笔记 ID
|
||||||
|
* @return 笔记评论总数完整 KEY
|
||||||
|
*/
|
||||||
|
public static String buildNoteCommentTotalKey(Long noteId) {
|
||||||
|
return COUNT_COMMENT_TOTAL_KEY_PREFIX + noteId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构建评论分页 ZSET 完整 KEY
|
||||||
|
*
|
||||||
|
* @param noteId 笔记 ID
|
||||||
|
* @return 评论分页 ZSET 完整 KEY
|
||||||
|
*/
|
||||||
|
public static String buildCommentListKey(Long noteId) {
|
||||||
|
return COMMENT_LIST_KEY_PREFIX + noteId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构建评论详情完整 KEY
|
||||||
|
*
|
||||||
|
* @param commentId 评论 ID
|
||||||
|
* @return 评论详情完整 KEY
|
||||||
|
*/
|
||||||
|
public static String buildCommentDetailKey(Object commentId) {
|
||||||
|
return COMMENT_DETAIL_KEY_PREFIX + commentId;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,295 @@
|
|||||||
|
package com.hanserwei.hannote.comment.biz.consumer;
|
||||||
|
|
||||||
|
import cn.hutool.core.collection.CollUtil;
|
||||||
|
import com.google.common.collect.Lists;
|
||||||
|
import com.google.common.collect.Maps;
|
||||||
|
import com.google.common.util.concurrent.RateLimiter;
|
||||||
|
import com.hanserwei.framework.common.utils.JsonUtils;
|
||||||
|
import com.hanserwei.hannote.comment.biz.constants.MQConstants;
|
||||||
|
import com.hanserwei.hannote.comment.biz.constants.RedisKeyConstants;
|
||||||
|
import com.hanserwei.hannote.comment.biz.domain.dataobject.CommentDO;
|
||||||
|
import com.hanserwei.hannote.comment.biz.domain.mapper.CommentDOMapper;
|
||||||
|
import com.hanserwei.hannote.comment.biz.enums.CommentLevelEnum;
|
||||||
|
import com.hanserwei.hannote.comment.biz.model.bo.CommentBO;
|
||||||
|
import com.hanserwei.hannote.comment.biz.model.dto.CountPublishCommentMqDTO;
|
||||||
|
import com.hanserwei.hannote.comment.biz.model.dto.PublishCommentMqDTO;
|
||||||
|
import com.hanserwei.hannote.comment.biz.rpc.KeyValueRpcService;
|
||||||
|
import jakarta.annotation.PreDestroy;
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer;
|
||||||
|
import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus;
|
||||||
|
import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently;
|
||||||
|
import org.apache.rocketmq.client.exception.MQClientException;
|
||||||
|
import org.apache.rocketmq.client.producer.SendCallback;
|
||||||
|
import org.apache.rocketmq.client.producer.SendResult;
|
||||||
|
import org.apache.rocketmq.common.consumer.ConsumeFromWhere;
|
||||||
|
import org.apache.rocketmq.common.message.MessageExt;
|
||||||
|
import org.apache.rocketmq.remoting.protocol.heartbeat.MessageModel;
|
||||||
|
import org.apache.rocketmq.spring.core.RocketMQTemplate;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.core.io.ClassPathResource;
|
||||||
|
import org.springframework.data.redis.core.RedisTemplate;
|
||||||
|
import org.springframework.data.redis.core.script.DefaultRedisScript;
|
||||||
|
import org.springframework.messaging.support.MessageBuilder;
|
||||||
|
import org.springframework.scripting.support.ResourceScriptSource;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.transaction.support.TransactionTemplate;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
@SuppressWarnings("UnstableApiUsage")
|
||||||
|
@Component
|
||||||
|
@Slf4j
|
||||||
|
public class Comment2DBConsumer {
|
||||||
|
|
||||||
|
@Value("${rocketmq.name-server}")
|
||||||
|
private String nameServer;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private CommentDOMapper commentDOMapper;
|
||||||
|
@Resource
|
||||||
|
private TransactionTemplate transactionTemplate;
|
||||||
|
@Resource
|
||||||
|
private KeyValueRpcService keyValueRpcService;
|
||||||
|
@Resource
|
||||||
|
private RocketMQTemplate rocketMQTemplate;
|
||||||
|
@Resource
|
||||||
|
private RedisTemplate<String, Object> redisTemplate;
|
||||||
|
|
||||||
|
private DefaultMQPushConsumer consumer;
|
||||||
|
|
||||||
|
// 每秒创建 1000 个令牌
|
||||||
|
private final RateLimiter rateLimiter = RateLimiter.create(1000);
|
||||||
|
|
||||||
|
@Bean(name = "Comment2DBConsumer")
|
||||||
|
public DefaultMQPushConsumer mqPushConsumer() throws MQClientException {
|
||||||
|
// Group组
|
||||||
|
String group = "han_note_group_" + MQConstants.TOPIC_PUBLISH_COMMENT;
|
||||||
|
|
||||||
|
// 创建一个新的DefaultMQPushConsumer示例并指定消费者的消费组名
|
||||||
|
consumer = new DefaultMQPushConsumer(group);
|
||||||
|
|
||||||
|
// 设置NameServer地址
|
||||||
|
consumer.setNamesrvAddr(nameServer);
|
||||||
|
|
||||||
|
// 订阅指定的主题,并设置主题的订阅规则("*" 表示订阅所有标签的消息)
|
||||||
|
consumer.subscribe(MQConstants.TOPIC_PUBLISH_COMMENT, "*");
|
||||||
|
|
||||||
|
// 设置消费者消费消息的起始位置,如果队列中没有消息,则从最新的消息开始消费。
|
||||||
|
consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_LAST_OFFSET);
|
||||||
|
|
||||||
|
// 设置消息消费模式,这里使用集群模式 (CLUSTERING)
|
||||||
|
consumer.setMessageModel(MessageModel.CLUSTERING);
|
||||||
|
|
||||||
|
// 设置每批次消费的最大消息数量,这里设置为 30,表示每次拉取时最多消费 30 条消息
|
||||||
|
consumer.setConsumeMessageBatchMaxSize(30);
|
||||||
|
|
||||||
|
// 消息体 Json 字符串转 DTO
|
||||||
|
List<PublishCommentMqDTO> publishCommentMqDTOS = Lists.newArrayList();
|
||||||
|
|
||||||
|
// 注册消息监听器
|
||||||
|
consumer.registerMessageListener((MessageListenerConcurrently) (msgs, context) -> {
|
||||||
|
log.info("==> 本批次消息大小: {}", msgs.size());
|
||||||
|
try {
|
||||||
|
// 令牌桶流控
|
||||||
|
rateLimiter.acquire();
|
||||||
|
|
||||||
|
for (MessageExt msg : msgs) {
|
||||||
|
String message = new String(msg.getBody());
|
||||||
|
log.info("==> Consumer - Received message: {}", message);
|
||||||
|
|
||||||
|
publishCommentMqDTOS.add(JsonUtils.parseObject(message, PublishCommentMqDTO.class));
|
||||||
|
}
|
||||||
|
// 提取所有不为空的回复评论 ID
|
||||||
|
List<Long> replyCommentIds = publishCommentMqDTOS.stream()
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.map(PublishCommentMqDTO::getReplyCommentId)
|
||||||
|
.toList();
|
||||||
|
// 批量查询相关回复评论记录
|
||||||
|
List<CommentDO> replyCommentDOS = null;
|
||||||
|
if (CollUtil.isNotEmpty(replyCommentIds)) {
|
||||||
|
// 批量查询数据库
|
||||||
|
replyCommentDOS = commentDOMapper.selectByCommentIds(replyCommentIds);
|
||||||
|
}
|
||||||
|
// DO 集合转 <评论 ID - 评论 DO> 字典, 以方便后续查找
|
||||||
|
Map<Long, CommentDO> commentIdAndCommentDOMap = Maps.newHashMap();
|
||||||
|
if (CollUtil.isNotEmpty(replyCommentDOS)) {
|
||||||
|
commentIdAndCommentDOMap = replyCommentDOS.stream()
|
||||||
|
.collect(Collectors.toMap(CommentDO::getId, commentDO -> commentDO));
|
||||||
|
}
|
||||||
|
|
||||||
|
// DTO 转 BO
|
||||||
|
List<CommentBO> commentBOS = Lists.newArrayList();
|
||||||
|
for (PublishCommentMqDTO publishCommentMqDTO : publishCommentMqDTOS) {
|
||||||
|
String imageUrl = publishCommentMqDTO.getImageUrl();
|
||||||
|
CommentBO commentBO = CommentBO.builder()
|
||||||
|
.id(publishCommentMqDTO.getCommentId())
|
||||||
|
.noteId(publishCommentMqDTO.getNoteId())
|
||||||
|
.userId(publishCommentMqDTO.getCreatorId())
|
||||||
|
.isContentEmpty(true) // 默认评论内容为空
|
||||||
|
.imageUrl(StringUtils.isBlank(imageUrl) ? "" : imageUrl)
|
||||||
|
.level(CommentLevelEnum.ONE.getCode()) // 默认为一级评论
|
||||||
|
.parentId(publishCommentMqDTO.getNoteId()) // 默认设置为所属笔记 ID
|
||||||
|
.createTime(publishCommentMqDTO.getCreateTime())
|
||||||
|
.updateTime(publishCommentMqDTO.getCreateTime())
|
||||||
|
.isTop(false)
|
||||||
|
.replyTotal(0L)
|
||||||
|
.likeTotal(0L)
|
||||||
|
.replyCommentId(0L)
|
||||||
|
.replyUserId(0L)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
// 评论内容若不为空
|
||||||
|
String content = publishCommentMqDTO.getContent();
|
||||||
|
if (StringUtils.isNotBlank(content)) {
|
||||||
|
commentBO.setContentUuid(UUID.randomUUID().toString()); // 生成评论内容的 UUID 标识
|
||||||
|
commentBO.setIsContentEmpty(false);
|
||||||
|
commentBO.setContent(content);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置评论级别、回复用户 ID (reply_user_id)、父评论 ID (parent_id)
|
||||||
|
Long replyCommentId = publishCommentMqDTO.getReplyCommentId();
|
||||||
|
if (Objects.nonNull(replyCommentId)) {
|
||||||
|
CommentDO replyCommentDO = commentIdAndCommentDOMap.get(replyCommentId);
|
||||||
|
|
||||||
|
if (Objects.nonNull(replyCommentDO)) {
|
||||||
|
// 若回复的评论 ID 不为空,说明是二级评论
|
||||||
|
commentBO.setLevel(CommentLevelEnum.TWO.getCode());
|
||||||
|
|
||||||
|
commentBO.setReplyCommentId(publishCommentMqDTO.getReplyCommentId());
|
||||||
|
// 父评论 ID
|
||||||
|
commentBO.setParentId(replyCommentDO.getId());
|
||||||
|
if (Objects.equals(replyCommentDO.getLevel(), CommentLevelEnum.TWO.getCode())) { // 如果回复的评论属于二级评论
|
||||||
|
commentBO.setParentId(replyCommentDO.getParentId());
|
||||||
|
}
|
||||||
|
// 回复的哪个用户
|
||||||
|
commentBO.setReplyUserId(replyCommentDO.getUserId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
commentBOS.add(commentBO);
|
||||||
|
}
|
||||||
|
|
||||||
|
log.info("## 清洗后的 CommentBOS: {}", JsonUtils.toJsonString(commentBOS));
|
||||||
|
// 编程式事务,保证整体操作的原子性
|
||||||
|
Integer insertedRows = transactionTemplate.execute(status -> {
|
||||||
|
try {
|
||||||
|
// 先批量存入评论元数据
|
||||||
|
int count = commentDOMapper.batchInsert(commentBOS);
|
||||||
|
|
||||||
|
// 过滤出评论内容不为空的 BO
|
||||||
|
List<CommentBO> commentContentNotEmptyBOS = commentBOS.stream()
|
||||||
|
.filter(commentBO -> Boolean.FALSE.equals(commentBO.getIsContentEmpty()))
|
||||||
|
.toList();
|
||||||
|
if (CollUtil.isNotEmpty(commentContentNotEmptyBOS)) {
|
||||||
|
// 批量存入评论内容
|
||||||
|
boolean result = keyValueRpcService.batchSaveCommentContent(commentContentNotEmptyBOS);
|
||||||
|
if (!result) {
|
||||||
|
throw new RuntimeException("批量保存评论内容失败");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return count;
|
||||||
|
} catch (Exception ex) {
|
||||||
|
status.setRollbackOnly(); // 标记事务为回滚
|
||||||
|
log.error("", ex);
|
||||||
|
throw ex;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 如果批量插入的行数大于 0
|
||||||
|
if (Objects.nonNull(insertedRows) && insertedRows > 0) {
|
||||||
|
// 构建发送给计数服务的 DTO 集合
|
||||||
|
List<CountPublishCommentMqDTO> countPublishCommentMqDTOS = commentBOS.stream()
|
||||||
|
.map(commentBO -> CountPublishCommentMqDTO.builder()
|
||||||
|
.noteId(commentBO.getNoteId())
|
||||||
|
.commentId(commentBO.getId())
|
||||||
|
.level(commentBO.getLevel())
|
||||||
|
.parentId(commentBO.getParentId())
|
||||||
|
.build())
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
// 异步发送计数 MQ
|
||||||
|
org.springframework.messaging.Message<String> message = MessageBuilder.withPayload(JsonUtils.toJsonString(countPublishCommentMqDTOS))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
// 同步一级评论到 Redis 热点评论 ZSET 中
|
||||||
|
syncOneLevelComment2RedisZSet(commentBOS);
|
||||||
|
|
||||||
|
// 异步发送 MQ 消息
|
||||||
|
rocketMQTemplate.asyncSend(MQConstants.TOPIC_COUNT_NOTE_COMMENT, message, new SendCallback() {
|
||||||
|
@Override
|
||||||
|
public void onSuccess(SendResult sendResult) {
|
||||||
|
log.info("==> 【计数: 评论发布】MQ 发送成功,SendResult: {}", sendResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onException(Throwable throwable) {
|
||||||
|
log.error("==> 【计数: 评论发布】MQ 发送异常: ", throwable);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 手动 ACK,告诉 RocketMQ 这批次消息消费成功
|
||||||
|
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("", e);
|
||||||
|
// 手动 ACK,告诉 RocketMQ 这批次消息处理失败,稍后再进行重试
|
||||||
|
return ConsumeConcurrentlyStatus.RECONSUME_LATER;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 启动消费者
|
||||||
|
consumer.start();
|
||||||
|
return consumer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 同步一级评论到 Redis 热点评论 ZSET 中
|
||||||
|
*
|
||||||
|
* @param commentBOS 评论 BO 列表
|
||||||
|
*/
|
||||||
|
private void syncOneLevelComment2RedisZSet(List<CommentBO> commentBOS) {
|
||||||
|
// 过滤出一级评论,并按所属笔记进行分组,转换为一个 Map 字典
|
||||||
|
Map<Long, List<CommentBO>> commentIdAndBOListMap = commentBOS.stream()
|
||||||
|
.filter(commentBO -> Objects.equals(commentBO.getLevel(), CommentLevelEnum.ONE.getCode())) // 仅过滤一级评论
|
||||||
|
.collect(Collectors.groupingBy(CommentBO::getNoteId));
|
||||||
|
|
||||||
|
// 循环字典
|
||||||
|
commentIdAndBOListMap.forEach((noteId, commentBOList) -> {
|
||||||
|
// 构建 Redis 热点评论 ZSET Key
|
||||||
|
String key = RedisKeyConstants.buildCommentListKey(noteId);
|
||||||
|
|
||||||
|
DefaultRedisScript<Long> script = new DefaultRedisScript<>();
|
||||||
|
// Lua 脚本路径
|
||||||
|
script.setScriptSource(new ResourceScriptSource(new ClassPathResource("/lua/add_hot_comments.lua")));
|
||||||
|
// 返回值类型
|
||||||
|
script.setResultType(Long.class);
|
||||||
|
|
||||||
|
// 构建执行 Lua 脚本所需的 ARGS 参数
|
||||||
|
List<Object> args = Lists.newArrayList();
|
||||||
|
commentBOList.forEach(commentBO -> {
|
||||||
|
args.add(commentBO.getId()); // Member: 评论ID
|
||||||
|
args.add(0); // Score: 热度值,初始值为 0
|
||||||
|
});
|
||||||
|
|
||||||
|
// 执行 Lua 脚本
|
||||||
|
redisTemplate.execute(script, Collections.singletonList(key), args.toArray());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreDestroy
|
||||||
|
public void destroy() {
|
||||||
|
if (Objects.nonNull(consumer)) {
|
||||||
|
try {
|
||||||
|
consumer.shutdown(); // 关闭消费者
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,138 @@
|
|||||||
|
package com.hanserwei.hannote.comment.biz.consumer;
|
||||||
|
|
||||||
|
import com.github.phantomthief.collection.BufferTrigger;
|
||||||
|
import com.google.common.collect.Lists;
|
||||||
|
import com.google.common.collect.Sets;
|
||||||
|
import com.hanserwei.framework.common.utils.JsonUtils;
|
||||||
|
import com.hanserwei.hannote.comment.biz.constants.MQConstants;
|
||||||
|
import com.hanserwei.hannote.comment.biz.constants.RedisKeyConstants;
|
||||||
|
import com.hanserwei.hannote.comment.biz.domain.dataobject.CommentDO;
|
||||||
|
import com.hanserwei.hannote.comment.biz.domain.mapper.CommentDOMapper;
|
||||||
|
import com.hanserwei.hannote.comment.biz.model.bo.CommentHeatBO;
|
||||||
|
import com.hanserwei.hannote.comment.biz.utils.HeatCalculator;
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
|
||||||
|
import org.apache.rocketmq.spring.core.RocketMQListener;
|
||||||
|
import org.springframework.core.io.ClassPathResource;
|
||||||
|
import org.springframework.data.redis.core.RedisTemplate;
|
||||||
|
import org.springframework.data.redis.core.script.DefaultRedisScript;
|
||||||
|
import org.springframework.scripting.support.ResourceScriptSource;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
@RocketMQMessageListener(consumerGroup = "han_note_group_" + MQConstants.TOPIC_COMMENT_HEAT_UPDATE, // Group 组
|
||||||
|
topic = MQConstants.TOPIC_COMMENT_HEAT_UPDATE // 主题 Topic
|
||||||
|
)
|
||||||
|
@Slf4j
|
||||||
|
public class CommentHeatUpdateConsumer implements RocketMQListener<String> {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private CommentDOMapper commentDOMapper;
|
||||||
|
@Resource
|
||||||
|
private RedisTemplate<String, Object> redisTemplate;
|
||||||
|
|
||||||
|
private final BufferTrigger<String> bufferTrigger = BufferTrigger.<String>batchBlocking()
|
||||||
|
.bufferSize(50000) // 缓存队列的最大容量
|
||||||
|
.batchSize(300) // 一批次最多聚合 300 条
|
||||||
|
.linger(Duration.ofSeconds(2)) // 多久聚合一次(2s 一次)
|
||||||
|
.setConsumerEx(this::consumeMessage) // 设置消费者方法
|
||||||
|
.build();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onMessage(String body) {
|
||||||
|
// 往 bufferTrigger 中添加元素
|
||||||
|
bufferTrigger.enqueue(body);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void consumeMessage(List<String> bodys) {
|
||||||
|
log.info("==> 【评论热度值计算】聚合消息, size: {}", bodys.size());
|
||||||
|
log.info("==> 【评论热度值计算】聚合消息, {}", JsonUtils.toJsonString(bodys));
|
||||||
|
|
||||||
|
// 将聚合后的消息体 Json 转 Set<Long>, 去重相同的评论 ID, 防止重复计算
|
||||||
|
Set<Long> commentIds = Sets.newHashSet();
|
||||||
|
bodys.forEach(body -> {
|
||||||
|
try {
|
||||||
|
Set<Long> list = JsonUtils.parseSet(body, Long.class);
|
||||||
|
commentIds.addAll(list);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("", e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
log.info("==> 去重后的评论 ID: {}", commentIds);
|
||||||
|
|
||||||
|
// 批量查询评论
|
||||||
|
List<CommentDO> commentDOS = commentDOMapper.selectByCommentIds(commentIds.stream().toList());
|
||||||
|
|
||||||
|
// 评论 ID
|
||||||
|
List<Long> ids = Lists.newArrayList();
|
||||||
|
// 热度值 BO
|
||||||
|
List<CommentHeatBO> commentBOS = Lists.newArrayList();
|
||||||
|
|
||||||
|
//重新计算每条评论的热度值
|
||||||
|
commentDOS.forEach(commentDO -> {
|
||||||
|
Long commentId = commentDO.getId();
|
||||||
|
// 被点赞数
|
||||||
|
Long likeTotal = commentDO.getLikeTotal();
|
||||||
|
// 被回复数
|
||||||
|
Long childCommentTotal = commentDO.getChildCommentTotal();
|
||||||
|
|
||||||
|
// 计算热度值
|
||||||
|
BigDecimal heatNum = HeatCalculator.calculateHeat(likeTotal, childCommentTotal);
|
||||||
|
ids.add(commentId);
|
||||||
|
commentBOS.add(CommentHeatBO.builder()
|
||||||
|
.id(commentId)
|
||||||
|
.heat(heatNum.doubleValue())
|
||||||
|
.noteId(commentDO.getNoteId())
|
||||||
|
.build());
|
||||||
|
});
|
||||||
|
// 批量更新评论热度值
|
||||||
|
commentDOMapper.batchUpdateHeatByCommentIds(ids, commentBOS);
|
||||||
|
|
||||||
|
// 更新 Redis 中热度评论 ZSET
|
||||||
|
updateRedisHotComments(commentBOS);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新 Redis 中热点评论 ZSET
|
||||||
|
*
|
||||||
|
* @param commentHeatBOList 热度值 BO 列表
|
||||||
|
*/
|
||||||
|
private void updateRedisHotComments(List<CommentHeatBO> commentHeatBOList) {
|
||||||
|
// 过滤出热度值大于 0 的,并按所属笔记 ID 分组(若热度等于0,则不进行更新)
|
||||||
|
Map<Long, List<CommentHeatBO>> noteIdAndBOListMap = commentHeatBOList.stream()
|
||||||
|
.filter(commentHeatBO -> commentHeatBO.getHeat() > 0)
|
||||||
|
.collect(Collectors.groupingBy(CommentHeatBO::getNoteId));
|
||||||
|
|
||||||
|
// 循环
|
||||||
|
noteIdAndBOListMap.forEach((noteId, commentHeatBOS) -> {
|
||||||
|
// 构建热点评论 Redis Key
|
||||||
|
String key = RedisKeyConstants.buildCommentListKey(noteId);
|
||||||
|
|
||||||
|
DefaultRedisScript<Long> script = new DefaultRedisScript<>();
|
||||||
|
// Lua 脚本路径
|
||||||
|
script.setScriptSource(new ResourceScriptSource(new ClassPathResource("/lua/update_hot_comments.lua")));
|
||||||
|
// 返回值类型
|
||||||
|
script.setResultType(Long.class);
|
||||||
|
|
||||||
|
// 构建执行 Lua 脚本所需的 ARGS 参数
|
||||||
|
List<Object> args = Lists.newArrayList();
|
||||||
|
commentHeatBOS.forEach(commentHeatBO -> {
|
||||||
|
args.add(commentHeatBO.getId()); // Member: 评论ID
|
||||||
|
args.add(commentHeatBO.getHeat()); // Score: 热度值
|
||||||
|
});
|
||||||
|
|
||||||
|
// 执行 Lua 脚本
|
||||||
|
redisTemplate.execute(script, Collections.singletonList(key), args.toArray());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,184 @@
|
|||||||
|
package com.hanserwei.hannote.comment.biz.consumer;
|
||||||
|
|
||||||
|
import cn.hutool.core.collection.CollUtil;
|
||||||
|
import com.google.common.collect.Lists;
|
||||||
|
import com.google.common.collect.Sets;
|
||||||
|
import com.google.common.util.concurrent.RateLimiter;
|
||||||
|
import com.hanserwei.framework.common.utils.JsonUtils;
|
||||||
|
import com.hanserwei.hannote.comment.biz.constants.MQConstants;
|
||||||
|
import com.hanserwei.hannote.comment.biz.constants.RedisKeyConstants;
|
||||||
|
import com.hanserwei.hannote.comment.biz.domain.dataobject.CommentDO;
|
||||||
|
import com.hanserwei.hannote.comment.biz.domain.mapper.CommentDOMapper;
|
||||||
|
import com.hanserwei.hannote.comment.biz.domain.mapper.NoteCountDOMapper;
|
||||||
|
import com.hanserwei.hannote.comment.biz.enums.CommentLevelEnum;
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.apache.rocketmq.client.producer.SendCallback;
|
||||||
|
import org.apache.rocketmq.client.producer.SendResult;
|
||||||
|
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
|
||||||
|
import org.apache.rocketmq.spring.core.RocketMQListener;
|
||||||
|
import org.apache.rocketmq.spring.core.RocketMQTemplate;
|
||||||
|
import org.springframework.data.redis.core.RedisTemplate;
|
||||||
|
import org.springframework.messaging.support.MessageBuilder;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
@SuppressWarnings("UnstableApiUsage")
|
||||||
|
@Component
|
||||||
|
@Slf4j
|
||||||
|
@RocketMQMessageListener(consumerGroup = "han_note_group_" + MQConstants.TOPIC_DELETE_COMMENT, // Group
|
||||||
|
topic = MQConstants.TOPIC_DELETE_COMMENT // 消费的主题 Topic
|
||||||
|
)
|
||||||
|
public class DeleteCommentConsumer implements RocketMQListener<String> {
|
||||||
|
|
||||||
|
// 每秒创建 1000 个令牌
|
||||||
|
private final RateLimiter rateLimiter = RateLimiter.create(1000);
|
||||||
|
@Resource
|
||||||
|
private CommentDOMapper commentDOMapper;
|
||||||
|
@Resource
|
||||||
|
private NoteCountDOMapper noteCountDOMapper;
|
||||||
|
@Resource
|
||||||
|
private RedisTemplate<String, Object> redisTemplate;
|
||||||
|
@Resource
|
||||||
|
private RocketMQTemplate rocketMQTemplate;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onMessage(String body) {
|
||||||
|
// 令牌桶流控
|
||||||
|
rateLimiter.acquire();
|
||||||
|
|
||||||
|
log.info("## 【删除评论 - 后续业务处理】消费者消费成功, body: {}", body);
|
||||||
|
|
||||||
|
CommentDO commentDO = JsonUtils.parseObject(body, CommentDO.class);
|
||||||
|
|
||||||
|
// 评论级别
|
||||||
|
Integer level = null;
|
||||||
|
if (commentDO != null) {
|
||||||
|
level = commentDO.getLevel();
|
||||||
|
}
|
||||||
|
|
||||||
|
CommentLevelEnum commentLevelEnum = CommentLevelEnum.valueOf(level);
|
||||||
|
|
||||||
|
if (commentLevelEnum != null) {
|
||||||
|
switch (commentLevelEnum) {
|
||||||
|
case ONE -> // 一级评论
|
||||||
|
handleOneLevelComment(commentDO);
|
||||||
|
case TWO -> // 二级评论
|
||||||
|
handleTwoLevelComment(commentDO);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 一级评论处理
|
||||||
|
*
|
||||||
|
* @param commentDO 评论
|
||||||
|
*/
|
||||||
|
private void handleOneLevelComment(CommentDO commentDO) {
|
||||||
|
Long commentId = commentDO.getId();
|
||||||
|
Long noteId = commentDO.getNoteId();
|
||||||
|
|
||||||
|
// 1. 关联评论删除(一级评论下所有子评论,都需要删除)
|
||||||
|
int count = commentDOMapper.deleteByParentId(commentId);
|
||||||
|
|
||||||
|
// 2. 计数更新(笔记下总评论数)
|
||||||
|
// 更新 Redis 缓存
|
||||||
|
String redisKey = RedisKeyConstants.buildNoteCommentTotalKey(noteId);
|
||||||
|
boolean hasKey = redisTemplate.hasKey(redisKey);
|
||||||
|
|
||||||
|
if (hasKey) {
|
||||||
|
// 笔记评论总数 -1
|
||||||
|
redisTemplate.opsForHash().increment(redisKey, RedisKeyConstants.FIELD_COMMENT_TOTAL, -(count + 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新 t_note_count 计数表
|
||||||
|
noteCountDOMapper.updateCommentTotalByNoteId(noteId, -(count + 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 二级评论处理
|
||||||
|
*
|
||||||
|
* @param commentDO 评论
|
||||||
|
*/
|
||||||
|
private void handleTwoLevelComment(CommentDO commentDO) {
|
||||||
|
Long commentId = commentDO.getId();
|
||||||
|
|
||||||
|
// 1. 批量删除关联评论(递归查询回复评论,并批量删除)
|
||||||
|
List<Long> replyCommentIds = Lists.newArrayList();
|
||||||
|
recurrentGetReplyCommentId(replyCommentIds, commentId);
|
||||||
|
|
||||||
|
// 被删除的行数
|
||||||
|
int count = 0;
|
||||||
|
if (CollUtil.isNotEmpty(replyCommentIds)) {
|
||||||
|
count = commentDOMapper.deleteByIds(replyCommentIds);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 更新一级评论的计数
|
||||||
|
Long parentCommentId = commentDO.getParentId();
|
||||||
|
String redisKey = RedisKeyConstants.buildCountCommentKey(parentCommentId);
|
||||||
|
|
||||||
|
boolean hasKey = redisTemplate.hasKey(redisKey);
|
||||||
|
if (hasKey) {
|
||||||
|
redisTemplate.opsForHash().increment(redisKey, RedisKeyConstants.FIELD_CHILD_COMMENT_TOTAL, -(count + 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 若是最早的发布的二级评论被删除,需要更新一级评论的 first_reply_comment_id
|
||||||
|
|
||||||
|
// 查询一级评论
|
||||||
|
CommentDO oneLevelCommentDO = commentDOMapper.selectById(parentCommentId);
|
||||||
|
Long firstReplyCommentId = oneLevelCommentDO.getFirstReplyCommentId();
|
||||||
|
|
||||||
|
// 若删除的是最早回复的二级评论
|
||||||
|
if (Objects.equals(firstReplyCommentId, commentId)) {
|
||||||
|
// 查询数据库,重新获取一级评论最早回复的评论
|
||||||
|
CommentDO earliestCommentDO = commentDOMapper.selectEarliestByParentId(parentCommentId);
|
||||||
|
|
||||||
|
// 最早回复的那条评论 ID。若查询结果为 null, 则最早回复的评论 ID 为 null
|
||||||
|
Long earliestCommentId = Objects.nonNull(earliestCommentDO) ? earliestCommentDO.getId() : null;
|
||||||
|
// 更新其一级评论的 first_reply_comment_id
|
||||||
|
commentDOMapper.updateFirstReplyCommentIdByPrimaryKey(earliestCommentId, parentCommentId);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. 重新计算一级评论的热度值
|
||||||
|
Set<Long> commentIds = Sets.newHashSetWithExpectedSize(1);
|
||||||
|
commentIds.add(parentCommentId);
|
||||||
|
|
||||||
|
// 异步发送计数 MQ, 更新评论热度值
|
||||||
|
org.springframework.messaging.Message<String> message = MessageBuilder.withPayload(JsonUtils.toJsonString(commentIds))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
// 异步发送 MQ 消息
|
||||||
|
rocketMQTemplate.asyncSend(MQConstants.TOPIC_COMMENT_HEAT_UPDATE, message, new SendCallback() {
|
||||||
|
@Override
|
||||||
|
public void onSuccess(SendResult sendResult) {
|
||||||
|
log.info("==> 【评论热度值更新】MQ 发送成功,SendResult: {}", sendResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onException(Throwable throwable) {
|
||||||
|
log.error("==> 【评论热度值更新】MQ 发送异常: ", throwable);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 递归获取全部回复的评论 ID
|
||||||
|
*
|
||||||
|
* @param commentIds 评论 ID 列表
|
||||||
|
* @param commentId 评论 ID
|
||||||
|
*/
|
||||||
|
private void recurrentGetReplyCommentId(List<Long> commentIds, Long commentId) {
|
||||||
|
CommentDO replyCommentDO = commentDOMapper.selectByReplyCommentId(commentId);
|
||||||
|
|
||||||
|
if (Objects.isNull(replyCommentDO)) return;
|
||||||
|
|
||||||
|
commentIds.add(replyCommentDO.getId());
|
||||||
|
Long replyCommentId = replyCommentDO.getId();
|
||||||
|
// 递归调用
|
||||||
|
recurrentGetReplyCommentId(commentIds, replyCommentId);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
package com.hanserwei.hannote.comment.biz.consumer;
|
||||||
|
|
||||||
|
import com.hanserwei.hannote.comment.biz.constants.MQConstants;
|
||||||
|
import com.hanserwei.hannote.comment.biz.service.CommentService;
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.apache.rocketmq.spring.annotation.MessageModel;
|
||||||
|
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
|
||||||
|
import org.apache.rocketmq.spring.core.RocketMQListener;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
@Slf4j
|
||||||
|
@RocketMQMessageListener(consumerGroup = "han_note_group_" + MQConstants.TOPIC_DELETE_COMMENT_LOCAL_CACHE, // Group
|
||||||
|
topic = MQConstants.TOPIC_DELETE_COMMENT_LOCAL_CACHE, // 消费的主题 Topic
|
||||||
|
messageModel = MessageModel.BROADCASTING) // 广播模式
|
||||||
|
public class DeleteCommentLocalCacheConsumer implements RocketMQListener<String> {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private CommentService commentService;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onMessage(String body) {
|
||||||
|
Long commentId = Long.valueOf(body);
|
||||||
|
log.info("## 消费者消费成功, commentId: {}", commentId);
|
||||||
|
|
||||||
|
commentService.deleteCommentLocalCache(commentId);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,156 @@
|
|||||||
|
package com.hanserwei.hannote.comment.biz.consumer;
|
||||||
|
|
||||||
|
import cn.hutool.core.collection.CollUtil;
|
||||||
|
import com.google.common.collect.Lists;
|
||||||
|
import com.google.common.util.concurrent.RateLimiter;
|
||||||
|
import com.hanserwei.framework.common.utils.JsonUtils;
|
||||||
|
import com.hanserwei.hannote.comment.biz.constants.MQConstants;
|
||||||
|
import com.hanserwei.hannote.comment.biz.domain.mapper.CommentLikeDOMapper;
|
||||||
|
import com.hanserwei.hannote.comment.biz.enums.LikeUnlikeCommentTypeEnum;
|
||||||
|
import com.hanserwei.hannote.comment.biz.model.dto.LikeUnlikeCommentMqDTO;
|
||||||
|
import jakarta.annotation.PreDestroy;
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer;
|
||||||
|
import org.apache.rocketmq.client.consumer.listener.ConsumeOrderlyStatus;
|
||||||
|
import org.apache.rocketmq.client.consumer.listener.MessageListenerOrderly;
|
||||||
|
import org.apache.rocketmq.client.exception.MQClientException;
|
||||||
|
import org.apache.rocketmq.common.consumer.ConsumeFromWhere;
|
||||||
|
import org.apache.rocketmq.remoting.protocol.heartbeat.MessageModel;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
@SuppressWarnings("UnstableApiUsage")
|
||||||
|
@Component
|
||||||
|
@Slf4j
|
||||||
|
public class LikeUnlikeComment2DBConsumer {
|
||||||
|
|
||||||
|
// 每秒创建 5000 个令牌
|
||||||
|
private final RateLimiter rateLimiter = RateLimiter.create(5000);
|
||||||
|
@Value("${rocketmq.name-server}")
|
||||||
|
private String nameServer;
|
||||||
|
@Resource
|
||||||
|
private CommentLikeDOMapper commentLikeDOMapper;
|
||||||
|
private DefaultMQPushConsumer consumer;
|
||||||
|
|
||||||
|
@Bean(name = "LikeUnlikeComment2DBConsumer")
|
||||||
|
public DefaultMQPushConsumer mqPushConsumer() throws MQClientException {
|
||||||
|
// Group 组
|
||||||
|
String group = "han_note_group_" + MQConstants.TOPIC_COMMENT_LIKE_OR_UNLIKE;
|
||||||
|
|
||||||
|
// 创建一个新的 DefaultMQPushConsumer 实例,并指定消费者的消费组名
|
||||||
|
consumer = new DefaultMQPushConsumer(group);
|
||||||
|
|
||||||
|
// 设置 RocketMQ 的 NameServer 地址
|
||||||
|
consumer.setNamesrvAddr(nameServer);
|
||||||
|
|
||||||
|
// 订阅指定的主题,并设置主题的订阅规则("*" 表示订阅所有标签的消息)
|
||||||
|
consumer.subscribe(MQConstants.TOPIC_COMMENT_LIKE_OR_UNLIKE, "*");
|
||||||
|
|
||||||
|
// 设置消费者消费消息的起始位置,如果队列中没有消息,则从最新的消息开始消费。
|
||||||
|
consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_LAST_OFFSET);
|
||||||
|
|
||||||
|
// 设置消息消费模式,这里使用集群模式 (CLUSTERING)
|
||||||
|
consumer.setMessageModel(MessageModel.CLUSTERING);
|
||||||
|
|
||||||
|
// 最大重试次数, 以防消息重试过多次仍然没有成功,避免消息卡在消费队列中。
|
||||||
|
consumer.setMaxReconsumeTimes(3);
|
||||||
|
// 设置每批次消费的最大消息数量,这里设置为 30,表示每次拉取时最多消费 30 条消息。
|
||||||
|
consumer.setConsumeMessageBatchMaxSize(30);
|
||||||
|
|
||||||
|
// 注册消息监听器
|
||||||
|
consumer.registerMessageListener((MessageListenerOrderly) (msgs, context) -> {
|
||||||
|
log.info("==> 【评论点赞、取消点赞】本批次消息大小: {}", msgs.size());
|
||||||
|
try {
|
||||||
|
// 令牌桶流控, 以控制数据库能够承受的 QPS
|
||||||
|
rateLimiter.acquire();
|
||||||
|
|
||||||
|
// 将批次 Json 消息体转换 DTO 集合
|
||||||
|
List<LikeUnlikeCommentMqDTO> likeUnlikeCommentMqDTOS = Lists.newArrayList();
|
||||||
|
|
||||||
|
msgs.forEach(msg -> {
|
||||||
|
String tag = msg.getTags(); // Tag 标签
|
||||||
|
String msgJson = new String(msg.getBody()); // 消息体 Json 字符串
|
||||||
|
log.info("==> 【评论点赞、取消点赞】Consumer - Tag: {}, Received message: {}", tag, msgJson);
|
||||||
|
// Json 转 DTO
|
||||||
|
likeUnlikeCommentMqDTOS.add(JsonUtils.parseObject(msgJson, LikeUnlikeCommentMqDTO.class));
|
||||||
|
});
|
||||||
|
|
||||||
|
// 按评论 ID 分组
|
||||||
|
Map<Long, List<LikeUnlikeCommentMqDTO>> commentIdAndListMap = likeUnlikeCommentMqDTOS.stream()
|
||||||
|
.collect(Collectors.groupingBy(LikeUnlikeCommentMqDTO::getCommentId));
|
||||||
|
|
||||||
|
List<LikeUnlikeCommentMqDTO> finalLikeUnlikeCommentMqDTOS = Lists.newArrayList();
|
||||||
|
|
||||||
|
commentIdAndListMap.forEach((commentId, ops) -> {
|
||||||
|
// 优化:若某个用户对某评论,多次操作,如点赞 -> 取消点赞 -> 点赞,需进行操作合并,只提取最后一次操作,进一步降低操作数据库的频率
|
||||||
|
Map<Long, LikeUnlikeCommentMqDTO> userLastOp = ops.stream()
|
||||||
|
.collect(Collectors.toMap(
|
||||||
|
LikeUnlikeCommentMqDTO::getUserId, // 以发布评论的用户 ID 作为 Map 的键
|
||||||
|
Function.identity(), // 直接使用 DTO 对象本身作为 Map 的值
|
||||||
|
// 合并策略:当出现重复键(同一用户多次操作)时,保留时间更晚的记录
|
||||||
|
(oldValue, newValue) ->
|
||||||
|
oldValue.getCreateTime().isAfter(newValue.getCreateTime()) ? oldValue : newValue
|
||||||
|
));
|
||||||
|
|
||||||
|
|
||||||
|
finalLikeUnlikeCommentMqDTOS.addAll(userLastOp.values());
|
||||||
|
});
|
||||||
|
|
||||||
|
// 批量操作数据库
|
||||||
|
executeBatchSQL(finalLikeUnlikeCommentMqDTOS);
|
||||||
|
|
||||||
|
// 手动 ACK,告诉 RocketMQ 这批次消息消费成功
|
||||||
|
return ConsumeOrderlyStatus.SUCCESS;
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("", e);
|
||||||
|
// 这样 RocketMQ 会暂停当前队列的消费一段时间,再重试
|
||||||
|
return ConsumeOrderlyStatus.SUSPEND_CURRENT_QUEUE_A_MOMENT;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 启动消费者
|
||||||
|
consumer.start();
|
||||||
|
return consumer;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void executeBatchSQL(List<LikeUnlikeCommentMqDTO> values) {
|
||||||
|
// 过滤出点赞操作
|
||||||
|
List<LikeUnlikeCommentMqDTO> likes = values.stream()
|
||||||
|
.filter(op -> Objects.equals(op.getType(), LikeUnlikeCommentTypeEnum.LIKE.getCode()))
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
// 过滤出取消点赞操作
|
||||||
|
List<LikeUnlikeCommentMqDTO> unlikes = values.stream()
|
||||||
|
.filter(op -> Objects.equals(op.getType(), LikeUnlikeCommentTypeEnum.UNLIKE.getCode()))
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
// 取消点赞:批量删除
|
||||||
|
if (CollUtil.isNotEmpty(unlikes)) {
|
||||||
|
commentLikeDOMapper.batchDelete(unlikes);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 点赞:批量新增
|
||||||
|
if (CollUtil.isNotEmpty(likes)) {
|
||||||
|
commentLikeDOMapper.batchInsert(likes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreDestroy
|
||||||
|
public void destroy() {
|
||||||
|
if (Objects.nonNull(consumer)) {
|
||||||
|
try {
|
||||||
|
consumer.shutdown(); // 关闭消费者
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,164 @@
|
|||||||
|
package com.hanserwei.hannote.comment.biz.consumer;
|
||||||
|
|
||||||
|
import cn.hutool.core.collection.CollUtil;
|
||||||
|
import cn.hutool.core.util.RandomUtil;
|
||||||
|
import com.github.phantomthief.collection.BufferTrigger;
|
||||||
|
import com.google.common.collect.Lists;
|
||||||
|
import com.hanserwei.framework.common.utils.JsonUtils;
|
||||||
|
import com.hanserwei.hannote.comment.biz.constants.MQConstants;
|
||||||
|
import com.hanserwei.hannote.comment.biz.constants.RedisKeyConstants;
|
||||||
|
import com.hanserwei.hannote.comment.biz.domain.dataobject.CommentDO;
|
||||||
|
import com.hanserwei.hannote.comment.biz.domain.mapper.CommentDOMapper;
|
||||||
|
import com.hanserwei.hannote.comment.biz.enums.CommentLevelEnum;
|
||||||
|
import com.hanserwei.hannote.comment.biz.model.dto.CountPublishCommentMqDTO;
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
|
||||||
|
import org.apache.rocketmq.spring.core.RocketMQListener;
|
||||||
|
import org.springframework.data.redis.core.RedisCallback;
|
||||||
|
import org.springframework.data.redis.core.RedisTemplate;
|
||||||
|
import org.springframework.data.redis.core.ValueOperations;
|
||||||
|
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
@RocketMQMessageListener(consumerGroup = "han_note_group_first_reply_comment_id" + MQConstants.TOPIC_COUNT_NOTE_COMMENT, // Group 组
|
||||||
|
topic = MQConstants.TOPIC_COUNT_NOTE_COMMENT // 主题 Topic
|
||||||
|
)
|
||||||
|
@Slf4j
|
||||||
|
public class OneLevelCommentFirstReplyCommentIdUpdateConsumer implements RocketMQListener<String> {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private CommentDOMapper commentDOMapper;
|
||||||
|
@Resource(name = "taskExecutor")
|
||||||
|
private ThreadPoolTaskExecutor threadPoolTaskExecutor;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private RedisTemplate<String, Object> redisTemplate;
|
||||||
|
|
||||||
|
private final BufferTrigger<String> bufferTrigger = BufferTrigger.<String>batchBlocking()
|
||||||
|
.bufferSize(50000) // 缓存队列的最大容量
|
||||||
|
.batchSize(1000) // 一批次最多聚合 1000 条
|
||||||
|
.linger(Duration.ofSeconds(1)) // 多久聚合一次(1s 一次)
|
||||||
|
.setConsumerEx(this::consumeMessage) // 设置消费者方法
|
||||||
|
.build();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onMessage(String body) {
|
||||||
|
// 往 bufferTrigger 中添加元素
|
||||||
|
bufferTrigger.enqueue(body);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void consumeMessage(List<String> bodys) {
|
||||||
|
log.info("==> 【一级评论 first_reply_comment_id 更新】聚合消息, size: {}", bodys.size());
|
||||||
|
log.info("==> 【一级评论 first_reply_comment_id 更新】聚合消息, {}", JsonUtils.toJsonString(bodys));
|
||||||
|
|
||||||
|
// 将聚合后的消息体 Json 转 List<CountPublishCommentMqDTO>
|
||||||
|
List<CountPublishCommentMqDTO> publishCommentMqDTOS = Lists.newArrayList();
|
||||||
|
bodys.forEach(body -> {
|
||||||
|
try {
|
||||||
|
List<CountPublishCommentMqDTO> list = JsonUtils.parseList(body, CountPublishCommentMqDTO.class);
|
||||||
|
publishCommentMqDTOS.addAll(list);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("", e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 过滤出二级评论的 parent_id(即一级评论 ID),并去重,需要更新对应一级评论的 first_reply_comment_id
|
||||||
|
List<Long> parentIds = publishCommentMqDTOS.stream()
|
||||||
|
.filter(publishCommentMqDTO -> Objects.equals(publishCommentMqDTO.getLevel(), CommentLevelEnum.TWO.getCode()))
|
||||||
|
.map(CountPublishCommentMqDTO::getParentId)
|
||||||
|
.distinct() // 去重
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
if (CollUtil.isEmpty(parentIds)) return;
|
||||||
|
|
||||||
|
// 构建RedisKey
|
||||||
|
List<String> keys = parentIds.stream()
|
||||||
|
.map(RedisKeyConstants::buildHaveFirstReplyCommentKey)
|
||||||
|
.toList();
|
||||||
|
// 批量查询Redis
|
||||||
|
List<Object> values = redisTemplate.opsForValue().multiGet(keys);
|
||||||
|
|
||||||
|
// 提取Redis中不存在的评论ID
|
||||||
|
List<Long> missingCommentIds = Lists.newArrayList();
|
||||||
|
|
||||||
|
if (values != null) {
|
||||||
|
for (int i = 0; i < values.size(); i++) {
|
||||||
|
if (Objects.isNull(values.get(i))) {
|
||||||
|
missingCommentIds.add(parentIds.get(i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 存在的一级评论 ID,说明表中对应记录的 first_reply_comment_id 已经有值
|
||||||
|
if (CollUtil.isNotEmpty(missingCommentIds)) {
|
||||||
|
// 不存在的,则需要进一步查询数据库来确定,是否要更新记录对应的 first_reply_comment_id 值
|
||||||
|
// 批量去数据库中查询
|
||||||
|
List<CommentDO> commentDOS = commentDOMapper.selectByCommentIds(missingCommentIds);
|
||||||
|
|
||||||
|
// 异步将 first_reply_comment_id 不为 0 的一级评论 ID, 同步到 redis 中
|
||||||
|
threadPoolTaskExecutor.submit(() -> {
|
||||||
|
List<Long> needSyncCommentIds = commentDOS.stream()
|
||||||
|
.filter(commentDO -> commentDO.getFirstReplyCommentId() != 0)
|
||||||
|
.map(CommentDO::getId)
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
sync2Redis(needSyncCommentIds);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 过滤出值为 0 的,都需要更新其 first_reply_comment_id
|
||||||
|
List<CommentDO> needUpdateCommentDOS = commentDOS.stream()
|
||||||
|
.filter(commentDO -> commentDO.getFirstReplyCommentId() == 0)
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
needUpdateCommentDOS.forEach(needUpdateCommentDO -> {
|
||||||
|
// 一级评论 ID
|
||||||
|
Long needUpdateCommentId = needUpdateCommentDO.getId();
|
||||||
|
|
||||||
|
// 查询数据库,拿到一级评论最早回复的那条评论
|
||||||
|
CommentDO earliestCommentDO = commentDOMapper.selectEarliestByParentId(needUpdateCommentId);
|
||||||
|
|
||||||
|
if (Objects.nonNull(earliestCommentDO)) {
|
||||||
|
// 最早回复的那条评论 ID
|
||||||
|
Long earliestCommentId = earliestCommentDO.getId();
|
||||||
|
|
||||||
|
// 更新其一级评论的 first_reply_comment_id
|
||||||
|
commentDOMapper.updateFirstReplyCommentIdByPrimaryKey(earliestCommentId, needUpdateCommentId);
|
||||||
|
|
||||||
|
// 异步同步到 Redis
|
||||||
|
threadPoolTaskExecutor.submit(() -> sync2Redis(Lists.newArrayList(needUpdateCommentId)));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 异步将 first_reply_comment_id 不为 0 的一级评论 ID, 同步到 redis 中
|
||||||
|
*
|
||||||
|
* @param needSyncCommentIds 需要同步的评论 ID
|
||||||
|
*/
|
||||||
|
private void sync2Redis(List<Long> needSyncCommentIds) {
|
||||||
|
// 获取 ValueOperations
|
||||||
|
ValueOperations<String, Object> valueOperations = redisTemplate.opsForValue();
|
||||||
|
|
||||||
|
// 使用 RedisTemplate 的管道模式,允许在一个操作中批量发送多个命令,防止频繁操作 Redis
|
||||||
|
redisTemplate.executePipelined((RedisCallback<?>) (connection) -> {
|
||||||
|
needSyncCommentIds.forEach(needSyncCommentId -> {
|
||||||
|
// 构建 Redis Key
|
||||||
|
String key = RedisKeyConstants.buildHaveFirstReplyCommentKey(needSyncCommentId);
|
||||||
|
|
||||||
|
// 批量设置值并指定过期时间(5小时以内)
|
||||||
|
valueOperations.set(key, 1, RandomUtil.randomInt(5 * 60 * 60), TimeUnit.SECONDS);
|
||||||
|
});
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
package com.hanserwei.hannote.comment.biz.controller;
|
||||||
|
|
||||||
|
import com.hanserwei.framework.biz.operationlog.aspect.ApiOperationLog;
|
||||||
|
import com.hanserwei.framework.common.response.PageResponse;
|
||||||
|
import com.hanserwei.framework.common.response.Response;
|
||||||
|
import com.hanserwei.hannote.comment.biz.model.vo.*;
|
||||||
|
import com.hanserwei.hannote.comment.biz.service.CommentService;
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.validation.annotation.Validated;
|
||||||
|
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;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/comment")
|
||||||
|
@Slf4j
|
||||||
|
public class CommentController {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private CommentService commentService;
|
||||||
|
|
||||||
|
@PostMapping("/publish")
|
||||||
|
@ApiOperationLog(description = "发布评论")
|
||||||
|
public Response<?> publishComment(@Validated @RequestBody PublishCommentReqVO publishCommentReqVO) {
|
||||||
|
return commentService.publishComment(publishCommentReqVO);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/list")
|
||||||
|
@ApiOperationLog(description = "评论分页查询")
|
||||||
|
public PageResponse<FindCommentItemRspVO> findCommentPageList(@Validated @RequestBody FindCommentPageListReqVO findCommentPageListReqVO) {
|
||||||
|
return commentService.findCommentPageList(findCommentPageListReqVO);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/child/list")
|
||||||
|
@ApiOperationLog(description = "二级评论分页查询")
|
||||||
|
public PageResponse<FindChildCommentItemRspVO> findChildCommentPageList(@Validated @RequestBody FindChildCommentPageListReqVO findChildCommentPageListReqVO) {
|
||||||
|
return commentService.findChildCommentPageList(findChildCommentPageListReqVO);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/like")
|
||||||
|
@ApiOperationLog(description = "评论点赞")
|
||||||
|
public Response<?> likeComment(@Validated @RequestBody LikeCommentReqVO likeCommentReqVO) {
|
||||||
|
return commentService.likeComment(likeCommentReqVO);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/unlike")
|
||||||
|
@ApiOperationLog(description = "评论取消点赞")
|
||||||
|
public Response<?> unlikeComment(@Validated @RequestBody UnLikeCommentReqVO unLikeCommentReqVO) {
|
||||||
|
return commentService.unlikeComment(unLikeCommentReqVO);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/delete")
|
||||||
|
@ApiOperationLog(description = "删除评论")
|
||||||
|
public Response<?> deleteComment(@Validated @RequestBody DeleteCommentReqVO deleteCommentReqVO) {
|
||||||
|
return commentService.deleteComment(deleteCommentReqVO);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,130 @@
|
|||||||
|
package com.hanserwei.hannote.comment.biz.domain.dataobject;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.IdType;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableField;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 评论表
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
@TableName(value = "t_comment")
|
||||||
|
public class CommentDO {
|
||||||
|
/**
|
||||||
|
* id
|
||||||
|
*/
|
||||||
|
@TableId(value = "id", type = IdType.AUTO)
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 关联的笔记ID
|
||||||
|
*/
|
||||||
|
@TableField(value = "note_id")
|
||||||
|
private Long noteId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发布者用户ID
|
||||||
|
*/
|
||||||
|
@TableField(value = "user_id")
|
||||||
|
private Long userId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 评论内容UUID
|
||||||
|
*/
|
||||||
|
@TableField(value = "content_uuid")
|
||||||
|
private String contentUuid;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 内容是否为空(0:不为空 1:为空)
|
||||||
|
*/
|
||||||
|
@TableField(value = "is_content_empty")
|
||||||
|
private Boolean isContentEmpty;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 评论附加图片URL
|
||||||
|
*/
|
||||||
|
@TableField(value = "image_url")
|
||||||
|
private String imageUrl;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 级别(1:一级评论 2:二级评论)
|
||||||
|
*/
|
||||||
|
@TableField(value = "`level`")
|
||||||
|
private Integer level;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 评论被回复次数,仅一级评论需要
|
||||||
|
*/
|
||||||
|
@TableField(value = "reply_total")
|
||||||
|
private Long replyTotal;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 评论被点赞次数
|
||||||
|
*/
|
||||||
|
@TableField(value = "like_total")
|
||||||
|
private Long likeTotal;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 父ID (若是对笔记的评论,则此字段存储笔记ID; 若是二级评论,则此字段存储一级评论的ID)
|
||||||
|
*/
|
||||||
|
@TableField(value = "parent_id")
|
||||||
|
private Long parentId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 回复哪个的评论 (0表示是对笔记的评论,若是对他人评论的回复,则存储回复评论的ID)
|
||||||
|
*/
|
||||||
|
@TableField(value = "reply_comment_id")
|
||||||
|
private Long replyCommentId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 回复的哪个用户, 存储用户ID
|
||||||
|
*/
|
||||||
|
@TableField(value = "reply_user_id")
|
||||||
|
private Long replyUserId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否置顶(0:不置顶 1:置顶)
|
||||||
|
*/
|
||||||
|
@TableField(value = "is_top")
|
||||||
|
private Boolean isTop;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建时间
|
||||||
|
*/
|
||||||
|
@TableField(value = "create_time")
|
||||||
|
private LocalDateTime createTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新时间
|
||||||
|
*/
|
||||||
|
@TableField(value = "update_time")
|
||||||
|
private LocalDateTime updateTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 二级评论总数(只有一级评论才需要统计)
|
||||||
|
*/
|
||||||
|
@TableField(value = "child_comment_total")
|
||||||
|
private Long childCommentTotal;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 评论热度
|
||||||
|
*/
|
||||||
|
@TableField(value = "heat")
|
||||||
|
private Double heat;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 最早回复的评论ID (只有一级评论需要)
|
||||||
|
*/
|
||||||
|
@TableField(value = "first_reply_comment_id")
|
||||||
|
private Long firstReplyCommentId;
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
package com.hanserwei.hannote.comment.biz.domain.dataobject;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.IdType;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableField;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 评论点赞表
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
@TableName(value = "t_comment_like")
|
||||||
|
public class CommentLikeDO {
|
||||||
|
/**
|
||||||
|
* 主键ID
|
||||||
|
*/
|
||||||
|
@TableId(value = "id", type = IdType.AUTO)
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户ID
|
||||||
|
*/
|
||||||
|
@TableField(value = "user_id")
|
||||||
|
private Long userId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 评论ID
|
||||||
|
*/
|
||||||
|
@TableField(value = "comment_id")
|
||||||
|
private Long commentId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建时间
|
||||||
|
*/
|
||||||
|
@TableField(value = "create_time")
|
||||||
|
private LocalDateTime createTime;
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
package com.hanserwei.hannote.comment.biz.domain.dataobject;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.IdType;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableField;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 笔记计数表
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
@TableName(value = "t_note_count")
|
||||||
|
public class NoteCountDO {
|
||||||
|
/**
|
||||||
|
* 主键ID
|
||||||
|
*/
|
||||||
|
@TableId(value = "id", type = IdType.ASSIGN_ID)
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 笔记ID
|
||||||
|
*/
|
||||||
|
@TableField(value = "note_id")
|
||||||
|
private Long noteId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获得点赞总数
|
||||||
|
*/
|
||||||
|
@TableField(value = "like_total")
|
||||||
|
private Long likeTotal;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获得收藏总数
|
||||||
|
*/
|
||||||
|
@TableField(value = "collect_total")
|
||||||
|
private Long collectTotal;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 被评论总数
|
||||||
|
*/
|
||||||
|
@TableField(value = "comment_total")
|
||||||
|
private Long commentTotal;
|
||||||
|
}
|
||||||
@@ -0,0 +1,149 @@
|
|||||||
|
package com.hanserwei.hannote.comment.biz.domain.mapper;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
|
import com.hanserwei.hannote.comment.biz.domain.dataobject.CommentDO;
|
||||||
|
import com.hanserwei.hannote.comment.biz.model.bo.CommentBO;
|
||||||
|
import com.hanserwei.hannote.comment.biz.model.bo.CommentHeatBO;
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
import org.apache.ibatis.annotations.Param;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Mapper
|
||||||
|
public interface CommentDOMapper extends BaseMapper<CommentDO> {
|
||||||
|
/**
|
||||||
|
* 根据评论 ID 批量查询
|
||||||
|
*
|
||||||
|
* @param commentIds 评论 ID 列表
|
||||||
|
* @return 评论列表
|
||||||
|
*/
|
||||||
|
List<CommentDO> selectByCommentIds(@Param("commentIds") List<Long> commentIds);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量插入评论
|
||||||
|
*
|
||||||
|
* @param comments 评论列表
|
||||||
|
* @return 插入数量
|
||||||
|
*/
|
||||||
|
int batchInsert(@Param("comments") List<CommentBO> comments);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量更新热度值
|
||||||
|
*
|
||||||
|
* @param commentIds 评论 ID 列表
|
||||||
|
* @param commentHeatBOS 热度值列表
|
||||||
|
* @return 更新数量
|
||||||
|
*/
|
||||||
|
int batchUpdateHeatByCommentIds(@Param("commentIds") List<Long> commentIds,
|
||||||
|
@Param("commentHeatBOS") List<CommentHeatBO> commentHeatBOS);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询一级评论下最早回复的评论
|
||||||
|
*
|
||||||
|
* @param parentId 一级评论 ID
|
||||||
|
* @return 一级评论下最早回复的评论
|
||||||
|
*/
|
||||||
|
CommentDO selectEarliestByParentId(Long parentId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新一级评论的 first_reply_comment_id
|
||||||
|
*
|
||||||
|
* @param firstReplyCommentId 一级评论下最早回复的评论 ID
|
||||||
|
* @param id 一级评论 ID
|
||||||
|
* @return 更新数量
|
||||||
|
*/
|
||||||
|
int updateFirstReplyCommentIdByPrimaryKey(@Param("firstReplyCommentId") Long firstReplyCommentId,
|
||||||
|
@Param("id") Long id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询评论分页数据
|
||||||
|
*
|
||||||
|
* @param noteId 笔记 ID
|
||||||
|
* @param offset 偏移量
|
||||||
|
* @param pageSize 页大小
|
||||||
|
* @return 评论分页数据
|
||||||
|
*/
|
||||||
|
List<CommentDO> selectPageList(@Param("noteId") Long noteId,
|
||||||
|
@Param("offset") long offset,
|
||||||
|
@Param("pageSize") long pageSize);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量查询二级评论
|
||||||
|
*
|
||||||
|
* @param commentIds 评论 ID 列表
|
||||||
|
* @return 二级评论
|
||||||
|
*/
|
||||||
|
List<CommentDO> selectTwoLevelCommentByIds(@Param("commentIds") List<Long> commentIds);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询热门评论
|
||||||
|
*
|
||||||
|
* @param noteId 笔记 ID
|
||||||
|
* @return 热门评论
|
||||||
|
*/
|
||||||
|
List<CommentDO> selectHeatComments(Long noteId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询一级评论下子评论总数
|
||||||
|
*
|
||||||
|
* @param commentId 一级评论 ID
|
||||||
|
* @return 一级评论下子评论总数
|
||||||
|
*/
|
||||||
|
Long selectChildCommentTotalById(Long commentId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询二级评论分页数据
|
||||||
|
*
|
||||||
|
* @param parentId 一级评论 ID
|
||||||
|
* @param offset 偏移量
|
||||||
|
* @param pageSize 页大小
|
||||||
|
* @return 二级评论分页数据
|
||||||
|
*/
|
||||||
|
List<CommentDO> selectChildPageList(@Param("parentId") Long parentId,
|
||||||
|
@Param("offset") long offset,
|
||||||
|
@Param("pageSize") long pageSize);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量查询计数数据
|
||||||
|
*
|
||||||
|
* @param commentIds 评论 ID 列表
|
||||||
|
* @return 计数数据
|
||||||
|
*/
|
||||||
|
List<CommentDO> selectCommentCountByIds(@Param("commentIds") List<Long> commentIds);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询子评论
|
||||||
|
*
|
||||||
|
* @param parentId 一级评论 ID
|
||||||
|
* @param limit 子评论数量限制
|
||||||
|
* @return 子评论
|
||||||
|
*/
|
||||||
|
List<CommentDO> selectChildCommentsByParentIdAndLimit(@Param("parentId") Long parentId,
|
||||||
|
@Param("limit") int limit);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除一级评论下,所有二级评论
|
||||||
|
*
|
||||||
|
* @param commentId 一级评论 ID
|
||||||
|
* @return 删除数量
|
||||||
|
*/
|
||||||
|
int deleteByParentId(Long commentId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量删除评论
|
||||||
|
*
|
||||||
|
* @param commentIds 评论 ID 列表
|
||||||
|
* @return 删除数量
|
||||||
|
*/
|
||||||
|
int deleteByIds(@Param("commentIds") List<Long> commentIds);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据 reply_comment_id 查询
|
||||||
|
*
|
||||||
|
* @param commentId 回复的评论 ID
|
||||||
|
* @return 评论
|
||||||
|
*/
|
||||||
|
CommentDO selectByReplyCommentId(Long commentId);
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
package com.hanserwei.hannote.comment.biz.domain.mapper;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
|
import com.hanserwei.hannote.comment.biz.domain.dataobject.CommentLikeDO;
|
||||||
|
import com.hanserwei.hannote.comment.biz.model.dto.LikeUnlikeCommentMqDTO;
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
import org.apache.ibatis.annotations.Param;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Mapper
|
||||||
|
public interface CommentLikeDOMapper extends BaseMapper<CommentLikeDO> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询某个评论是否被点赞
|
||||||
|
*
|
||||||
|
* @param userId 用户 ID
|
||||||
|
* @param commentId 评论 ID
|
||||||
|
* @return 1 表示已点赞,0 表示未点赞
|
||||||
|
*/
|
||||||
|
int selectCountByUserIdAndCommentId(@Param("userId") Long userId,
|
||||||
|
@Param("commentId") Long commentId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询对应用户点赞的所有评论
|
||||||
|
*
|
||||||
|
* @param userId 用户 ID
|
||||||
|
* @return 评论点赞列表
|
||||||
|
*/
|
||||||
|
List<CommentLikeDO> selectByUserId(@Param("userId") Long userId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量删除点赞记录
|
||||||
|
*
|
||||||
|
* @param unlikes 删除点赞记录
|
||||||
|
* @return 删除数量
|
||||||
|
*/
|
||||||
|
int batchDelete(@Param("unlikes") List<LikeUnlikeCommentMqDTO> unlikes);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量添加点赞记录
|
||||||
|
*
|
||||||
|
* @param likes 添加点赞记录
|
||||||
|
* @return 添加数量
|
||||||
|
*/
|
||||||
|
int batchInsert(@Param("likes") List<LikeUnlikeCommentMqDTO> likes);
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
package com.hanserwei.hannote.comment.biz.domain.mapper;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
|
import com.hanserwei.hannote.comment.biz.domain.dataobject.NoteCountDO;
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
import org.apache.ibatis.annotations.Param;
|
||||||
|
|
||||||
|
@Mapper
|
||||||
|
public interface NoteCountDOMapper extends BaseMapper<NoteCountDO> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询笔记评论总数
|
||||||
|
*
|
||||||
|
* @param noteId 笔记ID
|
||||||
|
* @return 笔记评论总数
|
||||||
|
*/
|
||||||
|
Long selectCommentTotalByNoteId(Long noteId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新评论总数
|
||||||
|
*
|
||||||
|
* @param noteId 笔记 ID
|
||||||
|
* @param count 评论总数
|
||||||
|
* @return 更新数量
|
||||||
|
*/
|
||||||
|
int updateCommentTotalByNoteId(@Param("noteId") Long noteId,
|
||||||
|
@Param("count") int count);
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
package com.hanserwei.hannote.comment.biz.enums;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@AllArgsConstructor
|
||||||
|
public enum CommentLevelEnum {
|
||||||
|
// 一级评论
|
||||||
|
ONE(1),
|
||||||
|
// 二级评论
|
||||||
|
TWO(2),
|
||||||
|
;
|
||||||
|
|
||||||
|
private final Integer code;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据类型 code 获取对应的枚举
|
||||||
|
*
|
||||||
|
* @param code 类型 code
|
||||||
|
* @return 枚举
|
||||||
|
*/
|
||||||
|
public static CommentLevelEnum valueOf(Integer code) {
|
||||||
|
for (CommentLevelEnum commentLevelEnum : CommentLevelEnum.values()) {
|
||||||
|
if (Objects.equals(code, commentLevelEnum.getCode())) {
|
||||||
|
return commentLevelEnum;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
package com.hanserwei.hannote.comment.biz.enums;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@AllArgsConstructor
|
||||||
|
public enum CommentLikeLuaResultEnum {
|
||||||
|
// 布隆过滤器不存在
|
||||||
|
NOT_EXIST(-1L),
|
||||||
|
// 评论已点赞
|
||||||
|
COMMENT_LIKED(1L),
|
||||||
|
// 评论点赞成功
|
||||||
|
COMMENT_LIKE_SUCCESS(0L),
|
||||||
|
;
|
||||||
|
|
||||||
|
private final Long code;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据类型 code 获取对应的枚举
|
||||||
|
*
|
||||||
|
* @param code 类型 code
|
||||||
|
* @return 枚举
|
||||||
|
*/
|
||||||
|
public static CommentLikeLuaResultEnum valueOf(Long code) {
|
||||||
|
for (CommentLikeLuaResultEnum commentLikeLuaResultEnum : CommentLikeLuaResultEnum.values()) {
|
||||||
|
if (Objects.equals(code, commentLikeLuaResultEnum.getCode())) {
|
||||||
|
return commentLikeLuaResultEnum;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
package com.hanserwei.hannote.comment.biz.enums;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@AllArgsConstructor
|
||||||
|
public enum CommentUnlikeLuaResultEnum {
|
||||||
|
// 布隆过滤器不存在
|
||||||
|
NOT_EXIST(-1L),
|
||||||
|
// 评论已点赞
|
||||||
|
COMMENT_LIKED(1L),
|
||||||
|
// 评论未点赞
|
||||||
|
COMMENT_NOT_LIKED(0L),
|
||||||
|
;
|
||||||
|
|
||||||
|
private final Long code;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据类型 code 获取对应的枚举
|
||||||
|
*
|
||||||
|
* @param code 类型 code
|
||||||
|
* @return 枚举
|
||||||
|
*/
|
||||||
|
public static CommentUnlikeLuaResultEnum valueOf(Long code) {
|
||||||
|
for (CommentUnlikeLuaResultEnum commentUnlikeLuaResultEnum : CommentUnlikeLuaResultEnum.values()) {
|
||||||
|
if (Objects.equals(code, commentUnlikeLuaResultEnum.getCode())) {
|
||||||
|
return commentUnlikeLuaResultEnum;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package com.hanserwei.hannote.comment.biz.enums;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@AllArgsConstructor
|
||||||
|
public enum LikeUnlikeCommentTypeEnum {
|
||||||
|
// 点赞
|
||||||
|
LIKE(1),
|
||||||
|
// 取消点赞
|
||||||
|
UNLIKE(0),
|
||||||
|
;
|
||||||
|
|
||||||
|
private final Integer code;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
package com.hanserwei.hannote.comment.biz.enums;
|
||||||
|
|
||||||
|
import com.hanserwei.framework.common.exception.BaseExceptionInterface;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@AllArgsConstructor
|
||||||
|
public enum ResponseCodeEnum implements BaseExceptionInterface {
|
||||||
|
|
||||||
|
// ----------- 通用异常状态码 -----------
|
||||||
|
SYSTEM_ERROR("COMMENT-10000", "出错啦,后台小哥正在努力修复中..."),
|
||||||
|
PARAM_NOT_VALID("COMMENT-10001", "参数错误"),
|
||||||
|
|
||||||
|
// ----------- 业务异常状态码 -----------
|
||||||
|
COMMENT_NOT_FOUND("COMMENT-20001", "此评论不存在"),
|
||||||
|
PARENT_COMMENT_NOT_FOUND("COMMENT-20000", "此父评论不存在"),
|
||||||
|
COMMENT_ALREADY_LIKED("COMMENT-20002", "您已经点赞过该评论"),
|
||||||
|
COMMENT_NOT_LIKED("COMMENT-20003", "您未点赞该评论,无法取消点赞"),
|
||||||
|
COMMENT_CANT_OPERATE("COMMENT-20004", "您无法操作该评论"),
|
||||||
|
;
|
||||||
|
|
||||||
|
// 异常码
|
||||||
|
private final String errorCode;
|
||||||
|
// 错误信息
|
||||||
|
private final String errorMsg;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,103 @@
|
|||||||
|
package com.hanserwei.hannote.comment.biz.exception;
|
||||||
|
|
||||||
|
import com.hanserwei.framework.common.exception.ApiException;
|
||||||
|
import com.hanserwei.framework.common.response.Response;
|
||||||
|
import com.hanserwei.hannote.comment.biz.enums.ResponseCodeEnum;
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.validation.BindingResult;
|
||||||
|
import org.springframework.web.bind.MethodArgumentNotValidException;
|
||||||
|
import org.springframework.web.bind.annotation.ControllerAdvice;
|
||||||
|
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||||
|
import org.springframework.web.bind.annotation.ResponseBody;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
@SuppressWarnings("LoggingSimilarMessage")
|
||||||
|
@ControllerAdvice
|
||||||
|
@Slf4j
|
||||||
|
public class GlobalExceptionHandler {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 捕获自定义业务异常
|
||||||
|
*
|
||||||
|
* @return Response.fail(e)
|
||||||
|
*/
|
||||||
|
@ExceptionHandler({ApiException.class})
|
||||||
|
@ResponseBody
|
||||||
|
public Response<Object> handleApiException(HttpServletRequest request, ApiException e) {
|
||||||
|
log.warn("{} request fail, errorCode: {}, errorMessage: {}", request.getRequestURI(), e.getErrorCode(), e.getErrorMsg());
|
||||||
|
return Response.fail(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 捕获参数校验异常
|
||||||
|
*
|
||||||
|
* @return Response.fail(errorCode, errorMessage)
|
||||||
|
*/
|
||||||
|
@ExceptionHandler({MethodArgumentNotValidException.class})
|
||||||
|
@ResponseBody
|
||||||
|
public Response<Object> handleMethodArgumentNotValidException(HttpServletRequest request, MethodArgumentNotValidException e) {
|
||||||
|
// 参数错误异常码
|
||||||
|
String errorCode = ResponseCodeEnum.PARAM_NOT_VALID.getErrorCode();
|
||||||
|
|
||||||
|
// 获取 BindingResult
|
||||||
|
BindingResult bindingResult = e.getBindingResult();
|
||||||
|
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
|
||||||
|
// 获取校验不通过的字段,并组合错误信息,格式为: email 邮箱格式不正确, 当前值: '123124qq.com';
|
||||||
|
Optional.of(bindingResult.getFieldErrors()).ifPresent(errors -> {
|
||||||
|
errors.forEach(error ->
|
||||||
|
sb.append(error.getField())
|
||||||
|
.append(" ")
|
||||||
|
.append(error.getDefaultMessage())
|
||||||
|
.append(", 当前值: '")
|
||||||
|
.append(error.getRejectedValue())
|
||||||
|
.append("'; ")
|
||||||
|
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 错误信息
|
||||||
|
String errorMessage = sb.toString();
|
||||||
|
|
||||||
|
log.warn("{} request error, errorCode: {}, errorMessage: {}", request.getRequestURI(), errorCode, errorMessage);
|
||||||
|
|
||||||
|
return Response.fail(errorCode, errorMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 捕获 guava 参数校验异常
|
||||||
|
*
|
||||||
|
* @return Response.fail(ResponseCodeEnum.PARAM_NOT_VALID)
|
||||||
|
*/
|
||||||
|
@ExceptionHandler({IllegalArgumentException.class})
|
||||||
|
@ResponseBody
|
||||||
|
public Response<Object> handleIllegalArgumentException(HttpServletRequest request, IllegalArgumentException e) {
|
||||||
|
// 参数错误异常码
|
||||||
|
String errorCode = ResponseCodeEnum.PARAM_NOT_VALID.getErrorCode();
|
||||||
|
|
||||||
|
// 错误信息
|
||||||
|
String errorMessage = e.getMessage();
|
||||||
|
|
||||||
|
log.warn("{} request error, errorCode: {}, errorMessage: {}", request.getRequestURI(), errorCode, errorMessage);
|
||||||
|
|
||||||
|
return Response.fail(errorCode, errorMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 其他类型异常
|
||||||
|
*
|
||||||
|
* @param request 请求
|
||||||
|
* @param e 异常
|
||||||
|
* @return Response.fail(ResponseCodeEnum.SYSTEM_ERROR)
|
||||||
|
*/
|
||||||
|
@ExceptionHandler({Exception.class})
|
||||||
|
@ResponseBody
|
||||||
|
public Response<Object> handleOtherException(HttpServletRequest request, Exception e) {
|
||||||
|
log.error("{} request error, ", request.getRequestURI(), e);
|
||||||
|
return Response.fail(ResponseCodeEnum.SYSTEM_ERROR);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
package com.hanserwei.hannote.comment.biz.model.bo;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
@Builder
|
||||||
|
public class CommentBO {
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
private Long noteId;
|
||||||
|
|
||||||
|
private Long userId;
|
||||||
|
|
||||||
|
private String contentUuid;
|
||||||
|
|
||||||
|
private String content;
|
||||||
|
|
||||||
|
private Boolean isContentEmpty;
|
||||||
|
|
||||||
|
private String imageUrl;
|
||||||
|
|
||||||
|
private Integer level;
|
||||||
|
|
||||||
|
private Long replyTotal;
|
||||||
|
|
||||||
|
private Long likeTotal;
|
||||||
|
|
||||||
|
private Long parentId;
|
||||||
|
|
||||||
|
private Long replyCommentId;
|
||||||
|
|
||||||
|
private Long replyUserId;
|
||||||
|
|
||||||
|
private Boolean isTop;
|
||||||
|
|
||||||
|
private LocalDateTime createTime;
|
||||||
|
|
||||||
|
private LocalDateTime updateTime;
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
package com.hanserwei.hannote.comment.biz.model.bo;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
@Builder
|
||||||
|
public class CommentHeatBO {
|
||||||
|
/**
|
||||||
|
* 评论 ID
|
||||||
|
*/
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 热度值
|
||||||
|
*/
|
||||||
|
private Double heat;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 笔记 ID
|
||||||
|
*/
|
||||||
|
private Long noteId;
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
package com.hanserwei.hannote.comment.biz.model.dto;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
@Builder
|
||||||
|
public class CountPublishCommentMqDTO {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 笔记 ID
|
||||||
|
*/
|
||||||
|
private Long noteId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 评论 ID
|
||||||
|
*/
|
||||||
|
private Long commentId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 评论级别
|
||||||
|
*/
|
||||||
|
private Integer level;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 父 ID
|
||||||
|
*/
|
||||||
|
private Long parentId;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
package com.hanserwei.hannote.comment.biz.model.dto;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
@Builder
|
||||||
|
public class LikeUnlikeCommentMqDTO {
|
||||||
|
|
||||||
|
private Long userId;
|
||||||
|
|
||||||
|
private Long commentId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 0: 取消点赞, 1:点赞
|
||||||
|
*/
|
||||||
|
private Integer type;
|
||||||
|
|
||||||
|
private LocalDateTime createTime;
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
package com.hanserwei.hannote.comment.biz.model.dto;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
@Builder
|
||||||
|
public class PublishCommentMqDTO {
|
||||||
|
|
||||||
|
private Long noteId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 评论内容
|
||||||
|
*/
|
||||||
|
private String content;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 评论图片链接
|
||||||
|
*/
|
||||||
|
private String imageUrl;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 回复的哪个评论(评论 ID)
|
||||||
|
*/
|
||||||
|
private Long replyCommentId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发布时间
|
||||||
|
*/
|
||||||
|
private LocalDateTime createTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发布者 ID
|
||||||
|
*/
|
||||||
|
private Long creatorId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 评论 ID
|
||||||
|
*/
|
||||||
|
private Long commentId;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
package com.hanserwei.hannote.comment.biz.model.vo;
|
||||||
|
|
||||||
|
import jakarta.validation.constraints.NotNull;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
@Builder
|
||||||
|
public class DeleteCommentReqVO {
|
||||||
|
|
||||||
|
@NotNull(message = "评论 ID 不能为空")
|
||||||
|
private Long commentId;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
package com.hanserwei.hannote.comment.biz.model.vo;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
@Builder
|
||||||
|
public class FindChildCommentItemRspVO {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 评论 ID
|
||||||
|
*/
|
||||||
|
private Long commentId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发布者用户 ID
|
||||||
|
*/
|
||||||
|
private Long userId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 头像
|
||||||
|
*/
|
||||||
|
private String avatar;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 昵称
|
||||||
|
*/
|
||||||
|
private String nickname;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 评论内容
|
||||||
|
*/
|
||||||
|
private String content;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 评论内容
|
||||||
|
*/
|
||||||
|
private String imageUrl;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发布时间
|
||||||
|
*/
|
||||||
|
private String createTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 被点赞数
|
||||||
|
*/
|
||||||
|
private Long likeTotal;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 回复的用户昵称
|
||||||
|
*/
|
||||||
|
private String replyUserName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 回复的用户 ID
|
||||||
|
*/
|
||||||
|
private Long replyUserId;
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package com.hanserwei.hannote.comment.biz.model.vo;
|
||||||
|
|
||||||
|
import jakarta.validation.constraints.NotNull;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
@Builder
|
||||||
|
public class FindChildCommentPageListReqVO {
|
||||||
|
|
||||||
|
@NotNull(message = "父评论 ID 不能为空")
|
||||||
|
private Long parentCommentId;
|
||||||
|
|
||||||
|
@NotNull(message = "页码不能为空")
|
||||||
|
private Integer pageNo = 1;
|
||||||
|
}
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
package com.hanserwei.hannote.comment.biz.model.vo;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
@Builder
|
||||||
|
public class FindCommentItemRspVO {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 评论 ID
|
||||||
|
*/
|
||||||
|
private Long commentId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发布者用户 ID
|
||||||
|
*/
|
||||||
|
private Long userId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 头像
|
||||||
|
*/
|
||||||
|
private String avatar;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 昵称
|
||||||
|
*/
|
||||||
|
private String nickname;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 评论内容
|
||||||
|
*/
|
||||||
|
private String content;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 评论内容
|
||||||
|
*/
|
||||||
|
private String imageUrl;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发布时间
|
||||||
|
*/
|
||||||
|
private String createTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 被点赞数
|
||||||
|
*/
|
||||||
|
private Long likeTotal;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 二级评论总数
|
||||||
|
*/
|
||||||
|
private Long childCommentTotal;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 最早回复的评论
|
||||||
|
*/
|
||||||
|
private FindCommentItemRspVO firstReplyComment;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 热度值
|
||||||
|
*/
|
||||||
|
private Double heat;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package com.hanserwei.hannote.comment.biz.model.vo;
|
||||||
|
|
||||||
|
import jakarta.validation.constraints.NotNull;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
@Builder
|
||||||
|
public class FindCommentPageListReqVO {
|
||||||
|
|
||||||
|
@NotNull(message = "笔记 ID 不能为空")
|
||||||
|
private Long noteId;
|
||||||
|
|
||||||
|
@NotNull(message = "页码不能为空")
|
||||||
|
private Integer pageNo = 1;
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
package com.hanserwei.hannote.comment.biz.model.vo;
|
||||||
|
|
||||||
|
import jakarta.validation.constraints.NotNull;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
@Builder
|
||||||
|
public class LikeCommentReqVO {
|
||||||
|
|
||||||
|
@NotNull(message = "评论 ID 不能为空")
|
||||||
|
private Long commentId;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
package com.hanserwei.hannote.comment.biz.model.vo;
|
||||||
|
|
||||||
|
import jakarta.validation.constraints.NotNull;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
@Builder
|
||||||
|
public class PublishCommentReqVO {
|
||||||
|
|
||||||
|
@NotNull(message = "笔记 ID 不能为空")
|
||||||
|
private Long noteId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 评论内容
|
||||||
|
*/
|
||||||
|
private String content;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 评论图片链接
|
||||||
|
*/
|
||||||
|
private String imageUrl;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 回复的哪个评论(评论 ID)
|
||||||
|
*/
|
||||||
|
private Long replyCommentId;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
package com.hanserwei.hannote.comment.biz.model.vo;
|
||||||
|
|
||||||
|
import jakarta.validation.constraints.NotNull;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
@Builder
|
||||||
|
public class UnLikeCommentReqVO {
|
||||||
|
|
||||||
|
@NotNull(message = "评论 ID 不能为空")
|
||||||
|
private Long commentId;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
package com.hanserwei.hannote.comment.biz.retry;
|
||||||
|
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.apache.rocketmq.client.producer.SendCallback;
|
||||||
|
import org.apache.rocketmq.client.producer.SendResult;
|
||||||
|
import org.apache.rocketmq.spring.core.RocketMQTemplate;
|
||||||
|
import org.springframework.messaging.Message;
|
||||||
|
import org.springframework.messaging.support.MessageBuilder;
|
||||||
|
import org.springframework.retry.RetryCallback;
|
||||||
|
import org.springframework.retry.support.RetryTemplate;
|
||||||
|
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
@Slf4j
|
||||||
|
public class SendMqRetryHelper {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private RocketMQTemplate rocketMQTemplate;
|
||||||
|
@Resource
|
||||||
|
private RetryTemplate retryTemplate;
|
||||||
|
@Resource(name = "taskExecutor")
|
||||||
|
private ThreadPoolTaskExecutor threadPoolTaskExecutor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 异步发送 MQ
|
||||||
|
*
|
||||||
|
* @param topic MQ topic
|
||||||
|
*/
|
||||||
|
public void asyncSend(String topic, String body) {
|
||||||
|
log.info("==> 开始异步发送 MQ, Topic: {}, Body: {}", topic, body);
|
||||||
|
|
||||||
|
// 构建消息对象,并将 DTO 转成 Json 字符串设置到消息体中
|
||||||
|
Message<String> message = MessageBuilder.withPayload(body)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
// 异步发送 MQ 消息,提升接口响应速度
|
||||||
|
rocketMQTemplate.asyncSend(topic, message, new SendCallback() {
|
||||||
|
@Override
|
||||||
|
public void onSuccess(SendResult sendResult) {
|
||||||
|
log.info("==> 【评论发布】MQ 发送成功,SendResult: {}", sendResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onException(Throwable throwable) {
|
||||||
|
log.error("==> 【评论发布】MQ 发送异常: ", throwable);
|
||||||
|
handleRetry(topic, message);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 重试处理
|
||||||
|
*
|
||||||
|
* @param topic MQ topic
|
||||||
|
* @param message 消息对象
|
||||||
|
*/
|
||||||
|
private void handleRetry(String topic, Message<String> message) {
|
||||||
|
// 异步处理
|
||||||
|
threadPoolTaskExecutor.submit(() -> {
|
||||||
|
try {
|
||||||
|
// 通过 retryTemplate 执行重试
|
||||||
|
retryTemplate.execute((RetryCallback<Void, RuntimeException>) context -> {
|
||||||
|
log.info("==> 开始重试 MQ 发送, 当前重试次数: {}, 时间: {}", context.getRetryCount() + 1, LocalDateTime.now());
|
||||||
|
// 同步发送 MQ
|
||||||
|
rocketMQTemplate.syncSend(topic, message);
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
} catch (Exception e) {
|
||||||
|
// 多次重试失败,进入兜底方案
|
||||||
|
fallback(e, topic, message.getPayload());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 兜底方案: 将发送失败的 MQ 写入数据库,之后,通过定时任务扫表,将发送失败的 MQ 再次发送,最终发送成功后,将该记录物理删除
|
||||||
|
*/
|
||||||
|
private void fallback(Exception e, String topic, String bodyJson) {
|
||||||
|
log.error("==> 多次发送失败, 进入兜底方案, Topic: {}, bodyJson: {}", topic, bodyJson);
|
||||||
|
|
||||||
|
// TODO:
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
package com.hanserwei.hannote.comment.biz.rpc;
|
||||||
|
|
||||||
|
import com.hanserwei.hannote.distributed.id.generator.api.DistributedIdGeneratorFeignApi;
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class DistributedIdGeneratorRpcService {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private DistributedIdGeneratorFeignApi distributedIdGeneratorFeignApi;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成评论 ID
|
||||||
|
*
|
||||||
|
* @return 评论 ID
|
||||||
|
*/
|
||||||
|
public String generateCommentId() {
|
||||||
|
return distributedIdGeneratorFeignApi.getSegmentId("leaf-segment-comment-id");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,107 @@
|
|||||||
|
package com.hanserwei.hannote.comment.biz.rpc;
|
||||||
|
|
||||||
|
import cn.hutool.core.collection.CollUtil;
|
||||||
|
import com.google.common.collect.Lists;
|
||||||
|
import com.hanserwei.framework.common.constant.DateConstants;
|
||||||
|
import com.hanserwei.framework.common.response.Response;
|
||||||
|
import com.hanserwei.hannote.comment.biz.model.bo.CommentBO;
|
||||||
|
import com.hanserwei.hannote.kv.api.KeyValueFeignApi;
|
||||||
|
import com.hanserwei.hannote.kv.dto.req.*;
|
||||||
|
import com.hanserwei.hannote.kv.dto.resp.FindCommentContentRspDTO;
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class KeyValueRpcService {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private KeyValueFeignApi keyValueFeignApi;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量存储评论内容
|
||||||
|
*
|
||||||
|
* @param commentBOS 评论 BO
|
||||||
|
* @return 批量保存结果
|
||||||
|
*/
|
||||||
|
public boolean batchSaveCommentContent(List<CommentBO> commentBOS) {
|
||||||
|
List<CommentContentReqDTO> comments = Lists.newArrayList();
|
||||||
|
|
||||||
|
// BO 转 DTO
|
||||||
|
commentBOS.forEach(commentBO -> {
|
||||||
|
CommentContentReqDTO commentContentReqDTO = CommentContentReqDTO.builder()
|
||||||
|
.noteId(commentBO.getNoteId())
|
||||||
|
.content(commentBO.getContent())
|
||||||
|
.contentId(commentBO.getContentUuid())
|
||||||
|
.yearMonth(commentBO.getCreateTime().format(DateConstants.DATE_FORMAT_Y_M))
|
||||||
|
.build();
|
||||||
|
comments.add(commentContentReqDTO);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 构建接口入参实体类
|
||||||
|
BatchAddCommentContentReqDTO batchAddCommentContentReqDTO = BatchAddCommentContentReqDTO.builder()
|
||||||
|
.comments(comments)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
// 调用 KV 存储服务
|
||||||
|
Response<?> response = keyValueFeignApi.batchAddCommentContent(batchAddCommentContentReqDTO);
|
||||||
|
|
||||||
|
// 若返参中 success 为 false, 则主动抛出异常,以便调用层回滚事务
|
||||||
|
if (!response.isSuccess()) {
|
||||||
|
throw new RuntimeException("批量保存评论内容失败");
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量查询评论内容
|
||||||
|
*
|
||||||
|
* @param noteId 笔记ID
|
||||||
|
* @param findCommentContentReqDTOS 查询参数
|
||||||
|
* @return 批量查询结果
|
||||||
|
*/
|
||||||
|
public List<FindCommentContentRspDTO> batchFindCommentContent(Long noteId, List<FindCommentContentReqDTO> findCommentContentReqDTOS) {
|
||||||
|
BatchFindCommentContentReqDTO bathFindCommentContentReqDTO = BatchFindCommentContentReqDTO.builder()
|
||||||
|
.noteId(noteId)
|
||||||
|
.commentContentKeys(findCommentContentReqDTOS)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
Response<List<FindCommentContentRspDTO>> response = keyValueFeignApi.batchFindCommentContent(bathFindCommentContentReqDTO);
|
||||||
|
|
||||||
|
if (!response.isSuccess() || Objects.isNull(response.getData()) || CollUtil.isEmpty(response.getData())) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.getData();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除评论内容
|
||||||
|
*
|
||||||
|
* @param noteId 笔记ID
|
||||||
|
* @param createTime 创建时间
|
||||||
|
* @param contentId 评论内容ID
|
||||||
|
* @return 是否成功
|
||||||
|
*/
|
||||||
|
public boolean deleteCommentContent(Long noteId, LocalDateTime createTime, String contentId) {
|
||||||
|
DeleteCommentContentReqDTO deleteCommentContentReqDTO = DeleteCommentContentReqDTO.builder()
|
||||||
|
.noteId(noteId)
|
||||||
|
.yearMonth(DateConstants.DATE_FORMAT_Y_M.format(createTime))
|
||||||
|
.contentId(contentId)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
// 调用 KV 存储服务
|
||||||
|
Response<?> response = keyValueFeignApi.deleteCommentContent(deleteCommentContentReqDTO);
|
||||||
|
|
||||||
|
if (!response.isSuccess()) {
|
||||||
|
throw new RuntimeException("删除评论内容失败");
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
package com.hanserwei.hannote.comment.biz.rpc;
|
||||||
|
|
||||||
|
import cn.hutool.core.collection.CollUtil;
|
||||||
|
import com.hanserwei.framework.common.response.Response;
|
||||||
|
import com.hanserwei.hannote.user.api.UserFeignApi;
|
||||||
|
import com.hanserwei.hannote.user.dto.req.FindUsersByIdsReqDTO;
|
||||||
|
import com.hanserwei.hannote.user.dto.resp.FindUserByIdRspDTO;
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class UserRpcService {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private UserFeignApi userFeignApi;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量查询用户信息
|
||||||
|
*
|
||||||
|
* @param userIds 用户 ID集合
|
||||||
|
* @return 用户信息集合
|
||||||
|
*/
|
||||||
|
public List<FindUserByIdRspDTO> findByIds(List<Long> userIds) {
|
||||||
|
if (CollUtil.isEmpty(userIds)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
FindUsersByIdsReqDTO findUsersByIdsReqDTO = new FindUsersByIdsReqDTO();
|
||||||
|
// 去重, 并设置用户 ID 集合
|
||||||
|
findUsersByIdsReqDTO.setIds(userIds.stream().distinct().collect(Collectors.toList()));
|
||||||
|
|
||||||
|
Response<List<FindUserByIdRspDTO>> response = userFeignApi.findByIds(findUsersByIdsReqDTO);
|
||||||
|
|
||||||
|
if (!response.isSuccess() || Objects.isNull(response.getData()) || CollUtil.isEmpty(response.getData())) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.getData();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package com.hanserwei.hannote.comment.biz.service;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.extension.service.IService;
|
||||||
|
import com.hanserwei.hannote.comment.biz.domain.dataobject.CommentLikeDO;
|
||||||
|
|
||||||
|
public interface CommentLikeService extends IService<CommentLikeDO> {
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
package com.hanserwei.hannote.comment.biz.service;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.extension.service.IService;
|
||||||
|
import com.hanserwei.framework.common.response.PageResponse;
|
||||||
|
import com.hanserwei.framework.common.response.Response;
|
||||||
|
import com.hanserwei.hannote.comment.biz.domain.dataobject.CommentDO;
|
||||||
|
import com.hanserwei.hannote.comment.biz.model.vo.*;
|
||||||
|
|
||||||
|
public interface CommentService extends IService<CommentDO> {
|
||||||
|
/**
|
||||||
|
* 发布评论
|
||||||
|
*
|
||||||
|
* @param publishCommentReqVO 发布评论请求
|
||||||
|
* @return 响应
|
||||||
|
*/
|
||||||
|
Response<?> publishComment(PublishCommentReqVO publishCommentReqVO);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 评论列表分页查询
|
||||||
|
*
|
||||||
|
* @param findCommentPageListReqVO 评论列表分页查询参数
|
||||||
|
* @return 响应
|
||||||
|
*/
|
||||||
|
PageResponse<FindCommentItemRspVO> findCommentPageList(FindCommentPageListReqVO findCommentPageListReqVO);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 二级评论分页查询
|
||||||
|
*
|
||||||
|
* @param findChildCommentPageListReqVO 二级评论分页查询参数
|
||||||
|
* @return 响应
|
||||||
|
*/
|
||||||
|
PageResponse<FindChildCommentItemRspVO> findChildCommentPageList(FindChildCommentPageListReqVO findChildCommentPageListReqVO);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 评论点赞
|
||||||
|
*
|
||||||
|
* @param likeCommentReqVO 评论点赞请求
|
||||||
|
* @return 响应
|
||||||
|
*/
|
||||||
|
Response<?> likeComment(LikeCommentReqVO likeCommentReqVO);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 取消评论点赞
|
||||||
|
*
|
||||||
|
* @param unLikeCommentReqVO 取消评论点赞请求
|
||||||
|
* @return 响应
|
||||||
|
*/
|
||||||
|
Response<?> unlikeComment(UnLikeCommentReqVO unLikeCommentReqVO);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除评论
|
||||||
|
*
|
||||||
|
* @param deleteCommentReqVO 删除评论请求
|
||||||
|
* @return 响应
|
||||||
|
*/
|
||||||
|
Response<?> deleteComment(DeleteCommentReqVO deleteCommentReqVO);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除本地评论缓存
|
||||||
|
*
|
||||||
|
* @param commentId 评论ID
|
||||||
|
*/
|
||||||
|
void deleteCommentLocalCache(Long commentId);
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package com.hanserwei.hannote.comment.biz.service.impl;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||||
|
import com.hanserwei.hannote.comment.biz.domain.dataobject.CommentLikeDO;
|
||||||
|
import com.hanserwei.hannote.comment.biz.domain.mapper.CommentLikeDOMapper;
|
||||||
|
import com.hanserwei.hannote.comment.biz.service.CommentLikeService;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class CommentLikeServiceImpl extends ServiceImpl<CommentLikeDOMapper, CommentLikeDO> implements CommentLikeService {
|
||||||
|
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,40 @@
|
|||||||
|
package com.hanserwei.hannote.comment.biz.utils;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.math.RoundingMode;
|
||||||
|
|
||||||
|
public class HeatCalculator {
|
||||||
|
|
||||||
|
// 热度计算的权重配置
|
||||||
|
private static final double LIKE_WEIGHT = 0.7; // 点赞权重 70%
|
||||||
|
private static final double REPLY_WEIGHT = 0.3; // 回复权重 30%
|
||||||
|
|
||||||
|
public static BigDecimal calculateHeat(long likeCount, long replyCount) {
|
||||||
|
// 点赞数权重 70%,被回复数权重 30%
|
||||||
|
BigDecimal likeWeight = new BigDecimal(LIKE_WEIGHT);
|
||||||
|
BigDecimal replyWeight = new BigDecimal(REPLY_WEIGHT);
|
||||||
|
|
||||||
|
// 转换点赞数和回复数为 BigDecimal
|
||||||
|
BigDecimal likeCountBD = new BigDecimal(likeCount);
|
||||||
|
BigDecimal replyCountBD = new BigDecimal(replyCount);
|
||||||
|
|
||||||
|
// 计算热度 (点赞数*点赞权重 + 回复数*回复权重)
|
||||||
|
BigDecimal heat = likeCountBD.multiply(likeWeight).add(replyCountBD.multiply(replyWeight));
|
||||||
|
|
||||||
|
// 四舍五入保留两位小数
|
||||||
|
return heat.setScale(2, RoundingMode.HALF_UP);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
int likeCount = 150; // 点赞数
|
||||||
|
int replyCount = 10; // 被回复数
|
||||||
|
|
||||||
|
// 计算热度
|
||||||
|
BigDecimal heat = calculateHeat(likeCount, replyCount);
|
||||||
|
|
||||||
|
// 输出热度值
|
||||||
|
System.out.println("Calculated Heat: " + heat);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
server:
|
||||||
|
port: 8093 # 项目启动的端口
|
||||||
|
|
||||||
|
spring:
|
||||||
|
profiles:
|
||||||
|
active: dev # 默认激活 dev 本地开发环境
|
||||||
|
servlet:
|
||||||
|
multipart:
|
||||||
|
max-file-size: 20MB # 单个文件最大大小
|
||||||
|
max-request-size: 100MB # 单次请求最大大小(包含多个文件)
|
||||||
|
mybatis-plus:
|
||||||
|
configuration:
|
||||||
|
map-underscore-to-camel-case: true
|
||||||
|
log-impl: org.apache.ibatis.logging.slf4j.Slf4jImpl
|
||||||
|
global-config:
|
||||||
|
banner: false
|
||||||
|
mapper-locations: classpath*:/mapperxml/*.xml
|
||||||
|
retry:
|
||||||
|
max-attempts: 3 # 最大重试次数
|
||||||
|
init-interval: 1000 # 初始延迟时间,单位 ms
|
||||||
|
multiplier: 2 # 每次重试间隔加倍(每次乘以 2)
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
spring:
|
||||||
|
application:
|
||||||
|
name: han-note-comment # 应用名称
|
||||||
|
profiles:
|
||||||
|
active: dev # 默认激活 dev 本地开发环境
|
||||||
|
cloud:
|
||||||
|
nacos:
|
||||||
|
discovery:
|
||||||
|
enabled: true # 启用服务发现
|
||||||
|
group: DEFAULT_GROUP # 所属组
|
||||||
|
namespace: han-note # 命名空间
|
||||||
|
server-addr: 127.0.0.1:8848 # 指定 Nacos 配置中心的服务器地址
|
||||||
|
config:
|
||||||
|
server-addr: http://127.0.0.1:8848 # 指定 Nacos 配置中心的服务器地址
|
||||||
|
prefix: ${spring.application.name} # 配置 Data Id 前缀,这里使用应用名称作为前缀
|
||||||
|
group: DEFAULT_GROUP # 所属组
|
||||||
|
namespace: han-note # 命名空间
|
||||||
|
file-extension: yaml # 配置文件格式
|
||||||
|
refresh-enabled: true # 是否开启动态刷新
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
<configuration>
|
||||||
|
<!-- 引用 Spring Boot 的 logback 基础配置 -->
|
||||||
|
<include resource="org/springframework/boot/logging/logback/defaults.xml"/>
|
||||||
|
|
||||||
|
<!-- 应用名称 -->
|
||||||
|
<property scope="context" name="appName" value="comment"/>
|
||||||
|
<!-- 自定义日志输出路径,以及日志名称前缀 -->
|
||||||
|
<property name="LOG_FILE" value="./logs/${appName}.%d{yyyy-MM-dd}"/>
|
||||||
|
<!-- 每行日志输出的格式 -->
|
||||||
|
<property name="LOG_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n"/>
|
||||||
|
|
||||||
|
<!-- 文件输出 -->
|
||||||
|
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||||
|
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
|
||||||
|
<!-- 日志文件的命名格式 -->
|
||||||
|
<fileNamePattern>${LOG_FILE}-%i.log</fileNamePattern>
|
||||||
|
<!-- 保留 30 天的日志文件 -->
|
||||||
|
<maxHistory>30</maxHistory>
|
||||||
|
<!-- 单个日志文件最大大小 -->
|
||||||
|
<maxFileSize>10MB</maxFileSize>
|
||||||
|
<!-- 日志文件的总大小,0 表示不限制 -->
|
||||||
|
<totalSizeCap>0</totalSizeCap>
|
||||||
|
<!-- 重启服务时,是否清除历史日志,不推荐清理 -->
|
||||||
|
<cleanHistoryOnStart>false</cleanHistoryOnStart>
|
||||||
|
</rollingPolicy>
|
||||||
|
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
|
||||||
|
<pattern>${LOG_PATTERN}</pattern>
|
||||||
|
<charset>UTF-8</charset>
|
||||||
|
</encoder>
|
||||||
|
</appender>
|
||||||
|
|
||||||
|
<!-- 异步写入日志,提升性能 -->
|
||||||
|
<appender name="ASYNC_FILE" class="ch.qos.logback.classic.AsyncAppender">
|
||||||
|
<!-- 是否丢弃日志, 0 表示不丢弃。默认情况下,如果队列满 80%, 会丢弃 TRACE、DEBUG、INFO 级别的日志 -->
|
||||||
|
<discardingThreshold>0</discardingThreshold>
|
||||||
|
<!-- 队列大小。默认值为 256 -->
|
||||||
|
<queueSize>256</queueSize>
|
||||||
|
<appender-ref ref="FILE"/>
|
||||||
|
</appender>
|
||||||
|
|
||||||
|
<!-- 本地 dev 开发环境 -->
|
||||||
|
<springProfile name="dev">
|
||||||
|
<include resource="org/springframework/boot/logging/logback/console-appender.xml"/>
|
||||||
|
<root level="INFO">
|
||||||
|
<appender-ref ref="CONSOLE"/> <!-- 输出控制台日志 -->
|
||||||
|
<appender-ref ref="ASYNC_FILE"/> <!-- 打印日志到文件中。PS: 本地环境下,如果不想打印日志到文件,可注释掉此行 -->
|
||||||
|
</root>
|
||||||
|
</springProfile>
|
||||||
|
|
||||||
|
<!-- 其它环境 -->
|
||||||
|
<springProfile name="prod">
|
||||||
|
<include resource="org/springframework/boot/logging/logback/console-appender.xml"/>
|
||||||
|
<root level="INFO">
|
||||||
|
<appender-ref ref="ASYNC_FILE"/> <!-- 生产环境下,仅打印日志到文件中 -->
|
||||||
|
</root>
|
||||||
|
</springProfile>
|
||||||
|
|
||||||
|
</configuration>
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
-- 操作的 Key
|
||||||
|
local zsetKey = KEYS[1]
|
||||||
|
-- 获取传入的成员和分数列表
|
||||||
|
local membersScores = ARGV
|
||||||
|
-- ZSet 最多缓存 500 条评论
|
||||||
|
local sizeLimit = 500
|
||||||
|
|
||||||
|
-- 检查 ZSet 是否存在
|
||||||
|
if redis.call('EXISTS', zsetKey) == 0 then
|
||||||
|
return -1 -- 若不存在,直接 return
|
||||||
|
end
|
||||||
|
|
||||||
|
-- 获取当前 ZSet 的大小
|
||||||
|
local currentSize = redis.call('ZCARD', zsetKey)
|
||||||
|
|
||||||
|
-- 遍历传入的成员和分数,添加到 ZSet 中
|
||||||
|
for i = 1, #membersScores, 2 do
|
||||||
|
-- 评论 ID
|
||||||
|
local member = membersScores[i]
|
||||||
|
-- 热度值
|
||||||
|
local score = membersScores[i + 1]
|
||||||
|
|
||||||
|
-- 检查当前 ZSet 的大小是否小于 500 条
|
||||||
|
if currentSize < sizeLimit then
|
||||||
|
-- 若是,则添加缓存
|
||||||
|
redis.call('ZADD', zsetKey, score, member)
|
||||||
|
currentSize = currentSize + 1 -- 更新 ZSet 大小
|
||||||
|
else
|
||||||
|
break -- 否则,则达到最大限制,停止添加
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return 0
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
-- 操作的 Key
|
||||||
|
local key = KEYS[1]
|
||||||
|
local commentId = ARGV[1] -- 评论ID
|
||||||
|
local expireSeconds = ARGV[2] -- 过期时间(秒)
|
||||||
|
|
||||||
|
redis.call("BF.ADD", key, commentId)
|
||||||
|
|
||||||
|
-- 设置过期时间
|
||||||
|
redis.call("EXPIRE", key, expireSeconds)
|
||||||
|
return 0
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
-- 操作的 Key
|
||||||
|
local key = KEYS[1]
|
||||||
|
|
||||||
|
for i = 1, #ARGV - 1 do
|
||||||
|
redis.call("BF.ADD", key, ARGV[i])
|
||||||
|
end
|
||||||
|
|
||||||
|
---- 最后一个参数为过期时间
|
||||||
|
local expireTime = ARGV[#ARGV]
|
||||||
|
-- 设置过期时间
|
||||||
|
redis.call("EXPIRE", key, expireTime)
|
||||||
|
return 0
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
-- LUA 脚本:评论点赞布隆过滤器
|
||||||
|
|
||||||
|
local key = KEYS[1] -- 操作的 Redis Key
|
||||||
|
local commentId = ARGV[1] -- 笔记ID
|
||||||
|
|
||||||
|
-- 使用 EXISTS 命令检查布隆过滤器是否存在
|
||||||
|
local exists = redis.call('EXISTS', key)
|
||||||
|
if exists == 0 then
|
||||||
|
return -1
|
||||||
|
end
|
||||||
|
|
||||||
|
-- 校验该评论是否被点赞过(1 表示已经点赞,0 表示未点赞)
|
||||||
|
local isLiked = redis.call('BF.EXISTS', key, commentId)
|
||||||
|
if isLiked == 1 then
|
||||||
|
return 1
|
||||||
|
end
|
||||||
|
|
||||||
|
-- 未被点赞,添加点赞数据
|
||||||
|
redis.call('BF.ADD', key, commentId)
|
||||||
|
return 0
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
local key = KEYS[1] -- 操作的 Redis Key
|
||||||
|
local commentId = ARGV[1] -- 评论ID
|
||||||
|
|
||||||
|
-- 使用 EXISTS 命令检查布隆过滤器是否存在
|
||||||
|
local exists = redis.call('EXISTS', key)
|
||||||
|
if exists == 0 then
|
||||||
|
return -1
|
||||||
|
end
|
||||||
|
|
||||||
|
-- 校验该评论是否被点赞过(1 表示已经点赞,0 表示未点赞)
|
||||||
|
return redis.call('BF.EXISTS', key, commentId)
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
-- 入参说明:
|
||||||
|
-- KEYS[1]: ZSet 的键
|
||||||
|
-- ARGV: 每个评论的数据,格式为 member1, score1, member2, score2 ...
|
||||||
|
|
||||||
|
local zsetKey = KEYS[1]
|
||||||
|
local maxSize = 500 -- 最多缓存 500 条热点评论
|
||||||
|
local batchSize = #ARGV / 2 -- 有多少条评论
|
||||||
|
|
||||||
|
-- 确认 ZSet 是否存在
|
||||||
|
if redis.call("EXISTS", zsetKey) == 0 then
|
||||||
|
return -1 -- 如果 ZSet 不存在,直接返回
|
||||||
|
end
|
||||||
|
|
||||||
|
for i = 1, batchSize do
|
||||||
|
local member = ARGV[(i - 1) * 2 + 1] -- 获取当前评论 ID
|
||||||
|
local score = ARGV[(i - 1) * 2 + 2] -- 获取当前评论的热度
|
||||||
|
|
||||||
|
-- 获取 ZSet 的大小
|
||||||
|
local currentSize = redis.call("ZCARD", zsetKey)
|
||||||
|
|
||||||
|
if currentSize < maxSize then
|
||||||
|
-- 如果 ZSet 的大小小于 maxSize,直接添加
|
||||||
|
redis.call("ZADD", zsetKey, score, member)
|
||||||
|
else
|
||||||
|
-- 若已缓存 500 条热点评论
|
||||||
|
-- 获取当前 ZSet 中热度值最小的评论
|
||||||
|
local minEntry = redis.call("ZRANGE", zsetKey, 0, 0, "WITHSCORES")
|
||||||
|
-- 热度最小评论的值
|
||||||
|
local minScore = minEntry[2]
|
||||||
|
|
||||||
|
if score > minScore then
|
||||||
|
-- 如果当前评论的热度大于最小热度,替换掉最小的;否则无视
|
||||||
|
redis.call("ZREM", zsetKey, minEntry[1])
|
||||||
|
redis.call("ZADD", zsetKey, score, member)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return 0
|
||||||
|
|
||||||
@@ -0,0 +1,226 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||||
|
<mapper namespace="com.hanserwei.hannote.comment.biz.domain.mapper.CommentDOMapper">
|
||||||
|
<resultMap id="BaseResultMap" type="com.hanserwei.hannote.comment.biz.domain.dataobject.CommentDO">
|
||||||
|
<!--@mbg.generated-->
|
||||||
|
<!--@Table t_comment-->
|
||||||
|
<id column="id" jdbcType="BIGINT" property="id" />
|
||||||
|
<result column="note_id" jdbcType="BIGINT" property="noteId" />
|
||||||
|
<result column="user_id" jdbcType="BIGINT" property="userId" />
|
||||||
|
<result column="content_uuid" jdbcType="VARCHAR" property="contentUuid" />
|
||||||
|
<result column="is_content_empty" jdbcType="BIT" property="isContentEmpty" />
|
||||||
|
<result column="image_url" jdbcType="VARCHAR" property="imageUrl" />
|
||||||
|
<result column="level" jdbcType="TINYINT" property="level" />
|
||||||
|
<result column="reply_total" jdbcType="BIGINT" property="replyTotal" />
|
||||||
|
<result column="like_total" jdbcType="BIGINT" property="likeTotal" />
|
||||||
|
<result column="parent_id" jdbcType="BIGINT" property="parentId" />
|
||||||
|
<result column="reply_comment_id" jdbcType="BIGINT" property="replyCommentId" />
|
||||||
|
<result column="reply_user_id" jdbcType="BIGINT" property="replyUserId" />
|
||||||
|
<result column="is_top" jdbcType="TINYINT" property="isTop" />
|
||||||
|
<result column="create_time" jdbcType="TIMESTAMP" property="createTime" />
|
||||||
|
<result column="update_time" jdbcType="TIMESTAMP" property="updateTime" />
|
||||||
|
<result column="child_comment_total" jdbcType="BIGINT" property="childCommentTotal"/>
|
||||||
|
<result column="heat" jdbcType="DOUBLE" property="heat"/>
|
||||||
|
<result column="first_reply_comment_id" jdbcType="BIGINT" property="firstReplyCommentId"/>
|
||||||
|
</resultMap>
|
||||||
|
<sql id="Base_Column_List">
|
||||||
|
<!--@mbg.generated-->
|
||||||
|
id,
|
||||||
|
note_id,
|
||||||
|
user_id,
|
||||||
|
content_uuid,
|
||||||
|
is_content_empty,
|
||||||
|
image_url,
|
||||||
|
`level`,
|
||||||
|
reply_total,
|
||||||
|
like_total,
|
||||||
|
parent_id,
|
||||||
|
reply_comment_id,
|
||||||
|
reply_user_id,
|
||||||
|
is_top,
|
||||||
|
create_time,
|
||||||
|
update_time,
|
||||||
|
child_comment_total,
|
||||||
|
heat,
|
||||||
|
first_reply_comment_id
|
||||||
|
</sql>
|
||||||
|
|
||||||
|
<select id="selectByCommentIds" resultMap="BaseResultMap" parameterType="list">
|
||||||
|
select id,
|
||||||
|
user_id,
|
||||||
|
content_uuid,
|
||||||
|
is_content_empty,
|
||||||
|
image_url,
|
||||||
|
like_total,
|
||||||
|
is_top,
|
||||||
|
create_time,
|
||||||
|
first_reply_comment_id,
|
||||||
|
child_comment_total,
|
||||||
|
level,
|
||||||
|
parent_id,
|
||||||
|
heat,
|
||||||
|
note_id
|
||||||
|
from t_comment
|
||||||
|
where id in
|
||||||
|
<foreach collection="commentIds" open="(" separator="," close=")" item="commentId">
|
||||||
|
#{commentId}
|
||||||
|
</foreach>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
|
||||||
|
<insert id="batchInsert" parameterType="list">
|
||||||
|
insert IGNORE into t_comment (id, note_id, user_id,
|
||||||
|
content_uuid, is_content_empty, image_url,
|
||||||
|
`level`, reply_total, like_total,
|
||||||
|
parent_id, reply_comment_id, reply_user_id,
|
||||||
|
is_top, create_time, update_time)
|
||||||
|
values
|
||||||
|
<foreach collection="comments" item="comment" separator=",">
|
||||||
|
( #{comment.id}, #{comment.noteId}, #{comment.userId}, #{comment.contentUuid}, #{comment.isContentEmpty}
|
||||||
|
, #{comment.imageUrl}, #{comment.level}, #{comment.replyTotal}, #{comment.likeTotal}, #{comment.parentId}
|
||||||
|
, #{comment.replyCommentId}, #{comment.replyUserId}, #{comment.isTop}, #{comment.createTime}
|
||||||
|
, #{comment.updateTime})
|
||||||
|
</foreach>
|
||||||
|
</insert>
|
||||||
|
|
||||||
|
<update id="batchUpdateHeatByCommentIds" parameterType="map">
|
||||||
|
UPDATE t_comment
|
||||||
|
SET heat = CASE id
|
||||||
|
<foreach collection="commentHeatBOS" item="bo" separator="">
|
||||||
|
WHEN #{bo.id} THEN #{bo.heat}
|
||||||
|
</foreach>
|
||||||
|
ELSE heat END
|
||||||
|
WHERE id IN
|
||||||
|
<foreach close=")" collection="commentIds" item="commentId" open="(" separator=",">
|
||||||
|
#{commentId}
|
||||||
|
</foreach>
|
||||||
|
</update>
|
||||||
|
|
||||||
|
<select id="selectEarliestByParentId" parameterType="map" resultMap="BaseResultMap">
|
||||||
|
select id
|
||||||
|
from t_comment
|
||||||
|
where parent_id = #{parentId}
|
||||||
|
and level = 2
|
||||||
|
order by create_time
|
||||||
|
limit 1
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<update id="updateFirstReplyCommentIdByPrimaryKey" parameterType="map">
|
||||||
|
update t_comment
|
||||||
|
set first_reply_comment_id = #{firstReplyCommentId}
|
||||||
|
where id = #{id}
|
||||||
|
</update>
|
||||||
|
|
||||||
|
<select id="selectPageList" resultMap="BaseResultMap" parameterType="map">
|
||||||
|
select id,
|
||||||
|
user_id,
|
||||||
|
content_uuid,
|
||||||
|
is_content_empty,
|
||||||
|
image_url,
|
||||||
|
like_total,
|
||||||
|
is_top,
|
||||||
|
create_time,
|
||||||
|
first_reply_comment_id,
|
||||||
|
child_comment_total,
|
||||||
|
heat
|
||||||
|
from t_comment
|
||||||
|
where note_id = #{noteId}
|
||||||
|
and level = 1
|
||||||
|
order by heat desc
|
||||||
|
limit #{offset}, #{pageSize}
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select id="selectTwoLevelCommentByIds" resultMap="BaseResultMap" parameterType="list">
|
||||||
|
select id,
|
||||||
|
user_id,
|
||||||
|
content_uuid,
|
||||||
|
is_content_empty,
|
||||||
|
image_url,
|
||||||
|
like_total,
|
||||||
|
create_time,
|
||||||
|
heat
|
||||||
|
from t_comment
|
||||||
|
where id in
|
||||||
|
<foreach collection="commentIds" open="(" separator="," close=")" item="commentId">
|
||||||
|
#{commentId}
|
||||||
|
</foreach>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select id="selectHeatComments" resultMap="BaseResultMap">
|
||||||
|
select id, heat
|
||||||
|
from t_comment
|
||||||
|
where note_id = #{noteId}
|
||||||
|
and level = 1
|
||||||
|
order by heat desc
|
||||||
|
limit 500
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select id="selectChildCommentTotalById" resultType="long">
|
||||||
|
select child_comment_total
|
||||||
|
from t_comment
|
||||||
|
where id = #{commentId}
|
||||||
|
and level = 1
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select id="selectChildPageList" resultMap="BaseResultMap" parameterType="map">
|
||||||
|
select id,
|
||||||
|
user_id,
|
||||||
|
note_id,
|
||||||
|
content_uuid,
|
||||||
|
is_content_empty,
|
||||||
|
image_url,
|
||||||
|
like_total,
|
||||||
|
create_time,
|
||||||
|
reply_user_id,
|
||||||
|
parent_id,
|
||||||
|
reply_comment_id
|
||||||
|
from t_comment
|
||||||
|
where parent_id = #{parentId}
|
||||||
|
and level = 2
|
||||||
|
order by id
|
||||||
|
limit #{offset}, #{pageSize}
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select id="selectCommentCountByIds" resultMap="BaseResultMap" parameterType="list">
|
||||||
|
select id,
|
||||||
|
child_comment_total,
|
||||||
|
like_total,
|
||||||
|
level
|
||||||
|
from t_comment
|
||||||
|
where id in
|
||||||
|
<foreach collection="commentIds" open="(" separator="," close=")" item="commentId">
|
||||||
|
#{commentId}
|
||||||
|
</foreach>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select id="selectChildCommentsByParentIdAndLimit" resultMap="BaseResultMap" parameterType="map">
|
||||||
|
select id, create_time
|
||||||
|
from t_comment
|
||||||
|
where parent_id = #{parentId}
|
||||||
|
and level = 2
|
||||||
|
order by create_time
|
||||||
|
limit #{limit}
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<delete id="deleteByParentId" parameterType="long">
|
||||||
|
delete
|
||||||
|
from t_comment
|
||||||
|
where parent_id = #{commentId}
|
||||||
|
</delete>
|
||||||
|
|
||||||
|
<delete id="deleteByIds" parameterType="map">
|
||||||
|
delete
|
||||||
|
from t_comment
|
||||||
|
where id in
|
||||||
|
<foreach collection="commentIds" item="commentId" open="(" separator="," close=")">
|
||||||
|
#{commentId}
|
||||||
|
</foreach>
|
||||||
|
</delete>
|
||||||
|
|
||||||
|
<select id="selectByReplyCommentId" resultMap="BaseResultMap" parameterType="long">
|
||||||
|
select
|
||||||
|
<include refid="Base_Column_List"/>
|
||||||
|
from t_comment
|
||||||
|
where reply_comment_id = #{commentId}
|
||||||
|
</select>
|
||||||
|
</mapper>
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||||
|
<mapper namespace="com.hanserwei.hannote.comment.biz.domain.mapper.CommentLikeDOMapper">
|
||||||
|
<resultMap id="BaseResultMap" type="com.hanserwei.hannote.comment.biz.domain.dataobject.CommentLikeDO">
|
||||||
|
<!--@mbg.generated-->
|
||||||
|
<!--@Table t_comment_like-->
|
||||||
|
<id column="id" jdbcType="BIGINT" property="id" />
|
||||||
|
<result column="user_id" jdbcType="BIGINT" property="userId" />
|
||||||
|
<result column="comment_id" jdbcType="BIGINT" property="commentId" />
|
||||||
|
<result column="create_time" jdbcType="TIMESTAMP" property="createTime" />
|
||||||
|
</resultMap>
|
||||||
|
<sql id="Base_Column_List">
|
||||||
|
<!--@mbg.generated-->
|
||||||
|
id, user_id, comment_id, create_time
|
||||||
|
</sql>
|
||||||
|
|
||||||
|
<select id="selectCountByUserIdAndCommentId" resultType="int" parameterType="map">
|
||||||
|
select count(1)
|
||||||
|
from t_comment_like
|
||||||
|
where user_id = #{userId}
|
||||||
|
and comment_id = #{commentId}
|
||||||
|
limit 1
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select id="selectByUserId" resultMap="BaseResultMap" parameterType="map">
|
||||||
|
select comment_id
|
||||||
|
from t_comment_like
|
||||||
|
where user_id = #{userId}
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<delete id="batchDelete" parameterType="map">
|
||||||
|
DELETE
|
||||||
|
FROM t_comment_like
|
||||||
|
WHERE (comment_id, user_id) IN
|
||||||
|
<foreach collection="unlikes" item="unlike" open="(" separator="," close=")">
|
||||||
|
(#{unlike.commentId}, #{unlike.userId})
|
||||||
|
</foreach>
|
||||||
|
</delete>
|
||||||
|
|
||||||
|
<insert id="batchInsert" parameterType="list">
|
||||||
|
INSERT INTO t_comment_like (comment_id, user_id, create_time)
|
||||||
|
VALUES
|
||||||
|
<foreach collection="likes" item="like" separator=",">
|
||||||
|
(#{like.commentId}, #{like.userId}, #{like.createTime})
|
||||||
|
</foreach>
|
||||||
|
ON DUPLICATE KEY UPDATE id=id
|
||||||
|
</insert>
|
||||||
|
</mapper>
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||||
|
<mapper namespace="com.hanserwei.hannote.comment.biz.domain.mapper.NoteCountDOMapper">
|
||||||
|
<resultMap id="BaseResultMap" type="com.hanserwei.hannote.comment.biz.domain.dataobject.NoteCountDO">
|
||||||
|
<!--@mbg.generated-->
|
||||||
|
<!--@Table t_note_count-->
|
||||||
|
<id column="id" jdbcType="BIGINT" property="id"/>
|
||||||
|
<result column="note_id" jdbcType="BIGINT" property="noteId"/>
|
||||||
|
<result column="like_total" jdbcType="BIGINT" property="likeTotal"/>
|
||||||
|
<result column="collect_total" jdbcType="BIGINT" property="collectTotal"/>
|
||||||
|
<result column="comment_total" jdbcType="BIGINT" property="commentTotal"/>
|
||||||
|
</resultMap>
|
||||||
|
<sql id="Base_Column_List">
|
||||||
|
<!--@mbg.generated-->
|
||||||
|
id, note_id, like_total, collect_total, comment_total
|
||||||
|
</sql>
|
||||||
|
|
||||||
|
<select id="selectCommentTotalByNoteId" resultType="long">
|
||||||
|
select comment_total
|
||||||
|
from t_note_count
|
||||||
|
where note_id = #{noteId}
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<update id="updateCommentTotalByNoteId" parameterType="map">
|
||||||
|
update t_note_count
|
||||||
|
set comment_total = comment_total + #{count}
|
||||||
|
where note_id = #{noteId}
|
||||||
|
</update>
|
||||||
|
</mapper>
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
package com.hanserwei.hannote.comment.biz;
|
||||||
|
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.apache.rocketmq.client.producer.SendCallback;
|
||||||
|
import org.apache.rocketmq.client.producer.SendResult;
|
||||||
|
import org.apache.rocketmq.spring.core.RocketMQTemplate;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.springframework.boot.test.context.SpringBootTest;
|
||||||
|
import org.springframework.messaging.Message;
|
||||||
|
import org.springframework.messaging.support.MessageBuilder;
|
||||||
|
|
||||||
|
@SpringBootTest
|
||||||
|
@Slf4j
|
||||||
|
class MQTests {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private RocketMQTemplate rocketMQTemplate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 测试:模拟发送评论发布消息
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
void testBatchSendMQ() {
|
||||||
|
for (long i = 0; i < 1620; i++) {
|
||||||
|
|
||||||
|
// 构建消息对象
|
||||||
|
Message<String> message = MessageBuilder.withPayload("消息体数据")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
// 异步发送 MQ 消息
|
||||||
|
rocketMQTemplate.asyncSend("PublishCommentTopic", message, new SendCallback() {
|
||||||
|
@Override
|
||||||
|
public void onSuccess(SendResult sendResult) {
|
||||||
|
log.info("==> 【评论发布】MQ 发送成功,SendResult: {}", sendResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onException(Throwable throwable) {
|
||||||
|
log.error("==> 【评论发布】MQ 发送异常: ", throwable);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
25
han-note-comment/pom.xml
Normal file
25
han-note-comment/pom.xml
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<!-- 指定父项目 -->
|
||||||
|
<parent>
|
||||||
|
<groupId>com.hanserwei</groupId>
|
||||||
|
<artifactId>han-note</artifactId>
|
||||||
|
<version>${revision}</version>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<!-- 多模块项目需要配置打包方式为 pom -->
|
||||||
|
<packaging>pom</packaging>
|
||||||
|
|
||||||
|
<!-- 子模块管理 -->
|
||||||
|
<modules>
|
||||||
|
<module>han-note-comment-api</module>
|
||||||
|
<module>han-note-comment-biz</module>
|
||||||
|
</modules>
|
||||||
|
|
||||||
|
<artifactId>han-note-comment</artifactId>
|
||||||
|
<!-- 项目名称 -->
|
||||||
|
<name>${project.artifactId}</name>
|
||||||
|
<!-- 项目描述 -->
|
||||||
|
<description>评论服务</description>
|
||||||
|
</project>
|
||||||
25
han-note-count/han-note-count-api/pom.xml
Normal file
25
han-note-count/han-note-count-api/pom.xml
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<!-- 指定父项目 -->
|
||||||
|
<parent>
|
||||||
|
<groupId>com.hanserwei</groupId>
|
||||||
|
<artifactId>han-note-count</artifactId>
|
||||||
|
<version>${revision}</version>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<!-- 打包方式 -->
|
||||||
|
<packaging>jar</packaging>
|
||||||
|
|
||||||
|
<artifactId>han-note-count-api</artifactId>
|
||||||
|
<name>${project.artifactId}</name>
|
||||||
|
<description>RPC层, 供其他服务调用</description>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.hanserwei</groupId>
|
||||||
|
<artifactId>hanserwei-common</artifactId>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
</project>
|
||||||
117
han-note-count/han-note-count-biz/pom.xml
Normal file
117
han-note-count/han-note-count-biz/pom.xml
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<!-- 指定父项目 -->
|
||||||
|
<parent>
|
||||||
|
<groupId>com.hanserwei</groupId>
|
||||||
|
<artifactId>han-note-count</artifactId>
|
||||||
|
<version>${revision}</version>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<!-- 打包方式 -->
|
||||||
|
<packaging>jar</packaging>
|
||||||
|
|
||||||
|
<artifactId>han-note-count-biz</artifactId>
|
||||||
|
<name>${project.artifactId}</name>
|
||||||
|
<description>计数服务业务模块</description>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.hanserwei</groupId>
|
||||||
|
<artifactId>hanserwei-common</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- 业务接口日志组件 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.hanserwei</groupId>
|
||||||
|
<artifactId>hanserwei-spring-boot-starter-biz-operationlog</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- 上下文组件 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.hanserwei</groupId>
|
||||||
|
<artifactId>hanserwei-spring-boot-starter-biz-context</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Jackson 组件 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.hanserwei</groupId>
|
||||||
|
<artifactId>hanserwei-spring-boot-starter-jackson</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-web</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-test</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.cloud</groupId>
|
||||||
|
<artifactId>spring-cloud-starter-bootstrap</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- 服务发现 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.alibaba.cloud</groupId>
|
||||||
|
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Mybatis-plus -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.baomidou</groupId>
|
||||||
|
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- MySQL 驱动 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.mysql</groupId>
|
||||||
|
<artifactId>mysql-connector-j</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Druid 数据库连接池 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.alibaba</groupId>
|
||||||
|
<artifactId>druid-spring-boot-3-starter</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- 提供 Redis 连接池 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.commons</groupId>
|
||||||
|
<artifactId>commons-pool2</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Redis -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-data-redis</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Rocket MQ -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.rocketmq</groupId>
|
||||||
|
<artifactId>rocketmq-spring-boot-starter</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- 快手 Buffer Trigger -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.github.phantomthief</groupId>
|
||||||
|
<artifactId>buffer-trigger</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
</project>
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package com.hanserwei.hannote.count.biz;
|
||||||
|
|
||||||
|
import org.mybatis.spring.annotation.MapperScan;
|
||||||
|
import org.springframework.boot.SpringApplication;
|
||||||
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
|
||||||
|
@SpringBootApplication
|
||||||
|
@MapperScan("com.hanserwei.hannote.count.biz.domain.mapper")
|
||||||
|
public class HannoteCountBizApplication {
|
||||||
|
public static void main(String[] args) {
|
||||||
|
SpringApplication.run(HannoteCountBizApplication.class, args);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
package com.hanserwei.hannote.count.biz.config;
|
||||||
|
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.data.redis.connection.RedisConnectionFactory;
|
||||||
|
import org.springframework.data.redis.core.RedisTemplate;
|
||||||
|
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
|
||||||
|
import org.springframework.data.redis.serializer.StringRedisSerializer;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
public class RedisTemplateConfig {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
|
||||||
|
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
|
||||||
|
// 设置 RedisTemplate 的连接工厂
|
||||||
|
redisTemplate.setConnectionFactory(connectionFactory);
|
||||||
|
|
||||||
|
// 使用 StringRedisSerializer 来序列化和反序列化 redis 的 key 值,确保 key 是可读的字符串
|
||||||
|
redisTemplate.setKeySerializer(new StringRedisSerializer());
|
||||||
|
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
|
||||||
|
|
||||||
|
// 使用 Jackson2JsonRedisSerializer 来序列化和反序列化 redis 的 value 值, 确保存储的是 JSON 格式
|
||||||
|
Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer<>(Object.class);
|
||||||
|
redisTemplate.setValueSerializer(serializer);
|
||||||
|
redisTemplate.setHashValueSerializer(serializer);
|
||||||
|
|
||||||
|
redisTemplate.afterPropertiesSet();
|
||||||
|
return redisTemplate;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package com.hanserwei.hannote.count.biz.config;
|
||||||
|
|
||||||
|
import org.apache.rocketmq.spring.autoconfigure.RocketMQAutoConfiguration;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.context.annotation.Import;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@Import(RocketMQAutoConfiguration.class)
|
||||||
|
public class RocketMQConfig {
|
||||||
|
}
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
package com.hanserwei.hannote.count.biz.constant;
|
||||||
|
|
||||||
|
public interface MQConstants {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Topic: 笔记评论总数计数
|
||||||
|
*/
|
||||||
|
String TOPIC_COUNT_NOTE_COMMENT = "CountNoteCommentTopic";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Topic: 评论热度值更新
|
||||||
|
*/
|
||||||
|
String TOPIC_COMMENT_HEAT_UPDATE = "CommentHeatUpdateTopic";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Topic: 计数 - 笔记点赞数
|
||||||
|
*/
|
||||||
|
String TOPIC_LIKE_OR_UNLIKE = "LikeUnlikeTopic";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Topic: 笔记收藏、取消收藏
|
||||||
|
*/
|
||||||
|
String TOPIC_COLLECT_OR_UN_COLLECT = "CollectUnCollectTopic";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Topic: 关注数计数
|
||||||
|
*/
|
||||||
|
String TOPIC_COUNT_FOLLOWING = "CountFollowingTopic";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Topic: 粉丝数计数
|
||||||
|
*/
|
||||||
|
String TOPIC_COUNT_FANS = "CountFansTopic";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Topic: 粉丝数计数入库
|
||||||
|
*/
|
||||||
|
String TOPIC_COUNT_FANS_2_DB = "CountFans2DBTopic";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Topic: 粉丝数计数入库
|
||||||
|
*/
|
||||||
|
String TOPIC_COUNT_FOLLOWING_2_DB = "CountFollowing2DBTopic";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Topic: 计数 - 笔记点赞数落库
|
||||||
|
*/
|
||||||
|
String TOPIC_COUNT_NOTE_LIKE_2_DB = "CountNoteLike2DBTTopic";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Topic: 计数 - 笔记收藏数落库
|
||||||
|
*/
|
||||||
|
String TOPIC_COUNT_NOTE_COLLECT_2_DB = "CountNoteCollect2DBTTopic";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Topic: 笔记操作(发布、删除)
|
||||||
|
*/
|
||||||
|
String TOPIC_NOTE_OPERATE = "NoteOperateTopic";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Topic: 评论点赞数更新
|
||||||
|
*/
|
||||||
|
String TOPIC_COMMENT_LIKE_OR_UNLIKE = "CommentLikeUnlikeTopic";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Topic: 计数 - 评论点赞数落库
|
||||||
|
*/
|
||||||
|
String TOPIC_COUNT_COMMENT_LIKE_2_DB = "CountCommentLike2DBTTopic";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tag 标签:笔记发布
|
||||||
|
*/
|
||||||
|
String TAG_NOTE_PUBLISH = "publishNote";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tag 标签:笔记删除
|
||||||
|
*/
|
||||||
|
String TAG_NOTE_DELETE = "deleteNote";
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
package com.hanserwei.hannote.count.biz.constant;
|
||||||
|
|
||||||
|
public class RedisKeyConstants {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hash Field: 粉丝总数
|
||||||
|
*/
|
||||||
|
public static final String FIELD_FANS_TOTAL = "fansTotal";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hash Field: 关注总数
|
||||||
|
*/
|
||||||
|
public static final String FIELD_FOLLOWING_TOTAL = "followingTotal";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hash Field: 笔记发布总数
|
||||||
|
*/
|
||||||
|
public static final String FIELD_NOTE_TOTAL = "noteTotal";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户维度计数 Key 前缀
|
||||||
|
*/
|
||||||
|
private static final String COUNT_USER_KEY_PREFIX = "count:user:";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hash Field: 笔记点赞总数
|
||||||
|
*/
|
||||||
|
public static final String FIELD_LIKE_TOTAL = "likeTotal";
|
||||||
|
/**
|
||||||
|
* 笔记维度计数 Key 前缀
|
||||||
|
*/
|
||||||
|
private static final String COUNT_NOTE_KEY_PREFIX = "count:note:";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hash Field: 笔记评论总数
|
||||||
|
*/
|
||||||
|
public static final String FIELD_COMMENT_TOTAL = "commentTotal";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hash Field: 笔记收藏总数
|
||||||
|
*/
|
||||||
|
public static final String FIELD_COLLECT_TOTAL = "collectTotal";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hash Field: 子评论总数
|
||||||
|
*/
|
||||||
|
public static final String FIELD_CHILD_COMMENT_TOTAL = "childCommentTotal";
|
||||||
|
/**
|
||||||
|
* 评论维度计数 Key 前缀
|
||||||
|
*/
|
||||||
|
private static final String COUNT_COMMENT_KEY_PREFIX = "count:comment:";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构建评论维度计数 Key
|
||||||
|
*
|
||||||
|
* @param commentId 评论ID
|
||||||
|
* @return 评论维度计数 Key
|
||||||
|
*/
|
||||||
|
public static String buildCountCommentKey(Long commentId) {
|
||||||
|
return COUNT_COMMENT_KEY_PREFIX + commentId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构建用户维度计数 Key
|
||||||
|
*
|
||||||
|
* @param userId 用户ID
|
||||||
|
* @return 用户维度计数 Key
|
||||||
|
*/
|
||||||
|
public static String buildCountUserKey(Long userId) {
|
||||||
|
return COUNT_USER_KEY_PREFIX + userId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构建笔记维度计数 Key
|
||||||
|
*
|
||||||
|
* @param noteId 笔记ID
|
||||||
|
* @return 笔记维度计数 Key
|
||||||
|
*/
|
||||||
|
public static String buildCountNoteKey(Long noteId) {
|
||||||
|
return COUNT_NOTE_KEY_PREFIX + noteId;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
package com.hanserwei.hannote.count.biz.consumer;
|
||||||
|
|
||||||
|
import cn.hutool.core.collection.CollUtil;
|
||||||
|
import com.google.common.util.concurrent.RateLimiter;
|
||||||
|
import com.hanserwei.framework.common.utils.JsonUtils;
|
||||||
|
import com.hanserwei.hannote.count.biz.constant.MQConstants;
|
||||||
|
import com.hanserwei.hannote.count.biz.domain.mapper.CommentDOMapper;
|
||||||
|
import com.hanserwei.hannote.count.biz.model.dto.AggregationCountLikeUnlikeCommentMqDTO;
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
|
||||||
|
import org.apache.rocketmq.spring.core.RocketMQListener;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@SuppressWarnings("ALL")
|
||||||
|
@Component
|
||||||
|
@RocketMQMessageListener(consumerGroup = "han_note_group_" + MQConstants.TOPIC_COUNT_COMMENT_LIKE_2_DB, // Group 组
|
||||||
|
topic = MQConstants.TOPIC_COUNT_COMMENT_LIKE_2_DB // 主题 Topic
|
||||||
|
)
|
||||||
|
@Slf4j
|
||||||
|
public class CountCommentLike2DBConsumer implements RocketMQListener<String> {
|
||||||
|
|
||||||
|
// 每秒创建 5000 个令牌
|
||||||
|
private final RateLimiter rateLimiter = RateLimiter.create(5000);
|
||||||
|
@Resource
|
||||||
|
private CommentDOMapper commentDOMapper;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onMessage(String body) {
|
||||||
|
// 流量削峰:通过获取令牌,如果没有令牌可用,将阻塞,直到获得
|
||||||
|
rateLimiter.acquire();
|
||||||
|
|
||||||
|
log.info("## 消费到了 MQ 【计数: 评论点赞数入库】, {}...", body);
|
||||||
|
|
||||||
|
List<AggregationCountLikeUnlikeCommentMqDTO> countList = null;
|
||||||
|
try {
|
||||||
|
countList = JsonUtils.parseList(body, AggregationCountLikeUnlikeCommentMqDTO.class);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("## 解析 JSON 字符串异常", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (CollUtil.isNotEmpty(countList)) {
|
||||||
|
// 更新评论点赞数
|
||||||
|
countList.forEach(item -> {
|
||||||
|
Long commentId = item.getCommentId();
|
||||||
|
Integer count = item.getCount();
|
||||||
|
|
||||||
|
commentDOMapper.updateLikeTotalByCommentId(count, commentId);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,138 @@
|
|||||||
|
package com.hanserwei.hannote.count.biz.consumer;
|
||||||
|
|
||||||
|
import com.github.phantomthief.collection.BufferTrigger;
|
||||||
|
import com.google.common.collect.Lists;
|
||||||
|
import com.hanserwei.framework.common.utils.JsonUtils;
|
||||||
|
import com.hanserwei.hannote.count.biz.constant.MQConstants;
|
||||||
|
import com.hanserwei.hannote.count.biz.constant.RedisKeyConstants;
|
||||||
|
import com.hanserwei.hannote.count.biz.enums.LikeUnlikeCommentTypeEnum;
|
||||||
|
import com.hanserwei.hannote.count.biz.model.dto.AggregationCountLikeUnlikeCommentMqDTO;
|
||||||
|
import com.hanserwei.hannote.count.biz.model.dto.CountLikeUnlikeCommentMqDTO;
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.apache.rocketmq.client.producer.SendCallback;
|
||||||
|
import org.apache.rocketmq.client.producer.SendResult;
|
||||||
|
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
|
||||||
|
import org.apache.rocketmq.spring.core.RocketMQListener;
|
||||||
|
import org.apache.rocketmq.spring.core.RocketMQTemplate;
|
||||||
|
import org.springframework.data.redis.core.RedisTemplate;
|
||||||
|
import org.springframework.messaging.Message;
|
||||||
|
import org.springframework.messaging.support.MessageBuilder;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
@RocketMQMessageListener(consumerGroup = "han_note_group_count_" + MQConstants.TOPIC_COMMENT_LIKE_OR_UNLIKE, // Group 组
|
||||||
|
topic = MQConstants.TOPIC_COMMENT_LIKE_OR_UNLIKE // 主题 Topic
|
||||||
|
)
|
||||||
|
@Slf4j
|
||||||
|
public class CountCommentLikeConsumer implements RocketMQListener<String> {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private RedisTemplate<String, Object> redisTemplate;
|
||||||
|
@Resource
|
||||||
|
private RocketMQTemplate rocketMQTemplate;
|
||||||
|
|
||||||
|
private final BufferTrigger<String> bufferTrigger = BufferTrigger.<String>batchBlocking()
|
||||||
|
.bufferSize(50000) // 缓存队列的最大容量
|
||||||
|
.batchSize(1000) // 一批次最多聚合 1000 条
|
||||||
|
.linger(Duration.ofSeconds(1)) // 多久聚合一次
|
||||||
|
.setConsumerEx(this::consumeMessage) // 设置消费者方法
|
||||||
|
.build();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onMessage(String body) {
|
||||||
|
// 往 bufferTrigger 中添加元素
|
||||||
|
bufferTrigger.enqueue(body);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void consumeMessage(List<String> bodys) {
|
||||||
|
log.info("==> 【评论点赞数】聚合消息, size: {}", bodys.size());
|
||||||
|
log.info("==> 【评论点赞数】聚合消息, {}", JsonUtils.toJsonString(bodys));
|
||||||
|
|
||||||
|
// List<String> 转 List<CountLikeUnlikeCommentMqDTO>
|
||||||
|
List<CountLikeUnlikeCommentMqDTO> countLikeUnlikeCommentMqDTOS = bodys.stream()
|
||||||
|
.map(body -> JsonUtils.parseObject(body, CountLikeUnlikeCommentMqDTO.class)).toList();
|
||||||
|
|
||||||
|
// 按评论 ID 进行分组
|
||||||
|
Map<Long, List<CountLikeUnlikeCommentMqDTO>> groupMap = countLikeUnlikeCommentMqDTOS.stream()
|
||||||
|
.collect(Collectors.groupingBy(CountLikeUnlikeCommentMqDTO::getCommentId));
|
||||||
|
|
||||||
|
// 按组汇总数据,统计出最终的计数
|
||||||
|
// 最终操作的计数对象
|
||||||
|
List<AggregationCountLikeUnlikeCommentMqDTO> countList = Lists.newArrayList();
|
||||||
|
|
||||||
|
for (Map.Entry<Long, List<CountLikeUnlikeCommentMqDTO>> entry : groupMap.entrySet()) {
|
||||||
|
// 评论 ID
|
||||||
|
Long commentId = entry.getKey();
|
||||||
|
|
||||||
|
List<CountLikeUnlikeCommentMqDTO> list = entry.getValue();
|
||||||
|
// 最终的计数值,默认为 0
|
||||||
|
int finalCount = 0;
|
||||||
|
for (CountLikeUnlikeCommentMqDTO countLikeUnlikeCommentMqDTO : list) {
|
||||||
|
// 获取操作类型
|
||||||
|
Integer type = countLikeUnlikeCommentMqDTO.getType();
|
||||||
|
|
||||||
|
// 根据操作类型,获取对应枚举
|
||||||
|
LikeUnlikeCommentTypeEnum likeUnlikeCommentTypeEnum = LikeUnlikeCommentTypeEnum.valueOf(type);
|
||||||
|
|
||||||
|
// 若枚举为空,跳到下一次循环
|
||||||
|
if (Objects.isNull(likeUnlikeCommentTypeEnum)) continue;
|
||||||
|
|
||||||
|
switch (likeUnlikeCommentTypeEnum) {
|
||||||
|
case LIKE -> finalCount += 1; // 如果为点赞操作,点赞数 +1
|
||||||
|
case UNLIKE -> finalCount -= 1; // 如果为取消点赞操作,点赞数 -1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 将分组后统计出的最终计数,存入 countList 中
|
||||||
|
countList.add(AggregationCountLikeUnlikeCommentMqDTO.builder()
|
||||||
|
.commentId(commentId)
|
||||||
|
.count(finalCount)
|
||||||
|
.build());
|
||||||
|
}
|
||||||
|
|
||||||
|
log.info("## 【评论点赞数】聚合后的计数数据: {}", JsonUtils.toJsonString(countList));
|
||||||
|
|
||||||
|
// 更新 Redis
|
||||||
|
countList.forEach(item -> {
|
||||||
|
// 评论 ID
|
||||||
|
Long commentId = item.getCommentId();
|
||||||
|
// 聚合后的计数
|
||||||
|
Integer count = item.getCount();
|
||||||
|
|
||||||
|
// Redis 中评论计数 Hash Key
|
||||||
|
String countCommentRedisKey = RedisKeyConstants.buildCountCommentKey(commentId);
|
||||||
|
// 判断 Redis 中 Hash 是否存在
|
||||||
|
boolean isCountCommentExisted = redisTemplate.hasKey(countCommentRedisKey);
|
||||||
|
|
||||||
|
// 若存在才会更新
|
||||||
|
// (因为缓存设有过期时间,考虑到过期后,缓存会被删除,这里需要判断一下,存在才会去更新,而初始化工作放在查询计数来做)
|
||||||
|
if (isCountCommentExisted) {
|
||||||
|
// 对目标用户 Hash 中的点赞数字段进行计数操作
|
||||||
|
redisTemplate.opsForHash().increment(countCommentRedisKey, RedisKeyConstants.FIELD_LIKE_TOTAL, count);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 发送 MQ, 评论点赞数据落库
|
||||||
|
Message<String> message = MessageBuilder.withPayload(JsonUtils.toJsonString(countList))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
// 异步发送 MQ 消息
|
||||||
|
rocketMQTemplate.asyncSend(MQConstants.TOPIC_COUNT_COMMENT_LIKE_2_DB, message, new SendCallback() {
|
||||||
|
@Override
|
||||||
|
public void onSuccess(SendResult sendResult) {
|
||||||
|
log.info("==> 【计数服务:评论点赞数写库】MQ 发送成功,SendResult: {}", sendResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onException(Throwable throwable) {
|
||||||
|
log.error("==> 【计数服务:评论点赞数写库】MQ 发送异常: ", throwable);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
package com.hanserwei.hannote.count.biz.consumer;
|
||||||
|
|
||||||
|
import cn.hutool.core.collection.CollUtil;
|
||||||
|
import com.google.common.util.concurrent.RateLimiter;
|
||||||
|
import com.hanserwei.framework.common.utils.JsonUtils;
|
||||||
|
import com.hanserwei.hannote.count.biz.constant.MQConstants;
|
||||||
|
import com.hanserwei.hannote.count.biz.domain.mapper.UserCountDOMapper;
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
|
||||||
|
import org.apache.rocketmq.spring.core.RocketMQListener;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@SuppressWarnings("ALL")
|
||||||
|
@Component
|
||||||
|
@RocketMQMessageListener(consumerGroup = "han_note_group_" + MQConstants.TOPIC_COUNT_FANS_2_DB, // Group 组
|
||||||
|
topic = MQConstants.TOPIC_COUNT_FANS_2_DB // 主题 Topic
|
||||||
|
)
|
||||||
|
@Slf4j
|
||||||
|
public class CountFans2DBConsumer implements RocketMQListener<String> {
|
||||||
|
@Resource
|
||||||
|
private UserCountDOMapper userCountDOMapper;
|
||||||
|
|
||||||
|
// 每秒创建 5000 个令牌
|
||||||
|
private RateLimiter rateLimiter = RateLimiter.create(5000);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onMessage(String body) {
|
||||||
|
// 流量削峰:通过获取令牌,如果没有令牌可用,将阻塞,直到获得
|
||||||
|
rateLimiter.acquire();
|
||||||
|
log.info("## 消费到了 MQ 【计数: 粉丝数入库】, {}...", body);
|
||||||
|
Map<Long, Integer> countMap = null;
|
||||||
|
try {
|
||||||
|
countMap = JsonUtils.parseMap(body, Long.class, Integer.class);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("## 解析 JSON 字符串异常", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (CollUtil.isNotEmpty(countMap)) {
|
||||||
|
// 判断数据库中,若目标用户的记录不存在,则插入;若记录已存在,则直接更新
|
||||||
|
countMap.forEach((k, v) -> userCountDOMapper.insertOrUpdateFansTotalByUserId(v, k));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,127 @@
|
|||||||
|
package com.hanserwei.hannote.count.biz.consumer;
|
||||||
|
|
||||||
|
import com.github.phantomthief.collection.BufferTrigger;
|
||||||
|
import com.google.common.collect.Maps;
|
||||||
|
import com.hanserwei.framework.common.utils.JsonUtils;
|
||||||
|
import com.hanserwei.hannote.count.biz.constant.MQConstants;
|
||||||
|
import com.hanserwei.hannote.count.biz.constant.RedisKeyConstants;
|
||||||
|
import com.hanserwei.hannote.count.biz.enums.FollowUnfollowTypeEnum;
|
||||||
|
import com.hanserwei.hannote.count.biz.model.dto.CountFollowUnfollowMqDTO;
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.apache.rocketmq.client.producer.SendCallback;
|
||||||
|
import org.apache.rocketmq.client.producer.SendResult;
|
||||||
|
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
|
||||||
|
import org.apache.rocketmq.spring.core.RocketMQListener;
|
||||||
|
import org.apache.rocketmq.spring.core.RocketMQTemplate;
|
||||||
|
import org.springframework.data.redis.core.RedisTemplate;
|
||||||
|
import org.springframework.messaging.Message;
|
||||||
|
import org.springframework.messaging.support.MessageBuilder;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
@RocketMQMessageListener(
|
||||||
|
consumerGroup = "han_note_group_" + MQConstants.TOPIC_COUNT_FANS,
|
||||||
|
topic = MQConstants.TOPIC_COUNT_FANS
|
||||||
|
)
|
||||||
|
@Slf4j
|
||||||
|
public class CountFansConsumer implements RocketMQListener<String> {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private RedisTemplate<String, Object> redisTemplate;
|
||||||
|
@Resource
|
||||||
|
private RocketMQTemplate rocketMQTemplate;
|
||||||
|
|
||||||
|
private final BufferTrigger<String> bufferTrigger = BufferTrigger.<String>batchBlocking()
|
||||||
|
.bufferSize(50000) // 缓存队列的最大容量
|
||||||
|
.batchSize(1000) // 一批次最多聚合 1000 条
|
||||||
|
.linger(Duration.ofSeconds(1)) // 多久聚合一次
|
||||||
|
.setConsumerEx(this::consumeMessage)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onMessage(String body) {
|
||||||
|
// 往 bufferTrigger 中添加元素
|
||||||
|
bufferTrigger.enqueue(body);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void consumeMessage(List<String> body) {
|
||||||
|
log.info("==> 聚合消息, size: {}", body.size());
|
||||||
|
log.info("==> 聚合消息, {}", JsonUtils.toJsonString(body));
|
||||||
|
|
||||||
|
// List<String> body 转换成 List<CountFollowUnfollowMqDTO>
|
||||||
|
List<CountFollowUnfollowMqDTO> countFollowUnfollowMqDTOList = body.stream()
|
||||||
|
.map(e -> JsonUtils.parseObject(e, CountFollowUnfollowMqDTO.class))
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
// 按目标用户进行分组
|
||||||
|
Map<Long, List<CountFollowUnfollowMqDTO>> groupMap = countFollowUnfollowMqDTOList.stream()
|
||||||
|
.collect(Collectors.groupingBy(CountFollowUnfollowMqDTO::getTargetUserId));
|
||||||
|
|
||||||
|
// 按组汇聚数据,统计出最终数据
|
||||||
|
Map<Long, Integer> countMap = Maps.newHashMap();
|
||||||
|
for (Map.Entry<Long, List<CountFollowUnfollowMqDTO>> entry : groupMap.entrySet()) {
|
||||||
|
List<CountFollowUnfollowMqDTO> list = entry.getValue();
|
||||||
|
// 最终数据
|
||||||
|
int finalCount = 0;
|
||||||
|
for (CountFollowUnfollowMqDTO countFollowUnfollowMqDTO : list) {
|
||||||
|
// 获取操作类型
|
||||||
|
Integer type = countFollowUnfollowMqDTO.getType();
|
||||||
|
|
||||||
|
// 根据操作类型,获取对应枚举
|
||||||
|
FollowUnfollowTypeEnum followUnfollowTypeEnum = FollowUnfollowTypeEnum.valueOf(type);
|
||||||
|
|
||||||
|
// 若枚举类型为空,则跳过
|
||||||
|
if (Objects.isNull(followUnfollowTypeEnum)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (followUnfollowTypeEnum) {
|
||||||
|
case FOLLOW -> finalCount++;
|
||||||
|
case UNFOLLOW -> finalCount--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 将分组后统计出的最终计数,存入 countMap 中
|
||||||
|
countMap.put(entry.getKey(), finalCount);
|
||||||
|
}
|
||||||
|
log.info("## 聚合后的计数数据: {}", JsonUtils.toJsonString(countMap));
|
||||||
|
// 更新 Redis
|
||||||
|
countMap.forEach((k, v) -> {
|
||||||
|
// Redis Key
|
||||||
|
String redisKey = RedisKeyConstants.buildCountUserKey(k);
|
||||||
|
// 判断 Redis 中 Hash 是否存在
|
||||||
|
boolean isExisted = redisTemplate.hasKey(redisKey);
|
||||||
|
|
||||||
|
// 若存在才会更新
|
||||||
|
// (因为缓存设有过期时间,考虑到过期后,缓存会被删除,这里需要判断一下,存在才会去更新,而初始化工作放在查询计数来做)
|
||||||
|
if (isExisted) {
|
||||||
|
// 对目标用户 Hash 中的粉丝数字段进行计数操作
|
||||||
|
redisTemplate.opsForHash().increment(redisKey, RedisKeyConstants.FIELD_FANS_TOTAL, v);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 发送 MQ, 计数数据落库
|
||||||
|
// 构建MQ消息体
|
||||||
|
Message<String> message = MessageBuilder.withPayload(JsonUtils.toJsonString(countMap))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
// 异步发送消息提高接口响应速度
|
||||||
|
rocketMQTemplate.asyncSend(MQConstants.TOPIC_COUNT_FANS_2_DB, message, new SendCallback() {
|
||||||
|
@Override
|
||||||
|
public void onSuccess(SendResult sendResult) {
|
||||||
|
log.info("==> 【计数服务:粉丝数入库】MQ 发送成功,SendResult: {}", sendResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onException(Throwable throwable) {
|
||||||
|
log.error("==> 【计数服务:粉丝数入库】MQ 发送异常: ", throwable);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
package com.hanserwei.hannote.count.biz.consumer;
|
||||||
|
|
||||||
|
import com.google.common.util.concurrent.RateLimiter;
|
||||||
|
import com.hanserwei.framework.common.utils.JsonUtils;
|
||||||
|
import com.hanserwei.hannote.count.biz.constant.MQConstants;
|
||||||
|
import com.hanserwei.hannote.count.biz.domain.mapper.UserCountDOMapper;
|
||||||
|
import com.hanserwei.hannote.count.biz.enums.FollowUnfollowTypeEnum;
|
||||||
|
import com.hanserwei.hannote.count.biz.model.dto.CountFollowUnfollowMqDTO;
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
|
||||||
|
import org.apache.rocketmq.spring.core.RocketMQListener;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
@SuppressWarnings("ALL")
|
||||||
|
@RocketMQMessageListener(consumerGroup = "han_note_group_" + MQConstants.TOPIC_COUNT_FOLLOWING_2_DB, // Group 组
|
||||||
|
topic = MQConstants.TOPIC_COUNT_FOLLOWING_2_DB // 主题 Topic
|
||||||
|
)
|
||||||
|
@Slf4j
|
||||||
|
public class CountFollowing2DBConsumer implements RocketMQListener<String> {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private UserCountDOMapper userCountDOMapper;
|
||||||
|
|
||||||
|
// 每秒创建 5000 个令牌
|
||||||
|
private RateLimiter rateLimiter = RateLimiter.create(5000);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onMessage(String body) {
|
||||||
|
// 流量削峰:通过获取令牌,如果没有令牌可用,将阻塞,直到获得
|
||||||
|
rateLimiter.acquire();
|
||||||
|
|
||||||
|
log.info("## 消费到了 MQ 【计数: 关注数入库】, {}...", body);
|
||||||
|
|
||||||
|
if (StringUtils.isBlank(body)) return;
|
||||||
|
|
||||||
|
CountFollowUnfollowMqDTO countFollowUnfollowMqDTO = JsonUtils.parseObject(body, CountFollowUnfollowMqDTO.class);
|
||||||
|
|
||||||
|
// 操作类型:关注 or 取关
|
||||||
|
Integer type = countFollowUnfollowMqDTO.getType();
|
||||||
|
// 原用户ID
|
||||||
|
Long userId = countFollowUnfollowMqDTO.getUserId();
|
||||||
|
|
||||||
|
// 关注数:关注 +1, 取关 -1
|
||||||
|
int count = Objects.equals(type, FollowUnfollowTypeEnum.FOLLOW.getCode()) ? 1 : -1;
|
||||||
|
// 判断数据库中,若原用户的记录不存在,则插入;若记录已存在,则直接更新
|
||||||
|
userCountDOMapper.insertOrUpdateFollowingTotalByUserId(count, userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
package com.hanserwei.hannote.count.biz.consumer;
|
||||||
|
|
||||||
|
import com.hanserwei.framework.common.utils.JsonUtils;
|
||||||
|
import com.hanserwei.hannote.count.biz.constant.MQConstants;
|
||||||
|
import com.hanserwei.hannote.count.biz.constant.RedisKeyConstants;
|
||||||
|
import com.hanserwei.hannote.count.biz.enums.FollowUnfollowTypeEnum;
|
||||||
|
import com.hanserwei.hannote.count.biz.model.dto.CountFollowUnfollowMqDTO;
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.apache.rocketmq.client.producer.SendCallback;
|
||||||
|
import org.apache.rocketmq.client.producer.SendResult;
|
||||||
|
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
|
||||||
|
import org.apache.rocketmq.spring.core.RocketMQListener;
|
||||||
|
import org.apache.rocketmq.spring.core.RocketMQTemplate;
|
||||||
|
import org.springframework.data.redis.core.RedisTemplate;
|
||||||
|
import org.springframework.messaging.Message;
|
||||||
|
import org.springframework.messaging.support.MessageBuilder;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
@RocketMQMessageListener(
|
||||||
|
consumerGroup = "han_note_group_" + MQConstants.TOPIC_COUNT_FOLLOWING,
|
||||||
|
topic = MQConstants.TOPIC_COUNT_FOLLOWING
|
||||||
|
)
|
||||||
|
@Slf4j
|
||||||
|
public class CountFollowingConsumer implements RocketMQListener<String> {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private RedisTemplate<String, Object> redisTemplate;
|
||||||
|
@Resource
|
||||||
|
private RocketMQTemplate rocketMQTemplate;
|
||||||
|
@Override
|
||||||
|
public void onMessage(String body) {
|
||||||
|
log.info("## 消费了 MQ [计数:关注数]: {}", body);
|
||||||
|
if (StringUtils.isBlank(body)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 关注数和粉丝数计数场景不同,单个用户无法短时间内关注大量用户,所以无需聚合
|
||||||
|
// 直接对 Redis 中的 Hash 进行 +1 或 -1 操作即可
|
||||||
|
CountFollowUnfollowMqDTO countFollowUnfollowMqDTO = JsonUtils.parseObject(body, CountFollowUnfollowMqDTO.class);
|
||||||
|
|
||||||
|
// 操作类型:关注 or 取关
|
||||||
|
assert countFollowUnfollowMqDTO != null;
|
||||||
|
Integer type = countFollowUnfollowMqDTO.getType();
|
||||||
|
// 原用户ID
|
||||||
|
Long userId = countFollowUnfollowMqDTO.getUserId();
|
||||||
|
|
||||||
|
// 更新 Redis
|
||||||
|
String redisKey = RedisKeyConstants.buildCountUserKey(userId);
|
||||||
|
// 判断 Hash 是否存在
|
||||||
|
boolean isExisted = redisTemplate.hasKey(redisKey);
|
||||||
|
|
||||||
|
// 若存在
|
||||||
|
if (isExisted) {
|
||||||
|
// 关注数:关注 +1, 取关 -1
|
||||||
|
long count = Objects.equals(type, FollowUnfollowTypeEnum.FOLLOW.getCode()) ? 1 : -1;
|
||||||
|
// 对 Hash 中的 followingTotal 字段进行加减操作
|
||||||
|
redisTemplate.opsForHash().increment(redisKey, RedisKeyConstants.FIELD_FOLLOWING_TOTAL, count);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 发送 MQ, 关注数写库
|
||||||
|
// 构建消息对象
|
||||||
|
Message<String> message = MessageBuilder.withPayload(body)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
// 异步发送 MQ 消息
|
||||||
|
rocketMQTemplate.asyncSend(MQConstants.TOPIC_COUNT_FOLLOWING_2_DB, message, new SendCallback() {
|
||||||
|
@Override
|
||||||
|
public void onSuccess(SendResult sendResult) {
|
||||||
|
log.info("==> 【计数服务:关注数入库】MQ 发送成功,SendResult: {}", sendResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onException(Throwable throwable) {
|
||||||
|
log.error("==> 【计数服务:关注数入库】MQ 发送异常: ", throwable);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,126 @@
|
|||||||
|
package com.hanserwei.hannote.count.biz.consumer;
|
||||||
|
|
||||||
|
import cn.hutool.core.collection.CollUtil;
|
||||||
|
import com.github.phantomthief.collection.BufferTrigger;
|
||||||
|
import com.google.common.collect.Lists;
|
||||||
|
import com.hanserwei.framework.common.utils.JsonUtils;
|
||||||
|
import com.hanserwei.hannote.count.biz.constant.MQConstants;
|
||||||
|
import com.hanserwei.hannote.count.biz.constant.RedisKeyConstants;
|
||||||
|
import com.hanserwei.hannote.count.biz.domain.mapper.CommentDOMapper;
|
||||||
|
import com.hanserwei.hannote.count.biz.enums.CommentLevelEnum;
|
||||||
|
import com.hanserwei.hannote.count.biz.model.dto.CountPublishCommentMqDTO;
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.apache.rocketmq.client.producer.SendCallback;
|
||||||
|
import org.apache.rocketmq.client.producer.SendResult;
|
||||||
|
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
|
||||||
|
import org.apache.rocketmq.spring.core.RocketMQListener;
|
||||||
|
import org.apache.rocketmq.spring.core.RocketMQTemplate;
|
||||||
|
import org.springframework.data.redis.core.RedisTemplate;
|
||||||
|
import org.springframework.messaging.Message;
|
||||||
|
import org.springframework.messaging.support.MessageBuilder;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
@RocketMQMessageListener(consumerGroup = "han_note_group_child_comment_total" + MQConstants.TOPIC_COUNT_NOTE_COMMENT, // Group 组
|
||||||
|
topic = MQConstants.TOPIC_COUNT_NOTE_COMMENT // 主题 Topic
|
||||||
|
)
|
||||||
|
@Slf4j
|
||||||
|
public class CountNoteChildCommentConsumer implements RocketMQListener<String> {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private RocketMQTemplate rocketMQTemplate;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private CommentDOMapper commentDOMapper;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private RedisTemplate<String, Object> redisTemplate;
|
||||||
|
|
||||||
|
private final BufferTrigger<String> bufferTrigger = BufferTrigger.<String>batchBlocking()
|
||||||
|
.bufferSize(50000) // 缓存队列的最大容量
|
||||||
|
.batchSize(1000) // 一批次最多聚合 1000 条
|
||||||
|
.linger(Duration.ofSeconds(1)) // 多久聚合一次(1s 一次)
|
||||||
|
.setConsumerEx(this::consumeMessage) // 设置消费者方法
|
||||||
|
.build();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onMessage(String body) {
|
||||||
|
// 往 bufferTrigger 中添加元素
|
||||||
|
bufferTrigger.enqueue(body);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void consumeMessage(List<String> bodys) {
|
||||||
|
log.info("==> 【笔记二级评论数】聚合消息, size: {}", bodys.size());
|
||||||
|
log.info("==> 【笔记二级评论数】聚合消息, {}", JsonUtils.toJsonString(bodys));
|
||||||
|
|
||||||
|
// 将聚合后的消息体 Json 转 List<CountPublishCommentMqDTO>
|
||||||
|
List<CountPublishCommentMqDTO> countPublishCommentMqDTOList = Lists.newArrayList();
|
||||||
|
bodys.forEach(body -> {
|
||||||
|
try {
|
||||||
|
List<CountPublishCommentMqDTO> list = JsonUtils.parseList(body, CountPublishCommentMqDTO.class);
|
||||||
|
countPublishCommentMqDTOList.addAll(list);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("", e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 过滤出二级评论,并按 parent_id 分组
|
||||||
|
Map<Long, List<CountPublishCommentMqDTO>> groupMap = countPublishCommentMqDTOList.stream()
|
||||||
|
.filter(commentMqDTO -> Objects.equals(CommentLevelEnum.TWO.getCode(), commentMqDTO.getLevel()))
|
||||||
|
.collect(Collectors.groupingBy(CountPublishCommentMqDTO::getParentId)); // 按 parent_id 分组
|
||||||
|
|
||||||
|
// 若无二级评论,则直接 return
|
||||||
|
if (CollUtil.isEmpty(groupMap)) return;
|
||||||
|
|
||||||
|
// 循环分组字典
|
||||||
|
for (Map.Entry<Long, List<CountPublishCommentMqDTO>> entry : groupMap.entrySet()) {
|
||||||
|
// 一级评论 ID
|
||||||
|
Long parentId = entry.getKey();
|
||||||
|
// 评论数
|
||||||
|
int count = CollUtil.size(entry.getValue());
|
||||||
|
|
||||||
|
// 更新 Redis 缓存中的评论计数数据
|
||||||
|
// 构建 Key
|
||||||
|
String commentCountHashKey = RedisKeyConstants.buildCountCommentKey(parentId);
|
||||||
|
// 判断 Hash 是否存在
|
||||||
|
boolean hasKey = redisTemplate.hasKey(commentCountHashKey);
|
||||||
|
|
||||||
|
// 若 Hash 存在,则更新子评论总数
|
||||||
|
if (hasKey) {
|
||||||
|
// 累加
|
||||||
|
redisTemplate.opsForHash()
|
||||||
|
.increment(commentCountHashKey, RedisKeyConstants.FIELD_CHILD_COMMENT_TOTAL, count);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新一级评论的下级评论总数,进行累加操作
|
||||||
|
commentDOMapper.updateChildCommentTotal(parentId, count);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取字典中所用的评论ID
|
||||||
|
Set<Long> commentIds = groupMap.keySet();
|
||||||
|
|
||||||
|
// 异步发送MQ消息计数,更新评论热度值
|
||||||
|
Message<String> message = MessageBuilder.withPayload(JsonUtils.toJsonString(commentIds))
|
||||||
|
.build();
|
||||||
|
// 异步发送 MQ 消息
|
||||||
|
rocketMQTemplate.asyncSend(MQConstants.TOPIC_COMMENT_HEAT_UPDATE, message, new SendCallback() {
|
||||||
|
@Override
|
||||||
|
public void onSuccess(SendResult sendResult) {
|
||||||
|
log.info("==> 【评论热度值更新】MQ 发送成功,SendResult: {}", sendResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onException(Throwable throwable) {
|
||||||
|
log.error("==> 【评论热度值更新】MQ 发送异常: ", throwable);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
package com.hanserwei.hannote.count.biz.consumer;
|
||||||
|
|
||||||
|
import cn.hutool.core.collection.CollUtil;
|
||||||
|
import com.google.common.util.concurrent.RateLimiter;
|
||||||
|
import com.hanserwei.framework.common.utils.JsonUtils;
|
||||||
|
import com.hanserwei.hannote.count.biz.constant.MQConstants;
|
||||||
|
import com.hanserwei.hannote.count.biz.domain.mapper.NoteCountDOMapper;
|
||||||
|
import com.hanserwei.hannote.count.biz.domain.mapper.UserCountDOMapper;
|
||||||
|
import com.hanserwei.hannote.count.biz.model.dto.AggregationCountCollectedUncollectedNoteMqDTO;
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
|
||||||
|
import org.apache.rocketmq.spring.core.RocketMQListener;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.transaction.support.TransactionTemplate;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@SuppressWarnings("UnstableApiUsage")
|
||||||
|
@Component
|
||||||
|
@Slf4j
|
||||||
|
@RocketMQMessageListener(
|
||||||
|
consumerGroup = "han_note_group_" + MQConstants.TOPIC_COUNT_NOTE_COLLECT_2_DB,
|
||||||
|
topic = MQConstants.TOPIC_COUNT_NOTE_COLLECT_2_DB
|
||||||
|
)
|
||||||
|
public class CountNoteCollect2DBConsumer implements RocketMQListener<String> {
|
||||||
|
|
||||||
|
// 每秒创建 5000 个令牌
|
||||||
|
private final RateLimiter rateLimiter = RateLimiter.create(5000);
|
||||||
|
@Resource
|
||||||
|
private NoteCountDOMapper noteCountDOMapper;
|
||||||
|
@Resource
|
||||||
|
private UserCountDOMapper userCountDOMapper;
|
||||||
|
@Resource
|
||||||
|
private TransactionTemplate transactionTemplate;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onMessage(String body) {
|
||||||
|
// 流量削峰:通过获取令牌,如果没有令牌可用,将阻塞,直到获得
|
||||||
|
rateLimiter.acquire();
|
||||||
|
|
||||||
|
log.info("## 消费到了 MQ 【计数: 笔记收藏数入库】, {}...", body);
|
||||||
|
|
||||||
|
List<AggregationCountCollectedUncollectedNoteMqDTO> countList = null;
|
||||||
|
try {
|
||||||
|
countList = JsonUtils.parseList(body, AggregationCountCollectedUncollectedNoteMqDTO.class);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("## 解析 JSON 字符串异常");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (CollUtil.isNotEmpty(countList)) {
|
||||||
|
countList.forEach(item -> {
|
||||||
|
Long creatorId = item.getCreatorId();
|
||||||
|
Long noteId = item.getNoteId();
|
||||||
|
Integer count = item.getCount();
|
||||||
|
|
||||||
|
// 编程式事务,保证两条语句的原子性
|
||||||
|
transactionTemplate.execute(status -> {
|
||||||
|
try {
|
||||||
|
noteCountDOMapper.insertOrUpdateCollectTotalByNoteId(count, noteId);
|
||||||
|
userCountDOMapper.insertOrUpdateCollectTotalByUserId(count, creatorId);
|
||||||
|
return true;
|
||||||
|
} catch (Exception ex) {
|
||||||
|
status.setRollbackOnly(); // 标记事务为回滚
|
||||||
|
log.error("", ex);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,141 @@
|
|||||||
|
package com.hanserwei.hannote.count.biz.consumer;
|
||||||
|
|
||||||
|
import com.github.phantomthief.collection.BufferTrigger;
|
||||||
|
import com.google.common.collect.Lists;
|
||||||
|
import com.hanserwei.framework.common.utils.JsonUtils;
|
||||||
|
import com.hanserwei.hannote.count.biz.constant.MQConstants;
|
||||||
|
import com.hanserwei.hannote.count.biz.constant.RedisKeyConstants;
|
||||||
|
import com.hanserwei.hannote.count.biz.enums.CollectUnCollectNoteTypeEnum;
|
||||||
|
import com.hanserwei.hannote.count.biz.model.dto.AggregationCountCollectedUncollectedNoteMqDTO;
|
||||||
|
import com.hanserwei.hannote.count.biz.model.dto.CountCollectUnCollectNoteMqDTO;
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.apache.rocketmq.client.producer.SendCallback;
|
||||||
|
import org.apache.rocketmq.client.producer.SendResult;
|
||||||
|
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
|
||||||
|
import org.apache.rocketmq.spring.core.RocketMQListener;
|
||||||
|
import org.apache.rocketmq.spring.core.RocketMQTemplate;
|
||||||
|
import org.springframework.data.redis.core.RedisTemplate;
|
||||||
|
import org.springframework.messaging.Message;
|
||||||
|
import org.springframework.messaging.support.MessageBuilder;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
@Slf4j
|
||||||
|
@RocketMQMessageListener(
|
||||||
|
consumerGroup = "han_note_group_" + MQConstants.TOPIC_COLLECT_OR_UN_COLLECT,
|
||||||
|
topic = MQConstants.TOPIC_COLLECT_OR_UN_COLLECT
|
||||||
|
)
|
||||||
|
public class CountNoteCollectConsumer implements RocketMQListener<String> {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private RedisTemplate<String, Object> redisTemplate;
|
||||||
|
@Resource
|
||||||
|
private RocketMQTemplate rocketMQTemplate;
|
||||||
|
private final BufferTrigger<String> bufferTrigger = BufferTrigger.<String>batchBlocking()
|
||||||
|
.bufferSize(50000) // 缓存队列的最大容量
|
||||||
|
.batchSize(1000) // 一批次最多聚合 1000 条
|
||||||
|
.linger(Duration.ofSeconds(1)) // 多久聚合一次
|
||||||
|
.setConsumerEx(this::consumeMessage) // 设置消费者方法
|
||||||
|
.build();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onMessage(String body) {
|
||||||
|
// 往 bufferTrigger 中添加元素
|
||||||
|
bufferTrigger.enqueue(body);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void consumeMessage(List<String> bodies) {
|
||||||
|
log.info("==> 【笔记收藏数】聚合消息, size: {}", bodies.size());
|
||||||
|
log.info("==> 【笔记收藏数】聚合消息, {}", JsonUtils.toJsonString(bodies));
|
||||||
|
|
||||||
|
// List<String> -> List<CountCollectUnCollectNoteMqDTO>
|
||||||
|
List<CountCollectUnCollectNoteMqDTO> countCollectUnCollectNoteMqDTOS = bodies.stream()
|
||||||
|
.map(body -> JsonUtils.parseObject(body, CountCollectUnCollectNoteMqDTO.class))
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
// 按笔记ID分组
|
||||||
|
Map<Long, List<CountCollectUnCollectNoteMqDTO>> groupMap = countCollectUnCollectNoteMqDTOS.stream()
|
||||||
|
.collect(Collectors.groupingBy(CountCollectUnCollectNoteMqDTO::getNoteId));
|
||||||
|
// 按组汇总数据,统计出最终的计数
|
||||||
|
List<AggregationCountCollectedUncollectedNoteMqDTO> countList = Lists.newArrayList();
|
||||||
|
for (Map.Entry<Long, List<CountCollectUnCollectNoteMqDTO>> entry : groupMap.entrySet()) {
|
||||||
|
// 笔记 ID
|
||||||
|
Long noteId = entry.getKey();
|
||||||
|
// 笔记发布者 ID
|
||||||
|
Long creatorId = null;
|
||||||
|
List<CountCollectUnCollectNoteMqDTO> list = entry.getValue();
|
||||||
|
// 默认计数为0
|
||||||
|
int finalCount = 0;
|
||||||
|
for (CountCollectUnCollectNoteMqDTO countCollectUnCollectNoteMqDTO : list) {
|
||||||
|
Integer type = countCollectUnCollectNoteMqDTO.getType();
|
||||||
|
creatorId = countCollectUnCollectNoteMqDTO.getNoteCreatorId();
|
||||||
|
// 获取枚举类
|
||||||
|
CollectUnCollectNoteTypeEnum collectUnCollectNoteTypeEnum = CollectUnCollectNoteTypeEnum.valueOf(type);
|
||||||
|
switch (Objects.requireNonNull(collectUnCollectNoteTypeEnum)) {
|
||||||
|
case COLLECT -> finalCount++;
|
||||||
|
case UN_COLLECT -> finalCount--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 将分组后统计出的最终计数,存入 countList 中
|
||||||
|
countList.add(AggregationCountCollectedUncollectedNoteMqDTO.builder()
|
||||||
|
.noteId(noteId)
|
||||||
|
.creatorId(creatorId)
|
||||||
|
.count(finalCount)
|
||||||
|
.build());
|
||||||
|
}
|
||||||
|
log.info("==> 【笔记收藏数】最终结果, {}", JsonUtils.toJsonString(countList));
|
||||||
|
|
||||||
|
// 更新 Redis
|
||||||
|
countList.forEach(item -> {
|
||||||
|
// 笔记发布者 ID
|
||||||
|
Long creatorId = item.getCreatorId();
|
||||||
|
// 笔记 ID
|
||||||
|
Long noteId = item.getNoteId();
|
||||||
|
// 聚合后的计数
|
||||||
|
Integer count = item.getCount();
|
||||||
|
|
||||||
|
// 笔记维度计数 Redis Key
|
||||||
|
String countNoteRedisKey = RedisKeyConstants.buildCountNoteKey(noteId);
|
||||||
|
// 判断Redis 中 Hash 是否存在
|
||||||
|
boolean isCountNoteExisted = redisTemplate.hasKey(countNoteRedisKey);
|
||||||
|
// 若存在才会更新
|
||||||
|
// (因为缓存设有过期时间,考虑到过期后,缓存会被删除,这里需要判断一下,存在才会去更新,而初始化工作放在查询计数来做)
|
||||||
|
if (isCountNoteExisted) {
|
||||||
|
// 对目标用户 Hash 中的点赞数字段进行计数操作
|
||||||
|
redisTemplate.opsForHash().increment(countNoteRedisKey, RedisKeyConstants.FIELD_COLLECT_TOTAL, count);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新 Redis 用户维度收藏数
|
||||||
|
String countUserRedisKey = RedisKeyConstants.buildCountUserKey(creatorId);
|
||||||
|
Boolean isCountUserExisted = redisTemplate.hasKey(countUserRedisKey);
|
||||||
|
if (isCountUserExisted) {
|
||||||
|
// 对目标用户 Hash 中的收藏数字段进行计数操作
|
||||||
|
redisTemplate.opsForHash().increment(countUserRedisKey, RedisKeyConstants.FIELD_COLLECT_TOTAL, count);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 发送 MQ, 笔记收藏数据落库
|
||||||
|
Message<String> message = MessageBuilder.withPayload(JsonUtils.toJsonString(countList))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
// 异步发送 MQ 消息
|
||||||
|
rocketMQTemplate.asyncSend(MQConstants.TOPIC_COUNT_NOTE_COLLECT_2_DB, message, new SendCallback() {
|
||||||
|
@Override
|
||||||
|
public void onSuccess(SendResult sendResult) {
|
||||||
|
log.info("==> 【计数服务:笔记收藏数入库】MQ 发送成功,SendResult: {}", sendResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onException(Throwable throwable) {
|
||||||
|
log.error("==> 【计数服务:笔记收藏数入库】MQ 发送异常: ", throwable);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
package com.hanserwei.hannote.count.biz.consumer;
|
||||||
|
|
||||||
|
import cn.hutool.core.collection.CollUtil;
|
||||||
|
import com.github.phantomthief.collection.BufferTrigger;
|
||||||
|
import com.google.common.collect.Lists;
|
||||||
|
import com.hanserwei.framework.common.utils.JsonUtils;
|
||||||
|
import com.hanserwei.hannote.count.biz.constant.MQConstants;
|
||||||
|
import com.hanserwei.hannote.count.biz.constant.RedisKeyConstants;
|
||||||
|
import com.hanserwei.hannote.count.biz.domain.mapper.NoteCountDOMapper;
|
||||||
|
import com.hanserwei.hannote.count.biz.model.dto.CountPublishCommentMqDTO;
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
|
||||||
|
import org.apache.rocketmq.spring.core.RocketMQListener;
|
||||||
|
import org.springframework.data.redis.core.RedisTemplate;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
@RocketMQMessageListener(consumerGroup = "han_note_group_" + MQConstants.TOPIC_COUNT_NOTE_COMMENT, // Group 组
|
||||||
|
topic = MQConstants.TOPIC_COUNT_NOTE_COMMENT // 主题 Topic
|
||||||
|
)
|
||||||
|
@Slf4j
|
||||||
|
public class CountNoteCommentConsumer implements RocketMQListener<String> {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private NoteCountDOMapper noteCountDOMapper;
|
||||||
|
@Resource
|
||||||
|
private RedisTemplate<String, Object> redisTemplate;
|
||||||
|
|
||||||
|
private final BufferTrigger<String> bufferTrigger = BufferTrigger.<String>batchBlocking()
|
||||||
|
.bufferSize(50000) // 缓存队列的最大容量
|
||||||
|
.batchSize(1000) // 一批次最多聚合 1000 条
|
||||||
|
.linger(Duration.ofSeconds(1)) // 多久聚合一次(1s 一次)
|
||||||
|
.setConsumerEx(this::consumeMessage) // 设置消费者方法
|
||||||
|
.build();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onMessage(String body) {
|
||||||
|
// 往 bufferTrigger 中添加元素
|
||||||
|
bufferTrigger.enqueue(body);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void consumeMessage(List<String> bodys) {
|
||||||
|
log.info("==> 【笔记评论数】聚合消息, size: {}", bodys.size());
|
||||||
|
log.info("==> 【笔记评论数】聚合消息, {}", JsonUtils.toJsonString(bodys));
|
||||||
|
|
||||||
|
// 将聚合后的消息体 Json 转 List<CountPublishCommentMqDTO>
|
||||||
|
List<CountPublishCommentMqDTO> countPublishCommentMqDTOList = Lists.newArrayList();
|
||||||
|
bodys.forEach(body -> {
|
||||||
|
try {
|
||||||
|
List<CountPublishCommentMqDTO> list = JsonUtils.parseList(body, CountPublishCommentMqDTO.class);
|
||||||
|
countPublishCommentMqDTOList.addAll(list);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("", e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 按笔记 ID 进行分组
|
||||||
|
Map<Long, List<CountPublishCommentMqDTO>> groupMap = countPublishCommentMqDTOList.stream()
|
||||||
|
.collect(Collectors.groupingBy(CountPublishCommentMqDTO::getNoteId));
|
||||||
|
|
||||||
|
// 循环分组字典
|
||||||
|
for (Map.Entry<Long, List<CountPublishCommentMqDTO>> entry : groupMap.entrySet()) {
|
||||||
|
// 笔记 ID
|
||||||
|
Long noteId = entry.getKey();
|
||||||
|
// 评论数
|
||||||
|
int count = CollUtil.size(entry.getValue());
|
||||||
|
|
||||||
|
// 更新 Redis 缓存中的笔记评论总数
|
||||||
|
// 构建 Key
|
||||||
|
String noteCountHashKey = RedisKeyConstants.buildCountNoteKey(noteId);
|
||||||
|
// 判断 Hash 是否存在
|
||||||
|
boolean hasKey = redisTemplate.hasKey(noteCountHashKey);
|
||||||
|
|
||||||
|
// 若 Hash 存在
|
||||||
|
if (hasKey) {
|
||||||
|
// 累加更新
|
||||||
|
redisTemplate.opsForHash()
|
||||||
|
.increment(noteCountHashKey, RedisKeyConstants.FIELD_COMMENT_TOTAL, count);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 若评论数大于零,则执行更新操作:累加评论总数
|
||||||
|
if (count > 0) {
|
||||||
|
noteCountDOMapper.insertOrUpdateCommentTotalByNoteId(count, noteId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
package com.hanserwei.hannote.count.biz.consumer;
|
||||||
|
|
||||||
|
import cn.hutool.core.collection.CollUtil;
|
||||||
|
import com.google.common.util.concurrent.RateLimiter;
|
||||||
|
import com.hanserwei.framework.common.utils.JsonUtils;
|
||||||
|
import com.hanserwei.hannote.count.biz.constant.MQConstants;
|
||||||
|
import com.hanserwei.hannote.count.biz.domain.mapper.NoteCountDOMapper;
|
||||||
|
import com.hanserwei.hannote.count.biz.domain.mapper.UserCountDOMapper;
|
||||||
|
import com.hanserwei.hannote.count.biz.model.dto.AggregationCountLikeUnlikeNoteMqDTO;
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
|
||||||
|
import org.apache.rocketmq.spring.core.RocketMQListener;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.transaction.support.TransactionTemplate;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
@Slf4j
|
||||||
|
@SuppressWarnings({"UnstableApiUsage"})
|
||||||
|
@RocketMQMessageListener(
|
||||||
|
consumerGroup = "han_note_group_" + MQConstants.TOPIC_COUNT_NOTE_LIKE_2_DB,
|
||||||
|
topic = MQConstants.TOPIC_COUNT_NOTE_LIKE_2_DB
|
||||||
|
)
|
||||||
|
public class CountNoteLike2DBConsumer implements RocketMQListener<String> {
|
||||||
|
|
||||||
|
// 每秒创建 5000 个令牌
|
||||||
|
private final RateLimiter rateLimiter = RateLimiter.create(5000);
|
||||||
|
@Resource
|
||||||
|
private NoteCountDOMapper noteCountDOMapper;
|
||||||
|
@Resource
|
||||||
|
private UserCountDOMapper userCountDOMapper;
|
||||||
|
@Resource
|
||||||
|
private TransactionTemplate transactionTemplate;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onMessage(String body) {
|
||||||
|
// 流量削峰:通过获取令牌,如果没有令牌可用,将阻塞,直到获得
|
||||||
|
rateLimiter.acquire();
|
||||||
|
|
||||||
|
log.info("## 消费到了 MQ 【计数: 笔记点赞数入库】, {}...", body);
|
||||||
|
|
||||||
|
List<AggregationCountLikeUnlikeNoteMqDTO> countList = null;
|
||||||
|
try {
|
||||||
|
countList = JsonUtils.parseList(body, AggregationCountLikeUnlikeNoteMqDTO.class);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("## 解析 JSON 字符串异常", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (CollUtil.isNotEmpty(countList)) {
|
||||||
|
// 判断数据库中 t_user_count 和 t_note_count 表,若笔记计数记录不存在,则插入;若记录已存在,则直接更新
|
||||||
|
countList.forEach(item -> {
|
||||||
|
Long creatorId = item.getCreatorId();
|
||||||
|
Long noteId = item.getNoteId();
|
||||||
|
Integer count = item.getCount();
|
||||||
|
|
||||||
|
// 编程式事务,保证两条语句的原子性
|
||||||
|
transactionTemplate.execute(status -> {
|
||||||
|
try {
|
||||||
|
noteCountDOMapper.insertOrUpdateLikeTotalByNoteId(count, noteId);
|
||||||
|
userCountDOMapper.insertOrUpdateLikeTotalByUserId(count, creatorId);
|
||||||
|
return true;
|
||||||
|
} catch (Exception ex) {
|
||||||
|
status.setRollbackOnly(); // 标记事务为回滚
|
||||||
|
log.error("", ex);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,140 @@
|
|||||||
|
package com.hanserwei.hannote.count.biz.consumer;
|
||||||
|
|
||||||
|
import com.github.phantomthief.collection.BufferTrigger;
|
||||||
|
import com.google.common.collect.Lists;
|
||||||
|
import com.hanserwei.framework.common.utils.JsonUtils;
|
||||||
|
import com.hanserwei.hannote.count.biz.constant.MQConstants;
|
||||||
|
import com.hanserwei.hannote.count.biz.constant.RedisKeyConstants;
|
||||||
|
import com.hanserwei.hannote.count.biz.enums.LikeUnlikeNoteTypeEnum;
|
||||||
|
import com.hanserwei.hannote.count.biz.model.dto.AggregationCountLikeUnlikeNoteMqDTO;
|
||||||
|
import com.hanserwei.hannote.count.biz.model.dto.CountLikeUnlikeNoteMqDTO;
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.apache.rocketmq.client.producer.SendCallback;
|
||||||
|
import org.apache.rocketmq.client.producer.SendResult;
|
||||||
|
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
|
||||||
|
import org.apache.rocketmq.spring.core.RocketMQListener;
|
||||||
|
import org.apache.rocketmq.spring.core.RocketMQTemplate;
|
||||||
|
import org.springframework.data.redis.core.RedisTemplate;
|
||||||
|
import org.springframework.messaging.Message;
|
||||||
|
import org.springframework.messaging.support.MessageBuilder;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
@Slf4j
|
||||||
|
@RocketMQMessageListener(
|
||||||
|
consumerGroup = "han_note_count_group_" + MQConstants.TOPIC_LIKE_OR_UNLIKE,
|
||||||
|
topic = MQConstants.TOPIC_LIKE_OR_UNLIKE
|
||||||
|
)
|
||||||
|
public class CountNoteLikeConsumer implements RocketMQListener<String> {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private RedisTemplate<String, Object> redisTemplate;
|
||||||
|
@Resource
|
||||||
|
private RocketMQTemplate rocketMQTemplate;
|
||||||
|
|
||||||
|
private final BufferTrigger<String> bufferTrigger = BufferTrigger.<String>batchBlocking()
|
||||||
|
.bufferSize(50000) // 缓存队列的最大容量
|
||||||
|
.batchSize(1000) // 一批次最多聚合 1000 条
|
||||||
|
.linger(Duration.ofSeconds(1)) // 多久聚合一次
|
||||||
|
.setConsumerEx(this::consumeMessage) // 设置消费者方法
|
||||||
|
.build();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onMessage(String body) {
|
||||||
|
// 往 bufferTrigger 中添加元素
|
||||||
|
bufferTrigger.enqueue(body);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void consumeMessage(List<String> bodies) {
|
||||||
|
log.info("==> 【笔记点赞数】聚合消息, size: {}", bodies.size());
|
||||||
|
log.info("==> 【笔记点赞数】聚合消息, {}", JsonUtils.toJsonString(bodies));
|
||||||
|
List<CountLikeUnlikeNoteMqDTO> countLikeUnlikeNoteMqDTOS = bodies.stream()
|
||||||
|
.map(body -> JsonUtils.parseObject(body, CountLikeUnlikeNoteMqDTO.class)).toList();
|
||||||
|
// 按笔记ID分组
|
||||||
|
Map<Long, List<CountLikeUnlikeNoteMqDTO>> groupMap = countLikeUnlikeNoteMqDTOS.stream()
|
||||||
|
.collect(Collectors.groupingBy(CountLikeUnlikeNoteMqDTO::getNoteId));
|
||||||
|
|
||||||
|
// 按组汇总统计处最终计数
|
||||||
|
List<AggregationCountLikeUnlikeNoteMqDTO> countList = Lists.newArrayList();
|
||||||
|
for (Map.Entry<Long, List<CountLikeUnlikeNoteMqDTO>> entry : groupMap.entrySet()) {
|
||||||
|
// 笔记 ID
|
||||||
|
Long noteId = entry.getKey();
|
||||||
|
// 笔记发布者 ID
|
||||||
|
Long creatorId = null;
|
||||||
|
List<CountLikeUnlikeNoteMqDTO> list = entry.getValue();
|
||||||
|
// 最终地计数值,默认为 0
|
||||||
|
int finalCount = 0;
|
||||||
|
for (CountLikeUnlikeNoteMqDTO countLikeUnlikeNoteMqDTO : list) {
|
||||||
|
// 设置笔记发布者用户 ID
|
||||||
|
creatorId = countLikeUnlikeNoteMqDTO.getNoteCreatorId();
|
||||||
|
Integer type = countLikeUnlikeNoteMqDTO.getType();
|
||||||
|
LikeUnlikeNoteTypeEnum likeUnlikeNoteTypeEnum = LikeUnlikeNoteTypeEnum.valueOf(type);
|
||||||
|
if (likeUnlikeNoteTypeEnum == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
switch (likeUnlikeNoteTypeEnum) {
|
||||||
|
case LIKE -> finalCount++;
|
||||||
|
case UNLIKE -> finalCount--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 将分组后统计出的最终计数,存入 countList 中
|
||||||
|
countList.add(AggregationCountLikeUnlikeNoteMqDTO.builder()
|
||||||
|
.noteId(noteId)
|
||||||
|
.creatorId(creatorId)
|
||||||
|
.count(finalCount)
|
||||||
|
.build());
|
||||||
|
}
|
||||||
|
log.info("## 【笔记点赞数】聚合后的计数数据: {}", JsonUtils.toJsonString(countList));
|
||||||
|
// 更新 Redis
|
||||||
|
countList.forEach(item -> {
|
||||||
|
// 笔记发布者 ID
|
||||||
|
Long creatorId = item.getCreatorId();
|
||||||
|
// 笔记 ID
|
||||||
|
Long noteId = item.getNoteId();
|
||||||
|
// 聚合后的计数
|
||||||
|
Integer count = item.getCount();
|
||||||
|
|
||||||
|
// 笔记维度计数 Redis Key
|
||||||
|
String countNoteRedisKey = RedisKeyConstants.buildCountNoteKey(noteId);
|
||||||
|
// 判断 Redis 中 Hash 是否存在
|
||||||
|
boolean isCountNoteExisted = redisTemplate.hasKey(countNoteRedisKey);
|
||||||
|
|
||||||
|
// 若存在才会更新
|
||||||
|
// (因为缓存设有过期时间,考虑到过期后,缓存会被删除,这里需要判断一下,存在才会去更新,而初始化工作放在查询计数来做)
|
||||||
|
if (isCountNoteExisted) {
|
||||||
|
// 对目标用户 Hash 中的点赞数字段进行计数操作
|
||||||
|
redisTemplate.opsForHash().increment(countNoteRedisKey, RedisKeyConstants.FIELD_LIKE_TOTAL, count);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新 Redis 用户维度点赞数
|
||||||
|
String countUserRedisKey = RedisKeyConstants.buildCountUserKey(creatorId);
|
||||||
|
boolean isCountUserExisted = redisTemplate.hasKey(countUserRedisKey);
|
||||||
|
if (isCountUserExisted) {
|
||||||
|
redisTemplate.opsForHash().increment(countUserRedisKey, RedisKeyConstants.FIELD_LIKE_TOTAL, count);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 发送 MQ, 笔记点赞数据落库
|
||||||
|
Message<String> message = MessageBuilder.withPayload(JsonUtils.toJsonString(countList))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
// 异步发送 MQ 消息
|
||||||
|
rocketMQTemplate.asyncSend(MQConstants.TOPIC_COUNT_NOTE_LIKE_2_DB, message, new SendCallback() {
|
||||||
|
@Override
|
||||||
|
public void onSuccess(SendResult sendResult) {
|
||||||
|
log.info("==> 【计数服务:笔记点赞数入库】MQ 发送成功,SendResult: {}", sendResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onException(Throwable throwable) {
|
||||||
|
log.error("==> 【计数服务:笔记点赞数入库】MQ 发送异常: ", throwable);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
package com.hanserwei.hannote.count.biz.consumer;
|
||||||
|
|
||||||
|
import com.hanserwei.framework.common.utils.JsonUtils;
|
||||||
|
import com.hanserwei.hannote.count.biz.constant.MQConstants;
|
||||||
|
import com.hanserwei.hannote.count.biz.constant.RedisKeyConstants;
|
||||||
|
import com.hanserwei.hannote.count.biz.domain.mapper.UserCountDOMapper;
|
||||||
|
import com.hanserwei.hannote.count.biz.model.dto.NoteOperateMqDTO;
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.apache.rocketmq.common.message.Message;
|
||||||
|
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
|
||||||
|
import org.apache.rocketmq.spring.core.RocketMQListener;
|
||||||
|
import org.springframework.data.redis.core.RedisTemplate;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
@Slf4j
|
||||||
|
@RocketMQMessageListener(
|
||||||
|
consumerGroup = "han_note_group_" + MQConstants.TOPIC_NOTE_OPERATE,
|
||||||
|
topic = MQConstants.TOPIC_NOTE_OPERATE
|
||||||
|
)
|
||||||
|
public class CountNotePublishConsumer implements RocketMQListener<Message> {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private RedisTemplate<String, Object> redisTemplate;
|
||||||
|
@Resource
|
||||||
|
private UserCountDOMapper userCountDOMapper;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onMessage(Message message) {
|
||||||
|
// 消息体
|
||||||
|
String bodyJsonStr = new String(message.getBody());
|
||||||
|
// 标签
|
||||||
|
String tags = message.getTags();
|
||||||
|
|
||||||
|
log.info("==> CountNotePublishConsumer 消费了消息 {}, tags: {}", bodyJsonStr, tags);
|
||||||
|
|
||||||
|
// 根据 MQ 标签,判断笔记操作类型
|
||||||
|
if (Objects.equals(tags, MQConstants.TAG_NOTE_PUBLISH)) { // 笔记发布
|
||||||
|
handleTagMessage(bodyJsonStr, 1);
|
||||||
|
} else if (Objects.equals(tags, MQConstants.TAG_NOTE_DELETE)) { // 笔记删除
|
||||||
|
handleTagMessage(bodyJsonStr, -1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理笔记发布和笔记删除的 MQ 消息
|
||||||
|
*
|
||||||
|
* @param bodyJsonStr 笔记发布或删除的 MQ 消息体
|
||||||
|
* @param count 笔记发布或删除的计数
|
||||||
|
*/
|
||||||
|
private void handleTagMessage(String bodyJsonStr, long count) {
|
||||||
|
// 消息体 JSON 字符串转 DTO
|
||||||
|
NoteOperateMqDTO noteOperateMqDTO = JsonUtils.parseObject(bodyJsonStr, NoteOperateMqDTO.class);
|
||||||
|
|
||||||
|
if (Objects.isNull(noteOperateMqDTO)) return;
|
||||||
|
|
||||||
|
// 笔记发布者 ID
|
||||||
|
Long creatorId = noteOperateMqDTO.getCreatorId();
|
||||||
|
|
||||||
|
// 更新 Redis 中用户维度的计数 Hash
|
||||||
|
String countUserRedisKey = RedisKeyConstants.buildCountUserKey(creatorId);
|
||||||
|
// 判断 Redis 中 Hash 是否存在
|
||||||
|
boolean isCountUserExisted = redisTemplate.hasKey(countUserRedisKey);
|
||||||
|
|
||||||
|
// 若存在才会更新
|
||||||
|
// (因为缓存设有过期时间,考虑到过期后,缓存会被删除,这里需要判断一下,存在才会去更新,而初始化工作放在查询计数来做)
|
||||||
|
if (isCountUserExisted) {
|
||||||
|
// 对目标用户 Hash 中的笔记发布总数,进行加减操作
|
||||||
|
redisTemplate.opsForHash().increment(countUserRedisKey, RedisKeyConstants.FIELD_NOTE_TOTAL, count);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新 t_user_count 表
|
||||||
|
userCountDOMapper.insertOrUpdateNoteTotalByUserId(count, creatorId);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,118 @@
|
|||||||
|
package com.hanserwei.hannote.count.biz.domain.dataobject;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.IdType;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableField;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 评论表
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
@TableName(value = "t_comment")
|
||||||
|
public class CommentDO {
|
||||||
|
/**
|
||||||
|
* id
|
||||||
|
*/
|
||||||
|
@TableId(value = "id", type = IdType.AUTO)
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 关联的笔记ID
|
||||||
|
*/
|
||||||
|
@TableField(value = "note_id")
|
||||||
|
private Long noteId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发布者用户ID
|
||||||
|
*/
|
||||||
|
@TableField(value = "user_id")
|
||||||
|
private Long userId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 评论内容UUID
|
||||||
|
*/
|
||||||
|
@TableField(value = "content_uuid")
|
||||||
|
private String contentUuid;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 内容是否为空(0:不为空 1:为空)
|
||||||
|
*/
|
||||||
|
@TableField(value = "is_content_empty")
|
||||||
|
private Boolean isContentEmpty;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 评论附加图片URL
|
||||||
|
*/
|
||||||
|
@TableField(value = "image_url")
|
||||||
|
private String imageUrl;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 级别(1:一级评论 2:二级评论)
|
||||||
|
*/
|
||||||
|
@TableField(value = "`level`")
|
||||||
|
private Integer level;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 评论被回复次数,仅一级评论需要
|
||||||
|
*/
|
||||||
|
@TableField(value = "reply_total")
|
||||||
|
private Long replyTotal;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 评论被点赞次数
|
||||||
|
*/
|
||||||
|
@TableField(value = "like_total")
|
||||||
|
private Long likeTotal;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 父ID (若是对笔记的评论,则此字段存储笔记ID; 若是二级评论,则此字段存储一级评论的ID)
|
||||||
|
*/
|
||||||
|
@TableField(value = "parent_id")
|
||||||
|
private Long parentId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 回复哪个的评论 (0表示是对笔记的评论,若是对他人评论的回复,则存储回复评论的ID)
|
||||||
|
*/
|
||||||
|
@TableField(value = "reply_comment_id")
|
||||||
|
private Long replyCommentId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 回复的哪个用户, 存储用户ID
|
||||||
|
*/
|
||||||
|
@TableField(value = "reply_user_id")
|
||||||
|
private Long replyUserId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否置顶(0:不置顶 1:置顶)
|
||||||
|
*/
|
||||||
|
@TableField(value = "is_top")
|
||||||
|
private Boolean isTop;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建时间
|
||||||
|
*/
|
||||||
|
@TableField(value = "create_time")
|
||||||
|
private LocalDateTime createTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新时间
|
||||||
|
*/
|
||||||
|
@TableField(value = "update_time")
|
||||||
|
private LocalDateTime updateTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 二级评论总数(只有一级评论才需要统计)
|
||||||
|
*/
|
||||||
|
@TableField(value = "child_comment_total")
|
||||||
|
private Long childCommentTotal;
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
package com.hanserwei.hannote.count.biz.domain.dataobject;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.IdType;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableField;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 笔记计数表
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
@TableName(value = "t_note_count")
|
||||||
|
public class NoteCountDO {
|
||||||
|
/**
|
||||||
|
* 主键ID
|
||||||
|
*/
|
||||||
|
@TableId(value = "id", type = IdType.ASSIGN_ID)
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 笔记ID
|
||||||
|
*/
|
||||||
|
@TableField(value = "note_id")
|
||||||
|
private Long noteId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获得点赞总数
|
||||||
|
*/
|
||||||
|
@TableField(value = "like_total")
|
||||||
|
private Long likeTotal;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获得收藏总数
|
||||||
|
*/
|
||||||
|
@TableField(value = "collect_total")
|
||||||
|
private Long collectTotal;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 被评论总数
|
||||||
|
*/
|
||||||
|
@TableField(value = "comment_total")
|
||||||
|
private Long commentTotal;
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
package com.hanserwei.hannote.count.biz.domain.dataobject;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.IdType;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableField;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 笔记计数表
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
@TableName(value = "t_note_count")
|
||||||
|
public class TNoteCount {
|
||||||
|
/**
|
||||||
|
* 主键ID
|
||||||
|
*/
|
||||||
|
@TableId(value = "id", type = IdType.ASSIGN_ID)
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 笔记ID
|
||||||
|
*/
|
||||||
|
@TableField(value = "note_id")
|
||||||
|
private Long noteId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获得点赞总数
|
||||||
|
*/
|
||||||
|
@TableField(value = "like_total")
|
||||||
|
private Long likeTotal;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获得收藏总数
|
||||||
|
*/
|
||||||
|
@TableField(value = "collect_total")
|
||||||
|
private Long collectTotal;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 被评论总数
|
||||||
|
*/
|
||||||
|
@TableField(value = "comment_total")
|
||||||
|
private Long commentTotal;
|
||||||
|
}
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
package com.hanserwei.hannote.count.biz.domain.dataobject;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.IdType;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableField;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户计数表
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
@TableName(value = "t_user_count")
|
||||||
|
public class UserCountDO {
|
||||||
|
/**
|
||||||
|
* 主键ID
|
||||||
|
*/
|
||||||
|
@TableId(value = "id", type = IdType.AUTO)
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户ID
|
||||||
|
*/
|
||||||
|
@TableField(value = "user_id")
|
||||||
|
private Long userId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 粉丝总数
|
||||||
|
*/
|
||||||
|
@TableField(value = "fans_total")
|
||||||
|
private Long fansTotal;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 关注总数
|
||||||
|
*/
|
||||||
|
@TableField(value = "following_total")
|
||||||
|
private Long followingTotal;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发布笔记总数
|
||||||
|
*/
|
||||||
|
@TableField(value = "note_total")
|
||||||
|
private Long noteTotal;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获得点赞总数
|
||||||
|
*/
|
||||||
|
@TableField(value = "like_total")
|
||||||
|
private Long likeTotal;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获得收藏总数
|
||||||
|
*/
|
||||||
|
@TableField(value = "collect_total")
|
||||||
|
private Long collectTotal;
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user