Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

/**
Expand All @@ -25,6 +26,7 @@
@SpringBootApplication
@EnableAspectJAutoProxy
@MapperScan({"com.tinyengine.it.mapper","com.tinyengine.it.dynamic.dao"})
@EntityScan("com.tinyengine.it.modeldata.entity")
public class TinyEngineApplication {
/**
* The entry point of application.
Expand Down
22 changes: 22 additions & 0 deletions app/src/main/resources/application-alpha.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,28 @@ spring:
min-evictable-idle-time-millis: 300000 # 连接在池中保持空闲的最小时间(单位:毫秒)。如果空闲时间超过这个值,连接将被回收,默认值为 1800000。
pool-prepared-statements: true # 是否缓存 PreparedStatement 对象,默认值为 true。
max-open-prepared-statements: 20 # 最大缓存的 PreparedStatement 数量,默认值为 -1,表示无限制。如果 `pool-prepared-statements` 设置为 true,设置此值以限制缓存数量。

jpa:
hibernate:
ddl-auto: update
show-sql: true

data:
redis:
host: tiny-engine-redis
port: 6379
database: 0 # 默认库,Nonce 建议单独使用一个库(如 1),避免业务数据干扰
# 连接超时配置(非常重要)
connect-timeout: 5000ms # 建立连接超时,5秒
timeout: 5000ms # 读取数据超时,5秒
jedis: # Jedis 连接池配置
pool:
max-active: 20
max-idle: 10
min-idle: 5
max-wait: 2000ms


# 清空任务配置
cleanup:
enabled: false
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
drop table if exists `t_api_key`;

create table `t_api_key`
(
`id` int not null auto_increment comment '主键id',
`api_key` varchar(255) not null comment 'api_key',
`api_secret` varchar(255) comment '秘钥',
`expire_time` timestamp not null comment '过期时间',
`tenant_id` varchar(60) comment '租户id',
`status` int comment '业务租户id',
primary key (`id`) using btree,
unique index `u_idx_api_key` (`api_key`,`api_secret`) using btree
) engine = innodb comment = 'api_key表';

Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
drop table if exists `t_model_data`;

create table `t_model_data`
(
`id` int not null auto_increment comment '主键id',
`model_id` varchar(255) not null comment '模型id',
`data_json` json NOT NULL COMMENT '模型数据',
`version` varchar(255) comment '版本',
`created_by` varchar(60) not null comment '创建人',
`created_time` timestamp not null default current_timestamp comment '创建时间',
`last_updated_by` varchar(60) not null comment '最后修改人',
`last_updated_time` timestamp not null default current_timestamp comment '更新时间',
`tenant_id` varchar(60) comment '租户id',
`renter_id` varchar(60) comment '业务租户id',
`site_id` varchar(60) comment '站点id,设计预留字段',
primary key (`id`) using btree,
unique index `u_idx_model_data` (`id`,`model_id`) using btree
) engine = innodb comment = '模型数据表';

1 change: 1 addition & 0 deletions app/src/main/resources/sql/mysql/init_data_2026_0630.sql
Original file line number Diff line number Diff line change
@@ -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,环境变量管理解密密钥

Original file line number Diff line number Diff line change
Expand Up @@ -352,7 +352,11 @@ public enum ExceptionEnum implements IBaseError {
/**
* Cm 345 exception enum.
*/
CM345("CM345", "用户名不存在,请重新输入"),;
CM345("CM345", "用户名不存在,请重新输入"),
/**
* Cm 345 exception enum.
*/
CM346("CM346", "缺少请求头");
/**
* 错误码
*/
Expand Down
48 changes: 48 additions & 0 deletions base/src/main/java/com/tinyengine/it/config/RedisConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package com.tinyengine.it.config;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.RedisPassword;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;

@Configuration
public class RedisConfig {

/**
* 专门用于 Nonce 存储的 RedisTemplate(Key 和 Value 都存字符串)
* 避免乱码,方便 redis-cli 命令行调试查看
*/
@Bean
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory connectionFactory) {
StringRedisTemplate template = new StringRedisTemplate();
template.setConnectionFactory(connectionFactory);
// StringRedisTemplate 默认使用 StringRedisSerializer,无需额外配置
return template;
}

/**
* (可选)如果你的业务还需要存对象,可以配置通用的 RedisTemplate
*/
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(connectionFactory);

// 使用 StringRedisSerializer 序列化 Key
StringRedisSerializer stringSerializer = new StringRedisSerializer();
template.setKeySerializer(stringSerializer);
template.setHashKeySerializer(stringSerializer);

// Value 使用 Jackson 序列化(如果存对象)
// 这里不重复贴 Jackson 代码,保持配置简洁
template.afterPropertiesSet();
return template;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ public Result<Map<String, Object> > insert(@RequestBody @Valid DynamicInsert dto
return Result.success(dynamicService.insert(dto));
} catch (Exception e) {
log.error("insert failed for table: {}", dto.getNameEn(), e);
return Result.failed("insert operation failed");
return Result.failed("insert operation failed:"+e.getMessage());
}

}
Expand All @@ -90,7 +90,7 @@ public Result<Map<String, Object> > update(@RequestBody @Valid DynamicUpdate dto
return Result.success(dynamicService.update(dto));
} catch (Exception e) {
log.error("updateApi failed for table: {}", dto.getNameEn(), e);
return Result.failed("update operation failed");
return Result.failed("update operation failed:"+e.getMessage());
}

}
Expand All @@ -111,7 +111,7 @@ public Result<Map<String, Object> > delete(@RequestBody @Valid DynamicDelete dto
return Result.success(dynamicService.delete(dto));
} catch (Exception e) {
log.error("deleteApi failed for table: {}", dto.getNameEn(), e);
return Result.failed("delete operation failed");
return Result.failed("delete operation failed:"+e.getMessage());
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -606,18 +606,18 @@ public Map<String, Object> createData(DynamicInsert dataDto) {


String tableName = getTableName(dataDto.getNameEn());
Map<String, Object> record = new HashMap<>(dataDto.getParams());
for (String col : record.keySet()) {
Map<String, Object> map = new HashMap<>(dataDto.getParams());
for (String col : map.keySet()) {
SqlIdentifierValidator.validate(col);
}
String userId = loginUserContext.getLoginUserId();
// 添加系统字段
record.put("created_by",userId);
record.put("updated_by", userId);
map.put("created_by",userId);
map.put("updated_by", userId);

// 构建SQL
String columns = String.join(", ", record.keySet());
String placeholders = record.keySet().stream()
String columns = String.join(", ", map.keySet());
String placeholders = map.keySet().stream()
.map(k -> "?")
.collect(Collectors.joining(", "));

Expand All @@ -634,7 +634,7 @@ public PreparedStatement createPreparedStatement(Connection con) throws SQLExcep
PreparedStatement ps = con.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);

int index = 1;
for (Object value : record.values()) {
for (Object value : map.values()) {
ps.setObject(index++, value);
}

Expand All @@ -645,10 +645,10 @@ public PreparedStatement createPreparedStatement(Connection con) throws SQLExcep
Long generatedId = keyHolder.getKey() != null ? keyHolder.getKey().longValue() : null;

if (generatedId != null) {
record.put("id", generatedId);
map.put("id", generatedId);
}

return record;
return map;
}


Expand Down
Loading
Loading