建筑版文书档案管理系统从零到一搭建实操指南
一、系统核心需求分析与技术选型
建筑行业文书档案管理需满足图纸、合同、变更单、验收报告等多类型文件管理,同时关联项目、楼栋、楼层等空间维度。
1.1 核心功能需求
- 多层级项目结构:公司→项目→标段→楼栋→楼层
- 文件分类管理:设计图纸、施工合同、过程文档、竣工资料
- 版本控制:图纸变更必须保留历史版本
- 权限体系:按项目、角色、部门进行数据隔离
- 全文检索:支持PDF、Word、Excel等格式内容搜索
1.2 技术栈选择
基于快速部署和成本考虑,选择以下开源技术栈:
- 后端:Spring Boot 2.7 + MyBatis Plus
- 前端:Vue 3 + Element Plus
- 数据库:MySQL 8.0
- 文件存储:MinIO(替代S3的本地存储方案)
- 全文检索:Elasticsearch 7.17
- 部署:Docker Compose
二、环境准备与基础框架搭建
2.1 开发环境安装
安装JDK 17:
``` wget https://download.oracle.com/java/17/latest/jdk-17_linux-x64_bin.tar.gz tar -xzf jdk-17_linux-x64_bin.tar.gz sudo mv jdk-17.0.10 /usr/local/ echo 'export JAVA_HOME=/usr/local/jdk-17.0.10' >> ~/.bashrc echo 'export PATH=$JAVA_HOME/bin:$PATH' >> ~/.bashrc source ~/.bashrc ```安装Node.js 18:
``` curl -fsSL https://deb.nodesource.com/setup_18.x | sudo -E bash - sudo apt-get install -y nodejs ```2.2 项目初始化
创建Spring Boot项目:
``` curl https://start.spring.io/starter.zip \ -d type=maven-project \ -d language=java \ -d bootVersion=2.7.18 \ -d baseDir=construction-doc-system \ -d groupId=com.construction \ -d artifactId=doc-system \ -d name=ConstructionDocSystem \ -d dependencies=web,mybatis,mysql,lombok \ -o construction-doc-system.zip unzip construction-doc-system.zip ```前端项目初始化:
``` npm create vue@latest construction-doc-web cd construction-doc-web npm install element-plus @element-plus/icons-vue vue-router@4 pinia axios ```三、数据库设计与核心表结构
3.1 数据库创建
登录MySQL执行:
``` CREATE DATABASE construction_doc DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE construction_doc; ```3.2 核心表SQL
项目结构表:
``` CREATE TABLE project_structure ( id BIGINT PRIMARY KEY AUTO_INCREMENT, parent_id BIGINT DEFAULT 0, code VARCHAR(50) NOT NULL COMMENT '项目编码', name VARCHAR(100) NOT NULL COMMENT '项目名称', type TINYINT NOT NULL COMMENT '1:公司 2:项目 3:标段 4:楼栋 5:楼层', full_path VARCHAR(500) COMMENT '完整路径,如1/2/3/4', created_time DATETIME DEFAULT CURRENT_TIMESTAMP, INDEX idx_parent_id (parent_id), INDEX idx_full_path (full_path(100)) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; ```文档分类表:
``` CREATE TABLE doc_category ( id INT PRIMARY KEY AUTO_INCREMENT, category_code VARCHAR(20) NOT NULL UNIQUE COMMENT '分类编码', category_name VARCHAR(50) NOT NULL COMMENT '分类名称', parent_id INT DEFAULT 0, sort_order INT DEFAULT 0, project_type TINYINT DEFAULT 0 COMMENT '适用的项目类型,0:全部', created_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; ```插入初始分类数据:
``` INSERT INTO doc_category (category_code, category_name, parent_id, sort_order) VALUES ('DESIGN', '设计图纸', 0, 1), ('DESIGN_ARCH', '建筑图纸', 1, 1), ('DESIGN_STRU', '结构图纸', 1, 2), ('CONTRACT', '合同文件', 0, 2), ('PROCESS', '过程文档', 0, 3), ('PROCESS_CHANGE', '变更单', 3, 1), ('PROCESS_REPORT', '进度报告', 3, 2), ('COMPLETION', '竣工资料', 0, 4); ```文档主表:
``` CREATE TABLE document ( id BIGINT PRIMARY KEY AUTO_INCREMENT, doc_no VARCHAR(50) NOT NULL UNIQUE COMMENT '文档编号', doc_name VARCHAR(200) NOT NULL COMMENT '文档名称', category_id INT NOT NULL COMMENT '分类ID', project_id BIGINT NOT NULL COMMENT '所属项目节点ID', version INT DEFAULT 1 COMMENT '版本号', current_version TINYINT DEFAULT 1 COMMENT '是否当前版本', file_size BIGINT COMMENT '文件大小(字节)', file_path VARCHAR(500) COMMENT '文件存储路径', file_hash VARCHAR(64) COMMENT '文件哈希值,用于去重', upload_user_id BIGINT NOT NULL, upload_time DATETIME DEFAULT CURRENT_TIMESTAMP, description TEXT COMMENT '文档描述', INDEX idx_project_category (project_id, category_id), INDEX idx_doc_no (doc_no), INDEX idx_upload_time (upload_time), FOREIGN KEY (category_id) REFERENCES doc_category(id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; ```四、文件存储服务配置
4.1 MinIO安装与配置
使用Docker启动MinIO:
``` docker run -d \ -p 9000:9000 \ -p 9001:9001 \ --name minio \ -v /data/minio/data:/data \ -v /data/minio/config:/root/.minio \ -e "MINIO_ROOT_USER=admin" \ -e "MINIO_ROOT_PASSWORD=your_strong_password_here" \ minio/minio server /data --console-address ":9001" ```创建存储桶:
``` 安装MinIO客户端 wget https://dl.min.io/client/mc/release/linux-amd64/mc chmod +x mc sudo mv mc /usr/local/bin/ 配置访问 mc alias set local http://localhost:9000 admin your_strong_password_here 创建建筑文档专用桶 mc mb local/construction-docs mc anonymous set download local/construction-docs ```4.2 Spring Boot集成MinIO

