diff --git a/src/main/java/com/onixbyte/deltaforceguide/controller/ModificationController.java b/src/main/java/com/onixbyte/deltaforceguide/controller/ModificationController.java index 5f3b1c6..5b77c01 100644 --- a/src/main/java/com/onixbyte/deltaforceguide/controller/ModificationController.java +++ b/src/main/java/com/onixbyte/deltaforceguide/controller/ModificationController.java @@ -1,18 +1,26 @@ package com.onixbyte.deltaforceguide.controller; +import com.onixbyte.deltaforceguide.domain.dto.ModificationBatchDeleteRequest; +import com.onixbyte.deltaforceguide.domain.dto.ModificationBatchCreateRequest; +import com.onixbyte.deltaforceguide.domain.dto.ModificationRequest; import com.onixbyte.deltaforceguide.domain.dto.ModificationResponse; import com.onixbyte.deltaforceguide.domain.dto.PageResponse; import com.onixbyte.deltaforceguide.service.ModificationService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; import jakarta.validation.constraints.Max; import jakarta.validation.constraints.Min; import jakarta.validation.constraints.Positive; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Sort; import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @@ -49,4 +57,34 @@ public class ModificationController { public ModificationResponse queryById(@PathVariable Long id) { return modificationService.queryById(id); } + + @Operation(description = "创建改装") + @PostMapping + public ModificationResponse create(@Valid @RequestBody ModificationRequest request) { + return modificationService.create(request); + } + + @Operation(description = "批量创建改装") + @PostMapping("/batch") + public List batchCreate(@Valid @RequestBody ModificationBatchCreateRequest request) { + return modificationService.batchCreate(request.modifications()); + } + + @Operation(description = "修改指定改装") + @PutMapping("/{id}") + public ModificationResponse update(@PathVariable Long id, @Valid @RequestBody ModificationRequest request) { + return modificationService.update(id, request); + } + + @Operation(description = "删除指定改装") + @DeleteMapping("/{id}") + public void delete(@PathVariable Long id) { + modificationService.delete(id); + } + + @Operation(description = "批量删除改装") + @DeleteMapping("/batch-delete") + public void batchDelete(@Valid @RequestBody ModificationBatchDeleteRequest request) { + modificationService.batchDelete(request.ids()); + } } diff --git a/src/main/java/com/onixbyte/deltaforceguide/domain/dto/AccessoryRequest.java b/src/main/java/com/onixbyte/deltaforceguide/domain/dto/AccessoryRequest.java new file mode 100644 index 0000000..29fa638 --- /dev/null +++ b/src/main/java/com/onixbyte/deltaforceguide/domain/dto/AccessoryRequest.java @@ -0,0 +1,20 @@ +package com.onixbyte.deltaforceguide.domain.dto; + +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotBlank; + +import java.util.ArrayList; +import java.util.List; + +public record AccessoryRequest( + @NotBlank(message = "插槽名称不能为空") + String slotName, + @NotBlank(message = "配件名称不能为空") + String accessoryName, + List<@Valid TuningRequest> tunings +) { + public List tunings() { + return tunings == null ? new ArrayList<>() : tunings; + } +} + diff --git a/src/main/java/com/onixbyte/deltaforceguide/domain/dto/ModificationBatchCreateRequest.java b/src/main/java/com/onixbyte/deltaforceguide/domain/dto/ModificationBatchCreateRequest.java new file mode 100644 index 0000000..cdb3d93 --- /dev/null +++ b/src/main/java/com/onixbyte/deltaforceguide/domain/dto/ModificationBatchCreateRequest.java @@ -0,0 +1,13 @@ +package com.onixbyte.deltaforceguide.domain.dto; + +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotEmpty; + +import java.util.List; + +public record ModificationBatchCreateRequest( + @NotEmpty(message = "批量创建列表不能为空") + List<@Valid ModificationRequest> modifications +) { +} + diff --git a/src/main/java/com/onixbyte/deltaforceguide/domain/dto/ModificationBatchDeleteRequest.java b/src/main/java/com/onixbyte/deltaforceguide/domain/dto/ModificationBatchDeleteRequest.java new file mode 100644 index 0000000..7e46400 --- /dev/null +++ b/src/main/java/com/onixbyte/deltaforceguide/domain/dto/ModificationBatchDeleteRequest.java @@ -0,0 +1,13 @@ +package com.onixbyte.deltaforceguide.domain.dto; + +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.Positive; + +import java.util.List; + +public record ModificationBatchDeleteRequest( + @NotEmpty(message = "批量删除ID列表不能为空") + List<@Positive(message = "ID必须为正数") Long> ids +) { +} + diff --git a/src/main/java/com/onixbyte/deltaforceguide/domain/dto/ModificationRequest.java b/src/main/java/com/onixbyte/deltaforceguide/domain/dto/ModificationRequest.java new file mode 100644 index 0000000..c602dbd --- /dev/null +++ b/src/main/java/com/onixbyte/deltaforceguide/domain/dto/ModificationRequest.java @@ -0,0 +1,33 @@ +package com.onixbyte.deltaforceguide.domain.dto; + +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Positive; + +import java.util.ArrayList; +import java.util.List; + +public record ModificationRequest( + @NotNull(message = "武器ID不能为空") + @Positive(message = "武器ID必须为正数") + Long firearmId, + @NotBlank(message = "改装名称不能为空") + String name, + @NotBlank(message = "改装代码不能为空") + String code, + List<@NotBlank(message = "标签不能为空") String> tags, + List<@Valid AccessoryRequest> accessories, + String note, + String author, + String videoUrl +) { + public List tags() { + return tags == null ? new ArrayList<>() : tags; + } + + public List accessories() { + return accessories == null ? new ArrayList<>() : accessories; + } +} + diff --git a/src/main/java/com/onixbyte/deltaforceguide/domain/dto/TuningRequest.java b/src/main/java/com/onixbyte/deltaforceguide/domain/dto/TuningRequest.java new file mode 100644 index 0000000..414becd --- /dev/null +++ b/src/main/java/com/onixbyte/deltaforceguide/domain/dto/TuningRequest.java @@ -0,0 +1,13 @@ +package com.onixbyte.deltaforceguide.domain.dto; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; + +public record TuningRequest( + @NotBlank(message = "调校项名称不能为空") + String tuningName, + @NotNull(message = "调校值不能为空") + Double tuningValue +) { +} + diff --git a/src/main/java/com/onixbyte/deltaforceguide/service/ModificationService.java b/src/main/java/com/onixbyte/deltaforceguide/service/ModificationService.java index 4da45d3..39e5ec0 100644 --- a/src/main/java/com/onixbyte/deltaforceguide/service/ModificationService.java +++ b/src/main/java/com/onixbyte/deltaforceguide/service/ModificationService.java @@ -1,8 +1,15 @@ package com.onixbyte.deltaforceguide.service; +import com.onixbyte.deltaforceguide.domain.dto.AccessoryRequest; +import com.onixbyte.deltaforceguide.domain.dto.ModificationRequest; import com.onixbyte.deltaforceguide.domain.dto.ModificationResponse; import com.onixbyte.deltaforceguide.domain.dto.PageResponse; +import com.onixbyte.deltaforceguide.domain.dto.TuningRequest; +import com.onixbyte.deltaforceguide.domain.entity.Accessory; +import com.onixbyte.deltaforceguide.domain.entity.Firearm; import com.onixbyte.deltaforceguide.domain.entity.Modification; +import com.onixbyte.deltaforceguide.domain.entity.Tuning; +import com.onixbyte.deltaforceguide.repository.FirearmRepository; import com.onixbyte.deltaforceguide.repository.ModificationRepository; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; @@ -13,16 +20,27 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.server.ResponseStatusException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedHashSet; import java.util.List; +import java.util.Map; +import java.util.Set; @Service public class ModificationService { private final ModificationRepository modificationRepository; + private final FirearmRepository firearmRepository; private final ObjectMapper objectMapper; - public ModificationService(ModificationRepository modificationRepository, ObjectMapper objectMapper) { + public ModificationService( + ModificationRepository modificationRepository, + FirearmRepository firearmRepository, + ObjectMapper objectMapper + ) { this.modificationRepository = modificationRepository; + this.firearmRepository = firearmRepository; this.objectMapper = objectMapper; } @@ -58,4 +76,134 @@ public class ModificationService { public List findAllTags(Long firearmId) { return modificationRepository.findAllTags(firearmId); } + + @Transactional + public ModificationResponse create(ModificationRequest request) { + Firearm firearm = firearmRepository.findById(request.firearmId()) + .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Firearm not found: " + request.firearmId())); + + Modification modification = toEntity(request, firearm); + return ModificationResponse.from(modificationRepository.save(modification)); + } + + @Transactional + public List batchCreate(List requests) { + Set firearmIds = requests.stream() + .map(ModificationRequest::firearmId) + .collect(java.util.stream.Collectors.toCollection(LinkedHashSet::new)); + + Map firearmMap = new HashMap<>(); + firearmRepository.findAllById(firearmIds).forEach(firearm -> firearmMap.put(firearm.getId(), firearm)); + + if (firearmMap.size() != firearmIds.size()) { + List missingFirearmIds = firearmIds.stream() + .filter(id -> !firearmMap.containsKey(id)) + .toList(); + throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Firearm not found: " + missingFirearmIds); + } + + List modifications = requests.stream() + .map(request -> toEntity(request, firearmMap.get(request.firearmId()))) + .toList(); + return modificationRepository.saveAll(modifications) + .stream() + .map(ModificationResponse::from) + .toList(); + } + + @Transactional + public ModificationResponse update(Long id, ModificationRequest request) { + Modification modification = modificationRepository.findById(id) + .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Modification not found: " + id)); + Firearm firearm = firearmRepository.findById(request.firearmId()) + .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Firearm not found: " + request.firearmId())); + + modification.setFirearm(firearm); + modification.setName(request.name()); + modification.setCode(request.code()); + modification.setTags(safeTags(request.tags())); + modification.setAccessories(toAccessories(request.accessories())); + modification.setNote(request.note()); + modification.setAuthor(request.author()); + modification.setVideoUrl(request.videoUrl()); + + return ModificationResponse.from(modificationRepository.save(modification)); + } + + @Transactional + public void delete(Long id) { + Modification modification = modificationRepository.findById(id) + .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Modification not found: " + id)); + modificationRepository.delete(modification); + } + + @Transactional + public void batchDelete(List ids) { + Set uniqueIds = new LinkedHashSet<>(ids); + List modifications = modificationRepository.findAllById(uniqueIds); + + if (modifications.size() != uniqueIds.size()) { + Set foundIds = modifications.stream() + .map(Modification::getId) + .collect(java.util.stream.Collectors.toSet()); + List missingIds = uniqueIds.stream() + .filter(id -> !foundIds.contains(id)) + .toList(); + throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Modification not found: " + missingIds); + } + + modificationRepository.deleteAllInBatch(modifications); + } + + private Modification toEntity(ModificationRequest request, Firearm firearm) { + return Modification.builder() + .firearm(firearm) + .name(request.name()) + .code(request.code()) + .tags(safeTags(request.tags())) + .accessories(toAccessories(request.accessories())) + .note(request.note()) + .author(request.author()) + .videoUrl(request.videoUrl()) + .build(); + } + + private List safeTags(List tags) { + return tags == null ? new ArrayList<>() : tags; + } + + private List toAccessories(List accessoryRequests) { + if (accessoryRequests == null) { + return new ArrayList<>(); + } + + return accessoryRequests.stream() + .map(this::toAccessory) + .toList(); + } + + private Accessory toAccessory(AccessoryRequest request) { + Accessory accessory = new Accessory(); + accessory.setSlotName(request.slotName()); + accessory.setAccessoryName(request.accessoryName()); + accessory.setTunings(toTunings(request.tunings())); + return accessory; + } + + private List toTunings(List tuningRequests) { + if (tuningRequests == null) { + return new ArrayList<>(); + } + + return tuningRequests.stream() + .map(this::toTuning) + .toList(); + } + + private Tuning toTuning(TuningRequest request) { + Tuning tuning = new Tuning(); + tuning.setTuningName(request.tuningName()); + tuning.setTuningValue(request.tuningValue()); + return tuning; + } }