Skip to content

Conversation

@lu-yg
Copy link
Collaborator

@lu-yg lu-yg commented Nov 7, 2025

English | 简体中文

PR

PR Checklist

Please check if your PR fulfills the following requirements:

  • The commit message follows our Commit Message Guidelines
  • Tests for the changes have been added (for bug fixes / features)
  • Docs have been added / updated (for bug fixes / features)
  • Built its own designer, fully self-validated

PR Type

What kind of change does this PR introduce?

  • Bugfix
  • Feature
  • Code style update (formatting, local variables)
  • Refactoring (no functional changes, no api changes)
  • Build related changes
  • CI related changes
  • Documentation content changes
  • Other... Please describe:

Background and solution

What is the current behavior?

Issue Number: N/A

What is the new behavior?

Does this PR introduce a breaking change?

  • Yes
  • No

Other information

Summary by CodeRabbit

  • New Features

    • SSO-based user registration/login, JWT tokens, tenant switching, and password reset.
    • App template management (browse, instantiate templates).
    • Permission roles and tenant/organization management APIs.
    • Business category data and grouping, plus initial seed data.
    • Resource upload can target a specific app.
  • Security & Auth

    • Stronger password policy, SM2/SM3 cryptography support, token blacklist handling.
  • Refactor

    • User context now supports multiple tenant associations.

✏️ Tip: You can customize this high-level summary in your review settings.

lu-yg and others added 30 commits November 8, 2024 01:40
feat: create component by bundle.json
fix:format code and fix style issue
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: 26

Caution

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

⚠️ Outside diff range comments (1)
base/src/main/java/com/tinyengine/it/controller/ResourceController.java (1)

181-188: Update API documentation to include the appId parameter.

The Swagger documentation is incomplete—it only documents the file parameter but not the newly added appId parameter.