添加依赖到pom.xml:
```创建MinIO配置类:
``` @Configuration public class MinioConfig { @Value("${minio.endpoint}") private String endpoint; @Value("${minio.accessKey}") private String accessKey; @Value("${minio.secretKey}") private String secretKey; @Bean public MinioClient minioClient() { return MinioClient.builder() .endpoint(endpoint) .credentials(accessKey, secretKey) .build(); } } ```application.yml配置:
``` minio: endpoint: http://localhost:9000 accessKey: admin secretKey: your_strong_password_here bucket: construction-docs ```五、文档上传与版本控制实现
5.1 文件上传服务
创建文件上传Service:
``` @Service public class FileUploadService { @Autowired private MinioClient minioClient; @Value("${minio.bucket}") private String bucketName; public String uploadFile(MultipartFile file, String objectName) throws Exception { // 检查文件是否已存在 String fileHash = calculateFileHash(file); // 生成存储路径:年/月/日/UUID_原文件名 String datePath = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy/MM/dd")); String fileName = file.getOriginalFilename(); String extension = fileName.substring(fileName.lastIndexOf(".")); String finalObjectName = datePath + "/" + UUID.randomUUID() + extension; // 上传到MinIO minioClient.putObject( PutObjectArgs.builder() .bucket(bucketName) .object(finalObjectName) .stream(file.getInputStream(), file.getSize(), -1) .contentType(file.getContentType()) .build() ); return finalObjectName; } private String calculateFileHash(MultipartFile file) throws Exception { try (InputStream is = file.getInputStream()) { MessageDigest digest = MessageDigest.getInstance("SHA-256"); byte[] buffer = new byte[8192]; int bytesRead; while ((bytesRead = is.read(buffer)) != -1) { digest.update(buffer, 0, bytesRead); } return bytesToHex(digest.digest()); } } } ```5.2 版本控制逻辑
文档版本更新Service:
``` @Service public class DocumentVersionService { @Autowired private DocumentMapper documentMapper; @Transactional public Long createNewVersion(Long docId, MultipartFile newFile, String description) throws Exception { // 查询当前文档 Document currentDoc = documentMapper.selectById(docId); // 将当前文档标记为非最新版本 currentDoc.setCurrentVersion(0); documentMapper.updateById(currentDoc); // 创建新版本 Document newVersion = new Document(); BeanUtils.copyProperties(currentDoc, newVersion); newVersion.setId(null); newVersion.setVersion(currentDoc.getVersion() + 1); newVersion.setCurrentVersion(1); newVersion.setDescription(description); newVersion.setUploadTime(new Date()); // 上传新文件并更新文件信息 String newFilePath = fileUploadService.uploadFile(newFile, currentDoc.getDocNo() + "_v" + newVersion.getVersion()); newVersion.setFilePath(newFilePath); newVersion.setFileSize(newFile.getSize()); documentMapper.insert(newVersion); return newVersion.getId(); } } ```六、全文检索集成
6.1 Elasticsearch安装
使用Docker Compose部署:
``` version: '3.8' services: elasticsearch: image: elasticsearch:7.17.21 container_name: es-doc-search environment: - discovery.type=single-node - ES_JAVA_OPTS=-Xms512m -Xmx512m - xpack.security.enabled=false ports: - "9200:9200" volumes: - es-data:/usr/share/elasticsearch/data networks: - es-net kibana: image: kibana:7.17.21 container_name: kibana-doc ports: - "5601:5601" environment: - ELASTICSEARCH_HOSTS=http://elasticsearch:9200 networks: - es-net volumes: es-data: driver: local networks: es-net: driver: bridge ```启动服务:
``` docker-compose up -d ```6.2 文档索引创建
创建文档索引mapping:
``` PUT /construction-documents { "mappings": { "properties": { "docId": {"type": "long"}, "docNo": {"type": "keyword"}, "docName": { "type": "text", "analyzer": "ik_max_word", "search_analyzer": "ik_smart" }, "content": { "type": "text", "analyzer": "ik_max_word", "search_analyzer": "ik_smart" }, "category": {"type": "keyword"}, "projectPath": {"type": "keyword"}, "uploadTime": {"type": "date"}, "uploadUser": {"type": "keyword"} } } } ```6.3 文件内容提取
集成Tika提取文件内容:
```内容提取工具类:
``` @Component public class ContentExtractor { private final Tika tika = new Tika(); public String extractContent(File file) throws Exception { try (InputStream is = new FileInputStream(file)) { // 设置元数据 Metadata metadata = new Metadata(); metadata.set(Metadata.RESOURCE_NAME_KEY, file.getName()); // 提取文本内容 return tika.parseToString(is, metadata, -1); } } public String extractContent(MultipartFile file) throws Exception { return tika.parseToString(file.getInputStream()); } } ```七、权限控制系统实现
7.1 权限表设计
用户-项目权限关联表:
``` CREATE TABLE user_project_permission ( id BIGINT PRIMARY KEY AUTO_INCREMENT, user_id BIGINT NOT NULL, project_id BIGINT NOT NULL, permission_type TINYINT NOT NULL COMMENT '1:只读 2:编辑 3:管理', created_time DATETIME DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uk_user_project (user_id, project_id), INDEX idx_project_id (project_id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; ```7.2 权限拦截器
创建权限检查拦截器:
``` @Component public class PermissionInterceptor implements HandlerInterceptor { @Autowired private UserProjectPermissionService permissionService; @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 获取当前用户ID(从Session或Token中) Long userId = getCurrentUserId(request); // 获取请求的项目ID Long projectId = getProjectIdFromRequest(request); // 获取请求的权限级别 Integer requiredPermission = getRequiredPermission(request); // 检查权限 boolean hasPermission = permissionService.checkPermission(userId, projectId, requiredPermission); if (!hasPermission) { response.setStatus(HttpStatus.FORBIDDEN.value()); response.getWriter().write("{\"code\":403,\"message\":\"权限不足\"}"); return false; } return true; } } ```八、系统部署与运维
8.1 生产环境Docker部署
创建docker-compose.prod.yml:
``` version: '3.8' services: mysql: image: mysql:8.0 container_name: construction-mysql environment: MYSQL_ROOT_PASSWORD: your_mysql_root_password MYSQL_DATABASE: construction_doc ports: - "3306:3306" volumes: - mysql-data:/var/lib/mysql - ./init.sql:/docker-entrypoint-initdb.d/init.sql command: --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci minio: image: minio/minio container_name: construction-minio command: server /data --console-address ":9001" environment: MINIO_ROOT_USER: admin MINIO_ROOT_PASSWORD: your_minio_password ports: - "9000:9000" - "9001:9001" volumes: - minio-data:/data elasticsearch: image: elasticsearch:7.17.21 container_name: construction-es environment: - discovery.type=single-node - ES_JAVA_OPTS=-Xms1g -Xmx1g - xpack.security.enabled=false ports: - "9200:9200" volumes: - es-data:/usr/share/elasticsearch/data ulimits: memlock: soft: -1 hard: -1 backend: build: ./backend container_name: construction-backend ports: - "8080:8080" environment: - SPRING_PROFILES_ACTIVE=prod - DB_HOST=mysql - MINIO_ENDPOINT=http://minio:9000 - ES_HOST=elasticsearch depends_on: - mysql - minio - elasticsearch frontend: build: ./frontend container_name: construction-frontend ports: - "80:80" depends_on: