Browse Source

feat(chat): 实现消息实时推送和学员收藏功能

- 集成 Spring WebSocket 消息模板实现聊天消息实时推送
- 在消息发送、图片上传、文件上传和岗位推荐场景下推送实时消息
- 添加学员意向公司字段支持求职偏好管理
- 实现学员收藏服务接口定义和业务逻辑
- 创建学员收藏数据表结构和映射关系
- 开发小程序收藏控制器提供收藏/取消/查询接口
- 完善学员信息查询时头像URL的转换映射配置
西格玛许 1 tuần trước cách đây
mục cha
commit
13467aa490

+ 61 - 0
ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/controller/miniapp/MiniappCollectionController.java

@@ -0,0 +1,61 @@
+package org.dromara.main.controller.miniapp;
+
+import cn.dev33.satoken.annotation.SaIgnore;
+import lombok.RequiredArgsConstructor;
+import org.dromara.common.core.domain.R;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.common.web.core.BaseController;
+import org.dromara.main.domain.bo.MainStudentCollectionBo;
+import org.dromara.main.domain.vo.MainStudentCollectionVo;
+import org.dromara.main.service.IMainStudentCollectionService;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+/**
+ * 小程序收藏接口
+ */
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/miniapp/collection")
+public class MiniappCollectionController extends BaseController {
+
+    private final IMainStudentCollectionService collectionService;
+
+    /**
+     * 查询收藏列表
+     */
+    @SaIgnore
+    @GetMapping("/list")
+    public TableDataInfo<MainStudentCollectionVo> list(MainStudentCollectionBo bo, PageQuery pageQuery) {
+        return collectionService.queryPageList(bo, pageQuery);
+    }
+
+    /**
+     * 添加收藏
+     */
+    @SaIgnore
+    @PostMapping
+    public R<Void> add(@RequestBody MainStudentCollectionBo bo) {
+        return toAjax(collectionService.insertByBo(bo));
+    }
+
+    /**
+     * 取消收藏
+     */
+    @SaIgnore
+    @DeleteMapping("/{id}")
+    public R<Void> remove(@PathVariable Long id) {
+        return toAjax(collectionService.deleteById(id));
+    }
+
+    /**
+     * 检查是否已收藏
+     */
+    @SaIgnore
+    @GetMapping("/check")
+    public R<MainStudentCollectionVo> check(Long studentId, Long targetId, String type) {
+        return R.ok(collectionService.checkCollection(studentId, targetId, type));
+    }
+}

+ 5 - 0
ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/domain/MainStudent.java

@@ -78,6 +78,11 @@ public class MainStudent extends BaseEntity {
      */
      */
     private String jobIntention;
     private String jobIntention;
 
 
+    /**
+     * 意向公司列表(公司名称,逗号分隔)
+     */
+    private String intentionCompanies;
+
     /**
     /**
      * 建议类型(1全职 2实习 3兼职)
      * 建议类型(1全职 2实习 3兼职)
      */
      */

+ 45 - 0
ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/domain/MainStudentCollection.java

@@ -0,0 +1,45 @@
+package org.dromara.main.domain;
+
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.dromara.common.mybatis.core.domain.BaseEntity;
+
+import java.io.Serial;
+
+/**
+ * 学员收藏对象 main_student_collection
+ *
+ * @author Cascade
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@TableName("main_student_collection")
+public class MainStudentCollection extends BaseEntity {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 主键
+     */
+    @TableId(value = "id")
+    private Long id;
+
+    /**
+     * 学员ID
+     */
+    private Long studentId;
+
+    /**
+     * 收藏目标ID(岗位ID/测评ID)
+     */
+    private Long targetId;
+
+    /**
+     * 收藏类型(job:岗位, assessment:测评)
+     */
+    private String type;
+
+}

+ 5 - 0
ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/domain/bo/MainStudentBo.java

@@ -74,6 +74,11 @@ public class MainStudentBo extends BaseEntity {
      */
      */
     private String jobIntention;
     private String jobIntention;
 
 
+    /**
+     * 意向公司列表(公司名称,逗号分隔)
+     */
+    private String intentionCompanies;
+
     /**
     /**
      * 建议类型(1全职 2实习 3兼职)
      * 建议类型(1全职 2实习 3兼职)
      */
      */

+ 39 - 0
ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/domain/bo/MainStudentCollectionBo.java

@@ -0,0 +1,39 @@
+package org.dromara.main.domain.bo;
+
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.dromara.common.mybatis.core.domain.BaseEntity;
+
+import java.io.Serial;
+
+/**
+ * 学员收藏业务对象 main_student_collection
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class MainStudentCollectionBo extends BaseEntity {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 主键
+     */
+    private Long id;
+
+    /**
+     * 学员ID
+     */
+    private Long studentId;
+
+    /**
+     * 收藏目标ID(岗位ID/测评ID)
+     */
+    private Long targetId;
+
+    /**
+     * 收藏类型(job:岗位, assessment:测评)
+     */
+    private String type;
+
+}

+ 47 - 0
ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/domain/vo/MainStudentCollectionVo.java

@@ -0,0 +1,47 @@
+package org.dromara.main.domain.vo;
+
+import io.github.linpeilie.annotations.AutoMapper;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.dromara.common.mybatis.core.domain.BaseEntity;
+import org.dromara.main.domain.MainStudentCollection;
+
+import java.io.Serial;
+
+/**
+ * 学员收藏视图对象 main_student_collection
+ */
+@Data
+@AutoMapper(target = MainStudentCollection.class)
+@EqualsAndHashCode(callSuper = true)
+public class MainStudentCollectionVo extends BaseEntity {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 主键
+     */
+    private Long id;
+
+    /**
+     * 学员ID
+     */
+    private Long studentId;
+
+    /**
+     * 收藏目标ID(岗位ID/测评ID)
+     */
+    private Long targetId;
+
+    /**
+     * 收藏类型(job:岗位, assessment:测评)
+     */
+    private String type;
+
+    /**
+     * 目标详情数据(岗位详情或测评详情)
+     */
+    private Object targetData;
+
+}

+ 6 - 1
ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/domain/vo/MainStudentVo.java

