refactor: 重命名 backend-single 目录为 server

This commit is contained in:
2026-06-27 17:23:53 +08:00
parent 8daf51301a
commit 0d77d76858
464 changed files with 0 additions and 0 deletions
@@ -0,0 +1,32 @@
package com.emotion;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* 情感博物馆简化版启动类
*
* @author huazhongmin
* @date 2025-07-21
*/
@SpringBootApplication
@MapperScan("com.emotion.mapper")
public class EmotionSimpleApplication {
public static void main(String[] args) {
System.setProperty("spring.profiles.active",
System.getProperty("spring.profiles.active", "local"));
SpringApplication.run(EmotionSimpleApplication.class, args);
System.out.println("========================================");
System.out.println("🎉 情感博物馆服务启动成功!");
System.out.println("📋 服务信息:");
System.out.println(" - 服务名称: backend-single");
System.out.println(" - 服务端口: 19089");
System.out.println(" - 环境配置: " + System.getProperty("spring.profiles.active"));
System.out.println(" - API文档: http://localhost:19089/api/health");
System.out.println("========================================");
}
}
@@ -0,0 +1,71 @@
package com.emotion.common;
import com.baomidou.mybatisplus.annotation.*;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import lombok.experimental.SuperBuilder;
import lombok.NoArgsConstructor;
import lombok.AllArgsConstructor;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* 基础实体类
*
* @author huazhongmin
* @date 2025-07-22
*/
@Data
@SuperBuilder
@NoArgsConstructor
@AllArgsConstructor
public class BaseEntity implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 主键ID
*/
@TableId(value = "id", type = IdType.ASSIGN_UUID)
private String id;
/**
* 创建人
*/
@TableField(value = "create_by", fill = FieldFill.INSERT)
private String createBy;
/**
* 创建时间
*/
@TableField(value = "create_time", fill = FieldFill.INSERT)
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime createTime;
/**
* 更新人
*/
@TableField(value = "update_by", fill = FieldFill.INSERT_UPDATE)
private String updateBy;
/**
* 更新时间
*/
@TableField(value = "update_time", fill = FieldFill.INSERT_UPDATE)
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime updateTime;
/**
* 是否删除:0-未删除,1-已删除
*/
@TableField("is_deleted")
@TableLogic
private Integer isDeleted;
/**
* 备注
*/
@TableField("remarks")
private String remarks;
}
@@ -0,0 +1,44 @@
package com.emotion.common;
import lombok.Data;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
/**
* 分页查询基类请求参数
*
* @author huazhongmin
* @date 2025-07-23
*/
@Data
public class BasePageRequest {
/**
* 当前页码,从1开始
*/
@Min(value = 1, message = "页码不能小于1")
private Long current = 1L;
/**
* 每页大小
*/
@Min(value = 1, message = "每页大小不能小于1")
@Max(value = 100, message = "每页大小不能超过100")
private Long size = 10L;
/**
* 排序字段
*/
private String orderBy;
/**
* 排序方式:asc-升序,desc-降序
*/
private String orderDirection = "desc";
/**
* 搜索关键词
*/
private String keyword;
}
@@ -0,0 +1,63 @@
package com.emotion.common;
import com.baomidou.mybatisplus.core.metadata.IPage;
import lombok.Data;
import java.util.List;
/**
* 分页结果封装
*
* @author huazhongmin
* @date 2025-07-23
*/
@Data
public class PageResult<T> {
/**
* 当前页码
*/
private Long current;
/**
* 每页大小
*/
private Long size;
/**
* 总记录数
*/
private Long total;
/**
* 总页数
*/
private Long pages;
/**
* 数据列表
*/
private List<T> records;
public PageResult() {}
public PageResult(IPage<T> page) {
this.current = page.getCurrent();
this.size = page.getSize();
this.total = page.getTotal();
this.pages = page.getPages();
this.records = page.getRecords();
}
public static <T> PageResult<T> of(IPage<T> page) {
return new PageResult<>(page);
}
/**
* 设置数据列表
*/
public PageResult<T> setRecords(List<T> records) {
this.records = records;
return this;
}
}
@@ -0,0 +1,139 @@
package com.emotion.common;
import lombok.Data;
import java.io.Serializable;
/**
* 统一返回结果
*
* @author huazhongmin
* @date 2025-07-22
*/
@Data
public class Result<T> implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 状态码
*/
private Integer code;
/**
* 返回消息
*/
private String message;
/**
* 返回数据
*/
private T data;
/**
* 时间戳
*/
private Long timestamp;
public Result() {
this.timestamp = System.currentTimeMillis();
}
public Result(Integer code, String message, T data) {
this.code = code;
this.message = message;
this.data = data;
this.timestamp = System.currentTimeMillis();
}
/**
* 成功返回
*/
public static <T> Result<T> success() {
return new Result<>(200, "操作成功", null);
}
/**
* 成功返回
*/
public static <T> Result<T> success(T data) {
return new Result<>(200, "操作成功", data);
}
/**
* 成功返回
*/
public static <T> Result<T> success(String message, T data) {
return new Result<>(200, message, data);
}
/**
* 失败返回
*/
public static <T> Result<T> error() {
return new Result<>(500, "操作失败", null);
}
/**
* 失败返回
*/
public static <T> Result<T> error(String message) {
return new Result<>(500, message, null);
}
/**
* 失败返回
*/
public static <T> Result<T> error(Integer code, String message) {
return new Result<>(code, message, null);
}
/**
* 未授权
*/
public static <T> Result<T> unauthorized() {
return new Result<>(401, "未授权", null);
}
/**
* 未授权带消息
*/
public static <T> Result<T> unauthorized(String message) {
return new Result<>(401, message, null);
}
/**
* 禁止访问
*/
public static <T> Result<T> forbidden() {
return new Result<>(403, "禁止访问", null);
}
/**
* 禁止访问带消息
*/
public static <T> Result<T> forbidden(String message) {
return new Result<>(403, message, null);
}
/**
* 请求参数错误
*/
public static <T> Result<T> badRequest(String message) {
return new Result<>(400, message, null);
}
/**
* 资源未找到
*/
public static <T> Result<T> notFound() {
return new Result<>(404, "资源未找到", null);
}
/**
* 资源未找到带消息
*/
public static <T> Result<T> notFound(String message) {
return new Result<>(404, message, null);
}
}
@@ -0,0 +1,45 @@
package com.emotion.config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.Executor;
/**
* 多线程异步任务线程池配置
*/
@Configuration
@EnableAsync
public class AsyncConfig {
@Value("${async.core-pool-size:10}")
private int corePoolSize;
@Value("${async.max-pool-size:50}")
private int maxPoolSize;
@Value("${async.queue-capacity:200}")
private int queueCapacity;
@Value("${async.keep-alive-seconds:60}")
private int keepAliveSeconds;
@Value("${async.thread-name-prefix:single-async-}")
private String threadNamePrefix;
@Bean(name = "taskExecutor")
public Executor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(corePoolSize);
executor.setMaxPoolSize(maxPoolSize);
executor.setQueueCapacity(queueCapacity);
executor.setKeepAliveSeconds(keepAliveSeconds);
executor.setThreadNamePrefix(threadNamePrefix);
executor.setRejectedExecutionHandler(new java.util.concurrent.ThreadPoolExecutor.CallerRunsPolicy());
executor.initialize();
return executor;
}
}
@@ -0,0 +1,91 @@
package com.emotion.config;
import com.emotion.util.SnowflakeIdGenerator;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.net.InetAddress;
import java.net.NetworkInterface;
/**
* ID生成器配置类
*
* @author huazhongmin
* @date 2025-07-23
*/
@Slf4j
@Configuration
public class IdGeneratorConfig {
/**
* 机器ID配置(可通过配置文件指定)
*/
@Value("${emotion.snowflake.machine-id:#{null}}")
private Long machineId;
/**
* 配置雪花算法ID生成器
*
* @return SnowflakeIdGenerator实例
*/
@Bean
public SnowflakeIdGenerator snowflakeIdGenerator() {
long finalMachineId;
if (machineId != null) {
// 使用配置文件中指定的机器ID
finalMachineId = machineId;
log.info("使用配置文件指定的机器ID: {}", finalMachineId);
} else {
// 自动生成机器ID
finalMachineId = generateMachineId();
log.info("自动生成机器ID: {}", finalMachineId);
}
return new SnowflakeIdGenerator(finalMachineId);
}
/**
* 自动生成机器ID
* 基于MAC地址和IP地址生成唯一的机器ID
*
* @return 机器ID (0-1023)
*/
private long generateMachineId() {
try {
// 获取本机IP地址
InetAddress localHost = InetAddress.getLocalHost();
// 获取网络接口
NetworkInterface networkInterface = NetworkInterface.getByInetAddress(localHost);
if (networkInterface != null) {
// 获取MAC地址
byte[] hardwareAddress = networkInterface.getHardwareAddress();
if (hardwareAddress != null && hardwareAddress.length >= 6) {
// 使用MAC地址的后两个字节生成机器ID
long machineId = ((hardwareAddress[4] & 0xFF) << 8) | (hardwareAddress[5] & 0xFF);
// 确保机器ID在有效范围内 (0-1023)
return machineId & 0x3FF;
}
}
// 如果无法获取MAC地址,使用IP地址生成
byte[] address = localHost.getAddress();
if (address.length >= 4) {
// 使用IP地址的后两个字节生成机器ID
long machineId = ((address[2] & 0xFF) << 8) | (address[3] & 0xFF);
return machineId & 0x3FF;
}
} catch (Exception e) {
log.warn("自动生成机器ID失败,使用默认策略: {}", e.getMessage());
}
// 如果所有方法都失败,使用当前时间戳生成
return System.currentTimeMillis() % 1024;
}
}
@@ -0,0 +1,26 @@
package com.emotion.config;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Contact;
import io.swagger.v3.oas.models.info.Info;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* Knife4j / OpenAPI 文档配置
*/
@Configuration
public class Knife4jConfig {
@Bean
public OpenAPI customOpenAPI() {
return new OpenAPI()
.info(new Info()
.title("情绪博物馆 API 文档")
.version("1.0.0")
.description("情绪博物馆后端接口文档")
.contact(new Contact()
.name("emotion-museum")
.email("")));
}
}
@@ -0,0 +1,28 @@
package com.emotion.config;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* MyBatis-Plus配置类
*
* @author huazhongmin
* @date 2025-07-23
*/
@Configuration
public class MybatisPlusConfig {
/**
* 分页插件配置
*/
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 添加分页插件
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
}
@@ -0,0 +1,40 @@
package com.emotion.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
/**
* Redis配置类
*
* @author huazhongmin
* @date 2025-07-23
*/
@Configuration
public class RedisConfig {
/**
* 配置RedisTemplate
*/
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(connectionFactory);
// 使用String序列化器作为key的序列化器
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
template.setKeySerializer(stringRedisSerializer);
template.setHashKeySerializer(stringRedisSerializer);
// 使用JSON序列化器作为value的序列化器
GenericJackson2JsonRedisSerializer jsonRedisSerializer = new GenericJackson2JsonRedisSerializer();
template.setValueSerializer(jsonRedisSerializer);
template.setHashValueSerializer(jsonRedisSerializer);
template.afterPropertiesSet();
return template;
}
}
@@ -0,0 +1,52 @@
package com.emotion.config;
import com.emotion.interceptor.WechatApiLoggingInterceptor;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.BufferingClientHttpRequestFactory;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.web.client.RestTemplate;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
/**
* 统一 RestTemplate Bean
*
* <p>设计要点:
* <ol>
* <li>用 BufferingClientHttpRequestFactory 包装 SimpleClientHttpRequestFactory
* 响应体被缓存到字节数组,可被 Interceptor / ErrorHandler / Converter 多次读取
* (避免破坏其他 7 个 RestTemplate 使用点:DifyProviderAdapter、
* CozeProviderAdapter、HttpTtsEngineClient、AsrServiceImpl、
* ApiEndpointServiceImpl、AiChatServiceImpl、ApiTestProxyController</li>
* <li>显式设置 User-Agent 为 Mozilla/5.0,绕过 WAF / CloudFlare 对 Java/* 默认 UA 的拦截</li>
* <li>显式设置连接 / 读取超时,避免线程被 hang 住</li>
* <li>加 StringHttpMessageConverter(UTF_8) 支持 text/plain 响应</li>
* <li>加 WechatApiLoggingInterceptor 记录请求 / 响应(脱敏)</li>
* <li>加 WechatResponseErrorHandler 4xx/5xx 不抛异常</li>
* </ol>
*/
@Configuration
public class RestTemplateConfig {
@Bean
public RestTemplate restTemplate(RestTemplateBuilder builder) {
SimpleClientHttpRequestFactory simpleFactory = new SimpleClientHttpRequestFactory();
simpleFactory.setConnectTimeout((int) Duration.ofSeconds(5).toMillis());
simpleFactory.setReadTimeout((int) Duration.ofSeconds(10).toMillis());
BufferingClientHttpRequestFactory bufferingFactory =
new BufferingClientHttpRequestFactory(simpleFactory);
return builder
.requestFactory(() -> bufferingFactory)
.defaultHeader("User-Agent", "Mozilla/5.0 (compatible; EmotionMuseum/1.0)")
.defaultHeader("Accept", "application/json, text/plain, */*")
.additionalMessageConverters(new StringHttpMessageConverter(StandardCharsets.UTF_8))
.additionalInterceptors(new WechatApiLoggingInterceptor())
.errorHandler(new WechatResponseErrorHandler())
.build();
}
}
@@ -0,0 +1,92 @@
package com.emotion.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import java.util.Arrays;
/**
* 安全配置
*
* @author huazhongmin
* @date 2025-07-22
*/
@Configuration
@EnableWebSecurity
public class SecurityConfig {
/**
* 密码编码器
*/
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
/**
* 安全过滤器链
*/
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
// 禁用CSRF
.csrf(csrf -> csrf.disable())
// 配置CORS
.cors(cors -> cors.configurationSource(corsConfigurationSource()))
// 配置会话管理
.sessionManagement(management -> management
.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
// 配置授权规则
.authorizeHttpRequests(authz -> authz
// 允许所有请求(简化配置)
.anyRequest().permitAll())
// 禁用默认登录页面
.formLogin(login -> login.disable())
// 禁用HTTP Basic认证
.httpBasic(basic -> basic.disable());
return http.build();
}
/**
* CORS配置
*/
@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
// 允许所有来源
configuration.setAllowedOriginPatterns(Arrays.asList("*"));
// 允许的HTTP方法
configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS", "PATCH"));
// 允许的请求头
configuration.setAllowedHeaders(Arrays.asList("*"));
// 允许携带凭证
configuration.setAllowCredentials(true);
// 预检请求的缓存时间
configuration.setMaxAge(3600L);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
}
@@ -0,0 +1,96 @@
package com.emotion.config;
import com.emotion.interceptor.AuthInterceptor;
import com.emotion.interceptor.AdminAuthInterceptor;
import com.emotion.interceptor.UserContextInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* Web配置类
* 配置拦截器、跨域等
*
* @author huazhongmin
* @date 2025-07-23
*/
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Autowired
private AuthInterceptor authInterceptor;
@Autowired
private AdminAuthInterceptor adminAuthInterceptor;
@Autowired
private UserContextInterceptor userContextInterceptor;
/**
* 添加拦截器
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 管理员认证拦截器 - 优先级最高
registry.addInterceptor(adminAuthInterceptor)
.addPathPatterns("/admin/**")
.order(1);
// 用户认证拦截器
registry.addInterceptor(authInterceptor)
.addPathPatterns("/**")
.excludePathPatterns(
"/auth/**",
"/analytics/events/batch",
"/tts/audio/**",
"/admin/**", // 排除管理员接口,由AdminAuthInterceptor处理
"/error",
"/analytics/events/batch",
"/tts/audio/**",
"/favicon.ico",
"/actuator/**",
"/swagger-ui/**",
"/swagger-resources/**",
"/v2/api-docs",
"/v3/api-docs",
"/webjars/**",
"/doc.html",
"/static/**",
"/public/**"
)
.order(2);
// 用户上下文拦截器
registry.addInterceptor(userContextInterceptor)
.addPathPatterns("/**")
.excludePathPatterns(
"/error",
"/favicon.ico",
"/actuator/**",
"/swagger-ui/**",
"/swagger-resources/**",
"/v2/api-docs",
"/v3/api-docs",
"/webjars/**",
"/doc.html",
"/static/**",
"/public/**"
)
.order(3);
}
/**
* 跨域配置
*/
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOriginPatterns("*")
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
.allowedHeaders("*")
.allowCredentials(true)
.maxAge(3600);
}
}
@@ -0,0 +1,68 @@
package com.emotion.config;
import com.emotion.interceptor.AdminAuthInterceptor;
import com.emotion.interceptor.JwtAuthInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* Web MVC配置
*
* @author huazhongmin
* @date 2025-07-23
*/
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Autowired
private JwtAuthInterceptor jwtAuthInterceptor;
@Autowired
private AdminAuthInterceptor adminAuthInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 管理员拦截器 - 优先级最高,只拦截 /admin/** 路径
registry.addInterceptor(adminAuthInterceptor)
.addPathPatterns("/admin/**")
.excludePathPatterns(
"/admin/auth/login", // 管理员登录接口
"/admin/auth/refreshToken" // 管理员刷新token接口
)
.order(1); // 优先级1,最先执行
// 普通用户拦截器 - 拦截除管理员路径外的所有请求
// 注意: 由于 context-path=/api, excludePathPatterns 需要包含 /api 前缀的路径
registry.addInterceptor(jwtAuthInterceptor)
.addPathPatterns("/**")
.excludePathPatterns(
"/auth/login", // 登录接口
"/auth/register", // 注册接口
"/auth/captcha", // 图形验证码接口
"/auth/sms-code", // 短信验证码接口(免登录)
"/auth/refresh-token", // 刷新token接口
"/auth/resetPassword", // 重置密码接口(免登录)
"/analytics/events/batch", // Analytics event batch endpoint
"/tts/audio/**", // Public generated TTS audio files
"/health", // 健康检查接口
"/ws/**", // WebSocket接口
"/swagger-ui", // Swagger UI
"/swagger-ui/**", // Swagger UI sub-paths
"/v3/api-docs", // API docs root
"/v3/api-docs/**", // API docs sub-paths
"/doc.html", // Knife4j entry
"/webjars", // Knife4j static resources
"/webjars/**", // Knife4j static resources sub-paths
"/swagger-resources", // Swagger resources root
"/swagger-resources/**", // Swagger resources sub-paths
"/actuator/**", // Actuator endpoints
"/admin/**", // 排除管理员路径,由管理员拦截器处理
"/auth/wechat/login", // WeChat mini program login endpoint
"/auth/loginConfig", // 登录方式配置接口(免登录)
"/error" // Spring Boot error page
)
.order(2); // 优先级2
}
}
@@ -0,0 +1,54 @@
package com.emotion.config;
import com.emotion.interceptor.WebSocketAuthInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.ChannelRegistration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;
/**
* WebSocket配置
*
* @author huazhongmin
* @date 2025-07-22
*/
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
@Autowired
private WebSocketAuthInterceptor webSocketAuthInterceptor;
@Override
public void configureMessageBroker(MessageBrokerRegistry config) {
// 启用简单消息代理,并设置消息代理的前缀
config.enableSimpleBroker("/topic", "/queue", "/user");
// 设置应用程序的目标前缀
config.setApplicationDestinationPrefixes("/app");
// 设置用户目标前缀
config.setUserDestinationPrefix("/user");
}
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
// 注册STOMP端点
registry.addEndpoint("/ws/chat")
.setAllowedOriginPatterns("*")
.withSockJS();
// 注册普通WebSocket端点
registry.addEndpoint("/ws/chat")
.setAllowedOriginPatterns("*");
}
@Override
public void configureClientInboundChannel(ChannelRegistration registration) {
// 添加WebSocket认证拦截器
registration.interceptors(webSocketAuthInterceptor);
}
}
@@ -0,0 +1,17 @@
package com.emotion.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
@Data
@Configuration
@ConfigurationProperties(prefix = "emotion.wechat.mini-program")
public class WechatMiniProgramProperties {
private String appId;
private String appSecret;
private String baseUrl = "https://api.weixin.qq.com";
}
@@ -0,0 +1,35 @@
package com.emotion.config;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.web.client.DefaultResponseErrorHandler;
import java.io.IOException;
/**
* 微信 API 错误处理器
*
* <p>设计要点:
* <ol>
* <li>显式重写 hasError() 委托父类判断(4xx/5xx → true),让 handleError() 在错误时被调用</li>
* <li>handleError() 不抛 RestClientException,由业务层(WechatMiniProgramServiceImpl
* 根据 status code 判断如何处理</li>
* <li>不在此处读 body(避免流消费,由 LoggingInterceptor 统一处理日志)</li>
* </ol>
*/
@Slf4j
public class WechatResponseErrorHandler extends DefaultResponseErrorHandler {
@Override
public boolean hasError(ClientHttpResponse response) throws IOException {
// 委托父类 DefaultResponseErrorHandler.hasError()4xx/5xx 返回 true,其他返回 false
return super.hasError(response);
}
@Override
public void handleError(ClientHttpResponse response) throws IOException {
// 不抛异常;body 已在 LoggingInterceptor 中读取并脱敏记录
// 业务层会通过 response.getStatusCode() 判断
log.warn("[WeChatAPI] HTTP error response, status={}", response.getStatusCode());
}
}
@@ -0,0 +1,189 @@
package com.emotion.controller;
import com.emotion.common.PageResult;
import com.emotion.common.Result;
import com.emotion.dto.request.achievement.*;
import com.emotion.dto.response.achievement.AchievementResponse;
import com.emotion.service.AchievementService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.time.LocalDateTime;
import java.util.List;
/**
* 成就控制器
*
* @author huazhongmin
* @date 2025-09-08
*/
@RestController
@RequestMapping("/achievement")
@Tag(name = "成就管理", description = "成就的增删改查功能")
public class AchievementController {
@Autowired
private AchievementService achievementService;
/**
* 分页查询成就
*/
@Operation(summary = "分页查询成就", description = "分页查询成就列表")
@GetMapping("/page")
public Result<PageResult<AchievementResponse>> getPage(@Validated AchievementPageRequest request) {
PageResult<AchievementResponse> pageResult = achievementService.getPageWithResponse(request);
return Result.success(pageResult);
}
/**
* 根据ID获取成就
*/
@Operation(summary = "根据ID获取成就", description = "根据ID获取成就详情")
@GetMapping("/detail")
public Result<AchievementResponse> getById(@Parameter(description = "成就 ID") @RequestParam String id) {
AchievementResponse response = achievementService.getAchievementResponseById(id);
if (response == null) {
return Result.notFound("成就不存在");
}
return Result.success(response);
}
/**
* 创建成就
*/
@Operation(summary = "创建成就", description = "创建新的成就")
@PostMapping("/create")
public Result<AchievementResponse> create(@RequestBody @Validated AchievementCreateRequest request) {
AchievementResponse response = achievementService.createAchievementWithResponse(request);
if (response == null) {
return Result.error("创建失败");
}
return Result.success("创建成功", response);
}
/**
* 更新成就
*/
@Operation(summary = "更新成就", description = "更新指定成就")
@PutMapping("/update")
public Result<AchievementResponse> update(@RequestBody @Validated AchievementUpdateRequest request) {
AchievementResponse response = achievementService.updateAchievementWithResponse(request);
if (response == null) {
return Result.error("更新失败");
}
return Result.success("更新成功", response);
}
/**
* 删除成就
*/
@Operation(summary = "删除成就", description = "删除指定成就")
@DeleteMapping("/delete")
public Result<Void> delete(@Parameter(description = "成就 ID") @RequestParam String id) {
boolean deleted = achievementService.removeById(id);
if (!deleted) {
return Result.error("删除失败");
}
return Result.success();
}
/**
* 根据分类查询成就
*/
@Operation(summary = "根据分类查询成就", description = "根据分类查询成就列表")
@GetMapping("/byCategory")
public Result<List<AchievementResponse>> getByCategory(@Parameter(description = "分类") @RequestParam String category) {
List<AchievementResponse> responses = achievementService.getByCategoryWithResponse(category);
return Result.success(responses);
}
/**
* 根据稀有度查询成就
*/
@Operation(summary = "根据稀有度查询成就", description = "根据稀有度查询成就列表")
@GetMapping("/byRarity")
public Result<List<AchievementResponse>> getByRarity(@Parameter(description = "稀有度") @RequestParam String rarity) {
List<AchievementResponse> responses = achievementService.getByRarityWithResponse(rarity);
return Result.success(responses);
}
/**
* 查询已解锁的成就
*/
@Operation(summary = "查询已解锁的成就", description = "查询已解锁的成就列表")
@GetMapping("/unlocked")
public Result<List<AchievementResponse>> getUnlockedAchievements() {
List<AchievementResponse> responses = achievementService.getUnlockedAchievementsWithResponse();
return Result.success(responses);
}
/**
* 查询未解锁的成就
*/
@Operation(summary = "查询未解锁的成就", description = "查询未解锁的成就列表")
@GetMapping("/locked")
public Result<List<AchievementResponse>> getLockedAchievements() {
List<AchievementResponse> responses = achievementService.getLockedAchievementsWithResponse();
return Result.success(responses);
}
/**
* 统计已解锁成就数量
*/
@Operation(summary = "统计已解锁成就数量", description = "统计已解锁成就数量")
@GetMapping("/unlockedCount")
public Result<Long> countUnlockedAchievements() {
Long count = achievementService.countUnlockedAchievements();
return Result.success(count);
}
/**
* 统计未解锁成就数量
*/
@Operation(summary = "统计未解锁成就数量", description = "统计未解锁成就数量")
@GetMapping("/lockedCount")
public Result<Long> countLockedAchievements() {
Long count = achievementService.countLockedAchievements();
return Result.success(count);
}
/**
* 解锁成就
*/
@Operation(summary = "解锁成就", description = "解锁指定成就")
@PutMapping("/unlock")
public Result<Void> unlockAchievement(@RequestBody @Validated AchievementUnlockRequest request) {
boolean unlocked = achievementService.unlockAchievement(request.getId(), LocalDateTime.now());
if (!unlocked) {
return Result.error("解锁失败");
}
return Result.success();
}
/**
* 更新成就进度
*/
@Operation(summary = "更新成就进度", description = "更新指定成就的进度")
@PutMapping("/updateProgress")
public Result<Void> updateProgress(@RequestBody @Validated AchievementProgressUpdateRequest request) {
boolean updated = achievementService.updateProgress(request.getId(), request.getProgress());
if (!updated) {
return Result.error("更新进度失败");
}
return Result.success();
}
/**
* 查询最近解锁的成就
*/
@Operation(summary = "查询最近解锁的成就", description = "查询最近解锁的成就列表")
@GetMapping("/recent")
public Result<List<AchievementResponse>> getRecentlyUnlocked(@Parameter(description = "排名数量限制") @RequestParam(defaultValue = "10") Integer limit) {
List<AchievementResponse> responses = achievementService.getRecentlyUnlockedWithResponse(limit);
return Result.success(responses);
}
}
@@ -0,0 +1,65 @@
package com.emotion.controller;
import com.emotion.common.Result;
import com.emotion.dto.request.analytics.AnalyticsQueryRequest;
import com.emotion.dto.response.analytics.AnalyticsFunnelItem;
import com.emotion.dto.response.analytics.AnalyticsOverviewResponse;
import com.emotion.dto.response.analytics.AnalyticsPreferenceItem;
import com.emotion.dto.response.analytics.AnalyticsTopEventItem;
import com.emotion.dto.response.analytics.AnalyticsTrendItem;
import com.emotion.dto.response.analytics.AnalyticsUserItem;
import com.emotion.service.AnalyticsService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
@RequestMapping("/admin/analytics")
@Tag(name = "数据分析管理", description = "管理后台的数据分析看板接口")
public class AdminAnalyticsController {
@Autowired
private AnalyticsService analyticsService;
@Operation(summary = "获取数据概览", description = "获取数据分析概览,包括关键指标和汇总统计")
@GetMapping("/overview")
public Result<AnalyticsOverviewResponse> overview(@Validated AnalyticsQueryRequest request) {
return Result.success(analyticsService.getOverview(request));
}
@Operation(summary = "获取趋势数据", description = "获取指定时间范围内的数据趋势")
@GetMapping("/trend")
public Result<List<AnalyticsTrendItem>> trend(@Validated AnalyticsQueryRequest request) {
return Result.success(analyticsService.getTrend(request));
}
@Operation(summary = "获取漏斗数据", description = "获取用户转化漏斗数据,分析各阶段转化率")
@GetMapping("/funnel")
public Result<List<AnalyticsFunnelItem>> funnel(@Validated AnalyticsQueryRequest request) {
return Result.success(analyticsService.getFunnel(request));
}
@Operation(summary = "获取用户偏好数据", description = "获取用户偏好设置和使用习惯统计")
@GetMapping("/preferences")
public Result<List<AnalyticsPreferenceItem>> preferences(@Validated AnalyticsQueryRequest request) {
return Result.success(analyticsService.getPreferences(request));
}
@Operation(summary = "获取热门事件排行", description = "获取热门事件排行榜,按访问次数倒序排列")
@GetMapping("/top-events")
public Result<List<AnalyticsTopEventItem>> topEvents(@Validated AnalyticsQueryRequest request) {
return Result.success(analyticsService.getTopEvents(request));
}
@Operation(summary = "获取用户活跃数据", description = "获取用户活跃度分析和行为统计数据")
@GetMapping("/users")
public Result<List<AnalyticsUserItem>> users(@Validated AnalyticsQueryRequest request) {
return Result.success(analyticsService.getUsers(request));
}
}
@@ -0,0 +1,111 @@
package com.emotion.controller;
import com.emotion.common.Result;
import com.emotion.dto.request.AdminChangePasswordRequest;
import com.emotion.dto.request.AdminLoginRequest;
import com.emotion.dto.request.RefreshTokenRequest;
import com.emotion.dto.response.AdminAuthResponse;
import com.emotion.dto.response.AdminInfoResponse;
import com.emotion.service.AdminAuthService;
import com.emotion.util.JwtUtil;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
/**
* 管理员认证控制器
*
* @author emotion-museum
* @date 2025-10-27
*/
@RestController
@RequestMapping("/admin/auth")
@Tag(name = "管理员认证管理", description = "管理员登录、登出等认证相关接口")
public class AdminAuthController {
@Autowired
private AdminAuthService adminAuthService;
@Autowired
private JwtUtil jwtUtil;
/**
* 管理员登录
*/
@PostMapping("/login")
@Operation(summary = "管理员登录", description = "使用账号和密码登录")
public Result<AdminAuthResponse> login(@Validated @RequestBody AdminLoginRequest request) {
AdminAuthResponse response = adminAuthService.login(request);
return Result.success("登录成功", response);
}
/**
* 获取当前管理员信息
*/
@GetMapping("/info")
@Operation(summary = "获取当前管理员信息", description = "根据Token获取当前登录的管理员信息")
public Result<AdminInfoResponse> getCurrentAdminInfo(HttpServletRequest request) {
String authHeader = request.getHeader("Authorization");
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
return Result.unauthorized("未登录");
}
String token = authHeader.substring(7);
String adminId = jwtUtil.getUserIdFromToken(token);
AdminInfoResponse adminInfo = adminAuthService.getCurrentAdminInfo(adminId);
return Result.success(adminInfo);
}
/**
* 管理员登出
*/
@PostMapping("/logout")
@Operation(summary = "管理员登出", description = "退出登录")
public Result<Void> logout(HttpServletRequest request) {
adminAuthService.logout(request);
return Result.success("登出成功", null);
}
/**
* 刷新访问令牌
*/
@PostMapping("/refreshToken")
@Operation(summary = "刷新访问令牌", description = "使用刷新令牌获取新的访问令牌")
public Result<AdminAuthResponse> refreshToken(@Validated @RequestBody RefreshTokenRequest request) {
AdminAuthResponse response = adminAuthService.refreshToken(request.getRefreshToken());
return Result.success("令牌刷新成功", response);
}
/**
* 验证访问令牌
*/
@GetMapping("/validateToken")
@Operation(summary = "验证访问令牌", description = "验证当前Token是否有效")
public Result<Boolean> validateToken(HttpServletRequest request) {
boolean isValid = adminAuthService.validateToken(request);
return Result.success(isValid);
}
/**
* 修改管理员密码(修改自己的密码)
*/
@PostMapping("/changePassword")
@Operation(summary = "修改管理员密码", description = "当前登录的管理员修改自己的密码,需要提供原密码")
public Result<Void> changePassword(HttpServletRequest request, @Validated @RequestBody AdminChangePasswordRequest req) {
String authHeader = request.getHeader("Authorization");
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
return Result.unauthorized("未登录");
}
String token = authHeader.substring(7);
String adminId = jwtUtil.getUserIdFromToken(token);
adminAuthService.changePassword(adminId, req);
return Result.success("密码修改成功", null);
}
}
@@ -0,0 +1,199 @@
package com.emotion.controller;
import com.emotion.common.PageResult;
import com.emotion.common.Result;
import com.emotion.dto.request.AiConfigCallStatsRequest;
import com.emotion.dto.request.AdminCreateRequest;
import com.emotion.dto.request.AdminPageRequest;
import com.emotion.dto.request.AdminResetPasswordRequest;
import com.emotion.dto.request.AdminUpdateRequest;
import com.emotion.dto.response.AiConfigCallStatsResponse;
import com.emotion.dto.response.AdminResponse;
import com.emotion.dto.response.DashboardStatsResponse;
import com.emotion.service.AdminService;
import com.emotion.service.DashboardService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* 管理员控制器
*
* @author huazhongmin
* @date 2025-10-27
*/
@RestController
@RequestMapping("/admin")
@Tag(name = "管理员管理", description = "管理员的增删改查功能")
public class AdminController {
@Autowired
private AdminService adminService;
@Autowired
private DashboardService dashboardService;
/**
* 分页查询管理员
*/
@Operation(summary = "分页查询管理员", description = "分页查询管理员列表")
@GetMapping("/page")
public Result<PageResult<AdminResponse>> getPage(@Validated AdminPageRequest request) {
PageResult<AdminResponse> pageResult = adminService.getPageWithResponse(request);
return Result.success(pageResult);
}
/**
* 根据ID获取管理员
*/
@Operation(summary = "根据ID获取管理员", description = "根据ID获取管理员详情")
@GetMapping("/detail")
public Result<AdminResponse> getById(
@Parameter(description = "管理员ID", required = true)
@RequestParam String id) {
AdminResponse response = adminService.getAdminResponseById(id);
if (response == null) {
return Result.notFound("管理员不存在");
}
return Result.success(response);
}
/**
* 创建管理员
*/
@Operation(summary = "创建管理员", description = "创建新的管理员")
@PostMapping("/create")
public Result<AdminResponse> create(@RequestBody @Validated AdminCreateRequest request) {
AdminResponse response = adminService.createAdminWithResponse(request);
if (response == null) {
return Result.error("创建失败");
}
return Result.success("创建成功", response);
}
/**
* 更新管理员
*/
@Operation(summary = "更新管理员", description = "更新指定管理员")
@PutMapping("/update")
public Result<AdminResponse> update(@RequestBody @Validated AdminUpdateRequest request) {
AdminResponse response = adminService.updateAdminWithResponse(request);
if (response == null) {
return Result.error("更新失败");
}
return Result.success("更新成功", response);
}
/**
* 删除管理员
*/
@Operation(summary = "删除管理员", description = "删除指定管理员")
@DeleteMapping("/delete")
public Result<Void> delete(
@Parameter(description = "管理员ID", required = true)
@RequestParam String id) {
boolean deleted = adminService.removeById(id);
if (!deleted) {
return Result.error("删除失败");
}
return Result.success("删除成功", null);
}
/**
* 重置管理员密码(超级管理员操作)
*/
@Operation(summary = "重置管理员密码", description = "超级管理员重置指定管理员的密码")
@PostMapping("/changePassword")
public Result<Void> changePassword(@Validated @RequestBody AdminResetPasswordRequest request) {
adminService.resetPassword(request.getId(), request.getNewPassword());
return Result.success("密码重置成功", null);
}
/**
* 获取仪表盘统计数据
*/
@Operation(summary = "获取仪表盘统计数据", description = "获取管理后台仪表盘的统计数据,包括用户、内容、AI服务和系统统计")
@GetMapping("/dashboard/stats")
public Result<DashboardStatsResponse> getDashboardStats() {
DashboardStatsResponse stats = dashboardService.getDashboardStats();
return Result.success("获取成功", stats);
}
/**
* 获取用户统计数据
*/
@Operation(summary = "获取用户统计数据", description = "获取用户相关的统计数据")
@GetMapping("/dashboard/user-stats")
public Result<DashboardStatsResponse.UserStats> getUserStats() {
DashboardStatsResponse.UserStats userStats = dashboardService.getUserStats();
return Result.success("获取成功", userStats);
}
/**
* 获取内容统计数据
*/
@Operation(summary = "获取内容统计数据", description = "获取内容相关的统计数据")
@GetMapping("/dashboard/content-stats")
public Result<DashboardStatsResponse.ContentStats> getContentStats() {
DashboardStatsResponse.ContentStats contentStats = dashboardService.getContentStats();
return Result.success("获取成功", contentStats);
}
/**
* 获取AI服务统计数据
*/
@Operation(summary = "获取AI服务统计数据", description = "获取AI服务相关的统计数据")
@GetMapping("/dashboard/ai-stats")
public Result<DashboardStatsResponse.AiServiceStats> getAiServiceStats() {
DashboardStatsResponse.AiServiceStats aiStats = dashboardService.getAiServiceStats();
return Result.success("获取成功", aiStats);
}
/**
* 获取系统统计数据
*/
@Operation(summary = "获取系统统计数据", description = "获取系统相关的统计数据")
@GetMapping("/dashboard/system-stats")
public Result<DashboardStatsResponse.SystemStats> getSystemStats() {
DashboardStatsResponse.SystemStats systemStats = dashboardService.getSystemStats();
return Result.success("获取成功", systemStats);
}
/**
* 获取用户增长趋势数据
*/
@Operation(summary = "获取用户增长趋势数据", description = "获取指定天数的用户增长趋势数据")
@GetMapping("/dashboard/user-growth-trends")
public Result<List<DashboardStatsResponse.UserGrowthTrend>> getUserGrowthTrends(
@Parameter(description = "查询天数", required = false)
@RequestParam(defaultValue = "7") int days) {
List<DashboardStatsResponse.UserGrowthTrend> trends = dashboardService.getUserGrowthTrends(days);
return Result.success("获取成功", trends);
}
/**
* 获取最近登录用户
*/
@Operation(summary = "获取最近登录用户", description = "获取最近登录的用户列表")
@GetMapping("/dashboard/recent-logins")
public Result<List<DashboardStatsResponse.RecentLogin>> getRecentLogins(
@Parameter(description = "返回数量限制", required = false)
@RequestParam(defaultValue = "10") int limit) {
List<DashboardStatsResponse.RecentLogin> recentLogins = dashboardService.getRecentLogins(limit);
return Result.success("获取成功", recentLogins);
}
/**
* 获取 AI 配置调用次数统计
*/
@Operation(summary = "获取AI配置调用次数统计", description = "按 t_ai_config 的 workflow_id 关联 t_coze_api_call 统计调用次数并按次数倒序返回")
@GetMapping(value = "/dashboard/aiConfigCallStats")
public Result<AiConfigCallStatsResponse> getAiConfigCallStats(@Validated AiConfigCallStatsRequest request) {
AiConfigCallStatsResponse response = dashboardService.getAiConfigCallStats(request);
return Result.success("获取成功", response);
}
}
@@ -0,0 +1,133 @@
package com.emotion.controller;
import com.emotion.common.Result;
import com.emotion.dto.request.AiChatRequest;
import com.emotion.dto.request.AiSummaryRequest;
import com.emotion.dto.request.GuestChatRequest;
import com.emotion.dto.request.ConversationCreateRequest;
import com.emotion.dto.request.ChatStatsRequest;
import com.emotion.dto.response.AiChatResponse;
import com.emotion.dto.response.AiSummaryResponse;
import com.emotion.dto.response.AiStatusResponse;
import com.emotion.dto.response.ChatStatsResponse;
import com.emotion.dto.response.GuestChatResponse;
import com.emotion.dto.response.GuestUserInfoResponse;
import com.emotion.dto.response.ConversationResponse;
import com.emotion.service.AiChatService;
import com.emotion.util.UserContextUtils;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
import javax.validation.Valid;
/**
* AI聊天控制器
*
* @author huazhongmin
* @date 2025-07-23
*/
@Slf4j
@RestController
@RequestMapping("/ai")
@Tag(name = "AI 聊天", description = "AI 智能对话聊天接口,支持普通聊天、总结、状态查询、访客模式等")
public class AiChatController {
@Autowired
private AiChatService aiChatService;
/**
* 发送聊天消息
*/
@Operation(summary = "发送 AI 聊天消息", description = "向 AI 发送聊天消息,返回 AI 回复。支持指定会话 ID 和用户 ID。")
@PostMapping("/chat")
public Result<AiChatResponse> sendChatMessage(@Valid @RequestBody AiChatRequest request) {
log.info("收到AI聊天请求: conversationId={}, userId={}, message={}",
request.getConversationId(), request.getUserId(), request.getMessage());
// 调用AI服务
AiChatResponse response = aiChatService.sendChatMessage(request);
return Result.success(response);
}
/**
* 生成对话总结
*/
@Operation(summary = "获取 AI 聊天总结", description = "获取指定会话的 AI 聊天总结,概括对话要点。")
@PostMapping("/summary")
public Result<AiSummaryResponse> generateSummary(@Valid @RequestBody AiSummaryRequest request) {
log.info("收到对话总结请求: conversationId={}, userId={}", request.getConversationId(), request.getUserId());
// 调用AI总结服务
AiSummaryResponse response = aiChatService.generateConversationSummary(request);
return Result.success(response);
}
/**
* 获取AI服务状态
*/
@Operation(summary = "获取 AI 状态", description = "获取当前 AI 服务的运行状态信息。")
@GetMapping("/status")
public Result<AiStatusResponse> getServiceStatus() {
log.info("获取AI服务状态");
AiStatusResponse response = aiChatService.getServiceStatus();
return Result.success(response);
}
/**
* 获取聊天记录统计
*/
@Operation(summary = "获取聊天统计数据", description = "获取指定时间范围内的聊天统计数据,包括消息数、Token 消耗等。")
@GetMapping("/stats")
public Result<ChatStatsResponse> getChatStats(@Valid ChatStatsRequest request) {
log.info("获取聊天统计: userId={}, conversationId={}", request.getUserId(), request.getConversationId());
ChatStatsResponse response = aiChatService.getChatStats(request);
return Result.success(response);
}
/**
* 访客聊天(不需要登录)
*/
@Operation(summary = "访客模式聊天", description = "未登录用户通过访客模式与 AI 进行对话,无需 token 认证。")
@PostMapping("/guestChat")
public Result<GuestChatResponse> guestChat(@Valid @RequestBody GuestChatRequest request,
HttpServletRequest httpRequest) {
String clientIp = UserContextUtils.getClientIpAddress(httpRequest);
log.info("访客聊天请求: {}, IP: {}", request.getMessage(), clientIp);
GuestChatResponse response = aiChatService.guestChat(request, clientIp);
return Result.success("发送成功", response);
}
/**
* 获取访客用户信息
*/
@Operation(summary = "获取访客用户信息", description = "根据访客 ID 获取对应的用户信息。")
@GetMapping("/guestUserInfo")
public Result<GuestUserInfoResponse> getGuestUserInfo(HttpServletRequest request) {
String clientIp = UserContextUtils.getClientIpAddress(request);
log.info("获取访客用户信息: IP={}", clientIp);
GuestUserInfoResponse response = aiChatService.getGuestUserInfo(clientIp);
return Result.success(response);
}
/**
* 创建对话
*/
@Operation(summary = "创建对话会话", description = "为当前用户创建一个新的 AI 对话会话。")
@PostMapping("/createConversation")
public Result<ConversationResponse> createConversation(@Valid @RequestBody ConversationCreateRequest request,
HttpServletRequest httpRequest) {
log.info("创建对话请求: userId={}, title={}", request.getUserId(), request.getTitle());
String clientIp = UserContextUtils.getClientIpAddress(httpRequest);
ConversationResponse response = aiChatService.createConversation(request, clientIp);
return Result.success("创建成功", response);
}
}
@@ -0,0 +1,303 @@
package com.emotion.controller;
import com.emotion.common.PageResult;
import com.emotion.common.Result;
import com.emotion.dto.request.aiconfig.*;
import com.emotion.dto.response.aiconfig.AiConfigResponse;
import com.emotion.service.AiConfigService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* AI配置控制器
*
* @author system
* @date 2025-10-30
*/
@RestController
@RequestMapping("/aiConfig")
@Tag(name = "AI配置管理", description = "AI配置的增删改查功能")
public class AiConfigController {
@Autowired
private AiConfigService aiConfigService;
/**
* 分页查询AI配置
*/
@Operation(summary = "分页查询AI配置", description = "分页查询AI配置列表")
@GetMapping("/page")
public Result<PageResult<AiConfigResponse>> getPage(@Validated AiConfigPageRequest request) {
PageResult<AiConfigResponse> pageResult = aiConfigService.getPageWithResponse(request);
return Result.success(pageResult);
}
/**
* 根据ID获取AI配置
*/
@Operation(summary = "根据ID获取AI配置", description = "根据ID获取AI配置详情")
@GetMapping("/detail")
public Result<AiConfigResponse> getById(@RequestParam String id) {
AiConfigResponse response = aiConfigService.getAiConfigResponseById(id);
if (response == null) {
return Result.notFound("AI配置不存在");
}
return Result.success(response);
}
/**
* 创建AI配置
*/
@Operation(summary = "创建AI配置", description = "创建新的AI配置")
@PostMapping("/create")
public Result<AiConfigResponse> create(@RequestBody @Validated AiConfigCreateRequest request) {
AiConfigResponse response = aiConfigService.createAiConfigWithResponse(request);
if (response == null) {
return Result.error("创建失败");
}
return Result.success("创建成功", response);
}
/**
* 更新AI配置
*/
@Operation(summary = "更新AI配置", description = "更新指定AI配置")
@PutMapping("/update")
public Result<AiConfigResponse> update(@RequestBody @Validated AiConfigUpdateRequest request) {
AiConfigResponse response = aiConfigService.updateAiConfigWithResponse(request);
if (response == null) {
return Result.error("更新失败");
}
return Result.success("更新成功", response);
}
/**
* 删除AI配置
*/
@Operation(summary = "删除AI配置", description = "删除指定AI配置")
@DeleteMapping("/delete")
public Result<Void> delete(@RequestParam String id) {
boolean deleted = aiConfigService.removeById(id);
if (!deleted) {
return Result.error("删除失败");
}
return Result.success();
}
/**
* 根据配置类型查询AI配置
*/
@Operation(summary = "根据配置类型查询AI配置", description = "根据配置类型查询AI配置列表")
@GetMapping("/byConfigType")
public Result<List<AiConfigResponse>> getByConfigType(@RequestParam String configType) {
List<AiConfigResponse> responses = aiConfigService.getByConfigTypeWithResponse(configType);
return Result.success(responses);
}
/**
* 根据服务提供商查询AI配置
*/
@Operation(summary = "根据服务提供商查询AI配置", description = "根据服务提供商查询AI配置列表")
@GetMapping("/byProvider")
public Result<List<AiConfigResponse>> getByProvider(@RequestParam String provider) {
List<AiConfigResponse> responses = aiConfigService.getByProviderWithResponse(provider);
return Result.success(responses);
}
/**
* 根据使用场景查询AI配置
*/
@Operation(summary = "根据使用场景查询AI配置", description = "根据使用场景查询AI配置列表")
@GetMapping("/byUsageScenario")
public Result<List<AiConfigResponse>> getByUsageScenario(@RequestParam String usageScenario) {
List<AiConfigResponse> responses = aiConfigService.getByUsageScenarioWithResponse(usageScenario);
return Result.success(responses);
}
/**
* 根据环境查询AI配置
*/
@Operation(summary = "根据环境查询AI配置", description = "根据环境查询AI配置列表")
@GetMapping("/byEnvironment")
public Result<List<AiConfigResponse>> getByEnvironment(@RequestParam String environment) {
List<AiConfigResponse> responses = aiConfigService.getByEnvironmentWithResponse(environment);
return Result.success(responses);
}
/**
* 查询已启用的AI配置
*/
@Operation(summary = "查询已启用的AI配置", description = "查询已启用的AI配置列表")
@GetMapping("/enabled")
public Result<List<AiConfigResponse>> getEnabledConfigs() {
List<AiConfigResponse> responses = aiConfigService.getEnabledConfigsWithResponse();
return Result.success(responses);
}
/**
* 查询已禁用的AI配置
*/
@Operation(summary = "查询已禁用的AI配置", description = "查询已禁用的AI配置列表")
@GetMapping("/disabled")
public Result<List<AiConfigResponse>> getDisabledConfigs() {
List<AiConfigResponse> responses = aiConfigService.getDisabledConfigsWithResponse();
return Result.success(responses);
}
/**
* 查询默认配置
*/
@Operation(summary = "查询默认配置", description = "查询默认配置列表")
@GetMapping("/default")
public Result<List<AiConfigResponse>> getDefaultConfigs() {
List<AiConfigResponse> responses = aiConfigService.getDefaultConfigsWithResponse();
return Result.success(responses);
}
/**
* 根据配置键值查询AI配置
*/
@Operation(summary = "根据配置键值查询AI配置", description = "根据配置键值查询AI配置")
@GetMapping("/byConfigKey")
public Result<AiConfigResponse> getByConfigKey(@RequestParam String configKey) {
AiConfigResponse response = aiConfigService.getByConfigKeyWithResponse(configKey);
if (response == null) {
return Result.notFound("AI配置不存在");
}
return Result.success(response);
}
/**
* 启用AI配置
*/
@Operation(summary = "启用AI配置", description = "启用指定AI配置")
@PutMapping("/enable")
public Result<Void> enableConfig(@RequestParam String id) {
boolean enabled = aiConfigService.enableConfig(id);
if (!enabled) {
return Result.error("启用失败");
}
return Result.success();
}
/**
* 禁用AI配置
*/
@Operation(summary = "禁用AI配置", description = "禁用指定AI配置")
@PutMapping("/disable")
public Result<Void> disableConfig(@RequestParam String id) {
boolean disabled = aiConfigService.disableConfig(id);
if (!disabled) {
return Result.error("禁用失败");
}
return Result.success();
}
/**
* 设置为默认配置
*/
@Operation(summary = "设置为默认配置", description = "设置指定AI配置为默认配置")
@PutMapping("/setDefault")
public Result<Void> setAsDefault(@RequestParam String id) {
boolean set = aiConfigService.setAsDefault(id);
if (!set) {
return Result.error("设置默认配置失败");
}
return Result.success();
}
/**
* 取消默认配置
*/
@Operation(summary = "取消默认配置", description = "取消指定AI配置的默认设置")
@PutMapping("/unsetDefault")
public Result<Void> unsetDefault(@RequestParam String id) {
boolean unset = aiConfigService.unsetDefault(id);
if (!unset) {
return Result.error("取消默认配置失败");
}
return Result.success();
}
/**
* 根据使用场景和环境查询最优配置
*/
@Operation(summary = "查询最优配置", description = "根据使用场景和环境查询最优配置")
@GetMapping("/bestConfig")
public Result<AiConfigResponse> getBestConfig(@RequestParam String usageScenario,
@RequestParam String environment) {
AiConfigResponse response = aiConfigService.getBestConfigWithResponse(usageScenario, environment);
if (response == null) {
return Result.notFound("未找到匹配的AI配置");
}
return Result.success(response);
}
/**
* 统计已启用配置数量
*/
@Operation(summary = "统计已启用配置数量", description = "统计已启用配置数量")
@GetMapping("/countEnabled")
public Result<Long> countEnabledConfigs() {
Long count = aiConfigService.countEnabledConfigs();
return Result.success(count);
}
/**
* 统计已禁用配置数量
*/
@Operation(summary = "统计已禁用配置数量", description = "统计已禁用配置数量")
@GetMapping("/countDisabled")
public Result<Long> countDisabledConfigs() {
Long count = aiConfigService.countDisabledConfigs();
return Result.success(count);
}
/**
* 统计默认配置数量
*/
@Operation(summary = "统计默认配置数量", description = "统计默认配置数量")
@GetMapping("/countDefault")
public Result<Long> countDefaultConfigs() {
Long count = aiConfigService.countDefaultConfigs();
return Result.success(count);
}
/**
* 根据配置类型统计数量
*/
@Operation(summary = "根据配置类型统计数量", description = "根据配置类型统计数量")
@GetMapping("/countByConfigType")
public Result<Long> countByConfigType(@RequestParam String configType) {
Long count = aiConfigService.countByConfigType(configType);
return Result.success(count);
}
/**
* 根据服务提供商统计数量
*/
@Operation(summary = "根据服务提供商统计数量", description = "根据服务提供商统计数量")
@GetMapping("/countByProvider")
public Result<Long> countByProvider(@RequestParam String provider) {
Long count = aiConfigService.countByProvider(provider);
return Result.success(count);
}
/**
* 测试后更新AI配置
*/
@Operation(summary = "测试后更新AI配置", description = "从测试请求中解析参数并更新配置")
@PutMapping("/updateFromTest")
public Result<AiConfigResponse> updateFromTest(@RequestBody @Validated AiConfigTestUpdateRequest request) {
AiConfigResponse response = aiConfigService.updateFromTestRequest(request);
if (response == null) {
return Result.error("更新失败,配置不存在");
}
return Result.success("更新成功", response);
}
}
@@ -0,0 +1,252 @@
package com.emotion.controller;
import com.alibaba.fastjson2.JSONObject;
import com.emotion.common.PageResult;
import com.emotion.common.Result;
import com.emotion.dto.request.ai.AiCallLogQueryRequest;
import com.emotion.dto.request.ai.AiRuntimeRequest;
import javax.validation.Valid;
import com.emotion.dto.response.ai.AiTestTemplateResponse;
import com.emotion.dto.response.ai.AiRuntimeTestResponse;
import com.emotion.dto.response.ai.AiStreamEvent;
import com.emotion.entity.AiCallLog;
import com.emotion.entity.AiEndpointConfig;
import com.emotion.entity.AiProvider;
import com.emotion.entity.AiSceneBinding;
import com.emotion.service.AiCallLogService;
import com.emotion.service.AiEndpointConfigService;
import com.emotion.service.AiProviderService;
import com.emotion.service.AiRuntimeService;
import com.emotion.service.AiSceneBindingService;
import com.emotion.util.UserContextHolder;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
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.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
@Slf4j
@RestController
@RequestMapping("/ai")
@Tag(name = "AI 路由管理", description = "AI 服务提供商路由管理接口,包括 Provider/Endpoint/Scene 的 CRUD、调用日志、运行时测试和流式测试")
public class AiRoutingController {
private final AiProviderService providerService;
private final AiEndpointConfigService endpointConfigService;
private final AiSceneBindingService sceneBindingService;
private final AiCallLogService callLogService;
private final AiRuntimeService runtimeService;
public AiRoutingController(AiProviderService providerService,
AiEndpointConfigService endpointConfigService,
AiSceneBindingService sceneBindingService,
AiCallLogService callLogService,
AiRuntimeService runtimeService) {
this.providerService = providerService;
this.endpointConfigService = endpointConfigService;
this.sceneBindingService = sceneBindingService;
this.callLogService = callLogService;
this.runtimeService = runtimeService;
}
@Operation(summary = "查询 Provider 列表", description = "查询所有可见的 AI Provider 列表。")
@GetMapping("/providers")
public Result<List<AiProvider>> providers() {
return Result.success(providerService.listVisible());
}
@Operation(summary = "创建 Provider", description = "创建一个新的 AI Provider 配置。")
@PostMapping("/providers")
public Result<AiProvider> createProvider(@RequestBody AiProvider provider) {
return Result.success(providerService.saveProvider(provider));
}
@Operation(summary = "更新 Provider", description = "更新已有的 AI Provider 配置。")
@PutMapping("/providers")
public Result<AiProvider> updateProvider(@RequestBody AiProvider provider) {
return Result.success(providerService.updateProvider(provider));
}
@Operation(summary = "删除 Provider", description = "根据 ID 删除指定的 AI Provider 配置。")
@DeleteMapping("/providers")
public Result<Void> deleteProvider(@Parameter(description = "Provider ID", required = true) @RequestParam String id) {
providerService.removeById(id);
return Result.success();
}
@Operation(summary = "查询 Endpoint 列表", description = "查询所有可见的 AI Endpoint 配置列表。")
@GetMapping("/endpoints")
public Result<List<AiEndpointConfig>> endpoints() {
return Result.success(endpointConfigService.listVisible());
}
@Operation(summary = "获取 Endpoint 测试模板", description = "根据 Endpoint ID 获取对应的测试模板。")
@GetMapping("/endpoints/test-template")
public Result<AiTestTemplateResponse> endpointTestTemplate(@Parameter(description = "Endpoint ID", required = true) @RequestParam String id) {
return Result.success(runtimeService.buildEndpointTestTemplate(id));
}
@Operation(summary = "创建 Endpoint", description = "创建一个新的 AI Endpoint 配置。")
@PostMapping("/endpoints")
public Result<AiEndpointConfig> createEndpoint(@RequestBody AiEndpointConfig endpoint) {
return Result.success(endpointConfigService.saveEndpoint(endpoint));
}
@Operation(summary = "更新 Endpoint", description = "更新已有的 AI Endpoint 配置。")
@PutMapping("/endpoints")
public Result<AiEndpointConfig> updateEndpoint(@RequestBody AiEndpointConfig endpoint) {
return Result.success(endpointConfigService.updateEndpoint(endpoint));
}
@Operation(summary = "删除 Endpoint", description = "根据 ID 删除指定的 AI Endpoint 配置。")
@DeleteMapping("/endpoints")
public Result<Void> deleteEndpoint(@Parameter(description = "Endpoint ID", required = true) @RequestParam String id) {
endpointConfigService.removeById(id);
return Result.success();
}
@Operation(summary = "查询 Scene 列表", description = "查询所有可见的 AI Scene 绑定配置列表。")
@GetMapping("/scenes")
public Result<List<AiSceneBinding>> scenes() {
return Result.success(sceneBindingService.listVisible());
}
@Operation(summary = "获取 Scene 测试模板", description = "根据场景编码获取对应的测试模板。")
@GetMapping("/scenes/test-template")
public Result<AiTestTemplateResponse> sceneTestTemplate(@Parameter(description = "场景编码", required = true) @RequestParam String sceneCode) {
return Result.success(runtimeService.buildSceneTestTemplate(sceneCode));
}
@Operation(summary = "创建 Scene", description = "创建一个新的 AI Scene 绑定配置。")
@PostMapping("/scenes")
public Result<AiSceneBinding> createScene(@RequestBody AiSceneBinding scene) {
if (scene.getIsEnabled() == null) {
scene.setIsEnabled(1);
}
if (scene.getRequiredStream() == null) {
scene.setRequiredStream(1);
}
sceneBindingService.save(scene);
return Result.success(scene);
}
@Operation(summary = "更新 Scene", description = "更新已有的 AI Scene 绑定配置。")
@PutMapping("/scenes")
public Result<AiSceneBinding> updateScene(@RequestBody AiSceneBinding scene) {
sceneBindingService.updateById(scene);
return Result.success(sceneBindingService.getById(scene.getId()));
}
@Operation(summary = "删除 Scene", description = "根据 ID 删除指定的 AI Scene 绑定配置。")
@DeleteMapping("/scenes")
public Result<Void> deleteScene(@Parameter(description = "Scene ID", required = true) @RequestParam String id) {
sceneBindingService.removeById(id);
return Result.success();
}
@Operation(summary = "查询调用日志", description = "查询 AI 调用日志列表,支持限制返回数量。")
@GetMapping("/call-logs")
public Result<List<AiCallLog>> callLogs(@Parameter(description = "返回数量限制") @RequestParam(required = false) Integer limit) {
return Result.success(callLogService.latest(limit));
}
@Operation(summary = "分页查询调用日志", description = "分页查询 AI 调用日志,支持多条件筛选和关键词搜索。")
@PostMapping("/call-logs")
public Result<PageResult<AiCallLog>> queryCallLogs(@RequestBody @Valid AiCallLogQueryRequest request) {
return Result.success(callLogService.query(request));
}
@Operation(summary = "查询运行时调用结果", description = "用户端根据 requestId 查询刚刚触发的 AI 调用结果,用于流式连接异常后的结果恢复。")
@GetMapping("/runtime/result")
public Result<AiCallLog> runtimeResult(@RequestParam String requestId) {
AiCallLog log = callLogService.findByRequestId(requestId, UserContextHolder.getCurrentUserId());
if (log == null) {
return Result.notFound("AI 调用结果未生成");
}
return Result.success(log);
}
@Operation(summary = "运行时测试", description = "对指定的 AI 配置进行运行时连通性测试,支持同步和流式模式。")
@PostMapping("/runtime/test")
public Result<AiRuntimeTestResponse> runtimeTest(@RequestBody JSONObject payload) {
AiRuntimeRequest request = withCurrentUser(AiRuntimeRequest.fromPayload(payload));
return Result.success(runtimeService.test(request));
}
@Operation(summary = "流式运行时测试", description = "对指定的 AI 配置进行流式运行时连通性测试,以 SSE 事件流返回结果。")
@PostMapping("/runtime/stream")
public SseEmitter runtimeStream(@RequestBody JSONObject payload) {
AiRuntimeRequest request = withCurrentUser(AiRuntimeRequest.fromPayload(payload));
SseEmitter emitter = new SseEmitter(0L);
CompletableFuture.runAsync(() -> {
runtimeService.invokeStream(request, event -> sendEvent(emitter, event));
emitter.complete();
}).exceptionally(error -> {
sendEvent(emitter, AiStreamEvent.error("AI_STREAM_INTERRUPTED", error.getMessage()));
emitter.completeWithError(error);
return null;
});
return emitter;
}
@Operation(summary = "Endpoint 运行时测试", description = "对指定的 Endpoint 进行运行时连通性测试。")
@PostMapping("/endpoint/test")
public Result<AiRuntimeTestResponse> endpointTest(@RequestBody JSONObject payload) {
String endpointId = payload.getString("endpointId");
JSONObject inputs = payload.getJSONObject("inputs");
Map<String, Object> inputMap = inputs == null ? Map.of() : inputs;
return Result.success(runtimeService.testEndpoint(endpointId, inputMap));
}
@Operation(summary = "Endpoint 流式测试", description = "对指定的 Endpoint 进行流式运行时测试,以 SSE 事件流返回结果。")
@PostMapping("/endpoint/stream")
public SseEmitter endpointStream(@RequestBody JSONObject payload) {
String endpointId = payload.getString("endpointId");
JSONObject inputs = payload.getJSONObject("inputs");
Map<String, Object> inputMap = inputs == null ? Map.of() : inputs;
SseEmitter emitter = new SseEmitter(0L);
CompletableFuture.runAsync(() -> {
runtimeService.invokeEndpointStream(endpointId, inputMap, event -> sendEvent(emitter, event));
emitter.complete();
}).exceptionally(error -> {
sendEvent(emitter, AiStreamEvent.error("AI_ENDPOINT_TEST_INTERRUPTED", error.getMessage()));
emitter.completeWithError(error);
return null;
});
return emitter;
}
private AiRuntimeRequest withCurrentUser(AiRuntimeRequest request) {
request.setUserId(UserContextHolder.getCurrentUserId());
request.setUserName(UserContextHolder.getCurrentUsername());
request.setUserType(UserContextHolder.getCurrentUserType());
if (!org.springframework.util.StringUtils.hasText(request.getRequestId())) {
request.setRequestId(UserContextHolder.getRequestId());
}
return request;
}
private void sendEvent(SseEmitter emitter, AiStreamEvent event) {
try {
emitter.send(SseEmitter.event()
.name(event.getType())
.data(event));
} catch (IOException e) {
log.warn("AI stream client disconnected: {}", e.getMessage());
throw new IllegalStateException("AI_STREAM_CLIENT_DISCONNECTED", e);
}
}
}
@@ -0,0 +1,58 @@
package com.emotion.controller;
import com.emotion.common.Result;
import com.emotion.dto.request.analytics.AnalyticsEventBatchRequest;
import com.emotion.dto.response.analytics.AnalyticsBatchResponse;
import com.emotion.service.AnalyticsService;
import com.emotion.util.JwtUtil;
import com.emotion.util.UserContextHolder;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.StringUtils;
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;
import javax.servlet.http.HttpServletRequest;
import javax.validation.Valid;
@RestController
@RequestMapping("/analytics")
@Tag(name = "事件分析", description = "前端行为事件上报和分析接口")
public class AnalyticsController {
@Autowired
private AnalyticsService analyticsService;
@Autowired
private JwtUtil jwtUtil;
@PostMapping("/events/batch")
public Result<AnalyticsBatchResponse> batch(@Valid @RequestBody AnalyticsEventBatchRequest request,
HttpServletRequest servletRequest) {
bindOptionalUser(servletRequest);
try {
return Result.success(analyticsService.ingestBatch(request));
} finally {
UserContextHolder.clear();
}
}
private void bindOptionalUser(HttpServletRequest request) {
String authHeader = request.getHeader("Authorization");
if (!StringUtils.hasText(authHeader) || !authHeader.startsWith("Bearer ")) {
return;
}
String token = authHeader.substring(7);
if (!jwtUtil.validateToken(token)) {
return;
}
UserContextHolder.setCurrentUserId(jwtUtil.getUserIdFromToken(token));
UserContextHolder.setCurrentUsername(jwtUtil.getUsernameFromToken(token));
UserContextHolder.setCurrentToken(token);
}
}
@@ -0,0 +1,65 @@
package com.emotion.controller;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.emotion.common.PageResult;
import com.emotion.common.Result;
import com.emotion.dto.request.ApiEndpointListRequest;
import com.emotion.dto.response.ApiEndpointDetailResponse;
import com.emotion.dto.response.ApiEndpointItemResponse;
import com.emotion.service.ApiEndpointService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import java.util.HashMap;
import java.util.Map;
/**
* 接口端点控制器
*
* @author Peanut
* @date 2026-05-23
*/
@RestController
@RequestMapping("/admin/endpoint")
@Tag(name = "接口管理", description = "接口发现、同步和测试")
public class ApiEndpointController {
@Autowired
private ApiEndpointService apiEndpointService;
@Operation(summary = "分页查询接口列表", description = "支持关键词、方法、标签过滤")
@PostMapping("/list")
public Result<PageResult<ApiEndpointItemResponse>> list(@Valid @RequestBody ApiEndpointListRequest request) {
IPage<ApiEndpointItemResponse> page = apiEndpointService.getPage(request);
PageResult<ApiEndpointItemResponse> result = new PageResult<>(page);
return Result.success(result);
}
@Operation(summary = "查询接口详情", description = "返回接口详情和参数列表")
@GetMapping("/detail")
public Result<ApiEndpointDetailResponse> detail(@RequestParam String operationId) {
ApiEndpointDetailResponse response = apiEndpointService.getDetail(operationId);
if (response == null) {
return Result.notFound("接口不存在");
}
return Result.success(response);
}
@Operation(summary = "手动触发同步", description = "异步执行,返回 taskId")
@PostMapping("/sync")
public Result<Map<String, String>> sync() {
Map<String, String> result = new HashMap<>();
result.put("taskId", "sync-" + System.currentTimeMillis());
new Thread(() -> {
try {
apiEndpointService.syncFromOpenApi();
} catch (Exception e) {
// 异常已在 service 层记录
}
}).start();
return Result.success("同步任务已提交", result);
}
}
@@ -0,0 +1,141 @@
package com.emotion.controller;
import com.emotion.common.Result;
import com.emotion.dto.request.ApiTestProxyRequest;
import com.emotion.dto.response.ApiTestProxyResponse;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.*;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.client.HttpClientErrorException;
import org.springframework.web.client.HttpServerErrorException;
import org.springframework.web.client.ResourceAccessException;
import org.springframework.web.client.RestTemplate;
import javax.validation.Valid;
import java.net.SocketTimeoutException;
import java.util.HashMap;
import java.util.Map;
/**
* 接口代理测试控制器
* 仅允许转发到 /api/* 路径,避免 SSRF
*
* @author Peanut
* @date 2026-05-23
*/
@RestController
@RequestMapping("/admin/endpoint")
@Tag(name = "接口管理", description = "接口测试代理")
public class ApiTestProxyController {
private static final Logger log = LoggerFactory.getLogger(ApiTestProxyController.class);
private static final int DEFAULT_TIMEOUT = 30;
private static final int MAX_RAW_BODY_LENGTH = 2000;
@Value("${server.port:19089}")
private int serverPort;
@Autowired
private RestTemplate restTemplate;
@Autowired
private ObjectMapper objectMapper;
@Operation(summary = "代理测试请求", description = "转发请求到本地后端并返回响应")
@PostMapping("/test")
public Result<ApiTestProxyResponse> test(@Valid @RequestBody ApiTestProxyRequest request) {
if (!request.getPath().startsWith("/api/")) {
return Result.error("仅允许代理 /api/* 路径的请求");
}
String url = "http://127.0.0.1:" + serverPort + request.getPath();
int timeout = request.getTimeoutSeconds() != null ? request.getTimeoutSeconds() : DEFAULT_TIMEOUT;
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
if (request.getHeaders() != null) {
for (Map.Entry<String, String> entry : request.getHeaders().entrySet()) {
headers.set(entry.getKey(), entry.getValue());
}
}
// Append query params to URL if present
if (request.getParams() != null && !request.getParams().isEmpty()) {
StringBuilder sb = new StringBuilder(url);
sb.append("?");
for (Map.Entry<String, String> entry : request.getParams().entrySet()) {
sb.append(entry.getKey()).append("=").append(entry.getValue()).append("&");
}
url = sb.substring(0, sb.length() - 1);
}
Object body = null;
if (request.getBody() != null && !request.getBody().isBlank()) {
try {
body = objectMapper.readValue(request.getBody(), Object.class);
} catch (Exception e) {
return Result.error("请求体 JSON 格式错误: " + e.getMessage());
}
}
HttpMethod httpMethod = HttpMethod.valueOf(request.getMethod().toUpperCase());
HttpEntity<Object> entity = new HttpEntity<>(body, headers);
long startTime = System.currentTimeMillis();
ApiTestProxyResponse response = new ApiTestProxyResponse();
try {
ResponseEntity<String> rawResponse = restTemplate.exchange(url, httpMethod, entity, String.class);
long duration = System.currentTimeMillis() - startTime;
response.setStatus(rawResponse.getStatusCodeValue());
response.setDuration(duration);
try {
response.setBody(objectMapper.readValue(rawResponse.getBody(), Object.class));
} catch (Exception e) {
String rawBody = rawResponse.getBody();
if (rawBody != null && rawBody.length() > MAX_RAW_BODY_LENGTH) {
rawBody = rawBody.substring(0, MAX_RAW_BODY_LENGTH) + "\n... (已截断)";
}
if (rawBody != null) {
rawBody = rawBody.replace("<", "&lt;").replace(">", "&gt;");
}
response.setRawBody(rawBody);
}
Map<String, String> respHeaders = new HashMap<>();
for (String key : rawResponse.getHeaders().keySet()) {
respHeaders.put(key, rawResponse.getHeaders().getFirst(key));
}
response.setHeaders(respHeaders);
return Result.success(response);
} catch (ResourceAccessException e) {
long duration = System.currentTimeMillis() - startTime;
if (e.getCause() instanceof SocketTimeoutException) {
return Result.error("代理请求超时(" + timeout + "s),目标接口可能响应过慢或不可达");
}
return Result.error("代理请求失败: " + e.getMessage());
} catch (HttpClientErrorException | HttpServerErrorException e) {
long duration = System.currentTimeMillis() - startTime;
response.setStatus(e.getStatusCode().value());
response.setDuration(duration);
String rawBody = e.getResponseBodyAsString();
if (rawBody.length() > MAX_RAW_BODY_LENGTH) {
rawBody = rawBody.substring(0, MAX_RAW_BODY_LENGTH) + "\n... (已截断)";
}
response.setRawBody(rawBody.replace("<", "&lt;").replace(">", "&gt;"));
return Result.success(response);
} catch (Exception e) {
return Result.error("代理请求失败: " + e.getMessage());
}
}
}
@@ -0,0 +1,41 @@
package com.emotion.controller;
import com.emotion.common.Result;
import com.emotion.dto.response.asr.AsrTranscribeResponse;
import com.emotion.service.AsrService;
import com.emotion.util.UserContextHolder;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
@RestController
@RequestMapping("/asr")
@Tag(name = "语音识别(ASR)", description = "语音转文字识别接口")
public class AsrController {
private final AsrService asrService;
public AsrController(AsrService asrService) {
this.asrService = asrService;
}
@Operation(summary = "语音转文字", description = "上传音频文件并将其转换为文字内容。")
@PostMapping("/transcribe")
public Result<AsrTranscribeResponse> transcribe(@Parameter(description = "音频文件") @RequestPart("file") MultipartFile file) {
if (UserContextHolder.getCurrentUserId() == null) {
return Result.unauthorized();
}
try {
return Result.success(asrService.transcribe(file));
} catch (IllegalArgumentException e) {
return Result.badRequest(e.getMessage());
} catch (IllegalStateException e) {
return Result.error(e.getMessage());
}
}
}
@@ -0,0 +1,203 @@
package com.emotion.controller;
import com.emotion.common.Result;
import com.emotion.dto.request.LoginRequest;
import com.emotion.dto.request.RegisterRequest;
import com.emotion.dto.request.RefreshTokenRequest;
import com.emotion.dto.request.ResetPasswordRequest;
import com.emotion.dto.request.WechatLoginRequest;
import com.emotion.dto.response.ResetPasswordResponse;
import com.emotion.dto.response.AuthResponse;
import com.emotion.dto.response.CaptchaResponse;
import com.emotion.dto.response.LoginConfigResponse;
import com.emotion.dto.response.SmsCodeResponse;
import com.emotion.dto.response.UserInfoResponse;
import com.emotion.service.AuthService;
import com.emotion.service.SystemConfigService;
import com.emotion.service.TokenService;
import com.emotion.util.UserContextUtils;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
import javax.validation.Valid;
/**
* 认证控制器
*
* @author huazhongmin
* @date 2025-07-23
*/
@RestController
@RequestMapping("/auth")
@Tag(name = "认证管理", description = "用户注册、登录、验证码等认证相关接口")
public class AuthController {
@Autowired
private AuthService authService;
@Autowired
private TokenService tokenService;
@Autowired
private SystemConfigService systemConfigService;
/**
* 用户登录(简化版:手机号+验证码,不存在则自动注册)
*/
@PostMapping("/login")
@Operation(summary = "用户登录", description = "使用手机号和短信验证码登录,若用户不存在则自动注册")
public Result<AuthResponse> login(@Valid @RequestBody LoginRequest request) {
AuthResponse response = authService.login(request);
return Result.success("登录成功", response);
}
/**
* 用户注册(简化版:仅需手机号、密码和短信验证码)
*/
@PostMapping("/wechat/login")
@Operation(summary = "微信小程序登录", description = "使用微信小程序登录 code 换取 openid 并登录")
public Result<AuthResponse> wechatLogin(@Valid @RequestBody WechatLoginRequest request) {
AuthResponse response = authService.wechatLogin(request);
return Result.success("登录成功", response);
}
/**
* 获取登录方式配置
*/
@GetMapping("/loginConfig")
@Operation(summary = "获取登录方式配置", description = "返回当前启用的登录方式,供小程序登录页动态渲染")
public Result<LoginConfigResponse> getLoginConfig() {
LoginConfigResponse response = systemConfigService.getLoginConfig();
return Result.success(response);
}
@PostMapping(value = "/register")
@Operation(summary = "用户注册", description = "使用手机号、密码和短信验证码进行注册")
public Result<AuthResponse> register(@Valid @RequestBody RegisterRequest request) {
AuthResponse response = authService.register(request);
return Result.success("注册成功", response);
}
/**
* 重置密码(手机号 + 验证码)
*/
@PostMapping(value = "/resetPassword")
@Operation(summary = "重置密码", description = "通过手机号和验证码重置密码,验证码本期固定为123456")
public Result<ResetPasswordResponse> resetPassword(@Valid @RequestBody ResetPasswordRequest request) {
ResetPasswordResponse response = authService.resetPassword(request);
return Result.success("重置密码成功", response);
}
/**
* 获取当前用户信息
*/
@GetMapping("/userInfo")
@Operation(summary = "获取当前用户信息", description = "根据当前登录用户的Token获取用户详细信息")
public Result<UserInfoResponse> getCurrentUserInfo(HttpServletRequest request) {
UserInfoResponse userInfo = tokenService.getUserInfoByToken(request);
return Result.success(userInfo);
}
/**
* 生成验证码(图形验证码,用于登录)
*/
@GetMapping("/captcha")
@Operation(summary = "获取图形验证码", description = "用于登录时的图形验证码")
public Result<CaptchaResponse> generateCaptcha() {
CaptchaResponse response = authService.generateCaptcha();
return Result.success(response);
}
/**
* 获取短信验证码(用于注册)
*/
@GetMapping("/sms-code")
@Operation(summary = "获取短信验证码", description = "用于注册时的短信验证码")
public Result<SmsCodeResponse> getSmsCode(
@Parameter(description = "手机号", required = true)
@RequestParam String phone) {
SmsCodeResponse response = authService.sendSmsCode(phone);
return Result.success("验证码已发送", response);
}
/**
* 用户登出
*/
@PostMapping("/logout")
@Operation(summary = "用户登出", description = "退出登录,清除服务端Token")
public Result<Void> logout(HttpServletRequest request) {
authService.logoutByToken(request);
return Result.success();
}
/**
* 刷新访问令牌
*/
@PostMapping("/refreshToken")
@Operation(summary = "刷新访问令牌", description = "使用刷新令牌获取新的访问令牌和刷新令牌")
public Result<AuthResponse> refreshToken(@Valid @RequestBody RefreshTokenRequest request) {
AuthResponse response = authService.refreshToken(request.getRefreshToken());
return Result.success("令牌刷新成功", response);
}
/**
* 验证访问令牌
*/
@GetMapping("/validateToken")
@Operation(summary = "验证访问令牌", description = "验证当前Token是否有效")
public Result<Boolean> validateToken(HttpServletRequest request) {
boolean isValid = authService.validateToken(request);
return Result.success(isValid);
}
/**
* 获取用户名(通过令牌)
*/
@GetMapping("/username")
@Operation(summary = "获取用户名", description = "根据Token获取当前登录用户的用户名")
public Result<String> getUsernameFromToken(HttpServletRequest request) {
String username = tokenService.getUsernameByToken(request);
return Result.success(username);
}
/**
* 检查账号是否存在
*/
@GetMapping("/checkAccount")
@Operation(summary = "检查账号是否存在", description = "检查指定账号是否已被注册")
public Result<Boolean> checkAccount(
@Parameter(description = "账号(手机号/邮箱/用户名)", required = true)
@RequestParam String account) {
boolean exists = authService.existsByAccount(account);
return Result.success(exists);
}
/**
* 检查邮箱是否存在
*/
@GetMapping("/checkEmail")
@Operation(summary = "检查邮箱是否存在", description = "检查指定邮箱是否已被注册")
public Result<Boolean> checkEmail(
@Parameter(description = "邮箱地址", required = true)
@RequestParam String email) {
boolean exists = authService.existsByEmail(email);
return Result.success(exists);
}
/**
* 检查手机号是否存在
*/
@GetMapping("/checkPhone")
@Operation(summary = "检查手机号是否存在", description = "检查指定手机号是否已被注册")
public Result<Boolean> checkPhone(
@Parameter(description = "手机号码", required = true)
@RequestParam String phone) {
boolean exists = authService.existsByPhone(phone);
return Result.success(exists);
}
}
@@ -0,0 +1,91 @@
package com.emotion.controller;
import com.emotion.dto.request.WebSocketRequest;
import com.emotion.dto.websocket.ConnectRequest;
import com.emotion.service.WebSocketService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.handler.annotation.Payload;
import org.springframework.messaging.simp.SimpMessageHeaderAccessor;
import org.springframework.stereotype.Controller;
import javax.validation.Valid;
import java.security.Principal;
/**
* WebSocket聊天控制器
* 使用规范的请求对象封装参数,符合项目开发规则
*
* @author huazhongmin
* @date 2025-09-08
*/
@Slf4j
@Controller
@MessageMapping("/chat")
@Tag(name = "WebSocket 聊天", description = "基于 WebSocket 的实时 AI 聊天通信接口")
public class ChatWebSocketController {
@Autowired
private WebSocketService webSocketService;
/**
* 处理聊天消息
*
* @param webSocketRequest WebSocket请求对象
* @param headerAccessor 消息头访问器
* @param principal 用户主体
*/
@Operation(summary = "发送聊天消息", description = "向 AI 发送聊天消息,触发流式响应。")
@MessageMapping("/send")
public void handleChatMessage(@Valid @Payload WebSocketRequest webSocketRequest,
SimpMessageHeaderAccessor headerAccessor,
Principal principal) {
String sessionId = headerAccessor.getSessionId();
webSocketService.handleChatMessage(webSocketRequest, sessionId, principal);
}
/**
* 处理用户连接
*
* @param connectRequest 连接请求对象
* @param headerAccessor 消息头访问器
* @param principal 用户主体
*/
@Operation(summary = "用户连接", description = "处理用户 WebSocket 连接建立。")
@MessageMapping("/connect")
public void connectUser(@Payload ConnectRequest connectRequest,
SimpMessageHeaderAccessor headerAccessor,
Principal principal) {
String sessionId = headerAccessor.getSessionId();
webSocketService.handleUserConnect(connectRequest, sessionId, principal);
}
/**
* 处理用户断开连接
*
* @param headerAccessor 消息头访问器
* @param principal 用户主体
*/
@Operation(summary = "用户断开连接", description = "处理用户 WebSocket 连接断开。")
@MessageMapping("/disconnect")
public void disconnectUser(SimpMessageHeaderAccessor headerAccessor, Principal principal) {
String sessionId = headerAccessor.getSessionId();
webSocketService.handleUserDisconnect(sessionId, principal);
}
/**
* 处理心跳消息
*
* @param headerAccessor 消息头访问器
* @param principal 用户主体
*/
@Operation(summary = "心跳检测", description = "处理客户端心跳消息,保持 WebSocket 连接活跃。")
@MessageMapping("/heartbeat")
public void heartbeat(SimpMessageHeaderAccessor headerAccessor, Principal principal) {
String sessionId = headerAccessor.getSessionId();
webSocketService.handleHeartbeat(sessionId, principal);
}
}
@@ -0,0 +1,92 @@
package com.emotion.controller;
import com.emotion.common.PageResult;
import com.emotion.common.Result;
import com.emotion.dto.request.comment.CommentCreateRequest;
import com.emotion.dto.request.comment.CommentUpdateRequest;
import com.emotion.dto.request.comment.CommentPageRequest;
import com.emotion.dto.response.comment.CommentResponse;
import com.emotion.service.CommentService;
import com.emotion.util.UserContextUtils;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
/**
* 评论控制器
*
* @author huazhongmin
* @date 2025-07-23
*/
@RestController
@RequestMapping("/comment")
@Tag(name = "评论管理", description = "评论的查询、创建、更新和删除接口")
public class CommentController {
@Autowired
private CommentService commentService;
/**
* 分页查询评论
*/
@Operation(summary = "分页查询评论", description = "根据条件分页查询评论列表。")
@GetMapping(value = "/page")
public Result<PageResult<CommentResponse>> getCommentPage(@Validated CommentPageRequest request) {
PageResult<CommentResponse> pageResult = commentService.getPage(request);
return Result.success(pageResult);
}
/**
* 创建评论
*/
@Operation(summary = "创建评论", description = "对指定目标发表一条新评论。")
@PostMapping(value = "/create")
public Result<CommentResponse> createComment(@RequestBody @Validated CommentCreateRequest request) {
// 从上下文中获取当前用户ID,而不是直接使用请求中的用户ID
String currentUserId = UserContextUtils.getCurrentUserId();
if (currentUserId != null) {
request.setUserId(currentUserId);
}
CommentResponse response = commentService.create(request);
return Result.success(response);
}
/**
* 更新评论
*/
@Operation(summary = "更新评论", description = "修改已有评论的内容。")
@PutMapping(value = "/update")
public Result<CommentResponse> updateComment(@RequestBody @Validated CommentUpdateRequest request) {
CommentResponse response = commentService.update(request);
return Result.success(response);
}
/**
* 删除评论
*/
@Operation(summary = "删除评论", description = "删除指定的评论。")
@DeleteMapping(value = "/delete")
public Result<Void> deleteComment(@Parameter(description = "评论 ID", required = true) @RequestParam String id) {
boolean deleted = commentService.delete(id);
if (!deleted) {
return Result.error("删除失败");
}
return Result.success();
}
/**
* 根据ID获取评论
*/
@Operation(summary = "获取评论详情", description = "根据 ID 获取评论的详细信息。")
@GetMapping(value = "/detail")
public Result<CommentResponse> getCommentById(@Parameter(description = "评论 ID", required = true) @RequestParam String id) {
CommentResponse response = commentService.getById(id);
if (response == null) {
return Result.notFound("评论不存在");
}
return Result.success(response);
}
}
@@ -0,0 +1,86 @@
package com.emotion.controller;
import com.emotion.common.PageResult;
import com.emotion.common.Result;
import com.emotion.dto.request.community.CommunityPostCreateRequest;
import com.emotion.dto.request.community.CommunityPostUpdateRequest;
import com.emotion.dto.request.community.CommunityPostPageRequest;
import com.emotion.dto.response.community.CommunityPostResponse;
import com.emotion.service.CommunityPostService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
/**
* 社区帖子控制器
*
* @author huazhongmin
* @date 2025-07-23
*/
@RestController
@RequestMapping("/communityPost")
@Tag(name = "社区帖子管理", description = "社区帖子的查询、创建、更新和删除接口")
public class CommunityPostController {
@Autowired
private CommunityPostService communityPostService;
/**
* 分页查询帖子
*/
@Operation(summary = "分页查询社区帖子", description = "根据条件分页查询社区帖子列表。")
@GetMapping(value = "/page")
public Result<PageResult<CommunityPostResponse>> getPage(@Validated CommunityPostPageRequest request) {
PageResult<CommunityPostResponse> pageResult = communityPostService.getPage(request);
return Result.success(pageResult);
}
/**
* 根据ID获取帖子
*/
@Operation(summary = "获取帖子详情", description = "根据 ID 获取社区帖子的详细信息。")
@GetMapping(value = "/detail")
public Result<CommunityPostResponse> getById(@Parameter(description = "帖子 ID", required = true) @RequestParam String id) {
CommunityPostResponse response = communityPostService.getById(id);
if (response == null) {
return Result.notFound("帖子不存在");
}
return Result.success(response);
}
/**
* 创建帖子
*/
@Operation(summary = "创建社区帖子", description = "发布一篇新的社区帖子。")
@PostMapping(value = "/create")
public Result<CommunityPostResponse> create(@RequestBody @Validated CommunityPostCreateRequest request) {
CommunityPostResponse response = communityPostService.create(request);
return Result.success(response);
}
/**
* 更新帖子
*/
@Operation(summary = "更新社区帖子", description = "修改已有的社区帖子内容。")
@PutMapping(value = "/update")
public Result<CommunityPostResponse> update(@RequestBody @Validated CommunityPostUpdateRequest request) {
CommunityPostResponse response = communityPostService.update(request);
return Result.success(response);
}
/**
* 删除帖子
*/
@Operation(summary = "删除社区帖子", description = "删除指定的社区帖子。")
@DeleteMapping(value = "/delete")
public Result<Void> delete(@Parameter(description = "帖子 ID", required = true) @RequestParam String id) {
boolean deleted = communityPostService.delete(id);
if (!deleted) {
return Result.error("删除失败");
}
return Result.success();
}
}
@@ -0,0 +1,124 @@
package com.emotion.controller;
import com.emotion.common.PageResult;
import com.emotion.common.Result;
import com.emotion.dto.request.ConversationCreateRequest;
import com.emotion.dto.request.ConversationPageRequest;
import com.emotion.dto.response.ConversationResponse;
import com.emotion.service.ConversationService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
import javax.validation.Valid;
import java.util.List;
/**
* 对话控制器
*
* @author huazhongmin
* @date 2025-07-23
*/
@RestController
@RequestMapping("/conversation")
@Tag(name = "会话管理", description = "AI 对话会话的查询、创建、更新、删除和状态管理接口")
public class ConversationController {
@Autowired
private ConversationService conversationService;
/**
* 分页查询对话
*/
@Operation(summary = "分页查询会话", description = "分页查询当前用户的对话会话列表。")
@GetMapping(value = "/page")
public Result<PageResult<ConversationResponse>> getPage(@Valid ConversationPageRequest request) {
PageResult<ConversationResponse> pageResult = conversationService.getPageWithResponse(request);
return Result.success(pageResult);
}
/**
* 根据ID获取对话
*/
@Operation(summary = "获取会话详情", description = "根据 ID 获取会话的详细信息。")
@GetMapping(value = "/detail")
public Result<ConversationResponse> getById(@Parameter(description = "会话 ID") @RequestParam String id) {
ConversationResponse response = conversationService.getConversationResponseById(id);
if (response == null) {
return Result.notFound("对话不存在");
}
return Result.success(response);
}
/**
* 创建对话
*/
@Operation(summary = "创建会话", description = "创建一个新的 AI 对话会话。")
@PostMapping(value = "/create")
public Result<ConversationResponse> create(@Valid @RequestBody ConversationCreateRequest request,
HttpServletRequest httpRequest) {
ConversationResponse response = conversationService.createConversationWithResponse(request, httpRequest);
return Result.success(response);
}
/**
* 更新对话
*/
@Operation(summary = "更新会话", description = "修改已有会话的标题等属性。")
@PutMapping(value = "/update")
public Result<ConversationResponse> update(@RequestBody ConversationCreateRequest request) {
ConversationResponse response = conversationService.updateConversationWithResponse(request);
return Result.success(response);
}
/**
* 删除对话
*/
@Operation(summary = "删除会话", description = "删除指定的对话会话。")
@DeleteMapping(value = "/delete")
public Result<Void> delete(@Parameter(description = "会话 ID") @RequestParam String id) {
boolean deleted = conversationService.removeById(id);
if (!deleted) {
return Result.error("删除失败");
}
return Result.success();
}
/**
* 获取活跃对话
*/
@Operation(summary = "获取活跃会话", description = "获取当前用户最近的活跃会话列表。")
@GetMapping(value = "/active")
public Result<List<ConversationResponse>> getActiveConversations() {
List<ConversationResponse> responses = conversationService.getActiveConversationsWithResponse();
return Result.success(responses);
}
/**
* 获取归档对话
*/
@Operation(summary = "获取归档会话", description = "获取当前用户已归档的会话列表。")
@GetMapping(value = "/archived")
public Result<List<ConversationResponse>> getArchivedConversations() {
List<ConversationResponse> responses = conversationService.getArchivedConversationsWithResponse();
return Result.success(responses);
}
/**
* 更新对话状态
*/
@Operation(summary = "获取会话状态", description = "获取指定会话的当前状态信息。")
@PutMapping(value = "/status")
public Result<Void> updateConversationStatus(
@Parameter(description = "会话 ID") @RequestParam String id,
@RequestParam String status) {
boolean updated = conversationService.updateConversationStatus(id, status);
if (!updated) {
return Result.error("更新状态失败");
}
return Result.success();
}
}
@@ -0,0 +1,141 @@
package com.emotion.controller;
import com.emotion.common.PageResult;
import com.emotion.common.Result;
import com.emotion.dto.request.coze.CozeApiCallPageRequest;
import com.emotion.dto.request.coze.CozeApiCallCreateRequest;
import com.emotion.dto.request.coze.CozeApiCallUpdateRequest;
import com.emotion.dto.response.coze.CozeApiCallResponse;
import com.emotion.service.CozeApiCallService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.math.BigDecimal;
/**
* Coze API调用记录控制器
*
* @author huazhongmin
* @date 2025-07-23
*/
@RestController
@RequestMapping("/coze-api-call")
@Tag(name = "Coze API 调用记录", description = "Coze API 调用记录的查询、创建、更新和删除接口")
public class CozeApiCallController {
@Autowired
private CozeApiCallService cozeApiCallService;
/**
* 分页查询API调用记录
*/
@Operation(summary = "分页查询调用记录", description = "根据条件分页查询 Coze API 调用记录。")
@GetMapping(value = "/page")
public Result<PageResult<CozeApiCallResponse>> getCozeApiCallPage(@Validated CozeApiCallPageRequest request) {
PageResult<CozeApiCallResponse> pageResult = cozeApiCallService.getPage(request);
return Result.success(pageResult);
}
/**
* 根据ID获取API调用记录
*/
@Operation(summary = "获取调用记录详情", description = "根据 ID 获取单条调用记录的详细信息。")
@GetMapping(value = "/detail")
public Result<CozeApiCallResponse> getCozeApiCallById(@Parameter(description = "调用记录 ID", required = true) @RequestParam String id) {
CozeApiCallResponse response = cozeApiCallService.getById(id);
if (response == null) {
return Result.notFound("API调用记录不存在");
}
return Result.success(response);
}
/**
* 创建API调用记录
*/
@Operation(summary = "创建调用记录", description = "新增一条 Coze API 调用记录。")
@PostMapping(value = "/create")
public Result<CozeApiCallResponse> createCozeApiCall(@RequestBody @Validated CozeApiCallCreateRequest request) {
CozeApiCallResponse response = cozeApiCallService.create(request);
return Result.success(response);
}
/**
* 更新API调用记录
*/
@Operation(summary = "更新调用记录", description = "更新已有的 Coze API 调用记录。")
@PutMapping(value = "/update")
public Result<CozeApiCallResponse> updateCozeApiCall(@RequestBody @Validated CozeApiCallUpdateRequest request) {
CozeApiCallResponse response = cozeApiCallService.update(request);
if (response == null) {
return Result.error("更新失败,记录不存在");
}
return Result.success(response);
}
/**
* 删除API调用记录
*/
@Operation(summary = "删除调用记录", description = "删除指定的 Coze API 调用记录。")
@DeleteMapping(value = "/delete")
public Result<Void> deleteCozeApiCall(@Parameter(description = "调用记录 ID", required = true) @RequestParam String id) {
boolean deleted = cozeApiCallService.delete(id);
if (!deleted) {
return Result.error("删除失败");
}
return Result.success();
}
/**
* 统计用户的API调用次数
*/
@Operation(summary = "按用户统计调用量", description = "统计指定用户在时间范围内的 Coze API 调用次数。")
@GetMapping(value = "/countByUser")
public Result<Long> countByUserId(@Parameter(description = "用户 ID", required = true) @RequestParam String userId) {
Long count = cozeApiCallService.countByUserId(userId);
return Result.success(count);
}
/**
* 统计Bot的API调用次数
*/
@Operation(summary = "按 Bot 统计调用量", description = "统计指定 Bot 的 Coze API 调用次数。")
@GetMapping(value = "/countByBot")
public Result<Long> countByBotId(@Parameter(description = "Bot ID", required = true) @RequestParam String botId) {
Long count = cozeApiCallService.countByBotId(botId);
return Result.success(count);
}
/**
* 统计指定状态的API调用次数
*/
@Operation(summary = "按状态统计调用量", description = "按调用状态统计 Coze API 调用次数。")
@GetMapping(value = "/countByStatus")
public Result<Long> countByStatus(@RequestParam String status) {
Long count = cozeApiCallService.countByStatus(status);
return Result.success(count);
}
/**
* 统计用户的Token使用量
*/
@Operation(summary = "按用户统计 Token 消耗", description = "统计指定用户的 Token 总消耗量。")
@GetMapping(value = "/tokensByUser")
public Result<Long> sumTokensByUserId(@Parameter(description = "用户 ID", required = true) @RequestParam String userId) {
Long totalTokens = cozeApiCallService.sumTokensByUserId(userId);
return Result.success(totalTokens);
}
/**
* 统计用户的API调用费用
*/
@Operation(summary = "按用户统计费用", description = "统计指定用户的 API 调用总费用。")
@GetMapping(value = "/costByUser")
public Result<BigDecimal> sumCostByUserId(@Parameter(description = "用户 ID", required = true) @RequestParam String userId) {
BigDecimal totalCost = cozeApiCallService.sumCostByUserId(userId);
return Result.success(totalCost);
}
}
@@ -0,0 +1,199 @@
package com.emotion.controller;
import com.emotion.common.PageResult;
import com.emotion.common.Result;
import com.emotion.dto.request.DiaryCommentCreateRequest;
import com.emotion.dto.request.DiaryCommentPageRequest;
import com.emotion.dto.response.DiaryCommentResponse;
import com.emotion.service.DiaryCommentService;
import com.emotion.service.DiaryPostService;
import com.emotion.util.UserContextUtils;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import java.util.List;
/**
* 日记评论控制器
*
* @author huazhongmin
* @date 2025-07-23
*/
@RestController
@RequestMapping("/diaryComment")
@Tag(name = "日记评论管理", description = "日记评论的查询、创建、更新、删除、点赞、置顶等接口")
public class DiaryCommentController {
@Autowired
private DiaryCommentService diaryCommentService;
@Autowired
private DiaryPostService diaryPostService;
/**
* 分页查询评论
*/
@Operation(summary = "分页查询评论", description = "分页查询指定日记的评论列表。")
@GetMapping(value = "/page")
public Result<PageResult<DiaryCommentResponse>> getPage(@Validated DiaryCommentPageRequest request) {
PageResult<DiaryCommentResponse> pageResult = diaryCommentService.getPageWithResponse(request);
return Result.success(pageResult);
}
/**
* 获取评论树结构
*/
@Operation(summary = "获取评论树", description = "以树形结构获取日记的评论及其回复列表。")
@GetMapping(value = "/commentTree")
public Result<List<DiaryCommentResponse>> getCommentTree(@Parameter(description = "日记 ID") @RequestParam String diaryId) {
List<DiaryCommentResponse> responses = diaryCommentService.getCommentTreeWithResponse(diaryId);
return Result.success(responses);
}
/**
* 根据ID获取评论详情
*/
@Operation(summary = "获取评论详情", description = "根据 ID 获取评论详情。")
@GetMapping(value = "/detail")
public Result<DiaryCommentResponse> getById(@Parameter(description = "评论 ID") @RequestParam String id) {
DiaryCommentResponse response = diaryCommentService.getCommentResponseById(id);
if (response == null) {
return Result.notFound("评论不存在");
}
return Result.success(response);
}
/**
* 创建评论
*/
@Operation(summary = "创建评论", description = "对指定日记发表一条新评论或回复。")
@PostMapping(value = "/create")
public Result<DiaryCommentResponse> create(@Valid @RequestBody DiaryCommentCreateRequest request) {
// 从上下文中获取当前用户ID,而不是直接使用请求中的用户ID
String currentUserId = UserContextUtils.getCurrentUserId();
if (currentUserId != null) {
request.setUserId(currentUserId);
}
DiaryCommentResponse response = diaryCommentService.createCommentWithResponse(request);
// 更新日记的评论数
diaryPostService.incrementCommentCount(request.getDiaryId());
diaryPostService.updateLastCommentTime(request.getDiaryId());
return Result.success(response);
}
/**
* 更新评论
*/
@Operation(summary = "更新评论", description = "修改已有评论的内容。")
@PutMapping(value = "/update")
public Result<DiaryCommentResponse> update(@Valid @RequestBody DiaryCommentCreateRequest request) {
DiaryCommentResponse response = diaryCommentService.updateCommentWithResponse(request);
return Result.success(response);
}
/**
* 删除评论
*/
@Operation(summary = "删除评论", description = "永久删除指定评论。")
@DeleteMapping(value = "/delete")
public Result<Void> delete(@Parameter(description = "评论 ID") @RequestParam String id) {
boolean deleted = diaryCommentService.deleteComment(id);
if (!deleted) {
return Result.error("删除失败");
}
// 更新日记的评论数
DiaryCommentResponse response = diaryCommentService.getCommentResponseById(id);
if (response != null) {
diaryPostService.decrementCommentCount(response.getDiaryId());
}
return Result.success();
}
/**
* 软删除评论
*/
@Operation(summary = "软删除评论", description = "将评论标记为已删除状态。")
@DeleteMapping(value = "/softDelete")
public Result<Void> softDelete(@Parameter(description = "评论 ID") @RequestParam String id) {
boolean deleted = diaryCommentService.softDeleteComment(id);
if (!deleted) {
return Result.error("删除失败");
}
// 更新日记的评论数
DiaryCommentResponse response = diaryCommentService.getCommentResponseById(id);
if (response != null) {
diaryPostService.decrementCommentCount(response.getDiaryId());
}
return Result.success();
}
/**
* 恢复评论
*/
@Operation(summary = "恢复评论", description = "恢复已软删除的评论。")
@PutMapping(value = "/restore")
public Result<Void> restore(@Parameter(description = "评论 ID") @RequestParam String id) {
boolean restored = diaryCommentService.restoreComment(id);
if (!restored) {
return Result.error("恢复失败");
}
// 更新日记的评论数
DiaryCommentResponse response = diaryCommentService.getCommentResponseById(id);
if (response != null) {
diaryPostService.incrementCommentCount(response.getDiaryId());
}
return Result.success();
}
/**
* 点赞评论
*/
@Operation(summary = "点赞评论", description = "对指定评论进行点赞。")
@PostMapping(value = "/like")
public Result<Void> like(@Parameter(description = "评论 ID") @RequestParam String id) {
boolean liked = diaryCommentService.incrementLikeCount(id);
if (!liked) {
return Result.error("点赞失败");
}
return Result.success();
}
/**
* 取消点赞评论
*/
@Operation(summary = "取消点赞评论", description = "取消对指定评论的点赞。")
@DeleteMapping(value = "/unlike")
public Result<Void> unlike(@Parameter(description = "评论 ID") @RequestParam String id) {
boolean unliked = diaryCommentService.decrementLikeCount(id);
if (!unliked) {
return Result.error("取消点赞失败");
}
return Result.success();
}
/**
* 设置置顶状态
*/
@Operation(summary = "置顶评论", description = "将指定评论设为置顶显示。")
@PutMapping(value = "/setTop")
public Result<Void> setTop(@Parameter(description = "评论 ID") @RequestParam String id, @RequestParam Integer isTop) {
boolean set = diaryCommentService.setTop(id, isTop);
if (!set) {
return Result.error("设置置顶状态失败");
}
return Result.success();
}
}
@@ -0,0 +1,200 @@
package com.emotion.controller;
import com.emotion.common.PageResult;
import com.emotion.common.Result;
import com.emotion.dto.request.DiaryPostCreateRequest;
import com.emotion.dto.request.DiaryPostPageRequest;
import com.emotion.dto.request.DiaryPostUpdateRequest;
import com.emotion.dto.response.DiaryPostResponse;
import com.emotion.service.DiaryPostService;
import com.emotion.util.UserContextUtils;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
/**
* 用户日记控制器
*
* @author huazhongmin
* @date 2025-07-23
*/
@RestController
@RequestMapping("/diaryPost")
@Tag(name = "日记管理", description = "用户日记帖子的查询、创建、发布、更新、删除、点赞、分享等接口")
public class DiaryPostController {
@Autowired
private DiaryPostService diaryPostService;
/**
* 分页查询日记
*/
@Operation(summary = "分页查询日记", description = "根据条件分页查询当前用户的日记帖子列表。")
@GetMapping(value = "/page")
public Result<PageResult<DiaryPostResponse>> getPage(@Validated DiaryPostPageRequest request) {
return Result.success(diaryPostService.getPageWithResponse(request));
}
/**
* 根据ID获取日记详情
*/
@Operation(summary = "获取日记详情", description = "根据 ID 获取日记帖子的详细信息。")
@GetMapping(value = "/detail")
public Result<DiaryPostResponse> getById(@Parameter(description = "日记 ID") @RequestParam String id) {
DiaryPostResponse response = diaryPostService.getDiaryPostResponseById(id);
if (response == null) {
return Result.notFound("日记不存在");
}
return Result.success(response);
}
/**
* 创建日记
*/
@Operation(summary = "创建日记", description = "创建一篇新的日记帖子。")
@PostMapping(value = "/create")
public Result<DiaryPostResponse> create(@Valid @RequestBody DiaryPostCreateRequest request) {
// 从上下文中获取当前用户ID,而不是直接使用请求中的用户ID
String currentUserId = UserContextUtils.getCurrentUserId();
if (currentUserId != null) {
request.setUserId(currentUserId);
}
return Result.success(diaryPostService.createDiaryPostWithResponse(request));
}
/**
* 发表日记并生成AI评论
*/
@Operation(summary = "发布日记", description = "将草稿状态的日记帖子发布为正式内容。")
@PostMapping(value = "/publish")
public Result<DiaryPostResponse> publish(@Valid @RequestBody DiaryPostCreateRequest request) {
// 从上下文中获取当前用户ID,而不是直接使用请求中的用户ID
String currentUserId = UserContextUtils.getCurrentUserId();
if (currentUserId != null) {
request.setUserId(currentUserId);
}
return Result.success(diaryPostService.publishDiaryWithAiComment(request));
}
/**
* 更新日记
*/
@Operation(summary = "更新日记", description = "修改已有日记帖子的内容。")
@PutMapping(value = "/update")
public Result<DiaryPostResponse> update(@Valid @RequestBody DiaryPostUpdateRequest request) {
DiaryPostResponse response = diaryPostService.updateDiaryPostWithResponse(request);
if (response == null) {
return Result.error("更新失败");
}
return Result.success(response);
}
/**
* 删除日记
*/
@Operation(summary = "删除日记", description = "永久删除指定的日记帖子。")
@DeleteMapping(value = "/delete")
public Result<Void> delete(@Parameter(description = "日记 ID") @RequestParam String id) {
boolean deleted = diaryPostService.deleteDiaryPost(id);
if (!deleted) {
return Result.error("删除失败");
}
return Result.success();
}
/**
* 软删除日记
*/
@Operation(summary = "软删除日记", description = "将日记帖子标记为已删除状态(可恢复)。")
@DeleteMapping(value = "/softDelete")
public Result<Void> softDelete(@Parameter(description = "日记 ID") @RequestParam String id) {
boolean deleted = diaryPostService.softDeleteDiaryPost(id);
if (!deleted) {
return Result.error("删除失败");
}
return Result.success();
}
/**
* 恢复日记
*/
@Operation(summary = "恢复日记", description = "将已软删除的日记帖子恢复为正常状态。")
@PutMapping(value = "/restore")
public Result<Void> restore(@Parameter(description = "日记 ID") @RequestParam String id) {
boolean restored = diaryPostService.restoreDiaryPost(id);
if (!restored) {
return Result.error("恢复失败");
}
return Result.success();
}
/**
* 点赞日记
*/
@Operation(summary = "点赞日记", description = "对指定日记帖子进行点赞操作。")
@PostMapping(value = "/like")
public Result<Void> like(@Parameter(description = "日记 ID") @RequestParam String id) {
boolean liked = diaryPostService.incrementLikeCount(id);
if (!liked) {
return Result.error("点赞失败");
}
return Result.success();
}
/**
* 取消点赞日记
*/
@Operation(summary = "取消点赞日记", description = "取消对指定日记帖子的点赞。")
@DeleteMapping(value = "/unlike")
public Result<Void> unlike(@Parameter(description = "日记 ID") @RequestParam String id) {
boolean unliked = diaryPostService.decrementLikeCount(id);
if (!unliked) {
return Result.error("取消点赞失败");
}
return Result.success();
}
/**
* 分享日记
*/
@Operation(summary = "分享日记", description = "将指定日记帖子分享给其他用户。")
@PostMapping(value = "/share")
public Result<Void> share(@Parameter(description = "日记 ID") @RequestParam String id) {
boolean shared = diaryPostService.incrementShareCount(id);
if (!shared) {
return Result.error("分享失败");
}
return Result.success();
}
/**
* 设置精选状态
*/
@Operation(summary = "设置精选日记", description = "将指定日记帖子标记为精选内容。")
@PutMapping(value = "/setFeatured")
public Result<Void> setFeatured(@Parameter(description = "日记 ID") @RequestParam String id, @RequestParam Integer featured) {
boolean set = diaryPostService.setFeatured(id, featured);
if (!set) {
return Result.error("设置精选状态失败");
}
return Result.success();
}
/**
* 设置置顶优先级
*/
@Operation(summary = "设置日记优先级", description = "修改指定日记帖子的优先级排序。")
@PutMapping(value = "/setPriority")
public Result<Void> setPriority(@Parameter(description = "日记 ID") @RequestParam String id, @RequestParam Integer priority) {
boolean set = diaryPostService.setPriority(id, priority);
if (!set) {
return Result.error("设置优先级失败");
}
return Result.success();
}
}
@@ -0,0 +1,116 @@
package com.emotion.controller;
import com.emotion.service.DictionaryService;
import com.emotion.dto.request.dictionary.DictionaryCreateRequest;
import com.emotion.dto.request.dictionary.DictionaryPageRequest;
import com.emotion.dto.request.dictionary.DictionaryUpdateRequest;
import com.emotion.dto.response.dictionary.DictionaryResponse;
import com.emotion.common.PageResult;
import com.emotion.common.Result;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* 字典Controller
*
* @author huazhongmin
* @date 2025-12-22
*/
@RestController
@RequestMapping("/dictionary")
@Tag(name = "字典管理", description = "数据字典的查询、创建、更新和删除接口")
public class DictionaryController {
@Autowired
private DictionaryService dictionaryService;
/**
* 创建字典
*
* @param request 创建请求
* @return 创建结果
*/
@Operation(summary = "创建字典项", description = "创建一个新的数据字典项。")
@PostMapping
public Result<DictionaryResponse> createDictionary(@Validated @RequestBody DictionaryCreateRequest request) {
return dictionaryService.createDictionary(request);
}
/**
* 更新字典
*
* @param request 更新请求
* @return 更新结果
*/
@Operation(summary = "更新字典项", description = "修改已有数据字典项的内容。")
@PutMapping
public Result<DictionaryResponse> updateDictionary(@Validated @RequestBody DictionaryUpdateRequest request) {
return dictionaryService.updateDictionary(request);
}
/**
* 删除字典
*
* @param id 字典ID
* @return 删除结果
*/
@Operation(summary = "删除字典项", description = "删除指定的数据字典项。")
@DeleteMapping("/delete")
public Result<Void> deleteDictionary(@Parameter(description = "字典 ID") @RequestParam String id) {
return dictionaryService.deleteDictionary(id);
}
/**
* 获取字典详情
*
* @param id 字典ID
* @return 字典详情
*/
@Operation(summary = "获取字典详情", description = "根据 ID 获取字典项的详细信息。")
@GetMapping("/detail")
public Result<DictionaryResponse> getDictionary(@Parameter(description = "字典 ID") @RequestParam String id) {
return dictionaryService.getDictionary(id);
}
/**
* 分页查询字典
*
* @param request 分页请求
* @return 分页结果
*/
@Operation(summary = "分页查询字典", description = "分页查询数据字典列表。")
@GetMapping("/list")
public Result<PageResult<DictionaryResponse>> listDictionaries(DictionaryPageRequest request) {
return dictionaryService.listDictionaries(request);
}
/**
* 根据字典类型查询字典集合
*
* @param dictType 字典类型
* @return 字典集合
*/
@Operation(summary = "根据类型查询字典", description = "根据字典类型查询字典集合。")
@GetMapping("/byType")
public Result<List<DictionaryResponse>> getDictionariesByType(@Parameter(description = "字典类型") @RequestParam String dictType) {
return dictionaryService.getDictionariesByType(dictType);
}
/**
* 根据字典类型查询启用的字典集合
*
* @param dictType 字典类型
* @return 启用的字典集合
*/
@Operation(summary = "查询启用的字典", description = "根据字典类型查询启用的字典集合。")
@GetMapping("/enabledByType")
public Result<List<DictionaryResponse>> getEnabledDictionariesByType(@Parameter(description = "字典类型") @RequestParam String dictType) {
return dictionaryService.getEnabledDictionariesByType(dictType);
}
}
@@ -0,0 +1,90 @@
package com.emotion.controller;
import com.emotion.common.PageResult;
import com.emotion.common.Result;
import com.emotion.dto.request.EmotionAnalysisCreateRequest;
import com.emotion.dto.request.EmotionAnalysisPageRequest;
import com.emotion.dto.request.EmotionAnalysisUpdateRequest;
import com.emotion.dto.response.EmotionAnalysisResponse;
import com.emotion.service.EmotionAnalysisService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import java.util.List;
/**
* 情绪分析控制器
*
* @author huazhongmin
* @date 2025-07-23
*/
@RestController
@RequestMapping("/emotionAnalysis")
@Tag(name = "情绪分析管理", description = "情绪分析记录的查询、创建、更新和删除接口")
public class EmotionAnalysisController {
@Autowired
private EmotionAnalysisService emotionAnalysisService;
/**
* 分页查询情绪分析记录
*/
@Operation(summary = "分页查询情绪分析", description = "根据条件分页查询情绪分析记录列表。")
@GetMapping(value = "/page")
public Result<PageResult<EmotionAnalysisResponse>> getPage(@Validated EmotionAnalysisPageRequest request) {
return Result.success(emotionAnalysisService.getPageWithResponse(request));
}
/**
* 根据ID获取情绪分析记录
*/
@Operation(summary = "获取分析详情", description = "根据 ID 获取情绪分析报告的详细信息。")
@GetMapping(value = "/detail")
public Result<EmotionAnalysisResponse> getById(@Parameter(description = "分析 ID") @RequestParam String id) {
EmotionAnalysisResponse response = emotionAnalysisService.getEmotionAnalysisResponseById(id);
if (response == null) {
return Result.notFound("情绪分析记录不存在");
}
return Result.success(response);
}
/**
* 创建情绪分析记录
*/
@Operation(summary = "创建情绪分析", description = "基于情绪记录数据创建一条新的 AI 情绪分析报告。")
@PostMapping(value = "/create")
public Result<EmotionAnalysisResponse> create(@RequestBody @Valid EmotionAnalysisCreateRequest request) {
return Result.success(emotionAnalysisService.createEmotionAnalysisWithResponse(request));
}
/**
* 更新情绪分析记录
*/
@Operation(summary = "更新情绪分析", description = "修改已有情绪分析报告的内容。")
@PutMapping(value = "/update")
public Result<EmotionAnalysisResponse> update(@RequestBody @Valid EmotionAnalysisUpdateRequest request) {
EmotionAnalysisResponse response = emotionAnalysisService.updateEmotionAnalysisWithResponse(request);
if (response == null) {
return Result.error("更新失败");
}
return Result.success(response);
}
/**
* 删除情绪分析记录
*/
@Operation(summary = "删除情绪分析", description = "删除指定的情绪分析记录。")
@DeleteMapping(value = "/delete")
public Result<Void> delete(@Parameter(description = "分析 ID") @RequestParam String id) {
boolean deleted = emotionAnalysisService.deleteEmotionAnalysis(id);
if (!deleted) {
return Result.error("删除失败");
}
return Result.success();
}
}
@@ -0,0 +1,138 @@
package com.emotion.controller;
import com.emotion.common.PageResult;
import com.emotion.common.Result;
import com.emotion.dto.request.EmotionRecordCreateRequest;
import com.emotion.dto.request.EmotionRecordPageRequest;
import com.emotion.dto.request.EmotionRecordUpdateRequest;
import com.emotion.dto.response.EmotionRecordResponse;
import com.emotion.service.EmotionRecordService;
import com.emotion.util.UserContextUtils;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import java.util.List;
import java.util.Map;
/**
* 情绪记录控制器
*
* @author huazhongmin
* @date 2025-07-22
*/
@RestController
@RequestMapping("/emotionRecord")
@Tag(name = "情绪记录管理", description = "用户情绪记录的查询、创建、更新、删除和统计接口")
public class EmotionRecordController {
private static final Logger log = LoggerFactory.getLogger(EmotionRecordController.class);
@Autowired
private EmotionRecordService emotionRecordService;
/**
* 创建情绪记录
*/
@Operation(summary = "创建情绪记录", description = "记录用户当天的情绪数据,包括类型、强度、触发因素等。")
@PostMapping(value = "/create")
public Result<EmotionRecordResponse> createRecord(@RequestBody @Valid EmotionRecordCreateRequest request) {
log.info("创建情绪记录: userId={}", request.getUserId());
// 从上下文中获取当前用户ID
String userId = UserContextUtils.requireCurrentUserId();
request.setUserId(userId);
EmotionRecordResponse response = emotionRecordService.createEmotionRecordWithResponse(request);
return Result.success("创建成功", response);
}
/**
* 分页查询情绪记录
*/
@Operation(summary = "分页查询情绪记录", description = "根据日期范围、情绪类型等条件分页查询情绪记录。")
@GetMapping(value = "/page")
public Result<PageResult<EmotionRecordResponse>> getPage(@Validated EmotionRecordPageRequest request) {
// 从上下文中获取当前用户ID
String userId = UserContextUtils.requireCurrentUserId();
log.info("分页查询情绪记录: userId={}, current={}, size={}", userId, request.getCurrent(), request.getSize());
PageResult<EmotionRecordResponse> page = emotionRecordService.getPageByUserIdWithResponse(userId, request);
log.info("分页查询情绪记录成功: userId={}, total={}, records={}",
userId, page.getTotal(), page.getRecords().size());
return Result.success(page);
}
/**
* 获取情绪记录详情
*/
@Operation(summary = "获取记录详情", description = "根据 ID 获取情绪记录的详细信息。")
@GetMapping(value = "/detail")
public Result<EmotionRecordResponse> getRecord(@Parameter(description = "记录 ID") @RequestParam String id) {
log.info("获取情绪记录详情: {}", id);
EmotionRecordResponse response = emotionRecordService.getEmotionRecordResponseById(id);
if (response == null) {
return Result.notFound("情绪记录不存在");
}
return Result.success(response);
}
/**
* 更新情绪记录
*/
@Operation(summary = "更新情绪记录", description = "修改已有情绪记录的内容。")
@PutMapping(value = "/update")
public Result<EmotionRecordResponse> updateRecord(@RequestBody @Valid EmotionRecordUpdateRequest request) {
log.info("更新情绪记录: {}", request.getId());
EmotionRecordResponse response = emotionRecordService.updateEmotionRecordWithResponse(request);
if (response == null) {
return Result.notFound("情绪记录不存在");
}
return Result.success("更新成功", response);
}
/**
* 删除情绪记录
*/
@Operation(summary = "删除情绪记录", description = "删除指定的情绪记录。")
@DeleteMapping(value = "/delete")
public Result<String> deleteRecord(@Parameter(description = "记录 ID") @RequestParam String id) {
log.info("删除情绪记录: {}", id);
boolean deleted = emotionRecordService.deleteEmotionRecord(id);
if (!deleted) {
return Result.notFound("情绪记录不存在");
}
return Result.success("删除成功");
}
/**
* 获取情绪统计
*/
@Operation(summary = "获取情绪统计", description = "统计指定时间范围内的情绪数据分布。")
@GetMapping(value = "/stats")
public Result<Map<String, Object>> getEmotionStats(
@RequestParam(required = false) String startDate,
@RequestParam(required = false) String endDate) {
// 从上下文中获取当前用户ID
String userId = UserContextUtils.requireCurrentUserId();
log.info("获取情绪统计: userId={}, startDate={}, endDate={}", userId, startDate, endDate);
Map<String, Object> stats = emotionRecordService.getEmotionStats(userId, startDate, endDate);
return Result.success(stats);
}
}
@@ -0,0 +1,72 @@
package com.emotion.controller;
import com.emotion.common.Result;
import com.emotion.dto.request.EmotionSummaryGenerateRequest;
import com.emotion.dto.request.EmotionSummaryStatusRequest;
import com.emotion.dto.response.EmotionSummaryGenerateResponse;
import com.emotion.dto.response.EmotionSummaryStatusResponse;
import com.emotion.service.AiChatService;
import com.emotion.util.UserContextUtils;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
/**
* 情绪总结控制器
*
* @author huazhongmin
* @date 2025-07-25
*/
@Slf4j
@RestController
@RequestMapping("/emotionSummary")
@Tag(name = "情绪总结管理", description = "用户情绪记录总结和分析功能")
public class EmotionSummaryController {
@Autowired
private AiChatService aiChatService;
/**
* 生成用户当天的情绪记录总结
*/
@Operation(summary = "生成情绪总结", description = "基于指定时间范围的情绪记录数据,生成 AI 情绪总结报告。")
@PostMapping(value = "/generate")
public Result<EmotionSummaryGenerateResponse> generateEmotionSummary(
@RequestBody @Valid EmotionSummaryGenerateRequest request) {
String userId = UserContextUtils.requireCurrentUserId();
log.info("收到生成情绪记录总结请求: userId={}", userId);
// 调用AI服务生成情绪总结
EmotionSummaryGenerateResponse response = aiChatService.generateEmotionSummaryWithResponse(userId);
if (response.getSuccess()) {
log.info("情绪记录总结生成成功: userId={}", userId);
return Result.success("情绪记录总结生成成功", response);
} else {
log.warn("情绪记录总结生成失败: userId={}, message={}", userId, response.getMessage());
return Result.error(response.getMessage());
}
}
/**
* 获取用户情绪记录总结状态
*/
@Operation(summary = "查询总结状态", description = "查询指定时间段内是否已生成情绪总结及其状态。")
@GetMapping(value = "/status")
public Result<EmotionSummaryStatusResponse> getEmotionSummaryStatus(
@Validated EmotionSummaryStatusRequest request) {
// 从上下文中获取当前用户ID
String userId = UserContextUtils.requireCurrentUserId();
log.info("查询用户情绪记录总结状态: userId={}", userId);
// 调用AI服务获取状态信息
EmotionSummaryStatusResponse response = aiChatService.getEmotionSummaryStatusWithResponse(userId);
return Result.success(response);
}
}
@@ -0,0 +1,150 @@
package com.emotion.controller;
import com.emotion.common.PageResult;
import com.emotion.common.Result;
import com.emotion.dto.request.EpicScriptCreateRequest;
import com.emotion.dto.request.EpicScriptInspirationRequest;
import com.emotion.dto.request.EpicScriptPageRequest;
import com.emotion.dto.request.EpicScriptUpdateRequest;
import com.emotion.dto.response.EpicScriptInspirationResponse;
import com.emotion.dto.response.EpicScriptResponse;
import com.emotion.dto.response.InspirationSuggestionResponse;
import com.emotion.service.EpicScriptService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import java.util.List;
/**
* 爽文剧本控制器
*
* @author huazhongmin
* @date 2025-12-22
*/
@RestController
@RequestMapping("/epicScript")
@Tag(name = "爽文剧本管理", description = "爽文剧本的查询、创建、更新、删除和灵感推荐接口")
public class EpicScriptController {
@Autowired
private EpicScriptService epicScriptService;
/**
* 分页查询当前用户的爽文剧本
*/
@Operation(summary = "分页查询爽文剧本", description = "分页查询当前用户的爽文剧本列表。")
@GetMapping(value = "/page")
public Result<PageResult<EpicScriptResponse>> getPage(@Validated EpicScriptPageRequest request) {
PageResult<EpicScriptResponse> pageResult = epicScriptService.getPageByCurrentUser(request);
return Result.success(pageResult);
}
/**
* 获取当前用户的所有爽文剧本列表
*/
@Operation(summary = "获取爽文剧本列表", description = "获取当前用户的所有爽文剧本列表。")
@GetMapping(value = "/listAll")
public Result<List<EpicScriptResponse>> getList() {
List<EpicScriptResponse> scripts = epicScriptService.getListByCurrentUser();
return Result.success(scripts);
}
@Operation(summary = "获取灵感推荐", description = "获取系统推荐的灵感建议列表。")
@GetMapping(value = "/inspiration/recommendations")
public Result<List<InspirationSuggestionResponse>> getInspirationRecommendations() {
return Result.success(epicScriptService.getInspirationRecommendations());
}
@Operation(summary = "获取随机灵感", description = "随机获取指定数量的灵感建议。")
@GetMapping(value = "/inspiration/random")
public Result<List<InspirationSuggestionResponse>> getRandomInspirations(
@Parameter(description = "灵感数量") @RequestParam(required = false, defaultValue = "3") Integer size) {
return Result.success(epicScriptService.getRandomInspirations(size));
}
@Operation(summary = "从灵感生成剧本", description = "基于选定的灵感建议生成爽文剧本。")
@PostMapping(value = "/inspiration/generate")
public Result<EpicScriptInspirationResponse> generateFromInspiration(
@Valid @RequestBody EpicScriptInspirationRequest request) {
EpicScriptInspirationResponse response;
try {
response = epicScriptService.generateFromInspiration(request);
} catch (IllegalStateException e) {
return Result.error(e.getMessage());
}
if (response == null) {
return Result.error("灵感剧本生成失败");
}
return Result.success(response);
}
/**
* 根据ID获取爽文剧本详情
*/
@Operation(summary = "获取剧本详情", description = "根据 ID 获取爽文剧本的详细信息。")
@GetMapping(value = "/detail")
public Result<EpicScriptResponse> getById(@Parameter(description = "剧本 ID") @RequestParam String id) {
EpicScriptResponse script = epicScriptService.getScriptById(id);
if (script == null) {
return Result.notFound("爽文剧本不存在");
}
return Result.success(script);
}
/**
* 创建爽文剧本
*/
@Operation(summary = "创建爽文剧本", description = "创建一个新的爽文剧本。")
@PostMapping(value = "/create")
public Result<EpicScriptResponse> create(@Valid @RequestBody EpicScriptCreateRequest request) {
EpicScriptResponse script = epicScriptService.createScript(request);
if (script == null) {
return Result.error("创建失败");
}
return Result.success(script);
}
/**
* 更新爽文剧本
*/
@Operation(summary = "更新爽文剧本", description = "修改已有爽文剧本的内容。")
@PutMapping(value = "/update")
public Result<EpicScriptResponse> update(@Valid @RequestBody EpicScriptUpdateRequest request) {
EpicScriptResponse script = epicScriptService.updateScript(request);
if (script == null) {
return Result.error("更新失败");
}
return Result.success(script);
}
/**
* 选中剧本(取消其他选中状态)
*/
@Operation(summary = "选中剧本", description = "选中指定剧本并取消其他剧本的选中状态。")
@PutMapping(value = "/select")
public Result<EpicScriptResponse> select(@Parameter(description = "剧本 ID") @RequestParam String id) {
EpicScriptResponse script = epicScriptService.selectScript(id);
if (script == null) {
return Result.error("选中失败");
}
return Result.success(script);
}
/**
* 删除爽文剧本
*/
@Operation(summary = "删除爽文剧本", description = "删除指定的爽文剧本。")
@DeleteMapping(value = "/delete")
public Result<Void> delete(@Parameter(description = "剧本 ID") @RequestParam String id) {
boolean deleted = epicScriptService.deleteScript(id);
if (!deleted) {
return Result.error("删除失败");
}
return Result.success();
}
}
@@ -0,0 +1,91 @@
package com.emotion.controller;
import com.emotion.common.PageResult;
import com.emotion.common.Result;
import com.emotion.dto.request.growth.GrowthTopicCreateRequest;
import com.emotion.dto.request.growth.GrowthTopicPageRequest;
import com.emotion.dto.request.growth.GrowthTopicUpdateRequest;
import com.emotion.dto.response.growth.GrowthTopicResponse;
import com.emotion.service.GrowthTopicService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
/**
* 成长话题控制器
*
* @author huazhongmin
* @date 2025-09-08
*/
@RestController
@RequestMapping("/growthTopic")
@Tag(name = "成长话题管理", description = "成长话题的查询、创建、更新和删除接口")
public class GrowthTopicController {
@Autowired
private GrowthTopicService growthTopicService;
/**
* 分页查询成长话题
*/
@Operation(summary = "分页查询成长话题", description = "分页查询成长话题列表。")
@GetMapping(value = "/page")
public Result<PageResult<GrowthTopicResponse>> getPage(@Validated GrowthTopicPageRequest request) {
PageResult<GrowthTopicResponse> pageResult = growthTopicService.getPageWithResponse(request);
return Result.success(pageResult);
}
/**
* 根据ID获取成长话题
*/
@Operation(summary = "获取话题详情", description = "根据 ID 获取成长话题的详细信息。")
@GetMapping(value = "/detail")
public Result<GrowthTopicResponse> getById(@Parameter(description = "话题 ID") @RequestParam String id) {
GrowthTopicResponse response = growthTopicService.getGrowthTopicResponseById(id);
if (response == null) {
return Result.notFound("成长话题不存在");
}
return Result.success(response);
}
/**
* 创建成长话题
*/
@Operation(summary = "创建成长话题", description = "创建一个新的成长话题。")
@PostMapping(value = "/create")
public Result<GrowthTopicResponse> create(@Valid @RequestBody GrowthTopicCreateRequest request) {
GrowthTopicResponse response = growthTopicService.createGrowthTopicWithResponse(request);
return Result.success(response);
}
/**
* 更新成长话题
*/
@Operation(summary = "更新成长话题", description = "修改已有成长话题的内容。")
@PutMapping(value = "/update")
public Result<GrowthTopicResponse> update(@Valid @RequestBody GrowthTopicUpdateRequest request) {
GrowthTopicResponse response = growthTopicService.updateGrowthTopicWithResponse(request);
if (response == null) {
return Result.notFound("成长话题不存在");
}
return Result.success(response);
}
/**
* 删除成长话题
*/
@Operation(summary = "删除成长话题", description = "删除指定的成长话题。")
@DeleteMapping(value = "/delete")
public Result<Void> delete(@Parameter(description = "话题 ID") @RequestParam String id) {
boolean deleted = growthTopicService.deleteGrowthTopic(id);
if (!deleted) {
return Result.error("删除失败");
}
return Result.success();
}
}
@@ -0,0 +1,91 @@
package com.emotion.controller;
import com.emotion.common.PageResult;
import com.emotion.common.Result;
import com.emotion.dto.request.guest.GuestUserCreateRequest;
import com.emotion.dto.request.guest.GuestUserPageRequest;
import com.emotion.dto.request.guest.GuestUserUpdateRequest;
import com.emotion.dto.response.guest.GuestUserResponse;
import com.emotion.service.GuestUserService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
/**
* 访客用户控制器
*
* @author huazhongmin
* @date 2025-09-08
*/
@RestController
@RequestMapping("/guestUser")
@Tag(name = "访客用户管理", description = "访客用户的查询、创建、更新和删除接口")
public class GuestUserController {
@Autowired
private GuestUserService guestUserService;
/**
* 分页查询访客用户
*/
@Operation(summary = "分页查询访客用户", description = "分页查询访客用户列表。")
@GetMapping(value = "/page")
public Result<PageResult<GuestUserResponse>> getPage(@Validated GuestUserPageRequest request) {
PageResult<GuestUserResponse> pageResult = guestUserService.getPageWithResponse(request);
return Result.success(pageResult);
}
/**
* 根据ID获取访客用户
*/
@Operation(summary = "获取访客详情", description = "根据 ID 获取访客用户的详细信息。")
@GetMapping(value = "/detail")
public Result<GuestUserResponse> getById(@Parameter(description = "访客 ID") @RequestParam String id) {
GuestUserResponse response = guestUserService.getGuestUserResponseById(id);
if (response == null) {
return Result.notFound("访客用户不存在");
}
return Result.success(response);
}
/**
* 创建访客用户
*/
@Operation(summary = "创建访客用户", description = "创建一个新的访客用户。")
@PostMapping(value = "/create")
public Result<GuestUserResponse> create(@Valid @RequestBody GuestUserCreateRequest request) {
GuestUserResponse response = guestUserService.createGuestUserWithResponse(request);
return Result.success(response);
}
/**
* 更新访客用户
*/
@Operation(summary = "更新访客用户", description = "修改已有访客用户的信息。")
@PutMapping(value = "/update")
public Result<GuestUserResponse> update(@Valid @RequestBody GuestUserUpdateRequest request) {
GuestUserResponse response = guestUserService.updateGuestUserWithResponse(request);
if (response == null) {
return Result.notFound("访客用户不存在");
}
return Result.success(response);
}
/**
* 删除访客用户
*/
@Operation(summary = "删除访客用户", description = "删除指定的访客用户。")
@DeleteMapping(value = "/delete")
public Result<Void> delete(@Parameter(description = "访客 ID") @RequestParam String id) {
boolean deleted = guestUserService.deleteGuestUser(id);
if (!deleted) {
return Result.error("删除失败");
}
return Result.success();
}
}
@@ -0,0 +1,63 @@
package com.emotion.controller;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;
/**
* 健康检查控制器
*
* @author huazhongmin
* @date 2025-07-23
*/
@RestController
@Tag(name = "健康检查", description = "系统健康状态检查接口")
public class HealthController {
private static final Logger log = LoggerFactory.getLogger(HealthController.class);
/**
* 健康检查
*/
@Operation(summary = "系统健康检查", description = "返回系统各组件的健康状态,包括数据库、Redis 等。")
@GetMapping("/health")
public Map<String, Object> health() {
log.info("健康检查请求");
Map<String, Object> response = new HashMap<>();
response.put("service", "backend-single");
response.put("message", "情感博物馆单体服务运行正常");
response.put("version", "1.0.0");
response.put("status", "UP");
response.put("timestamp", LocalDateTime.now());
return response;
}
/**
* 服务信息
*/
@Operation(summary = "获取服务信息", description = "返回服务的基本信息,包括版本号、构建时间等。")
@GetMapping("/health/info")
public Map<String, Object> info() {
log.info("服务信息请求");
Map<String, Object> response = new HashMap<>();
response.put("service", "backend-single");
response.put("description", "情感博物馆单体服务");
response.put("version", "1.0.0");
response.put("author", "emotion-museum");
response.put("buildTime", "2025-07-23");
response.put("javaVersion", System.getProperty("java.version"));
response.put("timestamp", LocalDateTime.now());
return response;
}
}
@@ -0,0 +1,185 @@
package com.emotion.controller;
import com.emotion.common.PageResult;
import com.emotion.common.Result;
import com.emotion.dto.request.LifeEventCreateRequest;
import com.emotion.dto.request.LifeEventPageRequest;
import com.emotion.dto.request.LifeEventUpdateRequest;
import com.emotion.dto.response.LifeEventResponse;
import com.emotion.service.LifeEventService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
* 生命事件控制器
*
* @author huazhongmin
* @date 2025-12-22
*/
@RestController
@RequestMapping("/lifeEvent")
@Tag(name = "生命事件管理", description = "生命事件的查询、创建、更新和删除接口")
public class LifeEventController {
@Autowired
private LifeEventService lifeEventService;
/**
* 分页查询当前用户的生命事件
*/
@Operation(summary = "分页查询生命事件", description = "分页查询生命事件列表。")
@GetMapping(value = "/page")
public Result<PageResult<LifeEventResponse>> getPage(@Validated LifeEventPageRequest request) {
PageResult<LifeEventResponse> pageResult = lifeEventService.getPageByCurrentUser(request);
return Result.success(pageResult);
}
/**
* 获取当前用户的所有生命事件列表
*/
@Operation(summary = "获取生命事件列表", description = "获取当前用户的所有生命事件列表。")
@GetMapping(value = "/list")
public Result<List<LifeEventResponse>> getList() {
List<LifeEventResponse> events = lifeEventService.getListByCurrentUser();
return Result.success(events);
}
/**
* 根据ID获取生命事件详情
*/
@Operation(summary = "获取生命事件详情", description = "根据 ID 获取生命事件的详细信息。")
@GetMapping(value = "/detail")
public Result<LifeEventResponse> getById(@Parameter(description = "事件 ID") @RequestParam String id) {
LifeEventResponse event = lifeEventService.getEventById(id);
if (event == null) {
return Result.notFound("生命事件不存在");
}
return Result.success(event);
}
/**
* 创建生命事件
*/
@Operation(summary = "创建生命事件", description = "创建一个新的生命事件。")
@PostMapping(value = "/create")
public Result<LifeEventResponse> create(@Valid @RequestBody LifeEventCreateRequest request) {
LifeEventResponse event = lifeEventService.createEvent(request);
if (event == null) {
return Result.error("创建失败");
}
return Result.success(event);
}
/**
* 更新生命事件
*/
@Operation(summary = "更新生命事件", description = "修改已有生命事件的内容。")
@PutMapping(value = "/update")
public Result<LifeEventResponse> update(@Valid @RequestBody LifeEventUpdateRequest request) {
LifeEventResponse event = lifeEventService.updateEvent(request);
if (event == null) {
return Result.error("更新失败");
}
return Result.success(event);
}
/**
* 删除生命事件
*/
@Operation(summary = "删除生命事件", description = "删除指定的生命事件。")
@DeleteMapping(value = "/delete")
public Result<Void> delete(@Parameter(description = "事件 ID") @RequestParam String id) {
boolean deleted = lifeEventService.deleteEvent(id);
if (!deleted) {
return Result.error("删除失败");
}
return Result.success();
}
@Operation(summary = "AI 辅助生成内容", description = "根据标题和内容调用 AI 生成生命事件的解读文本、标签等信息。")
@PostMapping(value = "/ai-assist")
public Result<Map<String, Object>> aiAssist(@RequestBody Map<String, Object> request) {
String title = stringValue(request.get("title"), "这段经历");
String content = stringValue(request.get("content"), "");
List<String> tags = readTags(request.get("tags"));
if (tags.isEmpty()) {
tags.add("成长");
tags.add("记录");
}
Map<String, Object> data = new LinkedHashMap<>();
data.put("content", content.isBlank()
? "那一天,我清楚地感受到自己正在经历一次变化。事情本身也许并不宏大,但它让我重新看见了自己的选择、情绪和力量。"
: content + "\n\n我愿意把这段经历记录下来,因为它提醒我:每一次真实面对,都会成为未来的底气。");
data.put("aiReply", "AI占位解读:" + title + "体现了你的自我观察和复盘能力。后续接入真实AI后,这里会返回更完整的情绪整理、能力映射和行动建议。");
data.put("tags", tags);
data.put("placeholder", true);
return Result.success(data);
}
@Operation(summary = "聊天占位接口", description = "返回聊天功能的占位回复和建议问题。")
@PostMapping(value = "/chat-placeholder")
public Result<Map<String, Object>> chatPlaceholder(@RequestBody Map<String, Object> request) {
String title = stringValue(request.get("title"), "这段经历");
Map<String, Object> data = new LinkedHashMap<>();
data.put("reply", "我在这里陪你回看「" + title + "」。真实聊天能力后续接入AI工作流;当前先保留这个入口和上下文。");
data.put("suggestions", List.of("这件事让我学到了什么?", "如果重来一次我会怎么选?", "它会怎样影响我的人生剧本?"));
data.put("placeholder", true);
return Result.success(data);
}
@Operation(summary = "分享占位接口", description = "返回分享功能的占位文本和分享信息。")
@PostMapping(value = "/share-placeholder")
public Result<Map<String, Object>> sharePlaceholder(@RequestBody Map<String, Object> request) {
String title = stringValue(request.get("title"), "人生经历");
Map<String, Object> data = new HashMap<>();
data.put("title", title);
data.put("summary", "我刚刚记录了一段重要的人生轨迹。");
data.put("shareText", "分享我的人生轨迹:" + title);
data.put("placeholder", true);
return Result.success(data);
}
@Operation(summary = "收藏占位接口", description = "返回收藏功能的占位响应。")
@PostMapping(value = "/favorite-placeholder")
public Result<Map<String, Object>> favoritePlaceholder(@RequestBody Map<String, Object> request) {
String id = stringValue(request.get("id"), "");
Boolean favorite = Boolean.TRUE.equals(request.get("favorite"));
Map<String, Object> data = new HashMap<>();
data.put("id", id);
data.put("favorite", favorite);
data.put("placeholder", true);
return Result.success(data);
}
private String stringValue(Object value, String fallback) {
if (value == null) {
return fallback;
}
String text = String.valueOf(value).trim();
return text.isEmpty() ? fallback : text;
}
@SuppressWarnings("unchecked")
private List<String> readTags(Object value) {
List<String> tags = new ArrayList<>();
if (value instanceof List<?>) {
for (Object item : (List<Object>) value) {
if (item != null && !String.valueOf(item).trim().isEmpty()) {
tags.add(String.valueOf(item).trim());
}
}
}
return tags;
}
}
@@ -0,0 +1,118 @@
package com.emotion.controller;
import com.emotion.common.PageResult;
import com.emotion.common.Result;
import com.emotion.dto.request.LifePathCreateRequest;
import com.emotion.dto.request.LifePathPageRequest;
import com.emotion.dto.request.LifePathUpdateRequest;
import com.emotion.dto.response.LifePathResponse;
import com.emotion.service.LifePathService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import java.util.List;
/**
* 实现路径控制器
*
* @author huazhongmin
* @date 2025-12-22
*/
@RestController
@RequestMapping("/lifePath")
@Tag(name = "实现路径管理", description = "人生剧本实现路径的查询、创建、更新和删除接口")
public class LifePathController {
@Autowired
private LifePathService lifePathService;
/**
* 分页查询当前用户的实现路径
*/
@Operation(summary = "分页查询实现路径", description = "分页查询当前用户的实现路径列表。")
@GetMapping(value = "/page")
public Result<PageResult<LifePathResponse>> getPage(@Validated LifePathPageRequest request) {
PageResult<LifePathResponse> pageResult = lifePathService.getPageByCurrentUser(request);
return Result.success(pageResult);
}
/**
* 获取当前用户的所有实现路径列表
*/
@Operation(summary = "获取实现路径列表", description = "获取当前用户的所有实现路径列表。")
@GetMapping(value = "/listAll")
public Result<List<LifePathResponse>> getList() {
List<LifePathResponse> paths = lifePathService.getListByCurrentUser();
return Result.success(paths);
}
/**
* 根据剧本ID获取实现路径
*/
@Operation(summary = "根据剧本ID获取路径", description = "根据剧本 ID 获取对应的实现路径。")
@GetMapping(value = "/byScript")
public Result<LifePathResponse> getByScriptId(@Parameter(description = "剧本 ID") @RequestParam String scriptId) {
LifePathResponse path = lifePathService.getByScriptId(scriptId);
if (path == null) {
return Result.notFound("实现路径不存在");
}
return Result.success(path);
}
/**
* 根据ID获取实现路径详情
*/
@Operation(summary = "获取路径详情", description = "根据 ID 获取实现路径的详细信息。")
@GetMapping(value = "/detail")
public Result<LifePathResponse> getById(@Parameter(description = "路径 ID") @RequestParam String id) {
LifePathResponse path = lifePathService.getPathById(id);
if (path == null) {
return Result.notFound("实现路径不存在");
}
return Result.success(path);
}
/**
* 创建实现路径
*/
@Operation(summary = "创建实现路径", description = "创建一条新的实现路径。")
@PostMapping(value = "/create")
public Result<LifePathResponse> create(@Valid @RequestBody LifePathCreateRequest request) {
LifePathResponse path = lifePathService.createPath(request);
if (path == null) {
return Result.error("创建失败");
}
return Result.success(path);
}
/**
* 更新实现路径
*/
@Operation(summary = "更新实现路径", description = "修改已有实现路径的内容。")
@PutMapping(value = "/update")
public Result<LifePathResponse> update(@Valid @RequestBody LifePathUpdateRequest request) {
LifePathResponse path = lifePathService.updatePath(request);
if (path == null) {
return Result.error("更新失败");
}
return Result.success(path);
}
/**
* 删除实现路径
*/
@Operation(summary = "删除实现路径", description = "删除指定的实现路径。")
@DeleteMapping(value = "/delete")
public Result<Void> delete(@Parameter(description = "路径 ID") @RequestParam String id) {
boolean deleted = lifePathService.deletePath(id);
if (!deleted) {
return Result.error("删除失败");
}
return Result.success();
}
}
@@ -0,0 +1,114 @@
package com.emotion.controller;
import com.emotion.common.PageResult;
import com.emotion.common.Result;
import com.emotion.dto.request.MessageCreateRequest;
import com.emotion.dto.request.MessagePageRequest;
import com.emotion.dto.request.MessageSearchRequest;
import com.emotion.dto.request.MessageRecentRequest;
import com.emotion.dto.response.MessageResponse;
import com.emotion.service.MessageService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
/**
* 消息控制器
*
* @author huazhongmin
* @date 2025-09-08
*/
@RestController
@RequestMapping("/message")
@Slf4j
@Tag(name = "消息管理", description = "会话消息的查询、创建、搜索、更新和删除接口")
public class MessageController {
@Autowired
private MessageService messageService;
/**
* 创建消息
*/
@Operation(summary = "创建消息", description = "在指定会话中创建一条新消息。")
@PostMapping(value = "/create")
public Result<MessageResponse> create(@Valid @RequestBody MessageCreateRequest request) {
MessageResponse response = messageService.createMessageFromRequest(request);
return Result.success(response);
}
/**
* 根据ID获取消息
*/
@Operation(summary = "获取消息详情", description = "根据 ID 获取消息的详细信息。")
@GetMapping(value = "/detail")
public Result<MessageResponse> getById(@Parameter(description = "消息 ID") @RequestParam String id) {
MessageResponse response = messageService.getMessageById(id);
if (response == null) {
return Result.notFound("消息不存在");
}
return Result.success(response);
}
/**
* 分页查询消息
*/
@Operation(summary = "分页查询消息", description = "分页查询指定会话的消息列表。")
@GetMapping(value = "/page")
public Result<PageResult<MessageResponse>> getPage(@Validated MessagePageRequest request) {
PageResult<MessageResponse> pageResult = messageService.getPageWithResponse(request);
return Result.success(pageResult);
}
/**
* 搜索消息
*/
@Operation(summary = "搜索消息", description = "根据关键词在指定会话中搜索消息内容。")
@PostMapping(value = "/search")
public Result<PageResult<MessageResponse>> search(@Valid @RequestBody MessageSearchRequest request) {
PageResult<MessageResponse> pageResult = messageService.searchWithResponse(request);
return Result.success(pageResult);
}
/**
* 获取最近的消息
*/
@Operation(summary = "获取最近消息", description = "获取指定会话最近的消息列表。")
@PostMapping(value = "/recent")
public Result<PageResult<MessageResponse>> getRecentMessages(@Valid @RequestBody MessageRecentRequest request) {
PageResult<MessageResponse> pageResult = messageService.getRecentWithResponse(request);
return Result.success(pageResult);
}
/**
* 更新消息
*/
@Operation(summary = "更新消息", description = "修改指定消息的内容。")
@PutMapping(value = "/update")
public Result<MessageResponse> update(@RequestParam String id, @RequestParam String content) {
MessageResponse response = messageService.updateMessage(id, content);
if (response == null) {
return Result.notFound("消息不存在");
}
return Result.success(response);
}
/**
* 删除消息
*/
@Operation(summary = "删除消息", description = "删除指定的消息记录。")
@DeleteMapping(value = "/delete")
public Result<Void> delete(@Parameter(description = "消息 ID") @RequestParam String id) {
boolean deleted = messageService.deleteMessage(id);
if (!deleted) {
return Result.error("删除失败");
}
return Result.success();
}
}
@@ -0,0 +1,91 @@
package com.emotion.controller;
import com.emotion.common.PageResult;
import com.emotion.common.Result;
import com.emotion.dto.request.reward.RewardCreateRequest;
import com.emotion.dto.request.reward.RewardPageRequest;
import com.emotion.dto.request.reward.RewardUpdateRequest;
import com.emotion.dto.response.reward.RewardResponse;
import com.emotion.service.RewardService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
/**
* 奖励控制器
*
* @author huazhongmin
* @date 2025-09-08
*/
@RestController
@RequestMapping("/reward")
@Tag(name = "奖励管理", description = "奖励的查询、创建、更新和删除接口")
public class RewardController {
@Autowired
private RewardService rewardService;
/**
* 分页查询奖励
*/
@Operation(summary = "分页查询奖励", description = "分页查询奖励列表。")
@GetMapping(value = "/page")
public Result<PageResult<RewardResponse>> getPage(@Validated RewardPageRequest request) {
PageResult<RewardResponse> pageResult = rewardService.getPageWithResponse(request);
return Result.success(pageResult);
}
/**
* 根据ID获取奖励
*/
@Operation(summary = "获取奖励详情", description = "根据 ID 获取奖励的详细信息。")
@GetMapping(value = "/detail")
public Result<RewardResponse> getById(@Parameter(description = "奖励 ID") @RequestParam String id) {
RewardResponse response = rewardService.getRewardResponseById(id);
if (response == null) {
return Result.notFound("奖励不存在");
}
return Result.success(response);
}
/**
* 创建奖励
*/
@Operation(summary = "创建奖励", description = "创建一个新的奖励。")
@PostMapping(value = "/create")
public Result<RewardResponse> create(@Valid @RequestBody RewardCreateRequest request) {
RewardResponse response = rewardService.createRewardWithResponse(request);
return Result.success(response);
}
/**
* 更新奖励
*/
@Operation(summary = "更新奖励", description = "修改已有奖励的信息。")
@PutMapping(value = "/update")
public Result<RewardResponse> update(@Valid @RequestBody RewardUpdateRequest request) {
RewardResponse response = rewardService.updateRewardWithResponse(request);
if (response == null) {
return Result.notFound("奖励不存在");
}
return Result.success(response);
}
/**
* 删除奖励
*/
@Operation(summary = "删除奖励", description = "删除指定的奖励。")
@DeleteMapping(value = "/delete")
public Result<Void> delete(@Parameter(description = "奖励 ID") @RequestParam String id) {
boolean deleted = rewardService.deleteReward(id);
if (!deleted) {
return Result.error("删除失败");
}
return Result.success();
}
}
@@ -0,0 +1,125 @@
package com.emotion.controller;
import com.emotion.common.Result;
import com.emotion.dto.request.social.SocialContentApprovalRequest;
import com.emotion.dto.request.social.SocialContentLinkImportRequest;
import com.emotion.dto.request.social.SocialContentManualImportRequest;
import com.emotion.dto.response.social.SocialContentItemResponse;
import com.emotion.service.SocialContentService;
import com.emotion.util.UserContextHolder;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.DeleteMapping;
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.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import javax.validation.Valid;
import java.util.List;
@Validated
@RestController
@RequestMapping("/social/content")
@Tag(name = "社交内容管理", description = "社交媒体内容的导入、截图、审核等接口")
public class SocialContentController {
@Autowired
private SocialContentService socialContentService;
@Operation(summary = "手动导入社交内容", description = "通过上传文本内容手动导入社交媒体数据。")
@PostMapping("/manual")
public Result<SocialContentItemResponse> manualImport(@Valid @RequestBody SocialContentManualImportRequest request) {
String userId = currentUserId();
if (userId == null) {
return Result.unauthorized();
}
try {
return Result.success(socialContentService.manualImport(userId, request));
} catch (IllegalArgumentException e) {
return Result.badRequest(e.getMessage());
}
}
@Operation(summary = "链接导入社交内容", description = "通过社交媒体链接 URL 导入内容。")
@PostMapping("/link")
public Result<SocialContentItemResponse> linkImport(@Valid @RequestBody SocialContentLinkImportRequest request) {
String userId = currentUserId();
if (userId == null) {
return Result.unauthorized();
}
try {
return Result.success(socialContentService.linkImport(userId, request));
} catch (IllegalArgumentException e) {
return Result.badRequest(e.getMessage());
}
}
@Operation(summary = "截图导入社交内容", description = "通过上传截图图片来导入社交媒体内容。")
@PostMapping("/screenshot")
public Result<SocialContentItemResponse> screenshotImport(@Parameter(description = "平台名称", required = true) @RequestParam String platform,
@RequestPart("file") MultipartFile file) {
String userId = currentUserId();
if (userId == null) {
return Result.unauthorized();
}
try {
return Result.success(socialContentService.screenshotImport(userId, platform, file));
} catch (IllegalArgumentException | IllegalStateException e) {
return Result.badRequest(e.getMessage());
}
}
@Operation(summary = "获取社交内容列表", description = "查询所有已导入的社交媒体内容列表。")
@GetMapping("/list")
public Result<List<SocialContentItemResponse>> list() {
String userId = currentUserId();
if (userId == null) {
return Result.unauthorized();
}
return Result.success(socialContentService.listByUser(userId));
}
@Operation(summary = "更新内容审核状态", description = "修改指定社交内容的审核状态(通过/拒绝)。")
@PutMapping("/{id}/approval")
public Result<SocialContentItemResponse> updateApproval(@Parameter(description = "内容 ID", required = true) @PathVariable String id,
@RequestBody SocialContentApprovalRequest request) {
String userId = currentUserId();
if (userId == null) {
return Result.unauthorized();
}
SocialContentItemResponse response = socialContentService.updateApproval(userId, id, request.getApprovedForAi());
if (response == null) {
return Result.notFound("导入内容不存在");
}
return Result.success(response);
}
@Operation(summary = "删除社交内容", description = "删除指定的社交媒体内容记录。")
@DeleteMapping("/{id}")
public Result<Void> delete(@Parameter(description = "内容 ID", required = true) @PathVariable String id,
@Parameter(description = "是否保留已确认的洞察") @RequestParam(required = false, defaultValue = "true") Boolean keepConfirmedInsights) {
String userId = currentUserId();
if (userId == null) {
return Result.unauthorized();
}
boolean deleted = socialContentService.deleteByUser(userId, id, keepConfirmedInsights);
if (!deleted) {
return Result.notFound("导入内容不存在");
}
return Result.success();
}
private String currentUserId() {
return UserContextHolder.getCurrentUserId();
}
}
@@ -0,0 +1,92 @@
package com.emotion.controller;
import com.emotion.common.Result;
import com.emotion.dto.request.social.SocialInsightGenerateRequest;
import com.emotion.dto.request.social.SocialInsightUpdateRequest;
import com.emotion.dto.response.social.SocialProfileInsightResponse;
import com.emotion.service.SocialInsightService;
import com.emotion.util.UserContextHolder;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.DeleteMapping;
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.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import javax.validation.Valid;
import java.util.List;
@Validated
@RestController
@RequestMapping("/social/insight")
@Tag(name = "社交洞察管理", description = "社交媒体洞察生成、查询和更新接口")
public class SocialInsightController {
@Autowired
private SocialInsightService socialInsightService;
@Operation(summary = "生成社交洞察", description = "基于用户社交数据生成 AI 分析洞察报告。")
@PostMapping("/generate")
public Result<List<SocialProfileInsightResponse>> generate(@RequestBody(required = false) SocialInsightGenerateRequest request) {
String userId = currentUserId();
if (userId == null) {
return Result.unauthorized();
}
return Result.success(socialInsightService.generateInsights(userId, request));
}
@Operation(summary = "获取洞察列表", description = "查询当前用户的所有社交洞察记录。")
@GetMapping("/list")
public Result<List<SocialProfileInsightResponse>> list(@Parameter(description = "洞察状态") @RequestParam(required = false) String status) {
String userId = currentUserId();
if (userId == null) {
return Result.unauthorized();
}
return Result.success(socialInsightService.listByUser(userId, status));
}
@Operation(summary = "更新洞察记录", description = "更新已有洞察的状态或备注信息。")
@PutMapping("/{id}")
public Result<SocialProfileInsightResponse> update(@Parameter(description = "洞察 ID", required = true) @PathVariable String id,
@Valid @RequestBody SocialInsightUpdateRequest request) {
String userId = currentUserId();
if (userId == null) {
return Result.unauthorized();
}
try {
SocialProfileInsightResponse response = socialInsightService.updateByUser(userId, id, request);
if (response == null) {
return Result.notFound("画像不存在");
}
return Result.success(response);
} catch (IllegalArgumentException e) {
return Result.badRequest(e.getMessage());
}
}
@Operation(summary = "删除洞察记录", description = "删除指定的社交洞察记录。")
@DeleteMapping("/{id}")
public Result<Void> delete(@Parameter(description = "洞察 ID", required = true) @PathVariable String id) {
String userId = currentUserId();
if (userId == null) {
return Result.unauthorized();
}
boolean deleted = socialInsightService.deleteByUser(userId, id);
if (!deleted) {
return Result.notFound("画像不存在");
}
return Result.success();
}
private String currentUserId() {
return UserContextHolder.getCurrentUserId();
}
}
@@ -0,0 +1,48 @@
package com.emotion.controller;
import com.emotion.common.Result;
import com.emotion.dto.request.SystemConfigUpdateRequest;
import com.emotion.dto.response.SystemConfigResponse;
import com.emotion.service.SystemConfigService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* 系统配置管理控制器(管理端)
*
* @author system
* @date 2026-06-27
*/
@RestController
@RequestMapping("/admin/systemConfig")
@Tag(name = "系统配置管理", description = "系统配置的查询和更新功能")
public class SystemConfigController {
@Autowired
private SystemConfigService systemConfigService;
/**
* 获取所有可见配置列表
*/
@GetMapping("/list")
@Operation(summary = "获取配置列表", description = "获取所有可见的系统配置列表")
public Result<List<SystemConfigResponse>> list() {
List<SystemConfigResponse> configs = systemConfigService.getVisibleConfigList();
return Result.success(configs);
}
/**
* 批量更新配置
*/
@PutMapping("/update")
@Operation(summary = "批量更新配置", description = "批量更新系统配置值")
public Result<Void> update(@RequestBody @Validated List<SystemConfigUpdateRequest> requests) {
systemConfigService.batchUpdateConfigs(requests);
return Result.success("更新成功", null);
}
}
@@ -0,0 +1,60 @@
package com.emotion.controller;
import com.emotion.common.Result;
import com.emotion.dto.response.UserInfoResponse;
import com.emotion.service.TokenService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
/**
* Token控制器
* 提供基于请求头Authorization的token验证和用户信息获取功能
*
* @author huazhongmin
* @date 2025-07-23
*/
@RestController
@RequestMapping("/token")
@Tag(name = "Token管理", description = "Token验证和用户信息获取")
public class TokenController {
@Autowired
private TokenService tokenService;
/**
* 通过请求头中的token获取用户信息
* Token应该在请求头中以 "Authorization: Bearer {token}" 的形式传递
*/
@Operation(summary = "获取用户信息", description = "通过请求头中的token获取当前用户信息")
@GetMapping("/user-info")
public Result<UserInfoResponse> getUserInfoByToken(HttpServletRequest request) {
UserInfoResponse userInfo = tokenService.getUserInfoByToken(request);
return Result.success(userInfo);
}
/**
* 通过请求头中的token获取用户名
* Token应该在请求头中以 "Authorization: Bearer {token}" 的形式传递
*/
@GetMapping("/username")
@Operation(summary = "获取用户名", description = "通过请求头中的token获取当前用户名")
public Result<String> getUsernameByToken(HttpServletRequest request) {
String username = tokenService.getUsernameByToken(request);
return Result.success(username);
}
/**
* 验证请求头中的token并返回用户ID
* Token应该在请求头中以 "Authorization: Bearer {token}" 的形式传递
*/
@GetMapping("/validate")
@Operation(summary = "验证Token", description = "验证请求头中的token并返回用户ID")
public Result<String> validateTokenAndGetUserId(HttpServletRequest request) {
String userId = tokenService.validateTokenAndGetUserId(request);
return Result.success(userId);
}
}
@@ -0,0 +1,91 @@
package com.emotion.controller;
import com.emotion.common.PageResult;
import com.emotion.common.Result;
import com.emotion.dto.request.TopicInteractionCreateRequest;
import com.emotion.dto.request.TopicInteractionPageRequest;
import com.emotion.dto.request.TopicInteractionUpdateRequest;
import com.emotion.dto.response.TopicInteractionResponse;
import com.emotion.service.TopicInteractionService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
/**
* 话题互动控制器
*
* @author huazhongmin
* @date 2025-09-08
*/
@RestController
@RequestMapping("/topicInteraction")
@Tag(name = "话题互动管理", description = "话题互动记录的查询、创建、更新和删除接口")
public class TopicInteractionController {
@Autowired
private TopicInteractionService topicInteractionService;
/**
* 分页查询话题互动
*/
@Operation(summary = "分页查询互动记录", description = "分页查询话题互动记录列表。")
@GetMapping(value = "/page")
public Result<PageResult<TopicInteractionResponse>> getPage(@Validated TopicInteractionPageRequest request) {
PageResult<TopicInteractionResponse> pageResult = topicInteractionService.getPageWithResponse(request);
return Result.success(pageResult);
}
/**
* 根据ID获取话题互动
*/
@Operation(summary = "获取互动详情", description = "根据 ID 获取互动记录的详细信息。")
@GetMapping(value = "/detail")
public Result<TopicInteractionResponse> getById(@Parameter(description = "互动 ID") @RequestParam String id) {
TopicInteractionResponse response = topicInteractionService.getTopicInteractionResponseById(id);
if (response == null) {
return Result.notFound("话题互动不存在");
}
return Result.success(response);
}
/**
* 创建话题互动
*/
@Operation(summary = "创建互动记录", description = "创建一条新的话题互动记录。")
@PostMapping(value = "/create")
public Result<TopicInteractionResponse> create(@Valid @RequestBody TopicInteractionCreateRequest request) {
TopicInteractionResponse response = topicInteractionService.createTopicInteractionWithResponse(request);
return Result.success(response);
}
/**
* 更新话题互动
*/
@Operation(summary = "更新互动记录", description = "修改已有话题互动的内容。")
@PutMapping(value = "/update")
public Result<TopicInteractionResponse> update(@Valid @RequestBody TopicInteractionUpdateRequest request) {
TopicInteractionResponse response = topicInteractionService.updateTopicInteractionWithResponse(request);
if (response == null) {
return Result.notFound("话题互动不存在");
}
return Result.success(response);
}
/**
* 删除话题互动
*/
@Operation(summary = "删除互动记录", description = "删除指定的话题互动记录。")
@DeleteMapping(value = "/delete")
public Result<Void> delete(@Parameter(description = "互动 ID") @RequestParam String id) {
boolean deleted = topicInteractionService.deleteTopicInteraction(id);
if (!deleted) {
return Result.error("删除失败");
}
return Result.success();
}
}
@@ -0,0 +1,92 @@
package com.emotion.controller;
import com.emotion.common.Result;
import com.emotion.dto.request.tts.TtsTaskCreateRequest;
import com.emotion.dto.response.tts.TtsTaskResponse;
import com.emotion.service.TtsTaskService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;
import org.springframework.http.CacheControl;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
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.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import javax.validation.Valid;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.concurrent.TimeUnit;
@RestController
@RequestMapping("/tts")
@Tag(name = "语音合成(TTS)", description = "文字转语音任务创建和查询接口")
public class TtsController {
private final TtsTaskService ttsTaskService;
@Value("${emotion.tts.output-dir:/data/uploads/emotion-museum/tts}")
private String outputDir;
public TtsController(TtsTaskService ttsTaskService) {
this.ttsTaskService = ttsTaskService;
}
@Operation(summary = "创建语音合成任务", description = "根据文本内容创建语音合成任务,返回任务信息。")
@PostMapping("/tasks")
public Result<TtsTaskResponse> create(@Valid @RequestBody TtsTaskCreateRequest request) {
try {
return Result.success(ttsTaskService.createOrReuse(request));
} catch (IllegalArgumentException | IllegalStateException e) {
return Result.badRequest(e.getMessage());
}
}
@Operation(summary = "获取语音合成任务详情", description = "根据任务 ID 获取语音合成任务的详细信息。")
@GetMapping("/tasks/{id}")
public Result<TtsTaskResponse> detail(@Parameter(description = "任务 ID") @PathVariable String id) {
TtsTaskResponse response = ttsTaskService.getTask(id);
return response == null ? Result.notFound("TTS task not found") : Result.success(response);
}
@Operation(summary = "根据来源查询语音合成任务", description = "根据来源类型和来源 ID 查询已存在的语音合成任务。")
@GetMapping("/tasks/by-source")
public Result<TtsTaskResponse> bySource(@Parameter(description = "来源类型") @RequestParam String sourceType,
@Parameter(description = "来源 ID") @RequestParam String sourceId,
@Parameter(description = "音色") @RequestParam(required = false) String voice,
@Parameter(description = "语速") @RequestParam(required = false) Double speechRate,
@Parameter(description = "音调") @RequestParam(required = false) Double pitch,
@Parameter(description = "情绪") @RequestParam(required = false) String emotion) {
return Result.success(ttsTaskService.getBySource(sourceType, sourceId, voice, speechRate, pitch, emotion));
}
@Operation(summary = "获取音频文件", description = "返回已合成的音频音频文件(MP3 或 WAV 格式)。")
@GetMapping("/audio/{filename:.+}")
public ResponseEntity<Resource> audio(@Parameter(description = "音频文件名") @PathVariable String filename) {
if (filename.contains("..") || filename.contains("/") || filename.contains("\\")) {
return ResponseEntity.badRequest().build();
}
Path path = Paths.get(outputDir).resolve(filename).normalize();
FileSystemResource resource = new FileSystemResource(path);
if (!resource.exists() || !resource.isReadable()) {
return ResponseEntity.notFound().build();
}
MediaType mediaType = filename.endsWith(".wav")
? MediaType.valueOf("audio/wav")
: MediaType.valueOf("audio/mpeg");
return ResponseEntity.ok()
.contentType(mediaType)
.cacheControl(CacheControl.maxAge(30, TimeUnit.DAYS).cachePublic())
.body(resource);
}
}
@@ -0,0 +1,121 @@
package com.emotion.controller;
import com.emotion.common.PageResult;
import com.emotion.common.Result;
import com.emotion.dto.request.UserCreateRequest;
import com.emotion.dto.request.UserPageRequest;
import com.emotion.dto.request.UserProfileUpdateRequest;
import com.emotion.dto.request.UserUpdateRequest;
import com.emotion.dto.response.UserResponse;
import com.emotion.service.UserService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
/**
* 用户控制器
*
* @author huazhongmin
* @date 2025-07-22
*/
@RestController
@RequestMapping("/user")
@Tag(name = "用户管理", description = "管理员对用户的增删改查功能")
public class UserController {
@Autowired
private UserService userService;
/**
* 分页查询用户
*/
@Operation(summary = "分页查询用户", description = "分页查询用户列表")
@GetMapping(value = "/page")
public Result<PageResult<UserResponse>> getPage(@Validated UserPageRequest request) {
PageResult<UserResponse> pageResult = userService.getPageWithResponse(request);
return Result.success(pageResult);
}
/**
* 根据ID获取用户信息
*/
@Operation(summary = "根据ID获取用户信息", description = "根据ID获取用户详情")
@GetMapping(value = "/detail")
public Result<UserResponse> getById(
@Parameter(description = "用户ID", required = true)
@RequestParam String id) {
UserResponse user = userService.getUserResponseById(id);
if (user == null) {
return Result.notFound("用户不存在");
}
return Result.success(user);
}
/**
* 创建用户
*/
@Operation(summary = "创建用户", description = "创建新用户")
@PostMapping(value = "/create")
public Result<UserResponse> create(@Valid @RequestBody UserCreateRequest request) {
UserResponse user = userService.createUserWithResponse(request);
return Result.success(user);
}
/**
* 更新用户
*/
@Operation(summary = "更新用户", description = "更新指定用户信息")
@PutMapping(value = "/update")
public Result<UserResponse> update(@Valid @RequestBody UserUpdateRequest request) {
UserResponse updatedUser = userService.updateUserWithResponse(request);
if (updatedUser == null) {
return Result.error("更新失败");
}
return Result.success(updatedUser);
}
/**
* 删除用户
*/
@Operation(summary = "删除用户", description = "删除指定用户")
@DeleteMapping(value = "/delete")
public Result<Void> delete(
@Parameter(description = "用户ID", required = true)
@RequestParam String id) {
boolean deleted = userService.deleteUser(id);
if (!deleted) {
return Result.error("删除失败");
}
return Result.success();
}
/**
* 获取当前用户个人资料
*/
@Operation(summary = "获取当前用户个人资料", description = "获取当前登录用户的个人资料")
@GetMapping(value = "/profile")
public Result<UserResponse> getCurrentUserProfile() {
UserResponse user = userService.getCurrentUserProfileWithResponse();
if (user == null) {
return Result.unauthorized("用户未登录");
}
return Result.success(user);
}
/**
* 更新当前用户个人资料
*/
@PutMapping(value = "/profile")
public Result<UserResponse> updateCurrentUserProfile(@Valid @RequestBody UserProfileUpdateRequest request) {
UserResponse updatedUser = userService.updateCurrentUserProfileWithResponse(request);
if (updatedUser == null) {
return Result.unauthorized("用户未登录");
}
return Result.success(updatedUser);
}
}
@@ -0,0 +1,123 @@
package com.emotion.controller;
import com.emotion.common.PageResult;
import com.emotion.common.Result;
import com.emotion.dto.request.userprofile.UserProfileCreateRequest;
import com.emotion.dto.request.userprofile.UserProfilePageRequest;
import com.emotion.dto.request.userprofile.UserProfileUpdateRequest;
import com.emotion.dto.response.userprofile.UserProfileResponse;
import com.emotion.service.UserProfileService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import java.util.List;
/**
* 用户档案控制器
*
* @author huazhongmin
* @date 2025-12-21
*/
@RestController
@RequestMapping("/user-profile")
@Tag(name = "用户档案管理", description = "用户档案的查询、创建、更新、删除和 AI 设置管理接口")
public class UserProfileController {
@Autowired
private UserProfileService userProfileService;
/**
* 新增档案
*/
@Operation(summary = "创建用户档案", description = "为当前用户创建个人档案,包含昵称、MBTI 人格类型、童年经历等。")
@PostMapping("/create")
public Result<UserProfileResponse> create(@Valid @RequestBody UserProfileCreateRequest request) {
UserProfileResponse response = userProfileService.createProfile(request);
return Result.success(response);
}
/**
* 删除档案
*/
@Operation(summary = "删除用户档案", description = "删除指定的用户档案。")
@DeleteMapping("/delete")
public Result<Void> delete(@Parameter(description = "档案 ID") @RequestParam String id) {
boolean success = userProfileService.deleteProfile(id);
if (success) {
return Result.success();
} else {
return Result.error("删除失败");
}
}
/**
* 修改档案
*/
@Operation(summary = "更新用户档案", description = "更新当前用户的个人档案信息。")
@PutMapping("/update")
public Result<UserProfileResponse> update(@Valid @RequestBody UserProfileUpdateRequest request) {
UserProfileResponse response = userProfileService.updateProfile(request);
return Result.success(response);
}
/**
* 根据ID查询详情
*/
@Operation(summary = "获取档案详情", description = "根据 ID 获取用户档案详情。")
@GetMapping("/detail")
public Result<UserProfileResponse> getById(@Parameter(description = "档案 ID") @RequestParam String id) {
UserProfileResponse response = userProfileService.getProfileById(id);
if (response == null) {
return Result.notFound("档案不存在");
}
return Result.success(response);
}
/**
* 获取当前登录用户的档案
*/
@Operation(summary = "获取当前用户档案", description = "获取当前登录用户的个人档案信息。")
@GetMapping("/me")
public Result<UserProfileResponse> getCurrentProfile() {
UserProfileResponse response = userProfileService.getCurrentUserProfile();
// 如果不存在,返回null data,不报错
return Result.success(response);
}
/**
* 分页查询
*/
@Operation(summary = "分页查询用户档案", description = "分页查询用户档案列表。")
@GetMapping("/page")
public Result<PageResult<UserProfileResponse>> getPage(@Validated UserProfilePageRequest request) {
PageResult<UserProfileResponse> pageResult = userProfileService.getProfilePage(request);
return Result.success(pageResult);
}
/**
* 列表查询
*/
@Operation(summary = "列表查询用户档案", description = "列表查询用户档案。")
@GetMapping("/list")
public Result<List<UserProfileResponse>> getList(@Validated UserProfilePageRequest request) {
List<UserProfileResponse> list = userProfileService.getProfileList(request);
return Result.success(list);
}
/**
* 迁移用户档案中的生命事件数据到t_life_event表
* 将childhood、peak、valley数据迁移到生命事件表
* 注意:此接口为一次性数据迁移接口,迁移完成后可删除
*/
@Operation(summary = "迁移生活事件", description = "将生活事件模块的数据迁移到用户档案中。")
@PostMapping("/migrateLifeEvents")
public Result<Integer> migrateLifeEvents() {
int count = userProfileService.migrateLifeEventsFromProfiles();
return Result.success(count);
}
}
@@ -0,0 +1,121 @@
package com.emotion.controller;
import com.emotion.common.PageResult;
import com.emotion.common.Result;
import com.emotion.dto.request.UserStatsCreateRequest;
import com.emotion.dto.request.UserStatsIncrementRequest;
import com.emotion.dto.request.UserStatsPageRequest;
import com.emotion.dto.request.UserStatsUpdateValueRequest;
import com.emotion.dto.response.UserStatsResponse;
import com.emotion.service.UserStatsService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import javax.validation.constraints.NotNull;
import java.util.List;
/**
* 用户统计控制器
*
* @author huazhongmin
* @date 2025-07-23
*/
@RestController
@RequestMapping("/userStats")
@Validated
@Tag(name = "用户统计管理", description = "用户统计数据的查询、创建、更新、增量操作和重新计算接口")
public class UserStatsController {
@Autowired
private UserStatsService userStatsService;
/**
* 分页查询用户统计
*/
@Operation(summary = "分页查询用户统计", description = "分页查询用户统计数据。")
@GetMapping(value = "/page")
public Result<PageResult<UserStatsResponse>> getPage(@Validated UserStatsPageRequest request) {
PageResult<UserStatsResponse> pageResult = userStatsService.getPageWithResponse(request);
return Result.success(pageResult);
}
/**
* 创建或更新用户统计
*/
@Operation(summary = "创建或更新用户统计", description = "创建新用户统计数据或更新已有数据。")
@PostMapping(value = "/createOrUpdate")
public Result<UserStatsResponse> createOrUpdate(@Valid @RequestBody UserStatsCreateRequest request) {
UserStatsResponse stats = userStatsService.createOrUpdateUserStatsWithResponse(request);
return Result.success(stats);
}
/**
* 更新用户统计值
*/
@Operation(summary = "更新用户统计值", description = "直接更新指定统计项的值。")
@PutMapping(value = "/updateStatsValue")
public Result<Void> updateStatsValue(@Valid @RequestBody UserStatsUpdateValueRequest request) {
boolean updated = userStatsService.updateStatsValue(request);
if (!updated) {
return Result.error("更新失败");
}
return Result.success();
}
/**
* 增加用户统计值
*/
@Operation(summary = "增加用户统计值", description = "对指定统计项进行增量累加操作。")
@PutMapping(value = "/incrementStatsValue")
public Result<Void> incrementStatsValue(@Valid @RequestBody UserStatsIncrementRequest request) {
boolean updated = userStatsService.incrementStatsValue(request);
if (!updated) {
return Result.error("增加失败");
}
return Result.success();
}
/**
* 重新计算用户统计
*/
@Operation(summary = "重新计算用户统计", description = "基于原始数据重新计算用户的统计值。")
@PutMapping(value = "/recalculateUserStats")
public Result<Void> recalculateUserStats(@Parameter(description = "用户 ID") @RequestParam String userId) {
boolean recalculated = userStatsService.recalculateUserStats(userId);
if (!recalculated) {
return Result.error("重新计算失败");
}
return Result.success();
}
/**
* 重新计算所有用户统计
*/
@Operation(summary = "重新计算所有用户统计", description = "重新计算所有用户的统计值(管理员操作)。")
@PutMapping(value = "/recalculateAll")
public Result<Void> recalculateAllUserStats() {
boolean recalculated = userStatsService.recalculateAllUserStats();
if (!recalculated) {
return Result.error("重新计算失败");
}
return Result.success();
}
/**
* 删除过期的统计数据
*/
@Operation(summary = "删除过期统计数据", description = "清理超过指定天数的过期统计数据。")
@DeleteMapping(value = "/deleteExpired")
public Result<Void> deleteExpiredStats(@Parameter(description = "过期天数阈值") @RequestParam(defaultValue = "30") Integer days) {
boolean deleted = userStatsService.deleteExpiredStats(days);
if (!deleted) {
return Result.error("删除失败");
}
return Result.success();
}
}
@@ -0,0 +1,24 @@
package com.emotion.dto.request;
import io.swagger.v3.oas.annotations.media.Schema;
import javax.validation.constraints.NotBlank;
import lombok.Data;
/**
* 管理员修改密码请求(修改自己的密码,需要原密码)
*
* @author huazhongmin
* @date 2026-05-10
*/
@Data
@Schema(description = "管理员修改密码请求")
public class AdminChangePasswordRequest {
@NotBlank(message = "原密码不能为空")
@Schema(description = "原密码")
private String oldPassword;
@NotBlank(message = "新密码不能为空")
@Schema(description = "新密码")
private String newPassword;
}
@@ -0,0 +1,93 @@
package com.emotion.dto.request;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import javax.validation.constraints.Email;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Pattern;
import javax.validation.constraints.Size;
/**
* 管理员创建请求
*
* @author huazhongmin
* @date 2025-10-27
*/
@Data
public class AdminCreateRequest {
/**
* 管理员账号
*/
@NotBlank(message = "账号不能为空")
@Size(min = 3, max = 50, message = "账号长度必须在3-50个字符之间")
@Schema(description = "管理员账号")
private String account;
/**
* 密码
*/
@NotBlank(message = "密码不能为空")
@Size(min = 6, max = 20, message = "密码长度必须在6-20个字符之间")
@Schema(description = "初始密码")
private String password;
/**
* 管理员姓名
*/
@NotBlank(message = "姓名不能为空")
@Size(min = 2, max = 50, message = "姓名长度必须在2-50个字符之间")
@Schema(description = "管理员姓名")
private String username;
/**
* 邮箱
*/
@Email(message = "邮箱格式不正确")
@Size(max = 100, message = "邮箱长度不能超过100个字符")
@Schema(description = "邮箱")
private String email;
/**
* 手机号
*/
@Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确")
@Schema(description = "手机号")
private String phone;
/**
* 头像URL
*/
@Size(max = 500, message = "头像URL长度不能超过500个字符")
@Schema(description = "头像URL")
private String avatar;
/**
* 角色
*/
@NotBlank(message = "角色不能为空")
@Pattern(regexp = "^(super_admin|admin|operator)$", message = "角色必须是super_admin、admin或operator")
@Schema(description = "角色(super_admin/admin/operator")
private String role;
/**
* 权限列表(JSON格式)
*/
@Schema(description = "权限列表(JSON格式)")
private String permissions;
/**
* 所属部门
*/
@Size(max = 50, message = "部门长度不能超过50个字符")
@Schema(description = "所属部门")
private String department;
/**
* 职位
*/
@Size(max = 50, message = "职位长度不能超过50个字符")
@Schema(description = "职位")
private String position;
}
@@ -0,0 +1,33 @@
package com.emotion.dto.request;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Size;
/**
* 管理员登录请求
*
* @author emotion-museum
* @date 2025-10-27
*/
@Data
public class AdminLoginRequest {
/**
* 账号
*/
@NotBlank(message = "账号不能为空")
@Size(min = 3, max = 50, message = "账号长度必须在3-50个字符之间")
@Schema(description = "管理员账号")
private String account;
/**
* 密码
*/
@NotBlank(message = "密码不能为空")
@Size(min = 6, max = 20, message = "密码长度必须在6-20个字符之间")
@Schema(description = "管理员密码")
private String password;
}
@@ -0,0 +1,67 @@
package com.emotion.dto.request;
import io.swagger.v3.oas.annotations.media.Schema;
import com.emotion.common.BasePageRequest;
import lombok.Data;
import lombok.EqualsAndHashCode;
import javax.validation.constraints.Size;
/**
* 管理员分页查询请求
*
* @author huazhongmin
* @date 2025-10-27
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class AdminPageRequest extends BasePageRequest {
/**
* 账号
*/
@Size(max = 50, message = "账号长度不能超过50个字符")
@Schema(description = "账号(模糊搜索)")
private String account;
/**
* 姓名
*/
@Size(max = 50, message = "姓名长度不能超过50个字符")
@Schema(description = "姓名(模糊搜索)")
private String username;
/**
* 邮箱
*/
@Size(max = 100, message = "邮箱长度不能超过100个字符")
@Schema(description = "邮箱(模糊搜索)")
private String email;
/**
* 手机号
*/
@Size(max = 20, message = "手机号长度不能超过20个字符")
@Schema(description = "手机号(模糊搜索)")
private String phone;
/**
* 角色
*/
@Size(max = 20, message = "角色长度不能超过20个字符")
@Schema(description = "角色")
private String role;
/**
* 状态: 0-禁用, 1-正常
*/
@Schema(description = "状态(0-禁用,1-正常)")
private Integer status;
/**
* 部门
*/
@Size(max = 50, message = "部门长度不能超过50个字符")
@Schema(description = "部门(模糊搜索)")
private String department;
}
@@ -0,0 +1,24 @@
package com.emotion.dto.request;
import io.swagger.v3.oas.annotations.media.Schema;
import javax.validation.constraints.NotBlank;
import lombok.Data;
/**
* 超级管理员重置其他管理员密码请求(不需要原密码)
*
* @author huazhongmin
* @date 2026-05-10
*/
@Data
@Schema(description = "管理员重置密码请求")
public class AdminResetPasswordRequest {
@NotBlank(message = "管理员ID不能为空")
@Schema(description = "管理员ID")
private String id;
@NotBlank(message = "新密码不能为空")
@Schema(description = "新密码")
private String newPassword;
}
@@ -0,0 +1,88 @@
package com.emotion.dto.request;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import javax.validation.constraints.Email;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Pattern;
import javax.validation.constraints.Size;
/**
* 管理员更新请求
*
* @author huazhongmin
* @date 2025-10-27
*/
@Data
public class AdminUpdateRequest {
/**
* ID
*/
@NotBlank(message = "ID不能为空")
@Schema(description = "管理员ID")
private String id;
/**
* 管理员姓名
*/
@Size(min = 2, max = 50, message = "姓名长度必须在2-50个字符之间")
@Schema(description = "管理员姓名")
private String username;
/**
* 邮箱
*/
@Email(message = "邮箱格式不正确")
@Size(max = 100, message = "邮箱长度不能超过100个字符")
@Schema(description = "邮箱")
private String email;
/**
* 手机号
*/
@Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确")
@Schema(description = "手机号")
private String phone;
/**
* 头像URL
*/
@Size(max = 500, message = "头像URL长度不能超过500个字符")
@Schema(description = "头像URL")
private String avatar;
/**
* 角色
*/
@Pattern(regexp = "^(super_admin|admin|operator)$", message = "角色必须是super_admin、admin或operator")
@Schema(description = "角色(super_admin/admin/operator")
private String role;
/**
* 权限列表(JSON格式)
*/
@Schema(description = "权限列表(JSON格式)")
private String permissions;
/**
* 状态: 0-禁用, 1-正常
*/
@Schema(description = "状态(0-禁用,1-正常)")
private Integer status;
/**
* 所属部门
*/
@Size(max = 50, message = "部门长度不能超过50个字符")
@Schema(description = "所属部门")
private String department;
/**
* 职位
*/
@Size(max = 50, message = "职位长度不能超过50个字符")
@Schema(description = "职位")
private String position;
}
@@ -0,0 +1,33 @@
package com.emotion.dto.request;
import lombok.Data;
import lombok.EqualsAndHashCode;
import javax.validation.constraints.NotBlank;
/**
* AI聊天请求类
*
* @author huazhongmin
* @date 2025-07-24
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class AiChatRequest extends BaseRequest {
/**
* 会话ID
*/
private String conversationId;
/**
* 消息内容
*/
@NotBlank(message = "消息内容不能为空")
private String message;
/**
* 用户ID
*/
private String userId;
}
@@ -0,0 +1,29 @@
package com.emotion.dto.request;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
/**
* AI配置调用次数统计请求
* 用于管理后台仪表盘:按 t_ai_config 配置统计 t_coze_api_call 调用次数
*
* @author system
* @date 2025-12-24
*/
@Data
@Schema(description = "AI配置调用次数统计请求")
public class AiConfigCallStatsRequest {
/**
* 返回条数限制(默认 20,最大 200)
*/
@Min(1)
@Max(200)
@Schema(description = "返回条数限制(默认20,最大200", example = "20")
private Integer limit;
}
@@ -0,0 +1,28 @@
package com.emotion.dto.request;
import lombok.Data;
import lombok.EqualsAndHashCode;
import javax.validation.constraints.NotBlank;
/**
* AI总结请求类
*
* @author huazhongmin
* @date 2025-07-24
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class AiSummaryRequest extends BaseRequest {
/**
* 会话ID
*/
@NotBlank(message = "会话ID不能为空")
private String conversationId;
/**
* 用户ID
*/
private String userId;
}
@@ -0,0 +1,28 @@
package com.emotion.dto.request;
import com.emotion.common.BasePageRequest;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* 接口端点分页查询请求
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class ApiEndpointListRequest extends BasePageRequest {
/**
* HTTP 方法过滤:GET/POST/PUT/DELETE/PATCH
*/
private String method;
/**
* 标签过滤
*/
private String tags;
/**
* 是否仅显示废弃接口
*/
private Integer deprecated;
}
@@ -0,0 +1,27 @@
package com.emotion.dto.request;
import lombok.Data;
import javax.validation.constraints.NotBlank;
import java.util.Map;
/**
* 代理测试请求参数
*/
@Data
public class ApiTestProxyRequest {
@NotBlank(message = "请求方法不能为空")
private String method;
@NotBlank(message = "接口路径不能为空")
private String path;
private String body;
private Map<String, String> headers;
private Map<String, String> params;
private Integer timeoutSeconds;
}
@@ -0,0 +1,23 @@
package com.emotion.dto.request;
import lombok.Data;
/**
* 基础请求类
*
* @author huazhongmin
* @date 2025-07-23
*/
@Data
public class BaseRequest {
/**
* 请求ID,用于链路追踪
*/
private String requestId;
/**
* 客户端时间戳
*/
private Long timestamp;
}
@@ -0,0 +1,25 @@
package com.emotion.dto.request;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* 聊天统计请求类
*
* @author huazhongmin
* @date 2025-07-24
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class ChatStatsRequest extends BaseRequest {
/**
* 用户ID
*/
private String userId;
/**
* 会话ID
*/
private String conversationId;
}
@@ -0,0 +1,38 @@
package com.emotion.dto.request;
import lombok.Data;
import lombok.EqualsAndHashCode;
import javax.validation.constraints.NotBlank;
/**
* 对话创建请求类
*
* @author huazhongmin
* @date 2025-07-24
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class ConversationCreateRequest extends BaseRequest {
/**
* 对话ID(更新时使用)
*/
private String id;
/**
* 用户ID
*/
@NotBlank(message = "用户ID不能为空")
private String userId;
/**
* 对话标题
*/
private String title;
/**
* 对话类型
*/
private String type;
}
@@ -0,0 +1,30 @@
package com.emotion.dto.request;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* 对话分页请求类
*
* @author huazhongmin
* @date 2025-09-08
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class ConversationPageRequest extends PageRequest {
/**
* 用户ID(可选)
*/
private String userId;
/**
* 对话状态(可选)
*/
private String status;
/**
* 对话类型(可选)
*/
private String type;
}
@@ -0,0 +1,56 @@
package com.emotion.dto.request;
import lombok.Data;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import java.util.List;
/**
* 创建日记评论请求DTO
*
* @author huazhongmin
* @date 2025-07-23
*/
@Data
public class DiaryCommentCreateRequest {
/**
* 评论ID (用于更新操作)
*/
private String id;
/**
* 日记ID
*/
@NotBlank(message = "日记ID不能为空")
private String diaryId;
/**
* 用户ID
*/
@NotBlank(message = "用户ID不能为空")
private String userId;
/**
* 评论内容
*/
@NotBlank(message = "评论内容不能为空")
private String content;
/**
* 评论图片
*/
private List<String> images;
/**
* 父评论ID (用于回复功能)
*/
private String parentCommentId;
/**
* 是否匿名评论: 0-实名, 1-匿名
*/
@NotNull(message = "是否匿名不能为空")
private Integer isAnonymous;
}
@@ -0,0 +1,38 @@
package com.emotion.dto.request;
import com.emotion.common.BasePageRequest;
import lombok.Data;
import lombok.EqualsAndHashCode;
import javax.validation.constraints.Pattern;
/**
* 日记评论分页请求类
*
* @author huazhongmin
* @date 2025-09-08
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class DiaryCommentPageRequest extends BasePageRequest {
/**
* 日记ID(可选)
*/
private String diaryId;
/**
* 用户ID(可选)
*/
private String userId;
/**
* 评论类型(可选)
*/
private String commentType;
/**
* 是否只查询顶级评论(可选)
*/
private Boolean topLevelOnly;
}
@@ -0,0 +1,97 @@
package com.emotion.dto.request;
import lombok.Data;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import java.math.BigDecimal;
import java.util.List;
/**
* 创建日记请求DTO
*
* @author huazhongmin
* @date 2025-07-23
*/
@Data
public class DiaryPostCreateRequest {
/**
* 日记标题
*/
@Size(max = 200, message = "日记标题长度不能超过200个字符")
private String title;
/**
* 日记内容
*/
@NotBlank(message = "日记内容不能为空")
private String content;
/**
* 图片列表
*/
private List<String> images;
/**
* 视频列表
*/
private List<String> videos;
/**
* 发布地点
*/
@Size(max = 200, message = "发布地点长度不能超过200个字符")
private String location;
/**
* 纬度
*/
private BigDecimal latitude;
/**
* 经度
*/
private BigDecimal longitude;
/**
* 天气信息
*/
@Size(max = 50, message = "天气信息长度不能超过50个字符")
private String weather;
/**
* 心情状态
*/
@Size(max = 50, message = "心情状态长度不能超过50个字符")
private String mood;
/**
* 心情评分 (0-10)
*/
private BigDecimal moodScore;
/**
* 标签列表
*/
private List<String> tags;
/**
* 是否公开: 0-仅自己可见, 1-公开
*/
@NotNull(message = "是否公开不能为空")
private Integer isPublic;
/**
* 是否匿名发布: 0-实名, 1-匿名
*/
@NotNull(message = "是否匿名不能为空")
private Integer isAnonymous;
/**
* 用户ID
*/
@NotBlank(message = "用户ID不能为空")
private String userId;
}
@@ -0,0 +1,41 @@
package com.emotion.dto.request;
import com.emotion.common.BasePageRequest;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* 日记分页请求类
*
* @author huazhongmin
* @date 2025-09-08
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class DiaryPostPageRequest extends BasePageRequest {
/**
* 用户ID(可选)
*/
private String userId;
/**
* 是否只查询公开日记(可选)
*/
private Boolean publicOnly;
/**
* 是否只查询精选日记(可选)
*/
private Boolean featuredOnly;
/**
* 心情状态(可选)
*/
private String mood;
/**
* 标签(可选)
*/
private String tag;
}
@@ -0,0 +1,98 @@
package com.emotion.dto.request;
import lombok.Data;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Size;
import java.math.BigDecimal;
import java.util.List;
/**
* 更新日记请求DTO
*
* @author huazhongmin
* @date 2025-07-23
*/
@Data
public class DiaryPostUpdateRequest {
/**
* 日记ID
*/
@NotBlank(message = "日记ID不能为空")
private String id;
/**
* 日记标题
*/
@Size(max = 200, message = "日记标题长度不能超过200个字符")
private String title;
/**
* 日记内容
*/
private String content;
/**
* 图片列表
*/
private List<String> images;
/**
* 视频列表
*/
private List<String> videos;
/**
* 发布地点
*/
@Size(max = 200, message = "发布地点长度不能超过200个字符")
private String location;
/**
* 纬度
*/
private BigDecimal latitude;
/**
* 经度
*/
private BigDecimal longitude;
/**
* 天气信息
*/
@Size(max = 50, message = "天气信息长度不能超过50个字符")
private String weather;
/**
* 心情状态
*/
@Size(max = 50, message = "心情状态长度不能超过50个字符")
private String mood;
/**
* 心情评分 (0-10)
*/
private BigDecimal moodScore;
/**
* 标签列表
*/
private List<String> tags;
/**
* 是否公开: 0-仅自己可见, 1-公开
*/
private Integer isPublic;
/**
* 是否匿名发布: 0-实名, 1-匿名
*/
private Integer isAnonymous;
/**
* 状态: draft-草稿, published-已发布, hidden-隐藏, deleted-已删除
*/
private String status;
}
@@ -0,0 +1,56 @@
package com.emotion.dto.request;
import lombok.Data;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
/**
* 情绪分析创建请求类
*
* @author huazhongmin
* @date 2025-09-08
*/
@Data
public class EmotionAnalysisCreateRequest {
/**
* 消息ID
*/
@NotBlank(message = "消息ID不能为空")
@Size(max = 64, message = "消息ID长度不能超过64个字符")
private String messageId;
/**
* 用户ID
*/
@NotBlank(message = "用户ID不能为空")
@Size(max = 32, message = "用户ID长度不能超过32个字符")
private String userId;
/**
* 主要情绪
*/
@NotBlank(message = "主要情绪不能为空")
@Size(max = 32, message = "主要情绪长度不能超过32个字符")
private String primaryEmotion;
/**
* 情绪极性
*/
@Size(max = 9, message = "情绪极性长度不能超过9个字符")
private String polarity;
/**
* 情绪强度
*/
@NotNull(message = "情绪强度不能为空")
private Double intensity;
/**
* 置信度
*/
@NotNull(message = "置信度不能为空")
private Double confidence;
}
@@ -0,0 +1,42 @@
package com.emotion.dto.request;
import com.emotion.common.BasePageRequest;
import lombok.Data;
import lombok.EqualsAndHashCode;
import javax.validation.constraints.Size;
/**
* 情绪分析分页请求类
*
* @author huazhongmin
* @date 2025-09-08
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class EmotionAnalysisPageRequest extends BasePageRequest {
/**
* 用户ID(可选)
*/
@Size(max = 32, message = "用户ID长度不能超过32个字符")
private String userId;
/**
* 消息ID(可选)
*/
@Size(max = 64, message = "消息ID长度不能超过64个字符")
private String messageId;
/**
* 主要情绪(可选)
*/
@Size(max = 32, message = "主要情绪长度不能超过32个字符")
private String primaryEmotion;
/**
* 情绪极性(可选)
*/
@Size(max = 9, message = "情绪极性长度不能超过9个字符")
private String polarity;
}
@@ -0,0 +1,57 @@
package com.emotion.dto.request;
import lombok.Data;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Size;
/**
* 情绪分析更新请求类
*
* @author huazhongmin
* @date 2025-09-08
*/
@Data
public class EmotionAnalysisUpdateRequest {
/**
* 情绪分析ID
*/
@NotBlank(message = "情绪分析ID不能为空")
@Size(max = 32, message = "情绪分析ID长度不能超过32个字符")
private String id;
/**
* 消息ID
*/
@Size(max = 64, message = "消息ID长度不能超过64个字符")
private String messageId;
/**
* 用户ID
*/
@Size(max = 32, message = "用户ID长度不能超过32个字符")
private String userId;
/**
* 主要情绪
*/
@Size(max = 32, message = "主要情绪长度不能超过32个字符")
private String primaryEmotion;
/**
* 情绪极性
*/
@Size(max = 9, message = "情绪极性长度不能超过9个字符")
private String polarity;
/**
* 情绪强度
*/
private Double intensity;
/**
* 置信度
*/
private Double confidence;
}
@@ -0,0 +1,93 @@
package com.emotion.dto.request;
import lombok.Data;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.List;
/**
* 情绪记录创建请求类
*
* @author huazhongmin
* @date 2025-09-08
*/
@Data
public class EmotionRecordCreateRequest {
/**
* 用户ID
*/
@NotBlank(message = "用户ID不能为空")
@Size(max = 32, message = "用户ID长度不能超过32个字符")
private String userId;
/**
* 记录日期
*/
@NotNull(message = "记录日期不能为空")
private LocalDate recordDate;
/**
* 情绪类型
*/
@NotBlank(message = "情绪类型不能为空")
@Size(max = 32, message = "情绪类型长度不能超过32个字符")
private String emotionType;
/**
* 情绪强度
*/
@NotNull(message = "情绪强度不能为空")
private BigDecimal intensity;
/**
* 触发因素
*/
@Size(max = 768, message = "触发因素长度不能超过768个字符")
private String triggers;
/**
* 描述
*/
@Size(max = 768, message = "描述长度不能超过768个字符")
private String description;
/**
* 标签
*/
private List<String> tags;
/**
* 天气
*/
@Size(max = 64, message = "天气长度不能超过64个字符")
private String weather;
/**
* 地点
*/
@Size(max = 128, message = "地点长度不能超过128个字符")
private String location;
/**
* 活动
*/
@Size(max = 128, message = "活动长度不能超过128个字符")
private String activity;
/**
* 相关人物
*/
@Size(max = 256, message = "相关人物长度不能超过256个字符")
private String people;
/**
* 备注
*/
@Size(max = 1024, message = "备注长度不能超过1024个字符")
private String notes;
}
@@ -0,0 +1,36 @@
package com.emotion.dto.request;
import com.emotion.common.BasePageRequest;
import lombok.Data;
import lombok.EqualsAndHashCode;
import javax.validation.constraints.Size;
/**
* 情绪记录分页请求类
*
* @author huazhongmin
* @date 2025-09-08
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class EmotionRecordPageRequest extends BasePageRequest {
/**
* 用户ID(可选)
*/
@Size(max = 32, message = "用户ID长度不能超过32个字符")
private String userId;
/**
* 情绪类型(可选)
*/
@Size(max = 32, message = "情绪类型长度不能超过32个字符")
private String emotionType;
/**
* 地点(可选)
*/
@Size(max = 128, message = "地点长度不能超过128个字符")
private String location;
}
@@ -0,0 +1,89 @@
package com.emotion.dto.request;
import lombok.Data;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Size;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.List;
/**
* 情绪记录更新请求类
*
* @author huazhongmin
* @date 2025-09-08
*/
@Data
public class EmotionRecordUpdateRequest {
/**
* 情绪记录ID
*/
@NotBlank(message = "情绪记录ID不能为空")
@Size(max = 32, message = "情绪记录ID长度不能超过32个字符")
private String id;
/**
* 记录日期
*/
private LocalDate recordDate;
/**
* 情绪类型
*/
@Size(max = 32, message = "情绪类型长度不能超过32个字符")
private String emotionType;
/**
* 情绪强度
*/
private BigDecimal intensity;
/**
* 触发因素
*/
@Size(max = 768, message = "触发因素长度不能超过768个字符")
private String triggers;
/**
* 描述
*/
@Size(max = 768, message = "描述长度不能超过768个字符")
private String description;
/**
* 标签
*/
private List<String> tags;
/**
* 天气
*/
@Size(max = 64, message = "天气长度不能超过64个字符")
private String weather;
/**
* 地点
*/
@Size(max = 128, message = "地点长度不能超过128个字符")
private String location;
/**
* 活动
*/
@Size(max = 128, message = "活动长度不能超过128个字符")
private String activity;
/**
* 相关人物
*/
@Size(max = 256, message = "相关人物长度不能超过256个字符")
private String people;
/**
* 备注
*/
@Size(max = 1024, message = "备注长度不能超过1024个字符")
private String notes;
}
@@ -0,0 +1,14 @@
package com.emotion.dto.request;
import lombok.Data;
/**
* 情绪总结生成请求类
*
* @author huazhongmin
* @date 2025-09-08
*/
@Data
public class EmotionSummaryGenerateRequest {
// 目前情绪总结生成不需要额外参数
}
@@ -0,0 +1,14 @@
package com.emotion.dto.request;
import lombok.Data;
/**
* 情绪总结状态请求类
*
* @author huazhongmin
* @date 2025-09-08
*/
@Data
public class EmotionSummaryStatusRequest {
// 目前情绪总结状态查询不需要额外参数
}
@@ -0,0 +1,85 @@
package com.emotion.dto.request;
import lombok.Data;
import lombok.EqualsAndHashCode;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Size;
import java.util.Map;
/**
* 爽文剧本创建请求
*
* @author huazhongmin
* @date 2025-12-22
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class EpicScriptCreateRequest extends BaseRequest {
/**
* 剧本标题
*/
@NotBlank(message = "剧本标题不能为空")
@Size(max = 200, message = "剧本标题长度不能超过200个字符")
private String title;
/**
* 剧本主题/渴望
*/
private String theme;
/**
* 剧本风格: career-职场逆袭, love-情感圆满, fantasy-玄幻觉醒
*/
private String style;
/**
* 篇幅长度: medium-标准篇, long-长篇
*/
private String length;
/**
* 序幕:低谷回响
*/
private String plotIntro;
/**
* 转折:契机出现
*/
private String plotTurning;
/**
* 高潮:命运抉择
*/
private String plotClimax;
/**
* 结局:新的开始
*/
private String plotEnding;
/**
* 完整剧情JSON结构
*/
private Map<String, Object> plotJson;
/**
* 是否当前选中
*/
private Boolean isSelected;
/**
* 角色信息(前端传入,用于AI生成)
*/
private String characterInfo;
/**
* 过往经历关键词(前端传入,用于AI生成)
*/
private String lifeEventsSummary;
/**
* 是否使用用户已确认的社交画像增强剧本生成。
*/
private Boolean useSocialInsights;
}
@@ -0,0 +1,32 @@
package com.emotion.dto.request;
import lombok.Data;
import lombok.EqualsAndHashCode;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Size;
@Data
@EqualsAndHashCode(callSuper = true)
public class EpicScriptInspirationRequest extends BaseRequest {
@NotBlank(message = "灵感内容不能为空")
@Size(max = 500, message = "灵感内容不能超过500个字符")
private String prompt;
private String mode = "inspiration";
private String style;
private String length;
private String characterInfo;
private String lifeEventsSummary;
private String source;
/**
* 是否使用用户已确认的社交画像增强生成。
*/
private Boolean useSocialInsights;
}
@@ -0,0 +1,26 @@
package com.emotion.dto.request;
import com.emotion.common.BasePageRequest;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* 爽文剧本分页查询请求
*
* @author huazhongmin
* @date 2025-12-22
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class EpicScriptPageRequest extends BasePageRequest {
/**
* 剧本风格筛选
*/
private String style;
/**
* 篇幅长度筛选
*/
private String length;
}
@@ -0,0 +1,95 @@
package com.emotion.dto.request;
import lombok.Data;
import lombok.EqualsAndHashCode;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Size;
import java.util.Map;
/**
* 爽文剧本更新请求
*
* @author huazhongmin
* @date 2025-12-22
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class EpicScriptUpdateRequest extends BaseRequest {
/**
* 剧本ID
*/
@NotBlank(message = "剧本ID不能为空")
private String id;
/**
* 剧本标题
*/
@Size(max = 200, message = "剧本标题长度不能超过200个字符")
private String title;
/**
* 剧本主题/渴望
*/
private String theme;
/**
* 剧本风格: career-职场逆袭, love-情感圆满, fantasy-玄幻觉醒
*/
private String style;
/**
* 篇幅长度: medium-标准篇, long-长篇
*/
private String length;
/**
* 序幕:低谷回响
*/
private String plotIntro;
/**
* 转折:契机出现
*/
private String plotTurning;
/**
* 高潮:命运抉择
*/
private String plotClimax;
/**
* 结局:新的开始
*/
private String plotEnding;
/**
* 完整剧情JSON结构
*/
private Map<String, Object> plotJson;
/**
* 是否当前选中
*/
private Boolean isSelected;
/**
* 角色信息(用于AI重新生成)
*/
private String characterInfo;
/**
* 过往经历摘要(用于AI重新生成)
*/
private String lifeEventsSummary;
/**
* 是否需要重新生成AI内容
*/
private Boolean regenerateContent;
/**
* 是否使用用户已确认的社交画像增强重新生成。
*/
private Boolean useSocialInsights;
}
@@ -0,0 +1,23 @@
package com.emotion.dto.request;
import lombok.Data;
import lombok.EqualsAndHashCode;
import javax.validation.constraints.NotBlank;
/**
* 访客聊天请求类
*
* @author huazhongmin
* @date 2025-07-24
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class GuestChatRequest extends BaseRequest {
/**
* 消息内容
*/
@NotBlank(message = "消息内容不能为空")
private String message;
}
@@ -0,0 +1,20 @@
package com.emotion.dto.request;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* 访客用户信息请求类
*
* @author huazhongmin
* @date 2025-07-24
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class GuestUserInfoRequest extends BaseRequest {
/**
* 客户端IP
*/
private String clientIp;
}
@@ -0,0 +1,23 @@
package com.emotion.dto.request;
import lombok.Data;
import lombok.EqualsAndHashCode;
import javax.validation.constraints.NotBlank;
/**
* ID请求
*
* @author huazhongmin
* @date 2025-07-23
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class IdRequest extends BaseRequest {
/**
* ID
*/
@NotBlank(message = "ID不能为空")
private String id;
}
@@ -0,0 +1,77 @@
package com.emotion.dto.request;
import lombok.Data;
import lombok.EqualsAndHashCode;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Size;
import java.util.List;
/**
* 生命事件创建请求
*
* @author huazhongmin
* @date 2025-12-22
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class LifeEventCreateRequest extends BaseRequest {
/**
* 事件类型: daily_log-日常记录, milestone-里程碑
*/
private String eventType;
/**
* 事件日期 (ISO格式字符串)
*/
private String eventDate;
/**
* 时间模式: date-具体日期, month-年月, season-季节, range-时间范围
*/
private String timeMode;
/**
* 原始时间文本
*/
private String eventDateText;
/**
* 结束日期,仅时间范围使用
*/
private String eventEndDate;
/**
* 事件标题
*/
@NotBlank(message = "事件标题不能为空")
@Size(max = 200, message = "事件标题长度不能超过200个字符")
private String title;
/**
* 事件内容
*/
@NotBlank(message = "事件内容不能为空")
private String content;
/**
* AI疗愈回复
*/
private String aiReply;
/**
* 情绪类型
*/
private String emotionType;
/**
* 情绪评分
*/
private Double emotionScore;
/**
* 标签列表
*/
private List<String> tags;
}
@@ -0,0 +1,36 @@
package com.emotion.dto.request;
import com.emotion.common.BasePageRequest;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* 生命事件分页查询请求
*
* @author huazhongmin
* @date 2025-12-22
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class LifeEventPageRequest extends BasePageRequest {
/**
* 事件类型筛选
*/
private String eventType;
/**
* 开始日期
*/
private String startDate;
/**
* 结束日期
*/
private String endDate;
/**
* 情绪类型筛选
*/
private String emotionType;
}
@@ -0,0 +1,81 @@
package com.emotion.dto.request;
import lombok.Data;
import lombok.EqualsAndHashCode;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Size;
import java.util.List;
/**
* 生命事件更新请求
*
* @author huazhongmin
* @date 2025-12-22
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class LifeEventUpdateRequest extends BaseRequest {
/**
* 事件ID
*/
@NotBlank(message = "事件ID不能为空")
private String id;
/**
* 事件类型: daily_log-日常记录, milestone-里程碑
*/
private String eventType;
/**
* 事件日期 (ISO格式字符串)
*/
private String eventDate;
/**
* 时间模式: date-具体日期, month-年月, season-季节, range-时间范围
*/
private String timeMode;
/**
* 原始时间文本
*/
private String eventDateText;
/**
* 结束日期,仅时间范围使用
*/
private String eventEndDate;
/**
* 事件标题
*/
@Size(max = 200, message = "事件标题长度不能超过200个字符")
private String title;
/**
* 事件内容
*/
private String content;
/**
* AI疗愈回复
*/
private String aiReply;
/**
* 情绪类型
*/
private String emotionType;
/**
* 情绪评分
*/
private Double emotionScore;
/**
* 标签列表
*/
private List<String> tags;
}
@@ -0,0 +1,53 @@
package com.emotion.dto.request;
import lombok.Data;
import lombok.EqualsAndHashCode;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Size;
import java.util.List;
import java.util.Map;
/**
* 实现路径创建请求
*
* @author huazhongmin
* @date 2025-12-22
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class LifePathCreateRequest extends BaseRequest {
/**
* 关联剧本ID
*/
@NotBlank(message = "关联剧本ID不能为空")
private String scriptId;
/**
* 路径标题
*/
@Size(max = 200, message = "路径标题长度不能超过200个字符")
private String title;
/**
* 路径描述
*/
private String description;
/**
* 路径步骤列表
* 每个步骤包含: phase, time, content, action, resources, habit
*/
private List<Map<String, Object>> steps;
/**
* 状态: active-进行中, completed-已完成, archived-已归档
*/
private String status;
/**
* 完成进度百分比
*/
private Double progress;
}
@@ -0,0 +1,26 @@
package com.emotion.dto.request;
import com.emotion.common.BasePageRequest;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* 实现路径分页查询请求
*
* @author huazhongmin
* @date 2025-12-22
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class LifePathPageRequest extends BasePageRequest {
/**
* 关联剧本ID筛选
*/
private String scriptId;
/**
* 状态筛选
*/
private String status;
}
@@ -0,0 +1,53 @@
package com.emotion.dto.request;
import lombok.Data;
import lombok.EqualsAndHashCode;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Size;
import java.util.List;
import java.util.Map;
/**
* 实现路径更新请求
*
* @author huazhongmin
* @date 2025-12-22
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class LifePathUpdateRequest extends BaseRequest {
/**
* 路径ID
*/
@NotBlank(message = "路径ID不能为空")
private String id;
/**
* 路径标题
*/
@Size(max = 200, message = "路径标题长度不能超过200个字符")
private String title;
/**
* 路径描述
*/
private String description;
/**
* 路径步骤列表
* 每个步骤包含: phase, time, content, action, resources, habit
*/
private List<Map<String, Object>> steps;
/**
* 状态: active-进行中, completed-已完成, archived-已归档
*/
private String status;
/**
* 完成进度百分比
*/
private Double progress;
}
@@ -0,0 +1,37 @@
package com.emotion.dto.request;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Pattern;
import javax.validation.constraints.Size;
/**
* 登录请求
* 简化版:仅需手机号和短信验证码
*
* @author huazhongmin
* @date 2025-07-23
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class LoginRequest extends BaseRequest {
/**
* 手机号
*/
@NotBlank(message = "手机号不能为空")
@Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确")
@Schema(description = "手机号")
private String phone;
/**
* 短信验证码
*/
@NotBlank(message = "验证码不能为空")
@Size(min = 6, max = 6, message = "验证码必须为6位")
@Schema(description = "短信验证码")
private String smsCode;
}
@@ -0,0 +1,50 @@
package com.emotion.dto.request;
import lombok.Data;
import lombok.EqualsAndHashCode;
import javax.validation.constraints.NotBlank;
/**
* 消息创建请求类
*
* @author huazhongmin
* @date 2025-07-24
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class MessageCreateRequest extends BaseRequest {
/**
* 会话ID
*/
@NotBlank(message = "会话ID不能为空")
private String conversationId;
/**
* 用户ID
*/
@NotBlank(message = "用户ID不能为空")
private String userId;
/**
* 消息内容
*/
@NotBlank(message = "消息内容不能为空")
private String content;
/**
* 内容类型
*/
private String contentType;
/**
* 发送者类型
*/
private String senderType;
/**
* 发送者ID
*/
private String senderId;
}

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