Browse Source

新增AI实验课模块(课程管理、权限、前端等all)

liyanbo 3 months ago
parent
commit
fb7b4b9e22
67 changed files with 3307 additions and 52 deletions
  1. 39 0
      byzs-aicourse/pom.xml
  2. 101 0
      byzs-aicourse/src/main/java/cn/iocoder/byzs/module/aicourse/controller/admin/aiCourse/AiCourseController.java
  3. 70 0
      byzs-aicourse/src/main/java/cn/iocoder/byzs/module/aicourse/controller/admin/aiCourse/vo/AiCoursePageReqVO.java
  4. 95 0
      byzs-aicourse/src/main/java/cn/iocoder/byzs/module/aicourse/controller/admin/aiCourse/vo/AiCourseRespVO.java
  5. 60 0
      byzs-aicourse/src/main/java/cn/iocoder/byzs/module/aicourse/controller/admin/aiCourse/vo/AiCourseSaveReqVO.java
  6. 111 0
      byzs-aicourse/src/main/java/cn/iocoder/byzs/module/aicourse/controller/admin/aiCourseConfig/AiCourseConfigController.java
  7. 49 0
      byzs-aicourse/src/main/java/cn/iocoder/byzs/module/aicourse/controller/admin/aiCourseConfig/vo/AiCourseConfigPageReqVO.java
  8. 66 0
      byzs-aicourse/src/main/java/cn/iocoder/byzs/module/aicourse/controller/admin/aiCourseConfig/vo/AiCourseConfigRespVO.java
  9. 43 0
      byzs-aicourse/src/main/java/cn/iocoder/byzs/module/aicourse/controller/admin/aiCourseConfig/vo/AiCourseConfigSaveReqVO.java
  10. 99 0
      byzs-aicourse/src/main/java/cn/iocoder/byzs/module/aicourse/controller/admin/aiCourseType/AiCourseTypeController.java
  11. 28 0
      byzs-aicourse/src/main/java/cn/iocoder/byzs/module/aicourse/controller/admin/aiCourseType/vo/AiCourseTypeListReqVO.java
  12. 41 0
      byzs-aicourse/src/main/java/cn/iocoder/byzs/module/aicourse/controller/admin/aiCourseType/vo/AiCourseTypeRespVO.java
  13. 31 0
      byzs-aicourse/src/main/java/cn/iocoder/byzs/module/aicourse/controller/admin/aiCourseType/vo/AiCourseTypeSaveReqVO.java
  14. 118 0
      byzs-aicourse/src/main/java/cn/iocoder/byzs/module/aicourse/dal/dataobject/aiCourse/AiCourseDO.java
  15. 84 0
      byzs-aicourse/src/main/java/cn/iocoder/byzs/module/aicourse/dal/dataobject/aiCourseConfig/AiCourseConfigDO.java
  16. 65 0
      byzs-aicourse/src/main/java/cn/iocoder/byzs/module/aicourse/dal/dataobject/aiCourseProgress/AiCourseProgressDO.java
  17. 60 0
      byzs-aicourse/src/main/java/cn/iocoder/byzs/module/aicourse/dal/dataobject/aiCourseType/AiCourseTypeDO.java
  18. 35 0
      byzs-aicourse/src/main/java/cn/iocoder/byzs/module/aicourse/dal/mysql/aiCourse/AiCourseMapper.java
  19. 39 0
      byzs-aicourse/src/main/java/cn/iocoder/byzs/module/aicourse/dal/mysql/aiCourseConfig/AiCourseConfigMapper.java
  20. 15 0
      byzs-aicourse/src/main/java/cn/iocoder/byzs/module/aicourse/dal/mysql/aiCourseProgress/AiCourseProgressMapper.java
  21. 66 0
      byzs-aicourse/src/main/java/cn/iocoder/byzs/module/aicourse/dal/mysql/aiCourseType/AiCourseTypeMapper.java
  22. 25 0
      byzs-aicourse/src/main/java/cn/iocoder/byzs/module/aicourse/enums/ErrorCodeConstants.java
  23. 120 0
      byzs-aicourse/src/main/java/cn/iocoder/byzs/module/aicourse/job/JobAiCourseAotoRoleJob.java
  24. 72 0
      byzs-aicourse/src/main/java/cn/iocoder/byzs/module/aicourse/service/aiCourse/AiCourseService.java
  25. 185 0
      byzs-aicourse/src/main/java/cn/iocoder/byzs/module/aicourse/service/aiCourse/AiCourseServiceImpl.java
  26. 81 0
      byzs-aicourse/src/main/java/cn/iocoder/byzs/module/aicourse/service/aiCourseConfig/AiCourseConfigService.java
  27. 141 0
      byzs-aicourse/src/main/java/cn/iocoder/byzs/module/aicourse/service/aiCourseConfig/AiCourseConfigServiceImpl.java
  28. 64 0
      byzs-aicourse/src/main/java/cn/iocoder/byzs/module/aicourse/service/aiCourseType/AiCourseTypeService.java
  29. 155 0
      byzs-aicourse/src/main/java/cn/iocoder/byzs/module/aicourse/service/aiCourseType/AiCourseTypeServiceImpl.java
  30. 65 0
      byzs-aicourse/src/main/resources/mapper/aiCourse/AiCourseMapper.xml
  31. 74 0
      byzs-aicourse/src/main/resources/mapper/aiCourseConfig/AiCourseConfigMapper.xml
  32. 7 0
      byzs-aicourse/src/main/resources/mapper/aiCourseType/AiCourseTypeMapper.xml
  33. 0 6
      byzs-blockly/src/main/java/cn/iocoder/byzs/module/blockly/controller/admin/blockly/vo/BlocklyPageReqVO.java
  34. 0 9
      byzs-blockly/src/main/java/cn/iocoder/byzs/module/blockly/controller/admin/blockly/vo/BlocklyRespVO.java
  35. 0 6
      byzs-blockly/src/main/java/cn/iocoder/byzs/module/blockly/controller/admin/blockly/vo/BlocklySaveReqVO.java
  36. 0 12
      byzs-blockly/src/main/java/cn/iocoder/byzs/module/blockly/dal/dataobject/blockly/BlocklyDO.java
  37. 2 4
      byzs-blockly/src/main/resources/mapper/blockly/BlocklyMapper.xml
  38. 10 0
      byzs-module-system/src/main/java/cn/iocoder/byzs/module/system/controller/admin/permission/PermissionController.java
  39. 21 0
      byzs-module-system/src/main/java/cn/iocoder/byzs/module/system/controller/admin/permission/vo/permission/PermissionAssignRoleAiCourseScopeReqVO.java
  40. 3 0
      byzs-module-system/src/main/java/cn/iocoder/byzs/module/system/controller/admin/permission/vo/role/RoleRespVO.java
  41. 11 0
      byzs-module-system/src/main/java/cn/iocoder/byzs/module/system/dal/dataobject/permission/RoleDO.java
  42. 8 0
      byzs-module-system/src/main/java/cn/iocoder/byzs/module/system/service/permission/PermissionService.java
  43. 5 0
      byzs-module-system/src/main/java/cn/iocoder/byzs/module/system/service/permission/PermissionServiceImpl.java
  44. 8 0
      byzs-module-system/src/main/java/cn/iocoder/byzs/module/system/service/permission/RoleService.java
  45. 12 0
      byzs-module-system/src/main/java/cn/iocoder/byzs/module/system/service/permission/RoleServiceImpl.java
  46. 7 0
      byzs-server/pom.xml
  47. 5 0
      byzs-web/pom.xml
  48. 0 6
      byzs-web/src/main/java/cn/iocoder/byzs/module/web/controller/admin/ai/WebAiController.java
  49. 161 0
      byzs-web/src/main/java/cn/iocoder/byzs/module/web/controller/admin/aiCourse/WebAICourseController.java
  50. 41 0
      byzs-web/src/main/java/cn/iocoder/byzs/module/web/controller/admin/aiCourse/vo/WebAiCourseTypeVO.java
  51. 71 0
      byzs-web/src/main/java/cn/iocoder/byzs/module/web/controller/admin/aiCourse/vo/WebAiCourseVO.java
  52. 47 0
      byzs-web/src/main/java/cn/iocoder/byzs/module/web/controller/admin/aiCourseProgress/WebAiCourseProgressController.java
  53. 65 0
      byzs-web/src/main/java/cn/iocoder/byzs/module/web/controller/admin/aiCourseProgress/vo/WebAiCourseProgressDO.java
  54. 45 0
      byzs-web/src/main/java/cn/iocoder/byzs/module/web/controller/admin/aiCourseProgress/vo/WebAiCourseProgressDTO.java
  55. 70 0
      byzs-web/src/main/java/cn/iocoder/byzs/module/web/controller/admin/aiCourseProgress/vo/WebAiCourseProgressVO.java
  56. 0 1
      byzs-web/src/main/java/cn/iocoder/byzs/module/web/controller/admin/blockly/WebBlocklyController.java
  57. 0 2
      byzs-web/src/main/java/cn/iocoder/byzs/module/web/controller/admin/course/WebCourseController.java
  58. 0 4
      byzs-web/src/main/java/cn/iocoder/byzs/module/web/controller/admin/questionnaire/WebQuestionnaireController.java
  59. 0 2
      byzs-web/src/main/java/cn/iocoder/byzs/module/web/controller/admin/reportprogress/WebReportProgressController.java
  60. 31 0
      byzs-web/src/main/java/cn/iocoder/byzs/module/web/dal/mysql/aiCourse/WebAiCourseMapper.java
  61. 23 0
      byzs-web/src/main/java/cn/iocoder/byzs/module/web/dal/mysql/aiCourseProgress/WebAiCourseProgressMapper.java
  62. 133 0
      byzs-web/src/main/java/cn/iocoder/byzs/module/web/service/aiCourse/WebAiCourseServiceImpl.java
  63. 120 0
      byzs-web/src/main/java/cn/iocoder/byzs/module/web/service/aiCourseProgress/WebAiCourseProgressServiceImpl.java
  64. 21 0
      byzs-web/src/main/java/cn/iocoder/byzs/module/web/service/role/WebAuthRoleServiceImpl.java
  65. 26 0
      byzs-web/src/main/resources/mapper/aiCourse/WebAiCourseMapper.xml
  66. 55 0
      byzs-web/src/main/resources/mapper/aiCourseProgress/WebAiCourseProgressMapper.xml
  67. 2 0
      pom.xml

+ 39 - 0
byzs-aicourse/pom.xml

@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>cn.iocoder.boot</groupId>
+        <artifactId>byzs-bjdx</artifactId>
+        <version>${revision}</version>
+    </parent>
+
+    <artifactId>byzs-aicourse</artifactId>
+
+    <properties>
+        <maven.compiler.source>17</maven.compiler.source>
+        <maven.compiler.target>17</maven.compiler.target>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+    </properties>
+
+
+    <dependencies>
+        <dependency>
+            <groupId>cn.iocoder.boot</groupId>
+            <artifactId>byzs-course</artifactId>
+            <version>${revision}</version>
+        </dependency>
+        <dependency>
+            <groupId>cn.iocoder.boot</groupId>
+            <artifactId>byzs-module-system</artifactId>
+            <version>${revision}</version>
+        </dependency>
+        <dependency>
+            <groupId>cn.iocoder.boot</groupId>
+            <artifactId>byzs-module-infra</artifactId>
+            <version>${revision}</version>
+        </dependency>
+    </dependencies>
+
+</project>

+ 101 - 0
byzs-aicourse/src/main/java/cn/iocoder/byzs/module/aicourse/controller/admin/aiCourse/AiCourseController.java

@@ -0,0 +1,101 @@
+package cn.iocoder.byzs.module.aicourse.controller.admin.aiCourse;
+
+import cn.iocoder.byzs.framework.apilog.core.annotation.ApiAccessLog;
+import cn.iocoder.byzs.framework.common.pojo.CommonResult;
+import cn.iocoder.byzs.framework.common.pojo.PageParam;
+import cn.iocoder.byzs.framework.common.pojo.PageResult;
+import cn.iocoder.byzs.framework.common.util.object.BeanUtils;
+import cn.iocoder.byzs.framework.excel.core.util.ExcelUtils;
+import cn.iocoder.byzs.module.aicourse.controller.admin.aiCourse.vo.AiCoursePageReqVO;
+import cn.iocoder.byzs.module.aicourse.controller.admin.aiCourse.vo.AiCourseRespVO;
+import cn.iocoder.byzs.module.aicourse.controller.admin.aiCourse.vo.AiCourseSaveReqVO;
+import cn.iocoder.byzs.module.aicourse.dal.dataobject.aiCourse.AiCourseDO;
+import cn.iocoder.byzs.module.aicourse.service.aiCourse.AiCourseService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.annotation.Resource;
+import jakarta.servlet.http.HttpServletResponse;
+import jakarta.validation.Valid;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import java.io.IOException;
+import java.util.List;
+
+import static cn.iocoder.byzs.framework.apilog.core.enums.OperateTypeEnum.EXPORT;
+import static cn.iocoder.byzs.framework.common.pojo.CommonResult.success;
+
+@Tag(name = "管理后台 - 课程")
+@RestController
+@RequestMapping("/aiCourse/course")
+@Validated
+public class AiCourseController {
+
+    @Resource
+    private AiCourseService aiCourseService;
+
+    @PostMapping("/create")
+    @Operation(summary = "创建课程")
+    @PreAuthorize("@ss.hasPermission('aiCourse:course:create')")
+    public CommonResult<Long> createAiCourse(@Valid @RequestBody AiCourseSaveReqVO createReqVO) {
+        return success(aiCourseService.createAiCourse(createReqVO));
+    }
+
+    @PutMapping("/update")
+    @Operation(summary = "更新课程")
+    @PreAuthorize("@ss.hasPermission('aiCourse:course:update')")
+    public CommonResult<Boolean> updateAiCourse(@Valid @RequestBody AiCourseSaveReqVO updateReqVO) {
+        aiCourseService.updateAiCourse(updateReqVO);
+        return success(true);
+    }
+
+    @DeleteMapping("/delete")
+    @Operation(summary = "删除课程")
+    @Parameter(name = "id", description = "编号", required = true)
+    @PreAuthorize("@ss.hasPermission('aiCourse:course:delete')")
+    public CommonResult<Boolean> deleteAiCourse(@RequestParam("id") Long id) {
+        aiCourseService.deleteAiCourse(id);
+        return success(true);
+    }
+
+    @DeleteMapping("/delete-list")
+    @Parameter(name = "ids", description = "编号", required = true)
+    @Operation(summary = "批量删除课程")
+    @PreAuthorize("@ss.hasPermission('aiCourse:course:delete')")
+    public CommonResult<Boolean> deleteAiCourseList(@RequestParam("ids") List<Long> ids) {
+        aiCourseService.deleteAiCourseListByIds(ids);
+        return success(true);
+    }
+
+    @GetMapping("/get")
+    @Operation(summary = "获得课程")
+    @Parameter(name = "id", description = "编号", required = true, example = "1024")
+    @PreAuthorize("@ss.hasPermission('aiCourse:course:query')")
+    public CommonResult<AiCourseRespVO> getAiCourse(@RequestParam("id") Long id) {
+        AiCourseDO aiCourse = aiCourseService.getAiCourse(id);
+        return success(BeanUtils.toBean(aiCourse, AiCourseRespVO.class));
+    }
+
+    @GetMapping("/page")
+    @Operation(summary = "获得课程分页")
+    @PreAuthorize("@ss.hasPermission('aiCourse:course:query')")
+    public CommonResult<PageResult<AiCourseRespVO>> getAiCoursePage(@Valid AiCoursePageReqVO pageReqVO) {
+        PageResult<AiCourseRespVO> pageResult = aiCourseService.getAiCoursePage(pageReqVO);
+        return success(BeanUtils.toBean(pageResult, AiCourseRespVO.class));
+    }
+
+    @GetMapping("/export-excel")
+    @Operation(summary = "导出课程 Excel")
+    @PreAuthorize("@ss.hasPermission('aiCourse:course:export')")
+    @ApiAccessLog(operateType = EXPORT)
+    public void exportAiCourseExcel(@Valid AiCoursePageReqVO pageReqVO,
+              HttpServletResponse response) throws IOException {
+        pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
+        List<AiCourseRespVO> list = aiCourseService.getAiCoursePage(pageReqVO).getList();
+        // 导出 Excel
+        ExcelUtils.write(response, "课程.xls", "数据", AiCourseRespVO.class,list);
+    }
+
+}

+ 70 - 0
byzs-aicourse/src/main/java/cn/iocoder/byzs/module/aicourse/controller/admin/aiCourse/vo/AiCoursePageReqVO.java

@@ -0,0 +1,70 @@
+package cn.iocoder.byzs.module.aicourse.controller.admin.aiCourse.vo;
+
+import cn.iocoder.byzs.framework.common.pojo.PageParam;
+import com.alibaba.excel.annotation.ExcelProperty;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+@Schema(description = "管理后台 - 课程分页 Request VO")
+@Data
+public class AiCoursePageReqVO extends PageParam {
+
+    @Schema(description = "租户编号")
+    private Long tenantId;
+
+    @Schema(description = "课程id")
+    private Long id;
+
+    @Schema(description = "课程名称")
+    private String acName;
+
+    @Schema(description = "课程内容类型")
+    private String acContentType;
+
+    @Schema(description = "课程内容")
+    private String acContent;
+
+    @ExcelProperty("简介")
+    private String blocklyInfo;
+    @ExcelProperty("人物图标")
+    private String blocklyUserImage;
+    @ExcelProperty("人物初始方向")
+    private Integer blocklyUserDirection;
+    @ExcelProperty("地图方格大小")
+    private String blocklyTileSize;
+    @ExcelProperty("地图背景图")
+    private String blocklyBackground;
+    @ExcelProperty("地图开始坐标")
+    private String blocklyStartPoint;
+    @ExcelProperty("地图结束坐标")
+    private String blocklyEndPoint;
+    @ExcelProperty("地图可行走坐标")
+    private String blocklyWalkablePoints;
+    @ExcelProperty("特殊方块")
+    private String blocklySpecialBlocks;
+    @ExcelProperty("路径列表")
+    private String blocklyRouteList;
+
+
+
+    @Schema(description = "课程是否有检查")
+    private String acIsInspect;
+
+    @Schema(description = "课程类型id")
+    private Long acType;
+
+    @Schema(description = "课程类型名称")
+    private String acTypeName;
+
+
+
+    @Schema(description = "课程排序")
+    private Integer acOrder;
+
+    @Schema(description = "课程标签")
+    private String acLabel;
+
+    @Schema(description = "课程状态")
+    private String acStatus;
+
+}

+ 95 - 0
byzs-aicourse/src/main/java/cn/iocoder/byzs/module/aicourse/controller/admin/aiCourse/vo/AiCourseRespVO.java

