Pu Zhibing
2025-02-28 315c07aa4f70fa26143076f462ed0e6a44238bfa
Merge branch 'dev-1.2' of http://120.76.84.145:10101/gitblit/r/java/HongRuiTang
12个文件已修改
31个文件已添加
2611 ■■■■■ 已修改文件
ruoyi-api/ruoyi-api-system/src/main/java/com/ruoyi/system/api/constant/DelayTaskEnum.java 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-api/ruoyi-api-system/src/main/java/com/ruoyi/system/api/domain/poji/shop/Shop.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-api/ruoyi-api-system/src/main/java/com/ruoyi/system/api/factory/RemoteGoodsFallbackFactory.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-api/ruoyi-api-system/src/main/java/com/ruoyi/system/api/service/RemoteGoodsService.java 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-core/pom.xml 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/utils/DateUtils.java 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-goods/src/main/java/com/ruoyi/goods/controller/business/MerCourseController.java 68 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-goods/src/main/java/com/ruoyi/goods/controller/business/MerLiveController.java 112 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-goods/src/main/java/com/ruoyi/goods/controller/concole/LiveController.java 33 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-goods/src/main/java/com/ruoyi/goods/controller/management/MgtLiveController.java 90 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-goods/src/main/java/com/ruoyi/goods/controller/miniapp/AppCourseController.java 68 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-goods/src/main/java/com/ruoyi/goods/controller/miniapp/AppLiveController.java 54 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-goods/src/main/java/com/ruoyi/goods/controller/staff/StaffLiveController.java 39 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-goods/src/main/java/com/ruoyi/goods/domain/dto/XiaoeCourseQueryDto.java 43 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-goods/src/main/java/com/ruoyi/goods/domain/dto/XiaoeLiveDto.java 69 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-goods/src/main/java/com/ruoyi/goods/domain/dto/XiaoeLiveQueryDto.java 41 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-goods/src/main/java/com/ruoyi/goods/domain/pojo/live/XiaoeLiveAppointment.java 63 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-goods/src/main/java/com/ruoyi/goods/domain/pojo/live/XiaoeLiveRecord.java 64 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-goods/src/main/java/com/ruoyi/goods/domain/vo/XiaoeCourseChapterVO.java 36 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-goods/src/main/java/com/ruoyi/goods/domain/vo/XiaoeCourseGroupVO.java 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-goods/src/main/java/com/ruoyi/goods/domain/vo/XiaoeCourseVO.java 53 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-goods/src/main/java/com/ruoyi/goods/domain/vo/XiaoeLiveDetailVOV2.java 181 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-goods/src/main/java/com/ruoyi/goods/domain/vo/XiaoeLiveTeacherVo.java 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-goods/src/main/java/com/ruoyi/goods/domain/vo/XiaoeLiveVo.java 112 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-goods/src/main/java/com/ruoyi/goods/domain/vo/XiaoeLiveVoV2.java 182 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-goods/src/main/java/com/ruoyi/goods/mapper/live/XiaoeLiveAppointmentMapper.java 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-goods/src/main/java/com/ruoyi/goods/mapper/live/XiaoeLiveRecordMapper.java 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-goods/src/main/java/com/ruoyi/goods/service/biz/XiaoeCourseService.java 48 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-goods/src/main/java/com/ruoyi/goods/service/biz/XiaoeLiveService.java 334 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-goods/src/main/java/com/ruoyi/goods/service/impl/live/XiaoeLiveAppointmentServiceImpl.java 57 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-goods/src/main/java/com/ruoyi/goods/service/impl/live/XiaoeLiveRecordServiceImpl.java 70 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-goods/src/main/java/com/ruoyi/goods/service/live/IXiaoeLiveAppointmentService.java 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-goods/src/main/java/com/ruoyi/goods/service/live/IXiaoeLiveRecordService.java 41 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-goods/src/main/java/com/ruoyi/goods/utils/WeChatSubscribeMessageSender.java 83 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-goods/src/main/java/com/ruoyi/goods/utils/XiaoeUtils.java 458 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-goods/src/main/resources/mapper/live/XiaoeLiveAppointmentMapper.xml 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-goods/src/main/resources/mapper/live/XiaoeLiveRecordMapper.xml 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-member/pom.xml 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-shop/src/main/java/com/ruoyi/shop/domain/dto/MgtEditShopDto.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-shop/src/main/java/com/ruoyi/shop/domain/vo/MgtShopInfoVo.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/controller/GeneratorMybatisPlus.java 12 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/controller/sys/SysUserController.java 20 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/listener/RedisListener.java 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
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;
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_user_id")
    private String xiaoeUserId;
    @Override
    protected Serializable pkVal() {
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());
            }
        };
    }
}
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);
}
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>
ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/utils/DateUtils.java
@@ -222,4 +222,15 @@
        }
        return sb.toString();
    }
    public static Date getTodayStartTime() {
        // 获取今天的日期
        LocalDate today = LocalDate.now();
        // 获取当天的开始时间(00:00:00)
        LocalDateTime startOfDay = today.atStartOfDay();
        // 转换为 Date 类型
        return Date.from(startOfDay.atZone(ZoneId.systemDefault()).toInstant());
    }
}
ruoyi-modules/ruoyi-goods/src/main/java/com/ruoyi/goods/controller/business/MerCourseController.java
New file
@@ -0,0 +1,68 @@
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.XiaoeCourseGroupVO;
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));
    }
    /**
     * 获取课程分组列表
     * @return
     */
    @ApiOperation("获取课程分组列表")
    @GetMapping("/group")
    public R<List<XiaoeCourseGroupVO>> getCourseGroupList() {
        return R.ok(xiaoeCourseService.getCourseGroupList());
    }
}
ruoyi-modules/ruoyi-goods/src/main/java/com/ruoyi/goods/controller/business/MerLiveController.java
New file
@@ -0,0 +1,112 @@
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.XiaoeLiveDetailVOV2;
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;
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("/detail/{id}")
    public R<XiaoeLiveDetailVOV2> detail(@ApiParam(name = "id",value = "直播id", required = true) @PathVariable("id") String id) {
        return R.ok(xiaoeLiveService.getLiveDetail(id));
    }
    /**
     * 预约直播
     * @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("编辑直播")
    @PostMapping
    public R<Boolean> edit(@Valid @RequestBody XiaoeLiveDto dto) {
        return R.ok(xiaoeLiveService.edit(dto));
    }
}
ruoyi-modules/ruoyi-goods/src/main/java/com/ruoyi/goods/controller/concole/LiveController.java
New file
@@ -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();
    }
}
ruoyi-modules/ruoyi-goods/src/main/java/com/ruoyi/goods/controller/management/MgtLiveController.java
New file
@@ -0,0 +1,90 @@
package com.ruoyi.goods.controller.management;
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.XiaoeLiveDetailVOV2;
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.web.bind.annotation.DeleteMapping;
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;
/**
 * @author mitao
 * @date 2025/1/12
 */
@Api(tags = {"管理后台直播相关接口"})
@RestController
@RequestMapping("/mgt/live")
@RequiredArgsConstructor
public class MgtLiveController {
    private final XiaoeLiveService xiaoeLiveService;
    /**
     * 直播分页列表
     * @param dto
     * @return
     */
    @ApiOperation("直播分页列表")
    @PostMapping("/page")
    public R<Page<XiaoeLiveVo>> page(@Valid @RequestBody XiaoeLiveQueryDto dto) {
        return R.ok(xiaoeLiveService.getMgtLivePage(dto));
    }
    /**
     * 直播详情
     * @param id
     * @return
     */
    @ApiOperation("直播详情")
    @GetMapping("/detail/{id}")
    public R<XiaoeLiveDetailVOV2> detail(@ApiParam(name = "id",value = "直播id", required = true) @PathVariable("id") String id) {
        return R.ok(xiaoeLiveService.getLiveDetail(id));
    }
    /**
     * 创建直播
     * @param dto
     * @return
     */
    @ApiOperation("添加直播")
    @PostMapping("/create")
    public R<?> create(@Valid @RequestBody XiaoeLiveDto dto) {
        xiaoeLiveService.create(dto,1);
        return R.ok();
    }
    /**
     * 编辑直播
     * @param dto
     * @return
     */
    @ApiOperation("编辑直播")
    @PutMapping
    public R<Boolean> edit(@Valid @RequestBody XiaoeLiveDto dto) {
        return R.ok(xiaoeLiveService.edit(dto));
    }
    /**
     * 删除直播
     * @param id
     * @return
     */
    @ApiOperation("删除")
    @DeleteMapping("/{id}")
    public R<?> delete(@ApiParam(name = "id",value = "直播id", required = true) @PathVariable("id") String id) {
        xiaoeLiveService.deleteLive(id);
        return R.ok();
    }
}
ruoyi-modules/ruoyi-goods/src/main/java/com/ruoyi/goods/controller/miniapp/AppCourseController.java
New file
@@ -0,0 +1,68 @@
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.XiaoeCourseGroupVO;
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));
    }
    /**
     * 获取课程分组列表
     * @return
     */
    @ApiOperation("获取课程分组列表")
    @GetMapping("/group")
    public R<List<XiaoeCourseGroupVO>> getCourseGroupList() {
        return R.ok(xiaoeCourseService.getCourseGroupList());
    }
}
ruoyi-modules/ruoyi-goods/src/main/java/com/ruoyi/goods/controller/miniapp/AppLiveController.java
New file
@@ -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));
    }
}
ruoyi-modules/ruoyi-goods/src/main/java/com/ruoyi/goods/controller/staff/StaffLiveController.java
New file
@@ -0,0 +1,39 @@
package com.ruoyi.goods.controller.staff;
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 lombok.RequiredArgsConstructor;
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/15
 */
@Api(tags = {"员工端直播相关接口"})
@RestController
@RequestMapping("/staff/live")
@RequiredArgsConstructor
public class StaffLiveController {
    private final XiaoeLiveService xiaoeLiveService;
    /**
     * 员工端直播列表
     * @param dto
     * @return
     */
    @ApiOperation("直播列表")
    @PostMapping("/page")
    public R<Page<XiaoeLiveVo>> minePage(@Valid @RequestBody XiaoeLiveQueryDto dto) {
        return R.ok(xiaoeLiveService.getStaffLivePage(dto));
    }
}
ruoyi-modules/ruoyi-goods/src/main/java/com/ruoyi/goods/domain/dto/XiaoeCourseQueryDto.java
New file
@@ -0,0 +1,43 @@
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.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 = "课程创建来源 0:全部,1:课程,2:圈子")
    @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 = "排序类型不能为空")
    private Integer sortBy;
}
ruoyi-modules/ruoyi-goods/src/main/java/com/ruoyi/goods/domain/dto/XiaoeLiveDto.java
New file
@@ -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 org.hibernate.validator.constraints.Length;
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 = "直播简介不能为空")
    @Length(max = 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 = "直播类型 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 = "直播详情不能为空")
    @Length(max = 5000, message = "直播详情长度不能超过5000")
    @JSONField(name = "descrb")
    private String descrb;
}
ruoyi-modules/ruoyi-goods/src/main/java/com/ruoyi/goods/domain/dto/XiaoeLiveQueryDto.java
New file
@@ -0,0 +1,41 @@
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;
    @ApiModelProperty("门店id")
    private Long shopId;
    @ApiModelProperty("直播类型 1:平台直播 2:商家直播")
    private Integer liveType;
}
ruoyi-modules/ruoyi-goods/src/main/java/com/ruoyi/goods/domain/pojo/live/XiaoeLiveAppointment.java
New file
@@ -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;
}
ruoyi-modules/ruoyi-goods/src/main/java/com/ruoyi/goods/domain/pojo/live/XiaoeLiveRecord.java
New file
@@ -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;
}
ruoyi-modules/ruoyi-goods/src/main/java/com/ruoyi/goods/domain/vo/XiaoeCourseChapterVO.java
New file
@@ -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;
}
ruoyi-modules/ruoyi-goods/src/main/java/com/ruoyi/goods/domain/vo/XiaoeCourseGroupVO.java
New file
@@ -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;
}
ruoyi-modules/ruoyi-goods/src/main/java/com/ruoyi/goods/domain/vo/XiaoeCourseVO.java
New file
@@ -0,0 +1,53 @@
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("课程id")
    @JSONField(name = "resource_type")
    private Integer resourceType;
    @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 = "last_updated_at")
    private Date lastUpdatedAt;
    @ApiModelProperty("开课结束时间")
    @JSONField(name = "curriculum_end_time")
    private Date curriculumEndTime;
}
ruoyi-modules/ruoyi-goods/src/main/java/com/ruoyi/goods/domain/vo/XiaoeLiveDetailVOV2.java
New file
@@ -0,0 +1,181 @@
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/14
 */
@Data
@ApiModel("直播详情2.0视图对象")
public class XiaoeLiveDetailVOV2 {
    @ApiModelProperty(value = "资源信息")
    @JSONField(name = "resource_info")
    private ResourceInfo resourceInfo;
    @ApiModelProperty(value = "配置信息")
    @JSONField(name = "module_info")
    private ModuleInfo moduleInfo;
    @ApiModelProperty(value = "商品信息")
    @JSONField(name = "goods_info")
    private GoodsInfo goodsInfo;
    @ApiModelProperty(value = "关联信息")
    @JSONField(name = "relation_info")
    private RelationInfo relationInfo;
    @ApiModelProperty(value = "讲师信息")
    @JSONField(name = "role_info")
    private List<RelationInfo.RoleInfo> roleInfo;
    @Data
    @ApiModel(description = "资源信息")
    public static class ResourceInfo {
        @ApiModelProperty(value = "店铺id", example = "app_id")
        @JSONField(name = "app_id")
        private String appId;
        @ApiModelProperty(value = "直播ID", example = "id")
        @JSONField(name = "id")
        private String id;
        @ApiModelProperty(value = "直播标题", example = "title")
        @JSONField(name = "title")
        private String title;
        @ApiModelProperty(value = "直播简介", example = "summary")
        @JSONField(name = "summary")
        private String summary;
        @ApiModelProperty(value = "直播详情(仅允许纯文本)", example = "descrb")
        @JSONField(name = "descrb")
        private String descrb;
        @ApiModelProperty(value = "直播类型:0-语音,1-录播直播,2-推流直播", example = "2")
        @JSONField(name = "alive_type")
        private Integer aliveType;
        @ApiModelProperty(value = "预设直播开始时间", example = "2023-05-31 18:00:00")
        @JSONField(name = "zb_start_at")
        private String zbStartAt;
        @ApiModelProperty(value = "预设直播时长,单位:秒", example = "3600")
        @JSONField(name = "zb_stop_at")
        private Integer zbStopAt;
        @ApiModelProperty(value = "详情封面图素材ID", example = "img_material_id")
        @JSONField(name = "img_material_id")
        private String imgMaterialId;
        @ApiModelProperty(value = "宣传封面图素材ID", example = "alive_img_material_id")
        @JSONField(name = "alive_img_material_id")
        private String aliveImgMaterialId;
        @ApiModelProperty(value = "暖场封面图素材ID", example = "aliveroom_img_material_id")
        @JSONField(name = "aliveroom_img_material_id")
        private String aliveroomImgMaterialId;
        @ApiModelProperty(value = "暖场视频封面图素材ID", example = "warm_up_video_cover_material_id")
        @JSONField(name = "warm_up_video_cover_material_id")
        private String warmUpVideoCoverMaterialId;
        @ApiModelProperty(value = "暖场视频素材ID", example = "warm_up_video_material_id")
        @JSONField(name = "warm_up_video_material_id")
        private String warmUpVideoMaterialId;
        @ApiModelProperty(value = "录播视频素材ID", example = "alive_video_material_id")
        @JSONField(name = "alive_video_material_id")
        private String aliveVideoMaterialId;
    }
    @Data
    @ApiModel(description = "配置信息")
    public static class ModuleInfo {
        @ApiModelProperty(value = "是否开启回放:0-开启,1-关闭", example = "0")
        @JSONField(name = "is_lookback")
        private Integer isLookback;
        @ApiModelProperty(value = "回放是否允许倍速播放或快进:0-允许,1-禁止", example = "0")
        @JSONField(name = "play_fast_state_switch")
        private Integer playFastStateSwitch;
        @ApiModelProperty(value = "直播模式:0-横屏直播,1-竖屏直播", example = "0")
        @JSONField(name = "alive_mode")
        private Integer aliveMode;
        @ApiModelProperty(value = "回放有效期设置:1-永久,2-限时", example = "1")
        @JSONField(name = "expire_type")
        private Integer expireType;
        @ApiModelProperty(value = "回放过期时间", example = "2023-06-30")
        @JSONField(name = "expire")
        private String expire;
        @ApiModelProperty(value = "暖场设置:1-暖场图,2-暖场视频", example = "1")
        @JSONField(name = "warm_up")
        private Integer warmUp;
        @ApiModelProperty(value = "是否开启完成条件:0-关闭,1-开启", example = "0")
        @JSONField(name = "is_open_complete_time")
        private Integer isOpenCompleteTime;
        @ApiModelProperty(value = "设置最短学习时间(单位:分钟)", example = "0")
        @JSONField(name = "complete_time")
        private Integer completeTime;
        @ApiModelProperty(value = "是否开启联系学员:0-关闭,1-开启", example = "0")
        @JSONField(name = "is_contact_on")
        private Integer isContactOn;
    }
    @Data
    @ApiModel(description = "商品信息")
    public static class GoodsInfo {
        @ApiModelProperty(value = "售卖类型:1-单独售卖、2-关联售卖", example = "1")
        @JSONField(name = "sale_type")
        private Integer saleType;
        @ApiModelProperty(value = "支付类型:1-免费,2-收费,3-加密,4-指定学员可用,5-仅关联上级资源", example = "1")
        @JSONField(name = "payment_type")
        private Integer paymentType;
        @ApiModelProperty(value = "价格(单位:分)", example = "0")
        @JSONField(name = "piece_price")
        private Integer piecePrice;
        @ApiModelProperty(value = "划线价格(单位:分)", example = "0")
        @JSONField(name = "line_price")
        private Integer linePrice;
        @ApiModelProperty(value = "密码", example = "resource_password")
        @JSONField(name = "resource_password")
        private String resourcePassword;
        @ApiModelProperty(value = "上下架:1-下架,0-上架", example = "0")
        @JSONField(name = "recycle_bin_state")
        private Integer recycleBinState;
        @ApiModelProperty(value = "定时上架时间", example = "2023-05-31 17:19:26")
        @JSONField(name = "start_at")
        private String startAt;
        @ApiModelProperty(value = "是否停售:0-否,1-是", example = "0")
        @JSONField(name = "is_stop_sell")
        private Integer isStopSell;
        @ApiModelProperty(value = "商品编码", example = "goods_sn")
        @JSONField(name = "goods_sn")
        private String goodsSn;
        @ApiModelProperty(value = "商品状态:0-可见,1-隐藏,2-删除", example = "0")
        @JSONField(name = "state")
        private Integer state;
    }
    @Data
    @ApiModel(description = "关联信息")
    public static class RelationInfo {
        @ApiModelProperty(value = "资源id")
        @JSONField(name = "package")
        private List<String> packageIds;
        @ApiModelProperty(value = "预留字段,暂未支持")
        @JSONField(name = "attach_goods")
        private List<String> attachGoods;
        @ApiModelProperty(value = "预留字段,暂未支持")
        @JSONField(name = "tags")
        private List<String> tags;
        @Data
        @ApiModel(description = "讲师信息列表")
        public static class RoleInfo {
            @ApiModelProperty(value = "自定义身份标签", example = "讲师")
            @JSONField(name = "role_name")
            private String roleName;
            @ApiModelProperty(value = "用户id", example = "u_6688888888889999999")
            @JSONField(name = "user_id")
            private String userId;
            @ApiModelProperty(value = "用户昵称", example = "测试")
            @JSONField(name = "nickname")
            private String nickname;
            @ApiModelProperty(value = "用户头像", example = "avator")
            @JSONField(name = "avator")
            private String avator;
            @ApiModelProperty(value = "是否接受打赏:1-接受打赏,0-不接受打赏", example = "1")
            @JSONField(name = "is_can_exceptional")
            private Integer isCanExceptional;
        }
    }
}
ruoyi-modules/ruoyi-goods/src/main/java/com/ruoyi/goods/domain/vo/XiaoeLiveTeacherVo.java
New file
@@ -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;  // 讲师绑定手机号
}
ruoyi-modules/ruoyi-goods/src/main/java/com/ruoyi/goods/domain/vo/XiaoeLiveVo.java
New file
@@ -0,0 +1,112 @@
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;
    @ApiModelProperty("创建门店")
    private String shopName;
}
ruoyi-modules/ruoyi-goods/src/main/java/com/ruoyi/goods/domain/vo/XiaoeLiveVoV2.java
New file
@@ -0,0 +1,182 @@
package com.ruoyi.goods.domain.vo;
import lombok.Data;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import com.alibaba.fastjson2.annotation.JSONField;
@Data
@ApiModel(description = "小鹅通直播视图对象2.0")
public class XiaoeLiveVoV2 {
    @ApiModelProperty(value = "店铺ID", example = "app_id")
    @JSONField(name = "app_id")
    private String appId;
    @ApiModelProperty(value = "直播ID", example = "id")
    @JSONField(name = "id")
    private String id;
    @ApiModelProperty(value = "房间ID", example = "room_id")
    @JSONField(name = "room_id")
    private String roomId;
    @ApiModelProperty(value = "直播标题", example = "title")
    @JSONField(name = "title")
    private String title;
    @ApiModelProperty(value = "直播封面图", example = "img_url")
    @JSONField(name = "img_url")
    private String imgUrl;
    @ApiModelProperty(value = "页面url", example = "page_url")
    @JSONField(name = "page_url")
    private String pageUrl;
    @ApiModelProperty(value = "封面压缩后的路径", example = "img_url_compressed")
    @JSONField(name = "img_url_compressed")
    private String imgUrlCompressed;
    @ApiModelProperty(value = "评论数量", example = "comment_count")
    @JSONField(name = "comment_count")
    private Integer commentCount;
    @ApiModelProperty(value = "带货开关:1=开,0=关", example = "1")
    @JSONField(name = "is_takegoods")
    private Integer isTakegoods;
    @ApiModelProperty(value = "带货商品分组ID", example = "takegoods")
    @JSONField(name = "takegoods")
    private String takegoods;
    @ApiModelProperty(value = "付费类型:1-免费、2-单笔、3-付费产品包", example = "1")
    @JSONField(name = "payment_type")
    private Integer paymentType;
    @ApiModelProperty(value = "是否公开售卖,1公开,0不公开", example = "1")
    @JSONField(name = "is_public")
    private Integer isPublic;
    @ApiModelProperty(value = "是否停售,0-否、1-是", example = "0")
    @JSONField(name = "is_stop_sell")
    private Integer isStopSell;
    @ApiModelProperty(value = "视频是否转码,0-表示未转码,1-表示已转码,2-转码失败", example = "0")
    @JSONField(name = "is_transcode")
    private Integer isTranscode;
    @ApiModelProperty(value = "payment_type为2时,单笔价格(分);payment_type为3时,专栏价格(分)", example = "0")
    @JSONField(name = "piece_price")
    private Integer piecePrice;
    @ApiModelProperty(value = "划线价", example = "0")
    @JSONField(name = "line_price")
    private Integer linePrice;
    @ApiModelProperty(value = "该资源是否需要密码", example = "0")
    @JSONField(name = "have_password")
    private Integer havePassword;
    @ApiModelProperty(value = "直播类型:0-语音直播,1-视频直播,2-推流直播,3-ppt直播", example = "1")
    @JSONField(name = "alive_type")
    private Integer aliveType;
    @ApiModelProperty(value = "订阅量", example = "0")
    @JSONField(name = "purchase_count")
    private Integer purchaseCount;
    @ApiModelProperty(value = "打赏金额", example = "0")
    @JSONField(name = "reward_sum")
    private Integer rewardSum;
    @ApiModelProperty(value = "强制封禁:0-否 1-是", example = "0")
    @JSONField(name = "is_ban")
    private Integer isBan;
    @ApiModelProperty(value = "强制下架:0-否 1-是", example = "0")
    @JSONField(name = "on_shelf")
    private Integer onShelf;
    @ApiModelProperty(value = "上下架状态:0-上架,1-下架", example = "0")
    @JSONField(name = "recycle_bin_state")
    private Integer recycleBinState;
    @ApiModelProperty(value = "推流状态,0断流,1推流中,2推流未开始", example = "0")
    @JSONField(name = "push_state")
    private Integer pushState;
    @ApiModelProperty(value = "直播状态:0-可见,1-关闭,2-删除", example = "0")
    @JSONField(name = "state")
    private Integer state;
    @ApiModelProperty(value = "上架时间", example = "2023-05-31 17:19:26")
    @JSONField(name = "start_at")
    private String startAt;
    @ApiModelProperty(value = "直播开始时间", example = "2023-05-31 17:19:26")
    @JSONField(name = "zb_start_at")
    private String zbStartAt;
    @ApiModelProperty(value = "手动结束直播时间", example = "2023-05-31 17:19:26")
    @JSONField(name = "manual_stop_at")
    private String manualStopAt;
    @ApiModelProperty(value = "转播店铺名称", example = "source_shop_name")
    @JSONField(name = "source_shop_name")
    private String sourceShopName;
    @ApiModelProperty(value = "素材状态", example = "0")
    @JSONField(name = "material_state")
    private Integer materialState;
    @ApiModelProperty(value = "视频时长(s)", example = "0")
    @JSONField(name = "video_length")
    private Integer videoLength;
    @ApiModelProperty(value = "直播状态", example = "0")
    @JSONField(name = "alive_state")
    private Integer aliveState;
    @ApiModelProperty(value = "直播模式:0.无;1竖屏直播", example = "0")
    @JSONField(name = "alive_mode")
    private Integer aliveMode;
    @ApiModelProperty(value = "创建类型:0-自创建,1-转播创建", example = "0")
    @JSONField(name = "create_mode")
    private Integer createMode;
    @ApiModelProperty(value = "圆桌会议功能是否开启", example = "0")
    @JSONField(name = "is_round_table_on")
    private Integer isRoundTableOn;
    @ApiModelProperty(value = "关联商品", example = "query_package_list")
    @JSONField(name = "query_package_list")
    private String queryPackageList;
    @ApiModelProperty(value = "售卖有效期", example = "course_expire")
    @JSONField(name = "course_expire")
    private CourseExpire courseExpire;
    @ApiModelProperty("预约状态 1:已预约 0:未预约")
    private Integer appointmentState=0;
    @ApiModelProperty("创建门店")
    private String shopName;
    @Data
    @ApiModel(description = "Course Expire Information")
    public static class CourseExpire {
        @ApiModelProperty(value = "有效期类型:0=永久有效,1=固定有效期,2=自定义有效期", example = "0")
        @JSONField(name = "period_type")
        private Integer periodType;
        @ApiModelProperty(value = "自定义有效时长", example = "period_value")
        @JSONField(name = "period_value")
        private String periodValue;
        @ApiModelProperty(value = "是否允许重复购买:1=是,0=否", example = "0")
        @JSONField(name = "is_allow_repeat_purchase")
        private Integer isAllowRepeatPurchase;
    }
}
ruoyi-modules/ruoyi-goods/src/main/java/com/ruoyi/goods/mapper/live/XiaoeLiveAppointmentMapper.java
New file
@@ -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> {
}
ruoyi-modules/ruoyi-goods/src/main/java/com/ruoyi/goods/mapper/live/XiaoeLiveRecordMapper.java
New file
@@ -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> {
}
ruoyi-modules/ruoyi-goods/src/main/java/com/ruoyi/goods/service/biz/XiaoeCourseService.java
New file
@@ -0,0 +1,48 @@
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.XiaoeCourseGroupVO;
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);
    }
    /**
     * 获取课程分组列表
     * @return
     */
    public List<XiaoeCourseGroupVO> getCourseGroupList() {
        return xiaoeUtils.getCourseGroupList();
    }
}
ruoyi-modules/ruoyi-goods/src/main/java/com/ruoyi/goods/service/biz/XiaoeLiveService.java
New file
@@ -0,0 +1,334 @@
package com.ruoyi.goods.service.biz;
import cn.hutool.core.collection.CollUtil;
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.XiaoeLiveDetailVOV2;
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.Map;
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();
        Map<String, XiaoeLiveAppointment> appointmentMap = appointments.stream().collect(Collectors.toMap(XiaoeLiveAppointment::getLiveId, appointment -> appointment));
        // 处理直播数据
        List<XiaoeLiveVo> filteredRecords = livePageList.getRecords().stream()
                .filter(item -> !item.getAliveState().equals(2)) // 过滤直播状态为已结束的记录
                .peek(item -> {
                    // 查找对应的预约记录
                    XiaoeLiveAppointment appointment = appointmentMap.get(item.getId());
                    // 如果预约记录存在且直播状态为未开始,则设置状态为已预约
                    if (item.getAliveState().equals(0) && appointment != null) {
                        item.setAppointmentState(1); // 设置为已预约
                    }
                })
                .sorted(Comparator.comparing(XiaoeLiveVo::getAliveState).reversed()) // 按照直播状态排序
                .collect(Collectors.toList());
        //根据直播类型过滤平台和店铺直播
        if (Objects.nonNull(dto.getLiveType())) {
            List<XiaoeLiveRecord> liveRecordList = xiaoeLiveRecordService.lambdaQuery().eq(XiaoeLiveRecord::getType, dto.getLiveType()).list();
            if (CollUtil.isEmpty(liveRecordList)) {
                return new Page<>();
            }
            List<String> liveIdList = liveRecordList.stream().map(XiaoeLiveRecord::getLiveId).collect(Collectors.toList());
            filteredRecords = filteredRecords.stream().filter(item -> liveIdList.contains(item.getId())).collect(Collectors.toList());
        }
        livePageList.setRecords(filteredRecords);
        return livePageList;
    }
    /**
     * 直播详情
     * @param id
     * @return
     */
    public XiaoeLiveDetailVOV2 getLiveDetail(String id) {
        return xiaoeUtils.getLiveDetailV2(id);
    }
    /**
     * 预约直播
     * @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());
        xiaoeLiveAppointment.setTitle(liveDetail.getTitle());
        xiaoeLiveAppointmentService.save(xiaoeLiveAppointment);
        /// 若为C端用户向 redis 添加预约记录
        if (sysUser.getUserType().equals("03")) {
            // 获取当前时间
            Date nowTime = new Date();
            // 计算直播开始时间与当前时间的差值
            long startTimeDifference = aliveStartAt.getTime() - nowTime.getTime();
            startTimeDifference = Math.max(startTimeDifference, 3000L); // 确保差值至少为3秒
            // 获取延时任务
            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))
                // 按照直播状态排序
                .sorted(Comparator.comparing(XiaoeLiveVo::getAliveState).reversed()).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);
    }
    /**
     * 管理后台直播分页列表
     * @param dto
     * @return
     */
    public Page<XiaoeLiveVo> getMgtLivePage(XiaoeLiveQueryDto dto) {
        Page<XiaoeLiveVo> livePageList = xiaoeUtils.getLivePageList(dto);
        Long shopId = dto.getShopId();
        if (Objects.nonNull(shopId)) {
            List<XiaoeLiveRecord> xiaoeLiveRecords = xiaoeLiveRecordService.getListByShopId(shopId);
            if (CollUtil.isEmpty(xiaoeLiveRecords)) {
                return new Page<>();
            }
            List<String> liveIdList = xiaoeLiveRecords.stream().map(XiaoeLiveRecord::getLiveId).collect(Collectors.toList());
            List<XiaoeLiveVo> filteredList = livePageList.getRecords().stream().filter(item -> liveIdList.contains(item.getId())).collect(Collectors.toList());
            livePageList.setRecords(filteredList);
        }
        List<XiaoeLiveRecord> shopRecordList = xiaoeLiveRecordService.lambdaQuery().eq(XiaoeLiveRecord::getType, 2).list();
        Map<String, String> shopNameMap = shopRecordList.stream().collect(Collectors.toMap(XiaoeLiveRecord::getLiveId, XiaoeLiveRecord::getShopName));
        livePageList.setRecords(livePageList.getRecords().stream().peek(item -> {
            String name = shopNameMap.get(item.getId());
            if (StringUtils.isNotBlank(name)) {
                item.setShopName(name);
            }
        }).collect(Collectors.toList()));
        return livePageList;
    }
    /**
     * 删除直播
     * @param id
     */
    public void deleteLive(String id) {
        Boolean flag = xiaoeUtils.deleteLive(id);
        if (flag) {
            //删除直播添加记录
            xiaoeLiveRecordService.lambdaUpdate().eq(XiaoeLiveRecord::getLiveId, id).remove();
        }
    }
    /**
     * 员工端直播列表
     * @param dto
     * @return
     */
    public Page<XiaoeLiveVo> getStaffLivePage(XiaoeLiveQueryDto dto) {
        Page<XiaoeLiveVo> page = new Page<>();
        List<XiaoeLiveRecord> xiaoeLiveRecordList = xiaoeLiveRecordService.getMgtRecord();
        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;
    }
}
ruoyi-modules/ruoyi-goods/src/main/java/com/ruoyi/goods/service/impl/live/XiaoeLiveAppointmentServiceImpl.java
New file
@@ -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);
    }
}
ruoyi-modules/ruoyi-goods/src/main/java/com/ruoyi/goods/service/impl/live/XiaoeLiveRecordServiceImpl.java
New file
@@ -0,0 +1,70 @@
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);
    }
    /**
     * 平台直播哦添加记录
     * @return
     */
    @Override
    public List<XiaoeLiveRecord> getMgtRecord() {
        return lambdaQuery().eq(XiaoeLiveRecord::getType, 1).list();
    }
}
ruoyi-modules/ruoyi-goods/src/main/java/com/ruoyi/goods/service/live/IXiaoeLiveAppointmentService.java
New file
@@ -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);
}
ruoyi-modules/ruoyi-goods/src/main/java/com/ruoyi/goods/service/live/IXiaoeLiveRecordService.java
New file
@@ -0,0 +1,41 @@
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);
    /**
     * 添加直播创建记录
     * @param shopId
     * @param type
     * @param shopName
     * @param liveId
     */
    void add(Long shopId, Integer type, String shopName, String liveId);
    List<XiaoeLiveRecord> getMgtRecord();
}
ruoyi-modules/ruoyi-goods/src/main/java/com/ruoyi/goods/utils/WeChatSubscribeMessageSender.java
New file
@@ -0,0 +1,83 @@
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 STABLE_ACCESS_TOKEN_HOST = "https://api.weixin.qq.com/cgi-bin/stable_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("time5", createDataItem("直播时间", DateUtil.format(aliveStartAt, "MM-dd HH:mm:ss")));
        messageData.put("thing4", createDataItem("直播间名称", shopName));
        messageData.put("thing1", createDataItem("直播主题", title));
        params.put("data", messageData);
        params.put("miniprogram_state", "trial");
        params.put("lang", "zh_CN");
        try {
            log.info("发送消息参数:{}", JSONObject.toJSONString(params));
            String post = HttpUtil.post(url, JSONObject.toJSONString(params));
            log.info("发送消息返回结果:{}", post);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
    /**
     * 获取access_token
     * @return
     */
    private static String getAccessTokenByWX() {
        Map<String, Object> params = new HashMap<>();
        params.put("appid", WX_APPID);
        params.put("secret", WX_SECRET);
        params.put("grant_type", "client_credential");
        String token = HttpUtil.post(STABLE_ACCESS_TOKEN_HOST,JSONObject.toJSONString(params));
        log.info("token:{}", token);
        JSONObject jsonObject = JSONObject.parseObject(token);
        return jsonObject.getString("access_token");
    }
    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());
    }
}
ruoyi-modules/ruoyi-goods/src/main/java/com/ruoyi/goods/utils/XiaoeUtils.java
New file
@@ -0,0 +1,458 @@
package com.ruoyi.goods.utils;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUtil;
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.DateUtils;
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.XiaoeLiveDetailVOV2;
import com.ruoyi.goods.domain.vo.XiaoeLiveVo;
import com.ruoyi.goods.domain.vo.XiaoeLiveVoV2;
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;
import java.util.concurrent.TimeUnit;
/**
 * 小鹅通工具类
 * @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/1.0.0";
    private static final String LIVE_PAGE_LIST_V2 = "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_DETAIL_V2 = "https://api.xiaoe-tech.com/xe.alive.detail.get/2.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);
            log.info("获取access_token返回结果:{}", result);
            Map data = (Map) jsonObject.get("data");
            accessToken = (String)data.get("access_token");
            Integer expiresIn = (int) data.get("expires_in");
            redisService.setCacheObject("xiaoe:access_token", accessToken,Long.parseLong(expiresIn.toString()), TimeUnit.SECONDS);
        }
        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", 0);
        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)) {
            log.info("获取直播列表返回结果:{}", post);
            JSONObject jsonObject = JSONObject.parseObject(post);
            if (jsonObject.get("code").equals(0) ) {
                JSONObject data = jsonObject.getJSONObject("data");
                JSONArray list = data.getJSONArray("list");
                if (CollectionUtil.isNotEmpty(list)) {
                    List<XiaoeLiveVo> xiaoeLiveVos = JSONArray.parseArray(list.toJSONString(), XiaoeLiveVo.class);
                    xiaoeLiveVOPage.setRecords(xiaoeLiveVos);
                    xiaoeLiveVOPage.setCurrent(dto.getPage());
                    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;
    }
    /**
     * 获取直播列表2.0
     * @param dto
     * @return
     */
    public Page<XiaoeLiveVoV2> getLivePageListV2(XiaoeLiveQueryDto dto) {
        Map<String,Object> postParams = new HashMap<>();
        postParams.put("search_content", dto.getSearchContent());
        postParams.put("create_mode", dto.getCreateMode());
        postParams.put("state", 0);
        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());
        postParams.put("zb_start_at_min", DateUtil.format(DateUtils.getTodayStartTime(), DatePattern.NORM_DATETIME_PATTERN));
        String post = HttpUtil.post(LIVE_PAGE_LIST_V2, JSONObject.toJSONString(postParams));
        Page<XiaoeLiveVoV2> xiaoeLiveVOPage = new Page<>();
        if (StringUtils.isNotBlank(post)) {
            log.info("获取直播详情2.0返回结果:{}", post);
            JSONObject jsonObject = JSONObject.parseObject(post);
            if (jsonObject.get("code").equals(0)) {
                JSONObject data = jsonObject.getJSONObject("data");
                JSONArray list = data.getJSONArray("live_list");
                if (!list.isEmpty()){
                    List<XiaoeLiveVoV2> xiaoeLiveVos = JSONArray.parseArray(list.toJSONString(), XiaoeLiveVoV2.class);
                    xiaoeLiveVOPage.setRecords(xiaoeLiveVos);
                    xiaoeLiveVOPage.setCurrent(dto.getPage());
                    xiaoeLiveVOPage.setTotal(data.getLong("total_count"));
                }
            }
        }
        return xiaoeLiveVOPage;
    }
    /**
     *获取直播详情
     * @param id 直播ID
     * @return
     */
    public XiaoeLiveDetailVOV2 getLiveDetailV2(String id) {
        Map<String, Object> postParams = new HashMap<>();
        postParams.put("id", id);
        postParams.put("access_token", getAccessToken());
        String post = HttpUtil.post(LIVE_DETAIL_V2, JSONObject.toJSONString(postParams));
        XiaoeLiveDetailVOV2 vo = null;
        if (StringUtils.isNotBlank(post)) {
            JSONObject jsonObject = JSONObject.parseObject(post);
            if (jsonObject.get("code").equals(0)) {
                JSONObject data = jsonObject.getJSONObject("data");
                vo = JSONObject.parseObject(data.toJSONString(), XiaoeLiveDetailVOV2.class);
            }
        }
        return vo;
    }
    /**
     * 创建直播
     * @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);
        //讲师信息
        List<Map<String, Object>> roleList = new ArrayList<>();
        Map<String, Object> roleInfoMap = new HashMap<>();
        roleInfoMap.put("role_name", "讲师");
        roleInfoMap.put("user_id", dto.getUserId());
        roleList.add(roleInfoMap);
        requestParams.put("role_info", roleList);
        //商品信息
        Map<String, Object> goodsInfoMap = new HashMap<>();
        goodsInfoMap.put("sale_type", 1);//售卖类型: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());
        log.info("创建直播参数:{}" ,JSONObject.toJSONString(requestParams));
        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);
        //讲师信息
        List<Map<String, Object>> roleList = new ArrayList<>();
        Map<String, Object> roleInfoMap = new HashMap<>();
        roleInfoMap.put("role_name", "讲师");
        roleInfoMap.put("user_id", dto.getUserId());
        roleList.add(roleInfoMap);
        requestParams.put("role_info", roleList);
        //商品信息
        Map<String, Object> goodsInfoMap = new HashMap<>();
        goodsInfoMap.put("sale_type", 1);//售卖类型: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());
        log.info("编辑直播参数:{}" ,JSONObject.toJSONString(requestParams));
        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) {
        Map<String, Object> requestParams = new HashMap<>();
        requestParams.put("search_content", dto.getSearchContent());
        requestParams.put("page_index", dto.getPageNo());
        requestParams.put("page_size", dto.getPageSize());
        requestParams.put("created_source", dto.getCreatedSource());
        requestParams.put("tags", dto.getTags());
        if (dto.getSortBy().equals(1)) {
            requestParams.put("order_by", "modify");//根据创建时间排序
            requestParams.put("order_type",1);
        }
        requestParams.put("sale_status", 1);
        requestParams.put("access_token", getAccessToken());
        String post = HttpUtil.post(COURSE_PAGE_LIST, JSONObject.toJSONString(requestParams));
        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> courseChapterVOList = new ArrayList<>();
        if (StringUtils.isNotBlank(post)) {
            log.info("查询课程目录小节返回结果:{}" ,post);
            JSONObject jsonObject = JSONObject.parseObject(post);
            if (jsonObject.get("code").equals(0)) {
                JSONObject data = jsonObject.getJSONObject("data");
                courseChapterVOList = JSONArray.parseArray(data.getString("list"), XiaoeCourseChapterVO.class);
            }
        }
        return courseChapterVOList;
    }
    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);
    }
}
ruoyi-modules/ruoyi-goods/src/main/resources/mapper/live/XiaoeLiveAppointmentMapper.xml
New file
@@ -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>
ruoyi-modules/ruoyi-goods/src/main/resources/mapper/live/XiaoeLiveRecordMapper.xml
New file
@@ -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>
ruoyi-modules/ruoyi-member/pom.xml
@@ -132,6 +132,17 @@
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.mapstruct</groupId>
            <artifactId>mapstruct-jdk8</artifactId>
            <version>1.3.0.Final</version>
        </dependency>
        <dependency>
            <groupId>org.mapstruct</groupId>
            <artifactId>mapstruct-processor</artifactId>
            <version>1.3.0.Final</version>
        </dependency>
    </dependencies>
    <build>
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;
}
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;
ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/controller/GeneratorMybatisPlus.java
@@ -22,7 +22,7 @@
        // 全局配置
        GlobalConfig globalConfig = new GlobalConfig();
        //生成文件的输出目录
        String path="D:/hrt";
        String path="F:\\DeskTop";
        globalConfig.setOutputDir(path);
        // Author设置作者
        globalConfig.setAuthor("mybatis-plus");
@@ -35,7 +35,7 @@
        globalConfig.setXmlName("%sMapper");
        globalConfig.setServiceName("%sService");
        globalConfig.setServiceImplName("%sServiceImpl");
        globalConfig.setAuthor("jqs");
        globalConfig.setAuthor("mitao");
        // globalConfig.setEntityName("%s");
        globalConfig.setControllerName("%sController");
        autoGenerator.setGlobalConfig(globalConfig);
@@ -44,16 +44,16 @@
        DataSourceConfig dataSourceConfig = new DataSourceConfig();
        dataSourceConfig.setDbType(DbType.MYSQL);
        dataSourceConfig.setTypeConvert(new MySqlTypeConvert());
        dataSourceConfig.setUrl("jdbc:mysql://47.109.78.184:10010/hrt_sys?tinyInt1isBit=false");
        dataSourceConfig.setUrl("jdbc:mysql://127.0.0.1:3306/hrt_goods?tinyInt1isBit=false");
        dataSourceConfig.setDriverName("com.mysql.cj.jdbc.Driver");
        dataSourceConfig.setUsername("root");
        dataSourceConfig.setPassword("hrt123456");
        dataSourceConfig.setPassword("123456");
        autoGenerator.setDataSource(dataSourceConfig);
        // 包名配置
        PackageConfig packageConfig = new PackageConfig();
        // 父包和子包名分开处理
        packageConfig.setParent("com.ruoyi.system");
        packageConfig.setParent("com.ruoyi.goods");
        packageConfig.setController("app");
        packageConfig.setEntity("domain.pojo");
        packageConfig.setMapper("mapper");
@@ -74,7 +74,7 @@
        strategy.setControllerMappingHyphenStyle(true);
        //表和前缀处理
        String[] table = {
                "t_file_record"
                "t_xiaoe_live_record"
        };
        strategy.setInclude(table);
        String[] tablePre = new String[]{"t_"};
ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/controller/sys/SysUserController.java
@@ -1,6 +1,5 @@
package com.ruoyi.system.controller.sys;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.ruoyi.common.core.domain.R;
import com.ruoyi.common.core.exception.ServiceException;
import com.ruoyi.common.core.utils.StringUtils;
@@ -18,19 +17,31 @@
import com.ruoyi.system.api.domain.dto.MgtFrozenMemberDto;
import com.ruoyi.system.api.domain.poji.sys.SysDept;
import com.ruoyi.system.api.domain.poji.sys.SysRole;
import com.ruoyi.system.api.domain.poji.sys.SysStaff;
import com.ruoyi.system.api.domain.poji.sys.SysUser;
import com.ruoyi.system.api.model.LoginUser;
import com.ruoyi.system.api.model.QwH5LoginVo;
import com.ruoyi.system.api.model.QwUserDetailDto;
import com.ruoyi.system.domain.dto.UserMenuEditDto;
import com.ruoyi.system.api.domain.poji.sys.SysStaff;
import com.ruoyi.system.service.staff.SysStaffService;
import com.ruoyi.system.service.sys.*;
import com.ruoyi.system.service.sys.ISysConfigService;
import com.ruoyi.system.service.sys.ISysDeptService;
import com.ruoyi.system.service.sys.ISysPermissionService;
import com.ruoyi.system.service.sys.ISysPostService;
import com.ruoyi.system.service.sys.ISysRoleService;
import com.ruoyi.system.service.sys.ISysUserService;
import io.swagger.annotations.ApiOperation;
import org.apache.commons.lang3.ArrayUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.bind.annotation.DeleteMapping;
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 org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletResponse;
@@ -459,6 +470,7 @@
     * @return  R<QwH5LoginVo>
     */
    @PostMapping("/qwH5StaffLogin")
    @ApiOperation(value = "员工端登录")
    public R<QwH5LoginVo> qwH5StaffLogin(@RequestBody QwUserDetailDto qwUserDetail)
    {
        String mobile = qwUserDetail.getMobile();
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 {