Bladeren bron

feat(main): 添加测评评估和岗位管理功能,完善了岗位审核的逻辑

- 新增 IMainExamEvaluationService 接口定义测评相关操作方法
- 新增 IMainPositionService 接口定义岗位相关操作方法
- 添加多个考试星集成相关 DTO 类用于考试数据交互
- 实现 KaoshixingService 提供考试星接口调用功能
- 在 main_position 表中添加岗位管理和测评评估相关字段
- 新增 MainAbilityConfig 实体类和对应的映射器及 XML 配置
- 修改 MainAuditServiceImpl 将岗位审核逻辑调整为同步到 main_position 表
- 更新数据库脚本添加 main_position 表结构定义
西格玛许 2 weken geleden
bovenliggende
commit
776dc34d34
31 gewijzigde bestanden met toevoegingen van 1799 en 17 verwijderingen
  1. 16 0
      ruoyi-modules/ruoyi-demo/pom.xml
  2. 15 0
      ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/domain/dto/KaoshixingAnswerListRequest.java
  3. 31 0
      ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/domain/dto/KaoshixingAutoLoginRequest.java
  4. 38 0
      ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/domain/dto/KaoshixingExamListRequest.java
  5. 25 0
      ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/domain/dto/KaoshixingRequest.java
  6. 21 0
      ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/domain/dto/KaoshixingScoreListRequest.java
  7. 24 0
      ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/domain/dto/KaoshixingStudentRequest.java
  8. 21 0
      ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/domain/dto/KaoshixingUserDetailRequest.java
  9. 24 0
      ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/domain/dto/KaoshixingUserExamListRequest.java
  10. 376 0
      ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/service/impl/KaoshixingService.java
  11. 10 0
      ruoyi-modules/ruoyi-main/pom.xml
  12. 139 0
      ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/controller/MainExamEvaluationController.java
  13. 44 0
      ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/domain/MainAbilityConfig.java
  14. 48 0
      ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/domain/MainExamEvaluation.java
  15. 55 0
      ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/domain/MainPosition.java
  16. 51 0
      ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/domain/bo/MainExamEvaluationBo.java
  17. 57 0
      ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/domain/bo/MainPositionBo.java
  18. 96 0
      ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/domain/vo/MainExamEvaluationVo.java
  19. 112 0
      ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/domain/vo/MainPositionVo.java
  20. 24 0
      ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/mapper/MainAbilityConfigMapper.java
  21. 22 0
      ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/mapper/MainExamEvaluationMapper.java
  22. 10 0
      ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/mapper/MainPositionMapper.java
  23. 57 0
      ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/service/IMainExamEvaluationService.java
  24. 31 0
      ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/service/IMainPositionService.java
  25. 41 17
      ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/service/impl/MainAuditServiceImpl.java
  26. 170 0
      ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/service/impl/MainExamEvaluationServiceImpl.java
  27. 88 0
      ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/service/impl/MainPositionServiceImpl.java
  28. 50 0
      ruoyi-modules/ruoyi-main/src/main/resources/mapper/MainAbilityConfigMapper.xml
  29. 61 0
      ruoyi-modules/ruoyi-main/src/main/resources/mapper/MainExamEvaluationMapper.xml
  30. 42 0
      script/sql/main.sql
  31. 0 0
      script/sql/新建 文本文档.txt

+ 16 - 0
ruoyi-modules/ruoyi-demo/pom.xml

@@ -103,6 +103,22 @@
             <artifactId>ruoyi-common-websocket</artifactId>
         </dependency>
 
+        <dependency>
+            <groupId>io.jsonwebtoken</groupId>
+            <artifactId>jjwt</artifactId>
+            <version>0.7.0</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.httpcomponents</groupId>
+            <artifactId>httpclient</artifactId>
+            <version>4.5.14</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.httpcomponents</groupId>
+            <artifactId>httpcore</artifactId>
+            <version>4.4.16</version>
+        </dependency>
+
     </dependencies>
 
 </project>

+ 15 - 0
ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/domain/dto/KaoshixingAnswerListRequest.java

@@ -0,0 +1,15 @@
+package org.dromara.demo.domain.dto;
+
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+/**
+ * 考生答案列表请求(action_id=604)。
+ */
+@Data
+public class KaoshixingAnswerListRequest {
+
+    /** 考试ID */
+    @NotNull(message = "examInfoId不能为空")
+    private Long examInfoId;
+}

+ 31 - 0
ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/domain/dto/KaoshixingAutoLoginRequest.java

@@ -0,0 +1,31 @@
+package org.dromara.demo.domain.dto;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import jakarta.validation.constraints.NotBlank;
+import lombok.Data;
+
+/**
+ * 免注册登录请求:先静默登录(203),失败则注册并登录(201)。
+ */
+@Data
+public class KaoshixingAutoLoginRequest {
+
+    /** 考生唯一标识 */
+    @JsonProperty("user_id")
+    @NotBlank(message = "user_id不能为空")
+    private String userId;
+
+    /** 考生姓名,用于注册场景 */
+    @JsonProperty("user_name")
+    @NotBlank(message = "user_name不能为空")
+    private String userName;
+
+    /** 部门层级,用于注册场景 */
+    @JsonProperty("department")
+    @NotBlank(message = "department不能为空")
+    private String department;
+
+    /** 登录后跳转地址,可选 */
+    @JsonProperty("custom_url")
+    private String customUrl;
+}

+ 38 - 0
ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/domain/dto/KaoshixingExamListRequest.java

@@ -0,0 +1,38 @@
+package org.dromara.demo.domain.dto;
+
+import jakarta.validation.constraints.Min;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+/**
+ * 考试星考试信息列表请求(action_id=601)。
+ */
+@Data
+public class KaoshixingExamListRequest {
+
+//    @NotBlank(message = "appId不能为空")
+    private String appId;
+
+//    @NotBlank(message = "appKey不能为空")
+    private String appKey;
+
+    /** 页码,从1开始 */
+    @NotNull(message = "page不能为空")
+    @Min(value = 1, message = "page必须>=1")
+    private Integer page;
+
+    /** 筛选:考试开始时间 >= examStartTime */
+    private String examStartTime;
+
+    /** 筛选:考试结束时间 <= examEndTime */
+    private String examEndTime;
+
+    /** 筛选:创建时间 >= createTime */
+    private String createTime;
+
+    /** 筛选:考试ID集合,逗号分隔 */
+    private String examIds;
+
+    /** JWT 过期秒数,默认300秒 */
+    private Integer expSeconds = 300;
+}

+ 25 - 0
ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/domain/dto/KaoshixingRequest.java

@@ -0,0 +1,25 @@
+package org.dromara.demo.domain.dto;
+
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+@Data
+public class KaoshixingRequest {
+
+    @NotBlank(message = "appId不能为空")
+    private String appId;
+
+    @NotBlank(message = "appKey不能为空")
+    private String appKey;
+
+    /** 考试ID */
+    @NotNull(message = "examInfoId不能为空")
+    private Long examInfoId;
+
+    /** 动作ID,默认603 试卷试题列表 */
+    private String actionId = "603";
+
+    /** JWT 过期秒数,默认300秒 */
+    private Integer expSeconds = 300;
+}

+ 21 - 0
ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/domain/dto/KaoshixingScoreListRequest.java

@@ -0,0 +1,21 @@
+package org.dromara.demo.domain.dto;
+
+import jakarta.validation.constraints.Min;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+/**
+ * 考生分数列表请求(action_id=602)。
+ */
+@Data
+public class KaoshixingScoreListRequest {
+
+    /** 考试ID */
+    @NotNull(message = "examInfoId不能为空")
+    private Long examInfoId;
+
+    /** 页码,从1开始 */
+    @NotNull(message = "page不能为空")
+    @Min(value = 1, message = "page必须>=1")
+    private Integer page;
+}

+ 24 - 0
ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/domain/dto/KaoshixingStudentRequest.java

@@ -0,0 +1,24 @@
+package org.dromara.demo.domain.dto;
+
+import jakarta.validation.constraints.Min;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+@Data
+public class KaoshixingStudentRequest {
+
+    @NotBlank(message = "appId不能为空")
+    private String appId;
+
+    @NotBlank(message = "appKey不能为空")
+    private String appKey;
+
+    /** 页码,从1开始 */
+    @NotNull(message = "page不能为空")
+    @Min(value = 1, message = "page必须>=1")
+    private Integer page;
+
+    /** JWT 过期秒数,默认300秒 */
+    private Integer expSeconds = 300;
+}

+ 21 - 0
ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/domain/dto/KaoshixingUserDetailRequest.java

@@ -0,0 +1,21 @@
+package org.dromara.demo.domain.dto;
+
+import jakarta.validation.constraints.NotBlank;
+import lombok.Data;
+
+@Data
+public class KaoshixingUserDetailRequest {
+
+    @NotBlank(message = "appId不能为空")
+    private String appId;
+
+    @NotBlank(message = "appKey不能为空")
+    private String appKey;
+
+    /** 考生账号 user_id */
+    @NotBlank(message = "user_id不能为空")
+    private String userId;
+
+    /** JWT 过期秒数,默认300秒 */
+    private Integer expSeconds = 300;
+}

