从零构建SaaS档案管理系统的技术实现指南

一、系统架构设计

一个可扩展的SaaS档案管理系统应采用前后端分离架构,核心是微服务化的后端与多租户数据隔离方案。

1. 技术栈选型

后端:Spring Boot + Spring Cloud Alibaba,数据库:PostgreSQL(主业务)+ Redis(缓存),文件存储:MinIO(兼容S3协议),前端:Vue 3 + Element Plus。

2. 多租户方案

采用数据库Schema隔离模式,每个租户拥有独立的Schema,确保数据物理隔离。在数据库连接层实现动态数据源切换。

二、环境搭建与初始化

1. 开发环境准备

  • JDK 17:sudo apt install openjdk-17-jdk
  • Node.js 18:curl -fsSL https://deb.nodesource.com/setup_18.x | sudo -E bash - && sudo apt-get install -y nodejs
  • PostgreSQL 15:sudo apt install postgresql postgresql-contrib
  • MinIO Server:wget https://dl.min.io/server/minio/release/linux-amd64/minio && chmod +x minio

2. 数据库初始化

创建系统管理数据库和基础表结构:

```sql -- 创建系统库 CREATE DATABASE saas_archive_system; \c saas_archive_system; -- 租户信息表 CREATE TABLE tenants ( id VARCHAR(32) PRIMARY KEY, name VARCHAR(100) NOT NULL, schema_name VARCHAR(50) UNIQUE NOT NULL, status INT DEFAULT 1, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); -- 创建默认租户schema CREATE SCHEMA tenant_default; ```

执行完成后,系统会有一个用于管理租户的系统库。

三、核心功能实现

1. 动态数据源配置

创建DynamicDataSourceConfig类实现多租户数据源切换:

```java @Configuration public class DynamicDataSourceConfig { @Bean @ConfigurationProperties(prefix = "spring.datasource.system") public DataSource systemDataSource() { return DataSourceBuilder.create().build(); } @Bean public DataSource dynamicDataSource() { Map targetDataSources = new HashMap<>(); targetDataSources.put("system", systemDataSource()); DynamicDataSource dataSource = new DynamicDataSource(); dataSource.setTargetDataSources(targetDataSources); dataSource.setDefaultTargetDataSource(systemDataSource()); return dataSource; } } ```

2. 租户上下文管理

创建TenantContextHolder管理租户标识:

```java public class TenantContextHolder { private static final ThreadLocal CONTEXT = new ThreadLocal<>(); public static void setTenantId(String tenantId) { CONTEXT.set(tenantId); } public static String getTenantId() { return CONTEXT.get(); } public static void clear() { CONTEXT.remove(); } } ```

3. 文件上传服务

实现基于MinIO的文件存储服务:

```java @Service public class FileStorageService { @Value("${minio.endpoint}") private String endpoint; @Value("${minio.accessKey}") private String accessKey; @Value("${minio.secretKey}") private String secretKey; public String uploadFile(MultipartFile file, String tenantId) { try { MinioClient minioClient = MinioClient.builder() .endpoint(endpoint) .credentials(accessKey, secretKey) .build(); String bucketName = "tenant-" + tenantId; String objectName = UUID.randomUUID() + "_" + file.getOriginalFilename(); // 确保桶存在 boolean found = minioClient.bucketExists( BucketExistsArgs.builder().bucket(bucketName).build()); if (!found) { minioClient.makeBucket( MakeBucketArgs.builder().bucket(bucketName).build()); } minioClient.putObject( PutObjectArgs.builder() .bucket(bucketName) .object(objectName) .stream(file.getInputStream(), file.getSize(), -1) .contentType(file.getContentType()) .build()); return endpoint + "/" + bucketName + "/" + objectName; } catch (Exception e) { throw new RuntimeException("文件上传失败", e); } } } ```

四、档案管理核心API实现

1. 档案元数据模型

定义档案基础实体类:

```java @Entity @Table(name = "archives") public class Archive { @Id @GeneratedValue(strategy = GenerationType.UUID) private String id; private String fileName; private String fileType; private Long fileSize; private String filePath; private String category; @ElementCollection @CollectionTable(name = "archive_tags") private Set tags = new HashSet<>(); @Column(columnDefinition = "jsonb") private String metadata; private LocalDateTime uploadTime; private String uploadUserId; } ```

2. 档案上传接口

实现完整的档案上传API:

```java @RestController @RequestMapping("/api/archives") public class ArchiveController { @Autowired private ArchiveService archiveService; @PostMapping("/upload") public ResponseEntity uploadArchive( @RequestParam("file") MultipartFile file, @RequestParam("category") String category, @RequestParam(value = "tags", required = false) String tags) { // 1. 验证文件类型和大小 if (file.isEmpty()) { return ResponseEntity.badRequest().body("文件不能为空"); } if (file.getSize() > 100 1024 1024) { // 100MB限制 return ResponseEntity.badRequest().body("文件大小不能超过100MB"); } // 2. 解析标签 Set tagSet = new HashSet<>(); if (tags != null && !tags.isEmpty()) { tagSet.addAll(Arrays.asList(tags.split(","))); } // 3. 保存档案记录 Archive archive = archiveService.saveArchive(file, category, tagSet); return ResponseEntity.ok(Map.of( "id", archive.getId(), "fileName", archive.getFileName(), "fileUrl", archive.getFilePath() )); } } ```

