2 次代碼提交 37eb029f58 ... 3c97e1105c

作者 SHA1 備註 提交日期
  xgb 3c97e1105c Merge remote-tracking branch 'origin/Payment-Configuration' into Payment-Configuration 3 天之前
  xgb caba1f6610 直播签到和直播密码房 3 天之前

+ 64 - 0
fs-admin/src/main/java/com/fs/live/controller/LiveSignRecordController.java

@@ -0,0 +1,64 @@
+package com.fs.live.controller;
+
+
+import com.fs.common.annotation.Log;
+import com.fs.common.core.controller.BaseController;
+import com.fs.common.core.domain.AjaxResult;
+import com.fs.common.core.domain.R;
+import com.fs.common.core.page.TableDataInfo;
+import com.fs.common.enums.BusinessType;
+import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.live.domain.LiveSignRecord;
+import com.fs.live.service.ILiveSignRecordService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+import javax.servlet.http.HttpServletResponse;
+import java.util.List;
+
+/**
+ * 直播签到记录Controller
+ *
+ * @author ylrz
+ * @date 2026-04-07
+ */
+@RestController
+@RequestMapping("/his/liveSignRecord")
+public class LiveSignRecordController extends BaseController {
+
+    @Autowired
+    private ILiveSignRecordService liveSignRecordService;
+
+    /**
+     * 查询直播签到记录列表
+     */
+    @PreAuthorize("@ss.hasPermi('his:liveSignRecord:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(LiveSignRecord liveSignRecord) {
+        startPage();
+        List<LiveSignRecord> list = liveSignRecordService.selectLiveSignRecordList(liveSignRecord);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出直播签到记录列表
+     */
+    @PreAuthorize("@ss.hasPermi('his:liveSignRecord:export')")
+    @Log(title = "直播签到记录", businessType = BusinessType.EXPORT)
+    @PostMapping("/export")
+    public AjaxResult export(HttpServletResponse response, LiveSignRecord liveSignRecord) {
+        List<LiveSignRecord> list = liveSignRecordService.selectLiveSignRecordList(liveSignRecord);
+        ExcelUtil<LiveSignRecord> util = new ExcelUtil<>(LiveSignRecord.class);
+        return util.exportExcel( list, "直播签到记录数据");
+    }
+
+    /**
+     * 获取直播签到记录详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('his:liveSignRecord:query')")
+    @GetMapping(value = "/{id}")
+    public R getInfo(@PathVariable("id") Long id) {
+        return R.ok().put("data", liveSignRecordService.getById(id));
+    }
+}

+ 1 - 0
fs-common/src/main/java/com/fs/common/constant/LiveKeysConstant.java

@@ -40,5 +40,6 @@ public class LiveKeysConstant {
     //记录用户观看直播间信息 直播间id、用户id、外部联系人id、qwUserId
     public static final String LIVE_USER_WATCH_LOG_CACHE = "live:user:watch:log:%s:%s:%s:%s";
 
+    public static final String LIVE_ROOM_PASSWORD_CACHE = "live:room:password:%s";
 
 }

+ 4 - 0
fs-live-app/src/main/java/com/fs/live/websocket/service/WebSocketServer.java

@@ -1209,6 +1209,10 @@ public class WebSocketServer {
 //                    msg.setData(JSON.toJSONString(liveGoodsVo));
 //                    msg.setStatus(status);
 //                }
+            }else if (task.getTaskType() == 7L) {
+                // 签到
+                msg.setCmd("sign");
+                msg.setData(JSON.toJSONString(task.getContent()));
             }
             msg.setStatus(1);
             // 定时任务消息作为管理员消息插队

+ 330 - 0
fs-live-app/src/main/java/com/fs/live/websocket/test/WebSocketTestClient.java

@@ -0,0 +1,330 @@
+package com.fs.live.websocket.test;
+
+import javax.websocket.*;
+import java.io.IOException;
+import java.net.URI;
+import java.util.Scanner;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * WebSocket 测试客户端
+ * 使用 @ClientEndpoint 注解方式
+ */
+@ClientEndpoint
+public class WebSocketTestClient {
+
+    private Session session;
+    private CountDownLatch closeLatch = new CountDownLatch(1);
+    private boolean connected = false;
+    private Throwable connectionError = null;
+
+    /**
+     * 连接成功回调
+     */
+    @OnOpen
+    public void onOpen(Session session) {
+        this.session = session;
+        this.connected = true;
+
+        System.out.println("\n✅ WebSocket 连接成功!");
+        System.out.println("Session ID: " + session.getId());
+        System.out.println("服务器地址: " + session.getRequestURI());
+        System.out.println("请求参数: " + session.getRequestParameterMap());
+        System.out.println("\n等待消息...");
+    }
+
+    /**
+     * 收到消息回调
+     */
+    @OnMessage
+    public void onMessage(String message) {
+        System.out.println("\n📨 收到消息: ");
+        System.out.println(message);
+        System.out.print("> ");
+    }
+
+    /**
+     * 连接关闭回调
+     */
+    @OnClose
+    public void onClose(CloseReason closeReason) {
+        System.out.println("\n❌ WebSocket 连接关闭");
+        System.out.println("关闭原因: " + closeReason.getReasonPhrase());
+        System.out.println("关闭代码: " + closeReason.getCloseCode());
+        this.connected = false;
+        closeLatch.countDown();
+    }
+
+    /**
+     * 错误回调
+     */
+    @OnError
+    public void onError(Throwable throwable) {
+        System.err.println("\n⚠️ WebSocket 错误:");
+        this.connectionError = throwable;
+        throwable.printStackTrace();
+    }
+
+    /**
+     * 发送消息
+     */
+    public void sendMessage(String message) {
+        if (session != null && session.isOpen()) {
+            try {
+                session.getBasicRemote().sendText(message);
+                System.out.println("\n📤 发送消息: " + message);
+            } catch (IOException e) {
+                System.err.println("发送消息失败: " + e.getMessage());
+                e.printStackTrace();
+            }
+        } else {
+            System.err.println("连接未打开或已关闭");
+        }
+    }
+
+    /**
+     * 关闭连接
+     */
+    public void close() {
+        if (session != null && session.isOpen()) {
+            try {
+                session.close(new CloseReason(CloseReason.CloseCodes.NORMAL_CLOSURE, "测试结束"));
+            } catch (Exception e) {
+                e.printStackTrace();
+            }
+        }
+    }
+
+    /**
+     * 检查是否已连接
+     */
+    public boolean isConnected() {
+        return connected && session != null && session.isOpen();
+    }
+
+    /**
+     * 获取连接错误
+     */
+    public Throwable getConnectionError() {
+        return connectionError;
+    }
+
+    /**
+     * 等待连接关闭
+     */
+    public boolean awaitClose(long timeout, TimeUnit unit) throws InterruptedException {
+        return closeLatch.await(timeout, unit);
+    }
+
+    public static void main(String[] args) {
+        WebSocketContainer container = null;
+        WebSocketTestClient client = null;
+
+        try {
+            // ==================== 配置区域 ====================
+            String host = "localhost";
+            int port = 7114;
+
+            // 直播间和用户配置
+            long liveId = 27;        // ⚠️ 修改为实际存在的直播间ID
+            long userId = 856648;   // Token 中的用户ID(sub字段)
+            long userType = 0;      // 0=普通用户, 1=管理员
+
+            // JWT Token(从 Postman 生成)
+            String jwtToken = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.eyJzdWIiOiI4NTY2NDgiLCJpYXQiOjE3NzU5NTc2OTEsImV4cCI6MTgwNzQ5MzY5MX0.keB7XqAQcGhpsKjKbaIhmFhxf2443jHUBRvRSaLjqi-igUaPGA37dUBLJ65R89h6g-7pGr-4Cn99GhvnmkpdpQ";
+            String jwtHeader = "AppToken";  // JWT header 参数名
+
+            // 可选参数
+            Long companyId = -1L;
+            Long companyUserId = -1L;
+            String location = "";
+            // ================================================
+
+            // 构建 WebSocket URL
+            StringBuilder urlBuilder = new StringBuilder();
+            urlBuilder.append(String.format("ws://%s:%d/ws/app/webSocket?liveId=%d&userId=%d&userType=%d",
+                    host, port, liveId, userId, userType));
+
+            // 添加 JWT Token
+            urlBuilder.append("&").append(jwtHeader).append("=").append(jwtToken);
+
+            // 添加可选参数
+            if (location != null && !location.isEmpty()) {
+                urlBuilder.append("&location=").append(location);
+            }
+            if (companyId != null && companyId != -1L) {
+                urlBuilder.append("&companyId=").append(companyId);
+            }
+            if (companyUserId != null && companyUserId != -1L) {
+                urlBuilder.append("&companyUserId=").append(companyUserId);
+            }
+
+            String wsUrl = urlBuilder.toString();
+
+            System.out.println("===========================================");
+            System.out.println("WebSocket 连接测试");
+            System.out.println("===========================================");
+            System.out.println("测试时间: " + new java.util.Date());
+            System.out.println("连接地址: " + wsUrl);
+            System.out.println("用户ID: " + userId);
+            System.out.println("直播间ID: " + liveId);
+            System.out.println("用户类型: " + (userType == 0 ? "普通用户" : "管理员"));
+            System.out.println("JWT Header: " + jwtHeader);
+            System.out.println("JWT Token: " + jwtToken.substring(0, Math.min(50, jwtToken.length())) + "...");
+            System.out.println("===========================================\n");
+
+            // 创建 WebSocket 容器
+            container = ContainerProvider.getWebSocketContainer();
+
+            // 设置容器配置
+            container.setDefaultMaxSessionIdleTimeout(60000);
+            container.setDefaultMaxBinaryMessageBufferSize(1024 * 1024);
+            container.setDefaultMaxTextMessageBufferSize(1024 * 1024);
+
+            System.out.println("✓ WebSocket 容器创建成功");
+
+            // 创建客户端实例
+            client = new WebSocketTestClient();
+            System.out.println("✓ 客户端实例创建成功\n");
+
+            System.out.println("正在连接到: " + wsUrl);
+            System.out.println("请稍候...\n");
+
+            // 连接到 WebSocket 服务器
+            long startTime = System.currentTimeMillis();
+            try {
+                container.connectToServer(client, new URI(wsUrl));
+                long connectTime = System.currentTimeMillis() - startTime;
+                System.out.println("✓ 连接请求已发送 (耗时: " + connectTime + "ms)");
+            } catch (Exception e) {
+                long failTime = System.currentTimeMillis() - startTime;
+                System.err.println("✗ 连接请求失败 (耗时: " + failTime + "ms)");
+                throw e;
+            }
+
+            // 等待连接建立(最多等待5秒)
+            System.out.println("等待服务器响应...");
+            for (int i = 0; i < 50; i++) {
+                Thread.sleep(100);
+                if (client.isConnected()) {
+                    break;
+                }
+                if (i % 10 == 0 && i > 0) {
+                    System.out.print(".");
+                }
+            }
+            System.out.println();
+
+            // 检查连接状态
+            if (!client.isConnected()) {
+                System.err.println("\n❌ 连接未能建立!");
+                if (client.getConnectionError() != null) {
+                    System.err.println("连接错误:");
+                    client.getConnectionError().printStackTrace();
+                }
+                System.err.println("\n请检查:");
+                System.err.println("1. fs-live-app 服务是否已启动?");
+                System.err.println("2. 服务是否运行在端口 " + port + "?");
+                System.err.println("3. 数据库中是否存在 live_id=" + liveId + " 的直播间?");
+                System.err.println("4. 数据库中是否存在 user_id=" + userId + " 的用户?");
+                return;
+            }
+
+            Scanner scanner = new Scanner(System.in);
+            System.out.println("\n✅ 连接成功!请输入要发送的消息:");
+            System.out.println("   - 输入 'heartbeat' 发送心跳消息");
+            System.out.println("   - 输入其他文本发送聊天消息");
+            System.out.println("   - 输入 'quit' 或 'exit' 退出\n");
+
+            while (true) {
+                System.out.print("> ");
+                String input = scanner.nextLine();
+
+                if ("quit".equalsIgnoreCase(input) || "exit".equalsIgnoreCase(input)) {
+                    break;
+                }
+
+                if (input.trim().isEmpty()) {
+                    continue;
+                }
+
+                if ("heartbeat".equalsIgnoreCase(input)) {
+                    String heartbeatMsg = String.format(
+                            "{\"cmd\":\"heartbeat\",\"liveId\":%d,\"userId\":%d,\"userType\":%d}",
+                            liveId, userId, userType
+                    );
+                    client.sendMessage(heartbeatMsg);
+                } else {
+                    String chatMsg = String.format(
+                            "{\"cmd\":\"sendMsg\",\"liveId\":%d,\"userId\":%d,\"userType\":%d,\"nickName\":\"测试用户\",\"avatar\":\"\",\"msg\":\"%s\"}",
+                            liveId, userId, userType, input
+                    );
+                    client.sendMessage(chatMsg);
+                }
+            }
+
+            scanner.close();
+
+        } catch (Exception e) {
+            System.err.println("\n❌ 连接失败: " + e.getClass().getSimpleName() + ": " + e.getMessage());
+
+            // 打印完整堆栈
+            System.err.println("\n📋 完整异常堆栈:");
+            e.printStackTrace();
+
+            // 分析错误原因
+            System.err.println("\n💡 错误分析:");
+            System.err.println("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
+
+            String errorMsg = e.getMessage();
+            if (errorMsg != null) {
+                if (errorMsg.contains("500")) {
+                    System.err.println("🔴 HTTP 500 错误 - 服务器内部错误");
+                    System.err.println("   可能原因:");
+                    System.err.println("   1. 服务器端握手处理抛出异常");
+                    System.err.println("   2. 数据库中不存在对应的 liveId 或 userId");
+                    System.err.println("   3. JWT Token 验证失败");
+                    System.err.println("   4. Spring Bean 注入失败");
+                    System.err.println();
+                    System.err.println("   ✅ 解决方案:");
+                    System.err.println("   1. 查看 fs-live-app 的控制台输出");
+                    System.err.println("   2. 查看日志文件: fs-live-app/logs/");
+                    System.err.println("   3. 确认 liveId=27 是否存在于数据库");
+                    System.err.println("   4. 确认 userId=856648 是否存在于数据库");
+
+                } else if (errorMsg.contains("404")) {
+                    System.err.println("🔴 HTTP 404 错误 - 路径不存在");
+                    System.err.println("   可能原因: WebSocket 端点路径错误");
+                    System.err.println("   正确路径: /ws/app/webSocket");
+
+                } else if (errorMsg.contains("401") || errorMsg.contains("403")) {
+                    System.err.println("🔴 HTTP " + (errorMsg.contains("401") ? "401" : "403") + " 错误 - 认证失败");
+                    System.err.println("   可能原因: JWT Token 无效或过期");
+                    System.err.println("   解决方案: 重新生成 Token");
+
+                } else if (errorMsg.contains("Connection refused") || errorMsg.contains("ConnectException")) {
+                    System.err.println("🔴 连接被拒绝");
+                    System.err.println("   可能原因: 服务未启动或端口错误");
+                    System.err.println("   解决方案:");
+                    System.err.println("   1. 启动 fs-live-app 服务");
+                    System.err.println("   2. 确认服务运行在端口 7114");
+                    System.err.println("   3. 访问 http://localhost:7114 测试");
+
+                } else if (errorMsg.contains("timeout") || errorMsg.contains("Timeout")) {
+                    System.err.println("🔴 连接超时");
+                    System.err.println("   可能原因: 网络问题或服务器无响应");
+                    System.err.println("   解决方案: 检查网络连接和服务器状态");
+                }
+            }
+
+            System.err.println("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
+
+        } finally {
+            if (client != null) {
+                client.close();
+            }
+            System.out.println("\n测试结束");
+        }
+    }
+}

+ 6 - 0
fs-service/src/main/java/com/fs/live/domain/Live.java

@@ -131,6 +131,12 @@ public class   Live extends BaseEntity {
     private Long videoDuration;
     private Integer globalVisible;
 
+    private String roomPassword;
+
+    // 是否需要秘钥
+    @TableField(exist = false)
+    private Boolean isNeedPassword;
+
     @TableField(exist = false)
     private List<LiveTagItemVO> liveTagList;
 }

+ 49 - 0
fs-service/src/main/java/com/fs/live/domain/LiveSignRecord.java

@@ -0,0 +1,49 @@
+package com.fs.live.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.fs.common.annotation.Excel;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * 直播签到记录对象 live_sign_record
+ *
+ * @author ylrz
+ * @date 2026-04-07
+ */
+@Data
+@TableName("live_sign_record")
+public class LiveSignRecord implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    /** 主键ID */
+    @TableId(type = IdType.AUTO)
+    private Long id;
+
+    /** 直播ID */
+    @Excel(name = "直播ID")
+    private Long liveId;
+
+    /** 用户ID */
+    @Excel(name = "用户ID")
+    private Long userId;
+
+    /** 用户名称 */
+    @Excel(name = "用户名称")
+    private String userName;
+
+    /** 签到序号(第几次签到) */
+    @Excel(name = "签到序号")
+    private String signNo;
+
+    /** 签到时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @Excel(name = "签到时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")
+    private Date createTime;
+}
+

+ 17 - 0
fs-service/src/main/java/com/fs/live/mapper/LiveSignRecordMapper.java

@@ -0,0 +1,17 @@
+package com.fs.live.mapper;
+
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.fs.live.domain.LiveSignRecord;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * 直播签到记录Mapper接口
+ *
+ * @author ylrz
+ * @date 2026-04-07
+ */
+@Mapper
+public interface LiveSignRecordMapper extends BaseMapper<LiveSignRecord> {
+
+}

+ 2 - 0
fs-service/src/main/java/com/fs/live/service/ILiveService.java

@@ -217,4 +217,6 @@ public interface ILiveService
      * @return
      */
     List<CompanyVO> getCompanyDropList();
+
+    R checkLiveRoomPassword(Live live);
 }

+ 26 - 0
fs-service/src/main/java/com/fs/live/service/ILiveSignRecordService.java

@@ -0,0 +1,26 @@
+package com.fs.live.service;
+
+
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.fs.live.domain.LiveSignRecord;
+
+import java.util.List;
+
+/**
+ * 直播签到记录Service接口
+ *
+ * @author ylrz
+ * @date 2026-04-07
+ */
+public interface ILiveSignRecordService extends IService<LiveSignRecord> {
+
+    /**
+     * 查询直播签到记录列表
+     *
+     * @param liveSignRecord 直播签到记录
+     * @return 直播签到记录集合
+     */
+    List<LiveSignRecord> selectLiveSignRecordList(LiveSignRecord liveSignRecord);
+}
+

+ 5 - 0
fs-service/src/main/java/com/fs/live/service/impl/LiveAutoTaskServiceImpl.java

@@ -178,6 +178,9 @@ public class LiveAutoTaskServiceImpl implements ILiveAutoTaskService {
         } else if(liveAutoTask.getTaskType() == 3L){
             baseMapper.insertLiveAutoTask(liveAutoTask);
 
+        }else if(liveAutoTask.getTaskType() == 7L){
+            baseMapper.insertLiveAutoTask(liveAutoTask);
+
         } else {
             return R.error("任务类型错误");
         }
@@ -315,6 +318,8 @@ public class LiveAutoTaskServiceImpl implements ILiveAutoTaskService {
                 log.error("上架/下架商品自动化任务,更新异常!" + e.getMessage());
                 return R.error("上架/下架商品自动化任务,更新异常!");
             }
+        } else if(liveAutoTask.getTaskType() == 7L){
+            baseMapper.updateLiveAutoTask(liveAutoTask);
         } else {
             return R.error("任务类型错误");
         }

+ 45 - 0
fs-service/src/main/java/com/fs/live/service/impl/LiveServiceImpl.java

@@ -176,6 +176,29 @@ public class LiveServiceImpl implements ILiveService
        return  companyMapper.getCompanyDropList();
     }
 
+    /**
+     * @Description: 校验房间密码
+     * @Param:
+     * @Return:
+     * @Author xgb
+     * @Date 2026/4/10 15:47
+     */
+    @Override
+    public R checkLiveRoomPassword(Live live) {
+
+        String cacheKey = String.format(LiveKeysConstant.LIVE_ROOM_PASSWORD_CACHE, live.getLiveId());
+        String password = redisCache.getCacheObject(cacheKey);
+        if (StringUtils.isNotEmpty(password)) {
+            if (password.equals(live.getRoomPassword())) {
+                return R.ok();
+            }else {
+                return R.error("密码错误");
+            }
+        }else {
+            return R.error("密码错误");
+        }
+    }
+
 
     /**
      * 查询直播
@@ -274,6 +297,10 @@ public class LiveServiceImpl implements ILiveService
         }
 		// Long storeId = liveGoodsService.getStoreIdByLiveId(live.getLiveId());
 		LiveVo liveVo = new LiveVo();
+        if(StringUtils.isNotBlank(live.getRoomPassword())){
+            liveVo.setIsNeedPassword(true);
+        }
+
         // liveVo.setStoreId(storeId);
 		BeanUtils.copyProperties(live, liveVo);
 		liveVo.setNowDuration(200L);
@@ -635,6 +662,17 @@ public class LiveServiceImpl implements ILiveService
 
         }
         log.error("updateLive:" + live.getLiveId());
+        if(live.getRoomPassword() != null){
+            if(live.getRoomPassword().isEmpty()){
+                // 清空房间密码
+                String cacheKey = String.format(LiveKeysConstant.LIVE_ROOM_PASSWORD_CACHE, live.getLiveId());
+                redisCache.deleteObject(cacheKey);
+            } else {
+                // 设置房间密码并缓存
+                String cacheKey = String.format(LiveKeysConstant.LIVE_ROOM_PASSWORD_CACHE, live.getLiveId());
+                redisCache.setCacheObject(cacheKey, live.getRoomPassword());
+            }
+        }
         int result = baseMapper.updateLive(live);
         liveAutoTaskService.recalcLiveAutoTask(live);
 
@@ -700,6 +738,13 @@ public class LiveServiceImpl implements ILiveService
     public List<Live> liveList() {
         List<Long> companyIds = companyMapper.selectLiveShowCompanyId();
         List<Live> livesList = baseMapper.liveShowList(companyIds);
+        livesList.forEach(live -> {
+            if(StringUtils.isNotEmpty(live.getRoomPassword())){
+                live.setRoomPassword("");
+                live.setIsNeedPassword(true);
+            }
+        });
+
         try {
             // 查询准备开播的直播间
             livesList.addAll(baseMapper.selectLiveShowReadyStartLiveList(companyIds));

+ 41 - 0
fs-service/src/main/java/com/fs/live/service/impl/LiveSignRecordServiceImpl.java

@@ -0,0 +1,41 @@
+package com.fs.live.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.fs.live.domain.LiveSignRecord;
+import com.fs.live.mapper.LiveSignRecordMapper;
+import com.fs.live.service.ILiveSignRecordService;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+
+/**
+ * 直播签到记录Service业务层处理
+ *
+ * @author ylrz
+ * @date 2026-04-07
+ */
+@Service
+public class LiveSignRecordServiceImpl extends ServiceImpl<LiveSignRecordMapper, LiveSignRecord> implements ILiveSignRecordService {
+
+    /**
+     * 查询直播签到记录列表
+     *
+     * @param liveSignRecord 直播签到记录
+     * @return 直播签到记录
+     */
+    @Override
+    public List<LiveSignRecord> selectLiveSignRecordList(LiveSignRecord liveSignRecord) {
+        LambdaQueryWrapper<LiveSignRecord> wrapper = new LambdaQueryWrapper<>();
+        wrapper.eq(liveSignRecord.getLiveId() != null, LiveSignRecord::getLiveId, liveSignRecord.getLiveId())
+                .eq(liveSignRecord.getUserId() != null, LiveSignRecord::getUserId, liveSignRecord.getUserId())
+                .like(liveSignRecord.getUserName() != null && !liveSignRecord.getUserName().isEmpty(),
+                        LiveSignRecord::getUserName, liveSignRecord.getUserName())
+                .eq(liveSignRecord.getSignNo() != null && !liveSignRecord.getSignNo().isEmpty(),
+                        LiveSignRecord::getSignNo, liveSignRecord.getSignNo())
+                .orderByDesc(LiveSignRecord::getCreateTime);
+        return this.list(wrapper);
+    }
+}
+

+ 5 - 2
fs-service/src/main/java/com/fs/live/vo/LiveVo.java

@@ -33,7 +33,7 @@ public class LiveVo {
     private String liveImgUrl;
 
     private String liveConfig;
-    
+
     /** 直播配置 */
     private String configJson;
 
@@ -62,7 +62,10 @@ public class LiveVo {
 
     /** 是否开启直播完课积分功能 */
     private Boolean completionPointsEnabled;
-    
+
     /** 今天是否已领取完课奖励 */
     private Boolean todayRewardReceived;
+
+    // 是否需要秘钥
+    private Boolean isNeedPassword;
 }

+ 5 - 3
fs-service/src/main/resources/mapper/live/LiveMapper.xml

@@ -33,12 +33,13 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         <result property="idCardUrl"    column="id_card_url"    />
         <result property="liveCodeUrl"    column="live_code_url"    />
         <result property="globalVisible"    column="global_visible"    />
+        <result property="roomPassword"    column="room_password"    />
     </resultMap>
 
     <sql id="selectLiveVo">
         select live_id, company_id, company_user_id,talent_id, live_name, is_audit, live_desc, show_type, status, anchor_id, live_type, start_time, finish_time,
                live_img_url, live_config, id_card_url, is_show, is_del, qw_qr_code, rtmp_url, flv_hls_url,
-               create_time, create_by, update_by, update_time, remark,config_json,global_visible from live
+               create_time, create_by, update_by, update_time, remark,config_json,global_visible,room_password from live
     </sql>
 
     <select id="liveList" parameterType="Live" resultMap="LiveResult">
@@ -70,7 +71,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
     <select id="selectLiveList" parameterType="com.fs.live.domain.Live" resultMap="LiveResult">
         select a.live_id, a.company_id, a.company_user_id,talent_id, a.live_name, a.is_audit, a.live_desc, a.show_type, a.status, a.anchor_id,
                a.live_type, a.start_time, a.finish_time, a.live_img_url, a.live_config, a.id_card_url, a.is_show, a.is_del, a.qw_qr_code, a.rtmp_url,
-               a.flv_hls_url, a.create_time, a.create_by, a.update_by, a.update_time, a.remark,config_json, b.video_url,a.global_visible
+               a.flv_hls_url, a.create_time, a.create_by, a.update_by, a.update_time, a.remark,config_json, b.video_url,a.global_visible,a.room_password
                 ,c.live_code_url,IFNULL(d.company_name, '总台') AS company_name,b.file_size
 
         from live a
@@ -222,6 +223,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="isAudit != null">is_audit = #{isAudit},</if>
             <if test="idCardUrl != null">id_card_url = #{idCardUrl},</if>
             <if test="globalVisible != null">global_visible = #{globalVisible},</if>
+            <if test="roomPassword != null">room_password = #{roomPassword},</if>
         </trim>
         where live_id = #{liveId}
     </update>
@@ -322,7 +324,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
 
     <select id="liveShowList"  resultMap="LiveResult">
         select a.live_id, a.company_id, a.company_user_id,talent_id, a.live_name, a.is_audit, a.live_desc, a.show_type, a.status, a.anchor_id,
-        a.live_type, a.start_time, a.finish_time, a.live_img_url, a.live_config, a.id_card_url, a.is_show, a.is_del, a.qw_qr_code, a.rtmp_url,
+        a.live_type, a.start_time, a.finish_time, a.live_img_url, a.live_config, a.id_card_url, a.is_show, a.is_del, a.qw_qr_code, a.rtmp_url,a.room_password,
         a.flv_hls_url, a.create_time, a.create_by, a.update_by, a.update_time, a.remark,config_json, b.video_url,c.company_name,b.file_size
         from live
         a

+ 23 - 9
fs-user-app/src/main/java/com/fs/app/controller/live/LiveController.java

@@ -332,10 +332,10 @@ public class LiveController extends AppBaseController {
 
 	@Autowired
 	private WxMaProperties properties;
-	
+
 	@Autowired
 	private RedisCache redisCache;
-	
+
 	@ApiOperation("微信直播间urlScheme")
 	@GetMapping("/getAppletScheme")
 	public R getAppletScheme(@RequestParam(value = "liveId") Long liveId,@RequestParam(value = "companyUserId") Long companyUserId) {
@@ -356,37 +356,37 @@ public class LiveController extends AppBaseController {
 			String param = "liveId=" + liveId + "&companyUserId=" + companyUser.getUserId() + "&companyId=" + companyUser.getCompanyId() ;
 			String appId = properties.getConfigs().get(0).getAppid();
 			String secret = properties.getConfigs().get(0).getSecret();
-			
+
 			// 从 Redis 缓存中获取 access_token
 			String cacheKey = "wx:access_token:" + appId;
 			String access_token = redisCache.getCacheObject(cacheKey);
-			
+
 			// 如果缓存中没有或已过期,则重新获取
 			if (StringUtils.isEmpty(access_token)) {
 				String rspStr = HttpUtils.sendGet("https://api.weixin.qq.com/cgi-bin/token", "grant_type=client_credential&" + "appid=" + appId + "&secret=" + secret);
 				JSONObject obj = JSONObject.parseObject(rspStr);
 				access_token = obj.getString("access_token");
-				
+
 				// 检查是否获取成功
 				if (StringUtils.isEmpty(access_token)) {
 					log.error("获取微信 access_token 失败: {}", obj);
 					return R.error("获取微信 access_token 失败");
 				}
-				
+
 				// 将 access_token 存入 Redis,缓存时间为 7200 秒
 				redisCache.setCacheObject(cacheKey, access_token, 7200, java.util.concurrent.TimeUnit.SECONDS);
 				log.info("微信 access_token 已刷新并缓存,appId: {}", appId);
 			} else {
 				log.debug("从 Redis 缓存中获取 access_token,appId: {}", appId);
 			}
-			
+
 			JSONObject jump_wxaObj = new JSONObject();
 			// 跳转直播间
 			jump_wxaObj.put("page_url", "pages_course/living.html?" + param);
 			String paramStr = jump_wxaObj.toJSONString();
 			String postStr = HttpUtils.sendPost("https://api.weixin.qq.com/wxa/genwxashortlink?access_token=" + access_token, paramStr);
 			JSONObject obj = JSONObject.parseObject(postStr);
-			
+
 			// 如果 access_token 失效,清除缓存并重新获取
 			if (obj != null && (obj.getInteger("errcode") != null && obj.getInteger("errcode") == 40001)) {
 				log.warn("access_token 已失效,清除缓存并重新获取,appId: {}", appId);
@@ -402,7 +402,7 @@ public class LiveController extends AppBaseController {
 					obj = JSONObject.parseObject(postStr);
 				}
 			}
-			
+
 			//response.addHeader("Access-Control-Allow-Origin", "*");
 			return R.ok().put("result", obj);
 		} catch (Exception e) {
@@ -411,6 +411,20 @@ public class LiveController extends AppBaseController {
 		}
 	}
 
+	/**
+	 * @Description: 直播间密码校验
+	 * @Param:
+	 * @Return:
+	 * @Author xgb
+	 * @Date 2026/4/10 15:41
+	 */
+	@PostMapping("/checkLiveRoomPassword")
+	@Login
+	public R checkLiveRoomPassword(@RequestBody Live live) {
+		return liveService.checkLiveRoomPassword(live);
+	}
+
+
 
 
 

+ 65 - 0
fs-user-app/src/main/java/com/fs/app/controller/live/LiveSignController.java

@@ -0,0 +1,65 @@
+package com.fs.app.controller.live;
+
+import com.fs.app.annotation.Login;
+import com.fs.app.controller.AppBaseController;
+import com.fs.common.core.domain.R;
+import com.fs.live.domain.LiveSignRecord;
+import com.fs.live.service.ILiveSignRecordService;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * @description: xgb 直播签到功能
+ * @author: Xgb
+ * @createDate: 2026/4/9
+ * @version: 1.0
+ */
+@Slf4j
+@RestController
+@RequestMapping("/app/live/liveSign")
+public class LiveSignController extends AppBaseController {
+
+    @Autowired
+    private ILiveSignRecordService liveSignRecordService;
+
+    /**
+     * 用户签到
+     * @param params 请求参数,包含 liveId 和 signNo
+     * @return 签到结果
+     */
+    @PostMapping("/sign")
+    @Login
+    public R liveSign(@RequestBody LiveSignRecord params) {
+        try {
+            // 检查是否已经签到过
+            params.setUserId(Long.parseLong(getUserId()));
+
+            long count = liveSignRecordService.lambdaQuery()
+                    .eq(LiveSignRecord::getLiveId, params.getLiveId())
+                    .eq(LiveSignRecord::getUserId, params.getUserId())
+                    .eq(LiveSignRecord::getSignNo, params.getSignNo())
+                    .count();
+
+            if (count > 0) {
+                return R.error("您已经签到过了");
+            }
+
+
+            // 插入数据库
+            boolean success = liveSignRecordService.save(params);
+
+            if (success) {
+                return R.ok("签到成功");
+            } else {
+                return R.error("签到失败,请稍后重试");
+            }
+        } catch (Exception e) {
+            log.error("签到异常: error={}", e.getMessage(), e);
+            return R.error("签到异常");
+        }
+    }
+}

+ 13 - 11
fs-user-app/src/main/java/com/fs/app/facade/impl/LiveFacadeServiceImpl.java

@@ -53,7 +53,7 @@ public class LiveFacadeServiceImpl extends BaseController implements LiveFacadeS
 
     @Autowired
     private ILiveLotteryConfService liveLotteryConfService;
-    
+
     @Autowired
     private ILiveCompletionPointsRecordService completionPointsRecordService;
 
@@ -133,32 +133,34 @@ public class LiveFacadeServiceImpl extends BaseController implements LiveFacadeS
         if(liveVo.getIsShow() == 2) {
             return R.error("直播未开放");
         }
-        
+
+
+
         // 查询用户今天是否已领取完课奖励
         if (userId != null) {
             try {
-                List<LiveCompletionPointsRecord> unreceivedRecords = 
+                List<LiveCompletionPointsRecord> unreceivedRecords =
                     completionPointsRecordService.getUserUnreceivedRecords(id, userId);
-                
+
                 // 判断是否有未领取的奖励,如果有则说明今天还未领取
                 // 如果没有未领取的,再查询是否有已领取的记录
                 if (unreceivedRecords != null && !unreceivedRecords.isEmpty()) {
                     liveVo.setTodayRewardReceived(false);
                 } else {
                     // 查询所有记录(包括已领取和未领取)
-                    List<LiveCompletionPointsRecord> allRecords = 
+                    List<LiveCompletionPointsRecord> allRecords =
                         completionPointsRecordService.getUserRecords(id, userId);
-                    
+
                     if (allRecords != null && !allRecords.isEmpty()) {
                         // 检查最近一条记录是否是今天的且已领取
                         LiveCompletionPointsRecord latestRecord = allRecords.get(0);
                         Date today = new Date();
                         Date recordDate = latestRecord.getCurrentCompletionDate();
-                        
+
                         // 判断是否为同一天
-                        boolean isSameDay = recordDate != null && 
+                        boolean isSameDay = recordDate != null &&
                             isSameDay(recordDate, today);
-                        
+
                         if (isSameDay && latestRecord.getReceiveStatus() == 1) {
                             liveVo.setTodayRewardReceived(true);
                         } else {
@@ -175,13 +177,13 @@ public class LiveFacadeServiceImpl extends BaseController implements LiveFacadeS
         } else {
             liveVo.setTodayRewardReceived(false);
         }
-        
+
         return R.ok().put("serviceTime", System.currentTimeMillis())
                 .put("serviceStartTime", liveVo.getStartTime() == null ? null : liveVo.getStartTime().atZone(ZONE_ID).toInstant().toEpochMilli())
                 .put("serviceEndTime", liveVo.getFinishTime() == null ? null : liveVo.getFinishTime().atZone(ZONE_ID).toInstant().toEpochMilli())
                 .put("data", liveVo);
     }
-    
+
     /**
      * 判断两个日期是否为同一天
      */