init: 初始化项目

This commit is contained in:
胡海星 2025-02-20 15:34:19 +08:00
commit 0852760852
155 changed files with 5887 additions and 0 deletions

161
backend/pom.xml Normal file
View File

@ -0,0 +1,161 @@
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>ltd.qubit</groupId>
<artifactId>llm-survey-api</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>
<properties>
<java.version>17</java.version>
<maven.compiler.source>${java.version}</maven.compiler.source>
<maven.compiler.target>${java.version}</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<spring.version>5.3.31</spring.version>
<mybatis.version>3.5.15</mybatis.version>
<mybatis-spring.version>2.1.2</mybatis-spring.version>
<mysql-connector.version>8.3.0</mysql-connector.version>
<hikaricp.version>5.1.0</hikaricp.version>
<jackson.version>2.16.1</jackson.version>
<lombok.version>1.18.30</lombok.version>
<slf4j.version>2.0.11</slf4j.version>
<logback.version>1.4.14</logback.version>
<servlet-api.version>4.0.1</servlet-api.version>
</properties>
<dependencies>
<!-- Spring Framework -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>${spring.version}</version>
</dependency>
<!-- MyBatis -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>${mybatis.version}</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>${mybatis-spring.version}</version>
</dependency>
<!-- MySQL Connector -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>${mysql-connector.version}</version>
</dependency>
<!-- HikariCP -->
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
<version>${hikaricp.version}</version>
</dependency>
<!-- Jackson -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${jackson.version}</version>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
<scope>provided</scope>
</dependency>
<!-- Logging -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${slf4j.version}</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>${logback.version}</version>
</dependency>
<!-- Servlet API -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>${servlet-api.version}</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<finalName>llm-survey-api</finalName>
<plugins>
<!-- 编译插件 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
<configuration>
<release>${java.version}</release>
<encoding>${project.build.sourceEncoding}</encoding>
<annotationProcessorPaths>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
<!-- WAR打包插件 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>3.4.0</version>
<configuration>
<failOnMissingWebXml>false</failOnMissingWebXml>
</configuration>
</plugin>
</plugins>
<!-- 资源文件配置 -->
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
</resource>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.xml</include>
</includes>
<filtering>true</filtering>
</resource>
</resources>
</build>
</project>

View File

@ -0,0 +1,78 @@
package ltd.qubit.survey.controller;
import lombok.extern.slf4j.Slf4j;
import ltd.qubit.survey.model.ErrorInfo;
import org.springframework.dao.DataAccessException;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.BindException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
/**
* 全局异常处理器
*/
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
/**
* 处理参数校验异常
*/
@ExceptionHandler(BindException.class)
public ResponseEntity<ErrorInfo> handleBindException(BindException e) {
String message = e.getBindingResult().getFieldErrors().stream()
.map(error -> error.getField() + ": " + error.getDefaultMessage())
.reduce((a, b) -> a + "; " + b)
.orElse("参数错误");
ErrorInfo error = ErrorInfo.of(
"INVALID_ARGUMENT",
"参数校验失败",
message,
e.getObjectName());
return new ResponseEntity<>(error, HttpStatus.BAD_REQUEST);
}
/**
* 处理业务异常
*/
@ExceptionHandler(IllegalArgumentException.class)
public ResponseEntity<ErrorInfo> handleIllegalArgumentException(IllegalArgumentException e) {
log.warn("业务异常", e);
ErrorInfo error = ErrorInfo.of(
"BAD_REQUEST",
e.getMessage(),
e.getClass().getName());
return new ResponseEntity<>(error, HttpStatus.BAD_REQUEST);
}
/**
* 处理数据访问异常
*/
@ExceptionHandler(DataAccessException.class)
public ResponseEntity<ErrorInfo> handleDataAccessException(DataAccessException e) {
log.error("数据访问异常", e);
ErrorInfo error = ErrorInfo.of(
"DATABASE_ERROR",
"数据库访问错误",
e.getMessage(),
e.getClass().getName());
return new ResponseEntity<>(error, HttpStatus.INTERNAL_SERVER_ERROR);
}
/**
* 处理其他未知异常
*/
@ExceptionHandler(Exception.class)
public ResponseEntity<ErrorInfo> handleUnknownException(Exception e) {
log.error("系统异常", e);
ErrorInfo error = ErrorInfo.of(
"SYSTEM_ERROR",
"系统错误",
e.getMessage(),
e.getClass().getName());
return new ResponseEntity<>(error, HttpStatus.INTERNAL_SERVER_ERROR);
}
}

View File

@ -0,0 +1,61 @@
package ltd.qubit.survey.controller;
import java.util.List;
import lombok.RequiredArgsConstructor;
import ltd.qubit.survey.model.Question;
import ltd.qubit.survey.model.Option;
import ltd.qubit.survey.service.QuestionService;
import ltd.qubit.survey.service.OptionService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 问题控制器
*/
@RestController
@RequiredArgsConstructor
public class QuestionController {
private final QuestionService questionService;
private final OptionService optionService;
/**
* 获取用户的问题列表
*
* @param userId 用户ID
* @return 问题列表
*/
@GetMapping("/question/user/{userId}")
public List<Question> getUserQuestions(@PathVariable Long userId) {
return questionService.getUserQuestions(userId);
}
/**
* 获取问题的选项列表
*
* @param questionId 问题ID
* @return 选项列表
*/
@GetMapping("/question/{questionId}/option")
public List<Option> getQuestionOptions(@PathVariable Long questionId) {
return optionService.findByQuestionId(questionId);
}
/**
* 获取下一个问题
*
* @param userId 用户ID
* @param currentQuestionNumber 当前问题序号
* @param selectedOptions 选中的选项列表
* @return 下一个问题
*/
@GetMapping("/question/next")
public Question getNextQuestion(
@PathVariable Long userId,
@PathVariable Integer currentQuestionNumber,
@PathVariable List<String> selectedOptions) {
return questionService.getNextQuestion(userId, currentQuestionNumber, selectedOptions)
.orElseThrow(() -> new IllegalArgumentException("没有更多问题了"));
}
}

View File

@ -0,0 +1,56 @@
package ltd.qubit.survey.controller;
import java.util.List;
import lombok.RequiredArgsConstructor;
import ltd.qubit.survey.model.SurveyResponse;
import ltd.qubit.survey.service.SurveyResponseService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
/**
* 问卷控制器
*/
@RestController
@RequiredArgsConstructor
public class SurveyController {
private final SurveyResponseService surveyResponseService;
/**
* 提交问卷答案
*
* @param userId 用户ID
* @param responses 答案列表
* @return 提交成功的答案列表
*/
@PostMapping("/survey/submit/{userId}")
public List<SurveyResponse> submitSurvey(
@PathVariable Long userId,
@RequestBody List<SurveyResponse> responses) {
return surveyResponseService.submitSurvey(userId, responses);
}
/**
* 获取用户的问卷答案
*
* @param userId 用户ID
* @return 答案列表
*/
@GetMapping("/survey/user/{userId}")
public List<SurveyResponse> getUserResponses(@PathVariable Long userId) {
return surveyResponseService.findByUserId(userId);
}
/**
* 获取问题的所有答案
*
* @param questionId 问题ID
* @return 答案列表
*/
@GetMapping("/survey/question/{questionId}")
public List<SurveyResponse> getQuestionResponses(@PathVariable Long questionId) {
return surveyResponseService.findByQuestionId(questionId);
}
}

View File

@ -0,0 +1,53 @@
package ltd.qubit.survey.controller;
import lombok.RequiredArgsConstructor;
import ltd.qubit.survey.model.User;
import ltd.qubit.survey.service.UserService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
/**
* 用户控制器
*/
@RestController
@RequiredArgsConstructor
public class UserController {
private final UserService userService;
/**
* 用户注册
*
* @param user 用户信息
* @return 注册成功的用户信息
*/
@PostMapping("/user/register")
public User register(@RequestBody User user) {
return userService.register(user);
}
/**
* 根据ID查询用户
*
* @param id 用户ID
* @return 用户信息
*/
@GetMapping("/user/{id}")
public User findById(@PathVariable Long id) {
return userService.findById(id)
.orElseThrow(() -> new IllegalArgumentException("用户不存在"));
}
/**
* 检查手机号是否已注册
*
* @param phone 手机号
* @return 是否已注册
*/
@GetMapping("/user/check/{phone}")
public boolean checkPhone(@PathVariable String phone) {
return userService.isPhoneRegistered(phone);
}
}

View File

@ -0,0 +1,51 @@
package ltd.qubit.survey.dao;
import java.util.List;
import java.util.Optional;
/**
* 基础DAO接口定义通用的CRUD操作
*
* @param <T> 实体类型
* @param <K> 主键类型
*/
public interface BaseDao<T, K> {
/**
* 插入一条记录
*
* @param entity 实体对象
* @return 影响的行数
*/
int insert(T entity);
/**
* 根据主键删除记录
*
* @param id 主键
* @return 影响的行数
*/
int deleteById(K id);
/**
* 更新记录
*
* @param entity 实体对象
* @return 影响的行数
*/
int update(T entity);
/**
* 根据主键查询
*
* @param id 主键
* @return 实体对象
*/
Optional<T> findById(K id);
/**
* 查询所有记录
*
* @return 实体对象列表
*/
List<T> findAll();
}

View File

@ -0,0 +1,43 @@
package ltd.qubit.survey.dao;
import java.util.List;
import java.util.Optional;
import ltd.qubit.survey.model.Option;
/**
* 选项DAO接口
*/
public interface OptionDao extends BaseDao<Option, Long> {
/**
* 根据问题ID查询选项列表
*
* @param questionId 问题ID
* @return 选项列表
*/
List<Option> findByQuestionId(Long questionId);
/**
* 根据问题ID和选项代码查询
*
* @param questionId 问题ID
* @param optionCode 选项代码
* @return 选项对象
*/
Optional<Option> findByQuestionIdAndCode(Long questionId, String optionCode);
/**
* 批量插入选项
*
* @param options 选项列表
* @return 影响的行数
*/
int batchInsert(List<Option> options);
/**
* 根据问题ID删除所有选项
*
* @param questionId 问题ID
* @return 影响的行数
*/
int deleteByQuestionId(Long questionId);
}

View File

@ -0,0 +1,41 @@
package ltd.qubit.survey.dao;
import java.util.List;
import java.util.Optional;
import ltd.qubit.survey.model.Question;
import ltd.qubit.survey.model.WorkArea;
/**
* 问题DAO接口
*/
public interface QuestionDao extends BaseDao<Question, Long> {
/**
* 根据问题序号查询
*
* @param questionNumber 问题序号
* @return 问题对象
*/
Optional<Question> findByQuestionNumber(Integer questionNumber);
/**
* 根据工作领域查询问题列表
*
* @param workArea 工作领域
* @return 问题列表
*/
List<Question> findByWorkArea(WorkArea workArea);
/**
* 查询通用问题列表不针对特定工作领域
*
* @return 问题列表
*/
List<Question> findCommonQuestions();
/**
* 获取下一个可用的问题序号
*
* @return 下一个问题序号
*/
Integer getNextQuestionNumber();
}

View File

@ -0,0 +1,51 @@
package ltd.qubit.survey.dao;
import java.util.List;
import java.util.Optional;
import ltd.qubit.survey.model.SurveyResponse;
/**
* 问卷答案DAO接口
*/
public interface SurveyResponseDao extends BaseDao<SurveyResponse, Long> {
/**
* 根据用户ID查询答案列表
*
* @param userId 用户ID
* @return 答案列表
*/
List<SurveyResponse> findByUserId(Long userId);
/**
* 根据问题ID查询答案列表
*
* @param questionId 问题ID
* @return 答案列表
*/
List<SurveyResponse> findByQuestionId(Long questionId);
/**
* 根据用户ID和问题ID查询答案
*
* @param userId 用户ID
* @param questionId 问题ID
* @return 答案对象
*/
Optional<SurveyResponse> findByUserIdAndQuestionId(Long userId, Long questionId);
/**
* 批量插入答案
*
* @param responses 答案列表
* @return 影响的行数
*/
int batchInsert(List<SurveyResponse> responses);
/**
* 根据用户ID删除所有答案
*
* @param userId 用户ID
* @return 影响的行数
*/
int deleteByUserId(Long userId);
}

View File

@ -0,0 +1,27 @@
package ltd.qubit.survey.dao;
import java.util.List;
import java.util.Optional;
import ltd.qubit.survey.model.User;
import ltd.qubit.survey.model.WorkArea;
/**
* 用户DAO接口
*/
public interface UserDao extends BaseDao<User, Long> {
/**
* 根据手机号查询用户
*
* @param phone 手机号
* @return 用户对象
*/
Optional<User> findByPhone(String phone);
/**
* 根据工作领域查询用户列表
*
* @param workArea 工作领域
* @return 用户列表
*/
List<User> findByWorkArea(WorkArea workArea);
}

View File

@ -0,0 +1,65 @@
package ltd.qubit.survey.model;
import lombok.Data;
/**
* 错误信息
*/
@Data
public class ErrorInfo {
/**
* 错误代码
*/
private String code;
/**
* 错误消息
*/
private String message;
/**
* 错误详情
*/
private String detail;
/**
* 时间戳
*/
private long timestamp;
/**
* 请求路径
*/
private String path;
/**
* 创建错误信息
*
* @param code 错误代码
* @param message 错误消息
* @param detail 错误详情
* @param path 请求路径
* @return 错误信息
*/
public static ErrorInfo of(String code, String message, String detail, String path) {
ErrorInfo error = new ErrorInfo();
error.setCode(code);
error.setMessage(message);
error.setDetail(detail);
error.setPath(path);
error.setTimestamp(System.currentTimeMillis());
return error;
}
/**
* 创建错误信息无详情
*
* @param code 错误代码
* @param message 错误消息
* @param path 请求路径
* @return 错误信息
*/
public static ErrorInfo of(String code, String message, String path) {
return of(code, message, null, path);
}
}

View File

@ -0,0 +1,40 @@
package ltd.qubit.survey.model;
import java.time.LocalDateTime;
import lombok.Data;
/**
* 问题选项实体类
*/
@Data
public class Option {
/**
* 选项ID
*/
private Long id;
/**
* 关联的问题ID
*/
private Long questionId;
/**
* 选项代码如ABC
*/
private String optionCode;
/**
* 选项内容
*/
private String content;
/**
* 是否需要填写文本
*/
private Boolean requiresText;
/**
* 创建时间
*/
private LocalDateTime createdAt;
}

View File

@ -0,0 +1,36 @@
package ltd.qubit.survey.model;
/**
* 岗位性质枚举
*/
public enum PositionType {
/**
* 管理岗
*/
MANAGEMENT("管理岗"),
/**
* 技术岗
*/
TECHNICAL("技术岗"),
/**
* 业务岗
*/
BUSINESS("业务岗"),
/**
* 职能支持岗
*/
SUPPORT("职能支持岗");
private final String displayName;
PositionType(String displayName) {
this.displayName = displayName;
}
public String getDisplayName() {
return displayName;
}
}

View File

@ -0,0 +1,50 @@
package ltd.qubit.survey.model;
import java.time.LocalDateTime;
import lombok.Data;
/**
* 问题实体类
*/
@Data
public class Question {
/**
* 问题ID
*/
private Long id;
/**
* 问题序号
*/
private Integer questionNumber;
/**
* 问题内容
*/
private String content;
/**
* 问题类型单选多选文本
*/
private QuestionType questionType;
/**
* 针对的工作领域为null表示通用问题
*/
private WorkArea workArea;
/**
* 是否必答
*/
private Boolean isRequired;
/**
* 跳转逻辑JSON格式
*/
private String nextQuestionLogic;
/**
* 创建时间
*/
private LocalDateTime createdAt;
}

View File

@ -0,0 +1,21 @@
package ltd.qubit.survey.model;
/**
* 问题类型枚举
*/
public enum QuestionType {
/**
* 单选题
*/
SINGLE_CHOICE,
/**
* 多选题
*/
MULTIPLE_CHOICE,
/**
* 文本题
*/
TEXT
}

View File

@ -0,0 +1,41 @@
package ltd.qubit.survey.model;
import java.time.LocalDateTime;
import java.util.List;
import lombok.Data;
/**
* 问卷答案实体类
*/
@Data
public class SurveyResponse {
/**
* 答案ID
*/
private Long id;
/**
* 用户ID
*/
private Long userId;
/**
* 问题ID
*/
private Long questionId;
/**
* 选中的选项代码列表JSON格式
*/
private List<String> selectedOptions;
/**
* 文本答案
*/
private String textAnswer;
/**
* 创建时间
*/
private LocalDateTime createdAt;
}

View File

@ -0,0 +1,40 @@
package ltd.qubit.survey.model;
import java.time.LocalDateTime;
import lombok.Data;
/**
* 用户信息实体类
*/
@Data
public class User {
/**
* 用户ID
*/
private Long id;
/**
* 姓名
*/
private String name;
/**
* 手机号码
*/
private String phone;
/**
* 工作领域
*/
private WorkArea workArea;
/**
* 岗位性质
*/
private PositionType positionType;
/**
* 创建时间
*/
private LocalDateTime createdAt;
}

View File

@ -0,0 +1,51 @@
package ltd.qubit.survey.model;
/**
* 工作领域枚举
*/
public enum WorkArea {
/**
* 研发领域
*/
RD("研发"),
/**
* 项目领域
*/
PROJECT("项目"),
/**
* 保险领域
*/
INSURANCE("保险"),
/**
* 财务领域
*/
FINANCE("财务"),
/**
* 运营领域
*/
OPERATION("运营"),
/**
* 客服领域
*/
CUSTOMER_SERVICE("客服"),
/**
* 综合管理领域
*/
ADMIN("综合管理");
private final String displayName;
WorkArea(String displayName) {
this.displayName = displayName;
}
public String getDisplayName() {
return displayName;
}
}

View File

@ -0,0 +1,55 @@
package ltd.qubit.survey.service;
import java.util.List;
import java.util.Optional;
import org.springframework.transaction.annotation.Transactional;
/**
* 基础Service接口定义通用的CRUD操作
*
* @param <T> 实体类型
* @param <K> 主键类型
*/
@Transactional(readOnly = true)
public interface BaseService<T, K> {
/**
* 新增
*
* @param entity 实体对象
* @return 实体对象
*/
@Transactional
T create(T entity);
/**
* 删除
*
* @param id 主键
*/
@Transactional
void delete(K id);
/**
* 更新
*
* @param entity 实体对象
* @return 实体对象
*/
@Transactional
T update(T entity);
/**
* 根据ID查询
*
* @param id 主键
* @return 实体对象
*/
Optional<T> findById(K id);
/**
* 查询所有
*
* @return 实体对象列表
*/
List<T> findAll();
}

View File

@ -0,0 +1,42 @@
package ltd.qubit.survey.service;
import java.util.List;
import java.util.Optional;
import ltd.qubit.survey.model.Option;
/**
* 选项服务接口
*/
public interface OptionService extends BaseService<Option, Long> {
/**
* 根据问题ID查询选项列表
*
* @param questionId 问题ID
* @return 选项列表
*/
List<Option> findByQuestionId(Long questionId);
/**
* 根据问题ID和选项代码查询
*
* @param questionId 问题ID
* @param optionCode 选项代码
* @return 选项对象
*/
Optional<Option> findByQuestionIdAndCode(Long questionId, String optionCode);
/**
* 批量创建选项
*
* @param options 选项列表
* @return 创建成功的选项列表
*/
List<Option> batchCreate(List<Option> options);
/**
* 删除问题的所有选项
*
* @param questionId 问题ID
*/
void deleteByQuestionId(Long questionId);
}

View File

@ -0,0 +1,54 @@
package ltd.qubit.survey.service;
import java.util.List;
import java.util.Optional;
import ltd.qubit.survey.model.Question;
import ltd.qubit.survey.model.WorkArea;
import org.springframework.transaction.annotation.Transactional;
/**
* 问题服务接口
*/
@Transactional(readOnly = true)
public interface QuestionService extends BaseService<Question, Long> {
/**
* 根据问题序号查询
*
* @param questionNumber 问题序号
* @return 问题对象
*/
Optional<Question> findByQuestionNumber(Integer questionNumber);
/**
* 根据工作领域查询问题列表
*
* @param workArea 工作领域
* @return 问题列表
*/
List<Question> findByWorkArea(WorkArea workArea);
/**
* 查询通用问题列表不针对特定工作领域
*
* @return 问题列表
*/
List<Question> findCommonQuestions();
/**
* 获取用户的下一个问题
*
* @param userId 用户ID
* @param currentQuestionNumber 当前问题序号
* @param selectedOptions 当前问题的选择项
* @return 下一个问题
*/
Optional<Question> getNextQuestion(Long userId, Integer currentQuestionNumber, List<String> selectedOptions);
/**
* 获取用户的问题列表包括通用问题和针对其工作领域的问题
*
* @param userId 用户ID
* @return 问题列表
*/
List<Question> getUserQuestions(Long userId);
}

View File

@ -0,0 +1,64 @@
package ltd.qubit.survey.service;
import java.util.List;
import java.util.Optional;
import ltd.qubit.survey.model.SurveyResponse;
import org.springframework.transaction.annotation.Transactional;
/**
* 问卷答案服务接口
*/
@Transactional(readOnly = true)
public interface SurveyResponseService extends BaseService<SurveyResponse, Long> {
/**
* 根据用户ID查询答案列表
*
* @param userId 用户ID
* @return 答案列表
*/
List<SurveyResponse> findByUserId(Long userId);
/**
* 根据问题ID查询答案列表
*
* @param questionId 问题ID
* @return 答案列表
*/
List<SurveyResponse> findByQuestionId(Long questionId);
/**
* 根据用户ID和问题ID查询答案
*
* @param userId 用户ID
* @param questionId 问题ID
* @return 答案对象
*/
Optional<SurveyResponse> findByUserIdAndQuestionId(Long userId, Long questionId);
/**
* 批量保存答案
*
* @param responses 答案列表
* @return 保存成功的答案列表
*/
@Transactional
List<SurveyResponse> batchSave(List<SurveyResponse> responses);
/**
* 删除用户的所有答案
*
* @param userId 用户ID
*/
@Transactional
void deleteByUserId(Long userId);
/**
* 提交问卷答案
*
* @param userId 用户ID
* @param responses 答案列表
* @return 提交成功的答案列表
*/
@Transactional
List<SurveyResponse> submitSurvey(Long userId, List<SurveyResponse> responses);
}

View File

@ -0,0 +1,46 @@
package ltd.qubit.survey.service;
import java.util.List;
import java.util.Optional;
import ltd.qubit.survey.model.User;
import ltd.qubit.survey.model.WorkArea;
import org.springframework.transaction.annotation.Transactional;
/**
* 用户服务接口
*/
@Transactional(readOnly = true)
public interface UserService extends BaseService<User, Long> {
/**
* 根据手机号查询用户
*
* @param phone 手机号
* @return 用户对象
*/
Optional<User> findByPhone(String phone);
/**
* 根据工作领域查询用户列表
*
* @param workArea 工作领域
* @return 用户列表
*/
List<User> findByWorkArea(WorkArea workArea);
/**
* 用户注册
*
* @param user 用户信息
* @return 注册成功的用户
*/
@Transactional
User register(User user);
/**
* 检查手机号是否已被注册
*
* @param phone 手机号
* @return 是否已注册
*/
boolean isPhoneRegistered(String phone);
}

View File

@ -0,0 +1,73 @@
package ltd.qubit.survey.service.impl;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Optional;
import lombok.RequiredArgsConstructor;
import ltd.qubit.survey.dao.OptionDao;
import ltd.qubit.survey.model.Option;
import ltd.qubit.survey.service.OptionService;
import org.springframework.stereotype.Service;
/**
* 选项服务实现类
*/
@Service
@RequiredArgsConstructor
public class OptionServiceImpl implements OptionService {
private final OptionDao optionDao;
@Override
public Option create(Option option) {
option.setCreatedAt(LocalDateTime.now());
optionDao.insert(option);
return option;
}
@Override
public void delete(Long id) {
optionDao.deleteById(id);
}
@Override
public Option update(Option option) {
optionDao.update(option);
return option;
}
@Override
public Optional<Option> findById(Long id) {
return optionDao.findById(id);
}
@Override
public List<Option> findAll() {
return optionDao.findAll();
}
@Override
public List<Option> findByQuestionId(Long questionId) {
return optionDao.findByQuestionId(questionId);
}
@Override
public Optional<Option> findByQuestionIdAndCode(Long questionId, String optionCode) {
return optionDao.findByQuestionIdAndCode(questionId, optionCode);
}
@Override
public List<Option> batchCreate(List<Option> options) {
// 设置创建时间
LocalDateTime now = LocalDateTime.now();
options.forEach(option -> option.setCreatedAt(now));
// 批量插入
optionDao.batchInsert(options);
return options;
}
@Override
public void deleteByQuestionId(Long questionId) {
optionDao.deleteByQuestionId(questionId);
}
}

View File

