From b9679b6f96bbe8b52ee1d699033d9021d5fe870f Mon Sep 17 00:00:00 2001
From: mitao <2763622819@qq.com>
Date: 星期五, 10 一月 2025 18:35:06 +0800
Subject: [PATCH] 1.小鹅通直播课程接口对接 2.商户端直播课程接口

---
 ruoyi-modules/ruoyi-goods/src/main/java/com/ruoyi/goods/domain/dto/XiaoeLiveDto.java                           |   69 ++
 ruoyi-modules/ruoyi-goods/src/main/java/com/ruoyi/goods/controller/miniapp/AppCourseController.java            |   57 +
 ruoyi-modules/ruoyi-goods/src/main/java/com/ruoyi/goods/mapper/live/XiaoeLiveRecordMapper.java                 |   17 
 ruoyi-modules/ruoyi-goods/src/main/resources/mapper/live/XiaoeLiveAppointmentMapper.xml                        |    5 
 ruoyi-modules/ruoyi-goods/src/main/java/com/ruoyi/goods/domain/vo/XiaoeLiveTeacherVo.java                      |   23 
 ruoyi-modules/ruoyi-goods/src/main/java/com/ruoyi/goods/service/biz/XiaoeLiveService.java                      |  249 ++++++++
 ruoyi-modules/ruoyi-goods/src/main/java/com/ruoyi/goods/domain/vo/XiaoeCourseGroupVO.java                      |   20 
 ruoyi-modules/ruoyi-goods/src/main/java/com/ruoyi/goods/domain/vo/XiaoeCourseChapterVO.java                    |   36 +
 ruoyi-modules/ruoyi-goods/src/main/java/com/ruoyi/goods/service/live/IXiaoeLiveAppointmentService.java         |   22 
 ruoyi-api/ruoyi-api-system/src/main/java/com/ruoyi/system/api/factory/RemoteGoodsFallbackFactory.java          |    5 
 ruoyi-modules/ruoyi-goods/src/main/java/com/ruoyi/goods/controller/concole/LiveController.java                 |   33 +
 ruoyi-modules/ruoyi-goods/src/main/java/com/ruoyi/goods/service/biz/XiaoeCourseService.java                    |   39 +
 ruoyi-modules/ruoyi-goods/src/main/java/com/ruoyi/goods/controller/miniapp/AppLiveController.java              |   54 +
 ruoyi-modules/ruoyi-shop/src/main/java/com/ruoyi/shop/domain/dto/MgtEditShopDto.java                           |    3 
 ruoyi-api/ruoyi-api-system/src/main/java/com/ruoyi/system/api/constant/DelayTaskEnum.java                      |    1 
 ruoyi-modules/ruoyi-goods/src/main/java/com/ruoyi/goods/service/impl/live/XiaoeLiveAppointmentServiceImpl.java |   57 +
 ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/listener/RedisListener.java                          |   16 
 ruoyi-modules/ruoyi-shop/src/main/java/com/ruoyi/shop/domain/vo/MgtShopInfoVo.java                             |    3 
 ruoyi-modules/ruoyi-goods/src/main/java/com/ruoyi/goods/controller/business/MerLiveController.java             |  101 +++
 ruoyi-modules/ruoyi-goods/src/main/resources/mapper/live/XiaoeLiveRecordMapper.xml                             |    5 
 ruoyi-modules/ruoyi-goods/src/main/java/com/ruoyi/goods/domain/dto/XiaoeCourseQueryDto.java                    |   45 +
 ruoyi-modules/ruoyi-goods/src/main/java/com/ruoyi/goods/mapper/live/XiaoeLiveAppointmentMapper.java            |   17 
 ruoyi-modules/ruoyi-goods/src/main/java/com/ruoyi/goods/domain/vo/XiaoeLiveVo.java                             |  109 +++
 ruoyi-modules/ruoyi-goods/src/main/java/com/ruoyi/goods/utils/WeChatSubscribeMessageSender.java                |   76 ++
 ruoyi-api/ruoyi-api-system/src/main/java/com/ruoyi/system/api/service/RemoteGoodsService.java                  |   10 
 ruoyi-api/ruoyi-api-system/src/main/java/com/ruoyi/system/api/domain/poji/shop/Shop.java                       |    6 
 ruoyi-modules/ruoyi-goods/src/main/java/com/ruoyi/goods/service/impl/live/XiaoeLiveRecordServiceImpl.java      |   61 +
 ruoyi-modules/ruoyi-goods/src/main/java/com/ruoyi/goods/domain/pojo/live/XiaoeLiveRecord.java                  |   64 ++
 ruoyi-modules/ruoyi-goods/src/main/java/com/ruoyi/goods/domain/vo/XiaoeCourseVO.java                           |   45 +
 ruoyi-modules/ruoyi-goods/src/main/java/com/ruoyi/goods/domain/pojo/live/XiaoeLiveAppointment.java             |   63 ++
 ruoyi-modules/ruoyi-goods/src/main/java/com/ruoyi/goods/controller/business/MerCourseController.java           |   57 +
 ruoyi-modules/ruoyi-goods/src/main/java/com/ruoyi/goods/domain/dto/XiaoeLiveQueryDto.java                      |   35 +
 ruoyi-common/ruoyi-common-core/pom.xml                                                                         |    5 
 ruoyi-modules/ruoyi-goods/src/main/java/com/ruoyi/goods/service/live/IXiaoeLiveRecordService.java              |   33 +
 ruoyi-modules/ruoyi-goods/src/main/java/com/ruoyi/goods/utils/XiaoeUtils.java                                  |  375 ++++++++++++
 35 files changed, 1,816 insertions(+), 0 deletions(-)

diff --git a/ruoyi-api/ruoyi-api-system/src/main/java/com/ruoyi/system/api/constant/DelayTaskEnum.java b/ruoyi-api/ruoyi-api-system/src/main/java/com/ruoyi/system/api/constant/DelayTaskEnum.java
index d22b973..f9781f6 100644
--- a/ruoyi-api/ruoyi-api-system/src/main/java/com/ruoyi/system/api/constant/DelayTaskEnum.java
+++ b/ruoyi-api/ruoyi-api-system/src/main/java/com/ruoyi/system/api/constant/DelayTaskEnum.java
@@ -14,6 +14,7 @@
     COUPON_SEND_DELAY_TASK("优惠券延时任务","定时启动优惠券发送"),
     ACTIVITY_START_TASK("活动延时任务","定时开始任务"),
     ACTIVITY_END_TASK("活动延时任务","定时结束任务"),
+    LIVE_APPOINTMENT_TASK("直播预约任务","直播预约任务")
     ;
 
     String name;
diff --git a/ruoyi-api/ruoyi-api-system/src/main/java/com/ruoyi/system/api/domain/poji/shop/Shop.java b/ruoyi-api/ruoyi-api-system/src/main/java/com/ruoyi/system/api/domain/poji/shop/Shop.java
index 11230b8..ed91abb 100644
--- a/ruoyi-api/ruoyi-api-system/src/main/java/com/ruoyi/system/api/domain/poji/shop/Shop.java
+++ b/ruoyi-api/ruoyi-api-system/src/main/java/com/ruoyi/system/api/domain/poji/shop/Shop.java
@@ -292,6 +292,12 @@
     @TableField("shop_code")
     private String shopCode;
 
+    /**
+     * 小鹅通讲师id
+     */
+    @TableField("xiaoe_guest_id")
+    private String xiaoeUserId;
+
 
     @Override
     protected Serializable pkVal() {
diff --git a/ruoyi-api/ruoyi-api-system/src/main/java/com/ruoyi/system/api/factory/RemoteGoodsFallbackFactory.java b/ruoyi-api/ruoyi-api-system/src/main/java/com/ruoyi/system/api/factory/RemoteGoodsFallbackFactory.java
index 8513675..a5e8c61 100644
--- a/ruoyi-api/ruoyi-api-system/src/main/java/com/ruoyi/system/api/factory/RemoteGoodsFallbackFactory.java
+++ b/ruoyi-api/ruoyi-api-system/src/main/java/com/ruoyi/system/api/factory/RemoteGoodsFallbackFactory.java
@@ -80,6 +80,11 @@
             public R<List<String>> listGoodsNameByGoodsClass(Long classId) {
                 return R.fail("获取分类商品列表失败:" + throwable.getMessage());
             }
+
+            @Override
+            public R<?> push(Long appointmentId) {
+                return R.fail("推送小程序订阅消息失败:" + throwable.getMessage());
+            }
         };
     }
 }
diff --git a/ruoyi-api/ruoyi-api-system/src/main/java/com/ruoyi/system/api/service/RemoteGoodsService.java b/ruoyi-api/ruoyi-api-system/src/main/java/com/ruoyi/system/api/service/RemoteGoodsService.java
index b82e6a5..0fca1fe 100644
--- a/ruoyi-api/ruoyi-api-system/src/main/java/com/ruoyi/system/api/service/RemoteGoodsService.java
+++ b/ruoyi-api/ruoyi-api-system/src/main/java/com/ruoyi/system/api/service/RemoteGoodsService.java
@@ -11,6 +11,8 @@
 import com.ruoyi.system.api.domain.vo.MerGoodsPriceListVo;
 import com.ruoyi.system.api.factory.RemoteGoodsFallbackFactory;
 import org.springframework.cloud.openfeign.FeignClient;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
 import org.springframework.web.bind.annotation.PostMapping;
 import org.springframework.web.bind.annotation.RequestBody;
 
@@ -78,4 +80,12 @@
      */
     @PostMapping("/goods/listGoodsNameByGoodsClass")
     public R<List<String>> listGoodsNameByGoodsClass(@RequestBody Long classId);
+
+    /**
+     * 推送微信小程序订阅消息
+     * @param appointmentId
+     * @return
+     */
+    @GetMapping("/live/push/{appointmentId}")
+    R<?> push(@PathVariable("appointmentId") Long appointmentId);
 }
diff --git a/ruoyi-common/ruoyi-common-core/pom.xml b/ruoyi-common/ruoyi-common-core/pom.xml
index ed4df6a..ad70db5 100644
--- a/ruoyi-common/ruoyi-common-core/pom.xml
+++ b/ruoyi-common/ruoyi-common-core/pom.xml
@@ -118,6 +118,11 @@
             <groupId>org.projectlombok</groupId>
             <artifactId>lombok</artifactId>
         </dependency>
+        <dependency>
+            <groupId>cn.hutool</groupId>
+            <artifactId>hutool-all</artifactId>
+            <version>5.8.22</version>
+        </dependency>
 
     </dependencies>
 
