Skip to content

Conversation

tacklequestions
Copy link
Contributor

@tacklequestions tacklequestions commented Sep 27, 2025

What's the purpose of this PR

Reserve a final cleanup placeholder to consolidate the OpenAPI refactor after PR01–PR09 land. This PR intentionally makes no code changes; it documents
and tracks the final fold-in and removals.

Which issue(s) this PR fixes:

Fixes #5462

Brief changelog

  • No code changes in this PR (placeholder).
  • Planned follow-ups once prior PRs are merged:
    • Merge OpenApiModelConverters back into OpenApiBeanUtils (align names/signatures).
    • Replace all usages of OpenApiModelConverters with OpenApiBeanUtils across services/controllers.
    • Remove OpenApiModelConverters.java.
    • Remove legacy “Old” services kept for Java 8 compatibility:
      • ServerAppOpenApiServiceOld
      • ServerClusterOpenApiServiceOld
      • ServerInstanceOpenApiServiceOld
      • ServerItemOpenApiServiceOld
      • ServerNamespaceOpenApiServiceOld
      • ServerOrganizationOpenApiServiceOld
      • ServerReleaseOpenApiServiceOld
    • Verify portal module builds and tests on Java 8/11/17.
    • Provide a short migration note in PR description for the cleanup scope.

Summary by CodeRabbit

  • New Features

    • Enhanced OpenAPI support with automatic model mapping and code generation from a configurable specification.
    • OpenAPI requests now resolve user context via consumer credentials, improving ownership visibility in audits and UI.
  • Chores

    • Build updated to generate and compile OpenAPI sources; required dependencies added.
    • JDK-aware build profiles introduced for Java 8 and Java 11+.

@dosubot dosubot bot added the size:XL This PR changes 500-999 lines, ignoring generated files. label Sep 27, 2025
Copy link
Contributor

coderabbitai bot commented Sep 27, 2025

Walkthrough

Adds OpenAPI code-generation tooling in apollo-portal/pom.xml with new dependencies, properties, plugins, and JDK-specific profiles. Introduces OpenApiModelConverters with numerous DTO/BO conversion methods. Updates AuthConfiguration to pass a lazily-injected ConsumerService. Enhances SpringSecurityUserInfoHolder to resolve users from OpenAPI Consumer context before falling back to Spring Security.

Changes

Cohort / File(s) Summary
Build & OpenAPI generation
apollo-portal/pom.xml
Adds apollo.openapi.spec.url property; adds OpenAPI-related dependencies; configures openapi-generator-maven-plugin under pluginManagement and build; adds build-helper-maven-plugin to include generated sources; introduces java8/java11plus profiles for plugin versioning.
OpenAPI model converters
apollo-portal/src/main/java/com/ctrip/framework/apollo/openapi/util/OpenApiModelConverters.java
New utility class with static converters between internal models and Open API DTOs for items, apps, namespaces, releases, clusters, organizations, instances, env-cluster info, diffs, and change sets; includes JSON parsing and collection mappings.
Auth configuration wiring
apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/spi/configuration/AuthConfiguration.java
Bean methods now accept @lazy ConsumerService and construct SpringSecurityUserInfoHolder with UserService and ConsumerService; imports updated.
User resolution logic
apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/spi/springsecurity/SpringSecurityUserInfoHolder.java
Constructor updated to include @lazy ConsumerService; getUser() first attempts OpenAPI consumer-based user resolution from request context, then falls back to existing Spring Security username-based lookup.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor Client
  participant Portal as Portal (Controller)
  participant UIH as SpringSecurityUserInfoHolder
  participant CS as ConsumerService
  participant US as UserService

  Client->>Portal: HTTP request
  Portal->>UIH: getUser()

  alt OpenAPI request (/openapi/...)
    UIH->>UIH: Extract consumerId from request context
    UIH->>CS: findConsumer(consumerId)
    alt Consumer found
      CS-->>UIH: Consumer data
      UIH-->>Portal: UserInfo (from Consumer)
    else Consumer not found/error
      UIH->>US: findByUsername(principal)
      US-->>UIH: UserInfo
      UIH-->>Portal: UserInfo (from Spring Security)
    end
  else Non-OpenAPI request
    UIH->>US: findByUsername(principal)
    US-->>UIH: UserInfo
    UIH-->>Portal: UserInfo
  end

  Portal-->>Client: Response
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested labels

