482 lines
12 KiB
Markdown
482 lines
12 KiB
Markdown
# `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 应用公共平台开发组 |