环保局数字档案馆搭建指南:从零构建可扩展的文档管理系统
系统架构与核心技术选型
本系统采用B/S架构,分为前端展示层、业务逻辑层和数据存储层。前端使用Vue 3 + Element Plus构建交互界面,后端采用Spring Boot框架,数据库使用PostgreSQL 15,全文检索使用Elasticsearch 8.11。
硬件与软件环境要求
服务器最低配置:4核CPU,8GB内存,500GB SSD硬盘。操作系统推荐使用Ubuntu Server 22.04 LTS。需要安装的依赖软件包如下:
- Java Development Kit 17
- Node.js 18 LTS
- PostgreSQL 15
- Elasticsearch 8.11
- Nginx 1.24
环境配置与依赖安装
操作系统与基础环境
通过SSH连接到服务器,执行以下命令更新系统并安装基础工具:
``` sudo apt update && sudo apt upgrade -y sudo apt install -y curl wget git vim ```Java环境安装
安装OpenJDK 17:
``` sudo apt install -y openjdk-17-jdk java -version 验证安装,应显示"openjdk version 17" ```数据库安装与配置
安装PostgreSQL 15:
``` sudo sh -c 'echo "deb https://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" > /etc/apt/sources.list.d/pgdg.list' wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo apt-key add - sudo apt update sudo apt install -y postgresql-15 postgresql-client-15 ```创建数据库和用户:
``` sudo -u postgres psql CREATE DATABASE eco_archive; CREATE USER archive_admin WITH PASSWORD 'YourSecurePassword123!'; GRANT ALL PRIVILEGES ON DATABASE eco_archive TO archive_admin; \q ```Elasticsearch安装
安装Elasticsearch 8.11:
``` wget https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-8.11.1-amd64.deb sudo dpkg -i elasticsearch-8.11.1-amd64.deb sudo systemctl enable elasticsearch sudo systemctl start elasticsearch ```后端系统搭建
项目初始化
使用Spring Initializr创建项目,选择以下依赖:Spring Web, Spring Data JPA, Spring Security, PostgreSQL Driver。
核心配置文件
创建application.yml配置文件:
``` server: port: 8080 spring: datasource: url: jdbc:postgresql://localhost:5432/eco_archive username: archive_admin password: YourSecurePassword123! driver-class-name: org.postgresql.Driver jpa: hibernate: ddl-auto: update show-sql: true properties: hibernate: dialect: org.hibernate.dialect.PostgreSQLDialect elasticsearch: uris: http://localhost:9200 servlet: multipart: max-file-size: 100MB max-request-size: 100MB ```实体类设计
创建档案文档实体类:
``` @Entity @Table(name = "archive_documents") public class ArchiveDocument { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Column(nullable = false) private String documentNumber; @Column(nullable = false) private String title; @Column(nullable = false) private String documentType; // 环评报告、监测数据、批复文件等 @Column(nullable = false) private LocalDate createDate; @Column(nullable = false) private String department; @Column(nullable = false) private String filePath; @Column(length = 2000) private String description; @ElementCollection private Set文件存储服务
创建文件上传服务类:
``` @Service public class FileStorageService { private final Path fileStorageLocation; public FileStorageService() { this.fileStorageLocation = Paths.get("/var/eco-archive/files") .toAbsolutePath().normalize(); try { Files.createDirectories(this.fileStorageLocation); } catch (Exception ex) { throw new RuntimeException("无法创建文件存储目录", ex); } } public String storeFile(MultipartFile file, String documentNumber) { String originalFileName = StringUtils.cleanPath(file.getOriginalFilename()); String fileExtension = originalFileName.substring(originalFileName.lastIndexOf(".")); String fileName = documentNumber + "_" + System.currentTimeMillis() + fileExtension; try { Path targetLocation = this.fileStorageLocation.resolve(fileName); Files.copy(file.getInputStream(), targetLocation, StandardCopyOption.REPLACE_EXISTING); return fileName; } catch (IOException ex) { throw new RuntimeException("文件存储失败: " + fileName, ex); } } } ```前端系统开发
Vue项目初始化
创建Vue项目并安装依赖:
``` npm create vue@latest eco-archive-frontend cd eco-archive-frontend npm install element-plus axios vue-router ```路由配置
配置路由文件:
``` import { createRouter, createWebHistory } from 'vue-router' const routes = [ { path: '/', name: 'Home', component: () => import('../views/HomeView.vue') }, { path: '/upload', name: 'Upload', component: () => import('../views/UploadView.vue') }, { path: '/search', name: 'Search', component: () => import('../views/SearchView.vue') }, { path: '/document/:id', name: 'DocumentDetail', component: () => import('../views/DocumentDetail.vue') } ] const router = createRouter({ history: createWebHistory(), routes }) export default router ```文件上传组件

创建文件上传组件:
```拖拽文件到此处,或点击上传
支持PDF、DOC、DOCX、XLS、XLSX格式,单个文件不超过100MB
全文检索功能实现
Elasticsearch索引配置
创建文档索引映射:
``` PUT /eco_documents { "mappings": { "properties": { "documentNumber": { "type": "keyword" }, "title": { "type": "text", "analyzer": "ik_max_word", "search_analyzer": "ik_smart" }, "content": { "type": "text", "analyzer": "ik_max_word", "search_analyzer": "ik_smart" }, "documentType": { "type": "keyword" }, "createDate": { "type": "date", "format": "yyyy-MM-dd" }, "department": { "type": "keyword" }, "tags": { "type": "keyword" } } } } ```搜索服务实现
创建Elasticsearch搜索服务:
``` @Service public class DocumentSearchService { @Autowired private ElasticsearchRestTemplate elasticsearchTemplate; public SearchHits权限控制与安全配置
Spring Security配置
配置安全策略:
``` @Configuration @EnableWebSecurity public class SecurityConfig { @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http .csrf().disable() .authorizeHttpRequests(authz -> authz .requestMatchers("/api/auth/").permitAll() .requestMatchers("/api/documents/search").permitAll() .requestMatchers("/api/documents/upload").hasRole("ADMIN") .requestMatchers("/api/documents/").hasAnyRole("USER", "ADMIN") .anyRequest().authenticated() ) .sessionManagement(session -> session .sessionCreationPolicy(SessionCreationPolicy.STATELESS) ) .addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class); return http.build(); } } ```系统部署与运维
Nginx反向代理配置
配置Nginx作为反向代理:
``` server { listen 80; server_name archive.example.com; location / { root /var/www/eco-archive-frontend/dist; try_files $uri $uri/ /index.html; } location /api/ { proxy_pass http://localhost:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } location /files/ { alias /var/eco-archive/files/; expires 30d; add_header Cache-Control "public, immutable"; } } ```系统监控配置
配置Prometheus监控:
``` management: endpoints: web: exposure: include: health,metrics,prometheus metrics: export: prometheus: enabled: true ```数据备份脚本
创建数据库备份脚本:
``` !/bin/bash BACKUP_DIR="/backup/eco-archive" DATE=$(date +%Y%m%d_%H%M%S) DB_NAME="eco_archive" 备份数据库 pg_dump -U archive_admin $DB_NAME > $BACKUP_DIR/db_backup_$DATE.sql 备份上传的文件 tar -czf $BACKUP_DIR/files_backup_$DATE.tar.gz /var/eco-archive/files/ 删除7天前的备份 find $BACKUP_DIR -type f -mtime +7 -delete echo "备份完成: $DATE" ```将脚本加入crontab,每天凌晨2点执行:
``` 0 2 /opt/eco-archive/backup.sh ```常见问题排查
文件上传失败
检查文件存储目录权限:
``` sudo chown -R www-data:www-data /var/eco-archive/files sudo chmod -R 755 /var/eco-archive/files ```搜索功能无结果
检查Elasticsearch连接状态:
``` curl http://localhost:9200/_cluster/health ```确保状态显示为green,检查索引是否存在:
``` curl http://localhost:9200/_cat/indices?v ```数据库连接失败
检查PostgreSQL服务状态和连接配置:
``` sudo systemctl status postgresql sudo -u postgres psql -c "\l" ```修改pg_hba.conf允许连接:
``` /etc/postgresql/15/main/pg_hba.conf host all all 127.0.0.1/32 md5 host all all ::1/128 md5 ```