Skip to content

fix:update model data and API key authentication support#315

Open
msslulu wants to merge 8 commits into
opentiny:developfrom
msslulu:fix/model-driven
Open

fix:update model data and API key authentication support#315
msslulu wants to merge 8 commits into
opentiny:developfrom
msslulu:fix/model-driven

Conversation

@msslulu

@msslulu msslulu commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

1. PR变更背景:

模型驱动接口不能传递token的安全漏洞

2 解决方案

  1. 请求过滤

API Key + 请求签名 的方式,将认证信息放在 Header 中:

  • .X-API-Key: 客户端标识,

  • X-Timestamp: 时间戳

  • X-Nonce: 随机字符串

  • X-Signature: HMAC-SHA256 签名

通过 X-API-Key、X-Timestamp、X-Nonce、X-Signature 四个 Header 实现身份认证、防篡改、防重放,并自动绑定租户

postman 测试脚本:
// ========== 1. 从环境变量获取密钥 ==========
const apiKey = pm.environment.get("API_KEY") || "与服务端一致";
const secret = pm.environment.get("API_SECRET") || "与服务端一致";

// ========== 2. 生成时间戳和 Nonce ==========
const timestamp = Date.now().toString(); // 毫秒级时间戳
const nonce = Math.random().toString(36).substring(2, 18) + Math.random().toString(36).substring(2, 18); // 32位随机字符串

// ========== 3. 获取请求方法和路径 ==========
const method = pm.request.method;
const path = pm.request.url.getPath(); // 不包含 host 和 query 参数

// ========== 4. 获取请求体(如果有) ==========
let body = "";
if (pm.request.body && pm.request.body.mode === "raw") {
// 只对 raw 类型(JSON/文本)进行签名
body = pm.request.body.raw || "";
// 注意:如果 body 是空对象 {},也应该保留为 "{}",不能省略
} else if (pm.request.body && pm.request.body.mode === "formdata") {
// 对于 form-data,通常不签体,或者签一个空字符串,根据实际约定
// 这里为了简单,留空
body = "";
} else {
// GET/DELETE 等没有 body 的请求
body = "";
}

// ========== 5. 构造待签名字符串 ==========
// 格式: method\npath\ntimestamp\nnonce\nbody
const stringToSign = ${method}\n${path}\n${timestamp}\n${nonce}\n${body};

// ========== 6. 计算 HMAC-SHA256 签名 ==========
const signature = CryptoJS.HmacSHA256(stringToSign, secret).toString(CryptoJS.enc.Hex);

// ========== 7. 设置环境变量 ==========
pm.environment.set("timestamp", timestamp)
pm.environment.set("nonce", nonce)
pm.environment.set("signature", signature)

2.数据持久化

增加t_model_data,redis存储模型驱动数据,不再创建模型表,杜绝SQL注入风险与DDL注入风险

影响范围:
模型驱动前端调用接口需要增加以上head参数

自测增删改查接口:

POST:
platform-center/api/model-data/queryApi
request :
{
"nameEn":"staff",
"nameCh":"员工",
"currentPage":2,
"pageSize":5,
"params":{
"name":"like:s",
"age":28,
"ifperm":true,
"startDate":null,
"politStatus":null

},
"fields":[
    "name",
    "age",
    "ifperm"
]

}
respose :
{
"data": {
"total": 9,
"pages": 2,
"pageSize": 5,
"list": [
{
"id": 13,
"name": "s6",
"age": 28,
"ifperm": true
},
{
"id": 14,
"name": "s7",
"age": 28,
"ifperm": true
},
{
"id": 15,
"name": "s8",
"age": 28,
"ifperm": true
},
{
"id": 16,
"name": "s9",
"age": 28,
"ifperm": true
}
],
"pageNum": 2
},
"code": "200",
"message": "操作成功",
"error": null,
"errMsg": null,
"success": true
}

127.0.0.1:9090/platform-center/api/model-data/insertApi
POST:

Summary by CodeRabbit

  • New Features
    • Added API-key protected access for model data endpoints with signed requests, timestamp checks, nonce replay protection, and tenant context handling.
    • Introduced Redis-backed caching and improved paginated querying for dynamic model data.
    • Added new model data and API key persistence support (plus input validation for dynamic fields).
  • Bug Fixes
    • Enhanced create/update/delete error responses to include failure details.
    • Corrected duplicate resource checks to respect tenant boundaries.
  • Chores
    • Upgraded Spring Boot and refreshed Redis/JPA/database initialization (including updated table DDL).
  • Tests
    • Added/expanded test coverage for API key validation, caching, dynamic model data, and input validation.

@coderabbitai

