Skip to content

[feature] Desensitize sensitive information(new) #11

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 6 commits into
base: master
Choose a base branch
from

Conversation

arvi18
Copy link

@arvi18 arvi18 commented Jul 31, 2025

What's changed?

Checklist

  • I have read the Contributing Guide
  • I have written the necessary doc or comment.
  • I have added the necessary unit tests and all cases have passed.

Add or update API

  • I have added the necessary e2e tests and all cases have passed.

Summary by CodeRabbit

  • New Features

    • Introduced a value object for notification recipients supporting multiple channels and enhanced API documentation.
    • Added automatic desensitization (masking) of phone numbers and emails in notification recipient data for improved privacy.
    • Implemented caching to restore original contact information when editing or testing notification recipients.
  • Bug Fixes

    • Ensured original contact details are correctly restored from cache during recipient edits and test message sends.
  • Tests

    • Added tests to verify phone and email desensitization and caching behavior.
  • Chores

    • Added Hutool utility library as a new project dependency.

Copy link

coderabbitai bot commented Jul 31, 2025

Walkthrough

This update introduces a new NoticeReceiverVO data transfer object to support multi-channel notification recipients, implements custom serializers for desensitizing emails and phone numbers with corresponding cache logic, and updates the controller and service layers to handle desensitization and restoration of sensitive data. Hutool utility library dependencies are also added.

Changes

Cohort / File(s) Change Summary
Dependency Management
pom.xml, hertzbeat-common/pom.xml
Added hutool-all dependency and version property to both project and module POM files.
Cache Enhancements
hertzbeat-common/src/main/java/org/apache/hertzbeat/common/cache/CacheFactory.java
Added a new cache instance and getter for desensitization mapping.
DTO Introduction
hertzbeat-common/src/main/java/org/apache/hertzbeat/common/entity/dto/vo/NoticeReceiverVO.java
Introduced NoticeReceiverVO class with multi-channel notification fields, validation, and auditing annotations.
Custom Serializers
hertzbeat-common/src/main/java/org/apache/hertzbeat/common/serialize/EmailDesensitizationSerializer.java, hertzbeat-common/src/main/java/org/apache/hertzbeat/common/serialize/PhoneDesensitizationSerializer.java
Added serializers for desensitizing email and phone fields during JSON serialization, with cache integration.
Serializer Tests
hertzbeat-common/src/test/java/org/apache/hertzbeat/common/serialize/EmailDesensitizationSerializerTest.java, hertzbeat-common/src/test/java/org/apache/hertzbeat/common/serialize/PhoneDesensitizationSerializerTest.java
Added unit tests for email and phone desensitization serializers, validating cache behavior.
Controller Update
hertzbeat-manager/src/main/java/org/apache/hertzbeat/manager/controller/NoticeConfigController.java
Changed the return type of getReceivers to use NoticeReceiverVO and added entity-to-VO mapping.
Service Logic Update
hertzbeat-manager/src/main/java/org/apache/hertzbeat/manager/service/impl/NoticeConfigServiceImpl.java
Enhanced methods to restore desensitized email and phone values from cache before saving or sending messages.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant Controller
    participant Service
    participant Cache
    participant Database

    Client->>Controller: GET /receivers
    Controller->>Service: getReceivers(name)
    Service->>Database: Query NoticeReceiver entities
    Database-->>Service: List<NoticeReceiver>
    Service->>Controller: List<NoticeReceiver>
    Controller->>Controller: Map to List<NoticeReceiverVO>
    Controller-->>Client: List<NoticeReceiverVO>

    Client->>Controller: POST /editReceiver (with desensitized email/phone)
    Controller->>Service: editReceiver(NoticeReceiver)
    Service->>Cache: Lookup original email/phone by (id+masked value)
    Cache-->>Service: Original email/phone (if exists)
    Service->>Database: Save NoticeReceiver with restored values
    Database-->>Service: Success
    Service-->>Controller: Success
    Controller-->>Client: Success
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~18 minutes

Poem

A rabbit hops with code in tow,
Masking emails, phones aglow—
Sensitive bytes now safely stashed,
In caches where old data’s cached.
New DTOs and tests appear,
With Hutool tools now drawing near,
Review complete—let’s give a cheer! 🐇✨

Note

⚡️ Unit Test Generation is now available in beta!

Learn more here, or try it out under "Finishing Touches" below.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch clone-e-branch

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai generate unit tests to generate unit tests for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Summary of Changes

Hello @arvi18, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request introduces a robust mechanism for sensitive data desensitization within the application, focusing on NoticeReceiver entities. It ensures that personal information like phone numbers and emails are masked when presented to users via the API, while maintaining the ability to access the original data internally for necessary operations. This enhances data privacy and security.

Highlights

  • Data Desensitization Implementation: Implemented a new feature to desensitize sensitive information, specifically phone numbers and email addresses, for NoticeReceiver entities when they are exposed via the API.
  • Custom Serialization Logic: Introduced custom Jackson JsonSerializer implementations (EmailDesensitizationSerializer and PhoneDesensitizationSerializer) to automatically apply desensitization rules during JSON serialization of NoticeReceiverVO objects.
  • Original Data Caching: Established a DESENSITIZATION_MAP_CACHE within CacheFactory to temporarily store the original, un-desensitized sensitive values. This allows the system to retrieve the full data when needed for internal operations, such as updating records or sending test notifications.
  • API and Service Layer Integration: Modified the NoticeConfigController to return NoticeReceiverVO objects, ensuring that sensitive data is desensitized when retrieved through the API. Correspondingly, NoticeConfigServiceImpl was updated to fetch original sensitive data from the cache before performing editReceiver or sendTestMsg operations.
  • New Dependency: Added the hutool-all library as a new dependency to the hertzbeat-common and root pom.xml files, leveraging its utility functions, particularly for phone number desensitization.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point in your pull request via creating an issue comment (i.e. comment on the pull request page) using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in issue comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments or fill out our survey to provide feedback.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link

refacto-test bot commented Jul 31, 2025

🤖 Refacto.AI: PR Summary & Reviewer Guide

🎯 1. The Goal (Why was this PR raised?)

This PR implements data desensitization for sensitive information (specifically email and phone numbers) in the notification receiver functionality. The goal is to protect user privacy by masking sensitive contact information when it's displayed, while maintaining the ability to use the original data when needed.

🗺️ 2. The Changes (What was implemented?)

File Name Implementation Details Goal / Requirement Addressed
hertzbeat-common/src/main/java/org/apache/hertzbeat/common/entity/dto/vo/NoticeReceiverVO.java (Added) Added a new VO class that extends the existing NoticeReceiver entity with JSON serialization annotations for desensitizing email and phone fields. Create data structure for desensitized display
hertzbeat-common/src/main/java/org/apache/hertzbeat/common/serialize/EmailDesensitizationSerializer.java (Added) Added a custom JSON serializer that masks email addresses (e.g., "u***@example.com") while storing the original value in cache. Implement email desensitization
hertzbeat-common/src/main/java/org/apache/hertzbeat/common/serialize/PhoneDesensitizationSerializer.java (Added) Added a custom JSON serializer that masks phone numbers while storing the original value in cache. Implement phone number desensitization
hertzbeat-common/src/main/java/org/apache/hertzbeat/common/cache/CacheFactory.java (Modified) Added a new cache service for storing the mapping between desensitized and original values. Store desensitization mapping data
hertzbeat-manager/src/main/java/org/apache/hertzbeat/manager/controller/NoticeConfigController.java (Modified) Modified the getReceivers endpoint to return NoticeReceiverVO objects with desensitized data instead of raw NoticeReceiver entities. Apply desensitization in API response
hertzbeat-manager/src/main/java/org/apache/hertzbeat/manager/service/impl/NoticeConfigServiceImpl.java (Modified) Added logic to retrieve original values from cache when editing or testing notification receivers. Restore original values for operations
pom.xml (Modified) Added Hutool dependency (version 5.8.20) for utility functions. Add required dependency
hertzbeat-common/pom.xml (Modified) Added Hutool dependency to the common module. Add required dependency
hertzbeat-common/src/test/java/org/apache/hertzbeat/common/serialize/EmailDesensitizationSerializerTest.java (Added) Added unit tests for the email desensitization serializer. Test email desensitization
hertzbeat-common/src/test/java/org/apache/hertzbeat/common/serialize/PhoneDesensitizationSerializerTest.java (Added) Added unit tests for the phone desensitization serializer. Test phone desensitization