+ 24 - 0
ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/domain/dto/KaoshixingUserExamListRequest.java

@@ -0,0 +1,24 @@
+package org.dromara.demo.domain.dto;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import jakarta.validation.constraints.Min;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+/**
+ * 获取考生可见考试和考试结果整合列表(action_id=702)。
+ */
+@Data
+public class KaoshixingUserExamListRequest {
+
+    /** 学员账号 */
+    @JsonProperty("user_id")
+    @NotBlank(message = "user_id不能为空")
+    private String userId;
+
+    /** 页码,从1开始,每页500 */
+    @NotNull(message = "page不能为空")
+    @Min(value = 1, message = "page必须>=1")
+    private Integer page;
+}

+ 376 - 0
ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/service/impl/KaoshixingService.java

@@ -0,0 +1,376 @@
+package org.dromara.demo.service.impl;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import io.jsonwebtoken.Jwts;
+import io.jsonwebtoken.SignatureAlgorithm;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.http.HttpEntity;
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.entity.StringEntity;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClients;
+import org.apache.http.util.EntityUtils;
+import org.dromara.demo.domain.dto.*;
+import org.springframework.stereotype.Service;
+
+import java.io.UnsupportedEncodingException;
+import java.nio.charset.StandardCharsets;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 考试星-试卷试题列表接口(action_id=603,完全对齐文档)
+ */
+@Slf4j
+@Service
+public class KaoshixingService {
+
+    // 文档指定的基础接口地址
+    private static final String BASE_API_URL = "https://api.kaoshixing.com/api/company/data/";
+    // 试卷试题列表固定action_id
+    private static final String ACTION_ID_QUESTIONS = "603";
+    // 考试列表 action_id
+    private static final String ACTION_ID_EXAMS = "601";
+    // 考生分数列表 action_id
+    private static final String ACTION_ID_SCORES = "602";
+    // 考生答案列表 action_id
+    private static final String ACTION_ID_ANSWERS = "604";
+    // 考生可见考试+结果列表 action_id
+    private static final String ACTION_ID_USER_EXAMS = "702";
+    // 静默登录 action_id
+    private static final String ACTION_ID_SILENT_LOGIN = "203";
+    // 考生登录(可注册) action_id
+    private static final String ACTION_ID_LOGIN = "201";
+    // 查询考生信息 action_id
+    private static final String ACTION_ID_QUERY_USER = "209";
+    // 文档指定的JWT过期时间(10秒)
+    private static final long EXPIRATION_MILLIS = 10 * 1000L;
+
+    // 固定的 appId / appKey(按需求写死)
+    private static final String FIXED_APP_ID = "631088";
+    private static final String FIXED_APP_KEY = "605f5f51895f423195850957bb903a43";
+
+    private final ObjectMapper objectMapper = new ObjectMapper();
+    /**
+     * 调用试卷试题列表接口
+     * @param examInfoId 考试ID(示例:119551)
+     * @return 试题列表JSON字符串
+     */
+    public String fetchExamPaperQuestions(KaoshixingRequest request) {
+        Long examInfoId = request.getExamInfoId();
+
+        validateParams(FIXED_APP_ID, FIXED_APP_KEY, examInfoId);
+
+        String jwtInfo = generateJwt(FIXED_APP_KEY, ACTION_ID_QUESTIONS);
+        log.info("试卷试题列表接口JWT:{}", jwtInfo);
+
+        String requestUrl = String.format("%s%s/?jwt=%s", BASE_API_URL, FIXED_APP_ID, jwtInfo);
+        log.info("试卷试题列表接口请求地址:{}", requestUrl);
+
+        String requestBody = String.format("{\"examInfoId\":\"%s\"}", examInfoId);
+        log.info("试卷试题列表接口请求体:{}", requestBody);
+
+        return sendPostRequest(requestUrl, requestBody);
+    }
+
+    /**
+     * 考生答案列表(action_id=604)
+     */
+    public String fetchAnswerList(KaoshixingAnswerListRequest req) {
+        validateAnswerListParams(req);
+
+        String jwtInfo = generateJwt(FIXED_APP_KEY, ACTION_ID_ANSWERS);
+        log.info("考生答案列表接口JWT:{}", jwtInfo);
+
+        String requestUrl = String.format("%s%s/?jwt=%s", BASE_API_URL, FIXED_APP_ID, jwtInfo);
+        log.info("考生答案列表接口请求地址:{}", requestUrl);
+
+        String requestBody = buildAnswerListBody(req);
+        log.info("考生答案列表接口请求体:{}", requestBody);
+
+        return sendPostRequest(requestUrl, requestBody);
+    }
+
+    /**
+     * 考生分数列表(action_id=602)
+     */
+    public String fetchScoreList(KaoshixingScoreListRequest req) {
+        validateScoreListParams(req);
+
+        String jwtInfo = generateJwt(FIXED_APP_KEY, ACTION_ID_SCORES);
+        log.info("考生分数列表接口JWT:{}", jwtInfo);
+
+        String requestUrl = String.format("%s%s/?jwt=%s", BASE_API_URL, FIXED_APP_ID, jwtInfo);
+        log.info("考生分数列表接口请求地址:{}", requestUrl);
+
+        String requestBody = buildScoreListBody(req);
+        log.info("考生分数列表接口请求体:{}", requestBody);
+
+        return sendPostRequest(requestUrl, requestBody);
+    }
+
+    /**
+     * 免注册登录:先静默登录(203),失败则注册并登录(201)
+     */
+    public String fetchAutoLogin(KaoshixingAutoLoginRequest req) {
+        // 0) 先查是否存在(action_id=209)
+        if (userExists(req.getUserId())) {
+            // 已存在 -> 静默登录
+            String silentJwt = generateJwt(FIXED_APP_KEY, ACTION_ID_SILENT_LOGIN);
+            String silentUrl = String.format("%s%s/?jwt=%s", BASE_API_URL, FIXED_APP_ID, silentJwt);
+            String silentBody = String.format("{\"user_id\":\"%s\"}", req.getUserId());
+            log.info("静默登录请求体:{}", silentBody);
+            return sendPostRequest(silentUrl, silentBody);
+        }
+
+        // 不存在 -> 注册并登录(action_id=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("考生登录(可注册)请求体:{}", loginBody);
+        return sendPostRequest(loginUrl, loginBody);
+    }
+
+    /**
+     * 获取考生可见考试和考试结果整合列表(action_id=702)
+     */
+    public String fetchUserExamList(KaoshixingUserExamListRequest req) {
+        validateUserExamListParams(req);
+
+        String jwtInfo = generateJwt(FIXED_APP_KEY, ACTION_ID_USER_EXAMS);
+        log.info("考生可见考试列表接口JWT:{}", jwtInfo);
+
+        String requestUrl = String.format("%s%s/?jwt=%s", BASE_API_URL, FIXED_APP_ID, jwtInfo);
+        log.info("考生可见考试列表接口请求地址:{}", requestUrl);
+
+        String requestBody = String.format("{\"user_id\":\"%s\",\"page\":%d}", req.getUserId(), req.getPage());
+        log.info("考生可见考试列表接口请求体:{}", requestBody);
+
+        return sendPostRequest(requestUrl, requestBody);
+    }
+
+    /**
+     * 调用考试信息列表接口(action_id=601,支持分页及时间筛选)
+     */
+    public String fetchExamList(KaoshixingExamListRequest req) {
+        validateExamListParams(req);
+
+        String jwtInfo = generateJwt(FIXED_APP_KEY, ACTION_ID_EXAMS);
+        log.info("考试信息列表接口JWT:{}", jwtInfo);
+
+        String requestUrl = String.format("%s%s/?jwt=%s", BASE_API_URL, FIXED_APP_ID, jwtInfo);
+        log.info("考试信息列表接口请求地址:{}", requestUrl);
+
+        String requestBody = buildExamListBody(req);
+        log.info("考试信息列表接口请求体:{}", requestBody);
+
+        return sendPostRequest(requestUrl, requestBody);
+    }
+
+
+    /**
+     * 生成JWT(完全按文档示例代码实现)
+     */
+    private String generateJwt(String appKey, String actionId) {
+        try {
+            return Jwts.builder()
+                .claim("exp", System.currentTimeMillis() + EXPIRATION_MILLIS) // 文档要求的过期时间
+                .claim("action_id", actionId)
+                .signWith(
+                    SignatureAlgorithm.HS256,
+                    appKey.getBytes(StandardCharsets.UTF_8.toString()) // 文档要求的UTF-8编码
+                )
+                .compact();
+        } catch (UnsupportedEncodingException e) {
+            log.error("生成JWT失败", e);
+            throw new RuntimeException("JWT生成失败:" + e.getMessage());
+        }
+    }
+
+    private boolean userExists(String userId) {
+        try {
+            String queryJwt = generateJwt(FIXED_APP_KEY, ACTION_ID_QUERY_USER);
+            String queryUrl = String.format("%s%s/?jwt=%s", BASE_API_URL, FIXED_APP_ID, queryJwt);
+            String queryBody = String.format("{\"user_id\":\"%s\"}", userId);
+            log.info("查询考生信息请求体:{}", queryBody);
+            String resp = sendPostRequest(queryUrl, queryBody);
+            JsonNode root = objectMapper.readTree(resp);
+            if (root.get("code") != null && root.get("code").asInt() == 10000) {
+                JsonNode rowCount = root.get("rowCount");
+                JsonNode total = root.get("total");
+                int count = 0;
+                if (rowCount != null) {
+                    count = rowCount.asInt(0);
+                } else if (total != null) {
+                    count = total.asInt(0);
+                }
+                return count > 0;
+            }
+        } catch (Exception e) {
+            log.warn("查询考生信息失败,视为不存在", e);
+        }
+        return false;
+    }
+
+
+    /**
+     * 发送POST请求(对齐官方示例的HttpClient)
+     */
+    private String sendPostRequest(String url, String body) {
+        try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
+            HttpPost httpPost = new HttpPost(url);
+            // 文档要求的Content-Type(JSON格式)
+            httpPost.addHeader("Content-Type", "application/json");
+            // 设置请求体(UTF-8编码)
+            httpPost.setEntity(new StringEntity(body, StandardCharsets.UTF_8.toString()));
+
+            // 执行请求
+            try (CloseableHttpResponse response = httpClient.execute(httpPost)) {
+                HttpEntity entity = response.getEntity();
+                String responseContent = EntityUtils.toString(entity, StandardCharsets.UTF_8.toString());
+
+                // 打印响应信息
+                log.info("接口响应码:{}", response.getStatusLine().getStatusCode());
+                log.info("接口响应体:{}", responseContent);
+
+                // 非200响应抛出异常
+                if (response.getStatusLine().getStatusCode() != 200) {
+                    throw new RuntimeException(String.format(
+                        "接口请求失败:状态码=%s,内容=%s",
+                        response.getStatusLine().getStatusCode(),
+                        responseContent
+                    ));
+                }
+                return responseContent;
+            }
+        } catch (Exception e) {
+            log.error("调用试卷试题列表接口失败", e);
+            throw new RuntimeException("接口调用失败:" + e.getMessage());
+        }
+    }
+
+
+    /**
+     * 校验必填参数
+     */
+    private void validateParams(String appId, String appKey, Long examInfoId) {
+        if (isBlank(appId)) {
+            throw new IllegalArgumentException("appId不能为空(来自考试星后台)");
+        }
+        if (isBlank(appKey)) {
+            throw new IllegalArgumentException("appKey不能为空(来自考试星后台)");
+        }
+        if (examInfoId == null) {
+            throw new IllegalArgumentException("examInfoId不能为空(考试ID)");
+        }
+    }
+
+    private void validateExamListParams(KaoshixingExamListRequest req) {
+        if (req.getPage() == null) {
+            throw new IllegalArgumentException("page不能为空");
+        }
+    }
+
+    private void validateScoreListParams(KaoshixingScoreListRequest req) {
+        if (req.getExamInfoId() == null) {
+            throw new IllegalArgumentException("examInfoId不能为空");
+        }
+        if (req.getPage() == null) {
+            throw new IllegalArgumentException("page不能为空");
+        }
+    }
+
+    private void validateAnswerListParams(KaoshixingAnswerListRequest req) {
+        if (req.getExamInfoId() == null) {
+            throw new IllegalArgumentException("examInfoId不能为空");
+        }
+    }
+
+    private void validateUserExamListParams(KaoshixingUserExamListRequest req) {
+        if (isBlank(req.getUserId())) {
+            throw new IllegalArgumentException("user_id不能为空");
+        }
+        if (req.getPage() == null) {
+            throw new IllegalArgumentException("page不能为空");
+        }
+    }
+
+    private String buildLoginBody(KaoshixingAutoLoginRequest req) {
+        try {
+            Map<String, Object> payload = new HashMap<>();
+            payload.put("user_id", req.getUserId());
+            payload.put("user_name", req.getUserName());
+            payload.put("department", req.getDepartment());
+            if (!isBlank(req.getCustomUrl())) {
+                payload.put("custom_url", req.getCustomUrl());
+            }
+            return objectMapper.writeValueAsString(payload);
+        } catch (Exception e) {
+            log.error("构造登录请求体失败", e);
+            throw new RuntimeException("构造请求体失败:" + e.getMessage());
+        }
+    }
+
+    private boolean isSuccessResponse(String resp) {
+        try {
+            JsonNode root = objectMapper.readTree(resp);
+            JsonNode codeNode = root.get("code");
+            return codeNode != null && codeNode.asInt() == 10000;
+        } catch (Exception e) {
+            log.warn("解析响应判断成功标识失败,默认视为失败", e);
+            return false;
+        }
+    }
+
+    private String buildExamListBody(KaoshixingExamListRequest req) {
+        try {
+            Map<String, Object> payload = new HashMap<>();
+            payload.put("page", req.getPage());
+            if (!isBlank(req.getExamStartTime())) {
+                payload.put("examStartTime", req.getExamStartTime());
+            }
+            if (!isBlank(req.getExamEndTime())) {
+                payload.put("examEndTime", req.getExamEndTime());
+            }
+            if (!isBlank(req.getCreateTime())) {
+                payload.put("createTime", req.getCreateTime());
+            }
+            if (!isBlank(req.getExamIds())) {
+                payload.put("examIds", req.getExamIds());
+            }
+            return objectMapper.writeValueAsString(payload);
+        } catch (Exception e) {
+            log.error("构造考试信息列表请求体失败", e);
+            throw new RuntimeException("构造请求体失败:" + e.getMessage());
+        }
+    }
+
+    private String buildScoreListBody(KaoshixingScoreListRequest req) {
+        try {
+            Map<String, Object> payload = new HashMap<>();
+            payload.put("examInfoId", req.getExamInfoId());
+            payload.put("page", req.getPage());
+            return objectMapper.writeValueAsString(payload);
+        } catch (Exception e) {
+            log.error("构造成绩列表请求体失败", e);
+            throw new RuntimeException("构造请求体失败:" + e.getMessage());
+        }
+    }
+
+    private String buildAnswerListBody(KaoshixingAnswerListRequest req) {
+        try {
+            Map<String, Object> payload = new HashMap<>();
+            payload.put("examInfoId", req.getExamInfoId());
+            return objectMapper.writeValueAsString(payload);
+        } catch (Exception e) {
+            log.error("构造答案列表请求体失败", e);
+            throw new RuntimeException("构造请求体失败:" + e.getMessage());
+        }
+    }
+
+    private boolean isBlank(String val) {
+        return val == null || val.trim().isEmpty();
+    }
+}

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

