西格玛许 2 days ago
parent
commit
137827476e
15 changed files with 417 additions and 51 deletions
  1. 52 3
      ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/controller/CsMessageController.java
  2. 7 1
      ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/controller/CsOrderCardController.java
  3. 5 0
      ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/domain/bo/CsMessageBo.java
  4. 5 0
      ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/domain/vo/CsOrderCardVo.java
  5. 3 0
      ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/domain/vo/MainPositionVo.java
  6. 10 0
      ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/service/ICsMessageService.java
  7. 38 5
      ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/service/impl/CsMessageServiceImpl.java
  8. 114 18
      ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/service/impl/CsOrderCardServiceImpl.java
  9. 38 16
      ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/service/impl/CsSessionServiceImpl.java
  10. 2 0
      ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/service/impl/MainAuditServiceImpl.java
  11. 84 1
      ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/service/impl/MainBackCandidateServiceImpl.java
  12. 11 6
      ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/service/impl/MainExamApplyServiceImpl.java
  13. 28 1
      ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/service/impl/MainPositionServiceImpl.java
  14. 18 0
      ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/service/impl/MainStudentServiceImpl.java
  15. 2 0
      ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/bo/SysUserBo.java

+ 52 - 3
ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/controller/CsMessageController.java

@@ -11,11 +11,16 @@ import org.dromara.common.satoken.utils.LoginHelper;
 import org.dromara.common.web.core.BaseController;
 import org.dromara.main.domain.bo.CsMessageBo;
 import org.dromara.main.domain.vo.CsMessageVo;
+import org.dromara.main.mapper.CsSeatWaiterMapper;
+import org.dromara.main.domain.CsSeatWaiter;
 import org.dromara.main.service.ICsMessageService;
 import org.springframework.validation.annotation.Validated;
 import org.springframework.web.bind.annotation.*;
 import org.springframework.web.multipart.MultipartFile;
 
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+
+import java.util.List;
 import java.util.Map;
 
 @Validated
