diff --git a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/controller/AnalysisLaborForceRecruitmentDemandController.java b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/controller/AnalysisLaborForceRecruitmentDemandController.java new file mode 100644 index 0000000..a801c92 --- /dev/null +++ b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/controller/AnalysisLaborForceRecruitmentDemandController.java @@ -0,0 +1,124 @@ +package com.ruoyi.cms.controller; + +import com.ruoyi.cms.domain.dto.QueryParamDto; +import com.ruoyi.cms.service.AnalysisLaborForceRecruitmentDemandService; +import com.ruoyi.common.core.domain.AjaxResult; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * 劳动力招聘需求分析 + * + * @author 马宝龙 + * @date 2026/6/30 + */ +@RestController +@RequestMapping("/analysis/laborForceRecruitmentDemand") +@RequiredArgsConstructor +public class AnalysisLaborForceRecruitmentDemandController { + + private final AnalysisLaborForceRecruitmentDemandService analysisLaborForceRecruitmentDemandService; + + /** + * 数据总览 + * + * @param dto 查询参数 + * @return 数据总览 + */ + @PostMapping("/overview") + public AjaxResult overview(@RequestBody QueryParamDto dto) { + return AjaxResult.success(analysisLaborForceRecruitmentDemandService.overview(dto)); + } + + /** + * 月度招聘需求规模趋势 + * + * @param dto 查询参数 + * @return 趋势列表 + */ + @PostMapping("/recruitmentTrend") + public AjaxResult recruitmentTrend(@RequestBody QueryParamDto dto) { + return AjaxResult.success(analysisLaborForceRecruitmentDemandService.recruitmentTrend(dto)); + } + + /** + * 重点产业招聘需求对比 + * + * @param dto 查询参数 + * @return 结果列表 + */ + @PostMapping("/majorIndustryRecruitment") + public AjaxResult majorIndustryRecruitment(@RequestBody QueryParamDto dto) { + return AjaxResult.success(analysisLaborForceRecruitmentDemandService.majorIndustryRecruitment(dto)); + } + + /** + * 重点监测行业劳动力动态走势 + * + * @param dto 查询参数 + * @return 趋势数据 + */ + @PostMapping("/majorIndustryLaborForceTread") + public AjaxResult majorIndustryLaborForceTread(@RequestBody QueryParamDto dto) { + return AjaxResult.success(analysisLaborForceRecruitmentDemandService.majorIndustryLaborForceTread(dto)); + } + + /** + * 不同类型企业用工规模 + * + * @param dto 查询参数 + * @return 结果列表 + */ + @PostMapping("/companyRecruitmentCount") + public AjaxResult companyRecruitmentCount(@RequestBody QueryParamDto dto) { + return AjaxResult.success(analysisLaborForceRecruitmentDemandService.companyRecruitmentCount(dto)); + } + + /** + * 不同企业类型招聘活跃度 + * + * @param dto 查询参数 + * @return 结果列表 + */ + @PostMapping("/companyRecruitmentWay") + public AjaxResult companyRecruitmentWay(@RequestBody QueryParamDto dto) { + return AjaxResult.success(analysisLaborForceRecruitmentDemandService.companyRecruitmentWay(dto)); + } + + /** + * 不同类型企业的人才结构需求 + * + * @param dto 查询参数 + * @return 结果列表 + */ + @PostMapping("/companyRecruitmentEducationLevel") + public AjaxResult companyRecruitmentEducationLevel(@RequestBody QueryParamDto dto) { + return AjaxResult.success(analysisLaborForceRecruitmentDemandService.companyRecruitmentEducationLevel(dto)); + } + + /** + * 招聘需求区域分布 + * + * @param dto 查询参数 + * @return 结果列表 + */ + @PostMapping("/recruitmentArea") + public AjaxResult recruitmentArea(@RequestBody QueryParamDto dto) { + return AjaxResult.success(analysisLaborForceRecruitmentDemandService.recruitmentArea(dto)); + } + + /** + * 承诺薪酬分布分布 + * + * @param dto 查询参数 + * @return 结果列表 + */ + @PostMapping("/commitmentSalary") + public AjaxResult commitmentSalary(@RequestBody QueryParamDto dto) { + return AjaxResult.success(analysisLaborForceRecruitmentDemandService.commitmentSalary(dto)); + } + +} \ No newline at end of file diff --git a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/domain/vo/LaborForceRecruitmentDemandOverviewVo.java b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/domain/vo/LaborForceRecruitmentDemandOverviewVo.java new file mode 100644 index 0000000..b246914 --- /dev/null +++ b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/domain/vo/LaborForceRecruitmentDemandOverviewVo.java @@ -0,0 +1,71 @@ +package com.ruoyi.cms.domain.vo; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; +import java.math.BigDecimal; + +/** + * 劳动力招聘需求分析 - 数据总览 + * + * @author 马宝龙 + * @date 2026/6/25 + */ +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class LaborForceRecruitmentDemandOverviewVo implements Serializable { + private static final long serialVersionUID = -8415943006313275664L; + /** + * 招聘岗位数 + */ + private Long recruitmentJobCount; + /** + * 招聘岗位数环比 + */ + private BigDecimal recruitmentJobCountMom; + /** + * 招聘总人数 + */ + private Long recruitmentCount; + /** + * 招聘总人数环比 + */ + private BigDecimal recruitmentCountMom; + /** + * 市场承诺薪酬 + */ + private BigDecimal marketCommitmentSalary; + /** + * 市场承诺薪酬环比 + */ + private BigDecimal marketCommitmentSalaryMom; + /** + * 求人倍率 + */ + private BigDecimal demandSupplyRate; + /** + * 求人倍率环比 + */ + private BigDecimal demandSupplyRateMom; + /** + * 求人倍率标签 + */ + private String demandSupplyRateTag; + /** + * 求人倍率描述 + */ + private String demandSupplyRateDesc; + /** + * 活跃企业数 + */ + private Long activeCompanyCount; + /** + * 活跃企业数环比 + */ + private BigDecimal activeCompanyCountMom; +} diff --git a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/mapper/ThemeRecruitmentJobMapper.java b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/mapper/ThemeRecruitmentJobMapper.java index 19ec7b1..2104de4 100644 --- a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/mapper/ThemeRecruitmentJobMapper.java +++ b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/mapper/ThemeRecruitmentJobMapper.java @@ -5,6 +5,7 @@ import com.ruoyi.cms.domain.vo.HeatmapVo; import com.ruoyi.cms.domain.vo.MajorIndustryEmploymentMonitorTreadVo; import com.ruoyi.cms.domain.vo.QueryResultVo; +import java.math.BigDecimal; import java.util.List; /** @@ -39,6 +40,22 @@ public interface ThemeRecruitmentJobMapper { */ Long queryEmploymentDemandCount(QueryParamDto dto); + /** + * 查询招聘岗位数 + * + * @param dto 查询参数 + * @return 数 + */ + Long queryRecruitmentJobCount(QueryParamDto dto); + + /** + * 查询平均薪资 + * + * @param dto 查询参数 + * @return 平均薪资 + */ + BigDecimal queryAverageSalary(QueryParamDto dto); + /** * 用工需求增长趋势 - 年度 * diff --git a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/service/AnalysisLaborForceRecruitmentDemandService.java b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/service/AnalysisLaborForceRecruitmentDemandService.java new file mode 100644 index 0000000..0d5fa5e --- /dev/null +++ b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/service/AnalysisLaborForceRecruitmentDemandService.java @@ -0,0 +1,92 @@ +package com.ruoyi.cms.service; + +import com.ruoyi.cms.domain.dto.QueryParamDto; +import com.ruoyi.cms.domain.vo.EchartsVo; +import com.ruoyi.cms.domain.vo.HeatmapVo; +import com.ruoyi.cms.domain.vo.LaborForceRecruitmentDemandOverviewVo; +import com.ruoyi.cms.domain.vo.QueryResultVo; +import com.ruoyi.cms.domain.vo.TreadVo; + +import java.util.List; + +/** + * 劳动力招聘需求分析 + * + * @author 马宝龙 + * @date 2026/6/30 + */ +public interface AnalysisLaborForceRecruitmentDemandService { + + + /** + * 数据总览 + * + * @param dto 查询参数 + * @return 数据总览 + */ + LaborForceRecruitmentDemandOverviewVo overview(QueryParamDto dto); + + /** + * 月度招聘需求规模趋势 + * + * @param dto 查询参数 + * @return 趋势列表 + */ + List recruitmentTrend(QueryParamDto dto); + + /** + * 重点产业招聘需求对比 + * + * @param dto 查询参数 + * @return 结果列表 + */ + List majorIndustryRecruitment(QueryParamDto dto); + + /** + * 重点监测行业劳动力动态走势 + * + * @param dto 查询参数 + * @return 趋势数据 + */ + EchartsVo majorIndustryLaborForceTread(QueryParamDto dto); + + /** + * 不同类型企业用工规模 + * + * @param dto 查询参数 + * @return 结果列表 + */ + List companyRecruitmentCount(QueryParamDto dto); + + /** + * 不同企业类型招聘活跃度 + * + * @param dto 查询参数 + * @return 结果列表 + */ + EchartsVo companyRecruitmentWay(QueryParamDto dto); + + /** + * 不同类型企业的人才结构需求 + * + * @param dto 查询参数 + * @return 结果列表 + */ + EchartsVo companyRecruitmentEducationLevel(QueryParamDto dto); + + /** + * 招聘需求区域分布 + * + * @param dto 查询参数 + * @return 结果列表 + */ + List recruitmentArea(QueryParamDto dto); + + /** + * 承诺薪酬分布分布 + * + * @param dto 查询参数 + * @return 结果列表 + */ + List commitmentSalary(QueryParamDto dto); +} \ No newline at end of file diff --git a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/service/impl/AnalysisLaborForceRecruitmentDemandServiceImpl.java b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/service/impl/AnalysisLaborForceRecruitmentDemandServiceImpl.java new file mode 100644 index 0000000..33f6462 --- /dev/null +++ b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/service/impl/AnalysisLaborForceRecruitmentDemandServiceImpl.java @@ -0,0 +1,276 @@ +package com.ruoyi.cms.service.impl; + +import com.ruoyi.cms.constant.CommonConstant; +import com.ruoyi.cms.domain.dto.QueryParamDto; +import com.ruoyi.cms.domain.vo.EchartsSeriesVo; +import com.ruoyi.cms.domain.vo.EchartsVo; +import com.ruoyi.cms.domain.vo.LaborForceRecruitmentDemandOverviewVo; +import com.ruoyi.cms.domain.vo.QueryResultVo; +import com.ruoyi.cms.domain.vo.TreadVo; +import com.ruoyi.cms.mapper.ThemeRecruitmentJobMapper; +import com.ruoyi.cms.service.AnalysisLaborForceRecruitmentDemandService; +import com.ruoyi.cms.util.MathUtil; +import com.ruoyi.cms.util.ParamUtil; +import com.ruoyi.common.utils.bean.BeanUtils; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.validation.annotation.Validated; +import reactor.util.function.Tuple2; +import reactor.util.function.Tuples; + +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +/** + * 劳动力招聘需求分析 + * + * @author 马宝龙 + * @date 2026/6/30 + */ +@Service +@Validated +@Slf4j +@RequiredArgsConstructor +public class AnalysisLaborForceRecruitmentDemandServiceImpl implements AnalysisLaborForceRecruitmentDemandService { + + private final ThemeRecruitmentJobMapper themeRecruitmentJobMapper; + + /** + * 处理参数 + * + * @param dto 参数 + */ + private void dealParam(QueryParamDto dto) { + ParamUtil.dealParam(dto); + // 设置重点行业 + List industryList = new ArrayList<>(); + for (Tuple2 industry : CommonConstant.MAJOR_INDUSTRY_LIST) { + industryList.add(industry.getT1()); + } + dto.setIndustryList(industryList); + } + + + @Override + public LaborForceRecruitmentDemandOverviewVo overview(QueryParamDto dto) { + + // 计算当前时间数据 + ParamUtil.dealParam(dto); + Long recruitmentJobCount = themeRecruitmentJobMapper.queryRecruitmentJobCount(dto); + + Long recruitmentCount = themeRecruitmentJobMapper.queryEmploymentDemandCount(dto); + + BigDecimal marketCommitmentSalary = themeRecruitmentJobMapper.queryAverageSalary(dto); + + // 计算上一时间段数据 + QueryParamDto lastDto = new QueryParamDto(); + BeanUtils.copyProperties(dto, lastDto); + ParamUtil.calculateLastTime(lastDto); + Long lastRecruitmentJobCount = themeRecruitmentJobMapper.queryRecruitmentJobCount(lastDto); + + Long lastRecruitmentCount = themeRecruitmentJobMapper.queryEmploymentDemandCount(lastDto); + + BigDecimal lastMarketCommitmentSalary = themeRecruitmentJobMapper.queryAverageSalary(lastDto); + + + return LaborForceRecruitmentDemandOverviewVo.builder() + .recruitmentJobCount(recruitmentJobCount) + .recruitmentJobCountMom( + MathUtil.calculatePercentageGrowthRate(lastRecruitmentJobCount,recruitmentJobCount)) + .recruitmentCount(recruitmentCount) + .recruitmentCountMom(MathUtil.calculatePercentageGrowthRate(lastRecruitmentCount,recruitmentCount)) + .marketCommitmentSalary(marketCommitmentSalary) + .marketCommitmentSalaryMom( + MathUtil.calculatePercentageGrowthRate(lastMarketCommitmentSalary,marketCommitmentSalary)) + .demandSupplyRate(BigDecimal.valueOf(1.28)) + .demandSupplyRateMom(BigDecimal.valueOf(-0.03)) + .demandSupplyRateTag("偏紧") + .demandSupplyRateDesc("供需相对均衡") + .activeCompanyCount(860L) + .activeCompanyCountMom(BigDecimal.valueOf(4.8)) + .build(); + } + + @Override + public List recruitmentTrend(QueryParamDto dto) { + List list = new ArrayList<>(); + for (int i = 1; i <= 12; i++) { + long currentCount = (long) Math.floor(Math.random() * 15000) + 5000; + long lastYearCount = (long) Math.floor(Math.random() * 14000) + 4000; + list.add(TreadVo.builder() + .code(String.valueOf(i)) + .desc(i + "月") + .sort(i) + .currentCount(currentCount) + .lastYearCount(lastYearCount) + .build()); + } + MathUtil.calculatePercentageGrowthRate(dto.getQueryTimeType(), list); + return list; + } + + @Override + public List majorIndustryRecruitment(QueryParamDto dto) { + List list = new ArrayList<>(); + List> industryList = + Collections.unmodifiableList(new ArrayList<>(Arrays.asList( + Tuples.of("1", "制造业"), + Tuples.of("2", "信息技术"), + Tuples.of("3", "建筑业"), + Tuples.of("4", "批发零售业"), + Tuples.of("5", "住宿餐饮业"), + Tuples.of("6", "交通运输业") + ))); + for (Tuple2 industry : industryList) { + list.add(QueryResultVo.builder() + .code(industry.getT1()) + .desc(industry.getT2()) + .count((long) Math.floor(Math.random() * 5000) + 500) + .build()); + } + MathUtil.calculatePercentage(list); + return list; + } + + @Override + public EchartsVo majorIndustryLaborForceTread(QueryParamDto dto) { + List xAxis = IntStream.rangeClosed(1, 12) + .mapToObj(i -> i + "月") + .collect(Collectors.toList()); + + List> industryList = + Collections.unmodifiableList(new ArrayList<>(Arrays.asList( + Tuples.of("1", "制造业"), + Tuples.of("2", "信息技术"), + Tuples.of("3", "建筑业") + ))); + + List series = new ArrayList<>(); + for (Tuple2 industry : industryList) { + List dataList = new ArrayList<>(); + for (int i = 0; i < 12; i++) { + dataList.add((long) Math.floor(Math.random() * 4000) + 1000); + } + series.add(EchartsSeriesVo.builder().name(industry.getT2()).data(dataList).build()); + } + + return EchartsVo.builder().xAxis(xAxis).series(series).build(); + } + + @Override + public List companyRecruitmentCount(QueryParamDto dto) { + List list = new ArrayList<>(); + List> companyTypeList = + Collections.unmodifiableList(new ArrayList<>(Arrays.asList( + Tuples.of("1", "国有企业"), + Tuples.of("2", "民营企业"), + Tuples.of("3", "外资企业"), + Tuples.of("4", "合资企业"), + Tuples.of("5", "事业单位") + ))); + for (Tuple2 type : companyTypeList) { + list.add(QueryResultVo.builder() + .code(type.getT1()) + .desc(type.getT2()) + .count((long) Math.floor(Math.random() * 3000) + 200) + .build()); + } + MathUtil.calculatePercentage(list); + return list; + } + + @Override + public EchartsVo companyRecruitmentWay(QueryParamDto dto) { + List xAxis = IntStream.rangeClosed(1, 12) + .mapToObj(i -> i + "月") + .collect(Collectors.toList()); + + List> wayList = + Collections.unmodifiableList(new ArrayList<>(Arrays.asList( + Tuples.of("1", "线上招聘"), + Tuples.of("2", "线下招聘"), + Tuples.of("3", "校园招聘"), + Tuples.of("4", "内部推荐") + ))); + + List series = new ArrayList<>(); + for (Tuple2 way : wayList) { + List dataList = new ArrayList<>(); + for (int i = 0; i < 12; i++) { + dataList.add((long) Math.floor(Math.random() * 300) + 50); + } + series.add(EchartsSeriesVo.builder().name(way.getT2()).data(dataList).build()); + } + + return EchartsVo.builder().xAxis(xAxis).series(series).build(); + } + + @Override + public EchartsVo companyRecruitmentEducationLevel(QueryParamDto dto) { + List xAxis = IntStream.rangeClosed(1, 12) + .mapToObj(i -> i + "月") + .collect(Collectors.toList()); + + List> levelList = + Collections.unmodifiableList(new ArrayList<>(Arrays.asList( + Tuples.of("1", "高中及以下"), + Tuples.of("2", "大专"), + Tuples.of("3", "本科"), + Tuples.of("4", "硕士及以上") + ))); + + List series = new ArrayList<>(); + for (Tuple2 level : levelList) { + List dataList = new ArrayList<>(); + for (int i = 0; i < 12; i++) { + dataList.add((long) Math.floor(Math.random() * 500) + 100); + } + series.add(EchartsSeriesVo.builder().name(level.getT2()).data(dataList).build()); + } + + return EchartsVo.builder().xAxis(xAxis).series(series).build(); + } + + @Override + public List recruitmentArea(QueryParamDto dto) { + List list = new ArrayList<>(); + for (Tuple2 area : CommonConstant.AREA_LIST) { + list.add(QueryResultVo.builder() + .code(area.getT1()) + .desc(area.getT2()) + .count((long) Math.floor(Math.random() * 2000) + 100) + .build()); + } + MathUtil.calculatePercentage(list); + return list; + } + + @Override + public List commitmentSalary(QueryParamDto dto) { + List list = new ArrayList<>(); + List> salaryList = + Collections.unmodifiableList(new ArrayList<>(Arrays.asList( + Tuples.of("1", "2000元以下"), + Tuples.of("2", "2000-4000元"), + Tuples.of("3", "4000-6000元"), + Tuples.of("4", "6000-8000元"), + Tuples.of("5", "8000-10000元"), + Tuples.of("6", "10000元以上") + ))); + for (Tuple2 salary : salaryList) { + list.add(QueryResultVo.builder() + .code(salary.getT1()) + .desc(salary.getT2()) + .count((long) Math.floor(Math.random() * 3000) + 200) + .build()); + } + MathUtil.calculatePercentage(list); + return list; + } +} \ No newline at end of file diff --git a/ruoyi-bussiness/src/main/resources/mapper/ThemeRecruitmentJobMapper.xml b/ruoyi-bussiness/src/main/resources/mapper/ThemeRecruitmentJobMapper.xml index 3afa880..1d568a9 100644 --- a/ruoyi-bussiness/src/main/resources/mapper/ThemeRecruitmentJobMapper.xml +++ b/ruoyi-bussiness/src/main/resources/mapper/ThemeRecruitmentJobMapper.xml @@ -57,6 +57,31 @@ + + + +