5 Revize c925f5cf15 ... 06d39a52f4

Autor SHA1 Zpráva Datum
  lyx 06d39a52f4 add:平台端经营版块三级结构 před 2 týdny
  lyx 1290c467f2 Merge remote-tracking branch 'origin/sit' into sit před 2 týdny
  wxd dafc8bdd26 Merge branch 'store-plantform' of alien/alien_cloud into sit před 2 týdny
  lyx 8cbaf155be add:签名生成 před 2 týdny
  lyx 660b0425ef add:微信支付 před 2 týdny

+ 4 - 0
alien-entity/src/main/java/shop/alien/entity/store/StoreDictionary.java

@@ -80,6 +80,10 @@ public class StoreDictionary extends Model<StoreDictionary> {
     @TableField(exist = false)
     private List<StoreDictionary> storeDictionaryList;
 
+    @ApiModelProperty(value = "是否隐藏, 0:不隐藏, 1:隐藏")
+    @TableField("hidden")
+    private Integer hidden;
+
     @Override
     protected Serializable pkVal() {
         return this.id;

+ 36 - 0
alien-entity/src/main/java/shop/alien/entity/store/excelVo/BusinessSectionExcelVo.java

@@ -0,0 +1,36 @@
+package shop.alien.entity.store.excelVo;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import shop.alien.entity.store.excelVo.util.ExcelHeader;
+
+/**
+ * 经营版块Excel导入导出对象
+ *
+ * @author ssk
+ * @since 2025-01-XX
+ */
+@Data
+@JsonInclude
+@ApiModel(value = "BusinessSectionExcelVo对象", description = "经营版块Excel导入导出对象")
+public class BusinessSectionExcelVo {
+
+    @ExcelHeader("一级分类名称")
+    @ApiModelProperty(value = "一级分类名称(必填,如果二级和三级为空,则创建一级分类)")
+    private String firstLevelName;
+
+    @ExcelHeader("二级分类名称")
+    @ApiModelProperty(value = "二级分类名称(可选,如果三级为空,则创建二级分类)")
+    private String secondLevelName;
+
+    @ExcelHeader("三级分类名称")
+    @ApiModelProperty(value = "三级分类名称(可选,如果填写则创建三级分类)")
+    private String thirdLevelName;
+
+    @ExcelHeader("状态")
+    @ApiModelProperty(value = "状态为显示隐藏,如不填默认显示")
+    private String hidden;
+}
+

+ 18 - 0
alien-store/pom.xml

@@ -270,6 +270,24 @@
             <artifactId>spring-boot-starter-webflux</artifactId>
         </dependency>
 
+        <dependency>
+            <groupId>com.github.wechatpay-apiv3</groupId>
+            <artifactId>wechatpay-java</artifactId>
+            <version>0.2.17</version>
+        </dependency>
+
+        <!-- Google Gson -->
+        <dependency>
+            <groupId>com.google.code.gson</groupId>
+            <artifactId>gson</artifactId>
+            <version>2.11.0</version>
+        </dependency>
+        <!-- OkHttp -->
+        <dependency>
+            <groupId>com.squareup.okhttp3</groupId>
+            <artifactId>okhttp</artifactId>
+            <version>4.12.0</version>
+        </dependency>
     </dependencies>
 
     <build>

+ 89 - 0
alien-store/src/main/java/shop/alien/store/controller/PaymentController.java

@@ -0,0 +1,89 @@
+package shop.alien.store.controller;
+
+
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiImplicitParam;
+import io.swagger.annotations.ApiImplicitParams;
+import io.swagger.annotations.ApiOperation;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.web.bind.annotation.CrossOrigin;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import shop.alien.entity.result.R;
+import shop.alien.store.strategy.payment.PaymentStrategyFactory;
+
+import java.util.Map;
+
+/**
+ * @author lyx
+ * @version 1.0
+ * @date 2025/11/20 17:34
+ */
+@Slf4j
+@Api(tags = {"2.5-支付接口"})
+@CrossOrigin
+@RestController
+@RequestMapping("/payment")
+@RequiredArgsConstructor
+public class PaymentController {
+
+    private final PaymentStrategyFactory paymentStrategyFactory;
+
+
+    @ApiOperation("创建预支付订单")
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "price", value = "订单金额", required = true, paramType = "query", dataType = "String"),
+            @ApiImplicitParam(name = "subject", value = "订单标题", required = true, paramType = "query", dataType = "String"),
+            @ApiImplicitParam(name = "payType", value = "支付类型(alipay:支付宝, wechatPay:微信支付)", required = true, paramType = "query", dataType = "String")
+    })
+    @RequestMapping("/prePay")
+    public R prePay(String price, String subject, String payType) {
+        log.info("PaymentController:prePay, price: {}, subject: {}, payType: {}", price, subject, payType);
+        try {
+            return paymentStrategyFactory.getStrategy(payType).createPrePayOrder(price, subject);
+        } catch (Exception e) {
+            return R.fail(e.getMessage());
+        }
+    }
+
+    /**
+     * 通知接口 之后可能会用
+     * @param notifyData
+     * @return
+     */
+    @RequestMapping("/notify")
+    public R notify(String notifyData) {
+        return  null;
+    }
+
+    /**
+     * 查询订单状态
+     * @param transactionId 交易订单号(微信支付订单号/商户订单号)
+     * @param payType 支付类型
+     * @return 订单状态信息
+     */
+    @RequestMapping("/searchOrderByOutTradeNoPath")
+    public R searchOrderByOutTradeNoPath(String transactionId, String payType) {
+        try {
+            return paymentStrategyFactory.getStrategy(payType).searchOrderByOutTradeNoPath(transactionId);
+        } catch (Exception e) {
+            return R.fail(e.getMessage());
+        }
+    }
+
+    /**
+     * 退款接口
+     * @param params 退款参数(包含订单号、退款金额等)
+     * @return 退款结果
+     */
+    @RequestMapping("/refunds")
+    public R refunds(@RequestBody Map<String, String> params) {
+        try {
+            return R.data(paymentStrategyFactory.getStrategy(params.get("payType")).handleRefund(params));
+        } catch (Exception e) {
+            return R.fail(e.getMessage());
+        }
+    }
+}

+ 102 - 0
alien-store/src/main/java/shop/alien/store/controller/PlatformBusinessSectionController.java

@@ -0,0 +1,102 @@
+package shop.alien.store.controller;
+
+
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiOperationSupport;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
+import shop.alien.entity.result.R;
+import shop.alien.entity.store.StoreDictionary;
+import shop.alien.store.service.PlatformBusinessSectionService;
+
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.util.List;
+
+@Api(tags = {"2.5平台-经营版块管理"})
+@Slf4j
+@RestController
+@CrossOrigin
+@RequestMapping("/platformBusinessSection")
+@RequiredArgsConstructor
+public class PlatformBusinessSectionController {
+
+    private final PlatformBusinessSectionService platformBusinessSectionService;
+
+    @ApiOperation("查询经营种类(三级树形结构)")
+    @ApiOperationSupport(order = 1)
+    @GetMapping("/queryBusinessSectionTree")
+    public R<List<StoreDictionary>> queryBusinessSectionTree() {
+        log.info("platformBusinessSection.queryBusinessSectionTree");
+        List<StoreDictionary> result = platformBusinessSectionService.queryBusinessSectionTree();
+        return R.data(result);
+    }
+
+    @ApiOperation("新增经营种类(支持一级、二级、三级)")
+    @ApiOperationSupport(order = 2)
+    @PostMapping("/addBusinessSection")
+    public R<Boolean> addBusinessSection(@RequestBody StoreDictionary storeDictionary) {
+        log.info("platformBusinessSection.addBusinessSection:{}", storeDictionary);
+        try {
+            boolean result = platformBusinessSectionService.addBusinessSection(storeDictionary);
+            if (result) {
+                return R.success("新增成功");
+            } else {
+                return R.fail("新增失败");
+            }
+        } catch (IllegalArgumentException e) {
+            log.error("platformBusinessSection.addBusinessSection error: {}", e.getMessage());
+            return R.fail(e.getMessage());
+        } catch (Exception e) {
+            log.error("platformBusinessSection.addBusinessSection error", e);
+            return R.fail("新增失败:" + e.getMessage());
+        }
+    }
+
+    @ApiOperation("修改经营种类(支持一级、二级、三级)")
+    @ApiOperationSupport(order = 3)
+    @PutMapping("/updateBusinessSection")
+    public R<Boolean> updateBusinessSection(@RequestBody StoreDictionary storeDictionary) {
+        log.info("platformBusinessSection.updateBusinessSection:{}", storeDictionary);
+        try {
+            boolean result = platformBusinessSectionService.updateBusinessSection(storeDictionary);
+            if (result) {
+                return R.success("修改成功");
+            } else {
+                return R.fail("修改失败");
+            }
+        } catch (IllegalArgumentException e) {
+            log.error("platformBusinessSection.updateBusinessSection error: {}", e.getMessage());
+            return R.fail(e.getMessage());
+        } catch (Exception e) {
+            log.error("platformBusinessSection.updateBusinessSection error", e);
+            return R.fail("修改失败:" + e.getMessage());
+        }
+    }
+
+    @ApiOperation("批量导入经营版块")
+    @ApiOperationSupport(order = 4)
+    @PostMapping("/import")
+    public R<String> importBusinessSection(@RequestParam("file") MultipartFile file) {
+        log.info("platformBusinessSection.importBusinessSection fileName={}", 
+                file != null ? file.getOriginalFilename() : "null");
+        try {
+            return platformBusinessSectionService.importBusinessSection(file);
+        } catch (Exception e) {
+            log.error("platformBusinessSection.importBusinessSection error", e);
+            return R.fail("导入失败:" + e.getMessage());
+        }
+    }
+
+    @ApiOperation("下载经营版块导入模板")
+    @ApiOperationSupport(order = 5)
+    @GetMapping("/downloadTemplate")
+    public void downloadTemplate(HttpServletResponse response) throws IOException {
+        log.info("platformBusinessSection.downloadTemplate");
+        platformBusinessSectionService.downloadTemplate(response);
+    }
+
+}

+ 58 - 0
alien-store/src/main/java/shop/alien/store/service/PlatformBusinessSectionService.java

@@ -0,0 +1,58 @@
+package shop.alien.store.service;
+
+import shop.alien.entity.store.StoreDictionary;
+import shop.alien.entity.result.R;
+
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.util.List;
+
+/**
+ * 平台经营版块管理Service
+ *
+ * @author ssk
+ * @version 1.0
+ * @date 2025/01/01
+ */
+public interface PlatformBusinessSectionService {
+
+    /**
+     * 查询经营种类(三级树形结构)
+     *
+     * @return 经营种类树形结构列表
+     */
+    List<StoreDictionary> queryBusinessSectionTree();
+
+    /**
+     * 新增经营种类(支持一级、二级、三级)
+     *
+     * @param storeDictionary 经营种类信息
+     * @return 新增结果
+     */
+    boolean addBusinessSection(StoreDictionary storeDictionary);
+
+    /**
+     * 修改经营种类(支持一级、二级、三级)
+     *
+     * @param storeDictionary 经营种类信息
+     * @return 修改结果
+     */
+    boolean updateBusinessSection(StoreDictionary storeDictionary);
+
+    /**
+     * 批量导入经营版块
+     *
+     * @param file Excel文件
+     * @return 导入结果
+     */
+    R<String> importBusinessSection(org.springframework.web.multipart.MultipartFile file);
+
+    /**
+     * 下载经营版块导入模板
+     *
+     * @param response HTTP响应
+     * @throws IOException IO异常
+     */
+    void downloadTemplate(HttpServletResponse response) throws IOException;
+}
+

+ 967 - 0
alien-store/src/main/java/shop/alien/store/service/impl/PlatformBusinessSectionServiceImpl.java

@@ -0,0 +1,967 @@
+package shop.alien.store.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.poi.ss.usermodel.*;
+import org.apache.poi.xssf.usermodel.XSSFWorkbook;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.web.multipart.MultipartFile;
+import shop.alien.entity.result.R;
+import shop.alien.entity.store.StoreDictionary;
+import shop.alien.entity.store.excelVo.BusinessSectionExcelVo;
+import shop.alien.entity.store.excelVo.util.ExcelHeader;
+import shop.alien.mapper.StoreDictionaryMapper;
+import shop.alien.store.service.PlatformBusinessSectionService;
+
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.lang.reflect.Field;
+import java.net.URLEncoder;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 平台经营版块管理ServiceImpl
+ *
+ * @author ssk
+ * @version 1.0
+ * @date 2025/01/01
+ */
+@Slf4j
+@Service
+@RequiredArgsConstructor
+public class PlatformBusinessSectionServiceImpl extends ServiceImpl<StoreDictionaryMapper, StoreDictionary> implements PlatformBusinessSectionService {
+
+    private final StoreDictionaryMapper storeDictionaryMapper;
+
+    /**
+     * 查询经营种类(三级树形结构)
+     *
+     * @return 经营种类树形结构列表
+     */
+    @Override
+    public List<StoreDictionary> queryBusinessSectionTree() {
+        // 查询所有经营种类数据
+        LambdaQueryWrapper<StoreDictionary> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.eq(StoreDictionary::getTypeName, "business_section");
+        queryWrapper.eq(StoreDictionary::getDeleteFlag, 0);
+        queryWrapper.orderByAsc(StoreDictionary::getDictId);
+        List<StoreDictionary> storeDictionaryList = storeDictionaryMapper.selectList(queryWrapper);
+
+        // 构建三级树形结构
+        return buildTreeOptimized(storeDictionaryList);
+    }
+
+    /**
+     * 构建树形结构(优化版)
+     *
+     * @param flatList 扁平列表
+     * @return 树形结构列表
+     */
+    private List<StoreDictionary> buildTreeOptimized(List<StoreDictionary> flatList) {
+        if (flatList == null || flatList.isEmpty()) {
+            return new ArrayList<>();
+        }
+
+        // 创建三个存储结构
+        Map<Integer, StoreDictionary> nodeMap = new HashMap<>();  // ID到节点的映射
+        Map<Integer, List<StoreDictionary>> parentChildMap = new HashMap<>();  // 父ID到子节点列表的映射
+        List<StoreDictionary> result = new ArrayList<>();  // 结果列表
+
+        // 填充nodeMap和parentChildMap
+        for (StoreDictionary entity : flatList) {
+            Integer id = entity.getId();
+            Integer parentId = entity.getParentId();
+
+            // 存入节点映射
+            nodeMap.put(id, entity);
+
+            // 初始化子节点列表
+            entity.setStoreDictionaryList(new ArrayList<>());
+
+            // 如果是根节点(parentId为null或0),直接添加到结果
+            if (parentId == null || parentId == 0) {
+                result.add(entity);
+            } else {
+                // 否则,记录父子关系
+                parentChildMap.computeIfAbsent(parentId, k -> new ArrayList<>()).add(entity);
+            }
+        }
+
+        // 建立父子关系
+        for (StoreDictionary entity : flatList) {
+            Integer id = entity.getId();
+            if (parentChildMap.containsKey(id)) {
+                entity.getStoreDictionaryList().addAll(parentChildMap.get(id));
+            }
+        }
+
+        return result;
+    }
+
+    /**
+     * 新增经营种类(支持一级、二级、三级)
+     *
+     * @param storeDictionary 经营种类信息
+     * @return 新增结果
+     */
+    @Override
+    public boolean addBusinessSection(StoreDictionary storeDictionary) {
+        // 参数校验
+        if (storeDictionary == null || StringUtils.isBlank(storeDictionary.getDictDetail())) {
+            throw new IllegalArgumentException("经营种类描述不能为空");
+        }
+
+        // 设置固定字段
+        storeDictionary.setTypeName("business_section");
+//        storeDictionary.setTypeDetail("经营板块");
+        storeDictionary.setDeleteFlag(0);
+        storeDictionary.setCreatedTime(new Date());
+
+        // 处理 parent_id:如果为 null,设置为 0(一级)
+        Integer parentId = storeDictionary.getParentId();
+        if (parentId == null) {
+            parentId = 0;
+//            storeDictionary.setParentId(0);
+        }
+
+        // 如果是二级或三级,验证父节点是否存在
+        if (parentId != 0) {
+            StoreDictionary parent = storeDictionaryMapper.selectById(parentId);
+            if (parent == null || !"business_section".equals(parent.getTypeName()) || parent.getDeleteFlag() == 1) {
+                throw new IllegalArgumentException("父节点不存在或已删除");
+            }
+        }
+
+        // 生成 dict_id:查询同级(相同 parent_id)的最大 dict_id,然后 +1
+        String maxDictId = getMaxDictIdByParentId(parentId);
+        int nextDictId = (maxDictId == null ? 0 : Integer.parseInt(maxDictId)) + 1;
+        storeDictionary.setDictId(String.valueOf(nextDictId));
+
+        // 保存
+        return this.save(storeDictionary);
+    }
+
+    /**
+     * 修改经营种类(支持一级、二级、三级)
+     *
+     * @param storeDictionary 经营种类信息
+     * @return 修改结果
+     */
+    @Override
+    public boolean updateBusinessSection(StoreDictionary storeDictionary) {
+        // 参数校验
+        if (storeDictionary == null || storeDictionary.getId() == null) {
+            throw new IllegalArgumentException("ID不能为空");
+        }
+        if (StringUtils.isBlank(storeDictionary.getDictDetail())) {
+            throw new IllegalArgumentException("经营种类描述不能为空");
+        }
+
+        // 查询原记录
+        StoreDictionary existing = storeDictionaryMapper.selectById(storeDictionary.getId());
+        if (existing == null) {
+            throw new IllegalArgumentException("记录不存在");
+        }
+        if (!"business_section".equals(existing.getTypeName())) {
+            throw new IllegalArgumentException("该记录不是经营种类类型");
+        }
+        if (existing.getDeleteFlag() == 1) {
+            throw new IllegalArgumentException("该记录已删除");
+        }
+
+        // 判断是否是一级或二级节点
+        Integer existingParentId = existing.getParentId();
+        boolean isFirstLevel = (existingParentId == null || existingParentId == 0);
+        boolean isSecondLevel = false;
+        
+        // 如果不是一级节点,判断是否是二级节点(父节点是一级节点)
+        if (!isFirstLevel) {
+            StoreDictionary parent = storeDictionaryMapper.selectById(existingParentId);
+            if (parent != null) {
+                Integer grandParentId = parent.getParentId();
+                isSecondLevel = (grandParentId == null || grandParentId == 0);
+            }
+        }
+
+        // 处理 parent_id:如果为 null,设置为 0(一级)
+        Integer parentId = storeDictionary.getParentId();
+        if (parentId == null) {
+            parentId = 0;
+        }
+
+        // 标准化parentId用于比较(null转为0)
+        Integer normalizedParentId = (parentId == null) ? 0 : parentId;
+        Integer normalizedExistingParentId = (existingParentId == null) ? 0 : existingParentId;
+        String oldDictId = existing.getDictId();
+        String newDictId = storeDictionary.getDictId();
+        boolean parentIdChanged = !normalizedParentId.equals(normalizedExistingParentId);
+        
+        // 判断是否修改了关键字段(parent_id)
+        boolean keyFieldChanged = parentIdChanged;
+        
+        // 如果是一级或二级节点,且修改了关键字段(parent_id),检查是否有未删除的子节点
+        if ((isFirstLevel || isSecondLevel) && keyFieldChanged) {
+            boolean hasChildren = hasUndeletedChildren(existing.getId());
+            if (hasChildren) {
+                throw new IllegalArgumentException("该节点存在未删除的子节点,不允许修改关键字段(父节点)");
+            }
+        }
+        
+        // 如果修改了 parentId,需要验证新的父节点是否存在
+        if (parentIdChanged) {
+            if (parentId != 0) {
+                StoreDictionary parent = storeDictionaryMapper.selectById(parentId);
+                if (parent == null || !"business_section".equals(parent.getTypeName()) || parent.getDeleteFlag() == 1) {
+                    throw new IllegalArgumentException("父节点不存在或已删除");
+                }
+            }
+            
+            // 如果用户指定了新位置(newDictId不为空),使用用户指定的位置
+            // 否则,使用新节点下的最大+1
+            if (StringUtils.isBlank(newDictId)) {
+                String maxDictId = getMaxDictIdByParentId(parentId);
+                int nextDictId = (maxDictId == null ? 0 : Integer.parseInt(maxDictId)) + 1;
+                newDictId = String.valueOf(nextDictId);
+                storeDictionary.setDictId(newDictId);
+            }
+            
+            // 原节点下,原位置之后的记录需要前移(dictId - 1)
+            adjustSortOrderForMoveOut(existing.getId(), normalizedExistingParentId, oldDictId);
+            
+            // 新节点下,如果指定了新位置,需要调整新节点下的排序
+            if (StringUtils.isNotBlank(newDictId)) {
+                try {
+                    int newOrder = Integer.parseInt(newDictId);
+                    String maxDictId = getMaxDictIdByParentId(parentId);
+                    int maxOrder = (maxDictId == null ? 0 : Integer.parseInt(maxDictId));
+                    // 如果新位置不是最大+1,需要调整新节点下的排序
+                    if (newOrder <= maxOrder) {
+                        adjustSortOrderForMoveIn(existing.getId(), normalizedParentId, newDictId);
+                    }
+                } catch (NumberFormatException e) {
+                    log.warn("无法解析新dictId: {}", newDictId, e);
+                }
+            }
+        }
+
+        // 设置固定字段
+        storeDictionary.setTypeName("business_section");
+        // 如果没有指定删除标记,保持原有值;如果指定了,使用新值
+        Integer oldHidden = existing.getHidden();
+        Integer newHidden = storeDictionary.getHidden() == null ? oldHidden : storeDictionary.getHidden();
+        storeDictionary.setHidden(newHidden);
+        storeDictionary.setDeleteFlag(existing.getDeleteFlag()); // 保持原有删除标记
+        storeDictionary.setCreatedTime(existing.getCreatedTime()); // 保持原有创建时间
+        storeDictionary.setCreatedUserId(existing.getCreatedUserId()); // 保持原有创建人
+        storeDictionary.setUpdatedTime(new Date()); // 更新修改时间
+
+        // 处理排序逻辑:如果 parentId 没有改变,但 dictId 改变了,需要调整同级其他记录的排序
+        if (!parentIdChanged) {
+            // 如果 dictId 为空,保持原有值
+            if (StringUtils.isBlank(newDictId)) {
+                storeDictionary.setDictId(oldDictId);
+            } else if (!newDictId.equals(oldDictId)) {
+                // 调整同级其他记录的排序
+                adjustSortOrder(existing.getId(), normalizedParentId, oldDictId, newDictId);
+            }
+        }
+
+        // 更新当前节点
+        boolean updateResult = this.updateById(storeDictionary);
+        
+        // 如果是一级或二级节点,且hidden值发生了变化,同步更新所有子节点的hidden值
+        boolean hiddenChanged = (oldHidden == null && newHidden != null) || 
+                                (oldHidden != null && !oldHidden.equals(newHidden));
+        if (updateResult && (isFirstLevel || isSecondLevel) && hiddenChanged && newHidden != null) {
+            updateChildrenHidden(existing.getId(), newHidden);
+        }
+        
+        return updateResult;
+    }
+
+    /**
+     * 判断节点是否有未删除的子节点
+     *
+     * @param parentId 父节点ID
+     * @return 如果有未删除的子节点返回 true,否则返回 false
+     */
+    private boolean hasUndeletedChildren(Integer parentId) {
+        LambdaQueryWrapper<StoreDictionary> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.eq(StoreDictionary::getTypeName, "business_section");
+        queryWrapper.eq(StoreDictionary::getParentId, parentId);
+        queryWrapper.eq(StoreDictionary::getDeleteFlag, 0);
+        queryWrapper.last("LIMIT 1");
+        
+        StoreDictionary child = storeDictionaryMapper.selectOne(queryWrapper);
+        return child != null;
+    }
+
+    /**
+     * 递归更新子节点的显示/隐藏状态
+     * 当一级或二级节点的hidden值改变时,同步更新其所有子节点的hidden值
+     *
+     * @param parentId 父节点ID
+     * @param hidden 新的hidden值(0:不隐藏, 1:隐藏)
+     */
+    private void updateChildrenHidden(Integer parentId, Integer hidden) {
+        if (parentId == null || hidden == null) {
+            return;
+        }
+        
+        // 查询所有未删除的子节点
+        LambdaQueryWrapper<StoreDictionary> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.eq(StoreDictionary::getTypeName, "business_section");
+        queryWrapper.eq(StoreDictionary::getParentId, parentId);
+        queryWrapper.eq(StoreDictionary::getDeleteFlag, 0);
+        
+        List<StoreDictionary> children = storeDictionaryMapper.selectList(queryWrapper);
+        
+        if (children == null || children.isEmpty()) {
+            return;
+        }
+        
+        // 批量更新子节点的hidden值
+        Date updateTime = new Date();
+        for (StoreDictionary child : children) {
+            child.setHidden(hidden);
+            child.setUpdatedTime(updateTime);
+            storeDictionaryMapper.updateById(child);
+            
+            // 递归更新子节点的子节点(二级节点的子节点是三级节点)
+            updateChildrenHidden(child.getId(), hidden);
+        }
+    }
+
+    /**
+     * 根据 parent_id 获取同级最大 dict_id
+     *
+     * @param parentId 父节点ID
+     * @return 最大 dict_id,如果不存在则返回 null
+     */
+    private String getMaxDictIdByParentId(Integer parentId) {
+        LambdaQueryWrapper<StoreDictionary> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.eq(StoreDictionary::getTypeName, "business_section");
+        queryWrapper.eq(StoreDictionary::getDeleteFlag, 0);
+        
+        if (parentId == null || parentId == 0) {
+            queryWrapper.and(wrapper -> wrapper.isNull(StoreDictionary::getParentId)
+                    .or().eq(StoreDictionary::getParentId, 0));
+        } else {
+            queryWrapper.eq(StoreDictionary::getParentId, parentId);
+        }
+        
+        queryWrapper.orderByDesc(StoreDictionary::getDictId);
+        queryWrapper.last("LIMIT 1");
+        
+        StoreDictionary maxDict = storeDictionaryMapper.selectOne(queryWrapper);
+        return maxDict != null ? maxDict.getDictId() : null;
+    }
+
+    /**
+     * 调整排序:当记录的dictId改变时,调整同级其他记录的排序
+     * 上升则顺推:如果新dictId < 原dictId,将原dictId到新dictId之间的记录的dictId都+1
+     * 下降则顺升:如果新dictId > 原dictId,将原dictId到新dictId之间的记录的dictId都-1
+     *
+     * @param currentId 当前记录ID
+     * @param parentId 父节点ID
+     * @param oldDictId 原dictId
+     * @param newDictId 新dictId
+     */
+    private void adjustSortOrder(Integer currentId, Integer parentId, String oldDictId, String newDictId) {
+        try {
+            int oldOrder = Integer.parseInt(oldDictId);
+            int newOrder = Integer.parseInt(newDictId);
+            
+            // 如果排序没有改变,直接返回
+            if (oldOrder == newOrder) {
+                return;
+            }
+            
+            // 查询同级所有记录(排除当前记录)
+            LambdaQueryWrapper<StoreDictionary> queryWrapper = new LambdaQueryWrapper<>();
+            queryWrapper.eq(StoreDictionary::getTypeName, "business_section");
+            queryWrapper.eq(StoreDictionary::getDeleteFlag, 0);
+            queryWrapper.ne(StoreDictionary::getId, currentId);
+            
+            if (parentId == null || parentId == 0) {
+                queryWrapper.and(wrapper -> wrapper.isNull(StoreDictionary::getParentId)
+                        .or().eq(StoreDictionary::getParentId, 0));
+            } else {
+                queryWrapper.eq(StoreDictionary::getParentId, parentId);
+            }
+            
+            List<StoreDictionary> siblings = storeDictionaryMapper.selectList(queryWrapper);
+            
+            if (siblings == null || siblings.isEmpty()) {
+                return;
+            }
+            
+            // 上升则顺推:新dictId < 原dictId
+            if (newOrder < oldOrder) {
+                // 将新dictId到原dictId之间的记录的dictId都+1
+                for (StoreDictionary sibling : siblings) {
+                    try {
+                        int siblingOrder = Integer.parseInt(sibling.getDictId());
+                        if (siblingOrder >= newOrder && siblingOrder < oldOrder) {
+                            sibling.setDictId(String.valueOf(siblingOrder + 1));
+                            sibling.setUpdatedTime(new Date());
+                            storeDictionaryMapper.updateById(sibling);
+                        }
+                    } catch (NumberFormatException e) {
+                        log.warn("无法解析dictId: {}", sibling.getDictId(), e);
+                    }
+                }
+            } 
+            // 下降则顺升:新dictId > 原dictId
+            else if (newOrder > oldOrder) {
+                // 将原dictId到新dictId之间的记录的dictId都-1
+                for (StoreDictionary sibling : siblings) {
+                    try {
+                        int siblingOrder = Integer.parseInt(sibling.getDictId());
+                        if (siblingOrder > oldOrder && siblingOrder <= newOrder) {
+                            sibling.setDictId(String.valueOf(siblingOrder - 1));
+                            sibling.setUpdatedTime(new Date());
+                            storeDictionaryMapper.updateById(sibling);
+                        }
+                    } catch (NumberFormatException e) {
+                        log.warn("无法解析dictId: {}", sibling.getDictId(), e);
+                    }
+                }
+            }
+        } catch (NumberFormatException e) {
+            log.error("调整排序失败,dictId格式错误: oldDictId={}, newDictId={}", oldDictId, newDictId, e);
+        } catch (Exception e) {
+            log.error("调整排序失败", e);
+        }
+    }
+
+    /**
+     * 处理从原节点移出时的排序调整:原位置之后的记录需要前移(dictId - 1)
+     *
+     * @param currentId 当前记录ID
+     * @param oldParentId 原父节点ID
+     * @param oldDictId 原dictId
+     */
+    private void adjustSortOrderForMoveOut(Integer currentId, Integer oldParentId, String oldDictId) {
+        try {
+            int oldOrder = Integer.parseInt(oldDictId);
+            
+            // 查询原节点同级所有记录(排除当前记录)
+            LambdaQueryWrapper<StoreDictionary> queryWrapper = new LambdaQueryWrapper<>();
+            queryWrapper.eq(StoreDictionary::getTypeName, "business_section");
+            queryWrapper.eq(StoreDictionary::getDeleteFlag, 0);
+            queryWrapper.ne(StoreDictionary::getId, currentId);
+            
+            if (oldParentId == null || oldParentId == 0) {
+                queryWrapper.and(wrapper -> wrapper.isNull(StoreDictionary::getParentId)
+                        .or().eq(StoreDictionary::getParentId, 0));
+            } else {
+                queryWrapper.eq(StoreDictionary::getParentId, oldParentId);
+            }
+            
+            List<StoreDictionary> siblings = storeDictionaryMapper.selectList(queryWrapper);
+            
+            if (siblings == null || siblings.isEmpty()) {
+                return;
+            }
+            
+            // 原位置之后的记录需要前移(dictId - 1)
+            for (StoreDictionary sibling : siblings) {
+                try {
+                    int siblingOrder = Integer.parseInt(sibling.getDictId());
+                    if (siblingOrder > oldOrder) {
+                        sibling.setDictId(String.valueOf(siblingOrder - 1));
+                        sibling.setUpdatedTime(new Date());
+                        storeDictionaryMapper.updateById(sibling);
+                    }
+                } catch (NumberFormatException e) {
+                    log.warn("无法解析dictId: {}", sibling.getDictId(), e);
+                }
+            }
+        } catch (NumberFormatException e) {
+            log.error("移出节点排序调整失败,dictId格式错误: oldDictId={}", oldDictId, e);
+        } catch (Exception e) {
+            log.error("移出节点排序调整失败", e);
+        }
+    }
+
+    /**
+     * 处理移入新节点时的排序调整:新位置之后的记录需要后移(dictId + 1)
+     *
+     * @param currentId 当前记录ID
+     * @param newParentId 新父节点ID
+     * @param newDictId 新dictId
+     */
+    private void adjustSortOrderForMoveIn(Integer currentId, Integer newParentId, String newDictId) {
+        try {
+            int newOrder = Integer.parseInt(newDictId);
+            
+            // 查询新节点同级所有记录(排除当前记录)
+            LambdaQueryWrapper<StoreDictionary> queryWrapper = new LambdaQueryWrapper<>();
+            queryWrapper.eq(StoreDictionary::getTypeName, "business_section");
+            queryWrapper.eq(StoreDictionary::getDeleteFlag, 0);
+            queryWrapper.ne(StoreDictionary::getId, currentId);
+            
+            if (newParentId == null || newParentId == 0) {
+                queryWrapper.and(wrapper -> wrapper.isNull(StoreDictionary::getParentId)
+                        .or().eq(StoreDictionary::getParentId, 0));
+            } else {
+                queryWrapper.eq(StoreDictionary::getParentId, newParentId);
+            }
+            
+            List<StoreDictionary> siblings = storeDictionaryMapper.selectList(queryWrapper);
+            
+            if (siblings == null || siblings.isEmpty()) {
+                return;
+            }
+            
+            // 新位置之后的记录需要后移(dictId + 1)
+            for (StoreDictionary sibling : siblings) {
+                try {
+                    int siblingOrder = Integer.parseInt(sibling.getDictId());
+                    if (siblingOrder >= newOrder) {
+                        sibling.setDictId(String.valueOf(siblingOrder + 1));
+                        sibling.setUpdatedTime(new Date());
+                        storeDictionaryMapper.updateById(sibling);
+                    }
+                } catch (NumberFormatException e) {
+                    log.warn("无法解析dictId: {}", sibling.getDictId(), e);
+                }
+            }
+        } catch (NumberFormatException e) {
+            log.error("移入节点排序调整失败,dictId格式错误: newDictId={}", newDictId, e);
+        } catch (Exception e) {
+            log.error("移入节点排序调整失败", e);
+        }
+    }
+
+    /**
+     * 批量导入经营版块
+     *
+     * @param file Excel文件
+     * @return 导入结果
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public R<String> importBusinessSection(MultipartFile file) {
+        log.info("PlatformBusinessSectionServiceImpl.importBusinessSection fileName={}", 
+                file != null ? file.getOriginalFilename() : "null");
+        try {
+            if (file == null || file.isEmpty()) {
+                return R.fail("文件不能为空");
+            }
+
+            String fileName = file.getOriginalFilename();
+            if (fileName == null || (!fileName.endsWith(".xlsx") && !fileName.endsWith(".xls"))) {
+                return R.fail("文件格式不正确,请上传Excel文件");
+            }
+
+            List<String> errorMessages = new ArrayList<>();
+            int successCount = 0;
+            int totalCount = 0;
+
+            // 使用POI读取Excel
+            try (InputStream inputStream = file.getInputStream();
+                 Workbook workbook = new XSSFWorkbook(inputStream)) {
+                Sheet sheet = workbook.getSheetAt(0);
+
+                // 获取表头
+                Row headerRow = sheet.getRow(0);
+                if (headerRow == null) {
+                    return R.fail("Excel文件格式不正确,缺少表头");
+                }
+
+                // 构建字段映射(表头名称 -> 列索引)
+                Map<String, Integer> headerMap = new HashMap<>();
+                Field[] fields = BusinessSectionExcelVo.class.getDeclaredFields();
+                for (int i = 0; i < headerRow.getLastCellNum(); i++) {
+                    Cell cell = headerRow.getCell(i);
+                    if (cell != null) {
+                        String headerName = getCellValueAsString(cell);
+                        if (StringUtils.isNotBlank(headerName)) {
+                            headerMap.put(headerName.trim(), i);
+                        }
+                    }
+                }
+
+                // 读取数据行
+                for (int rowIndex = 1; rowIndex <= sheet.getLastRowNum(); rowIndex++) {
+                    Row row = sheet.getRow(rowIndex);
+                    if (row == null) {
+                        continue;
+                    }
+
+                    // 检查是否为空行
+                    boolean isEmptyRow = true;
+                    for (int i = 0; i < row.getLastCellNum(); i++) {
+                        Cell cell = row.getCell(i);
+                        if (cell != null && cell.getCellType() != CellType.BLANK) {
+                            String cellValue = getCellValueAsString(cell);
+                            if (StringUtils.isNotBlank(cellValue)) {
+                                isEmptyRow = false;
+                                break;
+                            }
+                        }
+                    }
+                    if (isEmptyRow) {
+                        continue;
+                    }
+
+                    totalCount++;
+                    BusinessSectionExcelVo excelVo = new BusinessSectionExcelVo();
+
+                    // 读取每个字段
+                    for (Field field : fields) {
+                        if (!field.isAnnotationPresent(ExcelHeader.class)) {
+                            continue;
+                        }
+                        ExcelHeader excelHeader = field.getAnnotation(ExcelHeader.class);
+                        String headerName = excelHeader.value();
+                        Integer colIndex = headerMap.get(headerName);
+                        if (colIndex == null) {
+                            continue;
+                        }
+
+                        Cell cell = row.getCell(colIndex);
+                        if (cell == null) {
+                            continue;
+                        }
+
+                        field.setAccessible(true);
+                        try {
+                            String cellValue = getCellValueAsString(cell);
+                            if (StringUtils.isNotBlank(cellValue)) {
+                                field.set(excelVo, cellValue.trim());
+                            }
+                        } catch (Exception e) {
+                            log.warn("读取字段{}失败:{}", headerName, e.getMessage());
+                        }
+                    }
+
+                    // 处理导入数据
+                    try {
+                        processImportData(excelVo, rowIndex + 1, errorMessages);
+                        successCount++;
+                    } catch (Exception e) {
+                        errorMessages.add(String.format("第%d行:%s", rowIndex + 1, e.getMessage()));
+                        log.error("导入第{}行数据失败", rowIndex + 1, e);
+                    }
+                }
+            }
+
+            // 构建返回消息
+            StringBuilder message = new StringBuilder();
+            message.append(String.format("导入完成:成功%d条,失败%d条", successCount, totalCount - successCount));
+            if (!errorMessages.isEmpty()) {
+                message.append("\n失败详情:\n");
+                int maxErrors = Math.min(errorMessages.size(), 10); // 最多显示10条错误
+                for (int i = 0; i < maxErrors; i++) {
+                    message.append(errorMessages.get(i)).append("\n");
+                }
+                if (errorMessages.size() > 10) {
+                    message.append(String.format("...还有%d条错误未显示", errorMessages.size() - 10));
+                }
+            }
+
+            if (successCount == 0) {
+                return R.fail(message.toString());
+            } else if (totalCount - successCount > 0) {
+                return R.success(message.toString());
+            } else {
+                return R.success(message.toString());
+            }
+        } catch (Exception e) {
+            log.error("导入经营版块失败", e);
+            return R.fail("导入失败:" + e.getMessage());
+        }
+    }
+
+    /**
+     * 处理导入数据
+     *
+     * @param excelVo Excel数据对象
+     * @param rowIndex 行号
+     * @param errorMessages 错误消息列表
+     */
+    private void processImportData(BusinessSectionExcelVo excelVo, int rowIndex, List<String> errorMessages) {
+        // 校验必填字段
+        if (StringUtils.isBlank(excelVo.getFirstLevelName())) {
+            throw new IllegalArgumentException("一级分类名称不能为空");
+        }
+
+        String firstLevelName = excelVo.getFirstLevelName().trim();
+        String secondLevelName = StringUtils.isNotBlank(excelVo.getSecondLevelName()) 
+                ? excelVo.getSecondLevelName().trim() : null;
+        String thirdLevelName = StringUtils.isNotBlank(excelVo.getThirdLevelName()) 
+                ? excelVo.getThirdLevelName().trim() : null;
+
+        // 确定要创建的级别
+        if (StringUtils.isNotBlank(thirdLevelName)) {
+            // 创建三级分类
+            createOrUpdateLevel(firstLevelName, secondLevelName, thirdLevelName, 3, excelVo.getHidden(), errorMessages, rowIndex);
+        } else if (StringUtils.isNotBlank(secondLevelName)) {
+            // 创建二级分类
+            createOrUpdateLevel(firstLevelName, secondLevelName, null, 2, excelVo.getHidden(), errorMessages, rowIndex);
+        } else {
+            // 创建一级分类
+            createOrUpdateLevel(firstLevelName, null, null, 1, excelVo.getHidden(), errorMessages, rowIndex);
+        }
+    }
+
+    /**
+     * 创建或更新分类级别
+     *
+     * @param firstLevelName 一级分类名称
+     * @param secondLevelName 二级分类名称
+     * @param thirdLevelName 三级分类名称
+     * @param level 级别(1、2、3)
+     * @param hidden 状态
+     * @param errorMessages 错误消息列表
+     * @param rowIndex 行号
+     */
+    private void createOrUpdateLevel(String firstLevelName, String secondLevelName, String thirdLevelName,
+                                     int level, String hidden, List<String> errorMessages, int rowIndex) {
+        try {
+            // 查找或创建一级分类
+            StoreDictionary firstLevel = findOrCreateLevel(firstLevelName, 0, 0, hidden);
+            if (firstLevel == null) {
+                throw new IllegalArgumentException("创建一级分类失败");
+            }
+            if (level >= 2 && StringUtils.isNotBlank(secondLevelName)) {
+                // 查找或创建二级分类
+                StoreDictionary secondLevel = findOrCreateLevel(secondLevelName, firstLevel.getId(), 1, hidden);
+                if (secondLevel == null) {
+                    throw new IllegalArgumentException("创建二级分类失败");
+                }
+
+                if (level >= 3 && StringUtils.isNotBlank(thirdLevelName)) {
+                    // 查找或创建三级分类
+                    StoreDictionary thirdLevel = findOrCreateLevel(thirdLevelName, secondLevel.getId(), 2, hidden);
+                    if (thirdLevel == null) {
+                        throw new IllegalArgumentException("创建三级分类失败");
+                    }
+                }
+            }
+        } catch (Exception e) {
+            throw new IllegalArgumentException(e.getMessage());
+        }
+    }
+
+    /**
+     * 查找或创建分类
+     *
+     * @param dictDetail 分类名称
+     * @param parentId 父节点ID
+     * @param expectedLevel 期望的级别(用于校验)
+     * @param hidden 状态
+     * @return 分类对象
+     */
+    private StoreDictionary findOrCreateLevel(String dictDetail, Integer parentId, int expectedLevel, String hidden) {
+        // 查找是否存在
+        LambdaQueryWrapper<StoreDictionary> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.eq(StoreDictionary::getTypeName, "business_section");
+        queryWrapper.eq(StoreDictionary::getDeleteFlag, 0);
+        queryWrapper.eq(StoreDictionary::getDictDetail, dictDetail);
+
+        if (parentId == null || parentId == 0) {
+            queryWrapper.and(wrapper -> wrapper.isNull(StoreDictionary::getParentId)
+                    .or().eq(StoreDictionary::getParentId, 0));
+        } else {
+            queryWrapper.eq(StoreDictionary::getParentId, parentId);
+        }
+        
+        StoreDictionary existing = storeDictionaryMapper.selectOne(queryWrapper);
+        
+        if (existing != null) {
+            return existing;
+        }
+
+        // 创建新分类
+        StoreDictionary newDict = new StoreDictionary();
+        newDict.setTypeName("business_section");
+        newDict.setDictDetail(dictDetail);
+        // 一级为经营版块 ,二级为经营种类,三级为分类
+        if (expectedLevel == 0) {
+            newDict.setTypeDetail("经营板块");
+        } else if (expectedLevel == 1) {
+            newDict.setTypeDetail("经营种类");
+        } else if (expectedLevel == 2) {
+            newDict.setTypeDetail("分类");
+        }
+        newDict.setParentId(parentId == null ? 0 : parentId);
+        newDict.setHidden(StringUtils.isNotBlank(hidden) && hidden.equals("隐藏") ? 1 : 0);
+        newDict.setDeleteFlag(0);
+        newDict.setCreatedTime(new Date());
+
+        // 生成 dict_id
+        String maxDictId = getMaxDictIdByParentId(newDict.getParentId());
+        /*int nextDictId;
+        if (StringUtils.isNotBlank(sortOrder) && expectedLevel == 1) {
+            try {
+                nextDictId = Integer.parseInt(sortOrder);
+                // 如果指定的排序已存在,需要调整
+                String existingDictId = getDictIdByParentIdAndOrder(newDict.getParentId(), nextDictId);
+                if (existingDictId != null) {
+                    // 使用最大+1
+                    nextDictId = (maxDictId == null ? 0 : Integer.parseInt(maxDictId)) + 1;
+                }
+            } catch (NumberFormatException e) {
+                nextDictId = (maxDictId == null ? 0 : Integer.parseInt(maxDictId)) + 1;
+            }
+        } else {
+            nextDictId = (maxDictId == null ? 0 : Integer.parseInt(maxDictId)) + 1;
+        }*/
+        newDict.setDictId(String.valueOf(maxDictId == null ? 1 : Integer.parseInt(maxDictId) + 1));
+
+        boolean saved = this.save(newDict);
+        return saved ? newDict : null;
+    }
+
+    /**
+     * 根据parentId和排序获取dictId
+     *
+     * @param parentId 父节点ID
+     * @param order 排序
+     * @return dictId
+     */
+    private String getDictIdByParentIdAndOrder(Integer parentId, int order) {
+        LambdaQueryWrapper<StoreDictionary> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.eq(StoreDictionary::getTypeName, "business_section");
+        queryWrapper.eq(StoreDictionary::getDeleteFlag, 0);
+        queryWrapper.eq(StoreDictionary::getDictId, String.valueOf(order));
+        
+        if (parentId == null || parentId == 0) {
+            queryWrapper.and(wrapper -> wrapper.isNull(StoreDictionary::getParentId)
+                    .or().eq(StoreDictionary::getParentId, 0));
+        } else {
+            queryWrapper.eq(StoreDictionary::getParentId, parentId);
+        }
+        
+        StoreDictionary dict = storeDictionaryMapper.selectOne(queryWrapper);
+        return dict != null ? dict.getDictId() : null;
+    }
+
+    /**
+     * 获取单元格值(字符串格式)
+     *
+     * @param cell 单元格
+     * @return 字符串值
+     */
+    private String getCellValueAsString(Cell cell) {
+        if (cell == null) {
+            return null;
+        }
+        
+        switch (cell.getCellType()) {
+            case STRING:
+                return cell.getStringCellValue();
+            case NUMERIC:
+                if (DateUtil.isCellDateFormatted(cell)) {
+                    return cell.getDateCellValue().toString();
+                } else {
+                    // 处理数字,避免科学计数法
+                    double numericValue = cell.getNumericCellValue();
+                    if (numericValue == (long) numericValue) {
+                        return String.valueOf((long) numericValue);
+                    } else {
+                        return String.valueOf(numericValue);
+                    }
+                }
+            case BOOLEAN:
+                return String.valueOf(cell.getBooleanCellValue());
+            case FORMULA:
+                return cell.getCellFormula();
+            default:
+                return null;
+        }
+    }
+
+    /**
+     * 下载经营版块导入模板
+     *
+     * @param response HTTP响应
+     * @throws IOException IO异常
+     */
+    @Override
+    public void downloadTemplate(HttpServletResponse response) throws IOException {
+        log.info("PlatformBusinessSectionServiceImpl.downloadTemplate");
+        
+        XSSFWorkbook workbook = new XSSFWorkbook();
+        try {
+            Sheet sheet = workbook.createSheet("经营版块导入模板");
+
+            // 获取带有 @ExcelHeader 注解的字段
+            Field[] fields = BusinessSectionExcelVo.class.getDeclaredFields();
+            List<Field> excelFields = new ArrayList<>();
+            for (Field field : fields) {
+                if (field.isAnnotationPresent(ExcelHeader.class)) {
+                    excelFields.add(field);
+                }
+            }
+
+            // 创建表头样式
+            Font headerFont = workbook.createFont();
+            headerFont.setBold(true);
+            headerFont.setFontHeightInPoints((short) 12);
+            CellStyle headerCellStyle = workbook.createCellStyle();
+            headerCellStyle.setFont(headerFont);
+            headerCellStyle.setAlignment(HorizontalAlignment.CENTER);
+            headerCellStyle.setVerticalAlignment(VerticalAlignment.CENTER);
+            headerCellStyle.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex());
+            headerCellStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
+            headerCellStyle.setBorderBottom(BorderStyle.THIN);
+            headerCellStyle.setBorderTop(BorderStyle.THIN);
+            headerCellStyle.setBorderLeft(BorderStyle.THIN);
+            headerCellStyle.setBorderRight(BorderStyle.THIN);
+
+            // 创建表头
+            Row headerRow = sheet.createRow(0);
+            for (int i = 0; i < excelFields.size(); i++) {
+                Field field = excelFields.get(i);
+                ExcelHeader excelHeader = field.getAnnotation(ExcelHeader.class);
+                Cell cell = headerRow.createCell(i);
+                cell.setCellValue(excelHeader.value());
+                cell.setCellStyle(headerCellStyle);
+            }
+
+            // 设置列宽
+            sheet.setColumnWidth(0, 25 * 256); // 一级分类名称
+            sheet.setColumnWidth(1, 25 * 256); // 二级分类名称
+            sheet.setColumnWidth(2, 25 * 256); // 三级分类名称
+            sheet.setColumnWidth(3, 15 * 256); // 状态(隐藏/显示)
+
+
+
+            // 设置响应头(必须在获取输出流之前设置)
+            response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
+            response.setCharacterEncoding("utf-8");
+            String fileName = URLEncoder.encode("经营版块导入模板", "UTF-8").replaceAll("\\+", "%20");
+            response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");
+
+            // 输出到响应流
+            OutputStream outputStream = response.getOutputStream();
+            workbook.write(outputStream);
+            outputStream.flush();
+        } finally {
+            // 确保 workbook 被正确关闭
+            if (workbook != null) {
+                try {
+                    workbook.close();
+                } catch (IOException e) {
+                    log.error("关闭workbook失败", e);
+                }
+            }
+        }
+    }
+}
+

