Gqingci 1 vecka sedan
förälder
incheckning
00fc2ad294
35 ändrade filer med 1145 tillägg och 2 borttagningar
  1. 2 0
      ruoyi-admin/src/main/resources/application.yml
  2. 5 0
      ruoyi-modules/ruoyi-main/pom.xml
  3. 11 0
      ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/controller/MainExamEvaluationController.java
  4. 10 0
      ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/controller/MainPostApplyController.java
  5. 61 0
      ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/controller/PortalAccountController.java
  6. 27 0
      ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/controller/PortalCheckController.java
  7. 85 0
      ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/controller/PortalPaymentController.java
  8. 6 0
      ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/domain/MainBackOrder.java
  9. 1 0
      ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/domain/MainPostApply.java
  10. 43 0
      ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/domain/Payment.java
  11. 46 0
      ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/domain/PaymentConfig.java
  12. 1 0
      ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/domain/bo/MainPostApplyBo.java
  13. 69 0
      ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/domain/bo/PortalCheckOrderCreateBo.java
  14. 18 0
      ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/domain/vo/MainBackOrderCandidateVo.java
  15. 5 0
      ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/domain/vo/MainBackOrderVo.java
  16. 3 0
      ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/domain/vo/MainPostApplyVo.java
  17. 29 0
      ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/domain/vo/PaymentVo.java
  18. 7 0
      ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/mapper/PaymentConfigMapper.java
  19. 8 0
      ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/mapper/PaymentMapper.java
  20. 11 0
      ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/service/IMainBackOrderService.java
  21. 5 0
      ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/service/IMainExamEvaluationService.java
  22. 4 0
      ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/service/IMainPostApplyService.java
  23. 10 0
      ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/service/IPaymentConfigService.java
  24. 16 0
      ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/service/IPaymentService.java
  25. 331 0
      ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/service/impl/MainBackOrderServiceImpl.java
  26. 12 0
      ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/service/impl/MainExamEvaluationServiceImpl.java
  27. 16 0
      ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/service/impl/MainPostApplyServiceImpl.java
  28. 32 0
      ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/service/impl/PaymentConfigServiceImpl.java
  29. 241 0
      ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/service/impl/PaymentServiceImpl.java
  30. 8 0
      ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysIndustryController.java
  31. 8 0
      ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysIndustrySkillController.java
  32. 1 0
      ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysIndustryService.java
  33. 1 0
      ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysIndustrySkillService.java
  34. 6 0
      ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysIndustryServiceImpl.java
  35. 6 2
      ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysIndustrySkillServiceImpl.java

+ 2 - 0
ruoyi-admin/src/main/resources/application.yml

@@ -133,6 +133,8 @@ tenant:
     - sys_role_menu
     - sys_user_post
     - sys_user_role
+    - sys_tag
+    - sys_industry
     - sys_client
     - sys_oss_config
     - flow_spel

+ 5 - 0
ruoyi-modules/ruoyi-main/pom.xml

@@ -17,6 +17,11 @@
         <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
     </properties>
     <dependencies>
+        <dependency>
+            <groupId>com.alipay.sdk</groupId>
+            <artifactId>alipay-sdk-java</artifactId>
+            <version>4.38.192.ALL</version>
+        </dependency>
         <dependency>
             <groupId>org.projectlombok</groupId>
             <artifactId>lombok</artifactId>

+ 11 - 0
ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/controller/MainExamEvaluationController.java

@@ -93,6 +93,17 @@ public class MainExamEvaluationController extends BaseController {
         return toAjax(examEvaluationService.updateStatus(ids, status));
     }
 
+    /**
+     * 单个更新状态
+     */
+    @SaCheckPermission("main:examEvaluation:edit")
+    @Log(title = "测评管理", businessType = BusinessType.UPDATE)
+    @PutMapping("/status/{id}/{status}")
+    public R<Void> updateSingleStatus(@NotNull(message = "主键不能为空") @PathVariable Long id,
+                                      @PathVariable String status) {
+        return toAjax(examEvaluationService.updateStatus(id, status));
+    }
+
     /**
      * 同步第三方数据
      */

+ 10 - 0
ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/controller/MainPostApplyController.java

@@ -41,6 +41,16 @@ public class MainPostApplyController extends BaseController {
         return toAjax(mainPostApplyService.updateByBo(bo));
     }
 
