news 2026/10/21 12:30:44

添加操作如何做才可靠?从表单校验到数据库落库的完整实践

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
添加操作如何做才可靠?从表单校验到数据库落库的完整实践

做项目管理系统做到第五个章节,终于要正面处理“添加操作”了。如果你是从前面几章一路看下来的,应该清楚我们已经把项目骨架搭好、列表页也跑通了,而这一章要解决的问题非常具体:怎么把一条新的任务数据,从前端表单一路安全地送进数据库。添加操作在整个系统里看起来只是一个“增”字,但实际动手之后你会发现,它牵扯的细节一点不比列表查询少。

这一章的内容,适合刚接触前后端开发的新手,更适合那些能把代码跑通、但说不清“为什么这样写”的人。我会把添加操作从需求拆解、技术选型、表单校验、后端接口、数据库插入到最终联调的过程完整过一遍,并且把我在这个模块上踩过的坑和排查思路一并写出来。读完你应该能独立实现一个经得起推敲的添加功能,而不只是“能插进去一条数据”。

1. 内容整体设计与思路拆解

1.1 为什么说添加操作是系统的命脉

增删改查四个动作里,很多人觉得“增”最简单,无非是往表里insert一条记录。但真要把添加操作做好,它牵扯的东西比想象中多得多。你可以把添加操作类比成酒店前台办理入住:客户在前台填登记表只是第一步,随后前台要核对证件信息、确认房间状态、分配房卡、登记入住时间,每一步都不能漏。对应到系统里就是:前端表单收集数据、后端做参数校验、业务层处理默认值和唯一性检查、数据库执行插入并返回主键,最后还要给用户一个清晰的成功或失败反馈。

如果没有一个扎实的添加操作,后面的详情查询、编辑、删除、统计报表全都成了无源之水。数据进不来,系统就是空壳。所以我开发模拟项目X的时候,把添加操作单独拉出来作为第五章来精雕细琢,而不是随手写个insert完事。尤其是团队协作场景下,任务数据的质量直接影响后续所有人的工作效率,脏数据一旦进来,后续清洗成本极高。

这一章我会用“前端表单 + 后端接口 + 数据库存储”三个视角来完整拆解添加操作。你不必把自己限定在某个具体技术栈里,核心思路是通用的;代码示例我会用一套常见组合来讲,但思路和排查方法在所有技术栈里都适用。

1.2 本次实现的技术选型与理由

模拟项目X这个模块我用的是前后端分离架构,前端选了Vue 3配合Element Plus组件库,后端用Spring Boot加上MyBatis作为持久层框架,数据库用的MySQL 8。这个组合不是唯一答案,但非常典型,拿来讲解添加操作足够清晰。

前端选Vue 3加Element Plus,主要是因为Element Plus的表单组件开箱即用,校验规则声明式写法非常直观。el-form配上rules规则,你只需要维护一个JSON配置,表单校验逻辑就能覆盖大部分场景。相比手写原生HTML表单,效率高很多,代码可读性也好。

后端选Spring Boot,核心原因是它的分层结构很明确:Controller负责接收请求、Service负责业务逻辑、Mapper负责数据库操作。这种分层在处理“添加操作”时优势明显——每层各自干各自的事,出了问题能快速定位。Bean Validation的注解校验也帮了大忙,在DTO上加几个注解就能完成大部分参数校验,不用在Controller里写一堆if判断。

MySQL作为存储层则是看中它的稳定性和易用性。对绝大多数团队内部系统来说,MySQL的事务能力和约束机制完全够用。再加上使用MyBatis的useGeneratedKeys配置,可以在插入成功后直接拿到自增主键,这一点对后续回显详情或关联操作非常关键。

1.3 我把添加操作拆成了五个关键环节

很多人写添加操作是“一杆子捅到底”:前端拿数据,POST到后端,后端直接insert,完事。这样做在demo里没问题,但放到真实项目里就会埋下各种隐患。我习惯把添加操作拆成五个环节:入口准备、数据收集、格式校验、业务处理、持久化落库。每个环节都设置了明确的边界,后面任一个环节出问题,我们都能第一时间锁定范围。

