feat: 修复 Redis 超时问题、固定小程序端口、新增人生事件模块及优化多个页面

- 修复 Redis 超时:添加 commons-pool2 依赖,启用 Lettuce 连接池,超时提升至 15s
- 固定 mini-program H5 端口为 5175,避免与 web 项目端口冲突
- 新增人生事件(life-event)模块:表单和详情页面
- 新增 EpicScript 灵感接口(Controller/Service/DTO)
- 优化登录、引导、主页、记录、剧本详情等多个页面
- 优化服务管理脚本和 Nginx 配置

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-10 11:38:35 +08:00
parent 507d1ebdab
commit 60c63850ee
36 changed files with 4545 additions and 3043 deletions
+93 -99
View File
@@ -1,139 +1,117 @@
/**
* 爽文剧本服务
* 处理剧本的增删改查
*/
import { get, post, put, del } from './request.js'
export const getScriptList = async () => {
const response = await get('/epicScript/listAll')
return response
return get('/epicScript/listAll')
}
export const getScriptPage = async ({ pageNum = 1, pageSize = 10 }) => {
const response = await get('/epicScript/page', { pageNum, pageSize })
return response
return get('/epicScript/page', { pageNum, pageSize })
}
export const getScriptById = async (id) => {
const response = await get('/epicScript/detail', { id })
return response
return get('/epicScript/detail', { id })
}
export const getInspirationRecommendations = async () => {
return get('/epicScript/inspiration/recommendations')
}
export const getRandomInspirations = async (size = 3) => {
return get('/epicScript/inspiration/random', { size })
}
export const generateFromInspiration = async (payload) => {
return post('/epicScript/inspiration/generate', payload)
}
export const createScript = async (scriptData) => {
const requestData = transformToBackendFormat(scriptData)
const response = await post('/epicScript/create', requestData)
return response
return post('/epicScript/create', transformToBackendFormat(scriptData))
}
export const updateScript = async (scriptData) => {
const requestData = transformToBackendFormat(scriptData)
const response = await put('/epicScript/update', requestData)
return response
return put('/epicScript/update', transformToBackendFormat(scriptData))
}
export const selectScript = async (id) => {
const response = await put('/epicScript/select', { id })
return response
return put('/epicScript/select', { id })
}
export const deleteScript = async (id) => {
const response = await del('/epicScript/delete', { id })
return response
return del('/epicScript/delete', { id })
}
const textOrFallback = (value, fallback) => {
return value && String(value).trim() ? String(value).trim() : fallback
}
export const buildCharacterInfo = (profile = {}) => {
const hobbies = Array.isArray(profile.hobbies) && profile.hobbies.length
? profile.hobbies.join('、')
: '未设置'
const parts = [
`昵称:${textOrFallback(profile.nickname, '未设置')}`,
`性别:${textOrFallback(profile.gender, '未设置')}`,
`MBTI${textOrFallback(profile.mbti, '未设置')}`,
`星座:${textOrFallback(profile.zodiac, '未设置')}`,
`职业:${textOrFallback(profile.profession, '未设置')}`,
`兴趣:${hobbies}`
]
if (profile.future?.vision) parts.push(`未来愿景:${profile.future.vision}`)
if (profile.future?.ideal) parts.push(`理想生活:${profile.future.ideal}`)
return parts.join('\n')
}
export const buildLifeEventsSummary = (events = [], profile = {}) => {
const parts = []
const addMemory = (label, memory) => {
if (memory?.text) {
parts.push(`${label}${memory.date || '未知日期'}${memory.text}`)
}
}
addMemory('童年记忆', profile.childhood)
addMemory('高光时刻', profile.joy)
addMemory('低谷经历', profile.low)
events.forEach((event) => {
const tag = Array.isArray(event.tags) && event.tags.length ? ` #${event.tags.join(' #')}` : ''
parts.push(`【人生事件】${event.time || event.date || '未知日期'} ${event.title || '未命名'}${tag}${event.content || event.description || ''}`)
})
return parts.join('\n')
}
const transformToBackendFormat = (frontendData) => {
const {
id,
title,
theme,
style,
length,
content,
isSelected,
character,
events
events,
plotJson
} = frontendData
let title = theme || '我的剧本'
let plotIntro = ''
let plotTurning = ''
let plotClimax = ''
let plotEnding = ''
if (content) {
const sections = content.split(/【[^】]+】/)
const titles = content.match(/【[^】]+】/g) || []
titles.forEach((t, index) => {
const sectionContent = sections[index + 1]?.trim() || ''
if (t.includes('序幕') || t.includes('低谷')) {
plotIntro = sectionContent
} else if (t.includes('转折') || t.includes('契机')) {
plotTurning = sectionContent
} else if (t.includes('高潮') || t.includes('抉择')) {
plotClimax = sectionContent
} else if (t.includes('结局') || t.includes('开始')) {
plotEnding = sectionContent
}
})
}
let characterInfo = ''
if (character) {
const parts = [
`姓名:${character.nickname || '未设置'}`,
`性别:${character.gender || '未设置'}`,
`MBTI${character.mbti || '未设置'}`,
`星座:${character.zodiac || '未设置'}`,
`职业:${character.profession || '未设置'}`,
`兴趣爱好:${character.hobbies?.join(',') || '无'}`
]
if (character.future) {
if (character.future.vision) parts.push(`未来愿景:${character.future.vision}`)
if (character.future.ideal) parts.push(`理想生活:${character.future.ideal}`)
}
characterInfo = parts.join('\n')
}
let lifeEventsSummary = ''
const eventParts = []
if (character) {
if (character.childhood?.text) {
eventParts.push(`【童年记忆】(${character.childhood.date || '未知时间'}): ${character.childhood.text}`)
}
if (character.joy?.text) {
eventParts.push(`【高光时刻】(${character.joy.date || '未知时间'}): ${character.joy.text}`)
}
if (character.low?.text) {
eventParts.push(`【至暗时刻】(${character.low.date || '未知时间'}): ${character.low.text}`)
}
}
if (events && Array.isArray(events)) {
events.forEach(e => {
const dateStr = e.time || e.eventDate || '未知时间'
const titleStr = e.title || '无标题'
const contentStr = e.content || ''
eventParts.push(`【人生事件】(${dateStr}) ${titleStr}${contentStr ? ': ' + contentStr : ''}`)
})
}
lifeEventsSummary = eventParts.join('\n')
const scriptTitle = title || theme || '我的人生剧本'
const characterInfo = frontendData.characterInfo || buildCharacterInfo(character || {})
const lifeEventsSummary = frontendData.lifeEventsSummary || buildLifeEventsSummary(events || [], character || {})
return {
id,
title,
title: scriptTitle,
theme,
style,
length,
plotIntro,
plotTurning,
plotClimax,
plotEnding,
plotJson: content ? { fullContent: content } : null,
plotIntro: frontendData.plotIntro || '',
plotTurning: frontendData.plotTurning || '',
plotClimax: frontendData.plotClimax || '',
plotEnding: frontendData.plotEnding || '',
plotJson: plotJson || (content ? { fullContent: content } : null),
isSelected,
characterInfo,
lifeEventsSummary
@@ -156,7 +134,8 @@ export const transformToFrontendFormat = (backendData) => {
plotEnding,
plotJson,
isSelected,
createTime
createTime,
updateTime
} = backendData
let content = ''
@@ -171,6 +150,9 @@ export const transformToFrontendFormat = (backendData) => {
content = parts.join('\n\n')
}
const plainContent = content.replace(/[#>*_`-]/g, '').trim()
const summary = plainContent ? plainContent.slice(0, 90) : (theme || '一段正在生成的人生爽文。')
return {
id,
userId,
@@ -179,8 +161,15 @@ export const transformToFrontendFormat = (backendData) => {
style: style || '',
length: length || 'medium',
content,
isSelected: isSelected || false,
date: createTime ? new Date(createTime).toLocaleDateString() : new Date().toLocaleDateString()
summary,
plotJson,
isSelected: Boolean(isSelected),
createTime,
updateTime,
date: createTime ? String(createTime).slice(0, 10) : new Date().toLocaleDateString(),
mode: plotJson?.mode || 'custom',
prompt: plotJson?.prompt || '',
wordCount: content ? content.length : 0
}
}
@@ -193,10 +182,15 @@ export default {
getScriptList,
getScriptPage,
getScriptById,
getInspirationRecommendations,
getRandomInspirations,
generateFromInspiration,
createScript,
updateScript,
selectScript,
deleteScript,
buildCharacterInfo,
buildLifeEventsSummary,
transformToFrontendFormat,
transformListToFrontend
}