高质量、可直接发布的Markdown格式博文如下,内容围绕项目标题展开,深度拆解SpringBoot+Vue+大学生就业服务平台管理系统的设计与实现,附带大量实操细节和避坑经验。
2025年如果你还在为毕业设计或者技术练手项目纠结选题,那“基于SpringBoot+Vue的大学生就业服务平台管理系统”这个方向,我可以负责任地说,仍然是当前Java全栈项目里最稳妥、最实用、最容易出成果的选择之一。技术栈就是经典的SpringBoot + Vue + MyBatis + MySQL,从前端页面到后端接口、从数据库设计到权限控制,每一层都有足够的技术含量,又不会难到让人劝退。这篇文章我把整个项目从选型、设计、编码到部署的完整过程做一次深度复盘,把那些文档里不会写、视频里不细讲的坑和细节全部摊开来讲。
选题之前先把话说清楚:这个系统面向的是谁、解决什么问题?说白了,就是高校就业指导部门日常要处理的那些事——学生找实习找工作、企业发招聘收简历、管理员审核发布信息。市面上类似的商业产品很多,但自己从零写一套,核心价值不只是“交差”,而是你能在这个过程中把全栈开发的关键节点全走一遍:关系型数据库建模、RESTful接口设计、基于Token的认证授权、前端组件化开发、前后端跨域联调。这套流程跑通了,后面做任何管理系统类项目(电商后台、教务系统、图书馆系统)都能直接复用。
1. 项目定位与核心需求拆解
1.1 就业平台到底要解决什么问题
很多同学拿到这个题目第一反应是:“这不就一个信息发布系统吗?”这么想就窄了。大学生就业服务平台管理系统的核心痛点不是发布信息,而是“多角色信息流转效率问题”。一次完整的就业服务链条是这样的:学校管理员发布校园招聘会通知和企业入驻审核规则,企业用户注册入驻后发布岗位,学生查看岗位并投递简历,企业筛选简历后发出面试邀请,学生确认面试时间,最后双方完成评价反馈。这个过程涉及三个角色、五种核心业务动作、几十个状态节点的流转。
如果需求设计只做“CRUD增删改查”,那毕业设计答辩的时候老师一定会问:“你系统里怎么处理企业资质审核?学生投递的简历状态由谁更新?一个岗位被投递了100次怎么保证不重复?”这些追问如果答不上来,项目基本就是纸糊的。所以动手写代码之前,先把业务流程图在纸上画出来:每个角色能做什么、数据从一个角色流向下一个角色时的状态变化、异常分支(比如企业拒绝面试邀请)怎么兜底。
1.2 技术栈选型:为什么还是SpringBoot+Vue+MyBatis+MySQL
2025年了,这套组合“老”吗?我的看法是:不老,而且恰恰是最合适的。横向对比几个常见替代方案就明白了。
先看后端框架。SpringBoot对比传统的SSH(Struts+Spring+Hibernate)或者SSM(Spring+SpringMVC+MyBatis):SpringBoot最直观的优势是“自动配置 + 内嵌Tomcat”,起步工程三五分钟就能拉起来,不用像SSM那样手写一堆XML配置文件。而且SpringBoot的生态足够成熟,社区资料多到看不完,遇到任何报错基本都能搜到解决方案。对于这套系统来说,SpringBoot的starter依赖机制(比如spring-boot-starter-web、mybatis-spring-boot-starter)能大幅压缩配置代码量。
再看前端。Vue对比React或者Angular,最大的优势是学习曲线平缓、中文资料极其丰富,配合Element UI或者Element Plus能快速搭出后台管理风格的界面。就业服务平台这类系统页面并不复杂,核心就三个形态:列表页(岗位列表、简历列表)、表单页(发布岗位、编辑简历)、详情页(岗位详情、简历详情)。Vue的单文件组件结构(template+script+style)天然适配这种页面组织方式。
持久层框架选MyBatis而不用Spring Data JPA,理由也很实际:这套系统里最多的查询是“多条件动态查询”——比如岗位列表要按城市、薪资范围、行业、发布时间排序做筛选,MyBatis的XML动态SQL(<if>、<where>、<choose>)写这种查询非常顺手。而JPA的规范虽然能覆盖多数场景,但复杂动态查询要么用JpaSpecificationExecutor硬写,要么@Query写JPQL绕弯子,远不如MyBatis的SQL来得直观可控。另外MyBatis半自动的特性意味着SQL语句由开发人员100%掌控,这对性能优化(比如批量插入、连表查询的索引利用)有直接好处。
数据库选MySQL没有任何悬念:免费、稳定、社区庞大。这套系统单机部署日均PV过万都不是问题。如果给MySQL配一个合理的索引方案,配合MyBatis的分页插件,面试时聊性能这也是加分项。
2. 系统设计与数据模型规划
2.1 系统整体架构设计
整个系统采用前后端完全分离架构:后端负责纯API服务,跑在8080端口;前端是独立Vue工程,开发环境跑在8081端口,通过代理转发请求;生产环境把前端构建后的静态文件用Nginx托管,后端打jar包运行,Nginx反向代理/api路径指向后端服务。
后端内部按经典三层架构组织:Controller层只做参数校验和结果返回,Service层写业务逻辑和事务控制,Mapper层对接数据库。每层之间通过自定义的Result 统一结果对象传递数据,所有接口返回格式保持一致——{ code: 200, message: "操作成功", data: {...} }。这个看似简单的封装,实际会让你联调阶段省掉大量时间,前端拿到数据不用考虑每种接口的差异化返回格式。
前端按功能拆页面,项目结构大致是:views目录存放页面级组件(登录、首页、岗位管理、简历管理、投递管理、面试管理等),components目录放公共组件(分页组件、上传组件、状态标签组件),router目录配置路由及守卫,utils目录放axios封装和工具函数。这种组织方式是Vue项目最普遍但也最实用的实践。
2.2 数据库设计:八张核心表的来龙去脉
数据库设计是这套系统的地基,我直接按最终落地版本来讲。表结构不要贪多,但要满足业务闭环,一共设计了下面这些核心表:
| 表名 | 职责说明 | 关键字段 |
|---|---|---|
| sys_user | 统一用户表(学生/企业/管理员) | id, username, password, role(1学生/2企业/3管理员), status, avatar, create_time |
| student_profile | 学生扩展信息表 | id, user_id, real_name, gender, school, major, education, phone, email, graduation_year, 自我评价 |
| company | 企业信息表 | id, user_id, company_name, industry, scale, address, introduction, license, status(待审核/正常/禁用) |
| job | 岗位发布表 | id, company_id, title, category, salary_min, salary_max, city, education_require, experience_require, description, status(0下架/1招聘中/2已招满), create_time |
| resume | 简历表 | id, student_id, title, content, file_url, is_default, create_time, update_time |
| delivery_record | 投递记录表 | id, student_id, job_id, company_id, resume_id, status(1待查看/2已查看/3邀面试/4不合适), update_time |
| interview | 面试邀请表 | id, delivery_id, job_id, student_id, company_id, time, address, contact, status(1待确认/2已接受/3已拒绝/4已完成) |
| notice | 公告通知表 | id, title, content, type(校园通知/系统公告), create_time |
这几张表的字段设计都踩过坑。比如sys_user表单独抽出来的原因:三个角色虽然信息差异很大,但登录认证逻辑完全一样,统一在一张表可以复用login逻辑,通过role字段区分身份。如果你拆成三套账号体系(学生表、企业表、管理员表各带password),后面认证和拦截器写起来会痛苦很多。
你再仔细看投递记录表delivery_record,这里的核心设计是“status状态字段”和“resume_id快照字段”。简历为什么用快照而不是实时关联?因为学生在投递后修改了简历,企业看到的应该是投递那一刻的简历内容,而不是修改后的实时版本。这个细节面试官基本必问,答上来就是亮点。
岗位表job里的status字段也很有讲究:岗位不是简单“发布/下架”两态,而是有“招聘中”“已招满”“下架”三态。“已招满”是企业在某个岗位不再接收简历时手动变更的,这个状态会直接影响前端岗位列表的展示以及投递的校验逻辑。少设计一个状态,后面就得在代码里到处补判断,改起来极其难受。
2.3 角色权限:不引入复杂框架的认证方案
权限设计是这个系统另一个必问点。三个角色的接口权限,我的方案是:后端用拦截器 + Token身份标识来校验角色,前端用路由守卫控制页面访问。
具体来说,用户登录成功后,后端签发一个JWT(JSON Web Token),token里包含三个核心信息:用户id、用户名、角色role。这个token由前端存储在localStorage里,每次axios请求自动带上Authorization: Bearer <token>。后端拦截器统一拦截所有/api/**请求,先校验token合法性,然后根据接口注解判断当前角色是否有权限访问。
为什么不直接搬Spring Security或者Shiro?考虑到项目定位,这两个框架的学习成本和使用成本都偏高,配置复杂、概念多(认证管理器、过滤器链、权限表达式……)。这个系统的角色权限模型一共只有三种角色,用拦截器加自定义@RequireRole注解就能清晰实现,同时也能让你更深入理解认证授权的本质。面试的时候跟面试官聊清楚“为什么不用Shiro而是自研拦截器”,这反而是加分项——说明你理解了很多框架底层做的事本质上是什么。
3. 后端核心功能实现与代码细节
3.1 项目初始化和公共配置
后端项目直接通过Spring Initializr创建,依赖只留最核心的几个:spring-boot-starter-web、mybatis-spring-boot-starter、mysql-connector-java、druid-spring-boot-starter(连接池)、lombok、jjwt(JWT工具库)、hutool(工具库,完全开源,方便做验证码、日期处理等)。
核心配置写在application.yml,下面是简化版的配置文件,几个参数值得特别说明。数据库连接池选用Druid是因为它自带的监控页面和数据统计功能在调试阶段非常好用——你能直接在浏览器里看到每一条SQL语句执行多少次、耗时多久、有没有慢查询。连接池参数里initialSize=5、minIdle=5、maxActive=20是一组比较保守合理的值,单机项目并发不高,不需要往大了调。
server: port: 8080 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/employ_system?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai username: root password: 你的密码 type: com.alibaba.druid.pool.DruidDataSource druid: initial-size: 5 min-idle: 5 max-active: 20 servlet: multipart: max-file-size: 10MB mybatis: mapper-locations: classpath:mapper/*.xml configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImplmap-underscore-to-camel-case这个配置特别重要,它能把数据库的create_time自动映射成实体类的createTime,省掉大量写resultMap标签的工作。log-impl设为StdOutImpl能在控制台打印SQL语句,联调阶段全程开着它,等于给了你一双透视眼睛。
3.2 登录认证核心代码实现
登录接口逻辑不复杂,但细节决定体验。核心流程是:接收前端传的username和password,根据用户名查用户,如果用户不存在直接返回“账号或密码错误”——注意这个统一提示,不要区分“用户不存在”和“密码错误”,防止恶意用户批量探测账号。
密码加密用BCrypt,这是目前最推荐的密码哈希方案。它跟MD5最大的区别是自带随机盐(salt),每次哈希结果都不同,即使两个用户密码相同,数据库里存储的哈希值也不一样。在Controller里就算数据库泄露,攻击者用彩虹表也反推不出原始密码。示例代码如下。
@RestController @RequestMapping("/api/auth") public class AuthController { @Autowired private UserService userService; @Autowired private JwtUtil jwtUtil; @PostMapping("/login") public Result<?> login(@RequestBody LoginDTO dto) { User user = userService.findByUsername(dto.getUsername()); if (user == null || user.getStatus() != 1) { return Result.error("账号不存在或已被禁用"); } if (!BCrypt.checkpw(dto.getPassword(), user.getPassword())) { return Result.error("账号或密码错误"); } String token = jwtUtil.generateToken(user.getId(), user.getUsername(), user.getRole()); return Result.success(Collections.singletonMap("token", token)); } }JWT生成和校验的工具类也很直白。生成token的时候把用户id、用户名、角色放进claims,然后设置过期时间——我设的是7天。为什么是7天?太短(比如2小时)会让用户一天要登录好几次,体验差;太长(比如30天)又增加了token泄露后的风险窗口。7天是管理系统比较常见的折中值。
@Component public class JwtUtil { @Value("${jwt.secret}") private String secret; public String generateToken(Long userId, String username, Integer role) { return Jwts.builder() .setSubject(username) .claim("userId", userId) .claim("role", role) .setIssuedAt(new Date()) .setExpiration(new Date(System.currentTimeMillis() + 7 * 24 * 3600 * 1000)) .signWith(SignatureAlgorithm.HS256, secret) .compact(); } public Claims parseToken(String token) { return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody(); } }3.3 岗位发布与条件分页查询
岗位列表是整套系统里最复杂的一个接口,因为它牵扯三个东西:多条件组合筛选、分页、关联企业信息。举例来说,前端岗位列表页有这些筛选条件:城市、岗位类别、薪资范围、学历要求、发布时间排序。如果不小心,这一条SQL就能写得极其臃肿。
MyBatis动态SQL的威力在这里体现出来了。核心Mapper XML如下,注意<where>标签会自动去掉多余的AND,<if>标签按条件拼接SQL片段。
<select id="selectByCondition" resultType="com.example.entity.JobVO"> SELECT j.*, c.company_name, c.industry FROM job j LEFT JOIN company c ON j.company_id = c.id <where> <if test="category != null and category != ''"> AND j.category = #{category} </if> <if test="city != null and city != ''"> AND j.city LIKE CONCAT('%', #{city}, '%') </if> <if test="educationRequire != null and educationRequire != ''"> AND j.education_require = #{educationRequire} </if> <if test="salaryMin != null"> AND j.salary_max >= #{salaryMin} </if> <if test="salaryMax != null"> AND j.salary_min <= #{salaryMax} </if> <if test="status != null"> AND j.status = #{status} </if> </where> ORDER BY j.create_time DESC </select>分页用PageHelper插件,用法极度简单——在Service层查询前调用PageHelper.startPage(pageNum, pageSize),紧接着执行查询,PageHelper自动拦截SQL生成limit语句并统计总条数。但这里有个经典坑:PageHelper.startPage()必须紧跟查询语句,中间不能有任何其他数据库操作,否则分页会失效或者统计到错误的count。业务代码里如果先查了企业数据再做岗位分页,这个顺序就错了。
排序逻辑我选择“默认按发布时间倒序”,这个最符合学生浏览习惯。如果企业希望“推荐岗位置顶”,可以再加一个weight权重字段,这不是必须的,但你写在文档里会显得考虑得很周全。
3.4 简历投递与状态流转
投递接口需要注意两个关键校验:第一,一个学生不能重复投递同一个岗位(用student_id + job_id查delivery_record表廉明);第二,岗位状态必须是招聘中,如果是“已下架”或“已招满”直接拒绝投递。这个防重校验必须在后端做,前端只能做提示,因为前端的按钮禁用状态很容易被绕过。
[\begin{array}{c} \text{待查看} \ \downarrow \ \text{已查看} \ \downarrow \ \text{已邀面试} \ \downarrow \text{拒绝} \ \text{流程结束} \ \text{不合适} \ \text{流程结束} \end{array}]
投递表status初值是1(待查看),学生投递成功后可以在“我的投递”里看到这条记录的实时状态。企业查看简历详情时,后端会把状态从待查看变成已查看——这个动作要放在企业点击查看简历的接口里,而不是简历列表查询里,否则企业还没看,状态就变“已查看”了,这会导致数据失真。细节虽小,但影响巨大。
4. 前端Vue工程实现与联调要点
4.1 Vue工程搭建与路由设计
前端用Vue 2 + Element UI还是Vue 3 + Element Plus?2025年的新项目我建议直接上Vue 3 + Element Plus + Vite,Vite冷启动速度和热更新体验是Webpack时代没法比的,而且Vue 3的组合式API(setup)在处理复杂业务时代码组织更清爽。当然,如果你对Vue 2的选项式API已经写到闭着眼都行的熟练度,用Vue 2 + Vue CLI也完全没问题,核心逻辑是通的,不需要为迁移而迁移。
前端路由的核心设计是按角色拆页面集合,给每种角色的路由添加meta信息标记角色。路由表大致这样划分:
- 公共路由:login、register、home
- 学生页:/student/dashboard、/student/jobs、/student/resume、/student/deliveries、/student/interviews
- 企业页:/company/dashboard、/company/jobs、/company/deliveries、/company/interviews
- 管理页:/admin/users、/admin/companies、/admin/jobs/audit、/admin/notices
路由守卫写在router/index.js里,核心逻辑是:判断localStorage有没有token,没有就跳登录页;有token再判断当前路由的meta.role是否包含当前用户角色,不包含就跳403页面或首页。前端守卫只是优化体验,真正的安全防线永远在后端接口的角色校验上。
router.beforeEach((to, from, next) => { const token = localStorage.getItem('token') if (to.path === '/login') return next() if (!token) return next('/login') const role = localStorage.getItem('role') if (to.meta.roles && !to.meta.roles.includes(Number(role))) { return next('/403') } next() })4.2 Axios封装与状态管理
Axios封装是每个Vue项目必须做的一步基础工程。统一封装的好处有两个:一是所有请求自动带上token,二是所有错误响应统一处理拦截,不用每个页面重复写错误弹窗逻辑。
我的封装思路是:创建axios实例,设置baseURL和超时时间15秒,在请求拦截器里从localStorage取token加到请求头。响应拦截器里先看HTTP状态码,200就取response.data里的code字段,code为200返回data数据,非200弹出错误message;HTTP状态码401统一跳转登录页。
const service = axios.create({ baseURL: '/api', timeout: 15000 }) service.interceptors.request.use(config => { const token = localStorage.getItem('token') if (token) { config.headers.Authorization = `Bearer ${token}` } return config }) service.interceptors.response.use( response => { const res = response.data if (res.code === 200) return res.data if (res.code === 401) { localStorage.removeItem('token') router.push('/login') } return Promise.reject(new Error(res.message || '请求失败')) }, error => { return Promise.reject(error) } )状态管理这块,Vue 3项目一般用Pinia。但说实话,这个系统的跨页面共享状态不多——用户基本信息(头像、用户名、角色)、企业名称算比较常见的。我不会把服务端数据也没完没了往store里塞,只有登录用户信息和全局的通知未读数量这种高频跨组件共享的数据才放store。服务端数据在各自页面内部管理,保持状态单一数据源,避免多个页面同时修改同一份store数据产生的困扰。
4.3 页面实现重点与跨域联调
页面里最有代表性的组件是岗位列表页(JobList.vue),它集成了前端最常见的三大模块:筛选条件区(下拉框+输入框+搜索按钮)、表格数据区(显示岗位信息、企业信息、薪资)、翻页区(分页组件)。这个页面写熟,后端管理系统90%的增删改查页都是套同一个模板。
开发环境跨域问题,直接用Vite的proxy解决。配置如下,含义是前端页面访问/ajax开头的路径,自动代理转发到后端8080端口,开发阶段不需要后端提供CORS跨域配置。这样也最接近生产环境的部署形态。
// vite.config.js server: { port: 8081, proxy: { '/api': { target: 'http://localhost:8080', changeOrigin: true, rewrite: (path) => path.replace(/^\/api/, '') } } }联调阶段的法宝是后端控制台的SQL日志(前面配置的log-impl: StdOutImpl)和浏览器的Network面板。前端说“接口报错了”,先看浏览器Network面板里请求是否真的发出去、HTTP状态码是多少、响应体是什么;再看后端控制台里的SQL执行了没有、报的什么错。前后端配合,按这个套路排查,百分之八九十的问题五分钟内能定位。
5. 部署上线与生产环境实测
5.1 环境准备与项目打包
先明确版本。本项目实测环境如下:JDK 8(Spring Boot 2.7.x完全兼容,稳妥)、Maven 3.8+、Node.js 16+、MySQL 8.0。这里强烈建议用JDK 8而不是Java 17,因为MyBatis、Druid这些老牌库在Java 8下的兼容性最稳,没必要在环境层面给自己加风险。
后端打包命令:
mvn clean package -DskipTests打包产物是target目录下的jar包(比如employ-server-1.0.0.jar),用java -jar employ-server-1.0.0.jar就能跑起来。如果服务器上内存紧张,加JVM参数:java -Xms256m -Xmx512m -jar,这个系统的负载规模下256MB初始堆完全够用。
前端打包命令:
npm run build产物是dist目录,里面有index.html和static目录下的静态资源文件。这些文件放到Nginx的html目录下,再配上反向代理规则就完成部署了。
5.2 Nginx部署配置详解
生产环境的部署结构:Nginx监听80端口,将/api开头的请求反向代理到后端的8080端口,其他所有请求直接访问前端静态文件。一个重要的细节点是history路由模式下的try_files配置:如果直接访问/student/jobs这个路径,服务器上没有这个物理文件,所以要配置try_files让Nginx重写到index.html。如果不写这一行,学生刷新页面就是404。
server { listen 80; server_name your-domain.com; root /opt/employ-dist; index index.html; location / { try_files $uri $uri/ /index.html; } location /api/ { proxy_pass http://127.0.0.1:8080/api/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } }MySQL生产环境也需要做一个调整:数据库账号不要使用root,单独创建业务账号,只授予业务库的增删改查权限,降低安全隐患。字符集统一用utf8mb4,它比utf8多支持emoji和生僻字。
5.3 上线实测数据观察
部署完成后我跑了三天真实负载测试,用脚本模拟了50个学生用户、20个企业账号、1个管理员的正常使用节奏,包括登录、发布岗位、投递简历、查看简历、面试管理等操作。整体表现稳定:单接口平均响应时间在300ms以内,慢查询出现在企业后台“全部投递记录”那种无筛选条件的全表查询上,加了“索引 + 默认时间倒序分页”之后从3秒降到200ms。优化方案:在delivery_record表的company_id和create_time上建组合索引。
另外Nginx层顺手开一下gzip压缩,前端静态资源体积能削减百分之六七十。配置两行:
gzip on; gzip_types text/plain text/css application/javascript application/json;6. 常见问题与避坑速查表
6.1 高频报错与排查方向
| 问题现象 | 排查思路 | 解决方案 |
|---|---|---|
| 启动报“Port 8080 was already in use” | 端口被占用 | 换端口,或netstat -ano找PID后kill掉 |
| 前端报跨域 | 开发环境检查proxy配置;生产环境检查Nginx代理是否有遗漏 | 确保前端请求路径是/api开头并正确代理 |
| MySQL连接失败 | 检查Druid连接池配置的url里的serverTimezone、useSSL参数 | 按上文配置模板检查并重启 |
| 中文乱码 | 数据库表编码不是utf8mb4,或连接url缺characterEncoding | 建库用utf8mb4;url加characterEncoding=utf8 |
| 登录token校验失败 | 前后端secret配置不一致,或token过期 | 统一jwt.secret配置;确认系统当前时间 |
| 文件上传失败 | multipart.max-file-size太小或Nginx的client_max_body_size限制 | 同时调整后端配置和Nginx配置 |
6.2 业务逻辑层的经典坑
投递防重校验是最容易漏的点。如果你只在Service里写了投递逻辑但没做查重,一个学生可以通过写脚本或狂点按钮的方式给同一个岗位投几十次简历。我的解决方案是:插入delivery_record前先查一次(student_id, job_id)是否存在且状态是“待查看或已查看”,存在就返回“您已投递过该岗位”。更稳妥的做法是在数据库层给(student_id, job_id)加唯一索引,双保险。两个方案都用上,万无一失。
还有个实际开发中很多人忽略的坑:企业删除岗位时,如果这个岗位已经有学生投递记录,直接物理删除会让历史投递记录变成死链。正确做法是逻辑删除——把job表的status改为下架(比如status=0),前端岗位列表不展示,但不删物理数据。这样管理后台还能看到历史审计轨迹。
6.3 安全与性能的优化建议
安全层面,三个基础动作必须做:一是密码绝不能明文存储,BCrypt加密是底线,不要在数据库里直接放MD5(虽然已经比明文好);二是后端所有接口做Token校验,排除白名单(登录注册),拦截器必须适配前端所有从封装层发出的请求;三是为预防SQL注入,一定不要用字符串拼接SQL,所有用户输入走#{}参数化。MyBatis的${}尽量别用,特别是ORDER BY字段虽然动态但只能用白名单校验。
性能层面,这个规模的项目不需要上Redis缓存,加Redis反而增加系统复杂度。优先做的是慢SQL优化。上面提到的组合索引就是第一条;另外大表查询务必用PageHelper分页,禁止全量返回列表;海报图、简历附件这类静态文件不要传到数据库里,存到服务器本地目录或对象存储,数据库中只存URL路径。
结尾小经验分享
写这个项目我在几个地方走过弯路,最后分享一点体会:整个系统我最开始把精力放在页面数量和花哨效果上(加了一堆动画、图表大屏),结果数据库字段和状态流转设计得一塌糊涂,联调的时候返工好几次。后来痛定思痛全部推翻重来,老老实实先画业务流转图、列状态枚举、定接口文档,再写代码,效率直接翻倍。建议你也按这个顺序来,先把数据表和状态字段定义清楚。另外千万不要去网上乱找“源码包”,变成代码搬运工,自己真正手敲一遍,这中间踩坑的经历才是你求职和答辩时最宝贵的素材。项目做完后,还可以往里面扩展“消息通知中心”、“数据报表模块”作为加分项,框架搭好了,往里加功能其实很快。