项目实践 之 pdf简历的解析和填充(若依+vue3)
文章目录
- 环境背景
- 最终效果
- 前端讲解
- 左侧模块解析
- 右侧上传模块解析
- 前端步骤
- 后端讲解
- 代码
- 前端
环境背景
- 若依前后端分离框架 + vue
- 最后边附有代码哦
最终效果


前端讲解
左侧模块解析
- 1、左侧表单使用el-form
注意:
1、prop出现的字段,需要保证是该类所具有的字段
2、点击提交按钮后,调用的是handleSubmit方法
右侧上传模块解析

① v-if=“uploadedFileName” 如果对uploadedFileName不为空,该控件显示
② v-model=“upload.open” 在vue2中会写成 :visible.sync=“upload.open” ,在vue3中是不生效的,需要修改
③ 上传文件限制,只能上传1个
④ 前端限制,上传的文件只能是pdf
前端步骤
-
1、在打开页面时,通过 created() 的 this.fetchResumeData()来获取数据

-
2、fetchResumeData通过await getResumeByUsername(username)来调用js的方法然后获得数据,然后通过this.myResume=response.data填充

-
3、当点击上传简历按钮时,会调用handleImport方法,然后更改upload的open属性为true,这样就显示了上传文件的对话框了


-
4、文件上传完成后,会调用submitFileForm方法,开始上传,同时调用upload中的url进行文件解析



-
5、上传成功后,会调用handleFileSuccess方法,然后将内容填充


