提交工作更新
This commit is contained in:
@@ -6,12 +6,19 @@ import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
@Slf4j
|
||||
public class PermissionMenuSchemaInitializer {
|
||||
|
||||
private final JdbcTemplate jdbcTemplate;
|
||||
private static final List<DefaultAdminMenu> DEFAULT_ADMIN_MENUS = List.of(
|
||||
new DefaultAdminMenu("数据去重总数据", "admin_dedupe_total_data", "dedupe-total-data"),
|
||||
new DefaultAdminMenu("店铺管理", "admin_shop_manage", "shop-manage"),
|
||||
new DefaultAdminMenu("跳过跟价ASIN", "admin_skip_price_asin", "skip-price-asin")
|
||||
);
|
||||
|
||||
@PostConstruct
|
||||
public void initialize() {
|
||||
@@ -27,6 +34,7 @@ public class PermissionMenuSchemaInitializer {
|
||||
executeQuietly("ALTER TABLE columns ADD COLUMN menu_type VARCHAR(20) NOT NULL DEFAULT 'app' COMMENT '菜单类型: app/admin' AFTER column_key");
|
||||
executeQuietly("ALTER TABLE columns ADD COLUMN route_path VARCHAR(255) NOT NULL DEFAULT '' COMMENT '菜单路由或页面标识' AFTER menu_type");
|
||||
executeQuietly("UPDATE columns SET menu_type = 'app' WHERE menu_type IS NULL OR menu_type = ''");
|
||||
ensureDefaultAdminMenus();
|
||||
executeQuietly("""
|
||||
INSERT INTO columns (name, column_key, menu_type, route_path)
|
||||
SELECT '店铺管理', 'admin_shop_manage', 'admin', 'shop-manage'
|
||||
@@ -45,6 +53,18 @@ public class PermissionMenuSchemaInitializer {
|
||||
""");
|
||||
}
|
||||
|
||||
private void ensureDefaultAdminMenus() {
|
||||
for (DefaultAdminMenu menu : DEFAULT_ADMIN_MENUS) {
|
||||
executeQuietly("""
|
||||
INSERT INTO columns (name, column_key, menu_type, route_path)
|
||||
SELECT '%s', '%s', 'admin', '%s'
|
||||
WHERE NOT EXISTS (
|
||||
SELECT 1 FROM columns WHERE column_key = '%s'
|
||||
)
|
||||
""".formatted(menu.name(), menu.columnKey(), menu.routePath(), menu.columnKey()));
|
||||
}
|
||||
}
|
||||
|
||||
private void executeQuietly(String sql) {
|
||||
try {
|
||||
jdbcTemplate.execute(sql);
|
||||
@@ -52,4 +72,7 @@ public class PermissionMenuSchemaInitializer {
|
||||
log.debug("[permission-menu] schema init skipped: {}", ex.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private record DefaultAdminMenu(String name, String columnKey, String routePath) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
package com.nanri.aiimage.modules.shopkey.controller;
|
||||
|
||||
import com.nanri.aiimage.common.api.ApiResponse;
|
||||
import com.nanri.aiimage.modules.shopkey.model.dto.SkipPriceAsinCreateRequest;
|
||||
import com.nanri.aiimage.modules.shopkey.model.vo.SkipPriceAsinItemVo;
|
||||
import com.nanri.aiimage.modules.shopkey.model.vo.SkipPriceAsinPageVo;
|
||||
import com.nanri.aiimage.modules.shopkey.service.SkipPriceAsinService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.validation.Valid;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
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.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@RestController
|
||||
@RequiredArgsConstructor
|
||||
@RequestMapping("/api/admin/skip-price-asins")
|
||||
@Tag(name = "跳过跟价 ASIN", description = "维护店铺按国家跳过跟价的 ASIN")
|
||||
public class SkipPriceAsinController {
|
||||
|
||||
private final SkipPriceAsinService skipPriceAsinService;
|
||||
|
||||
@GetMapping
|
||||
@Operation(summary = "分页查询跳过跟价 ASIN")
|
||||
public ApiResponse<SkipPriceAsinPageVo> page(
|
||||
@Parameter(description = "页码") @RequestParam(defaultValue = "1") Long page,
|
||||
@Parameter(description = "每页数量") @RequestParam(defaultValue = "15") Long pageSize,
|
||||
@Parameter(description = "分组ID") @RequestParam(required = false) Long groupId,
|
||||
@Parameter(description = "店铺名") @RequestParam(required = false) String shopName,
|
||||
@Parameter(description = "ASIN") @RequestParam(required = false) String asin) {
|
||||
return ApiResponse.success(skipPriceAsinService.page(page, pageSize, groupId, shopName, asin));
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
@Operation(summary = "新增或覆盖跳过跟价 ASIN")
|
||||
public ApiResponse<SkipPriceAsinItemVo> create(@Valid @RequestBody SkipPriceAsinCreateRequest request) {
|
||||
return ApiResponse.success("保存成功", skipPriceAsinService.createOrUpdate(request));
|
||||
}
|
||||
|
||||
@DeleteMapping("/{id}/countries/{country}")
|
||||
@Operation(summary = "删除指定国家的跳过跟价 ASIN")
|
||||
public ApiResponse<Void> deleteCountry(
|
||||
@Parameter(description = "主键ID", required = true) @PathVariable Long id,
|
||||
@Parameter(description = "国家编码", required = true) @PathVariable String country) {
|
||||
skipPriceAsinService.deleteCountry(id, country);
|
||||
return ApiResponse.success("删除成功", null);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.nanri.aiimage.modules.shopkey.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.nanri.aiimage.modules.shopkey.model.entity.SkipPriceAsinEntity;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@Mapper
|
||||
public interface SkipPriceAsinMapper extends BaseMapper<SkipPriceAsinEntity> {
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.nanri.aiimage.modules.shopkey.model.dto;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotEmpty;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
public class SkipPriceAsinCreateRequest {
|
||||
|
||||
@NotNull(message = "分组不能为空")
|
||||
private Long groupId;
|
||||
|
||||
@NotBlank(message = "店铺名不能为空")
|
||||
private String shopName;
|
||||
|
||||
@NotEmpty(message = "请至少选择一个国家")
|
||||
private List<String> countries;
|
||||
|
||||
@NotBlank(message = "ASIN 不能为空")
|
||||
private String asin;
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package com.nanri.aiimage.modules.shopkey.model.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.time.LocalDateTime;
|
||||
|
||||
@Data
|
||||
@TableName("biz_skip_price_asin")
|
||||
public class SkipPriceAsinEntity {
|
||||
|
||||
@TableId(type = IdType.AUTO)
|
||||
private Long id;
|
||||
|
||||
@TableField("group_id")
|
||||
private Long groupId;
|
||||
|
||||
@TableField("shop_name")
|
||||
private String shopName;
|
||||
|
||||
@TableField("asin_de")
|
||||
private String asinDe;
|
||||
|
||||
@TableField("asin_uk")
|
||||
private String asinUk;
|
||||
|
||||
@TableField("asin_fr")
|
||||
private String asinFr;
|
||||
|
||||
@TableField("asin_it")
|
||||
private String asinIt;
|
||||
|
||||
@TableField("asin_es")
|
||||
private String asinEs;
|
||||
|
||||
@TableField("created_at")
|
||||
private LocalDateTime createdAt;
|
||||
|
||||
@TableField("updated_at")
|
||||
private LocalDateTime updatedAt;
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.nanri.aiimage.modules.shopkey.model.vo;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Data
|
||||
public class SkipPriceAsinItemVo {
|
||||
|
||||
private Long id;
|
||||
private Long groupId;
|
||||
private String groupName;
|
||||
private String shopName;
|
||||
private String asinDe;
|
||||
private String asinUk;
|
||||
private String asinFr;
|
||||
private String asinIt;
|
||||
private String asinEs;
|
||||
private LocalDateTime createdAt;
|
||||
private LocalDateTime updatedAt;
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.nanri.aiimage.modules.shopkey.model.vo;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
public class SkipPriceAsinPageVo {
|
||||
|
||||
private List<SkipPriceAsinItemVo> items = new ArrayList<>();
|
||||
private Long total = 0L;
|
||||
private Long page = 1L;
|
||||
private Long pageSize = 15L;
|
||||
}
|
||||
@@ -0,0 +1,212 @@
|
||||
package com.nanri.aiimage.modules.shopkey.service;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.nanri.aiimage.common.exception.BusinessException;
|
||||
import com.nanri.aiimage.modules.shopkey.mapper.SkipPriceAsinMapper;
|
||||
import com.nanri.aiimage.modules.shopkey.model.dto.SkipPriceAsinCreateRequest;
|
||||
import com.nanri.aiimage.modules.shopkey.model.entity.ShopManageGroupEntity;
|
||||
import com.nanri.aiimage.modules.shopkey.model.entity.SkipPriceAsinEntity;
|
||||
import com.nanri.aiimage.modules.shopkey.model.vo.SkipPriceAsinItemVo;
|
||||
import com.nanri.aiimage.modules.shopkey.model.vo.SkipPriceAsinPageVo;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class SkipPriceAsinService {
|
||||
|
||||
private static final List<String> SUPPORTED_COUNTRIES = List.of("DE", "UK", "FR", "IT", "ES");
|
||||
|
||||
private final SkipPriceAsinMapper skipPriceAsinMapper;
|
||||
private final ShopManageGroupService shopManageGroupService;
|
||||
|
||||
public SkipPriceAsinPageVo page(long page, long pageSize, Long groupId, String shopName, String asin) {
|
||||
long safePage = Math.max(page, 1);
|
||||
long safePageSize = Math.min(Math.max(pageSize, 1), 100);
|
||||
String safeShopName = normalizeBlank(shopName);
|
||||
String safeAsin = normalizeBlank(asin);
|
||||
|
||||
LambdaQueryWrapper<SkipPriceAsinEntity> query = new LambdaQueryWrapper<SkipPriceAsinEntity>()
|
||||
.eq(groupId != null && groupId > 0, SkipPriceAsinEntity::getGroupId, groupId)
|
||||
.like(!safeShopName.isEmpty(), SkipPriceAsinEntity::getShopName, safeShopName)
|
||||
.and(!safeAsin.isEmpty(), wrapper -> wrapper
|
||||
.like(SkipPriceAsinEntity::getAsinDe, safeAsin)
|
||||
.or().like(SkipPriceAsinEntity::getAsinUk, safeAsin)
|
||||
.or().like(SkipPriceAsinEntity::getAsinFr, safeAsin)
|
||||
.or().like(SkipPriceAsinEntity::getAsinIt, safeAsin)
|
||||
.or().like(SkipPriceAsinEntity::getAsinEs, safeAsin))
|
||||
.orderByDesc(SkipPriceAsinEntity::getId);
|
||||
|
||||
Long total = skipPriceAsinMapper.selectCount(query);
|
||||
List<SkipPriceAsinEntity> rows = skipPriceAsinMapper
|
||||
.selectList(query.last("LIMIT " + ((safePage - 1) * safePageSize) + ", " + safePageSize));
|
||||
|
||||
Map<Long, String> groupNameById = buildGroupNameMap(rows.stream()
|
||||
.map(SkipPriceAsinEntity::getGroupId)
|
||||
.filter(id -> id != null && id > 0)
|
||||
.distinct()
|
||||
.toList());
|
||||
|
||||
SkipPriceAsinPageVo vo = new SkipPriceAsinPageVo();
|
||||
vo.setItems(rows.stream().map(row -> toItemVo(row, groupNameById.get(row.getGroupId()))).toList());
|
||||
vo.setTotal(total);
|
||||
vo.setPage(safePage);
|
||||
vo.setPageSize(safePageSize);
|
||||
return vo;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public SkipPriceAsinItemVo createOrUpdate(SkipPriceAsinCreateRequest request) {
|
||||
ShopManageGroupEntity group = shopManageGroupService.getById(request.getGroupId());
|
||||
String shopName = normalizeRequired(request.getShopName(), "店铺名不能为空");
|
||||
String asin = normalizeAsin(request.getAsin());
|
||||
Set<String> countries = normalizeCountries(request.getCountries());
|
||||
|
||||
SkipPriceAsinEntity entity = skipPriceAsinMapper.selectOne(new LambdaQueryWrapper<SkipPriceAsinEntity>()
|
||||
.eq(SkipPriceAsinEntity::getGroupId, group.getId())
|
||||
.eq(SkipPriceAsinEntity::getShopName, shopName)
|
||||
.last("LIMIT 1"));
|
||||
if (entity == null) {
|
||||
entity = new SkipPriceAsinEntity();
|
||||
entity.setGroupId(group.getId());
|
||||
entity.setShopName(shopName);
|
||||
}
|
||||
|
||||
for (String country : countries) {
|
||||
setCountryAsin(entity, country, asin);
|
||||
}
|
||||
|
||||
if (entity.getId() == null) {
|
||||
skipPriceAsinMapper.insert(entity);
|
||||
} else {
|
||||
skipPriceAsinMapper.updateById(entity);
|
||||
}
|
||||
SkipPriceAsinEntity saved = getById(entity.getId());
|
||||
return toItemVo(saved, group.getGroupName());
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void deleteCountry(Long id, String country) {
|
||||
SkipPriceAsinEntity entity = getById(id);
|
||||
String normalizedCountry = normalizeCountry(country);
|
||||
setCountryAsin(entity, normalizedCountry, "");
|
||||
if (isEmptyRow(entity)) {
|
||||
skipPriceAsinMapper.deleteById(entity.getId());
|
||||
return;
|
||||
}
|
||||
skipPriceAsinMapper.updateById(entity);
|
||||
}
|
||||
|
||||
private SkipPriceAsinEntity getById(Long id) {
|
||||
SkipPriceAsinEntity entity = skipPriceAsinMapper.selectById(id);
|
||||
if (entity == null) {
|
||||
throw new BusinessException("记录不存在");
|
||||
}
|
||||
return entity;
|
||||
}
|
||||
|
||||
private SkipPriceAsinItemVo toItemVo(SkipPriceAsinEntity entity, String groupName) {
|
||||
SkipPriceAsinItemVo vo = new SkipPriceAsinItemVo();
|
||||
vo.setId(entity.getId());
|
||||
vo.setGroupId(entity.getGroupId());
|
||||
vo.setGroupName(groupName == null ? "" : groupName);
|
||||
vo.setShopName(entity.getShopName());
|
||||
vo.setAsinDe(blankToEmpty(entity.getAsinDe()));
|
||||
vo.setAsinUk(blankToEmpty(entity.getAsinUk()));
|
||||
vo.setAsinFr(blankToEmpty(entity.getAsinFr()));
|
||||
vo.setAsinIt(blankToEmpty(entity.getAsinIt()));
|
||||
vo.setAsinEs(blankToEmpty(entity.getAsinEs()));
|
||||
vo.setCreatedAt(entity.getCreatedAt());
|
||||
vo.setUpdatedAt(entity.getUpdatedAt());
|
||||
return vo;
|
||||
}
|
||||
|
||||
private Map<Long, String> buildGroupNameMap(List<Long> groupIds) {
|
||||
if (groupIds == null || groupIds.isEmpty()) {
|
||||
return Map.of();
|
||||
}
|
||||
LinkedHashMap<Long, String> map = new LinkedHashMap<>();
|
||||
for (Long groupId : groupIds) {
|
||||
try {
|
||||
ShopManageGroupEntity group = shopManageGroupService.getById(groupId);
|
||||
map.put(groupId, group.getGroupName());
|
||||
} catch (Exception ignored) {
|
||||
map.put(groupId, "");
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
private Set<String> normalizeCountries(List<String> countries) {
|
||||
LinkedHashSet<String> normalized = new LinkedHashSet<>();
|
||||
if (countries != null) {
|
||||
for (String country : countries) {
|
||||
normalized.add(normalizeCountry(country));
|
||||
}
|
||||
}
|
||||
if (normalized.isEmpty()) {
|
||||
throw new BusinessException("请至少选择一个国家");
|
||||
}
|
||||
return normalized;
|
||||
}
|
||||
|
||||
private String normalizeCountry(String country) {
|
||||
String normalized = normalizeBlank(country).toUpperCase(Locale.ROOT);
|
||||
if (!SUPPORTED_COUNTRIES.contains(normalized)) {
|
||||
throw new BusinessException("国家参数不支持: " + country);
|
||||
}
|
||||
return normalized;
|
||||
}
|
||||
|
||||
private String normalizeAsin(String asin) {
|
||||
String normalized = normalizeRequired(asin, "ASIN 不能为空").toUpperCase(Locale.ROOT);
|
||||
if (normalized.length() > 64) {
|
||||
throw new BusinessException("ASIN 长度不能超过 64");
|
||||
}
|
||||
return normalized;
|
||||
}
|
||||
|
||||
private String normalizeRequired(String value, String message) {
|
||||
String normalized = normalizeBlank(value);
|
||||
if (normalized.isEmpty()) {
|
||||
throw new BusinessException(message);
|
||||
}
|
||||
return normalized;
|
||||
}
|
||||
|
||||
private String normalizeBlank(String value) {
|
||||
return value == null ? "" : value.trim();
|
||||
}
|
||||
|
||||
private String blankToEmpty(String value) {
|
||||
return value == null ? "" : value;
|
||||
}
|
||||
|
||||
private void setCountryAsin(SkipPriceAsinEntity entity, String country, String asin) {
|
||||
String value = normalizeBlank(asin);
|
||||
switch (country) {
|
||||
case "DE" -> entity.setAsinDe(value);
|
||||
case "UK" -> entity.setAsinUk(value);
|
||||
case "FR" -> entity.setAsinFr(value);
|
||||
case "IT" -> entity.setAsinIt(value);
|
||||
case "ES" -> entity.setAsinEs(value);
|
||||
default -> throw new BusinessException("国家参数不支持: " + country);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isEmptyRow(SkipPriceAsinEntity entity) {
|
||||
return normalizeBlank(entity.getAsinDe()).isEmpty()
|
||||
&& normalizeBlank(entity.getAsinUk()).isEmpty()
|
||||
&& normalizeBlank(entity.getAsinFr()).isEmpty()
|
||||
&& normalizeBlank(entity.getAsinIt()).isEmpty()
|
||||
&& normalizeBlank(entity.getAsinEs()).isEmpty();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
CREATE TABLE IF NOT EXISTS biz_skip_price_asin (
|
||||
id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '主键',
|
||||
group_id BIGINT NOT NULL COMMENT '分组ID',
|
||||
shop_name VARCHAR(128) NOT NULL COMMENT '店铺名',
|
||||
asin_de VARCHAR(64) NOT NULL DEFAULT '' COMMENT '德国 ASIN',
|
||||
asin_uk VARCHAR(64) NOT NULL DEFAULT '' COMMENT '英国 ASIN',
|
||||
asin_fr VARCHAR(64) NOT NULL DEFAULT '' COMMENT '法国 ASIN',
|
||||
asin_it VARCHAR(64) NOT NULL DEFAULT '' COMMENT '意大利 ASIN',
|
||||
asin_es VARCHAR(64) NOT NULL DEFAULT '' COMMENT '西班牙 ASIN',
|
||||
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
UNIQUE KEY uk_group_shop_name (group_id, shop_name),
|
||||
KEY idx_shop_name (shop_name),
|
||||
KEY idx_created_at (created_at)
|
||||
) COMMENT='跳过跟价 ASIN';
|
||||
|
||||
INSERT INTO `columns` (`name`, `column_key`, `menu_type`, `route_path`)
|
||||
SELECT '数据去重总数据', 'admin_dedupe_total_data', 'admin', 'dedupe-total-data'
|
||||
WHERE NOT EXISTS (
|
||||
SELECT 1 FROM `columns` WHERE `column_key` = 'admin_dedupe_total_data'
|
||||
);
|
||||
|
||||
INSERT INTO `columns` (`name`, `column_key`, `menu_type`, `route_path`)
|
||||
SELECT '店铺管理', 'admin_shop_manage', 'admin', 'shop-manage'
|
||||
WHERE NOT EXISTS (
|
||||
SELECT 1 FROM `columns` WHERE `column_key` = 'admin_shop_manage'
|
||||
);
|
||||
|
||||
INSERT INTO `columns` (`name`, `column_key`, `menu_type`, `route_path`)
|
||||
SELECT '跳过跟价ASIN', 'admin_skip_price_asin', 'admin', 'skip-price-asin'
|
||||
WHERE NOT EXISTS (
|
||||
SELECT 1 FROM `columns` WHERE `column_key` = 'admin_skip_price_asin'
|
||||
);
|
||||
Reference in New Issue
Block a user