一、基本信息

组号:69

组员:李重昊

负责模块:前端开发和库表设计

二、任务概述

应用模块:前端开发未完成部分

  • 主页应用创建、我的应用/精选应用展示

  • 应用对话页 SSE 流式输出与网页预览

  • 应用管理页(管理员)操作完善

  • 菜单可见性与权限提示修复

对话历史模块:库表设计相关部分

  • 设计并落地 chat_history

  • 增加游标分页关键复合索引 (appId, createTime)

三、主要技术方案与实现

1、目标

1.1 应用生成预览能力

根据文档要求,AI 生成过程需要“边输出边展示”,并在生成结束后能在页面右侧直接预览网页。 我的方案是:

  1. 前端对话页用 EventSource 对接 /app/chat/gen/code

  2. 实时拼接 AI 返回 chunk 到聊天气泡

  3. 收到 done 事件后,自动尝试预览地址 /static/{codeGenType}_{appId}/

  4. 若连接关闭但无 chunk,给出明确提示,避免空白气泡误导

1.2 应用管理页面可用性

应用管理、用户管理菜单不应“看起来消失”。 处理方式:保留菜单可见,点击时做权限提示(未登录/非管理员)。

1.3 对话历史库表设计

为后续“游标分页加载历史消息”做准备,在 chat_history 表中加入:

  • messageType 区分 user/ai

  • parentId 预留扩展

  • 核心索引 idx_appId_createTime (appId, createTime)`

2、对话历史库表设计与落库

我在 SQL 初始化脚本中新增 chat_history 表结构,并补齐单列索引与复合索引,满足后续按 createTime 游标向前翻页的性能需求。

-- 对话历史表(用于对话记忆与游标分页)
create table if not exists chat_history
(
    id          bigint auto_increment comment 'id' primary key,
    message     text                               not null comment '消息',
    messageType varchar(32)                        not null comment 'user/ai',
    appId       bigint                             not null comment '应用id',
    userId      bigint                             not null comment '创建用户id',
    parentId    bigint                             null comment '父消息id',
    createTime  datetime default CURRENT_TIMESTAMP not null comment '创建时间',
    updateTime  datetime default CURRENT_TIMESTAMP not null on update CURRENT_TIMESTAMP comment '更新时间',
    isDelete    tinyint  default 0                 not null comment '是否删除',
    INDEX idx_appId (appId),
    INDEX idx_createTime (createTime),
    INDEX idx_appId_createTime (appId, createTime)
) comment '对话历史' collate = utf8mb4_unicode_ci;

3、应用管理页新增“预览生成应用”能力

在管理员应用列表中增加“预览生成”按钮,直接打开后端静态预览路径;若已有部署 key,则可打开部署作品地址。

<template v-else-if="column.key === 'action'">
  <a-space>
    <a-button size="small" type="primary" ghost @click="previewGenerated(record)">
      预览生成
    </a-button>
    <a-button v-if="record.deployKey" size="small" @click="openDeployed(record)">查看作品</a-button>
    <a-button size="small" @click="goEdit(record.id)">编辑</a-button>
    <a-button size="small" @click="setFeatured(record, true)">精选</a-button>
    <a-button size="small" @click="setFeatured(record, false)">取消精选</a-button>
    <a-popconfirm title="确定删除该应用吗?" @confirm="doDelete(record.id)">
      <a-button danger size="small">删除</a-button>
    </a-popconfirm>
  </a-space>
</template>

预览URL构建逻辑

const getApiBaseUrl = () => {
  const defaults = (request as unknown as { defaults?: { baseURL?: string } }).defaults
  return defaults?.baseURL || 'http://localhost:8080/api'
}

const previewGenerated = (record: API.AppVO) => {
  if (!record?.id) return
  const codeGenType = record.codeGenType || 'multi_file'
  const url = `${getApiBaseUrl()}/static/${codeGenType}_${record.id}/`
  window.open(url, '_blank')
}

4、 菜单补全与权限设置

“应用管理/用户管理”菜单显示,对非管理员访问做前端提示拦截。

const originItems = [
  {
    key: '/',
    icon: () => h(HomeOutlined),
    label: '主页',
    title: '主页',
  },
  {
    key: '/admin/appManage',
    label: '应用管理',
    title: '应用管理',
  },
  {
    key: '/admin/userManage',
    label: '用户管理',
    title: '用户管理',
  },
  // ...
]
const handleMenuClick: MenuProps['onClick'] = (e) => {
  const key = e.key as string
  selectedKeys.value = [key]
  const isAdminMenu = key.startsWith('/admin')
  if (isAdminMenu) {
    const loginUser = loginUserStore.loginUser
    if (!loginUser?.id) {
      message.warning('请先登录')
      router.push('/user/login')
      return
    }
    if (loginUser.userRole !== 'admin') {
      message.warning('该页面仅管理员可访问')
      return
    }
  }
  if (key.startsWith('/')) {
    router.push(key)
  }
}

5、 应用对话页 SSE 输出与预览链路增强

实现了更稳健的流式展示与预览逻辑:

  • 兼容 JSON chunk / 纯文本 chunk

  • done 后刷新预览

  • 进入页面时自动探测是否存在预览文件

  • 无输出时显示明确提示,避免空白 AI 气泡

const refreshPreviewIfExists = async () => {
  if (!appId.value) return
  const codeGenType = appInfo.value?.codeGenType || 'multi_file'
  const candidate = `${baseURL.value}/static/${codeGenType}_${appId.value}/index.html`
  try {
    const res = await fetch(candidate, {
      method: 'GET',
      credentials: 'include',
    })
    if (res.ok) {
      previewUrl.value = `${baseURL.value}/static/${codeGenType}_${appId.value}/`
    } else {
      previewUrl.value = ''
    }
  } catch {
    previewUrl.value = ''
  }
}
es = new EventSource(url, { withCredentials: true } as unknown as EventSourceInit)

es.onmessage = (evt) => {
  if (completed) return
  try {
    let chunk = ''
    try {
      const parsed = JSON.parse(evt.data)
      chunk = parsed?.d ?? ''
    } catch {
      // 兼容后端异常情况下直接返回纯文本 data
      chunk = evt.data ?? ''
    }
    if (chunk === undefined || chunk === null) {
      chunk = ''
    }
    full += chunk
    if (chunk.trim().length > 0) {
      hasValidChunk = true
    }
    messages.value[aiIndex].content = full
    messages.value[aiIndex].loading = false
    scrollBottom()
  } catch {
    messages.value[aiIndex].loading = false
    messages.value[aiIndex].content = '解析流式响应失败,请重试'
    completed = true
    isGenerating.value = false
    es?.close()
    es = null
  }
}

es.addEventListener('done', async () => {
  if (completed) return
  completed = true
  isGenerating.value = false
  messages.value[aiIndex].loading = false
  if (!hasValidChunk && !messages.value[aiIndex].content) {
    messages.value[aiIndex].content = 'AI 暂无输出,请重试或检查后端日志。'
  }
  es?.close()
  es = null
  await fetchApp()
  await refreshPreviewIfExists()
})

6、 后端 AI 门面注入修复

在服务层补齐 AiCodeGeneratorFacade 注入,保证ai能正确输出到前端

@Autowired
private UserService userService;

@Autowired
private AiCodeGeneratorFacade aiCodeGeneratorFacade;

四、结果与验证

4.1 功能结果

  • 已完成应用管理页中的“预览生成应用”入口

  • 应用对话页支持流式输出与预览自动刷新

  • 菜单项可见性恢复,权限提示更明确

  • chat_history 表已创建,复合索引可用

4.2 验证结果

  • 前端 lint / build 通过(已在开发环境执行)

  • 数据库验证通过:DESCRIBE chat_history 可返回字段,索引包含 idx_appId_createTime

五、问题与解决

  1. 管理菜单“消失”

    • 原因:前端按角色直接过滤隐藏

    • 处理:改为可见 + 点击时提示权限

  2. AI 无输出空白气泡

    • 原因:流关闭时无有效 chunk 没有反馈

    • 处理:增加 hasValidChunk 与结束兜底提示;兼容纯文本流

  3. 后端流式调用潜在空指针

    • 原因:AiCodeGeneratorFacade 未注入

    • 处理:补齐 @Autowired

六、经验总结

本次任务中我需要将同组同学制作的后端接口,完成相应的前端显示。主要完成的是主页的展示以及应用管理和用户管理。在本次实现过程中,在写好前端页面时与ai的对话一直不能正常实现,并多次出现空白或者报错。经过多次检查输出后,发现可能是SSE在解析失败的情况下被当成了正常结束,先修正了在无返回条件下无输出的问题,然后发现后端AppServiceImpl没有注入,返回为空指针。

本次修正的难度略大于前几次,总体来说有不少收获。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