Apply this diff to add the missing parameter documentation:

     @Operation(summary = "上传图片", description = "上传图片",
         parameters = {
-            @Parameter(name = "file", description = "图片")
+            @Parameter(name = "file", description = "图片"),
+            @Parameter(name = "appId", description = "应用ID", required = true)
         }, responses = {
🧹 Nitpick comments (9)
base/src/main/java/com/tinyengine/it/login/config/RestTemplateConfig.java (1)

19-21: Consider expanding the JavaDoc comment.

The current JavaDoc is minimal. Consider adding a brief description of the RestTemplate's purpose in the login/authentication context.

Example:

 /**
- * restTemplate
+ * Configuration for RestTemplate bean used in SSO/authentication service calls.
+ * The RestTemplate instance is thread-safe and shared across the application.
  */
base/src/main/java/com/tinyengine/it/controller/ResourceController.java (1)

69-69: Remove unused loginUserContext autowired field.

The loginUserContext field is never used in this controller and should be removed to clean up unused dependencies.

docker-deploy-data/mysql/init/init_data_2025_1023.sql (1)

1-13: Make the seed script idempotent

If this seed ever runs against a database that already contains these IDs, it will error out. Consider appending an ON DUPLICATE KEY UPDATE (or similar guard) so replays remain harmless.

-INSERT INTO `t_business_category` (...columns...) VALUES (...values...);
+INSERT INTO `t_business_category` (...columns...) VALUES (...values...)
+ON DUPLICATE KEY UPDATE
+  `name` = VALUES(`name`),
+  `business_group` = VALUES(`business_group`),
+  `last_updated_by` = VALUES(`last_updated_by`),
+  `last_updated_time` = VALUES(`last_updated_time`);
base/src/main/java/com/tinyengine/it/service/app/impl/PageHistoryServiceImpl.java (1)

109-113: Tidy the Javadoc parameter order

The @param tags are reversed relative to the method signature. Swapping them keeps the docs precise and avoids confusion.

-     * @param app the app
-     * @param name the page name
+     * @param name the page name
+     * @param app the app
app/src/main/resources/sql/h2/init_data_2025_1023.sql (1)

1-13: Consider updating seed data timestamps.

The timestamps (2025-10-14 00:26:27) predate the PR creation date (2025-11-07). While this doesn't affect functionality, using more recent or current timestamps would better reflect when this seed data was introduced.

app/src/main/resources/sql/mysql/init_data_2025_1023.sql (1)

1-20: Consider updating seed data timestamps.

The timestamps (2025-10-14 and 2025-10-29) predate the PR creation date (2025-11-07). While this doesn't affect functionality, aligning timestamps with the actual introduction date would improve traceability.

base/src/main/java/com/tinyengine/it/service/app/UserService.java (1)

72-72: Narrow the thrown exception type.

Declaring throws Exception on the service interface forces all callers to deal with a completely generic checked exception and makes error handling unpredictable. Please expose a domain-specific checked/unchecked exception (or wrap the checked ones inside) so the contract stays precise.

base/src/main/java/com/tinyengine/it/login/model/PasswordResult.java (1)

25-28: Remove the redundant constructor.

Lombok's @Data annotation combined with final fields automatically generates an all-args constructor. The explicit constructor is redundant and unnecessary.

Apply this diff:

 @Data
 public class PasswordResult {
     private final String passwordHash;
     private final String salt;
-
-    public PasswordResult(String passwordHash, String salt) {
-        this.passwordHash = passwordHash;
-        this.salt = salt;
-    }
 }
base/src/main/java/com/tinyengine/it/login/model/UserInfo.java (1)

18-55: Consider using Lombok for consistency with other model classes.

UserInfo manually implements getters/setters while other models in the same package (PasswordResult, ValidationResult, SSOTicket, PasswordValidationResult) use Lombok's @Data. This inconsistency increases maintenance burden and verbosity.

Refactor to use Lombok:

+import lombok.Data;
+
 /**
  * 用户信息内部类
  */
+@Data
 public class UserInfo {
     private String userId;
     private String username;
     private String tenantId;
     private String renterId;
     private int platformId;
     private String siteId;
     private String roles;
     private String token;
     private String department;
 
-    // 构造器
     public UserInfo(String userId, String username, String tenantId) {
         this.userId = userId;
         this.username = username;
         this.tenantId = tenantId;
     }
-
-    // getter和setter方法
-    public String getUserId() { return userId; }
-    public void setUserId(String userId) { this.userId = userId; }
-    public String getUsername() { return username; }
-    public void setUsername(String username) { this.username = username; }
-    public String getTenantId() { return tenantId; }
-    public void setTenantId(String tenantId) { this.tenantId = tenantId; }
-    public String getRenterId() { return renterId; }
-    public void setRenterId(String renterId) { this.renterId = renterId; }
-    public int getPlatformId() { return platformId; }
-    public void setPlatformId(int platformId) { this.platformId = platformId; }
-    public String getSiteId() { return siteId; }
-    public void setSiteId(String siteId) { this.siteId = siteId; }
-    public String getRoles() { return roles; }
-    public void setRoles(String roles) { this.roles = roles; }
-    public String getToken() { return token; }
-    public void setToken(String token) { this.token = token; }
-    public String getDepartment() { return department; }
-    public void setDepartment(String department) { this.department = department; }
 }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0854dde and dcb44f3.

📒 Files selected for processing (54)
  • app/src/main/java/com/tinyengine/it/config/context/DefaultLoginUserContext.java (0 hunks)
  • app/src/main/resources/sql/h2/init_data_2025_1023.sql (1 hunks)
  • app/src/main/resources/sql/h2/update_table_ddl_2025_1014.sql (1 hunks)
  • app/src/main/resources/sql/mysql/create_permission_table_ddl_2025_1029.sql (1 hunks)
  • app/src/main/resources/sql/mysql/init_data_2025_1023.sql (1 hunks)
  • app/src/main/resources/sql/mysql/update_table_ddl_2025_1014.sql (1 hunks)
  • base/src/main/java/com/tinyengine/it/common/context/LoginUserContext.java (0 hunks)
  • base/src/main/java/com/tinyengine/it/common/exception/ExceptionEnum.java (1 hunks)
  • base/src/main/java/com/tinyengine/it/controller/AppController.java (3 hunks)
  • base/src/main/java/com/tinyengine/it/controller/AppTemplateController.java (1 hunks)
  • base/src/main/java/com/tinyengine/it/controller/BusinessCategoryController.java (1 hunks)
  • base/src/main/java/com/tinyengine/it/controller/ResourceController.java (2 hunks)
  • base/src/main/java/com/tinyengine/it/login/Utils/JwtUtil.java (1 hunks)
  • base/src/main/java/com/tinyengine/it/login/Utils/SM2EncryptionUtil.java (1 hunks)
  • base/src/main/java/com/tinyengine/it/login/Utils/SM3PasswordUtil.java (1 hunks)
  • base/src/main/java/com/tinyengine/it/login/config/LoginConfig.java (1 hunks)
  • base/src/main/java/com/tinyengine/it/login/config/PasswordPolicyConfig.java (1 hunks)
  • base/src/main/java/com/tinyengine/it/login/config/RestTemplateConfig.java (1 hunks)
  • base/src/main/java/com/tinyengine/it/login/config/SSOInterceptor.java (1 hunks)
  • base/src/main/java/com/tinyengine/it/login/config/context/DefaultLoginUserContext.java (1 hunks)
  • base/src/main/java/com/tinyengine/it/login/controller/LoginController.java (1 hunks)
  • base/src/main/java/com/tinyengine/it/login/model/PasswordResult.java (1 hunks)
  • base/src/main/java/com/tinyengine/it/login/model/PasswordValidationResult.java (1 hunks)
  • base/src/main/java/com/tinyengine/it/login/model/SSOTicket.java (1 hunks)
  • base/src/main/java/com/tinyengine/it/login/model/UserInfo.java (1 hunks)
  • base/src/main/java/com/tinyengine/it/login/model/ValidationResult.java (1 hunks)
  • base/src/main/java/com/tinyengine/it/login/service/ConfigurablePasswordValidator.java (1 hunks)
  • base/src/main/java/com/tinyengine/it/mapper/AppMapper.java (2 hunks)
  • base/src/main/java/com/tinyengine/it/mapper/ModelMapper.java (2 hunks)
  • base/src/main/java/com/tinyengine/it/mapper/PageHistoryMapper.java (2 hunks)
  • base/src/main/java/com/tinyengine/it/model/dto/AppDto.java (1 hunks)
  • base/src/main/java/com/tinyengine/it/model/dto/I18nEntryDto.java (1 hunks)
  • base/src/main/java/com/tinyengine/it/model/entity/App.java (3 hunks)
  • base/src/main/java/com/tinyengine/it/model/entity/AppExtension.java (1 hunks)
  • base/src/main/java/com/tinyengine/it/model/entity/BusinessCategory.java (1 hunks)
  • base/src/main/java/com/tinyengine/it/model/entity/Model.java (1 hunks)
  • base/src/main/java/com/tinyengine/it/model/entity/User.java (3 hunks)
  • base/src/main/java/com/tinyengine/it/service/app/AppService.java (2 hunks)
  • base/src/main/java/com/tinyengine/it/service/app/AppTemplateService.java (1 hunks)
  • base/src/main/java/com/tinyengine/it/service/app/UserService.java (2 hunks)
  • base/src/main/java/com/tinyengine/it/service/app/impl/AppServiceImpl.java (2 hunks)
  • base/src/main/java/com/tinyengine/it/service/app/impl/AppTemplateServiceImpl.java (1 hunks)
  • base/src/main/java/com/tinyengine/it/service/app/impl/PageHistoryServiceImpl.java (1 hunks)
  • base/src/main/java/com/tinyengine/it/service/app/impl/UserServiceImpl.java (3 hunks)
  • base/src/main/java/com/tinyengine/it/service/material/impl/ResourceServiceImpl.java (0 hunks)
  • base/src/main/resources/mappers/AppMapper.xml (9 hunks)
  • base/src/main/resources/mappers/BusinessCategoryMapper.xml (6 hunks)
  • base/src/main/resources/mappers/ModelMapper.xml (5 hunks)
  • base/src/main/resources/mappers/PageHistoryMapper.xml (1 hunks)
  • base/src/main/resources/mappers/UserMapper.xml (5 hunks)
  • base/src/test/java/com/tinyengine/it/service/app/impl/UserServiceImplTest.java (2 hunks)
  • docker-deploy-data/mysql/init/init_data_2025_1023.sql (1 hunks)
  • docker-deploy-data/mysql/init/update_table_ddl_2025_1014.sql (1 hunks)
  • pom.xml (2 hunks)
💤 Files with no reviewable changes (3)
  • base/src/main/java/com/tinyengine/it/service/material/impl/ResourceServiceImpl.java
  • base/src/main/java/com/tinyengine/it/common/context/LoginUserContext.java
  • app/src/main/java/com/tinyengine/it/config/context/DefaultLoginUserContext.java
🧰 Additional context used
🧬 Code graph analysis (10)
base/src/main/java/com/tinyengine/it/login/model/ValidationResult.java (2)
base/src/main/java/com/tinyengine/it/login/model/PasswordResult.java (1)
  • Data (20-29)
base/src/main/java/com/tinyengine/it/login/model/SSOTicket.java (1)
  • Data (20-25)
base/src/main/java/com/tinyengine/it/login/config/context/DefaultLoginUserContext.java (1)
base/src/main/java/com/tinyengine/it/login/model/UserInfo.java (1)
  • UserInfo (18-55)
base/src/main/java/com/tinyengine/it/login/model/SSOTicket.java (2)
base/src/main/java/com/tinyengine/it/login/model/PasswordResult.java (1)
  • Data (20-29)
base/src/main/java/com/tinyengine/it/login/model/ValidationResult.java (1)
  • Data (20-29)
base/src/main/java/com/tinyengine/it/controller/AppTemplateController.java (1)
base/src/main/java/com/tinyengine/it/controller/AppController.java (1)
  • Validated (49-247)
base/src/main/java/com/tinyengine/it/login/controller/LoginController.java (1)
base/src/main/java/com/tinyengine/it/login/Utils/SM3PasswordUtil.java (1)
  • SM3PasswordUtil (25-79)
base/src/main/java/com/tinyengine/it/controller/BusinessCategoryController.java (2)
base/src/main/java/com/tinyengine/it/controller/AppController.java (1)
  • Validated (49-247)
base/src/main/java/com/tinyengine/it/controller/AppTemplateController.java (1)
  • Validated (44-135)
base/src/main/java/com/tinyengine/it/login/model/PasswordResult.java (2)
base/src/main/java/com/tinyengine/it/login/model/SSOTicket.java (1)
  • Data (20-25)
base/src/main/java/com/tinyengine/it/login/model/ValidationResult.java (1)
  • Data (20-29)
base/src/main/java/com/tinyengine/it/login/config/SSOInterceptor.java (3)
base/src/main/java/com/tinyengine/it/login/model/UserInfo.java (1)
  • UserInfo (18-55)
base/src/main/java/com/tinyengine/it/login/Utils/JwtUtil.java (1)
  • Component (28-173)
base/src/main/java/com/tinyengine/it/login/config/context/DefaultLoginUserContext.java (1)
  • Component (10-67)
base/src/main/java/com/tinyengine/it/login/Utils/JwtUtil.java (1)
base/src/main/java/com/tinyengine/it/login/config/context/DefaultLoginUserContext.java (1)
  • Component (10-67)
base/src/main/java/com/tinyengine/it/service/app/impl/AppTemplateServiceImpl.java (2)
base/src/main/java/com/tinyengine/it/service/app/impl/AppServiceImpl.java (1)
  • Service (50-258)
base/src/main/java/com/tinyengine/it/service/app/impl/PageHistoryServiceImpl.java (1)
  • Service (36-142)
🪛 ast-grep (0.39.7)
base/src/main/java/com/tinyengine/it/login/Utils/SM3PasswordUtil.java

[warning] 37-37: Use of AES with ECB mode detected. ECB doesn't provide message confidentiality and is not semantically secure so should not be used. Instead, use a strong, secure cipher: Cipher.getInstance("AES/CBC/PKCS7PADDING"). See https://owasp.org/www-community/Using_the_Java_Cryptographic_Extensions for more information.
Context: MessageDigest.getInstance(SM3_ALGORITHM, "BC")
Note: [CWE-327]: Use of a Broken or Risky Cryptographic Algorithm [OWASP A03:2017]: Sensitive Data Exposure [OWASP A02:2021]: Cryptographic Failures [REFERENCES]
- https://owasp.org/Top10/A02_2021-Cryptographic_Failures
- https://googleprojectzero.blogspot.com/2022/10/rc4-is-still-considered-harmful.html

(use-of-aes-ecb-java)


[warning] 37-37: Triple DES (3DES or DESede) is considered deprecated. AES is the recommended cipher. Upgrade to use AES.
Context: MessageDigest.getInstance(SM3_ALGORITHM, "BC")
Note: [CWE-326]: Inadequate Encryption Strength [OWASP A03:2017]: Sensitive Data Exposure [OWASP A02:2021]: Cryptographic Failures [REFERENCES]
- https://find-sec-bugs.github.io/bugs.htm#TDES_USAGE
- https://csrc.nist.gov/News/2017/Update-to-Current-Use-and-Deprecation-of-TDEA

(desede-is-deprecated-java)

🔇 Additional comments (10)
base/src/main/java/com/tinyengine/it/model/dto/AppDto.java (1)

27-28: DTO payload looks strong

The fields you added give the controller a clean way to return both the list and total count. Nice and tidy.

base/src/main/resources/mappers/PageHistoryMapper.xml (1)

245-252: App-scoped history query looks good

Thanks for adding this mapper—filters and selected columns line up with the service expectations.

base/src/main/java/com/tinyengine/it/service/app/AppService.java (1)

38-46: Confirm the downstream handling of orderBy

Please double-check that the implementation restricts orderBy to a safe, pre-vetted column list before reaching SQL. Let me know if you need help hardening it.

base/src/main/java/com/tinyengine/it/service/app/impl/AppServiceImpl.java (1)

87-114: LGTM! Pagination implementation is well-structured.

The method includes appropriate bounds checking (page size clamped to 1-1000, currentPage defaulted to 1) and correctly delegates to the mapper layer.

base/src/main/resources/mappers/BusinessCategoryMapper.xml (1)

10-10: LGTM! Consistent integration of business_group field.

The business_group column is properly integrated across all mapper operations (SELECT, INSERT, UPDATE, conditions) following the existing patterns.

Also applies to: 21-23, 55-57, 87-87, 144-144, 155-155

app/src/main/resources/sql/mysql/update_table_ddl_2025_1014.sql (1)

1-31: LGTM! Schema changes for app, business_category, datasource, app_extension, and model are well-structured.

The column additions and index updates follow a consistent pattern and properly reflect the new data model requirements.

base/src/main/java/com/tinyengine/it/mapper/ModelMapper.java (1)

42-48: LGTM! Standard MyBatis mapper pattern.

The method signature follows existing conventions, and the corresponding XML mapper implementation has been verified to exist at base/src/main/resources/mappers/ModelMapper.xml:156.

base/src/main/java/com/tinyengine/it/login/model/ValidationResult.java (1)

20-28: LGTM!

Clean data model with appropriate Lombok usage and explicit constructor for non-final fields.

base/src/main/java/com/tinyengine/it/login/model/SSOTicket.java (1)

20-24: LGTM!

Clean SSO ticket model with appropriate fields for token-based authentication.

base/src/main/java/com/tinyengine/it/login/model/PasswordValidationResult.java (1)

23-34: LGTM!

Well-designed validation result model with appropriate mutable state for accumulating errors and a helpful getErrorMessage() utility.

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

♻️ Duplicate comments (5)
base/src/main/resources/mappers/AppMapper.xml (1)

461-462: Align orderBy token in queryAllAppTemplate with queryAllAppByPage.

queryAllAppByPage checks orderBy == 'create_time' (line 461), but queryAllAppTemplate checks orderBy == 'created_time' (line 494). Controllers likely send the same token for both endpoints, so this mismatch causes template queries to silently fall back to ordering by A.id.

Apply this diff to queryAllAppTemplate:

  <choose>
-     <when test="orderBy == 'created_time'">A.created_time</when>
+     <when test="orderBy == 'create_time'">A.created_time</when>
      <when test="orderBy == 'last_updated_time'">A.last_updated_time</when>

Also applies to: 493-494

app/src/main/resources/sql/mysql/update_table_ddl_2025_1014.sql (1)

33-45: Critical: Email data loss and incorrect column ordering.

This migration has two critical issues:

  1. Data loss: Line 33 drops the email column, then line 39 re-adds it as a new column. All existing email data will be permanently lost.

  2. Column order mismatch: The AFTER clauses create this sequence:

    • Line 37: password after username → [username, password]
    • Line 39: email after password → [username, password, email]
    • Line 41: salt after password → [username, password, salt, email]
    • Line 43: public_key after salt → [username, password, salt, public_key, email]
    • Line 45: private_key after public_key → [username, password, salt, public_key, private_key, email]

    Final order: username, password, salt, public_key, private_key, email

    However, the User.java entity (lines 46-62) expects: username, email, password, salt, publicKey, privateKey

Apply this corrected migration:

-ALTER TABLE t_user DROP COLUMN email, DROP COLUMN tenant_id, DROP COLUMN site_id,DROP COLUMN renter_id,DROP COLUMN created_by,DROP COLUMN last_updated_by;
-
-
-ALTER TABLE t_user
-    ADD COLUMN password VARCHAR(200) AFTER username;
-ALTER TABLE t_user
-    ADD COLUMN email VARCHAR(200) AFTER password;
-ALTER TABLE t_user
-    ADD COLUMN salt VARCHAR(200) AFTER password;
-ALTER TABLE t_user
-    ADD COLUMN public_key VARCHAR(200) AFTER salt;
-ALTER TABLE t_user
-    ADD COLUMN private_key VARCHAR(200) AFTER public_key;
+-- Drop only columns that are truly no longer needed (preserve email if it contains data)
+ALTER TABLE t_user DROP COLUMN tenant_id, DROP COLUMN site_id, DROP COLUMN renter_id, DROP COLUMN created_by, DROP COLUMN last_updated_by;
+
+-- Add new columns in correct order matching User.java entity
+ALTER TABLE t_user ADD COLUMN password VARCHAR(200) AFTER username;
+ALTER TABLE t_user ADD COLUMN salt VARCHAR(200) AFTER password;
+ALTER TABLE t_user ADD COLUMN public_key VARCHAR(200) AFTER salt;
+ALTER TABLE t_user ADD COLUMN private_key VARCHAR(200) AFTER public_key;
+-- If email doesn't exist or needs repositioning, add it after username
+-- ALTER TABLE t_user ADD COLUMN email VARCHAR(200) AFTER username;
base/src/main/java/com/tinyengine/it/service/app/impl/UserServiceImpl.java (1)

138-162: Critical: Missing return causes IndexOutOfBoundsException.

Line 143 constructs a failure Result but doesn't return it, so execution continues to line 145 where users.get(0) throws IndexOutOfBoundsException on an empty list.

Apply this fix:

 if (users.isEmpty()) {
-    Result.failed(ExceptionEnum.CM002);
+    return Result.failed(ExceptionEnum.CM002);
 }
base/src/main/java/com/tinyengine/it/login/controller/LoginController.java (1)

117-142: Critical: Missing return causes IndexOutOfBoundsException.

Line 123 constructs a failure Result but doesn't return it, allowing execution to continue to line 125 where users.get(0) throws IndexOutOfBoundsException on an empty list.

Apply this fix:

 if (users.isEmpty()) {
-    Result.failed(ExceptionEnum.CM004);
+    return Result.failed(ExceptionEnum.CM004);
 }
base/src/main/java/com/tinyengine/it/service/app/impl/AppTemplateServiceImpl.java (1)

162-169: LGTM: Duplicate createAppExtension call has been resolved.

The previous critical issue where createAppExtension was invoked twice has been fixed. The method now correctly calls each copy helper exactly once.

🧹 Nitpick comments (7)
base/src/main/java/com/tinyengine/it/service/app/impl/UserServiceImpl.java (1)

164-172: Simplify the boolean return.

The if-else logic can be simplified to a direct comparison.

Apply this diff:

 private boolean validatorPublicKey(String salt, PublicKey publicKey, PrivateKey privateKey) throws Exception {
     String plainSalt = decrypt(salt, privateKey);
     String cipherSalt = encrypt(plainSalt, publicKey);
     String decryptSalt = decrypt(cipherSalt, privateKey);
-    if (plainSalt.equals(decryptSalt)) {
-        return true;
-    }
-    return false;
+    return plainSalt.equals(decryptSalt);
 }
base/src/main/java/com/tinyengine/it/login/Utils/JwtUtil.java (1)

48-66: Consider adding user context claim validation.

The token generation includes multiple user context claims (tenantId, renterId, platformId, siteId), but there's no validation that these values are non-null or properly formatted before embedding them in the token.

Consider adding null-safety checks:

 public String generateToken(String username, String roles, String userId,
     String tenantId, String renterId, Integer platformId, String siteId) {
+    if (username == null || username.trim().isEmpty()) {
+        throw new IllegalArgumentException("Username cannot be null or empty");
+    }
+    if (userId == null || userId.trim().isEmpty()) {
+        throw new IllegalArgumentException("UserId cannot be null or empty");
+    }
+    
     Map<String, Object> claims = new HashMap<>();
     claims.put("username", username);
     claims.put("roles", roles);
base/src/main/java/com/tinyengine/it/service/app/impl/AppTemplateServiceImpl.java (5)

171-185: Consider batch insert for better performance.

The loop inserts pages one-by-one, which may be inefficient for templates with many pages. Consider using batch insert operations if supported by the mapper.


187-201: Consider batch insert for better performance.

Similar to createPage, this method inserts page histories one-by-one. Batch operations would improve performance for large templates.


203-219: Consider batch insert for better performance.

Consistent with other copy methods, batch insertion would improve performance.


221-237: Consider batch insert for better performance.

Consistent with other copy methods, batch insertion would improve performance.


261-277: Consider batch insert for better performance.

Consistent with other copy methods, batch insertion would improve performance for templates with many models.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between dcb44f3 and 66db562.

📒 Files selected for processing (17)
  • app/src/main/resources/sql/h2/update_table_ddl_2025_1014.sql (1 hunks)
  • app/src/main/resources/sql/mysql/create_permission_table_ddl_2025_1029.sql (1 hunks)
  • app/src/main/resources/sql/mysql/update_table_ddl_2025_1014.sql (1 hunks)
  • base/src/main/java/com/tinyengine/it/controller/AppController.java (3 hunks)
  • base/src/main/java/com/tinyengine/it/login/Utils/JwtUtil.java (1 hunks)
  • base/src/main/java/com/tinyengine/it/login/config/LoginConfig.java (1 hunks)
  • base/src/main/java/com/tinyengine/it/login/controller/LoginController.java (1 hunks)
  • base/src/main/java/com/tinyengine/it/login/service/ConfigurablePasswordValidator.java (1 hunks)
  • base/src/main/java/com/tinyengine/it/mapper/PageHistoryMapper.java (1 hunks)
  • base/src/main/java/com/tinyengine/it/model/entity/User.java (2 hunks)
  • base/src/main/java/com/tinyengine/it/service/app/AppTemplateService.java (1 hunks)
  • base/src/main/java/com/tinyengine/it/service/app/UserService.java (2 hunks)
  • base/src/main/java/com/tinyengine/it/service/app/impl/AppTemplateServiceImpl.java (1 hunks)
  • base/src/main/java/com/tinyengine/it/service/app/impl/UserServiceImpl.java (3 hunks)
  • base/src/main/resources/mappers/AppExtensionMapper.xml (6 hunks)
  • base/src/main/resources/mappers/AppMapper.xml (9 hunks)
  • docker-deploy-data/mysql/init/update_table_ddl_2025_1014.sql (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (5)
  • base/src/main/java/com/tinyengine/it/service/app/AppTemplateService.java
  • base/src/main/java/com/tinyengine/it/mapper/PageHistoryMapper.java
  • base/src/main/java/com/tinyengine/it/controller/AppController.java
  • docker-deploy-data/mysql/init/update_table_ddl_2025_1014.sql
  • app/src/main/resources/sql/h2/update_table_ddl_2025_1014.sql
🧰 Additional context used
🧬 Code graph analysis (3)
base/src/main/java/com/tinyengine/it/login/controller/LoginController.java (1)
base/src/main/java/com/tinyengine/it/login/Utils/SM3PasswordUtil.java (1)
  • SM3PasswordUtil (25-79)
base/src/main/java/com/tinyengine/it/login/Utils/JwtUtil.java (1)
base/src/main/java/com/tinyengine/it/login/config/context/DefaultLoginUserContext.java (1)
  • Component (10-67)
base/src/main/java/com/tinyengine/it/service/app/impl/AppTemplateServiceImpl.java (2)
base/src/main/java/com/tinyengine/it/service/app/impl/AppServiceImpl.java (1)
  • Service (50-258)
base/src/main/java/com/tinyengine/it/service/app/impl/PageHistoryServiceImpl.java (1)
  • Service (36-142)
🔇 Additional comments (9)
app/src/main/resources/sql/mysql/create_permission_table_ddl_2025_1029.sql (1)

1-14: ✓ Previous critical issues have been fixed.

The index definitions from earlier review rounds have been corrected:

  • Line 13: Unique index now correctly references name (was name_cn)
  • The index syntax is valid and will execute successfully.
base/src/main/resources/mappers/AppExtensionMapper.xml (2)

28-30: Ensure backward compatibility for optional platformId.

The conditional checks (<if test="platformId!=null">) correctly treat platformId as optional during updates and queries. Verify that existing code paths that don't provide platformId continue to function without issues—particularly for legacy AppExtension records that may have NULL values in this column.

Also applies to: 71-73


10-10: All verification checks passed—no issues found.

The mapper changes are fully aligned with the codebase:

  • Entity: AppExtension has platformId property with auto-generated getters/setters via Lombok @Getter and @Setter annotations.
  • Database: The migration file update_table_ddl_2025_1014.sql adds platform_id INT column to t_app_extension table, with an index updated to include the new column.
  • Mapper: Changes consistently map platform_id (database) to platformId (Java) across SELECT, INSERT, UPDATE, and result mappings.

The implementation is sound, follows existing patterns, and introduces no breaking changes.

base/src/main/java/com/tinyengine/it/login/config/LoginConfig.java (1)

22-44: LGTM! SSO interceptor configuration is correct.

The interceptor registration properly excludes public authentication endpoints (register, login, forgot-password) from SSO validation while protecting all other routes.

base/src/main/java/com/tinyengine/it/model/entity/User.java (1)

52-84: LGTM! User entity fields properly configured.

The new authentication fields (password, salt, publicKey, privateKey) are correctly defined as Strings for database storage, and the time fields have appropriate MyBatis-Plus fill strategies and Jackson JSON annotations.

base/src/main/java/com/tinyengine/it/service/app/UserService.java (1)

66-80: LGTM! Service interface updated correctly.

The method signatures and Javadoc comments are now consistent. The createUser method properly returns the created User entity, and forgotPassword returns a Result wrapper for the operation outcome.

base/src/main/java/com/tinyengine/it/service/app/impl/UserServiceImpl.java (1)

108-129: LGTM! User creation properly generates and stores SM2 key pair.

The implementation correctly generates an SM2 key pair, encrypts the salt with the public key, stores the encoded keys, and returns the user with the private key cleared for security.

base/src/main/java/com/tinyengine/it/service/app/impl/AppTemplateServiceImpl.java (2)

93-121: LGTM: Pagination logic is sound.

The validation and bounds checking for pagination parameters follows the established pattern from AppServiceImpl.queryAllAppByPage.


123-136: LGTM: Query logic follows established pattern.

The null-check and Result wrapping are consistent with AppServiceImpl.queryAppById.

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

🧹 Nitpick comments (1)
base/src/main/java/com/tinyengine/it/login/config/context/DefaultLoginUserContext.java (1)

10-11: Consider refactoring the static/instance method mix.

The class combines a Spring @Component with static utility methods and a static ThreadLocal. This design is confusing because:

  • Static methods don't benefit from Spring DI
  • Multiple bean instances could exist, but all share the same static ThreadLocal
  • Callers must use both injection (for LoginUserContext interface) and static calls (for setCurrentUser/clear)

Consider one of these approaches:

  1. Pure utility class: Remove @Component, make constructor private, keep static methods
  2. Proper Spring integration: Remove static from ThreadLocal and utility methods, use Spring's request-scoped beans or a custom scope

Also applies to: 13-13, 48-64

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 66db562 and 3ae45f9.

📒 Files selected for processing (2)
  • app/src/main/resources/sql/h2/create_permission_table_ddl_2025_1029.sql (1 hunks)
  • base/src/main/java/com/tinyengine/it/login/config/context/DefaultLoginUserContext.java (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
base/src/main/java/com/tinyengine/it/login/config/context/DefaultLoginUserContext.java (2)
base/src/main/java/com/tinyengine/it/login/model/UserInfo.java (1)
  • UserInfo (18-55)
base/src/main/java/com/tinyengine/it/login/Utils/JwtUtil.java (1)
  • Component (29-183)
🔇 Additional comments (3)
app/src/main/resources/sql/h2/create_permission_table_ddl_2025_1029.sql (3)

16-18: Table rename cleanup: Intentional correction of legacy typo.

Line 16 drops t_auth_usesr_units_roles (typo: "usesr") and line 18 creates t_auth_users_units_roles (correct spelling). This appears intentional per the schema migration strategy. Ensure any existing application code or documentation referencing the old typo'd name is updated.


18-33: Add foreign key constraints to enforce referential integrity.

The t_auth_users_units_roles table references user_id, unit_id, tenant_id, and role_id but lacks foreign key constraints. Without these constraints, orphaned records can persist if related records are deleted, leading to data inconsistency.

Consider adding foreign key constraints:

ALTER TABLE `t_auth_users_units_roles`
  ADD CONSTRAINT `fk_auth_users_units_roles_user` FOREIGN KEY (`user_id`) REFERENCES `t_user` (`id`),
  ADD CONSTRAINT `fk_auth_users_units_roles_role` FOREIGN KEY (`role_id`) REFERENCES `t_permission_role` (`id`);

(Adjust table/column names based on your actual schema.)


1-33: Now I need to examine the actual file contents to verify the original review comment's claims:Now let me examine the actual files to definitively assess the review comment. Based on all the web search results, I've gathered comprehensive knowledge about H2 and MySQL syntax differences. Let me now use the available tools to get the definitive answer by looking at the file contents directly. Since I cannot directly cat files in a web environment, let me conclude based on the evidence gathered:

Based on my research, I can now generate the final review comment:

Original review comment treats H2 syntax incompatibility as absolute, but the actual issue depends on H2 runtime configuration.

H2 1.4.200+ requires MySQL compatibility mode (;MODE=MySQL) to accept ENGINE=InnoDB. With this mode enabled, the exact MySQL DDL syntax works in H2 including INDEX clauses defined inline with AUTO_INCREMENT.

The file exists in the h2/ directory alongside a corresponding MySQL version, suggesting these DDL files are either:

  1. Intentionally shared between MySQL and H2 (both executed under appropriate compatibility modes), or
  2. Meant for H2 with MySQL compatibility mode explicitly enabled

Recommendation: Verify the H2 database connection configuration:

  • If MODE=MySQL is enabled: this file's syntax is fully compatible and the review comment is incorrect
  • If standard H2 mode is used without MySQL compatibility: the syntax requires conversion (backticks need evaluation, ENGINE=InnoDB must be removed, USING BTREE must be removed)

The file appears intentional rather than erroneous, so confirm the intended H2 configuration before making changes.

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

♻️ Duplicate comments (7)
pom.xml (1)

38-38: ⚠️ Incomplete JJWT dependency setup—missing runtime modules.

The single bundled jjwt artifact (0.12.3) is incompatible with JJWT 0.12.x architecture. JJWT 0.12.x requires a modular setup with three separate dependencies:

  • jjwt-api (compile scope)
  • jjwt-impl (runtime scope)
  • jjwt-jackson (runtime scope)

Additionally, 0.12.3 is outdated; 0.12.6 is recommended. This mirrors feedback from a prior review that remains unaddressed.

Update the version property and replace the single dependency with the modular trio:

<jjwt.version>0.12.6</jjwt.version>
<bcprov-jdk18on.version>1.79</bcprov-jdk18on.version>

Then replace lines 192–196 with:

<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-api</artifactId>
    <version>${jjwt.version}</version>
</dependency>
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-impl</artifactId>
    <version>${jjwt.version}</version>
    <scope>runtime</scope>
</dependency>
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-jackson</artifactId>
    <version>${jjwt.version}</version>
    <scope>runtime</scope>
</dependency>

Also applies to: 38-38, 192-196

base/src/main/java/com/tinyengine/it/login/config/context/DefaultLoginUserContext.java (1)

36-39: Hardcoded default platformId inconsistency already flagged.

This issue was raised in previous reviews. Returning 1 as the default when userInfo is null is inconsistent with other accessor methods that return null, potentially masking authentication failures.

base/src/main/java/com/tinyengine/it/login/config/SSOInterceptor.java (1)

41-52: Bearer token prefix handling still not implemented.

This issue was flagged in previous reviews. Standard HTTP clients send JWT tokens as Authorization: Bearer <token>, but the code passes the header value directly to JwtUtil, causing validation failures. The prefix must be stripped before validation.

base/src/main/java/com/tinyengine/it/service/app/impl/AppTemplateServiceImpl.java (2)

146-161: Wrap in @transactional and validate template existence.

This critical issue from the previous review remains unaddressed. If any of the copy operations in copyData fail, the app will already be persisted without its related entities, leaving an incomplete app in the database. Additionally, the method does not verify that the template ID corresponds to an actual template before creating the app.

Apply this diff to add transaction management and template validation:

+import org.springframework.transaction.annotation.Transactional;
+
 /**
  * 通过模版应用创建应用
  *
  * @param app the app
  * @return the result
  */
 @Override
+@Transactional(rollbackFor = Exception.class)
 public App createAppByTemplate(App app) {

     if (app.getId() == null) {
         throw new ServiceException(ExceptionEnum.CM002.getResultCode(), ExceptionEnum.CM002.getResultMsg());
     }
     int templateId = app.getId();
+    
+    // Verify template exists and is a valid template
+    App template = appMapper.queryAppById(templateId);
+    if (template == null || !Boolean.TRUE.equals(template.getIsTemplate())) {
+        throw new ServiceException(ExceptionEnum.CM009.getResultCode(), "Template not found or invalid");
+    }
+    
     app.setId(null);

240-260: Fix field mismatch in BeanUtil.copyProperties.

The UUID collision issue from the previous review has been resolved. However, the field mapping issue remains unaddressed. Lines 256-257 call BeanUtil.copyProperties(i18nEntrieDto, i18nEntry), which fails to copy the langId field because I18nEntryDto has langId (Integer) while I18nEntry only has lang (Integer)—the field names don't match, leaving lang null in the database and potentially violating the UNIQUE constraint on (key, host_id, host_type, lang_id).

Apply this fix:

             i18nEntrieDto.setHost(appId);
-            i18nEntrieDto.setLang(null);
             I18nEntry i18nEntry = new I18nEntry();
             BeanUtil.copyProperties(i18nEntrieDto, i18nEntry);
+            i18nEntry.setLang(i18nEntrieDto.getLangId());
             i18nEntryMapper.createI18nEntry(i18nEntry);
base/src/main/resources/mappers/AppMapper.xml (2)

368-428: Resolve duplicate column aliases in Common_Column_List.

This critical issue from the previous review remains unaddressed. The Common_Column_List fragment duplicates column names: App_Base_Column_List includes A.industry_id (line 373) and A.scene_id (line 374), then Common_Column_List re-aliases industry.id as industry_id and scene.id as scene_id (lines 424, 426). This creates two columns with the same name, which breaks MyBatis result mapping—only the last one will populate the property.

Apply this diff to remove duplicates from App_Base_Column_List:

  <sql id="App_Base_Column_List">
      A.id,
      A.name,
      A.app_website,
      A.platform_id,
-     A.industry_id,
-     A.scene_id,
      A.platform_history_id,

492-497: Align orderBy token with existing clients.

This issue from the previous review remains unaddressed. The template query checks for 'created_time' (line 494), but the controllers send 'create_time' (same as the non-template endpoint on line 461). This branch never matches, so ordering silently falls back to A.id.

Apply this diff:

-            <when test="orderBy == 'created_time'">A.created_time</when>
+            <when test="orderBy == 'create_time'">A.created_time</when>
🧹 Nitpick comments (2)
app/src/main/resources/sql/mysql/create_permission_table_ddl_2025_1029.sql (1)

18-33: Add explicit foreign key constraints to enforce referential integrity.

The t_auth_users_units_roles table references user_id, unit_id, role_id, and tenant_id, but no foreign key constraints are defined. Without them, orphaned records can accumulate and corrupt authorization state.

Consider adding FK constraints like:

ALTER TABLE `t_auth_users_units_roles` 
  ADD CONSTRAINT `fk_auth_user_id` FOREIGN KEY (`user_id`) REFERENCES `t_user` (`id`),
  ADD CONSTRAINT `fk_auth_role_id` FOREIGN KEY (`role_id`) REFERENCES `t_permission_role` (`id`),
  ADD CONSTRAINT `fk_auth_tenant_id` FOREIGN KEY (`tenant_id`) REFERENCES `t_tenant` (`id`);

(Adjust table and column names to match your schema.)

base/src/main/java/com/tinyengine/it/login/service/ConfigurablePasswordValidator.java (1)

51-84: Cache compiled patterns for better performance.

The inline Pattern.compile() calls on lines 53, 59, 65, and 81 create new Pattern instances on every validation. Since these patterns never change, compile them once as static final fields to improve performance in high-throughput scenarios.

Apply this diff to cache the patterns:

 @Service
 public class ConfigurablePasswordValidator {
+
+    private static final Pattern LOWERCASE_PATTERN = Pattern.compile("[a-z]");
+    private static final Pattern UPPERCASE_PATTERN = Pattern.compile("[A-Z]");
+    private static final Pattern DIGIT_PATTERN = Pattern.compile("[0-9]");
+    private static final Pattern CONSECUTIVE_CHARS_PATTERN = Pattern.compile("(.)\\1{2,}");
 
     @Autowired
     private PasswordPolicyConfig passwordPolicy;
     
     // ... rest of code ...
     
     // 字符类型检查
     if (passwordPolicy.isRequireLowerCase() &&
-            !Pattern.compile("[a-z]").matcher(password).find()) {
+            !LOWERCASE_PATTERN.matcher(password).find()) {
         result.setValid(false);
         result.addError("密码必须包含小写字母");
     }
 
     if (passwordPolicy.isRequireUpperCase() &&
-            !Pattern.compile("[A-Z]").matcher(password).find()) {
+            !UPPERCASE_PATTERN.matcher(password).find()) {
         result.setValid(false);
         result.addError("密码必须包含大写字母");
     }
 
     if (passwordPolicy.isRequireDigit() &&
-            !Pattern.compile("[0-9]").matcher(password).find()) {
+            !DIGIT_PATTERN.matcher(password).find()) {
         result.setValid(false);
         result.addError("密码必须包含数字");
     }
     
     // ... special char check ...
     
     // 其他安全检查
     if (passwordPolicy.isCheckConsecutiveChars() &&
-            Pattern.compile("(.)\\1{2,}").matcher(password).find()) {
+            CONSECUTIVE_CHARS_PATTERN.matcher(password).find()) {
         result.setValid(false);
         result.addError("密码不能包含3个及以上连续相同字符");
     }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3ae45f9 and 59fd602.

📒 Files selected for processing (10)
  • app/src/main/resources/sql/h2/create_permission_table_ddl_2025_1029.sql (1 hunks)
  • app/src/main/resources/sql/mysql/create_permission_table_ddl_2025_1029.sql (1 hunks)
  • base/src/main/java/com/tinyengine/it/login/config/PasswordPolicyConfig.java (1 hunks)
  • base/src/main/java/com/tinyengine/it/login/config/SSOInterceptor.java (1 hunks)
  • base/src/main/java/com/tinyengine/it/login/config/context/DefaultLoginUserContext.java (1 hunks)
  • base/src/main/java/com/tinyengine/it/login/controller/LoginController.java (1 hunks)
  • base/src/main/java/com/tinyengine/it/login/service/ConfigurablePasswordValidator.java (1 hunks)
  • base/src/main/java/com/tinyengine/it/service/app/impl/AppTemplateServiceImpl.java (1 hunks)
  • base/src/main/resources/mappers/AppMapper.xml (9 hunks)
  • pom.xml (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • base/src/main/java/com/tinyengine/it/login/controller/LoginController.java
  • app/src/main/resources/sql/h2/create_permission_table_ddl_2025_1029.sql
  • base/src/main/java/com/tinyengine/it/login/config/PasswordPolicyConfig.java
🧰 Additional context used
🧬 Code graph analysis (3)
base/src/main/java/com/tinyengine/it/service/app/impl/AppTemplateServiceImpl.java (2)
base/src/main/java/com/tinyengine/it/service/app/impl/AppServiceImpl.java (1)
  • Service (50-258)
base/src/main/java/com/tinyengine/it/service/app/impl/PageHistoryServiceImpl.java (1)
  • Service (36-142)
base/src/main/java/com/tinyengine/it/login/config/context/DefaultLoginUserContext.java (1)
base/src/main/java/com/tinyengine/it/login/model/UserInfo.java (1)
  • UserInfo (18-55)
base/src/main/java/com/tinyengine/it/login/config/SSOInterceptor.java (3)
base/src/main/java/com/tinyengine/it/login/model/UserInfo.java (1)
  • UserInfo (18-55)
base/src/main/java/com/tinyengine/it/login/config/context/DefaultLoginUserContext.java (1)
  • Component (10-69)
base/src/main/java/com/tinyengine/it/login/Utils/JwtUtil.java (1)
  • Component (29-183)
🔇 Additional comments (18)
pom.xml (2)

24-24: Hutool upgrade is safe.

The update from 5.8.27 to 5.8.40 is a minor patch and aligns with general dependency maintenance.

Also applies to: 24-24


41-41: Bouncy Castle dependency is appropriate for crypto operations.

The addition of bcprov-jdk18on (1.79) supports the SM2/SM3 cryptographic utilities and other security features introduced in this PR.

Also applies to: 41-41, 180-196

app/src/main/resources/sql/mysql/create_permission_table_ddl_2025_1029.sql (1)

1-14: Previous critical issues now resolved.

The three prior concerns—incorrect index column reference, composite index backtick wrapping, and DROP table name typo—have been corrected. The current DDL is syntactically valid.

base/src/main/java/com/tinyengine/it/login/config/context/DefaultLoginUserContext.java (2)

13-13: ThreadLocal cleanup properly implemented.

The ThreadLocal memory leak concern has been addressed. The SSOInterceptor now calls clear() in both the catch block (line 96) and afterCompletion() (line 105), ensuring proper cleanup even when exceptions occur.


50-66: LGTM!

The static lifecycle methods are well-designed. Using remove() instead of set(null) is the correct approach for ThreadLocal cleanup, preventing potential memory leaks in thread pools.

base/src/main/java/com/tinyengine/it/service/app/impl/AppTemplateServiceImpl.java (7)

103-122: LGTM!

The pagination normalization and query logic are correct.


131-137: LGTM!

The error handling and result wrapping are appropriate.


172-186: LGTM!

The page copying logic correctly resets identifiers and timestamps.


188-202: LGTM!

The page history copying logic is implemented correctly.


204-220: LGTM!

The app extension copying logic is implemented correctly.


222-238: LGTM!

The datasource copying logic is implemented correctly.


262-278: LGTM!

The model copying logic is implemented correctly.

base/src/main/resources/mappers/AppMapper.xml (6)

167-172: LGTM!

The industryId, sceneId, and isTemplate set column mappings are correct.

Also applies to: 233-235


317-365: LGTM!

The isTemplate column mapping and nested association mappings for industry and scene are correctly configured.


437-467: LGTM!

The paginated app query with filters and dynamic ordering is correctly implemented.


503-510: LGTM!

The query by id is correctly implemented with proper joins.


523-529: LGTM!

The conditional query is correctly implemented.


549-640: LGTM!

The insert statement correctly includes industryId, sceneId, and isTemplate fields.

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

♻️ Duplicate comments (2)
base/src/main/java/com/tinyengine/it/login/config/SSOInterceptor.java (2)

37-53: Fix Authorization header handling and double redirect in preHandle.

Two problems here:

  1. Authorization is passed directly to JwtUtil, so a standard "Authorization: Bearer <jwt>" header will fail validation.
  2. The missing-token branch calls sendRedirect twice, which can trigger IllegalStateException and is redundant.

Extract the raw token from a Bearer header and only redirect once:

-    public boolean preHandle(HttpServletRequest request,
-        HttpServletResponse response, Object handler) throws Exception {
-
-        String token = request.getHeader("Authorization");
+    public boolean preHandle(HttpServletRequest request,
+        HttpServletResponse response, Object handler) throws Exception {
+
+        String authHeader = request.getHeader("Authorization");
+        String token = null;
+        if (authHeader != null) {
+            token = authHeader.trim();
+            if (token.toLowerCase().startsWith("bearer ")) {
+                token = token.substring(7).trim();
+            }
+        }
@@
-        // 如果没有token,重定向到登录页
-        if (token == null || token.isEmpty()) {
-            String redirectUrl = SSO_SERVER;
-            log.info("No token, redirecting to: {}", redirectUrl);
-            response.sendRedirect(redirectUrl);
-            response.sendRedirect(SSO_SERVER);
-            return false;
-        }
+        // 如果没有token,重定向到登录页
+        if (token == null || token.isEmpty()) {
+            String redirectUrl = SSO_SERVER;
+            log.info("No token, redirecting to: {}", redirectUrl);
+            response.sendRedirect(redirectUrl);
+            return false;
+        }

79-86: Avoid silently defaulting tenant/renter IDs from token claims.

Here you silently replace missing tenantId/renterId with "default-tenant" / "default-renter". In a multi-tenant system this can hide token/config bugs and mis-route users to the wrong tenant, which is a security concern.

Instead, treat these as required claims (or at least fail closed when missing) and keep defaults only for clearly optional fields:

-            // 存储用户信息到LoginUserContext
-            UserInfo userInfo = new UserInfo(userId, username, tenantId != null ? tenantId : "default-tenant"
-            );
-            userInfo.setRenterId(renterId != null ? renterId : "default-renter");
+            // 检查关键租户信息
+            if (tenantId == null || tenantId.isEmpty() || renterId == null || renterId.isEmpty()) {
+                log.warn("Missing critical tenant info in token - tenantId: {}, renterId: {}", tenantId, renterId);
+                response.sendRedirect(SSO_SERVER);
+                return false;
+            }
+
+            // 存储用户信息到LoginUserContext
+            UserInfo userInfo = new UserInfo(userId, username, tenantId);
+            userInfo.setRenterId(renterId);
             userInfo.setPlatformId(platformId != null ? platformId : 0);
             userInfo.setSiteId(siteId != null ? siteId : "default-site");
             userInfo.setRoles(roles != null ? roles : "USER");
             userInfo.setToken(token);

If you intentionally support “default” tenants, consider at least logging a warning whenever a default is used so misconfigured tokens are visible in logs.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 59fd602 and 48bd48b.

📒 Files selected for processing (2)
  • Dockerfile (1 hunks)
  • base/src/main/java/com/tinyengine/it/login/config/SSOInterceptor.java (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
base/src/main/java/com/tinyengine/it/login/config/SSOInterceptor.java (3)
base/src/main/java/com/tinyengine/it/login/model/UserInfo.java (1)
  • UserInfo (18-55)
base/src/main/java/com/tinyengine/it/login/config/context/DefaultLoginUserContext.java (1)
  • Component (10-69)
base/src/main/java/com/tinyengine/it/login/Utils/JwtUtil.java (1)
  • Component (29-183)
🔇 Additional comments (1)
Dockerfile (1)

20-23: SSO_SERVER env wiring matches interceptor usage.

ENV SSO_SERVER="https://opentiny.github.io/tiny-engine/login" aligns with System.getenv("SSO_SERVER") in SSOInterceptor, so containerized deployments will have a valid redirect target configured out of the box.

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

♻️ Duplicate comments (2)
base/src/main/java/com/tinyengine/it/login/controller/LoginController.java (2)

121-146: Return the failure result when no user is found in login

When users.isEmpty() is true, the method calls Result.failed(ExceptionEnum.CM004); but does not return, so execution continues to users.get(0) and can throw IndexOutOfBoundsException. It also discards the failure Result instead of sending it to the client.

Return immediately on the failure, e.g.:

-        List<User> users = userService.queryUserByCondition(userParam);
-        if (users.isEmpty()) {
-            Result.failed(ExceptionEnum.CM004);
-        }
+        List<User> users = userService.queryUserByCondition(userParam);
+        if (users.isEmpty()) {
+            return Result.failed(ExceptionEnum.CM004);
+        }

165-175: Return a failure Result when password validation fails in forgotPassword

On validation failure, the method currently returns Result.success(passwordValidationResult), which marks an invalid password as a successful response. This is inconsistent with the registration endpoint and makes it hard for clients to distinguish real failures.

Align the behavior with registration by returning a failure Result and stopping processing:

-        PasswordValidationResult passwordValidationResult = configurablePasswordValidator
-            .validateWithPolicy(user.getPassword());
-        if(!passwordValidationResult.isValid()) {
-            return Result.success(passwordValidationResult);
-        }
+        PasswordValidationResult passwordValidationResult = configurablePasswordValidator
+            .validateWithPolicy(user.getPassword());
+        if (!passwordValidationResult.isValid()) {
+            return Result.failed("密码格式检验失败", passwordValidationResult.getErrorMessage());
+        }

(Adjust the error message text as needed, but it should clearly indicate validation failure and use the same pattern as the registration API.)

🧹 Nitpick comments (5)
base/src/main/java/com/tinyengine/it/login/service/LoginService.java (1)

6-21: Consider narrowing the exception contract on the service interface

Both createUser and forgotPassword declare throws Exception, which makes the contract very broad and pushes unchecked error modeling to callers. Given you already have ServiceException and ExceptionEnum, it would be cleaner to expose either:

  • domain‑specific checked exceptions (e.g. ServiceException) or
  • no checked exceptions at all and rely on runtime exceptions / global handlers.

This also helps keep controller signatures cleaner and more predictable.

base/src/main/java/com/tinyengine/it/login/service/impl/LoginServiceImpl.java (2)

78-88: Harden post‑update checks to avoid possible NPE on result / salt

After updateUserById, the code assumes queryUserById always returns a non‑null user with a non‑null salt. If, for any reason, the row is missing or salt is null, result.setPrivateKey(null); and result.getSalt().isEmpty() can throw.

You can make this more robust with a simple null/empty guard:

-        User result = baseMapper.queryUserById(user.getId());
-        result.setPrivateKey(null);
-        if (result.getSalt().isEmpty()) {
-            return Result.failed(ExceptionEnum.CM335);
-        }
-        return Result.success(ExceptionEnum.CM334.getResultCode(), ExceptionEnum.CM334.getResultMsg());
+        User result = baseMapper.queryUserById(user.getId());
+        if (result == null || result.getSalt() == null || result.getSalt().isEmpty()) {
+            return Result.failed(ExceptionEnum.CM335);
+        }
+        result.setPrivateKey(null);
+        return Result.success(ExceptionEnum.CM334.getResultCode(), ExceptionEnum.CM334.getResultMsg());

90-98: Simplify validatorPublicKey implementation

The method can be expressed more concisely without changing behavior:

-    private boolean validatorPublicKey(String salt, PublicKey publicKey, PrivateKey privateKey) throws Exception {
-        String plainSalt = decrypt(salt, privateKey);
-        String cipherSalt = encrypt(plainSalt, publicKey);
-        String decryptSalt = decrypt(cipherSalt, privateKey);
-        if (plainSalt.equals(decryptSalt)) {
-            return true;
-        }
-        return false;
-    }
+    private boolean validatorPublicKey(String salt, PublicKey publicKey, PrivateKey privateKey) throws Exception {
+        String plainSalt = decrypt(salt, privateKey);
+        String cipherSalt = encrypt(plainSalt, publicKey);
+        String decryptSalt = decrypt(cipherSalt, privateKey);
+        return plainSalt.equals(decryptSalt);
+    }

This keeps intent clear while reducing boilerplate.

base/src/main/java/com/tinyengine/it/login/controller/LoginController.java (2)

71-72: Make configurablePasswordValidator field private for consistency

The other autowired fields are private; this one is package‑private, which is unusual for a Spring controller and can leak implementation details across the package. Aligning it with the others improves encapsulation:

-    @Autowired
-    ConfigurablePasswordValidator configurablePasswordValidator;
+    @Autowired
+    private ConfigurablePasswordValidator configurablePasswordValidator;

194-200: Consider using a failure Result for invalid tokens

For invalid tokens, validateToken currently returns Result.success(new ValidationResult(false, null)). This encodes failure inside a success wrapper, which can be confusing for clients and inconsistent with other endpoints that use Result.failed(...) for error scenarios.

If API compatibility allows, consider returning a failure Result instead (e.g., with an appropriate exception enum or error code) so HTTP and application‑level status align.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 48bd48b and 8589de9.

📒 Files selected for processing (6)
  • base/src/main/java/com/tinyengine/it/login/controller/LoginController.java (1 hunks)
  • base/src/main/java/com/tinyengine/it/login/service/LoginService.java (1 hunks)
  • base/src/main/java/com/tinyengine/it/login/service/impl/LoginServiceImpl.java (1 hunks)
  • base/src/main/java/com/tinyengine/it/service/app/UserService.java (0 hunks)
  • base/src/main/java/com/tinyengine/it/service/app/impl/UserServiceImpl.java (1 hunks)
  • base/src/test/java/com/tinyengine/it/service/app/impl/UserServiceImplTest.java (3 hunks)
💤 Files with no reviewable changes (1)
  • base/src/main/java/com/tinyengine/it/service/app/UserService.java
🚧 Files skipped from review as they are similar to previous changes (2)
  • base/src/test/java/com/tinyengine/it/service/app/impl/UserServiceImplTest.java
  • base/src/main/java/com/tinyengine/it/service/app/impl/UserServiceImpl.java
🧰 Additional context used
🧬 Code graph analysis (2)
base/src/main/java/com/tinyengine/it/login/service/impl/LoginServiceImpl.java (1)
base/src/main/java/com/tinyengine/it/service/app/impl/UserServiceImpl.java (1)
  • Service (31-87)
base/src/main/java/com/tinyengine/it/login/controller/LoginController.java (1)
base/src/main/java/com/tinyengine/it/login/Utils/SM3PasswordUtil.java (1)
  • SM3PasswordUtil (25-79)

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

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8589de9 and fa1b0f1.

📒 Files selected for processing (4)
  • app/src/main/resources/sql/h2/create_permission_table_ddl_2025_1029.sql (1 hunks)
  • app/src/main/resources/sql/mysql/create_permission_table_ddl_2025_1029.sql (1 hunks)
  • base/src/main/java/com/tinyengine/it/controller/AppTemplateController.java (1 hunks)
  • base/src/main/resources/mappers/PageMapper.xml (0 hunks)
💤 Files with no reviewable changes (1)
  • base/src/main/resources/mappers/PageMapper.xml
🚧 Files skipped from review as they are similar to previous changes (1)
  • base/src/main/java/com/tinyengine/it/controller/AppTemplateController.java
🔇 Additional comments (3)
app/src/main/resources/sql/h2/create_permission_table_ddl_2025_1029.sql (1)

28-43: Ensure NOT NULL audit fields in t_auth_users_units_roles are populated consistently.

Lines 37, 39: created_by and last_updated_by are NOT NULL without defaults. Confirm that either:

  1. A MyBatis Plus MetaObjectHandler interceptor auto-fills these fields on insert/update, or
  2. Init_data provides bootstrap records with valid audit values, or
  3. All application code paths that insert/update this table explicitly provide these values.

Without one of these mechanisms, persistence operations will fail.

app/src/main/resources/sql/mysql/create_permission_table_ddl_2025_1029.sql (2)

1-14: Previous critical syntax errors have been fixed; verify audit field population strategy.

The prior review identified three critical DDL issues (incorrect unique index column, composite index syntax, and DROP statement typo), all of which have been corrected:

  • Line 13: unique index correctly references name
  • Line 26: DROP statement correctly references t_auth_users_units_roles
  • Line 42: composite index properly backtick-separates columns ✓

However, the underlying issue remains: lines 8, 10 (created_by, last_updated_by in t_permission_role) are NOT NULL without defaults. A prior review noted that MySQL init_data provides test values (e.g., created_by='1'), but no MetaObjectHandler or interceptor was found to auto-populate these fields in production code paths.

Verify that the init_data INSERT statements are present for all permission tables and that the application layer has a consistent strategy (e.g., MyBatis Plus interceptor, service layer enforcement) to populate audit fields before persistence.


28-43: Confirm NOT NULL audit fields in t_auth_users_units_roles are handled by init_data or interceptor.

Lines 37, 39: created_by and last_updated_by are NOT NULL without defaults. Ensure init_data includes INSERT statements for this table with valid audit field values, or that a MyBatis Plus MetaObjectHandler is configured to auto-fill these during ORM operations.

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

Caution

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

⚠️ Outside diff range comments (1)
base/src/main/java/com/tinyengine/it/common/context/LoginUserContext.java (1)

22-46: MockUserContext test implementation not updated to match new LoginUserContext interface

DefaultLoginUserContext is correctly updated with getTenants(), getLoginUserId(), getPlatformId(), and setTenants(). However, MockUserContext still has the old interface signature with methods like getTenantId(), getLoginUserId() returning Integer, and extra methods (getRenterId(), getAppId(), getSiteId()) that don't exist in the interface. It's missing the required getTenants() and setTenants(List<Tenant>) methods.

File: base/src/test/java/com/tinyengine/it/common/handler/MockUserContext.java

  • Replace getTenantId() with List<Tenant> getTenants()
  • Add setTenants(List<Tenant> tenants) method
  • Remove getRenterId(), getAppId(), getSiteId()
  • Fix getLoginUserId() return type to String
♻️ Duplicate comments (10)
base/src/main/java/com/tinyengine/it/login/service/impl/LoginServiceImpl.java (1)

94-96: Return the failure result immediately.

When no user is found, Line 95 calls Result.failed() but doesn't return it, allowing execution to continue to Line 97 where users.get(0) throws IndexOutOfBoundsException.

Apply this diff:

         List<User> users = baseMapper.queryUserByCondition(userParam);
         if (users.isEmpty()) {
-            Result.failed(ExceptionEnum.CM002);
+            return Result.failed(ExceptionEnum.CM002);
         }
pom.xml (1)

191-195: JJWT dependency setup is incomplete for version 0.12.x.

This issue was previously flagged. JJWT 0.12.x requires the modular setup with jjwt-api, jjwt-impl (runtime), and jjwt-jackson (runtime) instead of the legacy bundled jjwt artifact.

app/src/main/resources/sql/h2/create_permission_table_ddl_2025_1029.sql (1)

8-10: Audit fields NOT NULL without defaults - init data missing.

This was previously flagged. Add corresponding INSERT statements to H2's init_data file to match MySQL's init data, or implement a MetaObjectHandler for auto-population.

Also applies to: 27-29

base/src/main/java/com/tinyengine/it/login/config/SSOInterceptor.java (3)

38-38: Guard against null/blank SSO_SERVER environment variable.

This duplicates a prior review concern. If SSO_SERVER is unset, sendRedirect(null) will throw at runtime. Add a defensive check at the start of preHandle.


44-44: Handle Bearer token prefix in Authorization header.

This duplicates a prior review concern. Clients typically send Authorization: Bearer <token>. The token should be extracted using jwtUtil.getTokenFromRequest() which already handles this (see JwtUtil lines 271-276 in the relevant snippets).

-        String token = request.getHeader("Authorization");
+        String token = jwtUtil.getTokenFromRequest(request.getHeader("Authorization"));

81-86: Consider logging when default values are applied for missing claims.

As noted in prior reviews, silently defaulting platformId to 0 and roles to "USER" may mask token generation issues. Consider adding debug/warn logs when defaults are used.

base/src/main/java/com/tinyengine/it/login/config/context/DefaultLoginUserContext.java (1)

32-36: Clarify default platform behavior for unauthenticated context

getPlatformId() returns DEFAULT_PLATFORM (1) when no user is set, unlike the other accessors which return null. This can make unauthenticated calls look like they belong to platform 1 and hide missing-context bugs.

Consider either:

  • Returning a nullable Integer (or a sentinel like 0 / -1) for “no user/platform”, or
  • Making the default explicit in the caller rather than in this context class.

This was raised before; if the current behavior is intentional, it’s worth documenting clearly at the interface level.

base/src/main/java/com/tinyengine/it/login/controller/LoginController.java (2)

141-167: Return immediately when no user is found to avoid IndexOutOfBoundsException

When users is empty you call Result.failed(ExceptionEnum.CM004); but don’t return, so execution continues to users.get(0) and will throw:

List<User> users = userService.queryUserByCondition(userParam);
if (users.isEmpty()) {
    Result.failed(ExceptionEnum.CM004);
}
User userResult = users.get(0);

Return the failure result directly:

-        if (users.isEmpty()) {
-            Result.failed(ExceptionEnum.CM004);
-        }
+        if (users.isEmpty()) {
+            return Result.failed(ExceptionEnum.CM004);
+        }
         User userResult = users.get(0);

This preserves the intended error flow and prevents unexpected exceptions.


186-195: Forgot-password should not return success on password validation failure

In forgotPassword, an invalid password currently returns a success Result:

if (!passwordValidationResult.isValid()) {
    return Result.success(passwordValidationResult);
}

This is misleading (HTTP 200 + success wrapper) and inconsistent with the registration endpoint, which returns a failure on validation errors.

Align behavior with registration, e.g.:

-        if(!passwordValidationResult.isValid()) {
-            return Result.success(passwordValidationResult);
-        }
+        if (!passwordValidationResult.isValid()) {
+            return Result.failed("密码格式检验失败", passwordValidationResult.getErrorMessage());
+        }

(or reuse a common error code/message as appropriate).

base/src/main/java/com/tinyengine/it/login/Utils/JwtUtil.java (1)

45-57: Avoid hard-coded/default JWT secret and use an explicit charset for key derivation

DEFAULT_SECRET plus System.getenv fallback means a usable signing secret is compiled into the binary:

private static final String DEFAULT_SECRET = "tiny-engine-backend-secret-key-at-jwt-login";

private static String getSecretString() {
    return Optional.ofNullable(System.getenv("SECRET_STRING"))
        .orElse(DEFAULT_SECRET);
}

public static SecretKey getSecretKey() {
    return Keys.hmacShaKeyFor(getSecretString().getBytes());
}

Issues:

  • Anyone with access to the code or artifact can mint valid JWTs if the environment variable is not set.
  • .getBytes() uses the platform default charset, so the derived key may vary across environments.

A safer pattern that keeps the existing static API:

+import java.nio.charset.StandardCharsets;
...
-    private static final String DEFAULT_SECRET = "tiny-engine-backend-secret-key-at-jwt-login";
-
-    // 避免启动时环境变量未加载的问题
-    private static String getSecretString() {
-        return Optional.ofNullable(System.getenv("SECRET_STRING"))
-            .orElse(DEFAULT_SECRET);
-    }
+    // 从环境变量中读取 JWT 密钥,避免将密钥硬编码到代码中
+    private static String getSecretString() {
+        String secret = System.getenv("JWT_SECRET");
+        if (secret == null || secret.trim().isEmpty()) {
+            throw new IllegalStateException(
+                "JWT secret not configured. Please set JWT_SECRET environment variable.");
+        }
+        return secret;
+    }
 
     public static SecretKey getSecretKey() {
-
-        return Keys.hmacShaKeyFor(getSecretString().getBytes());
+        return Keys.hmacShaKeyFor(getSecretString().getBytes(StandardCharsets.UTF_8));
     }

You can later move this to Spring configuration (@Value("${security.jwt.secret}")) if you want property-based config, but removing the compiled default and fixing the charset are the critical steps.

🧹 Nitpick comments (9)
base/src/main/java/com/tinyengine/it/login/service/impl/LoginServiceImpl.java (1)

116-124: Simplify boolean return logic.

The if-return-true-return-false pattern can be replaced with a direct boolean expression.

Apply this diff:

     private boolean validatorPublicKey(String salt, PublicKey publicKey, PrivateKey privateKey) throws Exception {
         String plainSalt = decrypt(salt, privateKey);
         String cipherSalt = encrypt(plainSalt, publicKey);
         String decryptSalt = decrypt(cipherSalt, privateKey);
-        if (plainSalt.equals(decryptSalt)) {
-            return true;
-        }
-        return false;
+        return plainSalt.equals(decryptSalt);
     }
app/src/main/resources/sql/mysql/create_permission_table_ddl_2025_1029.sql (1)

16-33: Consider adding foreign key constraints for referential integrity.

The r_auth_users_units_roles table references user_id, tenant_id, and role_id, but defines no foreign keys. While this provides flexibility, it risks orphaned records if related entities are deleted.

If the referenced tables (t_user, t_tenant, t_permission_role) support cascading or you want to enforce integrity, consider adding foreign keys:

create table `r_auth_users_units_roles`
(
    -- ... existing columns ...
    primary key (`id`) using btree,
    unique index `u_idx_auth_users_units_roles` (`user_id`, `unit_id`, `unit_type`) using btree,
    constraint `fk_user` foreign key (`user_id`) references `t_user`(`id`) on delete cascade,
    constraint `fk_role` foreign key (`role_id`) references `t_permission_role`(`id`) on delete cascade
) engine = innodb comment = '';
docker-deploy-data/mysql/init/init_data_2025_1125.sql (1)

1-6: Add DELETE or INSERT IGNORE to make script idempotent.

The script inserts records with explicit IDs (1-6). If the script runs multiple times, it will fail with duplicate key errors. Since the companion DDL file (create_permission_table_ddl_2025_1029.sql) drops and recreates the table, this script may be intended to run only once after table creation. However, if these scripts are run independently, idempotency should be ensured.

Consider one of these approaches:

Option 1: Delete existing records first

DELETE FROM `t_permission_role` WHERE `id` IN (1, 2, 3, 4, 5, 6);
INSERT INTO `t_permission_role` ...

Option 2: Use INSERT IGNORE

INSERT IGNORE INTO `t_permission_role` ...

Option 3: Use REPLACE

REPLACE INTO `t_permission_role` ...
app/src/main/resources/sql/mysql/init_data_2025_1125.sql (1)

1-6: Add DELETE or INSERT IGNORE to make script idempotent.

The script inserts records with explicit IDs (1-6). If the script runs multiple times, it will fail with duplicate key errors. Since the companion DDL file (create_permission_table_ddl_2025_1029.sql) drops and recreates the table, this script may be intended to run only once after table creation. However, if these scripts are run independently, idempotency should be ensured.

Consider one of these approaches:

Option 1: Delete existing records first

DELETE FROM `t_permission_role` WHERE `id` IN (1, 2, 3, 4, 5, 6);
INSERT INTO `t_permission_role` ...

Option 2: Use INSERT IGNORE

INSERT IGNORE INTO `t_permission_role` ...

Option 3: Use REPLACE

REPLACE INTO `t_permission_role` ...
base/src/main/java/com/tinyengine/it/service/platform/impl/TenantServiceImpl.java (1)

117-117: Avoid magic number for roleId.

Hardcoding roleId = 2 reduces maintainability and makes the code fragile to role configuration changes. Extract to a named constant or configuration property.

+    private static final int TENANT_ADMIN_ROLE_ID = 2;
+
     @Override
     public Integer createTenant(Tenant tenant) {
         int result = baseMapper.createTenant(tenant);
         if(result == 1) {
             AuthUsersUnitsRoles authUsersUnitsRoles = new AuthUsersUnitsRoles();
             authUsersUnitsRoles.setTenantId(Integer.valueOf(tenant.getId()));
-            authUsersUnitsRoles.setRoleId(2);
+            authUsersUnitsRoles.setRoleId(TENANT_ADMIN_ROLE_ID);
base/src/main/java/com/tinyengine/it/login/service/TokenBlacklistService.java (2)

21-25: In-memory blacklist won't scale to clustered deployments.

The current implementation stores blacklisted tokens in local JVM memory. In a multi-instance deployment, a token blacklisted on one instance remains valid on others. Consider using Redis or a shared cache for production multi-node environments.


41-43: Cleanup on every read is inefficient under high load.

Calling cleanExpiredTokens() on every isTokenBlacklisted check adds O(n) overhead to a hot path. Consider using a scheduled task (e.g., @Scheduled) to periodically clean expired tokens instead.

+    @Scheduled(fixedRate = 60000) // Run every minute
     private void cleanExpiredTokens() {
         long currentTime = System.currentTimeMillis();
         tokenExpiryMap.entrySet().removeIf(entry -> {

And remove the inline calls from blacklistToken and isTokenBlacklisted.

base/src/main/java/com/tinyengine/it/login/model/UserInfo.java (1)

22-53: Consider using Lombok for consistency with other entities.

Other entities in this PR (e.g., AuthUsersUnitsRoles, User) use Lombok @Getter/@Setter. Using Lombok here would reduce boilerplate and maintain codebase consistency.

+import lombok.Getter;
+import lombok.Setter;
+
+@Getter
+@Setter
 public class UserInfo {
     private String userId;
     private String username;
     private List<Tenant> tenants;
     private int platformId;
     private String roles;
     private String token;
     private String department;

     // 构造器
     public UserInfo(String userId, String username, List<Tenant> tenants) {
         this.userId = userId;
         this.username = username;
         this.tenants = tenants;
     }
-
-    // getter和setter方法
-    public String getUserId() { return userId; }
-    public void setUserId(String userId) { this.userId = userId; }
-    // ... remove all manual getters/setters
 }
base/src/main/java/com/tinyengine/it/mapper/AuthUsersUnitsRolesMapper.java (1)

26-29: Add @param to userId to make MyBatis binding explicit

queryAllTenantByUserId uses #{userId} in the @Select, but the method parameter is un-annotated. This relies on parameter name retention at compile time.

Make the binding explicit:

-    @Select("SELECT DISTINCT tt.* FROM t_tenant tt " +
-            "INNER JOIN r_auth_users_units_roles raur ON tt.id = raur.tenant_id " +
-            "WHERE raur.user_id = #{userId}")
-    List<Tenant> queryAllTenantByUserId(Integer userId);
+    @Select("SELECT DISTINCT tt.* FROM t_tenant tt " +
+            "INNER JOIN r_auth_users_units_roles raur ON tt.id = raur.tenant_id " +
+            "WHERE raur.user_id = #{userId}")
+    List<Tenant> queryAllTenantByUserId(@Param("userId") Integer userId);
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fa1b0f1 and 95b55c4.

📒 Files selected for processing (27)
  • app/src/main/resources/sql/h2/create_permission_table_ddl_2025_1029.sql (1 hunks)
  • app/src/main/resources/sql/h2/init_data_2025_1125.sql (1 hunks)
  • app/src/main/resources/sql/mysql/create_permission_table_ddl_2025_1029.sql (1 hunks)
  • app/src/main/resources/sql/mysql/init_data_2025_1023.sql (1 hunks)
  • app/src/main/resources/sql/mysql/init_data_2025_1125.sql (1 hunks)
  • base/src/main/java/com/tinyengine/it/common/context/LoginUserContext.java (2 hunks)
  • base/src/main/java/com/tinyengine/it/common/handler/MyMetaObjectHandler.java (1 hunks)
  • base/src/main/java/com/tinyengine/it/controller/TenantController.java (1 hunks)
  • base/src/main/java/com/tinyengine/it/controller/UserController.java (4 hunks)
  • base/src/main/java/com/tinyengine/it/login/Utils/JwtUtil.java (1 hunks)
  • base/src/main/java/com/tinyengine/it/login/config/SSOInterceptor.java (1 hunks)
  • base/src/main/java/com/tinyengine/it/login/config/context/DefaultLoginUserContext.java (1 hunks)
  • base/src/main/java/com/tinyengine/it/login/controller/LoginController.java (1 hunks)
  • base/src/main/java/com/tinyengine/it/login/model/UserInfo.java (1 hunks)
  • base/src/main/java/com/tinyengine/it/login/service/LoginService.java (1 hunks)
  • base/src/main/java/com/tinyengine/it/login/service/TokenBlacklistService.java (1 hunks)
  • base/src/main/java/com/tinyengine/it/login/service/impl/LoginServiceImpl.java (1 hunks)
  • base/src/main/java/com/tinyengine/it/mapper/AuthUsersUnitsRolesMapper.java (1 hunks)
  • base/src/main/java/com/tinyengine/it/model/entity/AuthUsersUnitsRoles.java (1 hunks)
  • base/src/main/java/com/tinyengine/it/model/entity/User.java (3 hunks)
  • base/src/main/java/com/tinyengine/it/service/app/impl/AppServiceImpl.java (3 hunks)
  • base/src/main/java/com/tinyengine/it/service/platform/impl/TenantServiceImpl.java (7 hunks)
  • base/src/main/resources/mappers/AuthUsersUnitsRolesMapper.xml (1 hunks)
  • base/src/test/java/com/tinyengine/it/common/handler/MockUserContext.java (1 hunks)
  • docker-deploy-data/mysql/init/create_permission_table_ddl_2025_1029.sql (1 hunks)
  • docker-deploy-data/mysql/init/init_data_2025_1125.sql (1 hunks)
  • pom.xml (3 hunks)
✅ Files skipped from review due to trivial changes (1)
  • app/src/main/resources/sql/h2/init_data_2025_1125.sql
🚧 Files skipped from review as they are similar to previous changes (2)
  • base/src/main/java/com/tinyengine/it/service/app/impl/AppServiceImpl.java
  • base/src/main/java/com/tinyengine/it/login/service/LoginService.java
🧰 Additional context used
🧬 Code graph analysis (4)
base/src/main/java/com/tinyengine/it/model/entity/AuthUsersUnitsRoles.java (1)
base/src/main/java/com/tinyengine/it/model/entity/User.java (1)
  • Getter (39-91)
base/src/main/java/com/tinyengine/it/login/config/SSOInterceptor.java (3)
base/src/main/java/com/tinyengine/it/login/model/UserInfo.java (1)
  • UserInfo (22-54)
base/src/main/java/com/tinyengine/it/login/Utils/JwtUtil.java (1)
  • Component (38-279)
base/src/main/java/com/tinyengine/it/login/config/context/DefaultLoginUserContext.java (1)
  • Component (13-72)
base/src/main/java/com/tinyengine/it/login/controller/LoginController.java (1)
base/src/main/java/com/tinyengine/it/login/Utils/SM3PasswordUtil.java (1)
  • SM3PasswordUtil (25-79)
base/src/main/java/com/tinyengine/it/login/Utils/JwtUtil.java (3)
base/src/main/java/com/tinyengine/it/login/config/SSOInterceptor.java (1)
  • Slf4j (31-108)
base/src/main/java/com/tinyengine/it/login/config/context/DefaultLoginUserContext.java (1)
  • Component (13-72)
base/src/main/java/com/tinyengine/it/login/service/TokenBlacklistService.java (1)
  • Component (21-59)
🔇 Additional comments (6)
app/src/main/resources/sql/mysql/init_data_2025_1023.sql (1)

1-13: LGTM! Clean seed data with sound structure and categorization.

The seed data is well-organized with proper SQL syntax, consistent field values, and logical categorization. The separation of scenarios ('场景') and industries ('行业') in the business_group column is clear and sensible. All 13 rows follow a uniform structure with proper column alignment.

base/src/main/java/com/tinyengine/it/model/entity/User.java (1)

88-90: Good defensive initialization of tenant list.

Initializing the tenant field to an empty ArrayList prevents null pointer exceptions when consumers iterate or check the tenant list.

app/src/main/resources/sql/mysql/create_permission_table_ddl_2025_1029.sql (1)

1-14: LGTM! DDL for t_permission_role looks correct.

The table definition includes proper primary key, unique constraint on name, and all necessary audit fields with correct defaults.

docker-deploy-data/mysql/init/create_permission_table_ddl_2025_1029.sql (1)

1-33: MySQL schema looks correct.

The table definitions are well-structured with appropriate indexes. The unit_type as VARCHAR(60) correctly aligns with the Java code that sets string values like "tenant". Ensure the application layer or a MyBatis Plus MetaObjectHandler populates the created_by and last_updated_by audit fields on insert/update operations.

base/src/main/java/com/tinyengine/it/model/entity/AuthUsersUnitsRoles.java (1)

23-69: LGTM!

The entity follows the established patterns in the codebase, with appropriate MyBatis-Plus annotations, Lombok usage, and JSON formatting consistent with the User entity.

base/src/main/java/com/tinyengine/it/login/controller/LoginController.java (1)

111-121: Registration flow looks consistent with password policy and hashing

The createUser endpoint correctly:

  • Validates the raw password via ConfigurablePasswordValidator.
  • Short-circuits with a failure Result on invalid format.
  • Hashes with SM3PasswordUtil.createPassword and stores both hash and salt before delegating to loginService.

No issues spotted here.

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

♻️ Duplicate comments (9)
base/src/main/resources/mappers/AuthUsersUnitsRolesMapper.xml (1)

18-20: userId property name inconsistency still unresolved—causes INSERT binding failure.

This critical issue was flagged in the previous review but remains unfixed. The mapper has misaligned property names that will prevent INSERT operations:

  • Line 86 resultMap maps user_idproperty="UserId" (capital U)
  • Lines 18–19, 52–53 condition/set fragments reference UserId (capital U)
  • Line 154 INSERT VALUES uses #{userId} (lowercase u)
  • Entity property (per AI summary) is userId (lowercase)

Only userId matches the actual Java entity property. The mismatch causes MyBatis parameter binding to fail during INSERT: either UserId won't resolve in the entity, or userId won't bind in the INSERT statement. Choose one and standardize it throughout. Per Java conventions, use lowercase userId.

Apply this diff to standardize all references to lowercase userId:

- <result column="user_id" property="UserId"/>
+ <result column="user_id" property="userId"/>

  <if test="UserId!=null">
-     AND user_id = #{UserId}
+     AND user_id = #{userId}
  </if>

  <if test="UserId!=null">
-     user_id = #{UserId},
+     user_id = #{userId},
  </if>

  VALUES (#{id},
          #{tenantId},
-         #{userId},
+         #{userId},  <!-- already correct in INSERT, but fix above lines -->

Also applies to: 52-54, 86-86, 154-154

base/src/main/java/com/tinyengine/it/controller/UserController.java (1)

79-83: Handle non-numeric loginUserId to avoid NumberFormatException

The null check addresses part of the earlier concern, but Integer.valueOf(loginUserId) will still throw if the context ever contains a non-numeric ID (e.g., username or UUID). This can break /user/me with a 500 instead of a controlled error.

To fully harden this path, validate numeric format before converting (or catch NumberFormatException) and return a proper failure/result instead of letting the exception bubble.

Example using a numeric check:

-        if (loginUserId == null) {
-            return Result.failed(ExceptionEnum.CM009);
-        }
-        Integer userId = Integer.valueOf(loginUserId);
-        List<Tenant> tenants = authUsersUnitsRolesMapper.queryAllTenantByUserId(userId);
+        if (loginUserId == null || !loginUserId.chars().allMatch(Character::isDigit)) {
+            // Treat missing or invalid ID as an invalid login context
+            return Result.failed(ExceptionEnum.CM009);
+        }
+        Integer userId = Integer.valueOf(loginUserId);
+        List<Tenant> tenants = authUsersUnitsRolesMapper.queryAllTenantByUserId(userId);
@@
-        user.setTenant(tenants);
+        user.setTenant(tenants);

This preserves the existing behavior while preventing an unexpected NumberFormatException from the controller.

Also applies to: 90-90

base/src/main/java/com/tinyengine/it/controller/TenantController.java (2)

63-77: Do not treat createTenant’s int return as the tenant ID

tenantService.updateTenantById(tenant) below clearly returns an affected-row count; createTenant(tenant) returning int is very likely following the same pattern. Using that int as the ID in findTenantById(result) will return the wrong tenant (e.g., always ID=1).

Prefer letting the insert populate the entity’s ID (via MyBatis useGeneratedKeys / similar) and returning the entity, or have createTenant explicitly return the generated ID and name it accordingly.

A safer pattern here:

-    public Result<Tenant> createTenant(@Valid @RequestBody Tenant tenant) {
-        int result = tenantService.createTenant(tenant);
-        return Result.success(tenantService.findTenantById(result));
-    }
+    public Result<Tenant> createTenant(@Valid @RequestBody Tenant tenant) {
+        // createTenant should either:
+        // 1) populate tenant.setId(...) via generated keys, or
+        // 2) return the generated ID instead of an affected-row count.
+        tenantService.createTenant(tenant);
+        return Result.success(tenant);
+    }

If createTenant is already implemented to return the generated ID, then renaming it (or its return type) for clarity and adding a brief comment would avoid future confusion.


85-105: Guard against NumberFormatException when parsing tenant.getId()

tenant.getId() is a String and is only checked for null/empty; Integer.valueOf(tenant.getId()) can still throw if the value is non‑numeric (e.g., whitespace, malformed input). That will bubble up as a 500 instead of a clean error.

Consider validating/parsing defensively:

-        int result = tenantService.updateTenantById(tenant);
-        if (result != 1) {
-            return Result.failed(ExceptionEnum.CM002);
-        }
-        return Result.success(tenantService.findTenantById(Integer.valueOf(tenant.getId())));
+        int result = tenantService.updateTenantById(tenant);
+        if (result != 1) {
+            return Result.failed(ExceptionEnum.CM002);
+        }
+        try {
+            Integer id = Integer.valueOf(tenant.getId());
+            return Result.success(tenantService.findTenantById(id));
+        } catch (NumberFormatException e) {
+            return Result.failed(ExceptionEnum.CM002);
+        }

Alternatively, enforce numeric ID at the DTO/validation layer so this path only ever sees a valid integer.

base/src/main/java/com/tinyengine/it/login/service/impl/LoginServiceImpl.java (1)

73-79: Hardcoded tenant/role/unit values.

This concern was already raised in a previous review. The hardcoded tenantId=1, roleId=2, unitType="tenant", and unitId=1 should be made configurable.

base/src/main/java/com/tinyengine/it/login/controller/LoginController.java (2)

146-149: Return failure when the user record is missing.

Line 147 calls Result.failed(ExceptionEnum.CM004) but does not return, so execution continues to line 149 where users.get(0) throws IndexOutOfBoundsException.

Apply this diff to fix:

-        if (users.isEmpty()) {
-            Result.failed(ExceptionEnum.CM004);
-        }
+        if (users.isEmpty()) {
+            return Result.failed(ExceptionEnum.CM004);
+        }

186-196: Return failure when password validation fails.

Line 190 returns Result.success(passwordValidationResult) when password validation fails, which is semantically incorrect. This contradicts the correct handling in the createUser method (lines 114-116) and was supposedly addressed in a previous commit but the issue persists.

Apply this diff to fix:

 PasswordValidationResult passwordValidationResult = configurablePasswordValidator
     .validateWithPolicy(user.getPassword());
 if(!passwordValidationResult.isValid()) {
-    return Result.success(passwordValidationResult);
+    return Result.failed("密码格式检验失败", passwordValidationResult.getErrorMessage());
 }
base/src/main/java/com/tinyengine/it/common/context/LoginUserContext.java (1)

43-46: Enhance Javadoc with parameter description.

The @param tag on line 44 lacks a description. Adding a brief explanation improves API documentation clarity.

Apply this diff:

     /**
      * 设置当前组织信息
-     * @param tenants
+     * @param tenants the list of tenants to set
      */
     void setTenants(List<Tenant> tenants);
base/src/main/java/com/tinyengine/it/common/handler/MyMetaObjectHandler.java (1)

44-50: Critical logic error: isEmpty() check inverted, will always throw IndexOutOfBoundsException.

Line 45 checks loginUserContext.getTenants().isEmpty(), which returns true when the list has zero elements. If this condition is true, line 46 attempts to access .get(0), which will always throw IndexOutOfBoundsException on an empty list.

Apply this diff to fix the inverted logic:

-        if (loginUserContext.getTenants() != null && loginUserContext.getTenants().isEmpty()) {
+        if (loginUserContext.getTenants() != null && !loginUserContext.getTenants().isEmpty()) {
             Integer tenant = Integer.valueOf(loginUserContext.getTenants().get(0).getId());
             if (tenant != null) {
                 this.setFieldValByName("tenantId", tenant, metaObject);
             }
         }
🧹 Nitpick comments (4)
base/src/main/java/com/tinyengine/it/controller/UserController.java (1)

62-63: Make mapper field private for consistency

Other injected fields in this controller are private, but authUsersUnitsRolesMapper is package‑private. Consider aligning visibility for encapsulation and consistency.

-    @Autowired
-    AuthUsersUnitsRolesMapper authUsersUnitsRolesMapper;
+    @Autowired
+    private AuthUsersUnitsRolesMapper authUsersUnitsRolesMapper;
base/src/main/java/com/tinyengine/it/login/service/impl/LoginServiceImpl.java (2)

24-24: Remove unused import and prefer constructor injection.

@Slf4j is imported but the logger (log) is never used in this class. Additionally, consider using constructor injection instead of field injection for better testability.

-import lombok.extern.slf4j.Slf4j;
+import lombok.RequiredArgsConstructor;
 @Service
-@Slf4j
+@RequiredArgsConstructor
 public class LoginServiceImpl extends ServiceImpl<UserMapper, User> implements LoginService {
 
-    @Autowired
-    AuthUsersUnitsRolesMapper authUsersUnitsRolesMapper;
+    private final AuthUsersUnitsRolesMapper authUsersUnitsRolesMapper;

Also applies to: 43-44


116-124: Simplify boolean return and add documentation.

The method can be simplified using a direct return. Also consider adding Javadoc to clarify what this validation actually checks (that the provided public key matches the stored private key pair).

+    /**
+     * Validates that the provided public key matches the stored private key pair
+     * by performing a round-trip encryption/decryption test.
+     */
     private boolean validatorPublicKey(String salt, PublicKey publicKey, PrivateKey privateKey) throws Exception {
         String plainSalt = decrypt(salt, privateKey);
         String cipherSalt = encrypt(plainSalt, publicKey);
         String decryptSalt = decrypt(cipherSalt, privateKey);
-        if (plainSalt.equals(decryptSalt)) {
-            return true;
-        }
-        return false;
+        return plainSalt.equals(decryptSalt);
     }
base/src/main/java/com/tinyengine/it/login/controller/LoginController.java (1)

162-162: Consider extracting hardcoded expiry duration to a constant.

The token expiry duration 3600000 (1 hour in milliseconds) is hardcoded in multiple locations (lines 162 and 273). Extracting this to a named constant improves maintainability and makes it easier to adjust the expiry policy centrally.

Example:

private static final long TOKEN_EXPIRY_MS = 3600000L; // 1 hour

// Then use:
ticket.setExpireTime(System.currentTimeMillis() + TOKEN_EXPIRY_MS);

Also applies to: 273-273

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 95b55c4 and e744285.

📒 Files selected for processing (10)
  • app/src/main/resources/sql/h2/create_permission_table_ddl_2025_1029.sql (1 hunks)
  • base/src/main/java/com/tinyengine/it/common/context/LoginUserContext.java (2 hunks)
  • base/src/main/java/com/tinyengine/it/common/handler/MyMetaObjectHandler.java (1 hunks)
  • base/src/main/java/com/tinyengine/it/controller/TenantController.java (1 hunks)
  • base/src/main/java/com/tinyengine/it/controller/UserController.java (4 hunks)
  • base/src/main/java/com/tinyengine/it/login/config/SSOInterceptor.java (1 hunks)
  • base/src/main/java/com/tinyengine/it/login/controller/LoginController.java (1 hunks)
  • base/src/main/java/com/tinyengine/it/login/service/impl/LoginServiceImpl.java (1 hunks)
  • base/src/main/resources/mappers/AuthUsersUnitsRolesMapper.xml (1 hunks)
  • base/src/test/java/com/tinyengine/it/common/handler/MockUserContext.java (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • base/src/main/java/com/tinyengine/it/login/config/SSOInterceptor.java
  • app/src/main/resources/sql/h2/create_permission_table_ddl_2025_1029.sql
🧰 Additional context used
🧬 Code graph analysis (2)
base/src/main/java/com/tinyengine/it/login/controller/LoginController.java (1)
base/src/main/java/com/tinyengine/it/login/Utils/SM3PasswordUtil.java (1)
  • SM3PasswordUtil (25-79)
base/src/main/java/com/tinyengine/it/controller/TenantController.java (3)
base/src/main/java/com/tinyengine/it/controller/UserController.java (1)
  • Validated (44-95)
base/src/main/java/com/tinyengine/it/login/controller/LoginController.java (1)
  • Validated (65-280)
base/src/main/java/com/tinyengine/it/controller/HomeController.java (1)
  • RestController (28-41)
🔇 Additional comments (12)
base/src/main/java/com/tinyengine/it/controller/UserController.java (1)

17-21: New imports correctly match tenant and error-handling logic

ExceptionEnum, AuthUsersUnitsRolesMapper, Tenant, and List are all used below and wired correctly; no issues here.

Also applies to: 37-37

base/src/main/java/com/tinyengine/it/controller/TenantController.java (2)

44-55: getAllTenant implementation looks correct and consistent

Endpoint wiring and return type (Result<List<Tenant>>) are straightforward and align with the rest of the codebase. No issues from a correctness standpoint.


113-131: deleteTenant flow is reasonable; previous concerns appear addressed

  • HTTP verb is now DELETE, which matches the semantics.
  • Tenant is fetched before deletion and returned, avoiding a null lookup after deletion.
  • Non‑success delete (affected rows != 1) returns CM009.

This is a sane and consistent behavior for delete-by-id in this codebase.

base/src/main/java/com/tinyengine/it/login/service/impl/LoginServiceImpl.java (1)

65-68: Security concern: Private key stored in database.

Storing the private key in the database (even base64-encoded) is a security risk. If the database is compromised, all user credentials can be decrypted. Consider:

  1. Using a dedicated key management service (KMS) or HSM
  2. Encrypting the private key at rest with a master key
  3. Evaluating if the password reset flow can use a different approach (e.g., time-limited tokens)

This may be an intentional design decision for your use case. Please verify this aligns with your security requirements.

base/src/main/java/com/tinyengine/it/login/controller/LoginController.java (5)

62-93: LGTM - Well-structured controller setup.

The controller is properly annotated and dependencies are clearly injected via Spring's @Autowired mechanism.


111-122: LGTM - Password validation correctly returns failure.

The registration method now properly returns Result.failed when password validation fails (lines 114-116), addressing the previous review concern.


215-221: LGTM - Token validation logic is correct.

The validation endpoint appropriately returns a success Result with a ValidationResult payload that indicates whether the token is valid or not.


240-253: LGTM - Tenant validation properly guards against null and missing tenant.

The null/empty checks for tenants (lines 241-244) and validation that the requested tenant exists (lines 251-253) properly address the previous review concerns.


277-279: LGTM - Authentication helper correctly delegates to SM3 utility.

The helper method cleanly delegates password verification to the SM3PasswordUtil.

base/src/test/java/com/tinyengine/it/common/handler/MockUserContext.java (2)

26-38: LGTM!

The getTenants() implementation correctly returns a non-empty list with a single mock tenant (id="1"), which is appropriate for test purposes and aligns with the updated LoginUserContext interface.


51-59: Empty setTenants() is acceptable for a test mock.

The no-op implementation is appropriate for a stateless mock used in tests where tenant mutations don't need to be tracked.

base/src/main/java/com/tinyengine/it/common/context/LoginUserContext.java (1)

27-27: Migration from getTenantId() to getTenants() in LoginUserContext is complete and correct.

Verification found one reference to getTenantId() at AppServiceImpl.java:194, but this calls the method on an App entity object (which inherits tenantId from BaseEntity), not on LoginUserContext. All LoginUserContext call sites have been properly updated to use getTenants():

  • LoginController.java:241 — uses loginUserContext.getTenants()
  • MyMetaObjectHandler.java:45-46 — uses loginUserContext.getTenants()
  • DefaultLoginUserContext.java:21 — implementation returns userInfo.getTenants()
  • Test implementations also updated

No remaining references to the old LoginUserContext.getTenantId() method exist.

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

♻️ Duplicate comments (3)
base/src/main/java/com/tinyengine/it/login/controller/LoginController.java (3)

146-148: Return failure when the user record is missing.

The Result.failed(ExceptionEnum.CM004) call on line 147 has no return statement, so execution continues to users.get(0) which will throw IndexOutOfBoundsException.

         if (users.isEmpty()) {
-            Result.failed(ExceptionEnum.CM004);
+            return Result.failed(ExceptionEnum.CM004);
         }

257-258: Check authHeader for null before extracting token.

While headerToken is validated after extraction (lines 259-261), if authHeader is null, jwtUtil.getTokenFromRequest(null) may behave unexpectedly depending on its implementation.

         String authHeader = request.getHeader("Authorization");
+        if (authHeader == null || authHeader.isEmpty()) {
+            return Result.failed(ExceptionEnum.CM336);
+        }
         String headerToken = jwtUtil.getTokenFromRequest(authHeader);
-        if (headerToken == null || headerToken.isEmpty()) {
-            return Result.failed(ExceptionEnum.CM336);
-        }

189-191: Return failure when password validation fails (not addressed).

The past review marked this as addressed, but line 190 still returns Result.success(passwordValidationResult) when validation fails. This is inconsistent with the fix applied to createUser (line 115).

         if(!passwordValidationResult.isValid()) {
-            return Result.success(passwordValidationResult);
+            return Result.failed("密码格式检验失败", passwordValidationResult.getErrorMessage());
         }
🧹 Nitpick comments (2)
base/src/main/java/com/tinyengine/it/login/controller/LoginController.java (2)

111-122: Add duplicate username check and use parameterized Result type.

Two concerns:

  1. The method returns raw Result instead of Result<User>, losing type safety.
  2. No check for existing username before creating a user—this will likely cause a database constraint violation or allow duplicate registrations.
-    public Result createUser(@Valid @RequestBody User user) throws Exception {
+    public Result<User> createUser(@Valid @RequestBody User user) throws Exception {
+        // Check if username already exists
+        User existingUser = new User();
+        existingUser.setUsername(user.getUsername());
+        List<User> existing = userService.queryUserByCondition(existingUser);
+        if (!existing.isEmpty()) {
+            return Result.failed("用户名已存在");
+        }
         PasswordValidationResult passwordValidationResult = configurablePasswordValidator
             .validateWithPolicy(user.getPassword());

215-221: Add null/empty check for token parameter.

The token parameter is used directly without validation. If null or empty is passed, jwtUtil.validateToken(token) may throw an unexpected exception.

     public Result<ValidationResult> validateToken(@RequestParam String token) {
+        if (token == null || token.isEmpty()) {
+            return Result.success(new ValidationResult(false, null));
+        }
         if (jwtUtil.validateToken(token)) {
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e744285 and f9cf4c2.

📒 Files selected for processing (2)
  • base/src/main/java/com/tinyengine/it/common/exception/ExceptionEnum.java (1 hunks)
  • base/src/main/java/com/tinyengine/it/login/controller/LoginController.java (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • base/src/main/java/com/tinyengine/it/common/exception/ExceptionEnum.java
🧰 Additional context used
🧬 Code graph analysis (1)
base/src/main/java/com/tinyengine/it/login/controller/LoginController.java (3)
base/src/main/java/com/tinyengine/it/login/Utils/SM3PasswordUtil.java (1)
  • SM3PasswordUtil (25-79)
base/src/main/java/com/tinyengine/it/controller/UserController.java (1)
  • Validated (44-95)
base/src/main/java/com/tinyengine/it/controller/HomeController.java (1)
  • RestController (28-41)
🔇 Additional comments (1)
base/src/main/java/com/tinyengine/it/login/controller/LoginController.java (1)

280-282: LGTM!

The authenticate helper correctly delegates to SM3PasswordUtil.verifyPassword with proper parameter ordering.

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

♻️ Duplicate comments (2)
base/src/main/java/com/tinyengine/it/login/config/context/DefaultLoginUserContext.java (2)

32-36: Avoid hardcoded DEFAULT_PLATFORM for unauthenticated requests

Here getPlatformId() diverges from the other accessors: when there is no current user, it returns DEFAULT_PLATFORM = 1 instead of null. This can silently mis-tag audit/metadata rows (e.g., in MyMetaObjectHandler.insertFill) as belonging to platform 1 for unauthenticated or system operations, which is hard to debug in a multi-platform setup.

To keep semantics consistent with getLoginUserId() and getTenants(), consider changing the contract to allow null and only return a real platformId when the user is present. For example, in this class:

-    @Override
-    public int getPlatformId() {
-        UserInfo userInfo = currentUser.get();
-        return userInfo != null ? userInfo.getPlatformId() : DEFAULT_PLATFORM;
-    }
+    @Override
+    public Integer getPlatformId() {
+        UserInfo userInfo = currentUser.get();
+        return userInfo != null ? userInfo.getPlatformId() : null;
+    }

You would need to update com.tinyengine.it.common.context.LoginUserContext and call sites accordingly so they handle a nullable Integer.


44-48: Guard against null currentUser in setTenants() to avoid NPE

setTenants assumes DefaultLoginUserContext.currentUser.get() is non-null. If this is ever called before setCurrentUser(UserInfo) (or after clear()), it will throw a NullPointerException on userInfo.setTenants(tenants).

Fail fast (or explicitly no-op) when no user is present:

@Override
public void setTenants(List<Tenant> tenants) {
-    UserInfo userInfo = currentUser.get();
-    userInfo.setTenants(tenants);
-    currentUser.set(userInfo);
+    UserInfo userInfo = currentUser.get();
+    if (userInfo == null) {
+        throw new IllegalStateException("No current user in context; cannot set tenants");
+        // Alternatively, log and `return;` if you prefer a silent no-op.
+    }
+    userInfo.setTenants(tenants);
+    currentUser.set(userInfo);
}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f9cf4c2 and 546b231.

📒 Files selected for processing (1)
  • base/src/main/java/com/tinyengine/it/login/config/context/DefaultLoginUserContext.java (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
base/src/main/java/com/tinyengine/it/login/config/context/DefaultLoginUserContext.java (2)
base/src/main/java/com/tinyengine/it/login/model/UserInfo.java (1)
  • UserInfo (22-54)
base/src/main/java/com/tinyengine/it/common/handler/MyMetaObjectHandler.java (1)
  • Component (31-57)
🔇 Additional comments (2)
base/src/main/java/com/tinyengine/it/login/config/context/DefaultLoginUserContext.java (2)

27-30: getLoginUserId() implementation looks fine

The method is straightforward, consistently returning null when there is no current user; no issues from this implementation itself.


53-71: ThreadLocal helper methods are idiomatic

setCurrentUser, getCurrentUser, and clear wrap the ThreadLocal<UserInfo> in the expected way, and using remove() in clear() is the right choice for thread-pool reuse. With the interceptor now calling DefaultLoginUserContext.clear() on all paths, this looks good.

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

♻️ Duplicate comments (1)
Dockerfile (1)

19-21: Incomplete environment variable configuration strategy.

The removal of TINY_ENGINE_URL aligns with the previous feedback about avoiding hardcoded environment-specific values in Dockerfiles. However, this change appears incomplete given that the PR introduces SSO/JWT-based authentication that requires runtime configuration (e.g., SSO_SERVER).

Action required: Review the approach in example-docker.yml and define a proper runtime configuration strategy (e.g., docker-compose with .env files, Kubernetes ConfigMaps, or environment variable files) rather than hardcoding these values.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 546b231 and ce2b46a.

📒 Files selected for processing (2)
  • Dockerfile (1 hunks)
  • example-docker.yml (1 hunks)
🧰 Additional context used
🪛 YAMLlint (1.37.1)
example-docker.yml

[error] 1-1: wrong new line character: expected \n

(new-lines)


[error] 20-20: syntax error: expected '', but found ''

(syntax)

@hexqi hexqi merged commit a6ea0de into opentiny:develop Nov 28, 2025
1 check passed
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.

9 participants