# intaca-sys **Repository Path**: feixiang-data/intaca-sys ## Basic Information - **Project Name**: intaca-sys - **Description**: No description available - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-09-28 - **Last Updated**: 2026-11-09 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # intaca-sys 大学生实习实践管理与讲座扫码签到系统 面向高校教务 / 学业导师 / 学生的实习实践一体化平台: - 学生 / 班级 / 导师基础档案(含专业、班级名称)与 EasyExcel 批量导入导出; - 实习三阶段任务(社会实践 / 行业实践 / 企业实习)发布与学分设定; - 讲座场次管理,动态二维码签到(进场 + 离场双签),大屏二维码下方附 6 位数字签到码供 H5 端免相机输入(可在讲座编辑中开关),异常签到识别与复核; - 讲座**位置签到**(第三期):场次的签到方式二选一 —— 扫码 / 数字码(默认,历史场次全部是这一档)或**位置签到**;后者由教务在高德地图上选点并设 **50~5000 米**的电子围栏,学生签到时上报定位,**超出范围直接拒签**(`41005`,提示里带实测距离与超出米数),不落「签成了但可疑」的记录。围栏中心一律存 GCJ-02,浏览器上报的 WGS-84 由**服务端**换算;围栏中心**不下发**学生端(只给签到方式与半径),否则学生在宿舍就能构造一个"圈内"坐标; - 讲座**报名**(第二期):每场可单独开启「是否需要报名」(默认关,历史场次行为不变),可设报名限额(默认不限)与报名截止时间(默认不截止);学生报名后可撤销,**已提交本场心得则不可撤销**(服务端硬拦);报名记录独立菜单,支持筛选与导出(数据范围见下条「教研室主任」里的教师数据范围口径); - 讲座**面向群体**(第二期):在原有「面向年级」下方新增「面向群体」筛选框,两者取**交集**,都为空即面向全体;群体在「学生群体」菜单里按 专业 / 班级 / 学生 圈选,成员按快照保存(发布那一刻的受众是确定事实,改规则需显式「重新解析」); - **教研室主任**角色(第二期):能力对齐学业导师,区别只在数据范围;「负责哪个专业」记在专业的属性上,由「专业管理」菜单维护专业字典并指派主任。 **教师数据范围 =「他当班主任的班级」∪「他任教研室主任的专业下的全部班级」,取并集**,教务 / 系统管理员为全部。一个人可以同时是学业导师和教研室主任,**两个身份叠加生效**;判据是「实际负责关系」,与账号上选的类型(学业导师 / 教研室主任)无关 —— 所以在「专业管理」里指派主任**本身就是授权**,不必再去账号管理改他的账号类型; - 学生端报名与参会(第二期):**报名入口只在 H5 端与小程序端**(与签到同一入口,Web 学生端不做报名,见下),报名后可撤销,**未报名也能到场签到、提交心得**,签到时标注为「未报名」待人工复核;不给学业导师开放「补充报名」; - 讲座**心得提交期限**(第三期增补):可提交区间是「讲座结束后 ~ 结束后 N 天」,N 默认 **30**,由 `sys_config` 的 `lecture.report.deadlineDays` 统一控制,改配置即时生效、**无需重启**;学生端(Web「我的报告」/ 小程序「报告」)显示本场截止日期,提交时由服务端强制校验(早了、晚了都是 `43000`)。截止时刻**每次请求现算、不落库**,所以调整配置后所有未截止场次的截止日会一起变;被驳回(待重传)的报告豁免该期限; - 实习报告 / 讲座心得体会在线提交(含字数下限校验),管理端评定(百分制 / 分段制); - 毕业资格自动汇总:实习通过项数、讲座双签到场场数、心得通过项数,支持 Excel 导出; - Web 端学生自助门户:学生用浏览器登录(学号 + 密码)即可上传实习报告 / 讲座心得、查看评定进度,并可自助修改密码。**该端只有「我的报告」与「密码管理」两个页面,没有讲座列表,也就没有报名入口** —— 报名统一走 H5 与小程序(与签到同一个入口,避免"报名在这端、签到在那端"的割裂); - H5 移动端数字签到码签到 + 报名:手机浏览器用学号 + 密码登录后,在「讲座签到」(`/h5/lectures`) 对需要报名的场次**报名 / 撤销报名**(按钮随限额、截止、是否已交心得自动置灰),并输入大屏二维码下方的 6 位数字签到码完成进场 + 离场双签到(无需相机 / HTTPS),可自助修改密码; - 微信小程序学生端:登录绑定学号、讲座列表与报名 / 撤销报名、扫码 / 手动签到、报告上传、我的进度; - 系统配置驱动业务规则、操作审计日志、mysqldump 自动备份; - **安全加固(1.0.3 / 1.0.4)**:管理端鉴权闸门改用与 Spring 路由相同的路径解析器,URL 编码路径(如 `/api/%61dmin/...`)一律按未授权处理;`/api/admin/auth/info` 补齐角色注解;小程序登录的 mock 只认显式开关(`appid` 为空不再自动放行),学籍绑定增加登录密码校验。详见「安全加固」一节。 ## 目录结构 ``` backend/ Spring Boot 2.7 后端(REST /api/admin、/api/mini) frontend/ Vue 3 + Vite + Element Plus 管理端 + 学生 Web/H5 门户(端口 5173,/api 代理到 8083;H5 移动端含数字签到码签到、报名、位置签到) miniprogram/ 微信小程序原生工程(学生端) 需求分析.md / 系统设计文档.md ``` ## 环境要求 - JDK 1.8+ / Maven 3.6+ - MySQL 5.7+ 或 8.x(开发默认 `localhost:3306/intaca_sys`,账号 `root`) - Node 16+(管理端) - 微信开发者工具(学生端本地联调用) ## 快速启动 ### 1. 后端 确保本地 MySQL 已启动,默认连接信息在 `backend/src/main/resources/application-dev.yml`(可通过环境变量 `MYSQL_HOST/MYSQL_PORT/MYSQL_DB/MYSQL_USER/MYSQL_PASSWORD` 覆盖)。 ```bash cd backend mvn spring-boot:run ``` 启动时自动: 1. 执行 `db/schema.sql` 建表、初始化 `sys_config` 规则,并对存量库做**幂等字段增量迁移**—— - 第一期:`student.major` / `student.class_name` / `class_info.major` / `student.password` / `report_record.reject_reason` / `lecture_session.sign_code_enabled`; - **第二期**:新建 `major` / `student_group` / `student_group_rule` / `student_group_member` / `lecture_group` / `enrollment_record` 六张表;`lecture_session` 加 `need_enroll` / `enroll_quota` / `enroll_deadline`,`class_info` 加 `major_id`(+ `idx_class_major`),`major` 加 `leader_teacher_id`(+ `idx_major_leader`);并把存量班级的专业文本灌入 `major` 字典、回填 `class_info.major_id`。 - **第三期(位置签到/电子围栏)**:只加列,**不建表、不加索引、零回填**——`lecture_session` 加 `sign_mode` / `fence_lat` / `fence_lng` / `fence_radius`,`attendance_record` 加 `sign_lat` / `sign_lng` / `sign_coord_type` / `sign_distance`。历史场次 `sign_mode` 自动为 1(扫码/数字码),**行为与改造前逐字一致**。 > 全部为 `CREATE TABLE IF NOT EXISTS` / `information_schema` 判断 + 动态 DDL,**可反复执行**;`continue-on-error: false`,迁移失败则启动即失败(宁可起不来,也不带半截 schema 对外服务)。各期的上线 SQL 与逐功能点改动清单见 `第二期修改说明-前端后端与数据库.md`、`第三期修改说明-前端后端与数据库.md`。 > ⚠️ **执行迁移时务必带 `--default-character-set=utf8mb4`**。本机 MySQL 的 `character_set_client` 默认是 `gbk`,漏掉该参数会把 UTF-8 脚本里的中文列注释按 GBK 解释后写入,**不报错、退出码仍为 0**,结果是列注释变乱码(开发中已踩过一次)。 2. 对存量库中 `password` 为空的学生**自动补初始密码** `123456`(否则老数据无法登录),并打印补发日志; 3. `intaca.demo-data=true` 时自动写入演示数据: - 演示账号(密码均为 `123456`):`admin`(系统管理员)、`jiaowu`(教务管理员)、`zhangwei`(学业导师) - 班级「软件工程2301班」(2023 届,专业:软件工程)与 6 名学生(`20230001 李小明` … `20230006 杨帆`,专业/班级名称随班级带出) - 2023 届三段实习任务(社会实践 / 行业实践 / 企业实习),提交窗口覆盖当前时间,可立即体验上传与评定 - 学生 Web / H5 端登录密码同为 `123456`(初始密码由学业导师发放,学生登录后可自助修改) 接口文档风格:统一 `{ code, msg, data }`(code=0 成功),管理端 JWT 放 `Authorization: Bearer`,小程序 JWT 放 `X-Student-Token`。 #### 打 fat jar(可执行包,用于部署) ```bash cd backend mvn -DskipTests package # 产物:backend/target/intaca-sys.jar(内嵌 Tomcat,java -jar 即可运行) # 启动(默认 application.yml,可用 --spring.profiles.active=dev 切开发环境): java -jar target/intaca-sys.jar # 或覆盖端口 / 数据库连接: java -jar target/intaca-sys.jar --server.port=8083 --MYSQL_HOST=localhost --MYSQL_PASSWORD=123456 ``` ### 2. 管理端 ```bash cd frontend npm install npm run dev ``` 浏览器打开 http://localhost:5173 ,用 `admin` / `123456`(或 `zhangwei` / `123456`)登录。若 8083 仍被占用,可改 `application-dev.yml` 的 `server.port` 与 `frontend/vite.config.js` 的 proxy target 为一致端口。 学生 Web 上传入口:http://localhost:5173/student-login ,输入 学号 + 密码 登录(演示:`20230001` + `123456`),进入「我的报告」(`/my-reports`) 上传实习报告 / 讲座心得并查看评定;左侧栏「密码管理」(`/my-password`) 可自助修改密码。密码忘记了请联系学业导师。**讲座报名不在此端**:该端没有讲座列表页,报名请用下面的 H5 入口或小程序。 H5 数字签到码入口:手机 / 电脑浏览器打开 http://localhost:5173/h5/login ,用 学号 + 密码 登录(演示:`20230001` + `123456`),进入「讲座签到」(`/h5/lectures`)。页面分两块:**需要报名的场次**在卡片上显示「报名 / 撤销报名」按钮(报名截止、名额已满、已提交本场心得时按钮置灰并给出原因),**报名与撤销报名都在这里**;**签到**则点「输入数字签到码(进场 / 离场)」,照着管理端大屏二维码**下方的 6 位数字签到码**输入即可(进场、离场各一个,与二维码同一有效期、同步刷新;无需自己区分类型,输哪个就签哪个)。H5 输码与小程序扫码共用同一份签到记录,任一端签过两端都显示「已进场签到 / 已离场签到」,重复签到会提示无需重复;进场与离场都完成后按钮自动置灰。页面右上角「密码」可自助修改密码;密码忘记了请联系学业导师。 ### 3. 小程序端 1. 微信开发者工具导入 `miniprogram/` 目录; 2. 「详情 - 本地设置」勾选 **不校验合法域名**; 3. 接口地址默认 `http://127.0.0.1:8083/api/mini`,如需真机请改 `miniprogram/utils/config.js` 的 `BASE` 为局域网 / 服务器地址; 4. 首次进入用演示学生绑定:学号 `20230001`、姓名 `李小明`、登录密码 `123456`(与 H5 登录同一个密码)。 > 开发环境 `wx.mock-enabled=true`,后端用稳定 mock openid 模拟微信登录,无需真实 AppID;生产环境在 `application.yml` 填入真实 `intaca.wx.appid/secret` 并设 `mock-enabled: ${WX_MOCK_ENABLED:false}`(或环境变量 `WX_MOCK_ENABLED=false`)。**1.0.4 起 `appid` 为空不再自动降级为 mock**:mock 关着而 appid/secret 缺一项,小程序登录会直接报「未配置」而不是静默放行。 ## 核心页面与演示路径 | 模块 | 管理端 | 小程序 | | --- | --- | --- | | 学生 / 班级 / 导师 | `/students` `/classes` `/teachers`:班级维护专业、学生自动带出班级名称与专业(可单独调整),支持按专业检索与 Excel 导入导出 | 绑定学号登录 | | 专业管理(第二期) | `/majors`:专业字典 + **指派教研室主任**。存量班级的专业文本已在启动迁移时灌入字典;班级挂到专业(`major_id`)后主任就按它算数据范围。**指派即授权**:被指派者立刻能看到该专业下的全部班级与学生,不必再去账号管理把他的账号类型改成「教研室主任」(判据是实际负责关系,不是账号标签)。**指派限教务 / 系统管理员**。清空指派则收回该专业的数据范围(他当班主任的班级不受影响) | — | | 学生群体(第二期) | `/groups`:按 专业 / 班级 / 学生 圈选,**成员按快照保存**——讲座「面向谁」是发布那一刻的确定事实,否则转专业 / 新生入学会让已发布讲座的受众悄悄变化,报名限额与考勤就对不上。规则改了要生效,点「重新解析」显式刷新。**圈不到人的群体不允许保存**(空群体用到讲座上等于那场讲座谁都不面向,是个很难排查的静默故障) | — | | 实习任务 | `/tasks` 发布阶段任务并设定提交窗口与字数 | 「报告」- 实习报告 | | 讲座与签到 | `/lectures` 发布场次、生成进出场二维码(下方附 6 位数字签到码,可在编辑弹窗开关);列表行「监控」打开实时统计弹窗(进出场人数、异常标记)。编辑弹窗里新增**「是否需要报名」开关**(默认关,关闭时不显示报名入口,与改造前行为完全一致)、**报名限额**(默认不限)与**报名截止**(默认不截止);「面向年级」下方新增**「面向群体」**多选框,与面向年级取**交集**,都为空即面向全体。二维码有效期在编辑弹窗里设定:拨「自动刷新」开关或把秒数填成 0 都等于关闭(关闭即长期有效的静态码,大屏显示「长期有效 · 不自动刷新」并给出「保存图片」);开启时大屏倒计时是**本轮**的剩余秒数 —— 点开进场码/离场码那一刻从整段秒数起算,数到 0 自动换新码再从头数,关掉大屏再打开又是从头数,中途不会因为数字码轮换而跳变。编辑弹窗最上方新增**「签到方式」**(第三期):**扫码 / 数字码**(默认,历史场次全部是这一档)或**位置签到**——选后者时弹出高德地图选点,并填 50~5000 米的**围栏半径**;列表新增「签到方式」列。位置签到的场次**不再出二维码**(按钮隐藏,接口也会拒绝),因为二维码与位置是互斥的两条通道 | 小程序「讲座」扫码签到;H5 `/h5/lectures` 输入 6 位数字签到码双签;**位置签到的场次**两端都改用定位签到(小程序 `wx.getLocation`、H5 `navigator.geolocation`),按钮文案自动变「位置签到」 | | 报告评定 | `/grading` 待评定列表、一键通过、下载原文、成绩录入;点「评定」/「驳回」开右侧抽屉,左(宽屏)在线看原文、右填成绩或驳回原因;列表的报告名称下方显示学生上传时的原文件名;姓名之后是「班级」列;工具栏可按「状态」筛选(待评定页签:待评定 / 已评未过 / 已驳回 · 等待重传,已评定页签:通过 / 未通过),筛选只影响显示与勾选范围、不影响接口;「批量下载」面板可全选或按需勾选,一键打成 zip(如 `实习报告评定_待评定_202609160910.zip`) | 小程序「报告」上传 / Web「我的报告」 | | 毕业汇总 | `/summary` 学生进度、成绩分布、导出 Excel | 「我的」查看已提交与通过项 | | 考勤管理 | `/attendance` 明细查询、异常复核、导出 Excel(文件名 `考勤记录_202609161435.xlsx` = 模块名 + 导出时刻,工作表同名)。「异常」筛选多一档 **未报名**(`abnormal=2`):需报名的场次没报名却来签到了,**这不是错误、是待复核的留痕**,所以只打标记不拦签到。列表与导出另有一列**「签到缺失」**(查询时派生、不写库):同一学生同一场次只有一端记录且该端窗口已过时,显示「缺签到 / 缺签退」;「复核正常」只清异常标记,不清这一列,补签后刷新即消失 | 「我的」- 签到记录 | | 报名记录(第二期) | `/enrollments`:报名明细,按场次 / 班级 / 学号姓名筛选,导出 Excel;每行并列报名时间与进场、离场签到,列表与导出同样带**「签到缺失」**列。**数据范围:管理员看全部;教师类账号看「他当班主任的班级 ∪ 他任主任的专业下的班级」**(导出同样受限)。紧挨着考勤记录摆放——两者是同一批人对同一批场次的两条线,放一起才好对照 | 小程序「讲座」详情页「报名 / 撤销报名」;H5 `/h5/lectures` 卡片上的「报名 / 撤销报名」(**Web 学生端无报名入口**) | | 密码管理 | `/password` 教务管理员 / 学业导师自助修改本人登录密码 | Web `/my-password`、H5 右上角「密码」自助改密 | | 系统管理 | 账号 / 系统配置 / 数据备份 / 操作日志(仅 SYS_ADMIN) | — | **双签签到演示**(无实体二维码设备时): 1. 管理端在讲座列表点「进场码」或「离场码」,弹出框上半是二维码、下半是 6 位数字签到码(可在讲座编辑弹窗关闭数字码的展示); 2. 小程序:复制接口返回的签到 token,在讲座详情页底部「开发调试:手动输入签到码」粘贴提交; 3. H5:直接把弹窗里那 6 位数字敲进「输入数字签到码(进场 / 离场)」弹窗即可,进场、离场各来一次。 业务规则(及格线、毕业门槛场次 / 通过项、讲座心得提交天数等)均可登录 `admin` 后在「系统配置」中修改并即时生效。 ## 安全加固(1.0.3 / 1.0.4) 线上安全测试报告定位到「管理端鉴权闸门可被 URL 编码绕过」这一根因,1.0.3 已修复并验证;1.0.4 继续修复小程序端的登录与绑定: | 编号 | 问题 | 处理 | | --- | --- | --- | | VULN-01 | `/api/admin/**` 角色闸门用**未解码**的原始 URI 做前缀匹配,`/api/%61dmin/...` 能绕过闸门却被路由到管理端 Controller,匿名可创建 `SYS_ADMIN` 账号 | `AuthInterceptor` 改用 Spring 路由自己的解析器 `UrlPathHelper.getPathWithinApplication(request)`,闸门与路由看到同一串解码后的字符;编码变体、双斜杠、矩阵参数一律 **fail-closed** | | VULN-02 / 08 / 09 | 同一绕过路径可匿名下载全库备份、触发服务端执行 `mysqldump`、读取全体教师信息 | 随 VULN-01 一并关闭(两个备份接口与教师接口本就有 `@RequireRole({"SYS_ADMIN"})`,失效原因仅为该绕过) | | VULN-11 | `/api/admin/auth/info` 所在 Controller 没有类级角色注解,学生令牌(`userId` 实为 `wechat_account.id`)可读到同 id 管理员的登录名、姓名与角色 | 补 `@RequireRole({"MENTOR","DIRECTOR","EDU_ADMIN","SYS_ADMIN"})`,与同文件改密接口口径一致;学生令牌访问返回 `40300` | | VULN-05(1.0.4) | 小程序 mock 登录的原判据是 `mockEnabled \|\| appid 为空`:交付默认配置里 `appid` 取自环境变量、默认空,未设 `WX_APPID` 时会**静默进入 mock**,任何非空 `code` 都能换到同一个固定 openid 的学生令牌 | `WechatService` 只认 `intaca.wx.mock-enabled`;`appid/secret` 缺失时**报错而不是降级**(错误信息直接指向配置项)。⚠️ 若线上原本靠「appid 为空自动 mock」在跑,升级后必须配真实 `WX_APPID/WX_SECRET`,或内网演示时显式开 `mock-enabled: true` | | VULN-06(1.0.4) | 绑定只认「学号 + 姓名」——这两个值同班同学都知道,可把任意未绑定学籍绑到自己微信上,token 里的 `studentId` 就是被绑学生 | 绑定增加**登录密码**校验(`BindReq.password`,BCrypt 比对 `student.password`,与学生 Web/H5 登录同一个密码);失败文案统一为「学号、姓名或密码不匹配」,学号不存在时走等价 BCrypt 空转,避免变成枚举器。小程序绑定页同步增加密码输入框 | 隔离实例 + 一次性库上的验证结果:鉴权闸门 **31/31** 通过(改前 6 条可被匿名或学生令牌打通)、`/auth/info` **16/16** 通过、14 组畸形路径的应用层 500 为 **0**。 > **仍未修复**:VULN-04(建号默认口令)、VULN-07(RSA Padding Oracle)、VULN-10(改密后不吊销会话)、VULN-12/13/14/15,以及备份加密与访问审计。完整清单与整改优先级见《实习实践管理系统Report.md》与各版发布说明。生产上请把 `intaca.demo-data` 设为 `false`,并确认未使用 dev profile 启动(否则会自动创建密码为 `123456` 的演示账号)。 ## 生产部署注意 - 更换 `intaca.jwt.secret`,数据库独立账号与备份路径; - `intaca.backup.mysqldump-path` 指向服务器 mysqldump,备份任务默认每天 02:00 执行,保留 30 天; - 管理端域名加入 CORS 白名单;小程序真实域名需在微信公众平台配置 request 合法域名; - `db/schema.sql` 的自动执行**只在 dev profile 下生效**:`spring.sql.init.mode: always` 配在 `application-dev.yml`,每次启动都会执行,对存量库做幂等增量迁移与数据回填(建表 `IF NOT EXISTS`、加列先查 `information_schema`、回填 `INSERT IGNORE`,所以**不会重复**;从第二遍起等于检查一遍什么都不做)。 - ⚠️ **生产不会自动迁移**:部署方案 §3.3 给 `application-prod.yml` 定的是 `spring.sql.init.mode: never`(原话「生产必须 never」)。所以**上线第二期必须手动执行同目录的 `第二期上线SQL.sql`**(含完整部署命令与核对查询),再发后端。详见 `第二期修改说明-前端后端与数据库.md`。 - ⚠️ **上线第三期同理,手动执行 `第三期上线SQL.sql`**,再发后端。**库必须最先改** —— 新后端第一个请求就会 `SELECT sign_mode`,列不存在直接报 SQL 异常。本期是**纯加列、零回填**,所以「跑完 SQL 不发代码」线上行为不变;也正因如此回滚**不需要动库**。详见 `第三期修改说明-前端后端与数据库.md`。 - 但**误用 dev profile 部署**是危险的:schema.sql 会重跑(幂等无害),可 `demo-data: true` 会**自动创建 `admin`/`jiaowu`/`zhangwei` 三个密码为 `123456` 的账号** —— 这才是必须先解决的严重问题。 - 前后端**必须一起发**:新前端会调 `/admin/student-groups`、`/admin/enrollments`、`/admin/majors` 等新接口,旧后端没有这些路由。 - 第二期**无破坏性变更**(全是新建表 + 新增列,带默认值或允许 NULL),代码回滚只需发回旧版前后端,**不要 DROP 表或列**——`enrollment_record` 里是真实报名数据。