@@ -57,6 +57,16 @@
             <groupId>com.microsoft.sqlserver</groupId>
             <artifactId>mssql-jdbc</artifactId>
         </dependency>
+        <dependency>
+            <groupId>org.dromara</groupId>
+            <artifactId>ruoyi-demo</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.google.api.grpc</groupId>
+            <artifactId>proto-google-common-protos</artifactId>
+            <version>2.62.0</version>
+            <scope>compile</scope>
+        </dependency>
 
     </dependencies>
 

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

@@ -0,0 +1,139 @@
+package org.dromara.main.controller;
+
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import jakarta.validation.constraints.NotEmpty;
+import jakarta.validation.constraints.NotNull;
+import lombok.RequiredArgsConstructor;
+import org.dromara.common.core.domain.R;
+import org.dromara.common.core.validate.AddGroup;
+import org.dromara.common.core.validate.EditGroup;
+import org.dromara.common.log.annotation.Log;
+import org.dromara.common.log.enums.BusinessType;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.common.web.core.BaseController;
+import org.dromara.demo.domain.dto.KaoshixingExamListRequest;
+import org.dromara.demo.service.impl.KaoshixingService;
+import org.dromara.main.domain.bo.MainExamEvaluationBo;
+import org.dromara.main.domain.vo.MainExamEvaluationVo;
+import org.dromara.main.service.IMainExamEvaluationService;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/main/examEvaluation")
+public class MainExamEvaluationController extends BaseController {
+
+    private final IMainExamEvaluationService examEvaluationService;
+    private final KaoshixingService kaoshixingService;
+
+    /**
+     * 查询测评列表
+     */
+    @SaCheckPermission("main:examEvaluation:list")
+    @GetMapping("/list")
+    public TableDataInfo<MainExamEvaluationVo> list(MainExamEvaluationBo bo, PageQuery pageQuery) {
+        return examEvaluationService.queryPageList(bo, pageQuery);
+    }
+
+    /**
+     * 获取测评详细信息
+     */
+    @SaCheckPermission("main:examEvaluation:query")
+    @GetMapping("/{id}")
+    public R<MainExamEvaluationVo> getInfo(@NotNull(message = "主键不能为空") @PathVariable Long id) {
+        return R.ok(examEvaluationService.queryById(id));
+    }
+
+    /**
+     * 新增测评
+     */
+    @SaCheckPermission("main:examEvaluation:add")
+    @Log(title = "测评管理", businessType = BusinessType.INSERT)
+    @PostMapping()
+    public R<Void> add(@Validated(AddGroup.class) @RequestBody MainExamEvaluationBo bo) {
+        return toAjax(examEvaluationService.insertByBo(bo));
+    }
+
+    /**
+     * 修改测评
+     */
+    @SaCheckPermission("main:examEvaluation:edit")
+    @Log(title = "测评管理", businessType = BusinessType.UPDATE)
+    @PutMapping()
+    public R<Void> edit(@Validated(EditGroup.class) @RequestBody MainExamEvaluationBo bo) {
+        return toAjax(examEvaluationService.updateByBo(bo));
+    }
+
+    /**
+     * 删除测评
+     */
+    @SaCheckPermission("main:examEvaluation:remove")
+    @Log(title = "测评管理", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{ids}")
+    public R<Void> remove(@NotEmpty(message = "主键不能为空") @PathVariable Long[] ids) {
+        return toAjax(examEvaluationService.deleteWithValidByIds(Arrays.asList(ids), true));
+    }
+
+    /**
+     * 批量更新状态
+     */
+    @SaCheckPermission("main:examEvaluation:edit")
+    @Log(title = "测评管理", businessType = BusinessType.UPDATE)
+    @PutMapping("/updateStatus")
+    public R<Void> updateStatus(@RequestBody List<Long> ids, @RequestParam String status) {
+        return toAjax(examEvaluationService.updateStatus(ids, status));
+    }
+
+    /**
+     * 同步第三方数据
+     */
+    @SaCheckPermission("main:examEvaluation:sync")
+    @Log(title = "测评管理", businessType = BusinessType.OTHER)
+    @PostMapping("/sync")
+    public R<Void> syncThirdPartyData() {
+        return toAjax(examEvaluationService.syncThirdPartyData());
+    }
+
+
+
+    @SaCheckPermission("main:evaluation:list")
+    @ResponseBody
+    @PostMapping("/exam-list")
+    @Log(title = "获取第三方考试列表", businessType = BusinessType.OTHER)
+    public R<String> getExamList(@RequestBody Map<String, Object> params) {
+        // 写死的第三方凭证
+        String appId = "631088";  // TODO: 后续改为配置
+        String appKey = "605f5f51895f423195850957bb903a43"; // TODO: 后续改为配置
+
+        // 构建请求参数
+        KaoshixingExamListRequest request = new KaoshixingExamListRequest();
+        request.setAppId(appId);
+        request.setAppKey(appKey);
+        request.setPage((Integer) params.getOrDefault("page", 1));
+
+        // 转换日期格式:2026-01-01 -> 2026-01-01 00:00:00
+        String examStartTime = (String) params.get("examStartTime");
+        if (examStartTime != null && !examStartTime.isEmpty()) {
+            request.setExamStartTime(examStartTime + " 00:00:00");
+        }
+
+        String examEndTime = (String) params.get("examEndTime");
+        if (examEndTime != null && !examEndTime.isEmpty()) {
+            request.setExamEndTime(examEndTime + " 23:59:59");
+        }
+
+        request.setExamIds((String) params.get("examIds"));
+        request.setExpSeconds((Integer) params.getOrDefault("expSeconds", 300));
+
+        // 调用第三方接口
+        String result = kaoshixingService.fetchExamList(request);
+        return R.ok(result);
+    }
+}

+ 44 - 0
ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/domain/MainAbilityConfig.java

@@ -0,0 +1,44 @@
+package org.dromara.main.domain;
+
+import com.baomidou.mybatisplus.annotation.*;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.math.BigDecimal;
+import java.util.Date;
+
+@Data
+@TableName("main_ability_config")
+public class MainAbilityConfig implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    @TableId(value = "id", type = IdType.AUTO)
+    private Long id;
+
+    private Long evaluationId;
+    private String abilityName;
+    private Long thirdExamInfoId;
+    private String thirdExamName;
+    private Integer thirdExamTime;
+    private BigDecimal thirdExamPassMark;
+    private BigDecimal thirdExamTotalScore;
+    private Integer sortOrder;
+    private Long createDept;
+    private Long createBy;
+
+    @TableField(fill = FieldFill.INSERT)
+    private Date createTime;
+
+    private Long updateBy;
+
+    @TableField(fill = FieldFill.INSERT_UPDATE)
+    private Date updateTime;
+
+    private String remark;
+
+    @TableLogic
+    private String delFlag;
+}

