1. 这古建筑档案平台,到底要管什么?
先聊一个很实际的问题:古建筑档案管理这东西,听起来偏文博行业,但真正上手做系统的时候会发现,它本质上就是一个“业务规则很特殊的 CMS + 资产管理平台”。做过的人都知道,古建筑档案有几个非常折磨人的特点:档案类型杂、属性字段高度不统一、关联资料(图、文、测绘数据、视频、修缮记录)数量多,而且同一个建筑在不同时期的档案状态可能完全不一样。
关中地区的老建筑——比如各类传统民居、庙宇、戏楼——它们的时间跨度大,有些建筑从明清一直用到今天,中间经历过多次修缮,每一次修缮都会产生一堆新资料。如果按照传统信息系统的做法,把“一栋建筑”当作一条记录,然后把所有附件丢进一个附件表,系统顶多是个电子台账,谈不上“档案管理”。
所以我在设计的时候,第一件事不是写代码,而是把“档案”这个词拆开看。对古建筑来说,一份“档案”不仅仅是基本信息,它应该包括至少五层内容:基础信息档案(位置、年代、结构类型、产权归属)、测绘档案(图纸、点云、尺寸数据)、影像档案(照片、视频、全景资源)、修缮档案(历次修缮记录、审批文件、施工方案)、状态档案(安全隐患记录、日常巡查数据)。
这样一来,系统的核心就不是“CRUD一栋建筑”,而是围绕建筑 ID 建立一套可扩展的档案目录树。这套思路贯穿了整个前后端设计:前端用树形组件展示目录层级,后端用“档案主表 + 多张扩展表 + 统一附件表”来支撑。我把这套逻辑想清楚之后,Spring Boot 和 Vue 这些技术才真正有了用武之地。
这个平台适合谁来参考?两类人:一类是正在做文博、历史建筑、不可移动文物信息化的小伙伴,可以参考它的档案模型设计;另一类是准备用 Spring Boot + Vue 做管理类系统但不想做成普通增删改查的人,这套目录驱动的设计思路同样适用,可以套用到设备档案、工程档案甚至合同档案管理上。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 技术选型的“为什么”,比“是什么”更重要
2.1 怎么把 Spring Boot、Vue 拼到一起
这几年只要搜管理类系统的关键词,基本绕不开 springboot 和 vue。但很多人只把这俩当“后端框架 + 前端框架”,不考虑它们到底怎么分工才算合理。
我最终采用的是标准的前后端分离结构:Spring Boot 负责提供 REST API、鉴权、文件处理和业务逻辑,Vue 负责页面交互和展示。后端只通过 JSON 和前端通信,不关心页面长什么样;前端只管把数据渲染成树、表格、表单,不直接碰数据库。
这种分离最大的好处不是“高大上”,而是它让档案管理这种“界面调整频率远高于流程调整频率”的系统变得好维护。档案目录要加一层?前端加个节点就行。某个字段要从文本改成下拉选择?只需要后端改字段约束,前端跟着调整表单配置。只要接口约定保持稳定,前面怎么折腾都不会牵连数据库结构。
前后端联调时,我最常用的是 Vue 开发服务器的代理转发功能。比如前端跑在 http://localhost:8080,后端跑在 http://localhost:8081,前端配置一个 /api 开头的代理转发,开发环境下就不需要处理跨域。核心代码就一段:
javascript复制// vue.config.js(Vue 3 + vue-cli 写法)
module.exports = {
devServer: {
proxy: {
'/api': {
target: 'http://localhost:8081',
changeOrigin: true
}
}
}
}
生产环境部署相对简单一点。前端 npm run build 之后生成 dist 目录,Nginx 直接指向 dist,并把 /api 反向代理到 Spring Boot 服务。这样浏览器访问的是同一个域名下的静态资源,就不会出现跨域问题。开发环境和生产环境用两套方案,是我实际项目中一直沿用的稳定搭配。
2.2 后端技术栈,怎么选版本才不被坑
Spring Boot 的版本选择,是很多初学者最先踩的坑。对老手来说,这甚至不该成为问题,但对新手来说,不同版本之间的差异足以让人崩溃:Spring Boot 2.x 默认用 javax.servlet,Spring Boot 3.x 切换到了 jakarta.servlet,诸多第三方依赖也跟着变动。如果你在本地装的是高版本,参考的教程却是旧写法,很容易出现“导入一大堆依赖,启动直接报 NoClassDefFoundError”的问题。
我的建议是:如果你只是做管理类系统,图稳定,选 Spring Boot 2.7.x(JDK 1.8 即可)是最稳妥的。不是最新版本不好,而是很多老牌工具(比如一些权限组件、Excel 处理库)对 3.x 的适配进度不一样,没必要为了追新给自己增加排查成本。等项目完全跑通,想升级再统一升级,这是更理性的路线。
配套组件尽量精简。我最后用的核心依赖是:Spring Web、Spring Data JPA(或 MyBatis-Plus,看团队习惯)、Spring Security + JWT 做登录鉴权、MySQL 存业务数据、MinIO 或者本地磁盘存附件。搜索功能我并没有像很多人一样一上来就引入 Elasticsearch,初期只用了 MySQL 的 LIKE 查询,后面数据量大了再考虑检索服务。这个思路对于几千栋建筑的规模完全够用。
用 JPA 时有个额外的好处,实体关系映射能提升效率。一个建筑对应多份档案,一份档案对应多个附件,这种一对多关系用注解就能维护。但注意,JPA 的懒加载很容易在 JSON 序列化时报错,我的规避办法是专门写 DTO 对象返回前端,而不是直接返回实体类。最开始图省事直接返回实体,结果遇到了循环引用导致栈溢出,后来改成 DTO 方案再没有折腾过。
2.3 前端用 Vue 2 还是 Vue 3?这是个务实问题
关于 Vue 版本,我的判断是:如果是全新项目,直接 Vue 3。原因不复杂,Vue 3 的 Composition API 在处理复杂表单、动态组件这些场景时确实比 Options API 好用,而且生态已经起来了。Element Plus(Vue 3 版组件库)对表单组件的封装非常成熟,做管理后台的效率很高。
但如果你对 Composition API 不熟,也别硬上,Vue 2 + Element UI 仍然可以做管理后台,只是后续维护会越来越被动。我更推荐的做法是:项目开始前先花半天时间跑一遍 Vue 3 的基础,特别是 setup 语法糖、ref 和 reactive 的区别、watch 和 computed 的适用场景。把这几个核心概念搞明白,写起来不会比 Vue 2 慢多少。
我实际开发时使用的核心页面组件包括:左侧/顶部的目录树(el-tree)、右侧的档案详情主表(el-descriptions 展示 + el-table 展示附件列表)、档案编辑的大表单(el-form 动态渲染)。这些组件没有太复杂的东西,但把它们组织成“目录树驱动的主从界面”,交互起来非常顺手——选中一棵树节点,右边显示该节点的档案信息和附件列表,这是古建筑档案管理的标准操作模式。
3. 数据库设计,敢不敢把“档案号”作为主心骨
3.1 一份古建筑档案,应该拆成几张表
数据库设计是整个系统的地基,这一层出了问题,后面写多少代码都不稳。我强烈不推荐把一栋建筑的所有信息塞进一张大表,字段超过二十个之后,加字段、改类型、做统计都会变成灾难。
我采用的拆分方案是:建筑基础信息表(building)存的是所有建筑“共性”的信息,比如名称、地址、建造年代、结构类型、保护级别、地理坐标;真正的档案数据单独拆出去,用“档案主表(archive)”存目录树关系,用“档案明细扩展表(archive_detail)”存自定义的字段键值对,用“附件表(attachment)”统一管理所有文件。
使用这套方案的核心逻辑是:不同的建筑,甚至同一建筑不同的档案类别,属性字段完全不一样。比如测绘图档案需要“比例尺”“测绘单位”,而修缮记录需要“施工单位”“验收结论”。如果非要把这些做成固定字段,表结构至少要预留几十个冗余列。而用“主表 + 键值对扩展表”的方式,具体字段全部由前端动态渲染,后端只需要做好类型校验和归档,就非常灵活。
核心的表结构设计如下(简化后的 MySQL 建表语句):
sql复制-- 建筑基础信息表
CREATE TABLE building (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
building_code VARCHAR(64) NOT NULL UNIQUE COMMENT '建筑编号',
name VARCHAR(128) NOT NULL COMMENT '建筑名称',
location VARCHAR(255) COMMENT '详细位置',
era VARCHAR(32) COMMENT '建造年代',
structure_type VARCHAR(32) COMMENT '结构类型',
protection_level VARCHAR(32) COMMENT '保护级别',
status TINYINT DEFAULT 1 COMMENT '1-现存 0-已消失',
description TEXT COMMENT '简介',
create_time DATETIME,
update_time DATETIME
);
-- 档案主表(目录树节点)
CREATE TABLE archive (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
building_id BIGINT NOT NULL,
parent_id BIGINT DEFAULT 0 COMMENT '父级档案节点',
archive_code VARCHAR(64) NOT NULL COMMENT '档案号',
title VARCHAR(255) NOT NULL COMMENT '档案标题',
category VARCHAR(32) COMMENT '档案分类',
sort_order INT DEFAULT 0,
create_time DATETIME
);
-- 附件表
CREATE TABLE attachment (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
archive_id BIGINT NOT NULL,
file_name VARCHAR(255) NOT NULL,
file_path VARCHAR(512) NOT NULL,
file_size BIGINT,
file_type VARCHAR(16),
uploader VARCHAR(64),
create_time DATETIME
);
这套结构的好处在于:查询某建筑下所有档案,只要 WHERE building_id = ?;查询档案的完整资料,只要左连接附件表;扩展新档案类别,不需要变更表结构,前端配置好类别表单即可。
注意,档案号是一个容易被忽略但非常关键的字段。档案管理系统如果没有一个唯一编号体系,后续做借阅登记、状态流转、统计核对会非常痛苦。我给每个建筑生成的档案号规则是“区域代码 + 建筑序号 + 档案类别代码 + 时间戳后四位”,比如 GZ-0001-XF-0823(关中共有字头可自行定义)。这样拿到一串编号就能看懂它属于哪栋建筑哪一类档案,这也是从传统纸质档案管理里继承过来的好习惯。
3.2 状态和权限:让档案不光能存,还能走流程
古建筑档案有不少场景涉及“待审核”—“已归档”—“已借阅”这样的状态变更。虽然这类系统的流程没有OA那么重,但状态字段的预留还是很有必要。
我单独建了一张 archive_status_log 表记录状态流转历史。这算是一个小设计,但价值很高:平时看一个档案的当前状态很容易,但有的时候需要回答“这份档案什么时候从草稿变成正式归档的,谁操作过”。留了日志表之后,这类审计需求随时能查,不用看代码猜。
权限控制方面没有做太复杂。系统分为管理员、录入员、访客三类角色。录入员可以新增和修改档案,但不能删除;管理员可以执行全部操作包括用户管理;访客只能查看已归档的档案而不能看到草稿。用 Spring Security + JWT 实现无状态登录,后端在每个接口上标注 @PreAuthorize 校验角色即可。
提示:接口权限不只是前端隐藏按钮就完事了。后端每个写操作都要做鉴权,否则别人拼一个 POST 请求就能绕过界面执行操作。
3.3 影像档案的特殊处理:一个建筑放几十张照片怎么办
古建筑档案里最容易“爆表”的是影像档案。一次实地调研光照片就是上百张,更不用说全景漫游和视频。当初设计时我就在想,如果这些文件直接传数据库或者单机磁盘,后面容量和迁移都是麻烦事。
权衡之下,我用的是“后端规定存储目录 + 附件表记录元信息 + 前端按需加载缩略图”三件套。大文件统一丢到服务器磁盘,规范目录结构是 /data/archive/{archiveId}/{timestamp}_{文件名};数据库只保存相对路径而不是二进制文件。前端列表展示时用 nginx 配置好的静态地址拼出可访问 URL,而不是把文件流全部加载到页面。
这里有个容易踩的坑:Spring Boot 默认的静态资源目录映射的是 classpath:/static/,如果你把文件存在项目目录外面(比如 D:/archive_files 或者 Linux 的 /data/archive),是不能直接用浏览器访问的,必须配置资源映射。
java复制@Configuration
public class WebResourceConfig implements WebMvcConfigurer {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
// 将 URL 中以 /files/ 开头的请求映射到本地磁盘目录
registry.addResourceHandler("/files/**")
.addResourceLocations("file:" + System.getProperty("user.dir") + "/upload/");
}
}
这种“磁盘路径 + URL 映射”的方式,是我测试后最稳的方案。数据库只存相对路径 upload/2024/05/xxx.jpg,实现业务数据与文件数据解耦,备份时也可以分开操作。如果你在 Windows 本地开发,在 Linux 服务器部署,一定要注意路径分隔符的问题,代码里不能写死 \ 或 /,用常量拼接或者 Paths.get() 处理更稳妥。
3.4 历史资料搜索功能,从一开始就要做对
搜索模块做得好不好,直接影响文博单位用户的实际体验。很多管理后台的搜索功能都相当“鸡肋”,因为默认的模糊查询只能查一两个字段,而且查出来一堆不相关的内容。
我给系统设计了分面搜索:默认搜索框,输入关键词后,按照“建筑名称、建筑简介、档案标题、档案内容”四个维度同时匹配,并且结果列表附带分类筛选标签(例如“全部 / 基础信息 / 修缮档案 / 影像资料”)。这个功能在真实使用中的点击率远高于普通表格自带的过滤,算是整个系统性价比很高的功能。
实现上没有用特别高深的技术。MySQL 侧就是一条 UNION 或者多条 OR 条件的 SQL,配合 LIKE CONCAT('%', #{keyword}, '%'),数据量在几万条以内时性能完全没压力。语义层面如果想做得更智能,可以考虑引入 HanLP 或 jieba 分词,把分词结果存一个索引表;但我最终还是没做这一步,因为初始量级下收益不大,反而徒增维护成本。
如果你把系统做大了,再考虑引入更专业的检索方案。更换搜索组件时,能复用现在查出来的结果模型的话,会更平滑地升级,万不得已不要替自己提前堆复杂度。
4. 前端实操环节:Vue 开发中的关键页面实现
4.1 环境准备:先保证本地能跑起来
Vue 环境配置是许多新手卡的第一个点。我建议把 Node.js 和 npm 的安装提前搞定,不要边写代码边装。国内网络环境下,npm 安装依赖容易超时。这里分享一个我常用的做法:给 npm 配置淘宝镜像源,能省掉非常多安装依赖的等待时间。
bash复制npm config set registry https://registry.npmmirror.com
npm install -g @vue/cli
npm install -g yarn # 可选,用 yarn 装依赖在某些场景更快
创建项目时,我用 vue-cli 生成 Vue 3 项目:
bash复制vue create ancient-architecture-frontend
按需选择 Router、Vuex/Pinia、ESLint。项目创建完,先装 UI 组件库和必要工具。
bash复制npm install element-plus axios pinia
然后按 Element Plus 官方推荐方式完整引入(初期为了省时间,没有做按需引入,项目体量不大,这点体积可以忽略),在 main.js 里注册:
javascript复制import { createApp } from 'vue'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import App from './App.vue'
import router from './router'
const app = createApp(App)
app.use(router)
app.use(ElementPlus)
app.mount('#app')
经验之谈:这些环境操作千万不要照着一篇文章抄,先确认自己的 Node 版本。Vue 3 对 Node 有基本要求,如果 Node 版本太低,装依赖的时候会报各种奇奇怪怪的错。我本地用的 Node 16 LTS 和 npm 8,一路没有障碍。装在公司的旧电脑就碰到过 Node 10 装 Vue 3 项目直接失败的情况,检查一看版本,就立刻明白了。
4.2 页面框架设计:目录树的实现
首页我采用的布局很朴素:上半部分是全局搜索栏和统计卡片,下方左侧是建筑档案目录树,右侧是档案列表和详情抽屉。
树形目录是其中最有技术含量的部分。档案分类体系设计为三级结构:第一级为“类别”,第二级为“子类别”,第三级为“具体档案”(每次传承/每次修缮/每次调查各占一个叶子)。这样设计的好处是,用户点开的路径是唯一的,不会出现“这份测绘图到底属于哪一类”的歧义。前端直接渲染 el-tree,数据是后端一次性提供的全部节点。
html复制<el-tree
:data="archiveTree"
:props="{ label: 'title', children: 'children' }"
node-key="id"
highlight-current
@node-click="handleNodeClick"
/>
为了让目录树点击更顺畅,我在后端做了一个处理:当选中某个节点时,如果它不是叶子节点,默认查出它下辖全部子孙档案的附件共同显示。这个操作不是递归写在前端,而是在后端用了一个 WITH RECURSIVE 风格的 SQL(MySQL 8.0 以上支持)。如果不想用递归 CTE,也可以先查出所有节点,在 Java 代码里做树形过滤,数据量小的时候这种内存操作反而直观。
4.3 档案详情的展与收:响应式表单怎么设计
在“新增/编辑档案”页面,一个核心问题是:不同档案类别的表单不一样。这里的“不一样”不是指按钮数量,而是字段集完全不同。
为了不写十几个几乎重复的页面组件,我把表单抽象为“配置驱动”。每个档案类别配置一个 JSON Schema 类型的结构,描述这个类别包含哪些字段,每个字段是什么类型、是否必填、有哪些选项。前端拿到配置后动态渲染 el-form-item。这样后端新增一个档案分类,不用改前端代码,只提供配置,前端自动适配。
配置文件的简化示例:
json复制{
"category": "survey",
"title": "测绘档案",
"fields": [
{ "key": "surveyUnit", "label": "测绘单位", "type": "input", "required": true },
{ "key": "surveyDate", "label": "测绘日期", "type": "date", "required": true },
{ "key": "scale", "label": "比例尺", "type": "select", "options": ["1:50", "1:100", "1:200"] }
]
}
这种设计有几个明显好处:第一,代码重复度大幅下降;第二,后续客户提出“我要加一个字段”,不再需要前后端同时改代码,只需要在数据库的配置里加一条记录;第三,表单校验规则和渲染逻辑只用维护一份。缺点是需要花点心思写一个通用动态表单组件,但一次投入,后面收益很大。
需要注意的是,动态表单的数据最终要转成键值对保存到 archive_detail 表。存的时候我保留了 JSON 字段(存一份原始表单数据),同时拆出几个高频查询字段(如测绘日期、施工单位)放到独立列,方便列表页排序和筛选。用空间换查询效率,在档案场景下很实用。
4.4 影像与流媒体资源的显示
之前用户搜索词里有“vue 播放 m3u8”,这个在古建筑影像档案场景里确实有实际需求。有些建筑物我们会定期拍摄全景视频或者把现场采集的内容编码为 HLS 流(m3u8)进行存档预览。网页原生 video 标签不能直接播放 m3u8,我引入 hls.js 来解决:
bash复制npm install hls.js
然后在组件里写一个判断:如果视频地址后缀是 .m3u8,用 Hls 实例加载,否则直接交给 video 播放。
javascript复制import Hls from 'hls.js'
function playVideo(videoElement, src) {
if (src.endsWith('.m3u8') && Hls.isSupported()) {
const hls = new Hls()
hls.loadSource(src)
hls.attachMedia(videoElement)
} else {
videoElement.src = src
}
}
不过初版系统里没有立刻引入 m3u8 播放,因为档案平台核心仍是“可查阅、可下载”,视频大多以 mp4 原文件挂载,只有需要在线预览的实时监控类视频才单独接入流媒体服务。把点播文件和流分开想,是我踩过需求变化坑之后沉淀的经验。先让系统简单,再看场景适时扩展,少走很多弯路。
5. 接口设计、鉴权拦截与文件上传的完整细节
5.1 路由与控制器:Restful 风格下的接口拆解
Spring Boot 后端接口按资源划分,整体比较直观。建筑资源和档案资源分开两个 Controller,避免一个 Controller 几百行。
java复制@RestController
@RequestMapping("/api/building")
public class BuildingController {
@GetMapping("/list")
public Result list(BuildingQuery query) { ... }
@GetMapping("/{id}")
public Result detail(@PathVariable Long id) { ... }
@PostMapping
public Result create(@RequestBody BuildingDTO dto) { ... }
@PutMapping
public Result update(@RequestBody BuildingDTO dto) { ... }
@DeleteMapping("/{id}")
public Result delete(@PathVariable Long id) { ... }
}
返回结果我用了一个统一的 Result 包装类:code、message、data 三段式。对前端来说,判断 code 为 200 时取 data;否则弹出 message 提示。这套风格贯彻所有接口非常管用,前端写 axios 拦截器能统一处理异常,不需要每个请求都判断 HTTP 状态码。
接口设计时有一个细节需要花心思:列表接口的返回结构。不要简单返回所有数据,我分的结构是 records(当前页数据)、total(总条数)、pageNum、pageSize。前端分页组件需要的正是这套结构。有些人图省事一次性返回几千条让前端自己分页,一开始可能没感觉,但数据量过万之后页面卡顿会非常明显,所以这个基础结构要趁早做好。
5.2 JWT 身份认证与登录流程
用户管理这块,我用 JWT 做登录态管理,因为前后端分离后,Session 方式需要处理跨域携带 Cookie 的各种兼容问题,JWT 只需要前端在每次请求时在 Header 加 Authorization: Bearer xxx。
实现思路是:用户用账号密码请求 /api/auth/login,后端校验通过后生成 JWT,把用户 ID、用户名、角色塞进 token,设置好过期时间(我设置的是 24 小时)。前端 axios 拦截器统一在请求前从 localStorage 中读 token,放到 Header 里;响应拦截器检测到 401 时,自动跳转回登录页。
javascript复制// axios 拦截器(Vue 前端)
axios.interceptors.request.use(config => {
const token = localStorage.getItem('token')
if (token) {
config.headers.Authorization = `Bearer ${token}`
}
return config
})
后端写一个 OncePerRequestFilter 校验每个请求的 token(白名单放行登录接口)。确认没问题再解析出用户信息,存入 ThreadLocal 供后续业务代码获取当前用户。这个设计虽然简单,但足够支撑这类管理平台的权限场景。
提示:千万不要把用户密码明文存数据库。用 BCryptPasswordEncoder 加密后用
{bcrypt}前缀存储密文,哪怕数据库泄露,原始密码也不会直接暴露。
5.3 大文件上传与服务端存储路径管理
古建筑测绘资料里,一些 CAD 图纸、高清扫描件体积轻松上几十 MB,甚至上百 MB。前期简单文件上传还够用,但要将来可能会传超大文件,我提前预留了分片上传能力。
设计思路很简单:前端把文件按固定大小(例如 5MB)切成多个分片,逐个上传到后端临时目录。后端每个分片附带一个 uploadId 和一个分片序号。所有分片上传完成后,前端再通知后端合并成一个完整文件。
java复制@PostMapping("/api/file/chunk")
public Result uploadChunk(
@RequestParam("file") MultipartFile file,
@RequestParam("uploadId") String uploadId,
@RequestParam("chunkIndex") int chunkIndex) {
// 将分片写入临时目录 uploadId/
file.transferTo(new File(tmpDir + uploadId + "/" + chunkIndex));
return Result.success();
}
@PostMapping("/api/file/merge")
public Result mergeChunk(@RequestParam("uploadId") String uploadId,
@RequestParam("fileName") String fileName) {
// 合并所有分片到正式存储目录
// 删除临时目录
return Result.success();
}
这个功能初版可能用不上,但留出来之后就不用担心单文件大小限制的问题了。Spring Boot 默认上传大小限制是 1MB,如果不改配置,稍微大一点的扫描件都传不上去。
yaml复制spring:
servlet:
multipart:
max-file-size: 500MB
max-request-size: 500MB
很多人一看到“啊,文件要分片”,上来就写前端分片组件,容易想得特别复杂,其实先确认清楚需求优先级。如果文件普遍在 50MB 以下,普通 POST 上传就能解决;只有你要传几个 G 的全景点云数据,分片才有必要。别让系统一开始就背上沉重的技术包袱。
5.4 前后端分离下的跨域与代理配置
没有代理的情况下,前端页面(localhost:8080)直接请求后端接口(localhost:8081)会被浏览器的同源策略拦截。解决方式有几种:后端加 CORS 配置、前端加代理、生产环境用 Nginx 反代,三种我都试用过,最稳定的是“开发环境加代理 + 生产环境用 Nginx”。
后端加 CORS 时,我曾经用 @CrossOrigin 一个个加到 Controller 上,但很快发现每个接口都要加很啰嗦,后来改成全局配置 CorsFilter 或实现 WebMvcConfigurer 统一设置。但更推荐的做法是开发时别依赖后端 CORS,因为上线后 Nginx 阶段就不用它了,在开发环境用 Vue 代理转发既简单又不会带出不必要的安全风险。
nginx复制# Nginx 生产部署核心配置(示例)
server {
listen 80;
server_name your-domain;
root /var/www/ancient-archive/dist;
index index.html;
location / {
try_files $uri $uri/ /index.html;
}
location /api/ {
proxy_pass http://127.0.0.1:8081/api/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
location /files/ {
proxy_pass http://127.0.0.1:8081/files/;
}
}
这里有一个前端路由模式的小坑:Vue Router 默认是 history 模式,页面路径在刷新时会真的请求 Nginx 的对应资源,如果没有 try_files 配置,会出现“从首页点进详情正常,一刷新就 404”的经典问题。解决办法就是上面 Nginx 配置里那个 try_files。如果不想配置,也可以把 Vue Router 改成 hash 模式,但 URL 里会出现一个 # 号,观感略差。实际部署中我用的是 history 模式加 try_files 配置。
6. 和数据库交互的大坑与排查实录
6.1 时区、编码、连接配置容易忽略的细节
Spring Boot + MySQL 项目里,最容易出问题的其实是配置文件里那些“看起来能跑就行”的参数。我刚搭建这个平台时,因为 MySQL 连接串没有显式指定时区,出现了所有时间字段早 8 小时的问题。后来在连接串上加 serverTimezone=Asia/Shanghai 解决。中文乱码问题则通过指定 characterEncoding=utf8 避免。
一个经验:如果你在 Windows 里用 MySQL 5.7,在 Linux 上用 MySQL 8.0,连接驱动也要跟着变。MySQL 8.0 推荐用 com.mysql.cj.jdbc.Driver,老驱动在新版本上有警告甚至直接报错。这种兼容性问题如果不提前查文档,很容易被卡一下午。
6.2 常见异常汇总与避坑手册
这里记录几个我实际开发中遇到的典型问题,给后来人参考:
| 现象 | 原因 | 解决方案 |
|---|---|---|
启动报 Failed to configure a DataSource |
没有配置数据源或依赖冲突 | 检查 application.yml 中 spring.datasource 配置;排除不用的数据库自动配置 |
上传文件报 MaxUploadSizeExceededException |
multipart 配置没改 | 在配置文件中调大 max-file-size 与 max-request-size |
| JPA 实体返回 JSON 报循环引用 | 多对多/一对多关联双向序列化 | 使用 DTO 而不是直接返回实体;或在关联字段加 @JsonIgnore |
| Vue 页面刷新 404 | history 路由模式没有 fallback | Nginx 配置 try_files $uri $uri/ /index.html |
跨域报 CORS policy |
开发环境没走代理或后端未允许 | 用前端 devServer 代理转发;生产由 Nginx 统一代理 |
| 文件存储到项目 jar 包内,重启丢失 | 将文件写进了 classpath | 文件应存储到外部独立目录,不建议放 resources 目录 |
| 列表查询慢 | 大量 LEFT JOIN 全表扫描 |
给 building_id、archive_id 加索引;必要时用分页和条件索引 |
| 汉字搜索不到 | 数据库字段排序规则不是 utf8mb4 | 库表统一使用 utf8mb4 和 utf8mb4_unicode_ci |
6.3 从头理一遍:跟着操作就能复现
如果需要快速上手,建议按这个顺序走一遍:
- 准备环境:安装 JDK 1.8、Maven 3.6+、MySQL 5.7/8.0、Node 14+(推荐 16),确保命令行能直接执行
java -version、mvn -v、node -v。 - 创建后端工程:用 Spring Initializr(start.spring.io)生成工程,依赖勾选 Spring Web、Spring Data JPA、MySQL Driver、Lombok。下载后导入 IDE。
- 创建数据库:执行建库语句
CREATE DATABASE ancient_archive DEFAULT CHARACTER SET utf8mb4;,再导入建表 SQL。 - 配置 application.yml:配置数据源连接、JPA 的 ddl-auto 设为 update(开发期用,上线后最好改为 validate)、文件上传大小限制。
- 编写核心实体:Building、Archive、Attachment、User,写好 Repository。
- 实现登录:用 Spring Security + JWT 实现登录和过滤链,保证
/api/building/**接口都需要 token。 - 实现档案接口:先做列表查询接口(分页 + 搜索),再做树形目录接口(返回嵌套结构),最后做文件上传接口。
- 创建前端工程:用 vue-cli 创建 Vue 3 项目,装 element-plus、axios、pinia。
- 搭页面骨架:登录页、主布局(左侧目录树、右侧内容区)、档案列表页、档案详情抽屉、档案编辑页。
- 联调:前端启动 devServer 加代理,后端启动在 8081,从登录到列表到详情逐条测试。
- 构建部署:前端
npm run build,后端mvn package生成 jar;用 Nginx + Systemd 或 Docker 方式部署。
第五步到第七步是后端工作量比较大的地方,建议先把“架构草图”画好再动手,而不是上来就复制别人的 Controller。想清楚每个接口的入参、出参、异常处理方式,后期联调能省非常多心力。
7. 更进一步:让档案平台从“能用”到“好用”
7.1 无纸化审批流,值得纳入 Roadmap
老式档案管理中,查阅、借阅、复制都会走纸质审批。系统上线后,如果继续把这些流程放在线下,数字化只完成了一半。我目前预留了状态流转日志表,后续可以扩展为一份简单的审批工作流。功能上可以做“借阅申请—部门审批—档案管理员处理—归还登记”的小闭环,技术上用状态机枚举就能处理,不一定引入重量级工作流引擎。
如果确实要引入工作流引擎,社区近年讨论多的是 Flowable,它和 Spring Boot 整合比较顺。但我的观点是:除非审批步骤特别复杂(条件分支、多人会签、动态驳回),否则一个小型档案系统用状态机写死的流程比引擎更可控。引擎本身有学习成本,也要维护 BPMN 文件,能用在刀刃上最好。
7.2 数字化存档与检索优化的延展空间
真正的古建筑档案还要考虑更多载体:历史照片、老地图、航片、三维扫描点云、BIM模型甚至VR导览数据。这些资料一个共同点是文件体积大、格式杂、没有统一渲染技术栈。
遇到这个层次,平台的定位就不仅是管理系统,还要当“媒体资产库”。我的方向是:先保证附件表能存下所有格式,文件存储统一走 MinIO 之类对象存储,并提供按文件类型自动归类标签(图片、图纸、模型、视频、压缩包)。不同格式的文件配置不同的在线预览策略:图片直接预览缩略图,PDF 用 PDF.js 展示,CAD 图纸先转 PDF 再预览,视频调用播放器,三维点云可以接 Three.js 显示轻量化结果。
目前我已经尝试用全景图(通过 Photo Sphere Viewer 插件嵌到 Vue 页面里)来展示古建筑院落,实际体验比普通图片列表好很多。这是一条性价比高的演进方向,既保留了数字档案的严谨性,又能直观呈现建筑现状。
8. 安全加固与后续建议
平台虽然没有公有云级别的安全压力,但涉到文博数据后还是要做好基本的安全习惯。密码必须加密存储;文件上传要做扩展名白名单,防止有人传可执行脚本后通过 URL 访问到服务器;文件下载要校验权限,不能简单把文件路径拼上去就返回。
后端 Spring Security 需要放行静态资源和登录接口,但业务接口一个个保护。白名单怎么配置,不要图省事直接 permitAll(),而是精确到路径。还有一个容易忽视的点:生产环境不要把后端的错误堆栈直接返回给前端,否则容易泄露内部类名和数据库结构。统一异常处理器里只返回“服务器内部错误”,详细日志记录到文件即可。
数据库账号也不要使用 root 连接业务库,创建一个只拥有单库读写权限的专用账号。这个好习惯其实在学习阶段就该养成,等系统部署后才想起来,那时要迁移的重构成本就高了。
关于上线后的维护,我也有一些心得。档案类系统的数据质量是生命线。前端表单即使校验再严,后端入库前仍要再做一次校验;必填字段缺失、编号重复这类错误如果流入库里,后面查起来非常痛苦。我习惯在数据库层再设一层唯一索引和检查约束,双保险。还有一点:任何批量修改、删除操作,上线前先在备份库上跑一遍,确认影响行数符合预期再操作生产库。
我真正体会最深的是在上线之后。你辛辛苦苦做的界面、前端组件的动效,在业务人员眼里都排在第二位,他们最关心的是“查一份档案快不快,传一份资料稳不稳,别动不动就丢”。开发这个平台时守住这三个底线,后续的优化才有一个稳的地基。如果你也准备做类似的档案管理系统,先把“清晰的数据模型、可控的文件存储、完整的操作日志”这三件事做好,系统就已经成功了一大半。剩下的锦上添花,都留到下一次迭代慢慢做,都不迟。
