This commit is contained in:
中青华年 2025-04-14 17:01:00 +08:00
commit 5224cfb1a7
44 changed files with 1586 additions and 0 deletions

8
.idea/.gitignore vendored Normal file
View File

@ -0,0 +1,8 @@
# 默认忽略的文件
/shelf/
/workspace.xml
# 基于编辑器的 HTTP 客户端请求
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

19
.idea/compiler.xml Normal file
View File

@ -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>

17
.idea/dataSources.xml Normal file
View File

@ -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>

6
.idea/encodings.xml Normal file
View File

@ -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>

20
.idea/jarRepositories.xml Normal file
View File

@ -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>

14
.idea/misc.xml Normal file
View File

@ -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>

6
.idea/vcs.xml Normal file
View File

@ -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>

19
.mvn/wrapper/maven-wrapper.properties vendored Normal file
View File

@ -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

77
pom.xml Normal file
View File

@ -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
server.log Normal file
View File

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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", "获取结果失败"));
}
}
}

View File

@ -0,0 +1,9 @@
package com.yang.test.domain;
import lombok.Data;
import java.util.List;
@Data
public class Answer {
private List<Integer> answers;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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", "系统内部错误"));
}
}

View File

@ -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);
}
}

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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);
}
}

View File

@ -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();
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}
}

View File

@ -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;
}
}

View File

@ -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

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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() {
}
}