+ 48 - 0
ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/domain/MainExamEvaluation.java

@@ -0,0 +1,48 @@
+package org.dromara.main.domain;
+
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableLogic;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.dromara.common.tenant.core.TenantEntity;
+
+import java.io.Serial;
+import java.math.BigDecimal;
+import java.util.Date;
+import java.util.List;
+
+@Data
+@EqualsAndHashCode(callSuper = true)
+@TableName("main_exam_evaluation")
+public class MainExamEvaluation extends TenantEntity {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    @TableId(value = "id")
+    private Long id;
+
+    private String evaluationName;
+    private String grade;
+    private String position;
+    private String positionType;
+    private String tags;
+    private Long mainImage;
+    private String imageAlbum;
+    private String detail;
+    private BigDecimal price;
+    private Date onTime;
+    private Date downTime;
+    private String status;
+
+    @TableLogic
+    private String delFlag;
+
+    /**
+     * 能力配置列表(一对多关联,不存储到数据库)
+     */
+    @TableField(exist = false)
+    private List<MainAbilityConfig> abilityConfigs;
+}

+ 55 - 0
ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/domain/MainPosition.java

@@ -0,0 +1,55 @@
+package org.dromara.main.domain;
+
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableLogic;
+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_position")
+public class MainPosition extends BaseEntity {
+
+    private static final long serialVersionUID = 1L;
+
+    @TableId(value = "id")
+    private Long id;
+
+    private String tenantId;
+    private Long deptId;
+    private String postName;
+    private String postDescription;
+    private String workProvince;
+    private String workCity;
+    private String workDistrict;
+    private String workAddress;
+    private String postType;
+    private String educationRequirement;
+    private String salaryType;
+    private String salaryRange;
+    private Integer recruitNum;
+    private Date registrationStartDate;
+    private Date registrationEndDate;
+    private Integer isUrgent;
+    private String schoolRequirement;
+    private String genderRequirement;
+    private String gradeRequirement;
+    private String arrivalTime;
+    private String internshipDuration;
+    private Integer willingToTravel;
+    private String welfareTags;
+    private String jobRequirement;
+    private String postLevel;
+    private String assessmentTime;
+    private BigDecimal gradeA;
+    private BigDecimal gradeB;
+    private BigDecimal gradeC;
+
+    @TableLogic
+    private String delFlag;
+}

+ 51 - 0
ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/domain/bo/MainExamEvaluationBo.java

@@ -0,0 +1,51 @@
+package org.dromara.main.domain.bo;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import io.github.linpeilie.annotations.AutoMapper;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.dromara.common.core.validate.AddGroup;
+import org.dromara.common.core.validate.EditGroup;
+import org.dromara.common.mybatis.core.domain.BaseEntity;
+import org.dromara.main.domain.MainAbilityConfig;
+import org.dromara.main.domain.MainExamEvaluation;
+
+import java.math.BigDecimal;
+import java.util.Date;
+import java.util.List;
+
+@Data
+@EqualsAndHashCode(callSuper = true)
+@AutoMapper(target = MainExamEvaluation.class, reverseConvertGenerate = false)
+public class MainExamEvaluationBo extends BaseEntity {
+
+    @NotNull(message = "测评ID不能为空", groups = { EditGroup.class })
+    private Long id;
+
+    @NotBlank(message = "测评名称不能为空", groups = { AddGroup.class, EditGroup.class })
+    private String evaluationName;
+
+    private String grade;
+    private String position;
+    private String positionType;
+    private String tags;
+    private Long mainImage;
+    private String imageAlbum;
+    private String detail;
+    private BigDecimal price;
+
+    @JsonFormat(pattern = "yyyy-MM-dd")
+    private Date onTime;
+
+    @JsonFormat(pattern = "yyyy-MM-dd")
+    private Date downTime;
+
+    private String status;
+
+    /**
+     * 能力配置列表
+     */
+    private List<MainAbilityConfig> abilityConfigs;
+}

+ 57 - 0
ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/domain/bo/MainPositionBo.java

