feature: [课程] 修改和删除课程

This commit is contained in:
ovo 2025-05-12 18:00:08 +08:00
parent 520207c8d2
commit 16eb04344b
8 changed files with 257 additions and 20 deletions

View File

@ -142,8 +142,6 @@
<version>2.15.3</version> <!-- 请使用最新的版本 --> <version>2.15.3</version> <!-- 请使用最新的版本 -->
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.alibaba</groupId> <groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId> <artifactId>fastjson</artifactId>
@ -228,7 +226,6 @@
<version>2.3.0</version> <version>2.3.0</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.arcsoft.face</groupId> <groupId>com.arcsoft.face</groupId>
<artifactId>arcsoft-sdk-face</artifactId> <artifactId>arcsoft-sdk-face</artifactId>
@ -525,6 +522,12 @@
<artifactId>spring-boot-starter-data-jpa</artifactId> <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency> </dependency>
<dependency>
<groupId>ws.schild</groupId>
<artifactId>jave-all-deps</artifactId>
<version>3.5.0</version>
</dependency>
</dependencies> </dependencies>

View File

@ -21,6 +21,8 @@ public class SecurityConstants {
"/bs/courses/queryByPage", "/bs/courses/queryByPage",
"/captcha/verify", //验证码认证接口
"/challenge", "/challenge",
"/ws/**", "/ws/**",
"/faceTest", "/compareFaces", "/faceTest", "/compareFaces",

View File

@ -0,0 +1,83 @@
package com.guwan.backend.controller;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.http.ResponseEntity;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.GetMapping;
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.client.RestTemplate;
import org.springframework.web.util.UriComponentsBuilder;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.HexFormat;
import java.util.Map;
@RestController
@RequestMapping("/captcha")
public class CaptchaController {
private static final Map<String, String> keyMap = new HashMap<>();
static {
keyMap.put("99c6323b7b65100d4f9ff07b036e57fa", "12e9e88d5cb19d63d44f8d95a21b69b6");
}
@GetMapping("/verify")
public ResponseEntity<?> login(@RequestParam Map<String, String> params) {
String captchaId = params.get("captcha_id");
String lotNumber = params.get("lot_number");
String prikey = keyMap.get(captchaId);
if (prikey == null) {
return ResponseEntity.ok(Map.of("result", "fail", "reason", "id is not in id pools"));
}
String signToken = generateSignToken(lotNumber, prikey);
// 构建新的查询参数
MultiValueMap<String, String> queryParams = new LinkedMultiValueMap<>();
queryParams.setAll(params);
queryParams.add("sign_token", signToken);
// 调用 Geetest 接口
try {
RestTemplate restTemplate = new RestTemplate();
URI uri = UriComponentsBuilder.fromHttpUrl("http://gcaptcha4.geetest.com/validate")
.queryParams(queryParams)
.build()
.toUri();
//这个url的响应是text/javascript;charset=UTF-8
// 不是application/jsonRestTemplate 默认的 JSON 转换器Jackson不会处理它就会抛出异常
//Map response = restTemplate.getForObject(uri, Map.class);
String responseStr = restTemplate.getForObject(uri, String.class);
ObjectMapper mapper = new ObjectMapper();
Map<String, Object> responseMap = mapper.readValue(responseStr, new TypeReference<>() {});
return ResponseEntity.ok(responseMap);
} catch (Exception e) {
return ResponseEntity.ok(Map.of("result", "success", "reason", "request geetest api fail"));
}
}
private String generateSignToken(String lotNumber, String privateKey) {
try {
Mac sha256Hmac = Mac.getInstance("HmacSHA256");
SecretKeySpec secretKey = new SecretKeySpec(privateKey.getBytes(StandardCharsets.UTF_8), "HmacSHA256");
sha256Hmac.init(secretKey);
byte[] hashBytes = sha256Hmac.doFinal(lotNumber.getBytes(StandardCharsets.UTF_8));
return HexFormat.of().formatHex(hashBytes); // Java 17+或者使用 Apache Commons Codec
} catch (Exception e) {
throw new RuntimeException("Failed to generate sign token", e);
}
}
}

View File

@ -3,6 +3,8 @@ package com.guwan.backend.controller;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.extension.conditions.update.LambdaUpdateChainWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.guwan.backend.common.Result; import com.guwan.backend.common.Result;
import com.guwan.backend.common.SearchResult; import com.guwan.backend.common.SearchResult;
@ -18,6 +20,7 @@ import com.guwan.backend.service.*;
import com.guwan.backend.util.MinioUtil; import com.guwan.backend.util.MinioUtil;
import com.guwan.backend.util.PPTUtil; import com.guwan.backend.util.PPTUtil;
import com.guwan.backend.util.UUIDUtil; import com.guwan.backend.util.UUIDUtil;
import com.guwan.backend.util.VideoDurationUtils;
import jakarta.transaction.Transactional; import jakarta.transaction.Transactional;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
@ -211,6 +214,12 @@ public class CourseController {
section.setType(sectionVO.getType()); section.setType(sectionVO.getType());
section.setUrl(sectionVO.getUrl()); section.setUrl(sectionVO.getUrl());
section.setOrderNumber(j + 1); section.setOrderNumber(j + 1);
if (Objects.equals(sectionVO.getType(), "video")){
long videoDuration = VideoDurationUtils.getVideoDuration(sectionVO.getUrl());
section.setDuration((int) videoDuration);
}
sectionService.save(section); sectionService.save(section);
if (Objects.equals(sectionVO.getType(), "ppt")){ if (Objects.equals(sectionVO.getType(), "ppt")){
@ -238,6 +247,108 @@ public class CourseController {
return Result.success(); return Result.success();
} }
@Transactional
@PostMapping("/updateCourse")
public Result updateCourse(@RequestBody InsertCourseDTO insertCourseDTO) {
System.out.println("insertCourseDTO = " + insertCourseDTO);
String courseDTOId = insertCourseDTO.getId();
Course course = courseService.getById(courseDTOId);
BeanUtils.copyProperties(insertCourseDTO, course);
course.setId(courseDTOId);
courseService.update(course, new LambdaUpdateWrapper<Course>().eq(Course::getId, courseDTOId));
List<ChapterVO> chapterVOS = insertCourseDTO.getChapterVOS();
//全删 TODO 优化
List<String> chapterIds = null;
if (!chapterVOS.isEmpty()) {
chapterIds = chapterVOS.stream().map(ChapterVO::getId).toList();
}
chapterService.remove(new LambdaQueryWrapper<Chapter>().eq(Chapter::getCourseId, courseDTOId));
if (chapterIds != null && !chapterIds.isEmpty()) {
List<Section> sectionList = sectionService.list(new LambdaQueryWrapper<Section>().in(Section::getChapterId, chapterIds));
List<String> sectionIds = null;
if (!sectionList.isEmpty()) {
sectionIds = sectionList.stream().map(Section::getId).toList();
}
sectionService.remove(new LambdaQueryWrapper<Section>().in(Section::getChapterId, chapterIds));
if (sectionIds != null && !sectionIds.isEmpty()) {
slideService.remove(new LambdaQueryWrapper<Slide>().in(Slide::getSectionId, sectionIds));
}
}
for (int i = 0; i < chapterVOS.size(); i++) {
ChapterVO chapterVO = chapterVOS.get(i);
Chapter chapter = new Chapter();
String chapterId = UUIDUtil.uuid();
chapter.setId(chapterId);
chapter.setCourseId(courseDTOId);
chapter.setTitle(chapterVO.getTitle());
chapter.setOrderNumber(i);
chapterService.save(chapter);
List<BaseSectionVO> sectionVOS = chapterVO.getSectionVOS();
for (int j = 0; j < sectionVOS.size(); j++) {
BaseSectionVO sectionVO = sectionVOS.get(j);
String sectionId = UUIDUtil.uuid();
Section section = new Section();
section.setId(sectionId);
section.setChapterId(chapterId);
section.setTitle(sectionVO.getTitle());
section.setType(sectionVO.getType());
section.setUrl(sectionVO.getUrl());
section.setOrderNumber(j + 1);
//根据url 得到视频时长
if (Objects.equals(sectionVO.getType(), "video")){
long videoDuration = VideoDurationUtils.getVideoDuration(sectionVO.getUrl());
section.setDuration((int) videoDuration);
}
sectionService.save(section);
if (Objects.equals(sectionVO.getType(), "ppt")) {
try {
List<String> urlList = pptUtil.PPTToImageConverter(sectionVO.getUrl());
for (int k = 0; k < urlList.size(); k++) {
Slide slide = new Slide();
slide.setId(UUIDUtil.uuid());
slide.setSectionId(sectionId);
slide.setUrl(minioUtil.getUrl(minioUtil.getFileUrl("photo", urlList.get(k))));
slide.setOrderNumber(k + 1);
slideService.save(slide);
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
return Result.success();
}
@Transactional
@PostMapping("/deleteCourse")
public Result deleteCourse(@RequestParam String courseId) {
courseService.remove(new LambdaQueryWrapper<Course>().eq(Course::getId, courseId));
return Result.success();
}
} }

View File

@ -4,16 +4,19 @@ import com.fasterxml.jackson.annotation.JsonProperty;
import com.guwan.backend.pojo.response.courseDetail.ChapterVO; import com.guwan.backend.pojo.response.courseDetail.ChapterVO;
import lombok.Data; import lombok.Data;
import java.math.BigDecimal;
import java.util.List; import java.util.List;
@Data @Data
public class InsertCourseDTO { public class InsertCourseDTO {
private String id;
private String title; private String title;
private String description; private String description;
private String categoryId; private String categoryId;
private String levelId; private String levelId;
private String typeId; private String typeId;
private String teacherId; private String teacherId;
private BigDecimal price;
private String coverImg; private String coverImg;
@JsonProperty("chapters") @JsonProperty("chapters")
private List<ChapterVO> chapterVOS; private List<ChapterVO> chapterVOS;

View File

@ -11,6 +11,7 @@ import java.util.List;
@AllArgsConstructor @AllArgsConstructor
@NoArgsConstructor @NoArgsConstructor
public class CourseDetailVO { public class CourseDetailVO {
private String id;
/** /**
* 课程标题 * 课程标题
*/ */

View File

@ -72,6 +72,7 @@ public class CourseServiceImpl extends ServiceImpl<CourseMapper, Course>
System.out.println("course = " + course); System.out.println("course = " + course);
CourseDetailVO courseDetailVO = new CourseDetailVO(); CourseDetailVO courseDetailVO = new CourseDetailVO();
courseDetailVO.setId(course.getId());
courseDetailVO.setTitle(course.getTitle()); courseDetailVO.setTitle(course.getTitle());
courseDetailVO.setDescription(course.getDescription()); courseDetailVO.setDescription(course.getDescription());
courseDetailVO.setCategoryName(bsCategoryMapper.selectById(course.getCategoryId()).getChineseName()); courseDetailVO.setCategoryName(bsCategoryMapper.selectById(course.getCategoryId()).getChineseName());
@ -118,31 +119,33 @@ public class CourseServiceImpl extends ServiceImpl<CourseMapper, Course>
List<Chapter> chapters = chapterMapper.selectList(new LambdaQueryWrapper<Chapter>().eq(Chapter::getCourseId, courseId)); List<Chapter> chapters = chapterMapper.selectList(new LambdaQueryWrapper<Chapter>().eq(Chapter::getCourseId, courseId));
List<String> chapterIds = chapters.stream().map(Chapter::getId).toList(); List<String> chapterIds = chapters.stream().map(Chapter::getId).toList();
//TODO // 注意当集合为 空或null , sql会拼接为WHERE (字段名 IN ()), 执行时报错
List<Section> sections = sectionMapper.selectList(new LambdaQueryWrapper<Section>().in(Section::getChapterId, chapterIds));
int videoCount = 0; if (!chapterIds.isEmpty()) {
List<Section> sections = sectionMapper.selectList(new LambdaQueryWrapper<Section>().in(Section::getChapterId, chapterIds));
int videoCount = 0;
int docCount = 0; int docCount = 0;
int totalTimeOfMinute = 0; int totalTimeOfMinute = 0;
for (Section section : sections) { for (Section section : sections) {
if (Objects.equals("video", section.getType())){ if (Objects.equals("video", section.getType())){
videoCount ++; videoCount ++;
totalTimeOfMinute += section.getDuration(); totalTimeOfMinute += section.getDuration();
}else { }else {
docCount ++; docCount ++;
}
} }
courseDetailVO.setVideoCount(videoCount);
courseDetailVO.setDocCount(docCount);
courseDetailVO.setTotalDuration(totalTimeOfMinute / 60);
} }
courseDetailVO.setVideoCount(videoCount);
courseDetailVO.setDocCount(docCount);
courseDetailVO.setTotalDuration(totalTimeOfMinute / 60);
UserCourseRegistration userCourseRegistration = userCourseRegistrationMapper.selectOne(new LambdaQueryWrapper<UserCourseRegistration>() UserCourseRegistration userCourseRegistration = userCourseRegistrationMapper.selectOne(new LambdaQueryWrapper<UserCourseRegistration>()
.eq(UserCourseRegistration::getCourseId, courseId) .eq(UserCourseRegistration::getCourseId, courseId)

View File

@ -0,0 +1,31 @@
package com.guwan.backend.util;
import com.guwan.backend.core.exception.ServiceException;
import ws.schild.jave.EncoderException;
import ws.schild.jave.MultimediaObject;
import java.net.MalformedURLException;
import java.net.URL;
public class VideoDurationUtils {
/**
* 获取视频时长
*/
public static long getVideoDuration(String videoFilePath) {
try {
URL url = new URL(videoFilePath);
MultimediaObject multimediaObject = new MultimediaObject(url);
long duration = (multimediaObject.getInfo().getDuration()) / 1000 / 60;
System.out.println(duration + "分钟");
return duration;
} catch (EncoderException | MalformedURLException e) {
throw new ServiceException("" + e);
}
}
public static void main(String[] args) {
getVideoDuration("http://localhost:9000/file/courseSource/4b0445fb-7d47-45ea-a8f8-fa9f2fa108bd.mp4");
}
}