+    @PostMapping("/publish/{id}")
+    public R<Void> publish(@NotNull(message = "主键不能为空") @PathVariable Long id) {
+        return toAjax(mainPostApplyService.publishById(id));
+    }
+
+    @PostMapping("/unpublish/{id}")
+    public R<Void> unpublish(@NotNull(message = "主键不能为空") @PathVariable Long id) {
+        return toAjax(mainPostApplyService.unpublishById(id));
+    }
+
     @DeleteMapping("/{ids}")
     public R<Void> remove(@NotEmpty(message = "主键不能为空") @PathVariable Long[] ids) {
         return toAjax(mainPostApplyService.deleteWithValidByIds(Arrays.asList(ids), true));

+ 61 - 0
ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/controller/PortalAccountController.java

@@ -0,0 +1,61 @@
+package org.dromara.main.controller;
+
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.dromara.common.core.domain.R;
+import org.dromara.common.satoken.utils.LoginHelper;
+import org.dromara.common.web.core.BaseController;
+import org.dromara.system.domain.vo.SysTenantVo;
+import org.dromara.system.service.ISysTenantService;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.math.BigDecimal;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 门户企业账户信息接口
+ *
+ * 当前新版项目的企业主体挂在 sys_tenant 上,但钱包余额相关字段/表尚未迁移,
+ * 因此这里先返回当前登录企业基础信息,并对余额字段做零值兜底,避免前端 404。
+ */
+@Slf4j
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/portal/account")
+public class PortalAccountController extends BaseController {
+
+    private final ISysTenantService tenantService;
+
+    /**
+     * 获取当前企业钱包余额信息
+     */
+    @GetMapping("/balance")
+    public R<Map<String, Object>> getAccountBalance() {
+        String tenantId = LoginHelper.getTenantId();
+        if (tenantId == null || tenantId.isBlank()) {
+            return R.fail("未登录或token已失效");
+        }
+
+        SysTenantVo company = tenantService.queryByTenantId(tenantId);
+        if (company == null) {
+            return R.fail("企业信息不存在");
+        }
+
+        Map<String, Object> result = new HashMap<>(5);
+        result.put("companyId", company.getId());
+        result.put("companyName", company.getCompanyName());
+
+        // TODO: 钱包体系迁移后,改为读取真实余额字段/账户表。
+        result.put("availableBalance", BigDecimal.ZERO);
+        result.put("inUseBalance", BigDecimal.ZERO);
+        result.put("withdrawingBalance", BigDecimal.ZERO);
+
+        log.warn("portal/account/balance 使用零值兜底返回,tenantId={},待接入真实钱包数据", tenantId);
+        return R.ok(result);
+    }
+}

+ 27 - 0
ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/controller/PortalCheckController.java

@@ -9,6 +9,7 @@ import org.dromara.common.satoken.utils.LoginHelper;
 import org.dromara.common.web.core.BaseController;
 import org.dromara.main.domain.bo.MainBackCategoryBo;
 import org.dromara.main.domain.bo.MainBackClauseBo;
+import org.dromara.main.domain.bo.PortalCheckOrderCreateBo;
 import org.dromara.main.domain.vo.MainBackCategoryVo;
 import org.dromara.main.domain.vo.MainBackClauseVo;
 import org.dromara.main.domain.vo.MainBackOrderVo;
@@ -17,8 +18,12 @@ import org.dromara.main.service.IMainBackClauseService;
 import org.dromara.main.service.IMainBackOrderService;
 import org.dromara.system.domain.vo.SysDictDataVo;
 import org.dromara.system.service.ISysDictTypeService;
+import org.springframework.validation.annotation.Validated;
 import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestParam;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RestController;
 
@@ -134,6 +139,28 @@ public class PortalCheckController extends BaseController {
         return R.ok(mainBackOrderService.queryPortalListByTenantId(tenantId));
     }
 
+    @GetMapping("/order/detail")
+    public R<MainBackOrderVo> getOrderDetail(@RequestParam Long orderId) {
+        String tenantId = LoginHelper.getTenantId();
+        if (tenantId == null || tenantId.isBlank()) {
+            return R.fail("未登录或token已失效");
+        }
+        MainBackOrderVo detail = mainBackOrderService.queryPortalDetail(orderId, tenantId);
+        if (detail == null) {
+            return R.fail("订单不存在");
+        }
+        return R.ok(detail);
+    }
+
+    @PostMapping("/order/create")
+    public R<MainBackOrderVo> createOrder(@Validated @RequestBody PortalCheckOrderCreateBo bo) {
+        String tenantId = LoginHelper.getTenantId();
+        if (tenantId == null || tenantId.isBlank()) {
+            return R.fail("未登录或token已失效");
+        }
+        return R.ok(mainBackOrderService.createPortalOrder(tenantId, bo));
+    }
+
     private PortalCheckCategoryVo toPortalCategoryVo(MainBackCategoryVo category) {
         PortalCheckCategoryVo vo = new PortalCheckCategoryVo();
         vo.setId(category.getId());

+ 85 - 0
ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/controller/PortalPaymentController.java

@@ -0,0 +1,85 @@
+package org.dromara.main.controller;
+
+import cn.dev33.satoken.annotation.SaIgnore;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.validation.constraints.NotNull;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.dromara.common.core.domain.R;
+import org.dromara.common.satoken.utils.LoginHelper;
+import org.dromara.common.web.core.BaseController;
+import org.dromara.main.domain.vo.PaymentVo;
+import org.dromara.main.service.IPaymentService;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.HashMap;
+import java.util.Map;
+
+@Slf4j
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/portal/payment")
+public class PortalPaymentController extends BaseController {
+
+    private final IPaymentService paymentService;
+
+    @PostMapping("/alipay/qrcode")
+    public R<PaymentVo> createAlipayQrCode(@RequestBody Map<String, Object> request) {
+        Object orderIdValue = request.get("orderId");
+        if (orderIdValue == null) {
+            return R.fail("订单ID不能为空");
+        }
+        String tenantId = LoginHelper.getTenantId();
+        if (tenantId == null || tenantId.isBlank()) {
+            return R.fail("未登录或token已失效");
+        }
+        Long orderId = Long.valueOf(orderIdValue.toString());
+        return R.ok(paymentService.createAlipayQrCode(orderId, tenantId));
+    }
+
+    @SaIgnore
+    @PostMapping("/alipay/notify")
+    public String alipayNotify(HttpServletRequest request) {
+        Map<String, String> params = new HashMap<>();
+        Map<String, String[]> requestParams = request.getParameterMap();
+        for (String name : requestParams.keySet()) {
+            String[] values = requestParams.get(name);
+            StringBuilder valueStr = new StringBuilder();
+            for (int i = 0; i < values.length; i++) {
+                valueStr.append(values[i]);
+                if (i != values.length - 1) {
+                    valueStr.append(",");
+                }
+            }
+            params.put(name, valueStr.toString());
+        }
+        return paymentService.handleAlipayNotify(params);
+    }
+
+    @GetMapping("/status")
+    public R<PaymentVo> queryPaymentStatus(@NotNull(message = "订单ID不能为空") @RequestParam Long orderId) {
+        PaymentVo vo = paymentService.queryPaymentStatus(orderId);
+        return vo == null ? R.fail("支付记录不存在") : R.ok(vo);
+    }
+
+    @SaIgnore
+    @GetMapping("/query-by-payment-no")
+    public R<Map<String, Object>> queryByPaymentNo(@NotNull(message = "支付单号不能为空") @RequestParam String paymentNo) {
+        PaymentVo payment = paymentService.queryByPaymentNo(paymentNo);
+        if (payment == null) {
+            return R.fail("支付记录不存在");
+        }
+        Map<String, Object> result = new HashMap<>();
+        result.put("orderId", payment.getOrderId());
+        result.put("paymentStatus", payment.getPaymentStatus());
+        result.put("paymentAmount", payment.getPaymentAmount());
+        return R.ok(result);
+    }
+}

+ 6 - 0
ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/domain/MainBackOrder.java

@@ -32,6 +32,12 @@ public class MainBackOrder extends BaseEntity {
     /** 订单金额 */
     private BigDecimal totalAmount;
 
+    /** 自定义条款ID列表(JSON数组) */
+    private String clauseIds;
+
+    /** 自定义条款组合唯一标识 */
+    private String clauseGroupKey;
+
     /** 订单状态(0待处理 1进行中 2已完成 3已退款) */
     private String status;
 

+ 1 - 0
ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/domain/MainPostApply.java

@@ -54,6 +54,7 @@ public class MainPostApply extends BaseEntity {
     private Long auditId;
     private Long postId;
     private String rejectReason;
+    private String status;
 
     @TableLogic
     private String delFlag;

+ 43 - 0
ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/domain/Payment.java

@@ -0,0 +1,43 @@
+package org.dromara.main.domain;
+
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.dromara.common.mybatis.core.domain.BaseEntity;
+
+import java.math.BigDecimal;
+import java.util.Date;
+
+@Data
+@EqualsAndHashCode(callSuper = true)
+@TableName("main_payment")
+public class Payment extends BaseEntity {
+
+    @TableId(value = "id")
+    private Long id;
+
+    private String paymentNo;
+
+    private Long orderId;
+
+    private String orderNo;
+
+    private Integer paymentType;
+
+    private Integer paymentMethod;
+
+    private BigDecimal paymentAmount;
+
+    private String tradeNo;
+
+    private String qrCodeUrl;
+
+    private String tradeStatus;
+
+    private Integer paymentStatus;
+
+    private Date payTime;
+
+    private String remark;
+}

+ 46 - 0
ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/domain/PaymentConfig.java

@@ -0,0 +1,46 @@
+package org.dromara.main.domain;
+
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.dromara.common.mybatis.core.domain.BaseEntity;
+
+@Data
+@EqualsAndHashCode(callSuper = true)
+@TableName("main_payment_config")
+public class PaymentConfig extends BaseEntity {
+
+    @TableId(value = "id")
+    private Long id;
+
+    private String configName;
+
+    private Integer configType;
+
+    private Integer paymentType;
+
+    private String gatewayUrl;
+
+    private String appId;
+
+    private String privateKey;
+
+    private String publicKey;
+
+    private String signType;
+
+    private String format;
+
+    private String charset;
+
+    private String payNotifyUrl;
+
+    private String transferNotifyUrl;
+
+    private String returnUrl;
+
+    private Integer isEnabled;
+
+    private String remark;
+}

+ 1 - 0
ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/domain/bo/MainPostApplyBo.java

@@ -47,4 +47,5 @@ public class MainPostApplyBo extends BaseEntity {
     private BigDecimal gradeB;
     private BigDecimal gradeC;
     private Integer applyStatus;
+    private String status;
 }

+ 69 - 0
ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/domain/bo/PortalCheckOrderCreateBo.java

@@ -0,0 +1,69 @@
+package org.dromara.main.domain.bo;
+
+import jakarta.validation.Valid;
+import jakarta.validation.constraints.DecimalMin;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotEmpty;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.util.List;
+
+/**
+ * 门户背调下单请求
+ */
+@Data
+public class PortalCheckOrderCreateBo {
+
+    /**
+     * 前端会传企业ID,实际下单仍以当前登录租户为准
+     */
+    private Long companyId;
+
+    /**
+     * 1-分类套餐 2-自定义条款
+     */
+    @NotNull(message = "订单类型不能为空")
+    private Integer orderType;
+
+    /**
+     * 套餐订单必填
+     */
+    private Long categoryId;
+
+    /**
+     * 自定义条款订单必填,JSON数组字符串
+     */
+    private String clauseIds;
+
+    private String clauseGroupKey;
+
+    private Integer candidateCount;
+
+    @DecimalMin(value = "0.00", inclusive = false, message = "单价必须大于0")
+    private BigDecimal unitPrice;
+
+    @DecimalMin(value = "0.00", inclusive = false, message = "总金额必须大于0")
+    private BigDecimal totalAmount;
+
+    @Valid
+    @NotEmpty(message = "候选人列表不能为空")
+    private List<CandidateInfo> candidates;
+
+    @Data
+    public static class CandidateInfo {
+
+        @NotBlank(message = "候选人姓名不能为空")
+        private String candidateName;
+
+        @NotBlank(message = "候选人手机号不能为空")
+        private String candidatePhone;
+
+        @NotBlank(message = "证件类型不能为空")
+        private String candidateIdType;
+
+        @NotBlank(message = "证件号码不能为空")
+        private String candidateIdNumber;
+    }
+}

+ 18 - 0
ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/domain/vo/MainBackOrderCandidateVo.java

@@ -0,0 +1,18 @@
+package org.dromara.main.domain.vo;
+
+import lombok.Data;
+
+import java.io.Serializable;
+
+@Data
+public class MainBackOrderCandidateVo implements Serializable {
+
+    private Long id;
+    private Long orderId;
+    private String studentName;
+    private String candidatePhone;
+    private String candidateIdType;
+    private String candidateIdNumber;
+    private Integer status;
+    private String reportUrl;
+}

+ 5 - 0
ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/domain/vo/MainBackOrderVo.java

@@ -8,6 +8,7 @@ import org.dromara.main.domain.MainBackOrder;
 import java.io.Serializable;
 import java.math.BigDecimal;
 import java.time.LocalDateTime;
+import java.util.List;
 
 @Data
 @AutoMapper(target = MainBackOrder.class)
@@ -33,4 +34,8 @@ public class MainBackOrderVo implements Serializable {
 
     /** 候选人人数 (需在 Service 中统计或 SQL 关联查询) */
     private Long candidateCount;
+
+    private PaymentVo payment;
+    private List<MainBackOrderCandidateVo> candidates;
+    private List<MainBackClauseVo> clauses;
 }

+ 3 - 0
ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/domain/vo/MainPostApplyVo.java

@@ -122,6 +122,9 @@ public class MainPostApplyVo implements Serializable {
     @ExcelProperty(value = "驳回原因")
     private String rejectReason;
 
+    @ExcelProperty(value = "发布状态")
+    private String status;
+
     @ExcelProperty(value = "创建时间")
     private Date createTime;
 }

+ 29 - 0
ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/domain/vo/PaymentVo.java

@@ -0,0 +1,29 @@
+package org.dromara.main.domain.vo;
+
+import io.github.linpeilie.annotations.AutoMapper;
+import lombok.Data;
+import org.dromara.main.domain.Payment;
+
+import java.io.Serializable;
+import java.math.BigDecimal;
+import java.util.Date;
+
+@Data
+@AutoMapper(target = Payment.class)
+public class PaymentVo implements Serializable {
+
+    private Long id;
+    private String paymentNo;
+    private Long orderId;
+    private String orderNo;
+    private Integer paymentType;
+    private Integer paymentMethod;
+    private BigDecimal paymentAmount;
+    private String tradeNo;
+    private String tradeStatus;
+    private Integer paymentStatus;
+    private Date payTime;
+    private String remark;
+    private Date createTime;
+    private String qrCodeUrl;
+}

+ 7 - 0
ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/mapper/PaymentConfigMapper.java

@@ -0,0 +1,7 @@
+package org.dromara.main.mapper;
+
+import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
+import org.dromara.main.domain.PaymentConfig;
+
+public interface PaymentConfigMapper extends BaseMapperPlus<PaymentConfig, PaymentConfig> {
+}

+ 8 - 0
ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/mapper/PaymentMapper.java

@@ -0,0 +1,8 @@
+package org.dromara.main.mapper;
+
+import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
+import org.dromara.main.domain.Payment;
+import org.dromara.main.domain.vo.PaymentVo;
+
+public interface PaymentMapper extends BaseMapperPlus<Payment, PaymentVo> {
+}

+ 11 - 0
ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/service/IMainBackOrderService.java

@@ -3,6 +3,7 @@ package org.dromara.main.service;
 import org.dromara.common.mybatis.core.page.PageQuery;
 import org.dromara.common.mybatis.core.page.TableDataInfo;
 import org.dromara.main.domain.bo.MainBackOrderBo;
+import org.dromara.main.domain.bo.PortalCheckOrderCreateBo;
 import org.dromara.main.domain.vo.MainBackOrderVo;
 
 import java.util.List;
@@ -21,4 +22,14 @@ public interface IMainBackOrderService {
      * 查询门户当前企业的背调订单列表
      */
     List<MainBackOrderVo> queryPortalListByTenantId(String tenantId);
+
+    /**
+     * 查询门户订单详情
+     */
+    MainBackOrderVo queryPortalDetail(Long orderId, String tenantId);
+
+    /**
+     * 门户创建背调订单
+     */
+    MainBackOrderVo createPortalOrder(String tenantId, PortalCheckOrderCreateBo bo);
 }

+ 5 - 0
ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/service/IMainExamEvaluationService.java

@@ -50,6 +50,11 @@ public interface IMainExamEvaluationService {
      */
     Boolean updateStatus(List<Long> ids, String status);
 
+    /**
+     * 单个更新状态
+     */
+    Boolean updateStatus(Long id, String status);
+
     /**
      * 同步第三方数据
      */

+ 4 - 0
ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/service/IMainPostApplyService.java

@@ -20,5 +20,9 @@ public interface IMainPostApplyService {
 
     Boolean updateByBo(MainPostApplyBo bo);
 
+    Boolean publishById(Long id);
+
+    Boolean unpublishById(Long id);
+
     Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
 }

+ 10 - 0
ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/service/IPaymentConfigService.java

@@ -0,0 +1,10 @@
+package org.dromara.main.service;
+
+import org.dromara.main.domain.PaymentConfig;
+
+public interface IPaymentConfigService {
+
+    PaymentConfig getEnabledAlipayConfig();
+
+    PaymentConfig getEnabledReceiveConfig();
+}

+ 16 - 0
ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/service/IPaymentService.java

@@ -0,0 +1,16 @@
+package org.dromara.main.service;
+
+import org.dromara.main.domain.vo.PaymentVo;
+
+import java.util.Map;
+
+public interface IPaymentService {
+
+    PaymentVo createAlipayQrCode(Long orderId, String tenantId);
+
+    String handleAlipayNotify(Map<String, String> params);
+
+    PaymentVo queryPaymentStatus(Long orderId);
+
+    PaymentVo queryByPaymentNo(String paymentNo);
+}

+ 331 - 0
ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/service/impl/MainBackOrderServiceImpl.java

@@ -1,30 +1,50 @@
 package org.dromara.main.service.impl;
 
 import cn.hutool.core.util.StrUtil;
+import cn.hutool.crypto.digest.DigestUtil;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import lombok.RequiredArgsConstructor;
+import org.dromara.common.core.exception.ServiceException;
 import org.dromara.common.core.utils.MapstructUtils;
 import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.json.utils.JsonUtils;
 import org.dromara.common.mybatis.core.page.PageQuery;
 import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.main.domain.MainBackCandidate;
+import org.dromara.main.domain.MainBackCategory;
+import org.dromara.main.domain.MainBackClause;
 import org.dromara.main.domain.MainBackOrder;
 import org.dromara.main.domain.MainBackRecord;
 import org.dromara.main.domain.MainOrder;
 import org.dromara.main.domain.bo.MainBackOrderBo;
+import org.dromara.main.domain.bo.PortalCheckOrderCreateBo;
+import org.dromara.main.domain.MainStudent;
+import org.dromara.main.domain.Payment;
+import org.dromara.main.domain.vo.MainBackClauseVo;
+import org.dromara.main.domain.vo.MainBackOrderCandidateVo;
 import org.dromara.main.domain.vo.MainBackOrderVo;
+import org.dromara.main.mapper.MainBackCandidateMapper;
+import org.dromara.main.mapper.MainBackCategoryMapper;
+import org.dromara.main.mapper.MainBackClauseMapper;
 import org.dromara.main.mapper.MainBackOrderMapper;
 import org.dromara.main.mapper.MainBackRecordMapper;
 import org.dromara.main.mapper.MainOrderMapper;
+import org.dromara.main.mapper.MainStudentMapper;
+import org.dromara.main.mapper.PaymentMapper;
 import org.dromara.main.service.IMainBackOrderService;
 import org.dromara.system.domain.bo.SysTenantBo;
 import org.dromara.system.domain.vo.SysTenantVo;
 import org.dromara.system.service.ISysTenantService;
 import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
 
 import java.math.BigDecimal;
 import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.Date;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
@@ -37,6 +57,11 @@ public class MainBackOrderServiceImpl implements IMainBackOrderService {
     private final MainBackOrderMapper baseMapper;
     private final MainBackRecordMapper recordMapper;
     private final MainOrderMapper mainOrderMapper;
+    private final MainBackCategoryMapper mainBackCategoryMapper;
+    private final MainBackClauseMapper mainBackClauseMapper;
+    private final MainBackCandidateMapper mainBackCandidateMapper;
+    private final MainStudentMapper mainStudentMapper;
+    private final PaymentMapper paymentMapper;
     private final ISysTenantService tenantService;
 
     @Override
@@ -124,6 +149,176 @@ public class MainBackOrderServiceImpl implements IMainBackOrderService {
         return result;
     }
 
+    @Override
+    public MainBackOrderVo queryPortalDetail(Long orderId, String tenantId) {
+        MainOrder mainOrder = mainOrderMapper.selectById(orderId);
+        if (mainOrder == null || !StringUtils.equals(mainOrder.getTenantId(), tenantId)) {
+            return null;
+        }
+        if (mainOrder.getBusinessId() == null) {
+            return null;
+        }
+
+        MainBackOrder backOrder = baseMapper.selectById(mainOrder.getBusinessId());
+        if (backOrder == null) {
+            return null;
+        }
+
+        MainBackOrderVo vo = MapstructUtils.convert(backOrder, MainBackOrderVo.class);
+        fillPortalFields(vo, mainOrder);
+        vo.setPayment(paymentMapper.selectVoOne(
+            Wrappers.<Payment>lambdaQuery()
+                .eq(Payment::getOrderId, orderId)
+                .orderByDesc(Payment::getCreateTime)
+                .last("limit 1"),
+            false
+        ));
+        vo.setCandidates(buildPortalCandidates(backOrder.getId()));
+        vo.setClauses(buildPortalClauses(backOrder));
+        return vo;
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public MainBackOrderVo createPortalOrder(String tenantId, PortalCheckOrderCreateBo bo) {
+        if (StringUtils.isBlank(tenantId)) {
+            throw new ServiceException("未登录或token已失效");
+        }
+        if (bo.getCandidates() == null || bo.getCandidates().isEmpty()) {
+            throw new ServiceException("候选人列表不能为空");
+        }
+
+        SysTenantVo tenant = tenantService.queryByTenantId(tenantId);
+        if (tenant == null) {
+            throw new ServiceException("企业信息不存在");
+        }
+
+        List<Long> clauseIds;
+        String clauseIdsJson;
+        String clauseGroupKey = null;
+        Long categoryId = null;
+        String categoryName;
+        BigDecimal unitPrice;
+
+        if (Integer.valueOf(1).equals(bo.getOrderType())) {
+            if (bo.getCategoryId() == null) {
+                throw new ServiceException("套餐ID不能为空");
+            }
+            MainBackCategory category = mainBackCategoryMapper.selectById(bo.getCategoryId());
+            if (category == null || !"1".equals(category.getStatus())) {
+                throw new ServiceException("背调套餐不存在或已停用");
+            }
+
+            List<MainBackClause> clauses = mainBackClauseMapper.selectList(
+                Wrappers.<MainBackClause>lambdaQuery()
+                    .eq(MainBackClause::getCategoryId, category.getId())
+                    .eq(MainBackClause::getStatus, "1")
+                    .orderByAsc(MainBackClause::getId)
+            );
+            if (clauses.isEmpty()) {
+                throw new ServiceException("该套餐下暂无可用条款");
+            }
+
+            categoryId = category.getId();
+            categoryName = category.getName();
+            unitPrice = defaultAmount(category.getPrice());
+            clauseIds = clauses.stream().map(MainBackClause::getId).toList();
+            clauseIdsJson = JsonUtils.toJsonString(clauseIds);
+        } else if (Integer.valueOf(2).equals(bo.getOrderType())) {
+            if (StringUtils.isBlank(bo.getClauseIds())) {
+                throw new ServiceException("自定义条款不能为空");
+            }
+            List<Long> parsedClauseIds = JsonUtils.parseArray(bo.getClauseIds(), Long.class);
+            if (parsedClauseIds == null || parsedClauseIds.isEmpty()) {
+                throw new ServiceException("自定义条款不能为空");
+            }
+
+            clauseIds = parsedClauseIds.stream().distinct().sorted().collect(Collectors.toList());
+            List<MainBackClause> clauses = mainBackClauseMapper.selectBatchIds(clauseIds).stream()
+                .filter(clause -> clause != null && "1".equals(clause.getStatus()))
+                .sorted(Comparator.comparing(MainBackClause::getId))
+                .toList();
+            if (clauses.size() != clauseIds.size()) {
+                throw new ServiceException("部分自定义条款不存在或已停用");
+            }
+
+            unitPrice = clauses.stream()
+                .map(MainBackClause::getPrice)
+                .filter(java.util.Objects::nonNull)
+                .reduce(BigDecimal.ZERO, BigDecimal::add);
+            categoryName = "自定义背调组合";
+            clauseIdsJson = JsonUtils.toJsonString(clauseIds);
+            clauseGroupKey = DigestUtil.md5Hex(clauseIds.stream().map(String::valueOf).collect(Collectors.joining(",")));
+        } else {
+            throw new ServiceException("不支持的订单类型");
+        }
+
+        int candidateCount = bo.getCandidates().size();
+        BigDecimal totalAmount = unitPrice.multiply(BigDecimal.valueOf(candidateCount));
+        if (totalAmount.compareTo(BigDecimal.ZERO) <= 0) {
+            throw new ServiceException("订单金额必须大于0");
+        }
+
+        List<Long> candidateRelationIds = new ArrayList<>(candidateCount);
+        for (PortalCheckOrderCreateBo.CandidateInfo candidateInfo : bo.getCandidates()) {
+            MainStudent student = matchStudent(candidateInfo);
+            MainBackCandidate relation = findOrCreateCandidateRelation(tenantId, student.getId());
+            candidateRelationIds.add(relation.getId());
+        }
+
+        String orderNo = generateOrderNo();
+
+        MainBackOrder backOrder = new MainBackOrder();
+        backOrder.setOrderNo(orderNo);
+        backOrder.setTenantId(tenantId);
+        backOrder.setCategoryId(categoryId);
+        backOrder.setCategoryName(categoryName);
+        backOrder.setTotalAmount(totalAmount);
+        backOrder.setClauseIds(clauseIdsJson);
+        backOrder.setClauseGroupKey(clauseGroupKey);
+        backOrder.setStatus("0");
+        baseMapper.insert(backOrder);
+
+        MainOrder mainOrder = new MainOrder();
+        mainOrder.setOrderNo(orderNo);
+        mainOrder.setOrderType(3);
+        mainOrder.setBuyerType(1);
+        mainOrder.setBuyerId(tenant.getId());
+        mainOrder.setBuyerName(tenant.getCompanyName());
+        mainOrder.setSellerId(1L);
+        mainOrder.setTotalAmount(totalAmount);
+        mainOrder.setPaidAmount(BigDecimal.ZERO);
+        mainOrder.setRefundAmount(BigDecimal.ZERO);
+        mainOrder.setOrderStatus(0);
+        mainOrder.setPayStatus(0);
+        mainOrder.setBusinessId(backOrder.getId());
+        mainOrder.setTenantId(tenantId);
+        mainOrderMapper.insert(mainOrder);
+
+        for (Long candidateRelationId : candidateRelationIds) {
+            MainBackRecord record = new MainBackRecord();
+            record.setOrderId(backOrder.getId());
+            record.setCandidateId(candidateRelationId);
+            record.setStatus("未完成");
+            recordMapper.insert(record);
+        }
+
+        MainBackOrderVo vo = MapstructUtils.convert(backOrder, MainBackOrderVo.class);
+        vo.setOrderId(mainOrder.getId());
+        vo.setOrderNo(mainOrder.getOrderNo());
+        vo.setCompanyId(tenant.getId());
+        vo.setCompanyName(tenant.getCompanyName());
+        vo.setTenantId(tenantId);
+        vo.setOrderType(bo.getOrderType());
+        vo.setClauseIds(clauseIdsJson);
+        vo.setUnitPrice(unitPrice);
+        vo.setTotalAmount(totalAmount);
+        vo.setOrderStatus(0);
+        vo.setMainOrderStatus(0);
+        vo.setCandidateCount((long) candidateCount);
+        return vo;
+    }
+
     private void fillPortalFields(MainBackOrderVo vo, MainOrder mainOrder) {
         Long count = recordMapper.selectCount(
             new LambdaQueryWrapper<MainBackRecord>()
@@ -146,6 +341,142 @@ public class MainBackOrderServiceImpl implements IMainBackOrderService {
         }
     }
 
+    private MainStudent matchStudent(PortalCheckOrderCreateBo.CandidateInfo candidateInfo) {
+        MainStudent student = mainStudentMapper.selectOne(
+            Wrappers.<MainStudent>lambdaQuery()
+                .eq(MainStudent::getMobile, candidateInfo.getCandidatePhone())
+                .eq(MainStudent::getIdCardNumber, candidateInfo.getCandidateIdNumber())
+                .eq(MainStudent::getStatus, "0")
+                .last("limit 1")
+        );
+        if (student == null) {
+            throw new ServiceException(
+                "候选人【" + candidateInfo.getCandidateName() + "】未在学员库中找到,请先完成学员注册"
+            );
+        }
+        return student;
+    }
+
+    private MainBackCandidate findOrCreateCandidateRelation(String tenantId, Long studentId) {
+        MainBackCandidate relation = mainBackCandidateMapper.selectOne(
+            Wrappers.<MainBackCandidate>lambdaQuery()
+                .eq(MainBackCandidate::getTenantId, tenantId)
+                .eq(MainBackCandidate::getStudentId, studentId)
+                .last("limit 1")
+        );
+        if (relation != null) {
+            return relation;
+        }
+
+        relation = new MainBackCandidate();
+        relation.setTenantId(tenantId);
+        relation.setStudentId(studentId);
+        relation.setSource("portal");
+        mainBackCandidateMapper.insert(relation);
+        return relation;
+    }
+
+    private BigDecimal defaultAmount(BigDecimal amount) {
+        return amount == null ? BigDecimal.ZERO : amount;
+    }
+
+    private List<MainBackOrderCandidateVo> buildPortalCandidates(Long backOrderId) {
+        List<MainBackRecord> records = recordMapper.selectList(
+            Wrappers.<MainBackRecord>lambdaQuery()
+                .eq(MainBackRecord::getOrderId, backOrderId)
+                .orderByAsc(MainBackRecord::getCreateTime)
+        );
+        if (records.isEmpty()) {
+            return new ArrayList<>();
+        }
+
+        List<Long> candidateIds = records.stream()
+            .map(MainBackRecord::getCandidateId)
+            .distinct()
+            .toList();
+        Map<Long, MainBackCandidate> candidateMap = new HashMap<>();
+        for (MainBackCandidate candidate : mainBackCandidateMapper.selectBatchIds(candidateIds)) {
+            candidateMap.put(candidate.getId(), candidate);
+        }
+
+        List<Long> studentIds = candidateMap.values().stream()
+            .map(MainBackCandidate::getStudentId)
+            .filter(java.util.Objects::nonNull)
+            .distinct()
+            .toList();
+        Map<Long, MainStudent> studentMap = new HashMap<>();
+        for (MainStudent student : mainStudentMapper.selectBatchIds(studentIds)) {
+            studentMap.put(student.getId(), student);
+        }
+
+        List<MainBackOrderCandidateVo> result = new ArrayList<>(records.size());
+        for (MainBackRecord record : records) {
+            MainBackCandidate candidate = candidateMap.get(record.getCandidateId());
+            MainStudent student = candidate == null ? null : studentMap.get(candidate.getStudentId());
+
+            MainBackOrderCandidateVo vo = new MainBackOrderCandidateVo();
+            vo.setId(record.getId());
+            vo.setOrderId(backOrderId);
+            vo.setStudentName(student == null ? null : student.getName());
+            vo.setCandidatePhone(student == null ? null : student.getMobile());
+            vo.setCandidateIdType("1");
+            vo.setCandidateIdNumber(student == null ? null : student.getIdCardNumber());
+            vo.setStatus(mapRecordStatus(record.getStatus()));
+            vo.setReportUrl(record.getReportUrl());
+            result.add(vo);
+        }
+        return result;
+    }
+
+    private List<MainBackClauseVo> buildPortalClauses(MainBackOrder backOrder) {
+        if (backOrder.getCategoryId() != null) {
+            return mainBackClauseMapper.selectVoList(
+                Wrappers.<MainBackClause>lambdaQuery()
+                    .eq(MainBackClause::getCategoryId, backOrder.getCategoryId())
+                    .eq(MainBackClause::getStatus, "1")
+                    .orderByAsc(MainBackClause::getId)
+            );
+        }
+        if (StringUtils.isBlank(backOrder.getClauseIds())) {
+            return new ArrayList<>();
+        }
+
+        List<Long> clauseIds = JsonUtils.parseArray(backOrder.getClauseIds(), Long.class);
+        if (clauseIds == null || clauseIds.isEmpty()) {
+            return new ArrayList<>();
+        }
+
+        Map<Long, MainBackClauseVo> clauseMap = mainBackClauseMapper.selectVoList(
+            Wrappers.<MainBackClause>lambdaQuery().in(MainBackClause::getId, clauseIds)
+        ).stream().collect(Collectors.toMap(MainBackClauseVo::getId, item -> item, (a, b) -> a));
+
+        List<MainBackClauseVo> result = new ArrayList<>();
+        for (Long clauseId : clauseIds) {
+            MainBackClauseVo clause = clauseMap.get(clauseId);
+            if (clause != null) {
+                result.add(clause);
+            }
+        }
+        return result;
+    }
+
+    private String generateOrderNo() {
+        return "ORD" + cn.hutool.core.date.DateUtil.format(new Date(), "yyyyMMddHHmmss")
+            + cn.hutool.core.util.RandomUtil.randomNumbers(4);
+    }
+
+    private Integer mapRecordStatus(String status) {
+        if (StringUtils.isBlank(status)) {
+            return 0;
+        }
+        return switch (status) {
+            case "进行中" -> 1;
+            case "已完成", "完成" -> 2;
+            case "已取消", "失败" -> 3;
+            default -> 0;
+        };
+    }
+
     private Integer parseIntOrDefault(String value, int defaultValue) {
         if (StringUtils.isBlank(value)) {
             return defaultValue;

+ 12 - 0
ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/service/impl/MainExamEvaluationServiceImpl.java

@@ -140,6 +140,18 @@ public class MainExamEvaluationServiceImpl implements IMainExamEvaluationService
         return true;
     }
 
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public Boolean updateStatus(Long id, String status) {
+        if (id == null || StringUtils.isBlank(status)) {
+            return false;
+        }
+        MainExamEvaluation entity = new MainExamEvaluation();
+        entity.setId(id);
+        entity.setStatus(status);
+        return baseMapper.updateById(entity) > 0;
+    }
+
     @Override
     public Boolean syncThirdPartyData() {
         log.info("开始同步第三方测评数据");

+ 16 - 0
ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/service/impl/MainPostApplyServiceImpl.java

@@ -62,6 +62,22 @@ public class MainPostApplyServiceImpl implements IMainPostApplyService {
         return baseMapper.updateById(update) > 0;
     }
 
+    @Override
+    public Boolean publishById(Long id) {
+        MainPostApply entity = new MainPostApply();
+        entity.setId(id);
+        entity.setStatus("0");
+        return baseMapper.updateById(entity) > 0;
+    }
+
+    @Override
+    public Boolean unpublishById(Long id) {
+        MainPostApply entity = new MainPostApply();
+        entity.setId(id);
+        entity.setStatus("1");
+        return baseMapper.updateById(entity) > 0;
+    }
+
     @Override
     public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
         return baseMapper.deleteBatchIds(ids) > 0;

+ 32 - 0
ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/service/impl/PaymentConfigServiceImpl.java

@@ -0,0 +1,32 @@
+package org.dromara.main.service.impl;
+
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import lombok.RequiredArgsConstructor;
+import org.dromara.main.domain.PaymentConfig;
+import org.dromara.main.mapper.PaymentConfigMapper;
+import org.dromara.main.service.IPaymentConfigService;
+import org.springframework.stereotype.Service;
+
+@RequiredArgsConstructor
+@Service
+public class PaymentConfigServiceImpl implements IPaymentConfigService {
+
+    private final PaymentConfigMapper paymentConfigMapper;
+
+    @Override
+    public PaymentConfig getEnabledAlipayConfig() {
+        return getEnabledReceiveConfig();
+    }
+
+    @Override
+    public PaymentConfig getEnabledReceiveConfig() {
+        return paymentConfigMapper.selectOne(
+            Wrappers.<PaymentConfig>lambdaQuery()
+                .eq(PaymentConfig::getConfigType, 1)
+                .eq(PaymentConfig::getPaymentType, 1)
+                .eq(PaymentConfig::getIsEnabled, 1)
+                .orderByDesc(PaymentConfig::getCreateTime)
+                .last("limit 1")
+        );
+    }
+}

+ 241 - 0
ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/service/impl/PaymentServiceImpl.java

@@ -0,0 +1,241 @@
+package org.dromara.main.service.impl;
+
+import cn.hutool.core.date.DateUtil;
+import cn.hutool.core.util.RandomUtil;
+import cn.hutool.json.JSONObject;
+import cn.hutool.json.JSONUtil;
+import com.alipay.api.AlipayApiException;
+import com.alipay.api.AlipayClient;
+import com.alipay.api.DefaultAlipayClient;
+import com.alipay.api.internal.util.AlipaySignature;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.dromara.common.core.exception.ServiceException;
+import org.dromara.main.domain.MainBackOrder;
+import org.dromara.main.domain.MainOrder;
+import org.dromara.main.domain.Payment;
+import org.dromara.main.domain.PaymentConfig;
+import org.dromara.main.domain.vo.PaymentVo;
+import org.dromara.main.mapper.MainBackOrderMapper;
+import org.dromara.main.mapper.MainOrderMapper;
+import org.dromara.main.mapper.PaymentMapper;
+import org.dromara.main.service.IPaymentConfigService;
+import org.dromara.main.service.IPaymentService;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.math.BigDecimal;
+import java.util.Date;
+import java.util.Map;
+
+@Slf4j
+@RequiredArgsConstructor
+@Service
+public class PaymentServiceImpl implements IPaymentService {
+
+    private final PaymentMapper paymentMapper;
+    private final MainOrderMapper mainOrderMapper;
+    private final MainBackOrderMapper mainBackOrderMapper;
+    private final IPaymentConfigService paymentConfigService;
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public PaymentVo createAlipayQrCode(Long orderId, String tenantId) {
+        MainOrder order = mainOrderMapper.selectById(orderId);
+        if (order == null) {
+            throw new ServiceException("订单不存在");
+        }
+        if (tenantId != null && !tenantId.equals(order.getTenantId())) {
+            throw new ServiceException("无权操作该订单");
+        }
+        if (order.getPayStatus() != null && order.getPayStatus() != 0) {
+            throw new ServiceException("订单已支付或已关闭");
+        }
+
+        Payment existPayment = paymentMapper.selectOne(
+            Wrappers.<Payment>lambdaQuery()
+                .eq(Payment::getOrderId, orderId)
+                .in(Payment::getPaymentStatus, java.util.List.of(0, 1))
+                .orderByDesc(Payment::getCreateTime)
+                .last("limit 1")
+        );
+        if (existPayment != null) {
+            try {
+                String formHtml = createAlipayPagePayInternal(existPayment, order);
+                PaymentVo vo = paymentMapper.selectVoById(existPayment.getId());
+                vo.setQrCodeUrl(formHtml);
+                return vo;
+            } catch (AlipayApiException e) {
+                throw new ServiceException("生成支付表单失败:" + e.getMessage());
+            }
+        }
+
+        Payment payment = new Payment();
+        payment.setPaymentNo(generatePaymentNo());
+        payment.setOrderId(orderId);
+        payment.setOrderNo(order.getOrderNo());
+        payment.setPaymentType(3);
+        payment.setPaymentAmount(order.getTotalAmount());
+        payment.setPaymentStatus(0);
+        paymentMapper.insert(payment);
+
+        try {
+            String formHtml = createAlipayPagePayInternal(payment, order);
+            payment.setPaymentStatus(1);
+            paymentMapper.updateById(payment);
+
+            PaymentVo vo = paymentMapper.selectVoById(payment.getId());
+            vo.setQrCodeUrl(formHtml);
+            return vo;
+        } catch (Exception e) {
+            payment.setPaymentStatus(3);
+            payment.setRemark("创建支付失败:" + e.getMessage());
+            paymentMapper.updateById(payment);
+            throw new ServiceException("创建支付失败:" + e.getMessage());
+        }
+    }
+
+    private String createAlipayPagePayInternal(Payment payment, MainOrder order) throws AlipayApiException {
+        PaymentConfig config = paymentConfigService.getEnabledAlipayConfig();
+        if (config == null) {
+            throw new ServiceException("未找到启用的支付宝配置");
+        }
+
+        AlipayClient client = new DefaultAlipayClient(
+            config.getGatewayUrl(),
+            config.getAppId(),
+            config.getPrivateKey(),
+            defaultIfBlank(config.getFormat(), "json"),
+            defaultIfBlank(config.getCharset(), "UTF-8"),
+            config.getPublicKey(),
+            defaultIfBlank(config.getSignType(), "RSA2")
+        );
+
+        com.alipay.api.request.AlipayTradePagePayRequest request =
+            new com.alipay.api.request.AlipayTradePagePayRequest();
+        request.setNotifyUrl(config.getPayNotifyUrl());
+        request.setReturnUrl(config.getReturnUrl());
+
+        JSONObject bizContent = JSONUtil.createObj();
+        bizContent.set("out_trade_no", payment.getPaymentNo());
+        bizContent.set("total_amount", payment.getPaymentAmount().toString());
+        bizContent.set("subject", "背调服务订单-" + order.getOrderNo());
+        bizContent.set("body", "背调服务订单支付");
+        bizContent.set("product_code", "FAST_INSTANT_TRADE_PAY");
+        bizContent.set("timeout_express", "30m");
+        request.setBizContent(bizContent.toString());
+
+        com.alipay.api.response.AlipayTradePagePayResponse response = client.pageExecute(request);
+        if (!response.isSuccess()) {
+            throw new ServiceException("支付宝返回错误:" + response.getSubMsg());
+        }
+        return response.getBody();
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public String handleAlipayNotify(Map<String, String> params) {
+        try {
+            PaymentConfig config = paymentConfigService.getEnabledReceiveConfig();
+            if (config == null) {
+                log.error("未找到启用的支付宝配置");
+                return "failure";
+            }
+
+            boolean signVerified = AlipaySignature.rsaCheckV1(
+                params,
+                config.getPublicKey(),
+                defaultIfBlank(config.getCharset(), "UTF-8"),
+                defaultIfBlank(config.getSignType(), "RSA2")
+            );
+            if (!signVerified) {
+                log.error("支付宝回调签名验证失败");
+                return "failure";
+            }
+
+            String outTradeNo = params.get("out_trade_no");
+            String tradeNo = params.get("trade_no");
+            String tradeStatus = params.get("trade_status");
+            String totalAmount = params.get("total_amount");
+
+            Payment payment = paymentMapper.selectOne(
+                Wrappers.<Payment>lambdaQuery()
+                    .eq(Payment::getPaymentNo, outTradeNo)
+                    .last("limit 1")
+            );
+            if (payment == null) {
+                return "failure";
+            }
+            if (payment.getPaymentStatus() != null && payment.getPaymentStatus() == 2) {
+                return "success";
+            }
+
+            BigDecimal notifyAmount = new BigDecimal(totalAmount);
+            if (notifyAmount.compareTo(payment.getPaymentAmount()) != 0) {
+                log.error("支付金额不匹配:expect={}, actual={}", payment.getPaymentAmount(), notifyAmount);
+                return "failure";
+            }
+
+            if ("TRADE_SUCCESS".equals(tradeStatus) || "TRADE_FINISHED".equals(tradeStatus)) {
+                payment.setTradeNo(tradeNo);
+                payment.setTradeStatus(tradeStatus);
+                payment.setPaymentMethod(2);
+                payment.setPaymentStatus(2);
+                payment.setPayTime(new Date());
+                paymentMapper.updateById(payment);
+
+                MainOrder order = mainOrderMapper.selectById(payment.getOrderId());
+                if (order != null && order.getOrderStatus() != null && order.getOrderStatus() == 0) {
+                    order.setOrderStatus(1);
+                    order.setPayStatus(2);
+                    order.setPaidAmount(payment.getPaymentAmount());
+                    order.setPayTime(new Date());
+                    mainOrderMapper.updateById(order);
+
+                    if (order.getOrderType() != null && order.getOrderType() == 3 && order.getBusinessId() != null) {
+                        MainBackOrder backOrder = mainBackOrderMapper.selectById(order.getBusinessId());
+                        if (backOrder != null && "0".equals(backOrder.getStatus())) {
+                            backOrder.setStatus("1");
+                            mainBackOrderMapper.updateById(backOrder);
+                        }
+                    }
+                }
+                return "success";
+            }
+            return "success";
+        } catch (Exception e) {
+            log.error("处理支付宝回调异常", e);
+            return "failure";
+        }
+    }
+
+    @Override
+    public PaymentVo queryPaymentStatus(Long orderId) {
+        Payment payment = paymentMapper.selectOne(
+            Wrappers.<Payment>lambdaQuery()
+                .eq(Payment::getOrderId, orderId)
+                .orderByDesc(Payment::getCreateTime)
+                .last("limit 1")
+        );
+        return payment == null ? null : paymentMapper.selectVoById(payment.getId());
+    }
+
+    @Override
+    public PaymentVo queryByPaymentNo(String paymentNo) {
+        Payment payment = paymentMapper.selectOne(
+            Wrappers.<Payment>lambdaQuery()
+                .eq(Payment::getPaymentNo, paymentNo)
+                .last("limit 1")
+        );
+        return payment == null ? null : paymentMapper.selectVoById(payment.getId());
+    }
+
+    private String generatePaymentNo() {
+        return "PAY" + DateUtil.format(new Date(), "yyyyMMddHHmmss") + RandomUtil.randomNumbers(4);
+    }
+
+    private String defaultIfBlank(String value, String defaultValue) {
+        return (value == null || value.isBlank()) ? defaultValue : value;
+    }
+}

+ 8 - 0
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysIndustryController.java

@@ -32,6 +32,14 @@ public class SysIndustryController extends BaseController {
         return R.ok(industryService.queryList(bo));
     }
 
+    /**
+     * 查询行业分类列表(忽略租户)
+     */
+    @GetMapping("/open/list")
+    public R<List<SysIndustryVo>> openList(SysIndustryBo bo) {
+        return R.ok(industryService.queryListIgnoreTenant(bo));
+    }
+
     /**
      * 获取行业分类详情
      */

+ 8 - 0
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysIndustrySkillController.java

@@ -31,6 +31,14 @@ public class SysIndustrySkillController extends BaseController {
         return R.ok(skillService.queryList(bo));
     }
 
+    /**
+     * 查询职位列表(忽略租户)
+     */
+    @GetMapping("/open/list")
+    public R<List<SysIndustrySkillVo>> openList(SysIndustrySkillBo bo) {
+        return R.ok(skillService.queryListIgnoreTenant(bo));
+    }
+
     /**
      * 获取职位详情
      */

+ 1 - 0
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysIndustryService.java

@@ -7,6 +7,7 @@ import java.util.List;
 
 public interface ISysIndustryService {
     List<SysIndustryVo> queryList(SysIndustryBo bo);
+    List<SysIndustryVo> queryListIgnoreTenant(SysIndustryBo bo);
     SysIndustryVo queryById(Long industryId);
     Boolean insertByBo(SysIndustryBo bo);
     Boolean updateByBo(SysIndustryBo bo);

+ 1 - 0
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysIndustrySkillService.java

@@ -9,6 +9,7 @@ import java.util.List;
 
 public interface ISysIndustrySkillService {
     List<SysIndustrySkillVo> queryList(SysIndustrySkillBo bo);
+    List<SysIndustrySkillVo> queryListIgnoreTenant(SysIndustrySkillBo bo);
     SysIndustrySkillVo queryById(Long skillId);
     Boolean insertByBo(SysIndustrySkillBo bo);
     Boolean updateByBo(SysIndustrySkillBo bo);

+ 6 - 0
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysIndustryServiceImpl.java

@@ -4,6 +4,7 @@ import cn.hutool.core.bean.BeanUtil;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.tenant.helper.TenantHelper;
 import org.dromara.system.domain.SysIndustry;
 import org.dromara.system.domain.bo.SysIndustryBo;
 import org.dromara.system.domain.vo.SysIndustryVo;
@@ -34,6 +35,11 @@ public class SysIndustryServiceImpl implements ISysIndustryService {
         return BeanUtil.copyToList(list, SysIndustryVo.class);
     }
 
+    @Override
+    public List<SysIndustryVo> queryListIgnoreTenant(SysIndustryBo bo) {
+        return TenantHelper.ignore(() -> queryList(bo));
+    }
+
     @Override
     public SysIndustryVo queryById(Long industryId) {
         SysIndustry industry = baseMapper.selectById(industryId);

+ 6 - 2
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysIndustrySkillServiceImpl.java

@@ -5,11 +5,10 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import lombok.RequiredArgsConstructor;
 import org.dromara.common.core.utils.StringUtils;
-import org.dromara.system.domain.SysIndustry;
+import org.dromara.common.tenant.helper.TenantHelper;
 import org.dromara.system.domain.SysIndustrySkill;
 import org.dromara.system.domain.bo.SysIndustrySkillBo;
 import org.dromara.system.domain.vo.SysIndustrySkillVo;
-import org.dromara.system.domain.vo.SysIndustryVo;
 import org.dromara.system.mapper.SysIndustrySkillMapper;
 import org.dromara.system.service.ISysIndustrySkillService;
 import org.springframework.stereotype.Service;
@@ -36,6 +35,11 @@ public class SysIndustrySkillServiceImpl implements ISysIndustrySkillService {
         return BeanUtil.copyToList(list, SysIndustrySkillVo.class);
     }
 
+    @Override
+    public List<SysIndustrySkillVo> queryListIgnoreTenant(SysIndustrySkillBo bo) {
+        return TenantHelper.ignore(() -> queryList(bo));
+    }
+
     @Override
     public SysIndustrySkillVo queryById(Long skillId) {
         SysIndustrySkill skill = baseMapper.selectById(skillId);