@@ -0,0 +1,57 @@
+package org.dromara.main.domain.bo;
+
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+import org.dromara.common.core.validate.AddGroup;
+import org.dromara.common.core.validate.EditGroup;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.math.BigDecimal;
+import java.util.Date;
+
+@Data
+public class MainPositionBo implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    @NotNull(message = "岗位ID不能为空", groups = EditGroup.class)
+    private Long id;
+
+    @NotBlank(message = "租户ID不能为空", groups = {AddGroup.class, EditGroup.class})
+    private String tenantId;
+
+    private Long deptId;
+
+    @NotBlank(message = "岗位名称不能为空", groups = {AddGroup.class, EditGroup.class})
+    private String postName;
+
+    private String postDescription;
+    private String workProvince;
+    private String workCity;
+    private String workDistrict;
+    private String workAddress;
+    private String postType;
+    private String educationRequirement;
+    private String salaryType;
+    private String salaryRange;
+    private Integer recruitNum;
+    private Date registrationStartDate;
+    private Date registrationEndDate;
+    private Integer isUrgent;
+    private String schoolRequirement;
+    private String genderRequirement;
+    private String gradeRequirement;
+    private String arrivalTime;
+    private String internshipDuration;
+    private Integer willingToTravel;
+    private String welfareTags;
+    private String jobRequirement;
+    private String postLevel;
+    private String assessmentTime;
+    private BigDecimal gradeA;
+    private BigDecimal gradeB;
+    private BigDecimal gradeC;
+}

+ 96 - 0
ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/domain/vo/MainExamEvaluationVo.java

@@ -0,0 +1,96 @@
+package org.dromara.main.domain.vo;
+
+import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
+import cn.idev.excel.annotation.ExcelProperty;
+import com.baomidou.mybatisplus.annotation.TableLogic;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import io.github.linpeilie.annotations.AutoMapper;
+import lombok.Data;
+import org.dromara.common.excel.annotation.ExcelDictFormat;
+import org.dromara.common.excel.convert.ExcelDictConvert;
+import org.dromara.main.domain.MainAbilityConfig;
+import org.dromara.main.domain.MainExamEvaluation;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.math.BigDecimal;
+import java.util.Date;
+import java.util.List;
+
+@Data
+@ExcelIgnoreUnannotated
+@AutoMapper(target = MainExamEvaluation.class)
+public class MainExamEvaluationVo implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    @ExcelProperty(value = "测评ID")
+    private Long id;
+
+    @ExcelProperty(value = "测评名称")
+    private String evaluationName;
+
+    @ExcelProperty(value = "级别", converter = ExcelDictConvert.class)
+    @ExcelDictFormat(dictType = "main_position_level")
+    private String grade;
+
+    @ExcelProperty(value = "岗位")
+    private String position;
+
+    @ExcelProperty(value = "岗位类型", converter = ExcelDictConvert.class)
+    @ExcelDictFormat(dictType = "main_position_type")
+    private String positionType;
+
+    @ExcelProperty(value = "标签")
+    private String tags;
+
+    @ExcelProperty(value = "主图")
+    private Long mainImage;
+
+    private String mainImageUrl;
+
+    @ExcelProperty(value = "商品相册")
+    private String imageAlbum;
+
+    @ExcelProperty(value = "详情")
+    private String detail;
+
+    @ExcelProperty(value = "价格")
+    private BigDecimal price;
+
+    @ExcelProperty(value = "上架时间")
+    @JsonFormat(pattern = "yyyy-MM-dd")
+    private Date onTime;
+
+    @ExcelProperty(value = "下架时间")
+    @JsonFormat(pattern = "yyyy-MM-dd")
+    private Date downTime;
+
+    @ExcelProperty(value = "状态", converter = ExcelDictConvert.class)
+    @ExcelDictFormat(readConverterExp = "0=草稿,1=已发布,2=已下架")
+    private String status;
+
+    private String tenantId;
+    private Long createDept;
+    private Long createBy;
+
+    @ExcelProperty(value = "创建时间")
+    private Date createTime;
+
+    private Long updateBy;
+
+    @ExcelProperty(value = "更新时间")
+    private Date updateTime;
+
+    @ExcelProperty(value = "备注")
+    private String remark;
+
+    @TableLogic
+    private String delFlag;
+
+    /**
+     * 能力配置列表
+     */
+    private List<MainAbilityConfig> abilityConfigs;
+}

+ 112 - 0
ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/domain/vo/MainPositionVo.java

@@ -0,0 +1,112 @@
+package org.dromara.main.domain.vo;
+
+import cn.idev.excel.annotation.ExcelProperty;
+import io.github.linpeilie.annotations.AutoMapper;
+import lombok.Data;
+import org.dromara.main.domain.MainPosition;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.math.BigDecimal;
+import java.util.Date;
+
+@Data
+@AutoMapper(target = MainPosition.class)
+public class MainPositionVo implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    @ExcelProperty(value = "岗位ID")
+    private Long id;
+
+    @ExcelProperty(value = "租户ID")
+    private String tenantId;
+
+    @ExcelProperty(value = "部门ID")
+    private Long deptId;
+
+    @ExcelProperty(value = "岗位名称")
+    private String postName;
+
+    @ExcelProperty(value = "岗位描述")
+    private String postDescription;
+
+    @ExcelProperty(value = "工作省份")
+    private String workProvince;
+
+    @ExcelProperty(value = "工作城市")
+    private String workCity;
+
+    @ExcelProperty(value = "工作区县")
+    private String workDistrict;
+
+    @ExcelProperty(value = "详细地址")
+    private String workAddress;
+
+    @ExcelProperty(value = "岗位类型")
+    private String postType;
+
+    @ExcelProperty(value = "学历要求")
+    private String educationRequirement;
+
+    @ExcelProperty(value = "薪资类型")
+    private String salaryType;
+
+    @ExcelProperty(value = "薪资范围")
+    private String salaryRange;
+
+    @ExcelProperty(value = "招聘人数")
+    private Integer recruitNum;
+
+    @ExcelProperty(value = "报名开始时间")
+    private Date registrationStartDate;
+
+    @ExcelProperty(value = "报名结束时间")
+    private Date registrationEndDate;
+
+    @ExcelProperty(value = "是否急招")
+    private Integer isUrgent;
+
+    @ExcelProperty(value = "学校要求")
+    private String schoolRequirement;
+
+    @ExcelProperty(value = "性别要求")
+    private String genderRequirement;
+
+    @ExcelProperty(value = "年级要求")
+    private String gradeRequirement;
+
+    @ExcelProperty(value = "到岗时间")
+    private String arrivalTime;
+
+    @ExcelProperty(value = "实习时长")
+    private String internshipDuration;
+
+    @ExcelProperty(value = "是否愿意出差")
+    private Integer willingToTravel;
+
+    @ExcelProperty(value = "福利标签")
+    private String welfareTags;
+
+    @ExcelProperty(value = "岗位要求")
+    private String jobRequirement;
+
+    @ExcelProperty(value = "岗位等级")
+    private String postLevel;
+
+    @ExcelProperty(value = "测评时长")
+    private String assessmentTime;
+
+    @ExcelProperty(value = "能力A及格分")
+    private BigDecimal gradeA;
+
+    @ExcelProperty(value = "能力B及格分")
+    private BigDecimal gradeB;
+
+    @ExcelProperty(value = "能力C及格分")
+    private BigDecimal gradeC;
+
+    @ExcelProperty(value = "创建时间")
+    private Date createTime;
+}

+ 24 - 0
ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/mapper/MainAbilityConfigMapper.java

@@ -0,0 +1,24 @@
+package org.dromara.main.mapper;
+
+import org.apache.ibatis.annotations.Param;
+import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
+import org.dromara.main.domain.MainAbilityConfig;
+
+import java.util.List;
+
+public interface MainAbilityConfigMapper extends BaseMapperPlus<MainAbilityConfig, MainAbilityConfig> {
+    /**
+     * 根据测评ID查询能力配置列表
+     */
+    List<MainAbilityConfig> selectByEvaluationId(@Param("evaluationId") Long evaluationId);
+
+    /**
+     * 根据测评ID删除能力配置
+     */
+    int deleteByEvaluationId(@Param("evaluationId") Long evaluationId);
+
+    /**
+     * 批量插入能力配置
+     */
+    int insertBatch(@Param("configs") List<MainAbilityConfig> configs);
+}

+ 22 - 0
ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/mapper/MainExamEvaluationMapper.java

@@ -0,0 +1,22 @@
+package org.dromara.main.mapper;
+
+import com.baomidou.mybatisplus.core.conditions.Wrapper;
+import com.baomidou.mybatisplus.core.toolkit.Constants;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import org.apache.ibatis.annotations.Param;
+import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
+import org.dromara.main.domain.MainExamEvaluation;
+import org.dromara.main.domain.vo.MainExamEvaluationVo;
+
+public interface MainExamEvaluationMapper extends BaseMapperPlus<MainExamEvaluation, MainExamEvaluationVo> {
+
+    /**
+     * 查询测评详情(包含主图URL和能力配置)
+     */
+    MainExamEvaluationVo selectVoByIdWithImages(@Param("id") Long id);
+
+    /**
+     * 分页查询测评列表(包含主图URL和能力配置)
+     */
+    Page<MainExamEvaluationVo> selectVoPageWithImages(Page<MainExamEvaluationVo> page, @Param(Constants.WRAPPER) Wrapper<MainExamEvaluation> queryWrapper);
+}

