☰
基于SSM和Flask的工作日志系统开发实战与调试指南
2026/10/4 2:18:47 网站建设 项目流程

先说我看到这个标题的第一反应:一个员工工作日志系统,技术栈却横跨了Java的SSM和Python的Flask,这种组合在学校做课设的学生里不算多见。但为啥我说它挺有代表性?因为工作日志这东西,本质上就是一家公司“过程管理”的最小闭环——它有用户、有角色、有业务流程、有审批、有统计,做一遍下来,SSM那套三层架构、拦截器、动态SQL、事务控制基本全练到了;挂一个Flask上去做统计和可视化接口,又顺手把跨语言协作的坑踩了一遍。这篇文章我就以这套系统为样本,把这套组合拳怎么打、里面哪些点最容易翻车、以及调试文档到底该怎么写,一次说透。

1. 先把需求盘清楚:工作日志系统到底在解决什么问题

1.1 一个“被低估”的业务场景

很多人第一眼看到“工作日志”四个字,觉得太平凡了,不就是员工每天写几百字汇报吗?但你真去一家有几十个人的公司里待过就明白,这个功能是企业管理的刚需。领导关心的是“人到底在不在干活”“活推进成什么样了”——日报、周报、月报就是回答这两个问题最直接的载体;员工需要的则是一个能沉淀工作记录、被看到、被反馈的入口;再往上,人事和老板还会想看到“这个月哪个部门提交率高”“哪个项目占了多少人力”这类的统计结果。

把这个场景抽象成系统需求,就会发现它一点都不简单:它需要用户管理、部门管理、角色权限、日志的增删改查、提交与审批状态流转、批阅评论、统计报表、消息提醒。这是典型的小型办公自动化(OA)系统的核心子集。项目标题里堆了“办公自动化、协同办公、工作流程、团队管理”这些词,本质上是在描述同一件事——用系统把“员工—领导—管理动作”之间这条信息链路数字化。

拿来做毕业设计也好,拿来练手也好,这类系统的最大优点是:麻雀虽小,五脏俱全。它不会像电商系统那样有一堆并发、秒杀、分布式的问题让人望而生畏,但该有的工程结构、数据库设计、权限校验、前后端交互,一样不落。做完一遍,你对“一个Web业务系统是怎么跑起来的”会有一个完整的概念。

1.2 需求拆解:三类用户与核心用例

在动代码之前,先把角色定清楚。典型的员工日志系统,至少要拆出三类角色,他们的关注点完全不同:

  • 普通员工:每天/每周填写日志,可以保存草稿,最终提交;提交后可以查看领导的批阅意见;被退回时可以修改重新提交。
  • 部门经理:查看本部门员工的日志列表,对下属日志进行批阅(通过/退回),查看部门的提交统计。
  • 系统管理员:管理员工账号、部门信息,可以查看全公司日志,能做数据导出,通常还负责重置密码这类后台操作。

这个角色划分直接决定了后面所有的数据库设计、Session管理、拦截器放行策略。这里最容易犯的错是“只做一个登录,登进去以后所有人权限一样”——后面你会发现,一旦需要“经理只能看本部门”,权限判断会牵扯到接口、页面、按钮多处,提前把角色模型画出来能省掉至少一半返工。

1.3 用例落表:功能清单与优先级

结合上述用户,把功能清单和优先级排成下面这样,做项目的时候按这个顺序推进就不会乱。

优先级功能模块用户核心动作
P0登录认证与会话管理所有用户登录、登出、Session校验
P0日志填报与状态流转员工新增、编辑、草稿、提交
P0日志审批经理/管理员查看待审日志、通过、退回、填写批语
P1部门与员工管理管理员CRUD、分配角色、维护部门
P1日志查看与检索员工/经理/管理员按人、按日期、按类型检索
P2数据统计与图表展示经理/管理员提交率、日志数量趋势、部门对比
P2批语通知与消息中心员工查看被退回原因、收到新批语提醒

P0先做,P2后做,这条线不能乱。很多人一开始就跑去研究ECharts画多高级的图,结果把日志的增删改查都写得磕磕绊绊,这是典型的次序错乱。

2. 技术选型深挖:为什么会是SSM + Flask这种“混搭”

2.1 SSM为什么还是中坚力量

标题里同时出现了SSM和Flask,很多人第一反应是“这不就是两个项目硬拼吗?”其实不然。SSM(Spring + SpringMVC + MyBatis)在Java Web领域依然是极其成熟的主力框架组,尤其适合以传统分层架构为主的中小团队。Spring负责依赖注入和事务管理,SpringMVC负责请求调度和参数绑定,MyBatis负责SQL与对象的映射,三者配合,结构清晰得有点像搭积木。

为什么不去追逐Spring Boot + Cloud那套?对这个体量的系统来说,复杂的东西反而会成为负担。SSM的优势是“裸奔可控”——配置规则是显式的,请求从Controller到Service到Mapper的每一环你都能看透。对于一个需要写论文(也就是交付说明)、需要讲清楚设计思路的技术项目来说,SSM是一套非常适合“解剖教学”的骨架:底层原理都在配置和代码里摆着,而不是被框架自动装配和外化配置给吞掉了。这也是大量高校课设、毕设仍然延续SSM的原因之一。

2.2 Flask的角色定位:不抢主业务,专攻辅助

那Flask在这里面干吗?我见过不少同学把Flask和SSM做成一套系统里互相调用的“双服务”——SSM管登录、菜单、日志CRUD,Flask只负责统计数据接口和返回图表需要的数据。这个定位非常合理。

原因很简单:Python生态在处理数据分析、聚合统计、生成图表JSON这些事情上,比Java写起来顺手得多。一个部门提交率统计,在Java里要写一长串业务代码加MyBatis动态SQL,在Flask里可能几十行就能把数据从MySQL里查出来拼好JSON返回。Flask又是个极轻的框架,没有ORM强绑定、没有模块约束,适合做“微服务中的微服务”——你甚至可以把它理解成SSM旁边一个独立的计算引擎,被Java通过HTTP调用来完成统计职能。

所以正确的心态是:不要觉得Flask是来抢SSM饭碗的,它是来做SSM不擅长、也不愿意做的那部分辅助工作的。一个管业务事务,一个管数据计算,分工明确。

2.3 架构形态:从单体到“双引擎”的协作方式

从实际交付的架构来看,这套系统的典型形态是这样的:

浏览器(用户页面) ↓ 请求 Java SSM服务(端口8080) ├── Controller接收请求 ├── Service处理业务事务 ├── Mapper/MyBatis 访问MySQL ↓ 需要统计数据时,通过HTTP调用 Flask服务(端口5000) ├── /api/stats/xxx 接口 ├── pymysql连接同一个MySQL └── 返回聚合后的JSON数据

这里有一个特别重要的设计细节:不要让浏览器直接去请求Flask的接口。你想象一下,前端页面在8080端口,Flask服务在5000端口,浏览器直接跨端口发Ajax请求,瞬间就会撞上CORS跨域问题。虽然可以通过flask-cors解决,但在生产上其实不推荐。更稳的做法是:SSM作为统一对外入口,Java后端通过RestTemplate或HttpClient,向Flask的5000端口发起一个服务端到服务端的内部请求,拿到JSON后再原样返回给前端。

这样做有三个好处:

  • 前端永远只跟8080一个源打交道,不会出现跨域。
  • Flask服务对浏览器不可见,网络边界更干净,也更安全。
  • 以后如果要把Flask换成别的服务,前端代码一行都不用改。

3. 数据库设计与核心模块:从建表到权限控制

3.1 数据模型设计:日志、员工、部门、角色的表关系

数据库建设我建议遵循“先建主数据,再建业务表”的顺序。员工、部门、角色属于主数据,日志和批语属于业务数据。下面这套是我觉得最贴合需求、也适合写进论文的表结构:

部门表department:

CREATE TABLE department ( id INT PRIMARY KEY AUTO_INCREMENT, dept_name VARCHAR(50) NOT NULL UNIQUE, manager_id INT COMMENT '部门经理的用户ID', create_time DATETIME DEFAULT CURRENT_TIMESTAMP );

员工表sys_user:

CREATE TABLE sys_user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(64) NOT NULL COMMENT 'MD5/BCrypt加密存储', real_name VARCHAR(50) NOT NULL, dept_id INT, role_id INT COMMENT '关联角色表', status TINYINT DEFAULT 1 COMMENT '1启用 0禁用', create_time DATETIME DEFAULT CURRENT_TIMESTAMP );

角色表role:

CREATE TABLE role ( id INT PRIMARY KEY AUTO_INCREMENT, role_name VARCHAR(50) NOT NULL, role_code VARCHAR(20) NOT NULL UNIQUE COMMENT 'ADMIN/MANAGER/STAFF' );

工作日志表work_log:

CREATE TABLE work_log ( id INT PRIMARY KEY AUTO_INCREMENT, user_id INT NOT NULL, log_type TINYINT COMMENT '1日报 2周报 3月报', log_date DATE COMMENT '日志所属日期', content TEXT COMMENT '今日完成内容', next_plan TEXT COMMENT '明日/下周计划', status TINYINT DEFAULT 0 COMMENT '0草稿 1待审核 2已通过 3已退回', reviewer_id INT, review_comment VARCHAR(255), review_time DATETIME, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, UNIQUE KEY uk_user_date (user_id, log_type, log_date) );

第 一条业务经验是:UNIQUE KEY uk_user_date非常关键。一个员工一天最多只能有一条日报,这条唯一索引能从数据库层面挡住重复提交,不然业务代码里写十遍判重都可能漏。

批阅记录表log_review:

CREATE TABLE log_review ( id INT PRIMARY KEY AUTO_INCREMENT, log_id INT NOT NULL, reviewer_id INT NOT NULL, review_result TINYINT COMMENT '1通过 2退回', comment_content VARCHAR(500), review_time DATETIME DEFAULT CURRENT_TIMESTAMP );

把批阅历史单独拆一张表,而不是只在work_log上放一个状态字段,好处是以后可以追溯“退回了几次”“为什么退回”,还可以做“被退回率”这类质量统计。

另外强调一点:密码不能明文存。即便只是课设项目,也建议至少用MD5加盐或者BCrypt。这已经是行业底线,不是加分项了。

3.2 登录会话与基于角色的鉴权实现

登录模块看起来简单,但它是整个安全模型的地基。我常用的实现方式是:登录成功后将用户对象、角色编码写入Session,同时提供工具方法从Session取值;在这个基础上用拦截器(HandlerInterceptor)统一处理“未登录”和“无权限”两种情况。

先看登录Controller的核心逻辑:

@Controller @RequestMapping("/user") public class UserController { @Autowired private IUserService userService; @PostMapping("/login") public String login(String username, String password, HttpSession session, Model model) { User user = userService.login(username, password); if (user == null) { model.addAttribute("error", "用户名或密码错误"); return "login"; } if (user.getStatus() == 0) { model.addAttribute("error", "账号已被禁用,请联系管理员"); return "login"; } // 把用户对象和角色编码存进session session.setAttribute("loginUser", user); session.setAttribute("roleCode", user.getRole().getRoleCode()); return "redirect:/index"; } }

真正容易出错的是拦截器。建议拆成两个:一个LoginInterceptor管会话校验,一个AuthInterceptor管角色权限校验。前者检查Session里有没有loginUser,没有就重定向到login页面并带上“请先登录”;后者根据URL或请求参数判断当前操作要求什么角色,再跟Session里的roleCode比对。

public class LoginInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { HttpSession session = request.getSession(); if (session.getAttribute("loginUser") == null) { // 判断是否Ajax请求 String xrw = request.getHeader("X-Requested-With"); if ("XMLHttpRequest".equals(xrw)) { response.setContentType("application/json;charset=utf-8"); response.getWriter().write("{\"code\":401,\"msg\":\"登录已失效,请重新登录\"}"); return false; } response.sendRedirect(request.getContextPath() + "/login"); return false; } return true; } }

在spring-mvc.xml里配置拦截路径时,要注意三种例外:登录接口、静态资源(js/css/image)、注册接口(如果有)。否则静态资源全被拦死,页面会丑到你怀疑人生。

3.3 日志填报主流程:从前端表单到MyBatis落库

日志填报是整个系统的核心动作,流程上要保证操作闭环:新建日志 → 选择类型和日期 → 填写内容 → 保存草稿 → 提交审核 → 经理批阅 → 员工查看结果。如果被退回,员工看到批语后可以再编辑并二次提交,此时状态重新变为待审核。

Service层处理提交时,建议把“保存”和“提交”两个动作分开。保存只是写草稿,状态是0;提交则要同时校验内容非空、校验是否重复(靠表唯一索引兜底)、更新状态为1。

这里有一段MyBatis动态SQL值得参考,它解决了多条件组合查询的核心场景:

<select id="selectLogList" resultType="Map"> SELECT l.id, u.real_name, d.dept_name, l.log_date, l.log_type, l.status, l.content, l.review_comment FROM work_log l LEFT JOIN sys_user u ON l.user_id = u.id LEFT JOIN department d ON u.dept_id = d.id <where> <if test="deptId != null and deptId != ''"> AND u.dept_id = #{deptId} </if> <if test="logType != null and logType != ''"> AND l.log_type = #{logType} </if> <if test="startDate != null and startDate != ''"> AND l.log_date >= #{startDate} </if> <if test="endDate != null and endDate != ''"> AND l.log_date &lt;= #{endDate} </if> <if test="status != null"> AND l.status = #{status} </if> <if test="userId != null"> AND l.user_id = #{userId} </if> </where> ORDER BY l.log_date DESC, l.create_time DESC </select>

这个<where>标签配合<if>是做条件查询最经典的方式,直接把“经理只看本部门”和“管理员看所有”两种场景统一到了一套SQL里。技能扎实的同学还可以在这里声明:<where>标签会自动去掉第一个多余的AND/OR,这是很多面试官爱问的点。

4. Flask辅助模块实战:数据统计与可视化接口

4.1 Flask服务搭建与配置要点

Flask这块我按“轻量实用”来搭建,它只需要四个文件:app.py、config.py、stats_util.py,以及一个依赖清单requirements.txt。数据库连接我直接用的是PyMySQL,不引入SQLAlchemy。原因很简单:Flask在这里只是做聚合统计,又不需要建表,没必要给项目挂上一个完整的ORM,那样反而会让新手看得一头雾水。

# app.py from flask import Flask, jsonify, request from flask_cors import CORS import pymysql app = Flask(__name__) CORS(app) # 建议保留,防止开发阶段前端直接访问时报跨域错 def get_db(): conn = pymysql.connect( host="127.0.0.1", user="root", password="123456", database="work_log_db", charset="utf8mb4", cursorclass=pymysql.cursors.DictCursor ) return conn

这里有一个新手最容易踩的坑:pymysql连接MySQL如果漏了charset="utf8mb4",一旦日志内容里有中文或者表情符号,聚合查询返回的JSON到前端就是乱码。还有,cursorclass必须设成DictCursor,否则默认返回元组,你还要自己下标取值,非常痛苦。

4.2 统计接口设计与SQL聚合示例

统计接口的设计思路是“为前端准备好字典数组,而不是让前端自己拼数据”。举个最常做的接口例子:按部门统计近30天的日志提交率。

后端思路:先查部门列表,再查每个部门应提交的天数、实际提交的总数,然后计算提交率。为了讲解方便,这里用两个SQL就够。

@app.route("/api/stats/dept_submit_rate", methods=["GET"]) def dept_submit_rate(): days = request.args.get("days", 30, type=int) conn = get_db() cur = conn.cursor() # 查询各部门员工数 cur.execute(""" SELECT d.id, d.dept_name, COUNT(u.id) AS emp_count FROM department d LEFT JOIN sys_user u ON d.id = u.dept_id WHERE u.status = 1 GROUP BY d.id, d.dept_name """) departments = cur.fetchall() result = [] for dept in departments: dept_id = dept["id"] cur.execute(""" SELECT COUNT(DISTINCT l.user_id, l.log_date) AS submit_cnt FROM work_log l JOIN sys_user u ON l.user_id = u.id WHERE u.dept_id = %s AND l.log_type = 1 AND l.log_date >= DATE_SUB(CURDATE(), INTERVAL %s DAY) AND l.status IN (2, 3) """, (dept_id, days)) row = cur.fetchone() expect_cnt = dept["emp_count"] * days rate = round(row["submit_cnt"] / expect_cnt * 100, 1) if expect_cnt > 0 else 0 result.append({ "dept_name": dept["dept_name"], "submit_rate": rate, "total": row["submit_cnt"] }) cur.close() conn.close() return jsonify({"code": 0, "data": result})

注意SQL里用了%(参数)s(或者说%s以后端传参的方式),这是参数化查询的标准姿势。我从一开始就强调:Python拼接SQL就是给自己埋雷,字符串拼接一旦混入用户输入,等于把SQL注入漏洞送到别人嘴边。参数化以后这问题就没了。

4.3 与Java后端的联动:RestTemplate反向代理

前面说了,前端不要直接访问Flask。在Java后端定义一个RemoteStatsClient,把Flask接口包一层,这样对调用方来说跟调用本地Service没有区别。

@Component public class FlaskStatsClient { private RestTemplate restTemplate = new RestTemplate(); public String getDepartSubmitRate(int days) { String url = "http://127.0.0.1:5000/api/stats/dept_submit_rate?days=" + days; ResponseEntity<String> resp = restTemplate.getForEntity(url, String.class); return resp.getBody(); } }

然后Controller里调用这个Client,把JSON原样返回给前端:

@RestController @RequestMapping("/api/stats") public class StatsController { @Autowired private FlaskStatsClient flaskStatsClient; @GetMapping("/deptSubmitRate") public String deptSubmitRate(@RequestParam(defaultValue = "30") int days) { return flaskStatsClient.getDepartSubmitRate(days); } }

这个模式我把每一层的目的都用一句话写清楚:

  • Controller:只管接收前端参数,不做任何业务计算。
  • FlaskStatsClient:负责跨语言通信,拼URL、发HTTP请求、拿结果。
  • Flask接口:负责查库聚合、返回Jsonify结果。
  • 前端:只用ECharts等库消费JSON,展示图表。

4.4 图表展示与数据联动

Flask返回的数据交给前端ECharts时,尽量不要让前端去处理null或undefined。在Flask端把文案、颜色等展示字段都组装好,前端直接setOption,省事也少报错。比如这样:

rate_data.append({ "value": rate, "name": dept["dept_name"], "itemStyle": {"color": "#5470c6" if rate >= 60 else "#ee6666"} })

实际效果是:阈值以上蓝色、以下红色。这不是什么高深逻辑,但对“领导一眼看出哪个部门提交率低”非常有帮助。

5. 调试文档的价值:别把“排障记录”当废纸

5.1 一份合格调试文档应该包含的内容

项目标题里带着“调试文档”四个字,很多人其实是把它当形式主义来看的。但到了最后答辩或公司内部交接时,调试文档才是救命的那一叠纸。我建议按下面四个部分来写,每个部分是递进关系:

  • 环境清单:JDK版本、Tomcat版本、MySQL版本(5.7和8.0差异巨大)、Maven版本、Python版本,每个都标注“已验证可用版本”。
  • 部署步骤:从克隆源码到启动成功的顺序,每一步配截图,尤其是数据库导入脚本和IDEA导入Maven项目的步骤。
  • 常见问题对照表:把下面的“踩坑实录”直接整理成表格,每个问题写明“现象—原因—解决方案”。
  • 测试用例:至少列出10个冒烟测试用例,比如“员工登录→填写日报→提交→经理登录→查看待审→批阅通过→员工看到已通过状态”,这个主链路必须完整跑通。

5.2 我踩过的5个经典坑与排查思路

接下来,把这套系统从零跑通时最容易摔跤的坑挨个过一遍。这些是我自己帮人做调试时反复遇到的,每个都对应真实会话。

坑一:启动后访问登录页报HTTP 404,Tomcat控制台却不报错

排查思路:先看spring-mvc.xml里的视图解析器配置。如果是InternalResourceViewResolver,前缀/WEB-INF/jsp/,那么JSP必须放在src/main/webapp/WEB-INF/jsp/下。很多人把JSP放错目录,比如放到了src/main/resources里,编译后根本不会被复制到WEB-INF下,自然404。另外检查一下web.xml是否配置了DispatcherServlet的<url-pattern>/</url-pattern>,如果配成*.do,所有非.do请求都会走默认Servlet,也会引发404。

坑二:登录查询报ClassNotFoundException: com.mysql.jdbc.Driver

原因几乎可以断定是MySQL驱动版本与连接串不匹配。如果用的是MySQL 8.0,驱动类应该是com.mysql.cj.jdbc.Driver,URL需要加serverTimezone=Asia/Shanghai;如果用的是MySQL 5.7,那可以用com.mysql.jdbc.Driver,但强烈建议统一升级到MySQL 8.0并写成新版驱动类,免得到处找老jar包。

对应的jdbc.properties参考:

jdbc.driver=com.mysql.cj.jdbc.Driver jdbc.url=jdbc:mysql://127.0.0.1:3306/work_log_db?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false jdbc.username=root jdbc.password=123456

坑三:中文乱码,登录后页面所有中文全是问号

分三个层面排查:第一,web.xml里有没有CharacterEncodingFilter强制UTF-8;第二,MySQL表结构默认字符集是不是utf8mb4;第三,Tomcat的server.xml里Connector是否设置了URIEncoding="UTF-8"。通常第一处漏了最致命,因为GET请求和POST请求都会经过过滤器。建议三处全部统一,不要只改其中一处。

坑四:MyBatis的Mapper接口找不到,Spring启动直接报Invalid bound statement (not found)

这是Maven配置文件过滤问题。默认Maven在打包时只拷贝src/main/resources下的资源,如果mapper/*.xml放在了src/main/java里,编译后classes目录下根本没有XML。解决办法有两个:一是把Mapper XML统一放到src/main/resources/mapper/;二是在pom.xml里显式配置资源:

<resources> <resource> <directory>src/main/java</directory> <includes> <include>**/*.xml</include> </includes> </resource> <resource> <directory>src/main/resources</directory> </resource> </resources>

坑五:前端页面调/api/stats/deptSubmitRate报405 Method Not Allowed

多半是Controller里用了@GetMapping,但请求是用@RequestBody那种POST方式发出的;也可能是Tomcat的POST表单参数解析需要HttpServletRequest,但你用了@RequestParam在GET请求上。先确认前端Ajax的方式和后端注解是否一一对应,再看Tomcat日志里有没有Parameters is null这样的底层报错。这个坑一般五分钟能定位,但描述不出来会让人白熬一夜。

5.3 调试文档的“增量维护”习惯

最后聊一个习惯问题。我发现很多同学是“项目做完了才想起来写调试文档”,写的时候全靠回忆,效果大打折扣。我的建议是:从拿到项目第一天就在项目根目录建一个DEBUG.md文件,每遇到一个坑,解决之后立刻记三行:现象是什么、原因是什么、怎么解决的。等到项目做完,这份文件自然就是最完整的调试文档。你看,它根本不用“写”,只是“记”。

6. 源码阅读与二次开发建议

6.1 源码结构梳理:从包名看懂项目脉络

如果你拿到的是一份完整的源码包,第一步不要急着点开Controller,先看包结构。一个规范的SSM项目包名已经能透露给你很多东西:

com.company.worklog ├── controller # 控制器:用户、日志、部门、统计、批阅 ├── service # 业务接口 │ └── impl # 业务实现 ├── dao # MyBatis Mapper接口 ├── entity # 实体类:User、Department、WorkLog、Role ├── common # 通用类:Result、PageBean、常量 ├── interceptor # 登录拦截器、权限拦截器 └── utils # 工具类:日期处理、MD5加密

看一个项目先搞清楚controller和service的分工:Controller里如果出现了超过十行的业务代码,这个项目的基本功就有问题。一个健康的项目,Controller只负责参数接收、调用Service、返回视图或JSON;事务、判重、状态流转全部下沉到Service。前面“日志填报”那一节的逻辑,就应该完整写在Service层,而不散落在Controller里。

6.2 可以扩展的3个方向

这套系统做完以后,想继续升级可以在三个方向里选一个,难度和收益都不同:

