
项目没什么亮点和难点,投出去面试机会可能不多,大家觉得这个项目应该怎么优化,欢迎大家在评论区讨论。
优化后的项目描述
该项目是面向集团级企业的大型分布式协同办公平台,服务用户规模3000+,日均处理业务数据约10万+。平台采用微服务架构,实现了信息发布中心、流程审批中心、远程办公中心等核心功能模块,支持全面的信创环境适配。
项目成果:
1.设计基于MinIO的分布式文件存储方案,支持PB级文档存储
这里文件存储的整体架构很容易被问到,可以通过redis和mysql的一些关键存储结构举例说明:
MySQL表结构设计
-- 文件信息主表:存储文件的基本信息CREATE TABLE file_info (file_id VARCHAR(32) COMMENT '文件ID,主键',file_name VARCHAR(255) COMMENT '文件名称',file_size BIGINT COMMENT '文件大小,单位字节',file_type VARCHAR(50) COMMENT '文件类型,如pdf、doc等',md5 VARCHAR(32) COMMENT '文件MD5值,用于秒传判断',bucket_name VARCHAR(100) COMMENT 'MinIO的存储桶名称',object_name VARCHAR(255) COMMENT 'MinIO中的对象名称,即存储路径',chunk_count INT COMMENT '文件分片总数',upload_status TINYINT COMMENT '上传状态:0-未上传,1-上传中,2-已完成,3-上传失败',create_time DATETIME COMMENT '创建时间',update_time DATETIME COMMENT '最后更新时间',creator VARCHAR(50) COMMENT '创建者用户ID',is_deleted TINYINT COMMENT '是否删除:0-未删除,1-已删除',PRIMARY KEY (file_id)) COMMENT '文件信息主表';-- 文件分片信息表:存储文件分片上传的详细信息CREATE TABLE file_chunk (chunk_id VARCHAR(32) COMMENT '分片ID,主键',file_id VARCHAR(32) COMMENT '关联的文件ID',chunk_index INT COMMENT '分片序号,从0开始',chunk_size BIGINT COMMENT '分片大小,单位字节',chunk_path VARCHAR(255) COMMENT '分片在MinIO中的存储路径',upload_time DATETIME COMMENT '分片上传完成时间',PRIMARY KEY (chunk_id),INDEX idx_file_id (file_id) COMMENT '文件ID索引') COMMENT '文件分片信息表';
Redis存热点文件元数据设计
# 文件基本信息(Hash结构)key: file:info:{fileId}{fileName: "测试文档.pdf",fileSize: "1024000",fileType: "pdf",md5: "xxxxx",uploadStatus: "1",bucketName: "documents",objectName: "2024/01/测试文档.pdf"}# 文件分片上传进度(Hash结构)key: upload:{fileId}{chunk:0: "1",chunk:1: "1",chunk:2: "1"}# 热门文件访问计数(String结构)key: file:access:{fileId}value: 访问次数# 文件下载URL缓存(String结构)key: file:url:{fileId}value: 临时下载URL
代码实现示例
@Servicepublic class FileService {@Autowiredprivate RedisTemplate<String, Object> redisTemplate;@Autowiredprivate FileInfoMapper fileInfoMapper;// 保存文件信息public void saveFileInfo(FileInfo fileInfo) {// 1. 保存到MySQLfileInfoMapper.insert(fileInfo);// 2. 保存到Redis缓存String redisKey = "file:info:" + fileInfo.getFileId();Map<String, String> fileInfoMap = new HashMap<>();fileInfoMap.put("fileName", fileInfo.getFileName());fileInfoMap.put("fileSize", String.valueOf(fileInfo.getFileSize()));fileInfoMap.put("fileType", fileInfo.getFileType());redisTemplate.opsForHash().putAll(redisKey, fileInfoMap);// 设置过期时间redisTemplate.expire(redisKey, 24, TimeUnit.HOURS);}// 获取文件信息public FileInfo getFileInfo(String fileId) {// 1. 先从Redis获取String redisKey = "file:info:" + fileId;Map<Object, Object> fileInfoMap = redisTemplate.opsForHash().entries(redisKey);if (!fileInfoMap.isEmpty()) {// 更新访问计数redisTemplate.opsForValue().increment("file:access:" + fileId);return convertMapToFileInfo(fileInfoMap);}// 2. Redis没有,从MySQL获取FileInfo fileInfo = fileInfoMapper.selectById(fileId);if (fileInfo != null) {// 放入Redis缓存saveFileInfoToRedis(fileInfo);}return fileInfo;}// 更新上传进度public void updateUploadProgress(String fileId, int chunkIndex) {// 1. 更新Redis进度String uploadKey = "upload:" + fileId;redisTemplate.opsForHash().put(uploadKey, "chunk:" + chunkIndex, "1");// 2. 更新MySQL状态FileChunk fileChunk = new FileChunk();fileChunk.setFileId(fileId);fileChunk.setChunkIndex(chunkIndex);fileChunk.setUploadTime(new Date());fileChunkMapper.insert(fileChunk);}}
public CompletableFuture<String> uploadAsync(MultipartFile file) {String objectName = generateObjectName(file);minioClient.putObject(bucketName, objectName, file.getInputStream());return CompletableFuture.completedFuture(objectName);}
纠删码机制:
纠删码(Erasure Code)是一种数据保护机制,它的核心思想是将数据分片并生成校验数据,即使部分数据丢失也能通过剩余数据进行恢复。
举个简单的例子来说明:
优势:
在MinIO中的应用:
这就像是一本书的内容被分成多页,即使丢失几页,通过目录和其他页面的信息也能推算出丢失页面的内容,这就是纠删码的基本原理。
关键技术实现
// 分片上传实现public void multipartUpload(String fileId, MultipartFile file) {// 初始化分片上传String uploadId = minioClient.initiateMultipartUpload(bucket, fileId);// 分片上传List<CompletableFuture<PartETag>> uploadFutures = new ArrayList<>();int partNumber = 1;for(byte[] bytes : splitFile(file)) {uploadFutures.add(CompletableFuture.supplyAsync(() ->uploadPart(bucket, fileId, uploadId, partNumber, bytes)));partNumber++;}// 合并分片List<PartETag> partETags = uploadFutures.stream().map(CompletableFuture::join).collect(Collectors.toList());minioClient.completeMultipartUpload(bucket, fileId, uploadId, partETags);}
遇到的主要挑战和解决方案
大文件上传性能问题
分布式文件系统上线后效果:
我们以一个简单的员工报销流程为例:
业务规则:
@Servicepublic class ExpenseService {public void processExpense(String expenseId, String action, String operator) {Expense expense = expenseRepository.findById(expenseId);// 复杂的状态判断和处理逻辑if ("DRAFT".equals(expense.getStatus())) {if ("submit".equals(action)) {// 提交处理if (expense.getAmount() < 1000) {expense.setStatus("SUPERVISOR_REVIEW");expense.setCurrentApprover(getSupervisor(expense.getApplicant()));} else if (expense.getAmount() <= 5000) {expense.setStatus("MANAGER_REVIEW");expense.setCurrentApprover(getManager(expense.getApplicant()));} else {expense.setStatus("FINANCE_REVIEW");expense.setCurrentApprover(getFinanceManager());}}} else if ("SUPERVISOR_REVIEW".equals(expense.getStatus())) {if ("approve".equals(action)) {expense.setStatus("APPROVED");} else if ("reject".equals(action)) {expense.setStatus("REJECTED");}} else if ("MANAGER_REVIEW".equals(expense.getStatus())) {if ("approve".equals(action)) {expense.setStatus("APPROVED");} else if ("reject".equals(action)) {expense.setStatus("REJECTED");}} else if ("FINANCE_REVIEW".equals(expense.getStatus())) {if ("approve".equals(action)) {expense.setStatus("APPROVED");} else if ("reject".equals(action)) {expense.setStatus("REJECTED");}}// 更新数据expenseRepository.save(expense);}}
// 1. 状态定义public enum ExpenseState {DRAFT("草稿"),SUBMITTED("已提交"),SUPERVISOR_REVIEW("主管审批中"),MANAGER_REVIEW("经理审批中"),FINANCE_REVIEW("财务审批中"),APPROVED("已通过"),REJECTED("已拒绝"),CANCELED("已取消");private String description;}// 2. 报销上下文@Data@Builderpublic class ExpenseContext {private String expenseId;private ExpenseState state;private String applicant;private BigDecimal amount;private String description;private String currentApprover;private LocalDateTime createTime;private LocalDateTime updateTime;}// 3. 状态机实现@Service@Slf4jpublic class ExpenseStateMachine {@Autowiredprivate ExpenseRepository expenseRepository;@Autowiredprivate NotificationService notificationService;@Transactionalpublic void processStateTransition(String expenseId, String action) {ExpenseContext context = getExpenseContext(expenseId);handleStateTransition(context, action);saveContext(context);notifyRelevantUsers(context);}private void handleStateTransition(ExpenseContext context, String action) {switch (context.getState()) {case DRAFT:handleDraftState(context, action);break;case SUBMITTED:handleSubmittedState(context);break;case SUPERVISOR_REVIEW:case MANAGER_REVIEW:case FINANCE_REVIEW:handleReviewState(context, action);break;default:throw new IllegalStateException("非法的状态转换");}}private void handleDraftState(ExpenseContext context, String action) {if ("submit".equals(action)) {context.setState(ExpenseState.SUBMITTED);} else if ("cancel".equals(action)) {context.setState(ExpenseState.CANCELED);}}private void handleSubmittedState(ExpenseContext context) {// 根据金额确定下一个审批人if (context.getAmount().compareTo(new BigDecimal(1000)) < 0) {context.setState(ExpenseState.SUPERVISOR_REVIEW);context.setCurrentApprover(getSupervisor(context.getApplicant()));} else if (context.getAmount().compareTo(new BigDecimal(5000)) <= 0) {context.setState(ExpenseState.MANAGER_REVIEW);context.setCurrentApprover(getManager(context.getApplicant()));} else {context.setState(ExpenseState.FINANCE_REVIEW);context.setCurrentApprover(getFinanceManager());}}private void handleReviewState(ExpenseContext context, String action) {if ("approve".equals(action)) {context.setState(ExpenseState.APPROVED);// 触发报销发放triggerPayment(context);} else if ("reject".equals(action)) {context.setState(ExpenseState.REJECTED);}}}
使用状态机的优势:
实际效果:
这个简单的例子展示了即使在相对简单的业务场景中,使用状态机也能带来显著优势:
而且,随着业务复杂度增加(比如添加更多审批环节、特殊审批规则等),状态机的优势会更加明显。
我们在实现审批系统时,通过多个层面的优化措施实现了高性能和高并发:
具体性能提升来自以下几个方面:
性能数据:
这些优化措施整体提升了审批处理效率:
最终实现了审批处理效率提升200%,支持千级并发处理的目标。这些优化不仅提升了性能,还保证了系统的可靠性和稳定性。
通过上述优化,原本平淡无奇的OA办公系统项目变得亮点纷呈,不仅提升了简历的吸引力,还大幅增加了获得面试机会的可能性。这些优化不仅展示了技术深度和解决问题的能力,还体现了对系统性能、用户体验和安全性的全面考虑。
如果你也在准备简历或有类似的项目需要优化,希望这篇文章能给你带来一些启发和帮助。欢迎大家在评论区分享你的想法和经验:
原文作者:Fox爱分享;如有侵权,请联系管理员删除。