This is an automated email from the ASF dual-hosted git repository.

Duansg pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/hertzbeat.git


The following commit(s) were added to refs/heads/master by this push:
     new bf3bb4fa45 Define bulletin write fields explicitly (#4292)
bf3bb4fa45 is described below

commit bf3bb4fa456bca7b1bb488d387784947005ed0b4
Author: Logic <[email protected]>
AuthorDate: Wed Aug 5 13:35:59 2026 +0800

    Define bulletin write fields explicitly (#4292)
    
    Co-authored-by: Duansg <[email protected]>
---
 .../hertzbeat/common/entity/manager/Bulletin.java  | 16 ++--
 .../manager/controller/BulletinController.java     |  4 +-
 .../hertzbeat/manager/service/BulletinService.java |  2 +-
 .../manager/service/impl/BulletinServiceImpl.java  | 22 +++++-
 .../manager/controller/BulletinControllerTest.java |  8 +-
 .../manager/service/BulletinServiceTest.java       | 85 ++++++++++++++++++++--
 6 files changed, 115 insertions(+), 22 deletions(-)

diff --git 
a/hertzbeat-common-spring/src/main/java/org/apache/hertzbeat/common/entity/manager/Bulletin.java
 
b/hertzbeat-common-spring/src/main/java/org/apache/hertzbeat/common/entity/manager/Bulletin.java
index b98082296f..f5ba5658b2 100644
--- 
a/hertzbeat-common-spring/src/main/java/org/apache/hertzbeat/common/entity/manager/Bulletin.java
+++ 
b/hertzbeat-common-spring/src/main/java/org/apache/hertzbeat/common/entity/manager/Bulletin.java
@@ -17,6 +17,7 @@
 
 package org.apache.hertzbeat.common.entity.manager;
 
+import static io.swagger.v3.oas.annotations.media.Schema.AccessMode.READ_ONLY;
 import static io.swagger.v3.oas.annotations.media.Schema.AccessMode.READ_WRITE;
 import io.swagger.v3.oas.annotations.media.Schema;
 import jakarta.persistence.Column;
@@ -27,6 +28,7 @@ import jakarta.persistence.GeneratedValue;
 import jakarta.persistence.GenerationType;
 import jakarta.persistence.Id;
 import jakarta.persistence.Table;
+import jakarta.persistence.UniqueConstraint;
 import java.time.LocalDateTime;
 import java.util.List;
 import java.util.Map;
@@ -50,12 +52,14 @@ import 
org.springframework.data.jpa.domain.support.AuditingEntityListener;
 @AllArgsConstructor
 @NoArgsConstructor
 @EntityListeners(AuditingEntityListener.class)
-@Table(name = "hzb_bulletin")
+@Table(name = "hzb_bulletin", uniqueConstraints = {
+        @UniqueConstraint(name = "uk_bulletin_name", columnNames = "name")
+})
 public class Bulletin {
 
     @Id
     @GeneratedValue(strategy = GenerationType.IDENTITY)
-    @Schema(description = "Bulletin ID", example = "1")
+    @Schema(description = "Bulletin ID", example = "1", accessMode = READ_ONLY)
     private Long id;
 
     @Schema(description = "Bulletin Name", example = "Bulletin1", accessMode = 
READ_WRITE)
@@ -74,19 +78,19 @@ public class Bulletin {
     @Convert(converter = JsonMapListAttributeConverter.class)
     private Map<String, List<String>> fields;
 
-    @Schema(title = "The creator of this record", example = "tom", accessMode 
= READ_WRITE)
+    @Schema(title = "The creator of this record", example = "tom", accessMode 
= READ_ONLY)
     @CreatedBy
     private String creator;
 
-    @Schema(title = "The modifier of this record", example = "tom", accessMode 
= READ_WRITE)
+    @Schema(title = "The modifier of this record", example = "tom", accessMode 
= READ_ONLY)
     @LastModifiedBy
     private String modifier;
 
-    @Schema(title = "Record create time", example = 
"2024-07-02T20:09:34.903217", accessMode = READ_WRITE)
+    @Schema(title = "Record create time", example = 
"2024-07-02T20:09:34.903217", accessMode = READ_ONLY)
     @CreatedDate
     private LocalDateTime gmtCreate;
 
-    @Schema(title = "Record modify time", example = 
"2024-07-02T20:09:34.903217", accessMode = READ_WRITE)
+    @Schema(title = "Record modify time", example = 
"2024-07-02T20:09:34.903217", accessMode = READ_ONLY)
     @LastModifiedDate
     private LocalDateTime gmtUpdate;
 }
diff --git 
a/hertzbeat-manager/src/main/java/org/apache/hertzbeat/manager/controller/BulletinController.java
 
b/hertzbeat-manager/src/main/java/org/apache/hertzbeat/manager/controller/BulletinController.java
index 3c8bddf6ca..ee0bc9fd5a 100644
--- 
a/hertzbeat-manager/src/main/java/org/apache/hertzbeat/manager/controller/BulletinController.java
+++ 
b/hertzbeat-manager/src/main/java/org/apache/hertzbeat/manager/controller/BulletinController.java
@@ -62,7 +62,7 @@ public class BulletinController {
     @PostMapping
     public ResponseEntity<Message<Void>> addNewBulletin(@Valid @RequestBody 
Bulletin bulletin) {
         try {
-            bulletinService.validate(bulletin);
+            bulletinService.validate(bulletin, false);
             bulletinService.addBulletin(bulletin);
         } catch (Exception e) {
             return ResponseEntity.ok(Message.fail(FAIL_CODE, "Add failed! " + 
e.getMessage()));
@@ -74,7 +74,7 @@ public class BulletinController {
     @PutMapping
     public ResponseEntity<Message<Void>> editBulletin(@Valid @RequestBody 
Bulletin bulletin) {
         try {
-            bulletinService.validate(bulletin);
+            bulletinService.validate(bulletin, true);
             bulletinService.editBulletin(bulletin);
         } catch (Exception e) {
             return ResponseEntity.ok(Message.fail(FAIL_CODE, "Edit failed! " + 
e.getMessage()));
diff --git 
a/hertzbeat-manager/src/main/java/org/apache/hertzbeat/manager/service/BulletinService.java
 
b/hertzbeat-manager/src/main/java/org/apache/hertzbeat/manager/service/BulletinService.java
index f69840e6f2..70d92c7c32 100644
--- 
a/hertzbeat-manager/src/main/java/org/apache/hertzbeat/manager/service/BulletinService.java
+++ 
b/hertzbeat-manager/src/main/java/org/apache/hertzbeat/manager/service/BulletinService.java
@@ -31,7 +31,7 @@ public interface BulletinService {
     /**
      * validate Bulletin
      */
-    void validate(Bulletin bulletin) throws IllegalArgumentException;
+    void validate(Bulletin bulletin, boolean isModify) throws 
IllegalArgumentException;
     
     /**
      * Get Bulletin by id
diff --git 
a/hertzbeat-manager/src/main/java/org/apache/hertzbeat/manager/service/impl/BulletinServiceImpl.java
 
b/hertzbeat-manager/src/main/java/org/apache/hertzbeat/manager/service/impl/BulletinServiceImpl.java
index f28cb309f9..0100421bf0 100644
--- 
a/hertzbeat-manager/src/main/java/org/apache/hertzbeat/manager/service/impl/BulletinServiceImpl.java
+++ 
b/hertzbeat-manager/src/main/java/org/apache/hertzbeat/manager/service/impl/BulletinServiceImpl.java
@@ -65,7 +65,7 @@ public class BulletinServiceImpl implements BulletinService {
      * validate Bulletin
      */
     @Override
-    public void validate(Bulletin bulletin) throws IllegalArgumentException {
+    public void validate(Bulletin bulletin, boolean isModify) throws 
IllegalArgumentException {
         if (bulletin == null) {
             throw new IllegalArgumentException("Bulletin cannot be null");
         }
@@ -78,8 +78,11 @@ public class BulletinServiceImpl implements BulletinService {
         if (bulletin.getMonitorIds() == null || 
bulletin.getMonitorIds().isEmpty()) {
             throw new IllegalArgumentException("Bulletin monitorIds cannot be 
null or empty");
         }
+        if (isModify && bulletin.getId() == null) {
+            throw new IllegalArgumentException("Bulletin id cannot be null 
when editing");
+        }
         Bulletin existBulletin = bulletinDao.findByName(bulletin.getName());
-        if (existBulletin != null && 
!existBulletin.getId().equals(bulletin.getId())) {
+        if (existBulletin != null && (!isModify || 
!existBulletin.getId().equals(bulletin.getId()))) {
             throw new IllegalArgumentException("Bulletin name duplicated");
         }
     }
@@ -94,7 +97,12 @@ public class BulletinServiceImpl implements BulletinService {
         if (optional.isEmpty()) {
             throw new IllegalArgumentException("Bulletin not found");
         }
-        bulletinDao.save(bulletin);
+        Bulletin storedBulletin = optional.get();
+        storedBulletin.setName(bulletin.getName());
+        storedBulletin.setMonitorIds(bulletin.getMonitorIds());
+        storedBulletin.setApp(bulletin.getApp());
+        storedBulletin.setFields(bulletin.getFields());
+        bulletinDao.save(storedBulletin);
     }
 
     /**
@@ -103,7 +111,13 @@ public class BulletinServiceImpl implements 
BulletinService {
     @Override
     @Transactional(rollbackFor = Exception.class)
     public void addBulletin(Bulletin bulletin) {
-        bulletinDao.save(bulletin);
+        Bulletin newBulletin = Bulletin.builder()
+                .name(bulletin.getName())
+                .monitorIds(bulletin.getMonitorIds())
+                .app(bulletin.getApp())
+                .fields(bulletin.getFields())
+                .build();
+        bulletinDao.save(newBulletin);
     }
 
     /**
diff --git 
a/hertzbeat-manager/src/test/java/org/apache/hertzbeat/manager/controller/BulletinControllerTest.java
 
b/hertzbeat-manager/src/test/java/org/apache/hertzbeat/manager/controller/BulletinControllerTest.java
index cc383f7229..2a520a3652 100644
--- 
a/hertzbeat-manager/src/test/java/org/apache/hertzbeat/manager/controller/BulletinControllerTest.java
+++ 
b/hertzbeat-manager/src/test/java/org/apache/hertzbeat/manager/controller/BulletinControllerTest.java
@@ -62,7 +62,7 @@ class BulletinControllerTest {
         Bulletin bulletinDto = new Bulletin();
         doAnswer(invocation -> {
             throw new IllegalArgumentException("Invalid bulletin");
-        }).when(bulletinService).validate(bulletinDto);
+        }).when(bulletinService).validate(bulletinDto, false);
 
         this.mockMvc.perform(MockMvcRequestBuilders.post("/api/bulletin")
                 .contentType("application/json")
@@ -72,7 +72,7 @@ class BulletinControllerTest {
 
         doAnswer(invocation -> {
             return null;
-        }).when(bulletinService).validate(bulletinDto);
+        }).when(bulletinService).validate(bulletinDto, false);
         doAnswer(invocation -> {
             return null;
         }).when(bulletinService).addBulletin(bulletinDto);
@@ -88,7 +88,7 @@ class BulletinControllerTest {
         Bulletin bulletinDto = new Bulletin();
         doAnswer(invocation -> {
             throw new IllegalArgumentException("Invalid bulletin");
-        }).when(bulletinService).validate(bulletinDto);
+        }).when(bulletinService).validate(bulletinDto, true);
 
         this.mockMvc.perform(MockMvcRequestBuilders.put("/api/bulletin")
                 .contentType("application/json")
@@ -98,7 +98,7 @@ class BulletinControllerTest {
 
         doAnswer(invocation -> {
             return null;
-        }).when(bulletinService).validate(bulletinDto);
+        }).when(bulletinService).validate(bulletinDto, true);
         doAnswer(invocation -> {
             return null;
         }).when(bulletinService).editBulletin(bulletinDto);
diff --git 
a/hertzbeat-manager/src/test/java/org/apache/hertzbeat/manager/service/BulletinServiceTest.java
 
b/hertzbeat-manager/src/test/java/org/apache/hertzbeat/manager/service/BulletinServiceTest.java
index dd9768347b..529199829e 100644
--- 
a/hertzbeat-manager/src/test/java/org/apache/hertzbeat/manager/service/BulletinServiceTest.java
+++ 
b/hertzbeat-manager/src/test/java/org/apache/hertzbeat/manager/service/BulletinServiceTest.java
@@ -24,6 +24,7 @@ import 
org.apache.hertzbeat.manager.service.impl.BulletinServiceImpl;
 import org.apache.hertzbeat.warehouse.store.realtime.RealTimeDataReader;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.ArgumentCaptor;
 import org.mockito.InjectMocks;
 import org.mockito.Mock;
 import org.mockito.junit.jupiter.MockitoExtension;
@@ -32,19 +33,24 @@ import org.springframework.data.domain.PageImpl;
 import org.springframework.data.domain.PageRequest;
 import org.springframework.data.jpa.domain.Specification;
 
+import java.time.LocalDateTime;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Optional;
 
 import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertSame;
 import static org.junit.jupiter.api.Assertions.assertThrows;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 /**
@@ -67,17 +73,17 @@ public class BulletinServiceTest {
     @Test
     public void testValidate() throws Exception {
         assertThrows(IllegalArgumentException.class, () -> {
-            bulletinService.validate(null);
+            bulletinService.validate(null, false);
         });
 
         assertThrows(IllegalArgumentException.class, () -> {
-            bulletinService.validate(new Bulletin());
+            bulletinService.validate(new Bulletin(), false);
         });
 
         assertThrows(IllegalArgumentException.class, () -> {
             Bulletin obj = new Bulletin();
             obj.setApp("app");
-            bulletinService.validate(obj);
+            bulletinService.validate(obj, false);
         });
 
         assertThrows(IllegalArgumentException.class, () -> {
@@ -87,7 +93,7 @@ public class BulletinServiceTest {
             Bulletin obj = new Bulletin();
             obj.setApp("app");
             obj.setFields(fields);
-            bulletinService.validate(obj);
+            bulletinService.validate(obj, false);
         });
 
         assertDoesNotThrow(() -> {
@@ -101,7 +107,7 @@ public class BulletinServiceTest {
             obj.setApp("app");
             obj.setFields(fields);
             obj.setMonitorIds(ids);
-            bulletinService.validate(obj);
+            bulletinService.validate(obj, false);
         });
     }
 
@@ -119,6 +125,75 @@ public class BulletinServiceTest {
         });
     }
 
+    @Test
+    void validateCreateRejectsDuplicateNameEvenWhenClientSubmitsExistingId() {
+        Bulletin stored = 
Bulletin.builder().id(7L).name("duplicate-name").build();
+        Bulletin submitted = Bulletin.builder()
+                .id(7L)
+                .name("duplicate-name")
+                .app("app")
+                .fields(Map.of("metric", List.of("field")))
+                .monitorIds(List.of(1L))
+                .build();
+        when(bulletinDao.findByName("duplicate-name")).thenReturn(stored);
+
+        assertThrows(IllegalArgumentException.class, () -> 
bulletinService.validate(submitted, false));
+    }
+
+    @Test
+    void addBulletinIgnoresClientManagedFields() {
+        Bulletin bulletin = new Bulletin();
+        bulletin.setId(7L);
+        bulletin.setCreator("submitted-creator");
+        bulletin.setModifier("submitted-modifier");
+        bulletin.setGmtCreate(LocalDateTime.of(2020, 1, 1, 0, 0));
+        bulletin.setGmtUpdate(LocalDateTime.of(2020, 1, 2, 0, 0));
+
+        bulletinService.addBulletin(bulletin);
+
+        ArgumentCaptor<Bulletin> saved = 
ArgumentCaptor.forClass(Bulletin.class);
+        verify(bulletinDao).save(saved.capture());
+        assertNull(saved.getValue().getId());
+        assertNull(saved.getValue().getCreator());
+        assertNull(saved.getValue().getModifier());
+        assertNull(saved.getValue().getGmtCreate());
+        assertNull(saved.getValue().getGmtUpdate());
+    }
+
+    @Test
+    void editBulletinPreservesStoredManagedFields() {
+        LocalDateTime createdAt = LocalDateTime.of(2020, 1, 1, 0, 0);
+        LocalDateTime updatedAt = LocalDateTime.of(2020, 1, 2, 0, 0);
+        Bulletin stored = Bulletin.builder()
+                .id(7L)
+                .name("old-name")
+                .creator("stored-creator")
+                .modifier("stored-modifier")
+                .gmtCreate(createdAt)
+                .gmtUpdate(updatedAt)
+                .build();
+        Bulletin submitted = Bulletin.builder()
+                .id(7L)
+                .name("new-name")
+                .creator("submitted-creator")
+                .modifier("submitted-modifier")
+                .gmtCreate(createdAt.minusYears(1))
+                .gmtUpdate(updatedAt.minusYears(1))
+                .build();
+        when(bulletinDao.findById(7L)).thenReturn(Optional.of(stored));
+
+        bulletinService.editBulletin(submitted);
+
+        ArgumentCaptor<Bulletin> saved = 
ArgumentCaptor.forClass(Bulletin.class);
+        verify(bulletinDao).save(saved.capture());
+        assertSame(stored, saved.getValue());
+        assertEquals("new-name", saved.getValue().getName());
+        assertEquals("stored-creator", saved.getValue().getCreator());
+        assertEquals("stored-modifier", saved.getValue().getModifier());
+        assertEquals(createdAt, saved.getValue().getGmtCreate());
+        assertEquals(updatedAt, saved.getValue().getGmtUpdate());
+    }
+
     @Test
     public void testGetBulletins() throws Exception {
         Bulletin bulletin = new Bulletin();


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to