  • 方向一:增加消息通知。领导批阅通过或退回时,往消息表插一条记录,员工登录后在页面右上角看到未读数字。改动量不大,但对“协同办公”的体验提升非常明显。
  • 方向二:整合Redis缓存登录状态。把Session替换成Redis Token,登录后生成一个token返回给前端,前端请求头里带着token,后端用拦截器验证。这一步做完了,就离真正的无状态会话不远了。
  • 方向三:引入日志全文检索。日志内容存久了以后,用LIKE查会越来越吃力,可以把数据同步到ElasticSearch,或者至少用MySQL的全文索引过渡一下。这条路线偏重,但对理解搜索原理帮助很大。

我个人的建议是优先做方向一,因为它在现有数据模型上只增加一张表和两个接口,对刚入门的人来说不会造成太大认知负担,又能让整个系统功能上显得完整很多。

7. 关于LW(说明文档)的一点提醒

我看标题里带了一个“LW”,我猜这是论文/说明文档的简称。很多人把它当成最后的苦差事,但我干活的经验恰恰相反:给这类系统写设计说明,应该跟写代码同步进行。你在设计数据库表结构的时候,顺手把E-R图画了;在做权限设计的时候,顺手把用例图补上;在调通Flask跨服务调用的时候,顺手把系统架构图补上。这样等代码写完,论文的第三章“系统设计”和第四章“系统实现”其实已经完成大半了。

写的时候还要注意把关键决策写清楚,不要只写“我用了SSM”,要写“为什么用SSM”——因为项目规模适中、分层清晰、便于教学;Flask同理,不要只说“我用了Flask”,要写“Flask承担了数据聚合职责,通过HTTP接口被Java调用,从而规避了跨域并保持前后端调用路径单一”。这种“选型+理由+替代方案对比”的写作方式,才是答辩老师最想看到的内容。

再补一句:论文里所有截图不要直接用浏览器缩放,把IDEA、数据库工具、页面都整理干净再截图。一份干净清晰的文档,胜过你答辩时多说十句话。

8. 尾声:一点个人体会式的中肯建议

最后讲点我反复跟学生说的经验。如果你从上到下把这篇读完了,会发现这套系统的技术难度不在“某个算法、某个框架”,而在“把一个多角色业务流程完整落地”的工程能力。你把员工、经理、管理员三个视角分别登录一遍,走一遍日志提交、审批退回、统计查看的完整链路,这比背诵十个Java面试题都管用。

真要去动手做,我的建议是:顺序绝对不要乱。先把数据库脚本写好,灌入至少30个员工和2到3个月的日志假数据,然后把登录、日志填报、审批主流程跑通,再碰Flask统计。一次只加一个功能模块,千万不要同时改权限逻辑和统计接口,不然出了Bug你连是哪个环节引入的都不知道。

最后分享一个小技巧,能帮你省大量时间:写一个测试数据生成脚本,用Java或Python随机生成上个月的日志数据,日期跳过周末,每人每天一条,内容从预设模板里随机选取。有了这堆假数据,调列表、调统计、调饼图都是一瞬间的事;没有假数据,你排查“为什么图表是空的”这类问题,根本无从下手。这个脚本半小时就能写完,但它给你省下来的时间是以天为单位的。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询