Skip to content

Conversation

@MatheMatrix
Copy link
Owner

sync from gitlab !9066

@coderabbitai
Copy link

coderabbitai bot commented Jan 26, 2026

总览

新增问候语管理功能模块,包括数据库表定义、REST API端点(创建、删除、查询问候语)、消息处理框架、数据持久化层及其配置。涵盖数据库模式、API消息事件类、业务实现、RBAC权限配置和集成测试。

变更

功能模块 / 文件 变更说明
数据库迁移
conf/db/upgrade/V4.10.26__schema.sql
创建 GreetingVO 表,包含uuid(主键)、greeting、lastOpDate、createDate字段,使用InnoDB引擎,utf8字符集
API消息与事件类
plugin/helloworld/src/main/java/org/zstack/plugin/example/APIHelloWorldMsg.javaAPIHelloWorldEvent.javaAPICreateGreetingMsg.javaAPICreateGreetingEvent.javaAPIDeleteGreetingMsg.javaAPIDeleteGreetingEvent.java
新增6个API类:支持GET /helloworld/greetings(查询)、POST /helloworld/greetings(创建)、DELETE /helloworld/greetings/{uuid}(删除)REST端点;包含对应的事件响应类和消息参数类
消息接口与基础类
plugin/helloworld/src/main/java/org/zstack/plugin/example/Greeting.javaGreetingMessage.java
定义 Greeting 接口(handleMessage方法)和 GreetingMessage 接口(getGreetingUuid方法),作为消息处理的契约
业务实现类
plugin/helloworld/src/main/java/org/zstack/plugin/example/GreetingBase.javaHelloWorldManagerImpl.java
GreetingBase:处理单个问候语的删除操作;HelloWorldManagerImpl:服务主类,实现消息路由、API处理(创建/查询)、数据库持久化、事件发布
数据映射与持久化
plugin/helloworld/src/main/java/org/zstack/plugin/example/GreetingVO.javaGreetingVO_.javaGreetingInventory.java
GreetingVO:JPA实体类,映射数据库表;GreetingVO_:JPA静态元模型类;GreetingInventory:数据传输对象,提供VO与Inventory间的转换
API拦截与权限
plugin/helloworld/src/main/java/org/zstack/plugin/example/HelloWorldApiInterceptor.javaHelloWorldManager.javaRBACInfo.java
HelloWorldApiInterceptor:拦截APIHelloWorldMsg进行参数验证;HelloWorldManager:定义service ID常量;RBACInfo:配置RBAC权限描述符
集成测试
test/src/test/groovy/org/zstack/test/integration/plugin/HelloWorld.groovy
新增Groovy测试类,测试完整工作流:创建问候语、验证数据库持久化、删除问候语、验证删除结果

时序图

sequenceDiagram
    participant Client
    participant API
    participant HelloWorldManagerImpl as HelloWorldManagerImpl<br/>(Service)
    participant DB as DatabaseFacade
    participant CloudBus
    
    Note over Client,CloudBus: 创建问候语流程
    Client->>API: POST /helloworld/greetings<br/>{greeting:"hello"}
    API->>HelloWorldManagerImpl: handle(APICreateGreetingMsg)
    HelloWorldManagerImpl->>DB: persist(GreetingVO)
    DB-->>HelloWorldManagerImpl: GreetingVO created
    HelloWorldManagerImpl->>CloudBus: publish(APICreateGreetingEvent)
    CloudBus-->>API: event
    API-->>Client: {inventory:{uuid,greeting,...}}
Loading
sequenceDiagram
    participant Client
    participant API
    participant HelloWorldManagerImpl as HelloWorldManagerImpl<br/>(Service)
    participant GreetingBase
    participant DB as DatabaseFacade
    participant CloudBus
    
    Note over Client,CloudBus: 删除问候语流程
    Client->>API: DELETE /helloworld/greetings/{uuid}
    API->>HelloWorldManagerImpl: handleMessage(APIDeleteGreetingMsg)
    HelloWorldManagerImpl->>HelloWorldManagerImpl: load GreetingVO by uuid
    HelloWorldManagerImpl->>GreetingBase: new GreetingBase(vo)
    HelloWorldManagerImpl->>GreetingBase: handleMessage(APIDeleteGreetingMsg)
    GreetingBase->>DB: remove(GreetingVO)
    DB-->>GreetingBase: removed
    GreetingBase->>CloudBus: publish(APIDeleteGreetingEvent)
    CloudBus-->>API: event
    API-->>Client: 200 OK
Loading

预估代码审查工作量

🎯 4 (复杂) | ⏱️ ~50 分钟

诗句

🐰 一声问好在数据库里,
创建、删除、查询皆有序,
REST端点闪闪发光,
消息在云间穿梭往返,
权限守护每一句问候!

🚥 Pre-merge checks | ✅ 1 | ❌ 2
❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive 标题'HelloWorld'过于宽泛且不具体,未能清晰传达此PR的主要变更内容。 建议使用更具体的标题,例如'Add HelloWorld plugin with greeting management API'或'Implement greeting management feature in helloworld plugin'。
✅ Passed checks (1 passed)
Check name Status Explanation
Description check ✅ Passed 描述'sync from gitlab !9066'与变更集相关,虽然缺乏具体细节,但确实表明了变更的来源。

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🤖 Fix all issues with AI agents
In
`@plugin/helloworld/src/main/java/org/zstack/plugin/example/APICreateGreetingMsg.java`:
- Around line 9-21: APICreateGreetingMsg is missing a static __example__ method
used by the API doc generator; add a public static APICreateGreetingMsg
__example__() method that constructs and returns an instance with the greeting
field populated (e.g., new APICreateGreetingMsg().setGreeting(...)), ensuring
the method uses the class APICreateGreetingMsg and the greeting accessor/mutator
(getGreeting/setGreeting) to produce a representative example object for docs
and templates.

In
`@plugin/helloworld/src/main/java/org/zstack/plugin/example/GreetingMessage.java`:
- Around line 3-4: 为 GreetingMessage 接口的 getGreetingUuid() 方法补充 Javadoc
注释,说明该方法的用途(返回哪个实体的 UUID 或该 UUID 的语义)、返回值类型及含义(非空/可能为
null)、以及任何使用或约束(例如格式或生命周期);在 GreetingMessage 接口中为 getGreetingUuid() 添加完整的 /**
... */ 注释,包含简短描述、@return 说明以及必要时的 `@see` 或 `@throws` 注记以符合项目编码规范。

In
`@plugin/helloworld/src/main/java/org/zstack/plugin/example/HelloWorldManagerImpl.java`:
- Around line 60-69: In handle(APICreateGreetingMsg msg) the new GreetingVO is
being persisted with dbf.updateAndRefresh(vo) which is for existing entities;
replace that call with dbf.persistAndRefresh(vo) so the newly constructed
GreetingVO (created in GreetingVO vo = new GreetingVO(); and populated via
vo.setUuid(...) and vo.setGreeting(...)) is correctly saved and refreshed before
creating APICreateGreetingEvent and publishing via bus.publish(evt).

In `@test/src/test/groovy/org/zstack/test/integration/plugin/HelloWorld.groovy`:
- Around line 148-150: The post-delete assertion is inverted: after calling
deleteGreeting you should assert the GreetingVO no longer exists. Replace the
existing assertion that checks Q.New(GreetingVO).eq(GreetingVO_.greeting,
"qiuyu-12/18").isExists() with a non-existence check (e.g., assert
!Q.New(GreetingVO).eq(GreetingVO_.greeting, "qiuyu-12/18").isExists() or an
assertFalse variant) so the test fails if the record remains after
deleteGreeting.
- Line 1: The package declaration in HelloWorld.groovy does not match its
intended module (class/file HelloWorld); update the package statement from
org.zstack.test.integration to org.zstack.test.integration.plugin so the package
declaration aligns with the HelloWorld.groovy file location and class namespace.
🧹 Nitpick comments (8)
plugin/helloworld/src/main/java/org/zstack/plugin/example/Greeting.java (1)

5-7: 接口方法缺少 Javadoc 注释。

根据编码规范,接口方法必须配有有效的 Javadoc 注释。建议为 handleMessage 方法添加文档说明其用途和参数含义。

📝 建议添加 Javadoc
 public interface Greeting {
+    /**
+     * 处理与 Greeting 资源相关的消息
+     *
+     * `@param` msg 待处理的消息
+     */
     void handleMessage(Message msg);
 }
plugin/helloworld/src/main/java/org/zstack/plugin/example/GreetingInventory.java (1)

26-28: 为集合映射加空值防护。
Line 26 若 vos 可能为 null 会触发 NPE,可考虑返回空集合或显式约束非空。

