feat(优先级):维度权重可配置并即时重算
- 新表 user_priority_weights(每用户一行,默认 0.35/0.25/0.20/0.10/0.10) - PriorityWeightsService:读取/保存权重,权重和必须为 1,保存后重算全部任务 - TaskController:GET/PUT /tasks/priority-weights - CalculatedPriorityTool 支持自定义权重 - 修复 updateTask 不重算优先级的问题
This commit is contained in:
@@ -11,8 +11,10 @@ import com.guo.learningprogresstracker.entity.CommonResult;
|
|||||||
import com.guo.learningprogresstracker.dto.request.TaskRequest;
|
import com.guo.learningprogresstracker.dto.request.TaskRequest;
|
||||||
import com.guo.learningprogresstracker.dto.request.UpdateTaskApplicationRequest;
|
import com.guo.learningprogresstracker.dto.request.UpdateTaskApplicationRequest;
|
||||||
import com.guo.learningprogresstracker.entity.TaskApplicationEntity;
|
import com.guo.learningprogresstracker.entity.TaskApplicationEntity;
|
||||||
|
import com.guo.learningprogresstracker.entity.UserPriorityWeightsEntity;
|
||||||
import com.guo.learningprogresstracker.exception.ErrorParameterException;
|
import com.guo.learningprogresstracker.exception.ErrorParameterException;
|
||||||
import com.guo.learningprogresstracker.exception.NotFindEntitiesException;
|
import com.guo.learningprogresstracker.exception.NotFindEntitiesException;
|
||||||
|
import com.guo.learningprogresstracker.service.PriorityWeightsService;
|
||||||
import com.guo.learningprogresstracker.service.TasksService;
|
import com.guo.learningprogresstracker.service.TasksService;
|
||||||
import com.guo.learningprogresstracker.service.impl.StudySessionsServiceImpl;
|
import com.guo.learningprogresstracker.service.impl.StudySessionsServiceImpl;
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
@@ -48,6 +50,21 @@ public class TaskController {
|
|||||||
|
|
||||||
private final StudySessionsServiceImpl studySessionsServiceImpl;
|
private final StudySessionsServiceImpl studySessionsServiceImpl;
|
||||||
|
|
||||||
|
private final PriorityWeightsService priorityWeightsService;
|
||||||
|
|
||||||
|
@GetMapping("/priority-weights")
|
||||||
|
@Operation(summary = "获取优先级维度权重配置")
|
||||||
|
public CommonResult<UserPriorityWeightsEntity> getPriorityWeights() {
|
||||||
|
return CommonResult.success(priorityWeightsService.getWeights());
|
||||||
|
}
|
||||||
|
|
||||||
|
@PutMapping("/priority-weights")
|
||||||
|
@Operation(summary = "保存优先级维度权重配置并重算全部任务优先级")
|
||||||
|
public CommonResult<UserPriorityWeightsEntity> savePriorityWeights(
|
||||||
|
@RequestBody UserPriorityWeightsEntity weights) throws ErrorParameterException {
|
||||||
|
return CommonResult.success(priorityWeightsService.saveWeights(weights));
|
||||||
|
}
|
||||||
|
|
||||||
@PostMapping
|
@PostMapping
|
||||||
@Operation(summary = "添加新任务")
|
@Operation(summary = "添加新任务")
|
||||||
public CommonResult<String> addTask(@RequestBody @Validated({Ops.CreateG.class}) TaskRequest taskRequest) throws ErrorParameterException {
|
public CommonResult<String> addTask(@RequestBody @Validated({Ops.CreateG.class}) TaskRequest taskRequest) throws ErrorParameterException {
|
||||||
|
|||||||
@@ -0,0 +1,49 @@
|
|||||||
|
package com.guo.learningprogresstracker.entity;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.IdType;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableField;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户自定义的任务优先级维度权重(每用户一行,依赖多租户拦截器隔离)
|
||||||
|
*/
|
||||||
|
@TableName(value = "user_priority_weights")
|
||||||
|
@Data
|
||||||
|
public class UserPriorityWeightsEntity extends BaseEntity implements Serializable {
|
||||||
|
|
||||||
|
@TableId(value = "id", type = IdType.AUTO)
|
||||||
|
private Integer id;
|
||||||
|
|
||||||
|
@TableField(value = "urgency_weight")
|
||||||
|
private Double urgencyWeight;
|
||||||
|
|
||||||
|
@TableField(value = "importance_weight")
|
||||||
|
private Double importanceWeight;
|
||||||
|
|
||||||
|
@TableField(value = "content_difficulty_weight")
|
||||||
|
private Double contentDifficultyWeight;
|
||||||
|
|
||||||
|
@TableField(value = "future_value_weight")
|
||||||
|
private Double futureValueWeight;
|
||||||
|
|
||||||
|
@TableField(value = "subjective_priority_weight")
|
||||||
|
private Double subjectivePriorityWeight;
|
||||||
|
|
||||||
|
@TableField(exist = false)
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
/** 系统默认权重 */
|
||||||
|
public static UserPriorityWeightsEntity defaults() {
|
||||||
|
UserPriorityWeightsEntity entity = new UserPriorityWeightsEntity();
|
||||||
|
entity.setUrgencyWeight(0.35);
|
||||||
|
entity.setImportanceWeight(0.25);
|
||||||
|
entity.setContentDifficultyWeight(0.20);
|
||||||
|
entity.setFutureValueWeight(0.10);
|
||||||
|
entity.setSubjectivePriorityWeight(0.10);
|
||||||
|
return entity;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package com.guo.learningprogresstracker.mapper;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
|
import com.guo.learningprogresstracker.entity.UserPriorityWeightsEntity;
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
|
||||||
|
@Mapper
|
||||||
|
public interface UserPriorityWeightsMapper extends BaseMapper<UserPriorityWeightsEntity> {
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package com.guo.learningprogresstracker.service;
|
||||||
|
|
||||||
|
import com.guo.learningprogresstracker.entity.UserPriorityWeightsEntity;
|
||||||
|
import com.guo.learningprogresstracker.exception.ErrorParameterException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 优先级权重配置服务
|
||||||
|
*/
|
||||||
|
public interface PriorityWeightsService {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取当前用户的权重配置,不存在时返回系统默认值
|
||||||
|
*/
|
||||||
|
UserPriorityWeightsEntity getWeights();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 保存权重配置并重算当前用户全部任务的优先级。
|
||||||
|
* 五项权重之和必须为 1(容差 0.001)。
|
||||||
|
*/
|
||||||
|
UserPriorityWeightsEntity saveWeights(UserPriorityWeightsEntity weights) throws ErrorParameterException;
|
||||||
|
}
|
||||||
+99
@@ -0,0 +1,99 @@
|
|||||||
|
package com.guo.learningprogresstracker.service.impl;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||||
|
import com.guo.learningprogresstracker.dto.PriorityDto;
|
||||||
|
import com.guo.learningprogresstracker.entity.TaskEntity;
|
||||||
|
import com.guo.learningprogresstracker.entity.UserPriorityWeightsEntity;
|
||||||
|
import com.guo.learningprogresstracker.exception.ErrorParameterException;
|
||||||
|
import com.guo.learningprogresstracker.mapper.TasksMapper;
|
||||||
|
import com.guo.learningprogresstracker.mapper.UserPriorityWeightsMapper;
|
||||||
|
import com.guo.learningprogresstracker.service.PriorityWeightsService;
|
||||||
|
import com.guo.learningprogresstracker.utils.CalculatedPriorityTool;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 优先级权重配置服务实现
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class PriorityWeightsServiceImpl implements PriorityWeightsService {
|
||||||
|
|
||||||
|
private static final double SUM_TOLERANCE = 0.001;
|
||||||
|
|
||||||
|
private final UserPriorityWeightsMapper weightsMapper;
|
||||||
|
private final TasksMapper tasksMapper;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public UserPriorityWeightsEntity getWeights() {
|
||||||
|
UserPriorityWeightsEntity existing = weightsMapper.selectOne(
|
||||||
|
Wrappers.<UserPriorityWeightsEntity>lambdaQuery().last("LIMIT 1"));
|
||||||
|
return existing != null ? existing : UserPriorityWeightsEntity.defaults();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional
|
||||||
|
public UserPriorityWeightsEntity saveWeights(UserPriorityWeightsEntity weights) throws ErrorParameterException {
|
||||||
|
validate(weights);
|
||||||
|
|
||||||
|
UserPriorityWeightsEntity existing = weightsMapper.selectOne(
|
||||||
|
Wrappers.<UserPriorityWeightsEntity>lambdaQuery().last("LIMIT 1"));
|
||||||
|
if (existing == null) {
|
||||||
|
weightsMapper.insert(weights);
|
||||||
|
} else {
|
||||||
|
weights.setId(existing.getId());
|
||||||
|
weightsMapper.updateById(weights);
|
||||||
|
}
|
||||||
|
|
||||||
|
recalculateAllTasks(weights);
|
||||||
|
return weights;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 用新权重重算当前用户的全部任务优先级 */
|
||||||
|
private void recalculateAllTasks(UserPriorityWeightsEntity weights) {
|
||||||
|
List<TaskEntity> tasks = tasksMapper.selectList(Wrappers.lambdaQuery(TaskEntity.class));
|
||||||
|
for (TaskEntity task : tasks) {
|
||||||
|
PriorityDto dto = new PriorityDto();
|
||||||
|
dto.setUrgency(orZero(task.getUrgency()));
|
||||||
|
dto.setImportance(orZero(task.getImportance()));
|
||||||
|
dto.setContentDifficulty(orZero(task.getContentDifficulty()));
|
||||||
|
dto.setFutureValue(orZero(task.getFutureValue()));
|
||||||
|
dto.setSubjectivePriority(orZero(task.getSubjectivePriority()));
|
||||||
|
task.setCalculatedPriority(CalculatedPriorityTool.calculatedPriority(dto, weights));
|
||||||
|
tasksMapper.updateById(task);
|
||||||
|
}
|
||||||
|
log.info("权重更新,已重算 {} 个任务的优先级", tasks.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void validate(UserPriorityWeightsEntity weights) throws ErrorParameterException {
|
||||||
|
double[] values = {
|
||||||
|
orZero(weights.getUrgencyWeight()),
|
||||||
|
orZero(weights.getImportanceWeight()),
|
||||||
|
orZero(weights.getContentDifficultyWeight()),
|
||||||
|
orZero(weights.getFutureValueWeight()),
|
||||||
|
orZero(weights.getSubjectivePriorityWeight())};
|
||||||
|
double sum = 0;
|
||||||
|
for (double v : values) {
|
||||||
|
if (v < 0 || v > 1) {
|
||||||
|
throw new ErrorParameterException("权重必须在 0 到 1 之间");
|
||||||
|
}
|
||||||
|
sum += v;
|
||||||
|
}
|
||||||
|
if (Math.abs(sum - 1.0) > SUM_TOLERANCE) {
|
||||||
|
throw new ErrorParameterException("五项权重之和必须为 1,当前为 " + sum);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int orZero(Integer v) {
|
||||||
|
return v == null ? 0 : v;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static double orZero(Double v) {
|
||||||
|
return v == null ? 0 : v;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -17,6 +17,7 @@ import com.guo.learningprogresstracker.exception.NotFindEntitiesException;
|
|||||||
import com.guo.learningprogresstracker.mapStruct.RequestConvert;
|
import com.guo.learningprogresstracker.mapStruct.RequestConvert;
|
||||||
import com.guo.learningprogresstracker.mapStruct.TaskConvert;
|
import com.guo.learningprogresstracker.mapStruct.TaskConvert;
|
||||||
import com.guo.learningprogresstracker.mapper.TaskApplicationMapper;
|
import com.guo.learningprogresstracker.mapper.TaskApplicationMapper;
|
||||||
|
import com.guo.learningprogresstracker.service.PriorityWeightsService;
|
||||||
import com.guo.learningprogresstracker.service.TasksService;
|
import com.guo.learningprogresstracker.service.TasksService;
|
||||||
import com.guo.learningprogresstracker.mapper.TasksMapper;
|
import com.guo.learningprogresstracker.mapper.TasksMapper;
|
||||||
import com.guo.learningprogresstracker.utils.CalculatedPriorityTool;
|
import com.guo.learningprogresstracker.utils.CalculatedPriorityTool;
|
||||||
@@ -42,6 +43,8 @@ public class TasksServiceImpl extends ServiceImpl<TasksMapper, TaskEntity>
|
|||||||
|
|
||||||
private final TaskApplicationMapper taskApplicationMapper;
|
private final TaskApplicationMapper taskApplicationMapper;
|
||||||
|
|
||||||
|
private final PriorityWeightsService priorityWeightsService;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Page<TaskInfo> taskList(Integer pageNum, Integer pageSize) {
|
public Page<TaskInfo> taskList(Integer pageNum, Integer pageSize) {
|
||||||
Page<TaskEntity> page = tasksMapper.selectPage(new Page<TaskEntity>(pageNum, pageSize),
|
Page<TaskEntity> page = tasksMapper.selectPage(new Page<TaskEntity>(pageNum, pageSize),
|
||||||
@@ -62,7 +65,7 @@ public class TasksServiceImpl extends ServiceImpl<TasksMapper, TaskEntity>
|
|||||||
|
|
||||||
task.setTaskNum(GenerateNumTool.generateNum("TASK"));
|
task.setTaskNum(GenerateNumTool.generateNum("TASK"));
|
||||||
PriorityDto priorityDto = RequestConvert.MAPPER.taskRequestToPriorityDto(taskRequest);
|
PriorityDto priorityDto = RequestConvert.MAPPER.taskRequestToPriorityDto(taskRequest);
|
||||||
task.setCalculatedPriority(CalculatedPriorityTool.calculatedPriority(priorityDto));
|
task.setCalculatedPriority(CalculatedPriorityTool.calculatedPriority(priorityDto, priorityWeightsService.getWeights()));
|
||||||
this.save(task);
|
this.save(task);
|
||||||
return task.getTaskNum();
|
return task.getTaskNum();
|
||||||
}
|
}
|
||||||
@@ -87,6 +90,9 @@ public class TasksServiceImpl extends ServiceImpl<TasksMapper, TaskEntity>
|
|||||||
throw new IllegalArgumentException("任务ID格式错误: " + taskId);
|
throw new IllegalArgumentException("任务ID格式错误: " + taskId);
|
||||||
}
|
}
|
||||||
taskEntity.setId(id);
|
taskEntity.setId(id);
|
||||||
|
// 维度数据可能变化,更新时重算优先级
|
||||||
|
PriorityDto priorityDto = RequestConvert.MAPPER.taskRequestToPriorityDto(updatedTask);
|
||||||
|
taskEntity.setCalculatedPriority(CalculatedPriorityTool.calculatedPriority(priorityDto, priorityWeightsService.getWeights()));
|
||||||
this.updateById(taskEntity);
|
this.updateById(taskEntity);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package com.guo.learningprogresstracker.utils;
|
package com.guo.learningprogresstracker.utils;
|
||||||
|
|
||||||
import com.guo.learningprogresstracker.dto.PriorityDto;
|
import com.guo.learningprogresstracker.dto.PriorityDto;
|
||||||
|
import com.guo.learningprogresstracker.entity.UserPriorityWeightsEntity;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 加权优先级计算工具
|
* 加权优先级计算工具
|
||||||
@@ -8,31 +9,21 @@ import com.guo.learningprogresstracker.dto.PriorityDto;
|
|||||||
public class CalculatedPriorityTool {
|
public class CalculatedPriorityTool {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 通过用户选择的5个选项,结合定义的权重计算任务的加权优先级。
|
* 使用系统默认权重计算加权优先级。
|
||||||
* 急迫性 (Urgency)
|
* 急迫性 0.35 / 重要性 0.25 / 内容难度 0.20 / 未来价值 0.10 / 主观优先级 0.10
|
||||||
* 急迫性代表任务的紧急程度。权重:0.35
|
|
||||||
* <p>
|
|
||||||
* 重要性 (Importance)
|
|
||||||
* 重要性指示任务的重要程度。权重:0.25
|
|
||||||
* <p>
|
|
||||||
* 内容难度 (Content Difficulty)
|
|
||||||
* 内容难度衡量任务内容的复杂性。权重:0.20
|
|
||||||
* <p>
|
|
||||||
* 未来价值 (Future Value)
|
|
||||||
* 未来价值估计完成任务的长期价值。权重:0.10
|
|
||||||
* <p>
|
|
||||||
* 主观优先级 (Subjective Priority)
|
|
||||||
* 主观优先级是用户对任务优先级的个人评估。权重:0.10
|
|
||||||
*
|
|
||||||
* @param priorityDto
|
|
||||||
* @return
|
|
||||||
*/
|
*/
|
||||||
public static Double calculatedPriority(PriorityDto priorityDto) {
|
public static Double calculatedPriority(PriorityDto priorityDto) {
|
||||||
// 暂时写死权重,也许可以考虑让用户自行配置权重
|
return calculatedPriority(priorityDto, UserPriorityWeightsEntity.defaults());
|
||||||
return (priorityDto.getUrgency() * 0.35) +
|
}
|
||||||
(priorityDto.getImportance() * 0.25) +
|
|
||||||
(priorityDto.getContentDifficulty() * 0.20) +
|
/**
|
||||||
(priorityDto.getFutureValue() * 0.10) +
|
* 使用用户自定义权重计算加权优先级。
|
||||||
(priorityDto.getSubjectivePriority() * 0.10);
|
*/
|
||||||
|
public static Double calculatedPriority(PriorityDto priorityDto, UserPriorityWeightsEntity weights) {
|
||||||
|
return (priorityDto.getUrgency() * weights.getUrgencyWeight()) +
|
||||||
|
(priorityDto.getImportance() * weights.getImportanceWeight()) +
|
||||||
|
(priorityDto.getContentDifficulty() * weights.getContentDifficultyWeight()) +
|
||||||
|
(priorityDto.getFutureValue() * weights.getFutureValueWeight()) +
|
||||||
|
(priorityDto.getSubjectivePriority() * weights.getSubjectivePriorityWeight());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
create table user_priority_weights
|
||||||
|
(
|
||||||
|
id int auto_increment comment 'id无业务含义'
|
||||||
|
primary key,
|
||||||
|
urgency_weight double not null default 0.35 comment '紧急性权重',
|
||||||
|
importance_weight double not null default 0.25 comment '重要性权重',
|
||||||
|
content_difficulty_weight double not null default 0.20 comment '内容难度权重',
|
||||||
|
future_value_weight double not null default 0.10 comment '未来价值权重',
|
||||||
|
subjective_priority_weight double not null default 0.10 comment '主观优先级权重',
|
||||||
|
created_time datetime not null comment '创建时间',
|
||||||
|
created_by varchar(255) null,
|
||||||
|
last_modified_time datetime null,
|
||||||
|
last_modified_by varchar(255) null,
|
||||||
|
device_info varchar(50) null comment '操作者设备类型',
|
||||||
|
deleted int default 0 not null comment '逻辑删除符'
|
||||||
|
)
|
||||||
|
comment '用户自定义的任务优先级维度权重(每用户一行)';
|
||||||
Reference in New Issue
Block a user