西格玛许 1 ngày trước cách đây
mục cha
commit
38067edd82

+ 100 - 8
ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/service/impl/KaoshixingService.java

@@ -47,8 +47,8 @@ public class KaoshixingService {
     private static final String ACTION_ID_LOGIN = "201";
     // 查询考生信息 action_id
     private static final String ACTION_ID_QUERY_USER = "209";
-    // JWT过期时间(30秒,考虑网络延迟
-    private static final long EXPIRATION_SECONDS = 30L;
+    // JWT过期时间(5分钟,确保在高延迟网络下依然有效
+    private static final long EXPIRATION_SECONDS = 300L;
 
     // 固定的 appId / appKey(按需求写死)
     private static final String FIXED_APP_ID = "631088";
@@ -113,18 +113,108 @@ public class KaoshixingService {
         return sendPostRequest(requestUrl, requestBody);
     }
 
-    /**
-     * 免注册登录:始终使用考生登录(201)
-     */
     public String fetchAutoLogin(KaoshixingAutoLoginRequest req) {
-        // 始终走 201:由考试星侧完成已存在用户登录或首次注册后登录
+        validateAutoLoginParams(req);
+
+        // 直接执行 201:由考试星侧完成已存在用户登录或首次注册后登录
         String loginJwt = generateJwt(FIXED_APP_KEY, ACTION_ID_LOGIN);
         String loginUrl = String.format("%s%s/?jwt=%s", BASE_API_URL, FIXED_APP_ID, loginJwt);
         String loginBody = buildLoginBody(req);
-        log.info("考生登录(201)请求体:{}", loginBody);
+
+        log.info("直接发起考试星登录(201)请求,请求体:{}", loginBody);
         return sendPostRequest(loginUrl, loginBody);
     }
 
+    private String queryUser(KaoshixingAutoLoginRequest req) {
+        String jwtInfo = generateJwt(FIXED_APP_KEY, ACTION_ID_QUERY_USER);
+        String requestUrl = String.format("%s%s/?jwt=%s", BASE_API_URL, FIXED_APP_ID, jwtInfo);
+
+        Map<String, Object> payload = new HashMap<>();
+        payload.put("user_id", req.getUserId());
+
+        try {
+            String requestBody = objectMapper.writeValueAsString(payload);
+            log.info("查询考生(209)请求体:{}", requestBody);
+            return sendPostRequest(requestUrl, requestBody);
+        } catch (Exception e) {
+            throw new RuntimeException("构造209请求失败:" + e.getMessage(), e);
+        }
+    }
+
+    private String buildSilentLoginBody(KaoshixingAutoLoginRequest req) {
+        try {
+            Map<String, Object> payload = new HashMap<>();
+            payload.put("user_id", req.getUserId());
+            if (!isBlank(req.getCustomUrl())) {
+                payload.put("custom_url", req.getCustomUrl());
+            }
+            return objectMapper.writeValueAsString(payload);
+        } catch (Exception e) {
+            throw new RuntimeException("构造203请求体失败:" + e.getMessage(), e);
+        }
+    }
+
+    private boolean isUserExists(String resp) {
+        try {
+            JsonNode root = objectMapper.readTree(resp);
+            int code = root.path("code").asInt();
+            if (code != 10000) {
+                return false;
+            }
+
+            JsonNode data = root.path("data");
+            if (data.isMissingNode() || data.isNull()) {
+                return false;
+            }
+
+            if (data.isArray()) {
+                return data.size() > 0;
+            }
+
+            return !data.isEmpty();
+        } catch (Exception e) {
+            log.warn("判断209是否存在用户失败", e);
+            return false;
+        }
+    }
+
+
+    private boolean isUserNotExists(String resp) {
+        try {
+            JsonNode root = objectMapper.readTree(resp);
+            int code = root.path("code").asInt();
+
+            if (code == 10000) {
+                JsonNode data = root.path("data");
+                if (data.isMissingNode() || data.isNull()) {
+                    return true;
+                }
+                if (data.isArray()) {
+                    return data.isEmpty();
+                }
+                return data.isEmpty();
+            }
+
+            String msg = root.path("msg").asText("");
+            return msg.contains("不存在") || msg.contains("未找到") || msg.contains("无此用户");
+        } catch (Exception e) {
+            log.warn("判断209是否为不存在用户失败", e);
+            return false;
+        }
+    }
+
+    private void validateAutoLoginParams(KaoshixingAutoLoginRequest req) {
+        if (req == null || isBlank(req.getUserId())) {
+            throw new RuntimeException("user_id不能为空");
+        }
+        if (isBlank(req.getUserName())) {
+            req.setUserName("学员");
+        }
+        if (isBlank(req.getDepartment())) {
+            req.setDepartment("学员");
+        }
+    }
+
     /**
      * 获取考生可见考试和考试结果整合列表(action_id=702)
      */
@@ -204,12 +294,13 @@ public class KaoshixingService {
         String jwt = Jwts.builder()
             .claim("exp", expTimeMillis) // 考试星要求毫秒级时间戳,不是JWT标准的秒级
             .claim("action_id", actionId)
+            .claim("app_id", FIXED_APP_ID)
             .signWith(
                 SignatureAlgorithm.HS256,
                 appKey.getBytes(StandardCharsets.UTF_8)
             )
             .compact();
-        
+
         log.info("生成的JWT: {}", jwt);
         return jwt;
     }
@@ -328,6 +419,7 @@ public class KaoshixingService {
             payload.put("user_id", req.getUserId());
             payload.put("user_name", req.getUserName());
             payload.put("department", req.getDepartment());
+            payload.put("app_id", FIXED_APP_ID);
             if (!isBlank(req.getCustomUrl())) {
                 payload.put("custom_url", req.getCustomUrl());
             }

+ 58 - 6
ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/config/WxPayConfig.java

@@ -13,14 +13,19 @@ import jakarta.annotation.PostConstruct;
 import lombok.Data;
 import lombok.extern.slf4j.Slf4j;
 import org.dromara.common.core.utils.StringUtils;
+import org.dromara.main.domain.PaymentConfig;
+import org.dromara.main.service.IPaymentConfigService;
 import org.dromara.main.utils.WXPayUtility;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Lazy;
 import org.springframework.core.io.Resource;
 import org.springframework.core.io.ResourceLoader;
 import org.springframework.stereotype.Component;
 
+import java.io.InputStream;
 import java.math.BigDecimal;
+import java.net.URL;
 import java.security.PrivateKey;
 import java.time.Instant;
 import java.util.HashMap;
@@ -50,24 +55,71 @@ public class WxPayConfig {
     @Autowired
     private ResourceLoader resourceLoader;
 
+    @Lazy
+    @Autowired(required = false)
+    private IPaymentConfigService paymentConfigService;
+
     @PostConstruct
     public void init() {
         try {
             log.info("开始初始化微信支付配置...");
 
+            // 1. 尝试从数据库获取配置
+            if (paymentConfigService != null) {
+                try {
+                    PaymentConfig dbConfig = paymentConfigService.getEnabledWechatConfig();
+                    if (dbConfig != null) {
+                        log.info("检测到数据库微信支付配置,尝试覆盖本地配置...");
+                        if (StringUtils.isNotBlank(dbConfig.getAppId())) {
+                            this.appId = dbConfig.getAppId();
+                        }
+                        if (StringUtils.isNotBlank(dbConfig.getMchId())) {
+                            this.mchId = dbConfig.getMchId();
+                        }
+                        if (StringUtils.isNotBlank(dbConfig.getApiV3Key())) {
+                            this.apiV3Key = dbConfig.getApiV3Key();
+                        }
+                        if (StringUtils.isNotBlank(dbConfig.getPrivateKeyPath())) {
+                            this.privateKeyPath = dbConfig.getPrivateKeyPath();
+                        }
+                        if (StringUtils.isNotBlank(dbConfig.getSerialNo())) {
+                            this.certSerialNo = dbConfig.getSerialNo();
+                        }
+                        if (StringUtils.isNotBlank(dbConfig.getPayNotifyUrl())) {
+                            this.notifyUrl = dbConfig.getPayNotifyUrl();
+                        }
+                    }
+                } catch (Exception e) {
+                    log.warn("从数据库获取微信支付配置失败,将使用配置文件:{}", e.getMessage());
+                }
+            }
+
             // 检查配置是否完整
             if (StringUtils.isEmpty(privateKeyPath)) {
-                log.warn("微信支付配置不完整,跳过初始化");
+                log.warn("微信支付配置不完整 (privateKeyPath 为空),跳过初始化");
                 return;
             }
 
             // 加载商户私钥
-            Resource resource = resourceLoader.getResource(privateKeyPath);
-            if (resource.exists()) {
-                privateKey = PemUtil.readPemPrivateKey(resource.getInputStream());
-                log.info("商户私钥加载成功");
+            InputStream privateKeyStream = null;
+            if (privateKeyPath.startsWith("http")) {
+                // 如果是网络路径 (OSS)
+                privateKeyStream = new URL(privateKeyPath).openStream();
+                log.info("从网络加载商户私钥成功");
+            } else {
+                // 如果是本地路径或 classpath
+                Resource resource = resourceLoader.getResource(privateKeyPath);
+                if (resource.exists()) {
+                    privateKeyStream = resource.getInputStream();
+                    log.info("从本地/资源路径加载商户私钥成功: {}", privateKeyPath);
+                }
+            }
+
+            if (privateKeyStream != null) {
+                privateKey = PemUtil.readPemPrivateKey(privateKeyStream);
+                privateKeyStream.close();
             } else {
-                log.warn("找不到商户私钥文件:{},跳过微信支付初始化", privateKeyPath);
+                log.warn("找不到商户私钥:{},跳过微信支付初始化", privateKeyPath);
                 return;
             }
 

+ 12 - 0
ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/controller/MainExamApplyController.java

@@ -7,11 +7,14 @@ import org.dromara.common.core.domain.R;
 import org.dromara.common.log.annotation.Log;
 import org.dromara.common.log.enums.BusinessType;
 import org.dromara.common.web.core.BaseController;
+import org.dromara.main.domain.vo.MainExamApplyRecordVo;
 import org.dromara.main.domain.vo.MainExamApplyVo;
 import org.dromara.main.service.IMainExamApplyService;
 import org.springframework.validation.annotation.Validated;
 import org.springframework.web.bind.annotation.*;
 
+import java.util.List;
+
 /**
  * 测评申请Controller
  */
@@ -50,6 +53,15 @@ public class MainExamApplyController extends BaseController {
         return R.ok(result);
     }
 
+    /**
+     * 查询学生测评记录列表
+     */
+    @SaIgnore
+    @GetMapping("/record/list")
+    public R<List<MainExamApplyRecordVo>> recordList(@RequestParam Long studentId) {
+        return R.ok(examApplyService.queryRecordList(studentId));
+    }
+
     /**
      * 更新申请状态
      */

+ 7 - 0
ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/domain/vo/MainAuditVo.java

@@ -120,4 +120,11 @@ public class MainAuditVo implements Serializable {
     private BigDecimal gradeB;
     private BigDecimal gradeC;
     private String auditByName;
+
+
+//    @ExcelProperty(value = "最小工资")
+    private BigDecimal minSalary;
+
+//    @ExcelProperty(value = "最大工资")
+    private BigDecimal maxSalary;
 }

+ 35 - 0
ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/mapper/MainExamApplyMapper.java

@@ -5,6 +5,9 @@ import org.apache.ibatis.annotations.Select;
 import org.apache.ibatis.annotations.Update;
 import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
 import org.dromara.main.domain.MainExamApply;
+import org.dromara.main.domain.vo.MainExamApplyRecordVo;
+
+import java.util.List;
 
 public interface MainExamApplyMapper extends BaseMapperPlus<MainExamApply, MainExamApply> {
 
@@ -15,4 +18,36 @@ public interface MainExamApplyMapper extends BaseMapperPlus<MainExamApply, MainE
 
     @Update("UPDATE main_exam_apply SET del_flag = '0', apply_source = '3', apply_status = '0', schedule_start_time = NOW(), deadline_time = NULL, max_attempt_count = 1, finished_time = NULL, final_result = NULL, update_time = NOW() WHERE id = #{id}")
     int restoreDeletedApply(@Param("id") Long id);
+
+    @Select("""
+        SELECT
+            a.id,
+            a.evaluation_id AS evaluationId,
+            e.evaluation_name AS evaluationName,
+            a.student_id AS studentId,
+            a.apply_status AS applyStatus,
+            a.final_result AS finalResult,
+            CASE
+                WHEN a.final_result = '1' THEN '通过'
+                WHEN a.final_result = '2' THEN '未通过'
+                WHEN a.apply_status = '2' THEN '待评分'
+                WHEN a.apply_status = '1' THEN '测评中'
+                ELSE '待测评'
+            END AS statusText,
+            CASE
+                WHEN a.final_result = '1' THEN 'pass'
+                WHEN a.final_result = '2' THEN 'fail'
+                ELSE 'pending'
+            END AS statusType,
+            a.schedule_start_time AS scheduleStartTime,
+            a.deadline_time AS deadlineTime,
+            a.finished_time AS finishedTime,
+            a.create_time AS createTime
+        FROM main_exam_apply a
+        LEFT JOIN main_exam_evaluation e ON a.evaluation_id = e.id AND IFNULL(e.del_flag, '0') = '0'
+        WHERE IFNULL(a.del_flag, '0') = '0'
+          AND a.student_id = #{studentId}
+        ORDER BY COALESCE(a.finished_time, a.update_time, a.create_time) DESC, a.id DESC
+        """)
+    List<MainExamApplyRecordVo> selectRecordListByStudentId(@Param("studentId") Long studentId);
 }

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

@@ -2,8 +2,11 @@ package org.dromara.main.service;
 
 import org.dromara.main.domain.MainExamApply;
 import org.dromara.main.domain.bo.MainExamApplyBo;
+import org.dromara.main.domain.vo.MainExamApplyRecordVo;
 import org.dromara.main.domain.vo.MainExamApplyVo;
 
+import java.util.List;
+
 /**
  * 测评申请Service接口
  */
@@ -24,6 +27,13 @@ public interface IMainExamApplyService {
      */
     MainExamApplyVo queryById(Long id);
 
+    /**
+     * 查询学生测评记录列表
+     * @param studentId 学生ID
+     * @return 测评记录列表
+     */
+    List<MainExamApplyRecordVo> queryRecordList(Long studentId);
+
     /**
      * 更新申请状态
      * @param id 申请ID

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

@@ -8,6 +8,7 @@ import org.dromara.common.core.exception.ServiceException;
 import org.dromara.common.satoken.utils.LoginHelper;
 import org.dromara.main.domain.MainExamApply;
 import org.dromara.main.domain.MainStudent;
+import org.dromara.main.domain.vo.MainExamApplyRecordVo;
 import org.dromara.main.domain.vo.MainExamApplyVo;
 import org.dromara.main.mapper.MainExamApplyMapper;
 import org.dromara.main.mapper.MainStudentMapper;
@@ -15,6 +16,7 @@ import org.dromara.main.service.IMainExamApplyService;
 import org.springframework.stereotype.Service;
 
 import java.util.Date;
+import java.util.List;
 
 /**
  * 测评申请Service业务层处理
@@ -83,6 +85,14 @@ public class MainExamApplyServiceImpl implements IMainExamApplyService {
         return converter.convert(examApply, MainExamApplyVo.class);
     }
 
+    @Override
+    public List<MainExamApplyRecordVo> queryRecordList(Long studentId) {
+        if (studentId == null) {
+            throw new ServiceException("学生ID不能为空");
+        }
+        return baseMapper.selectRecordListByStudentId(studentId);
+    }
+
     @Override
     public Boolean updateApplyStatus(Long id, String status) {
         MainExamApply examApply = baseMapper.selectById(id);