🤔 3. Key Areas for Human Review

Area of Concern: Data Security in Desensitization Implementation

  • File: hertzbeat-common/src/main/java/org/apache/hertzbeat/common/serialize/EmailDesensitizationSerializer.java and PhoneDesensitizationSerializer.java
  • Why: These files implement core security functionality for masking sensitive user data. The implementation stores both masked and original values in a cache, which could potentially lead to data exposure if not properly managed.
  • Testing Instruction: Verify that the desensitized values are properly masked in API responses. Test that the original values are correctly retrieved from cache when needed for operations like sending test messages.

Area of Concern: Cache Management for Sensitive Data

  • File: hertzbeat-common/src/main/java/org/apache/hertzbeat/common/cache/CacheFactory.java (Lines ~60-68)
  • Why: The PR introduces a new cache for storing mappings between desensitized and original values. This cache needs proper configuration to avoid memory leaks or unauthorized access to sensitive data.
  • Testing Instruction: Verify that the cache has appropriate TTL settings and that sensitive data is not persisted longer than necessary. Check that the cache is properly cleared when no longer needed.

Area of Concern: API Response Transformation

  • File: hertzbeat-manager/src/main/java/org/apache/hertzbeat/manager/controller/NoticeConfigController.java (Lines ~85-90)
  • Why: The controller now returns a different object type (NoticeReceiverVO instead of NoticeReceiver) which could potentially break existing API consumers.
  • Testing Instruction: Test the API endpoint to ensure it returns properly formatted responses with desensitized data. Verify that all required fields are still present in the response and that clients can still parse the response correctly.

Area of Concern: Original Value Restoration Logic

  • File: hertzbeat-manager/src/main/java/org/apache/hertzbeat/manager/service/impl/NoticeConfigServiceImpl.java (Lines ~133-146 and ~276-287)
  • Why: The service now includes logic to restore original values from cache for operations like editing and testing. If this logic fails, it could prevent these operations from working correctly.
  • Testing Instruction: Test the edit and test message functionality with receivers that have desensitized data. Verify that the original values are correctly restored and used in these operations.

Copy link

refacto-test bot commented Jul 31, 2025

Refacto is reviewing this PR. Please wait for the review comments to be posted.

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a feature to desensitize sensitive information. The implementation is generally good, but there are areas for improvement in dependency management, code duplication, testing, and code style. Please address the comments provided.

Comment on lines +30 to +33
SimpleModule simpleModule = new SimpleModule();
simpleModule.addSerializer(String.class,emailDesensitizationSerializer);

objectMapper.registerModule(simpleModule);

Choose a reason for hiding this comment

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

high

The EmailDesensitizationSerializer is already registered via @JsonSerialize in NoticeReceiverVO. Registering it again for all strings is incorrect and unnecessary. Remove the simpleModule registration.

Suggested change
SimpleModule simpleModule = new SimpleModule();
simpleModule.addSerializer(String.class,emailDesensitizationSerializer);
objectMapper.registerModule(simpleModule);
objectMapper.registerModule(simpleModule);

Comment on lines +30 to +33
SimpleModule simpleModule = new SimpleModule();
simpleModule.addSerializer(String.class,phoneDesensitizationSerializer);

objectMapper.registerModule(simpleModule);

Choose a reason for hiding this comment

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

high

The PhoneDesensitizationSerializer is already registered via @JsonSerialize in NoticeReceiverVO. Registering it again for all strings is incorrect and unnecessary. Remove the simpleModule registration.

Suggested change
SimpleModule simpleModule = new SimpleModule();
simpleModule.addSerializer(String.class,phoneDesensitizationSerializer);
objectMapper.registerModule(simpleModule);
objectMapper.registerModule(simpleModule);

Comment on lines +147 to +151
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>${hutool-all.version}</version>
</dependency>

Choose a reason for hiding this comment

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

medium

Consider using specific hutool modules instead of hutool-all to reduce the application size. For the functionality used in this PR (StrUtil, DesensitizedUtil), hutool-core might be sufficient.

* get desensitizationMap cache
* @return desensitizationMap cache
*/
public static CommonCacheService<String, Object> getDesensitizationMapCache(){return DESENSITIZATION_MAP_CACHE;};

Choose a reason for hiding this comment

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

medium

For better readability, format this method across multiple lines.

    public static CommonCacheService<String, Object> getDesensitizationMapCache() {
        return DESENSITIZATION_MAP_CACHE;
    }

Comment on lines +22 to +25
import jakarta.persistence.Column;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;

Choose a reason for hiding this comment

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

medium

The NoticeReceiverVO is a DTO, and should not have JPA annotations. Remove @Id and @GeneratedValue.

desensitizationMapCache.put(currentValue.getId() + "_" + emailDesensitization, email);
}
jsonGenerator.writeString(emailDesensitization);
}

Choose a reason for hiding this comment

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

medium

Add a newline character at the end of the file.

}

}

jsonGenerator.writeString(phoneDesensitization);
}

Choose a reason for hiding this comment

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

medium

Add a newline character at the end of the file.

}

Comment on lines +136 to +142
CommonCacheService<String, Object> desensitizationMapCache = CacheFactory.getDesensitizationMapCache();
if (ObjectUtil.isNotEmpty(noticeReceiver.getEmail()) && ObjectUtil.isNotEmpty(desensitizationMapCache.get(noticeReceiver.getId()+"_"+noticeReceiver.getEmail()))){
noticeReceiver.setEmail((String) desensitizationMapCache.get(noticeReceiver.getId()+"_"+noticeReceiver.getEmail()));
}
if (ObjectUtil.isNotEmpty(noticeReceiver.getPhone()) && ObjectUtil.isNotEmpty(desensitizationMapCache.get(noticeReceiver.getId()+"_"+noticeReceiver.getPhone()))){
noticeReceiver.setPhone((String) desensitizationMapCache.get(noticeReceiver.getId()+"_"+noticeReceiver.getPhone()));
}

Choose a reason for hiding this comment

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

medium

Optimize the cache retrieval logic by performing a single lookup per field. Extract this logic into a private helper method to avoid code duplication with the sendTestMsg method.

Suggested change
CommonCacheService<String, Object> desensitizationMapCache = CacheFactory.getDesensitizationMapCache();
if (ObjectUtil.isNotEmpty(noticeReceiver.getEmail()) && ObjectUtil.isNotEmpty(desensitizationMapCache.get(noticeReceiver.getId()+"_"+noticeReceiver.getEmail()))){
noticeReceiver.setEmail((String) desensitizationMapCache.get(noticeReceiver.getId()+"_"+noticeReceiver.getEmail()));
}
if (ObjectUtil.isNotEmpty(noticeReceiver.getPhone()) && ObjectUtil.isNotEmpty(desensitizationMapCache.get(noticeReceiver.getId()+"_"+noticeReceiver.getPhone()))){
noticeReceiver.setPhone((String) desensitizationMapCache.get(noticeReceiver.getId()+"_"+noticeReceiver.getPhone()));
}
CommonCacheService<String, Object> desensitizationMapCache = CacheFactory.getDesensitizationMapCache();
if (ObjectUtil.isNotEmpty(noticeReceiver.getEmail())) {
Object originalEmail = desensitizationMapCache.get(noticeReceiver.getId() + "_" + noticeReceiver.getEmail());
if (ObjectUtil.isNotEmpty(originalEmail)) {
noticeReceiver.setEmail((String) originalEmail);
}
}
if (ObjectUtil.isNotEmpty(noticeReceiver.getPhone())) {
Object originalPhone = desensitizationMapCache.get(noticeReceiver.getId() + "_" + noticeReceiver.getPhone());
if (ObjectUtil.isNotEmpty(originalPhone)) {
noticeReceiver.setPhone((String) originalPhone);
}
}

Comment on lines +279 to +287
if (null != noticeReceiver.getId()){
CommonCacheService<String, Object> desensitizationMapCache = CacheFactory.getDesensitizationMapCache();
if (ObjectUtil.isNotEmpty(noticeReceiver.getEmail()) && ObjectUtil.isNotEmpty(desensitizationMapCache.get(noticeReceiver.getId()+"_"+noticeReceiver.getEmail()))){
noticeReceiver.setEmail((String) desensitizationMapCache.get(noticeReceiver.getId()+"_"+noticeReceiver.getEmail()));
}
if (ObjectUtil.isNotEmpty(noticeReceiver.getPhone()) && ObjectUtil.isNotEmpty(desensitizationMapCache.get(noticeReceiver.getId()+"_"+noticeReceiver.getPhone()))){
noticeReceiver.setPhone((String) desensitizationMapCache.get(noticeReceiver.getId()+"_"+noticeReceiver.getPhone()));
}
}

Choose a reason for hiding this comment

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

medium

This cache retrieval logic is duplicated from the editReceiver method. Extract it into a private helper method to avoid code duplication.

Suggested change
if (null != noticeReceiver.getId()){
CommonCacheService<String, Object> desensitizationMapCache = CacheFactory.getDesensitizationMapCache();
if (ObjectUtil.isNotEmpty(noticeReceiver.getEmail()) && ObjectUtil.isNotEmpty(desensitizationMapCache.get(noticeReceiver.getId()+"_"+noticeReceiver.getEmail()))){
noticeReceiver.setEmail((String) desensitizationMapCache.get(noticeReceiver.getId()+"_"+noticeReceiver.getEmail()));
}
if (ObjectUtil.isNotEmpty(noticeReceiver.getPhone()) && ObjectUtil.isNotEmpty(desensitizationMapCache.get(noticeReceiver.getId()+"_"+noticeReceiver.getPhone()))){
noticeReceiver.setPhone((String) desensitizationMapCache.get(noticeReceiver.getId()+"_"+noticeReceiver.getPhone()));
}
}
if (null != noticeReceiver.getId()){
CommonCacheService<String, Object> desensitizationMapCache = CacheFactory.getDesensitizationMapCache();
if (ObjectUtil.isNotEmpty(noticeReceiver.getEmail())) {
Object originalEmail = desensitizationMapCache.get(noticeReceiver.getId() + "_" + noticeReceiver.getEmail());
if (ObjectUtil.isNotEmpty(originalEmail)) {
noticeReceiver.setEmail((String) originalEmail);
}
}
if (ObjectUtil.isNotEmpty(noticeReceiver.getPhone())) {
Object originalPhone = desensitizationMapCache.get(noticeReceiver.getId() + "_" + noticeReceiver.getPhone());
if (ObjectUtil.isNotEmpty(originalPhone)) {
noticeReceiver.setPhone((String) originalPhone);
}
}
}

Comment on lines +481 to +484
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>${hutool-all.version}</version>
</dependency>

Choose a reason for hiding this comment

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

medium

Consider using specific hutool modules instead of hutool-all to reduce the application size. For the functionality used, hutool-core should be sufficient.

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: 10

♻️ Duplicate comments (1)
hertzbeat-common/src/main/java/org/apache/hertzbeat/common/serialize/EmailDesensitizationSerializer.java (1)

44-45: Add null safety checks (same issue as phone serializer).

The same null safety issues exist here as in the phone serializer. Apply the same fixes for null checking and error handling.

🧹 Nitpick comments (5)
hertzbeat-common/src/main/java/org/apache/hertzbeat/common/cache/CacheFactory.java (1)

64-68: Improve method formatting for consistency.

The getter method formatting is inconsistent with other methods in the class.

-    /**
-     * get desensitizationMap cache
-     * @return desensitizationMap cache
-     */
-    public static CommonCacheService<String, Object> getDesensitizationMapCache(){return DESENSITIZATION_MAP_CACHE;};
+    /**
+     * get desensitizationMap cache
+     * @return desensitizationMap cache
+     */
+    public static CommonCacheService<String, Object> getDesensitizationMapCache() {
+        return DESENSITIZATION_MAP_CACHE;
+    }
hertzbeat-common/src/test/java/org/apache/hertzbeat/common/serialize/EmailDesensitizationSerializerTest.java (1)

23-24: Improve test method naming for clarity.

The test method name "test" is not descriptive and doesn't follow JUnit naming conventions.

