Compare commits
2 Commits
ac36c08460
...
7643094f1d
| Author | SHA1 | Date | |
|---|---|---|---|
| 7643094f1d | |||
| 1b480f915f |
+2
-2
@@ -1,7 +1,7 @@
|
||||
package com.nanri.aiimage.modules.permission.mapper;
|
||||
package com.nanri.aiimage.common.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.nanri.aiimage.modules.permission.model.entity.AdminUserEntity;
|
||||
import com.nanri.aiimage.common.model.entity.AdminUserEntity;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@Mapper
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
package com.nanri.aiimage.modules.shopkey.mapper;
|
||||
package com.nanri.aiimage.common.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.nanri.aiimage.modules.shopkey.model.entity.ShopManageGroupEntity;
|
||||
import com.nanri.aiimage.common.model.entity.ShopManageGroupEntity;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.apache.ibatis.annotations.Select;
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package com.nanri.aiimage.modules.permission.model.entity;
|
||||
package com.nanri.aiimage.common.model.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package com.nanri.aiimage.modules.shopkey.model.entity;
|
||||
package com.nanri.aiimage.common.model.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
+6
-6
@@ -1,10 +1,10 @@
|
||||
package com.nanri.aiimage.modules.admin.support;
|
||||
package com.nanri.aiimage.common.security;
|
||||
|
||||
import com.nanri.aiimage.common.exception.BusinessException;
|
||||
import com.nanri.aiimage.modules.auth.service.JwtService;
|
||||
import com.nanri.aiimage.modules.auth.support.DeviceSessionPolicy;
|
||||
import com.nanri.aiimage.modules.permission.mapper.AdminUserMapper;
|
||||
import com.nanri.aiimage.modules.permission.model.entity.AdminUserEntity;
|
||||
import com.nanri.aiimage.common.security.JwtService;
|
||||
import com.nanri.aiimage.common.security.DeviceSessionPolicy;
|
||||
import com.nanri.aiimage.common.mapper.AdminUserMapper;
|
||||
import com.nanri.aiimage.common.model.entity.AdminUserEntity;
|
||||
import io.jsonwebtoken.Claims;
|
||||
import jakarta.servlet.http.Cookie;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
@@ -12,7 +12,7 @@ import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import com.nanri.aiimage.modules.auth.config.AuthProperties;
|
||||
import com.nanri.aiimage.common.security.AuthProperties;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package com.nanri.aiimage.modules.auth.config;
|
||||
package com.nanri.aiimage.common.security;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package com.nanri.aiimage.modules.auth.support;
|
||||
package com.nanri.aiimage.common.security;
|
||||
|
||||
import com.nanri.aiimage.common.exception.BusinessException;
|
||||
import io.jsonwebtoken.Claims;
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
package com.nanri.aiimage.modules.auth.service;
|
||||
package com.nanri.aiimage.common.security;
|
||||
|
||||
import com.nanri.aiimage.modules.auth.config.AuthProperties;
|
||||
import com.nanri.aiimage.common.security.AuthProperties;
|
||||
import io.jsonwebtoken.Claims;
|
||||
import io.jsonwebtoken.JwtException;
|
||||
import io.jsonwebtoken.Jwts;
|
||||
@@ -3,7 +3,7 @@ package com.nanri.aiimage.config;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.nanri.aiimage.common.api.ApiResponse;
|
||||
import com.nanri.aiimage.common.exception.BusinessException;
|
||||
import com.nanri.aiimage.modules.admin.support.AdminAuthSupport;
|
||||
import com.nanri.aiimage.common.security.AdminAuthSupport;
|
||||
import jakarta.servlet.FilterChain;
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
|
||||
+2
-2
@@ -1,8 +1,8 @@
|
||||
package com.nanri.aiimage.modules.admin.controller;
|
||||
|
||||
import com.nanri.aiimage.common.api.ApiResponse;
|
||||
import com.nanri.aiimage.modules.admin.support.AdminAuthSupport;
|
||||
import com.nanri.aiimage.modules.permission.model.entity.AdminUserEntity;
|
||||
import com.nanri.aiimage.common.security.AdminAuthSupport;
|
||||
import com.nanri.aiimage.common.model.entity.AdminUserEntity;
|
||||
import com.nanri.aiimage.modules.permission.model.vo.PermissionMenuItemVo;
|
||||
import com.nanri.aiimage.modules.permission.service.PermissionMenuService;
|
||||
import com.nanri.aiimage.modules.permission.service.support.AdminMenuTreeBuilder;
|
||||
|
||||
+2
-2
@@ -5,8 +5,8 @@ import com.nanri.aiimage.modules.admin.model.dto.AdminUserCreateRequest;
|
||||
import com.nanri.aiimage.modules.admin.model.dto.AdminUserUpdateRequest;
|
||||
import com.nanri.aiimage.modules.admin.model.vo.AdminUserListVo;
|
||||
import com.nanri.aiimage.modules.admin.service.AdminUserService;
|
||||
import com.nanri.aiimage.modules.admin.support.AdminAuthSupport;
|
||||
import com.nanri.aiimage.modules.permission.model.entity.AdminUserEntity;
|
||||
import com.nanri.aiimage.common.security.AdminAuthSupport;
|
||||
import com.nanri.aiimage.common.model.entity.AdminUserEntity;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
|
||||
+6
-6
@@ -8,14 +8,14 @@ import com.nanri.aiimage.modules.admin.model.dto.AdminUserUpdateRequest;
|
||||
import com.nanri.aiimage.modules.admin.model.vo.AdminBriefVo;
|
||||
import com.nanri.aiimage.modules.admin.model.vo.AdminUserItemVo;
|
||||
import com.nanri.aiimage.modules.admin.model.vo.AdminUserListVo;
|
||||
import com.nanri.aiimage.modules.admin.support.AdminAuthSupport;
|
||||
import com.nanri.aiimage.common.security.AdminAuthSupport;
|
||||
import com.nanri.aiimage.modules.admin.util.PinyinAbbrUtil;
|
||||
import com.nanri.aiimage.modules.auth.util.WerkzeugPasswordEncoder;
|
||||
import com.nanri.aiimage.modules.permission.mapper.AdminUserMapper;
|
||||
import com.nanri.aiimage.common.mapper.AdminUserMapper;
|
||||
import com.nanri.aiimage.modules.permission.model.dto.UserColumnPermissionUpdateRequest;
|
||||
import com.nanri.aiimage.modules.permission.model.entity.AdminUserEntity;
|
||||
import com.nanri.aiimage.common.model.entity.AdminUserEntity;
|
||||
import com.nanri.aiimage.modules.permission.service.PermissionMenuService;
|
||||
import com.nanri.aiimage.modules.usersecret.service.UserApiSecretService;
|
||||
import com.nanri.aiimage.modules.admin.spi.UserSecretCleanupPort;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.dao.DuplicateKeyException;
|
||||
@@ -43,7 +43,7 @@ public class AdminUserService {
|
||||
private final WerkzeugPasswordEncoder passwordEncoder;
|
||||
private final AdminAuthSupport adminAuthSupport;
|
||||
private final PermissionMenuService permissionMenuService;
|
||||
private final UserApiSecretService userApiSecretService;
|
||||
private final UserSecretCleanupPort userSecretCleanupPort;
|
||||
|
||||
public AdminUserListVo listUsers(AdminUserEntity currentUser, Integer page, Integer pageSize,
|
||||
String username, Long createdById, String roleFilter) {
|
||||
@@ -303,7 +303,7 @@ public class AdminUserService {
|
||||
if (affected == 0) {
|
||||
throw new BusinessException("用户不存在");
|
||||
}
|
||||
int secretRows = userApiSecretService.adminClearByUser(uid);
|
||||
int secretRows = userSecretCleanupPort.adminClearByUser(uid);
|
||||
log.info("[admin-user] 用户已删除 uid={} 级联清理密钥行={}", uid, secretRows);
|
||||
}
|
||||
|
||||
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
package com.nanri.aiimage.modules.admin.spi;
|
||||
|
||||
/**
|
||||
* 删除用户时级联清理其密钥数据(2026-09 边界收敛:admin → usersecret 的类依赖改为端口)。
|
||||
*
|
||||
* <p>实现方在 usersecret 模块;返回清理的行数用于日志。
|
||||
*/
|
||||
public interface UserSecretCleanupPort {
|
||||
|
||||
/** 清理该用户的全部密钥相关行,返回受影响行数。 */
|
||||
int adminClearByUser(Long userId);
|
||||
}
|
||||
@@ -3,12 +3,12 @@ package com.nanri.aiimage.modules.auth.service;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
||||
import com.nanri.aiimage.common.exception.BusinessException;
|
||||
import com.nanri.aiimage.modules.auth.config.AuthProperties;
|
||||
import com.nanri.aiimage.common.security.AuthProperties;
|
||||
import com.nanri.aiimage.modules.auth.mapper.LoginUserMapper;
|
||||
import com.nanri.aiimage.modules.auth.model.dto.LoginRequest;
|
||||
import com.nanri.aiimage.modules.auth.model.entity.LoginUserEntity;
|
||||
import com.nanri.aiimage.modules.auth.model.vo.LoginResultVo;
|
||||
import com.nanri.aiimage.modules.auth.support.DeviceSessionPolicy;
|
||||
import com.nanri.aiimage.common.security.DeviceSessionPolicy;
|
||||
import com.nanri.aiimage.modules.auth.util.WerkzeugPasswordEncoder;
|
||||
import com.nanri.aiimage.modules.permission.service.PermissionMenuService;
|
||||
import io.jsonwebtoken.Claims;
|
||||
@@ -21,6 +21,7 @@ import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import com.nanri.aiimage.common.security.JwtService;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
|
||||
+10
-2
@@ -7,8 +7,8 @@ import com.nanri.aiimage.modules.task.model.entity.TaskFileJobEntity;
|
||||
|
||||
/**
|
||||
* BRAND 结果文件 Job Handler(04 注册表)。
|
||||
* 注意:resultFileUrl 解析特例(resolveResultObjectKey,无 resultId 也走)
|
||||
* 保留在 Worker 公共路径 resolveResultFileUrl,Handler 不接管 URL 解析;
|
||||
* resultFileUrl 解析特例(resolveResultObjectKey,无 resultId 也走)由本 Handler 的
|
||||
* resolveResultFileUrl 钩子承担(2026-09:原实现在 Worker 内直接 import 本模块,构成 task → 业务依赖);
|
||||
* cleanup 为空(原 cleanupAfterSuccess 无 BRAND 分支)。
|
||||
*/
|
||||
public class BrandResultFileJobHandler implements ResultFileJobHandler {
|
||||
@@ -32,4 +32,12 @@ public class BrandResultFileJobHandler implements ResultFileJobHandler {
|
||||
brandTaskService.processResultFileJob(job);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String resolveResultFileUrl(TaskFileJobEntity job) {
|
||||
if (job == null || job.getTaskId() == null) {
|
||||
return null;
|
||||
}
|
||||
return brandTaskService.resolveResultObjectKey(job.getTaskId());
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -2,7 +2,7 @@ package com.nanri.aiimage.modules.dedupe.controller;
|
||||
|
||||
import com.nanri.aiimage.common.api.ApiResponse;
|
||||
import com.nanri.aiimage.common.exception.BusinessException;
|
||||
import com.nanri.aiimage.modules.admin.support.AdminAuthSupport;
|
||||
import com.nanri.aiimage.common.security.AdminAuthSupport;
|
||||
import com.nanri.aiimage.common.util.DownloadHeaderUtil;
|
||||
import com.nanri.aiimage.modules.dedupe.model.dto.DedupeTotalDataCreateRequest;
|
||||
import com.nanri.aiimage.modules.dedupe.model.dto.DedupeTotalDataUpdateRequest;
|
||||
@@ -12,7 +12,7 @@ import com.nanri.aiimage.modules.dedupe.model.vo.DedupeTotalDataImportVo;
|
||||
import com.nanri.aiimage.modules.dedupe.model.vo.DedupeTotalDataItemVo;
|
||||
import com.nanri.aiimage.modules.dedupe.model.vo.DedupeTotalDataPageVo;
|
||||
import com.nanri.aiimage.modules.dedupe.service.DedupeTotalDataService;
|
||||
import com.nanri.aiimage.modules.permission.model.entity.AdminUserEntity;
|
||||
import com.nanri.aiimage.common.model.entity.AdminUserEntity;
|
||||
import com.nanri.aiimage.modules.permission.service.PermissionMenuService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
|
||||
+4
-4
@@ -15,10 +15,10 @@ import com.nanri.aiimage.modules.dedupe.model.vo.DedupeTotalDataImportStartVo;
|
||||
import com.nanri.aiimage.modules.dedupe.model.vo.DedupeTotalDataImportVo;
|
||||
import com.nanri.aiimage.modules.dedupe.model.vo.DedupeTotalDataItemVo;
|
||||
import com.nanri.aiimage.modules.dedupe.model.vo.DedupeTotalDataPageVo;
|
||||
import com.nanri.aiimage.modules.permission.mapper.AdminUserMapper;
|
||||
import com.nanri.aiimage.modules.permission.model.entity.AdminUserEntity;
|
||||
import com.nanri.aiimage.modules.shopkey.mapper.ShopManageGroupMapper;
|
||||
import com.nanri.aiimage.modules.shopkey.model.entity.ShopManageGroupEntity;
|
||||
import com.nanri.aiimage.common.mapper.AdminUserMapper;
|
||||
import com.nanri.aiimage.common.model.entity.AdminUserEntity;
|
||||
import com.nanri.aiimage.common.mapper.ShopManageGroupMapper;
|
||||
import com.nanri.aiimage.common.model.entity.ShopManageGroupEntity;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.poi.ss.usermodel.Cell;
|
||||
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
package com.nanri.aiimage.modules.dedupe.service;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.nanri.aiimage.common.exception.BusinessException;
|
||||
import com.nanri.aiimage.modules.dedupe.mapper.DedupeTotalDataMapper;
|
||||
import com.nanri.aiimage.modules.dedupe.model.entity.DedupeTotalDataEntity;
|
||||
import com.nanri.aiimage.modules.shopkey.spi.GroupDeletionGuardPort;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* 分组删除守卫:去重总数据(2026-09 从 shopkey 收回本模块,文案保持不变)。
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class GroupDeletionGuard implements GroupDeletionGuardPort {
|
||||
|
||||
private final DedupeTotalDataMapper dedupeTotalDataMapper;
|
||||
|
||||
@Override
|
||||
public void ensureNoGroupData(Long groupId) {
|
||||
Long count = dedupeTotalDataMapper.selectCount(new LambdaQueryWrapper<DedupeTotalDataEntity>()
|
||||
.eq(DedupeTotalDataEntity::getGroupId, groupId));
|
||||
if (count != null && count > 0) {
|
||||
throw new BusinessException("该分组下存在数据去重总数据,无法删除");
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
package com.nanri.aiimage.modules.digitalhuman.controller;
|
||||
|
||||
import com.nanri.aiimage.common.api.ApiResponse;
|
||||
import com.nanri.aiimage.modules.admin.support.AdminAuthSupport;
|
||||
import com.nanri.aiimage.common.security.AdminAuthSupport;
|
||||
import com.nanri.aiimage.modules.digitalhuman.model.dto.UploadVersionRequest;
|
||||
import com.nanri.aiimage.modules.digitalhuman.model.vo.DigitalHumanVersionVo;
|
||||
import com.nanri.aiimage.modules.digitalhuman.service.DigitalHumanVersionService;
|
||||
|
||||
+2
-2
@@ -2,10 +2,10 @@ package com.nanri.aiimage.modules.imagehistory.controller;
|
||||
|
||||
import com.nanri.aiimage.common.api.ApiResponse;
|
||||
import com.nanri.aiimage.common.exception.BusinessException;
|
||||
import com.nanri.aiimage.modules.admin.support.AdminAuthSupport;
|
||||
import com.nanri.aiimage.common.security.AdminAuthSupport;
|
||||
import com.nanri.aiimage.modules.imagehistory.model.vo.ImageHistoryListVo;
|
||||
import com.nanri.aiimage.modules.imagehistory.service.ImageHistoryService;
|
||||
import com.nanri.aiimage.modules.permission.model.entity.AdminUserEntity;
|
||||
import com.nanri.aiimage.common.model.entity.AdminUserEntity;
|
||||
import com.nanri.aiimage.modules.permission.service.PermissionMenuService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
|
||||
+2
-2
@@ -8,8 +8,8 @@ import com.nanri.aiimage.modules.imagehistory.mapper.ImageHistoryMapper;
|
||||
import com.nanri.aiimage.modules.imagehistory.model.entity.ImageHistoryEntity;
|
||||
import com.nanri.aiimage.modules.imagehistory.model.vo.ImageHistoryItemVo;
|
||||
import com.nanri.aiimage.modules.imagehistory.model.vo.ImageHistoryListVo;
|
||||
import com.nanri.aiimage.modules.permission.mapper.AdminUserMapper;
|
||||
import com.nanri.aiimage.modules.permission.model.entity.AdminUserEntity;
|
||||
import com.nanri.aiimage.common.mapper.AdminUserMapper;
|
||||
import com.nanri.aiimage.common.model.entity.AdminUserEntity;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@ package com.nanri.aiimage.modules.imagevideo.controller;
|
||||
import com.nanri.aiimage.common.api.ApiResponse;
|
||||
import com.nanri.aiimage.common.exception.BusinessException;
|
||||
import com.nanri.aiimage.common.util.DownloadHeaderUtil;
|
||||
import com.nanri.aiimage.modules.admin.support.AdminAuthSupport;
|
||||
import com.nanri.aiimage.common.security.AdminAuthSupport;
|
||||
import com.nanri.aiimage.modules.imagevideo.service.ImageVideoAdminTaskService;
|
||||
import com.nanri.aiimage.modules.imagevideo.service.ImageVideoAdminTaskService.VideoSelection;
|
||||
import com.nanri.aiimage.modules.imagevideo.service.ImageVideoAdminTaskService.ZipBundle;
|
||||
|
||||
+2
-2
@@ -2,8 +2,8 @@ package com.nanri.aiimage.modules.imagevideo.controller;
|
||||
|
||||
import com.nanri.aiimage.common.api.ApiResponse;
|
||||
import com.nanri.aiimage.common.exception.BusinessException;
|
||||
import com.nanri.aiimage.modules.permission.model.entity.AdminUserEntity;
|
||||
import com.nanri.aiimage.modules.admin.support.AdminAuthSupport;
|
||||
import com.nanri.aiimage.common.model.entity.AdminUserEntity;
|
||||
import com.nanri.aiimage.common.security.AdminAuthSupport;
|
||||
import com.nanri.aiimage.modules.imagevideo.model.dto.DouyinCopyRequest;
|
||||
import com.nanri.aiimage.modules.imagevideo.model.dto.ImageVideoSecretSaveRequest;
|
||||
import com.nanri.aiimage.modules.imagevideo.model.dto.ImageVideoVoiceCloneRequest;
|
||||
|
||||
+2
-2
@@ -2,13 +2,13 @@ package com.nanri.aiimage.modules.invalidasin.controller;
|
||||
|
||||
import com.nanri.aiimage.common.api.ApiResponse;
|
||||
import com.nanri.aiimage.common.exception.BusinessException;
|
||||
import com.nanri.aiimage.modules.admin.support.AdminAuthSupport;
|
||||
import com.nanri.aiimage.common.security.AdminAuthSupport;
|
||||
import com.nanri.aiimage.modules.invalidasin.model.dto.InvalidAsinDataCreateRequest;
|
||||
import com.nanri.aiimage.modules.invalidasin.model.dto.InvalidAsinDataUpdateRequest;
|
||||
import com.nanri.aiimage.modules.invalidasin.model.vo.InvalidAsinDataItemVo;
|
||||
import com.nanri.aiimage.modules.invalidasin.model.vo.InvalidAsinDataPageVo;
|
||||
import com.nanri.aiimage.modules.invalidasin.service.InvalidAsinDataService;
|
||||
import com.nanri.aiimage.modules.permission.model.entity.AdminUserEntity;
|
||||
import com.nanri.aiimage.common.model.entity.AdminUserEntity;
|
||||
import com.nanri.aiimage.modules.permission.service.PermissionMenuService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
package com.nanri.aiimage.modules.invalidasin.service;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.nanri.aiimage.common.exception.BusinessException;
|
||||
import com.nanri.aiimage.modules.invalidasin.mapper.InvalidAsinDataMapper;
|
||||
import com.nanri.aiimage.modules.invalidasin.model.entity.InvalidAsinDataEntity;
|
||||
import com.nanri.aiimage.modules.shopkey.spi.GroupDeletionGuardPort;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* 分组删除守卫:品牌数据库(不符合 ASIN)数据(2026-09 从 shopkey 收回本模块,文案保持不变)。
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class GroupDeletionGuard implements GroupDeletionGuardPort {
|
||||
|
||||
private final InvalidAsinDataMapper invalidAsinDataMapper;
|
||||
|
||||
@Override
|
||||
public void ensureNoGroupData(Long groupId) {
|
||||
Long count = invalidAsinDataMapper.selectCount(new LambdaQueryWrapper<InvalidAsinDataEntity>()
|
||||
.eq(InvalidAsinDataEntity::getGroupId, groupId));
|
||||
if (count != null && count > 0) {
|
||||
throw new BusinessException("该分组下存在品牌数据库数据,无法删除");
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -8,7 +8,7 @@ import com.nanri.aiimage.modules.invalidasin.model.dto.InvalidAsinDataUpdateRequ
|
||||
import com.nanri.aiimage.modules.invalidasin.model.entity.InvalidAsinDataEntity;
|
||||
import com.nanri.aiimage.modules.invalidasin.model.vo.InvalidAsinDataItemVo;
|
||||
import com.nanri.aiimage.modules.invalidasin.model.vo.InvalidAsinDataPageVo;
|
||||
import com.nanri.aiimage.modules.shopkey.model.entity.ShopManageGroupEntity;
|
||||
import com.nanri.aiimage.common.model.entity.ShopManageGroupEntity;
|
||||
import com.nanri.aiimage.modules.shopkey.service.ShopManageGroupService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.http.HttpStatus;
|
||||
|
||||
+2
-2
@@ -1,12 +1,12 @@
|
||||
package com.nanri.aiimage.modules.notification.controller;
|
||||
|
||||
import com.nanri.aiimage.common.api.ApiResponse;
|
||||
import com.nanri.aiimage.modules.admin.support.AdminAuthSupport;
|
||||
import com.nanri.aiimage.common.security.AdminAuthSupport;
|
||||
import com.nanri.aiimage.modules.notification.model.dto.NotificationPageQuery;
|
||||
import com.nanri.aiimage.modules.notification.model.vo.NotificationPageVo;
|
||||
import com.nanri.aiimage.modules.notification.model.vo.NotificationSummaryVo;
|
||||
import com.nanri.aiimage.modules.notification.service.NotificationService;
|
||||
import com.nanri.aiimage.modules.permission.model.entity.AdminUserEntity;
|
||||
import com.nanri.aiimage.common.model.entity.AdminUserEntity;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
|
||||
+2
-2
@@ -1,12 +1,12 @@
|
||||
package com.nanri.aiimage.modules.notification.controller;
|
||||
|
||||
import com.nanri.aiimage.common.api.ApiResponse;
|
||||
import com.nanri.aiimage.modules.admin.support.AdminAuthSupport;
|
||||
import com.nanri.aiimage.common.security.AdminAuthSupport;
|
||||
import com.nanri.aiimage.modules.notification.model.dto.NotificationPageQuery;
|
||||
import com.nanri.aiimage.modules.notification.model.vo.NotificationPageVo;
|
||||
import com.nanri.aiimage.modules.notification.model.vo.NotificationSummaryVo;
|
||||
import com.nanri.aiimage.modules.notification.service.NotificationService;
|
||||
import com.nanri.aiimage.modules.permission.model.entity.AdminUserEntity;
|
||||
import com.nanri.aiimage.common.model.entity.AdminUserEntity;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
|
||||
+3
-3
@@ -1,9 +1,9 @@
|
||||
package com.nanri.aiimage.modules.notification.service;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.nanri.aiimage.modules.admin.support.AdminAuthSupport;
|
||||
import com.nanri.aiimage.modules.permission.mapper.AdminUserMapper;
|
||||
import com.nanri.aiimage.modules.permission.model.entity.AdminUserEntity;
|
||||
import com.nanri.aiimage.common.security.AdminAuthSupport;
|
||||
import com.nanri.aiimage.common.mapper.AdminUserMapper;
|
||||
import com.nanri.aiimage.common.model.entity.AdminUserEntity;
|
||||
import com.nanri.aiimage.modules.permission.support.UserDataScopeSupport;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
+7
-8
@@ -9,8 +9,7 @@ import com.nanri.aiimage.modules.brand.mapper.BrandCrawlTaskMapper;
|
||||
import com.nanri.aiimage.modules.brand.model.entity.BrandCrawlTaskEntity;
|
||||
import com.nanri.aiimage.modules.task.mapper.FileTaskMapper;
|
||||
import com.nanri.aiimage.modules.task.model.entity.FileTaskEntity;
|
||||
import com.nanri.aiimage.modules.usersecret.client.JikipProxyClient;
|
||||
import com.nanri.aiimage.modules.usersecret.model.vo.UserApiSecretBalanceVo;
|
||||
import com.nanri.aiimage.modules.notification.spi.ProxyBalancePort;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
@@ -67,7 +66,7 @@ public class NotificationScanScheduler {
|
||||
private final NotificationDispatchService notificationDispatchService;
|
||||
private final DistributedJobLockService distributedJobLockService;
|
||||
private final NotificationProperties properties;
|
||||
private final JikipProxyClient jikipProxyClient;
|
||||
private final ProxyBalancePort proxyBalancePort;
|
||||
|
||||
/** 上次清理日期(每天最多清理一次;双节点由分布式锁保证只有一个实例执行)。 */
|
||||
private volatile LocalDate lastCleanupDate;
|
||||
@@ -234,12 +233,12 @@ public class NotificationScanScheduler {
|
||||
/** jikip 探测:复用余额查询接口;未配置套餐信息时跳过(返回 null 不告警)。 */
|
||||
private ProbeOutcome probeJikip() {
|
||||
try {
|
||||
UserApiSecretBalanceVo balance = jikipProxyClient.fetchBalance();
|
||||
if (Boolean.TRUE.equals(balance.getAvailable())) {
|
||||
return new ProbeOutcome(true, "余额接口可用 surplus=" + balance.getSurplus()
|
||||
+ " balance=" + balance.getBalance());
|
||||
ProxyBalancePort.ProxyBalanceSnapshot balance = proxyBalancePort.fetchBalance();
|
||||
if (balance.available()) {
|
||||
return new ProbeOutcome(true, "余额接口可用 surplus=" + balance.surplus()
|
||||
+ " balance=" + balance.balance());
|
||||
}
|
||||
String message = balance.getMessage() == null ? "" : balance.getMessage();
|
||||
String message = balance.message() == null ? "" : balance.message();
|
||||
if (message.contains("未配置")) {
|
||||
log.info("[notification-scan] jikip 探测跳过:{}", message);
|
||||
return null;
|
||||
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
package com.nanri.aiimage.modules.notification.spi;
|
||||
|
||||
/**
|
||||
* 代理余额探测(2026-09 边界收敛:notification → usersecret 的类依赖改为端口)。
|
||||
*
|
||||
* <p>实现方在 usersecret 模块(复用其 jikip 客户端);通知巡检只做"能不能告警"的判断,
|
||||
* 不关心上游返回结构,因此这里只暴露最小快照。
|
||||
*/
|
||||
public interface ProxyBalancePort {
|
||||
|
||||
/**
|
||||
* 查询代理服务商余额。
|
||||
*
|
||||
* @return 余额快照;调用异常由实现方抛出(调用方负责降级)
|
||||
*/
|
||||
ProxyBalanceSnapshot fetchBalance();
|
||||
|
||||
/**
|
||||
* @param available 余额接口是否可用
|
||||
* @param surplus 剩余额度(上游口径,可能为 null)
|
||||
* @param balance 账户余额(上游口径,可能为 null)
|
||||
* @param message 上游提示信息(未配置套餐时含"未配置"字样,调用方据此跳过告警)
|
||||
*/
|
||||
record ProxyBalanceSnapshot(boolean available, String surplus, String balance, String message) {
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -1,12 +1,12 @@
|
||||
package com.nanri.aiimage.modules.permission.controller;
|
||||
|
||||
import com.nanri.aiimage.common.api.ApiResponse;
|
||||
import com.nanri.aiimage.modules.admin.support.AdminAuthSupport;
|
||||
import com.nanri.aiimage.common.security.AdminAuthSupport;
|
||||
import com.nanri.aiimage.modules.permission.model.dto.PermissionMenuCreateRequest;
|
||||
import com.nanri.aiimage.modules.permission.model.dto.PermissionMenuUpdateRequest;
|
||||
import com.nanri.aiimage.modules.permission.model.dto.ImageVideoDataPermissionUpdateRequest;
|
||||
import com.nanri.aiimage.modules.permission.model.dto.UserColumnPermissionUpdateRequest;
|
||||
import com.nanri.aiimage.modules.permission.model.entity.AdminUserEntity;
|
||||
import com.nanri.aiimage.common.model.entity.AdminUserEntity;
|
||||
import com.nanri.aiimage.modules.permission.model.vo.PermissionMenuItemVo;
|
||||
import com.nanri.aiimage.modules.permission.model.vo.UserColumnIdsVo;
|
||||
import com.nanri.aiimage.modules.permission.model.vo.ImageVideoDataPermissionUserVo;
|
||||
|
||||
+2
-2
@@ -3,13 +3,13 @@ package com.nanri.aiimage.modules.permission.service;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
||||
import com.nanri.aiimage.common.exception.BusinessException;
|
||||
import com.nanri.aiimage.modules.permission.mapper.AdminUserMapper;
|
||||
import com.nanri.aiimage.common.mapper.AdminUserMapper;
|
||||
import com.nanri.aiimage.modules.permission.mapper.PermissionMenuMapper;
|
||||
import com.nanri.aiimage.modules.permission.mapper.UserColumnPermissionMapper;
|
||||
import com.nanri.aiimage.modules.permission.model.dto.PermissionMenuCreateRequest;
|
||||
import com.nanri.aiimage.modules.permission.model.dto.PermissionMenuUpdateRequest;
|
||||
import com.nanri.aiimage.modules.permission.model.dto.UserColumnPermissionUpdateRequest;
|
||||
import com.nanri.aiimage.modules.permission.model.entity.AdminUserEntity;
|
||||
import com.nanri.aiimage.common.model.entity.AdminUserEntity;
|
||||
import com.nanri.aiimage.modules.permission.model.entity.PermissionMenuEntity;
|
||||
import com.nanri.aiimage.modules.permission.model.entity.UserColumnPermissionEntity;
|
||||
import com.nanri.aiimage.modules.permission.model.vo.ImageVideoDataPermissionUserVo;
|
||||
|
||||
+4
-4
@@ -1,10 +1,10 @@
|
||||
package com.nanri.aiimage.modules.permission.support;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.nanri.aiimage.modules.permission.mapper.AdminUserMapper;
|
||||
import com.nanri.aiimage.modules.permission.model.entity.AdminUserEntity;
|
||||
import com.nanri.aiimage.modules.shopkey.mapper.ShopManageGroupMapper;
|
||||
import com.nanri.aiimage.modules.shopkey.model.entity.ShopManageGroupEntity;
|
||||
import com.nanri.aiimage.common.mapper.AdminUserMapper;
|
||||
import com.nanri.aiimage.common.model.entity.AdminUserEntity;
|
||||
import com.nanri.aiimage.common.mapper.ShopManageGroupMapper;
|
||||
import com.nanri.aiimage.common.model.entity.ShopManageGroupEntity;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@ package com.nanri.aiimage.modules.productcategory.controller;
|
||||
|
||||
import com.nanri.aiimage.common.api.ApiResponse;
|
||||
import com.nanri.aiimage.common.util.DownloadHeaderUtil;
|
||||
import com.nanri.aiimage.modules.admin.support.AdminAuthSupport;
|
||||
import com.nanri.aiimage.common.security.AdminAuthSupport;
|
||||
import com.nanri.aiimage.modules.productcategory.model.dto.ProductCategorySaveRequest;
|
||||
import com.nanri.aiimage.modules.productcategory.model.vo.ProductCategoryItemVo;
|
||||
import com.nanri.aiimage.modules.productcategory.model.vo.ProductCategoryListVo;
|
||||
|
||||
+2
-2
@@ -4,8 +4,8 @@ import com.nanri.aiimage.common.api.ApiResponse;
|
||||
import com.nanri.aiimage.common.exception.BusinessException;
|
||||
import com.nanri.aiimage.common.util.DownloadHeaderUtil;
|
||||
import com.nanri.aiimage.config.HttpClientPool;
|
||||
import com.nanri.aiimage.modules.admin.support.AdminAuthSupport;
|
||||
import com.nanri.aiimage.modules.permission.model.entity.AdminUserEntity;
|
||||
import com.nanri.aiimage.common.security.AdminAuthSupport;
|
||||
import com.nanri.aiimage.common.model.entity.AdminUserEntity;
|
||||
import com.nanri.aiimage.modules.permission.service.PermissionMenuService;
|
||||
import com.nanri.aiimage.modules.shopdatacrawl.service.ShopDataCrawlTaskService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
|
||||
+2
-2
@@ -4,8 +4,8 @@ import com.nanri.aiimage.common.api.ApiResponse;
|
||||
import com.nanri.aiimage.common.exception.BusinessException;
|
||||
import com.nanri.aiimage.common.util.DownloadHeaderUtil;
|
||||
import com.nanri.aiimage.config.HttpClientPool;
|
||||
import com.nanri.aiimage.modules.admin.support.AdminAuthSupport;
|
||||
import com.nanri.aiimage.modules.permission.model.entity.AdminUserEntity;
|
||||
import com.nanri.aiimage.common.security.AdminAuthSupport;
|
||||
import com.nanri.aiimage.common.model.entity.AdminUserEntity;
|
||||
import com.nanri.aiimage.modules.permission.service.PermissionMenuService;
|
||||
import com.nanri.aiimage.modules.shopdatacrawl.model.dto.ShopDataCrawlDownloadRowDto;
|
||||
import com.nanri.aiimage.modules.shopdatacrawl.service.ShopDataCrawlAdminTasksService;
|
||||
|
||||
+3
-3
@@ -12,7 +12,7 @@ import com.nanri.aiimage.modules.shopdatacrawl.model.dto.ShopDataCrawlAdminRow;
|
||||
import com.nanri.aiimage.modules.shopdatacrawl.model.dto.ShopDataCrawlDownloadRowDto;
|
||||
import com.nanri.aiimage.modules.shopdatacrawl.model.entity.ShopDataCrawlDailyFileEntity;
|
||||
import com.nanri.aiimage.modules.shopdatacrawl.model.entity.ShopDataCrawlDailyMemberEntity;
|
||||
import com.nanri.aiimage.modules.shopduplicatecheck.mapper.ShopDuplicateCheckSourceMapper;
|
||||
import com.nanri.aiimage.modules.shopdatacrawl.spi.ManagedShopNamesPort;
|
||||
import com.nanri.aiimage.modules.task.mapper.FileResultMapper;
|
||||
import com.nanri.aiimage.modules.task.model.entity.FileResultEntity;
|
||||
import com.nanri.aiimage.modules.task.service.TaskDistributedLockService;
|
||||
@@ -47,7 +47,7 @@ public class ShopDataCrawlAdminTasksService {
|
||||
private static final Set<String> SUPPORTED_COUNTRIES = Set.of("DE", "UK", "FR", "IT", "ES");
|
||||
|
||||
private final ShopDataCrawlAdminTasksMapper adminTasksMapper;
|
||||
private final ShopDuplicateCheckSourceMapper duplicateCheckSourceMapper;
|
||||
private final ManagedShopNamesPort managedShopNamesPort;
|
||||
private final ShopDataCrawlItemMapper itemMapper;
|
||||
private final FileResultMapper fileResultMapper;
|
||||
private final ShopDataCrawlDailyFileService dailyFileService;
|
||||
@@ -215,7 +215,7 @@ public class ShopDataCrawlAdminTasksService {
|
||||
|
||||
/** 非超管可管店铺名(trim+小写去重,与撞款可见口径一致);超管调用方传 null 表示全量。 */
|
||||
public List<String> resolveVisibleShopNameKeys(Long operatorId) {
|
||||
List<String> names = duplicateCheckSourceMapper.selectManagedShopNames(operatorId);
|
||||
List<String> names = managedShopNamesPort.selectManagedShopNames(operatorId);
|
||||
if (names == null || names.isEmpty()) {
|
||||
return List.of();
|
||||
}
|
||||
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
package com.nanri.aiimage.modules.shopdatacrawl.spi;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 可管店铺名查询(2026-09 边界收敛:shopdatacrawl → shopduplicatecheck 的 Mapper 直连改为端口)。
|
||||
*
|
||||
* <p>实现方在 shopduplicatecheck 模块(口径与撞款可见范围一致)。
|
||||
*/
|
||||
public interface ManagedShopNamesPort {
|
||||
|
||||
/** 该操作人可管的店铺名;传 null 表示全量(超管调用方)。 */
|
||||
List<String> selectManagedShopNames(Long operatorId);
|
||||
}
|
||||
+2
-2
@@ -3,8 +3,8 @@ package com.nanri.aiimage.modules.shopduplicatecheck.controller;
|
||||
import com.nanri.aiimage.common.api.ApiResponse;
|
||||
import com.nanri.aiimage.common.exception.BusinessException;
|
||||
import com.nanri.aiimage.common.util.DownloadHeaderUtil;
|
||||
import com.nanri.aiimage.modules.admin.support.AdminAuthSupport;
|
||||
import com.nanri.aiimage.modules.permission.model.entity.AdminUserEntity;
|
||||
import com.nanri.aiimage.common.security.AdminAuthSupport;
|
||||
import com.nanri.aiimage.common.model.entity.AdminUserEntity;
|
||||
import com.nanri.aiimage.modules.permission.service.PermissionMenuService;
|
||||
import com.nanri.aiimage.modules.shopduplicatecheck.service.ShopDataDuplicateCheckImportService;
|
||||
import com.nanri.aiimage.modules.shopduplicatecheck.service.ShopDataDuplicateCheckQueryService;
|
||||
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
package com.nanri.aiimage.modules.shopduplicatecheck.service;
|
||||
|
||||
import com.nanri.aiimage.modules.shopdatacrawl.spi.ManagedShopNamesPort;
|
||||
import com.nanri.aiimage.modules.shopduplicatecheck.mapper.ShopDuplicateCheckSourceMapper;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 可管店铺名端口实现(2026-09 边界收敛):Mapper 与口径留在 shopduplicatecheck 模块内。
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class ManagedShopNamesPortAdapter implements ManagedShopNamesPort {
|
||||
|
||||
private final ShopDuplicateCheckSourceMapper sourceMapper;
|
||||
|
||||
@Override
|
||||
public List<String> selectManagedShopNames(Long operatorId) {
|
||||
return sourceMapper.selectManagedShopNames(operatorId);
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -2,8 +2,8 @@ package com.nanri.aiimage.modules.shopkey.controller;
|
||||
|
||||
import com.nanri.aiimage.common.api.ApiResponse;
|
||||
import com.nanri.aiimage.common.util.DownloadHeaderUtil;
|
||||
import com.nanri.aiimage.modules.admin.support.AdminAuthSupport;
|
||||
import com.nanri.aiimage.modules.permission.model.entity.AdminUserEntity;
|
||||
import com.nanri.aiimage.common.security.AdminAuthSupport;
|
||||
import com.nanri.aiimage.common.model.entity.AdminUserEntity;
|
||||
import com.nanri.aiimage.modules.shopkey.model.dto.QueryAsinCountryUpdateRequest;
|
||||
import com.nanri.aiimage.modules.shopkey.model.dto.QueryAsinCreateRequest;
|
||||
import com.nanri.aiimage.modules.shopkey.model.vo.QueryAsinImportProgressVo;
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
package com.nanri.aiimage.modules.shopkey.controller;
|
||||
|
||||
import com.nanri.aiimage.common.api.ApiResponse;
|
||||
import com.nanri.aiimage.modules.admin.support.AdminAuthSupport;
|
||||
import com.nanri.aiimage.common.security.AdminAuthSupport;
|
||||
import com.nanri.aiimage.modules.shopkey.model.dto.ShopKeyCreateRequest;
|
||||
import com.nanri.aiimage.modules.shopkey.model.dto.ShopKeyUpdateRequest;
|
||||
import com.nanri.aiimage.modules.shopkey.model.vo.ShopKeyItemVo;
|
||||
|
||||
+2
-2
@@ -1,8 +1,8 @@
|
||||
package com.nanri.aiimage.modules.shopkey.controller;
|
||||
|
||||
import com.nanri.aiimage.common.api.ApiResponse;
|
||||
import com.nanri.aiimage.modules.admin.support.AdminAuthSupport;
|
||||
import com.nanri.aiimage.modules.permission.model.entity.AdminUserEntity;
|
||||
import com.nanri.aiimage.common.security.AdminAuthSupport;
|
||||
import com.nanri.aiimage.common.model.entity.AdminUserEntity;
|
||||
import com.nanri.aiimage.modules.shopkey.model.dto.ShopManageCreateRequest;
|
||||
import com.nanri.aiimage.modules.shopkey.model.dto.ShopManageGroupCreateRequest;
|
||||
import com.nanri.aiimage.modules.shopkey.model.dto.ShopManageGroupUpdateRequest;
|
||||
|
||||
+2
-2
@@ -2,8 +2,8 @@ package com.nanri.aiimage.modules.shopkey.controller;
|
||||
|
||||
import com.nanri.aiimage.common.api.ApiResponse;
|
||||
import com.nanri.aiimage.common.util.DownloadHeaderUtil;
|
||||
import com.nanri.aiimage.modules.admin.support.AdminAuthSupport;
|
||||
import com.nanri.aiimage.modules.permission.model.entity.AdminUserEntity;
|
||||
import com.nanri.aiimage.common.security.AdminAuthSupport;
|
||||
import com.nanri.aiimage.common.model.entity.AdminUserEntity;
|
||||
import com.nanri.aiimage.modules.shopkey.model.dto.SkipPriceAsinCountryUpdateRequest;
|
||||
import com.nanri.aiimage.modules.shopkey.model.dto.SkipPriceAsinCreateRequest;
|
||||
import com.nanri.aiimage.modules.shopkey.model.vo.QueryAsinImportProgressVo;
|
||||
|
||||
+1
-1
@@ -11,7 +11,7 @@ import com.nanri.aiimage.config.TaskPressureProperties;
|
||||
import com.nanri.aiimage.modules.shopkey.mapper.QueryAsinMapper;
|
||||
import com.nanri.aiimage.modules.shopkey.model.dto.QueryAsinCreateRequest;
|
||||
import com.nanri.aiimage.modules.shopkey.model.entity.QueryAsinEntity;
|
||||
import com.nanri.aiimage.modules.shopkey.model.entity.ShopManageGroupEntity;
|
||||
import com.nanri.aiimage.common.model.entity.ShopManageGroupEntity;
|
||||
import com.nanri.aiimage.modules.shopkey.model.vo.QueryAsinImportProgressVo;
|
||||
import com.nanri.aiimage.modules.shopkey.model.vo.QueryAsinImportStartVo;
|
||||
import com.nanri.aiimage.modules.shopkey.model.vo.QueryAsinItemVo;
|
||||
|
||||
+10
-19
@@ -2,24 +2,21 @@ 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.permission.mapper.AdminUserMapper;
|
||||
import com.nanri.aiimage.modules.permission.model.entity.AdminUserEntity;
|
||||
import com.nanri.aiimage.common.mapper.AdminUserMapper;
|
||||
import com.nanri.aiimage.common.model.entity.AdminUserEntity;
|
||||
import com.nanri.aiimage.modules.shopkey.mapper.QueryAsinMapper;
|
||||
import com.nanri.aiimage.modules.shopkey.mapper.ShopManageGroupMapper;
|
||||
import com.nanri.aiimage.modules.shopkey.spi.GroupDeletionGuardPort;
|
||||
import com.nanri.aiimage.common.mapper.ShopManageGroupMapper;
|
||||
import com.nanri.aiimage.modules.shopkey.mapper.ShopManageGroupMemberMapper;
|
||||
import com.nanri.aiimage.modules.shopkey.mapper.ShopManageMapper;
|
||||
import com.nanri.aiimage.modules.shopkey.mapper.SkipPriceAsinMapper;
|
||||
import com.nanri.aiimage.modules.invalidasin.mapper.InvalidAsinDataMapper;
|
||||
import com.nanri.aiimage.modules.dedupe.mapper.DedupeTotalDataMapper;
|
||||
import com.nanri.aiimage.modules.shopkey.model.dto.ShopManageGroupCreateRequest;
|
||||
import com.nanri.aiimage.modules.shopkey.model.dto.ShopManageGroupUpdateRequest;
|
||||
import com.nanri.aiimage.modules.shopkey.model.entity.QueryAsinEntity;
|
||||
import com.nanri.aiimage.modules.shopkey.model.entity.ShopManageEntity;
|
||||
import com.nanri.aiimage.modules.shopkey.model.entity.ShopManageGroupEntity;
|
||||
import com.nanri.aiimage.common.model.entity.ShopManageGroupEntity;
|
||||
import com.nanri.aiimage.modules.shopkey.model.entity.ShopManageGroupMemberEntity;
|
||||
import com.nanri.aiimage.modules.shopkey.model.entity.SkipPriceAsinEntity;
|
||||
import com.nanri.aiimage.modules.invalidasin.model.entity.InvalidAsinDataEntity;
|
||||
import com.nanri.aiimage.modules.dedupe.model.entity.DedupeTotalDataEntity;
|
||||
import com.nanri.aiimage.modules.shopkey.model.vo.ShopManageGroupItemVo;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
@@ -40,11 +37,11 @@ public class ShopManageGroupService {
|
||||
|
||||
private final ShopManageGroupMapper groupMapper;
|
||||
private final ShopManageGroupMemberMapper groupMemberMapper;
|
||||
/** 各业务模块的分组删除守卫(Spring 注入全部实现) */
|
||||
private final java.util.List<GroupDeletionGuardPort> groupDeletionGuards;
|
||||
private final ShopManageMapper shopManageMapper;
|
||||
private final QueryAsinMapper queryAsinMapper;
|
||||
private final SkipPriceAsinMapper skipPriceAsinMapper;
|
||||
private final InvalidAsinDataMapper invalidAsinDataMapper;
|
||||
private final DedupeTotalDataMapper dedupeTotalDataMapper;
|
||||
private final AdminUserMapper adminUserMapper;
|
||||
|
||||
public List<ShopManageGroupItemVo> list() {
|
||||
@@ -166,15 +163,9 @@ public class ShopManageGroupService {
|
||||
if (queryAsinCount != null && queryAsinCount > 0) {
|
||||
throw new BusinessException("该分组下存在查询 ASIN,无法删除");
|
||||
}
|
||||
Long invalidAsinDataCount = invalidAsinDataMapper.selectCount(new LambdaQueryWrapper<InvalidAsinDataEntity>()
|
||||
.eq(InvalidAsinDataEntity::getGroupId, entity.getId()));
|
||||
if (invalidAsinDataCount != null && invalidAsinDataCount > 0) {
|
||||
throw new BusinessException("该分组下存在品牌数据库数据,无法删除");
|
||||
}
|
||||
Long dedupeTotalDataCount = dedupeTotalDataMapper.selectCount(new LambdaQueryWrapper<DedupeTotalDataEntity>()
|
||||
.eq(DedupeTotalDataEntity::getGroupId, entity.getId()));
|
||||
if (dedupeTotalDataCount != null && dedupeTotalDataCount > 0) {
|
||||
throw new BusinessException("该分组下存在数据去重总数据,无法删除");
|
||||
// 其它模块的数据残留由各模块自己实现的守卫校验(2026-09 边界收敛:shopkey 不再直连它们的表)
|
||||
for (GroupDeletionGuardPort guard : groupDeletionGuards) {
|
||||
guard.ensureNoGroupData(entity.getId());
|
||||
}
|
||||
groupMemberMapper.delete(new LambdaQueryWrapper<ShopManageGroupMemberEntity>()
|
||||
.eq(ShopManageGroupMemberEntity::getGroupId, entity.getId()));
|
||||
|
||||
+1
-1
@@ -7,7 +7,7 @@ import com.nanri.aiimage.modules.shopkey.mapper.ShopManageMapper;
|
||||
import com.nanri.aiimage.modules.shopkey.model.dto.ShopManageCreateRequest;
|
||||
import com.nanri.aiimage.modules.shopkey.model.dto.ShopManageUpdateRequest;
|
||||
import com.nanri.aiimage.modules.shopkey.model.entity.ShopManageEntity;
|
||||
import com.nanri.aiimage.modules.shopkey.model.entity.ShopManageGroupEntity;
|
||||
import com.nanri.aiimage.common.model.entity.ShopManageGroupEntity;
|
||||
import com.nanri.aiimage.modules.shopkey.model.vo.ShopManageCredentialVo;
|
||||
import com.nanri.aiimage.modules.shopkey.model.vo.ShopManageItemVo;
|
||||
import com.nanri.aiimage.modules.shopkey.model.vo.ShopManagePageVo;
|
||||
|
||||
+1
-1
@@ -13,7 +13,7 @@ import com.nanri.aiimage.modules.shopkey.mapper.ShopManageMapper;
|
||||
import com.nanri.aiimage.modules.shopkey.model.dto.SkipPriceAsinCreateRequest;
|
||||
import com.nanri.aiimage.modules.shopkey.model.dto.SkipPriceAsinDetailDto;
|
||||
import com.nanri.aiimage.modules.shopkey.model.entity.ShopManageEntity;
|
||||
import com.nanri.aiimage.modules.shopkey.model.entity.ShopManageGroupEntity;
|
||||
import com.nanri.aiimage.common.model.entity.ShopManageGroupEntity;
|
||||
import com.nanri.aiimage.modules.shopkey.model.entity.SkipPriceAsinEntity;
|
||||
import com.nanri.aiimage.modules.shopkey.model.vo.QueryAsinImportProgressVo;
|
||||
import com.nanri.aiimage.modules.shopkey.model.vo.QueryAsinImportStartVo;
|
||||
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
package com.nanri.aiimage.modules.shopkey.spi;
|
||||
|
||||
/**
|
||||
* 分组删除守卫(2026-09 边界收敛):分组下若仍存在某模块的业务数据,拒绝删除该分组。
|
||||
*
|
||||
* <p>原实现由 shopkey 直接 count 其它模块的表(invalidasin / dedupe),构成 shopkey → 业务 的反向依赖;
|
||||
* 现改为各业务模块自己实现本端口(Spring 注入 List 后逐个校验),新增模块只需加一个实现 Bean。
|
||||
*
|
||||
* <p>实现应在"存在数据"时抛出 {@code BusinessException},文案自带模块名(沿用历史文案,前端无需改)。
|
||||
*/
|
||||
public interface GroupDeletionGuardPort {
|
||||
|
||||
/** 校验该分组下本模块无残留数据;有则抛业务异常。 */
|
||||
void ensureNoGroupData(Long groupId);
|
||||
}
|
||||
+2
-2
@@ -1,8 +1,8 @@
|
||||
package com.nanri.aiimage.modules.softwareversion.controller;
|
||||
|
||||
import com.nanri.aiimage.common.api.ApiResponse;
|
||||
import com.nanri.aiimage.modules.admin.support.AdminAuthSupport;
|
||||
import com.nanri.aiimage.modules.permission.model.entity.AdminUserEntity;
|
||||
import com.nanri.aiimage.common.security.AdminAuthSupport;
|
||||
import com.nanri.aiimage.common.model.entity.AdminUserEntity;
|
||||
import com.nanri.aiimage.modules.softwareversion.service.SoftwareVersionService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
|
||||
+9
@@ -37,6 +37,15 @@ public interface ResultFileJobHandler {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 模块自定义的结果对象 key 解析:返回非空时覆盖默认路径(按 resultId 查 biz_file_result)。
|
||||
* BRAND 的结果文件 job 上没有 resultId,需要用任务 id 反查品牌侧存放的对象 key。
|
||||
* 实现应自吞异常(解析失败按 null 处理,走默认路径)。
|
||||
*/
|
||||
default String resolveResultFileUrl(TaskFileJobEntity job) {
|
||||
return null;
|
||||
}
|
||||
|
||||
default boolean supportsAsyncOffload() {
|
||||
return false;
|
||||
}
|
||||
|
||||
+11
-10
@@ -3,11 +3,9 @@ package com.nanri.aiimage.modules.task.service;
|
||||
import com.nanri.aiimage.common.exception.BusinessException;
|
||||
import com.nanri.aiimage.common.exception.TaskOwnerMismatchException;
|
||||
import com.nanri.aiimage.config.InstanceMetadata;
|
||||
import com.nanri.aiimage.modules.brand.service.BrandTaskService;
|
||||
import com.nanri.aiimage.modules.task.mapper.FileResultMapper;
|
||||
import com.nanri.aiimage.modules.task.model.entity.FileResultEntity;
|
||||
import com.nanri.aiimage.modules.task.model.entity.TaskFileJobEntity;
|
||||
import com.nanri.aiimage.modules.withdraw.service.WithdrawTaskService;
|
||||
import jakarta.annotation.PreDestroy;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@@ -34,8 +32,6 @@ public class TaskResultFileJobWorker {
|
||||
private final FileResultMapper fileResultMapper;
|
||||
private final TaskFileJobLocalDispatcher taskFileJobLocalDispatcher;
|
||||
private final InstanceMetadata instanceMetadata;
|
||||
private final WithdrawTaskService withdrawTaskService;
|
||||
private final BrandTaskService brandTaskService;
|
||||
private final ResultFileJobHandlerRegistry handlerRegistry;
|
||||
@Autowired
|
||||
@Qualifier("taskQueueExecutor")
|
||||
@@ -178,7 +174,6 @@ public class TaskResultFileJobWorker {
|
||||
|
||||
private void processInternal(TaskFileJobEntity job) {
|
||||
long startedAt = System.currentTimeMillis();
|
||||
boolean finalizeWithdraw = false;
|
||||
try {
|
||||
TaskFileJobEntity latest = taskFileJobService.findById(job.getId());
|
||||
if (latest != null && "SUCCESS".equals(latest.getStatus())) {
|
||||
@@ -219,13 +214,15 @@ public class TaskResultFileJobWorker {
|
||||
String resultFileUrl = resolveResultFileUrl(job);
|
||||
taskFileJobService.markSuccess(job, resultFileUrl);
|
||||
cleanupAfterSuccess(job);
|
||||
finalizeWithdraw = "WITHDRAW".equals(job.getModuleType());
|
||||
log.info("[task-file-job] process success jobId={} taskId={} moduleType={} resultId={} elapsedMs={} resultFileUrl={} stage=SUCCESS",
|
||||
job.getId(), job.getTaskId(), job.getModuleType(), job.getResultId(),
|
||||
System.currentTimeMillis() - startedAt, resultFileUrl);
|
||||
}
|
||||
if (finalizeWithdraw) {
|
||||
withdrawTaskService.tryFinalizeTask(job.getTaskId(), false);
|
||||
// 成功后回调解耦(原 WITHDRAW 特判):时机与历史一致——锁已释放、markSuccess 之后;
|
||||
// 各模块需要"任务收尾"时实现 ResultFileJobHandler.onSuccess,task 侧不再 import 业务模块
|
||||
ResultFileJobHandler successHandler = handlerRegistry.asMap().get(job.getModuleType());
|
||||
if (successHandler != null) {
|
||||
successHandler.onSuccess(job);
|
||||
}
|
||||
} catch (TaskOwnerMismatchException ex) {
|
||||
// 归属校验失败不是"生成失败":不能计入 retryCount,否则任务会在归属机
|
||||
@@ -308,8 +305,12 @@ public class TaskResultFileJobWorker {
|
||||
}
|
||||
|
||||
private String resolveResultFileUrl(TaskFileJobEntity job) {
|
||||
if ("BRAND".equals(job.getModuleType())) {
|
||||
return brandTaskService.resolveResultObjectKey(job.getTaskId());
|
||||
ResultFileJobHandler handler = handlerRegistry.asMap().get(job.getModuleType());
|
||||
if (handler != null) {
|
||||
String custom = handler.resolveResultFileUrl(job);
|
||||
if (custom != null && !custom.isBlank()) {
|
||||
return custom;
|
||||
}
|
||||
}
|
||||
if (job.getResultId() == null) {
|
||||
return null;
|
||||
|
||||
+2
-2
@@ -1,8 +1,8 @@
|
||||
package com.nanri.aiimage.modules.tutorial.controller;
|
||||
|
||||
import com.nanri.aiimage.common.api.ApiResponse;
|
||||
import com.nanri.aiimage.modules.admin.support.AdminAuthSupport;
|
||||
import com.nanri.aiimage.modules.permission.model.entity.AdminUserEntity;
|
||||
import com.nanri.aiimage.common.security.AdminAuthSupport;
|
||||
import com.nanri.aiimage.common.model.entity.AdminUserEntity;
|
||||
import com.nanri.aiimage.modules.tutorial.service.TutorialPackageService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
|
||||
+2
-2
@@ -2,8 +2,8 @@ package com.nanri.aiimage.modules.usersecret.controller;
|
||||
|
||||
import com.nanri.aiimage.common.api.ApiResponse;
|
||||
import com.nanri.aiimage.config.UserSecretProperties;
|
||||
import com.nanri.aiimage.modules.admin.support.AdminAuthSupport;
|
||||
import com.nanri.aiimage.modules.permission.model.entity.AdminUserEntity;
|
||||
import com.nanri.aiimage.common.security.AdminAuthSupport;
|
||||
import com.nanri.aiimage.common.model.entity.AdminUserEntity;
|
||||
import com.nanri.aiimage.modules.usersecret.model.dto.AdminUserSecretQuery;
|
||||
import com.nanri.aiimage.modules.usersecret.model.vo.AdminUserSecretPageVo;
|
||||
import com.nanri.aiimage.modules.usersecret.model.vo.UserApiSecretCheckResultVo;
|
||||
|
||||
+2
-2
@@ -1,8 +1,8 @@
|
||||
package com.nanri.aiimage.modules.usersecret.controller;
|
||||
|
||||
import com.nanri.aiimage.common.api.ApiResponse;
|
||||
import com.nanri.aiimage.modules.admin.support.AdminAuthSupport;
|
||||
import com.nanri.aiimage.modules.permission.model.entity.AdminUserEntity;
|
||||
import com.nanri.aiimage.common.security.AdminAuthSupport;
|
||||
import com.nanri.aiimage.common.model.entity.AdminUserEntity;
|
||||
import com.nanri.aiimage.modules.usersecret.model.dto.AdminUserSecretUsageQuery;
|
||||
import com.nanri.aiimage.modules.usersecret.model.vo.AdminUserSecretUsagePageVo;
|
||||
import com.nanri.aiimage.modules.usersecret.service.UserSecretUsageService;
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@ package com.nanri.aiimage.modules.usersecret.controller;
|
||||
|
||||
import com.nanri.aiimage.common.api.ApiResponse;
|
||||
import com.nanri.aiimage.common.exception.BusinessException;
|
||||
import com.nanri.aiimage.modules.admin.support.AdminAuthSupport;
|
||||
import com.nanri.aiimage.common.security.AdminAuthSupport;
|
||||
import com.nanri.aiimage.modules.usersecret.service.UserApiSecretService;
|
||||
import com.nanri.aiimage.modules.usersecret.support.UserSecretModule;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
package com.nanri.aiimage.modules.usersecret.controller;
|
||||
|
||||
import com.nanri.aiimage.common.api.ApiResponse;
|
||||
import com.nanri.aiimage.modules.admin.support.AdminAuthSupport;
|
||||
import com.nanri.aiimage.common.security.AdminAuthSupport;
|
||||
import com.nanri.aiimage.modules.usersecret.model.dto.UserSecretUsageReportRequest;
|
||||
import com.nanri.aiimage.modules.usersecret.service.UserSecretUsageService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
|
||||
+2
-2
@@ -1,8 +1,8 @@
|
||||
package com.nanri.aiimage.modules.usersecret.controller;
|
||||
|
||||
import com.nanri.aiimage.common.api.ApiResponse;
|
||||
import com.nanri.aiimage.modules.admin.support.AdminAuthSupport;
|
||||
import com.nanri.aiimage.modules.permission.model.entity.AdminUserEntity;
|
||||
import com.nanri.aiimage.common.security.AdminAuthSupport;
|
||||
import com.nanri.aiimage.common.model.entity.AdminUserEntity;
|
||||
import com.nanri.aiimage.modules.usersecret.model.dto.UserApiSecretCheckRequest;
|
||||
import com.nanri.aiimage.modules.usersecret.model.dto.UserApiSecretMigrateRequest;
|
||||
import com.nanri.aiimage.modules.usersecret.model.dto.UserApiSecretSaveRequest;
|
||||
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
package com.nanri.aiimage.modules.usersecret.service;
|
||||
|
||||
import com.nanri.aiimage.modules.notification.spi.ProxyBalancePort;
|
||||
import com.nanri.aiimage.modules.usersecret.client.JikipProxyClient;
|
||||
import com.nanri.aiimage.modules.usersecret.model.vo.UserApiSecretBalanceVo;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* 代理余额探测端口实现(2026-09 边界收敛):把 jikip 客户端与余额 VO 收敛在 usersecret 模块内。
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class ProxyBalancePortAdapter implements ProxyBalancePort {
|
||||
|
||||
private final JikipProxyClient jikipProxyClient;
|
||||
|
||||
@Override
|
||||
public ProxyBalanceSnapshot fetchBalance() {
|
||||
UserApiSecretBalanceVo balance = jikipProxyClient.fetchBalance();
|
||||
if (balance == null) {
|
||||
return new ProxyBalanceSnapshot(false, null, null, "余额接口返回空");
|
||||
}
|
||||
return new ProxyBalanceSnapshot(
|
||||
Boolean.TRUE.equals(balance.getAvailable()),
|
||||
balance.getSurplus(),
|
||||
balance.getBalance(),
|
||||
balance.getMessage());
|
||||
}
|
||||
}
|
||||
+8
-6
@@ -2,17 +2,18 @@ package com.nanri.aiimage.modules.usersecret.service;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.nanri.aiimage.common.exception.BusinessException;
|
||||
import com.nanri.aiimage.modules.admin.spi.UserSecretCleanupPort;
|
||||
import com.nanri.aiimage.common.security.ShopCredentialCryptoService;
|
||||
import com.nanri.aiimage.config.UserSecretProperties;
|
||||
import com.nanri.aiimage.modules.admin.support.AdminAuthSupport;
|
||||
import com.nanri.aiimage.common.security.AdminAuthSupport;
|
||||
import com.nanri.aiimage.modules.notification.service.NotificationDispatchService;
|
||||
import com.nanri.aiimage.modules.notification.service.NotificationService;
|
||||
import com.nanri.aiimage.modules.permission.mapper.AdminUserMapper;
|
||||
import com.nanri.aiimage.modules.permission.model.entity.AdminUserEntity;
|
||||
import com.nanri.aiimage.common.mapper.AdminUserMapper;
|
||||
import com.nanri.aiimage.common.model.entity.AdminUserEntity;
|
||||
import com.nanri.aiimage.modules.permission.support.UserDataScopeSupport;
|
||||
import com.nanri.aiimage.modules.shopkey.mapper.ShopManageGroupMapper;
|
||||
import com.nanri.aiimage.common.mapper.ShopManageGroupMapper;
|
||||
import com.nanri.aiimage.modules.shopkey.model.dto.UserGroupRef;
|
||||
import com.nanri.aiimage.modules.shopkey.model.entity.ShopManageGroupEntity;
|
||||
import com.nanri.aiimage.common.model.entity.ShopManageGroupEntity;
|
||||
import com.nanri.aiimage.modules.usersecret.client.JikipProxyClient;import com.nanri.aiimage.modules.usersecret.mapper.UserApiSecretMapper;
|
||||
import com.nanri.aiimage.modules.usersecret.model.dto.AdminUserSecretQuery;
|
||||
import com.nanri.aiimage.modules.usersecret.model.dto.UserApiSecretMigrateRequest;
|
||||
@@ -53,7 +54,7 @@ import java.util.Set;
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
@Slf4j
|
||||
public class UserApiSecretService {
|
||||
public class UserApiSecretService implements UserSecretCleanupPort {
|
||||
|
||||
public static final String SOURCE_CLIENT = "client";
|
||||
public static final String SOURCE_ADMIN = "admin";
|
||||
@@ -412,6 +413,7 @@ public class UserApiSecretService {
|
||||
|
||||
/** 后台:清空该用户全部密钥与代理配置。 */
|
||||
@Transactional
|
||||
@Override
|
||||
public int adminClearByUser(Long userId) {
|
||||
requireUserId(userId);
|
||||
int deleted = userApiSecretMapper.delete(new LambdaQueryWrapper<UserApiSecretEntity>()
|
||||
|
||||
+5
-5
@@ -1,13 +1,13 @@
|
||||
package com.nanri.aiimage.modules.usersecret.service;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.nanri.aiimage.modules.admin.support.AdminAuthSupport;
|
||||
import com.nanri.aiimage.modules.permission.mapper.AdminUserMapper;
|
||||
import com.nanri.aiimage.modules.permission.model.entity.AdminUserEntity;
|
||||
import com.nanri.aiimage.common.security.AdminAuthSupport;
|
||||
import com.nanri.aiimage.common.mapper.AdminUserMapper;
|
||||
import com.nanri.aiimage.common.model.entity.AdminUserEntity;
|
||||
import com.nanri.aiimage.modules.permission.support.UserDataScopeSupport;
|
||||
import com.nanri.aiimage.modules.shopkey.mapper.ShopManageGroupMapper;
|
||||
import com.nanri.aiimage.common.mapper.ShopManageGroupMapper;
|
||||
import com.nanri.aiimage.modules.shopkey.model.dto.UserGroupRef;
|
||||
import com.nanri.aiimage.modules.shopkey.model.entity.ShopManageGroupEntity;
|
||||
import com.nanri.aiimage.common.model.entity.ShopManageGroupEntity;
|
||||
import com.nanri.aiimage.modules.usersecret.mapper.UserSecretUsageMapper;
|
||||
import com.nanri.aiimage.modules.usersecret.model.dto.AdminUserSecretUsageQuery;
|
||||
import com.nanri.aiimage.modules.usersecret.model.entity.UserSecretUsageEntity;
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
package com.nanri.aiimage.modules.ziniao.controller;
|
||||
|
||||
import com.nanri.aiimage.common.api.ApiResponse;
|
||||
import com.nanri.aiimage.modules.admin.support.AdminAuthSupport;
|
||||
import com.nanri.aiimage.common.security.AdminAuthSupport;
|
||||
import com.nanri.aiimage.modules.ziniao.model.cache.ZiniaoShopIndexRefreshCursorDto;
|
||||
import com.nanri.aiimage.modules.ziniao.model.dto.ZiniaoOpenShopRequest;
|
||||
import com.nanri.aiimage.modules.ziniao.model.vo.ZiniaoOpenShopVo;
|
||||
|
||||
@@ -34,9 +34,9 @@ class ArchitectureBoundaryTest {
|
||||
* 本次以实测值 119 为准恢复告警能力;经 git diff 核验,当日的 task 模块改动
|
||||
* (引用计数按 task 收敛、快照 upsert 缓存、心跳条件更新、锁续期重试等)未新增任何业务依赖。
|
||||
*/
|
||||
// 2026-09-14 心跳/陈旧修复/历史清理 SPI 化后实测 6(G5:task 只依赖 task.spi 下接口,
|
||||
// 业务模块各自实现);继续按棘轮收敛,不允许回升
|
||||
private static final int TASK_TO_BUSINESS_BASELINE = 6;
|
||||
// 2026-09-14 心跳/陈旧修复/历史清理/结果文件 URL 全部 SPI 化后,task → 业务 依赖实测为 0。
|
||||
// 基线钉死为 0:task 只允许依赖 task.spi 下的接口(业务模块各自实现),不允许回升。
|
||||
private static final int TASK_TO_BUSINESS_BASELINE = 0;
|
||||
|
||||
private static volatile JavaClasses cached;
|
||||
|
||||
@@ -76,8 +76,9 @@ class ArchitectureBoundaryTest {
|
||||
void taskToBusinessDependencyDoesNotGrow() {
|
||||
// 冻结存量:Handler 迁移后 task→业务 依赖已大幅收敛;不允许继续增长
|
||||
int violations = countViolations(taskRule());
|
||||
assertTrue(violations >= 1, "task→业务 依赖仍有存量(心跳/清理等待 SPI 化)");
|
||||
assertTrue(violations <= TASK_TO_BUSINESS_BASELINE,
|
||||
"task→业务 依赖必须为 0(当前 " + violations + ");跨模块动作 = 在 task/spi 加接口 + 业务模块实现");
|
||||
assertTrue(violations >= 0,
|
||||
"task→业务 依赖不得超过基线 " + TASK_TO_BUSINESS_BASELINE + ",当前 " + violations
|
||||
+ ";新增依赖走 Handler SPI 或先消除存量");
|
||||
}
|
||||
|
||||
+5
-5
@@ -1,10 +1,10 @@
|
||||
package com.nanri.aiimage.modules.admin.support;
|
||||
package com.nanri.aiimage.common.security;
|
||||
|
||||
import com.nanri.aiimage.common.exception.BusinessException;
|
||||
import com.nanri.aiimage.modules.auth.config.AuthProperties;
|
||||
import com.nanri.aiimage.modules.auth.service.JwtService;
|
||||
import com.nanri.aiimage.modules.permission.mapper.AdminUserMapper;
|
||||
import com.nanri.aiimage.modules.permission.model.entity.AdminUserEntity;
|
||||
import com.nanri.aiimage.common.security.AuthProperties;
|
||||
import com.nanri.aiimage.common.security.JwtService;
|
||||
import com.nanri.aiimage.common.mapper.AdminUserMapper;
|
||||
import com.nanri.aiimage.common.model.entity.AdminUserEntity;
|
||||
import io.jsonwebtoken.Claims;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import org.junit.jupiter.api.Test;
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package com.nanri.aiimage.modules.auth.support;
|
||||
package com.nanri.aiimage.common.security;
|
||||
|
||||
import com.nanri.aiimage.common.exception.BusinessException;
|
||||
import io.jsonwebtoken.Claims;
|
||||
@@ -3,8 +3,8 @@ package com.nanri.aiimage.config;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.nanri.aiimage.common.exception.BusinessException;
|
||||
import com.nanri.aiimage.modules.admin.support.AdminAuthSupport;
|
||||
import com.nanri.aiimage.modules.permission.model.entity.AdminUserEntity;
|
||||
import com.nanri.aiimage.common.security.AdminAuthSupport;
|
||||
import com.nanri.aiimage.common.model.entity.AdminUserEntity;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.mock.web.MockFilterChain;
|
||||
import org.springframework.mock.web.MockHttpServletRequest;
|
||||
|
||||
+3
-3
@@ -3,11 +3,11 @@ package com.nanri.aiimage.modules.admin.service;
|
||||
import com.nanri.aiimage.common.exception.BusinessException;
|
||||
import com.nanri.aiimage.modules.admin.model.dto.AdminUserCreateRequest;
|
||||
import com.nanri.aiimage.modules.admin.model.dto.AdminUserUpdateRequest;
|
||||
import com.nanri.aiimage.modules.admin.support.AdminAuthSupport;
|
||||
import com.nanri.aiimage.common.security.AdminAuthSupport;
|
||||
import com.nanri.aiimage.modules.auth.util.WerkzeugPasswordEncoder;
|
||||
import com.nanri.aiimage.modules.permission.mapper.AdminUserMapper;
|
||||
import com.nanri.aiimage.common.mapper.AdminUserMapper;
|
||||
import com.nanri.aiimage.modules.permission.model.dto.UserColumnPermissionUpdateRequest;
|
||||
import com.nanri.aiimage.modules.permission.model.entity.AdminUserEntity;
|
||||
import com.nanri.aiimage.common.model.entity.AdminUserEntity;
|
||||
import com.nanri.aiimage.modules.permission.service.PermissionMenuService;
|
||||
import com.nanri.aiimage.modules.usersecret.service.UserApiSecretService;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
+2
-1
@@ -3,7 +3,7 @@ package com.nanri.aiimage.modules.auth.service;
|
||||
import com.baomidou.mybatisplus.core.MybatisConfiguration;
|
||||
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
|
||||
import com.nanri.aiimage.common.exception.BusinessException;
|
||||
import com.nanri.aiimage.modules.auth.config.AuthProperties;
|
||||
import com.nanri.aiimage.common.security.AuthProperties;
|
||||
import com.nanri.aiimage.modules.auth.mapper.LoginUserMapper;
|
||||
import com.nanri.aiimage.modules.auth.model.dto.LoginRequest;
|
||||
import com.nanri.aiimage.modules.auth.model.entity.LoginUserEntity;
|
||||
@@ -26,6 +26,7 @@ import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
import com.nanri.aiimage.common.security.JwtService;
|
||||
|
||||
class AuthServiceTest {
|
||||
|
||||
|
||||
+171
@@ -0,0 +1,171 @@
|
||||
package com.nanri.aiimage.modules.convert.service;
|
||||
|
||||
import com.baomidou.mybatisplus.core.MybatisConfiguration;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
|
||||
import com.nanri.aiimage.common.exception.BusinessException;
|
||||
import com.nanri.aiimage.modules.convert.mapper.ConvertTemplateMapper;
|
||||
import com.nanri.aiimage.modules.convert.model.dto.ConvertTemplateImportRequest;
|
||||
import com.nanri.aiimage.modules.convert.model.entity.ConvertTemplateEntity;
|
||||
import com.nanri.aiimage.modules.convert.model.vo.ConvertTemplateVo;
|
||||
import org.apache.ibatis.builder.MapperBuilderAssistant;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
* 格式转换的模板管理(2026-09 审查 G4:该模块此前零测试文件)。
|
||||
*
|
||||
* <p>重点:内置模板不可删(软禁用语义)、导入的自定义模板命名与文件名补全、
|
||||
* 设为默认时其余模板的默认位必须被清掉。
|
||||
*/
|
||||
class ConvertTemplateServiceTest {
|
||||
|
||||
@BeforeAll
|
||||
static void initializeMybatisMetadata() {
|
||||
TableInfoHelper.initTableInfo(
|
||||
new MapperBuilderAssistant(new MybatisConfiguration(), ""),
|
||||
ConvertTemplateEntity.class);
|
||||
}
|
||||
|
||||
private final ConvertTemplateMapper templateMapper = mock(ConvertTemplateMapper.class);
|
||||
private final ConvertTemplateService service = new ConvertTemplateService(templateMapper);
|
||||
|
||||
private static ConvertTemplateEntity template(long id, String code, int isDefault, int builtIn) {
|
||||
ConvertTemplateEntity entity = new ConvertTemplateEntity();
|
||||
entity.setId(id);
|
||||
entity.setTemplateCode(code);
|
||||
entity.setTemplateName(code + "模板");
|
||||
entity.setOutputFilename(code + ".txt");
|
||||
entity.setIsDefault(isDefault);
|
||||
entity.setBuiltIn(builtIn);
|
||||
entity.setEnabled(1);
|
||||
return entity;
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
void listEnabledTemplatesKeepsMapperOrder() {
|
||||
when(templateMapper.selectList(any(LambdaQueryWrapper.class)))
|
||||
.thenReturn(List.of(template(1L, "builtin_offer", 1, 1), template(2L, "user_1", 0, 0)));
|
||||
|
||||
List<ConvertTemplateVo> templates = service.listEnabledTemplates();
|
||||
|
||||
assertEquals(2, templates.size());
|
||||
assertEquals("builtin_offer", templates.get(0).getTemplateCode());
|
||||
assertTrue(templates.get(0).getIsDefault());
|
||||
assertEquals("user_1", templates.get(1).getTemplateCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
void getByCodeRejectsMissingOrDisabledTemplate() {
|
||||
when(templateMapper.selectOne(any(LambdaQueryWrapper.class))).thenReturn(null);
|
||||
|
||||
BusinessException exception = assertThrows(BusinessException.class, () -> service.getByCode("gone"));
|
||||
|
||||
assertEquals("模板不存在或已禁用", exception.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
void importTemplateRejectsBlankContent() {
|
||||
ConvertTemplateImportRequest request = new ConvertTemplateImportRequest();
|
||||
request.setTemplateName("我的模板");
|
||||
request.setTemplateContent(" ");
|
||||
|
||||
assertThrows(BusinessException.class, () -> service.importTemplate(request));
|
||||
verify(templateMapper, never()).insert(any(ConvertTemplateEntity.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void importTemplateGeneratesUserCodeAndTxtFilename() {
|
||||
ConvertTemplateImportRequest request = new ConvertTemplateImportRequest();
|
||||
request.setTemplateName("我的模板");
|
||||
request.setTemplateContent("TemplateType=Offer\tVersion=1.4\n");
|
||||
|
||||
ConvertTemplateVo vo = service.importTemplate(request);
|
||||
|
||||
ArgumentCaptor<ConvertTemplateEntity> captor = ArgumentCaptor.forClass(ConvertTemplateEntity.class);
|
||||
verify(templateMapper).insert(captor.capture());
|
||||
ConvertTemplateEntity saved = captor.getValue();
|
||||
assertTrue(saved.getTemplateCode().startsWith("user_"), "自定义模板编码带 user_ 前缀: " + saved.getTemplateCode());
|
||||
assertEquals("我的模板.txt", saved.getOutputFilename(), "文件名补全 .txt 后缀");
|
||||
assertEquals(0, saved.getBuiltIn(), "导入的模板不是内置模板");
|
||||
assertEquals(0, saved.getIsDefault(), "导入不自动成为默认模板");
|
||||
assertEquals(saved.getTemplateCode(), vo.getTemplateCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
void importTemplateKeepsGivenTxtSuffixAndFallsBackToDefaultName() {
|
||||
ConvertTemplateImportRequest request = new ConvertTemplateImportRequest();
|
||||
request.setTemplateName("自定义.txt");
|
||||
request.setTemplateContent("x");
|
||||
|
||||
service.importTemplate(request);
|
||||
|
||||
ArgumentCaptor<ConvertTemplateEntity> captor = ArgumentCaptor.forClass(ConvertTemplateEntity.class);
|
||||
verify(templateMapper).insert(captor.capture());
|
||||
assertEquals("自定义.txt", captor.getValue().getOutputFilename(), "已有 .txt 后缀不重复追加");
|
||||
|
||||
ConvertTemplateImportRequest unnamed = new ConvertTemplateImportRequest();
|
||||
unnamed.setTemplateName(" ");
|
||||
unnamed.setTemplateContent("x");
|
||||
service.importTemplate(unnamed);
|
||||
|
||||
ArgumentCaptor<ConvertTemplateEntity> second = ArgumentCaptor.forClass(ConvertTemplateEntity.class);
|
||||
verify(templateMapper, times(2)).insert(second.capture());
|
||||
assertEquals("自定义 Offer 模板.txt", second.getAllValues().get(1).getOutputFilename());
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
void setDefaultTemplateClearsOthers() {
|
||||
ConvertTemplateEntity target = template(2L, "user_1", 0, 0);
|
||||
ConvertTemplateEntity other = template(3L, "user_2", 1, 0);
|
||||
when(templateMapper.selectOne(any(LambdaQueryWrapper.class))).thenReturn(target);
|
||||
when(templateMapper.selectList(any(LambdaQueryWrapper.class))).thenReturn(List.of(target, other));
|
||||
|
||||
service.setDefaultTemplate("user_1");
|
||||
|
||||
assertEquals(1, target.getIsDefault(), "目标模板置为默认");
|
||||
assertEquals(0, other.getIsDefault(), "其余模板的默认位必须清掉,否则会同时存在多个默认模板");
|
||||
verify(templateMapper, times(2)).updateById(any(ConvertTemplateEntity.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
void deleteTemplateRejectsBuiltIn() {
|
||||
when(templateMapper.selectOne(any(LambdaQueryWrapper.class))).thenReturn(template(1L, "builtin_offer", 1, 1));
|
||||
|
||||
BusinessException exception = assertThrows(BusinessException.class,
|
||||
() -> service.deleteTemplate("builtin_offer"));
|
||||
|
||||
assertEquals("内置模板不允许删除", exception.getMessage());
|
||||
verify(templateMapper, never()).updateById(any(ConvertTemplateEntity.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
void deleteTemplateSoftDisablesInsteadOfRemoving() {
|
||||
ConvertTemplateEntity userTemplate = template(5L, "user_5", 0, 0);
|
||||
when(templateMapper.selectOne(any(LambdaQueryWrapper.class))).thenReturn(userTemplate);
|
||||
|
||||
service.deleteTemplate("user_5");
|
||||
|
||||
assertEquals(0, userTemplate.getEnabled(), "删除是软禁用(enabled=0),保留历史可追溯");
|
||||
verify(templateMapper).updateById(userTemplate);
|
||||
verify(templateMapper, never()).deleteById(any(Long.class));
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -1,9 +1,9 @@
|
||||
package com.nanri.aiimage.modules.dedupe.controller;
|
||||
|
||||
import com.nanri.aiimage.common.exception.BusinessException;
|
||||
import com.nanri.aiimage.modules.admin.support.AdminAuthSupport;
|
||||
import com.nanri.aiimage.common.security.AdminAuthSupport;
|
||||
import com.nanri.aiimage.modules.dedupe.service.DedupeTotalDataService;
|
||||
import com.nanri.aiimage.modules.permission.model.entity.AdminUserEntity;
|
||||
import com.nanri.aiimage.common.model.entity.AdminUserEntity;
|
||||
import com.nanri.aiimage.modules.permission.service.PermissionMenuService;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
+4
-4
@@ -11,10 +11,10 @@ import com.nanri.aiimage.common.service.NodeSharedStore;
|
||||
import com.nanri.aiimage.modules.dedupe.model.vo.DedupeTotalDataImportProgressVo;
|
||||
import com.nanri.aiimage.modules.dedupe.model.vo.DedupeTotalDataItemVo;
|
||||
import com.nanri.aiimage.modules.dedupe.model.vo.DedupeTotalDataPageVo;
|
||||
import com.nanri.aiimage.modules.permission.mapper.AdminUserMapper;
|
||||
import com.nanri.aiimage.modules.permission.model.entity.AdminUserEntity;
|
||||
import com.nanri.aiimage.modules.shopkey.mapper.ShopManageGroupMapper;
|
||||
import com.nanri.aiimage.modules.shopkey.model.entity.ShopManageGroupEntity;
|
||||
import com.nanri.aiimage.common.mapper.AdminUserMapper;
|
||||
import com.nanri.aiimage.common.model.entity.AdminUserEntity;
|
||||
import com.nanri.aiimage.common.mapper.ShopManageGroupMapper;
|
||||
import com.nanri.aiimage.common.model.entity.ShopManageGroupEntity;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
package com.nanri.aiimage.modules.imagehistory.service;
|
||||
|
||||
import com.nanri.aiimage.modules.imagehistory.mapper.ImageHistoryMapper;
|
||||
import com.nanri.aiimage.modules.permission.mapper.AdminUserMapper;
|
||||
import com.nanri.aiimage.common.mapper.AdminUserMapper;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
|
||||
+2
-2
@@ -1,10 +1,10 @@
|
||||
package com.nanri.aiimage.modules.invalidasin.controller;
|
||||
|
||||
import com.nanri.aiimage.common.exception.BusinessException;
|
||||
import com.nanri.aiimage.modules.admin.support.AdminAuthSupport;
|
||||
import com.nanri.aiimage.common.security.AdminAuthSupport;
|
||||
import com.nanri.aiimage.modules.invalidasin.model.vo.InvalidAsinDataPageVo;
|
||||
import com.nanri.aiimage.modules.invalidasin.service.InvalidAsinDataService;
|
||||
import com.nanri.aiimage.modules.permission.model.entity.AdminUserEntity;
|
||||
import com.nanri.aiimage.common.model.entity.AdminUserEntity;
|
||||
import com.nanri.aiimage.modules.permission.model.vo.PermissionMenuItemVo;
|
||||
import com.nanri.aiimage.modules.permission.service.PermissionMenuService;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@ import com.nanri.aiimage.modules.invalidasin.model.dto.InvalidAsinDataUpdateRequ
|
||||
import com.nanri.aiimage.modules.invalidasin.model.entity.InvalidAsinDataEntity;
|
||||
import com.nanri.aiimage.modules.invalidasin.model.vo.InvalidAsinDataItemVo;
|
||||
import com.nanri.aiimage.modules.invalidasin.model.vo.InvalidAsinDataPageVo;
|
||||
import com.nanri.aiimage.modules.shopkey.model.entity.ShopManageGroupEntity;
|
||||
import com.nanri.aiimage.common.model.entity.ShopManageGroupEntity;
|
||||
import com.nanri.aiimage.modules.shopkey.service.ShopManageGroupService;
|
||||
import org.apache.ibatis.builder.MapperBuilderAssistant;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
+2
-2
@@ -1,9 +1,9 @@
|
||||
package com.nanri.aiimage.modules.notification.controller;
|
||||
|
||||
import com.nanri.aiimage.modules.admin.support.AdminAuthSupport;
|
||||
import com.nanri.aiimage.common.security.AdminAuthSupport;
|
||||
import com.nanri.aiimage.modules.notification.model.dto.NotificationPageQuery;
|
||||
import com.nanri.aiimage.modules.notification.service.NotificationService;
|
||||
import com.nanri.aiimage.modules.permission.model.entity.AdminUserEntity;
|
||||
import com.nanri.aiimage.common.model.entity.AdminUserEntity;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
|
||||
+2
-2
@@ -1,9 +1,9 @@
|
||||
package com.nanri.aiimage.modules.notification.controller;
|
||||
|
||||
import com.nanri.aiimage.modules.admin.support.AdminAuthSupport;
|
||||
import com.nanri.aiimage.common.security.AdminAuthSupport;
|
||||
import com.nanri.aiimage.modules.notification.model.dto.NotificationPageQuery;
|
||||
import com.nanri.aiimage.modules.notification.service.NotificationService;
|
||||
import com.nanri.aiimage.modules.permission.model.entity.AdminUserEntity;
|
||||
import com.nanri.aiimage.common.model.entity.AdminUserEntity;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
|
||||
+3
-3
@@ -1,8 +1,8 @@
|
||||
package com.nanri.aiimage.modules.notification.service;
|
||||
|
||||
import com.nanri.aiimage.modules.admin.support.AdminAuthSupport;
|
||||
import com.nanri.aiimage.modules.permission.mapper.AdminUserMapper;
|
||||
import com.nanri.aiimage.modules.permission.model.entity.AdminUserEntity;
|
||||
import com.nanri.aiimage.common.security.AdminAuthSupport;
|
||||
import com.nanri.aiimage.common.mapper.AdminUserMapper;
|
||||
import com.nanri.aiimage.common.model.entity.AdminUserEntity;
|
||||
import com.nanri.aiimage.modules.permission.support.UserDataScopeSupport;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
|
||||
+3
-3
@@ -7,7 +7,7 @@ import com.nanri.aiimage.modules.brand.mapper.BrandCrawlTaskMapper;
|
||||
import com.nanri.aiimage.modules.brand.model.entity.BrandCrawlTaskEntity;
|
||||
import com.nanri.aiimage.modules.task.mapper.FileTaskMapper;
|
||||
import com.nanri.aiimage.modules.task.model.entity.FileTaskEntity;
|
||||
import com.nanri.aiimage.modules.usersecret.client.JikipProxyClient;
|
||||
import com.nanri.aiimage.modules.notification.spi.ProxyBalancePort;
|
||||
import org.apache.ibatis.builder.MapperBuilderAssistant;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@@ -45,11 +45,11 @@ class NotificationScanSchedulerTest {
|
||||
private final com.nanri.aiimage.common.service.DistributedJobLockService lockService =
|
||||
mock(com.nanri.aiimage.common.service.DistributedJobLockService.class);
|
||||
private final NotificationProperties properties = new NotificationProperties();
|
||||
private final JikipProxyClient jikipProxyClient = mock(JikipProxyClient.class);
|
||||
private final ProxyBalancePort proxyBalancePort = mock(ProxyBalancePort.class);
|
||||
|
||||
private NotificationScanScheduler newScheduler() {
|
||||
return new NotificationScanScheduler(fileTaskMapper, brandCrawlTaskMapper, notificationService,
|
||||
dispatch, lockService, properties, jikipProxyClient);
|
||||
dispatch, lockService, properties, proxyBalancePort);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
+2
-2
@@ -1,10 +1,10 @@
|
||||
package com.nanri.aiimage.modules.permission.controller;
|
||||
|
||||
import com.nanri.aiimage.common.exception.BusinessException;
|
||||
import com.nanri.aiimage.modules.admin.support.AdminAuthSupport;
|
||||
import com.nanri.aiimage.common.security.AdminAuthSupport;
|
||||
import com.nanri.aiimage.modules.permission.model.dto.ImageVideoDataPermissionUpdateRequest;
|
||||
import com.nanri.aiimage.modules.permission.model.dto.UserColumnPermissionUpdateRequest;
|
||||
import com.nanri.aiimage.modules.permission.model.entity.AdminUserEntity;
|
||||
import com.nanri.aiimage.common.model.entity.AdminUserEntity;
|
||||
import com.nanri.aiimage.modules.permission.model.vo.ImageVideoDataPermissionUserVo;
|
||||
import com.nanri.aiimage.modules.permission.model.vo.PermissionMenuItemVo;
|
||||
import com.nanri.aiimage.modules.permission.service.PermissionMenuService;
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
package com.nanri.aiimage.modules.permission.service;
|
||||
|
||||
import com.nanri.aiimage.modules.permission.mapper.AdminUserMapper;
|
||||
import com.nanri.aiimage.common.mapper.AdminUserMapper;
|
||||
import com.nanri.aiimage.modules.permission.mapper.PermissionMenuMapper;
|
||||
import com.nanri.aiimage.modules.permission.mapper.UserColumnPermissionMapper;
|
||||
import com.nanri.aiimage.modules.permission.model.entity.PermissionMenuEntity;
|
||||
|
||||
+2
-2
@@ -1,13 +1,13 @@
|
||||
package com.nanri.aiimage.modules.permission.service;
|
||||
|
||||
import com.nanri.aiimage.common.exception.BusinessException;
|
||||
import com.nanri.aiimage.modules.permission.mapper.AdminUserMapper;
|
||||
import com.nanri.aiimage.common.mapper.AdminUserMapper;
|
||||
import com.nanri.aiimage.modules.permission.mapper.PermissionMenuMapper;
|
||||
import com.nanri.aiimage.modules.permission.mapper.UserColumnPermissionMapper;
|
||||
import com.nanri.aiimage.modules.permission.model.dto.PermissionMenuCreateRequest;
|
||||
import com.nanri.aiimage.modules.permission.model.dto.PermissionMenuUpdateRequest;
|
||||
import com.nanri.aiimage.modules.permission.model.dto.UserColumnPermissionUpdateRequest;
|
||||
import com.nanri.aiimage.modules.permission.model.entity.AdminUserEntity;
|
||||
import com.nanri.aiimage.common.model.entity.AdminUserEntity;
|
||||
import com.nanri.aiimage.modules.permission.model.entity.PermissionMenuEntity;
|
||||
import com.nanri.aiimage.modules.permission.model.entity.UserColumnPermissionEntity;
|
||||
import com.nanri.aiimage.modules.permission.model.vo.ImageVideoDataPermissionUserVo;
|
||||
|
||||
+2
-2
@@ -1,8 +1,8 @@
|
||||
package com.nanri.aiimage.modules.shopdatacrawl.controller;
|
||||
|
||||
import com.nanri.aiimage.common.exception.BusinessException;
|
||||
import com.nanri.aiimage.modules.admin.support.AdminAuthSupport;
|
||||
import com.nanri.aiimage.modules.permission.model.entity.AdminUserEntity;
|
||||
import com.nanri.aiimage.common.security.AdminAuthSupport;
|
||||
import com.nanri.aiimage.common.model.entity.AdminUserEntity;
|
||||
import com.nanri.aiimage.modules.permission.service.PermissionMenuService;
|
||||
import com.nanri.aiimage.modules.shopdatacrawl.service.ShopDataCrawlTaskService;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
|
||||
+1
-1
@@ -9,7 +9,7 @@ import com.nanri.aiimage.common.exception.BusinessException;
|
||||
import com.nanri.aiimage.modules.shopkey.mapper.ShopManageMapper;
|
||||
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.common.model.entity.ShopManageGroupEntity;
|
||||
import com.nanri.aiimage.modules.shopkey.model.entity.SkipPriceAsinEntity;
|
||||
import com.nanri.aiimage.modules.shopkey.model.vo.QueryAsinImportProgressVo;
|
||||
import com.nanri.aiimage.modules.shopkey.model.vo.SkipPriceAsinItemVo;
|
||||
|
||||
+264
@@ -0,0 +1,264 @@
|
||||
package com.nanri.aiimage.modules.shopmatch.service;
|
||||
|
||||
import com.baomidou.mybatisplus.core.MybatisConfiguration;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.nanri.aiimage.common.exception.BusinessException;
|
||||
import com.nanri.aiimage.common.model.vo.ProductRiskCandidateVo;
|
||||
import com.nanri.aiimage.common.model.vo.ProductRiskCountryPreferenceVo;
|
||||
import com.nanri.aiimage.common.model.vo.ProductRiskMatchShopsVo;
|
||||
import com.nanri.aiimage.modules.productrisk.model.dto.ProductRiskCandidateAddRequest;
|
||||
import com.nanri.aiimage.modules.productrisk.model.dto.ProductRiskCountryPreferenceSaveRequest;
|
||||
import com.nanri.aiimage.modules.productrisk.model.dto.ProductRiskMatchShopsRequest;
|
||||
import com.nanri.aiimage.modules.shopmatch.mapper.ShopMatchCountryPrefMapper;
|
||||
import com.nanri.aiimage.modules.shopmatch.mapper.ShopMatchShopCandidateMapper;
|
||||
import com.nanri.aiimage.modules.shopmatch.model.entity.ShopMatchCountryPrefEntity;
|
||||
import com.nanri.aiimage.modules.shopmatch.model.entity.ShopMatchShopCandidateEntity;
|
||||
import com.nanri.aiimage.modules.ziniao.model.vo.ZiniaoShopMatchResultVo;
|
||||
import com.nanri.aiimage.modules.ziniao.service.ZiniaoShopIndexService;
|
||||
import com.nanri.aiimage.modules.ziniao.service.ZiniaoShopSwitchService;
|
||||
import org.apache.ibatis.builder.MapperBuilderAssistant;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyBoolean;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
* 定时匹配工具的备选店铺与国家偏好(2026-09 审查 G4:该模块此前零测试文件)。
|
||||
*
|
||||
* <p>重点:候选增删的越权保护与幂等、匹配去重保序、国家偏好的默认顺序与坏数据兜底。
|
||||
*/
|
||||
class ShopMatchResolveServiceTest {
|
||||
|
||||
@BeforeAll
|
||||
static void initializeMybatisMetadata() {
|
||||
MapperBuilderAssistant assistant = new MapperBuilderAssistant(new MybatisConfiguration(), "");
|
||||
TableInfoHelper.initTableInfo(assistant, ShopMatchShopCandidateEntity.class);
|
||||
TableInfoHelper.initTableInfo(assistant, ShopMatchCountryPrefEntity.class);
|
||||
}
|
||||
|
||||
private final ShopMatchShopCandidateMapper candidateMapper = mock(ShopMatchShopCandidateMapper.class);
|
||||
private final ShopMatchCountryPrefMapper countryPrefMapper = mock(ShopMatchCountryPrefMapper.class);
|
||||
private final ObjectMapper objectMapper = new ObjectMapper();
|
||||
private final ZiniaoShopSwitchService shopSwitchService = mock(ZiniaoShopSwitchService.class);
|
||||
|
||||
private ShopMatchResolveService service;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
service = new ShopMatchResolveService(candidateMapper, countryPrefMapper, objectMapper, shopSwitchService);
|
||||
when(shopSwitchService.normalizeShopName(anyString())).thenAnswer(invocation ->
|
||||
((String) invocation.getArgument(0)).trim());
|
||||
}
|
||||
|
||||
private static ProductRiskCandidateAddRequest addRequest(Long userId, String shopName) {
|
||||
ProductRiskCandidateAddRequest request = new ProductRiskCandidateAddRequest();
|
||||
request.setUserId(userId);
|
||||
request.setShopName(shopName);
|
||||
return request;
|
||||
}
|
||||
|
||||
private static ProductRiskCountryPreferenceSaveRequest prefRequest(Long userId, List<String> codes) {
|
||||
ProductRiskCountryPreferenceSaveRequest request = new ProductRiskCountryPreferenceSaveRequest();
|
||||
request.setUserId(userId);
|
||||
request.setCountryCodes(codes);
|
||||
return request;
|
||||
}
|
||||
|
||||
private static ZiniaoShopMatchResultVo matched(String shopName) {
|
||||
ZiniaoShopMatchResultVo vo = new ZiniaoShopMatchResultVo();
|
||||
vo.setMatched(true);
|
||||
vo.setShopName(shopName);
|
||||
vo.setMatchStatus(ZiniaoShopIndexService.MATCH_STATUS_MATCHED);
|
||||
return vo;
|
||||
}
|
||||
|
||||
@Test
|
||||
void listCandidatesRejectsInvalidUser() {
|
||||
assertThrows(BusinessException.class, () -> service.listCandidates(null));
|
||||
assertThrows(BusinessException.class, () -> service.listCandidates(0L));
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
void listCandidatesMapsRowsToViews() {
|
||||
ShopMatchShopCandidateEntity row = new ShopMatchShopCandidateEntity();
|
||||
row.setId(21L);
|
||||
row.setUserId(23L);
|
||||
row.setShopName("匹配店铺");
|
||||
row.setCreatedAt(LocalDateTime.now());
|
||||
when(candidateMapper.selectList(any(LambdaQueryWrapper.class))).thenReturn(List.of(row));
|
||||
|
||||
List<ProductRiskCandidateVo> views = service.listCandidates(23L);
|
||||
|
||||
assertEquals(1, views.size());
|
||||
assertEquals(21L, views.getFirst().getId());
|
||||
assertEquals("匹配店铺", views.getFirst().getShopName());
|
||||
}
|
||||
|
||||
@Test
|
||||
void addCandidateRejectsBlankShopName() {
|
||||
when(shopSwitchService.normalizeShopName(" ")).thenReturn("");
|
||||
|
||||
assertThrows(BusinessException.class, () -> service.addCandidate(addRequest(23L, " ")));
|
||||
verify(candidateMapper, never()).insert(any(ShopMatchShopCandidateEntity.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void addCandidateRejectsWhenIndexMisses() {
|
||||
ZiniaoShopMatchResultVo miss = new ZiniaoShopMatchResultVo();
|
||||
miss.setMatched(false);
|
||||
when(shopSwitchService.findIndexedStoreByName("未收录", false)).thenReturn(miss);
|
||||
|
||||
assertThrows(BusinessException.class, () -> service.addCandidate(addRequest(23L, "未收录")));
|
||||
verify(candidateMapper, never()).insert(any(ShopMatchShopCandidateEntity.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void addCandidateRejectsConflictStatus() {
|
||||
ZiniaoShopMatchResultVo conflict = matched("同名店铺");
|
||||
conflict.setMatchStatus(ZiniaoShopIndexService.MATCH_STATUS_CONFLICT);
|
||||
when(shopSwitchService.findIndexedStoreByName("同名店铺", false)).thenReturn(conflict);
|
||||
|
||||
assertThrows(BusinessException.class, () -> service.addCandidate(addRequest(23L, "同名店铺")));
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
void addCandidateIsIdempotentForExistingShop() {
|
||||
when(shopSwitchService.findIndexedStoreByName("已存在", false)).thenReturn(matched("已存在"));
|
||||
ShopMatchShopCandidateEntity existing = new ShopMatchShopCandidateEntity();
|
||||
existing.setId(66L);
|
||||
existing.setUserId(23L);
|
||||
existing.setShopName("已存在");
|
||||
existing.setCreatedAt(LocalDateTime.now());
|
||||
when(candidateMapper.selectOne(any(LambdaQueryWrapper.class))).thenReturn(existing);
|
||||
|
||||
ProductRiskCandidateVo vo = service.addCandidate(addRequest(23L, " 已存在 "));
|
||||
|
||||
assertEquals(66L, vo.getId());
|
||||
verify(candidateMapper, never()).insert(any(ShopMatchShopCandidateEntity.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void deleteCandidateValidatesIdAndOwnership() {
|
||||
assertThrows(BusinessException.class, () -> service.deleteCandidate(23L, null));
|
||||
assertThrows(BusinessException.class, () -> service.deleteCandidate(23L, 0L));
|
||||
|
||||
ShopMatchShopCandidateEntity other = new ShopMatchShopCandidateEntity();
|
||||
other.setId(8L);
|
||||
other.setUserId(77L);
|
||||
when(candidateMapper.selectById(8L)).thenReturn(other);
|
||||
|
||||
BusinessException exception = assertThrows(BusinessException.class,
|
||||
() -> service.deleteCandidate(23L, 8L));
|
||||
|
||||
assertEquals("记录不存在", exception.getMessage(), "他人记录与不存在同文案,避免探测");
|
||||
verify(candidateMapper, never()).deleteById(any(Long.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void deleteCandidateRemovesOwnRecord() {
|
||||
ShopMatchShopCandidateEntity own = new ShopMatchShopCandidateEntity();
|
||||
own.setId(8L);
|
||||
own.setUserId(23L);
|
||||
when(candidateMapper.selectById(8L)).thenReturn(own);
|
||||
|
||||
service.deleteCandidate(23L, 8L);
|
||||
|
||||
verify(candidateMapper).deleteById(8L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void matchShopsRejectsEmptyAfterNormalization() {
|
||||
ProductRiskMatchShopsRequest request = new ProductRiskMatchShopsRequest();
|
||||
request.setUserId(23L);
|
||||
request.setShopNames(List.of(" "));
|
||||
|
||||
assertThrows(BusinessException.class, () -> service.matchShops(request));
|
||||
}
|
||||
|
||||
@Test
|
||||
void matchShopsDeduplicatesKeepingFirstOccurrenceOrder() {
|
||||
ProductRiskMatchShopsRequest request = new ProductRiskMatchShopsRequest();
|
||||
request.setUserId(23L);
|
||||
request.setShopNames(List.of(" A ", "B", "A"));
|
||||
when(shopSwitchService.findIndexedStoreByName(anyString(), anyBoolean())).thenAnswer(invocation ->
|
||||
matched((String) invocation.getArgument(0)));
|
||||
|
||||
ProductRiskMatchShopsVo vo = service.matchShops(request);
|
||||
|
||||
assertEquals(2, vo.getItems().size());
|
||||
assertEquals("A", vo.getItems().get(0).getShopName());
|
||||
assertEquals("B", vo.getItems().get(1).getShopName());
|
||||
}
|
||||
|
||||
@Test
|
||||
void countryPreferenceFallsBackToDefaultOrderWhenAbsent() {
|
||||
when(countryPrefMapper.selectById(23L)).thenReturn(null);
|
||||
|
||||
ProductRiskCountryPreferenceVo vo = service.getCountryPreference(23L);
|
||||
|
||||
assertEquals(ShopMatchResolveService.DEFAULT_COUNTRY_PREFERENCE_ORDER, vo.getCountryCodes());
|
||||
}
|
||||
|
||||
@Test
|
||||
void countryPreferenceFallsBackToDefaultOrderOnBrokenJson() {
|
||||
ShopMatchCountryPrefEntity broken = new ShopMatchCountryPrefEntity();
|
||||
broken.setUserId(23L);
|
||||
broken.setCountryCodesJson("{not-json");
|
||||
when(countryPrefMapper.selectById(23L)).thenReturn(broken);
|
||||
|
||||
ProductRiskCountryPreferenceVo vo = service.getCountryPreference(23L);
|
||||
|
||||
assertEquals(ShopMatchResolveService.DEFAULT_COUNTRY_PREFERENCE_ORDER, vo.getCountryCodes(),
|
||||
"坏数据不能让页面空白,回退默认顺序");
|
||||
}
|
||||
|
||||
@Test
|
||||
void countryPreferenceReadsStoredOrder() {
|
||||
ShopMatchCountryPrefEntity stored = new ShopMatchCountryPrefEntity();
|
||||
stored.setUserId(23L);
|
||||
stored.setCountryCodesJson("[\"UK\",\"DE\"]");
|
||||
when(countryPrefMapper.selectById(23L)).thenReturn(stored);
|
||||
|
||||
ProductRiskCountryPreferenceVo vo = service.getCountryPreference(23L);
|
||||
|
||||
assertEquals(List.of("UK", "DE"), vo.getCountryCodes(), "已保存顺序优先于默认顺序");
|
||||
}
|
||||
|
||||
@Test
|
||||
void saveCountryPreferenceRejectsEmptySelection() {
|
||||
assertThrows(BusinessException.class, () -> service.saveCountryPreference(prefRequest(23L, List.of())));
|
||||
assertThrows(BusinessException.class, () -> service.saveCountryPreference(prefRequest(23L, null)));
|
||||
}
|
||||
|
||||
@Test
|
||||
void saveCountryPreferenceInsertsThenUpdates() {
|
||||
when(countryPrefMapper.selectById(23L)).thenReturn(null);
|
||||
service.saveCountryPreference(prefRequest(23L, List.of("UK", "DE")));
|
||||
verify(countryPrefMapper).insert(any(ShopMatchCountryPrefEntity.class));
|
||||
|
||||
ShopMatchCountryPrefEntity existing = new ShopMatchCountryPrefEntity();
|
||||
existing.setUserId(23L);
|
||||
existing.setCountryCodesJson("[\"DE\"]");
|
||||
when(countryPrefMapper.selectById(23L)).thenReturn(existing);
|
||||
ProductRiskCountryPreferenceVo updated = service.saveCountryPreference(prefRequest(23L, List.of("DE", "UK")));
|
||||
|
||||
assertEquals(List.of("DE", "UK"), updated.getCountryCodes());
|
||||
verify(countryPrefMapper).updateById(existing);
|
||||
}
|
||||
}
|
||||
+107
@@ -0,0 +1,107 @@
|
||||
package com.nanri.aiimage.modules.split.service;
|
||||
|
||||
import com.baomidou.mybatisplus.core.MybatisConfiguration;
|
||||
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
|
||||
import com.nanri.aiimage.common.exception.BusinessException;
|
||||
import com.nanri.aiimage.config.StorageProperties;
|
||||
import com.nanri.aiimage.modules.file.service.oss.OssStorageService;
|
||||
import com.nanri.aiimage.modules.task.mapper.FileResultMapper;
|
||||
import com.nanri.aiimage.modules.task.mapper.FileTaskMapper;
|
||||
import com.nanri.aiimage.modules.task.model.entity.FileResultEntity;
|
||||
import org.apache.ibatis.builder.MapperBuilderAssistant;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyLong;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
* 数据拆分的结果文件归属校验(2026-09 审查 G4:该模块此前零测试文件)。
|
||||
*
|
||||
* <p>只覆盖"能安全断言、且与安全相关"的部分:下载与删除的历史记录必须属于本人且模块匹配。
|
||||
* `run(...)` 依赖真实 workbook 与 OSS 上传,另需集成环境,此处不构造。
|
||||
*/
|
||||
class SplitRunServiceTest {
|
||||
|
||||
private static final String MODULE_TYPE = "SPLIT";
|
||||
|
||||
@BeforeAll
|
||||
static void initializeMybatisMetadata() {
|
||||
TableInfoHelper.initTableInfo(
|
||||
new MapperBuilderAssistant(new MybatisConfiguration(), ""),
|
||||
FileResultEntity.class);
|
||||
}
|
||||
|
||||
private final FileTaskMapper fileTaskMapper = mock(FileTaskMapper.class);
|
||||
private final FileResultMapper fileResultMapper = mock(FileResultMapper.class);
|
||||
private final StorageProperties storageProperties = mock(StorageProperties.class);
|
||||
private final OssStorageService ossStorageService = mock(OssStorageService.class);
|
||||
|
||||
private SplitRunService service;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
service = new SplitRunService(fileTaskMapper, fileResultMapper, storageProperties, ossStorageService);
|
||||
}
|
||||
|
||||
private static FileResultEntity result(Long id, String moduleType, Long userId, String url) {
|
||||
FileResultEntity entity = new FileResultEntity();
|
||||
entity.setId(id);
|
||||
entity.setModuleType(moduleType);
|
||||
entity.setUserId(userId);
|
||||
entity.setResultFileUrl(url);
|
||||
return entity;
|
||||
}
|
||||
|
||||
@Test
|
||||
void deleteHistoryRejectsMissingOrForeignOrOtherModuleRecord() {
|
||||
when(fileResultMapper.selectById(99L)).thenReturn(null);
|
||||
assertThrows(BusinessException.class, () -> service.deleteHistory(99L, 23L), "记录不存在");
|
||||
|
||||
when(fileResultMapper.selectById(100L)).thenReturn(result(100L, MODULE_TYPE, 88L, "oss/x.xlsx"));
|
||||
assertThrows(BusinessException.class, () -> service.deleteHistory(100L, 23L), "他人记录不可删");
|
||||
|
||||
when(fileResultMapper.selectById(101L)).thenReturn(result(101L, "DEDUPE", 23L, "oss/x.xlsx"));
|
||||
assertThrows(BusinessException.class, () -> service.deleteHistory(101L, 23L), "跨模块记录不可删");
|
||||
|
||||
verify(fileResultMapper, never()).deleteById(anyLong());
|
||||
}
|
||||
|
||||
@Test
|
||||
void deleteHistoryRemovesOwnSplitRecord() {
|
||||
when(fileResultMapper.selectById(102L)).thenReturn(result(102L, MODULE_TYPE, 23L, "oss/x.xlsx"));
|
||||
|
||||
service.deleteHistory(102L, 23L);
|
||||
|
||||
verify(fileResultMapper).deleteById(102L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void downloadUrlRejectsForeignOrMissingFile() {
|
||||
when(fileResultMapper.selectById(200L)).thenReturn(result(200L, MODULE_TYPE, 88L, "oss/x.xlsx"));
|
||||
assertThrows(BusinessException.class, () -> service.getResultDownloadUrl(200L, 23L));
|
||||
|
||||
when(fileResultMapper.selectById(201L)).thenReturn(result(201L, MODULE_TYPE, 23L, null));
|
||||
assertThrows(BusinessException.class, () -> service.getResultDownloadUrl(201L, 23L), "无结果文件时不可下载");
|
||||
|
||||
when(fileResultMapper.selectById(202L)).thenReturn(result(202L, "CONVERT", 23L, "oss/x.xlsx"));
|
||||
assertThrows(BusinessException.class, () -> service.getResultDownloadUrl(202L, 23L));
|
||||
|
||||
verify(ossStorageService, never()).generateFreshDownloadUrl(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void downloadUrlReturnsFreshSignedUrlForOwnRecord() {
|
||||
when(fileResultMapper.selectById(203L)).thenReturn(result(203L, MODULE_TYPE, 23L, "oss/split.xlsx"));
|
||||
when(ossStorageService.generateFreshDownloadUrl("oss/split.xlsx")).thenReturn("https://oss.example/split.xlsx?sig=1");
|
||||
|
||||
assertEquals("https://oss.example/split.xlsx?sig=1", service.getResultDownloadUrl(203L, 23L));
|
||||
}
|
||||
}
|
||||
+21
-13
@@ -12,16 +12,19 @@ import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
* BrandResultFileJobHandler 测试(任务 71)。
|
||||
* BRAND 的 resolveResultObjectKey 特例(无 resultId 也走)保留在 Worker 公共路径 resolveResultFileUrl,
|
||||
* Handler 不接管 URL 解析;cleanup 为空(原 cleanupAfterSuccess 无 BRAND 分支,不走 payload.deleteLatest)。
|
||||
* BrandResultFileJobHandler 测试(任务 71 / 2026-09 边界收敛)。
|
||||
* BRAND 的 resolveResultObjectKey 特例(无 resultId 也走)由本 Handler 的 resolveResultFileUrl 钩子承担
|
||||
* (原实现的 Worker 直连品牌模块构成 task → 业务依赖,已翻转为 SPI 钩子);
|
||||
* cleanup 为空(原 cleanupAfterSuccess 无 BRAND 分支,不走 payload.deleteLatest)。
|
||||
*/
|
||||
class BrandResultFileJobHandlerTest {
|
||||
|
||||
@@ -49,18 +52,23 @@ class BrandResultFileJobHandlerTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void resultUrlBrandSpecial() throws IOException {
|
||||
// Handler 不接管 URL 解析:无 resolveResultObjectKey / resolveResultFileUrl 方法
|
||||
for (java.lang.reflect.Method method : handler.getClass().getDeclaredMethods()) {
|
||||
assertFalse(method.getName().contains("resolve"),
|
||||
"Handler 不接管 URL 解析:" + method.getName());
|
||||
}
|
||||
// 特例仍留在 Worker 公共路径 resolveResultFileUrl(含 BRAND 分支,无 resultId 也走)
|
||||
void resultUrlBrandSpecialResolvedByHandlerHook() throws IOException {
|
||||
// 2026-09:URL 解析特例改由本 Handler 的 resolveResultFileUrl 钩子承担,Worker 不再 import 品牌模块
|
||||
TaskFileJobEntity job = new TaskFileJobEntity();
|
||||
job.setTaskId(33L);
|
||||
when(brandTaskService.resolveResultObjectKey(33L)).thenReturn("oss/brand/33.json");
|
||||
|
||||
assertEquals("oss/brand/33.json", handler.resolveResultFileUrl(job));
|
||||
assertNull(handler.resolveResultFileUrl(null), "空 job 返回 null 交回默认路径");
|
||||
assertNull(handler.resolveResultFileUrl(new TaskFileJobEntity()), "无 taskId 返回 null 交回默认路径");
|
||||
|
||||
String source = workerSource();
|
||||
assertTrue(source.contains("resolveResultFileUrl"), "Worker 保留 resolveResultFileUrl 公共路径");
|
||||
assertTrue(source.contains("resolveResultObjectKey"), "Worker 保留 BRAND 的 resolveResultObjectKey 特例");
|
||||
assertTrue(source.contains("\"BRAND\"") || source.contains("BRAND"),
|
||||
"Worker resolveResultFileUrl 含 BRAND 分支");
|
||||
assertTrue(source.contains("handler.resolveResultFileUrl")
|
||||
|| source.contains("resolveResultFileUrl(job)"),
|
||||
"Worker 通过 Handler 钩子解析模块自定义 URL");
|
||||
assertFalse(source.contains("resolveResultObjectKey"), "Worker 不再直连品牌模块");
|
||||
assertFalse(source.contains("modules.brand"), "Worker 不再 import 品牌模块");
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
+6
-5
@@ -38,16 +38,17 @@ class ResultFileJobHandlerTest {
|
||||
|
||||
@Test
|
||||
void interfaceMethodsPresent() throws Exception {
|
||||
// 2026-09:新增 fallbackAssembleOnFailure(job 重试耗尽时用部分数据兜底生成结果文件),
|
||||
// 接口方法从 7 个增到 8 个,同步更新契约断言。
|
||||
assertEquals(8, ResultFileJobHandler.class.getDeclaredMethods().length,
|
||||
"接口方法数量为 8(moduleType/process/onSuccess/cleanup/onFailure/"
|
||||
+ "fallbackAssembleOnFailure/supportsAsyncOffload/isOwnerScoped)");
|
||||
// 2026-09:新增 resolveResultFileUrl(模块自定义结果对象 key 解析,BRAND 用),
|
||||
// 接口方法从 8 个增到 9 个,同步更新契约断言。
|
||||
assertEquals(9, ResultFileJobHandler.class.getDeclaredMethods().length,
|
||||
"接口方法数量为 9(moduleType/process/onSuccess/cleanup/onFailure/"
|
||||
+ "fallbackAssembleOnFailure/resolveResultFileUrl/supportsAsyncOffload/isOwnerScoped)");
|
||||
assertNotNull(methodOf(ResultFileJobHandler.class, "moduleType"));
|
||||
assertNotNull(methodOf(ResultFileJobHandler.class, "process", TaskFileJobEntity.class));
|
||||
assertNotNull(methodOf(ResultFileJobHandler.class, "onSuccess", TaskFileJobEntity.class));
|
||||
assertNotNull(methodOf(ResultFileJobHandler.class, "cleanup", TaskFileJobEntity.class));
|
||||
assertNotNull(methodOf(ResultFileJobHandler.class, "onFailure", TaskFileJobEntity.class, String.class));
|
||||
assertNotNull(methodOf(ResultFileJobHandler.class, "resolveResultFileUrl", TaskFileJobEntity.class));
|
||||
assertNotNull(methodOf(ResultFileJobHandler.class, "supportsAsyncOffload"));
|
||||
assertNotNull(methodOf(ResultFileJobHandler.class, "isOwnerScoped"));
|
||||
}
|
||||
|
||||
-1
@@ -89,7 +89,6 @@ class TaskResultFileJobWorkerCleanupTest {
|
||||
mock(FileResultMapper.class),
|
||||
mock(TaskFileJobLocalDispatcher.class),
|
||||
mock(InstanceMetadata.class),
|
||||
withdraw, brand,
|
||||
registry);
|
||||
}
|
||||
|
||||
|
||||
-1
@@ -92,7 +92,6 @@ class TaskResultFileJobWorkerDispatchTest {
|
||||
mock(FileResultMapper.class),
|
||||
mock(TaskFileJobLocalDispatcher.class),
|
||||
mock(InstanceMetadata.class),
|
||||
withdraw, brand,
|
||||
registry);
|
||||
}
|
||||
|
||||
|
||||
-1
@@ -100,7 +100,6 @@ class TaskResultFileJobWorkerEquivalenceTest {
|
||||
fileResultMapper,
|
||||
mock(TaskFileJobLocalDispatcher.class),
|
||||
instanceMetadata,
|
||||
withdraw, brand,
|
||||
registry);
|
||||
Field executorField = TaskResultFileJobWorker.class.getDeclaredField("taskQueueExecutor");
|
||||
executorField.setAccessible(true);
|
||||
|
||||
-1
@@ -99,7 +99,6 @@ class TaskResultFileJobWorkerFailureNotifyTest {
|
||||
mock(FileResultMapper.class),
|
||||
mock(TaskFileJobLocalDispatcher.class),
|
||||
instanceMetadata,
|
||||
withdraw, brand,
|
||||
registry);
|
||||
}
|
||||
|
||||
|
||||
-1
@@ -102,7 +102,6 @@ class TaskResultFileJobWorkerOffloadTest {
|
||||
mock(FileResultMapper.class),
|
||||
mock(TaskFileJobLocalDispatcher.class),
|
||||
instanceMetadata,
|
||||
withdraw, brand,
|
||||
registry);
|
||||
setTaskQueueExecutor(worker, taskQueueExecutor);
|
||||
return worker;
|
||||
|
||||
-1
@@ -96,7 +96,6 @@ class TaskResultFileJobWorkerOrphanTest {
|
||||
mock(FileResultMapper.class),
|
||||
mock(TaskFileJobLocalDispatcher.class),
|
||||
instanceMetadata,
|
||||
withdraw, brand,
|
||||
registry);
|
||||
}
|
||||
|
||||
|
||||
-1
@@ -93,7 +93,6 @@ class TaskResultFileJobWorkerOwnerRoutingTest {
|
||||
mock(FileResultMapper.class),
|
||||
localDispatcher,
|
||||
instanceMetadata,
|
||||
withdraw, brand,
|
||||
new ResultFileJobHandlerRegistry(handlers));
|
||||
set(worker, "taskQueueExecutor", (org.springframework.core.task.TaskExecutor) Runnable::run);
|
||||
set(worker, "localWorkerEnabled", true);
|
||||
|
||||
-1
@@ -96,7 +96,6 @@ class TaskResultFileJobWorkerOwnerScopedTest {
|
||||
mock(FileResultMapper.class),
|
||||
mock(TaskFileJobLocalDispatcher.class),
|
||||
instanceMetadata,
|
||||
withdraw, brand,
|
||||
registry);
|
||||
}
|
||||
|
||||
|
||||
-1
@@ -72,7 +72,6 @@ class TaskResultFileJobWorkerSpecialRuleTest {
|
||||
fileResultMapper,
|
||||
mock(TaskFileJobLocalDispatcher.class),
|
||||
instanceMetadata,
|
||||
withdraw, brand,
|
||||
handlerRegistry);
|
||||
java.lang.reflect.Field executorField =
|
||||
TaskResultFileJobWorker.class.getDeclaredField("taskQueueExecutor");
|
||||
|
||||
-1
@@ -96,7 +96,6 @@ class TaskResultFileJobWorkerStuckScanTest {
|
||||
mock(FileResultMapper.class),
|
||||
mock(TaskFileJobLocalDispatcher.class),
|
||||
instanceMetadata,
|
||||
withdraw, brand,
|
||||
registry);
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@ package com.nanri.aiimage.modules.usersecret.controller;
|
||||
|
||||
import com.nanri.aiimage.common.api.ApiResponse;
|
||||
import com.nanri.aiimage.common.exception.BusinessException;
|
||||
import com.nanri.aiimage.modules.admin.support.AdminAuthSupport;
|
||||
import com.nanri.aiimage.common.security.AdminAuthSupport;
|
||||
import com.nanri.aiimage.modules.usersecret.service.UserApiSecretService;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
+7
-6
@@ -2,13 +2,13 @@ package com.nanri.aiimage.modules.usersecret.service;
|
||||
|
||||
import com.nanri.aiimage.common.security.ShopCredentialCryptoService;
|
||||
import com.nanri.aiimage.config.UserSecretProperties;
|
||||
import com.nanri.aiimage.modules.admin.support.AdminAuthSupport;
|
||||
import com.nanri.aiimage.common.security.AdminAuthSupport;
|
||||
import com.nanri.aiimage.modules.notification.service.NotificationDispatchService;
|
||||
import com.nanri.aiimage.modules.permission.mapper.AdminUserMapper;
|
||||
import com.nanri.aiimage.modules.permission.model.entity.AdminUserEntity;
|
||||
import com.nanri.aiimage.common.mapper.AdminUserMapper;
|
||||
import com.nanri.aiimage.common.model.entity.AdminUserEntity;
|
||||
import com.nanri.aiimage.modules.permission.support.UserDataScopeSupport;
|
||||
import com.nanri.aiimage.modules.shopkey.model.dto.UserGroupRef;
|
||||
import com.nanri.aiimage.modules.shopkey.model.entity.ShopManageGroupEntity;
|
||||
import com.nanri.aiimage.common.model.entity.ShopManageGroupEntity;
|
||||
import com.nanri.aiimage.modules.usersecret.client.JikipProxyClient;
|
||||
import com.nanri.aiimage.modules.usersecret.mapper.UserApiSecretMapper;
|
||||
import com.nanri.aiimage.modules.usersecret.model.dto.AdminUserSecretQuery;
|
||||
@@ -28,6 +28,7 @@ import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
import com.nanri.aiimage.common.mapper.ShopManageGroupMapper;
|
||||
|
||||
class UserApiSecretServiceTest {
|
||||
|
||||
@@ -37,8 +38,8 @@ class UserApiSecretServiceTest {
|
||||
private final JikipProxyClient jikipProxyClient = mock(JikipProxyClient.class);
|
||||
private final AdminUserMapper adminUserMapper = mock(AdminUserMapper.class);
|
||||
private final AdminAuthSupport adminAuthSupport = mock(AdminAuthSupport.class);
|
||||
private final com.nanri.aiimage.modules.shopkey.mapper.ShopManageGroupMapper adminGroupMapper =
|
||||
mock(com.nanri.aiimage.modules.shopkey.mapper.ShopManageGroupMapper.class);
|
||||
private final com.nanri.aiimage.common.mapper.ShopManageGroupMapper adminGroupMapper =
|
||||
mock(com.nanri.aiimage.common.mapper.ShopManageGroupMapper.class);
|
||||
private final UserDataScopeSupport userDataScopeSupport = mock(UserDataScopeSupport.class);
|
||||
private final NotificationDispatchService notificationDispatchService = mock(NotificationDispatchService.class);
|
||||
private final UserSecretProperties userSecretProperties = new UserSecretProperties();
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user