This commit is contained in:
commit
5224cfb1a7
|
@ -0,0 +1,8 @@
|
|||
# 默认忽略的文件
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# 基于编辑器的 HTTP 客户端请求
|
||||
/httpRequests/
|
||||
# Datasource local storage ignored files
|
||||
/dataSources/
|
||||
/dataSources.local.xml
|
|
@ -0,0 +1,19 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="CompilerConfiguration">
|
||||
<annotationProcessing>
|
||||
<profile default="true" name="Default" enabled="true" />
|
||||
<profile name="Maven default annotation processors profile" enabled="true">
|
||||
<sourceOutputDir name="target/generated-sources/annotations" />
|
||||
<sourceTestOutputDir name="target/generated-test-sources/test-annotations" />
|
||||
<outputRelativeToContentRoot value="true" />
|
||||
<module name="test" />
|
||||
</profile>
|
||||
</annotationProcessing>
|
||||
</component>
|
||||
<component name="JavacSettings">
|
||||
<option name="ADDITIONAL_OPTIONS_OVERRIDE">
|
||||
<module name="test" options="-parameters" />
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
|
@ -0,0 +1,17 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="DataSourceManagerImpl" format="xml" multifile-model="true">
|
||||
<data-source source="LOCAL" name="mbti@62.234.217.137" uuid="9f99ef1b-6137-4d81-9ba5-db10e4ed455c">
|
||||
<driver-ref>mysql.8</driver-ref>
|
||||
<synchronize>true</synchronize>
|
||||
<jdbc-driver>com.mysql.cj.jdbc.Driver</jdbc-driver>
|
||||
<jdbc-url>jdbc:mysql://62.234.217.137:3306/mbti</jdbc-url>
|
||||
<jdbc-additional-properties>
|
||||
<property name="com.intellij.clouds.kubernetes.db.host.port" />
|
||||
<property name="com.intellij.clouds.kubernetes.db.enabled" value="false" />
|
||||
<property name="com.intellij.clouds.kubernetes.db.container.port" />
|
||||
</jdbc-additional-properties>
|
||||
<working-dir>$ProjectFileDir$</working-dir>
|
||||
</data-source>
|
||||
</component>
|
||||
</project>
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="Encoding">
|
||||
<file url="file://$PROJECT_DIR$/src/main/java" charset="UTF-8" />
|
||||
</component>
|
||||
</project>
|
|
@ -0,0 +1,20 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="RemoteRepositoriesConfiguration">
|
||||
<remote-repository>
|
||||
<option name="id" value="central" />
|
||||
<option name="name" value="Maven Central repository" />
|
||||
<option name="url" value="https://repo1.maven.org/maven2" />
|
||||
</remote-repository>
|
||||
<remote-repository>
|
||||
<option name="id" value="jboss.community" />
|
||||
<option name="name" value="JBoss Community repository" />
|
||||
<option name="url" value="https://repository.jboss.org/nexus/content/repositories/public/" />
|
||||
</remote-repository>
|
||||
<remote-repository>
|
||||
<option name="id" value="central" />
|
||||
<option name="name" value="Central Repository" />
|
||||
<option name="url" value="https://maven.aliyun.com/repository/public" />
|
||||
</remote-repository>
|
||||
</component>
|
||||
</project>
|
|
@ -0,0 +1,14 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
||||
<component name="MavenProjectsManager">
|
||||
<option name="originalFiles">
|
||||
<list>
|
||||
<option value="$PROJECT_DIR$/pom.xml" />
|
||||
</list>
|
||||
</option>
|
||||
</component>
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="zulu-17" project-jdk-type="JavaSDK">
|
||||
<output url="file://$PROJECT_DIR$/out" />
|
||||
</component>
|
||||
</project>
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
|
@ -0,0 +1,19 @@
|
|||
# Licensed to the Apache Software Foundation (ASF) under one
|
||||
# or more contributor license agreements. See the NOTICE file
|
||||
# distributed with this work for additional information
|
||||
# regarding copyright ownership. The ASF licenses this file
|
||||
# to you under the Apache License, Version 2.0 (the
|
||||
# "License"); you may not use this file except in compliance
|
||||
# with the License. You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing,
|
||||
# software distributed under the License is distributed on an
|
||||
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
# KIND, either express or implied. See the License for the
|
||||
# specific language governing permissions and limitations
|
||||
# under the License.
|
||||
wrapperVersion=3.3.2
|
||||
distributionType=only-script
|
||||
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.9/apache-maven-3.9.9-bin.zip
|
|
@ -0,0 +1,77 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>3.4.4</version>
|
||||
<relativePath/> <!-- lookup parent from repository -->
|
||||
</parent>
|
||||
<groupId>com.yang</groupId>
|
||||
<artifactId>test</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
<name>test</name>
|
||||
<description>test</description>
|
||||
<url/>
|
||||
<licenses>
|
||||
<license/>
|
||||
</licenses>
|
||||
<developers>
|
||||
<developer/>
|
||||
</developers>
|
||||
<scm>
|
||||
<connection/>
|
||||
<developerConnection/>
|
||||
<tag/>
|
||||
<url/>
|
||||
</scm>
|
||||
<properties>
|
||||
<java.version>17</java.version>
|
||||
</properties>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mybatis.spring.boot</groupId>
|
||||
<artifactId>mybatis-spring-boot-starter</artifactId>
|
||||
<version>3.0.4</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.mysql</groupId>
|
||||
<artifactId>mysql-connector-j</artifactId>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mybatis.spring.boot</groupId>
|
||||
<artifactId>mybatis-spring-boot-starter-test</artifactId>
|
||||
<version>3.0.4</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<version>1.18.26</version> <!-- 请根据需要选择最新版本 -->
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
|
@ -0,0 +1,20 @@
|
|||
package com.yang.test;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.core.env.Environment;
|
||||
|
||||
@Slf4j
|
||||
@SpringBootApplication
|
||||
public class TestApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
ConfigurableApplicationContext context = SpringApplication.run(TestApplication.class, args);
|
||||
Environment env = context.getEnvironment();
|
||||
String port = env.getProperty("server.port", "8080");
|
||||
log.info("MBTI测试系统启动成功! 服务运行在端口: {}", port);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
package com.yang.test.config;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.servlet.config.annotation.CorsRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
|
||||
@Configuration
|
||||
public class CorsConfig implements WebMvcConfigurer {
|
||||
|
||||
@Value("${cors.allowed-origins}")
|
||||
private String allowedOrigins;
|
||||
|
||||
@Override
|
||||
public void addCorsMappings(CorsRegistry registry) {
|
||||
registry.addMapping("/api/**")
|
||||
.allowedOrigins(allowedOrigins.split(","))
|
||||
.allowedMethods("GET", "POST", "OPTIONS")
|
||||
.allowedHeaders("Content-Type")
|
||||
.allowCredentials(true)
|
||||
.maxAge(3600);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
package com.yang.test.config;
|
||||
|
||||
import com.zaxxer.hikari.HikariConfig;
|
||||
import com.zaxxer.hikari.HikariDataSource;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
@Configuration
|
||||
public class DatabaseConfig {
|
||||
|
||||
@Value("${spring.datasource.url}")
|
||||
private String jdbcUrl;
|
||||
|
||||
@Value("${spring.datasource.username}")
|
||||
private String username;
|
||||
|
||||
@Value("${spring.datasource.password}")
|
||||
private String password;
|
||||
|
||||
@Value("${spring.datasource.driver-class-name}")
|
||||
private String driverClassName;
|
||||
|
||||
@Bean
|
||||
public DataSource dataSource() {
|
||||
HikariConfig config = new HikariConfig();
|
||||
config.setJdbcUrl(jdbcUrl);
|
||||
config.setUsername(username);
|
||||
config.setPassword(password);
|
||||
config.setDriverClassName(driverClassName);
|
||||
config.setMaximumPoolSize(10);
|
||||
config.setMinimumIdle(5);
|
||||
config.setConnectionTimeout(30000);
|
||||
config.setIdleTimeout(600000);
|
||||
config.setMaxLifetime(1800000);
|
||||
return new HikariDataSource(config);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public JdbcTemplate jdbcTemplate(DataSource dataSource) {
|
||||
return new JdbcTemplate(dataSource);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
package com.yang.test.config;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.http.client.SimpleClientHttpRequestFactory;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
@Configuration
|
||||
public class RestTemplateConfig {
|
||||
|
||||
@Bean
|
||||
public RestTemplate restTemplate() {
|
||||
SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
|
||||
factory.setConnectTimeout(5000);
|
||||
factory.setReadTimeout(300000); // 5分钟超时
|
||||
return new RestTemplate(factory);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
package com.yang.test.controller;
|
||||
|
||||
import com.yang.test.domain.DTO.ResultRequest;
|
||||
import com.yang.test.domain.DTO.ResultResponse;
|
||||
import com.yang.test.domain.QuestionH;
|
||||
import com.yang.test.service.HldzyService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api")
|
||||
public class HldzyController {
|
||||
|
||||
@Autowired
|
||||
private HldzyService hldzyService;
|
||||
|
||||
/**
|
||||
* 获取所有测试题目
|
||||
*/
|
||||
@GetMapping("/questionsH")
|
||||
public Map<String, Object> getAllQuestions() {
|
||||
List<QuestionH> questions = hldzyService.getAllQuestions();
|
||||
Collections.shuffle(questions); // 随机排序问题
|
||||
|
||||
Map<String, Object> response = new HashMap<>();
|
||||
response.put("code", 0);
|
||||
response.put("message", "success");
|
||||
response.put("data", questions);
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
/**
|
||||
* 提交测试结果并获取分析
|
||||
*/
|
||||
@PostMapping("/results")
|
||||
public Map<String, Object> submitResult(@RequestBody ResultRequest request) {
|
||||
ResultResponse result = hldzyService.calculateResult(request.getAnswers());
|
||||
|
||||
Map<String, Object> response = new HashMap<>();
|
||||
response.put("code", 0);
|
||||
response.put("message", "success");
|
||||
response.put("data", result);
|
||||
|
||||
return response;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,82 @@
|
|||
package com.yang.test.controller;
|
||||
|
||||
import com.yang.test.domain.Answer;
|
||||
import com.yang.test.domain.VO.QuestionVO;
|
||||
import com.yang.test.domain.VO.ResultVO;
|
||||
import com.yang.test.service.MbtiService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Slf4j
|
||||
@RestController
|
||||
@RequestMapping("/api")
|
||||
public class MbtiController {
|
||||
|
||||
@Autowired
|
||||
private MbtiService mbtiService;
|
||||
|
||||
@GetMapping("/questions")
|
||||
public ResponseEntity<?> getQuestions(@RequestParam(value = "type", defaultValue = "simple") String testType) {
|
||||
Instant start = Instant.now();
|
||||
log.info("开始获取题目, 类型: {}", testType);
|
||||
try {
|
||||
List<QuestionVO> questions = mbtiService.getQuestions(testType);
|
||||
|
||||
log.info("获取题目完成, 类型: {}, 题目数量: {}, 总耗时: {}",
|
||||
testType, questions.size(), Duration.between(start, Instant.now()));
|
||||
|
||||
return ResponseEntity.ok(questions);
|
||||
} catch (Exception e) {
|
||||
log.error("获取题目失败: {}", e.getMessage(), e);
|
||||
return ResponseEntity.internalServerError().body(Map.of("error", "获取题目失败"));
|
||||
}
|
||||
}
|
||||
|
||||
@PostMapping("/submit")
|
||||
public ResponseEntity<?> submitAnswers(@RequestBody Answer answer,
|
||||
@RequestParam(value = "type", defaultValue = "simple") String testType) {
|
||||
Instant start = Instant.now();
|
||||
log.info("开始处理答案提交请求");
|
||||
|
||||
try {
|
||||
String resultId = mbtiService.processAnswers(answer.getAnswers(), testType);
|
||||
|
||||
log.info("答案提交处理完成, 结果ID: {}, 总耗时: {}",
|
||||
resultId, Duration.between(start, Instant.now()));
|
||||
|
||||
return ResponseEntity.ok(Map.of("resultId", resultId));
|
||||
} catch (Exception e) {
|
||||
log.error("处理答案提交请求失败: {}", e.getMessage(), e);
|
||||
return ResponseEntity.internalServerError().body(Map.of("error", "处理请求失败"));
|
||||
}
|
||||
}
|
||||
|
||||
@GetMapping("/result/{id}")
|
||||
public ResponseEntity<?> getResult(@PathVariable("id") String resultId) {
|
||||
Instant start = Instant.now();
|
||||
log.info("开始获取结果, ID: {}", resultId);
|
||||
|
||||
try {
|
||||
ResultVO result = mbtiService.getResult(resultId);
|
||||
|
||||
if (result == null) {
|
||||
return ResponseEntity.notFound().build();
|
||||
}
|
||||
|
||||
log.info("获取结果完成, ID: {}, 类型: {}, 总耗时: {}",
|
||||
resultId, result.getType(), Duration.between(start, Instant.now()));
|
||||
|
||||
return ResponseEntity.ok(result);
|
||||
} catch (Exception e) {
|
||||
log.error("获取结果失败: {}", e.getMessage(), e);
|
||||
return ResponseEntity.internalServerError().body(Map.of("error", "获取结果失败"));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
package com.yang.test.domain;
|
||||
|
||||
import lombok.Data;
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
public class Answer {
|
||||
private List<Integer> answers;
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
package com.yang.test.domain;
|
||||
|
||||
import lombok.Data;
|
||||
import java.util.Date;
|
||||
|
||||
@Data
|
||||
public class Category {
|
||||
private Integer id;
|
||||
private String cateKey;
|
||||
private String cateName;
|
||||
private String typeDesc;
|
||||
private String info;
|
||||
private Date createTime;
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
package com.yang.test.domain.DTO;
|
||||
|
||||
import lombok.Data;
|
||||
import java.util.Map;
|
||||
|
||||
@Data
|
||||
public class ResultRequest {
|
||||
private Map<String, Integer> answers;
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
package com.yang.test.domain.DTO;
|
||||
|
||||
import com.yang.test.domain.Category;
|
||||
import lombok.Data;
|
||||
import java.util.Map;
|
||||
|
||||
@Data
|
||||
public class ResultResponse {
|
||||
private String resultCode;
|
||||
private Map<String, Integer> scores;
|
||||
private Category primaryType;
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
package com.yang.test.domain;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class Dimension {
|
||||
private String left;
|
||||
private String right;
|
||||
private double leftValue;
|
||||
private double rightValue;
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
package com.yang.test.domain;
|
||||
|
||||
import lombok.Data;
|
||||
import java.util.List;
|
||||
import java.util.Date;
|
||||
|
||||
@Data
|
||||
public class Question {
|
||||
private Integer id;
|
||||
private String question;
|
||||
private String dimension;
|
||||
private Integer direction;
|
||||
private String type;
|
||||
private Date createdAt;
|
||||
private List<String> options;
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
package com.yang.test.domain;
|
||||
|
||||
import lombok.Data;
|
||||
import java.util.Date;
|
||||
|
||||
@Data
|
||||
public class QuestionH {
|
||||
private Long id;
|
||||
private String title;
|
||||
private String cateKey;
|
||||
private Integer cateId;
|
||||
private String cateName;
|
||||
private Integer score;
|
||||
private Date createTime;
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
package com.yang.test.domain;
|
||||
|
||||
import lombok.Data;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
public class Result {
|
||||
private String id;
|
||||
private String type;
|
||||
private List<Dimension> dimensions;
|
||||
private String report;
|
||||
private Date createdAt;
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
package com.yang.test.domain;
|
||||
|
||||
import lombok.Data;
|
||||
import java.util.Date;
|
||||
|
||||
@Data
|
||||
public class TestResult {
|
||||
private Long id;
|
||||
private Long userId;
|
||||
private String resultCode;
|
||||
private Integer rScore;
|
||||
private Integer cScore;
|
||||
private Integer eScore;
|
||||
private Integer sScore;
|
||||
private Integer aScore;
|
||||
private Integer iScore;
|
||||
private Date createTime;
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
package com.yang.test.domain.VO;
|
||||
|
||||
import lombok.Data;
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
public class QuestionVO {
|
||||
private Integer id;
|
||||
private String question;
|
||||
private List<String> options;
|
||||
private String dimension;
|
||||
private Integer direction;
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
package com.yang.test.domain.VO;
|
||||
|
||||
|
||||
import com.yang.test.domain.Dimension;
|
||||
import lombok.Data;
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
public class ResultVO {
|
||||
private String id;
|
||||
private String type;
|
||||
private List<Dimension> dimensions;
|
||||
private String report;
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
package com.yang.test.handler;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@Slf4j
|
||||
@RestControllerAdvice
|
||||
public class GlobalExceptionHandler {
|
||||
|
||||
@ExceptionHandler(MbtiException.class)
|
||||
public ResponseEntity<?> handleMbtiException(MbtiException e) {
|
||||
log.error("业务异常: {}", e.getMessage(), e);
|
||||
return ResponseEntity.badRequest().body(Map.of("error", e.getMessage()));
|
||||
}
|
||||
|
||||
@ExceptionHandler(Exception.class)
|
||||
public ResponseEntity<?> handleException(Exception e) {
|
||||
log.error("系统异常: {}", e.getMessage(), e);
|
||||
return ResponseEntity.internalServerError().body(Map.of("error", "系统内部错误"));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
package com.yang.test.handler;
|
||||
|
||||
public class MbtiException extends RuntimeException {
|
||||
|
||||
public MbtiException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public MbtiException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
package com.yang.test.mapper;
|
||||
|
||||
import com.yang.test.domain.Category;
|
||||
import com.yang.test.domain.QuestionH;
|
||||
import com.yang.test.domain.TestResult;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Mapper
|
||||
public interface HldzyMapper {
|
||||
/**
|
||||
* 查询所有问题
|
||||
*/
|
||||
List<QuestionH> selectAllQuestions();
|
||||
|
||||
|
||||
/**
|
||||
* 根据键值查询类型
|
||||
*/
|
||||
Category selectCategoryByKey(@Param("cateKey") String cateKey);
|
||||
|
||||
/**
|
||||
* 插入测试结果
|
||||
*/
|
||||
int insertTestResult(TestResult testResult);
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
package com.yang.test.mapper;
|
||||
|
||||
|
||||
|
||||
import com.yang.test.domain.Question;
|
||||
import com.yang.test.domain.Result;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Mapper
|
||||
public interface MbtiMapper {
|
||||
/**
|
||||
* 根据类型获取题目列表
|
||||
* @param types 题目类型
|
||||
* @return 题目列表
|
||||
*/
|
||||
List<Question> getQuestionsByTypes(List<String> types);
|
||||
|
||||
/**
|
||||
* 获取题目的维度和方向信息
|
||||
* @param types 题目类型
|
||||
* @return 维度和方向信息列表
|
||||
*/
|
||||
List<Map<String, Object>> getQuestionDimensionsAndDirections(List<String> types);
|
||||
|
||||
/**
|
||||
* 保存测试结果
|
||||
* @param result 结果对象
|
||||
* @return 影响的行数
|
||||
*/
|
||||
int saveResult(Result result);
|
||||
|
||||
/**
|
||||
* 根据ID获取结果
|
||||
* @param resultId 结果ID
|
||||
* @return 结果对象
|
||||
*/
|
||||
Result getResultById(String resultId);
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
package com.yang.test.service;
|
||||
|
||||
|
||||
import com.yang.test.domain.Dimension;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface DeepseekService {
|
||||
/**
|
||||
* 生成MBTI性格报告
|
||||
* @param mbtiType MBTI类型
|
||||
* @param dimensions 维度数据
|
||||
* @return 生成的报告
|
||||
*/
|
||||
String generateReport(String mbtiType, List<Dimension> dimensions);
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
package com.yang.test.service;
|
||||
|
||||
import com.yang.test.domain.DTO.ResultResponse;
|
||||
import com.yang.test.domain.Category;
|
||||
import com.yang.test.domain.QuestionH;
|
||||
import com.yang.test.domain.TestResult;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public interface HldzyService {
|
||||
/**
|
||||
* 获取所有问题
|
||||
*/
|
||||
List<QuestionH> getAllQuestions();
|
||||
|
||||
/**
|
||||
* 根据键值获取类型
|
||||
*/
|
||||
Category getCategoryByKey(String cateKey);
|
||||
|
||||
/**
|
||||
* 计算测试结果
|
||||
*/
|
||||
ResultResponse calculateResult(Map<String, Integer> answers);
|
||||
|
||||
/**
|
||||
* 保存测试结果
|
||||
*/
|
||||
Long saveResult(TestResult testResult);
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
package com.yang.test.service;
|
||||
|
||||
|
||||
|
||||
import com.yang.test.domain.VO.QuestionVO;
|
||||
import com.yang.test.domain.VO.ResultVO;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public interface MbtiService {
|
||||
/**
|
||||
* 获取测试题目
|
||||
* @param testType 测试类型
|
||||
* @return 题目列表
|
||||
*/
|
||||
List<QuestionVO> getQuestions(String testType);
|
||||
|
||||
/**
|
||||
* 处理用户提交的答案
|
||||
* @param answers 用户答案
|
||||
* @param testType 测试类型
|
||||
* @return 结果ID
|
||||
*/
|
||||
String processAnswers(List<Integer> answers, String testType);
|
||||
|
||||
/**
|
||||
* 获取测试结果
|
||||
* @param resultId 结果ID
|
||||
* @return 结果视图对象
|
||||
*/
|
||||
ResultVO getResult(String resultId);
|
||||
|
||||
/**
|
||||
* 计算MBTI维度得分
|
||||
* @param answers 用户答案
|
||||
* @param testType 测试类型
|
||||
* @return 维度得分映射
|
||||
*/
|
||||
Map<String, Double> calculateScores(List<Integer> answers, String testType);
|
||||
|
||||
/**
|
||||
* 根据得分确定MBTI类型
|
||||
* @param scores 维度得分
|
||||
* @return MBTI类型
|
||||
*/
|
||||
String determineMbtiType(Map<String, Double> scores);
|
||||
}
|
|
@ -0,0 +1,232 @@
|
|||
package com.yang.test.service.serviceImpl;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.node.ArrayNode;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
|
||||
import com.yang.test.domain.Dimension;
|
||||
import com.yang.test.service.DeepseekService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.http.HttpEntity;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
public class DeepseekServiceImpl implements DeepseekService {
|
||||
|
||||
@Value("${deepseek.api.key}")
|
||||
private String apiKey;
|
||||
|
||||
@Value("${deepseek.api.url:https://api.deepseek.com/v1/chat/completions}")
|
||||
private String apiUrl;
|
||||
|
||||
@Value("${deepseek.model:deepseek-chat}")
|
||||
private String model;
|
||||
|
||||
@Autowired
|
||||
private ObjectMapper objectMapper;
|
||||
|
||||
@Autowired
|
||||
private RestTemplate restTemplate;
|
||||
|
||||
@Override
|
||||
public String generateReport(String mbtiType, List<Dimension> dimensions) {
|
||||
if (apiKey == null || apiKey.isEmpty()) {
|
||||
log.error("[MBTI报告生成] 错误: DEEPSEEK_API_KEY未设置,将使用默认模板");
|
||||
return mbtiType + "类型的性格特点是...";
|
||||
}
|
||||
|
||||
try {
|
||||
// 构建维度倾向描述
|
||||
List<String> dimensionDescriptions = new ArrayList<>();
|
||||
for (Dimension dim : dimensions) {
|
||||
String tendency;
|
||||
if (dim.getLeftValue() > dim.getRightValue()) {
|
||||
tendency = String.format("偏向%s (%.0f%%)", dim.getRight(), dim.getLeftValue());
|
||||
} else {
|
||||
tendency = String.format("偏向%s (%.0f%%)", dim.getLeft(), dim.getRightValue());
|
||||
}
|
||||
dimensionDescriptions.add(String.format("%s vs %s: %s", dim.getLeft(), dim.getRight(), tendency));
|
||||
}
|
||||
|
||||
// 构建提示词
|
||||
String prompt = buildPrompt(mbtiType, dimensionDescriptions);
|
||||
|
||||
// 构建DeepSeek API请求
|
||||
ObjectNode requestBody = objectMapper.createObjectNode();
|
||||
requestBody.put("model", model);
|
||||
|
||||
ArrayNode messagesArray = requestBody.putArray("messages");
|
||||
ObjectNode message = messagesArray.addObject();
|
||||
message.put("role", "user");
|
||||
message.put("content", prompt);
|
||||
|
||||
requestBody.put("temperature", 0.5);
|
||||
requestBody.put("max_tokens", 2500);
|
||||
requestBody.put("stream", false);
|
||||
requestBody.put("presence_penalty", 0.1);
|
||||
requestBody.put("frequency_penalty", 0.1);
|
||||
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setContentType(MediaType.APPLICATION_JSON);
|
||||
headers.set("Authorization", "Bearer " + apiKey);
|
||||
|
||||
HttpEntity<String> entity = new HttpEntity<>(objectMapper.writeValueAsString(requestBody), headers);
|
||||
|
||||
// 发送请求
|
||||
log.info("[MBTI报告生成] 调用DeepSeek API");
|
||||
ResponseEntity<String> response = restTemplate.postForEntity(apiUrl, entity, String.class);
|
||||
|
||||
// 处理响应
|
||||
if (response.getStatusCode().is2xxSuccessful() && response.getBody() != null) {
|
||||
JsonNode responseJson = objectMapper.readTree(response.getBody());
|
||||
|
||||
// 提取生成的报告内容
|
||||
if (responseJson.has("choices") && responseJson.get("choices").isArray() && responseJson.get("choices").size() > 0) {
|
||||
JsonNode choices = responseJson.get("choices");
|
||||
JsonNode messageNode = choices.get(0).get("message");
|
||||
|
||||
if (messageNode != null && messageNode.has("content")) {
|
||||
String content = messageNode.get("content").asText();
|
||||
|
||||
// 清理内容,只保留HTML部分
|
||||
content = content.trim();
|
||||
if (content.startsWith("```html")) {
|
||||
content = content.substring("```html".length());
|
||||
}
|
||||
if (content.endsWith("```")) {
|
||||
content = content.substring(0, content.length() - 3);
|
||||
}
|
||||
|
||||
return content.trim();
|
||||
}
|
||||
}
|
||||
|
||||
log.error("[MBTI报告生成] 错误: 无法从API响应中提取报告内容,将使用默认模板");
|
||||
} else {
|
||||
log.error("[MBTI报告生成] 错误: API返回非200状态码 - {}", response.getStatusCodeValue());
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("[MBTI报告生成] 错误: 调用DeepSeek API失败 - {}", e.getMessage(), e);
|
||||
}
|
||||
|
||||
return mbtiType + "类型的性格特点是...";
|
||||
}
|
||||
|
||||
private String buildPrompt(String mbtiType, List<String> dimensionDescriptions) {
|
||||
String dimensionsText = dimensionDescriptions.stream().collect(Collectors.joining("\n"));
|
||||
|
||||
return String.format("""
|
||||
请以专业的心理学视角,对MBTI中的%s类型进行全面分析,并使用HTML标签组织内容。该用户在各维度上的具体倾向如下:
|
||||
|
||||
%s
|
||||
|
||||
请按以下HTML结构组织内容:
|
||||
|
||||
<div class="mbti-report">
|
||||
<section class="core-traits">
|
||||
<h2>核心特质概述</h2>
|
||||
<ul>
|
||||
<li><strong>主要认知功能:</strong>[内容]</li>
|
||||
<li><strong>思维方式特点:</strong>[内容]</li>
|
||||
<li><strong>行为模式倾向:</strong>[内容]</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section class="relationships">
|
||||
<h2>人际关系分析</h2>
|
||||
<ul>
|
||||
<li><strong>沟通风格:</strong>[内容]</li>
|
||||
<li><strong>与他人互动方式:</strong>[内容]</li>
|
||||
<li><strong>在团队中的角色:</strong>[内容]</li>
|
||||
<li><strong>理想的社交环境:</strong>[内容]</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section class="career">
|
||||
<h2>职业发展洞察</h2>
|
||||
<ul>
|
||||
<li><strong>最适合的工作环境:</strong>[内容]</li>
|
||||
<li><strong>职业优势:</strong>[内容]</li>
|
||||
<li><strong>潜在的职业挑战:</strong>[内容]</li>
|
||||
<li><strong>理想的职业方向:</strong>[内容]</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section class="growth">
|
||||
<h2>个人成长建议</h2>
|
||||
<ul>
|
||||
<li><strong>需要培养的能力:</strong>[内容]</li>
|
||||
<li><strong>潜在的发展盲点:</strong>[内容]</li>
|
||||
<li><strong>压力管理方式:</strong>[内容]</li>
|
||||
<li><strong>自我提升方向:</strong>[内容]</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section class="strengths-weaknesses">
|
||||
<h2>独特优势和劣势</h2>
|
||||
<ul>
|
||||
<li><strong>核心优势:</strong>[内容]</li>
|
||||
<li><strong>潜在劣势:</strong>[内容]</li>
|
||||
<li><strong>如何发挥优势:</strong>[内容]</li>
|
||||
<li><strong>如何克服劣势:</strong>[内容]</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section class="love-relationships">
|
||||
<h2>爱情关系分析</h2>
|
||||
<ul>
|
||||
<li><strong>恋爱中的表现:</strong>[内容]</li>
|
||||
<li><strong>理想伴侣特质:</strong>[内容]</li>
|
||||
<li><strong>感情相处方式:</strong>[内容]</li>
|
||||
<li><strong>维系感情建议:</strong>[内容]</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section class="friendships">
|
||||
<h2>友情互动特点</h2>
|
||||
<ul>
|
||||
<li><strong>交友方式:</strong>[内容]</li>
|
||||
<li><strong>理想友谊类型:</strong>[内容]</li>
|
||||
<li><strong>友情维护特点:</strong>[内容]</li>
|
||||
<li><strong>社交圈建议:</strong>[内容]</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section class="parenting">
|
||||
<h2>育儿方式建议</h2>
|
||||
<ul>
|
||||
<li><strong>教育理念:</strong>[内容]</li>
|
||||
<li><strong>亲子互动风格:</strong>[内容]</li>
|
||||
<li><strong>可能的教育挑战:</strong>[内容]</li>
|
||||
<li><strong>育儿建议:</strong>[内容]</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section class="work-habits">
|
||||
<h2>工作习惯分析</h2>
|
||||
<ul>
|
||||
<li><strong>工作节奏偏好:</strong>[内容]</li>
|
||||
<li><strong>任务管理方式:</strong>[内容]</li>
|
||||
<li><strong>工作环境需求:</strong>[内容]</li>
|
||||
<li><strong>效率提升建议:</strong>[内容]</li>
|
||||
</ul>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
请根据用户在各维度上的具体倾向程度,用专业但易懂的语言填充上述HTML结构中的[内容]部分。特别注意根据维度分数的强弱来调整建议的针对性。请确保生成的HTML格式正确,可以直接在网页中显示。
|
||||
""", mbtiType, dimensionsText);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,87 @@
|
|||
package com.yang.test.service.serviceImpl;
|
||||
|
||||
import com.yang.test.mapper.HldzyMapper;
|
||||
import com.yang.test.domain.DTO.ResultResponse;
|
||||
import com.yang.test.domain.Category;
|
||||
import com.yang.test.domain.QuestionH;
|
||||
import com.yang.test.domain.TestResult;
|
||||
import com.yang.test.service.HldzyService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Service
|
||||
public class HldzyServiceImpl implements HldzyService {
|
||||
|
||||
@Autowired
|
||||
private HldzyMapper hldzyMapper;
|
||||
|
||||
@Override
|
||||
public List<QuestionH> getAllQuestions() {
|
||||
return hldzyMapper.selectAllQuestions();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Category getCategoryByKey(String cateKey) {
|
||||
return hldzyMapper.selectCategoryByKey(cateKey);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResultResponse calculateResult(Map<String, Integer> answers) {
|
||||
// 计算各类型分数
|
||||
Map<String, Integer> scores = new HashMap<>();
|
||||
|
||||
// 确保所有类型都有分数,没有回答的默认为0
|
||||
scores.put("R", answers.getOrDefault("R", 0));
|
||||
scores.put("C", answers.getOrDefault("C", 0));
|
||||
scores.put("E", answers.getOrDefault("E", 0));
|
||||
scores.put("S", answers.getOrDefault("S", 0));
|
||||
scores.put("A", answers.getOrDefault("A", 0));
|
||||
scores.put("I", answers.getOrDefault("I", 0));
|
||||
|
||||
// 按分数排序并获取前三个类型
|
||||
List<Map.Entry<String, Integer>> sortedScores = scores.entrySet()
|
||||
.stream()
|
||||
.sorted(Map.Entry.<String, Integer>comparingByValue().reversed())
|
||||
.collect(Collectors.toList());
|
||||
|
||||
// 构建结果代码(前三类型的组合)
|
||||
StringBuilder resultCode = new StringBuilder();
|
||||
for (int i = 0; i < Math.min(3, sortedScores.size()); i++) {
|
||||
resultCode.append(sortedScores.get(i).getKey());
|
||||
}
|
||||
|
||||
// 获取主导类型
|
||||
String primaryTypeKey = sortedScores.get(0).getKey();
|
||||
Category primaryType = getCategoryByKey(primaryTypeKey);
|
||||
|
||||
// 构建响应
|
||||
ResultResponse response = new ResultResponse();
|
||||
response.setResultCode(resultCode.toString());
|
||||
response.setScores(scores);
|
||||
response.setPrimaryType(primaryType);
|
||||
|
||||
// 保存结果到数据库
|
||||
TestResult testResult = new TestResult();
|
||||
testResult.setResultCode(resultCode.toString());
|
||||
testResult.setRScore(scores.get("R"));
|
||||
testResult.setCScore(scores.get("C"));
|
||||
testResult.setEScore(scores.get("E"));
|
||||
testResult.setSScore(scores.get("S"));
|
||||
testResult.setAScore(scores.get("A"));
|
||||
testResult.setIScore(scores.get("I"));
|
||||
|
||||
saveResult(testResult);
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long saveResult(TestResult testResult) {
|
||||
hldzyMapper.insertTestResult(testResult);
|
||||
return testResult.getId();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,236 @@
|
|||
package com.yang.test.service.serviceImpl;
|
||||
|
||||
|
||||
import com.yang.test.mapper.MbtiMapper;
|
||||
import com.yang.test.domain.Dimension;
|
||||
import com.yang.test.domain.Question;
|
||||
import com.yang.test.domain.Result;
|
||||
import com.yang.test.domain.VO.QuestionVO;
|
||||
import com.yang.test.domain.VO.ResultVO;
|
||||
import com.yang.test.service.DeepseekService;
|
||||
import com.yang.test.service.MbtiService;
|
||||
import lombok.Data;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.*;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
public class MbtiServiceImpl implements MbtiService {
|
||||
|
||||
@Autowired
|
||||
private MbtiMapper mbtiMapper;
|
||||
|
||||
@Autowired
|
||||
private DeepseekService deepseekService;
|
||||
|
||||
@Override
|
||||
public List<QuestionVO> getQuestions(String testType) {
|
||||
List<String> types;
|
||||
switch (testType) {
|
||||
case "simple":
|
||||
types = Collections.singletonList("simple");
|
||||
break;
|
||||
case "detailed":
|
||||
types = Arrays.asList("simple", "detailed");
|
||||
break;
|
||||
case "full":
|
||||
types = Arrays.asList("simple", "detailed", "full");
|
||||
break;
|
||||
default:
|
||||
log.warn("无效的测试类型: {}, 使用simple类型", testType);
|
||||
types = Collections.singletonList("simple");
|
||||
}
|
||||
|
||||
List<Question> questions = mbtiMapper.getQuestionsByTypes(types);
|
||||
List<QuestionVO> questionVOs = new ArrayList<>();
|
||||
|
||||
for (Question q : questions) {
|
||||
QuestionVO vo = new QuestionVO();
|
||||
vo.setId(q.getId());
|
||||
vo.setQuestion(q.getQuestion());
|
||||
vo.setDimension(q.getDimension());
|
||||
vo.setDirection(q.getDirection());
|
||||
// 设置7级量表选项
|
||||
vo.setOptions(Arrays.asList("非常不符合", "不符合", "有点不符合", "中立", "有点符合", "符合", "非常符合"));
|
||||
questionVOs.add(vo);
|
||||
}
|
||||
|
||||
return questionVOs;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String processAnswers(List<Integer> answers, String testType) {
|
||||
// 计算MBTI维度得分
|
||||
Map<String, Double> scores = calculateScores(answers, testType);
|
||||
|
||||
// 生成结果ID
|
||||
String resultId = generateResultId();
|
||||
|
||||
// 根据得分确定MBTI类型
|
||||
String mbtiType = determineMbtiType(scores);
|
||||
log.info("计算得到MBTI类型: {}, 得分: {}", mbtiType, scores);
|
||||
|
||||
// 生成维度数据
|
||||
List<Dimension> dimensions = Arrays.asList(
|
||||
new Dimension("E", "I", Math.round(100 - scores.getOrDefault("EI", 0.0)), Math.round(scores.getOrDefault("EI", 0.0))),
|
||||
new Dimension("S", "N", Math.round(100 - scores.getOrDefault("SN", 0.0)), Math.round(scores.getOrDefault("SN", 0.0))),
|
||||
new Dimension("T", "F", Math.round(100 - scores.getOrDefault("TF", 0.0)), Math.round(scores.getOrDefault("TF", 0.0))),
|
||||
new Dimension("J", "P", Math.round(100 - scores.getOrDefault("JP", 0.0)), Math.round(scores.getOrDefault("JP", 0.0)))
|
||||
);
|
||||
|
||||
// 生成个性化报告
|
||||
String report = deepseekService.generateReport(mbtiType, dimensions);
|
||||
|
||||
// 创建结果对象
|
||||
Result result = new Result();
|
||||
result.setId(resultId);
|
||||
result.setType(mbtiType);
|
||||
result.setDimensions(dimensions);
|
||||
result.setReport(report);
|
||||
result.setCreatedAt(new Date());
|
||||
|
||||
// 保存结果到数据库
|
||||
int rows = mbtiMapper.saveResult(result);
|
||||
log.info("结果保存完成, 影响行数: {}", rows);
|
||||
|
||||
return resultId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResultVO getResult(String resultId) {
|
||||
Result result = mbtiMapper.getResultById(resultId);
|
||||
|
||||
if (result == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 转换为视图对象
|
||||
ResultVO resultVO = new ResultVO();
|
||||
resultVO.setId(result.getId());
|
||||
resultVO.setType(result.getType());
|
||||
resultVO.setDimensions(result.getDimensions());
|
||||
resultVO.setReport(result.getReport());
|
||||
|
||||
return resultVO;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Double> calculateScores(List<Integer> answers, String testType) {
|
||||
Map<String, Double> scores = new HashMap<>();
|
||||
|
||||
// 根据测试类型确定题目范围
|
||||
List<String> types;
|
||||
switch (testType) {
|
||||
case "simple":
|
||||
types = Collections.singletonList("simple");
|
||||
break;
|
||||
case "detailed":
|
||||
types = Arrays.asList("simple", "detailed");
|
||||
break;
|
||||
case "full":
|
||||
types = Arrays.asList("simple", "detailed", "full");
|
||||
break;
|
||||
default:
|
||||
log.warn("无效的测试类型: {}", testType);
|
||||
return scores;
|
||||
}
|
||||
|
||||
// 查询题目信息
|
||||
List<Map<String, Object>> questions = mbtiMapper.getQuestionDimensionsAndDirections(types);
|
||||
|
||||
// 计算每个维度的得分
|
||||
Map<String, ScoreInfo> dimensionScores = new HashMap<>();
|
||||
|
||||
for (int i = 0; i < Math.min(answers.size(), questions.size()); i++) {
|
||||
Map<String, Object> question = questions.get(i);
|
||||
String dimension = (String) question.get("dimension");
|
||||
int direction = ((Number) question.get("direction")).intValue();
|
||||
|
||||
// 确保答案在1-7的范围内
|
||||
int answer = answers.get(i);
|
||||
if (answer < 1) answer = 1;
|
||||
if (answer > 7) answer = 7;
|
||||
|
||||
// 计算得分(考虑题目方向)
|
||||
double score;
|
||||
if (direction == 1) {
|
||||
score = (answer - 1) * (100.0 / 6.0); // 正向题目:将1-7线性映射到0-100
|
||||
} else {
|
||||
score = (7 - answer) * (100.0 / 6.0); // 反向题目:将7-1线性映射到0-100
|
||||
}
|
||||
|
||||
// 确保得分在0-100范围内
|
||||
if (score < 0) score = 0;
|
||||
if (score > 100) score = 100;
|
||||
|
||||
// 累加分数
|
||||
ScoreInfo scoreInfo = dimensionScores.getOrDefault(dimension, new ScoreInfo());
|
||||
scoreInfo.setTotal(scoreInfo.getTotal() + score);
|
||||
scoreInfo.setCount(scoreInfo.getCount() + 1);
|
||||
dimensionScores.put(dimension, scoreInfo);
|
||||
}
|
||||
|
||||
// 计算每个维度的最终得分
|
||||
for (Map.Entry<String, ScoreInfo> entry : dimensionScores.entrySet()) {
|
||||
String dim = entry.getKey();
|
||||
ScoreInfo info = entry.getValue();
|
||||
|
||||
if (info.getCount() > 0) {
|
||||
// 计算平均分并确保在0-100范围内
|
||||
double avgScore = info.getTotal() / info.getCount();
|
||||
if (avgScore < 0) avgScore = 0;
|
||||
if (avgScore > 100) avgScore = 100;
|
||||
scores.put(dim, (double) Math.round(avgScore));
|
||||
}
|
||||
}
|
||||
|
||||
return scores;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String determineMbtiType(Map<String, Double> scores) {
|
||||
StringBuilder mbtiType = new StringBuilder();
|
||||
|
||||
// 当分数等于50时,默认选择第二个类型
|
||||
if (scores.getOrDefault("EI", 0.0) >= 50) {
|
||||
mbtiType.append("E");
|
||||
} else {
|
||||
mbtiType.append("I");
|
||||
}
|
||||
|
||||
if (scores.getOrDefault("SN", 0.0) >= 50) {
|
||||
mbtiType.append("S");
|
||||
} else {
|
||||
mbtiType.append("N");
|
||||
}
|
||||
|
||||
if (scores.getOrDefault("TF", 0.0) >= 50) {
|
||||
mbtiType.append("T");
|
||||
} else {
|
||||
mbtiType.append("F");
|
||||
}
|
||||
|
||||
if (scores.getOrDefault("JP", 0.0) >= 50) {
|
||||
mbtiType.append("J");
|
||||
} else {
|
||||
mbtiType.append("P");
|
||||
}
|
||||
|
||||
return mbtiType.toString();
|
||||
}
|
||||
|
||||
private String generateResultId() {
|
||||
return "result_" + Instant.now().toEpochMilli();
|
||||
}
|
||||
|
||||
// 内部类用于存储每个维度的分数信息
|
||||
@Data
|
||||
private static class ScoreInfo {
|
||||
private double total = 0;
|
||||
private int count = 0;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,71 @@
|
|||
package com.yang.test.util;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.apache.ibatis.type.BaseTypeHandler;
|
||||
import org.apache.ibatis.type.JdbcType;
|
||||
import org.apache.ibatis.type.MappedTypes;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.sql.CallableStatement;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* JSON类型处理器,用于将Java对象与数据库JSON字段之间进行转换
|
||||
*/
|
||||
@MappedTypes({List.class})
|
||||
public class JsonTypeHandler<T> extends BaseTypeHandler<T> {
|
||||
private static final Logger logger = LoggerFactory.getLogger(JsonTypeHandler.class);
|
||||
private static final ObjectMapper objectMapper = new ObjectMapper();
|
||||
private final Class<T> type;
|
||||
|
||||
public JsonTypeHandler(Class<T> type) {
|
||||
if (type == null) {
|
||||
throw new IllegalArgumentException("Type argument cannot be null");
|
||||
}
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setNonNullParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException {
|
||||
try {
|
||||
String json = objectMapper.writeValueAsString(parameter);
|
||||
ps.setString(i, json);
|
||||
} catch (JsonProcessingException e) {
|
||||
logger.error("Error converting object to JSON", e);
|
||||
throw new SQLException("Error converting object to JSON", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public T getNullableResult(ResultSet rs, String columnName) throws SQLException {
|
||||
return parseJSON(rs.getString(columnName));
|
||||
}
|
||||
|
||||
@Override
|
||||
public T getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
|
||||
return parseJSON(rs.getString(columnIndex));
|
||||
}
|
||||
|
||||
@Override
|
||||
public T getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
|
||||
return parseJSON(cs.getString(columnIndex));
|
||||
}
|
||||
|
||||
private T parseJSON(String json) {
|
||||
if (json == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
return objectMapper.readValue(json, type);
|
||||
} catch (Exception e) {
|
||||
logger.error("Error parsing JSON to object", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
package com.yang.test.util;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class ResponseUtil {
|
||||
|
||||
public static Map<String, Object> success(Object data) {
|
||||
Map<String, Object> response = new HashMap<>();
|
||||
response.put("code", 0);
|
||||
response.put("message", "success");
|
||||
response.put("data", data);
|
||||
return response;
|
||||
}
|
||||
|
||||
public static Map<String, Object> error(String message) {
|
||||
Map<String, Object> response = new HashMap<>();
|
||||
response.put("code", 1);
|
||||
response.put("message", message);
|
||||
return response;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
# 数据库配置
|
||||
spring.datasource.url=jdbc:mysql://62.234.217.137:3306/mbti?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
|
||||
spring.datasource.username=root
|
||||
spring.datasource.password=nWZpHMb8mNxWE5Xk
|
||||
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
|
||||
|
||||
# 服务器配置
|
||||
server.port=8080
|
||||
|
||||
# HikariCP配置
|
||||
spring.datasource.hikari.pool-name=HikariPool-1
|
||||
spring.datasource.hikari.connection-timeout=3000
|
||||
spring.datasource.hikari.max-lifetime=540000
|
||||
spring.datasource.hikari.idle-timeout=600000
|
||||
spring.datasource.hikari.minimum-idle=5
|
||||
spring.datasource.hikari.maximum-pool-size=20
|
||||
spring.datasource.hikari.validation-timeout=3000
|
||||
spring.datasource.hikari.keepalive-time=30000
|
||||
spring.datasource.hikari.data-source-properties.socketTimeout=30
|
||||
spring.datasource.hikari.data-source-properties.autoReconnect=true
|
||||
|
||||
# DeepSeek API配置
|
||||
deepseek.api.key=sk-1d3dd761c40045228547efb38ce59754
|
||||
deepseek.model=deepseek-chat
|
||||
deepseek.api.url=https://api.deepseek.com/v1/chat/completions
|
||||
|
||||
# 跨域配置
|
||||
cors.allowed-origins=http://localhost:8080,http://localhost:5173
|
||||
|
||||
# MyBatis配置
|
||||
mybatis.config-location=classpath:mybatis-config.xml
|
||||
mybatis.mapper-locations=classpath:mapper/*.xml
|
||||
mybatis.type-aliases-package=com.yang.test.pojo
|
||||
|
||||
# 日志配置
|
||||
logging.level.root=INFO
|
||||
logging.level.com.yang.test=DEBUG
|
||||
logging.level.com.yang.test.mapper=DEBUG
|
||||
logging.file.name=server.log
|
||||
logging.pattern.console=%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n
|
||||
logging.pattern.file=%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n
|
|
@ -0,0 +1,60 @@
|
|||
<?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="com.yang.test.mapper.HldzyMapper">
|
||||
<!-- 问题映射 -->
|
||||
<resultMap id="questionMap" type="com.yang.test.domain.QuestionH">
|
||||
<id property="id" column="id"/>
|
||||
<result property="title" column="title"/>
|
||||
<result property="cateKey" column="cate_key"/>
|
||||
<result property="cateId" column="cate_id"/>
|
||||
<result property="cateName" column="cate_name"/>
|
||||
<result property="score" column="score"/>
|
||||
<result property="createTime" column="create_time"/>
|
||||
</resultMap>
|
||||
|
||||
<!-- 类型映射 -->
|
||||
<resultMap id="categoryMap" type="com.yang.test.domain.Category">
|
||||
<id property="id" column="id"/>
|
||||
<result property="cateKey" column="cate_key"/>
|
||||
<result property="cateName" column="cate_name"/>
|
||||
<result property="typeDesc" column="type_desc"/>
|
||||
<result property="info" column="info"/>
|
||||
<result property="createTime" column="create_time"/>
|
||||
</resultMap>
|
||||
|
||||
<!-- 测试结果映射 -->
|
||||
<resultMap id="testResultMap" type="com.yang.test.domain.TestResult">
|
||||
<id property="id" column="id"/>
|
||||
<result property="userId" column="user_id"/>
|
||||
<result property="resultCode" column="result_code"/>
|
||||
<result property="rScore" column="r_score"/>
|
||||
<result property="cScore" column="c_score"/>
|
||||
<result property="eScore" column="e_score"/>
|
||||
<result property="sScore" column="s_score"/>
|
||||
<result property="aScore" column="a_score"/>
|
||||
<result property="iScore" column="i_score"/>
|
||||
<result property="createTime" column="create_time"/>
|
||||
</resultMap>
|
||||
|
||||
<!-- 查询所有问题 -->
|
||||
<select id="selectAllQuestions" resultMap="questionMap">
|
||||
SELECT id, title, cate_key, cate_id, cate_name, score, create_time
|
||||
FROM t_question
|
||||
</select>
|
||||
|
||||
<!-- 根据键值查询类型 -->
|
||||
<select id="selectCategoryByKey" resultMap="categoryMap">
|
||||
SELECT id, cate_key, cate_name, type_desc, info, create_time
|
||||
FROM t_category
|
||||
WHERE cate_key = #{cateKey}
|
||||
</select>
|
||||
|
||||
<!-- 插入测试结果 -->
|
||||
<insert id="insertTestResult" parameterType="com.yang.test.domain.TestResult" useGeneratedKeys="true" keyProperty="id">
|
||||
INSERT INTO t_test_result(
|
||||
user_id, result_code, r_score, c_score, e_score, s_score, a_score, i_score
|
||||
) VALUES (
|
||||
#{userId}, #{resultCode}, #{rScore}, #{cScore}, #{eScore}, #{sScore}, #{aScore}, #{iScore}
|
||||
)
|
||||
</insert>
|
||||
</mapper>
|
|
@ -0,0 +1,38 @@
|
|||
<?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="com.yang.test.mapper.MbtiMapper">
|
||||
<!-- 获取题目列表 -->
|
||||
<select id="getQuestionsByTypes" resultType="com.yang.test.domain.Question">
|
||||
SELECT id, question, dimension, direction, type, created_at as createdAt
|
||||
FROM questions
|
||||
WHERE type IN
|
||||
<foreach item="item" collection="list" open="(" separator="," close=")">
|
||||
#{item}
|
||||
</foreach>
|
||||
ORDER BY id
|
||||
</select>
|
||||
|
||||
<!-- 获取题目的维度和方向信息 -->
|
||||
<select id="getQuestionDimensionsAndDirections" resultType="java.util.Map">
|
||||
SELECT id, dimension, direction
|
||||
FROM questions
|
||||
WHERE type IN
|
||||
<foreach item="item" collection="list" open="(" separator="," close=")">
|
||||
#{item}
|
||||
</foreach>
|
||||
ORDER BY id
|
||||
</select>
|
||||
|
||||
<!-- 保存测试结果 -->
|
||||
<insert id="saveResult" parameterType="com.yang.test.domain.Result">
|
||||
INSERT INTO results (id, type, dimensions, report, created_at)
|
||||
VALUES (#{id}, #{type}, #{dimensions, typeHandler=com.yang.test.util.JsonTypeHandler}, #{report}, #{createdAt})
|
||||
</insert>
|
||||
|
||||
<!-- 根据ID获取结果 -->
|
||||
<select id="getResultById" resultType="com.yang.test.domain.Result">
|
||||
SELECT id, type, dimensions, report, created_at as createdAt
|
||||
FROM results
|
||||
WHERE id = #{resultId}
|
||||
</select>
|
||||
</mapper>
|
|
@ -0,0 +1,9 @@
|
|||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<!DOCTYPE configuration
|
||||
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
|
||||
"http://mybatis.org/dtd/mybatis-3-config.dtd">
|
||||
<configuration>
|
||||
<typeHandlers>
|
||||
<typeHandler handler="com.yang.test.util.JsonTypeHandler" javaType="java.util.List"/>
|
||||
</typeHandlers>
|
||||
</configuration>
|
|
@ -0,0 +1,13 @@
|
|||
package com.yang.test;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
|
||||
@SpringBootTest
|
||||
class TestApplicationTests {
|
||||
|
||||
@Test
|
||||
void contextLoads() {
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue