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

12 KiB
Raw Permalink Blame History

CRUD 模块技术文档

文件编码UTF-8

该模块提供了一个基于数据库连接池的通用增删改查CRUD操作类支持异步执行、字段类型转换、主外键处理、数据网格生成等功能。适用于 Web 后端服务中对数据库表进行标准化访问。


目录


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

将不同字段类型的值转为字符串表示的方法映射。

toStringFuncs = {
    'char': None,
    'str': None,
    'short': str,
    'long': str,
    'float': str,
    'date': date2str,
    'time': time2str,
}
  • 若值为 None,表示不需特殊处理。
  • 例如:日期类型通过 date2str 转换为 'YYYY-MM-DD' 格式字符串。

fromStringFuncs

将字符串反向解析为对应类型的数据转换函数。

fromStringFuncs = {
    'short': int,
    'long': int,
    'float': float,
    'date': str2Date,
    'time': str2Date
}
  • 空字符串会被转换为 None
  • 支持自动类型转换,在插入或更新前预处理输入数据。

3. 异常定义

DatabaseNotfound

当指定的数据库名称在连接池中不存在时抛出此异常。

属性:

  • dbname: 请求但未找到的数据库名

方法:

  • __str__(): 返回 "xxx not found" 的可读信息。
raise DatabaseNotfound("mydb")

4. 核心类:CRUD

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
示例输出片段:
[
  {
    "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. 若主键为空,则自动生成唯一 IDgetID()
  3. 触发 beforeAdd 钩子
  4. 构造并执行插入语句:
    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. 执行更新语句:
    UPDATE table SET col1=${col1}$ WHERE id=${id}$
    
  5. 触发 beforeUpdate / afterUpdate 钩子
返回:
  • 更新后的完整数据对象

async D(data, **kw)

删除记录Delete

参数:
  • data (dict): 包含主键字段的字典
流程:
  1. 构造 WHERE 条件(所有主键字段 AND 连接)
  2. 执行删除语句:
    DELETE FROM table WHERE id=${id}$
    
  3. 触发 beforeDelete / afterDelete 钩子
返回:
  • 被删除的数据副本(可用于日志或通知)

⚠️ 注意:未实现软删除,为物理删除。


5. 示例用法

# 初始化数据库连接池(仅一次)
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. 时间格式datetime 类型需确保 str2Datedate2str 支持正确格式。
  5. 外键未用于级联操作:当前仅提供信息获取,不自动处理关联数据。

🔧 扩展建议:

  • ObjectAction 中注册钩子函数以增强业务逻辑:
    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 应用公共平台开发组