From 22ad1aff40661cf03446479ea955cc95236f9e3d Mon Sep 17 00:00:00 2001 From: lapuda <577732344@qq.com> Date: Fri, 24 Jul 2026 11:52:34 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9E=E6=88=B7=E5=A4=96?= =?UTF-8?q?=E6=8B=9B=E8=81=98=E4=BC=9A=E4=BC=81=E4=B8=9A=E9=80=80=E8=AE=A2?= =?UTF-8?q?=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/cms/OutdoorFairController.java | 90 +++++++++++++++++++ .../domain/OutdoorFairCompanyUnsubscribe.java | 50 +++++++++++ .../OutdoorFairCompanyUnsubscribeMapper.java | 11 +++ .../cms/OutdoorFairControllerTest.java | 12 +++ ...fair_company_unsubscribe.test-executed.sql | 83 +++++++++++++++++ 5 files changed, 246 insertions(+) create mode 100644 ruoyi-bussiness/src/main/java/com/ruoyi/cms/domain/OutdoorFairCompanyUnsubscribe.java create mode 100644 ruoyi-bussiness/src/main/java/com/ruoyi/cms/mapper/OutdoorFairCompanyUnsubscribeMapper.java create mode 100644 sql/migration_outdoor_fair_company_unsubscribe.test-executed.sql 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 6ef6343..04a12ec 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 @@ -18,11 +18,13 @@ import java.time.temporal.ChronoUnit; import java.time.temporal.TemporalAdjusters; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper; import com.ruoyi.cms.domain.FairCompany; 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.OutdoorFairCompanyUnsubscribe; import com.ruoyi.cms.domain.OutdoorFairDevice; import com.ruoyi.cms.domain.OutdoorFairDictDataRequest; import com.ruoyi.cms.domain.OutdoorFairJob; @@ -32,6 +34,7 @@ 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.OutdoorFairCompanyUnsubscribeMapper; import com.ruoyi.cms.mapper.OutdoorFairJobMapper; import com.ruoyi.cms.mapper.VenueBoothMapper; import com.ruoyi.cms.mapper.CompanyMapper; @@ -91,6 +94,9 @@ public class OutdoorFairController extends BaseController @Autowired private FairCompanyMapper fairCompanyMapper; + @Autowired + private OutdoorFairCompanyUnsubscribeMapper outdoorFairCompanyUnsubscribeMapper; + @Autowired private CompanyMapper companyMapper; @@ -543,6 +549,64 @@ public class OutdoorFairController extends BaseController return success(data); } + @ApiOperation("企业退订户外招聘会") + @PreAuthorize("@ss.hasPermi('cms:outdoorFair:unsubscribe')") + @Log(title = "户外招聘会企业退订", businessType = BusinessType.UPDATE) + @PostMapping("/{fairId}/company/unsubscribe") + @Transactional(rollbackFor = Exception.class) + public AjaxResult unsubscribeCompany(@PathVariable Long fairId, @RequestBody Map request) + { + String reason = request == null || request.get("reason") == null + ? "" : String.valueOf(request.get("reason")).trim(); + if (StringUtils.isBlank(reason)) { + return AjaxResult.error("请填写退订原因"); + } + if (reason.length() > 500) { + return AjaxResult.error("退订原因不能超过500个字"); + } + + Company company = getCurrentCompanyOrThrow(); + ensureFairExists(fairId); + FairCompany relation = fairCompanyMapper.selectOne(new LambdaQueryWrapper() + .eq(FairCompany::getJobFairId, fairId) + .eq(FairCompany::getCompanyId, company.getCompanyId()) + .last("LIMIT 1")); + if (relation == null) { + return AjaxResult.error("该企业未报名当前招聘会"); + } + if (!"1".equals(relation.getReviewStatus())) { + return AjaxResult.error("仅审核通过的报名可以退订"); + } + + OutdoorFairCompanyUnsubscribe record = new OutdoorFairCompanyUnsubscribe(); + record.setFairId(fairId); + record.setCompanyId(company.getCompanyId()); + record.setCompanyName(company.getName()); + record.setContactPerson(company.getContactPerson()); + record.setContactPhone(company.getContactPersonPhone()); + record.setReason(reason); + record.setUnsubscribeTime(new Date()); + record.setCreateBy(getUsername()); + outdoorFairCompanyUnsubscribeMapper.insert(record); + + // 退订无审核流程:保留退订快照后,移除当前招聘会的报名关联及其岗位。 + outdoorFairJobMapper.delete(new LambdaQueryWrapper() + .eq(OutdoorFairJob::getFairId, fairId) + .eq(OutdoorFairJob::getCompanyId, company.getCompanyId())); + outdoorFairBoothBookingMapper.delete(new LambdaQueryWrapper() + .eq(OutdoorFairBoothBooking::getFairId, fairId) + .eq(OutdoorFairBoothBooking::getCompanyId, company.getCompanyId()) + .eq(OutdoorFairBoothBooking::getStatus, "reserved")); + outdoorFairDeviceMapper.update(null, new UpdateWrapper() + .eq("fair_id", fairId) + .eq("company_id", company.getCompanyId()) + .set("company_id", null) + .set("company_name", null) + .set("bind_time", null)); + fairCompanyMapper.deleteById(relation.getId()); + return success(); + } + @ApiOperation("获取当前企业户外招聘会二维码内容") @PreAuthorize("@ss.hasPermi('cms:outdoorFair:list')") @GetMapping("/{fairId}/company/qrcode") @@ -649,6 +713,32 @@ public class OutdoorFairController extends BaseController return result; } + @ApiOperation("查询户外招聘会企业退订记录") + @PreAuthorize("@ss.hasPermi('cms:outdoorFair:query')") + @GetMapping("/{fairId}/company-unsubscribes") + public AjaxResult listCompanyUnsubscribes(@PathVariable Long fairId, String companyName, + Integer current, Integer pageSize) + { + ensureFairExists(fairId); + LambdaQueryWrapper wrapper = + new LambdaQueryWrapper() + .eq(OutdoorFairCompanyUnsubscribe::getFairId, fairId) + .like(StringUtils.isNotBlank(companyName), + OutdoorFairCompanyUnsubscribe::getCompanyName, companyName) + .orderByDesc(OutdoorFairCompanyUnsubscribe::getUnsubscribeTime) + .orderByDesc(OutdoorFairCompanyUnsubscribe::getId); + List rows = outdoorFairCompanyUnsubscribeMapper.selectList(wrapper); + int total = rows.size(); + int page = current == null || current < 1 ? 1 : current; + int size = pageSize == null || pageSize < 1 ? 10 : Math.min(pageSize, 100); + int fromIndex = Math.min((page - 1) * size, total); + int toIndex = Math.min(fromIndex + size, total); + AjaxResult result = AjaxResult.success(); + result.put("rows", rows.subList(fromIndex, toIndex)); + result.put("total", total); + return result; + } + @ApiOperation("获取户外招聘会参会企业二维码内容") @PreAuthorize("@ss.hasPermi('cms:outdoorFair:query')") @GetMapping("/{fairId}/companies/{companyId}/qrcode") diff --git a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/domain/OutdoorFairCompanyUnsubscribe.java b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/domain/OutdoorFairCompanyUnsubscribe.java new file mode 100644 index 0000000..47c673c --- /dev/null +++ b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/domain/OutdoorFairCompanyUnsubscribe.java @@ -0,0 +1,50 @@ +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.fasterxml.jackson.annotation.JsonFormat; +import com.ruoyi.common.core.domain.BaseEntity; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.util.Date; + +/** + * 户外招聘会企业退订记录。 + * + *

退订后报名关联会被移除,本表保留企业提交的退订快照,便于管理端查询和审计。

+ */ +@Data +@ApiModel("户外招聘会企业退订记录") +@TableName("cms_outdoor_fair_company_unsubscribe") +public class OutdoorFairCompanyUnsubscribe 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 companyId; + + @ApiModelProperty("企业名称快照") + private String companyName; + + @ApiModelProperty("联系人快照") + private String contactPerson; + + @ApiModelProperty("联系电话快照") + private String contactPhone; + + @ApiModelProperty("退订原因") + private String reason; + + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + @ApiModelProperty("退订时间") + private Date unsubscribeTime; +} diff --git a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/mapper/OutdoorFairCompanyUnsubscribeMapper.java b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/mapper/OutdoorFairCompanyUnsubscribeMapper.java new file mode 100644 index 0000000..616f5eb --- /dev/null +++ b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/mapper/OutdoorFairCompanyUnsubscribeMapper.java @@ -0,0 +1,11 @@ +package com.ruoyi.cms.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.ruoyi.cms.domain.OutdoorFairCompanyUnsubscribe; + +/** + * 户外招聘会企业退订记录 Mapper。 + */ +public interface OutdoorFairCompanyUnsubscribeMapper extends BaseMapper +{ +} diff --git a/ruoyi-bussiness/src/test/java/com/ruoyi/cms/controller/cms/OutdoorFairControllerTest.java b/ruoyi-bussiness/src/test/java/com/ruoyi/cms/controller/cms/OutdoorFairControllerTest.java index 10580e3..031700b 100644 --- a/ruoyi-bussiness/src/test/java/com/ruoyi/cms/controller/cms/OutdoorFairControllerTest.java +++ b/ruoyi-bussiness/src/test/java/com/ruoyi/cms/controller/cms/OutdoorFairControllerTest.java @@ -24,6 +24,18 @@ class OutdoorFairControllerTest assertEquals("@ss.hasPermi('cms:outdoorFair:qrcode')", annotation.value()); } + @Test + void companyUnsubscribeEndpointRequiresDedicatedPermission() throws Exception + { + Method method = OutdoorFairController.class.getDeclaredMethod( + "unsubscribeCompany", Long.class, java.util.Map.class); + org.springframework.security.access.prepost.PreAuthorize annotation = + method.getAnnotation(org.springframework.security.access.prepost.PreAuthorize.class); + + assertNotNull(annotation); + assertEquals("@ss.hasPermi('cms:outdoorFair:unsubscribe')", annotation.value()); + } + @Test void companyQrCodeUrlUsesPublicForwardedHost() throws Exception { diff --git a/sql/migration_outdoor_fair_company_unsubscribe.test-executed.sql b/sql/migration_outdoor_fair_company_unsubscribe.test-executed.sql new file mode 100644 index 0000000..3de22e3 --- /dev/null +++ b/sql/migration_outdoor_fair_company_unsubscribe.test-executed.sql @@ -0,0 +1,83 @@ +-- 户外招聘会企业退订:企业退订后移除参会关系,并保留退订快照。 +-- 瀚高数据库 / PostgreSQL 兼容;可重复执行。 +-- 执行状态:测试环境已执行 +-- 测试环境:test +-- 测试执行时间:2026-07-24 11:46:48 +0800 +-- 正式环境:未执行 + +BEGIN; + +SET LOCAL search_path = shz, public; + +CREATE SEQUENCE IF NOT EXISTS "shz"."cms_outdoor_fair_company_unsubscribe_id_seq" + START WITH 1 INCREMENT BY 1 NO MINVALUE NO MAXVALUE CACHE 1; + +CREATE TABLE IF NOT EXISTS "shz"."cms_outdoor_fair_company_unsubscribe" ( + "id" BIGINT NOT NULL DEFAULT nextval('"shz"."cms_outdoor_fair_company_unsubscribe_id_seq"'::regclass) PRIMARY KEY, + "fair_id" BIGINT NOT NULL, + "company_id" BIGINT NOT NULL, + "company_name" VARCHAR(255) NOT NULL, + "contact_person" VARCHAR(64), + "contact_phone" VARCHAR(64), + "reason" VARCHAR(500) NOT NULL, + "unsubscribe_time" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + "create_by" VARCHAR(64) DEFAULT '', + "create_time" TIMESTAMP, + "update_by" VARCHAR(64) DEFAULT '', + "update_time" TIMESTAMP, + "remark" VARCHAR(500), + "del_flag" CHAR(1) DEFAULT '0' +); + +COMMENT ON TABLE "shz"."cms_outdoor_fair_company_unsubscribe" IS '户外招聘会企业退订记录表'; +COMMENT ON COLUMN "shz"."cms_outdoor_fair_company_unsubscribe"."fair_id" IS '招聘会ID'; +COMMENT ON COLUMN "shz"."cms_outdoor_fair_company_unsubscribe"."company_id" IS '企业ID'; +COMMENT ON COLUMN "shz"."cms_outdoor_fair_company_unsubscribe"."company_name" IS '企业名称快照'; +COMMENT ON COLUMN "shz"."cms_outdoor_fair_company_unsubscribe"."contact_person" IS '联系人快照'; +COMMENT ON COLUMN "shz"."cms_outdoor_fair_company_unsubscribe"."contact_phone" IS '联系电话快照'; +COMMENT ON COLUMN "shz"."cms_outdoor_fair_company_unsubscribe"."reason" IS '退订原因'; +COMMENT ON COLUMN "shz"."cms_outdoor_fair_company_unsubscribe"."unsubscribe_time" IS '退订时间'; +COMMENT ON COLUMN "shz"."cms_outdoor_fair_company_unsubscribe"."del_flag" IS '删除标志(0代表存在 2代表删除)'; + +CREATE INDEX IF NOT EXISTS "idx_cms_outdoor_fair_company_unsubscribe_fair_time" + ON "shz"."cms_outdoor_fair_company_unsubscribe" ("fair_id", "unsubscribe_time" DESC); +CREATE INDEX IF NOT EXISTS "idx_cms_outdoor_fair_company_unsubscribe_company" + ON "shz"."cms_outdoor_fair_company_unsubscribe" ("company_id"); + +-- 企业端退订按钮权限。授权给已有户外招聘会列表权限的角色,覆盖企业角色和管理角色。 +INSERT INTO "shz"."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", "update_by", + "update_time", "remark" +) +SELECT + (SELECT COALESCE(MAX("menu_id"), 0) + 1 FROM "shz"."sys_menu"), + '企业退订', list_menu."menu_id", 5, '', '', '', '', + 1, 0, 'F', '0', '0', 'cms:outdoorFair:unsubscribe', '#', 'system', CURRENT_TIMESTAMP, + '', NULL, '户外招聘会企业端退订按钮权限' +FROM "shz"."sys_menu" list_menu +WHERE list_menu."perms" = 'cms:outdoorFair:list' + AND NOT EXISTS ( + SELECT 1 FROM "shz"."sys_menu" + WHERE "perms" = 'cms:outdoorFair:unsubscribe' + ) +ORDER BY list_menu."menu_id" +LIMIT 1; + +INSERT INTO "shz"."sys_role_menu" ("role_id", "menu_id") +SELECT DISTINCT role_menu."role_id", unsubscribe_menu."menu_id" +FROM "shz"."sys_role_menu" role_menu +JOIN "shz"."sys_menu" list_menu + ON list_menu."menu_id" = role_menu."menu_id" +JOIN "shz"."sys_menu" unsubscribe_menu + ON unsubscribe_menu."perms" = 'cms:outdoorFair:unsubscribe' +WHERE list_menu."perms" = 'cms:outdoorFair:list' + AND NOT EXISTS ( + SELECT 1 + FROM "shz"."sys_role_menu" existing + WHERE existing."role_id" = role_menu."role_id" + AND existing."menu_id" = unsubscribe_menu."menu_id" + ); + +COMMIT;