添加岗位模板下载,岗位模板上传功能

This commit is contained in:
sh
2026-01-21 15:35:15 +08:00
parent 90c553a31d
commit 5e3ec6ddae
10 changed files with 388 additions and 5 deletions

View File

@@ -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<JobExcelVo> 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();
}
}

View File

@@ -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;
}

View File

@@ -29,4 +29,6 @@ public interface CompanyMapper extends BaseMapper<Company>
List<Company> selectLikeCompanyList(Company company);
Company selectCompanyByJobId(Long jobId);
List<Company> selectByNames(List<String> list);
}

View File

@@ -60,4 +60,6 @@ public interface JobMapper extends BaseMapper<Job>
Job getJobInfo(Long jobId);
Integer getTotals(Job job);
void updateFileBatchInsert(List<Job> list);
}

View File

@@ -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<JobExcelVo> list);
}

View File

@@ -14,5 +14,7 @@ import java.util.List;
public interface JobContactService{
List<JobContact> getSelectList(JobContact jobContact);
int batchInsert(List<JobContact> list);
}

View File

@@ -18,4 +18,9 @@ public class JobContactServiceImpl extends ServiceImpl<JobContactMapper, JobCont
public List<JobContact> getSelectList(JobContact jobContact){
return jobContactMapper.getSelectList(jobContact);
}
@Override
public int batchInsert(List<JobContact> list) {
return jobContactMapper.batchInsert(list);
}
}

View File

@@ -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<JobMapper,Job> implements IJobSe
public Integer getTotals(Job job) {
return jobMapper.getTotals(job);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void uploadFileJob(List<JobExcelVo> list) {
//查询所有企业
Map<String, Long> companyNameToIdMap = companyNameToIdMap(list);
//岗位字典转换
List<Job> jobList = buildJobList(list, companyNameToIdMap);
//岗位去重
List<Job> dedupedJobList = dedupJobList(jobList);
//批量保存岗
jobMapper.updateFileBatchInsert(dedupedJobList);
//构建联系人列表(关联去重后的岗位)
List<JobContact> contactList = buildJobContactList(dedupedJobList, list, companyNameToIdMap);
//联系人去重企业ID+联系人手机号 去重企业ID为0/null时仅按手机号去重
List<JobContact> dedupedContactList = dedupContactList(contactList, dedupedJobList);
//批量保存联系人
if (CollectionUtils.isNotEmpty(dedupedContactList)) {
jobContactMapper.batchInsert(dedupedContactList);
}
}
/**
* 企业名称查询关联(核心业务逻辑)
*/
private Map<String, Long> companyNameToIdMap(List<JobExcelVo> allExcelVoList) {
Set<String> companyNameSet = allExcelVoList.stream()
.map(JobExcelVo::getCompanyName)
.filter(StringUtils::hasText)
.collect(Collectors.toSet());
if (companyNameSet.isEmpty()) {
return Collections.emptyMap();
}
List<Company> 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<Job> buildJobList(List<JobExcelVo> allExcelVoList, Map<String, Long> 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<Job> dedupJobList(List<Job> jobList) {
Map<String, Job> 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<JobContact> buildJobContactList(List<Job> dedupedJobList, List<JobExcelVo> allExcelVoList, Map<String, Long> companyNameToIdMap) {
List<JobContact> contactList = new ArrayList<>();
Map<String, Job> 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<JobContact> dedupContactList(List<JobContact> contactList, List<Job> dedupedJobList) {
if (CollectionUtils.isEmpty(contactList)) {
return new ArrayList<>();
}
Set<Long> validJobIds = dedupedJobList.stream().map(Job::getJobId).filter(Objects::nonNull).collect(Collectors.toSet());
Map<String, JobContact> 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());
}
}