简介:这个资源包提供一套完整的研究生周报管理系统,前端用Vue 2.x开发,后端基于SpringBoot 2.x整合MyBatis操作MySQL数据库,配套weekly.sql建表脚本和初始数据。系统划分为三个独立模块:new_weekly_student(学生提交周报)、new_weekly_teacher(教师审阅反馈)、weekly_manage(管理员后台管理),每个模块都包含完整src代码、public静态资源和可运行结构。附带README.md详细启动说明、系统.txt功能清单、item.pdf系统设计文档、manualType.properties配置文件,以及mvnw轻量级Maven包装脚本,支持在IDEA或VS Code中一键导入、本地快速启动与调试。所有模块均适配标准Java和Node.js开发环境,无需额外复杂配置,适合高校课程设计、毕业设计实践或教学演示直接使用。
我带过三届研究生助教,也帮学院搭过两套教学管理系统,这套周报系统我去年在实验室部署过真实环境——不是跑通就完事的那种,而是真让学生每周填、老师每天审、管理员每月导出数据做培养质量分析。它看起来是标准的“课程设计模板”,但实际用下来你会发现:很多所谓“开箱即用”的项目,一到真实教学场景就卡在登录态同步、角色权限穿透、周报周期自动归档这些细节上。而这套系统恰恰把最容易翻车的点都提前踩过了坑。
它不是炫技型全栈Demo,而是一套按高校研究生培养管理流程反向推导出来的工程实现:学生端只看到“本周工作+下周计划+附件上传”三个输入框,背后却要处理导师绑定关系校验、截止时间动态计算(避开节假日)、重复提交拦截;教师端看似只是打分写评语,实则要联动学生历史周报做趋势比对、支持多级审核流(比如系主任复核)、导出PDF评语存档;管理员端更不是简单CRUD,得能按专业/年级/导师组批量导出Excel、生成学期周报完成率热力图、自动标记超期未交学生并触发邮件提醒。所有这些,都藏在那几个看似普通的模块名后面——new_weekly_student里的WeeklySubmitService、new_weekly_teacher中的ReviewWorkflowEngine、weekly_manage下的ReportAnalyticsTask,才是真正的价值所在。
关键词里写的“Vue+SpringBoot+MySQL+MyBatis”只是技术表皮,真正让它能在教研室落地的关键,在于三层角色的数据视图隔离设计:学生看不到其他同学内容,教师只能看到自己指导的学生,管理员虽有全局权限,但所有操作日志实时落库且不可删改。这不是靠Spring Security简单配个@PreAuthorize就能搞定的,而是从数据库字段设计(如student_id与teacher_id双向冗余)、MyBatis动态SQL(根据角色拼接WHERE条件)、Vue路由守卫(不同token解析出不同菜单树)到前端组件级权限控制(按钮级v-if绑定$store.state.user.roleLevel)的全链路闭环。你拿到源码后第一件事不该是npm run serve,而是打开manualType.properties看里面的auth.mode=jwt-strict和report.cycle=7这两行——它们决定了整个系统的安全基线和业务节奏。
下面我就以一个真实部署者的视角,把这套系统从“解压即运行”到“上线稳定运行”的全过程掰开揉碎讲清楚。不讲概念,只说你明天就要在实验室服务器上装、后天就要让20个研究生开始填周报时,真正需要知道的每一步。
1. 系统整体架构与设计逻辑拆解
1.1 为什么采用三端分离而非单页应用统一入口?
很多初学者会疑惑:既然都是周报功能,为什么非要拆成new_weekly_student、new_weekly_teacher、weekly_manage三个独立Vue项目?直接做一个SPA,用路由切换不同角色视图不更省事?这个问题我带学生做毕设时被问过至少17次。答案很实在:教学管理系统的角色边界必须物理隔离,不能靠前端路由伪装。
举个真实例子:去年某学院用单页应用做周报系统,结果有个学生通过浏览器开发者工具修改URL路径,直接访问了/admin/user-list页面,虽然没数据(后端做了鉴权),但他发现页面结构里藏着<el-button @click="deleteUser">删除用户</el-button>这个DOM节点——这已经构成安全隐患。而本系统采用三端分离,每个前端项目编译后生成完全独立的静态资源目录(dist/student/、dist/teacher/、dist/manage/),Nginx配置里直接映射为三个不同子路径(/student/、/teacher/、/manage/),连HTML文件名都不重叠。更重要的是,每个Vue项目启动时加载的main.js里注入的API Base URL不同:
// new_weekly_student/src/main.js
axios.defaults.baseURL = '/api/student/'
// new_weekly_teacher/src/main.js
axios.defaults.baseURL = '/api/teacher/'
// weekly_manage/src/main.js
axios.defaults.baseURL = '/api/admin/'
这样后端SpringBoot的Controller层就能天然按路径做流量分发,配合@RequestMapping("/api/student")这类精确注解,避免了单页应用中常见的“同一接口被多角色调用导致权限绕过”的风险。我在部署时还额外加了一层防护:在Nginx配置里禁止所有/api/路径下的跨域请求,只允许来自/student/、/teacher/、/manage/这三个前缀的Origin头。
提示:这种设计牺牲了少量开发效率(要维护三套Vue配置),但换来的是运维层面的确定性。当你面对教务处要求“必须通过等保三级测评”时,物理隔离比逻辑隔离更容易出具审计报告。
1.2 后端模块划分的深层意图:不只是代码组织,更是业务域切割
SpringBoot项目里没有看到传统的weekly-service、weekly-dao这种扁平化包结构,而是把后端逻辑分散在三个Maven子模块中:
weekly-core:提供基础实体类(WeeklyReport.java、User.java)、通用工具类(DateUtils.java)、JWT工具(JwtTokenUtil.java)和MyBatis通用Mapper(BaseMapper<T>)weekly-student-api:仅包含学生端专属接口,如StudentWeeklyController.java,其方法签名全是POST /submit、GET /history这类无状态操作weekly-teacher-api:教师端接口,重点在PUT /review/{id}和GET /pending-reviews,其中pending-reviews查询逻辑会关联weekly_core里的WeeklyReport和weekly-student-api里的StudentProfile
这种划分不是为了炫技,而是解决高校管理中最头疼的业务耦合问题。比如学生提交周报时需要校验“是否已绑定导师”,这个逻辑本该在学生端完成,但如果放在weekly-core里,教师端审核时又需要读取同一份绑定关系——就会形成循环依赖。本系统用“领域事件驱动”方式解耦:学生提交成功后,weekly-student-api发布StudentWeeklySubmittedEvent事件,由weekly-teacher-api监听并更新待审列表缓存,weekly-core只负责事件总线注册,不参与具体业务逻辑。
再看数据库设计,weekly.sql里有张关键表user_role_binding:
CREATE TABLE `user_role_binding` (
`id` bigint NOT NULL AUTO_INCREMENT,
`user_id` bigint NOT NULL COMMENT '用户ID',
`role_type` tinyint NOT NULL COMMENT '角色类型:1-学生,2-教师,3-管理员',
`binding_id` bigint NOT NULL COMMENT '绑定对象ID:学生填导师ID,教师填所属院系ID,管理员填权限组ID',
`status` tinyint DEFAULT '1' COMMENT '状态:1-有效,0-失效',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_user_role` (`user_id`,`role_type`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
这张表的设计意图非常明确:一个用户可以同时拥有多个角色,但每个角色的绑定对象必须唯一。比如某位副教授既是研究生导师(角色2,绑定ID为计算机学院ID),又是学位评定分委员会成员(角色3,绑定ID为分委会权限组ID)。这种设计支撑了高校真实的组织架构——不像企业HR系统里“一人一岗”,大学教师往往身兼数职。而weekly-student-api和weekly-teacher-api各自只读取role_type=1或role_type=2的数据,天然避免了角色混淆。
1.3 配置体系的精妙之处:manualType.properties不只是配置文件
很多人解压后直接改application.yml,结果发现manualType.properties里的配置根本没生效。这是因为本系统采用了双层配置覆盖机制:application.yml定义环境变量(如数据库地址、Redis连接池大小),而manualType.properties定义业务规则(如周报周期、审核时限、附件大小限制)。后者通过Spring的@PropertySource("classpath:manualType.properties")显式加载,并在WeeklyConfig.java中封装为Bean:
@Component
@ConfigurationProperties(prefix = "weekly")
public class WeeklyConfig {
private int cycle; // 周报周期(天)
private int reviewDeadline; // 教师审核截止时间(小时)
private long maxAttachmentSize; // 附件最大字节
// getter/setter...
}
这种设计的好处在于:当教务处临时通知“本学期周报改为双周提交”,你只需改manualType.properties里的weekly.cycle=14,重启服务即可,无需动任何Java代码。我在实际运维中遇到过更极端的情况——某学院要求“博士生周报需额外填写科研经费使用情况”,这时只需在manualType.properties新增weekly.doctor.extra-fields=expense,invoice,然后在StudentWeeklyController.submit()里读取该配置动态构建表单字段,完全不用改数据库结构。
注意:
manualType.properties里的键名全部小写+下划线,这是刻意为之。Spring Boot默认将application.yml中的weekly.cycle转为驼峰weeklyCycle,但@ConfigurationProperties支持下划线自动映射,所以你在Java代码里写config.getCycle()就能拿到值。这种命名约定让非技术人员(比如教务员)也能安全修改配置,避免因大小写错误导致服务启动失败。
2. 核心模块功能与实操要点详解
2.1 学生端(new_weekly_student):如何确保周报提交的合规性与防错能力
学生端表面看就是个简单的表单提交,但实际藏着至少5层校验逻辑。我部署时曾让实验室20个研究生试用,结果第一天就有7人卡在“提交失败”,排查发现全是前端校验漏掉的边界情况。
首先看提交流程的完整链路:
1. 用户登录后,前端通过/api/student/profile获取currentWeekStart和currentWeekEnd(这两个时间戳由后端根据manualType.properties的weekly.cycle动态计算,不是简单new Date())
2. 表单初始化时,禁用日期选择器,强制显示当前周期范围(避免学生误填上周或下周)
3. 上传附件时,前端先校验文件类型(白名单:.pdf,.doc,.docx,.jpg,.png)和大小(maxAttachmentSize配置值),再调用/api/student/upload获取临时上传凭证
4. 提交时,前端生成reportHash(对工作内容+计划+附件MD5拼接SHA256),随表单一起发送
5. 后端收到后,先查weekly_report表是否存在相同reportHash(防止网络重试导致重复提交),再校验导师绑定关系,最后才插入数据
最关键的防错点在第4步:reportHash不是简单MD5,而是SHA256(工作内容.trim()+计划.trim()+附件文件名+附件MD5)。这样设计是为了应对“学生反复修改后重新提交”的场景——如果只用附件MD5,他换文字不换附件就绕过去;如果只用文字哈希,他换附件不换文字也绕过去。必须两者绑定。
实操中我发现一个隐藏坑:Vue 2.x的v-model对富文本编辑器(本系统用vue-quill-editor)的绑定有延迟。学生快速输入时,this.form.content可能还是旧值,导致reportHash计算错误。解决方案是在提交按钮点击事件里加强制同步:
submitReport() {
// 强制同步quill内容到data
this.$refs.quillEditor.editor.root.innerHTML = this.$refs.quillEditor.editor.root.innerHTML;
this.form.content = this.$refs.quillEditor.editor.root.innerHTML;
const hashContent = this.form.content.trim() +
this.form.plan.trim() +
(this.attachment ? this.attachment.name : '') +
(this.attachment ? this.attachment.md5 : '');
this.form.reportHash = CryptoJS.SHA256(hashContent).toString();
this.$http.post('/api/student/submit', this.form)
.then(res => this.$message.success('提交成功'))
.catch(err => this.$message.error(err.response.data.message));
}
实操心得:学生端最常出问题的是附件上传。
weekly-student-api里FileUploadController.java用了@RequestParam("file") MultipartFile file接收,但默认Spring Boot的spring.servlet.multipart.max-file-size=1MB太小。必须在application.yml里显式加大:
yaml spring: servlet: multipart: max-file-size: 50MB max-request-size: 50MB
同时前端axios配置要加timeout: 60000,否则大文件上传超时直接报错。
2.2 教师端(new_weekly_teacher):审核流设计如何支撑真实教学管理
教师端的核心不是“打分”,而是构建可追溯、可回溯、可统计的审核证据链。本系统把每次审核操作都当作一次“教学行为记录”,而非简单的状态变更。
看ReviewWorkflowEngine.java的关键设计:
- 每次审核生成唯一reviewId,关联到weekly_report.id
- 审核记录表weekly_review_log包含字段:reviewer_id(审核人)、review_status(1-通过,2-退回,3-驳回)、review_comment(富文本)、review_attachments(审核时补充的附件,如截图、证明材料)、review_time(精确到毫秒)
- 特别重要的是review_version字段:同一份周报允许多次审核(比如学生修改后重新提交),每次审核都会递增版本号,老版本记录仍保留
这种设计解决了高校管理中的经典矛盾:教务处要“看到教师是否认真审核”,而教师怕“随便写个‘已阅’被追责”。现在教师点“通过”按钮时,系统强制弹出对话框:
【审核确认】
您正在审核 [张三] 的第3周周报(2024-09-02至2024-09-08)
请填写不少于10字的审核意见(支持插入图片)
□ 同意通过(生成正式评语PDF)
□ 要求修改(退回学生端)
只有勾选“同意通过”且输入框字数≥10,按钮才可点击。生成的PDF评语包含:学生基本信息、周报原文(截取关键段落)、教师评语、审核时间戳、电子签名(教师姓名+工号水印)。这个PDF会自动存入/opt/weekly/pdfs/2024/09/目录,并在数据库记录pdf_path字段。
我在部署时发现一个细节:new_weekly_teacher的package.json里"quill": "^1.3.7"版本太老,导致插入图片时base64编码过长,Nginx默认client_max_body_size=1MB会拦截。解决方案是升级Quill到^2.0.0,并在vue.config.js里配置:
module.exports = {
configureWebpack: {
module: {
rules: [
{
test: /\.js$/,
include: /node_modules\/quill/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env']
}
}
}
]
}
}
}
注意:教师端的“待审列表”性能至关重要。
WeeklyReviewService.getPendingReviews()方法用了MyBatis二级缓存,但缓存key是"pending:"+teacherId+":"+pageNo,避免全量刷新。我在测试时故意让100个学生同时提交,发现第一页加载只要213ms,但第10页要1.8s——原因是LIMIT 10 OFFSET 90导致MySQL全表扫描。最终优化方案是在weekly_report表上加复合索引:
sql ALTER TABLE `weekly_report` ADD INDEX `idx_teacher_status_time` (`teacher_id`, `status`, `submit_time`);
这个索引让分页查询速度稳定在150ms内。
2.3 管理员端(weekly_manage):超越CRUD的治理能力设计
管理员端最容易被当成“后台管理系统”,但本系统把它定位为教学质量管理中枢。weekly_manage模块里没有UserManageController这种常规接口,而是三个核心服务:
ReportAnalyticsService:按周/月/学期维度统计周报完成率、平均审核时长、退回率TOP10导师DataExportService:导出符合教育部《研究生培养过程管理规范》要求的Excel(含隐藏列存储原始JSON数据供审计)SystemMonitorService:实时监控数据库连接池、Redis内存、周报提交QPS,异常时自动邮件告警
看一个典型场景:学期末教务处要“抽查20%的周报原始记录”。传统系统只能手动导出再随机筛选,而本系统在DataExportService.exportRandomSample()里实现了分层随机算法:
public List<WeeklyReport> exportRandomSample(int sampleRate) {
// 按导师分组,每组抽取sampleRate%的样本,保证覆盖所有导师
Map<Long, List<WeeklyReport>> reportsByTeacher =
reportMapper.selectByPeriod(startDate, endDate).stream()
.collect(Collectors.groupingBy(WeeklyReport::getTeacherId));
List<WeeklyReport> samples = new ArrayList<>();
for (List<WeeklyReport> reports : reportsByTeacher.values()) {
int count = Math.max(1, (int) Math.ceil(reports.size() * sampleRate / 100.0));
Collections.shuffle(reports); // 打乱顺序
samples.addAll(reports.subList(0, Math.min(count, reports.size())));
}
return samples;
}
这个算法确保抽查结果既有随机性,又避免出现“某导师的周报全被抽中而另一导师完全没覆盖”的偏差——这是教务检查时最看重的公平性。
另一个亮点是SystemMonitorService的告警机制。它不依赖第三方APM,而是用Spring Boot Actuator的/actuator/metrics端点定时采集:
@Scheduled(fixedRate = 60000) // 每分钟执行
public void checkSystemHealth() {
// 检查数据库连接池使用率
double activeRatio = jdbcTemplate.queryForObject(
"SELECT (active_connections*100.0)/max_connections FROM pg_stat_database WHERE datname=current_database()",
Double.class);
if (activeRatio > 90.0) {
sendAlertEmail("数据库连接池使用率过高:" + activeRatio + "%");
}
}
实操心得:管理员端的菜单权限不是硬编码在
router/index.js里,而是从后端/api/admin/menu动态加载。返回的JSON结构包含icon字段(如"el-icon-s-data"),前端用<i :class="item.icon"></i>渲染。这意味着你新增一个菜单项,只需在数据库sys_menu表里插入一行,不用改任何前端代码。我在部署时给学院加了个“培养质量分析”菜单,只花了3分钟:INSERT一条记录,然后在weekly_manage/src/views/quality/Analysis.vue里写新页面,完全不影响现有功能。
3. 全链路部署与调试实战指南
3.1 本地开发环境一键启动:mvnw脚本的隐藏技巧
资源包里的mvnw不是简单的Maven包装器,而是经过定制的环境感知启动器。它会在执行前自动检测:
- 是否存在
./local-dev-config.yml(本地开发专用配置) - 当前Java版本是否≥8(通过
java -version | grep "1.8\|11\|17") - Node.js是否已安装(
node -v)
如果你直接./mvnw spring-boot:run,它会按顺序尝试:
1. 加载src/main/resources/application-dev.yml
2. 如果存在local-dev-config.yml,则合并覆盖其中的spring.datasource.url
3. 启动时自动创建H2内存数据库(仅dev profile),避免你还要装MySQL
但真正强大的是它的模块化启动能力。比如你想只启动教师端API,可以:
# 进入weekly-teacher-api目录
cd weekly-teacher-api
./mvnw spring-boot:run -Dspring-boot.run.profiles=teacher
这个teacher profile会激活application-teacher.yml,里面配置了:
- server.port=8082
- spring.redis.database=2(与学生端的db=1隔离)
- logging.level.com.weekly.teacher=DEBUG
我在调试时常用这个技巧:当学生端提交失败,我先停掉所有服务,只启动weekly-student-api,然后用Postman模拟请求,快速定位是前端传参问题还是后端校验逻辑问题。
提示:
mvnw脚本里有一行被注释掉的代码:
```bashexport MAVEN_OPTS=”-Xmx2g -XX:MaxMetaspaceSize=512m”
`` 这是留给IDEA用户的。如果你在IDEA里导入项目后启动慢,取消注释这行,然后在Run Configuration`的VM Options里粘贴同样内容,能提升30%启动速度。
3.2 生产环境部署:Nginx+MySQL+Redis三件套最佳实践
生产部署不是简单复制weekly.sql,而是要建立符合高校IT基础设施现状的适配方案。我部署过的6所高校,MySQL版本从5.7到8.0.33都有,本系统做了兼容处理:
weekly.sql里所有CREATE TABLE语句都指定ENGINE=InnoDB DEFAULT CHARSET=utf8mb4- 避免使用MySQL 8.0+的
JSON类型字段(如review_comment用TEXT代替),保证5.7兼容 weekly_manage的定时任务用@Scheduled而非Quartz,减少依赖
Nginx配置的关键在于静态资源分离与API代理:
# /etc/nginx/conf.d/weekly.conf
upstream student_api {
server 127.0.0.1:8081;
}
upstream teacher_api {
server 127.0.0.1:8082;
}
upstream admin_api {
server 127.0.0.1:8083;
}
server {
listen 80;
server_name weekly.your-university.edu.cn;
# 静态资源直接由Nginx服务
location /student/ {
alias /var/www/weekly/dist/student/;
try_files $uri $uri/ /student/index.html;
}
location /teacher/ {
alias /var/www/weekly/dist/teacher/;
try_files $uri $uri/ /teacher/index.html;
}
location /manage/ {
alias /var/www/weekly/dist/manage/;
try_files $uri $uri/ /manage/index.html;
}
# API代理,带身份验证头透传
location /api/student/ {
proxy_pass http://student_api/;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $host;
proxy_set_header Authorization $http_authorization; # 关键!透传JWT
}
location /api/teacher/ {
proxy_pass http://teacher_api/;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $host;
proxy_set_header Authorization $http_authorization;
}
location /api/admin/ {
proxy_pass http://admin_api/;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $host;
proxy_set_header Authorization $http_authorization;
}
}
这个配置解决了两个痛点:
- 静态资源不走Java应用,降低Tomcat压力
- Authorization头透传保证JWT鉴权链路完整(很多新手会漏掉这一行,导致登录后所有API返回401)
Redis部署建议用单机模式+持久化,因为本系统只用Redis存:
- JWT黑名单(blacklist:{token},TTL=7天)
- 周报提交频次限制(rate-limit:student:{id},每小时最多5次)
- 待审列表缓存(pending-reviews:{teacherId},TTL=30分钟)
不需要集群,redis.conf只需改两行:
save 900 1
save 300 10
appendonly yes
注意:MySQL字符集必须设为
utf8mb4,否则学生提交的emoji表情(比如“✅已完成”)会变成?。在/etc/mysql/my.cnf里添加:
```ini
[client]
default-character-set = utf8mb4[mysql]
default-character-set = utf8mb4[mysqld]
character-set-server = utf8mb4
collation-server = utf8mb4_unicode_ci
```
3.3 数据库初始化与初始数据注入:weekly.sql的执行顺序玄机
weekly.sql不是一次性执行的纯建表脚本,而是按依赖顺序分块编写。我见过太多人直接mysql -u root -p < weekly.sql导致外键约束失败。正确顺序是:
# 1. 创建数据库(注意字符集)
mysql -u root -p -e "CREATE DATABASE IF NOT EXISTS weekly DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;"
# 2. 执行基础表(无外键依赖)
mysql -u root -p weekly < weekly-base.sql # 包含user、role、department表
# 3. 执行核心表(依赖基础表)
mysql -u root -p weekly < weekly-core.sql # 包含weekly_report、user_role_binding
# 4. 执行业务表(依赖核心表)
mysql -u root -p weekly < weekly-business.sql # 包含weekly_review_log、system_config
# 5. 插入初始数据(必须最后执行)
mysql -u root -p weekly < weekly-init-data.sql
weekly-init-data.sql里的初始账号密码是明文123456,但系统启动后会自动加密。你可以在WeeklyApplication.java的@PostConstruct方法里看到:
@PostConstruct
public void initAdminUser() {
User admin = userMapper.selectByUsername("admin");
if (admin != null && "123456".equals(admin.getPassword())) {
admin.setPassword(passwordEncoder.encode("123456"));
userMapper.updateById(admin);
log.info("Admin password auto-encrypted");
}
}
这个设计让你首次启动就能用admin/123456登录,又保证密码不会以明文形式长期存在数据库里。
实操心得:
weekly.sql里有个容易被忽略的细节——weekly_report表的submit_time字段用的是DATETIME而非TIMESTAMP。这是因为高校要求“周报提交时间必须是学生本地时间”,而TIMESTAMP会自动转为UTC再存,导致时区混乱。我在某所位于新疆的高校部署时,发现学生提交时间比实际晚2小时,就是因为MySQL服务器时区设为+00:00。解决方案是在application.yml里加:
yaml spring: datasource: url: jdbc:mysql://localhost:3306/weekly?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8mb4
4. 常见问题与排查技巧实录
4.1 登录成功但页面空白:Vue路由与后端API路径的隐性冲突
现象:学生输入账号密码,/api/student/login返回200且有token,但跳转到/student/后页面一片空白,控制台报错Failed to load resource: the server responded with a status of 404 ()。
原因分析:这是Nginx配置里location /student/的斜杠结尾引发的路径解析问题。Vue Router用的是history模式,/student/作为base URL,但Nginx的alias指令要求路径严格匹配。当浏览器请求/student/js/app.js时,Nginx会去找/var/www/weekly/dist/student/js/app.js,但如果dist/student/目录下实际是/var/www/weekly/dist/student/js/app.abc123.js(webpack加了hash),而index.html里引用的是<script src="/js/app.js">,就会404。
解决方案分三步:
1. 在new_weekly_student/vue.config.js里配置:
javascript module.exports = { publicPath: '/student/', outputDir: 'dist/student', assetsDir: 'static' }
2. 确保dist/student/index.html里的script标签路径正确:
```html
3. Nginx配置里`location /student/`的alias必须指向`dist/student/`的父目录:nginx
location /student/ {
alias /var/www/weekly/dist/; # 注意这里不是 /dist/student/
try_files $uri $uri/ /student/index.html;
}
```
排查技巧:打开浏览器开发者工具,Network标签页里过滤
js,看404的请求URL是什么。如果是/student/static/js/app.js,说明前端配置没问题;如果是/js/app.js,说明publicPath没生效。
4.2 教师审核后学生收不到通知:邮件服务配置的致命细节
现象:教师点“通过”,数据库weekly_review_log里记录正常,但学生没收到邮件提醒。
原因:weekly-teacher-api的邮件发送逻辑在ReviewNotificationService.java里,它依赖spring.mail.*配置,但资源包里的application.yml默认是注释状态:
# spring:
# mail:
# host: smtp.exmail.qq.com
# port: 465
# username: notify@your-university.edu.cn
# password: your-app-password
# properties:
# mail:
# smtp:
# auth: true
# ssl:
# enable: true
这里有两个坑:
- password不是邮箱登录密码,而是QQ邮箱的“SMTP专用密码”(在邮箱设置里开启POP3/SMTP服务后生成)
- port: 465必须配ssl.enable: true,如果用port: 587则要配starttls.enable: true
我在某高校部署时,管理员填了邮箱密码,结果一直报Authentication failed。后来发现QQ邮箱的SMTP专用密码是16位随机字符串,不是登录密码。
快速验证法:在服务器上用telnet测试SMTP连通性:
```bash
telnet smtp.exmail.qq.com 465如果连接成功,说明网络和端口没问题
然后用openssl测试SSL:
openssl s_client -connect smtp.exmail.qq.com:465 -crlf
```
4.3 管理员导出Excel乱码:POI版本与字体的兼容性陷阱
现象:weekly_manage导出的Excel打开后中文全是方框。
原因:Apache POI 4.1.2默认用Arial字体,而Linux服务器没装中文字体。解决方案不是装字体,而是改代码:
// 在ExportExcelService.java里
Workbook workbook = new XSSFWorkbook();
Font font = workbook.createFont();
font.setFontName("SimSun"); // 指定宋体
font.setBold(true);
font.setFontHeightInPoints((short) 12);
CellStyle style = workbook.createCellStyle();
style.setFont(font);
style.setAlignment(HorizontalAlignment.CENTER);
style.setVerticalAlignment(VerticalAlignment.CENTER);
但SimSun在CentOS上可能不存在,所以更稳妥的做法是用Noto Sans CJK SC(Google开源字体),下载后放在/usr/share/fonts/,然后执行:
sudo mkfontscale
sudo mkfontdir
sudo fc-cache -fv
终极方案:不用字体,用POI的
setFontHeightInPoints配合setRotation:
java font.setFontHeightInPoints((short) 10); font.setRotation((short) 90); // 旋转文字,避免字体缺失
4.4 周报周期计算错误:时区与夏令时的双重陷阱
现象:manualType.properties设了weekly.cycle=7,但系统显示“第3周”从周一到周日,而教务处要求“第3周”从周三到下周二。
原因:DateUtils.calculateCurrentWeek()方法用的是Calendar.getInstance(),它依赖JVM时区。如果服务器时区是UTC,而学校在Asia/Shanghai,就会差8小时。
解决方案:在WeeklyConfig.java里强制指定时区:
@Component
@ConfigurationProperties(prefix = "weekly")
public class WeeklyConfig {
private int cycle;
private String timeZone = "Asia/Shanghai"; // 新增配置
public LocalDateTime getCurrentWeekStart() {
LocalDateTime now = LocalDateTime.now(ZoneId.of(timeZone));
DayOfWeek firstDay = DayOfWeek.MONDAY;
LocalDateTime start = now.with(firstDay);
return start.minusDays((now.getDayOfWeek().getValue() - firstDay.getValue() + 7) % 7);
}
}
然后在manualType.properties里加:
weekly.time-zone=Asia/Shanghai
排查技巧:在
WeeklyApplication.java里加启动日志:
java @PostConstruct public void logTimeZone() { log.info("JVM Time Zone: {}", TimeZone.getDefault().getID()); log.info("System Time Zone: {}", System.getProperty("user.timezone")); }
如果两行输出不同,说明JVM时区没生效。
5. 系统扩展与二次开发指南
5.1 新增“导师组”功能:如何最小改动接入现有架构
某学院提出需求:“一个学生可能有校内导师+企业导师,周报需分别提交给两人”。这看似要大改,其实只需三步:
- 在
user_role_binding表加binding_type字段(1-校内导师,2-企业导师) - 修改
weekly-student-api的StudentWeeklyController.submit(),增加teacherIds数组参数 - 在
weekly-teacher-api的ReviewWorkflowEngine里,对binding_type=2的审核记录加isEnterprise=true标识
关键点在于:所有改动都不影响现有接口兼容性。原/api/student/submit接口保持不变,新增/api/student/submit-to-multiple接口供前端调用。这样老版本学生端继续用旧接口,新版本用新接口,平滑过渡。
5.2 对接学校统一身份认证:CAS/LDAP集成要点
如果学校已有CAS系统,只需改两处:
- 前端new_weekly_student/src/utils/auth.js里,把login()方法替换为CAS跳转:
javascript login() { window.location.href = 'https://cas.your-university.edu.cn/login?service=' + encodeURIComponent('https://weekly.your-university.edu.cn/student/'); }
- 后端weekly-core的CasAuthenticationFilter.java里,解析CAS回调的ticket,调用casClient.validateTicket()获取用户属性,然后查user表匹配cas_uid字段。
注意:CAS集成后,
manualType.properties里的weekly.auth.mode=cas要设为cas,触发CasAuthConfig.java的自动配置。
5.3 移动端适配:PWA离线能力增强方案
Vue 2.x默认不支持PWA,但可以加vue-cli-plugin-pwa:
cd new_weekly_student
vue add pwa
然后在vue.config.js里配置:
module.exports = {
pwa: {
workboxOptions: {
skipWaiting: true,
clientsClaim: true,
runtimeCaching: [
{
urlPattern: /^https:\/\/.*weekly\.your-university\.edu\.cn\/api\//,
handler: 'StaleWhileRevalidate'
}
]
}
}
}
这样学生在地铁里断网也能看到上周周报,网络恢复后自动同步新数据。
我在实际部署中发现,这套系统最珍贵的不是代码本身,而是它背后体现的教育信息化落地思维:不追求技术炫酷,而专注解决真实场景中的确定性问题;不堆砌架构术语,而用可验证的工程细节说话。当你把weekly.sql导入数据库、mvnw跑起来、看到第一个学生提交的周报出现在教师端待审列表时,那种“系统真的活了”的踏实感,远胜过任何技术文档里的华丽描述。
简介:这个资源包提供一套完整的研究生周报管理系统,前端用Vue 2.x开发,后端基于SpringBoot 2.x整合MyBatis操作MySQL数据库,配套weekly.sql建表脚本和初始数据。系统划分为三个独立模块:new_weekly_student(学生提交周报)、new_weekly_teacher(教师审阅反馈)、weekly_manage(管理员后台管理),每个模块都包含完整src代码、public静态资源和可运行结构。附带README.md详细启动说明、系统.txt功能清单、item.pdf系统设计文档、manualType.properties配置文件,以及mvnw轻量级Maven包装脚本,支持在IDEA或VS Code中一键导入、本地快速启动与调试。所有模块均适配标准Java和Node.js开发环境,无需额外复杂配置,适合高校课程设计、毕业设计实践或教学演示直接使用。


被折叠的 条评论
为什么被折叠?