lgtm

Suggested reviewers

  • nobodyiam

Poem

I nibble specs and hop through code,
From pom to portal’s guarded node.
A Consumer whispers, “OpenAPI, go!”
I twitch my ears—converters flow.
With lazy beans and tidy streams,
I thump approve—onward, dreams! 🐇✨

Pre-merge checks and finishing touches

❌ Failed checks (4 warnings)
Check name Status Explanation Resolution
Title Check ⚠️ Warning The title indicates a final cleanup placeholder with no operational changes, but the PR includes substantial code modifications to build configurations, model converters, and security components. It does not summarize the main contributions of adding OpenAPI generator support, conversion utilities, and authentication enhancements. As a result, the title is misleading and does not capture the actual scope of this changeset. Rename the PR title to describe the main functional changes, such as adding OpenAPI generation configuration and converter utilities and updating authentication handling. For example, use a title like “feat(openapi): add Maven OpenAPI generator and model converters with consumer-based security integration” to clearly reflect the content. This will align the title with the implemented modifications and improve clarity for reviewers.
Linked Issues Check ⚠️ Warning The changes in this PR focus on build configuration for OpenAPI code generation, a large model converter utility, and authentication service enhancements, but they do not address the RESTful URL design, HTTP method clarity, DTO parameterization, unified response format, pagination validation, or structured permission error handling specified in issue #5462. None of the core API refactoring objectives related to URL patterns, controller annotations, or exception handlers are implemented. Therefore, the PR does not meet the coding requirements of the linked issue. Implement the API refactoring steps outlined in issue #5462 before merging, including updating endpoint URLs to resource-oriented paths, clarifying HTTP method annotations, introducing DTOs for request and response payloads wrapped in ApiResponse, standardizing pagination validation, and adding a PermissionExceptionHandler for structured error responses. Ensure that all checklist items from the linked issue are completed and verified in the code. Once those objectives are met, the changes can be reviewed for compliance.
Out of Scope Changes Check ⚠️ Warning This PR includes extensive changes to Maven build configuration, new conversion utilities, and security holder logic that are not related to the API design refactor specified in issue #5462. These modifications introduce new dependencies, build plugins, and code-generation tooling outside the scope of RESTful API redesign, parameter unification, and error handling improvements. As such, these changes are out of scope for the objectives of the linked issue. Remove or isolate changes unrelated to the API design refactor, and focus this PR on implementing the required updates to controllers, DTOs, and exception handling as defined in issue #5462. Separate the addition of OpenAPI generation tooling and converter utilities into a dedicated feature PR. This will keep each PR aligned with its intended scope and improve review focus.
Docstring Coverage ⚠️ Warning Docstring coverage is 27.27% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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

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

Copy link
Contributor

@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: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apollo-portal/pom.xml (1)

30-174: Remote OpenAPI spec makes the build non-deterministic

Binding code generation to https://raw.githubusercontent.com/.../apollo-openapi.yaml means every local/CI build now depends on external network access and on whatever happens to be on main of that personal repo at build time. This is a supply-chain and reproducibility risk; the spec (and therefore generated sources) can change without review, and offline builds will fail outright. Please vendor the spec inside this repository (or at least pin to an immutable commit/tag that we control) before wiring the generator into the build.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4fa881a and c49cca1.

