长春数字档案馆系统采用微服务架构,核心组件包括档案数字化处理、元数据管理、全文检索、权限控制四大模块。技术栈选择基于实际部署环境考虑:后端使用Java Spring Boot 2.7.8,数据库采用PostgreSQL 14,全文检索使用Elasticsearch 7.17,文件存储用MinIO,前端采用Vue 3 + Element Plus。
在CentOS 7.9服务器上执行以下命令安装基础环境:
```bash 安装JDK 11 yum install -y java-11-openjdk-devel java -version 确认版本为11.0.18+ 安装PostgreSQL yum install -y https://download.postgresql.org/pub/repos/yum/reporpms/EL-7-x86_64/pgdg-redhat-repo-latest.noarch.rpm yum install -y postgresql14-server /usr/pgsql-14/bin/postgresql-14-setup initdb systemctl start postgresql-14 systemctl enable postgresql-14 创建数据库和用户 sudo -u postgres psql -c "CREATE USER archive_admin WITH PASSWORD 'StrongPass123!';" sudo -u postgres psql -c "CREATE DATABASE digital_archive OWNER archive_admin;" 安装Elasticsearch rpm --import https://artifacts.elastic.co/GPG-KEY-elasticsearch cat > /etc/yum.repos.d/elasticsearch.repo << EOF [elasticsearch] name=Elasticsearch repository for 7.x packages baseurl=https://artifacts.elastic.co/packages/7.x/yum gpgcheck=1 gpgkey=https://artifacts.elastic.co/GPG-KEY-elasticsearch enabled=1 autorefresh=1 type=rpm-md EOF yum install -y elasticsearch systemctl start elasticsearch systemctl enable elasticsearch ```
创建Spring Boot项目,在pom.xml中添加关键依赖:
```xml
配置application.yml文件:
```yaml server: port: 8080 spring: datasource: url: jdbc:postgresql://localhost:5432/digital_archive username: archive_admin password: StrongPass123! driver-class-name: org.postgresql.Driver jpa: hibernate: ddl-auto: update show-sql: true properties: hibernate: dialect: org.hibernate.dialect.PostgreSQLDialect format_sql: true archive: storage: minio: endpoint: http://localhost:9000 access-key: minioadmin secret-key: minioadmin bucket: archive-files ocr: tessdata-path: /usr/share/tesseract-ocr/4.00/tessdata ```
创建FileUploadController处理档案文件上传:
```java
@RestController
@RequestMapping("/api/archive")
public class FileUploadController {
@PostMapping("/upload")
public ResponseEntity
执行以下SQL创建核心表结构:
```sql -- 档案文件主表 CREATE TABLE archive_files ( file_id VARCHAR(36) PRIMARY KEY, file_name VARCHAR(500) NOT NULL, file_size BIGINT NOT NULL, file_type VARCHAR(50), category VARCHAR(100) NOT NULL, storage_path VARCHAR(1000) NOT NULL, extracted_text TEXT, upload_time TIMESTAMP NOT NULL, upload_user VARCHAR(100), archive_year INTEGER, security_level VARCHAR(20) DEFAULT '公开', retention_period INTEGER DEFAULT 30 ); -- 档案元数据表 CREATE TABLE file_metadata ( metadata_id SERIAL PRIMARY KEY, file_id VARCHAR(36) REFERENCES archive_files(file_id), metadata_key VARCHAR(100) NOT NULL, metadata_value TEXT NOT NULL, created_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); -- 全文检索索引表 CREATE TABLE search_index ( index_id SERIAL PRIMARY KEY, file_id VARCHAR(36) REFERENCES archive_files(file_id), indexed_text TEXT NOT NULL, index_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); -- 创建索引优化查询性能 CREATE INDEX idx_archive_category ON archive_files(category); CREATE INDEX idx_archive_year ON archive_files(archive_year); CREATE INDEX idx_metadata_key ON file_metadata(metadata_key); CREATE INDEX idx_search_file ON search_index(file_id); ```

创建Elasticsearch索引映射:
```bash curl -X PUT "localhost:9200/archive_documents" -H 'Content-Type: application/json' -d' { "mappings": { "properties": { "fileId": {"type": "keyword"}, "fileName": { "type": "text", "analyzer": "ik_max_word", "search_analyzer": "ik_smart" }, "category": {"type": "keyword"}, "content": { "type": "text", "analyzer": "ik_max_word", "search_analyzer": "ik_smart" }, "uploadTime": {"type": "date"}, "archiveYear": {"type": "integer"}, "securityLevel": {"type": "keyword"}, "metadata": { "type": "nested", "properties": { "key": {"type": "keyword"}, "value": {"type": "text"} } } } } } ' ```
实现高级搜索功能:
```java
@Service
public class ArchiveSearchService {
@Autowired
private RestHighLevelClient elasticsearchClient;
public SearchResponse searchArchives(SearchRequest request) throws IOException {
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
// 构建布尔查询
BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
// 关键词搜索(支持中文分词)
if (StringUtils.isNotBlank(request.getKeyword())) {
boolQuery.must(QueryBuilders.multiMatchQuery(request.getKeyword())
.field("fileName", 3.0f) // 文件名权重更高
.field("content")
.type(MultiMatchQueryBuilder.Type.BEST_FIELDS));
}
// 分类筛选
if (StringUtils.isNotBlank(request.getCategory())) {
boolQuery.filter(QueryBuilders.termQuery("category", request.getCategory()));
}
// 年份范围查询
if (request.getStartYear() != null && request.getEndYear() != null) {
boolQuery.filter(QueryBuilders.rangeQuery("archiveYear")
.gte(request.getStartYear())
.lte(request.getEndYear()));
}
// 安全级别过滤
if (StringUtils.isNotBlank(request.getSecurityLevel())) {
boolQuery.filter(QueryBuilders.termQuery("securityLevel",
request.getSecurityLevel()));
}
sourceBuilder.query(boolQuery);
sourceBuilder.from((request.getPage() - 1) request.getSize());
sourceBuilder.size(request.getSize());
// 高亮显示
HighlightBuilder highlightBuilder = new HighlightBuilder();
highlightBuilder.field("fileName");
highlightBuilder.field("content");
highlightBuilder.preTags("");
highlightBuilder.postTags("");
sourceBuilder.highlighter(highlightBuilder);
// 执行搜索
SearchRequest searchRequest = new SearchRequest("archive_documents");
searchRequest.source(sourceBuilder);
return elasticsearchClient.search(searchRequest, RequestOptions.DEFAULT);
}
// 自动补全建议
public List
创建权限控制表和执行SQL:
```sql -- 用户表 CREATE TABLE system_users ( user_id VARCHAR(36) PRIMARY KEY, username VARCHAR(100) UNIQUE NOT NULL, password_hash VARCHAR(255) NOT NULL, real_name VARCHAR(100), department VARCHAR(200), email VARCHAR(200), is_active BOOLEAN DEFAULT true, created_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); -- 角色表 CREATE TABLE user_roles ( role_id SERIAL PRIMARY KEY, role_name VARCHAR(50) UNIQUE NOT NULL, description TEXT ); -- 权限表 CREATE TABLE permissions ( permission_id SERIAL PRIMARY KEY, permission_code VARCHAR(100) UNIQUE NOT NULL, permission_name VARCHAR(200), resource_type VARCHAR(50) ); -- 用户角色关联 CREATE TABLE user_role_mapping ( user_id VARCHAR(36) REFERENCES system_users(user_id), role_id INTEGER REFERENCES user_roles(role_id), PRIMARY KEY (user_id, role_id) ); -- 角色权限关联 CREATE TABLE role_permission_mapping ( role_id INTEGER REFERENCES user_roles(role_id), permission_id INTEGER REFERENCES permissions(permission_id), PRIMARY KEY (role_id, permission_id) ); -- 插入基础权限数据 INSERT INTO permissions (permission_code, permission_name, resource_type) VALUES ('ARCHIVE:READ', '查看档案', 'ARCHIVE'), ('ARCHIVE:WRITE', '上传档案', 'ARCHIVE'), ('ARCHIVE:DELETE', '删除档案', 'ARCHIVE'), ('ARCHIVE:EXPORT', '导出档案', 'ARCHIVE'), ('METADATA:MANAGE', '管理元数据', 'METADATA'), ('SYSTEM:CONFIG', '系统配置', 'SYSTEM'); ```
配置安全过滤器链:
```java @Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http .csrf().disable() .authorizeRequests() .antMatchers("/api/auth/").permitAll() .antMatchers("/api/archive/upload").hasAuthority('ARCHIVE:WRITE') .antMatchers("/api/archive/search").hasAuthority('ARCHIVE:READ') .antMatchers("/api/archive/delete/").hasAuthority('ARCHIVE:DELETE') .antMatchers("/api/admin/").hasAuthority('SYSTEM:CONFIG') .anyRequest().authenticated() .and() .sessionManagement() .sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and() .addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class); } @Bean public JwtAuthenticationFilter jwtAuthenticationFilter() { return new JwtAuthenticationFilter(); } } ```
创建AOP切面记录所有关键操作:
```java @Aspect @Component public class AuditLogAspect { @Autowired private AuditLogRepository auditLogRepository; @Around("@annotation(com.example.archive.annotation.OperationLog)") public Object logOperation(ProceedingJoinPoint joinPoint) throws Throwable { MethodSignature signature = (MethodSignature) joinPoint.getSignature(); OperationLog annotation = signature.getMethod() .getAnnotation(OperationLog.class); String operation = annotation.value(); String operator = SecurityContextHolder.getContext() .getAuthentication().getName(); LocalDateTime startTime = LocalDateTime.now(); try { Object result = joinPoint.proceed(); LocalDateTime endTime = LocalDateTime.now(); // 保存成功日志 saveAuditLog(operation, operator, "SUCCESS", startTime, endTime, null); return result; } catch (Exception e) { // 保存失败日志 saveAuditLog(operation, operator, "FAILED", startTime, LocalDateTime.now(), e.getMessage()); throw e; } } private void saveAuditLog(String operation, String operator, String status, LocalDateTime startTime, LocalDateTime endTime, String errorMsg) { AuditLog log = new AuditLog(); log.setOperation(operation); log.setOperator(operator); log.setStatus(status); log.setStartTime(startTime); log.setEndTime(endTime); log.setDuration(Duration.between(startTime, endTime).toMillis()); log.setErrorMsg(errorMsg); log.setIpAddress(getClientIp()); auditLogRepository.save(log); } } ```
创建docker-compose.yml文件:
```yaml version: '3.8' services: postgres: image: postgres:14-alpine environment: POSTGRES_DB: digital_archive POSTGRES_USER: archive_admin POSTGRES_PASSWORD: StrongPass123! volumes: - postgres_data:/var/lib/postgresql/data ports: - "5432:5432" elasticsearch: image: elasticsearch:7.17.10 environment: - discovery.type=single-node - "ES_JAVA_OPTS=-Xms512m -Xmx512m" volumes: - es_data:/usr/share/elasticsearch/data ports: - "9200:9200" minio: image: minio/minio:latest command: server /data --console-address ":9001" environment: MINIO_ROOT_USER: minioadmin MINIO_ROOT_PASSWORD: minioadmin volumes: - minio_data:/data ports: