Compare commits
3 Commits
e2a37ac3d1
...
a7e9d911db
| Author | SHA1 | Date | |
|---|---|---|---|
| a7e9d911db | |||
| 1758dd9cfa | |||
| 6e3fb28d79 |
@@ -1,4 +1,4 @@
|
||||
# 户外招聘会企业 H5 公共接口
|
||||
# 户外招聘会公共接口
|
||||
|
||||
基础路径:`/app/outdoor-fair`
|
||||
|
||||
@@ -42,9 +42,25 @@
|
||||
|
||||
**请求路径:** `/{fairId}`
|
||||
|
||||
返回 `fair` 和 `fairH5Url`。
|
||||
返回 `fair`、参会企业 `companies` 和 `fairH5Url`。登录用户对应的 `fair.checkedIn` 表示当前用户是否已在本场招聘会签到。
|
||||
|
||||
## 4. 后台参会企业二维码入口
|
||||
## 4. 移动端用户一键签到
|
||||
|
||||
**请求方式:** `POST`
|
||||
|
||||
**请求路径:** `/check-in`
|
||||
|
||||
**请求头:** 需要移动端登录态 `Authorization`。
|
||||
|
||||
**请求参数:**
|
||||
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
| --- | --- | --- | --- |
|
||||
| fairId | Long | 是 | 招聘会 ID |
|
||||
|
||||
接口从当前登录用户读取姓名、手机号和地址,签到记录关联 `app_user.user_id`,不再接受前端传入的姓名和手机号。招聘会开始后,同一用户在同一场招聘会只能签到一次。
|
||||
|
||||
## 5. 后台参会企业二维码入口
|
||||
|
||||
后台接口 `GET /cms/outdoor-fair/{fairId}/companies` 的每行企业现在额外返回:
|
||||
|
||||
@@ -57,7 +73,7 @@
|
||||
|
||||
也可以调用 `GET /cms/outdoor-fair/{fairId}/companies/{companyId}/qrcode` 单独获取某个参会企业的二维码内容。
|
||||
|
||||
## 5. 已提供的静态 H5
|
||||
## 6. 已提供的静态 H5
|
||||
|
||||
| 页面 | 说明 |
|
||||
| --- | --- |
|
||||
|
||||
@@ -48,37 +48,8 @@
|
||||
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>
|
||||
@@ -169,7 +140,6 @@
|
||||
'<p>' + escapeHtml(fair.address) + '</p>' +
|
||||
'</section>' +
|
||||
'<div class="actions">' +
|
||||
'<button type="button" class="btn checkin" id="checkinBtn">签到</button>' +
|
||||
'<a class="btn" href="' + escapeHtml(publicBase() + '/h5/outdoor-fair/detail.html?fairId=' + pageFairId) + '">查看招聘会</a>' +
|
||||
'</div>' +
|
||||
'<section class="section">' +
|
||||
@@ -193,87 +163,7 @@
|
||||
(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(apiBase() + '/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);
|
||||
});
|
||||
'</section>';
|
||||
}
|
||||
|
||||
function renderJob(job, educationDict, experienceDict) {
|
||||
|
||||
@@ -31,41 +31,11 @@
|
||||
.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>
|
||||
@@ -76,7 +46,6 @@
|
||||
(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);
|
||||
@@ -103,7 +72,6 @@
|
||||
|
||||
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">' +
|
||||
@@ -111,9 +79,6 @@
|
||||
'<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">' +
|
||||
@@ -125,80 +90,7 @@
|
||||
'<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(apiBase() + '/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);
|
||||
});
|
||||
'</section>';
|
||||
}
|
||||
|
||||
if (!fairId) {
|
||||
|
||||
@@ -18,14 +18,18 @@ 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.IAppUserService;
|
||||
import com.ruoyi.cms.service.IOutdoorFairAttendeeService;
|
||||
import com.ruoyi.cms.service.IOutdoorFairService;
|
||||
import com.ruoyi.cms.util.StringUtil;
|
||||
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.AppUser;
|
||||
import com.ruoyi.common.core.domain.entity.Company;
|
||||
import com.ruoyi.common.exception.ServiceException;
|
||||
import com.ruoyi.common.utils.RequestUrlUtils;
|
||||
import com.ruoyi.common.utils.SiteSecurityUtils;
|
||||
import com.ruoyi.common.utils.StringUtils;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
@@ -33,7 +37,6 @@ 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;
|
||||
@@ -83,6 +86,9 @@ public class AppOutdoorFairController extends BaseController
|
||||
@Autowired
|
||||
private IOutdoorFairAttendeeService outdoorFairAttendeeService;
|
||||
|
||||
@Autowired
|
||||
private IAppUserService appUserService;
|
||||
|
||||
@BussinessLog(title = "户外招聘会列表")
|
||||
@ApiOperation("分页查询户外招聘会列表")
|
||||
@GetMapping("/page")
|
||||
@@ -193,30 +199,50 @@ public class AppOutdoorFairController extends BaseController
|
||||
return AjaxResult.error("招聘会不存在");
|
||||
}
|
||||
Map<String, Object> data = new HashMap<>();
|
||||
data.put("fair", buildPublicFairDetail(fair));
|
||||
Map<String, Object> publicFair = buildPublicFairDetail(fair);
|
||||
publicFair.put("checkedIn", hasCheckedIn(fairId));
|
||||
data.put("fair", publicFair);
|
||||
data.put("companies", buildPublicParticipatingCompanies(fairId));
|
||||
data.put("fairH5Url", buildFairH5Url(request, fairId));
|
||||
return success(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 匿名签到(姓名+手机号)。H5 企业页调用,无需登录。
|
||||
* 当前登录移动端用户一键签到。
|
||||
*/
|
||||
@BussinessLog(title = "户外招聘会企业H5签到")
|
||||
@ApiOperation("匿名签到(姓名+手机号)")
|
||||
@BussinessLog(title = "户外招聘会移动端签到")
|
||||
@ApiOperation("移动端用户一键签到")
|
||||
@PostMapping("/check-in")
|
||||
public AjaxResult checkIn(@RequestBody OutdoorFairAttendee attendee)
|
||||
public AjaxResult checkIn(@RequestParam Long fairId)
|
||||
{
|
||||
if (attendee.getFairId() == null) {
|
||||
if (!SiteSecurityUtils.isLogin()) {
|
||||
return AjaxResult.error("请先登录");
|
||||
}
|
||||
return checkIn(fairId, SiteSecurityUtils.getUserId());
|
||||
}
|
||||
|
||||
/**
|
||||
* 签到业务实现单独保留 userId 入参,便于无 Web 容器的单元测试覆盖业务规则。
|
||||
*/
|
||||
AjaxResult checkIn(Long fairId, Long userId)
|
||||
{
|
||||
if (fairId == null) {
|
||||
return AjaxResult.error("缺少招聘会参数");
|
||||
}
|
||||
if (StringUtils.isBlank(attendee.getName())) {
|
||||
return AjaxResult.error("姓名不能为空");
|
||||
if (userId == null) {
|
||||
return AjaxResult.error("用户信息不存在,请重新登录");
|
||||
}
|
||||
if (StringUtils.isBlank(attendee.getPhone())) {
|
||||
return AjaxResult.error("手机号不能为空");
|
||||
AppUser appUser = appUserService.selectAppUserByUserId(userId);
|
||||
if (appUser == null || !"0".equals(appUser.getDelFlag())) {
|
||||
return AjaxResult.error("用户信息不存在,请重新登录");
|
||||
}
|
||||
OutdoorFair fair = outdoorFairService.getById(attendee.getFairId());
|
||||
if (!StringUtil.IS_JOB_REQUEST_USER.equals(appUser.getIsCompanyUser())) {
|
||||
return AjaxResult.error("仅移动端求职者可以签到");
|
||||
}
|
||||
if (StringUtils.isBlank(appUser.getName())) {
|
||||
return AjaxResult.error("请先完善移动端用户姓名");
|
||||
}
|
||||
OutdoorFair fair = outdoorFairService.getById(fairId);
|
||||
if (fair == null) {
|
||||
return AjaxResult.error("招聘会不存在");
|
||||
}
|
||||
@@ -226,13 +252,20 @@ public class AppOutdoorFairController extends BaseController
|
||||
if (!hasFairStarted(fair, new Date())) {
|
||||
return AjaxResult.error("招聘会还没有开始,暂不能签到");
|
||||
}
|
||||
// 同一招聘会下同手机号已签到则不重复记录
|
||||
// 同一招聘会下同一移动端用户已签到则不重复记录
|
||||
Long exists = outdoorFairAttendeeService.count(new LambdaQueryWrapper<OutdoorFairAttendee>()
|
||||
.eq(OutdoorFairAttendee::getFairId, attendee.getFairId())
|
||||
.eq(OutdoorFairAttendee::getPhone, attendee.getPhone()));
|
||||
.eq(OutdoorFairAttendee::getFairId, fairId)
|
||||
.eq(OutdoorFairAttendee::getUserId, userId));
|
||||
if (exists != null && exists > 0) {
|
||||
return AjaxResult.error("该手机号已签到");
|
||||
return AjaxResult.error("您已签到,无需重复签到");
|
||||
}
|
||||
OutdoorFairAttendee attendee = new OutdoorFairAttendee();
|
||||
attendee.setFairId(fairId);
|
||||
attendee.setUserId(userId);
|
||||
attendee.setName(appUser.getName());
|
||||
attendee.setPhone(appUser.getPhone());
|
||||
attendee.setAddress(StringUtils.isNotBlank(appUser.getAddress())
|
||||
? appUser.getAddress() : appUser.getDomicileAddress());
|
||||
attendee.setCheckInTime(new Date());
|
||||
return toAjax(outdoorFairAttendeeService.save(attendee));
|
||||
}
|
||||
@@ -243,6 +276,23 @@ public class AppOutdoorFairController extends BaseController
|
||||
&& !currentTime.before(fair.getHoldTime());
|
||||
}
|
||||
|
||||
private boolean hasCheckedIn(Long fairId)
|
||||
{
|
||||
if (!SiteSecurityUtils.isLogin()) {
|
||||
return false;
|
||||
}
|
||||
Long userId;
|
||||
try {
|
||||
userId = SiteSecurityUtils.getUserId();
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
Long count = outdoorFairAttendeeService.count(new LambdaQueryWrapper<OutdoorFairAttendee>()
|
||||
.eq(OutdoorFairAttendee::getFairId, fairId)
|
||||
.eq(OutdoorFairAttendee::getUserId, userId));
|
||||
return count != null && count > 0;
|
||||
}
|
||||
|
||||
private Map<String, Object> buildCompanyPage(Long fairId, Long companyId, OutdoorFairDevice device,
|
||||
HttpServletRequest request)
|
||||
{
|
||||
|
||||
@@ -3,13 +3,21 @@ package com.ruoyi.cms.controller.cms;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.ruoyi.cms.domain.OutdoorFair;
|
||||
import com.ruoyi.cms.domain.OutdoorFairAttendee;
|
||||
import com.ruoyi.cms.domain.vo.OutdoorFairAttendeeUserOption;
|
||||
import com.ruoyi.cms.service.IAppUserService;
|
||||
import com.ruoyi.cms.service.IOutdoorFairAttendeeService;
|
||||
import com.ruoyi.cms.service.IOutdoorFairService;
|
||||
import com.ruoyi.cms.util.StringUtil;
|
||||
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.AppUser;
|
||||
import com.ruoyi.common.core.page.TableDataInfo;
|
||||
import com.ruoyi.common.enums.BusinessType;
|
||||
import com.ruoyi.common.utils.StringUtils;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
@@ -37,6 +45,12 @@ public class OutdoorFairAttendeeController extends BaseController
|
||||
@Autowired
|
||||
private IOutdoorFairAttendeeService outdoorFairAttendeeService;
|
||||
|
||||
@Autowired
|
||||
private IOutdoorFairService outdoorFairService;
|
||||
|
||||
@Autowired
|
||||
private IAppUserService appUserService;
|
||||
|
||||
/**
|
||||
* 查询签到人员列表(支持按人员姓名、手机号模糊查询)
|
||||
*/
|
||||
@@ -50,6 +64,22 @@ public class OutdoorFairAttendeeController extends BaseController
|
||||
return getDataTable(list);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询当前招聘会尚未签到的移动端求职者。
|
||||
*/
|
||||
@ApiOperation("查询可录入签到的移动端用户")
|
||||
@PreAuthorize("@ss.hasPermi('cms:outdoorFair:add')")
|
||||
@GetMapping("/available-users")
|
||||
public AjaxResult availableUsers(Long fairId, String keyword)
|
||||
{
|
||||
if (fairId == null) {
|
||||
return AjaxResult.error("缺少招聘会参数");
|
||||
}
|
||||
List<OutdoorFairAttendeeUserOption> list = outdoorFairAttendeeService
|
||||
.selectAvailableUserOptions(fairId, keyword);
|
||||
return success(list);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取签到人员详细信息
|
||||
*/
|
||||
@@ -70,7 +100,42 @@ public class OutdoorFairAttendeeController extends BaseController
|
||||
@PostMapping
|
||||
public AjaxResult add(@RequestBody OutdoorFairAttendee attendee)
|
||||
{
|
||||
return toAjax(outdoorFairAttendeeService.save(attendee));
|
||||
if (attendee == null || attendee.getFairId() == null) {
|
||||
return AjaxResult.error("缺少招聘会参数");
|
||||
}
|
||||
if (attendee.getUserId() == null) {
|
||||
return AjaxResult.error("请选择移动端用户");
|
||||
}
|
||||
OutdoorFair fair = outdoorFairService.getById(attendee.getFairId());
|
||||
if (fair == null) {
|
||||
return AjaxResult.error("招聘会不存在");
|
||||
}
|
||||
AppUser appUser = appUserService.selectAppUserByUserId(attendee.getUserId());
|
||||
if (appUser == null || !"0".equals(appUser.getDelFlag())) {
|
||||
return AjaxResult.error("移动端用户不存在或已删除");
|
||||
}
|
||||
if (!StringUtil.IS_JOB_REQUEST_USER.equals(appUser.getIsCompanyUser())) {
|
||||
return AjaxResult.error("只能录入移动端求职者");
|
||||
}
|
||||
if (StringUtils.isBlank(appUser.getName())) {
|
||||
return AjaxResult.error("移动端用户姓名为空,无法签到");
|
||||
}
|
||||
Long exists = outdoorFairAttendeeService.count(new LambdaQueryWrapper<OutdoorFairAttendee>()
|
||||
.eq(OutdoorFairAttendee::getFairId, attendee.getFairId())
|
||||
.eq(OutdoorFairAttendee::getUserId, attendee.getUserId()));
|
||||
if (exists != null && exists > 0) {
|
||||
return AjaxResult.error("该用户已签到");
|
||||
}
|
||||
|
||||
OutdoorFairAttendee record = new OutdoorFairAttendee();
|
||||
record.setFairId(attendee.getFairId());
|
||||
record.setUserId(appUser.getUserId());
|
||||
record.setName(appUser.getName());
|
||||
record.setPhone(appUser.getPhone());
|
||||
record.setAddress(StringUtils.isNotBlank(appUser.getAddress())
|
||||
? appUser.getAddress() : appUser.getDomicileAddress());
|
||||
record.setCheckInTime(new java.util.Date());
|
||||
return toAjax(outdoorFairAttendeeService.save(record));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -22,6 +22,9 @@ public class OutdoorFairAttendee extends BaseEntity
|
||||
/** 招聘会ID */
|
||||
private Long fairId;
|
||||
|
||||
/** 移动端用户ID */
|
||||
private Long userId;
|
||||
|
||||
/** 人员姓名 */
|
||||
private String name;
|
||||
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.ruoyi.cms.domain.vo;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 户外招聘会后台录入签到人员时使用的移动端用户选项。
|
||||
*/
|
||||
@Data
|
||||
public class OutdoorFairAttendeeUserOption
|
||||
{
|
||||
/** 移动端用户ID */
|
||||
private Long userId;
|
||||
|
||||
/** 用户名称 */
|
||||
private String name;
|
||||
|
||||
/** 手机号 */
|
||||
private String phone;
|
||||
|
||||
/** 用户地址 */
|
||||
private String address;
|
||||
}
|
||||
@@ -2,10 +2,24 @@ package com.ruoyi.cms.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.ruoyi.cms.domain.OutdoorFairAttendee;
|
||||
import com.ruoyi.cms.domain.vo.OutdoorFairAttendeeUserOption;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 户外招聘会签到人员 Mapper。
|
||||
*/
|
||||
public interface OutdoorFairAttendeeMapper extends BaseMapper<OutdoorFairAttendee>
|
||||
{
|
||||
/**
|
||||
* 查询签到人员列表。关联移动端用户后,优先返回用户当前信息。
|
||||
*/
|
||||
List<OutdoorFairAttendee> selectAttendeeList(@Param("query") OutdoorFairAttendee query);
|
||||
|
||||
/**
|
||||
* 查询当前招聘会尚未签到的移动端求职者。
|
||||
*/
|
||||
List<OutdoorFairAttendeeUserOption> selectAvailableUserOptions(@Param("fairId") Long fairId,
|
||||
@Param("keyword") String keyword);
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.ruoyi.cms.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.ruoyi.cms.domain.OutdoorFairAttendee;
|
||||
import com.ruoyi.cms.domain.vo.OutdoorFairAttendeeUserOption;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@@ -14,4 +15,9 @@ public interface IOutdoorFairAttendeeService extends IService<OutdoorFairAttende
|
||||
* 查询签到人员列表(支持按人员姓名、手机号模糊查询)。
|
||||
*/
|
||||
List<OutdoorFairAttendee> selectAttendeeList(OutdoorFairAttendee query);
|
||||
|
||||
/**
|
||||
* 查询当前招聘会尚未签到的移动端求职者。
|
||||
*/
|
||||
List<OutdoorFairAttendeeUserOption> selectAvailableUserOptions(Long fairId, String keyword);
|
||||
}
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
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.OutdoorFairAttendee;
|
||||
import com.ruoyi.cms.domain.vo.OutdoorFairAttendeeUserOption;
|
||||
import com.ruoyi.cms.mapper.OutdoorFairAttendeeMapper;
|
||||
import com.ruoyi.cms.service.IOutdoorFairAttendeeService;
|
||||
import com.ruoyi.common.utils.StringUtils;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.List;
|
||||
@@ -21,12 +20,12 @@ public class OutdoorFairAttendeeServiceImpl
|
||||
@Override
|
||||
public List<OutdoorFairAttendee> selectAttendeeList(OutdoorFairAttendee query)
|
||||
{
|
||||
LambdaQueryWrapper<OutdoorFairAttendee> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.eq(query.getFairId() != null, OutdoorFairAttendee::getFairId, query.getFairId());
|
||||
wrapper.like(StringUtils.isNotBlank(query.getName()), OutdoorFairAttendee::getName, query.getName());
|
||||
wrapper.like(StringUtils.isNotBlank(query.getPhone()), OutdoorFairAttendee::getPhone, query.getPhone());
|
||||
wrapper.orderByDesc(OutdoorFairAttendee::getCheckInTime);
|
||||
wrapper.orderByDesc(OutdoorFairAttendee::getId);
|
||||
return list(wrapper);
|
||||
return baseMapper.selectAttendeeList(query);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<OutdoorFairAttendeeUserOption> selectAvailableUserOptions(Long fairId, String keyword)
|
||||
{
|
||||
return baseMapper.selectAvailableUserOptions(fairId, keyword);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,6 +59,7 @@ public class PublicJobFairServiceImpl implements IPublicJobFairService {
|
||||
|
||||
@Override
|
||||
public PublicJobFairResponse getJobFairPage(PublicJobFairQuery query) {
|
||||
query.setJobFairTitle(normalizeJobFairTitle(query.getJobFairTitle()));
|
||||
PageHelper.startPage(query.getPageNum(), query.getPageSize());
|
||||
List<PublicJobFair> list = publicJobFairMapper.selectPublicJobFairList(query);
|
||||
fillJobFairStatus(list);
|
||||
@@ -180,12 +181,28 @@ public class PublicJobFairServiceImpl implements IPublicJobFairService {
|
||||
|
||||
@Override
|
||||
public List<PublicJobFair> selectList(PublicJobFairQuery query) {
|
||||
query.setJobFairTitle(normalizeJobFairTitle(query.getJobFairTitle()));
|
||||
List<PublicJobFair> list = publicJobFairMapper.selectPublicJobFairList(query);
|
||||
fillJobFairStatus(list);
|
||||
fillCompanyApplyState(list);
|
||||
return list;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将标题查询条件转换为 LIKE 的字面量片段。
|
||||
*
|
||||
* <p>标题搜索是普通的包含匹配,用户输入的通配符不应改变 SQL 的匹配范围。</p>
|
||||
*/
|
||||
static String normalizeJobFairTitle(String title) {
|
||||
if (StringUtils.isBlank(title)) {
|
||||
return null;
|
||||
}
|
||||
return title.trim()
|
||||
.replace("\\", "\\\\")
|
||||
.replace("%", "\\%")
|
||||
.replace("_", "\\_");
|
||||
}
|
||||
|
||||
@Override
|
||||
public PublicJobFair selectById(String jobFairId) {
|
||||
PublicJobFair jobFair = publicJobFairMapper.selectPublicJobFairById(jobFairId);
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<!DOCTYPE mapper
|
||||
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="com.ruoyi.cms.mapper.OutdoorFairAttendeeMapper">
|
||||
|
||||
<resultMap id="OutdoorFairAttendeeResult"
|
||||
type="com.ruoyi.cms.domain.OutdoorFairAttendee">
|
||||
<id property="id" column="id"/>
|
||||
<result property="fairId" column="fair_id"/>
|
||||
<result property="userId" column="user_id"/>
|
||||
<result property="name" column="name"/>
|
||||
<result property="phone" column="phone"/>
|
||||
<result property="address" column="address"/>
|
||||
<result property="checkInTime" column="check_in_time"/>
|
||||
<result property="createBy" column="create_by"/>
|
||||
<result property="createTime" column="create_time"/>
|
||||
<result property="updateBy" column="update_by"/>
|
||||
<result property="updateTime" column="update_time"/>
|
||||
<result property="remark" column="remark"/>
|
||||
<result property="delFlag" column="del_flag"/>
|
||||
</resultMap>
|
||||
|
||||
<resultMap id="OutdoorFairAttendeeUserOptionResult"
|
||||
type="com.ruoyi.cms.domain.vo.OutdoorFairAttendeeUserOption">
|
||||
<result property="userId" column="user_id"/>
|
||||
<result property="name" column="name"/>
|
||||
<result property="phone" column="phone"/>
|
||||
<result property="address" column="address"/>
|
||||
</resultMap>
|
||||
|
||||
<select id="selectAttendeeList"
|
||||
parameterType="com.ruoyi.cms.domain.OutdoorFairAttendee"
|
||||
resultMap="OutdoorFairAttendeeResult">
|
||||
SELECT a.id,
|
||||
a.fair_id,
|
||||
a.user_id,
|
||||
COALESCE(NULLIF(u.name, ''), a.name) AS name,
|
||||
COALESCE(NULLIF(u.phone, ''), a.phone) AS phone,
|
||||
COALESCE(NULLIF(u.address, ''), NULLIF(u.domicile_address, ''), a.address) AS address,
|
||||
a.check_in_time,
|
||||
a.create_by,
|
||||
a.create_time,
|
||||
a.update_by,
|
||||
a.update_time,
|
||||
a.remark,
|
||||
a.del_flag
|
||||
FROM shz.cms_outdoor_fair_attendee a
|
||||
LEFT JOIN app_user u ON u.user_id = a.user_id AND u.del_flag = '0'
|
||||
<where>
|
||||
a.del_flag = '0'
|
||||
<if test="query != null and query.fairId != null">
|
||||
AND a.fair_id = #{query.fairId}
|
||||
</if>
|
||||
<if test="query != null and query.name != null and query.name != ''">
|
||||
AND COALESCE(NULLIF(u.name, ''), a.name) LIKE CONCAT('%', CAST(#{query.name} AS VARCHAR), '%')
|
||||
</if>
|
||||
<if test="query != null and query.phone != null and query.phone != ''">
|
||||
AND COALESCE(NULLIF(u.phone, ''), a.phone) LIKE CONCAT('%', CAST(#{query.phone} AS VARCHAR), '%')
|
||||
</if>
|
||||
</where>
|
||||
ORDER BY a.check_in_time DESC, a.id DESC
|
||||
</select>
|
||||
|
||||
<select id="selectAvailableUserOptions"
|
||||
resultMap="OutdoorFairAttendeeUserOptionResult">
|
||||
SELECT u.user_id,
|
||||
u.name,
|
||||
u.phone,
|
||||
COALESCE(u.address, u.domicile_address) AS address
|
||||
FROM app_user u
|
||||
WHERE u.del_flag = '0'
|
||||
AND u.is_company_user = '1'
|
||||
AND NULLIF(u.name, '') IS NOT NULL
|
||||
AND NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM shz.cms_outdoor_fair_attendee a
|
||||
WHERE a.fair_id = #{fairId}
|
||||
AND a.user_id = u.user_id
|
||||
AND a.del_flag = '0'
|
||||
)
|
||||
<if test="keyword != null and keyword != ''">
|
||||
AND (
|
||||
u.name LIKE CONCAT('%', CAST(#{keyword} AS VARCHAR), '%')
|
||||
OR u.phone LIKE CONCAT('%', CAST(#{keyword} AS VARCHAR), '%')
|
||||
)
|
||||
</if>
|
||||
ORDER BY u.name ASC NULLS LAST, u.user_id ASC
|
||||
</select>
|
||||
</mapper>
|
||||
@@ -9,10 +9,13 @@ import com.ruoyi.cms.mapper.OutdoorFairBoothBookingMapper;
|
||||
import com.ruoyi.cms.mapper.OutdoorFairBoothMapper;
|
||||
import com.ruoyi.cms.mapper.OutdoorFairJobMapper;
|
||||
import com.ruoyi.cms.service.IOutdoorFairAttendeeService;
|
||||
import com.ruoyi.cms.service.IAppUserService;
|
||||
import com.ruoyi.cms.service.IOutdoorFairService;
|
||||
import com.ruoyi.common.core.domain.AjaxResult;
|
||||
import com.ruoyi.common.core.domain.entity.AppUser;
|
||||
import com.ruoyi.common.core.domain.entity.Company;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.springframework.mock.web.MockHttpServletRequest;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
@@ -37,11 +40,13 @@ class AppOutdoorFairControllerTest
|
||||
{
|
||||
IOutdoorFairService fairService = mock(IOutdoorFairService.class);
|
||||
IOutdoorFairAttendeeService attendeeService = mock(IOutdoorFairAttendeeService.class);
|
||||
IAppUserService appUserService = mock(IAppUserService.class);
|
||||
OutdoorFair fair = fairAt(new Date(System.currentTimeMillis() + 60_000L));
|
||||
when(fairService.getById(12L)).thenReturn(fair);
|
||||
AppOutdoorFairController controller = controllerWith(fairService, attendeeService);
|
||||
when(appUserService.selectAppUserByUserId(99L)).thenReturn(appUser());
|
||||
AppOutdoorFairController controller = controllerWith(fairService, attendeeService, appUserService);
|
||||
|
||||
AjaxResult result = controller.checkIn(attendee(12L));
|
||||
AjaxResult result = controller.checkIn(12L, 99L);
|
||||
|
||||
assertEquals(500, result.get(AjaxResult.CODE_TAG));
|
||||
assertEquals("招聘会还没有开始,暂不能签到", result.get(AjaxResult.MSG_TAG));
|
||||
@@ -53,10 +58,12 @@ class AppOutdoorFairControllerTest
|
||||
{
|
||||
IOutdoorFairService fairService = mock(IOutdoorFairService.class);
|
||||
IOutdoorFairAttendeeService attendeeService = mock(IOutdoorFairAttendeeService.class);
|
||||
IAppUserService appUserService = mock(IAppUserService.class);
|
||||
when(fairService.getById(12L)).thenReturn(fairAt(null));
|
||||
AppOutdoorFairController controller = controllerWith(fairService, attendeeService);
|
||||
when(appUserService.selectAppUserByUserId(99L)).thenReturn(appUser());
|
||||
AppOutdoorFairController controller = controllerWith(fairService, attendeeService, appUserService);
|
||||
|
||||
AjaxResult result = controller.checkIn(attendee(12L));
|
||||
AjaxResult result = controller.checkIn(12L, 99L);
|
||||
|
||||
assertEquals(500, result.get(AjaxResult.CODE_TAG));
|
||||
assertEquals("招聘会还没有开始,暂不能签到", result.get(AjaxResult.MSG_TAG));
|
||||
@@ -68,13 +75,15 @@ class AppOutdoorFairControllerTest
|
||||
{
|
||||
IOutdoorFairService fairService = mock(IOutdoorFairService.class);
|
||||
IOutdoorFairAttendeeService attendeeService = mock(IOutdoorFairAttendeeService.class);
|
||||
IAppUserService appUserService = mock(IAppUserService.class);
|
||||
OutdoorFair fair = fairAt(new Date(System.currentTimeMillis() - 1_000L));
|
||||
when(fairService.getById(12L)).thenReturn(fair);
|
||||
when(appUserService.selectAppUserByUserId(99L)).thenReturn(appUser());
|
||||
when(attendeeService.count(any())).thenReturn(0L);
|
||||
when(attendeeService.save(any(OutdoorFairAttendee.class))).thenReturn(true);
|
||||
AppOutdoorFairController controller = controllerWith(fairService, attendeeService);
|
||||
AppOutdoorFairController controller = controllerWith(fairService, attendeeService, appUserService);
|
||||
|
||||
AjaxResult result = controller.checkIn(attendee(12L));
|
||||
AjaxResult result = controller.checkIn(12L, 99L);
|
||||
|
||||
assertEquals(200, result.get(AjaxResult.CODE_TAG));
|
||||
verify(attendeeService).save(any(OutdoorFairAttendee.class));
|
||||
@@ -83,6 +92,72 @@ class AppOutdoorFairControllerTest
|
||||
fair, new Date(fair.getHoldTime().getTime() - 1L)));
|
||||
}
|
||||
|
||||
@Test
|
||||
void checkInUsesCurrentMobileUserProfile() throws Exception
|
||||
{
|
||||
IOutdoorFairService fairService = mock(IOutdoorFairService.class);
|
||||
IOutdoorFairAttendeeService attendeeService = mock(IOutdoorFairAttendeeService.class);
|
||||
IAppUserService appUserService = mock(IAppUserService.class);
|
||||
OutdoorFair fair = fairAt(new Date(System.currentTimeMillis() - 1_000L));
|
||||
AppUser appUser = appUser();
|
||||
appUser.setAddress("石河子市测试地址");
|
||||
when(fairService.getById(12L)).thenReturn(fair);
|
||||
when(appUserService.selectAppUserByUserId(99L)).thenReturn(appUser);
|
||||
when(attendeeService.count(any())).thenReturn(0L);
|
||||
when(attendeeService.save(any(OutdoorFairAttendee.class))).thenReturn(true);
|
||||
AppOutdoorFairController controller = controllerWith(fairService, attendeeService, appUserService);
|
||||
|
||||
AjaxResult result = controller.checkIn(12L, 99L);
|
||||
|
||||
assertEquals(200, result.get(AjaxResult.CODE_TAG));
|
||||
ArgumentCaptor<OutdoorFairAttendee> captor = ArgumentCaptor.forClass(OutdoorFairAttendee.class);
|
||||
verify(attendeeService).save(captor.capture());
|
||||
OutdoorFairAttendee record = captor.getValue();
|
||||
assertEquals(12L, record.getFairId());
|
||||
assertEquals(99L, record.getUserId());
|
||||
assertEquals("测试人员", record.getName());
|
||||
assertEquals("13800000000", record.getPhone());
|
||||
assertEquals("石河子市测试地址", record.getAddress());
|
||||
assertTrue(record.getCheckInTime() != null);
|
||||
}
|
||||
|
||||
@Test
|
||||
void duplicateUserCannotCheckInAgain() throws Exception
|
||||
{
|
||||
IOutdoorFairService fairService = mock(IOutdoorFairService.class);
|
||||
IOutdoorFairAttendeeService attendeeService = mock(IOutdoorFairAttendeeService.class);
|
||||
IAppUserService appUserService = mock(IAppUserService.class);
|
||||
when(fairService.getById(12L)).thenReturn(fairAt(new Date(System.currentTimeMillis() - 1_000L)));
|
||||
when(appUserService.selectAppUserByUserId(99L)).thenReturn(appUser());
|
||||
when(attendeeService.count(any())).thenReturn(1L);
|
||||
AppOutdoorFairController controller = controllerWith(fairService, attendeeService, appUserService);
|
||||
|
||||
AjaxResult result = controller.checkIn(12L, 99L);
|
||||
|
||||
assertEquals(500, result.get(AjaxResult.CODE_TAG));
|
||||
assertEquals("您已签到,无需重复签到", result.get(AjaxResult.MSG_TAG));
|
||||
verify(attendeeService, never()).save(any(OutdoorFairAttendee.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void nonJobSeekerCannotCheckIn() throws Exception
|
||||
{
|
||||
IOutdoorFairService fairService = mock(IOutdoorFairService.class);
|
||||
IOutdoorFairAttendeeService attendeeService = mock(IOutdoorFairAttendeeService.class);
|
||||
IAppUserService appUserService = mock(IAppUserService.class);
|
||||
AppUser appUser = appUser();
|
||||
appUser.setIsCompanyUser("0");
|
||||
when(appUserService.selectAppUserByUserId(99L)).thenReturn(appUser);
|
||||
AppOutdoorFairController controller = controllerWith(fairService, attendeeService, appUserService);
|
||||
|
||||
AjaxResult result = controller.checkIn(12L, 99L);
|
||||
|
||||
assertEquals(500, result.get(AjaxResult.CODE_TAG));
|
||||
assertEquals("仅移动端求职者可以签到", result.get(AjaxResult.MSG_TAG));
|
||||
verify(fairService, never()).getById(12L);
|
||||
verify(attendeeService, never()).save(any(OutdoorFairAttendee.class));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Test
|
||||
void publicDetailIncludesOnlySafeCompanyCardFields() throws Exception
|
||||
@@ -144,6 +219,17 @@ class AppOutdoorFairControllerTest
|
||||
AppOutdoorFairController controller = new AppOutdoorFairController();
|
||||
setField(controller, "outdoorFairService", fairService);
|
||||
setField(controller, "outdoorFairAttendeeService", attendeeService);
|
||||
setField(controller, "appUserService", mock(IAppUserService.class));
|
||||
return controller;
|
||||
}
|
||||
|
||||
private AppOutdoorFairController controllerWith(IOutdoorFairService fairService,
|
||||
IOutdoorFairAttendeeService attendeeService,
|
||||
IAppUserService appUserService)
|
||||
throws Exception
|
||||
{
|
||||
AppOutdoorFairController controller = controllerWith(fairService, attendeeService);
|
||||
setField(controller, "appUserService", appUserService);
|
||||
return controller;
|
||||
}
|
||||
|
||||
@@ -154,13 +240,15 @@ class AppOutdoorFairControllerTest
|
||||
field.set(target, value);
|
||||
}
|
||||
|
||||
private OutdoorFairAttendee attendee(Long fairId)
|
||||
private AppUser appUser()
|
||||
{
|
||||
OutdoorFairAttendee attendee = new OutdoorFairAttendee();
|
||||
attendee.setFairId(fairId);
|
||||
attendee.setName("测试人员");
|
||||
attendee.setPhone("13800000000");
|
||||
return attendee;
|
||||
AppUser appUser = new AppUser();
|
||||
appUser.setUserId(99L);
|
||||
appUser.setName("测试人员");
|
||||
appUser.setPhone("13800000000");
|
||||
appUser.setIsCompanyUser("1");
|
||||
appUser.setDelFlag("0");
|
||||
return appUser;
|
||||
}
|
||||
|
||||
private OutdoorFair fairAt(Date holdTime)
|
||||
|
||||
@@ -39,6 +39,18 @@ class PublicJobFairServiceImplTest
|
||||
assertNull(PublicJobFairServiceImpl.resolveJobFairStatus(START_TIME, null, END_TIME));
|
||||
}
|
||||
|
||||
@Test
|
||||
void titleSearchEscapesLikeWildcardsAndTrimsWhitespace()
|
||||
{
|
||||
assertEquals("100\\%\\_\\\\", PublicJobFairServiceImpl.normalizeJobFairTitle(" 100%_\\ "));
|
||||
}
|
||||
|
||||
@Test
|
||||
void blankTitleSearchIsIgnored()
|
||||
{
|
||||
assertNull(PublicJobFairServiceImpl.normalizeJobFairTitle(" \t"));
|
||||
}
|
||||
|
||||
private String resolveStatus(Date now)
|
||||
{
|
||||
return PublicJobFairServiceImpl.resolveJobFairStatus(START_TIME, END_TIME, now);
|
||||
|
||||
@@ -7,6 +7,7 @@ CREATE SEQUENCE IF NOT EXISTS shz.cms_outdoor_fair_attendee_id_seq
|
||||
CREATE TABLE IF NOT EXISTS shz.cms_outdoor_fair_attendee (
|
||||
id BIGINT NOT NULL DEFAULT nextval('shz.cms_outdoor_fair_attendee_id_seq'::regclass) PRIMARY KEY,
|
||||
fair_id BIGINT NOT NULL,
|
||||
user_id BIGINT,
|
||||
name VARCHAR(64) NOT NULL,
|
||||
phone VARCHAR(20),
|
||||
address VARCHAR(255),
|
||||
@@ -22,9 +23,14 @@ CREATE TABLE IF NOT EXISTS shz.cms_outdoor_fair_attendee (
|
||||
CREATE INDEX IF NOT EXISTS idx_cms_outdoor_fair_attendee_fair
|
||||
ON shz.cms_outdoor_fair_attendee (fair_id);
|
||||
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS uk_cms_outdoor_fair_attendee_user
|
||||
ON shz.cms_outdoor_fair_attendee (fair_id, user_id)
|
||||
WHERE user_id IS NOT NULL AND del_flag = '0';
|
||||
|
||||
COMMENT ON TABLE shz.cms_outdoor_fair_attendee IS '户外招聘会签到人员表';
|
||||
COMMENT ON COLUMN shz.cms_outdoor_fair_attendee.id IS '主键';
|
||||
COMMENT ON COLUMN shz.cms_outdoor_fair_attendee.fair_id IS '招聘会ID';
|
||||
COMMENT ON COLUMN shz.cms_outdoor_fair_attendee.user_id IS '移动端用户ID';
|
||||
COMMENT ON COLUMN shz.cms_outdoor_fair_attendee.name IS '人员姓名';
|
||||
COMMENT ON COLUMN shz.cms_outdoor_fair_attendee.phone IS '手机号';
|
||||
COMMENT ON COLUMN shz.cms_outdoor_fair_attendee.address IS '住址';
|
||||
|
||||
@@ -0,0 +1,149 @@
|
||||
-- 执行状态:测试环境已执行,功能验证已通过,正式环境待执行
|
||||
-- 测试环境:test
|
||||
-- 测试执行时间:2026-07-24 09:39:31 +0800
|
||||
-- 功能验证时间:2026-07-24 09:56:15 +0800
|
||||
-- 正式环境:待明确授权后执行
|
||||
-- 招聘会机构管理及场地所属机构多选关联。
|
||||
-- 依赖:招聘会管理目录(menu_name='招聘会管理')和场地信息维护权限已存在。
|
||||
-- 可重复执行;正式环境仅在功能验收通过并获单独授权后执行。
|
||||
|
||||
BEGIN;
|
||||
|
||||
SET LOCAL search_path = shz, public;
|
||||
|
||||
CREATE SEQUENCE IF NOT EXISTS cms_job_fair_institution_id_seq
|
||||
START WITH 1 INCREMENT BY 1 NO MINVALUE NO MAXVALUE CACHE 1;
|
||||
|
||||
CREATE SEQUENCE IF NOT EXISTS cms_venue_institution_id_seq
|
||||
START WITH 1 INCREMENT BY 1 NO MINVALUE NO MAXVALUE CACHE 1;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS cms_job_fair_institution (
|
||||
id BIGINT NOT NULL DEFAULT nextval('cms_job_fair_institution_id_seq') PRIMARY KEY,
|
||||
institution_name VARCHAR(200) NOT NULL,
|
||||
institution_intro TEXT,
|
||||
create_by VARCHAR(64) DEFAULT '',
|
||||
create_time TIMESTAMP(0),
|
||||
update_by VARCHAR(64) DEFAULT '',
|
||||
update_time TIMESTAMP(0),
|
||||
remark VARCHAR(500),
|
||||
del_flag CHAR(1) DEFAULT '0'
|
||||
);
|
||||
|
||||
COMMENT ON TABLE cms_job_fair_institution IS '招聘会机构管理表';
|
||||
COMMENT ON COLUMN cms_job_fair_institution.institution_name IS '机构名称';
|
||||
COMMENT ON COLUMN cms_job_fair_institution.institution_intro IS '机构简介';
|
||||
COMMENT ON COLUMN cms_job_fair_institution.del_flag IS '删除标志(0代表存在 2代表删除)';
|
||||
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS uk_cms_job_fair_institution_name
|
||||
ON cms_job_fair_institution (institution_name) WHERE del_flag = '0';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS cms_venue_institution (
|
||||
id BIGINT NOT NULL DEFAULT nextval('cms_venue_institution_id_seq') PRIMARY KEY,
|
||||
venue_id BIGINT NOT NULL,
|
||||
institution_id BIGINT NOT NULL
|
||||
);
|
||||
|
||||
COMMENT ON TABLE cms_venue_institution IS '场地与招聘会机构关联表';
|
||||
COMMENT ON COLUMN cms_venue_institution.venue_id IS '场地ID';
|
||||
COMMENT ON COLUMN cms_venue_institution.institution_id IS '机构ID';
|
||||
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS uk_cms_venue_institution
|
||||
ON cms_venue_institution (venue_id, institution_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_cms_venue_institution_institution
|
||||
ON cms_venue_institution (institution_id);
|
||||
|
||||
WITH parent_menu AS (
|
||||
SELECT menu_id
|
||||
FROM sys_menu
|
||||
WHERE menu_name = '招聘会管理' AND parent_id = 0
|
||||
ORDER BY menu_id
|
||||
LIMIT 1
|
||||
), inserted_page AS (
|
||||
INSERT INTO sys_menu (
|
||||
menu_id, menu_name, parent_id, order_num, path, component, query, route_name,
|
||||
is_frame, is_cache, menu_type, visible, status, perms, icon,
|
||||
create_by, create_time, remark
|
||||
)
|
||||
SELECT
|
||||
(SELECT COALESCE(MAX(menu_id), 0) + 1 FROM sys_menu),
|
||||
'机构管理', parent_menu.menu_id, 3, 'institution', 'jobfair/institution/index', NULL, '',
|
||||
1, 0, 'C', '0', '0', 'cms:jobFairInstitution:list', '#',
|
||||
'system', NOW(), '招聘会机构管理菜单'
|
||||
FROM parent_menu
|
||||
WHERE NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM sys_menu existing
|
||||
WHERE existing.parent_id = parent_menu.menu_id
|
||||
AND existing.menu_name = '机构管理'
|
||||
)
|
||||
RETURNING menu_id
|
||||
), institution_page AS (
|
||||
SELECT menu_id FROM inserted_page
|
||||
UNION
|
||||
SELECT menu_id
|
||||
FROM sys_menu
|
||||
WHERE menu_name = '机构管理'
|
||||
AND parent_id = (SELECT menu_id FROM parent_menu)
|
||||
), button_seed(menu_name, order_num, perms) AS (
|
||||
VALUES
|
||||
('机构查询', 1, 'cms:jobFairInstitution:query'),
|
||||
('机构新增', 2, 'cms:jobFairInstitution:add'),
|
||||
('机构修改', 3, 'cms:jobFairInstitution:edit'),
|
||||
('机构删除', 4, 'cms:jobFairInstitution:remove')
|
||||
), max_menu AS (
|
||||
SELECT COALESCE(MAX(menu_id), 0) AS menu_id FROM sys_menu
|
||||
)
|
||||
INSERT INTO sys_menu (
|
||||
menu_id, menu_name, parent_id, order_num, path, component, query, route_name,
|
||||
is_frame, is_cache, menu_type, visible, status, perms, icon,
|
||||
create_by, create_time, remark
|
||||
)
|
||||
SELECT
|
||||
max_menu.menu_id + 1 + ROW_NUMBER() OVER (ORDER BY button_seed.order_num),
|
||||
button_seed.menu_name, institution_page.menu_id, button_seed.order_num, '', NULL, NULL, '',
|
||||
1, 0, 'F', '0', '0', button_seed.perms, '#',
|
||||
'system', NOW(), '招聘会机构管理按钮权限'
|
||||
FROM institution_page
|
||||
CROSS JOIN button_seed
|
||||
CROSS JOIN max_menu
|
||||
WHERE NOT EXISTS (
|
||||
SELECT 1 FROM sys_menu existing WHERE existing.perms = button_seed.perms
|
||||
);
|
||||
|
||||
WITH institution_menus AS (
|
||||
SELECT menu_id
|
||||
FROM sys_menu
|
||||
WHERE perms IN (
|
||||
'cms:jobFairInstitution:list',
|
||||
'cms:jobFairInstitution:query',
|
||||
'cms:jobFairInstitution:add',
|
||||
'cms:jobFairInstitution:edit',
|
||||
'cms:jobFairInstitution:remove'
|
||||
)
|
||||
UNION
|
||||
SELECT menu_id
|
||||
FROM sys_menu
|
||||
WHERE menu_name = '机构管理'
|
||||
AND parent_id = (SELECT menu_id FROM sys_menu WHERE menu_name = '招聘会管理' AND parent_id = 0 ORDER BY menu_id LIMIT 1)
|
||||
), venue_roles AS (
|
||||
SELECT DISTINCT role_id
|
||||
FROM sys_role_menu
|
||||
WHERE menu_id IN (
|
||||
SELECT menu_id FROM sys_menu WHERE perms = 'cms:venueInfo:list'
|
||||
)
|
||||
), role_menu_rows AS (
|
||||
SELECT venue_roles.role_id, institution_menus.menu_id
|
||||
FROM venue_roles
|
||||
CROSS JOIN institution_menus
|
||||
)
|
||||
INSERT INTO sys_role_menu (role_id, menu_id)
|
||||
SELECT role_menu_rows.role_id, role_menu_rows.menu_id
|
||||
FROM role_menu_rows
|
||||
WHERE NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM sys_role_menu existing
|
||||
WHERE existing.role_id = role_menu_rows.role_id
|
||||
AND existing.menu_id = role_menu_rows.menu_id
|
||||
);
|
||||
|
||||
COMMIT;
|
||||
18
sql/migration_outdoor_fair_attendee_user.test-executed.sql
Normal file
18
sql/migration_outdoor_fair_attendee_user.test-executed.sql
Normal file
@@ -0,0 +1,18 @@
|
||||
-- 执行状态:测试环境已执行
|
||||
-- 测试环境:test
|
||||
-- 测试执行时间:2026-07-24 10:16:38 +0800
|
||||
-- 正式环境:未执行
|
||||
|
||||
-- 户外招聘会签到记录关联移动端用户
|
||||
-- 历史签到记录 user_id 保持为空,仍使用原有快照字段展示。
|
||||
|
||||
ALTER TABLE shz.cms_outdoor_fair_attendee
|
||||
ADD COLUMN IF NOT EXISTS user_id BIGINT;
|
||||
|
||||
COMMENT ON COLUMN shz.cms_outdoor_fair_attendee.user_id IS '移动端用户ID';
|
||||
|
||||
DROP INDEX IF EXISTS shz.uk_cms_outdoor_fair_attendee_user;
|
||||
|
||||
CREATE UNIQUE INDEX uk_cms_outdoor_fair_attendee_user
|
||||
ON shz.cms_outdoor_fair_attendee (fair_id, user_id)
|
||||
WHERE user_id IS NOT NULL AND del_flag = '0';
|
||||
Reference in New Issue
Block a user