diff --git a/ruoyi-modules/ruoyi-goods/src/main/java/com/ruoyi/goods/controller/business/MerCourseController.java b/ruoyi-modules/ruoyi-goods/src/main/java/com/ruoyi/goods/controller/business/MerCourseController.java
new file mode 100644
index 0000000..8302029
--- /dev/null
+++ b/ruoyi-modules/ruoyi-goods/src/main/java/com/ruoyi/goods/controller/business/MerCourseController.java
@@ -0,0 +1,57 @@
+package com.ruoyi.goods.controller.business;
+
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.ruoyi.common.core.domain.R;
+import com.ruoyi.goods.domain.dto.XiaoeCourseQueryDto;
+import com.ruoyi.goods.domain.vo.XiaoeCourseChapterVO;
+import com.ruoyi.goods.domain.vo.XiaoeCourseVO;
+import com.ruoyi.goods.service.biz.XiaoeCourseService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiParam;
+import lombok.RequiredArgsConstructor;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+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;
+
+import javax.validation.Valid;
+import java.util.List;
+
+/**
+ * @author mitao
+ * @date 2025/1/10
+ */
+@Api(tags = {"商户端海恒课程相关接口"})
+@Validated
+@RestController
+@RequestMapping("/mer/course")
+@RequiredArgsConstructor
+public class MerCourseController {
+    private final XiaoeCourseService xiaoeCourseService;
+
+    /**
+     * 课程列表
+     * @param dto
+     * @return
+     */
+    @ApiOperation("课程列表")
+    @PostMapping("/page")
+    public R<Page<XiaoeCourseVO>> page(@Valid @RequestBody XiaoeCourseQueryDto dto) {
+        return R.ok(xiaoeCourseService.getCoursePageList(dto));
+    }
+
+    /**
+     * 课程章节详情
+     * @param id
+     * @return
+     */
+    @ApiOperation("课程章节详情")
+    @GetMapping("/detail/{id}")
+    public R<List<XiaoeCourseChapterVO>> detail(@ApiParam(name = "id",value = "课程id",required = true)@PathVariable("id") String id) {
+        return R.ok(xiaoeCourseService.getCourseDetail(id));
+    }
+}
diff --git a/ruoyi-modules/ruoyi-goods/src/main/java/com/ruoyi/goods/controller/business/MerLiveController.java b/ruoyi-modules/ruoyi-goods/src/main/java/com/ruoyi/goods/controller/business/MerLiveController.java
new file mode 100644
index 0000000..2efc448
--- /dev/null
+++ b/ruoyi-modules/ruoyi-goods/src/main/java/com/ruoyi/goods/controller/business/MerLiveController.java
@@ -0,0 +1,101 @@
+package com.ruoyi.goods.controller.business;
+
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.ruoyi.common.core.domain.R;
+import com.ruoyi.goods.domain.dto.XiaoeLiveDto;
+import com.ruoyi.goods.domain.dto.XiaoeLiveQueryDto;
+import com.ruoyi.goods.domain.vo.XiaoeLiveVo;
+import com.ruoyi.goods.service.biz.XiaoeLiveService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiParam;
+import lombok.RequiredArgsConstructor;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.validation.Valid;
+import java.util.List;
+
+/**
+ * @author mitao
+ * @date 2025/1/9
+ */
+@Validated
+@RestController
+@RequiredArgsConstructor
+@Api(tags = "商户端直播相关接口")
+@RequestMapping("/mer/live")
+public class MerLiveController {
+    private final XiaoeLiveService xiaoeLiveService;
+
+    /**
+     * 直播首页分页列表
+     * @param dto
+     * @return
+     */
+    @ApiOperation("直播首页分页列表")
+    @PostMapping("/page")
+    public R<Page<XiaoeLiveVo>> page(@Valid @RequestBody XiaoeLiveQueryDto dto) {
+        return R.ok(xiaoeLiveService.getLivePage(dto));
+    }
+
+    /**
+     * 预约直播
+     * @param id
+     * @return
+     */
+    @ApiOperation("预约")
+    @GetMapping("/appointment/{id}")
+    public R<Boolean> appointment(@ApiParam(name = "id",value = "直播id", required = true) @PathVariable("id") String id) {
+        return R.ok(xiaoeLiveService.appointment(id));
+    }
+
+    /**
+     * 我的直播
+     * @param dto
+     * @return
+     */
+    @ApiOperation("我的直播")
+    @PostMapping("/mine")
+    public R<Page<XiaoeLiveVo>> minePage(@Valid @RequestBody XiaoeLiveQueryDto dto) {
+        return R.ok(xiaoeLiveService.getMineLivePage(dto));
+    }
+
+    /**
+     * 获取当前店铺讲师id列表/创建直播前校验讲师信息
+     * @return
+     */
+    @ApiOperation("获取当前店铺讲师id列表/创建直播前校验讲师信息")
+    @GetMapping("/xiaoe/ids")
+    public R<List<String>> getShopXiaoeUserIdList() {
+        return R.ok(xiaoeLiveService.getShopXiaoeUserIdList());
+    }
+    /**
+     * 创建直播
+     * @param dto
+     * @return
+     */
+    @ApiOperation("创建直播")
+    @PostMapping("/create")
+    public R<?> create(@Valid @RequestBody XiaoeLiveDto dto) {
+        xiaoeLiveService.create(dto,2);
+        return R.ok();
+    }
+
+    /**
+     * 编辑直播
+     * @param dto
+     * @return
+     */
+    @ApiOperation("编辑直播")
+    @PutMapping
+    public R<Boolean> edit(@Valid @RequestBody XiaoeLiveDto dto) {
+        return R.ok(xiaoeLiveService.edit(dto));
+    }
+}
diff --git a/ruoyi-modules/ruoyi-goods/src/main/java/com/ruoyi/goods/controller/concole/LiveController.java b/ruoyi-modules/ruoyi-goods/src/main/java/com/ruoyi/goods/controller/concole/LiveController.java
new file mode 100644
index 0000000..73cda39
--- /dev/null
+++ b/ruoyi-modules/ruoyi-goods/src/main/java/com/ruoyi/goods/controller/concole/LiveController.java
@@ -0,0 +1,33 @@
+package com.ruoyi.goods.controller.concole;
+
+import com.ruoyi.common.core.domain.R;
+import com.ruoyi.goods.service.live.IXiaoeLiveAppointmentService;
+import com.ruoyi.goods.utils.XiaoeUtils;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * @author mitao
+ * @date 2025/1/10
+ */
+@RestController
+@RequestMapping("/live")
+@RequiredArgsConstructor
+public class LiveController {
+    private final XiaoeUtils xiaoeUtils;
+    private final IXiaoeLiveAppointmentService xiaoeLiveAppointmentService;
+
+    /**
+     * 推送微信小程序订阅消息
+     * @param appointmentId
+     * @return
+     */
+    @GetMapping("/push/{appointmentId}")
+    public R<?> push(@PathVariable("appointmentId") Long appointmentId) {
+        xiaoeLiveAppointmentService.push(appointmentId);
+        return R.ok();
+    }
+}
diff --git a/ruoyi-modules/ruoyi-goods/src/main/java/com/ruoyi/goods/controller/miniapp/AppCourseController.java b/ruoyi-modules/ruoyi-goods/src/main/java/com/ruoyi/goods/controller/miniapp/AppCourseController.java
new file mode 100644
index 0000000..aa86dd2
--- /dev/null
+++ b/ruoyi-modules/ruoyi-goods/src/main/java/com/ruoyi/goods/controller/miniapp/AppCourseController.java
@@ -0,0 +1,57 @@
+package com.ruoyi.goods.controller.miniapp;
+
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.ruoyi.common.core.domain.R;
+import com.ruoyi.goods.domain.dto.XiaoeCourseQueryDto;
+import com.ruoyi.goods.domain.vo.XiaoeCourseChapterVO;
+import com.ruoyi.goods.domain.vo.XiaoeCourseVO;
+import com.ruoyi.goods.service.biz.XiaoeCourseService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiParam;
+import lombok.RequiredArgsConstructor;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+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;
+
+import javax.validation.Valid;
+import java.util.List;
+
+/**
+ * @author mitao
+ * @date 2025/1/10
+ */
+@Api(tags = {"小程序海恒课程相关接口"})
+@Validated
+@RestController
+@RequestMapping("/app/course")
+@RequiredArgsConstructor
+public class AppCourseController {
+    private final XiaoeCourseService xiaoeCourseService;
+
+    /**
+     * 课程列表
+     * @param dto
+     * @return
+     */
+    @ApiOperation("课程列表")
+    @PostMapping("/page")
+    public R<Page<XiaoeCourseVO>> page(@Valid @RequestBody XiaoeCourseQueryDto dto) {
+        return R.ok(xiaoeCourseService.getCoursePageList(dto));
+    }
+
+    /**
+     * 课程章节详情
+     * @param id
+     * @return
+     */
+    @ApiOperation("课程章节详情")
+    @GetMapping("/detail/{id}")
+    public R<List<XiaoeCourseChapterVO>> detail(@ApiParam(name = "id",value = "课程id",required = true)@PathVariable("id") String id) {
+        return R.ok(xiaoeCourseService.getCourseDetail(id));
+    }
+}
diff --git a/ruoyi-modules/ruoyi-goods/src/main/java/com/ruoyi/goods/controller/miniapp/AppLiveController.java b/ruoyi-modules/ruoyi-goods/src/main/java/com/ruoyi/goods/controller/miniapp/AppLiveController.java
new file mode 100644
index 0000000..5c006fb
--- /dev/null
+++ b/ruoyi-modules/ruoyi-goods/src/main/java/com/ruoyi/goods/controller/miniapp/AppLiveController.java
@@ -0,0 +1,54 @@
+package com.ruoyi.goods.controller.miniapp;
+
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.ruoyi.common.core.domain.R;
+import com.ruoyi.goods.domain.dto.XiaoeLiveQueryDto;
+import com.ruoyi.goods.domain.vo.XiaoeLiveVo;
+import com.ruoyi.goods.service.biz.XiaoeLiveService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiParam;
+import lombok.RequiredArgsConstructor;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+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;
+
+import javax.validation.Valid;
+
+/**
+ * @author mitao
+ * @date 2025/1/10
+ */
+@Validated
+@RestController
+@RequiredArgsConstructor
+@Api(tags = "小程序直播相关接口")
+@RequestMapping("/app/live")
+public class AppLiveController {
+    private final XiaoeLiveService xiaoeLiveService;
+    /**
+     * 直播首页分页列表
+     * @param dto
+     * @return
+     */
+    @ApiOperation("直播首页分页列表")
+    @PostMapping("/page")
+    public R<Page<XiaoeLiveVo>> page(@Valid @RequestBody XiaoeLiveQueryDto dto) {
+        return R.ok(xiaoeLiveService.getLivePage(dto));
+    }
+
+    /**
+     * 预约直播
+     * @param id
+     * @return
+     */
+    @ApiOperation("预约")
+    @GetMapping("/appointment/{id}")
+    public R<Boolean> appointment(@ApiParam(name = "id",value = "直播id", required = true) @PathVariable("id") String id) {
+        return R.ok(xiaoeLiveService.appointment(id));
+    }
+}
diff --git a/ruoyi-modules/ruoyi-goods/src/main/java/com/ruoyi/goods/domain/dto/XiaoeCourseQueryDto.java b/ruoyi-modules/ruoyi-goods/src/main/java/com/ruoyi/goods/domain/dto/XiaoeCourseQueryDto.java
new file mode 100644
index 0000000..8bae0c9
--- /dev/null
+++ b/ruoyi-modules/ruoyi-goods/src/main/java/com/ruoyi/goods/domain/dto/XiaoeCourseQueryDto.java
@@ -0,0 +1,45 @@
+package com.ruoyi.goods.domain.dto;
+
+import com.alibaba.fastjson2.annotation.JSONField;
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import javax.validation.constraints.NotNull;
+import java.util.List;
+
+/**
+ * @author mitao
+ * @date 2025/1/9
+ */
+@Data
+@ApiModel("小鹅通课程列表查询参数")
+public class XiaoeCourseQueryDto {
+
+    @ApiModelProperty(value = "课程名字模糊搜索")
+    @JSONField(name = "search_content")
+    private String searchContent;
+
+    @ApiModelProperty(value = "课程创建来源")
+    @JSONField(name = "created_source")
+    private Integer createdSource;
+
+    @ApiModelProperty("课程分组id数组")
+    @JSONField(name = "tags")
+    private List<Integer> tags;
+
+    @ApiModelProperty(value = "当前页")
+    @JSONField(name = "page_index")
+    private Integer pageNo;
+
+    @ApiModelProperty("每页条数")
+    @JSONField(name = "page_size")
+    private Integer pageSize;
+
+    @ApiModelProperty("排序类型 1:创建时间倒序 2:开始时间升序")
+    @NotNull(message = "排序类型不能为空")
+    @JsonIgnore
+    private Integer sortBy;
+
+}
diff --git a/ruoyi-modules/ruoyi-goods/src/main/java/com/ruoyi/goods/domain/dto/XiaoeLiveDto.java b/ruoyi-modules/ruoyi-goods/src/main/java/com/ruoyi/goods/domain/dto/XiaoeLiveDto.java
new file mode 100644
index 0000000..c321af8
--- /dev/null
+++ b/ruoyi-modules/ruoyi-goods/src/main/java/com/ruoyi/goods/domain/dto/XiaoeLiveDto.java
@@ -0,0 +1,69 @@
+package com.ruoyi.goods.domain.dto;
+
+import com.alibaba.fastjson2.annotation.JSONField;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import javax.validation.constraints.Max;
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotNull;
+
+@ApiModel(description = "资源信息")
+@Data
+public class XiaoeLiveDto {
+
+    @ApiModelProperty(value = "直播id",notes = "添加不传,编辑必传")
+    @JSONField(name = "id")
+    private String id;
+
+    @ApiModelProperty(value = "直播标题", required = true, notes = "字符长度必须小于45")
+    @NotBlank(message = "直播标题不能为空")
+    @JSONField(name = "title")
+    private String title;
+
+    @ApiModelProperty(value = "直播简介", notes = "字符长度必须小于256,默认为空")
+    @NotBlank(message = "直播简介不能为空")
+    @Max(value = 256, message = "直播简介长度不能超过256")
+    @JSONField(name = "summary")
+    private String summary;
+
+    @ApiModelProperty(value = "预设直播开始时间", required = true, notes = "距离当前时间不能超过五年", example = "2023-05-31 18:00:00")
+    @NotBlank(message = "预设直播开始时间不能为空")
+    @JSONField(name = "zb_start_at")
+    private String zbStartAt;
+
+    @ApiModelProperty(value = "预设直播时长,单位:秒", required = true, notes = "距离预设直播开始时间不能超过十年")
+    @JSONField(name = "zb_stop_at")
+    private Integer zbStopAt;
+
+    @ApiModelProperty(value = "直播类型", notes = "0-语音,1-录播直播,2-推流直播,默认为0-语音直播")
+    @NotNull(message = "直播类型不能为空")
+    @JSONField(name = "alive_type")
+    private Integer aliveType;
+
+    @ApiModelProperty(value = "直播模式:0-横屏直播,1-竖屏直播(默认为0-横屏直播)", example = "0")
+    @NotNull(message = "直播模式不能为空")
+    @JSONField(name = "alive_mode")
+    private Integer aliveMode;
+
+    @ApiModelProperty(value = "讲师用户id")
+    @NotBlank(message = "讲师用户id不能为空")
+    @JSONField(name = "user_id")
+    private String userId;
+
+    @ApiModelProperty(value = "支付类型:1-免费,3-加密,仅当goods_info.sale_type=2时才可用", example = "1")
+    @NotNull(message = "支付类型不能为空")
+    @JSONField(name = "payment_type")
+    private Integer paymentType;
+
+    @ApiModelProperty(value = "密码(paymentType为3时必填,否则该字段无效)")
+    @JSONField(name = "resource_password")
+    private String resourcePassword;
+
+    @ApiModelProperty(value = "直播详情", notes = "仅允许纯文本,不得超过5000个字符,默认为空")
+    @NotBlank(message = "直播详情不能为空")
+    @Max(value = 5000, message = "直播详情长度不能超过5000")
+    @JSONField(name = "descrb")
+    private String descrb;
+}
diff --git a/ruoyi-modules/ruoyi-goods/src/main/java/com/ruoyi/goods/domain/dto/XiaoeLiveQueryDto.java b/ruoyi-modules/ruoyi-goods/src/main/java/com/ruoyi/goods/domain/dto/XiaoeLiveQueryDto.java
new file mode 100644
index 0000000..83974ca
--- /dev/null
+++ b/ruoyi-modules/ruoyi-goods/src/main/java/com/ruoyi/goods/domain/dto/XiaoeLiveQueryDto.java
@@ -0,0 +1,35 @@
+package com.ruoyi.goods.domain.dto;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+/**
+ * @author mitao
+ * @date 2025/1/8
+ */
+@Data
+@ApiModel("直播分页查询参数")
+public class XiaoeLiveQueryDto {
+
+    @ApiModelProperty(value = "直播名称关键字")
+    private String searchContent;
+
+    @ApiModelProperty(value = "直播课程类型 -1 全部;0 店铺课程; 1 转播课程;默认-1")
+    private Integer createMode;
+
+    @ApiModelProperty(value = "直播商品状态 -1 全部; 0 已上架; 1 已下架;2 待上架;默认-1")
+    private Integer state;
+
+    @ApiModelProperty(value = "直播模式:-1 全部;10 横屏直播;11竖屏直播;12语音直播;13录播直播;默认-1")
+    private Integer searchAliveType;
+
+    @ApiModelProperty(value = "直播状态:-1全部;0未开始;1直播中;2已结束;默认-1")
+    private Integer alivePlayState;
+
+    @ApiModelProperty(value = "页码,表示第几页,从1开始;默认1")
+    private Integer page;
+
+    @ApiModelProperty(value = "每页条数,最大50条;默认10")
+    private Integer pageSize;
+}
diff --git a/ruoyi-modules/ruoyi-goods/src/main/java/com/ruoyi/goods/domain/pojo/live/XiaoeLiveAppointment.java b/ruoyi-modules/ruoyi-goods/src/main/java/com/ruoyi/goods/domain/pojo/live/XiaoeLiveAppointment.java
new file mode 100644
index 0000000..540b53f
--- /dev/null
+++ b/ruoyi-modules/ruoyi-goods/src/main/java/com/ruoyi/goods/domain/pojo/live/XiaoeLiveAppointment.java
@@ -0,0 +1,63 @@
+package com.ruoyi.goods.domain.pojo.live;
+
+import com.baomidou.mybatisplus.annotation.FieldFill;
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.experimental.Accessors;
+
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * <p>
+ * 直播预约记录
+ * </p>
+ *
+ * @author mitao
+ * @since 2025-01-09
+ */
+@Data
+@EqualsAndHashCode(callSuper = false)
+@Accessors(chain = true)
+@TableName("t_xiaoe_live_appointment")
+public class XiaoeLiveAppointment implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 主键id
+     */
+    @TableId(value = "id", type = IdType.AUTO)
+    private Integer id;
+
+    /**
+     * 直播id
+     */
+    private String liveId;
+
+    /**
+     * 预约人id
+     */
+    private Long userId;
+    /**
+     * 直播开始时间
+     */
+    private Date aliveStartAt;
+
+    /**
+     * 直播标题
+     */
+    private String title;
+
+    /**
+     * 预约时间
+     */
+    @TableField(fill = FieldFill.INSERT)
+    private Date createTime;
+
+
+}
diff --git a/ruoyi-modules/ruoyi-goods/src/main/java/com/ruoyi/goods/domain/pojo/live/XiaoeLiveRecord.java b/ruoyi-modules/ruoyi-goods/src/main/java/com/ruoyi/goods/domain/pojo/live/XiaoeLiveRecord.java
new file mode 100644
index 0000000..440c69a
--- /dev/null
+++ b/ruoyi-modules/ruoyi-goods/src/main/java/com/ruoyi/goods/domain/pojo/live/XiaoeLiveRecord.java
@@ -0,0 +1,64 @@
+package com.ruoyi.goods.domain.pojo.live;
+
+import com.baomidou.mybatisplus.annotation.FieldFill;
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.experimental.Accessors;
+
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * <p>
+ * 小鹅通直播添加记录表
+ * </p>
+ *
+ * @author mitao
+ * @since 2025-01-09
+ */
+@Data
+@EqualsAndHashCode(callSuper = false)
+@Accessors(chain = true)
+@TableName("t_xiaoe_live_record")
+public class XiaoeLiveRecord implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 主键id
+     */
+    @TableId(value = "id", type = IdType.AUTO)
+    private Long id;
+
+    /**
+     * 记录类型(1:平台 2:经销商)
+     */
+    private Integer type;
+
+    /**
+     * 直播id
+     */
+    private String liveId;
+
+    /**
+     * 店铺(仅记录类型为经销商时使用)
+     */
+    private Long shopId;
+
+    /**
+     * 店铺名称
+     */
+    private String shopName;
+
+    /**
+     * 添加时间
+     */
+    @TableField(fill = FieldFill.INSERT)
+    private Date createTime;
+
+
+}
diff --git a/ruoyi-modules/ruoyi-goods/src/main/java/com/ruoyi/goods/domain/vo/XiaoeCourseChapterVO.java b/ruoyi-modules/ruoyi-goods/src/main/java/com/ruoyi/goods/domain/vo/XiaoeCourseChapterVO.java
new file mode 100644
index 0000000..b764d0b
--- /dev/null
+++ b/ruoyi-modules/ruoyi-goods/src/main/java/com/ruoyi/goods/domain/vo/XiaoeCourseChapterVO.java
@@ -0,0 +1,36 @@
+package com.ruoyi.goods.domain.vo;
+
+import com.alibaba.fastjson2.annotation.JSONField;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * @author mitao
+ * @date 2025/1/9
+ */
+@Data
+@ApiModel("课程目录小节视图对象")
+public class XiaoeCourseChapterVO {
+    @ApiModelProperty(value = "章节id")
+    @JSONField(name = "chapter_id")
+    private String chapterId;
+
+    @ApiModelProperty(value = "章节名称")
+    @JSONField(name = "chapter_title")
+    private String chapterTitle;
+
+    @ApiModelProperty(value = "章节类型 0-无 1-章 2-节")
+    @JSONField(name = "chapter_type")
+    private Integer chapterType;
+
+    @ApiModelProperty(value = "关联资源的种类(1:图文,2:音频,3:视频,4:直播,20:电子书,45:AI互动课,34练习,27考试,51文档,15作业,13表单,14测试互动)")
+    @JSONField(name = "resource_type")
+    private Integer resourceType;
+
+    @ApiModelProperty(value = "子章节集合")
+    @JSONField(name = "children")
+    private List<XiaoeCourseChapterVO> children;
+}
diff --git a/ruoyi-modules/ruoyi-goods/src/main/java/com/ruoyi/goods/domain/vo/XiaoeCourseGroupVO.java b/ruoyi-modules/ruoyi-goods/src/main/java/com/ruoyi/goods/domain/vo/XiaoeCourseGroupVO.java
new file mode 100644
index 0000000..9817d41
--- /dev/null
+++ b/ruoyi-modules/ruoyi-goods/src/main/java/com/ruoyi/goods/domain/vo/XiaoeCourseGroupVO.java
@@ -0,0 +1,20 @@
+package com.ruoyi.goods.domain.vo;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+/**
+ * @author mitao
+ * @date 2025/1/9
+ */
+@Data
+@ApiModel("商品分组视图对象")
+public class XiaoeCourseGroupVO {
+
+    @ApiModelProperty("商品分组id")
+    private String id;
+
+    @ApiModelProperty("商品分组名称")
+    private String name;
+}
diff --git a/ruoyi-modules/ruoyi-goods/src/main/java/com/ruoyi/goods/domain/vo/XiaoeCourseVO.java b/ruoyi-modules/ruoyi-goods/src/main/java/com/ruoyi/goods/domain/vo/XiaoeCourseVO.java
new file mode 100644
index 0000000..fd6f8ba
--- /dev/null
+++ b/ruoyi-modules/ruoyi-goods/src/main/java/com/ruoyi/goods/domain/vo/XiaoeCourseVO.java
@@ -0,0 +1,45 @@
+package com.ruoyi.goods.domain.vo;
+
+import com.alibaba.fastjson2.annotation.JSONField;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.util.Date;
+
+/**
+ * @author mitao
+ * @date 2025/1/9
+ */
+@Data
+@ApiModel("小鹅通课程视图对象")
+public class XiaoeCourseVO {
+    @ApiModelProperty("课程id")
+    @JSONField(name = "resource_id")
+    private String resourceId;
+
+    @ApiModelProperty("课程名称")
+    @JSONField(name = "title")
+    private String title;
+
+    @ApiModelProperty("课程封面")
+    @JSONField(name = "img_url")
+    private String imgUrl;
+
+    @ApiModelProperty("用户数")
+    @JSONField(name = "user_count")
+    private Integer userCount;
+
+    @ApiModelProperty("内容数")
+    @JSONField(name = "resource_cnt")
+    private Integer resourceCnt;
+
+    @ApiModelProperty("开课时间")
+    @JSONField(name = "curriculum_time")
+    private Date curriculumTime;
+
+    @ApiModelProperty("开课结束时间")
+    @JSONField(name = "curriculum_end_time")
+    private Date curriculumEndTime;
+
+}
diff --git a/ruoyi-modules/ruoyi-goods/src/main/java/com/ruoyi/goods/domain/vo/XiaoeLiveTeacherVo.java b/ruoyi-modules/ruoyi-goods/src/main/java/com/ruoyi/goods/domain/vo/XiaoeLiveTeacherVo.java
new file mode 100644
index 0000000..bc81487
--- /dev/null
+++ b/ruoyi-modules/ruoyi-goods/src/main/java/com/ruoyi/goods/domain/vo/XiaoeLiveTeacherVo.java
@@ -0,0 +1,23 @@
+package com.ruoyi.goods.domain.vo;
+
+import com.alibaba.fastjson2.annotation.JSONField;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+@ApiModel(description = "讲师信息")
+@Data
+public class XiaoeLiveTeacherVo {
+
+    @ApiModelProperty(value = "讲师ID")
+    @JSONField(name = "user_id")
+    private String userId;  // 讲师ID
+
+    @ApiModelProperty(value = "讲师昵称")
+    @JSONField(name = "user_name")
+    private String userName;  // 讲师昵称
+
+    @ApiModelProperty(value = "讲师绑定手机号")
+    @JSONField(name = "phone")
+    private String phone;  // 讲师绑定手机号
+}
\ No newline at end of file
diff --git a/ruoyi-modules/ruoyi-goods/src/main/java/com/ruoyi/goods/domain/vo/XiaoeLiveVo.java b/ruoyi-modules/ruoyi-goods/src/main/java/com/ruoyi/goods/domain/vo/XiaoeLiveVo.java
new file mode 100644
index 0000000..0eeebe2
--- /dev/null
+++ b/ruoyi-modules/ruoyi-goods/src/main/java/com/ruoyi/goods/domain/vo/XiaoeLiveVo.java
@@ -0,0 +1,109 @@
+package com.ruoyi.goods.domain.vo;
+
+import com.alibaba.fastjson2.annotation.JSONField;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * @author mitao
+ * @date 2025/1/8
+ */
+@Data
+@ApiModel("小鹅通直播视图对象")
+public class XiaoeLiveVo {
+
+    @ApiModelProperty(value = "直播ID")
+    @JSONField(name = "id")
+    private String id;  // 直播ID
+
+    @ApiModelProperty(value = "直播手动结束时间")
+    @JSONField(name = "alive_manual_stop_at")
+    private String aliveManualStopAt;  // 直播手动结束时间
+
+    @ApiModelProperty(value = "直播结束时间")
+    @JSONField(name = "alive_stop_at")
+    private String aliveStopAt;  // 直播结束时间
+
+    @ApiModelProperty(value = "直播名称")
+    @JSONField(name = "title")
+    private String title;  // 直播名称
+
+    @ApiModelProperty(value = "直播观看人次,每5分钟同步一次数据")
+    @JSONField(name = "view_count")
+    private Integer viewCount;  // 直播观看人次
+
+    @ApiModelProperty(value = "直播类型:10 横屏直播;11竖屏直播;12语音直播;13录播直播")
+    @JSONField(name = "alive_type")
+    private Integer aliveType;  // 直播类型
+
+    @ApiModelProperty(value = "订阅量")
+    @JSONField(name = "purchase_count")
+    private Integer purchaseCount;  // 订阅量
+
+    @ApiModelProperty(value = "打赏金额(元)")
+    @JSONField(name = "reward_sum")
+    private String rewardSum;  // 打赏金额(元)
+
+    @ApiModelProperty(value = "直播商品状态:-1 全部; 0 已上架; 1 已下架;2 待上架")
+    @JSONField(name = "recycle_bin_state")
+    private Integer recycleBinState;  // 直播商品状态
+
+    @ApiModelProperty(value = "直播开始时间")
+    @JSONField(name = "alive_start_at")
+    private String aliveStartAt;  // 直播开始时间
+
+    @ApiModelProperty(value = "直播状态:-1全部;0未开始;1直播中;2已结束")
+    @JSONField(name = "alive_state")
+    private Integer aliveState;  // 直播状态
+
+    @ApiModelProperty(value = "直播显隐状态:0-显示,1-隐藏")
+    @JSONField(name = "resource_state")
+    private Integer resourceState;  // 直播显隐状态
+
+    @ApiModelProperty(value = "店铺直播详情页地址")
+    @JSONField(name = "page_url")
+    private String pageUrl;  // 店铺直播详情页地址
+
+    @ApiModelProperty(value = "直播课程类型:-1 全部;0 店铺课程; 1 转播课程")
+    @JSONField(name = "create_mode")
+    private Integer createMode;  // 直播课程类型
+
+    @ApiModelProperty(value = "封面地址")
+    @JSONField(name = "img_url")
+    private String imgUrl;  // 封面地址
+
+    @ApiModelProperty(value = "直播宣传图地址")
+    @JSONField(name = "alive_img_url")
+    private String aliveImgUrl;  // 直播宣传图地址
+
+    @ApiModelProperty(value = "讲师列表")
+    @JSONField(name = "guest_list")
+    private List<XiaoeLiveTeacherVo> guestList;
+
+    @ApiModelProperty("评论数")
+    @JSONField(name = "comment_count")
+    private Integer commentCount;
+
+    @ApiModelProperty("评论用户数")
+    @JSONField(name = "comment_user_count")
+    private Integer commentUserCount;
+
+    @ApiModelProperty("打赏次数")
+    @JSONField(name = "reward_count")
+    private Integer rewardCount;
+
+    @ApiModelProperty("打赏人数")
+    @JSONField(name = "reward_user_count")
+    private Integer rewardUserCount;
+
+    @ApiModelProperty("直播简介")
+    @JSONField(name = "summary")
+    private String summary;
+
+    @ApiModelProperty("预约状态 1:已预约 0:未预约")
+    private Integer appointmentState=0;
+
+}
diff --git a/ruoyi-modules/ruoyi-goods/src/main/java/com/ruoyi/goods/mapper/live/XiaoeLiveAppointmentMapper.java b/ruoyi-modules/ruoyi-goods/src/main/java/com/ruoyi/goods/mapper/live/XiaoeLiveAppointmentMapper.java
new file mode 100644
index 0000000..bf98aec
--- /dev/null
+++ b/ruoyi-modules/ruoyi-goods/src/main/java/com/ruoyi/goods/mapper/live/XiaoeLiveAppointmentMapper.java
@@ -0,0 +1,17 @@
+package com.ruoyi.goods.mapper.live;
+
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.ruoyi.goods.domain.pojo.live.XiaoeLiveAppointment;
+
+/**
+ * <p>
+ * 直播预约记录 Mapper 接口
+ * </p>
+ *
+ * @author mitao
+ * @since 2025-01-09
+ */
+public interface XiaoeLiveAppointmentMapper extends BaseMapper<XiaoeLiveAppointment> {
+
+}
diff --git a/ruoyi-modules/ruoyi-goods/src/main/java/com/ruoyi/goods/mapper/live/XiaoeLiveRecordMapper.java b/ruoyi-modules/ruoyi-goods/src/main/java/com/ruoyi/goods/mapper/live/XiaoeLiveRecordMapper.java
new file mode 100644
index 0000000..60fbd6d
--- /dev/null
+++ b/ruoyi-modules/ruoyi-goods/src/main/java/com/ruoyi/goods/mapper/live/XiaoeLiveRecordMapper.java
@@ -0,0 +1,17 @@
+package com.ruoyi.goods.mapper.live;
+
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.ruoyi.goods.domain.pojo.live.XiaoeLiveRecord;
+
+/**
+ * <p>
+ * 小鹅通直播添加记录表 Mapper 接口
+ * </p>
+ *
+ * @author mitao
+ * @since 2025-01-09
+ */
+public interface XiaoeLiveRecordMapper extends BaseMapper<XiaoeLiveRecord> {
+
+}
diff --git a/ruoyi-modules/ruoyi-goods/src/main/java/com/ruoyi/goods/service/biz/XiaoeCourseService.java b/ruoyi-modules/ruoyi-goods/src/main/java/com/ruoyi/goods/service/biz/XiaoeCourseService.java
new file mode 100644
index 0000000..cf72f59
--- /dev/null
+++ b/ruoyi-modules/ruoyi-goods/src/main/java/com/ruoyi/goods/service/biz/XiaoeCourseService.java
@@ -0,0 +1,39 @@
+package com.ruoyi.goods.service.biz;
+
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.ruoyi.goods.domain.dto.XiaoeCourseQueryDto;
+import com.ruoyi.goods.domain.vo.XiaoeCourseChapterVO;
+import com.ruoyi.goods.domain.vo.XiaoeCourseVO;
+import com.ruoyi.goods.utils.XiaoeUtils;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+
+/**
+ * @author mitao
+ * @date 2025/1/10
+ */
+@Service
+@RequiredArgsConstructor
+public class XiaoeCourseService {
+    private final XiaoeUtils xiaoeUtils;
+
+    /**
+     * 课程列表
+     * @param dto
+     * @return
+     */
+    public Page<XiaoeCourseVO> getCoursePageList(XiaoeCourseQueryDto dto) {
+        return xiaoeUtils.getCoursePageList(dto);
+    }
+
+    /**
+     * 获取课程章节详情
+     * @param id
+     * @return
+     */
+    public List<XiaoeCourseChapterVO> getCourseDetail(String id) {
+        return xiaoeUtils.getCourseChapterDetail(id);
+    }
+}
diff --git a/ruoyi-modules/ruoyi-goods/src/main/java/com/ruoyi/goods/service/biz/XiaoeLiveService.java b/ruoyi-modules/ruoyi-goods/src/main/java/com/ruoyi/goods/service/biz/XiaoeLiveService.java
new file mode 100644
index 0000000..9df3c7d
--- /dev/null
+++ b/ruoyi-modules/ruoyi-goods/src/main/java/com/ruoyi/goods/service/biz/XiaoeLiveService.java
@@ -0,0 +1,249 @@
+package com.ruoyi.goods.service.biz;
+
+import cn.hutool.core.collection.CollectionUtil;
+import cn.hutool.core.date.DatePattern;
+import cn.hutool.core.date.DateUtil;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.ruoyi.common.core.exception.ServiceException;
+import com.ruoyi.common.core.utils.DateUtils;
+import com.ruoyi.common.core.utils.StringUtils;
+import com.ruoyi.common.redis.service.RedisService;
+import com.ruoyi.common.security.utils.SecurityUtils;
+import com.ruoyi.goods.domain.dto.XiaoeLiveDto;
+import com.ruoyi.goods.domain.dto.XiaoeLiveQueryDto;
+import com.ruoyi.goods.domain.pojo.live.XiaoeLiveAppointment;
+import com.ruoyi.goods.domain.pojo.live.XiaoeLiveRecord;
+import com.ruoyi.goods.domain.vo.XiaoeLiveVo;
+import com.ruoyi.goods.service.live.IXiaoeLiveAppointmentService;
+import com.ruoyi.goods.service.live.IXiaoeLiveRecordService;
+import com.ruoyi.goods.utils.XiaoeUtils;
+import com.ruoyi.system.api.constant.DelayTaskEnum;
+import com.ruoyi.system.api.domain.poji.config.DelayTask;
+import com.ruoyi.system.api.domain.poji.shop.Shop;
+import com.ruoyi.system.api.domain.poji.sys.SysUser;
+import com.ruoyi.system.api.domain.vo.ShopRelUserVo;
+import com.ruoyi.system.api.service.RemoteConfigService;
+import com.ruoyi.system.api.service.RemoteShopService;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Service;
+
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.Date;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
+
+/**
+ * @author mitao
+ * @date 2025/1/9
+ */
+@Service
+@RequiredArgsConstructor
+public class XiaoeLiveService {
+    private final IXiaoeLiveRecordService xiaoeLiveRecordService;
+    private final XiaoeUtils xiaoeUtils;
+    private final IXiaoeLiveAppointmentService xiaoeLiveAppointmentService;
+    private final RedisService redisService;
+    private final RemoteConfigService remoteConfigService;
+    private final RemoteShopService remoteShopService;
+
+
+    /**
+     * 直播首页分页列表
+     * @param dto
+     * @return
+     */
+    public Page<XiaoeLiveVo> getLivePage(XiaoeLiveQueryDto dto) {
+        //当前登录用户id
+        Long userId = SecurityUtils.getUserId();
+        Page<XiaoeLiveVo> livePageList = xiaoeUtils.getLivePageList(dto);
+        if (CollectionUtil.isEmpty(livePageList.getRecords())) {
+            return new Page<>();
+        }
+        //已预约的记录
+        List<XiaoeLiveAppointment> appointments = xiaoeLiveAppointmentService.lambdaQuery()
+                .gt(XiaoeLiveAppointment::getAliveStartAt, DateUtils.getNowDate()).eq(XiaoeLiveAppointment::getUserId, userId).list();
+        //处理获取到的直播数据
+        List<XiaoeLiveVo> filteredRecords = livePageList.getRecords().stream()
+                .filter(item -> !item.getAliveState().equals(2))
+                .flatMap(item -> appointments.stream().map(appointment -> {
+                    //直播状态为未开始 且 预约记录存在 设置状态为已预约
+                    if (item.getAliveState().equals(0) && appointment.getLiveId().equals(item.getId())) {
+                        item.setAppointmentState(1);
+                    }
+                    return item;
+                })).sorted(Comparator.comparing(XiaoeLiveVo::getAliveState).reversed())
+                .collect(Collectors.toList());
+        livePageList.setRecords(filteredRecords);
+        return livePageList;
+    }
+
+    /**
+     * 预约直播
+     * @param id
+     * @return
+     */
+    public Boolean appointment(String id) {
+        //当前登录用户
+        SysUser sysUser = SecurityUtils.getSysUser();
+        Long userId = sysUser.getUserId();
+        //查询预约记录
+        Integer count = xiaoeLiveAppointmentService.lambdaQuery().eq(XiaoeLiveAppointment::getLiveId, id).gt(XiaoeLiveAppointment::getAliveStartAt, DateUtils.getNowDate()).eq(XiaoeLiveAppointment::getUserId, userId).count();
+        if (count > 0) {
+            throw new ServiceException("您已预约过该直播,请勿重复预约");
+        }
+        XiaoeLiveVo liveDetail = xiaoeUtils.getLiveDetail(id);
+        if (Objects.isNull(liveDetail)) {
+            throw new ServiceException("直播不存在");
+        }
+        XiaoeLiveAppointment xiaoeLiveAppointment = new XiaoeLiveAppointment();
+        xiaoeLiveAppointment.setLiveId(id);
+        xiaoeLiveAppointment.setUserId(userId);
+        Date aliveStartAt = DateUtil.parse(liveDetail.getAliveStartAt(), DatePattern.NORM_DATETIME_PATTERN);
+        xiaoeLiveAppointment.setAliveStartAt(aliveStartAt);
+        xiaoeLiveAppointment.setCreateTime(DateUtils.getNowDate());
+        xiaoeLiveAppointmentService.save(xiaoeLiveAppointment);
+        /// 若为C端用户向 redis 添加预约记录
+        if (sysUser.getUserType().equals("03")) {
+            // 如果活动在1小时内生成自动开始任务的延时任务
+            Date checkTime = DateUtils.addMinutes(new Date(), 61);
+            // 获取当前时间
+            Date nowTime = new Date();
+
+            // 计算直播开始时间与当前时间的差值
+            long startTimeDifference = aliveStartAt.getTime() - nowTime.getTime();
+            startTimeDifference = Math.max(startTimeDifference, 3000L); // 确保差值至少为3秒
+
+            // 根据条件判断是否创建延时任务
+            if (checkTime.compareTo(aliveStartAt) > 0 || nowTime.compareTo(aliveStartAt) > 0) {
+                // 获取延时任务
+                DelayTask startDelayTask = remoteConfigService.getDelayTask(DelayTaskEnum.LIVE_APPOINTMENT_TASK.getCode() + "-" + xiaoeLiveAppointment.getId()).getData();
+                // 创建或者更新延时任务
+                if (startDelayTask == null || !startDelayTask.getEndTime().equals(aliveStartAt)) {
+                    // 删除旧的延时任务及缓存
+                    if (startDelayTask != null) {
+                        remoteConfigService.deleteDelayTask(DelayTaskEnum.LIVE_APPOINTMENT_TASK.getCode() + "-" + xiaoeLiveAppointment.getId());
+                        redisService.deleteObject(DelayTaskEnum.LIVE_APPOINTMENT_TASK.getCode() + "-" + xiaoeLiveAppointment.getId());
+                    }
+                    // 设置新的延时任务
+                    redisService.setCacheObject(DelayTaskEnum.LIVE_APPOINTMENT_TASK.getCode() + "-" + xiaoeLiveAppointment.getId(), aliveStartAt, startTimeDifference, TimeUnit.MILLISECONDS);
+                    startDelayTask = new DelayTask();
+                    startDelayTask.setDelFlag(0);
+                    startDelayTask.setCreateTime(new Date());
+                    startDelayTask.setEndTime(aliveStartAt);
+                    startDelayTask.setRedisKey(DelayTaskEnum.LIVE_APPOINTMENT_TASK.getCode() + "-" + xiaoeLiveAppointment.getId());
+
+                    // 添加新的延时任务
+                    remoteConfigService.addDelayTask(startDelayTask);
+                }
+            }
+        }
+        return true;
+    }
+
+    /**
+     * 我的直播
+     * @param dto
+     * @return
+     */
+    public Page<XiaoeLiveVo> getMineLivePage(XiaoeLiveQueryDto dto) {
+        Long userId = SecurityUtils.getUserId();
+        Page<XiaoeLiveVo> page = new Page<>();
+        ShopRelUserVo shopRelUserVo = remoteShopService.getShopByUserId(userId).getData();
+        if (Objects.isNull(shopRelUserVo)) {
+            return page;
+        }
+        List<XiaoeLiveRecord> xiaoeLiveRecordList = xiaoeLiveRecordService.getListByShopId(shopRelUserVo.getShopId());
+        if (CollectionUtil.isEmpty(xiaoeLiveRecordList)) {
+            return page;
+        }
+        Page<XiaoeLiveVo> livePageList = xiaoeUtils.getLivePageList(dto);
+        if (CollectionUtil.isEmpty(livePageList.getRecords())) {
+            return page;
+        }
+        //当前商家创建的直播
+        Set<String> liveIdSet = xiaoeLiveRecordList.stream().map(XiaoeLiveRecord::getLiveId).collect(Collectors.toSet());
+        //过滤数据
+        List<XiaoeLiveVo> filteredRecords = livePageList.getRecords().stream().filter(item -> liveIdSet.contains(item.getId()) && !item.getAliveState().equals(2)).collect(Collectors.toList());
+        livePageList.setRecords(filteredRecords);
+        return livePageList;
+    }
+
+    /**
+     * 获取当前店铺讲师id列表
+     * @return
+     */
+    public List<String> getShopXiaoeUserIdList() {
+        Long userId = SecurityUtils.getUserId();
+        //获取用户所属店铺
+        ShopRelUserVo shopRelUserVo = remoteShopService.getShopByUserId(userId).getData();
+        if (Objects.isNull(shopRelUserVo)) {
+            throw new ServiceException("当前用户未关联店铺,请先关联店铺");
+        }
+        //查询店铺信息
+        Shop shop = remoteShopService.getShop(shopRelUserVo.getShopId()).getData();
+        if (Objects.isNull(shop)) {
+            throw new ServiceException("店铺不存在");
+        }
+        if (StringUtils.isBlank(shop.getXiaoeUserId())) {
+            throw new ServiceException("创建失败,请联系平台添加讲师。");
+        }
+        return Arrays.asList(shop.getXiaoeUserId().split(","));
+    }
+    /**
+     * 创建直播
+     * @param dto
+     * @param type 创建类型 1:平台 2:经销商
+     * @return
+     */
+    public void create(XiaoeLiveDto dto,Integer type) {
+        if (dto.getPaymentType().equals(3) && StringUtils.isBlank(dto.getResourcePassword())){
+            throw new ServiceException("密码不能为空");
+        }
+        if (type.equals(2)) {
+            //获取当前登录用户
+            Long userId = SecurityUtils.getUserId();
+            //获取用户所属店铺
+            ShopRelUserVo shopRelUserVo = remoteShopService.getShopByUserId(userId).getData();
+            if (Objects.isNull(shopRelUserVo)) {
+                throw new ServiceException("当前用户未关联店铺,请先关联店铺");
+            }
+            //查询店铺信息
+            Shop shop = remoteShopService.getShop(shopRelUserVo.getShopId()).getData();
+            if (Objects.isNull(shop)) {
+                throw new ServiceException("店铺不存在");
+            }
+            String liveId = xiaoeUtils.addLive(dto);
+            if (StringUtils.isBlank(liveId)) {
+                throw new ServiceException("创建直播失败,请联系平台管理员");
+            }
+            Long shopId = shop.getShopId();
+            String shopName = shop.getShopName();
+            //添加直播创建记录
+            xiaoeLiveRecordService.add(shopId, type, shopName, liveId);
+        } else {
+            String liveId = xiaoeUtils.addLive(dto);
+            if (StringUtils.isBlank(liveId)) {
+                throw new ServiceException("创建直播失败,请联系平台管理员");
+            }
+            //添加直播创建记录
+            xiaoeLiveRecordService.add(null, type, "鸿瑞堂", liveId);
+        }
+    }
+
+    /**
+     * 编辑直播
+     * @param dto
+     */
+    public Boolean edit(XiaoeLiveDto dto) {
+        if (StringUtils.isBlank(dto.getId())) {
+            throw new ServiceException("直播id不能为空");
+        }
+        return xiaoeUtils.editLive(dto);
+    }
+
+
+}
diff --git a/ruoyi-modules/ruoyi-goods/src/main/java/com/ruoyi/goods/service/impl/live/XiaoeLiveAppointmentServiceImpl.java b/ruoyi-modules/ruoyi-goods/src/main/java/com/ruoyi/goods/service/impl/live/XiaoeLiveAppointmentServiceImpl.java
new file mode 100644
index 0000000..69956e1
--- /dev/null
+++ b/ruoyi-modules/ruoyi-goods/src/main/java/com/ruoyi/goods/service/impl/live/XiaoeLiveAppointmentServiceImpl.java
@@ -0,0 +1,57 @@
+package com.ruoyi.goods.service.impl.live;
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.ruoyi.goods.domain.pojo.live.XiaoeLiveAppointment;
+import com.ruoyi.goods.domain.pojo.live.XiaoeLiveRecord;
+import com.ruoyi.goods.mapper.live.XiaoeLiveAppointmentMapper;
+import com.ruoyi.goods.service.live.IXiaoeLiveAppointmentService;
+import com.ruoyi.goods.service.live.IXiaoeLiveRecordService;
+import com.ruoyi.goods.utils.WeChatSubscribeMessageSender;
+import com.ruoyi.system.api.domain.poji.member.Member;
+import com.ruoyi.system.api.service.RemoteMemberService;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+
+import java.util.Objects;
+
+/**
+ * <p>
+ * 直播预约记录 服务实现类
+ * </p>
+ *
+ * @author mitao
+ * @since 2025-01-09
+ */
+@Slf4j
+@Service
+@RequiredArgsConstructor
+public class XiaoeLiveAppointmentServiceImpl extends ServiceImpl<XiaoeLiveAppointmentMapper, XiaoeLiveAppointment> implements IXiaoeLiveAppointmentService {
+    private final RemoteMemberService remoteMemberService;
+    private final IXiaoeLiveRecordService xiaoeLiveRecordService;
+
+    /**
+     * 推送微信小程序订阅消息
+     * @param appointmentId
+     * @return
+     */
+    @Override
+    public void push(Long appointmentId) {
+        log.info("开始推送预约id为:{}的订阅消息",appointmentId);
+        XiaoeLiveAppointment appointment = getById(appointmentId);
+        if (Objects.nonNull(appointment)) {
+            Member member = remoteMemberService.getMember(appointment.getUserId()).getData();
+            if (Objects.isNull(member)){
+                return;
+            }
+            XiaoeLiveRecord record = xiaoeLiveRecordService.getByLiveId(appointment.getLiveId());
+            if (Objects.isNull(record)) {
+                return;
+            }
+            //推送微信小程序订阅消息
+            WeChatSubscribeMessageSender.push(member.getMiniOpenid(), appointment.getTitle(), record.getShopName(), appointment.getAliveStartAt());
+        }
+        log.info("预约id为:{}的订阅消息推送成功",appointmentId);
+    }
+
+}
diff --git a/ruoyi-modules/ruoyi-goods/src/main/java/com/ruoyi/goods/service/impl/live/XiaoeLiveRecordServiceImpl.java b/ruoyi-modules/ruoyi-goods/src/main/java/com/ruoyi/goods/service/impl/live/XiaoeLiveRecordServiceImpl.java
new file mode 100644
index 0000000..8f4454d
--- /dev/null
+++ b/ruoyi-modules/ruoyi-goods/src/main/java/com/ruoyi/goods/service/impl/live/XiaoeLiveRecordServiceImpl.java
@@ -0,0 +1,61 @@
+package com.ruoyi.goods.service.impl.live;
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.ruoyi.common.core.utils.DateUtils;
+import com.ruoyi.goods.domain.pojo.live.XiaoeLiveRecord;
+import com.ruoyi.goods.mapper.live.XiaoeLiveRecordMapper;
+import com.ruoyi.goods.service.live.IXiaoeLiveRecordService;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+
+/**
+ * <p>
+ * 小鹅通直播添加记录表 服务实现类
+ * </p>
+ *
+ * @author mitao
+ * @since 2025-01-09
+ */
+@Service
+public class XiaoeLiveRecordServiceImpl extends ServiceImpl<XiaoeLiveRecordMapper, XiaoeLiveRecord> implements IXiaoeLiveRecordService {
+    /**
+     * 根据直播id查询记录
+     *
+     * @param liveId
+     * @return
+     */
+    @Override
+    public XiaoeLiveRecord getByLiveId(String liveId) {
+        return lambdaQuery().eq(XiaoeLiveRecord::getLiveId, liveId).one();
+    }
+
+    /**
+     * 根据店铺id查询记录
+     *
+     * @param shopId
+     * @return
+     */
+    @Override
+    public List<XiaoeLiveRecord> getListByShopId(Long shopId) {
+        return lambdaQuery().eq(XiaoeLiveRecord::getType, 2).eq(XiaoeLiveRecord::getShopId, shopId).list();
+    }
+
+    /**
+     * 添加直播创建记录
+     * @param shopId
+     * @param type
+     * @param shopName
+     * @param liveId
+     */
+    @Override
+    public void add(Long shopId, Integer type, String shopName, String liveId) {
+        XiaoeLiveRecord xiaoeLiveRecord = new XiaoeLiveRecord();
+        xiaoeLiveRecord.setShopId(shopId);
+        xiaoeLiveRecord.setType(type);
+        xiaoeLiveRecord.setShopName(shopName);
+        xiaoeLiveRecord.setLiveId(liveId);
+        xiaoeLiveRecord.setCreateTime(DateUtils.getNowDate());
+        save(xiaoeLiveRecord);
+    }
+}
diff --git a/ruoyi-modules/ruoyi-goods/src/main/java/com/ruoyi/goods/service/live/IXiaoeLiveAppointmentService.java b/ruoyi-modules/ruoyi-goods/src/main/java/com/ruoyi/goods/service/live/IXiaoeLiveAppointmentService.java
new file mode 100644
index 0000000..52dd780
--- /dev/null
+++ b/ruoyi-modules/ruoyi-goods/src/main/java/com/ruoyi/goods/service/live/IXiaoeLiveAppointmentService.java
@@ -0,0 +1,22 @@
+package com.ruoyi.goods.service.live;
+
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.ruoyi.goods.domain.pojo.live.XiaoeLiveAppointment;
+
+/**
+ * <p>
+ * 直播预约记录 服务类
+ * </p>
+ *
+ * @author mitao
+ * @since 2025-01-09
+ */
+public interface IXiaoeLiveAppointmentService extends IService<XiaoeLiveAppointment> {
+    /**
+     * 推送微信小程序订阅消息
+     * @param appointmentId
+     * @return
+     */
+    void push(Long appointmentId);
+}
diff --git a/ruoyi-modules/ruoyi-goods/src/main/java/com/ruoyi/goods/service/live/IXiaoeLiveRecordService.java b/ruoyi-modules/ruoyi-goods/src/main/java/com/ruoyi/goods/service/live/IXiaoeLiveRecordService.java
new file mode 100644
index 0000000..a1db5cf
--- /dev/null
+++ b/ruoyi-modules/ruoyi-goods/src/main/java/com/ruoyi/goods/service/live/IXiaoeLiveRecordService.java
@@ -0,0 +1,33 @@
+package com.ruoyi.goods.service.live;
+
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.ruoyi.goods.domain.pojo.live.XiaoeLiveRecord;
+
+import java.util.List;
+
+/**
+ * <p>
+ * 小鹅通直播添加记录表 服务类
+ * </p>
+ *
+ * @author mitao
+ * @since 2025-01-09
+ */
+public interface IXiaoeLiveRecordService extends IService<XiaoeLiveRecord> {
+    /**
+     * 根据直播id查询记录
+     * @param liveId
+     * @return
+     */
+    XiaoeLiveRecord getByLiveId(String liveId);
+
+    /**
+     * 根据店铺id查询记录
+     * @param shopId
+     * @return
+     */
+    List<XiaoeLiveRecord> getListByShopId(Long shopId);
+
+    void add(Long shopId, Integer type, String shopName, String liveId);
+}
diff --git a/ruoyi-modules/ruoyi-goods/src/main/java/com/ruoyi/goods/utils/WeChatSubscribeMessageSender.java b/ruoyi-modules/ruoyi-goods/src/main/java/com/ruoyi/goods/utils/WeChatSubscribeMessageSender.java
new file mode 100644
index 0000000..d1d858b
--- /dev/null
+++ b/ruoyi-modules/ruoyi-goods/src/main/java/com/ruoyi/goods/utils/WeChatSubscribeMessageSender.java
@@ -0,0 +1,76 @@
+package com.ruoyi.goods.utils;
+
+import cn.hutool.core.date.DateUtil;
+import cn.hutool.http.HttpUtil;
+import com.alibaba.fastjson2.JSONObject;
+import lombok.extern.slf4j.Slf4j;
+
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @author mitao
+ * @date 2025/1/10
+ */
+@Slf4j
+public class WeChatSubscribeMessageSender {
+    private static final String ACCESS_TOKEN_HOST = "https://api.weixin.qq.com/cgi-bin/token";
+    private static final String API_URL = "https://api.weixin.qq.com/cgi-bin/message/subscribe/send";
+
+    private static final String WX_APPID = "wxb7f0ea286fc4e535";
+
+    private static final String WX_SECRET = "852a2512a6ab559cafc68bae5d4160ac";
+    private static final String TEMPLATE_ID = "EFeu75n2GMmOg33PxL1HNoyftp16ukco5DUbBfNBytE";
+
+    /**
+     *发送消息
+     * @param touser  接收者(用户)的 openid
+     * @param title  直播主题
+     * @param shopName 直播间名称
+     * @param aliveStartAt 直播时间
+     */
+    public static void push(String touser, String title, String shopName, Date aliveStartAt) {
+
+        //1,获取access_token
+        String accessToken = getAccessTokenByWX();
+        String url = API_URL + "?access_token=" + accessToken;
+        Map<String, Object> params = new HashMap<>();
+        params.put("template_id", TEMPLATE_ID);
+        params.put("page", null);
+        params.put("touser", touser);
+        // 构建订阅消息内容的JSON对象
+        JSONObject messageData = new JSONObject();
+        messageData.put("thing1", createDataItem("直播主题", title));
+        messageData.put("thing4", createDataItem("直播间名称", shopName));
+        messageData.put("time5", createDataItem("直播时间", DateUtil.format(aliveStartAt, "MM-dd HH:mm:ss")));
+        params.put("data", messageData.toJSONString());
+        params.put("miniprogram_state", "trial");
+        params.put("lang", "zh_CN");
+        try {
+            String post = HttpUtil.post(url, JSONObject.toJSONString(params));
+            log.info("发送消息返回结果:{}", post);
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * 获取access_token
+     * @return
+     */
+    public static String getAccessTokenByWX() {
+        String host = ACCESS_TOKEN_HOST + "?appid=" + WX_APPID + "&secret=" + WX_SECRET + "&grant_type=client_credential";
+        log.info("host:{}", host);
+        return HttpUtil.get(host);
+    }
+    private static Map<String, Object> createDataItem(String name, String value) {
+        Map<String, Object> item = new HashMap<>();
+        item.put("value", value);
+        return item;
+    }
+
+    public static void main(String[] args) throws Exception {
+        push("oL-gp5Fn7BobtFZCsQ3ZTY7QGU84", "直播推送", "鸿瑞堂", new Date());
+    }
+}
diff --git a/ruoyi-modules/ruoyi-goods/src/main/java/com/ruoyi/goods/utils/XiaoeUtils.java b/ruoyi-modules/ruoyi-goods/src/main/java/com/ruoyi/goods/utils/XiaoeUtils.java
new file mode 100644
index 0000000..d409416
--- /dev/null
+++ b/ruoyi-modules/ruoyi-goods/src/main/java/com/ruoyi/goods/utils/XiaoeUtils.java
@@ -0,0 +1,375 @@
+package com.ruoyi.goods.utils;
+
+import cn.hutool.http.HttpUtil;
+import com.alibaba.fastjson2.JSONArray;
+import com.alibaba.fastjson2.JSONObject;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.ruoyi.common.core.utils.StringUtils;
+import com.ruoyi.common.redis.service.RedisService;
+import com.ruoyi.goods.domain.dto.XiaoeCourseQueryDto;
+import com.ruoyi.goods.domain.dto.XiaoeLiveDto;
+import com.ruoyi.goods.domain.dto.XiaoeLiveQueryDto;
+import com.ruoyi.goods.domain.vo.XiaoeCourseChapterVO;
+import com.ruoyi.goods.domain.vo.XiaoeCourseGroupVO;
+import com.ruoyi.goods.domain.vo.XiaoeCourseVO;
+import com.ruoyi.goods.domain.vo.XiaoeLiveVo;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.Resource;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.net.HttpURLConnection;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 小鹅通工具类
+ * @author mitao
+ * @date 2025/1/8
+ */
+@Slf4j
+@Component
+public class XiaoeUtils {
+    @Resource
+    private RedisService redisService;
+    private static final String BASE_URL = "https://api.xiaoe-tech.com/token";
+    //店铺的业务id
+    private  String app_id = "appwmuwNWD48082";
+    //应用的唯一标识,通过 client_id 来鉴别应用的身份
+    private  String client_id = "xopYwOAqNI36444";
+    //应用的凭证秘钥,即client_secret,用来保证应用来源的可靠性,防止被伪造
+    private  String secret_key = "qKFxbGR0OlKX85PVyfCvkRF1P6fLRBEu";
+    //固定填写client_credential
+    private  String grant_type = "client_credential";
+    //获取直播列表
+    private static final String LIVE_PAGE_LIST = "https://api.xiaoe-tech.com/xe.alive.list.get/2.0.0";
+    //获取直播详情
+    private static final String LIVE_DETAIL = "https://api.xiaoe-tech.com/xe.alive.detail.get/1.0.0";
+    //创建直播
+    private static final String LIVE_ADD = "https://api.xiaoe-tech.com/xe.alive.live.create/1.0.0";
+    //编辑直播
+    private static final String LIVE_EDIT = "https://api.xiaoe-tech.com/xe.alive.live.update/1.0.0";
+    //删除直播
+    private static final String LIVE_DELETE = "https://api.xiaoe-tech.com/xe.alive.live.delete/1.0.0";
+    //课程列表
+    private static final String COURSE_PAGE_LIST = "https://api.xiaoe-tech.com/xe.course.course.list/1.0.0";
+    //课程小节
+    private static final String COURSE_CHAPTER = "https://api.xiaoe-tech.com/xe.course.course.chapter.get/1.0.0";
+    //获取店铺商品分组列表
+    private static final String COURSE_GROUP_LIST = "https://api.xiaoe-tech.com/xe.resource_tags.list/1.0.0";
+
+    /**
+     * 获取小鹅通access_token
+     * @return
+     */
+    private String getAccessToken() {
+        Boolean flag = redisService.hasKey("xiaoe:access_token");
+        String accessToken = "";
+        if (flag) {
+            accessToken = redisService.getCacheObject("xiaoe:access_token");
+        } else {
+            String urlString = BASE_URL +
+                    "?app_id=" + this.app_id +
+                    "&client_id=" + this.client_id +
+                    "&secret_key=" + this.secret_key +
+                    "&grant_type=" + this.grant_type;
+            String result = null;
+            try {
+                URL reqURL = new URL(urlString);
+                HttpURLConnection httpURLConnection = (HttpURLConnection) reqURL.openConnection();
+                InputStreamReader isr = new InputStreamReader(httpURLConnection.getInputStream());
+                char[] chars = new char[1024];
+                result = "";
+                int len;
+                while ((len = isr.read(chars)) != -1) {
+                    result += new String(chars, 0, len);
+                }
+                isr.close();
+            } catch (MalformedURLException e) {
+                e.printStackTrace();
+            } catch (IOException e) {
+                e.printStackTrace();
+            }
+            JSONObject jsonObject = JSONObject.parseObject(result);
+            Map data = (Map) jsonObject.get("data");
+            accessToken = (String) data.get("access_token");
+        }
+        return accessToken;
+    }
+
+    /**
+     * 获取直播列表
+     * @param dto
+     * @return
+     */
+    public Page<XiaoeLiveVo> getLivePageList(XiaoeLiveQueryDto dto) {
+        Map<String,Object> postParams = new HashMap<>();
+        postParams.put("search_content", dto.getSearchContent());
+        postParams.put("create_mode", dto.getCreateMode());
+        postParams.put("state", dto.getState());
+        postParams.put("search_alive_type", dto.getSearchAliveType());
+        postParams.put("alive_play_state", dto.getAlivePlayState());
+        postParams.put("page", dto.getPage());
+        postParams.put("page_size", dto.getPageSize());
+        postParams.put("access_token", getAccessToken());
+        String post = HttpUtil.post(LIVE_PAGE_LIST, JSONObject.toJSONString(postParams));
+        Page<XiaoeLiveVo> xiaoeLiveVOPage = new Page<>();
+        if (StringUtils.isNotBlank(post)) {
+            JSONObject jsonObject = JSONObject.parseObject(post);
+            if (jsonObject.get("code").equals(0)) {
+                JSONObject data = jsonObject.getJSONObject("data");
+                JSONArray list = data.getJSONArray("list");
+                List<XiaoeLiveVo> xiaoeLiveVos = JSONArray.parseArray(list.toJSONString(), XiaoeLiveVo.class);
+                xiaoeLiveVOPage.setRecords(xiaoeLiveVos);
+                xiaoeLiveVOPage.setCurrent(data.getLong("page"));
+                xiaoeLiveVOPage.setTotal(data.getLong("total"));
+                xiaoeLiveVOPage.setPages(data.getLong("page_count"));
+            }
+        }
+        return xiaoeLiveVOPage;
+    }
+
+    /**
+     *获取直播详情
+     * @param id 直播ID
+     * @return
+     */
+    public XiaoeLiveVo getLiveDetail(String id) {
+        Map<String, Object> postParams = new HashMap<>();
+        postParams.put("id", id);
+        postParams.put("access_token", getAccessToken());
+        String post = HttpUtil.post(LIVE_DETAIL, JSONObject.toJSONString(postParams));
+        XiaoeLiveVo xiaoeLiveVO = null;
+        if (StringUtils.isNotBlank(post)) {
+            JSONObject jsonObject = JSONObject.parseObject(post);
+            if (jsonObject.get("code").equals(0)) {
+                JSONObject data = jsonObject.getJSONObject("data");
+                xiaoeLiveVO = JSONObject.parseObject(data.toJSONString(), XiaoeLiveVo.class);
+            }
+        }
+        return xiaoeLiveVO;
+    }
+
+    /**
+     * 创建直播
+     * @param dto
+     * @return
+     */
+    public String addLive(XiaoeLiveDto dto) {
+        Map<String, Object> requestParams = new HashMap<>();
+        Map<String, Object> resourceInfoMap = new HashMap<>();
+        resourceInfoMap.put("title", dto.getTitle());
+        resourceInfoMap.put("summary", dto.getSummary());
+        resourceInfoMap.put("zb_start_at", dto.getZbStartAt());
+        resourceInfoMap.put("zb_stop_at", dto.getZbStopAt());
+        resourceInfoMap.put("alive_type", dto.getAliveType());
+        resourceInfoMap.put("descrb", dto.getDescrb());
+        requestParams.put("resource_info", resourceInfoMap);
+        //	配置信息
+        Map<String, Object> moudleInfoMap = new HashMap<>();
+        moudleInfoMap.put("alive_mode", dto.getAliveMode());
+        requestParams.put("module_info", moudleInfoMap);
+        //讲师信息
+        Map<String, Object> roleInfoMap = new HashMap<>();
+        roleInfoMap.put("user_id", dto.getUserId());
+        requestParams.put("role_info", roleInfoMap);
+        //商品信息
+        Map<String, Object> goodsInfoMap = new HashMap<>();
+        goodsInfoMap.put("sale_type", 2);//售卖类型:1-单独售卖、2-关联售卖
+        goodsInfoMap.put("payment_type", dto.getPaymentType());
+        if (dto.getPaymentType().equals(3)) {
+            goodsInfoMap.put("resource_password", dto.getResourcePassword());
+        }
+        requestParams.put("goods_info", goodsInfoMap);
+        requestParams.put("access_token", getAccessToken());
+        String post = HttpUtil.post(LIVE_ADD, JSONObject.toJSONString(requestParams));
+        if (StringUtils.isNotBlank(post)) {
+            JSONObject jsonObject = JSONObject.parseObject(post);
+            log.info("创建直播返回结果:{}" ,post);
+            if (jsonObject.get("code").equals(0)) {
+                JSONObject data = jsonObject.getJSONObject("data");
+                return data.getString("id");
+            }
+        }
+        return null;
+    }
+    /**
+     * 创建直播
+     * @param dto
+     * @return
+     */
+    public Boolean editLive(XiaoeLiveDto dto) {
+        boolean flag = false;
+        Map<String, Object> requestParams = new HashMap<>();
+        Map<String, Object> resourceInfoMap = new HashMap<>();
+        resourceInfoMap.put("id", dto.getId());
+        resourceInfoMap.put("title", dto.getTitle());
+        resourceInfoMap.put("summary", dto.getSummary());
+        resourceInfoMap.put("zb_start_at", dto.getZbStartAt());
+        resourceInfoMap.put("zb_stop_at", dto.getZbStopAt());
+        resourceInfoMap.put("alive_type", dto.getAliveType());
+        resourceInfoMap.put("descrb", dto.getDescrb());
+        requestParams.put("resource_info", resourceInfoMap);
+        //	配置信息
+        Map<String, Object> moudleInfoMap = new HashMap<>();
+        moudleInfoMap.put("alive_mode", dto.getAliveMode());
+        requestParams.put("module_info", moudleInfoMap);
+        //讲师信息
+        Map<String, Object> roleInfoMap = new HashMap<>();
+        roleInfoMap.put("user_id", dto.getUserId());
+        requestParams.put("role_info", roleInfoMap);
+        //商品信息
+        Map<String, Object> goodsInfoMap = new HashMap<>();
+        goodsInfoMap.put("sale_type", 2);//售卖类型:1-单独售卖、2-关联售卖
+        goodsInfoMap.put("payment_type", dto.getPaymentType());
+        if (dto.getPaymentType().equals(3)) {
+            goodsInfoMap.put("resource_password", dto.getResourcePassword());
+        }
+        requestParams.put("goods_info", goodsInfoMap);
+        requestParams.put("access_token", getAccessToken());
+        String post = HttpUtil.post(LIVE_EDIT, JSONObject.toJSONString(requestParams));
+        if (StringUtils.isNotBlank(post)) {
+            JSONObject jsonObject = JSONObject.parseObject(post);
+            log.info("编辑直播返回结果:{}" ,post);
+            if (jsonObject.get("code").equals(0)) {
+                flag = true;
+            }
+        }
+        return flag;
+    }
+
+    /**
+     * 删除直播
+     * @param id
+     * @return
+     */
+    public Boolean deleteLive(String id) {
+        boolean flag = false;
+        Map<String, Object> requestParams = new HashMap<>();
+        requestParams.put("id", id);
+        requestParams.put("access_token", getAccessToken());
+        String post = HttpUtil.post(LIVE_DELETE, JSONObject.toJSONString(requestParams));
+        if (StringUtils.isNotBlank(post)) {
+            JSONObject jsonObject = JSONObject.parseObject(post);
+            log.info("删除直播返回结果:{}" ,post);
+            if (jsonObject.get("code").equals(0)) {
+                flag = true;
+            }
+        }
+        return flag;
+    }
+
+    /**
+     * 获取商品分组列表
+     * @return
+     */
+    public List<XiaoeCourseGroupVO> getCourseGroupList(){
+        Map<String, Object> requestParams = new HashMap<>();
+        requestParams.put("page", 1);
+        requestParams.put("page_size", 50);
+        requestParams.put("access_token", getAccessToken());
+        String post = HttpUtil.post(COURSE_GROUP_LIST, JSONObject.toJSONString(requestParams));
+        List<XiaoeCourseGroupVO> xiaoeCourseGroupVOList = new ArrayList<>();
+        if (StringUtils.isNotBlank(post)) {
+            JSONObject jsonObject = JSONObject.parseObject(post);
+            log.info("获取商品分组返回结果:{}" ,post);
+            if (jsonObject.get("code").equals(0)) {
+                JSONObject data = jsonObject.getJSONObject("data");
+                JSONArray jsonArray = data.getJSONArray("list");
+                xiaoeCourseGroupVOList = JSONArray.parseArray(jsonArray.toJSONString(), XiaoeCourseGroupVO.class);
+            }
+        }
+        return xiaoeCourseGroupVOList;
+    }
+
+    /**
+     * 获取课程列表
+     * @param dto
+     * @return
+     */
+    public Page<XiaoeCourseVO> getCoursePageList(XiaoeCourseQueryDto dto) {
+        JSONObject requestParams = JSONObject.parseObject(JSONObject.toJSONString(dto));
+        if (dto.getSortBy().equals(1)) {
+            requestParams.put("order_by", "modify");//根据创建时间排序
+            requestParams.put("order_type", "降序");
+        }
+
+        requestParams.put("sale_status", 1);
+        requestParams.put("access_token", getAccessToken());
+        String post = HttpUtil.post(COURSE_PAGE_LIST, requestParams.toJSONString());
+        Page<XiaoeCourseVO> page = new Page<>();
+        if (StringUtils.isNotBlank(post)) {
+            log.info("获取课程列表返回结果:{}" ,post);
+            JSONObject jsonObject = JSONObject.parseObject(post);
+            if (jsonObject.get("code").equals(0)) {
+                JSONObject data = jsonObject.getJSONObject("data");
+                JSONArray jsonArray = data.getJSONArray("list");
+                List<XiaoeCourseVO> xiaoeCourseVOList = JSONArray.parseArray(jsonArray.toJSONString(), XiaoeCourseVO.class);
+                if (dto.getSortBy().equals(2)){
+                    xiaoeCourseVOList.sort(Comparator.comparing(XiaoeCourseVO::getCurriculumTime));
+                }
+                page.setRecords(xiaoeCourseVOList);
+               page.setTotal(data.getLong("total"));
+            }
+        }
+        return page;
+    }
+
+    /**
+     * 查询课程目录小节
+     * @param id
+     * @return
+     */
+    public List<XiaoeCourseChapterVO> getCourseChapterDetail(String id) {
+        Map<String, Object> requestParams = new HashMap<>();
+        requestParams.put("course_id", id);
+        requestParams.put("access_token", getAccessToken());
+        String post = HttpUtil.post(COURSE_CHAPTER, JSONObject.toJSONString(requestParams));
+        List<XiaoeCourseChapterVO> xiaoeCourseChapterVO = new ArrayList<>();
+        if (StringUtils.isNotBlank(post)) {
+            log.info("查询课程目录小节返回结果:{}" ,post);
+            JSONObject jsonObject = JSONObject.parseObject(post);
+            if (jsonObject.get("code").equals(0)) {
+                String data = jsonObject.getString("data");
+                xiaoeCourseChapterVO = JSONArray.parseArray(data, XiaoeCourseChapterVO.class);
+            }
+        }
+        return xiaoeCourseChapterVO;
+    }
+    public static void main(String[] args) {
+        String urlString = BASE_URL +
+                "?app_id=appwmuwNWD48082" +
+                "&client_id=xopYwOAqNI36444"+
+                "&secret_key=qKFxbGR0OlKX85PVyfCvkRF1P6fLRBEu" +
+                "&grant_type=client_credential";
+        String result = null;
+        try {
+            URL reqURL = new URL(urlString);
+            HttpURLConnection httpURLConnection = (HttpURLConnection) reqURL.openConnection();
+            InputStreamReader isr = new InputStreamReader(httpURLConnection.getInputStream());
+            char[] chars = new char[1024];
+            result = "";
+            int len;
+            while ((len = isr.read(chars)) != -1) {
+                result += new String(chars, 0, len);
+            }
+            isr.close();
+        } catch (MalformedURLException e) {
+            e.printStackTrace();
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+        JSONObject jsonObject = JSONObject.parseObject(result);
+        System.out.println(jsonObject);
+        Map data = (Map) jsonObject.get("data");
+      String accessToken = (String) data.get("access_token");
+        System.out.println(accessToken);
+    }
+
+}
diff --git a/ruoyi-modules/ruoyi-goods/src/main/resources/mapper/live/XiaoeLiveAppointmentMapper.xml b/ruoyi-modules/ruoyi-goods/src/main/resources/mapper/live/XiaoeLiveAppointmentMapper.xml
new file mode 100644
index 0000000..08ed38e
--- /dev/null
+++ b/ruoyi-modules/ruoyi-goods/src/main/resources/mapper/live/XiaoeLiveAppointmentMapper.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.ruoyi.goods.mapper.live.XiaoeLiveAppointmentMapper">
+
+</mapper>
diff --git a/ruoyi-modules/ruoyi-goods/src/main/resources/mapper/live/XiaoeLiveRecordMapper.xml b/ruoyi-modules/ruoyi-goods/src/main/resources/mapper/live/XiaoeLiveRecordMapper.xml
new file mode 100644
index 0000000..7e4d32f
--- /dev/null
+++ b/ruoyi-modules/ruoyi-goods/src/main/resources/mapper/live/XiaoeLiveRecordMapper.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.ruoyi.goods.mapper.live.XiaoeLiveRecordMapper">
+
+</mapper>
diff --git a/ruoyi-modules/ruoyi-shop/src/main/java/com/ruoyi/shop/domain/dto/MgtEditShopDto.java b/ruoyi-modules/ruoyi-shop/src/main/java/com/ruoyi/shop/domain/dto/MgtEditShopDto.java
index 116f5a9..3920dfe 100644
--- a/ruoyi-modules/ruoyi-shop/src/main/java/com/ruoyi/shop/domain/dto/MgtEditShopDto.java
+++ b/ruoyi-modules/ruoyi-shop/src/main/java/com/ruoyi/shop/domain/dto/MgtEditShopDto.java
@@ -139,4 +139,7 @@
 
     @ApiModelProperty(value="商户banner 多个用,隔开")
     private String shopBanners;
+
+    @ApiModelProperty(value="小鹅通讲师id 多个用,隔开")
+    private String xiaoeUserId;
 }
diff --git a/ruoyi-modules/ruoyi-shop/src/main/java/com/ruoyi/shop/domain/vo/MgtShopInfoVo.java b/ruoyi-modules/ruoyi-shop/src/main/java/com/ruoyi/shop/domain/vo/MgtShopInfoVo.java
index ce732c8..8bf91bf 100644
--- a/ruoyi-modules/ruoyi-shop/src/main/java/com/ruoyi/shop/domain/vo/MgtShopInfoVo.java
+++ b/ruoyi-modules/ruoyi-shop/src/main/java/com/ruoyi/shop/domain/vo/MgtShopInfoVo.java
@@ -149,6 +149,9 @@
     @ApiModelProperty(value="证书list")
     private List<ShopCertificate> shopCertificateList;
 
+    @ApiModelProperty(value="小鹅通讲师id 多个用,隔开")
+    private String xiaoeUserId;
+
 
 
 
diff --git a/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/listener/RedisListener.java b/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/listener/RedisListener.java
index b852a0e..572ce5c 100644
--- a/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/listener/RedisListener.java
+++ b/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/listener/RedisListener.java
@@ -7,6 +7,7 @@
 import com.ruoyi.system.api.service.RemoteActivityService;
 import com.ruoyi.system.api.service.RemoteConfigService;
 import com.ruoyi.system.api.service.RemoteCouponService;
+import com.ruoyi.system.api.service.RemoteGoodsService;
 import com.ruoyi.system.api.service.RemoteOrderService;
 import lombok.extern.log4j.Log4j2;
 import org.springframework.data.redis.connection.Message;
@@ -44,6 +45,9 @@
     @Resource
     private RemoteOrderService remoteOrderService;
 
+    @Resource
+    private RemoteGoodsService remoteGoodsService;
+
     public RedisListener(RedisMessageListenerContainer listenerContainer,
                          RedisTemplate redisTemplate) {
         super(listenerContainer);
@@ -73,6 +77,8 @@
                     }else if(DelayTaskEnum.ORDER_AUTOMATIC_CANCEL.getCode().equals(operation)){
                         //自动结束任务
                         autoCancelOrder(split[1]);
+                    } else if (DelayTaskEnum.LIVE_APPOINTMENT_TASK.getCode().equals(operation)) {
+                        push(Long.valueOf(split[1]));
                     }
 
                     //删除失效的key
@@ -84,6 +90,16 @@
         }
     }
 
+    /**
+     * 推送消息
+     * @param appointmentId
+     */
+    private void push(Long appointmentId) {
+        remoteGoodsService.push(appointmentId);
+        //删除定时任务
+        remoteConfigService.deleteDelayTask(DelayTaskEnum.LIVE_APPOINTMENT_TASK.getCode()+"-"+appointmentId);
+    }
+
     public <T> T getAndSet(final String key, T value){
         T oldValue=null;
         try {

--
Gitblit v1.7.1