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:
2026-07-03 23:41:50 +08:00
parent b67cf238c2
commit 7461b6b5ad
8 changed files with 234 additions and 25 deletions
@@ -11,8 +11,10 @@ import com.guo.learningprogresstracker.entity.CommonResult;
import com.guo.learningprogresstracker.dto.request.TaskRequest;
import com.guo.learningprogresstracker.dto.request.UpdateTaskApplicationRequest;
import com.guo.learningprogresstracker.entity.TaskApplicationEntity;
import com.guo.learningprogresstracker.entity.UserPriorityWeightsEntity;
import com.guo.learningprogresstracker.exception.ErrorParameterException;
import com.guo.learningprogresstracker.exception.NotFindEntitiesException;
import com.guo.learningprogresstracker.service.PriorityWeightsService;
import com.guo.learningprogresstracker.service.TasksService;
import com.guo.learningprogresstracker.service.impl.StudySessionsServiceImpl;
import io.swagger.v3.oas.annotations.Operation;
@@ -48,6 +50,21 @@ public class TaskController {
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
@Operation(summary = "添加新任务")
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;
}
@@ -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.TaskConvert;
import com.guo.learningprogresstracker.mapper.TaskApplicationMapper;
import com.guo.learningprogresstracker.service.PriorityWeightsService;
import com.guo.learningprogresstracker.service.TasksService;
import com.guo.learningprogresstracker.mapper.TasksMapper;
import com.guo.learningprogresstracker.utils.CalculatedPriorityTool;
@@ -42,6 +43,8 @@ public class TasksServiceImpl extends ServiceImpl<TasksMapper, TaskEntity>
private final TaskApplicationMapper taskApplicationMapper;
private final PriorityWeightsService priorityWeightsService;
@Override
public Page<TaskInfo> taskList(Integer pageNum, Integer 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"));
PriorityDto priorityDto = RequestConvert.MAPPER.taskRequestToPriorityDto(taskRequest);
task.setCalculatedPriority(CalculatedPriorityTool.calculatedPriority(priorityDto));
task.setCalculatedPriority(CalculatedPriorityTool.calculatedPriority(priorityDto, priorityWeightsService.getWeights()));
this.save(task);
return task.getTaskNum();
}
@@ -87,6 +90,9 @@ public class TasksServiceImpl extends ServiceImpl<TasksMapper, TaskEntity>
throw new IllegalArgumentException("任务ID格式错误: " + taskId);
}
taskEntity.setId(id);
// 维度数据可能变化,更新时重算优先级
PriorityDto priorityDto = RequestConvert.MAPPER.taskRequestToPriorityDto(updatedTask);
taskEntity.setCalculatedPriority(CalculatedPriorityTool.calculatedPriority(priorityDto, priorityWeightsService.getWeights()));
this.updateById(taskEntity);
}
@@ -1,6 +1,7 @@
package com.guo.learningprogresstracker.utils;
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 {
/**
* 通过用户选择的5个选项,结合定义的权重计算任务的加权优先级。
* 急迫性 (Urgency)
* 急迫性代表任务的紧急程度。权重:0.35
* <p>
* 重要性 (Importance)
* 重要性指示任务的重要程度。权重:0.25
* <p>
* 内容难度 (Content Difficulty)
* 内容难度衡量任务内容的复杂性。权重:0.20
* <p>
* 未来价值 (Future Value)
* 未来价值估计完成任务的长期价值。权重:0.10
* <p>
* 主观优先级 (Subjective Priority)
* 主观优先级是用户对任务优先级的个人评估。权重:0.10
*
* @param priorityDto
* @return
* 使用系统默认权重计算加权优先级。
* 急迫性 0.35 / 重要性 0.25 / 内容难度 0.20 / 未来价值 0.10 / 主观优先级 0.10
*/
public static Double calculatedPriority(PriorityDto priorityDto) {
// 暂时写死权重,也许可以考虑让用户自行配置权重
return (priorityDto.getUrgency() * 0.35) +
(priorityDto.getImportance() * 0.25) +
(priorityDto.getContentDifficulty() * 0.20) +
(priorityDto.getFutureValue() * 0.10) +
(priorityDto.getSubjectivePriority() * 0.10);
return calculatedPriority(priorityDto, UserPriorityWeightsEntity.defaults());
}
/**
* 使用用户自定义权重计算加权优先级。
*/
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 '用户自定义的任务优先级维度权重(每用户一行)';