📒 Files selected for processing (4)
  • apollo-portal/pom.xml (4 hunks)
  • apollo-portal/src/main/java/com/ctrip/framework/apollo/openapi/util/OpenApiModelConverters.java (1 hunks)
  • apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/spi/configuration/AuthConfiguration.java (5 hunks)
  • apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/spi/springsecurity/SpringSecurityUserInfoHolder.java (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (3)
apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/spi/configuration/AuthConfiguration.java (1)
apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/spi/springsecurity/SpringSecurityUserInfoHolder.java (1)
  • SpringSecurityUserInfoHolder (33-121)
apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/spi/springsecurity/SpringSecurityUserInfoHolder.java (1)
apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/entity/bo/UserInfo.java (1)
  • UserInfo (19-79)
apollo-portal/src/main/java/com/ctrip/framework/apollo/openapi/util/OpenApiModelConverters.java (8)
apollo-common/src/main/java/com/ctrip/framework/apollo/common/utils/BeanUtils.java (1)
  • BeanUtils (37-255)
apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/entity/bo/ItemBO.java (1)
  • ItemBO (21-76)
apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/entity/bo/NamespaceBO.java (1)
  • NamespaceBO (23-103)
apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/entity/bo/ReleaseBO.java (1)
  • ReleaseBO (24-46)
apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/entity/model/NamespaceSyncModel.java (1)
  • NamespaceSyncModel (26-70)
apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/entity/model/NamespaceTextModel.java (1)
  • NamespaceTextModel (24-104)
apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/entity/vo/EnvClusterInfo.java (1)
  • EnvClusterInfo (24-48)
apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/entity/vo/NamespaceIdentifier.java (1)
  • NamespaceIdentifier (23-76)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build (8)

Comment on lines +242 to +256
public static GrayReleaseRuleDTO toGrayReleaseRuleDTO(OpenGrayReleaseRuleDTO openGrayReleaseRuleDTO) {
Preconditions.checkArgument(openGrayReleaseRuleDTO != null);
String appId = openGrayReleaseRuleDTO.getAppId();
String branchName = openGrayReleaseRuleDTO.getBranchName();
String clusterName = openGrayReleaseRuleDTO.getClusterName();
String namespaceName = openGrayReleaseRuleDTO.getNamespaceName();
GrayReleaseRuleDTO grayReleaseRuleDTO = new GrayReleaseRuleDTO(appId, clusterName, namespaceName, branchName);
Set<OpenGrayReleaseRuleItemDTO> openGrayReleaseRuleItemDTOSet = new HashSet<>(openGrayReleaseRuleDTO.getRuleItems());
openGrayReleaseRuleItemDTOSet.forEach(openGrayReleaseRuleItemDTO -> {
String clientAppId = openGrayReleaseRuleItemDTO.getClientAppId();
Set<String> clientIpList = new HashSet<>(openGrayReleaseRuleItemDTO.getClientIpList());
Set<String> clientLabelList = new HashSet<>(openGrayReleaseRuleItemDTO.getClientLabelList());
GrayReleaseRuleItemDTO ruleItem = new GrayReleaseRuleItemDTO(clientAppId, clientIpList, clientLabelList);
grayReleaseRuleDTO.addRuleItem(ruleItem);
});
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Guard against null rule items in gray release conversion.

new HashSet<>(openGrayReleaseRuleDTO.getRuleItems()) blows up when clients omit ruleItems, and the follow-up new HashSet<>(openGrayReleaseRuleItemDTO.getClientIpList()) / getClientLabelList() NPE if those collections are null. That turns a valid OpenAPI payload into a 500. Please null-check before copying and default to empty sets. Example fix:

-    Set<OpenGrayReleaseRuleItemDTO> openGrayReleaseRuleItemDTOSet = new HashSet<>(openGrayReleaseRuleDTO.getRuleItems());
-    openGrayReleaseRuleItemDTOSet.forEach(openGrayReleaseRuleItemDTO -> {
+    Collection<OpenGrayReleaseRuleItemDTO> ruleItems = openGrayReleaseRuleDTO.getRuleItems();
+    if (CollectionUtils.isEmpty(ruleItems)) {
+      return grayReleaseRuleDTO;
+    }
+    for (OpenGrayReleaseRuleItemDTO openGrayReleaseRuleItemDTO : new LinkedHashSet<>(ruleItems)) {
       String clientAppId = openGrayReleaseRuleItemDTO.getClientAppId();
-      Set<String> clientIpList = new HashSet<>(openGrayReleaseRuleItemDTO.getClientIpList());
-      Set<String> clientLabelList = new HashSet<>(openGrayReleaseRuleItemDTO.getClientLabelList());
+      Set<String> clientIpList = openGrayReleaseRuleItemDTO.getClientIpList() == null
+          ? Collections.emptySet()
+          : new HashSet<>(openGrayReleaseRuleItemDTO.getClientIpList());
+      Set<String> clientLabelList = openGrayReleaseRuleItemDTO.getClientLabelList() == null
+          ? Collections.emptySet()
+          : new HashSet<>(openGrayReleaseRuleItemDTO.getClientLabelList());
       GrayReleaseRuleItemDTO ruleItem = new GrayReleaseRuleItemDTO(clientAppId, clientIpList, clientLabelList);
       grayReleaseRuleDTO.addRuleItem(ruleItem);
-    });
+    }
📝 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 static GrayReleaseRuleDTO toGrayReleaseRuleDTO(OpenGrayReleaseRuleDTO openGrayReleaseRuleDTO) {
Preconditions.checkArgument(openGrayReleaseRuleDTO != null);
String appId = openGrayReleaseRuleDTO.getAppId();
String branchName = openGrayReleaseRuleDTO.getBranchName();
String clusterName = openGrayReleaseRuleDTO.getClusterName();
String namespaceName = openGrayReleaseRuleDTO.getNamespaceName();
GrayReleaseRuleDTO grayReleaseRuleDTO = new GrayReleaseRuleDTO(appId, clusterName, namespaceName, branchName);
Set<OpenGrayReleaseRuleItemDTO> openGrayReleaseRuleItemDTOSet = new HashSet<>(openGrayReleaseRuleDTO.getRuleItems());
openGrayReleaseRuleItemDTOSet.forEach(openGrayReleaseRuleItemDTO -> {
String clientAppId = openGrayReleaseRuleItemDTO.getClientAppId();
Set<String> clientIpList = new HashSet<>(openGrayReleaseRuleItemDTO.getClientIpList());
Set<String> clientLabelList = new HashSet<>(openGrayReleaseRuleItemDTO.getClientLabelList());
GrayReleaseRuleItemDTO ruleItem = new GrayReleaseRuleItemDTO(clientAppId, clientIpList, clientLabelList);
grayReleaseRuleDTO.addRuleItem(ruleItem);
});
public static GrayReleaseRuleDTO toGrayReleaseRuleDTO(OpenGrayReleaseRuleDTO openGrayReleaseRuleDTO) {
Preconditions.checkArgument(openGrayReleaseRuleDTO != null);
String appId = openGrayReleaseRuleDTO.getAppId();
String branchName = openGrayReleaseRuleDTO.getBranchName();
String clusterName = openGrayReleaseRuleDTO.getClusterName();
String namespaceName = openGrayReleaseRuleDTO.getNamespaceName();
GrayReleaseRuleDTO grayReleaseRuleDTO = new GrayReleaseRuleDTO(appId, clusterName, namespaceName, branchName);
Collection<OpenGrayReleaseRuleItemDTO> ruleItems = openGrayReleaseRuleDTO.getRuleItems();
if (CollectionUtils.isEmpty(ruleItems)) {
return grayReleaseRuleDTO;
}
for (OpenGrayReleaseRuleItemDTO openGrayReleaseRuleItemDTO : new LinkedHashSet<>(ruleItems)) {
String clientAppId = openGrayReleaseRuleItemDTO.getClientAppId();
Set<String> clientIpList = openGrayReleaseRuleItemDTO.getClientIpList() == null
? Collections.emptySet()
: new HashSet<>(openGrayReleaseRuleItemDTO.getClientIpList());
Set<String> clientLabelList = openGrayReleaseRuleItemDTO.getClientLabelList() == null
? Collections.emptySet()
: new HashSet<>(openGrayReleaseRuleItemDTO.getClientLabelList());
GrayReleaseRuleItemDTO ruleItem =
new GrayReleaseRuleItemDTO(clientAppId, clientIpList, clientLabelList);
grayReleaseRuleDTO.addRuleItem(ruleItem);
}
🤖 Prompt for AI Agents
In
apollo-portal/src/main/java/com/ctrip/framework/apollo/openapi/util/OpenApiModelConverters.java
around lines 242 to 256, the conversion assumes
openGrayReleaseRuleDTO.getRuleItems(), getClientIpList(), and
getClientLabelList() are non-null and will NPE; change to guard against nulls by
treating ruleItems as empty collection when null, iterate only if
non-null/empty, and when constructing clientIpList and clientLabelList default
to empty sets if their getters return null before creating
GrayReleaseRuleItemDTO; ensure you still add items when present but never pass
null collections into new HashSet<>().

Comment on lines +83 to +104
// 获取Consumer ID
Object consumerIdObj = request.getAttribute("Authorization");
if (consumerIdObj == null) {
return null;
}

long consumerId = Long.parseLong(consumerIdObj.toString());
Consumer consumer = consumerService.getConsumerByConsumerId(consumerId);
if (consumer == null) {
return null;
}

// 构建基于Consumer的用户信息
UserInfo userInfo = new UserInfo();
userInfo.setUserId(consumer.getOwnerName());
userInfo.setName(consumer.getName());
userInfo.setEmail(consumer.getOwnerEmail());

return userInfo;
} catch (Exception e) {
// 如果获取Consumer信息失败,返回null,让系统回退到默认方式
return null;
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

OpenAPI consumer resolution never succeeds

request.getAttribute("Authorization") is never populated by the OpenAPI auth filters; the consumer credential only arrives as the HTTP Authorization header (e.g. Apollo <consumerId>:<signature>). Parsing that string with Long.parseLong(...) will therefore always throw and you silently fall back to the legacy path, wiping out the intended OpenAPI user resolution. Please read the header and extract the consumerId before calling consumerService.

-      // 获取Consumer ID
-      Object consumerIdObj = request.getAttribute("Authorization");
-      if (consumerIdObj == null) {
-        return null;
-      }
-
-      long consumerId = Long.parseLong(consumerIdObj.toString());
+      String authorization = request.getHeader("Authorization");
+      if (authorization == null || !authorization.startsWith("Apollo ")) {
+        return null;
+      }
+
+      String credentials = authorization.substring("Apollo ".length());
+      int separator = credentials.indexOf(':');
+      if (separator < 0) {
+        return null;
+      }
+
+      long consumerId = Long.parseLong(credentials.substring(0, separator));
📝 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
// 获取Consumer ID
Object consumerIdObj = request.getAttribute("Authorization");
if (consumerIdObj == null) {
return null;
}
long consumerId = Long.parseLong(consumerIdObj.toString());
Consumer consumer = consumerService.getConsumerByConsumerId(consumerId);
if (consumer == null) {
return null;
}
// 构建基于Consumer的用户信息
UserInfo userInfo = new UserInfo();
userInfo.setUserId(consumer.getOwnerName());
userInfo.setName(consumer.getName());
userInfo.setEmail(consumer.getOwnerEmail());
return userInfo;
} catch (Exception e) {
// 如果获取Consumer信息失败,返回null,让系统回退到默认方式
return null;
// 获取Consumer ID
- Object consumerIdObj = request.getAttribute("Authorization");
- if (consumerIdObj == null) {
- return null;
- }
-
String authorization = request.getHeader("Authorization");
if (authorization == null || !authorization.startsWith("Apollo ")) {
return null;
}
String credentials = authorization.substring("Apollo ".length());
int separator = credentials.indexOf(':');
if (separator < 0) {
return null;
}
long consumerId = Long.parseLong(credentials.substring(0, separator));
Consumer consumer = consumerService.getConsumerByConsumerId(consumerId);
if (consumer == null) {
return null;
}
// 构建基于Consumer的用户信息
UserInfo userInfo = new UserInfo();
userInfo.setUserId(consumer.getOwnerName());
userInfo.setName(consumer.getName());
userInfo.setEmail(consumer.getOwnerEmail());
return userInfo;
} catch (Exception e) {
// 如果获取Consumer信息失败,返回null,让系统回退到默认方式
return null;
🤖 Prompt for AI Agents
In
apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/spi/springsecurity/SpringSecurityUserInfoHolder.java
around lines 83 to 104, the code reads the consumer credential from
request.getAttribute("Authorization") and parses it as a long which never
matches the actual OpenAPI header format; instead, read the HTTP Authorization
header via request.getHeader("Authorization"), verify it follows the expected
"Apollo <consumerId>:<signature>" pattern, extract the substring between the
space and the colon as the consumerId token, parse that token with
Long.parseLong, then call consumerService.getConsumerByConsumerId(consumerId);
if the header is missing or malformed return null (without throwing), and ensure
you only catch unexpected exceptions but do not mask header parsing failures
silently.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
size:XL This PR changes 500-999 lines, ignoring generated files.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[Refactor] 统一 Apollo-portal API 设计规范
1 participant