Browse Source

商户入驻支付相关参数接口

zhangchen 1 month ago
parent
commit
e97800eb76

+ 24 - 15
alien-entity/src/main/java/shop/alien/entity/store/StorePaymentConfig.java

@@ -7,6 +7,7 @@ import io.swagger.annotations.ApiModel;
 import io.swagger.annotations.ApiModelProperty;
 import lombok.Data;
 import org.springframework.format.annotation.DateTimeFormat;
+import shop.alien.util.typehandler.BlobByteArrayTypeHandler;
 
 import java.util.Date;
 
@@ -29,6 +30,10 @@ public class StorePaymentConfig {
     @TableField("store_id")
     private Integer storeId;
 
+    @ApiModelProperty(value = "店铺用户id")
+    @TableField("store_user_id")
+    private Integer storeUserId;
+
     @ApiModelProperty(value = "应用ID")
     @TableField("app_id")
     private String appId;
@@ -37,9 +42,9 @@ public class StorePaymentConfig {
     @TableField("app_secret_cert")
     private String appSecretCert;
 
-    @ApiModelProperty(value = "应用公钥证书文件(存储文件内容)")
-    @TableField("app_public_cert")
-    private String appPublicCert;
+    @ApiModelProperty(value = "应用公钥证书文件(存储文件内容)(支付宝)BLOB,原样存取不经过字符编码")
+    @TableField(value = "app_public_cert", typeHandler = BlobByteArrayTypeHandler.class)
+    private byte[] appPublicCert;
 
     @ApiModelProperty(value = "应用公钥证书路径")
     @TableField("app_public_cert_path")
@@ -49,9 +54,9 @@ public class StorePaymentConfig {
     @TableField("app_public_cert_name")
     private String appPublicCertName;
 
-    @ApiModelProperty(value = "支付宝公钥证书文件(存储文件内容)")
-    @TableField("alipay_public_cert")
-    private String alipayPublicCert;
+    @ApiModelProperty(value = "支付宝公钥证书文件(存储文件内容)(支付宝)BLOB,原样存取不经过字符编码")
+    @TableField(value = "alipay_public_cert", typeHandler = BlobByteArrayTypeHandler.class)
+    private byte[] alipayPublicCert;
 
     @ApiModelProperty(value = "支付宝公钥证书路径")
     @TableField("alipay_public_cert_path")
@@ -61,9 +66,9 @@ public class StorePaymentConfig {
     @TableField("alipay_public_cert_name")
     private String alipayPublicCertName;
 
-    @ApiModelProperty(value = "支付宝根证书文件(存储文件内容)")
-    @TableField("alipay_root_cert")
-    private String alipayRootCert;
+    @ApiModelProperty(value = "支付宝根证书文件(存储文件内容)(支付宝)BLOB,原样存取不经过字符编码")
+    @TableField(value = "alipay_root_cert", typeHandler = BlobByteArrayTypeHandler.class)
+    private byte[] alipayRootCert;
 
     @ApiModelProperty(value = "支付宝根证书路径")
     @TableField("alipay_root_cert_path")
@@ -77,6 +82,10 @@ public class StorePaymentConfig {
     @TableField("wechat_app_id")
     private String wechatAppId;
 
+    @ApiModelProperty(value = "微信支付mini appId")
+    @TableField("wechat_mini_app_id")
+    private String wechatMiniAppId;
+
     @ApiModelProperty(value = "微信mchId")
     @TableField("wechat_mch_id")
     private String wechatMchId;
@@ -101,9 +110,9 @@ public class StorePaymentConfig {
     @TableField("wechat_private_key_name")
     private String wechatPrivateKeyName;
 
-    @ApiModelProperty(value = "微信私钥文件(存储文件内容)")
-    @TableField("wechat_private_key_file")
-    private String wechatPrivateKeyFile;
+    @ApiModelProperty(value = "微信私钥文件(存储文件内容)BLOB,原样存取不经过字符编码")
+    @TableField(value = "wechat_private_key_file", typeHandler = BlobByteArrayTypeHandler.class)
+    private byte[] wechatPrivateKeyFile;
 
     @ApiModelProperty(value = "微信公钥路径")
     @TableField("wechat_pay_public_key_file_path")
@@ -113,9 +122,9 @@ public class StorePaymentConfig {
     @TableField("wechat_pay_public_key_file_name")
     private String wechatPayPublicKeyFileName;
 
-    @ApiModelProperty(value = "微信公钥文件(存储文件内容)")
-    @TableField("wechat_pay_public_key_file")
-    private String wechatPayPublicKeyFile;
+    @ApiModelProperty(value = "微信公钥文件(存储文件内容)BLOB,原样存取不经过字符编码")
+    @TableField(value = "wechat_pay_public_key_file", typeHandler = BlobByteArrayTypeHandler.class)
+    private byte[] wechatPayPublicKeyFile;
 
     @ApiModelProperty(value = "删除标记, 0:未删除, 1:已删除")
     @TableField("delete_flag")

+ 3 - 4
alien-store/src/main/java/shop/alien/store/config/StorePaymentConfigCertInitRunner.java

@@ -13,7 +13,6 @@ import shop.alien.entity.store.StorePaymentConfig;
 import shop.alien.store.service.StoreInfoService;
 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;
@@ -141,12 +140,12 @@ public class StorePaymentConfigCertInitRunner implements ApplicationRunner {
         void set(String certFilePath, String fileName);
     }
 
-    private void writeIfPresent(Path baseDir, String content, String fileName, PathUpdater updater) throws Exception {
-        if (!StringUtils.hasText(content)) {
+    private void writeIfPresent(Path baseDir, byte[] content, String fileName, PathUpdater updater) throws Exception {
+        if (content == null || content.length == 0) {
             return;
         }
         Path file = baseDir.resolve(fileName);
-        Files.write(file, content.getBytes(StandardCharsets.UTF_8));
+        Files.write(file, content);
         if (updater != null) {
             String certFilePath = file.toAbsolutePath().toString();
             updater.set(certFilePath, fileName);

+ 161 - 15
alien-store/src/main/java/shop/alien/store/controller/StorePaymentConfigController.java

@@ -10,11 +10,12 @@ import org.springframework.web.bind.annotation.*;
 import org.springframework.web.multipart.MultipartFile;
 import shop.alien.entity.result.R;
 import shop.alien.entity.store.StorePaymentConfig;
+import shop.alien.entity.store.StoreUser;
 import shop.alien.store.service.StorePaymentConfigCertService;
 import shop.alien.store.service.StorePaymentConfigService;
+import shop.alien.store.service.StoreUserService;
 import shop.alien.util.common.JwtUtil;
 
-import java.nio.charset.StandardCharsets;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
@@ -37,6 +38,7 @@ public class StorePaymentConfigController {
 
     private final StorePaymentConfigService storePaymentConfigService;
     private final StorePaymentConfigCertService storePaymentConfigCertService;
+    private final StoreUserService storeUserService;
 
     @ApiOperationSupport(order = 1)
     @ApiOperation("新增支付配置(JSON  body)")
@@ -190,27 +192,27 @@ public class StorePaymentConfigController {
         }
     }
 
-    /** 将表中三个证书内容写入指定目录,优先使用表存的文件名;UTF-8,返回已写入文件的绝对路径列表 */
+    /** 将表中三个证书内容(byte[] 原样)写入指定目录,不经过字符编码,返回已写入文件的绝对路径列表 */
     private List<String> writeCertsToPath(StorePaymentConfig config, String targetPath) throws Exception {
         Path dir = Paths.get(targetPath);
         Files.createDirectories(dir);
         List<String> written = new ArrayList<>();
-        if (config.getAppPublicCert() != null && !config.getAppPublicCert().isEmpty()) {
+        if (config.getAppPublicCert() != null && config.getAppPublicCert().length > 0) {
             String name = safeCertFileName(config.getAppPublicCertName(), "app_public.crt");
             Path file = dir.resolve(name);
-            Files.write(file, config.getAppPublicCert().getBytes(StandardCharsets.UTF_8));
+            Files.write(file, config.getAppPublicCert());
             written.add(file.toAbsolutePath().toString());
         }
-        if (config.getAlipayPublicCert() != null && !config.getAlipayPublicCert().isEmpty()) {
+        if (config.getAlipayPublicCert() != null && config.getAlipayPublicCert().length > 0) {
             String name = safeCertFileName(config.getAlipayPublicCertName(), "alipay_public.crt");
             Path file = dir.resolve(name);
-            Files.write(file, config.getAlipayPublicCert().getBytes(StandardCharsets.UTF_8));
+            Files.write(file, config.getAlipayPublicCert());
             written.add(file.toAbsolutePath().toString());
         }
-        if (config.getAlipayRootCert() != null && !config.getAlipayRootCert().isEmpty()) {
+        if (config.getAlipayRootCert() != null && config.getAlipayRootCert().length > 0) {
             String name = safeCertFileName(config.getAlipayRootCertName(), "alipay_root.crt");
             Path file = dir.resolve(name);
-            Files.write(file, config.getAlipayRootCert().getBytes(StandardCharsets.UTF_8));
+            Files.write(file, config.getAlipayRootCert());
             written.add(file.toAbsolutePath().toString());
         }
         return written;
@@ -232,27 +234,27 @@ public class StorePaymentConfigController {
         return name;
     }
 
-    /** 将三个证书文件内容及文件名读入实体(存入表) */
+    /** 将三个证书文件内容原样(byte[])读入实体存入表,不经过字符编码,保证存取一致 */
     private void fillCertContent(StorePaymentConfig config,
                                  MultipartFile appPublicCertFile,
                                  MultipartFile alipayPublicCertFile,
                                  MultipartFile alipayRootCertFile) throws Exception {
         if (appPublicCertFile != null && !appPublicCertFile.isEmpty()) {
-            config.setAppPublicCert(new String(appPublicCertFile.getBytes(), StandardCharsets.UTF_8));
+            config.setAppPublicCert(appPublicCertFile.getBytes());
             String fn = appPublicCertFile.getOriginalFilename();
             if (fn != null && !fn.trim().isEmpty()) {
                 config.setAppPublicCertName(fn.trim());
             }
         }
         if (alipayPublicCertFile != null && !alipayPublicCertFile.isEmpty()) {
-            config.setAlipayPublicCert(new String(alipayPublicCertFile.getBytes(), StandardCharsets.UTF_8));
+            config.setAlipayPublicCert(alipayPublicCertFile.getBytes());
             String fn = alipayPublicCertFile.getOriginalFilename();
             if (fn != null && !fn.trim().isEmpty()) {
                 config.setAlipayPublicCertName(fn.trim());
             }
         }
         if (alipayRootCertFile != null && !alipayRootCertFile.isEmpty()) {
-            config.setAlipayRootCert(new String(alipayRootCertFile.getBytes(), StandardCharsets.UTF_8));
+            config.setAlipayRootCert(alipayRootCertFile.getBytes());
             String fn = alipayRootCertFile.getOriginalFilename();
             if (fn != null && !fn.trim().isEmpty()) {
                 config.setAlipayRootCertName(fn.trim());
@@ -348,6 +350,150 @@ public class StorePaymentConfigController {
     }
 
     @ApiOperationSupport(order = 8)
+    @ApiOperation("根据店铺用户ID查询支付配置(设置收款账号)")
+    @ApiImplicitParam(name = "storeUserId", value = "店铺用户ID(store_user.id)", dataType = "int", paramType = "query", required = true)
+    @GetMapping("/getByStoreUserId")
+    public R<StorePaymentConfig> getByStoreUserId(@RequestParam Integer storeUserId) {
+        log.info("StorePaymentConfigController.getByStoreUserId storeUserId={}", storeUserId);
+        if (storeUserId == null) {
+            return R.fail("店铺用户ID不能为空");
+        }
+        try {
+            StorePaymentConfig config = storePaymentConfigService.getByStoreUserId(storeUserId);
+            return R.data(config);
+        } catch (Exception e) {
+            log.error("根据店铺用户ID查询支付配置失败", e);
+            return R.fail("查询失败:" + e.getMessage());
+        }
+    }
+
+    @ApiOperationSupport(order = 9)
+    @ApiOperation(value = "设置收款账号-保存微信", notes = "按 store_user_id 新增或更新微信支付参数及证书文件(内容存入表)")
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "storeUserId", value = "店铺用户ID(store_user.id)", dataType = "int", paramType = "form", required = true),
+            @ApiImplicitParam(name = "wechatMiniAppId", value = "微信支付mini appId", dataType = "string", paramType = "form"),
+            @ApiImplicitParam(name = "wechatAppId", value = "微信appId", dataType = "string", paramType = "form"),
+            @ApiImplicitParam(name = "wechatMchId", value = "微信mchId", dataType = "string", paramType = "form"),
+            @ApiImplicitParam(name = "merchantSerialNumber", value = "API证书序列号", dataType = "string", paramType = "form"),
+            @ApiImplicitParam(name = "apiV3Key", value = "APIv3 Key", dataType = "string", paramType = "form"),
+            @ApiImplicitParam(name = "wechatPayPublicKeyId", value = "支付公钥ID", dataType = "string", paramType = "form"),
+            @ApiImplicitParam(name = "wechatPrivateKeyFile", value = "私钥证书", dataType = "file", paramType = "form"),
+            @ApiImplicitParam(name = "wechatPayPublicKeyFile", value = "公钥证书", dataType = "file", paramType = "form")
+    })
+    @PostMapping("/saveWechatByStoreUserId")
+    public R<Integer> saveWechatByStoreUserId(
+            @RequestParam Integer storeUserId,
+            @RequestParam(required = false) String wechatMiniAppId,
+            @RequestParam(required = false) String wechatAppId,
+            @RequestParam(required = false) String wechatMchId,
+            @RequestParam(required = false) String merchantSerialNumber,
+            @RequestParam(required = false) String apiV3Key,
+            @RequestParam(required = false) String wechatPayPublicKeyId,
+            @RequestParam(value = "wechatPrivateKeyFile", required = false) MultipartFile wechatPrivateKeyFile,
+            @RequestParam(value = "wechatPayPublicKeyFile", required = false) MultipartFile wechatPayPublicKeyFile) {
+
+        log.info("StorePaymentConfigController.saveWechatByStoreUserId storeUserId={}", storeUserId);
+        if (storeUserId == null) {
+            return R.fail("店铺用户ID不能为空");
+        }
+        try {
+            StoreUser storeUser = storeUserService.getById(storeUserId);
+            if (storeUser == null) {
+                return R.fail("店铺用户不存在");
+            }
+            StorePaymentConfig config = storePaymentConfigService.getByStoreUserId(storeUserId);
+            if (config == null) {
+                config = new StorePaymentConfig();
+                config.setStoreId(storeUser.getStoreId());
+                config.setStoreUserId(storeUserId);
+                config.setAppId(wechatAppId != null ? wechatAppId : "");
+                fillCreatedUser(config);
+            } else {
+                fillUpdatedUser(config);
+            }
+            config.setWechatMiniAppId(wechatMiniAppId);
+            config.setWechatAppId(wechatAppId);
+            config.setWechatMchId(wechatMchId);
+            config.setMerchantSerialNumber(merchantSerialNumber);
+            config.setApiV3Key(apiV3Key);
+            config.setWechatPayPublicKeyId(wechatPayPublicKeyId);
+            if (wechatPrivateKeyFile != null && !wechatPrivateKeyFile.isEmpty()) {
+                config.setWechatPrivateKeyFile(wechatPrivateKeyFile.getBytes());
+                String fn = wechatPrivateKeyFile.getOriginalFilename();
+                if (fn != null && !fn.trim().isEmpty()) {
+                    config.setWechatPrivateKeyName(fn.trim());
+                }
+            }
+            if (wechatPayPublicKeyFile != null && !wechatPayPublicKeyFile.isEmpty()) {
+                config.setWechatPayPublicKeyFile(wechatPayPublicKeyFile.getBytes());
+                String fn = wechatPayPublicKeyFile.getOriginalFilename();
+                if (fn != null && !fn.trim().isEmpty()) {
+                    config.setWechatPayPublicKeyFileName(fn.trim());
+                }
+            }
+            boolean ok = config.getId() == null ? storePaymentConfigService.save(config) : storePaymentConfigService.updateById(config);
+            if (!ok) {
+                return R.fail("保存失败");
+            }
+            return R.data(config.getId());
+        } catch (Exception e) {
+            log.error("保存微信收款账号失败 storeUserId={}", storeUserId, e);
+            return R.fail("保存失败:" + e.getMessage());
+        }
+    }
+
+    @ApiOperationSupport(order = 10)
+    @ApiOperation(value = "设置收款账号-保存支付宝", notes = "按 store_user_id 新增或更新支付宝支付参数及证书文件(内容存入表)")
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "storeUserId", value = "店铺用户ID(store_user.id)", dataType = "int", paramType = "form", required = true),
+            @ApiImplicitParam(name = "appId", value = "应用ID", dataType = "string", paramType = "form"),
+            @ApiImplicitParam(name = "appSecretCert", value = "应用私钥", dataType = "string", paramType = "form"),
+            @ApiImplicitParam(name = "appPublicCert", value = "应用公钥证书", dataType = "file", paramType = "form"),
+            @ApiImplicitParam(name = "alipayPublicCert", value = "支付宝公钥证书", dataType = "file", paramType = "form"),
+            @ApiImplicitParam(name = "alipayRootCert", value = "支付宝根证书", dataType = "file", paramType = "form")
+    })
+    @PostMapping("/saveAlipayByStoreUserId")
+    public R<Integer> saveAlipayByStoreUserId(
+            @RequestParam Integer storeUserId,
+            @RequestParam(required = false) String appId,
+            @RequestParam(required = false) String appSecretCert,
+            @RequestParam(value = "appPublicCert", required = false) MultipartFile appPublicCertFile,
+            @RequestParam(value = "alipayPublicCert", required = false) MultipartFile alipayPublicCertFile,
+            @RequestParam(value = "alipayRootCert", required = false) MultipartFile alipayRootCertFile) {
+        log.info("StorePaymentConfigController.saveAlipayByStoreUserId storeUserId={}", storeUserId);
+        if (storeUserId == null) {
+            return R.fail("店铺用户ID不能为空");
+        }
+        try {
+            StoreUser storeUser = storeUserService.getById(storeUserId);
+            if (storeUser == null) {
+                return R.fail("店铺用户不存在");
+            }
+            StorePaymentConfig config = storePaymentConfigService.getByStoreUserId(storeUserId);
+            if (config == null) {
+                config = new StorePaymentConfig();
+                config.setStoreId(storeUser.getStoreId());
+                config.setStoreUserId(storeUserId);
+                config.setAppId(appId != null ? appId : "");
+                fillCreatedUser(config);
+            } else {
+                fillUpdatedUser(config);
+            }
+            config.setAppId(appId);
+            config.setAppSecretCert(appSecretCert);
+            fillCertContent(config, appPublicCertFile, alipayPublicCertFile, alipayRootCertFile);
+            boolean ok = config.getId() == null ? storePaymentConfigService.save(config) : storePaymentConfigService.updateById(config);
+            if (!ok) {
+                return R.fail("保存失败");
+            }
+            return R.data(config.getId());
+        } catch (Exception e) {
+            log.error("保存支付宝收款账号失败 storeUserId={}", storeUserId, e);
+            return R.fail("保存失败:" + e.getMessage());
+        }
+    }
+
+    @ApiOperationSupport(order = 12)
     @ApiOperation("分页查询支付配置列表")
     @ApiImplicitParams({
             @ApiImplicitParam(name = "page", value = "页码", dataType = "Integer", paramType = "query", defaultValue = "1"),
@@ -372,7 +518,7 @@ public class StorePaymentConfigController {
         }
     }
 
-    @ApiOperationSupport(order = 9)
+    @ApiOperationSupport(order = 13)
     @ApiOperation(value = "新增/更新微信支付配置信息", notes = "按 storeId 查找配置,存在则更新微信字段;不存在则新建一条仅含 storeId 与微信信息的配置。可传表单+两个证书文件(内容存入表)。")
     @ApiImplicitParams({
             @ApiImplicitParam(name = "storeId", value = "店铺ID", dataType = "int", paramType = "form", required = true),
@@ -426,7 +572,7 @@ public class StorePaymentConfigController {
             config.setWechatPayPublicKeyFilePath(wechatPayPublicKeyFilePath);
             config.setWechatPayPublicKeyFileName(wechatPayPublicKeyFileName);
             if (wechatPrivateKeyFile != null && !wechatPrivateKeyFile.isEmpty()) {
-                config.setWechatPrivateKeyFile(new String(wechatPrivateKeyFile.getBytes(), StandardCharsets.UTF_8));
+                config.setWechatPrivateKeyFile(wechatPrivateKeyFile.getBytes());
                 if (wechatPrivateKeyName == null || wechatPrivateKeyName.trim().isEmpty()) {
                     String fn = wechatPrivateKeyFile.getOriginalFilename();
                     if (fn != null && !fn.trim().isEmpty()) {
@@ -435,7 +581,7 @@ public class StorePaymentConfigController {
                 }
             }
             if (wechatPayPublicKeyFile != null && !wechatPayPublicKeyFile.isEmpty()) {
-                config.setWechatPayPublicKeyFile(new String(wechatPayPublicKeyFile.getBytes(), StandardCharsets.UTF_8));
+                config.setWechatPayPublicKeyFile(wechatPayPublicKeyFile.getBytes());
                 if (wechatPayPublicKeyFileName == null || wechatPayPublicKeyFileName.trim().isEmpty()) {
                     String fn = wechatPayPublicKeyFile.getOriginalFilename();
                     if (fn != null && !fn.trim().isEmpty()) {

+ 8 - 0
alien-store/src/main/java/shop/alien/store/service/StorePaymentConfigService.java

@@ -21,6 +21,14 @@ public interface StorePaymentConfigService extends IService<StorePaymentConfig>
     StorePaymentConfig getByStoreId(Integer storeId);
 
     /**
+     * 根据店铺用户ID查询支付配置(设置收款账号场景)
+     *
+     * @param storeUserId 店铺用户ID(store_user.id)
+     * @return 支付配置,不存在返回 null
+     */
+    StorePaymentConfig getByStoreUserId(Integer storeUserId);
+
+    /**
      * 分页查询支付配置列表
      *
      * @param page   分页参数

+ 3 - 4
alien-store/src/main/java/shop/alien/store/service/impl/StorePaymentConfigCertServiceImpl.java

@@ -11,7 +11,6 @@ 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;
@@ -154,12 +153,12 @@ public class StorePaymentConfigCertServiceImpl implements StorePaymentConfigCert
         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)) {
+    private void writeIfPresent(Path baseDir, byte[] content, String fileName, PathUpdater updater, List<String> writtenPaths) throws Exception {
+        if (content == null || content.length == 0) {
             return;
         }
         Path file = baseDir.resolve(fileName);
-        Files.write(file, content.getBytes(StandardCharsets.UTF_8));
+        Files.write(file, content);
         String certFilePath = file.toAbsolutePath().toString();
         writtenPaths.add(certFilePath);
         if (updater != null) {

+ 10 - 0
alien-store/src/main/java/shop/alien/store/service/impl/StorePaymentConfigServiceImpl.java

@@ -35,6 +35,16 @@ public class StorePaymentConfigServiceImpl extends ServiceImpl<StorePaymentConfi
     }
 
     @Override
+    public StorePaymentConfig getByStoreUserId(Integer storeUserId) {
+        if (storeUserId == null) {
+            return null;
+        }
+        LambdaQueryWrapper<StorePaymentConfig> wrapper = new LambdaQueryWrapper<>();
+        wrapper.eq(StorePaymentConfig::getStoreUserId, storeUserId);
+        return this.getOne(wrapper);
+    }
+
+    @Override
     public IPage<StorePaymentConfig> pageList(Page<StorePaymentConfig> page, Integer storeId, String appId) {
         LambdaQueryWrapper<StorePaymentConfig> wrapper = new LambdaQueryWrapper<>();
         if (storeId != null) {

+ 41 - 0
alien-util/src/main/java/shop/alien/util/typehandler/BlobByteArrayTypeHandler.java

@@ -0,0 +1,41 @@
+package shop.alien.util.typehandler;
+
+import org.apache.ibatis.type.BaseTypeHandler;
+import org.apache.ibatis.type.JdbcType;
+import org.apache.ibatis.type.MappedJdbcTypes;
+import org.apache.ibatis.type.MappedTypes;
+
+import java.sql.CallableStatement;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+/**
+ * MyBatis BLOB 与 byte[] 直通类型处理器,不做任何编码转换。
+ * 用于证书等二进制内容:存入与取出字节完全一致,避免 UTF-8/Latin-1 等字符集导致数据损坏。
+ * 使用方式:在实体类字段上添加 @TableField(typeHandler = BlobByteArrayTypeHandler.class)
+ */
+@MappedJdbcTypes(JdbcType.BLOB)
+@MappedTypes(byte[].class)
+public class BlobByteArrayTypeHandler extends BaseTypeHandler<byte[]> {
+
+    @Override
+    public void setNonNullParameter(PreparedStatement ps, int i, byte[] parameter, JdbcType jdbcType) throws SQLException {
+        ps.setBytes(i, parameter);
+    }
+
+    @Override
+    public byte[] getNullableResult(ResultSet rs, String columnName) throws SQLException {
+        return rs.getBytes(columnName);
+    }
+
+    @Override
+    public byte[] getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
+        return rs.getBytes(columnIndex);
+    }
+
+    @Override
+    public byte[] getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
+        return cs.getBytes(columnIndex);
+    }
+}

+ 45 - 0
alien-util/src/main/java/shop/alien/util/typehandler/StringBlobTypeHandler.java

@@ -0,0 +1,45 @@
+package shop.alien.util.typehandler;
+
+import org.apache.ibatis.type.BaseTypeHandler;
+import org.apache.ibatis.type.JdbcType;
+import org.apache.ibatis.type.MappedJdbcTypes;
+import org.apache.ibatis.type.MappedTypes;
+
+import java.nio.charset.StandardCharsets;
+import java.sql.CallableStatement;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+/**
+ * MyBatis BLOB 与 String 互转类型处理器(UTF-8)。
+ * 用于证书等大文本字段在数据库中存为 BLOB、在 Java 实体中为 String 的场景。
+ * 使用方式:在实体类字段上添加 @TableField(typeHandler = StringBlobTypeHandler.class)
+ */
+@MappedJdbcTypes(JdbcType.BLOB)
+@MappedTypes(String.class)
+public class StringBlobTypeHandler extends BaseTypeHandler<String> {
+
+    @Override
+    public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) throws SQLException {
+        ps.setBytes(i, parameter.getBytes(StandardCharsets.UTF_8));
+    }
+
+    @Override
+    public String getNullableResult(ResultSet rs, String columnName) throws SQLException {
+        byte[] bytes = rs.getBytes(columnName);
+        return bytes == null ? null : new String(bytes, StandardCharsets.UTF_8);
+    }
+
+    @Override
+    public String getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
+        byte[] bytes = rs.getBytes(columnIndex);
+        return bytes == null ? null : new String(bytes, StandardCharsets.UTF_8);
+    }
+
+    @Override
+    public String getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
+        byte[] bytes = cs.getBytes(columnIndex);
+        return bytes == null ? null : new String(bytes, StandardCharsets.UTF_8);
+    }
+}