feat: 🎸 课程接口&用户登录
This commit is contained in:
parent
c08fc2bb79
commit
801e48b660
3
.vscode/settings.json
vendored
Normal file
3
.vscode/settings.json
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"java.configuration.updateBuildConfiguration": "automatic"
|
||||
}
|
@ -21,6 +21,11 @@
|
||||
<groupId>org.jeecgframework.boot</groupId>
|
||||
<artifactId>jeecg-system-local-api</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.jeecgframework.boot</groupId>
|
||||
<artifactId>jeecg-system-biz</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
@ -0,0 +1,39 @@
|
||||
package org.jeecg.modules.biz.constant;
|
||||
|
||||
/**
|
||||
* entity_link 表类型常量定义
|
||||
* source_type:主体类型
|
||||
* target_type:内容类型
|
||||
*/
|
||||
public final class EntityLinkConst {
|
||||
|
||||
private EntityLinkConst() {}
|
||||
|
||||
/** 主体类型 */
|
||||
public static final class SourceType {
|
||||
private SourceType() {}
|
||||
// 课程
|
||||
public static final String COURSE = "course";
|
||||
// 课程分类
|
||||
public static final String COURSE_CATEGORY = "course_category";
|
||||
// 课程章节
|
||||
public static final String COURSE_SECTION = "course_section";
|
||||
// 专题(字典/专题)
|
||||
public static final String SUBJECT = "subject";
|
||||
// 活动
|
||||
public static final String ACTIVITY = "activity";
|
||||
}
|
||||
|
||||
/** 内容类型 */
|
||||
public static final class TargetType {
|
||||
private TargetType() {}
|
||||
// 资源(对应资源表)
|
||||
public static final String RESOURCE = "resource";
|
||||
// 作业(对应作业表)
|
||||
public static final String HOMEWORK = "homework";
|
||||
// 考试(对应考试表)
|
||||
public static final String EXAM = "exam";
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -6,10 +6,12 @@ import lombok.extern.slf4j.Slf4j;
|
||||
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.DictModel;
|
||||
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.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
@ -17,8 +19,12 @@ import org.springframework.web.bind.annotation.RestController;
|
||||
import org.jeecg.common.system.api.ISysBaseAPI;
|
||||
import org.jeecg.modules.biz.service.CourseBizService;
|
||||
import org.jeecg.modules.gen.course.entity.Course;
|
||||
import org.jeecg.modules.gen.coursecategory.entity.CourseCategory;
|
||||
import org.jeecg.modules.gen.coursesection.entity.CourseSection;
|
||||
import org.jeecg.modules.gen.resource.entity.Resource;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
@ -35,7 +41,7 @@ public class CourseBizController {
|
||||
private ISysBaseAPI sysBaseApi;
|
||||
|
||||
@GetMapping("/list")
|
||||
@Operation(summary = "根据分类、难度、专题查询课程列表")
|
||||
@Operation(summary = "查询课程列表", description = "可根据分类、难度、专题进行检索,三个参数可任意传递其中之一、之二或全部,不传参则查询所有课程")
|
||||
@IgnoreAuth
|
||||
public Result<List<Course>> queryCourseList(
|
||||
@RequestParam(value = "categoryId", required = false) String categoryId,
|
||||
@ -45,15 +51,72 @@ public class CourseBizController {
|
||||
return Result.OK(list);
|
||||
}
|
||||
|
||||
@GetMapping("/detail")
|
||||
@Operation(summary = "查询课程详情", description = "根据课程ID查询课程详情")
|
||||
@IgnoreAuth
|
||||
public Result<Course> queryCourseDetail(@RequestParam(value = "id") String id) {
|
||||
Course course = courseBizService.getById(id);
|
||||
return Result.OK(course);
|
||||
}
|
||||
|
||||
@GetMapping("/subject/list")
|
||||
@Operation(summary = "查询课程专题列表", description = "返回字典值")
|
||||
@IgnoreAuth
|
||||
public Result<List<LabelValue>> querySubjectList() {
|
||||
List<DictModel> list = sysBaseApi.getDictItems("course_subject");
|
||||
List<LabelValue> simple = list.stream()
|
||||
.map(d -> new LabelValue(d.getValue(), d.getLabel() != null ? d.getLabel() : d.getText()))
|
||||
.collect(Collectors.toList());
|
||||
return Result.OK(simple);
|
||||
}
|
||||
|
||||
/** 仅返回 value、label 的简单对象 */
|
||||
private static record LabelValue(String value, String label) {
|
||||
}
|
||||
|
||||
@GetMapping("/difficulty/list")
|
||||
@Operation(summary = "查询课程难度列表", description = "返回字典值")
|
||||
@IgnoreAuth
|
||||
public Result<List<LabelValue>> queryDifficultyList() {
|
||||
List<DictModel> list = sysBaseApi.getDictItems("course_difficulty");
|
||||
List<LabelValue> simple = list.stream()
|
||||
.map(d -> new LabelValue(d.getValue(), d.getLabel() != null ? d.getLabel() : d.getText()))
|
||||
.collect(Collectors.toList());
|
||||
return Result.OK(simple);
|
||||
}
|
||||
|
||||
@GetMapping("/category/list")
|
||||
@Operation(summary = "查询课程分类列表", description = "根据sortOrder降序排序")
|
||||
@IgnoreAuth
|
||||
public Result<List<CourseCategory>> queryCategoryList() {
|
||||
List<CourseCategory> list = courseBizService.getCourseCategoryList();
|
||||
return Result.OK(list);
|
||||
}
|
||||
|
||||
@GetMapping("/{courseId}/section")
|
||||
@Operation(summary = "查询课程章节列表", description = "根据课程id查询章节列表,type表示章节类型:0=视频、1=资料、2=考试、3=作业;level表示章节层级,0=一级章节、1=二级章节,通过parentId记录父子关系,前端需自行组织树形结构;当前层级的顺序通过sortOrder排序")
|
||||
@IgnoreAuth
|
||||
public Result<List<CourseSection>> querySectionList(@PathVariable(value = "courseId") String courseId) {
|
||||
List<CourseSection> list = courseBizService.getCourseSectionList(courseId);
|
||||
return Result.OK(list);
|
||||
}
|
||||
|
||||
@GetMapping("/{courseId}/section_video/{sectionId}")
|
||||
@Operation(summary = "查询视频章节详情", description = "该接口需要携带用户登录token。根据章节id查询章节详情,不同类型的章节,返回的内容不同")
|
||||
public Result<List<Resource>> querySectionDetail(@PathVariable(value = "courseId") String courseId, @PathVariable(value = "sectionId") String sectionId) {
|
||||
// TODO 获取用户id,根据courseId判断当前用户是否报名课程,只有已报名的课程才能查看章节详情
|
||||
|
||||
List<Resource> list = courseBizService.getCourseSectionDetail(0, sectionId, Resource.class);
|
||||
return Result.OK(list);
|
||||
}
|
||||
|
||||
@GetMapping("/test")
|
||||
@Operation(summary = "测试")
|
||||
@IgnoreAuth
|
||||
public Result<String> test() {
|
||||
return Result.OK("test");
|
||||
}
|
||||
|
||||
@GetMapping("/test2")
|
||||
@Operation(summary = "测试2")
|
||||
public Result<String> test2(HttpServletRequest request, HttpServletResponse response) {
|
||||
String token = request.getHeader(CommonConstant.X_ACCESS_TOKEN);
|
||||
String username = JwtUtil.getUsername(token);
|
||||
@ -62,7 +125,6 @@ public class CourseBizController {
|
||||
}
|
||||
|
||||
@GetMapping("/test3")
|
||||
@Operation(summary = "测试3")
|
||||
@IgnoreAuth
|
||||
public Result<Long> test3() {
|
||||
long count = courseBizService.count();
|
||||
|
@ -0,0 +1,119 @@
|
||||
package org.jeecg.modules.biz.controller;
|
||||
|
||||
import org.jeecg.config.shiro.IgnoreAuth;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
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 org.jeecg.modules.system.entity.SysUser;
|
||||
import org.jeecg.modules.system.service.*;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
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.common.util.PasswordUtil;
|
||||
import org.jeecg.common.util.RedisUtil;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Tag(name = "用户")
|
||||
@RestController
|
||||
@RequestMapping("/biz/user")
|
||||
@Slf4j
|
||||
public class UserBizController {
|
||||
|
||||
@Autowired
|
||||
private ISysUserService sysUserService;
|
||||
@Autowired
|
||||
private RedisUtil redisUtil;
|
||||
|
||||
@PostMapping("/login")
|
||||
@Operation(summary = "用户登录")
|
||||
@IgnoreAuth
|
||||
public Result<JSONObject> login(@RequestBody Map<String, String> user, HttpServletRequest request) {
|
||||
Result<JSONObject> result = new Result<JSONObject>();
|
||||
String username = user.get("username");
|
||||
String password = user.get("password");
|
||||
if (isLoginFailOvertimes(username)) {
|
||||
return result.error500("该用户登录失败次数过多,请于10分钟后再次登录!");
|
||||
}
|
||||
|
||||
// step.2 校验用户是否存在且有效
|
||||
LambdaQueryWrapper<SysUser> queryWrapper = new LambdaQueryWrapper<>();
|
||||
queryWrapper.eq(SysUser::getUsername,username);
|
||||
SysUser sysUser = sysUserService.getOne(queryWrapper);
|
||||
result = sysUserService.checkUserIsEffective(sysUser);
|
||||
if(!result.isSuccess()) {
|
||||
return result;
|
||||
}
|
||||
|
||||
// step.3 校验用户名或密码是否正确
|
||||
String userpassword = PasswordUtil.encrypt(username, password, sysUser.getSalt());
|
||||
String syspassword = sysUser.getPassword();
|
||||
if (!syspassword.equals(userpassword)) {
|
||||
addLoginFailOvertimes(username);
|
||||
result.error500("用户名或密码错误");
|
||||
return result;
|
||||
}
|
||||
|
||||
// step.4 登录成功获取用户信息
|
||||
JSONObject obj = new JSONObject(new LinkedHashMap<>());
|
||||
//1.生成token
|
||||
String token = JwtUtil.sign(username, syspassword);
|
||||
// 设置token缓存有效时间
|
||||
redisUtil.set(CommonConstant.PREFIX_USER_TOKEN + token, token);
|
||||
redisUtil.expire(CommonConstant.PREFIX_USER_TOKEN + token, JwtUtil.EXPIRE_TIME * 2 / 1000);
|
||||
obj.put("token", token);
|
||||
|
||||
// TODO 查询用户信息
|
||||
|
||||
result.setResult(obj);
|
||||
result.success("登录成功");
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 登录失败超出次数5 返回true
|
||||
*
|
||||
* @param username
|
||||
* @return
|
||||
*/
|
||||
private boolean isLoginFailOvertimes(String username) {
|
||||
String key = CommonConstant.LOGIN_FAIL + username;
|
||||
Object failTime = redisUtil.get(key);
|
||||
if (failTime != null) {
|
||||
Integer val = Integer.parseInt(failTime.toString());
|
||||
if (val > 5) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录登录失败次数
|
||||
* @param username
|
||||
*/
|
||||
private void addLoginFailOvertimes(String username){
|
||||
String key = CommonConstant.LOGIN_FAIL + username;
|
||||
Object failTime = redisUtil.get(key);
|
||||
Integer val = 0;
|
||||
if(failTime!=null){
|
||||
val = Integer.parseInt(failTime.toString());
|
||||
}
|
||||
// 10分钟,一分钟为60s
|
||||
redisUtil.set(key, ++val, 600);
|
||||
}
|
||||
}
|
@ -5,14 +5,16 @@ 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.jeecg.modules.gen.coursecategory.entity.CourseCategory;
|
||||
import org.jeecg.modules.gen.coursesection.entity.CourseSection;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
|
||||
|
||||
/**
|
||||
* 课程业务
|
||||
*/
|
||||
public interface CourseBizService extends IService<TestTable> {
|
||||
public interface CourseBizService extends IService<Course> {
|
||||
|
||||
/**
|
||||
* 上传视频并切片为 HLS(m3u8+ts),按配置(local|minio|alioss)上传,返回 m3u8 的路径/URL
|
||||
@ -31,6 +33,28 @@ public interface CourseBizService extends IService<TestTable> {
|
||||
* @return
|
||||
*/
|
||||
List<Course> getCourseList(String categoryId, String difficulty, String topic);
|
||||
|
||||
/**
|
||||
* 查询课程分类列表
|
||||
* @return
|
||||
*/
|
||||
List<CourseCategory> getCourseCategoryList();
|
||||
|
||||
/**
|
||||
* 查询指定课程下的章节列表
|
||||
* @param courseId
|
||||
* @return
|
||||
*/
|
||||
List<CourseSection> getCourseSectionList(String courseId);
|
||||
|
||||
/**
|
||||
* 查询章节详情(泛型)
|
||||
* @param type 章节类型:0=视频、1=资料、2=考试、3=作业
|
||||
* @param sectionId 章节ID
|
||||
* @param clazz 期望返回的实体类型
|
||||
* @return 指定类型的列表
|
||||
*/
|
||||
<T> List<T> getCourseSectionDetail(Integer type, String sectionId, Class<T> clazz);
|
||||
}
|
||||
|
||||
|
||||
|
@ -1,7 +1,17 @@
|
||||
package org.jeecg.modules.biz.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import org.jeecg.modules.gen.entitylink.entity.EntityLink;
|
||||
|
||||
public interface EntityLinkBizService {
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import java.util.List;
|
||||
|
||||
public interface EntityLinkBizService extends IService<EntityLink>{
|
||||
/**
|
||||
* 根据主体与内容类型查询绑定的 target_id 列表
|
||||
* @param sourceType 主体类型
|
||||
* @param sourceId 主体ID
|
||||
* @param targetType 内容类型
|
||||
* @return target_id 列表(去重)
|
||||
*/
|
||||
List<String> listTargetIds(String sourceType, String sourceId, String targetType);
|
||||
}
|
||||
|
@ -5,11 +5,16 @@ 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.constant.EntityLinkConst;
|
||||
import org.jeecg.modules.biz.service.CourseBizService;
|
||||
import org.jeecg.modules.gen.test.mapper.TestTableMapper;
|
||||
import org.jeecg.modules.biz.service.EntityLinkBizService;
|
||||
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.jeecg.modules.gen.coursecategory.entity.CourseCategory;
|
||||
import org.jeecg.modules.gen.coursecategory.mapper.CourseCategoryMapper;
|
||||
import org.jeecg.modules.gen.coursesection.entity.CourseSection;
|
||||
import org.jeecg.modules.gen.coursesection.mapper.CourseSectionMapper;
|
||||
import org.jeecg.modules.gen.resource.mapper.ResourceMapper;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
@ -29,11 +34,102 @@ import java.util.stream.Stream;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
public class CourseBizServiceImpl extends ServiceImpl<TestTableMapper, TestTable> implements CourseBizService {
|
||||
public class CourseBizServiceImpl extends ServiceImpl<CourseMapper, Course> implements CourseBizService {
|
||||
|
||||
@Autowired
|
||||
private CourseMapper courseMapper;
|
||||
|
||||
@Autowired
|
||||
private CourseCategoryMapper courseCategoryMapper;
|
||||
|
||||
@Autowired
|
||||
private CourseSectionMapper courseSectionMapper;
|
||||
|
||||
@Autowired
|
||||
private EntityLinkBizService entityLinkBizService;
|
||||
|
||||
@Autowired
|
||||
private ResourceMapper resourceMapper;
|
||||
|
||||
@Override
|
||||
public <T> List<T> getCourseSectionDetail(Integer type, String sectionId, Class<T> clazz) {
|
||||
// 1. 查询章节是否存在
|
||||
// 2. 根据章节类型,查询entitylink表,获取关联的实体id
|
||||
// 3. 根据实体id,查询实体表,获取实体详情
|
||||
// 4. 返回实体详情
|
||||
|
||||
// 1. 查询章节是否存在
|
||||
CourseSection section = courseSectionMapper.selectById(sectionId);
|
||||
if (section == null) {
|
||||
throw new RuntimeException("章节不存在");
|
||||
}
|
||||
|
||||
// 2. 根据章节类型,查询entitylink表,获取关联的实体id
|
||||
String sourceType = EntityLinkConst.SourceType.COURSE_SECTION;
|
||||
String sourceId = section.getId();
|
||||
String targetType = null;
|
||||
// 和数据字典对应
|
||||
// 视频和资料章节的区别在于资料可能存在多份资料,而视频只有一份
|
||||
switch (type) {
|
||||
case 0:
|
||||
// 视频章节
|
||||
targetType = EntityLinkConst.TargetType.RESOURCE;
|
||||
break;
|
||||
case 1:
|
||||
// 资料章节
|
||||
targetType = EntityLinkConst.TargetType.RESOURCE;
|
||||
break;
|
||||
case 2:
|
||||
// 考试章节
|
||||
targetType = EntityLinkConst.TargetType.EXAM;
|
||||
break;
|
||||
case 3:
|
||||
// 作业章节
|
||||
targetType = EntityLinkConst.TargetType.HOMEWORK;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
List<String> targetIds = entityLinkBizService.listTargetIds(sourceType, sourceId, targetType);
|
||||
if (targetIds.isEmpty()) {
|
||||
throw new RuntimeException("章节没有关联的实体");
|
||||
}
|
||||
|
||||
// 3. 根据实体id,查询实体表,获取实体详情
|
||||
List<T> result = new ArrayList<>();
|
||||
for (String targetId : targetIds) {
|
||||
switch (type) {
|
||||
case 0:
|
||||
// 视频章节
|
||||
result.add(clazz.cast(resourceMapper.selectById(targetId)));
|
||||
break;
|
||||
case 1:
|
||||
// 资料章节
|
||||
result.add(clazz.cast(resourceMapper.selectById(targetId)));
|
||||
break;
|
||||
case 2:
|
||||
// TODO 考试章节
|
||||
break;
|
||||
case 3:
|
||||
// TODO 作业章节
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 4. 返回实体详情
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<CourseSection> getCourseSectionList(String courseId) {
|
||||
return courseSectionMapper.selectList(new QueryWrapper<CourseSection>().eq("course_id", courseId));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<CourseCategory> getCourseCategoryList() {
|
||||
return courseCategoryMapper.selectList(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Course> getCourseList(String categoryId, String difficulty, String topic) {
|
||||
QueryWrapper<Course> queryWrapper = new QueryWrapper<>();
|
||||
|
@ -0,0 +1,26 @@
|
||||
package org.jeecg.modules.biz.service.impl;
|
||||
|
||||
import org.jeecg.modules.biz.service.EntityLinkBizService;
|
||||
import org.jeecg.modules.gen.entitylink.entity.EntityLink;
|
||||
import org.jeecg.modules.gen.entitylink.mapper.EntityLinkMapper;
|
||||
import org.springframework.stereotype.Service;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Service
|
||||
public class EntityLinkBizServiceImpl extends ServiceImpl<EntityLinkMapper, EntityLink> implements EntityLinkBizService {
|
||||
@Override
|
||||
public List<String> listTargetIds(String sourceType, String sourceId, String targetType) {
|
||||
LambdaQueryWrapper<EntityLink> qw = new LambdaQueryWrapper<>();
|
||||
qw.eq(EntityLink::getSourceType, sourceType)
|
||||
.eq(EntityLink::getSourceId, sourceId)
|
||||
.eq(EntityLink::getTargetType, targetType)
|
||||
.select(EntityLink::getTargetId);
|
||||
return this.list(qw).stream()
|
||||
.map(EntityLink::getTargetId)
|
||||
.distinct()
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user