入口准备指的是用户从哪里进入添加页面或弹出添加弹窗,这一步要解决的是“表单初始状态是什么”。数据收集是用户填写表单并触发表单校验的过程,前端在这个阶段负责拦截明显不合规的输入。格式校验是前后端各做一遍的检查工作,保证数据格式、长度、必填项都符合预期。业务处理是后端Service层的核心,包括唯一性检查、状态字段默认值填充、关联数据补齐等。最后的持久化落库才是真正执行insert语句,把数据写入MySQL。

把这五个环节拆开之后,你会发现添加操作根本不是一个“insert”那么简单。每个环节都有各自的设计要点和容易踩的坑。下面我就从数据模型设计开始,逐个环节把细节讲透。

2. 核心细节解析与实操要点

2.1 数据模型设计:从表结构反推表单字段

添加操作的第一个核心步骤,不是写表单,而是把数据模型设计清楚。我强烈建议你先搞定数据库表结构,再反推前端表单需要哪些字段。模拟项目X这里是一个任务管理模块,我设计的任务表大致是这样的结构:

字段名类型约束说明
idbigint主键,自增任务唯一标识
task_namevarchar(50)非空,唯一索引任务名称
assigneevarchar(20)非空负责人
priorityvarchar(10)非空,默认MEDIUM优先级
statusvarchar(10)非空,默认TODO任务状态
descriptionvarchar(500)可空任务描述
deadlinedate可空截止日期
created_atdatetime非空,默认当前时间创建时间

设计这张表的时候有个很重要的原则:要把“用户填的字段”和“系统生成的字段”分清楚。用户要填的是task_name、assignee、priority、description、deadline这些业务字段;而id是自增主键,created_at是数据库默认当前时间,status有默认值TODO。这样区分的好处是,后端在承接请求时心里有数:哪些字段必须从前端拿,哪些字段我可以自己补默认值,哪些字段根本不接受前端传参。

额外加了一个task_name唯一索引,这是我对“添加操作”的第一次防守。业务上任务名称不应该重复,把它做成数据库约束,等于给后端校验上了最后一道保险。哪怕前端校验被绕过、后端检查漏掉,数据库层也能兜住。很多新手只做应用层校验不做数据库约束,这不对。

2.2 前端表单校验规则设计:体验靠前端,安全靠后端

表单校验是添加操作里最容易做“表面功夫”的地方。我的原则始终是:前端校验负责用户体验,后端校验负责数据安全,两者缺一不可。

前端校验的目的是让用户第一时间发现问题。比如任务名称为空时,鼠标点一下提交按钮,表单下方立刻红字提示“任务名称不能为空”,这个反馈几乎零延迟,用户体验很好。Element Plus的rules规则写起来很简洁,我在模拟项目X里是这样配置的:

const rules = { taskName: [ { required: true, message: '任务名称不能为空', trigger: 'blur' }, { max: 50, message: '任务名称不能超过50个字符', trigger: 'blur' } ], assignee: [ { required: true, message: '负责人不能为空', trigger: 'change' } ], priority: [ { required: true, message: '请选择优先级', trigger: 'change' } ], deadline: [ { required: true, message: '请选择截止日期', trigger: 'change' } ] }

注意trigger的写法。input类的字段适合用blur事件,因为用户输入过程中频繁弹提示会很烦;select和date-picker这类选择组件适合用change事件,因为用户一旦做了选择就应该立刻校验。这个细节看起来不起眼,但直接影响用户填表的手感。

但前端校验只能防君子不能防小人。用户完全可以绕过页面,直接用Postman或脚本向你后端接口发请求。如果后端不校验,一条任务名称为空、字段超长的脏数据就会落库。所以后端必须再做一遍完整校验,而且后端的校验规则必须和前端保持一致,否则就会出现“前端说可以提交、后端报错”的尴尬局面。

2.3 后端接口约定:统一返回结构是添加操作的安全网

前后端联调的时候,最怕各写各的,谁也看不懂谁的返回。从添加操作开始,我就给模拟项目X定了一套统一的接口返回结构:

{ "code": 0, "message": "success", "data": { "id": 1024 } }

code为0表示成功,非0表示各类业务异常;message给用户看,要写得人话一点;data放业务数据,添加操作成功后会返回新记录的自增主键。这套结构一旦定下来,前端封装的请求逻辑就可以统一处理,比如code为0时弹成功提示、刷新列表,code非0时弹message、高亮对应错误,不用每个接口各写一套判断逻辑。

