diff --git a/src/main/kotlin/plus/maa/backend/common/annotation/SensitiveWordDetection.kt b/src/main/kotlin/plus/maa/backend/common/annotation/SensitiveWordDetection.kt
deleted file mode 100644
index d976ec10..00000000
--- a/src/main/kotlin/plus/maa/backend/common/annotation/SensitiveWordDetection.kt
+++ /dev/null
@@ -1,19 +0,0 @@
-package plus.maa.backend.common.annotation
-
-/**
- * 敏感词检测注解
- * 用于方法上,标注该方法需要进行敏感词检测
- * 通过 SpEL 表达式获取方法参数
- *
- * @author lixuhuilll
- * Date: 2023-08-25 18:50
- */
-@MustBeDocumented
-@Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER)
-@Retention(AnnotationRetention.RUNTIME)
-annotation class SensitiveWordDetection(
- /**
- * SpEL 表达式
- */
- vararg val value: String = [],
-)
diff --git a/src/main/kotlin/plus/maa/backend/common/aop/SensitiveWordAop.kt b/src/main/kotlin/plus/maa/backend/common/aop/SensitiveWordAop.kt
deleted file mode 100644
index f19be503..00000000
--- a/src/main/kotlin/plus/maa/backend/common/aop/SensitiveWordAop.kt
+++ /dev/null
@@ -1,75 +0,0 @@
-package plus.maa.backend.common.aop
-
-import cn.hutool.dfa.WordTree
-import com.fasterxml.jackson.databind.ObjectMapper
-import org.aspectj.lang.JoinPoint
-import org.aspectj.lang.annotation.Aspect
-import org.aspectj.lang.annotation.Before
-import org.aspectj.lang.reflect.MethodSignature
-import org.springframework.core.DefaultParameterNameDiscoverer
-import org.springframework.expression.EvaluationContext
-import org.springframework.expression.spel.standard.SpelExpressionParser
-import org.springframework.expression.spel.support.StandardEvaluationContext
-import org.springframework.http.HttpStatus
-import org.springframework.stereotype.Component
-import plus.maa.backend.common.annotation.SensitiveWordDetection
-import plus.maa.backend.controller.response.MaaResultException
-
-/**
- * 敏感词处理程序
- *
- * @author lixuhuilll
- * Date: 2023-08-25 18:50
- */
-@Aspect
-@Component
-class SensitiveWordAop(
- // 敏感词库
- private val wordTree: WordTree,
- private val objectMapper: ObjectMapper,
-) {
- // SpEL 表达式解析器
- private val parser = SpelExpressionParser()
-
- // 用于获取方法参数名
- private val nameDiscoverer = DefaultParameterNameDiscoverer()
-
- fun getObjectBySpEL(spELString: String, joinPoint: JoinPoint): Any? {
- // 获取被注解方法
- val signature = joinPoint.signature as? MethodSignature ?: return null
- // 获取方法参数名数组
- val paramNames = nameDiscoverer.getParameterNames(signature.method)
- // 解析 Spring 表达式对象
- val expression = parser.parseExpression(spELString)
- // Spring 表达式上下文对象
- val context: EvaluationContext = StandardEvaluationContext()
- // 通过 joinPoint 获取被注解方法的参数
- val args = joinPoint.args
- // 给上下文赋值
- for (i in args.indices) {
- if (paramNames != null) {
- context.setVariable(paramNames[i], args[i])
- }
- }
- context.setVariable("objectMapper", objectMapper)
- // 表达式从上下文中计算出实际参数值
- return expression.getValue(context)
- }
-
- @Before("@annotation(annotation)") // 处理 SensitiveWordDetection 注解
- fun before(joinPoint: JoinPoint, annotation: SensitiveWordDetection) {
- // 获取 SpEL 表达式
- val expressions = annotation.value
- for (expression in expressions) {
- // 解析 SpEL 表达式
- val value = getObjectBySpEL(expression, joinPoint)
- // 校验
- if (value is String) {
- val matchAll = wordTree.matchAll(value)
- if (matchAll != null && matchAll.isNotEmpty()) {
- throw MaaResultException(HttpStatus.BAD_REQUEST.value(), "包含敏感词:$matchAll")
- }
- }
- }
- }
-}
diff --git a/src/main/kotlin/plus/maa/backend/config/SensitiveWordConfig.kt b/src/main/kotlin/plus/maa/backend/config/SensitiveWordConfig.kt
deleted file mode 100644
index 86452392..00000000
--- a/src/main/kotlin/plus/maa/backend/config/SensitiveWordConfig.kt
+++ /dev/null
@@ -1,61 +0,0 @@
-package plus.maa.backend.config
-
-import cn.hutool.dfa.WordTree
-import io.github.oshai.kotlinlogging.KotlinLogging
-import org.springframework.beans.factory.annotation.Value
-import org.springframework.context.ApplicationContext
-import org.springframework.context.annotation.Bean
-import org.springframework.context.annotation.Configuration
-import java.io.BufferedReader
-import java.io.IOException
-import java.io.InputStreamReader
-
-/**
- * 敏感词配置类
- *
- * @author lixuhuilll
- * Date: 2023-08-25 18:50
- */
-@Configuration
-class SensitiveWordConfig(
- // 标准的 Spring 路径匹配语法,默认为 classpath:sensitive-word.txt
- @Value("\${maa-copilot.sensitive-word.path:classpath:sensitive-word.txt}") private val sensitiveWordPath: String,
-) {
- private val log = KotlinLogging.logger {}
-
- /**
- * 敏感词库初始化
- * 使用 Hutool 的 DFA 算法库,如果后续需要可转其他开源库或者使用付费的敏感词库
- *
- * @return 敏感词库
- */
- @Bean
- @Throws(IOException::class)
- fun sensitiveWordInit(applicationContext: ApplicationContext): WordTree {
- // Spring 上下文获取敏感词文件
- val sensitiveWordResource = applicationContext.getResource(sensitiveWordPath)
- val wordTree = WordTree()
-
- // 获取载入用时
- val start = System.currentTimeMillis()
-
- // 以行为单位载入敏感词
- try {
- BufferedReader(
- InputStreamReader(sensitiveWordResource.inputStream),
- ).use { bufferedReader ->
- var line: String?
- while ((bufferedReader.readLine().also { line = it }) != null) {
- wordTree.addWord(line)
- }
- }
- } catch (e: Exception) {
- log.error { "敏感词库初始化失败:${e.message}" }
- throw e
- }
-
- log.info { "敏感词库初始化完成,耗时 ${System.currentTimeMillis() - start} ms" }
-
- return wordTree
- }
-}
diff --git a/src/main/kotlin/plus/maa/backend/controller/CommentsAreaController.kt b/src/main/kotlin/plus/maa/backend/controller/CommentsAreaController.kt
index c15a952b..d8ad9b9a 100644
--- a/src/main/kotlin/plus/maa/backend/controller/CommentsAreaController.kt
+++ b/src/main/kotlin/plus/maa/backend/controller/CommentsAreaController.kt
@@ -11,7 +11,6 @@ import org.springframework.web.bind.annotation.RequestBody
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RequestParam
import org.springframework.web.bind.annotation.RestController
-import plus.maa.backend.common.annotation.SensitiveWordDetection
import plus.maa.backend.config.doc.RequireJwt
import plus.maa.backend.config.security.AuthenticationHelper
import plus.maa.backend.controller.request.comments.CommentsAddDTO
@@ -35,7 +34,6 @@ class CommentsAreaController(
private val commentsAreaService: CommentsAreaService,
private val authHelper: AuthenticationHelper,
) {
- @SensitiveWordDetection("#comments.message")
@PostMapping("/add")
@Operation(summary = "发送评论")
@ApiResponse(description = "发送评论结果")
diff --git a/src/main/kotlin/plus/maa/backend/controller/CopilotController.kt b/src/main/kotlin/plus/maa/backend/controller/CopilotController.kt
index 594a3c29..1572003e 100644
--- a/src/main/kotlin/plus/maa/backend/controller/CopilotController.kt
+++ b/src/main/kotlin/plus/maa/backend/controller/CopilotController.kt
@@ -16,7 +16,6 @@ import org.springframework.web.bind.annotation.RequestBody
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RequestParam
import org.springframework.web.bind.annotation.RestController
-import plus.maa.backend.common.annotation.SensitiveWordDetection
import plus.maa.backend.config.doc.RequireJwt
import plus.maa.backend.config.security.AuthenticationHelper
import plus.maa.backend.controller.request.copilot.CopilotCUDRequest
@@ -44,12 +43,10 @@ class CopilotController(
@Operation(summary = "上传作业")
@ApiResponse(description = "上传作业结果")
@RequireJwt
- @SensitiveWordDetection(
- "#request.content != null ? #objectMapper.readTree(#request.content).get('doc')?.toString() : null",
- )
@PostMapping("/upload")
- fun uploadCopilot(@RequestBody @Valid request: CopilotCUDRequest): MaaResult =
- success(copilotService.upload(helper.requireUserId(), request.content))
+ fun uploadCopilot(@RequestBody @Valid request: CopilotCUDRequest): MaaResult {
+ return success(copilotService.upload(helper.requireUserId(), request.content))
+ }
@Operation(summary = "删除作业")
@ApiResponse(description = "删除作业结果")
@@ -81,9 +78,6 @@ class CopilotController(
@Operation(summary = "更新作业")
@ApiResponse(description = "更新结果")
@RequireJwt
- @SensitiveWordDetection(
- "#copilotCUDRequest.content != null ? #objectMapper.readTree(#copilotCUDRequest.content).get('doc')?.toString() : null",
- )
@PostMapping("/update")
fun updateCopilot(@RequestBody @Valid copilotCUDRequest: CopilotCUDRequest): MaaResult {
copilotService.update(helper.requireUserId(), copilotCUDRequest)
diff --git a/src/main/kotlin/plus/maa/backend/handler/GlobalExceptionHandler.kt b/src/main/kotlin/plus/maa/backend/handler/GlobalExceptionHandler.kt
index c9bc3056..d444f0a6 100644
--- a/src/main/kotlin/plus/maa/backend/handler/GlobalExceptionHandler.kt
+++ b/src/main/kotlin/plus/maa/backend/handler/GlobalExceptionHandler.kt
@@ -17,6 +17,7 @@ import org.springframework.web.servlet.NoHandlerFoundException
import plus.maa.backend.controller.response.MaaResult
import plus.maa.backend.controller.response.MaaResult.Companion.fail
import plus.maa.backend.controller.response.MaaResultException
+import plus.maa.backend.service.sensitiveword.SensitiveWordException
private val log = KotlinLogging.logger { }
@@ -132,6 +133,11 @@ class GlobalExceptionHandler {
return fail(ex.statusCode.value(), ex.message)
}
+ @ExceptionHandler(SensitiveWordException::class)
+ fun handleSensitiveWordException(ex: SensitiveWordException): MaaResult {
+ return fail(400, ex.message)
+ }
+
/**
* @return plus.maa.backend.controller.response.MaaResult
* @author john180
diff --git a/src/main/kotlin/plus/maa/backend/service/CommentsAreaService.kt b/src/main/kotlin/plus/maa/backend/service/CommentsAreaService.kt
index e787c95b..53e69786 100644
--- a/src/main/kotlin/plus/maa/backend/service/CommentsAreaService.kt
+++ b/src/main/kotlin/plus/maa/backend/service/CommentsAreaService.kt
@@ -20,6 +20,7 @@ import plus.maa.backend.repository.entity.CommentsArea
import plus.maa.backend.repository.entity.Copilot
import plus.maa.backend.repository.entity.MaaUser
import plus.maa.backend.service.model.RatingType
+import plus.maa.backend.service.sensitiveword.SensitiveWordService
import java.time.LocalDateTime
/**
@@ -33,6 +34,7 @@ class CommentsAreaService(
private val copilotRepository: CopilotRepository,
private val userRepository: UserRepository,
private val emailService: EmailService,
+ private val sensitiveWordService: SensitiveWordService,
) {
/**
* 评论
@@ -42,6 +44,7 @@ class CommentsAreaService(
* @param commentsAddDTO CommentsRequest
*/
fun addComments(userId: String, commentsAddDTO: CommentsAddDTO) {
+ sensitiveWordService.validate(commentsAddDTO.message)
val copilotId = commentsAddDTO.copilotId
val copilot = copilotRepository.findByCopilotId(copilotId).requireNotNull { "作业不存在" }
diff --git a/src/main/kotlin/plus/maa/backend/service/CopilotService.kt b/src/main/kotlin/plus/maa/backend/service/CopilotService.kt
index e38d0ea8..7f3b9736 100644
--- a/src/main/kotlin/plus/maa/backend/service/CopilotService.kt
+++ b/src/main/kotlin/plus/maa/backend/service/CopilotService.kt
@@ -16,6 +16,7 @@ import org.springframework.util.Assert
import org.springframework.util.ObjectUtils
import plus.maa.backend.common.utils.IdComponent
import plus.maa.backend.common.utils.converter.CopilotConverter
+import plus.maa.backend.common.utils.requireNotNull
import plus.maa.backend.config.external.MaaCopilotProperties
import plus.maa.backend.controller.request.copilot.CopilotCUDRequest
import plus.maa.backend.controller.request.copilot.CopilotDTO
@@ -36,6 +37,7 @@ import plus.maa.backend.repository.findByUsersId
import plus.maa.backend.service.level.ArkLevelService
import plus.maa.backend.service.model.RatingCache
import plus.maa.backend.service.model.RatingType
+import plus.maa.backend.service.sensitiveword.SensitiveWordService
import java.math.RoundingMode
import java.time.LocalDateTime
import java.time.temporal.ChronoUnit
@@ -67,6 +69,7 @@ class CopilotService(
private val commentsAreaRepository: CommentsAreaRepository,
private val properties: MaaCopilotProperties,
private val copilotConverter: CopilotConverter,
+ private val sensitiveWordService: SensitiveWordService,
) {
/**
* 并修正前端的冗余部分
@@ -132,6 +135,7 @@ class CopilotService(
*/
fun upload(loginUserId: String, content: String?): Long {
val copilotDTO = correctCopilot(parseToCopilotDto(content))
+ sensitiveWordService.validate(copilotDTO.doc)
// 将其转换为数据库存储对象
val copilot = copilotConverter.toCopilot(
copilotDTO,
@@ -363,9 +367,10 @@ class CopilotService(
*/
fun update(loginUserId: String, copilotCUDRequest: CopilotCUDRequest) {
val content = copilotCUDRequest.content
- val id = copilotCUDRequest.id
- copilotRepository.findByCopilotId(id!!)?.let { copilot: Copilot ->
+ val id = copilotCUDRequest.id.requireNotNull { "id 不能为空" }
+ copilotRepository.findByCopilotId(id)?.let { copilot: Copilot ->
val copilotDTO = correctCopilot(parseToCopilotDto(content))
+ sensitiveWordService.validate(copilotDTO.doc)
Assert.state(copilot.uploaderId == loginUserId, "您无法修改不属于您的作业")
copilot.uploadTime = LocalDateTime.now()
copilotConverter.updateCopilotFromDto(copilotDTO, content!!, copilot)
diff --git a/src/main/kotlin/plus/maa/backend/service/sensitiveword/SensitiveWordException.kt b/src/main/kotlin/plus/maa/backend/service/sensitiveword/SensitiveWordException.kt
new file mode 100644
index 00000000..037521de
--- /dev/null
+++ b/src/main/kotlin/plus/maa/backend/service/sensitiveword/SensitiveWordException.kt
@@ -0,0 +1,3 @@
+package plus.maa.backend.service.sensitiveword
+
+class SensitiveWordException(message: String?) : RuntimeException(message)
diff --git a/src/main/kotlin/plus/maa/backend/service/sensitiveword/SensitiveWordService.kt b/src/main/kotlin/plus/maa/backend/service/sensitiveword/SensitiveWordService.kt
new file mode 100644
index 00000000..91022266
--- /dev/null
+++ b/src/main/kotlin/plus/maa/backend/service/sensitiveword/SensitiveWordService.kt
@@ -0,0 +1,35 @@
+package plus.maa.backend.service.sensitiveword
+
+import cn.hutool.dfa.WordTree
+import com.fasterxml.jackson.databind.ObjectMapper
+import io.github.oshai.kotlinlogging.KotlinLogging
+import org.springframework.context.ApplicationContext
+import org.springframework.stereotype.Service
+import plus.maa.backend.config.external.MaaCopilotProperties
+
+@Service
+class SensitiveWordService(
+ private val ctx: ApplicationContext,
+ maaCopilotProperties: MaaCopilotProperties,
+ private val objectMapper: ObjectMapper,
+) {
+ private val log = KotlinLogging.logger {}
+ private val wordTree = WordTree().apply {
+ val path = maaCopilotProperties.sensitiveWord.path
+ try {
+ ctx.getResource(path).inputStream.bufferedReader().use { it.lines().forEach(::addWord) }
+ log.info { "初始化敏感词库完成: $path" }
+ } catch (e: Exception) {
+ log.error { "初始化敏感词库失败: $path" }
+ throw e
+ }
+ }
+
+ @Throws(SensitiveWordException::class)
+ fun validate(value: T) {
+ if (value == null) return
+ val text = if (value is String) value else objectMapper.writeValueAsString(value)
+ val detected = wordTree.matchAll(text)
+ if (detected.size > 0) throw SensitiveWordException("包含敏感词:$detected")
+ }
+}