diff --git a/src/main/java/cn/teammodel/ai/listener/SparkGptStreamListener.java b/src/main/java/cn/teammodel/ai/listener/SparkGptStreamListener.java index 771f058..a557e12 100644 --- a/src/main/java/cn/teammodel/ai/listener/SparkGptStreamListener.java +++ b/src/main/java/cn/teammodel/ai/listener/SparkGptStreamListener.java @@ -1,6 +1,5 @@ package cn.teammodel.ai.listener; -import cn.hutool.core.util.StrUtil; import cn.hutool.json.JSONUtil; import cn.teammodel.ai.SseHelper; import cn.teammodel.ai.domain.SparkChatResponse; @@ -65,7 +64,7 @@ public class SparkGptStreamListener extends WebSocketListener { String msgSegment = sparkChatResponse.getPayload().getChoices().getText().get(0).getContent(); // 处理消息格式(空格和换行符) // msgSegment = StrUtil.replace(msgSegment, " ", " ").replaceAll("\n", "\n"); - msgSegment = StrUtil.replace(msgSegment, " ", " ").replaceAll("\n", "
"); +// msgSegment = StrUtil.replace(msgSegment, " ", " ").replaceAll("\n", "
"); answer += msgSegment; SseHelper.send(sseEmitter, msgSegment); diff --git a/src/main/java/cn/teammodel/common/PK.java b/src/main/java/cn/teammodel/common/PK.java index 599750c..877d43b 100644 --- a/src/main/java/cn/teammodel/common/PK.java +++ b/src/main/java/cn/teammodel/common/PK.java @@ -24,6 +24,7 @@ public interface PK { String STUDENT = "Base-%s"; String CLASS = "Class-%s"; String CHAT_SESSION = "ChatSession"; + String WEEK_DUTY = "Duty"; /** * 构建分区键 diff --git a/src/main/java/cn/teammodel/config/exception/ServiceException.java b/src/main/java/cn/teammodel/config/exception/ServiceException.java index d41050d..4368e9a 100644 --- a/src/main/java/cn/teammodel/config/exception/ServiceException.java +++ b/src/main/java/cn/teammodel/config/exception/ServiceException.java @@ -33,6 +33,7 @@ public final class ServiceException extends RuntimeException public ServiceException(String message) { + this.code = ErrorCode.SYSTEM_ERROR.getCode(); this.message = message; } diff --git a/src/main/java/cn/teammodel/controller/frontend/AiController.java b/src/main/java/cn/teammodel/controller/frontend/AiController.java index e246560..427a5ba 100644 --- a/src/main/java/cn/teammodel/controller/frontend/AiController.java +++ b/src/main/java/cn/teammodel/controller/frontend/AiController.java @@ -13,7 +13,9 @@ import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; import javax.annotation.Resource; import javax.validation.Valid; +import java.io.IOException; import java.util.List; +import java.util.concurrent.CompletableFuture; @RestController @RequestMapping("/ai") @@ -29,6 +31,40 @@ public class AiController { return chatMessageService.chatCompletion(chatCompletionReqDto); } + @PostMapping("chat/test/completion") + @ApiOperation("与 spark 的流式对话") + public SseEmitter testCompletion(@RequestBody @Valid ChatCompletionReqDto chatCompletionReqDto) throws IOException, InterruptedException { + SseEmitter sseEmitter = new SseEmitter(); + CompletableFuture.runAsync(() -> { + try { + sseEmitter.send("曾经以为我们"); + Thread.sleep(1000); + sseEmitter.send("开始了就会走到最后,可是当分手之后才明白我想的"); + Thread.sleep(1000); + sseEmitter.send("你不一定能做到,当我开始去遗忘我们"); + Thread.sleep(1500); + sseEmitter.send("的经历时,却不知道遗忘已变成另一种开始,"); + Thread.sleep(800); + sseEmitter.send("回忆是淡了,可是痛却还在,还是最真实。放手的时候微笑着说无所谓"); + Thread.sleep(800); + sseEmitter.send(",离开了你我还可以过的很好"); + Thread.sleep(1000); + sseEmitter.send(",而你却不知道微笑只是用来掩盖疼痛的伤疤。"); + Thread.sleep(1200); + sseEmitter.send("离开你之后的自己陷入一种无助的状态,空洞的"); + Thread.sleep(1300); + sseEmitter.send("双眼回忆不起曾经的你,那时候以为与世隔绝或许才是维护自己的最好方式。"); + Thread.sleep(1100); + sseEmitter.send("[DONE]"); + sseEmitter.complete(); + + } catch (IOException | InterruptedException e) { + throw new RuntimeException(e); + } + }); + return sseEmitter; + } + @GetMapping("session/my") @ApiOperation("查询我的聊天会话") public R> listMySession() { diff --git a/src/main/java/cn/teammodel/controller/frontend/AppraiseController.java b/src/main/java/cn/teammodel/controller/frontend/AppraiseController.java index 91ae70a..462f7a0 100644 --- a/src/main/java/cn/teammodel/controller/frontend/AppraiseController.java +++ b/src/main/java/cn/teammodel/controller/frontend/AppraiseController.java @@ -43,6 +43,7 @@ public class AppraiseController { @PostMapping("updateNode") @ApiOperation(value = "更新评价树的节点", notes = "传递更新后的节点,而不是局部更新的值") public R updateTree(@RequestBody @Valid UpdateNodeDto updateNodeDto) { + // fixme: 更新一二级节点应该同时更新三级的 path Appraise appraise = evaluationService.updateNode(updateNodeDto); return R.success(appraise); } diff --git a/src/main/java/cn/teammodel/controller/frontend/DutyController.java b/src/main/java/cn/teammodel/controller/frontend/DutyController.java new file mode 100644 index 0000000..716bcfe --- /dev/null +++ b/src/main/java/cn/teammodel/controller/frontend/DutyController.java @@ -0,0 +1,65 @@ +package cn.teammodel.controller.frontend; + +import cn.teammodel.common.R; +import cn.teammodel.model.dto.weekDuty.DeleteDutyNodeDto; +import cn.teammodel.model.dto.weekDuty.DutyVoteDto; +import cn.teammodel.model.dto.weekDuty.InsertDutyNodeDto; +import cn.teammodel.model.dto.weekDuty.UpdateDutyNodeDto; +import cn.teammodel.model.entity.weekDuty.WeekDuty; +import cn.teammodel.service.DutyService; +import io.swagger.annotations.ApiOperation; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import javax.validation.Valid; + +/** + * @author winter + * @create 2024-01-03 10:06 + */ +@RestController +@RequestMapping("/duty") +public class DutyController { + @Resource + private DutyService dutyService; + + @GetMapping("/getTree") + @ApiOperation("获取值周评价标准树(不存在则拷贝模板)") + public R getTree() { + WeekDuty weekDuty = dutyService.getTree(); + return R.success(weekDuty); + } + + @PostMapping("/insertNode") + @ApiOperation("插入值周评价标准树节点") + public R insertNode(@RequestBody @Valid InsertDutyNodeDto insertDutyNodeDto) { + WeekDuty weekDuty = dutyService.insertNode(insertDutyNodeDto); + return R.success(weekDuty); + } + + @PostMapping("/deleteNode") + @ApiOperation("删除值周评价标准树节点") + public R deleteNode(@RequestBody @Valid DeleteDutyNodeDto deleteDutyNodeDto) { + WeekDuty weekDuty = dutyService.deleteNode(deleteDutyNodeDto); + return R.success(weekDuty); + } + + @PostMapping("/updateNode") + @ApiOperation("更新值周评价标准树节点") + public R updateNode(@RequestBody @Valid UpdateDutyNodeDto updateDutyNodeDto) { + WeekDuty weekDuty = dutyService.updateNode(updateDutyNodeDto); + return R.success(weekDuty); + } + + @PostMapping("/vote") + @ApiOperation("值周评价投票") + public R vote(@RequestBody @Valid DutyVoteDto dutyVoteDto) { + dutyService.vote(dutyVoteDto); + return R.success("评价成功"); + } + + + + + +} diff --git a/src/main/java/cn/teammodel/model/dto/Appraise/InsertNodeDto.java b/src/main/java/cn/teammodel/model/dto/Appraise/InsertNodeDto.java index 912c6a4..258345b 100644 --- a/src/main/java/cn/teammodel/model/dto/Appraise/InsertNodeDto.java +++ b/src/main/java/cn/teammodel/model/dto/Appraise/InsertNodeDto.java @@ -24,5 +24,5 @@ public class InsertNodeDto { String logo; Integer order = 0; Integer score = 0; - boolean isPraise; + Boolean isPraise = true; } diff --git a/src/main/java/cn/teammodel/model/dto/weekDuty/DeleteDutyNodeDto.java b/src/main/java/cn/teammodel/model/dto/weekDuty/DeleteDutyNodeDto.java new file mode 100644 index 0000000..190f97f --- /dev/null +++ b/src/main/java/cn/teammodel/model/dto/weekDuty/DeleteDutyNodeDto.java @@ -0,0 +1,17 @@ +package cn.teammodel.model.dto.weekDuty; + +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import javax.validation.constraints.NotNull; + +/** + * @author winter + * @create 2024-01-03 10:56 + */ +@Data +public class DeleteDutyNodeDto { + @ApiModelProperty("要删除的节点 id") + @NotNull + private String nodeId; +} diff --git a/src/main/java/cn/teammodel/model/dto/weekDuty/DutyVoteDto.java b/src/main/java/cn/teammodel/model/dto/weekDuty/DutyVoteDto.java new file mode 100644 index 0000000..449f205 --- /dev/null +++ b/src/main/java/cn/teammodel/model/dto/weekDuty/DutyVoteDto.java @@ -0,0 +1,25 @@ +package cn.teammodel.model.dto.weekDuty; + +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import javax.validation.constraints.NotNull; + +/** + * @author winter + * @create 2024-01-03 15:05 + */ +@Data +public class DutyVoteDto { + @NotNull + @ApiModelProperty(value = "班级id", required = true) + private String classId; + @NotNull + @ApiModelProperty(value = "评价地点 id", required = true) + private String spotId; + @NotNull + @ApiModelProperty(value = "评价节点id", required = true) + private String dutyNodeId; + private String note; + private String[] attachments; +} diff --git a/src/main/java/cn/teammodel/model/dto/weekDuty/InsertDutyNodeDto.java b/src/main/java/cn/teammodel/model/dto/weekDuty/InsertDutyNodeDto.java new file mode 100644 index 0000000..19cfc1b --- /dev/null +++ b/src/main/java/cn/teammodel/model/dto/weekDuty/InsertDutyNodeDto.java @@ -0,0 +1,23 @@ +package cn.teammodel.model.dto.weekDuty; + +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import javax.validation.constraints.NotBlank; + +/** + * @author winter + * @create 2024-01-03 10:45 + */ + +@Data +public class InsertDutyNodeDto { + @ApiModelProperty(value = "父亲节点,不传则为根节点") + String pid; + @ApiModelProperty(value = "节点名称", required = true) + @NotBlank(message = "name 不能为空") + String name; + Integer order = 0; + Integer score = 0; +// Boolean positive = true; +} diff --git a/src/main/java/cn/teammodel/model/dto/weekDuty/UpdateDutyNodeDto.java b/src/main/java/cn/teammodel/model/dto/weekDuty/UpdateDutyNodeDto.java new file mode 100644 index 0000000..c1c4c6e --- /dev/null +++ b/src/main/java/cn/teammodel/model/dto/weekDuty/UpdateDutyNodeDto.java @@ -0,0 +1,23 @@ +package cn.teammodel.model.dto.weekDuty; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import javax.validation.constraints.NotNull; + +/** + * @author winter + * @create 2024-01-03 10:59 + */ +@Data +@ApiModel("覆盖原节点") +public class UpdateDutyNodeDto { + @ApiModelProperty(value = "待修改的节点 id") + @NotNull + private String id; + private String name; + private Integer order = 0; + private Integer score = 0; +// boolean positive; +} diff --git a/src/main/java/cn/teammodel/model/entity/TmdUserDetail.java b/src/main/java/cn/teammodel/model/entity/TmdUserDetail.java index 9dc5584..a9a4362 100644 --- a/src/main/java/cn/teammodel/model/entity/TmdUserDetail.java +++ b/src/main/java/cn/teammodel/model/entity/TmdUserDetail.java @@ -6,6 +6,7 @@ import org.springframework.security.core.userdetails.UserDetails; import java.util.Collection; + /** * @author winter * @create 2023-11-09 15:28 diff --git a/src/main/java/cn/teammodel/model/entity/weekDuty/WeekDuty.java b/src/main/java/cn/teammodel/model/entity/weekDuty/WeekDuty.java index 8570d3f..fdafe5e 100644 --- a/src/main/java/cn/teammodel/model/entity/weekDuty/WeekDuty.java +++ b/src/main/java/cn/teammodel/model/entity/weekDuty/WeekDuty.java @@ -6,21 +6,53 @@ import com.fasterxml.jackson.annotation.JsonInclude; import lombok.Data; import lombok.EqualsAndHashCode; +import java.util.List; + + /** - * 值周对象是班级,对应一个评价的指标 + * 每个学校值周评分标准 (一个学校只有一套) * @author winter - * @create 2023-12-29 15:53 + * @create 2024-01-02 17:05 */ @EqualsAndHashCode(callSuper = true) @Container(containerName = "School") @Data @JsonInclude(JsonInclude.Include.NON_NULL) public class WeekDuty extends BaseItem { - private String classId; - private String className; + private String schoolId; + private List nodes; /** - * 备注 + * 打卡地标 */ - private String note; - private Long createTime; + private List spots; + + @Data + @JsonInclude(JsonInclude.Include.NON_NULL) + public static class DutyTreeNode { + private String id; + private String pid; + private String name; + private Integer score; + private Integer order; + /** + * 加分还是减分 + */ + private boolean positive; + /** + * 只有三级有 + */ + private String[] path; + private List children; + private String creator; + private String creatorId; + private Long createTime; + } + @Data + @JsonInclude(JsonInclude.Include.NON_NULL) + public static class DutySpot { + private String id; + private String name; + } + } + diff --git a/src/main/java/cn/teammodel/model/entity/weekDuty/WeekDutyRecord.java b/src/main/java/cn/teammodel/model/entity/weekDuty/WeekDutyRecord.java new file mode 100644 index 0000000..7ca0e08 --- /dev/null +++ b/src/main/java/cn/teammodel/model/entity/weekDuty/WeekDutyRecord.java @@ -0,0 +1,48 @@ +package cn.teammodel.model.entity.weekDuty; + +import cn.teammodel.model.entity.BaseItem; +import com.azure.spring.data.cosmos.core.mapping.Container; +import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.util.List; + +/** + * 值周对象是班级,对应一个评价的指标 + * @author winter + * @create 2023-12-29 15:53 + */ +@EqualsAndHashCode(callSuper = true) +@Container(containerName = "School") +@Data +@JsonInclude(JsonInclude.Include.NON_NULL) +public class WeekDutyRecord extends BaseItem { + private String classId; + private String className; + private String academicYearId; + /** + * 班级得分 + */ + private Integer score; + private List nodes; + private Long createTime; + + @Data + public static class WeekDutyItem { + private String id; + private WeekDuty.DutyTreeNode dutyTreeNode; + private String spotId; + private String spotName; + /** + * 评分是否传播到学生个人 + */ + private boolean spread; + private List attachments; + /** + * 备注 + */ + private String note; + private Long createTime; + } +} diff --git a/src/main/java/cn/teammodel/repository/DutyRecordRepository.java b/src/main/java/cn/teammodel/repository/DutyRecordRepository.java new file mode 100644 index 0000000..dab276c --- /dev/null +++ b/src/main/java/cn/teammodel/repository/DutyRecordRepository.java @@ -0,0 +1,13 @@ +package cn.teammodel.repository; + +import cn.teammodel.model.entity.weekDuty.WeekDutyRecord; +import com.azure.spring.data.cosmos.repository.CosmosRepository; +import org.springframework.stereotype.Repository; + +/** + * @author winter + * @create 2024-01-04 17:23 + */ +@Repository +public interface DutyRecordRepository extends CosmosRepository { +} diff --git a/src/main/java/cn/teammodel/repository/DutyRepository.java b/src/main/java/cn/teammodel/repository/DutyRepository.java new file mode 100644 index 0000000..7fc183a --- /dev/null +++ b/src/main/java/cn/teammodel/repository/DutyRepository.java @@ -0,0 +1,21 @@ +package cn.teammodel.repository; + +import cn.teammodel.model.entity.weekDuty.WeekDuty; +import com.azure.spring.data.cosmos.repository.CosmosRepository; +import com.azure.spring.data.cosmos.repository.Query; +import org.springframework.stereotype.Repository; + +import java.util.List; + +/** + * @author winter + * @create 2024-01-03 15:18 + */ +@Repository +public interface DutyRepository extends CosmosRepository { + WeekDuty findBySchoolIdAndCode(String schoolId, String code); + + @Query("select value n from c join n in c.nodes where c.schoolId = @schoolId and c.code = 'Duty' and n.id = @nodeId") + List findDutyNodeById(String nodeId, String schoolId); + +} diff --git a/src/main/java/cn/teammodel/service/DutyService.java b/src/main/java/cn/teammodel/service/DutyService.java new file mode 100644 index 0000000..cf89937 --- /dev/null +++ b/src/main/java/cn/teammodel/service/DutyService.java @@ -0,0 +1,24 @@ +package cn.teammodel.service; + +import cn.teammodel.model.dto.weekDuty.DeleteDutyNodeDto; +import cn.teammodel.model.dto.weekDuty.DutyVoteDto; +import cn.teammodel.model.dto.weekDuty.InsertDutyNodeDto; +import cn.teammodel.model.dto.weekDuty.UpdateDutyNodeDto; +import cn.teammodel.model.entity.weekDuty.WeekDuty; + +/** + * @author winter + * @create 2024-01-03 10:07 + */ +public interface DutyService { + WeekDuty insertNode(InsertDutyNodeDto insertDutyNodeDto); + + WeekDuty getTree(); + + WeekDuty deleteNode(DeleteDutyNodeDto deleteDutyNodeDto); + + WeekDuty updateNode(UpdateDutyNodeDto updateDutyNodeDto); + + void vote(DutyVoteDto dutyVoteDto); + +} diff --git a/src/main/java/cn/teammodel/service/impl/DutyServiceImpl.java b/src/main/java/cn/teammodel/service/impl/DutyServiceImpl.java new file mode 100644 index 0000000..50233b9 --- /dev/null +++ b/src/main/java/cn/teammodel/service/impl/DutyServiceImpl.java @@ -0,0 +1,300 @@ +package cn.teammodel.service.impl; + +import cn.hutool.core.lang.UUID; +import cn.teammodel.common.ErrorCode; +import cn.teammodel.common.PK; +import cn.teammodel.config.exception.ServiceException; +import cn.teammodel.model.dto.weekDuty.DeleteDutyNodeDto; +import cn.teammodel.model.dto.weekDuty.DutyVoteDto; +import cn.teammodel.model.dto.weekDuty.InsertDutyNodeDto; +import cn.teammodel.model.dto.weekDuty.UpdateDutyNodeDto; +import cn.teammodel.model.entity.User; +import cn.teammodel.model.entity.school.ClassInfo; +import cn.teammodel.model.entity.school.School; +import cn.teammodel.model.entity.weekDuty.WeekDuty; +import cn.teammodel.repository.ClassRepository; +import cn.teammodel.repository.DutyRecordRepository; +import cn.teammodel.repository.DutyRepository; +import cn.teammodel.repository.SchoolRepository; +import cn.teammodel.security.utils.SecurityUtil; +import cn.teammodel.service.DutyService; +import cn.teammodel.utils.SchoolDateUtil; +import org.apache.commons.lang3.ObjectUtils; +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; +import java.time.Instant; +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.stream.Collectors; + +/** + * @author winter + * @create 2024-01-03 10:08 + */ +@Service +public class DutyServiceImpl implements DutyService { + @Resource + private ClassRepository classRepository; + @Resource + private SchoolRepository schoolRepository; + @Resource + private DutyRecordRepository dutyRecordRepository; + @Resource + private DutyRepository dutyRepository; + + @Override + public WeekDuty getTree() { + User user = SecurityUtil.getLoginUser(); + String schoolId = user.getSchoolId(); + + WeekDuty duty = dutyRepository.findBySchoolIdAndCode(schoolId , PK.WEEK_DUTY); + if (duty == null) { + // copy from template + duty = dutyRepository.findBySchoolIdAndCode("template", PK.WEEK_DUTY); + if (duty == null) { + throw new RuntimeException("值周模板不存在"); + } + duty.setId(null); + duty.setSchoolId(schoolId); + // 刷新值周树和 spots 的 id + refreshDuty(duty); + dutyRepository.save(duty); + } + + return this.buildTree(duty); + } + + @Override + public WeekDuty insertNode(InsertDutyNodeDto insertDutyNodeDto) { + String pid = insertDutyNodeDto.getPid(); + String name = insertDutyNodeDto.getName(); + Integer order = insertDutyNodeDto.getOrder(); + Integer score = insertDutyNodeDto.getScore(); + + User user = SecurityUtil.getLoginUser(); + String schoolId = user.getSchoolId(); + WeekDuty duty = dutyRepository.findBySchoolIdAndCode(schoolId, PK.WEEK_DUTY); + if (duty == null) { + throw new ServiceException("值周树不存在"); + } + WeekDuty.DutyTreeNode newNode = new WeekDuty.DutyTreeNode(); + + List nodes = duty.getNodes(); + // 非根节点(需考虑填充 path) + if (StringUtils.isNotEmpty(pid)) { + WeekDuty.DutyTreeNode parent = nodes.stream().filter(x -> pid.equals(x.getId())) + .findFirst() + .orElseThrow(() -> new ServiceException(ErrorCode.PARAMS_ERROR.getCode(), "pid 无效")); + String level2 = parent.getName(); + // 新节点是三级节点(二级节点不用特殊处理 + String ppid = parent.getId(); + if (ppid != null) { + String[] path = new String[3]; + WeekDuty.DutyTreeNode root = nodes.stream().filter(x -> ppid.equals(x.getId())) + .findFirst() + .orElseThrow(() -> new ServiceException(ErrorCode.SYSTEM_ERROR.getCode(), "值周树结构错误")); + // 层数不为三级,抛出异常 + if (root.getPid() != null) { + throw new ServiceException(ErrorCode.SYSTEM_ERROR.getCode(), "值周树结构错误"); + } + String level1 = root.getName(); + path[0] = level1; + path[1] = level2; + path[2] = name; + newNode.setPath(path); + } + } + + newNode.setId(UUID.randomUUID().toString()); + newNode.setPid(pid); + newNode.setName(name); + newNode.setScore(score); + newNode.setOrder(order); + newNode.setPositive(score >= 0); + newNode.setCreator(user.getName()); + newNode.setCreatorId(user.getId()); + newNode.setCreateTime(Instant.now().toEpochMilli()); + nodes.add(newNode); + return buildTree(dutyRepository.save(duty)); + } + + @Override + public WeekDuty deleteNode(DeleteDutyNodeDto deleteDutyNodeDto) { + String schoolId = SecurityUtil.getLoginUser().getSchoolId(); + String nodeToDeleteId = deleteDutyNodeDto.getNodeId(); + + WeekDuty duty = dutyRepository.findBySchoolIdAndCode(schoolId, PK.WEEK_DUTY); + if (duty == null) { + throw new ServiceException("值周树不存在"); + } + List nodes = duty.getNodes(); + List nodesToDelete = new ArrayList<>(); + WeekDuty.DutyTreeNode node = nodes.stream() + .filter(x -> nodeToDeleteId.equals(x.getId())) + .findFirst() + .orElseThrow(() -> new ServiceException(ErrorCode.PARAMS_ERROR.getCode(), "删除的节点不存在")); + + nodesToDelete.add(node); + this.collectNodesToDelete(nodeToDeleteId, nodes, nodesToDelete); + // 批量删除节点 + nodes.removeAll(nodesToDelete); + + return buildTree(dutyRepository.save(duty)); + } + + @Override + public WeekDuty updateNode(UpdateDutyNodeDto updateDutyNodeDto) { + String nodeId = updateDutyNodeDto.getId(); + String newNodeName = updateDutyNodeDto.getName(); + Integer newScore = updateDutyNodeDto.getScore(); + + String schoolId = SecurityUtil.getLoginUser().getSchoolId(); + WeekDuty duty = dutyRepository.findBySchoolIdAndCode(schoolId, PK.WEEK_DUTY); + if (duty == null) { + throw new ServiceException("值周树不存在"); + } + List nodes = duty.getNodes(); + WeekDuty.DutyTreeNode nodeToUpdate = nodes.stream().filter(x -> nodeId.equals(x.getId())) + .findFirst() + .orElseThrow(() -> new ServiceException("待更新的节点不存在")); + String originNodeName = nodeToUpdate.getName(); + // 更新当前节点 + nodeToUpdate.setName(newNodeName); + nodeToUpdate.setScore(newScore); + nodeToUpdate.setOrder(updateDutyNodeDto.getOrder()); + nodeToUpdate.setPositive(newScore >= 0); + + // 如果是一级,二级需同步更新三级节点的 path + if (nodeToUpdate.getPath() != null) { + int index = nodeToUpdate.getPid() == null ? 0 : 1; + nodes.forEach(x -> { + // 修改三级节点的 path + if (x.getPath() == null) { + return; + } + String[] path = x.getPath(); + if (originNodeName.equals(path[index])) { + path[index] = newNodeName; + x.setPath(path); + } + }); + } + + return buildTree(dutyRepository.save(duty)); + } + + @Override + public void vote(DutyVoteDto dutyVoteDto) { + String classId = dutyVoteDto.getClassId(); + String spotId = dutyVoteDto.getSpotId(); + String dutyNodeId = dutyVoteDto.getDutyNodeId(); + String note = dutyVoteDto.getNote(); + String[] attachments = dutyVoteDto.getAttachments(); + + String schoolId = SecurityUtil.getLoginUser().getSchoolId(); + ClassInfo classInfo = classRepository.findClassByIdAndCode(classId, String.format(PK.CLASS, schoolId)); + if (classInfo == null) { + throw new ServiceException(ErrorCode.PARAMS_ERROR.getCode(), "目标班级不存在"); + } + String className = classInfo.getName(); + String periodId = classInfo.getPeriodId(); + List semesters = schoolRepository.findSemestersById(schoolId, periodId); + if (ObjectUtils.isEmpty(semesters)) { + throw new ServiceException(ErrorCode.SYSTEM_ERROR.getCode(), "内部结构异常"); + } + String academicYearId = SchoolDateUtil.calculateAcademicYearId(semesters, LocalDate.now()); + WeekDuty duty = dutyRepository.findBySchoolIdAndCode(schoolId, PK.WEEK_DUTY); + WeekDuty.DutyTreeNode dutyNode = duty.getNodes().stream() + .filter(x -> dutyNodeId.equals(x.getId())) + .findFirst() + .orElseThrow(() -> new ServiceException(ErrorCode.PARAMS_ERROR.getCode(), "评价节点不存在")); + + + + } + + /** + * 刷新值周树和 spots 的 id + */ + private void refreshDuty(WeekDuty duty) { + List nodes = duty.getNodes(); + List spots = duty.getSpots(); + // refresh tree nodes + nodes.forEach(x -> { + String oldId = x.getId(); + String newId = UUID.randomUUID().toString(); + x.setId(newId); + // 将孩子节点的 pid 改为新的 id + nodes.stream().filter(y -> oldId.equals(y.getPid())).forEach(y -> y.setPid(newId)); + }); + + // refresh spot id + spots.forEach(x -> x.setId(UUID.randomUUID().toString())); + } + + /** + * 递归收集要删除的节点 + */ + private void collectNodesToDelete(String nodeToDeleteId, List nodes, List nodesToDelete) { + List children = nodes.stream() + .filter(x -> nodeToDeleteId.equals(x.getPid())) + .collect(Collectors.toList()); + if (children.size() == 0) return; + nodesToDelete.addAll(children); + for (WeekDuty.DutyTreeNode child : children) { + this.collectNodesToDelete(child.getId(), nodes, nodesToDelete); + } + } + + public WeekDuty buildTree(WeekDuty weekDuty) { + if (weekDuty == null) { + return null; + } + + List nodes = weekDuty.getNodes(); + List parents = new ArrayList<>(); + + // pid 为 null 或者 "" 则为 parents + for (WeekDuty.DutyTreeNode node : nodes) { + if (StringUtils.isBlank(node.getPid())) { + parents.add(node); + } + } + // 迭代构建孩子节点 + for (WeekDuty.DutyTreeNode parent : parents) { + buildChildren(parent, nodes); + } + // 根据 order 排序 + parents = parents.stream().sorted(Comparator.comparing(WeekDuty.DutyTreeNode::getOrder)).collect(Collectors.toList()); + weekDuty.setNodes(parents); + return weekDuty; + } + + private void buildChildren(WeekDuty.DutyTreeNode parent, List nodes) { + List children = getChildren(parent, nodes); + if (children.size() == 0) return; + + parent.setChildren(children); + for (WeekDuty.DutyTreeNode child : children) { + // 如果子节点还有孩子的话,就继续递归构建 + if (getChildren(child, nodes).size() > 0) { + buildChildren(child, nodes); + } + } + + } + + private List getChildren(WeekDuty.DutyTreeNode parent, List nodes) { + List children = new ArrayList<>(); + for (WeekDuty.DutyTreeNode node : nodes) { + if (StringUtils.isNotBlank(node.getPid()) && node.getPid().equals(parent.getId())) { + children.add(node); + } + } + return children; + } +} diff --git a/src/main/java/cn/teammodel/service/impl/EvaluationServiceImpl.java b/src/main/java/cn/teammodel/service/impl/EvaluationServiceImpl.java index 5e1694c..cabc2d6 100644 --- a/src/main/java/cn/teammodel/service/impl/EvaluationServiceImpl.java +++ b/src/main/java/cn/teammodel/service/impl/EvaluationServiceImpl.java @@ -84,7 +84,7 @@ public class EvaluationServiceImpl implements EvaluationService { if (appraise == null) { appraise = RepositoryUtil.findOne(appraiseRepository.findTemplateTree(), "获取模板评价树失败"); if (appraise == null) { - throw new ServiceException(); + throw new ServiceException("模板评价树不存在"); } // refresh refreshAppraiseTree(appraise); @@ -173,6 +173,7 @@ public class EvaluationServiceImpl implements EvaluationService { AppraiseTreeNode newNode = new AppraiseTreeNode(); BeanUtils.copyProperties(insertNodeDto, newNode); + // todo: build path String newNodePid = newNode.getPid(); // 根节点直接添加 if (StringUtils.isNotEmpty(newNodePid)) { @@ -201,13 +202,12 @@ public class EvaluationServiceImpl implements EvaluationService { .filter(item -> updateNodeId.equals(item.getId())) .findFirst() .orElseThrow(() -> new ServiceException(ErrorCode.PARAMS_ERROR.getCode(), "更新节点不存在")); - // 更新字段,考虑直接 copy properties + // todo: 如果是一级,二级需同步更新三级节点的 path updateNode.setName(updateNodeDto.getName()); updateNode.setPath(updateNodeDto.getPath()); updateNode.setLogo(updateNodeDto.getLogo()); updateNode.setOrder(updateNodeDto.getOrder()); updateNode.setPraise(updateNodeDto.isPraise()); - // todo: 为新节点赋值必须参数 (id, creator), 可不可以添加默认值 order ? return buildTree(appraiseRepository.save(appraise)); } @@ -219,13 +219,14 @@ public class EvaluationServiceImpl implements EvaluationService { List nodes = appraise.getNodes(); List nodesToDelete = new ArrayList<>(); - // 迭代器安全删除 + // 收集需要删除的节点 for (AppraiseTreeNode node : nodes) { if (node.getId().equals(deleteNodeDto.getId())) { // 删除当前节点 nodesToDelete.add(node); // 递归删除其孩子节点 this.collectNodesToDelete(node.getId(), nodes, nodesToDelete); + break; } } if (ObjectUtils.isEmpty(nodesToDelete)) { diff --git a/src/test/java/cn/teammodel/Scripts.java b/src/test/java/cn/teammodel/Scripts.java new file mode 100644 index 0000000..749525d --- /dev/null +++ b/src/test/java/cn/teammodel/Scripts.java @@ -0,0 +1,27 @@ +package cn.teammodel; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +import java.io.File; +import java.io.IOException; + +/** + * @author winter + * @create 2024-01-03 16:21 + */ +@SpringBootTest +public class Scripts { + + + @Test + public void genDocFromJson() throws IOException { + String path = "C://Users/Administrator/Desktop/data.json"; + ObjectMapper objectMapper = new ObjectMapper(); + JsonNode root = objectMapper.readTree(new File(path)); + System.out.println(root.get("content")); + + } +}