+ 61 - 0
alien-store/src/main/java/shop/alien/store/strategy/payment/PaymentStrategy.java

@@ -0,0 +1,61 @@
+package shop.alien.store.strategy.payment;
+
+
+import shop.alien.entity.result.R;
+
+import java.util.Map;
+
+/**
+ * 支付策略接口
+ *
+ * @author lyx
+ * @date 2025/11/20
+ */
+public interface PaymentStrategy {
+
+    /** 生成预支付订单
+     *
+     * @param price 订单金额
+     * @param subject 订单标题
+     * @return 预支付订单信息
+     * @throws Exception 生成异常
+     */
+    R createPrePayOrder(String price, String subject) throws Exception;
+
+
+    /**
+     * 处理支付通知
+     *
+     * @param notifyData 支付通知数据
+     * @return 处理结果
+     * @throws Exception 处理异常
+     */
+    R handleNotify(String notifyData) throws Exception;
+
+     /**
+     * 查询订单状态
+     *
+     * @param transactionId 交易订单号
+     * @return 订单状态信息
+     * @throws Exception 查询异常
+     */
+    R searchOrderByOutTradeNoPath(String transactionId) throws Exception;
+
+     /**
+     * 处理退款请求
+     *
+     * @param params 退款请求参数
+     * @return 处理结果
+     * @throws Exception 处理异常
+     */
+     String handleRefund(Map<String,String> params) throws Exception;
+
+
+
+    /**
+     * 获取策略类型字符串
+     *
+     * @return 策略类型字符串
+     */
+    String getType();
+}

