新增培训:实现课程两步式向导与教学资源关联
This commit is contained in:
1 parent
55143af4a5
commit
83d22ba5e0
20 files changed
+1145
-290
No files matched your search
+33
-2
@@ -3,6 +3,7 @@ package com.ruoyi.web.controller.train;
|
||||
import java.util.List;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.PutMapping;
|
||||
@@ -17,7 +18,9 @@ import com.ruoyi.common.core.domain.AjaxResult;
|
||||
import com.ruoyi.common.enums.BusinessType;
|
||||
import com.ruoyi.common.utils.StringUtils;
|
||||
import com.ruoyi.train.domain.TrainCourse;
|
||||
import com.ruoyi.train.domain.dto.CourseResourceBindDTO;
|
||||
import com.ruoyi.train.domain.vo.CourseDetailVO;
|
||||
import com.ruoyi.train.service.ITrainCourseResourceService;
|
||||
import com.ruoyi.train.service.ITrainCourseService;
|
||||
import com.ruoyi.common.core.page.TableDataInfo;
|
||||
|
||||
@@ -34,6 +37,9 @@ public class TrainCourseController extends BaseController
|
||||
@Autowired
|
||||
private ITrainCourseService trainCourseService;
|
||||
|
||||
@Autowired
|
||||
private ITrainCourseResourceService trainCourseResourceService;
|
||||
|
||||
/**
|
||||
* 查询课程信息列表 【P0】
|
||||
*/
|
||||
@@ -48,7 +54,7 @@ public class TrainCourseController extends BaseController
|
||||
|
||||
/**
|
||||
* 获取课程信息详细信息 【P0】
|
||||
* 返回结构:CourseDetailVO (包含课程基础字段与契约预留resources列表)
|
||||
* 返回结构:CourseDetailVO(包含课程基础字段与已关联资源列表)
|
||||
*/
|
||||
@PreAuthorize("@ss.hasPermi('train:course:query')")
|
||||
@GetMapping(value = "/{cId}")
|
||||
@@ -82,7 +88,8 @@ public class TrainCourseController extends BaseController
|
||||
{
|
||||
return AjaxResult.error(400, "课程学时必须为正整数");
|
||||
}
|
||||
return toAjax(trainCourseService.insertTrainCourse(trainCourse));
|
||||
int rows = trainCourseService.insertTrainCourse(trainCourse);
|
||||
return rows > 0 ? success(trainCourse) : error();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -105,6 +112,30 @@ public class TrainCourseController extends BaseController
|
||||
return toAjax(trainCourseService.updateTrainCourse(trainCourse));
|
||||
}
|
||||
|
||||
/**
|
||||
* 为课程绑定教学资源。
|
||||
*/
|
||||
@PreAuthorize("@ss.hasPermi('train:course:edit')")
|
||||
@Log(title = "课程教学资源", businessType = BusinessType.INSERT)
|
||||
@PostMapping("/{cId}/resources")
|
||||
public AjaxResult bindResource(@PathVariable("cId") String cId,
|
||||
@Validated @RequestBody CourseResourceBindDTO bindDTO)
|
||||
{
|
||||
return toAjax(trainCourseResourceService.bindResource(cId, bindDTO.getResourceId(), bindDTO.getSortNo()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 解除课程与教学资源的关联,不删除资源本身。
|
||||
*/
|
||||
@PreAuthorize("@ss.hasPermi('train:course:edit')")
|
||||
@Log(title = "课程教学资源", businessType = BusinessType.DELETE)
|
||||
@DeleteMapping("/{cId}/resources/{resourceId}")
|
||||
public AjaxResult unbindResource(@PathVariable("cId") String cId,
|
||||
@PathVariable("resourceId") String resourceId)
|
||||
{
|
||||
return toAjax(trainCourseResourceService.unbindResource(cId, resourceId));
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除课程信息
|
||||
*/
|
||||
|
||||
+2
-1
@@ -64,7 +64,8 @@ public class TrainResourceController extends BaseController
|
||||
public AjaxResult add(@Validated @RequestBody TrainResource trainResource)
|
||||
{
|
||||
trainResource.setCreateBy(SecurityUtils.getUsername());
|
||||
return toAjax(trainResourceService.insertTrainResource(trainResource));
|
||||
int rows = trainResourceService.insertTrainResource(trainResource);
|
||||
return rows > 0 ? success(trainResource) : error();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -43,10 +43,6 @@ public class TrainCourse extends BaseEntity
|
||||
@Excel(name = "课程内容")
|
||||
private String courseContent;
|
||||
|
||||
/** 课程资源ID,多个资源以英文逗号分隔,对应 train_resource.resource_id */
|
||||
@Excel(name = "课程资源ID")
|
||||
private String resourceId;
|
||||
|
||||
/** 课程状态 (1:正常, 0:停用) */
|
||||
@Excel(name = "课程状态", readConverterExp = "1=正常,0=停用")
|
||||
private Integer status;
|
||||
@@ -184,16 +180,6 @@ public class TrainCourse extends BaseEntity
|
||||
return courseContent;
|
||||
}
|
||||
|
||||
public void setResourceId(String resourceId)
|
||||
{
|
||||
this.resourceId = resourceId;
|
||||
}
|
||||
|
||||
public String getResourceId()
|
||||
{
|
||||
return resourceId;
|
||||
}
|
||||
|
||||
public void setStatus(Integer status)
|
||||
{
|
||||
this.status = status;
|
||||
@@ -224,7 +210,6 @@ public class TrainCourse extends BaseEntity
|
||||
.append("cDuration", getcDuration())
|
||||
.append("teachingObjective", getTeachingObjective())
|
||||
.append("courseContent", getCourseContent())
|
||||
.append("resourceId", getResourceId())
|
||||
.append("status", getStatus())
|
||||
.append("delFlag", getDelFlag())
|
||||
.append("createBy", getCreateBy())
|
||||
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
package com.ruoyi.train.domain.dto;
|
||||
|
||||
import javax.validation.constraints.Min;
|
||||
import javax.validation.constraints.NotBlank;
|
||||
|
||||
/**
|
||||
* 课程绑定教学资源请求。
|
||||
*/
|
||||
public class CourseResourceBindDTO
|
||||
{
|
||||
@NotBlank(message = "资源ID不能为空")
|
||||
private String resourceId;
|
||||
|
||||
@Min(value = 0, message = "资源排序号不能小于0")
|
||||
private Integer sortNo;
|
||||
|
||||
public String getResourceId()
|
||||
{
|
||||
return resourceId;
|
||||
}
|
||||
|
||||
public void setResourceId(String resourceId)
|
||||
{
|
||||
this.resourceId = resourceId;
|
||||
}
|
||||
|
||||
public Integer getSortNo()
|
||||
{
|
||||
return sortNo;
|
||||
}
|
||||
|
||||
public void setSortNo(Integer sortNo)
|
||||
{
|
||||
this.sortNo = sortNo;
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,12 @@
|
||||
package com.ruoyi.train.domain.vo;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.ruoyi.train.domain.TrainCourse;
|
||||
import com.ruoyi.train.domain.TrainResource;
|
||||
|
||||
/**
|
||||
* 课程详情视图对象 (API Contract: CourseDetailVO)
|
||||
@@ -60,14 +60,10 @@ public class CourseDetailVO implements Serializable
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date updateTime;
|
||||
|
||||
private String resourceId;
|
||||
|
||||
/**
|
||||
* 已关联的教学资源列表
|
||||
* 注:当前 ISSUE-01-03 未实现真实 Course<->Resource 查询;
|
||||
* 绑定/解绑及真实关联能力属于 P1。为保证集合字段结构稳定,默认返回空列表 (empty list)。
|
||||
*/
|
||||
private List<Object> resources;
|
||||
private List<TrainResource> resources;
|
||||
|
||||
public CourseDetailVO()
|
||||
{
|
||||
@@ -93,9 +89,6 @@ public class CourseDetailVO implements Serializable
|
||||
vo.setCreateTime(course.getCreateTime());
|
||||
vo.setUpdateBy(course.getUpdateBy());
|
||||
vo.setUpdateTime(course.getUpdateTime());
|
||||
vo.setResourceId(course.getResourceId());
|
||||
// 资源绑定/解绑能力属于 P1,当前返回空列表保持响应结构稳定
|
||||
vo.setResources(Collections.emptyList());
|
||||
return vo;
|
||||
}
|
||||
|
||||
@@ -289,20 +282,12 @@ public class CourseDetailVO implements Serializable
|
||||
this.updateTime = updateTime;
|
||||
}
|
||||
|
||||
public String getResourceId() {
|
||||
return resourceId;
|
||||
}
|
||||
|
||||
public void setResourceId(String resourceId) {
|
||||
this.resourceId = resourceId;
|
||||
}
|
||||
|
||||
public List<Object> getResources()
|
||||
public List<TrainResource> getResources()
|
||||
{
|
||||
return resources;
|
||||
}
|
||||
|
||||
public void setResources(List<Object> resources)
|
||||
public void setResources(List<TrainResource> resources)
|
||||
{
|
||||
this.resources = resources;
|
||||
}
|
||||
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
package com.ruoyi.train.mapper;
|
||||
|
||||
import java.util.List;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import com.ruoyi.train.domain.TrainResource;
|
||||
|
||||
/**
|
||||
* 课程与教学资源关联Mapper。
|
||||
*/
|
||||
public interface TrainCourseResourceMapper
|
||||
{
|
||||
public List<TrainResource> selectResourcesByCourseId(@Param("cId") String cId);
|
||||
|
||||
public int countBinding(@Param("cId") String cId, @Param("resourceId") String resourceId);
|
||||
|
||||
public int insertBinding(@Param("cId") String cId, @Param("resourceId") String resourceId,
|
||||
@Param("sortNo") Integer sortNo, @Param("createBy") String createBy);
|
||||
|
||||
public int updateBindingSort(@Param("cId") String cId, @Param("resourceId") String resourceId,
|
||||
@Param("sortNo") Integer sortNo);
|
||||
|
||||
public int deleteBinding(@Param("cId") String cId, @Param("resourceId") String resourceId);
|
||||
|
||||
public int countCourseReferencesByResourceId(@Param("resourceId") String resourceId);
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
package com.ruoyi.train.service;
|
||||
|
||||
/**
|
||||
* 课程与教学资源关联Service。
|
||||
*/
|
||||
public interface ITrainCourseResourceService
|
||||
{
|
||||
public int bindResource(String cId, String resourceId, Integer sortNo);
|
||||
|
||||
public int unbindResource(String cId, String resourceId);
|
||||
}
|
||||
+78
@@ -0,0 +1,78 @@
|
||||
package com.ruoyi.train.service.impl;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import com.ruoyi.common.exception.ServiceException;
|
||||
import com.ruoyi.common.utils.SecurityUtils;
|
||||
import com.ruoyi.common.utils.StringUtils;
|
||||
import com.ruoyi.train.domain.TrainResource;
|
||||
import com.ruoyi.train.mapper.TrainCourseMapper;
|
||||
import com.ruoyi.train.mapper.TrainCourseResourceMapper;
|
||||
import com.ruoyi.train.mapper.TrainResourceMapper;
|
||||
import com.ruoyi.train.service.ITrainCourseResourceService;
|
||||
|
||||
/**
|
||||
* 课程与教学资源关联Service实现。
|
||||
*/
|
||||
@Service
|
||||
public class TrainCourseResourceServiceImpl implements ITrainCourseResourceService
|
||||
{
|
||||
@Autowired
|
||||
private TrainCourseMapper trainCourseMapper;
|
||||
|
||||
@Autowired
|
||||
private TrainResourceMapper trainResourceMapper;
|
||||
|
||||
@Autowired
|
||||
private TrainCourseResourceMapper trainCourseResourceMapper;
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public int bindResource(String cId, String resourceId, Integer sortNo)
|
||||
{
|
||||
if (StringUtils.isEmpty(cId) || trainCourseMapper.selectTrainCourseByCId(cId) == null)
|
||||
{
|
||||
throw new ServiceException("课程不存在", 404);
|
||||
}
|
||||
TrainResource resource = trainResourceMapper.selectTrainResourceByResourceId(resourceId);
|
||||
if (resource == null)
|
||||
{
|
||||
throw new ServiceException("教学资源不存在", 404);
|
||||
}
|
||||
|
||||
int normalizedSortNo = sortNo == null ? 0 : sortNo;
|
||||
if (trainCourseResourceMapper.countBinding(cId, resourceId) > 0)
|
||||
{
|
||||
trainCourseResourceMapper.updateBindingSort(cId, resourceId, normalizedSortNo);
|
||||
return 1;
|
||||
}
|
||||
|
||||
String createBy = null;
|
||||
try
|
||||
{
|
||||
createBy = SecurityUtils.getUsername();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
// 无登录上下文的测试环境允许为空。
|
||||
}
|
||||
return trainCourseResourceMapper.insertBinding(cId, resourceId, normalizedSortNo, createBy);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public int unbindResource(String cId, String resourceId)
|
||||
{
|
||||
if (StringUtils.isEmpty(cId) || trainCourseMapper.selectTrainCourseByCId(cId) == null)
|
||||
{
|
||||
throw new ServiceException("课程不存在", 404);
|
||||
}
|
||||
int rows = trainCourseResourceMapper.deleteBinding(cId, resourceId);
|
||||
if (rows == 0)
|
||||
{
|
||||
throw new ServiceException("课程未关联该教学资源", 404);
|
||||
}
|
||||
return rows;
|
||||
}
|
||||
}
|
||||
+10
-1
@@ -9,6 +9,7 @@ import com.ruoyi.common.utils.SecurityUtils;
|
||||
import com.ruoyi.common.utils.StringUtils;
|
||||
import com.ruoyi.common.utils.uuid.IdUtils;
|
||||
import com.ruoyi.train.mapper.TrainCourseMapper;
|
||||
import com.ruoyi.train.mapper.TrainCourseResourceMapper;
|
||||
import com.ruoyi.train.mapper.TrainPlanCourseMapper;
|
||||
import com.ruoyi.train.domain.TrainCourse;
|
||||
import com.ruoyi.train.domain.vo.CourseDetailVO;
|
||||
@@ -26,6 +27,9 @@ public class TrainCourseServiceImpl implements ITrainCourseService
|
||||
@Autowired
|
||||
private TrainCourseMapper trainCourseMapper;
|
||||
|
||||
@Autowired
|
||||
private TrainCourseResourceMapper trainCourseResourceMapper;
|
||||
|
||||
@Autowired
|
||||
private TrainPlanCourseMapper trainPlanCourseMapper;
|
||||
|
||||
@@ -39,7 +43,12 @@ public class TrainCourseServiceImpl implements ITrainCourseService
|
||||
public CourseDetailVO selectCourseDetailByCId(String cId)
|
||||
{
|
||||
TrainCourse course = trainCourseMapper.selectTrainCourseByCId(cId);
|
||||
return CourseDetailVO.from(course);
|
||||
CourseDetailVO detail = CourseDetailVO.from(course);
|
||||
if (detail != null)
|
||||
{
|
||||
detail.setResources(trainCourseResourceMapper.selectResourcesByCourseId(cId));
|
||||
}
|
||||
return detail;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+17
@@ -9,6 +9,7 @@ import com.ruoyi.common.utils.StringUtils;
|
||||
import com.ruoyi.common.utils.uuid.IdUtils;
|
||||
import com.ruoyi.train.domain.TrainResource;
|
||||
import com.ruoyi.train.mapper.TrainResourceMapper;
|
||||
import com.ruoyi.train.mapper.TrainCourseResourceMapper;
|
||||
import com.ruoyi.train.service.ITrainResourceService;
|
||||
|
||||
/**
|
||||
@@ -25,6 +26,9 @@ public class TrainResourceServiceImpl implements ITrainResourceService
|
||||
@Autowired
|
||||
private TrainResourceMapper trainResourceMapper;
|
||||
|
||||
@Autowired
|
||||
private TrainCourseResourceMapper trainCourseResourceMapper;
|
||||
|
||||
/**
|
||||
* 查询教学资源
|
||||
*
|
||||
@@ -112,6 +116,10 @@ public class TrainResourceServiceImpl implements ITrainResourceService
|
||||
{
|
||||
throw new ServiceException("请选择要删除的资源");
|
||||
}
|
||||
for (String resourceId : resourceIds)
|
||||
{
|
||||
assertResourceNotReferenced(resourceId);
|
||||
}
|
||||
return trainResourceMapper.deleteTrainResourceByResourceIds(resourceIds);
|
||||
}
|
||||
|
||||
@@ -128,9 +136,18 @@ public class TrainResourceServiceImpl implements ITrainResourceService
|
||||
{
|
||||
throw new ServiceException("资源ID不能为空");
|
||||
}
|
||||
assertResourceNotReferenced(resourceId);
|
||||
return trainResourceMapper.deleteTrainResourceByResourceId(resourceId);
|
||||
}
|
||||
|
||||
private void assertResourceNotReferenced(String resourceId)
|
||||
{
|
||||
if (trainCourseResourceMapper.countCourseReferencesByResourceId(resourceId) > 0)
|
||||
{
|
||||
throw new ServiceException("教学资源已被课程引用,请先解除课程关联");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验资源类型取值:1文档 2视频 33D模型 4VR资源 5外部教学链接 6其他
|
||||
*/
|
||||
|
||||
@@ -12,7 +12,6 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
<result property="cDuration" column="c_duration" />
|
||||
<result property="teachingObjective" column="teaching_objective" />
|
||||
<result property="courseContent" column="course_content" />
|
||||
<result property="resourceId" column="resource_id" />
|
||||
<result property="status" column="status" />
|
||||
<result property="delFlag" column="del_flag" />
|
||||
<result property="createBy" column="create_by" />
|
||||
@@ -23,7 +22,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
</resultMap>
|
||||
|
||||
<sql id="selectTrainCourseVo">
|
||||
select c_id, c_code, c_name, c_type, c_duration, teaching_objective, course_content, resource_id, status, del_flag, create_by, create_time, update_by, update_time, remark from train_course
|
||||
select c_id, c_code, c_name, c_type, c_duration, teaching_objective, course_content, status, del_flag, create_by, create_time, update_by, update_time, remark from train_course
|
||||
</sql>
|
||||
|
||||
<select id="selectTrainCourseList" parameterType="TrainCourse" resultMap="TrainCourseResult">
|
||||
@@ -64,7 +63,6 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
<if test="cDuration != null">c_duration,</if>
|
||||
<if test="teachingObjective != null and teachingObjective != ''">teaching_objective,</if>
|
||||
<if test="courseContent != null and courseContent != ''">course_content,</if>
|
||||
<if test="resourceId != null and resourceId != ''">resource_id,</if>
|
||||
<if test="status != null">status,</if>
|
||||
<if test="delFlag != null and delFlag != ''">del_flag,</if>
|
||||
<if test="createBy != null and createBy != ''">create_by,</if>
|
||||
@@ -81,7 +79,6 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
<if test="cDuration != null">#{cDuration},</if>
|
||||
<if test="teachingObjective != null and teachingObjective != ''">#{teachingObjective},</if>
|
||||
<if test="courseContent != null and courseContent != ''">#{courseContent},</if>
|
||||
<if test="resourceId != null and resourceId != ''">#{resourceId},</if>
|
||||
<if test="status != null">#{status},</if>
|
||||
<if test="delFlag != null and delFlag != ''">#{delFlag},</if>
|
||||
<if test="createBy != null and createBy != ''">#{createBy},</if>
|
||||
@@ -101,7 +98,6 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
<if test="cDuration != null">c_duration = #{cDuration},</if>
|
||||
<if test="teachingObjective != null">teaching_objective = #{teachingObjective},</if>
|
||||
<if test="courseContent != null">course_content = #{courseContent},</if>
|
||||
<if test="resourceId != null">resource_id = #{resourceId},</if>
|
||||
<if test="status != null">status = #{status},</if>
|
||||
<if test="updateBy != null and updateBy != ''">update_by = #{updateBy},</if>
|
||||
<if test="updateTime != null">update_time = #{updateTime},</if>
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
<?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="com.ruoyi.train.mapper.TrainCourseResourceMapper">
|
||||
|
||||
<resultMap type="TrainResource" id="CourseResourceResult">
|
||||
<result property="resourceId" column="resource_id" />
|
||||
<result property="resourceName" column="resource_name" />
|
||||
<result property="resourceType" column="resource_type" />
|
||||
<result property="resourceUrl" column="resource_url" />
|
||||
<result property="description" column="description" />
|
||||
<result property="status" column="status" />
|
||||
<result property="createBy" column="create_by" />
|
||||
<result property="createTime" column="create_time" />
|
||||
<result property="updateBy" column="update_by" />
|
||||
<result property="updateTime" column="update_time" />
|
||||
<result property="remark" column="remark" />
|
||||
</resultMap>
|
||||
|
||||
<select id="selectResourcesByCourseId" parameterType="String" resultMap="CourseResourceResult">
|
||||
select r.resource_id, r.resource_name, r.resource_type, r.resource_url,
|
||||
r.description, r.status, r.create_by, r.create_time,
|
||||
r.update_by, r.update_time, r.remark
|
||||
from train_course_resource cr
|
||||
join train_resource r on r.resource_id = cr.resource_id and r.del_flag = '0'
|
||||
where cr.c_id = #{cId}
|
||||
order by cr.sort_no asc, cr.id asc
|
||||
</select>
|
||||
|
||||
<select id="countBinding" resultType="int">
|
||||
select count(1)
|
||||
from train_course_resource
|
||||
where c_id = #{cId} and resource_id = #{resourceId}
|
||||
</select>
|
||||
|
||||
<insert id="insertBinding">
|
||||
insert into train_course_resource (c_id, resource_id, sort_no, create_by, create_time)
|
||||
values (#{cId}, #{resourceId}, #{sortNo}, #{createBy}, current_timestamp)
|
||||
</insert>
|
||||
|
||||
<update id="updateBindingSort">
|
||||
update train_course_resource
|
||||
set sort_no = #{sortNo}
|
||||
where c_id = #{cId} and resource_id = #{resourceId}
|
||||
</update>
|
||||
|
||||
<delete id="deleteBinding">
|
||||
delete from train_course_resource
|
||||
where c_id = #{cId} and resource_id = #{resourceId}
|
||||
</delete>
|
||||
|
||||
<select id="countCourseReferencesByResourceId" parameterType="String" resultType="int">
|
||||
select count(1)
|
||||
from train_course_resource
|
||||
where resource_id = #{resourceId}
|
||||
</select>
|
||||
</mapper>
|
||||
+149
@@ -0,0 +1,149 @@
|
||||
package com.ruoyi.train.service.impl;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Proxy;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import com.ruoyi.common.exception.ServiceException;
|
||||
import com.ruoyi.train.domain.TrainCourse;
|
||||
import com.ruoyi.train.domain.TrainResource;
|
||||
import com.ruoyi.train.mapper.TrainCourseMapper;
|
||||
import com.ruoyi.train.mapper.TrainCourseResourceMapper;
|
||||
import com.ruoyi.train.mapper.TrainResourceMapper;
|
||||
|
||||
/**
|
||||
* 课程与教学资源规范关联服务测试。
|
||||
*/
|
||||
public class TrainCourseResourceServiceTest
|
||||
{
|
||||
private TrainCourseResourceServiceImpl service;
|
||||
private TrainCourse course;
|
||||
private TrainResource resource;
|
||||
|
||||
@Before
|
||||
public void setUp()
|
||||
{
|
||||
service = new TrainCourseResourceServiceImpl();
|
||||
course = new TrainCourse();
|
||||
course.setcId("C001");
|
||||
resource = new TrainResource();
|
||||
resource.setResourceId("R001");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void bindCreatesAssociationInsteadOfSerializingIds() throws Exception
|
||||
{
|
||||
AtomicInteger insertedSortNo = new AtomicInteger(-1);
|
||||
inject("trainCourseMapper", courseMapper(course));
|
||||
inject("trainResourceMapper", resourceMapper(resource));
|
||||
inject("trainCourseResourceMapper", associationMapper((proxy, method, args) -> {
|
||||
if ("countBinding".equals(method.getName()))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
if ("insertBinding".equals(method.getName()))
|
||||
{
|
||||
insertedSortNo.set((Integer) args[2]);
|
||||
return 1;
|
||||
}
|
||||
return defaultValue(method.getReturnType());
|
||||
}));
|
||||
|
||||
assertEquals(1, service.bindResource("C001", "R001", 3));
|
||||
assertEquals(3, insertedSortNo.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void duplicateBindUpdatesSortAndRemainsIdempotent() throws Exception
|
||||
{
|
||||
AtomicInteger updates = new AtomicInteger();
|
||||
inject("trainCourseMapper", courseMapper(course));
|
||||
inject("trainResourceMapper", resourceMapper(resource));
|
||||
inject("trainCourseResourceMapper", associationMapper((proxy, method, args) -> {
|
||||
if ("countBinding".equals(method.getName()))
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
if ("updateBindingSort".equals(method.getName()))
|
||||
{
|
||||
updates.incrementAndGet();
|
||||
return 1;
|
||||
}
|
||||
if ("insertBinding".equals(method.getName()))
|
||||
{
|
||||
fail("重复绑定不应再次插入关联记录");
|
||||
}
|
||||
return defaultValue(method.getReturnType());
|
||||
}));
|
||||
|
||||
assertEquals(1, service.bindResource("C001", "R001", 5));
|
||||
assertEquals(1, updates.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void bindRejectsMissingResource() throws Exception
|
||||
{
|
||||
inject("trainCourseMapper", courseMapper(course));
|
||||
inject("trainResourceMapper", resourceMapper(null));
|
||||
inject("trainCourseResourceMapper", associationMapper((proxy, method, args) -> defaultValue(method.getReturnType())));
|
||||
|
||||
try
|
||||
{
|
||||
service.bindResource("C001", "R404", 0);
|
||||
fail("不存在的教学资源必须被拒绝");
|
||||
}
|
||||
catch (ServiceException expected)
|
||||
{
|
||||
assertEquals("教学资源不存在", expected.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private TrainCourseMapper courseMapper(TrainCourse value)
|
||||
{
|
||||
return (TrainCourseMapper) Proxy.newProxyInstance(
|
||||
TrainCourseMapper.class.getClassLoader(),
|
||||
new Class<?>[] { TrainCourseMapper.class },
|
||||
(proxy, method, args) -> "selectTrainCourseByCId".equals(method.getName())
|
||||
? value : defaultValue(method.getReturnType()));
|
||||
}
|
||||
|
||||
private TrainResourceMapper resourceMapper(TrainResource value)
|
||||
{
|
||||
return (TrainResourceMapper) Proxy.newProxyInstance(
|
||||
TrainResourceMapper.class.getClassLoader(),
|
||||
new Class<?>[] { TrainResourceMapper.class },
|
||||
(proxy, method, args) -> "selectTrainResourceByResourceId".equals(method.getName())
|
||||
? value : defaultValue(method.getReturnType()));
|
||||
}
|
||||
|
||||
private TrainCourseResourceMapper associationMapper(java.lang.reflect.InvocationHandler handler)
|
||||
{
|
||||
return (TrainCourseResourceMapper) Proxy.newProxyInstance(
|
||||
TrainCourseResourceMapper.class.getClassLoader(),
|
||||
new Class<?>[] { TrainCourseResourceMapper.class }, handler);
|
||||
}
|
||||
|
||||
private void inject(String fieldName, Object value) throws Exception
|
||||
{
|
||||
Field field = TrainCourseResourceServiceImpl.class.getDeclaredField(fieldName);
|
||||
field.setAccessible(true);
|
||||
field.set(service, value);
|
||||
}
|
||||
|
||||
private Object defaultValue(Class<?> type)
|
||||
{
|
||||
if (type == int.class)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
if (type == boolean.class)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@
|
||||
-- 适用数据库: KingbaseES V009R003C018 (schema: public)
|
||||
-- 执行目标:
|
||||
-- 1. 补齐学员、教师、教室、实训场地、课程主数据新增扩展字段 (11个物理列)
|
||||
-- 2. 创建统一教学资源表 (train_resource) 及其索引与字典
|
||||
-- 2. 创建统一教学资源表及课程-资源规范关联表
|
||||
-- 3. 补齐教学资源管理菜单 (sys_menu) 与培训管理员/超级管理员角色授权 (sys_role_menu)
|
||||
-- 事务边界: 全程在显式事务 BEGIN ... COMMIT 中执行
|
||||
-- 幂等保证: 全部 DDL / DML 均具备 IF NOT EXISTS 或 NOT EXISTS 守卫,可安全重复执行
|
||||
@@ -45,10 +45,6 @@ ALTER TABLE public.train_training_site ADD COLUMN IF NOT EXISTS seat_arrange VAR
|
||||
COMMENT ON COLUMN public.train_training_site.major IS '适用专业/实训工种';
|
||||
COMMENT ON COLUMN public.train_training_site.seat_arrange IS '工位布局形式';
|
||||
|
||||
-- 1.5 课程表 train_course 增加 资源ID关联串 (逗号分隔资源ID)
|
||||
ALTER TABLE public.train_course ADD COLUMN IF NOT EXISTS resource_id VARCHAR(500);
|
||||
COMMENT ON COLUMN public.train_course.resource_id IS '关联教学资源ID集合 (逗号分隔)';
|
||||
|
||||
-- =============================================================================
|
||||
-- 步骤 2: 创建统一教学资源表 (train_resource)
|
||||
-- =============================================================================
|
||||
@@ -81,6 +77,81 @@ COMMENT ON COLUMN public.train_resource.del_flag IS '删除标志 (0:存在, 2:
|
||||
CREATE INDEX IF NOT EXISTS idx_train_resource_type ON public.train_resource (resource_type);
|
||||
CREATE INDEX IF NOT EXISTS idx_train_resource_status ON public.train_resource (status);
|
||||
|
||||
-- 2.1 课程与教学资源采用规范 N:M 关联,不在课程表保存逗号分隔 ID
|
||||
CREATE TABLE IF NOT EXISTS public.train_course_resource (
|
||||
id BIGINT GENERATED BY DEFAULT AS IDENTITY,
|
||||
c_id VARCHAR(32) NOT NULL,
|
||||
resource_id VARCHAR(32) NOT NULL,
|
||||
sort_no INTEGER DEFAULT 0,
|
||||
create_by VARCHAR(64),
|
||||
create_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
CONSTRAINT pk_train_course_resource PRIMARY KEY (id)
|
||||
);
|
||||
|
||||
COMMENT ON TABLE public.train_course_resource IS '课程与教学资源关联表 (N:M)';
|
||||
COMMENT ON COLUMN public.train_course_resource.c_id IS '课程ID';
|
||||
COMMENT ON COLUMN public.train_course_resource.resource_id IS '教学资源ID';
|
||||
COMMENT ON COLUMN public.train_course_resource.sort_no IS '课程内资源排序号';
|
||||
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS uq_course_resource
|
||||
ON public.train_course_resource (c_id, resource_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_course_resource_course
|
||||
ON public.train_course_resource (c_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_course_resource_resource
|
||||
ON public.train_course_resource (resource_id);
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'fk_cr_course') THEN
|
||||
ALTER TABLE public.train_course_resource
|
||||
ADD CONSTRAINT fk_cr_course FOREIGN KEY (c_id)
|
||||
REFERENCES public.train_course (c_id);
|
||||
END IF;
|
||||
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'fk_cr_resource') THEN
|
||||
ALTER TABLE public.train_course_resource
|
||||
ADD CONSTRAINT fk_cr_resource FOREIGN KEY (resource_id)
|
||||
REFERENCES public.train_resource (resource_id);
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
-- 兼容曾执行过旧版 011 的环境:先迁移逗号串,再删除非规范字段。
|
||||
DO $$
|
||||
BEGIN
|
||||
IF EXISTS (
|
||||
SELECT 1 FROM information_schema.columns
|
||||
WHERE table_schema = 'public'
|
||||
AND table_name = 'train_course'
|
||||
AND column_name = 'resource_id'
|
||||
) THEN
|
||||
EXECUTE $migration$
|
||||
INSERT INTO public.train_course_resource
|
||||
(c_id, resource_id, sort_no, create_by, create_time)
|
||||
SELECT legacy.c_id,
|
||||
legacy.resource_id,
|
||||
legacy.sort_no,
|
||||
'migration',
|
||||
CURRENT_TIMESTAMP
|
||||
FROM (
|
||||
SELECT c.c_id,
|
||||
trim(parts.resource_id) AS resource_id,
|
||||
CAST(parts.ordinality - 1 AS INTEGER) AS sort_no
|
||||
FROM public.train_course c
|
||||
CROSS JOIN LATERAL regexp_split_to_table(c.resource_id, ',')
|
||||
WITH ORDINALITY AS parts(resource_id, ordinality)
|
||||
WHERE c.resource_id IS NOT NULL
|
||||
AND trim(c.resource_id) <> ''
|
||||
) legacy
|
||||
JOIN public.train_resource r
|
||||
ON r.resource_id = legacy.resource_id
|
||||
AND r.del_flag = '0'
|
||||
WHERE legacy.resource_id <> ''
|
||||
ON CONFLICT (c_id, resource_id) DO NOTHING
|
||||
$migration$;
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
ALTER TABLE public.train_course DROP COLUMN IF EXISTS resource_id;
|
||||
|
||||
-- =============================================================================
|
||||
-- 步骤 3: 补齐教学资源类型字典 (train_resource_type)
|
||||
-- =============================================================================
|
||||
|
||||
+2
-3
@@ -14,7 +14,8 @@ DELETE FROM public.sys_menu WHERE menu_id IN (2270, 2271, 2272);
|
||||
DELETE FROM public.sys_dict_data WHERE dict_type = 'train_resource_type';
|
||||
DELETE FROM public.sys_dict_type WHERE dict_type = 'train_resource_type';
|
||||
|
||||
-- 3. 删除教学资源表及索引
|
||||
-- 3. 删除课程资源关联表、教学资源表及索引
|
||||
DROP TABLE IF EXISTS public.train_course_resource CASCADE;
|
||||
DROP TABLE IF EXISTS public.train_resource CASCADE;
|
||||
|
||||
-- 4. 删除主数据扩展列
|
||||
@@ -33,6 +34,4 @@ ALTER TABLE public.train_classroom_info DROP COLUMN IF EXISTS seat_arrange;
|
||||
ALTER TABLE public.train_training_site DROP COLUMN IF EXISTS major;
|
||||
ALTER TABLE public.train_training_site DROP COLUMN IF EXISTS seat_arrange;
|
||||
|
||||
ALTER TABLE public.train_course DROP COLUMN IF EXISTS resource_id;
|
||||
|
||||
COMMIT;
|
||||
Reference in new issue
Block a user