文章目录
- 环境背景
- 最终效果
- 前端讲解
- 左侧模块解析
- 右侧上传模块解析
- 前端步骤
- 后端讲解
- 代码
- 前端
环境背景
- 若依前后端分离框架 + 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.DOTALL
Matcher 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-upload
ref="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(); // 获取当前用户的username
console.log(username);
if(!username){
this.$message.error('未获取到用户信息');
return;
}
const res = await updateResume(this.myResume);// 调用更新简历的API
const 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; // 使用返回的数据更新 myResume
this.uploadedFileName = this.myResume.file_name;// 显示已上传的文件名称
if(this.uploadedFileName){
this.upload.open = true;
}
} else {
console.error('获取简历数据失败:', response.msg);
}
} catch (error) {
console.error('请求失败:', error);
}
},
// 获取当前用户的username
getCurrentUsername(){
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.js
import 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',
})
}