v1.1.0: Alpine 轻量级 Docker 开发环境 + 敏感凭证清理
This commit is contained in:
@@ -0,0 +1,124 @@
|
||||
# MySQL 慢查询分析
|
||||
|
||||
---
|
||||
|
||||
## 查看慢查询配置
|
||||
|
||||
```sql
|
||||
SHOW VARIABLES LIKE 'slow_query%';
|
||||
SHOW VARIABLES LIKE 'long_query_time';
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 查询慢日志(RDS/MySQL)
|
||||
|
||||
```sql
|
||||
-- 最近24小时慢查询
|
||||
SELECT
|
||||
db,
|
||||
LEFT(REPLACE(REPLACE(sql_text, '\n', ' '), ' ', ' '), 200) as sql_preview,
|
||||
query_time,
|
||||
rows_examined,
|
||||
rows_sent,
|
||||
start_time
|
||||
FROM mysql.slow_log
|
||||
WHERE start_time > DATE_SUB(NOW(), INTERVAL 24 HOUR)
|
||||
ORDER BY query_time DESC
|
||||
LIMIT 30;
|
||||
|
||||
-- 查询所有慢记录
|
||||
SELECT * FROM mysql.slow_log ORDER BY start_time DESC LIMIT 20;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 查看当前运行的长查询
|
||||
|
||||
```sql
|
||||
-- 查看所有查询
|
||||
SHOW FULL PROCESSLIST;
|
||||
|
||||
-- 筛选长查询(超过10秒)
|
||||
SELECT
|
||||
id, User, Host, db, Time, State,
|
||||
LEFT(Info, 150) as sql_preview
|
||||
FROM information_schema.processlist
|
||||
WHERE Command = 'Query' AND Time > 10
|
||||
ORDER BY Time DESC;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 终止查询
|
||||
|
||||
```sql
|
||||
-- 单个终止
|
||||
KILL <process_id>;
|
||||
|
||||
-- 批量生成终止语句(按来源IP)
|
||||
SELECT CONCAT('KILL ', id, ';')
|
||||
FROM information_schema.processlist
|
||||
WHERE Host LIKE '39.99.243.191%' AND Command = 'Query';
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## performance_schema 分析
|
||||
|
||||
```sql
|
||||
-- 按总耗时排序
|
||||
SELECT
|
||||
SCHEMA_NAME,
|
||||
LEFT(DIGEST_TEXT, 150) as sql_pattern,
|
||||
COUNT_STAR as exec_count,
|
||||
ROUND(SUM_TIMER_WAIT/1000000000000, 2) as total_sec,
|
||||
ROUND(AVG_TIMER_WAIT/1000000000, 2) as avg_ms,
|
||||
ROUND(MAX_TIMER_WAIT/1000000000000, 2) as max_sec
|
||||
FROM performance_schema.events_statements_summary_by_digest
|
||||
WHERE SCHEMA_NAME IS NOT NULL
|
||||
ORDER BY SUM_TIMER_WAIT DESC
|
||||
LIMIT 20;
|
||||
|
||||
-- 按平均耗时排序
|
||||
SELECT
|
||||
SCHEMA_NAME,
|
||||
LEFT(DIGEST_TEXT, 150) as sql_pattern,
|
||||
COUNT_STAR as exec_count,
|
||||
ROUND(AVG_TIMER_WAIT/1000000000, 2) as avg_ms,
|
||||
ROUND(MAX_TIMER_WAIT/1000000000000, 2) as max_sec
|
||||
FROM performance_schema.events_statements_summary_by_digest
|
||||
WHERE SCHEMA_NAME IS NOT NULL
|
||||
ORDER BY AVG_TIMER_WAIT DESC
|
||||
LIMIT 15;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 查看表大小
|
||||
|
||||
```sql
|
||||
SELECT
|
||||
table_schema,
|
||||
table_name,
|
||||
ROUND(data_length/1024/1024, 2) as data_MB,
|
||||
ROUND(index_length/1024/1024, 2) as index_MB,
|
||||
table_rows
|
||||
FROM information_schema.tables
|
||||
WHERE table_schema IN ('db1', 'db2')
|
||||
ORDER BY data_length DESC
|
||||
LIMIT 30;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 常用连接
|
||||
|
||||
| 库 | 连接命令 |
|
||||
|----|----------|
|
||||
| suke 生产库(阿里云RDS) | `mysql -hrm-8vbrd2wil14hclop3vm.mysql.zhangbei.rds.aliyuncs.com -uroot -pWFFGwffg233` |
|
||||
| 二号机 | `mysql -h47.92.117.13 -uroot -px251119!` |
|
||||
|
||||
---
|
||||
|
||||
*创建时间:2026-03-04*
|
||||
@@ -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
|
||||
// 速贷请求Bean(Java驼峰,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` 简化转换
|
||||
Reference in New Issue
Block a user