@@ -66,7 +66,7 @@ public class MainStudentVo implements Serializable {
      */
      */
     private Long avatar;
     private Long avatar;
 
 
-    @Translation(type=TransConstant.OSS_ID_TO_URL)
+    @Translation(type=TransConstant.OSS_ID_TO_URL,mapper="avatar")
     private String avatarUrl;
     private String avatarUrl;
 
 
     /**
     /**
@@ -92,6 +92,11 @@ public class MainStudentVo implements Serializable {
      */
      */
     private String jobIntention;
     private String jobIntention;
 
 
+    /**
+     * 意向公司列表(公司名称,逗号分隔)
+     */
+    private String intentionCompanies;
+
     /**
     /**
      * 建议类型(1全职 2实习 3兼职)
      * 建议类型(1全职 2实习 3兼职)
      */
      */

+ 12 - 0
ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/mapper/MainStudentCollectionMapper.java

@@ -0,0 +1,12 @@
+package org.dromara.main.mapper;
+
+import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
+import org.dromara.main.domain.MainStudentCollection;
+import org.dromara.main.domain.vo.MainStudentCollectionVo;
+
+/**
+ * 学员收藏Mapper接口
+ */
+public interface MainStudentCollectionMapper extends BaseMapperPlus<MainStudentCollection, MainStudentCollectionVo> {
+
+}

+ 32 - 0
ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/service/IMainStudentCollectionService.java

@@ -0,0 +1,32 @@
+package org.dromara.main.service;
+
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.main.domain.bo.MainStudentCollectionBo;
+import org.dromara.main.domain.vo.MainStudentCollectionVo;
+
+/**
+ * 学员收藏服务接口
+ */
+public interface IMainStudentCollectionService {
+
+    /**
+     * 查询学员收藏列表
+     */
+    TableDataInfo<MainStudentCollectionVo> queryPageList(MainStudentCollectionBo bo, PageQuery pageQuery);
+
+    /**
+     * 新增学员收藏
+     */
+    Boolean insertByBo(MainStudentCollectionBo bo);
+
+    /**
+     * 取消收藏
+     */
+    Boolean deleteById(Long id);
+
+    /**
+     * 检查是否已收藏
+     */
+    MainStudentCollectionVo checkCollection(Long studentId, Long targetId, String type);
+}

+ 14 - 4
ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/service/impl/CsMessageServiceImpl.java

@@ -24,6 +24,7 @@ import org.dromara.main.mapper.CsSessionMapper;
 import org.dromara.main.mapper.MainCompanyApplyMapper;
 import org.dromara.main.mapper.MainCompanyApplyMapper;
 import org.dromara.main.service.ICsMessageService;
 import org.dromara.main.service.ICsMessageService;
 import org.dromara.main.service.ICsSessionService;
 import org.dromara.main.service.ICsSessionService;
+import org.springframework.messaging.simp.SimpMessagingTemplate;
 import org.springframework.stereotype.Service;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 import org.springframework.transaction.annotation.Transactional;
 import org.springframework.web.multipart.MultipartFile;
 import org.springframework.web.multipart.MultipartFile;
@@ -53,6 +54,7 @@ public class CsMessageServiceImpl implements ICsMessageService {
     private final SysUserMapper sysUserMapper;
     private final SysUserMapper sysUserMapper;
     private final ICsSessionService sessionService;
     private final ICsSessionService sessionService;
     private final Converter converter;
     private final Converter converter;
+    private final SimpMessagingTemplate messagingTemplate;
 
 
     @Override
     @Override
     public TableDataInfo<CsMessageVo> queryHistoryMessages(Long sessionId, Long beforeMsgId, PageQuery pageQuery) {
     public TableDataInfo<CsMessageVo> queryHistoryMessages(Long sessionId, Long beforeMsgId, PageQuery pageQuery) {
@@ -90,7 +92,9 @@ public class CsMessageServiceImpl implements ICsMessageService {
         sessionService.updateLastMessage(bo.getSessionId(),
         sessionService.updateLastMessage(bo.getSessionId(),
             StrUtil.sub(bo.getContent(), 0, 100));
             StrUtil.sub(bo.getContent(), 0, 100));
 
 
-        return converter.convert(message, CsMessageVo.class);
+        CsMessageVo vo = converter.convert(message, CsMessageVo.class);
+        messagingTemplate.convertAndSend("/topic/session/" + bo.getSessionId(), vo);
+        return vo;
     }
     }
 
 
     @Override
     @Override
@@ -127,7 +131,9 @@ public class CsMessageServiceImpl implements ICsMessageService {
             baseMapper.insert(message);
             baseMapper.insert(message);
             sessionService.updateLastMessage(sessionId, "[图片]");
             sessionService.updateLastMessage(sessionId, "[图片]");
 
 
-            return converter.convert(message, CsMessageVo.class);
+            CsMessageVo vo = converter.convert(message, CsMessageVo.class);
+            messagingTemplate.convertAndSend("/topic/session/" + sessionId, vo);
+            return vo;
         } catch (Exception e) {
         } catch (Exception e) {
             throw new RuntimeException("上传图片失败: " + e.getMessage(), e);
             throw new RuntimeException("上传图片失败: " + e.getMessage(), e);
         }
         }
@@ -167,7 +173,9 @@ public class CsMessageServiceImpl implements ICsMessageService {
             baseMapper.insert(message);
             baseMapper.insert(message);
             sessionService.updateLastMessage(sessionId, "[文件]" + fileName);
             sessionService.updateLastMessage(sessionId, "[文件]" + fileName);
 
 
-            return converter.convert(message, CsMessageVo.class);
+            CsMessageVo vo = converter.convert(message, CsMessageVo.class);
+            messagingTemplate.convertAndSend("/topic/session/" + sessionId, vo);
+            return vo;
         } catch (Exception e) {
         } catch (Exception e) {
             throw new RuntimeException("上传文件失败: " + e.getMessage(), e);
             throw new RuntimeException("上传文件失败: " + e.getMessage(), e);
         }
         }
@@ -191,7 +199,9 @@ public class CsMessageServiceImpl implements ICsMessageService {
         baseMapper.insert(message);
         baseMapper.insert(message);
         sessionService.updateLastMessage(bo.getSessionId(), "[岗位推荐]");
         sessionService.updateLastMessage(bo.getSessionId(), "[岗位推荐]");
 
 
-        return converter.convert(message, CsMessageVo.class);
+        CsMessageVo vo = converter.convert(message, CsMessageVo.class);
+        messagingTemplate.convertAndSend("/topic/session/" + bo.getSessionId(), vo);
+        return vo;
     }
     }
 
 
     @Override
     @Override

