Merge branch 'main' of http://124.243.245.42:3000/zkr/shz-backend
This commit is contained in:
65
docs/app-outdoor-fair-h5-api.md
Normal file
65
docs/app-outdoor-fair-h5-api.md
Normal file
@@ -0,0 +1,65 @@
|
||||
# 户外招聘会企业 H5 公共接口
|
||||
|
||||
基础路径:`/app/outdoor-fair`
|
||||
|
||||
## 1. 扫码进入企业 H5
|
||||
|
||||
**请求方式:** `GET`
|
||||
|
||||
**请求路径:** `/company-page`
|
||||
|
||||
**请求参数:**
|
||||
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
| --- | --- | --- | --- |
|
||||
| deviceCode | String | 是 | 后台设备码绑定企业后生成的扫码参数 |
|
||||
|
||||
**响应字段:**
|
||||
|
||||
| 字段 | 说明 |
|
||||
| --- | --- |
|
||||
| fair | 户外招聘会信息 |
|
||||
| company | 企业信息 |
|
||||
| booth | 企业展位信息,未分配展位时为空 |
|
||||
| jobs | 当前企业在本场招聘会下的岗位列表 |
|
||||
| fairH5Url | 本场招聘会 H5 链接 |
|
||||
| companyH5Url | 当前企业 H5 链接 |
|
||||
| qrCodeContent | 二维码内容,前端用该值生成二维码 |
|
||||
| deviceId | 绑定设备码 ID |
|
||||
| deviceCode | 绑定设备码 |
|
||||
|
||||
## 2. 通过招聘会和企业获取 H5 数据
|
||||
|
||||
**请求方式:** `GET`
|
||||
|
||||
**请求路径:** `/{fairId}/companies/{companyId}/page`
|
||||
|
||||
用于未绑定设备码或后台预览企业 H5 页面。
|
||||
|
||||
## 3. 获取招聘会公共详情
|
||||
|
||||
**请求方式:** `GET`
|
||||
|
||||
**请求路径:** `/{fairId}`
|
||||
|
||||
返回 `fair` 和 `fairH5Url`。
|
||||
|
||||
## 4. 后台参会企业二维码入口
|
||||
|
||||
后台接口 `GET /cms/outdoor-fair/{fairId}/companies` 的每行企业现在额外返回:
|
||||
|
||||
| 字段 | 说明 |
|
||||
| --- | --- |
|
||||
| deviceId | 已绑定设备码 ID,未绑定为空 |
|
||||
| deviceCode | 已绑定设备码,未绑定为空 |
|
||||
| h5Url | 企业 H5 链接,格式为 `/h5/outdoor-fair/company.html?fairId={fairId}&companyId={companyId}` |
|
||||
| qrCodeContent | 二维码内容,前端点击“查看二维码”时用该值生成二维码 |
|
||||
|
||||
也可以调用 `GET /cms/outdoor-fair/{fairId}/companies/{companyId}/qrcode` 单独获取某个参会企业的二维码内容。
|
||||
|
||||
## 5. 已提供的静态 H5
|
||||
|
||||
| 页面 | 说明 |
|
||||
| --- | --- |
|
||||
| `/h5/outdoor-fair/company.html?fairId={fairId}&companyId={companyId}` | 扫码后的参会企业公共页 |
|
||||
| `/h5/outdoor-fair/detail.html?fairId={fairId}` | 招聘会公共详情页 |
|
||||
@@ -20,12 +20,16 @@ spring:
|
||||
driverClassName: com.highgo.jdbc.Driver
|
||||
druid:
|
||||
# 主库数据源
|
||||
# master:
|
||||
# url: jdbc:highgo://124.243.245.42:5866/shz?useUnicode=true&characterEncoding=utf8¤tSchema=shz&stringtype=unspecified
|
||||
# #username: syssso
|
||||
# username: sysdba
|
||||
# password: ZKR2024@comzkr
|
||||
master:
|
||||
url: jdbc:highgo://39.98.44.136:6023/highgo?useUnicode=true&characterEncoding=utf8¤tSchema=shz&stringtype=unspecified
|
||||
#username: syssso
|
||||
username: sysdba
|
||||
password: Hello@2026
|
||||
|
||||
# 从库数据源
|
||||
slave:
|
||||
# 从数据源开关/默认关闭
|
||||
@@ -86,7 +90,7 @@ spring:
|
||||
# 密码
|
||||
password: ZKR2024@@.com
|
||||
# 连接超时时间
|
||||
timeout: 10s
|
||||
timeout: 100s
|
||||
lettuce:
|
||||
pool:
|
||||
# 连接池中的最小空闲连接
|
||||
|
||||
@@ -0,0 +1,299 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
|
||||
<title>参会企业</title>
|
||||
<style>
|
||||
:root {
|
||||
color: #172033;
|
||||
background: #f4f7fb;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Microsoft YaHei", sans-serif;
|
||||
}
|
||||
* { box-sizing: border-box; }
|
||||
body { margin: 0; background: #f4f7fb; }
|
||||
.page { min-height: 100vh; padding: 18px 14px 28px; }
|
||||
.hero {
|
||||
padding: 22px 18px;
|
||||
color: #fff;
|
||||
background: linear-gradient(135deg, #1f6feb, #14866d);
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 10px 28px rgba(31, 111, 235, .18);
|
||||
}
|
||||
.hero h1 { margin: 0 0 10px; font-size: 24px; line-height: 1.25; letter-spacing: 0; }
|
||||
.hero p { margin: 4px 0 0; font-size: 14px; line-height: 1.7; opacity: .92; }
|
||||
.section { margin-top: 14px; padding: 16px; background: #fff; border-radius: 8px; box-shadow: 0 8px 22px rgba(23, 32, 51, .06); }
|
||||
.section h2 { margin: 0 0 12px; font-size: 17px; line-height: 1.4; letter-spacing: 0; }
|
||||
.meta { display: grid; gap: 8px; }
|
||||
.meta div { display: flex; gap: 10px; font-size: 14px; line-height: 1.55; }
|
||||
.meta span:first-child { flex: 0 0 72px; color: #667085; }
|
||||
.meta span:last-child { min-width: 0; flex: 1; word-break: break-word; }
|
||||
.desc { margin: 0; color: #344054; font-size: 14px; line-height: 1.75; white-space: pre-wrap; }
|
||||
.jobs { display: grid; gap: 10px; }
|
||||
.job { padding: 12px; border: 1px solid #e5eaf2; border-radius: 8px; background: #fbfcff; }
|
||||
.job h3 { margin: 0 0 8px; font-size: 16px; line-height: 1.35; letter-spacing: 0; }
|
||||
.job p { margin: 3px 0; color: #475467; font-size: 13px; line-height: 1.55; }
|
||||
.actions { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; margin-top: 14px; }
|
||||
.btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 44px;
|
||||
padding: 10px 14px;
|
||||
color: #fff;
|
||||
background: #1f6feb;
|
||||
border: 0;
|
||||
border-radius: 8px;
|
||||
font-size: 15px;
|
||||
text-decoration: none;
|
||||
}
|
||||
.btn.secondary { color: #1f6feb; background: #eaf2ff; }
|
||||
.btn.checkin { background: #14866d; }
|
||||
.empty, .loading, .error { padding: 28px 16px; color: #667085; text-align: center; font-size: 14px; line-height: 1.7; }
|
||||
.error { color: #b42318; }
|
||||
/* 签到弹窗 */
|
||||
.mask { position: fixed; inset: 0; background: rgba(23,32,51,.45); display: none; z-index: 50; }
|
||||
.mask.show { display: block; }
|
||||
.dialog {
|
||||
position: fixed; left: 50%; top: 50%; transform: translate(-50%, -50%);
|
||||
width: calc(100vw - 56px); max-width: 380px; background: #fff;
|
||||
border-radius: 10px; padding: 18px 16px; z-index: 51; display: none;
|
||||
box-shadow: 0 18px 48px rgba(23,32,51,.22);
|
||||
}
|
||||
.dialog.show { display: block; }
|
||||
.dialog h3 { margin: 0 0 4px; font-size: 17px; }
|
||||
.dialog .sub { margin: 0 0 14px; font-size: 13px; color: #667085; }
|
||||
.field { margin-bottom: 12px; }
|
||||
.field label { display: block; margin-bottom: 6px; font-size: 14px; color: #344054; }
|
||||
.field input {
|
||||
width: 100%; min-height: 42px; padding: 8px 12px; border: 1px solid #d0d5dd;
|
||||
border-radius: 8px; font-size: 15px; background: #fff; box-sizing: border-box;
|
||||
}
|
||||
.field input:focus { outline: none; border-color: #1f6feb; }
|
||||
.dialog-actions { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; margin-top: 4px; }
|
||||
.btn.ghost { color: #475467; background: #f2f4f7; }
|
||||
.toast {
|
||||
position: fixed; left: 50%; top: 24px; transform: translateX(-50%);
|
||||
padding: 10px 16px; border-radius: 8px; font-size: 14px; color: #fff;
|
||||
background: #14866d; z-index: 60; display: none; max-width: 80vw; text-align: center;
|
||||
}
|
||||
.toast.error { background: #b42318; }
|
||||
.toast.show { display: block; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<main class="page" id="app">
|
||||
<div class="loading">正在加载...</div>
|
||||
</main>
|
||||
<script>
|
||||
(function () {
|
||||
var app = document.getElementById('app');
|
||||
var params = new URLSearchParams(window.location.search);
|
||||
var deviceCode = params.get('deviceCode');
|
||||
var fairId = params.get('fairId');
|
||||
var companyId = params.get('companyId');
|
||||
var pageFairId = null;
|
||||
|
||||
function text(value) {
|
||||
return value === null || value === undefined || value === '' ? '暂无' : String(value);
|
||||
}
|
||||
|
||||
function escapeHtml(value) {
|
||||
return text(value).replace(/[&<>"']/g, function (ch) {
|
||||
return ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' })[ch];
|
||||
});
|
||||
}
|
||||
|
||||
function apiUrl() {
|
||||
if (deviceCode) {
|
||||
return '/app/outdoor-fair/company-page?deviceCode=' + encodeURIComponent(deviceCode);
|
||||
}
|
||||
return '/app/outdoor-fair/' + encodeURIComponent(fairId) + '/companies/' + encodeURIComponent(companyId) + '/page';
|
||||
}
|
||||
|
||||
function fetchJson(url) {
|
||||
return fetch(url).then(function (res) {
|
||||
return res.json();
|
||||
});
|
||||
}
|
||||
|
||||
function fetchDict(dictType) {
|
||||
return fetchJson('/app/common/dict/' + encodeURIComponent(dictType)).then(function (body) {
|
||||
if (body.code !== 200 || !Array.isArray(body.data)) {
|
||||
return {};
|
||||
}
|
||||
return body.data.reduce(function (map, item) {
|
||||
if (item && item.dictValue !== undefined && item.dictValue !== null) {
|
||||
map[String(item.dictValue)] = item.dictLabel;
|
||||
}
|
||||
return map;
|
||||
}, {});
|
||||
}).catch(function () {
|
||||
return {};
|
||||
});
|
||||
}
|
||||
|
||||
function dictLabel(dictMap, value) {
|
||||
if (value === null || value === undefined || value === '') {
|
||||
return '暂无';
|
||||
}
|
||||
return dictMap[String(value)] || String(value);
|
||||
}
|
||||
|
||||
function render(data, dicts) {
|
||||
var fair = data.fair || {};
|
||||
var company = data.company || {};
|
||||
var booth = data.booth || {};
|
||||
var jobs = data.jobs || [];
|
||||
var educationDict = dicts.education || {};
|
||||
var experienceDict = dicts.experience || {};
|
||||
pageFairId = (fair.id !== null && fair.id !== undefined) ? fair.id : fairId;
|
||||
document.title = text(company.name);
|
||||
app.innerHTML =
|
||||
'<section class="hero">' +
|
||||
'<h1>' + escapeHtml(company.name) + '</h1>' +
|
||||
'<p>' + escapeHtml(fair.title) + '</p>' +
|
||||
'<p>' + escapeHtml(fair.address) + '</p>' +
|
||||
'</section>' +
|
||||
'<div class="actions">' +
|
||||
'<button type="button" class="btn checkin" id="checkinBtn">签到</button>' +
|
||||
'<a class="btn" href="' + escapeHtml(data.fairH5Url) + '">查看招聘会</a>' +
|
||||
'</div>' +
|
||||
'<section class="section">' +
|
||||
'<h2>企业信息</h2>' +
|
||||
'<div class="meta">' +
|
||||
'<div><span>行业</span><span>' + escapeHtml(company.industry) + '</span></div>' +
|
||||
'<div><span>规模</span><span>' + escapeHtml(company.scale) + '</span></div>' +
|
||||
'<div><span>性质</span><span>' + escapeHtml(company.nature) + '</span></div>' +
|
||||
'<div><span>地址</span><span>' + escapeHtml(company.location || company.registeredAddress) + '</span></div>' +
|
||||
'<div><span>联系人</span><span>' + escapeHtml(company.contactPerson) + '</span></div>' +
|
||||
'<div><span>联系电话</span><span>' + escapeHtml(company.contactPersonPhone) + '</span></div>' +
|
||||
'<div><span>展位</span><span>' + escapeHtml(booth.boothNumber) + '</span></div>' +
|
||||
'</div>' +
|
||||
'</section>' +
|
||||
'<section class="section">' +
|
||||
'<h2>企业介绍</h2>' +
|
||||
'<p class="desc">' + escapeHtml(company.description) + '</p>' +
|
||||
'</section>' +
|
||||
'<section class="section">' +
|
||||
'<h2>招聘岗位</h2>' +
|
||||
(jobs.length ? '<div class="jobs">' + jobs.map(function (job) {
|
||||
return renderJob(job, educationDict, experienceDict);
|
||||
}).join('') + '</div>' : '<div class="empty">暂无岗位</div>') +
|
||||
'</section>' +
|
||||
'<div class="mask" id="checkinMask"></div>' +
|
||||
'<div class="dialog" id="checkinDialog">' +
|
||||
'<h3>签到</h3>' +
|
||||
'<p class="sub">请填写您的姓名和手机号完成签到</p>' +
|
||||
'<div class="field">' +
|
||||
'<label for="checkinName">姓名</label>' +
|
||||
'<input type="text" id="checkinName" placeholder="请输入姓名" autocomplete="name">' +
|
||||
'</div>' +
|
||||
'<div class="field">' +
|
||||
'<label for="checkinPhone">手机号</label>' +
|
||||
'<input type="tel" id="checkinPhone" placeholder="请输入手机号" autocomplete="tel" maxlength="11">' +
|
||||
'</div>' +
|
||||
'<div class="dialog-actions">' +
|
||||
'<button type="button" class="btn ghost" id="checkinCancel">取消</button>' +
|
||||
'<button type="button" class="btn checkin" id="checkinSubmit">提交签到</button>' +
|
||||
'</div>' +
|
||||
'</div>' +
|
||||
'<div class="toast" id="checkinToast"></div>';
|
||||
bindCheckIn();
|
||||
}
|
||||
|
||||
function showToast(msg, isError) {
|
||||
var toast = document.getElementById('checkinToast');
|
||||
if (!toast) return;
|
||||
toast.textContent = msg;
|
||||
toast.className = 'toast show' + (isError ? ' error' : '');
|
||||
setTimeout(function () { toast.className = 'toast' + (isError ? ' error' : ''); }, 2400);
|
||||
}
|
||||
|
||||
function openCheckIn() {
|
||||
var mask = document.getElementById('checkinMask');
|
||||
var dialog = document.getElementById('checkinDialog');
|
||||
if (mask && dialog) {
|
||||
mask.className = 'mask show';
|
||||
dialog.className = 'dialog show';
|
||||
}
|
||||
}
|
||||
|
||||
function closeCheckIn() {
|
||||
var mask = document.getElementById('checkinMask');
|
||||
var dialog = document.getElementById('checkinDialog');
|
||||
if (mask) mask.className = 'mask';
|
||||
if (dialog) dialog.className = 'dialog';
|
||||
}
|
||||
|
||||
function bindCheckIn() {
|
||||
var openBtn = document.getElementById('checkinBtn');
|
||||
var cancelBtn = document.getElementById('checkinCancel');
|
||||
var mask = document.getElementById('checkinMask');
|
||||
var submitBtn = document.getElementById('checkinSubmit');
|
||||
if (openBtn) openBtn.onclick = openCheckIn;
|
||||
if (cancelBtn) cancelBtn.onclick = closeCheckIn;
|
||||
if (mask) mask.onclick = closeCheckIn;
|
||||
if (submitBtn) submitBtn.onclick = submitCheckIn;
|
||||
}
|
||||
|
||||
function submitCheckIn() {
|
||||
var name = (document.getElementById('checkinName') || {}).value;
|
||||
var phone = (document.getElementById('checkinPhone') || {}).value;
|
||||
if (!name || !name.trim()) { showToast('请输入姓名', true); return; }
|
||||
if (!phone || !/^\d{11}$/.test(phone.trim())) { showToast('请输入正确的11位手机号', true); return; }
|
||||
if (!pageFairId) { showToast('缺少招聘会信息', true); return; }
|
||||
var submitBtn = document.getElementById('checkinSubmit');
|
||||
if (submitBtn) submitBtn.disabled = true;
|
||||
fetch('/app/outdoor-fair/check-in', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ fairId: Number(pageFairId), name: name.trim(), phone: phone.trim() })
|
||||
}).then(function (res) { return res.json(); }).then(function (body) {
|
||||
if (submitBtn) submitBtn.disabled = false;
|
||||
if (body && body.code === 200) {
|
||||
closeCheckIn();
|
||||
showToast('签到成功');
|
||||
} else {
|
||||
showToast((body && body.msg) || '签到失败', true);
|
||||
}
|
||||
}).catch(function () {
|
||||
if (submitBtn) submitBtn.disabled = false;
|
||||
showToast('网络异常,请重试', true);
|
||||
});
|
||||
}
|
||||
|
||||
function renderJob(job, educationDict, experienceDict) {
|
||||
var salary = job.minSalary || job.maxSalary ? text(job.minSalary) + '-' + text(job.maxSalary) + ' 元' : '薪资面议';
|
||||
return '<article class="job">' +
|
||||
'<h3>' + escapeHtml(job.jobTitle) + '</h3>' +
|
||||
'<p>' + escapeHtml(salary) + ' · ' + escapeHtml(dictLabel(educationDict, job.education)) + ' · ' + escapeHtml(dictLabel(experienceDict, job.experience)) + '</p>' +
|
||||
'<p>招聘人数:' + escapeHtml(job.vacancies) + '</p>' +
|
||||
'<p>工作地点:' + escapeHtml(job.jobAddress || job.jobLocation) + '</p>' +
|
||||
'</article>';
|
||||
}
|
||||
|
||||
if (!deviceCode && (!fairId || !companyId)) {
|
||||
app.innerHTML = '<div class="error">缺少页面参数</div>';
|
||||
return;
|
||||
}
|
||||
Promise.all([
|
||||
fetchJson(apiUrl()),
|
||||
fetchDict('education'),
|
||||
fetchDict('experience')
|
||||
]).then(function (results) {
|
||||
var body = results[0];
|
||||
if (body.code !== 200) {
|
||||
throw new Error(body.msg || '加载失败');
|
||||
}
|
||||
render(body.data || {}, {
|
||||
education: results[1],
|
||||
experience: results[2]
|
||||
});
|
||||
}).catch(function (err) {
|
||||
app.innerHTML = '<div class="error">' + escapeHtml(err.message) + '</div>';
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,208 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
|
||||
<title>户外招聘会</title>
|
||||
<style>
|
||||
:root {
|
||||
color: #172033;
|
||||
background: #f4f7fb;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Microsoft YaHei", sans-serif;
|
||||
}
|
||||
* { box-sizing: border-box; }
|
||||
body { margin: 0; background: #f4f7fb; }
|
||||
.page { min-height: 100vh; padding: 18px 14px 28px; }
|
||||
.hero {
|
||||
padding: 22px 18px;
|
||||
color: #fff;
|
||||
background: linear-gradient(135deg, #1f6feb, #14866d);
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 10px 28px rgba(31, 111, 235, .18);
|
||||
}
|
||||
.hero h1 { margin: 0 0 10px; font-size: 24px; line-height: 1.25; letter-spacing: 0; }
|
||||
.hero p { margin: 4px 0 0; font-size: 14px; line-height: 1.7; opacity: .92; }
|
||||
.section { margin-top: 14px; padding: 16px; background: #fff; border-radius: 8px; box-shadow: 0 8px 22px rgba(23, 32, 51, .06); }
|
||||
.section h2 { margin: 0 0 12px; font-size: 17px; line-height: 1.4; letter-spacing: 0; }
|
||||
.meta { display: grid; gap: 8px; }
|
||||
.meta div { display: flex; gap: 10px; font-size: 14px; line-height: 1.55; }
|
||||
.meta span:first-child { flex: 0 0 86px; color: #667085; }
|
||||
.meta span:last-child { min-width: 0; flex: 1; word-break: break-word; }
|
||||
.photo { width: 100%; margin-top: 14px; border-radius: 8px; object-fit: cover; }
|
||||
.loading, .error { padding: 28px 16px; color: #667085; text-align: center; font-size: 14px; line-height: 1.7; }
|
||||
.error { color: #b42318; }
|
||||
.actions { display: grid; grid-template-columns: 1fr; gap: 10px; margin-top: 14px; }
|
||||
.btn {
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
min-height: 44px; padding: 10px 14px; color: #fff; background: #1f6feb;
|
||||
border: 0; border-radius: 8px; font-size: 15px; text-decoration: none; width: 100%;
|
||||
}
|
||||
.btn.checkin { background: #14866d; }
|
||||
/* 签到弹窗 */
|
||||
.mask { position: fixed; inset: 0; background: rgba(23,32,51,.45); display: none; z-index: 50; }
|
||||
.mask.show { display: block; }
|
||||
.dialog {
|
||||
position: fixed; left: 50%; top: 50%; transform: translate(-50%, -50%);
|
||||
width: calc(100vw - 56px); max-width: 380px; background: #fff;
|
||||
border-radius: 10px; padding: 18px 16px; z-index: 51; display: none;
|
||||
box-shadow: 0 18px 48px rgba(23,32,51,.22);
|
||||
}
|
||||
.dialog.show { display: block; }
|
||||
.dialog h3 { margin: 0 0 4px; font-size: 17px; }
|
||||
.dialog .sub { margin: 0 0 14px; font-size: 13px; color: #667085; }
|
||||
.field { margin-bottom: 12px; }
|
||||
.field label { display: block; margin-bottom: 6px; font-size: 14px; color: #344054; }
|
||||
.field input {
|
||||
width: 100%; min-height: 42px; padding: 8px 12px; border: 1px solid #d0d5dd;
|
||||
border-radius: 8px; font-size: 15px; background: #fff; box-sizing: border-box;
|
||||
}
|
||||
.field input:focus { outline: none; border-color: #1f6feb; }
|
||||
.dialog-actions { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; margin-top: 4px; }
|
||||
.btn.ghost { color: #475467; background: #f2f4f7; }
|
||||
.toast {
|
||||
position: fixed; left: 50%; top: 24px; transform: translateX(-50%);
|
||||
padding: 10px 16px; border-radius: 8px; font-size: 14px; color: #fff;
|
||||
background: #14866d; z-index: 60; display: none; max-width: 80vw; text-align: center;
|
||||
}
|
||||
.toast.error { background: #b42318; }
|
||||
.toast.show { display: block; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<main class="page" id="app">
|
||||
<div class="loading">正在加载...</div>
|
||||
</main>
|
||||
<script>
|
||||
(function () {
|
||||
var app = document.getElementById('app');
|
||||
var fairId = new URLSearchParams(window.location.search).get('fairId');
|
||||
var pageFairId = null;
|
||||
|
||||
function text(value) {
|
||||
return value === null || value === undefined || value === '' ? '暂无' : String(value);
|
||||
}
|
||||
|
||||
function escapeHtml(value) {
|
||||
return text(value).replace(/[&<>"']/g, function (ch) {
|
||||
return ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' })[ch];
|
||||
});
|
||||
}
|
||||
|
||||
function render(data) {
|
||||
var fair = data.fair || {};
|
||||
pageFairId = (fair.id !== null && fair.id !== undefined) ? fair.id : fairId;
|
||||
document.title = text(fair.title);
|
||||
app.innerHTML =
|
||||
'<section class="hero">' +
|
||||
'<h1>' + escapeHtml(fair.title) + '</h1>' +
|
||||
'<p>' + escapeHtml(fair.hostUnit) + '</p>' +
|
||||
'<p>' + escapeHtml(fair.address) + '</p>' +
|
||||
'</section>' +
|
||||
'<div class="actions">' +
|
||||
'<button type="button" class="btn checkin" id="checkinBtn">签到</button>' +
|
||||
'</div>' +
|
||||
'<section class="section">' +
|
||||
'<h2>招聘会信息</h2>' +
|
||||
'<div class="meta">' +
|
||||
'<div><span>举办时间</span><span>' + escapeHtml(fair.holdTime) + '</span></div>' +
|
||||
'<div><span>截止时间</span><span>' + escapeHtml(fair.endTime) + '</span></div>' +
|
||||
'<div><span>招聘会类型</span><span>' + escapeHtml(fair.fairType) + '</span></div>' +
|
||||
'<div><span>举办区域</span><span>' + escapeHtml(fair.region) + '</span></div>' +
|
||||
'<div><span>举办场地</span><span>' + escapeHtml(fair.venueName) + '</span></div>' +
|
||||
'<div><span>展位数量</span><span>' + escapeHtml(fair.boothCount) + '</span></div>' +
|
||||
'</div>' +
|
||||
(fair.photoUrl ? '<img class="photo" src="' + escapeHtml(fair.photoUrl) + '" alt="招聘会照片">' : '') +
|
||||
'</section>' +
|
||||
'<div class="mask" id="checkinMask"></div>' +
|
||||
'<div class="dialog" id="checkinDialog">' +
|
||||
'<h3>签到</h3>' +
|
||||
'<p class="sub">请填写您的姓名和手机号完成签到</p>' +
|
||||
'<div class="field">' +
|
||||
'<label for="checkinName">姓名</label>' +
|
||||
'<input type="text" id="checkinName" placeholder="请输入姓名" autocomplete="name">' +
|
||||
'</div>' +
|
||||
'<div class="field">' +
|
||||
'<label for="checkinPhone">手机号</label>' +
|
||||
'<input type="tel" id="checkinPhone" placeholder="请输入手机号" autocomplete="tel" maxlength="11">' +
|
||||
'</div>' +
|
||||
'<div class="dialog-actions">' +
|
||||
'<button type="button" class="btn ghost" id="checkinCancel">取消</button>' +
|
||||
'<button type="button" class="btn checkin" id="checkinSubmit">提交签到</button>' +
|
||||
'</div>' +
|
||||
'</div>' +
|
||||
'<div class="toast" id="checkinToast"></div>';
|
||||
bindCheckIn();
|
||||
}
|
||||
|
||||
function showToast(msg, isError) {
|
||||
var toast = document.getElementById('checkinToast');
|
||||
if (!toast) return;
|
||||
toast.textContent = msg;
|
||||
toast.className = 'toast show' + (isError ? ' error' : '');
|
||||
setTimeout(function () { toast.className = 'toast' + (isError ? ' error' : ''); }, 2400);
|
||||
}
|
||||
|
||||
function openCheckIn() {
|
||||
var mask = document.getElementById('checkinMask');
|
||||
var dialog = document.getElementById('checkinDialog');
|
||||
if (mask && dialog) { mask.className = 'mask show'; dialog.className = 'dialog show'; }
|
||||
}
|
||||
|
||||
function closeCheckIn() {
|
||||
var mask = document.getElementById('checkinMask');
|
||||
var dialog = document.getElementById('checkinDialog');
|
||||
if (mask) mask.className = 'mask';
|
||||
if (dialog) dialog.className = 'dialog';
|
||||
}
|
||||
|
||||
function bindCheckIn() {
|
||||
var openBtn = document.getElementById('checkinBtn');
|
||||
var cancelBtn = document.getElementById('checkinCancel');
|
||||
var mask = document.getElementById('checkinMask');
|
||||
var submitBtn = document.getElementById('checkinSubmit');
|
||||
if (openBtn) openBtn.onclick = openCheckIn;
|
||||
if (cancelBtn) cancelBtn.onclick = closeCheckIn;
|
||||
if (mask) mask.onclick = closeCheckIn;
|
||||
if (submitBtn) submitBtn.onclick = submitCheckIn;
|
||||
}
|
||||
|
||||
function submitCheckIn() {
|
||||
var name = (document.getElementById('checkinName') || {}).value;
|
||||
var phone = (document.getElementById('checkinPhone') || {}).value;
|
||||
if (!name || !name.trim()) { showToast('请输入姓名', true); return; }
|
||||
if (!phone || !/^\d{11}$/.test(phone.trim())) { showToast('请输入正确的11位手机号', true); return; }
|
||||
if (!pageFairId) { showToast('缺少招聘会信息', true); return; }
|
||||
var submitBtn = document.getElementById('checkinSubmit');
|
||||
if (submitBtn) submitBtn.disabled = true;
|
||||
fetch('/app/outdoor-fair/check-in', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ fairId: Number(pageFairId), name: name.trim(), phone: phone.trim() })
|
||||
}).then(function (res) { return res.json(); }).then(function (body) {
|
||||
if (submitBtn) submitBtn.disabled = false;
|
||||
if (body && body.code === 200) { closeCheckIn(); showToast('签到成功'); }
|
||||
else { showToast((body && body.msg) || '签到失败', true); }
|
||||
}).catch(function () {
|
||||
if (submitBtn) submitBtn.disabled = false;
|
||||
showToast('网络异常,请重试', true);
|
||||
});
|
||||
}
|
||||
|
||||
if (!fairId) {
|
||||
app.innerHTML = '<div class="error">缺少招聘会参数</div>';
|
||||
return;
|
||||
}
|
||||
fetch('/app/outdoor-fair/' + encodeURIComponent(fairId)).then(function (res) {
|
||||
return res.json();
|
||||
}).then(function (body) {
|
||||
if (body.code !== 200) {
|
||||
throw new Error(body.msg || '加载失败');
|
||||
}
|
||||
render(body.data || {});
|
||||
}).catch(function (err) {
|
||||
app.innerHTML = '<div class="error">' + escapeHtml(err.message) + '</div>';
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,398 @@
|
||||
package com.ruoyi.cms.controller.app;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.ruoyi.cms.domain.FairCompany;
|
||||
import com.ruoyi.cms.domain.Job;
|
||||
import com.ruoyi.cms.domain.OutdoorFair;
|
||||
import com.ruoyi.cms.domain.OutdoorFairAttendee;
|
||||
import com.ruoyi.cms.domain.OutdoorFairBoothBooking;
|
||||
import com.ruoyi.cms.domain.OutdoorFairDevice;
|
||||
import com.ruoyi.cms.mapper.CompanyMapper;
|
||||
import com.ruoyi.cms.mapper.FairCompanyMapper;
|
||||
import com.ruoyi.cms.mapper.OutdoorFairBoothBookingMapper;
|
||||
import com.ruoyi.cms.mapper.OutdoorFairDeviceMapper;
|
||||
import com.ruoyi.cms.mapper.OutdoorFairJobMapper;
|
||||
import com.ruoyi.cms.domain.rc.PublicJobFair;
|
||||
import com.ruoyi.cms.domain.rc.PublicJobFairQuery;
|
||||
import com.ruoyi.cms.domain.rc.PublicJobFairResponse;
|
||||
import com.ruoyi.cms.service.IOutdoorFairAttendeeService;
|
||||
import com.ruoyi.cms.service.IOutdoorFairService;
|
||||
import com.ruoyi.common.annotation.BussinessLog;
|
||||
import com.ruoyi.common.core.controller.BaseController;
|
||||
import com.ruoyi.common.core.domain.AjaxResult;
|
||||
import com.ruoyi.common.core.domain.entity.Company;
|
||||
import com.ruoyi.common.exception.ServiceException;
|
||||
import com.ruoyi.common.utils.StringUtils;
|
||||
import io.swagger.annotations.Api;
|
||||
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.PathVariable;
|
||||
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.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 移动端:户外招聘会公共 H5 接口。
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/app/outdoor-fair")
|
||||
@Api(tags = "移动端:户外招聘会公共H5")
|
||||
public class AppOutdoorFairController extends BaseController
|
||||
{
|
||||
private static final String DAY_PATTERN = "yyyy-MM-dd";
|
||||
|
||||
@Autowired
|
||||
private IOutdoorFairService outdoorFairService;
|
||||
|
||||
@Autowired
|
||||
private CompanyMapper companyMapper;
|
||||
|
||||
@Autowired
|
||||
private FairCompanyMapper fairCompanyMapper;
|
||||
|
||||
@Autowired
|
||||
private OutdoorFairDeviceMapper outdoorFairDeviceMapper;
|
||||
|
||||
@Autowired
|
||||
private OutdoorFairBoothBookingMapper outdoorFairBoothBookingMapper;
|
||||
|
||||
@Autowired
|
||||
private OutdoorFairJobMapper outdoorFairJobMapper;
|
||||
|
||||
@Autowired
|
||||
private IOutdoorFairAttendeeService outdoorFairAttendeeService;
|
||||
|
||||
@BussinessLog(title = "户外招聘会列表")
|
||||
@ApiOperation("分页查询户外招聘会列表")
|
||||
@GetMapping("/page")
|
||||
public PublicJobFairResponse page(PublicJobFairQuery query)
|
||||
{
|
||||
if (query == null) {
|
||||
query = new PublicJobFairQuery();
|
||||
}
|
||||
int pageNum = query.getPageNum() == null || query.getPageNum() < 1 ? 1 : query.getPageNum();
|
||||
int pageSize = query.getPageSize() == null || query.getPageSize() < 1 ? 10 : query.getPageSize();
|
||||
Page<OutdoorFair> page = outdoorFairService.page(new Page<>(pageNum, pageSize), buildOutdoorFairWrapper(query));
|
||||
|
||||
PublicJobFairResponse response = new PublicJobFairResponse();
|
||||
response.setCode(200);
|
||||
response.setMsg("查询成功");
|
||||
response.setData(buildPageData(page, pageNum, pageSize));
|
||||
return response;
|
||||
}
|
||||
|
||||
@BussinessLog(title = "查看当季度户外招聘会信息")
|
||||
@ApiOperation("查看当季度户外招聘会信息")
|
||||
@GetMapping("/currentQuarter")
|
||||
public AjaxResult currentQuarterFairs()
|
||||
{
|
||||
Calendar calendar = Calendar.getInstance();
|
||||
int currentMonth = calendar.get(Calendar.MONTH);
|
||||
int quarterStartMonth = currentMonth / 3 * 3;
|
||||
calendar.set(Calendar.MONTH, quarterStartMonth);
|
||||
calendar.set(Calendar.DAY_OF_MONTH, 1);
|
||||
setStartOfDay(calendar);
|
||||
Date startTime = calendar.getTime();
|
||||
|
||||
calendar.add(Calendar.MONTH, 3);
|
||||
Date endTime = calendar.getTime();
|
||||
|
||||
return success(listOutdoorFairsByRange(startTime, endTime));
|
||||
}
|
||||
|
||||
@BussinessLog(title = "查看当月户外招聘会信息")
|
||||
@ApiOperation("查看当月户外招聘会信息")
|
||||
@GetMapping("/currentMonth")
|
||||
public AjaxResult currentMonthFairs()
|
||||
{
|
||||
Calendar calendar = Calendar.getInstance();
|
||||
calendar.set(Calendar.DAY_OF_MONTH, 1);
|
||||
setStartOfDay(calendar);
|
||||
Date startTime = calendar.getTime();
|
||||
|
||||
calendar.add(Calendar.MONTH, 1);
|
||||
Date endTime = calendar.getTime();
|
||||
|
||||
return success(listOutdoorFairsByRange(startTime, endTime));
|
||||
}
|
||||
|
||||
@BussinessLog(title = "查询有户外招聘会的日期列表")
|
||||
@ApiOperation("查询有户外招聘会的日期列表")
|
||||
@GetMapping("/dates")
|
||||
public AjaxResult dates()
|
||||
{
|
||||
List<String> dates = outdoorFairService.list(new LambdaQueryWrapper<OutdoorFair>()
|
||||
.isNotNull(OutdoorFair::getHoldTime)
|
||||
.orderByAsc(OutdoorFair::getHoldTime))
|
||||
.stream()
|
||||
.map(OutdoorFair::getHoldTime)
|
||||
.map(AppOutdoorFairController::formatDay)
|
||||
.distinct()
|
||||
.collect(Collectors.toList());
|
||||
return success(dates);
|
||||
}
|
||||
|
||||
@BussinessLog(title = "户外招聘会企业H5详情")
|
||||
@ApiOperation("通过设备码获取户外招聘会企业H5详情")
|
||||
@GetMapping("/company-page")
|
||||
public AjaxResult companyPageByDeviceCode(@RequestParam String deviceCode, HttpServletRequest request)
|
||||
{
|
||||
if (StringUtils.isBlank(deviceCode)) {
|
||||
return AjaxResult.error("设备码不能为空");
|
||||
}
|
||||
OutdoorFairDevice device = outdoorFairDeviceMapper.selectOne(new LambdaQueryWrapper<OutdoorFairDevice>()
|
||||
.eq(OutdoorFairDevice::getDeviceCode, deviceCode)
|
||||
.last("LIMIT 1"));
|
||||
if (device == null || device.getCompanyId() == null) {
|
||||
return AjaxResult.error("设备码未绑定参会企业");
|
||||
}
|
||||
return success(buildCompanyPage(device.getFairId(), device.getCompanyId(), device, request));
|
||||
}
|
||||
|
||||
@BussinessLog(title = "户外招聘会企业H5详情")
|
||||
@ApiOperation("通过招聘会ID和企业ID获取户外招聘会企业H5详情")
|
||||
@GetMapping("/{fairId}/companies/{companyId}/page")
|
||||
public AjaxResult companyPage(@PathVariable Long fairId, @PathVariable Long companyId,
|
||||
HttpServletRequest request)
|
||||
{
|
||||
OutdoorFairDevice device = outdoorFairDeviceMapper.selectOne(new LambdaQueryWrapper<OutdoorFairDevice>()
|
||||
.eq(OutdoorFairDevice::getFairId, fairId)
|
||||
.eq(OutdoorFairDevice::getCompanyId, companyId)
|
||||
.last("LIMIT 1"));
|
||||
return success(buildCompanyPage(fairId, companyId, device, request));
|
||||
}
|
||||
|
||||
@BussinessLog(title = "户外招聘会详情")
|
||||
@ApiOperation("获取户外招聘会公共详情")
|
||||
@GetMapping("/{fairId}")
|
||||
public AjaxResult detail(@PathVariable Long fairId, HttpServletRequest request)
|
||||
{
|
||||
OutdoorFair fair = outdoorFairService.getById(fairId);
|
||||
if (fair == null) {
|
||||
return AjaxResult.error("招聘会不存在");
|
||||
}
|
||||
Map<String, Object> data = new HashMap<>();
|
||||
data.put("fair", fair);
|
||||
data.put("fairH5Url", buildFairH5Url(request, fairId));
|
||||
return success(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 匿名签到(姓名+手机号)。H5 企业页调用,无需登录。
|
||||
*/
|
||||
@BussinessLog(title = "户外招聘会企业H5签到")
|
||||
@ApiOperation("匿名签到(姓名+手机号)")
|
||||
@PostMapping("/check-in")
|
||||
public AjaxResult checkIn(@RequestBody OutdoorFairAttendee attendee)
|
||||
{
|
||||
if (attendee.getFairId() == null) {
|
||||
return AjaxResult.error("缺少招聘会参数");
|
||||
}
|
||||
if (StringUtils.isBlank(attendee.getName())) {
|
||||
return AjaxResult.error("姓名不能为空");
|
||||
}
|
||||
if (StringUtils.isBlank(attendee.getPhone())) {
|
||||
return AjaxResult.error("手机号不能为空");
|
||||
}
|
||||
// 同一招聘会下同手机号已签到则不重复记录
|
||||
Long exists = outdoorFairAttendeeService.count(new LambdaQueryWrapper<OutdoorFairAttendee>()
|
||||
.eq(OutdoorFairAttendee::getFairId, attendee.getFairId())
|
||||
.eq(OutdoorFairAttendee::getPhone, attendee.getPhone()));
|
||||
if (exists != null && exists > 0) {
|
||||
return AjaxResult.error("该手机号已签到");
|
||||
}
|
||||
attendee.setCheckInTime(new Date());
|
||||
return toAjax(outdoorFairAttendeeService.save(attendee));
|
||||
}
|
||||
|
||||
private Map<String, Object> buildCompanyPage(Long fairId, Long companyId, OutdoorFairDevice device,
|
||||
HttpServletRequest request)
|
||||
{
|
||||
OutdoorFair fair = outdoorFairService.getById(fairId);
|
||||
if (fair == null) {
|
||||
throw new ServiceException("招聘会不存在");
|
||||
}
|
||||
Company company = companyMapper.selectById(companyId);
|
||||
if (company == null) {
|
||||
throw new ServiceException("企业不存在");
|
||||
}
|
||||
Long relationCount = fairCompanyMapper.selectCount(new LambdaQueryWrapper<FairCompany>()
|
||||
.eq(FairCompany::getJobFairId, fairId)
|
||||
.eq(FairCompany::getCompanyId, companyId));
|
||||
if (relationCount == null || relationCount == 0) {
|
||||
throw new ServiceException("该企业未参加当前招聘会");
|
||||
}
|
||||
OutdoorFairBoothBooking booking = outdoorFairBoothBookingMapper.selectOne(
|
||||
new LambdaQueryWrapper<OutdoorFairBoothBooking>()
|
||||
.eq(OutdoorFairBoothBooking::getFairId, fairId)
|
||||
.eq(OutdoorFairBoothBooking::getCompanyId, companyId)
|
||||
.eq(OutdoorFairBoothBooking::getStatus, "reserved")
|
||||
.last("LIMIT 1"));
|
||||
List<Job> jobs = outdoorFairJobMapper.selectFairJobPage(
|
||||
fairId, companyId, null, null, null, null, 0, 200);
|
||||
|
||||
String companyH5Url = buildCompanyH5Url(request, fairId, companyId);
|
||||
Map<String, Object> data = new HashMap<>();
|
||||
data.put("fair", fair);
|
||||
data.put("company", company);
|
||||
data.put("booth", booking);
|
||||
data.put("jobs", jobs);
|
||||
data.put("fairH5Url", buildFairH5Url(request, fairId));
|
||||
data.put("companyH5Url", companyH5Url);
|
||||
data.put("qrCodeContent", companyH5Url);
|
||||
data.put("deviceId", device == null ? null : device.getId());
|
||||
data.put("deviceCode", device == null ? null : device.getDeviceCode());
|
||||
return data;
|
||||
}
|
||||
|
||||
private String buildCompanyH5Url(HttpServletRequest request, Long fairId, Long companyId)
|
||||
{
|
||||
String baseUrl = buildBaseUrl(request);
|
||||
return baseUrl + "/h5/outdoor-fair/company.html?fairId=" + fairId + "&companyId=" + companyId;
|
||||
}
|
||||
|
||||
private String buildFairH5Url(HttpServletRequest request, Long fairId)
|
||||
{
|
||||
return buildBaseUrl(request) + "/h5/outdoor-fair/detail.html?fairId=" + fairId;
|
||||
}
|
||||
|
||||
private LambdaQueryWrapper<OutdoorFair> buildOutdoorFairWrapper(PublicJobFairQuery query)
|
||||
{
|
||||
LambdaQueryWrapper<OutdoorFair> wrapper = new LambdaQueryWrapper<>();
|
||||
if (query != null && StringUtils.isNotBlank(query.getJobFairTitle())) {
|
||||
wrapper.like(OutdoorFair::getTitle, query.getJobFairTitle());
|
||||
}
|
||||
if (query != null && StringUtils.isNotBlank(query.getZphjbsj())) {
|
||||
Date[] dayRange = parseDayRange(query.getZphjbsj());
|
||||
if (dayRange != null) {
|
||||
wrapper.lt(OutdoorFair::getHoldTime, dayRange[1])
|
||||
.and(w -> w.isNull(OutdoorFair::getEndTime)
|
||||
.or()
|
||||
.ge(OutdoorFair::getEndTime, dayRange[0]));
|
||||
}
|
||||
}
|
||||
wrapper.orderByDesc(OutdoorFair::getHoldTime)
|
||||
.orderByDesc(OutdoorFair::getCreateTime);
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
private PublicJobFairResponse.PageData buildPageData(Page<OutdoorFair> page, int pageNum, int pageSize)
|
||||
{
|
||||
PublicJobFairResponse.PageData data = new PublicJobFairResponse.PageData();
|
||||
List<PublicJobFair> list = page.getRecords().stream()
|
||||
.map(this::buildPublicJobFair)
|
||||
.collect(Collectors.toList());
|
||||
int pages = (int) page.getPages();
|
||||
data.setTotal(page.getTotal());
|
||||
data.setList(list);
|
||||
data.setPageNum(pageNum);
|
||||
data.setPageSize(pageSize);
|
||||
data.setSize(list.size());
|
||||
data.setStartRow(list.isEmpty() ? 0 : (pageNum - 1) * pageSize + 1);
|
||||
data.setEndRow(list.isEmpty() ? 0 : (pageNum - 1) * pageSize + list.size());
|
||||
data.setPages(pages);
|
||||
data.setPrePage(pageNum > 1 ? pageNum - 1 : 0);
|
||||
data.setNextPage(pageNum < pages ? pageNum + 1 : 0);
|
||||
data.setIsFirstPage(pageNum <= 1);
|
||||
data.setIsLastPage(pageNum >= pages);
|
||||
data.setHasPreviousPage(pageNum > 1);
|
||||
data.setHasNextPage(pageNum < pages);
|
||||
data.setNavigatePages(8);
|
||||
data.setNavigatepageNums(buildNavigatePageNums(pages));
|
||||
data.setNavigateFirstPage(pages > 0 ? 1 : 0);
|
||||
data.setNavigateLastPage(pages);
|
||||
return data;
|
||||
}
|
||||
|
||||
private List<Integer> buildNavigatePageNums(int pages)
|
||||
{
|
||||
List<Integer> pageNums = new ArrayList<>();
|
||||
for (int i = 1; i <= pages; i++) {
|
||||
pageNums.add(i);
|
||||
}
|
||||
return pageNums;
|
||||
}
|
||||
|
||||
private List<PublicJobFair> listOutdoorFairsByRange(Date startTime, Date endTime)
|
||||
{
|
||||
return outdoorFairService.list(new LambdaQueryWrapper<OutdoorFair>()
|
||||
.ge(OutdoorFair::getHoldTime, startTime)
|
||||
.lt(OutdoorFair::getHoldTime, endTime)
|
||||
.orderByAsc(OutdoorFair::getHoldTime))
|
||||
.stream()
|
||||
.map(this::buildPublicJobFair)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private PublicJobFair buildPublicJobFair(OutdoorFair fair)
|
||||
{
|
||||
PublicJobFair jobFair = new PublicJobFair();
|
||||
jobFair.setJobFairId(String.valueOf(fair.getId()));
|
||||
jobFair.setJobFairTitle(fair.getTitle());
|
||||
jobFair.setJobFairAddress(StringUtils.isNotBlank(fair.getAddress()) ? fair.getAddress() : fair.getVenueName());
|
||||
jobFair.setJobFairType("outdoor");
|
||||
jobFair.setJobFairStartTime(fair.getHoldTime());
|
||||
jobFair.setJobFairEndTime(fair.getEndTime());
|
||||
jobFair.setJobFairHostUnit(fair.getHostUnit());
|
||||
jobFair.setJobFairOrganizeUnit(fair.getHostUnit());
|
||||
jobFair.setJobFairIntroduction(StringUtils.isNotBlank(fair.getVenueName()) ? fair.getVenueName() : fair.getAddress());
|
||||
jobFair.setJobFairImage(fair.getPhotoUrl());
|
||||
jobFair.setJobFairSignUpStartTime(fair.getApplyStartTime());
|
||||
jobFair.setJobFairSignUpEndTime(fair.getApplyEndTime());
|
||||
jobFair.setJobFairVenueId(fair.getVenueId() == null ? null : String.valueOf(fair.getVenueId()));
|
||||
jobFair.setBoothNum(fair.getBoothCount() == null ? null : String.valueOf(fair.getBoothCount()));
|
||||
return jobFair;
|
||||
}
|
||||
|
||||
private Date[] parseDayRange(String day)
|
||||
{
|
||||
try {
|
||||
Date startTime = new SimpleDateFormat(DAY_PATTERN).parse(day);
|
||||
Calendar calendar = Calendar.getInstance();
|
||||
calendar.setTime(startTime);
|
||||
calendar.add(Calendar.DAY_OF_MONTH, 1);
|
||||
return new Date[]{startTime, calendar.getTime()};
|
||||
} catch (ParseException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static String formatDay(Date date)
|
||||
{
|
||||
return new SimpleDateFormat(DAY_PATTERN).format(date);
|
||||
}
|
||||
|
||||
private void setStartOfDay(Calendar calendar)
|
||||
{
|
||||
calendar.set(Calendar.HOUR_OF_DAY, 0);
|
||||
calendar.set(Calendar.MINUTE, 0);
|
||||
calendar.set(Calendar.SECOND, 0);
|
||||
calendar.set(Calendar.MILLISECOND, 0);
|
||||
}
|
||||
|
||||
private String buildBaseUrl(HttpServletRequest request)
|
||||
{
|
||||
String baseUrl = request.getScheme() + "://" + request.getServerName();
|
||||
int port = request.getServerPort();
|
||||
if (port != 80 && port != 443) {
|
||||
baseUrl += ":" + port;
|
||||
}
|
||||
return baseUrl + (request.getContextPath() == null ? "" : request.getContextPath());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -39,6 +39,18 @@ public class CmsPublicJobFairController extends BaseController {
|
||||
return getDataTable(list);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询跨域招聘会下的岗位(可按联盟城市过滤,cityNames 为逗号分隔的城市名称)
|
||||
*/
|
||||
@ApiOperation("跨域招聘会岗位列表")
|
||||
@PreAuthorize("@ss.hasPermi('cms:crossCityFair:list')")
|
||||
@GetMapping("/cross-domain-jobs")
|
||||
public TableDataInfo crossDomainJobs(@RequestParam(required = false) String cityNames) {
|
||||
startPage();
|
||||
List<CrossDomainJobVO> list = publicJobFairService.selectCrossDomainJobs(cityNames);
|
||||
return getDataTable(list);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取招聘会详情
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
package com.ruoyi.cms.controller.cms;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import com.ruoyi.cms.domain.CrossCityAlliance;
|
||||
import com.ruoyi.cms.service.ICrossCityAllianceService;
|
||||
import com.ruoyi.common.annotation.Log;
|
||||
import com.ruoyi.common.core.controller.BaseController;
|
||||
import com.ruoyi.common.core.domain.AjaxResult;
|
||||
import com.ruoyi.common.core.page.TableDataInfo;
|
||||
import com.ruoyi.common.enums.BusinessType;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.PutMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/**
|
||||
* 跨域联合招聘会-联盟城市 Controller
|
||||
*
|
||||
* @author ruoyi
|
||||
* @date 2026-06-27
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/cms/cross-city-alliance")
|
||||
@Api(tags = "后台:跨域联合招聘会-联盟城市")
|
||||
public class CrossCityAllianceController extends BaseController
|
||||
{
|
||||
@Autowired
|
||||
private ICrossCityAllianceService crossCityAllianceService;
|
||||
|
||||
/**
|
||||
* 查询联盟城市列表(支持按城市名称模糊查询)
|
||||
*/
|
||||
@ApiOperation("查询联盟城市列表")
|
||||
@PreAuthorize("@ss.hasPermi('cms:crossCityFair:list')")
|
||||
@GetMapping("/list")
|
||||
public TableDataInfo list(CrossCityAlliance query)
|
||||
{
|
||||
startPage();
|
||||
List<CrossCityAlliance> list = crossCityAllianceService.selectAllianceList(query);
|
||||
return getDataTable(list);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取联盟城市详细信息
|
||||
*/
|
||||
@ApiOperation("获取联盟城市详细信息")
|
||||
@PreAuthorize("@ss.hasPermi('cms:crossCityFair:query')")
|
||||
@GetMapping("/{id}")
|
||||
public AjaxResult getInfo(@PathVariable("id") Long id)
|
||||
{
|
||||
return success(crossCityAllianceService.getById(id));
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增联盟城市
|
||||
*/
|
||||
@ApiOperation("新增联盟城市")
|
||||
@PreAuthorize("@ss.hasPermi('cms:crossCityFair:add')")
|
||||
@Log(title = "跨域联合招聘会-联盟城市", businessType = BusinessType.INSERT)
|
||||
@PostMapping
|
||||
public AjaxResult add(@RequestBody CrossCityAlliance alliance)
|
||||
{
|
||||
return toAjax(crossCityAllianceService.save(alliance));
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改联盟城市
|
||||
*/
|
||||
@ApiOperation("修改联盟城市")
|
||||
@PreAuthorize("@ss.hasPermi('cms:crossCityFair:edit')")
|
||||
@Log(title = "跨域联合招聘会-联盟城市", businessType = BusinessType.UPDATE)
|
||||
@PutMapping
|
||||
public AjaxResult edit(@RequestBody CrossCityAlliance alliance)
|
||||
{
|
||||
return toAjax(crossCityAllianceService.updateById(alliance));
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除联盟城市
|
||||
*/
|
||||
@ApiOperation("删除联盟城市")
|
||||
@PreAuthorize("@ss.hasPermi('cms:crossCityFair:remove')")
|
||||
@Log(title = "跨域联合招聘会-联盟城市", businessType = BusinessType.DELETE)
|
||||
@DeleteMapping("/{ids}")
|
||||
public AjaxResult remove(@PathVariable Long[] ids)
|
||||
{
|
||||
return toAjax(crossCityAllianceService.removeByIds(Arrays.asList(ids)));
|
||||
}
|
||||
}
|
||||
@@ -23,24 +23,31 @@ import com.ruoyi.cms.domain.Job;
|
||||
import com.ruoyi.cms.domain.OutdoorFair;
|
||||
import com.ruoyi.cms.domain.OutdoorFairBooth;
|
||||
import com.ruoyi.cms.domain.OutdoorFairBoothBooking;
|
||||
import com.ruoyi.cms.domain.OutdoorFairDevice;
|
||||
import com.ruoyi.cms.domain.OutdoorFairDictDataRequest;
|
||||
import com.ruoyi.cms.domain.OutdoorFairJob;
|
||||
import com.ruoyi.cms.domain.VenueInfo;
|
||||
import com.ruoyi.cms.mapper.FairCompanyMapper;
|
||||
import com.ruoyi.cms.mapper.OutdoorFairBoothMapper;
|
||||
import com.ruoyi.cms.mapper.OutdoorFairBoothBookingMapper;
|
||||
import com.ruoyi.cms.mapper.OutdoorFairDeviceMapper;
|
||||
import com.ruoyi.cms.mapper.OutdoorFairJobMapper;
|
||||
import com.ruoyi.cms.mapper.CompanyMapper;
|
||||
import com.ruoyi.cms.service.ICompanyService;
|
||||
import com.ruoyi.cms.service.IJobService;
|
||||
import com.ruoyi.cms.service.IOutdoorFairService;
|
||||
import com.ruoyi.cms.service.IVenueInfoService;
|
||||
import com.ruoyi.cms.util.RoleUtils;
|
||||
import com.ruoyi.common.annotation.Log;
|
||||
import com.ruoyi.common.core.controller.BaseController;
|
||||
import com.ruoyi.common.core.domain.AjaxResult;
|
||||
import com.ruoyi.common.core.domain.entity.Company;
|
||||
import com.ruoyi.common.core.domain.entity.SysUser;
|
||||
import com.ruoyi.common.core.domain.model.LoginUser;
|
||||
import com.ruoyi.common.core.page.TableDataInfo;
|
||||
import com.ruoyi.common.enums.BusinessType;
|
||||
import com.ruoyi.common.exception.ServiceException;
|
||||
import com.ruoyi.common.utils.SecurityUtils;
|
||||
import com.ruoyi.common.utils.StringUtils;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
@@ -58,6 +65,8 @@ import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
/**
|
||||
* 户外招聘会Controller
|
||||
*
|
||||
@@ -90,19 +99,27 @@ public class OutdoorFairController extends BaseController
|
||||
@Autowired
|
||||
private OutdoorFairJobMapper outdoorFairJobMapper;
|
||||
|
||||
@Autowired
|
||||
private OutdoorFairDeviceMapper outdoorFairDeviceMapper;
|
||||
|
||||
@Autowired
|
||||
private IJobService jobService;
|
||||
|
||||
@Autowired
|
||||
private ICompanyService companyService;
|
||||
|
||||
/**
|
||||
* 查询户外招聘会列表
|
||||
*/
|
||||
@ApiOperation("查询户外招聘会列表")
|
||||
@PreAuthorize("@ss.hasPermi('cms:outdoorFair:list')")
|
||||
@GetMapping("/list")
|
||||
public TableDataInfo list(OutdoorFair outdoorFair)
|
||||
public TableDataInfo list(OutdoorFair outdoorFair, HttpServletRequest request)
|
||||
{
|
||||
Company company = RoleUtils.isCompanyAdmin() ? getCurrentCompanyOrNull() : null;
|
||||
startPage();
|
||||
List<OutdoorFair> list = outdoorFairService.selectOutdoorFairList(outdoorFair);
|
||||
fillCompanyApplyState(list, request, company);
|
||||
return getDataTable(list);
|
||||
}
|
||||
|
||||
@@ -114,6 +131,9 @@ public class OutdoorFairController extends BaseController
|
||||
@GetMapping(value = "/{id}")
|
||||
public AjaxResult getInfo(@PathVariable("id") Long id)
|
||||
{
|
||||
if (RoleUtils.isCompanyAdmin()) {
|
||||
return AjaxResult.error("企业用户无权查看招聘会详情");
|
||||
}
|
||||
return success(outdoorFairService.getById(id));
|
||||
}
|
||||
|
||||
@@ -346,6 +366,9 @@ public class OutdoorFairController extends BaseController
|
||||
@PostMapping
|
||||
public AjaxResult add(@RequestBody OutdoorFair outdoorFair)
|
||||
{
|
||||
if (RoleUtils.isCompanyAdmin()) {
|
||||
return AjaxResult.error("企业用户无权新增户外招聘会");
|
||||
}
|
||||
fillVenueFields(outdoorFair);
|
||||
boolean saved = outdoorFairService.save(outdoorFair);
|
||||
if (saved) {
|
||||
@@ -364,6 +387,9 @@ public class OutdoorFairController extends BaseController
|
||||
@PutMapping
|
||||
public AjaxResult edit(@RequestBody OutdoorFair outdoorFair)
|
||||
{
|
||||
if (RoleUtils.isCompanyAdmin()) {
|
||||
return AjaxResult.error("企业用户无权编辑户外招聘会");
|
||||
}
|
||||
OutdoorFair oldFair = outdoorFair.getId() == null ? null : outdoorFairService.getById(outdoorFair.getId());
|
||||
fillVenueFields(outdoorFair);
|
||||
boolean updated = outdoorFairService.updateById(outdoorFair);
|
||||
@@ -383,6 +409,9 @@ public class OutdoorFairController extends BaseController
|
||||
@DeleteMapping("/{ids}")
|
||||
public AjaxResult remove(@PathVariable Long[] ids)
|
||||
{
|
||||
if (RoleUtils.isCompanyAdmin()) {
|
||||
return AjaxResult.error("企业用户无权删除户外招聘会");
|
||||
}
|
||||
return toAjax(outdoorFairService.removeByIds(Arrays.asList(ids)));
|
||||
}
|
||||
|
||||
@@ -395,23 +424,60 @@ public class OutdoorFairController extends BaseController
|
||||
@PostMapping("/dict-data")
|
||||
public AjaxResult addDictData(@Validated @RequestBody OutdoorFairDictDataRequest request)
|
||||
{
|
||||
if (RoleUtils.isCompanyAdmin()) {
|
||||
return AjaxResult.error("企业用户无权维护户外招聘会字典");
|
||||
}
|
||||
return toAjax(outdoorFairService.insertOutdoorFairDictData(request.getDictType(), request.getDictLabel(), getUsername()));
|
||||
}
|
||||
|
||||
@ApiOperation("企业报名户外招聘会")
|
||||
@PreAuthorize("@ss.hasPermi('cms:outdoorFair:list')")
|
||||
@PostMapping("/{fairId}/company/signup")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public AjaxResult signupCompany(@PathVariable Long fairId, HttpServletRequest request)
|
||||
{
|
||||
Company company = getCurrentCompanyOrThrow();
|
||||
ensureFairExists(fairId);
|
||||
FairCompany relation = ensureFairCompany(fairId, company.getCompanyId(), "0");
|
||||
Map<String, Object> data = buildCompanyQrCodeData(request, fairId, company.getCompanyId());
|
||||
data.put("companyName", company.getName());
|
||||
data.put("signedUp", true);
|
||||
data.put("reviewStatus", relation.getReviewStatus());
|
||||
data.put("reviewRemark", relation.getReviewRemark());
|
||||
return success(data);
|
||||
}
|
||||
|
||||
@ApiOperation("获取当前企业户外招聘会二维码内容")
|
||||
@PreAuthorize("@ss.hasPermi('cms:outdoorFair:list')")
|
||||
@GetMapping("/{fairId}/company/qrcode")
|
||||
public AjaxResult getCurrentCompanyQrCode(@PathVariable Long fairId, HttpServletRequest request)
|
||||
{
|
||||
Company company = getCurrentCompanyOrThrow();
|
||||
ensureFairCompanyExists(fairId, company.getCompanyId());
|
||||
ensureFairCompanyApprovedIfCompany(fairId, company.getCompanyId());
|
||||
return success(buildCompanyQrCodeData(request, fairId, company.getCompanyId()));
|
||||
}
|
||||
|
||||
@ApiOperation("查询户外招聘会参会企业")
|
||||
@PreAuthorize("@ss.hasPermi('cms:outdoorFair:query')")
|
||||
@GetMapping("/{fairId}/companies")
|
||||
public AjaxResult listCompanies(@PathVariable Long fairId, String companyName,
|
||||
Integer current, Integer pageSize)
|
||||
Integer current, Integer pageSize,
|
||||
HttpServletRequest request)
|
||||
{
|
||||
List<FairCompany> relations = fairCompanyMapper.selectList(new LambdaQueryWrapper<FairCompany>()
|
||||
LambdaQueryWrapper<FairCompany> relationWrapper = new LambdaQueryWrapper<FairCompany>()
|
||||
.eq(FairCompany::getJobFairId, fairId)
|
||||
.orderByDesc(FairCompany::getId));
|
||||
.orderByDesc(FairCompany::getId);
|
||||
if (RoleUtils.isCompanyAdmin()) {
|
||||
Company company = getCurrentCompanyOrNull();
|
||||
if (company == null) {
|
||||
return emptyAjaxPage();
|
||||
}
|
||||
relationWrapper.eq(FairCompany::getCompanyId, company.getCompanyId());
|
||||
}
|
||||
List<FairCompany> relations = fairCompanyMapper.selectList(relationWrapper);
|
||||
if (relations.isEmpty()) {
|
||||
AjaxResult result = AjaxResult.success();
|
||||
result.put("rows", new ArrayList<>());
|
||||
result.put("total", 0);
|
||||
return result;
|
||||
return emptyAjaxPage();
|
||||
}
|
||||
|
||||
List<Long> companyIds = relations.stream().map(FairCompany::getCompanyId).collect(Collectors.toList());
|
||||
@@ -428,6 +494,11 @@ public class OutdoorFairController extends BaseController
|
||||
item -> Long.valueOf(String.valueOf(item.get("companyId"))),
|
||||
item -> Long.valueOf(String.valueOf(item.get("jobCount"))),
|
||||
(a, b) -> a));
|
||||
Map<Long, OutdoorFairDevice> deviceMap = outdoorFairDeviceMapper.selectList(new LambdaQueryWrapper<OutdoorFairDevice>()
|
||||
.eq(OutdoorFairDevice::getFairId, fairId)
|
||||
.isNotNull(OutdoorFairDevice::getCompanyId))
|
||||
.stream()
|
||||
.collect(Collectors.toMap(OutdoorFairDevice::getCompanyId, item -> item, (a, b) -> a));
|
||||
|
||||
List<Map<String, Object>> rows = new ArrayList<>();
|
||||
for (FairCompany relation : relations) {
|
||||
@@ -451,6 +522,16 @@ public class OutdoorFairController extends BaseController
|
||||
row.put("boothId", booking == null ? null : booking.getBoothId());
|
||||
row.put("boothNumber", booking == null ? null : booking.getBoothNumber());
|
||||
row.put("jobCount", jobCountMap.getOrDefault(company.getCompanyId(), 0L));
|
||||
row.put("reviewStatus", relation.getReviewStatus());
|
||||
row.put("reviewBy", relation.getReviewBy());
|
||||
row.put("reviewTime", relation.getReviewTime());
|
||||
row.put("reviewRemark", relation.getReviewRemark());
|
||||
OutdoorFairDevice device = deviceMap.get(company.getCompanyId());
|
||||
String h5Url = buildOutdoorFairCompanyH5Url(request, fairId, company.getCompanyId());
|
||||
row.put("deviceId", device == null ? null : device.getId());
|
||||
row.put("deviceCode", device == null ? null : device.getDeviceCode());
|
||||
row.put("h5Url", h5Url);
|
||||
row.put("qrCodeContent", h5Url);
|
||||
rows.add(row);
|
||||
}
|
||||
|
||||
@@ -465,13 +546,30 @@ public class OutdoorFairController extends BaseController
|
||||
return result;
|
||||
}
|
||||
|
||||
@ApiOperation("分页查询户外招聘会参会岗位")
|
||||
@ApiOperation("获取户外招聘会参会企业二维码内容")
|
||||
@PreAuthorize("@ss.hasPermi('cms:outdoorFair:query')")
|
||||
@GetMapping("/{fairId}/companies/{companyId}/qrcode")
|
||||
public AjaxResult getCompanyQrCode(@PathVariable Long fairId, @PathVariable Long companyId,
|
||||
HttpServletRequest request)
|
||||
{
|
||||
ensureCurrentCompanyIfNeeded(companyId);
|
||||
ensureFairCompanyExists(fairId, companyId);
|
||||
ensureFairCompanyApprovedIfCompany(fairId, companyId);
|
||||
return success(buildCompanyQrCodeData(request, fairId, companyId));
|
||||
}
|
||||
|
||||
@ApiOperation("分页查询户外招聘会参会岗位")
|
||||
@PreAuthorize("@ss.hasAnyPermi('cms:outdoorFair:list,cms:outdoorFair:query')")
|
||||
@GetMapping("/{fairId}/jobs")
|
||||
public AjaxResult listJobs(@PathVariable Long fairId, Long companyId, String companyName,
|
||||
String jobTitle, String education, String experience,
|
||||
Integer current, Integer pageSize)
|
||||
{
|
||||
if (RoleUtils.isCompanyAdmin()) {
|
||||
Company company = getCurrentCompanyOrThrow();
|
||||
companyId = company.getCompanyId();
|
||||
companyName = null;
|
||||
}
|
||||
int page = current == null || current < 1 ? 1 : current;
|
||||
int size = pageSize == null || pageSize < 1 ? 10 : Math.min(pageSize, 100);
|
||||
int offset = (page - 1) * size;
|
||||
@@ -491,14 +589,29 @@ public class OutdoorFairController extends BaseController
|
||||
public AjaxResult getCompanyJob(@PathVariable Long fairId, @PathVariable Long jobId)
|
||||
{
|
||||
ensureFairJobExists(fairId, jobId);
|
||||
ensureFairJobBelongsToCurrentCompanyIfNeeded(fairId, jobId);
|
||||
return success(jobService.selectJobByJobId(jobId));
|
||||
}
|
||||
|
||||
@ApiOperation("当前企业新增户外招聘会岗位")
|
||||
@PreAuthorize("@ss.hasPermi('cms:outdoorFair:list')")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
@PostMapping("/{fairId}/company/jobs")
|
||||
public AjaxResult addCurrentCompanyJob(@PathVariable Long fairId, @RequestBody Job job)
|
||||
{
|
||||
Company company = getCurrentCompanyOrThrow();
|
||||
ensureFairCompanyApprovedIfCompany(fairId, company.getCompanyId());
|
||||
return addCompanyJobInternal(fairId, company.getCompanyId(), job);
|
||||
}
|
||||
|
||||
@ApiOperation("新增户外招聘会参会企业")
|
||||
@PreAuthorize("@ss.hasPermi('cms:outdoorFair:edit')")
|
||||
@PostMapping("/{fairId}/companies")
|
||||
public AjaxResult addCompany(@PathVariable Long fairId, @RequestBody Map<String, Object> request)
|
||||
{
|
||||
if (RoleUtils.isCompanyAdmin()) {
|
||||
return AjaxResult.error("企业用户请使用报名入口");
|
||||
}
|
||||
Long companyId = Long.valueOf(String.valueOf(request.get("companyId")));
|
||||
Company company = companyMapper.selectById(companyId);
|
||||
if (company == null || company.getStatus() == null || company.getStatus() != 1) {
|
||||
@@ -508,12 +621,36 @@ public class OutdoorFairController extends BaseController
|
||||
return success();
|
||||
}
|
||||
|
||||
@ApiOperation("审核户外招聘会参会企业")
|
||||
@PreAuthorize("@ss.hasPermi('cms:outdoorFair:edit')")
|
||||
@PutMapping("/{fairId}/companies/{id}/review")
|
||||
public AjaxResult reviewCompany(@PathVariable Long fairId, @PathVariable Long id,
|
||||
@RequestBody Map<String, Object> request)
|
||||
{
|
||||
String reviewStatus = String.valueOf(request.get("reviewStatus"));
|
||||
validateReviewStatus(reviewStatus);
|
||||
FairCompany relation = fairCompanyMapper.selectById(id);
|
||||
if (relation == null || !fairId.equals(relation.getJobFairId())) {
|
||||
return AjaxResult.error("参会企业不存在");
|
||||
}
|
||||
String reviewRemark = request.get("reviewRemark") == null ? null : String.valueOf(request.get("reviewRemark"));
|
||||
validateRejectRemark(reviewStatus, reviewRemark);
|
||||
relation.setReviewStatus(reviewStatus);
|
||||
relation.setReviewBy(getUsername());
|
||||
relation.setReviewTime(new Date());
|
||||
relation.setReviewRemark(reviewRemark);
|
||||
return toAjax(fairCompanyMapper.updateById(relation));
|
||||
}
|
||||
|
||||
@ApiOperation("移除户外招聘会参会企业")
|
||||
@PreAuthorize("@ss.hasPermi('cms:outdoorFair:edit')")
|
||||
@DeleteMapping("/{fairId}/companies/{id}")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public AjaxResult removeCompany(@PathVariable Long fairId, @PathVariable Long id)
|
||||
{
|
||||
if (RoleUtils.isCompanyAdmin()) {
|
||||
return AjaxResult.error("企业用户无权移除参会企业");
|
||||
}
|
||||
FairCompany relation = fairCompanyMapper.selectById(id);
|
||||
if (relation == null || !fairId.equals(relation.getJobFairId())) {
|
||||
return AjaxResult.error("参会企业不存在");
|
||||
@@ -530,9 +667,19 @@ public class OutdoorFairController extends BaseController
|
||||
@PostMapping("/{fairId}/companies/{companyId}/jobs")
|
||||
public AjaxResult addCompanyJob(@PathVariable Long fairId, @PathVariable Long companyId,
|
||||
@RequestBody Job job)
|
||||
{
|
||||
ensureCurrentCompanyIfNeeded(companyId);
|
||||
ensureFairCompanyApprovedIfCompany(fairId, companyId);
|
||||
return addCompanyJobInternal(fairId, companyId, job);
|
||||
}
|
||||
|
||||
private AjaxResult addCompanyJobInternal(Long fairId, Long companyId, Job job)
|
||||
{
|
||||
ensureFairCompanyExists(fairId, companyId);
|
||||
Company company = companyMapper.selectById(companyId);
|
||||
if (company == null) {
|
||||
return AjaxResult.error("企业不存在");
|
||||
}
|
||||
job.setCompanyId(companyId);
|
||||
job.setCompanyName(company.getName());
|
||||
if (job.getIsPublish() == null) {
|
||||
@@ -546,6 +693,7 @@ public class OutdoorFairController extends BaseController
|
||||
relation.setFairId(fairId);
|
||||
relation.setCompanyId(companyId);
|
||||
relation.setJobId(job.getJobId());
|
||||
relation.setReviewStatus(RoleUtils.isCompanyAdmin() ? "0" : "1");
|
||||
relation.setCreateBy(getUsername());
|
||||
outdoorFairJobMapper.insert(relation);
|
||||
return success(job);
|
||||
@@ -558,6 +706,7 @@ public class OutdoorFairController extends BaseController
|
||||
@RequestBody Job job)
|
||||
{
|
||||
ensureFairJobExists(fairId, jobId);
|
||||
ensureFairJobBelongsToCurrentCompanyIfNeeded(fairId, jobId);
|
||||
job.setJobId(jobId);
|
||||
// 当前弹窗不修改发布状态,避免重复触发岗位发布流程。
|
||||
job.setIsPublish(null);
|
||||
@@ -571,12 +720,37 @@ public class OutdoorFairController extends BaseController
|
||||
public AjaxResult removeCompanyJob(@PathVariable Long fairId, @PathVariable Long jobId)
|
||||
{
|
||||
ensureFairJobExists(fairId, jobId);
|
||||
ensureFairJobBelongsToCurrentCompanyIfNeeded(fairId, jobId);
|
||||
outdoorFairJobMapper.delete(new LambdaQueryWrapper<OutdoorFairJob>()
|
||||
.eq(OutdoorFairJob::getFairId, fairId)
|
||||
.eq(OutdoorFairJob::getJobId, jobId));
|
||||
return toAjax(jobService.deleteJobByJobIds(new Long[]{jobId}));
|
||||
}
|
||||
|
||||
@ApiOperation("审核户外招聘会岗位")
|
||||
@PreAuthorize("@ss.hasPermi('cms:outdoorFair:edit')")
|
||||
@PutMapping("/{fairId}/jobs/{jobId}/review")
|
||||
public AjaxResult reviewCompanyJob(@PathVariable Long fairId, @PathVariable Long jobId,
|
||||
@RequestBody Map<String, Object> request)
|
||||
{
|
||||
String reviewStatus = String.valueOf(request.get("reviewStatus"));
|
||||
validateReviewStatus(reviewStatus);
|
||||
OutdoorFairJob relation = outdoorFairJobMapper.selectOne(new LambdaQueryWrapper<OutdoorFairJob>()
|
||||
.eq(OutdoorFairJob::getFairId, fairId)
|
||||
.eq(OutdoorFairJob::getJobId, jobId)
|
||||
.last("LIMIT 1"));
|
||||
if (relation == null) {
|
||||
return AjaxResult.error("当前招聘会不存在该岗位");
|
||||
}
|
||||
String reviewRemark = request.get("reviewRemark") == null ? null : String.valueOf(request.get("reviewRemark"));
|
||||
validateRejectRemark(reviewStatus, reviewRemark);
|
||||
relation.setReviewStatus(reviewStatus);
|
||||
relation.setReviewBy(getUsername());
|
||||
relation.setReviewTime(new Date());
|
||||
relation.setReviewRemark(reviewRemark);
|
||||
return toAjax(outdoorFairJobMapper.updateById(relation));
|
||||
}
|
||||
|
||||
private void ensureFairCompanyExists(Long fairId, Long companyId)
|
||||
{
|
||||
Long count = fairCompanyMapper.selectCount(new LambdaQueryWrapper<FairCompany>()
|
||||
@@ -587,6 +761,30 @@ public class OutdoorFairController extends BaseController
|
||||
}
|
||||
}
|
||||
|
||||
private void ensureFairCompanyApprovedIfCompany(Long fairId, Long companyId)
|
||||
{
|
||||
if (!RoleUtils.isCompanyAdmin()) {
|
||||
return;
|
||||
}
|
||||
FairCompany relation = fairCompanyMapper.selectOne(new LambdaQueryWrapper<FairCompany>()
|
||||
.eq(FairCompany::getJobFairId, fairId)
|
||||
.eq(FairCompany::getCompanyId, companyId)
|
||||
.last("LIMIT 1"));
|
||||
if (relation == null) {
|
||||
throw new ServiceException("该企业未参加当前招聘会");
|
||||
}
|
||||
if (!"1".equals(relation.getReviewStatus())) {
|
||||
throw new ServiceException("报名审核通过后才能操作");
|
||||
}
|
||||
}
|
||||
|
||||
private void ensureFairExists(Long fairId)
|
||||
{
|
||||
if (fairId == null || outdoorFairService.getById(fairId) == null) {
|
||||
throw new ServiceException("户外招聘会不存在");
|
||||
}
|
||||
}
|
||||
|
||||
private void ensureFairJobExists(Long fairId, Long jobId)
|
||||
{
|
||||
Long count = outdoorFairJobMapper.selectCount(new LambdaQueryWrapper<OutdoorFairJob>()
|
||||
@@ -597,18 +795,171 @@ public class OutdoorFairController extends BaseController
|
||||
}
|
||||
}
|
||||
|
||||
private void ensureFairJobBelongsToCurrentCompanyIfNeeded(Long fairId, Long jobId)
|
||||
{
|
||||
if (!RoleUtils.isCompanyAdmin()) {
|
||||
return;
|
||||
}
|
||||
Company company = getCurrentCompanyOrThrow();
|
||||
Long count = outdoorFairJobMapper.selectCount(new LambdaQueryWrapper<OutdoorFairJob>()
|
||||
.eq(OutdoorFairJob::getFairId, fairId)
|
||||
.eq(OutdoorFairJob::getJobId, jobId)
|
||||
.eq(OutdoorFairJob::getCompanyId, company.getCompanyId()));
|
||||
if (count == null || count == 0) {
|
||||
throw new ServiceException("只能维护本企业报名岗位");
|
||||
}
|
||||
}
|
||||
|
||||
private void ensureCurrentCompanyIfNeeded(Long companyId)
|
||||
{
|
||||
if (!RoleUtils.isCompanyAdmin()) {
|
||||
return;
|
||||
}
|
||||
Company company = getCurrentCompanyOrThrow();
|
||||
if (!company.getCompanyId().equals(companyId)) {
|
||||
throw new ServiceException("只能维护本企业报名信息");
|
||||
}
|
||||
}
|
||||
|
||||
private Company getCurrentCompanyOrThrow()
|
||||
{
|
||||
Company company = getCurrentCompanyOrNull();
|
||||
if (company == null) {
|
||||
throw new ServiceException("未查询到当前登录企业信息");
|
||||
}
|
||||
return company;
|
||||
}
|
||||
|
||||
private Company getCurrentCompanyOrNull()
|
||||
{
|
||||
if (!RoleUtils.isCompanyAdmin()) {
|
||||
return null;
|
||||
}
|
||||
LoginUser loginUser = SecurityUtils.getLoginUser();
|
||||
if (loginUser == null || loginUser.getUser() == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
SysUser sysUser = loginUser.getUser();
|
||||
Company company = null;
|
||||
if (StringUtils.isNotBlank(sysUser.getIdCard())) {
|
||||
company = companyService.queryCodeCompany(sysUser.getIdCard());
|
||||
if (company != null) {
|
||||
return company;
|
||||
}
|
||||
}
|
||||
|
||||
Long userId = loginUser.getUserId() == null ? sysUser.getUserId() : loginUser.getUserId();
|
||||
if (userId != null) {
|
||||
company = companyMapper.selectOne(new LambdaQueryWrapper<Company>()
|
||||
.eq(Company::getUserId, userId)
|
||||
.eq(Company::getDelFlag, "0")
|
||||
.orderByDesc(Company::getUpdateTime)
|
||||
.last("LIMIT 1"));
|
||||
if (company != null) {
|
||||
return company;
|
||||
}
|
||||
}
|
||||
|
||||
List<String> companyNames = new ArrayList<>();
|
||||
if (StringUtils.isNotBlank(sysUser.getNickName())) {
|
||||
companyNames.add(sysUser.getNickName());
|
||||
}
|
||||
if (StringUtils.isNotBlank(sysUser.getUserName()) && !companyNames.contains(sysUser.getUserName())) {
|
||||
companyNames.add(sysUser.getUserName());
|
||||
}
|
||||
if (!companyNames.isEmpty()) {
|
||||
List<Company> companies = companyMapper.selectList(new LambdaQueryWrapper<Company>()
|
||||
.in(Company::getName, companyNames)
|
||||
.eq(Company::getStatus, 1)
|
||||
.eq(Company::getDelFlag, "0")
|
||||
.orderByDesc(Company::getUpdateTime));
|
||||
if (companies.size() == 1) {
|
||||
return companies.get(0);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void fillCompanyApplyState(List<OutdoorFair> list, HttpServletRequest request, Company company)
|
||||
{
|
||||
if (!RoleUtils.isCompanyAdmin() || list == null || list.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
if (company == null) {
|
||||
for (OutdoorFair fair : list) {
|
||||
fair.setSignedUp(false);
|
||||
}
|
||||
return;
|
||||
}
|
||||
List<Long> fairIds = list.stream().map(OutdoorFair::getId).collect(Collectors.toList());
|
||||
Map<Long, FairCompany> relationMap = fairCompanyMapper.selectList(new LambdaQueryWrapper<FairCompany>()
|
||||
.in(FairCompany::getJobFairId, fairIds)
|
||||
.eq(FairCompany::getCompanyId, company.getCompanyId()))
|
||||
.stream()
|
||||
.collect(Collectors.toMap(FairCompany::getJobFairId, item -> item, (a, b) -> a));
|
||||
for (OutdoorFair fair : list) {
|
||||
FairCompany relation = relationMap.get(fair.getId());
|
||||
boolean signedUp = relation != null;
|
||||
fair.setCompanyId(company.getCompanyId());
|
||||
fair.setCompanyName(company.getName());
|
||||
fair.setSignedUp(signedUp);
|
||||
fair.setReviewStatus(relation == null ? null : relation.getReviewStatus());
|
||||
fair.setReviewRemark(relation == null ? null : relation.getReviewRemark());
|
||||
if (signedUp) {
|
||||
String h5Url = buildOutdoorFairCompanyH5Url(request, fair.getId(), company.getCompanyId());
|
||||
fair.setH5Url(h5Url);
|
||||
fair.setQrCodeContent(h5Url);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private AjaxResult emptyAjaxPage()
|
||||
{
|
||||
AjaxResult result = AjaxResult.success();
|
||||
result.put("rows", new ArrayList<>());
|
||||
result.put("total", 0);
|
||||
return result;
|
||||
}
|
||||
|
||||
private void ensureFairCompany(Long fairId, Long companyId)
|
||||
{
|
||||
Long count = fairCompanyMapper.selectCount(new LambdaQueryWrapper<FairCompany>()
|
||||
ensureFairCompany(fairId, companyId, "1");
|
||||
}
|
||||
|
||||
private FairCompany ensureFairCompany(Long fairId, Long companyId, String reviewStatus)
|
||||
{
|
||||
FairCompany existing = fairCompanyMapper.selectOne(new LambdaQueryWrapper<FairCompany>()
|
||||
.eq(FairCompany::getJobFairId, fairId)
|
||||
.eq(FairCompany::getCompanyId, companyId));
|
||||
if (count != null && count > 0) {
|
||||
return;
|
||||
.eq(FairCompany::getCompanyId, companyId)
|
||||
.last("LIMIT 1"));
|
||||
if (existing != null) {
|
||||
if (StringUtils.isBlank(existing.getReviewStatus())) {
|
||||
existing.setReviewStatus(reviewStatus);
|
||||
fairCompanyMapper.updateById(existing);
|
||||
}
|
||||
return existing;
|
||||
}
|
||||
FairCompany fairCompany = new FairCompany();
|
||||
fairCompany.setJobFairId(fairId);
|
||||
fairCompany.setCompanyId(companyId);
|
||||
fairCompany.setReviewStatus(reviewStatus);
|
||||
fairCompanyMapper.insert(fairCompany);
|
||||
return fairCompany;
|
||||
}
|
||||
|
||||
private void validateReviewStatus(String reviewStatus)
|
||||
{
|
||||
if (!"0".equals(reviewStatus) && !"1".equals(reviewStatus) && !"2".equals(reviewStatus)) {
|
||||
throw new ServiceException("审核状态不正确");
|
||||
}
|
||||
}
|
||||
|
||||
private void validateRejectRemark(String reviewStatus, String reviewRemark)
|
||||
{
|
||||
if ("2".equals(reviewStatus) && StringUtils.isBlank(reviewRemark)) {
|
||||
throw new ServiceException("驳回时请填写原因");
|
||||
}
|
||||
}
|
||||
|
||||
private void resetBoothSnapshot(OutdoorFair outdoorFair, boolean forceRecreate)
|
||||
@@ -640,4 +991,32 @@ public class OutdoorFairController extends BaseController
|
||||
outdoorFair.setBoothCount(0);
|
||||
}
|
||||
}
|
||||
|
||||
private String buildOutdoorFairCompanyH5Url(HttpServletRequest request, Long fairId, Long companyId)
|
||||
{
|
||||
String baseUrl = request.getScheme() + "://" + request.getServerName();
|
||||
int port = request.getServerPort();
|
||||
if (port != 80 && port != 443) {
|
||||
baseUrl += ":" + port;
|
||||
}
|
||||
String contextPath = request.getContextPath() == null ? "" : request.getContextPath();
|
||||
return baseUrl + contextPath + "/h5/outdoor-fair/company.html?fairId=" + fairId + "&companyId=" + companyId;
|
||||
}
|
||||
|
||||
private Map<String, Object> buildCompanyQrCodeData(HttpServletRequest request, Long fairId, Long companyId)
|
||||
{
|
||||
OutdoorFairDevice device = outdoorFairDeviceMapper.selectOne(new LambdaQueryWrapper<OutdoorFairDevice>()
|
||||
.eq(OutdoorFairDevice::getFairId, fairId)
|
||||
.eq(OutdoorFairDevice::getCompanyId, companyId)
|
||||
.last("LIMIT 1"));
|
||||
String h5Url = buildOutdoorFairCompanyH5Url(request, fairId, companyId);
|
||||
Map<String, Object> data = new HashMap<>();
|
||||
data.put("fairId", fairId);
|
||||
data.put("companyId", companyId);
|
||||
data.put("deviceId", device == null ? null : device.getId());
|
||||
data.put("deviceCode", device == null ? null : device.getDeviceCode());
|
||||
data.put("h5Url", h5Url);
|
||||
data.put("qrCodeContent", h5Url);
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,20 +63,21 @@ public class OutdoorFairDeviceController extends BaseController
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量生成设备码(UUID)
|
||||
* 请求体:{ fairId, quantity }
|
||||
* 新增设备码(由用户手动填写设备码,可选同时绑定参会企业)
|
||||
* 请求体:{ fairId, deviceCode, companyId? }
|
||||
*/
|
||||
@ApiOperation("批量生成设备码")
|
||||
@ApiOperation("新增设备码")
|
||||
@PreAuthorize("@ss.hasPermi('cms:outdoorFair:add')")
|
||||
@Log(title = "户外招聘会设备码", businessType = BusinessType.INSERT)
|
||||
@PostMapping("/batch")
|
||||
public AjaxResult batch(@RequestBody Map<String, Object> request)
|
||||
@PostMapping("/add")
|
||||
public AjaxResult add(@RequestBody Map<String, Object> request)
|
||||
{
|
||||
Long fairId = request.get("fairId") == null ? null : Long.valueOf(String.valueOf(request.get("fairId")));
|
||||
Integer quantity = request.get("quantity") == null ? null : Integer.valueOf(String.valueOf(request.get("quantity")));
|
||||
List<OutdoorFairDevice> devices = outdoorFairDeviceService.batchGenerate(fairId, quantity);
|
||||
AjaxResult result = AjaxResult.success("生成成功");
|
||||
result.put("data", devices);
|
||||
String deviceCode = request.get("deviceCode") == null ? null : String.valueOf(request.get("deviceCode")).trim();
|
||||
Long companyId = request.get("companyId") == null ? null : Long.valueOf(String.valueOf(request.get("companyId")));
|
||||
OutdoorFairDevice device = outdoorFairDeviceService.addDevice(fairId, deviceCode, companyId);
|
||||
AjaxResult result = AjaxResult.success("新增成功");
|
||||
result.put("data", device);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.ruoyi.cms.domain;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.ruoyi.common.core.domain.BaseEntity;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 跨域联合招聘会-联盟城市。
|
||||
*/
|
||||
@Data
|
||||
@TableName("shz.cms_cross_city_alliance")
|
||||
public class CrossCityAlliance extends BaseEntity
|
||||
{
|
||||
@TableId(value = "id", type = IdType.AUTO)
|
||||
private Long id;
|
||||
|
||||
/** 城市名称 */
|
||||
private String name;
|
||||
}
|
||||
@@ -9,6 +9,8 @@ import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.ruoyi.common.core.domain.BaseEntity;
|
||||
|
||||
import java.util.Date;
|
||||
/**
|
||||
* 公司招聘会关联对象 fair_company
|
||||
* @author lishundong
|
||||
@@ -34,4 +36,16 @@ public class FairCompany extends BaseEntity
|
||||
@ApiModelProperty("招聘会id")
|
||||
private Long jobFairId;
|
||||
|
||||
@ApiModelProperty("审核状态 0待审核 1通过 2驳回")
|
||||
private String reviewStatus;
|
||||
|
||||
@ApiModelProperty("审核人")
|
||||
private String reviewBy;
|
||||
|
||||
@ApiModelProperty("审核时间")
|
||||
private Date reviewTime;
|
||||
|
||||
@ApiModelProperty("审核备注")
|
||||
private String reviewRemark;
|
||||
|
||||
}
|
||||
|
||||
@@ -205,6 +205,19 @@ public class Job extends BaseEntity
|
||||
|
||||
@ApiModelProperty("审核状态")
|
||||
private String reviewStatus;
|
||||
|
||||
@TableField(exist = false)
|
||||
@ApiModelProperty("户外招聘会岗位审核状态")
|
||||
private String fairReviewStatus;
|
||||
|
||||
@TableField(exist = false)
|
||||
@ApiModelProperty("户外招聘会岗位审核备注")
|
||||
private String fairReviewRemark;
|
||||
|
||||
@TableField(exist = false)
|
||||
@ApiModelProperty("户外招聘会岗位审核时间")
|
||||
private String fairReviewTime;
|
||||
|
||||
//简历是否被查看
|
||||
@TableField(exist = false)
|
||||
private String isView;
|
||||
|
||||
@@ -3,6 +3,7 @@ package com.ruoyi.cms.domain;
|
||||
import java.util.Date;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
@@ -88,4 +89,32 @@ public class OutdoorFair extends BaseEntity
|
||||
@Excel(name = "招聘会照片")
|
||||
@ApiModelProperty("招聘会照片")
|
||||
private String photoUrl;
|
||||
|
||||
@TableField(exist = false)
|
||||
@ApiModelProperty("当前企业ID")
|
||||
private Long companyId;
|
||||
|
||||
@TableField(exist = false)
|
||||
@ApiModelProperty("当前企业名称")
|
||||
private String companyName;
|
||||
|
||||
@TableField(exist = false)
|
||||
@ApiModelProperty("当前企业是否已报名")
|
||||
private Boolean signedUp;
|
||||
|
||||
@TableField(exist = false)
|
||||
@ApiModelProperty("当前企业公共H5页面链接")
|
||||
private String h5Url;
|
||||
|
||||
@TableField(exist = false)
|
||||
@ApiModelProperty("当前企业二维码内容")
|
||||
private String qrCodeContent;
|
||||
|
||||
@TableField(exist = false)
|
||||
@ApiModelProperty("当前企业报名审核状态")
|
||||
private String reviewStatus;
|
||||
|
||||
@TableField(exist = false)
|
||||
@ApiModelProperty("当前企业报名审核备注")
|
||||
private String reviewRemark;
|
||||
}
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
package com.ruoyi.cms.domain;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import com.ruoyi.common.core.domain.BaseEntity;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 户外招聘会岗位关联。
|
||||
*/
|
||||
@@ -21,4 +25,20 @@ public class OutdoorFairJob extends BaseEntity
|
||||
private Long companyId;
|
||||
|
||||
private Long jobId;
|
||||
|
||||
/** 审核状态 0待审核 1通过 2驳回。 */
|
||||
private String reviewStatus;
|
||||
|
||||
private String reviewBy;
|
||||
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date reviewTime;
|
||||
|
||||
private String reviewRemark;
|
||||
|
||||
@TableField(exist = false)
|
||||
private String companyName;
|
||||
|
||||
@TableField(exist = false)
|
||||
private String jobTitle;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
package com.ruoyi.cms.domain.rc;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 跨域招聘会岗位视图(聚合 public_job_fair -> public_job_fair_job -> job -> company)
|
||||
*/
|
||||
@Data
|
||||
@ApiModel("跨域招聘会岗位")
|
||||
public class CrossDomainJobVO implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@ApiModelProperty("岗位ID")
|
||||
private Long jobId;
|
||||
|
||||
@ApiModelProperty("岗位名称")
|
||||
private String jobTitle;
|
||||
|
||||
@ApiModelProperty("最低薪资")
|
||||
private Integer minSalary;
|
||||
|
||||
@ApiModelProperty("最高薪资")
|
||||
private Integer maxSalary;
|
||||
|
||||
@ApiModelProperty("学历要求")
|
||||
private String education;
|
||||
|
||||
@ApiModelProperty("经验要求")
|
||||
private String experience;
|
||||
|
||||
@ApiModelProperty("招聘人数")
|
||||
private Integer vacancies;
|
||||
|
||||
@JsonFormat(pattern = "yyyy-MM-dd")
|
||||
@ApiModelProperty("发布日期")
|
||||
private Date postingDate;
|
||||
|
||||
@ApiModelProperty("企业ID")
|
||||
private Long companyId;
|
||||
|
||||
@ApiModelProperty("企业名称")
|
||||
private String companyName;
|
||||
|
||||
@ApiModelProperty("行业")
|
||||
private String industry;
|
||||
|
||||
@ApiModelProperty("企业规模")
|
||||
private String scale;
|
||||
|
||||
@ApiModelProperty("招聘会ID")
|
||||
private String jobFairId;
|
||||
|
||||
@ApiModelProperty("招聘会标题")
|
||||
private String jobFairTitle;
|
||||
|
||||
@ApiModelProperty("跨域联盟城市(逗号分隔)")
|
||||
private String crossDomainCities;
|
||||
}
|
||||
@@ -151,6 +151,12 @@ public class PublicJobFair implements Serializable {
|
||||
@ApiModelProperty("招聘会类别")
|
||||
private String jobFairCategory;
|
||||
|
||||
@ApiModelProperty("是否跨域(Y-是 N-否)")
|
||||
private String isCrossDomain;
|
||||
|
||||
@ApiModelProperty("跨域联盟城市(逗号分隔的城市名称)")
|
||||
private String crossDomainCities;
|
||||
|
||||
@ApiModelProperty("纬度")
|
||||
private java.math.BigDecimal latitude;
|
||||
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.ruoyi.cms.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.ruoyi.cms.domain.CrossCityAlliance;
|
||||
|
||||
/**
|
||||
* 跨域联合招聘会-联盟城市 Mapper。
|
||||
*/
|
||||
public interface CrossCityAllianceMapper extends BaseMapper<CrossCityAlliance>
|
||||
{
|
||||
}
|
||||
@@ -32,6 +32,11 @@ public interface PublicJobFairMapper extends BaseMapper<PublicJobFair> {
|
||||
*/
|
||||
List<Job> selectJobListByJobFairIdAndCompanyId(@Param("jobFairId") String jobFairId, @Param("companyId") Long companyId);
|
||||
|
||||
/**
|
||||
* 查询跨域招聘会下的岗位(可按联盟城市过滤,cityNames 为逗号分隔的城市名称)
|
||||
*/
|
||||
List<CrossDomainJobVO> selectCrossDomainJobs(@Param("cityNames") String cityNames);
|
||||
|
||||
/**
|
||||
* 检查用户是否已报名
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.ruoyi.cms.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.ruoyi.cms.domain.CrossCityAlliance;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 跨域联合招聘会-联盟城市 Service。
|
||||
*/
|
||||
public interface ICrossCityAllianceService extends IService<CrossCityAlliance>
|
||||
{
|
||||
/**
|
||||
* 查询联盟城市列表(支持按城市名称模糊查询)。
|
||||
*/
|
||||
List<CrossCityAlliance> selectAllianceList(CrossCityAlliance query);
|
||||
}
|
||||
@@ -16,13 +16,14 @@ public interface IOutdoorFairDeviceService extends IService<OutdoorFairDevice>
|
||||
List<OutdoorFairDevice> selectDeviceList(OutdoorFairDevice query);
|
||||
|
||||
/**
|
||||
* 批量生成设备码(UUID)。
|
||||
* 新增设备码(由用户手动填写设备码,可选同时绑定参会企业)。
|
||||
*
|
||||
* @param fairId 招聘会ID
|
||||
* @param quantity 生成数量
|
||||
* @return 已生成的设备码列表
|
||||
* @param deviceCode 设备码(全局唯一)
|
||||
* @param companyId 参会企业ID,为空表示暂不绑定
|
||||
* @return 新增的设备码
|
||||
*/
|
||||
List<OutdoorFairDevice> batchGenerate(Long fairId, Integer quantity);
|
||||
OutdoorFairDevice addDevice(Long fairId, String deviceCode, Long companyId);
|
||||
|
||||
/**
|
||||
* 绑定/解绑参会企业(companyId 为空表示解绑)。
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
package com.ruoyi.cms.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.ruoyi.cms.domain.CrossCityAlliance;
|
||||
import com.ruoyi.cms.mapper.CrossCityAllianceMapper;
|
||||
import com.ruoyi.cms.service.ICrossCityAllianceService;
|
||||
import com.ruoyi.common.utils.StringUtils;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 跨域联合招聘会-联盟城市 Service 实现。
|
||||
*/
|
||||
@Service
|
||||
public class CrossCityAllianceServiceImpl
|
||||
extends ServiceImpl<CrossCityAllianceMapper, CrossCityAlliance>
|
||||
implements ICrossCityAllianceService
|
||||
{
|
||||
@Override
|
||||
public List<CrossCityAlliance> selectAllianceList(CrossCityAlliance query)
|
||||
{
|
||||
LambdaQueryWrapper<CrossCityAlliance> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.like(StringUtils.isNotBlank(query.getName()), CrossCityAlliance::getName, query.getName());
|
||||
wrapper.orderByAsc(CrossCityAlliance::getId);
|
||||
return list(wrapper);
|
||||
}
|
||||
}
|
||||
@@ -15,10 +15,8 @@ import com.ruoyi.common.utils.StringUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* 户外招聘会设备码 Service 实现。
|
||||
@@ -47,28 +45,45 @@ public class OutdoorFairDeviceServiceImpl
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<OutdoorFairDevice> batchGenerate(Long fairId, Integer quantity)
|
||||
public OutdoorFairDevice addDevice(Long fairId, String deviceCode, Long companyId)
|
||||
{
|
||||
if (fairId == null) {
|
||||
throw new ServiceException("招聘会ID不能为空");
|
||||
}
|
||||
int count = quantity == null ? 0 : quantity;
|
||||
if (count <= 0) {
|
||||
throw new ServiceException("生成数量必须大于0");
|
||||
if (StringUtils.isBlank(deviceCode)) {
|
||||
throw new ServiceException("设备码不能为空");
|
||||
}
|
||||
if (count > 1000) {
|
||||
throw new ServiceException("单次生成数量不能超过1000");
|
||||
// 设备码全局唯一(H5 端按设备码查询)
|
||||
Long exists = baseMapper.selectCount(new LambdaQueryWrapper<OutdoorFairDevice>()
|
||||
.eq(OutdoorFairDevice::getDeviceCode, deviceCode));
|
||||
if (exists != null && exists > 0) {
|
||||
throw new ServiceException("设备码已存在");
|
||||
}
|
||||
List<OutdoorFairDevice> devices = new ArrayList<>(count);
|
||||
for (int i = 0; i < count; i++) {
|
||||
OutdoorFairDevice device = new OutdoorFairDevice();
|
||||
device.setFairId(fairId);
|
||||
// 去掉中划线,得到 32 位十六进制设备码
|
||||
device.setDeviceCode(UUID.randomUUID().toString().replace("-", ""));
|
||||
devices.add(device);
|
||||
device.setDeviceCode(deviceCode);
|
||||
if (companyId != null) {
|
||||
// 仅允许绑定已参加当前招聘会的企业
|
||||
Long count = fairCompanyMapper.selectCount(new LambdaQueryWrapper<FairCompany>()
|
||||
.eq(FairCompany::getJobFairId, fairId)
|
||||
.eq(FairCompany::getCompanyId, companyId));
|
||||
if (count == null || count == 0) {
|
||||
throw new ServiceException("该企业未参加当前招聘会");
|
||||
}
|
||||
saveBatch(devices);
|
||||
return devices;
|
||||
// 同一招聘会下,一个企业只能绑定一个设备码
|
||||
Long bound = baseMapper.selectCount(new LambdaQueryWrapper<OutdoorFairDevice>()
|
||||
.eq(OutdoorFairDevice::getFairId, fairId)
|
||||
.eq(OutdoorFairDevice::getCompanyId, companyId));
|
||||
if (bound != null && bound > 0) {
|
||||
throw new ServiceException("该企业已绑定其他设备码");
|
||||
}
|
||||
Company company = companyMapper.selectById(companyId);
|
||||
device.setCompanyId(companyId);
|
||||
device.setCompanyName(company == null ? null : company.getName());
|
||||
device.setBindTime(new Date());
|
||||
}
|
||||
save(device);
|
||||
return device;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -100,4 +100,9 @@ public interface IPublicJobFairService {
|
||||
* 查询招聘会报名列表
|
||||
*/
|
||||
List<JobFairSignUpVO> selectSignUpList(String jobFairId);
|
||||
|
||||
/**
|
||||
* 查询跨域招聘会下的岗位(可按联盟城市过滤,cityNames 为逗号分隔的城市名称)
|
||||
*/
|
||||
List<CrossDomainJobVO> selectCrossDomainJobs(String cityNames);
|
||||
}
|
||||
|
||||
@@ -219,4 +219,9 @@ public class PublicJobFairServiceImpl implements IPublicJobFairService {
|
||||
public List<JobFairSignUpVO> selectSignUpList(String jobFairId) {
|
||||
return publicJobFairMapper.selectSignUpListByJobFairId(jobFairId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<CrossDomainJobVO> selectCrossDomainJobs(String cityNames) {
|
||||
return publicJobFairMapper.selectCrossDomainJobs(cityNames);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,7 +34,10 @@
|
||||
</sql>
|
||||
|
||||
<select id="selectFairJobPage" resultType="com.ruoyi.cms.domain.Job">
|
||||
SELECT job.*
|
||||
SELECT job.*,
|
||||
relation.review_status AS "fairReviewStatus",
|
||||
relation.review_remark AS "fairReviewRemark",
|
||||
to_char(relation.review_time, 'YYYY-MM-DD HH24:MI:SS') AS "fairReviewTime"
|
||||
FROM shz.cms_outdoor_fair_job relation
|
||||
INNER JOIN shz.job job ON job.job_id = relation.job_id
|
||||
<include refid="fairJobWhere"/>
|
||||
|
||||
@@ -31,6 +31,8 @@
|
||||
<result property="deptCodeLast" column="dept_code_last"/>
|
||||
<result property="deptCodeAll" column="dept_code_all"/>
|
||||
<result property="jobFairCategory" column="job_fair_category"/>
|
||||
<result property="isCrossDomain" column="is_cross_domain"/>
|
||||
<result property="crossDomainCities" column="cross_domain_cities"/>
|
||||
</resultMap>
|
||||
|
||||
<resultMap type="com.ruoyi.cms.domain.rc.PublicJobFairDetail" id="DetailResult">
|
||||
@@ -59,7 +61,7 @@
|
||||
job_fair_host_unit, job_fair_help_unit, job_fair_organize_unit, job_fair_introduction, job_fair_remark,
|
||||
job_fair_phone, job_fair_image, job_fair_area_image, create_by, create_time, update_by, update_time,
|
||||
job_fair_venue_id, job_fair_sign_up_start_time, job_fair_sign_up_end_time, enterprise_num, booth_num,
|
||||
division_name, dept_code_last, dept_code_all, job_fair_category
|
||||
division_name, dept_code_last, dept_code_all, job_fair_category, is_cross_domain, cross_domain_cities
|
||||
from public_job_fair
|
||||
</sql>
|
||||
|
||||
@@ -106,6 +108,23 @@
|
||||
order by j.create_time desc
|
||||
</select>
|
||||
|
||||
<select id="selectCrossDomainJobs" resultType="com.ruoyi.cms.domain.rc.CrossDomainJobVO">
|
||||
select j.job_id as jobId, j.job_title as jobTitle, j.min_salary as minSalary, j.max_salary as maxSalary,
|
||||
j.education, j.experience, j.vacancies, j.posting_date as postingDate,
|
||||
j.company_id as companyId, j.company_name as companyName,
|
||||
c.industry, c.scale,
|
||||
pf.job_fair_id as jobFairId, pf.job_fair_title as jobFairTitle, pf.cross_domain_cities as crossDomainCities
|
||||
from public_job_fair pf
|
||||
inner join public_job_fair_job pfj on pfj.job_fair_id = pf.job_fair_id and pfj.del_flag = '0'
|
||||
inner join job j on j.job_id = pfj.job_id and j.del_flag = '0'
|
||||
left join company c on c.company_id = pfj.company_id
|
||||
where pf.is_cross_domain = 'y' and pf.del_flag = '0'
|
||||
<if test="cityNames != null and cityNames != ''">
|
||||
and string_to_array(pf.cross_domain_cities, ',') && string_to_array(#{cityNames}, ',')
|
||||
</if>
|
||||
order by j.posting_date desc nulls last, j.job_id desc
|
||||
</select>
|
||||
|
||||
<select id="checkUserSignUp" resultType="java.lang.Integer">
|
||||
select count(1) from rc_job_fair_sign_up
|
||||
where job_fair_id = #{jobFairId}::bigint and user_id = #{personId} and del_flag = '0' and status = '0'
|
||||
@@ -126,7 +145,7 @@
|
||||
job_fair_image, job_fair_area_image, job_fair_venue_id,
|
||||
job_fair_sign_up_start_time, job_fair_sign_up_end_time,
|
||||
enterprise_num, booth_num, division_name, dept_code_last, dept_code_all,
|
||||
job_fair_category, latitude, longitude,
|
||||
job_fair_category, is_cross_domain, cross_domain_cities, latitude, longitude,
|
||||
create_by, create_time, del_flag
|
||||
) values (
|
||||
#{jobFairId}, #{jobFairTitle}, #{jobFairAddress}, #{jobFairType},
|
||||
@@ -135,7 +154,7 @@
|
||||
#{jobFairImage}, #{jobFairAreaImage}, #{jobFairVenueId},
|
||||
#{jobFairSignUpStartTime}, #{jobFairSignUpEndTime},
|
||||
#{enterpriseNum}, #{boothNum}, #{divisionName}, #{deptCodeLast}, #{deptCodeAll},
|
||||
#{jobFairCategory}, #{latitude}, #{longitude},
|
||||
#{jobFairCategory}, #{isCrossDomain}, #{crossDomainCities}, #{latitude}, #{longitude},
|
||||
#{createBy}, #{createTime}, '0'
|
||||
)
|
||||
</insert>
|
||||
@@ -165,6 +184,8 @@
|
||||
<if test="deptCodeLast != null">dept_code_last = #{deptCodeLast},</if>
|
||||
<if test="deptCodeAll != null">dept_code_all = #{deptCodeAll},</if>
|
||||
<if test="jobFairCategory != null">job_fair_category = #{jobFairCategory},</if>
|
||||
<if test="isCrossDomain != null">is_cross_domain = #{isCrossDomain},</if>
|
||||
<if test="crossDomainCities != null">cross_domain_cities = #{crossDomainCities},</if>
|
||||
<if test="latitude != null">latitude = #{latitude},</if>
|
||||
<if test="longitude != null">longitude = #{longitude},</if>
|
||||
update_by = #{updateBy},
|
||||
|
||||
@@ -282,6 +282,8 @@ public class SsoService {
|
||||
company.setLegalPhone(object.getString("entLegalPersonPhone"));
|
||||
company.setIndustry(object.getString("entIndustry"));
|
||||
company.setRegisteredAddress(object.getString("entRegisteredAddress"));
|
||||
company.setNature(object.getString("entType"));
|
||||
// company.setScale(object.getString("entSize"));
|
||||
companyService.insertCompany(company);
|
||||
}
|
||||
/**
|
||||
|
||||
100
scripts/migrate-highgo-shz.sh
Executable file
100
scripts/migrate-highgo-shz.sh
Executable file
@@ -0,0 +1,100 @@
|
||||
#!/usr/bin/env bash
|
||||
# Back up the current SHZ schema and restore it into a new `shz` database on
|
||||
# the destination HighGo instance. Run this manually from the repository root.
|
||||
set -euo pipefail
|
||||
|
||||
SOURCE_SSH_HOST="${SOURCE_SSH_HOST:-root@124.243.245.42}"
|
||||
SOURCE_DB_HOST="${SOURCE_DB_HOST:-39.98.44.136}"
|
||||
SOURCE_DB_PORT="${SOURCE_DB_PORT:-6023}"
|
||||
SOURCE_DB_NAME="${SOURCE_DB_NAME:-highgo}"
|
||||
SOURCE_DB_USER="${SOURCE_DB_USER:-sysdba}"
|
||||
SOURCE_SCHEMA="${SOURCE_SCHEMA:-shz}"
|
||||
SOURCE_PASSWORD_FILE="${SOURCE_PASSWORD_FILE:-.codex/skills/query-shz-highgo/.password}"
|
||||
|
||||
TARGET_SSH_HOST="${TARGET_SSH_HOST:-root@124.243.245.42}"
|
||||
TARGET_DB_HOST="${TARGET_DB_HOST:-127.0.0.1}"
|
||||
TARGET_DB_PORT="${TARGET_DB_PORT:-5866}"
|
||||
TARGET_DB_NAME="${TARGET_DB_NAME:-shz}"
|
||||
TARGET_DB_USER="${TARGET_DB_USER:-sysdba}"
|
||||
HIGHGO_BIN="${HIGHGO_BIN:-/opt/HighGo4.5.7-see/bin}"
|
||||
BACKUP_DIR="${BACKUP_DIR:-backups/highgo}"
|
||||
|
||||
for command in ssh shasum; do
|
||||
command -v "$command" >/dev/null || { echo "Missing required command: $command" >&2; exit 2; }
|
||||
done
|
||||
|
||||
[[ -r "$SOURCE_PASSWORD_FILE" ]] || {
|
||||
echo "Source password file is missing or unreadable: $SOURCE_PASSWORD_FILE" >&2
|
||||
exit 2
|
||||
}
|
||||
|
||||
read -r -s -p "Target HighGo password for ${TARGET_DB_USER}: " TARGET_PASSWORD
|
||||
printf '\n'
|
||||
[[ -n "$TARGET_PASSWORD" ]] || { echo "Target password cannot be empty." >&2; exit 2; }
|
||||
|
||||
timestamp="$(date +%Y%m%d-%H%M%S)"
|
||||
mkdir -p "$BACKUP_DIR"
|
||||
backup_file="$BACKUP_DIR/${SOURCE_SCHEMA}-${timestamp}.dump"
|
||||
checksum_file="${backup_file}.sha256"
|
||||
sequence_backup_file="$BACKUP_DIR/public-sequences-${timestamp}.dump"
|
||||
sequence_checksum_file="${sequence_backup_file}.sha256"
|
||||
|
||||
remote_psql() {
|
||||
local database="$1"
|
||||
local sql="$2"
|
||||
printf '%s\n' "$TARGET_PASSWORD" | ssh -o BatchMode=yes -o ConnectTimeout=10 "$TARGET_SSH_HOST" \
|
||||
"IFS= read -r PGPASSWORD; export PGPASSWORD; exec '$HIGHGO_BIN/psql' -X --no-psqlrc -h '$TARGET_DB_HOST' -p '$TARGET_DB_PORT' -U '$TARGET_DB_USER' -d '$database' -v ON_ERROR_STOP=1 -Atc \"$sql\""
|
||||
}
|
||||
|
||||
echo "Checking target instance and database name..."
|
||||
remote_psql highgo 'SELECT 1' >/dev/null
|
||||
if [[ "$(remote_psql highgo "SELECT 1 FROM pg_database WHERE datname = '$TARGET_DB_NAME'")" == "1" ]]; then
|
||||
echo "Refusing to continue: target database '$TARGET_DB_NAME' already exists." >&2
|
||||
echo "No backup or restore was performed." >&2
|
||||
exit 3
|
||||
fi
|
||||
|
||||
echo "Creating source backup: $backup_file"
|
||||
source_password="$(<"$SOURCE_PASSWORD_FILE")"
|
||||
printf '%s\n' "$source_password" | ssh -o BatchMode=yes -o ConnectTimeout=10 "$SOURCE_SSH_HOST" \
|
||||
"IFS= read -r PGPASSWORD; export PGPASSWORD; exec '$HIGHGO_BIN/pg_dump' -Fc --no-owner --no-privileges -h '$SOURCE_DB_HOST' -p '$SOURCE_DB_PORT' -U '$SOURCE_DB_USER' -d '$SOURCE_DB_NAME' -n '$SOURCE_SCHEMA'" \
|
||||
>"$backup_file"
|
||||
|
||||
[[ -s "$backup_file" ]] || { echo "Backup is empty; refusing to create target database." >&2; rm -f "$backup_file"; exit 4; }
|
||||
shasum -a 256 "$backup_file" >"$checksum_file"
|
||||
|
||||
echo "Creating public-sequence dependency backup: $sequence_backup_file"
|
||||
printf '%s\n' "$source_password" | ssh -o BatchMode=yes -o ConnectTimeout=10 "$SOURCE_SSH_HOST" \
|
||||
"IFS= read -r PGPASSWORD; export PGPASSWORD; exec '$HIGHGO_BIN/pg_dump' -Fc --no-owner --no-privileges -h '$SOURCE_DB_HOST' -p '$SOURCE_DB_PORT' -U '$SOURCE_DB_USER' -d '$SOURCE_DB_NAME' -t 'public.*_seq'" \
|
||||
>"$sequence_backup_file"
|
||||
|
||||
[[ -s "$sequence_backup_file" ]] || { echo "Sequence backup is empty; refusing to create target database." >&2; rm -f "$backup_file" "$sequence_backup_file"; exit 4; }
|
||||
shasum -a 256 "$sequence_backup_file" >"$sequence_checksum_file"
|
||||
|
||||
echo "Creating target database '$TARGET_DB_NAME'..."
|
||||
remote_psql highgo "CREATE DATABASE \"$TARGET_DB_NAME\"" >/dev/null
|
||||
|
||||
echo "Restoring schema '$SOURCE_SCHEMA' into '$TARGET_DB_NAME'..."
|
||||
{ printf '%s\n' "$TARGET_PASSWORD"; cat "$sequence_backup_file"; } | ssh -o BatchMode=yes -o ConnectTimeout=10 "$TARGET_SSH_HOST" \
|
||||
"IFS= read -r PGPASSWORD; export PGPASSWORD; exec '$HIGHGO_BIN/pg_restore' --single-transaction --exit-on-error --no-owner --no-privileges -h '$TARGET_DB_HOST' -p '$TARGET_DB_PORT' -U '$TARGET_DB_USER' -d '$TARGET_DB_NAME'" \
|
||||
>/dev/null
|
||||
{ printf '%s\n' "$TARGET_PASSWORD"; cat "$backup_file"; } | ssh -o BatchMode=yes -o ConnectTimeout=10 "$TARGET_SSH_HOST" \
|
||||
"IFS= read -r PGPASSWORD; export PGPASSWORD; exec '$HIGHGO_BIN/pg_restore' --single-transaction --exit-on-error --no-owner --no-privileges -h '$TARGET_DB_HOST' -p '$TARGET_DB_PORT' -U '$TARGET_DB_USER' -d '$TARGET_DB_NAME'" \
|
||||
>/dev/null
|
||||
|
||||
echo "Verifying restored schema..."
|
||||
source_count="$(printf '%s\n' "$source_password" | ssh -o BatchMode=yes -o ConnectTimeout=10 "$SOURCE_SSH_HOST" \
|
||||
"IFS= read -r PGPASSWORD; export PGPASSWORD; exec '$HIGHGO_BIN/psql' -X --no-psqlrc -h '$SOURCE_DB_HOST' -p '$SOURCE_DB_PORT' -U '$SOURCE_DB_USER' -d '$SOURCE_DB_NAME' -Atc \"SELECT count(*) FROM information_schema.tables WHERE table_schema = '$SOURCE_SCHEMA' AND table_type = 'BASE TABLE'\"")"
|
||||
target_count="$(remote_psql "$TARGET_DB_NAME" "SELECT count(*) FROM information_schema.tables WHERE table_schema = '$SOURCE_SCHEMA' AND table_type = 'BASE TABLE'")"
|
||||
|
||||
if [[ "$source_count" != "$target_count" ]]; then
|
||||
echo "Verification failed: source has $source_count tables, target has $target_count." >&2
|
||||
exit 5
|
||||
fi
|
||||
|
||||
unset TARGET_PASSWORD source_password
|
||||
echo "Migration complete. Backup: $backup_file"
|
||||
echo "Checksum: $checksum_file"
|
||||
echo "Sequence backup: $sequence_backup_file"
|
||||
echo "Sequence checksum: $sequence_checksum_file"
|
||||
echo "Verified base tables in schema '$SOURCE_SCHEMA': $target_count"
|
||||
67
sql/cms_cross_city_alliance.sql
Normal file
67
sql/cms_cross_city_alliance.sql
Normal file
@@ -0,0 +1,67 @@
|
||||
-- 跨域联合招聘会 - 联盟城市表
|
||||
-- 用于「各地市就业信息」Tab 的联盟城市筛选与管理(替代前端硬编码 mock)。
|
||||
-- 依赖:sys_menu 已存在 menu_id=2114「跨域联合招聘会管理」(parent_id=2055)。
|
||||
|
||||
CREATE SEQUENCE IF NOT EXISTS shz.cms_cross_city_alliance_id_seq
|
||||
START WITH 1 INCREMENT BY 1 NO MINVALUE NO MAXVALUE CACHE 1;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS shz.cms_cross_city_alliance (
|
||||
id BIGINT NOT NULL DEFAULT nextval('shz.cms_cross_city_alliance_id_seq'::regclass) PRIMARY KEY,
|
||||
name VARCHAR(64) NOT NULL,
|
||||
create_by VARCHAR(64),
|
||||
create_time TIMESTAMP,
|
||||
update_by VARCHAR(64),
|
||||
update_time TIMESTAMP,
|
||||
remark VARCHAR(500),
|
||||
del_flag CHAR(1) DEFAULT '0'
|
||||
);
|
||||
|
||||
COMMENT ON TABLE shz.cms_cross_city_alliance IS '跨域联合招聘会-联盟城市表';
|
||||
COMMENT ON COLUMN shz.cms_cross_city_alliance.id IS '主键';
|
||||
COMMENT ON COLUMN shz.cms_cross_city_alliance.name IS '城市名称';
|
||||
COMMENT ON COLUMN shz.cms_cross_city_alliance.del_flag IS '删除标志(0存在 2删除)';
|
||||
|
||||
-- 种子:8 个联盟城市(与原前端 mock 一致),仅在表为空时插入
|
||||
INSERT INTO shz.cms_cross_city_alliance (name, create_by, create_time, del_flag)
|
||||
SELECT * FROM (VALUES
|
||||
('乌鲁木齐市', 'admin', now(), '0'),
|
||||
('克拉玛依市', 'admin', now(), '0'),
|
||||
('昌吉回族自治州', 'admin', now(), '0'),
|
||||
('哈密市', 'admin', now(), '0'),
|
||||
('吐鲁番市', 'admin', now(), '0'),
|
||||
('喀什地区', 'admin', now(), '0'),
|
||||
('阿克苏地区', 'admin', now(), '0'),
|
||||
('伊犁哈萨克自治州','admin', now(), '0')
|
||||
) AS seed(name, create_by, create_time, del_flag)
|
||||
WHERE NOT EXISTS (SELECT 1 FROM shz.cms_cross_city_alliance);
|
||||
|
||||
-- 给已存在的页面菜单 2114 补上 perms(仅当为空时)
|
||||
UPDATE sys_menu
|
||||
SET perms = 'cms:crossCityFair:list', update_by = 'system', update_time = now()
|
||||
WHERE menu_id = 2114 AND (perms IS NULL OR perms = '');
|
||||
|
||||
-- 新增 4 个按钮权限(仅在不存在时插入)
|
||||
INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, remark)
|
||||
SELECT btn.menu_name, 2114, btn.order_num, '', NULL, NULL, '', 1, 0, 'F', '0', '0', btn.perms, '#', 'system', now(), '跨域联合招聘会-按钮'
|
||||
FROM (VALUES
|
||||
('联盟城市查询', 1, 'cms:crossCityFair:query'),
|
||||
('联盟城市新增', 2, 'cms:crossCityFair:add'),
|
||||
('联盟城市修改', 3, 'cms:crossCityFair:edit'),
|
||||
('联盟城市删除', 4, 'cms:crossCityFair:remove')
|
||||
) AS btn(menu_name, order_num, perms)
|
||||
WHERE NOT EXISTS (SELECT 1 FROM sys_menu WHERE perms = btn.perms);
|
||||
|
||||
-- 授权:把上述按钮权限授予拥有 menu_id=2114 的角色
|
||||
WITH cross_roles AS (
|
||||
SELECT DISTINCT role_id FROM sys_role_menu WHERE menu_id = 2114
|
||||
),
|
||||
new_menus AS (
|
||||
SELECT menu_id FROM sys_menu
|
||||
WHERE perms IN ('cms:crossCityFair:query','cms:crossCityFair:add','cms:crossCityFair:edit','cms:crossCityFair:remove')
|
||||
),
|
||||
rows AS (
|
||||
SELECT cross_roles.role_id, new_menus.menu_id FROM cross_roles CROSS JOIN new_menus
|
||||
)
|
||||
INSERT INTO sys_role_menu (role_id, menu_id)
|
||||
SELECT rows.role_id, rows.menu_id FROM rows
|
||||
WHERE NOT EXISTS (SELECT 1 FROM sys_role_menu ex WHERE ex.role_id = rows.role_id AND ex.menu_id = rows.menu_id);
|
||||
7
sql/public_job_fair_cross_domain_cities.sql
Normal file
7
sql/public_job_fair_cross_domain_cities.sql
Normal file
@@ -0,0 +1,7 @@
|
||||
-- 公共招聘会:新增「跨域联盟城市」列,存逗号分隔的城市名称。
|
||||
-- 依赖:shz.public_job_fair 已存在;shz.cms_cross_city_alliance 提供城市数据。
|
||||
|
||||
ALTER TABLE public_job_fair
|
||||
ADD COLUMN IF NOT EXISTS cross_domain_cities VARCHAR(500);
|
||||
|
||||
COMMENT ON COLUMN public_job_fair.cross_domain_cities IS '跨域联盟城市(逗号分隔的城市名称)';
|
||||
Reference in New Issue
Block a user