Forráskód Böngészése

1、新增课程权限
2、前台登录调取课程权限数据
3、课程筛选实操课、通识课默认包括年级

liyanbo 8 hónapja
szülő
commit
cd4dfd862a
15 módosított fájl, 265 hozzáadás és 10 törlés
  1. 22 4
      byzs-course/src/main/java/cn/iocoder/byzs/module/bjdx/dal/mysql/coursetype/CourseTypeMapper.java
  2. 1 1
      byzs-course/src/main/resources/mapper/course/CourseMapper.xml
  3. 1 0
      byzs-module-ai/src/main/java/cn/iocoder/byzs/module/ai/dal/mysql/virtualdevice/VirtualDeviceMapper.java
  4. 9 0
      byzs-module-system/src/main/java/cn/iocoder/byzs/module/system/controller/admin/permission/PermissionController.java
  5. 21 0
      byzs-module-system/src/main/java/cn/iocoder/byzs/module/system/controller/admin/permission/vo/permission/PermissionAssignRoleCourseScopeReqVO.java
  6. 5 0
      byzs-module-system/src/main/java/cn/iocoder/byzs/module/system/controller/admin/permission/vo/role/RoleRespVO.java
  7. 7 0
      byzs-module-system/src/main/java/cn/iocoder/byzs/module/system/dal/dataobject/permission/RoleDO.java
  8. 8 0
      byzs-module-system/src/main/java/cn/iocoder/byzs/module/system/service/permission/PermissionService.java
  9. 5 0
      byzs-module-system/src/main/java/cn/iocoder/byzs/module/system/service/permission/PermissionServiceImpl.java
  10. 7 0
      byzs-module-system/src/main/java/cn/iocoder/byzs/module/system/service/permission/RoleService.java
  11. 16 0
      byzs-module-system/src/main/java/cn/iocoder/byzs/module/system/service/permission/RoleServiceImpl.java
  12. 1 1
      byzs-web/src/main/java/cn/iocoder/byzs/module/web/controller/admin/ai/WebAiController.java
  13. 90 0
      byzs-web/src/main/java/cn/iocoder/byzs/module/web/controller/admin/course/WebCourseController.java
  14. 45 4
      byzs-web/src/main/java/cn/iocoder/byzs/module/web/controller/admin/login/WebLoginController.java
  15. 27 0
      byzs-web/src/main/java/cn/iocoder/byzs/module/web/controller/admin/login/vo/AuthLoginVO.java

+ 22 - 4
byzs-course/src/main/java/cn/iocoder/byzs/module/bjdx/dal/mysql/coursetype/CourseTypeMapper.java