+ 10 - 0
ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/mapper/MainPositionMapper.java

@@ -0,0 +1,10 @@
+package org.dromara.main.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
+import org.dromara.main.domain.MainPosition;
+import org.dromara.main.domain.vo.MainPositionVo;
+
+public interface MainPositionMapper extends BaseMapperPlus<MainPosition, MainPositionVo> {
+
+}

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

@@ -0,0 +1,57 @@
+package org.dromara.main.service;
+
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import org.dromara.main.domain.bo.MainExamEvaluationBo;
+import org.dromara.main.domain.vo.MainExamEvaluationVo;
+
+import java.util.Collection;
+import java.util.List;
+
+
+
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+
+
+public interface IMainExamEvaluationService {
+
+    /**
+     * 查询测评详情
+     */
+    MainExamEvaluationVo queryById(Long id);
+
+    /**
+     * 分页查询测评列表
+     */
+    TableDataInfo<MainExamEvaluationVo> queryPageList(MainExamEvaluationBo bo, PageQuery pageQuery);
+
+    /**
+     * 查询测评列表
+     */
+    List<MainExamEvaluationVo> queryList(MainExamEvaluationBo bo);
+
+    /**
+     * 新增测评
+     */
+    Boolean insertByBo(MainExamEvaluationBo bo);
+
+    /**
+     * 修改测评
+     */
+    Boolean updateByBo(MainExamEvaluationBo bo);
+
+    /**
+     * 删除测评
+     */
+    Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
+
+    /**
+     * 批量更新状态
+     */
+    Boolean updateStatus(List<Long> ids, String status);
+
+    /**
+     * 同步第三方数据
+     */
+    Boolean syncThirdPartyData();
+}

+ 31 - 0
ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/service/IMainPositionService.java

@@ -0,0 +1,31 @@
+package org.dromara.main.service;
+
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.extension.service.IService;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.main.domain.MainPosition;
+import org.dromara.main.domain.bo.MainPositionBo;
+import org.dromara.main.domain.vo.MainPositionVo;
+
+public interface IMainPositionService extends IService<MainPosition> {
+
+    /**
+     * 查询岗位分页列表
+     */
+    Page<MainPositionVo> queryPageList(MainPositionBo bo, PageQuery pageQuery);
+
+    /**
+     * 查询岗位详情
+     */
+    MainPositionVo queryById(Long id);
+
+    /**
+     * 新增岗位
+     */
+    Boolean insertPosition(MainPositionBo bo);
+
+    /**
+     * 修改岗位
+     */
+    Boolean updatePosition(MainPositionBo bo);
+}

+ 41 - 17
ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/service/impl/MainAuditServiceImpl.java

@@ -15,6 +15,7 @@ import org.dromara.common.mybatis.core.page.TableDataInfo;
 import org.dromara.common.satoken.utils.LoginHelper;
 import org.dromara.main.domain.MainAudit;
 import org.dromara.main.domain.MainCompanyApply;
+import org.dromara.main.domain.MainPosition;
 import org.dromara.main.domain.MainPostApply;
 import org.dromara.main.domain.bo.MainAuditBo;
 import org.dromara.main.domain.vo.MainAuditVo;
@@ -22,6 +23,7 @@ import org.dromara.main.mapper.MainAuditMapper;
 import org.dromara.main.mapper.MainCompanyApplyMapper;
 import org.dromara.main.mapper.MainPostApplyMapper;
 import org.dromara.main.service.IMainAuditService;
+import org.dromara.main.service.IMainPositionService;
 import org.dromara.system.domain.bo.SysPostBo;
 import org.dromara.system.domain.bo.SysTenantBo;
 import org.dromara.system.service.ISysPostService;
