乡村智慧社区服务平台实战:SpringBoot+Vue全栈开发与部署
1. 项目整体设计与思路拆解做乡村智慧社区服务平台核心不是把技术栈堆得多高而是先想清楚在乡村场景里什么功能会被真正用起来。我见过不少同类项目把重点放在“智慧”两个字上——做大屏、做AI识别、做一堆花哨的可视化图表结果村民根本不用村干部也觉得麻烦。这个项目我一开始就把重心放在公告与村务公开、事项预约与办理、事件上报与反馈、乡村积分、农产品展示这几个最贴近日常的模块上。技术选型用SpringBoot搭后端、Vue搭前端、MySQL存业务数据、MinIO存文件和视频、JWT管登录态。这套组合在中小型系统里非常成熟会的人也最多无论你是拿来做毕业设计还是真实部署到村委会后续维护都不至于抓瞎。整个平台按角色分成三条线管理员、村干部、村民。村民只管看公告、上报问题、预约办事村干部负责处理事件、受理预约、反馈结果管理员管账号、菜单、数据字典和系统配置。权限边界清晰之后前后端可以并行开发前端写页面不用等后端接口等得心慌。平台的功能模块划分我列成一张表模块主要功能涉及角色公告村务通知发布、公告轮播、详情展示管理员、村干部、村民事件上报问题拍照上报、进度跟踪、处理反馈村民、村干部办事预约服务事项、预约时间段、预约记录村民、村干部乡村积分积分规则、积分流水、积分排行村民、管理员农产品展示农产品列表、详情、联系农户村民系统管理用户、角色、菜单、数据字典管理员这张表定下来之后后端表结构就好设计了。每个模块可以独立拆成几张简单的表模块之间尽量少做深层join。乡村场景的数据量本身不会特别大但逻辑一定要独立后面加需求才不会牵一发动全身。1.1 技术选型为什么是SpringBoot Vue而不是别的这个问题每次答辩都会被问到索性一次说清楚。SpringBoot能火这么多年核心就在于把Spring生态里那些繁琐的XML配置变成了自动装配和starter依赖。创建新项目引入一个spring-boot-starter-web写一个启动类一个Hello接口就跑起来了。很多人对SpringBoot自动装配原理一知半解其实原理并不复杂SpringBoot通过AutoConfiguration.imports文件加载一堆自动配置类再配合ConditionalOnClass、ConditionalOnMissingBean这些条件注解按需装配Bean。你在配置文件里放一个spring.datasource.url数据源自动就配好了背后就是这条路。前端选Vue也类似。Vue的学习曲线比React平缓模板语法对后端出身的人特别友好。这个项目里要处理路由守卫、页面跳转、表单校验、数据绑定Vue Vue Router Pinia这套全家桶完全够用。而且Vue的组件化很舒服公告列表做一个组件事件上报做一个组件登录注册做一个组件同一套组件改改props就能在好几个页面复用对乡村场景这种页面风格统一的系统再合适不过。一句话总结选型逻辑成熟、文档多、社区大、出问题搜得到、后续招人容易。技术项目最怕用的就是冷门框架写代码的时候很爽后面维护的人想骂娘。1.2 功能模块划分与数据表设计表的划分我采用了“一模块一核心表”的思路。比如公告模块就一张notice表字段有id、title、content、cover_url、publish_time、status、create_by。事件上报就一张event_report表字段有id、user_id、type、description、image_urls、status、feedback、create_time、update_time。办事预约需要两张表appointment_item和appointment_record。农产品展示需要product表和category表。用户相关的是sys_user、sys_role、sys_menu、sys_user_role标准RBAC五件套再加上一张user_info存放村民的详细信息。这里有一个我写了很多项目之后才养成的习惯所有表都要有create_time、update_time、deleted三个字段。create_time和update_time用MyBatis-Plus的MetaObjectHandler自动填充deleted做逻辑删除。线上系统的用户数据、业务流程数据一定要留痕迹尤其是乡村社区这种可能涉及村务公开、事件追责的场景一条投诉记录如果被物理删了后面就说不清了。这个点在答辩时说出来是加分项。用户表的设计我简化成下面的结构字段类型说明idbigint主键usernamevarchar(50)登录名passwordvarchar(100)BCrypt加密后的密码real_namevarchar(50)真实姓名phonevarchar(20)手机号role_idbigint角色IDvillage_idbigint所属村IDstatustinyint0禁用1启用deletedtinyint逻辑删除create_timedatetime创建时间这种表结构非常朴素但胜在稳定、好理解、易扩展。不要一上来就设计复杂的微服务拆分方案单体应用加几张表就能跑得非常舒服先让系统真正落地比过度设计重要得多。2. 后端SpringBoot实现复盘后端部分是整个平台的“大脑”我从环境准备、工程初始化、登录鉴权、MinIO文件上传、接口统一返回格式五个方向来复盘实现过程。每个环节都有我实际踩过或者身边人反复踩的坑写出来给大家省点时间。2.1 环境准备与工程初始化本地开发环境我用的是JDK 8 Maven 3.8 MySQL 5.7没有刻意追高版本。SpringBoot 2.7.x在JDK 8下的兼容性最稳部署到服务器上也省心。这里插一句很多新手一上来装JDK 17甚至JDK 21然后发现SpringBoot老版本启动直接报错跑过去问“SpringBoot版本太高怎么办”其实就是版本匹配问题。SpringBoot 2.x配JDK 8或11都很稳SpringBoot 3.x才开始要求JDK 17。不要盲目追新版本能用、稳定才是第一位的。工程用IDEA自带的Spring Initializr生成Maven项目核心依赖不多dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-validation/artifactId /dependency dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-boot-starter/artifactId version3.5.3/version /dependency dependency groupIdio.jsonwebtoken/groupId artifactIdjjwt/artifactId version0.9.1/version /dependency dependency groupIdio.minio/groupId artifactIdminio/artifactId version8.4.6/version /dependencyMyBatis-Plus帮我省掉了绝大部分Mapper XML的工作单表CRUD直接调用IService和BaseMapper提供的方法需要自定义SQL时再在Mapper接口里写Select注解。这里要说一句MyBatis-Plus用得再顺手基础SQL能力不能丢。这个项目里查询条件动态组装要用LambdaQueryWrapper不懂SQL的话调试起来会非常吃力。工程目录我按controller、service、mapper、entity、dto、vo、common、config、util九个包划分。common放统一返回结果类Result和全局异常处理器util放JwtUtil、MinioUtilconfig放跨域配置、MyBatis-Plus分页配置、MinIO配置。application.yml分成三个application-dev.yml、application-prod.yml、application.yml用spring.profiles.active切换环境。这里我没有上Redis因为整个平台并发量根本到不了需要Redis的程度少一个依赖就少一个可能出问题的点。能用简单方案解决的事情就不要为了技术而技术。IDEA里配置SpringBoot启动项也很简单用Spring Boot的启动配置选好主类Program arguments里加上--spring.profiles.activedev就可以指定启动环境了。第一次跑项目如果报端口冲突在application.yml里改server.port就行不需要去系统层面折腾。2.2 登录鉴权与权限控制登录鉴权选的是JWT方案没有上Spring Security加OAuth2那套因为对乡村社区这种场景来说那套太重了。JWT的思路不复杂用户登录成功后后端生成一个token返回给前端前端保存token之后每次请求在Header里带上后端用一个HandlerInterceptor解析token拿到用户ID和角色决定是否放行。JwtUtil的核心方法就三个生成token、解析token、校验token是否过期。生成的时候把用户ID、用户名、角色塞进claims里过期时间我给了7天。太短了用户要频繁登录太长不安全。关键代码如下public String generateToken(Long userId, String username, String role) { MapString, Object claims new HashMap(); claims.put(userId, userId); claims.put(username, username); claims.put(role, role); return Jwts.builder() .setClaims(claims) .setExpiration(new Date(System.currentTimeMillis() 7 * 24 * 3600 * 1000L)) .signWith(SignatureAlgorithm.HS256, secret) .compact(); }拦截器这里有一个重点前端所有请求都走/api前缀白名单包括登录、注册、首页公告列表等公开接口其余全部拦截校验。校验通过后把userId和role放进request的attribute里后面的Controller直接从request里取不需要再查一次数据库。这个细节能减少一次数据库IO虽然不多但整个系统调优就是靠一个点一个点抠出来的。权限控制我用了一个最简单但有效的方式在Controller方法上加自定义注解RequireRole(admin)拦截器判断当前用户角色是否匹配不匹配直接返回403。这种方式和Spring Security的PreAuthorize比不够优雅但逻辑直观、代码量小答辩时把实现原理讲清楚也很加分。有空可以深入探索Spring Security但项目里用不上就不硬上保持方案精简。2.3 MinIO文件上传与访问乡村智慧社区平台最重的I/O就是图片和短视频。村民上报事件要拍照上传农产品要传图公告要传封面。文件存储我用的MinIO这是一个开源对象存储服务兼容S3协议部署非常简单下载一个可执行文件一条命令就起来了。MinIO在SpringBoot里的集成分三步第一创建配置类读取配置文件的endpoint、accessKey、secretKey、bucketName第二写一个MinioUtil工具类封装上传、删除、生成访问链接的方法第三在上传接口里接收MultipartFile转换成InputStream调用util上传返回文件URL。这里有一个非常容易踩的坑MinIO的endpoint地址。本地开发写的是http://localhost:9000部署到服务器后要改成http://服务器IP:9000如果做了Nginx反向代理文件访问路径也要处理好。我一开始就是没注意这个本地好好的部署到服务器后上传成功但图片加载不出来排查了半天发现前端拿到的URL是localhost浏览器当然打不开。解决方案是把MinIO返回的URL拼成可配置的在application里加一个file.access-url上传成功后用access-url加文件名拼出完整URL返回给前端。还有一个容易被忽略的坑bucket的访问权限。想让图片直接通过URL公开访问需要给bucket设置公共读策略。否则每次都要生成临时的presigned URL虽然更安全但前端显示图片要带上临时参数很麻烦。这个项目用的是公共读策略毕竟村务公告、农产品图片这些内容本来就是要公开的。2.4 接口设计与统一返回格式前后端分离项目统一返回格式是必须的不然前端对接的时候每个人写一个样能把人逼疯。我在common包里定义了一个Result类public class ResultT { private Integer code; private String msg; private T data; public static T ResultT success(T data) { ... } public static T ResultT error(Integer code, String msg) { ... } }成功的时候code是200业务失败code是400或者500未登录是401没有权限是403。前端axios在拦截器里判断code统一处理弹一次提示不会出现一个错误弹三次框的情况。分页接口的返回我自定义了一个PageResult类封装total、pages、records、current字段。虽然可以直接用MyBatis-Plus的Page对象返回但Controller里最好做个转换避免把MyBatis-Plus的底层对象暴露给前端。接口层的数据结构属于对外契约要尽量稳定不要和底层框架绑死。这个意识越早养成越好后面系统大了之后接口版本管理才不会崩。3. 前端Vue实现复盘前端我用的是Vue 3 Vite Pinia Vue Router Axios Element Plus这套组合。如果你很熟悉Vue 2转Vue 3需要一个适应过程但Vue 3的Composition API写起来确实更顺手逻辑复用性也更强新项目直接上Vue 3不要犹豫。3.1 Vue环境搭建与基础配置Node环境装好之后用npm create vitelatest初始化项目交互式命令行里选Vue再选JavaScript。这个平台我选的是JavaScript而不是TypeScript不是TS不好而是考虑到项目体量和上手成本纯JS开发速度更快。Vite比Vue CLI快太多热更新几乎是秒级开发体验提升明显。Vite 4要求Node 14.18以上建议用Node 16以上的稳定版本。工程里我建了views、components、router、store、api、utils、assets这些目录。views放页面组件components放公共组件router放路由配置store放Pinia状态api放所有接口请求封装utils放axios实例和工具函数assets放静态资源。环境配置用.env.development和.env.production两个文件分别配置VITE_API_BASE_URL。开发环境指向http://localhost:8080/api生产环境指向http://服务器IP:8080/api。前端所有请求走相对路径/api开头开发阶段由后端CORS配置处理跨域生产环境用Nginx反向代理转发两边都不折腾。Vue入门的同学容易卡在依赖安装上npm install慢或者报错多半是镜像源问题换一下taobao镜像地址就能解决。另外npm install之后node_modules目录特别大这是正常的不要慌。3.2 路由设计与动态路由权限路由是前端的一个重头戏。平台角色有三类不可能把所有页面都塞给所有人。管理员能看到用户管理、菜单管理村干部能看到事件处理、预约管理村民只能看到公告、上报、预约等功能。这种场景用动态路由最合适。我的做法是前端先定义一套静态路由只有登录页、注册页、首页三条。登录成功后后端根据角色返回该角色能访问的菜单列表和路由路径前端用router.addRoute()动态添加路由。同时把菜单列表存到Pinia里侧边栏渲染用的就是这份动态菜单。这里有个坑Vue Router的动态路由在刷新页面时会丢失。刷新时Pinia里的状态重置了路由守卫里需要重新从后端拉取菜单列表再重新addRoute。我的解决方案是在全局前置守卫router.beforeEach里做判断如果Pinia里有用户信息但没有菜单列表就调用后端接口重新拉取菜单然后next({...to, replace: true})重新进入目标路由。这个判断一定要做好否则会出现刷新后跳404的问题。路由传参也是实操中经常出问题的地方。query方式传参参数拼在URL里刷新页面不会丢params方式如果不用动态路由占位刷新就丢了。事件详情的跳转我用的是query方式router.push({path: /event/detail, query: {id: row.id}})详情页刷新也不丢。3.3 登录态管理与Axios封装Axios封装做了请求拦截器和响应拦截器。请求拦截器统一从localStorage取出token设置到Authorization头。响应拦截器统一处理code401时跳转到登录页其他错误码弹出提示。为了防止重复弹Toast拦截器里用一个状态标记来控制。service.interceptors.response.use( (response) { const res response.data if (res.code 200) { return res } if (res.code 401) { router.push(/login) return Promise.reject(new Error(res.msg)) } ElMessage.error(res.msg || 请求失败) return Promise.reject(new Error(res.msg)) }, (error) { ElMessage.error(网络异常请稍后重试) return Promise.reject(error) } )token存在localStorage里没进cookie也没用pinia持久化插件。token本身由后端控制过期时间前端只需要存起来每次请求带上就行。localStorage有XSS风险但这个项目没有引入用户自定义内容渲染页面风险基本可控。这类平台里如果最终要嵌入到微信生态使用那企业微信和微信公众号的JS-SDK也许还要做一层适配。前端项目集成JS-SDK实际上就是引入wecom/jssdk这个包在入口文件里初始化然后调用register方法。不过这些属于后续扩展的内容需求一旦明确再往项目里加不迟。3.4 核心页面公告列表、事件上报、办事预约公告列表页面是典型的列表加详情组合。列表用Element Plus的el-card展示标题、发布时间、封面图点击进入详情。详情页用el-descriptions展示内容支持富文本。富文本编辑器用的wangEditor正经项目里够用接入也简单。这里有个小细节el-upload上传文件时action属性要设置成后端的真实上传地址同时要带上Authorization头不然会401。直接写actionhttp://localhost:8080/api/file/upload是初学者最常犯的错误axios实例里配了baseURL但el-upload不会自动用必须单独把完整地址和headers写清楚。事件上报页面是最有“乡村场景感”的页面。村民要选事件类型道路损坏、垃圾堆积、公共设施故障等、填描述、上传照片。照片上传用el-upload组件对接后端上传接口支持多选、预览、删除。这里要注意el-upload的组件校验文件大小限制、图片格式限制要在beforeUpload钩子里做否则大图片传上去后端还要处理半天。办事预约页面分两步先选事项再选时间段。事项列表来自后端配置表时间段是固定的几个档位比如上午9:00-11:00、下午14:00-16:00。提交的时候后端要校验这个时间段是否已被预约过用数据库唯一索引加业务校验双重保障。唯一索引的字段是appointment_item_id、appointment_date、time_slot这样同一个事项同一天同一个时间段只能被预约一次。4. 平台核心业务流程串讲如果功能模块只是CRUD就太水了真正体现平台价值的是核心业务流的闭环。我挑三个最有代表性的展开讲。4.1 事件上报到处理反馈的完整闭环先说场景村民张三发现村口垃圾桶旁的路坑坑洼洼打开手机进平台点“事件上报”选“道路损坏”拍一张现场照提交。数据落到event_report表状态为pending。村干部李四登录后台待办列表里看到这条事件点进去看描述和照片点击“受理”状态变为processing系统自动给张三发一条站内信通知“您上报的事件已被受理”。李四处理完之后填写处理结果上传处理后的照片点击“完成”状态变为resolved。张三在“我的上报”里能看到处理反馈和前后对比照片还能对结果打分评价。这个闭环走完村民觉得平台有用才会有下一次使用。事件状态我用了一个Java枚举EventStatus而不是在代码里散落一堆字符串常量。PENDING(待处理)、PROCESSING(处理中)、RESOLVED(已解决)、REJECTED(已驳回)。状态流转写在枚举里方法签名可读性会好很多Swagger展示枚举含义也方便。这种细节在评审时是加分项。4.2 办事预约与提醒机制办事预约的流程是村干部在后台配置可预约事项比如“宅基地审批咨询”“低保申请材料预审”“养老保险认证”每个事项配置可预约的日期范围、时间段和每时段可预约人数。村民在前端选择事项和时间段填备注信息提交预约。系统校验时间段余量扣减余量并生成预约记录状态为已预约。快到时间了系统提前一天给用户发送提醒。提醒功能用了SpringBoot自带的Scheduled定时任务每天早上9点扫一遍第二天的预约记录给用户发站内信。Scheduled用法很简单配置类上加EnableScheduling定时任务方法上标注Scheduled(cron 0 0 9 * * ?)就行。需要注意如果项目部署在多节点定时任务会重复执行需要用分布式锁或者配置只允许主节点执行。这个项目是单节点部署所以暂时没这个问题。4.3 乡村积分体系的设计思路乡村积分是目前智慧乡村项目的常见亮点本质是激励体系。村民参与环保活动、参与村务会议、上报有效事件、垃圾分类正确都能获得积分积分可以在积分商城兑换生活用品或抵扣部分费用。积分实现拆成两部分积分规则表和积分流水表。规则表存放可获得的积分行为比如上报事件加5分、事件被采纳加10分、参与会议加2分。流水表记录每次积分变动明细包括用户ID、变动值、来源、关联业务ID、时间。每次增加积分时先查规则表拿到分值再插入流水表同时更新用户表的总积分字段。这三个操作放在一个事务里保证数据一致性。页面端展示积分排行用SQL的ORDER BY score DESC LIMIT 10查出前10名。排行榜这个功能虽然简单但在乡村场景里能实实在在激发大家参与公共事务的热情。5. 常见问题与排查技巧实录这一节把实际开发中遇到、有共性的问题列出来给后来的人提个醒。每个问题都是调试过的照着排查能省不少时间。5.1 前后端联调的跨域问题前后端分离项目跨域是第一道坎。我在后端配置类里写了一个CorsConfig允许的前端源地址是http://localhost:5173Vite默认端口允许方法包括GET、POST、PUT、DELETE、OPTIONS允许Header包括Authorization、Content-Type。请求头里如果带了自定义header要在allowedHeaders里声明否则预检请求会失败。如果加了CORS配置还是跨域排查两步一是后端allowedOrigins写没写对二是前端有没有走代理。生产环境我其实不建议靠跨域配置更稳妥的是用Nginx把前端静态资源和后端接口挂同一个域名下location /api/ { proxy_pass http://127.0.0.1:8080/api/; }这样前端请求的是同一个源根本没有跨域问题。5.2 文件上传成功后图片不显示的问题这个问题本质是URL拼接错误或访问权限错误。MinIO返回的URL中endpoint地址要配置化通过配置文件控制访问域名。另一个可能的坑是文件名重复导致覆盖自己加UUID前缀加当前时间戳保证唯一性实测这个方案最稳。还有一点很多人在本地测试时用绝对路径“D:/uploads/xxx.png”直接存数据库前端拿到这个路径在浏览器根本打不开。正确做法是数据库只存相对路径或者文件名访问时由后端拼上完整访问地址返回。5.3 播放器无法播放M3U8视频流乡村社区偶尔会有直播或录像回放需求后端输出M3U8流。前端播放器用的video.js播放M3U8格式需要安装videojs-contrib-hls插件。配置不复杂import videojs from video.js import videojs-contrib-hls const player videojs(video, { autoplay: true, controls: true, sources: [{ src: http://xxx/stream/index.m3u8, type: application/x-mpegURL }] })测试时发现有些视频源地址会带鉴权参数或者后端对M3U8请求做了token校验导致播放黑屏。我的建议是如果只是展示录像回放开发阶段用一个公开可访问的存储桶能直接播放最省事如果后续要求安全再考虑给视频地址做临时签名。千万别一上来就上高安全方案联调的时候会痛不欲生。5.4 SpringBoot版本过高带来的依赖兼容问题“SpringBoot版本太高”是很多新手反复问的问题。我最早新建项目时选了SpringBoot 3.0结果发现很多旧版本依赖不支持Jakarta命名空间javax全改jakarta了MyBatis-Plus旧版本也报错。后来老老实实换回SpringBoot 2.7.16配JDK 8依赖从Maven仓库拉下来一点都不折腾。经验就是除非项目有硬性需要比如必须跑在GraalVM或必须用SpringBoot 3的新特性否则用2.x的最终版是最稳妥的选择。Maven依赖版本冲突也一样先用mvn dependency:tree看依赖树搞清楚谁把谁升级了再决定是排除依赖还是调整版本。一定不要用IDEA里“update all dependencies”一键升级升级一时爽排查火葬场。5.5 前端路由参数丢失问题Vue Router传参有query和params两种方式。query拼在URL里刷新参数还在params如果不是动态路由占位刷新就丢。前面已经说过项目里统一用query方式传id。如果你用了params发现刷新后数据没了先查路由配置里有没有定义动态路径段。还有一个容易被忽视的问题是动态路由刷新后404。登录后动态addRoute的路由在刷新时会丢失需要在路由守卫里重新拉取菜单再addRoute然后next({...to, replace: true})。如果不加replace可能会出现重复导航或者白屏。这个坑踩的人非常多建议项目一上来就把动态路由的刷新处理写完整别等上线了再补。6. 部署上线与后续可持续扩展平台开发完总要部署。我的部署方案是后端打成jar包部署在服务器服务器装JDK 8和MySQL前端用Vite构建生成dist目录交给Nginx托管。后端服务用nohup启动nohup java -jar community-platform.jar --spring.profiles.activeprod app.log 21 第一次部署时踩了一个坑服务器MySQL的连接时区没配置后端日志一直报The server time zone value is unrecognized。解决方案是在jdbc连接串上加serverTimezoneAsia/Shanghai。还有创建数据库时一定要指定utf8mb4字符集否则中文存进去会出现乱码。这两点都是部署环节必踩的经典坑提前配好能省很多排查时间。如果团队有Docker环境用Docker部署SpringBoot项目也很方便。写一个Dockerfile基础镜像选eclipse-temurin:8-jre把jar包COPY进去ENTRYPOINT指定启动命令构建镜像后run起来即可。用Docker的好处是环境隔离本地怎么跑服务器就怎么跑不会出现“本地好好的服务器就是不行”的玄学问题。6.1 服务器部署的完整流程具体流程可以归纳为六步第一服务器安装JDK和MySQL初始化数据库导入sql脚本第二后端项目打包mvn clean package -DskipTests生成jar第三把jar上传到服务器指定目录用nohup启动第四前端项目构建npm run build生成dist第五配置Nginxroot指向dist目录location /api/反向代理到后端端口第六访问服务器IP验证页面测试登录、上传、预约等功能。整个流程走完确实有一种“尘埃落定”的感觉。这里建议部署前先检查一遍application-prod.yml里的数据库地址、MinIO endpoint、文件访问域名是否都改成了服务器地址。我在帮朋友排查部署问题时十次有八次是配置没改全或者数据库密码不对真正代码问题反而少见。6.2 面向后续迭代的经验建议这个平台上线后可以扩展的方向我梳理过两个第一接入政务服务接口让村民在平台上直接查看办事进度把平台从村内工具升级成政务服务的延伸触点第二做小程序端现在Web端虽然做了响应式适配但真正在村里推广小程序比浏览器更顺手。技术上后端接口完全复用前端用uni-app套一层壳就行工作量不大。我个人实际操作中的体会是做这类乡村信息化项目不要贪多求全先把一个核心业务闭环做到位让村民和村干部真实用起来再迭代下一个功能。技术永远是工具真正解决实际问题才是平台活下去的根本。把基础打牢了后面来什么需求都不怕。