项目实践 之 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文件
xml 复制代码
<dependency>
    <groupId>org.apache.pdfbox</groupId>
    <artifactId>pdfbox</artifactId>
    <version>2.0.26</version>
</dependency>
  • controller层
java 复制代码
@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类

java 复制代码
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;
    }
}

代码

前端

javascript 复制代码
<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内容
javascript 复制代码
// 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',
  })
}
相关推荐
码熔burning39 分钟前
(十)趣学设计模式 之 外观模式!
java·设计模式·外观模式
计算机毕设定制辅导-无忧学长1 小时前
Maven 生命周期与构建命令(一)
java·maven
老朋友此林1 小时前
浅析 Redis 分片集群 Cluster 原理、手动搭建、动态伸缩集群、故障转移
java·数据库·redis
CodeCaster1 小时前
他来了,为大模型量身定制的响应式编程范式(1) —— 从接入 DeepSeek 开始吧
java·ai·langchain
IT学长编程1 小时前
计算机毕业设计 基于SpringBoot的智慧社区管理系统的设计与实现 Java实战项目 附源码+文档+视频讲解
java·spring boot·后端·毕业设计·课程设计·论文笔记·1024程序员节
linkcoco1 小时前
记录h5使用navigator.mediaDevices.getUserMedia录制音视频
前端·javascript·vue·音视频·js
Pro_er1 小时前
Vue3组件通信全攻略:多种方式详解+实战场景,轻松玩转复杂数据流!
vue·前端开发
hadage2332 小时前
--- spring MVC ---
java·spring·mvc
m0_748255412 小时前
适用于IntelliJ IDEA 2024.1.2部署Tomcat的完整方法,以及笔者踩的坑,避免高血压,保姆级教程
java·tomcat·intellij-idea
奋斗的小方2 小时前
Springboot基础篇(3):Bean管理
java·spring boot·后端