欢迎光临
我们一直在努力

要持续维护AI生成的前端Java后端什么时候该系统学框架

我平时写得最多的是 Controller、Service、JPA 和 SQL。前端出问题时,我通常要补 Node 环境、Vite 配置、Vue 的响应式状态和路由。接口字段、空值和状态码只要没有对齐,前后端就得来回查。开工前,我估过一个中等复杂度 CRUD 项目:前后端各一人配合,大概需要一周。这个数只是我当时的工期预估,不是对照实验的结果。

飞算 JavaAI 对全栈能力的宣传很明确:Java 后端可以不系统学习前端框架,一个人完成前后端项目;它还给出过“传统约 7 天、AI 模式约 1 天”的效率对比。这些是官方宣传,不是本文验证的内容。我没有用传统方式重做同一项目,也没有记录逐项工时,所以不会拿这些数字替自己的项目背书。

我真正想弄清的是另一件事:首版生成后,如果需求继续加,代码还能不能维护?我又需要从哪里开始补前端知识?

这次我用“训程通|培训运营中心”来回答。它有培训场次、名额扣减、重复报名拦截、取消回补和现场签到;首版是我此前在 IntelliJ IDEA 中借助飞算 JavaAI 插件生成的。系统跑通后,我又做了三轮维护:先把单页拆成多路由,再让 URL 保留名单筛选条件,最后补上账号登录、路由守卫和用户信息预填。本文只复盘这三轮,不讨论从零生成首版的过程。

先说结论:首版可以借助工具完成;一旦需求涉及跨页跳转、可恢复的筛选条件和登录态,我就需要自己理解路由、状态和请求链路。

一、起点基线:一个已经跑通的系统,和它被划定的边界

首先把项目基线和本次环境列出来,后面的代码与测试都以它为准:

项目版本 / 说明
操作系统 macOS 27.0(Build 26A428)
IDE IntelliJ IDEA 2026.2.3
飞算 JavaAI 插件 CalEx-JavaAI: AI Coding Assistant(calex-javaai-jetbrains)3.9.16
后端编译目标 Java 17(pom.xml 的 java.version);实际运行时为 JDK 25.0.2
后端框架 Spring Boot 3.3.4 + Spring Data JPA;H2 内存数据库
后端构建 Apache Maven 3.9.14
前端框架 Vue 3.5.13 + TypeScript 5.7.2 + Vite 6.1.0 + Tailwind CSS 3.4.17 + Vue Router 4.6.4
前端运行时 Node.js 26.3.0 / npm 11.13.0(仓库锁定 pnpm 11.10.0)
浏览器 Chrome 153.0.8010.50
业务场景 培训场次、名额扣减、重复报名拦截、取消回补、名单筛选和现场签到。

图1:IDEA 中的训程通工程与飞算 JavaAI 插件同框,Settings→Plugins 显示 CalEx-JavaAI 3.9.16 已安装启用

1. 首版需求原文:请注意其中划定的边界

我没有一上来就改代码,而是先回看首版到底承诺了什么。下面是我当时在 IDEA 插件对话框输入的完整需求,原文未删改。我把它放在代码块里,方便读者核对;其中“不用做登录、权限、收费、扫码和消息通知”这句话,也是理解第四节的前提:

我想从零做一个给公司培训管理员使用的培训报名后台,项目叫“训程通”。先做一个本地能跑起来的 Java Web 项目,前后端都要有。前端目录为 frontend,后端目录为 backend。不用做登录、权限、收费、扫码和消息通知,把培训场次、报名、取消和签到这条流程做好就行。

场次列表要显示培训名称、时间、地点、总名额、已报名人数和剩余名额,可以从某个场次进入报名表单、报名名单和签到页面。报名时填写姓名、工号、部门和联系电话,邮箱可选。报名成功后生成报名单号和报名时间,名单里能查到这条记录,已报名人数加一,剩余名额减一。同一个工号不能在同一场次重复有效报名,名额用完后不能继续报名。

报名名单要能按场次、姓名或工号搜索,并按报名状态和签到状态筛选。未签到的报名可以取消,取消后保留记录并释放一个名额;重复取消不能多释放名额,已签到的报名不能取消。取消后如果还有名额,可以重新报名,之前的取消记录要保留。已报名人数只计算未取消的报名,剩余名额等于总名额减去已报名人数。

签到时先选场次,再用工号或报名单号找到有效报名,成功后记录签到时间。未报名、已取消和重复签到都要给出明确提示,不能误改其他场次的记录。报名、取消和签到后,相关页面重新查询时都要显示同一份最新结果;名额和报名记录由后端一起更新,不能只改页面上的数字。

所有页面都要使用真实接口,数据写入数据库,不要只做静态页面或前端假数据。先准备两个虚构培训场次,其中一个只有三个名额,报名记录先为空,方便我检查报满、取消和重新报名。页面简洁、好用,有加载、空数据和失败提示,提交失败时保留输入。最后给我前后端代码、数据库初始化方式、接口说明和 README,写清楚怎么配置、启动和测试,以及重启后数据是否保留;实际做过哪些检查,也请如实告诉我。

所以,首版没有账号体系,是我当时划出的边界,不该算成 AI 漏了功能。另一方面,按场次和状态组合查名单的需求已经写在首版里,这也为第三节的维护埋下了伏笔。

图2:在飞算 JavaAI 智能会话中粘贴首版需求原文并发送,插件开始响应,工作区显示“暂无代码变更”

2. 首版能验证的事实与我说不出口的数字

首版跑通后,我能写进复现记录的事实并不多,但都能回到源码或测试里核对:接口规范、前端 TypeScript 类型与后端 Entity/DTO 能对上;后端可以用 mvn spring-boot:run 启动,前端可用 pnpm dev 启动后联调。

证据边界:我没有用传统方式重做同一项目,也没有逐项记录工时。因此,本文不提供“节省多少天”或“提效多少倍”的数据。官方的“7 天 vs 1 天”属于官方口径,不是我的实测结论。

先把这个限制写清楚,后面的判断才不会被“提效数字”带偏。

二、第二轮需求来了:单页 App.vue 为什么会卡住?

如果需求只停在“先跑通一个 Demo”,全栈生成功能能帮我很快搭起首版。不过项目真正开始使用后,维护工作才会慢慢出现。

第二轮需求进来后,我发现首版的页面结构已经不适合继续往里塞功能:场次、名单和签到都挤在一个 App.vue 中。

项目当时还没有 Git 仓库,首版文件也没有单独留档。下面的变更前状态以维护包 CHANGE_REQUEST.md 为准:

  • 我在名单页设好“已确认、未签到”等条件后,刷新页面,条件会丢失;
  • 页面靠组件内变量切换,地址栏始终是 /,我没法把某个筛选后的名单链接交给同事;
  • 日期处理和状态文案散落在模板里,同一字段会出现多套写法。
  • 这类问题已经不是给单个页面补按钮那么简单。如果我还把所有修改都塞回一个大组件,下一轮需求只会更难改。所以,我先把页面边界和状态来源梳理出来,再开始拆分。

    三、多页拆分与 URL 状态持久化:刷新后还能恢复筛选条件

    接下来,我没有急着重写业务接口。首先,我们把页面拆开;然后,我再处理筛选状态在刷新后的恢复。

    1. 第一轮改动:三页独立路由与共享展示函数收敛

    我把原来的单页拆成三个命名路由:

    • /courses:培训场次视图;
    • /enrollments:报名名单视图;
    • /check-in:现场签到视图。

    我在 src/router/index.ts 中配置路由懒加载,再把模板里重复出现的日期和状态文案移到 src/utils/presentation.ts。下面是仓库中的真实代码:

    export function formatDateTime(value?: string): string {
    if (!value) {
    return '—'
    }

    return value.replace('T', ' ').slice(0, 16)
    }

    export function formatDateRange(startTime?: string, endTime?: string): string {
    if (!startTime || !endTime) {
    return '—'
    }

    return formatDateTime(startTime) + ' 至 ' + formatDateTime(endTime)
    }

    export function courseStatusText(status: CourseStatus): string {
    const labels: Record<CourseStatus, string> = {
    ENROLLING: '报名中',
    FULL: '名额已满',
    FINISHED: '已结束'
    }
    return labels[status]
    }

    三个页面通过 component: () => import(…) 按需加载。这样一来,我们改日期格式或状态文案时,只需要改一处,不必在三个页面里反复搜替换。

    2. 第二轮改动:URL Query 与响应式筛选双向绑定

    我以前写后端时,很容易把筛选条件理解为组件里的局部变量。这次维护让我意识到:对于能分享、能刷新恢复的筛选页面,URL 必须是状态来源之一。页面状态和 URL 要能相互转换,不能只靠一次性的内存变量。

    所以,我写了 src/composables/useEnrollmentFilters.ts。它把 route.query 转成 API 查询条件,也能把筛选结果写回地址栏。完整实现中的 filtersFromQuery 会校验 URL 参数并补上默认值;下面只保留转换和写回的关键部分,辅助解析、回读、请求参数和重置逻辑不在这里展开:

    export function useEnrollmentFilters(route: RouteLocationNormalizedLoaded, router: Router) {
    const filters = reactive<EnrollmentFilters>(filtersFromQuery(route.query))

    watch(
    () => route.fullPath,
    () => Object.assign(filters, filtersFromQuery(route.query))
    )

    async function apply(): Promise<void> {
    const query: Record<string, string> = {}
    if (filters.courseId) {
    query.courseId = String(filters.courseId)
    }
    if (filters.keyword.trim()) {
    query.keyword = filters.keyword.trim()
    }
    if (filters.signStatus) {
    query.signStatus = filters.signStatus
    }
    if (filters.status) {
    query.status = filters.status
    }
    if (filters.page !== 1) {
    query.page = String(filters.page)
    }

    // 筛选变化不新增历史记录,因此这里用 replace
    await router.replace({ name: 'enrollments', query })
    }

    return { filters, asRequest, apply, reset }
    }

    这里我最在意的是 watch。它会在地址变化后重新读取筛选条件,因此浏览器前进、后退和刷新时,组件不会继续保留一份过期的内存状态。

    然后,我把跨页面参数也接了起来:

    • 场次卡片点击“名单”:带上 courseId,名单页据此筛选;
    • 名单操作点击“去核销”:把 courseId 和学员 identifier(工号)带到签到页。

    筛选条件与 URL 的转换、跨页参数传递都由结构校验脚本逐条检查,具体见第六节。图 3、图 4 记录的是这次浏览器操作时的页面状态。

    图3:报名名单页按“场次 + 有效报名 + 待核销”组合筛选,地址栏携带 /enrollments?courseId=2&status=CONFIRMED&signStatus=NOT_SIGNED,刷新后筛选值原样恢复

    图4:名单页点击“去核销”后跳转签到页,地址栏透传 courseId=2 与 identifier=EMP2005,学员上下文自动带入

    四、跨出首版需求边界:补齐账号登录、全局路由守卫与用户状态

    回到第一节的需求原文。首版 Prompt 明确写了“不用做登录、权限、收费、扫码和消息通知”。所以首版没有账号认证和权限体系,是我当时划定的范围,不应把它当成生成结果的缺失。

    不过,需求边界会变。如果我要让不同身份的人使用系统,报名需要识别用户,签到不能让任意访客操作,学员也不该每次报名都重复填写个人信息。于是,账号体系从首版的边界外需求,变成了第三轮的改造重点。做到这里,我必须同时理解路由守卫、响应式状态和请求生命周期。

    1. 后端轻量 JWT 鉴权基础设施

    这一轮里,我在后端 xunchengtong/backend 加入了本地 JWT 鉴权:

    • 核心实体 SysUser:覆盖用户名、加盐哈希密码、真实姓名、部门、电话与角色枚举(ADMIN、TRAINER、STUDENT);
    • 密码存储:注册时使用 SHA-256 加随机 Salt(8 字节随机盐)。这是本地演示方案;如果进入生产环境,我会改用 BCrypt、PBKDF2 一类慢哈希;
    • 预置三类演练账号:
      • 管理员:admin / admin123(拥有全量场次与名单管理权限);
      • 讲师:trainer / 123456(负责授课与签到核销);
      • 学员:student / 123456(自主选课报名并自动带出个人身份);另有备用学员 EMP2002 / 123456(林峰),用于多学员场景演示,本文截图即以该账号登录。
    • 我增加了登录签发 Token(POST /api/auth/login)、学员自主注册(POST /api/auth/register)和当前登录人资料查询(GET /api/auth/me)三个接口。

    2. 前端路由守卫(Navigation Guards):未登录受控拦截与重定向回跳

    前端不能只等接口返回 401。我还需要在路由跳转前先判断登录状态,把未登录用户送到登录页。

    接下来,我在 src/router/index.ts 配置全局前置守卫。代码如下:

    // 全局路由守卫:未登录时强制跳转登录页
    router.beforeEach((to, _from, next) => {
    const token = localStorage.getItem('token')
    if (to.path === '/login') {
    if (token) {
    next('/courses')
    } else {
    next()
    }
    } else {
    if (!token) {
    next({
    path: '/login',
    query: { redirect: to.fullPath }
    })
    } else {
    next()
    }
    }
    })

    redirect 参数解决的是一个很实际的场景:有人打开同事分享的签到链接,例如 /check-in?courseId=2&identifier=EMP2005,系统先跳到登录页;登录成功后,再回到原来的地址,参数也还在。这样我不用在登录页另写一套临时状态来保存目标页面。

    图5:IDEA 中的 frontend/src/router/index.ts,根路径重定向与 /login、/courses、/enrollments、/check-in 四条命名路由(懒加载)

    图6:登录页提供“账号登录 / 新学员注册”双入口,下方为四个演示账号快捷卡片,点击即自动填入凭据

    3. 响应式用户状态机:ref 与 LocalStorage 的配合

    我在用户状态这里也绕开了两个常见问题:只存内存变量,刷新后登录态消失;只读 LocalStorage,其他组件又不知道用户已经登录或退出。

    在 src/composables/useAuth.ts 中,我做了统一的状态机(以下节选自仓库真实源码,// 此处省略 register、fetchProfile 与 loading/error 处理):

    // 持久化与响应式用户认证状态
    const token = ref<string | null>(localStorage.getItem('token'))
    const user = ref<User | null>((() => {
    const cached = localStorage.getItem('user')
    if (!cached) return null
    try {
    return JSON.parse(cached) as User
    } catch {
    return null
    }
    })())

    export function useAuth() {
    const isAuthenticated = computed(() => Boolean(token.value && user.value))

    async function login(input: LoginInput) {
    loading.value = true
    error.value = null
    try {
    const result = await authApi.login(input)
    token.value = result.token
    user.value = result.user
    localStorage.setItem('token', result.token)
    localStorage.setItem('user', JSON.stringify(result.user))
    return result.user
    } catch (err) {
    const message = err instanceof Error ? err.message : '登录失败'
    error.value = message
    throw err
    } finally {
    loading.value = false
    }
    }

    async function logout() {
    try {
    await authApi.logout().catch(() => {})
    } finally {
    token.value = null
    user.value = null
    localStorage.removeItem('token')
    localStorage.removeItem('user')
    }
    }

    return {
    token,
    user,
    loading,
    error,
    isAuthenticated,
    login,
    register,
    logout,
    fetchProfile
    }
    }

    这里有两个我会继续沿用的做法。模块顶层的 ref 让各个组件拿到同一份状态,而不是各自读取 LocalStorage;登出时,我先调用后端 /api/auth/logout,再清理本地凭据,后端也能留下这次操作的记录。

    4. 业务联动:表单预填与登出处理

    登录态接通后,我又回到几个具体页面检查。LoginView.vue 里可以填入演示账号,也保留了新学员注册入口;App.vue 会显示当前登录人的名字,点击退出后会清掉本地状态并调用登出接口。学员在 CoursesView.vue 打开报名弹窗时,姓名、工号、部门、电话和邮箱会先带出来。

    这里我没有把预填当作权限校验。表单字段仍然能修改,后端也没有强制报名工号必须等于当前账号。这个实现只服务于本地演示,离真正的账号绑定还有距离。

    图7:登录成功后回到培训场次页,顶部导航显示当前登录人“林峰 / 云平台研发部”与“退出登录”入口

    图8:报名弹窗自动预填当前登录学员的姓名、工号、部门、电话与邮箱(字段仍可编辑,仅为预填而非权限绑定)

    五、演进全景架构图

    三轮迭代后,系统从单页拆成了多路由的本地应用,并加入了基础登录态:

    #mermaid-svg-hFeWw28qwDebYkYF{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-hFeWw28qwDebYkYF .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-hFeWw28qwDebYkYF .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-hFeWw28qwDebYkYF .error-icon{fill:#552222;}#mermaid-svg-hFeWw28qwDebYkYF .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-hFeWw28qwDebYkYF .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-hFeWw28qwDebYkYF .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-hFeWw28qwDebYkYF .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-hFeWw28qwDebYkYF .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-hFeWw28qwDebYkYF .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-hFeWw28qwDebYkYF .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-hFeWw28qwDebYkYF .marker{fill:#333333;stroke:#333333;}#mermaid-svg-hFeWw28qwDebYkYF .marker.cross{stroke:#333333;}#mermaid-svg-hFeWw28qwDebYkYF svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-hFeWw28qwDebYkYF p{margin:0;}#mermaid-svg-hFeWw28qwDebYkYF .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-hFeWw28qwDebYkYF .cluster-label text{fill:#333;}#mermaid-svg-hFeWw28qwDebYkYF .cluster-label span{color:#333;}#mermaid-svg-hFeWw28qwDebYkYF .cluster-label span p{background-color:transparent;}#mermaid-svg-hFeWw28qwDebYkYF .label text,#mermaid-svg-hFeWw28qwDebYkYF span{fill:#333;color:#333;}#mermaid-svg-hFeWw28qwDebYkYF .node rect,#mermaid-svg-hFeWw28qwDebYkYF .node circle,#mermaid-svg-hFeWw28qwDebYkYF .node ellipse,#mermaid-svg-hFeWw28qwDebYkYF .node polygon,#mermaid-svg-hFeWw28qwDebYkYF .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-hFeWw28qwDebYkYF .rough-node .label text,#mermaid-svg-hFeWw28qwDebYkYF .node .label text,#mermaid-svg-hFeWw28qwDebYkYF .image-shape .label,#mermaid-svg-hFeWw28qwDebYkYF .icon-shape .label{text-anchor:middle;}#mermaid-svg-hFeWw28qwDebYkYF .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-hFeWw28qwDebYkYF .rough-node .label,#mermaid-svg-hFeWw28qwDebYkYF .node .label,#mermaid-svg-hFeWw28qwDebYkYF .image-shape .label,#mermaid-svg-hFeWw28qwDebYkYF .icon-shape .label{text-align:center;}#mermaid-svg-hFeWw28qwDebYkYF .node.clickable{cursor:pointer;}#mermaid-svg-hFeWw28qwDebYkYF .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-hFeWw28qwDebYkYF .arrowheadPath{fill:#333333;}#mermaid-svg-hFeWw28qwDebYkYF .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-hFeWw28qwDebYkYF .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-hFeWw28qwDebYkYF .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-hFeWw28qwDebYkYF .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-hFeWw28qwDebYkYF .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-hFeWw28qwDebYkYF .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-hFeWw28qwDebYkYF .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-hFeWw28qwDebYkYF .cluster text{fill:#333;}#mermaid-svg-hFeWw28qwDebYkYF .cluster span{color:#333;}#mermaid-svg-hFeWw28qwDebYkYF div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-hFeWw28qwDebYkYF .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-hFeWw28qwDebYkYF rect.text{fill:none;stroke-width:0;}#mermaid-svg-hFeWw28qwDebYkYF .icon-shape,#mermaid-svg-hFeWw28qwDebYkYF .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-hFeWw28qwDebYkYF .icon-shape p,#mermaid-svg-hFeWw28qwDebYkYF .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-hFeWw28qwDebYkYF .icon-shape .label rect,#mermaid-svg-hFeWw28qwDebYkYF .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-hFeWw28qwDebYkYF .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-hFeWw28qwDebYkYF .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-hFeWw28qwDebYkYF :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}

    登录成功 / 注册成功

    未登录拦截 redirect

    已认证放行

    已认证放行

    已认证放行

    courseId

    courseId + identifier

    URL query

    POST /api/auth/login, register

    GET /api/courses, POST /enrollments

    GET /api/enrollments, POST cancel

    GET /api/enrollments, POST sign

    自动预填姓名/工号/部门/电话

    实时展示姓名与登出

    身份登录 /login

    培训场次 /courses

    全局路由守卫

    报名名单 /enrollments

    现场签到 /check-in

    Spring Boot 后端

    useAuth 用户上下文

    App.vue 顶部导航

    六、我实际跑过的检查

    下面是我这次实际执行的命令和结果。如果你也在同一目录下,可以按顺序复跑:

    1. 结构与契约自动化校验

    运行维护包自带的校验脚本:

    cd xunchengtong-maintenance
    node evidence/verify-article12.mjs

    实测结果:14/14 项检查全部 PASS。检查项包括 4 条命名路由(含 /login)、全局路由守卫、登录与自主注册入口、学员信息预填、跨页字段传递,以及后端认证和业务 Surefire 报告。它检查的是源码结构和已有测试报告,不能替代完整的浏览器端人工回归。

    2. 前端类型检查与构建打包

    cd xunchengtong/frontend
    pnpm build

    实测结果:vue-tsc && vite build 通过,没有类型错误;构建产物包含 LoginView、CoursesView、EnrollmentsView、CheckInView 四个按需加载页面,以及共享的 presentation 函数模块。

    3. 后端自动化集成测试

    cd xunchengtong/backend
    mvn test

    这次 mvn test 一共跑了 20 个用例,结果为 0 Failure、0 Error、0 Skipped。认证相关的 AuthIntegrationTest 有 5 个用例:它覆盖种子账号登录、错误密码的 401、注册时的用户名冲突、无效或缺失 Token,以及读取当前用户资料。

    与跨页面维护直接相关的是 TrainingEnrollmentIntegrationTest,同样有 5 个用例。其中 courseAndEnrollmentFiltersExposeTheDateAndStatusFieldsUsedByThreePages 会检查课程日期和状态字段,也会检查报名记录按场次、关键字、报名状态和签到状态组合查询的结果。其余 10 个用例分属 EnrollmentSignRegressionIntegrationTest 和 SeedDataConsistencyTest,用于回归报名、签到和种子数据。

    七、我看到的优点、问题和改进方向

    我不想把这次经历写成工具优缺点的标准答案,只记录自己碰到的情况。

    1. 优点

    我拿到的前端类型、后端 Entity 和 DTO 能对上,第一轮没有花太多时间查字段不一致。页面和接口已经在那里,我可以直接从生成结果继续改,不用先搭一个空架子。更重要的是,项目的 Spring Boot 分层和 Vue 3 组合式 API 都是我能继续读下去的代码;拆路由、抽展示函数和加登录状态时,我能找到该改的文件。

    2. 不足

    但单页结构很快就卡住了。我要做深层跳转、分享筛选条件和刷新恢复时,只能自己拆路由、决定状态放在 URL 还是组件里。首版 Prompt 也没有写登录和权限,所以系统默认匿名运行。第三轮补鉴权时,前端路由、用户状态和后端接口保护必须一起改,不能只补一个登录页。

    3. 改进建议

    如果以后再做这类项目,我会在设计阶段先确认导航方式:只有一个简单操作面板时可以用单页;名单、详情、核销之间需要跳转时,我会直接选 Vue Router 多路由。鉴权也可以有一个可选骨架,但密码策略、密钥管理和权限模型仍要由项目自己确认。本地演示能跑,不等于能直接进生产环境。

    八、我的结论:Java 后端要学到什么程度

    做完从单页到多页、再到登录态的维护后,我回到开篇的问题:Java 后端还要不要学前端?

    我的答案取决于任务本身。

    如果只是给内部管理面板加表单和列表,我会先让全栈工具把首版搭出来,再自己检查接口、类型和基本运行结果。这个阶段不需要先把前端框架的每个概念学完。

    可是一旦出现跨页面跳转、筛选条件要能复制分享、刷新后还得保留查询结果,我就绕不开 Vue Router。至少要知道编程式导航怎么传参数,route.query 怎样变成组件状态,什么时候该用 push,什么时候该用 replace。

    再往下走到登录态、未登录拦截和表单预填,问题会从一个页面扩展到全局状态和请求过程。这时,我需要继续学 Navigation Guards、Composable 的响应式状态和 Axios 拦截器。否则,代码虽然能生成,我却很难判断 Token 应该在哪里读、退出后哪些状态要清、接口 401 又该怎样处理。

    所以,我现在不会把“会用 AI 生成页面”当作完成交付。对我来说,工具负责加快首版落地,我们负责看懂后续修改会影响什么、在什么地方验证。前端不必一次学完,但路由、状态、请求和认证这些会直接影响维护的部分,Java 后端还是得自己接住。

    #飞算 JavaAI #AI 编程 #Java #全栈开发 #前后端分离 #后端开发 #前端开发 #IDEA 插件 #程序员 #IDEA 开发 #Java 开发 #SpringBoot #程序员必备

    赞(0)
    未经允许不得转载:171主机测评 » 要持续维护AI生成的前端Java后端什么时候该系统学框架
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址