为了确保系统满足甲级资质对数据安全性、元数据规范性及操作可追溯性的严苛要求,本指南采用Python FastAPI作为后端框架,Vue 3作为前端框架。FastAPI原生支持异步请求,能高效处理档案文件的IO操作,且自动生成OpenAPI文档,便于后续对接。前端使用Vue 3的Composition API,确保表单交互的严谨性。
请在终端执行以下命令创建项目目录并安装核心依赖。请确保你的环境中已安装Python 3.8及以上版本和Node.js 16及以上版本。
```bash
mkdir archive-system && cd archive-system
mkdir backend && cd backend
pip install fastapi uvicorn python-multipart python-docx pillow pypdf reportlab
```
上述依赖中,python-multipart用于处理文件上传,python-docx和pypdf用于解析文书内容以提取元数据,reportlab用于生成符合资质要求的PDF副本或水印。
```bash
cd ..
npm create vite@latest frontend -- --template vue
cd frontend
npm install axios element-plus
```
前端使用Element Plus组件库,提供符合政府及企业级应用规范的UI组件,特别是上传组件和表单校验功能。
甲级资质核心在于文书档案的元数据必须完整且符合《文书档案元数据规范》。我们使用SQLite作为演示数据库,重点设计archives表。在backend目录下创建database.py文件,直接复制以下代码完成数据库初始化。
```python
import sqlite3
import os
from datetime import datetime
DB_FILE = "archives.db"
def init_db():
conn = sqlite3.connect(DB_FILE)
cursor = conn.cursor()
cursor.execute('''
CREATE TABLE IF NOT EXISTS archives (
id INTEGER PRIMARY KEY AUTOINCREMENT,
doc_code TEXT UNIQUE NOT NULL, -- 档号,必填且唯一
title TEXT NOT NULL, -- 题名
responsibility_date TEXT, -- 责任者(日期)
security_level TEXT, -- 密级:公开/内部/机密
retention_period TEXT, -- 保管期限:永久/长期/短期
file_path TEXT, -- 文件存储路径
operator TEXT, -- 操作员
timestamp TEXT -- 归档时间戳
)
''')
conn.commit()
conn.close()
if __name__ == "__main__":
init_db()
```
执行python database.py生成数据库文件。请注意,doc_code(档号)是甲级资质检查的重中之重,通常格式为“全宗号-目录号-年度-案卷号-件号”。系统将自动生成此编码以防止人工录入错误。
在backend目录下创建main.py。我们需要实现文件上传、自动元数据提取、档号生成以及“三员”管理中的审计日志记录。
```python
from fastapi import FastAPI, UploadFile, File, Form, HTTPException
from fastapi.responses import FileResponse
from fastapi.middleware.cors import CORSMiddleware
import shutil
import os
import uuid
from datetime import datetime
from database import init_db, sqlite3, DB_FILE
app = FastAPI()
允许前端跨域访问
app.add_middleware(
CORSMiddleware,
allow_origins=[""],
allow_credentials=True,
allow_methods=[""],
allow_headers=[""],
)
os.makedirs("uploads", exist_ok=True)
init_db()
def generate_doc_code(year: str, category: str = "001"):
"""生成符合甲级资质的档号:全宗号-目录号-年度-件号"""
conn = sqlite3.connect(DB_FILE)
cursor = conn.cursor()
简单模拟件号自增,实际需按年度和目录分组
cursor.execute("SELECT COUNT() FROM archives WHERE doc_code LIKE ?", (f"%-{year}-%",))
count = cursor.fetchone()[0] + 1
conn.close()
return f"001-{category}-{year}-{str(count).zfill(4)}"
@app.post("/upload")
async def upload_archive(
file: UploadFile = File(...),
title: str = Form(...),
responsibility_date: str = Form(...),
security_level: str = Form(...),
retention_period: str = Form(...),
operator: str = Form("admin")
):
1. 文件重命名存储,防止冲突
file_ext = os.path.splitext(file.filename)[1]
new_filename = f"{uuid.uuid4().hex}{file_ext}"
file_location = f"uploads/{new_filename}"
with open(file_location, "wb") as buffer:
shutil.copyfileobj(file.file, buffer)
2. 生成档号
current_year = datetime.now().strftime("%Y")
doc_code = generate_doc_code(current_year)
3. 入库
try:
conn = sqlite3.connect(DB_FILE)
cursor = conn.cursor()
cursor.execute("""
INSERT INTO archives (doc_code, title, responsibility_date, security_level, retention_period, file_path, operator, timestamp)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
""", (doc_code, title, responsibility_date, security_level, retention_period, file_location, operator, datetime.now().isoformat()))
conn.commit()
conn.close()
except Exception as e:
raise HTTPException(status_code=500, detail=f"数据库写入失败: {str(e)}")
4. 审计日志记录(甲级资质必须项)
log_entry = f"[{datetime.now()}] 操作员:{operator} | 动作:归档 | 档号:{doc_code} | 结果:成功\n"
with open("audit.log", "a", encoding="utf-8") as f:
f.write(log_entry)
return {"message": "归档成功", "doc_code": doc_code, "file_id": new_filename}
@app.get("/list")
async def list_archives():
conn = sqlite3.connect(DB_FILE)
conn.row_factory = sqlite3.Row
cursor = conn.cursor()
cursor.execute("SELECT FROM archives ORDER BY timestamp DESC")
rows = cursor.fetchall()
conn.close()
return [dict(row) for row in rows]
```
这段代码实现了甲级资质系统中最关键的“档号自动生成”逻辑和“审计日志”功能。日志文件audit.log将记录所有归档操作,这是资质评审时的必查项。
修改frontend/src/App.vue,构建一个包含必填项校验的表单。甲级资质要求上传时必须强制录入密级和保管期限,否则禁止入库。
```html

v-model="form.responsibility_date" type="date" placeholder="选择日期" value-format="YYYY-MM-DD" style="width: 100%" /> ref="uploadRef" :auto-upload="false" :on-change="handleChange" :limit="1" > 已归档列表
```
接下来是部分的逻辑,重点在于构建FormData对象,确保文件和元数据一起提交。
```javascript
import { ref, onMounted } from 'vue'
import { ElMessage } from 'element-plus'
import axios from 'axios'
const formRef = ref()
const uploadRef = ref()
const fileList = ref([])
const tableData = ref([])
const form = ref({
title: '',
responsibility_date: '',
security_level: '',
retention_period: ''
})
const rules = {
title: [{ required: true, message: '请输入题名', trigger: 'blur' }],
responsibility_date: [{ required: true, message: '请选择日期', trigger: 'change' }],
security_level: [{ required: true, message: '请选择密级', trigger: 'change' }],
retention_period: [{ required: true, message: '请选择保管期限', trigger: 'change' }]
}
const handleChange = (uploadFile) => {
fileList.value = [uploadFile.raw]
}
const submitForm = async () => {
if (!formRef.value) return
await formRef.value.validate((valid) => {
if (!valid) return false
if (fileList.value.length === 0) {
ElMessage.error('请上传文件')
return
}
const formData = new FormData()
formData.append('file', fileList.value[0])
Object.keys(form.value).forEach(key => {
formData.append(key, form.value[key])
})
formData.append('operator', 'admin') // 模拟当前登录用户
axios.post('http://127.0.0.1:8000/upload', formData, {
headers: { 'Content-Type': 'multipart/form-data' }
}).then(res => {
ElMessage.success('归档成功:' + res.data.doc_code)
fetchList()
formRef.value.resetFields()
uploadRef.value.clearFiles()
fileList.value = []
}).catch(err => {
ElMessage.error('归档失败')
})
})
}
const fetchList = () => {
axios.get('http://127.0.0.1:8000/list').then(res => {
tableData.value = res.data
})
}
onMounted(() => {
fetchList()
})
```
完成代码编写后,按以下步骤启动系统并进行验证。这一步将确认档号生成逻辑是否闭环,以及审计日志是否正常记录。
在backend目录下执行:
```bash
uvicorn main:app --reload
```
看到Uvicorn running on http://127.0.0.1:8000即表示后端就绪。
在frontend目录下执行: