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_guest_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-modules/ruoyi-goods/src/main/java/com/ruoyi/goods/controller/business/MerCourseController.java
New file @@ -0,0 +1,57 @@ package com.ruoyi.goods.controller.business; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.ruoyi.common.core.domain.R; import com.ruoyi.goods.domain.dto.XiaoeCourseQueryDto; import com.ruoyi.goods.domain.vo.XiaoeCourseChapterVO; import com.ruoyi.goods.domain.vo.XiaoeCourseVO; import com.ruoyi.goods.service.biz.XiaoeCourseService; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiParam; import lombok.RequiredArgsConstructor; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import javax.validation.Valid; import java.util.List; /** * @author mitao * @date 2025/1/10 */ @Api(tags = {"商户端海恒课程相关接口"}) @Validated @RestController @RequestMapping("/mer/course") @RequiredArgsConstructor public class MerCourseController { private final XiaoeCourseService xiaoeCourseService; /** * 课程列表 * @param dto * @return */ @ApiOperation("课程列表") @PostMapping("/page") public R<Page<XiaoeCourseVO>> page(@Valid @RequestBody XiaoeCourseQueryDto dto) { return R.ok(xiaoeCourseService.getCoursePageList(dto)); } /** * 课程章节详情 * @param id * @return */ @ApiOperation("课程章节详情") @GetMapping("/detail/{id}") public R<List<XiaoeCourseChapterVO>> detail(@ApiParam(name = "id",value = "课程id",required = true)@PathVariable("id") String id) { return R.ok(xiaoeCourseService.getCourseDetail(id)); } } ruoyi-modules/ruoyi-goods/src/main/java/com/ruoyi/goods/controller/business/MerLiveController.java
New file @@ -0,0 +1,101 @@ package com.ruoyi.goods.controller.business; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.ruoyi.common.core.domain.R; import com.ruoyi.goods.domain.dto.XiaoeLiveDto; import com.ruoyi.goods.domain.dto.XiaoeLiveQueryDto; import com.ruoyi.goods.domain.vo.XiaoeLiveVo; import com.ruoyi.goods.service.biz.XiaoeLiveService; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiParam; import lombok.RequiredArgsConstructor; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import javax.validation.Valid; import java.util.List; /** * @author mitao * @date 2025/1/9 */ @Validated @RestController @RequiredArgsConstructor @Api(tags = "商户端直播相关接口") @RequestMapping("/mer/live") public class MerLiveController { private final XiaoeLiveService xiaoeLiveService; /** * 直播首页分页列表 * @param dto * @return */ @ApiOperation("直播首页分页列表") @PostMapping("/page") public R<Page<XiaoeLiveVo>> page(@Valid @RequestBody XiaoeLiveQueryDto dto) { return R.ok(xiaoeLiveService.getLivePage(dto)); } /** * 预约直播 * @param id * @return */ @ApiOperation("预约") @GetMapping("/appointment/{id}") public R<Boolean> appointment(@ApiParam(name = "id",value = "直播id", required = true) @PathVariable("id") String id) { return R.ok(xiaoeLiveService.appointment(id)); } /** * 我的直播 * @param dto * @return */ @ApiOperation("我的直播") @PostMapping("/mine") public R<Page<XiaoeLiveVo>> minePage(@Valid @RequestBody XiaoeLiveQueryDto dto) { return R.ok(xiaoeLiveService.getMineLivePage(dto)); } /** * 获取当前店铺讲师id列表/创建直播前校验讲师信息 * @return */ @ApiOperation("获取当前店铺讲师id列表/创建直播前校验讲师信息") @GetMapping("/xiaoe/ids") public R<List<String>> getShopXiaoeUserIdList() { return R.ok(xiaoeLiveService.getShopXiaoeUserIdList()); } /** * 创建直播 * @param dto * @return */ @ApiOperation("创建直播") @PostMapping("/create") public R<?> create(@Valid @RequestBody XiaoeLiveDto dto) { xiaoeLiveService.create(dto,2); return R.ok(); } /** * 编辑直播 * @param dto * @return */ @ApiOperation("编辑直播") @PutMapping public R<Boolean> edit(@Valid @RequestBody XiaoeLiveDto dto) { return R.ok(xiaoeLiveService.edit(dto)); } } 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/miniapp/AppCourseController.java
New file @@ -0,0 +1,57 @@ package com.ruoyi.goods.controller.miniapp; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.ruoyi.common.core.domain.R; import com.ruoyi.goods.domain.dto.XiaoeCourseQueryDto; import com.ruoyi.goods.domain.vo.XiaoeCourseChapterVO; import com.ruoyi.goods.domain.vo.XiaoeCourseVO; import com.ruoyi.goods.service.biz.XiaoeCourseService; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiParam; import lombok.RequiredArgsConstructor; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import javax.validation.Valid; import java.util.List; /** * @author mitao * @date 2025/1/10 */ @Api(tags = {"小程序海恒课程相关接口"}) @Validated @RestController @RequestMapping("/app/course") @RequiredArgsConstructor public class AppCourseController { private final XiaoeCourseService xiaoeCourseService; /** * 课程列表 * @param dto * @return */ @ApiOperation("课程列表") @PostMapping("/page") public R<Page<XiaoeCourseVO>> page(@Valid @RequestBody XiaoeCourseQueryDto dto) { return R.ok(xiaoeCourseService.getCoursePageList(dto)); } /** * 课程章节详情 * @param id * @return */ @ApiOperation("课程章节详情") @GetMapping("/detail/{id}") public R<List<XiaoeCourseChapterVO>> detail(@ApiParam(name = "id",value = "课程id",required = true)@PathVariable("id") String id) { return R.ok(xiaoeCourseService.getCourseDetail(id)); } } 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/domain/dto/XiaoeCourseQueryDto.java
New file @@ -0,0 +1,45 @@ package com.ruoyi.goods.domain.dto; import com.alibaba.fastjson2.annotation.JSONField; import com.fasterxml.jackson.annotation.JsonIgnore; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.Data; import javax.validation.constraints.NotNull; import java.util.List; /** * @author mitao * @date 2025/1/9 */ @Data @ApiModel("小鹅通课程列表查询参数") public class XiaoeCourseQueryDto { @ApiModelProperty(value = "课程名字模糊搜索") @JSONField(name = "search_content") private String searchContent; @ApiModelProperty(value = "课程创建来源") @JSONField(name = "created_source") private Integer createdSource; @ApiModelProperty("课程分组id数组") @JSONField(name = "tags") private List<Integer> tags; @ApiModelProperty(value = "当前页") @JSONField(name = "page_index") private Integer pageNo; @ApiModelProperty("每页条数") @JSONField(name = "page_size") private Integer pageSize; @ApiModelProperty("排序类型 1:创建时间倒序 2:开始时间升序") @NotNull(message = "排序类型不能为空") @JsonIgnore private Integer sortBy; } 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 javax.validation.constraints.Max; import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotNull; @ApiModel(description = "资源信息") @Data public class XiaoeLiveDto { @ApiModelProperty(value = "直播id",notes = "添加不传,编辑必传") @JSONField(name = "id") private String id; @ApiModelProperty(value = "直播标题", required = true, notes = "字符长度必须小于45") @NotBlank(message = "直播标题不能为空") @JSONField(name = "title") private String title; @ApiModelProperty(value = "直播简介", notes = "字符长度必须小于256,默认为空") @NotBlank(message = "直播简介不能为空") @Max(value = 256, message = "直播简介长度不能超过256") @JSONField(name = "summary") private String summary; @ApiModelProperty(value = "预设直播开始时间", required = true, notes = "距离当前时间不能超过五年", example = "2023-05-31 18:00:00") @NotBlank(message = "预设直播开始时间不能为空") @JSONField(name = "zb_start_at") private String zbStartAt; @ApiModelProperty(value = "预设直播时长,单位:秒", required = true, notes = "距离预设直播开始时间不能超过十年") @JSONField(name = "zb_stop_at") private Integer zbStopAt; @ApiModelProperty(value = "直播类型", notes = "0-语音,1-录播直播,2-推流直播,默认为0-语音直播") @NotNull(message = "直播类型不能为空") @JSONField(name = "alive_type") private Integer aliveType; @ApiModelProperty(value = "直播模式:0-横屏直播,1-竖屏直播(默认为0-横屏直播)", example = "0") @NotNull(message = "直播模式不能为空") @JSONField(name = "alive_mode") private Integer aliveMode; @ApiModelProperty(value = "讲师用户id") @NotBlank(message = "讲师用户id不能为空") @JSONField(name = "user_id") private String userId; @ApiModelProperty(value = "支付类型:1-免费,3-加密,仅当goods_info.sale_type=2时才可用", example = "1") @NotNull(message = "支付类型不能为空") @JSONField(name = "payment_type") private Integer paymentType; @ApiModelProperty(value = "密码(paymentType为3时必填,否则该字段无效)") @JSONField(name = "resource_password") private String resourcePassword; @ApiModelProperty(value = "直播详情", notes = "仅允许纯文本,不得超过5000个字符,默认为空") @NotBlank(message = "直播详情不能为空") @Max(value = 5000, message = "直播详情长度不能超过5000") @JSONField(name = "descrb") private String descrb; } ruoyi-modules/ruoyi-goods/src/main/java/com/ruoyi/goods/domain/dto/XiaoeLiveQueryDto.java
New file @@ -0,0 +1,35 @@ package com.ruoyi.goods.domain.dto; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.Data; /** * @author mitao * @date 2025/1/8 */ @Data @ApiModel("直播分页查询参数") public class XiaoeLiveQueryDto { @ApiModelProperty(value = "直播名称关键字") private String searchContent; @ApiModelProperty(value = "直播课程类型 -1 全部;0 店铺课程; 1 转播课程;默认-1") private Integer createMode; @ApiModelProperty(value = "直播商品状态 -1 全部; 0 已上架; 1 已下架;2 待上架;默认-1") private Integer state; @ApiModelProperty(value = "直播模式:-1 全部;10 横屏直播;11竖屏直播;12语音直播;13录播直播;默认-1") private Integer searchAliveType; @ApiModelProperty(value = "直播状态:-1全部;0未开始;1直播中;2已结束;默认-1") private Integer alivePlayState; @ApiModelProperty(value = "页码,表示第几页,从1开始;默认1") private Integer page; @ApiModelProperty(value = "每页条数,最大50条;默认10") private Integer pageSize; } 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,45 @@ package com.ruoyi.goods.domain.vo; import com.alibaba.fastjson2.annotation.JSONField; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.Data; import java.util.Date; /** * @author mitao * @date 2025/1/9 */ @Data @ApiModel("小鹅通课程视图对象") public class XiaoeCourseVO { @ApiModelProperty("课程id") @JSONField(name = "resource_id") private String resourceId; @ApiModelProperty("课程名称") @JSONField(name = "title") private String title; @ApiModelProperty("课程封面") @JSONField(name = "img_url") private String imgUrl; @ApiModelProperty("用户数") @JSONField(name = "user_count") private Integer userCount; @ApiModelProperty("内容数") @JSONField(name = "resource_cnt") private Integer resourceCnt; @ApiModelProperty("开课时间") @JSONField(name = "curriculum_time") private Date curriculumTime; @ApiModelProperty("开课结束时间") @JSONField(name = "curriculum_end_time") private Date curriculumEndTime; } 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,109 @@ package com.ruoyi.goods.domain.vo; import com.alibaba.fastjson2.annotation.JSONField; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.Data; import java.util.List; /** * @author mitao * @date 2025/1/8 */ @Data @ApiModel("小鹅通直播视图对象") public class XiaoeLiveVo { @ApiModelProperty(value = "直播ID") @JSONField(name = "id") private String id; // 直播ID @ApiModelProperty(value = "直播手动结束时间") @JSONField(name = "alive_manual_stop_at") private String aliveManualStopAt; // 直播手动结束时间 @ApiModelProperty(value = "直播结束时间") @JSONField(name = "alive_stop_at") private String aliveStopAt; // 直播结束时间 @ApiModelProperty(value = "直播名称") @JSONField(name = "title") private String title; // 直播名称 @ApiModelProperty(value = "直播观看人次,每5分钟同步一次数据") @JSONField(name = "view_count") private Integer viewCount; // 直播观看人次 @ApiModelProperty(value = "直播类型:10 横屏直播;11竖屏直播;12语音直播;13录播直播") @JSONField(name = "alive_type") private Integer aliveType; // 直播类型 @ApiModelProperty(value = "订阅量") @JSONField(name = "purchase_count") private Integer purchaseCount; // 订阅量 @ApiModelProperty(value = "打赏金额(元)") @JSONField(name = "reward_sum") private String rewardSum; // 打赏金额(元) @ApiModelProperty(value = "直播商品状态:-1 全部; 0 已上架; 1 已下架;2 待上架") @JSONField(name = "recycle_bin_state") private Integer recycleBinState; // 直播商品状态 @ApiModelProperty(value = "直播开始时间") @JSONField(name = "alive_start_at") private String aliveStartAt; // 直播开始时间 @ApiModelProperty(value = "直播状态:-1全部;0未开始;1直播中;2已结束") @JSONField(name = "alive_state") private Integer aliveState; // 直播状态 @ApiModelProperty(value = "直播显隐状态:0-显示,1-隐藏") @JSONField(name = "resource_state") private Integer resourceState; // 直播显隐状态 @ApiModelProperty(value = "店铺直播详情页地址") @JSONField(name = "page_url") private String pageUrl; // 店铺直播详情页地址 @ApiModelProperty(value = "直播课程类型:-1 全部;0 店铺课程; 1 转播课程") @JSONField(name = "create_mode") private Integer createMode; // 直播课程类型 @ApiModelProperty(value = "封面地址") @JSONField(name = "img_url") private String imgUrl; // 封面地址 @ApiModelProperty(value = "直播宣传图地址") @JSONField(name = "alive_img_url") private String aliveImgUrl; // 直播宣传图地址 @ApiModelProperty(value = "讲师列表") @JSONField(name = "guest_list") private List<XiaoeLiveTeacherVo> guestList; @ApiModelProperty("评论数") @JSONField(name = "comment_count") private Integer commentCount; @ApiModelProperty("评论用户数") @JSONField(name = "comment_user_count") private Integer commentUserCount; @ApiModelProperty("打赏次数") @JSONField(name = "reward_count") private Integer rewardCount; @ApiModelProperty("打赏人数") @JSONField(name = "reward_user_count") private Integer rewardUserCount; @ApiModelProperty("直播简介") @JSONField(name = "summary") private String summary; @ApiModelProperty("预约状态 1:已预约 0:未预约") private Integer appointmentState=0; } 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,39 @@ package com.ruoyi.goods.service.biz; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.ruoyi.goods.domain.dto.XiaoeCourseQueryDto; import com.ruoyi.goods.domain.vo.XiaoeCourseChapterVO; import com.ruoyi.goods.domain.vo.XiaoeCourseVO; import com.ruoyi.goods.utils.XiaoeUtils; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import java.util.List; /** * @author mitao * @date 2025/1/10 */ @Service @RequiredArgsConstructor public class XiaoeCourseService { private final XiaoeUtils xiaoeUtils; /** * 课程列表 * @param dto * @return */ public Page<XiaoeCourseVO> getCoursePageList(XiaoeCourseQueryDto dto) { return xiaoeUtils.getCoursePageList(dto); } /** * 获取课程章节详情 * @param id * @return */ public List<XiaoeCourseChapterVO> getCourseDetail(String id) { return xiaoeUtils.getCourseChapterDetail(id); } } ruoyi-modules/ruoyi-goods/src/main/java/com/ruoyi/goods/service/biz/XiaoeLiveService.java
New file @@ -0,0 +1,249 @@ package com.ruoyi.goods.service.biz; import cn.hutool.core.collection.CollectionUtil; import cn.hutool.core.date.DatePattern; import cn.hutool.core.date.DateUtil; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.ruoyi.common.core.exception.ServiceException; import com.ruoyi.common.core.utils.DateUtils; import com.ruoyi.common.core.utils.StringUtils; import com.ruoyi.common.redis.service.RedisService; import com.ruoyi.common.security.utils.SecurityUtils; import com.ruoyi.goods.domain.dto.XiaoeLiveDto; import com.ruoyi.goods.domain.dto.XiaoeLiveQueryDto; import com.ruoyi.goods.domain.pojo.live.XiaoeLiveAppointment; import com.ruoyi.goods.domain.pojo.live.XiaoeLiveRecord; import com.ruoyi.goods.domain.vo.XiaoeLiveVo; import com.ruoyi.goods.service.live.IXiaoeLiveAppointmentService; import com.ruoyi.goods.service.live.IXiaoeLiveRecordService; import com.ruoyi.goods.utils.XiaoeUtils; import com.ruoyi.system.api.constant.DelayTaskEnum; import com.ruoyi.system.api.domain.poji.config.DelayTask; import com.ruoyi.system.api.domain.poji.shop.Shop; import com.ruoyi.system.api.domain.poji.sys.SysUser; import com.ruoyi.system.api.domain.vo.ShopRelUserVo; import com.ruoyi.system.api.service.RemoteConfigService; import com.ruoyi.system.api.service.RemoteShopService; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import java.util.Arrays; import java.util.Comparator; import java.util.Date; import java.util.List; import java.util.Objects; import java.util.Set; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; /** * @author mitao * @date 2025/1/9 */ @Service @RequiredArgsConstructor public class XiaoeLiveService { private final IXiaoeLiveRecordService xiaoeLiveRecordService; private final XiaoeUtils xiaoeUtils; private final IXiaoeLiveAppointmentService xiaoeLiveAppointmentService; private final RedisService redisService; private final RemoteConfigService remoteConfigService; private final RemoteShopService remoteShopService; /** * 直播首页分页列表 * @param dto * @return */ public Page<XiaoeLiveVo> getLivePage(XiaoeLiveQueryDto dto) { //当前登录用户id Long userId = SecurityUtils.getUserId(); Page<XiaoeLiveVo> livePageList = xiaoeUtils.getLivePageList(dto); if (CollectionUtil.isEmpty(livePageList.getRecords())) { return new Page<>(); } //已预约的记录 List<XiaoeLiveAppointment> appointments = xiaoeLiveAppointmentService.lambdaQuery() .gt(XiaoeLiveAppointment::getAliveStartAt, DateUtils.getNowDate()).eq(XiaoeLiveAppointment::getUserId, userId).list(); //处理获取到的直播数据 List<XiaoeLiveVo> filteredRecords = livePageList.getRecords().stream() .filter(item -> !item.getAliveState().equals(2)) .flatMap(item -> appointments.stream().map(appointment -> { //直播状态为未开始 且 预约记录存在 设置状态为已预约 if (item.getAliveState().equals(0) && appointment.getLiveId().equals(item.getId())) { item.setAppointmentState(1); } return item; })).sorted(Comparator.comparing(XiaoeLiveVo::getAliveState).reversed()) .collect(Collectors.toList()); livePageList.setRecords(filteredRecords); return livePageList; } /** * 预约直播 * @param id * @return */ public Boolean appointment(String id) { //当前登录用户 SysUser sysUser = SecurityUtils.getSysUser(); Long userId = sysUser.getUserId(); //查询预约记录 Integer count = xiaoeLiveAppointmentService.lambdaQuery().eq(XiaoeLiveAppointment::getLiveId, id).gt(XiaoeLiveAppointment::getAliveStartAt, DateUtils.getNowDate()).eq(XiaoeLiveAppointment::getUserId, userId).count(); if (count > 0) { throw new ServiceException("您已预约过该直播,请勿重复预约"); } XiaoeLiveVo liveDetail = xiaoeUtils.getLiveDetail(id); if (Objects.isNull(liveDetail)) { throw new ServiceException("直播不存在"); } XiaoeLiveAppointment xiaoeLiveAppointment = new XiaoeLiveAppointment(); xiaoeLiveAppointment.setLiveId(id); xiaoeLiveAppointment.setUserId(userId); Date aliveStartAt = DateUtil.parse(liveDetail.getAliveStartAt(), DatePattern.NORM_DATETIME_PATTERN); xiaoeLiveAppointment.setAliveStartAt(aliveStartAt); xiaoeLiveAppointment.setCreateTime(DateUtils.getNowDate()); xiaoeLiveAppointmentService.save(xiaoeLiveAppointment); /// 若为C端用户向 redis 添加预约记录 if (sysUser.getUserType().equals("03")) { // 如果活动在1小时内生成自动开始任务的延时任务 Date checkTime = DateUtils.addMinutes(new Date(), 61); // 获取当前时间 Date nowTime = new Date(); // 计算直播开始时间与当前时间的差值 long startTimeDifference = aliveStartAt.getTime() - nowTime.getTime(); startTimeDifference = Math.max(startTimeDifference, 3000L); // 确保差值至少为3秒 // 根据条件判断是否创建延时任务 if (checkTime.compareTo(aliveStartAt) > 0 || nowTime.compareTo(aliveStartAt) > 0) { // 获取延时任务 DelayTask startDelayTask = remoteConfigService.getDelayTask(DelayTaskEnum.LIVE_APPOINTMENT_TASK.getCode() + "-" + xiaoeLiveAppointment.getId()).getData(); // 创建或者更新延时任务 if (startDelayTask == null || !startDelayTask.getEndTime().equals(aliveStartAt)) { // 删除旧的延时任务及缓存 if (startDelayTask != null) { remoteConfigService.deleteDelayTask(DelayTaskEnum.LIVE_APPOINTMENT_TASK.getCode() + "-" + xiaoeLiveAppointment.getId()); redisService.deleteObject(DelayTaskEnum.LIVE_APPOINTMENT_TASK.getCode() + "-" + xiaoeLiveAppointment.getId()); } // 设置新的延时任务 redisService.setCacheObject(DelayTaskEnum.LIVE_APPOINTMENT_TASK.getCode() + "-" + xiaoeLiveAppointment.getId(), aliveStartAt, startTimeDifference, TimeUnit.MILLISECONDS); startDelayTask = new DelayTask(); startDelayTask.setDelFlag(0); startDelayTask.setCreateTime(new Date()); startDelayTask.setEndTime(aliveStartAt); startDelayTask.setRedisKey(DelayTaskEnum.LIVE_APPOINTMENT_TASK.getCode() + "-" + xiaoeLiveAppointment.getId()); // 添加新的延时任务 remoteConfigService.addDelayTask(startDelayTask); } } } return true; } /** * 我的直播 * @param dto * @return */ public Page<XiaoeLiveVo> getMineLivePage(XiaoeLiveQueryDto dto) { Long userId = SecurityUtils.getUserId(); Page<XiaoeLiveVo> page = new Page<>(); ShopRelUserVo shopRelUserVo = remoteShopService.getShopByUserId(userId).getData(); if (Objects.isNull(shopRelUserVo)) { return page; } List<XiaoeLiveRecord> xiaoeLiveRecordList = xiaoeLiveRecordService.getListByShopId(shopRelUserVo.getShopId()); if (CollectionUtil.isEmpty(xiaoeLiveRecordList)) { return page; } Page<XiaoeLiveVo> livePageList = xiaoeUtils.getLivePageList(dto); if (CollectionUtil.isEmpty(livePageList.getRecords())) { return page; } //当前商家创建的直播 Set<String> liveIdSet = xiaoeLiveRecordList.stream().map(XiaoeLiveRecord::getLiveId).collect(Collectors.toSet()); //过滤数据 List<XiaoeLiveVo> filteredRecords = livePageList.getRecords().stream().filter(item -> liveIdSet.contains(item.getId()) && !item.getAliveState().equals(2)).collect(Collectors.toList()); livePageList.setRecords(filteredRecords); return livePageList; } /** * 获取当前店铺讲师id列表 * @return */ public List<String> getShopXiaoeUserIdList() { Long userId = SecurityUtils.getUserId(); //获取用户所属店铺 ShopRelUserVo shopRelUserVo = remoteShopService.getShopByUserId(userId).getData(); if (Objects.isNull(shopRelUserVo)) { throw new ServiceException("当前用户未关联店铺,请先关联店铺"); } //查询店铺信息 Shop shop = remoteShopService.getShop(shopRelUserVo.getShopId()).getData(); if (Objects.isNull(shop)) { throw new ServiceException("店铺不存在"); } if (StringUtils.isBlank(shop.getXiaoeUserId())) { throw new ServiceException("创建失败,请联系平台添加讲师。"); } return Arrays.asList(shop.getXiaoeUserId().split(",")); } /** * 创建直播 * @param dto * @param type 创建类型 1:平台 2:经销商 * @return */ public void create(XiaoeLiveDto dto,Integer type) { if (dto.getPaymentType().equals(3) && StringUtils.isBlank(dto.getResourcePassword())){ throw new ServiceException("密码不能为空"); } if (type.equals(2)) { //获取当前登录用户 Long userId = SecurityUtils.getUserId(); //获取用户所属店铺 ShopRelUserVo shopRelUserVo = remoteShopService.getShopByUserId(userId).getData(); if (Objects.isNull(shopRelUserVo)) { throw new ServiceException("当前用户未关联店铺,请先关联店铺"); } //查询店铺信息 Shop shop = remoteShopService.getShop(shopRelUserVo.getShopId()).getData(); if (Objects.isNull(shop)) { throw new ServiceException("店铺不存在"); } String liveId = xiaoeUtils.addLive(dto); if (StringUtils.isBlank(liveId)) { throw new ServiceException("创建直播失败,请联系平台管理员"); } Long shopId = shop.getShopId(); String shopName = shop.getShopName(); //添加直播创建记录 xiaoeLiveRecordService.add(shopId, type, shopName, liveId); } else { String liveId = xiaoeUtils.addLive(dto); if (StringUtils.isBlank(liveId)) { throw new ServiceException("创建直播失败,请联系平台管理员"); } //添加直播创建记录 xiaoeLiveRecordService.add(null, type, "鸿瑞堂", liveId); } } /** * 编辑直播 * @param dto */ public Boolean edit(XiaoeLiveDto dto) { if (StringUtils.isBlank(dto.getId())) { throw new ServiceException("直播id不能为空"); } return xiaoeUtils.editLive(dto); } } 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,61 @@ package com.ruoyi.goods.service.impl.live; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.ruoyi.common.core.utils.DateUtils; import com.ruoyi.goods.domain.pojo.live.XiaoeLiveRecord; import com.ruoyi.goods.mapper.live.XiaoeLiveRecordMapper; import com.ruoyi.goods.service.live.IXiaoeLiveRecordService; import org.springframework.stereotype.Service; import java.util.List; /** * <p> * 小鹅通直播添加记录表 服务实现类 * </p> * * @author mitao * @since 2025-01-09 */ @Service public class XiaoeLiveRecordServiceImpl extends ServiceImpl<XiaoeLiveRecordMapper, XiaoeLiveRecord> implements IXiaoeLiveRecordService { /** * 根据直播id查询记录 * * @param liveId * @return */ @Override public XiaoeLiveRecord getByLiveId(String liveId) { return lambdaQuery().eq(XiaoeLiveRecord::getLiveId, liveId).one(); } /** * 根据店铺id查询记录 * * @param shopId * @return */ @Override public List<XiaoeLiveRecord> getListByShopId(Long shopId) { return lambdaQuery().eq(XiaoeLiveRecord::getType, 2).eq(XiaoeLiveRecord::getShopId, shopId).list(); } /** * 添加直播创建记录 * @param shopId * @param type * @param shopName * @param liveId */ @Override public void add(Long shopId, Integer type, String shopName, String liveId) { XiaoeLiveRecord xiaoeLiveRecord = new XiaoeLiveRecord(); xiaoeLiveRecord.setShopId(shopId); xiaoeLiveRecord.setType(type); xiaoeLiveRecord.setShopName(shopName); xiaoeLiveRecord.setLiveId(liveId); xiaoeLiveRecord.setCreateTime(DateUtils.getNowDate()); save(xiaoeLiveRecord); } } 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,33 @@ package com.ruoyi.goods.service.live; import com.baomidou.mybatisplus.extension.service.IService; import com.ruoyi.goods.domain.pojo.live.XiaoeLiveRecord; import java.util.List; /** * <p> * 小鹅通直播添加记录表 服务类 * </p> * * @author mitao * @since 2025-01-09 */ public interface IXiaoeLiveRecordService extends IService<XiaoeLiveRecord> { /** * 根据直播id查询记录 * @param liveId * @return */ XiaoeLiveRecord getByLiveId(String liveId); /** * 根据店铺id查询记录 * @param shopId * @return */ List<XiaoeLiveRecord> getListByShopId(Long shopId); void add(Long shopId, Integer type, String shopName, String liveId); } ruoyi-modules/ruoyi-goods/src/main/java/com/ruoyi/goods/utils/WeChatSubscribeMessageSender.java
New file @@ -0,0 +1,76 @@ package com.ruoyi.goods.utils; import cn.hutool.core.date.DateUtil; import cn.hutool.http.HttpUtil; import com.alibaba.fastjson2.JSONObject; import lombok.extern.slf4j.Slf4j; import java.util.Date; import java.util.HashMap; import java.util.Map; /** * @author mitao * @date 2025/1/10 */ @Slf4j public class WeChatSubscribeMessageSender { private static final String ACCESS_TOKEN_HOST = "https://api.weixin.qq.com/cgi-bin/token"; private static final String API_URL = "https://api.weixin.qq.com/cgi-bin/message/subscribe/send"; private static final String WX_APPID = "wxb7f0ea286fc4e535"; private static final String WX_SECRET = "852a2512a6ab559cafc68bae5d4160ac"; private static final String TEMPLATE_ID = "EFeu75n2GMmOg33PxL1HNoyftp16ukco5DUbBfNBytE"; /** *发送消息 * @param touser 接收者(用户)的 openid * @param title 直播主题 * @param shopName 直播间名称 * @param aliveStartAt 直播时间 */ public static void push(String touser, String title, String shopName, Date aliveStartAt) { //1,获取access_token String accessToken = getAccessTokenByWX(); String url = API_URL + "?access_token=" + accessToken; Map<String, Object> params = new HashMap<>(); params.put("template_id", TEMPLATE_ID); params.put("page", null); params.put("touser", touser); // 构建订阅消息内容的JSON对象 JSONObject messageData = new JSONObject(); messageData.put("thing1", createDataItem("直播主题", title)); messageData.put("thing4", createDataItem("直播间名称", shopName)); messageData.put("time5", createDataItem("直播时间", DateUtil.format(aliveStartAt, "MM-dd HH:mm:ss"))); params.put("data", messageData.toJSONString()); params.put("miniprogram_state", "trial"); params.put("lang", "zh_CN"); try { String post = HttpUtil.post(url, JSONObject.toJSONString(params)); log.info("发送消息返回结果:{}", post); } catch (Exception e) { throw new RuntimeException(e); } } /** * 获取access_token * @return */ public static String getAccessTokenByWX() { String host = ACCESS_TOKEN_HOST + "?appid=" + WX_APPID + "&secret=" + WX_SECRET + "&grant_type=client_credential"; log.info("host:{}", host); return HttpUtil.get(host); } private static Map<String, Object> createDataItem(String name, String value) { Map<String, Object> item = new HashMap<>(); item.put("value", value); return item; } public static void main(String[] args) throws Exception { push("oL-gp5Fn7BobtFZCsQ3ZTY7QGU84", "直播推送", "鸿瑞堂", new Date()); } } ruoyi-modules/ruoyi-goods/src/main/java/com/ruoyi/goods/utils/XiaoeUtils.java
New file @@ -0,0 +1,375 @@ package com.ruoyi.goods.utils; import cn.hutool.http.HttpUtil; import com.alibaba.fastjson2.JSONArray; import com.alibaba.fastjson2.JSONObject; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.ruoyi.common.core.utils.StringUtils; import com.ruoyi.common.redis.service.RedisService; import com.ruoyi.goods.domain.dto.XiaoeCourseQueryDto; import com.ruoyi.goods.domain.dto.XiaoeLiveDto; import com.ruoyi.goods.domain.dto.XiaoeLiveQueryDto; import com.ruoyi.goods.domain.vo.XiaoeCourseChapterVO; import com.ruoyi.goods.domain.vo.XiaoeCourseGroupVO; import com.ruoyi.goods.domain.vo.XiaoeCourseVO; import com.ruoyi.goods.domain.vo.XiaoeLiveVo; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; import javax.annotation.Resource; import java.io.IOException; import java.io.InputStreamReader; import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.URL; import java.util.ArrayList; import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; /** * 小鹅通工具类 * @author mitao * @date 2025/1/8 */ @Slf4j @Component public class XiaoeUtils { @Resource private RedisService redisService; private static final String BASE_URL = "https://api.xiaoe-tech.com/token"; //店铺的业务id private String app_id = "appwmuwNWD48082"; //应用的唯一标识,通过 client_id 来鉴别应用的身份 private String client_id = "xopYwOAqNI36444"; //应用的凭证秘钥,即client_secret,用来保证应用来源的可靠性,防止被伪造 private String secret_key = "qKFxbGR0OlKX85PVyfCvkRF1P6fLRBEu"; //固定填写client_credential private String grant_type = "client_credential"; //获取直播列表 private static final String LIVE_PAGE_LIST = "https://api.xiaoe-tech.com/xe.alive.list.get/2.0.0"; //获取直播详情 private static final String LIVE_DETAIL = "https://api.xiaoe-tech.com/xe.alive.detail.get/1.0.0"; //创建直播 private static final String LIVE_ADD = "https://api.xiaoe-tech.com/xe.alive.live.create/1.0.0"; //编辑直播 private static final String LIVE_EDIT = "https://api.xiaoe-tech.com/xe.alive.live.update/1.0.0"; //删除直播 private static final String LIVE_DELETE = "https://api.xiaoe-tech.com/xe.alive.live.delete/1.0.0"; //课程列表 private static final String COURSE_PAGE_LIST = "https://api.xiaoe-tech.com/xe.course.course.list/1.0.0"; //课程小节 private static final String COURSE_CHAPTER = "https://api.xiaoe-tech.com/xe.course.course.chapter.get/1.0.0"; //获取店铺商品分组列表 private static final String COURSE_GROUP_LIST = "https://api.xiaoe-tech.com/xe.resource_tags.list/1.0.0"; /** * 获取小鹅通access_token * @return */ private String getAccessToken() { Boolean flag = redisService.hasKey("xiaoe:access_token"); String accessToken = ""; if (flag) { accessToken = redisService.getCacheObject("xiaoe:access_token"); } else { String urlString = BASE_URL + "?app_id=" + this.app_id + "&client_id=" + this.client_id + "&secret_key=" + this.secret_key + "&grant_type=" + this.grant_type; String result = null; try { URL reqURL = new URL(urlString); HttpURLConnection httpURLConnection = (HttpURLConnection) reqURL.openConnection(); InputStreamReader isr = new InputStreamReader(httpURLConnection.getInputStream()); char[] chars = new char[1024]; result = ""; int len; while ((len = isr.read(chars)) != -1) { result += new String(chars, 0, len); } isr.close(); } catch (MalformedURLException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } JSONObject jsonObject = JSONObject.parseObject(result); Map data = (Map) jsonObject.get("data"); accessToken = (String) data.get("access_token"); } return accessToken; } /** * 获取直播列表 * @param dto * @return */ public Page<XiaoeLiveVo> getLivePageList(XiaoeLiveQueryDto dto) { Map<String,Object> postParams = new HashMap<>(); postParams.put("search_content", dto.getSearchContent()); postParams.put("create_mode", dto.getCreateMode()); postParams.put("state", dto.getState()); postParams.put("search_alive_type", dto.getSearchAliveType()); postParams.put("alive_play_state", dto.getAlivePlayState()); postParams.put("page", dto.getPage()); postParams.put("page_size", dto.getPageSize()); postParams.put("access_token", getAccessToken()); String post = HttpUtil.post(LIVE_PAGE_LIST, JSONObject.toJSONString(postParams)); Page<XiaoeLiveVo> xiaoeLiveVOPage = new Page<>(); if (StringUtils.isNotBlank(post)) { JSONObject jsonObject = JSONObject.parseObject(post); if (jsonObject.get("code").equals(0)) { JSONObject data = jsonObject.getJSONObject("data"); JSONArray list = data.getJSONArray("list"); List<XiaoeLiveVo> xiaoeLiveVos = JSONArray.parseArray(list.toJSONString(), XiaoeLiveVo.class); xiaoeLiveVOPage.setRecords(xiaoeLiveVos); xiaoeLiveVOPage.setCurrent(data.getLong("page")); xiaoeLiveVOPage.setTotal(data.getLong("total")); xiaoeLiveVOPage.setPages(data.getLong("page_count")); } } return xiaoeLiveVOPage; } /** *获取直播详情 * @param id 直播ID * @return */ public XiaoeLiveVo getLiveDetail(String id) { Map<String, Object> postParams = new HashMap<>(); postParams.put("id", id); postParams.put("access_token", getAccessToken()); String post = HttpUtil.post(LIVE_DETAIL, JSONObject.toJSONString(postParams)); XiaoeLiveVo xiaoeLiveVO = null; if (StringUtils.isNotBlank(post)) { JSONObject jsonObject = JSONObject.parseObject(post); if (jsonObject.get("code").equals(0)) { JSONObject data = jsonObject.getJSONObject("data"); xiaoeLiveVO = JSONObject.parseObject(data.toJSONString(), XiaoeLiveVo.class); } } return xiaoeLiveVO; } /** * 创建直播 * @param dto * @return */ public String addLive(XiaoeLiveDto dto) { Map<String, Object> requestParams = new HashMap<>(); Map<String, Object> resourceInfoMap = new HashMap<>(); resourceInfoMap.put("title", dto.getTitle()); resourceInfoMap.put("summary", dto.getSummary()); resourceInfoMap.put("zb_start_at", dto.getZbStartAt()); resourceInfoMap.put("zb_stop_at", dto.getZbStopAt()); resourceInfoMap.put("alive_type", dto.getAliveType()); resourceInfoMap.put("descrb", dto.getDescrb()); requestParams.put("resource_info", resourceInfoMap); // 配置信息 Map<String, Object> moudleInfoMap = new HashMap<>(); moudleInfoMap.put("alive_mode", dto.getAliveMode()); requestParams.put("module_info", moudleInfoMap); //讲师信息 Map<String, Object> roleInfoMap = new HashMap<>(); roleInfoMap.put("user_id", dto.getUserId()); requestParams.put("role_info", roleInfoMap); //商品信息 Map<String, Object> goodsInfoMap = new HashMap<>(); goodsInfoMap.put("sale_type", 2);//售卖类型:1-单独售卖、2-关联售卖 goodsInfoMap.put("payment_type", dto.getPaymentType()); if (dto.getPaymentType().equals(3)) { goodsInfoMap.put("resource_password", dto.getResourcePassword()); } requestParams.put("goods_info", goodsInfoMap); requestParams.put("access_token", getAccessToken()); String post = HttpUtil.post(LIVE_ADD, JSONObject.toJSONString(requestParams)); if (StringUtils.isNotBlank(post)) { JSONObject jsonObject = JSONObject.parseObject(post); log.info("创建直播返回结果:{}" ,post); if (jsonObject.get("code").equals(0)) { JSONObject data = jsonObject.getJSONObject("data"); return data.getString("id"); } } return null; } /** * 创建直播 * @param dto * @return */ public Boolean editLive(XiaoeLiveDto dto) { boolean flag = false; Map<String, Object> requestParams = new HashMap<>(); Map<String, Object> resourceInfoMap = new HashMap<>(); resourceInfoMap.put("id", dto.getId()); resourceInfoMap.put("title", dto.getTitle()); resourceInfoMap.put("summary", dto.getSummary()); resourceInfoMap.put("zb_start_at", dto.getZbStartAt()); resourceInfoMap.put("zb_stop_at", dto.getZbStopAt()); resourceInfoMap.put("alive_type", dto.getAliveType()); resourceInfoMap.put("descrb", dto.getDescrb()); requestParams.put("resource_info", resourceInfoMap); // 配置信息 Map<String, Object> moudleInfoMap = new HashMap<>(); moudleInfoMap.put("alive_mode", dto.getAliveMode()); requestParams.put("module_info", moudleInfoMap); //讲师信息 Map<String, Object> roleInfoMap = new HashMap<>(); roleInfoMap.put("user_id", dto.getUserId()); requestParams.put("role_info", roleInfoMap); //商品信息 Map<String, Object> goodsInfoMap = new HashMap<>(); goodsInfoMap.put("sale_type", 2);//售卖类型:1-单独售卖、2-关联售卖 goodsInfoMap.put("payment_type", dto.getPaymentType()); if (dto.getPaymentType().equals(3)) { goodsInfoMap.put("resource_password", dto.getResourcePassword()); } requestParams.put("goods_info", goodsInfoMap); requestParams.put("access_token", getAccessToken()); String post = HttpUtil.post(LIVE_EDIT, JSONObject.toJSONString(requestParams)); if (StringUtils.isNotBlank(post)) { JSONObject jsonObject = JSONObject.parseObject(post); log.info("编辑直播返回结果:{}" ,post); if (jsonObject.get("code").equals(0)) { flag = true; } } return flag; } /** * 删除直播 * @param id * @return */ public Boolean deleteLive(String id) { boolean flag = false; Map<String, Object> requestParams = new HashMap<>(); requestParams.put("id", id); requestParams.put("access_token", getAccessToken()); String post = HttpUtil.post(LIVE_DELETE, JSONObject.toJSONString(requestParams)); if (StringUtils.isNotBlank(post)) { JSONObject jsonObject = JSONObject.parseObject(post); log.info("删除直播返回结果:{}" ,post); if (jsonObject.get("code").equals(0)) { flag = true; } } return flag; } /** * 获取商品分组列表 * @return */ public List<XiaoeCourseGroupVO> getCourseGroupList(){ Map<String, Object> requestParams = new HashMap<>(); requestParams.put("page", 1); requestParams.put("page_size", 50); requestParams.put("access_token", getAccessToken()); String post = HttpUtil.post(COURSE_GROUP_LIST, JSONObject.toJSONString(requestParams)); List<XiaoeCourseGroupVO> xiaoeCourseGroupVOList = new ArrayList<>(); if (StringUtils.isNotBlank(post)) { JSONObject jsonObject = JSONObject.parseObject(post); log.info("获取商品分组返回结果:{}" ,post); if (jsonObject.get("code").equals(0)) { JSONObject data = jsonObject.getJSONObject("data"); JSONArray jsonArray = data.getJSONArray("list"); xiaoeCourseGroupVOList = JSONArray.parseArray(jsonArray.toJSONString(), XiaoeCourseGroupVO.class); } } return xiaoeCourseGroupVOList; } /** * 获取课程列表 * @param dto * @return */ public Page<XiaoeCourseVO> getCoursePageList(XiaoeCourseQueryDto dto) { JSONObject requestParams = JSONObject.parseObject(JSONObject.toJSONString(dto)); if (dto.getSortBy().equals(1)) { requestParams.put("order_by", "modify");//根据创建时间排序 requestParams.put("order_type", "降序"); } requestParams.put("sale_status", 1); requestParams.put("access_token", getAccessToken()); String post = HttpUtil.post(COURSE_PAGE_LIST, requestParams.toJSONString()); Page<XiaoeCourseVO> page = new Page<>(); if (StringUtils.isNotBlank(post)) { log.info("获取课程列表返回结果:{}" ,post); JSONObject jsonObject = JSONObject.parseObject(post); if (jsonObject.get("code").equals(0)) { JSONObject data = jsonObject.getJSONObject("data"); JSONArray jsonArray = data.getJSONArray("list"); List<XiaoeCourseVO> xiaoeCourseVOList = JSONArray.parseArray(jsonArray.toJSONString(), XiaoeCourseVO.class); if (dto.getSortBy().equals(2)){ xiaoeCourseVOList.sort(Comparator.comparing(XiaoeCourseVO::getCurriculumTime)); } page.setRecords(xiaoeCourseVOList); page.setTotal(data.getLong("total")); } } return page; } /** * 查询课程目录小节 * @param id * @return */ public List<XiaoeCourseChapterVO> getCourseChapterDetail(String id) { Map<String, Object> requestParams = new HashMap<>(); requestParams.put("course_id", id); requestParams.put("access_token", getAccessToken()); String post = HttpUtil.post(COURSE_CHAPTER, JSONObject.toJSONString(requestParams)); List<XiaoeCourseChapterVO> xiaoeCourseChapterVO = new ArrayList<>(); if (StringUtils.isNotBlank(post)) { log.info("查询课程目录小节返回结果:{}" ,post); JSONObject jsonObject = JSONObject.parseObject(post); if (jsonObject.get("code").equals(0)) { String data = jsonObject.getString("data"); xiaoeCourseChapterVO = JSONArray.parseArray(data, XiaoeCourseChapterVO.class); } } return xiaoeCourseChapterVO; } public static void main(String[] args) { String urlString = BASE_URL + "?app_id=appwmuwNWD48082" + "&client_id=xopYwOAqNI36444"+ "&secret_key=qKFxbGR0OlKX85PVyfCvkRF1P6fLRBEu" + "&grant_type=client_credential"; String result = null; try { URL reqURL = new URL(urlString); HttpURLConnection httpURLConnection = (HttpURLConnection) reqURL.openConnection(); InputStreamReader isr = new InputStreamReader(httpURLConnection.getInputStream()); char[] chars = new char[1024]; result = ""; int len; while ((len = isr.read(chars)) != -1) { result += new String(chars, 0, len); } isr.close(); } catch (MalformedURLException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } JSONObject jsonObject = JSONObject.parseObject(result); System.out.println(jsonObject); Map data = (Map) jsonObject.get("data"); String accessToken = (String) data.get("access_token"); System.out.println(accessToken); } } 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-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/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 {