@@ -18,12 +18,30 @@ import cn.iocoder.byzs.module.bjdx.controller.admin.coursetype.vo.*;
 public interface CourseTypeMapper extends BaseMapperX<CourseTypeDO> {
 
     default List<CourseTypeDO> selectList(CourseTypeListReqVO reqVO) {
-        return selectList(new LambdaQueryWrapperX<CourseTypeDO>()
+        LambdaQueryWrapperX<CourseTypeDO> queryWrapper = new LambdaQueryWrapperX<CourseTypeDO>()
                 .likeIfPresent(CourseTypeDO::getCtType, reqVO.getCtType())
-                .eqIfPresent(CourseTypeDO::getCtParentId, reqVO.getCtParentId())
-                .eqIfPresent(CourseTypeDO::getCtTypeNode, reqVO.getCtTypeNode())
+                .eqIfPresent(CourseTypeDO::getCtParentId, reqVO.getCtParentId());
+        
+        // 处理getCtTypeNode的特殊条件:不为空且不等于0时,同时查询该值和0
+        Object ctTypeNode = reqVO.getCtTypeNode();
+        if (ctTypeNode != null && !ctTypeNode.toString().equals("0")) {
+            queryWrapper.and(wrapper -> wrapper
+                    .eq(CourseTypeDO::getCtTypeNode, ctTypeNode)
+                    .or()
+                    .eq(CourseTypeDO::getCtTypeNode, 0)
+            );
+        } else {
+            // 保持原有的eqIfPresent行为
+            queryWrapper.eqIfPresent(CourseTypeDO::getCtTypeNode, ctTypeNode);
+        }
+        
+        // 完成构建queryWrapper并添加剩余条件
+        queryWrapper
                 .likeIfPresent(CourseTypeDO::getCtTypeDescribe, reqVO.getCtTypeDescribe())
-                .orderByAsc(CourseTypeDO::getCtTypeSort));
+                .orderByAsc(CourseTypeDO::getCtTypeSort);
+        
+        // 使用BaseMapperX的selectList方法执行查询
+        return selectList(queryWrapper);
     }
 
 	default CourseTypeDO selectByCtParentIdAndCtType(Long ctParentId, String ctType) {

+ 1 - 1
byzs-course/src/main/resources/mapper/course/CourseMapper.xml

@@ -41,7 +41,7 @@
     </select>
     <select id="selectCourseList" parameterType="cn.iocoder.byzs.module.bjdx.controller.admin.course.vo.CoursePageReqVO"
             resultType="cn.iocoder.byzs.module.bjdx.controller.admin.course.vo.CoursePageReqVO">
-        SELECT c.tenant_id as tenantId,c.id, c.course_name courseName, c.course_image_path courseImagePath, c.course_music_path courseMusicPath, c.course_video_path courseVideoPath, c.course_content_type courseContentType, c.course_author courseAuthor, c.course_teacher courseTeacher, c.course_size courseSize,
+        SELECT c.tenant_id as tenantId,c.id, c.course_name courseName, c.course_type courseType, c.course_image_path courseImagePath, c.course_music_path courseMusicPath, c.course_video_path courseVideoPath, c.course_content_type courseContentType, c.course_author courseAuthor, c.course_teacher courseTeacher, c.course_size courseSize,
         c.course_time courseTime, c.course_is_inspect courseIsInspect, c.course_label courseLabel, c.course_order courseOrder, c.course_status courseStatus, ct.ct_type courseTypeName
         <include refid="selectCourseList"/>
     </select>

+ 1 - 0
byzs-module-ai/src/main/java/cn/iocoder/byzs/module/ai/dal/mysql/virtualdevice/VirtualDeviceMapper.java

@@ -22,6 +22,7 @@ public interface VirtualDeviceMapper extends BaseMapperX<VirtualDeviceDO> {
                 .likeIfPresent(VirtualDeviceDO::getName, reqVO.getName())
                 .eqIfPresent(VirtualDeviceDO::getImage, reqVO.getImage())
                 .eqIfPresent(VirtualDeviceDO::getRoutePath, reqVO.getRoutePath())
+                .eqIfPresent(VirtualDeviceDO::getStatus, reqVO.getStatus())
                 .eqIfPresent(VirtualDeviceDO::getJsonData, reqVO.getJsonData())
                 .betweenIfPresent(VirtualDeviceDO::getCreateTime, reqVO.getCreateTime())
                 .eqIfPresent(VirtualDeviceDO::getTenantId, reqVO.getTenantId())

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

@@ -2,6 +2,7 @@ package cn.iocoder.byzs.module.system.controller.admin.permission;
 
 import cn.hutool.core.collection.CollUtil;
 import cn.iocoder.byzs.framework.common.pojo.CommonResult;
+import cn.iocoder.byzs.module.system.controller.admin.permission.vo.permission.PermissionAssignRoleCourseScopeReqVO;
 import cn.iocoder.byzs.module.system.controller.admin.permission.vo.permission.PermissionAssignRoleDataScopeReqVO;
 import cn.iocoder.byzs.module.system.controller.admin.permission.vo.permission.PermissionAssignRoleMenuReqVO;
 import cn.iocoder.byzs.module.system.controller.admin.permission.vo.permission.PermissionAssignUserRoleReqVO;
@@ -63,6 +64,14 @@ public class PermissionController {
         return success(true);
     }
 
+    @PostMapping("/assign-role-course-scope")
+    @Operation(summary = "赋予角色课程权限")
+    @PreAuthorize("@ss.hasPermission('system:permission:assign-role-data-scope')")//后续改成course单独权限,目前没办法测试并更改租户默认管理员的权限
+    public CommonResult<Boolean> assignRoleCourseScope(@Valid @RequestBody PermissionAssignRoleCourseScopeReqVO reqVO) {
+        permissionService.assignRoleCourseScope(reqVO.getRoleId(), reqVO.getDataScopeCourseIds());
+        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/PermissionAssignRoleCourseScopeReqVO.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 = "管理后台 - 赋予角色课程权限 Request VO")
+@Data
+public class PermissionAssignRoleCourseScopeReqVO {
+
+    @Schema(description = "角色编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    @NotNull(message = "角色编号不能为空")
+    private Long roleId;
+
+    @Schema(description = "课程编号列表,只有范围类型为 COURSE_CUSTOM 时,该字段才需要", example = "1,3,5")
+    private Set<Long> dataScopeCourseIds = Collections.emptySet(); // 兜底
+
+}

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

@@ -53,6 +53,11 @@ public class RoleRespVO {
     @Schema(description = "数据范围(指定部门数组)", example = "1")
     private Set<Long> dataScopeDeptIds;
 
+    @Schema(description = "课程范围(指定课程数组)", example = "1")
+    private Set<Long> dataScopeCourseIds;
+
+
+
     @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED, example = "时间戳格式")
     private LocalDateTime createTime;
 

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

@@ -74,5 +74,12 @@ public class RoleDO extends TenantBaseDO {
      */
     @TableField(typeHandler = JacksonTypeHandler.class)
     private Set<Long> dataScopeDeptIds;
+    /**
+     * 数据范围(指定课程数组)
+     *
+     * 适用于 {@link #dataScope} 的值为 {@link DataScopeEnum#COURSE_CUSTOM} 时
+     */
+    @TableField(typeHandler = JacksonTypeHandler.class)
+    private Set<Long> dataScopeCourseIds;
 
 }

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

@@ -135,6 +135,14 @@ public interface PermissionService {
      */
     void assignRoleDataScope(Long roleId, Integer dataScope, Set<Long> dataScopeDeptIds);
 
+    /**
+     * 设置角色的课程权限
+     *
+     * @param roleId           角色编号
+     * @param dataScopeCourseIds 课程编号数组
+     */
+    void assignRoleCourseScope(Long roleId, Set<Long> dataScopeCourseIds);
+
     /**
      * 获得登陆用户的部门数据权限
      *

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

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

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

@@ -48,6 +48,13 @@ public interface RoleService {
      * @param dataScopeDeptIds 部门编号数组
      */
     void updateRoleDataScope(Long id, Integer dataScope, Set<Long> dataScopeDeptIds);
+    /**
+     * 设置角色的课程权限
+     *
+     * @param id 角色编号
+     * @param dataScopeCourseIds 课程编号数组
+     */
+    void updateRoleCourseScope(Long id, Set<Long> dataScopeCourseIds);
 
     /**
      * 获得角色

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

@@ -30,6 +30,7 @@ import org.springframework.transaction.annotation.Transactional;
 import org.springframework.util.StringUtils;
 
 import java.util.*;
+import java.util.stream.Collectors;
 
 import static cn.iocoder.byzs.framework.common.exception.util.ServiceExceptionUtil.exception;
 import static cn.iocoder.byzs.framework.common.util.collection.CollectionUtils.convertMap;
@@ -104,6 +105,21 @@ public class RoleServiceImpl implements RoleService {
         roleMapper.updateById(updateObject);
     }
 
+    @Override
+    public void updateRoleCourseScope(Long id, Set<Long> dataScopeCourseIds) {
+        // 校验是否可以更新
+        validateRoleForUpdate(id);
+
+        // 更新课程权限
+        RoleDO updateObject = new RoleDO();
+        updateObject.setId(id);
+        dataScopeCourseIds = dataScopeCourseIds.stream()
+                .map(num -> num / 100)  // 除以100.0触发浮点运算
+                .collect(Collectors.toSet());
+        updateObject.setDataScopeCourseIds(dataScopeCourseIds);
+        roleMapper.updateById(updateObject);
+    }
+
     @Override
     @Transactional(rollbackFor = Exception.class)
     @CacheEvict(value = RedisKeyConstants.ROLE, key = "#id")

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

@@ -84,7 +84,7 @@ public class WebAiController {
     }
 
     @GetMapping("/selectVirtualDevice")
-    @Operation(summary = "获得聊天角色分页")
+    @Operation(summary = "获得虚拟设备分页")
     @PermitAll
     @TenantIgnore
     public CommonResult<PageResult<VirtualDeviceRespVO>> selectVirtualDevice(@Valid VirtualDevicePageReqVO pageReqVO) {

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

@@ -10,6 +10,7 @@ import cn.iocoder.byzs.module.bjdx.service.coursetype.CourseTypeService;
 import cn.iocoder.byzs.module.web.controller.admin.course.vo.WebCourseVO;
 import cn.iocoder.byzs.module.web.service.course.WebCourseServiceImpl;
 import jakarta.annotation.security.PermitAll;
+import lombok.Data;
 import org.springframework.web.bind.annotation.*;
 import jakarta.annotation.Resource;
 import org.springframework.validation.annotation.Validated;
@@ -75,4 +76,93 @@ public class WebCourseController {
         List<CourseTypeRespVO> listVo = BeanUtils.toBean(list, CourseTypeRespVO.class);
         return success(listVo);
     }
+
+    @GetMapping("/getCourseTypeTree")
+    @Operation(summary = "获取课程类型树结构")
+    @TenantIgnore
+    @PermitAll
+    public CommonResult<List<TreeNode>> getCourseTypeTree() {
+        // 获取所有课程类型
+        List<CourseTypeDO> allCourseTypes = courseTypeService.getCourseTypeList(new CourseTypeListReqVO());
+        // 获取所有课程
+        List<CoursePageReqVO> allCourses = courseService.getCourseList(new CoursePageReqVO());
+        
+        // 构建课程类型ID到课程类型的映射
+        Map<Long, CourseTypeDO> courseTypeMap = new HashMap<>();
+        for (CourseTypeDO courseType : allCourseTypes) {
+            courseTypeMap.put(courseType.getId(), courseType);
+        }
+        
+        // 构建课程类型树节点
+        List<TreeNode> treeNodes = new ArrayList<>();
+        
+        // 处理根节点 (parentId = 0)
+        for (CourseTypeDO courseType : allCourseTypes) {
+            if (CourseTypeDO.CT_PARENT_ID_ROOT.equals(courseType.getCtParentId())) {
+                TreeNode node = new TreeNode();
+                node.setId(courseType.getId());
+                node.setParentId(courseType.getCtParentId());
+                node.setName(courseType.getCtType() + "(年级)");
+                node.setType("年级");
+                treeNodes.add(node);
+            }
+        }
+        
+        // 递归添加子节点
+        for (TreeNode node : treeNodes) {
+            addChildNodes(node, allCourseTypes, allCourses, courseTypeMap);
+        }
+        
+        return success(treeNodes);
+    }
+    
+    /**
+     * 递归添加子节点
+     */
+    private void addChildNodes(TreeNode parentNode, List<CourseTypeDO> allCourseTypes, 
+                             List<CoursePageReqVO> allCourses, Map<Long, CourseTypeDO> courseTypeMap) {
+        List<TreeNode> children = new ArrayList<>();
+        
+        // 添加子课程类型
+        for (CourseTypeDO courseType : allCourseTypes) {
+            if (parentNode.getId().equals(courseType.getCtParentId())) {
+                TreeNode childNode = new TreeNode();
+                childNode.setId(courseType.getId());
+                childNode.setParentId(courseType.getCtParentId());
+                String nodeType = courseType.getCtTypeNode().equals("1") ? "通识课" : "实操课";
+                childNode.setName(courseType.getCtType() + "(类型)");
+                childNode.setType(nodeType);
+                children.add(childNode);
+                
+                // 递归添加子节点的子节点
+                addChildNodes(childNode, allCourseTypes, allCourses, courseTypeMap);
+            }
+        }
+        
+        // 添加关联的课程
+        for (CoursePageReqVO course : allCourses) {
+            if (parentNode.getId().equals(course.getCourseType())) {
+                TreeNode childNode = new TreeNode();
+                childNode.setId(course.getId()*100);
+                childNode.setParentId(course.getCourseType());
+                childNode.setName(course.getCourseName());
+                childNode.setType("course");
+                children.add(childNode);
+            }
+        }
+        
+        parentNode.setChildren(children);
+    }
+    
+    /**
+     * 树节点类,用于表示课程类型树结构
+     */
+    @Data
+    public static class TreeNode {
+        private Long id;
+        private Long parentId;
+        private String name;
+        private String type; // courseType 或 course
+        private List<TreeNode> children = new ArrayList<>();
+    }
 }

+ 45 - 4
byzs-web/src/main/java/cn/iocoder/byzs/module/web/controller/admin/login/WebLoginController.java

@@ -1,7 +1,9 @@
 package cn.iocoder.byzs.module.web.controller.admin.login;
 
 import cn.hutool.core.util.StrUtil;
+import cn.iocoder.byzs.framework.common.enums.CommonStatusEnum;
 import cn.iocoder.byzs.framework.common.pojo.CommonResult;
+import cn.iocoder.byzs.framework.common.util.object.BeanUtils;
 import cn.iocoder.byzs.framework.security.config.SecurityProperties;
 import cn.iocoder.byzs.framework.security.core.util.SecurityFrameworkUtils;
 import cn.iocoder.byzs.framework.tenant.core.aop.TenantIgnore;
@@ -9,10 +11,14 @@ import cn.iocoder.byzs.module.system.controller.admin.auth.vo.AuthLoginReqVO;
 import cn.iocoder.byzs.module.system.controller.admin.auth.vo.AuthLoginRespVO;
 import cn.iocoder.byzs.module.system.controller.admin.auth.vo.AuthSmsLoginReqVO;
 import cn.iocoder.byzs.module.system.controller.admin.auth.vo.AuthSmsSendReqVO;
+import cn.iocoder.byzs.module.system.dal.dataobject.permission.RoleDO;
 import cn.iocoder.byzs.module.system.dal.dataobject.tenant.TenantDO;
 import cn.iocoder.byzs.module.system.enums.logger.LoginLogTypeEnum;
 import cn.iocoder.byzs.module.system.service.auth.AdminAuthService;
+import cn.iocoder.byzs.module.system.service.permission.PermissionService;
+import cn.iocoder.byzs.module.system.service.permission.RoleService;
 import cn.iocoder.byzs.module.system.service.tenant.TenantService;
+import cn.iocoder.byzs.module.web.controller.admin.login.vo.AuthLoginVO;
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.Parameter;
 import io.swagger.v3.oas.annotations.tags.Tag;
@@ -22,6 +28,10 @@ import jakarta.servlet.http.HttpServletRequest;
 import jakarta.validation.Valid;
 import org.springframework.web.bind.annotation.*;
 
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
 import static cn.iocoder.byzs.framework.common.pojo.CommonResult.success;
 
 @Tag(name = "前端")
@@ -35,6 +45,10 @@ public class WebLoginController {
     private AdminAuthService authService;
     @Resource
     private SecurityProperties securityProperties;
+    @Resource
+    private PermissionService permissionService;
+    @Resource
+    private RoleService roleService;
 
 
     @GetMapping("/getTenantIdByName")
@@ -50,15 +64,15 @@ public class WebLoginController {
     @PostMapping("/login")
     @PermitAll
     @Operation(summary = "使用账号密码登录")
-    public CommonResult<AuthLoginRespVO> login(@RequestBody @Valid AuthLoginReqVO reqVO) {
-        return success(authService.login(reqVO));
+    public CommonResult<AuthLoginVO> login(@RequestBody @Valid AuthLoginReqVO reqVO) {
+        return success(setAuthRoleVO(authService.login(reqVO)));
     }
 
     @PostMapping("/sms-login")
     @PermitAll
     @Operation(summary = "使用短信验证码登录")
-    public CommonResult<AuthLoginRespVO> smsLogin(@RequestBody @Valid AuthSmsLoginReqVO reqVO) {
-        return success(authService.smsLogin(reqVO));
+    public CommonResult<AuthLoginVO> smsLogin(@RequestBody @Valid AuthSmsLoginReqVO reqVO) {
+        return success(setAuthRoleVO(authService.smsLogin(reqVO)));
     }
 
     @PostMapping("/send-sms-code")
@@ -80,4 +94,31 @@ public class WebLoginController {
         }
         return success(true);
     }
+
+    /**
+     * 填充课程数据权限
+     * @param login
+     * @return
+     */
+    private AuthLoginVO setAuthRoleVO(AuthLoginRespVO login) {
+
+        // 获得角色列表
+        Set<Long> roleIds = permissionService.getUserRoleIdListByUserId(login.getUserId());
+        List<RoleDO> roles = roleService.getRoleList(roleIds);
+        roles.removeIf(role -> !CommonStatusEnum.ENABLE.getStatus().equals(role.getStatus())); // 移除禁用的角色
+
+        // 将所有角色的dataScopeCourseIds拼接成一个Set集合
+        Set<Long> allDataScopeCourseIds = new HashSet<>();
+        for (RoleDO role : roles) {
+            Set<Long> dataScopeCourseIds = role.getDataScopeCourseIds();
+            if (dataScopeCourseIds != null && !dataScopeCourseIds.isEmpty()) {
+                allDataScopeCourseIds.addAll(dataScopeCourseIds);
+            }
+        }
+
+        //填充课程数据权限
+        AuthLoginVO authLoginVO = BeanUtils.toBean(login, AuthLoginVO.class);
+        authLoginVO.setCourseDataScope(allDataScopeCourseIds);
+        return authLoginVO;
+    }
 }

+ 27 - 0
byzs-web/src/main/java/cn/iocoder/byzs/module/web/controller/admin/login/vo/AuthLoginVO.java

@@ -0,0 +1,27 @@
+package cn.iocoder.byzs.module.web.controller.admin.login.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+import java.util.Set;
+
+@Schema(description = "管理强泰 - 账号密码登录 Request VO")
+@Data
+public class AuthLoginVO {
+
+    @Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+    private Long userId;
+
+    @Schema(description = "访问令牌", requiredMode = Schema.RequiredMode.REQUIRED, example = "happy")
+    private String accessToken;
+
+    @Schema(description = "刷新令牌", requiredMode = Schema.RequiredMode.REQUIRED, example = "nice")
+    private String refreshToken;
+
+    @Schema(description = "过期时间", requiredMode = Schema.RequiredMode.REQUIRED)
+    private LocalDateTime expiresTime;
+
+    @Schema(description = "课程权限")
+    private Set<Long> courseDataScope;
+}