+ 80 - 0
ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/service/impl/MainStudentCollectionServiceImpl.java

@@ -0,0 +1,80 @@
+package org.dromara.main.service.impl;
+
+import cn.hutool.core.bean.BeanUtil;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import lombok.RequiredArgsConstructor;
+import cn.hutool.core.util.ObjectUtil;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.main.domain.MainStudentCollection;
+import org.dromara.main.domain.bo.MainStudentCollectionBo;
+import org.dromara.main.domain.vo.MainStudentCollectionVo;
+import org.dromara.main.mapper.MainStudentCollectionMapper;
+import org.dromara.main.mapper.MainPositionMapper;
+import org.dromara.main.mapper.MainExamEvaluationMapper;
+import org.dromara.main.service.IMainStudentCollectionService;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+
+@RequiredArgsConstructor
+@Service
+public class MainStudentCollectionServiceImpl implements IMainStudentCollectionService {
+
+    private final MainStudentCollectionMapper baseMapper;
+    private final MainPositionMapper positionMapper;
+    private final MainExamEvaluationMapper assessmentMapper;
+
+    @Override
+    public TableDataInfo<MainStudentCollectionVo> queryPageList(MainStudentCollectionBo bo, PageQuery pageQuery) {
+        LambdaQueryWrapper<MainStudentCollection> lqw = Wrappers.lambdaQuery();
+        lqw.eq(ObjectUtil.isNotNull(bo.getStudentId()), MainStudentCollection::getStudentId, bo.getStudentId());
+        lqw.eq(ObjectUtil.isNotNull(bo.getType()), MainStudentCollection::getType, bo.getType());
+        lqw.orderByDesc(MainStudentCollection::getCreateTime);
+
+        Page<MainStudentCollectionVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
+        List<MainStudentCollectionVo> records = result.getRecords();
+
+        // 填充目标数据
+        for (MainStudentCollectionVo record : records) {
+            if ("job".equals(record.getType())) {
+                record.setTargetData(positionMapper.selectVoById(record.getTargetId()));
+            } else if ("assessment".equals(record.getType())) {
+                record.setTargetData(assessmentMapper.selectVoById(record.getTargetId()));
+            }
+        }
+
+        return TableDataInfo.build(result);
+    }
+
+    @Override
+    public Boolean insertByBo(MainStudentCollectionBo bo) {
+        // 先检查是否已收藏
+        LambdaQueryWrapper<MainStudentCollection> lqw = Wrappers.lambdaQuery();
+        lqw.eq(MainStudentCollection::getStudentId, bo.getStudentId())
+           .eq(MainStudentCollection::getTargetId, bo.getTargetId())
+           .eq(MainStudentCollection::getType, bo.getType());
+        if (baseMapper.exists(lqw)) {
+            return true; // 已存在视为成功
+        }
+
+        MainStudentCollection add = BeanUtil.toBean(bo, MainStudentCollection.class);
+        return baseMapper.insert(add) > 0;
+    }
+
+    @Override
+    public Boolean deleteById(Long id) {
+        return baseMapper.deleteById(id) > 0;
+    }
+
+    @Override
+    public MainStudentCollectionVo checkCollection(Long studentId, Long targetId, String type) {
+        LambdaQueryWrapper<MainStudentCollection> lqw = Wrappers.lambdaQuery();
+        lqw.eq(MainStudentCollection::getStudentId, studentId)
+           .eq(MainStudentCollection::getTargetId, targetId)
+           .eq(MainStudentCollection::getType, type);
+        return baseMapper.selectVoOne(lqw);
+    }
+}

+ 5 - 0
ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/service/impl/MainStudentServiceImpl.java

@@ -222,6 +222,11 @@ public class MainStudentServiceImpl implements IMainStudentService {
         result.setMobile(student.getMobile());
         result.setMobile(student.getMobile());
         result.setName(student.getName());
         result.setName(student.getName());
         result.setIsNewUser(isNewUser);
         result.setIsNewUser(isNewUser);
+        // 获取完整的学员信息以获取头像URL
+        MainStudentVo studentVo = baseMapper.selectVoById(student.getId());
+        if (studentVo != null) {
+            result.setAvatarUrl(studentVo.getAvatarUrl());
+        }
         return result;
         return result;
     }
     }
 }
 }