diff --git a/README.md b/README.md index e69de29..6a4319a 100644 --- a/README.md +++ b/README.md @@ -0,0 +1,73 @@ +# filemgr — 文件/目录管理模块 + +## 模块定位 + +通用文件与目录管理包:提供**目录树 + 文件上传/删除**的基础能力,核心是 `filemgr/filemgr.py` 里的 `FileMgr` 类(配 `FileMgrResult` 结果对象与 `get_file_hash` sha256 工具函数)。物理文件走 ahserver 的 `FileStorage`(上传落在 `conf/config.json` 的 `filesroot`,即 `$[workdir]$/files`),元数据落 MySQL 的 `folder`/`file` 两张表。 + +两种运行形态(仓库中都有实物): + +1. **独立 app 演示**:`app/fileMGR.py`(`load_appbase → load_rbac → load_filemgr → load_bricks`,监听 9080,库 `mediadb`,见 `conf/config.json`); +2. **宿主模块**:`filemgr/init.py` 的 `load_filemgr()` 把 **FileMgr 类本身**注册到 `ServerEnv`(`env.FileMgr = FileMgr`),宿主(sage / pipeline-app)在 dspy 里取用。库名经 `get_module_dbname('filemgr')` 解析,禁写死。 + +在账号/存储计费链路中,filemgr 是**容量事实的来源**:`file.filesize` 汇总即机构占用量,`storage_resource` 的容量采样(`record_storage_sample`)可由扫这些记录得到 `used_gb`。filemgr 自身**不直接调用** product_management / accounting。 + +## 表清单(models/*.json,另有同名 .xlsx 源) + +| 表 | 用途 | 关键字段 | +|---|---|---| +| `folder` | 目录(树) | `id`(pk32), `parentid`(父目录,自关联), `fiid`(目录信息id), `name`(500), `ownerid`(所属机构 → organization.id) | +| `file` | 文件元数据 | `id`(pk32), `folderid`(→folder.id), `fiid`, `name`(200), `webpath`(相对路径400), `realpath`(绝对路径400), `filetype`(扩展名≤10), `filesize`(long,字节), `ownerid`(属主机构), `orgperm`(同事权限 char3), `otherperm`(其他人权限), `hashvalue`(sha256,65) | + +⚠️ models 的 `codes` 段引用了 `folderinfo` 和 `organization` 两张表——**本仓库未定义**,需宿主提供(organization 属 appbase/组织模块)。`folderinfo`(fiid 指向的"目录信息")在仓库内无建表定义,接入新宿主前需确认其来源。 + +## 对外 API / 页面端点(wwwroot/,挂载在 `/filemgr/` 下) + +| 端点 | 类型 | 说明 | +|---|---|---| +| `/filemgr/get_folder_subs.dspy` | dspy | 取某目录的直接子项(子目录+文件),供树控件 `get_data_url` | +| `/filemgr/delete_folder_or_file.dspy` | dspy | 删除入口:目录有子项时返回 `UiComform` 二次确认(跳 conformed_delete),否则直接删 | +| `/filemgr/conformed_delete.dspy` | dspy | 确认后的目录递归删除 | +| `/filemgr/upload_file.ui` | Bricks Form | 上传弹窗(folderid/fiid 隐藏域 + upfile 文件域),submit 绑定 upload_file.dspy | +| `/filemgr/upload_file.dspy` | dspy | 调 `FileMgr.add_file(request, params_kw)` 落库 | +| `/filemgr/getallfiles.dspy` | dspy | 递归列出目录下全部文件(id+realpath) | +| `json/folder.json` | Bricks tree widget | 目录树页面定义(xls2ui 生成,`json/build.sh`: `xls2ui -m ../models -o ../wwwroot filemgr *.json`):文件类型图标映射(pdf/docx/xlsx/epub/csv…)、工具栏"上传文件"弹 `upload_file.ui` | + +`conf/config.json` 另注册了 startswith 路由 `/idfile → registerfunction 'idfile'`(独立演示形态用;该注册函数不在本仓库实现)。 + +## FileMgr 关键方法(实测签名) + +```python +FileMgr(fiid) # 构造需要 fiid(见"已知问题") +await add_file(request, params_kw) # 上传: 登录校验→属主校验→配额校验→sha256去重→落file表 +await del_folder(request, id) / _del_folder(sor, id, ownerid) # 递归删目录(属主校验) +await del_file(request, fid) / _del_file(sor, fid, ownerid) # 删文件(物理文件按引用计数unlink) +await has_sub(sor, folderid) # 目录是否有子项(union子目录+文件) +await get_subs(id) # 直接子项列表(自建连接) +await sor_get_subfolder(sor, fid) / sor_get_subfile(sor, fid) +await get_quota_used(sor, orgid) # sum(file.filesize)/1e6,单位 MB +await get_organization_quota(sor, orgid) # 基类桩: 返回 (0, '9999-12-31'),子类必须覆写 +get_file_hash(filepath, algo='sha256', chunk_size=8192) +``` + +上传语义(add_file):同 `hashvalue`+同属主 → 拒收"file exists";同 `hashvalue` 不同属主 → **复用已有物理文件路径**(物理去重,只加一条 file 记录)。删除时物理文件仅在**无任何记录再引用该 hashvalue** 时才 unlink。配额判断 `quota_used + filesize/1e6 >= quota` 即拒绝并删除已上传的临时文件。 + +## 与产品/计费链路(product_management、accounting)的集成关系 + +- filemgr **不注册** `env.product_interface`,不参与 resolve_ppid/定价映射——它只是资源事实层。 +- 链路位置:`file.filesize`(机构维度汇总,MB)→ `storage_resource.record_storage_sample(...)` 写 `storres_sample.used_gb/quota_gb/over_gb` → `summarize_samples` 按订购周期求均值/峰值 → `storage_charging` 出 `storres_usage`(accounting_status=pending)→ accounting 记账。 +- `get_organization_quota` 是配额挂钩点:接入 storage_resource 产品后,应由子类/宿主把机构已购容量(订购权益 `workspace_gb`/`storage_gb`)返回给 filemgr 做上传前置校验(当前基类返回 0,见下)。 + +## 部署注意事项 + +1. **库名动态解析**:`get_dbname()` 走 `get_serverenv('get_module_dbname')('filemgr')`;独立演示 app 里写死返回 `'sage'`,pipeline-app 宿主返回 `'pipeline'`——复用模块时勿改死。 +2. **建表**:models/file.json、models/folder.json 交宿主 create_tables/xls2ddl 流程;注意 `file` 表的 `codes` 外联 `folderinfo`/`organization` 必须先存在。 +3. **文件存储根**:`conf/config.json` 的 `filesroot=$[workdir]$/files`,上传大小受 `client_max_size: 20000` 限制;生产需确认磁盘与 FileStorage 配置一致。 +4. **i18n**:i18n/{zh,en,jp,ko}/msg.txt 已含表字段词条(commit 1461430)。 +5. **pyproject/setup.cfg**:包名 filemgr 0.0.1,依赖 apppublic/sqlor/ahserver;`packages = find:` 会把 `app/`、`json/` 一并扫描,安装时注意。 + +## 已知问题(实测代码,未修改——修前必读) + +- `FileMgr.__init__(self, fiid)` **要求 fiid**,但 wwwroot 各 dspy 均以 `FileMgr()` 无参构造 → 直接运行会 TypeError(getallfiles.dspy 更是 `filemgr = FileMgr` 拿了类当实例,且调用了不存在的 `get_subfiles`)。 +- `conformed_delete.dspy` 用小写 `Filemgr()` 且 `_del_folder(sor, params_kw.id)` 少传 `ownerid`。 +- `folder_files()` 有笔误 `dbanme = get_dbname()`(下一行用 `dbname` → NameError);`_folder_files` 递归调用漏传 `sor`;`del_folder` 里 `self.file_deleted(...)` 未 await。 +- `get_folder_ownerid`/`file_uploaded`/`file_deleted` 基类均为空实现(pass),`is_folder_ownerid` 因此恒 False → **add_file 属主校验必失败**;基类 `get_organization_quota` 返回 quota=0(commit 5a81ea2 特意 revert 回 0)→ 未覆写的子类**任何上传都触发配额溢出**。本模块设计为被业务子类继承覆写这三个钩子后使用。