添加功能

This commit is contained in:
马宝龙
2026-06-30 12:55:05 +08:00
parent 0d417b2393
commit f0fdd5981a
6 changed files with 605 additions and 0 deletions

View File

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

View File

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

View File

@@ -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);
/**
* 用工需求增长趋势 - 年度
*

View File

@@ -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<TreadVo> recruitmentTrend(QueryParamDto dto);
/**
* 重点产业招聘需求对比
*
* @param dto 查询参数
* @return 结果列表
*/
List<QueryResultVo> majorIndustryRecruitment(QueryParamDto dto);
/**
* 重点监测行业劳动力动态走势
*
* @param dto 查询参数
* @return 趋势数据
*/
EchartsVo majorIndustryLaborForceTread(QueryParamDto dto);
/**
* 不同类型企业用工规模
*
* @param dto 查询参数
* @return 结果列表
*/
List<QueryResultVo> companyRecruitmentCount(QueryParamDto dto);
/**
* 不同企业类型招聘活跃度
*
* @param dto 查询参数
* @return 结果列表
*/
EchartsVo companyRecruitmentWay(QueryParamDto dto);
/**
* 不同类型企业的人才结构需求
*
* @param dto 查询参数
* @return 结果列表
*/
EchartsVo companyRecruitmentEducationLevel(QueryParamDto dto);
/**
* 招聘需求区域分布
*
* @param dto 查询参数
* @return 结果列表
*/
List<QueryResultVo> recruitmentArea(QueryParamDto dto);
/**
* 承诺薪酬分布分布
*
* @param dto 查询参数
* @return 结果列表
*/
List<QueryResultVo> commitmentSalary(QueryParamDto dto);
}

View File

@@ -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<String> industryList = new ArrayList<>();
for (Tuple2<String, String> 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<TreadVo> recruitmentTrend(QueryParamDto dto) {
List<TreadVo> 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<QueryResultVo> majorIndustryRecruitment(QueryParamDto dto) {
List<QueryResultVo> list = new ArrayList<>();
List<Tuple2<String, String>> 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<String, String> 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<String> xAxis = IntStream.rangeClosed(1, 12)
.mapToObj(i -> i + "")
.collect(Collectors.toList());
List<Tuple2<String, String>> industryList =
Collections.unmodifiableList(new ArrayList<>(Arrays.asList(
Tuples.of("1", "制造业"),
Tuples.of("2", "信息技术"),
Tuples.of("3", "建筑业")
)));
List<EchartsSeriesVo> series = new ArrayList<>();
for (Tuple2<String, String> industry : industryList) {
List<Long> 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<QueryResultVo> companyRecruitmentCount(QueryParamDto dto) {
List<QueryResultVo> list = new ArrayList<>();
List<Tuple2<String, String>> companyTypeList =
Collections.unmodifiableList(new ArrayList<>(Arrays.asList(
Tuples.of("1", "国有企业"),
Tuples.of("2", "民营企业"),
Tuples.of("3", "外资企业"),
Tuples.of("4", "合资企业"),
Tuples.of("5", "事业单位")
)));
for (Tuple2<String, String> 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<String> xAxis = IntStream.rangeClosed(1, 12)
.mapToObj(i -> i + "")
.collect(Collectors.toList());
List<Tuple2<String, String>> wayList =
Collections.unmodifiableList(new ArrayList<>(Arrays.asList(
Tuples.of("1", "线上招聘"),
Tuples.of("2", "线下招聘"),
Tuples.of("3", "校园招聘"),
Tuples.of("4", "内部推荐")
)));
List<EchartsSeriesVo> series = new ArrayList<>();
for (Tuple2<String, String> way : wayList) {
List<Long> 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<String> xAxis = IntStream.rangeClosed(1, 12)
.mapToObj(i -> i + "")
.collect(Collectors.toList());
List<Tuple2<String, String>> levelList =
Collections.unmodifiableList(new ArrayList<>(Arrays.asList(
Tuples.of("1", "高中及以下"),
Tuples.of("2", "大专"),
Tuples.of("3", "本科"),
Tuples.of("4", "硕士及以上")
)));
List<EchartsSeriesVo> series = new ArrayList<>();
for (Tuple2<String, String> level : levelList) {
List<Long> 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<QueryResultVo> recruitmentArea(QueryParamDto dto) {
List<QueryResultVo> list = new ArrayList<>();
for (Tuple2<String, String> 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<QueryResultVo> commitmentSalary(QueryParamDto dto) {
List<QueryResultVo> list = new ArrayList<>();
List<Tuple2<String, String>> 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<String, String> 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;
}
}

View File

@@ -57,6 +57,31 @@
</select>
<select id="queryRecruitmentJobCount"
parameterType="com.ruoyi.cms.domain.dto.QueryParamDto"
resultType="java.lang.Long">
SELECT COUNT(DISTINCT job_id)
FROM theme_recruitment_job
WHERE
<!-- 引入公共条件 -->
<include refid="commonConditions"/>
</select>
<select id="queryAverageSalary"
parameterType="com.ruoyi.cms.domain.dto.QueryParamDto"
resultType="java.math.BigDecimal">
SELECT
COALESCE(ROUND(AVG(((min_salary + max_salary)/2)), 2), 0)
FROM theme_recruitment_job
WHERE
<!-- 引入公共条件 -->
<include refid="commonConditions"/>
</select>
<select id="laborDemandTrendYear"
parameterType="com.ruoyi.cms.domain.dto.QueryParamDto"