政府端统计分析(企业、岗位、就业分析)数据分析(岗位,招聘会,求职,热门岗位,单位规模/性质及其发布岗位数量)
This commit is contained in:
769
docs/cms-statistics-api.md
Normal file
769
docs/cms-statistics-api.md
Normal file
@@ -0,0 +1,769 @@
|
||||
# 统计分析接口文档
|
||||
|
||||
本文档面向 PC 管理端前端,覆盖业务统计、招聘会统计、求职统计、热门岗位统计和用人单位统计。
|
||||
|
||||
## 1. 通用约定
|
||||
|
||||
### 1.1 基础路径
|
||||
|
||||
```text
|
||||
/cms
|
||||
```
|
||||
|
||||
以下接口均为后台管理接口,实际请求时需要按项目现有登录机制携带登录态/Token。
|
||||
|
||||
### 1.2 响应包装
|
||||
|
||||
接口统一使用项目的 `AjaxResult` 响应包装,前端通常从 `data` 读取业务数据:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "操作成功",
|
||||
"data": {}
|
||||
}
|
||||
```
|
||||
|
||||
### 1.3 统计时间参数
|
||||
|
||||
`/cms/statics/*` 统计接口使用以下参数:
|
||||
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
|---|---|---:|---|
|
||||
| `startDate` | string | 否 | 开始日期,支持 `yyyy-MM-dd` 或 `yyyy-MM` |
|
||||
| `endDate` | string | 否 | 结束日期,支持 `yyyy-MM-dd` 或 `yyyy-MM` |
|
||||
| `startTime` | string | 否 | `startDate` 的兼容参数名 |
|
||||
| `endTime` | string | 否 | `endDate` 的兼容参数名 |
|
||||
|
||||
时间规则:
|
||||
|
||||
- 不传开始时间时,默认从当前年度 `1 月 1 日` 开始。
|
||||
- 不传结束时间时,默认到当前日期结束。
|
||||
- 传入 `yyyy-MM` 时,开始月份按当月 1 日处理,结束月份按当月最后一天处理。
|
||||
- 查询时间采用左闭右开区间,结束日期当天的数据会被包含。
|
||||
- `startDate` 和 `endDate` 不能颠倒。
|
||||
|
||||
示例:
|
||||
|
||||
```http
|
||||
GET /cms/statics/business?startDate=2026-01-01&endDate=2026-06-30
|
||||
```
|
||||
|
||||
## 2. 业务统计
|
||||
|
||||
### 2.1 接口信息
|
||||
|
||||
```http
|
||||
GET /cms/statics/business
|
||||
```
|
||||
|
||||
### 2.2 统计口径
|
||||
|
||||
返回按月统计的数据:
|
||||
|
||||
- 岗位来源:从 `job` 表统计 `data_source`。
|
||||
- 本地石河子模板导入:`data_source = '5'`。
|
||||
- 本地录入:`data_source = '1'`。
|
||||
- 互联网岗位上传:`data_source = '2'`。
|
||||
- 新增注册人数:从 `app_user` 表按 `create_time` 统计。
|
||||
|
||||
岗位和用户均只统计 `del_flag = '0'` 的数据。
|
||||
|
||||
### 2.3 返回结构
|
||||
|
||||
```json
|
||||
{
|
||||
"months": [
|
||||
"2026-01",
|
||||
"2026-02",
|
||||
"2026-03"
|
||||
],
|
||||
"jobSourceSeries": [
|
||||
{
|
||||
"code": "5",
|
||||
"name": "本地石河子模板导入",
|
||||
"data": [12, 18, 21]
|
||||
},
|
||||
{
|
||||
"code": "1",
|
||||
"name": "本地录入",
|
||||
"data": [8, 10, 13]
|
||||
},
|
||||
{
|
||||
"code": "2",
|
||||
"name": "互联网岗位上传",
|
||||
"data": [35, 42, 50]
|
||||
}
|
||||
],
|
||||
"newRegisterSeries": {
|
||||
"code": "register",
|
||||
"name": "新增注册人数",
|
||||
"data": [20, 31, 46]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2.4 字段说明
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| `months` | string[] | 横坐标月份,格式为 `yyyy-MM` |
|
||||
| `jobSourceSeries` | object[] | 三种岗位来源序列 |
|
||||
| `jobSourceSeries[].code` | string | 来源编码:`5`、`1`、`2` |
|
||||
| `jobSourceSeries[].name` | string | 来源名称 |
|
||||
| `jobSourceSeries[].data` | number[] | 与 `months` 按下标一一对应 |
|
||||
| `newRegisterSeries` | object | 新增注册人数序列 |
|
||||
| `newRegisterSeries.data` | number[] | 与 `months` 按下标一一对应 |
|
||||
|
||||
### 2.5 前端图表建议
|
||||
|
||||
- 折线图或柱状图横轴使用 `months`。
|
||||
- `jobSourceSeries` 可以绘制三条岗位来源曲线。
|
||||
- `newRegisterSeries` 单独绘制新增注册人数曲线,或作为第二个图表的数据源。
|
||||
|
||||
## 3. 招聘会统计
|
||||
|
||||
### 3.1 接口信息
|
||||
|
||||
```http
|
||||
GET /cms/outdoor-fair/statistics
|
||||
```
|
||||
|
||||
### 3.2 请求参数
|
||||
|
||||
| 参数 | 类型 | 必填 | 可选值/说明 |
|
||||
|---|---|---:|---|
|
||||
| `granularity` | string | 否 | `day`、`week`、`month`、`quarter`、`year`,默认 `month` |
|
||||
| `startTime` | string | 否 | 格式为 `yyyy-MM-dd HH:mm:ss` |
|
||||
| `endTime` | string | 否 | 格式为 `yyyy-MM-dd HH:mm:ss` |
|
||||
|
||||
示例:
|
||||
|
||||
```http
|
||||
GET /cms/outdoor-fair/statistics?granularity=quarter&startTime=2026-01-01%2000:00:00&endTime=2026-12-31%2023:59:59
|
||||
```
|
||||
|
||||
### 3.3 时间规则
|
||||
|
||||
- `granularity=day`:按天统计。
|
||||
- `granularity=week`:按周一作为一周开始统计。
|
||||
- `granularity=month`:按月统计,默认值。
|
||||
- `granularity=quarter`:按季度统计,Q1 为 1-3 月,Q2 为 4-6 月,Q3 为 7-9 月,Q4 为 10-12 月。
|
||||
- `granularity=year`:按年统计。
|
||||
- 不传任何时间参数时,累计统计全部招聘会,曲线时间范围根据招聘会实际举办时间的最小值和最大值生成。
|
||||
- 只传 `endTime` 时,默认向前推 12 个月作为开始范围。
|
||||
- 只传 `startTime` 时,结束时间默认为当前日期。
|
||||
|
||||
### 3.4 返回结构
|
||||
|
||||
```json
|
||||
{
|
||||
"granularity": "quarter",
|
||||
"totals": {
|
||||
"fairCount": 12,
|
||||
"companyCount": 86,
|
||||
"jobCount": 430
|
||||
},
|
||||
"yoy": {
|
||||
"fairCount": 12,
|
||||
"previousFairCount": 9,
|
||||
"rate": 0.3333333333
|
||||
},
|
||||
"mom": {
|
||||
"fairCount": 12,
|
||||
"previousFairCount": 10,
|
||||
"rate": 0.2
|
||||
},
|
||||
"series": [
|
||||
{
|
||||
"time": "2026-Q1",
|
||||
"fairCount": 3,
|
||||
"companyCount": 20,
|
||||
"jobCount": 105
|
||||
},
|
||||
{
|
||||
"time": "2026-Q2",
|
||||
"fairCount": 4,
|
||||
"companyCount": 28,
|
||||
"jobCount": 140
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### 3.5 字段说明
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| `granularity` | string | 实际使用的统计粒度 |
|
||||
| `totals.fairCount` | number | 区间内招聘会数量 |
|
||||
| `totals.companyCount` | number | 区间内参会单位数量 |
|
||||
| `totals.jobCount` | number | 区间内招聘会岗位数量 |
|
||||
| `yoy.fairCount` | number | 当前区间招聘会数量 |
|
||||
| `yoy.previousFairCount` | number | 去年同期招聘会数量 |
|
||||
| `yoy.rate` | number/null | 同比变化率,前值为 0 时返回 `null` |
|
||||
| `mom.fairCount` | number | 当前区间招聘会数量 |
|
||||
| `mom.previousFairCount` | number | 上一个等长区间招聘会数量 |
|
||||
| `mom.rate` | number/null | 环比变化率,前值为 0 时返回 `null` |
|
||||
| `series` | object[] | 各时间桶的曲线数据 |
|
||||
| `series[].time` | string | 时间桶名称 |
|
||||
| `series[].fairCount` | number | 当前时间桶招聘会数量 |
|
||||
| `series[].companyCount` | number | 当前时间桶参会单位数量 |
|
||||
| `series[].jobCount` | number | 当前时间桶岗位数量 |
|
||||
|
||||
时间桶格式:
|
||||
|
||||
| 粒度 | `series[].time` 格式 | 示例 |
|
||||
|---|---|---|
|
||||
| `day` | `yyyy-MM-dd` | `2026-07-30` |
|
||||
| `week` | `yyyy-Www` | `2026-W31` |
|
||||
| `month` | `yyyy-MM` | `2026-07` |
|
||||
| `quarter` | `yyyy-Qn` | `2026-Q3` |
|
||||
| `year` | `yyyy` | `2026` |
|
||||
|
||||
同比、环比的 `rate` 是小数,不是百分数字符串。前端展示百分比时可乘以 100 后保留两位小数,例如 `0.3333` 展示为 `33.33%`。
|
||||
|
||||
## 4. 求职统计
|
||||
|
||||
### 4.1 接口信息
|
||||
|
||||
```http
|
||||
GET /cms/statics/jobSeeker
|
||||
```
|
||||
|
||||
### 4.2 统计口径
|
||||
|
||||
只统计求职者:
|
||||
|
||||
```text
|
||||
app_user.is_company_user = '1'
|
||||
```
|
||||
|
||||
时间按 `app_user.create_time` 过滤。
|
||||
|
||||
统计维度:
|
||||
|
||||
- 年龄:`app_user.age`,使用 `dict_type='age'` 字典翻译名称。
|
||||
- 工作经验:`app_user.work_experience`,使用 `dict_type='experience'` 字典翻译名称。
|
||||
- 专业/求职方向:解析 `app_user.job_title_id`,关联 `job_title` 获取岗位名称。
|
||||
|
||||
### 4.3 返回结构
|
||||
|
||||
```json
|
||||
{
|
||||
"total": 156,
|
||||
"age": [
|
||||
{
|
||||
"code": "18-25",
|
||||
"label": "18-25岁",
|
||||
"count": 58
|
||||
},
|
||||
{
|
||||
"code": "26-35",
|
||||
"label": "26-35岁",
|
||||
"count": 72
|
||||
}
|
||||
],
|
||||
"workExperience": [
|
||||
{
|
||||
"code": "0",
|
||||
"label": "无经验",
|
||||
"count": 40
|
||||
},
|
||||
{
|
||||
"code": "1-3",
|
||||
"label": "1-3年",
|
||||
"count": 63
|
||||
}
|
||||
],
|
||||
"profession": [
|
||||
{
|
||||
"code": "101",
|
||||
"label": "行政管理",
|
||||
"count": 25
|
||||
},
|
||||
{
|
||||
"code": "102",
|
||||
"label": "销售",
|
||||
"count": 31
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### 4.4 字段说明
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| `total` | number | 时间范围内求职者总数 |
|
||||
| `age` | object[] | 年龄分组统计 |
|
||||
| `workExperience` | object[] | 工作经验分组统计 |
|
||||
| `profession` | object[] | 专业/期望岗位统计 |
|
||||
| `*.code` | string | 字典值或岗位 ID |
|
||||
| `*.label` | string | 前端显示名称 |
|
||||
| `*.count` | number | 该分组人数 |
|
||||
|
||||
求职者选择多个期望岗位时,会分别计入多个专业/求职方向分组;因此 `profession` 各项之和可能大于 `total`。
|
||||
|
||||
### 4.5 前端图表建议
|
||||
|
||||
- `age` 使用饼图或柱状图。
|
||||
- `workExperience` 使用饼图或柱状图。
|
||||
- `profession` 使用横向柱状图,岗位较多时建议按数量倒序显示 Top N。
|
||||
|
||||
## 5. 热门岗位统计
|
||||
|
||||
### 5.1 接口信息
|
||||
|
||||
```http
|
||||
GET /cms/statics/hotJob
|
||||
```
|
||||
|
||||
### 5.2 统计口径
|
||||
|
||||
热门岗位名称来自业务字典数据表:
|
||||
|
||||
```sql
|
||||
bussiness_dict_data.dict_type = 'job_hot'
|
||||
```
|
||||
|
||||
使用字典项的 `dict_label` 作为热门岗位名称,再按岗位名称前缀匹配 `job.job_title`:
|
||||
|
||||
```sql
|
||||
TRIM(job.job_title) LIKE TRIM(dict_label) || '%'
|
||||
```
|
||||
|
||||
例如字典中配置 `洗碗工`,会匹配:
|
||||
|
||||
```text
|
||||
洗碗工
|
||||
洗碗工杂工
|
||||
洗碗工服务员
|
||||
洗碗工(包吃住)
|
||||
```
|
||||
|
||||
只统计 `job.del_flag = '0'` 的岗位,并按 `job.create_time` 过滤时间范围。
|
||||
|
||||
### 5.3 返回结构
|
||||
|
||||
```json
|
||||
{
|
||||
"items": [
|
||||
{
|
||||
"code": "wash_worker",
|
||||
"name": "洗碗工",
|
||||
"jobCount": 8
|
||||
},
|
||||
{
|
||||
"code": "sales",
|
||||
"name": "销售",
|
||||
"jobCount": 32
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### 5.4 字段说明
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| `items` | object[] | 热门岗位统计列表 |
|
||||
| `items[].code` | string | 字典 `dict_value` |
|
||||
| `items[].name` | string | 字典 `dict_label`,前端图表显示名称 |
|
||||
| `items[].jobCount` | number | 匹配到的岗位数量 |
|
||||
|
||||
字典中已经配置但时间范围内没有匹配岗位的热门岗位也会返回,`jobCount` 为 `0`,便于图表保持固定分类顺序。
|
||||
|
||||
### 5.5 前端图表建议
|
||||
|
||||
```javascript
|
||||
const categories = data.items.map(item => item.name)
|
||||
const values = data.items.map(item => item.jobCount)
|
||||
```
|
||||
|
||||
建议使用柱状图或横向柱状图展示热门岗位数量。
|
||||
|
||||
## 6. 用人单位统计
|
||||
|
||||
### 6.1 接口信息
|
||||
|
||||
```http
|
||||
GET /cms/statics/company
|
||||
```
|
||||
|
||||
### 6.2 统计内容
|
||||
|
||||
接口返回两组统计:
|
||||
|
||||
- `scale`:按企业规模统计单位数量和发布岗位数量。
|
||||
- `nature`:按企业性质统计单位数量和发布岗位数量。
|
||||
|
||||
单位数量按 `company.create_time` 过滤;发布岗位数量按 `job.create_time` 过滤。这样历史企业在查询时间范围内发布新岗位时,岗位数量仍然可以被统计。
|
||||
|
||||
有效单位条件:
|
||||
|
||||
```text
|
||||
company.del_flag = '0'
|
||||
company.status = 1
|
||||
company.company_status = '0'
|
||||
```
|
||||
|
||||
有效发布岗位条件:
|
||||
|
||||
```text
|
||||
job.del_flag = '0'
|
||||
job.is_publish = 1
|
||||
job.job_status = '0'
|
||||
job.review_status = '1'
|
||||
```
|
||||
|
||||
### 6.3 企业规模分类
|
||||
|
||||
从 `dict_type='scale'` 的字典值读取企业规模,并按以下规则归并:
|
||||
|
||||
| 字典值 | 返回编码 | 返回名称 |
|
||||
|---|---|---|
|
||||
| `1` | `1` | 微型 |
|
||||
| `2` | `2` | 小型 |
|
||||
| `3` | `3` | 中型 |
|
||||
| `4、5` | `4_5` | 大型 |
|
||||
| `6、7` | `6_7` | 超大型 |
|
||||
|
||||
例如 `4` 和 `5` 的企业都会汇总到 `大型`,对应的 `jobCount` 也会合并统计。
|
||||
|
||||
### 6.4 企业性质分类
|
||||
|
||||
从 `dict_type='company_nature'` 且状态正常的字典数据读取企业性质,并使用字典 `dict_value` 匹配:
|
||||
|
||||
```text
|
||||
company.company_nature
|
||||
```
|
||||
|
||||
如果当前字典中只配置了国企,则 `nature` 只返回国企;如果后续新增规上企业等字典项,接口会自动返回新增分类。
|
||||
|
||||
### 6.5 返回结构
|
||||
|
||||
```json
|
||||
{
|
||||
"scale": [
|
||||
{
|
||||
"code": "1",
|
||||
"name": "微型",
|
||||
"companyCount": 3,
|
||||
"jobCount": 5
|
||||
},
|
||||
{
|
||||
"code": "2",
|
||||
"name": "小型",
|
||||
"companyCount": 20,
|
||||
"jobCount": 46
|
||||
},
|
||||
{
|
||||
"code": "3",
|
||||
"name": "中型",
|
||||
"companyCount": 8,
|
||||
"jobCount": 31
|
||||
},
|
||||
{
|
||||
"code": "4_5",
|
||||
"name": "大型",
|
||||
"companyCount": 4,
|
||||
"jobCount": 22
|
||||
},
|
||||
{
|
||||
"code": "6_7",
|
||||
"name": "超大型",
|
||||
"companyCount": 1,
|
||||
"jobCount": 12
|
||||
}
|
||||
],
|
||||
"nature": [
|
||||
{
|
||||
"code": "1",
|
||||
"name": "国企",
|
||||
"companyCount": 5,
|
||||
"jobCount": 18
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### 6.6 字段说明
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| `scale` | object[] | 企业规模统计列表 |
|
||||
| `nature` | object[] | 企业性质统计列表 |
|
||||
| `*.code` | string | 统计分类编码 |
|
||||
| `*.name` | string | 前端显示名称 |
|
||||
| `*.companyCount` | number | 该分类下单位数量 |
|
||||
| `*.jobCount` | number | 该分类下有效发布岗位数量 |
|
||||
|
||||
### 6.7 前端图表建议
|
||||
|
||||
可以按 `scale` 和 `nature` 各绘制一个双系列柱状图:
|
||||
|
||||
```javascript
|
||||
const categories = data.scale.map(item => item.name)
|
||||
const companyData = data.scale.map(item => item.companyCount)
|
||||
const jobData = data.scale.map(item => item.jobCount)
|
||||
```
|
||||
|
||||
系列名称建议:
|
||||
|
||||
```text
|
||||
单位数量
|
||||
发布岗位数量
|
||||
```
|
||||
|
||||
## 7. 区划企业分析
|
||||
|
||||
### 7.1 接口信息
|
||||
|
||||
```http
|
||||
GET /cms/areaAnalysis/company
|
||||
```
|
||||
|
||||
### 7.2 请求参数
|
||||
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
|---|---|---:|---|
|
||||
| `areaCode` | string | 否 | 区划字典值,不传时查询师市整体 |
|
||||
| `startDate` | string | 否 | 开始日期,格式 `yyyy-MM-dd` |
|
||||
| `endDate` | string | 否 | 结束日期,格式 `yyyy-MM-dd` |
|
||||
|
||||
`areaCode` 使用业务字典 `dict_type='area'` 的 `dict_value`,前端不需要直接传 `sys_area.code`。例如区划选择器应使用区域字典接口返回的 `dict_value` 作为请求参数。
|
||||
|
||||
### 7.3 统计口径
|
||||
|
||||
企业统计条件:
|
||||
|
||||
```text
|
||||
company.del_flag = '0'
|
||||
company.status = 1
|
||||
company.company_status = '0'
|
||||
```
|
||||
|
||||
统计内容:
|
||||
|
||||
- 单位总数。
|
||||
- 按行业统计单位数量。
|
||||
- 按企业规模统计单位数量,名称使用 `scale` 字典翻译。
|
||||
- 按企业性质统计单位数量,名称使用 `company_nature` 字典翻译。
|
||||
|
||||
### 7.4 返回结构
|
||||
|
||||
```json
|
||||
{
|
||||
"areaCode": "659001000000",
|
||||
"areaName": "石河子市",
|
||||
"totalCompanyCount": 120,
|
||||
"byIndustry": {
|
||||
"制造业": 35,
|
||||
"批发和零售业": 28
|
||||
},
|
||||
"byScale": {
|
||||
"小型": 60,
|
||||
"中型": 42,
|
||||
"大型": 18
|
||||
},
|
||||
"byNature": {
|
||||
"国企": 12
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 7.5 字段说明
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| `areaCode` | string | 实际统计区域编码 |
|
||||
| `areaName` | string | 实际统计区域名称 |
|
||||
| `totalCompanyCount` | number | 有效单位总数 |
|
||||
| `byIndustry` | object | 行业名称到单位数量的映射 |
|
||||
| `byScale` | object | 企业规模名称到单位数量的映射 |
|
||||
| `byNature` | object | 企业性质名称到单位数量的映射 |
|
||||
|
||||
对象类型字段的 key 为图表显示名称,value 为数量。例如:
|
||||
|
||||
```javascript
|
||||
const categories = Object.keys(data.byScale)
|
||||
const values = Object.values(data.byScale)
|
||||
```
|
||||
|
||||
## 8. 区划岗位分析
|
||||
|
||||
### 8.1 接口信息
|
||||
|
||||
```http
|
||||
GET /cms/areaAnalysis/job
|
||||
```
|
||||
|
||||
### 8.2 请求参数
|
||||
|
||||
参数与企业分析相同:
|
||||
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
|---|---|---:|---|
|
||||
| `areaCode` | string | 否 | `area` 字典的 `dict_value`,不传查询师市整体 |
|
||||
| `startDate` | string | 否 | 格式 `yyyy-MM-dd` |
|
||||
| `endDate` | string | 否 | 格式 `yyyy-MM-dd` |
|
||||
|
||||
### 8.3 统计口径
|
||||
|
||||
岗位统计条件:
|
||||
|
||||
```text
|
||||
job.del_flag = '0'
|
||||
job.is_publish = 1
|
||||
job.job_status = '0'
|
||||
job.review_status = '1'
|
||||
```
|
||||
|
||||
统计内容:
|
||||
|
||||
- 岗位总数。
|
||||
- 招聘总人数。
|
||||
- 急招岗位数量。
|
||||
- 重点人群岗位数量。
|
||||
- 按岗位分类统计。
|
||||
- 按薪资区间统计。
|
||||
- 按学历要求统计。
|
||||
- 按岗位类型统计。
|
||||
|
||||
岗位数量统计使用有效发布岗位。招聘总人数中,岗位 `vacancies` 为空或小于等于 0 时按 1 人计算。
|
||||
|
||||
### 8.4 返回结构
|
||||
|
||||
```json
|
||||
{
|
||||
"areaCode": "659001000000",
|
||||
"areaName": "石河子市",
|
||||
"totalJobCount": 360,
|
||||
"totalVacancies": 780,
|
||||
"urgentJobCount": 48,
|
||||
"keyPopulationJobCount": 22,
|
||||
"byCategory": {
|
||||
"销售": 80,
|
||||
"餐饮服务": 55
|
||||
},
|
||||
"bySalary": {
|
||||
"3k以下": 30,
|
||||
"3k-5k": 120,
|
||||
"5k-8k": 150,
|
||||
"8k-15k": 48,
|
||||
"15k+": 12
|
||||
},
|
||||
"byEducation": {
|
||||
"不限": 180,
|
||||
"大专": 110,
|
||||
"本科": 70
|
||||
},
|
||||
"byType": {
|
||||
"全职": 300,
|
||||
"兼职": 60
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 8.5 字段说明
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| `areaCode` | string | 实际统计区域编码 |
|
||||
| `areaName` | string | 实际统计区域名称 |
|
||||
| `totalJobCount` | number | 有效发布岗位总数 |
|
||||
| `totalVacancies` | number | 招聘总人数,空值/非正数岗位按 1 人计算 |
|
||||
| `urgentJobCount` | number | `is_urgent = 1` 的岗位数量 |
|
||||
| `keyPopulationJobCount` | number | `is_key_populations = '0'` 的岗位数量 |
|
||||
| `byCategory` | object | 岗位分类到岗位数量的映射 |
|
||||
| `bySalary` | object | 薪资区间到岗位数量的映射 |
|
||||
| `byEducation` | object | 学历名称到岗位数量的映射 |
|
||||
| `byType` | object | 岗位类型名称到岗位数量的映射 |
|
||||
|
||||
薪资区间固定为:
|
||||
|
||||
```text
|
||||
3k以下、3k-5k、5k-8k、8k-15k、15k+
|
||||
```
|
||||
|
||||
## 9. 区划就业分析
|
||||
|
||||
### 9.1 接口信息
|
||||
|
||||
```http
|
||||
GET /cms/areaAnalysis/employment
|
||||
```
|
||||
|
||||
### 9.2 请求参数
|
||||
|
||||
参数与企业分析、岗位分析相同:
|
||||
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
|---|---|---:|---|
|
||||
| `areaCode` | string | 否 | `area` 字典的 `dict_value`,不传查询师市整体 |
|
||||
| `startDate` | string | 否 | 格式 `yyyy-MM-dd` |
|
||||
| `endDate` | string | 否 | 格式 `yyyy-MM-dd` |
|
||||
|
||||
### 9.3 统计口径
|
||||
|
||||
已就业人数统计有效且已录用的求职记录:
|
||||
|
||||
```text
|
||||
job_apply.del_flag = '0'
|
||||
job_apply.hire = '0'
|
||||
app_user.del_flag = '0'
|
||||
app_user.is_company_user = '1'
|
||||
app_user.status = '0'
|
||||
```
|
||||
|
||||
就业区域根据求职者 `app_user.area` 进行统计。
|
||||
|
||||
录用来源映射:
|
||||
|
||||
| `hire_source` | 返回名称 |
|
||||
|---|---|
|
||||
| `0` | 本系统 |
|
||||
| `1` | 招聘会 |
|
||||
| 其他值 | 其他 |
|
||||
|
||||
新入职确认人数来自 `employee_confirm` 表的有效确认记录。
|
||||
|
||||
### 9.4 返回结构
|
||||
|
||||
```json
|
||||
{
|
||||
"areaCode": "659001000000",
|
||||
"areaName": "石河子市",
|
||||
"totalHired": 86,
|
||||
"byHireSource": {
|
||||
"本系统": 52,
|
||||
"招聘会": 26,
|
||||
"其他": 8
|
||||
},
|
||||
"newEmployeeConfirmed": 31
|
||||
}
|
||||
```
|
||||
|
||||
### 9.5 字段说明
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| `areaCode` | string | 实际统计区域编码 |
|
||||
| `areaName` | string | 实际统计区域名称 |
|
||||
| `totalHired` | number | 有效已录用人数 |
|
||||
| `byHireSource` | object | 录用来源名称到人数的映射 |
|
||||
| `newEmployeeConfirmed` | number | 新入职确认人数 |
|
||||
|
||||
## 10. 接口汇总
|
||||
|
||||
| 统计模块 | 请求地址 | 主要用途 |
|
||||
|---|---|---|
|
||||
| 业务统计 | `GET /cms/statics/business` | 按月统计岗位来源和新增注册人数 |
|
||||
| 招聘会统计 | `GET /cms/outdoor-fair/statistics` | 按日/周/月/季度/年度统计招聘会 |
|
||||
| 求职统计 | `GET /cms/statics/jobSeeker` | 统计求职者年龄、工作经验、专业 |
|
||||
| 热门岗位统计 | `GET /cms/statics/hotJob` | 统计 `job_hot` 字典配置的热门岗位数量 |
|
||||
| 用人单位统计 | `GET /cms/statics/company` | 统计单位规模/性质及其发布岗位数量 |
|
||||
| 区划企业分析 | `GET /cms/areaAnalysis/company` | 按区域统计企业总数、行业、规模、性质 |
|
||||
| 区划岗位分析 | `GET /cms/areaAnalysis/job` | 按区域统计岗位、招聘人数、薪资、学历等 |
|
||||
| 区划就业分析 | `GET /cms/areaAnalysis/employment` | 按区域统计已录用和新入职确认数据 |
|
||||
@@ -0,0 +1,72 @@
|
||||
package com.ruoyi.cms.controller.cms;
|
||||
|
||||
import com.ruoyi.cms.domain.query.AreaAnalysisQuery;
|
||||
import com.ruoyi.cms.domain.vo.AreaCompanyDetailVO;
|
||||
import com.ruoyi.cms.domain.vo.AreaEmploymentDetailVO;
|
||||
import com.ruoyi.cms.domain.vo.AreaJobDetailVO;
|
||||
import com.ruoyi.cms.service.IAreaAnalysisService;
|
||||
import com.ruoyi.common.core.controller.BaseController;
|
||||
import com.ruoyi.common.core.domain.AjaxResult;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import io.swagger.annotations.ApiParam;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/**
|
||||
* 区划数据分析 Controller
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/cms/areaAnalysis")
|
||||
@Api(tags = "后台:区划数据分析")
|
||||
public class AreaAnalysisController extends BaseController {
|
||||
|
||||
@Autowired
|
||||
private IAreaAnalysisService areaAnalysisService;
|
||||
|
||||
@ApiOperation("企业分析")
|
||||
@PreAuthorize("@ss.hasPermi('cms:areaAnalysis:query')")
|
||||
@GetMapping("/company")
|
||||
public AjaxResult companyAnalysis(
|
||||
@ApiParam("区划编码,不传则查师市整体") @RequestParam(required = false) String areaCode,
|
||||
@ApiParam("开始日期,格式 yyyy-MM-dd") @RequestParam(required = false) String startDate,
|
||||
@ApiParam("结束日期,格式 yyyy-MM-dd") @RequestParam(required = false) String endDate) {
|
||||
AreaAnalysisQuery query = new AreaAnalysisQuery();
|
||||
query.setAreaCode(areaCode);
|
||||
query.setStartDate(startDate);
|
||||
query.setEndDate(endDate);
|
||||
return success(areaAnalysisService.companyAnalysis(query));
|
||||
}
|
||||
|
||||
@ApiOperation("岗位分析")
|
||||
@PreAuthorize("@ss.hasPermi('cms:areaAnalysis:query')")
|
||||
@GetMapping("/job")
|
||||
public AjaxResult jobAnalysis(
|
||||
@ApiParam("区划编码,不传则查师市整体") @RequestParam(required = false) String areaCode,
|
||||
@ApiParam("开始日期,格式 yyyy-MM-dd") @RequestParam(required = false) String startDate,
|
||||
@ApiParam("结束日期,格式 yyyy-MM-dd") @RequestParam(required = false) String endDate) {
|
||||
AreaAnalysisQuery query = new AreaAnalysisQuery();
|
||||
query.setAreaCode(areaCode);
|
||||
query.setStartDate(startDate);
|
||||
query.setEndDate(endDate);
|
||||
return success(areaAnalysisService.jobAnalysis(query));
|
||||
}
|
||||
|
||||
@ApiOperation("就业分析")
|
||||
@PreAuthorize("@ss.hasPermi('cms:areaAnalysis:query')")
|
||||
@GetMapping("/employment")
|
||||
public AjaxResult employmentAnalysis(
|
||||
@ApiParam("区划编码,不传则查师市整体") @RequestParam(required = false) String areaCode,
|
||||
@ApiParam("开始日期,格式 yyyy-MM-dd") @RequestParam(required = false) String startDate,
|
||||
@ApiParam("结束日期,格式 yyyy-MM-dd") @RequestParam(required = false) String endDate) {
|
||||
AreaAnalysisQuery query = new AreaAnalysisQuery();
|
||||
query.setAreaCode(areaCode);
|
||||
query.setStartDate(startDate);
|
||||
query.setEndDate(endDate);
|
||||
return success(areaAnalysisService.employmentAnalysis(query));
|
||||
}
|
||||
}
|
||||
@@ -244,11 +244,11 @@ public class OutdoorFairController extends BaseController
|
||||
|
||||
/**
|
||||
* 招聘会数据统计(dashboard)
|
||||
* 按年/周/月/日维度,统计举办时间落在区间内的招聘会:
|
||||
* 按周/月/季度/年维度,统计举办时间落在区间内的招聘会:
|
||||
* - 累计发布招聘会数、参会单位数、职位数;
|
||||
* - 招聘会数同比(去年同期等长区间)、环比(紧邻的上一等长区间);
|
||||
* - 各时间桶的招聘会数/参会单位数/职位数序列(用于曲线图)。
|
||||
* granularity 取值:day/week/month/year,默认 month。未传时间时统计全部招聘会
|
||||
* granularity 取值:day/week/month/quarter/year,默认 month。未传时间时统计全部招聘会
|
||||
* (“累计”口径),曲线图区间按数据实际举办时间的最小/最大值生成。
|
||||
*/
|
||||
@ApiOperation("招聘会数据统计")
|
||||
@@ -400,6 +400,9 @@ public class OutdoorFairController extends BaseController
|
||||
return ld;
|
||||
case "week":
|
||||
return ld.with(TemporalAdjusters.previousOrSame(DayOfWeek.MONDAY));
|
||||
case "quarter":
|
||||
int firstMonthOfQuarter = ((ld.getMonthValue() - 1) / 3) * 3 + 1;
|
||||
return ld.withMonth(firstMonthOfQuarter).withDayOfMonth(1);
|
||||
case "year":
|
||||
return ld.withDayOfYear(1);
|
||||
default:
|
||||
@@ -421,6 +424,9 @@ public class OutdoorFairController extends BaseController
|
||||
case "week":
|
||||
cur = cur.plusWeeks(1);
|
||||
break;
|
||||
case "quarter":
|
||||
cur = cur.plusMonths(3);
|
||||
break;
|
||||
case "year":
|
||||
cur = cur.plusYears(1);
|
||||
break;
|
||||
@@ -438,6 +444,8 @@ public class OutdoorFairController extends BaseController
|
||||
return DateTimeFormatter.ofPattern("yyyy-MM-dd");
|
||||
case "week":
|
||||
return DateTimeFormatter.ofPattern("yyyy-'W'ww");
|
||||
case "quarter":
|
||||
return DateTimeFormatter.ofPattern("yyyy-'Q'Q");
|
||||
case "year":
|
||||
return DateTimeFormatter.ofPattern("yyyy");
|
||||
default:
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
package com.ruoyi.cms.controller.cms;
|
||||
|
||||
import com.ruoyi.cms.domain.BussinessDictType;
|
||||
import com.ruoyi.cms.domain.query.BusinessStatisticsQuery;
|
||||
import com.ruoyi.cms.domain.query.Staticsquery;
|
||||
import com.ruoyi.cms.service.IBusinessStatisticsService;
|
||||
import com.ruoyi.cms.service.StaticsqueryService;
|
||||
import com.ruoyi.common.core.controller.BaseController;
|
||||
import com.ruoyi.common.core.domain.AjaxResult;
|
||||
import com.ruoyi.common.core.page.TableDataInfo;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
@@ -19,6 +22,49 @@ import java.util.Map;
|
||||
public class StaticsController extends BaseController {
|
||||
@Autowired
|
||||
private StaticsqueryService service;
|
||||
|
||||
@Autowired
|
||||
private IBusinessStatisticsService businessStatisticsService;
|
||||
|
||||
/**
|
||||
* 岗位来源和新增注册人数月趋势统计。
|
||||
*/
|
||||
@ApiOperation("岗位来源和新增注册人数月趋势统计")
|
||||
@GetMapping("/business")
|
||||
public AjaxResult business(BusinessStatisticsQuery query)
|
||||
{
|
||||
return success(businessStatisticsService.trend(query));
|
||||
}
|
||||
|
||||
/**
|
||||
* 按年龄、工作经验和专业统计求职者。
|
||||
*/
|
||||
@ApiOperation("求职者统计")
|
||||
@GetMapping("/jobSeeker")
|
||||
public AjaxResult jobSeeker(BusinessStatisticsQuery query)
|
||||
{
|
||||
return success(businessStatisticsService.jobSeeker(query));
|
||||
}
|
||||
|
||||
/**
|
||||
* 按 job_hot 业务字典配置统计热门岗位。
|
||||
*/
|
||||
@ApiOperation("热门岗位统计")
|
||||
@GetMapping("/hotJob")
|
||||
public AjaxResult hotJob(BusinessStatisticsQuery query)
|
||||
{
|
||||
return success(businessStatisticsService.hotJob(query));
|
||||
}
|
||||
|
||||
/**
|
||||
* 按企业规模、企业性质统计单位数量和发布岗位数量。
|
||||
*/
|
||||
@ApiOperation("用人单位统计")
|
||||
@GetMapping("/company")
|
||||
public AjaxResult company(BusinessStatisticsQuery query)
|
||||
{
|
||||
return success(businessStatisticsService.company(query));
|
||||
}
|
||||
//分行业趋势分析
|
||||
@GetMapping("/industry")
|
||||
public AjaxResult industry(Staticsquery staticsquery)
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.ruoyi.cms.domain.query;
|
||||
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
@ApiModel("区划分析查询参数")
|
||||
public class AreaAnalysisQuery {
|
||||
|
||||
@ApiModelProperty("区划编码,不传则查师市整体")
|
||||
private String areaCode;
|
||||
|
||||
@ApiModelProperty("开始日期,格式 yyyy-MM-dd")
|
||||
private String startDate;
|
||||
|
||||
@ApiModelProperty("结束日期,格式 yyyy-MM-dd")
|
||||
private String endDate;
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.ruoyi.cms.domain.query;
|
||||
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 业务统计查询参数。
|
||||
*/
|
||||
@Data
|
||||
@ApiModel("业务统计查询参数")
|
||||
public class BusinessStatisticsQuery {
|
||||
|
||||
@ApiModelProperty("开始日期,支持 yyyy-MM 或 yyyy-MM-dd;不传时默认为本年度 1 月 1 日")
|
||||
private String startDate;
|
||||
|
||||
@ApiModelProperty("结束日期,支持 yyyy-MM 或 yyyy-MM-dd;不传时默认为当前日期")
|
||||
private String endDate;
|
||||
|
||||
/** 兼容现有 /cms/statics 接口使用的参数命名。 */
|
||||
@ApiModelProperty(hidden = true)
|
||||
private String startTime;
|
||||
|
||||
/** 兼容现有 /cms/statics 接口使用的参数命名。 */
|
||||
@ApiModelProperty(hidden = true)
|
||||
private String endTime;
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package com.ruoyi.cms.domain.vo;
|
||||
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Map;
|
||||
|
||||
@Data
|
||||
public class AreaCompanyDetailVO implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@ApiModelProperty("区划编码")
|
||||
private String areaCode;
|
||||
|
||||
@ApiModelProperty("区划名称")
|
||||
private String areaName;
|
||||
|
||||
@ApiModelProperty("企业总数")
|
||||
private Long totalCompanyCount;
|
||||
|
||||
@ApiModelProperty("按行业统计")
|
||||
private Map<String, Long> byIndustry;
|
||||
|
||||
@ApiModelProperty("按规模统计")
|
||||
private Map<String, Long> byScale;
|
||||
|
||||
@ApiModelProperty("按性质统计")
|
||||
private Map<String, Long> byNature;
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package com.ruoyi.cms.domain.vo;
|
||||
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Map;
|
||||
|
||||
@Data
|
||||
public class AreaEmploymentDetailVO implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@ApiModelProperty("区划编码")
|
||||
private String areaCode;
|
||||
|
||||
@ApiModelProperty("区划名称")
|
||||
private String areaName;
|
||||
|
||||
@ApiModelProperty("已录用人数")
|
||||
private Long totalHired;
|
||||
|
||||
@ApiModelProperty("按录用来源统计:本系统/招聘会")
|
||||
private Map<String, Long> byHireSource;
|
||||
|
||||
@ApiModelProperty("新入职确认人数")
|
||||
private Long newEmployeeConfirmed;
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package com.ruoyi.cms.domain.vo;
|
||||
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Map;
|
||||
|
||||
@Data
|
||||
public class AreaJobDetailVO implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@ApiModelProperty("区划编码")
|
||||
private String areaCode;
|
||||
|
||||
@ApiModelProperty("区划名称")
|
||||
private String areaName;
|
||||
|
||||
@ApiModelProperty("岗位总数")
|
||||
private Long totalJobCount;
|
||||
|
||||
@ApiModelProperty("招聘总人数")
|
||||
private Long totalVacancies;
|
||||
|
||||
@ApiModelProperty("急招岗位数")
|
||||
private Long urgentJobCount;
|
||||
|
||||
@ApiModelProperty("重点人群岗位数")
|
||||
private Long keyPopulationJobCount;
|
||||
|
||||
@ApiModelProperty("按岗位分类统计")
|
||||
private Map<String, Long> byCategory;
|
||||
|
||||
@ApiModelProperty("按薪资区间统计")
|
||||
private Map<String, Long> bySalary;
|
||||
|
||||
@ApiModelProperty("按学历要求统计")
|
||||
private Map<String, Long> byEducation;
|
||||
|
||||
@ApiModelProperty("按岗位类型统计")
|
||||
private Map<String, Long> byType;
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package com.ruoyi.cms.domain.vo;
|
||||
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 业务统计图表序列。
|
||||
*/
|
||||
@Data
|
||||
@ApiModel("业务统计图表序列")
|
||||
public class BusinessStatisticsSeriesVO {
|
||||
|
||||
@ApiModelProperty("序列编码")
|
||||
private String code;
|
||||
|
||||
@ApiModelProperty("序列名称")
|
||||
private String name;
|
||||
|
||||
@ApiModelProperty("按月份排列的数据,和 months 一一对应")
|
||||
private List<Long> data = new ArrayList<>();
|
||||
|
||||
public BusinessStatisticsSeriesVO() {
|
||||
}
|
||||
|
||||
public BusinessStatisticsSeriesVO(String code, String name, List<Long> data) {
|
||||
this.code = code;
|
||||
this.name = name;
|
||||
this.data = data;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.ruoyi.cms.domain.vo;
|
||||
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 业务统计图表数据。
|
||||
*/
|
||||
@Data
|
||||
@ApiModel("业务统计")
|
||||
public class BusinessStatisticsVO {
|
||||
|
||||
@ApiModelProperty("统计月份,按时间正序")
|
||||
private List<String> months = new ArrayList<>();
|
||||
|
||||
@ApiModelProperty("岗位来源统计序列:本地石河子模板导入、本地录入、互联网岗位上传")
|
||||
private List<BusinessStatisticsSeriesVO> jobSourceSeries = new ArrayList<>();
|
||||
|
||||
@ApiModelProperty("新增注册人数序列")
|
||||
private BusinessStatisticsSeriesVO newRegisterSeries;
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package com.ruoyi.cms.domain.vo;
|
||||
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 用人单位统计项。
|
||||
*/
|
||||
@Data
|
||||
@ApiModel("用人单位统计项")
|
||||
public class CompanyStatisticsItemVO {
|
||||
|
||||
@ApiModelProperty("字典值")
|
||||
private String code;
|
||||
|
||||
@ApiModelProperty("统计名称")
|
||||
private String name;
|
||||
|
||||
@ApiModelProperty("单位数量")
|
||||
private Long companyCount = 0L;
|
||||
|
||||
@ApiModelProperty("发布岗位数量")
|
||||
private Long jobCount = 0L;
|
||||
|
||||
public CompanyStatisticsItemVO() {
|
||||
}
|
||||
|
||||
public CompanyStatisticsItemVO(String code, String name, Long companyCount, Long jobCount) {
|
||||
this.code = code;
|
||||
this.name = name;
|
||||
this.companyCount = companyCount;
|
||||
this.jobCount = jobCount;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.ruoyi.cms.domain.vo;
|
||||
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 用人单位及其发布岗位统计结果。
|
||||
*/
|
||||
@Data
|
||||
@ApiModel("用人单位统计")
|
||||
public class CompanyStatisticsVO {
|
||||
|
||||
@ApiModelProperty("按企业规模统计:微型、小型、中型、大型、超大型")
|
||||
private List<CompanyStatisticsItemVO> scale = new ArrayList<>();
|
||||
|
||||
@ApiModelProperty("按企业性质统计,使用 company_nature 字典配置")
|
||||
private List<CompanyStatisticsItemVO> nature = new ArrayList<>();
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package com.ruoyi.cms.domain.vo;
|
||||
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 热门岗位统计项。
|
||||
*/
|
||||
@Data
|
||||
@ApiModel("热门岗位统计项")
|
||||
public class HotJobStatisticsItemVO {
|
||||
|
||||
@ApiModelProperty("热门岗位字典值")
|
||||
private String code;
|
||||
|
||||
@ApiModelProperty("热门岗位名称")
|
||||
private String name;
|
||||
|
||||
@ApiModelProperty("岗位数量")
|
||||
private Long jobCount = 0L;
|
||||
|
||||
public HotJobStatisticsItemVO() {
|
||||
}
|
||||
|
||||
public HotJobStatisticsItemVO(String code, String name, Long jobCount) {
|
||||
this.code = code;
|
||||
this.name = name;
|
||||
this.jobCount = jobCount;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.ruoyi.cms.domain.vo;
|
||||
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 热门岗位统计结果。
|
||||
*/
|
||||
@Data
|
||||
@ApiModel("热门岗位统计")
|
||||
public class HotJobStatisticsVO {
|
||||
|
||||
@ApiModelProperty("热门岗位明细")
|
||||
private List<HotJobStatisticsItemVO> items = new ArrayList<>();
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package com.ruoyi.cms.domain.vo;
|
||||
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 求职者统计维度项。
|
||||
*/
|
||||
@Data
|
||||
@ApiModel("求职者统计维度项")
|
||||
public class JobSeekerStatisticsItemVO {
|
||||
|
||||
@ApiModelProperty("维度编码")
|
||||
private String code;
|
||||
|
||||
@ApiModelProperty("维度名称")
|
||||
private String label;
|
||||
|
||||
@ApiModelProperty("求职者人数")
|
||||
private Long count;
|
||||
|
||||
public JobSeekerStatisticsItemVO() {
|
||||
}
|
||||
|
||||
public JobSeekerStatisticsItemVO(String code, String label, Long count) {
|
||||
this.code = code;
|
||||
this.label = label;
|
||||
this.count = count;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package com.ruoyi.cms.domain.vo;
|
||||
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 求职者统计结果。
|
||||
*/
|
||||
@Data
|
||||
@ApiModel("求职者统计")
|
||||
public class JobSeekerStatisticsVO {
|
||||
|
||||
@ApiModelProperty("时间范围内求职者总数")
|
||||
private Long total = 0L;
|
||||
|
||||
@ApiModelProperty("按年龄统计")
|
||||
private List<JobSeekerStatisticsItemVO> age = new ArrayList<>();
|
||||
|
||||
@ApiModelProperty("按工作经验统计")
|
||||
private List<JobSeekerStatisticsItemVO> workExperience = new ArrayList<>();
|
||||
|
||||
@ApiModelProperty("按专业/期望岗位统计")
|
||||
private List<JobSeekerStatisticsItemVO> profession = new ArrayList<>();
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
package com.ruoyi.cms.mapper;
|
||||
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 区划数据分析 Mapper
|
||||
*/
|
||||
public interface AreaAnalysisMapper {
|
||||
|
||||
/** 查询指定区划的企业汇总 */
|
||||
List<Map<String, Object>> selectCompanySummary(@Param("areaCode") String areaCode,
|
||||
@Param("startDate") String startDate,
|
||||
@Param("endDate") String endDate);
|
||||
|
||||
/** 查询企业按行业统计 */
|
||||
List<Map<String, Object>> selectCompanyByIndustry(@Param("areaCode") String areaCode,
|
||||
@Param("startDate") String startDate,
|
||||
@Param("endDate") String endDate);
|
||||
|
||||
/** 查询企业按规模统计 */
|
||||
List<Map<String, Object>> selectCompanyByScale(@Param("areaCode") String areaCode,
|
||||
@Param("startDate") String startDate,
|
||||
@Param("endDate") String endDate);
|
||||
|
||||
/** 查询企业按性质统计 */
|
||||
List<Map<String, Object>> selectCompanyByNature(@Param("areaCode") String areaCode,
|
||||
@Param("startDate") String startDate,
|
||||
@Param("endDate") String endDate);
|
||||
|
||||
/** 查询指定区划的岗位汇总 */
|
||||
List<Map<String, Object>> selectJobSummary(@Param("areaCode") String areaCode,
|
||||
@Param("startDate") String startDate,
|
||||
@Param("endDate") String endDate);
|
||||
|
||||
/** 查询岗位按分类统计 */
|
||||
List<Map<String, Object>> selectJobByCategory(@Param("areaCode") String areaCode,
|
||||
@Param("startDate") String startDate,
|
||||
@Param("endDate") String endDate);
|
||||
|
||||
/** 查询岗位按薪资区间统计 */
|
||||
List<Map<String, Object>> selectJobBySalary(@Param("areaCode") String areaCode,
|
||||
@Param("startDate") String startDate,
|
||||
@Param("endDate") String endDate);
|
||||
|
||||
/** 查询岗位按学历要求统计 */
|
||||
List<Map<String, Object>> selectJobByEducation(@Param("areaCode") String areaCode,
|
||||
@Param("startDate") String startDate,
|
||||
@Param("endDate") String endDate);
|
||||
|
||||
/** 查询岗位按类型统计 */
|
||||
List<Map<String, Object>> selectJobByType(@Param("areaCode") String areaCode,
|
||||
@Param("startDate") String startDate,
|
||||
@Param("endDate") String endDate);
|
||||
|
||||
/** 查询指定区划的就业汇总 */
|
||||
List<Map<String, Object>> selectEmploymentSummary(@Param("areaCode") String areaCode,
|
||||
@Param("startDate") String startDate,
|
||||
@Param("endDate") String endDate);
|
||||
|
||||
/** 查询就业按录用来源统计 */
|
||||
List<Map<String, Object>> selectEmploymentByHireSource(@Param("areaCode") String areaCode,
|
||||
@Param("startDate") String startDate,
|
||||
@Param("endDate") String endDate);
|
||||
|
||||
/** 查询新入职确认人数 */
|
||||
Long selectNewEmployeeConfirmed(@Param("areaCode") String areaCode,
|
||||
@Param("startDate") String startDate,
|
||||
@Param("endDate") String endDate);
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package com.ruoyi.cms.mapper;
|
||||
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 业务统计 Mapper。
|
||||
*/
|
||||
@Mapper
|
||||
public interface BusinessStatisticsMapper {
|
||||
|
||||
/** 按月统计三种来源的岗位新增数量。 */
|
||||
List<Map<String, Object>> selectJobSourceTrend(@Param("startTime") Date startTime,
|
||||
@Param("endTime") Date endTime);
|
||||
|
||||
/** 按月统计新增注册用户数量。 */
|
||||
List<Map<String, Object>> selectNewRegisterTrend(@Param("startTime") Date startTime,
|
||||
@Param("endTime") Date endTime);
|
||||
|
||||
/** 按年龄统计求职者。 */
|
||||
List<Map<String, Object>> selectJobSeekerByAge(@Param("startTime") Date startTime,
|
||||
@Param("endTime") Date endTime);
|
||||
|
||||
/** 按工作经验统计求职者。 */
|
||||
List<Map<String, Object>> selectJobSeekerByWorkExperience(@Param("startTime") Date startTime,
|
||||
@Param("endTime") Date endTime);
|
||||
|
||||
/** 按专业/期望岗位统计求职者。 */
|
||||
List<Map<String, Object>> selectJobSeekerByProfession(@Param("startTime") Date startTime,
|
||||
@Param("endTime") Date endTime);
|
||||
|
||||
/** 按 job_hot 字典配置统计热门岗位。 */
|
||||
List<Map<String, Object>> selectHotJobStatistics(@Param("startTime") Date startTime,
|
||||
@Param("endTime") Date endTime);
|
||||
|
||||
/** 按企业规模、企业性质统计单位数量和发布岗位数量。 */
|
||||
List<Map<String, Object>> selectCompanyStatistics(@Param("startTime") Date startTime,
|
||||
@Param("endTime") Date endTime);
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.ruoyi.cms.service;
|
||||
|
||||
import com.ruoyi.cms.domain.query.AreaAnalysisQuery;
|
||||
import com.ruoyi.cms.domain.vo.AreaCompanyDetailVO;
|
||||
import com.ruoyi.cms.domain.vo.AreaEmploymentDetailVO;
|
||||
import com.ruoyi.cms.domain.vo.AreaJobDetailVO;
|
||||
|
||||
/**
|
||||
* 区划数据分析 Service
|
||||
*/
|
||||
public interface IAreaAnalysisService {
|
||||
|
||||
/** 企业分析 */
|
||||
AreaCompanyDetailVO companyAnalysis(AreaAnalysisQuery query);
|
||||
|
||||
/** 岗位分析 */
|
||||
AreaJobDetailVO jobAnalysis(AreaAnalysisQuery query);
|
||||
|
||||
/** 就业分析 */
|
||||
AreaEmploymentDetailVO employmentAnalysis(AreaAnalysisQuery query);
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.ruoyi.cms.service;
|
||||
|
||||
import com.ruoyi.cms.domain.query.BusinessStatisticsQuery;
|
||||
import com.ruoyi.cms.domain.vo.JobSeekerStatisticsVO;
|
||||
import com.ruoyi.cms.domain.vo.BusinessStatisticsVO;
|
||||
import com.ruoyi.cms.domain.vo.HotJobStatisticsVO;
|
||||
import com.ruoyi.cms.domain.vo.CompanyStatisticsVO;
|
||||
|
||||
/**
|
||||
* 业务统计 Service。
|
||||
*/
|
||||
public interface IBusinessStatisticsService {
|
||||
|
||||
/** 查询岗位来源和新增注册人数的月趋势。 */
|
||||
BusinessStatisticsVO trend(BusinessStatisticsQuery query);
|
||||
|
||||
/** 查询按年龄、工作经验和专业分组的求职者统计。 */
|
||||
JobSeekerStatisticsVO jobSeeker(BusinessStatisticsQuery query);
|
||||
|
||||
/** 查询按 job_hot 字典配置的热门岗位统计。 */
|
||||
HotJobStatisticsVO hotJob(BusinessStatisticsQuery query);
|
||||
|
||||
/** 查询按企业规模、企业性质统计的单位和发布岗位数量。 */
|
||||
CompanyStatisticsVO company(BusinessStatisticsQuery query);
|
||||
}
|
||||
@@ -0,0 +1,231 @@
|
||||
package com.ruoyi.cms.service.impl;
|
||||
|
||||
import com.ruoyi.cms.domain.query.AreaAnalysisQuery;
|
||||
import com.ruoyi.cms.domain.vo.*;
|
||||
import com.ruoyi.cms.mapper.AreaAnalysisMapper;
|
||||
import com.ruoyi.cms.service.IAreaAnalysisService;
|
||||
import com.ruoyi.cms.util.DictUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
@Service
|
||||
public class AreaAnalysisServiceImpl implements IAreaAnalysisService {
|
||||
|
||||
@Autowired
|
||||
private AreaAnalysisMapper areaAnalysisMapper;
|
||||
|
||||
/**
|
||||
* dict_value → sys_area.code 映射
|
||||
* 前端传的 areaCode 是 bussiness_dict_data(dict_type='area') 的 dict_value
|
||||
* Mapper 中需要 sys_area.code 来做下级区划匹配
|
||||
*/
|
||||
private static final Map<String, String> DICT_VALUE_TO_AREA_CODE = new LinkedHashMap<>();
|
||||
static {
|
||||
DICT_VALUE_TO_AREA_CODE.put("0", "659001000000"); // 石河子市
|
||||
DICT_VALUE_TO_AREA_CODE.put("9", "659001100000"); // 石总场 → 北泉镇
|
||||
DICT_VALUE_TO_AREA_CODE.put("21", "659001100000"); // 北泉镇
|
||||
DICT_VALUE_TO_AREA_CODE.put("15", "659001003000"); // 红山街道
|
||||
DICT_VALUE_TO_AREA_CODE.put("16", "659001001000"); // 新城街道
|
||||
DICT_VALUE_TO_AREA_CODE.put("17", "659001005000"); // 东城街道
|
||||
DICT_VALUE_TO_AREA_CODE.put("18", "659001004000"); // 老街街道
|
||||
DICT_VALUE_TO_AREA_CODE.put("19", "659001002000"); // 向阳街道
|
||||
DICT_VALUE_TO_AREA_CODE.put("20", "659001101000"); // 石河子镇
|
||||
DICT_VALUE_TO_AREA_CODE.put("1", "654203503000"); // 121团
|
||||
DICT_VALUE_TO_AREA_CODE.put("11", "654203501000"); // 148团
|
||||
DICT_VALUE_TO_AREA_CODE.put("12", "654203502000"); // 149团
|
||||
DICT_VALUE_TO_AREA_CODE.put("2", "654202501000"); // 133团
|
||||
DICT_VALUE_TO_AREA_CODE.put("13", "654202504000"); // 150团
|
||||
DICT_VALUE_TO_AREA_CODE.put("3", "654202502000"); // 134团
|
||||
DICT_VALUE_TO_AREA_CODE.put("4", "654202503000"); // 136团
|
||||
DICT_VALUE_TO_AREA_CODE.put("14", "659001500000"); // 152团
|
||||
DICT_VALUE_TO_AREA_CODE.put("5", "654201501000"); // 141团
|
||||
DICT_VALUE_TO_AREA_CODE.put("6", "654201502000"); // 142团
|
||||
DICT_VALUE_TO_AREA_CODE.put("7", "654201503000"); // 143团
|
||||
DICT_VALUE_TO_AREA_CODE.put("8", "654201504000"); // 144团
|
||||
DICT_VALUE_TO_AREA_CODE.put("10", "654203504000"); // 147团
|
||||
}
|
||||
|
||||
// ========== 录用来源编码 → 中文名映射 ==========
|
||||
private static final Map<String, String> HIRE_SOURCE_MAP = new LinkedHashMap<>();
|
||||
static {
|
||||
HIRE_SOURCE_MAP.put("system", "本系统");
|
||||
HIRE_SOURCE_MAP.put("fair", "招聘会");
|
||||
HIRE_SOURCE_MAP.put("other", "其他");
|
||||
}
|
||||
|
||||
/**
|
||||
* 将前端传入的 dict_value 转换为 sys_area.code
|
||||
*/
|
||||
private String resolveAreaCode(String dictValue) {
|
||||
if (dictValue == null || dictValue.isEmpty()) {
|
||||
// 未传区域时默认统计石河子市整体,不能放开为全国所有顶级区域。
|
||||
return DICT_VALUE_TO_AREA_CODE.get("0");
|
||||
}
|
||||
return DICT_VALUE_TO_AREA_CODE.getOrDefault(dictValue, dictValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AreaCompanyDetailVO companyAnalysis(AreaAnalysisQuery query) {
|
||||
String areaCode = resolveAreaCode(query.getAreaCode());
|
||||
String startDate = query.getStartDate();
|
||||
String endDate = query.getEndDate();
|
||||
|
||||
List<Map<String, Object>> summaryList = areaAnalysisMapper.selectCompanySummary(areaCode, startDate, endDate);
|
||||
List<Map<String, Object>> byIndustry = areaAnalysisMapper.selectCompanyByIndustry(areaCode, startDate, endDate);
|
||||
List<Map<String, Object>> byScale = areaAnalysisMapper.selectCompanyByScale(areaCode, startDate, endDate);
|
||||
List<Map<String, Object>> byNature = areaAnalysisMapper.selectCompanyByNature(areaCode, startDate, endDate);
|
||||
|
||||
AreaCompanyDetailVO vo = new AreaCompanyDetailVO();
|
||||
long totalCompany = 0L;
|
||||
long keyCompany = 0L;
|
||||
long hrsCompany = 0L;
|
||||
String firstAreaCode = null;
|
||||
String firstAreaName = null;
|
||||
for (Map<String, Object> row : summaryList) {
|
||||
if (firstAreaCode == null) {
|
||||
firstAreaCode = String.valueOf(row.get("area_code"));
|
||||
firstAreaName = String.valueOf(row.get("area_name"));
|
||||
}
|
||||
totalCompany += toLong(row.get("total_company_count"));
|
||||
keyCompany += toLong(row.get("key_company_count"));
|
||||
hrsCompany += toLong(row.get("hrs_company_count"));
|
||||
}
|
||||
vo.setAreaCode(firstAreaCode != null ? firstAreaCode : areaCode);
|
||||
vo.setAreaName(firstAreaName != null ? firstAreaName : "");
|
||||
vo.setTotalCompanyCount(totalCompany);
|
||||
|
||||
vo.setByIndustry(toMap(byIndustry, "industry_key", "count"));
|
||||
vo.setByScale(translateMap(byScale, "scale_key", "count", "scale"));
|
||||
vo.setByNature(translateMap(byNature, "nature_key", "count", "company_nature"));
|
||||
|
||||
return vo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AreaJobDetailVO jobAnalysis(AreaAnalysisQuery query) {
|
||||
String areaCode = resolveAreaCode(query.getAreaCode());
|
||||
String startDate = query.getStartDate();
|
||||
String endDate = query.getEndDate();
|
||||
|
||||
List<Map<String, Object>> byCategory = areaAnalysisMapper.selectJobByCategory(areaCode, startDate, endDate);
|
||||
List<Map<String, Object>> bySalary = areaAnalysisMapper.selectJobBySalary(areaCode, startDate, endDate);
|
||||
List<Map<String, Object>> byEducation = areaAnalysisMapper.selectJobByEducation(areaCode, startDate, endDate);
|
||||
List<Map<String, Object>> byType = areaAnalysisMapper.selectJobByType(areaCode, startDate, endDate);
|
||||
List<Map<String, Object>> summaryList = areaAnalysisMapper.selectJobSummary(areaCode, startDate, endDate);
|
||||
|
||||
AreaJobDetailVO vo = new AreaJobDetailVO();
|
||||
long totalJob = 0L;
|
||||
long totalVacancies = 0L;
|
||||
long urgentJob = 0L;
|
||||
long keyPopJob = 0L;
|
||||
String firstAreaCode = null;
|
||||
String firstAreaName = null;
|
||||
for (Map<String, Object> row : summaryList) {
|
||||
if (firstAreaCode == null) {
|
||||
firstAreaCode = String.valueOf(row.get("area_code"));
|
||||
firstAreaName = String.valueOf(row.get("area_name"));
|
||||
}
|
||||
totalJob += toLong(row.get("total_job_count"));
|
||||
totalVacancies += toLong(row.get("total_vacancies"));
|
||||
urgentJob += toLong(row.get("urgent_job_count"));
|
||||
keyPopJob += toLong(row.get("key_population_job_count"));
|
||||
}
|
||||
vo.setAreaCode(firstAreaCode != null ? firstAreaCode : areaCode);
|
||||
vo.setAreaName(firstAreaName != null ? firstAreaName : "");
|
||||
vo.setTotalJobCount(totalJob);
|
||||
vo.setTotalVacancies(totalVacancies);
|
||||
vo.setUrgentJobCount(urgentJob);
|
||||
vo.setKeyPopulationJobCount(keyPopJob);
|
||||
|
||||
vo.setByCategory(toMap(byCategory, "category_key", "count"));
|
||||
vo.setBySalary(toMap(bySalary, "salary_key", "count"));
|
||||
vo.setByEducation(translateMap(byEducation, "education_key", "count", "education"));
|
||||
vo.setByType(translateMap(byType, "type_key", "count", "position_type"));
|
||||
|
||||
return vo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AreaEmploymentDetailVO employmentAnalysis(AreaAnalysisQuery query) {
|
||||
String areaCode = resolveAreaCode(query.getAreaCode());
|
||||
String startDate = query.getStartDate();
|
||||
String endDate = query.getEndDate();
|
||||
|
||||
List<Map<String, Object>> summaryList = areaAnalysisMapper.selectEmploymentSummary(areaCode, startDate, endDate);
|
||||
List<Map<String, Object>> byHireSource = areaAnalysisMapper.selectEmploymentByHireSource(areaCode, startDate, endDate);
|
||||
// Long newConfirmed = areaAnalysisMapper.selectNewEmployeeConfirmed(areaCode, startDate, endDate);
|
||||
|
||||
AreaEmploymentDetailVO vo = new AreaEmploymentDetailVO();
|
||||
long totalHired = 0L;
|
||||
String firstAreaCode = null;
|
||||
String firstAreaName = null;
|
||||
for (Map<String, Object> row : summaryList) {
|
||||
if (firstAreaCode == null) {
|
||||
firstAreaCode = String.valueOf(row.get("area_code"));
|
||||
firstAreaName = String.valueOf(row.get("area_name"));
|
||||
}
|
||||
totalHired += toLong(row.get("total_hired"));
|
||||
}
|
||||
vo.setAreaCode(firstAreaCode != null ? firstAreaCode : areaCode);
|
||||
vo.setAreaName(firstAreaName != null ? firstAreaName : "");
|
||||
vo.setTotalHired(totalHired);
|
||||
|
||||
vo.setByHireSource(translateStaticMap(toMap(byHireSource, "hire_source_key", "count"), HIRE_SOURCE_MAP));
|
||||
// vo.setNewEmployeeConfirmed(newConfirmed != null ? newConfirmed : 0L);
|
||||
|
||||
return vo;
|
||||
}
|
||||
|
||||
// ========== 字典翻译辅助方法 ==========
|
||||
|
||||
private Map<String, Long> translateMap(List<Map<String, Object>> list, String keyField, String valueField, String dictType) {
|
||||
Map<String, Long> raw = toMap(list, keyField, valueField);
|
||||
Map<String, Long> result = new LinkedHashMap<>();
|
||||
for (Map.Entry<String, Long> entry : raw.entrySet()) {
|
||||
String label = DictUtils.getDictLabel(dictType, entry.getKey());
|
||||
String key = (label != null && !label.isEmpty()) ? label : entry.getKey();
|
||||
result.put(key, entry.getValue());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private Map<String, Long> translateStaticMap(Map<String, Long> raw, Map<String, String> mapping) {
|
||||
Map<String, Long> result = new LinkedHashMap<>();
|
||||
for (Map.Entry<String, Long> entry : raw.entrySet()) {
|
||||
String key = mapping.getOrDefault(entry.getKey(), entry.getKey());
|
||||
result.put(key, entry.getValue());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private Map<String, Long> toMap(List<Map<String, Object>> list, String keyField, String valueField) {
|
||||
Map<String, Long> result = new LinkedHashMap<>();
|
||||
if (list == null) {
|
||||
return result;
|
||||
}
|
||||
for (Map<String, Object> row : list) {
|
||||
Object keyObj = row.get(keyField);
|
||||
if (keyObj == null) {
|
||||
continue;
|
||||
}
|
||||
result.put(String.valueOf(keyObj), toLong(row.get(valueField)));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private long toLong(Object value) {
|
||||
if (value == null) {
|
||||
return 0L;
|
||||
}
|
||||
if (value instanceof Number) {
|
||||
return ((Number) value).longValue();
|
||||
}
|
||||
try {
|
||||
return Long.parseLong(String.valueOf(value));
|
||||
} catch (NumberFormatException e) {
|
||||
return 0L;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,298 @@
|
||||
package com.ruoyi.cms.service.impl;
|
||||
|
||||
import com.ruoyi.cms.domain.query.BusinessStatisticsQuery;
|
||||
import com.ruoyi.cms.domain.vo.BusinessStatisticsSeriesVO;
|
||||
import com.ruoyi.cms.domain.vo.BusinessStatisticsVO;
|
||||
import com.ruoyi.cms.domain.vo.JobSeekerStatisticsItemVO;
|
||||
import com.ruoyi.cms.domain.vo.JobSeekerStatisticsVO;
|
||||
import com.ruoyi.cms.domain.vo.HotJobStatisticsItemVO;
|
||||
import com.ruoyi.cms.domain.vo.HotJobStatisticsVO;
|
||||
import com.ruoyi.cms.domain.vo.CompanyStatisticsItemVO;
|
||||
import com.ruoyi.cms.domain.vo.CompanyStatisticsVO;
|
||||
import com.ruoyi.cms.mapper.BusinessStatisticsMapper;
|
||||
import com.ruoyi.cms.service.IBusinessStatisticsService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.YearMonth;
|
||||
import java.time.ZoneId;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.time.format.DateTimeParseException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 业务统计 Service 实现。
|
||||
*/
|
||||
@Service
|
||||
public class BusinessStatisticsServiceImpl implements IBusinessStatisticsService {
|
||||
|
||||
private static final DateTimeFormatter MONTH_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM");
|
||||
private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd");
|
||||
|
||||
private static final String SOURCE_LOCAL_TEMPLATE = "5";
|
||||
private static final String SOURCE_LOCAL_ENTRY = "1";
|
||||
private static final String SOURCE_INTERNET = "2";
|
||||
|
||||
@Autowired
|
||||
private BusinessStatisticsMapper businessStatisticsMapper;
|
||||
|
||||
@Override
|
||||
public BusinessStatisticsVO trend(BusinessStatisticsQuery query) {
|
||||
DateRange range = resolveDateRange(query);
|
||||
List<YearMonth> monthRange = buildMonthRange(range.start, range.end);
|
||||
List<String> months = new ArrayList<>(monthRange.size());
|
||||
for (YearMonth month : monthRange) {
|
||||
months.add(month.format(MONTH_FORMATTER));
|
||||
}
|
||||
|
||||
Map<String, Map<String, Object>> jobRows = indexByMonth(
|
||||
businessStatisticsMapper.selectJobSourceTrend(range.startTime, range.endTime));
|
||||
Map<String, Map<String, Object>> registerRows = indexByMonth(
|
||||
businessStatisticsMapper.selectNewRegisterTrend(range.startTime, range.endTime));
|
||||
|
||||
List<Long> localTemplateImport = new ArrayList<>(monthRange.size());
|
||||
List<Long> localEntry = new ArrayList<>(monthRange.size());
|
||||
List<Long> internetUpload = new ArrayList<>(monthRange.size());
|
||||
List<Long> newRegister = new ArrayList<>(monthRange.size());
|
||||
|
||||
for (String month : months) {
|
||||
Map<String, Object> jobRow = jobRows.get(month);
|
||||
localTemplateImport.add(toLong(value(jobRow, "local_template_import_count")));
|
||||
localEntry.add(toLong(value(jobRow, "local_entry_count")));
|
||||
internetUpload.add(toLong(value(jobRow, "internet_upload_count")));
|
||||
|
||||
Map<String, Object> registerRow = registerRows.get(month);
|
||||
newRegister.add(toLong(value(registerRow, "register_count")));
|
||||
}
|
||||
|
||||
BusinessStatisticsVO result = new BusinessStatisticsVO();
|
||||
result.setMonths(months);
|
||||
List<BusinessStatisticsSeriesVO> jobSourceSeries = new ArrayList<>(3);
|
||||
jobSourceSeries.add(new BusinessStatisticsSeriesVO(
|
||||
SOURCE_LOCAL_TEMPLATE, "本地石河子模板导入", localTemplateImport));
|
||||
jobSourceSeries.add(new BusinessStatisticsSeriesVO(
|
||||
SOURCE_LOCAL_ENTRY, "本地录入", localEntry));
|
||||
jobSourceSeries.add(new BusinessStatisticsSeriesVO(
|
||||
SOURCE_INTERNET, "互联网岗位上传", internetUpload));
|
||||
result.setJobSourceSeries(jobSourceSeries);
|
||||
result.setNewRegisterSeries(new BusinessStatisticsSeriesVO(
|
||||
"register", "新增注册人数", newRegister));
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JobSeekerStatisticsVO jobSeeker(BusinessStatisticsQuery query) {
|
||||
DateRange range = resolveDateRange(query);
|
||||
List<Map<String, Object>> ageRows = businessStatisticsMapper.selectJobSeekerByAge(
|
||||
range.startTime, range.endTime);
|
||||
List<Map<String, Object>> experienceRows = businessStatisticsMapper.selectJobSeekerByWorkExperience(
|
||||
range.startTime, range.endTime);
|
||||
List<Map<String, Object>> professionRows = businessStatisticsMapper.selectJobSeekerByProfession(
|
||||
range.startTime, range.endTime);
|
||||
|
||||
JobSeekerStatisticsVO result = new JobSeekerStatisticsVO();
|
||||
result.setAge(toDimensionItems(ageRows));
|
||||
result.setWorkExperience(toDimensionItems(experienceRows));
|
||||
result.setProfession(toDimensionItems(professionRows));
|
||||
result.setTotal(result.getAge().stream()
|
||||
.map(JobSeekerStatisticsItemVO::getCount)
|
||||
.filter(java.util.Objects::nonNull)
|
||||
.mapToLong(Long::longValue)
|
||||
.sum());
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HotJobStatisticsVO hotJob(BusinessStatisticsQuery query) {
|
||||
DateRange range = resolveDateRange(query);
|
||||
List<Map<String, Object>> rows = businessStatisticsMapper.selectHotJobStatistics(
|
||||
range.startTime, range.endTime);
|
||||
|
||||
HotJobStatisticsVO result = new HotJobStatisticsVO();
|
||||
if (rows == null) {
|
||||
return result;
|
||||
}
|
||||
|
||||
List<HotJobStatisticsItemVO> items = new ArrayList<>(rows.size());
|
||||
for (Map<String, Object> row : rows) {
|
||||
HotJobStatisticsItemVO item = new HotJobStatisticsItemVO(
|
||||
stringValue(value(row, "code")),
|
||||
stringValue(value(row, "name")),
|
||||
toLong(value(row, "job_count")));
|
||||
items.add(item);
|
||||
}
|
||||
result.setItems(items);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompanyStatisticsVO company(BusinessStatisticsQuery query) {
|
||||
DateRange range = resolveDateRange(query);
|
||||
List<Map<String, Object>> rows = businessStatisticsMapper.selectCompanyStatistics(
|
||||
range.startTime, range.endTime);
|
||||
|
||||
CompanyStatisticsVO result = new CompanyStatisticsVO();
|
||||
if (rows == null) {
|
||||
return result;
|
||||
}
|
||||
|
||||
for (Map<String, Object> row : rows) {
|
||||
CompanyStatisticsItemVO item = new CompanyStatisticsItemVO(
|
||||
stringValue(value(row, "code")),
|
||||
stringValue(value(row, "name")),
|
||||
toLong(value(row, "company_count")),
|
||||
toLong(value(row, "job_count")));
|
||||
String dimension = stringValue(value(row, "dimension"));
|
||||
if ("nature".equalsIgnoreCase(dimension)) {
|
||||
result.getNature().add(item);
|
||||
} else if ("scale".equalsIgnoreCase(dimension)) {
|
||||
result.getScale().add(item);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private DateRange resolveDateRange(BusinessStatisticsQuery query) {
|
||||
LocalDate today = LocalDate.now();
|
||||
String startText = query == null ? null : firstNotBlank(query.getStartDate(), query.getStartTime());
|
||||
String endText = query == null ? null : firstNotBlank(query.getEndDate(), query.getEndTime());
|
||||
|
||||
LocalDate start = isBlank(startText)
|
||||
? today.withDayOfYear(1)
|
||||
: parseDate(startText, false);
|
||||
LocalDate end = isBlank(endText)
|
||||
? today
|
||||
: parseDate(endText, true);
|
||||
if (start.isAfter(end)) {
|
||||
throw new IllegalArgumentException("开始日期不能晚于结束日期");
|
||||
}
|
||||
|
||||
LocalDate endExclusive = end.plusDays(1);
|
||||
return new DateRange(
|
||||
start,
|
||||
end,
|
||||
toDate(start.atStartOfDay()),
|
||||
toDate(endExclusive.atStartOfDay()));
|
||||
}
|
||||
|
||||
/**
|
||||
* yyyy-MM 作为月份查询时,开始日期取月初,结束日期取月末;yyyy-MM-dd 按日期本身处理。
|
||||
*/
|
||||
private LocalDate parseDate(String text, boolean endDate) {
|
||||
String value = text == null ? "" : text.trim();
|
||||
try {
|
||||
if (value.matches("\\d{4}-\\d{2}")) {
|
||||
YearMonth month = YearMonth.parse(value, MONTH_FORMATTER);
|
||||
return endDate ? month.atEndOfMonth() : month.atDay(1);
|
||||
}
|
||||
return LocalDate.parse(value, DATE_FORMATTER);
|
||||
} catch (DateTimeParseException ex) {
|
||||
throw new IllegalArgumentException("日期格式错误,请使用 yyyy-MM 或 yyyy-MM-dd");
|
||||
}
|
||||
}
|
||||
|
||||
private List<YearMonth> buildMonthRange(LocalDate start, LocalDate end) {
|
||||
List<YearMonth> result = new ArrayList<>();
|
||||
YearMonth current = YearMonth.from(start);
|
||||
YearMonth last = YearMonth.from(end);
|
||||
while (!current.isAfter(last)) {
|
||||
result.add(current);
|
||||
current = current.plusMonths(1);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private Map<String, Map<String, Object>> indexByMonth(List<Map<String, Object>> rows) {
|
||||
Map<String, Map<String, Object>> result = new LinkedHashMap<>();
|
||||
if (rows == null) {
|
||||
return result;
|
||||
}
|
||||
for (Map<String, Object> row : rows) {
|
||||
Object month = value(row, "month");
|
||||
if (month != null) {
|
||||
result.put(String.valueOf(month), row);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private Object value(Map<String, Object> row, String key) {
|
||||
if (row == null) {
|
||||
return null;
|
||||
}
|
||||
Object value = row.get(key);
|
||||
if (value == null) {
|
||||
value = row.get(key.toUpperCase());
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
private List<JobSeekerStatisticsItemVO> toDimensionItems(List<Map<String, Object>> rows) {
|
||||
List<JobSeekerStatisticsItemVO> result = new ArrayList<>();
|
||||
if (rows == null) {
|
||||
return result;
|
||||
}
|
||||
for (Map<String, Object> row : rows) {
|
||||
Object code = value(row, "code");
|
||||
Object label = value(row, "label");
|
||||
if (label == null) {
|
||||
continue;
|
||||
}
|
||||
result.add(new JobSeekerStatisticsItemVO(
|
||||
code == null ? "unknown" : String.valueOf(code),
|
||||
String.valueOf(label),
|
||||
toLong(value(row, "count"))));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private long toLong(Object value) {
|
||||
if (value == null) {
|
||||
return 0L;
|
||||
}
|
||||
if (value instanceof Number) {
|
||||
return ((Number) value).longValue();
|
||||
}
|
||||
try {
|
||||
return Long.parseLong(String.valueOf(value));
|
||||
} catch (NumberFormatException ex) {
|
||||
return 0L;
|
||||
}
|
||||
}
|
||||
|
||||
private String stringValue(Object value) {
|
||||
return value == null ? null : String.valueOf(value);
|
||||
}
|
||||
|
||||
private boolean isBlank(String value) {
|
||||
return value == null || value.trim().isEmpty();
|
||||
}
|
||||
|
||||
private String firstNotBlank(String first, String second) {
|
||||
return !isBlank(first) ? first : second;
|
||||
}
|
||||
|
||||
private Date toDate(LocalDateTime value) {
|
||||
return Date.from(value.atZone(ZoneId.systemDefault()).toInstant());
|
||||
}
|
||||
|
||||
private static class DateRange {
|
||||
private final LocalDate start;
|
||||
private final LocalDate end;
|
||||
private final Date startTime;
|
||||
private final Date endTime;
|
||||
|
||||
private DateRange(LocalDate start, LocalDate end, Date startTime, Date endTime) {
|
||||
this.start = start;
|
||||
this.end = end;
|
||||
this.startTime = startTime;
|
||||
this.endTime = endTime;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,397 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="com.ruoyi.cms.mapper.AreaAnalysisMapper">
|
||||
|
||||
<!--
|
||||
区划映射说明:
|
||||
前端传入的 areaCode 是 bussiness_dict_data(dict_type='area') 的 dict_value
|
||||
ServiceImpl 中已将 dict_value 转为 sys_area.code,Mapper 中接收的 areaCode 是 sys_area.code
|
||||
job.job_location_area_code 存的是 dict_value,需要通过 bussiness_dict_data 映射到 sys_area.code
|
||||
company.company_city 直接等于 sys_area.code
|
||||
统计下级区划:sys_area.code LIKE CONCAT(CAST(#{areaCode} AS VARCHAR), '%')
|
||||
-->
|
||||
|
||||
<!-- ========== 企业 ========== -->
|
||||
|
||||
<!-- 企业汇总(含下级区划,areaCode 已是 sys_area.code) -->
|
||||
<select id="selectCompanySummary" resultType="java.util.LinkedHashMap">
|
||||
SELECT
|
||||
sa.code AS area_code,
|
||||
sa.name AS area_name,
|
||||
COALESCE(t.total_company, 0) AS total_company_count,
|
||||
COALESCE(t.key_company, 0) AS key_company_count,
|
||||
COALESCE(t.hrs_company, 0) AS hrs_company_count
|
||||
FROM sys_area sa
|
||||
LEFT JOIN (
|
||||
SELECT
|
||||
c.company_city AS area_code,
|
||||
COUNT(c.company_id) AS total_company,
|
||||
COUNT(CASE WHEN c.is_imp_company = '1' THEN 1 END) AS key_company,
|
||||
COUNT(CASE WHEN c.is_hrs = 'Y' THEN 1 END) AS hrs_company
|
||||
FROM company c
|
||||
WHERE c.del_flag = '0'
|
||||
AND c.status = 1
|
||||
AND c.company_status = '0'
|
||||
AND c.company_city IS NOT NULL AND c.company_city != ''
|
||||
<if test="startDate != null and startDate != ''">
|
||||
AND c.create_time >= #{startDate}
|
||||
</if>
|
||||
<if test="endDate != null and endDate != ''">
|
||||
AND c.create_time <![CDATA[ <= ]]> #{endDate}
|
||||
</if>
|
||||
GROUP BY c.company_city
|
||||
) t ON t.area_code = sa.code
|
||||
WHERE sa.del_flag = '0'
|
||||
<choose>
|
||||
<when test="areaCode != null and areaCode != ''">
|
||||
AND sa.code LIKE CONCAT(CAST(#{areaCode} AS VARCHAR), '%')
|
||||
</when>
|
||||
<otherwise>
|
||||
AND sa.parent_code IS NULL
|
||||
</otherwise>
|
||||
</choose>
|
||||
ORDER BY sa.code
|
||||
</select>
|
||||
|
||||
<!-- 企业按行业统计 -->
|
||||
<select id="selectCompanyByIndustry" resultType="java.util.LinkedHashMap">
|
||||
SELECT
|
||||
c.industry AS industry_key,
|
||||
COUNT(c.company_id) AS count
|
||||
FROM company c
|
||||
INNER JOIN sys_area sa ON sa.code = c.company_city AND sa.del_flag = '0'
|
||||
WHERE c.del_flag = '0'
|
||||
AND c.status = 1
|
||||
AND c.company_status = '0'
|
||||
AND c.company_city IS NOT NULL AND c.company_city != ''
|
||||
<if test="areaCode != null and areaCode != ''">
|
||||
AND sa.code LIKE CONCAT(CAST(#{areaCode} AS VARCHAR), '%')
|
||||
</if>
|
||||
<if test="startDate != null and startDate != ''">
|
||||
AND c.create_time >= #{startDate}
|
||||
</if>
|
||||
<if test="endDate != null and endDate != ''">
|
||||
AND c.create_time <![CDATA[ <= ]]> #{endDate}
|
||||
</if>
|
||||
GROUP BY c.industry
|
||||
</select>
|
||||
|
||||
<!-- 企业按规模统计 -->
|
||||
<select id="selectCompanyByScale" resultType="java.util.LinkedHashMap">
|
||||
SELECT
|
||||
c.scale AS scale_key,
|
||||
COUNT(c.company_id) AS count
|
||||
FROM company c
|
||||
INNER JOIN sys_area sa ON sa.code = c.company_city AND sa.del_flag = '0'
|
||||
WHERE c.del_flag = '0'
|
||||
AND c.status = 1
|
||||
AND c.company_status = '0'
|
||||
AND c.company_city IS NOT NULL AND c.company_city != ''
|
||||
<if test="areaCode != null and areaCode != ''">
|
||||
AND sa.code LIKE CONCAT(CAST(#{areaCode} AS VARCHAR), '%')
|
||||
</if>
|
||||
<if test="startDate != null and startDate != ''">
|
||||
AND c.create_time >= #{startDate}
|
||||
</if>
|
||||
<if test="endDate != null and endDate != ''">
|
||||
AND c.create_time <![CDATA[ <= ]]> #{endDate}
|
||||
</if>
|
||||
GROUP BY c.scale
|
||||
</select>
|
||||
|
||||
<!-- 企业按性质统计 -->
|
||||
<select id="selectCompanyByNature" resultType="java.util.LinkedHashMap">
|
||||
SELECT
|
||||
c.company_nature AS nature_key,
|
||||
COUNT(c.company_id) AS count
|
||||
FROM company c
|
||||
INNER JOIN sys_area sa ON sa.code = c.company_city AND sa.del_flag = '0'
|
||||
WHERE c.del_flag = '0'
|
||||
AND c.status = 1
|
||||
AND c.company_status = '0'
|
||||
AND c.company_city IS NOT NULL AND c.company_city != ''
|
||||
<if test="areaCode != null and areaCode != ''">
|
||||
AND sa.code LIKE CONCAT(CAST(#{areaCode} AS VARCHAR), '%')
|
||||
</if>
|
||||
<if test="startDate != null and startDate != ''">
|
||||
AND c.create_time >= #{startDate}
|
||||
</if>
|
||||
<if test="endDate != null and endDate != ''">
|
||||
AND c.create_time <![CDATA[ <= ]]> #{endDate}
|
||||
</if>
|
||||
GROUP BY c.company_nature
|
||||
</select>
|
||||
|
||||
<!-- ========== 岗位 ========== -->
|
||||
|
||||
<!-- 岗位汇总(含下级区划,job_location_area_code 通过 dict_value 映射到 sys_area.code) -->
|
||||
<select id="selectJobSummary" resultType="java.util.LinkedHashMap">
|
||||
SELECT
|
||||
sa.code AS area_code,
|
||||
sa.name AS area_name,
|
||||
COALESCE(t.job_count, 0) AS total_job_count,
|
||||
COALESCE(t.total_vacancies, 0) AS total_vacancies,
|
||||
COALESCE(t.urgent_count, 0) AS urgent_job_count,
|
||||
COALESCE(t.key_pop_count, 0) AS key_population_job_count
|
||||
FROM sys_area sa
|
||||
LEFT JOIN (
|
||||
SELECT
|
||||
sa2.code AS area_code,
|
||||
COUNT(j.job_id) AS job_count,
|
||||
COALESCE(SUM(CASE WHEN j.vacancies IS NULL OR j.vacancies <![CDATA[ <= ]]> 0 THEN 1 ELSE j.vacancies END), 0) AS total_vacancies,
|
||||
SUM(CASE WHEN j.is_urgent = '1' THEN 1 ELSE 0 END) AS urgent_count,
|
||||
SUM(CASE WHEN j.is_key_populations = '0' THEN 1 ELSE 0 END) AS key_pop_count
|
||||
FROM job j
|
||||
INNER JOIN bussiness_dict_data bdd ON bdd.dict_type = 'area' AND bdd.status = '0'
|
||||
AND CAST(j.job_location_area_code AS VARCHAR) = bdd.dict_value
|
||||
INNER JOIN sys_area sa2 ON sa2.del_flag = '0'
|
||||
AND sa2.name LIKE CONCAT('%', bdd.dict_label, '%')
|
||||
WHERE j.del_flag = '0'
|
||||
AND j.is_publish = 1
|
||||
AND j.job_status = '0'
|
||||
AND COALESCE(j.review_status, '1') = '1'
|
||||
AND j.job_location_area_code IS NOT NULL
|
||||
<if test="areaCode != null and areaCode != ''">
|
||||
AND sa2.code LIKE CONCAT(CAST(#{areaCode} AS VARCHAR), '%')
|
||||
</if>
|
||||
<if test="startDate != null and startDate != ''">
|
||||
AND j.create_time >= #{startDate}
|
||||
</if>
|
||||
<if test="endDate != null and endDate != ''">
|
||||
AND j.create_time <![CDATA[ <= ]]> #{endDate}
|
||||
</if>
|
||||
GROUP BY sa2.code
|
||||
) t ON t.area_code = sa.code
|
||||
WHERE sa.del_flag = '0'
|
||||
<choose>
|
||||
<when test="areaCode != null and areaCode != ''">
|
||||
AND sa.code LIKE CONCAT(CAST(#{areaCode} AS VARCHAR), '%')
|
||||
</when>
|
||||
<otherwise>
|
||||
AND sa.parent_code IS NULL
|
||||
</otherwise>
|
||||
</choose>
|
||||
ORDER BY sa.code
|
||||
</select>
|
||||
|
||||
<!-- 岗位按分类统计 -->
|
||||
<select id="selectJobByCategory" resultType="java.util.LinkedHashMap">
|
||||
SELECT
|
||||
j.job_category AS category_key,
|
||||
COUNT(j.job_id) AS count
|
||||
FROM job j
|
||||
INNER JOIN bussiness_dict_data bdd ON bdd.dict_type = 'area' AND bdd.status = '0'
|
||||
AND CAST(j.job_location_area_code AS VARCHAR) = bdd.dict_value
|
||||
INNER JOIN sys_area sa ON sa.del_flag = '0'
|
||||
AND sa.name LIKE CONCAT('%', bdd.dict_label, '%')
|
||||
WHERE j.del_flag = '0'
|
||||
AND j.is_publish = 1
|
||||
AND j.job_status = '0'
|
||||
AND COALESCE(j.review_status, '1') = '1'
|
||||
AND j.job_location_area_code IS NOT NULL
|
||||
<if test="areaCode != null and areaCode != ''">
|
||||
AND sa.code LIKE CONCAT(CAST(#{areaCode} AS VARCHAR), '%')
|
||||
</if>
|
||||
<if test="startDate != null and startDate != ''">
|
||||
AND j.create_time >= #{startDate}
|
||||
</if>
|
||||
<if test="endDate != null and endDate != ''">
|
||||
AND j.create_time <![CDATA[ <= ]]> #{endDate}
|
||||
</if>
|
||||
GROUP BY j.job_category
|
||||
</select>
|
||||
|
||||
<!-- 岗位按薪资区间统计 -->
|
||||
<select id="selectJobBySalary" resultType="java.util.LinkedHashMap">
|
||||
SELECT
|
||||
CASE
|
||||
WHEN j.max_salary <![CDATA[ < ]]> 3000 THEN '3k以下'
|
||||
WHEN j.min_salary >= 3000 AND j.max_salary <![CDATA[ <= ]]> 5000 THEN '3k-5k'
|
||||
WHEN j.min_salary >= 5000 AND j.max_salary <![CDATA[ <= ]]> 8000 THEN '5k-8k'
|
||||
WHEN j.min_salary >= 8000 AND j.max_salary <![CDATA[ <= ]]> 15000 THEN '8k-15k'
|
||||
WHEN j.min_salary >= 15000 THEN '15k+'
|
||||
ELSE '未知'
|
||||
END AS salary_key,
|
||||
COUNT(j.job_id) AS count
|
||||
FROM job j
|
||||
INNER JOIN bussiness_dict_data bdd ON bdd.dict_type = 'area' AND bdd.status = '0'
|
||||
AND CAST(j.job_location_area_code AS VARCHAR) = bdd.dict_value
|
||||
INNER JOIN sys_area sa ON sa.del_flag = '0'
|
||||
AND sa.name LIKE CONCAT('%', bdd.dict_label, '%')
|
||||
WHERE j.del_flag = '0'
|
||||
AND j.is_publish = 1
|
||||
AND j.job_status = '0'
|
||||
AND COALESCE(j.review_status, '1') = '1'
|
||||
AND j.job_location_area_code IS NOT NULL
|
||||
<if test="areaCode != null and areaCode != ''">
|
||||
AND sa.code LIKE CONCAT(CAST(#{areaCode} AS VARCHAR), '%')
|
||||
</if>
|
||||
<if test="startDate != null and startDate != ''">
|
||||
AND j.create_time >= #{startDate}
|
||||
</if>
|
||||
<if test="endDate != null and endDate != ''">
|
||||
AND j.create_time <![CDATA[ <= ]]> #{endDate}
|
||||
</if>
|
||||
GROUP BY CASE
|
||||
WHEN j.max_salary <![CDATA[ < ]]> 3000 THEN '3k以下'
|
||||
WHEN j.min_salary >= 3000 AND j.max_salary <![CDATA[ <= ]]> 5000 THEN '3k-5k'
|
||||
WHEN j.min_salary >= 5000 AND j.max_salary <![CDATA[ <= ]]> 8000 THEN '5k-8k'
|
||||
WHEN j.min_salary >= 8000 AND j.max_salary <![CDATA[ <= ]]> 15000 THEN '8k-15k'
|
||||
WHEN j.min_salary >= 15000 THEN '15k+'
|
||||
ELSE '未知'
|
||||
END
|
||||
</select>
|
||||
|
||||
<!-- 岗位按学历要求统计 -->
|
||||
<select id="selectJobByEducation" resultType="java.util.LinkedHashMap">
|
||||
SELECT
|
||||
j.education AS education_key,
|
||||
COUNT(j.job_id) AS count
|
||||
FROM job j
|
||||
INNER JOIN bussiness_dict_data bdd ON bdd.dict_type = 'area' AND bdd.status = '0'
|
||||
AND CAST(j.job_location_area_code AS VARCHAR) = bdd.dict_value
|
||||
INNER JOIN sys_area sa ON sa.del_flag = '0'
|
||||
AND sa.name LIKE CONCAT('%', bdd.dict_label, '%')
|
||||
WHERE j.del_flag = '0'
|
||||
AND j.is_publish = 1
|
||||
AND j.job_status = '0'
|
||||
AND COALESCE(j.review_status, '1') = '1'
|
||||
AND j.job_location_area_code IS NOT NULL
|
||||
<if test="areaCode != null and areaCode != ''">
|
||||
AND sa.code LIKE CONCAT(CAST(#{areaCode} AS VARCHAR), '%')
|
||||
</if>
|
||||
<if test="startDate != null and startDate != ''">
|
||||
AND j.create_time >= #{startDate}
|
||||
</if>
|
||||
<if test="endDate != null and endDate != ''">
|
||||
AND j.create_time <![CDATA[ <= ]]> #{endDate}
|
||||
</if>
|
||||
GROUP BY j.education
|
||||
</select>
|
||||
|
||||
<!-- 岗位按类型统计 -->
|
||||
<select id="selectJobByType" resultType="java.util.LinkedHashMap">
|
||||
SELECT
|
||||
j.type AS type_key,
|
||||
COUNT(j.job_id) AS count
|
||||
FROM job j
|
||||
INNER JOIN bussiness_dict_data bdd ON bdd.dict_type = 'area' AND bdd.status = '0'
|
||||
AND CAST(j.job_location_area_code AS VARCHAR) = bdd.dict_value
|
||||
INNER JOIN sys_area sa ON sa.del_flag = '0'
|
||||
AND sa.name LIKE CONCAT('%', bdd.dict_label, '%')
|
||||
WHERE j.del_flag = '0'
|
||||
AND j.is_publish = 1
|
||||
AND j.job_status = '0'
|
||||
AND COALESCE(j.review_status, '1') = '1'
|
||||
AND j.job_location_area_code IS NOT NULL
|
||||
<if test="areaCode != null and areaCode != ''">
|
||||
AND sa.code LIKE CONCAT(CAST(#{areaCode} AS VARCHAR), '%')
|
||||
</if>
|
||||
<if test="startDate != null and startDate != ''">
|
||||
AND j.create_time >= #{startDate}
|
||||
</if>
|
||||
<if test="endDate != null and endDate != ''">
|
||||
AND j.create_time <![CDATA[ <= ]]> #{endDate}
|
||||
</if>
|
||||
GROUP BY j.type
|
||||
</select>
|
||||
|
||||
<!-- ========== 就业(仅统计已录用 hire='0') ========== -->
|
||||
|
||||
<!-- 就业汇总(含下级区划,仅已录用) -->
|
||||
<select id="selectEmploymentSummary" resultType="java.util.LinkedHashMap">
|
||||
SELECT
|
||||
sa.code AS area_code,
|
||||
sa.name AS area_name,
|
||||
COALESCE(t.total_hired, 0) AS total_hired
|
||||
FROM sys_area sa
|
||||
LEFT JOIN (
|
||||
SELECT
|
||||
sa2.code AS area_code,
|
||||
COUNT(DISTINCT ja.id) AS total_hired
|
||||
FROM app_user u
|
||||
INNER JOIN job_apply ja ON ja.user_id = u.user_id AND ja.del_flag = '0' AND ja.hire = '0'
|
||||
INNER JOIN bussiness_dict_data bdd ON bdd.dict_type = 'area' AND bdd.status = '0'
|
||||
AND u.area = bdd.dict_value
|
||||
INNER JOIN sys_area sa2 ON sa2.del_flag = '0'
|
||||
AND sa2.name LIKE CONCAT('%', bdd.dict_label, '%')
|
||||
WHERE u.del_flag = '0'
|
||||
AND u.is_company_user = '1'
|
||||
AND u.status = '0'
|
||||
AND u.area IS NOT NULL AND u.area != ''
|
||||
<if test="startDate != null and startDate != ''">
|
||||
AND ja.create_time >= #{startDate}
|
||||
</if>
|
||||
<if test="endDate != null and endDate != ''">
|
||||
AND ja.create_time <![CDATA[ <= ]]> #{endDate}
|
||||
</if>
|
||||
GROUP BY sa2.code
|
||||
) t ON t.area_code = sa.code
|
||||
WHERE sa.del_flag = '0'
|
||||
<choose>
|
||||
<when test="areaCode != null and areaCode != ''">
|
||||
AND sa.code LIKE CONCAT(CAST(#{areaCode} AS VARCHAR), '%')
|
||||
</when>
|
||||
<otherwise>
|
||||
AND sa.parent_code IS NULL
|
||||
</otherwise>
|
||||
</choose>
|
||||
ORDER BY sa.code
|
||||
</select>
|
||||
|
||||
<!-- 就业按录用来源统计(仅已录用) -->
|
||||
<select id="selectEmploymentByHireSource" resultType="java.util.LinkedHashMap">
|
||||
SELECT
|
||||
CASE
|
||||
WHEN ja.hire_source = '0' THEN 'system'
|
||||
WHEN ja.hire_source = '1' THEN 'fair'
|
||||
ELSE 'other'
|
||||
END AS hire_source_key,
|
||||
COUNT(DISTINCT ja.id) AS count
|
||||
FROM app_user u
|
||||
INNER JOIN bussiness_dict_data bdd ON bdd.dict_type = 'area' AND bdd.status = '0'
|
||||
AND u.area = bdd.dict_value
|
||||
INNER JOIN sys_area sa ON sa.del_flag = '0'
|
||||
AND sa.name LIKE CONCAT('%', bdd.dict_label, '%')
|
||||
INNER JOIN job_apply ja ON ja.user_id = u.user_id AND ja.del_flag = '0' AND ja.hire = '0'
|
||||
WHERE u.del_flag = '0'
|
||||
AND u.is_company_user = '1'
|
||||
AND u.status = '0'
|
||||
<if test="areaCode != null and areaCode != ''">
|
||||
AND sa.code LIKE CONCAT(CAST(#{areaCode} AS VARCHAR), '%')
|
||||
</if>
|
||||
<if test="startDate != null and startDate != ''">
|
||||
AND ja.create_time >= #{startDate}
|
||||
</if>
|
||||
<if test="endDate != null and endDate != ''">
|
||||
AND ja.create_time <![CDATA[ <= ]]> #{endDate}
|
||||
</if>
|
||||
GROUP BY CASE
|
||||
WHEN ja.hire_source = '0' THEN 'system'
|
||||
WHEN ja.hire_source = '1' THEN 'fair'
|
||||
ELSE 'other'
|
||||
END
|
||||
</select>
|
||||
|
||||
<!-- 新入职确认人数(仅已录用) -->
|
||||
<select id="selectNewEmployeeConfirmed" resultType="java.lang.Long">
|
||||
SELECT COALESCE(COUNT(ec.id), 0)
|
||||
FROM employee_confirm ec
|
||||
INNER JOIN app_user u ON CAST(u.user_id AS VARCHAR) = ec.user_id AND u.del_flag = '0' AND u.is_company_user = '1' AND u.status = '0'
|
||||
INNER JOIN bussiness_dict_data bdd ON bdd.dict_type = 'area' AND bdd.status = '0'
|
||||
AND u.area = bdd.dict_value
|
||||
INNER JOIN sys_area sa ON sa.del_flag = '0'
|
||||
AND sa.name LIKE CONCAT('%', bdd.dict_label, '%')
|
||||
WHERE ec.del_flag = '0'
|
||||
<if test="areaCode != null and areaCode != ''">
|
||||
AND sa.code LIKE CONCAT(CAST(#{areaCode} AS VARCHAR), '%')
|
||||
</if>
|
||||
<if test="startDate != null and startDate != ''">
|
||||
AND ec.create_time >= #{startDate}
|
||||
</if>
|
||||
<if test="endDate != null and endDate != ''">
|
||||
AND ec.create_time <![CDATA[ <= ]]> #{endDate}
|
||||
</if>
|
||||
</select>
|
||||
|
||||
</mapper>
|
||||
@@ -0,0 +1,246 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<!DOCTYPE mapper
|
||||
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="com.ruoyi.cms.mapper.BusinessStatisticsMapper">
|
||||
|
||||
<!--
|
||||
create_time 表示数据入库/注册时间,而不是岗位 posting_date。
|
||||
统计采用左闭右开时间范围,避免结束日期为当天 00:00:00 时漏掉当天数据。
|
||||
-->
|
||||
<select id="selectJobSourceTrend" resultType="java.util.HashMap">
|
||||
SELECT TO_CHAR(j.create_time, 'YYYY-MM') AS month,
|
||||
SUM(CASE WHEN j.data_source = '5' THEN 1 ELSE 0 END) AS local_template_import_count,
|
||||
SUM(CASE WHEN j.data_source = '1' THEN 1 ELSE 0 END) AS local_entry_count,
|
||||
SUM(CASE WHEN j.data_source = '2' THEN 1 ELSE 0 END) AS internet_upload_count
|
||||
FROM job j
|
||||
WHERE j.del_flag = '0'
|
||||
AND j.data_source IN ('5', '1', '2')
|
||||
AND j.create_time <![CDATA[ >= ]]> #{startTime}
|
||||
AND j.create_time <![CDATA[ < ]]> #{endTime}
|
||||
GROUP BY TO_CHAR(j.create_time, 'YYYY-MM')
|
||||
ORDER BY month
|
||||
</select>
|
||||
|
||||
<select id="selectNewRegisterTrend" resultType="java.util.HashMap">
|
||||
SELECT TO_CHAR(u.create_time, 'YYYY-MM') AS month,
|
||||
COUNT(*) AS register_count
|
||||
FROM app_user u
|
||||
WHERE u.del_flag = '0'
|
||||
AND u.create_time <![CDATA[ >= ]]> #{startTime}
|
||||
AND u.create_time <![CDATA[ < ]]> #{endTime}
|
||||
GROUP BY TO_CHAR(u.create_time, 'YYYY-MM')
|
||||
ORDER BY month
|
||||
</select>
|
||||
|
||||
<!-- 求职者:is_company_user=1;create_time 按注册时间过滤。 -->
|
||||
<select id="selectJobSeekerByAge" resultType="java.util.HashMap">
|
||||
SELECT COALESCE(d.dict_value, 'unknown') AS code,
|
||||
COALESCE(d.dict_label, '未填写') AS label,
|
||||
COUNT(DISTINCT u.user_id) AS count
|
||||
FROM app_user u
|
||||
LEFT JOIN bussiness_dict_data d
|
||||
ON d.dict_type = 'age'
|
||||
AND d.dict_value = u.age
|
||||
AND d.status = '0'
|
||||
WHERE u.del_flag = '0'
|
||||
AND u.is_company_user = '1'
|
||||
AND u.create_time <![CDATA[ >= ]]> #{startTime}
|
||||
AND u.create_time <![CDATA[ < ]]> #{endTime}
|
||||
GROUP BY d.dict_value, d.dict_label, d.dict_sort
|
||||
ORDER BY d.dict_sort NULLS LAST, d.dict_value NULLS LAST
|
||||
</select>
|
||||
|
||||
<select id="selectJobSeekerByWorkExperience" resultType="java.util.HashMap">
|
||||
SELECT COALESCE(d.dict_value, 'unknown') AS code,
|
||||
COALESCE(d.dict_label, '未填写') AS label,
|
||||
COUNT(DISTINCT u.user_id) AS count
|
||||
FROM app_user u
|
||||
LEFT JOIN bussiness_dict_data d
|
||||
ON d.dict_type = 'experience'
|
||||
AND d.dict_value = u.work_experience
|
||||
AND d.status = '0'
|
||||
WHERE u.del_flag = '0'
|
||||
AND u.is_company_user = '1'
|
||||
AND u.create_time <![CDATA[ >= ]]> #{startTime}
|
||||
AND u.create_time <![CDATA[ < ]]> #{endTime}
|
||||
GROUP BY d.dict_value, d.dict_label, d.dict_sort
|
||||
ORDER BY d.dict_sort NULLS LAST, d.dict_value NULLS LAST
|
||||
</select>
|
||||
|
||||
<!--
|
||||
job_title_id 是逗号分隔的期望岗位 ID,按岗位名称作为“专业/求职方向”统计。
|
||||
一个求职者选择多个期望岗位时,会分别计入多个岗位项;使用 DISTINCT user_id
|
||||
防止同一用户同一岗位 ID重复出现时被重复计数。
|
||||
-->
|
||||
<select id="selectJobSeekerByProfession" resultType="java.util.HashMap">
|
||||
SELECT CAST(jt.job_id AS VARCHAR) AS code,
|
||||
COALESCE(jt.job_name, '未填写') AS label,
|
||||
COUNT(DISTINCT u.user_id) AS count
|
||||
FROM app_user u
|
||||
LEFT JOIN LATERAL unnest(string_to_array(COALESCE(u.job_title_id, ''), ','))
|
||||
AS selected_title(job_id_text) ON TRUE
|
||||
LEFT JOIN job_title jt
|
||||
ON jt.job_id = CAST(NULLIF(TRIM(selected_title.job_id_text), '') AS BIGINT)
|
||||
AND jt.status = '0'
|
||||
AND jt.del_flag = '0'
|
||||
WHERE u.del_flag = '0'
|
||||
AND u.is_company_user = '1'
|
||||
AND u.create_time <![CDATA[ >= ]]> #{startTime}
|
||||
AND u.create_time <![CDATA[ < ]]> #{endTime}
|
||||
GROUP BY jt.job_id, jt.job_name, jt.order_num
|
||||
ORDER BY CASE WHEN jt.job_id IS NULL THEN 1 ELSE 0 END,
|
||||
jt.order_num NULLS LAST,
|
||||
jt.job_id NULLS LAST
|
||||
</select>
|
||||
|
||||
<!--
|
||||
从 bussiness_dict_data 读取 job_hot 的 dict_label,直接按岗位名称前缀关联 job.job_title。
|
||||
字典项没有匹配岗位时仍返回一行,岗位数量为 0。
|
||||
-->
|
||||
<select id="selectHotJobStatistics" resultType="java.util.HashMap">
|
||||
SELECT d.dict_value AS code,
|
||||
d.dict_label AS name,
|
||||
COUNT(DISTINCT j.job_id) AS job_count
|
||||
FROM bussiness_dict_data d
|
||||
LEFT JOIN job j
|
||||
ON j.del_flag = '0'
|
||||
AND j.create_time <![CDATA[ >= ]]> #{startTime}
|
||||
AND j.create_time <![CDATA[ < ]]> #{endTime}
|
||||
AND TRIM(j.job_title) LIKE TRIM(d.dict_label) || '%'
|
||||
WHERE d.dict_type = 'job_hot'
|
||||
AND d.status = '0'
|
||||
AND NULLIF(BTRIM(d.dict_label), '') IS NOT NULL
|
||||
GROUP BY d.dict_value, d.dict_label, d.dict_sort
|
||||
ORDER BY d.dict_sort NULLS LAST, d.dict_value NULLS LAST
|
||||
</select>
|
||||
|
||||
<!--
|
||||
企业规模按 scale 字典值归并:1 微型,2 小型,3 中型,4/5 大型,6/7 超大型。
|
||||
company_count 按 company.create_time 统计,job_count 按 job.create_time 统计,
|
||||
避免历史企业在查询期间发布新岗位时被企业注册时间过滤掉。
|
||||
企业性质只读取 company_nature 字典中已配置的项目;当前只有国企时只返回国企。
|
||||
-->
|
||||
<select id="selectCompanyStatistics" resultType="java.util.HashMap">
|
||||
WITH scale_definitions AS (
|
||||
SELECT CASE
|
||||
WHEN d.dict_value = '1' THEN '1'
|
||||
WHEN d.dict_value = '2' THEN '2'
|
||||
WHEN d.dict_value = '3' THEN '3'
|
||||
WHEN d.dict_value IN ('4', '5') THEN '4_5'
|
||||
WHEN d.dict_value IN ('6', '7') THEN '6_7'
|
||||
END AS code,
|
||||
CASE
|
||||
WHEN d.dict_value = '1' THEN '微型'
|
||||
WHEN d.dict_value = '2' THEN '小型'
|
||||
WHEN d.dict_value = '3' THEN '中型'
|
||||
WHEN d.dict_value IN ('4', '5') THEN '大型'
|
||||
WHEN d.dict_value IN ('6', '7') THEN '超大型'
|
||||
END AS name,
|
||||
d.dict_value
|
||||
FROM bussiness_dict_data d
|
||||
WHERE d.dict_type = 'scale'
|
||||
AND d.status = '0'
|
||||
AND d.dict_value IN ('1', '2', '3', '4', '5', '6', '7')
|
||||
),
|
||||
scale_groups AS (
|
||||
SELECT code, name
|
||||
FROM scale_definitions
|
||||
GROUP BY code, name
|
||||
),
|
||||
company_scale AS (
|
||||
SELECT sd.code,
|
||||
COUNT(DISTINCT c.company_id) AS company_count
|
||||
FROM scale_definitions sd
|
||||
INNER JOIN company c ON c.scale = sd.dict_value
|
||||
WHERE c.del_flag = '0'
|
||||
AND c.status = 1
|
||||
AND c.company_status = '0'
|
||||
AND c.create_time <![CDATA[ >= ]]> #{startTime}
|
||||
AND c.create_time <![CDATA[ < ]]> #{endTime}
|
||||
GROUP BY sd.code
|
||||
),
|
||||
job_scale AS (
|
||||
SELECT sd.code,
|
||||
COUNT(DISTINCT j.job_id) AS job_count
|
||||
FROM scale_definitions sd
|
||||
INNER JOIN company c ON c.scale = sd.dict_value
|
||||
INNER JOIN job j ON j.company_id = c.company_id
|
||||
WHERE c.del_flag = '0'
|
||||
AND c.status = 1
|
||||
AND c.company_status = '0'
|
||||
AND j.del_flag = '0'
|
||||
AND j.is_publish = 1
|
||||
AND j.job_status = '0'
|
||||
AND j.review_status = '1'
|
||||
AND j.create_time <![CDATA[ >= ]]> #{startTime}
|
||||
AND j.create_time <![CDATA[ < ]]> #{endTime}
|
||||
GROUP BY sd.code
|
||||
),
|
||||
scale_rows AS (
|
||||
SELECT 'scale' AS dimension,
|
||||
sg.code,
|
||||
sg.name,
|
||||
COALESCE(cs.company_count, 0) AS company_count,
|
||||
COALESCE(js.job_count, 0) AS job_count
|
||||
FROM scale_groups sg
|
||||
LEFT JOIN company_scale cs ON cs.code = sg.code
|
||||
LEFT JOIN job_scale js ON js.code = sg.code
|
||||
),
|
||||
nature_definitions AS (
|
||||
SELECT DISTINCT d.dict_value AS code,
|
||||
d.dict_label AS name,
|
||||
d.dict_sort
|
||||
FROM bussiness_dict_data d
|
||||
WHERE d.dict_type = 'company_nature'
|
||||
AND d.status = '0'
|
||||
AND NULLIF(BTRIM(d.dict_label), '') IS NOT NULL
|
||||
),
|
||||
company_nature AS (
|
||||
SELECT nd.code,
|
||||
COUNT(DISTINCT c.company_id) AS company_count
|
||||
FROM nature_definitions nd
|
||||
INNER JOIN company c ON c.company_nature = nd.code
|
||||
WHERE c.del_flag = '0'
|
||||
AND c.status = 1
|
||||
AND c.company_status = '0'
|
||||
AND c.create_time <![CDATA[ >= ]]> #{startTime}
|
||||
AND c.create_time <![CDATA[ < ]]> #{endTime}
|
||||
GROUP BY nd.code
|
||||
),
|
||||
job_nature AS (
|
||||
SELECT nd.code,
|
||||
COUNT(DISTINCT j.job_id) AS job_count
|
||||
FROM nature_definitions nd
|
||||
INNER JOIN company c ON c.company_nature = nd.code
|
||||
INNER JOIN job j ON j.company_id = c.company_id
|
||||
WHERE c.del_flag = '0'
|
||||
AND c.status = 1
|
||||
AND c.company_status = '0'
|
||||
AND j.del_flag = '0'
|
||||
AND j.is_publish = 1
|
||||
AND j.job_status = '0'
|
||||
AND j.review_status = '1'
|
||||
AND j.create_time <![CDATA[ >= ]]> #{startTime}
|
||||
AND j.create_time <![CDATA[ < ]]> #{endTime}
|
||||
GROUP BY nd.code
|
||||
),
|
||||
nature_rows AS (
|
||||
SELECT 'nature' AS dimension,
|
||||
nd.code,
|
||||
nd.name,
|
||||
COALESCE(cn.company_count, 0) AS company_count,
|
||||
COALESCE(jn.job_count, 0) AS job_count
|
||||
FROM nature_definitions nd
|
||||
LEFT JOIN company_nature cn ON cn.code = nd.code
|
||||
LEFT JOIN job_nature jn ON jn.code = nd.code
|
||||
)
|
||||
SELECT dimension, code, name, company_count, job_count
|
||||
FROM scale_rows
|
||||
UNION ALL
|
||||
SELECT dimension, code, name, company_count, job_count
|
||||
FROM nature_rows
|
||||
ORDER BY dimension, code
|
||||
</select>
|
||||
|
||||
</mapper>
|
||||
Reference in New Issue
Block a user