From e558e70b450277f15a53a4b2781696156df45d74 Mon Sep 17 00:00:00 2001 From: lapuda <577732344@qq.com> Date: Thu, 23 Jul 2026 18:35:18 +0800 Subject: [PATCH] feat: add recruitment fair participant export --- .../cms/RecruitmentFairExportController.java | 47 ++ .../export/RecruitmentFairExportQuery.java | 89 ++++ .../export/RecruitmentFairExportRow.java | 66 +++ .../mapper/RecruitmentFairExportMapper.java | 23 + .../service/RecruitmentFairExportService.java | 402 ++++++++++++++++++ .../cms/RecruitmentFairExportMapper.xml | 278 ++++++++++++ ...on_recruitment_fair_export_permissions.sql | 108 +++++ 7 files changed, 1013 insertions(+) create mode 100644 ruoyi-bussiness/src/main/java/com/ruoyi/cms/controller/cms/RecruitmentFairExportController.java create mode 100644 ruoyi-bussiness/src/main/java/com/ruoyi/cms/domain/export/RecruitmentFairExportQuery.java create mode 100644 ruoyi-bussiness/src/main/java/com/ruoyi/cms/domain/export/RecruitmentFairExportRow.java create mode 100644 ruoyi-bussiness/src/main/java/com/ruoyi/cms/mapper/RecruitmentFairExportMapper.java create mode 100644 ruoyi-bussiness/src/main/java/com/ruoyi/cms/service/RecruitmentFairExportService.java create mode 100644 ruoyi-bussiness/src/main/resources/mapper/cms/RecruitmentFairExportMapper.xml create mode 100644 sql/migration_recruitment_fair_export_permissions.sql diff --git a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/controller/cms/RecruitmentFairExportController.java b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/controller/cms/RecruitmentFairExportController.java new file mode 100644 index 0000000..2a8b873 --- /dev/null +++ b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/controller/cms/RecruitmentFairExportController.java @@ -0,0 +1,47 @@ +package com.ruoyi.cms.controller.cms; + +import com.ruoyi.cms.domain.export.RecruitmentFairExportQuery; +import com.ruoyi.cms.service.RecruitmentFairExportService; +import com.ruoyi.common.annotation.Log; +import com.ruoyi.common.core.controller.BaseController; +import com.ruoyi.common.enums.BusinessType; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +/** 线上/户外招聘会参会企业与岗位导出接口。 */ +@RestController +@RequestMapping("/cms") +@Api(tags = "后台:招聘会参会数据导出") +public class RecruitmentFairExportController extends BaseController +{ + @Autowired + private RecruitmentFairExportService exportService; + + @ApiOperation("导出线上招聘会参会企业或岗位") + @PreAuthorize("@ss.hasPermi('cms:publicJobFair:export')") + @Log(title = "线上招聘会参会数据", businessType = BusinessType.EXPORT) + @PostMapping("/publicJobFair/export") + public void exportPublicJobFair(RecruitmentFairExportQuery query, HttpServletResponse response) + throws IOException + { + exportService.export(response, "online", query); + } + + @ApiOperation("导出户外招聘会参会企业或岗位") + @PreAuthorize("@ss.hasPermi('cms:outdoorFair:export')") + @Log(title = "户外招聘会参会数据", businessType = BusinessType.EXPORT) + @PostMapping("/outdoor-fair/export") + public void exportOutdoorFair(RecruitmentFairExportQuery query, HttpServletResponse response) + throws IOException + { + exportService.export(response, "outdoor", query); + } +} diff --git a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/domain/export/RecruitmentFairExportQuery.java b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/domain/export/RecruitmentFairExportQuery.java new file mode 100644 index 0000000..f2ca679 --- /dev/null +++ b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/domain/export/RecruitmentFairExportQuery.java @@ -0,0 +1,89 @@ +package com.ruoyi.cms.domain.export; + +import com.fasterxml.jackson.annotation.JsonFormat; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import org.springframework.format.annotation.DateTimeFormat; + +import java.io.Serializable; +import java.util.Date; + +/** + * 招聘会参会企业/岗位导出条件。 + * + *

线上招聘会和户外招聘会共用这组条件,未被某一类招聘会使用的字段会被忽略。 + * 时间条件使用闭区间,便于页面按“开始时间/结束时间”筛选整个招聘会。

+ */ +@Data +@ApiModel("招聘会参会数据导出条件") +public class RecruitmentFairExportQuery implements Serializable +{ + private static final long serialVersionUID = 1L; + + @ApiModelProperty("导出类型:companies 企业、jobs 岗位、companyJobs 企业及岗位") + private String exportType = "companies"; + + @ApiModelProperty("招聘会标题") + private String title; + + @ApiModelProperty("举办单位") + private String hostUnit; + + @ApiModelProperty("招聘会类型") + private String fairType; + + @ApiModelProperty("举办区域/区域类型") + private String region; + + @ApiModelProperty("场地ID") + private Long venueId; + + @ApiModelProperty("场地名称") + private String venueName; + + @ApiModelProperty("举办地址") + private String address; + + @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + @ApiModelProperty("招聘会开始时间下限") + private Date startTime; + + @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + @ApiModelProperty("招聘会结束时间上限") + private Date endTime; + + @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + @ApiModelProperty("报名开始时间下限") + private Date applyStartTime; + + @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + @ApiModelProperty("报名截止时间上限") + private Date applyEndTime; + + @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + @ApiModelProperty("创建时间下限") + private Date createStartTime; + + @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + @ApiModelProperty("创建时间上限") + private Date createEndTime; + + @ApiModelProperty("举办状态:NOT_STARTED 未举办,ONGOING 正在举办,ENDED 已举办") + private String fairStatus; + + @ApiModelProperty("是否跨域:Y/N;仅线上招聘会使用") + private String isCrossDomain; + + @ApiModelProperty("是否开启线上申请:true/false;仅户外招聘会使用") + private Boolean onlineApply; + + @ApiModelProperty("参会企业或岗位审核状态:0待审核,1通过,2驳回") + private String reviewStatus; +} diff --git a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/domain/export/RecruitmentFairExportRow.java b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/domain/export/RecruitmentFairExportRow.java new file mode 100644 index 0000000..d6946c6 --- /dev/null +++ b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/domain/export/RecruitmentFairExportRow.java @@ -0,0 +1,66 @@ +package com.ruoyi.cms.domain.export; + +import lombok.Data; + +import java.io.Serializable; +import java.util.Date; + +/** + * 招聘会导出行。线上和户外查询统一投影到该结构,Excel 层负责字典显示值转换。 + */ +@Data +public class RecruitmentFairExportRow implements Serializable +{ + private static final long serialVersionUID = 1L; + + private String fairId; + private String channel; + private String fairTitle; + private String fairAddress; + private String fairType; + private String fairRegion; + private String fairVenueName; + private String isCrossDomain; + private Boolean onlineApply; + private Date fairStartTime; + private Date fairEndTime; + private Date applyStartTime; + private Date applyEndTime; + private Date fairCreateTime; + private String fairHostUnit; + private String fairHelpUnit; + private String fairOrganizeUnit; + private String fairPhone; + private String fairCategory; + + private Long companyId; + private String companyName; + private String companyType; + private String companyIndustry; + private String companyScale; + private String companyLocation; + private String companyCode; + private String contactPerson; + private String contactPhone; + private Integer companyStatus; + private String companyReviewStatus; + private String companyReviewRemark; + + private Long jobId; + private String jobTitle; + private Integer minSalary; + private Integer maxSalary; + private String salaryComposition; + private String welfareBenefits; + private String workSchedule; + private String education; + private String experience; + private String jobType; + private String jobCategory; + private String jobLocation; + private String jobAddress; + private Integer vacancies; + private Date postingDate; + private String jobReviewStatus; + private String jobReviewRemark; +} diff --git a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/mapper/RecruitmentFairExportMapper.java b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/mapper/RecruitmentFairExportMapper.java new file mode 100644 index 0000000..013dbac --- /dev/null +++ b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/mapper/RecruitmentFairExportMapper.java @@ -0,0 +1,23 @@ +package com.ruoyi.cms.mapper; + +import com.ruoyi.cms.domain.export.RecruitmentFairExportQuery; +import com.ruoyi.cms.domain.export.RecruitmentFairExportRow; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** 招聘会参会企业/岗位导出查询。 */ +public interface RecruitmentFairExportMapper +{ + List selectPublicCompanyRows( + @Param("query") RecruitmentFairExportQuery query); + + List selectPublicJobRows( + @Param("query") RecruitmentFairExportQuery query); + + List selectOutdoorCompanyRows( + @Param("query") RecruitmentFairExportQuery query); + + List selectOutdoorJobRows( + @Param("query") RecruitmentFairExportQuery query); +} diff --git a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/service/RecruitmentFairExportService.java b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/service/RecruitmentFairExportService.java new file mode 100644 index 0000000..52800e6 --- /dev/null +++ b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/service/RecruitmentFairExportService.java @@ -0,0 +1,402 @@ +package com.ruoyi.cms.service; + +import com.ruoyi.cms.domain.export.RecruitmentFairExportQuery; +import com.ruoyi.cms.domain.export.RecruitmentFairExportRow; +import com.ruoyi.cms.mapper.JobTitleMapper; +import com.ruoyi.cms.mapper.RecruitmentFairExportMapper; +import com.ruoyi.common.core.domain.entity.JobTitle; +import org.apache.poi.ss.usermodel.BorderStyle; +import org.apache.poi.ss.usermodel.Cell; +import org.apache.poi.ss.usermodel.CellStyle; +import org.apache.poi.ss.usermodel.FillPatternType; +import org.apache.poi.ss.usermodel.Font; +import org.apache.poi.ss.usermodel.HorizontalAlignment; +import org.apache.poi.ss.usermodel.IndexedColors; +import org.apache.poi.ss.usermodel.Row; +import org.apache.poi.ss.usermodel.Sheet; +import org.apache.poi.ss.usermodel.Workbook; +import org.apache.poi.xssf.usermodel.XSSFWorkbook; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Date; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** 招聘会参会企业和岗位 Excel 导出服务。 */ +@Service +public class RecruitmentFairExportService +{ + private static final String ONLINE = "online"; + private static final String OUTDOOR = "outdoor"; + private static final String COMPANIES = "companies"; + private static final String JOBS = "jobs"; + private static final String COMPANY_JOBS = "companyJobs"; + private static final String DATE_PATTERN = "yyyy-MM-dd HH:mm:ss"; + + private static final List COMPANY_HEADERS = Arrays.asList( + "招聘会标题", "招聘会类型", "区域/举办区域", "是否跨域", "是否线上申请", "举办状态", + "开始时间", "结束时间", "报名开始时间", "报名截止时间", "创建时间", "招聘会地址", "举办单位", "协办单位", + "组织单位", "招聘会联系电话", "招聘会分类", "场地", "参会企业", "企业ID", "企业类型", "所属行业", "企业规模", "企业地址", "统一社会信用代码", + "联系人", "联系电话", "企业审核状态", "企业审核意见"); + + private static final List JOB_HEADERS = Arrays.asList( + "招聘会标题", "招聘会类型", "区域/举办区域", "是否跨域", "是否线上申请", "举办状态", + "开始时间", "结束时间", "报名开始时间", "报名截止时间", "创建时间", "招聘会地址", "举办单位", "协办单位", + "组织单位", "招聘会联系电话", "招聘会分类", "场地", "参会企业", "企业ID", "企业类型", "所属行业", "企业规模", "联系人", "联系电话", "企业审核状态", + "企业审核意见", "岗位名称", "岗位ID", "最低薪资(元)", "最高薪资(元)", "薪资构成", + "福利待遇", "工作时间安排", "学历要求", "工作经验", "岗位类型", "岗位分类", "岗位区划", + "工作地址", "招聘人数", "发布时间", "岗位审核状态", "岗位审核意见"); + + @Autowired + private RecruitmentFairExportMapper exportMapper; + + @Autowired + private JobTitleMapper jobTitleMapper; + + /** + * 导出指定渠道的数据。导出类型由 query.exportType 决定。 + */ + public void export(HttpServletResponse response, String channel, RecruitmentFairExportQuery query) + throws IOException + { + RecruitmentFairExportQuery condition = query == null ? new RecruitmentFairExportQuery() : query; + String exportType = normalizeExportType(condition.getExportType()); + List rows = queryRows(channel, exportType, condition); + + try (Workbook workbook = new XSSFWorkbook()) { + CellStyle headerStyle = createHeaderStyle(workbook); + if (COMPANIES.equals(exportType)) { + writeRows(workbook.createSheet("参会企业"), COMPANY_HEADERS, rows, headerStyle, + this::companyValues); + } else if (JOBS.equals(exportType)) { + writeRows(workbook.createSheet("参加岗位"), JOB_HEADERS, rows, headerStyle, + this::jobValues); + } else { + writeCompanySheets(workbook, rows, headerStyle); + } + + response.reset(); + response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); + response.setCharacterEncoding(StandardCharsets.UTF_8.name()); + String channelName = ONLINE.equals(channel) ? "线上招聘会" : "户外招聘会"; + String fileName = channelName + "参会数据_" + System.currentTimeMillis() + ".xlsx"; + String encodedFileName = URLEncoder.encode(fileName, StandardCharsets.UTF_8.name()) + .replace("+", "%20"); + response.setHeader("Content-Disposition", "attachment; filename*=UTF-8''" + encodedFileName); + workbook.write(response.getOutputStream()); + response.flushBuffer(); + } + } + + private List queryRows(String channel, String exportType, + RecruitmentFairExportQuery query) + { + boolean online = ONLINE.equals(channel); + if (COMPANIES.equals(exportType)) { + return online ? exportMapper.selectPublicCompanyRows(query) + : exportMapper.selectOutdoorCompanyRows(query); + } + if (JOBS.equals(exportType)) { + return online ? exportMapper.selectPublicJobRows(query) + : exportMapper.selectOutdoorJobRows(query); + } + + // 组合导出需要同时保留“没有岗位的参会企业”,因此分别查询企业和岗位。 + List companies = online + ? exportMapper.selectPublicCompanyRows(query) + : exportMapper.selectOutdoorCompanyRows(query); + List jobs = online + ? exportMapper.selectPublicJobRows(query) + : exportMapper.selectOutdoorJobRows(query); + List result = new ArrayList<>(companies); + result.addAll(jobs); + return result; + } + + private String normalizeExportType(String exportType) + { + if (JOBS.equals(exportType) || COMPANY_JOBS.equals(exportType)) { + return exportType; + } + return COMPANIES; + } + + private CellStyle createHeaderStyle(Workbook workbook) + { + CellStyle style = workbook.createCellStyle(); + style.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex()); + style.setFillPattern(FillPatternType.SOLID_FOREGROUND); + style.setAlignment(HorizontalAlignment.CENTER); + style.setBorderBottom(BorderStyle.THIN); + style.setBorderTop(BorderStyle.THIN); + style.setBorderLeft(BorderStyle.THIN); + style.setBorderRight(BorderStyle.THIN); + Font font = workbook.createFont(); + font.setBold(true); + style.setFont(font); + return style; + } + + private void writeRows(Sheet sheet, List headers, List rows, + CellStyle headerStyle, RowValues values) + { + writeHeader(sheet, headers, headerStyle); + int rowIndex = 1; + for (RecruitmentFairExportRow rowData : rows == null ? Collections.emptyList() : rows) { + Row row = sheet.createRow(rowIndex++); + List rowValues = values.values(rowData); + for (int i = 0; i < rowValues.size(); i++) { + setCellValue(row.createCell(i), rowValues.get(i)); + } + } + sizeColumns(sheet, headers.size()); + } + + private void writeCompanySheets(Workbook workbook, List rows, + CellStyle headerStyle) + { + Map> groups = new LinkedHashMap<>(); + for (RecruitmentFairExportRow row : rows == null ? Collections.emptyList() : rows) { + String key = row.getCompanyId() == null ? safe(row.getCompanyName()) + : String.valueOf(row.getCompanyId()); + groups.computeIfAbsent(key, ignored -> new ArrayList<>()).add(row); + } + if (groups.isEmpty()) { + writeRows(workbook.createSheet("企业岗位"), JOB_HEADERS, Collections.emptyList(), headerStyle, + this::jobValues); + return; + } + + Set sheetNames = new LinkedHashSet<>(); + for (List companyRows : groups.values()) { + RecruitmentFairExportRow first = companyRows.get(0); + String baseName = "企业-" + safe(first.getCompanyName()); + String sheetName = uniqueSheetName(baseName, sheetNames); + Sheet sheet = workbook.createSheet(sheetName); + writeHeader(sheet, JOB_HEADERS, headerStyle); + + // 同一企业可能参加多场招聘会;只补充没有岗位的场次,避免组合导出遗漏企业。 + Set fairIdsWithJobs = new LinkedHashSet<>(); + List jobRows = new ArrayList<>(); + List companyOnlyRows = new ArrayList<>(); + for (RecruitmentFairExportRow row : companyRows) { + if (row.getJobId() == null) { + companyOnlyRows.add(row); + } else { + jobRows.add(row); + fairIdsWithJobs.add(safe(row.getFairId())); + } + } + List output = new ArrayList<>(jobRows); + for (RecruitmentFairExportRow row : companyOnlyRows) { + if (!fairIdsWithJobs.contains(safe(row.getFairId()))) { + output.add(row); + } + } + int rowIndex = 1; + for (RecruitmentFairExportRow rowData : output) { + Row row = sheet.createRow(rowIndex++); + List rowValues = jobValues(rowData); + for (int i = 0; i < rowValues.size(); i++) { + setCellValue(row.createCell(i), rowValues.get(i)); + } + } + sizeColumns(sheet, JOB_HEADERS.size()); + } + } + + private void writeHeader(Sheet sheet, List headers, CellStyle style) + { + Row header = sheet.createRow(0); + header.setHeightInPoints(24); + for (int i = 0; i < headers.size(); i++) { + Cell cell = header.createCell(i); + cell.setCellValue(headers.get(i)); + cell.setCellStyle(style); + } + sheet.createFreezePane(0, 1); + sheet.setAutoFilter(new org.apache.poi.ss.util.CellRangeAddress(0, 0, 0, headers.size() - 1)); + } + + private void sizeColumns(Sheet sheet, int columnCount) + { + for (int i = 0; i < columnCount; i++) { + sheet.autoSizeColumn(i); + int width = Math.min(sheet.getColumnWidth(i) + 512, 12000); + sheet.setColumnWidth(i, Math.max(width, 2400)); + } + } + + private List companyValues(RecruitmentFairExportRow row) + { + return Arrays.asList( + row.getFairTitle(), fairTypeLabel(row), regionLabel(row), + yesNo(row.getIsCrossDomain()), yesNo(row.getOnlineApply()), fairStatus(row), + date(row.getFairStartTime()), date(row.getFairEndTime()), date(row.getApplyStartTime()), + date(row.getApplyEndTime()), date(row.getFairCreateTime()), row.getFairAddress(), row.getFairHostUnit(), + row.getFairHelpUnit(), row.getFairOrganizeUnit(), row.getFairPhone(), row.getFairCategory(), + row.getFairVenueName(), + row.getCompanyName(), row.getCompanyId(), dict("company_nature", row.getCompanyType()), + dict("industry", row.getCompanyIndustry()), dict("scale", row.getCompanyScale()), row.getCompanyLocation(), + row.getCompanyCode(), row.getContactPerson(), row.getContactPhone(), review(row.getCompanyReviewStatus()), + row.getCompanyReviewRemark()); + } + + private List jobValues(RecruitmentFairExportRow row) + { + return Arrays.asList( + row.getFairTitle(), fairTypeLabel(row), regionLabel(row), + yesNo(row.getIsCrossDomain()), yesNo(row.getOnlineApply()), fairStatus(row), + date(row.getFairStartTime()), date(row.getFairEndTime()), date(row.getApplyStartTime()), + date(row.getApplyEndTime()), date(row.getFairCreateTime()), row.getFairAddress(), row.getFairHostUnit(), + row.getFairHelpUnit(), row.getFairOrganizeUnit(), row.getFairPhone(), row.getFairCategory(), + row.getFairVenueName(), + row.getCompanyName(), row.getCompanyId(), dict("company_nature", row.getCompanyType()), + dict("industry", row.getCompanyIndustry()), dict("scale", row.getCompanyScale()), row.getContactPerson(), + row.getContactPhone(), review(row.getCompanyReviewStatus()), row.getCompanyReviewRemark(), row.getJobTitle(), + row.getJobId(), row.getMinSalary(), row.getMaxSalary(), dict("salary_composition", row.getSalaryComposition()), + dict("welfare_benefits", row.getWelfareBenefits()), dict("work_schedule", row.getWorkSchedule()), + dict("education", row.getEducation()), dict("experience", row.getExperience()), dict("job_type", row.getJobType()), + jobCategoryLabel(row.getJobCategory()), dict("area", row.getJobLocation()), row.getJobAddress(), + row.getVacancies(), date(row.getPostingDate()), review(row.getJobReviewStatus()), row.getJobReviewRemark()); + } + + private String regionLabel(RecruitmentFairExportRow row) + { + String dictType = "outdoor".equals(row.getChannel()) ? "outdoor_fair_region" : "job_fair_region_type"; + return dict(dictType, row.getFairRegion()); + } + + private String fairTypeLabel(RecruitmentFairExportRow row) + { + String dictType = "outdoor".equals(row.getChannel()) ? "outdoor_fair_type" : "job_fair_type"; + return dict(dictType, row.getFairType()); + } + + private String dict(String type, String value) + { + if (value == null || value.trim().isEmpty()) { + return ""; + } + // 招聘会类型/区域使用系统字典,企业和岗位字段使用业务字典;两套字典在项目中并存。 + String label = com.ruoyi.cms.util.DictUtils.getDictLabel(type, value); + if (label == null || label.trim().isEmpty()) { + label = com.ruoyi.common.utils.DictUtils.getDictLabel(type, value); + } + return label == null || label.trim().isEmpty() ? value : label; + } + + private String jobCategoryLabel(String value) + { + if (value == null || value.trim().isEmpty()) { + return ""; + } + try { + JobTitle jobTitle = jobTitleMapper.selectById(Long.valueOf(value)); + if (jobTitle != null && jobTitle.getJobName() != null && !jobTitle.getJobName().trim().isEmpty()) { + return jobTitle.getJobName(); + } + } catch (NumberFormatException ignored) { + // 岗位分类也可能已经保存为文字,直接保留原值。 + } + return value; + } + + private String yesNo(Object value) + { + if (value == null) { + return ""; + } + String raw = String.valueOf(value); + if ("Y".equalsIgnoreCase(raw) || "true".equalsIgnoreCase(raw) || "1".equals(raw)) { + return "是"; + } + if ("N".equalsIgnoreCase(raw) || "false".equalsIgnoreCase(raw) || "0".equals(raw)) { + return "否"; + } + return raw; + } + + private String review(String value) + { + if (value == null || value.trim().isEmpty()) { + return ""; + } + switch (value) { + case "0": + return "待审核"; + case "1": + return "审核通过"; + case "2": + return "审核不通过"; + default: + return value; + } + } + + private String fairStatus(RecruitmentFairExportRow row) + { + Date now = new Date(); + if (row.getFairStartTime() != null && now.before(row.getFairStartTime())) { + return "未举办"; + } + if (row.getFairEndTime() != null && !now.after(row.getFairEndTime())) { + return "正在举办"; + } + return "已举办"; + } + + private String date(Date value) + { + return value == null ? "" : new SimpleDateFormat(DATE_PATTERN).format(value); + } + + private String safe(String value) + { + return value == null || value.trim().isEmpty() ? "未命名企业" : value.trim(); + } + + private String uniqueSheetName(String value, Set used) + { + String base = value.replaceAll("[\\\\/:*?\\[\\]]", "_"); + if (base.length() > 31) { + base = base.substring(0, 31); + } + String candidate = base; + int suffix = 2; + while (!used.add(candidate)) { + String tail = "-" + suffix++; + int maxBaseLength = 31 - tail.length(); + candidate = base.substring(0, Math.min(base.length(), maxBaseLength)) + tail; + } + return candidate; + } + + private interface RowValues + { + List values(RecruitmentFairExportRow row); + } + + private void setCellValue(Cell cell, Object value) + { + if (value == null) { + cell.setCellValue(""); + } else if (value instanceof Number) { + cell.setCellValue(((Number) value).doubleValue()); + } else { + cell.setCellValue(String.valueOf(value)); + } + } +} diff --git a/ruoyi-bussiness/src/main/resources/mapper/cms/RecruitmentFairExportMapper.xml b/ruoyi-bussiness/src/main/resources/mapper/cms/RecruitmentFairExportMapper.xml new file mode 100644 index 0000000..7f3f186 --- /dev/null +++ b/ruoyi-bussiness/src/main/resources/mapper/cms/RecruitmentFairExportMapper.xml @@ -0,0 +1,278 @@ + + + + + + pf.del_flag = '0' + + AND pf.job_fair_title LIKE CONCAT('%', CAST(#{query.title, jdbcType=VARCHAR} AS VARCHAR), '%') + + + AND pf.job_fair_host_unit LIKE CONCAT('%', CAST(#{query.hostUnit, jdbcType=VARCHAR} AS VARCHAR), '%') + + + AND pf.job_fair_type = #{query.fairType} + + + AND pf.job_fair_region_type = #{query.region} + + + AND pf.job_fair_address LIKE CONCAT('%', CAST(#{query.address, jdbcType=VARCHAR} AS VARCHAR), '%') + + + AND pf.job_fair_start_time >= #{query.startTime} + + + AND pf.job_fair_end_time <= #{query.endTime} + + + AND pf.job_fair_sign_up_start_time >= #{query.applyStartTime} + + + AND pf.job_fair_sign_up_end_time <= #{query.applyEndTime} + + + AND pf.create_time >= #{query.createStartTime} + + + AND pf.create_time <= #{query.createEndTime} + + + AND UPPER(COALESCE(pf.is_cross_domain, 'N')) = UPPER(#{query.isCrossDomain}) + + + AND pf.job_fair_start_time > CURRENT_TIMESTAMP + + + AND pf.job_fair_start_time <= CURRENT_TIMESTAMP + AND pf.job_fair_end_time >= CURRENT_TIMESTAMP + + + AND pf.job_fair_end_time < CURRENT_TIMESTAMP + + + + + pf.del_flag = '0' + + AND pf.title LIKE CONCAT('%', CAST(#{query.title, jdbcType=VARCHAR} AS VARCHAR), '%') + + + AND pf.host_unit LIKE CONCAT('%', CAST(#{query.hostUnit, jdbcType=VARCHAR} AS VARCHAR), '%') + + + AND pf.fair_type = #{query.fairType} + + + AND pf.region = #{query.region} + + + AND pf.venue_id = #{query.venueId} + + + AND pf.venue_name LIKE CONCAT('%', CAST(#{query.venueName, jdbcType=VARCHAR} AS VARCHAR), '%') + + + AND pf.address LIKE CONCAT('%', CAST(#{query.address, jdbcType=VARCHAR} AS VARCHAR), '%') + + + AND pf.online_apply = #{query.onlineApply} + + + AND pf.hold_time >= #{query.startTime} + + + AND pf.end_time <= #{query.endTime} + + + AND pf.apply_start_time >= #{query.applyStartTime} + + + AND pf.apply_end_time <= #{query.applyEndTime} + + + AND pf.create_time >= #{query.createStartTime} + + + AND pf.create_time <= #{query.createEndTime} + + + AND pf.hold_time > CURRENT_TIMESTAMP + + + AND pf.hold_time <= CURRENT_TIMESTAMP + AND pf.end_time >= CURRENT_TIMESTAMP + + + AND pf.end_time < CURRENT_TIMESTAMP + + + + + 'online' AS channel, + pf.job_fair_id AS fair_id, + pf.job_fair_title AS fair_title, + pf.job_fair_address AS fair_address, + pf.job_fair_type AS fair_type, + pf.job_fair_region_type AS fair_region, + NULL AS fair_venue_name, + pf.is_cross_domain, + NULL AS online_apply, + pf.job_fair_start_time AS fair_start_time, + pf.job_fair_end_time AS fair_end_time, + pf.job_fair_sign_up_start_time AS apply_start_time, + pf.job_fair_sign_up_end_time AS apply_end_time, + pf.create_time AS fair_create_time, + pf.job_fair_host_unit AS fair_host_unit, + pf.job_fair_help_unit AS fair_help_unit, + pf.job_fair_organize_unit AS fair_organize_unit, + pf.job_fair_phone AS fair_phone, + pf.job_fair_category AS fair_category + + + + 'outdoor' AS channel, + CAST(pf.id AS VARCHAR) AS fair_id, + pf.title AS fair_title, + pf.address AS fair_address, + pf.fair_type, + pf.region AS fair_region, + pf.venue_name AS fair_venue_name, + NULL AS is_cross_domain, + pf.online_apply, + pf.hold_time AS fair_start_time, + pf.end_time AS fair_end_time, + pf.apply_start_time, + pf.apply_end_time, + pf.create_time AS fair_create_time, + pf.host_unit AS fair_host_unit, + NULL AS fair_help_unit, + NULL AS fair_organize_unit, + NULL AS fair_phone, + NULL AS fair_category + + + + c.company_id, + c.name AS company_name, + COALESCE(c.company_nature, c.nature) AS company_type, + c.industry AS company_industry, + c.scale AS company_scale, + c.location AS company_location, + c.code AS company_code, + c.contact_person, + c.contact_person_phone AS contact_phone, + c.status AS company_status + + + + j.job_id, + j.job_title, + j.min_salary, + j.max_salary, + j.salary_composition, + j.welfare_benefits, + j.work_schedule, + j.education, + j.experience, + j.job_type, + j.job_category, + j.job_location, + j.job_address, + j.vacancies, + j.posting_date, + j.description + + + + + + + + + + diff --git a/sql/migration_recruitment_fair_export_permissions.sql b/sql/migration_recruitment_fair_export_permissions.sql new file mode 100644 index 0000000..4ee4c26 --- /dev/null +++ b/sql/migration_recruitment_fair_export_permissions.sql @@ -0,0 +1,108 @@ +-- 线上/户外招聘会参会企业与岗位导出权限 +-- 瀚高数据库 / PostgreSQL 兼容;可重复执行。 +-- 导出权限会授予已经拥有对应招聘会列表权限的角色,包含企业端角色。 + +BEGIN; + +-- 户外岗位审核字段:历史关联按已通过处理,兼容旧数据。 +ALTER TABLE "shz"."cms_outdoor_fair_job" + ADD COLUMN IF NOT EXISTS "review_status" CHAR(1) DEFAULT '1'; +ALTER TABLE "shz"."cms_outdoor_fair_job" + ADD COLUMN IF NOT EXISTS "review_by" VARCHAR(64); +ALTER TABLE "shz"."cms_outdoor_fair_job" + ADD COLUMN IF NOT EXISTS "review_time" TIMESTAMP; +ALTER TABLE "shz"."cms_outdoor_fair_job" + ADD COLUMN IF NOT EXISTS "review_remark" VARCHAR(500); + +UPDATE "shz"."cms_outdoor_fair_job" +SET "review_status" = '1' +WHERE "review_status" IS NULL; + +-- 户外企业审核字段由 fair_company 复用;旧库没有字段时补齐。 +ALTER TABLE "shz"."fair_company" + ADD COLUMN IF NOT EXISTS "review_status" CHAR(1) DEFAULT '1'; +ALTER TABLE "shz"."fair_company" + ADD COLUMN IF NOT EXISTS "review_by" VARCHAR(64); +ALTER TABLE "shz"."fair_company" + ADD COLUMN IF NOT EXISTS "review_time" TIMESTAMP; +ALTER TABLE "shz"."fair_company" + ADD COLUMN IF NOT EXISTS "review_remark" VARCHAR(500); + +UPDATE "shz"."fair_company" +SET "review_status" = '1' +WHERE "review_status" IS NULL; + +-- 线上导出按钮。 +INSERT INTO "shz"."sys_menu" ( + "menu_id", "menu_name", "parent_id", "order_num", "path", "component", + "query", "route_name", "is_frame", "is_cache", "menu_type", "visible", + "status", "perms", "icon", "create_by", "create_time", "update_by", + "update_time", "remark" +) +SELECT + (SELECT COALESCE(MAX("menu_id"), 0) + 1 FROM "shz"."sys_menu"), + '导出参会企业/岗位', list_menu."menu_id", 8, '', '', '', '', + 1, 0, 'F', '0', '0', 'cms:publicJobFair:export', '#', 'admin', CURRENT_TIMESTAMP, + '', NULL, '线上招聘会参会企业与岗位导出' +FROM "shz"."sys_menu" list_menu +WHERE list_menu."perms" = 'cms:publicJobFair:list' + AND NOT EXISTS ( + SELECT 1 FROM "shz"."sys_menu" WHERE "perms" = 'cms:publicJobFair:export' + ) +ORDER BY list_menu."menu_id" +LIMIT 1; + +-- 户外导出按钮。 +INSERT INTO "shz"."sys_menu" ( + "menu_id", "menu_name", "parent_id", "order_num", "path", "component", + "query", "route_name", "is_frame", "is_cache", "menu_type", "visible", + "status", "perms", "icon", "create_by", "create_time", "update_by", + "update_time", "remark" +) +SELECT + (SELECT COALESCE(MAX("menu_id"), 0) + 1 FROM "shz"."sys_menu"), + '导出参会企业/岗位', list_menu."menu_id", 4, '', '', '', '', + 1, 0, 'F', '0', '0', 'cms:outdoorFair:export', '#', 'admin', CURRENT_TIMESTAMP, + '', NULL, '户外招聘会参会企业与岗位导出' +FROM "shz"."sys_menu" list_menu +WHERE list_menu."perms" = 'cms:outdoorFair:list' + AND NOT EXISTS ( + SELECT 1 FROM "shz"."sys_menu" WHERE "perms" = 'cms:outdoorFair:export' + ) +ORDER BY list_menu."menu_id" +LIMIT 1; + +-- 将按钮权限补给已有对应列表权限的角色;权限和关联都可重复执行。 +INSERT INTO "shz"."sys_role_menu" ("role_id", "menu_id") +SELECT DISTINCT role_menu."role_id", export_menu."menu_id" +FROM "shz"."sys_role_menu" role_menu +JOIN "shz"."sys_menu" list_menu + ON list_menu."menu_id" = role_menu."menu_id" +JOIN "shz"."sys_menu" export_menu + ON export_menu."perms" = 'cms:publicJobFair:export' +WHERE list_menu."perms" = 'cms:publicJobFair:list' + AND NOT EXISTS ( + SELECT 1 FROM "shz"."sys_role_menu" existing + WHERE existing."role_id" = role_menu."role_id" + AND existing."menu_id" = export_menu."menu_id" + ); + +INSERT INTO "shz"."sys_role_menu" ("role_id", "menu_id") +SELECT DISTINCT role_menu."role_id", export_menu."menu_id" +FROM "shz"."sys_role_menu" role_menu +JOIN "shz"."sys_menu" list_menu + ON list_menu."menu_id" = role_menu."menu_id" +JOIN "shz"."sys_menu" export_menu + ON export_menu."perms" = 'cms:outdoorFair:export' +WHERE list_menu."perms" = 'cms:outdoorFair:list' + AND NOT EXISTS ( + SELECT 1 FROM "shz"."sys_role_menu" existing + WHERE existing."role_id" = role_menu."role_id" + AND existing."menu_id" = export_menu."menu_id" + ); + +COMMIT; + +-- 验证: +-- SELECT menu_id, menu_name, perms FROM shz.sys_menu +-- WHERE perms IN ('cms:publicJobFair:export', 'cms:outdoorFair:export');