coderabbitai Bot commented Jun 30, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Adds API-key authentication for /platform-center/api/model-data/*, introduces Redis-backed model-data storage and querying, adds new JPA entities and MySQL DDL, and refactors dynamic model-data flows to use the new cache service. Spring Boot and Redis wiring were updated, and tests were added for the new services.

Changes

Backend infrastructure and schema bootstrap

Layer / File(s) Summary
Dependency and Spring Boot version upgrades
pom.xml
Spring Boot parent upgraded to 3.2.4; MySQL, Redis/Jedis, Hibernate 6.3.2, Druid SB3, and MyBatis-Plus SB3 dependencies were updated.
Infrastructure wiring and schema bootstrap
docker-compose.yml, app/src/main/resources/application-alpha.yml, base/src/main/java/com/tinyengine/it/config/RedisConfig.java, app/src/main/java/com/tinyengine/it/TinyEngineApplication.java, app/src/main/resources/sql/mysql/10_create_apikey_table_ddl_2026_0630.sql, app/src/main/resources/sql/mysql/11_create_modeldata_table_ddl_2026_0630.sql, app/src/main/resources/sql/mysql/init_data_2026_0630.sql
Adds the Redis container and app wiring, Redis/JPA Spring config, entity scanning, and DDL/seed scripts for t_api_key and t_model_data.

API-key authentication

Layer / File(s) Summary
API-key entities, signing, and filter
base/src/main/java/com/tinyengine/it/modeldata/entity/ApiKeyEntity.java, base/src/main/java/com/tinyengine/it/modeldata/dao/ApiKeyRepository.java, base/src/main/java/com/tinyengine/it/modeldata/service/ApiKeyService.java, base/src/main/java/com/tinyengine/it/common/exception/ExceptionEnum.java, base/src/main/java/com/tinyengine/it/login/utils/SignatureUtil.java, base/src/main/java/com/tinyengine/it/modeldata/util/CachedBodyServletInputStream.java, base/src/main/java/com/tinyengine/it/modeldata/util/CachedBodyHttpServletRequest.java, base/src/main/java/com/tinyengine/it/modeldata/filter/ApiKeyAuthFilter.java, base/src/main/java/com/tinyengine/it/login/config/LoginConfig.java, base/src/main/java/com/tinyengine/it/login/config/context/DefaultLoginUserContext.java, base/src/main/java/com/tinyengine/it/modeldata/exception/BusinessException.java
Defines API-key persistence and lookup, adds HMAC-SHA256 signing helpers and cached-body request wrappers, implements the request filter, extends the missing-header exception code, and stores the current tenant in request context.

Dynamic model-data services

Layer / File(s) Summary
Model-data entity, repository, and validation
base/src/main/java/com/tinyengine/it/modeldata/entity/ModelData.java, base/src/main/java/com/tinyengine/it/modeldata/dao/ModelDataRepository.java, base/src/main/java/com/tinyengine/it/modeldata/util/ModelParser.java, base/src/main/java/com/tinyengine/it/modeldata/service/ValidationService.java, base/src/main/java/com/tinyengine/it/modeldata/service/DynamicModelDataService.java
Adds the model-data persistence type, repository methods, parameter parser, field validation service, and the direct CRUD/query service for model-data records.
Redis-cached model-data service path
base/src/main/java/com/tinyengine/it/modeldata/service/ModelDataCacheService.java, base/src/main/java/com/tinyengine/it/dynamic/service/DynamicService.java, base/src/main/java/com/tinyengine/it/dynamic/service/DynamicModelService.java, base/src/main/java/com/tinyengine/it/dynamic/controller/ModelDataController.java, base/src/main/java/com/tinyengine/it/service/material/impl/ResourceServiceImpl.java
Adds Redis-backed query and mutation handling, refactors the dynamic service to use it, adjusts create-data binding, updates controller failure messages, and scopes resource duplicate checks by tenant.

Tests

Layer / File(s) Summary
Service and validation tests
base/src/test/java/com/tinyengine/it/modeldata/service/ApiKeyServiceTest.java, base/src/test/java/com/tinyengine/it/modeldata/service/DynamicModelDataServiceTest.java, base/src/test/java/com/tinyengine/it/modeldata/service/ModelDataCacheServiceTest.java, base/src/test/java/com/tinyengine/it/modeldata/service/ValidationServiceTest.java
Adds JUnit coverage for API-key lookup, dynamic CRUD, cache service behavior, and validation rules.

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Poem

🐇 I hopped through Redis with a key in my paw,
And HMAC signed neatly by bunny law.
The model-data fields all danced in a row,
While caches and tables began to glow.
New tests in the burrow said, “All is well!”
Then Spring Boot sprang up—what a tale to tell!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 39.29% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly reflects the main changes: model data updates and API key authentication support.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

❤️ Share

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

@coderabbitai

coderabbitai Bot commented Jun 30, 2026

Copy link
Copy Markdown

Note

Unit test generation is a beta feature. Expect some limitations and changes as we gather feedback and continue to improve it.


Generating unit tests... This may take up to 20 minutes.

@coderabbitai

coderabbitai Bot commented Jun 30, 2026

Copy link
Copy Markdown

❌ Failed to create PR with unit tests: AGENT_CHAT: Failed to open pull request

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 5

Note

Due to the large number of review comments, Critical severity comments were prioritized as inline comments.

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/dynamic/service/DynamicService.java (1)

165-175: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Reject or enforce extra update conditions.

The method still accepts a conditions map, but after requiring id, it ignores every other condition and updates by ID only. A request like {id: 1, status: "draft"} will update row 1 even if status does not match.

Also applies to: 193-193

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@base/src/main/java/com/tinyengine/it/dynamic/service/DynamicService.java`
around lines 165 - 175, The update flow in DynamicService still validates
dto.getParams() but then only uses the id for the update, so any extra
conditions are ignored. Update the logic in the relevant update method(s) to
either reject additional conditions beyond id or include all provided conditions
when building the update criteria, and make sure the params/conditions passed to
the mapper reflect the chosen behavior.
🟠 Major comments (19)
base/src/main/java/com/tinyengine/it/dynamic/controller/ModelDataController.java-71-71 (1)

71-71: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Do not expose raw exception messages in API responses.

These handlers catch broad Exception, and Result.failed(String) returns the message verbatim to clients. That leaks internal validation, SQL, Redis, or persistence error details from the new dynamic-data stack. Keep the detailed exception in logs and only surface a sanitized message, or whitelist known business exceptions.

🔒 Suggested fix
 		} catch (Exception e) {
 			log.error("insert failed for table: {}", dto.getNameEn(), e);
-			return Result.failed("insert operation failed:"+e.getMessage());
+			return Result.failed("insert operation failed");
 		}
@@
 		} catch (Exception e) {
 			log.error("updateApi failed for table: {}", dto.getNameEn(), e);
-			return Result.failed("update operation failed:"+e.getMessage());
+			return Result.failed("update operation failed");
 		}
@@
 		} catch (Exception e) {
 			log.error("deleteApi failed for table: {}", dto.getNameEn(), e);
-			return Result.failed("delete operation failed:"+e.getMessage());
+			return Result.failed("delete operation failed");
 		}

Also applies to: 93-93, 114-114

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@base/src/main/java/com/tinyengine/it/dynamic/controller/ModelDataController.java`
at line 71, The controller methods in ModelDataController are returning raw
Exception text through Result.failed(...), which leaks internal details to
clients. Update the catch blocks around the insert/update/delete handlers to log
the full exception for debugging, but return a sanitized, generic failure
message from Result.failed; if needed, only pass through whitelisted business
exception messages. Keep the fix localized to the affected controller methods
rather than changing the Result API.
base/src/main/java/com/tinyengine/it/service/material/impl/ResourceServiceImpl.java-203-205 (1)

203-205: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Use the authenticated tenant here, not the request payload.

resource.getTenantId() is caller-controlled, but this service already has LoginUserContext, and DefaultLoginUserContext.getTenantId() is the repo’s tenant contract. If the request omits/spoofs tenantId, this duplicate check runs against the wrong tenant and can either allow duplicate uploads or let callers probe another tenant’s hashes. Resolve the tenant from loginUserContext and stamp it onto resource before both the lookup and insert.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@base/src/main/java/com/tinyengine/it/service/material/impl/ResourceServiceImpl.java`
around lines 203 - 205, The duplicate-check in ResourceServiceImpl is using
caller-controlled tenantId from resource.getTenantId(), which can target the
wrong tenant. In the ResourceServiceImpl flow, resolve the tenant from
LoginUserContext via DefaultLoginUserContext.getTenantId(), stamp that value
onto resource before the QueryWrapper lookup and before insert, and use the
authenticated tenant consistently for the hash duplicate check.
base/src/main/java/com/tinyengine/it/modeldata/dao/ApiKeyRepository.java-9-10 (1)

9-10: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Align ApiKeyRepository with ApiKeyEntity.id
ApiKeyEntity.id is Integer, but this repository declares Long, so the inherited JpaRepository methods expose the wrong ID type. Change it to JpaRepository<ApiKeyEntity, Integer> to keep findById/deleteById consistent.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@base/src/main/java/com/tinyengine/it/modeldata/dao/ApiKeyRepository.java`
around lines 9 - 10, `ApiKeyRepository` is using the wrong primary key type,
which makes inherited `JpaRepository` methods inconsistent with
`ApiKeyEntity.id`. Update the `ApiKeyRepository` declaration to use the same ID
type as `ApiKeyEntity` by changing the `JpaRepository` generic from `Long` to
`Integer`, while keeping `findByApiKeyAndStatus` unchanged.
base/src/main/java/com/tinyengine/it/modeldata/filter/ApiKeyAuthFilter.java-74-80 (1)

74-80: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Reserve the nonce only after the signature is valid, and scope it to the API key.

nonce:<nonce> is written before authentication succeeds and is shared by every client. That lets an unauthenticated caller pre-burn arbitrary nonces, and two different API keys can also block each other if they reuse the same nonce value. Build the replay key from the authenticated principal (for example nonce:{apiKey}:{nonce}) and persist it only after the HMAC check passes.

Proposed fix
-		// 4. Nonce 防重放(Redis 存储,有效期5分钟)
-		String nonceKey = "nonce:" + nonce;
-		Boolean isNew = stringRedisTemplate.opsForValue().setIfAbsent(nonceKey, "1", Duration.ofMinutes(5));
-		if (Boolean.FALSE.equals(isNew)) {
-			response.setStatus(401);
-			response.getWriter().write("Nonce already used");
-			return;
-		}
-
-		// 5. 根据 API Key 获取密钥和租户信息
+		// 4. 根据 API Key 获取密钥和租户信息
 		ApiKeyEntity keyEntity = apiKeyService.getValidKey(apiKey);
 		if (keyEntity == null) {
 			response.setStatus(401);
 			response.getWriter().write("Invalid API Key");
 			return;
@@
 		String expectedSignature = SignatureUtil.hmacSha256(keyEntity.getApiSecret(), stringToSign);
 		if (!expectedSignature.equals(signature)) {
 			response.setStatus(401);
 			response.getWriter().write("Invalid signature");
 			return;
 		}
+
+		String nonceKey = "nonce:" + apiKey + ":" + nonce;
+		Boolean isNew = stringRedisTemplate.opsForValue().setIfAbsent(nonceKey, "1", Duration.ofMinutes(5));
+		if (!Boolean.TRUE.equals(isNew)) {
+			response.setStatus(401);
+			response.getWriter().write("Nonce already used");
+			return;
+		}

Also applies to: 99-105

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@base/src/main/java/com/tinyengine/it/modeldata/filter/ApiKeyAuthFilter.java`
around lines 74 - 80, The nonce replay check in ApiKeyAuthFilter is happening
too early and uses a global nonce key, so move the Redis reservation until after
the HMAC/signature validation succeeds and tie it to the authenticated API key.
Update the nonce handling in the filter around the nonce validation block to
build the Redis key from the principal, such as nonce:{apiKey}:{nonce}, and only
call setIfAbsent after the signature check passes. Apply the same change to the
duplicate nonce logic later in the method as well.
base/src/main/java/com/tinyengine/it/dynamic/service/DynamicService.java-69-69 (1)

69-69: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Avoid logging dynamic payloads and user identifiers.

These logs include arbitrary model params/data and userId. Dynamic model data can contain PII; log IDs, field names, and counts instead of raw values.

Also applies to: 144-145, 180-180, 191-191, 214-214

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@base/src/main/java/com/tinyengine/it/dynamic/service/DynamicService.java` at
line 69, Remove logging of raw dynamic payloads and user identifiers from
DynamicService, especially the log statements around query/init/update/delete
flows. Replace log entries in methods such as the query path and the other
flagged call sites with safe metadata only, using identifiers like modelId,
tenantId, userId, field names, and counts instead of params/data contents or raw
IDs. Keep the existing log context in DynamicService but sanitize any arbitrary
model params and PII before logging.
base/src/main/java/com/tinyengine/it/modeldata/service/ModelDataCacheService.java-83-116 (1)

83-116: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Reject unsupported filter operators instead of matching everything.

Line 115 makes an unknown operator behave as “no filter”, so a typo like gtee:10 can return broader results than requested.

Proposed fix
 				if (strValue.contains(":")) {
 					String[] parts = strValue.split(":", 2);
 					operator = parts[0];
 					value = parts[1];
 				}else{
@@
 					}
 				}
+				if (!Set.of("eq", "ne", "gt", "lt", "gte", "lte", "like", "startswith", "endswith", "in").contains(operator)) {
+					throw new IllegalArgumentException("Unsupported filter operator: " + operator);
+				}
 				final String op = operator;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@base/src/main/java/com/tinyengine/it/modeldata/service/ModelDataCacheService.java`
around lines 83 - 116, The filter logic in ModelDataCacheService currently
treats unknown operators as a pass-through in the switch inside the item filter,
which can silently broaden results on typos like gtee. Update the operator
handling so unsupported operators are rejected explicitly instead of falling
through to a match-all path; for example, validate the parsed operator before
applying the stream.filter and make the default case return a failure/empty
result or throw an error. Use the existing operator parsing and comparison logic
in this method to keep supported cases like eq, gt, like, in, and startswith
unchanged.
base/src/main/java/com/tinyengine/it/modeldata/dao/ModelDataRepository.java-13-13 (1)

13-13: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Align the ModelData ID type across entity, repository, and callers. ModelData.id is Integer, but the repository and service methods still use Long (findById(Long.valueOf(dataId)), deleteById(Long), etc.). Keep the ID type consistent so CRUD lookups don’t depend on mixed conversions.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@base/src/main/java/com/tinyengine/it/modeldata/dao/ModelDataRepository.java`
at line 13, The ModelData ID type is inconsistent across the entity, repository,
and service callers, since ModelData.id uses Integer while ModelDataRepository
and methods like findById/deleteById still rely on Long conversions. Update
ModelDataRepository to use the same ID type as ModelData, then adjust all
related service and caller methods that reference ModelDataRepository, findById,
and deleteById so they pass and return the consistent ID type without mixed
Long/Integer conversions.
base/src/main/java/com/tinyengine/it/modeldata/service/DynamicModelDataService.java-116-120 (1)

116-120: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Do not splice request field names into the JSON path.

field is caller-controlled and is concatenated directly into JSON_EXTRACT(data_json, '$."..."'). Prepared parameters only protect value; a crafted key can break the path literal and inject SQL. Validate filter keys against the model's allowed properties before building this clause.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@base/src/main/java/com/tinyengine/it/modeldata/service/DynamicModelDataService.java`
around lines 116 - 120, The filter-building logic in DynamicModelDataService
currently concatenates caller-controlled keys into the JSON_EXTRACT path, which
can allow SQL injection through the field name. Update the loop that builds the
WHERE clause to validate each entry key against the model’s allowed properties
before appending it, and only then construct the JSON path in the query. Keep
using prepared parameters for the value, but ensure the key used by JSON_EXTRACT
is strictly whitelisted or rejected in the same method.
base/src/main/java/com/tinyengine/it/modeldata/service/DynamicModelDataService.java-44-52 (1)

44-52: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Handle models with null parameters.

Both create and update call isEmpty() on model.getParameters() before null-checking it. Other code in this PR already guards that property as nullable, so models without parameter metadata will crash here with an NPE instead of validating against an empty schema.

Suggested change
-		List<ParametersDto> fieldDefs = model.getParameters();
+		List<ParametersDto> fieldDefs = model.getParameters();
+		if (fieldDefs == null) {
+			fieldDefs = new ArrayList<>();
+		}
 		if (!fieldDefs.isEmpty() && !(fieldDefs.get(0) instanceof ParametersDto)) {

Also applies to: 77-88

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@base/src/main/java/com/tinyengine/it/modeldata/service/DynamicModelDataService.java`
around lines 44 - 52, The model parameter handling in DynamicModelDataService is
missing a null guard: both the create and update flows call isEmpty() on
model.getParameters() before checking whether parameters is null, which can
throw an NPE for models without metadata. Update the parameter retrieval logic
in the validation path used by the create/update methods so it first checks for
null, treats missing parameters as an empty list, and only runs the JSON
conversion and validationService.validate when a non-null list is present; use
the existing model.getParameters(), fieldDefs, and validationService.validate
flow to keep behavior consistent.
base/src/main/java/com/tinyengine/it/modeldata/service/DynamicModelDataService.java-69-71 (1)

69-71: 🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift

Scope reads and mutations by tenant_id.

Create/query persist and filter on tenantId, but update/get/delete load rows by primary key only. The downstream callers in DynamicService also pass only id, so anyone who can guess another tenant's dataId can read, modify, or delete that row across tenant boundaries.

Also applies to: 97-105

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@base/src/main/java/com/tinyengine/it/modeldata/service/DynamicModelDataService.java`
around lines 69 - 71, The read/update/delete paths in DynamicModelDataService
currently load ModelData by primary key only, which bypasses tenant scoping.
Update the affected methods (including update and the related get/delete code
referenced in the review) to query by both dataId and tenantId, using the tenant
context already used by create/query, and make sure DynamicService passes
tenantId through instead of only id so all mutations and reads stay
tenant-isolated.
base/src/main/java/com/tinyengine/it/modeldata/service/ValidationService.java-52-53 (1)

52-53: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Remove raw payload logging from validation.

This writes every validated value to stdout, which can leak tenant data/PII and sidestep the application's logging policy.

Suggested change
 private void checkType(Object value, String expectedType, String fieldName) {
-		System.out.println("Validating field: " + fieldName + ", Expected type: " + expectedType + ", Actual value: " + value);
 		switch (expectedType) {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@base/src/main/java/com/tinyengine/it/modeldata/service/ValidationService.java`
around lines 52 - 53, Remove the raw payload print from
ValidationService.checkType, since logging the full validated value to stdout
can expose tenant data and bypass the app’s logging controls. Update checkType
so it only logs safe metadata like the field name or expected type, or remove
the debug output entirely, and keep validation behavior unchanged.
base/src/main/java/com/tinyengine/it/modeldata/service/ValidationService.java-16-19 (1)

16-19: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Guard nullable required flags here. ParametersDto.required is nullable, and DynamicModelService adds ParametersDto entries without setting it, so filter(ParametersDto::getRequired) can NPE before validation runs. Use Boolean.TRUE.equals(...) instead.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@base/src/main/java/com/tinyengine/it/modeldata/service/ValidationService.java`
around lines 16 - 19, The required-properties collection in ValidationService
should not rely on a nullable Boolean from ParametersDto, since
DynamicModelService can create entries without setting required and
`filter(ParametersDto::getRequired)` may NPE. Update the stream filter in the
validation logic to use a null-safe check like `Boolean.TRUE.equals(...)` on
`ParametersDto.getRequired`, keeping the rest of the requiredProps mapping
unchanged.
app/src/main/resources/sql/mysql/11_create_modeldata_table_ddl_2026_0630.sql-1-1 (1)

1-1: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

This migration will delete existing model data.

drop table if exists makes the rollout non-idempotent and destroys all rows on every re-run. For a data table, that is too risky for normal deployment or recovery workflows.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/src/main/resources/sql/mysql/11_create_modeldata_table_ddl_2026_0630.sql`
at line 1, The migration currently drops t_model_data unconditionally, which
wipes existing rows on every re-run. Update the SQL in the model data DDL
migration to preserve existing data, using a safe schema-change approach in the
same migration sequence instead of a DROP TABLE statement, and keep the change
aligned with the table definition handled by this script.
app/src/main/resources/sql/mysql/10_create_apikey_table_ddl_2026_0630.sql-6-12 (1)

6-12: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Make api_key the unique identity.

The auth flow looks up records by api_key, but this schema only enforces uniqueness on (api_key, api_secret) and even allows api_secret to be null. That permits multiple active rows for the same presented key, making authentication ambiguous. api_key should be unique by itself, and api_secret should be not null.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/src/main/resources/sql/mysql/10_create_apikey_table_ddl_2026_0630.sql`
around lines 6 - 12, The current DDL for the api key table allows duplicate
api_key values because the uniqueness constraint is on the combined pair instead
of the key alone, and api_secret is still nullable. Update the table definition
in the MySQL create script so api_key is enforced as the unique identity via a
unique index or constraint on api_key only, and make api_secret not null as part
of the same schema change. Use the existing column definitions in the
create-apikey DDL to locate the constraint and nullability settings.
app/src/main/resources/sql/mysql/11_create_modeldata_table_ddl_2026_0630.sql-5-8 (1)

5-8: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

model_id does not match the entity type.

The entity declares modelId as Integer, but this DDL creates model_id as varchar(255). That breaks the schema/ORM contract and is exactly the kind of mismatch Hibernate will try to “fix” at startup when ddl-auto: update is enabled. Make the column type align with the entity.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/src/main/resources/sql/mysql/11_create_modeldata_table_ddl_2026_0630.sql`
around lines 5 - 8, The DDL for the model data table has a type mismatch on the
modelId mapping: the entity uses Integer, but the create script defines model_id
as varchar(255). Update the column definition in the create table script so it
matches the entity’s Integer type, and keep the rest of the table definition
aligned with the ORM mapping to avoid Hibernate schema updates trying to alter
it at startup.
app/src/main/resources/sql/mysql/10_create_apikey_table_ddl_2026_0630.sql-1-1 (1)

1-1: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

This migration is destructive.

Re-running this script will wipe every issued API key before recreating the table. That turns a normal redeploy or rollback into credential loss. Use additive/guarded DDL instead of drop table if exists.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/src/main/resources/sql/mysql/10_create_apikey_table_ddl_2026_0630.sql` at
line 1, The migration in the `t_api_key` DDL is destructive because it drops the
existing table on rerun, causing API key loss. Update the SQL migration to use
additive or guarded schema changes instead of `drop table if exists`, preserving
existing data and making the `t_api_key` creation path safe for redeploys and
rollbacks.
pom.xml-201-206 (1)

201-206: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Remove the explicit Hibernate version override in pom.xml:201-206. Spring Boot 3.2.4 manages Hibernate 6.4.4.Final; pinning hibernate-core to 6.3.2.Final downgrades the BOM-managed stack and can desynchronize the JPA/Hibernate modules. Prefer the Boot-managed version unless you override the full Hibernate set.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pom.xml` around lines 201 - 206, The pom.xml Hibernate dependency is
overriding Spring Boot’s managed version, which can desynchronize the
JPA/Hibernate stack. Update the hibernate-core entry in the Maven dependency
list to stop pinning 6.3.2.Final and let Spring Boot 3.2.4 manage the version,
or if a custom Hibernate version is required, align the full Hibernate set
consistently across the BOM-managed dependencies.
app/src/main/resources/application-alpha.yml-32-35 (1)

32-35: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Avoid ddl-auto: update in this profile. The repo also includes explicit MySQL DDL for t_model_data, and ModelData.modelId is an Integer while the table defines model_id as varchar(255), so Hibernate can silently mutate the schema on startup. Use validate or none wherever those scripts are applied.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/src/main/resources/application-alpha.yml` around lines 32 - 35, The JPA
configuration in the alpha profile is using hibernate ddl-auto update, which can
silently change the schema and conflict with the explicit MySQL DDL for
t_model_data. Update the alpha profile settings to use validate or none instead
of update, and keep the existing ModelData/JPA mapping aligned with the scripted
table definition, especially the ModelData.modelId field and the t_model_data
model_id column.
docker-compose.yml-37-47 (1)

37-47: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Unify the Redis password source
REDIS_PASSWORD here is expanded by Compose before the container starts, so the service-level env value won’t feed --requirepass. The app password is also quoted (SPRING_DATA_REDIS_PASSWORD="000000"), which passes the quotes into the value. Use a single unquoted source of truth for both sides.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docker-compose.yml` around lines 37 - 47, The Redis service is mixing two
password sources, and the Compose variable expansion in the command is not using
the container-level env value; update the Redis service definition and the app
Redis setting so both read from one unquoted source of truth. Use the existing
Redis-related symbols in docker-compose.yml, especially REDIS_PASSWORD,
SPRING_DATA_REDIS_PASSWORD, and the redis-server --requirepass command, and make
sure the password value is not wrapped in quotes so Compose and the application
receive the same raw string.
🟡 Minor comments (1)
base/src/main/java/com/tinyengine/it/modeldata/entity/ModelData.java-38-40 (1)

38-40: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Remove @Version from ModelData.version ModelData.version is business data exposed as _version; keep it as a plain field and use a separate numeric/timestamp lock column if row-level optimistic locking is needed.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@base/src/main/java/com/tinyengine/it/modeldata/entity/ModelData.java` around
lines 38 - 40, Remove the JPA `@Version` annotation from ModelData.version and
keep this field as plain business data used for the exposed _version value. If
optimistic locking is still needed, introduce a separate dedicated lock field
(for example a numeric or timestamp column) in ModelData and update the entity
mapping accordingly, leaving version unchanged aside from its normal
Schema/documentation usage.
🧹 Nitpick comments (2)
base/src/main/java/com/tinyengine/it/modeldata/service/ModelDataCacheService.java (1)

41-57: 🚀 Performance & Scalability | 🔵 Trivial | 🏗️ Heavy lift

Avoid caching and scanning the full model dataset for every query.

This loads every row for a model/tenant into application memory and Redis, then filters/sorts/pages in memory. Large models can create oversized Redis values and O(N) request cost; prefer DB-side filtering/count/pagination or add strict cache-size limits.

Also applies to: 125-173

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@base/src/main/java/com/tinyengine/it/modeldata/service/ModelDataCacheService.java`
around lines 41 - 57, The loadDataList method is caching and returning the full
model dataset, which forces every query path to scan all records in memory and
Redis. Refactor ModelDataCacheService so query-time filtering, sorting, and
pagination happen in the database instead of materializing all rows in
loadDataList, and only cache narrowly scoped or bounded results if needed.
Update the related list/query flow that uses loadDataList to accept page/filter
parameters and avoid full-dataset Redis values for large models.
base/src/main/java/com/tinyengine/it/config/RedisConfig.java (1)

33-45: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Align the value serializer with the cache payload type.

ModelDataCacheService caches JSON strings, but this template still uses the default value serializer. That writes Java-serialized bytes instead of plain JSON/text and makes the cache format JVM-specific. Either switch this path to StringRedisTemplate or set valueSerializer/hashValueSerializer explicitly.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@base/src/main/java/com/tinyengine/it/config/RedisConfig.java` around lines 33
- 45, The Redis template configuration in RedisConfig.redisTemplate is leaving
the value serializer at the default, while ModelDataCacheService stores JSON
strings; update this setup so cached values are written as plain text/JSON
rather than Java-serialized bytes. Either switch the cache path to
StringRedisTemplate or explicitly configure template.setValueSerializer and
template.setHashValueSerializer to match the payload type, keeping the existing
StringRedisSerializer usage for keys.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@app/src/main/resources/sql/mysql/init_data_2026_0630.sql`:
- Line 1: Remove the hardcoded API credential from the MySQL seed data and
replace it with a non-sensitive placeholder or omit the row entirely. Update the
initialization data in init_data_2026_0630.sql so t_api_key is not populated
with a real api_key/api_secret pair, and move any environment-specific
provisioning into a separate secret-management flow outside source control.

In `@base/src/main/java/com/tinyengine/it/common/exception/ExceptionEnum.java`:
- Around line 355-359: The tail of ExceptionEnum is malformed because the last
enum constant CM346 is followed by an extra comma before the semicolon, which
prevents compilation. Update the enum declaration so the final constant in
ExceptionEnum ends normally with the closing semicolon only, keeping the
surrounding CM345 and CM346 definitions unchanged.

In `@base/src/main/java/com/tinyengine/it/dynamic/service/DynamicService.java`:
- Around line 140-146: The dynamic payload is being polluted with audit/tenant
fields before validation, which causes valid requests to fail in
ModelDataCacheService.add/update. Remove the injected created_by, tenantId, and
updated_by entries from the JSON params in DynamicService and pass those values
only through the existing service parameters / ModelData entity fields; update
both the add and update paths so dto.getParams() contains only model-defined
data while modelDataCacheService.add/update still receives userId and tenantId
separately.
- Around line 60-70: The lookup in DynamicService is selecting the first
getModelByEnName(dto.getNameEn()) result, which can resolve to the wrong tenant
and user when the same nameEn exists in multiple tenants. Update the caller flow
in DynamicService to resolve the model using the current caller tenant/context
instead of modelList.get(0), and use that resolved model’s tenantId, id, and
creator/user fields consistently in the query and update paths. Apply the same
tenant-scoped resolution in the other affected DynamicService blocks mentioned
by the reviewer so reads and writes cannot cross tenant boundaries.

In
`@base/src/main/java/com/tinyengine/it/modeldata/service/ModelDataCacheService.java`:
- Around line 207-222: The update/delete paths in ModelDataCacheService are
scoped only by row ID, so they can operate outside the intended model and tenant
boundary. Change the ModelDataCacheService update and delete flows to verify
both modelId and tenantId from the request/context before mutating, and use
repository lookups like findByIdAndModelIdAndTenantId in delete as well as the
update path via dynamicModelDataService so only records in the requested scope
are modified.

---

Outside diff comments:
In `@base/src/main/java/com/tinyengine/it/dynamic/service/DynamicService.java`:
- Around line 165-175: The update flow in DynamicService still validates
dto.getParams() but then only uses the id for the update, so any extra
conditions are ignored. Update the logic in the relevant update method(s) to
either reject additional conditions beyond id or include all provided conditions
when building the update criteria, and make sure the params/conditions passed to
the mapper reflect the chosen behavior.

---

Major comments:
In `@app/src/main/resources/application-alpha.yml`:
- Around line 32-35: The JPA configuration in the alpha profile is using
hibernate ddl-auto update, which can silently change the schema and conflict
with the explicit MySQL DDL for t_model_data. Update the alpha profile settings
to use validate or none instead of update, and keep the existing ModelData/JPA
mapping aligned with the scripted table definition, especially the
ModelData.modelId field and the t_model_data model_id column.

In `@app/src/main/resources/sql/mysql/10_create_apikey_table_ddl_2026_0630.sql`:
- Around line 6-12: The current DDL for the api key table allows duplicate
api_key values because the uniqueness constraint is on the combined pair instead
of the key alone, and api_secret is still nullable. Update the table definition
in the MySQL create script so api_key is enforced as the unique identity via a
unique index or constraint on api_key only, and make api_secret not null as part
of the same schema change. Use the existing column definitions in the
create-apikey DDL to locate the constraint and nullability settings.
- Line 1: The migration in the `t_api_key` DDL is destructive because it drops
the existing table on rerun, causing API key loss. Update the SQL migration to
use additive or guarded schema changes instead of `drop table if exists`,
preserving existing data and making the `t_api_key` creation path safe for
redeploys and rollbacks.

In
`@app/src/main/resources/sql/mysql/11_create_modeldata_table_ddl_2026_0630.sql`:
- Line 1: The migration currently drops t_model_data unconditionally, which
wipes existing rows on every re-run. Update the SQL in the model data DDL
migration to preserve existing data, using a safe schema-change approach in the
same migration sequence instead of a DROP TABLE statement, and keep the change
aligned with the table definition handled by this script.
- Around line 5-8: The DDL for the model data table has a type mismatch on the
modelId mapping: the entity uses Integer, but the create script defines model_id
as varchar(255). Update the column definition in the create table script so it
matches the entity’s Integer type, and keep the rest of the table definition
aligned with the ORM mapping to avoid Hibernate schema updates trying to alter
it at startup.

In
`@base/src/main/java/com/tinyengine/it/dynamic/controller/ModelDataController.java`:
- Line 71: The controller methods in ModelDataController are returning raw
Exception text through Result.failed(...), which leaks internal details to
clients. Update the catch blocks around the insert/update/delete handlers to log
the full exception for debugging, but return a sanitized, generic failure
message from Result.failed; if needed, only pass through whitelisted business
exception messages. Keep the fix localized to the affected controller methods
rather than changing the Result API.

In `@base/src/main/java/com/tinyengine/it/dynamic/service/DynamicService.java`:
- Line 69: Remove logging of raw dynamic payloads and user identifiers from
DynamicService, especially the log statements around query/init/update/delete
flows. Replace log entries in methods such as the query path and the other
flagged call sites with safe metadata only, using identifiers like modelId,
tenantId, userId, field names, and counts instead of params/data contents or raw
IDs. Keep the existing log context in DynamicService but sanitize any arbitrary
model params and PII before logging.

In `@base/src/main/java/com/tinyengine/it/modeldata/dao/ApiKeyRepository.java`:
- Around line 9-10: `ApiKeyRepository` is using the wrong primary key type,
which makes inherited `JpaRepository` methods inconsistent with
`ApiKeyEntity.id`. Update the `ApiKeyRepository` declaration to use the same ID
type as `ApiKeyEntity` by changing the `JpaRepository` generic from `Long` to
`Integer`, while keeping `findByApiKeyAndStatus` unchanged.

In `@base/src/main/java/com/tinyengine/it/modeldata/dao/ModelDataRepository.java`:
- Line 13: The ModelData ID type is inconsistent across the entity, repository,
and service callers, since ModelData.id uses Integer while ModelDataRepository
and methods like findById/deleteById still rely on Long conversions. Update
ModelDataRepository to use the same ID type as ModelData, then adjust all
related service and caller methods that reference ModelDataRepository, findById,
and deleteById so they pass and return the consistent ID type without mixed
Long/Integer conversions.

In `@base/src/main/java/com/tinyengine/it/modeldata/filter/ApiKeyAuthFilter.java`:
- Around line 74-80: The nonce replay check in ApiKeyAuthFilter is happening too
early and uses a global nonce key, so move the Redis reservation until after the
HMAC/signature validation succeeds and tie it to the authenticated API key.
Update the nonce handling in the filter around the nonce validation block to
build the Redis key from the principal, such as nonce:{apiKey}:{nonce}, and only
call setIfAbsent after the signature check passes. Apply the same change to the
duplicate nonce logic later in the method as well.

In
`@base/src/main/java/com/tinyengine/it/modeldata/service/DynamicModelDataService.java`:
- Around line 116-120: The filter-building logic in DynamicModelDataService
currently concatenates caller-controlled keys into the JSON_EXTRACT path, which
can allow SQL injection through the field name. Update the loop that builds the
WHERE clause to validate each entry key against the model’s allowed properties
before appending it, and only then construct the JSON path in the query. Keep
using prepared parameters for the value, but ensure the key used by JSON_EXTRACT
is strictly whitelisted or rejected in the same method.
- Around line 44-52: The model parameter handling in DynamicModelDataService is
missing a null guard: both the create and update flows call isEmpty() on
model.getParameters() before checking whether parameters is null, which can
throw an NPE for models without metadata. Update the parameter retrieval logic
in the validation path used by the create/update methods so it first checks for
null, treats missing parameters as an empty list, and only runs the JSON
conversion and validationService.validate when a non-null list is present; use
the existing model.getParameters(), fieldDefs, and validationService.validate
flow to keep behavior consistent.
- Around line 69-71: The read/update/delete paths in DynamicModelDataService
currently load ModelData by primary key only, which bypasses tenant scoping.
Update the affected methods (including update and the related get/delete code
referenced in the review) to query by both dataId and tenantId, using the tenant
context already used by create/query, and make sure DynamicService passes
tenantId through instead of only id so all mutations and reads stay
tenant-isolated.

In
`@base/src/main/java/com/tinyengine/it/modeldata/service/ModelDataCacheService.java`:
- Around line 83-116: The filter logic in ModelDataCacheService currently treats
unknown operators as a pass-through in the switch inside the item filter, which
can silently broaden results on typos like gtee. Update the operator handling so
unsupported operators are rejected explicitly instead of falling through to a
match-all path; for example, validate the parsed operator before applying the
stream.filter and make the default case return a failure/empty result or throw
an error. Use the existing operator parsing and comparison logic in this method
to keep supported cases like eq, gt, like, in, and startswith unchanged.

In
`@base/src/main/java/com/tinyengine/it/modeldata/service/ValidationService.java`:
- Around line 52-53: Remove the raw payload print from
ValidationService.checkType, since logging the full validated value to stdout
can expose tenant data and bypass the app’s logging controls. Update checkType
so it only logs safe metadata like the field name or expected type, or remove
the debug output entirely, and keep validation behavior unchanged.
- Around line 16-19: The required-properties collection in ValidationService
should not rely on a nullable Boolean from ParametersDto, since
DynamicModelService can create entries without setting required and
`filter(ParametersDto::getRequired)` may NPE. Update the stream filter in the
validation logic to use a null-safe check like `Boolean.TRUE.equals(...)` on
`ParametersDto.getRequired`, keeping the rest of the requiredProps mapping
unchanged.

In
`@base/src/main/java/com/tinyengine/it/service/material/impl/ResourceServiceImpl.java`:
- Around line 203-205: The duplicate-check in ResourceServiceImpl is using
caller-controlled tenantId from resource.getTenantId(), which can target the
wrong tenant. In the ResourceServiceImpl flow, resolve the tenant from
LoginUserContext via DefaultLoginUserContext.getTenantId(), stamp that value
onto resource before the QueryWrapper lookup and before insert, and use the
authenticated tenant consistently for the hash duplicate check.

In `@docker-compose.yml`:
- Around line 37-47: The Redis service is mixing two password sources, and the
Compose variable expansion in the command is not using the container-level env
value; update the Redis service definition and the app Redis setting so both
read from one unquoted source of truth. Use the existing Redis-related symbols
in docker-compose.yml, especially REDIS_PASSWORD, SPRING_DATA_REDIS_PASSWORD,
and the redis-server --requirepass command, and make sure the password value is
not wrapped in quotes so Compose and the application receive the same raw
string.

In `@pom.xml`:
- Around line 201-206: The pom.xml Hibernate dependency is overriding Spring
Boot’s managed version, which can desynchronize the JPA/Hibernate stack. Update
the hibernate-core entry in the Maven dependency list to stop pinning
6.3.2.Final and let Spring Boot 3.2.4 manage the version, or if a custom
Hibernate version is required, align the full Hibernate set consistently across
the BOM-managed dependencies.

---

Minor comments:
In `@base/src/main/java/com/tinyengine/it/modeldata/entity/ModelData.java`:
- Around line 38-40: Remove the JPA `@Version` annotation from ModelData.version
and keep this field as plain business data used for the exposed _version value.
If optimistic locking is still needed, introduce a separate dedicated lock field
(for example a numeric or timestamp column) in ModelData and update the entity
mapping accordingly, leaving version unchanged aside from its normal
Schema/documentation usage.

---

Nitpick comments:
In `@base/src/main/java/com/tinyengine/it/config/RedisConfig.java`:
- Around line 33-45: The Redis template configuration in
RedisConfig.redisTemplate is leaving the value serializer at the default, while
ModelDataCacheService stores JSON strings; update this setup so cached values
are written as plain text/JSON rather than Java-serialized bytes. Either switch
the cache path to StringRedisTemplate or explicitly configure
template.setValueSerializer and template.setHashValueSerializer to match the
payload type, keeping the existing StringRedisSerializer usage for keys.

In
`@base/src/main/java/com/tinyengine/it/modeldata/service/ModelDataCacheService.java`:
- Around line 41-57: The loadDataList method is caching and returning the full
model dataset, which forces every query path to scan all records in memory and
Redis. Refactor ModelDataCacheService so query-time filtering, sorting, and
pagination happen in the database instead of materializing all rows in
loadDataList, and only cache narrowly scoped or bounded results if needed.
Update the related list/query flow that uses loadDataList to accept page/filter
parameters and avoid full-dataset Redis values for large models.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 845d404c-baf3-43a7-bd9e-0392240e1a91

📥 Commits

Reviewing files that changed from the base of the PR and between e24a8eb and d476dab.

📒 Files selected for processing (28)
  • app/src/main/java/com/tinyengine/it/TinyEngineApplication.java
  • app/src/main/resources/application-alpha.yml
  • app/src/main/resources/sql/mysql/10_create_apikey_table_ddl_2026_0630.sql
  • app/src/main/resources/sql/mysql/11_create_modeldata_table_ddl_2026_0630.sql
  • app/src/main/resources/sql/mysql/init_data_2026_0630.sql
  • base/src/main/java/com/tinyengine/it/common/exception/ExceptionEnum.java
  • base/src/main/java/com/tinyengine/it/config/RedisConfig.java
  • base/src/main/java/com/tinyengine/it/dynamic/controller/ModelDataController.java
  • base/src/main/java/com/tinyengine/it/dynamic/service/DynamicModelService.java
  • base/src/main/java/com/tinyengine/it/dynamic/service/DynamicService.java
  • base/src/main/java/com/tinyengine/it/login/config/LoginConfig.java
  • base/src/main/java/com/tinyengine/it/login/utils/SignatureUtil.java
  • base/src/main/java/com/tinyengine/it/modeldata/dao/ApiKeyRepository.java
  • base/src/main/java/com/tinyengine/it/modeldata/dao/ModelDataRepository.java
  • base/src/main/java/com/tinyengine/it/modeldata/entity/ApiKeyEntity.java
  • base/src/main/java/com/tinyengine/it/modeldata/entity/ModelData.java
  • base/src/main/java/com/tinyengine/it/modeldata/exception/BusinessException.java
  • base/src/main/java/com/tinyengine/it/modeldata/filter/ApiKeyAuthFilter.java
  • base/src/main/java/com/tinyengine/it/modeldata/service/ApiKeyService.java
  • base/src/main/java/com/tinyengine/it/modeldata/service/DynamicModelDataService.java
  • base/src/main/java/com/tinyengine/it/modeldata/service/ModelDataCacheService.java
  • base/src/main/java/com/tinyengine/it/modeldata/service/ValidationService.java
  • base/src/main/java/com/tinyengine/it/modeldata/util/CachedBodyHttpServletRequest.java
  • base/src/main/java/com/tinyengine/it/modeldata/util/CachedBodyServletInputStream.java
  • base/src/main/java/com/tinyengine/it/modeldata/util/ModelParser.java
  • base/src/main/java/com/tinyengine/it/service/material/impl/ResourceServiceImpl.java
  • docker-compose.yml
  • pom.xml

Comment thread app/src/main/resources/sql/mysql/init_data_2026_0630.sql Outdated
Comment thread base/src/main/java/com/tinyengine/it/common/exception/ExceptionEnum.java Outdated
Comment thread base/src/main/java/com/tinyengine/it/dynamic/service/DynamicService.java Outdated
Comment thread base/src/main/java/com/tinyengine/it/modeldata/service/ModelDataCacheService.java Outdated
@@ -15,6 +15,7 @@
import org.mybatis.spring.annotation.MapperScan;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

PR变更太多,请先完成以下内容:

  • 在PR描述中补充:基础PR背景介绍、实现方案、改动与影响范围
  • 自测(描述下自测了哪些场景、测试点及结果)
  • 本地先通过AI检视一轮及完成修复,PR描述贴上截图
  • coderabbit 提的检视完成修复
  • 部署环境结果,环境测试结果

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

本地postman测试已ok

@msslulu msslulu changed the title fix:model data ,apikey fix:update model data and API key authentication support Jun 30, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
base/src/test/java/com/tinyengine/it/modeldata/service/ModelDataCacheServiceTest.java (1)

52-75: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Assert the exact cache key and returned metadata.

ModelDataCacheService invalidates a tenant-scoped Redis key and returns _version/_row on successful add/update/delete, but these tests only check _id and delete(anyString()). They will stay green if the wrong cache key is deleted or the response metadata regresses. Populate modelId/tenantId on the mocked update result and verify the concrete key plus _version/_row.

Example tightening
 `@Test`
 void testUpdate_Success() throws Exception {
   Integer dataId = 1;
   String userId = "user1";
   Map<String, Object> newData = new HashMap<>();
   newData.put("field1", "newValue");

   ModelData mockModelData = new ModelData();
   mockModelData.setId(1);
+  mockModelData.setModelId(1);
+  mockModelData.setTenantId("tenant1");
   mockModelData.setVersion("0.0.2");
   mockModelData.setDataJson(newData);

   when(dynamicModelDataService.update(dataId, newData, userId)).thenReturn(mockModelData);

   Map<String, Object> result = modelDataCacheService.update(dataId, newData, userId);

   assertNotNull(result);
   assertEquals(1, result.get("_id"));
+  assertEquals("0.0.2", result.get("_version"));
+  assertEquals(1, result.get("_row"));
   verify(dynamicModelDataService, times(1)).update(dataId, newData, userId);
-  verify(redisTemplate, times(1)).delete(anyString());
+  verify(redisTemplate, times(1)).delete("modelData:1:tenant1");
 }

Also applies to: 78-122

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@base/src/test/java/com/tinyengine/it/modeldata/service/ModelDataCacheServiceTest.java`
around lines 52 - 75, The ModelDataCacheService tests are too loose because they
only assert _id and delete(anyString()), so they won’t catch cache-key or
response-metadata regressions. Update the add/update/delete success tests in
ModelDataCacheServiceTest to verify the exact tenant-scoped Redis key that
ModelDataCacheService invalidates, and assert the returned metadata fields
(_version and _row) in addition to _id. Also make sure the mocked ModelData for
update/delete includes modelId and tenantId so the assertions can verify the
real cache key and metadata path.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@base/src/test/java/com/tinyengine/it/modeldata/service/DynamicModelDataServiceTest.java`:
- Around line 93-127: The DynamicModelDataService.update path is not being
exercised for its merge-before-validate behavior because the test uses an empty
existing JSON map and only matches anyMap() in validation. Update
testUpdate_Success in DynamicModelDataServiceTest to seed ModelData
existing.getDataJson() with an additional pre-existing field, then capture and
assert the merged payload passed through validationService.validate and
modelDataRepo.save. Use the update method, modelDataRepo.findById, and
validationService.validate as the key symbols to verify the patch preserves
existing fields instead of validating only the incoming data.

In
`@base/src/test/java/com/tinyengine/it/modeldata/service/ValidationServiceTest.java`:
- Around line 196-220: The test named testValidate_OptionalField is not covering
optional-field handling in ValidationService.validate(...), because both
ParametersDto entries are still marked required and both are present in the
input data. Update this test so one field definition is optional, omit that
field from the data map, and optionally include an allowed system field such as
tenantId to verify the intended contract; use the existing
testValidate_OptionalField setup and ValidationService.validate reference to
keep the change localized.

---

Nitpick comments:
In
`@base/src/test/java/com/tinyengine/it/modeldata/service/ModelDataCacheServiceTest.java`:
- Around line 52-75: The ModelDataCacheService tests are too loose because they
only assert _id and delete(anyString()), so they won’t catch cache-key or
response-metadata regressions. Update the add/update/delete success tests in
ModelDataCacheServiceTest to verify the exact tenant-scoped Redis key that
ModelDataCacheService invalidates, and assert the returned metadata fields
(_version and _row) in addition to _id. Also make sure the mocked ModelData for
update/delete includes modelId and tenantId so the assertions can verify the
real cache key and metadata path.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: fa879e5f-8f54-4570-926c-b77e5ecfcc5f

📥 Commits

Reviewing files that changed from the base of the PR and between d476dab and 01701c5.

📒 Files selected for processing (8)
  • app/src/main/resources/sql/mysql/init_data_2026_0630.sql
  • base/src/main/java/com/tinyengine/it/modeldata/exception/BusinessException.java
  • base/src/main/java/com/tinyengine/it/modeldata/service/ModelDataCacheService.java
  • base/src/main/java/com/tinyengine/it/modeldata/service/ValidationService.java
  • base/src/test/java/com/tinyengine/it/modeldata/service/ApiKeyServiceTest.java
  • base/src/test/java/com/tinyengine/it/modeldata/service/DynamicModelDataServiceTest.java
  • base/src/test/java/com/tinyengine/it/modeldata/service/ModelDataCacheServiceTest.java
  • base/src/test/java/com/tinyengine/it/modeldata/service/ValidationServiceTest.java
🚧 Files skipped from review as they are similar to previous changes (4)
  • base/src/main/java/com/tinyengine/it/modeldata/exception/BusinessException.java
  • app/src/main/resources/sql/mysql/init_data_2026_0630.sql
  • base/src/main/java/com/tinyengine/it/modeldata/service/ValidationService.java
  • base/src/main/java/com/tinyengine/it/modeldata/service/ModelDataCacheService.java

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

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

⚠️ Outside diff range comments (2)
base/src/main/java/com/tinyengine/it/modeldata/filter/ApiKeyAuthFilter.java (2)

81-88: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Move nonce reservation after signature validation and scope it per API key.

The current global nonce:<nonce> key is written before authentication succeeds, so invalid requests can burn nonces and collide across different API keys. Validate the signature first, then reserve nonce:<apiKey>:<nonce> atomically before forwarding.

Suggested fix shape
-String nonceKey = "nonce:" + nonce;
-Boolean isNew = stringRedisTemplate.opsForValue().setIfAbsent(nonceKey, "1", Duration.ofMinutes(5));
-if (Boolean.FALSE.equals(isNew)) {
-    response.setStatus(401);
-    response.getWriter().write("Nonce already used");
-    return;
-}
-
 ...
 String expectedSignature = SignatureUtil.hmacSha256(keyEntity.getApiSecret(), stringToSign);
 if (!expectedSignature.equals(signature)) {
     response.setStatus(401);
     response.getWriter().write("Invalid signature");
     return;
 }
+
+String nonceKey = "nonce:" + apiKey + ":" + nonce;
+Boolean isNew = stringRedisTemplate.opsForValue().setIfAbsent(nonceKey, "1", Duration.ofMinutes(5));
+if (!Boolean.TRUE.equals(isNew)) {
+    response.setStatus(401);
+    response.getWriter().write("Nonce already used");
+    return;
+}

Also applies to: 106-112

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@base/src/main/java/com/tinyengine/it/modeldata/filter/ApiKeyAuthFilter.java`
around lines 81 - 88, Move the nonce replay check in ApiKeyAuthFilter so it
happens only after signature validation succeeds, instead of reserving the nonce
before authentication. Scope the Redis key by API key using the same
apiKey/nonce values already available in the filter, and use the reservation
step atomically right before forwarding the request. Update both nonce check
locations in ApiKeyAuthFilter to follow the same per-key pattern and keep the
existing 401 handling for reused nonces.

100-104: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Include query parameters in the signed canonical request.

Only requestURI is signed. For query endpoints, an attacker can modify ?page=, filters, sorting, or IDs without invalidating the signature because getQueryString() is excluded.

Suggested fix shape
 String method = request.getMethod();
-String path = request.getRequestURI();
+String query = request.getQueryString();
+String path = query == null ? request.getRequestURI() : request.getRequestURI() + "?" + query;
 String body = wrappedRequest.getBodyString();
 String stringToSign = String.format("%s\n%s\n%s\n%s\n%s", method, path, timestamp, nonce, body);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@base/src/main/java/com/tinyengine/it/modeldata/filter/ApiKeyAuthFilter.java`
around lines 100 - 104, The canonical request built in ApiKeyAuthFilter’s
signing flow currently uses only request.getRequestURI(), so query parameters
are not covered by the signature. Update the string-to-sign construction to
include the query string when present (for example by appending the output of
request.getQueryString() in the canonical path component) so endpoints using
page, filters, sorting, or IDs are signed consistently. Keep the change
localized to the signing logic around stringToSign and preserve the existing
method/path/timestamp/nonce/body ordering while ensuring the full request target
is authenticated.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@base/src/main/java/com/tinyengine/it/modeldata/filter/ApiKeyAuthFilter.java`:
- Around line 114-120: `ApiKeyAuthFilter` sets the tenant context with
`DefaultLoginUserContext.setCurrentTenant(org)`, but the `finally` block only
calls `DefaultLoginUserContext.clear()`, which leaves `CURRENT_TENANT` behind on
the thread. Update the cleanup in this filter to also clear the tenant context
by calling `clearCurrentTenant()` alongside the existing `clear()` call so the
request-scoped state set here is fully removed.
- Around line 44-48: Bind the tenant to the API key in ApiKeyAuthFilter instead
of trusting the client-supplied X-Lowcode-Org header. In the request handling
flow around the keyEntity lookup and context population, verify the API key’s
owning tenant/org and use that value for the tenant context; if the header is
present and does not match, reject the request. Remove any direct use of the
header as the source of truth so downstream reads/writes are scoped only by the
tenant associated with the validated API key.

---

Outside diff comments:
In `@base/src/main/java/com/tinyengine/it/modeldata/filter/ApiKeyAuthFilter.java`:
- Around line 81-88: Move the nonce replay check in ApiKeyAuthFilter so it
happens only after signature validation succeeds, instead of reserving the nonce
before authentication. Scope the Redis key by API key using the same
apiKey/nonce values already available in the filter, and use the reservation
step atomically right before forwarding the request. Update both nonce check
locations in ApiKeyAuthFilter to follow the same per-key pattern and keep the
existing 401 handling for reused nonces.
- Around line 100-104: The canonical request built in ApiKeyAuthFilter’s signing
flow currently uses only request.getRequestURI(), so query parameters are not
covered by the signature. Update the string-to-sign construction to include the
query string when present (for example by appending the output of
request.getQueryString() in the canonical path component) so endpoints using
page, filters, sorting, or IDs are signed consistently. Keep the change
localized to the signing logic around stringToSign and preserve the existing
method/path/timestamp/nonce/body ordering while ensuring the full request target
is authenticated.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 90fd5876-661d-4720-b070-142e3e4290a8

📥 Commits

Reviewing files that changed from the base of the PR and between c582ac8 and 63e4864.

📒 Files selected for processing (5)
  • base/src/main/java/com/tinyengine/it/dynamic/service/DynamicService.java
  • base/src/main/java/com/tinyengine/it/login/config/context/DefaultLoginUserContext.java
  • base/src/main/java/com/tinyengine/it/modeldata/dao/ModelDataRepository.java
  • base/src/main/java/com/tinyengine/it/modeldata/filter/ApiKeyAuthFilter.java
  • base/src/main/java/com/tinyengine/it/modeldata/service/ModelDataCacheService.java
🚧 Files skipped from review as they are similar to previous changes (3)
  • base/src/main/java/com/tinyengine/it/modeldata/dao/ModelDataRepository.java
  • base/src/main/java/com/tinyengine/it/modeldata/service/ModelDataCacheService.java
  • base/src/main/java/com/tinyengine/it/dynamic/service/DynamicService.java

Comment on lines +44 to +48
String org = request.getHeader("X-Lowcode-Org");
if(org == null || org.isEmpty()) {
response.setStatus(401);
response.getWriter().write("Missing required header: X-Lowcode-Org");
return;

@coderabbitai coderabbitai Bot Jun 30, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🔴 Critical | ⚡ Quick win

Bind the tenant to the API key instead of trusting X-Lowcode-Org.

Line 115 stores a client-supplied tenant header directly into the context, while keyEntity is not used to verify tenant ownership. A valid API key holder can sign requests for another tenant by changing X-Lowcode-Org, which breaks the PR’s automatic tenant-binding goal and downstream tenant-scoped reads/writes.

Suggested fix shape
 ApiKeyEntity keyEntity = apiKeyService.getValidKey(apiKey);
 if (keyEntity == null) {
     response.setStatus(401);
     response.getWriter().write("Invalid API Key");
     return;
 }
+
+String boundTenant = keyEntity.getTenantId();
+if (!boundTenant.equals(org)) {
+    response.setStatus(401);
+    response.getWriter().write("Tenant does not match API Key");
+    return;
+}
 ...
-DefaultLoginUserContext.setCurrentTenant(org);
+DefaultLoginUserContext.setCurrentTenant(boundTenant);

Also applies to: 90-115

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@base/src/main/java/com/tinyengine/it/modeldata/filter/ApiKeyAuthFilter.java`
around lines 44 - 48, Bind the tenant to the API key in ApiKeyAuthFilter instead
of trusting the client-supplied X-Lowcode-Org header. In the request handling
flow around the keyEntity lookup and context population, verify the API key’s
owning tenant/org and use that value for the tenant context; if the header is
present and does not match, reject the request. Remove any direct use of the
header as the source of truth so downstream reads/writes are scoped only by the
tenant associated with the validated API key.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

no need change

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@msslulu Understood — I won’t push for a change on this thread.

If the current behavior is intentional, a short note about where tenant binding is guaranteed upstream would make that clearer for future reviewers, but I’m okay to leave it here.

(╥﹏╥)🐇

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

@msslulu 这个问题解决了吗,为什么不需要改

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!

@@ -0,0 +1 @@
INSERT INTO `t_api_key` (`id`, `api_key`, `api_secret`, `expire_time`, `tenant_id`, `status`) VALUES (1, '', '', '2032-11-01 11:38:23', NULL, 1);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

初始化数据插入 api_key=''api_secret=''status=1 且长期有效。过滤器只检查 header 是否为 null,没有拒绝空字符串;ApiKeyService 也只按 key 和 status 查询。只要该 init SQL 被部署执行,请求方可以发送空 X-API-Key,再用空 secret 计算 HMAC,通过签名校验。结合信任请求头 X-Lowcode-Org问题 ,还可以自选 X-Lowcode-Org 写入租户上下文。

这条是已关闭意见的二次风险:最初 CodeRabbit 说“不要提交真实 API key/secret”。当前代码把真实值改成空字符串,但这不是安全修复,因为空字符串同样可以作为有效凭据使用。

建议改法:不要在初始化 SQL 中插入任何启用状态的默认 API Key。服务端也应拒绝空 X-API-Key、空 X-Signature、空 secret,并且 API secret 不应明文长期落库。

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

数据库加密存储密钥和APIKEY,环境变量管理解密密钥


import java.util.List;
@Repository
public interface ModelDataRepository extends JpaRepository<ModelData, Long>, JpaSpecificationExecutor<ModelData> {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[Medium] ModelData 主键类型和 Repository 泛型不一致,更新/删除可能运行时失败。

ModelData.id 当前是 Integer,但这里声明为 JpaRepository<ModelData, Long>。这样 findById / deleteById 会按 Long 处理主键,而实体实际主键是 Integer,Hibernate/JPA 运行时容易出现 ID 类型不匹配异常,导致已创建的模型数据无法更新或删除。

建议统一主键类型:要么把 Repository 改成 JpaRepository<ModelData, Integer> 并同步调整 service 中的 Long.valueOf(...) / deleteById(Long);要么把实体 id 统一改成 Long

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

service层调用的时候已经做了转换


String tenantId = DefaultLoginUserContext.getCurrentTenant();
Integer modelId=null;
List<Model> modelList = modelService.getModelByEnName(dto.getNameEn());

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[High] 多租户场景下不能只按 nameEn 查模型后取第一条。

这里用 getModelByEnName(dto.getNameEn()) 查出列表后,后续直接 modelList.get(0) 获取 modelId / createdBy。如果不同租户存在相同 nameEn 的模型,就可能拿到另一个租户的模型定义或创建人,再用当前租户上下文去读写 t_model_data,造成数据错读、错写或权限边界混乱。

建议改为按当前租户精确查询模型,例如 nameEn + tenantId,并在 query/insert/update/delete 四条路径都使用同一个租户范围内解析出来的 model。多租户代码里不应依赖 “第一条记录” 决定模型归属。

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

创建模型的时候校验了模型唯一性,nameEn 重复会报错,所以通过nameEn 查到的只有一条数据

}

// 4. Nonce 防重放(Redis 存储,有效期5分钟)
String nonceKey = "nonce:" + nonce;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[Medium] nonce 在签名校验前就写入 Redis,且没有按 API Key 隔离。

当前先执行 setIfAbsent("nonce:" + nonce),之后才校验 API Key 和签名。这样无效请求也能消耗 nonce,导致后续合法请求被误判为重放;同时 Redis key 没包含 API Key,不同 API Key 如果使用相同 nonce 也会互相冲突。

建议先完成 API Key 和签名校验,再原子写入 nonce;Redis key 也应按 key 隔离,例如 nonce:<apiKey>:<nonce>。这样只有认证通过的请求会占用 nonce,不同 API Key 之间也不会相互影响。


// 7. 构建待签名字符串:method\npath\ntimestamp\nnonce\nbody
String method = request.getMethod();
String path = request.getRequestURI();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[Medium] 签名没有覆盖 query string,查询参数可被篡改。

这里使用 request.getRequestURI() 构造待签名 path,它只包含路径,不包含 ?page=...&sort=... 这类 query 参数。对于查询、分页、排序、过滤等接口,调用方或中间人可以修改 query 参数而不改变签名校验结果。

建议在 canonical path 中包含 request.getQueryString(),例如 query 存在时使用 requestURI + "?" + queryString,保持现有 method/path/timestamp/nonce/body 顺序不变,但确保完整请求目标被签名保护。

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

没有查询参数

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants