feat: 完善见素起名小程序功能
- 添加收藏锦囊功能,支持查看和删除收藏 - 实现积分系统,每日赠送5次灵感次数 - 添加静心阅读功能,阅读15秒可获得额外次数 - 实现灵感广场,展示用户分享的名字 - 添加字源溯源组件,长按汉字查看详情 - 优化空状态和结语卡片样式统一 - 添加音频控制(静音/风铃/雨落/古琴/白噪音/森林/溪流) - 优化名字生成逻辑,确保每次返回5个不重复名字 - 修复卡片翻转样式问题 - 移除首页动态提醒气泡
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,18 @@
|
||||
package com.jiansu.naming.controller;
|
||||
|
||||
import com.jiansu.naming.model.NameCard;
|
||||
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.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
@RestController
|
||||
@@ -19,18 +24,71 @@ public class NamingController {
|
||||
@Autowired
|
||||
private KimiService kimiService;
|
||||
|
||||
@Autowired
|
||||
private CreditsService creditsService;
|
||||
|
||||
@GetMapping("/generate")
|
||||
public List<NameCard> generate(
|
||||
public ResponseEntity<?> generate(
|
||||
@RequestParam(defaultValue = "清冷") String keyword,
|
||||
@RequestParam(required = false) String mode,
|
||||
@RequestParam(required = false) String surname,
|
||||
@RequestParam(required = false, defaultValue = "3") Integer count,
|
||||
@RequestParam(required = false) String batch) {
|
||||
@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"); // 如:从事艺术行业
|
||||
|
||||
log.info("生成名字请求 - 关键词: {}, 模式: {}, 批次: {}, 数量: {}", keyword, mode, batch, count);
|
||||
if (name == null || name.isEmpty()) {
|
||||
Map<String, Object> response = new HashMap<>();
|
||||
response.put("success", false);
|
||||
response.put("message", "名字不能为空");
|
||||
return ResponseEntity.badRequest().body(response);
|
||||
}
|
||||
|
||||
// 使用异步方法获取结果
|
||||
CompletableFuture<List<NameCard>> future = kimiService.generateNamesAsync(keyword, mode, surname, count);
|
||||
return future.join();
|
||||
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;
|
||||
}
|
||||
@@ -12,7 +12,9 @@ import lombok.NoArgsConstructor;
|
||||
public class NameCard {
|
||||
private String name; // 名字
|
||||
private String origin; // 出处/诗句
|
||||
private String description; // “通感”叙事描述
|
||||
private String description; // "通感"叙事描述
|
||||
private Double score; // 声韵评分(后端计算)
|
||||
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,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();
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ 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;
|
||||
@@ -62,6 +63,7 @@ public class FavoriteService {
|
||||
/**
|
||||
* 取消收藏
|
||||
*/
|
||||
@Transactional
|
||||
public void removeFavorite(String openid, String name) {
|
||||
userFavoriteRepository.deleteByOpenidAndName(openid, name);
|
||||
log.info("用户 {} 取消收藏名字 {}", openid, name);
|
||||
|
||||
@@ -34,6 +34,11 @@ public class KimiService {
|
||||
|
||||
@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;
|
||||
|
||||
@@ -124,49 +129,91 @@ public class KimiService {
|
||||
* @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("开始异步生成 {} 个名字,模式: {}", count, mode);
|
||||
|
||||
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;
|
||||
|
||||
// 创建 count 个并行任务
|
||||
List<CompletableFuture<NameCard>> futures = new ArrayList<>();
|
||||
for (int i = 0; i < count; i++) {
|
||||
final int index = i;
|
||||
CompletableFuture<NameCard> future = CompletableFuture.supplyAsync(() -> {
|
||||
try {
|
||||
return callAiToGenerateSingleName(keyword, finalMode, surname, index);
|
||||
} catch (Exception e) {
|
||||
log.error("第 {} 个名字生成失败: {}", index, e.getMessage());
|
||||
|
||||
// 使用 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);
|
||||
}
|
||||
}
|
||||
}, jianSuAiExecutor).exceptionally(ex -> {
|
||||
log.error("第 {} 个任务异常: {}", index, ex.getMessage());
|
||||
return null;
|
||||
});
|
||||
futures.add(future);
|
||||
}
|
||||
}
|
||||
|
||||
// 等待所有任务完成并合并结果
|
||||
return CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]))
|
||||
.thenApply(v -> {
|
||||
List<NameCard> results = futures.stream()
|
||||
.map(CompletableFuture::join)
|
||||
.filter(card -> card != null)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
long duration = System.currentTimeMillis() - startTime;
|
||||
log.info("异步生成完成,成功 {} 个,耗时 {}ms", results.size(), duration);
|
||||
|
||||
return results;
|
||||
});
|
||||
|
||||
long duration = System.currentTimeMillis() - startTime;
|
||||
log.info("异步生成完成,成功 {} 个不重复名字,耗时 {}ms,共进行 {} 轮", results.size(), duration, round);
|
||||
|
||||
return CompletableFuture.completedFuture(results);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -174,16 +221,20 @@ public class KimiService {
|
||||
*/
|
||||
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", "moonshot-v1-8k");
|
||||
jsonBody.put("model", model);
|
||||
jsonBody.put("temperature", 0.7);
|
||||
// 使用随机种子确保每次生成结果不同
|
||||
jsonBody.put("seed", (int) (Math.random() * 1000000) + seed * 1000);
|
||||
@@ -311,4 +362,189 @@ public class KimiService {
|
||||
|
||||
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("网络请求失败");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -38,6 +38,12 @@ kimi:
|
||||
api-key: ${KIMI_API_KEY:sk-EORjVwYTlXMTIFmelkt6ebWlOOLk9qCkm2PR0tvKXdkAnSdd}
|
||||
api-url: https://api.moonshot.cn/v1/chat/completions
|
||||
|
||||
# 微信小程序配置
|
||||
wechat:
|
||||
miniapp:
|
||||
app-id: wx4793dc8fe6f34d2d
|
||||
app-secret: d6514ef9115f2fa5224da98800a7bd8d
|
||||
|
||||
# 日志配置
|
||||
logging:
|
||||
level:
|
||||
|
||||
@@ -38,6 +38,12 @@ kimi:
|
||||
api-key: ${KIMI_API_KEY:sk-EORjVwYTlXMTIFmelkt6ebWlOOLk9qCkm2PR0tvKXdkAnSdd}
|
||||
api-url: https://api.moonshot.cn/v1/chat/completions
|
||||
|
||||
# 微信小程序配置
|
||||
wechat:
|
||||
miniapp:
|
||||
app-id: wx4793dc8fe6f34d2d
|
||||
app-secret: d6514ef9115f2fa5224da98800a7bd8d
|
||||
|
||||
# 日志配置
|
||||
logging:
|
||||
level:
|
||||
|
||||
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.
Reference in New Issue
Block a user