Compare commits
11 Commits
0852760852
...
03df696476
| Author | SHA1 | Date | |
|---|---|---|---|
| 03df696476 | |||
| f66549ee61 | |||
| cebbef1d53 | |||
| 81ba7ff43f | |||
| d7aaaa37e2 | |||
| 083f263d09 | |||
| 67a6a13b1d | |||
| 480008777b | |||
| 208024ed5c | |||
| d9c64cb28f | |||
| 20c57a13d2 |
81
.cursorrules
Normal file
81
.cursorrules
Normal file
@ -0,0 +1,81 @@
|
||||
# 项目规则文档
|
||||
|
||||
## 1. 日志查看规则
|
||||
- 不允许使用 `tail -f` 命令查看日志文件
|
||||
- 应该使用 `tail` 命令查看日志内容
|
||||
- 查看日志直接查看项目本身的日志,而非tomcat的日志
|
||||
|
||||
## 2. 项目结构规则
|
||||
- 后端项目目录:`backend/`
|
||||
- 前端项目目录:`frontend/`
|
||||
- 数据库脚本目录:`database/`
|
||||
- 文档目录:`doc/`
|
||||
|
||||
## 3. 构建和部署规则
|
||||
- 使用 Maven 构建后端项目:`mvn clean package -DskipTests`
|
||||
- Tomcat 运行在 Docker 容器中:
|
||||
- 端口映射:18080
|
||||
- 数据目录:`$DOCKER_DATA_DIR/tomcat`
|
||||
- 日志目录:`$DOCKER_DATA_DIR/tomcat/logs`
|
||||
- WAR包目录:`$DOCKER_DATA_DIR/tomcat/webapps`
|
||||
- 应用上下文路径:`/llm-survey-api`
|
||||
- 复制文件时,使用 `command cp` 命令
|
||||
- 查看日志始终只查看应用的业务日志
|
||||
- 部署完成后应该稍等几秒查看下业务日志看看发布有没有错误
|
||||
|
||||
## 4. 数据库规则
|
||||
- 数据库名称:`llm_survey`
|
||||
- 数据库用户:`dev`
|
||||
- 数据库地址:`127.0.0.1:3306`
|
||||
- 使用 `init_database.sh` 脚本初始化数据库
|
||||
|
||||
## 5. 代码规范
|
||||
- Java源代码使用UTF-8编码
|
||||
- 使用Lombok简化代码
|
||||
- DAO层继承BaseDao接口
|
||||
- Service层继承BaseService接口
|
||||
- 控制器使用RestController注解
|
||||
|
||||
## 6. Spring配置规则
|
||||
- 共享的bean定义放在 `applicationContext.xml`
|
||||
- MVC相关配置放在 `spring-mvc.xml`
|
||||
- MyBatis相关配置放在 `spring-mybatis.xml`
|
||||
- 避免重复的bean定义
|
||||
|
||||
## 7. 错误处理规则
|
||||
- 使用统一的错误处理格式(ErrorInfo)
|
||||
- 所有异常由GlobalExceptionHandler处理
|
||||
- 业务异常使用IllegalArgumentException
|
||||
|
||||
## 8. API规范
|
||||
- RESTful API设计
|
||||
- 统一的响应格式
|
||||
- 支持跨域访问
|
||||
- API文档位于 `doc/api.md`
|
||||
|
||||
## 9. 安全规则
|
||||
- 不在代码中硬编码敏感信息
|
||||
- 配置信息放在properties文件中
|
||||
- 使用prepared statement防止SQL注入
|
||||
|
||||
## 10. 版本控制
|
||||
- 使用Git进行版本控制
|
||||
- 遵循语义化版本规范
|
||||
- 重要配置文件加入版本控制
|
||||
|
||||
## 11. 文件操作
|
||||
- 当需要复制文件时,使用 `command cp` 命令
|
||||
- 如果发现目录不存在,首先确认自己当前目录是否正确
|
||||
- 如果需要创建目录,使用 `command mkdir -p` 命令
|
||||
- 不要尝试重新安装开发依赖工具,比如jdk, node,python等
|
||||
|
||||
## 前端开发
|
||||
- 前端使用vue3开发
|
||||
- 代码必须严格遵守 eslint 规则
|
||||
- 前端项目使用yarn打包,用最新的4.x版
|
||||
- 前端项目根目录下需要有`.yarn.yml`配置文件
|
||||
- 前端启动开发服务器,需要把切换到前端目录以及启动开发服务器两个命令合并执行
|
||||
|
||||
## Vant 框架
|
||||
|
||||
- `van-popup`的 `v-model` 需要绑定为`v-model:show`
|
||||
64
.gitignore
vendored
Normal file
64
.gitignore
vendored
Normal file
@ -0,0 +1,64 @@
|
||||
# IDE - VSCode
|
||||
.vscode/
|
||||
*.code-workspace
|
||||
.history/
|
||||
|
||||
# IDE - IntelliJ IDEA
|
||||
.idea/
|
||||
*.iml
|
||||
*.iws
|
||||
*.ipr
|
||||
|
||||
# Maven
|
||||
target/
|
||||
*.war
|
||||
*.jar
|
||||
*.ear
|
||||
.classpath
|
||||
.project
|
||||
.settings/
|
||||
|
||||
# Node.js
|
||||
node_modules/
|
||||
dist/
|
||||
.cache/
|
||||
.yarn/cache
|
||||
.yarn/unplugged
|
||||
.yarn/build-state.yml
|
||||
.yarn/install-state.gz
|
||||
.pnp.*
|
||||
|
||||
# Logs
|
||||
logs/
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
.pnpm-debug.log*
|
||||
|
||||
# OS
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# Environment
|
||||
.env
|
||||
.env.local
|
||||
.env.*.local
|
||||
|
||||
# Editor directories and files
|
||||
.idea
|
||||
.vscode
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
|
||||
# Build output
|
||||
backend/target/
|
||||
frontend/dist/
|
||||
frontend/dist-ssr/
|
||||
|
||||
# Cursor
|
||||
.cursor/
|
||||
.cursorrules
|
||||
@ -14,16 +14,18 @@
|
||||
<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>
|
||||
<spring.version>6.1.4</spring.version>
|
||||
<mybatis.version>3.5.15</mybatis.version>
|
||||
<mybatis-spring.version>2.1.2</mybatis-spring.version>
|
||||
<mybatis-spring.version>3.0.3</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>
|
||||
<servlet-api.version>6.0.0</servlet-api.version>
|
||||
<pagehelper.version>5.3.3</pagehelper.version>
|
||||
<aspectj.version>1.9.21</aspectj.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
@ -81,6 +83,18 @@
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
<version>${jackson.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.datatype</groupId>
|
||||
<artifactId>jackson-datatype-jsr310</artifactId>
|
||||
<version>${jackson.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- PageHelper -->
|
||||
<dependency>
|
||||
<groupId>com.github.pagehelper</groupId>
|
||||
<artifactId>pagehelper</artifactId>
|
||||
<version>${pagehelper.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Lombok -->
|
||||
<dependency>
|
||||
@ -102,13 +116,20 @@
|
||||
<version>${logback.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Servlet API -->
|
||||
<!-- Jakarta Servlet API -->
|
||||
<dependency>
|
||||
<groupId>javax.servlet</groupId>
|
||||
<artifactId>javax.servlet-api</artifactId>
|
||||
<groupId>jakarta.servlet</groupId>
|
||||
<artifactId>jakarta.servlet-api</artifactId>
|
||||
<version>${servlet-api.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- AspectJ -->
|
||||
<dependency>
|
||||
<groupId>org.aspectj</groupId>
|
||||
<artifactId>aspectjweaver</artifactId>
|
||||
<version>${aspectj.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
@ -122,6 +143,9 @@
|
||||
<configuration>
|
||||
<release>${java.version}</release>
|
||||
<encoding>${project.build.sourceEncoding}</encoding>
|
||||
<compilerArgs>
|
||||
<arg>-parameters</arg>
|
||||
</compilerArgs>
|
||||
<annotationProcessorPaths>
|
||||
<path>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
|
||||
@ -0,0 +1,39 @@
|
||||
package ltd.qubit.survey.common.mybatis;
|
||||
|
||||
import java.sql.CallableStatement;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Timestamp;
|
||||
import java.time.Instant;
|
||||
import org.apache.ibatis.type.BaseTypeHandler;
|
||||
import org.apache.ibatis.type.JdbcType;
|
||||
|
||||
/**
|
||||
* Instant类型处理器
|
||||
*/
|
||||
public class InstantTypeHandler extends BaseTypeHandler<Instant> {
|
||||
@Override
|
||||
public void setNonNullParameter(PreparedStatement ps, int i, Instant parameter, JdbcType jdbcType)
|
||||
throws SQLException {
|
||||
ps.setTimestamp(i, Timestamp.from(parameter));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Instant getNullableResult(ResultSet rs, String columnName) throws SQLException {
|
||||
Timestamp timestamp = rs.getTimestamp(columnName);
|
||||
return timestamp != null ? timestamp.toInstant() : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Instant getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
|
||||
Timestamp timestamp = rs.getTimestamp(columnIndex);
|
||||
return timestamp != null ? timestamp.toInstant() : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Instant getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
|
||||
Timestamp timestamp = cs.getTimestamp(columnIndex);
|
||||
return timestamp != null ? timestamp.toInstant() : null;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,52 @@
|
||||
package ltd.qubit.survey.common.mybatis;
|
||||
|
||||
import java.sql.CallableStatement;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import org.apache.ibatis.type.BaseTypeHandler;
|
||||
import org.apache.ibatis.type.JdbcType;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
/**
|
||||
* 自定义的 JSON 类型处理器
|
||||
*/
|
||||
public class JsonTypeHandler extends BaseTypeHandler<Object> {
|
||||
private static final ObjectMapper MAPPER = new ObjectMapper();
|
||||
|
||||
@Override
|
||||
public void setNonNullParameter(PreparedStatement ps, int i, Object parameter, JdbcType jdbcType)
|
||||
throws SQLException {
|
||||
try {
|
||||
ps.setString(i, MAPPER.writeValueAsString(parameter));
|
||||
} catch (Exception e) {
|
||||
throw new SQLException("Error converting JSON to String", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getNullableResult(ResultSet rs, String columnName) throws SQLException {
|
||||
return parse(rs.getString(columnName));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
|
||||
return parse(rs.getString(columnIndex));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
|
||||
return parse(cs.getString(columnIndex));
|
||||
}
|
||||
|
||||
private Object parse(String json) throws SQLException {
|
||||
try {
|
||||
if (json == null || json.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
return MAPPER.readValue(json, Object.class);
|
||||
} catch (Exception e) {
|
||||
throw new SQLException("Error converting String to JSON", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -8,7 +8,7 @@ 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.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/**
|
||||
@ -52,9 +52,9 @@ public class QuestionController {
|
||||
*/
|
||||
@GetMapping("/question/next")
|
||||
public Question getNextQuestion(
|
||||
@PathVariable Long userId,
|
||||
@PathVariable Integer currentQuestionNumber,
|
||||
@PathVariable List<String> selectedOptions) {
|
||||
@RequestParam Long userId,
|
||||
@RequestParam(defaultValue = "0") Integer currentQuestionNumber,
|
||||
@RequestParam(required = false) List<String> selectedOptions) {
|
||||
return questionService.getNextQuestion(userId, currentQuestionNumber, selectedOptions)
|
||||
.orElseThrow(() -> new IllegalArgumentException("没有更多问题了"));
|
||||
}
|
||||
|
||||
@ -6,6 +6,7 @@ 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.PutMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@ -40,6 +41,33 @@ public class UserController {
|
||||
.orElseThrow(() -> new IllegalArgumentException("用户不存在"));
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据手机号查询用户
|
||||
*
|
||||
* @param phone 手机号
|
||||
* @return 用户信息
|
||||
*/
|
||||
@GetMapping("/user/phone/{phone}")
|
||||
public User findByPhone(@PathVariable String phone) {
|
||||
return userService.findByPhone(phone)
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新用户信息
|
||||
*
|
||||
* @param id 用户ID
|
||||
* @param user 用户信息
|
||||
* @return 更新后的用户信息
|
||||
*/
|
||||
@PutMapping("/user/{id}")
|
||||
public User update(@PathVariable Long id, @RequestBody User user) {
|
||||
if (!id.equals(user.getId())) {
|
||||
throw new IllegalArgumentException("用户ID不匹配");
|
||||
}
|
||||
return userService.update(user);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查手机号是否已注册
|
||||
*
|
||||
@ -48,6 +76,6 @@ public class UserController {
|
||||
*/
|
||||
@GetMapping("/user/check/{phone}")
|
||||
public boolean checkPhone(@PathVariable String phone) {
|
||||
return userService.isPhoneRegistered(phone);
|
||||
return userService.findByPhone(phone).isPresent();
|
||||
}
|
||||
}
|
||||
@ -20,10 +20,10 @@ public interface OptionDao extends BaseDao<Option, Long> {
|
||||
* 根据问题ID和选项代码查询
|
||||
*
|
||||
* @param questionId 问题ID
|
||||
* @param optionCode 选项代码
|
||||
* @param code 选项代码
|
||||
* @return 选项对象
|
||||
*/
|
||||
Optional<Option> findByQuestionIdAndCode(Long questionId, String optionCode);
|
||||
Optional<Option> findByQuestionIdAndCode(Long questionId, String code);
|
||||
|
||||
/**
|
||||
* 批量插入选项
|
||||
|
||||
@ -3,7 +3,6 @@ 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接口
|
||||
@ -17,14 +16,6 @@ public interface QuestionDao extends BaseDao<Question, Long> {
|
||||
*/
|
||||
Optional<Question> findByQuestionNumber(Integer questionNumber);
|
||||
|
||||
/**
|
||||
* 根据工作领域查询问题列表
|
||||
*
|
||||
* @param workArea 工作领域
|
||||
* @return 问题列表
|
||||
*/
|
||||
List<Question> findByWorkArea(WorkArea workArea);
|
||||
|
||||
/**
|
||||
* 查询通用问题列表(不针对特定工作领域)
|
||||
*
|
||||
|
||||
@ -3,25 +3,19 @@ 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;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
/**
|
||||
* 用户DAO接口
|
||||
* 用户DAO
|
||||
*/
|
||||
@Mapper
|
||||
public interface UserDao extends BaseDao<User, Long> {
|
||||
/**
|
||||
* 根据手机号查询用户
|
||||
*
|
||||
* @param phone 手机号
|
||||
* @return 用户对象
|
||||
* @return 用户信息
|
||||
*/
|
||||
Optional<User> findByPhone(String phone);
|
||||
|
||||
/**
|
||||
* 根据工作领域查询用户列表
|
||||
*
|
||||
* @param workArea 工作领域
|
||||
* @return 用户列表
|
||||
*/
|
||||
List<User> findByWorkArea(WorkArea workArea);
|
||||
Optional<User> findByPhone(@Param("phone") String phone);
|
||||
}
|
||||
@ -1,6 +1,6 @@
|
||||
package ltd.qubit.survey.model;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.Instant;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
@ -21,7 +21,7 @@ public class Option {
|
||||
/**
|
||||
* 选项代码(如A、B、C)
|
||||
*/
|
||||
private String optionCode;
|
||||
private String code;
|
||||
|
||||
/**
|
||||
* 选项内容
|
||||
@ -36,5 +36,5 @@ public class Option {
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
private LocalDateTime createdAt;
|
||||
private Instant createdAt;
|
||||
}
|
||||
@ -1,6 +1,7 @@
|
||||
package ltd.qubit.survey.model;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.Instant;
|
||||
import java.util.Map;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
@ -16,7 +17,7 @@ public class Question {
|
||||
/**
|
||||
* 问题序号
|
||||
*/
|
||||
private Integer questionNumber;
|
||||
private Integer number;
|
||||
|
||||
/**
|
||||
* 问题内容
|
||||
@ -26,12 +27,7 @@ public class Question {
|
||||
/**
|
||||
* 问题类型(单选、多选、文本)
|
||||
*/
|
||||
private QuestionType questionType;
|
||||
|
||||
/**
|
||||
* 针对的工作领域(为null表示通用问题)
|
||||
*/
|
||||
private WorkArea workArea;
|
||||
private QuestionType type;
|
||||
|
||||
/**
|
||||
* 是否必答
|
||||
@ -41,10 +37,15 @@ public class Question {
|
||||
/**
|
||||
* 跳转逻辑(JSON格式)
|
||||
*/
|
||||
private String nextQuestionLogic;
|
||||
private Map<String, Integer> next;
|
||||
|
||||
/**
|
||||
* 是否是最后一题
|
||||
*/
|
||||
private Boolean isLast;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
private LocalDateTime createdAt;
|
||||
private Instant createdAt;
|
||||
}
|
||||
@ -1,6 +1,6 @@
|
||||
package ltd.qubit.survey.model;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
import lombok.Data;
|
||||
|
||||
@ -30,12 +30,12 @@ public class SurveyResponse {
|
||||
private List<String> selectedOptions;
|
||||
|
||||
/**
|
||||
* 文本答案
|
||||
* 文本答案(用于文本题或需要填写文本的选项)
|
||||
*/
|
||||
private String textAnswer;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
private LocalDateTime createdAt;
|
||||
private Instant createdAt;
|
||||
}
|
||||
@ -1,6 +1,6 @@
|
||||
package ltd.qubit.survey.model;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.Instant;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
@ -23,11 +23,6 @@ public class User {
|
||||
*/
|
||||
private String phone;
|
||||
|
||||
/**
|
||||
* 工作领域
|
||||
*/
|
||||
private WorkArea workArea;
|
||||
|
||||
/**
|
||||
* 岗位性质
|
||||
*/
|
||||
@ -36,5 +31,5 @@ public class User {
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
private LocalDateTime createdAt;
|
||||
private Instant createdAt;
|
||||
}
|
||||
@ -1,51 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@ -3,7 +3,6 @@ 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;
|
||||
|
||||
/**
|
||||
@ -19,14 +18,6 @@ public interface QuestionService extends BaseService<Question, Long> {
|
||||
*/
|
||||
Optional<Question> findByQuestionNumber(Integer questionNumber);
|
||||
|
||||
/**
|
||||
* 根据工作领域查询问题列表
|
||||
*
|
||||
* @param workArea 工作领域
|
||||
* @return 问题列表
|
||||
*/
|
||||
List<Question> findByWorkArea(WorkArea workArea);
|
||||
|
||||
/**
|
||||
* 查询通用问题列表(不针对特定工作领域)
|
||||
*
|
||||
|
||||
@ -3,44 +3,70 @@ 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> {
|
||||
public interface UserService {
|
||||
/**
|
||||
* 创建用户
|
||||
*
|
||||
* @param user 用户信息
|
||||
* @return 创建成功的用户信息
|
||||
*/
|
||||
User create(User user);
|
||||
|
||||
/**
|
||||
* 删除用户
|
||||
*
|
||||
* @param id 用户ID
|
||||
*/
|
||||
void delete(Long id);
|
||||
|
||||
/**
|
||||
* 更新用户信息
|
||||
*
|
||||
* @param user 用户信息
|
||||
* @return 更新后的用户信息
|
||||
*/
|
||||
User update(User user);
|
||||
|
||||
/**
|
||||
* 根据ID查询用户
|
||||
*
|
||||
* @param id 用户ID
|
||||
* @return 用户信息
|
||||
*/
|
||||
Optional<User> findById(Long id);
|
||||
|
||||
/**
|
||||
* 查询所有用户
|
||||
*
|
||||
* @return 用户列表
|
||||
*/
|
||||
List<User> findAll();
|
||||
|
||||
/**
|
||||
* 根据手机号查询用户
|
||||
*
|
||||
* @param phone 手机号
|
||||
* @return 用户对象
|
||||
* @return 用户信息
|
||||
*/
|
||||
Optional<User> findByPhone(String phone);
|
||||
|
||||
/**
|
||||
* 根据工作领域查询用户列表
|
||||
*
|
||||
* @param workArea 工作领域
|
||||
* @return 用户列表
|
||||
*/
|
||||
List<User> findByWorkArea(WorkArea workArea);
|
||||
|
||||
/**
|
||||
* 用户注册
|
||||
*
|
||||
* @param user 用户信息
|
||||
* @return 注册成功的用户
|
||||
* @return 注册成功的用户信息
|
||||
*/
|
||||
@Transactional
|
||||
User register(User user);
|
||||
|
||||
/**
|
||||
* 检查手机号是否已被注册
|
||||
* 用户登录或注册
|
||||
*
|
||||
* @param phone 手机号
|
||||
* @return 是否已注册
|
||||
* @param user 用户信息
|
||||
* @return 登录或注册成功的用户信息
|
||||
*/
|
||||
boolean isPhoneRegistered(String phone);
|
||||
User loginOrRegister(User user);
|
||||
}
|
||||
@ -1,6 +1,6 @@
|
||||
package ltd.qubit.survey.service.impl;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
@ -19,7 +19,7 @@ public class OptionServiceImpl implements OptionService {
|
||||
|
||||
@Override
|
||||
public Option create(Option option) {
|
||||
option.setCreatedAt(LocalDateTime.now());
|
||||
option.setCreatedAt(Instant.now());
|
||||
optionDao.insert(option);
|
||||
return option;
|
||||
}
|
||||
@ -58,7 +58,7 @@ public class OptionServiceImpl implements OptionService {
|
||||
@Override
|
||||
public List<Option> batchCreate(List<Option> options) {
|
||||
// 设置创建时间
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
Instant now = Instant.now();
|
||||
options.forEach(option -> option.setCreatedAt(now));
|
||||
|
||||
// 批量插入
|
||||
|
||||
@ -2,7 +2,7 @@ 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.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@ -11,7 +11,6 @@ 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;
|
||||
@ -29,10 +28,10 @@ public class QuestionServiceImpl implements QuestionService {
|
||||
@Override
|
||||
public Question create(Question question) {
|
||||
// 如果没有指定问题序号,则自动生成
|
||||
if (question.getQuestionNumber() == null) {
|
||||
question.setQuestionNumber(questionDao.getNextQuestionNumber());
|
||||
if (question.getNumber() == null) {
|
||||
question.setNumber(questionDao.getNextQuestionNumber());
|
||||
}
|
||||
question.setCreatedAt(LocalDateTime.now());
|
||||
question.setCreatedAt(Instant.now());
|
||||
questionDao.insert(question);
|
||||
return question;
|
||||
}
|
||||
@ -63,11 +62,6 @@ public class QuestionServiceImpl implements QuestionService {
|
||||
return questionDao.findByQuestionNumber(questionNumber);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Question> findByWorkArea(WorkArea workArea) {
|
||||
return questionDao.findByWorkArea(workArea);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Question> findCommonQuestions() {
|
||||
return questionDao.findCommonQuestions();
|
||||
@ -75,6 +69,11 @@ public class QuestionServiceImpl implements QuestionService {
|
||||
|
||||
@Override
|
||||
public Optional<Question> getNextQuestion(Long userId, Integer currentQuestionNumber, List<String> selectedOptions) {
|
||||
// 如果当前问题序号为0,返回第一个问题
|
||||
if (currentQuestionNumber == 0) {
|
||||
return findByQuestionNumber(1);
|
||||
}
|
||||
|
||||
// 获取当前问题
|
||||
Optional<Question> currentQuestion = findByQuestionNumber(currentQuestionNumber);
|
||||
if (currentQuestion.isEmpty()) {
|
||||
@ -82,21 +81,17 @@ public class QuestionServiceImpl implements QuestionService {
|
||||
}
|
||||
|
||||
// 如果当前问题有跳转逻辑,则根据选项判断下一个问题
|
||||
String nextQuestionLogic = currentQuestion.get().getNextQuestionLogic();
|
||||
if (nextQuestionLogic != null && !nextQuestionLogic.isEmpty()) {
|
||||
try {
|
||||
// 解析跳转逻辑JSON
|
||||
Map<String, Integer> logic = objectMapper.readValue(nextQuestionLogic,
|
||||
new TypeReference<Map<String, Integer>>() {});
|
||||
|
||||
Map<String, Integer> next = currentQuestion.get().getNext();
|
||||
if (next != null && !next.isEmpty() && selectedOptions != null && !selectedOptions.isEmpty()) {
|
||||
// 根据选项确定下一个问题序号
|
||||
for (String option : selectedOptions) {
|
||||
if (logic.containsKey(option)) {
|
||||
return findByQuestionNumber(logic.get(option));
|
||||
if (next.containsKey(option)) {
|
||||
return findByQuestionNumber(next.get(option));
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// JSON解析错误,继续使用默认的下一个问题
|
||||
// 如果有通配符跳转规则
|
||||
if (next.containsKey("*")) {
|
||||
return findByQuestionNumber(next.get("*"));
|
||||
}
|
||||
}
|
||||
|
||||
@ -115,13 +110,10 @@ public class QuestionServiceImpl implements QuestionService {
|
||||
}
|
||||
|
||||
// 添加通用问题
|
||||
questions.addAll(findCommonQuestions());
|
||||
|
||||
// 添加针对用户工作领域的问题
|
||||
questions.addAll(findByWorkArea(user.get().getWorkArea()));
|
||||
questions.addAll(findAll());
|
||||
|
||||
// 按问题序号排序
|
||||
questions.sort((q1, q2) -> q1.getQuestionNumber().compareTo(q2.getQuestionNumber()));
|
||||
questions.sort((q1, q2) -> q1.getNumber().compareTo(q2.getNumber()));
|
||||
|
||||
return questions;
|
||||
}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
package ltd.qubit.survey.service.impl;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
@ -22,7 +22,7 @@ public class SurveyResponseServiceImpl implements SurveyResponseService {
|
||||
|
||||
@Override
|
||||
public SurveyResponse create(SurveyResponse response) {
|
||||
response.setCreatedAt(LocalDateTime.now());
|
||||
response.setCreatedAt(Instant.now());
|
||||
surveyResponseDao.insert(response);
|
||||
return response;
|
||||
}
|
||||
@ -66,7 +66,7 @@ public class SurveyResponseServiceImpl implements SurveyResponseService {
|
||||
@Override
|
||||
public List<SurveyResponse> batchSave(List<SurveyResponse> responses) {
|
||||
// 设置创建时间
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
Instant now = Instant.now();
|
||||
responses.forEach(response -> response.setCreatedAt(now));
|
||||
|
||||
// 批量插入
|
||||
@ -81,24 +81,11 @@ public class SurveyResponseServiceImpl implements SurveyResponseService {
|
||||
|
||||
@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();
|
||||
Instant now = Instant.now();
|
||||
responses.forEach(response -> {
|
||||
response.setUserId(userId);
|
||||
response.setCreatedAt(now);
|
||||
|
||||
@ -1,14 +1,14 @@
|
||||
package ltd.qubit.survey.service.impl;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.Instant;
|
||||
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;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
/**
|
||||
* 用户服务实现类
|
||||
@ -19,19 +19,26 @@ public class UserServiceImpl implements UserService {
|
||||
private final UserDao userDao;
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public User create(User user) {
|
||||
user.setCreatedAt(LocalDateTime.now());
|
||||
user.setCreatedAt(Instant.now());
|
||||
userDao.insert(user);
|
||||
return user;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public void delete(Long id) {
|
||||
userDao.deleteById(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public User update(User user) {
|
||||
Optional<User> existingUser = findById(user.getId());
|
||||
if (existingUser.isEmpty()) {
|
||||
throw new IllegalArgumentException("用户不存在");
|
||||
}
|
||||
userDao.update(user);
|
||||
return user;
|
||||
}
|
||||
@ -52,21 +59,25 @@ public class UserServiceImpl implements UserService {
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<User> findByWorkArea(WorkArea workArea) {
|
||||
return userDao.findByWorkArea(workArea);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public User register(User user) {
|
||||
// 检查手机号是否已注册
|
||||
if (isPhoneRegistered(user.getPhone())) {
|
||||
Optional<User> existingUser = findByPhone(user.getPhone());
|
||||
if (existingUser.isPresent()) {
|
||||
throw new IllegalArgumentException("手机号已被注册");
|
||||
}
|
||||
return create(user);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPhoneRegistered(String phone) {
|
||||
return userDao.findByPhone(phone).isPresent();
|
||||
@Transactional
|
||||
public User loginOrRegister(User user) {
|
||||
Optional<User> existingUser = findByPhone(user.getPhone());
|
||||
if (existingUser.isPresent()) {
|
||||
User updatedUser = existingUser.get();
|
||||
updatedUser.setName(user.getName());
|
||||
updatedUser.setPositionType(user.getPositionType());
|
||||
return update(updatedUser);
|
||||
}
|
||||
return register(user);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,29 @@
|
||||
package ltd.qubit.survey.utils;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.SerializationFeature;
|
||||
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
|
||||
import java.time.ZoneOffset;
|
||||
|
||||
/**
|
||||
* 自定义的ObjectMapper,支持Java 8日期时间类型
|
||||
*/
|
||||
public class CustomObjectMapper extends ObjectMapper {
|
||||
public CustomObjectMapper() {
|
||||
super();
|
||||
// 注册Java 8日期时间模块
|
||||
JavaTimeModule javaTimeModule = new JavaTimeModule();
|
||||
registerModule(javaTimeModule);
|
||||
|
||||
// 配置序列化特性
|
||||
setSerializationInclusion(JsonInclude.Include.NON_NULL);
|
||||
// 将日期序列化为时间戳(毫秒)
|
||||
configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, true);
|
||||
configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
|
||||
|
||||
// 设置默认时区为UTC
|
||||
setTimeZone(java.util.TimeZone.getTimeZone(ZoneOffset.UTC));
|
||||
}
|
||||
}
|
||||
@ -1,6 +1,6 @@
|
||||
# 数据库配置
|
||||
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.url=jdbc:mysql://host.docker.internal:3306/llm_survey?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
|
||||
jdbc.username=dev
|
||||
jdbc.password=
|
||||
|
||||
|
||||
54
backend/src/main/resources/logback.xml
Normal file
54
backend/src/main/resources/logback.xml
Normal file
@ -0,0 +1,54 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<configuration>
|
||||
<!-- 定义日志文件的存储地址 -->
|
||||
<property name="LOG_HOME" value="${catalina.base}/logs/llm-survey-api"/>
|
||||
|
||||
<!-- 控制台输出 -->
|
||||
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder>
|
||||
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
|
||||
<charset>UTF-8</charset>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<!-- 按照每天生成日志文件 -->
|
||||
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||
<file>${LOG_HOME}/app.log</file>
|
||||
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
||||
<!-- 日志文件输出的文件名 -->
|
||||
<fileNamePattern>${LOG_HOME}/app.%d{yyyy-MM-dd}.log</fileNamePattern>
|
||||
<!-- 日志文件保留天数 -->
|
||||
<maxHistory>30</maxHistory>
|
||||
</rollingPolicy>
|
||||
<encoder>
|
||||
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
|
||||
<charset>UTF-8</charset>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<!-- 异步输出 -->
|
||||
<appender name="ASYNC_FILE" class="ch.qos.logback.classic.AsyncAppender">
|
||||
<!-- 不丢失日志,默认的,如果队列的80%已满,则会丢弃TRACE、DEBUG、INFO级别的日志 -->
|
||||
<discardingThreshold>0</discardingThreshold>
|
||||
<!-- 更改默认的队列的深度,该值会影响性能,默认值为256 -->
|
||||
<queueSize>512</queueSize>
|
||||
<!-- 添加附加的appender,最多只能添加一个 -->
|
||||
<appender-ref ref="FILE"/>
|
||||
</appender>
|
||||
|
||||
<!-- MyBatis日志配置 -->
|
||||
<logger name="org.apache.ibatis" level="DEBUG"/>
|
||||
<logger name="java.sql" level="DEBUG"/>
|
||||
|
||||
<!-- 项目日志配置 -->
|
||||
<logger name="ltd.qubit.survey" level="DEBUG"/>
|
||||
|
||||
<!-- Spring日志配置 -->
|
||||
<logger name="org.springframework" level="INFO"/>
|
||||
|
||||
<!-- 日志输出级别 -->
|
||||
<root level="INFO">
|
||||
<appender-ref ref="CONSOLE"/>
|
||||
<appender-ref ref="ASYNC_FILE"/>
|
||||
</root>
|
||||
</configuration>
|
||||
@ -5,7 +5,7 @@
|
||||
<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="code" column="code"/>
|
||||
<result property="content" column="content"/>
|
||||
<result property="requiresText" column="requires_text"/>
|
||||
<result property="createdAt" column="created_at"/>
|
||||
@ -13,71 +13,71 @@
|
||||
|
||||
<!-- 基础列 -->
|
||||
<sql id="baseColumns">
|
||||
id, question_id, option_code, content, requires_text, created_at
|
||||
`id`, `question_id`, `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 INTO `option` (`question_id`, `code`, `content`, `requires_text`)
|
||||
VALUES (#{questionId}, #{code}, #{content}, #{requiresText})
|
||||
</insert>
|
||||
|
||||
<!-- 批量插入 -->
|
||||
<insert id="batchInsert" parameterType="java.util.List">
|
||||
INSERT INTO options (question_id, option_code, content, requires_text)
|
||||
INSERT INTO `option` (`question_id`, `code`, `content`, `requires_text`)
|
||||
VALUES
|
||||
<foreach collection="list" item="item" separator=",">
|
||||
(#{item.questionId}, #{item.optionCode}, #{item.content}, #{item.requiresText})
|
||||
(#{item.questionId}, #{item.code}, #{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 `option`
|
||||
SET `question_id` = #{questionId},
|
||||
`code` = #{code},
|
||||
`content` = #{content},
|
||||
`requires_text` = #{requiresText}
|
||||
WHERE `id` = #{id}
|
||||
</update>
|
||||
|
||||
<!-- 删除 -->
|
||||
<delete id="deleteById" parameterType="long">
|
||||
DELETE FROM options WHERE id = #{id}
|
||||
DELETE FROM `option` WHERE `id` = #{id}
|
||||
</delete>
|
||||
|
||||
<!-- 根据问题ID删除 -->
|
||||
<delete id="deleteByQuestionId" parameterType="long">
|
||||
DELETE FROM options WHERE question_id = #{questionId}
|
||||
DELETE FROM `option` WHERE `question_id` = #{questionId}
|
||||
</delete>
|
||||
|
||||
<!-- 根据ID查询 -->
|
||||
<select id="findById" parameterType="long" resultMap="optionMap">
|
||||
SELECT <include refid="baseColumns"/>
|
||||
FROM options
|
||||
WHERE id = #{id}
|
||||
FROM `option`
|
||||
WHERE `id` = #{id}
|
||||
</select>
|
||||
|
||||
<!-- 查询所有 -->
|
||||
<select id="findAll" resultMap="optionMap">
|
||||
SELECT <include refid="baseColumns"/>
|
||||
FROM options
|
||||
ORDER BY question_id, option_code
|
||||
FROM `option`
|
||||
ORDER BY `question_id`, `code`
|
||||
</select>
|
||||
|
||||
<!-- 根据问题ID查询 -->
|
||||
<select id="findByQuestionId" parameterType="long" resultMap="optionMap">
|
||||
SELECT <include refid="baseColumns"/>
|
||||
FROM options
|
||||
WHERE question_id = #{questionId}
|
||||
ORDER BY option_code
|
||||
FROM `option`
|
||||
WHERE `question_id` = #{questionId}
|
||||
ORDER BY `code`
|
||||
</select>
|
||||
|
||||
<!-- 根据问题ID和选项代码查询 -->
|
||||
<select id="findByQuestionIdAndCode" resultMap="optionMap">
|
||||
SELECT <include refid="baseColumns"/>
|
||||
FROM options
|
||||
WHERE question_id = #{questionId}
|
||||
AND option_code = #{optionCode}
|
||||
FROM `option`
|
||||
WHERE `question_id` = #{questionId}
|
||||
AND `code` = #{code}
|
||||
</select>
|
||||
</mapper>
|
||||
@ -4,83 +4,76 @@
|
||||
<!-- 结果映射 -->
|
||||
<resultMap id="questionMap" type="ltd.qubit.survey.model.Question">
|
||||
<id property="id" column="id"/>
|
||||
<result property="questionNumber" column="question_number"/>
|
||||
<result property="number" column="number"/>
|
||||
<result property="content" column="content"/>
|
||||
<result property="questionType" column="question_type"/>
|
||||
<result property="workArea" column="work_area"/>
|
||||
<result property="type" column="type"/>
|
||||
<result property="isRequired" column="is_required"/>
|
||||
<result property="nextQuestionLogic" column="next_question_logic"/>
|
||||
<result property="next" column="next" typeHandler="ltd.qubit.survey.common.mybatis.JsonTypeHandler"/>
|
||||
<result property="isLast" column="is_last"/>
|
||||
<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
|
||||
`id`, `number`, `content`, `type`, `is_required`, `next`, `is_last`, `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 INTO `question` (`number`, `content`, `type`, `is_required`, `next`, `is_last`)
|
||||
VALUES (#{number}, #{content}, #{type}, #{isRequired},
|
||||
#{next,typeHandler=ltd.qubit.survey.common.mybatis.JsonTypeHandler},
|
||||
#{isLast})
|
||||
</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 `question`
|
||||
SET `number` = #{number},
|
||||
`content` = #{content},
|
||||
`type` = #{type},
|
||||
`is_required` = #{isRequired},
|
||||
`next` = #{next,typeHandler=ltd.qubit.survey.common.mybatis.JsonTypeHandler},
|
||||
`is_last` = #{isLast}
|
||||
WHERE `id` = #{id}
|
||||
</update>
|
||||
|
||||
<!-- 删除 -->
|
||||
<delete id="deleteById" parameterType="long">
|
||||
DELETE FROM questions WHERE id = #{id}
|
||||
DELETE FROM `question` WHERE `id` = #{id}
|
||||
</delete>
|
||||
|
||||
<!-- 根据ID查询 -->
|
||||
<select id="findById" parameterType="long" resultMap="questionMap">
|
||||
SELECT <include refid="baseColumns"/>
|
||||
FROM questions
|
||||
WHERE id = #{id}
|
||||
FROM `question`
|
||||
WHERE `id` = #{id}
|
||||
</select>
|
||||
|
||||
<!-- 查询所有 -->
|
||||
<select id="findAll" resultMap="questionMap">
|
||||
SELECT <include refid="baseColumns"/>
|
||||
FROM questions
|
||||
ORDER BY question_number
|
||||
FROM `question`
|
||||
ORDER BY `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
|
||||
FROM `question`
|
||||
WHERE `number` = #{number}
|
||||
</select>
|
||||
|
||||
<!-- 查询通用问题 -->
|
||||
<select id="findCommonQuestions" resultMap="questionMap">
|
||||
SELECT <include refid="baseColumns"/>
|
||||
FROM questions
|
||||
WHERE work_area IS NULL
|
||||
ORDER BY question_number
|
||||
FROM `question`
|
||||
ORDER BY `number`
|
||||
</select>
|
||||
|
||||
<!-- 获取下一个问题序号 -->
|
||||
<select id="getNextQuestionNumber" resultType="int">
|
||||
SELECT COALESCE(MAX(question_number) + 1, 1)
|
||||
FROM questions
|
||||
SELECT COALESCE(MAX(`number`) + 1, 1)
|
||||
FROM `question`
|
||||
</select>
|
||||
</mapper>
|
||||
@ -6,88 +6,90 @@
|
||||
<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="selectedOptions" column="selected_options" typeHandler="ltd.qubit.survey.common.mybatis.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
|
||||
`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 INTO `survey_response` (`user_id`, `question_id`, `selected_options`, `text_answer`)
|
||||
VALUES (#{userId}, #{questionId},
|
||||
#{selectedOptions,typeHandler=ltd.qubit.survey.common.mybatis.JsonTypeHandler},
|
||||
#{textAnswer})
|
||||
</insert>
|
||||
|
||||
<!-- 批量插入 -->
|
||||
<insert id="batchInsert" parameterType="java.util.List">
|
||||
INSERT INTO survey_responses (user_id, question_id, selected_options, text_answer)
|
||||
INSERT INTO `survey_response` (`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.selectedOptions,typeHandler=ltd.qubit.survey.common.mybatis.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 `survey_response`
|
||||
SET `user_id` = #{userId},
|
||||
`question_id` = #{questionId},
|
||||
`selected_options` = #{selectedOptions,typeHandler=ltd.qubit.survey.common.mybatis.JsonTypeHandler},
|
||||
`text_answer` = #{textAnswer}
|
||||
WHERE `id` = #{id}
|
||||
</update>
|
||||
|
||||
<!-- 删除 -->
|
||||
<delete id="deleteById" parameterType="long">
|
||||
DELETE FROM survey_responses WHERE id = #{id}
|
||||
DELETE FROM `survey_response` WHERE `id` = #{id}
|
||||
</delete>
|
||||
|
||||
<!-- 根据用户ID删除 -->
|
||||
<delete id="deleteByUserId" parameterType="long">
|
||||
DELETE FROM survey_responses WHERE user_id = #{userId}
|
||||
DELETE FROM `survey_response` WHERE `user_id` = #{userId}
|
||||
</delete>
|
||||
|
||||
<!-- 根据ID查询 -->
|
||||
<select id="findById" parameterType="long" resultMap="responseMap">
|
||||
SELECT <include refid="baseColumns"/>
|
||||
FROM survey_responses
|
||||
WHERE id = #{id}
|
||||
FROM `survey_response`
|
||||
WHERE `id` = #{id}
|
||||
</select>
|
||||
|
||||
<!-- 查询所有 -->
|
||||
<select id="findAll" resultMap="responseMap">
|
||||
SELECT <include refid="baseColumns"/>
|
||||
FROM survey_responses
|
||||
ORDER BY user_id, question_id
|
||||
FROM `survey_response`
|
||||
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
|
||||
FROM `survey_response`
|
||||
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
|
||||
FROM `survey_response`
|
||||
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}
|
||||
FROM `survey_response`
|
||||
WHERE `user_id` = #{userId}
|
||||
AND `question_id` = #{questionId}
|
||||
</select>
|
||||
</mapper>
|
||||
@ -4,65 +4,54 @@
|
||||
<!-- 结果映射 -->
|
||||
<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="name" column="name"/>
|
||||
<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 id="columns">
|
||||
id, phone, name, 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 INTO user (phone, name, position_type, created_at)
|
||||
VALUES (#{phone}, #{name}, #{positionType}, NOW())
|
||||
</insert>
|
||||
|
||||
<!-- 更新 -->
|
||||
<update id="update" parameterType="ltd.qubit.survey.model.User">
|
||||
UPDATE users
|
||||
UPDATE user
|
||||
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 FROM user WHERE id = #{id}
|
||||
</delete>
|
||||
|
||||
<!-- 根据ID查询 -->
|
||||
<select id="findById" parameterType="long" resultMap="userMap">
|
||||
SELECT <include refid="baseColumns"/>
|
||||
FROM users
|
||||
SELECT <include refid="columns"/>
|
||||
FROM user
|
||||
WHERE id = #{id}
|
||||
</select>
|
||||
|
||||
<!-- 查询所有 -->
|
||||
<select id="findAll" resultMap="userMap">
|
||||
SELECT <include refid="baseColumns"/>
|
||||
FROM users
|
||||
SELECT <include refid="columns"/>
|
||||
FROM user
|
||||
ORDER BY id
|
||||
</select>
|
||||
|
||||
<!-- 根据手机号查询 -->
|
||||
<select id="findByPhone" parameterType="string" resultMap="userMap">
|
||||
SELECT <include refid="baseColumns"/>
|
||||
FROM users
|
||||
SELECT <include refid="columns"/>
|
||||
FROM user
|
||||
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>
|
||||
@ -12,21 +12,28 @@
|
||||
<setting name="logImpl" value="SLF4J"/>
|
||||
</settings>
|
||||
|
||||
<!-- 类型别名配置 -->
|
||||
<typeAliases>
|
||||
<package name="ltd.qubit.survey.model"/>
|
||||
</typeAliases>
|
||||
|
||||
<!-- 类型处理器配置 -->
|
||||
<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"/>
|
||||
<!-- JSON类型处理器 -->
|
||||
<typeHandler handler="ltd.qubit.survey.common.mybatis.JsonTypeHandler"
|
||||
javaType="java.util.List"/>
|
||||
<!-- Instant类型处理器 -->
|
||||
<typeHandler handler="ltd.qubit.survey.common.mybatis.InstantTypeHandler"
|
||||
javaType="java.time.Instant"/>
|
||||
</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"/>
|
||||
<package name="ltd.qubit.survey.dao"/>
|
||||
</mappers>
|
||||
</configuration>
|
||||
@ -3,16 +3,22 @@
|
||||
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"
|
||||
xmlns:aop="http://www.springframework.org/schema/aop"
|
||||
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">
|
||||
http://www.springframework.org/schema/tx/spring-tx.xsd
|
||||
http://www.springframework.org/schema/aop
|
||||
http://www.springframework.org/schema/aop/spring-aop.xsd">
|
||||
|
||||
<!-- 加载属性文件 -->
|
||||
<context:property-placeholder location="classpath:application.properties"/>
|
||||
|
||||
<!-- 配置自定义ObjectMapper -->
|
||||
<bean id="objectMapper" class="ltd.qubit.survey.utils.CustomObjectMapper" primary="true"/>
|
||||
|
||||
<!-- 开启注解扫描,排除Controller -->
|
||||
<context:component-scan base-package="ltd.qubit.survey">
|
||||
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
|
||||
@ -21,4 +27,21 @@
|
||||
<!-- 配置事务注解驱动 -->
|
||||
<tx:annotation-driven/>
|
||||
|
||||
<!-- 配置事务通知 -->
|
||||
<tx:advice id="txAdvice" transaction-manager="transactionManager">
|
||||
<tx:attributes>
|
||||
<tx:method name="get*" read-only="true"/>
|
||||
<tx:method name="find*" read-only="true"/>
|
||||
<tx:method name="list*" read-only="true"/>
|
||||
<tx:method name="query*" read-only="true"/>
|
||||
<tx:method name="*" propagation="REQUIRED"/>
|
||||
</tx:attributes>
|
||||
</tx:advice>
|
||||
|
||||
<!-- 配置事务切面 -->
|
||||
<aop:config>
|
||||
<aop:pointcut id="txPointcut" expression="execution(* ltd.qubit.survey.service..*.*(..))"/>
|
||||
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut"/>
|
||||
</aop:config>
|
||||
|
||||
</beans>
|
||||
@ -20,27 +20,39 @@
|
||||
<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 name="objectMapper" ref="objectMapper"/>
|
||||
<property name="supportedMediaTypes">
|
||||
<list>
|
||||
<value>application/json;charset=UTF-8</value>
|
||||
</list>
|
||||
</property>
|
||||
</bean>
|
||||
<!-- 配置字符串转换器 -->
|
||||
<bean class="org.springframework.http.converter.StringHttpMessageConverter">
|
||||
<property name="supportedMediaTypes">
|
||||
<list>
|
||||
<value>text/plain;charset=UTF-8</value>
|
||||
<value>text/html;charset=UTF-8</value>
|
||||
</list>
|
||||
</property>
|
||||
</bean>
|
||||
</mvc:message-converters>
|
||||
</mvc:annotation-driven>
|
||||
|
||||
<!-- 配置静态资源处理 -->
|
||||
<mvc:default-servlet-handler/>
|
||||
|
||||
<!-- 配置跨域支持 -->
|
||||
<mvc:cors>
|
||||
<mvc:mapping path="/**"
|
||||
allowed-origins="*"
|
||||
allowed-origins="http://localhost:3000,http://localhost:8080"
|
||||
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>
|
||||
|
||||
<!-- 配置异常处理器 -->
|
||||
<bean class="ltd.qubit.survey.controller.GlobalExceptionHandler"/>
|
||||
|
||||
</beans>
|
||||
@ -1,8 +1,11 @@
|
||||
<?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: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/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/tx
|
||||
http://www.springframework.org/schema/tx/spring-tx.xsd">
|
||||
|
||||
<!-- 配置数据源 -->
|
||||
<bean id="dataSource" class="com.zaxxer.hikari.HikariDataSource" destroy-method="close">
|
||||
@ -16,13 +19,35 @@
|
||||
<property name="idleTimeout" value="${jdbc.pool.idleTimeout}"/>
|
||||
<property name="connectionTimeout" value="${jdbc.pool.connectionTimeout}"/>
|
||||
<property name="connectionTestQuery" value="${jdbc.pool.connectionTestQuery}"/>
|
||||
<!-- 其他配置 -->
|
||||
<property name="autoCommit" value="true"/>
|
||||
<property name="poolName" value="HikariPool-Survey"/>
|
||||
<property name="maxLifetime" value="1800000"/>
|
||||
<property name="validationTimeout" value="5000"/>
|
||||
<property name="leakDetectionThreshold" value="60000"/>
|
||||
</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"/>
|
||||
<property name="mapperLocations" value="classpath*:mybatis/mapper/*.xml"/>
|
||||
<!-- 配置插件 -->
|
||||
<property name="plugins">
|
||||
<array>
|
||||
<!-- 分页插件 -->
|
||||
<bean class="com.github.pagehelper.PageInterceptor">
|
||||
<property name="properties">
|
||||
<value>
|
||||
helperDialect=mysql
|
||||
reasonable=true
|
||||
supportMethodsArguments=true
|
||||
params=count=countSql
|
||||
</value>
|
||||
</property>
|
||||
</bean>
|
||||
</array>
|
||||
</property>
|
||||
</bean>
|
||||
|
||||
<!-- 配置Mapper扫描器 -->
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
|
||||
<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
|
||||
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">
|
||||
xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee
|
||||
https://jakarta.ee/xml/ns/jakartaee/web-app_6_0.xsd"
|
||||
version="6.0">
|
||||
|
||||
<display-name>LLM Survey API</display-name>
|
||||
|
||||
|
||||
@ -1,18 +0,0 @@
|
||||
# 数据库配置
|
||||
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
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -1,83 +0,0 @@
|
||||
<?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>
|
||||
@ -1,86 +0,0 @@
|
||||
<?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>
|
||||
@ -1,93 +0,0 @@
|
||||
<?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>
|
||||
@ -1,68 +0,0 @@
|
||||
<?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>
|
||||
@ -1,32 +0,0 @@
|
||||
<?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>
|
||||
@ -1,24 +0,0 @@
|
||||
<?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>
|
||||
@ -1,46 +0,0 @@
|
||||
<?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>
|
||||
@ -1,39 +0,0 @@
|
||||
<?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>
|
||||
@ -1,27 +0,0 @@
|
||||
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
|
||||
@ -1,26 +0,0 @@
|
||||
/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
|
||||
@ -1,73 +1,375 @@
|
||||
-- 创建数据库
|
||||
CREATE DATABASE IF NOT EXISTS llm_survey DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
CREATE DATABASE IF NOT EXISTS `llm_survey` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
|
||||
USE llm_survey;
|
||||
USE `llm_survey`;
|
||||
|
||||
-- 删除旧表(如果存在)
|
||||
DROP TABLE IF EXISTS `survey_response`;
|
||||
DROP TABLE IF EXISTS `option`;
|
||||
DROP TABLE IF EXISTS `question`;
|
||||
DROP TABLE IF EXISTS `user`;
|
||||
|
||||
-- 创建用户表
|
||||
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)
|
||||
CREATE TABLE IF NOT EXISTS `user` (
|
||||
`id` BIGINT PRIMARY KEY AUTO_INCREMENT,
|
||||
`name` VARCHAR(50) NOT NULL COMMENT '姓名',
|
||||
`phone` VARCHAR(20) NOT NULL COMMENT '手机号',
|
||||
`position_type` VARCHAR(20) NOT NULL COMMENT '岗位性质',
|
||||
`created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
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)
|
||||
CREATE TABLE IF NOT EXISTS `question` (
|
||||
`id` BIGINT PRIMARY KEY AUTO_INCREMENT,
|
||||
`number` INT NOT NULL COMMENT '问题序号',
|
||||
`content` TEXT NOT NULL COMMENT '问题内容',
|
||||
`type` VARCHAR(20) NOT NULL COMMENT '问题类型',
|
||||
`is_required` BOOLEAN NOT NULL DEFAULT TRUE COMMENT '是否必答',
|
||||
`next` JSON DEFAULT NULL COMMENT '跳转逻辑',
|
||||
`is_last` BOOLEAN NOT NULL DEFAULT FALSE COMMENT '是否是最后一题',
|
||||
`created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
UNIQUE KEY `uk_number` (`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)
|
||||
CREATE TABLE IF NOT EXISTS `option` (
|
||||
`id` BIGINT PRIMARY KEY AUTO_INCREMENT,
|
||||
`question_id` BIGINT NOT NULL COMMENT '关联的问题ID',
|
||||
`code` VARCHAR(10) NOT NULL COMMENT '选项代码',
|
||||
`content` TEXT NOT NULL COMMENT '选项内容',
|
||||
`requires_text` BOOLEAN NOT NULL DEFAULT FALSE COMMENT '是否需要填写文本',
|
||||
`created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
FOREIGN KEY (`question_id`) REFERENCES `question` (`id`),
|
||||
UNIQUE KEY `uk_question_code` (`question_id`, `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)
|
||||
-- 创建问卷答案表
|
||||
CREATE TABLE IF NOT EXISTS `survey_response` (
|
||||
`id` BIGINT PRIMARY KEY AUTO_INCREMENT,
|
||||
`user_id` BIGINT NOT NULL COMMENT '用户ID',
|
||||
`question_id` BIGINT NOT NULL COMMENT '问题ID',
|
||||
`selected_options` JSON DEFAULT NULL COMMENT '选中的选项代码列表',
|
||||
`text_answer` TEXT DEFAULT NULL COMMENT '文本答案(用于文本题或需要填写文本的选项)',
|
||||
`created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
FOREIGN KEY (`user_id`) REFERENCES `user` (`id`),
|
||||
FOREIGN KEY (`question_id`) REFERENCES `question` (`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);
|
||||
-- 清空现有数据
|
||||
SET FOREIGN_KEY_CHECKS = 0;
|
||||
TRUNCATE TABLE `survey_response`;
|
||||
TRUNCATE TABLE `option`;
|
||||
TRUNCATE TABLE `question`;
|
||||
TRUNCATE TABLE `user`;
|
||||
SET FOREIGN_KEY_CHECKS = 1;
|
||||
|
||||
-- 插入基础选项数据
|
||||
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 `question` (`number`, `content`, `type`, `is_required`, `is_last`)
|
||||
VALUES (1, '您对大模型(如ChatGPT、通义千问、DeepSeek)的了解程度:', 'SINGLE_CHOICE', TRUE, FALSE);
|
||||
|
||||
INSERT INTO options (question_id, option_code, content) VALUES
|
||||
(2, 'A', '管理岗'),
|
||||
(2, 'B', '技术岗'),
|
||||
(2, 'C', '业务岗'),
|
||||
(2, 'D', '职能支持岗');
|
||||
INSERT INTO `option` (`question_id`, `code`, `content`) VALUES
|
||||
(LAST_INSERT_ID(), 'A', '从未接触过'),
|
||||
(LAST_INSERT_ID(), 'B', '仅在日常简单使用过通用功能(如问答)'),
|
||||
(LAST_INSERT_ID(), 'C', '在工作中尝试过基础应用'),
|
||||
(LAST_INSERT_ID(), 'D', '深度研究过技术原理');
|
||||
|
||||
INSERT INTO `question` (`number`, `content`, `type`, `is_required`, `is_last`)
|
||||
VALUES (2, '您觉得大模型可以做到下面哪些事?', 'MULTIPLE_CHOICE', TRUE, FALSE);
|
||||
|
||||
INSERT INTO `option` (`question_id`, `code`, `content`, `requires_text`) VALUES
|
||||
(LAST_INSERT_ID(), 'A', '精准知识问答', FALSE),
|
||||
(LAST_INSERT_ID(), 'B', '文档撰写/报告生成/代码编写/图片视频生成等', FALSE),
|
||||
(LAST_INSERT_ID(), 'C', '数据清洗与分析', FALSE),
|
||||
(LAST_INSERT_ID(), 'D', '客户沟通与服务', FALSE),
|
||||
(LAST_INSERT_ID(), 'E', '风险识别与预警', FALSE),
|
||||
(LAST_INSERT_ID(), 'F', '流程自动化', FALSE),
|
||||
(LAST_INSERT_ID(), 'G', '其他', TRUE);
|
||||
|
||||
INSERT INTO `question` (`number`, `content`, `type`, `is_required`, `is_last`)
|
||||
VALUES (3, '您最关注大模型应用的哪些风险?', 'MULTIPLE_CHOICE', TRUE, FALSE);
|
||||
|
||||
INSERT INTO `option` (`question_id`, `code`, `content`, `requires_text`) VALUES
|
||||
(LAST_INSERT_ID(), 'A', '数据隐私泄露', FALSE),
|
||||
(LAST_INSERT_ID(), 'B', '生成内容不准确', FALSE),
|
||||
(LAST_INSERT_ID(), 'C', '合规审查风险', FALSE),
|
||||
(LAST_INSERT_ID(), 'D', '技术使用门槛高', FALSE),
|
||||
(LAST_INSERT_ID(), 'E', '其他', TRUE);
|
||||
|
||||
INSERT INTO `question` (`number`, `content`, `type`, `is_required`, `is_last`)
|
||||
VALUES (4, '您的主要工作内容是:', 'SINGLE_CHOICE', TRUE, FALSE);
|
||||
|
||||
INSERT INTO `option` (`question_id`, `code`, `content`) VALUES
|
||||
(LAST_INSERT_ID(), 'A', '研发(产品、开发、算法、测试、运维等)'),
|
||||
(LAST_INSERT_ID(), 'B', '项目管理(项目立项、进度跟踪、风险管理等)'),
|
||||
(LAST_INSERT_ID(), 'C', '保险(产品、核保、理赔、精算等)'),
|
||||
(LAST_INSERT_ID(), 'D', '财务(会计、税务、审计等)'),
|
||||
(LAST_INSERT_ID(), 'E', '客服(咨询、投诉、回访等)'),
|
||||
(LAST_INSERT_ID(), 'F', '运营(新媒体运营、广告宣传、活动策划、数据分析等)'),
|
||||
(LAST_INSERT_ID(), 'G', '市场拓展(渠道拓展、商务沟通、产品推广等)'),
|
||||
(LAST_INSERT_ID(), 'H', '人力资源(招聘、培训、绩效、薪酬等)'),
|
||||
(LAST_INSERT_ID(), 'I', '综合事务(行政、法务等)'),
|
||||
(LAST_INSERT_ID(), 'J', '公司高管(战略规划、组织架构、制度建设等)');
|
||||
|
||||
-- 研发部门专属问题
|
||||
INSERT INTO `question` (`number`, `content`, `type`, `is_required`, `is_last`)
|
||||
VALUES (5, '您在开发过程中最耗时的重复性工作:', 'MULTIPLE_CHOICE', TRUE, FALSE);
|
||||
|
||||
INSERT INTO `option` (`question_id`, `code`, `content`, `requires_text`) VALUES
|
||||
(LAST_INSERT_ID(), 'A', '文档编写(如需求文档、技术文档等)', FALSE),
|
||||
(LAST_INSERT_ID(), 'B', '产品原型、界面设计(如使用图片生成模型自动生成等)', FALSE),
|
||||
(LAST_INSERT_ID(), 'C', '代码编写', FALSE),
|
||||
(LAST_INSERT_ID(), 'D', '调试与测试', FALSE),
|
||||
(LAST_INSERT_ID(), 'E', '系统监控与维护', FALSE),
|
||||
(LAST_INSERT_ID(), 'F', '其他', TRUE);
|
||||
|
||||
INSERT INTO `question` (`number`, `content`, `type`, `is_required`, `is_last`)
|
||||
VALUES (6, '您希望大模型如何与现有系统集成:', 'MULTIPLE_CHOICE', TRUE, FALSE);
|
||||
|
||||
INSERT INTO `option` (`question_id`, `code`, `content`, `requires_text`) VALUES
|
||||
(LAST_INSERT_ID(), 'A', '回答技术问题', FALSE),
|
||||
(LAST_INSERT_ID(), 'B', '自动生成代码片段(如 Github Copilot等)', FALSE),
|
||||
(LAST_INSERT_ID(), 'C', '智能测试用例生成', FALSE),
|
||||
(LAST_INSERT_ID(), 'D', '生成需求文档、技术文档', FALSE),
|
||||
(LAST_INSERT_ID(), 'E', '完整项目生成(如Cursor等)', FALSE),
|
||||
(LAST_INSERT_ID(), 'F', '代码重构与优化', FALSE),
|
||||
(LAST_INSERT_ID(), 'G', '辅助设计算法(如DeepSeek等)', FALSE),
|
||||
(LAST_INSERT_ID(), 'H', '其他', TRUE);
|
||||
|
||||
-- 项目管理专属问题
|
||||
INSERT INTO `question` (`number`, `content`, `type`, `is_required`, `is_last`)
|
||||
VALUES (7, '项目管理中最常遇到的挑战是:', 'MULTIPLE_CHOICE', TRUE, FALSE);
|
||||
|
||||
INSERT INTO `option` (`question_id`, `code`, `content`, `requires_text`) VALUES
|
||||
(LAST_INSERT_ID(), 'A', '项目进度跟踪与更新', FALSE),
|
||||
(LAST_INSERT_ID(), 'B', '风险评估与管控', FALSE),
|
||||
(LAST_INSERT_ID(), 'C', '项目报告生成', FALSE),
|
||||
(LAST_INSERT_ID(), 'D', '其他', TRUE);
|
||||
|
||||
INSERT INTO `question` (`number`, `content`, `type`, `is_required`, `is_last`)
|
||||
VALUES (8, '您希望如何利用大模型提升项目管理效率:', 'MULTIPLE_CHOICE', TRUE, FALSE);
|
||||
|
||||
INSERT INTO `option` (`question_id`, `code`, `content`, `requires_text`) VALUES
|
||||
(LAST_INSERT_ID(), 'A', '自动生成立项报告、进度报告、总结报告等', FALSE),
|
||||
(LAST_INSERT_ID(), 'B', '风险预测与预警', FALSE),
|
||||
(LAST_INSERT_ID(), 'C', '项目资料自动化整理', FALSE),
|
||||
(LAST_INSERT_ID(), 'D', '知识库管理', FALSE),
|
||||
(LAST_INSERT_ID(), 'E', '其他', TRUE);
|
||||
|
||||
-- 保险专属问题
|
||||
INSERT INTO `question` (`number`, `content`, `type`, `is_required`, `is_last`)
|
||||
VALUES (9, '理赔处理中的主要瓶颈是:', 'MULTIPLE_CHOICE', TRUE, FALSE);
|
||||
|
||||
INSERT INTO `option` (`question_id`, `code`, `content`, `requires_text`) VALUES
|
||||
(LAST_INSERT_ID(), 'A', '理赔文档处理', FALSE),
|
||||
(LAST_INSERT_ID(), 'B', '医疗票据审核与核对', FALSE),
|
||||
(LAST_INSERT_ID(), 'C', '客户资料信息录入与处理', FALSE),
|
||||
(LAST_INSERT_ID(), 'D', '理赔规则理解与应用', FALSE),
|
||||
(LAST_INSERT_ID(), 'E', '其他', TRUE);
|
||||
|
||||
INSERT INTO `question` (`number`, `content`, `type`, `is_required`, `is_last`)
|
||||
VALUES (10, '大模型可以优化哪些保险工作环节:', 'MULTIPLE_CHOICE', TRUE, FALSE);
|
||||
|
||||
INSERT INTO `option` (`question_id`, `code`, `content`, `requires_text`) VALUES
|
||||
(LAST_INSERT_ID(), 'A', '新员工入职培训', FALSE),
|
||||
(LAST_INSERT_ID(), 'B', '保险产品设计的优化', FALSE),
|
||||
(LAST_INSERT_ID(), 'C', '自动生成理赔报告与告知书', FALSE),
|
||||
(LAST_INSERT_ID(), 'D', '自动化资料审核(如OCR识别票据数据、自动识别既往症等)', FALSE),
|
||||
(LAST_INSERT_ID(), 'E', '异常案件智能预警', FALSE),
|
||||
(LAST_INSERT_ID(), 'F', '其他', TRUE);
|
||||
|
||||
-- 财务专属问题
|
||||
INSERT INTO `question` (`number`, `content`, `type`, `is_required`, `is_last`)
|
||||
VALUES (11, '日常工作中最重复的任务是:', 'MULTIPLE_CHOICE', TRUE, FALSE);
|
||||
|
||||
INSERT INTO `option` (`question_id`, `code`, `content`, `requires_text`) VALUES
|
||||
(LAST_INSERT_ID(), 'A', '财务数据整理与报表生成', FALSE),
|
||||
(LAST_INSERT_ID(), 'B', '发票和报销单审核', FALSE),
|
||||
(LAST_INSERT_ID(), 'C', '财务审计与合规检查', FALSE),
|
||||
(LAST_INSERT_ID(), 'D', '其他', TRUE);
|
||||
|
||||
INSERT INTO `question` (`number`, `content`, `type`, `is_required`, `is_last`)
|
||||
VALUES (12, '大模型能如何协助提升财务工作效率:', 'MULTIPLE_CHOICE', TRUE, FALSE);
|
||||
|
||||
INSERT INTO `option` (`question_id`, `code`, `content`, `requires_text`) VALUES
|
||||
(LAST_INSERT_ID(), 'A', '各种报表格式的自动转换', FALSE),
|
||||
(LAST_INSERT_ID(), 'B', '自动生成财务报表与分析摘要', FALSE),
|
||||
(LAST_INSERT_ID(), 'C', '自动化审计和合规检查', FALSE),
|
||||
(LAST_INSERT_ID(), 'D', '财务数据智能分析与预测', FALSE),
|
||||
(LAST_INSERT_ID(), 'E', '其他', TRUE);
|
||||
|
||||
-- 客服专属问题
|
||||
INSERT INTO `question` (`number`, `content`, `type`, `is_required`, `is_last`)
|
||||
VALUES (13, '客户咨询中最常遇到的重复性问题:', 'MULTIPLE_CHOICE', TRUE, FALSE);
|
||||
|
||||
INSERT INTO `option` (`question_id`, `code`, `content`, `requires_text`) VALUES
|
||||
(LAST_INSERT_ID(), 'A', '参保资格咨询', FALSE),
|
||||
(LAST_INSERT_ID(), 'B', '理赔进度查询', FALSE),
|
||||
(LAST_INSERT_ID(), 'C', '材料补交通知', FALSE),
|
||||
(LAST_INSERT_ID(), 'D', '其他', TRUE);
|
||||
|
||||
INSERT INTO `question` (`number`, `content`, `type`, `is_required`, `is_last`)
|
||||
VALUES (14, '您希望大模型如何辅助客服工作:', 'MULTIPLE_CHOICE', TRUE, FALSE);
|
||||
|
||||
INSERT INTO `option` (`question_id`, `code`, `content`, `requires_text`) VALUES
|
||||
(LAST_INSERT_ID(), 'A', '自动生成客户回复模板', FALSE),
|
||||
(LAST_INSERT_ID(), 'B', '客户咨询自动分类与转接', FALSE),
|
||||
(LAST_INSERT_ID(), 'C', '智能分析客户情绪与需求', FALSE),
|
||||
(LAST_INSERT_ID(), 'D', '其他', TRUE);
|
||||
|
||||
-- 运营专属问题
|
||||
INSERT INTO `question` (`number`, `content`, `type`, `is_required`, `is_last`)
|
||||
VALUES (15, '在运营工作中,最需要自动化支持的任务是:', 'MULTIPLE_CHOICE', TRUE, FALSE);
|
||||
|
||||
INSERT INTO `option` (`question_id`, `code`, `content`, `requires_text`) VALUES
|
||||
(LAST_INSERT_ID(), 'A', '热点讯息的获取和跟踪', FALSE),
|
||||
(LAST_INSERT_ID(), 'B', '数据分析与报告生成', FALSE),
|
||||
(LAST_INSERT_ID(), 'C', '社交媒体内容创作', FALSE),
|
||||
(LAST_INSERT_ID(), 'D', '活动效果评估与预测', FALSE),
|
||||
(LAST_INSERT_ID(), 'E', '其他', TRUE);
|
||||
|
||||
INSERT INTO `question` (`number`, `content`, `type`, `is_required`, `is_last`)
|
||||
VALUES (16, '大模型可以如何帮助提升运营效率:', 'MULTIPLE_CHOICE', TRUE, FALSE);
|
||||
|
||||
INSERT INTO `option` (`question_id`, `code`, `content`, `requires_text`) VALUES
|
||||
(LAST_INSERT_ID(), 'A', '自动抓取热点讯息', FALSE),
|
||||
(LAST_INSERT_ID(), 'B', '自动生成社交媒体内容', FALSE),
|
||||
(LAST_INSERT_ID(), 'C', '用户评论分析与舆情监测', FALSE),
|
||||
(LAST_INSERT_ID(), 'D', '活动数据自动分析与报告生成', FALSE),
|
||||
(LAST_INSERT_ID(), 'E', '其他', TRUE);
|
||||
|
||||
-- 市场拓展专属问题
|
||||
INSERT INTO `question` (`number`, `content`, `type`, `is_required`, `is_last`)
|
||||
VALUES (17, '在市场拓展和商务沟通中,您最常遇到的挑战是:', 'MULTIPLE_CHOICE', TRUE, FALSE);
|
||||
|
||||
INSERT INTO `option` (`question_id`, `code`, `content`, `requires_text`) VALUES
|
||||
(LAST_INSERT_ID(), 'A', '市场分析和竞争对手跟踪', FALSE),
|
||||
(LAST_INSERT_ID(), 'B', '渠道拓展计划的自动化和优化', FALSE),
|
||||
(LAST_INSERT_ID(), 'C', '商务沟通中的信息处理与反馈跟踪', FALSE),
|
||||
(LAST_INSERT_ID(), 'D', '其他', TRUE);
|
||||
|
||||
INSERT INTO `question` (`number`, `content`, `type`, `is_required`, `is_last`)
|
||||
VALUES (18, '您希望大模型如何帮助提升市场拓展效率:', 'MULTIPLE_CHOICE', TRUE, FALSE);
|
||||
|
||||
INSERT INTO `option` (`question_id`, `code`, `content`, `requires_text`) VALUES
|
||||
(LAST_INSERT_ID(), 'A', '自动生成市场分析报告与趋势预测', FALSE),
|
||||
(LAST_INSERT_ID(), 'B', '根据目标客户数据生成个性化营销策略', FALSE),
|
||||
(LAST_INSERT_ID(), 'C', '自动化生成商务沟通邮件和提案文档', FALSE),
|
||||
(LAST_INSERT_ID(), 'D', '其他', TRUE);
|
||||
|
||||
-- 人力资源专属问题
|
||||
INSERT INTO `question` (`number`, `content`, `type`, `is_required`, `is_last`)
|
||||
VALUES (19, '人事部门最耗时的日常任务是:', 'MULTIPLE_CHOICE', TRUE, FALSE);
|
||||
|
||||
INSERT INTO `option` (`question_id`, `code`, `content`, `requires_text`) VALUES
|
||||
(LAST_INSERT_ID(), 'A', '招聘简历筛选与面试安排', FALSE),
|
||||
(LAST_INSERT_ID(), 'B', '员工培训与学习进度管理', FALSE),
|
||||
(LAST_INSERT_ID(), 'C', '绩效评估与报告生成', FALSE),
|
||||
(LAST_INSERT_ID(), 'D', '其他', TRUE);
|
||||
|
||||
INSERT INTO `question` (`number`, `content`, `type`, `is_required`, `is_last`)
|
||||
VALUES (20, '您希望大模型如何协助提升人事工作效率:', 'MULTIPLE_CHOICE', TRUE, FALSE);
|
||||
|
||||
INSERT INTO `option` (`question_id`, `code`, `content`, `requires_text`) VALUES
|
||||
(LAST_INSERT_ID(), 'A', '自动筛选招聘简历并推荐候选人', FALSE),
|
||||
(LAST_INSERT_ID(), 'B', '自动化培训内容推送与学习路径规划', FALSE),
|
||||
(LAST_INSERT_ID(), 'C', '绩效评估与员工反馈的自动化分析', FALSE),
|
||||
(LAST_INSERT_ID(), 'D', '其他', TRUE);
|
||||
|
||||
-- 综合管理专属问题
|
||||
INSERT INTO `question` (`number`, `content`, `type`, `is_required`, `is_last`)
|
||||
VALUES (21, '在行政工作中,最耗时的任务是:', 'MULTIPLE_CHOICE', TRUE, FALSE);
|
||||
|
||||
INSERT INTO `option` (`question_id`, `code`, `content`, `requires_text`) VALUES
|
||||
(LAST_INSERT_ID(), 'A', '合同审查与管理', FALSE),
|
||||
(LAST_INSERT_ID(), 'B', '会议纪要整理', FALSE),
|
||||
(LAST_INSERT_ID(), 'C', '文档管理与更新', FALSE),
|
||||
(LAST_INSERT_ID(), 'D', '其他', TRUE);
|
||||
|
||||
INSERT INTO `question` (`number`, `content`, `type`, `is_required`, `is_last`)
|
||||
VALUES (22, '您希望大模型如何协助提升行政工作效率:', 'MULTIPLE_CHOICE', TRUE, FALSE);
|
||||
|
||||
INSERT INTO `option` (`question_id`, `code`, `content`, `requires_text`) VALUES
|
||||
(LAST_INSERT_ID(), 'A', '自动生成合同和协议模板', FALSE),
|
||||
(LAST_INSERT_ID(), 'B', '自动化会议纪要整理与分发', FALSE),
|
||||
(LAST_INSERT_ID(), 'C', '自动化文档归档与管理', FALSE),
|
||||
(LAST_INSERT_ID(), 'D', '其他', TRUE);
|
||||
|
||||
-- 公司高管专属问题
|
||||
INSERT INTO `question` (`number`, `content`, `type`, `is_required`, `is_last`)
|
||||
VALUES (23, '您认为大模型在哪些战略层面的决策中可以发挥作用?', 'MULTIPLE_CHOICE', TRUE, FALSE);
|
||||
|
||||
INSERT INTO `option` (`question_id`, `code`, `content`, `requires_text`) VALUES
|
||||
(LAST_INSERT_ID(), 'A', '市场趋势预测与分析', FALSE),
|
||||
(LAST_INSERT_ID(), 'B', '组织结构优化与调整', FALSE),
|
||||
(LAST_INSERT_ID(), 'C', '业务流程优化与重组', FALSE),
|
||||
(LAST_INSERT_ID(), 'D', '其他', TRUE);
|
||||
|
||||
INSERT INTO `question` (`number`, `content`, `type`, `is_required`, `is_last`)
|
||||
VALUES (24, '在公司管理工作中,您最希望大模型协助哪些任务?', 'MULTIPLE_CHOICE', TRUE, FALSE);
|
||||
|
||||
INSERT INTO `option` (`question_id`, `code`, `content`, `requires_text`) VALUES
|
||||
(LAST_INSERT_ID(), 'A', '数据分析与报告自动生成', FALSE),
|
||||
(LAST_INSERT_ID(), 'B', '战略规划与方案优化', FALSE),
|
||||
(LAST_INSERT_ID(), 'C', '业务协同与跨部门信息流通', FALSE),
|
||||
(LAST_INSERT_ID(), 'D', '其他', TRUE);
|
||||
|
||||
-- 开放建议问题
|
||||
INSERT INTO `question` (`number`, `content`, `type`, `is_required`, `is_last`)
|
||||
VALUES (25, '您对大模型培训的具体期待:', 'TEXT', FALSE, FALSE);
|
||||
|
||||
INSERT INTO `question` (`number`, `content`, `type`, `is_required`, `is_last`)
|
||||
VALUES (26, '您认为公司引入AI需提前防范的风险:', 'TEXT', FALSE, TRUE);
|
||||
|
||||
-- 设置问题跳转逻辑
|
||||
UPDATE `question`
|
||||
SET `next` = JSON_OBJECT(
|
||||
'A', 5, -- 研发部门跳转到问题5
|
||||
'B', 7, -- 项目管理跳转到问题7
|
||||
'C', 9, -- 保险部门跳转到问题9
|
||||
'D', 11, -- 财务部门跳转到问题11
|
||||
'E', 13, -- 客服部门跳转到问题13
|
||||
'F', 15, -- 运营部门跳转到问题15
|
||||
'G', 17, -- 市场拓展跳转到问题17
|
||||
'H', 19, -- 人力资源跳转到问题19
|
||||
'I', 21, -- 综合管理跳转到问题21
|
||||
'J', 23 -- 公司高管跳转到问题23
|
||||
)
|
||||
WHERE `number` = 4;
|
||||
|
||||
-- 设置每个领域最后一题的跳转逻辑
|
||||
-- 研发部门
|
||||
UPDATE `question` SET `next` = JSON_OBJECT('*', 25) WHERE `number` = 6;
|
||||
|
||||
-- 项目管理
|
||||
UPDATE `question` SET `next` = JSON_OBJECT('*', 25) WHERE `number` = 8;
|
||||
|
||||
-- 保险部门
|
||||
UPDATE `question` SET `next` = JSON_OBJECT('*', 25) WHERE `number` = 10;
|
||||
|
||||
-- 财务部门
|
||||
UPDATE `question` SET `next` = JSON_OBJECT('*', 25) WHERE `number` = 12;
|
||||
|
||||
-- 客服部门
|
||||
UPDATE `question` SET `next` = JSON_OBJECT('*', 25) WHERE `number` = 14;
|
||||
|
||||
-- 运营部门
|
||||
UPDATE `question` SET `next` = JSON_OBJECT('*', 25) WHERE `number` = 16;
|
||||
|
||||
-- 市场拓展
|
||||
UPDATE `question` SET `next` = JSON_OBJECT('*', 25) WHERE `number` = 18;
|
||||
|
||||
-- 人力资源
|
||||
UPDATE `question` SET `next` = JSON_OBJECT('*', 25) WHERE `number` = 20;
|
||||
|
||||
-- 综合管理
|
||||
UPDATE `question` SET `next` = JSON_OBJECT('*', 25) WHERE `number` = 22;
|
||||
|
||||
-- 公司高管
|
||||
UPDATE `question` SET `next` = JSON_OBJECT('*', 25) WHERE `number` = 24;
|
||||
|
||||
-- 第一个开放性问题跳转到第二个
|
||||
UPDATE `question` SET `next` = JSON_OBJECT('*', 26) WHERE `number` = 25;
|
||||
|
||||
-- 设置最后一题标记
|
||||
UPDATE `question` SET `is_last` = TRUE WHERE `number` = 26;
|
||||
44
deploy-local.sh
Executable file
44
deploy-local.sh
Executable file
@ -0,0 +1,44 @@
|
||||
#!/bin/bash
|
||||
|
||||
# 获取脚本所在目录的绝对路径
|
||||
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
||||
|
||||
# 设置Docker数据目录
|
||||
DOCKER_DATA_DIR="/Volumes/working/docker"
|
||||
TOMCAT_WEBAPPS_DIR="$DOCKER_DATA_DIR/tomcat/webapps"
|
||||
TOMCAT_LOGS_DIR="$DOCKER_DATA_DIR/tomcat/logs/llm-survey-api"
|
||||
|
||||
echo "=== 开始部署 ==="
|
||||
|
||||
# 1. 进入后端项目目录
|
||||
cd "$SCRIPT_DIR/backend" || exit 1
|
||||
echo "✓ 已切换到后端项目目录"
|
||||
|
||||
# 2. 清理并打包项目
|
||||
echo "正在打包后端项目..."
|
||||
mvn clean package -DskipTests
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "✗ Maven打包失败"
|
||||
exit 1
|
||||
fi
|
||||
echo "✓ Maven打包成功"
|
||||
|
||||
# 3. 复制WAR包到Tomcat的webapps目录
|
||||
echo "正在复制WAR包到Tomcat..."
|
||||
cp target/llm-survey-api.war "$TOMCAT_WEBAPPS_DIR/"
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "✗ WAR包复制失败"
|
||||
exit 1
|
||||
fi
|
||||
echo "✓ WAR包复制成功"
|
||||
|
||||
# 4. 等待部署完成
|
||||
echo "等待应用部署完成..."
|
||||
sleep 5
|
||||
|
||||
# 5. 检查部署日志
|
||||
echo "检查业务日志..."
|
||||
tail "$TOMCAT_LOGS_DIR/app.log"
|
||||
|
||||
echo "=== 部署完成 ==="
|
||||
echo "请访问 http://localhost:18080/llm-survey-api 验证部署结果"
|
||||
74
doc/deployment.md
Normal file
74
doc/deployment.md
Normal file
@ -0,0 +1,74 @@
|
||||
# 部署文档
|
||||
|
||||
## 1. 开发环境配置
|
||||
|
||||
### 1.1 环境变量
|
||||
- `DOCKER_DATA_DIR`: Docker数据目录,默认为 `/Volumes/working/docker`
|
||||
|
||||
### 1.2 开发环境组件
|
||||
- Tomcat: 运行在Docker容器中
|
||||
- 数据目录: `$DOCKER_DATA_DIR/tomcat`
|
||||
- 日志目录: `$DOCKER_DATA_DIR/tomcat/logs`
|
||||
- 应用目录: `$DOCKER_DATA_DIR/tomcat/webapps`
|
||||
|
||||
## 2. 项目构建
|
||||
|
||||
### 2.1 编译打包
|
||||
```bash
|
||||
# 进入后端项目目录
|
||||
cd backend
|
||||
|
||||
# 清理并打包项目(跳过测试)
|
||||
mvn clean package -DskipTests
|
||||
|
||||
# 打包结果
|
||||
# - WAR包位置:backend/target/llm-survey-api.war
|
||||
```
|
||||
|
||||
### 2.2 数据库初始化
|
||||
```bash
|
||||
# 进入数据库脚本目录
|
||||
cd database
|
||||
|
||||
# 添加执行权限
|
||||
chmod +x init_database.sh
|
||||
|
||||
# 执行初始化脚本
|
||||
./init_database.sh
|
||||
|
||||
# 初始化内容
|
||||
# - 创建数据库:llm_survey
|
||||
# - 创建表:users, questions, options, survey_responses
|
||||
# - 插入基础数据:工作领域和岗位性质相关的问题和选项
|
||||
```
|
||||
|
||||
## 3. 开发环境部署
|
||||
|
||||
### 3.1 部署WAR包
|
||||
```bash
|
||||
# 复制WAR包到Tomcat的webapps目录
|
||||
cp backend/target/llm-survey-api.war $DOCKER_DATA_DIR/tomcat/webapps/
|
||||
|
||||
# 部署后的访问地址
|
||||
# - 上下文路径:/llm-survey-api
|
||||
# - API基础路径:/llm-survey-api/api
|
||||
```
|
||||
|
||||
### 3.2 查看部署结果
|
||||
```bash
|
||||
# 查看Tomcat日志
|
||||
tail -f $DOCKER_DATA_DIR/tomcat/logs/catalina.out
|
||||
|
||||
# 检查应用是否成功部署
|
||||
ls -l $DOCKER_DATA_DIR/tomcat/webapps/llm-survey-api/
|
||||
```
|
||||
|
||||
### 3.3 验证部署
|
||||
- 访问测试接口:`http://localhost:8080/llm-survey-api/user/check/13800000000`
|
||||
- 预期返回:`false`(表示手机号未注册)
|
||||
|
||||
## 4. 注意事项
|
||||
1. 确保MySQL服务已启动且能够通过localhost:3306访问
|
||||
2. 确保Tomcat容器已启动且8080端口可访问
|
||||
3. 部署前确保数据库已正确初始化
|
||||
4. 如需重新部署,可直接覆盖webapps目录下的WAR包,Tomcat会自动重新部署
|
||||
@ -2,155 +2,213 @@
|
||||
|
||||
---
|
||||
|
||||
## 第一部分:基本信息(必填)
|
||||
1. 您所属部门:
|
||||
A. 研发部
|
||||
B. 项目部
|
||||
C. 保险部
|
||||
D. 财务部
|
||||
E. 客服部
|
||||
F. 运营部
|
||||
G. 综合管理部
|
||||
## 第一部分:通用认知调研(必答)
|
||||
|
||||
2. 岗位性质:
|
||||
A. 管理岗
|
||||
B. 技术岗
|
||||
C. 业务岗
|
||||
D. 职能支持岗
|
||||
|
||||
---
|
||||
|
||||
## 第二部分:通用认知调研(必答)
|
||||
1. 您对大模型(如ChatGPT、通义千问、DeepSeek)的了解程度:
|
||||
A. 从未接触过
|
||||
B. 仅简单使用过通用功能(如问答)
|
||||
B. 仅在日常简单使用过通用功能(如问答)
|
||||
C. 在工作中尝试过基础应用
|
||||
D. 深度研究过技术原理
|
||||
|
||||
2. 您认为以下哪些业务场景最需要效率提升?(多选)
|
||||
A. 文档撰写/报告生成
|
||||
B. 数据清洗与分析
|
||||
C. 客户沟通与服务
|
||||
D. 风险识别与预警
|
||||
E. 流程自动化
|
||||
F. 其他:____________
|
||||
2. 您觉得大模型可以做到下面哪些事?(多选)
|
||||
A. 精准知识问答
|
||||
B. 文档撰写/报告生成/代码编写/图片视频生成等
|
||||
C. 数据清洗与分析
|
||||
D. 客户沟通与服务
|
||||
E. 风险识别与预警
|
||||
F. 流程自动化
|
||||
G. 如有其他答案,请详细说明:____________
|
||||
|
||||
3. 您最关注大模型应用的哪些风险?(多选)
|
||||
A. 数据隐私泄露
|
||||
B. 生成内容不准确
|
||||
C. 合规审查风险
|
||||
D. 技术使用门槛高
|
||||
E. 其他:____________ (需填写内容)
|
||||
E. 如有其他答案,请详细说明:____________ (需填写内容)
|
||||
|
||||
4. 您的主要工作内容是:
|
||||
A. 研发(产品、开发、算法、测试、运维等)
|
||||
B. 项目管理(项目立项、进度跟踪、风险管理等)
|
||||
C. 保险(产品、核保、理赔、精算等)
|
||||
D. 财务(会计、税务、审计等)
|
||||
E. 客服(咨询、投诉、回访等)
|
||||
F. 运营(新媒体运营、广告宣传、活动策划、数据分析等)
|
||||
G. 市场拓展(渠道拓展、商务沟通、产品推广等)
|
||||
H. 人力资源(招聘、培训、绩效、薪酬等)
|
||||
I. 综合事务(行政、法务等)
|
||||
J. 公司高管(战略规划、组织架构、制度建设等)
|
||||
|
||||
---
|
||||
|
||||
## 第三部分:部门专属问题
|
||||
**(系统将根据第一部分选择的部门自动跳转)**
|
||||
## 第二部分:领域专属问题
|
||||
**(系统将根据第4题选择的工作领域自动跳转)**
|
||||
|
||||
### 研发部
|
||||
4. 您在开发过程中最耗时的重复性工作:
|
||||
A. 保险条款文档编写
|
||||
B. 医疗知识图谱维护
|
||||
C. API接口调试
|
||||
D. 其他:____________
|
||||
### 研发
|
||||
5. 您在开发过程中最耗时的重复性工作:(多选)
|
||||
A. 文档编写(如需求文档、技术文档等)
|
||||
B. 产品原型、界面设计(如使用图片生成模型自动生成等)
|
||||
C. 代码编写
|
||||
D. 调试与测试
|
||||
E. 系统监控与维护
|
||||
F. 如有其他答案,请详细说明:____________
|
||||
|
||||
5. 您希望大模型如何与现有系统集成:
|
||||
A. 自动生成代码片段(如DeepSeek-Coder)
|
||||
B. 智能测试用例生成(如通义千问测试场景模拟)
|
||||
C. 需求文档结构化(如ChatGPT生成PRD框架)
|
||||
D. 其他:____________
|
||||
6. 您希望大模型如何与现有系统集成:(多选)
|
||||
A. 回答技术问题
|
||||
B. 自动生成代码片段(如 Github Copilot等)
|
||||
C. 智能测试用例生成
|
||||
D. 生成需求文档、技术文档
|
||||
E. 完整项目生成(如Cursor等)
|
||||
F. 代码重构与优化
|
||||
G. 辅助设计算法(如DeepSeek等)
|
||||
H. 如有其他答案,请详细说明:____________
|
||||
|
||||
### 项目部
|
||||
6. 惠民保产品设计中最需要数据支持的环节:
|
||||
A. 参保人群画像分析
|
||||
B. 竞品方案快速解析(如通义千问政策解读)
|
||||
C. 定价模型优化
|
||||
D. 其他:____________
|
||||
### 项目管理
|
||||
|
||||
7. 您希望如何用大模型提升方案输出效率:
|
||||
A. 自动生成PPT框架(ChatGPT生成大纲)
|
||||
B. 从政策文件中提取关键条款(DeepSeek语义解析)
|
||||
C. 风险测算报告自动化
|
||||
D. 其他:____________
|
||||
7. 项目管理中最常遇到的挑战是:
|
||||
A. 项目进度跟踪与更新
|
||||
B. 风险评估与管控
|
||||
C. 项目报告生成
|
||||
D. 如有其他答案,请详细说明:____________
|
||||
|
||||
### 保险部
|
||||
8. 理赔处理中的主要效率瓶颈:
|
||||
A. 材料完整性核验
|
||||
B. 医疗票据信息提取(如OCR+通义千问核对)
|
||||
C. 案件风险分级
|
||||
D. 其他:____________
|
||||
8. 您希望如何利用大模型提升项目管理效率:
|
||||
A. 自动生成立项报告、进度报告、总结报告等
|
||||
B. 风险预测与预警
|
||||
C. 项目资料自动化整理
|
||||
D. 知识库管理
|
||||
E. 如有其他答案,请详细说明:____________
|
||||
|
||||
9. 您认为大模型可优化的理赔环节:
|
||||
A. 自动生成理赔告知书(ChatGPT模板生成)
|
||||
B. 异常案件预警提示(DeepSeek数据分析)
|
||||
C. 保险规则智能问答
|
||||
D. 其他:____________
|
||||
### 保险
|
||||
|
||||
### 财务部
|
||||
10. 日常工作中重复性最高的任务:
|
||||
A. 发票信息录入核对
|
||||
B. 报销单据合规审查
|
||||
C. 财务报表数据汇总
|
||||
D. 其他:____________
|
||||
9.理赔处理中的主要瓶颈是:
|
||||
A. 理赔文档处理
|
||||
B. 医疗票据审核与核对
|
||||
C. 客户资料信息录入与处理
|
||||
D. 理赔规则理解与应用
|
||||
E. 如有其他答案,请详细说明:____________
|
||||
|
||||
11. 您希望大模型协助完成的财务工作:
|
||||
A. 自动提取票据关键字段(通义千问OCR识别)
|
||||
B. 生成财务分析摘要(ChatGPT文本总结)
|
||||
C. 异常收支模式检测(DeepSeek风险预测)
|
||||
D. 其他:____________
|
||||
10. 大模型可以优化哪些保险工作环节:
|
||||
A. 新员工入职培训
|
||||
B. 保险产品设计的优化
|
||||
B. 自动生成理赔报告与告知书
|
||||
C. 自动化资料审核(如OCR识别票据数据、自动识别既往症等)
|
||||
D. 异常案件智能预警
|
||||
E. 如有其他答案,请详细说明:____________
|
||||
|
||||
### 客服部
|
||||
12. 客户咨询中最常遇到的重复性问题:
|
||||
A. 理赔进度查询
|
||||
B. 参保资格咨询
|
||||
### 财务
|
||||
|
||||
11. 日常工作中最重复的任务是:
|
||||
A. 财务数据整理与报表生成
|
||||
B. 发票和报销单审核
|
||||
C. 财务审计与合规检查
|
||||
D. 如有其他答案,请详细说明:____________
|
||||
|
||||
12.大模型能如何协助提升财务工作效率:
|
||||
A. 各种报表格式的自动转换
|
||||
B. 自动生成财务报表与分析摘要
|
||||
C. 自动化审计和合规检查
|
||||
D. 财务数据智能分析与预测
|
||||
E. 如有其他答案,请详细说明:____________
|
||||
|
||||
### 客服
|
||||
|
||||
13. 客户咨询中最常遇到的重复性问题:
|
||||
A. 参保资格咨询
|
||||
B. 理赔进度查询
|
||||
C. 材料补交通知
|
||||
D. 其他:____________
|
||||
D. 如有其他答案,请详细说明:____________
|
||||
|
||||
13. 您希望大模型如何辅助客服工作:
|
||||
A. 自动生成个性化回复话术(ChatGPT对话生成)
|
||||
B. 客户情绪实时识别(通义千问情感分析)
|
||||
C. 咨询问题自动分类派单(DeepSeek意图分类)
|
||||
D. 其他:____________
|
||||
14. 您希望大模型如何辅助客服工作:
|
||||
A. 自动生成客户回复模板
|
||||
B. 客户咨询自动分类与转接
|
||||
C. 智能分析客户情绪与需求
|
||||
D. 如有其他答案,请详细说明:____________
|
||||
|
||||
### 运营部
|
||||
14. 运营数据分析中的主要痛点:
|
||||
A. 多平台数据整合
|
||||
B. 参保率预测模型优化(DeepSeek时序预测)
|
||||
C. 宣传文案创意生成(通义千问文案生成)
|
||||
D. 其他:____________
|
||||
### 运营
|
||||
|
||||
15. 您希望大模型赋能的运营场景:
|
||||
A. 自动生成社交媒体图文(ChatGPT+通义万相)
|
||||
B. 用户评论情感分析(DeepSeek语义分析)
|
||||
C. 活动效果模拟预测
|
||||
D. 其他:____________
|
||||
15. 在运营工作中,最需要自动化支持的任务是:
|
||||
A. 热点讯息的获取和跟踪
|
||||
B. 数据分析与报告生成
|
||||
C. 社交媒体内容创作
|
||||
D. 活动效果评估与预测
|
||||
D. 如有其他答案,请详细说明:____________
|
||||
|
||||
### 综合管理部
|
||||
16. 日常工作中最耗时的行政事务:
|
||||
A. 合同条款审查
|
||||
16.大模型可以如何帮助提升运营效率:
|
||||
A. 自动抓取热点讯息
|
||||
B. 自动生成社交媒体内容
|
||||
C. 用户评论分析与舆情监测
|
||||
D. 活动数据自动分析与报告生成
|
||||
E. 如有其他答案,请详细说明:____________
|
||||
|
||||
### 市场拓展
|
||||
|
||||
17. 在市场拓展和商务沟通中,您最常遇到的挑战是:
|
||||
A. 市场分析和竞争对手跟踪
|
||||
B. 渠道拓展计划的自动化和优化
|
||||
C. 商务沟通中的信息处理与反馈跟踪
|
||||
D. 如有其他答案,请详细说明:____________
|
||||
|
||||
18.您希望大模型如何帮助提升市场拓展效率:
|
||||
A. 自动生成市场分析报告与趋势预测
|
||||
B. 根据目标客户数据生成个性化营销策略
|
||||
C. 自动化生成商务沟通邮件和提案文档
|
||||
D. 如有其他答案,请详细说明:____________
|
||||
|
||||
### 人力资源
|
||||
|
||||
19. 人事部门最耗时的日常任务是:
|
||||
A. 招聘简历筛选与面试安排
|
||||
B. 员工培训与学习进度管理
|
||||
C. 绩效评估与报告生成
|
||||
D. 如有其他答案,请详细说明:____________
|
||||
|
||||
20. 您希望大模型如何协助提升人事工作效率:
|
||||
A. 自动筛选招聘简历并推荐候选人
|
||||
B. 自动化培训内容推送与学习路径规划
|
||||
C. 绩效评估与员工反馈的自动化分析
|
||||
D. 如有其他答案,请详细说明:____________
|
||||
|
||||
### 综合管理
|
||||
|
||||
21. 在行政工作中,最耗时的任务是:
|
||||
A. 合同审查与管理
|
||||
B. 会议纪要整理
|
||||
C. 制度文档更新
|
||||
D. 其他:____________
|
||||
C. 文档管理与更新
|
||||
D. 如有其他答案,请详细说明:____________
|
||||
|
||||
17. 您希望大模型协助完成的行政工作:
|
||||
A. 自动生成招投标文件模板(ChatGPT框架生成)
|
||||
B. 合同风险点智能排查(通义千问法律审查)
|
||||
C. 流程说明书自动更新(DeepSeek版本迭代)
|
||||
D. 其他:____________
|
||||
22. 您希望大模型如何协助提升行政工作效率:
|
||||
A. 自动生成合同和协议模板
|
||||
B. 自动化会议纪要整理与分发
|
||||
C. 自动化文档归档与管理
|
||||
D. 如有其他答案,请详细说明:____________
|
||||
|
||||
### 公司高管
|
||||
|
||||
23. 您认为大模型在哪些战略层面的决策中可以发挥作用?
|
||||
A. 市场趋势预测与分析
|
||||
B. 组织结构优化与调整
|
||||
C. 业务流程优化与重组
|
||||
D. 如有其他答案,请详细说明:____________
|
||||
|
||||
24. 在公司管理工作中,您最希望大模型协助哪些任务?
|
||||
A. 数据分析与报告自动生成
|
||||
B. 战略规划与方案优化
|
||||
C. 业务协同与跨部门信息流通
|
||||
D. 如有其他答案,请详细说明:____________
|
||||
|
||||
---
|
||||
|
||||
## 第四部分:开放建议(选填)
|
||||
18. 您对大模型培训的具体期待:
|
||||
25. 您对大模型培训的具体期待:
|
||||
____________________________
|
||||
|
||||
19. 您认为公司引入AI需提前防范的风险:
|
||||
26. 您认为公司引入AI需提前防范的风险:
|
||||
____________________________
|
||||
|
||||
---
|
||||
|
||||
### 问卷说明
|
||||
20. **逻辑跳转**:系统将根据所选部门显示专属问题,总题量约10-12题。
|
||||
21. **工具示例**:
|
||||
|
||||
- **逻辑跳转**:系统将根据第1题的答案显示专属问题,总题量约10-12题。
|
||||
- **工具示例**:
|
||||
- 通用大模型:ChatGPT(OpenAI)、通义千问(阿里云)、DeepSeek(深度求索)
|
||||
22. **提交方式**:匿名填写,预计耗时5-8分钟。
|
||||
- **提交方式**:匿名填写,预计耗时5-8分钟。
|
||||
14
frontend/.eslintrc.js
Normal file
14
frontend/.eslintrc.js
Normal file
@ -0,0 +1,14 @@
|
||||
/* eslint-env node */
|
||||
module.exports = {
|
||||
root: true,
|
||||
extends: ['plugin:vue/vue3-essential', 'eslint:recommended', '@vue/eslint-config-prettier'],
|
||||
parserOptions: {
|
||||
ecmaVersion: 'latest',
|
||||
},
|
||||
rules: {
|
||||
'vue/multi-word-component-names': 'off',
|
||||
'vue/no-v-model-argument': 'off',
|
||||
'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
|
||||
'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
|
||||
},
|
||||
};
|
||||
7
frontend/.prettierrc
Normal file
7
frontend/.prettierrc
Normal file
@ -0,0 +1,7 @@
|
||||
{
|
||||
"semi": true,
|
||||
"singleQuote": true,
|
||||
"tabWidth": 2,
|
||||
"printWidth": 100,
|
||||
"trailingComma": "es5"
|
||||
}
|
||||
BIN
frontend/.yarn/install-state.gz
Normal file
BIN
frontend/.yarn/install-state.gz
Normal file
Binary file not shown.
2
frontend/.yarnrc.yml
Normal file
2
frontend/.yarnrc.yml
Normal file
@ -0,0 +1,2 @@
|
||||
nodeLinker: node-modules
|
||||
enableGlobalCache: true
|
||||
13
frontend/index.html
Normal file
13
frontend/index.html
Normal file
@ -0,0 +1,13 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<link rel="icon" href="/favicon.ico">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, viewport-fit=cover">
|
||||
<title>LLM 问卷调查</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
38
frontend/package.json
Normal file
38
frontend/package.json
Normal file
@ -0,0 +1,38 @@
|
||||
{
|
||||
"name": "llm-survey",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview",
|
||||
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs --fix --ignore-path .gitignore"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"description": "",
|
||||
"dependencies": {
|
||||
"axios": "^1.6.7",
|
||||
"pinia": "^2.1.7",
|
||||
"vant": "^4.8.3",
|
||||
"vue": "^3.4.15",
|
||||
"vue-router": "^4.2.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@rollup/plugin-commonjs": "^28.0.2",
|
||||
"@rollup/plugin-node-resolve": "^16.0.0",
|
||||
"@vitejs/plugin-vue": "^5.0.3",
|
||||
"@vue/eslint-config-prettier": "^9.0.0",
|
||||
"esbuild": "^0.25.0",
|
||||
"eslint": "^8.56.0",
|
||||
"eslint-plugin-vue": "^9.21.1",
|
||||
"less": "^4.2.0",
|
||||
"postcss-px-to-viewport": "^1.1.1",
|
||||
"prettier": "^3.2.5",
|
||||
"rollup": "^4.34.8",
|
||||
"unplugin-vue-components": "^0.26.0",
|
||||
"vite": "^5.0.12"
|
||||
},
|
||||
"packageManager": "yarn@4.6.0"
|
||||
}
|
||||
BIN
frontend/public/logo.png
Normal file
BIN
frontend/public/logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 7.0 KiB |
27
frontend/src/App.vue
Normal file
27
frontend/src/App.vue
Normal file
@ -0,0 +1,27 @@
|
||||
<template>
|
||||
<router-view v-slot="{ Component }">
|
||||
<component :is="Component" />
|
||||
</router-view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
// 根组件逻辑
|
||||
</script>
|
||||
|
||||
<style>
|
||||
/* 全局样式 */
|
||||
html,
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell,
|
||||
'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#app {
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
56
frontend/src/api/survey.js
Normal file
56
frontend/src/api/survey.js
Normal file
@ -0,0 +1,56 @@
|
||||
import request from '@/utils/request';
|
||||
|
||||
// 获取用户的问题列表
|
||||
export function getUserQuestions(userId) {
|
||||
return request({
|
||||
url: `/question/user/${userId}`,
|
||||
method: 'get',
|
||||
});
|
||||
}
|
||||
|
||||
// 获取问题的选项列表
|
||||
export function getQuestionOptions(questionId) {
|
||||
return request({
|
||||
url: `/question/${questionId}/option`,
|
||||
method: 'get',
|
||||
});
|
||||
}
|
||||
|
||||
// 获取下一个问题
|
||||
export function getNextQuestion(userId, selectedOptions, currentQuestionNumber) {
|
||||
console.log('API调用参数:', { userId, selectedOptions, currentQuestionNumber });
|
||||
const params = {
|
||||
userId
|
||||
};
|
||||
|
||||
// 只有在不是第一次请求时才添加这些参数
|
||||
if (selectedOptions) {
|
||||
params.selectedOptions = selectedOptions.join(',');
|
||||
}
|
||||
if (currentQuestionNumber !== undefined) {
|
||||
params.currentQuestionNumber = currentQuestionNumber;
|
||||
}
|
||||
|
||||
return request({
|
||||
url: '/question/next',
|
||||
method: 'get',
|
||||
params
|
||||
});
|
||||
}
|
||||
|
||||
// 提交问卷答案
|
||||
export function submitSurvey(userId, responses) {
|
||||
return request({
|
||||
url: `/survey/submit/${userId}`,
|
||||
method: 'post',
|
||||
data: responses,
|
||||
});
|
||||
}
|
||||
|
||||
// 获取用户的问卷答案
|
||||
export function getUserResponses(userId) {
|
||||
return request({
|
||||
url: `/survey/user/${userId}`,
|
||||
method: 'get',
|
||||
});
|
||||
}
|
||||
35
frontend/src/api/user.js
Normal file
35
frontend/src/api/user.js
Normal file
@ -0,0 +1,35 @@
|
||||
import request from '@/utils/request';
|
||||
|
||||
// 用户注册
|
||||
export function register(data) {
|
||||
return request({
|
||||
url: '/user/register',
|
||||
method: 'post',
|
||||
data,
|
||||
});
|
||||
}
|
||||
|
||||
// 更新用户信息
|
||||
export function updateUser(data) {
|
||||
return request({
|
||||
url: `/user/${data.id}`,
|
||||
method: 'put',
|
||||
data,
|
||||
});
|
||||
}
|
||||
|
||||
// 根据手机号获取用户信息
|
||||
export function getUserInfoByPhone(phone) {
|
||||
return request({
|
||||
url: `/user/phone/${phone}`,
|
||||
method: 'get',
|
||||
});
|
||||
}
|
||||
|
||||
// 根据ID获取用户信息
|
||||
export function getUserInfo(id) {
|
||||
return request({
|
||||
url: `/user/${id}`,
|
||||
method: 'get',
|
||||
});
|
||||
}
|
||||
12
frontend/src/main.js
Normal file
12
frontend/src/main.js
Normal file
@ -0,0 +1,12 @@
|
||||
import { createApp } from 'vue';
|
||||
import { createPinia } from 'pinia';
|
||||
import App from './App.vue';
|
||||
import router from './router';
|
||||
import 'vant/lib/index.css';
|
||||
|
||||
const app = createApp(App);
|
||||
|
||||
app.use(createPinia());
|
||||
app.use(router);
|
||||
|
||||
app.mount('#app');
|
||||
49
frontend/src/router/index.js
Normal file
49
frontend/src/router/index.js
Normal file
@ -0,0 +1,49 @@
|
||||
import { createRouter, createWebHistory } from 'vue-router';
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHistory(import.meta.env.BASE_URL),
|
||||
routes: [
|
||||
{
|
||||
path: '/',
|
||||
name: 'home',
|
||||
component: () => import('@/views/HomeView.vue'),
|
||||
},
|
||||
{
|
||||
path: '/register',
|
||||
name: 'register',
|
||||
component: () => import('@/views/RegisterView.vue'),
|
||||
},
|
||||
{
|
||||
path: '/survey',
|
||||
name: 'survey',
|
||||
component: () => import('@/views/SurveyView.vue'),
|
||||
meta: { requiresAuth: true },
|
||||
},
|
||||
{
|
||||
path: '/completed',
|
||||
name: 'completed',
|
||||
component: () => import('@/views/CompletedView.vue'),
|
||||
meta: { requiresAuth: true },
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
// 路由守卫
|
||||
router.beforeEach(async (to, from, next) => {
|
||||
const userId = localStorage.getItem('userId');
|
||||
|
||||
// 如果是从注册页面来的,允许直接进入需要认证的页面
|
||||
if (from.name === 'register' && to.meta.requiresAuth) {
|
||||
next();
|
||||
return;
|
||||
}
|
||||
|
||||
// 其他情况下检查认证
|
||||
if (to.meta.requiresAuth && !userId) {
|
||||
next({ name: 'register' });
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
});
|
||||
|
||||
export default router;
|
||||
135
frontend/src/stores/survey.js
Normal file
135
frontend/src/stores/survey.js
Normal file
@ -0,0 +1,135 @@
|
||||
import { defineStore } from 'pinia';
|
||||
import { ref } from 'vue';
|
||||
import {
|
||||
getUserQuestions,
|
||||
getQuestionOptions,
|
||||
getNextQuestion,
|
||||
submitSurvey,
|
||||
getUserResponses
|
||||
} from '@/api/survey';
|
||||
import { showToast } from 'vant';
|
||||
|
||||
export const useSurveyStore = defineStore('survey', () => {
|
||||
const currentQuestion = ref(null);
|
||||
const questionOptions = ref([]);
|
||||
const userResponses = ref([]);
|
||||
const currentQuestionNumber = ref(null);
|
||||
const currentOptions = ref([]);
|
||||
const isCompleted = ref(false);
|
||||
|
||||
// 获取用户的问题列表
|
||||
async function fetchUserQuestions(userId) {
|
||||
try {
|
||||
const response = await getUserQuestions(userId);
|
||||
return response;
|
||||
} catch (error) {
|
||||
showToast('获取问题列表失败:' + error.message);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// 获取问题的选项列表
|
||||
async function fetchQuestionOptions(questionId) {
|
||||
try {
|
||||
console.log('获取问题选项,问题ID:', questionId);
|
||||
const options = await getQuestionOptions(questionId);
|
||||
console.log('获取到的选项:', options);
|
||||
currentOptions.value = options;
|
||||
return options;
|
||||
} catch (error) {
|
||||
console.error('获取问题选项失败:', error);
|
||||
showToast('获取问题选项失败:' + error.message);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// 获取下一个问题
|
||||
async function fetchNextQuestion(userId, selectedOptions, targetQuestionNumber) {
|
||||
try {
|
||||
console.log('获取问题,参数:', { userId, selectedOptions, targetQuestionNumber, currentQuestionNumber: currentQuestionNumber.value });
|
||||
|
||||
let question;
|
||||
if (targetQuestionNumber) {
|
||||
// 如果指定了目标问题号,直接获取该问题
|
||||
const questions = await getUserQuestions(userId);
|
||||
question = questions.find(q => q.number === targetQuestionNumber);
|
||||
} else {
|
||||
// 否则获取下一题
|
||||
question = await getNextQuestion(userId, selectedOptions, currentQuestionNumber.value);
|
||||
}
|
||||
|
||||
console.log('获取到的问题:', question);
|
||||
|
||||
if (question) {
|
||||
currentQuestion.value = question;
|
||||
currentQuestionNumber.value = question.number;
|
||||
|
||||
// 获取问题的选项
|
||||
console.log('开始获取问题选项');
|
||||
const options = await getQuestionOptions(question.id);
|
||||
console.log('获取到的选项:', options);
|
||||
currentOptions.value = options;
|
||||
} else {
|
||||
console.log('没有更多问题了');
|
||||
isCompleted.value = true;
|
||||
}
|
||||
|
||||
return question;
|
||||
} catch (error) {
|
||||
console.error('获取问题失败:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// 提交问卷答案
|
||||
async function submitSurveyResponses(userId, responses) {
|
||||
try {
|
||||
const response = await submitSurvey(userId, responses);
|
||||
userResponses.value = responses;
|
||||
return response;
|
||||
} catch (error) {
|
||||
showToast('提交问卷失败:' + error.message);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// 获取用户的问卷答案
|
||||
async function fetchUserResponses(userId) {
|
||||
try {
|
||||
const response = await getUserResponses(userId);
|
||||
userResponses.value = response;
|
||||
return response;
|
||||
} catch (error) {
|
||||
showToast('获取问卷答案失败:' + error.message);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// 重置问卷状态
|
||||
function resetSurvey() {
|
||||
currentQuestion.value = null;
|
||||
questionOptions.value = [];
|
||||
userResponses.value = [];
|
||||
currentQuestionNumber.value = null;
|
||||
currentOptions.value = [];
|
||||
isCompleted.value = false;
|
||||
// 清除本地存储中的问卷相关数据
|
||||
localStorage.removeItem('surveyProgress');
|
||||
localStorage.removeItem('surveyResponses');
|
||||
}
|
||||
|
||||
return {
|
||||
currentQuestion,
|
||||
questionOptions,
|
||||
userResponses,
|
||||
currentQuestionNumber,
|
||||
currentOptions,
|
||||
isCompleted,
|
||||
fetchUserQuestions,
|
||||
fetchQuestionOptions,
|
||||
fetchNextQuestion,
|
||||
submitSurveyResponses,
|
||||
fetchUserResponses,
|
||||
resetSurvey,
|
||||
};
|
||||
});
|
||||
109
frontend/src/stores/user.js
Normal file
109
frontend/src/stores/user.js
Normal file
@ -0,0 +1,109 @@
|
||||
import { defineStore } from 'pinia';
|
||||
import { ref } from 'vue';
|
||||
import { register, getUserInfo, getUserInfoByPhone, updateUser } from '@/api/user';
|
||||
import { showToast } from 'vant';
|
||||
import { useSurveyStore } from './survey';
|
||||
|
||||
export const useUserStore = defineStore('user', () => {
|
||||
const userId = ref(localStorage.getItem('userId') || '');
|
||||
const userInfo = ref(null);
|
||||
|
||||
// 用户登录/注册
|
||||
async function loginOrRegister(data) {
|
||||
try {
|
||||
console.log('发送登录/注册请求,数据:', data);
|
||||
const response = await getUserInfoByPhone(data.phone);
|
||||
|
||||
let userData;
|
||||
if (response) {
|
||||
// 用户存在,更新信息
|
||||
console.log('用户已存在,更新信息');
|
||||
userData = await updateUser({
|
||||
...response,
|
||||
name: data.name,
|
||||
positionType: data.positionType
|
||||
});
|
||||
} else {
|
||||
// 用户不存在,注册新用户
|
||||
console.log('用户不存在,创建新用户');
|
||||
userData = await register(data);
|
||||
}
|
||||
|
||||
console.log('用户数据:', userData);
|
||||
|
||||
if (!userData || !userData.id) {
|
||||
throw new Error('登录失败:无效的用户数据');
|
||||
}
|
||||
|
||||
// 保存用户信息
|
||||
userId.value = String(userData.id);
|
||||
userInfo.value = userData;
|
||||
localStorage.setItem('userId', userId.value);
|
||||
|
||||
console.log('用户信息已保存,userId:', userId.value);
|
||||
return userData;
|
||||
} catch (error) {
|
||||
console.error('登录/注册请求失败:', error);
|
||||
if (error.response) {
|
||||
console.error('错误响应:', error.response);
|
||||
}
|
||||
const message = error.response?.data?.message || error.message || '登录失败,请稍后重试';
|
||||
showToast(message);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// 获取用户信息
|
||||
async function fetchUserInfo(id) {
|
||||
try {
|
||||
console.log('获取用户信息:', id);
|
||||
const response = await getUserInfo(id);
|
||||
console.log('获取用户信息响应:', response);
|
||||
const userData = response.data || response;
|
||||
userInfo.value = userData;
|
||||
return userData;
|
||||
} catch (error) {
|
||||
console.error('获取用户信息失败:', error);
|
||||
if (error.response) {
|
||||
console.error('错误响应:', error.response);
|
||||
}
|
||||
const message = error.response?.data?.message || error.message || '获取用户信息失败,请稍后重试';
|
||||
showToast(message);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// 根据手机号获取用户信息
|
||||
async function getUserInfoByPhoneNumber(phone) {
|
||||
try {
|
||||
console.log('根据手机号获取用户信息:', phone);
|
||||
const response = await getUserInfoByPhone(phone);
|
||||
console.log('获取用户信息响应:', response);
|
||||
return response;
|
||||
} catch (error) {
|
||||
console.error('获取用户信息失败:', error);
|
||||
if (error.response) {
|
||||
console.error('错误响应:', error.response);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// 退出登录
|
||||
function logout() {
|
||||
const surveyStore = useSurveyStore();
|
||||
userId.value = null;
|
||||
userInfo.value = null;
|
||||
localStorage.removeItem('userId');
|
||||
surveyStore.resetSurvey();
|
||||
}
|
||||
|
||||
return {
|
||||
userId,
|
||||
userInfo,
|
||||
loginOrRegister,
|
||||
fetchUserInfo,
|
||||
getUserInfoByPhone: getUserInfoByPhoneNumber,
|
||||
logout,
|
||||
};
|
||||
});
|
||||
57
frontend/src/utils/request.js
Normal file
57
frontend/src/utils/request.js
Normal file
@ -0,0 +1,57 @@
|
||||
import axios from 'axios';
|
||||
import { showToast } from 'vant';
|
||||
|
||||
const request = axios.create({
|
||||
baseURL: '/api',
|
||||
timeout: 10000,
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
|
||||
// 请求拦截器
|
||||
request.interceptors.request.use(
|
||||
(config) => {
|
||||
console.log('发送请求:', {
|
||||
url: config.url,
|
||||
method: config.method,
|
||||
data: config.data,
|
||||
headers: config.headers
|
||||
});
|
||||
return config;
|
||||
},
|
||||
(error) => {
|
||||
console.error('请求错误:', error);
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
|
||||
// 响应拦截器
|
||||
request.interceptors.response.use(
|
||||
(response) => {
|
||||
console.log('收到响应:', {
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
data: response.data,
|
||||
headers: response.headers
|
||||
});
|
||||
return response.data;
|
||||
},
|
||||
(error) => {
|
||||
console.error('响应错误:', {
|
||||
message: error.message,
|
||||
config: error.config,
|
||||
response: error.response ? {
|
||||
status: error.response.status,
|
||||
statusText: error.response.statusText,
|
||||
data: error.response.data,
|
||||
headers: error.response.headers
|
||||
} : null
|
||||
});
|
||||
const message = error.response?.data?.message || '请求失败,请稍后重试';
|
||||
showToast(message);
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
|
||||
export default request;
|
||||
88
frontend/src/views/CompletedView.vue
Normal file
88
frontend/src/views/CompletedView.vue
Normal file
@ -0,0 +1,88 @@
|
||||
<template>
|
||||
<div class="completed-view">
|
||||
<van-nav-bar title="问卷完成" />
|
||||
|
||||
<div class="content">
|
||||
<van-empty description="感谢您完成问卷调查!">
|
||||
<template #image>
|
||||
<van-icon name="success" size="64" color="#ffffff" />
|
||||
</template>
|
||||
<div class="completion-message">
|
||||
<p>您的反馈对我们非常重要</p>
|
||||
<p>我们将根据调查结果持续改进和优化</p>
|
||||
</div>
|
||||
</van-empty>
|
||||
<van-button
|
||||
round
|
||||
type="primary"
|
||||
class="home-button"
|
||||
@click="router.replace('/')"
|
||||
>
|
||||
返回首页
|
||||
</van-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { useRouter } from 'vue-router';
|
||||
|
||||
const router = useRouter();
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.completed-view {
|
||||
min-height: 100vh;
|
||||
background-color: #f7f8fa;
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 40px 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.completion-message {
|
||||
margin: 24px 0;
|
||||
color: #666;
|
||||
line-height: 1.8;
|
||||
}
|
||||
|
||||
.completion-message p:first-child {
|
||||
font-size: 20px;
|
||||
font-weight: bold;
|
||||
color: #323233;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.completion-message p {
|
||||
margin: 8px 0;
|
||||
}
|
||||
|
||||
.home-button {
|
||||
margin-top: 32px;
|
||||
width: 80%;
|
||||
}
|
||||
|
||||
/* 自定义图标样式 */
|
||||
:deep(.van-empty__image) {
|
||||
width: 120px !important;
|
||||
height: 120px !important;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
:deep(.van-icon) {
|
||||
background-color: #07c160;
|
||||
border-radius: 50%;
|
||||
padding: 16px;
|
||||
box-shadow: 0 4px 12px rgba(7, 193, 96, 0.3);
|
||||
}
|
||||
|
||||
:deep(.van-empty__description) {
|
||||
color: #323233;
|
||||
font-size: 22px;
|
||||
font-weight: bold;
|
||||
margin-bottom: 8px;
|
||||
white-space: nowrap;
|
||||
padding: 0 20px;
|
||||
}
|
||||
</style>
|
||||
121
frontend/src/views/HomeView.vue
Normal file
121
frontend/src/views/HomeView.vue
Normal file
@ -0,0 +1,121 @@
|
||||
<template>
|
||||
<div class="home-view">
|
||||
<van-nav-bar title="LLM 问卷调查" />
|
||||
|
||||
<div class="content">
|
||||
<van-image
|
||||
class="logo"
|
||||
width="200"
|
||||
height="200"
|
||||
src="/logo.png"
|
||||
fit="contain"
|
||||
/>
|
||||
|
||||
<h1 class="title">欢迎参与 LLM 问卷调查</h1>
|
||||
<p class="description">
|
||||
本问卷旨在了解智慧医疗员工对大语言模型的使用体验和看法。您的反馈对我们非常重要。
|
||||
</p>
|
||||
<p class="description">
|
||||
此应用使用Cursor生成。所有代码,包括数据库脚本、前后端代码、配置文件等均通过提示词指挥AI生成;git提交、测试、代码打包发布等操作也是通过提示词指挥AI完成。
|
||||
</p>
|
||||
<p class="description">
|
||||
此项目开发到上线共计耗时1人5小时,传统开发方式需要产品、前端、后端、测试4人1~2天工作量。
|
||||
</p>
|
||||
|
||||
<div class="action-buttons">
|
||||
<van-button
|
||||
v-if="!userStore.userId"
|
||||
round
|
||||
block
|
||||
type="primary"
|
||||
@click="router.push('/register')"
|
||||
>
|
||||
开始参与
|
||||
</van-button>
|
||||
|
||||
<template v-else>
|
||||
<van-button
|
||||
round
|
||||
block
|
||||
type="primary"
|
||||
@click="surveyStore.isCompleted ? startNewSurvey() : router.push('/survey')"
|
||||
>
|
||||
{{ surveyStore.isCompleted ? '重新答题' : '继续答题' }}
|
||||
</van-button>
|
||||
|
||||
<van-button
|
||||
round
|
||||
block
|
||||
type="default"
|
||||
@click="onLogout"
|
||||
class="logout-btn"
|
||||
>
|
||||
退出登录
|
||||
</van-button>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { useRouter } from 'vue-router';
|
||||
import { useUserStore } from '@/stores/user';
|
||||
import { useSurveyStore } from '@/stores/survey';
|
||||
import { showToast } from 'vant';
|
||||
|
||||
const router = useRouter();
|
||||
const userStore = useUserStore();
|
||||
const surveyStore = useSurveyStore();
|
||||
|
||||
function onLogout() {
|
||||
userStore.logout();
|
||||
showToast('已退出登录');
|
||||
}
|
||||
|
||||
// 开始新的问卷
|
||||
function startNewSurvey() {
|
||||
surveyStore.resetSurvey();
|
||||
router.push('/survey');
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.home-view {
|
||||
min-height: 100vh;
|
||||
background-color: #f7f8fa;
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 40px 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.logo {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
color: #323233;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.description {
|
||||
font-size: 16px;
|
||||
color: #666;
|
||||
line-height: 1.5;
|
||||
margin-bottom: 20px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.action-buttons {
|
||||
max-width: 300px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.logout-btn {
|
||||
margin-top: 16px;
|
||||
}
|
||||
</style>
|
||||
163
frontend/src/views/RegisterView.vue
Normal file
163
frontend/src/views/RegisterView.vue
Normal file
@ -0,0 +1,163 @@
|
||||
<template>
|
||||
<div class="register-view">
|
||||
<van-nav-bar title="用户登录" />
|
||||
|
||||
<van-form @submit="onSubmit" class="register-form">
|
||||
<van-cell-group inset>
|
||||
<van-field
|
||||
v-model="formData.phone"
|
||||
name="phone"
|
||||
label="手机号"
|
||||
placeholder="请输入手机号"
|
||||
:rules="[
|
||||
{ required: true, message: '请填写手机号' },
|
||||
{ pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号' }
|
||||
]"
|
||||
@blur="onPhoneBlur"
|
||||
/>
|
||||
|
||||
<van-field
|
||||
v-model="formData.name"
|
||||
name="name"
|
||||
label="姓名"
|
||||
placeholder="请输入姓名"
|
||||
:rules="[{ required: true, message: '请填写姓名' }]"
|
||||
/>
|
||||
|
||||
<van-field
|
||||
v-model="displayPositionType"
|
||||
name="positionType"
|
||||
label="岗位性质"
|
||||
readonly
|
||||
clickable
|
||||
placeholder="请选择岗位性质"
|
||||
:rules="[{ required: true, message: '请选择岗位性质' }]"
|
||||
@click="showPositionTypePicker = true"
|
||||
/>
|
||||
<!-- eslint-disable-next-line vue/no-v-model-argument -->
|
||||
<van-popup v-model:show="showPositionTypePicker" position="bottom">
|
||||
<van-picker
|
||||
:columns="positionTypeOptions"
|
||||
@confirm="onPositionTypeConfirm"
|
||||
@cancel="showPositionTypePicker = false"
|
||||
show-toolbar
|
||||
title="选择岗位性质"
|
||||
/>
|
||||
</van-popup>
|
||||
</van-cell-group>
|
||||
|
||||
<div class="submit-btn">
|
||||
<van-button round block type="primary" native-type="submit">
|
||||
登录
|
||||
</van-button>
|
||||
<p class="tip-text">首次登录的用户将自动完成注册</p>
|
||||
</div>
|
||||
</van-form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { useUserStore } from '@/stores/user';
|
||||
import { showToast, showNotify } from 'vant';
|
||||
|
||||
const router = useRouter();
|
||||
const userStore = useUserStore();
|
||||
|
||||
const formData = reactive({
|
||||
phone: '',
|
||||
name: '',
|
||||
positionType: '',
|
||||
});
|
||||
|
||||
const showPositionTypePicker = ref(false);
|
||||
const displayPositionType = ref('');
|
||||
|
||||
const positionTypeOptions = [
|
||||
{ text: '管理岗', value: 'MANAGEMENT' },
|
||||
{ text: '技术岗', value: 'TECHNICAL' },
|
||||
{ text: '业务岗', value: 'BUSINESS' },
|
||||
{ text: '职能支持岗', value: 'SUPPORT' }
|
||||
];
|
||||
|
||||
// 手机号失去焦点时检查用户信息
|
||||
async function onPhoneBlur() {
|
||||
if (formData.phone && /^1[3-9]\d{9}$/.test(formData.phone)) {
|
||||
try {
|
||||
const userInfo = await userStore.getUserInfoByPhone(formData.phone);
|
||||
if (userInfo) {
|
||||
// 如果用户存在,自动填充信息
|
||||
formData.name = userInfo.name;
|
||||
formData.positionType = userInfo.positionType;
|
||||
displayPositionType.value = positionTypeOptions.find(
|
||||
opt => opt.value === userInfo.positionType
|
||||
)?.text || '';
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取用户信息失败:', error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 岗位性质选择确认
|
||||
function onPositionTypeConfirm({ selectedOptions }) {
|
||||
formData.positionType = selectedOptions[0].value;
|
||||
displayPositionType.value = selectedOptions[0].text;
|
||||
showPositionTypePicker.value = false;
|
||||
}
|
||||
|
||||
// 提交表单
|
||||
async function onSubmit() {
|
||||
try {
|
||||
console.log('开始注册,表单数据:', formData);
|
||||
const response = await userStore.loginOrRegister(formData);
|
||||
console.log('注册成功,响应数据:', response);
|
||||
|
||||
// 使用 showNotify 显示成功提示,不会干扰用户操作
|
||||
showNotify({
|
||||
type: 'success',
|
||||
message: '注册成功',
|
||||
duration: 2000,
|
||||
position: 'top'
|
||||
});
|
||||
|
||||
// 等待提示显示后再跳转
|
||||
setTimeout(() => {
|
||||
console.log('准备跳转到问卷页面');
|
||||
router.push({
|
||||
name: 'survey',
|
||||
replace: true
|
||||
}).catch(err => {
|
||||
console.error('路由跳转失败:', err);
|
||||
showToast('页面跳转失败,请刷新重试');
|
||||
});
|
||||
}, 500);
|
||||
} catch (error) {
|
||||
console.error('注册失败:', error);
|
||||
showToast(error.message || '注册失败,请重试');
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.register-view {
|
||||
min-height: 100vh;
|
||||
background-color: #f7f8fa;
|
||||
}
|
||||
|
||||
.register-form {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.submit-btn {
|
||||
margin: 16px;
|
||||
}
|
||||
|
||||
.tip-text {
|
||||
font-size: 14px;
|
||||
color: #999;
|
||||
text-align: center;
|
||||
margin-top: 12px;
|
||||
}
|
||||
</style>
|
||||
346
frontend/src/views/SurveyView.vue
Normal file
346
frontend/src/views/SurveyView.vue
Normal file
@ -0,0 +1,346 @@
|
||||
<template>
|
||||
<div class="survey-view">
|
||||
<van-nav-bar
|
||||
title="问卷调查"
|
||||
left-text="返回"
|
||||
left-arrow
|
||||
@click-left="onBack"
|
||||
/>
|
||||
|
||||
<div class="survey-content">
|
||||
<div v-if="surveyStore.currentQuestion" class="question-container">
|
||||
<h2 class="question-title">
|
||||
{{ surveyStore.currentQuestionNumber }}. {{ surveyStore.currentQuestion.content }}
|
||||
<span v-if="surveyStore.currentQuestion.type === 'MULTIPLE_CHOICE'" class="question-type">(多选)</span>
|
||||
</h2>
|
||||
<van-checkbox-group v-if="surveyStore.currentQuestion.type === 'MULTIPLE_CHOICE'" v-model="selectedOptions" class="options-container">
|
||||
<van-cell-group inset>
|
||||
<van-cell
|
||||
v-for="option in surveyStore.currentOptions"
|
||||
:key="option.code"
|
||||
clickable
|
||||
@click="toggleOption(option.code)"
|
||||
>
|
||||
<template #title>
|
||||
<span>{{ option.code }}. {{ option.content }}</span>
|
||||
<template v-if="option.requiresText && selectedOptions.includes(option.code)">
|
||||
<van-field
|
||||
v-model="textAnswer"
|
||||
type="text"
|
||||
placeholder="请输入具体内容"
|
||||
class="option-text-input"
|
||||
@click.stop
|
||||
/>
|
||||
</template>
|
||||
</template>
|
||||
<template #right-icon>
|
||||
<van-checkbox
|
||||
:name="option.code"
|
||||
@click.stop
|
||||
/>
|
||||
</template>
|
||||
</van-cell>
|
||||
</van-cell-group>
|
||||
</van-checkbox-group>
|
||||
<van-radio-group v-else-if="surveyStore.currentQuestion.type === 'SINGLE_CHOICE'" v-model="selectedOption" class="options-container">
|
||||
<van-cell-group inset>
|
||||
<van-cell
|
||||
v-for="option in surveyStore.currentOptions"
|
||||
:key="option.code"
|
||||
clickable
|
||||
@click="selectedOption = option.code"
|
||||
>
|
||||
<template #title>
|
||||
<span>{{ option.code }}. {{ option.content }}</span>
|
||||
<template v-if="option.requiresText && selectedOption === option.code">
|
||||
<van-field
|
||||
v-model="textAnswer"
|
||||
type="text"
|
||||
placeholder="请输入具体内容"
|
||||
class="option-text-input"
|
||||
@click.stop
|
||||
/>
|
||||
</template>
|
||||
</template>
|
||||
<template #right-icon>
|
||||
<van-radio :name="option.code" />
|
||||
</template>
|
||||
</van-cell>
|
||||
</van-cell-group>
|
||||
</van-radio-group>
|
||||
<div v-else-if="surveyStore.currentQuestion.type === 'TEXT'" class="text-input-container">
|
||||
<van-field
|
||||
v-model="textAnswer"
|
||||
type="textarea"
|
||||
rows="4"
|
||||
autosize
|
||||
placeholder="请输入您的答案"
|
||||
/>
|
||||
</div>
|
||||
<div class="button-container">
|
||||
<van-button
|
||||
type="primary"
|
||||
block
|
||||
:disabled="!isValidSelection"
|
||||
@click="onNextQuestion"
|
||||
>
|
||||
{{ surveyStore.currentQuestion?.isLast ? '完成' : '下一题' }}
|
||||
</van-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<van-empty v-else description="加载中..." />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted, computed } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { useUserStore } from '@/stores/user';
|
||||
import { useSurveyStore } from '@/stores/survey';
|
||||
import { showToast } from 'vant';
|
||||
|
||||
const router = useRouter();
|
||||
const userStore = useUserStore();
|
||||
const surveyStore = useSurveyStore();
|
||||
|
||||
const selectedOption = ref('');
|
||||
const selectedOptions = ref([]);
|
||||
const textAnswer = ref('');
|
||||
const responses = ref([]);
|
||||
|
||||
// 计算当前选择是否有效
|
||||
const isValidSelection = computed(() => {
|
||||
const questionType = surveyStore.currentQuestion?.type;
|
||||
if (!questionType) return false;
|
||||
|
||||
if (questionType === 'MULTIPLE_CHOICE') {
|
||||
// 检查是否有选中的选项需要填写文本
|
||||
const needsText = selectedOptions.value.some(code => {
|
||||
const option = surveyStore.currentOptions.find(opt => opt.code === code);
|
||||
return option?.requiresText;
|
||||
});
|
||||
return selectedOptions.value.length > 0 && (!needsText || (textAnswer.value?.trim() || '').length > 0);
|
||||
} else if (questionType === 'SINGLE_CHOICE') {
|
||||
// 检查单选的选项是否需要文本且已填写
|
||||
const selectedOpt = surveyStore.currentOptions.find(opt => opt.code === selectedOption.value);
|
||||
return selectedOption.value && (!selectedOpt?.requiresText || (textAnswer.value?.trim() || '').length > 0);
|
||||
} else if (questionType === 'TEXT') {
|
||||
return (textAnswer.value?.trim() || '').length > 0;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
// 切换多选选项
|
||||
function toggleOption(code) {
|
||||
const index = selectedOptions.value.indexOf(code);
|
||||
if (index === -1) {
|
||||
selectedOptions.value.push(code);
|
||||
} else {
|
||||
selectedOptions.value.splice(index, 1);
|
||||
}
|
||||
}
|
||||
|
||||
// 返回上一页
|
||||
function onBack() {
|
||||
router.back();
|
||||
}
|
||||
|
||||
// 获取下一个问题
|
||||
async function onNextQuestion() {
|
||||
const questionType = surveyStore.currentQuestion.type;
|
||||
try {
|
||||
let currentSelection = [];
|
||||
|
||||
if (questionType === 'MULTIPLE_CHOICE') {
|
||||
currentSelection = selectedOptions.value;
|
||||
} else if (questionType === 'SINGLE_CHOICE') {
|
||||
currentSelection = [selectedOption.value];
|
||||
}
|
||||
|
||||
// 保存当前答案
|
||||
const currentResponse = {
|
||||
questionId: surveyStore.currentQuestion.id,
|
||||
selectedOptions: currentSelection,
|
||||
textAnswer: textAnswer.value?.trim() || null
|
||||
};
|
||||
|
||||
// 更新或添加当前答案
|
||||
const existingIndex = responses.value.findIndex(r => r.questionId === currentResponse.questionId);
|
||||
if (existingIndex !== -1) {
|
||||
responses.value[existingIndex] = currentResponse;
|
||||
} else {
|
||||
responses.value.push(currentResponse);
|
||||
}
|
||||
|
||||
// 保存答题进度到本地存储
|
||||
localStorage.setItem('surveyProgress', JSON.stringify({
|
||||
currentQuestionNumber: surveyStore.currentQuestionNumber,
|
||||
responses: responses.value
|
||||
}));
|
||||
|
||||
// 如果是最后一题,直接提交答案
|
||||
if (surveyStore.currentQuestion.isLast) {
|
||||
try {
|
||||
await surveyStore.submitSurveyResponses(userStore.userId, responses.value);
|
||||
showToast('问卷提交成功');
|
||||
surveyStore.isCompleted = true;
|
||||
// 清除本地存储的进度
|
||||
localStorage.removeItem('surveyProgress');
|
||||
// 跳转到完成页面
|
||||
router.replace('/completed');
|
||||
return;
|
||||
} catch (error) {
|
||||
console.error('提交问卷失败:', error);
|
||||
showToast(error.response?.data?.message || '提交失败,请重试');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 获取下一题
|
||||
try {
|
||||
await surveyStore.fetchNextQuestion(userStore.userId, currentSelection);
|
||||
// 重置选项
|
||||
selectedOption.value = '';
|
||||
selectedOptions.value = [];
|
||||
textAnswer.value = '';
|
||||
} catch (error) {
|
||||
console.error('获取下一题失败:', error);
|
||||
showToast(error.response?.data?.message || '获取下一题失败,请重试');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('问卷处理失败:', error);
|
||||
showToast(error.response?.data?.message || '操作失败,请重试');
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化问卷
|
||||
async function initSurvey() {
|
||||
try {
|
||||
// 检查是否有保存的进度
|
||||
const savedProgress = localStorage.getItem('surveyProgress');
|
||||
if (savedProgress) {
|
||||
const progress = JSON.parse(savedProgress);
|
||||
responses.value = progress.responses || [];
|
||||
|
||||
// 恢复当前题目的选项状态
|
||||
const lastResponse = responses.value[responses.value.length - 1];
|
||||
if (lastResponse) {
|
||||
if (lastResponse.selectedOptions.length === 1) {
|
||||
selectedOption.value = lastResponse.selectedOptions[0];
|
||||
} else {
|
||||
selectedOptions.value = lastResponse.selectedOptions;
|
||||
}
|
||||
textAnswer.value = lastResponse.textAnswer || '';
|
||||
}
|
||||
|
||||
// 获取当前问题
|
||||
const nextQuestionNumber = progress.currentQuestionNumber + 1;
|
||||
await surveyStore.fetchNextQuestion(
|
||||
userStore.userId,
|
||||
[],
|
||||
nextQuestionNumber
|
||||
);
|
||||
|
||||
// 如果获取失败(可能是最后一题),尝试获取当前题目
|
||||
if (!surveyStore.currentQuestion) {
|
||||
await surveyStore.fetchNextQuestion(
|
||||
userStore.userId,
|
||||
[],
|
||||
progress.currentQuestionNumber
|
||||
);
|
||||
}
|
||||
} else {
|
||||
surveyStore.resetSurvey(); // 确保重置所有状态
|
||||
responses.value = [];
|
||||
// 初始化时不传递任何选项
|
||||
await surveyStore.fetchNextQuestion(userStore.userId);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('初始化问卷失败:', error);
|
||||
showToast('初始化问卷失败,请重试');
|
||||
}
|
||||
}
|
||||
|
||||
// 组件挂载时初始化
|
||||
onMounted(() => {
|
||||
if (!userStore.userId) {
|
||||
router.replace('/register');
|
||||
return;
|
||||
}
|
||||
initSurvey();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.survey-view {
|
||||
min-height: 100vh;
|
||||
background-color: #f7f8fa;
|
||||
}
|
||||
|
||||
.survey-content {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.question-container {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.question-title {
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
line-height: 1.5;
|
||||
margin-bottom: 16px;
|
||||
padding: 0 16px;
|
||||
}
|
||||
|
||||
.options-container {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.button-container {
|
||||
margin-top: 24px;
|
||||
padding: 0 16px;
|
||||
}
|
||||
|
||||
.survey-completed {
|
||||
padding: 40px 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.restart-button {
|
||||
margin-top: 20px;
|
||||
width: 80%;
|
||||
}
|
||||
|
||||
.question-type {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
.text-input-container {
|
||||
padding: 16px;
|
||||
background-color: #fff;
|
||||
border-radius: 8px;
|
||||
margin: 0 16px;
|
||||
}
|
||||
|
||||
.completion-message {
|
||||
margin: 16px 0;
|
||||
color: #666;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.home-button {
|
||||
margin-top: 24px;
|
||||
width: 80%;
|
||||
}
|
||||
|
||||
.option-text-input {
|
||||
margin-top: 8px;
|
||||
background-color: #f8f8f8;
|
||||
border-radius: 4px;
|
||||
}
|
||||
</style>
|
||||
48
frontend/vite.config.js
Normal file
48
frontend/vite.config.js
Normal file
@ -0,0 +1,48 @@
|
||||
import { defineConfig } from 'vite';
|
||||
import vue from '@vitejs/plugin-vue';
|
||||
import Components from 'unplugin-vue-components/vite';
|
||||
import { VantResolver } from 'unplugin-vue-components/resolvers';
|
||||
import { fileURLToPath, URL } from 'node:url';
|
||||
import postcssPxToViewport from 'postcss-px-to-viewport';
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
vue(),
|
||||
// 自动导入 Vant 组件
|
||||
Components({
|
||||
resolvers: [VantResolver()],
|
||||
}),
|
||||
],
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': fileURLToPath(new URL('./src', import.meta.url)),
|
||||
},
|
||||
},
|
||||
css: {
|
||||
postcss: {
|
||||
plugins: [
|
||||
// 移动端适配
|
||||
postcssPxToViewport({
|
||||
viewportWidth: 375, // 设计稿宽度
|
||||
viewportUnit: 'vw',
|
||||
fontViewportUnit: 'vw',
|
||||
selectorBlackList: ['.ignore-'], // 不转换的类名
|
||||
minPixelValue: 1, // 最小转换数值
|
||||
mediaQuery: false, // 是否转换媒体查询中的像素值
|
||||
}),
|
||||
],
|
||||
},
|
||||
},
|
||||
server: {
|
||||
host: '0.0.0.0',
|
||||
port: 3000,
|
||||
proxy: {
|
||||
'/api': {
|
||||
target: 'http://localhost:18080/llm-survey-api',
|
||||
changeOrigin: true,
|
||||
rewrite: (path) => path.replace(/^\/api/, ''),
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
3595
frontend/yarn.lock
Normal file
3595
frontend/yarn.lock
Normal file
File diff suppressed because it is too large
Load Diff
161
pom.xml
161
pom.xml
@ -1,161 +0,0 @@
|
||||
<?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>
|
||||
@ -1,78 +0,0 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
@ -1,62 +0,0 @@
|
||||
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("没有更多问题了"));
|
||||
}
|
||||
}
|
||||
@ -1,58 +0,0 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user