@@ -0,0 +1,95 @@
+package cn.iocoder.byzs.module.aicourse.controller.admin.aiCourse.vo;
+
+import cn.iocoder.byzs.framework.excel.core.annotations.DictFormat;
+import cn.iocoder.byzs.framework.excel.core.convert.DictConvert;
+import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
+import com.alibaba.excel.annotation.ExcelProperty;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+@Schema(description = "管理后台 - 课程 Response VO")
+@Data
+@ExcelIgnoreUnannotated
+public class AiCourseRespVO {
+
+    @Schema(description = "租户编号")
+    private Long tenantId;
+
+    @Schema(description = "课程d", requiredMode = Schema.RequiredMode.REQUIRED)
+    @ExcelProperty("课程d")
+    private Long id;
+
+    @Schema(description = "课程名称")
+    @ExcelProperty("课程名称")
+    private String acName;
+
+    @Schema(description = "课程内容类型")
+    @ExcelProperty("课程内容类型")
+    private String acContentType;
+
+    @Schema(description = "课程内容")
+    @ExcelProperty("课程内容")
+    private String acContent;
+
+    @ExcelProperty("简介")
+    @Schema(description = "简介")
+    private String blocklyInfo;
+    @ExcelProperty("人物图标")
+    @Schema(description = "人物图标")
+    private String blocklyUserImage;
+    @ExcelProperty("人物初始方向")
+    @Schema(description = "人物初始方向")
+    private Integer blocklyUserDirection;
+    @ExcelProperty("地图方格大小")
+    @Schema(description = "地图方格大小")
+    private String blocklyTileSize;
+    @ExcelProperty("地图背景图")
+    @Schema(description = "地图背景图")
+    private String blocklyBackground;
+    @ExcelProperty("地图开始坐标")
+    @Schema(description = "地图开始坐标")
+    private String blocklyStartPoint;
+    @ExcelProperty("地图结束坐标")
+    @Schema(description = "地图结束坐标")
+    private String blocklyEndPoint;
+    @ExcelProperty("地图可行走坐标")
+    @Schema(description = "地图可行走坐标")
+    private String blocklyWalkablePoints;
+    @ExcelProperty("特殊方块")
+    @Schema(description = "特殊方块")
+    private String blocklySpecialBlocks;
+
+    @ExcelProperty("路径列表")
+    @Schema(description = "路径列表")
+    private String blocklyRouteList;
+
+
+
+    @Schema(description = "课程是否有检查")
+    @ExcelProperty(value = "课程是否有检查", converter = DictConvert.class)
+    @DictFormat("infra_boolean_string")
+    private String acIsInspect;
+
+    @Schema(description = "课程类型")
+    @ExcelProperty("课程类型")
+    private Long acType;
+
+    @Schema(description = "课程类型")
+    @ExcelProperty("课程类型")
+    private String acTypeName;
+
+    @Schema(description = "课程排序")
+    @ExcelProperty("课程排序")
+    private Integer acOrder;
+
+    @Schema(description = "课程标签")
+    @ExcelProperty(value = "课程标签", converter = DictConvert.class)
+    @DictFormat("ac_course_label")
+    private String acLabel;
+
+    @Schema(description = "课程状态")
+    @ExcelProperty(value = "课程状态", converter = DictConvert.class)
+    @DictFormat("common_status")
+    private String acStatus;
+
+}

+ 60 - 0
byzs-aicourse/src/main/java/cn/iocoder/byzs/module/aicourse/controller/admin/aiCourse/vo/AiCourseSaveReqVO.java

@@ -0,0 +1,60 @@
+package cn.iocoder.byzs.module.aicourse.controller.admin.aiCourse.vo;
+
+import com.alibaba.excel.annotation.ExcelProperty;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+@Schema(description = "管理后台 - 课程新增/修改 Request VO")
+@Data
+public class AiCourseSaveReqVO {
+
+    @Schema(description = "课程id", requiredMode = Schema.RequiredMode.REQUIRED)
+    private Long id;
+
+    @Schema(description = "课程名称")
+    private String acName;
+
+    @Schema(description = "课程内容类型")
+    private String acContentType;
+
+    @Schema(description = "课程内容")
+    private String acContent;
+
+    @ExcelProperty("简介")
+    private String blocklyInfo;
+    @ExcelProperty("人物图标")
+    private String blocklyUserImage;
+    @ExcelProperty("人物初始方向")
+    private Integer blocklyUserDirection;
+    @ExcelProperty("地图方格大小")
+    private String blocklyTileSize;
+    @ExcelProperty("地图背景图")
+    private String blocklyBackground;
+    @ExcelProperty("地图开始坐标")
+    private String blocklyStartPoint;
+    @ExcelProperty("地图结束坐标")
+    private String blocklyEndPoint;
+    @ExcelProperty("地图可行走坐标")
+    private String blocklyWalkablePoints;
+    @ExcelProperty("特殊方块")
+    private String blocklySpecialBlocks;
+
+    @ExcelProperty("路径列表")
+    private String blocklyRouteList;
+
+    @Schema(description = "课程是否有检查")
+    private String acIsInspect;
+
+    @Schema(description = "课程类型")
+    private Long acType;
+
+    @Schema(description = "课程标签")
+    private String acLabel;
+
+    @Schema(description = "课程排序")
+    private Integer acOrder;
+
+    @Schema(description = "课程状态")
+    private String acStatus;
+
+}

+ 111 - 0
byzs-aicourse/src/main/java/cn/iocoder/byzs/module/aicourse/controller/admin/aiCourseConfig/AiCourseConfigController.java

@@ -0,0 +1,111 @@
+package cn.iocoder.byzs.module.aicourse.controller.admin.aiCourseConfig;
+
+import cn.iocoder.byzs.module.aicourse.controller.admin.aiCourseConfig.vo.AiCourseConfigPageReqVO;
+import cn.iocoder.byzs.module.aicourse.controller.admin.aiCourseConfig.vo.AiCourseConfigRespVO;
+import cn.iocoder.byzs.module.aicourse.controller.admin.aiCourseConfig.vo.AiCourseConfigSaveReqVO;
+import cn.iocoder.byzs.module.aicourse.dal.dataobject.aiCourseConfig.AiCourseConfigDO;
+import cn.iocoder.byzs.module.aicourse.service.aiCourseConfig.AiCourseConfigService;
+import cn.iocoder.byzs.framework.apilog.core.annotation.ApiAccessLog;
+import cn.iocoder.byzs.framework.common.pojo.CommonResult;
+import cn.iocoder.byzs.framework.common.pojo.PageParam;
+import cn.iocoder.byzs.framework.common.pojo.PageResult;
+import cn.iocoder.byzs.framework.common.util.object.BeanUtils;
+import cn.iocoder.byzs.framework.excel.core.util.ExcelUtils;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.annotation.Resource;
+import jakarta.servlet.http.HttpServletResponse;
+import jakarta.validation.Valid;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import java.io.IOException;
+import java.util.List;
+
+import static cn.iocoder.byzs.framework.apilog.core.enums.OperateTypeEnum.EXPORT;
+import static cn.iocoder.byzs.framework.common.pojo.CommonResult.success;
+
+@Tag(name = "管理后台 - 课程配置")
+@RestController
+@RequestMapping("/aiCourse/course-config")
+@Validated
+public class AiCourseConfigController {
+
+    @Resource
+    private AiCourseConfigService aiCourseConfigService;
+
+    @PostMapping("/create")
+    @Operation(summary = "创建课程配置")
+    @PreAuthorize("@ss.hasPermission('aiCourse:course-config:create')")
+    public CommonResult<Long>createAiCourseConfig(@Valid @RequestBody AiCourseConfigSaveReqVO createReqVO) {
+        return success(aiCourseConfigService.createAiCourseConfig(createReqVO));
+    }
+
+    @PutMapping("/update")
+    @Operation(summary = "更新课程配置")
+    @PreAuthorize("@ss.hasPermission('aiCourse:course-config:update')")
+    public CommonResult<Boolean> updateAiCourseConfig(@Valid @RequestBody AiCourseConfigSaveReqVO updateReqVO) {
+        aiCourseConfigService.updateAiCourseConfig(updateReqVO);
+        return success(true);
+    }
+
+    @DeleteMapping("/delete")
+    @Operation(summary = "删除课程配置")
+    @Parameter(name = "id", description = "编号", required = true)
+    @PreAuthorize("@ss.hasPermission('aiCourse:course-config:delete')")
+    public CommonResult<Boolean> deleteAiCourseConfig(@RequestParam("id") Long id) {
+        aiCourseConfigService.deleteAiCourseConfig(id);
+        return success(true);
+    }
+
+    @DeleteMapping("/delete-list")
+    @Parameter(name = "ids", description = "编号", required = true)
+    @Operation(summary = "批量删除课程配置")
+                @PreAuthorize("@ss.hasPermission('aiCourse:course-config:delete')")
+    public CommonResult<Boolean> deleteAiCourseConfigList(@RequestParam("ids") List<Long> ids) {
+        aiCourseConfigService.deleteAiCourseConfigListByIds(ids);
+        return success(true);
+    }
+
+    @GetMapping("/get")
+    @Operation(summary = "获得课程配置")
+    @Parameter(name = "id", description = "编号", required = true, example = "1024")
+    @PreAuthorize("@ss.hasPermission('aiCourse:course-config:query')")
+    public CommonResult<AiCourseConfigRespVO> getAiCourseConfig(@RequestParam("id") Long id) {
+        AiCourseConfigDO courseConfig = aiCourseConfigService.getAiCourseConfig(id);
+        return success(BeanUtils.toBean(courseConfig, AiCourseConfigRespVO.class));
+    }
+
+    @GetMapping("/getConfigQuest")
+    @Operation(summary = "获得课程配置试题列表")
+    @Parameter(name = "ccCourseId", description = "课程id", required = true, example = "1025")
+    @PreAuthorize("@ss.hasPermission('aiCourse:course-config:query')")
+    public CommonResult<List<AiCourseConfigRespVO>> getAiCourseConfigQuestion(@RequestParam("ccCourseId") Long ccCourseId) {
+        List<AiCourseConfigRespVO> courseConfigList = aiCourseConfigService.getAiCourseConfigQuestion(ccCourseId);
+        return success(courseConfigList);
+    }
+
+    @GetMapping("/page")
+    @Operation(summary = "获得课程配置分页")
+    @PreAuthorize("@ss.hasPermission('aiCourse:course-config:query')")
+    public CommonResult<PageResult<AiCourseConfigPageReqVO>> getAiCourseConfigPage(@Valid AiCourseConfigPageReqVO pageReqVO) {
+        PageResult<AiCourseConfigPageReqVO> pageResult = aiCourseConfigService.getAiCourseConfigPage(pageReqVO);
+        return success(BeanUtils.toBean(pageResult, AiCourseConfigPageReqVO.class));
+    }
+
+    @GetMapping("/export-excel")
+    @Operation(summary = "导出课程配置 Excel")
+    @PreAuthorize("@ss.hasPermission('aiCourse:course-config:export')")
+    @ApiAccessLog(operateType = EXPORT)
+    public void exportAiCourseConfigExcel(@Valid AiCourseConfigPageReqVO pageReqVO,
+              HttpServletResponse response) throws IOException {
+        pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
+        List<AiCourseConfigPageReqVO> list = aiCourseConfigService.getAiCourseConfigPage(pageReqVO).getList();
+        // 导出 Excel
+        ExcelUtils.write(response, "课程配置.xls", "数据", AiCourseConfigPageReqVO.class,
+                        BeanUtils.toBean(list, AiCourseConfigPageReqVO.class));
+    }
+
+}

+ 49 - 0
byzs-aicourse/src/main/java/cn/iocoder/byzs/module/aicourse/controller/admin/aiCourseConfig/vo/AiCourseConfigPageReqVO.java

@@ -0,0 +1,49 @@
+package cn.iocoder.byzs.module.aicourse.controller.admin.aiCourseConfig.vo;
+
+import cn.iocoder.byzs.framework.common.pojo.PageParam;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+@Schema(description = "管理后台 - 课程配置分页 Request VO")
+@Data
+public class AiCourseConfigPageReqVO extends PageParam {
+
+    @Schema(description = "租户编号")
+    private Long tenantId;
+
+    @Schema(description = "课程配置id")
+    private Integer id;
+
+    @Schema(description = "课程id")
+    private Integer ccCourseId;
+
+    @Schema(description = "课程名称")
+    private String courseName;
+
+    @Schema(description = "课程暂停时长")
+    private Double ccTime;
+
+    @Schema(description = "试题来源")
+    private String ccQuestSource;
+
+    @Schema(description = "试题内容")
+    private String ccQuestContent;
+
+    @Schema(description = "试题选项")
+    private String ccQuestOption;
+
+    @Schema(description = "ai问题提示")
+    private String ccAiQuestTip;
+
+    @Schema(description = "ai答案")
+    private String ccAiAnswer;
+
+    @Schema(description = "答案")
+    private String ccAnswer;
+
+    @Schema(description = "试题id")
+    private Integer ccQuestId;
+
+    @Schema(description = "是否显示答案")
+    private String ccAnswerJudge;
+}

+ 66 - 0
byzs-aicourse/src/main/java/cn/iocoder/byzs/module/aicourse/controller/admin/aiCourseConfig/vo/AiCourseConfigRespVO.java

@@ -0,0 +1,66 @@
+package cn.iocoder.byzs.module.aicourse.controller.admin.aiCourseConfig.vo;
+
+import cn.iocoder.byzs.framework.excel.core.annotations.DictFormat;
+import cn.iocoder.byzs.framework.excel.core.convert.DictConvert;
+import cn.iocoder.byzs.module.bjdx.controller.admin.coursequestion.vo.CourseQuestionRespVO;
+import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
+import com.alibaba.excel.annotation.ExcelProperty;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+@Schema(description = "管理后台 - 课程配置 Response VO")
+@Data
+@ExcelIgnoreUnannotated
+public class AiCourseConfigRespVO {
+
+    @Schema(description = "租户编号")
+    private Long tenantId;
+
+    @Schema(description = "课程配置id", requiredMode = Schema.RequiredMode.REQUIRED)
+    @ExcelProperty("课程配置id")
+    private Long id;
+
+    @Schema(description = "课程id")
+    @ExcelProperty("课程id")
+    private Long ccCourseId;
+
+    @Schema(description = "课程暂停时长")
+    @ExcelProperty("课程暂停时长")
+    private Integer ccTime;
+
+    @Schema(description = "试题来源")
+    @ExcelProperty("试题来源")
+    private String ccQuestSource;
+
+    @Schema(description = "试题内容")
+    @ExcelProperty("试题内容")
+    private String ccQuestContent;
+
+    @Schema(description = "试题选项")
+    @ExcelProperty("试题选项")
+    private String ccQuestOption;
+
+    @Schema(description = "ai问题提示")
+    private String ccAiQuestTip;
+
+    @Schema(description = "ai答案")
+    @ExcelProperty("ai答案")
+    private String ccAiAnswer;
+
+    @Schema(description = "答案")
+    @ExcelProperty("答案")
+    private String ccAnswer;
+
+    @Schema(description = "试题id")
+    @ExcelProperty("试题id")
+    private Long ccQuestId;
+
+    @Schema(description = "是否显示答案")
+    @ExcelProperty(value = "是否显示答案", converter = DictConvert.class)
+    @DictFormat("infra_boolean_string") // TODO 代码优化:建议设置到对应的 DictTypeConstants 枚举类中
+    private String ccAnswerJudge;
+
+    @Schema(description = "课程-试题")
+    private CourseQuestionRespVO aiCourseQuestion;
+
+}

+ 43 - 0
byzs-aicourse/src/main/java/cn/iocoder/byzs/module/aicourse/controller/admin/aiCourseConfig/vo/AiCourseConfigSaveReqVO.java

@@ -0,0 +1,43 @@
+package cn.iocoder.byzs.module.aicourse.controller.admin.aiCourseConfig.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+@Schema(description = "管理后台 - 课程配置新增/修改 Request VO")
+@Data
+public class AiCourseConfigSaveReqVO {
+
+    @Schema(description = "课程配置id", requiredMode = Schema.RequiredMode.REQUIRED)
+    private Long id;
+
+    @Schema(description = "课程id")
+    private Integer ccCourseId;
+
+    @Schema(description = "课程暂停时长")
+    private Integer ccTime;
+
+    @Schema(description = "试题来源")
+    private String ccQuestSource;
+
+    @Schema(description = "试题内容")
+    private String ccQuestContent;
+
+    @Schema(description = "试题选项")
+    private String ccQuestOption;
+
+    @Schema(description = "ai问题提示")
+    private String ccAiQuestTip;
+
+    @Schema(description = "ai答案")
+    private String ccAiAnswer;
+
+    @Schema(description = "答案")
+    private String ccAnswer;
+
+    @Schema(description = "试题id")
+    private Integer ccQuestId;
+
+    @Schema(description = "是否显示答案")
+    private String ccAnswerJudge;
+
+}

+ 99 - 0
byzs-aicourse/src/main/java/cn/iocoder/byzs/module/aicourse/controller/admin/aiCourseType/AiCourseTypeController.java

@@ -0,0 +1,99 @@
+package cn.iocoder.byzs.module.aicourse.controller.admin.aiCourseType;
+
+import cn.iocoder.byzs.framework.apilog.core.annotation.ApiAccessLog;
+import cn.iocoder.byzs.framework.common.pojo.CommonResult;
+import cn.iocoder.byzs.framework.common.util.object.BeanUtils;
+import cn.iocoder.byzs.framework.excel.core.util.ExcelUtils;
+import cn.iocoder.byzs.module.aicourse.controller.admin.aiCourseType.vo.AiCourseTypeListReqVO;
+import cn.iocoder.byzs.module.aicourse.controller.admin.aiCourseType.vo.AiCourseTypeRespVO;
+import cn.iocoder.byzs.module.aicourse.controller.admin.aiCourseType.vo.AiCourseTypeSaveReqVO;
+import cn.iocoder.byzs.module.aicourse.dal.dataobject.aiCourseType.AiCourseTypeDO;
+import cn.iocoder.byzs.module.aicourse.service.aiCourseType.AiCourseTypeService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.annotation.Resource;
+import jakarta.servlet.http.HttpServletResponse;
+import jakarta.validation.Valid;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import java.io.IOException;
+import java.util.List;
+
+import static cn.iocoder.byzs.framework.apilog.core.enums.OperateTypeEnum.EXPORT;
+import static cn.iocoder.byzs.framework.common.pojo.CommonResult.success;
+
+@Tag(name = "管理后台 - 课程-类型")
+@RestController
+@RequestMapping("/aiCourse/course-type")
+@Validated
+public class AiCourseTypeController {
+
+    @Resource
+    private AiCourseTypeService courseTypeService;
+
+    @PostMapping("/create")
+    @Operation(summary = "创建课程-类型")
+    @PreAuthorize("@ss.hasPermission('aiCourse:course-type:create')")
+    public CommonResult<Long> createAiCourseType(@Valid @RequestBody AiCourseTypeSaveReqVO createReqVO) {
+        return success(courseTypeService.createAiCourseType(createReqVO));
+    }
+
+    @PutMapping("/update")
+    @Operation(summary = "更新课程-类型")
+    @PreAuthorize("@ss.hasPermission('aiCourse:course-type:update')")
+    public CommonResult<Boolean> updateAiCourseType(@Valid @RequestBody AiCourseTypeSaveReqVO updateReqVO) {
+        courseTypeService.updateAiCourseType(updateReqVO);
+        return success(true);
+    }
+
+    @DeleteMapping("/delete")
+    @Operation(summary = "删除课程-类型")
+    @Parameter(name = "id", description = "编号", required = true)
+    @PreAuthorize("@ss.hasPermission('aiCourse:course-type:delete')")
+    public CommonResult<Boolean> deleteAiCourseType(@RequestParam("id") Long id) {
+        courseTypeService.deleteAiCourseType(id);
+        return success(true);
+    }
+
+
+    @GetMapping("/get")
+    @Operation(summary = "获得课程-类型")
+    @Parameter(name = "id", description = "编号", required = true, example = "1024")
+    @PreAuthorize("@ss.hasPermission('aiCourse:course-type:query')")
+    public CommonResult<AiCourseTypeRespVO> getAiCourseType(@RequestParam("id") Long id) {
+        AiCourseTypeDO aiCourseType = courseTypeService.getAiCourseType(id);
+        return success(BeanUtils.toBean(aiCourseType, AiCourseTypeRespVO.class));
+    }
+
+    @GetMapping("/list")
+    @Operation(summary = "获得课程-类型列表")
+    @PreAuthorize("@ss.hasPermission('aiCourse:course-type:query')")
+    public CommonResult<List<AiCourseTypeRespVO>> getAiCourseTypeList(@Valid AiCourseTypeListReqVO listReqVO) {
+        List<AiCourseTypeDO> list = courseTypeService.getAiCourseTypeList(listReqVO);
+        return success(BeanUtils.toBean(list, AiCourseTypeRespVO.class));
+    }
+
+    @GetMapping("/simple-list")
+    @Operation(summary = "获得课程-类型列表")
+    @PreAuthorize("@ss.hasPermission('aiCourse:course-type:query')")
+    public CommonResult<List<AiCourseTypeRespVO>> getAiCourseTypeSimpleList(@Valid AiCourseTypeListReqVO listReqVO) {
+        List<AiCourseTypeDO> list = courseTypeService.getAiCourseTypeSimpleList(listReqVO);
+        return success(BeanUtils.toBean(list, AiCourseTypeRespVO.class));
+    }
+
+    @GetMapping("/export-excel")
+    @Operation(summary = "导出课程-类型 Excel")
+    @PreAuthorize("@ss.hasPermission('aiCourse:course-type:export')")
+    @ApiAccessLog(operateType = EXPORT)
+    public void exportAiCourseTypeExcel(@Valid AiCourseTypeListReqVO listReqVO,
+              HttpServletResponse response) throws IOException {
+        List<AiCourseTypeDO> list = courseTypeService.getAiCourseTypeList(listReqVO);
+        // 导出 Excel
+        ExcelUtils.write(response, "课程-类型.xls", "数据", AiCourseTypeRespVO.class,
+                        BeanUtils.toBean(list, AiCourseTypeRespVO.class));
+    }
+
+}

+ 28 - 0
byzs-aicourse/src/main/java/cn/iocoder/byzs/module/aicourse/controller/admin/aiCourseType/vo/AiCourseTypeListReqVO.java

@@ -0,0 +1,28 @@
+package cn.iocoder.byzs.module.aicourse.controller.admin.aiCourseType.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+@Schema(description = "管理后台 - 课程-类型列表 Request VO")
+@Data
+public class AiCourseTypeListReqVO {
+
+    @Schema(description = "课程类型名称")
+    private String ctType;
+
+    @Schema(description = "课程类型封面")
+    private String ctTypeImage;
+
+    @Schema(description = "课程类型父级id")
+    private Long ctParentId;
+
+    @Schema(description = "课程节点")
+    private String ctTypeNode;
+
+    @Schema(description = "课程排序")
+    private Integer ctTypeSort;
+
+    @Schema(description = "课程类型描述")
+    private String ctTypeDescribe;
+
+}

+ 41 - 0
byzs-aicourse/src/main/java/cn/iocoder/byzs/module/aicourse/controller/admin/aiCourseType/vo/AiCourseTypeRespVO.java

@@ -0,0 +1,41 @@
+package cn.iocoder.byzs.module.aicourse.controller.admin.aiCourseType.vo;
+
+import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
+import com.alibaba.excel.annotation.ExcelProperty;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+@Schema(description = "管理后台 - 课程-类型 Response VO")
+@Data
+@ExcelIgnoreUnannotated
+public class AiCourseTypeRespVO {
+
+    @Schema(description = "租户编号")
+    private Long tenantId;
+
+    @Schema(description = "课程类型id", requiredMode = Schema.RequiredMode.REQUIRED)
+    @ExcelProperty("课程类型id")
+    private Long id;
+
+    @Schema(description = "课程类型名称")
+    @ExcelProperty("课程类型名称")
+    private String ctType;
+
+    @Schema(description = "课程类型封面")
+    private String ctTypeImage;
+
+    @Schema(description = "课程类型父级id")
+    @ExcelProperty("课程类型父级id")
+    private Integer ctParentId;
+
+    @Schema(description = "课程节点")
+    private String ctTypeNode;
+
+    @Schema(description = "课程排序")
+    private Integer ctTypeSort;
+
+    @Schema(description = "课程类型描述")
+    @ExcelProperty("课程类型描述")
+    private String ctTypeDescribe;
+
+}

+ 31 - 0
byzs-aicourse/src/main/java/cn/iocoder/byzs/module/aicourse/controller/admin/aiCourseType/vo/AiCourseTypeSaveReqVO.java

@@ -0,0 +1,31 @@
+package cn.iocoder.byzs.module.aicourse.controller.admin.aiCourseType.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+@Schema(description = "管理后台 - 课程-类型新增/修改 Request VO")
+@Data
+public class AiCourseTypeSaveReqVO {
+
+    @Schema(description = "课程类型id", requiredMode = Schema.RequiredMode.REQUIRED)
+    private Long id;
+
+    @Schema(description = "课程类型名称")
+    private String ctType;
+
+    @Schema(description = "课程类型封面")
+    private String ctTypeImage;
+
+    @Schema(description = "课程类型父级id")
+    private Long ctParentId;
+
+    @Schema(description = "课程节点")
+    private String ctTypeNode;
+
+    @Schema(description = "课程排序")
+    private Integer ctTypeSort;
+
+    @Schema(description = "课程类型描述")
+    private String ctTypeDescribe;
+
+}

+ 118 - 0
byzs-aicourse/src/main/java/cn/iocoder/byzs/module/aicourse/dal/dataobject/aiCourse/AiCourseDO.java

@@ -0,0 +1,118 @@
+package cn.iocoder.byzs.module.aicourse.dal.dataobject.aiCourse;
+
+import cn.iocoder.byzs.framework.mybatis.core.dataobject.BaseDO;
+import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.*;
+
+/**
+ * 课程 DO
+ *
+ * @author lyb
+ */
+@TableName("ai_course")
+@KeySequence("ai_course_seq")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class AiCourseDO extends BaseDO {
+
+    /**
+     * 租户编号
+     */
+    private Long tenantId;
+
+    /**
+     * 课程d
+     */
+    @TableId
+    private Long id;
+    /**
+     * 课程名称
+     */
+    private String acName;
+
+    /**
+     * 课程内容类型
+     */
+    private String acContentType;
+
+    /**
+     * 课程内容
+     */
+    private String acContent;
+
+    /**
+     * 简介
+     */
+    private String blocklyInfo;
+    /**
+     * 人物图标
+     */
+    private String blocklyUserImage;
+    /**
+     * 人物初始方向
+     */
+    private Integer blocklyUserDirection;
+    /**
+     * 地图方格大小
+     */
+    private String blocklyTileSize;
+    /**
+     * 地图背景图
+     */
+    private String blocklyBackground;
+    /**
+     * 地图开始坐标
+     */
+    private String blocklyStartPoint;
+    /**
+     * 地图结束坐标
+     */
+    private String blocklyEndPoint;
+    /**
+     * 地图可行走坐标
+     */
+    private String blocklyWalkablePoints;
+    /**
+     * 特殊方块
+     */
+    private String blocklySpecialBlocks;
+
+    /**
+     * 路径列表
+     */
+    private String blocklyRouteList;
+
+
+    /**
+     * 课程是否有检查
+     *
+     */
+    private String acIsInspect;
+    /**
+     * 课程类型
+     */
+    private Long acType;
+    /**
+     * 课程标签
+     *
+     */
+    private String acLabel;
+
+    /**
+     * 课程排序
+     */
+    private Integer acOrder;
+    /**
+     * 课程状态
+     *
+     */
+    private String acStatus;
+
+
+}

+ 84 - 0
byzs-aicourse/src/main/java/cn/iocoder/byzs/module/aicourse/dal/dataobject/aiCourseConfig/AiCourseConfigDO.java

@@ -0,0 +1,84 @@
+package cn.iocoder.byzs.module.aicourse.dal.dataobject.aiCourseConfig;
+
+import cn.iocoder.byzs.framework.mybatis.core.dataobject.BaseDO;
+import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.*;
+
+/**
+ * 课程配置 DO
+ *
+ * @author 博雅智算源码
+ */
+@TableName("ai_course_config")
+@KeySequence("ai_course_config_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class AiCourseConfigDO extends BaseDO {
+
+    /**
+     * 租户编号
+     */
+    private Long tenantId;
+
+    /**
+     * 课程配置id
+     */
+    @TableId
+    private Long id;
+    /**
+     * 课程id
+     */
+    private Long ccCourseId;
+    /**
+     * 课程暂停时长
+     */
+    private Integer ccTime;
+
+    /**
+     * 试题来源
+     */
+    private String ccQuestSource;
+
+    /**
+     * 试题内容
+     */
+    private String ccQuestContent;
+
+    /**
+     * 试题选项
+     */
+    private String ccQuestOption;
+
+    /**
+     * ai问题提示
+     */
+    private String ccAiQuestTip;
+
+    /**
+     * ai答案
+     */
+    private String ccAiAnswer;
+
+    /**
+     * 答案
+     */
+    private String ccAnswer;
+    /**
+     * 试题id
+     */
+    private Integer ccQuestId;
+    /**
+     * 是否显示答案
+     *
+     * 枚举 {@link TODO infra_boolean_string 对应的类}
+     */
+    private String ccAnswerJudge;
+
+
+}

+ 65 - 0
byzs-aicourse/src/main/java/cn/iocoder/byzs/module/aicourse/dal/dataobject/aiCourseProgress/AiCourseProgressDO.java

