diff --git a/.gitignore b/.gitignore index 0f3ddc9..a86bdd0 100644 --- a/.gitignore +++ b/.gitignore @@ -45,4 +45,12 @@ nbdist/ !*/build/*.java !*/build/*.html !*/build/*.xml -.DS_Store \ No newline at end of file +.DS_Store + +# Local credentials for project-scoped Codex skills +.codex/skills/*/.password + +# Local application runtime files +.local/ +.codex/ +local.sh \ No newline at end of file diff --git a/pom.xml b/pom.xml index c685488..e7dff0b 100644 --- a/pom.xml +++ b/pom.xml @@ -33,6 +33,7 @@ 3.5.1 7.19.0 2.5.0 + 1.18.34 5.5.9 @@ -129,6 +130,13 @@ ${velocity.version} + + + org.projectlombok + lombok + ${lombok.version} + + com.alibaba.fastjson2 @@ -228,7 +236,7 @@ org.apache.maven.plugins maven-compiler-plugin - 3.1 + 3.8.1 ${java.version} ${java.version} diff --git a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/config/InterviewMenuMigration.java b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/config/InterviewMenuMigration.java new file mode 100644 index 0000000..855235d --- /dev/null +++ b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/config/InterviewMenuMigration.java @@ -0,0 +1,140 @@ +package com.ruoyi.cms.config; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.context.event.ApplicationReadyEvent; +import org.springframework.context.event.EventListener; +import org.springframework.stereotype.Component; + +import javax.sql.DataSource; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.Statement; + +/** + * 面试菜单权限自动迁移 + * 应用启动后自动检查并添加面试列表菜单及按钮权限到 sys_menu 和 sys_role_menu 表 + */ +@Slf4j +@Component +public class InterviewMenuMigration { + + @Autowired + private DataSource dataSource; + + @EventListener(ApplicationReadyEvent.class) + public void migrate() { + log.info("开始检查面试菜单权限..."); + try (Connection conn = dataSource.getConnection()) { + // Step 1: 查找岗位管理目录的 menu_id + Long parentId = null; + try (Statement stmt = conn.createStatement(); + ResultSet rs = stmt.executeQuery( + "SELECT menu_id FROM sys_menu WHERE path = 'management' AND menu_type = 'M' LIMIT 1")) { + if (rs.next()) { + parentId = rs.getLong("menu_id"); + } + } + + if (parentId == null) { + log.warn("未找到 management 目录菜单,跳过面试菜单迁移"); + return; + } + + log.info("找到 management 目录菜单,parent_id={}", parentId); + + // Step 2: 插入面试列表菜单(幂等) + Long menuId = insertMenuIfNotExists(conn, 2100L, "面试列表", parentId, 10, + "interview-list", "Management/InterviewList", "InterviewList", + "C", "cms:interview:list", "message"); + + if (menuId == null) { + log.warn("面试列表菜单插入失败(可能已存在但 parent_id 不同)"); + return; + } + + // Step 3: 插入按钮权限 + insertMenuIfNotExists(conn, 2101L, "面试查询", menuId, 1, + "", "", "", "F", "cms:interview:query", "#"); + insertMenuIfNotExists(conn, 2102L, "面试新增", menuId, 2, + "", "", "", "F", "cms:interview:add", "#"); + insertMenuIfNotExists(conn, 2103L, "面试修改", menuId, 3, + "", "", "", "F", "cms:interview:edit", "#"); + insertMenuIfNotExists(conn, 2104L, "面试删除", menuId, 4, + "", "", "", "F", "cms:interview:remove", "#"); + + // Step 4: 将新菜单赋给所有非管理员角色 + try (Statement stmt = conn.createStatement()) { + stmt.execute( + "INSERT INTO sys_role_menu (role_id, menu_id) " + + "SELECT r.role_id, " + menuId + " FROM sys_role r " + + "WHERE r.role_key != 'admin' " + + "AND NOT EXISTS (SELECT 1 FROM sys_role_menu rm WHERE rm.role_id = r.role_id AND rm.menu_id = " + menuId + ")"); + } catch (Exception e) { + log.debug("角色菜单关联已存在或插入失败: {}", e.getMessage()); + } + + // 按钮也赋给角色 + for (long btnId : new long[]{2101L, 2102L, 2103L, 2104L}) { + try (Statement stmt = conn.createStatement()) { + stmt.execute( + "INSERT INTO sys_role_menu (role_id, menu_id) " + + "SELECT r.role_id, " + btnId + " FROM sys_role r " + + "WHERE r.role_key != 'admin' " + + "AND NOT EXISTS (SELECT 1 FROM sys_role_menu rm WHERE rm.role_id = r.role_id AND rm.menu_id = " + btnId + ")"); + } catch (Exception e) { + log.debug("按钮角色关联已存在: {}", e.getMessage()); + } + } + + log.info("面试菜单权限迁移完成"); + } catch (Exception e) { + log.error("面试菜单迁移失败: {}", e.getMessage(), e); + } + } + + /** + * 幂等插入菜单记录 + */ + private Long insertMenuIfNotExists(Connection conn, Long menuId, String menuName, + Long parentId, int orderNum, String path, + String component, String routeName, + String menuType, String perms, String icon) { + try { + // 检查是否已存在 + try (PreparedStatement ps = conn.prepareStatement( + "SELECT 1 FROM sys_menu WHERE menu_id = ?")) { + ps.setLong(1, menuId); + try (ResultSet rs = ps.executeQuery()) { + if (rs.next()) { + log.debug("菜单已存在: {} (id={})", menuName, menuId); + return menuId; + } + } + } + + // 插入新菜单 + try (PreparedStatement ps = conn.prepareStatement( + "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) " + + "VALUES (?, ?, ?, ?, ?, ?, '', ?, 1, 0, ?, '0', '0', ?, ?, 'admin', CURRENT_TIMESTAMP)")) { + ps.setLong(1, menuId); + ps.setString(2, menuName); + ps.setLong(3, parentId); + ps.setInt(4, orderNum); + ps.setString(5, path); + ps.setString(6, component); + ps.setString(7, routeName); + ps.setString(8, menuType); + ps.setString(9, perms); + ps.setString(10, icon); + ps.executeUpdate(); + log.info("菜单创建成功: {} (id={}, perms={})", menuName, menuId, perms); + return menuId; + } + } catch (Exception e) { + log.warn("菜单创建失败: {} (id={}), error={}", menuName, menuId, e.getMessage()); + return null; + } + } +} diff --git a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/controller/app/AppInterviewController.java b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/controller/app/AppInterviewController.java index 1fc40dd..5ec8fac 100644 --- a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/controller/app/AppInterviewController.java +++ b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/controller/app/AppInterviewController.java @@ -12,6 +12,8 @@ import io.swagger.annotations.ApiOperation; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; +import javax.servlet.http.HttpServletRequest; +import java.io.BufferedReader; import java.util.List; @@ -67,7 +69,32 @@ public class AppInterviewController extends BaseController { */ @ApiOperation("更新面试邀约状态") @PutMapping("/status/{id}") - public AjaxResult updateStatus(@PathVariable Long id, @RequestParam String status){ + public AjaxResult updateStatus(@PathVariable Long id, HttpServletRequest request) { + String status = request.getParameter("status"); + // 如果 URL 参数没有,尝试从 JSON body 中读取 + if (status == null || status.isEmpty()) { + try { + BufferedReader reader = request.getReader(); + StringBuilder sb = new StringBuilder(); + String line; + while ((line = reader.readLine()) != null) { + sb.append(line); + } + String body = sb.toString(); + if (body != null && !body.isEmpty()) { + // 简单解析 JSON: {"status": "accepted"} + status = body.replaceAll(".*\"status\"\\s*:\\s*\"([^\"]+)\".*", "$1"); + if (status.equals(body)) { + status = null; + } + } + } catch (Exception e) { + // ignore body read errors + } + } + if (status == null || status.isEmpty()) { + return AjaxResult.error("状态参数不能为空"); + } int rows = interviewInvitationService.updateInterviewStatus(id, status); if (rows > 0 && "accepted".equals(status)) { // 接受时返回面试官联系方式,供前端弹窗展示 diff --git a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/controller/cms/CmsInterviewController.java b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/controller/cms/CmsInterviewController.java index 27239ff..5ad5191 100644 --- a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/controller/cms/CmsInterviewController.java +++ b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/controller/cms/CmsInterviewController.java @@ -1,6 +1,7 @@ package com.ruoyi.cms.controller.cms; import com.ruoyi.cms.domain.InterviewInvitation; +import com.ruoyi.cms.domain.vo.InterviewInvitationVO; import com.ruoyi.cms.service.InterviewInvitationService; import com.ruoyi.common.annotation.Log; import com.ruoyi.common.core.controller.BaseController; @@ -38,7 +39,7 @@ public class CmsInterviewController extends BaseController { @GetMapping("/list") public TableDataInfo list(InterviewInvitation interviewInvitation){ startPage(); - List list = interviewInvitationService.getInterviewInvitationList(interviewInvitation); + List list = interviewInvitationService.getInterviewInvitationVOList(interviewInvitation); return getDataTable(list); } 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 new file mode 100644 index 0000000..e289dbe --- /dev/null +++ b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/controller/cms/OutdoorFairController.java @@ -0,0 +1,134 @@ +package com.ruoyi.cms.controller.cms; + +import java.util.Arrays; +import java.util.List; + +import com.ruoyi.cms.domain.OutdoorFair; +import com.ruoyi.cms.domain.OutdoorFairDictDataRequest; +import com.ruoyi.cms.domain.VenueInfo; +import com.ruoyi.cms.service.IOutdoorFairService; +import com.ruoyi.cms.service.IVenueInfoService; +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 com.ruoyi.common.exception.ServiceException; +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.validation.annotation.Validated; +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-25 + */ +@RestController +@RequestMapping("/cms/outdoor-fair") +@Api(tags = "后台:户外招聘会") +public class OutdoorFairController extends BaseController +{ + @Autowired + private IOutdoorFairService outdoorFairService; + + @Autowired + private IVenueInfoService venueInfoService; + + /** + * 查询户外招聘会列表 + */ + @ApiOperation("查询户外招聘会列表") + @PreAuthorize("@ss.hasPermi('cms:outdoorFair:list')") + @GetMapping("/list") + public TableDataInfo list(OutdoorFair outdoorFair) + { + startPage(); + List list = outdoorFairService.selectOutdoorFairList(outdoorFair); + return getDataTable(list); + } + + /** + * 获取户外招聘会详细信息 + */ + @ApiOperation("获取户外招聘会详细信息") + @PreAuthorize("@ss.hasPermi('cms:outdoorFair:query')") + @GetMapping(value = "/{id}") + public AjaxResult getInfo(@PathVariable("id") Long id) + { + return success(outdoorFairService.getById(id)); + } + + /** + * 新增户外招聘会 + */ + @ApiOperation("新增户外招聘会") + @PreAuthorize("@ss.hasPermi('cms:outdoorFair:add')") + @Log(title = "户外招聘会", businessType = BusinessType.INSERT) + @PostMapping + public AjaxResult add(@RequestBody OutdoorFair outdoorFair) + { + fillVenueFields(outdoorFair); + return toAjax(outdoorFairService.save(outdoorFair)); + } + + /** + * 修改户外招聘会 + */ + @ApiOperation("修改户外招聘会") + @PreAuthorize("@ss.hasPermi('cms:outdoorFair:edit')") + @Log(title = "户外招聘会", businessType = BusinessType.UPDATE) + @PutMapping + public AjaxResult edit(@RequestBody OutdoorFair outdoorFair) + { + fillVenueFields(outdoorFair); + return toAjax(outdoorFairService.updateById(outdoorFair)); + } + + /** + * 删除户外招聘会 + */ + @ApiOperation("删除户外招聘会") + @PreAuthorize("@ss.hasPermi('cms:outdoorFair:remove')") + @Log(title = "户外招聘会", businessType = BusinessType.DELETE) + @DeleteMapping("/{ids}") + public AjaxResult remove(@PathVariable Long[] ids) + { + return toAjax(outdoorFairService.removeByIds(Arrays.asList(ids))); + } + + /** + * 新增户外招聘会页面使用的字典项 + */ + @ApiOperation("新增户外招聘会字典项") + @PreAuthorize("@ss.hasAnyPermi('cms:outdoorFair:add,cms:outdoorFair:edit')") + @Log(title = "户外招聘会字典项", businessType = BusinessType.INSERT) + @PostMapping("/dict-data") + public AjaxResult addDictData(@Validated @RequestBody OutdoorFairDictDataRequest request) + { + return toAjax(outdoorFairService.insertOutdoorFairDictData(request.getDictType(), request.getDictLabel(), getUsername())); + } + + private void fillVenueFields(OutdoorFair outdoorFair) + { + if (outdoorFair.getVenueId() == null) { + return; + } + VenueInfo venueInfo = venueInfoService.getById(outdoorFair.getVenueId()); + if (venueInfo == null) { + throw new ServiceException("绑定场地不存在"); + } + outdoorFair.setVenueName(venueInfo.getVenueName()); + outdoorFair.setBoothCount(venueInfo.getFloorCount() == null ? 0 : venueInfo.getFloorCount()); + } +} diff --git a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/controller/cms/VenueInfoController.java b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/controller/cms/VenueInfoController.java new file mode 100644 index 0000000..c8e4414 --- /dev/null +++ b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/controller/cms/VenueInfoController.java @@ -0,0 +1,325 @@ +package com.ruoyi.cms.controller.cms; + +import java.util.Arrays; +import java.util.ArrayList; +import java.util.Date; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.ruoyi.cms.domain.VenueInfo; +import com.ruoyi.cms.domain.OutdoorFair; +import com.ruoyi.cms.domain.OutdoorFairBoothBooking; +import com.ruoyi.cms.domain.VenueBooth; +import com.ruoyi.cms.domain.VenueInfoDictDataRequest; +import com.ruoyi.cms.domain.VenueBoothSwapRequest; +import com.ruoyi.cms.mapper.CompanyMapper; +import com.ruoyi.cms.mapper.OutdoorFairBoothBookingMapper; +import com.ruoyi.cms.mapper.OutdoorFairMapper; +import com.ruoyi.cms.mapper.VenueBoothMapper; +import com.ruoyi.cms.service.IVenueInfoService; +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.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; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.validation.annotation.Validated; +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-25 + */ +@RestController +@RequestMapping("/cms/venue-info") +@Api(tags = "后台:场地信息维护") +public class VenueInfoController extends BaseController +{ + @Autowired + private IVenueInfoService venueInfoService; + + @Autowired + private VenueBoothMapper venueBoothMapper; + + @Autowired + private OutdoorFairMapper outdoorFairMapper; + + @Autowired + private OutdoorFairBoothBookingMapper outdoorFairBoothBookingMapper; + + @Autowired + private CompanyMapper companyMapper; + + /** + * 查询场地信息列表 + */ + @ApiOperation("查询场地信息列表") + @PreAuthorize("@ss.hasPermi('cms:venueInfo:list')") + @GetMapping("/list") + public TableDataInfo list(VenueInfo venueInfo) + { + startPage(); + List list = venueInfoService.selectVenueInfoList(venueInfo); + return getDataTable(list); + } + + /** + * 获取场地信息详细信息 + */ + @ApiOperation("获取场地信息详细信息") + @PreAuthorize("@ss.hasPermi('cms:venueInfo:query')") + @GetMapping(value = "/{id}") + public AjaxResult getInfo(@PathVariable("id") Long id) + { + return success(venueInfoService.getById(id)); + } + + /** + * 新增场地信息 + */ + @ApiOperation("新增场地信息") + @PreAuthorize("@ss.hasPermi('cms:venueInfo:add')") + @Log(title = "场地信息", businessType = BusinessType.INSERT) + @PostMapping + public AjaxResult add(@RequestBody VenueInfo venueInfo) + { + fillHandleFields(venueInfo); + return toAjax(venueInfoService.save(venueInfo)); + } + + /** + * 修改场地信息 + */ + @ApiOperation("修改场地信息") + @PreAuthorize("@ss.hasPermi('cms:venueInfo:edit')") + @Log(title = "场地信息", businessType = BusinessType.UPDATE) + @PutMapping + public AjaxResult edit(@RequestBody VenueInfo venueInfo) + { + fillHandleFields(venueInfo); + return toAjax(venueInfoService.updateById(venueInfo)); + } + + /** + * 删除场地信息 + */ + @ApiOperation("删除场地信息") + @PreAuthorize("@ss.hasPermi('cms:venueInfo:remove')") + @Log(title = "场地信息", businessType = BusinessType.DELETE) + @DeleteMapping("/{ids}") + public AjaxResult remove(@PathVariable Long[] ids) + { + return toAjax(venueInfoService.removeByIds(Arrays.asList(ids))); + } + + /** + * 新增场地信息维护页面使用的字典项 + */ + @ApiOperation("新增场地信息字典项") + @PreAuthorize("@ss.hasAnyPermi('cms:venueInfo:add,cms:venueInfo:edit')") + @Log(title = "场地信息字典项", businessType = BusinessType.INSERT) + @PostMapping("/dict-data") + public AjaxResult addDictData(@Validated @RequestBody VenueInfoDictDataRequest request) + { + return toAjax(venueInfoService.insertVenueInfoDictData(request.getDictType(), request.getDictLabel(), getUsername())); + } + + @ApiOperation("查询场地展位图") + @PreAuthorize("@ss.hasAnyPermi('cms:venueInfo:query,cms:outdoorFair:query')") + @GetMapping("/{venueId}/booths") + public AjaxResult listBooths(@PathVariable Long venueId) + { + List list = venueBoothMapper.selectList(new LambdaQueryWrapper() + .eq(VenueBooth::getVenueId, venueId) + .orderByAsc(VenueBooth::getPositionY) + .orderByAsc(VenueBooth::getPositionX) + .orderByAsc(VenueBooth::getBoothNumber)); + return success(list); + } + + @ApiOperation("保存场地展位图") + @PreAuthorize("@ss.hasPermi('cms:venueInfo:edit')") + @Transactional(rollbackFor = Exception.class) + @PostMapping("/{venueId}/booths") + public AjaxResult saveBooths(@PathVariable Long venueId, @RequestBody List booths) + { + venueBoothMapper.physicalDeleteDeletedByVenueId(venueId); + List existingBooths = venueBoothMapper.selectList(new LambdaQueryWrapper() + .eq(VenueBooth::getVenueId, venueId)); + Map existingById = existingBooths.stream() + .filter(item -> item.getId() != null) + .collect(Collectors.toMap(VenueBooth::getId, item -> item, (a, b) -> a)); + Map existingByNumber = existingBooths.stream() + .filter(item -> StringUtils.isNotBlank(item.getBoothNumber())) + .collect(Collectors.toMap(VenueBooth::getBoothNumber, item -> item, (a, b) -> a)); + Set boothNumbers = new HashSet<>(); + Set savedIds = new HashSet<>(); + if (booths != null) { + for (VenueBooth booth : booths) { + if (StringUtils.isBlank(booth.getBoothNumber())) { + return AjaxResult.error("展位号不能为空"); + } + if (!boothNumbers.add(booth.getBoothNumber())) { + return AjaxResult.error("展位号不能重复:" + booth.getBoothNumber()); + } + } + for (VenueBooth booth : booths) { + VenueBooth existing = booth.getId() == null ? null : existingById.get(booth.getId()); + if (existing == null) { + existing = existingByNumber.get(booth.getBoothNumber()); + } + booth.setVenueId(venueId); + if (existing == null) { + booth.setId(null); + venueBoothMapper.insert(booth); + } else { + booth.setId(existing.getId()); + venueBoothMapper.updateById(booth); + } + if (booth.getId() != null) { + savedIds.add(booth.getId()); + } + } + VenueInfo venueInfo = venueInfoService.getById(venueId); + if (venueInfo != null) { + venueInfo.setFloorCount(booths.size()); + venueInfoService.updateById(venueInfo); + } + } + List removedIds = existingBooths.stream() + .map(VenueBooth::getId) + .filter(id -> id != null && !savedIds.contains(id)) + .collect(Collectors.toList()); + if (!removedIds.isEmpty()) { + venueBoothMapper.physicalDeleteByVenueIdAndIds(venueId, removedIds); + } + return success(); + } + + @ApiOperation("查询户外招聘会展位图") + @PreAuthorize("@ss.hasPermi('cms:outdoorFair:query')") + @GetMapping("/fair/{fairId}/booth-map") + public AjaxResult fairBoothMap(@PathVariable Long fairId) + { + OutdoorFair fair = outdoorFairMapper.selectById(fairId); + if (fair == null || fair.getVenueId() == null) { + return success(new ArrayList<>()); + } + List booths = venueBoothMapper.selectList(new LambdaQueryWrapper() + .eq(VenueBooth::getVenueId, fair.getVenueId()) + .orderByAsc(VenueBooth::getPositionY) + .orderByAsc(VenueBooth::getPositionX) + .orderByAsc(VenueBooth::getBoothNumber)); + List bookings = outdoorFairBoothBookingMapper.selectList(new LambdaQueryWrapper() + .eq(OutdoorFairBoothBooking::getFairId, fairId)); + Map bookingMap = bookings.stream().collect(Collectors.toMap(OutdoorFairBoothBooking::getBoothId, item -> item, (a, b) -> a)); + List> result = new ArrayList<>(); + for (VenueBooth booth : booths) { + OutdoorFairBoothBooking booking = bookingMap.get(booth.getId()); + Map row = new java.util.HashMap<>(); + row.put("id", booth.getId()); + row.put("boothId", booth.getId()); + row.put("boothNumber", booth.getBoothNumber()); + row.put("positionX", booth.getPositionX()); + row.put("positionY", booth.getPositionY()); + row.put("bookingId", booking == null ? null : booking.getId()); + row.put("companyId", booking == null ? null : booking.getCompanyId()); + row.put("companyName", booking == null ? null : booking.getCompanyName()); + row.put("status", booking == null ? "available" : booking.getStatus()); + result.add(row); + } + return success(result); + } + + @ApiOperation("预定户外招聘会展位") + @PreAuthorize("@ss.hasPermi('cms:outdoorFair:edit')") + @PostMapping("/fair/{fairId}/booth-booking") + public AjaxResult bookBooth(@PathVariable Long fairId, @RequestBody OutdoorFairBoothBooking booking) + { + if (booking.getCompanyId() == null) { + return AjaxResult.error("请选择预定单位"); + } + Company company = companyMapper.selectById(booking.getCompanyId()); + if (company == null || company.getStatus() == null || company.getStatus() != 1) { + return AjaxResult.error("只能预定审核通过的企业"); + } + VenueBooth booth = venueBoothMapper.selectById(booking.getBoothId()); + booking.setFairId(fairId); + booking.setBoothNumber(booth == null ? booking.getBoothNumber() : booth.getBoothNumber()); + booking.setCompanyName(company.getName()); + booking.setStatus(StringUtils.isBlank(booking.getStatus()) ? "reserved" : booking.getStatus()); + OutdoorFairBoothBooking existing = outdoorFairBoothBookingMapper.selectOne(new LambdaQueryWrapper() + .eq(OutdoorFairBoothBooking::getFairId, fairId) + .eq(OutdoorFairBoothBooking::getBoothId, booking.getBoothId()) + .last("limit 1")); + if (existing == null) { + return toAjax(outdoorFairBoothBookingMapper.insert(booking)); + } + booking.setId(existing.getId()); + return toAjax(outdoorFairBoothBookingMapper.updateById(booking)); + } + + @ApiOperation("取消户外招聘会展位预定") + @PreAuthorize("@ss.hasPermi('cms:outdoorFair:edit')") + @DeleteMapping("/fair/{fairId}/booth-booking/{boothId}") + public AjaxResult cancelBooth(@PathVariable Long fairId, @PathVariable Long boothId) + { + return toAjax(outdoorFairBoothBookingMapper.delete(new LambdaQueryWrapper() + .eq(OutdoorFairBoothBooking::getFairId, fairId) + .eq(OutdoorFairBoothBooking::getBoothId, boothId))); + } + + @ApiOperation("调换户外招聘会展位") + @PreAuthorize("@ss.hasPermi('cms:outdoorFair:edit')") + @PostMapping("/fair/{fairId}/booth-swap") + public AjaxResult swapBooth(@PathVariable Long fairId, @RequestBody VenueBoothSwapRequest request) + { + OutdoorFairBoothBooking first = outdoorFairBoothBookingMapper.selectOne(new LambdaQueryWrapper() + .eq(OutdoorFairBoothBooking::getFairId, fairId) + .eq(OutdoorFairBoothBooking::getBoothId, request.getFirstBoothId()).last("limit 1")); + OutdoorFairBoothBooking second = outdoorFairBoothBookingMapper.selectOne(new LambdaQueryWrapper() + .eq(OutdoorFairBoothBooking::getFairId, fairId) + .eq(OutdoorFairBoothBooking::getBoothId, request.getSecondBoothId()).last("limit 1")); + if (first == null || second == null) { + return AjaxResult.error("请选择两个已预定展位"); + } + Long companyId = first.getCompanyId(); + String companyName = first.getCompanyName(); + first.setCompanyId(second.getCompanyId()); + first.setCompanyName(second.getCompanyName()); + second.setCompanyId(companyId); + second.setCompanyName(companyName); + outdoorFairBoothBookingMapper.updateById(first); + outdoorFairBoothBookingMapper.updateById(second); + return success(); + } + + private void fillHandleFields(VenueInfo venueInfo) + { + if (venueInfo.getHandleDate() == null) { + venueInfo.setHandleDate(new Date()); + } + if (StringUtils.isBlank(venueInfo.getHandler())) { + venueInfo.setHandler(getUsername()); + } + } +} diff --git a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/domain/InterviewInvitation.java b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/domain/InterviewInvitation.java index af095fb..c0f8281 100644 --- a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/domain/InterviewInvitation.java +++ b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/domain/InterviewInvitation.java @@ -77,6 +77,11 @@ public class InterviewInvitation extends BaseEntity { */ @ApiModelProperty("面试官联系方式") private String contactPhone; + /** + * 面试官姓名 + */ + @ApiModelProperty("面试官姓名") + private String interviewerName; /** * 公司名称(冗余存储,创建时从company表获取) */ diff --git a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/domain/OutdoorFair.java b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/domain/OutdoorFair.java new file mode 100644 index 0000000..e3dd59a --- /dev/null +++ b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/domain/OutdoorFair.java @@ -0,0 +1,91 @@ +package com.ruoyi.cms.domain; + +import java.util.Date; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.ruoyi.common.annotation.Excel; +import com.ruoyi.common.core.domain.BaseEntity; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +/** + * 户外招聘会对象 cms_outdoor_fair + * + * @author ruoyi + * @date 2026-06-25 + */ +@Data +@ApiModel("户外招聘会") +@TableName("cms_outdoor_fair") +public class OutdoorFair extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + @TableId(value = "id", type = IdType.AUTO) + @ApiModelProperty("主键ID") + private Long id; + + @Excel(name = "招聘会标题") + @ApiModelProperty("招聘会标题") + private String title; + + @Excel(name = "举办单位") + @ApiModelProperty("举办单位") + private String hostUnit; + + @Excel(name = "招聘会类型") + @ApiModelProperty("招聘会类型") + private String fairType; + + @Excel(name = "举办区域") + @ApiModelProperty("举办区域") + private String region; + + @Excel(name = "绑定场地ID") + @ApiModelProperty("绑定场地ID") + private Long venueId; + + @Excel(name = "绑定场地") + @ApiModelProperty("绑定场地") + private String venueName; + + @Excel(name = "举办地址") + @ApiModelProperty("举办地址") + private String address; + + @Excel(name = "展位数量") + @ApiModelProperty("展位数量") + private Integer boothCount; + + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + @Excel(name = "举办时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss") + @ApiModelProperty("举办时间") + private Date holdTime; + + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + @Excel(name = "截止时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss") + @ApiModelProperty("截止时间") + private Date endTime; + + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + @Excel(name = "开放申请时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss") + @ApiModelProperty("开放申请时间") + private Date applyStartTime; + + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + @Excel(name = "截止申请时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss") + @ApiModelProperty("截止申请时间") + private Date applyEndTime; + + @Excel(name = "是否开启线上申请") + @ApiModelProperty("是否开启线上申请") + private Boolean onlineApply; + + @Excel(name = "招聘会照片") + @ApiModelProperty("招聘会照片") + private String photoUrl; +} diff --git a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/domain/OutdoorFairBoothBooking.java b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/domain/OutdoorFairBoothBooking.java new file mode 100644 index 0000000..94fd298 --- /dev/null +++ b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/domain/OutdoorFairBoothBooking.java @@ -0,0 +1,38 @@ +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 io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +@Data +@ApiModel("户外招聘会展位预定") +@TableName("cms_outdoor_fair_booth_booking") +public class OutdoorFairBoothBooking extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + @TableId(value = "id", type = IdType.AUTO) + private Long id; + + @ApiModelProperty("招聘会ID") + private Long fairId; + + @ApiModelProperty("场地展位ID") + private Long boothId; + + @ApiModelProperty("展位号") + private String boothNumber; + + @ApiModelProperty("企业ID") + private Long companyId; + + @ApiModelProperty("企业名称") + private String companyName; + + @ApiModelProperty("预定状态") + private String status; +} diff --git a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/domain/OutdoorFairDictDataRequest.java b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/domain/OutdoorFairDictDataRequest.java new file mode 100644 index 0000000..99e8225 --- /dev/null +++ b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/domain/OutdoorFairDictDataRequest.java @@ -0,0 +1,26 @@ +package com.ruoyi.cms.domain; + +import javax.validation.constraints.NotBlank; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +/** + * 户外招聘会字典新增请求 + * + * @author ruoyi + * @date 2026-06-25 + */ +@Data +@ApiModel("户外招聘会字典新增请求") +public class OutdoorFairDictDataRequest +{ + @NotBlank(message = "字典类型不能为空") + @ApiModelProperty(value = "字典类型", required = true) + private String dictType; + + @NotBlank(message = "字典标签不能为空") + @ApiModelProperty(value = "字典标签", required = true) + private String dictLabel; +} diff --git a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/domain/VenueBooth.java b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/domain/VenueBooth.java new file mode 100644 index 0000000..c948bd7 --- /dev/null +++ b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/domain/VenueBooth.java @@ -0,0 +1,32 @@ +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 io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +@Data +@ApiModel("场地展位") +@TableName("cms_venue_booth") +public class VenueBooth extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + @TableId(value = "id", type = IdType.AUTO) + private Long id; + + @ApiModelProperty("场地ID") + private Long venueId; + + @ApiModelProperty("展位号") + private String boothNumber; + + @ApiModelProperty("X坐标") + private Integer positionX; + + @ApiModelProperty("Y坐标") + private Integer positionY; +} diff --git a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/domain/VenueBoothSwapRequest.java b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/domain/VenueBoothSwapRequest.java new file mode 100644 index 0000000..b72797b --- /dev/null +++ b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/domain/VenueBoothSwapRequest.java @@ -0,0 +1,10 @@ +package com.ruoyi.cms.domain; + +import lombok.Data; + +@Data +public class VenueBoothSwapRequest +{ + private Long firstBoothId; + private Long secondBoothId; +} diff --git a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/domain/VenueInfo.java b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/domain/VenueInfo.java new file mode 100644 index 0000000..685bf36 --- /dev/null +++ b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/domain/VenueInfo.java @@ -0,0 +1,77 @@ +package com.ruoyi.cms.domain; + +import java.math.BigDecimal; +import java.util.Date; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.ruoyi.common.annotation.Excel; +import com.ruoyi.common.core.domain.BaseEntity; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +/** + * 场地信息对象 cms_venue_info + * + * @author ruoyi + * @date 2026-06-25 + */ +@Data +@ApiModel("场地信息") +@TableName("cms_venue_info") +public class VenueInfo extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + @TableId(value = "id", type = IdType.AUTO) + @ApiModelProperty("主键ID") + private Long id; + + @Excel(name = "场地类型") + @ApiModelProperty("场地类型") + private String venueType; + + @Excel(name = "场地名称") + @ApiModelProperty("场地名称") + private String venueName; + + @Excel(name = "场地面积") + @ApiModelProperty("场地面积") + private BigDecimal venueArea; + + @Excel(name = "展位数量") + @ApiModelProperty("展位数量") + private Integer floorCount; + + @Excel(name = "场地地址") + @ApiModelProperty("场地地址") + private String venueAddress; + + @Excel(name = "联系电话") + @ApiModelProperty("联系电话") + private String contactPhone; + + @Excel(name = "乘坐线路ID") + @ApiModelProperty("乘坐线路ID,多个用英文逗号分隔") + private String routeLineIds; + + @Excel(name = "乘坐线路") + @ApiModelProperty("乘坐线路名称,多个用英文逗号分隔") + private String routeLineNames; + + @JsonFormat(pattern = "yyyy-MM-dd") + @Excel(name = "经办日期", width = 30, dateFormat = "yyyy-MM-dd") + @ApiModelProperty("经办日期") + private Date handleDate; + + @Excel(name = "经办机构") + @ApiModelProperty("经办机构") + private String handleOrg; + + @Excel(name = "经办人") + @ApiModelProperty("经办人") + private String handler; +} diff --git a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/domain/VenueInfoDictDataRequest.java b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/domain/VenueInfoDictDataRequest.java new file mode 100644 index 0000000..6a85774 --- /dev/null +++ b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/domain/VenueInfoDictDataRequest.java @@ -0,0 +1,26 @@ +package com.ruoyi.cms.domain; + +import javax.validation.constraints.NotBlank; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +/** + * 场地信息字典新增请求 + * + * @author ruoyi + * @date 2026-06-25 + */ +@Data +@ApiModel("场地信息字典新增请求") +public class VenueInfoDictDataRequest +{ + @NotBlank(message = "字典类型不能为空") + @ApiModelProperty(value = "字典类型", required = true) + private String dictType; + + @NotBlank(message = "字典标签不能为空") + @ApiModelProperty(value = "字典标签", required = true) + private String dictLabel; +} diff --git a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/domain/vo/CandidateVO.java b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/domain/vo/CandidateVO.java index 5ba7347..38ec0ec 100644 --- a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/domain/vo/CandidateVO.java +++ b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/domain/vo/CandidateVO.java @@ -18,4 +18,6 @@ public class CandidateVO extends AppUser { private String companyName; @Excel(name = "岗位名称", sort = 1) private String jobName; + private String interviewStatus; + private Long interviewId; } diff --git a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/domain/vo/InterviewInvitationVO.java b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/domain/vo/InterviewInvitationVO.java index aaa8356..2b56d61 100644 --- a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/domain/vo/InterviewInvitationVO.java +++ b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/domain/vo/InterviewInvitationVO.java @@ -11,4 +11,10 @@ public class InterviewInvitationVO extends InterviewInvitation { @ApiModelProperty("岗位名称(来自关联查询的实时数据)") private String jobTitle; + + @ApiModelProperty("求职者姓名(来自关联查询的实时数据)") + private String applicantName; + + @ApiModelProperty("求职者联系方式(来自关联查询的实时数据)") + private String applicantPhone; } diff --git a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/mapper/OutdoorFairBoothBookingMapper.java b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/mapper/OutdoorFairBoothBookingMapper.java new file mode 100644 index 0000000..4736774 --- /dev/null +++ b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/mapper/OutdoorFairBoothBookingMapper.java @@ -0,0 +1,8 @@ +package com.ruoyi.cms.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.ruoyi.cms.domain.OutdoorFairBoothBooking; + +public interface OutdoorFairBoothBookingMapper extends BaseMapper +{ +} diff --git a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/mapper/OutdoorFairMapper.java b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/mapper/OutdoorFairMapper.java new file mode 100644 index 0000000..3154722 --- /dev/null +++ b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/mapper/OutdoorFairMapper.java @@ -0,0 +1,53 @@ +package com.ruoyi.cms.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.ruoyi.cms.domain.OutdoorFair; +import org.apache.ibatis.annotations.Insert; +import org.apache.ibatis.annotations.Param; +import org.apache.ibatis.annotations.Select; + +/** + * 户外招聘会Mapper接口 + * + * @author ruoyi + * @date 2026-06-25 + */ +public interface OutdoorFairMapper extends BaseMapper +{ + /** + * 查询字典项是否存在 + * + * @param dictType 字典类型 + * @param dictValue 字典值 + * @return 存在数量 + */ + @Select("select count(1) from sys_dict_data where dict_type = #{dictType} and dict_value = #{dictValue}") + int countDictDataByTypeAndValue(@Param("dictType") String dictType, @Param("dictValue") String dictValue); + + /** + * 获取字典下一个排序 + * + * @param dictType 字典类型 + * @return 下一个排序值 + */ + @Select("select coalesce(max(dict_sort), 0) + 1 from sys_dict_data where dict_type = #{dictType}") + Long selectNextDictSort(@Param("dictType") String dictType); + + /** + * 新增系统字典项 + * + * @param dictSort 字典排序 + * @param dictLabel 字典标签 + * @param dictValue 字典值 + * @param dictType 字典类型 + * @param createBy 创建人 + * @return 影响行数 + */ + @Insert("insert into sys_dict_data (dict_code, dict_sort, dict_label, dict_value, dict_type, list_class, is_default, status, create_by, create_time, remark) " + + "values ((select coalesce(max(dict_code), 0) + 1 from sys_dict_data), #{dictSort}, #{dictLabel}, #{dictValue}, #{dictType}, 'default', 'N', '0', #{createBy}, sysdate(), '户外招聘会页面新增')") + int insertDictData(@Param("dictSort") Long dictSort, + @Param("dictLabel") String dictLabel, + @Param("dictValue") String dictValue, + @Param("dictType") String dictType, + @Param("createBy") String createBy); +} diff --git a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/mapper/VenueBoothMapper.java b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/mapper/VenueBoothMapper.java new file mode 100644 index 0000000..f2fe589 --- /dev/null +++ b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/mapper/VenueBoothMapper.java @@ -0,0 +1,24 @@ +package com.ruoyi.cms.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.ruoyi.cms.domain.VenueBooth; +import org.apache.ibatis.annotations.Delete; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +public interface VenueBoothMapper extends BaseMapper +{ + @Delete("DELETE FROM cms_venue_booth WHERE venue_id = #{venueId} AND del_flag <> '0'") + int physicalDeleteDeletedByVenueId(@Param("venueId") Long venueId); + + @Delete({ + "" + }) + int physicalDeleteByVenueIdAndIds(@Param("venueId") Long venueId, @Param("ids") List ids); +} diff --git a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/mapper/VenueInfoMapper.java b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/mapper/VenueInfoMapper.java new file mode 100644 index 0000000..3b74eeb --- /dev/null +++ b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/mapper/VenueInfoMapper.java @@ -0,0 +1,53 @@ +package com.ruoyi.cms.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.ruoyi.cms.domain.VenueInfo; +import org.apache.ibatis.annotations.Insert; +import org.apache.ibatis.annotations.Param; +import org.apache.ibatis.annotations.Select; + +/** + * 场地信息Mapper接口 + * + * @author ruoyi + * @date 2026-06-25 + */ +public interface VenueInfoMapper extends BaseMapper +{ + /** + * 查询字典项是否存在 + * + * @param dictType 字典类型 + * @param dictValue 字典值 + * @return 存在数量 + */ + @Select("select count(1) from sys_dict_data where dict_type = #{dictType} and dict_value = #{dictValue}") + int countDictDataByTypeAndValue(@Param("dictType") String dictType, @Param("dictValue") String dictValue); + + /** + * 获取字典下一个排序 + * + * @param dictType 字典类型 + * @return 下一个排序值 + */ + @Select("select coalesce(max(dict_sort), 0) + 1 from sys_dict_data where dict_type = #{dictType}") + Long selectNextDictSort(@Param("dictType") String dictType); + + /** + * 新增系统字典项 + * + * @param dictSort 字典排序 + * @param dictLabel 字典标签 + * @param dictValue 字典值 + * @param dictType 字典类型 + * @param createBy 创建人 + * @return 影响行数 + */ + @Insert("insert into sys_dict_data (dict_code, dict_sort, dict_label, dict_value, dict_type, list_class, is_default, status, create_by, create_time, remark) " + + "values ((select coalesce(max(dict_code), 0) + 1 from sys_dict_data), #{dictSort}, #{dictLabel}, #{dictValue}, #{dictType}, 'default', 'N', '0', #{createBy}, sysdate(), '场地信息维护页面新增')") + int insertDictData(@Param("dictSort") Long dictSort, + @Param("dictLabel") String dictLabel, + @Param("dictValue") String dictValue, + @Param("dictType") String dictType, + @Param("createBy") String createBy); +} diff --git a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/service/IOutdoorFairService.java b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/service/IOutdoorFairService.java new file mode 100644 index 0000000..210546f --- /dev/null +++ b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/service/IOutdoorFairService.java @@ -0,0 +1,33 @@ +package com.ruoyi.cms.service; + +import java.util.List; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.ruoyi.cms.domain.OutdoorFair; + +/** + * 户外招聘会Service接口 + * + * @author ruoyi + * @date 2026-06-25 + */ +public interface IOutdoorFairService extends IService +{ + /** + * 查询户外招聘会列表 + * + * @param outdoorFair 户外招聘会 + * @return 户外招聘会集合 + */ + List selectOutdoorFairList(OutdoorFair outdoorFair); + + /** + * 新增户外招聘会相关字典项 + * + * @param dictType 字典类型 + * @param dictLabel 字典标签 + * @param createBy 创建人 + * @return 影响行数 + */ + int insertOutdoorFairDictData(String dictType, String dictLabel, String createBy); +} diff --git a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/service/IVenueInfoService.java b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/service/IVenueInfoService.java new file mode 100644 index 0000000..fbfc4a0 --- /dev/null +++ b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/service/IVenueInfoService.java @@ -0,0 +1,33 @@ +package com.ruoyi.cms.service; + +import java.util.List; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.ruoyi.cms.domain.VenueInfo; + +/** + * 场地信息Service接口 + * + * @author ruoyi + * @date 2026-06-25 + */ +public interface IVenueInfoService extends IService +{ + /** + * 查询场地信息列表 + * + * @param venueInfo 场地信息 + * @return 场地信息集合 + */ + List selectVenueInfoList(VenueInfo venueInfo); + + /** + * 新增场地信息相关字典项 + * + * @param dictType 字典类型 + * @param dictLabel 字典标签 + * @param createBy 创建人 + * @return 影响行数 + */ + int insertVenueInfoDictData(String dictType, String dictLabel, String createBy); +} diff --git a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/service/impl/ESJobSearchImpl.java b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/service/impl/ESJobSearchImpl.java index 97461f2..2f2535c 100644 --- a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/service/impl/ESJobSearchImpl.java +++ b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/service/impl/ESJobSearchImpl.java @@ -72,7 +72,7 @@ public class ESJobSearchImpl implements IESJobSearchService /** * 项目启动时,初始化索引及数据 */ - @PostConstruct +// @PostConstruct public void init() { boolean isLockAcquired = false; diff --git a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/service/impl/InterviewInvitationServiceImpl.java b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/service/impl/InterviewInvitationServiceImpl.java index 8d334f6..d4f955e 100644 --- a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/service/impl/InterviewInvitationServiceImpl.java +++ b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/service/impl/InterviewInvitationServiceImpl.java @@ -17,12 +17,14 @@ import com.ruoyi.common.core.domain.entity.Company; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; -import javax.annotation.PostConstruct; import javax.sql.DataSource; import java.sql.Connection; import java.sql.Statement; import lombok.extern.slf4j.Slf4j; import java.util.Arrays; + +import org.springframework.boot.context.event.ApplicationReadyEvent; +import org.springframework.context.event.EventListener; import java.util.List; @@ -43,8 +45,9 @@ public class InterviewInvitationServiceImpl implements InterviewInvitationServic @Autowired private DataSource dataSource; - @PostConstruct + @EventListener(ApplicationReadyEvent.class) public void initTable() { + log.info("开始检查并初始化 interview_invitation 表..."); try (Connection conn = dataSource.getConnection(); Statement stmt = conn.createStatement()) { // Check if id column has auto-increment (BIGSERIAL or sequence default) @@ -80,6 +83,7 @@ public class InterviewInvitationServiceImpl implements InterviewInvitationServic + "meeting_password varchar(100) NULL," + "interview_location varchar(500) NULL," + "contact_phone varchar(50) NULL," + + "interviewer_name varchar(100) NULL," + "company_name varchar(200) NULL," + "job_name varchar(200) NULL," + "status varchar(20) DEFAULT 'pending' NULL," @@ -96,9 +100,16 @@ public class InterviewInvitationServiceImpl implements InterviewInvitationServic log.info("interview_invitation 表创建成功(序列自增)!"); } else { log.info("interview_invitation 表结构正常,检查新增列..."); - // 为已有数据库添加新列(幂等操作) - try { stmt.execute("ALTER TABLE interview_invitation ADD COLUMN IF NOT EXISTS company_name varchar(200) NULL"); } catch (Exception ignored) {} - try { stmt.execute("ALTER TABLE interview_invitation ADD COLUMN IF NOT EXISTS job_name varchar(200) NULL"); } catch (Exception ignored) {} + // 为已有数据库添加新列(幂等操作,使用 DO 块兼容不支持 IF NOT EXISTS 的数据库版本) + try { + stmt.execute("DO $$ BEGIN ALTER TABLE interview_invitation ADD COLUMN company_name varchar(200) NULL; EXCEPTION WHEN duplicate_column THEN END; $$;"); + } catch (Exception e) { log.warn("添加 company_name 列失败: {}", e.getMessage()); } + try { + stmt.execute("DO $$ BEGIN ALTER TABLE interview_invitation ADD COLUMN job_name varchar(200) NULL; EXCEPTION WHEN duplicate_column THEN END; $$;"); + } catch (Exception e) { log.warn("添加 job_name 列失败: {}", e.getMessage()); } + try { + stmt.execute("DO $$ BEGIN ALTER TABLE interview_invitation ADD COLUMN interviewer_name varchar(100) NULL; EXCEPTION WHEN duplicate_column THEN END; $$;"); + } catch (Exception e) { log.warn("添加 interviewer_name 列失败: {}", e.getMessage()); } } } catch (Exception e) { log.error("初始化表失败: {}", e.getMessage()); diff --git a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/service/impl/OutdoorFairServiceImpl.java b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/service/impl/OutdoorFairServiceImpl.java new file mode 100644 index 0000000..97362ff --- /dev/null +++ b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/service/impl/OutdoorFairServiceImpl.java @@ -0,0 +1,79 @@ +package com.ruoyi.cms.service.impl; + +import java.util.List; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.ruoyi.cms.domain.OutdoorFair; +import com.ruoyi.cms.mapper.OutdoorFairMapper; +import com.ruoyi.cms.service.IOutdoorFairService; +import com.ruoyi.common.exception.ServiceException; +import com.ruoyi.common.utils.DictUtils; +import com.ruoyi.common.utils.StringUtils; +import org.springframework.stereotype.Service; + +/** + * 户外招聘会Service业务层处理 + * + * @author ruoyi + * @date 2026-06-25 + */ +@Service +public class OutdoorFairServiceImpl extends ServiceImpl implements IOutdoorFairService +{ + private static final String OUTDOOR_FAIR_TYPE_DICT = "outdoor_fair_type"; + + private static final String OUTDOOR_FAIR_REGION_DICT = "outdoor_fair_region"; + + /** + * 查询户外招聘会列表 + * + * @param outdoorFair 户外招聘会 + * @return 户外招聘会集合 + */ + @Override + public List selectOutdoorFairList(OutdoorFair outdoorFair) + { + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.like(StringUtils.isNotBlank(outdoorFair.getTitle()), OutdoorFair::getTitle, outdoorFair.getTitle()); + queryWrapper.like(StringUtils.isNotBlank(outdoorFair.getHostUnit()), OutdoorFair::getHostUnit, outdoorFair.getHostUnit()); + queryWrapper.eq(StringUtils.isNotBlank(outdoorFair.getFairType()), OutdoorFair::getFairType, outdoorFair.getFairType()); + queryWrapper.eq(StringUtils.isNotBlank(outdoorFair.getRegion()), OutdoorFair::getRegion, outdoorFair.getRegion()); + queryWrapper.eq(outdoorFair.getVenueId() != null, OutdoorFair::getVenueId, outdoorFair.getVenueId()); + queryWrapper.orderByDesc(OutdoorFair::getHoldTime); + queryWrapper.orderByDesc(OutdoorFair::getCreateTime); + return list(queryWrapper); + } + + /** + * 新增户外招聘会相关字典项 + * + * @param dictType 字典类型 + * @param dictLabel 字典标签 + * @param createBy 创建人 + * @return 影响行数 + */ + @Override + public int insertOutdoorFairDictData(String dictType, String dictLabel, String createBy) + { + if (!OUTDOOR_FAIR_TYPE_DICT.equals(dictType) && !OUTDOOR_FAIR_REGION_DICT.equals(dictType)) { + throw new ServiceException("只允许维护户外招聘会类型和举办区域字典"); + } + String trimmedLabel = StringUtils.trim(dictLabel); + if (StringUtils.isBlank(trimmedLabel)) { + throw new ServiceException("字典名称不能为空"); + } + if (trimmedLabel.length() > 100) { + throw new ServiceException("字典名称长度不能超过100个字符"); + } + if (baseMapper.countDictDataByTypeAndValue(dictType, trimmedLabel) > 0) { + return 1; + } + Long dictSort = baseMapper.selectNextDictSort(dictType); + int rows = baseMapper.insertDictData(dictSort, trimmedLabel, trimmedLabel, dictType, createBy); + if (rows > 0) { + DictUtils.removeDictCache(dictType); + } + return rows; + } +} diff --git a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/service/impl/VenueInfoServiceImpl.java b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/service/impl/VenueInfoServiceImpl.java new file mode 100644 index 0000000..ed2a5ce --- /dev/null +++ b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/service/impl/VenueInfoServiceImpl.java @@ -0,0 +1,74 @@ +package com.ruoyi.cms.service.impl; + +import java.util.List; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.ruoyi.cms.domain.VenueInfo; +import com.ruoyi.cms.mapper.VenueInfoMapper; +import com.ruoyi.cms.service.IVenueInfoService; +import com.ruoyi.common.exception.ServiceException; +import com.ruoyi.common.utils.DictUtils; +import com.ruoyi.common.utils.StringUtils; +import org.springframework.stereotype.Service; + +/** + * 场地信息Service业务层处理 + * + * @author ruoyi + * @date 2026-06-25 + */ +@Service +public class VenueInfoServiceImpl extends ServiceImpl implements IVenueInfoService +{ + private static final String VENUE_TYPE_DICT = "venue_info_type"; + + /** + * 查询场地信息列表 + * + * @param venueInfo 场地信息 + * @return 场地信息集合 + */ + @Override + public List selectVenueInfoList(VenueInfo venueInfo) + { + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(StringUtils.isNotBlank(venueInfo.getVenueType()), VenueInfo::getVenueType, venueInfo.getVenueType()); + queryWrapper.like(StringUtils.isNotBlank(venueInfo.getVenueName()), VenueInfo::getVenueName, venueInfo.getVenueName()); + queryWrapper.like(StringUtils.isNotBlank(venueInfo.getVenueAddress()), VenueInfo::getVenueAddress, venueInfo.getVenueAddress()); + queryWrapper.orderByDesc(VenueInfo::getCreateTime); + return list(queryWrapper); + } + + /** + * 新增场地信息相关字典项 + * + * @param dictType 字典类型 + * @param dictLabel 字典标签 + * @param createBy 创建人 + * @return 影响行数 + */ + @Override + public int insertVenueInfoDictData(String dictType, String dictLabel, String createBy) + { + if (!VENUE_TYPE_DICT.equals(dictType)) { + throw new ServiceException("只允许维护场地类型字典"); + } + String trimmedLabel = StringUtils.trim(dictLabel); + if (StringUtils.isBlank(trimmedLabel)) { + throw new ServiceException("字典名称不能为空"); + } + if (trimmedLabel.length() > 100) { + throw new ServiceException("字典名称长度不能超过100个字符"); + } + if (baseMapper.countDictDataByTypeAndValue(dictType, trimmedLabel) > 0) { + return 1; + } + Long dictSort = baseMapper.selectNextDictSort(dictType); + int rows = baseMapper.insertDictData(dictSort, trimmedLabel, trimmedLabel, dictType, createBy); + if (rows > 0) { + DictUtils.removeDictCache(dictType); + } + return rows; + } +} diff --git a/ruoyi-bussiness/src/main/resources/mapper/app/InterviewInvitationMapper.xml b/ruoyi-bussiness/src/main/resources/mapper/app/InterviewInvitationMapper.xml index 59e7c90..652348d 100644 --- a/ruoyi-bussiness/src/main/resources/mapper/app/InterviewInvitationMapper.xml +++ b/ruoyi-bussiness/src/main/resources/mapper/app/InterviewInvitationMapper.xml @@ -16,6 +16,7 @@ + @@ -30,12 +31,14 @@ + + select id, company_id, job_id, user_id, apply_id, interview_time, interview_method, meeting_link, meeting_password, interview_location, - contact_phone, company_name, job_name, status, del_flag, + contact_phone, interviewer_name, company_name, job_name, status, del_flag, create_by, create_time, update_by, update_time, remark from interview_invitation @@ -53,13 +56,22 @@ +