diff --git a/docs/mobile-notice-settings-api.md b/docs/mobile-notice-settings-api.md new file mode 100644 index 0000000..eca2bbd --- /dev/null +++ b/docs/mobile-notice-settings-api.md @@ -0,0 +1,64 @@ +# 移动端消息接收设置接口 + +## 通用约定 + +- 基础地址:沿用移动端当前后端地址。 +- 请求头:`Authorization: Bearer <移动端求职者登录 Token>`。 +- 仅移动端**求职者**可调用;未携带或失效 Token 返回 HTTP 401,企业及其他角色返回业务码 `403`。 +- 四个字段均为布尔值,`true` 表示接收该类消息,`false` 表示不接收。保存时必须一次传齐四项。 + +## 查看设置 + +`GET /app/notice/settings` + +无需请求参数或请求体。首次查看(尚未保存过)会返回四项均开启的默认设置,且不会创建数据库记录。 + +成功响应示例: + +```json +{ + "code": 200, + "msg": "操作成功", + "data": { + "jobUpdateEnabled": true, + "appointmentReminderEnabled": true, + "systemNotificationEnabled": true, + "interviewNotificationEnabled": true + } +} +``` + +字段说明: + +| 字段 | 对应按钮 | +| --- | --- | +| `jobUpdateEnabled` | 职位上新 | +| `appointmentReminderEnabled` | 预约提醒 | +| `systemNotificationEnabled` | 系统通知 | +| `interviewNotificationEnabled` | 面试通知 | + +## 保存设置 + +`PUT /app/notice/settings` + +请求头 `Content-Type: application/json`,请求体示例: + +```json +{ + "jobUpdateEnabled": true, + "appointmentReminderEnabled": false, + "systemNotificationEnabled": true, + "interviewNotificationEnabled": false +} +``` + +成功响应: + +```json +{ + "code": 200, + "msg": "操作成功" +} +``` + +接口按登录 Token 自动识别当前用户,不接收也不允许客户端传入用户 ID。任一开关字段缺失或为 `null` 时,返回参数校验错误。 diff --git a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/controller/app/AppNoticeInfoController.java b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/controller/app/AppNoticeInfoController.java index 6a913a3..4c13af0 100644 --- a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/controller/app/AppNoticeInfoController.java +++ b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/controller/app/AppNoticeInfoController.java @@ -2,16 +2,27 @@ package com.ruoyi.cms.controller.app; import com.ruoyi.cms.domain.Job; import com.ruoyi.cms.domain.Notice; +import com.ruoyi.cms.domain.dto.NoticeSettingRequest; +import com.ruoyi.cms.domain.vo.NoticeSettingVO; import com.ruoyi.cms.service.IAppNoticeService; +import com.ruoyi.cms.service.IAppUserNoticeSettingService; import com.ruoyi.common.annotation.BussinessLog; +import com.ruoyi.common.constant.HttpStatus; import com.ruoyi.common.core.controller.BaseController; import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.core.domain.entity.AppUser; +import com.ruoyi.common.core.domain.model.LoginSiteUser; import com.ruoyi.common.core.page.TableDataInfo; +import com.ruoyi.cms.util.StringUtil; +import com.ruoyi.common.utils.SiteSecurityUtils; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; +import org.springframework.security.core.Authentication; + import java.util.List; @RestController @@ -21,6 +32,35 @@ public class AppNoticeInfoController extends BaseController { @Autowired private IAppNoticeService appNoticeService; + @Autowired + private IAppUserNoticeSettingService appUserNoticeSettingService; + + @ApiOperation("查看消息接收设置") + @GetMapping("/settings") + public AjaxResult getSettings() + { + Long userId = currentJobSeekerId(); + if (userId == null) + { + return AjaxResult.error(HttpStatus.FORBIDDEN, "仅求职者可查看消息接收设置"); + } + NoticeSettingVO settings = appUserNoticeSettingService.getSettings(userId); + return AjaxResult.success(settings); + } + + @ApiOperation("保存消息接收设置") + @PutMapping("/settings") + @BussinessLog(title = "保存消息接收设置") + public AjaxResult saveSettings(@Validated @RequestBody NoticeSettingRequest request) + { + Long userId = currentJobSeekerId(); + if (userId == null) + { + return AjaxResult.error(HttpStatus.FORBIDDEN, "仅求职者可保存消息接收设置"); + } + appUserNoticeSettingService.saveSettings(userId, request); + return AjaxResult.success(); + } @ApiOperation("消息列表") @GetMapping("/info") public AjaxResult listNotRead(@RequestParam(required = false) Integer isRead) @@ -60,4 +100,24 @@ public class AppNoticeInfoController extends BaseController { List list = appNoticeService.sysNoticeList(); return getDataTable(list); } + + /** + * 仅允许经移动端 Token 认证的求职者读写自己的设置。 + */ + private Long currentJobSeekerId() + { + Authentication authentication = SiteSecurityUtils.getAuthentication(); + if (authentication == null || !(authentication.getPrincipal() instanceof LoginSiteUser)) + { + return null; + } + LoginSiteUser loginUser = (LoginSiteUser) authentication.getPrincipal(); + AppUser appUser = loginUser.getUser(); + if (loginUser.getUserId() == null || appUser == null + || !StringUtil.IS_JOB_REQUEST_USER.equals(appUser.getIsCompanyUser())) + { + return null; + } + return loginUser.getUserId(); + } } diff --git a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/domain/AppUserNoticeSetting.java b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/domain/AppUserNoticeSetting.java new file mode 100644 index 0000000..c49d776 --- /dev/null +++ b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/domain/AppUserNoticeSetting.java @@ -0,0 +1,35 @@ +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 lombok.Data; + +/** + * 移动端求职者消息接收设置。 + */ +@Data +@ApiModel("移动端求职者消息接收设置") +@TableName("app_user_notice_setting") +public class AppUserNoticeSetting extends BaseEntity +{ + @TableId(type = IdType.AUTO) + private Long id; + + /** APP 用户 ID,每名用户仅有一条设置。 */ + private Long userId; + + /** 职位上新。 */ + private Boolean jobUpdateEnabled; + + /** 招聘会预约提醒。 */ + private Boolean appointmentReminderEnabled; + + /** 平台系统通知。 */ + private Boolean systemNotificationEnabled; + + /** 企业面试通知。 */ + private Boolean interviewNotificationEnabled; +} diff --git a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/domain/dto/NoticeSettingRequest.java b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/domain/dto/NoticeSettingRequest.java new file mode 100644 index 0000000..4b7a3bf --- /dev/null +++ b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/domain/dto/NoticeSettingRequest.java @@ -0,0 +1,31 @@ +package com.ruoyi.cms.domain.dto; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import javax.validation.constraints.NotNull; + +/** + * 移动端消息接收设置保存请求。 + */ +@Data +@ApiModel("移动端消息接收设置保存请求") +public class NoticeSettingRequest +{ + @NotNull(message = "职位上新开关不能为空") + @ApiModelProperty(value = "是否接收职位上新通知", required = true, example = "true") + private Boolean jobUpdateEnabled; + + @NotNull(message = "预约提醒开关不能为空") + @ApiModelProperty(value = "是否接收招聘会预约提醒", required = true, example = "true") + private Boolean appointmentReminderEnabled; + + @NotNull(message = "系统通知开关不能为空") + @ApiModelProperty(value = "是否接收系统通知", required = true, example = "true") + private Boolean systemNotificationEnabled; + + @NotNull(message = "面试通知开关不能为空") + @ApiModelProperty(value = "是否接收面试通知", required = true, example = "true") + private Boolean interviewNotificationEnabled; +} diff --git a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/domain/vo/NoticeSettingVO.java b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/domain/vo/NoticeSettingVO.java new file mode 100644 index 0000000..b94694a --- /dev/null +++ b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/domain/vo/NoticeSettingVO.java @@ -0,0 +1,36 @@ +package com.ruoyi.cms.domain.vo; + +import com.ruoyi.cms.domain.AppUserNoticeSetting; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +/** + * 移动端消息接收设置返回对象。 + */ +@Data +@ApiModel("移动端消息接收设置") +public class NoticeSettingVO +{ + @ApiModelProperty(value = "是否接收职位上新通知", example = "true") + private Boolean jobUpdateEnabled; + + @ApiModelProperty(value = "是否接收招聘会预约提醒", example = "true") + private Boolean appointmentReminderEnabled; + + @ApiModelProperty(value = "是否接收系统通知", example = "true") + private Boolean systemNotificationEnabled; + + @ApiModelProperty(value = "是否接收面试通知", example = "true") + private Boolean interviewNotificationEnabled; + + public static NoticeSettingVO from(AppUserNoticeSetting setting) + { + NoticeSettingVO result = new NoticeSettingVO(); + result.setJobUpdateEnabled(setting.getJobUpdateEnabled()); + result.setAppointmentReminderEnabled(setting.getAppointmentReminderEnabled()); + result.setSystemNotificationEnabled(setting.getSystemNotificationEnabled()); + result.setInterviewNotificationEnabled(setting.getInterviewNotificationEnabled()); + return result; + } +} diff --git a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/mapper/AppUserNoticeSettingMapper.java b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/mapper/AppUserNoticeSettingMapper.java new file mode 100644 index 0000000..3a5a55a --- /dev/null +++ b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/mapper/AppUserNoticeSettingMapper.java @@ -0,0 +1,15 @@ +package com.ruoyi.cms.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.ruoyi.cms.domain.AppUserNoticeSetting; + +/** + * 移动端用户消息接收设置 Mapper。 + */ +public interface AppUserNoticeSettingMapper extends BaseMapper +{ + /** + * 保存设置。user_id 唯一约束保证并发首次保存时只会保留一条记录。 + */ + int upsert(AppUserNoticeSetting setting); +} diff --git a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/service/IAppUserNoticeSettingService.java b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/service/IAppUserNoticeSettingService.java new file mode 100644 index 0000000..ad219bc --- /dev/null +++ b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/service/IAppUserNoticeSettingService.java @@ -0,0 +1,20 @@ +package com.ruoyi.cms.service; + +import com.ruoyi.cms.domain.dto.NoticeSettingRequest; +import com.ruoyi.cms.domain.vo.NoticeSettingVO; + +/** + * 移动端求职者消息接收设置服务。 + */ +public interface IAppUserNoticeSettingService +{ + /** + * 查询用户设置;尚未保存过时返回四项均开启的默认设置。 + */ + NoticeSettingVO getSettings(Long userId); + + /** + * 保存用户的完整四项设置。 + */ + void saveSettings(Long userId, NoticeSettingRequest request); +} diff --git a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/service/impl/AppUserNoticeSettingServiceImpl.java b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/service/impl/AppUserNoticeSettingServiceImpl.java new file mode 100644 index 0000000..9adfc84 --- /dev/null +++ b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/service/impl/AppUserNoticeSettingServiceImpl.java @@ -0,0 +1,62 @@ +package com.ruoyi.cms.service.impl; + +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.ruoyi.cms.domain.AppUserNoticeSetting; +import com.ruoyi.cms.domain.dto.NoticeSettingRequest; +import com.ruoyi.cms.domain.vo.NoticeSettingVO; +import com.ruoyi.cms.mapper.AppUserNoticeSettingMapper; +import com.ruoyi.cms.service.IAppUserNoticeSettingService; +import org.springframework.stereotype.Service; + +/** + * 移动端求职者消息接收设置服务实现。 + */ +@Service +public class AppUserNoticeSettingServiceImpl + extends ServiceImpl + implements IAppUserNoticeSettingService +{ + private final AppUserNoticeSettingMapper noticeSettingMapper; + + public AppUserNoticeSettingServiceImpl(AppUserNoticeSettingMapper noticeSettingMapper) + { + this.noticeSettingMapper = noticeSettingMapper; + } + + @Override + public NoticeSettingVO getSettings(Long userId) + { + AppUserNoticeSetting setting = noticeSettingMapper.selectOne( + Wrappers.lambdaQuery() + .eq(AppUserNoticeSetting::getUserId, userId)); + if (setting == null) + { + setting = defaultSetting(userId); + } + return NoticeSettingVO.from(setting); + } + + @Override + public void saveSettings(Long userId, NoticeSettingRequest request) + { + AppUserNoticeSetting setting = new AppUserNoticeSetting(); + setting.setUserId(userId); + setting.setJobUpdateEnabled(request.getJobUpdateEnabled()); + setting.setAppointmentReminderEnabled(request.getAppointmentReminderEnabled()); + setting.setSystemNotificationEnabled(request.getSystemNotificationEnabled()); + setting.setInterviewNotificationEnabled(request.getInterviewNotificationEnabled()); + noticeSettingMapper.upsert(setting); + } + + private AppUserNoticeSetting defaultSetting(Long userId) + { + AppUserNoticeSetting setting = new AppUserNoticeSetting(); + setting.setUserId(userId); + setting.setJobUpdateEnabled(Boolean.TRUE); + setting.setAppointmentReminderEnabled(Boolean.TRUE); + setting.setSystemNotificationEnabled(Boolean.TRUE); + setting.setInterviewNotificationEnabled(Boolean.TRUE); + return setting; + } +} diff --git a/ruoyi-bussiness/src/main/resources/mapper/app/AppUserNoticeSettingMapper.xml b/ruoyi-bussiness/src/main/resources/mapper/app/AppUserNoticeSettingMapper.xml new file mode 100644 index 0000000..246005f --- /dev/null +++ b/ruoyi-bussiness/src/main/resources/mapper/app/AppUserNoticeSettingMapper.xml @@ -0,0 +1,24 @@ + + + + + + INSERT INTO app_user_notice_setting + (user_id, job_update_enabled, appointment_reminder_enabled, + system_notification_enabled, interview_notification_enabled, + create_time, update_time, del_flag) + VALUES + (#{userId}, #{jobUpdateEnabled}, #{appointmentReminderEnabled}, + #{systemNotificationEnabled}, #{interviewNotificationEnabled}, + NOW(), NOW(), '0') + ON CONFLICT (user_id) DO UPDATE + SET job_update_enabled = EXCLUDED.job_update_enabled, + appointment_reminder_enabled = EXCLUDED.appointment_reminder_enabled, + system_notification_enabled = EXCLUDED.system_notification_enabled, + interview_notification_enabled = EXCLUDED.interview_notification_enabled, + update_time = NOW(), + del_flag = '0' + + diff --git a/ruoyi-bussiness/src/test/java/com/ruoyi/cms/controller/app/AppNoticeInfoControllerTest.java b/ruoyi-bussiness/src/test/java/com/ruoyi/cms/controller/app/AppNoticeInfoControllerTest.java new file mode 100644 index 0000000..6534f28 --- /dev/null +++ b/ruoyi-bussiness/src/test/java/com/ruoyi/cms/controller/app/AppNoticeInfoControllerTest.java @@ -0,0 +1,89 @@ +package com.ruoyi.cms.controller.app; + +import com.ruoyi.cms.domain.dto.NoticeSettingRequest; +import com.ruoyi.cms.domain.vo.NoticeSettingVO; +import com.ruoyi.cms.service.IAppUserNoticeSettingService; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.core.domain.entity.AppUser; +import com.ruoyi.common.core.domain.model.LoginSiteUser; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.context.SecurityContextHolder; + +import java.lang.reflect.Field; +import java.util.Collections; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +class AppNoticeInfoControllerTest +{ + @AfterEach + void clearSecurityContext() + { + SecurityContextHolder.clearContext(); + } + + @Test + void jobSeekerCanReadOwnSettings() throws Exception + { + IAppUserNoticeSettingService service = mock(IAppUserNoticeSettingService.class); + NoticeSettingVO expected = new NoticeSettingVO(); + expected.setJobUpdateEnabled(true); + expected.setAppointmentReminderEnabled(true); + expected.setSystemNotificationEnabled(false); + expected.setInterviewNotificationEnabled(true); + when(service.getSettings(12L)).thenReturn(expected); + authenticateSiteUser(12L, "1"); + + AppNoticeInfoController controller = controllerWith(service); + AjaxResult result = controller.getSettings(); + + assertEquals(200, result.get(AjaxResult.CODE_TAG)); + assertEquals(expected, result.get(AjaxResult.DATA_TAG)); + verify(service).getSettings(12L); + } + + @Test + void nonJobSeekerCannotSaveSettings() throws Exception + { + IAppUserNoticeSettingService service = mock(IAppUserNoticeSettingService.class); + authenticateSiteUser(12L, "0"); + NoticeSettingRequest request = new NoticeSettingRequest(); + request.setJobUpdateEnabled(true); + request.setAppointmentReminderEnabled(true); + request.setSystemNotificationEnabled(true); + request.setInterviewNotificationEnabled(true); + + AppNoticeInfoController controller = controllerWith(service); + AjaxResult result = controller.saveSettings(request); + + assertEquals(403, result.get(AjaxResult.CODE_TAG)); + verify(service, never()).saveSettings(eq(12L), any(NoticeSettingRequest.class)); + } + + private AppNoticeInfoController controllerWith(IAppUserNoticeSettingService service) throws Exception + { + AppNoticeInfoController controller = new AppNoticeInfoController(); + Field field = AppNoticeInfoController.class.getDeclaredField("appUserNoticeSettingService"); + field.setAccessible(true); + field.set(controller, service); + return controller; + } + + private void authenticateSiteUser(Long userId, String userType) + { + AppUser appUser = new AppUser(); + appUser.setIsCompanyUser(userType); + LoginSiteUser loginUser = new LoginSiteUser(userId, appUser); + UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken( + loginUser, null, Collections.emptyList()); + SecurityContextHolder.getContext().setAuthentication(authentication); + } +} diff --git a/ruoyi-bussiness/src/test/java/com/ruoyi/cms/service/impl/AppUserNoticeSettingServiceImplTest.java b/ruoyi-bussiness/src/test/java/com/ruoyi/cms/service/impl/AppUserNoticeSettingServiceImplTest.java new file mode 100644 index 0000000..8b94e26 --- /dev/null +++ b/ruoyi-bussiness/src/test/java/com/ruoyi/cms/service/impl/AppUserNoticeSettingServiceImplTest.java @@ -0,0 +1,77 @@ +package com.ruoyi.cms.service.impl; + +import com.ruoyi.cms.domain.AppUserNoticeSetting; +import com.ruoyi.cms.domain.dto.NoticeSettingRequest; +import com.ruoyi.cms.domain.vo.NoticeSettingVO; +import com.ruoyi.cms.mapper.AppUserNoticeSettingMapper; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +class AppUserNoticeSettingServiceImplTest +{ + @Test + void returnsAllEnabledDefaultsWhenUserHasNotSavedSettings() + { + AppUserNoticeSettingMapper mapper = mock(AppUserNoticeSettingMapper.class); + when(mapper.selectOne(any())).thenReturn(null); + AppUserNoticeSettingServiceImpl service = new AppUserNoticeSettingServiceImpl(mapper); + + NoticeSettingVO result = service.getSettings(12L); + + assertTrue(result.getJobUpdateEnabled()); + assertTrue(result.getAppointmentReminderEnabled()); + assertTrue(result.getSystemNotificationEnabled()); + assertTrue(result.getInterviewNotificationEnabled()); + } + + @Test + void returnsPersistedSettingsForCurrentUser() + { + AppUserNoticeSettingMapper mapper = mock(AppUserNoticeSettingMapper.class); + AppUserNoticeSetting setting = new AppUserNoticeSetting(); + setting.setJobUpdateEnabled(false); + setting.setAppointmentReminderEnabled(true); + setting.setSystemNotificationEnabled(false); + setting.setInterviewNotificationEnabled(true); + when(mapper.selectOne(any())).thenReturn(setting); + AppUserNoticeSettingServiceImpl service = new AppUserNoticeSettingServiceImpl(mapper); + + NoticeSettingVO result = service.getSettings(12L); + + assertFalse(result.getJobUpdateEnabled()); + assertTrue(result.getAppointmentReminderEnabled()); + assertFalse(result.getSystemNotificationEnabled()); + assertTrue(result.getInterviewNotificationEnabled()); + } + + @Test + void savesAllFourSettingsForTheAuthenticatedUser() + { + AppUserNoticeSettingMapper mapper = mock(AppUserNoticeSettingMapper.class); + AppUserNoticeSettingServiceImpl service = new AppUserNoticeSettingServiceImpl(mapper); + NoticeSettingRequest request = new NoticeSettingRequest(); + request.setJobUpdateEnabled(true); + request.setAppointmentReminderEnabled(false); + request.setSystemNotificationEnabled(true); + request.setInterviewNotificationEnabled(false); + + service.saveSettings(12L, request); + + ArgumentCaptor captor = ArgumentCaptor.forClass(AppUserNoticeSetting.class); + verify(mapper).upsert(captor.capture()); + AppUserNoticeSetting saved = captor.getValue(); + assertEquals(12L, saved.getUserId()); + assertTrue(saved.getJobUpdateEnabled()); + assertFalse(saved.getAppointmentReminderEnabled()); + assertTrue(saved.getSystemNotificationEnabled()); + assertFalse(saved.getInterviewNotificationEnabled()); + } +} diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/config/SecurityConfig.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/SecurityConfig.java index dcb734c..d3ebfd4 100644 --- a/ruoyi-framework/src/main/java/com/ruoyi/framework/config/SecurityConfig.java +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/SecurityConfig.java @@ -118,6 +118,9 @@ public class SecurityConfig "/cms/policyInfo/detail","/cms/policyInfo/list","/cms/industry/treeselect").permitAll() // 静态资源,可匿名访问 .antMatchers(HttpMethod.GET, "/", "/*.html", "/**/*.html", "/**/*.css", "/**/*.js", "/profile/**").permitAll() + // 移动端消息接收设置包含用户个人偏好,必须使用站点 Token 访问。 + // 该规则需位于 /app/** 的公共查询规则之前。 + .antMatchers("/app/notice/settings").authenticated() // 移动端公用查询,可匿名访问 .antMatchers("/app/common/**").permitAll() .antMatchers("/app/**").permitAll() diff --git a/sql/migration_app_user_notice_setting.sql b/sql/migration_app_user_notice_setting.sql new file mode 100644 index 0000000..86466fb --- /dev/null +++ b/sql/migration_app_user_notice_setting.sql @@ -0,0 +1,30 @@ +-- 移动端求职者消息接收设置。 +-- 瀚高 / PostgreSQL 兼容;重复执行安全。表放在 shz schema,应用通过 search_path 访问。 + +CREATE SEQUENCE IF NOT EXISTS shz.app_user_notice_setting_id_seq + START WITH 1 INCREMENT BY 1 NO MINVALUE NO MAXVALUE CACHE 1; + +CREATE TABLE IF NOT EXISTS shz.app_user_notice_setting ( + id BIGINT NOT NULL DEFAULT nextval('shz.app_user_notice_setting_id_seq'::regclass) PRIMARY KEY, + user_id BIGINT NOT NULL, + job_update_enabled BOOLEAN NOT NULL DEFAULT TRUE, + appointment_reminder_enabled BOOLEAN NOT NULL DEFAULT TRUE, + system_notification_enabled BOOLEAN NOT NULL DEFAULT TRUE, + interview_notification_enabled BOOLEAN NOT NULL DEFAULT TRUE, + create_by VARCHAR(64) DEFAULT '', + create_time TIMESTAMP(0), + update_by VARCHAR(64) DEFAULT '', + update_time TIMESTAMP(0), + remark VARCHAR(500), + del_flag CHAR(1) DEFAULT '0' +); + +CREATE UNIQUE INDEX IF NOT EXISTS uk_app_user_notice_setting_user_id + ON shz.app_user_notice_setting (user_id); + +COMMENT ON TABLE shz.app_user_notice_setting IS '移动端求职者消息接收设置'; +COMMENT ON COLUMN shz.app_user_notice_setting.user_id IS 'APP 用户 ID,每用户唯一'; +COMMENT ON COLUMN shz.app_user_notice_setting.job_update_enabled IS '是否接收职位上新通知'; +COMMENT ON COLUMN shz.app_user_notice_setting.appointment_reminder_enabled IS '是否接收招聘会预约提醒'; +COMMENT ON COLUMN shz.app_user_notice_setting.system_notification_enabled IS '是否接收系统通知'; +COMMENT ON COLUMN shz.app_user_notice_setting.interview_notification_enabled IS '是否接收面试通知';