@@ -0,0 +1,65 @@
+package cn.iocoder.byzs.module.aicourse.dal.dataobject.aiCourseProgress;
+
+import cn.iocoder.byzs.framework.mybatis.core.dataobject.BaseDO;
+import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.*;
+import lombok.experimental.Accessors;
+
+/**
+ * 评估报告-课程开课率 DO
+ *
+ * @author lyb
+ */
+@TableName("ai_report_progress")
+@KeySequence("ai_report_progress_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+@Accessors(chain = true)
+public class AiCourseProgressDO extends BaseDO {
+
+    /**
+     * 主键id
+     */
+    @TableId
+    private Long brpId;
+    /**
+     * 用户id
+     */
+    private Long brpUserId;
+    /**
+     * 主题id
+     */
+    @NonNull
+    private Long brpZtId;
+    /**
+     * 课程类型id
+     */
+    @NonNull
+    private Long brpCtId;
+    /**
+     * 课程id
+     */
+    @NonNull
+    private Long brpCourseId;
+    /**
+     * 课程配置id
+     */
+    private Long brpCourseConfigId;
+    /**
+     * 进度分类
+     */
+    @NonNull
+    private String brpType;
+    /**
+     * 进度(课程开课率、课程互动率、ai问答次数)
+     */
+    private Double brpProgress;
+
+
+}

+ 60 - 0
byzs-aicourse/src/main/java/cn/iocoder/byzs/module/aicourse/dal/dataobject/aiCourseType/AiCourseTypeDO.java

@@ -0,0 +1,60 @@
+package cn.iocoder.byzs.module.aicourse.dal.dataobject.aiCourseType;
+
+import cn.iocoder.byzs.framework.mybatis.core.dataobject.BaseDO;
+import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.*;
+
+/**
+ * 课程-类型 DO
+ *
+ * @author lyb
+ */
+@TableName("ai_course_type")
+@KeySequence("ai_course_type_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class AiCourseTypeDO extends BaseDO {
+
+    public static final Long CT_PARENT_ID_ROOT = 0L;
+
+    /**
+     * 课程类型id
+     */
+    @TableId
+    private Long id;
+    /**
+     * 课程类型名称
+     */
+    private String ctType;
+    /**
+     * 课程类型封面
+     */
+    private String ctTypeImage;
+    /**
+     * 课程类型父级id
+     */
+    private Long ctParentId;
+    /**
+     * 课程节点
+     */
+    private String ctTypeNode;
+    /**
+     * 课程排序
+     */
+    private Integer ctTypeSort;
+    /**
+     * 课程类型描述
+     */
+    private String ctTypeDescribe;
+    /**
+     * 租户id
+     */
+    private Long tenantId;
+
+}

+ 35 - 0
byzs-aicourse/src/main/java/cn/iocoder/byzs/module/aicourse/dal/mysql/aiCourse/AiCourseMapper.java

@@ -0,0 +1,35 @@
+package cn.iocoder.byzs.module.aicourse.dal.mysql.aiCourse;
+
+import cn.iocoder.byzs.framework.common.pojo.PageResult;
+import cn.iocoder.byzs.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.byzs.framework.mybatis.core.query.LambdaQueryWrapperX;
+import cn.iocoder.byzs.module.aicourse.controller.admin.aiCourse.vo.AiCoursePageReqVO;
+import cn.iocoder.byzs.module.aicourse.dal.dataobject.aiCourse.AiCourseDO;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.List;
+
+/**
+ * 课程 Mapper
+ *
+ * @author lyb
+ */
+@Mapper
+public interface AiCourseMapper extends BaseMapperX<AiCourseDO> {
+
+    default PageResult<AiCourseDO> selectPage(AiCoursePageReqVO reqVO) {
+        return selectPage(reqVO, new LambdaQueryWrapperX<AiCourseDO>()
+                .likeIfPresent(AiCourseDO::getAcName, reqVO.getAcName())
+                .eqIfPresent(AiCourseDO::getAcContentType, reqVO.getAcContentType())
+                .eqIfPresent(AiCourseDO::getAcContent, reqVO.getAcContent())
+                .eqIfPresent(AiCourseDO::getAcIsInspect, reqVO.getAcIsInspect())
+                .eqIfPresent(AiCourseDO::getAcType, reqVO.getAcType())
+                .eqIfPresent(AiCourseDO::getAcLabel, reqVO.getAcLabel())
+                .eqIfPresent(AiCourseDO::getAcStatus, reqVO.getAcStatus())
+                .orderByAsc(AiCourseDO::getAcLabel, AiCourseDO::getAcOrder));
+    }
+
+    List<AiCoursePageReqVO> selectAiCoursePage(AiCoursePageReqVO reqVO);
+    List<AiCoursePageReqVO> selectAiCourseList(AiCoursePageReqVO reqVO);
+    int selectAiCoursePageCount(AiCoursePageReqVO reqVO);
+}

+ 39 - 0
byzs-aicourse/src/main/java/cn/iocoder/byzs/module/aicourse/dal/mysql/aiCourseConfig/AiCourseConfigMapper.java

@@ -0,0 +1,39 @@
+package cn.iocoder.byzs.module.aicourse.dal.mysql.aiCourseConfig;
+
+import cn.iocoder.byzs.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.byzs.module.aicourse.controller.admin.aiCourseConfig.vo.AiCourseConfigPageReqVO;
+import cn.iocoder.byzs.module.aicourse.controller.admin.aiCourseConfig.vo.AiCourseConfigRespVO;
+import cn.iocoder.byzs.module.aicourse.dal.dataobject.aiCourseConfig.AiCourseConfigDO;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
+
+/**
+ * 课程配置 Mapper
+ *
+ * @author 博雅智算源码
+ */
+@Mapper
+public interface AiCourseConfigMapper extends BaseMapperX<AiCourseConfigDO> {
+
+//    default PageResult<AiCourseConfigDO> selectPage(AiCourseConfigPageReqVO reqVO) {
+//        return selectPage(reqVO, new LambdaQueryWrapperX<AiCourseConfigDO>()
+//                .eqIfPresent(AiCourseConfigDO::getCcCourseId, reqVO.getCcCourseId())
+//                .eqIfPresent(AiCourseConfigDO::getCcTime, reqVO.getCcTime())
+//                .eqIfPresent(AiCourseConfigDO::getCcQuestId, reqVO.getCcQuestId())
+//                .eqIfPresent(AiCourseConfigDO::getCcAnswerJudge, reqVO.getCcAnswerJudge())
+//                .orderByDesc(AiCourseConfigDO::getId));
+//    }
+
+    List<AiCourseConfigPageReqVO> selectAiCourseConfigPage(AiCourseConfigPageReqVO reqVO);
+    Integer selectAiCourseConfigCount(AiCourseConfigPageReqVO reqVO);
+
+    /**
+     * 根据课程id列表查询课程配置
+     *
+     * @param ccCourseIds 课程id列表
+     * @return 课程配置列表
+     */
+    List<AiCourseConfigRespVO> getAiCourseConfigQuestionByCourseIds(@Param("ccCourseIds") List<Long> ccCourseIds);
+}

+ 15 - 0
byzs-aicourse/src/main/java/cn/iocoder/byzs/module/aicourse/dal/mysql/aiCourseProgress/AiCourseProgressMapper.java

@@ -0,0 +1,15 @@
+package cn.iocoder.byzs.module.aicourse.dal.mysql.aiCourseProgress;
+
+import cn.iocoder.byzs.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.byzs.module.aicourse.dal.dataobject.aiCourseProgress.AiCourseProgressDO;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * 课程-类型 Mapper
+ *
+ * @author lyb
+ */
+@Mapper
+public interface AiCourseProgressMapper extends BaseMapperX<AiCourseProgressDO> {
+
+}

+ 66 - 0
byzs-aicourse/src/main/java/cn/iocoder/byzs/module/aicourse/dal/mysql/aiCourseType/AiCourseTypeMapper.java

@@ -0,0 +1,66 @@
+package cn.iocoder.byzs.module.aicourse.dal.mysql.aiCourseType;
+
+import cn.iocoder.byzs.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.byzs.framework.mybatis.core.query.LambdaQueryWrapperX;
+import cn.iocoder.byzs.module.aicourse.controller.admin.aiCourseType.vo.AiCourseTypeListReqVO;
+import cn.iocoder.byzs.module.aicourse.dal.dataobject.aiCourseType.AiCourseTypeDO;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.List;
+
+/**
+ * 课程-类型 Mapper
+ *
+ * @author lyb
+ */
+@Mapper
+public interface AiCourseTypeMapper extends BaseMapperX<AiCourseTypeDO> {
+
+    default List<AiCourseTypeDO> selectList(AiCourseTypeListReqVO reqVO) {
+        LambdaQueryWrapperX<AiCourseTypeDO> queryWrapper = getAiCourseTypeDOLambdaQueryWrapperX(reqVO);
+
+        // 使用BaseMapperX的selectList方法执行查询
+        return selectList(queryWrapper);
+    }
+
+    default List<AiCourseTypeDO> selectSimpleList(AiCourseTypeListReqVO reqVO) {
+        LambdaQueryWrapperX<AiCourseTypeDO> queryWrapper = getAiCourseTypeDOLambdaQueryWrapperX(reqVO);
+
+        // 使用BaseMapperX的selectList方法执行查询
+        return selectList(queryWrapper.select(AiCourseTypeDO::getId, AiCourseTypeDO::getCtParentId, AiCourseTypeDO::getCtType, AiCourseTypeDO::getCtTypeNode, AiCourseTypeDO::getCtTypeSort));
+    }
+
+    private static LambdaQueryWrapperX<AiCourseTypeDO> getAiCourseTypeDOLambdaQueryWrapperX(AiCourseTypeListReqVO reqVO) {
+        LambdaQueryWrapperX<AiCourseTypeDO> queryWrapper = new LambdaQueryWrapperX<AiCourseTypeDO>()
+                .likeIfPresent(AiCourseTypeDO::getCtType, reqVO.getCtType())
+                .eqIfPresent(AiCourseTypeDO::getCtParentId, reqVO.getCtParentId());
+
+        // 处理getCtTypeNode的特殊条件:不为空且不等于0时,同时查询该值和0
+        Object ctTypeNode = reqVO.getCtTypeNode();
+        if (ctTypeNode != null && !ctTypeNode.toString().equals("0")) {
+            queryWrapper.and(wrapper -> wrapper
+                    .eq(AiCourseTypeDO::getCtTypeNode, ctTypeNode)
+                    .or()
+                    .eq(AiCourseTypeDO::getCtTypeNode, 0)
+            );
+        } else {
+            // 保持原有的eqIfPresent行为
+            queryWrapper.eqIfPresent(AiCourseTypeDO::getCtTypeNode, ctTypeNode);
+        }
+
+        // 完成构建queryWrapper并添加剩余条件
+        queryWrapper
+                .likeIfPresent(AiCourseTypeDO::getCtTypeDescribe, reqVO.getCtTypeDescribe())
+                .orderByAsc(AiCourseTypeDO::getCtTypeNode, AiCourseTypeDO::getCtTypeSort);
+        return queryWrapper;
+    }
+
+	default AiCourseTypeDO selectByCtParentIdAndCtType(Long ctParentId, String ctType) {
+	    return selectOne(AiCourseTypeDO::getCtParentId, ctParentId, AiCourseTypeDO::getCtType, ctType);
+	}
+
+    default Long selectCountByCtParentId(Long ctParentId) {
+        return selectCount(AiCourseTypeDO::getCtParentId, ctParentId);
+    }
+
+}

+ 25 - 0
byzs-aicourse/src/main/java/cn/iocoder/byzs/module/aicourse/enums/ErrorCodeConstants.java

@@ -0,0 +1,25 @@
+package cn.iocoder.byzs.module.aicourse.enums;
+
+import cn.iocoder.byzs.framework.common.exception.ErrorCode;
+
+/**
+ * 课程 错误码枚举类
+ * <p>
+ * 课程,使用 1-018-000-000 段
+ */
+public interface ErrorCodeConstants {
+    // ========== 课程 1_018_000_000 ==========
+    ErrorCode BLOCKLY_NOT_EXISTS = new ErrorCode(1_018_000_000, "课程不存在");
+
+    // ========== 课程-类型 1_018_010_000 ==========
+    ErrorCode BLOCKLY_TYPE_NOT_EXISTS = new ErrorCode(1_018_010_001, "-课程-类型不存在");
+    ErrorCode BLOCKLY_TYPE_EXITS_CHILDREN = new ErrorCode(1_018_010_002, "存在存在子课程-类型,无法删除");
+    ErrorCode BLOCKLY_TYPE_PARENT_NOT_EXITS = new ErrorCode(1_018_010_003, "父级课程-类型不存在");
+    ErrorCode BLOCKLY_TYPE_PARENT_ERROR = new ErrorCode(1_018_010_004, "不能设置自己为父课程-类型");
+    ErrorCode BLOCKLY_TYPE_CT_TYPE_DUPLICATE = new ErrorCode(1_018_010_005, "已经存在该课程类型名称的课程-类型");
+    ErrorCode BLOCKLY_TYPE_PARENT_IS_CHILD = new ErrorCode(1_018_010_006, "不能设置自己的子AiCourseType为父AiCourseType");
+
+
+    // ========== 课程配置 1_019_020_000 ==========
+    ErrorCode BLOCKLY_CONFIG_NOT_EXISTS = new ErrorCode(1_019_020_000, "课程配置不存在");
+}

+ 120 - 0
byzs-aicourse/src/main/java/cn/iocoder/byzs/module/aicourse/job/JobAiCourseAotoRoleJob.java

@@ -0,0 +1,120 @@
+package cn.iocoder.byzs.module.aicourse.job;
+
+import cn.iocoder.byzs.framework.quartz.core.handler.JobHandler;
+import cn.iocoder.byzs.framework.tenant.core.aop.TenantIgnore;
+import cn.iocoder.byzs.module.aicourse.dal.dataobject.aiCourseType.AiCourseTypeDO;
+import cn.iocoder.byzs.module.aicourse.service.aiCourseType.AiCourseTypeService;
+import cn.iocoder.byzs.module.system.dal.dataobject.permission.RoleDO;
+import cn.iocoder.byzs.module.system.service.permission.RoleService;
+import jakarta.annotation.Resource;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+
+import java.util.Comparator;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * 每天开2节blockly课程 Job
+ *
+ * @author j-sentinel
+ */
+@Slf4j
+@Component
+public class JobAiCourseAotoRoleJob implements JobHandler {
+
+    @Resource
+    private AiCourseTypeService blocklyTypeService;
+
+    @Resource
+    private RoleService roleService;
+
+    // 定义常量
+    private static final Integer autoCount = 2; // 自动分配的课程数量
+
+    @Override
+    @TenantIgnore
+    public String execute(String roleIdParam) {
+        log.info("开始执行自动分配blockly课程权限任务, 角色ID: {}", roleIdParam);
+        Long roleId = Long.parseLong(roleIdParam);
+
+        try {
+            // 1. 获取指定租户下的角色信息
+            RoleDO role = roleService.getRole(roleId);
+            if (role == null) {
+                log.warn("未找到指定角色, roleId: {}", roleId);
+                return "未找到指定角色";
+            }
+
+            // 2. 获取角色当前的blockly权限
+            Set<Long> existingAiCourseIds = role.getDataScopeAiCourseIds();
+            if (existingAiCourseIds == null) {
+                existingAiCourseIds = new HashSet<>();
+            }
+
+            // 3. 获取所有blockly主题列表(父级ID为0的类型)
+            List<AiCourseTypeDO> themes = blocklyTypeService.getAiCourseTypeList(
+                    new cn.iocoder.byzs.module.aicourse.controller.admin.aiCourseType.vo.AiCourseTypeListReqVO()
+                            .setCtParentId(AiCourseTypeDO.CT_PARENT_ID_ROOT)
+            );
+
+            // 按排序字段升序排列主题
+            themes.sort(Comparator.comparing(AiCourseTypeDO::getCtTypeSort, Comparator.nullsLast(Comparator.naturalOrder())));
+
+            log.info("获取到 {} 个主题", themes.size());
+
+            // 4. 收集需要新增的课程ID
+            Set<Long> newAiCourseIds = new HashSet<>();
+
+            // 5. 遍历主题,查找每个主题下的课程类型和课程
+            for (AiCourseTypeDO theme : themes) {
+                if (newAiCourseIds.size() >= autoCount) {
+                    break; // 已经找到2个未配置的课程,跳出循环
+                }
+
+                // 获取当前主题下的所有课程类型
+                List<AiCourseTypeDO> courseTypes = blocklyTypeService.getAiCourseTypeList(
+                        new cn.iocoder.byzs.module.aicourse.controller.admin.aiCourseType.vo.AiCourseTypeListReqVO()
+                                .setCtParentId(theme.getId())
+                );
+
+                // 按排序字段升序排列课程类型
+                courseTypes.sort(Comparator.comparing(AiCourseTypeDO::getCtTypeSort, Comparator.nullsLast(Comparator.naturalOrder())));
+
+                log.info("主题 {} 下有 {} 个课程类型", theme.getCtType(), courseTypes.size());
+
+                // 遍历课程类型,获取课程
+                for (AiCourseTypeDO courseType : courseTypes) {
+                    if (newAiCourseIds.size() >= autoCount) {
+                        break; // 已经找到2个未配置的课程,跳出循环
+                    }
+
+                    // 检查该课程是否已经配置了权限
+                    if (!existingAiCourseIds.contains(courseType.getId())) {
+                        newAiCourseIds.add(courseType.getId());
+                        log.info("找到未配置权限的课程: ID={}, 名称={}", courseType.getId(), courseType.getCtType());
+                    }
+                }
+            }
+
+            // 6. 如果找到了新的课程ID,更新角色权限
+            if (!newAiCourseIds.isEmpty()) {
+                // 合并现有的权限和新权限
+                Set<Long> updatedAiCourseIds = new HashSet<>(existingAiCourseIds);
+                updatedAiCourseIds.addAll(newAiCourseIds);
+
+                // 更新角色的ai课程权限
+                roleService.updateRoleAiCourseScope(roleId, updatedAiCourseIds);
+                log.info("成功为角色 {} 添加了 {} 个新的blockly课程权限: {}", roleId, updatedAiCourseIds.size(), updatedAiCourseIds);
+                return String.format("成功添加了 %d 个新的blockly课程权限", updatedAiCourseIds.size());
+            } else {
+                log.info("未找到需要新增的blockly课程权限");
+                return "未找到需要新增的blockly课程权限";
+            }
+        } catch (Exception e) {
+            log.error("执行自动分配blockly课程权限任务时发生错误", e);
+            return "执行任务时发生错误: " + e.getMessage();
+        }
+    }
+}

+ 72 - 0
byzs-aicourse/src/main/java/cn/iocoder/byzs/module/aicourse/service/aiCourse/AiCourseService.java

@@ -0,0 +1,72 @@
+package cn.iocoder.byzs.module.aicourse.service.aiCourse;
+
+import cn.iocoder.byzs.framework.common.pojo.PageResult;
+import cn.iocoder.byzs.module.aicourse.controller.admin.aiCourse.vo.AiCoursePageReqVO;
+import cn.iocoder.byzs.module.aicourse.controller.admin.aiCourse.vo.AiCourseRespVO;
+import cn.iocoder.byzs.module.aicourse.controller.admin.aiCourse.vo.AiCourseSaveReqVO;
+import cn.iocoder.byzs.module.aicourse.dal.dataobject.aiCourse.AiCourseDO;
+import jakarta.validation.Valid;
+
+import java.util.List;
+
+/**
+ * 课程 Service 接口
+ *
+ * @author lyb
+ */
+public interface AiCourseService {
+
+    /**
+     * 创建课程
+     *
+     * @param createReqVO 创建信息
+     * @return 编号
+     */
+    Long createAiCourse(@Valid AiCourseSaveReqVO createReqVO);
+
+    /**
+     * 更新课程
+     *
+     * @param updateReqVO 更新信息
+     */
+    void updateAiCourse(@Valid AiCourseSaveReqVO updateReqVO);
+
+    /**
+     * 删除课程
+     *
+     * @param id 编号
+     */
+    void deleteAiCourse(Long id);
+
+    /**
+    * 批量删除课程
+    *
+    * @param ids 编号
+    */
+    void deleteAiCourseListByIds(List<Long> ids);
+
+    /**
+     * 获得课程
+     *
+     * @param id 编号
+     * @return 课程
+     */
+    AiCourseDO getAiCourse(Long id);
+
+    /**
+     * 获得课程分页
+     *
+     * @param pageReqVO 分页查询
+     * @return 课程分页
+     */
+    PageResult<AiCourseRespVO> getAiCoursePage(AiCoursePageReqVO pageReqVO);
+
+    /**
+     * 获得课程
+     *
+     * @param pageReqVO 查询
+     * @return 课程分页
+     */
+    List<AiCoursePageReqVO> getAiCourseList(AiCoursePageReqVO pageReqVO);
+
+}

+ 185 - 0
byzs-aicourse/src/main/java/cn/iocoder/byzs/module/aicourse/service/aiCourse/AiCourseServiceImpl.java

@@ -0,0 +1,185 @@
+package cn.iocoder.byzs.module.aicourse.service.aiCourse;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.StrUtil;
+import cn.hutool.extra.spring.SpringUtil;
+import cn.iocoder.byzs.framework.common.pojo.PageResult;
+import cn.iocoder.byzs.framework.common.util.object.BeanUtils;
+import cn.iocoder.byzs.framework.mybatis.core.query.LambdaQueryWrapperX;
+import cn.iocoder.byzs.module.aicourse.controller.admin.aiCourse.vo.AiCoursePageReqVO;
+import cn.iocoder.byzs.module.aicourse.controller.admin.aiCourse.vo.AiCourseRespVO;
+import cn.iocoder.byzs.module.aicourse.controller.admin.aiCourse.vo.AiCourseSaveReqVO;
+import cn.iocoder.byzs.module.aicourse.dal.dataobject.aiCourse.AiCourseDO;
+import cn.iocoder.byzs.module.aicourse.dal.dataobject.aiCourseProgress.AiCourseProgressDO;
+import cn.iocoder.byzs.module.aicourse.dal.dataobject.aiCourseType.AiCourseTypeDO;
+import cn.iocoder.byzs.module.aicourse.dal.mysql.aiCourse.AiCourseMapper;
+import cn.iocoder.byzs.module.aicourse.dal.mysql.aiCourseProgress.AiCourseProgressMapper;
+import cn.iocoder.byzs.module.aicourse.dal.mysql.aiCourseType.AiCourseTypeMapper;
+import cn.iocoder.byzs.module.infra.dal.dataobject.file.FileDO;
+import cn.iocoder.byzs.module.infra.framework.file.core.client.FileClient;
+import cn.iocoder.byzs.module.infra.service.file.FileConfigService;
+import cn.iocoder.byzs.module.infra.service.file.FileServiceImpl;
+import jakarta.annotation.Resource;
+import org.springframework.scheduling.annotation.Async;
+import org.springframework.stereotype.Service;
+import org.springframework.validation.annotation.Validated;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+import static cn.iocoder.byzs.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.byzs.module.aicourse.enums.ErrorCodeConstants.*;
+
+/**
+ * 课程 Service 实现类
+ *
+ * @author lyb
+ */
+@Service
+@Validated
+public class AiCourseServiceImpl implements AiCourseService {
+
+    @Resource
+    private AiCourseMapper aiCourseMapper;
+    @Resource
+    private AiCourseTypeMapper aiCourseTypeMapper;
+    @Resource
+    private FileServiceImpl fileService;
+    @Resource
+    private FileConfigService fileConfigService;
+    @Resource
+    private AiCourseProgressMapper aiCourseProgressMapper;
+
+    @Override
+    public Long createAiCourse(AiCourseSaveReqVO createReqVO) {
+        // 插入
+        AiCourseDO blockly = BeanUtils.toBean(createReqVO, AiCourseDO.class);
+        aiCourseMapper.insert(blockly);
+
+        if (blockly.getAcContentType().equals("video")) {
+            getSelf().asymcVideoFfmpeg(blockly);
+        }
+        // 返回
+        return blockly.getId();
+    }
+
+    @Override
+    public void updateAiCourse(AiCourseSaveReqVO updateReqVO) {
+        // 校验存在
+        AiCourseDO blocklyDO = validateAiCourseExists(updateReqVO.getId());
+        // 更新
+        AiCourseDO updateObj = BeanUtils.toBean(updateReqVO, AiCourseDO.class);
+        aiCourseMapper.updateById(updateObj);
+
+        //同步进度表中的课程类型
+        if (!blocklyDO.getAcType().equals(updateObj.getAcType())) {
+            AiCourseTypeDO blocklyTypeDO = aiCourseTypeMapper.selectById(updateObj.getAcType());
+            // 使用条件更新,将brpCtId等于旧课程类型ID的记录更新为新课程类型ID
+            aiCourseProgressMapper.update(new AiCourseProgressDO().setBrpCtId(updateObj.getAcType()).setBrpZtId(blocklyTypeDO.getCtParentId()),
+                                         new LambdaQueryWrapperX<AiCourseProgressDO>().eq(AiCourseProgressDO::getBrpCtId, blocklyDO.getAcType()));
+        }
+
+        if (updateObj.getAcContentType().equals("video")) {
+            getSelf().asymcVideoFfmpeg(updateObj);
+        }
+    }
+
+    @Async
+    public void asymcVideoFfmpeg(AiCourseDO blockly) {
+        //只对mp4文件进行处理
+        String blocklyVideoPath = blockly.getAcContent();
+        String ext = blocklyVideoPath.substring(blocklyVideoPath.lastIndexOf("."));
+        if (StrUtil.isBlank(ext) || ext.equals(".m3u8")) {
+            return;
+        }
+
+        //获取相对文件路径
+        FileDO fileDO= fileService.getFilePathByUrl(blockly.getAcContent());
+        if (fileDO == null || fileDO.getPath() == null) {
+            return;
+        }
+
+        //切片
+        FileClient client = fileConfigService.getMasterFileClient();
+        String m3u8Path = client.ffmpegHts(fileDO.getPath());
+        if(StrUtil.isNotEmpty(m3u8Path)){
+            aiCourseMapper.updateById(new AiCourseDO().setId(blockly.getId()).setAcContent(m3u8Path));
+            fileDO.setUrl(m3u8Path);
+            fileService.updateFileById(fileDO);
+
+            // 从文件存储器中删除【暂时不删除mp4文件,删除时需要同步更改fileDo中path的相对路径更改成m3u8文件】
+//            try {
+//                client.delete(blocklyVideoPath);
+//            } catch (Exception e) {
+//                throw new RuntimeException(e);
+//            }
+        }
+    }
+
+    @Override
+    public void deleteAiCourse(Long id) {
+        // 校验存在
+        validateAiCourseExists(id);
+        // 删除
+        aiCourseMapper.deleteById(id);
+    }
+
+    @Override
+        public void deleteAiCourseListByIds(List<Long> ids) {
+        // 校验存在
+        validateAiCourseExists(ids);
+        // 删除
+        aiCourseMapper.deleteByIds(ids);
+        }
+
+    private void validateAiCourseExists(List<Long> ids) {
+        List<AiCourseDO> list = aiCourseMapper.selectByIds(ids);
+        if (CollUtil.isEmpty(list) || list.size() != ids.size()) {
+            throw exception(BLOCKLY_NOT_EXISTS);
+        }
+    }
+
+    private AiCourseDO validateAiCourseExists(Long id) {
+        AiCourseDO blocklyDO = aiCourseMapper.selectById(id);
+        if (blocklyDO == null) {
+            throw exception(BLOCKLY_NOT_EXISTS);
+        }
+        return blocklyDO;
+    }
+
+    @Override
+    public AiCourseDO getAiCourse(Long id) {
+        return aiCourseMapper.selectById(id);
+    }
+
+    @Override
+    public PageResult<AiCourseRespVO> getAiCoursePage(AiCoursePageReqVO pageReqVO) {
+        pageReqVO.setPageNo((pageReqVO.getPageNo() - 1) * pageReqVO.getPageSize());
+        List<AiCoursePageReqVO> blocklyPageReqVOS = aiCourseMapper.selectAiCoursePage(pageReqVO);
+
+        // 获取总记录数
+        int total = aiCourseMapper.selectAiCoursePageCount(pageReqVO);
+        // 调用服务层方法获取分页结果
+        return new PageResult<>(
+                blocklyPageReqVOS.stream()
+                        .map(doItem -> BeanUtils.toBean(doItem, AiCourseRespVO.class))
+                        .collect(Collectors.toList()),
+                (long) total
+        );
+    }
+
+    @Override
+    public List<AiCoursePageReqVO> getAiCourseList(AiCoursePageReqVO pageReqVO) {
+        return aiCourseMapper.selectAiCourseList(pageReqVO);
+    }
+
+    /**
+     * 获得自身的代理对象,解决 AOP 生效问题
+     *
+     * @return 自己
+     */
+    private AiCourseServiceImpl getSelf() {
+        return SpringUtil.getBean(getClass());
+    }
+
+}

+ 81 - 0
byzs-aicourse/src/main/java/cn/iocoder/byzs/module/aicourse/service/aiCourseConfig/AiCourseConfigService.java

@@ -0,0 +1,81 @@
+package cn.iocoder.byzs.module.aicourse.service.aiCourseConfig;
+
+import cn.iocoder.byzs.module.aicourse.controller.admin.aiCourseConfig.vo.AiCourseConfigPageReqVO;
+import cn.iocoder.byzs.module.aicourse.controller.admin.aiCourseConfig.vo.AiCourseConfigRespVO;
+import cn.iocoder.byzs.module.aicourse.controller.admin.aiCourseConfig.vo.AiCourseConfigSaveReqVO;
+import cn.iocoder.byzs.module.aicourse.dal.dataobject.aiCourseConfig.AiCourseConfigDO;
+import cn.iocoder.byzs.framework.common.pojo.PageResult;
+import jakarta.validation.Valid;
+
+import java.util.List;
+
+/**
+ * 课程配置 Service 接口
+ *
+ * @author 博雅智算源码
+ */
+public interface AiCourseConfigService {
+
+    /**
+     * 创建课程配置
+     *
+     * @param createReqVO 创建信息
+     * @return 编号
+     */
+    Long createAiCourseConfig(@Valid AiCourseConfigSaveReqVO createReqVO);
+
+    /**
+     * 更新课程配置
+     *
+     * @param updateReqVO 更新信息
+     */
+    void updateAiCourseConfig(@Valid AiCourseConfigSaveReqVO updateReqVO);
+
+    /**
+     * 删除课程配置
+     *
+     * @param id 编号
+     */
+    void deleteAiCourseConfig(Long id);
+
+    /**
+    * 批量删除课程配置
+    *
+    * @param ids 编号
+    */
+    void deleteAiCourseConfigListByIds(List<Long> ids);
+
+    /**
+     * 获得课程配置
+     *
+     * @param id 编号
+     * @return 课程配置
+     */
+
+    AiCourseConfigDO getAiCourseConfig(Long id);
+
+    /**
+     * 获得课程配置-试题列表
+     *
+     * @param ccCourseId 课程id
+     * @return 课程配置
+     */
+    List<AiCourseConfigRespVO> getAiCourseConfigQuestion(Long ccCourseId);
+
+    /**
+     * 获得课程配置-试题列表
+     *
+     * @param ccCourseIds 课程ids
+     * @return 课程配置
+     */
+    List<AiCourseConfigRespVO> getAiCourseConfigQuestionByCourseIds(List<Long> ccCourseIds);
+
+    /**
+     * 获得课程配置分页
+     *
+     * @param pageReqVO 分页查询
+     * @return 课程配置分页
+     */
+    PageResult<AiCourseConfigPageReqVO> getAiCourseConfigPage(AiCourseConfigPageReqVO pageReqVO);
+
+}

+ 141 - 0
byzs-aicourse/src/main/java/cn/iocoder/byzs/module/aicourse/service/aiCourseConfig/AiCourseConfigServiceImpl.java

@@ -0,0 +1,141 @@
+package cn.iocoder.byzs.module.aicourse.service.aiCourseConfig;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.iocoder.byzs.framework.common.pojo.PageParam;
+import cn.iocoder.byzs.framework.common.pojo.PageResult;
+import cn.iocoder.byzs.framework.common.util.object.BeanUtils;
+import cn.iocoder.byzs.module.aicourse.controller.admin.aiCourseConfig.vo.AiCourseConfigPageReqVO;
+import cn.iocoder.byzs.module.aicourse.controller.admin.aiCourseConfig.vo.AiCourseConfigRespVO;
+import cn.iocoder.byzs.module.aicourse.controller.admin.aiCourseConfig.vo.AiCourseConfigSaveReqVO;
+import cn.iocoder.byzs.module.aicourse.dal.dataobject.aiCourseConfig.AiCourseConfigDO;
+import cn.iocoder.byzs.module.aicourse.dal.mysql.aiCourseConfig.AiCourseConfigMapper;
+import cn.iocoder.byzs.module.aicourse.service.aiCourseConfig.AiCourseConfigService;
+import cn.iocoder.byzs.module.bjdx.controller.admin.coursequestion.vo.CourseQuestionRespVO;
+import cn.iocoder.byzs.module.bjdx.service.coursequestion.CourseQuestionService;
+import jakarta.annotation.Resource;
+import org.springframework.stereotype.Service;
+import org.springframework.validation.annotation.Validated;
+
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import static cn.iocoder.byzs.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.byzs.module.aicourse.enums.ErrorCodeConstants.BLOCKLY_CONFIG_NOT_EXISTS;
+
+/**
+ * 课程配置 Service 实现类
+ *
+ * @author 博雅智算源码
+ */
+@Service
+@Validated
+public class AiCourseConfigServiceImpl implements AiCourseConfigService {
+
+    @Resource
+    private AiCourseConfigMapper aiCourseConfigMapper;
+    @Resource
+    private CourseQuestionService questionService;
+
+    @Override
+    public Long createAiCourseConfig(AiCourseConfigSaveReqVO createReqVO) {
+        // 插入
+        AiCourseConfigDO blocklyConfig = BeanUtils.toBean(createReqVO, AiCourseConfigDO.class);
+        aiCourseConfigMapper.insert(blocklyConfig);
+        // 返回
+        return blocklyConfig.getId();
+    }
+
+    @Override
+    public void updateAiCourseConfig(AiCourseConfigSaveReqVO updateReqVO) {
+        // 校验存在
+        validateAiCourseConfigExists(updateReqVO.getId());
+        // 更新
+        AiCourseConfigDO updateObj = BeanUtils.toBean(updateReqVO, AiCourseConfigDO.class);
+        aiCourseConfigMapper.updateById(updateObj);
+    }
+
+    @Override
+    public void deleteAiCourseConfig(Long id) {
+        // 校验存在
+        validateAiCourseConfigExists(id);
+        // 删除
+        aiCourseConfigMapper.deleteById(id);
+    }
+
+    @Override
+        public void deleteAiCourseConfigListByIds(List<Long> ids) {
+        // 校验存在
+        validateAiCourseConfigExists(ids);
+        // 删除
+        aiCourseConfigMapper.deleteByIds(ids);
+        }
+
+    private void validateAiCourseConfigExists(List<Long> ids) {
+        List<AiCourseConfigDO> list = aiCourseConfigMapper.selectByIds(ids);
+        if (CollUtil.isEmpty(list) || list.size() != ids.size()) {
+            throw exception(BLOCKLY_CONFIG_NOT_EXISTS);
+        }
+    }
+
+    private void validateAiCourseConfigExists(Long id) {
+        if (aiCourseConfigMapper.selectById(id) == null) {
+            throw exception(BLOCKLY_CONFIG_NOT_EXISTS);
+        }
+    }
+
+    @Override
+    public AiCourseConfigDO getAiCourseConfig(Long id) {
+        return aiCourseConfigMapper.selectById(id);
+    }
+
+    @Override
+    public List<AiCourseConfigRespVO> getAiCourseConfigQuestion(Long ccCourseId) {
+        //课程配置列表
+        AiCourseConfigPageReqVO blocklyConfigPageReqVO = new AiCourseConfigPageReqVO();
+        blocklyConfigPageReqVO.setCcCourseId(Math.toIntExact(ccCourseId))
+                .setPageSize(PageParam.PAGE_SIZE_NONE);
+        List<AiCourseConfigPageReqVO> blocklyConfigPageReqVOS = aiCourseConfigMapper.selectAiCourseConfigPage(blocklyConfigPageReqVO);
+
+        //课程配置列表转VO
+        List<AiCourseConfigRespVO> blocklyConfigList = blocklyConfigPageReqVOS.stream()
+                .map(doItem -> BeanUtils.toBean(doItem, AiCourseConfigRespVO.class))
+                .toList();
+        if (blocklyConfigList.isEmpty()) {return blocklyConfigList;}
+
+        //设置课程配置-试题列表(引用)
+        Map<Long, CourseQuestionRespVO> questionMap = questionService.setCourseQuestionList(blocklyConfigList.stream().map(AiCourseConfigRespVO::getCcQuestId).collect(Collectors.toList()));
+        blocklyConfigList.forEach(item -> item.setAiCourseQuestion(questionMap.get(item.getCcQuestId())));
+
+
+        return blocklyConfigList;
+    }
+
+    @Override
+    public List<AiCourseConfigRespVO> getAiCourseConfigQuestionByCourseIds(List<Long> ccCourseIds) {
+
+        List<AiCourseConfigRespVO> blocklyConfigList = aiCourseConfigMapper.getAiCourseConfigQuestionByCourseIds(ccCourseIds);
+
+        //设置课程配置-试题列表(引用)
+        Map<Long, CourseQuestionRespVO> questionMap = questionService.setCourseQuestionList(blocklyConfigList.stream().map(AiCourseConfigRespVO::getCcQuestId).collect(Collectors.toList()));
+        blocklyConfigList.forEach(item -> item.setAiCourseQuestion(questionMap.get(item.getCcQuestId())));
+
+        return blocklyConfigList;
+    }
+
+    @Override
+    public PageResult<AiCourseConfigPageReqVO> getAiCourseConfigPage(AiCourseConfigPageReqVO pageReqVO) {
+        pageReqVO.setPageNo((pageReqVO.getPageNo() - 1) * pageReqVO.getPageSize());
+        List<AiCourseConfigPageReqVO> blocklyConfigPageReqVOS = aiCourseConfigMapper.selectAiCourseConfigPage(pageReqVO);
+        Integer count = aiCourseConfigMapper.selectAiCourseConfigCount(pageReqVO);
+
+        // 调用服务层方法获取分页结果
+        return new PageResult<>(
+                blocklyConfigPageReqVOS.stream()
+                       .map(doItem -> BeanUtils.toBean(doItem, AiCourseConfigPageReqVO.class))
+                       .collect(Collectors.toList()),
+                (long) count
+        );
+    }
+
+}

+ 64 - 0
byzs-aicourse/src/main/java/cn/iocoder/byzs/module/aicourse/service/aiCourseType/AiCourseTypeService.java

@@ -0,0 +1,64 @@
+package cn.iocoder.byzs.module.aicourse.service.aiCourseType;
+
+import cn.iocoder.byzs.module.aicourse.controller.admin.aiCourseType.vo.AiCourseTypeListReqVO;
+import cn.iocoder.byzs.module.aicourse.controller.admin.aiCourseType.vo.AiCourseTypeSaveReqVO;
+import cn.iocoder.byzs.module.aicourse.dal.dataobject.aiCourseType.AiCourseTypeDO;
+import jakarta.validation.Valid;
+
+import java.util.List;
+
+/**
+ * 课程-类型 Service 接口
+ *
+ * @author lyb
+ */
+public interface AiCourseTypeService {
+
+    /**
+     * 创建课程-类型
+     *
+     * @param createReqVO 创建信息
+     * @return 编号
+     */
+    Long createAiCourseType(@Valid AiCourseTypeSaveReqVO createReqVO);
+
+    /**
+     * 更新课程-类型
+     *
+     * @param updateReqVO 更新信息
+     */
+    void updateAiCourseType(@Valid AiCourseTypeSaveReqVO updateReqVO);
+
+    /**
+     * 删除课程-类型
+     *
+     * @param id 编号
+     */
+    void deleteAiCourseType(Long id);
+
+
+    /**
+     * 获得课程-类型
+     *
+     * @param id 编号
+     * @return 课程-类型
+     */
+    AiCourseTypeDO getAiCourseType(Long id);
+
+    /**
+     * 获得课程-类型列表
+     *
+     * @param listReqVO 查询条件
+     * @return 课程-类型列表
+     */
+    List<AiCourseTypeDO> getAiCourseTypeList(AiCourseTypeListReqVO listReqVO);
+
+    /**
+     * 获得课程-类型列表
+     *
+     * @param listReqVO 查询条件
+     * @return 课程-类型列表
+     */
+    List<AiCourseTypeDO> getAiCourseTypeSimpleList(AiCourseTypeListReqVO listReqVO);
+
+}

+ 155 - 0
byzs-aicourse/src/main/java/cn/iocoder/byzs/module/aicourse/service/aiCourseType/AiCourseTypeServiceImpl.java

@@ -0,0 +1,155 @@
+package cn.iocoder.byzs.module.aicourse.service.aiCourseType;
+
+import cn.iocoder.byzs.framework.common.util.object.BeanUtils;
+import cn.iocoder.byzs.framework.mybatis.core.query.LambdaQueryWrapperX;
+import cn.iocoder.byzs.module.aicourse.controller.admin.aiCourseType.vo.AiCourseTypeListReqVO;
+import cn.iocoder.byzs.module.aicourse.controller.admin.aiCourseType.vo.AiCourseTypeSaveReqVO;
+import cn.iocoder.byzs.module.aicourse.dal.dataobject.aiCourseProgress.AiCourseProgressDO;
+import cn.iocoder.byzs.module.aicourse.dal.dataobject.aiCourseType.AiCourseTypeDO;
+import cn.iocoder.byzs.module.aicourse.dal.mysql.aiCourseProgress.AiCourseProgressMapper;
+import cn.iocoder.byzs.module.aicourse.dal.mysql.aiCourseType.AiCourseTypeMapper;
+import jakarta.annotation.Resource;
+import org.springframework.stereotype.Service;
+import org.springframework.validation.annotation.Validated;
+
+import java.util.List;
+import java.util.Objects;
+
+import static cn.iocoder.byzs.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.byzs.module.aicourse.enums.ErrorCodeConstants.*;
+
+/**
+ * 课程-类型 Service 实现类
+ *
+ * @author lyb
+ */
+@Service
+@Validated
+public class AiCourseTypeServiceImpl implements AiCourseTypeService {
+
+    @Resource
+    private AiCourseTypeMapper aiCourseTypeMapper;
+    @Resource
+    private AiCourseProgressMapper aiCourseProgressMapper;
+
+    @Override
+    public Long createAiCourseType(AiCourseTypeSaveReqVO createReqVO) {
+        // 校验课程类型父级id的有效性
+        validateParentAiCourseType(null, createReqVO.getCtParentId());
+        // 校验课程类型名称的唯一性
+        validateAiCourseTypeCtTypeUnique(null, createReqVO.getCtParentId(), createReqVO.getCtType());
+
+        // 插入
+        AiCourseTypeDO blocklyType = BeanUtils.toBean(createReqVO, AiCourseTypeDO.class);
+        aiCourseTypeMapper.insert(blocklyType);
+        // 返回
+        return blocklyType.getId();
+    }
+
+    @Override
+    public void updateAiCourseType(AiCourseTypeSaveReqVO updateReqVO) {
+        // 校验存在
+        AiCourseTypeDO blocklyTypeDO = validateAiCourseTypeExists(updateReqVO.getId());
+        // 校验课程类型父级id的有效性
+        validateParentAiCourseType(updateReqVO.getId(), updateReqVO.getCtParentId());
+        // 校验课程类型名称的唯一性
+        validateAiCourseTypeCtTypeUnique(updateReqVO.getId(), updateReqVO.getCtParentId(), updateReqVO.getCtType());
+
+        // 更新
+        AiCourseTypeDO updateObj = BeanUtils.toBean(updateReqVO, AiCourseTypeDO.class);
+        aiCourseTypeMapper.updateById(updateObj);
+
+        // 如果修改了类型,同步更新进度数据
+        if (!blocklyTypeDO.getCtTypeNode().equals("0") && !Objects.equals(updateObj.getCtParentId(), blocklyTypeDO.getCtParentId())) {
+
+            // 使用条件更新,将brpCtId等于旧课程类型ID的记录更新为新课程类型ID
+            aiCourseProgressMapper.update(new AiCourseProgressDO().setBrpZtId(updateObj.getCtParentId()),
+                    new LambdaQueryWrapperX<AiCourseProgressDO>().eq(AiCourseProgressDO::getBrpZtId, blocklyTypeDO.getCtParentId()));
+        }
+    }
+
+    @Override
+    public void deleteAiCourseType(Long id) {
+        // 校验存在
+        validateAiCourseTypeExists(id);
+        // 校验是否有子课程-类型
+        if (aiCourseTypeMapper.selectCountByCtParentId(id) > 0) {
+            throw exception(BLOCKLY_TYPE_EXITS_CHILDREN);
+        }
+        // 删除
+        aiCourseTypeMapper.deleteById(id);
+    }
+
+
+    private AiCourseTypeDO validateAiCourseTypeExists(Long id) {
+        AiCourseTypeDO blocklyTypeDO = aiCourseTypeMapper.selectById(id);
+        if (blocklyTypeDO == null) {
+            throw exception(BLOCKLY_TYPE_NOT_EXISTS);
+        }
+        return blocklyTypeDO;
+    }
+
+    private void validateParentAiCourseType(Long id, Long ctParentId) {
+        if (ctParentId == null || AiCourseTypeDO.CT_PARENT_ID_ROOT.equals(ctParentId)) {
+            return;
+        }
+        // 1. 不能设置自己为父课程-类型
+        if (Objects.equals(id, ctParentId)) {
+            throw exception(BLOCKLY_TYPE_PARENT_ERROR);
+        }
+        // 2. 父课程-类型不存在
+        AiCourseTypeDO parentAiCourseType = aiCourseTypeMapper.selectById(ctParentId);
+        if (parentAiCourseType == null) {
+            throw exception(BLOCKLY_TYPE_PARENT_NOT_EXITS);
+        }
+        // 3. 递归校验父课程-类型,如果父课程-类型是自己的子课程-类型,则报错,避免形成环路
+        if (id == null) { // id 为空,说明新增,不需要考虑环路
+            return;
+        }
+        for (int i = 0; i < Short.MAX_VALUE; i++) {
+            // 3.1 校验环路
+            ctParentId = parentAiCourseType.getCtParentId();
+            if (Objects.equals(id, ctParentId)) {
+                throw exception(BLOCKLY_TYPE_PARENT_IS_CHILD);
+            }
+            // 3.2 继续递归下一级父课程-类型
+            if (ctParentId == null || AiCourseTypeDO.CT_PARENT_ID_ROOT.equals(ctParentId)) {
+                break;
+            }
+            parentAiCourseType = aiCourseTypeMapper.selectById(ctParentId);
+            if (parentAiCourseType == null) {
+                break;
+            }
+        }
+    }
+
+    private void validateAiCourseTypeCtTypeUnique(Long id, Long ctParentId, String ctType) {
+        AiCourseTypeDO blocklyType = aiCourseTypeMapper.selectByCtParentIdAndCtType(ctParentId, ctType);
+        if (blocklyType == null) {
+            return;
+        }
+        // 如果 id 为空,说明不用比较是否为相同 id 的课程-类型
+        if (id == null) {
+            throw exception(BLOCKLY_TYPE_CT_TYPE_DUPLICATE);
+        }
+        if (!Objects.equals(blocklyType.getId(), id)) {
+            throw exception(BLOCKLY_TYPE_CT_TYPE_DUPLICATE);
+        }
+    }
+
+    @Override
+    public AiCourseTypeDO getAiCourseType(Long id) {
+        return aiCourseTypeMapper.selectById(id);
+    }
+
+    @Override
+    public List<AiCourseTypeDO> getAiCourseTypeList(AiCourseTypeListReqVO listReqVO) {
+        return aiCourseTypeMapper.selectList(listReqVO);
+    }
+
+    @Override
+    public List<AiCourseTypeDO> getAiCourseTypeSimpleList(AiCourseTypeListReqVO listReqVO) {
+        return aiCourseTypeMapper.selectSimpleList(listReqVO);
+    }
+
+}

+ 65 - 0
byzs-aicourse/src/main/resources/mapper/aiCourse/AiCourseMapper.xml

@@ -0,0 +1,65 @@
+<?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="cn.iocoder.byzs.module.aicourse.dal.mysql.aiCourse.AiCourseMapper">
+
+    <sql id="selectAiCourseList">
+        FROM ai_course c
+        LEFT JOIN ai_course_type ct ON c.ac_type = ct.id
+        <where>
+            c.deleted = 0
+            <if test="acName != null and acName != ''">
+                AND c.ac_name LIKE CONCAT('%', #{acName}, '%')
+            </if>
+            <if test="acStatus!= null and acStatus!= ''">
+                AND c.ac_status = #{acStatus}
+            </if>
+            <if test="acType!= null and acType!= ''">
+                AND c.ac_type = #{acType}
+            </if>
+            <if test="acLabel!= null and acLabel!= ''">
+                AND c.ac_label = #{acLabel}
+            </if>
+            <if test="acContentType!= null and acContentType!= ''">
+                AND c.ac_content_type = #{acContentType}
+            </if>
+            <if test="acIsInspect!= null and acIsInspect!= ''">
+                AND c.ac_is_inspect = #{acIsInspect}
+            </if>
+        </where>
+        ORDER BY ct.ct_type_sort, c.ac_order
+    </sql>
+    <select id="selectAiCoursePageCount" parameterType="cn.iocoder.byzs.module.aicourse.controller.admin.aiCourse.vo.AiCoursePageReqVO"
+            resultType="int">
+        SELECT COUNT(*)
+        <include refid="selectAiCourseList"/>
+    </select>
+     <select id="selectAiCoursePage" parameterType="cn.iocoder.byzs.module.aicourse.controller.admin.aiCourse.vo.AiCoursePageReqVO"
+            resultType="cn.iocoder.byzs.module.aicourse.controller.admin.aiCourse.vo.AiCoursePageReqVO">
+        SELECT c.tenant_id as tenantId,c.id, c.ac_name acName,
+               c.ac_content_type acContentType, c.ac_content acContent,
+               c.ai_info aiInfo, c.ai_user_image aiUserImage, c.ai_user_direction aiUserDirection,
+               c.ai_tile_size aiTileSize, c.ai_background aiBackground,
+               c.ai_start_point aiStartPoint, c.ai_end_point aiEndPoint,
+               c.ai_walkable_points aiWalkablePoints,
+               c.ai_special_blocks aiSpecialBlocks,
+               c.ac_is_inspect acIsInspect, c.ac_label acLabel, c.ac_order acOrder, c.ac_status acStatus,
+               ct.ct_type acTypeName
+        <include refid="selectAiCourseList"/>
+        <if test="pageSize != null and pageSize != -1">
+            limit #{pageNo}, #{pageSize}
+        </if>
+    </select>
+    <select id="selectAiCourseList" parameterType="cn.iocoder.byzs.module.aicourse.controller.admin.aiCourse.vo.AiCoursePageReqVO"
+            resultType="cn.iocoder.byzs.module.aicourse.controller.admin.aiCourse.vo.AiCoursePageReqVO">
+        SELECT c.tenant_id as tenantId,c.id, c.ac_name acName, c.ac_type acType,
+               c.ac_content_type acContentType, c.ac_content acContent,
+               c.ai_info aiInfo, c.ai_user_image aiUserImage, c.ai_user_direction aiUserDirection,
+               c.ai_tile_size aiTileSize, c.ai_background aiBackground,
+               c.ai_start_point aiStartPoint, c.ai_end_point aiEndPoint,
+               c.ai_walkable_points aiWalkablePoints,
+               c.ai_special_blocks aiSpecialBlocks,
+               c.ac_is_inspect acIsInspect, c.ac_label acLabel, c.ac_order acOrder, c.ac_status acStatus,
+               ct.ct_type acTypeName, c.ai_route_list aiRouteList
+        <include refid="selectAiCourseList"/>
+    </select>
+</mapper>

+ 74 - 0
byzs-aicourse/src/main/resources/mapper/aiCourseConfig/AiCourseConfigMapper.xml

@@ -0,0 +1,74 @@
+<?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="cn.iocoder.byzs.module.aicourse.dal.mysql.aiCourseConfig.AiCourseConfigMapper">
+
+
+    <select id="selectAiCourseConfigPage" parameterType="cn.iocoder.byzs.module.aicourse.controller.admin.aiCourseConfig.vo.AiCourseConfigPageReqVO" resultType="cn.iocoder.byzs.module.aicourse.controller.admin.aiCourseConfig.vo.AiCourseConfigPageReqVO">
+        SELECT cc.id, cc.cc_course_id as ccCourseId,c.tenant_id as tenantId,
+                cc_quest_source as ccQuestSource,
+                cc_quest_content as ccQuestContent,
+                cc_quest_option as ccQuestOption,
+                cc_ai_answer as ccAiAnswer,
+                cc_answer as ccAnswer, cc.cc_quest_id as ccQuestId, cc.cc_time as ccTime, cc.cc_answer_judge as ccAnswerJudge,
+               c.ac_name as courseName,
+               cc_ai_quest_tip as ccAiQuestTip
+        FROM ai_course_config cc
+        LEFT JOIN ai_course c ON cc.cc_course_id = c.id
+        <where>
+                c.deleted = 0 and cc.deleted = 0
+            <if test="ccCourseId != null and ccCourseId != ''">
+                AND cc.cc_course_id  = #{ccCourseId}
+            </if>
+            <if test="ccQuestContent != null and ccQuestContent != ''">
+                AND cc.cc_quest_content LIKE CONCAT('%', #{ccQuestContent}, '%')
+            </if>
+            <if test="ccTime != null and ccTime != ''">
+                AND cc.cc_time LIKE CONCAT('%', #{ccTime}, '%')
+            </if>
+            <if test="ccAnswerJudge != null and ccAnswerJudge != ''">
+                AND cc.cc_answer_judge = #{ccAnswerJudge}
+            </if>
+        </where>
+        <if test="pageSize != null and pageSize != -1">
+            limit #{pageNo}, #{pageSize}
+        </if>
+    </select>
+    <select id="selectAiCourseConfigCount" parameterType="cn.iocoder.byzs.module.aicourse.controller.admin.aiCourseConfig.vo.AiCourseConfigPageReqVO" resultType="java.lang.Integer">
+        SELECT count(*)
+        FROM ai_course_config cc
+        LEFT JOIN ai_course c ON cc.cc_course_id = c.id
+        <where>
+            c.deleted = 0 and cc.deleted = 0
+            <if test="ccCourseId != null and ccCourseId != ''">
+                AND cc.cc_course_id  = #{ccCourseId}
+            </if>
+            <if test="ccQuestContent != null and ccQuestContent != ''">
+                AND cc.cc_quest_content LIKE CONCAT('%', #{ccQuestContent}, '%')
+            </if>
+            <if test="ccTime != null and ccTime != ''">
+                AND cc.cc_time LIKE CONCAT('%', #{ccTime}, '%')
+            </if>
+            <if test="ccAnswerJudge != null and ccAnswerJudge != ''">
+                AND cc.cc_answer_judge = #{ccAnswerJudge}
+            </if>
+        </where>
+    </select>
+    <select id="getAiCourseConfigQuestionByCourseIds" parameterType="java.util.List"
+            resultType="cn.iocoder.byzs.module.aicourse.controller.admin.aiCourseConfig.vo.AiCourseConfigRespVO">
+        SELECT cc.id, cc.cc_course_id as ccCourseId,c.tenant_id as tenantId,
+                cc_quest_source as ccQuestSource,
+                cc_quest_content as ccQuestContent,
+                cc_quest_option as ccQuestOption,
+                cc_ai_answer as ccAiAnswer,
+                cc_answer as ccAnswer, cc.cc_quest_id as ccQuestId, cc.cc_time as ccTime, cc.cc_answer_judge as ccAnswerJudge,
+               c.ac_name as courseName,
+               cc_ai_quest_tip as ccAiQuestTip
+        FROM ai_course_config cc
+        LEFT JOIN ai_course c ON cc.cc_course_id = c.id
+        WHERE cc.cc_course_id IN
+        <foreach collection="ccCourseIds" item="courseId" open="(" close=")" separator=",">
+            #{courseId}
+        </foreach>
+        AND c.deleted = 0 and cc.deleted = 0
+    </select>
+</mapper>

+ 7 - 0
byzs-aicourse/src/main/resources/mapper/aiCourseType/AiCourseTypeMapper.xml

@@ -0,0 +1,7 @@
+<?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="cn.iocoder.byzs.module.aicourse.dal.mysql.aiCourseType.AiCourseTypeMapper">
+
+
+
+</mapper>

+ 0 - 6
byzs-blockly/src/main/java/cn/iocoder/byzs/module/blockly/controller/admin/blockly/vo/BlocklyPageReqVO.java

@@ -28,16 +28,10 @@ public class BlocklyPageReqVO extends PageParam {
     private String blocklyInfo;
     @ExcelProperty("人物图标")
     private String blocklyUserImage;
-    @ExcelProperty("人物初始方向")
-    private Integer blocklyUserDirection;
     @ExcelProperty("地图方格大小")
     private String blocklyTileSize;
     @ExcelProperty("地图背景图")
     private String blocklyBackground;
-    @ExcelProperty("地图开始坐标")
-    private String blocklyStartPoint;
-    @ExcelProperty("地图结束坐标")
-    private String blocklyEndPoint;
     @ExcelProperty("地图可行走坐标")
     private String blocklyWalkablePoints;
     @ExcelProperty("特殊方块")

+ 0 - 9
byzs-blockly/src/main/java/cn/iocoder/byzs/module/blockly/controller/admin/blockly/vo/BlocklyRespVO.java

@@ -36,21 +36,12 @@ public class BlocklyRespVO {
     @ExcelProperty("人物图标")
     @Schema(description = "人物图标")
     private String blocklyUserImage;
-    @ExcelProperty("人物初始方向")
-    @Schema(description = "人物初始方向")
-    private Integer blocklyUserDirection;
     @ExcelProperty("地图方格大小")
     @Schema(description = "地图方格大小")
     private String blocklyTileSize;
     @ExcelProperty("地图背景图")
     @Schema(description = "地图背景图")
     private String blocklyBackground;
-    @ExcelProperty("地图开始坐标")
-    @Schema(description = "地图开始坐标")
-    private String blocklyStartPoint;
-    @ExcelProperty("地图结束坐标")
-    @Schema(description = "地图结束坐标")
-    private String blocklyEndPoint;
     @ExcelProperty("地图可行走坐标")
     @Schema(description = "地图可行走坐标")
     private String blocklyWalkablePoints;

+ 0 - 6
byzs-blockly/src/main/java/cn/iocoder/byzs/module/blockly/controller/admin/blockly/vo/BlocklySaveReqVO.java

@@ -26,16 +26,10 @@ public class BlocklySaveReqVO {
     private String blocklyInfo;
     @ExcelProperty("人物图标")
     private String blocklyUserImage;
-    @ExcelProperty("人物初始方向")
-    private Integer blocklyUserDirection;
     @ExcelProperty("地图方格大小")
     private String blocklyTileSize;
     @ExcelProperty("地图背景图")
     private String blocklyBackground;
-    @ExcelProperty("地图开始坐标")
-    private String blocklyStartPoint;
-    @ExcelProperty("地图结束坐标")
-    private String blocklyEndPoint;
     @ExcelProperty("地图可行走坐标")
     private String blocklyWalkablePoints;
     @ExcelProperty("特殊方块")

+ 0 - 12
byzs-blockly/src/main/java/cn/iocoder/byzs/module/blockly/dal/dataobject/blockly/BlocklyDO.java

@@ -54,10 +54,6 @@ public class BlocklyDO extends BaseDO {
      * 人物图标
      */
     private String blocklyUserImage;
-    /**
-     * 人物初始方向
-     */
-    private Integer blocklyUserDirection;
     /**
      * 地图方格大小
      */
@@ -66,14 +62,6 @@ public class BlocklyDO extends BaseDO {
      * 地图背景图
      */
     private String blocklyBackground;
-    /**
-     * 地图开始坐标
-     */
-    private String blocklyStartPoint;
-    /**
-     * 地图结束坐标
-     */
-    private String blocklyEndPoint;
     /**
      * 地图可行走坐标
      */

+ 2 - 4
byzs-blockly/src/main/resources/mapper/blockly/BlocklyMapper.xml

@@ -37,9 +37,8 @@
             resultType="cn.iocoder.byzs.module.blockly.controller.admin.blockly.vo.BlocklyPageReqVO">
         SELECT c.tenant_id as tenantId,c.id, c.bc_name bcName,
                c.bc_content_type bcContentType, c.bc_content bcContent,
-               c.blockly_info blocklyInfo, c.blockly_user_image blocklyUserImage, c.blockly_user_direction blocklyUserDirection,
+               c.blockly_info blocklyInfo, c.blockly_user_image blocklyUserImage,
                c.blockly_tile_size blocklyTileSize, c.blockly_background blocklyBackground,
-               c.blockly_start_point blocklyStartPoint, c.blockly_end_point blocklyEndPoint,
                c.blockly_walkable_points blocklyWalkablePoints,
                c.blockly_special_blocks blocklySpecialBlocks,
                c.bc_is_inspect bcIsInspect, c.bc_label bcLabel, c.bc_order bcOrder, c.bc_status bcStatus,
@@ -53,9 +52,8 @@
             resultType="cn.iocoder.byzs.module.blockly.controller.admin.blockly.vo.BlocklyPageReqVO">
         SELECT c.tenant_id as tenantId,c.id, c.bc_name bcName, c.bc_type bcType,
                c.bc_content_type bcContentType, c.bc_content bcContent,
-               c.blockly_info blocklyInfo, c.blockly_user_image blocklyUserImage, c.blockly_user_direction blocklyUserDirection,
+               c.blockly_info blocklyInfo, c.blockly_user_image blocklyUserImage,
                c.blockly_tile_size blocklyTileSize, c.blockly_background blocklyBackground,
-               c.blockly_start_point blocklyStartPoint, c.blockly_end_point blocklyEndPoint,
                c.blockly_walkable_points blocklyWalkablePoints,
                c.blockly_special_blocks blocklySpecialBlocks,
                c.bc_is_inspect bcIsInspect, c.bc_label bcLabel, c.bc_order bcOrder, c.bc_status bcStatus,

+ 10 - 0
byzs-module-system/src/main/java/cn/iocoder/byzs/module/system/controller/admin/permission/PermissionController.java

@@ -87,6 +87,16 @@ public class PermissionController {
         return success(true);
     }
 
+    @PostMapping("/assign-role-aiCourse-scope")
+    @Operation(summary = "赋予角色aiCourse权限")
+    @PreAuthorize("@ss.hasPermission('system:permission:assign-role-data-scope')")//后续改成aiCourse单独权限,目前没办法测试并更改租户默认管理员的权限
+    public CommonResult<Boolean> assignRoleAiCourseScope(@Valid @RequestBody PermissionAssignRoleAiCourseScopeReqVO reqVO) {
+        permissionService.assignRoleAiCourseScope(reqVO.getRoleId(), reqVO.getDataScopeAiCourseIds());
+        // 更新角色缓存
+//        roleService.getRoleFromCache(reqVO.getRoleId());
+        return success(true);
+    }
+
     @Operation(summary = "获得管理员拥有的角色编号列表")
     @Parameter(name = "userId", description = "用户编号", required = true)
     @GetMapping("/list-user-roles")

+ 21 - 0
byzs-module-system/src/main/java/cn/iocoder/byzs/module/system/controller/admin/permission/vo/permission/PermissionAssignRoleAiCourseScopeReqVO.java

@@ -0,0 +1,21 @@
+package cn.iocoder.byzs.module.system.controller.admin.permission.vo.permission;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+import java.util.Collections;
+import java.util.Set;
+
+@Schema(description = "管理后台 - 赋予角色aiCourse权限 Request VO")
+@Data
+public class PermissionAssignRoleAiCourseScopeReqVO {
+
+    @Schema(description = "角色编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    @NotNull(message = "角色编号不能为空")
+    private Long roleId;
+
+    @Schema(description = "aiCourse课程编号列表,只有范围类型为 AI_COURSE_CUSTOM 时,该字段才需要", example = "1,3,5")
+    private Set<Long> dataScopeAiCourseIds = Collections.emptySet(); // 兜底
+
+}

+ 3 - 0
byzs-module-system/src/main/java/cn/iocoder/byzs/module/system/controller/admin/permission/vo/role/RoleRespVO.java

@@ -59,6 +59,9 @@ public class RoleRespVO {
     @Schema(description = "课程范围(指定blockly课程数组)", example = "1")
     private Set<Long> dataScopeBlocklyIds;
 
+    @Schema(description = "课程范围(指定Ai实验课程数组)", example = "1")
+    private Set<Long> dataScopeAiCourseIds;
+
 
 
     @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED, example = "时间戳格式")

+ 11 - 0
byzs-module-system/src/main/java/cn/iocoder/byzs/module/system/dal/dataobject/permission/RoleDO.java

@@ -89,4 +89,15 @@ public class RoleDO extends TenantBaseDO {
     @TableField(typeHandler = JacksonTypeHandler.class)
     private Set<Long> dataScopeBlocklyIds;
 
+    /**
+     * 数据范围(指定AI课程数组)
+     *
+     * 适用于 {@link #dataScope} 的值为 {@link DataScopeEnum#AI_COURSE_CUSTOM} 时
+     */
+    @TableField(typeHandler = JacksonTypeHandler.class)
+    private Set<Long> dataScopeAiCourseIds;
+
+
+
+
 }

+ 8 - 0
byzs-module-system/src/main/java/cn/iocoder/byzs/module/system/service/permission/PermissionService.java

@@ -151,6 +151,14 @@ public interface PermissionService {
      */
     void assignRoleBlocklyScope(Long roleId, Set<Long> dataScopeBlocklyIds);
 
+    /**
+     * 设置角色的aiCourse权限
+     *
+     * @param roleId           角色编号
+     * @param dataScopeAiCourseIds aiCourse编号数组
+     */
+    void assignRoleAiCourseScope(Long roleId, Set<Long> dataScopeAiCourseIds);
+
     /**
      * 获得登陆用户的部门数据权限
      *

+ 5 - 0
byzs-module-system/src/main/java/cn/iocoder/byzs/module/system/service/permission/PermissionServiceImpl.java

@@ -282,6 +282,11 @@ public class PermissionServiceImpl implements PermissionService {
         roleService.updateRoleBlocklyScope(roleId, dataScopeBlocklyIds);
     }
 
+    @Override
+    public void assignRoleAiCourseScope(Long roleId, Set<Long> dataScopeAiCourseIds) {
+        roleService.updateRoleAiCourseScope(roleId, dataScopeAiCourseIds);
+    }
+
     @Override
     @DataPermission(enable = false) // 关闭数据权限,不然就会出现递归获取数据权限的问题
     public DeptDataPermissionRespDTO getDeptDataPermission(Long userId) {

+ 8 - 0
byzs-module-system/src/main/java/cn/iocoder/byzs/module/system/service/permission/RoleService.java

@@ -63,6 +63,14 @@ public interface RoleService {
      */
     void updateRoleBlocklyScope(Long id, Set<Long> dataScopeBlocklyIds);
 
+    /**
+     * 设置角色的Ai课程权限
+     *
+     * @param id 角色编号
+     * @param dataScopeAiCourseIds blockly编号数组
+     */
+    void updateRoleAiCourseScope(Long id, Set<Long> dataScopeAiCourseIds);
+
     /**
      * 获得角色
      *

+ 12 - 0
byzs-module-system/src/main/java/cn/iocoder/byzs/module/system/service/permission/RoleServiceImpl.java

@@ -133,6 +133,18 @@ public class RoleServiceImpl implements RoleService {
         roleMapper.updateById(updateObject);
     }
 
+    @Override
+    public void updateRoleAiCourseScope(Long id, Set<Long> dataScopeAiCourseIds) {
+        // 校验是否可以更新
+        validateRoleForUpdate(id);
+
+        // 更新课程权限
+        RoleDO updateObject = new RoleDO();
+        updateObject.setId(id);
+        updateObject.setDataScopeAiCourseIds(dataScopeAiCourseIds);
+        roleMapper.updateById(updateObject);
+    }
+
     @Override
     @Transactional(rollbackFor = Exception.class)
     @CacheEvict(value = RedisKeyConstants.ROLE, key = "#id")

+ 7 - 0
byzs-server/pom.xml

@@ -52,6 +52,13 @@
             <version>${revision}</version>
         </dependency>
 
+        <!-- 北京大学课件管理 -->
+        <dependency>
+            <groupId>cn.iocoder.boot</groupId>
+            <artifactId>byzs-aicourse</artifactId>
+            <version>${revision}</version>
+        </dependency>
+
         <!-- WEB -->
         <dependency>
             <groupId>cn.iocoder.boot</groupId>

+ 5 - 0
byzs-web/pom.xml

@@ -23,6 +23,11 @@
             <artifactId>byzs-course</artifactId>
             <version>${revision}</version>
         </dependency>
+        <dependency>
+            <groupId>cn.iocoder.boot</groupId>
+            <artifactId>byzs-aicourse</artifactId>
+            <version>${revision}</version>
+        </dependency>
         <dependency>
             <groupId>cn.iocoder.boot</groupId>
             <artifactId>byzs-blockly</artifactId>

+ 0 - 6
byzs-web/src/main/java/cn/iocoder/byzs/module/web/controller/admin/ai/WebAiController.java

@@ -188,8 +188,6 @@ public class WebAiController {
 
     @GetMapping("/selectVirtualDevice")
     @Operation(summary = "获得虚拟设备分页")
-    @PermitAll
-    @TenantIgnore
     public CommonResult<PageResult<VirtualDeviceRespVO>> selectVirtualDevice(@Valid VirtualDevicePageReqVO pageReqVO) {
         pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
         pageReqVO.setStatus(CommonStatusEnum.ENABLE.getStatus());
@@ -202,8 +200,6 @@ public class WebAiController {
 
     @GetMapping("/selectMapGame")
     @Operation(summary = "获得AI-blockly地图编程游戏分页")
-    @PermitAll
-    @TenantIgnore
     public CommonResult<PageResult<MapGameVO>> selectMapGame(@Valid MapGamePageReqVO pageReqVO) {
         pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
         pageReqVO.setStatus(CommonStatusEnum.ENABLE.getStatus());
@@ -213,8 +209,6 @@ public class WebAiController {
     }
     @GetMapping("/getMapGameById")
     @Operation(summary = "获得AI-blockly地图编程游戏详情")
-    @PermitAll
-    @TenantIgnore
     public CommonResult<MapGameRespVO> getMapGameById(@RequestParam("id") Integer id) {
         MapGameDO mapGame = mapGameService.getMapGame(id);
         if (mapGame == null) {

+ 161 - 0
byzs-web/src/main/java/cn/iocoder/byzs/module/web/controller/admin/aiCourse/WebAICourseController.java

@@ -0,0 +1,161 @@
+package cn.iocoder.byzs.module.web.controller.admin.aiCourse;
+
+import cn.iocoder.byzs.framework.common.pojo.CommonResult;
+import cn.iocoder.byzs.framework.common.util.object.BeanUtils;
+import cn.iocoder.byzs.framework.tenant.core.aop.TenantIgnore;
+import cn.iocoder.byzs.module.aicourse.controller.admin.aiCourseType.vo.AiCourseTypeListReqVO;
+import cn.iocoder.byzs.module.aicourse.controller.admin.aiCourseType.vo.AiCourseTypeRespVO;
+import cn.iocoder.byzs.module.aicourse.dal.dataobject.aiCourseType.AiCourseTypeDO;
+import cn.iocoder.byzs.module.aicourse.service.aiCourse.AiCourseService;
+import cn.iocoder.byzs.module.aicourse.service.aiCourseType.AiCourseTypeService;
+import cn.iocoder.byzs.module.web.controller.admin.aiCourse.vo.WebAiCourseTypeVO;
+import cn.iocoder.byzs.module.web.controller.admin.aiCourse.vo.WebAiCourseVO;
+import cn.iocoder.byzs.module.web.service.aiCourse.WebAiCourseServiceImpl;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.annotation.Resource;
+import jakarta.annotation.security.PermitAll;
+import lombok.Data;
+import org.springframework.validation.annotation.Validated;
+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 java.beans.Transient;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static cn.iocoder.byzs.framework.common.pojo.CommonResult.success;
+
+@Tag(name = "Web-前端接口")
+@RestController
+@RequestMapping("/bjdxWeb/aiCourse")
+@Validated
+public class WebAICourseController {
+
+    @Resource
+    private AiCourseService aiCourseService;
+    @Resource
+    private WebAiCourseServiceImpl webAiCourseService;
+    @Resource
+    private AiCourseTypeService aiCourseTypeService;
+
+    @GetMapping("/getTypeTheme")
+    @Operation(summary = "获得aiCourse主题")
+    @TenantIgnore
+    @PermitAll
+    public CommonResult<List<AiCourseTypeRespVO>> getTypeTheme() {
+        AiCourseTypeListReqVO listReqVO = new AiCourseTypeListReqVO().setCtTypeNode("0");
+        List<AiCourseTypeDO> list = aiCourseTypeService.getAiCourseTypeList(listReqVO);
+        List<AiCourseTypeRespVO> listVo = BeanUtils.toBean(list, AiCourseTypeRespVO.class);
+        return success(listVo);
+    }
+
+    @GetMapping("/getTypeByThemeId")
+    @Operation(summary = "获得aiCourse课程类型-根据主题id")
+    @TenantIgnore
+    @PermitAll
+    public CommonResult<List<WebAiCourseTypeVO>> getTypeByThemeId(@RequestParam("id") Long themeId) {
+        List<WebAiCourseTypeVO> list = webAiCourseService.getTypeByThemeId(themeId);
+        return success(list);
+    }
+
+    @GetMapping("/getAiCourseByTypeId")
+    @Operation(summary = "获得aiCourse课程-根据课程类型id")
+    @TenantIgnore
+    @PermitAll
+    public CommonResult<List<WebAiCourseVO>> getAiCourseByTypeId(@RequestParam("typeId") Long typeId) {
+        List<WebAiCourseVO> aiCourseVoList = webAiCourseService.getAiCourseVoByTypeId(typeId);
+        return success(aiCourseVoList);
+    }
+
+    @GetMapping("/getAiCourseTypeTree")
+    @Operation(summary = "获取课程类型树结构(配置权限使用)")
+    @TenantIgnore
+    @PermitAll
+    public CommonResult<List<TreeNode>> getAiCourseTypeTree() {
+        // 获取所有课程类型
+        List<AiCourseTypeDO> allAiCourseTypes = aiCourseTypeService.getAiCourseTypeList(new AiCourseTypeListReqVO());
+        // 获取所有课程
+//        List<AiCoursePageReqVO> allAiCourses = aiCourseService.getAiCourseList(new AiCoursePageReqVO());
+        
+        // 构建课程类型ID到课程类型的映射
+        Map<Long, AiCourseTypeDO> aiCourseTypeMap = new HashMap<>();
+        for (AiCourseTypeDO aiCourseType : allAiCourseTypes) {
+            aiCourseTypeMap.put(aiCourseType.getId(), aiCourseType);
+        }
+        
+        // 构建课程类型树节点
+        List<TreeNode> treeNodes = new ArrayList<>();
+        
+        // 处理根节点 (parentId = 0)
+        for (AiCourseTypeDO aiCourseType : allAiCourseTypes) {
+            if (AiCourseTypeDO.CT_PARENT_ID_ROOT.equals(aiCourseType.getCtParentId())) {
+                TreeNode node = new TreeNode();
+                node.setId(aiCourseType.getId());
+                node.setParentId(aiCourseType.getCtParentId());
+                node.setName(aiCourseType.getCtType() + "(主题)");
+                node.setType("主题");
+                treeNodes.add(node);
+            }
+        }
+        
+        // 递归添加子节点
+        for (TreeNode node : treeNodes) {
+            addChildNodes(node, allAiCourseTypes, aiCourseTypeMap);
+        }
+        
+        return success(treeNodes);
+    }
+    
+    /**
+     * 递归添加子节点
+     */
+    private void addChildNodes(TreeNode parentNode, List<AiCourseTypeDO> allAiCourseTypes, Map<Long, AiCourseTypeDO> aiCourseTypeMap) {
+        List<TreeNode> children = new ArrayList<>();
+        
+        // 添加子课程类型
+        for (AiCourseTypeDO aiCourseType : allAiCourseTypes) {
+            if (parentNode.getId().equals(aiCourseType.getCtParentId())) {
+                TreeNode childNode = new TreeNode();
+                childNode.setId(aiCourseType.getId());
+                childNode.setParentId(aiCourseType.getCtParentId());
+                childNode.setName(aiCourseType.getCtType());
+                childNode.setType("aiCourseType");
+                children.add(childNode);
+                
+                // 递归添加子节点的子节点
+//                addChildNodes(childNode, allAiCourseTypes, allAiCourses, aiCourseTypeMap);
+            }
+        }
+        
+        // 添加关联的课程
+//        for (AiCoursePageReqVO aiCourse : allAiCourses) {
+//            if (parentNode.getId().equals(aiCourse.getBcType())) {
+//                TreeNode childNode = new TreeNode();
+//                childNode.setId(aiCourse.getId());
+//                childNode.setParentId(aiCourse.getBcType());
+//                childNode.setName(aiCourse.getBcName());
+//                childNode.setType("aiCourse");
+//                children.add(childNode);
+//            }
+//        }
+        
+        parentNode.setChildren(children);
+    }
+    
+    /**
+     * 树节点类,用于表示课程类型树结构
+     */
+    @Data
+    public static class TreeNode {
+        private Long id;
+        private Long parentId;
+        private String name;
+        private String type; // aiCourseType 或 aiCourse
+        private List<TreeNode> children = new ArrayList<>();
+    }
+}

+ 41 - 0
byzs-web/src/main/java/cn/iocoder/byzs/module/web/controller/admin/aiCourse/vo/WebAiCourseTypeVO.java

@@ -0,0 +1,41 @@
+package cn.iocoder.byzs.module.web.controller.admin.aiCourse.vo;
+
+import cn.iocoder.byzs.framework.common.pojo.PageParam;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+@Schema(description = "aiCourse课程类型VO")
+@Data
+public class WebAiCourseTypeVO extends PageParam {
+
+    @Schema(description = "租户编号")
+    private Long tenantId;
+
+    @Schema(description = "课程类型id", requiredMode = Schema.RequiredMode.REQUIRED)
+    private Long id;
+
+    @Schema(description = "课程类型名称")
+    private String ctType;
+
+    @Schema(description = "课程类型封面")
+    private String ctTypeImage;
+
+    @Schema(description = "课程类型父级id")
+    private Long ctParentId;
+
+    @Schema(description = "课程排序")
+    private Integer ctTypeSort;
+
+    @Schema(description = "课程小节数量")
+    private Integer sectionCount;
+
+    @Schema(description = "完成进度")
+    private Integer progress;
+
+    @Schema(description = "用户id")
+    private Long userId;
+
+    @Schema(description = "数据权限-只读")
+    private Boolean dataReadonly;
+
+}

+ 71 - 0
byzs-web/src/main/java/cn/iocoder/byzs/module/web/controller/admin/aiCourse/vo/WebAiCourseVO.java

@@ -0,0 +1,71 @@
+package cn.iocoder.byzs.module.web.controller.admin.aiCourse.vo;
+
+import cn.iocoder.byzs.framework.common.pojo.PageParam;
+import cn.iocoder.byzs.module.aicourse.controller.admin.aiCourseConfig.vo.AiCourseConfigRespVO;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.util.List;
+
+@Schema(description = "管理后台 - 课程分页 Request VO")
+@Data
+public class WebAiCourseVO extends PageParam {
+
+    @Schema(description = "课程id")
+    private Long id;
+
+    @Schema(description = "课程名称")
+    private String acName;
+
+    @Schema(description = "课程内容类型")
+    private String acContentType;
+
+    @Schema(description = "课程内容")
+    private String acContent;
+
+
+    @Schema(description = "简介")
+    private String aiCourseInfo;
+    @Schema(description = "人物图标")
+    private String aiCourseUserImage;
+    @Schema(description = "人物初始方向")
+    private Integer aiCourseUserDirection;
+    @Schema(description = "地图方格大小")
+    private String aiCourseTileSize;
+    @Schema(description = "地图背景图")
+    private String aiCourseBackground;
+    @Schema(description = "地图开始坐标")
+    private String aiCourseStartPoint;
+    @Schema(description = "地图结束坐标")
+    private String aiCourseEndPoint;
+    @Schema(description = "地图可行走坐标")
+    private String aiCourseWalkablePoints;
+    @Schema(description = "特殊方块")
+    private String aiCourseSpecialBlocks;
+    @Schema(description = "路径列表")
+    private String aiCourseRouteList;
+
+
+
+    @Schema(description = "课程是否有检查")
+    private String acIsInspect;
+
+    @Schema(description = "课程类型id")
+    private Long acType;
+
+    @Schema(description = "课程类型名称")
+    private String acTypeName;
+
+    @Schema(description = "课程标签")
+    private String acLabel;
+
+    @Schema(description = "课程状态")
+    private String acStatus;
+
+    // 课程配置列表
+    private List<AiCourseConfigRespVO> aiCourseConfigList;
+
+    // 课程进度
+    private Integer progress;
+
+}

+ 47 - 0
byzs-web/src/main/java/cn/iocoder/byzs/module/web/controller/admin/aiCourseProgress/WebAiCourseProgressController.java

@@ -0,0 +1,47 @@
+package cn.iocoder.byzs.module.web.controller.admin.aiCourseProgress;
+
+import cn.iocoder.byzs.framework.common.pojo.CommonResult;
+import cn.iocoder.byzs.module.web.controller.admin.aiCourseProgress.vo.WebAiCourseProgressDO;
+import cn.iocoder.byzs.module.web.service.aiCourseProgress.WebAiCourseProgressServiceImpl;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.annotation.Resource;
+import jakarta.validation.Valid;
+import jodd.util.StringUtil;
+import org.springframework.validation.annotation.Validated;
+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 java.util.Objects;
+
+import static cn.iocoder.byzs.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.byzs.framework.common.pojo.CommonResult.success;
+import static cn.iocoder.byzs.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
+import static cn.iocoder.byzs.module.bjdx.enums.ErrorCodeConstants.PROGRESS_NOT_EXISTS;
+
+
+@Tag(name = "管理后台 - 评估报告-课程开课率")
+@RestController
+@RequestMapping("/bjdxWeb/aiCourseReportProgress")
+@Validated
+public class WebAiCourseProgressController {
+
+    @Resource
+    private WebAiCourseProgressServiceImpl progressService;
+
+    @PostMapping("/saveReportProgress")
+    @Operation(summary = "保存评估报告进度")
+    public CommonResult<Boolean> saveReportProgress(@Valid @RequestBody WebAiCourseProgressDO reportProgressDO) {
+        // 校验
+        if (StringUtil.isBlank(reportProgressDO.getArpType()) || reportProgressDO.getArpType().equals("courseQuest") && Objects.isNull(reportProgressDO.getArpCourseConfigId())) {
+            throw exception(PROGRESS_NOT_EXISTS);
+        }
+        //用户id
+        reportProgressDO.setArpUserId(getLoginUserId());
+        // 插入
+        progressService.saveReportProgress(reportProgressDO);
+        return success(true);
+    }
+}

+ 65 - 0
byzs-web/src/main/java/cn/iocoder/byzs/module/web/controller/admin/aiCourseProgress/vo/WebAiCourseProgressDO.java

@@ -0,0 +1,65 @@
+package cn.iocoder.byzs.module.web.controller.admin.aiCourseProgress.vo;
+
+import cn.iocoder.byzs.framework.mybatis.core.dataobject.BaseDO;
+import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.*;
+import lombok.experimental.Accessors;
+
+/**
+ * 评估报告-课程开课率 DO
+ *
+ * @author lyb
+ */
+@TableName("blockly_report_progress")
+@KeySequence("blockly_report_progress_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+@Accessors(chain = true)
+public class WebAiCourseProgressDO extends BaseDO {
+
+    /**
+     * 主键id
+     */
+    @TableId
+    private Long arpId;
+    /**
+     * 用户id
+     */
+    private Long arpUserId;
+    /**
+     * 主题id
+     */
+    @NonNull
+    private Long arpZtId;
+    /**
+     * 课程类型id
+     */
+    @NonNull
+    private Long arpCtId;
+    /**
+     * 课程id
+     */
+    @NonNull
+    private Long arpCourseId;
+    /**
+     * 课程配置id
+     */
+    private Long arpCourseConfigId;
+    /**
+     * 进度分类
+     */
+    @NonNull
+    private String arpType;
+    /**
+     * 进度(课程开课率、课程互动率、ai问答次数)
+     */
+    private Double arpProgress;
+
+
+}

+ 45 - 0
byzs-web/src/main/java/cn/iocoder/byzs/module/web/controller/admin/aiCourseProgress/vo/WebAiCourseProgressDTO.java

@@ -0,0 +1,45 @@
+package cn.iocoder.byzs.module.web.controller.admin.aiCourseProgress.vo;
+
+import lombok.Data;
+import lombok.experimental.Accessors;
+
+/**
+ * 评估报告-课程开课率 VO
+ *
+ * @author lyb
+ */
+@Data
+@Accessors(chain = true)
+public class WebAiCourseProgressDTO {
+
+    /**
+     /**
+     * 用户id
+     */
+    private String userName;
+    /**
+     * 主题id
+     */
+    private Long brpZtId;
+    /**
+     * 课程类型名称
+     */
+    private String kcflName;
+    /**
+     * 课程名称
+     */
+    private String courseName;
+    /**
+     * 课程配置id
+     */
+    private Long courseConfigId;
+    /**
+     * 进度分类
+     */
+    private String progressType;
+    /**
+     * 进度(课程开课率、课程互动率、ai问答次数)
+     */
+    private Double progress;
+
+}

+ 70 - 0
byzs-web/src/main/java/cn/iocoder/byzs/module/web/controller/admin/aiCourseProgress/vo/WebAiCourseProgressVO.java

@@ -0,0 +1,70 @@
+package cn.iocoder.byzs.module.web.controller.admin.aiCourseProgress.vo;
+
+import lombok.Data;
+import lombok.experimental.Accessors;
+
+import java.util.List;
+
+/**
+ * 评估报告-课程开课率 DO
+ *
+ * @author lyb
+ */
+@Data
+@Accessors(chain = true)
+public class WebAiCourseProgressVO {
+
+    /**
+     /**
+     * 用户id
+     */
+//    private String userName;
+    /**
+     * 年级名称
+     */
+//    private String njName;
+    /**
+     * 年级课程开课率
+     */
+    private Double njCourseProgress;
+    /**
+     * 年级课程互动率
+     */
+    private Double njCourseConfigProgress;
+
+    /**
+     * ai问答次数
+     */
+    private Integer aiCount = 0;
+
+    /**
+     * 评语
+     */
+    private String comment;
+
+    /**
+     * 课程开课率
+     */
+    private List<ReportProgressVo> reportCourseProgressList;
+
+    /**
+     * 课程互动率
+     */
+    private List<ReportProgressVo> reportCourseConfigProgressList;
+
+
+    @Data
+    @Accessors(chain = true)
+    public static class ReportProgressVo {
+
+        /**
+         * 课程类型名称
+         */
+        private String kcflName;
+        /**
+         * 进度(课程开课率、课程互动率、ai问答次数)
+         */
+        private Double progress = 0d;
+    }
+
+}

+ 0 - 1
byzs-web/src/main/java/cn/iocoder/byzs/module/web/controller/admin/blockly/WebBlocklyController.java

@@ -15,7 +15,6 @@ import cn.iocoder.byzs.module.web.service.blockly.WebBlocklyServiceImpl;
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.tags.Tag;
 import jakarta.annotation.Resource;
-import jakarta.annotation.security.PermitAll;
 import lombok.Data;
 import org.springframework.validation.annotation.Validated;
 import org.springframework.web.bind.annotation.GetMapping;

+ 0 - 2
byzs-web/src/main/java/cn/iocoder/byzs/module/web/controller/admin/course/WebCourseController.java

@@ -79,8 +79,6 @@ public class WebCourseController {
 
     @GetMapping("/getCourseTypeTree")
     @Operation(summary = "获取课程类型树结构(配置权限使用)")
-    @TenantIgnore
-    @PermitAll
     public CommonResult<List<TreeNode>> getCourseTypeTree() {
         // 获取所有课程类型
         List<CourseTypeDO> allCourseTypes = courseTypeService.getCourseTypeList(new CourseTypeListReqVO());

+ 0 - 4
byzs-web/src/main/java/cn/iocoder/byzs/module/web/controller/admin/questionnaire/WebQuestionnaireController.java

@@ -2,19 +2,15 @@ package cn.iocoder.byzs.module.web.controller.admin.questionnaire;
 
 import cn.iocoder.byzs.framework.common.pojo.CommonResult;
 import cn.iocoder.byzs.framework.common.pojo.PageResult;
-import cn.iocoder.byzs.framework.tenant.core.aop.TenantIgnore;
 import cn.iocoder.byzs.module.bjdx.controller.admin.questionnaire.vo.QuestionnairePageReqVO;
 import cn.iocoder.byzs.module.bjdx.dal.dataobject.questionnaire.QuestionnaireDO;
 import cn.iocoder.byzs.module.bjdx.service.questionnaire.QuestionnaireService;
 import cn.iocoder.byzs.module.web.controller.admin.questionnaire.vo.WebQuestionnaireResultVo;
 import cn.iocoder.byzs.module.web.controller.admin.questionnaire.vo.WebQuestionnaireShowVo;
-import cn.iocoder.byzs.module.web.controller.admin.questionnaire.vo.WebQuestionnaireVo;
 import cn.iocoder.byzs.module.web.service.questionnaire.WebQuestionnaireServiceImpl;
 import io.swagger.v3.oas.annotations.Operation;
-import io.swagger.v3.oas.annotations.Parameter;
 import io.swagger.v3.oas.annotations.tags.Tag;
 import jakarta.annotation.Resource;
-import jakarta.annotation.security.PermitAll;
 import jakarta.validation.Valid;
 import org.springframework.validation.annotation.Validated;
 import org.springframework.web.bind.annotation.*;

+ 0 - 2
byzs-web/src/main/java/cn/iocoder/byzs/module/web/controller/admin/reportprogress/WebReportProgressController.java

@@ -1,14 +1,12 @@
 package cn.iocoder.byzs.module.web.controller.admin.reportprogress;
 
 import cn.iocoder.byzs.framework.common.pojo.CommonResult;
-import cn.iocoder.byzs.framework.tenant.core.aop.TenantIgnore;
 import cn.iocoder.byzs.module.web.controller.admin.reportprogress.vo.WebReportProgressDO;
 import cn.iocoder.byzs.module.web.controller.admin.reportprogress.vo.WebReportProgressVO;
 import cn.iocoder.byzs.module.web.service.reportprogress.WebReportProgressServiceImpl;
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.tags.Tag;
 import jakarta.annotation.Resource;
-import jakarta.annotation.security.PermitAll;
 import jakarta.validation.Valid;
 import jodd.util.StringUtil;
 import org.springframework.validation.annotation.Validated;

+ 31 - 0
byzs-web/src/main/java/cn/iocoder/byzs/module/web/dal/mysql/aiCourse/WebAiCourseMapper.java

@@ -0,0 +1,31 @@
+package cn.iocoder.byzs.module.web.dal.mysql.aiCourse;
+
+import cn.iocoder.byzs.module.bjdx.controller.admin.course.vo.CoursePageReqVO;
+import cn.iocoder.byzs.module.web.controller.admin.aiCourse.vo.WebAiCourseTypeVO;
+import cn.iocoder.byzs.module.web.controller.admin.blockly.vo.WebBlocklyTypeVO;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.List;
+
+/**
+ * web课程 Mapper
+ *
+ * @author lyb
+ */
+@Mapper
+public interface WebAiCourseMapper {
+    /**
+     * 查询课程列表
+     *
+     * @param reqVO 分页查询参数
+     * @return 课程列表
+     */
+    List<CoursePageReqVO> selectCourse(CoursePageReqVO reqVO);
+
+    /**
+     * 根据主题id查询课程类型列表
+     * @param aiCourseTypeVO
+     * @return
+     */
+    List<WebAiCourseTypeVO> getTypeByThemeId(WebAiCourseTypeVO aiCourseTypeVO);
+}

+ 23 - 0
byzs-web/src/main/java/cn/iocoder/byzs/module/web/dal/mysql/aiCourseProgress/WebAiCourseProgressMapper.java

@@ -0,0 +1,23 @@
+package cn.iocoder.byzs.module.web.dal.mysql.aiCourseProgress;
+
+import cn.iocoder.byzs.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.byzs.module.web.controller.admin.aiCourseProgress.vo.WebAiCourseProgressDO;
+import cn.iocoder.byzs.module.web.controller.admin.aiCourseProgress.vo.WebAiCourseProgressDTO;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.List;
+
+/**
+ * 评估报告-课程开课率 Mapper
+ *
+ * @author lyb
+ */
+@Mapper
+public interface WebAiCourseProgressMapper extends BaseMapperX<WebAiCourseProgressDO> {
+
+    List<WebAiCourseProgressDTO> selectProgres(WebAiCourseProgressDO aiCourseProgressDO);
+
+    List<WebAiCourseProgressDO> getAiCourseProgressByTypeId(WebAiCourseProgressDO aiCourseProgressDO);
+
+    List<WebAiCourseProgressDO> getAiCourseProgressByThemeId(WebAiCourseProgressDO aiCourseProgressDO);
+}

+ 133 - 0
byzs-web/src/main/java/cn/iocoder/byzs/module/web/service/aiCourse/WebAiCourseServiceImpl.java

@@ -0,0 +1,133 @@
+package cn.iocoder.byzs.module.web.service.aiCourse;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.iocoder.byzs.framework.common.enums.CommonStatusEnum;
+import cn.iocoder.byzs.framework.common.util.object.BeanUtils;
+import cn.iocoder.byzs.module.aicourse.controller.admin.aiCourse.vo.AiCoursePageReqVO;
+import cn.iocoder.byzs.module.aicourse.controller.admin.aiCourseConfig.vo.AiCourseConfigRespVO;
+import cn.iocoder.byzs.module.aicourse.service.aiCourse.AiCourseService;
+import cn.iocoder.byzs.module.aicourse.service.aiCourseConfig.AiCourseConfigService;
+import cn.iocoder.byzs.module.web.controller.admin.aiCourse.vo.WebAiCourseTypeVO;
+import cn.iocoder.byzs.module.web.controller.admin.aiCourse.vo.WebAiCourseVO;
+import cn.iocoder.byzs.module.web.controller.admin.aiCourseProgress.vo.WebAiCourseProgressDO;
+import cn.iocoder.byzs.module.web.dal.mysql.aiCourse.WebAiCourseMapper;
+import cn.iocoder.byzs.module.web.service.aiCourseProgress.WebAiCourseProgressServiceImpl;
+import cn.iocoder.byzs.module.web.service.role.WebAuthRoleServiceImpl;
+import jakarta.annotation.Resource;
+import org.springframework.stereotype.Service;
+import org.springframework.validation.annotation.Validated;
+
+import java.util.*;
+import java.util.stream.Collectors;
+
+/**
+ * web课程 Service 实现类
+ *
+ * @author lyb
+ */
+@Service
+@Validated
+public class WebAiCourseServiceImpl {
+
+    @Resource
+    private AiCourseService aiCourseService;
+    @Resource
+    private AiCourseConfigService aiCourseConfigService;
+    @Resource
+    private WebAiCourseMapper webAiCourseMapper;
+    @Resource
+    private WebAiCourseProgressServiceImpl webAiCourseProgressService;
+    @Resource
+    private WebAuthRoleServiceImpl webAuthRoleService;
+
+
+
+    /**
+     * 根据课程类型id查询课程列表
+     *
+     * @param typeId 课程类型id
+     * @return 课程列表
+     */
+    public List<WebAiCourseVO> getAiCourseVoByTypeId(Long typeId) {
+        List<AiCoursePageReqVO> aiCourseList = aiCourseService.getAiCourseList(new AiCoursePageReqVO().setAcType(typeId).setAcStatus(String.valueOf(CommonStatusEnum.ENABLE.getStatus())));
+
+        //填充课程配置
+        List<Long> idList = aiCourseList.stream()
+                .map(AiCoursePageReqVO::getId)
+                .collect(Collectors.toList());
+        List<AiCourseConfigRespVO> aiCourseConfigQuestionList = aiCourseConfigService.getAiCourseConfigQuestionByCourseIds(idList);
+        Map<Long, List<AiCourseConfigRespVO>> aiCourseConfigQuestionMap = aiCourseConfigQuestionList.stream().collect(Collectors.groupingBy(AiCourseConfigRespVO::getCcCourseId));
+
+        //课程小节进度
+        List<WebAiCourseProgressDO> progressList = webAiCourseProgressService.getAiCourseProgressByTypeId(typeId);
+        Map<Long, WebAiCourseProgressDO> progressMap = progressList.stream().collect(Collectors.toMap(WebAiCourseProgressDO::getArpCourseId, a -> a));
+
+
+        //重新组装
+        List<WebAiCourseVO> aiCourseVoList = new ArrayList<>();
+        for (AiCoursePageReqVO aiCourse : aiCourseList) {
+
+            //组装试题配置
+            List<AiCourseConfigRespVO> aiCourseConfigQuestion = aiCourseConfigQuestionMap.get(aiCourse.getId());
+            List<AiCourseConfigRespVO> list = new ArrayList<>();
+
+            WebAiCourseVO webAiCourseVO = BeanUtils.toBean(aiCourse, WebAiCourseVO.class);
+            if (CollUtil.isNotEmpty(aiCourseConfigQuestion)) {
+                // 根据 ccTime 升序排序(弹框时间)
+                list = aiCourseConfigQuestion.stream().sorted(Comparator.comparing(AiCourseConfigRespVO::getCcTime)).toList();
+            }
+            webAiCourseVO.setAiCourseConfigList(list);
+
+            //填充进度
+            if (progressMap.containsKey(aiCourse.getId())) {
+                WebAiCourseProgressDO progress = progressMap.get(aiCourse.getId());
+                webAiCourseVO.setProgress(progress.getArpProgress().intValue());
+            }else{
+                webAiCourseVO.setProgress(0);
+            }
+
+            aiCourseVoList.add(webAiCourseVO);
+        }
+
+        return aiCourseVoList;
+    }
+
+    /**
+     * 根据课程类型查询课程类型列表
+     * @param themeId
+     * @return
+     */
+    public List<WebAiCourseTypeVO> getTypeByThemeId(Long themeId) {
+
+        //查询课程类型列表
+        List<WebAiCourseTypeVO> list = webAiCourseMapper.getTypeByThemeId(new WebAiCourseTypeVO().setCtParentId(themeId));
+        List<WebAiCourseTypeVO> aiCourseTypeVoList = BeanUtils.toBean(list, WebAiCourseTypeVO.class);
+
+        //课程小节进度
+        List<WebAiCourseProgressDO> progressList = webAiCourseProgressService.getAiCourseProgressByThemeId(themeId);
+        Map<Long, WebAiCourseProgressDO> progressMap = progressList.stream().collect(Collectors.toMap(WebAiCourseProgressDO::getArpCtId, a -> a));
+
+        //获得aiCourse数据权限
+        Set<Long> aiCourseAuthRoleVO = webAuthRoleService.getAiCourseAuthRoleVO();
+
+        aiCourseTypeVoList.forEach(aiCourseType -> {
+
+            //判断是否有数据权限
+            if (aiCourseAuthRoleVO.isEmpty()) {
+                aiCourseType.setDataReadonly(false);
+            }else aiCourseType.setDataReadonly(!aiCourseAuthRoleVO.contains(aiCourseType.getId()));
+
+            if (progressMap.containsKey(aiCourseType.getId())) {
+                WebAiCourseProgressDO progress = progressMap.get(aiCourseType.getId());
+                Double arpProgress = progress.getArpProgress();
+                //向下取整
+                double progressL = Math.floor(arpProgress / aiCourseType.getSectionCount() * 5);
+                aiCourseType.setProgress((int) progressL);
+            }else{
+                aiCourseType.setProgress(0);
+            }
+        });
+
+        return aiCourseTypeVoList;
+    }
+}

+ 120 - 0
byzs-web/src/main/java/cn/iocoder/byzs/module/web/service/aiCourseProgress/WebAiCourseProgressServiceImpl.java

@@ -0,0 +1,120 @@
+package cn.iocoder.byzs.module.web.service.aiCourseProgress;
+
+import cn.iocoder.byzs.framework.mybatis.core.query.LambdaQueryWrapperX;
+import cn.iocoder.byzs.framework.web.core.util.WebFrameworkUtils;
+import cn.iocoder.byzs.module.aicourse.dal.mysql.aiCourseType.AiCourseTypeMapper;
+import cn.iocoder.byzs.module.bjdx.dal.mysql.reportmanage.ReportManageMapper;
+import cn.iocoder.byzs.module.web.controller.admin.aiCourseProgress.vo.WebAiCourseProgressDO;
+import cn.iocoder.byzs.module.web.dal.mysql.aiCourseProgress.WebAiCourseProgressMapper;
+import jakarta.annotation.Resource;
+import org.springframework.stereotype.Service;
+import org.springframework.validation.annotation.Validated;
+
+import java.util.List;
+
+/**
+ * 评估报告-课程开课率 Service 实现类
+ *
+ * @author lyb
+ */
+@Service
+@Validated
+public class WebAiCourseProgressServiceImpl {
+
+    @Resource
+    private WebAiCourseProgressMapper progressMapper;
+    @Resource
+    private ReportManageMapper reportManageMapper;
+    @Resource
+    private AiCourseTypeMapper aiCourseTypeMapper;
+
+    /**
+     * 保存评估报告进度
+     *
+     * @param progress 评估报告进度
+     */
+    public void saveReportProgress(WebAiCourseProgressDO progress) {
+
+        LambdaQueryWrapperX<WebAiCourseProgressDO> progressLambdaQueryWrapperX = new LambdaQueryWrapperX<WebAiCourseProgressDO>()
+                .eqIfPresent(WebAiCourseProgressDO::getArpUserId, progress.getArpUserId())
+                .eqIfPresent(WebAiCourseProgressDO::getArpType, progress.getArpType())
+                .eqIfPresent(WebAiCourseProgressDO::getArpZtId, progress.getArpZtId());
+
+        switch (progress.getArpType()) {
+            case "course":// 课程开课率
+                progressLambdaQueryWrapperX
+                        .eqIfPresent(WebAiCourseProgressDO::getArpCtId, progress.getArpCtId())
+                        .eqIfPresent(WebAiCourseProgressDO::getArpCourseId, progress.getArpCourseId());
+                WebAiCourseProgressDO courseProgressDo = progressMapper.selectOne(progressLambdaQueryWrapperX);
+                if (courseProgressDo == null) {
+                    progressMapper.insert(progress);
+                }else if (progress.getArpProgress() > courseProgressDo.getArpProgress()) {
+                    courseProgressDo.setArpProgress(progress.getArpProgress());
+                    progressMapper.updateById(courseProgressDo);
+                }
+                break;
+
+            case "courseQuest":// 课程互动率
+                progressLambdaQueryWrapperX
+                        .eqIfPresent(WebAiCourseProgressDO::getArpCtId, progress.getArpCtId())
+                        .eqIfPresent(WebAiCourseProgressDO::getArpCourseId, progress.getArpCourseId())
+                        .eqIfPresent(WebAiCourseProgressDO::getArpCourseConfigId, progress.getArpCourseConfigId());
+                WebAiCourseProgressDO courseConfigProgressDo = progressMapper.selectOne(progressLambdaQueryWrapperX);
+
+                if (courseConfigProgressDo == null) {
+                    progressMapper.insert(progress);
+                }
+                break;
+
+            case "aiCount":// ai问答次数
+                WebAiCourseProgressDO aiCountProgressDo = progressMapper.selectOne(progressLambdaQueryWrapperX);
+
+                if (aiCountProgressDo == null) {
+                    progressMapper.insert(progress);
+                }else {
+                    aiCountProgressDo.setArpProgress(aiCountProgressDo.getArpProgress()+1);
+                    progressMapper.updateById(aiCountProgressDo);
+                }
+                break;
+
+            case "aiCourse":// blocklu编程课通关
+                progressLambdaQueryWrapperX
+                        .eqIfPresent(WebAiCourseProgressDO::getArpCtId, progress.getArpCtId())
+                        .eqIfPresent(WebAiCourseProgressDO::getArpCourseId, progress.getArpCourseId());
+                WebAiCourseProgressDO aiCourseProgressDo = progressMapper.selectOne(progressLambdaQueryWrapperX);
+
+                if (aiCourseProgressDo == null) {
+                    progressMapper.insert(progress);
+                }else {
+                    if (progress.getArpProgress() > aiCourseProgressDo.getArpProgress()){
+                        aiCourseProgressDo.setArpProgress(progress.getArpProgress());
+                        progressMapper.updateById(aiCourseProgressDo);
+                    }
+                }
+                break;
+        }
+    }
+
+    /**
+     * 根据类型id查询进度
+     *
+     * @param typeId 类型id
+     */
+    public List<WebAiCourseProgressDO> getAiCourseProgressByTypeId(Long typeId) {
+        WebAiCourseProgressDO webAiCourseProgressDO = new WebAiCourseProgressDO().setArpCtId(typeId).setArpUserId(WebFrameworkUtils.getLoginUserId());
+        List<WebAiCourseProgressDO> aiCourseProgressList = progressMapper.getAiCourseProgressByTypeId(webAiCourseProgressDO);
+        return aiCourseProgressList;
+    }
+
+    /**
+     * 根据主题id查询进度
+     *
+     * @param themeId 主题id
+     * @return 进度列表
+     */
+    public List<WebAiCourseProgressDO> getAiCourseProgressByThemeId(Long themeId) {
+        WebAiCourseProgressDO webAiCourseProgressDO = new WebAiCourseProgressDO().setArpZtId(themeId).setArpUserId(WebFrameworkUtils.getLoginUserId());
+        List<WebAiCourseProgressDO> aiCourseProgressList = progressMapper.getAiCourseProgressByThemeId(webAiCourseProgressDO);
+        return aiCourseProgressList;
+    }
+}

+ 21 - 0
byzs-web/src/main/java/cn/iocoder/byzs/module/web/service/role/WebAuthRoleServiceImpl.java

@@ -84,4 +84,25 @@ public class WebAuthRoleServiceImpl {
         }
         return allDataScopeBlocklyIds;
     }
+
+    /**
+     * 取AiCourse数据权限
+     * @return
+     */
+    public Set<Long> getAiCourseAuthRoleVO() {
+
+        // 获得角色列表
+        List<RoleDO> roles = getUserRoleList();
+
+        // 将所有角色的dataScopeAiCourseIds拼接成一个Set集合
+        Set<Long> allDataScopeAiCourseIds = new HashSet<>();
+        for (RoleDO role : roles) {
+            //blocklu课程数据权限
+            Set<Long> dataScopeAiCourseIds = role.getDataScopeAiCourseIds();
+            if (dataScopeAiCourseIds != null && !dataScopeAiCourseIds.isEmpty()) {
+                allDataScopeAiCourseIds.addAll(dataScopeAiCourseIds);
+            }
+        }
+        return allDataScopeAiCourseIds;
+    }
 }

+ 26 - 0
byzs-web/src/main/resources/mapper/aiCourse/WebAiCourseMapper.xml

@@ -0,0 +1,26 @@
+<?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="cn.iocoder.byzs.module.web.dal.mysql.aiCourse.WebAiCourseMapper">
+
+    <select id="getTypeByThemeId"
+            parameterType="cn.iocoder.byzs.module.web.controller.admin.aiCourse.vo.WebAiCourseTypeVO"
+            resultType="cn.iocoder.byzs.module.web.controller.admin.aiCourse.vo.WebAiCourseTypeVO">
+        SELECT
+            ct.id as id,
+            ct.ct_type as ctType,
+            ct.ct_parent_id as ctParentId,
+            ct.ct_type_image as ctTypeImage,
+            ct.ct_type_sort as ctTypeSort,
+            COUNT(ct.id) + SUM(CASE WHEN ac.ac_label = 2 THEN 2 ELSE 0 END) AS sectionCount
+        FROM
+            ai_course_type ct
+                LEFT JOIN ai_course ac ON ct.id = ac.ac_type
+        WHERE
+            ct.deleted = 0 AND ac.deleted = 0 AND ac.ac_status = 0 AND
+            ct.ct_parent_id = #{ctParentId}
+        GROUP BY
+            id, ctType, ctParentId, ctTypeImage, ctTypeSort
+        ORDER BY
+            ctTypeSort;
+    </select>
+</mapper>

+ 55 - 0
byzs-web/src/main/resources/mapper/aiCourseProgress/WebAiCourseProgressMapper.xml

@@ -0,0 +1,55 @@
+<?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="cn.iocoder.byzs.module.web.dal.mysql.aiCourseProgress.WebAiCourseProgressMapper">
+
+
+    <select id="selectProgres" parameterType="cn.iocoder.byzs.module.web.controller.admin.aiCourseProgress.vo.WebAiCourseProgressDO"
+            resultType="cn.iocoder.byzs.module.web.controller.admin.aiCourseProgress.vo.WebAiCourseProgressDTO">
+        SELECT
+            u.nickname as userName,
+            CONCAT(LPAD(fl.ct_type_sort, 2, '0'), ' ', fl.ct_type) as kcflName,
+            c.course_name as courseName,
+            rp.arp_progress as progress,
+            rp.arp_type as progressType
+        FROM ai_report_progress rpt_id =fl.id
+        LEFT JOIN ai_course c ON rp.arp_course_i
+        LEFT JOIN ai_course_type fl ON rp.arp_cd = c.id
+        LEFT JOIN system_users u ON rp.arp_user_id = u.id
+        <where>
+            <if test="brpUserId != null">
+                and rp.arp_user_id = #{brpUserId}
+            </if>
+            <if test="brpZtId != null">
+                AND rp.arp_zt_id = #{brpZtId}
+            </if>
+        </where>
+    </select>
+
+    <select id="getAiCourseProgressByTypeId"
+            parameterType="cn.iocoder.byzs.module.web.controller.admin.aiCourseProgress.vo.WebAiCourseProgressDO"
+            resultType="cn.iocoder.byzs.module.web.controller.admin.aiCourseProgress.vo.WebAiCourseProgressDO">
+
+        SELECT
+            arp_course_id AS arpCourseId,
+            COALESCE(arp_progress / 100, 0) AS arpProgress
+        FROM ai_report_progress
+        WHERE
+            arp_ct_id = #{arpCtId}
+          AND arp_user_id = #{arpUserId}
+          AND arp_course_config_id IS NULL
+    </select>
+
+    <select id="getAiCourseProgressByThemeId"
+            parameterType="cn.iocoder.byzs.module.web.controller.admin.aiCourseProgress.vo.WebAiCourseProgressDO"
+            resultType="cn.iocoder.byzs.module.web.controller.admin.aiCourseProgress.vo.WebAiCourseProgressDO">
+        SELECT
+            arp_ct_id AS arpCtId,
+            COALESCE(SUM(arp_progress) / 100, 0) AS arpProgress
+        FROM ai_report_progress
+        WHERE
+            arp_zt_id = #{arpZtId}
+          AND arp_user_id = #{arpUserId}
+          AND arp_course_config_id IS NULL
+        GROUP BY arp_ct_id
+    </select>
+</mapper>

+ 2 - 0
pom.xml

@@ -18,9 +18,11 @@
 
         <module>byzs-module-ai</module>
 
+        <module>byzs-aicourse</module>
         <module>byzs-course</module>
         <module>byzs-web</module>
         <module>byzs-blockly</module>
+        <module>byzs-aicourse</module>
     </modules>
 
     <name>${project.artifactId}</name>