@@ -44,6 +46,7 @@ public class MainAuditServiceImpl implements IMainAuditService {
     private final MainPostApplyMapper postApplyMapper;
     private final ISysTenantService tenantService;
     private final ISysPostService sysPostService;
+    private final IMainPositionService mainPositionService;
     @Override
     public TableDataInfo<MainAuditVo> queryPageList(MainAuditBo bo, PageQuery pageQuery) {
         LambdaQueryWrapper<MainAudit> lqw = buildQueryWrapper(bo);
@@ -130,7 +133,7 @@ public class MainAuditServiceImpl implements IMainAuditService {
                 syncCompanyToTenant(audit.getTargetId());
             } else if (audit.getAuditType() == 2) {
                 // 岗位审核通过 - 保存到 main_post_apply 表(标记为已通过)
-                syncPostApplyToSysPost(audit.getTargetId());
+                syncPostApplyToMainPosition(audit.getTargetId());
             }
         } catch (Exception e) {
             log.error("审核通过后数据同步失败", e);
@@ -192,7 +195,7 @@ public class MainAuditServiceImpl implements IMainAuditService {
     /**
      * 将岗位申请数据同步到 sys_post 表
      */
-    private void syncPostApplyToSysPost(Long applyId) {
+    private void syncPostApplyToMainPosition(Long applyId) {
         // 1. 查询岗位申请信息
         MainPostApply postApply = postApplyMapper.selectById(applyId);
         if (postApply == null) {
@@ -204,24 +207,45 @@ public class MainAuditServiceImpl implements IMainAuditService {
             return; // 已经有岗位ID,说明已同步过
         }
 
-        // 3. 构建岗位对象
-        SysPostBo postBo = new SysPostBo();
-        postBo.setPostName(postApply.getPostName());
-        postBo.setPostCode(postApply.getApplyNo()); // 使用申请编号作为岗位编码
-        postBo.setPostCategory("1"); // 可根据需要设置岗位类别
-        postBo.setPostSort(0); // 默认排序
-        postBo.setStatus("0"); // 正常状态
-        postBo.setRemark(postApply.getPostDescription()); // 将岗位描述作为备注
-        postBo.setTenantId(postApply.getTenantId()); // 设置租户ID
-        postBo.setDeptId(100L);   //默认部门ID
-        // 4. 创建岗位
-        int result = sysPostService.insertPost(postBo);
-        if (result <= 0) {
+        // 3. 构建 MainPosition 对象,从 MainPostApply 复制数据
+        MainPosition position = new MainPosition();
+        position.setTenantId(postApply.getTenantId());
+        position.setPostName(postApply.getPostName());
+        position.setPostDescription(postApply.getPostDescription());
+        position.setWorkProvince(postApply.getWorkProvince());
+        position.setWorkCity(postApply.getWorkCity());
+        position.setWorkDistrict(postApply.getWorkDistrict());
+        position.setWorkAddress(postApply.getWorkAddress());
+        position.setPostType(postApply.getPostType());
+        position.setEducationRequirement(postApply.getEducationRequirement());
+        position.setSalaryType(postApply.getSalaryType());
+        position.setSalaryRange(postApply.getSalaryRange());
+        position.setRecruitNum(postApply.getRecruitNum());
+        position.setRegistrationStartDate(postApply.getRegistrationStartDate());
+        position.setRegistrationEndDate(postApply.getRegistrationEndDate());
+        position.setIsUrgent(postApply.getIsUrgent());
+        position.setSchoolRequirement(postApply.getSchoolRequirement());
+        position.setGenderRequirement(postApply.getGenderRequirement());
+        position.setGradeRequirement(postApply.getGradeRequirement());
+        position.setArrivalTime(postApply.getArrivalTime());
+        position.setInternshipDuration(postApply.getInternshipDuration());
+        position.setWillingToTravel(postApply.getWillingToTravel());
+        position.setWelfareTags(postApply.getWelfareTags());
+        position.setJobRequirement(postApply.getJobRequirement());
+        position.setPostLevel(postApply.getPostLevel());
+        position.setAssessmentTime(postApply.getAssessmentTime());
+        position.setGradeA(postApply.getGradeA());
+        position.setGradeB(postApply.getGradeB());
+        position.setGradeC(postApply.getGradeC());
+
+        // 4. 插入到 main_position 表
+        boolean success = mainPositionService.save(position);
+        if (!success) {
             throw new ServiceException("创建岗位失败");
         }
 
-        // 5. 更新岗位申请表的 post_id 和 apply_status
-        postApply.setPostId(postBo.getPostId()); // 获取新创建的岗位ID
+        // 5. 更新 main_post_apply 表
+        postApply.setPostId(position.getId()); // 保存新创建的岗位ID
         postApply.setApplyStatus(2); // 已通过
         postApplyMapper.updateById(postApply);
     }

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

@@ -0,0 +1,170 @@
+package org.dromara.main.service.impl;
+
+import cn.hutool.core.bean.BeanUtil;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.main.domain.MainAbilityConfig;
+import org.dromara.main.domain.MainExamEvaluation;
+import org.dromara.main.domain.bo.MainExamEvaluationBo;
+import org.dromara.main.domain.vo.MainExamEvaluationVo;
+import org.dromara.main.mapper.MainAbilityConfigMapper;
+import org.dromara.main.mapper.MainExamEvaluationMapper;
+import org.dromara.main.service.IMainExamEvaluationService;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.nio.charset.StandardCharsets;
+import java.util.Base64;
+import java.util.Collection;
+import java.util.List;
+
+@Slf4j
+@RequiredArgsConstructor
+@Service
+public class MainExamEvaluationServiceImpl implements IMainExamEvaluationService {
+
+    private final MainExamEvaluationMapper baseMapper;
+    private final MainAbilityConfigMapper mainAbilityConfigMapper;
+
+    @Override
+    public MainExamEvaluationVo queryById(Long id) {
+        MainExamEvaluationVo vo = baseMapper.selectVoByIdWithImages(id);
+        if (vo != null) {
+            encodeDetailFieldInVo(vo);
+        }
+        return vo;
+    }
+
+    @Override
+    public TableDataInfo<MainExamEvaluationVo> queryPageList(MainExamEvaluationBo bo, PageQuery pageQuery) {
+        LambdaQueryWrapper<MainExamEvaluation> lqw = buildQueryWrapper(bo);
+        Page<MainExamEvaluationVo> result = baseMapper.selectVoPageWithImages(pageQuery.build(), lqw);
+        result.getRecords().forEach(this::encodeDetailFieldInVo);
+        return TableDataInfo.build(result);
+    }
+
+    @Override
+    public List<MainExamEvaluationVo> queryList(MainExamEvaluationBo bo) {
+        LambdaQueryWrapper<MainExamEvaluation> lqw = buildQueryWrapper(bo);
+        List<MainExamEvaluationVo> list = baseMapper.selectVoList(lqw);
+        list.forEach(this::encodeDetailFieldInVo);
+        return list;
+    }
+
+    private LambdaQueryWrapper<MainExamEvaluation> buildQueryWrapper(MainExamEvaluationBo bo) {
+        LambdaQueryWrapper<MainExamEvaluation> lqw = Wrappers.lambdaQuery();
+        lqw.like(StringUtils.isNotBlank(bo.getEvaluationName()), MainExamEvaluation::getEvaluationName, bo.getEvaluationName());
+        lqw.eq(StringUtils.isNotBlank(bo.getGrade()), MainExamEvaluation::getGrade, bo.getGrade());
+        lqw.eq(StringUtils.isNotBlank(bo.getPosition()), MainExamEvaluation::getPosition, bo.getPosition());
+        lqw.eq(StringUtils.isNotBlank(bo.getPositionType()), MainExamEvaluation::getPositionType, bo.getPositionType());
+        lqw.eq(StringUtils.isNotBlank(bo.getStatus()), MainExamEvaluation::getStatus, bo.getStatus());
+        lqw.orderByDesc(MainExamEvaluation::getCreateTime);
+        return lqw;
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public Boolean insertByBo(MainExamEvaluationBo bo) {
+        decodeDetailFieldInBo(bo);
+        MainExamEvaluation add = BeanUtil.toBean(bo, MainExamEvaluation.class);
+        validEntityBeforeSave(add);
+
+        boolean flag = baseMapper.insert(add) > 0;
+        if (flag) {
+            bo.setId(add.getId());
+            saveMainAbilityConfigs(add.getId(), bo.getAbilityConfigs());
+        }
+        return flag;
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public Boolean updateByBo(MainExamEvaluationBo bo) {
+        decodeDetailFieldInBo(bo);
+        MainExamEvaluation update = BeanUtil.toBean(bo, MainExamEvaluation.class);
+        validEntityBeforeSave(update);
+
+        boolean flag = baseMapper.updateById(update) > 0;
+        if (flag) {
+            mainAbilityConfigMapper.deleteByEvaluationId(bo.getId());
+            saveMainAbilityConfigs(bo.getId(), bo.getAbilityConfigs());
+        }
+        return flag;
+    }
+
+    private void saveMainAbilityConfigs(Long evaluationId, List<MainAbilityConfig> configs) {
+        if (!CollectionUtils.isEmpty(configs)) {
+            for (int i = 0; i < configs.size(); i++) {
+                MainAbilityConfig config = configs.get(i);
+                config.setEvaluationId(evaluationId);
+                config.setSortOrder(i);
+            }
+            mainAbilityConfigMapper.insertBatch(configs);
+        }
+    }
+
+    private void validEntityBeforeSave(MainExamEvaluation entity) {
+        // TODO 做一些数据校验,如唯一约束
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
+        if (isValid) {
+            // TODO 做一些业务上的校验
+        }
+        ids.forEach(mainAbilityConfigMapper::deleteByEvaluationId);
+        return baseMapper.deleteByIds(ids) > 0;
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public Boolean updateStatus(List<Long> ids, String status) {
+        if (CollectionUtils.isEmpty(ids) || StringUtils.isBlank(status)) {
+            return false;
+        }
+        for (Long id : ids) {
+            MainExamEvaluation entity = new MainExamEvaluation();
+            entity.setId(id);
+            entity.setStatus(status);
+            baseMapper.updateById(entity);
+        }
+        return true;
+    }
+
+    @Override
+    public Boolean syncThirdPartyData() {
+        log.info("开始同步第三方测评数据");
+        return true;
+    }
+
+    private void decodeDetailFieldInBo(MainExamEvaluationBo bo) {
+        if (StringUtils.isNotBlank(bo.getDetail())) {
+            try {
+                String decoded = new String(Base64.getDecoder().decode(bo.getDetail()), StandardCharsets.UTF_8);
+                bo.setDetail(decoded);
+            } catch (Exception e) {
+                log.warn("详情字段Base64解码失败,使用原始值", e);
+            }
+        }
+    }
+
+    private void encodeDetailFieldInVo(MainExamEvaluationVo vo) {
+        if (StringUtils.isNotBlank(vo.getDetail())) {
+            try {
+                String encoded = Base64.getEncoder().encodeToString(vo.getDetail().getBytes(StandardCharsets.UTF_8));
+                vo.setDetail(encoded);
+            } catch (Exception e) {
+                log.warn("详情字段Base64编码失败,使用原始值", e);
+            }
+        }
+    }
+}

+ 88 - 0
ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/service/impl/MainPositionServiceImpl.java

@@ -0,0 +1,88 @@
+package org.dromara.main.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import lombok.RequiredArgsConstructor;
+import org.dromara.common.core.utils.MapstructUtils;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.main.domain.MainPosition;
+import org.dromara.main.domain.bo.MainPositionBo;
+import org.dromara.main.domain.vo.MainPositionVo;
+import org.dromara.main.mapper.MainPositionMapper;
+import org.dromara.main.service.IMainPositionService;
+import org.springframework.stereotype.Service;
+
+/**
+ * 岗位Service业务层处理
+ */
+@RequiredArgsConstructor
+@Service
+public class MainPositionServiceImpl extends ServiceImpl<MainPositionMapper, MainPosition> implements IMainPositionService {
+
+    private final MainPositionMapper baseMapper;
+
+    /**
+     * 查询岗位分页列表
+     */
+    @Override
+    public Page<MainPositionVo> queryPageList(MainPositionBo bo, PageQuery pageQuery) {
+        LambdaQueryWrapper<MainPosition> lqw = buildQueryWrapper(bo);
+        Page<MainPositionVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
+        return result;
+    }
+
+    /**
+     * 查询岗位详情
+     */
+    @Override
+    public MainPositionVo queryById(Long id) {
+        MainPosition position = baseMapper.selectById(id);
+        return MapstructUtils.convert(position, MainPositionVo.class);
+    }
+
+    /**
+     * 新增岗位
+     */
+    @Override
+    public Boolean insertPosition(MainPositionBo bo) {
+        MainPosition position = MapstructUtils.convert(bo, MainPosition.class);
+        validEntityBeforeSave(position);
+        return baseMapper.insert(position) > 0;
+    }
+
+    /**
+     * 修改岗位
+     */
+    @Override
+    public Boolean updatePosition(MainPositionBo bo) {
+        MainPosition position = MapstructUtils.convert(bo, MainPosition.class);
+        validEntityBeforeSave(position);
+        return baseMapper.updateById(position) > 0;
+    }
+
+    /**
+     * 构建查询条件
+     */
+    private LambdaQueryWrapper<MainPosition> buildQueryWrapper(MainPositionBo bo) {
+        LambdaQueryWrapper<MainPosition> lqw = Wrappers.lambdaQuery();
+        lqw.eq(StringUtils.isNotBlank(bo.getTenantId()), MainPosition::getTenantId, bo.getTenantId());
+        lqw.like(StringUtils.isNotBlank(bo.getPostName()), MainPosition::getPostName, bo.getPostName());
+        lqw.eq(StringUtils.isNotBlank(bo.getPostType()), MainPosition::getPostType, bo.getPostType());
+        lqw.eq(StringUtils.isNotBlank(bo.getWorkProvince()), MainPosition::getWorkProvince, bo.getWorkProvince());
+        lqw.eq(StringUtils.isNotBlank(bo.getWorkCity()), MainPosition::getWorkCity, bo.getWorkCity());
+        lqw.eq(StringUtils.isNotBlank(bo.getPostLevel()), MainPosition::getPostLevel, bo.getPostLevel());
+        lqw.eq(bo.getIsUrgent() != null, MainPosition::getIsUrgent, bo.getIsUrgent());
+        lqw.orderByDesc(MainPosition::getCreateTime);
+        return lqw;
+    }
+
+    /**
+     * 保存前的数据校验
+     */
+    private void validEntityBeforeSave(MainPosition entity) {
+        // 可以添加业务校验逻辑
+    }
+}

+ 50 - 0
ruoyi-modules/ruoyi-main/src/main/resources/mapper/MainAbilityConfigMapper.xml

@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+    PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+    "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="org.dromara.main.mapper.MainAbilityConfigMapper">
+
+    <resultMap type="org.dromara.main.domain.MainAbilityConfig" id="MainAbilityConfigResult">
+        <result property="id" column="id"/>
+        <result property="evaluationId" column="evaluation_id"/>
+        <result property="abilityName" column="ability_name"/>
+        <result property="thirdExamInfoId" column="third_exam_info_id"/>
+        <result property="thirdExamName" column="third_exam_name"/>
+        <result property="thirdExamTime" column="third_exam_time"/>
+        <result property="thirdExamPassMark" column="third_exam_pass_mark"/>
+        <result property="thirdExamTotalScore" column="third_exam_total_score"/>
+        <result property="sortOrder" column="sort_order"/>
+        <result property="createDept" column="create_dept"/>
+        <result property="createBy" column="create_by"/>
+        <result property="createTime" column="create_time"/>
+        <result property="updateBy" column="update_by"/>
+        <result property="updateTime" column="update_time"/>
+        <result property="remark" column="remark"/>
+        <result property="delFlag" column="del_flag"/>
+    </resultMap>
+
+    <select id="selectByEvaluationId" resultMap="MainAbilityConfigResult">
+        SELECT * FROM main_ability_config
+        WHERE evaluation_id = #{evaluationId} AND del_flag = '0'
+        ORDER BY sort_order ASC, id ASC
+    </select>
+
+    <delete id="deleteByEvaluationId">
+        UPDATE main_ability_config SET del_flag = '1' WHERE evaluation_id = #{evaluationId}
+    </delete>
+
+    <insert id="insertBatch" parameterType="java.util.List">
+        INSERT INTO main_ability_config (
+        evaluation_id, ability_name, third_exam_info_id, third_exam_name,
+        third_exam_time, third_exam_pass_mark, third_exam_total_score,
+        sort_order, create_dept, create_by, create_time, del_flag
+        ) VALUES
+        <foreach collection="configs" item="item" separator=",">
+            (#{item.evaluationId}, #{item.abilityName}, #{item.thirdExamInfoId},
+            #{item.thirdExamName}, #{item.thirdExamTime}, #{item.thirdExamPassMark},
+            #{item.thirdExamTotalScore}, #{item.sortOrder}, #{item.createDept},
+            #{item.createBy}, NOW(), '0')
+        </foreach>
+    </insert>
+
+</mapper>

+ 61 - 0
ruoyi-modules/ruoyi-main/src/main/resources/mapper/MainExamEvaluationMapper.xml

@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+    PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+    "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="org.dromara.main.mapper.MainExamEvaluationMapper">
+
+    <resultMap type="org.dromara.main.domain.MainExamEvaluation" id="MainExamEvaluationResult">
+        <result property="id" column="id"/>
+        <result property="evaluationName" column="evaluation_name"/>
+        <result property="grade" column="grade"/>
+        <result property="position" column="position"/>
+        <result property="positionType" column="position_type"/>
+        <result property="tags" column="tags"/>
+        <result property="mainImage" column="main_image"/>
+        <result property="imageAlbum" column="image_album"/>
+        <result property="detail" column="detail"/>
+        <result property="price" column="price"/>
+        <result property="onTime" column="on_time"/>
+        <result property="downTime" column="down_time"/>
+        <result property="status" column="status"/>
+        <result property="tenantId" column="tenant_id"/>
+        <result property="createDept" column="create_dept"/>
+        <result property="createBy" column="create_by"/>
+        <result property="createTime" column="create_time"/>
+        <result property="updateBy" column="update_by"/>
+        <result property="updateTime" column="update_time"/>
+        <result property="remark" column="remark"/>
+        <result property="delFlag" column="del_flag"/>
+    </resultMap>
+
+    <resultMap type="org.dromara.main.domain.vo.MainExamEvaluationVo" id="MainExamEvaluationVoResult" extends="MainExamEvaluationResult">
+        <result property="mainImageUrl" column="main_image_url"/>
+        <collection property="abilityConfigs"
+                    ofType="org.dromara.main.domain.MainAbilityConfig"
+                    select="org.dromara.main.mapper.MainAbilityConfigMapper.selectByEvaluationId"
+                    column="id"/>
+    </resultMap>
+
+    <sql id="selectMainExamEvaluationVo">
+        SELECT
+            e.id, e.evaluation_name, e.grade, e.position, e.position_type,
+            e.tags, e.main_image, e.image_album, e.detail, e.price,
+            e.on_time, e.down_time, e.status,
+            e.tenant_id, e.create_dept, e.create_by, e.create_time,
+            e.update_by, e.update_time, e.remark, e.del_flag,
+            oss.url AS main_image_url
+        FROM main_exam_evaluation e
+                 LEFT JOIN sys_oss oss ON e.main_image = oss.oss_id
+    </sql>
+
+    <select id="selectVoByIdWithImages" resultMap="MainExamEvaluationVoResult">
+        <include refid="selectMainExamEvaluationVo"/>
+        WHERE e.id = #{id} AND e.del_flag = '0'
+    </select>
+
+    <select id="selectVoPageWithImages" resultMap="MainExamEvaluationVoResult">
+        <include refid="selectMainExamEvaluationVo"/>
+        ${ew.customSqlSegment}
+    </select>
+
+</mapper>

+ 42 - 0
script/sql/main.sql

@@ -280,3 +280,45 @@ CREATE TABLE `sys_tag` (
   `tenant_id` varchar(20) DEFAULT '000000' COMMENT '租户编号',
   PRIMARY KEY (`id`) USING BTREE
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci ROW_FORMAT=DYNAMIC COMMENT='标签定义表';
+
+
+
+CREATE TABLE `main_position` (
+  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '岗位ID',
+  `tenant_id` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '租户ID',
+  `post_name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '岗位名称',
+  `post_description` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci COMMENT '岗位描述',
+  `work_province` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '工作省份',
+  `work_city` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '工作城市',
+  `work_district` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '工作区县',
+  `work_address` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '详细地址',
+  `post_type` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '岗位类型(字典:main_position_type)',
+  `education_requirement` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '学历要求',
+  `salary_type` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '薪资类型',
+  `salary_range` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '薪资范围',
+  `recruit_num` int NOT NULL COMMENT '招聘人数',
+  `registration_start_date` date DEFAULT NULL COMMENT '报名开始时间',
+  `registration_end_date` date DEFAULT NULL COMMENT '报名结束时间',
+  `is_urgent` tinyint(1) DEFAULT '0' COMMENT '是否急招(0-否 1-是)',
+  `school_requirement` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '学校要求(字典:main_education)',
+  `gender_requirement` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '性别要求(字典:sys_user_sex)',
+  `grade_requirement` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '年级要求(字典:main_experience)',
+  `arrival_time` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '到岗时间(字典:main_arrival_time)',
+  `internship_duration` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '实习时长(字典:main_internship_duration)',
+  `willing_to_travel` tinyint(1) DEFAULT '0' COMMENT '是否愿意出差(0-否 1-是)',
+  `welfare_tags` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '福利标签',
+  `job_requirement` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci COMMENT '岗位要求',
+  `post_level` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '岗位等级(字典:(main_position_level))',
+  `assessment_time` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '测评时长',
+  `grade_a` decimal(10,2) DEFAULT NULL COMMENT '能力A及格分',
+  `grade_b` decimal(10,2) DEFAULT NULL COMMENT '能力B及格分',
+  `grade_c` decimal(10,2) DEFAULT NULL COMMENT '能力C及格分',
+  `create_dept` bigint DEFAULT NULL COMMENT '创建部门',
+  `create_by` bigint DEFAULT NULL COMMENT '创建者',
+  `create_time` datetime DEFAULT NULL COMMENT '创建时间',
+  `update_by` bigint DEFAULT NULL COMMENT '更新者',
+  `update_time` datetime DEFAULT NULL COMMENT '更新时间',
+  `del_flag` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT '0' COMMENT '删除标志',
+  PRIMARY KEY (`id`) USING BTREE,
+  KEY `idx_tenant_id` (`tenant_id`) USING BTREE
+) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='岗位表(这是要发布出去的,与sys_post不同)';

+ 0 - 0
script/sql/新建 文本文档.txt