feat: 完成见素起名小程序核心功能
- 实现 AI 起名功能(Kimi API 接入) - 添加用户收藏功能(MySQL 数据库) - 实现海报生成与分享 - 添加音效和触觉反馈 - 配置生产环境部署(WAR 包 + Nginx) - 支持多种起名模式(经典、诗词、自然、现代) - 实现分批加载优化体验
This commit is contained in:
3
backend/.idea/misc.xml
generated
3
backend/.idea/misc.xml
generated
@@ -1,6 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
||||
<component name="FrameworkDetectionExcludesConfiguration">
|
||||
<file type="web" url="file://$PROJECT_DIR$" />
|
||||
</component>
|
||||
<component name="MavenProjectsManager">
|
||||
<option name="originalFiles">
|
||||
<list>
|
||||
|
||||
6
backend/.idea/vcs.xml
generated
Normal file
6
backend/.idea/vcs.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$/.." vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
@@ -11,6 +11,7 @@
|
||||
<groupId>com.jiansu</groupId>
|
||||
<artifactId>naming</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
<packaging>war</packaging>
|
||||
<name>jiansu-naming</name>
|
||||
<description>JianSu Naming AI Backend</description>
|
||||
<properties>
|
||||
@@ -21,6 +22,12 @@
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
<!-- 打包为 WAR,Tomcat 由外部容器提供 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-tomcat</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
@@ -46,6 +53,24 @@
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<!-- JPA 支持 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-jpa</artifactId>
|
||||
</dependency>
|
||||
<!-- H2 内存数据库(开发测试用) -->
|
||||
<dependency>
|
||||
<groupId>com.h2database</groupId>
|
||||
<artifactId>h2</artifactId>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
<!-- MySQL 5.7 驱动 -->
|
||||
<dependency>
|
||||
<groupId>mysql</groupId>
|
||||
<artifactId>mysql-connector-java</artifactId>
|
||||
<version>5.1.49</version>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
|
||||
@@ -2,10 +2,18 @@ package com.jiansu.naming;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.boot.builder.SpringApplicationBuilder;
|
||||
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
|
||||
|
||||
@SpringBootApplication
|
||||
public class NamingApplication {
|
||||
public class NamingApplication extends SpringBootServletInitializer {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(NamingApplication.class, args);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
|
||||
return application.sources(NamingApplication.class);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
package com.jiansu.naming.config;
|
||||
|
||||
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;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
|
||||
@Configuration
|
||||
@EnableAsync
|
||||
public class AsyncConfig {
|
||||
|
||||
@Bean("jianSuAiExecutor")
|
||||
public Executor jianSuAiExecutor() {
|
||||
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
|
||||
// 核心线程数
|
||||
executor.setCorePoolSize(10);
|
||||
// 最大线程数
|
||||
executor.setMaxPoolSize(20);
|
||||
// 队列容量
|
||||
executor.setQueueCapacity(50);
|
||||
// 线程名前缀
|
||||
executor.setThreadNamePrefix("jiansu-ai-");
|
||||
// 拒绝策略:由调用线程处理
|
||||
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
|
||||
// 等待所有任务完成后再关闭
|
||||
executor.setWaitForTasksToCompleteOnShutdown(true);
|
||||
// 等待时间
|
||||
executor.setAwaitTerminationSeconds(60);
|
||||
executor.initialize();
|
||||
return executor;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
package com.jiansu.naming.controller;
|
||||
|
||||
import com.jiansu.naming.entity.UserFavorite;
|
||||
import com.jiansu.naming.model.NameCard;
|
||||
import com.jiansu.naming.service.FavoriteService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/favorites")
|
||||
@CrossOrigin(origins = "*")
|
||||
@Slf4j
|
||||
public class FavoriteController {
|
||||
|
||||
@Autowired
|
||||
private FavoriteService favoriteService;
|
||||
|
||||
/**
|
||||
* 添加收藏
|
||||
*/
|
||||
@PostMapping("/add")
|
||||
public ResponseEntity<?> addFavorite(
|
||||
@RequestParam String openid,
|
||||
@RequestBody NameCard nameCard,
|
||||
@RequestParam(required = false) String mode,
|
||||
@RequestParam(required = false) String keyword) {
|
||||
|
||||
UserFavorite favorite = favoriteService.addFavorite(openid, nameCard, mode, keyword);
|
||||
Map<String, Object> response = new HashMap<>();
|
||||
|
||||
if (favorite != null) {
|
||||
response.put("success", true);
|
||||
response.put("message", "收藏成功");
|
||||
response.put("data", favorite);
|
||||
} else {
|
||||
response.put("success", false);
|
||||
response.put("message", "已收藏过该名字");
|
||||
}
|
||||
|
||||
return ResponseEntity.ok(response);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户收藏列表
|
||||
*/
|
||||
@GetMapping("/list")
|
||||
public ResponseEntity<?> getFavorites(@RequestParam String openid) {
|
||||
List<NameCard> favorites = favoriteService.getUserFavorites(openid);
|
||||
Map<String, Object> response = new HashMap<>();
|
||||
response.put("success", true);
|
||||
response.put("data", favorites);
|
||||
return ResponseEntity.ok(response);
|
||||
}
|
||||
|
||||
/**
|
||||
* 取消收藏
|
||||
*/
|
||||
@PostMapping("/remove")
|
||||
public ResponseEntity<?> removeFavorite(
|
||||
@RequestParam String openid,
|
||||
@RequestParam String name) {
|
||||
|
||||
favoriteService.removeFavorite(openid, name);
|
||||
Map<String, Object> response = new HashMap<>();
|
||||
response.put("success", true);
|
||||
response.put("message", "取消收藏成功");
|
||||
return ResponseEntity.ok(response);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否已收藏
|
||||
*/
|
||||
@GetMapping("/check")
|
||||
public ResponseEntity<?> checkFavorite(
|
||||
@RequestParam String openid,
|
||||
@RequestParam String name) {
|
||||
|
||||
boolean isFavorited = favoriteService.isFavorited(openid, name);
|
||||
Map<String, Object> response = new HashMap<>();
|
||||
response.put("success", true);
|
||||
response.put("isFavorited", isFavorited);
|
||||
return ResponseEntity.ok(response);
|
||||
}
|
||||
}
|
||||
@@ -1,22 +1,36 @@
|
||||
package com.jiansu.naming.controller;
|
||||
|
||||
import com.jiansu.naming.model.NameCard;
|
||||
import com.jiansu.naming.service.MiniMaxService;
|
||||
import com.jiansu.naming.service.KimiService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/names")
|
||||
@RequestMapping(value = "/api/names", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
|
||||
@CrossOrigin(origins = "*")
|
||||
@Slf4j
|
||||
public class NamingController {
|
||||
|
||||
@Autowired
|
||||
private MiniMaxService miniMaxService;
|
||||
private KimiService kimiService;
|
||||
|
||||
@GetMapping("/generate")
|
||||
public List<NameCard> generate(@RequestParam(defaultValue = "清冷") String keyword) {
|
||||
return miniMaxService.generateNames(keyword);
|
||||
public List<NameCard> generate(
|
||||
@RequestParam(defaultValue = "清冷") String keyword,
|
||||
@RequestParam(required = false) String mode,
|
||||
@RequestParam(required = false) String surname,
|
||||
@RequestParam(required = false, defaultValue = "3") Integer count,
|
||||
@RequestParam(required = false) String batch) {
|
||||
|
||||
log.info("生成名字请求 - 关键词: {}, 模式: {}, 批次: {}, 数量: {}", keyword, mode, batch, count);
|
||||
|
||||
// 使用异步方法获取结果
|
||||
CompletableFuture<List<NameCard>> future = kimiService.generateNamesAsync(keyword, mode, surname, count);
|
||||
return future.join();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
package com.jiansu.naming.entity;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import javax.persistence.*;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Entity
|
||||
@Table(name = "user_favorites")
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class UserFavorite {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
|
||||
@Column(name = "openid", nullable = false, length = 100)
|
||||
private String openid;
|
||||
|
||||
@Column(name = "name", nullable = false, length = 50)
|
||||
private String name;
|
||||
|
||||
@Column(name = "origin", length = 500)
|
||||
private String origin;
|
||||
|
||||
@Column(name = "description", length = 1000)
|
||||
private String description;
|
||||
|
||||
@Column(name = "tone", length = 20)
|
||||
private String tone;
|
||||
|
||||
@Column(name = "score")
|
||||
private Double score;
|
||||
|
||||
@Column(name = "mode", length = 20)
|
||||
private String mode;
|
||||
|
||||
@Column(name = "keyword", length = 100)
|
||||
private String keyword;
|
||||
|
||||
@Column(name = "create_time")
|
||||
private LocalDateTime createTime;
|
||||
|
||||
@PrePersist
|
||||
protected void onCreate() {
|
||||
createTime = LocalDateTime.now();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package com.jiansu.naming.repository;
|
||||
|
||||
import com.jiansu.naming.entity.UserFavorite;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
@Repository
|
||||
public interface UserFavoriteRepository extends JpaRepository<UserFavorite, Long> {
|
||||
|
||||
/**
|
||||
* 根据 openid 查询用户的所有收藏
|
||||
*/
|
||||
List<UserFavorite> findByOpenidOrderByCreateTimeDesc(String openid);
|
||||
|
||||
/**
|
||||
* 根据 openid 和名字查询收藏
|
||||
*/
|
||||
Optional<UserFavorite> findByOpenidAndName(String openid, String name);
|
||||
|
||||
/**
|
||||
* 检查用户是否已收藏某个名字
|
||||
*/
|
||||
boolean existsByOpenidAndName(String openid, String name);
|
||||
|
||||
/**
|
||||
* 删除用户的某个收藏
|
||||
*/
|
||||
void deleteByOpenidAndName(String openid, String name);
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
package com.jiansu.naming.service;
|
||||
|
||||
import com.jiansu.naming.entity.UserFavorite;
|
||||
import com.jiansu.naming.model.NameCard;
|
||||
import com.jiansu.naming.repository.UserFavoriteRepository;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
public class FavoriteService {
|
||||
|
||||
@Autowired
|
||||
private UserFavoriteRepository userFavoriteRepository;
|
||||
|
||||
/**
|
||||
* 添加收藏
|
||||
*/
|
||||
public UserFavorite addFavorite(String openid, NameCard nameCard, String mode, String keyword) {
|
||||
// 检查是否已收藏
|
||||
if (userFavoriteRepository.existsByOpenidAndName(openid, nameCard.getName())) {
|
||||
log.info("用户 {} 已收藏名字 {}", openid, nameCard.getName());
|
||||
return null;
|
||||
}
|
||||
|
||||
UserFavorite favorite = UserFavorite.builder()
|
||||
.openid(openid)
|
||||
.name(nameCard.getName())
|
||||
.origin(nameCard.getOrigin())
|
||||
.description(nameCard.getDescription())
|
||||
.tone(nameCard.getTone())
|
||||
.score(nameCard.getScore())
|
||||
.mode(mode)
|
||||
.keyword(keyword)
|
||||
.build();
|
||||
|
||||
UserFavorite saved = userFavoriteRepository.save(favorite);
|
||||
log.info("用户 {} 收藏名字 {} 成功", openid, nameCard.getName());
|
||||
return saved;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户的所有收藏
|
||||
*/
|
||||
public List<NameCard> getUserFavorites(String openid) {
|
||||
List<UserFavorite> favorites = userFavoriteRepository.findByOpenidOrderByCreateTimeDesc(openid);
|
||||
return favorites.stream()
|
||||
.map(fav -> NameCard.builder()
|
||||
.name(fav.getName())
|
||||
.origin(fav.getOrigin())
|
||||
.description(fav.getDescription())
|
||||
.tone(fav.getTone())
|
||||
.score(fav.getScore())
|
||||
.build())
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* 取消收藏
|
||||
*/
|
||||
public void removeFavorite(String openid, String name) {
|
||||
userFavoriteRepository.deleteByOpenidAndName(openid, name);
|
||||
log.info("用户 {} 取消收藏名字 {}", openid, name);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否已收藏
|
||||
*/
|
||||
public boolean isFavorited(String openid, String name) {
|
||||
return userFavoriteRepository.existsByOpenidAndName(openid, name);
|
||||
}
|
||||
}
|
||||
314
backend/src/main/java/com/jiansu/naming/service/KimiService.java
Normal file
314
backend/src/main/java/com/jiansu/naming/service/KimiService.java
Normal file
@@ -0,0 +1,314 @@
|
||||
package com.jiansu.naming.service;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.alibaba.fastjson.JSONArray;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.jiansu.naming.model.NameCard;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import okhttp3.*;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.TrustManager;
|
||||
import javax.net.ssl.X509TrustManager;
|
||||
import java.io.IOException;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
public class KimiService {
|
||||
|
||||
@Value("${kimi.api-key}")
|
||||
private String apiKey;
|
||||
|
||||
@Value("${kimi.api-url}")
|
||||
private String apiUrl;
|
||||
|
||||
private final ToneAnalysisService toneAnalysisService;
|
||||
|
||||
@Autowired
|
||||
@Qualifier("jianSuAiExecutor")
|
||||
private Executor jianSuAiExecutor;
|
||||
|
||||
public KimiService(ToneAnalysisService toneAnalysisService) {
|
||||
this.toneAnalysisService = toneAnalysisService;
|
||||
}
|
||||
|
||||
private final OkHttpClient client = createTrustAllOkHttpClient();
|
||||
|
||||
/**
|
||||
* 创建信任所有证书的 OkHttpClient(仅用于开发环境解决 SSL 证书问题)
|
||||
*/
|
||||
private static OkHttpClient createTrustAllOkHttpClient() {
|
||||
try {
|
||||
// 创建信任所有证书的信任管理器
|
||||
TrustManager[] trustAllCerts = new TrustManager[]{
|
||||
new X509TrustManager() {
|
||||
@Override
|
||||
public void checkClientTrusted(X509Certificate[] chain, String authType) {}
|
||||
@Override
|
||||
public void checkServerTrusted(X509Certificate[] chain, String authType) {}
|
||||
@Override
|
||||
public X509Certificate[] getAcceptedIssuers() {
|
||||
return new X509Certificate[]{};
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 安装信任管理器
|
||||
SSLContext sslContext = SSLContext.getInstance("SSL");
|
||||
sslContext.init(null, trustAllCerts, new java.security.SecureRandom());
|
||||
|
||||
return new OkHttpClient.Builder()
|
||||
.sslSocketFactory(sslContext.getSocketFactory(), (X509TrustManager) trustAllCerts[0])
|
||||
.hostnameVerifier((hostname, session) -> true)
|
||||
.connectTimeout(60, TimeUnit.SECONDS)
|
||||
.writeTimeout(60, TimeUnit.SECONDS)
|
||||
.readTimeout(120, TimeUnit.SECONDS)
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("创建 OkHttpClient 失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
// 多模式 Prompt 模板
|
||||
private static final Map<String, String> PROMPT_TEMPLATES = new HashMap<>();
|
||||
|
||||
static {
|
||||
// 宝宝起名模式 - 单次生成1个名字
|
||||
PROMPT_TEMPLATES.put("baby",
|
||||
"你是「见素」的起名大师。请为姓氏为{surname}的宝宝起名,结合关键词'{keyword}'的期待,从《诗经》、《楚辞》或宋词中提取意象,生成1个极具美感的名字。" +
|
||||
"名字必须配有一段 50 字以内的'通感'描述,包含气味、光线或声音的描写,拒绝说教。" +
|
||||
"请直接以 JSON 对象格式返回,JSON 对象包含三个字段:name (完整姓名), origin (诗词出处), description (通感描述)。不要包含任何 Markdown 格式。");
|
||||
|
||||
// 人设起名模式 - 单次生成1个名字
|
||||
PROMPT_TEMPLATES.put("persona",
|
||||
"你是「见素」的人设命名师。请为想要塑造'{keyword}'人设的用户起名,从《诗经》、《楚辞》或宋词中提取意象,生成1个极具美感的名字。" +
|
||||
"名字必须配有一段 50 字以内的'通感'描述,包含气味、光线或声音的描写,拒绝说教。" +
|
||||
"请直接以 JSON 对象格式返回,JSON 对象包含三个字段:name (名字), origin (诗词出处), description (通感描述)。不要包含任何 Markdown 格式。");
|
||||
|
||||
// 拾遗模式(经典模式)- 单次生成1个名字
|
||||
PROMPT_TEMPLATES.put("classic",
|
||||
"你是「见素」的灵魂导师。当用户输入期待或关键词'{keyword}'时,请从《诗经》、《楚辞》或宋词中提取意象,生成1个极具美感的名字。" +
|
||||
"名字必须配有一段 50 字以内的'通感'描述,包含气味、光线或声音的描写,拒绝说教。" +
|
||||
"请直接以 JSON 对象格式返回,JSON 对象包含三个字段:name (名字), origin (诗词出处), description (通感描述)。不要包含任何 Markdown 格式。");
|
||||
}
|
||||
|
||||
// 原方法保留,默认使用 classic 模式
|
||||
public List<NameCard> generateNames(String keyword) {
|
||||
return generateNames(keyword, "classic", null);
|
||||
}
|
||||
|
||||
// 原同步方法保留
|
||||
public List<NameCard> generateNames(String keyword, String mode, String surname) {
|
||||
return generateNamesAsync(keyword, mode, surname, 3).join();
|
||||
}
|
||||
|
||||
/**
|
||||
* 异步并行生成名字
|
||||
* @param keyword 关键词
|
||||
* @param mode 模式
|
||||
* @param surname 姓氏
|
||||
* @param count 生成数量
|
||||
* @return CompletableFuture<List<NameCard>>
|
||||
*/
|
||||
public CompletableFuture<List<NameCard>> generateNamesAsync(String keyword, String mode, String surname, int count) {
|
||||
long startTime = System.currentTimeMillis();
|
||||
log.info("开始异步生成 {} 个名字,模式: {}", count, mode);
|
||||
|
||||
// 后门:当关键词为 "test" 时,返回固定的测试数据
|
||||
if ("test".equalsIgnoreCase(keyword)) {
|
||||
log.info("Keyword is 'test', returning hardcoded test data.");
|
||||
return CompletableFuture.completedFuture(createTestData());
|
||||
}
|
||||
|
||||
// 默认使用 classic 模式
|
||||
final String finalMode = (mode == null || mode.isEmpty()) ? "classic" : mode;
|
||||
|
||||
// 创建 count 个并行任务
|
||||
List<CompletableFuture<NameCard>> futures = new ArrayList<>();
|
||||
for (int i = 0; i < count; i++) {
|
||||
final int index = i;
|
||||
CompletableFuture<NameCard> future = CompletableFuture.supplyAsync(() -> {
|
||||
try {
|
||||
return callAiToGenerateSingleName(keyword, finalMode, surname, index);
|
||||
} catch (Exception e) {
|
||||
log.error("第 {} 个名字生成失败: {}", index, e.getMessage());
|
||||
return null;
|
||||
}
|
||||
}, jianSuAiExecutor).exceptionally(ex -> {
|
||||
log.error("第 {} 个任务异常: {}", index, ex.getMessage());
|
||||
return null;
|
||||
});
|
||||
futures.add(future);
|
||||
}
|
||||
|
||||
// 等待所有任务完成并合并结果
|
||||
return CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]))
|
||||
.thenApply(v -> {
|
||||
List<NameCard> results = futures.stream()
|
||||
.map(CompletableFuture::join)
|
||||
.filter(card -> card != null)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
long duration = System.currentTimeMillis() - startTime;
|
||||
log.info("异步生成完成,成功 {} 个,耗时 {}ms", results.size(), duration);
|
||||
|
||||
return results;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 调用 Kimi AI 生成单个名字
|
||||
*/
|
||||
private NameCard callAiToGenerateSingleName(String keyword, String mode, String surname, int seed) {
|
||||
String promptTemplate = PROMPT_TEMPLATES.getOrDefault(mode, PROMPT_TEMPLATES.get("classic"));
|
||||
|
||||
// 动态构建 Prompt,加入 seed 使每次请求略有不同
|
||||
String systemPrompt = promptTemplate
|
||||
.replace("{keyword}", keyword != null ? keyword : "")
|
||||
.replace("{surname}", surname != null ? surname : "")
|
||||
+ " 请确保创意独特,避免与之前生成的名字重复。";
|
||||
|
||||
// 构建 Kimi API 请求体 (OpenAI 兼容格式)
|
||||
JSONObject jsonBody = new JSONObject();
|
||||
jsonBody.put("model", "moonshot-v1-8k");
|
||||
jsonBody.put("temperature", 0.7);
|
||||
// 使用随机种子确保每次生成结果不同
|
||||
jsonBody.put("seed", (int) (Math.random() * 1000000) + seed * 1000);
|
||||
|
||||
JSONArray messages = new JSONArray();
|
||||
|
||||
JSONObject systemMsg = new JSONObject();
|
||||
systemMsg.put("role", "system");
|
||||
systemMsg.put("content", systemPrompt);
|
||||
messages.add(systemMsg);
|
||||
|
||||
JSONObject userMsg = new JSONObject();
|
||||
userMsg.put("role", "user");
|
||||
userMsg.put("content", "用户期待/关键词是:" + keyword + ",请生成第" + (seed + 1) + "个名字。");
|
||||
messages.add(userMsg);
|
||||
|
||||
jsonBody.put("messages", messages);
|
||||
|
||||
RequestBody body = RequestBody.create(
|
||||
jsonBody.toJSONString(),
|
||||
MediaType.parse("application/json; charset=utf-8")
|
||||
);
|
||||
|
||||
Request request = new Request.Builder()
|
||||
.url(apiUrl)
|
||||
.post(body)
|
||||
.addHeader("Authorization", "Bearer " + apiKey)
|
||||
.addHeader("Content-Type", "application/json")
|
||||
.build();
|
||||
|
||||
try (Response response = client.newCall(request).execute()) {
|
||||
if (response.isSuccessful() && response.body() != null) {
|
||||
String responseBody = response.body().string();
|
||||
log.debug("Kimi raw response: {}", responseBody);
|
||||
return parseSingleNameCard(responseBody);
|
||||
} else {
|
||||
String errorMsg = response.body() != null ? response.body().string() : response.message();
|
||||
log.error("Kimi API Error: {} - {}", response.code(), errorMsg);
|
||||
throw new RuntimeException("Kimi API 调用失败: " + errorMsg);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
log.error("API Error: ", e);
|
||||
throw new RuntimeException("网络请求失败");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析单个名字卡片 (Kimi 格式)
|
||||
*/
|
||||
private NameCard parseSingleNameCard(String responseBody) {
|
||||
try {
|
||||
JSONObject root = JSON.parseObject(responseBody);
|
||||
|
||||
// 检查错误
|
||||
JSONObject error = root.getJSONObject("error");
|
||||
if (error != null) {
|
||||
String errorMsg = error.getString("message");
|
||||
log.error("Kimi API error: {}", errorMsg);
|
||||
throw new RuntimeException("Kimi API 错误: " + errorMsg);
|
||||
}
|
||||
|
||||
JSONArray choices = root.getJSONArray("choices");
|
||||
if (choices == null || choices.isEmpty()) {
|
||||
throw new RuntimeException("Kimi 返回内容为空");
|
||||
}
|
||||
|
||||
JSONObject firstChoice = choices.getJSONObject(0);
|
||||
JSONObject message = firstChoice.getJSONObject("message");
|
||||
String aiText = message.getString("content");
|
||||
|
||||
if (aiText == null || aiText.isEmpty()) {
|
||||
throw new RuntimeException("Kimi 返回内容为空");
|
||||
}
|
||||
|
||||
// 清理可能存在的 Markdown 代码块标记
|
||||
aiText = aiText.replaceAll("```json", "").replaceAll("```", "").trim();
|
||||
|
||||
JSONObject jsonObject = JSON.parseObject(aiText);
|
||||
String name = jsonObject.getString("name");
|
||||
|
||||
// 进行声韵分析
|
||||
ToneAnalysisService.ToneResult tr = toneAnalysisService.analyze(name);
|
||||
|
||||
return NameCard.builder()
|
||||
.name(name)
|
||||
.origin(jsonObject.getString("origin"))
|
||||
.description(jsonObject.getString("description"))
|
||||
.score(tr.score)
|
||||
.tone(tr.tonePattern)
|
||||
.build();
|
||||
} catch (RuntimeException e) {
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
log.error("Parse Error: ", e);
|
||||
throw new RuntimeException("数据解析失败");
|
||||
}
|
||||
}
|
||||
|
||||
private List<NameCard> createTestData() {
|
||||
List<NameCard> testData = new ArrayList<>();
|
||||
|
||||
testData.add(NameCard.builder()
|
||||
.name("月白")
|
||||
.origin("《月出》:月出皎兮,佼人僚兮。")
|
||||
.description("像月光洒在雪地,清冷又干净,带着一丝不易察觉的、风吹过竹林的微响。")
|
||||
.score(9.8)
|
||||
.tone("仄仄")
|
||||
.build());
|
||||
|
||||
testData.add(NameCard.builder()
|
||||
.name("南絮")
|
||||
.origin("晏几道《御街行》:街南绿树春饶絮。")
|
||||
.description("春天街巷里柔软的柳絮,随风飘浮,带着南方阳光的暖意和青草的气息。")
|
||||
.score(9.5)
|
||||
.tone("平仄")
|
||||
.build());
|
||||
|
||||
testData.add(NameCard.builder()
|
||||
.name("疏影")
|
||||
.origin("林逋《山园小梅》:疏影横斜水清浅,暗香浮动月黄昏。")
|
||||
.description("黄昏月下,梅枝稀疏的影子落在清浅的水面,空气中浮动着若有似无的冷香。")
|
||||
.score(9.7)
|
||||
.tone("平仄")
|
||||
.build());
|
||||
|
||||
return testData;
|
||||
}
|
||||
}
|
||||
@@ -1,184 +0,0 @@
|
||||
package com.jiansu.naming.service;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.alibaba.fastjson.JSONArray;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.jiansu.naming.model.NameCard;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import okhttp3.*;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
public class MiniMaxService {
|
||||
|
||||
@Value("${minimax.api-key}")
|
||||
private String apiKey;
|
||||
|
||||
@Value("${minimax.api-url}")
|
||||
private String apiUrl;
|
||||
|
||||
private final ToneAnalysisService toneAnalysisService;
|
||||
|
||||
public MiniMaxService(ToneAnalysisService toneAnalysisService) {
|
||||
this.toneAnalysisService = toneAnalysisService;
|
||||
}
|
||||
|
||||
private final OkHttpClient client = new OkHttpClient.Builder()
|
||||
.connectTimeout(30, TimeUnit.SECONDS)
|
||||
.writeTimeout(30, TimeUnit.SECONDS)
|
||||
.readTimeout(60, TimeUnit.SECONDS)
|
||||
.build();
|
||||
|
||||
private static final String SYSTEM_PROMPT =
|
||||
"你是「见素」的灵魂导师。当用户输入期待或关键词时,请从《诗经》、《楚辞》或宋词中提取意象,生成3个极具美感的名字。" +
|
||||
"每个名字必须配有一段 50 字以内的'通感'描述,包含气味、光线或声音的描写,拒绝说教。" +
|
||||
"请直接以 JSON 数组格式返回,JSON 对象包含三个字段:name (名字), origin (诗词出处), description (通感描述)。不要包含任何 Markdown 格式。";
|
||||
|
||||
public List<NameCard> generateNames(String keyword) {
|
||||
// 后门:当关键词为 "test" 时,返回固定的测试数据
|
||||
if ("test".equalsIgnoreCase(keyword)) {
|
||||
log.info("Keyword is 'test', returning hardcoded test data.");
|
||||
return createTestData();
|
||||
}
|
||||
|
||||
// 构建 MiniMax Token Plan (OpenAI 兼容) 请求体
|
||||
JSONObject jsonBody = new JSONObject();
|
||||
jsonBody.put("model", "MiniMax-M2.7");
|
||||
|
||||
JSONArray messages = new JSONArray();
|
||||
|
||||
JSONObject systemMsg = new JSONObject();
|
||||
systemMsg.put("role", "system");
|
||||
systemMsg.put("content", SYSTEM_PROMPT);
|
||||
messages.add(systemMsg);
|
||||
|
||||
JSONObject userMsg = new JSONObject();
|
||||
userMsg.put("role", "user");
|
||||
userMsg.put("content", "用户期待/关键词是:" + keyword);
|
||||
messages.add(userMsg);
|
||||
|
||||
jsonBody.put("messages", messages);
|
||||
|
||||
RequestBody body = RequestBody.create(
|
||||
jsonBody.toJSONString(),
|
||||
MediaType.parse("application/json; charset=utf-8")
|
||||
);
|
||||
|
||||
Request request = new Request.Builder()
|
||||
.url(apiUrl)
|
||||
.post(body)
|
||||
.addHeader("Authorization", "Bearer " + apiKey)
|
||||
.addHeader("Content-Type", "application/json")
|
||||
.build();
|
||||
|
||||
try (Response response = client.newCall(request).execute()) {
|
||||
if (response.isSuccessful() && response.body() != null) {
|
||||
String responseBody = response.body().string();
|
||||
log.info("MiniMax raw response: {}", responseBody);
|
||||
return parseMiniMaxResponse(responseBody);
|
||||
} else {
|
||||
String errorMsg = response.body() != null ? response.body().string() : response.message();
|
||||
log.error("MiniMax API Error: {} - {}", response.code(), errorMsg);
|
||||
throw new RuntimeException("MiniMax API 调用失败: " + errorMsg);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
log.error("API Error: ", e);
|
||||
throw new RuntimeException("网络请求失败");
|
||||
}
|
||||
}
|
||||
|
||||
private List<NameCard> parseMiniMaxResponse(String responseBody) {
|
||||
List<NameCard> nameCards = new ArrayList<>();
|
||||
try {
|
||||
JSONObject root = JSON.parseObject(responseBody);
|
||||
|
||||
// 检查 API 错误
|
||||
JSONObject baseResp = root.getJSONObject("base_resp");
|
||||
if (baseResp != null) {
|
||||
int statusCode = baseResp.getIntValue("status_code");
|
||||
if (statusCode != 0) {
|
||||
String statusMsg = baseResp.getString("status_msg");
|
||||
log.error("MiniMax API error: {} - {}", statusCode, statusMsg);
|
||||
throw new RuntimeException("MiniMax API 错误: " + statusMsg);
|
||||
}
|
||||
}
|
||||
|
||||
JSONArray choices = root.getJSONArray("choices");
|
||||
if (choices == null || choices.isEmpty()) {
|
||||
throw new RuntimeException("MiniMax 返回内容为空");
|
||||
}
|
||||
|
||||
JSONObject firstChoice = choices.getJSONObject(0);
|
||||
JSONObject message = firstChoice.getJSONObject("message");
|
||||
String aiText = message.getString("content");
|
||||
|
||||
if (aiText == null || aiText.isEmpty()) {
|
||||
throw new RuntimeException("MiniMax 返回内容为空");
|
||||
}
|
||||
|
||||
// 清理可能存在的 Markdown 代码块标记
|
||||
aiText = aiText.replaceAll("```json", "").replaceAll("```", "").trim();
|
||||
|
||||
JSONArray jsonArray = JSON.parseArray(aiText);
|
||||
for (int i = 0; i < jsonArray.size(); i++) {
|
||||
JSONObject obj = jsonArray.getJSONObject(i);
|
||||
String name = obj.getString("name");
|
||||
|
||||
// 进行声韵分析
|
||||
ToneAnalysisService.ToneResult tr = toneAnalysisService.analyze(name);
|
||||
|
||||
NameCard card = NameCard.builder()
|
||||
.name(name)
|
||||
.origin(obj.getString("origin"))
|
||||
.description(obj.getString("description"))
|
||||
.score(tr.score)
|
||||
.tone(tr.tonePattern)
|
||||
.build();
|
||||
nameCards.add(card);
|
||||
}
|
||||
} catch (RuntimeException e) {
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
log.error("Parse Error: ", e);
|
||||
throw new RuntimeException("数据解析失败");
|
||||
}
|
||||
return nameCards;
|
||||
}
|
||||
|
||||
private List<NameCard> createTestData() {
|
||||
List<NameCard> testData = new ArrayList<>();
|
||||
|
||||
testData.add(NameCard.builder()
|
||||
.name("月白")
|
||||
.origin("《月出》:月出皎兮,佼人僚兮。")
|
||||
.description("像月光洒在雪地,清冷又干净,带着一丝不易察觉的、风吹过竹林的微响。")
|
||||
.score(9.8)
|
||||
.tone("仄仄")
|
||||
.build());
|
||||
|
||||
testData.add(NameCard.builder()
|
||||
.name("南絮")
|
||||
.origin("晏几道《御街行》:街南绿树春饶絮。")
|
||||
.description("春天街巷里柔软的柳絮,随风飘浮,带着南方阳光的暖意和青草的气息。")
|
||||
.score(9.5)
|
||||
.tone("平仄")
|
||||
.build());
|
||||
|
||||
testData.add(NameCard.builder()
|
||||
.name("疏影")
|
||||
.origin("林逋《山园小梅》:疏影横斜水清浅,暗香浮动月黄昏。")
|
||||
.description("黄昏月下,梅枝稀疏的影子落在清浅的水面,空气中浮动着若有似无的冷香。")
|
||||
.score(9.7)
|
||||
.tone("平仄")
|
||||
.build());
|
||||
|
||||
return testData;
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,45 @@
|
||||
server:
|
||||
port: 8080
|
||||
servlet:
|
||||
encoding:
|
||||
charset: UTF-8
|
||||
enabled: true
|
||||
force: true
|
||||
|
||||
spring:
|
||||
application:
|
||||
name: jiansu-naming
|
||||
|
||||
# 数据库配置 - MySQL 5.7
|
||||
datasource:
|
||||
url: jdbc:mysql://47.115.201.202:3306/jiansu_db?useSSL=false&serverTimezone=Asia/Shanghai&characterEncoding=UTF-8&useUnicode=true
|
||||
driver-class-name: com.mysql.jdbc.Driver
|
||||
username: root
|
||||
password: Feastcoding@2023
|
||||
# 连接池配置
|
||||
hikari:
|
||||
minimum-idle: 5
|
||||
maximum-pool-size: 20
|
||||
idle-timeout: 300000
|
||||
max-lifetime: 1200000
|
||||
connection-timeout: 20000
|
||||
|
||||
jpa:
|
||||
hibernate:
|
||||
ddl-auto: update
|
||||
show-sql: false
|
||||
properties:
|
||||
hibernate:
|
||||
dialect: org.hibernate.dialect.MySQL57Dialect
|
||||
format_sql: true
|
||||
|
||||
# MiniMax API 配置
|
||||
minimax:
|
||||
api-key: sk-cp-n0eCZgH5s-NpduAVPo8rpWM9eUBsMOBnIroISIaH6y8eFIpT0VSrCMttzE4bVDbQ-loiMR1b8ZpIsgotQ_yqQRk8_fcUxKHsbhtLfN70oCVaV6-94ZC9Wjk
|
||||
api-url: https://api.minimax.chat/v1/text/chatcompletion_v2
|
||||
# Kimi (Moonshot AI) API 配置
|
||||
kimi:
|
||||
api-key: ${KIMI_API_KEY:sk-EORjVwYTlXMTIFmelkt6ebWlOOLk9qCkm2PR0tvKXdkAnSdd}
|
||||
api-url: https://api.moonshot.cn/v1/chat/completions
|
||||
|
||||
# 日志配置
|
||||
logging:
|
||||
level:
|
||||
com.jiansu.naming: INFO
|
||||
org.hibernate.SQL: WARN
|
||||
|
||||
28
backend/src/main/resources/schema.sql
Normal file
28
backend/src/main/resources/schema.sql
Normal file
@@ -0,0 +1,28 @@
|
||||
-- 见素起名 - MySQL 5.7 数据库建表语句
|
||||
-- 创建数据库
|
||||
CREATE DATABASE IF NOT EXISTS jiansu_db
|
||||
CHARACTER SET utf8mb4
|
||||
COLLATE utf8mb4_unicode_ci;
|
||||
|
||||
USE jiansu_db;
|
||||
|
||||
-- 用户收藏表
|
||||
CREATE TABLE IF NOT EXISTS user_favorites (
|
||||
id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
|
||||
openid VARCHAR(100) NOT NULL COMMENT '用户微信openid',
|
||||
name VARCHAR(50) NOT NULL COMMENT '名字',
|
||||
origin VARCHAR(500) COMMENT '出处/诗词',
|
||||
description VARCHAR(1000) COMMENT '解析文案',
|
||||
tone VARCHAR(20) COMMENT '声韵',
|
||||
score DOUBLE COMMENT '见素评分',
|
||||
mode VARCHAR(20) COMMENT '生成模式',
|
||||
keyword VARCHAR(100) COMMENT '关键词',
|
||||
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '收藏时间',
|
||||
|
||||
INDEX idx_openid (openid),
|
||||
INDEX idx_openid_name (openid, name),
|
||||
INDEX idx_create_time (create_time)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户收藏表';
|
||||
|
||||
-- 可选:创建唯一索引防止重复收藏
|
||||
-- ALTER TABLE user_favorites ADD UNIQUE INDEX uk_openid_name (openid, name);
|
||||
@@ -1,11 +1,45 @@
|
||||
server:
|
||||
port: 8080
|
||||
servlet:
|
||||
encoding:
|
||||
charset: UTF-8
|
||||
enabled: true
|
||||
force: true
|
||||
|
||||
spring:
|
||||
application:
|
||||
name: jiansu-naming
|
||||
|
||||
# 数据库配置 - MySQL 5.7
|
||||
datasource:
|
||||
url: jdbc:mysql://47.115.201.202:3306/jiansu_db?useSSL=false&serverTimezone=Asia/Shanghai&characterEncoding=UTF-8&useUnicode=true
|
||||
driver-class-name: com.mysql.jdbc.Driver
|
||||
username: root
|
||||
password: Feastcoding@2023
|
||||
# 连接池配置
|
||||
hikari:
|
||||
minimum-idle: 5
|
||||
maximum-pool-size: 20
|
||||
idle-timeout: 300000
|
||||
max-lifetime: 1200000
|
||||
connection-timeout: 20000
|
||||
|
||||
jpa:
|
||||
hibernate:
|
||||
ddl-auto: update
|
||||
show-sql: false
|
||||
properties:
|
||||
hibernate:
|
||||
dialect: org.hibernate.dialect.MySQL57Dialect
|
||||
format_sql: true
|
||||
|
||||
# MiniMax API 配置
|
||||
minimax:
|
||||
api-key: sk-cp-n0eCZgH5s-NpduAVPo8rpWM9eUBsMOBnIroISIaH6y8eFIpT0VSrCMttzE4bVDbQ-loiMR1b8ZpIsgotQ_yqQRk8_fcUxKHsbhtLfN70oCVaV6-94ZC9Wjk
|
||||
api-url: https://api.minimax.chat/v1/text/chatcompletion_v2
|
||||
# Kimi (Moonshot AI) API 配置
|
||||
kimi:
|
||||
api-key: ${KIMI_API_KEY:sk-EORjVwYTlXMTIFmelkt6ebWlOOLk9qCkm2PR0tvKXdkAnSdd}
|
||||
api-url: https://api.moonshot.cn/v1/chat/completions
|
||||
|
||||
# 日志配置
|
||||
logging:
|
||||
level:
|
||||
com.jiansu.naming: INFO
|
||||
org.hibernate.SQL: WARN
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
28
backend/target/classes/schema.sql
Normal file
28
backend/target/classes/schema.sql
Normal file
@@ -0,0 +1,28 @@
|
||||
-- 见素起名 - MySQL 5.7 数据库建表语句
|
||||
-- 创建数据库
|
||||
CREATE DATABASE IF NOT EXISTS jiansu_db
|
||||
CHARACTER SET utf8mb4
|
||||
COLLATE utf8mb4_unicode_ci;
|
||||
|
||||
USE jiansu_db;
|
||||
|
||||
-- 用户收藏表
|
||||
CREATE TABLE IF NOT EXISTS user_favorites (
|
||||
id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
|
||||
openid VARCHAR(100) NOT NULL COMMENT '用户微信openid',
|
||||
name VARCHAR(50) NOT NULL COMMENT '名字',
|
||||
origin VARCHAR(500) COMMENT '出处/诗词',
|
||||
description VARCHAR(1000) COMMENT '解析文案',
|
||||
tone VARCHAR(20) COMMENT '声韵',
|
||||
score DOUBLE COMMENT '见素评分',
|
||||
mode VARCHAR(20) COMMENT '生成模式',
|
||||
keyword VARCHAR(100) COMMENT '关键词',
|
||||
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '收藏时间',
|
||||
|
||||
INDEX idx_openid (openid),
|
||||
INDEX idx_openid_name (openid, name),
|
||||
INDEX idx_create_time (create_time)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户收藏表';
|
||||
|
||||
-- 可选:创建唯一索引防止重复收藏
|
||||
-- ALTER TABLE user_favorites ADD UNIQUE INDEX uk_openid_name (openid, name);
|
||||
3
backend/target/maven-archiver/pom.properties
Normal file
3
backend/target/maven-archiver/pom.properties
Normal file
@@ -0,0 +1,3 @@
|
||||
artifactId=naming
|
||||
groupId=com.jiansu
|
||||
version=0.0.1-SNAPSHOT
|
||||
@@ -1,5 +1,14 @@
|
||||
com\jiansu\naming\model\NameCard$NameCardBuilder.class
|
||||
com\jiansu\naming\model\NameCard.class
|
||||
com\jiansu\naming\repository\UserFavoriteRepository.class
|
||||
com\jiansu\naming\service\KimiService.class
|
||||
com\jiansu\naming\config\AsyncConfig.class
|
||||
com\jiansu\naming\controller\NamingController.class
|
||||
com\jiansu\naming\service\MiniMaxService.class
|
||||
com\jiansu\naming\entity\UserFavorite.class
|
||||
com\jiansu\naming\model\NameCard$NameCardBuilder.class
|
||||
com\jiansu\naming\service\ToneAnalysisService.class
|
||||
com\jiansu\naming\service\KimiService$1.class
|
||||
com\jiansu\naming\model\NameCard.class
|
||||
com\jiansu\naming\service\ToneAnalysisService$ToneResult.class
|
||||
com\jiansu\naming\controller\FavoriteController.class
|
||||
com\jiansu\naming\NamingApplication.class
|
||||
com\jiansu\naming\entity\UserFavorite$UserFavoriteBuilder.class
|
||||
com\jiansu\naming\service\FavoriteService.class
|
||||
|
||||
@@ -1,4 +1,10 @@
|
||||
C:\Users\<EFBFBD><EFBFBD><EFBFBD><EFBFBD>\Desktop\JianSu-Naming\backend\src\main\java\com\jiansu\naming\controller\NamingController.java
|
||||
C:\Users\<EFBFBD><EFBFBD><EFBFBD><EFBFBD>\Desktop\JianSu-Naming\backend\src\main\java\com\jiansu\naming\model\NameCard.java
|
||||
C:\Users\<EFBFBD><EFBFBD><EFBFBD><EFBFBD>\Desktop\JianSu-Naming\backend\src\main\java\com\jiansu\naming\service\GeminiService.java
|
||||
C:\Users\<EFBFBD><EFBFBD><EFBFBD><EFBFBD>\Desktop\JianSu-Naming\backend\src\main\java\com\jiansu\naming\NamingApplication.java
|
||||
C:\Users\南音\Desktop\JianSu-Naming\backend\src\main\java\com\jiansu\naming\entity\UserFavorite.java
|
||||
C:\Users\南音\Desktop\JianSu-Naming\backend\src\main\java\com\jiansu\naming\config\AsyncConfig.java
|
||||
C:\Users\南音\Desktop\JianSu-Naming\backend\src\main\java\com\jiansu\naming\controller\FavoriteController.java
|
||||
C:\Users\南音\Desktop\JianSu-Naming\backend\src\main\java\com\jiansu\naming\controller\NamingController.java
|
||||
C:\Users\南音\Desktop\JianSu-Naming\backend\src\main\java\com\jiansu\naming\model\NameCard.java
|
||||
C:\Users\南音\Desktop\JianSu-Naming\backend\src\main\java\com\jiansu\naming\repository\UserFavoriteRepository.java
|
||||
C:\Users\南音\Desktop\JianSu-Naming\backend\src\main\java\com\jiansu\naming\service\ToneAnalysisService.java
|
||||
C:\Users\南音\Desktop\JianSu-Naming\backend\src\main\java\com\jiansu\naming\NamingApplication.java
|
||||
C:\Users\南音\Desktop\JianSu-Naming\backend\src\main\java\com\jiansu\naming\service\KimiService.java
|
||||
C:\Users\南音\Desktop\JianSu-Naming\backend\src\main\java\com\jiansu\naming\service\FavoriteService.java
|
||||
|
||||
BIN
backend/target/naming-0.0.1-SNAPSHOT.war
Normal file
BIN
backend/target/naming-0.0.1-SNAPSHOT.war
Normal file
Binary file not shown.
BIN
backend/target/naming-0.0.1-SNAPSHOT.war.original
Normal file
BIN
backend/target/naming-0.0.1-SNAPSHOT.war.original
Normal file
Binary file not shown.
@@ -0,0 +1,45 @@
|
||||
server:
|
||||
port: 8080
|
||||
servlet:
|
||||
encoding:
|
||||
charset: UTF-8
|
||||
enabled: true
|
||||
force: true
|
||||
|
||||
spring:
|
||||
application:
|
||||
name: jiansu-naming
|
||||
|
||||
# 数据库配置 - MySQL 5.7
|
||||
datasource:
|
||||
url: jdbc:mysql://47.115.201.202:3306/jiansu_db?useSSL=false&serverTimezone=Asia/Shanghai&characterEncoding=UTF-8&useUnicode=true
|
||||
driver-class-name: com.mysql.jdbc.Driver
|
||||
username: root
|
||||
password: Feastcoding@2023
|
||||
# 连接池配置
|
||||
hikari:
|
||||
minimum-idle: 5
|
||||
maximum-pool-size: 20
|
||||
idle-timeout: 300000
|
||||
max-lifetime: 1200000
|
||||
connection-timeout: 20000
|
||||
|
||||
jpa:
|
||||
hibernate:
|
||||
ddl-auto: update
|
||||
show-sql: false
|
||||
properties:
|
||||
hibernate:
|
||||
dialect: org.hibernate.dialect.MySQL57Dialect
|
||||
format_sql: true
|
||||
|
||||
# Kimi (Moonshot AI) API 配置
|
||||
kimi:
|
||||
api-key: ${KIMI_API_KEY:sk-EORjVwYTlXMTIFmelkt6ebWlOOLk9qCkm2PR0tvKXdkAnSdd}
|
||||
api-url: https://api.moonshot.cn/v1/chat/completions
|
||||
|
||||
# 日志配置
|
||||
logging:
|
||||
level:
|
||||
com.jiansu.naming: INFO
|
||||
org.hibernate.SQL: WARN
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,28 @@
|
||||
-- 见素起名 - MySQL 5.7 数据库建表语句
|
||||
-- 创建数据库
|
||||
CREATE DATABASE IF NOT EXISTS jiansu_db
|
||||
CHARACTER SET utf8mb4
|
||||
COLLATE utf8mb4_unicode_ci;
|
||||
|
||||
USE jiansu_db;
|
||||
|
||||
-- 用户收藏表
|
||||
CREATE TABLE IF NOT EXISTS user_favorites (
|
||||
id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
|
||||
openid VARCHAR(100) NOT NULL COMMENT '用户微信openid',
|
||||
name VARCHAR(50) NOT NULL COMMENT '名字',
|
||||
origin VARCHAR(500) COMMENT '出处/诗词',
|
||||
description VARCHAR(1000) COMMENT '解析文案',
|
||||
tone VARCHAR(20) COMMENT '声韵',
|
||||
score DOUBLE COMMENT '见素评分',
|
||||
mode VARCHAR(20) COMMENT '生成模式',
|
||||
keyword VARCHAR(100) COMMENT '关键词',
|
||||
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '收藏时间',
|
||||
|
||||
INDEX idx_openid (openid),
|
||||
INDEX idx_openid_name (openid, name),
|
||||
INDEX idx_create_time (create_time)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户收藏表';
|
||||
|
||||
-- 可选:创建唯一索引防止重复收藏
|
||||
-- ALTER TABLE user_favorites ADD UNIQUE INDEX uk_openid_name (openid, name);
|
||||
Binary file not shown.
Binary file not shown.
BIN
backend/target/naming-0.0.1-SNAPSHOT/WEB-INF/lib/antlr-2.7.7.jar
Normal file
BIN
backend/target/naming-0.0.1-SNAPSHOT/WEB-INF/lib/antlr-2.7.7.jar
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
backend/target/naming-0.0.1-SNAPSHOT/WEB-INF/lib/h2-2.1.214.jar
Normal file
BIN
backend/target/naming-0.0.1-SNAPSHOT/WEB-INF/lib/h2-2.1.214.jar
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
backend/target/naming-0.0.1-SNAPSHOT/WEB-INF/lib/okio-3.6.0.jar
Normal file
BIN
backend/target/naming-0.0.1-SNAPSHOT/WEB-INF/lib/okio-3.6.0.jar
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user