项目实践 之 pdf简历的解析和填充(若依+vue3)

news2025/2/28 16:43:14

文章目录

  • 环境背景
  • 最终效果
  • 前端讲解
    • 左侧模块解析
    • 右侧上传模块解析
    • 前端步骤
  • 后端讲解
  • 代码
    • 前端

环境背景

  • 若依前后端分离框架 + 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',
  })
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2307478.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

lowagie(itext)老版本手绘PDF,包含页码、水印、图片、复选框、复杂行列合并、行高设置等。

入口类&#xff1a;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的操作系统&#xff0c;由BackTrack(简称BT)发展而来。BackTrack是2006年推出的一个用于渗透测试及黑客攻防的专用平台&#xff0c;基于Knoppix(linux的一个发行版)开发。BackTrack版本周期&#xff1a;2006年的起始版本BackTrack …

软件工程复试专业课-软件生命周期

文章目录 软件过程模型瀑布模型模型图特点优缺点改进后的瀑布模型 快速原型模型模型图优缺点 增量模型&#xff08;迭代-递增模型&#xff09;原型图与瀑布和快速原型的区别优缺点风险更大的增量模型 螺旋模型简介模型图优缺点 喷泉模型模型图优缺点 编码修补模型敏捷过程优缺点…

DILLEMA:扩散模型+大语言模型,多模态数据增强框架

引言&#xff1a;深度学习模型的鲁棒性测试需要全面且多样化的测试数据。现有的方法通常基于简单的数据增强技术或生成对抗网络&#xff0c;但这些方法在生成真实且多样化的测试数据集方面存在局限性。为了克服这些限制&#xff0c;DILLEMA框架应运而生&#xff0c;旨在通过结合…

C++程序员内功修炼——Linux C/C++编程技术汇总

在软件开发的宏大版图中&#xff0c;C 语言宛如一座巍峨的高山&#xff0c;吸引着无数开发者攀登探索。而 Linux 操作系统&#xff0c;以其开源、稳定、高效的特性&#xff0c;成为了众多开发者钟爱的开发平台。将 C 与 Linux 相结合&#xff0c;就如同为开发者配备了一把无坚不…

苍穹外卖-阿里云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…

DeepSeek 提示词:常见指令类型

&#x1f9d1; 博主简介&#xff1a;CSDN博客专家&#xff0c;历代文学网&#xff08;PC端可以访问&#xff1a;https://literature.sinhy.com/#/?__c1000&#xff0c;移动端可微信小程序搜索“历代文学”&#xff09;总架构师&#xff0c;15年工作经验&#xff0c;精通Java编…

springai系列(二)从0开始搭建和接入azure-openai实现智能问答

文章目录 前言1.从0开始搭建项目2.进入微软openai申请key3.配置application.yaml4.编写controller5.测试源码下载地址总结 前言 之前使用openai的官网的api需要科学上网&#xff0c;但是我们可以使用其他的代理间接实现使用chatgpt的相关模型&#xff0c;解决这个问题。比如:本…

Go在1.22版本修复for循环陷阱

记录 前段时间升级Go版本碰到一个大坑&#xff0c;先记录。 先上代码案例&#xff1a; func main() {testClosure() }func testClosure() {for i : 0; i < 5; i {defer func() {fmt.Println(i)}()} }在1.22之下&#xff08;不包括1.22&#xff09;版本&#xff1a; 输出的…

可视化约瑟夫生死环小游戏

这是一个基于Tkinter的图形界面应用程序&#xff0c;用于模拟约瑟夫环问题。约瑟夫环问题是一个经典的数学问题&#xff0c;描述的是N个人围成一圈&#xff0c;从第一个人开始报数&#xff0c;每数到第M个人就将其淘汰&#xff0c;然后从下一个人继续报数&#xff0c;直到剩下最…

【深入理解JWT】从认证授权到网关安全

最近的项目学习中&#xff0c;在进行登陆模块的用户信息验证这一部分又用到了JWT的一些概念和相关知识&#xff0c;特在此写了这篇文章、方便各位笔者理解JWT相关概念 目录 先来理解JWT是什么&#xff1f; 区分有状态认证和无状态认证 有状态认证 VS 无状态认证 JWT令牌的…

学习路之PHP --TP6异步执行功能 (无需安装任何框架)

学习路之PHP --异步执行功能 &#xff08;无需安装任何框架&#xff09; 简介一、工具类二、调用三、异步任务的操作四、效果&#xff1a; 简介 执行异步任务是一种很常见的需求&#xff0c;如批量发邮箱&#xff0c;短信等等执行耗时任务时&#xff0c;需要程序异步执行&…

九、数据治理架构流程

一、总体结构 《数据治理架构流程图》&#xff08;Data Governance Architecture Flowchart&#xff09; 水平结构&#xff1a;流程图采用水平组织&#xff0c;显示从数据源到数据应用的进程。 垂直结构&#xff1a;每个水平部分进一步划分为垂直列&#xff0c;代表数据治理的…

【数据结构】 最大最小堆实现优先队列 python

堆的定义 堆&#xff08;Heap&#xff09;是一种特殊的完全二叉树结构&#xff0c;通常分为最大堆和最小堆两种类型。 在最大堆中&#xff0c;父节点的值总是大于或等于其子节点的值&#xff1b; 而在最小堆中&#xff0c;父节点的值总是小于或等于其子节点的值。 堆常用于实…

51c自动驾驶~合集52

我自己的原文哦~ https://blog.51cto.com/whaosoft/13383340 #世界模型如何推演未来的千万种可能 驾驶世界模型&#xff08;DWM&#xff09;&#xff0c;专注于预测驾驶过程中的场景演变&#xff0c;已经成为追求自动驾驶的一种有前景的范式。这些方法使自动驾驶系统能够更…

【我的 PWN 学习手札】House of Husk

House of Husk House of Husk是利用格式化输出函数如printf、vprintf在打印输出时&#xff0c;会解析格式化字符如%x、%lld从而调用不同的格式化打印方法&#xff08;函数&#xff09;。同时C语言还提供了注册自定义格式化字符的方法。注册自定义格式化字符串输出方法&#xf…

Nmap使用指南

Nmap使用指南 Nmap (网络映射器) 是一款强大的应用网络扫描和安全核查工具&#xff0c;适合于网络管理和安全专家。本文将介绍Nmap的基本使用方法&#xff0c;包括基本命令和常用功能。 1. 基本使用方式 Nmap的基本命令格式如下&#xff1a; nmap [选项] 目标地址目标地址 可…

傅里叶分析

傅里叶分析之掐死教程&#xff08;完整版&#xff09;更新于2014.06.06 要让读者在不看任何数学公式的情况下理解傅里叶分析。 傅里叶分析不仅仅是一个数学工具&#xff0c;更是一种可以彻底颠覆一个人以前世界观的思维模式。但不幸的是&#xff0c;傅里叶分析的公式看起来太复…

从零开始用react + tailwindcss + express + mongodb实现一个聊天程序(五) 实现登录功能

1.登录页面 完善登录页面 和注册差不多 直接copy signUpPage 内容 再稍微修改下 import { useState } from "react"; import { useAuthStore } from "../store/useAuthStore"; import { MessageSquare,Mail,Lock,Eye, EyeOff,Loader2} from "lucide…