From b35ed5989e1b20571ffcf9ccb8f0b7f8167e625f Mon Sep 17 00:00:00 2001
From: lapuda <577732344@qq.com>
Date: Sat, 27 Jun 2026 22:44:56 +0800
Subject: [PATCH] feat: Add Cross-Domain Job Fair and Alliance City Management
- Implemented AppOutdoorFairController for outdoor job fair public H5 interfaces.
- Created CrossCityAllianceController for managing cross-domain alliance cities.
- Added CrossCityAlliance domain model and corresponding service and mapper.
- Developed CrossDomainJobVO for aggregating job data across job fairs.
- Created SQL scripts for initializing the cross-city alliance table and modifying the public job fair schema.
- Added migration script for backing up and restoring the HighGo database schema.
---
docs/app-outdoor-fair-h5-api.md | 65 +++
.../src/main/resources/application-local.yml | 8 +-
.../static/h5/outdoor-fair/company.html | 299 +++++++++++++
.../static/h5/outdoor-fair/detail.html | 208 +++++++++
.../app/AppOutdoorFairController.java | 398 +++++++++++++++++
.../cms/CmsPublicJobFairController.java | 12 +
.../cms/CrossCityAllianceController.java | 99 +++++
.../controller/cms/OutdoorFairController.java | 405 +++++++++++++++++-
.../cms/OutdoorFairDeviceController.java | 19 +-
.../ruoyi/cms/domain/CrossCityAlliance.java | 21 +
.../com/ruoyi/cms/domain/FairCompany.java | 14 +
.../main/java/com/ruoyi/cms/domain/Job.java | 13 +
.../com/ruoyi/cms/domain/OutdoorFair.java | 29 ++
.../com/ruoyi/cms/domain/OutdoorFairJob.java | 20 +
.../ruoyi/cms/domain/rc/CrossDomainJobVO.java | 65 +++
.../ruoyi/cms/domain/rc/PublicJobFair.java | 6 +
.../cms/mapper/CrossCityAllianceMapper.java | 11 +
.../cms/mapper/rc/PublicJobFairMapper.java | 5 +
.../service/ICrossCityAllianceService.java | 17 +
.../service/IOutdoorFairDeviceService.java | 11 +-
.../impl/CrossCityAllianceServiceImpl.java | 29 ++
.../impl/OutdoorFairDeviceServiceImpl.java | 49 ++-
.../cms/service/rc/IPublicJobFairService.java | 5 +
.../rc/impl/PublicJobFairServiceImpl.java | 5 +
.../mapper/app/OutdoorFairJobMapper.xml | 5 +-
.../mapper/rc/PublicJobFairMapper.xml | 27 +-
scripts/migrate-highgo-shz.sh | 100 +++++
sql/cms_cross_city_alliance.sql | 67 +++
sql/public_job_fair_cross_domain_cities.sql | 7 +
29 files changed, 1969 insertions(+), 50 deletions(-)
create mode 100644 docs/app-outdoor-fair-h5-api.md
create mode 100644 ruoyi-admin/src/main/resources/static/h5/outdoor-fair/company.html
create mode 100644 ruoyi-admin/src/main/resources/static/h5/outdoor-fair/detail.html
create mode 100644 ruoyi-bussiness/src/main/java/com/ruoyi/cms/controller/app/AppOutdoorFairController.java
create mode 100644 ruoyi-bussiness/src/main/java/com/ruoyi/cms/controller/cms/CrossCityAllianceController.java
create mode 100644 ruoyi-bussiness/src/main/java/com/ruoyi/cms/domain/CrossCityAlliance.java
create mode 100644 ruoyi-bussiness/src/main/java/com/ruoyi/cms/domain/rc/CrossDomainJobVO.java
create mode 100644 ruoyi-bussiness/src/main/java/com/ruoyi/cms/mapper/CrossCityAllianceMapper.java
create mode 100644 ruoyi-bussiness/src/main/java/com/ruoyi/cms/service/ICrossCityAllianceService.java
create mode 100644 ruoyi-bussiness/src/main/java/com/ruoyi/cms/service/impl/CrossCityAllianceServiceImpl.java
create mode 100755 scripts/migrate-highgo-shz.sh
create mode 100644 sql/cms_cross_city_alliance.sql
create mode 100644 sql/public_job_fair_cross_domain_cities.sql
diff --git a/docs/app-outdoor-fair-h5-api.md b/docs/app-outdoor-fair-h5-api.md
new file mode 100644
index 0000000..cb00790
--- /dev/null
+++ b/docs/app-outdoor-fair-h5-api.md
@@ -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}` | 招聘会公共详情页 |
diff --git a/ruoyi-admin/src/main/resources/application-local.yml b/ruoyi-admin/src/main/resources/application-local.yml
index a3c83d2..83d2353 100644
--- a/ruoyi-admin/src/main/resources/application-local.yml
+++ b/ruoyi-admin/src/main/resources/application-local.yml
@@ -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:
# 连接池中的最小空闲连接
diff --git a/ruoyi-admin/src/main/resources/static/h5/outdoor-fair/company.html b/ruoyi-admin/src/main/resources/static/h5/outdoor-fair/company.html
new file mode 100644
index 0000000..f95904a
--- /dev/null
+++ b/ruoyi-admin/src/main/resources/static/h5/outdoor-fair/company.html
@@ -0,0 +1,299 @@
+
+
+
+
+
+ 参会企业
+
+
+
+
+ 正在加载...
+
+
+
+
diff --git a/ruoyi-admin/src/main/resources/static/h5/outdoor-fair/detail.html b/ruoyi-admin/src/main/resources/static/h5/outdoor-fair/detail.html
new file mode 100644
index 0000000..17b4af7
--- /dev/null
+++ b/ruoyi-admin/src/main/resources/static/h5/outdoor-fair/detail.html
@@ -0,0 +1,208 @@
+
+
+
+
+
+ 户外招聘会
+
+
+
+
+ 正在加载...
+
+
+
+
diff --git a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/controller/app/AppOutdoorFairController.java b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/controller/app/AppOutdoorFairController.java
new file mode 100644
index 0000000..0290a7f
--- /dev/null
+++ b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/controller/app/AppOutdoorFairController.java
@@ -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 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 dates = outdoorFairService.list(new LambdaQueryWrapper()
+ .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()
+ .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()
+ .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 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()
+ .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 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()
+ .eq(FairCompany::getJobFairId, fairId)
+ .eq(FairCompany::getCompanyId, companyId));
+ if (relationCount == null || relationCount == 0) {
+ throw new ServiceException("该企业未参加当前招聘会");
+ }
+ OutdoorFairBoothBooking booking = outdoorFairBoothBookingMapper.selectOne(
+ new LambdaQueryWrapper()
+ .eq(OutdoorFairBoothBooking::getFairId, fairId)
+ .eq(OutdoorFairBoothBooking::getCompanyId, companyId)
+ .eq(OutdoorFairBoothBooking::getStatus, "reserved")
+ .last("LIMIT 1"));
+ List jobs = outdoorFairJobMapper.selectFairJobPage(
+ fairId, companyId, null, null, null, null, 0, 200);
+
+ String companyH5Url = buildCompanyH5Url(request, fairId, companyId);
+ Map 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 buildOutdoorFairWrapper(PublicJobFairQuery query)
+ {
+ LambdaQueryWrapper 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 page, int pageNum, int pageSize)
+ {
+ PublicJobFairResponse.PageData data = new PublicJobFairResponse.PageData();
+ List 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 buildNavigatePageNums(int pages)
+ {
+ List pageNums = new ArrayList<>();
+ for (int i = 1; i <= pages; i++) {
+ pageNums.add(i);
+ }
+ return pageNums;
+ }
+
+ private List listOutdoorFairsByRange(Date startTime, Date endTime)
+ {
+ return outdoorFairService.list(new LambdaQueryWrapper()
+ .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());
+ }
+
+}
diff --git a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/controller/cms/CmsPublicJobFairController.java b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/controller/cms/CmsPublicJobFairController.java
index f18647e..e61b6ef 100644
--- a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/controller/cms/CmsPublicJobFairController.java
+++ b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/controller/cms/CmsPublicJobFairController.java
@@ -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 list = publicJobFairService.selectCrossDomainJobs(cityNames);
+ return getDataTable(list);
+ }
+
/**
* 获取招聘会详情
*/
diff --git a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/controller/cms/CrossCityAllianceController.java b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/controller/cms/CrossCityAllianceController.java
new file mode 100644
index 0000000..613fff9
--- /dev/null
+++ b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/controller/cms/CrossCityAllianceController.java
@@ -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 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)));
+ }
+}
diff --git a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/controller/cms/OutdoorFairController.java b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/controller/cms/OutdoorFairController.java
index 3f85249..201a78a 100644
--- a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/controller/cms/OutdoorFairController.java
+++ b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/controller/cms/OutdoorFairController.java
@@ -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 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 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 relations = fairCompanyMapper.selectList(new LambdaQueryWrapper()
+ LambdaQueryWrapper relationWrapper = new LambdaQueryWrapper()
.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 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 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 deviceMap = outdoorFairDeviceMapper.selectList(new LambdaQueryWrapper()
+ .eq(OutdoorFairDevice::getFairId, fairId)
+ .isNotNull(OutdoorFairDevice::getCompanyId))
+ .stream()
+ .collect(Collectors.toMap(OutdoorFairDevice::getCompanyId, item -> item, (a, b) -> a));
List