♻️ 建议改动
 public static List<GreetingInventory> valueOf(Collection<GreetingVO> vos) {
-    return vos.stream().map(GreetingInventory::valueOf).collect(Collectors.toList());
+    if (vos == null) {
+        return java.util.Collections.emptyList();
+    }
+    return vos.stream().map(GreetingInventory::valueOf).collect(Collectors.toList());
 }
plugin/helloworld/src/main/java/org/zstack/plugin/example/APIHelloWorldMsg.java (1)

8-11: 缺少 __example__ 方法

根据编码规范,API 类需要实现 __example__ 方法以便生成 API 文档。此外,@APIParam 未设置 emptyString = false,但 HelloWorldApiInterceptor 中又对空字符串进行了校验,建议在此处统一使用 @APIParam(maxLength = 255, emptyString = false) 进行声明式校验,避免重复逻辑。

建议添加 __example__ 方法并完善 `@APIParam`
 `@RestRequest`(path = "/helloworld/greetings", method = HttpMethod.GET, responseClass = APIHelloWorldEvent.class)
 public class APIHelloWorldMsg extends APIMessage {
-    `@APIParam`(maxLength = 255)
+    `@APIParam`(maxLength = 255, emptyString = false)
     private String greeting;

     public String getGreeting() {
         return greeting;
     }

     public void setGreeting(String greeting) {
         this.greeting = greeting;
     }
+
+    public static APIHelloWorldMsg __example__() {
+        APIHelloWorldMsg msg = new APIHelloWorldMsg();
+        msg.setGreeting("Hello, World!");
+        return msg;
+    }
 }
plugin/helloworld/src/main/java/org/zstack/plugin/example/HelloWorldApiInterceptor.java (1)

10-22: 建议扩展拦截器覆盖其他 API 消息

当前拦截器仅处理 APIHelloWorldMsg,但同一插件中还有 APICreateGreetingMsgAPIDeleteGreetingMsg。建议统一在此拦截器中添加对这些消息的校验逻辑,例如校验 APIDeleteGreetingMsguuid 是否存在等。

另外,greeting 的非空校验可以直接通过 @APIParam(emptyString = false) 在消息类中声明,减少此处的重复校验逻辑。

建议扩展拦截器
     `@Override`
     public APIMessage intercept(APIMessage msg) throws ApiMessageInterceptionException {
         if (msg instanceof APIHelloWorldMsg) {
             validate((APIHelloWorldMsg) msg);
+        } else if (msg instanceof APIDeleteGreetingMsg) {
+            validate((APIDeleteGreetingMsg) msg);
         }

         return msg;
     }

     private void validate(APIHelloWorldMsg msg) {
         if (msg.getGreeting() == null || msg.getGreeting().isEmpty()) {
             throw new ApiMessageInterceptionException(argerr("greeting cannot be null or empty"));
         }
     }
+
+    private void validate(APIDeleteGreetingMsg msg) {
+        if (msg.getUuid() == null || msg.getUuid().isEmpty()) {
+            throw new ApiMessageInterceptionException(argerr("uuid cannot be null or empty"));
+        }
+    }
 }
plugin/helloworld/src/main/java/org/zstack/plugin/example/APIDeleteGreetingMsg.java (1)

7-22: uuid 字段缺少 @APIParam 注解,且缺少 __example__ 方法

uuid 字段作为删除操作的关键参数,应添加 @APIParam 注解进行校验。同时,根据编码规范,API 消息类需要实现 __example__ 方法。

建议添加注解和示例方法
+import org.zstack.header.message.APIParam;
+
 `@RestRequest`(path = "/helloworld/greetings/{uuid}", method = HttpMethod.DELETE, responseClass = APIDeleteGreetingEvent.class)
 public class APIDeleteGreetingMsg extends APIDeleteMessage implements GreetingMessage {
+    `@APIParam`(resourceType = GreetingVO.class, emptyString = false)
     private String uuid;

     public String getUuid() {
         return uuid;
     }

     public void setUuid(String uuid) {
         this.uuid = uuid;
     }

     `@Override`
     public String getGreetingUuid() {
         return uuid;
     }
+
+    public static APIDeleteGreetingMsg __example__() {
+        APIDeleteGreetingMsg msg = new APIDeleteGreetingMsg();
+        msg.setUuid(uuid());
+        return msg;
+    }
 }
plugin/helloworld/src/main/java/org/zstack/plugin/example/GreetingBase.java (1)

45-49: 删除操作缺少结果验证

dbf.remove(self) 执行后未验证删除是否成功。虽然在大多数情况下不会失败,但建议考虑添加防御性检查,或确保上层调用者能够感知到潜在的删除失败情况。

