diff --git a/README.md b/README.md index 0680e5ba..807d60a4 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,6 @@ # 项目介绍 -一个通用的聚合服务:通用黑名单服务 +一个通用的聚合服务: +* 通用黑白名单服务 # vm参数 ## dev环境 @@ -18,13 +19,19 @@ -DCUSTOM_ENV=test -Dserver.port=8080 -# 各模块结构 +# 黑白名单中各模块结构 ## all-login 登录白名单 ~~~json { "phone": "手机号" } ~~~ +## ou_register_black_list 企业注册黑名单 +~~~json +{ + "phone": "手机号" +} +~~~ ## attendance-pass 考勤黑名单 ~~~json { diff --git a/black-list/black-list-api/pom.xml b/black-list/black-list-api/pom.xml index 5b75d317..ffd67112 100644 --- a/black-list/black-list-api/pom.xml +++ b/black-list/black-list-api/pom.xml @@ -22,5 +22,9 @@ cn.axzo.framework axzo-common-domain + + jakarta.servlet + jakarta.servlet-api + diff --git a/black-list/black-list-api/src/main/java/cn/axzo/nanopart/api/BlackAndWhiteListApi.java b/black-list/black-list-api/src/main/java/cn/axzo/nanopart/api/BlackAndWhiteListApi.java index 1a1bba5a..99cf81af 100644 --- a/black-list/black-list-api/src/main/java/cn/axzo/nanopart/api/BlackAndWhiteListApi.java +++ b/black-list/black-list-api/src/main/java/cn/axzo/nanopart/api/BlackAndWhiteListApi.java @@ -1,8 +1,11 @@ package cn.axzo.nanopart.api; import cn.axzo.framework.domain.web.result.ApiResult; +import cn.axzo.nanopart.api.annotation.CheckSign; import cn.axzo.nanopart.api.constant.enums.ListTypeEnum; import cn.axzo.nanopart.api.request.BlackAndWhiteListExcelImportReq; +import cn.axzo.nanopart.api.request.BlackAndWhiteListInternalSyncReq; +import cn.axzo.nanopart.api.request.BlackAndWhiteListPlatformSyncReq; import cn.axzo.nanopart.api.request.BlackAndWhiteListReq; import cn.axzo.nanopart.api.response.BlackAndWhiteListExcelImportResp; import cn.axzo.nanopart.api.response.BlackAndWhiteListResp; @@ -13,6 +16,7 @@ import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RequestPart; +import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.multipart.MultipartFile; import javax.validation.constraints.NotBlank; @@ -84,4 +88,35 @@ public interface BlackAndWhiteListApi { @PostMapping("api/black-white-list/import") ApiResult importBlackAndWhiteListExcel(@RequestParam(value = "type") @NotNull(message = "名单类型不能为空") ListTypeEnum type, @RequestParam(value = "module") @NotBlank(message = "模块不能为空") String module, @RequestParam(value = "url", required = false) String url, @RequestPart(value = "file") MultipartFile file); + + /** + * 平台同步黑白名单 + *

+ * 每次最多同步1000条记录且整体上同步策略采用全量覆盖策略 + * 若总记录数超过1000条请自行分片多次调用 + *

+ * + *

+ * 该接口需要验签,参与签名的字段为header中的:appKey,timestamp, + * 签名字段为header中的sign + *

+ * + * @param req @{link BlackAndWhiteListPlatformSyncReq} + * @return true-同步成功,false-同步失败 + */ + @PostMapping("api/black-white-list/plat-sync") + ApiResult platformSync(@RequestBody @Validated BlackAndWhiteListPlatformSyncReq req); + + /** + * TODO 该接口目前耗时较长待优化 + * 内部自同步黑白名单 + *

+ * 例如将其他模块的黑名单同步到登录模块黑名单 + *

+ * + * @param req @{link BlackAndWhiteListInternalSyncReq} + * @return 同步结果,成功返回同步记录总数 + */ + @PostMapping("api/black-white-list/inner-sync") + ApiResult internalSync(@RequestBody @Validated BlackAndWhiteListInternalSyncReq req); } diff --git a/black-list/black-list-api/src/main/java/cn/axzo/nanopart/api/SignatureUtilApi.java b/black-list/black-list-api/src/main/java/cn/axzo/nanopart/api/SignatureUtilApi.java new file mode 100644 index 00000000..9d63fa35 --- /dev/null +++ b/black-list/black-list-api/src/main/java/cn/axzo/nanopart/api/SignatureUtilApi.java @@ -0,0 +1,42 @@ +package cn.axzo.nanopart.api; + +import cn.axzo.framework.domain.web.result.ApiResult; +import cn.axzo.nanopart.api.request.SignatureGetReq; +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestParam; + +import java.security.KeyPair; +import java.util.Map; + +/** + * 签名工具类 + * + * @author chenwenjian + * @version 1.0 + * @date 2024/1/2 16:53 + */ +@FeignClient(name = "nanopart", url = "http://nanopart:8080") +public interface SignatureUtilApi { + + /** + * 生成公私钥对 + * + * @param algorithm 算法,例如:RSA + * @return 公私钥对 + */ + @GetMapping("api/signature/generateKeyPair") + ApiResult> generateKeyPair(@RequestParam(value = "algorithm") String algorithm); + + /** + * 获取签名 + * + * @param req 请求 + * @return 签名 + */ + @PostMapping("api/signature/getSignature") + ApiResult getSignature(@RequestBody @Validated SignatureGetReq req); +} diff --git a/black-list/black-list-api/src/main/java/cn/axzo/nanopart/api/annotation/CheckSign.java b/black-list/black-list-api/src/main/java/cn/axzo/nanopart/api/annotation/CheckSign.java new file mode 100644 index 00000000..aeabc395 --- /dev/null +++ b/black-list/black-list-api/src/main/java/cn/axzo/nanopart/api/annotation/CheckSign.java @@ -0,0 +1,29 @@ +package cn.axzo.nanopart.api.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 签名验证注解 + * + * @author chenwenjian + * @version 1.0 + * @date 2024/1/2 10:37 + */ +@Documented +@Target({ElementType.METHOD, ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +public @interface CheckSign { + /** + * 签名字段,默认为sign + */ + String signField() default "sign"; + + /** + * header中需要参与签名的字段,若为空,则除签名字段外全部参与签名 + */ + String[] requireSignFields() default {}; +} diff --git a/black-list/black-list-api/src/main/java/cn/axzo/nanopart/api/aspect/CheckSignAspect.java b/black-list/black-list-api/src/main/java/cn/axzo/nanopart/api/aspect/CheckSignAspect.java new file mode 100644 index 00000000..704bb61d --- /dev/null +++ b/black-list/black-list-api/src/main/java/cn/axzo/nanopart/api/aspect/CheckSignAspect.java @@ -0,0 +1,164 @@ +package cn.axzo.nanopart.api.aspect; + +import cn.axzo.framework.domain.web.BizException; +import cn.axzo.framework.domain.web.code.RespCode; +import cn.axzo.nanopart.api.annotation.CheckSign; +import cn.axzo.nanopart.api.common.util.StringUtil; +import cn.axzo.nanopart.api.config.SignManager; +import cn.hutool.core.util.ArrayUtil; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.aspectj.lang.JoinPoint; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Before; +import org.aspectj.lang.annotation.Pointcut; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; +import org.springframework.util.CollectionUtils; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +import javax.annotation.Resource; +import javax.servlet.http.HttpServletRequest; + +import java.util.Collections; +import java.util.Comparator; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Objects; +import java.util.stream.Collectors; + +/** + * @author chenwenjian + * @version 1.0 + * @date 2024/1/2 10:05 + */ +@Slf4j +@Aspect +@Order(1) +@Component +public class CheckSignAspect { + + @Resource + private SignManager signManager; + + @Pointcut(value = "@annotation(org.springframework.web.bind.annotation.RequestMapping)") + public void requestMapping() { + } + + @Pointcut(value = "@annotation(org.springframework.web.bind.annotation.PostMapping)") + public void postMapping() { + log.info("post"); + } + + @Pointcut(value = "@annotation(org.springframework.web.bind.annotation.GetMapping)") + public void getMapping() { + } + + @Pointcut(value = "@annotation(org.springframework.web.bind.annotation.PutMapping)") + public void putMapping() { + } + + @Pointcut(value = "@annotation(org.springframework.web.bind.annotation.DeleteMapping)") + public void deleteMapping() { + } + + @Pointcut(value = "@annotation(org.springframework.web.bind.annotation.PatchMapping)") + public void patchMapping() { + } + + @Pointcut("requestMapping() || postMapping() || getMapping() || putMapping() || deleteMapping()|| patchMapping()") + public void mappingAnnotations() { + } + + // @Pointcut(value = "@annotation(cn.axzo.nanopart.api.annotation.CheckSign)") + // public void checkSign() { + // } + + /** + * 切入含有@CheckSign && @RestController 注解的类 + */ + @Before(value = "@within(checkSign) && @within(restController)", argNames = "joinPoint,checkSign,restController") + public void classHandler(JoinPoint joinPoint, CheckSign checkSign, RestController restController) { + handle(joinPoint, checkSign); + } + + /** + * 切入含有@checkSign && @RequestMapping/@PostMapping/@GetMapping/@PutMapping/@DeleteMapping/@PatchMapping 之一注解的方法 + */ + @Before(value = "@annotation(checkSign)") + public void methodHandler(JoinPoint joinPoint, CheckSign checkSign) { + handle(joinPoint, checkSign); + } + + @SneakyThrows + public void handle(JoinPoint joinPoint, CheckSign checkSign) { + // 验签 + verifySignature(checkSign); + } + + /** + * 验签 + * + * @param checkSign 注解,包含签名字段名等配置 + */ + private void verifySignature(CheckSign checkSign) { + HttpServletRequest httpRequest = ((ServletRequestAttributes) Objects + .requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest(); + + // 获取调用方appKey,检查是否是受信任调用方 + String appKey = httpRequest.getHeader("appKey"); + if (StringUtils.isEmpty(appKey)) { + throw new BizException(new RespCode("403", "验签失败")); + } + + // 从请求头中提取签名 + String sign = httpRequest.getHeader(checkSign.signField()); + if (StringUtils.isEmpty(sign)) { + throw new BizException(new RespCode("403", "验签失败")); + } + + // 从Header中获取需要验签的参数字段,若字段值为空则该字段不计入签名计算 + Map attendFields = new HashMap<>(); + // 无指定字段则所有字段参与计算签名 + if (ArrayUtil.isEmpty(checkSign.requireSignFields())) { + Enumeration headerNames = httpRequest.getHeaderNames(); + while (headerNames.hasMoreElements()) { + String headerName = headerNames.nextElement(); + String value = httpRequest.getHeader(headerName); + attendFields.put(headerName, value); + } + } else { + // 有指定参与计算签名的字段,则只提取指定字段参与计算签名 + for (String field : checkSign.requireSignFields()) { + String value = httpRequest.getHeader(field); + attendFields.put(field, value); + } + } + // 对所有参与签名计算的字段去除空值和去重后按照字段名的ASCII码从小到大排序(字典序) + Map sortedAttendFields = attendFields.entrySet() + .stream() + .filter(entry -> !StringUtils.isEmpty(entry.getValue())) + .distinct() + .sorted(Map.Entry.comparingByKey()) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (oldValue, newValue) -> oldValue, LinkedHashMap::new)); + + // 拼接所有签名数据为字符串 + String data = StringUtil.extracted(sortedAttendFields); + log.info("参与签名计算data:{}", data); + + try { + boolean verify = signManager.verify(appKey, data, sign); + if (!verify) { + throw new BizException(new RespCode("403", "验签失败")); + } + } catch (Exception e) { + throw new BizException(new RespCode("403", "验签失败")); + } + + } +} diff --git a/black-list/black-list-api/src/main/java/cn/axzo/nanopart/api/common/util/StringUtil.java b/black-list/black-list-api/src/main/java/cn/axzo/nanopart/api/common/util/StringUtil.java new file mode 100644 index 00000000..0fb23dd6 --- /dev/null +++ b/black-list/black-list-api/src/main/java/cn/axzo/nanopart/api/common/util/StringUtil.java @@ -0,0 +1,35 @@ +package cn.axzo.nanopart.api.common.util; + +import org.apache.commons.lang3.StringUtils; +import org.springframework.util.CollectionUtils; + +import java.util.Map; + +/** + * @author chenwenjian + * @version 1.0 + * @date 2024/1/4 10:53 + */ +public class StringUtil { + + /** + * 将Map中的参数拼接成字符串 + * + * @param params 键值对 + * @return 拼接后的字符串 + */ + public static String extracted(Map params) { + if (CollectionUtils.isEmpty(params)) { + return StringUtils.EMPTY; + } + StringBuilder data = new StringBuilder(); + for (Map.Entry entry : params.entrySet()) { + if (data.length() > 0) { + data.append("&"); + } + data.append(entry.getKey()).append("=").append(entry.getValue().toString()); + } + return data.toString(); + } + +} diff --git a/black-list/black-list-api/src/main/java/cn/axzo/nanopart/api/config/NanopartApiAutoConfiguration.java b/black-list/black-list-api/src/main/java/cn/axzo/nanopart/api/config/NanopartApiAutoConfiguration.java index 816d5e34..8ff5ab3f 100644 --- a/black-list/black-list-api/src/main/java/cn/axzo/nanopart/api/config/NanopartApiAutoConfiguration.java +++ b/black-list/black-list-api/src/main/java/cn/axzo/nanopart/api/config/NanopartApiAutoConfiguration.java @@ -1,9 +1,10 @@ package cn.axzo.nanopart.api.config; -import cn.axzo.nanopart.api.constant.NanopartConstant; +import cn.axzo.nanopart.api.constant.BlackAndWhiteListConstant; import org.springframework.cloud.openfeign.EnableFeignClients; +import org.springframework.context.annotation.EnableAspectJAutoProxy; -@EnableFeignClients(NanopartConstant.BASIC_FEIGN_PACKAGE) +@EnableFeignClients(BlackAndWhiteListConstant.BASIC_FEIGN_PACKAGE) public class NanopartApiAutoConfiguration { } diff --git a/black-list/black-list-api/src/main/java/cn/axzo/nanopart/api/config/SignConfigProps.java b/black-list/black-list-api/src/main/java/cn/axzo/nanopart/api/config/SignConfigProps.java new file mode 100644 index 00000000..9b5b09f9 --- /dev/null +++ b/black-list/black-list-api/src/main/java/cn/axzo/nanopart/api/config/SignConfigProps.java @@ -0,0 +1,47 @@ +package cn.axzo.nanopart.api.config; + +import cn.hutool.crypto.asymmetric.SignAlgorithm; +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +import java.util.Map; + +/** + * 签名配置 + * + * @author chenwenjian + * @version 1.0 + * @date 2024/1/2 14:33 + */ +@Data +@Component +@ConfigurationProperties(prefix = "api.sign") +public class SignConfigProps { + + /** + * 是否启用签名 + */ + private Boolean enable = true; + + private Map keyPairs; + + @Data + public static class KeyPair { + /** + * 签名算法 + */ + private SignAlgorithm algorithm; + + /** + * 私钥 + */ + private String privateKey; + + + /** + * 公钥 + */ + private String publicKey; + } +} diff --git a/black-list/black-list-api/src/main/java/cn/axzo/nanopart/api/config/SignManager.java b/black-list/black-list-api/src/main/java/cn/axzo/nanopart/api/config/SignManager.java new file mode 100644 index 00000000..d3121de9 --- /dev/null +++ b/black-list/black-list-api/src/main/java/cn/axzo/nanopart/api/config/SignManager.java @@ -0,0 +1,75 @@ +package cn.axzo.nanopart.api.config; + +import cn.hutool.core.util.HexUtil; +import cn.hutool.crypto.SecureUtil; +import cn.hutool.crypto.asymmetric.Sign; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; +import org.springframework.util.CollectionUtils; + +import java.nio.charset.StandardCharsets; +import java.util.Objects; + +/** + * 签名管理器 + * + * @author chenwenjian + * @version 1.0 + * @date 2024/1/2 15:41 + */ +@Component +@RequiredArgsConstructor +public class SignManager { + + private final SignConfigProps signConfigProps; + + /** + * 生成签名 + * 生成的签名为十六进制字符串 + * + * @param appKey 调用方 + * @param rawData 待签名数据,格式:所有需要参与签名计算的参数去重后按照字段名的ASCII码从小到大排序(字典序)后,使用URL键值对的格式(即key1=value1&key2=value2…)拼接成字符串(不包含签名字段) + * @return 签名 + */ + public String sign(String appKey, String rawData) { + Sign sign = getSignByAppKey(appKey); + if (Objects.isNull(sign)) { + return null; + } + return sign.signHex(rawData.getBytes(StandardCharsets.UTF_8)); + } + + /** + * 验证签名 + * + * @param appKey 调用方 + * @param rawData 待验证数据 + * @param rawSign 签名 + * @return true:验证成功 false:验证失败 + */ + public boolean verify(String appKey, String rawData, String rawSign) { + Sign sign = getSignByAppKey(appKey); + if (Objects.isNull(sign)) { + return false; + } + return sign.verify(rawData.getBytes(StandardCharsets.UTF_8), HexUtil.decodeHex(rawSign)); + } + + /** + * 获取签名 + * + * @param appKey 调用方 + * @return 签名算法对象 {@link Sign} + */ + public Sign getSignByAppKey(String appKey) { + if (CollectionUtils.isEmpty(signConfigProps.getKeyPairs())) { + return null; + } + SignConfigProps.KeyPair keyPair = signConfigProps.getKeyPairs().get(appKey); + if (Objects.isNull(keyPair)) { + return null; + } + return SecureUtil.sign(keyPair.getAlgorithm(), keyPair.getPrivateKey(), keyPair.getPublicKey()); + } + +} diff --git a/black-list/black-list-api/src/main/java/cn/axzo/nanopart/api/constant/NanopartConstant.java b/black-list/black-list-api/src/main/java/cn/axzo/nanopart/api/constant/BlackAndWhiteListConstant.java similarity index 87% rename from black-list/black-list-api/src/main/java/cn/axzo/nanopart/api/constant/NanopartConstant.java rename to black-list/black-list-api/src/main/java/cn/axzo/nanopart/api/constant/BlackAndWhiteListConstant.java index bd12bdf2..21eee5e4 100644 --- a/black-list/black-list-api/src/main/java/cn/axzo/nanopart/api/constant/NanopartConstant.java +++ b/black-list/black-list-api/src/main/java/cn/axzo/nanopart/api/constant/BlackAndWhiteListConstant.java @@ -7,7 +7,7 @@ package cn.axzo.nanopart.api.constant; * @modifiedBy: * @version: 1.0 */ -public class NanopartConstant { +public class BlackAndWhiteListConstant { public static final String BASIC_FEIGN_PACKAGE = "cn.axzo.nanopart.api"; public static final String PHONE_REGEXP = "^1[3456789]\\d{9}$"; diff --git a/black-list/black-list-api/src/main/java/cn/axzo/nanopart/api/request/BlackAndWhiteListInReq.java b/black-list/black-list-api/src/main/java/cn/axzo/nanopart/api/request/BlackAndWhiteListInReq.java index 7c336867..78480b08 100644 --- a/black-list/black-list-api/src/main/java/cn/axzo/nanopart/api/request/BlackAndWhiteListInReq.java +++ b/black-list/black-list-api/src/main/java/cn/axzo/nanopart/api/request/BlackAndWhiteListInReq.java @@ -1,7 +1,7 @@ package cn.axzo.nanopart.api.request; -import cn.axzo.nanopart.api.constant.NanopartConstant; +import cn.axzo.nanopart.api.constant.BlackAndWhiteListConstant; import lombok.Data; import javax.validation.constraints.NotBlank; @@ -21,6 +21,6 @@ public class BlackAndWhiteListInReq { @NotBlank(message = "模块名称不能为空格") private String module; - @Pattern(regexp = NanopartConstant.PHONE_REGEXP,message = "手机号格式错误") + @Pattern(regexp = BlackAndWhiteListConstant.PHONE_REGEXP,message = "手机号格式错误") private String phone; } diff --git a/black-list/black-list-api/src/main/java/cn/axzo/nanopart/api/request/BlackAndWhiteListInternalSyncReq.java b/black-list/black-list-api/src/main/java/cn/axzo/nanopart/api/request/BlackAndWhiteListInternalSyncReq.java new file mode 100644 index 00000000..59fb9529 --- /dev/null +++ b/black-list/black-list-api/src/main/java/cn/axzo/nanopart/api/request/BlackAndWhiteListInternalSyncReq.java @@ -0,0 +1,53 @@ +package cn.axzo.nanopart.api.request; + +import cn.axzo.nanopart.api.constant.enums.ListTypeEnum; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotEmpty; +import javax.validation.constraints.NotNull; +import java.util.List; + +/** + * 黑白名单内部自同步请求 + * + * @author chenwenjian + * @version 1.0 + * @date 2024/1/3 16:46 + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class BlackAndWhiteListInternalSyncReq { + + @NotNull(message = "同步类型不能为空") + private ListTypeEnum type; + + /** + * 同步源模块 + */ + @NotBlank(message = "同步源模块不能为空") + private String sourceModule; + + /** + * 同步源字段 + */ + @NotEmpty(message = "同步源字段不能为空") + private List sourceFields; + + /** + * 同步目标模块 + */ + @NotBlank(message = "同步目标模块不能为空") + private String targetModule; + + /** + * 同步目标字段 + */ + @NotEmpty(message = "同步目标字段不能为空") + private List targetFields; +} diff --git a/black-list/black-list-api/src/main/java/cn/axzo/nanopart/api/request/BlackAndWhiteListPlatformSyncReq.java b/black-list/black-list-api/src/main/java/cn/axzo/nanopart/api/request/BlackAndWhiteListPlatformSyncReq.java new file mode 100644 index 00000000..81b7e2ab --- /dev/null +++ b/black-list/black-list-api/src/main/java/cn/axzo/nanopart/api/request/BlackAndWhiteListPlatformSyncReq.java @@ -0,0 +1,80 @@ +package cn.axzo.nanopart.api.request; + +import cn.axzo.nanopart.api.constant.enums.ListTypeEnum; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import javax.validation.constraints.Min; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotEmpty; +import javax.validation.constraints.NotNull; +import java.util.List; +import java.util.Map; + +/** + * 黑白名单平台同步请求 + * + * @author chenwenjian + * @version 1.0 + * @date 2024/1/2 18:17 + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class BlackAndWhiteListPlatformSyncReq { + + /** + * 名单类型,0为黑名单,1为白名单 + */ + @NotNull(message = "名单类型错误") + private ListTypeEnum type; + + /** + * 模块名,自主定义,例:cms-login + */ + @NotBlank(message = "模块名不能为空") + private String module; + + /** + * 参数,例:[{"name":"username","value":"admin"},{"name":"password","value":"123456"}] + * 单次同步数量最多1000条 + */ + @NotEmpty(message = "参数不能为空") + private List> params; + + /** + * 分片号,从1开始,默认是1,若等于1,则先删除所有上次同步的名单,然后新增本次的名单,若大于1时,则直接新增到黑白名单中 + */ + @Min(value = 1, message = "分片号必须大于1") + @NotNull(message = "分片大小不能为空") + private Long fragment = 1L; + + /** + * 是否同步至登录黑名单 + */ + private Boolean isSyncLogin; + + /** + * 同步至登录黑名单时params中代表手机号的字段名,例:userPhone + *

+ * 因为目前登录黑名单以手机号作为判断依据,所以当isSyncLogin为true时此字段必填 + *

+ */ + private String syncLoginField; + + /** + * 是否同步至注册黑名单 + */ + private Boolean isSyncRegister; + + /** + * 同步至注册黑名单时params中代表手机号的字段名,例:userPhone + *

+ * 因为目前注册黑名单以手机号作为判断依据,所以当isSyncRegister为true时此字段必填 + *

+ */ + private String syncRegisterField; +} \ No newline at end of file diff --git a/black-list/black-list-api/src/main/java/cn/axzo/nanopart/api/request/BlackAndWhiteListReq.java b/black-list/black-list-api/src/main/java/cn/axzo/nanopart/api/request/BlackAndWhiteListReq.java index 1b2e0b8e..650bedc2 100644 --- a/black-list/black-list-api/src/main/java/cn/axzo/nanopart/api/request/BlackAndWhiteListReq.java +++ b/black-list/black-list-api/src/main/java/cn/axzo/nanopart/api/request/BlackAndWhiteListReq.java @@ -1,7 +1,10 @@ package cn.axzo.nanopart.api.request; import cn.axzo.nanopart.api.constant.enums.ListTypeEnum; +import lombok.AllArgsConstructor; +import lombok.Builder; import lombok.Data; +import lombok.NoArgsConstructor; import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotNull; @@ -15,6 +18,9 @@ import java.util.Map; * @version: 1.0 */ @Data +@Builder +@NoArgsConstructor +@AllArgsConstructor public class BlackAndWhiteListReq { /** diff --git a/black-list/black-list-api/src/main/java/cn/axzo/nanopart/api/request/SignatureGetReq.java b/black-list/black-list-api/src/main/java/cn/axzo/nanopart/api/request/SignatureGetReq.java new file mode 100644 index 00000000..30ef7cc2 --- /dev/null +++ b/black-list/black-list-api/src/main/java/cn/axzo/nanopart/api/request/SignatureGetReq.java @@ -0,0 +1,35 @@ +package cn.axzo.nanopart.api.request; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotEmpty; +import java.util.Map; + +/** + * 签名获取请求 + * @author chenwenjian + * @version 1.0 + * @date 2024/1/4 10:15 + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class SignatureGetReq { + + /** + * 应用key,例如:nanopart + */ + @NotBlank(message = "appKey不能为空") + private String appKey; + + /** + * 请求参数,需要参与计算签名的数据,例如:{"id":123456,"name":"张三"} + */ + @NotEmpty(message = "params不能为空") + private Map params; +} diff --git a/black-list/black-list-service/pom.xml b/black-list/black-list-service/pom.xml index 3cd31852..61d27631 100644 --- a/black-list/black-list-service/pom.xml +++ b/black-list/black-list-service/pom.xml @@ -33,5 +33,13 @@ black-list-api 2.0.0-SNAPSHOT + + org.springframework.cloud + spring-cloud-context + + + cn.axzo.pokonyan + pokonyan + diff --git a/black-list/black-list-service/src/main/java/cn/axzo/nanopart/server/constant/BlackAndWhiteListConstant.java b/black-list/black-list-service/src/main/java/cn/axzo/nanopart/server/constant/BlackAndWhiteListConstant.java new file mode 100644 index 00000000..25d6c7d5 --- /dev/null +++ b/black-list/black-list-service/src/main/java/cn/axzo/nanopart/server/constant/BlackAndWhiteListConstant.java @@ -0,0 +1,19 @@ +package cn.axzo.nanopart.server.constant; + +/** + * @author chenwenjian + * @version 1.0 + * @date 2024/1/3 11:28 + */ +public class BlackAndWhiteListConstant { + + /** + * 所有平台登录黑名单模块名 + */ + public static final String ALL_LOGIN = "all-login"; + + /** + * 企业注册黑名单模块名 + */ + public static final String OU_REGISTER_BLACK_LIST = "ou_register_black_list"; +} diff --git a/black-list/black-list-service/src/main/java/cn/axzo/nanopart/server/controller/BlackAndWhiteListController.java b/black-list/black-list-service/src/main/java/cn/axzo/nanopart/server/controller/BlackAndWhiteListController.java index fee9a255..e5ef8c66 100644 --- a/black-list/black-list-service/src/main/java/cn/axzo/nanopart/server/controller/BlackAndWhiteListController.java +++ b/black-list/black-list-service/src/main/java/cn/axzo/nanopart/server/controller/BlackAndWhiteListController.java @@ -3,13 +3,17 @@ package cn.axzo.nanopart.server.controller; import cn.axzo.framework.domain.web.result.ApiResult; import cn.axzo.nanopart.api.BlackAndWhiteListApi; +import cn.axzo.nanopart.api.annotation.CheckSign; import cn.axzo.nanopart.api.constant.enums.ListTypeEnum; import cn.axzo.nanopart.api.request.BlackAndWhiteListExcelImportReq; +import cn.axzo.nanopart.api.request.BlackAndWhiteListInternalSyncReq; +import cn.axzo.nanopart.api.request.BlackAndWhiteListPlatformSyncReq; import cn.axzo.nanopart.api.request.BlackAndWhiteListReq; import cn.axzo.nanopart.api.response.BlackAndWhiteListExcelImportResp; import cn.axzo.nanopart.api.response.BlackAndWhiteListResp; import cn.axzo.nanopart.server.dao.entity.SaasBlackWhiteList; import cn.axzo.nanopart.server.service.BlackAndWhiteListService; +import cn.hutool.json.JSONUtil; import com.alibaba.excel.EasyExcel; import com.alibaba.excel.context.AnalysisContext; import com.alibaba.excel.event.AnalysisEventListener; @@ -44,12 +48,12 @@ public class BlackAndWhiteListController implements BlackAndWhiteListApi { @Override public ApiResult create(BlackAndWhiteListReq req) { - return blackAndWhiteListService.create(req); + return ApiResult.ok(blackAndWhiteListService.create(req)); } @Override public ApiResult delete(BlackAndWhiteListReq req) { - return blackAndWhiteListService.delete(req); + return ApiResult.ok(blackAndWhiteListService.delete(req)); } @Override @@ -59,12 +63,12 @@ public class BlackAndWhiteListController implements BlackAndWhiteListApi { @Override public ApiResult> detail(BlackAndWhiteListReq req) { - return blackAndWhiteListService.detail(req); + return ApiResult.ok(blackAndWhiteListService.detail(req)); } @Override public ApiResult isInBlackOrWhiteList(BlackAndWhiteListReq req) { - return blackAndWhiteListService.isInBlackOrWhiteList(req); + return ApiResult.ok(blackAndWhiteListService.isInBlackOrWhiteList(req)); } @Override @@ -73,4 +77,29 @@ public class BlackAndWhiteListController implements BlackAndWhiteListApi { return ApiResult.ok(blackAndWhiteListService.importBlackAndWhiteListExcel(type, module, url, file)); } + /** + * 平台同步黑白名单 + * + * @param req @{link BlackAndWhiteListPlatformSyncReq} + * @return + */ + @CheckSign(requireSignFields = {"appKey", "timestamp"}) + @Override + public ApiResult platformSync(BlackAndWhiteListPlatformSyncReq req) { + log.info("平台同步参数:req = {}", JSONUtil.toJsonStr(req)); + return ApiResult.ok(blackAndWhiteListService.platformSync(req)); + } + + /** + * 内部自同步黑白名单 + * + * @param req @{link BlackAndWhiteListInternalSyncReq} + * @return 同步结果的记录id + */ + @CheckSign(requireSignFields = {"appKey", "timestamp"}) + @Override + public ApiResult internalSync(BlackAndWhiteListInternalSyncReq req) { + return ApiResult.ok(blackAndWhiteListService.internalSync(req)); + } + } diff --git a/black-list/black-list-service/src/main/java/cn/axzo/nanopart/server/controller/SignatureUtilController.java b/black-list/black-list-service/src/main/java/cn/axzo/nanopart/server/controller/SignatureUtilController.java new file mode 100644 index 00000000..d4ee91e6 --- /dev/null +++ b/black-list/black-list-service/src/main/java/cn/axzo/nanopart/server/controller/SignatureUtilController.java @@ -0,0 +1,66 @@ +package cn.axzo.nanopart.server.controller; + +import cn.axzo.framework.domain.ServiceException; +import cn.axzo.framework.domain.web.result.ApiResult; +import cn.axzo.nanopart.api.SignatureUtilApi; +import cn.axzo.nanopart.api.config.SignManager; +import cn.axzo.nanopart.api.request.SignatureGetReq; +import cn.axzo.nanopart.api.common.util.StringUtil; +import cn.hutool.core.codec.Base64; +import cn.hutool.crypto.SecureUtil; +import cn.hutool.json.JSONUtil; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.RestController; + +import java.security.KeyPair; +import java.util.HashMap; +import java.util.Map; + +/** + * @author chenwenjian + * @version 1.0 + * @date 2024/1/2 16:59 + */ +@Slf4j +@RestController +@RequiredArgsConstructor +public class SignatureUtilController implements SignatureUtilApi { + + private final SignManager signManager; + + /** + * 生成密钥对 + * + * @param algorithm 算法 + * @return 密钥对 + */ + @Override + public ApiResult> generateKeyPair(String algorithm) { + log.info("algorithm = {}", algorithm); + Map keyPair = new HashMap<>(); + try { + KeyPair generateKeyPair = SecureUtil.generateKeyPair(algorithm); + keyPair.put("publicKey", Base64.encode(generateKeyPair.getPublic().getEncoded())); + keyPair.put("privateKey", Base64.encode(generateKeyPair.getPrivate().getEncoded())); + } catch (Exception e) { + throw new ServiceException("密钥对生成失败"); + } + log.info("keyPair = {}", JSONUtil.toJsonStr(keyPair)); + return ApiResult.ok(keyPair); + } + + /** + * 获取签名 + * + * @param req 请求 + * @return 签名 + */ + @Override + public ApiResult getSignature(SignatureGetReq req) { + String data = StringUtil.extracted(req.getParams()); + return ApiResult.ok(signManager.sign(req.getAppKey(), data)); + } + + +} diff --git a/black-list/black-list-service/src/main/java/cn/axzo/nanopart/server/dao/entity/SaasBlackWhiteList.java b/black-list/black-list-service/src/main/java/cn/axzo/nanopart/server/dao/entity/SaasBlackWhiteList.java index f6f072c4..792b20f0 100644 --- a/black-list/black-list-service/src/main/java/cn/axzo/nanopart/server/dao/entity/SaasBlackWhiteList.java +++ b/black-list/black-list-service/src/main/java/cn/axzo/nanopart/server/dao/entity/SaasBlackWhiteList.java @@ -1,11 +1,15 @@ package cn.axzo.nanopart.server.dao.entity; -import cn.axzo.framework.data.mybatisplus.model.BaseEntity; +import cn.axzo.pokonyan.config.mybatisplus.BaseEntity; import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableName; import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler; +import lombok.AllArgsConstructor; +import lombok.Builder; import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; import java.util.Map; @@ -16,7 +20,11 @@ import java.util.Map; * @modifiedBy: * @version: 1.0 */ +@EqualsAndHashCode(callSuper = true) +@NoArgsConstructor +@AllArgsConstructor @Data +@Builder @TableName(value = "saas_black_white_list",autoResultMap = true) public class SaasBlackWhiteList extends BaseEntity { diff --git a/black-list/black-list-service/src/main/java/cn/axzo/nanopart/server/dao/repository/BlackAndWhiteListRepository.java b/black-list/black-list-service/src/main/java/cn/axzo/nanopart/server/dao/repository/BlackAndWhiteListRepository.java index 4c69e6de..253e291e 100644 --- a/black-list/black-list-service/src/main/java/cn/axzo/nanopart/server/dao/repository/BlackAndWhiteListRepository.java +++ b/black-list/black-list-service/src/main/java/cn/axzo/nanopart/server/dao/repository/BlackAndWhiteListRepository.java @@ -7,6 +7,7 @@ import cn.axzo.nanopart.server.dao.entity.SaasBlackWhiteList; import cn.axzo.nanopart.server.dao.mapper.BlackAndWhiteListMapper; import cn.azxo.framework.common.utils.StringUtils; import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.json.JSONUtil; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import lombok.RequiredArgsConstructor; @@ -33,24 +34,21 @@ public class BlackAndWhiteListRepository extends ServiceImpl saasBlackWhiteList.setType(req1.getType().getValue())); - Date nowTime = new Date(System.currentTimeMillis()); - blackWhiteList.setCreateAt(nowTime); - blackWhiteList.setUpdateAt(nowTime); blackAndWhiteListMapper.insert(blackWhiteList); return blackWhiteList.getId(); } public Void delete(BlackAndWhiteListReq req) { List blackWhiteLists = detail(req); - if (CollectionUtil.isNotEmpty(blackWhiteLists)){ + if (CollectionUtil.isNotEmpty(blackWhiteLists)) { blackWhiteLists.forEach(saasBlackWhiteList -> { - lambdaUpdate().eq(SaasBlackWhiteList::getId,saasBlackWhiteList.getId()) - .eq(SaasBlackWhiteList::getIsDelete,0) - .set(SaasBlackWhiteList::getIsDelete,saasBlackWhiteList.getId()) + lambdaUpdate().eq(SaasBlackWhiteList::getId, saasBlackWhiteList.getId()) + .eq(SaasBlackWhiteList::getIsDelete, 0) + .set(SaasBlackWhiteList::getIsDelete, saasBlackWhiteList.getId()) .update(); - log.info("deleted record:{}", JSONObject.valueToString(saasBlackWhiteList)); + log.info("deleted record:{}", JSONUtil.toJsonStr(saasBlackWhiteList)); }); } return null; @@ -58,10 +56,10 @@ public class BlackAndWhiteListRepository extends ServiceImpl detail(BlackAndWhiteListReq req) { QueryWrapper queryWrapper = new QueryWrapper<>(); - queryWrapper.eq("type",req.getType().getValue()) - .eq(StringUtils.isNotBlank(req.getModule()),"module",req.getModule()) - .eq("is_delete",0) - .groupBy("type","param"); + queryWrapper.eq("type", req.getType().getValue()) + .eq(StringUtils.isNotBlank(req.getModule()), "module", req.getModule()) + .eq("is_delete", 0) + .groupBy("type", "param"); buildQueryWrapper(queryWrapper, req.getParam(), ""); return blackAndWhiteListMapper.selectList(queryWrapper); } @@ -83,4 +81,50 @@ public class BlackAndWhiteListRepository extends ServiceImpl param) { + return lambdaUpdate() + .eq(SaasBlackWhiteList::getId, id) + .eq(SaasBlackWhiteList::getIsDelete, 0) + .set(SaasBlackWhiteList::getParam, JSONUtil.toJsonStr(param)) + .update(); + } + + /** + * 根据模块名获取名单 + * + * @param type 名单类型 + * @param module 模块名 + * @return 名单列表 + */ + public List getListByModule(Integer type, String module) { + return lambdaQuery() + .eq(SaasBlackWhiteList::getType, type) + .eq(SaasBlackWhiteList::getModule, module) + .eq(SaasBlackWhiteList::getIsDelete, 0) + .list(); + } } \ No newline at end of file diff --git a/black-list/black-list-service/src/main/java/cn/axzo/nanopart/server/service/BlackAndWhiteListService.java b/black-list/black-list-service/src/main/java/cn/axzo/nanopart/server/service/BlackAndWhiteListService.java index 4ceeed1f..a68301fb 100644 --- a/black-list/black-list-service/src/main/java/cn/axzo/nanopart/server/service/BlackAndWhiteListService.java +++ b/black-list/black-list-service/src/main/java/cn/axzo/nanopart/server/service/BlackAndWhiteListService.java @@ -2,6 +2,8 @@ package cn.axzo.nanopart.server.service; import cn.axzo.framework.domain.web.result.ApiResult; import cn.axzo.nanopart.api.constant.enums.ListTypeEnum; +import cn.axzo.nanopart.api.request.BlackAndWhiteListInternalSyncReq; +import cn.axzo.nanopart.api.request.BlackAndWhiteListPlatformSyncReq; import cn.axzo.nanopart.api.request.BlackAndWhiteListReq; import cn.axzo.nanopart.api.response.BlackAndWhiteListExcelImportResp; import cn.axzo.nanopart.api.response.BlackAndWhiteListResp; @@ -19,15 +21,19 @@ import java.util.List; */ public interface BlackAndWhiteListService { - ApiResult create(BlackAndWhiteListReq req); + Long create(BlackAndWhiteListReq req); - ApiResult delete(BlackAndWhiteListReq req); + Void delete(BlackAndWhiteListReq req); - ApiResult> detail(BlackAndWhiteListReq req); + List detail(BlackAndWhiteListReq req); - ApiResult isInBlackOrWhiteList(BlackAndWhiteListReq req); + Boolean isInBlackOrWhiteList(BlackAndWhiteListReq req); BlackAndWhiteListExcelImportResp importBlackAndWhiteListExcel(ListTypeEnum type, String module, String url, MultipartFile file); Boolean deleteByIds(List ids); + + Boolean platformSync(BlackAndWhiteListPlatformSyncReq req); + + Long internalSync(BlackAndWhiteListInternalSyncReq req); } diff --git a/black-list/black-list-service/src/main/java/cn/axzo/nanopart/server/service/impl/BlackAndWhiteListServiceImpl.java b/black-list/black-list-service/src/main/java/cn/axzo/nanopart/server/service/impl/BlackAndWhiteListServiceImpl.java index 6452b9e8..42c6b42f 100644 --- a/black-list/black-list-service/src/main/java/cn/axzo/nanopart/server/service/impl/BlackAndWhiteListServiceImpl.java +++ b/black-list/black-list-service/src/main/java/cn/axzo/nanopart/server/service/impl/BlackAndWhiteListServiceImpl.java @@ -2,20 +2,26 @@ package cn.axzo.nanopart.server.service.impl; import cn.axzo.basics.common.BeanMapper; import cn.axzo.basics.common.exception.ServiceException; -import cn.axzo.framework.domain.web.result.ApiResult; import cn.axzo.nanopart.api.constant.enums.ListTypeEnum; +import cn.axzo.nanopart.api.request.BlackAndWhiteListInternalSyncReq; +import cn.axzo.nanopart.api.request.BlackAndWhiteListPlatformSyncReq; import cn.axzo.nanopart.api.request.BlackAndWhiteListReq; import cn.axzo.nanopart.api.response.BlackAndWhiteListExcelImportResp; import cn.axzo.nanopart.api.response.BlackAndWhiteListResp; +import cn.axzo.nanopart.server.constant.BlackAndWhiteListConstant; import cn.axzo.nanopart.server.dao.entity.SaasBlackWhiteList; import cn.axzo.nanopart.server.dao.repository.BlackAndWhiteListRepository; import cn.axzo.nanopart.server.service.BlackAndWhiteListService; import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.json.JSONUtil; import com.alibaba.excel.EasyExcel; import com.alibaba.excel.context.AnalysisContext; import com.alibaba.excel.event.AnalysisEventListener; +import com.google.common.collect.Lists; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.cloud.context.config.annotation.RefreshScope; import org.springframework.stereotype.Service; import org.springframework.util.StringUtils; import org.springframework.web.multipart.MultipartFile; @@ -24,11 +30,14 @@ import java.io.IOException; import java.io.InputStream; import java.net.URL; import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Set; +import java.util.concurrent.atomic.AtomicReference; import java.util.stream.Collectors; /** @@ -40,11 +49,18 @@ import java.util.stream.Collectors; */ @Slf4j @Service +@RefreshScope @RequiredArgsConstructor public class BlackAndWhiteListServiceImpl implements BlackAndWhiteListService { private final BlackAndWhiteListRepository blackAndWhiteListRepository; + /** + * 黑白名单外部平台单次最大同步记录数 + */ + @Value("${sync.fragmentSize:1000}") + private Long fragmentSize; + /** * 新增一条黑白名单记录 * @@ -52,17 +68,17 @@ public class BlackAndWhiteListServiceImpl implements BlackAndWhiteListService { * @return 记录id */ @Override - public ApiResult create(BlackAndWhiteListReq req) { + public Long create(BlackAndWhiteListReq req) { // 黑白名单创建互斥,若存在于黑名单中则不允许存在于白名单中,反之亦然 BlackAndWhiteListReq reverseReq = BeanMapper.copyBean(req, BlackAndWhiteListReq.class, (req1, req2) -> req2.setType((req1.getType().equals(ListTypeEnum.BLACK_LIST)) ? ListTypeEnum.WHITE_LIST : ListTypeEnum.BLACK_LIST)); - if (isInBlackOrWhiteList(reverseReq).getData()) { + if (isInBlackOrWhiteList(reverseReq)) { throw new ServiceException("该记录已存在于" + reverseReq.getType().getDescription() + "中,不能创建对应" + req.getType().getDescription()); } // 同类型名单内唯一性校验 - if (isInBlackOrWhiteList(req).getData()) { + if (isInBlackOrWhiteList(req)) { throw new ServiceException("该记录已存在于" + req.getType().getDescription() + "中"); } - return ApiResult.ok(blackAndWhiteListRepository.create(req)); + return blackAndWhiteListRepository.create(req); } /** @@ -72,8 +88,8 @@ public class BlackAndWhiteListServiceImpl implements BlackAndWhiteListService { * @return null */ @Override - public ApiResult delete(BlackAndWhiteListReq req) { - return ApiResult.ok(blackAndWhiteListRepository.delete(req)); + public Void delete(BlackAndWhiteListReq req) { + return blackAndWhiteListRepository.delete(req); } /** @@ -83,10 +99,9 @@ public class BlackAndWhiteListServiceImpl implements BlackAndWhiteListService { * @return 记录列表 */ @Override - public ApiResult> detail(BlackAndWhiteListReq req) { + public List detail(BlackAndWhiteListReq req) { List saasBlackWhiteLists = blackAndWhiteListRepository.detail(req); - List blackAndWhiteListResps = BeanMapper.copyList(saasBlackWhiteLists, BlackAndWhiteListResp.class); - return ApiResult.ok(blackAndWhiteListResps); + return BeanMapper.copyList(saasBlackWhiteLists, BlackAndWhiteListResp.class); } /** @@ -96,9 +111,9 @@ public class BlackAndWhiteListServiceImpl implements BlackAndWhiteListService { * @return 记录存在返回true,否则返回false */ @Override - public ApiResult isInBlackOrWhiteList(BlackAndWhiteListReq req) { + public Boolean isInBlackOrWhiteList(BlackAndWhiteListReq req) { List blackWhiteLists = blackAndWhiteListRepository.detail(req); - return ApiResult.ok(CollectionUtil.isNotEmpty(blackWhiteLists)); + return CollectionUtil.isNotEmpty(blackWhiteLists); } /** @@ -144,6 +159,123 @@ public class BlackAndWhiteListServiceImpl implements BlackAndWhiteListService { return true; } + /** + * 平台同步黑白名单 + * + * @param req {@link BlackAndWhiteListPlatformSyncReq} + * @return true-同步成功,false-同步失败 + */ + @Override + public Boolean platformSync(BlackAndWhiteListPlatformSyncReq req) { + if (Objects.isNull(req.getType())) { + throw new ServiceException("请选择正确的名单类型"); + } + if (req.getParams().size() > fragmentSize) { + throw new ServiceException("请主动进行分片多次同步,单次同步数小于" + fragmentSize + "条数据"); + } + // 第一个分片,需要先删除所有对应模块的名单,再新增 + if (req.getFragment() == 1 ){ + deleteByModule(req.getType(),req.getModule()); + } + // 新增 + List collect = req.getParams().stream().map(param -> SaasBlackWhiteList.builder() + .type(req.getType().getValue()) + .module(req.getModule()) + .param(param) + .build()).collect(Collectors.toList()); + boolean saved = blackAndWhiteListRepository.saveBatch(collect); + // 同步至登录黑名单 + if (Objects.nonNull(req.getIsSyncLogin()) && req.getIsSyncLogin()){ + internalSync(BlackAndWhiteListInternalSyncReq.builder() + .type(req.getType()) + .sourceModule(req.getModule()) + .sourceFields(Collections.singletonList(req.getSyncLoginField())) + .targetModule(BlackAndWhiteListConstant.ALL_LOGIN).build()); + } + // 同步至注册黑名单 + if (Objects.nonNull(req.getIsSyncRegister()) && req.getIsSyncRegister()){ + internalSync(BlackAndWhiteListInternalSyncReq.builder() + .type(req.getType()) + .sourceModule(req.getModule()) + .sourceFields(Collections.singletonList(req.getSyncRegisterField())) + .targetModule(BlackAndWhiteListConstant.OU_REGISTER_BLACK_LIST).build()); + } + return saved; + } + + /** + * 内部自同步黑白名单 + * + * @param req {@link BlackAndWhiteListInternalSyncReq} + * @return 同步成功的记录总条数 + */ + public Long internalSync(BlackAndWhiteListInternalSyncReq req) { + AtomicReference count = new AtomicReference<>(0L); + // 获取所有源记录 + List sourceList = blackAndWhiteListRepository.getListByModule(req.getType().getValue(), req.getSourceModule()); + + sourceList.stream().map(s -> { + // 抽取出需要同步的字段值 + HashMap map = new HashMap<>(); + req.getSourceFields().forEach(sf -> map.put(sf, s.getParam().get(sf))); + return map; + }).forEach(m -> { + // 判断是否存在目标记录,不存在则新增一条 + if (!isInBlackOrWhiteList(BlackAndWhiteListReq.builder() + .type(req.getType()) + .module(req.getTargetModule()) + .param(m) + .build())) { + create(BlackAndWhiteListReq.builder() + .type(req.getType()) + .module(req.getTargetModule()) + .param(m) + .build()); + count.getAndSet(count.get() + 1); + } + }); + return count.get(); + } + + /** + * 获取指定模块名的所有黑/白名单记录 + * + * @param type 名单类型 + * @param module 模块名 + * @return 黑/白名单记录列表 + */ + public List getListByModule(ListTypeEnum type, String module) { + return BeanMapper.copyList(blackAndWhiteListRepository.getListByModule(type.getValue(), module), BlackAndWhiteListResp.class); + } + + /** + * 更新指定id的黑白名单记录 + * + * @param id 记录id + * @param param 记录参数 + * @return true-更新成功,false-更新失败 + */ + public boolean updateParamById(Long id, Map param) { + return blackAndWhiteListRepository.updateParamById(id, param); + } + + /** + * 删除指定模块名的所有黑/白名单记录 + * + * @param type 名单类型 + * @param module 模块名 + * @return true-删除成功,false-删除失败 + */ + public Boolean deleteByModule(ListTypeEnum type, String module) { + if (Objects.isNull(type)) { + throw new ServiceException("待删除名单类型不能为空"); + } + if (!StringUtils.hasText(module)) { + throw new ServiceException("待删除模块名不能为空"); + } + return blackAndWhiteListRepository.deleteByModule(type.getValue(), module); + } + /** * 使用EasyExcel读取Excel数据 * diff --git a/nanopart-server/src/main/java/cn/axzo/nanopart/NanopartApplication.java b/nanopart-server/src/main/java/cn/axzo/nanopart/NanopartApplication.java index b5eccb60..8cf18f79 100644 --- a/nanopart-server/src/main/java/cn/axzo/nanopart/NanopartApplication.java +++ b/nanopart-server/src/main/java/cn/axzo/nanopart/NanopartApplication.java @@ -4,12 +4,14 @@ import org.mybatis.spring.annotation.MapperScan; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.openfeign.EnableFeignClients; +import org.springframework.context.annotation.EnableAspectJAutoProxy; @MapperScan(value = {"cn.axzo.**.mapper"}) @SpringBootApplication @EnableFeignClients(basePackages = { "cn.axzo.nanopart.api" }) +@EnableAspectJAutoProxy() public class NanopartApplication { public static void main(String[] args) { SpringApplication.run(NanopartApplication.class, args); diff --git a/nanopart-server/src/main/resources/bootstrap.yml b/nanopart-server/src/main/resources/bootstrap.yml index d084c1f5..cbaf52b2 100644 --- a/nanopart-server/src/main/resources/bootstrap.yml +++ b/nanopart-server/src/main/resources/bootstrap.yml @@ -4,7 +4,7 @@ spring: cloud: nacos: config: - server-addr: ${NACOS_HOST:dev-nacos.axzo.cn}:${NACOS_PORT:80} + server-addr: ${NACOS_HOST:https://dev-nacos.axzo.cn}:${NACOS_PORT:443} file-extension: yaml namespace: ${NACOS_NAMESPACE_ID:35eada10-9574-4db8-9fea-bc6a4960b6c7} prefix: ${spring.application.name}