@ -0,0 +1,128 @@
package ltd.qubit.survey.service.impl;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import lombok.RequiredArgsConstructor;
import ltd.qubit.survey.dao.QuestionDao;
import ltd.qubit.survey.model.Question;
import ltd.qubit.survey.model.User;
import ltd.qubit.survey.model.WorkArea;
import ltd.qubit.survey.service.QuestionService;
import ltd.qubit.survey.service.UserService;
import org.springframework.stereotype.Service;
/**
* 问题服务实现类
*/
@Service
@RequiredArgsConstructor
public class QuestionServiceImpl implements QuestionService {
private final QuestionDao questionDao;
private final UserService userService;
private final ObjectMapper objectMapper;
@Override
public Question create(Question question) {
// 如果没有指定问题序号则自动生成
if (question.getQuestionNumber() == null) {
question.setQuestionNumber(questionDao.getNextQuestionNumber());
}
question.setCreatedAt(LocalDateTime.now());
questionDao.insert(question);
return question;
}
@Override
public void delete(Long id) {
questionDao.deleteById(id);
}
@Override
public Question update(Question question) {
questionDao.update(question);
return question;
}
@Override
public Optional<Question> findById(Long id) {
return questionDao.findById(id);
}
@Override
public List<Question> findAll() {
return questionDao.findAll();
}
@Override
public Optional<Question> findByQuestionNumber(Integer questionNumber) {
return questionDao.findByQuestionNumber(questionNumber);
}
@Override
public List<Question> findByWorkArea(WorkArea workArea) {
return questionDao.findByWorkArea(workArea);
}
@Override
public List<Question> findCommonQuestions() {
return questionDao.findCommonQuestions();
}
@Override
public Optional<Question> getNextQuestion(Long userId, Integer currentQuestionNumber, List<String> selectedOptions) {
// 获取当前问题
Optional<Question> currentQuestion = findByQuestionNumber(currentQuestionNumber);
if (currentQuestion.isEmpty()) {
return Optional.empty();
}
// 如果当前问题有跳转逻辑则根据选项判断下一个问题
String nextQuestionLogic = currentQuestion.get().getNextQuestionLogic();
if (nextQuestionLogic != null && !nextQuestionLogic.isEmpty()) {
try {
// 解析跳转逻辑JSON
Map<String, Integer> logic = objectMapper.readValue(nextQuestionLogic,
new TypeReference<Map<String, Integer>>() {});
// 根据选项确定下一个问题序号
for (String option : selectedOptions) {
if (logic.containsKey(option)) {
return findByQuestionNumber(logic.get(option));
}
}
} catch (Exception e) {
// JSON解析错误继续使用默认的下一个问题
}
}
// 如果没有特殊跳转逻辑则返回序号加1的问题
return findByQuestionNumber(currentQuestionNumber + 1);
}
@Override
public List<Question> getUserQuestions(Long userId) {
List<Question> questions = new ArrayList<>();
// 获取用户信息
Optional<User> user = userService.findById(userId);
if (user.isEmpty()) {
return questions;
}
// 添加通用问题
questions.addAll(findCommonQuestions());
// 添加针对用户工作领域的问题
questions.addAll(findByWorkArea(user.get().getWorkArea()));
// 按问题序号排序
questions.sort((q1, q2) -> q1.getQuestionNumber().compareTo(q2.getQuestionNumber()));
return questions;
}
}

View File

@ -0,0 +1,111 @@
package ltd.qubit.survey.service.impl;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Optional;
import lombok.RequiredArgsConstructor;
import ltd.qubit.survey.dao.SurveyResponseDao;
import ltd.qubit.survey.model.Question;
import ltd.qubit.survey.model.SurveyResponse;
import ltd.qubit.survey.service.QuestionService;
import ltd.qubit.survey.service.SurveyResponseService;
import org.springframework.stereotype.Service;
/**
* 问卷答案服务实现类
*/
@Service
@RequiredArgsConstructor
public class SurveyResponseServiceImpl implements SurveyResponseService {
private final SurveyResponseDao surveyResponseDao;
private final QuestionService questionService;
@Override
public SurveyResponse create(SurveyResponse response) {
response.setCreatedAt(LocalDateTime.now());
surveyResponseDao.insert(response);
return response;
}
@Override
public void delete(Long id) {
surveyResponseDao.deleteById(id);
}
@Override
public SurveyResponse update(SurveyResponse response) {
surveyResponseDao.update(response);
return response;
}
@Override
public Optional<SurveyResponse> findById(Long id) {
return surveyResponseDao.findById(id);
}
@Override
public List<SurveyResponse> findAll() {
return surveyResponseDao.findAll();
}
@Override
public List<SurveyResponse> findByUserId(Long userId) {
return surveyResponseDao.findByUserId(userId);
}
@Override
public List<SurveyResponse> findByQuestionId(Long questionId) {
return surveyResponseDao.findByQuestionId(questionId);
}
@Override
public Optional<SurveyResponse> findByUserIdAndQuestionId(Long userId, Long questionId) {
return surveyResponseDao.findByUserIdAndQuestionId(userId, questionId);
}
@Override
public List<SurveyResponse> batchSave(List<SurveyResponse> responses) {
// 设置创建时间
LocalDateTime now = LocalDateTime.now();
responses.forEach(response -> response.setCreatedAt(now));
// 批量插入
surveyResponseDao.batchInsert(responses);
return responses;
}
@Override
public void deleteByUserId(Long userId) {
surveyResponseDao.deleteByUserId(userId);
}
@Override
public List<SurveyResponse> submitSurvey(Long userId, List<SurveyResponse> responses) {
// 验证所有必答题是否已回答
List<Question> questions = questionService.getUserQuestions(userId);
for (Question question : questions) {
if (question.getIsRequired()) {
boolean answered = responses.stream()
.anyMatch(response -> response.getQuestionId().equals(question.getId()));
if (!answered) {
throw new IllegalArgumentException(
String.format("问题 %d 为必答题,请填写答案", question.getQuestionNumber()));
}
}
}
// 删除用户之前的答案
deleteByUserId(userId);
// 设置用户ID和创建时间
LocalDateTime now = LocalDateTime.now();
responses.forEach(response -> {
response.setUserId(userId);
response.setCreatedAt(now);
});
// 批量保存答案
surveyResponseDao.batchInsert(responses);
return responses;
}
}

View File

@ -0,0 +1,72 @@
package ltd.qubit.survey.service.impl;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Optional;
import lombok.RequiredArgsConstructor;
import ltd.qubit.survey.dao.UserDao;
import ltd.qubit.survey.model.User;
import ltd.qubit.survey.model.WorkArea;
import ltd.qubit.survey.service.UserService;
import org.springframework.stereotype.Service;
/**
* 用户服务实现类
*/
@Service
@RequiredArgsConstructor
public class UserServiceImpl implements UserService {
private final UserDao userDao;
@Override
public User create(User user) {
user.setCreatedAt(LocalDateTime.now());
userDao.insert(user);
return user;
}
@Override
public void delete(Long id) {
userDao.deleteById(id);
}
@Override
public User update(User user) {
userDao.update(user);
return user;
}
@Override
public Optional<User> findById(Long id) {
return userDao.findById(id);
}
@Override
public List<User> findAll() {
return userDao.findAll();
}
@Override
public Optional<User> findByPhone(String phone) {
return userDao.findByPhone(phone);
}
@Override
public List<User> findByWorkArea(WorkArea workArea) {
return userDao.findByWorkArea(workArea);
}
@Override
public User register(User user) {
// 检查手机号是否已注册
if (isPhoneRegistered(user.getPhone())) {
throw new IllegalArgumentException("手机号已被注册");
}
return create(user);
}
@Override
public boolean isPhoneRegistered(String phone) {
return userDao.findByPhone(phone).isPresent();
}
}

View File

@ -0,0 +1,18 @@
# 数据库配置
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://127.0.0.1:3306/llm_survey?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
jdbc.username=dev
jdbc.password=
# 连接池配置
jdbc.pool.minimumIdle=5
jdbc.pool.maximumPoolSize=20
jdbc.pool.idleTimeout=300000
jdbc.pool.connectionTimeout=20000
jdbc.pool.connectionTestQuery=SELECT 1
# 日志配置
logging.level.root=INFO
logging.level.ltd.qubit.survey=DEBUG
logging.level.org.springframework=INFO
logging.level.org.mybatis=DEBUG

View File

@ -0,0 +1,83 @@
<?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="ltd.qubit.survey.dao.OptionDao">
<!-- 结果映射 -->
<resultMap id="optionMap" type="ltd.qubit.survey.model.Option">
<id property="id" column="id"/>
<result property="questionId" column="question_id"/>
<result property="optionCode" column="option_code"/>
<result property="content" column="content"/>
<result property="requiresText" column="requires_text"/>
<result property="createdAt" column="created_at"/>
</resultMap>
<!-- 基础列 -->
<sql id="baseColumns">
id, question_id, option_code, content, requires_text, created_at
</sql>
<!-- 插入 -->
<insert id="insert" parameterType="ltd.qubit.survey.model.Option" useGeneratedKeys="true" keyProperty="id">
INSERT INTO options (question_id, option_code, content, requires_text)
VALUES (#{questionId}, #{optionCode}, #{content}, #{requiresText})
</insert>
<!-- 批量插入 -->
<insert id="batchInsert" parameterType="java.util.List">
INSERT INTO options (question_id, option_code, content, requires_text)
VALUES
<foreach collection="list" item="item" separator=",">
(#{item.questionId}, #{item.optionCode}, #{item.content}, #{item.requiresText})
</foreach>
</insert>
<!-- 更新 -->
<update id="update" parameterType="ltd.qubit.survey.model.Option">
UPDATE options
SET question_id = #{questionId},
option_code = #{optionCode},
content = #{content},
requires_text = #{requiresText}
WHERE id = #{id}
</update>
<!-- 删除 -->
<delete id="deleteById" parameterType="long">
DELETE FROM options WHERE id = #{id}
</delete>
<!-- 根据问题ID删除 -->
<delete id="deleteByQuestionId" parameterType="long">
DELETE FROM options WHERE question_id = #{questionId}
</delete>
<!-- 根据ID查询 -->
<select id="findById" parameterType="long" resultMap="optionMap">
SELECT <include refid="baseColumns"/>
FROM options
WHERE id = #{id}
</select>
<!-- 查询所有 -->
<select id="findAll" resultMap="optionMap">
SELECT <include refid="baseColumns"/>
FROM options
ORDER BY question_id, option_code
</select>
<!-- 根据问题ID查询 -->
<select id="findByQuestionId" parameterType="long" resultMap="optionMap">
SELECT <include refid="baseColumns"/>
FROM options
WHERE question_id = #{questionId}
ORDER BY option_code
</select>
<!-- 根据问题ID和选项代码查询 -->
<select id="findByQuestionIdAndCode" resultMap="optionMap">
SELECT <include refid="baseColumns"/>
FROM options
WHERE question_id = #{questionId}
AND option_code = #{optionCode}
</select>
</mapper>

View File

@ -0,0 +1,86 @@
<?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="ltd.qubit.survey.dao.QuestionDao">
<!-- 结果映射 -->
<resultMap id="questionMap" type="ltd.qubit.survey.model.Question">
<id property="id" column="id"/>
<result property="questionNumber" column="question_number"/>
<result property="content" column="content"/>
<result property="questionType" column="question_type"/>
<result property="workArea" column="work_area"/>
<result property="isRequired" column="is_required"/>
<result property="nextQuestionLogic" column="next_question_logic"/>
<result property="createdAt" column="created_at"/>
</resultMap>
<!-- 基础列 -->
<sql id="baseColumns">
id, question_number, content, question_type, work_area, is_required, next_question_logic, created_at
</sql>
<!-- 插入 -->
<insert id="insert" parameterType="ltd.qubit.survey.model.Question" useGeneratedKeys="true" keyProperty="id">
INSERT INTO questions (question_number, content, question_type, work_area, is_required, next_question_logic)
VALUES (#{questionNumber}, #{content}, #{questionType}, #{workArea}, #{isRequired}, #{nextQuestionLogic})
</insert>
<!-- 更新 -->
<update id="update" parameterType="ltd.qubit.survey.model.Question">
UPDATE questions
SET question_number = #{questionNumber},
content = #{content},
question_type = #{questionType},
work_area = #{workArea},
is_required = #{isRequired},
next_question_logic = #{nextQuestionLogic}
WHERE id = #{id}
</update>
<!-- 删除 -->
<delete id="deleteById" parameterType="long">
DELETE FROM questions WHERE id = #{id}
</delete>
<!-- 根据ID查询 -->
<select id="findById" parameterType="long" resultMap="questionMap">
SELECT <include refid="baseColumns"/>
FROM questions
WHERE id = #{id}
</select>
<!-- 查询所有 -->
<select id="findAll" resultMap="questionMap">
SELECT <include refid="baseColumns"/>
FROM questions
ORDER BY question_number
</select>
<!-- 根据问题序号查询 -->
<select id="findByQuestionNumber" parameterType="int" resultMap="questionMap">
SELECT <include refid="baseColumns"/>
FROM questions
WHERE question_number = #{questionNumber}
</select>
<!-- 根据工作领域查询 -->
<select id="findByWorkArea" parameterType="string" resultMap="questionMap">
SELECT <include refid="baseColumns"/>
FROM questions
WHERE work_area = #{workArea}
ORDER BY question_number
</select>
<!-- 查询通用问题 -->
<select id="findCommonQuestions" resultMap="questionMap">
SELECT <include refid="baseColumns"/>
FROM questions
WHERE work_area IS NULL
ORDER BY question_number
</select>
<!-- 获取下一个问题序号 -->
<select id="getNextQuestionNumber" resultType="int">
SELECT COALESCE(MAX(question_number) + 1, 1)
FROM questions
</select>
</mapper>

View File

@ -0,0 +1,93 @@
<?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="ltd.qubit.survey.dao.SurveyResponseDao">
<!-- 结果映射 -->
<resultMap id="responseMap" type="ltd.qubit.survey.model.SurveyResponse">
<id property="id" column="id"/>
<result property="userId" column="user_id"/>
<result property="questionId" column="question_id"/>
<result property="selectedOptions" column="selected_options" typeHandler="org.apache.ibatis.type.JsonTypeHandler"/>
<result property="textAnswer" column="text_answer"/>
<result property="createdAt" column="created_at"/>
</resultMap>
<!-- 基础列 -->
<sql id="baseColumns">
id, user_id, question_id, selected_options, text_answer, created_at
</sql>
<!-- 插入 -->
<insert id="insert" parameterType="ltd.qubit.survey.model.SurveyResponse" useGeneratedKeys="true" keyProperty="id">
INSERT INTO survey_responses (user_id, question_id, selected_options, text_answer)
VALUES (#{userId}, #{questionId}, #{selectedOptions,typeHandler=org.apache.ibatis.type.JsonTypeHandler}, #{textAnswer})
</insert>
<!-- 批量插入 -->
<insert id="batchInsert" parameterType="java.util.List">
INSERT INTO survey_responses (user_id, question_id, selected_options, text_answer)
VALUES
<foreach collection="list" item="item" separator=",">
(#{item.userId}, #{item.questionId},
#{item.selectedOptions,typeHandler=org.apache.ibatis.type.JsonTypeHandler},
#{item.textAnswer})
</foreach>
</insert>
<!-- 更新 -->
<update id="update" parameterType="ltd.qubit.survey.model.SurveyResponse">
UPDATE survey_responses
SET user_id = #{userId},
question_id = #{questionId},
selected_options = #{selectedOptions,typeHandler=org.apache.ibatis.type.JsonTypeHandler},
text_answer = #{textAnswer}
WHERE id = #{id}
</update>
<!-- 删除 -->
<delete id="deleteById" parameterType="long">
DELETE FROM survey_responses WHERE id = #{id}
</delete>
<!-- 根据用户ID删除 -->
<delete id="deleteByUserId" parameterType="long">
DELETE FROM survey_responses WHERE user_id = #{userId}
</delete>
<!-- 根据ID查询 -->
<select id="findById" parameterType="long" resultMap="responseMap">
SELECT <include refid="baseColumns"/>
FROM survey_responses
WHERE id = #{id}
</select>
<!-- 查询所有 -->
<select id="findAll" resultMap="responseMap">
SELECT <include refid="baseColumns"/>
FROM survey_responses
ORDER BY user_id, question_id
</select>
<!-- 根据用户ID查询 -->
<select id="findByUserId" parameterType="long" resultMap="responseMap">
SELECT <include refid="baseColumns"/>
FROM survey_responses
WHERE user_id = #{userId}
ORDER BY question_id
</select>
<!-- 根据问题ID查询 -->
<select id="findByQuestionId" parameterType="long" resultMap="responseMap">
SELECT <include refid="baseColumns"/>
FROM survey_responses
WHERE question_id = #{questionId}
ORDER BY user_id
</select>
<!-- 根据用户ID和问题ID查询 -->
<select id="findByUserIdAndQuestionId" resultMap="responseMap">
SELECT <include refid="baseColumns"/>
FROM survey_responses
WHERE user_id = #{userId}
AND question_id = #{questionId}
</select>
</mapper>

View File

@ -0,0 +1,68 @@
<?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="ltd.qubit.survey.dao.UserDao">
<!-- 结果映射 -->
<resultMap id="userMap" type="ltd.qubit.survey.model.User">
<id property="id" column="id"/>
<result property="name" column="name"/>
<result property="phone" column="phone"/>
<result property="workArea" column="work_area"/>
<result property="positionType" column="position_type"/>
<result property="createdAt" column="created_at"/>
</resultMap>
<!-- 基础列 -->
<sql id="baseColumns">
id, name, phone, work_area, position_type, created_at
</sql>
<!-- 插入 -->
<insert id="insert" parameterType="ltd.qubit.survey.model.User" useGeneratedKeys="true" keyProperty="id">
INSERT INTO users (name, phone, work_area, position_type)
VALUES (#{name}, #{phone}, #{workArea}, #{positionType})
</insert>
<!-- 更新 -->
<update id="update" parameterType="ltd.qubit.survey.model.User">
UPDATE users
SET name = #{name},
phone = #{phone},
work_area = #{workArea},
position_type = #{positionType}
WHERE id = #{id}
</update>
<!-- 删除 -->
<delete id="deleteById" parameterType="long">
DELETE FROM users WHERE id = #{id}
</delete>
<!-- 根据ID查询 -->
<select id="findById" parameterType="long" resultMap="userMap">
SELECT <include refid="baseColumns"/>
FROM users
WHERE id = #{id}
</select>
<!-- 查询所有 -->
<select id="findAll" resultMap="userMap">
SELECT <include refid="baseColumns"/>
FROM users
ORDER BY id
</select>
<!-- 根据手机号查询 -->
<select id="findByPhone" parameterType="string" resultMap="userMap">
SELECT <include refid="baseColumns"/>
FROM users
WHERE phone = #{phone}
</select>
<!-- 根据工作领域查询 -->
<select id="findByWorkArea" parameterType="string" resultMap="userMap">
SELECT <include refid="baseColumns"/>
FROM users
WHERE work_area = #{workArea}
ORDER BY id
</select>
</mapper>

View File

@ -0,0 +1,32 @@
<?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>
<settings>
<!-- 开启驼峰命名自动映射 -->
<setting name="mapUnderscoreToCamelCase" value="true"/>
<!-- 开启二级缓存 -->
<setting name="cacheEnabled" value="true"/>
<!-- 开启延迟加载 -->
<setting name="lazyLoadingEnabled" value="true"/>
<!-- 设置日志实现 -->
<setting name="logImpl" value="SLF4J"/>
</settings>
<typeHandlers>
<!-- 枚举类型处理器 -->
<typeHandler handler="org.apache.ibatis.type.EnumTypeHandler"
javaType="ltd.qubit.survey.model.WorkArea"/>
<typeHandler handler="org.apache.ibatis.type.EnumTypeHandler"
javaType="ltd.qubit.survey.model.PositionType"/>
<typeHandler handler="org.apache.ibatis.type.EnumTypeHandler"
javaType="ltd.qubit.survey.model.QuestionType"/>
</typeHandlers>
<mappers>
<!-- 映射文件 -->
<mapper resource="mybatis/mapper/UserMapper.xml"/>
<mapper resource="mybatis/mapper/QuestionMapper.xml"/>
<mapper resource="mybatis/mapper/OptionMapper.xml"/>
<mapper resource="mybatis/mapper/SurveyResponseMapper.xml"/>
</mappers>
</configuration>

View File

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">
<!-- 加载属性文件 -->
<context:property-placeholder location="classpath:application.properties"/>
<!-- 开启注解扫描排除Controller -->
<context:component-scan base-package="ltd.qubit.survey">
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
<!-- 配置事务注解驱动 -->
<tx:annotation-driven/>
</beans>

View File

@ -0,0 +1,46 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd">
<!-- 开启Controller注解扫描 -->
<context:component-scan base-package="ltd.qubit.survey.controller" use-default-filters="false">
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
<!-- 开启SpringMVC注解驱动 -->
<mvc:annotation-driven>
<mvc:message-converters>
<!-- 配置JSON转换器 -->
<bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
<property name="objectMapper">
<bean class="com.fasterxml.jackson.databind.ObjectMapper">
<property name="dateFormat">
<bean class="java.text.SimpleDateFormat">
<constructor-arg value="yyyy-MM-dd HH:mm:ss"/>
</bean>
</property>
</bean>
</property>
</bean>
</mvc:message-converters>
</mvc:annotation-driven>
<!-- 配置跨域支持 -->
<mvc:cors>
<mvc:mapping path="/**"
allowed-origins="*"
allowed-methods="GET,POST,PUT,DELETE,OPTIONS"
allowed-headers="Content-Type,X-Requested-With,accept,Origin,Access-Control-Request-Method,Access-Control-Request-Headers"
allow-credentials="true"
max-age="3600"/>
</mvc:cors>
</beans>

View File

@ -0,0 +1,39 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 配置数据源 -->
<bean id="dataSource" class="com.zaxxer.hikari.HikariDataSource" destroy-method="close">
<property name="driverClassName" value="${jdbc.driver}"/>
<property name="jdbcUrl" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
<!-- 连接池配置 -->
<property name="minimumIdle" value="${jdbc.pool.minimumIdle}"/>
<property name="maximumPoolSize" value="${jdbc.pool.maximumPoolSize}"/>
<property name="idleTimeout" value="${jdbc.pool.idleTimeout}"/>
<property name="connectionTimeout" value="${jdbc.pool.connectionTimeout}"/>
<property name="connectionTestQuery" value="${jdbc.pool.connectionTestQuery}"/>
</bean>
<!-- 配置SqlSessionFactory -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="configLocation" value="classpath:mybatis/mybatis-config.xml"/>
<property name="mapperLocations" value="classpath:mybatis/mapper/*.xml"/>
</bean>
<!-- 配置Mapper扫描器 -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="ltd.qubit.survey.dao"/>
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
</bean>
<!-- 配置事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
</beans>

View File

@ -0,0 +1,57 @@
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<display-name>LLM Survey API</display-name>
<!-- Spring配置文件位置 -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
classpath:spring/applicationContext.xml
classpath:spring/spring-mybatis.xml
</param-value>
</context-param>
<!-- Spring监听器 -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- Spring MVC Servlet -->
<servlet>
<servlet-name>springmvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring/spring-mvc.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>springmvc</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<!-- 字符编码过滤器 -->
<filter>
<filter-name>characterEncodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
<init-param>
<param-name>forceEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>characterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>

View File

@ -0,0 +1,18 @@
# 数据库配置
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://127.0.0.1:3306/llm_survey?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
jdbc.username=dev
jdbc.password=
# 连接池配置
jdbc.pool.minimumIdle=5
jdbc.pool.maximumPoolSize=20
jdbc.pool.idleTimeout=300000
jdbc.pool.connectionTimeout=20000
jdbc.pool.connectionTestQuery=SELECT 1
# 日志配置
logging.level.root=INFO
logging.level.ltd.qubit.survey=DEBUG
logging.level.org.springframework=INFO
logging.level.org.mybatis=DEBUG

View File

@ -0,0 +1,83 @@
<?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="ltd.qubit.survey.dao.OptionDao">
<!-- 结果映射 -->
<resultMap id="optionMap" type="ltd.qubit.survey.model.Option">
<id property="id" column="id"/>
<result property="questionId" column="question_id"/>
<result property="optionCode" column="option_code"/>
<result property="content" column="content"/>
<result property="requiresText" column="requires_text"/>
<result property="createdAt" column="created_at"/>
</resultMap>
<!-- 基础列 -->
<sql id="baseColumns">
id, question_id, option_code, content, requires_text, created_at
</sql>
<!-- 插入 -->
<insert id="insert" parameterType="ltd.qubit.survey.model.Option" useGeneratedKeys="true" keyProperty="id">
INSERT INTO options (question_id, option_code, content, requires_text)
VALUES (#{questionId}, #{optionCode}, #{content}, #{requiresText})
</insert>
<!-- 批量插入 -->
<insert id="batchInsert" parameterType="java.util.List">
INSERT INTO options (question_id, option_code, content, requires_text)
VALUES
<foreach collection="list" item="item" separator=",">
(#{item.questionId}, #{item.optionCode}, #{item.content}, #{item.requiresText})
</foreach>
</insert>
<!-- 更新 -->
<update id="update" parameterType="ltd.qubit.survey.model.Option">
UPDATE options
SET question_id = #{questionId},
option_code = #{optionCode},
content = #{content},
requires_text = #{requiresText}
WHERE id = #{id}
</update>
<!-- 删除 -->
<delete id="deleteById" parameterType="long">
DELETE FROM options WHERE id = #{id}
</delete>
<!-- 根据问题ID删除 -->
<delete id="deleteByQuestionId" parameterType="long">
DELETE FROM options WHERE question_id = #{questionId}
</delete>
<!-- 根据ID查询 -->
<select id="findById" parameterType="long" resultMap="optionMap">
SELECT <include refid="baseColumns"/>
FROM options
WHERE id = #{id}
</select>
<!-- 查询所有 -->
<select id="findAll" resultMap="optionMap">
SELECT <include refid="baseColumns"/>
FROM options
ORDER BY question_id, option_code
</select>
<!-- 根据问题ID查询 -->
<select id="findByQuestionId" parameterType="long" resultMap="optionMap">
SELECT <include refid="baseColumns"/>
FROM options
WHERE question_id = #{questionId}
ORDER BY option_code
</select>
<!-- 根据问题ID和选项代码查询 -->
<select id="findByQuestionIdAndCode" resultMap="optionMap">
SELECT <include refid="baseColumns"/>
FROM options
WHERE question_id = #{questionId}
AND option_code = #{optionCode}
</select>
</mapper>

View File

@ -0,0 +1,86 @@
<?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="ltd.qubit.survey.dao.QuestionDao">
<!-- 结果映射 -->
<resultMap id="questionMap" type="ltd.qubit.survey.model.Question">
<id property="id" column="id"/>
<result property="questionNumber" column="question_number"/>
<result property="content" column="content"/>
<result property="questionType" column="question_type"/>
<result property="workArea" column="work_area"/>
<result property="isRequired" column="is_required"/>
<result property="nextQuestionLogic" column="next_question_logic"/>
<result property="createdAt" column="created_at"/>
</resultMap>
<!-- 基础列 -->
<sql id="baseColumns">
id, question_number, content, question_type, work_area, is_required, next_question_logic, created_at
</sql>
<!-- 插入 -->
<insert id="insert" parameterType="ltd.qubit.survey.model.Question" useGeneratedKeys="true" keyProperty="id">
INSERT INTO questions (question_number, content, question_type, work_area, is_required, next_question_logic)
VALUES (#{questionNumber}, #{content}, #{questionType}, #{workArea}, #{isRequired}, #{nextQuestionLogic})
</insert>
<!-- 更新 -->
<update id="update" parameterType="ltd.qubit.survey.model.Question">
UPDATE questions
SET question_number = #{questionNumber},
content = #{content},
question_type = #{questionType},
work_area = #{workArea},
is_required = #{isRequired},
next_question_logic = #{nextQuestionLogic}
WHERE id = #{id}
</update>
<!-- 删除 -->
<delete id="deleteById" parameterType="long">
DELETE FROM questions WHERE id = #{id}
</delete>
<!-- 根据ID查询 -->
<select id="findById" parameterType="long" resultMap="questionMap">
SELECT <include refid="baseColumns"/>
FROM questions
WHERE id = #{id}
</select>
<!-- 查询所有 -->
<select id="findAll" resultMap="questionMap">
SELECT <include refid="baseColumns"/>
FROM questions
ORDER BY question_number
</select>
<!-- 根据问题序号查询 -->
<select id="findByQuestionNumber" parameterType="int" resultMap="questionMap">
SELECT <include refid="baseColumns"/>
FROM questions
WHERE question_number = #{questionNumber}
</select>
<!-- 根据工作领域查询 -->
<select id="findByWorkArea" parameterType="string" resultMap="questionMap">
SELECT <include refid="baseColumns"/>
FROM questions
WHERE work_area = #{workArea}
ORDER BY question_number
</select>
<!-- 查询通用问题 -->
<select id="findCommonQuestions" resultMap="questionMap">
SELECT <include refid="baseColumns"/>
FROM questions
WHERE work_area IS NULL
ORDER BY question_number
</select>
<!-- 获取下一个问题序号 -->
<select id="getNextQuestionNumber" resultType="int">
SELECT COALESCE(MAX(question_number) + 1, 1)
FROM questions
</select>
</mapper>

View File

@ -0,0 +1,93 @@
<?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="ltd.qubit.survey.dao.SurveyResponseDao">
<!-- 结果映射 -->
<resultMap id="responseMap" type="ltd.qubit.survey.model.SurveyResponse">
<id property="id" column="id"/>
<result property="userId" column="user_id"/>
<result property="questionId" column="question_id"/>
<result property="selectedOptions" column="selected_options" typeHandler="org.apache.ibatis.type.JsonTypeHandler"/>
<result property="textAnswer" column="text_answer"/>
<result property="createdAt" column="created_at"/>
</resultMap>
<!-- 基础列 -->
<sql id="baseColumns">
id, user_id, question_id, selected_options, text_answer, created_at
</sql>
<!-- 插入 -->
<insert id="insert" parameterType="ltd.qubit.survey.model.SurveyResponse" useGeneratedKeys="true" keyProperty="id">
INSERT INTO survey_responses (user_id, question_id, selected_options, text_answer)
VALUES (#{userId}, #{questionId}, #{selectedOptions,typeHandler=org.apache.ibatis.type.JsonTypeHandler}, #{textAnswer})
</insert>
<!-- 批量插入 -->
<insert id="batchInsert" parameterType="java.util.List">
INSERT INTO survey_responses (user_id, question_id, selected_options, text_answer)
VALUES
<foreach collection="list" item="item" separator=",">
(#{item.userId}, #{item.questionId},
#{item.selectedOptions,typeHandler=org.apache.ibatis.type.JsonTypeHandler},
#{item.textAnswer})
</foreach>
</insert>
<!-- 更新 -->
<update id="update" parameterType="ltd.qubit.survey.model.SurveyResponse">
UPDATE survey_responses
SET user_id = #{userId},
question_id = #{questionId},
selected_options = #{selectedOptions,typeHandler=org.apache.ibatis.type.JsonTypeHandler},
text_answer = #{textAnswer}
WHERE id = #{id}
</update>
<!-- 删除 -->
<delete id="deleteById" parameterType="long">
DELETE FROM survey_responses WHERE id = #{id}
</delete>
<!-- 根据用户ID删除 -->
<delete id="deleteByUserId" parameterType="long">
DELETE FROM survey_responses WHERE user_id = #{userId}
</delete>
<!-- 根据ID查询 -->
<select id="findById" parameterType="long" resultMap="responseMap">
SELECT <include refid="baseColumns"/>
FROM survey_responses
WHERE id = #{id}
</select>
<!-- 查询所有 -->
<select id="findAll" resultMap="responseMap">
SELECT <include refid="baseColumns"/>
FROM survey_responses
ORDER BY user_id, question_id
</select>
<!-- 根据用户ID查询 -->
<select id="findByUserId" parameterType="long" resultMap="responseMap">
SELECT <include refid="baseColumns"/>
FROM survey_responses
WHERE user_id = #{userId}
ORDER BY question_id
</select>
<!-- 根据问题ID查询 -->
<select id="findByQuestionId" parameterType="long" resultMap="responseMap">
SELECT <include refid="baseColumns"/>
FROM survey_responses
WHERE question_id = #{questionId}
ORDER BY user_id
</select>
<!-- 根据用户ID和问题ID查询 -->
<select id="findByUserIdAndQuestionId" resultMap="responseMap">
SELECT <include refid="baseColumns"/>
FROM survey_responses
WHERE user_id = #{userId}
AND question_id = #{questionId}
</select>
</mapper>

View File

@ -0,0 +1,68 @@
<?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="ltd.qubit.survey.dao.UserDao">
<!-- 结果映射 -->
<resultMap id="userMap" type="ltd.qubit.survey.model.User">
<id property="id" column="id"/>
<result property="name" column="name"/>
<result property="phone" column="phone"/>
<result property="workArea" column="work_area"/>
<result property="positionType" column="position_type"/>
<result property="createdAt" column="created_at"/>
</resultMap>
<!-- 基础列 -->
<sql id="baseColumns">
id, name, phone, work_area, position_type, created_at
</sql>
<!-- 插入 -->
<insert id="insert" parameterType="ltd.qubit.survey.model.User" useGeneratedKeys="true" keyProperty="id">
INSERT INTO users (name, phone, work_area, position_type)
VALUES (#{name}, #{phone}, #{workArea}, #{positionType})
</insert>
<!-- 更新 -->
<update id="update" parameterType="ltd.qubit.survey.model.User">
UPDATE users
SET name = #{name},
phone = #{phone},
work_area = #{workArea},
position_type = #{positionType}
WHERE id = #{id}
</update>
<!-- 删除 -->
<delete id="deleteById" parameterType="long">
DELETE FROM users WHERE id = #{id}
</delete>
<!-- 根据ID查询 -->
<select id="findById" parameterType="long" resultMap="userMap">
SELECT <include refid="baseColumns"/>
FROM users
WHERE id = #{id}
</select>
<!-- 查询所有 -->
<select id="findAll" resultMap="userMap">
SELECT <include refid="baseColumns"/>
FROM users
ORDER BY id
</select>
<!-- 根据手机号查询 -->
<select id="findByPhone" parameterType="string" resultMap="userMap">
SELECT <include refid="baseColumns"/>
FROM users
WHERE phone = #{phone}
</select>
<!-- 根据工作领域查询 -->
<select id="findByWorkArea" parameterType="string" resultMap="userMap">
SELECT <include refid="baseColumns"/>
FROM users
WHERE work_area = #{workArea}
ORDER BY id
</select>
</mapper>

View File

@ -0,0 +1,32 @@
<?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>
<settings>
<!-- 开启驼峰命名自动映射 -->
<setting name="mapUnderscoreToCamelCase" value="true"/>
<!-- 开启二级缓存 -->
<setting name="cacheEnabled" value="true"/>
<!-- 开启延迟加载 -->
<setting name="lazyLoadingEnabled" value="true"/>
<!-- 设置日志实现 -->
<setting name="logImpl" value="SLF4J"/>
</settings>
<typeHandlers>
<!-- 枚举类型处理器 -->
<typeHandler handler="org.apache.ibatis.type.EnumTypeHandler"
javaType="ltd.qubit.survey.model.WorkArea"/>
<typeHandler handler="org.apache.ibatis.type.EnumTypeHandler"
javaType="ltd.qubit.survey.model.PositionType"/>
<typeHandler handler="org.apache.ibatis.type.EnumTypeHandler"
javaType="ltd.qubit.survey.model.QuestionType"/>
</typeHandlers>
<mappers>
<!-- 映射文件 -->
<mapper resource="mybatis/mapper/UserMapper.xml"/>
<mapper resource="mybatis/mapper/QuestionMapper.xml"/>
<mapper resource="mybatis/mapper/OptionMapper.xml"/>
<mapper resource="mybatis/mapper/SurveyResponseMapper.xml"/>
</mappers>
</configuration>

View File

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">
<!-- 加载属性文件 -->
<context:property-placeholder location="classpath:application.properties"/>
<!-- 开启注解扫描排除Controller -->
<context:component-scan base-package="ltd.qubit.survey">
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
<!-- 配置事务注解驱动 -->
<tx:annotation-driven/>
</beans>

View File

@ -0,0 +1,46 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd">
<!-- 开启Controller注解扫描 -->
<context:component-scan base-package="ltd.qubit.survey.controller" use-default-filters="false">
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
<!-- 开启SpringMVC注解驱动 -->
<mvc:annotation-driven>
<mvc:message-converters>
<!-- 配置JSON转换器 -->
<bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
<property name="objectMapper">
<bean class="com.fasterxml.jackson.databind.ObjectMapper">
<property name="dateFormat">
<bean class="java.text.SimpleDateFormat">
<constructor-arg value="yyyy-MM-dd HH:mm:ss"/>
</bean>
</property>
</bean>
</property>
</bean>
</mvc:message-converters>
</mvc:annotation-driven>
<!-- 配置跨域支持 -->
<mvc:cors>
<mvc:mapping path="/**"
allowed-origins="*"
allowed-methods="GET,POST,PUT,DELETE,OPTIONS"
allowed-headers="Content-Type,X-Requested-With,accept,Origin,Access-Control-Request-Method,Access-Control-Request-Headers"
allow-credentials="true"
max-age="3600"/>
</mvc:cors>
</beans>

View File

@ -0,0 +1,39 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 配置数据源 -->
<bean id="dataSource" class="com.zaxxer.hikari.HikariDataSource" destroy-method="close">
<property name="driverClassName" value="${jdbc.driver}"/>
<property name="jdbcUrl" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
<!-- 连接池配置 -->
<property name="minimumIdle" value="${jdbc.pool.minimumIdle}"/>
<property name="maximumPoolSize" value="${jdbc.pool.maximumPoolSize}"/>
<property name="idleTimeout" value="${jdbc.pool.idleTimeout}"/>
<property name="connectionTimeout" value="${jdbc.pool.connectionTimeout}"/>
<property name="connectionTestQuery" value="${jdbc.pool.connectionTestQuery}"/>
</bean>
<!-- 配置SqlSessionFactory -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="configLocation" value="classpath:mybatis/mybatis-config.xml"/>
<property name="mapperLocations" value="classpath:mybatis/mapper/*.xml"/>
</bean>
<!-- 配置Mapper扫描器 -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="ltd.qubit.survey.dao"/>
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
</bean>
<!-- 配置事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
</beans>

View File

@ -0,0 +1,27 @@
ltd/qubit/survey/controller/GlobalExceptionHandler.class
ltd/qubit/survey/dao/QuestionDao.class
ltd/qubit/survey/model/PositionType.class
ltd/qubit/survey/service/OptionService.class
ltd/qubit/survey/controller/UserController.class
ltd/qubit/survey/service/impl/SurveyResponseServiceImpl.class
ltd/qubit/survey/dao/SurveyResponseDao.class
ltd/qubit/survey/dao/OptionDao.class
ltd/qubit/survey/controller/QuestionController.class
ltd/qubit/survey/service/impl/UserServiceImpl.class
ltd/qubit/survey/service/impl/QuestionServiceImpl.class
ltd/qubit/survey/model/SurveyResponse.class
ltd/qubit/survey/model/User.class
ltd/qubit/survey/service/QuestionService.class
ltd/qubit/survey/service/impl/QuestionServiceImpl$1.class
ltd/qubit/survey/service/UserService.class
ltd/qubit/survey/dao/BaseDao.class
ltd/qubit/survey/service/impl/OptionServiceImpl.class
ltd/qubit/survey/dao/UserDao.class
ltd/qubit/survey/model/ErrorInfo.class
ltd/qubit/survey/controller/SurveyController.class
ltd/qubit/survey/model/Question.class
ltd/qubit/survey/model/WorkArea.class
ltd/qubit/survey/service/SurveyResponseService.class
ltd/qubit/survey/model/Option.class
ltd/qubit/survey/model/QuestionType.class
ltd/qubit/survey/service/BaseService.class

View File

@ -0,0 +1,26 @@
/Volumes/working/qubit/project/llm-survey/backend/src/main/java/ltd/qubit/survey/dao/QuestionDao.java
/Volumes/working/qubit/project/llm-survey/backend/src/main/java/ltd/qubit/survey/service/UserService.java
/Volumes/working/qubit/project/llm-survey/backend/src/main/java/ltd/qubit/survey/service/OptionService.java
/Volumes/working/qubit/project/llm-survey/backend/src/main/java/ltd/qubit/survey/model/Question.java
/Volumes/working/qubit/project/llm-survey/backend/src/main/java/ltd/qubit/survey/service/QuestionService.java
/Volumes/working/qubit/project/llm-survey/backend/src/main/java/ltd/qubit/survey/service/BaseService.java
/Volumes/working/qubit/project/llm-survey/backend/src/main/java/ltd/qubit/survey/model/User.java
/Volumes/working/qubit/project/llm-survey/backend/src/main/java/ltd/qubit/survey/service/impl/OptionServiceImpl.java
/Volumes/working/qubit/project/llm-survey/backend/src/main/java/ltd/qubit/survey/service/impl/QuestionServiceImpl.java
/Volumes/working/qubit/project/llm-survey/backend/src/main/java/ltd/qubit/survey/service/SurveyResponseService.java
/Volumes/working/qubit/project/llm-survey/backend/src/main/java/ltd/qubit/survey/model/Option.java
/Volumes/working/qubit/project/llm-survey/backend/src/main/java/ltd/qubit/survey/controller/GlobalExceptionHandler.java
/Volumes/working/qubit/project/llm-survey/backend/src/main/java/ltd/qubit/survey/model/QuestionType.java
/Volumes/working/qubit/project/llm-survey/backend/src/main/java/ltd/qubit/survey/dao/UserDao.java
/Volumes/working/qubit/project/llm-survey/backend/src/main/java/ltd/qubit/survey/model/SurveyResponse.java
/Volumes/working/qubit/project/llm-survey/backend/src/main/java/ltd/qubit/survey/service/impl/SurveyResponseServiceImpl.java
/Volumes/working/qubit/project/llm-survey/backend/src/main/java/ltd/qubit/survey/model/ErrorInfo.java
/Volumes/working/qubit/project/llm-survey/backend/src/main/java/ltd/qubit/survey/controller/UserController.java
/Volumes/working/qubit/project/llm-survey/backend/src/main/java/ltd/qubit/survey/service/impl/UserServiceImpl.java
/Volumes/working/qubit/project/llm-survey/backend/src/main/java/ltd/qubit/survey/dao/SurveyResponseDao.java
/Volumes/working/qubit/project/llm-survey/backend/src/main/java/ltd/qubit/survey/controller/QuestionController.java
/Volumes/working/qubit/project/llm-survey/backend/src/main/java/ltd/qubit/survey/model/WorkArea.java
/Volumes/working/qubit/project/llm-survey/backend/src/main/java/ltd/qubit/survey/model/PositionType.java
/Volumes/working/qubit/project/llm-survey/backend/src/main/java/ltd/qubit/survey/dao/BaseDao.java
/Volumes/working/qubit/project/llm-survey/backend/src/main/java/ltd/qubit/survey/controller/SurveyController.java
/Volumes/working/qubit/project/llm-survey/backend/src/main/java/ltd/qubit/survey/dao/OptionDao.java

30
database/init_database.sh Executable file
View File

@ -0,0 +1,30 @@
#!/bin/bash
# 获取脚本所在目录的绝对路径
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
# 检查mysql命令是否可用
if ! command -v mysql &> /dev/null; then
echo "Error: mysql command not found"
exit 1
fi
# 数据库连接信息
DB_HOST="127.0.0.1"
DB_USER="dev"
DB_NAME="llm_survey"
echo "开始初始化数据库..."
# 执行SQL脚本
mysql -h"$DB_HOST" -u"$DB_USER" < "$SCRIPT_DIR/init_database.sql"
if [ $? -eq 0 ]; then
echo "数据库初始化成功!"
echo "数据库名称: $DB_NAME"
echo "数据库地址: $DB_HOST"
echo "数据库用户: $DB_USER"
else
echo "数据库初始化失败!"
exit 1
fi

View File

@ -0,0 +1,73 @@
-- 创建数据库
CREATE DATABASE IF NOT EXISTS llm_survey DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
USE llm_survey;
-- 创建用户表
CREATE TABLE IF NOT EXISTS users (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(50) NOT NULL COMMENT '姓名',
phone VARCHAR(20) NOT NULL COMMENT '手机号码',
work_area VARCHAR(20) NOT NULL COMMENT '工作领域',
position_type VARCHAR(20) NOT NULL COMMENT '岗位性质',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
UNIQUE KEY uk_phone (phone)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户信息表';
-- 创建问题表
CREATE TABLE IF NOT EXISTS questions (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
question_number INT NOT NULL COMMENT '问题序号',
content TEXT NOT NULL COMMENT '问题内容',
question_type VARCHAR(20) NOT NULL COMMENT '问题类型',
work_area VARCHAR(20) COMMENT '针对的工作领域NULL表示通用问题',
is_required BOOLEAN DEFAULT TRUE COMMENT '是否必答',
next_question_logic JSON COMMENT '跳转逻辑',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
UNIQUE KEY uk_question_number (question_number)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='问题表';
-- 创建选项表
CREATE TABLE IF NOT EXISTS options (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
question_id BIGINT NOT NULL COMMENT '关联的问题ID',
option_code VARCHAR(10) NOT NULL COMMENT '选项代码如A、B、C',
content TEXT NOT NULL COMMENT '选项内容',
requires_text BOOLEAN DEFAULT FALSE COMMENT '是否需要填写文本',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (question_id) REFERENCES questions(id),
UNIQUE KEY uk_question_option (question_id, option_code)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='问题选项表';
-- 创建答案表
CREATE TABLE IF NOT EXISTS survey_responses (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
user_id BIGINT NOT NULL COMMENT '用户ID',
question_id BIGINT NOT NULL COMMENT '问题ID',
selected_options JSON COMMENT '选中的选项代码列表',
text_answer TEXT COMMENT '文本答案',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(id),
FOREIGN KEY (question_id) REFERENCES questions(id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='问卷答案表';
-- 插入基础问题数据
INSERT INTO questions (question_number, content, question_type, is_required) VALUES
(1, '您的工作领域', 'SINGLE_CHOICE', TRUE),
(2, '岗位性质', 'SINGLE_CHOICE', TRUE);
-- 插入基础选项数据
INSERT INTO options (question_id, option_code, content) VALUES
(1, 'A', '研发'),
(1, 'B', '项目'),
(1, 'C', '保险'),
(1, 'D', '财务'),
(1, 'E', '运营'),
(1, 'F', '客服'),
(1, 'G', '综合管理');
INSERT INTO options (question_id, option_code, content) VALUES
(2, 'A', '管理岗'),
(2, 'B', '技术岗'),
(2, 'C', '业务岗'),
(2, 'D', '职能支持岗');

163
doc/api.md Normal file
View File

@ -0,0 +1,163 @@
# 在线调查问卷系统 API 文档
## 1. 数据结构
### 1.1 用户信息 (User)
```json
{
"id": "Long",
"name": "String", // 姓名
"phone": "String", // 手机号码
"workArea": "String", // 工作领域: RD/PROJECT/INSURANCE/FINANCE/OPERATION/CUSTOMER_SERVICE/ADMIN
"positionType": "String", // 岗位性质: MANAGEMENT/TECHNICAL/BUSINESS/SUPPORT
"createdAt": "DateTime" // 创建时间
}
```
### 1.2 问题 (Question)
```json
{
"id": "Long",
"questionNumber": "Integer", // 问题序号
"content": "String", // 问题内容
"questionType": "String", // 问题类型: SINGLE_CHOICE/MULTIPLE_CHOICE/TEXT
"workArea": "String", // 针对的工作领域null表示通用问题
"isRequired": "Boolean", // 是否必答
"nextQuestionLogic": "JSON", // 跳转逻辑
"createdAt": "DateTime" // 创建时间
}
```
### 1.3 选项 (Option)
```json
{
"id": "Long",
"questionId": "Long", // 关联的问题ID
"optionCode": "String", // 选项代码如A、B、C
"content": "String", // 选项内容
"requiresText": "Boolean", // 是否需要填写文本
"createdAt": "DateTime" // 创建时间
}
```
### 1.4 问卷答案 (SurveyResponse)
```json
{
"id": "Long",
"userId": "Long", // 用户ID
"questionId": "Long", // 问题ID
"selectedOptions": "List<String>", // 选中的选项代码列表
"textAnswer": "String", // 文本答案
"createdAt": "DateTime" // 创建时间
}
```
### 1.5 错误信息 (ErrorInfo)
```json
{
"code": "String", // 错误代码
"message": "String", // 错误消息
"detail": "String", // 错误详情
"timestamp": "Long", // 时间戳
"path": "String" // 请求路径
}
```
## 2. API 接口
### 2.1 用户相关接口
#### 2.1.1 用户注册
- **URL**: `/user/register`
- **Method**: POST
- **请求体**: User对象不需要id和createdAt
- **响应**: User对象
- **错误码**:
- `INVALID_ARGUMENT`: 参数校验失败
- `BAD_REQUEST`: 手机号已被注册
#### 2.1.2 查询用户信息
- **URL**: `/user/{id}`
- **Method**: GET
- **参数**:
- `id`: 用户ID
- **响应**: User对象
- **错误码**:
- `BAD_REQUEST`: 用户不存在
#### 2.1.3 检查手机号是否已注册
- **URL**: `/user/check/{phone}`
- **Method**: GET
- **参数**:
- `phone`: 手机号
- **响应**: Booleantrue表示已注册
### 2.2 问题相关接口
#### 2.2.1 获取用户的问题列表
- **URL**: `/question/user/{userId}`
- **Method**: GET
- **参数**:
- `userId`: 用户ID
- **响应**: Question对象列表
- **错误码**:
- `BAD_REQUEST`: 用户不存在
#### 2.2.2 获取问题的选项列表
- **URL**: `/question/{questionId}/option`
- **Method**: GET
- **参数**:
- `questionId`: 问题ID
- **响应**: Option对象列表
#### 2.2.3 获取下一个问题
- **URL**: `/question/next`
- **Method**: GET
- **参数**:
- `userId`: 用户ID
- `currentQuestionNumber`: 当前问题序号
- `selectedOptions`: 选中的选项代码列表
- **响应**: Question对象
- **错误码**:
- `BAD_REQUEST`: 没有更多问题了
### 2.3 问卷答案相关接口
#### 2.3.1 提交问卷答案
- **URL**: `/survey/submit/{userId}`
- **Method**: POST
- **参数**:
- `userId`: 用户ID
- **请求体**: SurveyResponse对象列表
- **响应**: SurveyResponse对象列表
- **错误码**:
- `BAD_REQUEST`: 必答题未回答
#### 2.3.2 获取用户的问卷答案
- **URL**: `/survey/user/{userId}`
- **Method**: GET
- **参数**:
- `userId`: 用户ID
- **响应**: SurveyResponse对象列表
#### 2.3.3 获取问题的所有答案
- **URL**: `/survey/question/{questionId}`
- **Method**: GET
- **参数**:
- `questionId`: 问题ID
- **响应**: SurveyResponse对象列表
## 3. 错误处理
所有接口在发生错误时都会返回统一格式的错误信息ErrorInfo对象
### 3.1 通用错误码
- `INVALID_ARGUMENT`: 参数校验失败
- `BAD_REQUEST`: 业务逻辑错误
- `DATABASE_ERROR`: 数据库访问错误
- `SYSTEM_ERROR`: 系统错误
### 3.2 HTTP状态码
- 200: 请求成功
- 400: 请求参数错误或业务逻辑错误
- 500: 服务器内部错误

62
doc/requirements.md Normal file
View File

@ -0,0 +1,62 @@
# 在线调查问卷系统需求文档
## 1. 系统概述
本系统是一个在线调查问卷系统,用于收集用户对大模型应用的需求调研。系统包括前端问卷展示和后端数据处理两个部分。
## 2. 功能需求
### 2.1 用户信息收集
- 用户需填写基本信息(姓名和手机号码)
- 系统自动收集用户的问卷答案
### 2.2 问卷功能
- 支持问题跳转逻辑(根据用户选择自动跳转到相应问题)
- 支持多种题型:
- 单选题
- 多选题
- 文本输入题
- 答案自动保存到数据库
### 2.3 数据存储
- 使用MySQL数据库存储用户信息和答案
- 数据库配置:
- 地址127.0.0.1
- 用户名dev
- 密码:无
- 权限:可建库建表
## 3. 技术要求
### 3.1 前端技术栈
- 框架Vue.js
- UI框架移动端适配的主流框架
- HTTP请求Axios需要封装
- 项目名称llm-survey
### 3.2 后端技术栈
- 开发语言Java
- 框架Spring + MyBatis
- 打包方式WAR
- 项目名称llm-survey-api
- 架构:三层架构
- 领域对象Domain Objects
- DAO层
- Service层
- Controller层
### 3.3 部署要求
- 前端llm-survey
- 后端llm-survey-api
- Nginx配置
- 反向代理Tomcat接口
- 将后端API重定向到127.0.0.1:80/llm-survey-api
## 4. 数据库初始化
- 需要提供shell脚本进行数据库初始化
- 脚本功能包括:
- 创建数据库
- 创建必要的表
- 设置必要的索引和约束
## 5. 问卷内容
问卷详细内容请参见 [大模型应用需求调研问卷.md](大模型应用需求调研问卷.md)

View File

@ -0,0 +1,156 @@
**(根据部门自动跳转专属问题)**
---
## 第一部分:基本信息(必填)
1. 您所属部门:
A. 研发部
B. 项目部
C. 保险部
D. 财务部
E. 客服部
F. 运营部
G. 综合管理部
2. 岗位性质:
A. 管理岗
B. 技术岗
C. 业务岗
D. 职能支持岗
---
## 第二部分:通用认知调研(必答)
1. 您对大模型如ChatGPT、通义千问、DeepSeek的了解程度
A. 从未接触过
B. 仅简单使用过通用功能(如问答)
C. 在工作中尝试过基础应用
D. 深度研究过技术原理
2. 您认为以下哪些业务场景最需要效率提升?(多选)
A. 文档撰写/报告生成
B. 数据清洗与分析
C. 客户沟通与服务
D. 风险识别与预警
E. 流程自动化
F. 其他____________
3. 您最关注大模型应用的哪些风险?(多选)
A. 数据隐私泄露
B. 生成内容不准确
C. 合规审查风险
D. 技术使用门槛高
E. 其他____________ (需填写内容)
---
## 第三部分:部门专属问题
**(系统将根据第一部分选择的部门自动跳转)**
### 研发部
4. 您在开发过程中最耗时的重复性工作:
A. 保险条款文档编写
B. 医疗知识图谱维护
C. API接口调试
D. 其他____________
5. 您希望大模型如何与现有系统集成:
A. 自动生成代码片段如DeepSeek-Coder
B. 智能测试用例生成(如通义千问测试场景模拟)
C. 需求文档结构化如ChatGPT生成PRD框架
D. 其他____________
### 项目部
6. 惠民保产品设计中最需要数据支持的环节:
A. 参保人群画像分析
B. 竞品方案快速解析(如通义千问政策解读)
C. 定价模型优化
D. 其他____________
7. 您希望如何用大模型提升方案输出效率:
A. 自动生成PPT框架ChatGPT生成大纲
B. 从政策文件中提取关键条款DeepSeek语义解析
C. 风险测算报告自动化
D. 其他____________
### 保险部
8. 理赔处理中的主要效率瓶颈:
A. 材料完整性核验
B. 医疗票据信息提取如OCR+通义千问核对)
C. 案件风险分级
D. 其他____________
9. 您认为大模型可优化的理赔环节:
A. 自动生成理赔告知书ChatGPT模板生成
B. 异常案件预警提示DeepSeek数据分析
C. 保险规则智能问答
D. 其他____________
### 财务部
10. 日常工作中重复性最高的任务:
A. 发票信息录入核对
B. 报销单据合规审查
C. 财务报表数据汇总
D. 其他____________
11. 您希望大模型协助完成的财务工作:
A. 自动提取票据关键字段通义千问OCR识别
B. 生成财务分析摘要ChatGPT文本总结
C. 异常收支模式检测DeepSeek风险预测
D. 其他____________
### 客服部
12. 客户咨询中最常遇到的重复性问题:
A. 理赔进度查询
B. 参保资格咨询
C. 材料补交通知
D. 其他____________
13. 您希望大模型如何辅助客服工作:
A. 自动生成个性化回复话术ChatGPT对话生成
B. 客户情绪实时识别(通义千问情感分析)
C. 咨询问题自动分类派单DeepSeek意图分类
D. 其他____________
### 运营部
14. 运营数据分析中的主要痛点:
A. 多平台数据整合
B. 参保率预测模型优化DeepSeek时序预测
C. 宣传文案创意生成(通义千问文案生成)
D. 其他____________
15. 您希望大模型赋能的运营场景:
A. 自动生成社交媒体图文ChatGPT+通义万相)
B. 用户评论情感分析DeepSeek语义分析
C. 活动效果模拟预测
D. 其他____________
### 综合管理部
16. 日常工作中最耗时的行政事务:
A. 合同条款审查
B. 会议纪要整理
C. 制度文档更新
D. 其他____________
17. 您希望大模型协助完成的行政工作:
A. 自动生成招投标文件模板ChatGPT框架生成
B. 合同风险点智能排查(通义千问法律审查)
C. 流程说明书自动更新DeepSeek版本迭代
D. 其他____________
---
## 第四部分:开放建议(选填)
18. 您对大模型培训的具体期待:
____________________________
19. 您认为公司引入AI需提前防范的风险
____________________________
---
### 问卷说明
20. **逻辑跳转**系统将根据所选部门显示专属问题总题量约10-12题。
21. **工具示例**
- 通用大模型ChatGPTOpenAI、通义千问阿里云、DeepSeek深度求索
22. **提交方式**匿名填写预计耗时5-8分钟。

161
pom.xml Normal file
View File

@ -0,0 +1,161 @@
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>ltd.qubit</groupId>
<artifactId>llm-survey-api</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>
<properties>
<java.version>17</java.version>
<maven.compiler.source>${java.version}</maven.compiler.source>
<maven.compiler.target>${java.version}</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<spring.version>5.3.31</spring.version>
<mybatis.version>3.5.15</mybatis.version>
<mybatis-spring.version>2.1.2</mybatis-spring.version>
<mysql-connector.version>8.3.0</mysql-connector.version>
<hikaricp.version>5.1.0</hikaricp.version>
<jackson.version>2.16.1</jackson.version>
<lombok.version>1.18.30</lombok.version>
<slf4j.version>2.0.11</slf4j.version>
<logback.version>1.4.14</logback.version>
<servlet-api.version>4.0.1</servlet-api.version>
</properties>
<dependencies>
<!-- Spring Framework -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>${spring.version}</version>
</dependency>
<!-- MyBatis -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>${mybatis.version}</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>${mybatis-spring.version}</version>
</dependency>
<!-- MySQL Connector -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>${mysql-connector.version}</version>
</dependency>
<!-- HikariCP -->
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
<version>${hikaricp.version}</version>
</dependency>
<!-- Jackson -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${jackson.version}</version>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
<scope>provided</scope>
</dependency>
<!-- Logging -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${slf4j.version}</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>${logback.version}</version>
</dependency>
<!-- Servlet API -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>${servlet-api.version}</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<finalName>llm-survey-api</finalName>
<plugins>
<!-- 编译插件 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
<configuration>
<release>${java.version}</release>
<encoding>${project.build.sourceEncoding}</encoding>
<annotationProcessorPaths>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
<!-- WAR打包插件 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>3.4.0</version>
<configuration>
<failOnMissingWebXml>false</failOnMissingWebXml>
</configuration>
</plugin>
</plugins>
<!-- 资源文件配置 -->
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
</resource>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.xml</include>
</includes>
<filtering>true</filtering>
</resource>
</resources>
</build>
</project>

View File

@ -0,0 +1,78 @@
package ltd.qubit.survey.controller;
import lombok.extern.slf4j.Slf4j;
import ltd.qubit.survey.model.ErrorInfo;
import org.springframework.dao.DataAccessException;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.BindException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
/**
* 全局异常处理器
*/
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
/**
* 处理参数校验异常
*/
@ExceptionHandler(BindException.class)
public ResponseEntity<ErrorInfo> handleBindException(BindException e) {
String message = e.getBindingResult().getFieldErrors().stream()
.map(error -> error.getField() + ": " + error.getDefaultMessage())
.reduce((a, b) -> a + "; " + b)
.orElse("参数错误");
ErrorInfo error = ErrorInfo.of(
"INVALID_ARGUMENT",
"参数校验失败",
message,
e.getObjectName());
return new ResponseEntity<>(error, HttpStatus.BAD_REQUEST);
}
/**
* 处理业务异常
*/
@ExceptionHandler(IllegalArgumentException.class)
public ResponseEntity<ErrorInfo> handleIllegalArgumentException(IllegalArgumentException e) {
log.warn("业务异常", e);
ErrorInfo error = ErrorInfo.of(
"BAD_REQUEST",
e.getMessage(),
e.getClass().getName());
return new ResponseEntity<>(error, HttpStatus.BAD_REQUEST);
}
/**
* 处理数据访问异常
*/
@ExceptionHandler(DataAccessException.class)
public ResponseEntity<ErrorInfo> handleDataAccessException(DataAccessException e) {
log.error("数据访问异常", e);
ErrorInfo error = ErrorInfo.of(
"DATABASE_ERROR",
"数据库访问错误",
e.getMessage(),
e.getClass().getName());
return new ResponseEntity<>(error, HttpStatus.INTERNAL_SERVER_ERROR);
}
/**
* 处理其他未知异常
*/
@ExceptionHandler(Exception.class)
public ResponseEntity<ErrorInfo> handleUnknownException(Exception e) {
log.error("系统异常", e);
ErrorInfo error = ErrorInfo.of(
"SYSTEM_ERROR",
"系统错误",
e.getMessage(),
e.getClass().getName());
return new ResponseEntity<>(error, HttpStatus.INTERNAL_SERVER_ERROR);
}
}

View File

@ -0,0 +1,62 @@
package ltd.qubit.survey.controller;
import java.util.List;
import lombok.RequiredArgsConstructor;
import ltd.qubit.survey.model.Question;
import ltd.qubit.survey.model.Option;
import ltd.qubit.survey.service.QuestionService;
import ltd.qubit.survey.service.OptionService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 问题控制器
*/
@RestController
@RequestMapping("/api/questions")
@RequiredArgsConstructor
public class QuestionController {
private final QuestionService questionService;
private final OptionService optionService;
/**
* 获取用户的问题列表
*
* @param userId 用户ID
* @return 问题列表
*/
@GetMapping("/user/{userId}")
public List<Question> getUserQuestions(@PathVariable Long userId) {
return questionService.getUserQuestions(userId);
}
/**
* 获取问题的选项列表
*
* @param questionId 问题ID
* @return 选项列表
*/
@GetMapping("/{questionId}/options")
public List<Option> getQuestionOptions(@PathVariable Long questionId) {
return optionService.findByQuestionId(questionId);
}
/**
* 获取下一个问题
*
* @param userId 用户ID
* @param currentQuestionNumber 当前问题序号
* @param selectedOptions 选中的选项列表
* @return 下一个问题
*/
@GetMapping("/next")
public Question getNextQuestion(
@PathVariable Long userId,
@PathVariable Integer currentQuestionNumber,
@PathVariable List<String> selectedOptions) {
return questionService.getNextQuestion(userId, currentQuestionNumber, selectedOptions)
.orElseThrow(() -> new IllegalArgumentException("没有更多问题了"));
}
}

View File

@ -0,0 +1,58 @@
package ltd.qubit.survey.controller;
import java.util.List;
import lombok.RequiredArgsConstructor;
import ltd.qubit.survey.model.SurveyResponse;
import ltd.qubit.survey.service.SurveyResponseService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 问卷控制器
*/
@RestController
@RequestMapping("/api/survey")
@RequiredArgsConstructor
public class SurveyController {
private final SurveyResponseService surveyResponseService;
/**
* 提交问卷答案
*
* @param userId 用户ID
* @param responses 答案列表
* @return 提交成功的答案列表
*/
@PostMapping("/submit/{userId}")
public List<SurveyResponse> submitSurvey(
@PathVariable Long userId,
@RequestBody List<SurveyResponse> responses) {
return surveyResponseService.submitSurvey(userId, responses);
}
/**
* 获取用户的问卷答案
*
* @param userId 用户ID
* @return 答案列表
*/
@GetMapping("/user/{userId}")
public List<SurveyResponse> getUserResponses(@PathVariable Long userId) {
return surveyResponseService.findByUserId(userId);
}
/**
* 获取问题的所有答案
*
* @param questionId 问题ID
* @return 答案列表
*/
@GetMapping("/question/{questionId}")
public List<SurveyResponse> getQuestionResponses(@PathVariable Long questionId) {
return surveyResponseService.findByQuestionId(questionId);
}
}

View File

@ -0,0 +1,55 @@
package ltd.qubit.survey.controller;
import lombok.RequiredArgsConstructor;
import ltd.qubit.survey.model.User;
import ltd.qubit.survey.service.UserService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 用户控制器
*/
@RestController
@RequestMapping("/api/users")
@RequiredArgsConstructor
public class UserController {
private final UserService userService;
/**
* 用户注册
*
* @param user 用户信息
* @return 注册成功的用户信息
*/
@PostMapping("/register")
public User register(@RequestBody User user) {
return userService.register(user);
}
/**
* 根据ID查询用户
*
* @param id 用户ID
* @return 用户信息
*/
@GetMapping("/{id}")
public User findById(@PathVariable Long id) {
return userService.findById(id)
.orElseThrow(() -> new IllegalArgumentException("用户不存在"));
}
/**
* 检查手机号是否已注册
*
* @param phone 手机号
* @return 是否已注册
*/
@GetMapping("/check/{phone}")
public boolean checkPhone(@PathVariable String phone) {
return userService.isPhoneRegistered(phone);
}
}

View File

@ -0,0 +1,51 @@
package ltd.qubit.survey.dao;
import java.util.List;
import java.util.Optional;
/**
* 基础DAO接口定义通用的CRUD操作
*
* @param <T> 实体类型
* @param <K> 主键类型
*/
public interface BaseDao<T, K> {
/**
* 插入一条记录
*
* @param entity 实体对象
* @return 影响的行数
*/
int insert(T entity);
/**
* 根据主键删除记录
*
* @param id 主键
* @return 影响的行数
*/
int deleteById(K id);
/**
* 更新记录
*
* @param entity 实体对象
* @return 影响的行数
*/
int update(T entity);
/**
* 根据主键查询
*
* @param id 主键
* @return 实体对象
*/
Optional<T> findById(K id);
/**
* 查询所有记录
*
* @return 实体对象列表
*/
List<T> findAll();
}

View File

@ -0,0 +1,43 @@
package ltd.qubit.survey.dao;
import java.util.List;
import java.util.Optional;
import ltd.qubit.survey.model.Option;
/**
* 选项DAO接口
*/
public interface OptionDao extends BaseDao<Option, Long> {
/**
* 根据问题ID查询选项列表
*
* @param questionId 问题ID
* @return 选项列表
*/
List<Option> findByQuestionId(Long questionId);
/**
* 根据问题ID和选项代码查询
*
* @param questionId 问题ID
* @param optionCode 选项代码
* @return 选项对象
*/
Optional<Option> findByQuestionIdAndCode(Long questionId, String optionCode);
/**
* 批量插入选项
*
* @param options 选项列表
* @return 影响的行数
*/
int batchInsert(List<Option> options);
/**
* 根据问题ID删除所有选项
*
* @param questionId 问题ID
* @return 影响的行数
*/
int deleteByQuestionId(Long questionId);
}

View File

@ -0,0 +1,41 @@
package ltd.qubit.survey.dao;
import java.util.List;
import java.util.Optional;
import ltd.qubit.survey.model.Question;
import ltd.qubit.survey.model.WorkArea;
/**
* 问题DAO接口
*/
public interface QuestionDao extends BaseDao<Question, Long> {
/**
* 根据问题序号查询
*
* @param questionNumber 问题序号
* @return 问题对象
*/
Optional<Question> findByQuestionNumber(Integer questionNumber);
/**
* 根据工作领域查询问题列表
*
* @param workArea 工作领域
* @return 问题列表
*/
List<Question> findByWorkArea(WorkArea workArea);
/**
* 查询通用问题列表不针对特定工作领域
*
* @return 问题列表
*/
List<Question> findCommonQuestions();
/**
* 获取下一个可用的问题序号
*
* @return 下一个问题序号
*/
Integer getNextQuestionNumber();
}

View File

@ -0,0 +1,51 @@
package ltd.qubit.survey.dao;
import java.util.List;
import java.util.Optional;
import ltd.qubit.survey.model.SurveyResponse;
/**
* 问卷答案DAO接口
*/
public interface SurveyResponseDao extends BaseDao<SurveyResponse, Long> {
/**
* 根据用户ID查询答案列表
*
* @param userId 用户ID
* @return 答案列表
*/
List<SurveyResponse> findByUserId(Long userId);
/**
* 根据问题ID查询答案列表
*
* @param questionId 问题ID
* @return 答案列表
*/
List<SurveyResponse> findByQuestionId(Long questionId);
/**
* 根据用户ID和问题ID查询答案
*
* @param userId 用户ID
* @param questionId 问题ID
* @return 答案对象
*/
Optional<SurveyResponse> findByUserIdAndQuestionId(Long userId, Long questionId);
/**
* 批量插入答案
*
* @param responses 答案列表
* @return 影响的行数
*/
int batchInsert(List<SurveyResponse> responses);
/**
* 根据用户ID删除所有答案
*
* @param userId 用户ID
* @return 影响的行数
*/
int deleteByUserId(Long userId);
}

View File

@ -0,0 +1,27 @@
package ltd.qubit.survey.dao;
import java.util.List;
import java.util.Optional;
import ltd.qubit.survey.model.User;
import ltd.qubit.survey.model.WorkArea;
/**
* 用户DAO接口
*/
public interface UserDao extends BaseDao<User, Long> {
/**
* 根据手机号查询用户
*
* @param phone 手机号
* @return 用户对象
*/
Optional<User> findByPhone(String phone);
/**
* 根据工作领域查询用户列表
*
* @param workArea 工作领域
* @return 用户列表
*/
List<User> findByWorkArea(WorkArea workArea);
}

View File

@ -0,0 +1,65 @@
package ltd.qubit.survey.model;
import lombok.Data;
/**
* 错误信息
*/
@Data
public class ErrorInfo {
/**
* 错误代码
*/
private String code;
/**
* 错误消息
*/
private String message;
/**
* 错误详情
*/
private String detail;
/**
* 时间戳
*/
private long timestamp;
/**
* 请求路径
*/
private String path;
/**
* 创建错误信息
*
* @param code 错误代码
* @param message 错误消息
* @param detail 错误详情
* @param path 请求路径
* @return 错误信息
*/
public static ErrorInfo of(String code, String message, String detail, String path) {
ErrorInfo error = new ErrorInfo();
error.setCode(code);
error.setMessage(message);
error.setDetail(detail);
error.setPath(path);
error.setTimestamp(System.currentTimeMillis());
return error;
}
/**
* 创建错误信息无详情
*
* @param code 错误代码
* @param message 错误消息
* @param path 请求路径
* @return 错误信息
*/
public static ErrorInfo of(String code, String message, String path) {
return of(code, message, null, path);
}
}

View File

@ -0,0 +1,40 @@
package ltd.qubit.survey.model;
import java.time.LocalDateTime;
import lombok.Data;
/**
* 问题选项实体类
*/
@Data
public class Option {
/**
* 选项ID
*/
private Long id;
/**
* 关联的问题ID
*/
private Long questionId;
/**
* 选项代码如ABC
*/
private String optionCode;
/**
* 选项内容
*/
private String content;
/**
* 是否需要填写文本
*/
private Boolean requiresText;
/**
* 创建时间
*/
private LocalDateTime createdAt;
}

View File

@ -0,0 +1,36 @@
package ltd.qubit.survey.model;
/**
* 岗位性质枚举
*/
public enum PositionType {
/**
* 管理岗
*/
MANAGEMENT("管理岗"),
/**
* 技术岗
*/
TECHNICAL("技术岗"),
/**
* 业务岗
*/
BUSINESS("业务岗"),
/**
* 职能支持岗
*/
SUPPORT("职能支持岗");
private final String displayName;
PositionType(String displayName) {
this.displayName = displayName;
}
public String getDisplayName() {
return displayName;
}
}

View File

@ -0,0 +1,50 @@
package ltd.qubit.survey.model;
import java.time.LocalDateTime;
import lombok.Data;
/**
* 问题实体类
*/
@Data
public class Question {
/**
* 问题ID
*/
private Long id;
/**
* 问题序号
*/
private Integer questionNumber;
/**
* 问题内容
*/
private String content;
/**
* 问题类型单选多选文本
*/
private QuestionType questionType;
/**
* 针对的工作领域为null表示通用问题
*/
private WorkArea workArea;
/**
* 是否必答
*/
private Boolean isRequired;
/**
* 跳转逻辑JSON格式
*/
private String nextQuestionLogic;
/**
* 创建时间
*/
private LocalDateTime createdAt;
}

View File

@ -0,0 +1,21 @@
package ltd.qubit.survey.model;
/**
* 问题类型枚举
*/
public enum QuestionType {
/**
* 单选题
*/
SINGLE_CHOICE,
/**
* 多选题
*/
MULTIPLE_CHOICE,
/**
* 文本题
*/
TEXT
}

View File

@ -0,0 +1,41 @@
package ltd.qubit.survey.model;
import java.time.LocalDateTime;
import java.util.List;
import lombok.Data;
/**
* 问卷答案实体类
*/
@Data
public class SurveyResponse {
/**
* 答案ID
*/
private Long id;
/**
* 用户ID
*/
private Long userId;
/**
* 问题ID
*/
private Long questionId;
/**
* 选中的选项代码列表JSON格式
*/
private List<String> selectedOptions;
/**
* 文本答案
*/
private String textAnswer;
/**
* 创建时间
*/
private LocalDateTime createdAt;
}

View File

@ -0,0 +1,40 @@
package ltd.qubit.survey.model;
import java.time.LocalDateTime;
import lombok.Data;
/**
* 用户信息实体类
*/
@Data
public class User {
/**
* 用户ID
*/
private Long id;
/**
* 姓名
*/
private String name;
/**
* 手机号码
*/
private String phone;
/**
* 工作领域
*/
private WorkArea workArea;
/**
* 岗位性质
*/
private PositionType positionType;
/**
* 创建时间
*/
private LocalDateTime createdAt;
}

View File

@ -0,0 +1,51 @@
package ltd.qubit.survey.model;
/**
* 工作领域枚举
*/
public enum WorkArea {
/**
* 研发领域
*/
RD("研发"),
/**
* 项目领域
*/
PROJECT("项目"),
/**
* 保险领域
*/
INSURANCE("保险"),
/**
* 财务领域
*/
FINANCE("财务"),
/**
* 运营领域
*/
OPERATION("运营"),
/**
* 客服领域
*/
CUSTOMER_SERVICE("客服"),
/**
* 综合管理领域
*/
ADMIN("综合管理");
private final String displayName;
WorkArea(String displayName) {
this.displayName = displayName;
}
public String getDisplayName() {
return displayName;
}
}

View File

@ -0,0 +1,55 @@
package ltd.qubit.survey.service;
import java.util.List;
import java.util.Optional;
import org.springframework.transaction.annotation.Transactional;
/**
* 基础Service接口定义通用的CRUD操作
*
* @param <T> 实体类型
* @param <K> 主键类型
*/
@Transactional(readOnly = true)
public interface BaseService<T, K> {
/**
* 新增
*
* @param entity 实体对象
* @return 实体对象
*/
@Transactional
T create(T entity);
/**
* 删除
*
* @param id 主键
*/
@Transactional
void delete(K id);
/**
* 更新
*
* @param entity 实体对象
* @return 实体对象
*/
@Transactional
T update(T entity);
/**
* 根据ID查询
*
* @param id 主键
* @return 实体对象
*/
Optional<T> findById(K id);
/**
* 查询所有
*
* @return 实体对象列表
*/
List<T> findAll();
}

View File

@ -0,0 +1,42 @@
package ltd.qubit.survey.service;
import java.util.List;
import java.util.Optional;
import ltd.qubit.survey.model.Option;
/**
* 选项服务接口
*/
public interface OptionService extends BaseService<Option, Long> {
/**
* 根据问题ID查询选项列表
*
* @param questionId 问题ID
* @return 选项列表
*/
List<Option> findByQuestionId(Long questionId);
/**
* 根据问题ID和选项代码查询
*
* @param questionId 问题ID
* @param optionCode 选项代码
* @return 选项对象
*/
Optional<Option> findByQuestionIdAndCode(Long questionId, String optionCode);
/**
* 批量创建选项
*
* @param options 选项列表
* @return 创建成功的选项列表
*/
List<Option> batchCreate(List<Option> options);
/**
* 删除问题的所有选项
*
* @param questionId 问题ID
*/
void deleteByQuestionId(Long questionId);
}

Some files were not shown because too many files have changed in this diff Show More