3. 档案查询接口

从零构建SaaS档案管理系统的技术实现指南

实现基于标签和分类的档案检索:

```java @Service public class ArchiveService { @Autowired private ArchiveRepository archiveRepository; public Page searchArchives(String keyword, String category, Set tags, Pageable pageable) { Specification spec = Specification.where(null); if (keyword != null && !keyword.trim().isEmpty()) { spec = spec.and((root, query, cb) -> cb.like(cb.lower(root.get("fileName")), "%" + keyword.toLowerCase() + "%")); } if (category != null && !category.trim().isEmpty()) { spec = spec.and((root, query, cb) -> cb.equal(root.get("category"), category)); } if (tags != null && !tags.isEmpty()) { spec = spec.and((root, query, cb) -> { Join tagJoin = root.join("tags"); return tagJoin.in(tags); }); } return archiveRepository.findAll(spec, pageable); } } ```

五、安全与权限控制

1. JWT认证配置

配置Spring Security实现基于JWT的认证:

```yaml application.yml jwt: secret: your-256-bit-secret-key-here-must-be-32-chars expiration: 86400000 24小时 ```

2. 权限注解实现

创建自定义权限注解:

```java @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @PreAuthorize("hasRole('ARCHIVE_ADMIN') or " + "@tenantSecurityService.hasTenantAccess(tenantId)") public @interface TenantAccess { } ```

六、部署与配置

1. Docker部署配置

创建docker-compose.yml文件:

```yaml version: '3.8' services: postgres: image: postgres:15 environment: POSTGRES_DB: saas_archive_system POSTGRES_PASSWORD: your_password volumes: - postgres_data:/var/lib/postgresql/data ports: - "5432:5432" minio: image: minio/minio command: server /data --console-address ":9001" environment: MINIO_ROOT_USER: minioadmin MINIO_ROOT_PASSWORD: minioadmin volumes: - minio_data:/data ports: - "9000:9000" - "9001:9001" redis: image: redis:7-alpine ports: - "6379:6379" app: build: . depends_on: - postgres - minio - redis environment: SPRING_DATASOURCE_URL: jdbc:postgresql://postgres:5432/saas_archive_system MINIO_ENDPOINT: http://minio:9000 ports: - "8080:8080" volumes: postgres_data: minio_data: ```

2. 应用配置文件

完整的application-prod.yml配置:

```yaml spring: datasource: system: url: jdbc:postgresql://localhost:5432/saas_archive_system username: postgres password: your_password driver-class-name: org.postgresql.Driver jpa: hibernate: ddl-auto: update properties: hibernate: dialect: org.hibernate.dialect.PostgreSQLDialect jdbc: lob: non_contextual_creation: true redis: host: localhost port: 6379 minio: endpoint: http://localhost:9000 accessKey: minioadmin secretKey: minioadmin file: upload: max-size: 100MB allowed-types: - application/pdf - image/jpeg - image/png - application/msword - application/vnd.openxmlformats-officedocument.wordprocessingml.document ```

七、前端实现要点

1. 文件上传组件

实现带进度显示的文件上传组件:

```vue ```

2. 档案列表展示

实现带搜索和筛选的档案列表:

```vue ```

八、测试与验证

1. API接口测试

使用curl测试核心接口:

```bash 1. 用户登录获取token curl -X POST http://localhost:8080/api/auth/login \ -H "Content-Type: application/json" \ -d '{"username":"admin","password":"admin123"}' 2. 上传文件测试 curl -X POST http://localhost:8080/api/archives/upload \ -H "Authorization: Bearer YOUR_JWT_TOKEN" \ -F "file=@/path/to/your/file.pdf" \ -F "category=contract" \ -F "tags=important,legal" 3. 查询档案测试 curl -X GET "http://localhost:8080/api/archives/search?keyword=contract&page=0&size=10" \ -H "Authorization: Bearer YOUR_JWT_TOKEN" ```

2. 数据库验证

验证多租户数据隔离:

```sql -- 切换到系统库查看租户信息 \c saas_archive_system; SELECT FROM tenants; -- 切换到具体租户的schema SET search_path TO tenant_abc123; SELECT FROM archives; ```

按照以上步骤完整实现后,你将获得一个具备完整多租户隔离、文件存储、权限控制的SaaS档案管理系统。所有代码均可直接复制使用,配置文件已包含完整参数,部署脚本开箱即用。

AI咨询
热线电话

028-85154420

15388110056

全国售前咨询电话

扫码咨询
安答联动微信公众号二维码

微信扫码关注安答联动

申请试用
热线电话
申请试用

安答联动档案管理系统