From 5e3ec6ddaeaa9af0dc2ba032eac57f9be83c1b6b Mon Sep 17 00:00:00 2001 From: sh Date: Wed, 21 Jan 2026 15:35:15 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E5=B2=97=E4=BD=8D=E6=A8=A1?= =?UTF-8?q?=E6=9D=BF=E4=B8=8B=E8=BD=BD,=E5=B2=97=E4=BD=8D=E6=A8=A1?= =?UTF-8?q?=E6=9D=BF=E4=B8=8A=E4=BC=A0=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cms/controller/cms/CmsJobController.java | 83 +++++++++ .../com/ruoyi/cms/domain/vo/JobExcelVo.java | 97 ++++++++++ .../com/ruoyi/cms/mapper/CompanyMapper.java | 2 + .../java/com/ruoyi/cms/mapper/JobMapper.java | 2 + .../com/ruoyi/cms/service/IJobService.java | 3 + .../ruoyi/cms/service/JobContactService.java | 2 + .../service/impl/JobContactServiceImpl.java | 5 + .../cms/service/impl/JobServiceImpl.java | 166 +++++++++++++++++- .../resources/mapper/app/CompanyMapper.xml | 10 +- .../main/resources/mapper/app/JobMapper.xml | 23 ++- 10 files changed, 388 insertions(+), 5 deletions(-) create mode 100644 ruoyi-bussiness/src/main/java/com/ruoyi/cms/domain/vo/JobExcelVo.java diff --git a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/controller/cms/CmsJobController.java b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/controller/cms/CmsJobController.java index f7d4403..df4d17f 100644 --- a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/controller/cms/CmsJobController.java +++ b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/controller/cms/CmsJobController.java @@ -5,7 +5,9 @@ import com.ruoyi.cms.domain.*; import com.ruoyi.cms.domain.query.ESJobSearch; import com.ruoyi.cms.domain.vo.CandidateVO; import com.ruoyi.cms.domain.vo.CompanyVo; +import com.ruoyi.cms.domain.vo.JobExcelVo; import com.ruoyi.cms.service.*; +import com.ruoyi.cms.util.EasyExcelUtils; import com.ruoyi.cms.util.RoleUtils; import com.ruoyi.cms.util.StringUtil; import com.ruoyi.cms.util.sensitiveWord.SensitiveWordChecker; @@ -24,12 +26,15 @@ import com.ruoyi.common.utils.bean.BeanUtils; import com.ruoyi.common.utils.poi.ExcelUtil; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; +import org.apache.commons.collections4.CollectionUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import java.io.*; import java.util.ArrayList; import java.util.Date; import java.util.List; @@ -358,4 +363,82 @@ public class CmsJobController extends BaseController } return AjaxResult.success("此岗位已存在!"); } + + /** + * 通用上传请求(单个) + */ + @PostMapping("/uploadFile") + @ApiOperation("岗位批量上传") + public AjaxResult uploadFile(@RequestParam("file") MultipartFile file) throws Exception { + if (file.isEmpty()) { + return AjaxResult.error("上传文件不能为空"); + } + String fileName = file.getOriginalFilename(); + if (fileName == null || !fileName.endsWith(".xlsx") && !fileName.endsWith(".xls")) { + return AjaxResult.error("请上传Excel格式的文件"); + } + + try (InputStream inputStream = file.getInputStream()){ + List allExcelVoList = new ArrayList<>(); + EasyExcelUtils.readExcelByBatch(inputStream, JobExcelVo.class, 100, list -> { + allExcelVoList.addAll(list); + }); + if (CollectionUtils.isEmpty(allExcelVoList)) { + throw new Exception("Excel文件中无有效数据"); + } + jobService.uploadFileJob(allExcelVoList); + return AjaxResult.success("已上传!"); + } catch (Exception e) { + e.printStackTrace(); + return AjaxResult.error(e.getMessage()); + } + } + + /** + * 模板下载 + * @param request + * @param response + * @throws Exception + */ + @PostMapping("/downloadModel") + public void downloadModel(HttpServletRequest request, HttpServletResponse response)throws Exception{ + String name = "模板.xlsx"; + String pathFile="/data/downloadmodel/"+name; + File url = new File(pathFile); + + String resMsg = ""; + try { + request.setCharacterEncoding("utf-8"); + } catch (UnsupportedEncodingException e1) { + e1.printStackTrace(); + } + try { + name = new String(name.getBytes("gb2312"), "ISO8859-1"); + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + } + response.reset(); + + response.setContentType("application/octet-stream"); + response.setHeader("Content-Disposition", "attachment; filename="+ name); + response.setHeader("Pragma", "public"); + response.setHeader("Cache-Control", "max-age=0"); + InputStream in = null; + try { + in = new FileInputStream(url); + } catch (FileNotFoundException e1) { + resMsg = "文件未找到"; + e1.printStackTrace(); + response.getWriter().write(resMsg + ":" + name); + } + OutputStream ou = response.getOutputStream(); + byte[] buffer = new byte[1024]; + int i = -1; + while ((i = in.read(buffer)) != -1) { + ou.write(buffer, 0, i); + } + ou.flush(); + ou.close(); + in.close(); + } } diff --git a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/domain/vo/JobExcelVo.java b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/domain/vo/JobExcelVo.java new file mode 100644 index 0000000..b7d1b74 --- /dev/null +++ b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/domain/vo/JobExcelVo.java @@ -0,0 +1,97 @@ +package com.ruoyi.cms.domain.vo; + +import com.alibaba.excel.annotation.ExcelProperty; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.ruoyi.common.annotation.Excel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +/** + * 岗位对象 job + */ +@Data +public class JobExcelVo +{ + + @ExcelProperty(value = "职位名称", index = 0) + @Excel(name = "职位名称") + @ApiModelProperty("职位名称") + private String jobTitle; + + @ExcelProperty(value = "职位名称", index = 1) + @Excel(name = "最小薪资", readConverterExp = "元=") + @ApiModelProperty("最小薪资(元)") + private Long minSalary; + + @ExcelProperty(value = "职位名称", index = 2) + @Excel(name = "最大薪资", readConverterExp = "元=") + @ApiModelProperty("最大薪资(元)") + private Long maxSalary; + + @ExcelProperty(value = "职位名称", index = 3) + @Excel(name = "学历要求 对应字典education") + @ApiModelProperty("学历要求 对应字典education") + private String education; + + @ExcelProperty(value = "职位名称", index = 4) + @Excel(name = "工作经验要求 对应字典experience") + @ApiModelProperty("工作经验要求 对应字典experience") + private String experience; + + @ExcelProperty(value = "职位名称", index = 5) + @Excel(name = "用人单位名称") + @ApiModelProperty("用人单位名称") + private String companyName; + + @ExcelProperty(value = "职位名称", index = 6) + @Excel(name = "招聘人数") + @ApiModelProperty("招聘人数") + private Long vacancies; + + @ExcelProperty(value = "职位名称", index = 7) + @ApiModelProperty("岗位描述") + private String description; + + @ExcelProperty(value = "职位名称", index = 8) + @ApiModelProperty("岗位分类") + private String jobCategory; + + @ExcelProperty(value = "职位名称", index = 9) + @ApiModelProperty("岗位类型 0疆内 1疆外") + private String jobType; + + @ExcelProperty(value = "职位名称", index = 10) + @ApiModelProperty("类型 0常规岗位 1就业见习岗位 2实习实训岗位 3社区实践岗位 4零工 对应字段字典position_type") + private String type; + + @ExcelProperty(value = "职位名称", index = 11) + @ApiModelProperty("工作地点") + private String jobAddress; + + @ExcelProperty(value = "职位名称", index = 12) + @Excel(name = "岗位区划") + @ApiModelProperty("岗位区划") + private String jobLocation; + + @ExcelProperty(value = "联系人", index = 13) + @ApiModelProperty("联系人") + private String contactPerson; + + @ExcelProperty(value = "联系人电话", index = 14) + @ApiModelProperty("联系人电话") + private String contactPersonPhone; + + @ApiModelProperty("是否发布 0未发布 1发布") + private Integer isPublish; + + @ApiModelProperty("工作地点区县字典代码") + private Integer jobLocationAreaCode; + + @JsonFormat(pattern = "yyyy-MM-dd") + @Excel(name = "发布时间", width = 30, dateFormat = "yyyy-MM-dd") + @ApiModelProperty("发布时间") + private String postingDate; + + @ApiModelProperty("数据来源") + private String dataSource; +} diff --git a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/mapper/CompanyMapper.java b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/mapper/CompanyMapper.java index 8c168f9..2e82c2f 100644 --- a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/mapper/CompanyMapper.java +++ b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/mapper/CompanyMapper.java @@ -29,4 +29,6 @@ public interface CompanyMapper extends BaseMapper List selectLikeCompanyList(Company company); Company selectCompanyByJobId(Long jobId); + + List selectByNames(List list); } diff --git a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/mapper/JobMapper.java b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/mapper/JobMapper.java index f5951db..7bf71cb 100644 --- a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/mapper/JobMapper.java +++ b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/mapper/JobMapper.java @@ -60,4 +60,6 @@ public interface JobMapper extends BaseMapper Job getJobInfo(Long jobId); Integer getTotals(Job job); + + void updateFileBatchInsert(List list); } diff --git a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/service/IJobService.java b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/service/IJobService.java index 7c95b89..76627e1 100644 --- a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/service/IJobService.java +++ b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/service/IJobService.java @@ -6,6 +6,7 @@ import com.ruoyi.cms.domain.ESJobDocument; import com.ruoyi.cms.domain.Job; import com.ruoyi.cms.domain.query.ESJobSearch; import com.ruoyi.cms.domain.vo.CandidateVO; +import com.ruoyi.cms.domain.vo.JobExcelVo; import com.ruoyi.common.core.domain.entity.AppUser; import org.dromara.easyes.core.biz.EsPageInfo; @@ -110,4 +111,6 @@ public interface IJobService * @return */ public Integer getTotals(Job job); + + void uploadFileJob(List list); } diff --git a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/service/JobContactService.java b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/service/JobContactService.java index 2547f5b..79d3a79 100644 --- a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/service/JobContactService.java +++ b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/service/JobContactService.java @@ -14,5 +14,7 @@ import java.util.List; public interface JobContactService{ List getSelectList(JobContact jobContact); + + int batchInsert(List list); } diff --git a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/service/impl/JobContactServiceImpl.java b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/service/impl/JobContactServiceImpl.java index 7e7cc1d..f07f138 100644 --- a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/service/impl/JobContactServiceImpl.java +++ b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/service/impl/JobContactServiceImpl.java @@ -18,4 +18,9 @@ public class JobContactServiceImpl extends ServiceImpl getSelectList(JobContact jobContact){ return jobContactMapper.getSelectList(jobContact); } + + @Override + public int batchInsert(List list) { + return jobContactMapper.batchInsert(list); + } } diff --git a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/service/impl/JobServiceImpl.java b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/service/impl/JobServiceImpl.java index bfc4ab6..5fb00f3 100644 --- a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/service/impl/JobServiceImpl.java +++ b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/service/impl/JobServiceImpl.java @@ -1,7 +1,5 @@ package com.ruoyi.cms.service.impl; -import cn.hutool.http.HttpUtil; -import cn.hutool.json.JSONUtil; import com.alibaba.fastjson2.JSONArray; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; @@ -10,6 +8,7 @@ import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; import com.ruoyi.cms.domain.*; +import com.ruoyi.cms.domain.vo.JobExcelVo; import com.ruoyi.cms.util.notice.NoticeUtils; import com.ruoyi.common.core.domain.entity.File; import com.ruoyi.cms.domain.query.ESJobSearch; @@ -25,6 +24,7 @@ import com.ruoyi.common.core.domain.entity.Company; import com.ruoyi.common.core.domain.entity.JobTitle; import com.ruoyi.common.core.redis.RedisCache; import com.ruoyi.common.exception.ServiceException; +import com.ruoyi.common.utils.DateUtils; import com.ruoyi.common.utils.SecurityUtils; import com.ruoyi.common.utils.SiteSecurityUtils; import com.ruoyi.common.utils.StringUtils; @@ -1137,4 +1137,166 @@ public class JobServiceImpl extends ServiceImpl implements IJobSe public Integer getTotals(Job job) { return jobMapper.getTotals(job); } + + @Override + @Transactional(rollbackFor = Exception.class) + public void uploadFileJob(List list) { + //查询所有企业 + Map companyNameToIdMap = companyNameToIdMap(list); + //岗位字典转换 + List jobList = buildJobList(list, companyNameToIdMap); + //岗位去重 + List dedupedJobList = dedupJobList(jobList); + //批量保存岗 + jobMapper.updateFileBatchInsert(dedupedJobList); + //构建联系人列表(关联去重后的岗位) + List contactList = buildJobContactList(dedupedJobList, list, companyNameToIdMap); + //联系人去重(按:企业ID+联系人手机号 去重;企业ID为0/null时,仅按手机号去重) + List dedupedContactList = dedupContactList(contactList, dedupedJobList); + //批量保存联系人 + if (CollectionUtils.isNotEmpty(dedupedContactList)) { + jobContactMapper.batchInsert(dedupedContactList); + } + } + + /** + * 企业名称查询关联(核心业务逻辑) + */ + private Map companyNameToIdMap(List allExcelVoList) { + Set companyNameSet = allExcelVoList.stream() + .map(JobExcelVo::getCompanyName) + .filter(StringUtils::hasText) + .collect(Collectors.toSet()); + + if (companyNameSet.isEmpty()) { + return Collections.emptyMap(); + } + + List companies = companyMapper.selectByNames(new ArrayList<>(companyNameSet)); + if (CollectionUtils.isEmpty(companies)) { + return Collections.emptyMap(); + } + return companies.stream() + .collect(Collectors.toMap( + Company::getName, + Company::getCompanyId, + (v1, v2) -> v1 + )); + } + + /** + * 字典转换 + Job对象构建(业务数据转换) + */ + private List buildJobList(List allExcelVoList, Map companyNameToIdMap) { + return allExcelVoList.stream().map(it -> { + Job job = new Job(); + BeanUtils.copyProperties(it, job); + //字典转换 + String education = DictUtils.getDictValue("education", it.getEducation()); + String experience = DictUtils.getDictValue("experience", it.getExperience()); + String jobType = DictUtils.getDictValue("job_type", it.getJobType()); + String type = DictUtils.getDictValue("position_type", it.getType()); + String areaCode = DictUtils.getDictValue("area", it.getJobLocation()); + // 字段赋值 + job.setEducation(education); + job.setExperience(experience); + job.setJobType(jobType); + job.setType(type); + job.setJobLocation(StringUtils.isBlank(it.getJobAddress()) ? it.getJobLocation() : it.getJobAddress()); + job.setJobLocationAreaCode(StringUtils.isBlank(areaCode) ? 0 : Integer.valueOf(areaCode)); + job.setIsPublish(1); + job.setDataSource("1"); + job.setPostingDate(DateUtils.dateTimeNow(DateUtils.YYYY_MM_DD_HH_MM_SS)); + + String companyName = it.getCompanyName(); + job.setCompanyName(companyName); + job.setCompanyId(companyNameToIdMap.getOrDefault(companyName, null)); + + return job; + }).collect(Collectors.toList()); + } + + /** + * 岗位去重(业务规则:企业ID+岗位名称+工作地点唯一) + */ + private List dedupJobList(List jobList) { + Map dedupMap = new LinkedHashMap<>(); + for (Job job : jobList) { + String dedupKey = String.format( + "%s_%s_%s", + Optional.ofNullable(job.getCompanyId()).orElse(null), + job.getJobTitle().trim(), + job.getJobLocation().trim() + ); + dedupMap.putIfAbsent(dedupKey, job); + } + return new ArrayList<>(dedupMap.values()); + } + + /** + * 构建联系人列表(业务关联) + */ + private List buildJobContactList(List dedupedJobList, List allExcelVoList, Map companyNameToIdMap) { + List contactList = new ArrayList<>(); + + Map jobMatchMap = dedupedJobList.stream() + .collect(Collectors.toMap( + job -> String.format( + "%s_%s", + job.getJobTitle().trim(), + job.getJobLocation().trim() + ), + job -> job + )); + + for (JobExcelVo vo : allExcelVoList) { + String jobTitle = vo.getJobTitle().trim(); + String jobLocation = StringUtils.isBlank(vo.getJobAddress()) ? vo.getJobLocation().trim() : vo.getJobAddress().trim(); + String matchKey = String.format("%s_%s", jobTitle, jobLocation); + + Job matchedJob = jobMatchMap.get(matchKey); + if (matchedJob == null) { + continue; + } + + JobContact contact = new JobContact(); + contact.setJobId(matchedJob.getJobId()); + contact.setContactPerson(vo.getContactPerson()); + contact.setContactPersonPhone(vo.getContactPersonPhone()); + contactList.add(contact); + } + + return contactList; + } + + /** + * 联系人去重(兼容企业ID为0的情况) + */ + private List dedupContactList(List contactList, List dedupedJobList) { + if (CollectionUtils.isEmpty(contactList)) { + return new ArrayList<>(); + } + + Set validJobIds = dedupedJobList.stream().map(Job::getJobId).filter(Objects::nonNull).collect(Collectors.toSet()); + + Map dedupMap = new LinkedHashMap<>(); + for (JobContact contact : contactList) { + if (StringUtils.isBlank(contact.getContactPersonPhone())) { + System.out.printf("跳过联系人:姓名=%s(手机号为空)%n", contact.getContactPerson()); + continue; + } + + Long jobId = contact.getJobId(); + if (jobId == null || !validJobIds.contains(jobId)) { + System.out.printf("跳过联系人:姓名=%s(岗位ID无效或不存在)%n", contact.getContactPerson()); + continue; + } + + String dedupKey = String.format("%s_%s",jobId,contact.getContactPersonPhone().trim()); + + dedupMap.putIfAbsent(dedupKey, contact); + } + + return new ArrayList<>(dedupMap.values()); + } } diff --git a/ruoyi-bussiness/src/main/resources/mapper/app/CompanyMapper.xml b/ruoyi-bussiness/src/main/resources/mapper/app/CompanyMapper.xml index 1fff035..2a28ea3 100644 --- a/ruoyi-bussiness/src/main/resources/mapper/app/CompanyMapper.xml +++ b/ruoyi-bussiness/src/main/resources/mapper/app/CompanyMapper.xml @@ -106,7 +106,15 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" + + \ No newline at end of file diff --git a/ruoyi-bussiness/src/main/resources/mapper/app/JobMapper.xml b/ruoyi-bussiness/src/main/resources/mapper/app/JobMapper.xml index 3337d3e..8abacf2 100644 --- a/ruoyi-bussiness/src/main/resources/mapper/app/JobMapper.xml +++ b/ruoyi-bussiness/src/main/resources/mapper/app/JobMapper.xml @@ -127,7 +127,7 @@ job_title, min_salary, max_salary, education, experience, company_name, job_location, job_location_area_code, posting_date, vacancies, latitude, longitude, "view", company_id, is_hot, apply_num, description, is_publish, data_source, job_url, remark, del_flag, - create_by, create_time, row_id, job_category,jobType,job_address + create_by, create_time, row_id, job_category,job_type,type,job_address ) VALUES ( @@ -136,7 +136,7 @@ #{job.vacancies}, #{job.latitude}, #{job.longitude}, #{job.view}, #{job.companyId}, #{job.isHot}, #{job.applyNum}, #{job.description}, #{job.isPublish}, #{job.dataSource}, #{job.jobUrl}, #{job.remark}, #{job.delFlag}, #{job.createBy}, #{job.createTime}, - #{job.rowId}, #{job.jobCategory},#{job.jobType},#{job.jobAddress} + #{job.rowId}, #{job.jobCategory},#{job.jobType},#{job.type},#{job.jobAddress} ) @@ -349,4 +349,23 @@ + + INSERT INTO job ( + job_title, min_salary, max_salary, education, experience, company_name, job_location, + job_location_area_code, posting_date, vacancies, latitude, longitude, "view", company_id, + is_hot, apply_num, description, is_publish, data_source, job_url, remark, del_flag, + create_by, create_time, row_id, job_category,job_type,type,job_address + ) VALUES + + ( + #{job.jobTitle}, #{job.minSalary}, #{job.maxSalary}, #{job.education}, #{job.experience}, + #{job.companyName}, #{job.jobLocation}, #{job.jobLocationAreaCode}, #{job.postingDate}, + #{job.vacancies}, #{job.latitude}, #{job.longitude}, #{job.view}, #{job.companyId}, + #{job.isHot}, #{job.applyNum}, #{job.description}, #{job.isPublish}, #{job.dataSource}, + #{job.jobUrl}, #{job.remark}, #{job.delFlag}, #{job.createBy}, #{job.createTime}, + #{job.rowId}, #{job.jobCategory},#{job.jobType},#{job.type},#{job.jobAddress} + ) + + + \ No newline at end of file