+ 64 - 0
alien-store/src/main/java/shop/alien/store/strategy/payment/PaymentStrategyFactory.java

@@ -0,0 +1,64 @@
+package shop.alien.store.strategy.payment;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.PostConstruct;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 支付策略工厂
+ *
+ * @author lyx
+ * @date 2025/11/20
+ */
+@Slf4j
+@Component
+public class PaymentStrategyFactory {
+
+    @Autowired
+    private List<PaymentStrategy> paymentStrategies;
+
+    private final Map<String, PaymentStrategy> strategyMap = new HashMap<>();
+
+        /**
+         * 初始化策略映射
+         */
+        @PostConstruct
+        public void init() {
+            if (paymentStrategies != null && !paymentStrategies.isEmpty()) {
+                for (PaymentStrategy strategy : paymentStrategies) {
+                    strategyMap.put(strategy.getType(), strategy);
+                    log.info("注册支付策略: {} -> {}", strategy.getType(), strategy.getClass().getSimpleName());
+                }
+            }
+        }
+
+    /**
+     * 根据类型获取OCR策略
+     *
+     * @param type OCR类型
+     * @return OCR策略实例
+     * @throws IllegalArgumentException 如果类型不存在
+     */
+    public PaymentStrategy getStrategy(String type) {
+        PaymentStrategy strategy = strategyMap.get(type);
+        if (strategy == null) {
+            throw new IllegalArgumentException("不支持的支付类型: " + type);
+        }
+        return strategy;
+    }
+
+    /**
+     * 检查是否支持指定的OCR类型
+     *
+     * @param type OCR类型
+     * @return 是否支持
+     */
+    public boolean supports(String type) {
+        return strategyMap.containsKey(type);
+    }
+}

+ 328 - 0
alien-store/src/main/java/shop/alien/store/strategy/payment/impl/AlipayPaymentStrategyImpl.java