test/src/test/groovy/org/zstack/test/integration/plugin/HelloWorld.groovy (1)

128-151: 日志消息建议使用英文保持一致性

测试代码中的日志消息使用了中文,建议统一使用英文,以保持代码库的一致性和可维护性。

plugin/helloworld/src/main/java/org/zstack/plugin/example/HelloWorldManagerImpl.java (1)

71-77: 日志输出可优化

第 72 行直接打印 msg 对象,建议打印更有意义的信息如 msg.getGreeting(),提高日志可读性。

优化日志输出
     private void handle(APIHelloWorldMsg msg) {
-        logger.debug(String.format("say hello: %s", msg));
+        logger.debug(String.format("say hello: %s", msg.getGreeting()));

         APIHelloWorldEvent evt = new APIHelloWorldEvent(msg.getId());
         evt.setGreeting(msg.getGreeting());
         bus.publish(evt);
     }

Comment on lines +9 to +21
@RestRequest(path = "/helloworld/greetings", method = HttpMethod.POST, responseClass = APICreateGreetingEvent.class, parameterName = "params")
public class APICreateGreetingMsg extends APICreateMessage {
@APIParam(emptyString = false)
private String greeting;

public String getGreeting() {
return greeting;
}

public void setGreeting(String greeting) {
this.greeting = greeting;
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

缺少 __example__,API 文档生成会缺口。
Line 10 的 API 消息类需要提供 __example__ 以生成 API 文档与模板。

✅ 建议补充
 public class APICreateGreetingMsg extends APICreateMessage {
@@
     public void setGreeting(String greeting) {
         this.greeting = greeting;
     }
+
+    public static APICreateGreetingMsg __example__() {
+        APICreateGreetingMsg msg = new APICreateGreetingMsg();
+        msg.setGreeting("hello");
+        return msg;
+    }
 }
根据编码规范。
🤖 Prompt for AI Agents
In
`@plugin/helloworld/src/main/java/org/zstack/plugin/example/APICreateGreetingMsg.java`
around lines 9 - 21, APICreateGreetingMsg is missing a static __example__ method
used by the API doc generator; add a public static APICreateGreetingMsg
__example__() method that constructs and returns an instance with the greeting
field populated (e.g., new APICreateGreetingMsg().setGreeting(...)), ensuring
the method uses the class APICreateGreetingMsg and the greeting accessor/mutator
(getGreeting/setGreeting) to produce a representative example object for docs
and templates.

Comment on lines +3 to +4
public interface GreetingMessage {
String getGreetingUuid();
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

接口方法缺少 Javadoc。
Line 4 的接口方法需补充 Javadoc 说明用途与返回值。

💡 建议补充
 public interface GreetingMessage {
+    /**
+     * `@return` greeting UUID
+     */
     String getGreetingUuid();
 }
根据编码规范。
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public interface GreetingMessage {
String getGreetingUuid();
public interface GreetingMessage {
/**
* `@return` greeting UUID
*/
String getGreetingUuid();
}
🤖 Prompt for AI Agents
In
`@plugin/helloworld/src/main/java/org/zstack/plugin/example/GreetingMessage.java`
around lines 3 - 4, 为 GreetingMessage 接口的 getGreetingUuid() 方法补充 Javadoc
注释,说明该方法的用途(返回哪个实体的 UUID 或该 UUID 的语义)、返回值类型及含义(非空/可能为
null)、以及任何使用或约束(例如格式或生命周期);在 GreetingMessage 接口中为 getGreetingUuid() 添加完整的 /**
... */ 注释,包含简短描述、@return 说明以及必要时的 `@see` 或 `@throws` 注记以符合项目编码规范。

Comment on lines +60 to +69
private void handle(APICreateGreetingMsg msg) {
GreetingVO vo = new GreetingVO();
vo.setUuid(msg.getResourceUuid() == null ? Platform.getUuid() : msg.getResourceUuid());
vo.setGreeting(msg.getGreeting());
vo = dbf.updateAndRefresh(vo);

APICreateGreetingEvent evt = new APICreateGreetingEvent(msg.getId());
evt.setInventory(vo.toInventory());
bus.publish(evt);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

新建实体应使用 persistAndRefresh 而非 updateAndRefresh

第 64 行使用 dbf.updateAndRefresh(vo) 来持久化新创建的 GreetingVO 对象。对于新建实体,应使用 dbf.persistAndRefresh(vo)updateAndRefresh 通常用于更新已存在的实体,可能导致非预期行为。

修复建议
     private void handle(APICreateGreetingMsg msg) {
         GreetingVO vo = new GreetingVO();
         vo.setUuid(msg.getResourceUuid() == null ? Platform.getUuid() : msg.getResourceUuid());
         vo.setGreeting(msg.getGreeting());
-        vo = dbf.updateAndRefresh(vo);
+        vo = dbf.persistAndRefresh(vo);

         APICreateGreetingEvent evt = new APICreateGreetingEvent(msg.getId());
         evt.setInventory(vo.toInventory());
         bus.publish(evt);
     }
🤖 Prompt for AI Agents
In
`@plugin/helloworld/src/main/java/org/zstack/plugin/example/HelloWorldManagerImpl.java`
around lines 60 - 69, In handle(APICreateGreetingMsg msg) the new GreetingVO is
being persisted with dbf.updateAndRefresh(vo) which is for existing entities;
replace that call with dbf.persistAndRefresh(vo) so the newly constructed
GreetingVO (created in GreetingVO vo = new GreetingVO(); and populated via
vo.setUuid(...) and vo.setGreeting(...)) is correctly saved and refreshed before
creating APICreateGreetingEvent and publishing via bus.publish(evt).

@@ -0,0 +1,158 @@
package org.zstack.test.integration
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

包名与文件路径不一致

包声明为 org.zstack.test.integration,但根据文件路径 test/src/test/groovy/org/zstack/test/integration/plugin/HelloWorld.groovy,包名应为 org.zstack.test.integration.plugin

修复建议
-package org.zstack.test.integration
+package org.zstack.test.integration.plugin
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
package org.zstack.test.integration
package org.zstack.test.integration.plugin
🤖 Prompt for AI Agents
In `@test/src/test/groovy/org/zstack/test/integration/plugin/HelloWorld.groovy` at
line 1, The package declaration in HelloWorld.groovy does not match its intended
module (class/file HelloWorld); update the package statement from
org.zstack.test.integration to org.zstack.test.integration.plugin so the package
declaration aligns with the HelloWorld.groovy file location and class namespace.

Comment on lines +148 to +150
logger.info(String.format("------------------开始验证deleteGreeting是否成功----------------------"));
assert Q.New(GreetingVO).eq(GreetingVO_.greeting,"qiuyu-12/18").isExists()
logger.info(String.format("------------------deleteGreeting还是存在,代码错误----------------------"));
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

严重逻辑错误:删除验证断言条件错误

第 149 行的断言逻辑有误。删除操作后应验证记录不存在,但当前代码断言记录存在。这会导致测试在删除失败时仍然通过,无法有效验证删除功能。

修复断言逻辑
         logger.info(String.format("------------------开始验证deleteGreeting是否成功----------------------"));
-        assert Q.New(GreetingVO).eq(GreetingVO_.greeting,"qiuyu-12/18").isExists()
-        logger.info(String.format("------------------deleteGreeting还是存在,代码错误----------------------"));
+        assert !Q.New(GreetingVO).eq(GreetingVO_.greeting,"qiuyu-12/18").isExists()
+        logger.info(String.format("------------------deleteGreeting验证成功,记录已删除----------------------"));
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
logger.info(String.format("------------------开始验证deleteGreeting是否成功----------------------"));
assert Q.New(GreetingVO).eq(GreetingVO_.greeting,"qiuyu-12/18").isExists()
logger.info(String.format("------------------deleteGreeting还是存在,代码错误----------------------"));
logger.info(String.format("------------------开始验证deleteGreeting是否成功----------------------"));
assert !Q.New(GreetingVO).eq(GreetingVO_.greeting,"qiuyu-12/18").isExists()
logger.info(String.format("------------------deleteGreeting验证成功,记录已删除----------------------"));
🤖 Prompt for AI Agents
In `@test/src/test/groovy/org/zstack/test/integration/plugin/HelloWorld.groovy`
around lines 148 - 150, The post-delete assertion is inverted: after calling
deleteGreeting you should assert the GreetingVO no longer exists. Replace the
existing assertion that checks Q.New(GreetingVO).eq(GreetingVO_.greeting,
"qiuyu-12/18").isExists() with a non-existence check (e.g., assert
!Q.New(GreetingVO).eq(GreetingVO_.greeting, "qiuyu-12/18").isExists() or an
assertFalse variant) so the test fails if the record remains after
deleteGreeting.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants