feat: 🎸 课程列表接口-01

This commit is contained in:
GoCo 2025-08-12 19:17:33 +08:00
parent 962101f37a
commit 8f9e974aad
14 changed files with 542 additions and 18 deletions

View File

@ -10,14 +10,15 @@ import org.jeecg.common.system.vo.LoginUser;
import org.jeecg.config.shiro.IgnoreAuth;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import org.jeecg.common.system.api.ISysBaseAPI;
import org.jeecg.modules.biz.service.CourseBizService;
import org.jeecg.modules.gen.course.entity.Course;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@ -28,6 +29,22 @@ import javax.servlet.http.HttpServletResponse;
@Slf4j
public class CourseBizController {
@Autowired
private CourseBizService courseBizService;
@Autowired
private ISysBaseAPI sysBaseApi;
@GetMapping("/list")
@Operation(summary = "根据分类、难度、专题查询课程列表")
@IgnoreAuth
public Result<List<Course>> queryCourseList(
@RequestParam(value = "categoryId", required = false) String categoryId,
@RequestParam(value = "difficulty", required = false) String difficulty,
@RequestParam(value = "subject", required = false) String topic) {
List<Course> list = courseBizService.getCourseList(categoryId, difficulty, topic);
return Result.OK(list);
}
@GetMapping("/test")
@Operation(summary = "测试")
@IgnoreAuth
@ -35,11 +52,6 @@ public class CourseBizController {
return Result.OK("test");
}
@Autowired
private ISysBaseAPI sysBaseApi;
@Autowired
private CourseBizService courseBusinessService;
@GetMapping("/test2")
@Operation(summary = "测试2")
public Result<String> test2(HttpServletRequest request, HttpServletResponse response) {
@ -53,16 +65,7 @@ public class CourseBizController {
@Operation(summary = "测试3")
@IgnoreAuth
public Result<Long> test3() {
long count = courseBusinessService.count();
long count = courseBizService.count();
return Result.OK(count);
}
@PostMapping("/upload")
@Operation(summary = "课程视频文件上传", description = "课程视频文件上传返回m3u8文件地址")
@IgnoreAuth
public Result<String> upload(@RequestParam("file") MultipartFile file, HttpServletRequest request) throws Exception {
if (file == null || file.isEmpty()) return Result.error("没有找到上传的文件");
String url = courseBusinessService.uploadHls(file, request);
return Result.OK(url);
}
}

View File

@ -0,0 +1,40 @@
package org.jeecg.modules.biz.controller;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletRequest;
import org.jeecg.common.api.vo.Result;
import org.jeecg.common.constant.CommonConstant;
import org.jeecg.common.system.util.JwtUtil;
import org.jeecg.common.system.vo.LoginUser;
import org.jeecg.config.shiro.IgnoreAuth;
import org.jeecg.modules.biz.service.ResourceBizService;
import io.swagger.v3.oas.annotations.Operation;
@Tag(name = "资源")
@RestController
@RequestMapping("/biz/resource")
@Slf4j
public class ResourceBizController {
@Autowired
private ResourceBizService resourceBizService;
@PostMapping("/upload")
@Operation(summary = "课程视频文件上传", description = "课程视频文件上传返回m3u8文件地址")
@IgnoreAuth
public Result<String> upload(@RequestParam("file") MultipartFile file, HttpServletRequest request) throws Exception {
if (file == null || file.isEmpty()) return Result.error("没有找到上传的文件");
String url = resourceBizService.uploadHls(file, request);
return Result.OK(url);
}
}

View File

@ -1,7 +1,10 @@
package org.jeecg.modules.biz.service;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import org.jeecg.modules.gen.course.entity.Course;
import org.jeecg.modules.gen.test.entity.TestTable;
import org.springframework.web.multipart.MultipartFile;
import com.baomidou.mybatisplus.extension.service.IService;
@ -19,6 +22,15 @@ public interface CourseBizService extends IService<TestTable> {
* @throws Exception 处理异常
*/
String uploadHls(MultipartFile file, HttpServletRequest request) throws Exception;
/**
* 根据分类难度专题查询课程列表
* @param categoryId
* @param difficulty
* @param topic
* @return
*/
List<Course> getCourseList(String categoryId, String difficulty, String topic);
}

View File

@ -0,0 +1,7 @@
package org.jeecg.modules.biz.service;
import com.baomidou.mybatisplus.extension.service.IService;
public interface EntityLinkBizService {
}

View File

@ -0,0 +1,24 @@
package org.jeecg.modules.biz.service;
import javax.servlet.http.HttpServletRequest;
import org.jeecg.modules.gen.resource.entity.Resource;
import org.springframework.web.multipart.MultipartFile;
import com.baomidou.mybatisplus.extension.service.IService;
/**
* 资源业务
*/
public interface ResourceBizService extends IService<Resource> {
/**
* 上传视频并切片为 HLSm3u8+ts按配置(local|minio|alioss)上传返回 m3u8 的路径/URL
* @param file 上传的视频文件
* @param request 用于读取 header 或环境配置
* @return m3u8 路径/URL
* @throws Exception 处理异常
*/
String uploadHls(MultipartFile file, HttpServletRequest request) throws Exception;
}

View File

@ -7,10 +7,14 @@ import org.jeecg.common.util.SpringContextUtils;
import org.jeecg.common.util.oss.OssBootUtil;
import org.jeecg.modules.biz.service.CourseBizService;
import org.jeecg.modules.gen.test.mapper.TestTableMapper;
import org.jeecg.modules.gen.course.entity.Course;
import org.jeecg.modules.gen.course.mapper.CourseMapper;
import org.jeecg.modules.gen.test.entity.TestTable;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import javax.servlet.http.HttpServletRequest;
@ -27,6 +31,18 @@ import java.util.stream.Stream;
@Service
public class CourseBizServiceImpl extends ServiceImpl<TestTableMapper, TestTable> implements CourseBizService {
@Autowired
private CourseMapper courseMapper;
@Override
public List<Course> getCourseList(String categoryId, String difficulty, String topic) {
QueryWrapper<Course> queryWrapper = new QueryWrapper<>();
if (difficulty != null && difficulty.trim().length() > 0) {
queryWrapper.eq("difficulty", difficulty);
}
return courseMapper.selectList(queryWrapper);
}
@Override
public String uploadHls(MultipartFile file, HttpServletRequest request) throws Exception {
// 读取上传类型header 优先

View File

@ -0,0 +1,117 @@
package org.jeecg.modules.biz.service.impl;
import lombok.extern.slf4j.Slf4j;
import org.jeecg.common.util.CommonUtils;
import org.jeecg.common.util.MinioUtil;
import org.jeecg.common.util.SpringContextUtils;
import org.jeecg.common.util.oss.OssBootUtil;
import org.jeecg.modules.biz.service.ResourceBizService;
import org.jeecg.modules.gen.test.mapper.TestTableMapper;
import org.jeecg.modules.gen.resource.mapper.ResourceMapper;
import org.jeecg.modules.gen.resource.entity.Resource;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import javax.servlet.http.HttpServletRequest;
import java.io.File;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Stream;
@Slf4j
@Service
public class ResourceBizServiceImpl extends ServiceImpl<ResourceMapper, Resource> implements ResourceBizService {
@Override
public String uploadHls(MultipartFile file, HttpServletRequest request) throws Exception {
// 读取上传类型header 优先
String headerUploadType = request.getHeader("uploadType");
String configUploadType = SpringContextUtils.getApplicationContext().getEnvironment().getProperty("jeecg.uploadType", "minio");
String uploadType = (headerUploadType != null && headerUploadType.trim().length() > 0) ? headerUploadType : configUploadType;
// 1) 保存临时原始视频
String uuid = UUID.randomUUID().toString();
String tmpRoot = System.getProperty("java.io.tmpdir");
Path tmpVideoDir = Path.of(tmpRoot, "jeecg", "video", uuid);
Path hlsDir = Path.of(tmpRoot, "jeecg", "hls", uuid);
Files.createDirectories(tmpVideoDir);
Files.createDirectories(hlsDir);
String original = CommonUtils.getFileName(Objects.requireNonNull(file.getOriginalFilename()));
Path tmpVideoFile = tmpVideoDir.resolve(original);
Files.copy(file.getInputStream(), tmpVideoFile, StandardCopyOption.REPLACE_EXISTING);
// 2) ffmpeg 切片
Path m3u8Path = hlsDir.resolve(uuid + ".m3u8");
List<String> cmd = Arrays.asList(
"ffmpeg", "-i", tmpVideoFile.toString(),
"-c:v", "libx264", "-c:a", "aac",
"-hls_time", "10", "-hls_playlist_type", "vod",
m3u8Path.toString());
Process p = new ProcessBuilder(cmd).redirectErrorStream(true).start();
boolean ok = p.waitFor(10, TimeUnit.MINUTES) && p.exitValue() == 0;
if (!ok) {
deleteQuietly(hlsDir.toFile());
deleteQuietly(tmpVideoDir.toFile());
throw new RuntimeException("ffmpeg切片超时");
}
// 3) 上传切片
String m3u8Url = "";
String base = "video/hls/" + uuid;
try (Stream<Path> paths = Files.list(hlsDir)) {
for (Path f : (Iterable<Path>) paths::iterator) {
if (!Files.isRegularFile(f)) continue;
String rel = base + "/" + f.getFileName().toString();
try (InputStream in = Files.newInputStream(f)) {
if ("minio".equals(uploadType)) {
String tmpUrl = MinioUtil.upload(in, rel);
if (f.getFileName().toString().endsWith(".m3u8")) {
m3u8Url = tmpUrl;
}
} else if ("alioss".equals(uploadType)) {
OssBootUtil.upload(in, rel);
if (f.getFileName().toString().endsWith(".m3u8")) {
m3u8Url = rel; // 可在网关拼域名
}
} else {
String uploadpath = SpringContextUtils.getApplicationContext().getEnvironment().getProperty("jeecg.path.upload");
Path target = Path.of(uploadpath, rel);
Files.createDirectories(target.getParent());
Files.copy(f, target, StandardCopyOption.REPLACE_EXISTING);
if (f.getFileName().toString().endsWith(".m3u8")) {
m3u8Url = rel; // local 返回相对路径
}
}
}
}
} finally {
deleteQuietly(hlsDir.toFile());
deleteQuietly(tmpVideoDir.toFile());
}
return m3u8Url;
}
/** 删除临时目录文件 */
private static void deleteQuietly(File file) {
try {
if (file == null || !file.exists()) return;
if (file.isDirectory()) {
File[] children = file.listFiles();
if (children != null) {
for (File c : children) deleteQuietly(c);
}
}
file.delete();
} catch (Exception ignored) {}
}
}

View File

@ -0,0 +1,182 @@
package org.jeecg.modules.gen.entitylink.controller;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.jeecg.common.api.vo.Result;
import org.jeecg.common.system.query.QueryGenerator;
import org.jeecg.common.system.query.QueryRuleEnum;
import org.jeecg.common.util.oConvertUtils;
import org.jeecg.modules.gen.entitylink.entity.EntityLink;
import org.jeecg.modules.gen.entitylink.service.IEntityLinkService;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.extern.slf4j.Slf4j;
import org.jeecgframework.poi.excel.ExcelImportUtil;
import org.jeecgframework.poi.excel.def.NormalExcelConstants;
import org.jeecgframework.poi.excel.entity.ExportParams;
import org.jeecgframework.poi.excel.entity.ImportParams;
import org.jeecgframework.poi.excel.view.JeecgEntityExcelView;
import org.jeecg.common.system.base.controller.JeecgController;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.MultipartHttpServletRequest;
import org.springframework.web.servlet.ModelAndView;
import com.alibaba.fastjson.JSON;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Operation;
import org.jeecg.common.aspect.annotation.AutoLog;
import org.apache.shiro.authz.annotation.RequiresPermissions;
/**
* @Description: 主体绑定
* @Author: jeecg-boot
* @Date: 2025-08-12
* @Version: V1.0
*/
@Tag(name="主体绑定")
@RestController
@RequestMapping("/gen/entitylink/entityLink")
@Slf4j
public class EntityLinkController extends JeecgController<EntityLink, IEntityLinkService> {
@Autowired
private IEntityLinkService entityLinkService;
/**
* 分页列表查询
*
* @param entityLink
* @param pageNo
* @param pageSize
* @param req
* @return
*/
//@AutoLog(value = "主体绑定-分页列表查询")
@Operation(summary="主体绑定-分页列表查询")
@GetMapping(value = "/list")
public Result<IPage<EntityLink>> queryPageList(EntityLink entityLink,
@RequestParam(name="pageNo", defaultValue="1") Integer pageNo,
@RequestParam(name="pageSize", defaultValue="10") Integer pageSize,
HttpServletRequest req) {
QueryWrapper<EntityLink> queryWrapper = QueryGenerator.initQueryWrapper(entityLink, req.getParameterMap());
Page<EntityLink> page = new Page<EntityLink>(pageNo, pageSize);
IPage<EntityLink> pageList = entityLinkService.page(page, queryWrapper);
return Result.OK(pageList);
}
/**
* 添加
*
* @param entityLink
* @return
*/
@AutoLog(value = "主体绑定-添加")
@Operation(summary="主体绑定-添加")
@RequiresPermissions("gen.entitylink:entity_link:add")
@PostMapping(value = "/add")
public Result<String> add(@RequestBody EntityLink entityLink) {
entityLinkService.save(entityLink);
return Result.OK("添加成功!");
}
/**
* 编辑
*
* @param entityLink
* @return
*/
@AutoLog(value = "主体绑定-编辑")
@Operation(summary="主体绑定-编辑")
@RequiresPermissions("gen.entitylink:entity_link:edit")
@RequestMapping(value = "/edit", method = {RequestMethod.PUT,RequestMethod.POST})
public Result<String> edit(@RequestBody EntityLink entityLink) {
entityLinkService.updateById(entityLink);
return Result.OK("编辑成功!");
}
/**
* 通过id删除
*
* @param id
* @return
*/
@AutoLog(value = "主体绑定-通过id删除")
@Operation(summary="主体绑定-通过id删除")
@RequiresPermissions("gen.entitylink:entity_link:delete")
@DeleteMapping(value = "/delete")
public Result<String> delete(@RequestParam(name="id",required=true) String id) {
entityLinkService.removeById(id);
return Result.OK("删除成功!");
}
/**
* 批量删除
*
* @param ids
* @return
*/
@AutoLog(value = "主体绑定-批量删除")
@Operation(summary="主体绑定-批量删除")
@RequiresPermissions("gen.entitylink:entity_link:deleteBatch")
@DeleteMapping(value = "/deleteBatch")
public Result<String> deleteBatch(@RequestParam(name="ids",required=true) String ids) {
this.entityLinkService.removeByIds(Arrays.asList(ids.split(",")));
return Result.OK("批量删除成功!");
}
/**
* 通过id查询
*
* @param id
* @return
*/
//@AutoLog(value = "主体绑定-通过id查询")
@Operation(summary="主体绑定-通过id查询")
@GetMapping(value = "/queryById")
public Result<EntityLink> queryById(@RequestParam(name="id",required=true) String id) {
EntityLink entityLink = entityLinkService.getById(id);
if(entityLink==null) {
return Result.error("未找到对应数据");
}
return Result.OK(entityLink);
}
/**
* 导出excel
*
* @param request
* @param entityLink
*/
@RequiresPermissions("gen.entitylink:entity_link:exportXls")
@RequestMapping(value = "/exportXls")
public ModelAndView exportXls(HttpServletRequest request, EntityLink entityLink) {
return super.exportXls(request, entityLink, EntityLink.class, "主体绑定");
}
/**
* 通过excel导入数据
*
* @param request
* @param response
* @return
*/
@RequiresPermissions("gen.entitylink:entity_link:importExcel")
@RequestMapping(value = "/importExcel", method = RequestMethod.POST)
public Result<?> importExcel(HttpServletRequest request, HttpServletResponse response) {
return super.importExcel(request, response, EntityLink.class);
}
}

View File

@ -0,0 +1,68 @@
package org.jeecg.modules.gen.entitylink.entity;
import java.io.Serializable;
import java.io.UnsupportedEncodingException;
import java.util.Date;
import java.math.BigDecimal;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.annotation.TableLogic;
import org.jeecg.common.constant.ProvinceCityArea;
import org.jeecg.common.util.SpringContextUtils;
import lombok.Data;
import com.fasterxml.jackson.annotation.JsonFormat;
import org.springframework.format.annotation.DateTimeFormat;
import org.jeecgframework.poi.excel.annotation.Excel;
import org.jeecg.common.aspect.annotation.Dict;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
/**
* @Description: 主体绑定
* @Author: jeecg-boot
* @Date: 2025-08-12
* @Version: V1.0
*/
@Data
@TableName("entity_link")
@Accessors(chain = true)
@EqualsAndHashCode(callSuper = false)
@Schema(description="主体绑定")
public class EntityLink implements Serializable {
private static final long serialVersionUID = 1L;
/**主键*/
@TableId(type = IdType.ASSIGN_ID)
@Schema(description = "主键")
private java.lang.String id;
/**主体类型*/
@Excel(name = "主体类型", width = 15)
@Schema(description = "主体类型")
private java.lang.String sourceType;
/**主体id*/
@Excel(name = "主体id", width = 15)
@Schema(description = "主体id")
private java.lang.String sourceId;
/**内容类型*/
@Excel(name = "内容类型", width = 15)
@Schema(description = "内容类型")
private java.lang.String targetType;
/**内容id*/
@Excel(name = "内容id", width = 15)
@Schema(description = "内容id")
private java.lang.String targetId;
/**排序*/
@Excel(name = "排序", width = 15)
@Schema(description = "排序")
private java.lang.Integer sortOrder;
/**创建人*/
@Schema(description = "创建人")
private java.lang.String createBy;
/**创建日期*/
@JsonFormat(timezone = "GMT+8",pattern = "yyyy-MM-dd HH:mm:ss")
@DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss")
@Schema(description = "创建日期")
private java.util.Date createTime;
}

View File

@ -0,0 +1,17 @@
package org.jeecg.modules.gen.entitylink.mapper;
import java.util.List;
import org.apache.ibatis.annotations.Param;
import org.jeecg.modules.gen.entitylink.entity.EntityLink;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
/**
* @Description: 主体绑定
* @Author: jeecg-boot
* @Date: 2025-08-12
* @Version: V1.0
*/
public interface EntityLinkMapper extends BaseMapper<EntityLink> {
}

View 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="org.jeecg.modules.gen.entitylink.mapper.EntityLinkMapper">
</mapper>

View File

@ -0,0 +1,14 @@
package org.jeecg.modules.gen.entitylink.service;
import org.jeecg.modules.gen.entitylink.entity.EntityLink;
import com.baomidou.mybatisplus.extension.service.IService;
/**
* @Description: 主体绑定
* @Author: jeecg-boot
* @Date: 2025-08-12
* @Version: V1.0
*/
public interface IEntityLinkService extends IService<EntityLink> {
}

View File

@ -0,0 +1,19 @@
package org.jeecg.modules.gen.entitylink.service.impl;
import org.jeecg.modules.gen.entitylink.entity.EntityLink;
import org.jeecg.modules.gen.entitylink.mapper.EntityLinkMapper;
import org.jeecg.modules.gen.entitylink.service.IEntityLinkService;
import org.springframework.stereotype.Service;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
/**
* @Description: 主体绑定
* @Author: jeecg-boot
* @Date: 2025-08-12
* @Version: V1.0
*/
@Service
public class EntityLinkServiceImpl extends ServiceImpl<EntityLinkMapper, EntityLink> implements IEntityLinkService {
}

View File

@ -152,7 +152,7 @@ spring:
datasource:
master:
# url: jdbc:mysql://127.0.0.1:33061/jeecg-boot?characterEncoding=UTF-8&useUnicode=true&useSSL=false&tinyInt1isBit=false&allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai
url: jdbc:mysql://110.42.96.65:55616/jeecg-boot?characterEncoding=UTF-8&useUnicode=true&useSSL=false&tinyInt1isBit=false&allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai
url: jdbc:mysql://103.40.14.23:25523/jeecg-boot?characterEncoding=UTF-8&useUnicode=true&useSSL=false&tinyInt1isBit=false&allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver