Compare commits
2 Commits
1a749cdf71
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2c47fb8f65 | ||
|
|
be1f5722ab |
3
backend/.idea/misc.xml
generated
3
backend/.idea/misc.xml
generated
@@ -1,6 +1,9 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
||||||
|
<component name="FrameworkDetectionExcludesConfiguration">
|
||||||
|
<file type="web" url="file://$PROJECT_DIR$" />
|
||||||
|
</component>
|
||||||
<component name="MavenProjectsManager">
|
<component name="MavenProjectsManager">
|
||||||
<option name="originalFiles">
|
<option name="originalFiles">
|
||||||
<list>
|
<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>
|
<groupId>com.jiansu</groupId>
|
||||||
<artifactId>naming</artifactId>
|
<artifactId>naming</artifactId>
|
||||||
<version>0.0.1-SNAPSHOT</version>
|
<version>0.0.1-SNAPSHOT</version>
|
||||||
|
<packaging>war</packaging>
|
||||||
<name>jiansu-naming</name>
|
<name>jiansu-naming</name>
|
||||||
<description>JianSu Naming AI Backend</description>
|
<description>JianSu Naming AI Backend</description>
|
||||||
<properties>
|
<properties>
|
||||||
@@ -21,6 +22,12 @@
|
|||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
<artifactId>spring-boot-starter-web</artifactId>
|
<artifactId>spring-boot-starter-web</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<!-- 打包为 WAR,Tomcat 由外部容器提供 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-tomcat</artifactId>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.projectlombok</groupId>
|
<groupId>org.projectlombok</groupId>
|
||||||
<artifactId>lombok</artifactId>
|
<artifactId>lombok</artifactId>
|
||||||
@@ -46,6 +53,24 @@
|
|||||||
<artifactId>spring-boot-starter-test</artifactId>
|
<artifactId>spring-boot-starter-test</artifactId>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</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>
|
</dependencies>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
|
|||||||
@@ -2,10 +2,18 @@ package com.jiansu.naming;
|
|||||||
|
|
||||||
import org.springframework.boot.SpringApplication;
|
import org.springframework.boot.SpringApplication;
|
||||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
import org.springframework.boot.builder.SpringApplicationBuilder;
|
||||||
|
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
|
||||||
|
|
||||||
@SpringBootApplication
|
@SpringBootApplication
|
||||||
public class NamingApplication {
|
public class NamingApplication extends SpringBootServletInitializer {
|
||||||
|
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
SpringApplication.run(NamingApplication.class, 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,74 @@
|
|||||||
|
package com.jiansu.naming.controller;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
import org.springframework.web.client.RestTemplate;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/auth")
|
||||||
|
@CrossOrigin(origins = "*")
|
||||||
|
@Slf4j
|
||||||
|
public class AuthController {
|
||||||
|
|
||||||
|
@Value("${wechat.miniapp.app-id}")
|
||||||
|
private String appId;
|
||||||
|
|
||||||
|
@Value("${wechat.miniapp.app-secret}")
|
||||||
|
private String appSecret;
|
||||||
|
|
||||||
|
private final RestTemplate restTemplate = new RestTemplate();
|
||||||
|
private final ObjectMapper objectMapper = new ObjectMapper();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 微信登录 - 用 code 换取 openid
|
||||||
|
*/
|
||||||
|
@PostMapping("/login")
|
||||||
|
public ResponseEntity<?> wxLogin(@RequestBody Map<String, String> request) {
|
||||||
|
String code = request.get("code");
|
||||||
|
if (code == null || code.isEmpty()) {
|
||||||
|
Map<String, Object> response = new HashMap<>();
|
||||||
|
response.put("success", false);
|
||||||
|
response.put("message", "code 不能为空");
|
||||||
|
return ResponseEntity.badRequest().body(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 调用微信接口换取 openid
|
||||||
|
String url = String.format(
|
||||||
|
"https://api.weixin.qq.com/sns/jscode2session?appid=%s&secret=%s&js_code=%s&grant_type=authorization_code",
|
||||||
|
appId, appSecret, code
|
||||||
|
);
|
||||||
|
|
||||||
|
String result = restTemplate.getForObject(url, String.class);
|
||||||
|
JsonNode jsonNode = objectMapper.readTree(result);
|
||||||
|
|
||||||
|
Map<String, Object> response = new HashMap<>();
|
||||||
|
|
||||||
|
if (jsonNode.has("openid")) {
|
||||||
|
String openid = jsonNode.get("openid").asText();
|
||||||
|
response.put("success", true);
|
||||||
|
response.put("openid", openid);
|
||||||
|
log.info("微信登录成功,openid: {}", openid);
|
||||||
|
} else {
|
||||||
|
response.put("success", false);
|
||||||
|
response.put("message", jsonNode.has("errmsg") ? jsonNode.get("errmsg").asText() : "登录失败");
|
||||||
|
log.error("微信登录失败: {}", result);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ResponseEntity.ok(response);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("微信登录异常", e);
|
||||||
|
Map<String, Object> response = new HashMap<>();
|
||||||
|
response.put("success", false);
|
||||||
|
response.put("message", "登录异常: " + e.getMessage());
|
||||||
|
return ResponseEntity.ok(response);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,401 @@
|
|||||||
|
package com.jiansu.naming.controller;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import net.sourceforge.pinyin4j.PinyinHelper;
|
||||||
|
import net.sourceforge.pinyin4j.format.HanyuPinyinOutputFormat;
|
||||||
|
import net.sourceforge.pinyin4j.format.HanyuPinyinToneType;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/char")
|
||||||
|
@CrossOrigin(origins = "*")
|
||||||
|
@Slf4j
|
||||||
|
public class CharController {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取汉字详情
|
||||||
|
*/
|
||||||
|
@GetMapping("/detail")
|
||||||
|
public ResponseEntity<?> getCharDetail(@RequestParam String character) {
|
||||||
|
Map<String, Object> response = new HashMap<>();
|
||||||
|
|
||||||
|
if (character == null || character.length() != 1) {
|
||||||
|
response.put("success", false);
|
||||||
|
response.put("message", "请输入单个汉字");
|
||||||
|
return ResponseEntity.badRequest().body(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
Map<String, Object> data = new HashMap<>();
|
||||||
|
data.put("char", character);
|
||||||
|
data.put("pinyin", getPinyin(character));
|
||||||
|
data.put("radical", getRadical(character));
|
||||||
|
data.put("strokes", getStrokes(character));
|
||||||
|
data.put("wuxing", getWuxing(character));
|
||||||
|
data.put("meaning", getMeaning(character));
|
||||||
|
data.put("imagery", getImagery(character));
|
||||||
|
data.put("poetry", getPoetry(character));
|
||||||
|
|
||||||
|
response.put("success", true);
|
||||||
|
response.put("data", data);
|
||||||
|
|
||||||
|
log.info("获取汉字详情: {}", character);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("获取汉字详情失败", e);
|
||||||
|
response.put("success", false);
|
||||||
|
response.put("message", "获取失败: " + e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
return ResponseEntity.ok(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取拼音
|
||||||
|
*/
|
||||||
|
private String getPinyin(String chinese) {
|
||||||
|
try {
|
||||||
|
HanyuPinyinOutputFormat format = new HanyuPinyinOutputFormat();
|
||||||
|
format.setToneType(HanyuPinyinToneType.WITH_TONE_MARK);
|
||||||
|
String[] pinyinArray = PinyinHelper.toHanyuPinyinStringArray(chinese.charAt(0));
|
||||||
|
if (pinyinArray != null && pinyinArray.length > 0) {
|
||||||
|
return pinyinArray[0];
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("拼音转换失败", e);
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取部首(简化版,实际应该查字典)
|
||||||
|
*/
|
||||||
|
private String getRadical(String chinese) {
|
||||||
|
// 这里简化处理,实际应该查询汉字字典数据库
|
||||||
|
Map<String, String> radicalMap = new HashMap<>();
|
||||||
|
radicalMap.put("兰", "艹");
|
||||||
|
radicalMap.put("泽", "氵");
|
||||||
|
radicalMap.put("墨", "土");
|
||||||
|
radicalMap.put("清", "氵");
|
||||||
|
radicalMap.put("风", "风");
|
||||||
|
radicalMap.put("云", "二");
|
||||||
|
radicalMap.put("山", "山");
|
||||||
|
radicalMap.put("水", "水");
|
||||||
|
radicalMap.put("雅", "隹");
|
||||||
|
radicalMap.put("若", "艹");
|
||||||
|
radicalMap.put("溪", "氵");
|
||||||
|
radicalMap.put("月", "月");
|
||||||
|
radicalMap.put("明", "日");
|
||||||
|
radicalMap.put("思", "心");
|
||||||
|
radicalMap.put("梦", "木");
|
||||||
|
radicalMap.put("雪", "雨");
|
||||||
|
radicalMap.put("诗", "讠");
|
||||||
|
radicalMap.put("书", "乛");
|
||||||
|
radicalMap.put("画", "田");
|
||||||
|
radicalMap.put("琴", "王");
|
||||||
|
radicalMap.put("茶", "艹");
|
||||||
|
radicalMap.put("竹", "竹");
|
||||||
|
radicalMap.put("松", "木");
|
||||||
|
radicalMap.put("梅", "木");
|
||||||
|
radicalMap.put("竹", "竹");
|
||||||
|
radicalMap.put("兰", "艹");
|
||||||
|
radicalMap.put("菊", "艹");
|
||||||
|
radicalMap.put("荷", "艹");
|
||||||
|
radicalMap.put("莲", "艹");
|
||||||
|
radicalMap.put("桃", "木");
|
||||||
|
radicalMap.put("李", "木");
|
||||||
|
radicalMap.put("杏", "木");
|
||||||
|
radicalMap.put("梨", "木");
|
||||||
|
radicalMap.put("棠", "木");
|
||||||
|
radicalMap.put("梧", "木");
|
||||||
|
radicalMap.put("桐", "木");
|
||||||
|
radicalMap.put("枫", "木");
|
||||||
|
radicalMap.put("柳", "木");
|
||||||
|
radicalMap.put("杨", "木");
|
||||||
|
radicalMap.put("柏", "木");
|
||||||
|
radicalMap.put("楠", "木");
|
||||||
|
radicalMap.put("梓", "木");
|
||||||
|
radicalMap.put("榆", "木");
|
||||||
|
radicalMap.put("桦", "木");
|
||||||
|
radicalMap.put("樱", "木");
|
||||||
|
radicalMap.put("柠", "木");
|
||||||
|
radicalMap.put("橙", "木");
|
||||||
|
radicalMap.put("柚", "木");
|
||||||
|
radicalMap.put("棠", "木");
|
||||||
|
return radicalMap.getOrDefault(chinese, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取笔画数(简化版)
|
||||||
|
*/
|
||||||
|
private int getStrokes(String chinese) {
|
||||||
|
// 简化处理,实际应该查询汉字字典
|
||||||
|
Map<String, Integer> strokesMap = new HashMap<>();
|
||||||
|
strokesMap.put("兰", 5);
|
||||||
|
strokesMap.put("泽", 8);
|
||||||
|
strokesMap.put("墨", 15);
|
||||||
|
strokesMap.put("清", 11);
|
||||||
|
strokesMap.put("风", 4);
|
||||||
|
strokesMap.put("云", 4);
|
||||||
|
strokesMap.put("山", 3);
|
||||||
|
strokesMap.put("水", 4);
|
||||||
|
strokesMap.put("雅", 12);
|
||||||
|
strokesMap.put("若", 8);
|
||||||
|
strokesMap.put("溪", 13);
|
||||||
|
strokesMap.put("月", 4);
|
||||||
|
strokesMap.put("明", 8);
|
||||||
|
strokesMap.put("思", 9);
|
||||||
|
strokesMap.put("梦", 11);
|
||||||
|
strokesMap.put("雪", 11);
|
||||||
|
strokesMap.put("诗", 8);
|
||||||
|
strokesMap.put("书", 4);
|
||||||
|
strokesMap.put("画", 8);
|
||||||
|
strokesMap.put("琴", 12);
|
||||||
|
strokesMap.put("茶", 9);
|
||||||
|
strokesMap.put("竹", 6);
|
||||||
|
strokesMap.put("松", 8);
|
||||||
|
strokesMap.put("梅", 11);
|
||||||
|
strokesMap.put("菊", 11);
|
||||||
|
strokesMap.put("荷", 10);
|
||||||
|
strokesMap.put("莲", 10);
|
||||||
|
strokesMap.put("桃", 10);
|
||||||
|
strokesMap.put("李", 7);
|
||||||
|
strokesMap.put("杏", 7);
|
||||||
|
strokesMap.put("梨", 11);
|
||||||
|
strokesMap.put("棠", 12);
|
||||||
|
strokesMap.put("梧", 11);
|
||||||
|
strokesMap.put("桐", 10);
|
||||||
|
strokesMap.put("枫", 8);
|
||||||
|
strokesMap.put("柳", 9);
|
||||||
|
strokesMap.put("杨", 7);
|
||||||
|
strokesMap.put("柏", 9);
|
||||||
|
strokesMap.put("楠", 13);
|
||||||
|
strokesMap.put("梓", 11);
|
||||||
|
strokesMap.put("榆", 13);
|
||||||
|
strokesMap.put("桦", 10);
|
||||||
|
strokesMap.put("樱", 15);
|
||||||
|
strokesMap.put("柠", 9);
|
||||||
|
strokesMap.put("橙", 16);
|
||||||
|
strokesMap.put("柚", 9);
|
||||||
|
return strokesMap.getOrDefault(chinese, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取五行属性(简化版)
|
||||||
|
*/
|
||||||
|
private String getWuxing(String chinese) {
|
||||||
|
Map<String, String> wuxingMap = new HashMap<>();
|
||||||
|
wuxingMap.put("兰", "木");
|
||||||
|
wuxingMap.put("泽", "水");
|
||||||
|
wuxingMap.put("墨", "土");
|
||||||
|
wuxingMap.put("清", "水");
|
||||||
|
wuxingMap.put("风", "木");
|
||||||
|
wuxingMap.put("云", "水");
|
||||||
|
wuxingMap.put("山", "土");
|
||||||
|
wuxingMap.put("水", "水");
|
||||||
|
wuxingMap.put("雅", "木");
|
||||||
|
wuxingMap.put("若", "木");
|
||||||
|
wuxingMap.put("溪", "水");
|
||||||
|
wuxingMap.put("月", "木");
|
||||||
|
wuxingMap.put("明", "火");
|
||||||
|
wuxingMap.put("思", "金");
|
||||||
|
wuxingMap.put("梦", "木");
|
||||||
|
wuxingMap.put("雪", "水");
|
||||||
|
wuxingMap.put("诗", "金");
|
||||||
|
wuxingMap.put("书", "金");
|
||||||
|
wuxingMap.put("画", "土");
|
||||||
|
wuxingMap.put("琴", "木");
|
||||||
|
wuxingMap.put("茶", "木");
|
||||||
|
wuxingMap.put("竹", "木");
|
||||||
|
wuxingMap.put("松", "木");
|
||||||
|
wuxingMap.put("梅", "木");
|
||||||
|
wuxingMap.put("菊", "木");
|
||||||
|
wuxingMap.put("荷", "木");
|
||||||
|
wuxingMap.put("莲", "木");
|
||||||
|
wuxingMap.put("桃", "木");
|
||||||
|
wuxingMap.put("李", "木");
|
||||||
|
wuxingMap.put("杏", "木");
|
||||||
|
wuxingMap.put("梨", "木");
|
||||||
|
wuxingMap.put("棠", "木");
|
||||||
|
wuxingMap.put("梧", "木");
|
||||||
|
wuxingMap.put("桐", "木");
|
||||||
|
wuxingMap.put("枫", "木");
|
||||||
|
wuxingMap.put("柳", "木");
|
||||||
|
wuxingMap.put("杨", "木");
|
||||||
|
wuxingMap.put("柏", "木");
|
||||||
|
wuxingMap.put("楠", "木");
|
||||||
|
wuxingMap.put("梓", "木");
|
||||||
|
wuxingMap.put("榆", "木");
|
||||||
|
wuxingMap.put("桦", "木");
|
||||||
|
wuxingMap.put("樱", "木");
|
||||||
|
wuxingMap.put("柠", "木");
|
||||||
|
wuxingMap.put("橙", "木");
|
||||||
|
wuxingMap.put("柚", "木");
|
||||||
|
return wuxingMap.getOrDefault(chinese, "未知");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取字义(简化版)
|
||||||
|
*/
|
||||||
|
private String getMeaning(String chinese) {
|
||||||
|
Map<String, String> meaningMap = new HashMap<>();
|
||||||
|
meaningMap.put("兰", "本义指兰花,象征高洁、典雅、坚贞不渝的品格。");
|
||||||
|
meaningMap.put("泽", "本义指水汇聚的地方,引申为恩泽、润泽、仁慈。");
|
||||||
|
meaningMap.put("墨", "本义指书写用的黑色颜料,象征文雅、深沉、有学问。");
|
||||||
|
meaningMap.put("清", "本义指水清澈透明,引申为纯洁、清明、高洁。");
|
||||||
|
meaningMap.put("风", "本义指空气流动,引申为风度、风采、气节。");
|
||||||
|
meaningMap.put("云", "本义指天空中的云彩,象征自由、飘逸、高远。");
|
||||||
|
meaningMap.put("山", "本义指地面高耸的部分,象征稳重、坚毅、可靠。");
|
||||||
|
meaningMap.put("水", "本义指无色无味的液体,象征智慧、柔韧、包容。");
|
||||||
|
meaningMap.put("雅", "本义指规范、标准,引申为高雅、文雅、美好。");
|
||||||
|
meaningMap.put("若", "本义指顺从、如同,引申为好像、选择,寓意温婉。");
|
||||||
|
meaningMap.put("溪", "本义指山间小河,引申为清澈、灵动、源远流长。");
|
||||||
|
meaningMap.put("月", "本义指月亮,象征光明、圆满、清雅高洁。");
|
||||||
|
meaningMap.put("明", "本义指光明、明亮,引申为聪慧、明理、通达。");
|
||||||
|
meaningMap.put("思", "本义指思考、思念,引申为智慧、深情、细腻。");
|
||||||
|
meaningMap.put("梦", "本义指睡眠中的幻象,引申为理想、憧憬、浪漫。");
|
||||||
|
meaningMap.put("雪", "本义指天空降落的白色晶体,象征纯洁、高洁、清雅。");
|
||||||
|
meaningMap.put("诗", "本义指诗歌、文学,象征文采、才情、雅致。");
|
||||||
|
meaningMap.put("书", "本义指书写、典籍,象征学识、修养、文雅。");
|
||||||
|
meaningMap.put("画", "本义指绘画、描绘,象征艺术、美感、才情。");
|
||||||
|
meaningMap.put("琴", "本义指古琴,象征高雅、艺术、知音。");
|
||||||
|
meaningMap.put("茶", "本义指茶树、茶叶,象征清雅、淡泊、禅意。");
|
||||||
|
meaningMap.put("竹", "本义指竹子,象征气节、坚韧、虚心。");
|
||||||
|
meaningMap.put("松", "本义指松树,象征坚贞、长寿、高洁。");
|
||||||
|
meaningMap.put("梅", "本义指梅花,象征傲骨、坚韧、清雅。");
|
||||||
|
meaningMap.put("菊", "本义指菊花,象征隐逸、高洁、长寿。");
|
||||||
|
meaningMap.put("荷", "本义指荷花,象征纯洁、高雅、出淤泥而不染。");
|
||||||
|
meaningMap.put("莲", "本义指莲花,象征纯洁、美好、佛教圣花。");
|
||||||
|
meaningMap.put("桃", "本义指桃树、桃花,象征美好、爱情、长寿。");
|
||||||
|
meaningMap.put("李", "本义指李树,象征果实、成就、春天。");
|
||||||
|
meaningMap.put("杏", "本义指杏树,象征春天、美好、医学。");
|
||||||
|
meaningMap.put("梨", "本义指梨树,象征纯洁、离别、清雅。");
|
||||||
|
meaningMap.put("棠", "本义指棠梨树,象征美丽、温和、春天。");
|
||||||
|
meaningMap.put("梧", "本义指梧桐树,象征高洁、忠贞、吉祥。");
|
||||||
|
meaningMap.put("桐", "本义指桐树,象征高洁、坚贞、美好。");
|
||||||
|
meaningMap.put("枫", "本义指枫树,象征热情、浪漫、秋天。");
|
||||||
|
meaningMap.put("柳", "本义指柳树,象征柔美、离别、春天。");
|
||||||
|
meaningMap.put("杨", "本义指杨树,象征挺拔、向上、正直。");
|
||||||
|
meaningMap.put("柏", "本义指柏树,象征坚贞、长寿、高洁。");
|
||||||
|
meaningMap.put("楠", "本义指楠木,象征珍贵、高贵、坚韧。");
|
||||||
|
meaningMap.put("梓", "本义指梓树,象征故乡、生机、成长。");
|
||||||
|
meaningMap.put("榆", "本义指榆树,象征坚韧、朴实、春天。");
|
||||||
|
meaningMap.put("桦", "本义指白桦树,象征纯洁、高洁、挺拔。");
|
||||||
|
meaningMap.put("樱", "本义指樱花树,象征美丽、浪漫、短暂。");
|
||||||
|
meaningMap.put("柠", "本义指柠檬,象征清新、活力、阳光。");
|
||||||
|
meaningMap.put("橙", "本义指橙子,象征丰收、喜悦、温暖。");
|
||||||
|
meaningMap.put("柚", "本义指柚子,象征团圆、保佑、清新。");
|
||||||
|
return meaningMap.getOrDefault(chinese, "暂无解析");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取起名意象
|
||||||
|
*/
|
||||||
|
private String getImagery(String chinese) {
|
||||||
|
Map<String, String> imageryMap = new HashMap<>();
|
||||||
|
imageryMap.put("兰", "兰花幽香,寓意品格高洁、气质优雅,适合期望孩子有高尚情操的家庭。");
|
||||||
|
imageryMap.put("泽", "泽被万物,寓意恩泽深厚、仁慈宽厚,适合期望孩子有博爱之心的家庭。");
|
||||||
|
imageryMap.put("墨", "墨香书卷,寓意文采斐然、学识渊博,适合期望孩子有文学才华的家庭。");
|
||||||
|
imageryMap.put("清", "清风明月,寓意心地纯净、志向高远,适合期望孩子有清正品格的家庭。");
|
||||||
|
imageryMap.put("风", "风度翩翩,寓意气宇轩昂、潇洒不凡,适合期望孩子有出众气质的家庭。");
|
||||||
|
imageryMap.put("云", "云游四方,寓意胸怀广阔、志向高远,适合期望孩子有远大理想的家庭。");
|
||||||
|
imageryMap.put("山", "高山仰止,寓意稳重可靠、坚毅不拔,适合期望孩子有坚强品格的家庭。");
|
||||||
|
imageryMap.put("水", "上善若水,寓意智慧灵动、柔韧包容,适合期望孩子有聪慧性情的家庭。");
|
||||||
|
imageryMap.put("雅", "温文尔雅,寓意举止得体、品味高雅,如芝兰玉树,生于庭阶。");
|
||||||
|
imageryMap.put("若", "虚怀若谷,寓意谦逊包容、从容自在,如兰若生春阳。");
|
||||||
|
imageryMap.put("溪", "溪水潺潺,寓意灵动清澈、生生不息,如溪水绕石,柔中带刚。");
|
||||||
|
imageryMap.put("月", "明月清风,寓意光明磊落、清雅高洁,如月出东山,照彻千里。");
|
||||||
|
imageryMap.put("明", "明德惟馨,寓意聪慧明理、通达事理,如日月之光,普照万物。");
|
||||||
|
imageryMap.put("思", "深思熟虑,寓意智慧深邃、情感细腻,如思接千载,视通万里。");
|
||||||
|
imageryMap.put("梦", "梦寐以求,寓意理想远大、憧憬美好,如梦笔生花,才华横溢。");
|
||||||
|
imageryMap.put("雪", "雪中送炭,寓意纯洁无瑕、高洁清雅,如冰雪聪明,晶莹剔透。");
|
||||||
|
imageryMap.put("诗", "诗情画意,寓意文采斐然、才情横溢,如诗中有画,画中有诗。");
|
||||||
|
imageryMap.put("书", "书香门第,寓意学识渊博、修养深厚,如博览群书,学富五车。");
|
||||||
|
imageryMap.put("画", "画龙点睛,寓意艺术天赋、审美高雅,如画中有诗,意境深远。");
|
||||||
|
imageryMap.put("琴", "琴瑟和鸣,寓意高雅艺术、知音难觅,如琴心剑胆,刚柔并济。");
|
||||||
|
imageryMap.put("茶", "茶余饭后,寓意清雅淡泊、禅意悠然,如品茶悟道,静心养性。");
|
||||||
|
imageryMap.put("竹", "竹报平安,寓意气节高尚、虚心向上,如竹影清风,高洁自持。");
|
||||||
|
imageryMap.put("松", "松柏之志,寓意坚贞不屈、长寿安康,如松鹤延年,傲雪凌霜。");
|
||||||
|
imageryMap.put("梅", "梅香自苦寒,寓意傲骨铮铮、坚韧不拔,如梅花三弄,清雅高洁。");
|
||||||
|
imageryMap.put("菊", "菊残犹有傲霜枝,寓意隐逸高洁、淡泊明志,如采菊东篱,悠然自得。");
|
||||||
|
imageryMap.put("荷", "出淤泥而不染,寓意纯洁高雅、不染尘俗,如荷风送香,竹露滴清。");
|
||||||
|
imageryMap.put("莲", "莲开并蒂,寓意纯洁美好、百年好合,如步步生莲,清雅脱俗。");
|
||||||
|
imageryMap.put("桃", "桃李满天下,寓意美好爱情、长寿安康,如人面桃花,相映成趣。");
|
||||||
|
imageryMap.put("李", "李下不正冠,寓意果实累累、成就斐然,如投桃报李,知恩图报。");
|
||||||
|
imageryMap.put("杏", "杏林春暖,寓意春天美好、医术高明,如红杏出墙,生机勃勃。");
|
||||||
|
imageryMap.put("梨", "梨花带雨,寓意纯洁清雅、楚楚动人,如梨园弟子,才艺双全。");
|
||||||
|
imageryMap.put("棠", "棠棣之花,寓意兄弟和睦、家庭美满,如海棠春睡,娇艳欲滴。");
|
||||||
|
imageryMap.put("梧", "梧桐更兼细雨,寓意高洁忠贞、吉祥美好,如凤栖梧桐,人才荟萃。");
|
||||||
|
imageryMap.put("桐", "桐花万里丹山路,寓意高洁坚贞、前程似锦,如桐叶封弟,仁义道德。");
|
||||||
|
imageryMap.put("枫", "枫叶荻花秋瑟瑟,寓意热情浪漫、成熟稳重,如丹枫迎秋,层林尽染。");
|
||||||
|
imageryMap.put("柳", "柳暗花明又一村,寓意柔美婉约、生机勃勃,如柳岸花明,春意盎然。");
|
||||||
|
imageryMap.put("杨", "杨花落尽子规啼,寓意挺拔向上、正直不阿,如百步穿杨,技艺精湛。");
|
||||||
|
imageryMap.put("柏", "柏舟之誓,寓意坚贞不渝、长寿安康,如松柏后凋,岁寒知友。");
|
||||||
|
imageryMap.put("楠", "楠木参天,寓意珍贵高贵、坚韧不拔,如楠梓之材,栋梁之器。");
|
||||||
|
imageryMap.put("梓", "梓里桑梓,寓意故乡情深、生机勃勃,如敬恭桑梓,不忘根本。");
|
||||||
|
imageryMap.put("榆", "榆柳荫后檐,寓意坚韧朴实、春天希望,如榆火相传,文明延续。");
|
||||||
|
imageryMap.put("桦", "白桦亭亭,寓意纯洁高洁、挺拔向上,如桦皮做纸,文化传承。");
|
||||||
|
imageryMap.put("樱", "樱花烂漫,寓意美丽浪漫、珍惜当下,如樱唇一点,娇艳动人。");
|
||||||
|
imageryMap.put("柠", "柠檬清香,寓意清新活力、阳光开朗,如柠月清风,沁人心脾。");
|
||||||
|
imageryMap.put("橙", "橙黄橘绿,寓意丰收喜悦、温暖阳光,如橙香四溢,甜蜜幸福。");
|
||||||
|
imageryMap.put("柚", "柚香满园,寓意团圆美满、保佑平安,如柚叶青青,清新怡人。");
|
||||||
|
return imageryMap.getOrDefault(chinese, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取诗词典故
|
||||||
|
*/
|
||||||
|
private String getPoetry(String chinese) {
|
||||||
|
Map<String, String> poetryMap = new HashMap<>();
|
||||||
|
poetryMap.put("兰", "「兰之猗猗,扬扬其香。」——《诗经》");
|
||||||
|
poetryMap.put("泽", "「芳与泽其杂糅兮,唯昭质其犹未亏。」——《离骚》");
|
||||||
|
poetryMap.put("墨", "「墨池飞出北溟鱼,笔锋杀尽中山兔。」——李白");
|
||||||
|
poetryMap.put("清", "「清风徐来,水波不兴。」——苏轼《赤壁赋》");
|
||||||
|
poetryMap.put("风", "「随风潜入夜,润物细无声。」——杜甫");
|
||||||
|
poetryMap.put("云", "「行到水穷处,坐看云起时。」——王维");
|
||||||
|
poetryMap.put("山", "「采菊东篱下,悠然见南山。」——陶渊明");
|
||||||
|
poetryMap.put("水", "「问君能有几多愁,恰似一江春水向东流。」——李煜");
|
||||||
|
poetryMap.put("雅", "「雅步擢纤腰,巧笑发皓齿。」——曹植");
|
||||||
|
poetryMap.put("若", "「若有人兮山之阿,被薜荔兮带女萝。」——屈原");
|
||||||
|
poetryMap.put("溪", "「旧时茅店社林边,路转溪桥忽见。」——辛弃疾");
|
||||||
|
poetryMap.put("月", "「举杯邀明月,对影成三人。」——李白");
|
||||||
|
poetryMap.put("明", "「明月几时有,把酒问青天。」——苏轼");
|
||||||
|
poetryMap.put("思", "「举头望明月,低头思故乡。」——李白");
|
||||||
|
poetryMap.put("梦", "「庄生晓梦迷蝴蝶,望帝春心托杜鹃。」——李商隐");
|
||||||
|
poetryMap.put("雪", "「忽如一夜春风来,千树万树梨花开。」——岑参");
|
||||||
|
poetryMap.put("诗", "「诗中有画,画中有诗。」——苏轼评王维");
|
||||||
|
poetryMap.put("书", "「书中自有黄金屋,书中自有颜如玉。」——赵恒");
|
||||||
|
poetryMap.put("画", "「远看山有色,近听水无声。」——王维");
|
||||||
|
poetryMap.put("琴", "「欲将心事付瑶琴,知音少,弦断有谁听。」——岳飞");
|
||||||
|
poetryMap.put("茶", "「茶烟轻扬落花风,竹影半墙如画。」——苏轼");
|
||||||
|
poetryMap.put("竹", "「宁可食无肉,不可居无竹。」——苏轼");
|
||||||
|
poetryMap.put("松", "「明月松间照,清泉石上流。」——王维");
|
||||||
|
poetryMap.put("梅", "「墙角数枝梅,凌寒独自开。」——王安石");
|
||||||
|
poetryMap.put("菊", "「采菊东篱下,悠然见南山。」——陶渊明");
|
||||||
|
poetryMap.put("荷", "「接天莲叶无穷碧,映日荷花别样红。」——杨万里");
|
||||||
|
poetryMap.put("莲", "「出淤泥而不染,濯清涟而不妖。」——周敦颐");
|
||||||
|
poetryMap.put("桃", "「人面不知何处去,桃花依旧笑春风。」——崔护");
|
||||||
|
poetryMap.put("李", "「桃李不言,下自成蹊。」——《史记》");
|
||||||
|
poetryMap.put("杏", "「春色满园关不住,一枝红杏出墙来。」——叶绍翁");
|
||||||
|
poetryMap.put("梨", "「梨花院落溶溶月,柳絮池塘淡淡风。」——晏殊");
|
||||||
|
poetryMap.put("棠", "「棠棣之华,鄂不韡韡。」——《诗经》");
|
||||||
|
poetryMap.put("梧", "「梧桐更兼细雨,到黄昏、点点滴滴。」——李清照");
|
||||||
|
poetryMap.put("桐", "「桐花万里丹山路,雏凤清于老凤声。」——李商隐");
|
||||||
|
poetryMap.put("枫", "「停车坐爱枫林晚,霜叶红于二月花。」——杜牧");
|
||||||
|
poetryMap.put("柳", "「碧玉妆成一树高,万条垂下绿丝绦。」——贺知章");
|
||||||
|
poetryMap.put("杨", "「杨花落尽子规啼,闻道龙标过五溪。」——李白");
|
||||||
|
poetryMap.put("柏", "「丞相祠堂何处寻,锦官城外柏森森。」——杜甫");
|
||||||
|
poetryMap.put("楠", "「楠树色冥冥,江边一盖青。」——杜甫");
|
||||||
|
poetryMap.put("梓", "「梓泽丘墟,姑苏台榭。」——王勃");
|
||||||
|
poetryMap.put("榆", "「榆柳荫后檐,桃李罗堂前。」——陶渊明");
|
||||||
|
poetryMap.put("桦", "「白桦亭亭立,清风徐自来。」——佚名");
|
||||||
|
poetryMap.put("樱", "「樱花红陌上,柳叶绿池边。」——周恩来");
|
||||||
|
poetryMap.put("柠", "「柠檬香气远,清风送爽来。」——佚名");
|
||||||
|
poetryMap.put("橙", "「橙黄橘绿时,正是好风景。」——苏轼");
|
||||||
|
poetryMap.put("柚", "「柚叶青青,秋实离离。」——佚名");
|
||||||
|
return poetryMap.getOrDefault(chinese, "");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,124 @@
|
|||||||
|
package com.jiansu.naming.controller;
|
||||||
|
|
||||||
|
import com.jiansu.naming.entity.UserCredits;
|
||||||
|
import com.jiansu.naming.service.CreditsService;
|
||||||
|
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.Map;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/credits")
|
||||||
|
@CrossOrigin(origins = "*")
|
||||||
|
@Slf4j
|
||||||
|
public class CreditsController {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private CreditsService creditsService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取用户积分信息
|
||||||
|
*/
|
||||||
|
@GetMapping("/info")
|
||||||
|
public ResponseEntity<?> getCreditsInfo(@RequestParam String openid) {
|
||||||
|
Map<String, Object> response = new HashMap<>();
|
||||||
|
|
||||||
|
if (openid == null || openid.isEmpty()) {
|
||||||
|
response.put("success", false);
|
||||||
|
response.put("message", "openid 不能为空");
|
||||||
|
return ResponseEntity.badRequest().body(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
UserCredits credits = creditsService.getUserCredits(openid);
|
||||||
|
|
||||||
|
Map<String, Object> data = new HashMap<>();
|
||||||
|
data.put("dailyCredits", credits.getDailyCredits());
|
||||||
|
data.put("totalCredits", credits.getTotalCredits());
|
||||||
|
data.put("watchedAdCount", credits.getWatchedAdCount());
|
||||||
|
data.put("maxAdPerDay", 5);
|
||||||
|
data.put("adReward", 3);
|
||||||
|
|
||||||
|
response.put("success", true);
|
||||||
|
response.put("data", data);
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("获取积分信息失败", e);
|
||||||
|
response.put("success", false);
|
||||||
|
response.put("message", "获取失败: " + e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
return ResponseEntity.ok(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 观看广告奖励
|
||||||
|
*/
|
||||||
|
@PostMapping("/reward")
|
||||||
|
public ResponseEntity<?> rewardForAd(@RequestParam String openid) {
|
||||||
|
|
||||||
|
Map<String, Object> response = new HashMap<>();
|
||||||
|
|
||||||
|
if (openid == null || openid.isEmpty()) {
|
||||||
|
response.put("success", false);
|
||||||
|
response.put("message", "openid 不能为空");
|
||||||
|
return ResponseEntity.badRequest().body(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
boolean success = creditsService.rewardForWatchingAd(openid);
|
||||||
|
|
||||||
|
if (success) {
|
||||||
|
UserCredits credits = creditsService.getUserCredits(openid);
|
||||||
|
|
||||||
|
response.put("success", true);
|
||||||
|
response.put("message", "观看成功,获得 3 次生成机会");
|
||||||
|
response.put("dailyCredits", credits.getDailyCredits());
|
||||||
|
response.put("watchedAdCount", credits.getWatchedAdCount());
|
||||||
|
} else {
|
||||||
|
response.put("success", false);
|
||||||
|
response.put("message", "今日观看次数已达上限");
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("奖励失败", e);
|
||||||
|
response.put("success", false);
|
||||||
|
response.put("message", "奖励失败: " + e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
return ResponseEntity.ok(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查是否可生成
|
||||||
|
*/
|
||||||
|
@GetMapping("/check")
|
||||||
|
public ResponseEntity<?> checkCanGenerate(@RequestParam String openid) {
|
||||||
|
Map<String, Object> response = new HashMap<>();
|
||||||
|
|
||||||
|
if (openid == null || openid.isEmpty()) {
|
||||||
|
response.put("success", false);
|
||||||
|
response.put("message", "openid 不能为空");
|
||||||
|
return ResponseEntity.badRequest().body(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
boolean canGenerate = creditsService.hasEnoughCredits(openid);
|
||||||
|
int remaining = creditsService.getRemainingCredits(openid);
|
||||||
|
|
||||||
|
response.put("success", true);
|
||||||
|
response.put("canGenerate", canGenerate);
|
||||||
|
response.put("remainingCredits", remaining);
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("检查失败", e);
|
||||||
|
response.put("success", false);
|
||||||
|
response.put("message", "检查失败: " + e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
return ResponseEntity.ok(response);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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,94 @@
|
|||||||
package com.jiansu.naming.controller;
|
package com.jiansu.naming.controller;
|
||||||
|
|
||||||
import com.jiansu.naming.model.NameCard;
|
import com.jiansu.naming.model.NameCard;
|
||||||
import com.jiansu.naming.service.MiniMaxService;
|
import com.jiansu.naming.service.CreditsService;
|
||||||
|
import com.jiansu.naming.service.KimiService;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/api/names")
|
@RequestMapping(value = "/api/names", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
|
||||||
@CrossOrigin(origins = "*")
|
@CrossOrigin(origins = "*")
|
||||||
|
@Slf4j
|
||||||
public class NamingController {
|
public class NamingController {
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private MiniMaxService miniMaxService;
|
private KimiService kimiService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private CreditsService creditsService;
|
||||||
|
|
||||||
@GetMapping("/generate")
|
@GetMapping("/generate")
|
||||||
public List<NameCard> generate(@RequestParam(defaultValue = "清冷") String keyword) {
|
public ResponseEntity<?> generate(
|
||||||
return miniMaxService.generateNames(keyword);
|
@RequestParam(defaultValue = "清冷") String keyword,
|
||||||
|
@RequestParam(required = false) String mode,
|
||||||
|
@RequestParam(required = false) String surname,
|
||||||
|
@RequestParam(required = false, defaultValue = "5") Integer count,
|
||||||
|
@RequestParam(required = false) String batch,
|
||||||
|
@RequestParam(required = false) String openid,
|
||||||
|
@RequestParam(required = false) String excludeNames) {
|
||||||
|
|
||||||
|
log.info("生成名字请求 - 关键词: {}, 模式: {}, 批次: {}, 数量: {}, openid: {}", keyword, mode, batch, count, openid);
|
||||||
|
|
||||||
|
// 检查并消耗积分(原子操作)
|
||||||
|
if (openid != null && !openid.isEmpty()) {
|
||||||
|
boolean consumed = creditsService.consumeCredit(openid);
|
||||||
|
if (!consumed) {
|
||||||
|
Map<String, Object> response = new HashMap<>();
|
||||||
|
response.put("success", false);
|
||||||
|
response.put("message", "今日灵感次数已用完,请前往个人中心静心阅读获取更多次数");
|
||||||
|
return ResponseEntity.ok(response);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析需要排除的名字列表
|
||||||
|
List<String> excludeList = new ArrayList<String>();
|
||||||
|
if (excludeNames != null && !excludeNames.isEmpty()) {
|
||||||
|
excludeList = java.util.Arrays.asList(excludeNames.split(","));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用异步方法获取结果,确保5个不重复的名字
|
||||||
|
CompletableFuture<List<NameCard>> future = kimiService.generateUniqueNamesAsync(keyword, mode, surname, 5, excludeList);
|
||||||
|
List<NameCard> result = future.join();
|
||||||
|
|
||||||
|
Map<String, Object> response = new HashMap<>();
|
||||||
|
response.put("success", true);
|
||||||
|
response.put("data", result);
|
||||||
|
return ResponseEntity.ok(response);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
/**
|
||||||
|
* AI 深度解析名字
|
||||||
|
*/
|
||||||
|
@PostMapping("/explain")
|
||||||
|
public ResponseEntity<?> explainName(@RequestBody Map<String, String> request) {
|
||||||
|
String name = request.get("name");
|
||||||
|
String context = request.get("context"); // 如:从事艺术行业
|
||||||
|
|
||||||
|
if (name == null || name.isEmpty()) {
|
||||||
|
Map<String, Object> response = new HashMap<>();
|
||||||
|
response.put("success", false);
|
||||||
|
response.put("message", "名字不能为空");
|
||||||
|
return ResponseEntity.badRequest().body(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
log.info("AI 解析名字: {}, 语境: {}", name, context);
|
||||||
|
|
||||||
|
String explanation = kimiService.explainName(name, context);
|
||||||
|
|
||||||
|
Map<String, Object> response = new HashMap<>();
|
||||||
|
response.put("success", true);
|
||||||
|
response.put("data", explanation);
|
||||||
|
|
||||||
|
return ResponseEntity.ok(response);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,149 @@
|
|||||||
|
package com.jiansu.naming.controller;
|
||||||
|
|
||||||
|
import com.jiansu.naming.entity.SquarePost;
|
||||||
|
import com.jiansu.naming.service.SquareService;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.data.domain.Page;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/square")
|
||||||
|
@CrossOrigin(origins = "*")
|
||||||
|
@Slf4j
|
||||||
|
public class SquareController {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private SquareService squareService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取广场帖子列表
|
||||||
|
*/
|
||||||
|
@GetMapping("/posts")
|
||||||
|
public ResponseEntity<?> getPosts(
|
||||||
|
@RequestParam(defaultValue = "0") int page,
|
||||||
|
@RequestParam(defaultValue = "20") int size,
|
||||||
|
@RequestParam(required = false) String tag,
|
||||||
|
@RequestParam(required = false) String keyword,
|
||||||
|
@RequestParam(defaultValue = "latest") String sort) {
|
||||||
|
|
||||||
|
Page<SquarePost> posts;
|
||||||
|
|
||||||
|
if (tag != null && !tag.isEmpty()) {
|
||||||
|
posts = squareService.getPostsByTag(tag, page, size);
|
||||||
|
} else if (keyword != null && !keyword.isEmpty()) {
|
||||||
|
posts = squareService.getPostsByKeyword(keyword, page, size);
|
||||||
|
} else if ("hot".equals(sort)) {
|
||||||
|
posts = squareService.getHotPosts(page, size);
|
||||||
|
} else {
|
||||||
|
posts = squareService.getPosts(page, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, Object> response = new HashMap<>();
|
||||||
|
response.put("success", true);
|
||||||
|
response.put("data", posts.getContent());
|
||||||
|
response.put("total", posts.getTotalElements());
|
||||||
|
response.put("hasMore", posts.hasNext());
|
||||||
|
|
||||||
|
return ResponseEntity.ok(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发布帖子
|
||||||
|
*/
|
||||||
|
@PostMapping("/posts")
|
||||||
|
public ResponseEntity<?> createPost(
|
||||||
|
@RequestParam String name,
|
||||||
|
@RequestParam String origin,
|
||||||
|
@RequestParam String description,
|
||||||
|
@RequestParam String keyword,
|
||||||
|
@RequestParam String mode,
|
||||||
|
@RequestParam String openid,
|
||||||
|
@RequestParam(required = false) String imageUrl) {
|
||||||
|
|
||||||
|
log.info("发布帖子: {}", name);
|
||||||
|
|
||||||
|
SquarePost post = new SquarePost();
|
||||||
|
post.setName(name);
|
||||||
|
post.setOrigin(origin);
|
||||||
|
post.setDescription(description);
|
||||||
|
post.setKeyword(keyword);
|
||||||
|
post.setMode(mode);
|
||||||
|
post.setOpenid(openid);
|
||||||
|
post.setImageUrl(imageUrl);
|
||||||
|
post.setIsPublic(true);
|
||||||
|
post.setLikeCount(0);
|
||||||
|
|
||||||
|
SquarePost saved = squareService.createPost(post);
|
||||||
|
|
||||||
|
Map<String, Object> response = new HashMap<>();
|
||||||
|
response.put("success", true);
|
||||||
|
response.put("data", saved);
|
||||||
|
response.put("message", "发布成功");
|
||||||
|
|
||||||
|
return ResponseEntity.ok(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 点赞
|
||||||
|
*/
|
||||||
|
@PostMapping("/posts/{id}/like")
|
||||||
|
public ResponseEntity<?> likePost(@PathVariable Long id) {
|
||||||
|
squareService.likePost(id);
|
||||||
|
|
||||||
|
Map<String, Object> response = new HashMap<>();
|
||||||
|
response.put("success", true);
|
||||||
|
response.put("message", "点赞成功");
|
||||||
|
|
||||||
|
return ResponseEntity.ok(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取帖子详情
|
||||||
|
*/
|
||||||
|
@GetMapping("/posts/{id}")
|
||||||
|
public ResponseEntity<?> getPostDetail(@PathVariable Long id) {
|
||||||
|
SquarePost post = squareService.getPostById(id).orElse(null);
|
||||||
|
|
||||||
|
Map<String, Object> response = new HashMap<>();
|
||||||
|
if (post != null) {
|
||||||
|
response.put("success", true);
|
||||||
|
response.put("data", post);
|
||||||
|
} else {
|
||||||
|
response.put("success", false);
|
||||||
|
response.put("message", "帖子不存在");
|
||||||
|
}
|
||||||
|
|
||||||
|
return ResponseEntity.ok(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除帖子
|
||||||
|
*/
|
||||||
|
@DeleteMapping("/posts/{id}")
|
||||||
|
public ResponseEntity<?> deletePost(@PathVariable Long id, @RequestParam String openid) {
|
||||||
|
squareService.deletePost(id, openid);
|
||||||
|
|
||||||
|
Map<String, Object> response = new HashMap<>();
|
||||||
|
response.put("success", true);
|
||||||
|
response.put("message", "删除成功");
|
||||||
|
|
||||||
|
return ResponseEntity.ok(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取用户的帖子
|
||||||
|
*/
|
||||||
|
@GetMapping("/user/posts")
|
||||||
|
public ResponseEntity<?> getUserPosts(@RequestParam String openid) {
|
||||||
|
Map<String, Object> response = new HashMap<>();
|
||||||
|
response.put("success", true);
|
||||||
|
response.put("data", squareService.getUserPosts(openid));
|
||||||
|
|
||||||
|
return ResponseEntity.ok(response);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
package com.jiansu.naming.entity;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import org.hibernate.annotations.CreationTimestamp;
|
||||||
|
|
||||||
|
import javax.persistence.*;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
@Table(name = "square_posts")
|
||||||
|
@Data
|
||||||
|
public class SquarePost {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@Column(name = "name_card_id")
|
||||||
|
private Long nameCardId;
|
||||||
|
|
||||||
|
@Column(name = "name", length = 50)
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
@Column(name = "origin", length = 500)
|
||||||
|
private String origin;
|
||||||
|
|
||||||
|
@Column(name = "description", length = 1000)
|
||||||
|
private String description;
|
||||||
|
|
||||||
|
@Column(name = "image_url", length = 500)
|
||||||
|
private String imageUrl;
|
||||||
|
|
||||||
|
@Column(name = "tags", length = 200)
|
||||||
|
private String tags;
|
||||||
|
|
||||||
|
@Column(name = "keyword", length = 50)
|
||||||
|
private String keyword;
|
||||||
|
|
||||||
|
@Column(name = "mode", length = 20)
|
||||||
|
private String mode;
|
||||||
|
|
||||||
|
@Column(name = "like_count")
|
||||||
|
private Integer likeCount = 0;
|
||||||
|
|
||||||
|
@Column(name = "is_public")
|
||||||
|
private Boolean isPublic = true;
|
||||||
|
|
||||||
|
@Column(name = "openid", length = 100)
|
||||||
|
private String openid;
|
||||||
|
|
||||||
|
@CreationTimestamp
|
||||||
|
@Column(name = "created_at")
|
||||||
|
private LocalDateTime createdAt;
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
package com.jiansu.naming.entity;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import org.hibernate.annotations.CreationTimestamp;
|
||||||
|
import org.hibernate.annotations.UpdateTimestamp;
|
||||||
|
|
||||||
|
import javax.persistence.*;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
@Table(name = "user_credits")
|
||||||
|
@Data
|
||||||
|
public class UserCredits {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@Column(name = "openid", length = 100, unique = true, nullable = false)
|
||||||
|
private String openid;
|
||||||
|
|
||||||
|
@Column(name = "total_credits")
|
||||||
|
private Integer totalCredits = 0;
|
||||||
|
|
||||||
|
@Column(name = "daily_credits")
|
||||||
|
private Integer dailyCredits = 5; // 每日赠送次数
|
||||||
|
|
||||||
|
@Column(name = "last_refresh_date")
|
||||||
|
private LocalDate lastRefreshDate;
|
||||||
|
|
||||||
|
@Column(name = "watched_ad_count")
|
||||||
|
private Integer watchedAdCount = 0; // 今日观看广告次数
|
||||||
|
|
||||||
|
@CreationTimestamp
|
||||||
|
@Column(name = "created_at")
|
||||||
|
private LocalDateTime createdAt;
|
||||||
|
|
||||||
|
@UpdateTimestamp
|
||||||
|
@Column(name = "updated_at")
|
||||||
|
private LocalDateTime updatedAt;
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,7 +12,9 @@ import lombok.NoArgsConstructor;
|
|||||||
public class NameCard {
|
public class NameCard {
|
||||||
private String name; // 名字
|
private String name; // 名字
|
||||||
private String origin; // 出处/诗句
|
private String origin; // 出处/诗句
|
||||||
private String description; // “通感”叙事描述
|
private String description; // "通感"叙事描述
|
||||||
private Double score; // 声韵评分(后端计算)
|
private Double score; // 声韵评分(后端计算)
|
||||||
private String tone; // 平仄分析 (如:平仄)
|
private String tone; // 平仄分析 (如:平仄)
|
||||||
|
private Boolean publicShared; // 是否已分享到广场
|
||||||
|
private Long squarePostId; // 广场帖子ID(如果已分享)
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
package com.jiansu.naming.repository;
|
||||||
|
|
||||||
|
import com.jiansu.naming.entity.SquarePost;
|
||||||
|
import org.springframework.data.domain.Page;
|
||||||
|
import org.springframework.data.domain.Pageable;
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
import org.springframework.data.jpa.repository.Modifying;
|
||||||
|
import org.springframework.data.jpa.repository.Query;
|
||||||
|
import org.springframework.data.repository.query.Param;
|
||||||
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Repository
|
||||||
|
public interface SquarePostRepository extends JpaRepository<SquarePost, Long> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询公开的帖子
|
||||||
|
*/
|
||||||
|
Page<SquarePost> findByIsPublicTrueOrderByCreatedAtDesc(Pageable pageable);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据标签查询
|
||||||
|
*/
|
||||||
|
@Query("SELECT s FROM SquarePost s WHERE s.isPublic = true AND s.tags LIKE %:tag% ORDER BY s.createdAt DESC")
|
||||||
|
Page<SquarePost> findByTag(@Param("tag") String tag, Pageable pageable);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据关键词查询
|
||||||
|
*/
|
||||||
|
@Query("SELECT s FROM SquarePost s WHERE s.isPublic = true AND s.keyword = :keyword ORDER BY s.createdAt DESC")
|
||||||
|
Page<SquarePost> findByKeyword(@Param("keyword") String keyword, Pageable pageable);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 增加点赞数
|
||||||
|
*/
|
||||||
|
@Modifying
|
||||||
|
@Query("UPDATE SquarePost s SET s.likeCount = s.likeCount + 1 WHERE s.id = :id")
|
||||||
|
void incrementLikeCount(@Param("id") Long id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询用户的帖子
|
||||||
|
*/
|
||||||
|
List<SquarePost> findByOpenidOrderByCreatedAtDesc(String openid);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询热门帖子
|
||||||
|
*/
|
||||||
|
@Query("SELECT s FROM SquarePost s WHERE s.isPublic = true ORDER BY s.likeCount DESC, s.createdAt DESC")
|
||||||
|
Page<SquarePost> findHotPosts(Pageable pageable);
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package com.jiansu.naming.repository;
|
||||||
|
|
||||||
|
import com.jiansu.naming.entity.UserCredits;
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
@Repository
|
||||||
|
public interface UserCreditsRepository extends JpaRepository<UserCredits, Long> {
|
||||||
|
|
||||||
|
Optional<UserCredits> findByOpenid(String openid);
|
||||||
|
|
||||||
|
boolean existsByOpenid(String openid);
|
||||||
|
}
|
||||||
@@ -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,124 @@
|
|||||||
|
package com.jiansu.naming.service;
|
||||||
|
|
||||||
|
import com.jiansu.naming.entity.UserCredits;
|
||||||
|
import com.jiansu.naming.repository.UserCreditsRepository;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
@Slf4j
|
||||||
|
public class CreditsService {
|
||||||
|
|
||||||
|
private static final int DAILY_FREE_CREDITS = 5; // 每日免费次数
|
||||||
|
private static final int AD_REWARD_CREDITS = 3; // 观看广告奖励次数
|
||||||
|
private static final int MAX_AD_PER_DAY = 5; // 每日最多观看广告次数
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private UserCreditsRepository userCreditsRepository;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取或创建用户积分记录
|
||||||
|
*/
|
||||||
|
@Transactional
|
||||||
|
public UserCredits getOrCreateUserCredits(String openid) {
|
||||||
|
Optional<UserCredits> optional = userCreditsRepository.findByOpenid(openid);
|
||||||
|
|
||||||
|
if (optional.isPresent()) {
|
||||||
|
UserCredits credits = optional.get();
|
||||||
|
// 检查是否需要刷新每日次数
|
||||||
|
if (credits.getLastRefreshDate() == null ||
|
||||||
|
!credits.getLastRefreshDate().equals(LocalDate.now())) {
|
||||||
|
credits.setDailyCredits(DAILY_FREE_CREDITS);
|
||||||
|
credits.setWatchedAdCount(0);
|
||||||
|
credits.setLastRefreshDate(LocalDate.now());
|
||||||
|
userCreditsRepository.save(credits);
|
||||||
|
log.info("刷新用户每日次数: {}", openid);
|
||||||
|
}
|
||||||
|
return credits;
|
||||||
|
} else {
|
||||||
|
// 创建新记录
|
||||||
|
UserCredits credits = new UserCredits();
|
||||||
|
credits.setOpenid(openid);
|
||||||
|
credits.setTotalCredits(0);
|
||||||
|
credits.setDailyCredits(DAILY_FREE_CREDITS);
|
||||||
|
credits.setLastRefreshDate(LocalDate.now());
|
||||||
|
credits.setWatchedAdCount(0);
|
||||||
|
userCreditsRepository.save(credits);
|
||||||
|
log.info("创建用户积分记录: {}", openid);
|
||||||
|
return credits;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查是否有足够的次数
|
||||||
|
*/
|
||||||
|
public boolean hasEnoughCredits(String openid) {
|
||||||
|
UserCredits credits = getOrCreateUserCredits(openid);
|
||||||
|
int remaining = credits.getDailyCredits();
|
||||||
|
log.info("检查用户 {} 的剩余次数: {}", openid, remaining);
|
||||||
|
return remaining > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 消耗一次生成次数(包含检查和消耗,原子操作)
|
||||||
|
*/
|
||||||
|
@Transactional
|
||||||
|
public boolean consumeCredit(String openid) {
|
||||||
|
UserCredits credits = getOrCreateUserCredits(openid);
|
||||||
|
int remaining = credits.getDailyCredits();
|
||||||
|
|
||||||
|
log.info("用户 {} 尝试消耗次数,当前剩余: {}", openid, remaining);
|
||||||
|
|
||||||
|
if (remaining > 0) {
|
||||||
|
credits.setDailyCredits(remaining - 1);
|
||||||
|
credits.setTotalCredits(credits.getTotalCredits() + 1);
|
||||||
|
userCreditsRepository.save(credits);
|
||||||
|
log.info("用户 {} 成功消耗一次生成次数,剩余: {}", openid, credits.getDailyCredits());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
log.warn("用户 {} 次数不足,无法消耗", openid);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 观看广告奖励次数
|
||||||
|
*/
|
||||||
|
@Transactional
|
||||||
|
public boolean rewardForWatchingAd(String openid) {
|
||||||
|
UserCredits credits = getOrCreateUserCredits(openid);
|
||||||
|
|
||||||
|
if (credits.getWatchedAdCount() >= MAX_AD_PER_DAY) {
|
||||||
|
log.info("用户 {} 今日广告观看次数已达上限", openid);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
credits.setDailyCredits(credits.getDailyCredits() + AD_REWARD_CREDITS);
|
||||||
|
credits.setWatchedAdCount(credits.getWatchedAdCount() + 1);
|
||||||
|
userCreditsRepository.save(credits);
|
||||||
|
|
||||||
|
log.info("用户 {} 观看广告获得 {} 次机会,今日已观看: {}",
|
||||||
|
openid, AD_REWARD_CREDITS, credits.getWatchedAdCount());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取用户积分信息
|
||||||
|
*/
|
||||||
|
public UserCredits getUserCredits(String openid) {
|
||||||
|
return getOrCreateUserCredits(openid);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取剩余次数
|
||||||
|
*/
|
||||||
|
public int getRemainingCredits(String openid) {
|
||||||
|
UserCredits credits = getOrCreateUserCredits(openid);
|
||||||
|
return credits.getDailyCredits();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
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 org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 取消收藏
|
||||||
|
*/
|
||||||
|
@Transactional
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
550
backend/src/main/java/com/jiansu/naming/service/KimiService.java
Normal file
550
backend/src/main/java/com/jiansu/naming/service/KimiService.java
Normal file
@@ -0,0 +1,550 @@
|
|||||||
|
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 static final String MODEL_STANDARD = "moonshot-v1-8k";
|
||||||
|
private static final String MODEL_PREMIUM = "moonshot-v1-32k";
|
||||||
|
private static final double PREMIUM_THRESHOLD = 9.5; // 高分名字使用高级模型
|
||||||
|
|
||||||
|
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) {
|
||||||
|
return generateUniqueNamesAsync(keyword, mode, surname, count, new ArrayList<>());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 异步生成指定数量的不重复名字
|
||||||
|
* @param keyword 关键词
|
||||||
|
* @param mode 模式
|
||||||
|
* @param surname 姓氏
|
||||||
|
* @param targetCount 目标数量(默认5个)
|
||||||
|
* @param excludeNames 需要排除的名字列表
|
||||||
|
* @return CompletableFuture<List<NameCard>>
|
||||||
|
*/
|
||||||
|
public CompletableFuture<List<NameCard>> generateUniqueNamesAsync(String keyword, String mode, String surname, int targetCount, List<String> excludeNames) {
|
||||||
|
long startTime = System.currentTimeMillis();
|
||||||
|
log.info("开始异步生成 {} 个不重复名字,模式: {},排除: {}", targetCount, mode, excludeNames);
|
||||||
|
|
||||||
|
// 后门:当关键词为 "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;
|
||||||
|
|
||||||
|
// 使用 Set 存储已生成的名字,确保唯一性
|
||||||
|
java.util.Set<String> generatedNames = new java.util.HashSet<>(excludeNames);
|
||||||
|
java.util.List<NameCard> results = new ArrayList<>();
|
||||||
|
|
||||||
|
// 最多尝试 3 轮,每轮生成 targetCount 个
|
||||||
|
int maxRounds = 3;
|
||||||
|
int round = 0;
|
||||||
|
|
||||||
|
while (results.size() < targetCount && round < maxRounds) {
|
||||||
|
round++;
|
||||||
|
int needCount = targetCount - results.size();
|
||||||
|
log.info("第 {} 轮生成,需要 {} 个名字", round, needCount);
|
||||||
|
|
||||||
|
// 创建本轮并行任务
|
||||||
|
List<CompletableFuture<NameCard>> futures = new ArrayList<>();
|
||||||
|
for (int i = 0; i < needCount; i++) {
|
||||||
|
final int index = i + (round - 1) * targetCount;
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 等待本轮任务完成
|
||||||
|
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
|
||||||
|
|
||||||
|
// 收集本轮结果,去重
|
||||||
|
for (CompletableFuture<NameCard> future : futures) {
|
||||||
|
NameCard card = future.join();
|
||||||
|
if (card != null && card.getName() != null) {
|
||||||
|
String nameKey = card.getName().trim();
|
||||||
|
// 检查是否与已生成的重复
|
||||||
|
if (!generatedNames.contains(nameKey)) {
|
||||||
|
generatedNames.add(nameKey);
|
||||||
|
results.add(card);
|
||||||
|
log.debug("添加名字: {}", nameKey);
|
||||||
|
|
||||||
|
// 如果已经收集够,提前退出
|
||||||
|
if (results.size() >= targetCount) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.debug("跳过重复名字: {}", nameKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
long duration = System.currentTimeMillis() - startTime;
|
||||||
|
log.info("异步生成完成,成功 {} 个不重复名字,耗时 {}ms,共进行 {} 轮", results.size(), duration, round);
|
||||||
|
|
||||||
|
return CompletableFuture.completedFuture(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 : "")
|
||||||
|
+ " 请确保创意独特,避免与之前生成的名字重复。";
|
||||||
|
|
||||||
|
// 模型选择策略:前2个使用标准模型,第3个及以后使用高级模型
|
||||||
|
String model = seed < 2 ? MODEL_STANDARD : MODEL_PREMIUM;
|
||||||
|
log.info("使用模型 {} 生成第 {} 个名字", model, seed + 1);
|
||||||
|
|
||||||
|
// 构建 Kimi API 请求体 (OpenAI 兼容格式)
|
||||||
|
JSONObject jsonBody = new JSONObject();
|
||||||
|
jsonBody.put("model", model);
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AI 深度解析名字
|
||||||
|
*/
|
||||||
|
public String explainName(String name, String context) {
|
||||||
|
String prompt = buildExplainPrompt(name, context);
|
||||||
|
|
||||||
|
Map<String, Object> jsonBody = new HashMap<>();
|
||||||
|
jsonBody.put("model", "moonshot-v1-8k");
|
||||||
|
jsonBody.put("temperature", 0.7);
|
||||||
|
|
||||||
|
List<Map<String, String>> messages = new ArrayList<>();
|
||||||
|
|
||||||
|
Map<String, String> systemMsg = new HashMap<>();
|
||||||
|
systemMsg.put("role", "system");
|
||||||
|
systemMsg.put("content", "你是一位精通中国传统文化、姓名学、诗词典故的起名专家。请用优美、富有诗意的语言解析名字。");
|
||||||
|
messages.add(systemMsg);
|
||||||
|
|
||||||
|
Map<String, String> userMsg = new HashMap<>();
|
||||||
|
userMsg.put("role", "user");
|
||||||
|
userMsg.put("content", prompt);
|
||||||
|
messages.add(userMsg);
|
||||||
|
|
||||||
|
jsonBody.put("messages", messages);
|
||||||
|
|
||||||
|
RequestBody body = RequestBody.create(
|
||||||
|
JSON.toJSONString(jsonBody),
|
||||||
|
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();
|
||||||
|
return parseExplanation(responseBody);
|
||||||
|
} else {
|
||||||
|
String errorMsg = response.body() != null ? response.body().string() : response.message();
|
||||||
|
log.error("Kimi API Error: {} - {}", response.code(), errorMsg);
|
||||||
|
return "抱歉,AI 解析暂时不可用,请稍后重试。";
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
log.error("API Error: ", e);
|
||||||
|
return "网络请求失败,请检查网络连接。";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构建解析 Prompt
|
||||||
|
*/
|
||||||
|
private String buildExplainPrompt(String name, String context) {
|
||||||
|
StringBuilder prompt = new StringBuilder();
|
||||||
|
prompt.append("请深度解析名字\"").append(name).append("\"");
|
||||||
|
|
||||||
|
if (context != null && !context.isEmpty()) {
|
||||||
|
prompt.append(",针对\"").append(context).append("\"这个背景");
|
||||||
|
}
|
||||||
|
|
||||||
|
prompt.append("。请从以下几个方面分析:\n\n" +
|
||||||
|
"1. 字面意境:每个字的含义和组合后的整体意象\n" +
|
||||||
|
"2. 诗词出处:是否有相关的诗词典故\n" +
|
||||||
|
"3. 性格暗示:这个名字给人的感觉和性格特质\n" +
|
||||||
|
"4. 适用场景:适合什么样的人或场合\n\n" +
|
||||||
|
"请用优美、富有诗意的语言回答,控制在300字以内。"
|
||||||
|
);
|
||||||
|
|
||||||
|
return prompt.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析 AI 回复
|
||||||
|
*/
|
||||||
|
private String parseExplanation(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);
|
||||||
|
return "解析失败,请稍后重试。";
|
||||||
|
}
|
||||||
|
|
||||||
|
JSONArray choices = root.getJSONArray("choices");
|
||||||
|
if (choices == null || choices.isEmpty()) {
|
||||||
|
return "暂无解析内容。";
|
||||||
|
}
|
||||||
|
|
||||||
|
JSONObject firstChoice = choices.getJSONObject(0);
|
||||||
|
JSONObject message = firstChoice.getJSONObject("message");
|
||||||
|
return message.getString("content");
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("解析 AI 回复失败", e);
|
||||||
|
return "解析失败,请稍后重试。";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== 模型策略方法 ====================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据分数选择模型
|
||||||
|
* 高分名字使用高级模型以获得更好的质量
|
||||||
|
*/
|
||||||
|
private String selectModelByScore(double score) {
|
||||||
|
return score >= PREMIUM_THRESHOLD ? MODEL_PREMIUM : MODEL_STANDARD;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据请求类型选择模型
|
||||||
|
*/
|
||||||
|
private String selectModelByRequestType(String requestType) {
|
||||||
|
switch (requestType) {
|
||||||
|
case "deep_explain": // 深度解析
|
||||||
|
case "premium": // 高级生成
|
||||||
|
return MODEL_PREMIUM;
|
||||||
|
case "standard":
|
||||||
|
default:
|
||||||
|
return MODEL_STANDARD;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 使用高级模型生成名字(用于高质量需求)
|
||||||
|
*/
|
||||||
|
public NameCard generatePremiumName(String keyword, String mode, String surname) {
|
||||||
|
log.info("使用高级模型生成名字 - 关键词: {}, 模式: {}", keyword, mode);
|
||||||
|
|
||||||
|
String promptTemplate = PROMPT_TEMPLATES.getOrDefault(mode, PROMPT_TEMPLATES.get("classic"));
|
||||||
|
String prompt = promptTemplate
|
||||||
|
.replace("{keyword}", keyword != null ? keyword : "")
|
||||||
|
.replace("{surname}", surname != null ? surname : "");
|
||||||
|
|
||||||
|
Map<String, Object> jsonBody = new HashMap<>();
|
||||||
|
jsonBody.put("model", MODEL_PREMIUM);
|
||||||
|
jsonBody.put("temperature", 0.7);
|
||||||
|
|
||||||
|
List<Map<String, String>> messages = new ArrayList<>();
|
||||||
|
|
||||||
|
Map<String, String> systemMsg = new HashMap<>();
|
||||||
|
systemMsg.put("role", "system");
|
||||||
|
systemMsg.put("content", "你是「见素」的首席起名大师,精通中国传统文化、诗词典故、音韵学。请创作极具意境和美感的名字。");
|
||||||
|
messages.add(systemMsg);
|
||||||
|
|
||||||
|
Map<String, String> userMsg = new HashMap<>();
|
||||||
|
userMsg.put("role", "user");
|
||||||
|
userMsg.put("content", prompt);
|
||||||
|
messages.add(userMsg);
|
||||||
|
|
||||||
|
jsonBody.put("messages", messages);
|
||||||
|
|
||||||
|
RequestBody body = RequestBody.create(
|
||||||
|
JSON.toJSONString(jsonBody),
|
||||||
|
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();
|
||||||
|
NameCard card = parseSingleNameCard(responseBody);
|
||||||
|
// 高级模型生成的名字给予更高评分
|
||||||
|
card.setScore(Math.min(10.0, card.getScore() + 0.3));
|
||||||
|
return card;
|
||||||
|
} else {
|
||||||
|
String errorMsg = response.body() != null ? response.body().string() : response.message();
|
||||||
|
log.error("Kimi Premium API Error: {} - {}", response.code(), errorMsg);
|
||||||
|
throw new RuntimeException("高级模型调用失败");
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
log.error("Premium API Error: ", e);
|
||||||
|
throw new RuntimeException("网络请求失败");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
package com.jiansu.naming.service;
|
||||||
|
|
||||||
|
import com.jiansu.naming.entity.SquarePost;
|
||||||
|
import com.jiansu.naming.repository.SquarePostRepository;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.data.domain.Page;
|
||||||
|
import org.springframework.data.domain.PageRequest;
|
||||||
|
import org.springframework.data.domain.Pageable;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
@Slf4j
|
||||||
|
public class SquareService {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private SquarePostRepository squarePostRepository;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发布帖子
|
||||||
|
*/
|
||||||
|
@Transactional
|
||||||
|
public SquarePost createPost(SquarePost post) {
|
||||||
|
log.info("发布广场帖子: {}", post.getName());
|
||||||
|
return squarePostRepository.save(post);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取帖子列表(分页)
|
||||||
|
*/
|
||||||
|
public Page<SquarePost> getPosts(int page, int size) {
|
||||||
|
Pageable pageable = PageRequest.of(page, size);
|
||||||
|
return squarePostRepository.findByIsPublicTrueOrderByCreatedAtDesc(pageable);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取热门帖子
|
||||||
|
*/
|
||||||
|
public Page<SquarePost> getHotPosts(int page, int size) {
|
||||||
|
Pageable pageable = PageRequest.of(page, size);
|
||||||
|
return squarePostRepository.findHotPosts(pageable);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据标签查询
|
||||||
|
*/
|
||||||
|
public Page<SquarePost> getPostsByTag(String tag, int page, int size) {
|
||||||
|
Pageable pageable = PageRequest.of(page, size);
|
||||||
|
return squarePostRepository.findByTag(tag, pageable);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据关键词查询
|
||||||
|
*/
|
||||||
|
public Page<SquarePost> getPostsByKeyword(String keyword, int page, int size) {
|
||||||
|
Pageable pageable = PageRequest.of(page, size);
|
||||||
|
return squarePostRepository.findByKeyword(keyword, pageable);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 点赞
|
||||||
|
*/
|
||||||
|
@Transactional
|
||||||
|
public void likePost(Long postId) {
|
||||||
|
log.info("帖子点赞: {}", postId);
|
||||||
|
squarePostRepository.incrementLikeCount(postId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取帖子详情
|
||||||
|
*/
|
||||||
|
public Optional<SquarePost> getPostById(Long id) {
|
||||||
|
return squarePostRepository.findById(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除帖子
|
||||||
|
*/
|
||||||
|
@Transactional
|
||||||
|
public void deletePost(Long id, String openid) {
|
||||||
|
Optional<SquarePost> post = squarePostRepository.findById(id);
|
||||||
|
if (post.isPresent() && post.get().getOpenid().equals(openid)) {
|
||||||
|
squarePostRepository.deleteById(id);
|
||||||
|
log.info("删除帖子: {}", id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取用户的帖子
|
||||||
|
*/
|
||||||
|
public List<SquarePost> getUserPosts(String openid) {
|
||||||
|
return squarePostRepository.findByOpenidOrderByCreatedAtDesc(openid);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,11 +1,51 @@
|
|||||||
server:
|
server:
|
||||||
port: 8080
|
port: 8080
|
||||||
|
servlet:
|
||||||
|
encoding:
|
||||||
|
charset: UTF-8
|
||||||
|
enabled: true
|
||||||
|
force: true
|
||||||
|
|
||||||
spring:
|
spring:
|
||||||
application:
|
application:
|
||||||
name: jiansu-naming
|
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 配置
|
# Kimi (Moonshot AI) API 配置
|
||||||
minimax:
|
kimi:
|
||||||
api-key: sk-cp-n0eCZgH5s-NpduAVPo8rpWM9eUBsMOBnIroISIaH6y8eFIpT0VSrCMttzE4bVDbQ-loiMR1b8ZpIsgotQ_yqQRk8_fcUxKHsbhtLfN70oCVaV6-94ZC9Wjk
|
api-key: ${KIMI_API_KEY:sk-EORjVwYTlXMTIFmelkt6ebWlOOLk9qCkm2PR0tvKXdkAnSdd}
|
||||||
api-url: https://api.minimax.chat/v1/text/chatcompletion_v2
|
api-url: https://api.moonshot.cn/v1/chat/completions
|
||||||
|
|
||||||
|
# 微信小程序配置
|
||||||
|
wechat:
|
||||||
|
miniapp:
|
||||||
|
app-id: wx4793dc8fe6f34d2d
|
||||||
|
app-secret: d6514ef9115f2fa5224da98800a7bd8d
|
||||||
|
|
||||||
|
# 日志配置
|
||||||
|
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,51 @@
|
|||||||
server:
|
server:
|
||||||
port: 8080
|
port: 8080
|
||||||
|
servlet:
|
||||||
|
encoding:
|
||||||
|
charset: UTF-8
|
||||||
|
enabled: true
|
||||||
|
force: true
|
||||||
|
|
||||||
spring:
|
spring:
|
||||||
application:
|
application:
|
||||||
name: jiansu-naming
|
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 配置
|
# Kimi (Moonshot AI) API 配置
|
||||||
minimax:
|
kimi:
|
||||||
api-key: sk-cp-n0eCZgH5s-NpduAVPo8rpWM9eUBsMOBnIroISIaH6y8eFIpT0VSrCMttzE4bVDbQ-loiMR1b8ZpIsgotQ_yqQRk8_fcUxKHsbhtLfN70oCVaV6-94ZC9Wjk
|
api-key: ${KIMI_API_KEY:sk-EORjVwYTlXMTIFmelkt6ebWlOOLk9qCkm2PR0tvKXdkAnSdd}
|
||||||
api-url: https://api.minimax.chat/v1/text/chatcompletion_v2
|
api-url: https://api.moonshot.cn/v1/chat/completions
|
||||||
|
|
||||||
|
# 微信小程序配置
|
||||||
|
wechat:
|
||||||
|
miniapp:
|
||||||
|
app-id: wx4793dc8fe6f34d2d
|
||||||
|
app-secret: d6514ef9115f2fa5224da98800a7bd8d
|
||||||
|
|
||||||
|
# 日志配置
|
||||||
|
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.
BIN
backend/target/classes/com/jiansu/naming/entity/SquarePost.class
Normal file
BIN
backend/target/classes/com/jiansu/naming/entity/SquarePost.class
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.
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\repository\UserFavoriteRepository.class
|
||||||
com\jiansu\naming\model\NameCard.class
|
com\jiansu\naming\service\KimiService.class
|
||||||
|
com\jiansu\naming\config\AsyncConfig.class
|
||||||
com\jiansu\naming\controller\NamingController.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\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\南音\Desktop\JianSu-Naming\backend\src\main\java\com\jiansu\naming\entity\UserFavorite.java
|
||||||
C:\Users\<EFBFBD><EFBFBD><EFBFBD><EFBFBD>\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\config\AsyncConfig.java
|
||||||
C:\Users\<EFBFBD><EFBFBD><EFBFBD><EFBFBD>\Desktop\JianSu-Naming\backend\src\main\java\com\jiansu\naming\service\GeminiService.java
|
C:\Users\南音\Desktop\JianSu-Naming\backend\src\main\java\com\jiansu\naming\controller\FavoriteController.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\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.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user