@@ -0,0 +1,328 @@
+package shop.alien.store.strategy.payment.impl;
+
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONArray;
+import com.alibaba.fastjson.JSONObject;
+import com.alipay.api.AlipayApiException;
+import com.alipay.api.AlipayClient;
+import com.alipay.api.AlipayConfig;
+import com.alipay.api.DefaultAlipayClient;
+import com.alipay.api.domain.AlipayTradeAppPayModel;
+import com.alipay.api.domain.AlipayTradeRefundModel;
+import com.alipay.api.request.AlipayTradeAppPayRequest;
+import com.alipay.api.request.AlipayTradeRefundRequest;
+import com.alipay.api.response.AlipayTradeAppPayResponse;
+import com.alipay.api.response.AlipayTradeRefundResponse;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+import shop.alien.entity.result.R;
+import shop.alien.entity.store.StoreAliPayRefundLog;
+import shop.alien.store.service.StoreAliPayRefundLogService;
+import shop.alien.store.strategy.payment.PaymentStrategy;
+import shop.alien.util.common.UniqueRandomNumGenerator;
+import shop.alien.util.common.UrlEncode;
+import shop.alien.util.common.constant.PaymentEnum;
+import shop.alien.util.system.OSUtil;
+
+import java.math.BigDecimal;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Map;
+
+/**
+ * 支付宝支付策略
+ *
+ * @author lyx
+ * @date 2025/11/20
+ */
+@Slf4j
+@Component
+@RequiredArgsConstructor
+public class AlipayPaymentStrategyImpl implements PaymentStrategy {
+
+    /**
+     * 商家端appId
+     */
+    @Value("${payment.aliPay.business.appId}")
+    private String businessAppId;
+
+    /**
+     * 商家端app私钥
+     */
+    @Value("${payment.aliPay.business.appPrivateKey}")
+    private String businessAppPrivateKey;
+
+    /**
+     * 商家端app公钥
+     */
+    @Value("${payment.aliPay.business.appPublicKey}")
+    private String businessAppPublicKey;
+
+    /**
+     * windows应用公钥证书文件路径
+     */
+    @Value("${payment.aliPay.business.win.appCertPath}")
+    private String businessWinAppCertPath;
+
+    /**
+     * windows支付宝公钥证书文件路径
+     */
+    @Value("${payment.aliPay.business.win.alipayPublicCertPath}")
+    private String businessWinAlipayPublicCertPath;
+
+    /**
+     * windows支付宝根证书文件路径
+     */
+    @Value("${payment.aliPay.business.win.alipayRootCertPath}")
+    private String businessWinAlipayRootCertPath;
+
+    /**
+     * linux应用公钥证书文件路径
+     */
+    @Value("${payment.aliPay.business.linux.appCertPath}")
+    private String businessLinuxAppCertPath;
+
+    /**
+     * linux支付宝公钥证书文件路径
+     */
+    @Value("${payment.aliPay.business.linux.alipayPublicCertPath}")
+    private String businessLinuxAlipayPublicCertPath;
+
+    /**
+     * linux支付宝根证书文件路径
+     */
+    @Value("${payment.aliPay.business.linux.alipayRootCertPath}")
+    private String businessLinuxAlipayRootCertPath;
+
+    /**
+     * 支对称加密算法密钥
+     */
+    @Value("${ali.aes.encryptKey}")
+    private String encryptKey;
+    /**
+     * 支付宝网关地址
+     */
+    @Value("${payment.aliPay.host}")
+    private String aliPayApiHost;
+
+    private final StoreAliPayRefundLogService storeAliPayRefundLogService;
+
+
+    /**
+     * 填写阿里配置
+     *
+     * @return AlipayConfig
+     */
+    private AlipayConfig setAlipayConfig(String type) {
+        AlipayConfig alipayConfig = new AlipayConfig();
+        alipayConfig.setServerUrl(aliPayApiHost);
+        alipayConfig.setAppId(businessAppId);
+        alipayConfig.setPrivateKey(businessAppPrivateKey);
+        alipayConfig.setFormat("json");
+        alipayConfig.setCharset("UTF-8");
+        alipayConfig.setSignType("RSA2");
+        if ("windows".equals(OSUtil.getOsName())) {
+            alipayConfig.setAppCertPath(businessWinAppCertPath);
+            alipayConfig.setAlipayPublicCertPath(businessWinAlipayPublicCertPath);
+            alipayConfig.setRootCertPath(businessWinAlipayRootCertPath);
+        } else {
+            alipayConfig.setAppCertPath(businessLinuxAppCertPath);
+            alipayConfig.setAlipayPublicCertPath(businessLinuxAlipayPublicCertPath);
+            alipayConfig.setRootCertPath(businessLinuxAlipayRootCertPath);
+        }
+        if ("aes".equals(type)) {
+            alipayConfig.setEncryptType("AES");
+            alipayConfig.setEncryptKey(encryptKey);
+        }
+        return alipayConfig;
+    }
+
+    @Override
+    public R createPrePayOrder(String price, String subject) throws Exception {
+        log.info("创建支付宝预支付订单,价格:{},描述:{}", price, subject);
+        // 参数验证
+        if (price == null || price.trim().isEmpty()) {
+            return R.fail("价格不能为空");
+        }
+        if (subject == null || subject.trim().isEmpty()) {
+            return R.fail("订单描述不能为空");
+        }
+        try {
+            BigDecimal amount = new BigDecimal(price);
+            if (amount.compareTo(BigDecimal.ZERO) <= 0) {
+                return R.fail("价格必须大于0");
+            }
+        } catch (NumberFormatException e) {
+            return R.fail("价格格式不正确");
+        }
+        // 生成订单号
+        String orderNo = UniqueRandomNumGenerator.generateUniqueCode(19);
+        // 构建预支付请求
+        AlipayTradeAppPayRequest request = buildPrePayRequest(price, subject,orderNo);
+        
+        try {
+            // 调用支付宝接口创建预支付订单
+            JSONObject result = prePayOrderRun(request);
+            result.put("orderNo", orderNo);
+            log.info("支付宝预支付订单创建成功,商家订单号:{}", orderNo);
+            return R.data(result);
+        } catch (AlipayApiException e) {
+            log.error("支付宝预支付失败,错误码:{},错误信息:{}", e.getErrCode(), e.getErrMsg());
+            return R.fail("支付宝预支付失败:" + e.getErrMsg());
+        }
+    }
+
+    @Override
+    public R<Object> handleNotify(String notifyData) throws Exception {
+        log.info("处理支付宝支付通知,通知数据:{}", notifyData);
+        // TODO: 实现支付通知处理逻辑
+        // 1. 验证签名
+        // 2. 解析通知数据
+        // 3. 更新订单状态
+        // 4. 返回处理结果
+        return R.fail("功能尚未实现");
+    }
+
+    @Override
+    public R<Object> searchOrderByOutTradeNoPath(String transactionId) throws Exception {
+        return null;
+    }
+
+    @Override
+    public String handleRefund(Map<String, String> params) throws Exception {
+        log.info("处理支付宝退款请求,参数:{}", params);
+        try {
+            AlipayTradeRefundRequest request = buildRefundRequest(params);
+            AlipayTradeRefundResponse response = refundRun(request);
+            String refundReslut = "";
+            if (response.isSuccess()) {
+                refundReslut = "调用成功";
+                // 保存退款信息进入到退款记录表
+                JSONObject responseBody = JSONObject.parseObject(response.getBody());
+                JSONObject refundResponse = responseBody.getJSONObject("alipay_trade_refund_response");
+
+                StoreAliPayRefundLog refundLog = new StoreAliPayRefundLog();
+                // 响应基本信息
+                refundLog.setResponseCode(refundResponse.getString("code"));
+                refundLog.setResponseMsg(refundResponse.getString("msg"));
+                // 买家信息
+                refundLog.setBuyerLogonId(refundResponse.getString("buyer_logon_id"));
+                refundLog.setBuyerOpenId(refundResponse.getString("buyer_open_id"));
+                // 资金变动信息
+                refundLog.setFundChange(refundResponse.getString("fund_change"));
+                // 订单信息
+                refundLog.setOutTradeNo(refundResponse.getString("out_trade_no"));
+                refundLog.setTradeNo(refundResponse.getString("trade_no"));
+                // 退款金额信息
+                refundLog.setRefundFee(refundResponse.getString("refund_fee"));
+                refundLog.setSendBackFee(refundResponse.getString("send_back_fee"));
+                // 退款渠道明细(转换为JSON字符串)
+                if (refundResponse.containsKey("refund_detail_item_list")) {
+                    JSONArray refundDetailList = refundResponse.getJSONArray("refund_detail_item_list");
+                    if (refundDetailList != null) {
+                        refundLog.setRefundDetailItemList(JSON.toJSONString(refundDetailList));
+                    }
+                }
+                // 退款时间(字符串转Date)
+                String gmtRefundPayStr = refundResponse.getString("gmt_refund_pay");
+                if (StringUtils.isNotBlank(gmtRefundPayStr)) {
+                    try {
+                        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+                        refundLog.setGmtRefundPay(sdf.parse(gmtRefundPayStr));
+                    } catch (ParseException e) {
+                        log.warn("解析退款时间失败: {}", gmtRefundPayStr, e);
+                    }
+                }
+                // 退款原因和部分退款编号(从请求参数中获取)
+                refundLog.setRefundReason(params.get("refundReason"));
+                refundLog.setOutRequestNo(params.get("partialRefundCode"));
+                // 证书和签名信息
+                refundLog.setAlipayCertSn(responseBody.getString("alipay_cert_sn"));
+                refundLog.setSign(responseBody.getString("sign"));
+                // 标准字段
+                refundLog.setDeleteFlag(0);
+                refundLog.setCreatedTime(new Date());
+                storeAliPayRefundLogService.save(refundLog);
+            } else {
+                log.warn("AliPayConfig.processRefund ERROR Msg={}", response.getBody());
+                JSONObject jsonObject = JSONObject.parseObject(response.getBody()).getJSONObject("alipay_trade_refund_response");
+                refundReslut =  jsonObject.getString("sub_msg");
+            }
+            return refundReslut;
+        } catch (AlipayApiException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Override
+    public String getType() {
+        return PaymentEnum.ALIPAY.getType();
+    }
+
+
+    /**
+     * 构建预支付请求
+     */
+    private AlipayTradeAppPayRequest buildPrePayRequest(String price, String subject,String outTradeNo) {
+            SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMddHHmmss");
+            AlipayTradeAppPayRequest request = new AlipayTradeAppPayRequest();
+            AlipayTradeAppPayModel model = new AlipayTradeAppPayModel();
+            model.setOutTradeNo(outTradeNo);
+            model.setTotalAmount(price);
+            model.setSubject(subject);
+            model.setPassbackParams(UrlEncode.getUrlEncode(dateFormat.toString()));
+            request.setBizModel(model);
+            return request;
+    }
+
+    /**
+     * 构建退款请求
+     */
+    private AlipayTradeRefundRequest buildRefundRequest(Map<String, String> params) {
+        AlipayTradeRefundRequest request = new AlipayTradeRefundRequest();
+        AlipayTradeRefundModel model = new AlipayTradeRefundModel();
+        model.setOutTradeNo(params.get("outTradeNo"));
+        model.setRefundAmount(params.get("refundAmount"));
+        model.setRefundReason(params.get("refundReason"));
+        if (StringUtils.isNotBlank(params.get("partialRefundCode"))) {
+            model.setOutRequestNo(params.get("partialRefundCode"));
+        }
+        request.setBizModel(model);
+        return request;
+    }
+
+    /**
+     * 执行预支付订单请求
+     */
+    private JSONObject prePayOrderRun(AlipayTradeAppPayRequest request) throws AlipayApiException {
+        JSONObject jsonObject = new JSONObject();
+        AlipayClient alipayClient = new DefaultAlipayClient(setAlipayConfig(null));
+        AlipayTradeAppPayResponse response = alipayClient.sdkExecute(request);
+        String orderStr = "";
+        if (response.isSuccess()) {
+            orderStr = response.getBody();
+        } else {
+            System.out.println("调用失败 response:" + response);
+            orderStr = "调用失败";
+        }
+        jsonObject.put("orderStr", orderStr);
+        return jsonObject;
+    }
+
+
+
+    /**
+     * 执行退款请求
+     */
+    private AlipayTradeRefundResponse refundRun(AlipayTradeRefundRequest request) throws AlipayApiException {
+        AlipayClient alipayClient = new DefaultAlipayClient(setAlipayConfig(null));
+        AlipayTradeRefundResponse response = alipayClient.certificateExecute(request);
+        return response;
+    }
+
+}
+

+ 867 - 0
alien-store/src/main/java/shop/alien/store/strategy/payment/impl/WeChatPaymentStrategyImpl.java

@@ -0,0 +1,867 @@
+package shop.alien.store.strategy.payment.impl;
+
+import com.google.gson.annotations.Expose;
+import com.google.gson.annotations.SerializedName;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import okhttp3.*;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+import shop.alien.entity.result.R;
+import shop.alien.store.strategy.payment.PaymentStrategy;
+import shop.alien.store.util.WXPayUtility;
+import shop.alien.util.common.UniqueRandomNumGenerator;
+import shop.alien.util.common.constant.PaymentEnum;
+import shop.alien.util.system.OSUtil;
+
+import javax.annotation.PostConstruct;
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.math.BigDecimal;
+import java.nio.charset.StandardCharsets;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.Signature;
+import java.util.Base64;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 微信支付策略
+ *
+ * @author lyx
+ * @date 2025/11/20
+ */
+
+@Slf4j
+@Component
+@RequiredArgsConstructor
+public class WeChatPaymentStrategyImpl implements PaymentStrategy {
+
+    @Value("${payment.wechatPay.host}")
+    private String wechatPayApiHost;
+
+    @Value("${payment.wechatPay.prePayPath}")
+    private String prePayPath;
+
+    @Value("${payment.wechatPay.searchOrderByTransactionIdPath}")
+    private String searchOrderByTransactionIdPath;
+
+    @Value("${payment.wechatPay.searchOrderByOutTradeNoPath}")
+    private String searchOrderByOutTradeNoPath;
+
+    @Value("${payment.wechatPay.refundPath}")
+    private String refundPath;
+
+    /**
+     * 微信支付应用id
+     */
+    @Value("${payment.wechatPay.business.appId}")
+    private String appId;
+
+    /**
+     * 微信支付商户id
+     */
+    @Value("${payment.wechatPay.business.mchId}")
+    private String mchId;
+
+    /**
+     * 微信支付商户私钥路径
+     */
+    @Value("${payment.wechatPay.business.win.privateKeyPath}")
+    private String privateWinKeyPath;
+
+    /**
+     * 微信支付商户私钥路径(Linux环境)
+     */
+    @Value("${payment.wechatPay.business.linux.privateKeyPath}")
+    private String privateLinuxKeyPath;
+
+    /**
+     * 微信支付公钥路径
+     */
+    @Value("${payment.wechatPay.business.win.wechatPayPublicKeyFilePath}")
+    private String wechatWinPayPublicKeyFilePath;
+
+    /**
+     * 微信支付公钥路径(Linux环境)
+     */
+    @Value("${payment.wechatPay.business.linux.wechatPayPublicKeyFilePath}")
+    private String wechatLinuxPayPublicKeyFilePath;
+
+    /**
+     * 微信支付商户证书序列号
+     */
+    @Value("${payment.wechatPay.business.merchantSerialNumber}")
+    private String merchantSerialNumber;
+
+    /**
+     * 微信支付API V3密钥
+     */
+    @Value("${payment.wechatPay.business.apiV3key}")
+    private String apiV3key;
+
+    /**
+     * 微信支付公钥id
+     */
+    @Value("${payment.wechatPay.business.wechatPayPublicKeyId}")
+    private String wechatPayPublicKeyId;
+
+    @Value("${payment.wechatPay.business.prePayNotifyUrl}")
+    private String prePayNotifyUrl;
+
+    @Value("${payment.wechatPay.business.refundNotifyUrl}")
+    private String refundNotifyUrl;
+
+    private PrivateKey privateKey;
+    private PublicKey wechatPayPublicKey;
+
+    private static String POSTMETHOD = "POST";
+    private static String GETMETHOD = "GET";
+
+    @PostConstruct
+    public void setWeChatPaymentConfig() {
+        String privateKeyPath;
+        String wechatPayPublicKeyFilePath;
+        if ("windows".equals(OSUtil.getOsName())) {
+            privateKeyPath = privateWinKeyPath;
+            wechatPayPublicKeyFilePath = wechatWinPayPublicKeyFilePath;
+        } else {
+            privateKeyPath = privateLinuxKeyPath;
+            wechatPayPublicKeyFilePath = wechatLinuxPayPublicKeyFilePath;
+        }
+        this.privateKey = WXPayUtility.loadPrivateKeyFromPath(privateKeyPath);
+        this.wechatPayPublicKey = WXPayUtility.loadPublicKeyFromPath(wechatPayPublicKeyFilePath);
+    }
+
+    @Override
+    public R createPrePayOrder(String price, String subject) throws Exception {
+        log.info("创建微信预支付订单,价格:{},描述:{}", price, subject);
+        // 参数验证
+        if (price == null || price.trim().isEmpty()) {
+            return R.fail("价格不能为空");
+        }
+        if (subject == null || subject.trim().isEmpty()) {
+            return R.fail("订单描述不能为空");
+        }
+        try {
+            BigDecimal amount = new BigDecimal(price);
+            if (amount.compareTo(BigDecimal.ZERO) <= 0) {
+                return R.fail("价格必须大于0");
+            }
+        } catch (NumberFormatException e) {
+            return R.fail("价格格式不正确");
+        }
+
+        CommonPrepayRequest request = new CommonPrepayRequest();
+        request.appid = appId;
+        request.mchid = mchId;
+        request.description = subject;
+        request.outTradeNo = UniqueRandomNumGenerator.generateUniqueCode(19);
+
+//        request.timeExpire = "2018-06-08T10:34:56+08:00"; 超时支付不传默认7天
+//        request.attach = "自定义数据说明";
+        // 目前没用,但是必填
+        request.notifyUrl = prePayNotifyUrl;
+//        request.goodsTag = "WXG";
+//        request.supportFapiao = false;
+        request.amount = new CommonAmountInfo();
+        request.amount.total = new BigDecimal(price).multiply(new BigDecimal(100)).longValue();
+        request.amount.currency = "CNY";
+        /* 【优惠功能】 优惠功能
+        request.detail = new CouponInfo();
+        request.detail.costPrice = 608800L;
+        request.detail.invoiceId = "微信123";
+        request.detail.goodsDetail = new ArrayList<>();
+        {
+            GoodsDetail goodsDetailItem = new GoodsDetail();
+            goodsDetailItem.merchantGoodsId = "1246464644";
+            goodsDetailItem.wechatpayGoodsId = "1001";
+            goodsDetailItem.goodsName = "iPhoneX 256G";
+            goodsDetailItem.quantity = 1L;
+            goodsDetailItem.unitPrice = 528800L;
+            request.detail.goodsDetail.add(goodsDetailItem);
+        };*/
+        /* 【场景信息】 场景信息
+        request.sceneInfo = new CommonSceneInfo();
+        request.sceneInfo.payerClientIp = "14.23.150.211";
+        request.sceneInfo.deviceId = "013467007045764";
+        request.sceneInfo.storeInfo = new StoreInfo();
+        request.sceneInfo.storeInfo.id = "0001";
+        request.sceneInfo.storeInfo.name = "腾讯大厦分店";
+        request.sceneInfo.storeInfo.areaCode = "440305";
+        request.sceneInfo.storeInfo.address = "广东省深圳市南山区科技中一道10000号";*/
+        /* 【结算信息】 结算信息
+        request.settleInfo = new SettleInfo();
+        request.settleInfo.profitSharing = false;*/
+        try {
+            DirectAPIv3AppPrepayResponse response = this.prePayOrderRun(request);
+            log.info("微信预支付订单创建成功,预支付ID:{}", response.prepayId);
+            Map<String,String> result = new HashMap<>();
+            result.put("prepayId", response.prepayId);
+            result.put("appId", appId);
+            result.put("mchId", mchId);
+            result.put("outTradeNo", request.outTradeNo);
+            // 生成sign
+//            appId
+
+//            随机字符串
+//            prepay_id
+            long timestamp = System.currentTimeMillis() / 1000; // 时间戳
+            String nonce = WXPayUtility.createNonce(32); // 随机字符串
+            String prepayId = response.prepayId;
+            String message = String.format("%s\n%s\n%s\n%s\n", appId, timestamp, nonce, prepayId);
+//            String sign = WXPayUtility.sign(message, sha256withRSA, privateKey);
+            Signature sign = Signature.getInstance("SHA256withRSA");
+            sign.initSign(privateKey);
+            sign.update(message.getBytes(StandardCharsets.UTF_8));
+            result.put("sign", Base64.getEncoder().encodeToString(sign.sign()));
+            result.put("timestamp", String.valueOf(timestamp));
+            result.put("nonce", nonce);
+            return R.data(result);
+        } catch (WXPayUtility.ApiException e) {
+            log.error("微信支付预支付失败,状态码:{},错误信息:{}", e.getErrorCode(), e.getMessage());
+            return R.fail(e.getMessage());
+        }
+    }
+
+    @Override
+    public R handleNotify(String notifyData) throws Exception {
+        /**
+         * 目前没用,先写着
+         */
+        return null;
+    }
+
+    @Override
+    public R searchOrderByOutTradeNoPath(String transactionId) throws Exception {
+        log.info("查询微信支付订单状态,交易订单号:{}", transactionId);
+        QueryByWxTradeNoRequest request = new QueryByWxTradeNoRequest();
+        request.transactionId = transactionId;
+        request.mchid = mchId;
+        try {
+            DirectAPIv3QueryResponse response = searchOrderRun(request);
+            return R.data(response);
+        } catch (WXPayUtility.ApiException e) {
+            log.error("查询微信支付订单状态失败,状态码:{},错误信息:{}", e.getErrorCode(), e.getMessage());
+            return R.fail(e.getMessage());
+        }
+    }
+
+    @Override
+    public String handleRefund(Map<String,String> params) throws Exception {
+        CreateRequest request = new CreateRequest();
+        // 微信支付订单号和商户订单号必须二选一,不能同时为空,查询的时候使用的是商户订单号
+        //request.transactionId = "1217752501201407033233368018";
+        request.outTradeNo = params.get("outTradeNo");
+        // 退款订单号
+        request.outRefundNo = UniqueRandomNumGenerator.generateUniqueCode(19);
+        // 退款原因
+        request.reason = params.get("reason");
+        // 退款回调通知地址
+        request.notifyUrl = refundNotifyUrl;
+        // 退款资金来源选填
+        //request.fundsAccount = ReqFundsAccount.AVAILABLE;
+        // 金额信息
+        request.amount = new AmountReq();
+        request.amount.refund = new BigDecimal(params.get("refundAmount")).multiply(new BigDecimal(100)).longValue();
+        // 退款出资账户及金额 目前不需要,需要的时候再看
+        /*
+        request.amount.from = new ArrayList<>();
+        {
+            FundsFromItem fromItem = new FundsFromItem();
+            fromItem.account = Account.AVAILABLE;
+            fromItem.amount = 444L;
+            request.amount.from.add(fromItem);
+        };*/
+        // 订单总金额
+        request.amount.total = new BigDecimal(params.get("totalAmount")).multiply(new BigDecimal(100)).longValue();
+        // 退款币种 目前默认人民币 CNY
+        request.amount.currency = "CNY";
+        // 退款商品信息 目前不需要,需要的时候再看
+        /*
+        request.goodsDetail = new ArrayList<>();
+        {
+            GoodsDetail goodsDetailItem = new GoodsDetail();
+            goodsDetailItem.merchantGoodsId = "1217752501201407033233368018";
+            goodsDetailItem.wechatpayGoodsId = "1001";
+            goodsDetailItem.goodsName = "iPhone6s 16G";
+            goodsDetailItem.unitPrice = 528800L;
+            goodsDetailItem.refundAmount = 528800L;
+            goodsDetailItem.refundQuantity = 1L;
+            request.goodsDetail.add(goodsDetailItem);
+        };*/
+        try {
+            Refund response = refundRun(request);
+            String refundReslut = "";
+            // TODO: 请求成功,继续业务逻辑
+            System.out.println(response);
+        } catch (WXPayUtility.ApiException e) {
+            // TODO: 请求失败,根据状态码执行不同的逻辑
+            e.printStackTrace();
+        }
+        return null;
+    }
+
+
+    @Override
+    public String getType() {
+        return PaymentEnum.WECHAT_PAY.getType();
+    }
+
+
+    public DirectAPIv3AppPrepayResponse prePayOrderRun(CommonPrepayRequest request) {
+        String uri = prePayPath;
+        String reqBody = WXPayUtility.toJson(request);
+
+        Request.Builder reqBuilder = new Request.Builder().url(wechatPayApiHost + uri);
+        reqBuilder.addHeader("Accept", "application/json");
+        reqBuilder.addHeader("Wechatpay-Serial", wechatPayPublicKeyId);
+        reqBuilder.addHeader("Authorization", WXPayUtility.buildAuthorization(mchId, merchantSerialNumber, privateKey, POSTMETHOD, uri, reqBody));
+        reqBuilder.addHeader("Content-Type", "application/json");
+        RequestBody requestBody = RequestBody.create(MediaType.parse("application/json; charset=utf-8"), reqBody);
+        reqBuilder.method(POSTMETHOD, requestBody);
+        Request httpRequest = reqBuilder.build();
+
+        // 发送HTTP请求
+        OkHttpClient client = new OkHttpClient.Builder().build();
+        try (Response httpResponse = client.newCall(httpRequest).execute()) {
+            String respBody = WXPayUtility.extractBody(httpResponse);
+            if (httpResponse.code() >= 200 && httpResponse.code() < 300) {
+                // 2XX 成功,验证应答签名
+                WXPayUtility.validateResponse(wechatPayPublicKeyId, wechatPayPublicKey,
+                        httpResponse.headers(), respBody);
+
+                // 从HTTP应答报文构建返回数据
+                return WXPayUtility.fromJson(respBody, DirectAPIv3AppPrepayResponse.class);
+            } else {
+                throw new WXPayUtility.ApiException(httpResponse.code(), respBody, httpResponse.headers());
+            }
+        } catch (IOException e) {
+            throw new UncheckedIOException("Sending request to " + uri + " failed.", e);
+        }
+    }
+
+    public DirectAPIv3QueryResponse searchOrderRun(QueryByWxTradeNoRequest request) {
+        String uri = searchOrderByOutTradeNoPath;
+        uri = uri.replace("{out_trade_no}", WXPayUtility.urlEncode(request.transactionId));
+        Map<String, Object> args = new HashMap<>();
+        args.put("mchid", mchId);
+        String queryString = WXPayUtility.urlEncode(args);
+        if (!queryString.isEmpty()) {
+            uri = uri + "?" + queryString;
+        }
+
+        Request.Builder reqBuilder = new Request.Builder().url(wechatPayApiHost + uri);
+        reqBuilder.addHeader("Accept", "application/json");
+        reqBuilder.addHeader("Wechatpay-Serial", wechatPayPublicKeyId);
+        reqBuilder.addHeader("Authorization", WXPayUtility.buildAuthorization(mchId, merchantSerialNumber, privateKey, GETMETHOD, uri, null));
+        reqBuilder.method(GETMETHOD, null);
+        Request httpRequest = reqBuilder.build();
+
+        // 发送HTTP请求
+        OkHttpClient client = new OkHttpClient.Builder().build();
+        try (Response httpResponse = client.newCall(httpRequest).execute()) {
+            String respBody = WXPayUtility.extractBody(httpResponse);
+            if (httpResponse.code() >= 200 && httpResponse.code() < 300) {
+                // 2XX 成功,验证应答签名
+                WXPayUtility.validateResponse(this.wechatPayPublicKeyId, this.wechatPayPublicKey,
+                        httpResponse.headers(), respBody);
+
+                // 从HTTP应答报文构建返回数据
+                return WXPayUtility.fromJson(respBody, DirectAPIv3QueryResponse.class);
+            } else {
+                throw new WXPayUtility.ApiException(httpResponse.code(), respBody, httpResponse.headers());
+            }
+        } catch (IOException e) {
+            throw new UncheckedIOException("Sending request to " + uri + " failed.", e);
+        }
+    }
+
+    public Refund refundRun(CreateRequest request) {
+        String uri = refundPath;
+        String reqBody = WXPayUtility.toJson(request);
+
+        Request.Builder reqBuilder = new Request.Builder().url(wechatPayApiHost + uri);
+        reqBuilder.addHeader("Accept", "application/json");
+        reqBuilder.addHeader("Wechatpay-Serial", wechatPayPublicKeyId);
+        reqBuilder.addHeader("Authorization", WXPayUtility.buildAuthorization(mchId, merchantSerialNumber, privateKey, POSTMETHOD, uri, reqBody));
+        reqBuilder.addHeader("Content-Type", "application/json");
+        RequestBody requestBody = RequestBody.create(MediaType.parse("application/json; charset=utf-8"), reqBody);
+        reqBuilder.method(POSTMETHOD, requestBody);
+        Request httpRequest = reqBuilder.build();
+
+        // 发送HTTP请求
+        OkHttpClient client = new OkHttpClient.Builder().build();
+        try (Response httpResponse = client.newCall(httpRequest).execute()) {
+            String respBody = WXPayUtility.extractBody(httpResponse);
+            if (httpResponse.code() >= 200 && httpResponse.code() < 300) {
+                // 2XX 成功,验证应答签名
+                WXPayUtility.validateResponse(this.wechatPayPublicKeyId, this.wechatPayPublicKey,
+                        httpResponse.headers(), respBody);
+
+                // 从HTTP应答报文构建返回数据
+                return WXPayUtility.fromJson(respBody, Refund.class);
+            } else {
+                throw new WXPayUtility.ApiException(httpResponse.code(), respBody, httpResponse.headers());
+            }
+        } catch (IOException e) {
+            throw new UncheckedIOException("Sending request to " + uri + " failed.", e);
+        }
+    }
+
+    /*****************************************下面为支付使用参数*****************************************/
+    public static class CommonPrepayRequest {
+        @SerializedName("appid")
+        public String appid;
+
+        @SerializedName("mchid")
+        public String mchid;
+
+        @SerializedName("description")
+        public String description;
+
+        @SerializedName("out_trade_no")
+        public String outTradeNo;
+
+        @SerializedName("time_expire")
+        public String timeExpire;
+
+        @SerializedName("attach")
+        public String attach;
+
+        @SerializedName("notify_url")
+        public String notifyUrl;
+
+        @SerializedName("goods_tag")
+        public String goodsTag;
+
+        @SerializedName("support_fapiao")
+        public Boolean supportFapiao;
+
+        @SerializedName("amount")
+        public CommonAmountInfo amount;
+
+        @SerializedName("detail")
+        public CouponInfo detail;
+
+        @SerializedName("scene_info")
+        public CommonSceneInfo sceneInfo;
+
+        @SerializedName("settle_info")
+        public SettleInfo settleInfo;
+    }
+
+    public static class DirectAPIv3AppPrepayResponse {
+        @SerializedName("prepay_id")
+        public String prepayId;
+    }
+
+    public static class CommonAmountInfo {
+        @SerializedName("total")
+        public Long total;
+
+        @SerializedName("currency")
+        public String currency;
+    }
+
+    public static class CouponInfo {
+        @SerializedName("cost_price")
+        public Long costPrice;
+
+        @SerializedName("invoice_id")
+        public String invoiceId;
+
+        @SerializedName("goods_detail")
+        public List<GoodsDetail> goodsDetail;
+    }
+
+    public static class CommonSceneInfo {
+        @SerializedName("payer_client_ip")
+        public String payerClientIp;
+
+        @SerializedName("device_id")
+        public String deviceId;
+
+        @SerializedName("store_info")
+        public StoreInfo storeInfo;
+    }
+
+    public static class SettleInfo {
+        @SerializedName("profit_sharing")
+        public Boolean profitSharing;
+    }
+
+    public static class StoreInfo {
+        @SerializedName("id")
+        public String id;
+
+        @SerializedName("name")
+        public String name;
+
+        @SerializedName("area_code")
+        public String areaCode;
+
+        @SerializedName("address")
+        public String address;
+    }
+
+    public static class QueryByWxTradeNoRequest {
+        @SerializedName("mchid")
+        @Expose(serialize = false)
+        public String mchid;
+
+        @SerializedName("transaction_id")
+        @Expose(serialize = false)
+        public String transactionId;
+    }
+
+    public static class DirectAPIv3QueryResponse {
+        @SerializedName("appid")
+        public String appid;
+
+        @SerializedName("mchid")
+        public String mchid;
+
+        @SerializedName("out_trade_no")
+        public String outTradeNo;
+
+        @SerializedName("transaction_id")
+        public String transactionId;
+
+        @SerializedName("trade_type")
+        public String tradeType;
+
+        @SerializedName("trade_state")
+        public String tradeState;
+
+        @SerializedName("trade_state_desc")
+        public String tradeStateDesc;
+
+        @SerializedName("bank_type")
+        public String bankType;
+
+        @SerializedName("attach")
+        public String attach;
+
+        @SerializedName("success_time")
+        public String successTime;
+
+        @SerializedName("payer")
+        public CommRespPayerInfo payer;
+
+        @SerializedName("amount")
+        public CommRespAmountInfo amount;
+
+        @SerializedName("scene_info")
+        public CommRespSceneInfo sceneInfo;
+
+        @SerializedName("promotion_detail")
+        public List<PromotionDetail> promotionDetail;
+    }
+
+    public static class CommRespPayerInfo {
+        @SerializedName("openid")
+        public String openid;
+    }
+
+    public static class CommRespAmountInfo {
+        @SerializedName("total")
+        public Long total;
+
+        @SerializedName("payer_total")
+        public Long payerTotal;
+
+        @SerializedName("currency")
+        public String currency;
+
+        @SerializedName("payer_currency")
+        public String payerCurrency;
+    }
+
+    public static class CommRespSceneInfo {
+        @SerializedName("device_id")
+        public String deviceId;
+    }
+
+    public static class PromotionDetail {
+        @SerializedName("coupon_id")
+        public String couponId;
+
+        @SerializedName("name")
+        public String name;
+
+        @SerializedName("scope")
+        public String scope;
+
+        @SerializedName("type")
+        public String type;
+
+        @SerializedName("amount")
+        public Long amount;
+
+        @SerializedName("stock_id")
+        public String stockId;
+
+        @SerializedName("wechatpay_contribute")
+        public Long wechatpayContribute;
+
+        @SerializedName("merchant_contribute")
+        public Long merchantContribute;
+
+        @SerializedName("other_contribute")
+        public Long otherContribute;
+
+        @SerializedName("currency")
+        public String currency;
+
+        @SerializedName("goods_detail")
+        public List<GoodsDetailInPromotion> goodsDetail;
+    }
+
+    public static class GoodsDetailInPromotion {
+        @SerializedName("goods_id")
+        public String goodsId;
+
+        @SerializedName("quantity")
+        public Long quantity;
+
+        @SerializedName("unit_price")
+        public Long unitPrice;
+
+        @SerializedName("discount_amount")
+        public Long discountAmount;
+
+        @SerializedName("goods_remark")
+        public String goodsRemark;
+    }
+
+    public static class CreateRequest {
+        @SerializedName("transaction_id")
+        public String transactionId;
+
+        @SerializedName("out_trade_no")
+        public String outTradeNo;
+
+        @SerializedName("out_refund_no")
+        public String outRefundNo;
+
+        @SerializedName("reason")
+        public String reason;
+
+        @SerializedName("notify_url")
+        public String notifyUrl;
+
+        @SerializedName("funds_account")
+        public ReqFundsAccount fundsAccount;
+
+        @SerializedName("amount")
+        public AmountReq amount;
+
+        @SerializedName("goods_detail")
+        public List<GoodsDetail> goodsDetail;
+
+    }
+
+    public static class Refund {
+        @SerializedName("refund_id")
+        public String refundId;
+
+        @SerializedName("out_refund_no")
+        public String outRefundNo;
+
+        @SerializedName("transaction_id")
+        public String transactionId;
+
+        @SerializedName("out_trade_no")
+        public String outTradeNo;
+
+        @SerializedName("channel")
+        public Channel channel;
+
+        @SerializedName("user_received_account")
+        public String userReceivedAccount;
+
+        @SerializedName("success_time")
+        public String successTime;
+
+        @SerializedName("create_time")
+        public String createTime;
+
+        @SerializedName("status")
+        public Status status;
+
+        @SerializedName("funds_account")
+        public FundsAccount fundsAccount;
+
+        @SerializedName("amount")
+        public Amount amount;
+
+        @SerializedName("promotion_detail")
+        public List<Promotion> promotionDetail;
+
+    }
+
+    public enum ReqFundsAccount {
+        @SerializedName("AVAILABLE")
+        AVAILABLE,
+        @SerializedName("UNSETTLED")
+        UNSETTLED
+    }
+
+    public static class AmountReq {
+        @SerializedName("refund")
+        public Long refund;
+
+        @SerializedName("from")
+        public List<FundsFromItem> from;
+
+        @SerializedName("total")
+        public Long total;
+
+        @SerializedName("currency")
+        public String currency;
+    }
+
+    public static class GoodsDetail {
+        @SerializedName("merchant_goods_id")
+        public String merchantGoodsId;
+
+        @SerializedName("wechatpay_goods_id")
+        public String wechatpayGoodsId;
+
+        @SerializedName("goods_name")
+        public String goodsName;
+
+        @SerializedName("quantity")
+        public Long quantity;
+
+        @SerializedName("unit_price")
+        public Long unitPrice;
+
+        @SerializedName("refund_amount")
+        public Long refundAmount;
+
+        @SerializedName("refund_quantity")
+        public Long refundQuantity;
+    }
+
+    public enum Channel {
+        @SerializedName("ORIGINAL")
+        ORIGINAL,
+        @SerializedName("BALANCE")
+        BALANCE,
+        @SerializedName("OTHER_BALANCE")
+        OTHER_BALANCE,
+        @SerializedName("OTHER_BANKCARD")
+        OTHER_BANKCARD
+    }
+
+    public enum Status {
+        @SerializedName("SUCCESS")
+        SUCCESS,
+        @SerializedName("CLOSED")
+        CLOSED,
+        @SerializedName("PROCESSING")
+        PROCESSING,
+        @SerializedName("ABNORMAL")
+        ABNORMAL
+    }
+
+    public enum FundsAccount {
+        @SerializedName("UNSETTLED")
+        UNSETTLED,
+        @SerializedName("AVAILABLE")
+        AVAILABLE,
+        @SerializedName("UNAVAILABLE")
+        UNAVAILABLE,
+        @SerializedName("OPERATION")
+        OPERATION,
+        @SerializedName("BASIC")
+        BASIC,
+        @SerializedName("ECNY_BASIC")
+        ECNY_BASIC
+    }
+
+    public static class Amount {
+        @SerializedName("total")
+        public Long total;
+
+        @SerializedName("refund")
+        public Long refund;
+
+        @SerializedName("from")
+        public List<FundsFromItem> from;
+
+        @SerializedName("payer_total")
+        public Long payerTotal;
+
+        @SerializedName("payer_refund")
+        public Long payerRefund;
+
+        @SerializedName("settlement_refund")
+        public Long settlementRefund;
+
+        @SerializedName("settlement_total")
+        public Long settlementTotal;
+
+        @SerializedName("discount_refund")
+        public Long discountRefund;
+
+        @SerializedName("currency")
+        public String currency;
+
+        @SerializedName("refund_fee")
+        public Long refundFee;
+    }
+
+    public static class Promotion {
+        @SerializedName("promotion_id")
+        public String promotionId;
+
+        @SerializedName("scope")
+        public PromotionScope scope;
+
+        @SerializedName("type")
+        public PromotionType type;
+
+        @SerializedName("amount")
+        public Long amount;
+
+        @SerializedName("refund_amount")
+        public Long refundAmount;
+
+        @SerializedName("goods_detail")
+        public List<GoodsDetail> goodsDetail;
+    }
+
+    public static class FundsFromItem {
+        @SerializedName("account")
+        public Account account;
+
+        @SerializedName("amount")
+        public Long amount;
+    }
+
+    public enum PromotionScope {
+        @SerializedName("GLOBAL")
+        GLOBAL,
+        @SerializedName("SINGLE")
+        SINGLE
+    }
+
+    public enum PromotionType {
+        @SerializedName("CASH")
+        CASH,
+        @SerializedName("NOCASH")
+        NOCASH
+    }
+
+    public enum Account {
+        @SerializedName("AVAILABLE")
+        AVAILABLE,
+        @SerializedName("UNAVAILABLE")
+        UNAVAILABLE
+    }
+}

+ 856 - 0
alien-store/src/main/java/shop/alien/store/util/WXPayUtility.java

@@ -0,0 +1,856 @@
+package shop.alien.store.util;
+
+import com.google.gson.*;
+import com.google.gson.annotations.Expose;
+import com.google.gson.annotations.SerializedName;
+import okhttp3.Headers;
+import okhttp3.Response;
+import okio.BufferedSource;
+import org.bouncycastle.crypto.digests.SM3Digest;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+
+import javax.crypto.BadPaddingException;
+import javax.crypto.Cipher;
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.NoSuchPaddingException;
+import javax.crypto.spec.GCMParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.UncheckedIOException;
+import java.io.UnsupportedEncodingException;
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.security.*;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.security.spec.X509EncodedKeySpec;
+import java.time.DateTimeException;
+import java.time.Duration;
+import java.time.Instant;
+import java.util.*;
+import java.util.Map.Entry;
+
+public class WXPayUtility {
+    private static final Gson gson = new GsonBuilder()
+            .disableHtmlEscaping()
+            .addSerializationExclusionStrategy(new ExclusionStrategy() {
+                @Override
+                public boolean shouldSkipField(FieldAttributes fieldAttributes) {
+                    final Expose expose = fieldAttributes.getAnnotation(Expose.class);
+                    return expose != null && !expose.serialize();
+                }
+
+                @Override
+                public boolean shouldSkipClass(Class<?> aClass) {
+                    return false;
+                }
+            })
+            .addDeserializationExclusionStrategy(new ExclusionStrategy() {
+                @Override
+                public boolean shouldSkipField(FieldAttributes fieldAttributes) {
+                    final Expose expose = fieldAttributes.getAnnotation(Expose.class);
+                    return expose != null && !expose.deserialize();
+                }
+
+                @Override
+                public boolean shouldSkipClass(Class<?> aClass) {
+                    return false;
+                }
+            })
+            .create();
+    private static final char[] SYMBOLS =
+            "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ".toCharArray();
+    private static final SecureRandom random = new SecureRandom();
+
+    /**
+     * 将 Object 转换为 JSON 字符串
+     */
+    public static String toJson(Object object) {
+        return gson.toJson(object);
+    }
+
+    /**
+     * 将 JSON 字符串解析为特定类型的实例
+     */
+    public static <T> T fromJson(String json, Class<T> classOfT) throws JsonSyntaxException {
+        return gson.fromJson(json, classOfT);
+    }
+
+    /**
+     * 从公私钥文件路径中读取文件内容
+     *
+     * @param keyPath 文件路径
+     * @return 文件内容
+     */
+    private static String readKeyStringFromPath(String keyPath) {
+        try {
+            return new String(Files.readAllBytes(Paths.get(keyPath)), StandardCharsets.UTF_8);
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+    }
+
+    /**
+     * 读取 PKCS#8 格式的私钥字符串并加载为私钥对象
+     *
+     * @param keyString 私钥文件内容,以 -----BEGIN PRIVATE KEY----- 开头
+     * @return PrivateKey 对象
+     */
+    public static PrivateKey loadPrivateKeyFromString(String keyString) {
+        try {
+            keyString = keyString.replace("-----BEGIN PRIVATE KEY-----", "")
+                    .replace("-----END PRIVATE KEY-----", "")
+                    .replaceAll("\\s+", "");
+            return KeyFactory.getInstance("RSA").generatePrivate(
+                    new PKCS8EncodedKeySpec(Base64.getDecoder().decode(keyString)));
+        } catch (NoSuchAlgorithmException e) {
+            throw new UnsupportedOperationException(e);
+        } catch (InvalidKeySpecException e) {
+            throw new IllegalArgumentException(e);
+        }
+    }
+
+    /**
+     * 从 PKCS#8 格式的私钥文件中加载私钥
+     *
+     * @param keyPath 私钥文件路径
+     * @return PrivateKey 对象
+     */
+    public static PrivateKey loadPrivateKeyFromPath(String keyPath) {
+        return loadPrivateKeyFromString(readKeyStringFromPath(keyPath));
+    }
+
+    /**
+     * 读取 PKCS#8 格式的公钥字符串并加载为公钥对象
+     *
+     * @param keyString 公钥文件内容,以 -----BEGIN PUBLIC KEY----- 开头
+     * @return PublicKey 对象
+     */
+    public static PublicKey loadPublicKeyFromString(String keyString) {
+        try {
+            keyString = keyString.replace("-----BEGIN PUBLIC KEY-----", "")
+                    .replace("-----END PUBLIC KEY-----", "")
+                    .replaceAll("\\s+", "");
+            return KeyFactory.getInstance("RSA").generatePublic(
+                    new X509EncodedKeySpec(Base64.getDecoder().decode(keyString)));
+        } catch (NoSuchAlgorithmException e) {
+            throw new UnsupportedOperationException(e);
+        } catch (InvalidKeySpecException e) {
+            throw new IllegalArgumentException(e);
+        }
+    }
+
+    /**
+     * 从 PKCS#8 格式的公钥文件中加载公钥
+     *
+     * @param keyPath 公钥文件路径
+     * @return PublicKey 对象
+     */
+    public static PublicKey loadPublicKeyFromPath(String keyPath) {
+        return loadPublicKeyFromString(readKeyStringFromPath(keyPath));
+    }
+
+    /**
+     * 创建指定长度的随机字符串,字符集为[0-9a-zA-Z],可用于安全相关用途
+     */
+    public static String createNonce(int length) {
+        char[] buf = new char[length];
+        for (int i = 0; i < length; ++i) {
+            buf[i] = SYMBOLS[random.nextInt(SYMBOLS.length)];
+        }
+        return new String(buf);
+    }
+
+    /**
+     * 使用公钥按照 RSA_PKCS1_OAEP_PADDING 算法进行加密
+     *
+     * @param publicKey 加密用公钥对象
+     * @param plaintext 待加密明文
+     * @return 加密后密文
+     */
+    public static String encrypt(PublicKey publicKey, String plaintext) {
+        final String transformation = "RSA/ECB/OAEPWithSHA-1AndMGF1Padding";
+
+        try {
+            Cipher cipher = Cipher.getInstance(transformation);
+            cipher.init(Cipher.ENCRYPT_MODE, publicKey);
+            return Base64.getEncoder().encodeToString(cipher.doFinal(plaintext.getBytes(StandardCharsets.UTF_8)));
+        } catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
+            throw new IllegalArgumentException("The current Java environment does not support " + transformation, e);
+        } catch (InvalidKeyException e) {
+            throw new IllegalArgumentException("RSA encryption using an illegal publicKey", e);
+        } catch (BadPaddingException | IllegalBlockSizeException e) {
+            throw new IllegalArgumentException("Plaintext is too long", e);
+        }
+    }
+
+    public static String aesAeadDecrypt(byte[] key, byte[] associatedData, byte[] nonce,
+                                        byte[] ciphertext) {
+        final String transformation = "AES/GCM/NoPadding";
+        final String algorithm = "AES";
+        final int tagLengthBit = 128;
+
+        try {
+            Cipher cipher = Cipher.getInstance(transformation);
+            cipher.init(
+                    Cipher.DECRYPT_MODE,
+                    new SecretKeySpec(key, algorithm),
+                    new GCMParameterSpec(tagLengthBit, nonce));
+            if (associatedData != null) {
+                cipher.updateAAD(associatedData);
+            }
+            return new String(cipher.doFinal(ciphertext), StandardCharsets.UTF_8);
+        } catch (InvalidKeyException
+                 | InvalidAlgorithmParameterException
+                 | BadPaddingException
+                 | IllegalBlockSizeException
+                 | NoSuchAlgorithmException
+                 | NoSuchPaddingException e) {
+            throw new IllegalArgumentException(String.format("AesAeadDecrypt with %s Failed",
+                    transformation), e);
+        }
+    }
+
+    /**
+     * 使用私钥按照指定算法进行签名
+     *
+     * @param message    待签名串
+     * @param algorithm  签名算法,如 SHA256withRSA
+     * @param privateKey 签名用私钥对象
+     * @return 签名结果
+     */
+    public static String sign(String message, String algorithm, PrivateKey privateKey) {
+        byte[] sign;
+        try {
+            Signature signature = Signature.getInstance(algorithm);
+            signature.initSign(privateKey);
+            signature.update(message.getBytes(StandardCharsets.UTF_8));
+            sign = signature.sign();
+        } catch (NoSuchAlgorithmException e) {
+            throw new UnsupportedOperationException("The current Java environment does not support " + algorithm, e);
+        } catch (InvalidKeyException e) {
+            throw new IllegalArgumentException(algorithm + " signature uses an illegal privateKey.", e);
+        } catch (SignatureException e) {
+            throw new RuntimeException("An error occurred during the sign process.", e);
+        }
+        return Base64.getEncoder().encodeToString(sign);
+    }
+
+    /**
+     * 使用公钥按照特定算法验证签名
+     *
+     * @param message   待签名串
+     * @param signature 待验证的签名内容
+     * @param algorithm 签名算法,如:SHA256withRSA
+     * @param publicKey 验签用公钥对象
+     * @return 签名验证是否通过
+     */
+    public static boolean verify(String message, String signature, String algorithm,
+                                 PublicKey publicKey) {
+        try {
+            Signature sign = Signature.getInstance(algorithm);
+            sign.initVerify(publicKey);
+            sign.update(message.getBytes(StandardCharsets.UTF_8));
+            return sign.verify(Base64.getDecoder().decode(signature));
+        } catch (SignatureException e) {
+            return false;
+        } catch (InvalidKeyException e) {
+            throw new IllegalArgumentException("verify uses an illegal publickey.", e);
+        } catch (NoSuchAlgorithmException e) {
+            throw new UnsupportedOperationException("The current Java environment does not support" + algorithm, e);
+        }
+    }
+
+    /**
+     * 根据微信支付APIv3请求签名规则构造 Authorization 签名
+     *
+     * @param mchid               商户号
+     * @param certificateSerialNo 商户API证书序列号
+     * @param privateKey          商户API证书私钥
+     * @param method              请求接口的HTTP方法,请使用全大写表述,如 GET、POST、PUT、DELETE
+     * @param uri                 请求接口的URL
+     * @param body                请求接口的Body
+     * @return 构造好的微信支付APIv3 Authorization 头
+     */
+    public static String buildAuthorization(String mchid, String certificateSerialNo,
+                                            PrivateKey privateKey,
+                                            String method, String uri, String body) {
+        String nonce = createNonce(32);
+        long timestamp = Instant.now().getEpochSecond();
+
+        String message = String.format("%s\n%s\n%d\n%s\n%s\n", method, uri, timestamp, nonce,
+                body == null ? "" : body);
+
+        String signature = sign(message, "SHA256withRSA", privateKey);
+
+        return String.format(
+                "WECHATPAY2-SHA256-RSA2048 mchid=\"%s\",nonce_str=\"%s\",signature=\"%s\"," +
+                        "timestamp=\"%d\",serial_no=\"%s\"",
+                mchid, nonce, signature, timestamp, certificateSerialNo);
+    }
+
+    /**
+     * 计算输入流的哈希值
+     *
+     * @param inputStream 输入流
+     * @param algorithm   哈希算法名称,如 "SHA-256", "SHA-1"
+     * @return 哈希值的十六进制字符串
+     */
+    private static String calculateHash(InputStream inputStream, String algorithm) {
+        try {
+            MessageDigest digest = MessageDigest.getInstance(algorithm);
+            byte[] buffer = new byte[8192];
+            int bytesRead;
+            while ((bytesRead = inputStream.read(buffer)) != -1) {
+                digest.update(buffer, 0, bytesRead);
+            }
+            byte[] hashBytes = digest.digest();
+            StringBuilder hexString = new StringBuilder();
+            for (byte b : hashBytes) {
+                String hex = Integer.toHexString(0xff & b);
+                if (hex.length() == 1) {
+                    hexString.append('0');
+                }
+                hexString.append(hex);
+            }
+            return hexString.toString();
+        } catch (NoSuchAlgorithmException e) {
+            throw new UnsupportedOperationException(algorithm + " algorithm not available", e);
+        } catch (IOException e) {
+            throw new RuntimeException("Error reading from input stream", e);
+        }
+    }
+
+    /**
+     * 计算输入流的 SHA256 哈希值
+     *
+     * @param inputStream 输入流
+     * @return SHA256 哈希值的十六进制字符串
+     */
+    public static String sha256(InputStream inputStream) {
+        return calculateHash(inputStream, "SHA-256");
+    }
+
+    /**
+     * 计算输入流的 SHA1 哈希值
+     *
+     * @param inputStream 输入流
+     * @return SHA1 哈希值的十六进制字符串
+     */
+    public static String sha1(InputStream inputStream) {
+        return calculateHash(inputStream, "SHA-1");
+    }
+
+    /**
+     * 计算输入流的 SM3 哈希值
+     *
+     * @param inputStream 输入流
+     * @return SM3 哈希值的十六进制字符串
+     */
+    public static String sm3(InputStream inputStream) {
+        // 确保Bouncy Castle Provider已注册
+        if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) {
+            Security.addProvider(new BouncyCastleProvider());
+        }
+
+        try {
+            SM3Digest digest = new SM3Digest();
+            byte[] buffer = new byte[8192];
+            int bytesRead;
+            while ((bytesRead = inputStream.read(buffer)) != -1) {
+                digest.update(buffer, 0, bytesRead);
+            }
+            byte[] hashBytes = new byte[digest.getDigestSize()];
+            digest.doFinal(hashBytes, 0);
+
+            StringBuilder hexString = new StringBuilder();
+            for (byte b : hashBytes) {
+                String hex = Integer.toHexString(0xff & b);
+                if (hex.length() == 1) {
+                    hexString.append('0');
+                }
+                hexString.append(hex);
+            }
+            return hexString.toString();
+        } catch (IOException e) {
+            throw new RuntimeException("Error reading from input stream", e);
+        }
+    }
+
+    /**
+     * 对参数进行 URL 编码
+     *
+     * @param content 参数内容
+     * @return 编码后的内容
+     */
+    public static String urlEncode(String content) {
+        try {
+            return URLEncoder.encode(content, StandardCharsets.UTF_8.name());
+        } catch (UnsupportedEncodingException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * 对参数Map进行 URL 编码,生成 QueryString
+     *
+     * @param params Query参数Map
+     * @return QueryString
+     */
+    public static String urlEncode(Map<String, Object> params) {
+        if (params == null || params.isEmpty()) {
+            return "";
+        }
+
+        StringBuilder result = new StringBuilder();
+        for (Entry<String, Object> entry : params.entrySet()) {
+            if (entry.getValue() == null) {
+                continue;
+            }
+
+            String key = entry.getKey();
+            Object value = entry.getValue();
+            if (value instanceof List) {
+                List<?> list = (List<?>) entry.getValue();
+                for (Object temp : list) {
+                    appendParam(result, key, temp);
+                }
+            } else {
+                appendParam(result, key, value);
+            }
+        }
+        return result.toString();
+    }
+
+    /**
+     * 将键值对 放入返回结果
+     *
+     * @param result 返回的query string
+     * @param key 属性
+     * @param value 属性值
+     */
+    private static void appendParam(StringBuilder result, String key, Object value) {
+        if (result.length() > 0) {
+            result.append("&");
+        }
+
+        String valueString;
+        // 如果是基本类型、字符串或枚举,直接转换;如果是对象,序列化为JSON
+        if (value instanceof String || value instanceof Number ||
+                value instanceof Boolean || value instanceof Enum) {
+            valueString = value.toString();
+        } else {
+            valueString = toJson(value);
+        }
+
+        result.append(key)
+                .append("=")
+                .append(urlEncode(valueString));
+    }
+
+    /**
+     * 从应答中提取 Body
+     *
+     * @param response HTTP 请求应答对象
+     * @return 应答中的Body内容,Body为空时返回空字符串
+     */
+    public static String extractBody(Response response) {
+        if (response.body() == null) {
+            return "";
+        }
+
+        try {
+            BufferedSource source = response.body().source();
+            return source.readUtf8();
+        } catch (IOException e) {
+            throw new RuntimeException(String.format("An error occurred during reading response body. " +
+                    "Status: %d", response.code()), e);
+        }
+    }
+
+    /**
+     * 根据微信支付APIv3应答验签规则对应答签名进行验证,验证不通过时抛出异常
+     *
+     * @param wechatpayPublicKeyId 微信支付公钥ID
+     * @param wechatpayPublicKey   微信支付公钥对象
+     * @param headers              微信支付应答 Header 列表
+     * @param body                 微信支付应答 Body
+     */
+    public static void validateResponse(String wechatpayPublicKeyId, PublicKey wechatpayPublicKey,
+                                        Headers headers,
+                                        String body) {
+        String timestamp = headers.get("Wechatpay-Timestamp");
+        String requestId = headers.get("Request-ID");
+        try {
+            Instant responseTime = Instant.ofEpochSecond(Long.parseLong(timestamp));
+            // 拒绝过期请求
+            if (Duration.between(responseTime, Instant.now()).abs().toMinutes() >= 5) {
+                throw new IllegalArgumentException(
+                        String.format("Validate response failed, timestamp[%s] is expired, request-id[%s]",
+                                timestamp, requestId));
+            }
+        } catch (DateTimeException | NumberFormatException e) {
+            throw new IllegalArgumentException(
+                    String.format("Validate response failed, timestamp[%s] is invalid, request-id[%s]",
+                            timestamp, requestId));
+        }
+        String serialNumber = headers.get("Wechatpay-Serial");
+        if (!Objects.equals(serialNumber, wechatpayPublicKeyId)) {
+            throw new IllegalArgumentException(
+                    String.format("Validate response failed, Invalid Wechatpay-Serial, Local: %s, Remote: " +
+                            "%s", wechatpayPublicKeyId, serialNumber));
+        }
+
+        String signature = headers.get("Wechatpay-Signature");
+        String message = String.format("%s\n%s\n%s\n", timestamp, headers.get("Wechatpay-Nonce"),
+                body == null ? "" : body);
+
+        boolean success = verify(message, signature, "SHA256withRSA", wechatpayPublicKey);
+        if (!success) {
+            throw new IllegalArgumentException(
+                    String.format("Validate response failed,the WechatPay signature is incorrect.%n"
+                                    + "Request-ID[%s]\tresponseHeader[%s]\tresponseBody[%.1024s]",
+                            headers.get("Request-ID"), headers, body));
+        }
+    }
+
+    /**
+     * 根据微信支付APIv3通知验签规则对通知签名进行验证,验证不通过时抛出异常
+     * @param wechatpayPublicKeyId 微信支付公钥ID
+     * @param wechatpayPublicKey 微信支付公钥对象
+     * @param headers 微信支付通知 Header 列表
+     * @param body 微信支付通知 Body
+     */
+    public static void validateNotification(String wechatpayPublicKeyId,
+                                            PublicKey wechatpayPublicKey, Headers headers,
+                                            String body) {
+        String timestamp = headers.get("Wechatpay-Timestamp");
+        try {
+            Instant responseTime = Instant.ofEpochSecond(Long.parseLong(timestamp));
+            // 拒绝过期请求
+            if (Duration.between(responseTime, Instant.now()).abs().toMinutes() >= 5) {
+                throw new IllegalArgumentException(
+                        String.format("Validate notification failed, timestamp[%s] is expired", timestamp));
+            }
+        } catch (DateTimeException | NumberFormatException e) {
+            throw new IllegalArgumentException(
+                    String.format("Validate notification failed, timestamp[%s] is invalid", timestamp));
+        }
+        String serialNumber = headers.get("Wechatpay-Serial");
+        if (!Objects.equals(serialNumber, wechatpayPublicKeyId)) {
+            throw new IllegalArgumentException(
+                    String.format("Validate notification failed, Invalid Wechatpay-Serial, Local: %s, " +
+                                    "Remote: %s",
+                            wechatpayPublicKeyId,
+                            serialNumber));
+        }
+
+        String signature = headers.get("Wechatpay-Signature");
+        String message = String.format("%s\n%s\n%s\n", timestamp, headers.get("Wechatpay-Nonce"),
+                body == null ? "" : body);
+
+        boolean success = verify(message, signature, "SHA256withRSA", wechatpayPublicKey);
+        if (!success) {
+            throw new IllegalArgumentException(
+                    String.format("Validate notification failed, WechatPay signature is incorrect.\n"
+                                    + "responseHeader[%s]\tresponseBody[%.1024s]",
+                            headers, body));
+        }
+    }
+
+    /**
+     * 对微信支付通知进行签名验证、解析,同时将业务数据解密。验签名失败、解析失败、解密失败时抛出异常
+     * @param apiv3Key 商户的 APIv3 Key
+     * @param wechatpayPublicKeyId 微信支付公钥ID
+     * @param wechatpayPublicKey   微信支付公钥对象
+     * @param headers              微信支付请求 Header 列表
+     * @param body                 微信支付请求 Body
+     * @return 解析后的通知内容,解密后的业务数据可以使用 Notification.getPlaintext() 访问
+     */
+    public static Notification parseNotification(String apiv3Key, String wechatpayPublicKeyId,
+                                                 PublicKey wechatpayPublicKey, Headers headers,
+                                                 String body) {
+        validateNotification(wechatpayPublicKeyId, wechatpayPublicKey, headers, body);
+        Notification notification = gson.fromJson(body, Notification.class);
+        notification.decrypt(apiv3Key);
+        return notification;
+    }
+
+    /**
+     * 微信支付API错误异常,发送HTTP请求成功,但返回状态码不是 2XX 时抛出本异常
+     */
+    public static class ApiException extends RuntimeException {
+        private static final long serialVersionUID = 2261086748874802175L;
+
+        private final int statusCode;
+        private final String body;
+        private final Headers headers;
+        private final String errorCode;
+        private final String errorMessage;
+
+        public ApiException(int statusCode, String body, Headers headers) {
+            super(String.format("微信支付API访问失败,StatusCode: [%s], Body: [%s], Headers: [%s]", statusCode,
+                    body, headers));
+            this.statusCode = statusCode;
+            this.body = body;
+            this.headers = headers;
+
+            if (body != null && !body.isEmpty()) {
+                JsonElement code;
+                JsonElement message;
+
+                try {
+                    JsonObject jsonObject = gson.fromJson(body, JsonObject.class);
+                    code = jsonObject.get("code");
+                    message = jsonObject.get("message");
+                } catch (JsonSyntaxException ignored) {
+                    code = null;
+                    message = null;
+                }
+                this.errorCode = code == null ? null : code.getAsString();
+                this.errorMessage = message == null ? null : message.getAsString();
+            } else {
+                this.errorCode = null;
+                this.errorMessage = null;
+            }
+        }
+
+        /**
+         * 获取 HTTP 应答状态码
+         */
+        public int getStatusCode() {
+            return statusCode;
+        }
+
+        /**
+         * 获取 HTTP 应答包体内容
+         */
+        public String getBody() {
+            return body;
+        }
+
+        /**
+         * 获取 HTTP 应答 Header
+         */
+        public Headers getHeaders() {
+            return headers;
+        }
+
+        /**
+         * 获取 错误码 (错误应答中的 code 字段)
+         */
+        public String getErrorCode() {
+            return errorCode;
+        }
+
+        /**
+         * 获取 错误消息 (错误应答中的 message 字段)
+         */
+        public String getErrorMessage() {
+            return errorMessage;
+        }
+    }
+
+    public static class Notification {
+        @SerializedName("id")
+        private String id;
+        @SerializedName("create_time")
+        private String createTime;
+        @SerializedName("event_type")
+        private String eventType;
+        @SerializedName("resource_type")
+        private String resourceType;
+        @SerializedName("summary")
+        private String summary;
+        @SerializedName("resource")
+        private Resource resource;
+        private String plaintext;
+
+        public String getId() {
+            return id;
+        }
+
+        public String getCreateTime() {
+            return createTime;
+        }
+
+        public String getEventType() {
+            return eventType;
+        }
+
+        public String getResourceType() {
+            return resourceType;
+        }
+
+        public String getSummary() {
+            return summary;
+        }
+
+        public Resource getResource() {
+            return resource;
+        }
+
+        /**
+         * 获取解密后的业务数据(JSON字符串,需要自行解析)
+         */
+        public String getPlaintext() {
+            return plaintext;
+        }
+
+        private void validate() {
+            if (resource == null) {
+                throw new IllegalArgumentException("Missing required field `resource` in notification");
+            }
+            resource.validate();
+        }
+
+        /**
+         * 使用 APIv3Key 对通知中的业务数据解密,解密结果可以通过 getPlainText 访问。
+         * 外部拿到的 Notification 一定是解密过的,因此本方法没有设置为 public
+         * @param apiv3Key 商户APIv3 Key
+         */
+        private void decrypt(String apiv3Key) {
+            validate();
+
+            plaintext = aesAeadDecrypt(
+                    apiv3Key.getBytes(StandardCharsets.UTF_8),
+                    resource.associatedData.getBytes(StandardCharsets.UTF_8),
+                    resource.nonce.getBytes(StandardCharsets.UTF_8),
+                    Base64.getDecoder().decode(resource.ciphertext)
+            );
+        }
+
+        public static class Resource {
+            @SerializedName("algorithm")
+            private String algorithm;
+
+            @SerializedName("ciphertext")
+            private String ciphertext;
+
+            @SerializedName("associated_data")
+            private String associatedData;
+
+            @SerializedName("nonce")
+            private String nonce;
+
+            @SerializedName("original_type")
+            private String originalType;
+
+            public String getAlgorithm() {
+                return algorithm;
+            }
+
+            public String getCiphertext() {
+                return ciphertext;
+            }
+
+            public String getAssociatedData() {
+                return associatedData;
+            }
+
+            public String getNonce() {
+                return nonce;
+            }
+
+            public String getOriginalType() {
+                return originalType;
+            }
+
+            private void validate() {
+                if (algorithm == null || algorithm.isEmpty()) {
+                    throw new IllegalArgumentException("Missing required field `algorithm` in Notification" +
+                            ".Resource");
+                }
+                if (!Objects.equals(algorithm, "AEAD_AES_256_GCM")) {
+                    throw new IllegalArgumentException(String.format("Unsupported `algorithm`[%s] in " +
+                            "Notification.Resource", algorithm));
+                }
+
+                if (ciphertext == null || ciphertext.isEmpty()) {
+                    throw new IllegalArgumentException("Missing required field `ciphertext` in Notification" +
+                            ".Resource");
+                }
+
+                if (associatedData == null || associatedData.isEmpty()) {
+                    throw new IllegalArgumentException("Missing required field `associatedData` in " +
+                            "Notification.Resource");
+                }
+
+                if (nonce == null || nonce.isEmpty()) {
+                    throw new IllegalArgumentException("Missing required field `nonce` in Notification" +
+                            ".Resource");
+                }
+
+                if (originalType == null || originalType.isEmpty()) {
+                    throw new IllegalArgumentException("Missing required field `originalType` in " +
+                            "Notification.Resource");
+                }
+            }
+        }
+    }
+    /**
+     * 根据文件名获取对应的Content-Type
+     * @param fileName 文件名
+     * @return Content-Type字符串
+     */
+    public static String getContentTypeByFileName(String fileName) {
+        if (fileName == null || fileName.isEmpty()) {
+            return "application/octet-stream";
+        }
+
+        // 获取文件扩展名
+        String extension = "";
+        int lastDotIndex = fileName.lastIndexOf('.');
+        if (lastDotIndex > 0 && lastDotIndex < fileName.length() - 1) {
+            extension = fileName.substring(lastDotIndex + 1).toLowerCase();
+        }
+
+        // 常见文件类型映射
+        Map<String, String> contentTypeMap = new HashMap<>();
+        // 图片类型
+        contentTypeMap.put("png", "image/png");
+        contentTypeMap.put("jpg", "image/jpeg");
+        contentTypeMap.put("jpeg", "image/jpeg");
+        contentTypeMap.put("gif", "image/gif");
+        contentTypeMap.put("bmp", "image/bmp");
+        contentTypeMap.put("webp", "image/webp");
+        contentTypeMap.put("svg", "image/svg+xml");
+        contentTypeMap.put("ico", "image/x-icon");
+
+        // 文档类型
+        contentTypeMap.put("pdf", "application/pdf");
+        contentTypeMap.put("doc", "application/msword");
+        contentTypeMap.put("docx", "application/vnd.openxmlformats-officedocument.wordprocessingml.document");
+        contentTypeMap.put("xls", "application/vnd.ms-excel");
+        contentTypeMap.put("xlsx", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
+        contentTypeMap.put("ppt", "application/vnd.ms-powerpoint");
+        contentTypeMap.put("pptx", "application/vnd.openxmlformats-officedocument.presentationml.presentation");
+
+        // 文本类型
+        contentTypeMap.put("txt", "text/plain");
+        contentTypeMap.put("html", "text/html");
+        contentTypeMap.put("css", "text/css");
+        contentTypeMap.put("js", "application/javascript");
+        contentTypeMap.put("json", "application/json");
+        contentTypeMap.put("xml", "application/xml");
+        contentTypeMap.put("csv", "text/csv");
+
+        // 音视频类型
+        contentTypeMap.put("mp3", "audio/mpeg");
+        contentTypeMap.put("wav", "audio/wav");
+        contentTypeMap.put("mp4", "video/mp4");
+        contentTypeMap.put("avi", "video/x-msvideo");
+        contentTypeMap.put("mov", "video/quicktime");
+
+        // 压缩文件类型
+        contentTypeMap.put("zip", "application/zip");
+        contentTypeMap.put("rar", "application/x-rar-compressed");
+        contentTypeMap.put("7z", "application/x-7z-compressed");
+
+
+        return contentTypeMap.getOrDefault(extension, "application/octet-stream");
+    }
+}

+ 26 - 0
alien-util/src/main/java/shop/alien/util/common/constant/PaymentEnum.java

@@ -0,0 +1,26 @@
+package shop.alien.util.common.constant;
+
+/**
+ * 支付枚举
+ *
+ * @author lyx
+ * @date 2025/11/18
+ */
+public enum PaymentEnum {
+    /** 支付宝 */
+    ALIPAY("alipay"),
+    /** 微信支付 */
+    WECHAT_PAY("wechatPay"),
+    /** 银联支付 */
+    UNION_PAY("unionPay");
+
+    private final String type;
+
+    PaymentEnum(String type) {
+        this.type = type;
+    }
+
+    public String getType() {
+        return type;
+    }
+}