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

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.query.ESJobSearch;
import com.ruoyi.cms.domain.vo.CandidateVO; import com.ruoyi.cms.domain.vo.CandidateVO;
import com.ruoyi.cms.domain.vo.CompanyVo; import com.ruoyi.cms.domain.vo.CompanyVo;
import com.ruoyi.cms.domain.vo.JobExcelVo;
import com.ruoyi.cms.service.*; import com.ruoyi.cms.service.*;
import com.ruoyi.cms.util.EasyExcelUtils;
import com.ruoyi.cms.util.RoleUtils; import com.ruoyi.cms.util.RoleUtils;
import com.ruoyi.cms.util.StringUtil; import com.ruoyi.cms.util.StringUtil;
import com.ruoyi.cms.util.sensitiveWord.SensitiveWordChecker; 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 com.ruoyi.common.utils.poi.ExcelUtil;
import io.swagger.annotations.Api; import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiOperation;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
@@ -358,4 +363,82 @@ public class CmsJobController extends BaseController
} }
return AjaxResult.success("此岗位已存在!"); 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); List<Company> selectLikeCompanyList(Company company);
Company selectCompanyByJobId(Long jobId); 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); Job getJobInfo(Long jobId);
Integer getTotals(Job job); 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.Job;
import com.ruoyi.cms.domain.query.ESJobSearch; import com.ruoyi.cms.domain.query.ESJobSearch;
import com.ruoyi.cms.domain.vo.CandidateVO; import com.ruoyi.cms.domain.vo.CandidateVO;
import com.ruoyi.cms.domain.vo.JobExcelVo;
import com.ruoyi.common.core.domain.entity.AppUser; import com.ruoyi.common.core.domain.entity.AppUser;
import org.dromara.easyes.core.biz.EsPageInfo; import org.dromara.easyes.core.biz.EsPageInfo;
@@ -110,4 +111,6 @@ public interface IJobService
* @return * @return
*/ */
public Integer getTotals(Job job); public Integer getTotals(Job job);
void uploadFileJob(List<JobExcelVo> list);
} }

View File

@@ -14,5 +14,7 @@ import java.util.List;
public interface JobContactService{ public interface JobContactService{
List<JobContact> getSelectList(JobContact jobContact); 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){ public List<JobContact> getSelectList(JobContact jobContact){
return jobContactMapper.getSelectList(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; package com.ruoyi.cms.service.impl;
import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson2.JSONArray; import com.alibaba.fastjson2.JSONArray;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; 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.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import com.ruoyi.cms.domain.*; import com.ruoyi.cms.domain.*;
import com.ruoyi.cms.domain.vo.JobExcelVo;
import com.ruoyi.cms.util.notice.NoticeUtils; import com.ruoyi.cms.util.notice.NoticeUtils;
import com.ruoyi.common.core.domain.entity.File; import com.ruoyi.common.core.domain.entity.File;
import com.ruoyi.cms.domain.query.ESJobSearch; 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.domain.entity.JobTitle;
import com.ruoyi.common.core.redis.RedisCache; import com.ruoyi.common.core.redis.RedisCache;
import com.ruoyi.common.exception.ServiceException; import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.common.utils.SecurityUtils; import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.utils.SiteSecurityUtils; import com.ruoyi.common.utils.SiteSecurityUtils;
import com.ruoyi.common.utils.StringUtils; import com.ruoyi.common.utils.StringUtils;
@@ -1137,4 +1137,166 @@ public class JobServiceImpl extends ServiceImpl<JobMapper,Job> implements IJobSe
public Integer getTotals(Job job) { public Integer getTotals(Job job) {
return jobMapper.getTotals(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());
}
} }

View File

@@ -106,7 +106,15 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
</select> </select>
<select id="selectCompanyByJobId" resultType="com.ruoyi.common.core.domain.entity.Company"> <select id="selectCompanyByJobId" resultType="com.ruoyi.common.core.domain.entity.Company">
select a.* from COMPANY a inner join job b on a.company_id=b.company_id and b.job_id=#{jobId} limit 1 select a.* from COMPANY a inner join job b on a.company_id=b.company_id and a.del_flag = '0' and b.del_flag = '0' and b.job_id=#{jobId} limit 1
</select>
<select id="selectByNames" resultType="com.ruoyi.common.core.domain.entity.Company" parameterType="java.util.List">
SELECT company_id, name FROM company
WHERE del_flag = '0' and name IN
<foreach collection="list" item="name" open="(" separator="," close=")">
#{name}
</foreach>
</select> </select>
</mapper> </mapper>

View File

@@ -127,7 +127,7 @@
job_title, min_salary, max_salary, education, experience, company_name, job_location, job_title, min_salary, max_salary, education, experience, company_name, job_location,
job_location_area_code, posting_date, vacancies, latitude, longitude, "view", company_id, 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, 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 ) VALUES
<foreach collection="list" item="job" separator=","> <foreach collection="list" item="job" separator=",">
( (
@@ -136,7 +136,7 @@
#{job.vacancies}, #{job.latitude}, #{job.longitude}, #{job.view}, #{job.companyId}, #{job.vacancies}, #{job.latitude}, #{job.longitude}, #{job.view}, #{job.companyId},
#{job.isHot}, #{job.applyNum}, #{job.description}, #{job.isPublish}, #{job.dataSource}, #{job.isHot}, #{job.applyNum}, #{job.description}, #{job.isPublish}, #{job.dataSource},
#{job.jobUrl}, #{job.remark}, #{job.delFlag}, #{job.createBy}, #{job.createTime}, #{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}
) )
</foreach> </foreach>
</insert> </insert>
@@ -349,4 +349,23 @@
</where> </where>
</select> </select>
<insert id="updateFileBatchInsert" useGeneratedKeys="true" keyProperty="jobId" parameterType="java.util.List">
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
<foreach collection="list" item="job" separator=",">
(
#{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}
)
</foreach>
</insert>
</mapper> </mapper>