基于JoyCode的全栈开发探索与实践
#
一、引言软件开发已经进入了新的时代,前后端之间的壁垒逐渐消解。软件开发工程师应该具备更全面的知识体系、更强的岗位、业务适应能力。而AI coding的能力现在愈发强大,使用自然语言的描述,现在的大模型就能给出一份完整的代码;依托公司JoyCode平台的技术支持,我们可进一步探索基于大模型的全栈开发实践路径。
#
二、概要抱着借助JoyCode进行全栈开发实践的想法,本文在“标准化巡检打分”这一需求上进行了从端到端的全链路JoyCode辅助开发的实践。该需求旨在实现以下核心功能:1)支持终端管理人员对站点标准化程度进行线上评分;2)提供问题整改及复审闭环管理;3)允许配置标准化检查项与审核人员权限。
使用JoyCode进行全栈开发的总流程如下,全程依托JoyCode进行代码生成,通过多轮对话迭代优化AI输出结果,并结合必要的人工微调,最终完成了“标准化巡检打分”这一需求的全栈开发。
人工开发 前后端合计 工时:51人日,采用大模型辅助开发,工时:23人日,整体提效28/51 = 54.90%。
人工开发排期甘特图
JoyCode辅助开发排期甘特图
#
三、实施内容本文以“标准化巡检打分”需求的一个接口为例,展示使用JoyCode辅助进行全栈开发的实践流程。
本文所使用的工具为JoyCode模型为Claude4-sonnet
#
1. PRD分析#
prompt输入:0327-NY-【标准化】-标准化巡检打分PRD你是一名全栈架构师,以上是标准化巡检打分的prd,帮我根据这个prd,生成一个关键设计说明。
#
JoyCode输出:关键设计说明
权限体系设计
- 动态权限加载:通过青龙接口实时获取用户角色权限
- 三级权限控制:
- 功能入口级(京ME/站长工作台)
- 页面级(巡检/审核/整改视图)
- 操作级(提交/驳回/配置权限)
- 跨省区拦截:通过组织机构编码校验属地规则
状态机设计
核心模块设计要点
- 巡检打分模块
- 动态表单生成:根据BZHY/BZHE编号动态渲染评分项
- AI集成标记:对标记"是否应用AI"的项增加智能识别入口
- 双校验机制:
- 前端实时计算总分(不超过100分)
- 后端提交二次校验
- 任务配置模块
- 得分计算模块
- 实时得分预览:展示当前巡检得分影响链(站点→片区→省区→集团)
- 虚拟站点处理:未巡检站点得分按省区平均分特殊计算规则
- 双倍扣分警示:对超时未处理项标红显示
消息机制设计
- 分级推送策略:
- 分级推送策略:
需要特别注意的PRD细节:
- 角色权限表需要建立映射字典(ERP角色 ↔ 前端权限标识)
- 时间计算需考虑闰年等边界条件
- 组织机构编码校验需支持正式/虚拟机构双模式
- AI检测结果与人工评分的数据融合策略
#
2. 技术方案设计#
前端设计prompt输入:0327-NY-【标准化】-标准化巡检打分PRD你是一名前端架构师,以上是标准化巡检打分的prd,帮我根据这个prd,生成前端功能流程图。
#
JoyCode输出:根据PRD内容,我为您梳理前端功能流程图如下:
功能流程图(Mermaid语法)
#
后端设计prompt输入:文字生成UML活动图输入: 请协助生成巡检系统的UML活动图; 巡检人员开始巡检 系统获取巡检标准打分项给巡检人 巡检人对打分项打分并提交 系统根据打分结果将不合格项推送至对应站点整改 站点进行整改并提交 系统根据打分项和站点省区随机选取审核人 审核人审核整改并提交生成UML时序图输入: 请协助生成巡检系统的UML时序图; 用户开始巡检,从基础资料获取人员信息和站点信息校验用户是否能够开启巡检 有巡检权限则下发巡检标准打分项 用户根据进行打分,最终提交不合格项给巡检服务 对不合格项进行扣分并推送站点进行整改
#
JoyCode输出:- 巡检系统设计的UML活动图
- 巡检流程的时序图
#
基于JoyCode输出的再加工:查看生成的uml活动图发现,有两点异常
- 巡检人打分时循环评分逻辑异常
- 提示词中未描述清楚,审核人审核后应双倍扣分,不会再推重新整改了
#
prompt输入:请检查此处uml代码逻辑 ,并修改审核人审核不通过的处理方式为双倍扣分repeat:巡检人对当前项打分;repeat while (还有未评分项?) is (否)->是;
#
JoyCode输出:#
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设计稿,右图为大模型实现的样式。
#
基于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输出的代码正确实现了滚动功能:
#
巡检标准的数据表及DAO层构建#
prompt输入:你是一名熟悉京东技术栈的Java高级架构师,请根据的示例字段和示例数据设计巡检标准化配置表,提供mysql建表语句并生成mybatis的mapper和Gateway文件序号 1..至..43一级编号 BZHY001...至...BZHY008一级指标 品牌形象、场内布局、站内作业规范、场地6S、耗材管理、场地安全、监控设备、车辆管理二级编号 BZHE001...至...BZHE0043二级指标 门头VI分值 3打分标准 有带京东Logo的品牌门头,且符合当前品牌标准、无破损和赃污是否应用AI 是/否审核角色 标准化、作业流程、物资支持、安全支持、车辆支持
#
JoyCode输出:MySQL建表语句
- 创建了inspection_standard_config.sql文件
- 包含完整的表结构定义,支持序号、一级/二级编号、指标名称、分值、打分标准、AI应用、审核角色等字段
- 添加了相应的索引优化查询性能
- 包含示例数据插入语句
数据对象DO类
- InspectionStandardConfigDO.java- 完整的数据对象类
- 包含所有字段的getter/setter方法和toString方法
- 使用合适的数据类型(BigDecimal用于分值,Integer用于状态等)
Gateway接口
- InspectionStandardConfigGateway.java- 数据访问网关接口
- 提供完整的CRUD操作方法
- 支持多种查询方式:按ID、序号、编号查询等
- 支持批量操作
MyBatis Mapper
- InspectionStandardConfigMapper.java- MyBatis接口
- InspectionStandardConfigMapper.xml- MyBatis XML映射文件
- 包含完整的SQL映射配置
- 支持动态SQL更新和批量插入
Gateway实现类
- InspectionStandardConfigGatewayImpl.java- Gateway接口实现
- 使用Spring注解进行依赖注入
- 完整实现所有接口方法
技术特点
- 遵循京东技术栈规范和DDD架构模式
- 使用MyBatis进行数据持久化
- 支持分页查询和条件查询
- 包含完整的审计字段(创建时间、更新时间、操作人等)
- 数据库设计考虑了性能优化和扩展性
所有代码文件已按照项目结构正确放置,可直接用于生产环境。