v1.1.0: Alpine 轻量级 Docker 开发环境 + 敏感凭证清理

This commit is contained in:
lxy
2026-07-27 13:59:18 +08:00
parent feb76081a0
commit ccca0fa62b
51 changed files with 6125 additions and 993 deletions
@@ -0,0 +1,382 @@
# Redkale FilterNode 使用易错点
> 创建时间:2026-02-03
> 基于:flux-api 项目实际代码
---
## ❌ 常见错误
### 1. LIKE 查询错误
**错误写法**
```java
// ❌ 错误:使用静态方法
node.and(PartnerFormRecord::getRealname, FilterNode.like(keyword));
```
**正确写法**
```java
// ✅ 正确:使用实例方法
node.like(PartnerFormRecord::getRealname, keyword);
```
---
### 2. 比较查询错误
**错误写法**
```java
// ❌ 错误:方法名拼写错误
node.and(PartnerFormRecord::getCreatedtime, FilterNode.greateEqual(starttime));
node.and(PartnerFormRecord::getCreatedtime, FilterNode.lessEqual(endtime));
```
**正确写法**
```java
// ✅ 正确:使用简短的方法名
node.ge(PartnerFormRecord::getCreatedtime, starttime);
node.le(PartnerFormRecord::getCreatedtime, endtime);
```
**参考方法**
- `ge()` - Greater or Equal>=
- `le()` - Less or Equal<=
- `gt()` - Greater Than>
- `lt()` - Less Than<
---
### 3. OR 条件错误
**错误写法**
```java
// ❌ 错误:在 and() 中混用 FilterNode 静态方法
node.and(
FilterNode.or(
PartnerFormRecord::getRealname, FilterNode.like(keyword),
PartnerFormRecord::getMobile, FilterNode.like(keyword)
)
);
```
**正确写法**
```java
// ✅ 正确:创建独立 OR 节点,链式调用
FilterNode orNode = new FilterNode();
orNode.or(PartnerFormRecord::getRealname, FilterNode.like(keyword))
.or(PartnerFormRecord::getMobile, FilterNode.like(keyword))
.or(PartnerFormRecord::getIdcard, FilterNode.like(keyword));
node.and(orNode);
```
---
### 4. 等值条件冗余
**错误写法**
```java
// ❌ 冗余:等值条件不需要特殊方法
node.and(PartnerFormRecord::getStatus, FilterNode.eq(1));
```
**正确写法**
```java
// ✅ 简洁:直接传值
node.and(PartnerFormRecord::getStatus, 1);
```
---
### 5. NOT 条件错误
**错误写法**
```java
// ❌ 错误:使用不存在的 notLike 方法
node.notLike(PartnerFormRecord::getStatus, 3);
```
**正确写法**
```java
// ✅ 正确:使用 notEq
node.notEq(PartnerFormRecord::getStatus, 3);
```
---
## ✅ 正确的模式
### 基础查询条件
```java
FilterNode node = new FilterNode();
// 固定条件
node.and(PartnerFormRecord::getDraftstatus, (short) 0);
// 可选条件
if (bean.getKeyword() != null && !bean.getKeyword().isEmpty()) {
node.like(PartnerFormRecord::getRealname, bean.getKeyword());
}
// 范围查询
if (bean.getStarttime() > 0) {
node.ge(PartnerFormRecord::getCreatedtime, bean.getStarttime());
}
if (bean.getEndtime() > 0) {
node.le(PartnerFormRecord::getCreatedtime, bean.getEndtime());
}
// 不等于
node.notEq(PartnerFormRecord::getStatus, (short) 3);
```
### OR 条件组合
```java
// 关键词:姓名 OR 电话 OR 身份证
if (bean.getKeyword() != null && !bean.getKeyword().isEmpty()) {
FilterNode orNode = new FilterNode();
orNode.or(PartnerFormRecord::getRealname, FilterNode.like(bean.getKeyword()))
.or(PartnerFormRecord::getMobile, FilterNode.like(bean.getKeyword()))
.or(PartnerFormRecord::getIdcard, FilterNode.like(bean.getKeyword()));
node.and(orNode);
}
```
### IN 条件
```java
// IN 查询
FilterNode node = new FilterNode();
node.in(PartnerFormRecord::getFormdataid, formIds.toArray());
List<PartnerFormRecord> list = dataSource.queryList(PartnerFormRecord.class, node);
```
---
## 📋 FilterNode 方法速查表
| 方法 | 说明 | 示例 |
|------|------|------|
| `and()` | AND 等值条件 | `node.and(Entity::getField, value)` |
| `like()` | LIKE 模糊查询 | `node.like(Entity::getField, "%keyword%")` |
| `ge()` | >= 大于等于 | `node.ge(Entity::getTime, timestamp)` |
| `le()` | <= 小于等于 | `node.le(Entity::getTime, timestamp)` |
| `gt()` | > 大于 | `node.gt(Entity::getField, value)` |
| `lt()` | < 小于 | `node.lt(Entity::getField, value)` |
| `notEq()` | != 不等于 | `node.notEq(Entity::getStatus, 3)` |
| `in()` | IN 查询 | `node.in(Entity::getId, ids.toArray())` |
| `or()` | OR 条件 | 需创建独立 FilterNode |
---
## 🔍 完整示例参考
### PartnerFormService.java
```java
@RestMapping(name = "list", comment = "分页查询表单记录")
public RetResult<Sheet<PartnerFormRecord>> list(FormRecordFilterBean bean, Flipper flipper) {
FilterNode node = new FilterNode();
node.notEq(PartnerFormRecord::getStatus, STATUS_DELETED);
if (!Utils.isEmpty(bean.getRealname())) {
node.like(PartnerFormRecord::getRealname, bean.getRealname());
}
if (bean.getDraftstatus() >= 0) {
node.and(PartnerFormRecord::getDraftstatus, bean.getDraftstatus());
}
if (bean.getStarttime() > 0) {
node.ge(PartnerFormRecord::getCreatedtime, bean.getStarttime());
}
if (bean.getEndtime() > 0) {
node.le(PartnerFormRecord::getCreatedtime, bean.getEndtime());
}
Sheet<PartnerFormRecord> sheet = dataSource.querySheet(PartnerFormRecord.class, flipper, node);
return render(sheet);
}
```
### PartnerCustomerService.java
```java
@RestMapping(name = "list", comment = "分页查询客户列表")
public RetResult<Sheet<PartnerCustomer>> list(PartnerCustomerFilterBean bean, Flipper flipper) {
FilterNode node = new FilterNode();
node.notEq(PartnerCustomer::getStatus, (short) 3);
if (!Utils.isEmpty(bean.getMobile())) {
node.and(PartnerCustomer::getMobile, bean.getMobile());
}
if (bean.getStatus() > 0) {
node.and(PartnerCustomer::getStatus, bean.getStatus());
}
if (bean.getStarttime() > 0) {
node.ge(PartnerCustomer::getCreatedtime, bean.getStarttime());
}
if (bean.getEndtime() > 0) {
node.le(PartnerCustomer::getCreatedtime, bean.getEndtime());
}
Sheet<PartnerCustomer> sheet = dataSource.querySheet(PartnerCustomer.class, flipper, node);
return render(sheet);
}
```
---
## ⚠️ 注意事项
1. **方法调用方式**:所有条件方法都是 `FilterNode` 实例的方法,不是静态方法
2. **方法命名**`ge`/`le` 而非 `greateEqual`/`lessEqual`
3. **OR 条件**:必须创建独立的 `FilterNode` 对象进行链式调用
4. **值比较**:等值条件直接传值,不需要 `FilterNode.eq()`
5. **LIKE 参数**:通常需要在参数中自行添加 `%` 通配符
---
## 📚 参考代码位置
- `flux-api/src/main/java/cn/casehub/partner/PartnerFormService.java`
- `flux-api/src/main/java/cn/casehub/partner/PartnerCustomerService.java`
- `flux-api/src/main/java/cn/casehub/partner/PartnerVisitService.java`
---
# Redkale JSON 使用要点
> 更新时间:2026-03-03
> 背景:速贷接口接入时错误使用 `@JsonProperty`Jackson 注解),Redkale 不支持
---
## ❌ 常见错误
### 1. 使用 Jackson 注解
**错误写法**
```java
// ❌ 错误:Redkale 不支持 Jackson 注解
import com.fasterxml.jackson.annotation.JsonProperty;
@JsonProperty("mobile_md5")
private String mobileMd5;
```
**正确方案**:使用 Redkale 的 `@ConvertColumn(name = "xxx")` 进行字段重命名:
```java
// ✅ 正确:使用 @ConvertColumn 重命名
import org.redkale.convert.ConvertColumn;
@ConvertColumn(name = "mobile_md5")
private String mobileMd5; // Java驼峰,JSON下划线
```
---
## ✅ JsonConvert 使用方式
### 序列化(对象 → JSON
```java
// 方式1:实例方法
JsonConvert convert = JsonConvert.root();
String json = convert.convertTo(object);
// 方式2:静态调用
String json = JsonConvert.root().convertTo(object);
```
### 反序列化(JSON → 对象)
```java
// 单个对象
User user = JsonConvert.root().convertFrom(User.class, json);
// 泛型集合
List<User> users = JsonConvert.root().convertFrom(
new TypeToken<List<User>>() {}.getType(),
json
);
// Map(用于解析不确定结构的JSON)
Map<String, Object> map = JsonConvert.root().convertFrom(Map.class, json);
```
---
## 📋 字段映射规则
| Java 字段名 | JSON 字段名 | 说明 |
|------------|------------|------|
| `mobileMd5` | `mobileMd5` | 默认:驼峰 → 驼峰 |
| `mobile_md5` | `mobile_md5` | 默认:下划线 → 下划线 |
| `mobileMd5` + `@ConvertColumn(name="mobile_md5")` | `mobile_md5` | ✅ 重命名 |
| `mobileMd5` + `@ConvertColumn(ignore=true)` | - | 忽略字段 |
**结论**:使用 `@ConvertColumn(name = "xxx")` 可实现字段重命名。
---
## 🔧 跨命名风格转换方案
当第三方接口使用下划线,内部使用驼峰时:
### 方案1@ConvertColumn + Utils.copy(推荐)
```java
// 速贷请求BeanJava驼峰,JSON下划线)
public class SuDaiRequest {
@ConvertColumn(name = "mobile_md5")
private String mobileMd5;
@ConvertColumn(name = "city_id")
private String cityId;
}
// 内部Bean(驼峰)
public class CollideBean {
private String mobileMd5;
private String cityId;
}
// 转换器 - 使用 Utils.copy
public class Converter {
public static CollideBean toBean(SuDaiRequest req) {
return Utils.copy(new CollideBean(), req); // 一行搞定
}
}
```
### 方案2:手动映射(不推荐)
```java
// 速贷请求Bean(下划线)
public class SuDaiRequest {
private String mobile_md5;
private String city_id;
}
// 转换器 - 手动映射
public class Converter {
public static CollideBean toBean(SuDaiRequest req) {
CollideBean bean = new CollideBean();
bean.setMobileMd5(req.getMobile_md5()); // 手动映射
bean.setCityId(req.getCity_id());
return bean;
}
}
```
---
## ⚠️ 注意事项
1. **使用 @ConvertColumn**:Redkale 的字段重命名注解,不是 Jackson 的 @JsonProperty
2. **@ConvertColumn 用法**
- `@ConvertColumn(name = "xxx")` - 重命名JSON字段
- `@ConvertColumn(ignore = true)` - 忽略字段
3. **配合 Utils.copy**:重命名后,Java字段名一致,可用 `Utils.copy` 简化转换