后端讲解
- pom文件
<dependency><groupId>org.apache.pdfbox</groupId><artifactId>pdfbox</artifactId><version>2.0.26</version>
</dependency>
- controller层
@RequestMapping("/parsepdf")public AjaxResult parsePdf(@RequestParam("file") MultipartFile file) {try {// 保存文件到本地String filePath = PdfUtil.save(file);// 获取 PDF 文件内容String content = PdfUtil.getContent(file.getInputStream());// 解析 PDF 内容并封装为简历信息Map<String, String> map = PdfUtil.setResume(content,file.getName(),filePath);// 返回解析后的数据return AjaxResult.success(map);} catch (Exception e) {System.err.println(e.getMessage());return AjaxResult.error("文件解析失败:" + e.getMessage());}}
-
pdf格式说明
需按照如下的格式,因为正则匹配的解析是这么来的,可以结合后边的正则函数查看

-
PdfUtil类
package com.ruoyi.utils;import com.ruoyi.common.config.RuoYiConfig;
import com.ruoyi.common.utils.file.FileUploadUtils;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.text.PDFTextStripper;
import org.springframework.web.multipart.MultipartFile;import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;public class PdfUtil {/*** 将上传的文档保存到本地* @param file* @return* @throws IOException*/public static String save(MultipartFile file) throws IOException{String path = FileUploadUtils.upload(file);// 为什么是e?String realPath = path.substring(path.indexOf("e")+2);String baseDir = RuoYiConfig.getProfile();String filePath = baseDir + realPath;return filePath;}public static String getContent(InputStream inputStream) throws IOException {try (PDDocument document = PDDocument.load(inputStream)) {PDFTextStripper stripper = new PDFTextStripper();return stripper.getText(document);}}/*** 将内容按照字段存储进行匹配* @param content* @return*/public static Map<String,String> setResume(String content,String fileName,String filePath){// map用来存储解析到的内容Map<String,String> map = new HashMap<>();map.put("file_name",fileName);map.put("file_path",filePath);String skillRegex ="专业技能\\s+(.*?)(?=工作经历|$)"; ; // "专业技能\r?\n([\s\S]+)"String skill = regex(skillRegex,content);System.err.println("--------------专业技能-------------");System.err.println("skills:"+skill);map.put("skills",skill);String phoneRegex = "联系方式:(\\d+) 邮箱:(\\S+)";String phone = regex(phoneRegex,content);System.err.println("--------------联系电话-------------");System.err.println("phone"+phone);map.put("phone",phone);String titleRegex = "求职意向\\s+(.*?)(?=简介|$)";String title = regex(titleRegex,content);System.err.println("--------------求职意向-------------");System.err.println("title"+title);map.put("title",title);String summaryRegex = "简介\\s+(.*?)(?=获奖及证书|$)";String summary = regex(summaryRegex,content);System.err.println("--------------简介即总结-------------");System.err.println("summary"+summary);map.put("summary",summary);String experienceRegex = "工作经历\\s+(.*?)(?=工作项目经历|$)";// "工作项目经历\\r?\\n([\\s\\S]+)"String experience = regex(experienceRegex,content);System.err.println("--------------工作项目经历-------------");System.err.println("experience"+experience);map.put("experience",experience);String projectRegex = "工作项目经历\\s+(.*)";// "工作项目经历\\r?\\n([\\s\\S]+)"String project = regex(projectRegex,content);System.err.println("--------------工作项目经历-------------");System.err.println("content"+project);map.put("content",project);String educationRegex = "教育经历\\s+(.*)"; // "< < < 个人信息\\s*(.*?)(?=< < < 教育背景)"String education = regex(educationRegex,content);System.err.println("--------------教育背景-------------");System.err.println("education"+education);map.put("education",education);String certificationRegex = "获奖及证书\\s+(.*?)(?=专业技能|$)";String certification = regex(certificationRegex,content);System.err.println("--------------获奖及证书-------------");System.err.println("certifications"+certification);map.put("certifications",certification);return map;}/*** 匹配规则* @param regex 匹配要求* @param content 需要匹配的内容* @return 匹配结果*/public static String regex(String regex,String content){Pattern pattern=Pattern.compile(regex,Pattern.DOTALL);// 如果想要获取多行,这里一定添加的是Pattern.DOTALLMatcher matcher=pattern.matcher(content);if(matcher.find()){String data=matcher.group(1).trim();return data;}return null;}
}
代码
前端
<template><div class="container"><div class="my-myResume"><!--简历编辑页面--><el-form :model="myResume" ref="resumeForm" label-width="120px" class="myResume-form"><el-form-item label="求职意向" prop="title"><el-input v-model="myResume.title" placeholder="请输入简历标题"></el-input></el-form-item><el-form-item label="联系方式A" prop="summary"><el-input type="textarea" v-model="myResume.phone" placeholder="请输入您的手机号码"></el-input></el-form-item><el-form-item label="个人介绍" prop="summary"><el-input type="textarea" v-model="myResume.summary" placeholder="请输入个人介绍"></el-input></el-form-item><el-form-item label="工作经历" prop="experience"><el-input type="textarea" v-model="myResume.experience" placeholder="请输入工作经历"></el-input></el-form-item><el-form-item label="工作项目经历" prop="content"><el-input type="textarea" v-model="myResume.content" placeholder="请输入工作项目经历"></el-input></el-form-item><el-form-item label="教育经历" prop="education"><el-input type="textarea" v-model="myResume.education" placeholder="请输入教育经历"></el-input></el-form-item><el-form-item label="专业技能" prop="skills"><el-input type="textarea" v-model="myResume.skills" placeholder="请输入专业技能"></el-input></el-form-item><el-form-item label="获奖及证书" prop="certifications"><el-input type="textarea" v-model="myResume.certifications" placeholder="请输入获得的认证"></el-input></el-form-item><el-button type="primary" @click="handleSubmit">提交</el-button></el-form></div><div class="pdfModule"><!-- 上传PDF按钮--><el-button type="primary" @click="handleImport">上传PDF简历</el-button><div v-if="uploadedFileName" class="file-name">已上传文件:{{ uploadedFileName }}</div><!-- 上传对话框--><el-dialog :title="upload.title" v-model="upload.open" width="400px" append-to-body="true"><el-uploadref="upload":limit="1"accept=".pdf":headers="upload.headers":action="upload.url":disabled="upload.isUploading":on-progress="handleFileUploadProgress":on-success="handleFileSuccess":auto-upload="false"><i class="el-icon-upload"></i><div class="el-upload__text">将文件拖到此处,或 <em>点击上传</em></div><div class="el-upload__tip text-center" slot="tip"><span>仅允许导入pdf格式文件</span></div></el-upload><div slot="footer" class="dialog-footer"><el-button type="primary" @click="submitFileForm">确定</el-button><el-button @click="upload.open = false">取消</el-button></div></el-dialog></div></div>
</template><script>
import {ElForm, ElFormItem, ElInput, ElButton, ElMessage} from 'element-plus';
import {updateResume, getResumeByUsername, uploadResume, getUserIdByUsername} from '@/api/myResume/myResume'; // 更新API路径import Cookies from 'js-cookie'import axios from 'axios';import {getToken} from "@/utils/auth.js";export default {name: 'MyResume',data() {return {// 初始化简历对象myResume: {resume_id: null, //初始化为null,后续从当前用户获取title: '',phone:'',summary: '',experience: '',content:'',education: '',skills: '',certifications: '',file_name:'',file_path:'',},// 简历导入参数upload:{open:false,title:"上传PDF简历",isUploading:false,headers:{ Authorization:"Bearer "+getToken()},// url:process.env.VUE_APP_BASE_API+"/resumes/resume/import"url:"http://localhost:8088/student/myResume/parsepdf"},uploadedFileName:'', // 存储上传的文件名称};},methods: {// 导入按钮操作/*** 打开上传对话框*/handleImport(){this.upload.title ="上传PDF简历";this.upload.open = true;},/*** 文件上传中处理*/handleFileUploadProgress(event,file,fileList){this.upload.isUploading = true;console.log("文件上传中", event, file, fileList);},/*** 文件上传成功处理*/async handleFileSuccess(response,file){this.upload.open = false;this.upload.isUploading = false;this.$refs.upload.clearFiles();if(response.code===200){this.fillFormWithPDFData(response.data);// 将解析的数据填充到表单中this.uploadedFileName = file.name; //显示上传的文件名称ElMessage.success('文件上传成功');}else{ElMessage.error('文件解析失败');}},/*** 提交上传的文件*/submitFileForm(){console.log("上传接口 URL:", this.upload.url); // 调试日志this.$refs.upload.submit();},// 将解析的PDF数据填充到表单中fillFormWithPDFData(data) {this.myResume.title = data.title || '';this.myResume.phone = data.phone || '';this.myResume.summary = data.summary || '';this.myResume.experience = data.experience || '';this.myResume.education = data.education || '';this.myResume.skills = data.skills || '';this.myResume.certifications = data.certifications || '';this.myResume.content = data.content || '';this.myResume.file_name = data.file_name || '';this.myResume.file_path = data.file_path || '';},// 提交表单更新简历async handleSubmit() {try {const username = this.getCurrentUsername(); // 获取当前用户的usernameconsole.log(username);if(!username){this.$message.error('未获取到用户信息');return;}const res = await updateResume(this.myResume);// 调用更新简历的APIconst userId = await getUserIdByUsername(username);console.log(userId);// const res = await axios.post(url, this.myResume);if (res.code === 200) {ElMessage.success('简历更新成功');} else {ElMessage.success('简历更新失败');}} catch (error) {console.error('提交失败:', error);ElMessage.success('简历更新失败');}},// 获取简历数据 (初始加载)async fetchResumeData() {try {const username = await this.getCurrentUsername();const response = await getResumeByUsername(username); // 调用获取简历数据的方法if (response.code === 200) {this.myResume = response.data; // 使用返回的数据更新 myResumethis.uploadedFileName = this.myResume.file_name;// 显示已上传的文件名称if(this.uploadedFileName){this.upload.open = true;}} else {console.error('获取简历数据失败:', response.msg);}} catch (error) {console.error('请求失败:', error);}},// 获取当前用户的usernamegetCurrentUsername(){const name = Cookies.get('username');return name;// return this.$store.state.user.userId;// return localStorage.getItem('userId');}},created() {this.fetchResumeData(); // 页面加载时获取简历数据}};
</script><style scoped>/* 容器布局 */
.container {display: flex;width: 100%;height: 100vh; /* 使容器占满整个视口高度 */background-color: #f9f9f9; /* 浅灰色背景 */
}/* 简历编辑区域 */
.my-myResume {flex: 7; /* 占据 4 份 */padding: 20px;overflow-y: auto; /* 如果内容过多,允许滚动 */
}/* PDF上传区域 */
.pdfModule {flex: 2; /* 占据 1 份 */padding: 20px;/*border-left: 1px solid #ddd; !* 添加左边框分隔 *!*/
}/* 表单样式 */
.myResume-form {max-width: 800px; /* 限制表单最大宽度 */margin: 0 auto; /* 居中显示 */
}.file-name{margin-top: 10px;font-size: 14px;color:#666;
}.el-upload__text {font-size: 14px;color: #666;
}.el-upload__tip {font-size: 12px;color: #999;
}.dialog-footer {text-align: right;
}
</style>
- js内容
// src/myResume/myResume.jsimport request from '@/utils/request'
// 根据username获取userId
export function getUserIdByUsername(username){return request({url:`/student/myResume/getUserId/${username}`,method:'get'})
}
// 更新简历数据
export function updateResume(data) {return request({url: '/student/myResume/updateResume',method: 'put',data: data})
}// 根据username获取简历数据
export function getResumeByUsername(username) {return request({url: `/student/myResume/getByUsername/${username}`,method: 'get',})
}
相关文章:
项目实践 之 pdf简历的解析和填充(若依+vue3)
文章目录 环境背景最终效果前端讲解左侧模块解析右侧上传模块解析前端步骤 后端讲解代码前端 环境背景 若依前后端分离框架 vue最后边附有代码哦 最终效果 前端讲解 左侧模块解析 1、左侧表单使用el-form 注意: 1、prop出现的字段,需要保证是该类所…...
C语言机试编程题
编写版本:vc2022 1.求最大/小值 #include<stdio.h> int main(){int a[50],n;int max, min;printf("请输入您要输入几个数");scanf_s("%d", &n);printf("请输入您要比较的%d个数\n",n);for (int i 0; i<n; i) {scanf_…...
lowagie(itext)老版本手绘PDF,包含页码、水印、图片、复选框、复杂行列合并、行高设置等。
入口类:exportPdf package xcsy.qms.webapi.service;import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONObject; import com.alibaba.nacos.common.utils.StringUtils; import com.ibm.icu.text.RuleBasedNumberFormat; import com.lowagie…...
第002文-kali虚拟机安全与网络配置
1、kali系统介绍 kali是一个基于Linux kernel的操作系统,由BackTrack(简称BT)发展而来。BackTrack是2006年推出的一个用于渗透测试及黑客攻防的专用平台,基于Knoppix(linux的一个发行版)开发。BackTrack版本周期:2006年的起始版本BackTrack …...
软件工程复试专业课-软件生命周期
文章目录 软件过程模型瀑布模型模型图特点优缺点改进后的瀑布模型 快速原型模型模型图优缺点 增量模型(迭代-递增模型)原型图与瀑布和快速原型的区别优缺点风险更大的增量模型 螺旋模型简介模型图优缺点 喷泉模型模型图优缺点 编码修补模型敏捷过程优缺点…...
DILLEMA:扩散模型+大语言模型,多模态数据增强框架
引言:深度学习模型的鲁棒性测试需要全面且多样化的测试数据。现有的方法通常基于简单的数据增强技术或生成对抗网络,但这些方法在生成真实且多样化的测试数据集方面存在局限性。为了克服这些限制,DILLEMA框架应运而生,旨在通过结合…...
OpenBMC:BmcWeb app获取socket
OpenBMC:BmcWeb app.run-CSDN博客 app对象在run函数中调用了setupSocket() static std::vector<Acceptor> setupSocket() {std::vector<Acceptor> acceptors;char** names = nullptr;int listenFdCount = sd_listen_fds_with_names(0, &names);BMCWEB_LOG_DE…...
突破加速度计的精度与量程瓶颈:HEROS-GAN技术
在当今科技飞速发展的背景下,低成本传感器的应用范围日益扩大。然而,低成本加速度计由于其固有的限制——如信号噪声显著和动态范围狭窄——往往难以满足高精度应用场景的需求。哈尔滨工业大学的研究团队最近提出了一项名为HEROS-GAN(Honed-E…...
C++程序员内功修炼——Linux C/C++编程技术汇总
在软件开发的宏大版图中,C 语言宛如一座巍峨的高山,吸引着无数开发者攀登探索。而 Linux 操作系统,以其开源、稳定、高效的特性,成为了众多开发者钟爱的开发平台。将 C 与 Linux 相结合,就如同为开发者配备了一把无坚不…...
苍穹外卖-阿里云OSS文件上传
苍穹外卖-阿里云OSS文件上传 一、阿里云OSS简介**获取AccessKey**获取enpoint 二、代码实现1 引入依赖2 定义OSS相关配置2.1 application-dev.yml2.2 application.yml 3 读取OSS配置3.1 AliOssProperties 4 生成OSS工具类对象4.1 AliOssUtil4.2 OssConfiguration2.5 CommonCont…...
C# 中 Array、ArrayList 和 List 的比较
C# 中 Array、ArrayList 和 List 的比较 在 C# 中,Array、ArrayList 和 List<T> 都用于存储和管理数据集合,但它们在类型安全性、性能、灵活性和使用场景上存在显著差异。以下是对这三种集合的详细比较: 1. 概述 特性ArrayArrayList…...
DeepSeek 提示词:常见指令类型
🧑 博主简介:CSDN博客专家,历代文学网(PC端可以访问:https://literature.sinhy.com/#/?__c1000,移动端可微信小程序搜索“历代文学”)总架构师,15年工作经验,精通Java编…...
商业AI模型新篇章:Granite 3.2引领行业创新
摘要 Granite 3.2 是一款专为商业环境设计的人工智能模型,旨在提高AI驱动应用的信任度和可扩展性。该模型从零开始构建,采用最新的密集型架构,支持12种语言,覆盖11个不同领域。通过其先进的技术,Granite 3.2 不仅增强了…...
Spring boot中的@ConfigurationProperties注解
Spring boot中的ConfigurationProperties注解 ConfigurationProperties 是 Spring Boot 提供的一个强大注解,用于将配置文件(如 application.properties 或 application.yml)中的配置绑定到一个 Java 对象中。它不仅简化了配置管理ÿ…...
Rust中的异步编程:构建简单的网页爬虫
一、什么是Rust中的Futures和Async? 在Rust中,异步编程基于future(未来)的概念。一个future表示一个当前可能不可用,但将来某个时候可以获得的值。Rust中的Future特征定义了这一概念,任何实现了该特征的类…...
springai系列(二)从0开始搭建和接入azure-openai实现智能问答
文章目录 前言1.从0开始搭建项目2.进入微软openai申请key3.配置application.yaml4.编写controller5.测试源码下载地址总结 前言 之前使用openai的官网的api需要科学上网,但是我们可以使用其他的代理间接实现使用chatgpt的相关模型,解决这个问题。比如:本…...
flutter 局部刷新控件Selector源码实现原理
Flutter 中的 Selector 组件是 provider 包提供的一个优化工具,用于在状态管理中仅选择所需数据片段,避免不必要的 Widget 重建。其实现原理基于以下几个关键点: 1. 核心设计目标 选择性重建:仅当特定数据变化时触发 Widget 重建&…...
Eclipse 编译项目指南
Eclipse 编译项目指南 引言 Eclipse 是一款功能强大的集成开发环境(IDE),广泛用于Java、C/C、Python等多种编程语言的开发。在Eclipse中编译项目是进行软件开发的基础步骤。本文将详细介绍如何在Eclipse中编译项目,包括项目设置…...
Go在1.22版本修复for循环陷阱
记录 前段时间升级Go版本碰到一个大坑,先记录。 先上代码案例: func main() {testClosure() }func testClosure() {for i : 0; i < 5; i {defer func() {fmt.Println(i)}()} }在1.22之下(不包括1.22)版本: 输出的…...
c++_sort函数
sort介绍 在C/C中,要想应用排序算法,可以使用c语言的qsort,也可以使用c的sort 。 1)qsort 是 C 标准库提供的一个通用排序函数,位于 stdlib.h 头文件中。 qsort 适用于 C 语言中的数组。 2)sort 是 C 中STL的泛型算法…...
【Stable Diffusion】AnimatedDiff--AI动画 插件使用技巧分享;文生视频、图生视频、AI生成视频工具;
本专栏主要记录人工智能的应用方面的内容,包括chatGPT、DeepSeek、AI绘画等等; 在当今AI的热潮下,不学习AI,就要被AI淘汰;所以欢迎小伙伴加入本专栏和我一起探索AI的应用,通过AI来帮助自己提升生产力; 本文的目标就是让每一个读者,都能学会并掌握AnimateDiff的使用;成…...
可视化约瑟夫生死环小游戏
这是一个基于Tkinter的图形界面应用程序,用于模拟约瑟夫环问题。约瑟夫环问题是一个经典的数学问题,描述的是N个人围成一圈,从第一个人开始报数,每数到第M个人就将其淘汰,然后从下一个人继续报数,直到剩下最…...
【深入理解JWT】从认证授权到网关安全
最近的项目学习中,在进行登陆模块的用户信息验证这一部分又用到了JWT的一些概念和相关知识,特在此写了这篇文章、方便各位笔者理解JWT相关概念 目录 先来理解JWT是什么? 区分有状态认证和无状态认证 有状态认证 VS 无状态认证 JWT令牌的…...
学习路之PHP --TP6异步执行功能 (无需安装任何框架)
学习路之PHP --异步执行功能 (无需安装任何框架) 简介一、工具类二、调用三、异步任务的操作四、效果: 简介 执行异步任务是一种很常见的需求,如批量发邮箱,短信等等执行耗时任务时,需要程序异步执行&…...
DeepSeek-R1:GPU编程自动化加速的新纪元
摘要 DeepSeek-R1是由斯坦福大学和普林斯顿大学研究者共同开发的项目,其自研的CUDA核心在性能测试中取得了卓越成绩,超越了o1和Claude 3.5 Sonnet,位居榜首。尽管DeepSeek-R1目前仅在约20%的任务中实现了对PyTorch Eager模式的性能超越&#…...
CSS 对齐:深入理解与技巧实践
CSS 对齐:深入理解与技巧实践 引言 在网页设计中,元素的对齐是至关重要的。一个页面中元素的对齐方式直接影响到页面的美观度和用户体验。CSS 提供了丰富的对齐属性,使得开发者可以轻松实现各种对齐效果。本文将深入探讨 CSS 对齐的原理、方法和技巧,帮助开发者更好地掌握…...
vue深拷贝:1、使用JSON.parse()和JSON.stringify();2、使用Lodash库;3、使用深拷贝函数(采用递归的方式)
文章目录 引言三种方法的优缺点在Vue中,实现数组的深拷贝I JSON.stringify和 JSON.parse的小技巧深拷贝步骤缺点:案例1:向后端请求路由数据案例2: 表单数据处理时复制用户输入的数据II 使用Lodash库步骤适用于复杂数据结构和需要处理循环引用的场景III 自定义的深拷贝函数(…...
九、数据治理架构流程
一、总体结构 《数据治理架构流程图》(Data Governance Architecture Flowchart) 水平结构:流程图采用水平组织,显示从数据源到数据应用的进程。 垂直结构:每个水平部分进一步划分为垂直列,代表数据治理的…...
【数据结构】 最大最小堆实现优先队列 python
堆的定义 堆(Heap)是一种特殊的完全二叉树结构,通常分为最大堆和最小堆两种类型。 在最大堆中,父节点的值总是大于或等于其子节点的值; 而在最小堆中,父节点的值总是小于或等于其子节点的值。 堆常用于实…...
51c自动驾驶~合集52
我自己的原文哦~ https://blog.51cto.com/whaosoft/13383340 #世界模型如何推演未来的千万种可能 驾驶世界模型(DWM),专注于预测驾驶过程中的场景演变,已经成为追求自动驾驶的一种有前景的范式。这些方法使自动驾驶系统能够更…...