-    @Test
-    public void test() throws JsonProcessingException {
+    @Test
+    public void testEmailDesensitizationAndCaching() throws JsonProcessingException {
hertzbeat-common/src/test/java/org/apache/hertzbeat/common/serialize/PhoneDesensitizationSerializerTest.java (2)

24-24: Use a more descriptive test method name.

The method name test is not descriptive and doesn't convey what functionality is being tested.

-public void test() throws JsonProcessingException {
+public void testPhoneDesensitizationAndCaching() throws JsonProcessingException {

24-41: Add edge case testing.

The test only covers the happy path. Consider adding tests for edge cases such as null phone numbers, empty strings, and phones without sufficient digits for desensitization.

hertzbeat-common/src/main/java/org/apache/hertzbeat/common/serialize/PhoneDesensitizationSerializer.java (1)

35-49: Consider making the serializer more generic.

The serializer is tightly coupled to NoticeReceiverVO. Consider using reflection or a generic approach to make it reusable with other entities that have an id field.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b02837b and 8ade3d3.

📒 Files selected for processing (10)
  • hertzbeat-common/pom.xml (1 hunks)
  • hertzbeat-common/src/main/java/org/apache/hertzbeat/common/cache/CacheFactory.java (2 hunks)
  • hertzbeat-common/src/main/java/org/apache/hertzbeat/common/entity/dto/vo/NoticeReceiverVO.java (1 hunks)
  • hertzbeat-common/src/main/java/org/apache/hertzbeat/common/serialize/EmailDesensitizationSerializer.java (1 hunks)
  • hertzbeat-common/src/main/java/org/apache/hertzbeat/common/serialize/PhoneDesensitizationSerializer.java (1 hunks)
  • hertzbeat-common/src/test/java/org/apache/hertzbeat/common/serialize/EmailDesensitizationSerializerTest.java (1 hunks)
  • hertzbeat-common/src/test/java/org/apache/hertzbeat/common/serialize/PhoneDesensitizationSerializerTest.java (1 hunks)
  • hertzbeat-manager/src/main/java/org/apache/hertzbeat/manager/controller/NoticeConfigController.java (2 hunks)
  • hertzbeat-manager/src/main/java/org/apache/hertzbeat/manager/service/impl/NoticeConfigServiceImpl.java (3 hunks)
  • pom.xml (2 hunks)
🔇 Additional comments (8)
hertzbeat-common/pom.xml (1)

146-151: LGTM! Hutool dependency addition supports desensitization features.

The addition of the Hutool utility library aligns well with the new desensitization serializers and caching functionality introduced in this PR. The dependency uses property-based versioning which is consistent with other dependencies in the project.

hertzbeat-common/src/main/java/org/apache/hertzbeat/common/cache/CacheFactory.java (1)

37-38: LGTM! Cache configuration is consistent.

The new desensitization cache follows the same configuration pattern as existing caches, which ensures consistent behavior across the application.

hertzbeat-manager/src/main/java/org/apache/hertzbeat/manager/controller/NoticeConfigController.java (3)

23-23: LGTM! Import addition supports VO conversion.

The Hutool BeanUtil import is necessary for the entity-to-VO conversion logic.


31-31: LGTM! VO import enables desensitization.

The NoticeReceiverVO import supports returning desensitized data instead of raw entities.


88-90: Double-check API clients & documentation for response schema change

We’ve searched the codebase and found no internal Java clients referencing the old NoticeReceiver type—only the controller’s own unit test and the E2E suite hit /api/notice/receivers without asserting on response fields. Because switching from NoticeReceiver to NoticeReceiverVO could alter the JSON payload, please:

  • Review any external API consumers (mobile apps, third-party integrations, etc.) to ensure they tolerate the new VO schema.
  • Update your OpenAPI/Swagger spec or other API documentation to reflect the new properties on NoticeReceiverVO.
  • If backward compatibility is required, consider versioning the endpoint or adding compatibility fields.

Relevant locations:

  • hertzbeat-manager/src/main/java/org/apache/hertzbeat/manager/controller/NoticeConfigController.java (lines ~88–90)
  • hertzbeat-manager/src/test/java/org/apache/hertzbeat/manager/controller/NoticeConfigControllerTest.java
pom.xml (1)

479-484: LGTM! Dependency structure is correct.

The Hutool dependency declaration correctly references the version property and follows the project's dependency management patterns.

hertzbeat-common/src/test/java/org/apache/hertzbeat/common/serialize/EmailDesensitizationSerializerTest.java (1)

29-33: Consider the scope of serializer registration.

Registering the EmailDesensitizationSerializer for all String.class fields may cause unintended desensitization of non-email string fields. Consider if this is the intended behavior or if the serializer should be more targeted.

Please verify that registering the serializer for all String fields aligns with the actual usage in the application. The NoticeReceiverVO likely uses field-specific annotations to control which fields get desensitized.

hertzbeat-common/src/test/java/org/apache/hertzbeat/common/serialize/PhoneDesensitizationSerializerTest.java (1)

40-40: Verify the cache key construction logic.

The cache key uses noticeReceiverVO.getPhone() which should contain the desensitized phone number after deserialization, but the test expects to find the original phone number in the cache. This suggests the cache key construction might be correct, but it would be clearer to explicitly verify both the desensitized value and the cache lookup.

Add assertions to verify the desensitization worked correctly:

 String jsonString = objectMapper.writeValueAsString(noticeReceiver);
 NoticeReceiverVO noticeReceiverVO = objectMapper.readValue(jsonString, NoticeReceiverVO.class);

+// Verify phone was desensitized
+assertNotEquals(noticeReceiver.getPhone(), noticeReceiverVO.getPhone());
+assertTrue(noticeReceiverVO.getPhone().contains("*"));

 CommonCacheService<String, Object> desensitizationMapCache = CacheFactory.getDesensitizationMapCache();
 assertEquals(noticeReceiver.getPhone(),desensitizationMapCache.get(noticeReceiverVO.getId()+"_"+noticeReceiverVO.getPhone()));

Comment on lines +22 to +25
import jakarta.persistence.Column;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Remove JPA entity annotations from VO class.

A VO (Value Object/View Object) should not contain JPA entity annotations like @Id, @GeneratedValue, @Column. These annotations are for entity classes that map to database tables, not for DTOs used for data transfer.

-import jakarta.persistence.Column;
-import jakarta.persistence.GeneratedValue;
-import jakarta.persistence.GenerationType;
-import jakarta.persistence.Id;

-@Id
-@GeneratedValue(strategy = GenerationType.IDENTITY)
 @Schema(title = "Recipient entity primary key index ID", description = "Recipient entity primary key index ID",
         example = "87584674384", accessMode = READ_ONLY)
 private Long id;

-@Column(length = 300)
 private String hookUrl;

Apply this pattern to remove all @Column annotations throughout the class.

Also applies to: 55-56, 97-98

🤖 Prompt for AI Agents
In
hertzbeat-common/src/main/java/org/apache/hertzbeat/common/entity/dto/vo/NoticeReceiverVO.java
at lines 22-25, 55-56, and 97-98, remove all JPA entity annotations such as @Id,
@GeneratedValue, and @Column from the VO class. These annotations are
inappropriate for a Value Object and should be eliminated to keep the class
focused on data transfer without persistence mapping.

Comment on lines +36 to +39
import org.springframework.data.annotation.CreatedBy;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedBy;
import org.springframework.data.annotation.LastModifiedDate;
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Remove Spring Data auditing annotations from VO class.

Spring Data auditing annotations (@CreatedBy, @LastModifiedBy, @CreatedDate, @LastModifiedDate) are meant for entity classes, not VOs. These annotations won't function in a VO and create confusion about the class's purpose.

-import org.springframework.data.annotation.CreatedBy;
-import org.springframework.data.annotation.CreatedDate;
-import org.springframework.data.annotation.LastModifiedBy;
-import org.springframework.data.annotation.LastModifiedDate;

 @Schema(title = "The creator of this record", example = "tom",
         accessMode = READ_ONLY)
-@CreatedBy
 private String creator;

 @Schema(title = "This record was last modified by", example = "tom", accessMode = READ_ONLY)
-@LastModifiedBy
 private String modifier;

 @Schema(title = "Record creation time (millisecond timestamp)",
         example = "1612198922000", accessMode = READ_ONLY)
-@CreatedDate
 private LocalDateTime gmtCreate;

 @Schema(title = "Record the latest modification time (timestamp in milliseconds)",
         example = "1612198444000", accessMode = READ_ONLY)
-@LastModifiedDate
 private LocalDateTime gmtUpdate;

Also applies to: 226-241

🤖 Prompt for AI Agents
In
hertzbeat-common/src/main/java/org/apache/hertzbeat/common/entity/dto/vo/NoticeReceiverVO.java
at lines 36-39 and 226-241, remove the Spring Data auditing annotations
@CreatedBy, @LastModifiedBy, @CreatedDate, and @LastModifiedDate from the VO
class. These annotations are intended for entity classes and do not function
properly in VO classes, so deleting them will clarify the class's purpose and
avoid confusion.

@AllArgsConstructor
@NoArgsConstructor
@Builder
public class NoticeReceiverVO {
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider the class design and naming convention.

The class appears to be a direct copy of an entity class with serializers added. Consider whether this is truly a VO/DTO or if you should:

  1. Create a proper mapping between entity and VO
  2. Keep only the fields needed for the specific view/transfer purpose
  3. Consider renaming to NoticeReceiverDTO to better reflect its purpose
🤖 Prompt for AI Agents
In
hertzbeat-common/src/main/java/org/apache/hertzbeat/common/entity/dto/vo/NoticeReceiverVO.java
at line 53, the class is currently named NoticeReceiverVO but appears to be a
direct copy of an entity class with serializers added. To fix this, create a
proper mapping between the entity and this class, retain only the fields
necessary for the view or transfer purpose, and consider renaming the class to
NoticeReceiverDTO to better reflect its role as a data transfer object.

Comment on lines +41 to +43
int index = StrUtil.indexOf(email, '@');
emailDesensitization = index <= 1 ? email :
StrUtil.replace(email, 1, index, '*');
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Improve email desensitization logic and use Hutool's built-in method.

The current email desensitization logic has several issues:

  1. No validation that @ symbol exists in the email
  2. Doesn't handle edge cases where @ is at position 0 or 1
  3. Hutool already provides DesensitizedUtil.email() method
-int index = StrUtil.indexOf(email, '@');
-emailDesensitization = index <= 1 ? email :
-        StrUtil.replace(email, 1, index, '*');
+emailDesensitization = DesensitizedUtil.email(email);
📝 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
int index = StrUtil.indexOf(email, '@');
emailDesensitization = index <= 1 ? email :
StrUtil.replace(email, 1, index, '*');
- int index = StrUtil.indexOf(email, '@');
- emailDesensitization = index <= 1 ? email :
- StrUtil.replace(email, 1, index, '*');
+ emailDesensitization = DesensitizedUtil.email(email);
🤖 Prompt for AI Agents
In
hertzbeat-common/src/main/java/org/apache/hertzbeat/common/serialize/EmailDesensitizationSerializer.java
around lines 41 to 43, the current email desensitization logic lacks validation
for the presence of '@' and does not handle edge cases where '@' is at position
0 or 1. Replace this custom logic with Hutool's built-in
DesensitizedUtil.email() method, which properly validates and desensitizes email
addresses, ensuring robust handling of all edge cases.

Comment on lines +43 to +44
NoticeReceiverVO currentValue = (NoticeReceiverVO)jsonGenerator.getOutputContext().getCurrentValue();
desensitizationMapCache.put(currentValue.getId()+"_"+phoneDesensitization, phone);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Add null safety checks and improve reusability.

The code has several issues:

  1. No null check for currentValue.getId() before string concatenation
  2. Hard-coded cast to NoticeReceiverVO makes the serializer non-reusable
  3. Missing error handling for cache operations
-NoticeReceiverVO currentValue = (NoticeReceiverVO)jsonGenerator.getOutputContext().getCurrentValue();
-desensitizationMapCache.put(currentValue.getId()+"_"+phoneDesensitization, phone);
+Object currentObj = jsonGenerator.getOutputContext().getCurrentValue();
+if (currentObj instanceof NoticeReceiverVO) {
+    NoticeReceiverVO currentValue = (NoticeReceiverVO) currentObj;
+    if (currentValue.getId() != null) {
+        try {
+            desensitizationMapCache.put(currentValue.getId() + "_" + phoneDesensitization, phone);
+        } catch (Exception e) {
+            // Log error but don't fail serialization
+            // Consider using a logger here
+        }
+    }
+}
📝 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
NoticeReceiverVO currentValue = (NoticeReceiverVO)jsonGenerator.getOutputContext().getCurrentValue();
desensitizationMapCache.put(currentValue.getId()+"_"+phoneDesensitization, phone);
Object currentObj = jsonGenerator.getOutputContext().getCurrentValue();
if (currentObj instanceof NoticeReceiverVO) {
NoticeReceiverVO currentValue = (NoticeReceiverVO) currentObj;
if (currentValue.getId() != null) {
try {
desensitizationMapCache.put(
currentValue.getId() + "_" + phoneDesensitization,
phone
);
} catch (Exception e) {
// Log error but don't fail serialization
// Consider using a logger here
}
}
}
🤖 Prompt for AI Agents
In
hertzbeat-common/src/main/java/org/apache/hertzbeat/common/serialize/PhoneDesensitizationSerializer.java
around lines 43 to 44, add a null check for currentValue and
currentValue.getId() before concatenating to avoid NullPointerException.
Refactor the code to avoid hard-coded casting to NoticeReceiverVO by making the
serializer generic or accepting a more flexible type. Also, add error handling
around the desensitizationMapCache.put operation to catch and log any exceptions
during cache updates.

Comment on lines +35 to +41
String jsonString = objectMapper.writeValueAsString(noticeReceiver);
NoticeReceiverVO noticeReceiverVO = objectMapper.readValue(jsonString, NoticeReceiverVO.class);

CommonCacheService<String, Object> desensitizationMapCache = CacheFactory.getDesensitizationMapCache();

assertEquals(noticeReceiver.getEmail(),desensitizationMapCache.get(noticeReceiverVO.getId()+"_"+noticeReceiverVO.getEmail()));
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Enhance test to verify actual desensitization.

The test only verifies caching but doesn't confirm that the email was actually desensitized in the JSON output. Consider adding assertions to verify the desensitization behavior.

         String jsonString = objectMapper.writeValueAsString(noticeReceiver);
+        // Verify that the JSON contains desensitized email, not the original
+        assertFalse(jsonString.contains("[email protected]"), "Original email should be desensitized in JSON");
+        
         NoticeReceiverVO noticeReceiverVO = objectMapper.readValue(jsonString, NoticeReceiverVO.class);
+        
+        // Verify that deserialized object has desensitized email
+        assertNotEquals("[email protected]", noticeReceiverVO.getEmail(), "Email should be desensitized after deserialization");
 
         CommonCacheService<String, Object> desensitizationMapCache = CacheFactory.getDesensitizationMapCache();
 
+        // Verify original email is cached for restoration
         assertEquals(noticeReceiver.getEmail(),desensitizationMapCache.get(noticeReceiverVO.getId()+"_"+noticeReceiverVO.getEmail()));
📝 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
String jsonString = objectMapper.writeValueAsString(noticeReceiver);
NoticeReceiverVO noticeReceiverVO = objectMapper.readValue(jsonString, NoticeReceiverVO.class);
CommonCacheService<String, Object> desensitizationMapCache = CacheFactory.getDesensitizationMapCache();
assertEquals(noticeReceiver.getEmail(),desensitizationMapCache.get(noticeReceiverVO.getId()+"_"+noticeReceiverVO.getEmail()));
}
String jsonString = objectMapper.writeValueAsString(noticeReceiver);
// Verify that the JSON contains desensitized email, not the original
assertFalse(jsonString.contains("[email protected]"), "Original email should be desensitized in JSON");
NoticeReceiverVO noticeReceiverVO = objectMapper.readValue(jsonString, NoticeReceiverVO.class);
// Verify that deserialized object has desensitized email
assertNotEquals("[email protected]", noticeReceiverVO.getEmail(), "Email should be desensitized after deserialization");
CommonCacheService<String, Object> desensitizationMapCache = CacheFactory.getDesensitizationMapCache();
// Verify original email is cached for restoration
assertEquals(noticeReceiver.getEmail(),desensitizationMapCache.get(noticeReceiverVO.getId()+"_"+noticeReceiverVO.getEmail()));
}
🤖 Prompt for AI Agents
In
hertzbeat-common/src/test/java/org/apache/hertzbeat/common/serialize/EmailDesensitizationSerializerTest.java
around lines 35 to 41, the test currently only checks if the email is cached but
does not verify if the email was actually desensitized in the JSON output. To
fix this, add assertions that inspect the serialized JSON string to confirm the
email field is masked or altered according to the desensitization rules,
ensuring the desensitization logic is properly tested.

Comment on lines +136 to +142
CommonCacheService<String, Object> desensitizationMapCache = CacheFactory.getDesensitizationMapCache();
if (ObjectUtil.isNotEmpty(noticeReceiver.getEmail()) && ObjectUtil.isNotEmpty(desensitizationMapCache.get(noticeReceiver.getId()+"_"+noticeReceiver.getEmail()))){
noticeReceiver.setEmail((String) desensitizationMapCache.get(noticeReceiver.getId()+"_"+noticeReceiver.getEmail()));
}
if (ObjectUtil.isNotEmpty(noticeReceiver.getPhone()) && ObjectUtil.isNotEmpty(desensitizationMapCache.get(noticeReceiver.getId()+"_"+noticeReceiver.getPhone()))){
noticeReceiver.setPhone((String) desensitizationMapCache.get(noticeReceiver.getId()+"_"+noticeReceiver.getPhone()));
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Extract duplicate cache restoration logic into a helper method.

The same cache restoration logic is duplicated in editReceiver and sendTestMsg methods. This violates the DRY principle and makes maintenance harder.

Extract the logic into a private helper method:

+private void restoreOriginalContactInfo(NoticeReceiver noticeReceiver) {
+    if (noticeReceiver.getId() == null) {
+        return;
+    }
+    
+    CommonCacheService<String, Object> desensitizationMapCache = CacheFactory.getDesensitizationMapCache();
+    
+    // Restore original email
+    if (ObjectUtil.isNotEmpty(noticeReceiver.getEmail())) {
+        String cacheKey = noticeReceiver.getId() + "_" + noticeReceiver.getEmail();
+        Object originalEmail = desensitizationMapCache.get(cacheKey);
+        if (originalEmail instanceof String) {
+            noticeReceiver.setEmail((String) originalEmail);
+        }
+    }
+    
+    // Restore original phone
+    if (ObjectUtil.isNotEmpty(noticeReceiver.getPhone())) {
+        String cacheKey = noticeReceiver.getId() + "_" + noticeReceiver.getPhone();
+        Object originalPhone = desensitizationMapCache.get(cacheKey);
+        if (originalPhone instanceof String) {
+            noticeReceiver.setPhone((String) originalPhone);
+        }
+    }
+}

 @Override
 public void editReceiver(NoticeReceiver noticeReceiver) {
-    CommonCacheService<String, Object> desensitizationMapCache = CacheFactory.getDesensitizationMapCache();
-    if (ObjectUtil.isNotEmpty(noticeReceiver.getEmail()) && ObjectUtil.isNotEmpty(desensitizationMapCache.get(noticeReceiver.getId()+"_"+noticeReceiver.getEmail()))){
-        noticeReceiver.setEmail((String) desensitizationMapCache.get(noticeReceiver.getId()+"_"+noticeReceiver.getEmail()));
-    }
-    if (ObjectUtil.isNotEmpty(noticeReceiver.getPhone()) && ObjectUtil.isNotEmpty(desensitizationMapCache.get(noticeReceiver.getId()+"_"+noticeReceiver.getPhone()))){
-        noticeReceiver.setPhone((String) desensitizationMapCache.get(noticeReceiver.getId()+"_"+noticeReceiver.getPhone()));
-    }
+    restoreOriginalContactInfo(noticeReceiver);
     noticeReceiverDao.save(noticeReceiver);
 }
📝 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
CommonCacheService<String, Object> desensitizationMapCache = CacheFactory.getDesensitizationMapCache();
if (ObjectUtil.isNotEmpty(noticeReceiver.getEmail()) && ObjectUtil.isNotEmpty(desensitizationMapCache.get(noticeReceiver.getId()+"_"+noticeReceiver.getEmail()))){
noticeReceiver.setEmail((String) desensitizationMapCache.get(noticeReceiver.getId()+"_"+noticeReceiver.getEmail()));
}
if (ObjectUtil.isNotEmpty(noticeReceiver.getPhone()) && ObjectUtil.isNotEmpty(desensitizationMapCache.get(noticeReceiver.getId()+"_"+noticeReceiver.getPhone()))){
noticeReceiver.setPhone((String) desensitizationMapCache.get(noticeReceiver.getId()+"_"+noticeReceiver.getPhone()));
}
// --- new helper at class level ---
private void restoreOriginalContactInfo(NoticeReceiver noticeReceiver) {
if (noticeReceiver.getId() == null) {
return;
}
CommonCacheService<String, Object> desensitizationMapCache =
CacheFactory.getDesensitizationMapCache();
// Restore original email
if (ObjectUtil.isNotEmpty(noticeReceiver.getEmail())) {
String cacheKey = noticeReceiver.getId() + "_" + noticeReceiver.getEmail();
Object originalEmail = desensitizationMapCache.get(cacheKey);
if (originalEmail instanceof String) {
noticeReceiver.setEmail((String) originalEmail);
}
}
// Restore original phone
if (ObjectUtil.isNotEmpty(noticeReceiver.getPhone())) {
String cacheKey = noticeReceiver.getId() + "_" + noticeReceiver.getPhone();
Object originalPhone = desensitizationMapCache.get(cacheKey);
if (originalPhone instanceof String) {
noticeReceiver.setPhone((String) originalPhone);
}
}
}
@Override
public void editReceiver(NoticeReceiver noticeReceiver) {
- CommonCacheService<String, Object> desensitizationMapCache = CacheFactory.getDesensitizationMapCache();
- if (ObjectUtil.isNotEmpty(noticeReceiver.getEmail()) && ObjectUtil.isNotEmpty(desensitizationMapCache.get(noticeReceiver.getId()+"_"+noticeReceiver.getEmail()))){
- noticeReceiver.setEmail((String) desensitizationMapCache.get(noticeReceiver.getId()+"_"+noticeReceiver.getEmail()));
- }
- if (ObjectUtil.isNotEmpty(noticeReceiver.getPhone()) && ObjectUtil.isNotEmpty(desensitizationMapCache.get(noticeReceiver.getId()+"_"+noticeReceiver.getPhone()))){
- noticeReceiver.setPhone((String) desensitizationMapCache.get(noticeReceiver.getId()+"_"+noticeReceiver.getPhone()));
- }
+ restoreOriginalContactInfo(noticeReceiver);
noticeReceiverDao.save(noticeReceiver);
}
🤖 Prompt for AI Agents
In
hertzbeat-manager/src/main/java/org/apache/hertzbeat/manager/service/impl/NoticeConfigServiceImpl.java
around lines 136 to 142, the cache restoration logic for email and phone in
noticeReceiver is duplicated in multiple methods. Extract this repeated logic
into a private helper method that takes a noticeReceiver object, checks and
restores email and phone from the cache, and then call this helper method from
the original locations to eliminate duplication and improve maintainability.

Comment on lines +137 to +138
if (ObjectUtil.isNotEmpty(noticeReceiver.getEmail()) && ObjectUtil.isNotEmpty(desensitizationMapCache.get(noticeReceiver.getId()+"_"+noticeReceiver.getEmail()))){
noticeReceiver.setEmail((String) desensitizationMapCache.get(noticeReceiver.getId()+"_"+noticeReceiver.getEmail()));
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Add type safety and error handling for cache operations.

The current implementation has several safety issues:

  1. No type checking before casting cached values to String
  2. No error handling for cache operations
  3. Multiple cache lookups for the same key

The suggested helper method above addresses these issues by:

  • Adding instanceof String checks before casting
  • Eliminating duplicate cache lookups
  • Consolidating error-prone logic in one place
🤖 Prompt for AI Agents
In
hertzbeat-manager/src/main/java/org/apache/hertzbeat/manager/service/impl/NoticeConfigServiceImpl.java
around lines 137-138, the code performs multiple cache lookups for the same key
without type checking or error handling, and casts cached values directly to
String. Refactor by creating a helper method that retrieves the cached value
once, checks if it is an instance of String before casting, and handles any
potential exceptions during cache access. Replace the existing code with a call
to this helper method to improve type safety and reduce redundant cache lookups.

Comment on lines +279 to +287
if (null != noticeReceiver.getId()){
CommonCacheService<String, Object> desensitizationMapCache = CacheFactory.getDesensitizationMapCache();
if (ObjectUtil.isNotEmpty(noticeReceiver.getEmail()) && ObjectUtil.isNotEmpty(desensitizationMapCache.get(noticeReceiver.getId()+"_"+noticeReceiver.getEmail()))){
noticeReceiver.setEmail((String) desensitizationMapCache.get(noticeReceiver.getId()+"_"+noticeReceiver.getEmail()));
}
if (ObjectUtil.isNotEmpty(noticeReceiver.getPhone()) && ObjectUtil.isNotEmpty(desensitizationMapCache.get(noticeReceiver.getId()+"_"+noticeReceiver.getPhone()))){
noticeReceiver.setPhone((String) desensitizationMapCache.get(noticeReceiver.getId()+"_"+noticeReceiver.getPhone()));
}
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Apply the same refactoring to sendTestMsg method.

Use the same helper method here to eliminate code duplication:

 @Override
 public boolean sendTestMsg(NoticeReceiver noticeReceiver) {
-    if (null != noticeReceiver.getId()){
-        CommonCacheService<String, Object> desensitizationMapCache = CacheFactory.getDesensitizationMapCache();
-        if (ObjectUtil.isNotEmpty(noticeReceiver.getEmail()) && ObjectUtil.isNotEmpty(desensitizationMapCache.get(noticeReceiver.getId()+"_"+noticeReceiver.getEmail()))){
-            noticeReceiver.setEmail((String) desensitizationMapCache.get(noticeReceiver.getId()+"_"+noticeReceiver.getEmail()));
-        }
-        if (ObjectUtil.isNotEmpty(noticeReceiver.getPhone()) && ObjectUtil.isNotEmpty(desensitizationMapCache.get(noticeReceiver.getId()+"_"+noticeReceiver.getPhone()))){
-            noticeReceiver.setPhone((String) desensitizationMapCache.get(noticeReceiver.getId()+"_"+noticeReceiver.getPhone()));
-        }
-    }
+    restoreOriginalContactInfo(noticeReceiver);

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In
hertzbeat-manager/src/main/java/org/apache/hertzbeat/manager/service/impl/NoticeConfigServiceImpl.java
around lines 279 to 287, the code for desensitizing noticeReceiver's email and
phone is duplicated. Refactor by extracting this logic into a helper method and
then call this helper method from the sendTestMsg method as well to eliminate
duplication and improve maintainability.

@@ -163,6 +163,7 @@
<huawei.sdk.version>3.1.37</huawei.sdk.version>
<huawei.obs.version>3.23.5</huawei.obs.version>
<commons-net>3.8.0</commons-net>
<hutool-all.version>5.8.20</hutool-all.version>
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Verify Hutool version is current and secure.

The property definition follows correct naming conventions. However, please verify that version 5.8.20 is the latest stable version and free from known vulnerabilities.


🌐 Web query:

What is the latest stable version of Hutool (cn.hutool:hutool-all) and are there any known security vulnerabilities in version 5.8.20?

💡 Result:

The latest stable version of Hutool (cn.hutool:hutool-all) as of July 2025 is 5.8.38[4][1].

Version 5.8.20 is known to have security vulnerabilities. Specifically, it is marked with 4 vulnerabilities related to core components[1][6]. These vulnerabilities are:

These vulnerabilities are considered high severity, as they enable potential denial-of-service (DoS) attacks or, in the case of buffer overflows, may lead to memory corruption or system crashes[3].

It is recommended to upgrade to the latest stable version (at least 5.8.38 as of June 2025) to ensure all known security issues are patched[4][1].

Citations:


Upgrade Hutool to a secure, up-to-date version

Version 5.8.20 is known to contain multiple high-severity CVEs (e.g., CVE-2023-51075, CVE-2023-42278) and is not the latest release. Please bump the property in your root pom.xml to at least 5.8.38 to ensure all known vulnerabilities are patched:

• pom.xml (root), around line 166

-    <hutool-all.version>5.8.20</hutool-all.version>
+    <hutool-all.version>5.8.38</hutool-all.version>
📝 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
<hutool-all.version>5.8.20</hutool-all.version>
<hutool-all.version>5.8.38</hutool-all.version>
🤖 Prompt for AI Agents
In pom.xml at line 166, the hutool-all.version property is set to 5.8.20, which
has known high-severity vulnerabilities. Update this version to at least 5.8.38
to ensure all security issues are addressed by changing the version number in
the property to 5.8.38 or a later secure release.

@coderabbit-test coderabbit-test deleted a comment from visz11 Jul 31, 2025
@arvi18
Copy link
Author

arvi18 commented Jul 31, 2025

/refacto-test

Copy link

refacto-test bot commented Jul 31, 2025

Refacto is reviewing this PR. Please wait for the review comments to be posted.

@coderabbit-test coderabbit-test deleted a comment from refacto-test bot Jul 31, 2025
@coderabbit-test coderabbit-test deleted a comment from refacto-test bot Jul 31, 2025
Copy link

refacto-test bot commented Jul 31, 2025

Solid Implementation - Let's Address Data Desensitization Security & Reliability!

Review Summary

This PR implements a well-structured data desensitization system for email and phone numbers, showing good attention to privacy protection. The approach using custom serializers and a cache mechanism is architecturally sound. However, our analysis identified several reliability and security concerns that should be addressed before merging. The most critical issues involve potential cache key collisions, missing error handling in the serializers, and security considerations for sensitive data storage. Addressing these items will ensure the desensitization system is robust, reliable, and secure for production use.

Well Done!!!

  • The implementation properly uses serializers to desensitize sensitive data like emails and phone numbers, following good security practices
  • The code includes comprehensive unit tests for the desensitization serializers, demonstrating good testing practices
  • The desensitization logic is well-structured with appropriate use of the Hutool library for string manipulation
  • The implementation follows a clean separation of concerns with dedicated serializer classes for different types of sensitive information

Files Processed

hertzbeat-common/src/main/java/org/apache/hertzbeat/common/serialize/EmailDesensitizationSerializer.java 35-46
hertzbeat-common/src/main/java/org/apache/hertzbeat/common/serialize/PhoneDesensitizationSerializer.java 37-44
hertzbeat-common/src/main/java/org/apache/hertzbeat/common/cache/CacheFactory.java 33-37
hertzbeat-manager/src/main/java/org/apache/hertzbeat/manager/service/impl/NoticeConfigServiceImpl.java 133-142

📌 Additional Comments (4)

Performance

Redundant Cache Lookups in NoticeConfigServiceImpl

Explanation: The current implementation performs redundant cache lookups for each sensitive field. For each field, the code first checks if the cache contains a value (desensitizationMapCache.get()) and then performs the exact same lookup again to retrieve the value. This doubles the number of cache operations, which is inefficient especially under high load scenarios.

CommonCacheService<String, Object> desensitizationMapCache = CacheFactory.getDesensitizationMapCache();
 if (ObjectUtil.isNotEmpty(noticeReceiver.getEmail()) && ObjectUtil.isNotEmpty(desensitizationMapCache.get(noticeReceiver.getId()+"_"+noticeReceiver.getEmail()))){
 noticeReceiver.setEmail((String) desensitizationMapCache.get(noticeReceiver.getId()+"_"+noticeReceiver.getEmail()));
 }
 if (ObjectUtil.isNotEmpty(noticeReceiver.getPhone()) && ObjectUtil.isNotEmpty(desensitizationMapCache.get(noticeReceiver.getId()+"_"+noticeReceiver.getPhone()))){
 noticeReceiver.setPhone((String) desensitizationMapCache.get(noticeReceiver.getId()+"_"+noticeReceiver.getPhone()));
 }

Fix Suggestion: Optimize cache lookups by retrieving values once and reusing them.

+  CommonCacheService<String, Object> desensitizationMapCache = CacheFactory.getDesensitizationMapCache();
+  if (ObjectUtil.isNotEmpty(noticeReceiver.getEmail())) {
+  String emailCacheKey = noticeReceiver.getId()+"_"+noticeReceiver.getEmail();
+  Object originalEmail = desensitizationMapCache.get(emailCacheKey);
+  if (ObjectUtil.isNotEmpty(originalEmail)) {
+  noticeReceiver.setEmail((String) originalEmail);
+  }
+  }
+  if (ObjectUtil.isNotEmpty(noticeReceiver.getPhone())) {
+  String phoneCacheKey = noticeReceiver.getId()+"_"+noticeReceiver.getPhone();
+  Object originalPhone = desensitizationMapCache.get(phoneCacheKey);
+  if (ObjectUtil.isNotEmpty(originalPhone)) {
+  noticeReceiver.setPhone((String) originalPhone);
+  }
+  }
Rationale
  • This optimization reduces the number of cache lookups from 4 to 2 for a typical case where both email and phone are present, improving performance by approximately 50% for this operation
  • The approach follows the principle of minimizing redundant operations in hot code paths, which is especially important for cache operations that may involve network calls in distributed systems
  • The fix also improves code readability by clearly separating the key generation from the value retrieval and usage
  • Expected performance gain: 2x reduction in cache lookup operations with minimal code complexity increase
References
  • Standard: Google's Performance Guidelines - Minimize Redundant Operations
---
Cache Key Concatenation Inefficiency

Explanation: The code uses string concatenation ('+') to create cache keys, which creates unnecessary temporary String objects. While the impact is minor for individual operations, this pattern occurs in multiple places and could lead to increased garbage collection pressure under high load, especially since this is in a serializer that would be called frequently during API responses.

NoticeReceiverVO currentValue = (NoticeReceiverVO) jsonGenerator.getOutputContext().getCurrentValue();
 desensitizationMapCache.put(currentValue.getId() + "_" + emailDesensitization, email);

Fix Suggestion: Use StringBuilder for efficient key construction to reduce object creation.

+  NoticeReceiverVO currentValue = (NoticeReceiverVO) jsonGenerator.getOutputContext().getCurrentValue();
+  String cacheKey = new StringBuilder().append(currentValue.getId()).append("_").append(emailDesensitization).toString();
+  desensitizationMapCache.put(cacheKey, email);
Rationale
  • This optimization reduces temporary String object creation by using StringBuilder, which is more efficient for multiple string concatenations
  • For high-throughput systems, reducing garbage collection pressure improves overall system stability and response times
  • While the performance impact is small for individual operations (~5-10% improvement in string handling), the cumulative effect across many API calls can be significant
  • Expected performance gain: Reduced garbage collection overhead and improved string handling efficiency in high-throughput scenarios
References
  • Standard: Java Performance Best Practices - Efficient String Handling
---

Reliability

Potential Cache Contention in Desensitization Map Cache

Explanation: The DESENSITIZATION_MAP_CACHE is configured with a maximum size of 1000 entries and a TTL of 1 day. In high-traffic environments, this could lead to cache eviction of still-needed entries if more than 1000 distinct desensitized values are stored. Additionally, the cache is not configured to handle concurrent modifications safely (the last parameter 'false' indicates no record access time). This could lead to inconsistent behavior in multi-threaded environments where desensitized values might not match their original values after cache evictions.

private static final CommonCacheService<String, Object> DESENSITIZATION_MAP_CACHE =
 new CaffeineCacheServiceImpl<>(10, 1000, Duration.ofDays(1), false);

Fix Suggestion: Increase cache size and enable record access time for better eviction strategy.

+ private static final CommonCacheService<String, Object> DESENSITIZATION_MAP_CACHE =
+  new CaffeineCacheServiceImpl<>(10, 10000, Duration.ofDays(1), true);
Rationale
  • Increasing the cache size from 1000 to 10000 reduces the risk of premature eviction of needed entries
  • Enabling record access time (changing false to true) improves the cache eviction strategy by keeping frequently accessed entries longer
  • This change follows resource management best practices by properly sizing the cache for the expected workload
  • The approach balances memory usage with application reliability, ensuring desensitized values can be consistently mapped back to their originals
References
  • Standard: Google SRE Practices - Resource Management
---
Missing Cache Cleanup in NoticeConfigServiceImpl

Explanation: When a notice receiver is deleted, the associated desensitized data remains in the cache. This can lead to memory leaks as the cache retains references to data that is no longer needed. Over time, this could cause the cache to reach its maximum size with stale entries, potentially affecting the application's memory usage and performance. Additionally, if a new receiver is created with the same ID later, it might incorrectly use the cached desensitized data from the deleted receiver.

@Override
 public void deleteReceiver(Long receiverId) {
 noticeReceiverDao.deleteById(receiverId);
 }

Fix Suggestion: Clean up desensitization cache entries when deleting a receiver.

+  @Override
+  public void deleteReceiver(Long receiverId) {
+  Optional<NoticeReceiver> receiverOptional = noticeReceiverDao.findById(receiverId);
+  if (receiverOptional.isPresent()) {
+  CommonCacheService<String, Object> desensitizationMapCache = CacheFactory.getDesensitizationMapCache();
+  // Remove any cached entries for this receiver
+  desensitizationMapCache.removeIf((key, value) -> key.toString().startsWith(receiverId + "_"));
+  }
+  noticeReceiverDao.deleteById(receiverId);
+  }
Rationale
  • This fix prevents memory leaks by cleaning up cache entries when they are no longer needed
  • It implements proper resource management by ensuring cache entries are removed when the associated data is deleted
  • The approach maintains cache consistency by preventing stale data from being used with new receivers that might reuse the same ID
  • It follows the principle of least surprise by ensuring the cache state accurately reflects the database state
References
  • Standard: ISO/IEC 25010 Resource Utilization - Memory Management
---

@@ -57,4 +60,10 @@ public static CommonCacheService<String, Object> getAlertSilenceCache() {
public static CommonCacheService<String, Object> getAlertConvergeCache() {
Copy link

Choose a reason for hiding this comment

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

Sensitive Data Exposure through Unprotected Cache

The implementation stores original sensitive data (emails and phone numbers) in a cache that lacks access controls. While the data is desensitized in API responses, there's no mechanism to ensure that only authorized code can retrieve the original values from the cache. The cache has a long expiration time (1 day) and doesn't implement any authentication or authorization checks for data access.

Suggested change
public static CommonCacheService<String, Object> getAlertConvergeCache() {
private static final CommonCacheService<String, Object> DESENSITIZATION_MAP_CACHE =
new CaffeineCacheServiceImpl<>(10, 1000, Duration.ofMinutes(30), false);
/**
* Get desensitizationMap cache with appropriate access control checks
* @param accessContext The context of the access request for authorization
* @return desensitizationMap cache if authorized
*/
Rationale
  • Reducing the cache expiration time from 1 day to 30 minutes minimizes the window of exposure if the cache is compromised
  • Adding documentation that reminds developers to implement proper access control when retrieving sensitive data
  • In a complete implementation, the method should verify the caller has appropriate permissions before returning the original sensitive data
References
  • CWE: CWE-922
  • OWASP: A02:2021-Cryptographic Failures

@visz11
Copy link
Collaborator

visz11 commented Aug 7, 2025

/refacto-test

Copy link

refacto-test bot commented Aug 7, 2025

Refacto is reviewing this PR. Please wait for the review comments to be posted.

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.

4 participants