ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

考研互助交流平台全栈实战:SpringBoot+Vue+MyBatis+MySQL开发指南

考研互助交流平台全栈实战:SpringBoot+Vue+MyBatis+MySQL开发指南 1. 为什么做考研互助交流平台项目背景与需求拆解我去年帮一个培训机构做复习管理系统的时候接触到不少考研学生。他们的需求很集中找资料、找研友、找经验、问问题。市面上的社交平台信息太杂论坛又太老学习群聊两句就被刷屏。说白了缺一个围绕考研这件事本身的垂直互助空间。这个项目就是奔着这个缺口去的——基于SpringBootVue搭建的考研互助交流平台后端用MyBatis操作MySQL前端用Vue做交互。它解决的核心问题有三个让考研用户能发布和获取复习资料、让用户之间能发起和参与互助话题、让管理员能对内容和用户进行有效管控。如果你正打算做类似的校园社团平台、兴趣小组社区、校友互助系统或者纯粹想拿一个SpringBootVue全栈项目来练手、写简历、做课程设计那这个项目的代码结构和设计思路都值得参考。它覆盖了一个完整Web系统从数据库建模到前后端联调的所有关键环节难度控制在认真看能看懂、动手能改出东西的范围比网上那些只丢个登录注册的demo不知道强多少。1.1 核心角色与功能全景整个系统我拆成了三种角色学生用户、管理员、访客。为什么这样拆后面数据库和权限部分会说这里先把功能盘清楚。学生用户能做的事情包括注册登录后修改个人资料维护自己的考研目标院校和专业方向发布求助帖、经验帖支持富文本编辑和图片上传在他人帖子下回复、点赞、收藏上传复习资料文件设置下载权限公开或登录可下载搜索帖子、按分类筛选、按热度排序私信互助也就是站内信功能管理员的功能就是后台管理那一套用户管理禁用、启用、重置密码内容审核帖子审核、评论删除、敏感词过滤数据统计注册趋势、帖子数量、活跃度排行公告管理发布首页公告和系统通知访客也就是未登录用户只能浏览帖子列表和部分公开详情页一旦需要点赞、评论、下载或发帖全部拦截跳转到登录页。1.2 从能跑到好用这个平台的价值定位开发这类系统最怕做成功能堆砌。我设计的时候始终拿三个问题来卡需求用户为什么每天要打开它管理员为什么愿意维护它这套数据沉淀下来有什么用答案就是信息匹配——研友之间的匹配、资料与需求的匹配、问题与经验的匹配。所以系统里特意设计了目标院校和专业方向两个用户属性字段帖子发布时可以关联标签。首页除了常规的最新、最热还加了一个同校推荐板块把跟你目标院校一致的用户和帖子往前排。这个细节不需要多高深的技术但用户粘性一下子就不一样了。懂的人应该能get到这种运营思维反推功能设计的思路比单纯照着需求文档写CRUD重要得多。2. 技术选型SpringBootVueMyBatisMySQL这套组合到底好在哪技术选型这个事很多人上来就纠结哪个框架最牛但实际项目里考虑的是哪个组合成本最低、团队最熟、生态最稳。这个项目定下来用SpringBootVueMyBatisMySQL不是因为它花哨而是因为它是目前Java全栈开发里综合性价比最高的一套没有之一。2.1 SpringBoot做主后端约定大于配置带来的开发效率SpringBoot的好处我不用多讲内嵌Tomcat、自动配置、Starter机制一套下来省掉了传统SSH时代大量的XML配置。做这个项目的时候我SpringBoot用的版本是2.7.x为什么不用3.x因为不少老年人插件和教程还在用2.x如果版本太高集成遇到的坑会多一些热搜里springboot版本太高就是这个意思。3.x虽然性能更好但包名从javax改成了jakarta很多老项目迁移会踩坑。这里给出我的建议课程设计、毕业设计或生产环境求稳的直接SpringBoot 2.7.18JDK用1.8或11依赖兼容性基本不用操心。核心依赖大致长这样parent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version2.7.18/version /parent dependencies !-- Web层 -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency !-- MyBatis整合 -- dependency groupIdorg.mybatis.spring.boot/groupId artifactIdmybatis-spring-boot-starter/artifactId version2.3.1/version /dependency !-- MySQL驱动 -- dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId version8.0.33/version /dependency !-- 密码加密 -- dependency groupIdorg.springframework.security/groupId artifactIdspring-security-crypto/artifactId /dependency !-- JWT -- dependency groupIdio.jsonwebtoken/groupId artifactIdjjwt/artifactId version0.9.1/version /dependency !-- Lombok省代码 -- dependency groupIdorg.projectlombok/groupId artifactIdlombok/artifactId optionaltrue/optional /dependency /dependencies2.2 Vue做前端组件化和工程化让你一个人顶一个小团队Vue的核心价值在于组件化和数据驱动。这个项目里我把页面拆成了首页、帖子列表、帖子详情、个人中心、后台管理五个大模块每个模块内部再拆组件。比如帖子卡片就是一个组件列表页和同校推荐都复用它只是传入的数据不同。Vue 2还是Vue 3我的建议是直接Vue 3 Vite。Vite的开发服务器冷启动速度极快HMR热更新也顺滑对前端调试体验的提升是实打实的。Vue Router用的4.x状态管理用了Pinia——说实话这个项目的状态共享没有复杂到必须上VuexPinia更轻API也更简洁。前端工程的目录结构按下面这样组织frontend/ ├── public/ ├── src/ │ ├── api/ # 接口请求封装 │ │ ├── request.js # axios实例 │ │ ├── user.js │ │ ├── post.js │ │ └── admin.js │ ├── assets/ # 静态资源 │ ├── components/ # 公共组件 │ │ ├── PostCard.vue │ │ ├── Pagination.vue │ │ └── Editor.vue │ ├── router/ # 路由配置 │ │ └── index.js │ ├── stores/ # Pinia状态 │ │ └── user.js │ ├── views/ # 页面级组件 │ │ ├── home/ │ │ ├── post/ │ │ ├── user/ │ │ └── admin/ │ ├── App.vue │ └── main.js └── vite.config.js2.3 MyBatis和MySQL半自动ORM跟关系型数据库的经典搭配选MyBatis而不是JPA最重要的一点是SQL可控。考研社区这种系统查询场景很杂多表关联、动态条件、分页排序、统计报表MyBatis能把所有SQL写在自己手里出了性能问题直接看代码就能定位。JPA虽然开发快但碰到复杂查询生成的SQL往往不是你想要的调优时还得回头改JPQL或原生SQL绕了一圈回来。MySQL这边项目用它来做持久化存储配置了8.0版本连接串需要带时区参数和SSL关闭参数否则容易报mysql ssl连接错误spring: datasource: url: jdbc:mysql://localhost:3306/kaoyan?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/ShanghaiuseSSLfalseallowPublicKeyRetrievaltrue username: root password: your_password driver-class-name: com.mysql.cj.jdbc.DriverallowPublicKeyRetrievaltrue这个参数8.0版本必须加因为新版驱动默认使用RSA公钥加密密码传输不开这个选项首次连接就会报Public Key Retrieval is not allowed。这个坑我见太多人踩了。3. 数据库设计考研社区的帖子、用户、互动关系怎么建模数据库设计决定系统的上限。代码写得再花哨表结构一塌糊涂数据一多就现原形。这个平台我设计了八张核心表这里把最重要的几张表结构和设计思路交代清楚。3.1 用户表与扩展信息表把考研身份单独拆出来用户表用最简单的结构存登录凭证和通用信息CREATE TABLE t_user ( id bigint(20) NOT NULL AUTO_INCREMENT, username varchar(50) NOT NULL COMMENT 登录名, password varchar(100) NOT NULL COMMENT BCrypt加密后的密码, nickname varchar(50) DEFAULT NULL COMMENT 昵称, avatar varchar(255) DEFAULT NULL COMMENT 头像URL, email varchar(100) DEFAULT NULL COMMENT 邮箱, role tinyint(1) NOT NULL DEFAULT 2 COMMENT 角色1管理员 2普通用户, status tinyint(1) NOT NULL DEFAULT 0 COMMENT 状态0正常 1禁用, create_time datetime DEFAULT NULL, update_time datetime DEFAULT NULL, PRIMARY KEY (id), UNIQUE KEY uk_username (username) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;关键是配套的t_user_profile表存考研专属信息目标院校、目标专业、备考年份、已在职或在校、自我介绍。为什么单独拆一张表因为用户的主查询是登录和列表页profile信息只在个人主页和同校推荐时用到拆出来避免主表行宽过大也方便未来扩展其他身份维度——比如以后加导师评价功能再建一张profile扩展表就行不用动主表。3.2 帖子表与回复表一对多关系的两种处理方式帖子表t_postCREATE TABLE t_post ( id bigint(20) NOT NULL AUTO_INCREMENT, user_id bigint(20) NOT NULL COMMENT 发布人, category_id bigint(20) DEFAULT NULL COMMENT 分类ID, title varchar(200) NOT NULL, content text COMMENT 富文本内容, type tinyint(1) NOT NULL DEFAULT 1 COMMENT 类型1求助 2经验, view_count int(11) NOT NULL DEFAULT 0, like_count int(11) NOT NULL DEFAULT 0, comment_count int(11) NOT NULL DEFAULT 0, status tinyint(1) NOT NULL DEFAULT 1 COMMENT 状态1待审 2上线 3下架, is_top tinyint(1) NOT NULL DEFAULT 0 COMMENT 是否置顶, create_time datetime DEFAULT NULL, update_time datetime DEFAULT NULL, PRIMARY KEY (id), KEY idx_category_time (category_id, create_time), KEY idx_user_id (user_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;这里有个设计细节comment_count、like_count这几个冗余字段。很多人开始会纠结为什么不直接count查出来答案是性能。帖子被评论一次就update t_post set comment_count comment_count 1代价极小但如果每次列表页都去count评论表评论一多就是慢查询。这就是典型的读多写少用冗余换性能策略。回复表t_comment是标准的一对多子表外键post_id指向帖子存楼层和回复内容。要注意的是我设计了一个reply_id字段用来支持回复某人的回复实现方式是存被回复评论的id前端展示时就展示xxx回复了xxx。3.3 分类表、标签表与帖子标签关联表垂直社区的灵活筛选机制考研话题需要细分数学、英语、政治、专业课、院校信息、复试调剂、二战心得……我用分类表和标签表做两级筛选。分类表是固定的一级维度数据量少后台管理可以维护。标签表是灵活的词云式二级维度比如高数十八讲刘晓艳调剂经验复试简历这种颗粒度。标签支持用户发帖时自由输入不做严格约束系统自动按频率排序。关联表设计上要避免重复干活很多同类项目会给帖子表直接加tag_ids VARCHAR字段用逗号分隔。这种设计短平快但一旦做点击标签查看所有帖子就尴尬了。我的建议是老老实实建t_post_tag表post_id和tag_id联合主键建索引5万条帖子量级的关联查询完全够用毫秒级返回。3.4 资料文件表文件上传逻辑与权限控制的数据基础这个平台的核心价值之一是资料分享所以t_resource表单独设计CREATE TABLE t_resource ( id bigint(20) NOT NULL AUTO_INCREMENT, user_id bigint(20) NOT NULL, post_id bigint(20) DEFAULT NULL COMMENT 关联帖子可为空, file_name varchar(255) NOT NULL COMMENT 原始文件名, file_url varchar(500) NOT NULL COMMENT 存储路径, file_size bigint(20) DEFAULT NULL COMMENT 字节数, file_type varchar(50) DEFAULT NULL COMMENT 扩展名, download_count int(11) NOT NULL DEFAULT 0, is_public tinyint(1) NOT NULL DEFAULT 1 COMMENT 1公开 2仅登录, create_time datetime DEFAULT NULL, PRIMARY KEY (id), KEY idx_post_id (post_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;文件存哪里开发阶段本地磁盘就好配一个upload目录并映射成虚拟路径。生产环境再考虑OSS或者MinIO。这道题的重点是不要把文件BASE64塞进数据库有些新手喜欢这么干觉得好查但一个5MB的PDF编码后大概能膨胀到7MB一个月下来数据库能膨胀到几十GB备份都成灾难。数据库只存元数据文件走磁盘或对象存储这是开发常识把它写进博文供大家参考。4. 后端实现登录鉴权、动态SQL和文件上传的完整流程后端这套我按一个请求怎么从浏览器走到数据库再走回来的逻辑来讲比按包结构讲解更直观。你吃透这条链路后面看源码时脑海里就会自动建立坐标。4.1 JWT登录与拦截器无状态鉴权怎么落地社区类系统不需要搞Session共享用JWT做无状态登录最合适。流程是这样的用户提交用户名密码controller接收后通过AuthenticationManager校验校验通过后JwtUtil生成tokentoken里放userId、username、role三个字段设置7天有效期登录接口返回token和用户基本信息前端存到Pinia和localStorage后续请求在axios拦截器里自动加Authorization: Bearer 请求头后端配一个拦截器注册时排除登录、注册、首页浏览接口其余全部校验Component public class JwtInterceptor implements HandlerInterceptor { Autowired private JwtUtil jwtUtil; Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 放行OPTIONS预检请求 if (OPTIONS.equalsIgnoreCase(request.getMethod())) { return true; } String token request.getHeader(Authorization); if (token ! null token.startsWith(Bearer )) { token token.substring(7); Long userId jwtUtil.getUserId(token); if (userId ! null) { // 线程变量传递用户信息controller里直接取 UserContext.set(userId); return true; } } response.setStatus(401); return false; } Override public void afterCompletion(...) { UserContext.clear(); } }拦截器最关键的是UserContext用ThreadLocal存当前登录用户id方便service层随时取。但切记afterCompletion里一定要remove否则Tomcat线程池复用时上次的用户信息会被下一个请求读到——这是我早期线上项目里踩过的严重bug用户A的评论混到用户B的接口响应里排查时一脸懵。4.2 MyBatis动态SQL复杂场景的搜索和分页怎么写考研平台最复杂的查询是帖子列表支持按分类、按标签、按关键词标题或内容、按类型、按时间或热度排序、分页。这种条件组合查询用动态SQL再合适不过。Mapper接口定义IPagePostVO selectPostPage(Page? page, Param(query) PostQuery query);XML里的核心片段select idselectPostPage resultTypecom.kaoyan.vo.PostVO SELECT p.id, p.title, p.view_count, p.like_count, p.comment_count, p.create_time, u.nickname, u.avatar, c.name AS category_name, GROUP_CONCAT(t.name SEPARATOR ,) AS tag_names FROM t_post p LEFT JOIN t_user u ON p.user_id u.id LEFT JOIN t_category c ON p.category_id c.id LEFT JOIN t_post_tag pt ON p.id pt.post_id LEFT JOIN t_tag t ON pt.tag_id t.id where p.status 2 if testquery.categoryId ! null AND p.category_id #{query.categoryId} /if if testquery.type ! null AND p.type #{query.type} /if if testquery.keyword ! null and query.keyword ! AND (p.title LIKE CONCAT(%, #{query.keyword}, %) OR p.content LIKE CONCAT(%, #{query.keyword}, %)) /if if testquery.tagId ! null AND EXISTS (SELECT 1 FROM t_post_tag pt2 WHERE pt2.post_id p.id AND pt2.tag_id #{query.tagId}) /if /where choose when testquery.orderBy hot ORDER BY p.view_count p.like_count * 5 p.comment_count * 10 DESC, p.id DESC /when otherwise ORDER BY p.create_time DESC /otherwise /choose /select有个细节要特别提醒LEFT JOIN了标签表如果再 里直接写t.id #{tagId}会把LEFT JOIN变成INNER JOIN的效果因为关联条件被过滤掉了导致该帖子的其他标签记录也被筛掉后最终可能产生重复行。所以这里用EXISTS子查询来处理标签筛选既避免重复又避免JOIN退化。这种细节就是MyBatis面试里常考的条件不生效问题——很多新人碰到筛选字段失效想半天其实是被JOIN和WHERE的语义迷惑了。分页我直接用的MyBatis-Plus的PaginationInnerInterceptor物理分页不会像手写LIMIT那样要拼接字符串而且自动统计总数。这个项目虽然是MyBatis原生XML写法但引入MyBatis-Plus只做分页插件也不冲突。4.3 文件上传与控制怎么处理大文件和下载权限资料文件上传是前端发FormData后端用MultipartFile接收。三个要点第一大小限制。SpringBoot默认单文件最大1MB必须改配置spring: servlet: multipart: max-file-size: 50MB max-request-size: 100MB第二存储策略。文件名用UUID重命名保留原始扩展名作为typeString originalFilename file.getOriginalFilename(); String ext originalFilename.substring(originalFilename.lastIndexOf(.)); String filename UUID.randomUUID().toString().replace(-, ) ext; file.transferTo(new File(uploadDir, filename));文件名一定要重命名否则用户上传两个同名的1.pdf第二次会覆盖第一次的文件而且中文名在部分Nginx和Linux配置下会有乱码问题。第三下载权限。下载接口在controller里先校验登录状态和is_public字段再通过ResponseEntity返回文件流GetMapping(/download/{id}) public ResponseEntityResource download(PathVariable Long id, HttpServletResponse response) { Resource resource resourceService.getDownloadResource(id); return ResponseEntity.ok() .header(HttpHeaders.CONTENT_DISPOSITION, attachment; filename URLEncoder.encode(resource.getFileName(), UTF-8)) .contentType(MediaType.APPLICATION_OCTET_STREAM) .body(resource); }URLEncoder一定要加不然下载的文件名带中文时浏览器会乱码。原来这里也是一个不起眼但坑人的点。4.4 事务与异常多表操作怎么保证一致性发帖时要做两件事插入帖子主记录、插入帖子和标签的关联关系。这两步必须在一个事务里。我在service层方法上加Transactional注解并且设置了rollbackFor Exception.class因为Spring默认只有在throw RuntimeException时才回滚如果你自定义了一个业务异常继承Exception没设置rollbackFor事务就不会生效数据就脏了。Transactional(rollbackFor Exception.class) public void createPost(PostCreateDTO dto) { Post post new Post(); BeanUtils.copyProperties(dto, post); post.setUserId(UserContext.getUserId()); postMapper.insert(post); // 批量插入标签关联 if (dto.getTagIds() ! null dto.getTagIds().size() 0) { postTagMapper.batchInsert(post.getId(), dto.getTagIds()); } }统一异常处理用RestControllerAdvice捕获业务异常返回code500捕获参数校验异常返回code400捕获兜底异常记录日志。别让Spring的默认错误页直接暴露给浏览器那既难看又泄露堆栈信息。5. 前端实现Vue3项目从路由到接口对接的细枝末节前端这套我按路由怎么设计、请求怎么封装、页面怎么渲染三个维度讲。前端代码不比后端少甚至踩的坑更多尤其版本兼容这类问题。5.1 路由设计动态路由与权限控制Vue Router的路由表分两部分。基础路由包括首页、帖子详情、公告列表这部分人人可访问。需要登录的路由包括个人中心、发帖、管理后台通过路由元信息标记requiresAuth。路由守卫里做两件事判断登录态判断角色权限。router.beforeEach((to, from, next) { const userStore useUserStore() if (to.meta.requiresAuth !userStore.token) { next({ path: /login, query: { redirect: to.fullPath } }) return } if (to.meta.requiresAdmin userStore.userInfo?.role ! 1) { next({ path: / }) return } next() })很多同类项目做动态路由是先在数据库存路由表再在登录后递归生成但一个考研互助平台没必要搞这么复杂静态路由元信息拦截足够代码还好维护。如果你因为面试想演示动态路由技能那另说生产环境我建议On Demand新增页面时再改路由表别过度设计。5.2 Axios封装统一携带Token和统一处理错误码axios封装的核心是拦截器。请求拦截器加token响应拦截器统一解包数据并处理错误码const request axios.create({ baseURL: /api, timeout: 15000 }) request.interceptors.request.use(config { const userStore useUserStore() if (userStore.token) { config.headers.Authorization Bearer ${userStore.token} } return config }) request.interceptors.response.use( response { const res response.data if (res.code ! 200) { if (res.code 401) { // token过期跳登录 } ElMessage.error(res.msg || 请求失败) return Promise.reject(new Error(res.msg)) } return res }, error { ElMessage.error(网络异常请检查后端服务) return Promise.reject(error) } )dev环境下baseURL配成/api然后在vite.config.js里配proxy把/api转发到localhost:8080这样开发阶段没有跨域问题。生产环境则把打包后的dist丢到Nginx配一个location /api的代理转发前端代码一行不用改。5.3 页面渲染帖子卡片组件和富文本展示的处理细节帖子卡片PostCard是前端复用度最高的组件包含标题、作者头像、标签、时间、浏览量、点赞数、评论数。这里有个前端渲染的性能点如果列表返回了几百条帖子每条都附带完整content字段的富文本初始渲染会卡。所以列表接口特意做了字段裁剪PostVO只查title、summary、count字段content只有在进入详情页时才加载。summary是发布时从content里截的前100个纯文本字符保存在帖子表里这个字段在列表页简直太重要了能省N多时间。富文本编辑器我用的wangEditor 5中文友好API清晰React和Vue都能接。展示时要注意XSS问题富文本里的script和onclick事件会很危险需要调用编辑器自带的dangerouslyInsertHtml或者自己写一个白名单过滤。这个平台因为是相对封闭的圈子我过滤了script标签、iframe和事件属性写了一个simpleSanitize函数够用就行。首页的同校推荐是纯前端逻辑——用户个人资料里有目标院校字段登录后从帖子列表接口拿到当前用户的目标院校再用一个独立的推荐接口查询同院校的帖子。接口实现不复杂一条SQL按profile.school字段匹配加上排序和limit 10。效果却很好很多用户反馈说每天打开首页先看同校动态。6. 部署与排查从本地联调走到服务器上线这几关必须过代码写完了不等于项目完成。我这部分专门讲部署上线过程中避不开的坑都是实际项目中反复出现的高频问题。6.1 跨域问题与开发代理前后端分离本地联调时跨域是头号问题。解决方案我推荐两个开发环境用vite proxy生产环境用Nginx反向代理。这两种方式的好处是浏览器始终访问同源Token不会因为跨域丢失也不需要后端额外配置CORS。如果你的团队坚持后端开CORS注意allowedHeaders不能是*因为带Authorization请求头时浏览器会先发OPTIONS预检后端必须处理OPTIONS请求并回带响应头。6.2 MyBatis二级缓存这个坑我劝你别踩MyBatis的二级缓存用好了能提升查询性能但这个项目里我关闭了二级缓存。原因很简单跨Mapper的关联查询缓存同步太难保证。举个例子帖子详情接口查了帖子、作者、评论、标签合计四次查询分属三个不同的Mapper namespace。如果只给PostMapper配了二级缓存评论表更新了评论CommentMapper里的缓存flush是清不到PostMapper缓存区里的那个帖子的缓存还会返回旧评论数据。这种缓存数据和数据库不一致的问题排查起来比性能问题痛苦多了。我的建议是学习阶段把二级缓存机制搞明白项目中保持谨慎默认关闭。那些mybatis面试题里问一级缓存和二级缓存区别的小伙伴答案背熟就可以了实战中能正确判断什么时候不开缓存比什么时候开缓存更重要。6.3 MySQL字符集和排序规则create table时如果没指定charset默认继承MySQL实例配置。很多本地Windows装MySQL默认是latin1存中文直接乱码或者???。我建库时统一用CREATE DATABASE kaoyan DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;为什么用utf8mb4而不是utf8MySQL里的utf8是utf8mb3只能存基本多语言平面字符遇到emoji、特殊符号就报错Specified key was too long或存储异常。现在考研交流群里学生动不动就发个表情包当昵称utf8mb4必须安排。排序规则的话我用utf8mb4_general_ci性能略好于unicode_ci对中文支持也够。如果你想按拼音排序或大小写不敏感更严格可以换utf8mb4_unicode_ci看需求。6.4 服务器部署Maven构建、Nginx和进程守护部署流程整理成一套顺手的过程本地mvn clean package打出jar包Windows环境下先装MySQL并完成初始化如果服务器在Windows注意防火墙放行3306端口否则外部连接不上通过scp或宝塔面板把jar包和前端dist传到服务器后端用systemd服务管理守护进程、崩溃自动重启、开机自启都靠它比nohup靠谱得多systemd服务文件示意[Unit] Descriptionkaoyan-backend Afternetwork.target mysql.service [Service] ExecStart/usr/bin/java -jar /opt/kaoyan/app.jar --spring.profiles.activeprod Restartalways RestartSec5 Userkaoyan Groupkaoyan EnvironmentSPRING_DATASOURCE_PASSWORD某个指定密码 [Install] WantedBymulti-user.target这里有个小坑数据库密码写在systemd的Environment里比写在application-prod.yml里安全一点因为yml经常会被别人拷走而systemd服务文件默认只有root可读。我在杀软和运维层面都做了例子具体场景自行评估即可。生产环境MySQL我用的是8.0CentOS上安装的话可以采用官方仓库方式。安装完记得运行mysql_secure_installation做初始安全配置然后单独建一个业务账号不要把root密码直接给Java应用连至少算一个安全底线。前端dist打完包放到Nginx的html目录配置server { listen 80; server_name your_domain; root /opt/kaoyan/dist; index index.html; location / { try_files $uri $uri/ /index.html; } location /api/ { proxy_pass http://127.0.0.1:8080/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }try_files那一行是SPAVue单页应用部署的核心否则刷新页面时Nginx会返回404因为浏览器请求的是一个不存在的磁盘路径。这个配置不写很多人刷新一个二级路由就直接白屏非常典型。6.5 安全加固防外漏的基本功不丢用户密码用BCrypt加密存储不用MD5因为MD5彩虹表一查就破。Spring Security Crypto库提供了BCryptPasswordEncoder这个我记得当时直接拿来用没有任何额外成本。SpringBoot的Actuator在生产环境要关闭或限制访问暴露端点会让别人看到你的健康状况、配置项甚至上下线接口。防止端口信息被扫出来生产环境建议管理员接口单独一个上下文路径管理员登录取判断。再一个就是别忘了给生产环境改默认的管理员账号密码这个项目初始管理员的username和password如果被谁在源码包里看到了上线后不马上改就会炸。7. 源码结构阅读指南与二次开发方向如果你拿到的源码是压缩包或者从github上下载了demo而看不懂从哪下手我按先看哪、再改哪给你一个阅读路径。7.1 代码结构怎么读最有效率后端包结构一般遵循controller-service-mapper-entity分层。我建议阅读顺序先看application.yml了解端口、数据库配置、文件上传路径再看entity实体类理解每个字段的含义对照着看数据库表结构然后看mapper.xml把页面功能和SQL语句对应起来最后看controller理解一个前端请求是怎么进入后端的实体类用Lombok注解会大量简化代码比如Data注解自动生成getter/setterBuilder支持链式调用。不要看到代码没有setter方法就觉得奇怪这是Lombok压掉了样板代码。如果你在IDE中看到红色的编译错误先装Lombok插件并开启annotation processing。前端阅读顺序反过来从router/index.js开始先知道有哪些页面再点进具体页面的api文件看调了哪些后端接口最后看views目录和components目录。7.2 可做二次开发的方向这个平台做成后不算终点有几个方向我实际评估过给想继续拓展的同学参考增加积分体系上传资料获得积分、下载消耗积分、每日签到送积分。对应改t_user加score字段再加一张t_score_log流水表。这是社区运营的常见套路能让资料分享的积极性明显提升接入WebSocket实现在线聊天考研互助群从刷帖子升级成即时沟通。SpringBoot集成WebSocket不难Vue端用原生WebSocket对象就行就是心跳检测和断线重连要写稳考研资料在线预览比如PDF转图片或者用前端组件直接渲染。热点里vue image能显示pdf吗说的就是这类需求实际做法是引入vue-pdf-embed做PDF预览但要注意浏览器兼容和分页加载性能管理员加数据统计图表SpringBoot后台定时汇总数据前端用ECharts画折线图和饼图。考研报名人数趋势、热门院校排行这种东西展示出来会很有说服力7.3 学习型项目的复盘建议不管你是为了课程作业还是面试项目做完之后一定要能回答这几个问题登录token过期怎么处理分页查询的性能瓶颈在哪缓存和数据库一致性怎么做富文本XSS怎么防文件上传为什么不建议存库这些如果张口就来面试官对你的项目评价会直接上一个台阶。我自己带过好几个实习生和徒弟发现一个共性角色权限、帖子发布这些核心功能大家都能写但问到底层为什么就含糊。比如动态SQL的where条件为什么用 不直接用字符串拼接因为 会根据参数动态包含SQL片段字符串拼接会导致SQL注入和语法错误。再比如JWT为什么适合微服务但小程序里要注意token长度因为header携带大token可能超限。这些问题不是刁难是区分会写代码和理解系统的分水岭。8. 实际操作中的体会和最后几条建议整个考研互助交流平台从设计到跑通我前后大概花了一个月左右每天抽几个钟头写。说实话平台本身的CRUD并不复杂真正花时间的地方都在细节事务要不要回滚、缓存要不要开、跨域怎么配、上传目录权限能不能写、Nginx配置刷新页面会不会404这些零散的坑单拎出来每一个都小叠在一起就是小白劝退路。我个人在实际操作中的体会是做全栈项目最困难的不是某一门技术而是上下文切换——早上还在JVM里调接口排错下午就跑到Vue组件里调样式晚上又在bash里debug systemd。所以强烈建议用笔记工具把每种环境的坑记录下来尤其那些报错关键词比如mysql ssl连接错误、springboot版本太高这种下次直接搜自己的笔记就知道怎么处理不用重新踩一遍。最后再分享一个小技巧把前后端项目都放到一个Git仓库里用backend和frontend两个目录区分。这样改后端时提交一次、改前端时提交一次历史记录里能清晰看出每个功能的演变过程。如果项目要写进简历把README写得多一点把架构图、功能清单、启动步骤都写清楚面试官看到的第一印象会好很多。技术栈本身只是工具真正有含金量的是你在这个项目里解决问题过程中形成的判断力。把这个平台做完、吃透每个报错背后的原因你再去写任何类似的SpringBootVue社区类项目都会轻松很多。
返回列表