|
|
@@ -0,0 +1,169 @@
|
|
|
+package shop.alien.store.service.impl;
|
|
|
+
|
|
|
+import lombok.RequiredArgsConstructor;
|
|
|
+import lombok.extern.slf4j.Slf4j;
|
|
|
+import org.springframework.beans.factory.annotation.Value;
|
|
|
+import org.springframework.stereotype.Service;
|
|
|
+import org.springframework.util.StringUtils;
|
|
|
+import shop.alien.entity.store.StoreInfo;
|
|
|
+import shop.alien.entity.store.StorePaymentConfig;
|
|
|
+import shop.alien.store.service.StoreInfoService;
|
|
|
+import shop.alien.store.service.StorePaymentConfigCertService;
|
|
|
+import shop.alien.store.service.StorePaymentConfigService;
|
|
|
+
|
|
|
+import java.nio.charset.StandardCharsets;
|
|
|
+import java.nio.file.Files;
|
|
|
+import java.nio.file.Path;
|
|
|
+import java.nio.file.Paths;
|
|
|
+import java.util.stream.Stream;
|
|
|
+import java.util.ArrayList;
|
|
|
+import java.util.List;
|
|
|
+import java.util.regex.Pattern;
|
|
|
+import java.util.stream.Collectors;
|
|
|
+
|
|
|
+/**
|
|
|
+ * 店铺支付证书落盘服务实现:删除店铺证书目录下原有文件后重新写入,并将完整证书路径回写配置表。
|
|
|
+ *
|
|
|
+ * @author system
|
|
|
+ */
|
|
|
+@Slf4j
|
|
|
+@Service
|
|
|
+@RequiredArgsConstructor
|
|
|
+public class StorePaymentConfigCertServiceImpl implements StorePaymentConfigCertService {
|
|
|
+
|
|
|
+ private static final Pattern NON_ALNUM = Pattern.compile("[^0-9a-zA-Z]");
|
|
|
+
|
|
|
+ @Value("${payment.cert.base-path:${user.dir}/payment-certs}")
|
|
|
+ private String certBasePath;
|
|
|
+
|
|
|
+ private final StorePaymentConfigService storePaymentConfigService;
|
|
|
+ private final StoreInfoService storeInfoService;
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public List<String> refreshCertByStoreId(Integer storeId) {
|
|
|
+ if (storeId == null) {
|
|
|
+ return new ArrayList<>();
|
|
|
+ }
|
|
|
+ StorePaymentConfig config = storePaymentConfigService.getByStoreId(storeId);
|
|
|
+ if (config == null) {
|
|
|
+ log.warn("店铺支付配置不存在 storeId={}", storeId);
|
|
|
+ return new ArrayList<>();
|
|
|
+ }
|
|
|
+ try {
|
|
|
+ return writeCertFilesForConfigWithClean(config);
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("店铺证书落盘失败 storeId={}", storeId, e);
|
|
|
+ throw new RuntimeException("证书落盘失败:" + e.getMessage());
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 先清空该店铺证书目录(删除目录下所有文件及目录本身再重建),再写入证书并回写路径。
|
|
|
+ */
|
|
|
+ private List<String> writeCertFilesForConfigWithClean(StorePaymentConfig config) throws Exception {
|
|
|
+ Integer storeId = config.getStoreId();
|
|
|
+ if (storeId == null) {
|
|
|
+ return new ArrayList<>();
|
|
|
+ }
|
|
|
+ String storeTel = null;
|
|
|
+ try {
|
|
|
+ StoreInfo store = storeInfoService.getById(storeId);
|
|
|
+ if (store != null && StringUtils.hasText(store.getStoreTel())) {
|
|
|
+ storeTel = store.getStoreTel();
|
|
|
+ }
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.debug("获取店铺手机号失败 storeId={}", storeId, e);
|
|
|
+ }
|
|
|
+ String dirSegment = sanitizeDirSegment(storeTel);
|
|
|
+ String storeDirName = storeId + "_" + dirSegment;
|
|
|
+ Path baseDir = Paths.get(certBasePath).resolve(storeDirName);
|
|
|
+
|
|
|
+ // 删除店铺证书目录下原有内容:若目录存在则删除后重建
|
|
|
+ if (Files.exists(baseDir)) {
|
|
|
+ deleteDirectoryRecursively(baseDir);
|
|
|
+ }
|
|
|
+ Files.createDirectories(baseDir);
|
|
|
+
|
|
|
+ List<String> writtenPaths = new ArrayList<>();
|
|
|
+
|
|
|
+ // 支付宝:应用公钥、支付宝公钥、根证书
|
|
|
+ writeIfPresent(baseDir, config.getAppPublicCert(),
|
|
|
+ defaultName(config.getAppPublicCertName(), "app_public_cert.crt"),
|
|
|
+ (path, name) -> {
|
|
|
+ config.setAppPublicCertPath(path);
|
|
|
+ config.setAppPublicCertName(name);
|
|
|
+ }, writtenPaths);
|
|
|
+ writeIfPresent(baseDir, config.getAlipayPublicCert(),
|
|
|
+ defaultName(config.getAlipayPublicCertName(), "alipay_public_cert.crt"),
|
|
|
+ (path, name) -> {
|
|
|
+ config.setAlipayPublicCertPath(path);
|
|
|
+ config.setAlipayPublicCertName(name);
|
|
|
+ }, writtenPaths);
|
|
|
+ writeIfPresent(baseDir, config.getAlipayRootCert(),
|
|
|
+ defaultName(config.getAlipayRootCertName(), "alipay_root_cert.crt"),
|
|
|
+ (path, name) -> {
|
|
|
+ config.setAlipayRootCertPath(path);
|
|
|
+ config.setAlipayRootCertName(name);
|
|
|
+ }, writtenPaths);
|
|
|
+
|
|
|
+ // 微信:商户私钥、微信公钥
|
|
|
+ writeIfPresent(baseDir, config.getWechatPrivateKeyFile(),
|
|
|
+ defaultName(config.getWechatPrivateKeyName(), "wechat_private_key.pem"),
|
|
|
+ (path, name) -> {
|
|
|
+ config.setWechatPrivateKeyPath(path);
|
|
|
+ config.setWechatPrivateKeyName(name);
|
|
|
+ }, writtenPaths);
|
|
|
+ writeIfPresent(baseDir, config.getWechatPayPublicKeyFile(),
|
|
|
+ defaultName(config.getWechatPayPublicKeyFileName(), "wechat_public_key.pem"),
|
|
|
+ (path, name) -> {
|
|
|
+ config.setWechatPayPublicKeyFilePath(path);
|
|
|
+ config.setWechatPayPublicKeyFileName(name);
|
|
|
+ }, writtenPaths);
|
|
|
+
|
|
|
+ storePaymentConfigService.updateById(config);
|
|
|
+ log.info("店铺支付证书已刷新落盘 storeId={}, dir={}, 写入文件数={}", storeId, baseDir.toAbsolutePath(), writtenPaths.size());
|
|
|
+ return writtenPaths;
|
|
|
+ }
|
|
|
+
|
|
|
+ private static void deleteDirectoryRecursively(Path path) throws Exception {
|
|
|
+ if (!Files.exists(path)) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ if (Files.isDirectory(path)) {
|
|
|
+ try (Stream<Path> stream = Files.list(path)) {
|
|
|
+ for (Path entry : stream.collect(Collectors.toList())) {
|
|
|
+ deleteDirectoryRecursively(entry);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ Files.delete(path);
|
|
|
+ }
|
|
|
+
|
|
|
+ private static String sanitizeDirSegment(String storeTel) {
|
|
|
+ if (!StringUtils.hasText(storeTel)) {
|
|
|
+ return "no_tel";
|
|
|
+ }
|
|
|
+ return NON_ALNUM.matcher(storeTel.trim()).replaceAll("_");
|
|
|
+ }
|
|
|
+
|
|
|
+ private static String defaultName(String name, String defaultName) {
|
|
|
+ return StringUtils.hasText(name) ? name.trim() : defaultName;
|
|
|
+ }
|
|
|
+
|
|
|
+ private interface PathUpdater {
|
|
|
+ void set(String certFilePath, String fileName);
|
|
|
+ }
|
|
|
+
|
|
|
+ private void writeIfPresent(Path baseDir, String content, String fileName, PathUpdater updater, List<String> writtenPaths) throws Exception {
|
|
|
+ if (!StringUtils.hasText(content)) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ Path file = baseDir.resolve(fileName);
|
|
|
+ Files.write(file, content.getBytes(StandardCharsets.UTF_8));
|
|
|
+ String certFilePath = file.toAbsolutePath().toString();
|
|
|
+ writtenPaths.add(certFilePath);
|
|
|
+ if (updater != null) {
|
|
|
+ updater.set(certFilePath, fileName);
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|