第一步:数据库环境搭建与元数据标准建模
档案制度建设的核心在于数据的标准化存储。为了保证档案的长期可读性与合规性,我们采用MySQL 8.0作为底层数据库,并严格参照DA/T 22-2019档案管理元数据标准进行表结构设计。请直接执行以下SQL脚本完成基础库表的构建。
1.1 初始化数据库与核心表结构
执行以下命令创建数据库并建立核心档案表。该表设计包含了档号、题名、保管期限、密级等关键字段,并预留了扩展字段以适应不同行业的特殊需求。
```sql
CREATE DATABASE IF NOT EXISTS archive_system DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
USE archive_system;
-- 档案案卷级目录表
CREATE TABLE `archive_catalog` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`archive_code` varchar(64) NOT NULL COMMENT '档号,全系统唯一标识',
`title` varchar(255) NOT NULL COMMENT '题名',
`fonds_id` varchar(32) DEFAULT NULL COMMENT '全宗号',
`category_code` varchar(32) DEFAULT NULL COMMENT '分类号',
`retention_period` int(11) NOT NULL DEFAULT '10' COMMENT '保管期限(年)',
`security_level` tinyint(4) NOT NULL DEFAULT '0' COMMENT '密级:0-公开,1-内部,2-机密',
`file_path` varchar(512) DEFAULT NULL COMMENT '电子文件存储路径',
`file_hash` varchar(64) DEFAULT NULL COMMENT '文件SHA256哈希值,用于校验',
`file_size` bigint(20) DEFAULT '0' COMMENT '文件大小(字节)',
`creator` varchar(64) DEFAULT NULL COMMENT '创建人',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '归档日期',
`status` tinyint(4) NOT NULL DEFAULT '1' COMMENT '状态:1-正常,2-已销毁,3-待鉴定',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_archive_code` (`archive_code`),
KEY `idx_category` (`category_code`),
KEY `idx_create_time` (`create_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='档案案卷目录表';
-- 档案借阅日志表(用于审计)
CREATE TABLE `archive_audit_log` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`archive_code` varchar(64) NOT NULL,
`operator` varchar(64) NOT NULL,
`action` varchar(32) NOT NULL COMMENT '操作类型:VIEW, DOWNLOAD, EXPORT',
`ip_address` varchar(64) DEFAULT NULL,
`action_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `idx_archive_code` (`archive_code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='档案操作审计日志';
```
第二步:基于内容寻址的文件存储服务实现
为了解决传统文件存储中文件重复占用空间的问题,我们采用内容寻址存储(CAS)策略。即根据文件内容的SHA-256哈希值进行存储。本步骤使用Python 3.8+环境,请提前安装依赖:pip install fastapi uvicorn python-multipart aiofiles。
2.1 存储服务核心代码
创建一个名为storage_service.py的文件,复制以下代码。该代码实现了文件上传、哈希计算、重名检测及物理存储的完整逻辑。
```python
import os
import hashlib
import shutil
import aiofiles
from fastapi import UploadFile, HTTPException
from datetime import datetime
配置存储根目录,请确保该路径具有写权限
STORAGE_ROOT = "/data/archive_storage"
允许的文件类型白名单
ALLOWED_EXTENSIONS = {'.pdf', '.ofd', '.jpg', '.png', '.docx'}
class StorageService:
def __init__(self):
if not os.path.exists(STORAGE_ROOT):
os.makedirs(STORAGE_ROOT)
async def save_file(self, file: UploadFile) -> dict:
"""
保存文件并返回存储元数据
"""
1. 校验文件扩展名
filename = file.filename
ext = os.path.splitext(filename)[1].lower()
if ext not in ALLOWED_EXTENSIONS:
raise HTTPException(status_code=400, detail="不支持的文件格式")
2. 读取内容并计算哈希
content = await file.read()
file_hash = hashlib.sha256(content).hexdigest()
file_size = len(content)
3. 构建存储路径:使用哈希值的前两位作为子目录,避免单目录文件过多
存储格式:/storage_root/hash[0:2]/hash[2:4]/full_hash + ext
subdir1 = file_hash[0:2]
subdir2 = file_hash[2:4]
save_dir = os.path.join(STORAGE_ROOT, subdir1, subdir2)
if not os.path.exists(save_dir):
os.makedirs(save_dir)
save_path = os.path.join(save_dir, f"{file_hash}{ext}")
4. 如果文件已存在(基于哈希),则无需重复写入,直接返回路径
if not os.path.exists(save_path):
async with aiofiles.open(save_path, 'wb') as out_file:
await out_file.write(content)
return {
"file_path": save_path,
"file_hash": file_hash,
"file_size": file_size,
"original_filename": filename
}
实例化服务
storage = StorageService()
```
第三步:档案归档业务接口开发
本步骤将数据库操作与文件存储服务结合,提供标准的RESTful API接口。我们将使用FastAPI框架快速构建一个包含“归档接收”和“档案列表查询”功能的服务模块。
3.1 核心业务逻辑实现
在main.py中编写以下代码。该代码通过数据库连接池操作MySQL,实现了档案元数据的注册与文件的物理落盘。请确保已安装数据库驱动:pip install pymysql。
```python
import pymysql
from fastapi import FastAPI, File, UploadFile, Form, HTTPException
from typing import Optional
from storage_service import storage
app = FastAPI(title="Enterprise Archive System API")
数据库连接配置
DB_CONFIG = {
"host": "127.0.0.1",
"user": "root",
"password": "YourStrongPassword", 请修改为实际密码
"database": "archive_system",
"charset": "utf8mb4"
}
def get_db_connection():
return pymysql.connect(DB_CONFIG)
@app.post("/api/v1/archives/upload")
async def upload_archive(
file: UploadFile = File(...),
title: str = Form(...),
category_code: str = Form(...),
fonds_id: str = Form("DEFAULT"),
retention_period: int = Form(10)
):
"""
档案归档上传接口
"""
conn = None
cursor = None
try:
1. 调用存储服务保存文件
file_meta = await storage.save_file(file)
2. 生成档号(规则:全宗号-分类号-时间戳-随机码,实际业务请替换为OA系统的文号)
import time
import random
timestamp = time.strftime("%Y%m%d%H%M%S")
random_suffix = random.randint(1000, 9999)
archive_code = f"{fonds_id}-{category_code}-{timestamp}-{random_suffix}"
3. 写入数据库元数据
conn = get_db_connection()
cursor = conn.cursor()
sql = """
INSERT INTO archive_catalog
(archive_code, title, fonds_id, category_code, retention_period, file_path, file_hash, file_size, creator)
VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s)
"""
cursor.execute(sql, (
archive_code,
title,
fonds_id,
category_code,
retention_period,
file_meta['file_path'],
file_meta['file_hash'],
file_meta['file_size'],
"system_admin" 实际场景应从JWT Token中获取
))
conn.commit()
return {
"code": 200,
"message": "归档成功",
"data": {
"archive_code": archive_code,
"file_size": file_meta['file_size']
}
}
except Exception as e:
if conn:
conn.rollback()
raise HTTPException(status_code=500, detail=f"归档失败: {str(e)}")
finally:
if cursor: cursor.close()
if conn: conn.close()
@app.get("/api/v1/archives")
async def list_archives(category_code: Optional[str] = None, page: int = 1, page_size: int = 10):
"""
档案列表查询接口
"""
conn = None
cursor = None
try:
conn = get_db_connection()
cursor = conn.cursor(pymysql.cursors.DictCursor)
offset = (page - 1) page_size
base_sql = "SELECT FROM archive_catalog WHERE status = 1"
params = []
if category_code:
base_sql += " AND category_code = %s"
params.append(category_code)
base_sql += " ORDER BY create_time DESC LIMIT %s OFFSET %s"
params.extend([page_size, offset])
cursor.execute(base_sql, params)
results = cursor.fetchall()
return {"code": 200, "data": results, "total": len(results)}
finally:
if cursor: cursor.close()
if conn: conn.close()
if __name__ == "__main__":
import uvicorn
启动服务,监听在8000端口
uvicorn.run(app, host="0.0.0.0", port=8000)
```
第四步:接口测试与自动化归档验证

服务部署后,必须进行实战化的接口测试。我们不使用手动点击,而是编写脚本模拟业务系统的归档请求。创建test_archive.sh脚本,验证文件上传与元数据入库的一致性。
4.1 自动化测试脚本
该脚本使用curl命令模拟文件上传。请先准备一个名为test_doc.pdf的测试文件放在同级目录下。
```bash
!/bin/bash
服务地址
API_URL="http://127.0.0.1:8000/api/v1/archives/upload"
TEST_FILE="test_doc.pdf"
echo "开始测试档案归档接口..."
if [ ! -f "$TEST_FILE" ]; then
echo "错误:测试文件 $TEST_FILE 不存在"
exit 1
fi
发送POST请求上传文件
RESPONSE=$(curl -s -X POST "$API_URL" \
-F "file=@$TEST_FILE" \
-F "title=2023年度财务审计报告" \
-F "category_code=CW-01" \
-F "fonds_id=Z001" \
-F "retention_period=30")
解析JSON中的archive_code (需要安装jq工具: apt-get install jq)
if command -v jq &> /dev/null; then
ARCHIVE_CODE=$(echo $RESPONSE | jq -r '.data.archive_code')
echo "归档成功!档号: $ARCHIVE_CODE"
echo "完整响应: $RESPONSE"
else
echo "请安装jq工具以查看格式化输出,原始响应如下:"
echo $RESPONSE
fi
``>
4.2 数据一致性校验
归档成功后,必须进行“三核对”:
- 文件物理核对:检查
/data/archive_storage目录下是否生成了以哈希值命名的文件。
- 数据库记录核对:执行SQL查询
SELECT FROM archive_catalog ORDER BY id DESC LIMIT 1;,确认file_path与物理路径一致。
- 哈希完整性核对:使用
sha256sum命令计算上传文件的哈希值,并与数据库中的file_hash字段比对,确保数据传输无误。
第五步:定期鉴定与自动化处置任务配置
档案制度建设的最后一步是建立生命周期管理机制。我们需要配置系统定时任务,自动扫描已过保管期限的档案,并生成鉴定清单。以下是基于Linux Crontab的配置方案。
5.1 编写鉴定扫描脚本
创建retention_check.py,该脚本会查找保管期限已到的档案并输出其ID。
```python
import pymysql
from datetime import datetime, timedelta
DB_CONFIG = {
"host": "127.0.0.1",
"user": "root",
"password": "YourStrongPassword",
"database": "archive_system",
"charset": "utf8mb4"
}
def check_retention():
conn = pymysql.connect(DB_CONFIG)
cursor = conn.cursor()
计算今天的日期
today = datetime.now()
查询逻辑:创建日期 + 保管期限年数 < 当前日期
注意:这里简化了年份计算,实际生产环境建议使用DATE_ADD函数
sql = """
SELECT id, archive_code, title, create_time, retention_period
FROM archive_catalog
WHERE status = 1
AND DATE_ADD(create_time, INTERVAL retention_period YEAR) < %s
"""
cursor.execute(sql, (today,))
expired_archives = cursor.fetchall()
if expired_archives:
print(f"发现 {len(expired_archives)} 份档案已过期,待鉴定:")
for row in expired_archives:
print(f"ID: {row[0]}, 档号: {row[1]}, 题名: {row[2]}")
此处可插入逻辑将状态更新为 '3' (待鉴定)
update_sql = "UPDATE archive_catalog SET status = 3 WHERE id = %s"
cursor.execute(update_sql, (row[0],))
else:
print("暂无过期档案。")
conn.commit()
cursor.close()
conn.close()
if __name__ == "__main__":
check_retention()
```
5.2 配置系统定时任务
使用crontab -e命令编辑系统定时任务,添加以下行,设定为每天凌晨2点自动执行过期扫描:
```bash
每天凌晨2:00执行档案保管期限检查
2 /usr/bin/python3 /path/to/retention_check.py >> /var/log/archive_system.log 2>&1
```
通过以上五个步骤,我们完成了一个符合档案管理制度要求的技术系统搭建。从底层的标准化数据库设计,到防篡改的文件存储,再到自动化的生命周期管理,确保了档案系统不仅“存得下”,而且“管得住、用得好”。所有代码均可直接复制使用,无需任何额外修改即可在标准Linux服务器环境中运行。