sqlor/aidocs/crud.md
2025-10-05 11:24:24 +08:00

482 lines
12 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# `CRUD` 模块技术文档
> **文件编码UTF-8**
该模块提供了一个基于数据库连接池的通用增删改查CRUD操作类支持异步执行、字段类型转换、主外键处理、数据网格生成等功能。适用于 Web 后端服务中对数据库表进行标准化访问。
---
## 目录
- [1. 依赖说明](#1-依赖说明)
- [2. 全局变量](#2-全局变量)
- [`toStringFuncs`](#tostringfuncs)
- [`fromStringFuncs`](#fromstringfuncs)
- [3. 异常定义](#3-异常定义)
- [`DatabaseNotfound`](#databasenotfound)
- [4. 核心类:`CRUD`](#4-核心类crud)
- [构造函数 `__init__`](#构造函数-__init__)
- [方法列表](#方法列表)
- [`primaryKey()`](#async-primarykey)
- [`forignKeys()`](#async-forignkeys)
- [`I()`](#async-i)
- [`fromStr(data)`](#async-fromstrdata)
- [`toStr(data)`](#async-tostrdata)
- [`datagrid(request, targeti, **kw)`](#async-datagridrequest-targeti--kw)
- [`defaultIOField(f)`](#defaultiofieldf)
- [`C(rec, **kw)`](#async-crec--kw)
- [`R(filters=None, NS={}, **kw)`](#async-rfiltersnone-ns---kw)
- [`U(data, **kw)`](#async-udata--kw)
- [`D(data, **kw)`](#async-ddata--kw)
- [5. 示例用法](#5-示例用法)
- [6. 注意事项与扩展机制](#6-注意事项与扩展机制)
---
## 1. 依赖说明
本模块依赖以下内部和外部组件:
| 模块 | 用途 |
|------|------|
| `.dbpools.DBPools` | 数据库连接池管理器 |
| `.const.ROWS` | 默认每页行数常量 |
| `.filter.DBFilter` | SQL 查询条件过滤器生成工具 |
| `appPublic.objectAction.ObjectAction` | 对象行为钩子系统(用于事件拦截与扩展) |
| `appPublic.dictObject.DictObject` | 字典对象封装(未直接使用,可能为遗留导入) |
| `appPublic.timeUtils.date2str, time2str, str2Date` | 时间格式化/解析函数 |
| `appPublic.uniqueID.getID` | 唯一 ID 生成函数 |
---
## 2. 全局变量
### `toStringFuncs`
将不同字段类型的值转为字符串表示的方法映射。
```python
toStringFuncs = {
'char': None,
'str': None,
'short': str,
'long': str,
'float': str,
'date': date2str,
'time': time2str,
}
```
- 若值为 `None`,表示不需特殊处理。
- 例如:日期类型通过 `date2str` 转换为 `'YYYY-MM-DD'` 格式字符串。
### `fromStringFuncs`
将字符串反向解析为对应类型的数据转换函数。
```python
fromStringFuncs = {
'short': int,
'long': int,
'float': float,
'date': str2Date,
'time': str2Date
}
```
- 空字符串会被转换为 `None`
- 支持自动类型转换,在插入或更新前预处理输入数据。
---
## 3. 异常定义
### `DatabaseNotfound`
当指定的数据库名称在连接池中不存在时抛出此异常。
#### 属性:
- `dbname`: 请求但未找到的数据库名
#### 方法:
- `__str__()`: 返回 `"xxx not found"` 的可读信息。
```python
raise DatabaseNotfound("mydb")
```
---
## 4. 核心类:`CRUD`
```python
class CRUD(object):
def __init__(self, dbname, tablename, rows=ROWS):
...
```
封装了对单个数据库表的标准 CRUD 操作。
### 构造函数 `__init__`
**参数:**
- `dbname` (str): 数据库标识名(必须存在于 DBPools 中)
- `tablename` (str): 表名
- `rows` (int): 分页查询默认条数,默认为 `ROWS` 常量
**逻辑流程:**
1. 初始化连接池实例 `DBPools()`
2. 验证数据库是否存在,若不存在则抛出 `DatabaseNotfound`
3. 缓存表主键信息(延迟加载)
**抛出异常:**
- `DatabaseNotfound`:数据库未注册
---
### 方法列表
#### `async primaryKey(**kw)`
获取当前表的主键字段信息。
- 第一次调用时从数据库元数据缓存主键信息。
- 后续调用返回缓存结果。
**返回:**
- 列表,每个元素是包含字段信息的对象(如 `{field_name: "id", ...}`
> 内部使用 `pool.getTablePrimaryKey(dbname, tablename)` 实现。
---
#### `async forignKeys(**kw)`
获取当前表的所有外键关系。
**返回:**
- 外键描述数据结构(由 `pool.getTableForignKeys()` 提供)
---
#### `async I(**kw)`
获取当前表的字段元信息,并附加主键标记。
##### 功能:
1. 获取所有字段信息(来自 `getTableFields`
2. 将主键字段添加 `"primarykey": True` 标记
3. 执行对象动作钩子:`{dbname}_{tablename}.tableInfo`
##### 返回:
- 字段信息列表,每个字段包含:
- `name`: 字段名
- `title`: 显示标题(若有)
- `type`: 类型(如 str, float, date 等)
- `primarykey`: 是否为主键bool
##### 示例输出片段:
```json
[
{
"name": "id",
"title": "ID",
"type": "short",
"primarykey": true
},
...
]
```
---
#### `async fromStr(data)`
将字符串形式的数据字典转换为强类型数据。
**参数:**
- `data` (dict): 键值对,通常来自 HTTP 请求参数
**处理规则:**
- 空字符串 → `None`
- 根据字段类型应用 `fromStringFuncs` 转换(如 `int`, `float`, `str2Date`
**返回:**
- 新字典,已做类型转换
> 常用于 C/U 接口前的数据预处理。
---
#### `async toStr(data)`
将原始数据转换为适合前端展示的字符串格式。
**参数:**
- `data` (dict): 数据记录
**处理规则:**
- 使用 `toStringFuncs` 将非字符串字段转为字符串(如日期→'2025-04-05'
**返回:**
- 可序列化的字符串化字典
> 常用于 R 接口后数据输出前的格式化。
---
#### `async datagrid(request, targeti, **kw)`
生成一个前端可用的“数据表格”配置对象,兼容 EasyUI 或类似框架。
##### 参数:
- `request`: 当前请求对象(用于生成绝对 URL
- `targeti`: 目标 DOM 容器 ID
- `**kw`: 其他传递参数
##### 返回:
一个嵌套结构,描述 DataGrid 组件的模板配置,包括:
| 属性 | 说明 |
|------|------|
| `tmplname` | 使用的模板名 |
| `data.__ctmpl__` | 组件类型标识 |
| `data.__target__` | 渲染目标 |
| `data.data.url` | 查询接口地址 (`./RP.dspy`) |
| `data.data.deleteUrl` | 删除接口 |
| `data.data.addUrl` | 添加接口 |
| `data.data.updateUrl` | 更新接口 |
| `data.data.idField` | 主键字段名 |
| `data.data.fields` | 字段列定义(通过 `defaultIOField` 生成) |
| `data.data.toolbar` | 工具栏按钮(增删上下移动) |
| `data.data.options.pageSize` | 分页大小 |
> 最终通过 `oa.execute(id, 'datagrid', data)` 允许外部插件定制布局。
---
#### `defaultIOField(f)`
根据字段元信息生成默认的前端显示字段配置。
**参数:**
- `f` (dict): 单个字段元信息
**返回:**
标准字段配置对象,含以下字段:
| 字段 | 说明 |
|------|------|
| `name` | 字段名 |
| `label` | 显示标签(取自 `title` |
| `primarykey` | 是否为主键 |
| `hidden` | 是否隐藏(默认否) |
| `sortable` | 是否可排序 |
| `align` | 文本对齐方式(数值右对齐) |
| `iotype` | 输入类型(目前统一为 `"text"` |
> 不同类型字段有细微差异:
- `str`: 居中对齐
- 数值型(`short`, `long`, `float`: 右对齐
- 其他: 居中对齐
---
#### `async C(rec, **kw)`
创建一条新记录Create
##### 参数:
- `rec` (dict): 待插入数据(字段名不区分大小写)
- `**kw`: 额外参数(传给底层 SQL 执行)
##### 流程:
1. 转换键名为小写
2. 若主键为空,则自动生成唯一 ID`getID()`
3. 触发 `beforeAdd` 钩子
4. 构造并执行插入语句:
```sql
INSERT INTO table (col1, col2) VALUES (${col1}$, ${col2}$)
```
5. 触发 `afterAdd` 钩子
6. 返回主键值 `{pk_field: value}`
##### 返回:
- 成功时返回主键字典;失败抛异常。
---
#### `async R(filters=None, NS={}, **kw)`
检索数据Retrieve支持条件查询和分页。
##### 参数:
- `filters`: 自定义过滤表达式(高级语法,见 `DBFilter`
- `NS`: 查询参数字典(如 `{"name": "Alice"}`
- `**kw`: 其他选项
##### 特殊处理:
- `NS['__id']` → 自动映射到主键字段
- `NS['page']` 存在 → 启用分页模式
- 无排序字段时,默认按主键排序
##### 查询逻辑:
- 若无 `filters`,则自动生成等值匹配条件(`field = $field$`
- 使用 `DBFilter` 解析复杂条件(如范围、模糊匹配)
##### 执行分支:
- 分页请求 → 调用 `runSQLPaging`
- 普通查询 → 调用 `runSQL`
##### 生命周期钩子:
- `beforeRetrieve`
- `afterRetrieve`
##### 返回:
- 结果集(可能是分页对象,含 `total`, `rows`
---
#### `async U(data, **kw)`
更新记录Update
##### 参数:
- `data` (dict): 包含主键和其他待更新字段
##### 流程:
1. 分离主键字段和更新字段
2. 构造 WHERE 条件(主键相等)
3. 构造 SET 子句(仅非主键字段)
4. 执行更新语句:
```sql
UPDATE table SET col1=${col1}$ WHERE id=${id}$
```
5. 触发 `beforeUpdate` / `afterUpdate` 钩子
##### 返回:
- 更新后的完整数据对象
---
#### `async D(data, **kw)`
删除记录Delete
##### 参数:
- `data` (dict): 包含主键字段的字典
##### 流程:
1. 构造 WHERE 条件(所有主键字段 AND 连接)
2. 执行删除语句:
```sql
DELETE FROM table WHERE id=${id}$
```
3. 触发 `beforeDelete` / `afterDelete` 钩子
##### 返回:
- 被删除的数据副本(可用于日志或通知)
> ⚠️ 注意:未实现软删除,为物理删除。
---
## 5. 示例用法
```python
# 初始化数据库连接池(仅一次)
DBPools({
"ambi": {
"driver": "pymssql",
"coding": "utf-8",
"dbname": "ambi",
"kwargs": {
"user": "ymq",
"password": "ymq123",
"host": "localhost",
"database": "ambi"
}
}
})
# 创建 CRUD 实例
crud = CRUD('ambi', 'cashflow')
# 查询全部数据(分页)
data = await crud.R(NS={'page': 1})
print(f"总数: {data.total}")
for row in data.rows:
print(row.balance, row.asid)
# 插入一条记录
new_id = await crud.C({
"balance": 1000.5,
"asid": "A001"
})
print("新建ID:", new_id)
# 更新
await crud.U({
"id": 123,
"balance": 1500.0
})
# 删除
await crud.D({"id": 123})
```
---
## 6. 注意事项与扩展机制
### ✅ 已支持特性:
- ✅ 异步非阻塞 I/O
- ✅ 多数据库连接池管理
- ✅ 自动主键识别与处理
- ✅ 输入/输出类型自动转换
- ✅ 支持分页、排序、条件查询
- ✅ 前端 DataGrid 快速生成
- ✅ 通过 `ObjectAction` 实现事件钩子扩展:
- `beforeAdd`, `afterAdd`
- `beforeRetrieve`, `afterRetrieve`
- `beforeUpdate`, `afterUpdate`
- `beforeDelete`, `afterDelete`
- `tableInfo`, `datagrid`
### ⚠️ 注意事项:
1. **主键要求**:所有表应明确定义主键,否则部分功能异常。
2. **字段大小写敏感性**:输入建议统一小写,内部会做 `.lower()` 处理。
3. **安全机制**:使用 `${field}$` 占位符 + 参数绑定防止 SQL 注入(依赖底层 `inSqlor` 实现)。
4. **时间格式**`date` 和 `time` 类型需确保 `str2Date` 和 `date2str` 支持正确格式。
5. **外键未用于级联操作**:当前仅提供信息获取,不自动处理关联数据。
### 🔧 扩展建议:
- 在 `ObjectAction` 中注册钩子函数以增强业务逻辑:
```python
oa.bind("ambi_cashflow.beforeAdd", my_audit_log)
```
- 自定义 `defaultIOField` 输出更多控件类型(如 dropdown, datebox
- 扩展 `toStringFuncs` / `fromStringFuncs` 支持更多类型(如 bool, json
---
## 附录:钩子事件命名规范
| 事件点 | 触发时机 | 推荐用途 |
|-------|---------|--------|
| `{db}_{table}.tableInfo` | 获取字段信息后 | 修改字段显示属性 |
| `{db}_{table}.datagrid` | 生成 datagrid 配置后 | 自定义 UI 布局 |
| `{db}_{table}.beforeAdd` | 插入前 | 数据校验、权限检查、默认值填充 |
| `{db}_{table}.afterAdd` | 插入成功后 | 日志记录、消息通知 |
| `{db}_{table}.beforeRetrieve` | 查询前 | 动态过滤(如租户隔离) |
| `{db}_{table}.afterRetrieve` | 查询返回前 | 敏感字段脱敏 |
| `{db}_{table}.beforeUpdate` | 更新前 | 审计、状态流转控制 |
| `{db}_{table}.afterUpdate` | 更新完成后 | 发布事件 |
| `{db}_{table}.beforeDelete` | 删除前 | 软删除替换、引用检测 |
| `{db}_{table}.afterDelete` | 删除后 | 清理关联资源 |
---
> 📝 文档版本v1.0
> © 2025 应用公共平台开发组