feat: add modification creation and deletion endpoints, including batch operations and request DTOs

This commit is contained in:
2026-04-21 23:39:05 +08:00
parent 93dbd857e0
commit 5ce8a994a4
7 changed files with 279 additions and 1 deletions
@@ -1,18 +1,26 @@
package com.onixbyte.deltaforceguide.controller; 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.ModificationResponse;
import com.onixbyte.deltaforceguide.domain.dto.PageResponse; import com.onixbyte.deltaforceguide.domain.dto.PageResponse;
import com.onixbyte.deltaforceguide.service.ModificationService; import com.onixbyte.deltaforceguide.service.ModificationService;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import jakarta.validation.constraints.Max; import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min; import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.Positive; import jakarta.validation.constraints.Positive;
import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort; import org.springframework.data.domain.Sort;
import org.springframework.validation.annotation.Validated; 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.GetMapping;
import org.springframework.web.bind.annotation.PathVariable; 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.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
@@ -49,4 +57,34 @@ public class ModificationController {
public ModificationResponse queryById(@PathVariable Long id) { public ModificationResponse queryById(@PathVariable Long id) {
return modificationService.queryById(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<ModificationResponse> 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());
}
} }
@@ -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<TuningRequest> tunings() {
return tunings == null ? new ArrayList<>() : tunings;
}
}
@@ -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
) {
}
@@ -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
) {
}
@@ -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<String> tags() {
return tags == null ? new ArrayList<>() : tags;
}
public List<AccessoryRequest> accessories() {
return accessories == null ? new ArrayList<>() : accessories;
}
}
@@ -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
) {
}
@@ -1,8 +1,15 @@
package com.onixbyte.deltaforceguide.service; 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.ModificationResponse;
import com.onixbyte.deltaforceguide.domain.dto.PageResponse; 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.Modification;
import com.onixbyte.deltaforceguide.domain.entity.Tuning;
import com.onixbyte.deltaforceguide.repository.FirearmRepository;
import com.onixbyte.deltaforceguide.repository.ModificationRepository; import com.onixbyte.deltaforceguide.repository.ModificationRepository;
import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
@@ -13,16 +20,27 @@ import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.server.ResponseStatusException; 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.List;
import java.util.Map;
import java.util.Set;
@Service @Service
public class ModificationService { public class ModificationService {
private final ModificationRepository modificationRepository; private final ModificationRepository modificationRepository;
private final FirearmRepository firearmRepository;
private final ObjectMapper objectMapper; private final ObjectMapper objectMapper;
public ModificationService(ModificationRepository modificationRepository, ObjectMapper objectMapper) { public ModificationService(
ModificationRepository modificationRepository,
FirearmRepository firearmRepository,
ObjectMapper objectMapper
) {
this.modificationRepository = modificationRepository; this.modificationRepository = modificationRepository;
this.firearmRepository = firearmRepository;
this.objectMapper = objectMapper; this.objectMapper = objectMapper;
} }
@@ -58,4 +76,134 @@ public class ModificationService {
public List<String> findAllTags(Long firearmId) { public List<String> findAllTags(Long firearmId) {
return modificationRepository.findAllTags(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<ModificationResponse> batchCreate(List<ModificationRequest> requests) {
Set<Long> firearmIds = requests.stream()
.map(ModificationRequest::firearmId)
.collect(java.util.stream.Collectors.toCollection(LinkedHashSet::new));
Map<Long, Firearm> firearmMap = new HashMap<>();
firearmRepository.findAllById(firearmIds).forEach(firearm -> firearmMap.put(firearm.getId(), firearm));
if (firearmMap.size() != firearmIds.size()) {
List<Long> missingFirearmIds = firearmIds.stream()
.filter(id -> !firearmMap.containsKey(id))
.toList();
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Firearm not found: " + missingFirearmIds);
}
List<Modification> 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<Long> ids) {
Set<Long> uniqueIds = new LinkedHashSet<>(ids);
List<Modification> modifications = modificationRepository.findAllById(uniqueIds);
if (modifications.size() != uniqueIds.size()) {
Set<Long> foundIds = modifications.stream()
.map(Modification::getId)
.collect(java.util.stream.Collectors.toSet());
List<Long> 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<String> safeTags(List<String> tags) {
return tags == null ? new ArrayList<>() : tags;
}
private List<Accessory> toAccessories(List<AccessoryRequest> 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<Tuning> toTunings(List<TuningRequest> 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;
}
} }