增加了shiro对虚拟线程池的支持,增加了plainText字段了,优化了消息的正则匹配逻辑以可以只匹配纯文本消息#371
Merged
Conversation
```yaml
spring:
threads:
virtual:
enabled: true
```
- 新增 MessageConverser.arrayToPlainText 和 stringToPlainText 方法,实现消息纯文本提取 - MessageEvent 增加 plainText 字段,自动同步纯文本内容 - MessageHandlerFilter 增加 matchPlainText 配置 (默认开启) ,支持仅匹配纯文本消息 - 优化 filterCheck 方法,支持按需匹配纯文本或原始消息 - 修正 arraysToString 逻辑,完善 CQ 码与文本处理 - 补充相关单元测试,验证纯文本提取与匹配功能 - 升级版本号至 2.5.5
Reviewer's Guide为 Shiro 的任务执行器添加虚拟线程支持,引入消息的派生纯文本视图,并更新消息过滤逻辑,以可选地基于纯文本而非原始 CQ 编码内容进行匹配,同时增加相应测试并提升版本号。 Sequence diagram for message filtering with optional plain-text matchingsequenceDiagram
actor PluginDeveloper
participant AnyMessageEvent as AnyMessageEvent
participant MessageEvent as MessageEvent
participant MessageConverser as MessageConverser
participant CommonUtils as CommonUtils
participant RegexUtils as RegexUtils
PluginDeveloper->>AnyMessageEvent: send CQ-coded message
AnyMessageEvent->>MessageEvent: construct from JSON message
activate MessageEvent
MessageEvent->>MessageConverser: stringToArray(message)
MessageConverser-->>MessageEvent: List ArrayMsg
MessageEvent->>MessageConverser: arrayToPlainText(arrayMsg)
MessageConverser-->>MessageEvent: plainText
deactivate MessageEvent
AnyMessageEvent->>CommonUtils: allFilterCheck(event, selfId, filter)
activate CommonUtils
CommonUtils->>CommonUtils: msgExtract(event.getMessage(), event.getArrayMsg(), filter.at(), event.getSelfId())
CommonUtils-->>CommonUtils: rawMessage
alt filter.matchPlainText() is true
CommonUtils->>RegexUtils: matcher(filter.cmd(), event.getPlainText())
else filter.matchPlainText() is false
CommonUtils->>RegexUtils: matcher(filter.cmd(), rawMessage)
end
RegexUtils-->>CommonUtils: Optional Matcher
CommonUtils-->>AnyMessageEvent: CheckResult
deactivate CommonUtils
Class diagram for updated message processing and filteringclassDiagram
class MessageEvent {
- String message
- String rawMessage
- List~ArrayMsg~ arrayMsg
- String plainText
+ String getMessage()
+ String getRawMessage()
+ List~ArrayMsg~ getArrayMsg()
+ String getPlainText()
- void setMessageFromJson(JsonNode json)
}
class MessageConverser {
- MessageConverser()
+ String arraysToString(List~ArrayMsg~ array)
+ List~ArrayMsg~ stringToArray(String msg)
+ String arrayToPlainText(List~ArrayMsg~ array)
+ String stringToPlainText(String msg)
- void addTextMsg(List~ArrayMsg~ chain, String text)
}
class ArrayMsg {
+ MsgTypeEnum getType()
+ String toCQCode()
+ String getStringData(String key)
}
class MsgTypeEnum {
<<enumeration>>
text
image
other
}
class MessageHandlerFilter {
<<annotation>>
+ boolean at() default false
+ String cmd() default ""
+ boolean invert() default false
+ boolean matchPlainText() default true
}
class CommonUtils {
+ CheckResult allFilterCheck(MessageEvent event, long selfId, MessageHandlerFilter filter)
- CheckResult filterCheck(MessageEvent event, long selfId, MessageHandlerFilter filter)
- String msgExtract(String message, List~ArrayMsg~ arrayMsg, boolean at, long selfId)
}
class RegexUtils {
+ Optional~Matcher~ matcher(String pattern, String input)
}
class ShiroTaskPoolConfig {
- TaskPoolProperties taskPoolProperties
+ ShiroTaskPoolConfig(TaskPoolProperties taskPoolProperties)
+ ThreadPoolTaskExecutor shiroTaskPlatformExecutor()
+ ThreadPoolTaskExecutor shiroTaskVirtualExecutor()
- ThreadPoolTaskExecutor getThreadPoolTaskExecutor(ThreadFactory factory)
}
class TaskPoolProperties {
+ int getCorePoolSize()
+ int getMaxPoolSize()
+ int getQueueCapacity()
+ String getThreadNamePrefix()
+ int getKeepAliveSeconds()
+ boolean isWaitForTasksToCompleteOnShutdown()
+ int getAwaitTerminationSeconds()
}
MessageEvent --> MessageConverser : uses
MessageEvent --> ArrayMsg : contains
ArrayMsg --> MsgTypeEnum : typed_by
CommonUtils --> MessageEvent : uses
CommonUtils --> MessageHandlerFilter : uses
CommonUtils --> RegexUtils : uses
ShiroTaskPoolConfig --> TaskPoolProperties : depends_on
ShiroTaskPoolConfig --> ThreadPoolTaskExecutor : produces
Flow diagram for conversion from CQ-coded message to plain-textflowchart TD
A["Incoming message string or array"] --> B{Type}
B -->|String| C["MessageConverser.stringToArray(msg)"]
B -->|List ArrayMsg| D["arrayMsg already present"]
C --> E["arrayMsg: List ArrayMsg"]
D --> E
E --> F["MessageConverser.arrayToPlainText(arrayMsg)"]
F --> G["Iterate items\nif type == MsgTypeEnum.text\nappend getStringData(text)"]
G --> H["plainText"]
H --> I["MessageEvent.plainText field"]
File-Level Changes
Possibly linked issues
Tips and commandsInteracting with Sourcery
Customizing Your Experience访问你的 dashboard 以:
Getting HelpOriginal review guide in EnglishReviewer's GuideAdds virtual-thread support to Shiro's task executor, introduces a derived plain-text view of messages, and updates message filtering to optionally match against plain text instead of the raw CQ-coded content, along with tests and a version bump. Sequence diagram for message filtering with optional plain-text matchingsequenceDiagram
actor PluginDeveloper
participant AnyMessageEvent as AnyMessageEvent
participant MessageEvent as MessageEvent
participant MessageConverser as MessageConverser
participant CommonUtils as CommonUtils
participant RegexUtils as RegexUtils
PluginDeveloper->>AnyMessageEvent: send CQ-coded message
AnyMessageEvent->>MessageEvent: construct from JSON message
activate MessageEvent
MessageEvent->>MessageConverser: stringToArray(message)
MessageConverser-->>MessageEvent: List ArrayMsg
MessageEvent->>MessageConverser: arrayToPlainText(arrayMsg)
MessageConverser-->>MessageEvent: plainText
deactivate MessageEvent
AnyMessageEvent->>CommonUtils: allFilterCheck(event, selfId, filter)
activate CommonUtils
CommonUtils->>CommonUtils: msgExtract(event.getMessage(), event.getArrayMsg(), filter.at(), event.getSelfId())
CommonUtils-->>CommonUtils: rawMessage
alt filter.matchPlainText() is true
CommonUtils->>RegexUtils: matcher(filter.cmd(), event.getPlainText())
else filter.matchPlainText() is false
CommonUtils->>RegexUtils: matcher(filter.cmd(), rawMessage)
end
RegexUtils-->>CommonUtils: Optional Matcher
CommonUtils-->>AnyMessageEvent: CheckResult
deactivate CommonUtils
Class diagram for updated message processing and filteringclassDiagram
class MessageEvent {
- String message
- String rawMessage
- List~ArrayMsg~ arrayMsg
- String plainText
+ String getMessage()
+ String getRawMessage()
+ List~ArrayMsg~ getArrayMsg()
+ String getPlainText()
- void setMessageFromJson(JsonNode json)
}
class MessageConverser {
- MessageConverser()
+ String arraysToString(List~ArrayMsg~ array)
+ List~ArrayMsg~ stringToArray(String msg)
+ String arrayToPlainText(List~ArrayMsg~ array)
+ String stringToPlainText(String msg)
- void addTextMsg(List~ArrayMsg~ chain, String text)
}
class ArrayMsg {
+ MsgTypeEnum getType()
+ String toCQCode()
+ String getStringData(String key)
}
class MsgTypeEnum {
<<enumeration>>
text
image
other
}
class MessageHandlerFilter {
<<annotation>>
+ boolean at() default false
+ String cmd() default ""
+ boolean invert() default false
+ boolean matchPlainText() default true
}
class CommonUtils {
+ CheckResult allFilterCheck(MessageEvent event, long selfId, MessageHandlerFilter filter)
- CheckResult filterCheck(MessageEvent event, long selfId, MessageHandlerFilter filter)
- String msgExtract(String message, List~ArrayMsg~ arrayMsg, boolean at, long selfId)
}
class RegexUtils {
+ Optional~Matcher~ matcher(String pattern, String input)
}
class ShiroTaskPoolConfig {
- TaskPoolProperties taskPoolProperties
+ ShiroTaskPoolConfig(TaskPoolProperties taskPoolProperties)
+ ThreadPoolTaskExecutor shiroTaskPlatformExecutor()
+ ThreadPoolTaskExecutor shiroTaskVirtualExecutor()
- ThreadPoolTaskExecutor getThreadPoolTaskExecutor(ThreadFactory factory)
}
class TaskPoolProperties {
+ int getCorePoolSize()
+ int getMaxPoolSize()
+ int getQueueCapacity()
+ String getThreadNamePrefix()
+ int getKeepAliveSeconds()
+ boolean isWaitForTasksToCompleteOnShutdown()
+ int getAwaitTerminationSeconds()
}
MessageEvent --> MessageConverser : uses
MessageEvent --> ArrayMsg : contains
ArrayMsg --> MsgTypeEnum : typed_by
CommonUtils --> MessageEvent : uses
CommonUtils --> MessageHandlerFilter : uses
CommonUtils --> RegexUtils : uses
ShiroTaskPoolConfig --> TaskPoolProperties : depends_on
ShiroTaskPoolConfig --> ThreadPoolTaskExecutor : produces
Flow diagram for conversion from CQ-coded message to plain-textflowchart TD
A["Incoming message string or array"] --> B{Type}
B -->|String| C["MessageConverser.stringToArray(msg)"]
B -->|List ArrayMsg| D["arrayMsg already present"]
C --> E["arrayMsg: List ArrayMsg"]
D --> E
E --> F["MessageConverser.arrayToPlainText(arrayMsg)"]
F --> G["Iterate items\nif type == MsgTypeEnum.text\nappend getStringData(text)"]
G --> H["plainText"]
H --> I["MessageEvent.plainText field"]
File-Level Changes
Possibly linked issues
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
There was a problem hiding this comment.
Hey - 我发现了两个问题,并给出了一些整体性的反馈:
- 将
MessageConverser.arraysToString中的条件从if (MsgTypeEnum.text.equals(item.getType()))改成if (!MsgTypeEnum.text.equals(item.getType()))实际上反转了原有行为,而且看起来并非有意为之:现在把“非文本”当作默认分支处理;请确认这一改动是否是刻意设计的,如不是,请恢复原始条件。 - 在
MessageConverser.arrayToPlainText中,建议使用MsgTypeEnum.text.toString()(或一个共享常量)来代替getStringData中硬编码的 "text" 字面量,以保持基于枚举的 key 使用方式一致,降低将来枚举名变更时引发问题的风险。
给 AI Agent 的提示词
请根据本次代码审查中的评论进行修改:
## 总体评论
- 将 `MessageConverser.arraysToString` 中的条件从 `if (MsgTypeEnum.text.equals(item.getType()))` 改成 `if (!MsgTypeEnum.text.equals(item.getType()))` 实际上反转了原有行为,而且看起来并非有意为之:现在把“非文本”当作默认分支处理;请确认这一改动是否是刻意设计的,如不是,请恢复原始条件。
- 在 `MessageConverser.arrayToPlainText` 中,建议使用 `MsgTypeEnum.text.toString()`(或一个共享常量)来代替 `getStringData` 中硬编码的 "text" 字面量,以保持基于枚举的 key 使用方式一致,降低将来枚举名变更时引发问题的风险。
## 逐条评论
### Comment 1
<location> `src/main/java/com/mikuac/shiro/common/utils/MessageConverser.java:21-24` </location>
<code_context>
public static String arraysToString(List<ArrayMsg> array) {
StringBuilder builder = new StringBuilder();
for (ArrayMsg item : array) {
- if (MsgTypeEnum.text.equals(item.getType())) {
+ if (!MsgTypeEnum.text.equals(item.getType())) {
builder.append(item.toCQCode());
</code_context>
<issue_to_address>
**suggestion:** 避免为 key 硬编码 "text" 字面量,改为复用枚举值以保持一致性。
该方法在此处使用 `item.getStringData("text")`,但在类的其他位置却使用 `item.getStringData(MsgTypeEnum.text.toString())`。一旦枚举名或底层 key 发生变化,这两处行为就会出现差异。建议这里也统一使用 `MsgTypeEnum.text.toString()`,从而在行为上保持一致,并在重构时更加健壮。
建议实现如下:
```java
public static String arraysToString(List<ArrayMsg> array) {
StringBuilder builder = new StringBuilder();
for (ArrayMsg item : array) {
if (!MsgTypeEnum.text.equals(item.getType())) {
builder.append(item.toCQCode());
} else {
builder.append(item.getStringData(MsgTypeEnum.text.toString()));
}
}
return builder.toString();
}
```
如果在 `MessageConverser.java`(或相关类)中还有其他 `item.getStringData("text")` 的使用位置,也建议一并替换为 `item.getStringData(MsgTypeEnum.text.toString())`,以保持行为一致,并在未来对枚举进行重构时更为稳健。
</issue_to_address>
### Comment 2
<location> `src/test/java/com/mikuac/shiro/common/utils/MessageConverserTest.java:183-188` </location>
<code_context>
assertEquals(originalResult, optimizedResult);
}
+ @Test
+ void testPlainMsgTest() {
+ val msg = "[CQ:at,qq=1122334455]测试消息1[CQ:face,id=1]测试消息2[CQ:video,file=https://test.com/1.mp4][CQ:image,file=test1.image,url=https://test.com/1.jpg]\n[CQ:image,file=test2.image,url=https://test.com/2.jpg]";
+ val expected = "测试消息1测试消息2\n";
+ val plainText = MessageConverser.stringToPlainText(msg);
+ assertEquals(expected, plainText);
+ }
+
</code_context>
<issue_to_address>
**suggestion (testing):** 为 `stringToPlainText` 和 `arrayToPlainText` 增加更多边界场景测试用例。
新的 `testPlainMsgTest` 覆盖了一个较复杂的场景,但这些工具方法还可以通过一些更加针对性的测试获益:
- 只包含 **CQ 码且没有任何文本** 的情况(纯文本结果应为空字符串)。
- 只包含 **纯文本** 且没有 CQ 码的情况,以验证这是一个 no-op。
- 文本 **前后包裹 CQ 码** 且使用不同分隔符(空格/换行)的情况,以验证格式是否正确保留。
- 直接针对 `arrayToPlainText(List<ArrayMsg>)` 编写单元测试,手动构造 `ArrayMsg` 实例,避免潜在问题被 `stringToArray` 的中间步骤掩盖。
这些测试有助于在你期望支持的主要消息模式下验证 `plainText` 的行为。
建议实现如下:
```java
@Test
void testPlainMsgTest() {
val msg = "[CQ:at,qq=1122334455]测试消息1[CQ:face,id=1]测试消息2[CQ:video,file=https://test.com/1.mp4][CQ:image,file=test1.image,url=https://test.com/1.jpg]\n[CQ:image,file=test2.image,url=https://test.com/2.jpg]";
val expected = "测试消息1测试消息2\n";
val plainText = MessageConverser.stringToPlainText(msg);
assertEquals(expected, plainText);
}
/**
* 仅包含 CQ 码、没有任何纯文本,结果应为空字符串
*/
@Test
void testStringToPlainTextOnlyCQCodes() {
val msg = "[CQ:at,qq=1122334455][CQ:face,id=1][CQ:image,file=test.image,url=https://test.com/1.jpg]";
val expected = "";
val plainText = MessageConverser.stringToPlainText(msg);
assertEquals(expected, plainText);
}
/**
* 仅包含纯文本,stringToPlainText 应为 no-op
*/
@Test
void testStringToPlainTextOnlyText() {
val msg = "这是一个纯文本消息 without any CQ codes.\n第二行文本。";
val expected = msg;
val plainText = MessageConverser.stringToPlainText(msg);
assertEquals(expected, plainText);
}
/**
* 文本前后存在 CQ 码与不同分隔符,确保格式(空格/换行)被保留
*/
@Test
void testStringToPlainTextLeadingAndTrailingCQCodes() {
val msg = "[CQ:at,qq=1122334455] 前置文本 [CQ:face,id=1]\n后置文本 [CQ:image,file=test.image,url=https://test.com/1.jpg]";
// 预期只去掉 CQ 码,本身的空格和换行应当保留
val expected = " 前置文本 \n后置文本 ";
val plainText = MessageConverser.stringToPlainText(msg);
assertEquals(expected, plainText);
}
/**
* 直接使用手动构造的 ArrayMsg 列表测试 arrayToPlainText,
* 避免 stringToArray 中间步骤掩盖潜在问题
*/
@Test
void testArrayToPlainTextDirectly() {
val arrayMsgs = new java.util.ArrayList<ArrayMsg>();
// 非文本类型(如 at),不应该出现在 plain text 中
{
val atMsg = new ArrayMsg();
atMsg.setType("at");
atMsg.setText(null);
arrayMsgs.add(atMsg);
}
// 第一段纯文本
{
val textMsg1 = new ArrayMsg();
textMsg1.setType("text");
textMsg1.setText("测试消息1");
arrayMsgs.add(textMsg1);
}
// 表情类 CQ 码,假设不参与 plain text 输出
{
val faceMsg = new ArrayMsg();
faceMsg.setType("face");
faceMsg.setText(null);
arrayMsgs.add(faceMsg);
}
// 第二段纯文本(带换行)
{
val textMsg2 = new ArrayMsg();
textMsg2.setType("text");
textMsg2.setText("测试消息2\n");
arrayMsgs.add(textMsg2);
}
// 图片/视频等富媒体消息
{
val imageMsg = new ArrayMsg();
imageMsg.setType("image");
imageMsg.setText(null);
arrayMsgs.add(imageMsg);
}
val expected = "测试消息1测试消息2\n";
val plainText = MessageConverser.arrayToPlainText(arrayMsgs);
assertEquals(expected, plainText);
}
@Test
void rawToArrayMsg2Test() {
```
1. 确认 `ArrayMsg` 类的包名和 API:
- 如果当前测试文件尚未导入 `ArrayMsg`,需要在文件顶部添加正确的 `import`,如 `import com.mikuac.shiro.dto.event.message.ArrayMsg;`(请根据实际项目结构调整)。
- 如果 `ArrayMsg` 不是通过无参构造 + `setType`/`setText` 方式使用,而是有构造函数或 builder,请将 `new ArrayMsg(); setType(...); setText(...);` 改成项目中实际使用的构建方式。
2. 如果 `arrayToPlainText` 的语义与这里的假设不同(例如会保留某些非 text 类型的内容,或对换行有特殊处理),请同步调整各测试中的 `expected` 字符串,以匹配实际行为。
</issue_to_address>帮我变得更有用!请在每条评论上点击 👍 或 👎,我会根据你的反馈改进后续的代码审查。
Original comment in English
Hey - I've found 2 issues, and left some high level feedback:
- The change in
MessageConverser.arraysToStringfromif (MsgTypeEnum.text.equals(item.getType()))toif (!MsgTypeEnum.text.equals(item.getType()))inverts the previous behavior and looks unintended, as it now treats non-text as the default branch; please verify and restore the original condition if this wasn’t deliberate. - In
MessageConverser.arrayToPlainText, consider usingMsgTypeEnum.text.toString()(or a shared constant) instead of the hard-coded "text" literal forgetStringDatato keep the enum-based key usage consistent and reduce risk of breakage if the enum name changes.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- The change in `MessageConverser.arraysToString` from `if (MsgTypeEnum.text.equals(item.getType()))` to `if (!MsgTypeEnum.text.equals(item.getType()))` inverts the previous behavior and looks unintended, as it now treats non-text as the default branch; please verify and restore the original condition if this wasn’t deliberate.
- In `MessageConverser.arrayToPlainText`, consider using `MsgTypeEnum.text.toString()` (or a shared constant) instead of the hard-coded "text" literal for `getStringData` to keep the enum-based key usage consistent and reduce risk of breakage if the enum name changes.
## Individual Comments
### Comment 1
<location> `src/main/java/com/mikuac/shiro/common/utils/MessageConverser.java:21-24` </location>
<code_context>
public static String arraysToString(List<ArrayMsg> array) {
StringBuilder builder = new StringBuilder();
for (ArrayMsg item : array) {
- if (MsgTypeEnum.text.equals(item.getType())) {
+ if (!MsgTypeEnum.text.equals(item.getType())) {
builder.append(item.toCQCode());
</code_context>
<issue_to_address>
**suggestion:** Avoid hardcoded "text" literal for key and reuse the enum value for consistency.
This method mixes `item.getStringData("text")` with `item.getStringData(MsgTypeEnum.text.toString())` used elsewhere in the class. If the enum name or underlying key changes, this will behave differently. Use `MsgTypeEnum.text.toString()` here as well to keep behavior consistent and robust against refactors.
Suggested implementation:
```java
public static String arraysToString(List<ArrayMsg> array) {
StringBuilder builder = new StringBuilder();
for (ArrayMsg item : array) {
if (!MsgTypeEnum.text.equals(item.getType())) {
builder.append(item.toCQCode());
} else {
builder.append(item.getStringData(MsgTypeEnum.text.toString()));
}
}
return builder.toString();
}
```
If there are any other occurrences of `item.getStringData("text")` elsewhere in `MessageConverser.java` (or related classes), they should also be updated to `item.getStringData(MsgTypeEnum.text.toString())` to keep the behavior consistent and robust against future enum refactors.
</issue_to_address>
### Comment 2
<location> `src/test/java/com/mikuac/shiro/common/utils/MessageConverserTest.java:183-188` </location>
<code_context>
assertEquals(originalResult, optimizedResult);
}
+ @Test
+ void testPlainMsgTest() {
+ val msg = "[CQ:at,qq=1122334455]测试消息1[CQ:face,id=1]测试消息2[CQ:video,file=https://test.com/1.mp4][CQ:image,file=test1.image,url=https://test.com/1.jpg]\n[CQ:image,file=test2.image,url=https://test.com/2.jpg]";
+ val expected = "测试消息1测试消息2\n";
+ val plainText = MessageConverser.stringToPlainText(msg);
+ assertEquals(expected, plainText);
+ }
+
</code_context>
<issue_to_address>
**suggestion (testing):** Add more edge-case coverage for `stringToPlainText` and `arrayToPlainText`.
The new `testPlainMsgTest` covers one complex scenario, but the utilities would benefit from a few more targeted tests:
- A case with **only CQ codes and no text** (plain text should be an empty string).
- A case with **only text** and no CQ codes to confirm it’s a no-op.
- A case with **leading/trailing CQ codes around text** with different separators (spaces/newlines) to confirm formatting.
- A direct unit test for `arrayToPlainText(List<ArrayMsg>)` using manually constructed `ArrayMsg` instances, so issues aren’t hidden by `stringToArray`.
These will help validate `plainText` across the main message patterns you expect to handle.
Suggested implementation:
```java
@Test
void testPlainMsgTest() {
val msg = "[CQ:at,qq=1122334455]测试消息1[CQ:face,id=1]测试消息2[CQ:video,file=https://test.com/1.mp4][CQ:image,file=test1.image,url=https://test.com/1.jpg]\n[CQ:image,file=test2.image,url=https://test.com/2.jpg]";
val expected = "测试消息1测试消息2\n";
val plainText = MessageConverser.stringToPlainText(msg);
assertEquals(expected, plainText);
}
/**
* 仅包含 CQ 码、没有任何纯文本,结果应为空字符串
*/
@Test
void testStringToPlainTextOnlyCQCodes() {
val msg = "[CQ:at,qq=1122334455][CQ:face,id=1][CQ:image,file=test.image,url=https://test.com/1.jpg]";
val expected = "";
val plainText = MessageConverser.stringToPlainText(msg);
assertEquals(expected, plainText);
}
/**
* 仅包含纯文本,stringToPlainText 应为 no-op
*/
@Test
void testStringToPlainTextOnlyText() {
val msg = "这是一个纯文本消息 without any CQ codes.\n第二行文本。";
val expected = msg;
val plainText = MessageConverser.stringToPlainText(msg);
assertEquals(expected, plainText);
}
/**
* 文本前后存在 CQ 码与不同分隔符,确保格式(空格/换行)被保留
*/
@Test
void testStringToPlainTextLeadingAndTrailingCQCodes() {
val msg = "[CQ:at,qq=1122334455] 前置文本 [CQ:face,id=1]\n后置文本 [CQ:image,file=test.image,url=https://test.com/1.jpg]";
// 预期只去掉 CQ 码,本身的空格和换行应当保留
val expected = " 前置文本 \n后置文本 ";
val plainText = MessageConverser.stringToPlainText(msg);
assertEquals(expected, plainText);
}
/**
* 直接使用手动构造的 ArrayMsg 列表测试 arrayToPlainText,
* 避免 stringToArray 中间步骤掩盖潜在问题
*/
@Test
void testArrayToPlainTextDirectly() {
val arrayMsgs = new java.util.ArrayList<ArrayMsg>();
// 非文本类型(如 at),不应该出现在 plain text 中
{
val atMsg = new ArrayMsg();
atMsg.setType("at");
atMsg.setText(null);
arrayMsgs.add(atMsg);
}
// 第一段纯文本
{
val textMsg1 = new ArrayMsg();
textMsg1.setType("text");
textMsg1.setText("测试消息1");
arrayMsgs.add(textMsg1);
}
// 表情类 CQ 码,假设不参与 plain text 输出
{
val faceMsg = new ArrayMsg();
faceMsg.setType("face");
faceMsg.setText(null);
arrayMsgs.add(faceMsg);
}
// 第二段纯文本(带换行)
{
val textMsg2 = new ArrayMsg();
textMsg2.setType("text");
textMsg2.setText("测试消息2\n");
arrayMsgs.add(textMsg2);
}
// 图片/视频等富媒体消息
{
val imageMsg = new ArrayMsg();
imageMsg.setType("image");
imageMsg.setText(null);
arrayMsgs.add(imageMsg);
}
val expected = "测试消息1测试消息2\n";
val plainText = MessageConverser.arrayToPlainText(arrayMsgs);
assertEquals(expected, plainText);
}
@Test
void rawToArrayMsg2Test() {
```
1. 确认 `ArrayMsg` 类的包名和 API:
- 如果当前测试文件尚未导入 `ArrayMsg`,需要在文件顶部添加正确的 `import`,如 `import com.mikuac.shiro.dto.event.message.ArrayMsg;`(请根据实际项目结构调整)。
- 如果 `ArrayMsg` 不是通过无参构造 + `setType`/`setText` 方式使用,而是有构造函数或 builder,请将 `new ArrayMsg(); setType(...); setText(...);` 改成项目中实际使用的构建方式。
2. 如果 `arrayToPlainText` 的语义与这里的假设不同(例如会保留某些非 text 类型的内容,或对换行有特殊处理),请同步调整各测试中的 `expected` 字符串,以匹配实际行为。
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
MisakaTAT
reviewed
Jan 27, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
在以下配置中进行设置,即可开启虚拟线程池
新增的
plainText字段将利于消息过滤匹配,以及开发者提取纯文本消息Summary by Sourcery
为纯文本消息提取和可配置的正则匹配方式提供支持(可选择对纯文本或完整消息进行匹配),并为 Shiro 任务新增独立的平台线程池和虚拟线程池执行器。
New Features:
plainText字段,从解析后的消息数组中派生而来,用于更方便地获取纯文本内容。Enhancements:
ArrayMsg元素正确重建消息字符串。Build:
2.5.3升级到2.5.5。Tests:
stringToPlainText能够从包含混合 CQ 码的消息中正确提取出仅包含文本的部分。Original summary in English
Summary by Sourcery
Add support for plain-text message extraction and configurable regex matching against plain text or full messages, and introduce separate platform and virtual thread pool executors for Shiro tasks.
New Features:
Enhancements:
Build:
Tests: