Skip to main content

基于JoyCode的全栈开发探索与实践

一、引言#

软件开发已经进入了新的时代,前后端之间的壁垒逐渐消解。软件开发工程师应该具备更全面的知识体系、更强的岗位、业务适应能力。而AI coding的能力现在愈发强大,使用自然语言的描述,现在的大模型就能给出一份完整的代码;依托公司JoyCode平台的技术支持,我们可进一步探索基于大模型的全栈开发实践路径。

二、概要#

抱着借助JoyCode进行全栈开发实践的想法,本文在“标准化巡检打分”这一需求上进行了从端到端的全链路JoyCode辅助开发的实践。该需求旨在实现以下核心功能:1)支持终端管理人员对站点标准化程度进行线上评分;2)提供问题整改及复审闭环管理;3)允许配置标准化检查项与审核人员权限。

使用JoyCode进行全栈开发的总流程如下,全程依托JoyCode进行代码生成,通过多轮对话迭代优化AI输出结果,并结合必要的人工微调,最终完成了“标准化巡检打分”这一需求的全栈开发。

Markdown Logo 人工开发 前后端合计 工时:51人日,采用大模型辅助开发,工时:23人日,整体提效28/51 = 54.90%。

Markdown Logo 人工开发排期甘特图

Markdown Logo JoyCode辅助开发排期甘特图

三、实施内容#

本文以“标准化巡检打分”需求的一个接口为例,展示使用JoyCode辅助进行全栈开发的实践流程。

本文所使用的工具为JoyCode模型为Claude4-sonnet

1. PRD分析#

prompt输入:#

0327-NY-【标准化】-标准化巡检打分PRD你是一名全栈架构师,以上是标准化巡检打分的prd,帮我根据这个prd,生成一个关键设计说明。

JoyCode输出:#

关键设计说明

  1. 权限体系设计

    • 动态权限加载:通过青龙接口实时获取用户角色权限
    • 三级权限控制:
      • 功能入口级(京ME/站长工作台)
      • 页面级(巡检/审核/整改视图)
      • 操作级(提交/驳回/配置权限)
    • 跨省区拦截:通过组织机构编码校验属地规则
  2. 状态机设计 Markdown Logo

  3. 核心模块设计要点

    1. 巡检打分模块
    • 动态表单生成:根据BZHY/BZHE编号动态渲染评分项
    • AI集成标记:对标记"是否应用AI"的项增加智能识别入口
    • 双校验机制:
      • 前端实时计算总分(不超过100分)
      • 后端提交二次校验
    1. 任务配置模块 Markdown Logo
    2. 得分计算模块
      • 实时得分预览:展示当前巡检得分影响链(站点→片区→省区→集团)
      • 虚拟站点处理:未巡检站点得分按省区平均分特殊计算规则
      • 双倍扣分警示:对超时未处理项标红显示
  4. 消息机制设计

    • 分级推送策略: Markdown Logo
  5. 需要特别注意的PRD细节:

    • 角色权限表需要建立映射字典(ERP角色 ↔ 前端权限标识)
    • 时间计算需考虑闰年等边界条件
    • 组织机构编码校验需支持正式/虚拟机构双模式
    • AI检测结果与人工评分的数据融合策略

2. 技术方案设计#

前端设计prompt输入:#

0327-NY-【标准化】-标准化巡检打分PRD你是一名前端架构师,以上是标准化巡检打分的prd,帮我根据这个prd,生成前端功能流程图。

JoyCode输出:#

根据PRD内容,我为您梳理前端功能流程图如下:

功能流程图(Mermaid语法) Markdown Logo

后端设计prompt输入:文字#

生成UML活动图输入:    请协助生成巡检系统的UML活动图;    巡检人员开始巡检    系统获取巡检标准打分项给巡检人    巡检人对打分项打分并提交    系统根据打分结果将不合格项推送至对应站点整改    站点进行整改并提交    系统根据打分项和站点省区随机选取审核人    审核人审核整改并提交生成UML时序图输入:    请协助生成巡检系统的UML时序图;    用户开始巡检,从基础资料获取人员信息和站点信息校验用户是否能够开启巡检    有巡检权限则下发巡检标准打分项    用户根据进行打分,最终提交不合格项给巡检服务    对不合格项进行扣分并推送站点进行整改

JoyCode输出:#

  1. 巡检系统设计的UML活动图 Markdown Logo
  2. 巡检流程的时序图 Markdown Logo

基于JoyCode输出的再加工:#

查看生成的uml活动图发现,有两点异常

  1. 巡检人打分时循环评分逻辑异常
  2. 提示词中未描述清楚,审核人审核后应双倍扣分,不会再推重新整改了

prompt输入:#

请检查此处uml代码逻辑 ,并修改审核人审核不通过的处理方式为双倍扣分repeat:巡检人对当前项打分;repeat while (还有未评分项?) is (否)->是;

JoyCode输出:#

Markdown Logo

3. 编码#

新增巡检页面生成#

prompt输入:#

UI设计图:使用Relay提供的工具,由设计稿转成的静态vue代码这段代码是静态页面代码,请帮我使用flex布局修改成自适应高宽的、列表中的卡片使用v-for动态生成、其中的class名称帮我修改成语义化的类名,不要用mod,cnt这种不可读的

JoyCode输出:#

<template>  <div class="inspection-container">    <!-- Fixed Category Navigation -->    <div class="category-nav" ref="categoryNav">      <div         v-for="category in categories"         :key="category.id"        class="category-tab"        :class="{ 'active': activeCategory === category.id, 'has-failures': hasFailures(category.id) }"        @click="scrollToCategory(category.id)"      >        <span>{{ category.name }}</span>        <div v-if="hasFailures(category.id)" class="failure-indicator"></div>      </div>    </div>
    <!-- Inspection Content -->    <div class="inspection-content" ref="contentContainer" @scroll="handleScroll">      <div class="instructions">        <p>请勾选不合格项并录入不合格原因</p>        <p class="hint">点击问号会弹出检查标准页面,并且自动定位到对应标准说明位置</p>      </div>
      <!-- All Categories Displayed -->      <div         v-for="category in categories"         :key="'category-'+category.id"         class="inspection-section"        :ref="'category-'+category.id"      >        <div class="category-header">          <h2>{{ category.name }}</h2>          <button class="info-button" @click="showStandards(category.id)">            <img src="//img12.360buyimg.com/ling/jfs/t1/287266/15/1006/1042/680f2af9F0a1295c4/c49a82370a53a843.png" alt="Info">          </button>        </div>
        <div           v-for="item in category.items"           :key="item.id"           class="inspection-item"          :class="{ 'failed': selectedFailures.includes(item.id) }"          @click="toggleFailure(item.id)"        >          <div class="checkbox">            <div class="checkmark" v-if="selectedFailures.includes(item.id)"></div>          </div>          <span class="item-name">{{ item.name }}</span>                    <div class="action-area" v-if="selectedFailures.includes(item.id)">            <span class="reason-text">填写原因</span>            <button class="reason-button" @click.stop="showReasonInput(item.id)">              <img src="//img14.360buyimg.com/ling/jfs/t1/282331/7/25786/288/680e4684Fca58754b/f905097e1453955e.png" alt="Edit">            </button>          </div>        </div>      </div>    </div>
    <!-- Footer with Summary -->    <footer class="inspection-footer">      <div class="failure-summary">        <span class="count">{{ selectedFailures.length }}</span>        <span class="label">项不合格</span>      </div>      <button         class="submit-button"        :disabled="selectedFailures.length === 0"        @click="submitInspection"      >        提交      </button>    </footer>  </div></template>
<script>export default {  data() {    return {      activeCategory: 0,      selectedFailures: [],      scrollTimeout: null,      categories: [        {          id: 0,          name: '品牌形象',          items: [            { id: 'brand-1', name: '门头VI' },            { id: 'brand-2', name: '员工形象' }          ]        },        {          id: 1,          name: '场内布局',          items: [            { id: 'layout-1', name: '场地布局' },            { id: 'layout-2', name: '功能区规范' },            { id: 'layout-3', name: '路区标识' },            { id: 'layout-4', name: '公告栏配置' },            { id: 'layout-5', name: '海报' },            { id: 'layout-6', name: '警示标识' }          ]        },        {          id: 2,          name: '场地6S',          items: [            { id: '6s-1', name: '场所环境-卫生' },            { id: '6s-2', name: '场所环境-定位' },            { id: '6s-3', name: '公告张贴' }          ]        },        {          id: 3,          name: '场地安全',          items: [            { id: 'safety-1', name: '用电规范-充电' },            { id: 'safety-2', name: '用电规范-布线' },            { id: 'safety-3', name: '消防-禁烟' },            { id: 'safety-4', name: '消防-灭火器' }          ]        },        {          id: 4,          name: '监控设备',          items: [            { id: 'monitor-1', name: '监控设备-LCV联网' },            { id: 'monitor-2', name: '监控设备-覆盖' }          ]        },        {          id: 5,          name: '站内作业规范',          items: [            { id: 'work-1', name: '暴力操作' },            { id: 'work-2', name: '定置定位' },            { id: 'work-3', name: '日清操作' },            { id: 'work-4', name: '虚假操作' },            { id: 'work-5', name: '异常处置 | 三无' },            { id: 'work-6', name: '异常处置 | 拦截' },            { id: 'work-7', name: '异常处置 | 破损' },            { id: 'work-8', name: '异常处置 | 错到' },            { id: 'work-9', name: '异常处置区 | 码放' },            { id: 'work-10', name: '异常处置区 | 弃件' },            { id: 'work-11', name: '异常处置区 | 报废' },            { id: 'work-12', name: '异常处置区 | 售后' },            { id: 'work-13', name: '派件滞留区 | 仓管-码放' },            { id: 'work-14', name: '派件滞留区 | 仓管-再投' },            { id: 'work-15', name: '派件滞留区 | 小哥-码放' },            { id: 'work-16', name: '派件滞留区 | 小哥-再投' },            { id: 'work-17', name: '发出件暂存区 | 发货' },            { id: 'work-18', name: '发出件暂存区 | 集包' }          ]        },        {          id: 6,          name: '耗材管理',          items: [            { id: 'supply-1', name: '耗材储备' },            { id: 'supply-2', name: '耗材执行' }          ]        },        {          id: 7,          name: '车辆管理',          items: [            { id: 'vehicle-1', name: '车体形象' },            { id: 'vehicle-2', name: '车辆停放' },            { id: 'vehicle-3', name: '部件检查 | 后视镜' },            { id: 'vehicle-4', name: '部件检查 | 灯光' },            { id: 'vehicle-5', name: '部件检查 | 制动' },            { id: 'vehicle-6', name: '部件检查 | 喇叭' }          ]        }      ]    }  },  mounted() {    this.setupIntersectionObserver()  },  methods: {    setupIntersectionObserver() {      const options = {        root: this.$refs.contentContainer,        rootMargin: '0px',        threshold: 0.5      }
      const observer = new IntersectionObserver((entries) => {        entries.forEach(entry => {          if (entry.isIntersecting) {            const categoryId = parseInt(entry.target.id.split('-')[1])            this.activeCategory = categoryId            this.scrollCategoryIntoView(categoryId)          }        })      }, options)
      this.categories.forEach(category => {        const element = this.$refs[`category-${category.id}`][0]        if (element) {          observer.observe(element)        }      })    },    scrollCategoryIntoView(categoryId) {      const nav = this.$refs.categoryNav      const tabs = nav.querySelectorAll('.category-tab')      const activeTab = tabs[categoryId]            if (activeTab) {        const navWidth = nav.clientWidth        const tabLeft = activeTab.offsetLeft        const tabWidth = activeTab.clientWidth        const scrollLeft = tabLeft - (navWidth - tabWidth) / 2                nav.scrollTo({          left: scrollLeft,          behavior: 'smooth'        })      }    },    scrollToCategory(categoryId) {      const element = this.$refs[`category-${categoryId}`][0]      if (element) {        this.$refs.contentContainer.scrollTo({          top: element.offsetTop - 60, // Adjust for header height          behavior: 'smooth'        })      }    },    handleScroll() {      // Debounce scroll events      clearTimeout(this.scrollTimeout)      this.scrollTimeout = setTimeout(() => {        // The intersection observer will handle active category updates      }, 50)    },    toggleFailure(itemId) {      const index = this.selectedFailures.indexOf(itemId)      if (index === -1) {        this.selectedFailures.push(itemId)      } else {        this.selectedFailures.splice(index, 1)      }    },    hasFailures(categoryId) {      const category = this.categories.find(c => c.id === categoryId)      if (!category) return false      return category.items.some(item => this.selectedFailures.includes(item.id))    },    showReasonInput(itemId) {      // Implement reason input dialog      console.log('Show reason input for:', itemId)    },    showStandards(categoryId) {      // Implement standards display      console.log('Show standards for category:', categoryId)    },    submitInspection() {      // Implement submission logic      console.log('Submitting inspection with failures:', this.selectedFailures)    }  }}</script>
<style scoped>    /* 样式省略 */</style>

UI设计稿和大模型实现样式如下所示,左图为UI设计稿,右图为大模型实现的样式。 Markdown Logo

基于JoyCode输出的再加工:#

生成的代码,未准确实现的滚动联动功能,使用大模型继续完善

再次输入:

将这段代码中的category-nav和inspection-content实现滚动导航功能,参考vant组件<van-tabs v-model="active" scrollspy sticky><van-tab v-for="index in 8" :title="'选项 ' + index">内容 {{ index }}</van-tab></van-tabs>

JoyCode输出的代码正确实现了滚动功能: Markdown Logo

巡检标准的数据表及DAO层构建#

prompt输入:#

你是一名熟悉京东技术栈的Java高级架构师,请根据的示例字段和示例数据设计巡检标准化配置表,提供mysql建表语句并生成mybatis的mapper和Gateway文件序号 1..至..43一级编号 BZHY001...至...BZHY008一级指标 品牌形象、场内布局、站内作业规范、场地6S、耗材管理、场地安全、监控设备、车辆管理二级编号 BZHE001...至...BZHE0043二级指标 门头VI分值 3打分标准 有带京东Logo的品牌门头,且符合当前品牌标准、无破损和赃污是否应用AI 是/否审核角色 标准化、作业流程、物资支持、安全支持、车辆支持

JoyCode输出:#

  1. MySQL建表语句

    • 创建了inspection_standard_config.sql文件
    • 包含完整的表结构定义,支持序号、一级/二级编号、指标名称、分值、打分标准、AI应用、审核角色等字段
    • 添加了相应的索引优化查询性能
    • 包含示例数据插入语句
  2. 数据对象DO类

    • InspectionStandardConfigDO.java- 完整的数据对象类
    • 包含所有字段的getter/setter方法和toString方法
    • 使用合适的数据类型(BigDecimal用于分值,Integer用于状态等)
  3. Gateway接口

    • InspectionStandardConfigGateway.java- 数据访问网关接口
    • 提供完整的CRUD操作方法
    • 支持多种查询方式:按ID、序号、编号查询等
    • 支持批量操作
  4. MyBatis Mapper

    • InspectionStandardConfigMapper.java- MyBatis接口
    • InspectionStandardConfigMapper.xml- MyBatis XML映射文件
    • 包含完整的SQL映射配置
    • 支持动态SQL更新和批量插入
  5. Gateway实现类

    • InspectionStandardConfigGatewayImpl.java- Gateway接口实现
    • 使用Spring注解进行依赖注入
    • 完整实现所有接口方法
  6. 技术特点

    • 遵循京东技术栈规范和DDD架构模式
    • 使用MyBatis进行数据持久化
    • 支持分页查询和条件查询
    • 包含完整的审计字段(创建时间、更新时间、操作人等)
    • 数据库设计考虑了性能优化和扩展性

所有代码文件已按照项目结构正确放置,可直接用于生产环境。