@@ -25,6 +30,7 @@ import java.util.Map;
 public class CsMessageController extends BaseController {
 
     private final ICsMessageService messageService;
+    private final CsSeatWaiterMapper seatWaiterMapper;
 
     /**
      * 获取历史消息
@@ -48,6 +54,18 @@ public class CsMessageController extends BaseController {
         Long currentUserId = LoginHelper.getUserId();
         if (currentUserId != null) {
             bo.setSenderId(currentUserId);
+            // 判断当前用户是否是坐席,如果是则直接设置 senderType = 2
+            if (bo.getSenderType() == null) {
+                List<CsSeatWaiter> waiters = seatWaiterMapper.selectList(
+                    Wrappers.lambdaQuery(CsSeatWaiter.class)
+                        .eq(CsSeatWaiter::getUserId, currentUserId)
+                        .last("LIMIT 1"));
+                if (!waiters.isEmpty()) {
+                    bo.setSenderType(2); // 坐席
+                } else {
+                    bo.setSenderType(1); // 客户
+                }
+            }
         }
         return R.ok(messageService.sendTextMessage(bo));
     }
@@ -64,7 +82,17 @@ public class CsMessageController extends BaseController {
         @RequestParam(required = false) Long senderId,
         @RequestParam("file") MultipartFile file) {
         Long currentUserId = LoginHelper.getUserId();
-        return R.ok(messageService.sendImageMessage(sessionId, msgNo, currentUserId != null ? currentUserId : senderId, file));
+        Long actualSenderId = currentUserId != null ? currentUserId : senderId;
+        // 判断发送者类型
+        Integer senderType = null;
+        if (currentUserId != null) {
+            List<CsSeatWaiter> waiters = seatWaiterMapper.selectList(
+                Wrappers.lambdaQuery(CsSeatWaiter.class)
+                    .eq(CsSeatWaiter::getUserId, currentUserId)
+                    .last("LIMIT 1"));
+            senderType = !waiters.isEmpty() ? 2 : 1;
+        }
+        return R.ok(messageService.sendImageMessage(sessionId, msgNo, actualSenderId, file, senderType));
     }
 
     /**
@@ -79,8 +107,17 @@ public class CsMessageController extends BaseController {
         @RequestParam(required = false) Long senderId,
         @RequestParam("file") MultipartFile file) {
         Long currentUserId = LoginHelper.getUserId();
-
-        return R.ok(messageService.sendFileMessage(sessionId, msgNo, currentUserId != null ? currentUserId : senderId, file));
+        Long actualSenderId = currentUserId != null ? currentUserId : senderId;
+        // 判断发送者类型
+        Integer senderType = null;
+        if (currentUserId != null) {
+            List<CsSeatWaiter> waiters = seatWaiterMapper.selectList(
+                Wrappers.lambdaQuery(CsSeatWaiter.class)
+                    .eq(CsSeatWaiter::getUserId, currentUserId)
+                    .last("LIMIT 1"));
+            senderType = !waiters.isEmpty() ? 2 : 1;
+        }
+        return R.ok(messageService.sendFileMessage(sessionId, msgNo, actualSenderId, file, senderType));
     }
 
     /**
@@ -89,6 +126,18 @@ public class CsMessageController extends BaseController {
     @Log(title = "发送岗位卡片", businessType = BusinessType.INSERT)
     @PostMapping("/send/job-card")
     public R<CsMessageVo> sendJobCard(@Validated @RequestBody CsMessageBo bo) {
+        // 注入当前登录客服ID(与 sendText 保持一致逻辑)
+        Long currentUserId = LoginHelper.getUserId();
+        if (currentUserId != null) {
+            bo.setSenderId(currentUserId);
+            if (bo.getSenderType() == null) {
+                List<CsSeatWaiter> waiters = seatWaiterMapper.selectList(
+                    Wrappers.lambdaQuery(CsSeatWaiter.class)
+                        .eq(CsSeatWaiter::getUserId, currentUserId)
+                        .last("LIMIT 1"));
+                bo.setSenderType(!waiters.isEmpty() ? 2 : 1);
+            }
+        }
         return R.ok(messageService.sendJobCard(bo));
     }
 

+ 7 - 1
ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/controller/CsOrderCardController.java

@@ -28,7 +28,7 @@ import java.nio.charset.StandardCharsets;
 import java.util.Base64;
 import java.util.Map;
 
-import static io.smallrye.common.function.FunctionsLogging.log;
+import org.dromara.common.satoken.utils.LoginHelper;
 
 @Slf4j
 @Validated
@@ -48,6 +48,11 @@ public class CsOrderCardController extends BaseController {
     @Log(title = "发送结算单", businessType = BusinessType.INSERT)
     @PostMapping("/send")
     public R<CsOrderCardVo> sendOrderCard(@Validated @RequestBody CsOrderCardBo bo) {
+        // 注入当前登录客服ID(与消息发送保持一致逻辑)
+        Long currentUserId = LoginHelper.getUserId();
+        if (currentUserId != null) {
+            bo.setSenderId(currentUserId);
+        }
         return R.ok(orderCardService.sendOrderCard(bo));
     }
 
@@ -80,6 +85,7 @@ public class CsOrderCardController extends BaseController {
 
     @PostMapping("/{orderCardId}/create-order")
     public R<Map<String, Object>> createPayOrder(@PathVariable Long orderCardId, @RequestParam Long userId) {
+        log.info("========== createPayOrder 请求: orderCardId={}, userId={} ==========", orderCardId, userId);
         return R.ok(orderCardService.createPayOrder(orderCardId, userId));
     }
 

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

@@ -26,6 +26,11 @@ public class CsMessageBo {
      */
     private Long senderId;
 
+    /**
+     * 发送者类型(1:客户 2:客服 3:系统),如果不传则由后端自动判断
+     */
+    private Integer senderType;
+
     /**
      * 消息类型
      */

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

@@ -73,4 +73,9 @@ public class CsOrderCardVo implements Serializable {
      */
     @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
     private LocalDateTime payTime;
+
+    /**
+     * 发送者类型(1:客户 2:客服 3:系统),来自关联的消息记录
+     */
+    private Integer senderType;
 }

+ 3 - 0
ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/domain/vo/MainPositionVo.java

@@ -120,6 +120,9 @@ public class MainPositionVo implements Serializable {
 
     private String companyName;
 
+    /** 企业头像/Logo URL */
+    private String companyAvatar;
+
     /** 经度 */
     private BigDecimal longitude;
 

+ 10 - 0
ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/service/ICsMessageService.java

@@ -23,11 +23,21 @@ public interface ICsMessageService {
      */
     CsMessageVo sendImageMessage(Long sessionId, String msgNo, Long senderId, MultipartFile file);
 
+    /**
+     * 发送图片消息(指定发送者类型)
+     */
+    CsMessageVo sendImageMessage(Long sessionId, String msgNo, Long senderId, MultipartFile file, Integer senderType);
+
     /**
      * 发送文件消息
      */
     CsMessageVo sendFileMessage(Long sessionId, String msgNo, Long senderId, MultipartFile file);
 
+    /**
+     * 发送文件消息(指定发送者类型)
+     */
+    CsMessageVo sendFileMessage(Long sessionId, String msgNo, Long senderId, MultipartFile file, Integer senderType);
+
     /**
      * 发送岗位卡片
      */

+ 38 - 5
ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/service/impl/CsMessageServiceImpl.java

@@ -41,6 +41,8 @@ import org.dromara.system.domain.SysUser;
 import org.dromara.system.mapper.SysUserMapper;
 import org.dromara.main.domain.CsSeatConfig;
 import org.dromara.main.domain.MainCompanyApply;
+import org.dromara.main.domain.MainStudent;
+import org.dromara.main.mapper.MainStudentMapper;
 
 @RequiredArgsConstructor
 @Service
@@ -50,6 +52,7 @@ public class CsMessageServiceImpl implements ICsMessageService {
     private final CsSessionMapper sessionMapper;
     private final CsSeatConfigMapper seatConfigMapper;
     private final MainCompanyApplyMapper mainCompanyApplyMapper;
+    private final MainStudentMapper mainStudentMapper;
     private final OssService ossService;
     private final SysUserMapper sysUserMapper;
     private final ICsSessionService sessionService;
@@ -81,7 +84,8 @@ public class CsMessageServiceImpl implements ICsMessageService {
         message.setSessionId(bo.getSessionId());
         message.setMsgNo(bo.getMsgNo());
         message.setSenderId(senderId);
-        message.setSenderType(resolveSenderType(bo.getSessionId(), senderId));
+        // 优先使用调用方指定的 senderType(Controller 层已根据用户角色判断),fallback 到自动判断
+        message.setSenderType(bo.getSenderType() != null ? bo.getSenderType() : resolveSenderType(bo.getSessionId(), senderId));
         message.setMsgType(bo.getMsgType());
         message.setContent(bo.getContent());
         message.setStatus(1);
@@ -93,6 +97,8 @@ public class CsMessageServiceImpl implements ICsMessageService {
             StrUtil.sub(bo.getContent(), 0, 100));
 
         CsMessageVo vo = converter.convert(message, CsMessageVo.class);
+        // 广播前填充 senderRole/senderName/senderAvatar 等字段,确保前端能正确识别发送者
+        enrichMessageVo(vo, sessionMapper.selectById(bo.getSessionId()), null, null, Collections.emptyMap());
         messagingTemplate.convertAndSend("/topic/session/" + bo.getSessionId(), vo);
         notifyWaiter(vo);
 
@@ -102,6 +108,12 @@ public class CsMessageServiceImpl implements ICsMessageService {
     @Override
     @Transactional(rollbackFor = Exception.class)
     public CsMessageVo sendImageMessage(Long sessionId, String msgNo, Long senderId, MultipartFile file) {
+        return sendImageMessage(sessionId, msgNo, senderId, file, null);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public CsMessageVo sendImageMessage(Long sessionId, String msgNo, Long senderId, MultipartFile file, Integer senderType) {
         try {
             Long actualSenderId = resolveSenderId(senderId);
             OssClient ossClient = OssFactory.instance();
@@ -120,7 +132,7 @@ public class CsMessageServiceImpl implements ICsMessageService {
             message.setSessionId(sessionId);
             message.setMsgNo(msgNo);
             message.setSenderId(actualSenderId);
-            message.setSenderType(resolveSenderType(sessionId, actualSenderId));
+            message.setSenderType(senderType != null ? senderType : resolveSenderType(sessionId, actualSenderId));
             message.setMsgType("image");
             message.setFileUrl(uploadResult.getUrl());
             message.setFileName(fileName);
@@ -134,6 +146,7 @@ public class CsMessageServiceImpl implements ICsMessageService {
             sessionService.updateLastMessage(sessionId, "[图片]");
 
             CsMessageVo vo = converter.convert(message, CsMessageVo.class);
+            enrichMessageVo(vo, sessionMapper.selectById(sessionId), null, null, Collections.emptyMap());
             messagingTemplate.convertAndSend("/topic/session/" + sessionId, vo);
             notifyWaiter(vo);
 
@@ -146,6 +159,12 @@ public class CsMessageServiceImpl implements ICsMessageService {
     @Override
     @Transactional(rollbackFor = Exception.class)
     public CsMessageVo sendFileMessage(Long sessionId, String msgNo, Long senderId, MultipartFile file) {
+        return sendFileMessage(sessionId, msgNo, senderId, file, null);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public CsMessageVo sendFileMessage(Long sessionId, String msgNo, Long senderId, MultipartFile file, Integer senderType) {
         try {
             Long actualSenderId = resolveSenderId(senderId);
             OssClient ossClient = OssFactory.instance();
@@ -164,7 +183,7 @@ public class CsMessageServiceImpl implements ICsMessageService {
             message.setSessionId(sessionId);
             message.setMsgNo(msgNo);
             message.setSenderId(actualSenderId);
-            message.setSenderType(resolveSenderType(sessionId, actualSenderId));
+            message.setSenderType(senderType != null ? senderType : resolveSenderType(sessionId, actualSenderId));
             message.setMsgType("file");
             message.setFileUrl(uploadResult.getUrl());
             message.setFileName(fileName);
@@ -178,6 +197,7 @@ public class CsMessageServiceImpl implements ICsMessageService {
             sessionService.updateLastMessage(sessionId, "[文件]" + fileName);
 
             CsMessageVo vo = converter.convert(message, CsMessageVo.class);
+            enrichMessageVo(vo, sessionMapper.selectById(sessionId), null, null, Collections.emptyMap());
             messagingTemplate.convertAndSend("/topic/session/" + sessionId, vo);
             notifyWaiter(vo);
 
@@ -195,7 +215,7 @@ public class CsMessageServiceImpl implements ICsMessageService {
         message.setSessionId(bo.getSessionId());
         message.setMsgNo(bo.getMsgNo());
         message.setSenderId(senderId);
-        message.setSenderType(resolveSenderType(bo.getSessionId(), senderId));
+        message.setSenderType(bo.getSenderType() != null ? bo.getSenderType() : resolveSenderType(bo.getSessionId(), senderId));
         message.setMsgType("job_card");
         message.setPayload(JSONUtil.toJsonStr(bo.getPayload()));
         message.setStatus(1);
@@ -206,6 +226,7 @@ public class CsMessageServiceImpl implements ICsMessageService {
         sessionService.updateLastMessage(bo.getSessionId(), "[岗位推荐]");
 
         CsMessageVo vo = converter.convert(message, CsMessageVo.class);
+        enrichMessageVo(vo, sessionMapper.selectById(bo.getSessionId()), null, null, Collections.emptyMap());
         messagingTemplate.convertAndSend("/topic/session/" + bo.getSessionId(), vo);
         notifyWaiter(vo);
 
@@ -268,7 +289,12 @@ public class CsMessageServiceImpl implements ICsMessageService {
             if (waiter != null) {
                 vo.setSenderName(StrUtil.blankToDefault(waiter.getNickName(), waiter.getUserName()));
             }
-            vo.setSenderAvatar(seat != null ? seat.getAvatar() : null);
+            // 坐席头像:优先用坐席配置头像,否则用客服系统用户头像
+            String seatAvatar = (seat != null && StrUtil.isNotBlank(seat.getAvatar())) ? seat.getAvatar() : null;
+            if (seatAvatar == null && waiter != null && waiter.getAvatar() != null) {
+                seatAvatar = ossService.selectUrlByIds(waiter.getAvatar().toString());
+            }
+            vo.setSenderAvatar(seatAvatar);
             vo.setSenderRole("waiter");
             return;
         }
@@ -305,6 +331,13 @@ public class CsMessageServiceImpl implements ICsMessageService {
         if (session != null && StrUtil.isNotBlank(session.getFromUserAvatar())) {
             return session.getFromUserAvatar();
         }
+        // 小程序用户(sessionType=1):从学员表获取头像
+        if (session != null && session.getSessionType() != null && session.getSessionType() == 1 && session.getFromUserId() != null) {
+            MainStudent student = mainStudentMapper.selectById(session.getFromUserId());
+            if (student != null && student.getAvatar() != null) {
+                return ossService.selectUrlByIds(student.getAvatar().toString());
+            }
+        }
         if (companyApply != null && companyApply.getAvatar() != null) {
             return ossService.selectUrlByIds(companyApply.getAvatar().toString());
         }

+ 114 - 18
ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/service/impl/CsOrderCardServiceImpl.java

@@ -9,11 +9,14 @@ import io.github.linpeilie.Converter;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.dromara.common.core.exception.ServiceException;
+import org.dromara.common.satoken.utils.LoginHelper;
 import org.dromara.main.config.WxPayConfig;
 import org.dromara.main.domain.*;
 import org.dromara.main.domain.bo.CsOrderCardBo;
+import org.dromara.main.domain.vo.CsMessageVo;
 import org.dromara.main.domain.vo.CsOrderCardVo;
 import org.dromara.main.mapper.CsMessageMapper;
+import org.springframework.messaging.simp.SimpMessagingTemplate;
 import org.dromara.main.mapper.CsOrderCardMapper;
 import org.dromara.main.mapper.CsSessionMapper;
 import org.dromara.main.mapper.MainExamEvaluationMapper;
@@ -40,6 +43,7 @@ public class CsOrderCardServiceImpl implements ICsOrderCardService {
     private final CsMessageMapper messageMapper;
     private final ICsSessionService sessionService;
     private final Converter converter;
+    private final SimpMessagingTemplate messagingTemplate;
     private final MainOrderMapper orderMapper;
     private final MainStudentMapper studentMapper;
     private final CsSessionMapper sessionMapper;
@@ -51,11 +55,21 @@ public class CsOrderCardServiceImpl implements ICsOrderCardService {
     @Override
     @Transactional(rollbackFor = Exception.class)
     public CsOrderCardVo sendOrderCard(CsOrderCardBo bo) {
+        // 解析发送者ID(优先使用传入值,fallback 到当前登录用户)
+        Long senderId = bo.getSenderId() != null ? bo.getSenderId() : LoginHelper.getUserId();
+        
+        // 动态解析发送者类型
+        CsSession session = sessionMapper.selectById(bo.getSessionId());
+        Integer senderType = 2; // 默认为客服(手动发送结算单只从总控端调用)
+        if (senderId != null && session != null && session.getFromUserId() != null && session.getFromUserId().equals(senderId)) {
+            senderType = 1; // 极端情况:用户自己发的
+        }
+        
         CsMessage message = new CsMessage();
         message.setSessionId(bo.getSessionId());
         message.setMsgNo(bo.getMsgNo());
-        message.setSenderType(2);
-        message.setSenderId(bo.getSenderId());
+        message.setSenderType(senderType);
+        message.setSenderId(senderId);
         message.setMsgType("order_card");
         message.setStatus(1);
         message.setIsRead(0);
@@ -86,8 +100,39 @@ public class CsOrderCardServiceImpl implements ICsOrderCardService {
 
         sessionService.updateLastMessage(bo.getSessionId(), "[结算单]" + bo.getOrderName());
 
+        // 推送消息到会话频道(与 CsMessageServiceImpl 保持一致)
+        CsMessageVo msgVo = new CsMessageVo();
+        msgVo.setId(message.getId());
+        msgVo.setSessionId(message.getSessionId());
+        msgVo.setMsgNo(message.getMsgNo());
+        msgVo.setSenderType(message.getSenderType());
+        msgVo.setSenderId(message.getSenderId());
+        msgVo.setMsgType(message.getMsgType());
+        msgVo.setPayload(message.getPayload());
+        msgVo.setStatus(message.getStatus());
+        msgVo.setSendTime(message.getSendTime());
+        // 设置 senderRole,确保前端正确识别发送者
+        if (message.getSenderType() == 2) {
+            msgVo.setSenderRole("waiter");
+        } else if (message.getSenderType() == 3) {
+            msgVo.setSenderRole("system");
+        } else {
+            msgVo.setSenderRole("user");
+        }
+        messagingTemplate.convertAndSend("/topic/session/" + bo.getSessionId(), msgVo);
+        // 通知坐席
+        if (session != null && session.getWaiterId() != null) {
+            messagingTemplate.convertAndSendToUser(
+                session.getWaiterId().toString(),
+                "/queue/notify",
+                msgVo
+            );
+        }
+
         CsOrderCardVo vo = converter.convert(orderCard, CsOrderCardVo.class);
         if (vo != null) {
+            vo.setOrderCardId(orderCard.getId());
+            vo.setSenderType(senderType);
             vo.setCountdownSeconds(60L);
         }
         return vo;
@@ -97,9 +142,12 @@ public class CsOrderCardServiceImpl implements ICsOrderCardService {
     public CsOrderCardVo queryById(Long orderCardId) {
         CsOrderCard entity = baseMapper.selectById(orderCardId);
         CsOrderCardVo vo = converter.convert(entity, CsOrderCardVo.class);
-        if (vo != null && "pending".equals(vo.getStatus())) {
-            long seconds = Duration.between(LocalDateTime.now(), vo.getExpireTime()).getSeconds();
-            vo.setCountdownSeconds(Math.max(0, seconds));
+        if (vo != null) {
+            vo.setOrderCardId(entity.getId());
+            if ("pending".equals(vo.getStatus())) {
+                long seconds = Duration.between(LocalDateTime.now(), vo.getExpireTime()).getSeconds();
+                vo.setCountdownSeconds(Math.max(0, seconds));
+            }
         }
         return vo;
     }
@@ -151,23 +199,23 @@ public class CsOrderCardServiceImpl implements ICsOrderCardService {
     @Override
     @Transactional(rollbackFor = Exception.class)
     public Map<String,Object>createPayOrder(Long orderCardId,Long userId) {
+        log.info("========== createPayOrder 入参: orderCardId={}, userId={} ==========", orderCardId, userId);
         CsOrderCard orderCard = baseMapper.selectById(orderCardId);
         if (orderCard == null) {
+            log.error("结算单不存在, orderCardId={}", orderCardId);
             throw new RuntimeException("结算单不存在");
         }
+        log.info("结算单查询成功: id={}, status={}, sessionId={}, orderPrice={}", orderCard.getId(), orderCard.getStatus(), orderCard.getSessionId(), orderCard.getOrderPrice());
         if (!"pending".equals(orderCard.getStatus())) {
             throw new RuntimeException("结算单状态异常");
         }
-//        if(LocalDateTime.now().isAfter(orderCard.getCreateTime())){
-//            orderCard.setStatus("expired");
-//            baseMapper.updateById(orderCard);
-//            throw new RuntimeException("结算单已过期");
-//        }
-
 
         MainStudent student = studentMapper.selectById(userId);
+        log.info("学员查询结果: userId={}, student={}", userId, student != null ? "id=" + student.getId() + ",name=" + student.getName() + ",openid=" + student.getOpenid() : "NULL");
         if (student == null) {
-            throw new ServiceException("用户不存在");
+            // 尝试通过 tenantId + mobile 查找,或者给出更详细的错误信息
+            log.error("用户不存在, 传入userId={}, 尝试检查main_student表是否有此ID", userId);
+            throw new ServiceException("用户不存在[userId=" + userId + "]");
         }
         if(StringUtils.isEmpty(student.getOpenid())){
             throw new ServiceException("用户未绑定微信");
@@ -313,9 +361,19 @@ public class CsOrderCardServiceImpl implements ICsOrderCardService {
             .last("LIMIT 1"));
         if (existing != null) {
             CsOrderCardVo vo = converter.convert(existing, CsOrderCardVo.class);
-            if (vo != null && "pending".equals(vo.getStatus())) {
-                long seconds = Duration.between(LocalDateTime.now(), existing.getExpireTime()).getSeconds();
-                vo.setCountdownSeconds(Math.max(0, seconds));
+            if (vo != null) {
+                vo.setOrderCardId(existing.getId());
+                // 从关联消息获取 senderType
+                if (existing.getMsgId() != null) {
+                    CsMessage existingMsg = messageMapper.selectById(existing.getMsgId());
+                    if (existingMsg != null) {
+                        vo.setSenderType(existingMsg.getSenderType());
+                    }
+                }
+                if ("pending".equals(vo.getStatus())) {
+                    long seconds = Duration.between(LocalDateTime.now(), existing.getExpireTime()).getSeconds();
+                    vo.setCountdownSeconds(Math.max(0, seconds));
+                }
             }
             return vo;
         }
@@ -376,13 +434,20 @@ public class CsOrderCardServiceImpl implements ICsOrderCardService {
             }
         }
 
-        // 创建消息
+        // 创建消息(自动创建结算单,有客服时以客服身份,否则以系统身份)
         CsMessage message = new CsMessage();
         message.setSessionId(sessionId);
         message.setMsgNo("ORDER_" + System.currentTimeMillis());
-        message.setSenderType(2);
-        message.setSenderId(session != null ? session.getWaiterId() : null);
         message.setMsgType("order_card");
+        Long waiterId = session != null ? session.getWaiterId() : null;
+        // senderId: 有坐席用坐席ID(客服),没有坐席时以系统身份
+        if (waiterId != null) {
+            message.setSenderId(waiterId);
+            message.setSenderType(2); // 客服
+        } else {
+            message.setSenderId(0L);
+            message.setSenderType(3); // 系统
+        }
         message.setStatus(1);
         message.setIsRead(0);
         message.setSendTime(LocalDateTime.now());
@@ -416,8 +481,39 @@ public class CsOrderCardServiceImpl implements ICsOrderCardService {
 
         sessionService.updateLastMessage(sessionId, "[结算单]" + orderCard.getOrderName());
 
+        // 通过 WebSocket 广播消息,确保前端实时收到
+        CsMessageVo msgVo = new CsMessageVo();
+        msgVo.setId(message.getId());
+        msgVo.setSessionId(message.getSessionId());
+        msgVo.setMsgNo(message.getMsgNo());
+        msgVo.setSenderType(message.getSenderType());
+        msgVo.setSenderId(message.getSenderId());
+        msgVo.setMsgType(message.getMsgType());
+        msgVo.setPayload(message.getPayload());
+        msgVo.setStatus(message.getStatus());
+        msgVo.setSendTime(message.getSendTime());
+        // 设置 senderRole,确保前端正确识别发送者
+        if (message.getSenderType() == 2) {
+            msgVo.setSenderRole("waiter");
+        } else if (message.getSenderType() == 3) {
+            msgVo.setSenderRole("system");
+        } else {
+            msgVo.setSenderRole("user");
+        }
+        messagingTemplate.convertAndSend("/topic/session/" + sessionId, msgVo);
+        // 通知坐席
+        if (session != null && session.getWaiterId() != null) {
+            messagingTemplate.convertAndSendToUser(
+                session.getWaiterId().toString(),
+                "/queue/notify",
+                msgVo
+            );
+        }
+
         CsOrderCardVo vo = converter.convert(orderCard, CsOrderCardVo.class);
         if (vo != null) {
+            vo.setOrderCardId(orderCard.getId());
+            vo.setSenderType(message.getSenderType());
             vo.setCountdownSeconds(1800L);
         }
         return vo;

+ 38 - 16
ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/service/impl/CsSessionServiceImpl.java

@@ -15,6 +15,7 @@ import org.dromara.main.domain.CsSeatConfig;
 import org.dromara.main.domain.CsSeatWaiter;
 import org.dromara.main.domain.CsSession;
 import org.dromara.main.domain.MainCompanyApply;
+import org.dromara.main.domain.MainStudent;
 import org.dromara.main.domain.bo.CsSessionBo;
 import org.dromara.main.domain.vo.CsSessionVo;
 import org.dromara.main.mapper.CsMessageMapper;
@@ -22,6 +23,7 @@ import org.dromara.main.mapper.CsSeatConfigMapper;
 import org.dromara.main.mapper.CsSeatWaiterMapper;
 import org.dromara.main.mapper.CsSessionMapper;
 import org.dromara.main.mapper.MainCompanyApplyMapper;
+import org.dromara.main.mapper.MainStudentMapper;
 import org.dromara.main.service.ICsSessionService;
 import org.dromara.system.domain.SysUser;
 import org.dromara.system.mapper.SysUserMapper;
@@ -46,6 +48,7 @@ public class CsSessionServiceImpl implements ICsSessionService {
     private final CsSeatConfigMapper seatConfigMapper;
     private final CsSeatWaiterMapper seatWaiterMapper;
     private final MainCompanyApplyMapper mainCompanyApplyMapper;
+    private final MainStudentMapper mainStudentMapper;
     private final OssService ossService;
     private final SysUserMapper sysUserMapper;
     private final Converter converter;
@@ -237,10 +240,19 @@ public class CsSessionServiceImpl implements ICsSessionService {
         if (StringUtils.isBlank(vo.getFromUserAvatar())) {
             vo.setFromUserAvatar(resolveFromUserAvatar(vo));
         }
+        // 坐席头像:优先用坐席配置头像,否则用客服系统用户头像
+        String waiterAvatar = null;
         if (vo.getSeatId() != null && seatMap.containsKey(vo.getSeatId())) {
             vo.setSeatName(seatMap.get(vo.getSeatId()).getSeatName());
-            vo.setWaiterAvatar(seatMap.get(vo.getSeatId()).getAvatar());
+            waiterAvatar = seatMap.get(vo.getSeatId()).getAvatar();
         }
+        if (StringUtils.isBlank(waiterAvatar) && vo.getWaiterId() != null && waiterMap.containsKey(vo.getWaiterId())) {
+            SysUser waiter = waiterMap.get(vo.getWaiterId());
+            if (waiter.getAvatar() != null) {
+                waiterAvatar = ossService.selectUrlByIds(waiter.getAvatar().toString());
+            }
+        }
+        vo.setWaiterAvatar(waiterAvatar);
         if (vo.getWaiterId() != null && waiterMap.containsKey(vo.getWaiterId())) {
             SysUser waiter = waiterMap.get(vo.getWaiterId());
             vo.setWaiterName(StringUtils.defaultIfBlank(waiter.getNickName(), waiter.getUserName()));
@@ -275,23 +287,33 @@ public class CsSessionServiceImpl implements ICsSessionService {
     }
 
     private String resolveFromUserAvatar(CsSessionVo vo) {
-        if (vo == null || vo.getSessionType() == null || vo.getSessionType() != 2) {
+        if (vo == null) {
             return null;
         }
-        MainCompanyApply companyApply = mainCompanyApplyMapper.selectOne(
-            Wrappers.lambdaQuery(MainCompanyApply.class)
-                .and(wrapper -> wrapper
-                    .eq(MainCompanyApply::getTenantId, String.valueOf(vo.getFromUserId()))
-                    .or()
-                    .eq(MainCompanyApply::getId, vo.getFromUserId())
-                    .or()
-                    .eq(StringUtils.isNotBlank(vo.getFromUserName()), MainCompanyApply::getCompanyName, vo.getFromUserName()))
-                .orderByDesc(MainCompanyApply::getCreateTime)
-                .last("LIMIT 1")
-        );
-        if (companyApply == null || companyApply.getAvatar() == null) {
-            return null;
+        // 小程序用户(sessionType=1):从学员表获取头像
+        if (vo.getSessionType() != null && vo.getSessionType() == 1 && vo.getFromUserId() != null) {
+            MainStudent student = mainStudentMapper.selectById(vo.getFromUserId());
+            if (student != null && student.getAvatar() != null) {
+                return ossService.selectUrlByIds(student.getAvatar().toString());
+            }
+        }
+        // 商家用户(sessionType=2):从企业申请表获取头像
+        if (vo.getSessionType() != null && vo.getSessionType() == 2) {
+            MainCompanyApply companyApply = mainCompanyApplyMapper.selectOne(
+                Wrappers.lambdaQuery(MainCompanyApply.class)
+                    .and(wrapper -> wrapper
+                        .eq(MainCompanyApply::getTenantId, String.valueOf(vo.getFromUserId()))
+                        .or()
+                        .eq(MainCompanyApply::getId, vo.getFromUserId())
+                        .or()
+                        .eq(StringUtils.isNotBlank(vo.getFromUserName()), MainCompanyApply::getCompanyName, vo.getFromUserName()))
+                    .orderByDesc(MainCompanyApply::getCreateTime)
+                    .last("LIMIT 1")
+            );
+            if (companyApply != null && companyApply.getAvatar() != null) {
+                return ossService.selectUrlByIds(companyApply.getAvatar().toString());
+            }
         }
-        return ossService.selectUrlByIds(companyApply.getAvatar().toString());
+        return null;
     }
 }

+ 2 - 0
ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/service/impl/MainAuditServiceImpl.java

@@ -170,6 +170,8 @@ public class MainAuditServiceImpl implements IMainAuditService {
         tenantBo.setContactPhone(companyApply.getMobile());
         tenantBo.setCompanyEntrustProof(companyApply.getAuthLetter()); // 暂时注释
         tenantBo.setLogo(companyApply.getAvatar());
+        tenantBo.setLongitude(companyApply.getLongitude());
+        tenantBo.setLatitude(companyApply.getLatitude());
         tenantBo.setStatus("0"); // 正常状态
 
         // 必需字段:创建系统用户的用户名和密码

+ 84 - 1
ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/service/impl/MainBackCandidateServiceImpl.java

@@ -2,16 +2,25 @@ package org.dromara.main.service.impl;
 
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
 import org.dromara.common.core.exception.ServiceException;
 import org.dromara.common.core.service.OssService;
+import org.dromara.common.tenant.helper.TenantHelper;
 import org.dromara.main.domain.MainBackCandidate;
 import org.dromara.main.domain.MainPosition;
+import org.dromara.main.domain.MainStudent;
 import org.dromara.main.mapper.MainBackCandidateMapper;
 import org.dromara.main.mapper.MainPositionMapper;
+import org.dromara.main.mapper.MainStudentMapper;
 import org.dromara.main.service.IMainBackCandidateService;
+import org.dromara.system.domain.SysUser;
+import org.dromara.system.domain.bo.SysUserBo;
 import org.dromara.system.domain.SysTenant;
 import org.dromara.system.mapper.SysTenantMapper;
+import org.dromara.system.mapper.SysUserMapper;
+import org.dromara.system.service.ISysUserService;
 import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
 
 import java.time.LocalDateTime;
 import java.util.HashMap;
@@ -22,13 +31,17 @@ import java.util.stream.Collectors;
 /**
  * 背调候选人Service实现
  */
+@Slf4j
 @RequiredArgsConstructor
 @Service
 public class MainBackCandidateServiceImpl implements IMainBackCandidateService {
 
     private final MainBackCandidateMapper mainBackCandidateMapper;
     private final MainPositionMapper mainPositionMapper;
+    private final MainStudentMapper mainStudentMapper;
     private final SysTenantMapper sysTenantMapper;
+    private final SysUserMapper sysUserMapper;
+    private final ISysUserService sysUserService;
     private final OssService ossService;
 
     @Override
@@ -92,6 +105,7 @@ public class MainBackCandidateServiceImpl implements IMainBackCandidateService {
     }
 
     @Override
+    @Transactional(rollbackFor = Exception.class)
     public Boolean acceptOffer(Long id, Long studentId) {
         MainBackCandidate candidate = mainBackCandidateMapper.selectById(id);
         if (candidate == null) {
@@ -114,7 +128,76 @@ public class MainBackCandidateServiceImpl implements IMainBackCandidateService {
         // 同时更新旧status字段保持兼容
         update.setStatus("adopted");
 
-        return mainBackCandidateMapper.updateById(update) > 0;
+        boolean updated = mainBackCandidateMapper.updateById(update) > 0;
+
+        // 接受Offer后,将学员添加到企业的员工表(sys_user)
+        if (updated) {
+            try {
+                addEmployeeFromStudent(candidate);
+            } catch (Exception e) {
+                log.warn("接受Offer后添加企业员工失败,candidateId={}, error={}", id, e.getMessage(), e);
+                // 不影响Offer接受的主流程
+            }
+        }
+
+        return updated;
+    }
+
+    /**
+     * 将学员添加到企业的员工表(sys_user)
+     */
+    private void addEmployeeFromStudent(MainBackCandidate candidate) {
+        String tenantId = candidate.getTenantId();
+        Long studentId = candidate.getStudentId();
+        if (tenantId == null || studentId == null) {
+            log.warn("租户ID或学员ID为空,跳过添加员工");
+            return;
+        }
+
+        // 查询学员信息
+        MainStudent student = mainStudentMapper.selectById(studentId);
+        if (student == null) {
+            log.warn("学员不存在,studentId={}", studentId);
+            return;
+        }
+
+        // 在目标租户下检查是否已存在该手机号的用户
+        SysUser existingUser = TenantHelper.dynamic(tenantId, () -> {
+            return sysUserMapper.selectOne(
+                new LambdaQueryWrapper<SysUser>()
+                    .eq(SysUser::getPhonenumber, student.getMobile())
+                    .eq(SysUser::getDelFlag, "0")
+                    .last("LIMIT 1")
+            );
+        });
+
+        if (existingUser != null) {
+            log.info("企业租户{}下已存在手机号{}的用户,跳过创建", tenantId, student.getMobile());
+            return;
+        }
+
+        // 在目标租户下创建企业员工账号
+        SysUserBo userBo = new SysUserBo();
+        userBo.setUserName(student.getMobile() != null ? student.getMobile() : "student_" + studentId);
+        userBo.setNickName(student.getName() != null ? student.getName() : "学员");
+        userBo.setPhonenumber(student.getMobile());
+        userBo.setEmail(student.getEmail());
+        userBo.setSex(student.getGender());
+        userBo.setAvatar(student.getAvatar());
+        userBo.setPassword(student.getMobile() != null ? student.getMobile() : "123456");
+        userBo.setStatus("0"); // 正常状态
+        userBo.setRemark("学员接受Offer自动创建");
+
+        try {
+            boolean created = TenantHelper.dynamic(tenantId, () -> {
+                return sysUserService.registerUser(userBo, tenantId);
+            });
+            if (created) {
+                log.info("成功为企业租户{}创建员工账号,手机号={}", tenantId, student.getMobile());
+            }
+        } catch (Exception e) {
+            log.error("为企业租户{}创建员工账号失败: {}", tenantId, e.getMessage(), e);
+        }
     }
 
     @Override

+ 11 - 6
ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/service/impl/MainExamApplyServiceImpl.java

@@ -7,10 +7,13 @@ import lombok.extern.slf4j.Slf4j;
 import org.dromara.common.core.exception.ServiceException;
 import org.dromara.common.satoken.utils.LoginHelper;
 import org.dromara.main.domain.MainExamApply;
+import org.dromara.main.domain.MainExamEvaluation;
 import org.dromara.main.domain.MainStudent;
 import org.dromara.main.domain.vo.MainExamApplyRecordVo;
 import org.dromara.main.domain.vo.MainExamApplyVo;
+import org.dromara.main.domain.vo.MainExamEvaluationVo;
 import org.dromara.main.mapper.MainExamApplyMapper;
+import org.dromara.main.mapper.MainExamEvaluationMapper;
 import org.dromara.main.mapper.MainStudentMapper;
 import org.dromara.main.service.IMainExamApplyService;
 import org.springframework.stereotype.Service;
@@ -29,11 +32,12 @@ public class MainExamApplyServiceImpl implements IMainExamApplyService {
     private final MainExamApplyMapper baseMapper;
     private final MainStudentMapper studentMapper;
     private final Converter converter;
+    private final MainExamEvaluationMapper evaluationMapper;
 
     @Override
     public MainExamApplyVo createExamApply(Long evaluationId, Long studentId) {
         log.info("创建测评申请,evaluationId: {}, studentId: {}", evaluationId, studentId);
-        
+
         // 检查学生是否存在
         MainStudent student = studentMapper.selectById(studentId);
         if (student == null) {
@@ -45,7 +49,7 @@ public class MainExamApplyServiceImpl implements IMainExamApplyService {
         queryWrapper.eq(MainExamApply::getEvaluationId, evaluationId)
                    .eq(MainExamApply::getStudentId, studentId)
                    .eq(MainExamApply::getDelFlag, "0");
-        
+
         MainExamApply existingApply = baseMapper.selectOne(queryWrapper);
         if (existingApply != null) {
             log.info("学生已申请过该测评,返回现有申请记录");
@@ -55,14 +59,15 @@ public class MainExamApplyServiceImpl implements IMainExamApplyService {
         // 创建新的申请记录
         MainExamApply examApply = new MainExamApply();
         examApply.setEvaluationId(evaluationId);
+        MainExamEvaluation evaluation = evaluationMapper.selectById(evaluationId);
         examApply.setStudentId(studentId);
-        examApply.setTenantId(student.getTenantId());
+        examApply.setTenantId(evaluation.getTenantId());
         examApply.setApplySource("1"); // 默认来源:小程序
         examApply.setApplyStatus("0"); // 默认状态:待开始
         examApply.setScheduleStartTime(new Date()); // 预定开始时间为当前时间
         examApply.setMaxAttemptCount(1); // 默认最大尝试次数为1
         examApply.setDelFlag("0");
-        
+
         // 设置截止时间(默认24小时后)
         Date deadlineTime = new Date(System.currentTimeMillis() + 24 * 60 * 60 * 1000);
         examApply.setDeadlineTime(deadlineTime);
@@ -99,12 +104,12 @@ public class MainExamApplyServiceImpl implements IMainExamApplyService {
         if (examApply == null) {
             throw new ServiceException("测评申请不存在");
         }
-        
+
         examApply.setApplyStatus(status);
         if ("2".equals(status)) { // 如果状态为已完成
             examApply.setFinishedTime(new Date());
         }
-        
+
         return baseMapper.updateById(examApply) > 0;
     }
 }

+ 28 - 1
ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/service/impl/MainPositionServiceImpl.java

@@ -9,9 +9,12 @@ import lombok.extern.slf4j.Slf4j;
 import org.dromara.common.core.utils.MapstructUtils;
 import org.dromara.common.core.utils.StringUtils;
 import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.core.service.OssService;
+import org.dromara.main.domain.MainCompanyApply;
 import org.dromara.main.domain.MainPosition;
 import org.dromara.main.domain.bo.MainPositionBo;
 import org.dromara.main.domain.vo.MainPositionVo;
+import org.dromara.main.mapper.MainCompanyApplyMapper;
 import org.dromara.main.mapper.MainPositionMapper;
 import org.dromara.main.service.IMainPositionService;
 import org.dromara.main.service.IMainStudentDislikeService;
@@ -34,6 +37,10 @@ public class MainPositionServiceImpl extends ServiceImpl<MainPositionMapper, Mai
 
     private final SysTenantMapper tenantMapper;
 
+    private final MainCompanyApplyMapper companyApplyMapper;
+
+    private final OssService ossService;
+
     private final IMainStudentDislikeService studentDislikeService;
     /**
      * 查询岗位分页列表
@@ -54,7 +61,7 @@ public class MainPositionServiceImpl extends ServiceImpl<MainPositionMapper, Mai
         Page<MainPositionVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw, MainPositionVo.class);
 
         log.info("查询岗位列表,共 {} 条", result.getTotal());
-        // 批量补全企业名称(根据 tenantId 查询 sys_tenant)
+        // 批量补全企业名称和企业logo(根据 tenantId 查询 sys_tenant 和 main_company_apply
         if (result.getTotal() > 0) {
             result.getRecords().forEach(vo -> {
                 SysTenantVo tenant = tenantMapper.selectVoOne(new LambdaQueryWrapper<SysTenant>().eq(SysTenant::getTenantId, vo.getTenantId()));
@@ -65,6 +72,16 @@ public class MainPositionServiceImpl extends ServiceImpl<MainPositionMapper, Mai
                 } else {
                     vo.setCompanyName("未知企业");
                 }
+                // 补全企业logo
+                try {
+                    MainCompanyApply companyApply = companyApplyMapper.selectOne(
+                        new LambdaQueryWrapper<MainCompanyApply>().eq(MainCompanyApply::getTenantId, vo.getTenantId()).last("LIMIT 1"));
+                    if (companyApply != null && companyApply.getAvatar() != null) {
+                        vo.setCompanyAvatar(ossService.selectUrlByIds(companyApply.getAvatar().toString()));
+                    }
+                } catch (Exception e) {
+                    log.warn("获取企业logo失败, tenantId={}: {}", vo.getTenantId(), e.getMessage());
+                }
             });
         }
         return result;
@@ -88,6 +105,16 @@ public class MainPositionServiceImpl extends ServiceImpl<MainPositionMapper, Mai
             } else {
                 vo.setCompanyName("未知企业");
             }
+            // 查询企业logo
+            MainCompanyApply companyApply = companyApplyMapper.selectOne(
+                new LambdaQueryWrapper<MainCompanyApply>().eq(MainCompanyApply::getTenantId, vo.getTenantId()).last("LIMIT 1"));
+            if (companyApply != null && companyApply.getAvatar() != null) {
+                try {
+                    vo.setCompanyAvatar(ossService.selectUrlByIds(companyApply.getAvatar().toString()));
+                } catch (Exception e) {
+                    log.warn("获取企业logo失败: {}", e.getMessage());
+                }
+            }
         }
         return vo;
     }

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

@@ -285,6 +285,7 @@ public class MainStudentServiceImpl implements IMainStudentService {
                 // 新用户,执行创建
                 isNewUser = true;
                 student = new MainStudent();
+                student.setStudentNo(generateStudentNo());
                 student.setName("用户_" + RandomUtil.randomString(6));
                 student.setGender("");
                 student.setOpenid(openid);
@@ -325,4 +326,21 @@ public class MainStudentServiceImpl implements IMainStudentService {
         }
         return result;
     }
+
+    /**
+     * 生成学员编号,格式:U + yyyyMMdd + 4位随机数
+     */
+    private String generateStudentNo() {
+        String datePart = cn.hutool.core.date.DateUtil.format(new java.util.Date(), "yyyyMMdd");
+        String randomPart = RandomUtil.randomNumbers(4);
+        String candidate = "U" + datePart + randomPart;
+        // 确保唯一性,若冲突则重新生成
+        while (baseMapper.selectCount(
+            Wrappers.lambdaQuery(MainStudent.class).eq(MainStudent::getStudentNo, candidate)
+        ) > 0) {
+            randomPart = RandomUtil.randomNumbers(4);
+            candidate = "U" + datePart + randomPart;
+        }
+        return candidate;
+    }
 }

+ 2 - 0
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/bo/SysUserBo.java

@@ -122,6 +122,8 @@ public class SysUserBo extends BaseEntity {
 //    租户id
     private String tenantId;
 
+    private Long avatar;
+
     public SysUserBo(Long userId) {
         this.userId = userId;
     }