diff --git a/src/main/java/com/zyx/controller/HelloController.java b/src/main/java/com/zyx/controller/HelloController.java
deleted file mode 100644
index 5fcccaa..0000000
--- a/src/main/java/com/zyx/controller/HelloController.java
+++ /dev/null
@@ -1,18 +0,0 @@
-package com.zyx.controller;
-
-import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.RestController;
-
-/**
- * @author Yaxi.Zhang
- * @since 2022/10/15 07:58
- */
-@RestController
-public class HelloController {
-
- @GetMapping("/hello")
- public String hello() {
- return "Hello Jenkins";
- }
-
-}
diff --git a/src/main/java/com/zyx/HelloApp.java b/src/main/java/com/zyx/mpdemo/MpDemoApp.java
similarity index 56%
rename from src/main/java/com/zyx/HelloApp.java
rename to src/main/java/com/zyx/mpdemo/MpDemoApp.java
index 7234fee..e3926ef 100644
--- a/src/main/java/com/zyx/HelloApp.java
+++ b/src/main/java/com/zyx/mpdemo/MpDemoApp.java
@@ -1,15 +1,13 @@
-package com.zyx;
+package com.zyx.mpdemo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
-/**
- * @author Yaxi.Zhang
- * @since 2022/10/15 07:56
- */
@SpringBootApplication
-public class HelloApp {
+public class MpDemoApp {
+
public static void main(String[] args) {
- SpringApplication.run(HelloApp.class, args);
+ SpringApplication.run(MpDemoApp.class, args);
}
+
}
diff --git a/src/main/java/com/zyx/mpdemo/common/constant/ResponseCode.java b/src/main/java/com/zyx/mpdemo/common/constant/ResponseCode.java
new file mode 100644
index 0000000..6faffa1
--- /dev/null
+++ b/src/main/java/com/zyx/mpdemo/common/constant/ResponseCode.java
@@ -0,0 +1,42 @@
+package com.zyx.mpdemo.common.constant;
+
+/**
+ * @author Yaxi.Zhang
+ * @since 2022/10/1 08:19
+ */
+public class ResponseCode {
+ // ==================================== HTPP状态码开始 ====================================
+ /** 200(成功) 服务器已成功处理了请求*/
+ public static final int RESPONSE_CODE_200 = 200;
+ /** 201 服务响应码--失败 */
+ public static final int RESPONSE_CODE_201 = 201;
+ /** 202(已接受,但未处理) 服务器已接受请求,但尚未处理 */
+ public static final int RESPONSE_CODE_202 = 202;
+ /** 204(无内容) 服务器成功处理了请求,但没有返回任何内容。 */
+ public static final int RESPONSE_CODE_204 = 204;
+ /** 206(部分内容) 服务器成功处理了部分 GET 请求。 类似于 FlashGet
+ * 或者迅雷这类的 HTTP 下载工具都是使用此类响应实现断点续传或者将一个大文档分解为多个下载段同时下载。 */
+ public static final int RESPONSE_CODE_206 = 206;
+ /** (语法错误请求) 服务器不理解请求的语法。 1、语义有误,当前请求无法被服务器理解。除非进行修改,否则客户端不应该重复提交这个请求。2、请求参数有误。 */
+ public static final int RESPONSE_CODE_400 = 400;
+ /** 401(未授权) 请求要求身份验证。 对于需要登录的网页,服务器可能返回此响应。 */
+ public static final int RESPONSE_CODE_401 = 401;
+ /** 403(禁止) 服务器拒绝请求。服务器理解客户的请求,但拒绝处理它。通常由于服务器上文件或目录的权限设置导致。 */
+ public static final int RESPONSE_CODE_403 = 403;
+ /** 404(未找到) 服务器找不到请求的网页。 例如,对于服务器上不存在的网页经常会返回此代码。 */
+ public static final int RESPONSE_CODE_404 = 404;
+ /** 408(请求超时) 在服务器许可的等待时间内,客户一直没有发出任何请求。客户可以在以后重复同一请求。 */
+ public static final int RESPONSE_CODE_408 = 408;
+ /** 411(需要有效长度) 服务器不能处理请求,除非客户发送一个Content-Length头。 */
+ public static final int RESPONSE_CODE_411 = 411;
+ /** 414(请求的 URI 过长) 请求的 URI(通常为网址)过长,服务器无法处理。 */
+ public static final int RESPONSE_CODE_414 = 414;
+ /** 415(不支持的媒体类型) 请求的格式不受请求页面的支持。 */
+ public static final int RESPONSE_CODE_415 = 415;
+ /** 416(请求范围不符合要求) 如果页面无法提供请求的范围,则服务器会返回此状态代码。 */
+ public static final int RESPONSE_CODE_416 = 416;
+ /** 500 内部应用程序错误 */
+ public static final int RESPONSE_CODE_500 = 500;
+ // ==================================== HTPP状态码结束 ====================================
+
+}
diff --git a/src/main/java/com/zyx/mpdemo/common/exception/BusinessException.java b/src/main/java/com/zyx/mpdemo/common/exception/BusinessException.java
new file mode 100644
index 0000000..10d5f76
--- /dev/null
+++ b/src/main/java/com/zyx/mpdemo/common/exception/BusinessException.java
@@ -0,0 +1,21 @@
+package com.zyx.mpdemo.common.exception;
+
+
+import com.zyx.mpdemo.common.constant.ResponseCode;
+import com.zyx.mpdemo.common.exception.base.BaseException;
+
+/**
+ * @author Yaxi.Zhang
+ * @since 2022/10/1 08:17
+ */
+public class BusinessException extends BaseException {
+
+ public BusinessException(String message) {
+ super(message);
+ }
+
+ public BusinessException() {
+ super(ResponseCode.RESPONSE_CODE_500, "内部系统错误");
+ }
+
+}
diff --git a/src/main/java/com/zyx/mpdemo/common/exception/base/BaseException.java b/src/main/java/com/zyx/mpdemo/common/exception/base/BaseException.java
new file mode 100644
index 0000000..0f2d61a
--- /dev/null
+++ b/src/main/java/com/zyx/mpdemo/common/exception/base/BaseException.java
@@ -0,0 +1,47 @@
+package com.zyx.mpdemo.common.exception.base;
+
+
+import com.zyx.mpdemo.common.constant.ResponseCode;
+
+/**
+ * 自定义异常基类
+ *
+ * @author Yaxi.Zhang
+ * @since 2022/10/1 08:13
+ */
+public abstract class BaseException extends RuntimeException {
+
+ private int code = ResponseCode.RESPONSE_CODE_200;
+
+ public int getCode() {
+ return code;
+ }
+
+ public void setCode(int code) {
+ this.code = code;
+ }
+
+ public BaseException(int code, String message) {
+ super(message);
+ this.code = code;
+ }
+
+ public BaseException(String message) {
+ super(message);
+ }
+
+ public BaseException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public BaseException(Throwable cause) {
+ super(cause);
+ }
+
+ public BaseException(String message, Throwable cause,
+ boolean enableSuppression,
+ boolean writableStackTrace) {
+ super(message, cause, enableSuppression, writableStackTrace);
+ }
+
+}
diff --git a/src/main/java/com/zyx/mpdemo/common/exception/code/IErrorCode.java b/src/main/java/com/zyx/mpdemo/common/exception/code/IErrorCode.java
new file mode 100644
index 0000000..2f5e323
--- /dev/null
+++ b/src/main/java/com/zyx/mpdemo/common/exception/code/IErrorCode.java
@@ -0,0 +1,14 @@
+package com.zyx.mpdemo.common.exception.code;
+
+/**
+ *
封装Response的错误码
+ *
+ * @author Yaxi.Zhang
+ * @since 2022/10/7 15:35
+ */
+public interface IErrorCode {
+
+ Integer getCode();
+ String getMessage();
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/zyx/mpdemo/common/exception/code/ResultCode.java b/src/main/java/com/zyx/mpdemo/common/exception/code/ResultCode.java
new file mode 100644
index 0000000..405ebd9
--- /dev/null
+++ b/src/main/java/com/zyx/mpdemo/common/exception/code/ResultCode.java
@@ -0,0 +1,46 @@
+package com.zyx.mpdemo.common.exception.code;
+
+import lombok.AllArgsConstructor;
+import org.springframework.http.HttpStatus;
+
+/**
+ * 枚举常用Response操作码
+ */
+@AllArgsConstructor
+public enum ResultCode implements IErrorCode {
+
+ SUCCESS(HttpStatus.OK.value(), "操作成功!"),
+ FAILED(HttpStatus.INTERNAL_SERVER_ERROR.value(), "操作失败!"),
+
+ AUTHENTICATION_FAILED(50001, "获取token失败!"),
+ AUTH_SERVER_ERROR(50002, "授权服务异常!"),
+ LOGIN_METHOD_IS_EMPTY(50003, "登录方式不能为空!"),
+ LOGIN_METHOD_IS_INVALID(50004, "登录方式只支持password模式!"),
+
+ UNAUTHORIZED(40001, "身份认证失败!"),
+ VALIDATE_FAILED(40002, "Token参数校验失败,请检查Token是否正确!"),
+ FORBIDDEN(40003, "无权限请求,请检测当前用户身份信息!"),
+ ACCOUNT_IS_EMPTY(40004, "账号不能为空!"),
+ PASSWORD_IS_EMPTY(40005, "密码不能为空!");
+
+ /**
+ * 消息码
+ */
+ private final Integer code;
+
+ /**
+ * 消息体
+ */
+ private final String message;
+
+ @Override
+ public Integer getCode() {
+ return code;
+ }
+
+ @Override
+ public String getMessage() {
+ return message;
+ }
+
+}
diff --git a/src/main/java/com/zyx/mpdemo/common/resp/CommonResponse.java b/src/main/java/com/zyx/mpdemo/common/resp/CommonResponse.java
new file mode 100644
index 0000000..9fe90df
--- /dev/null
+++ b/src/main/java/com/zyx/mpdemo/common/resp/CommonResponse.java
@@ -0,0 +1,128 @@
+package com.zyx.mpdemo.common.resp;
+
+import com.zyx.mpdemo.common.exception.code.IErrorCode;
+import com.zyx.mpdemo.common.exception.code.ResultCode;
+import com.zyx.mpdemo.common.resp.base.BaseResponse;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+
+/**
+ * @author Yaxi.Zhang
+ * @since 2022/10/1 08:32
+ */
+@EqualsAndHashCode(callSuper = true)
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class CommonResponse extends BaseResponse implements Serializable {
+
+ private T data;
+
+ public CommonResponse(Integer code, String message) {
+ this(code, message, null);
+ }
+
+ public CommonResponse(Integer code, String message, T data) {
+ this.code = code;
+ this.message = message;
+ this.data = data;
+ }
+
+ /**
+ * 成功返回结果
+ */
+ public static CommonResponse success(T data) {
+ return new CommonResponse<>(ResultCode.SUCCESS.getCode(), ResultCode.SUCCESS.getMessage(), data);
+ }
+
+ /**
+ * 成功返回结果
+ */
+ public static CommonResponse success(T data, String message) {
+ return new CommonResponse<>(ResultCode.SUCCESS.getCode(), message, data);
+ }
+
+ /**
+ * 失败返回结果
+ */
+ public static CommonResponse failed(IErrorCode errorCode) {
+ return new CommonResponse<>(errorCode.getCode(), errorCode.getMessage(), null);
+ }
+
+ /**
+ * 失败返回结果
+ */
+ public static CommonResponse failed(IErrorCode errorCode, String message) {
+ return new CommonResponse<>(errorCode.getCode(), message, null);
+ }
+
+ /**
+ * 失败返回结果
+ */
+ public static CommonResponse failed(String message) {
+ return new CommonResponse<>(ResultCode.FAILED.getCode(), message, null);
+ }
+
+ /**
+ * 失败返回结果
+ */
+ public static CommonResponse failed(Integer code, String message) {
+ return new CommonResponse<>(code, message, null);
+ }
+
+ /**
+ * 失败返回结果
+ */
+ public static CommonResponse failed() {
+ return failed(ResultCode.FAILED);
+ }
+
+ /**
+ * 参数验证失败返回结果
+ */
+ public static CommonResponse validateFailed() {
+ return failed(ResultCode.VALIDATE_FAILED);
+ }
+
+ /**
+ * 参数验证失败返回结果
+ */
+ public static CommonResponse validateFailed(String message) {
+ return new CommonResponse<>(ResultCode.VALIDATE_FAILED.getCode(),
+ String.format("%s,%s", message, ResultCode.VALIDATE_FAILED.getMessage()), null);
+ }
+
+ /**
+ * 身份验证失败返回结果
+ */
+ public static CommonResponse authenticationFailed(String message) {
+ return new CommonResponse(ResultCode.AUTHENTICATION_FAILED.getCode(),
+ String.format("%s,%s", message, ResultCode.AUTHENTICATION_FAILED.getMessage()), null);
+ }
+
+ /**
+ * Token已经过期
+ */
+ public static CommonResponse authenticationExpired(String message) {
+ return new CommonResponse<>(ResultCode.UNAUTHORIZED.getCode(), String.format("%s,%s", message, ResultCode.UNAUTHORIZED.getMessage()), null);
+ }
+
+
+ /**
+ * Token无效返回结果
+ */
+ public static CommonResponse unauthorized(T data) {
+ return new CommonResponse<>(ResultCode.UNAUTHORIZED.getCode(), ResultCode.UNAUTHORIZED.getMessage(), data);
+ }
+
+ /**
+ * 未授权返回结果
+ */
+ public static CommonResponse forbidden(T data) {
+ return new CommonResponse<>(ResultCode.FORBIDDEN.getCode(), ResultCode.FORBIDDEN.getMessage(), data);
+ }
+}
diff --git a/src/main/java/com/zyx/mpdemo/common/resp/base/BaseResponse.java b/src/main/java/com/zyx/mpdemo/common/resp/base/BaseResponse.java
new file mode 100644
index 0000000..8df4bd5
--- /dev/null
+++ b/src/main/java/com/zyx/mpdemo/common/resp/base/BaseResponse.java
@@ -0,0 +1,25 @@
+package com.zyx.mpdemo.common.resp.base;
+
+
+import com.zyx.mpdemo.common.constant.ResponseCode;
+import lombok.Data;
+
+/**
+ * @author Yaxi.Zhang
+ * @since 2022/10/1 08:32
+ */
+@Data
+public class BaseResponse {
+
+ protected int code = ResponseCode.RESPONSE_CODE_200;
+ protected String message;
+
+ public BaseResponse() {
+ }
+
+ public BaseResponse(int status, String message) {
+ this.code = status;
+ this.message = message;
+ }
+
+}
diff --git a/src/main/java/com/zyx/mpdemo/config/MyBatisPlusConfig.java b/src/main/java/com/zyx/mpdemo/config/MyBatisPlusConfig.java
new file mode 100644
index 0000000..0ce6c00
--- /dev/null
+++ b/src/main/java/com/zyx/mpdemo/config/MyBatisPlusConfig.java
@@ -0,0 +1,39 @@
+package com.zyx.mpdemo.config;
+
+import com.baomidou.mybatisplus.annotation.DbType;
+import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
+import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;
+import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
+import com.zyx.mpdemo.helpers.injector.EasySqlInjector;
+import org.mybatis.spring.annotation.MapperScan;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * @author Yaxi.Zhang
+ * @since 2022/3/29 10:56
+ */
+@Configuration
+// 扫描mapper接口所在的包,可以从启动类移动到该配置类
+@MapperScan("com.zyx.mpdemo.mapper")
+public class MyBatisPlusConfig {
+ @Bean
+ public MybatisPlusInterceptor mybatisPlusInterceptor(){
+ MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
+ // 添加分页插件
+ interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
+ // 添加乐观锁插件
+ interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
+ return interceptor;
+ }
+
+ /**
+ * 定义核心配置类注入EasySqlInjector
+ * 让mybatis-plus可以使用InsertBatchSomeColumn方法进行批量插入
+ */
+ @Bean
+ public EasySqlInjector sqlInjector() {
+ return new EasySqlInjector();
+ }
+
+}
diff --git a/src/main/java/com/zyx/mpdemo/config/MyMetaObjectHandler.java b/src/main/java/com/zyx/mpdemo/config/MyMetaObjectHandler.java
new file mode 100644
index 0000000..221c28c
--- /dev/null
+++ b/src/main/java/com/zyx/mpdemo/config/MyMetaObjectHandler.java
@@ -0,0 +1,41 @@
+package com.zyx.mpdemo.config;
+
+import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.ibatis.reflection.MetaObject;
+import org.springframework.stereotype.Component;
+
+import java.util.Calendar;
+import java.util.Date;
+
+/**
+ * MyBatisPlus自动填充
+ *
+ * @author Yaxi.Zhang
+ * @since 2022/11/21 10:27
+ */
+@Slf4j
+@Component
+public class MyMetaObjectHandler implements MetaObjectHandler {
+
+ @Override
+ public void insertFill(MetaObject metaObject) {
+ // log.info("start insert fill ....");
+ this.strictInsertFill(metaObject, "createTime", Date.class, Calendar.getInstance().getTime()); // 起始版本 3.3.0(推荐使用)
+ this.strictInsertFill(metaObject, "updateTime", Date.class, Calendar.getInstance().getTime()); // 起始版本 3.3.0(推荐使用)
+ // 或者
+ // this.strictInsertFill(metaObject, "createTime", () -> Calendar.getInstance().getTime(), Date.class); // 起始版本 3.3.3(推荐)
+ // 或者
+ // this.fillStrategy(metaObject, "createTime", Calendar.getInstance().getTime()); // 也可以使用(3.3.0 该方法有bug)
+ }
+
+ @Override
+ public void updateFill(MetaObject metaObject) {
+ // log.info("start update fill ....");
+ this.strictUpdateFill(metaObject, "updateTime", Date.class, Calendar.getInstance().getTime()); // 起始版本 3.3.0(推荐)
+ // 或者
+ // this.strictUpdateFill(metaObject, "updateTime", () -> Calendar.getInstance().getTime(), Date.class); // 起始版本 3.3.3(推荐)
+ // 或者
+ // this.fillStrategy(metaObject, "updateTime", Calendar.getInstance().getTime()); // 也可以使用(3.3.0 该方法有bug)
+ }
+}
diff --git a/src/main/java/com/zyx/mpdemo/config/ObjectWrapperFactoryConverter.java b/src/main/java/com/zyx/mpdemo/config/ObjectWrapperFactoryConverter.java
new file mode 100644
index 0000000..7ff0414
--- /dev/null
+++ b/src/main/java/com/zyx/mpdemo/config/ObjectWrapperFactoryConverter.java
@@ -0,0 +1,25 @@
+package com.zyx.mpdemo.config;
+
+import org.apache.ibatis.reflection.wrapper.ObjectWrapperFactory;
+import org.springframework.boot.context.properties.ConfigurationPropertiesBinding;
+import org.springframework.core.convert.converter.Converter;
+import org.springframework.stereotype.Component;
+
+/**
+ * @author Yaxi.Zhang
+ * @since 2022/3/28 18:09
+ * reference: https://www.cxyzjd.com/article/qq_39720208/104631573
+ */
+
+@Component
+@ConfigurationPropertiesBinding
+public class ObjectWrapperFactoryConverter implements Converter {
+ @Override
+ public ObjectWrapperFactory convert(String source) {
+ try {
+ return (ObjectWrapperFactory) Class.forName(source).newInstance();
+ } catch (InstantiationException | IllegalAccessException | ClassNotFoundException e) {
+ throw new RuntimeException(e);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/zyx/mpdemo/controller/CourseController.java b/src/main/java/com/zyx/mpdemo/controller/CourseController.java
new file mode 100644
index 0000000..24f6498
--- /dev/null
+++ b/src/main/java/com/zyx/mpdemo/controller/CourseController.java
@@ -0,0 +1,69 @@
+package com.zyx.mpdemo.controller;
+
+import com.zyx.mpdemo.common.resp.CommonResponse;
+import com.zyx.mpdemo.model.req.BatchLearnedReq;
+import com.zyx.mpdemo.model.req.CourseDetailReq;
+import com.zyx.mpdemo.model.req.CourseListReq;
+import com.zyx.mpdemo.model.vo.CourseDetailVO;
+import com.zyx.mpdemo.model.vo.CourseInfoVO;
+import com.zyx.mpdemo.model.vo.CourseListVO;
+import com.zyx.mpdemo.service.ICourseService;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Controller;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import javax.annotation.Resource;
+import java.util.List;
+
+/**
+ * 课程相关接口
+ *
+ * @author Yaxi.Zhang
+ * @since 2023/1/26 08:52
+ */
+@Slf4j
+@Controller
+@ResponseBody
+@RequestMapping("/course")
+public class CourseController {
+
+ @Resource
+ private ICourseService courseService;
+
+ /**
+ * 查询所有课程名称以及章节号
+ *
+ * @return 所有课程名称以及章节号
+ */
+ @GetMapping("/course-infos")
+ public CommonResponse> getCourseInfo() {
+ return CommonResponse.success(courseService.getCourseInfo(), "查询所有课程名称以及章节号成功");
+ }
+
+ /**
+ * 依据课程id查询课程列表
+ */
+ @PostMapping("/course-list")
+ public CommonResponse getCourseList(@Validated @RequestBody CourseListReq courseListReq) {
+ return CommonResponse.success(courseService.getCourseList(courseListReq), "查询课程列表成功");
+ }
+
+ /**
+ * 查询课程详情
+ */
+ @PostMapping("/course-detail")
+ public CommonResponse> getCourseDetail(@Validated @RequestBody CourseDetailReq courseDetailReq) {
+ return CommonResponse.success(courseService.getCourseDetail(courseDetailReq), "查询课程详情成功");
+ }
+
+ /**
+ * 批量修改课程为已学完
+ */
+ @PutMapping("/batch-learned")
+ public CommonResponse batchLearned(@Validated @RequestBody BatchLearnedReq batchLearnedReq) {
+ courseService.batchLearned(batchLearnedReq);
+ return CommonResponse.success(null, "批量修改课程为已学完成功");
+ }
+
+}
diff --git a/src/main/java/com/zyx/mpdemo/helpers/handler/GlobalExceptionHandler.java b/src/main/java/com/zyx/mpdemo/helpers/handler/GlobalExceptionHandler.java
new file mode 100644
index 0000000..17aabd8
--- /dev/null
+++ b/src/main/java/com/zyx/mpdemo/helpers/handler/GlobalExceptionHandler.java
@@ -0,0 +1,64 @@
+package com.zyx.mpdemo.helpers.handler;
+
+
+import cn.hutool.core.collection.CollUtil;
+import com.zyx.mpdemo.common.constant.ResponseCode;
+import com.zyx.mpdemo.common.exception.BusinessException;
+import com.zyx.mpdemo.common.exception.code.ResultCode;
+import com.zyx.mpdemo.common.resp.CommonResponse;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.validation.ObjectError;
+import org.springframework.web.bind.MethodArgumentNotValidException;
+import org.springframework.web.bind.annotation.ControllerAdvice;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.ResponseBody;
+
+import java.lang.reflect.Executable;
+import java.util.List;
+import java.util.Objects;
+import java.util.stream.Collectors;
+
+/**
+ * 全局异常捕获
+ *
+ * @author Yaxi.Zhang
+ * @since 2022/10/1 08:27
+ */
+@Slf4j
+@ControllerAdvice
+public class GlobalExceptionHandler {
+
+ @ResponseBody
+ @ExceptionHandler(value = Exception.class)
+ public CommonResponse invalidCommon(Exception ex) {
+ log.error("全局异常捕获", ex);
+ return new CommonResponse<>(ResponseCode.RESPONSE_CODE_500, "全局异常捕获:" + ex.getMessage(), null);
+ }
+
+ @ResponseBody
+ @ExceptionHandler(value = BusinessException.class)
+ public CommonResponse invalidBusiness(BusinessException ex) {
+ log.error("业务异常捕获", ex);
+ return new CommonResponse<>(ex.getCode(), ex.getMessage(), null);
+ }
+
+
+ @ResponseBody
+ @ExceptionHandler(value = MethodArgumentNotValidException.class)
+ public CommonResponse methodArgumentNotValidException(MethodArgumentNotValidException ex) {
+ log.error("全局异常捕获", ex);
+ List allErrors = ex.getBindingResult().getAllErrors();
+ String errorStr = "";
+ if (CollUtil.isNotEmpty(allErrors)) {
+ errorStr = allErrors.stream().map(ObjectError::getDefaultMessage).collect(Collectors.joining(","));
+ }
+ Executable executable = ex.getParameter().getParameter().getDeclaringExecutable();
+ if (Objects.nonNull(executable)) {
+ String clazz = executable.getDeclaringClass().getName();
+ String method = executable.getName();
+ log.error("{}#{} 参数校验失败: [{}]", clazz, method, errorStr);
+ }
+ return new CommonResponse<>(ResultCode.FAILED.getCode(), "参数校验失败:" + errorStr, null);
+ }
+
+}
diff --git a/src/main/java/com/zyx/mpdemo/helpers/injector/EasySqlInjector.java b/src/main/java/com/zyx/mpdemo/helpers/injector/EasySqlInjector.java
new file mode 100644
index 0000000..28e8ad5
--- /dev/null
+++ b/src/main/java/com/zyx/mpdemo/helpers/injector/EasySqlInjector.java
@@ -0,0 +1,25 @@
+package com.zyx.mpdemo.helpers.injector;
+
+import com.baomidou.mybatisplus.core.injector.AbstractMethod;
+import com.baomidou.mybatisplus.core.injector.DefaultSqlInjector;
+import com.baomidou.mybatisplus.core.metadata.TableInfo;
+import com.baomidou.mybatisplus.extension.injector.methods.InsertBatchSomeColumn;
+
+import java.util.List;
+
+/**
+ * mybatis-plus添加SQL注入器
+ *
+ * @author Yaxi.Zhang
+ * @since 2022/12/7 10:40
+ */
+public class EasySqlInjector extends DefaultSqlInjector {
+ @Override
+ public List getMethodList(Class> mapperClass, TableInfo tableInfo) {
+ // 注意:此SQL注入器继承了DefaultSqlInjector(默认注入器)
+ // 调用了DefaultSqlInjector的getMethodList方法,保留了mybatis-plus的自带方法
+ List methodList = super.getMethodList(mapperClass, tableInfo);
+ methodList.add(new InsertBatchSomeColumn());
+ return methodList;
+ }
+}
diff --git a/src/main/java/com/zyx/mpdemo/helpers/utils/CourseStatisticsUtils.java b/src/main/java/com/zyx/mpdemo/helpers/utils/CourseStatisticsUtils.java
new file mode 100644
index 0000000..034c964
--- /dev/null
+++ b/src/main/java/com/zyx/mpdemo/helpers/utils/CourseStatisticsUtils.java
@@ -0,0 +1,31 @@
+package com.zyx.mpdemo.helpers.utils;
+
+import com.zyx.mpdemo.model.entity.Course;
+import com.zyx.mpdemo.model.vo.CourseCost;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * 课程统计工具类
+ *
+ * @author Yaxi.Zhang
+ * @since 2022/11/21 14:23
+ */
+public class CourseStatisticsUtils {
+
+ /**
+ * 依据课程列表获取课程时间
+ */
+ public static CourseCost getCourseCost(List courses) {
+ List courseCostList = courses.stream()
+ .map(it -> new CourseCost().setHours(it.getCourseHours()).setMinutes(it.getCourseMinutes()).setSeconds(it.getCourseSeconds()))
+ .collect(Collectors.toList());
+ return CourseCost.fromList(courseCostList);
+ }
+
+ public static double getLearnedPercentage(CourseCost learnedCourse, CourseCost allCourse) {
+ return learnedCourse.getCourseSeconds() * 1.0 / allCourse.getCourseSeconds();
+ }
+
+}
diff --git a/src/main/java/com/zyx/mpdemo/helpers/utils/GenerateSqlFromEntityUtil.java b/src/main/java/com/zyx/mpdemo/helpers/utils/GenerateSqlFromEntityUtil.java
new file mode 100644
index 0000000..0855a88
--- /dev/null
+++ b/src/main/java/com/zyx/mpdemo/helpers/utils/GenerateSqlFromEntityUtil.java
@@ -0,0 +1,160 @@
+package com.zyx.mpdemo.helpers.utils;
+
+import com.zyx.mpdemo.model.entity.Product;
+
+import javax.xml.bind.annotation.XmlElement;
+import java.io.*;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Field;
+
+/**
+ * @author Yaxi.Zhang
+ * @since 2022/9/2 16:58
+ */
+public class GenerateSqlFromEntityUtil {
+ public static void main(String[] a) {
+
+ // 实体类的位置
+ Class klass = Product.class;
+ // 生成的sql语句的位置
+ String outputPath = "D:/outSql/Product.txt";
+ generateTableSql(klass, outputPath, null);
+ System.out.println("生成结束");
+ }
+
+ public static void writeFile(String content, String outputPath) {
+
+ File file = new File(outputPath);
+ System.out.println("文件路径: " + file.getAbsolutePath());
+ // 输出文件的路径
+ if (!file.getParentFile().exists()) {
+
+ file.getParentFile().mkdirs();
+ }
+ FileOutputStream fos = null;
+ OutputStreamWriter osw = null;
+ BufferedWriter out = null;
+
+ try {
+
+ // 如果文件存在,就删除
+ if (file.exists()) {
+
+ file.delete();
+ }
+ file.createNewFile();
+ fos = new FileOutputStream(file, true);
+ osw = new OutputStreamWriter(fos);
+ out = new BufferedWriter(osw);
+ out.write(content);
+ // 清空缓冲流,把缓冲流里的文本数据写入到目标文件里
+ out.flush();
+ } catch (FileNotFoundException e) {
+
+ e.printStackTrace();
+ } catch (IOException e) {
+ e.printStackTrace();
+ } finally {
+ try {
+ fos.close();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ try {
+ osw.close();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+
+ try {
+ out.close();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+
+ public static void generateTableSql(Class obj, String outputPath, String tableName) {
+
+ // tableName 如果是 null,就用类名做表名
+ if (tableName == null || tableName.equals("")) {
+ tableName = obj.getName();
+ tableName = tableName.substring(tableName.lastIndexOf(".") + 1);
+ }
+ // 表名用大写字母
+ tableName = tableName.toUpperCase();
+
+ Field[] fields = obj.getDeclaredFields();
+ Object param;
+ String column;
+
+ StringBuilder sb = new StringBuilder();
+
+ sb.append("drop table if exists ").append(tableName).append(";\r\n");
+
+ sb.append("\r\n");
+
+ sb.append("create table ").append(tableName).append("(\r\n");
+
+ System.out.println(tableName);
+
+ boolean firstId = true;
+
+ for (int i = 0; i < fields.length; i++) {
+
+ Field f = fields[i];
+
+ column = f.getName();
+
+ System.out.println(column + ", " + f.getType().getSimpleName());
+
+ param = f.getType();
+ sb.append(column); // 一般第一个是主键
+
+ if (param instanceof Integer) {
+ sb.append(" INTEGER ");
+ } else {
+ // 注意:根据需要,自行修改 varchar 的长度。这里设定为长度等于 50
+ int length = 50;
+ sb.append(" VARCHAR(" + length + ")");
+ }
+
+ if (firstId == true) {
+
+ sb.append(" PRIMARY KEY ");
+ firstId = false;
+ }
+
+ // 获取字段中包含 fieldMeta 的注解
+
+ // 获取属性的所有注释
+ Annotation[] allAnnotations = f.getAnnotations();
+
+ XmlElement xmlElement = null;
+ Class annotationType = null;
+
+ for (Annotation an : allAnnotations) {
+
+ sb.append(" COMMIT '");
+ xmlElement = (XmlElement) an;
+ annotationType = an.annotationType();
+ param = ((XmlElement) an).name();
+ System.out.println("属性 " + f.getName() + " ----- 的注释类型有: " + param);
+ sb.append(param).append("'");
+ }
+
+ if (i != fields.length - 1) {
+ // 最后一个属性后面不加逗号
+ sb.append(", ");
+ }
+
+ sb.append("\n");
+ }
+
+ String sql = sb.toString();
+
+ sql = sb.substring(0, sql.length() - 1) + "\n) " + "ENGINE = INNODB DEFAULT CHARSET = utf8;";
+
+ writeFile(sql, outputPath);
+ }
+}
diff --git a/src/main/java/com/zyx/mpdemo/mapper/CourseMapper.java b/src/main/java/com/zyx/mpdemo/mapper/CourseMapper.java
new file mode 100644
index 0000000..1dc7491
--- /dev/null
+++ b/src/main/java/com/zyx/mpdemo/mapper/CourseMapper.java
@@ -0,0 +1,20 @@
+package com.zyx.mpdemo.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.zyx.mpdemo.model.entity.Course;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * 针对表【t_course】的数据库操作Mapper
+ *
+ * @author zhangyaxi
+ * @since 2022-11-21 12:12
+ */
+@Mapper
+public interface CourseMapper extends BaseMapper {
+
+}
+
+
+
+
diff --git a/src/main/java/com/zyx/mpdemo/mapper/ProductMapper.java b/src/main/java/com/zyx/mpdemo/mapper/ProductMapper.java
new file mode 100644
index 0000000..248a9e8
--- /dev/null
+++ b/src/main/java/com/zyx/mpdemo/mapper/ProductMapper.java
@@ -0,0 +1,15 @@
+package com.zyx.mpdemo.mapper;
+
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.zyx.mpdemo.model.entity.Product;
+import org.springframework.stereotype.Repository;
+
+/**
+ * Date:2022/2/15
+ * Author:ybc
+ * Description:
+ */
+@Repository
+public interface ProductMapper extends BaseMapper {
+}
diff --git a/src/main/java/com/zyx/mpdemo/mapper/UserMapper.java b/src/main/java/com/zyx/mpdemo/mapper/UserMapper.java
new file mode 100644
index 0000000..ce55a00
--- /dev/null
+++ b/src/main/java/com/zyx/mpdemo/mapper/UserMapper.java
@@ -0,0 +1,49 @@
+package com.zyx.mpdemo.mapper;
+
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.zyx.mpdemo.mapper.base.EasyBaseMapper;
+import com.zyx.mpdemo.model.entity.User;
+import com.zyx.mpdemo.model.vo.PartUser;
+import org.apache.ibatis.annotations.MapKey;
+import org.apache.ibatis.annotations.Param;
+import org.apache.ibatis.annotations.Select;
+import org.springframework.stereotype.Repository;
+
+import java.util.List;
+import java.util.Map;
+
+
+/**
+ * @author Yaxi.Zhang
+ * @since 2022/3/28 10:25
+ * desc:
+ */
+@Repository
+public interface UserMapper extends EasyBaseMapper {
+
+ /**
+ * 根据id查询用户信息为map集合
+ * 如果只有一个参数, 不需要使用@Param注解
+ * 如果有多个, mapper.xml中可以使用#{param1} #{param2} ... 引用, 当然也可以使用@Param中定义的name进行引用
+ */
+ @MapKey(value = "id")
+ Map> selectMapByAge(Integer age);
+
+ /**
+ * 通过年龄查询用户信息并分页
+ * MyBatis-Plus所提供的分页对象,必须位于第一个参数的位置
+ */
+ Page selectPageVo(@Param("page") Page page, @Param("age") Integer age);
+
+ /** 注解方式查询 */
+ @Select("select user_name,age,email from t_user where id = #{id}")
+ PartUser queryPartUserById(@Param("id") Long id);
+
+ @Select("select user_name,age,email from t_user where user_name like '%${name}%'")
+ List queryPartUserLikeName(@Param("name") String name);
+
+ /** 注意${} 与 #{} 的区别 */
+ @Select("select user_name,age,email from t_user where user_name like '%${likeName}%'")
+ Page selectPagePartUser(@Param("page") Page page, @Param("likeName") String likeName);
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/zyx/mpdemo/mapper/base/EasyBaseMapper.java b/src/main/java/com/zyx/mpdemo/mapper/base/EasyBaseMapper.java
new file mode 100644
index 0000000..3007c82
--- /dev/null
+++ b/src/main/java/com/zyx/mpdemo/mapper/base/EasyBaseMapper.java
@@ -0,0 +1,21 @@
+package com.zyx.mpdemo.mapper.base;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+
+import java.util.Collection;
+
+/**
+ * EasyBaseMapper接口,添加insertBatchSomeColumn批量插入方法
+ *
+ * @author Yaxi.Zhang
+ * @since 2022/12/7 10:45
+ */
+public interface EasyBaseMapper extends BaseMapper {
+ /**
+ * 批量插入 仅适用于 mysql
+ *
+ * @param entityList 实体列表
+ * @return 影响行数
+ */
+ Integer insertBatchSomeColumn(Collection entityList);
+}
diff --git a/src/main/java/com/zyx/mpdemo/model/entity/Course.java b/src/main/java/com/zyx/mpdemo/model/entity/Course.java
new file mode 100644
index 0000000..ae33994
--- /dev/null
+++ b/src/main/java/com/zyx/mpdemo/model/entity/Course.java
@@ -0,0 +1,96 @@
+package com.zyx.mpdemo.model.entity;
+
+import com.baomidou.mybatisplus.annotation.*;
+import com.zyx.mpdemo.model.enums.CourseStatusEnums;
+import com.zyx.mpdemo.model.vo.CourseVO;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * t_course对应实体类
+ *
+ * @author Yaxi.Zhang
+ * @since 2022/11/21 12:13
+ */
+@TableName(value ="t_course")
+@Data
+public class Course implements Serializable {
+
+ @TableField(exist = false)
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 主键ID
+ */
+ @TableId(type = IdType.AUTO)
+ private Long id;
+
+ /**
+ * 课程id
+ */
+ private Long courseId;
+
+ /**
+ * 课程名称
+ */
+ private String courseName;
+
+ /**
+ * 课程章节
+ */
+ private String chapter;
+
+ /**
+ * 课程序号数
+ */
+ private String sequence;
+
+ /**
+ * 课程小时数
+ */
+ private Integer courseHours;
+
+ /**
+ * 课程分钟数
+ */
+ private Integer courseMinutes;
+
+ /**
+ * 课程秒数
+ */
+ private Integer courseSeconds;
+
+ /**
+ * 课程状态
+ */
+ private CourseStatusEnums courseStatus;
+
+ /**
+ * 创建时间
+ */
+ @TableField(fill = FieldFill.INSERT)
+ private Date createTime;
+
+ /**
+ * 更新时间
+ */
+ @TableField(fill = FieldFill.INSERT_UPDATE)
+ private Date updateTime;
+
+ public CourseVO toCourseVO() {
+ CourseVO courseVO = new CourseVO();
+ courseVO.setId(this.id);
+ courseVO.setCourseId(this.courseId);
+ courseVO.setCourseName(this.courseName);
+ courseVO.setChapter(this.chapter);
+ courseVO.setSequence(this.sequence);
+ courseVO.setCourseHours(this.courseHours);
+ courseVO.setCourseMinutes(this.courseMinutes);
+ courseVO.setCourseSeconds(this.courseSeconds);
+ courseVO.setCourseStatus(this.courseStatus.getDesc());
+ return courseVO;
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/zyx/mpdemo/model/entity/Product.java b/src/main/java/com/zyx/mpdemo/model/entity/Product.java
new file mode 100644
index 0000000..ae6842c
--- /dev/null
+++ b/src/main/java/com/zyx/mpdemo/model/entity/Product.java
@@ -0,0 +1,20 @@
+package com.zyx.mpdemo.model.entity;
+
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.baomidou.mybatisplus.annotation.Version;
+import lombok.Data;
+
+/**
+ * @author Yaxi.Zhang
+ * @since 2022/3/29 14:06
+ */
+@Data
+@TableName("t_product")
+public class Product {
+ private Long id;
+ private String name;
+ private Integer price;
+ /** 标识乐观锁版本号字段 */
+ @Version
+ private Integer version;
+}
diff --git a/src/main/java/com/zyx/mpdemo/model/entity/User.java b/src/main/java/com/zyx/mpdemo/model/entity/User.java
new file mode 100644
index 0000000..b24c70e
--- /dev/null
+++ b/src/main/java/com/zyx/mpdemo/model/entity/User.java
@@ -0,0 +1,37 @@
+package com.zyx.mpdemo.model.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+import lombok.experimental.Accessors;
+
+/**
+ * @author Yaxi.Zhang
+ * @since 2022/3/28 10:03
+ */
+@Data
+@Accessors(chain = true)
+@TableName("t_user")
+public class User {
+
+ // 将属性所对应的字段指定为主键
+ // @TableId注解的value属性用于指定主键的字段
+ // @TableId注解的type属性设置主键生成策略
+ // @TableId(value = "uid", type = IdType.AUTO)
+ @TableId(value = "id", type = IdType.AUTO)
+ private Long id;
+
+ // 指定属性所对应的字段名
+ // @TableField("user_name")
+ private String name;
+
+ private Integer age;
+
+ private String email;
+
+ // private SexEnum sex;
+
+ // @TableLogic
+ // private Integer isDeleted;
+}
diff --git a/src/main/java/com/zyx/mpdemo/model/enums/CourseStatusEnums.java b/src/main/java/com/zyx/mpdemo/model/enums/CourseStatusEnums.java
new file mode 100644
index 0000000..4b0ffdf
--- /dev/null
+++ b/src/main/java/com/zyx/mpdemo/model/enums/CourseStatusEnums.java
@@ -0,0 +1,36 @@
+package com.zyx.mpdemo.model.enums;
+
+import com.baomidou.mybatisplus.annotation.EnumValue;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.util.Objects;
+import java.util.stream.Stream;
+
+/**
+ *
课程状态枚举
+ *
+ * @author Yaxi.Zhang
+ * @since 2022/11/21 11:36
+ */
+@Getter
+@AllArgsConstructor
+public enum CourseStatusEnums {
+ DELETE(0, "删除"),
+ LEARNING(1, "未学习"),
+ LEARNED(2, "已学习"),
+ ;
+
+ @EnumValue
+ private final Integer code;
+ private final String desc;
+
+ public static CourseStatusEnums of(Integer code) {
+ Objects.requireNonNull(code);
+ return Stream.of(values())
+ .filter(it -> it.getCode().equals(code))
+ .findAny()
+ .orElseThrow(() -> new IllegalArgumentException(code + " not exists"));
+ }
+
+}
diff --git a/src/main/java/com/zyx/mpdemo/model/enums/SexEnum.java b/src/main/java/com/zyx/mpdemo/model/enums/SexEnum.java
new file mode 100644
index 0000000..858035a
--- /dev/null
+++ b/src/main/java/com/zyx/mpdemo/model/enums/SexEnum.java
@@ -0,0 +1,35 @@
+package com.zyx.mpdemo.model.enums;
+
+import com.baomidou.mybatisplus.annotation.EnumValue;
+import lombok.Getter;
+
+import java.util.stream.Stream;
+
+/**
+ * @author Yaxi.Zhang
+ * @since 2022/3/28 10:12
+ */
+@Getter
+public enum SexEnum {
+ /** 性别 */
+ MALE(1, "男"),
+ FEMALE(2, "女");
+
+ /**
+ * 将注解所标识的属性的值存储到数据库中
+ * 注意: 配置文件中需要配置 type-enums-package ,指定枚举类所在包
+ */
+ @EnumValue
+ private final Integer sex;
+ private final String sexName;
+
+ SexEnum(Integer sex, String sexName) {
+ this.sex = sex;
+ this.sexName = sexName;
+ }
+
+ public static SexEnum getSexEnumByCode(Integer sex) {
+ return Stream.of(values()).filter(item -> item.sex.equals(sex)).findFirst().orElse(null);
+ }
+
+}
diff --git a/src/main/java/com/zyx/mpdemo/model/req/BatchLearnedReq.java b/src/main/java/com/zyx/mpdemo/model/req/BatchLearnedReq.java
new file mode 100644
index 0000000..8542525
--- /dev/null
+++ b/src/main/java/com/zyx/mpdemo/model/req/BatchLearnedReq.java
@@ -0,0 +1,26 @@
+package com.zyx.mpdemo.model.req;
+
+import lombok.Data;
+
+import javax.validation.constraints.NotNull;
+
+/**
+ * @author Yaxi.Zhang
+ * @since 2023/1/26 10:33
+ */
+@Data
+public class BatchLearnedReq {
+
+ @NotNull(message = "课程名称不能为空")
+ private String courseName;
+
+ @NotNull(message = "章节不能为空")
+ private String chapter;
+
+ @NotNull(message = "开始序号不能为空")
+ private String startSeq;
+
+ @NotNull(message = "结束序号不能为空")
+ private String endSeq;
+
+}
diff --git a/src/main/java/com/zyx/mpdemo/model/req/CourseDetailReq.java b/src/main/java/com/zyx/mpdemo/model/req/CourseDetailReq.java
new file mode 100644
index 0000000..ecaa045
--- /dev/null
+++ b/src/main/java/com/zyx/mpdemo/model/req/CourseDetailReq.java
@@ -0,0 +1,28 @@
+package com.zyx.mpdemo.model.req;
+
+import lombok.Data;
+
+import javax.validation.constraints.NotNull;
+import java.util.Set;
+
+/**
+ * @author Yaxi.Zhang
+ * @since 2023/1/26 10:01
+ */
+@Data
+public class CourseDetailReq {
+
+ @NotNull(message = "课程名称不能为空")
+ private String courseName;
+
+ /**
+ * 返回时是否需要按章节划分
+ */
+ private boolean needChapter = false;
+
+ /**
+ * 只查询指定章节的信息
+ */
+ private Set chapters;
+
+}
diff --git a/src/main/java/com/zyx/mpdemo/model/req/CourseListReq.java b/src/main/java/com/zyx/mpdemo/model/req/CourseListReq.java
new file mode 100644
index 0000000..3cf16c7
--- /dev/null
+++ b/src/main/java/com/zyx/mpdemo/model/req/CourseListReq.java
@@ -0,0 +1,21 @@
+package com.zyx.mpdemo.model.req;
+
+import lombok.Data;
+
+import javax.validation.constraints.NotNull;
+
+/**
+ * @author Yaxi.Zhang
+ * @since 2023/1/26 13:05
+ */
+@Data
+public class CourseListReq {
+
+ @NotNull(message = "课程id不能为空")
+ private Long courseId;
+
+ private Integer status;
+
+ private String chapter;
+
+}
diff --git a/src/main/java/com/zyx/mpdemo/model/vo/CourseCost.java b/src/main/java/com/zyx/mpdemo/model/vo/CourseCost.java
new file mode 100644
index 0000000..6a79682
--- /dev/null
+++ b/src/main/java/com/zyx/mpdemo/model/vo/CourseCost.java
@@ -0,0 +1,66 @@
+package com.zyx.mpdemo.model.vo;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import lombok.experimental.Accessors;
+
+import java.util.List;
+
+/**
+ * 课程耗时
+ *
+ * @author Yaxi.Zhang
+ * @since 2022/11/21 14:20
+ */
+@Data
+@Accessors(chain = true)
+@NoArgsConstructor
+@AllArgsConstructor
+public class CourseCost {
+ /** 小时数 */
+ private Integer hours;
+
+ /** 分钟数 */
+ private Integer minutes;
+
+ /** 秒数 */
+ private Integer seconds;
+
+ /** 获取课程秒数 */
+ public Integer getCourseSeconds() {
+ return this.hours * 60 * 60 + this.minutes * 60 + this.seconds;
+ }
+
+ /** 通过总的小时数、分钟数以及秒数获取实际课程耗时 */
+ public static CourseCost fromAll(Integer allHours, Integer allMinutes, Integer allSeconds) {
+ allMinutes += allSeconds / 60;
+ allSeconds = allSeconds % 60;
+ allHours += allMinutes / 60;
+ allMinutes = allMinutes % 60;
+ return new CourseCost().setHours(allHours).setMinutes(allMinutes).setSeconds(allSeconds);
+ }
+
+ /** 通过课程耗时列表获取实际课程耗时 */
+ public static CourseCost fromList(List courseCostList) {
+ Integer hours = 0;
+ Integer minutes = 0;
+ Integer seconds = 0;
+ for (CourseCost courseCost : courseCostList) {
+ hours += courseCost.getHours();
+ minutes += courseCost.getMinutes();
+ seconds += courseCost.getSeconds();
+ }
+ return fromAll(hours, minutes, seconds);
+ }
+
+ /** 通过总秒数获取CourseCost */
+ public static CourseCost secondToCourseCost(int seconds) {
+ int hour = seconds / 3600;
+ int other = seconds % 3600;
+ int minute = other / 60;
+ int second = other % 60;
+ return new CourseCost().setHours(hour).setMinutes(minute).setSeconds(second);
+ }
+
+}
diff --git a/src/main/java/com/zyx/mpdemo/model/vo/CourseDetailVO.java b/src/main/java/com/zyx/mpdemo/model/vo/CourseDetailVO.java
new file mode 100644
index 0000000..6f3c96d
--- /dev/null
+++ b/src/main/java/com/zyx/mpdemo/model/vo/CourseDetailVO.java
@@ -0,0 +1,70 @@
+package com.zyx.mpdemo.model.vo;
+
+import com.zyx.mpdemo.helpers.utils.CourseStatisticsUtils;
+import com.zyx.mpdemo.model.entity.Course;
+import com.zyx.mpdemo.model.enums.CourseStatusEnums;
+import lombok.Data;
+import lombok.experimental.Accessors;
+
+import java.util.*;
+import java.util.stream.Collectors;
+
+/**
+ * 课程详情
+ *
+ * @author Yaxi.Zhang
+ * @since 2023/1/26 10:02
+ */
+@Data
+@Accessors(chain = true)
+public class CourseDetailVO {
+
+ private String courseName;
+ private String chapter;
+ private CourseCost allCost;
+ private CourseCost learnedCost;
+ private CourseCost learningCost;
+ private String learnedPercentage;
+
+ public static CourseDetailVO getCourseDetail(String courseName, String chapter, List courses) {
+ List learned = courses.stream()
+ .filter(it -> Objects.equals(it.getCourseStatus(), CourseStatusEnums.LEARNED)).collect(Collectors.toList());
+ List learning = courses.stream()
+ .filter(it -> Objects.equals(it.getCourseStatus(), CourseStatusEnums.LEARNING)).collect(Collectors.toList());
+ CourseCost allCost = CourseStatisticsUtils.getCourseCost(courses);
+ CourseCost learnedCost = CourseStatisticsUtils.getCourseCost(learned);
+ CourseCost learningCost = CourseStatisticsUtils.getCourseCost(learning);
+ double learnedPercentageDouble = CourseStatisticsUtils.getLearnedPercentage(learnedCost, allCost);
+ String learnedPercentage = String.format("%.2f%%", learnedPercentageDouble * 100);
+ return new CourseDetailVO()
+ .setCourseName(courseName)
+ .setChapter(chapter)
+ .setAllCost(allCost)
+ .setLearnedCost(learnedCost)
+ .setLearningCost(learningCost)
+ .setLearnedPercentage(learnedPercentage);
+ }
+
+ public static List getCourseDetails(String courseName, List allCourses) {
+ List courseDetails = new ArrayList<>();
+ Map> courseMap = new HashMap<>();
+ List chapters = new ArrayList<>();
+ allCourses.forEach(course->{
+ String chapter = course.getChapter();
+ if (!chapters.contains(chapter)) {
+ chapters.add(course.getChapter());
+ List courseList = new ArrayList<>();
+ courseList.add(course);
+ courseMap.put(chapter, courseList);
+ } else {
+ courseMap.get(chapter).add(course);
+ }
+ });
+ for (String chapter : chapters) {
+ List courses = courseMap.get(chapter);
+ courseDetails.add(getCourseDetail(courseName, chapter, courses));
+ }
+ return courseDetails;
+ }
+
+}
diff --git a/src/main/java/com/zyx/mpdemo/model/vo/CourseInfoVO.java b/src/main/java/com/zyx/mpdemo/model/vo/CourseInfoVO.java
new file mode 100644
index 0000000..38f3b0a
--- /dev/null
+++ b/src/main/java/com/zyx/mpdemo/model/vo/CourseInfoVO.java
@@ -0,0 +1,33 @@
+package com.zyx.mpdemo.model.vo;
+
+import lombok.Data;
+import lombok.experimental.Accessors;
+
+import java.util.Set;
+
+/**
+ * 课程详情
+ *
+ * @author Yaxi.Zhang
+ * @since 2023/1/26 09:18
+ */
+@Data
+@Accessors(chain = true)
+public class CourseInfoVO {
+
+ /**
+ * 课程id
+ */
+ private Long courseId;
+
+ /**
+ * 课程名称
+ */
+ private String courseName;
+
+ /**
+ * 章节名称
+ */
+ private Set chapters;
+
+}
diff --git a/src/main/java/com/zyx/mpdemo/model/vo/CourseListVO.java b/src/main/java/com/zyx/mpdemo/model/vo/CourseListVO.java
new file mode 100644
index 0000000..24357a5
--- /dev/null
+++ b/src/main/java/com/zyx/mpdemo/model/vo/CourseListVO.java
@@ -0,0 +1,24 @@
+package com.zyx.mpdemo.model.vo;
+
+import lombok.Data;
+import lombok.experimental.Accessors;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author Yaxi.Zhang
+ * @since 2023/1/26 10:56
+ */
+@Data
+@Accessors(chain = true)
+public class CourseListVO {
+
+ private Long courseId;
+ private String courseName;
+ /**
+ * (章节名称, 课程列表)
+ */
+ private Map> courseMap;
+
+}
diff --git a/src/main/java/com/zyx/mpdemo/model/vo/CourseVO.java b/src/main/java/com/zyx/mpdemo/model/vo/CourseVO.java
new file mode 100644
index 0000000..319b6f4
--- /dev/null
+++ b/src/main/java/com/zyx/mpdemo/model/vo/CourseVO.java
@@ -0,0 +1,59 @@
+package com.zyx.mpdemo.model.vo;
+
+import lombok.Data;
+import lombok.experimental.Accessors;
+
+/**
+ * @author Yaxi.Zhang
+ * @since 2023/1/26 10:51
+ */
+@Data
+@Accessors(chain = true)
+public class CourseVO {
+
+ /**
+ * 主键ID
+ */
+ private Long id;
+
+ /**
+ * 课程id
+ */
+ private Long courseId;
+
+ /**
+ * 课程名称
+ */
+ private String courseName;
+
+ /**
+ * 课程章节
+ */
+ private String chapter;
+
+ /**
+ * 课程序号数
+ */
+ private String sequence;
+
+ /**
+ * 课程小时数
+ */
+ private Integer courseHours;
+
+ /**
+ * 课程分钟数
+ */
+ private Integer courseMinutes;
+
+ /**
+ * 课程秒数
+ */
+ private Integer courseSeconds;
+
+ /**
+ * 课程状态
+ */
+ private String courseStatus;
+
+}
diff --git a/src/main/java/com/zyx/mpdemo/model/vo/PartUser.java b/src/main/java/com/zyx/mpdemo/model/vo/PartUser.java
new file mode 100644
index 0000000..b845220
--- /dev/null
+++ b/src/main/java/com/zyx/mpdemo/model/vo/PartUser.java
@@ -0,0 +1,14 @@
+package com.zyx.mpdemo.model.vo;
+
+import lombok.Data;
+
+/**
+ * @author Yaxi.Zhang
+ * @since 2022/3/29 09:46
+ */
+@Data
+public class PartUser {
+ private String userName;
+ private Integer age;
+ private String email;
+}
diff --git a/src/main/java/com/zyx/mpdemo/service/ICourseService.java b/src/main/java/com/zyx/mpdemo/service/ICourseService.java
new file mode 100644
index 0000000..404779b
--- /dev/null
+++ b/src/main/java/com/zyx/mpdemo/service/ICourseService.java
@@ -0,0 +1,41 @@
+package com.zyx.mpdemo.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.zyx.mpdemo.model.entity.Course;
+import com.zyx.mpdemo.model.req.BatchLearnedReq;
+import com.zyx.mpdemo.model.req.CourseDetailReq;
+import com.zyx.mpdemo.model.req.CourseListReq;
+import com.zyx.mpdemo.model.vo.CourseDetailVO;
+import com.zyx.mpdemo.model.vo.CourseInfoVO;
+import com.zyx.mpdemo.model.vo.CourseListVO;
+
+import java.util.List;
+
+/**
+ * 针对表【t_course】的数据库操作Service
+ *
+ * @author zhangyaxi
+ * @since 2022-11-21 12:12
+ */
+public interface ICourseService extends IService {
+
+ /**
+ * 查询课程信息
+ */
+ List getCourseInfo();
+
+ /**
+ * 查询课程详情
+ */
+ List getCourseDetail(CourseDetailReq courseDetailReq);
+
+ /**
+ * 批量修改课程为已学完
+ */
+ void batchLearned(BatchLearnedReq batchLearnedReq);
+
+ /**
+ * 依据课程id查询课程列表
+ */
+ CourseListVO getCourseList(CourseListReq courseListReq);
+}
diff --git a/src/main/java/com/zyx/mpdemo/service/IUserService.java b/src/main/java/com/zyx/mpdemo/service/IUserService.java
new file mode 100644
index 0000000..e94ba24
--- /dev/null
+++ b/src/main/java/com/zyx/mpdemo/service/IUserService.java
@@ -0,0 +1,11 @@
+package com.zyx.mpdemo.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.zyx.mpdemo.model.entity.User;
+
+/**
+ * @author Yaxi.Zhang
+ * @since 2022/3/28 17:09
+ */
+public interface IUserService extends IService {
+}
diff --git a/src/main/java/com/zyx/mpdemo/service/impl/CourseServiceImpl.java b/src/main/java/com/zyx/mpdemo/service/impl/CourseServiceImpl.java
new file mode 100644
index 0000000..4789f2a
--- /dev/null
+++ b/src/main/java/com/zyx/mpdemo/service/impl/CourseServiceImpl.java
@@ -0,0 +1,129 @@
+package com.zyx.mpdemo.service.impl;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.StrUtil;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.zyx.mpdemo.common.exception.BusinessException;
+import com.zyx.mpdemo.mapper.CourseMapper;
+import com.zyx.mpdemo.model.entity.Course;
+import com.zyx.mpdemo.model.enums.CourseStatusEnums;
+import com.zyx.mpdemo.model.req.BatchLearnedReq;
+import com.zyx.mpdemo.model.req.CourseDetailReq;
+import com.zyx.mpdemo.model.req.CourseListReq;
+import com.zyx.mpdemo.model.vo.CourseDetailVO;
+import com.zyx.mpdemo.model.vo.CourseInfoVO;
+import com.zyx.mpdemo.model.vo.CourseListVO;
+import com.zyx.mpdemo.model.vo.CourseVO;
+import com.zyx.mpdemo.service.ICourseService;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.*;
+import java.util.stream.Collectors;
+
+/**
+ * 针对表【t_course】的数据库操作Service实现
+ *
+ * @author zhangyaxi
+ * @since 2022-11-21 12:12
+ */
+@Service
+public class CourseServiceImpl extends ServiceImpl implements ICourseService {
+
+ @Override
+ public List getCourseInfo() {
+ List courseInfos = new ArrayList<>();
+ QueryWrapper queryWrapper = new QueryWrapper<>();
+ queryWrapper.select("distinct course_id, course_name, chapter")
+ .ne("course_status", CourseStatusEnums.DELETE);
+ List courses = list(queryWrapper);
+ Map> courseMap = courses.stream().collect(Collectors.groupingBy(Course::getCourseId));
+ List courseIds = new ArrayList<>(courseMap.keySet());
+ courseIds.sort(Long::compareTo);
+ for (Long courseId : courseIds) {
+ List coursesById = courseMap.get(courseId);
+ if (CollUtil.isNotEmpty(coursesById)) {
+ String courseName = coursesById.get(0).getCourseName();
+ Set chapters = coursesById.stream().map(Course::getChapter).collect(Collectors.toSet());
+ courseInfos.add(new CourseInfoVO().setCourseId(courseId).setCourseName(courseName).setChapters(chapters));
+ }
+ }
+ return courseInfos;
+ }
+
+
+ @Override
+ public List getCourseDetail(CourseDetailReq courseDetailReq) {
+ List courseDetails = new ArrayList<>();
+ String courseName = courseDetailReq.getCourseName();
+ LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>();
+ queryWrapper
+ .eq(Course::getCourseName, courseName)
+ .ne(Course::getCourseStatus, CourseStatusEnums.DELETE);
+ boolean needChapter = courseDetailReq.isNeedChapter();
+ Set chapters = courseDetailReq.getChapters();
+ if (!needChapter) {
+ // 不需要分组
+ List courses = list(queryWrapper);
+ courseDetails.add(CourseDetailVO.getCourseDetail(courseName, "all", courses));
+ } else {
+ // 需要分组
+ queryWrapper.in(CollUtil.isNotEmpty(chapters), Course::getChapter, chapters);
+ List courses = list(queryWrapper);
+ courseDetails.addAll(CourseDetailVO.getCourseDetails(courseName, courses));
+ }
+ return courseDetails;
+ }
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public void batchLearned(BatchLearnedReq batchLearnedReq) {
+ LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>();
+ queryWrapper
+ .eq(Course::getCourseName, batchLearnedReq.getCourseName())
+ .eq(Course::getChapter, batchLearnedReq.getChapter())
+ .ge(Course::getSequence, batchLearnedReq.getStartSeq())
+ .le(Course::getSequence, batchLearnedReq.getEndSeq())
+ .ne(Course::getCourseStatus, CourseStatusEnums.DELETE);
+ List updateCourses = list(queryWrapper).stream()
+ .filter(it -> it.getCourseStatus().equals(CourseStatusEnums.LEARNING))
+ .peek(it -> {
+ it.setCourseStatus(CourseStatusEnums.LEARNED);
+ it.setUpdateTime(null);
+ })
+ .collect(Collectors.toList());
+ if (CollUtil.isEmpty(updateCourses)) {
+ throw new BusinessException("当前课程及章节下不存在未学习的内容");
+ }
+ updateBatchById(updateCourses);
+ }
+
+ @Override
+ public CourseListVO getCourseList(CourseListReq courseListReq) {
+ Long courseId = courseListReq.getCourseId();
+ Integer status = courseListReq.getStatus();
+ String chapter = courseListReq.getChapter();
+ LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>();
+ queryWrapper
+ .eq(Course::getCourseId, courseId);
+ if (Objects.isNull(status) || Objects.equals(status, 0)) {
+ queryWrapper.ne(Course::getCourseStatus, CourseStatusEnums.DELETE);
+ } else {
+ CourseStatusEnums courseStatus = CourseStatusEnums.of(status);
+ queryWrapper.eq(Course::getCourseStatus, courseStatus);
+ }
+ queryWrapper.eq(StrUtil.isNotEmpty(chapter), Course::getChapter, chapter);
+ List courses = list(queryWrapper);
+ if (CollUtil.isEmpty(courses)) {
+ throw new BusinessException("不存在当前的课程");
+ }
+ String courseName = courses.get(0).getCourseName();
+ TreeMap> courseMap = courses.stream()
+ .map(Course::toCourseVO)
+ .collect(Collectors.groupingBy(CourseVO::getChapter, TreeMap::new, Collectors.toList()));
+ return new CourseListVO().setCourseId(courseId).setCourseName(courseName).setCourseMap(courseMap);
+ }
+
+}
diff --git a/src/main/java/com/zyx/mpdemo/service/impl/UserServiceImpl.java b/src/main/java/com/zyx/mpdemo/service/impl/UserServiceImpl.java
new file mode 100644
index 0000000..209e0d3
--- /dev/null
+++ b/src/main/java/com/zyx/mpdemo/service/impl/UserServiceImpl.java
@@ -0,0 +1,16 @@
+package com.zyx.mpdemo.service.impl;
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.zyx.mpdemo.mapper.UserMapper;
+import com.zyx.mpdemo.model.entity.User;
+import com.zyx.mpdemo.service.IUserService;
+import org.springframework.stereotype.Service;
+
+/**
+ * @author Yaxi.Zhang
+ * @since 2022/3/28 17:10
+ */
+@Service
+public class UserServiceImpl extends ServiceImpl implements IUserService {
+}
+
diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml
index 3621202..fe5970d 100644
--- a/src/main/resources/application.yml
+++ b/src/main/resources/application.yml
@@ -1,2 +1,52 @@
server:
- port: 9001
\ No newline at end of file
+ port: 9001
+spring:
+ application:
+ name: com-zyx-mpdemo
+ jackson:
+ date-format: yyyy-MM-dd HH:mm:ss
+ default-property-inclusion: non_null
+ time-zone: GMT+8
+ # 配置数据源信息
+ datasource:
+ type: com.zaxxer.hikari.HikariDataSource
+ # 配置连接数据库信息
+ driver-class-name: com.mysql.cj.jdbc.Driver
+ url: jdbc:mysql://81.68.117.201:3306/db_test?allowPublicKeyRetrieval=true&allowMultiQueries=true&useUnicode=true&useSSL=false&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&autoReconnect=true&nullCatalogMeansCurrent=true
+ username: test
+ password: test
+ hikari:
+ pool-name: MpDemoPool
+ maximum-pool-size: 10
+ minimum-idle: 5
+ validation-timeout: 2500
+ idle-timeout: 30000
+ max-lifetime: 60000
+ connection-timeout: 30000
+ connection-test-query: SELECT 1
+
+mybatis-plus:
+ # 设置MyBatis-Plus的全局配置
+ # global-config:
+ # db-config:
+ # # 设置统一的主键生成策略
+ # id-type: auto
+ # # 设置实体类所对应的表的统一前缀
+ # table-prefix: t_
+ configuration:
+ # mybatis日志输出
+ # log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
+ # 开启下划线转驼峰, MyBatisPlus默认是开启的
+ map-underscore-to-camel-case: true
+ # 开启返回map结果集的下划线转驼峰,
+ # https://www.jianshu.com/p/ae9fd5c3169c
+ # 需要配置ObjectWrapperFactoryConverter!!!
+ # 否则报错:
+ # Failed to bind properties under 'mybatis-plus.configuration.object-wrapper-factory' to org.apache.ibatis.reflection.wrapper.ObjectWrapperFactory
+ # object-wrapper-factory: com.baomidou.mybatisplus.extension.MybatisMapWrapperFactory
+ # 配置类型别名所对应的包, mapper.xml的resultType可以直接通过类名使用
+ type-aliases-package: com.zyx.mpdemo.model.entity
+ # 扫描通用枚举的包
+ type-enums-package: com.zyx.mpdemo.model.enums
+ # 默认值为 classpath*:/mapper/**/*.xml, 如果mapper放在其他的位置, 需要自己配置(这里可配可不配)
+ mapper-locations: classpath*:/mapper/**/*.xml
\ No newline at end of file
diff --git a/src/main/resources/mapper/CourseMapper.xml b/src/main/resources/mapper/CourseMapper.xml
new file mode 100644
index 0000000..7c0bed7
--- /dev/null
+++ b/src/main/resources/mapper/CourseMapper.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ id,course_id,course_name,
+ chapter,sequence,course_hours,
+ course_minutes,course_seconds,course_status,
+ create_time,update_time
+
+
diff --git a/src/main/resources/mapper/UserMapper.xml b/src/main/resources/mapper/UserMapper.xml
new file mode 100644
index 0000000..1ba32b2
--- /dev/null
+++ b/src/main/resources/mapper/UserMapper.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file