后端用Spring Boot实现时,我会定义一个Result类作为所有接口的统一返回体。再配一个全局异常处理器,把各种业务异常翻译成对应的返回结构。这样Controller层就很干净:只负责接收参数、调Service、返回Result,不需要关心异常怎么处理。添加操作的Controller示例大致是这样:

@RestController @RequestMapping("/api/tasks") public class TaskController { @Resource private TaskService taskService; @PostMapping public Result<Void> add(@Valid @RequestBody TaskAddDTO dto) { taskService.add(dto); return Result.success(); } }

这里@Valid注解会触发DTO字段上的校验规则,比如@NotBlank、@Size、@NotNull这些。如果校验不过,框架会自动抛出异常,再被全局异常处理器捕获并转成统一结构返回。这样的好处是校验逻辑集中在DTO上,Controller和Service里不需要出现大段if判断。

2.4 为什么要用DTO而不要把实体类直接暴露给前端

这个点我想单独拎出来说,因为我在不少项目里见过直接把实体类当成接收参数的用法。实体类对应的是数据库表结构,比如Task类里面有id、createdAt这些字段。如果直接拿Task来接收前端的JSON,用户就可以在请求体里塞一个id或者createdAt,后端如果不注意就会被迫接受这些不该由用户指定的值。

DTO的意思是数据传输对象,专门用来定义“接口这一层长什么样”。添加操作的DTO只需要包含前端允许提交的字段:taskName、assignee、priority、description、deadline。status和createdAt这些字段根本不该出现在DTO里,用户也就无从传参。Service层拿到DTO之后,再把DTO的数据逐个赋给实体对象,同时补充status、createdAt这些内部字段。

生活化一点理解:实体类是公司内部的员工档案,DTO是对外开放的登记表格。你给别人填的永远是登记表,对方不可能直接在你内部档案上乱写。这个习惯养成之后,很多安全隐患在入口处就被挡住了。

3. 实操过程与核心环节实现

3.1 前端表单与提交逻辑的完整实现

模拟项目X的前端添加入口是一个弹窗,从任务列表页点击“新增任务”按钮弹出。弹窗内部是el-form、el-form-item、el-input这些组件的组合。完整的表单区域大概长这样:

<template> <el-form ref="formRef" :model="form" :rules="rules" label-width="100px"> <el-form-item label="任务名称" prop="taskName"> <el-input v-model="form.taskName" placeholder="请输入任务名称" maxlength="50" show-word-limit /> </el-form-item> <el-form-item label="负责人" prop="assignee"> <el-input v-model="form.assignee" placeholder="请输入负责人姓名" /> </el-form-item> <el-form-item label="优先级" prop="priority"> <el-select v-model="form.priority" placeholder="请选择优先级"> <el-option label="高" value="HIGH" /> <el-option label="中" value="MEDIUM" /> <el-option label="低" value="LOW" /> </el-select> </el-form-item> <el-form-item label="截止日期" prop="deadline"> <el-date-picker v-model="form.deadline" type="date" value-format="YYYY-MM-DD" /> </el-form-item> <el-form-item label="任务描述" prop="description"> <el-input type="textarea" v-model="form.description" :rows="3" placeholder="请输入任务描述" /> </el-form-item> <el-form-item> <el-button type="primary" :loading="submitting" @click="handleSubmit"> 保存 </el-button> <el-button @click="handleReset">重置</el-button> </el-form-item> </el-form> </template>

字段的maxlength与数据库的varchar(50)保持一致,前端限制输入长度,后端再校验一次,两边的规则就不会打架。日期选择器加了一个value-format="YYYY-MM-DD"的配置,确保提交给后端的日期是一个字符串而不是Date对象,避免后端的LocalDate解析出时区问题。

提交时的逻辑最重要的一点是:防重复提交。用户手速很快或者网络慢的时候,连点两次保存按钮是常见操作。如果不做处理,后端就会收到两条相同数据的请求,产生两条重复记录。我在点击提交之后立刻把submitting置为true,把按钮变成loading状态,禁用掉重复点击。代码是这样:

const handleSubmit = () => { formRef.value.validate(async (valid) => { if (!valid) { return } submitting.value = true try { const res = await axios.post('/api/tasks', form.value) if (res.data.code === 0) { ElMessage.success('任务添加成功') // 关闭弹窗并通知父组件刷新列表 } else { ElMessage.error(res.data.message) } } catch (e) { ElMessage.error('网络异常,请稍后重试') } finally { submitting.value = false } }) }

这里有一个细节值得注意:提交成功后,submitting在finally里面重置。也就是说无论请求成功还是失败,按钮都会恢复可点击状态。有些新手喜欢只在成功分支里重置loading,万一接口报错,按钮就永远卡在加载状态,用户想重新补填数据都没法操作。

3.2 后端Service层:业务校验与默认值处理

后端Controller只是一个入口,真正干活的是Service层。添加操作在Service里的核心工作可以归纳为三件事:再次做业务校验、填补默认字段、执行数据落库。

业务校验里最常见的是唯一性检查。模拟项目X的任务名称不允许重复,所以我在Service里先查一下同名任务是否存在。注意这一步不是多余的,因为前端根本不知道数据库里现在有哪些任务名称,只能靠后端来判断:

@Service public class TaskService { @Resource private TaskMapper taskMapper; public void add(TaskAddDTO dto) { // 业务校验:任务名称不允许重复 int count = taskMapper.countByTaskName(dto.getTaskName()); if (count > 0) { throw new BusinessException("任务名称已存在,请重新输入"); } // 把DTO转换成实体,补全系统字段 Task task = new Task(); task.setTaskName(dto.getTaskName()); task.setAssignee(dto.getAssignee()); task.setPriority(dto.getPriority()); task.setDescription(dto.getDescription()); task.setDeadline(dto.getDeadline()); task.setStatus("TODO"); // 落库 taskMapper.insert(task); } }

status字段设为TODO是因为新添加的任务默认就是待办状态。业务上这个默认值很重要,你不能让用户在前端自己选“我这条任务是待办还是已完成”,那样数据很快就乱了。默认值放后端Service层统一处理,比前端传参更可靠。

还有一个细节,DTO转实体这一步看起来繁琐,但不能省。有人图省事用BeanUtils.copyProperties直接做属性拷贝,我建议谨慎使用。如果DTO和实体的字段名不完全一致,或者某些字段需要做转换,直接拷贝就会出问题。所以我倾向于显式地set每一个字段,代码长一点但安全可控。

3.3 数据库插入与事务边界的选择

数据库层是添加操作的最后一道关卡。MyBatis的插入配置我特意设置了useGeneratedKeys和keyProperty,目的是在插入成功后把数据库自动生成的自增主键回填到Task对象的id属性上:

<insert id="insert" useGeneratedKeys="true" keyProperty="id"> INSERT INTO task ( task_name, assignee, priority, status, description, deadline, created_at ) VALUES ( #{taskName}, #{assignee}, #{priority}, #{status}, #{description}, #{deadline}, NOW() ) </insert>

created_at字段没有从前端拿,也没有在Service层set,而是直接在SQL里用MySQL的NOW()函数生成。这是最稳妥的方式,时间以数据库服务器为准,不会因为前端和后端所在时区不同而产生偏差。

一个问题经常被问到:单条添加操作需不需要加事务?我给出的答案是:如果只是单条INSERT,不需要额外加@Transactional。因为单条INSERT语句本身是原子的,要么成功要么失败,不会出现半插入的状态。事务是给“多个写操作”准备的,比如同时插入任务表和更新统计表,任何一个失败都要整体回滚。模拟项目X目前的添加操作还没涉及多表写入,所以暂不加事务。但如果你在Service里做了“添加后发消息”“添加后更新某条汇总记录”这类操作,那一定要加@Transactional,否则前面插入成功、后面更新失败时,数据就是脏的。

3.4 联调验证:别急着打开浏览器直接点

完成前后端代码之后,联调这一步最忌讳“直接打开页面就点提交”。我习惯按顺序做三轮验证,每轮都能快速暴露不同层面的问题。

第一轮先用Postman或curl直接调后端接口。构造一条包含所有合法字段的请求,确认返回{code:0}且带回了主键id;再构造几组非法请求,比如taskName为空、assignee为空、taskName超长,确认返回的错误信息和业务预期一致。这一轮能把后端的校验能力和异常处理验证完。

第二轮才打开前端页面,正常填写一条数据,提交后检查列表页是否出现新数据,数据库里对应的记录是否字段完整。重点看两个地方:前端message提示是否友好、提交成功后弹窗是否自动关闭。

第三轮是模拟异常场景。把后端接口临时停掉,前端提交看是否提示“网络异常,请稍后重试”;把数据库连接断开,看错误提示是否会被全局异常处理器捕获,而不是把一堆堆栈信息端到前端页面上。三轮验证全部通过,这个添加操作才算真正可用。

4. 常见问题与排查技巧实录

4.1 前端提示成功,数据库里却没有数据

这个问题我在多个项目里都碰到过,而且排查起来各有各的原因。最常见的场景是:前端axios请求写的是成功回调,后端也返回了200,但数据库里就是找不到新记录。

第一反应先去后端日志里看Service和Mapper有没有被调用。如果日志里完全看不到添加方法的执行记录,那问题多半出在路由上:前端请求的路径是/api/tasks,但后端Controller的映射可能是/tasks而不是/api/tasks,结果请求被404拦截器处理了,前端却因为某种原因没感知到错误。

如果日志显示Mapper.insert执行了但数据库没数据,就要考虑事务回滚。之前说过,单条INSERT不需要事务,但如果代码里加了@Transactional又没注意异常情况,异常被某个catch块吞掉之后事务就会回滚得悄无声息。排查这类问题的方法很直接:把全局异常处理器和Service里的catch不要静默吞异常,至少打到日志里。我见过太多“catch(Exception e){}”空注释代码,这是排查工作最大的敌人。

4.2 重复提交导致数据库出现两条相同数据

防重复提交是一个系统性的问题,不要指望前端一个loading就彻底解决。前端按钮loading只能防手快连点,但网络超时后用户手动刷新重试、后端框架层重试机制,都可能绕过前端限制。

我给的方案是三层防线一起上。第一层是前端按钮loading,体验最好;第二层是后端在添加前做一次唯一性校验,就比如countByTaskName大于0时直接拒绝;第三层是数据库的唯一索引,这是最终防线。三层一起用,哪怕前端校验挂了、后端校验有漏洞,数据库也不会让你插入重复数据。

这里要提醒一点:第二层和第三层的报错信息要区分处理。唯一索引冲突时数据库会报Duplicate entry异常,全局异常处理器要把它翻译成“任务名称已存在”这样的用户友好提示,而不是让用户看到一堆底层的SQLException报错。

4.3 后端插入报错:常见的几种数据库异常

添加操作里,后端500错误最让人头疼,但绝大多数情况都是数据库字段和前端传参不匹配导致的。我做了一张速查表,列一下模拟项目X开发中经常遇到的几种报错和对应的处理方案:

报错现象可能原因排查思路
Data truncation某字段超长,比如taskName超过50字符检查前端maxlength和后端@Size规则是否一致
Column cannot be null必填字段没有给值检查DTO校验注解、Service层是否有漏set字段
Duplicate entry唯一索引冲突替换成业务提示,比如“任务名称已存在”
Incorrect date value日期字符串格式不对检查前端value-format和后端@JsonFormat是否匹配
Unknown column实体字段和表字段映射不一致检查MyBatis的resultMap或SQL列名

这些报错信息乍一看都是英文,新手容易慌。实际上只要在开发环境把数据库日志级别调到DEBUG,就能看到具体执行的SQL和参数,绝大部分问题一眼就定位了。

4.4 我踩过的坑:前后端校验规则不一致

说一个我在实际开发中踩得最深的坑。有一次前端把任务名称字段限制在30个字符以内,后端数据库设计的是varchar(50),后端DTO的校验也写的@Size(max=50)。结果就是:用户在前端填35个字符的时候,前端提示“最多30个字符”,但用户绕过前端直接调接口却能成功插入,数据库里就出现了一条35个字符的任务名称。

这个问题的本质是前端、后端、数据库三套规则没有对齐。从那之后我就给自己定了一条规矩:数据库表结构是唯一事实来源,前端和后端的校验规则必须从表结构推导出来,任何一边要改规则都要改三处。在团队协作里,我会把表结构文档放在技术文档库里,并标注哪些字段是必填、最大长度多少、有哪些枚举值,前后端各拿各的参考,而不是各拍脑袋。

另外还有一个容易忽视的小坑:错误提示要写成人话。后端校验失败时返回的message,最终是要展示在用户面前的。与其返回“taskName不能为空”这种开发语言,不如直接返回“任务名称不能为空”。全局异常处理器里把业务异常信息原样返回,校验异常则按字段提取对应的message返回,这样用户看到的信息才足够友好。

写在最后

添加操作这个模块,我在模拟项目X里前后重写过两次。第一次只是把流程跑通,觉得能插入数据就万事大吉;第二次才把校验分层、统一返回结构、防重复提交、数据库约束这些细节逐个补上。做完之后回头再看,代码量和复杂度并没有增加太多,但稳定性和可维护性完全不在一个档次。

如果非要提炼一点心得,我想说:添加操作本质上是在做一个“入口把关”的工作。数据是系统最重要的资产,入口把不好,后面所有功能都会被脏数据拖累。所以宁可在这里多花一点时间把校验、异常处理、边界情况想清楚,也不要等到线上出现一条垃圾数据之后再来填坑。

另外还有一个可以继续扩展的方向:当系统里有多个模块都需要添加操作时,可以把前面提到的统一返回结构、全局异常处理、字段校验规则这些沉淀成通用的基础能力,甚至抽成公共组件。但我不建议在第一版就直接上抽象,先把一条添加路径做到扎实,多在真实使用中发现问题,再谈复用。第五章节就到这里,下一步可以做编辑和删除,这两个动作和添加操作有不少相似之处,但也有各自不同的坑,到下一章再说。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/21 12:22:51

J1939协议栈C源码解析:CAN多帧报文与TP层传输的实现

简介&#xff1a;一份基于C语言实现的SAE J1939协议栈源码包&#xff0c;面向车载网络与嵌入式CAN总线开发者&#xff0c;旨在帮助快速理解J1939报文结构与通信流程&#xff0c;可用于构建兼容重型车辆及工程机械的通信节点。压缩包共5个文件&#xff0c;包含C源文件、头文件、…

作者头像 李华
网站建设 2026/10/21 12:22:37

Java Swing五子棋实战:GUI绘图、事件监听与AI算法全解

简介&#xff1a;这是一份五子棋游戏开发学习资源包&#xff0c;围绕棋盘、棋子、玩家与AI等模块讲解完整设计思路&#xff0c;解决从零搭建项目时在数据结构、界面交互与AI决策上的难点&#xff0c;适合Java学习者用于课程设计或博弈编程入门。项目从棋盘类、棋子类、玩家类入…

作者头像 李华
网站建设 2026/10/21 12:21:09

C#上位机Modbus TCP实时数据采集与图表绘制全解析

做上位机开发的人应该都碰过这种场景&#xff1a;设备信息从PLC或仪表里读出来了&#xff0c;但要在屏幕上画出实时曲线、再落一份报表&#xff0c;却发现怎么画都不顺。C# Modbus TCP/IP这套组合是工业采集中非常常见的一条技术路线&#xff0c;也是我这些年用得最多的组合之…

作者头像 李华
网站建设 2026/10/21 12:19:06

C# WinForm Panel边框颜色宽度自定义实战与避坑指南

简介&#xff1a;面向C# WinForm开发者的中级教程资源&#xff0c;解决Panel控件边框颜色与宽度无法通过内置属性修改的问题&#xff0c;核心方案是自定义Panel类并重写OnPaint方法&#xff0c;适用于需要精细控制界面UI样式的项目。压缩包内含27个文件&#xff0c;以C#源码&am…

作者头像 李华
网站建设 2026/10/21 12:14:55

宝塔与UFW防火墙不同步:两套规则体系冲突的排查与根治指南

1. 先复现一下那个让人抓狂的“不同步”场景我之前在帮一家小公司整理服务器的时候&#xff0c;遇到过一件挺有意思的事。客户说服务器好像被人扫描了&#xff0c;让我帮忙看看防火墙规则是不是漏了。我打开宝塔面板&#xff0c;防火墙页面显示着“已开启”&#xff0c;列表里也…

作者头像 李华
网站建设 2026/10/21 12:14:52

认识Qt Creator:从环境搭建到入门开发的完整指南

装完Qt环境之后&#xff0c;大多数人电脑上其实多出来了两个东西&#xff1a;一套Qt类库&#xff0c;和一个叫Qt Creator的集成开发环境。我见过很多初学Qt的人&#xff0c;把环境搭建当成一个要“熬过去”的环节&#xff0c;装完就迫不及待地新建工程&#xff0c;然后在第一个…

作者头像 李华