多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

基于SSM的健身房管理系统:数据库设计、后端实现与排错指南

基于SSM的健身房管理系统:数据库设计、后端实现与排错指南 简介这套基于SSM的健身房管理系统设计与实现源码及数据库面向计算机毕业设计、课程设计及Java毕设场景涵盖健身房日常管理核心功能可作为完整项目参考或直接用于课题交付。资源包共2个文件包含项目源码压缩包和SQL数据库文件整体约54.9MB导入数据库并部署环境即可运行。系统按角色划分登录模块管理员、会员、教练、访客等身份均可登录退出并区分权限操作功能上覆盖账号管理、教练管理、健身器材管理、课程管理、健身房信息管理及会员卡管理管理员可完成增删改查会员与教练可维护个人信息满足多角色协同管理需求。已有92人学习下载项目结构完整配有数据库文件拿来即可用适合需要快速获取可运行源码与数据表的毕设人群参考借鉴。1. 基于 SSM 的健身房管理系统为什么它总被当作练手和答辩首选很多开发者开始做完整的 Java Web 项目不是从电商系统起步而是先做基于 SSM 的健身房管理系统。原因在于它看起来只是一个“增删改查”但把会员卡续费、私教预约、课程排期、设备状态叠在一起之后表结构和接口设计都比想象中复杂得多。SSM 是 Spring、SpringMVC、MyBatis 的组合用这套组合实现健身房管理系统恰好能覆盖数据库设计、后端分层、前端页面、权限控制和部署排错的全流程。这篇笔记按“数据库设计 → 后端实现 → 页面与权限 → 排错 → 验收”的顺序把基于 SSM 的健身房管理系统源码和数据库该怎么做讲清楚适合想用一个完整项目巩固框架知识的开发者也适合准备项目答辩、需要快速把系统做扎实的同学。2. 从需求到表结构先把健身房业务装进数据库做系统之前先不要急着建表先把业务里的动作列出来。健身房管理系统常见的动作有办卡、续费、冻结、解冻、签到、预约课程、取消预约、退卡每个动作都应该能落到一张业务表上。很多照着商城项目改的同学一开始就把开卡和续费混在同一张表里后期想查“这个会员一共续了几次费”时只能靠猜。我一般先把“卡”和“卡流水”分开卡表只保存当前状态流水表记录每一次状态变化。这是这类系统设计的根基也是后面几节要展开的内容。2.1 会员、课程、私教、设备四张核心表怎么设计先从会员表开始。健身房系统的会员表不只是姓名和手机号还要带上卡类型和卡状态否则签到、预约都无从判断。我通常会这样建起步表CREATE TABLE member ( id BIGINT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(50) NOT NULL, phone VARCHAR(20) NOT NULL UNIQUE, gender TINYINT NOT NULL DEFAULT 1 COMMENT 1-男 2-女, card_type TINYINT DEFAULT 0 COMMENT 0-无 1-次卡 2-月卡 3-年卡, card_status TINYINT DEFAULT 1 COMMENT 1-正常 2-冻结 3-已过期 4-已退卡, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;phone 做唯一键是因为健身房办卡基本都靠手机号识别身份一个手机号重复开卡会让后续签到、续费对不上人。card_type 和 card_status 必须分开类型描述“这张卡是什么”状态描述“这张卡现在能不能用”。用 TINYINT 存状态比字符串省空间也方便在 Service 里写 switch 判断但可读性差所以每个字段的 COMMENT 要写清楚。updated_at 是调试时的好帮手别人改坏了数据先看这个字段就能定位。课程和预约要分开。课程表只描述“什么时间在哪个房间有谁的课”预约表记录“谁约了这节课”CREATE TABLE course ( id BIGINT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(100) NOT NULL, coach_id BIGINT NOT NULL, room VARCHAR(50) NOT NULL, start_time DATETIME NOT NULL, end_time DATETIME NOT NULL, max_count INT DEFAULT 20, sign_count INT DEFAULT 0 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE course_reservation ( id BIGINT PRIMARY KEY AUTO_INCREMENT, member_id BIGINT NOT NULL, course_id BIGINT NOT NULL, status TINYINT DEFAULT 1 COMMENT 1-已预约 2-已取消 3-已上课, created_at DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;course 表里的 sign_count 是冗余展示字段真正的预约人数应该通过 course_reservation 统计。为什么不直接 UPDATE sign_count因为并发取消时会丢更新两个人同时约最后一节课各自读到 19都 UPDATE 成 20最后可能只约进一个人。把预约记录落成行用 COUNT 去判断才能避免这种超卖问题。设备表的设计同理设备 ID、名称、状态每次维修都追加一条流水不要只改状态字段否则月底做设备报表时完全说不清设备这月修了几次。2.2 会员卡与续费状态机比你想的更复杂会员卡状态不是一句 UPDATE 能解决的。比如月卡续费不能只把 valid_end 加 30 天还要看当前是正常、冻结还是过期。把这些转移规则画成表格写代码时才不会漏当前状态触发动作下一步状态约束正常续费正常到期日向后顺延正常冻结冻结必须填写冻结区间冻结解冻正常到期日顺延冻结天数过期续费正常重新计算生效日过期退卡退卡需要核对退款金额把这套规则放在 Service 层做不要散在 Controller 里。同时member 表只能存当前状态不能回答“这个会员什么时候续过费、续了多少次”这类问题所以需要一张卡流水表CREATE TABLE card_order ( id BIGINT PRIMARY KEY AUTO_INCREMENT, member_id BIGINT NOT NULL, order_type TINYINT NOT NULL COMMENT 1-开卡 2-续费 3-冻结 4-解冻 5-退卡, amount DECIMAL(10,2) NOT NULL DEFAULT 0 COMMENT 正数收款负数退款, valid_start DATE, valid_end DATE, remark VARCHAR(200), created_at DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;amount 用 DECIMAL(10,2)不要用 FLOAT。浮点数在二进制里无法精确表示对账时会出现 0.1 0.2 不等于 0.3 的问题。valid_start 和 valid_end 用 DATE 而不是 DATETIME因为会员卡“有效期到哪一天”不需要时分秒用 DATETIME 反而会在跨天时出现边界判断错误。这张流水表也是后面做私教结算、经营报表的数据来源系统里所有跟钱相关的统计基本都从 card_order 来。2.3 初始化 SQL 的边界哪些数据该入 seed哪些不该源码包里通常会带一份 init.sql。这份脚本只该放三类东西管理员账号、状态枚举、演示用的基础课程和设备。千万不要把真实会员、预约和流水也写进去否则后续查数据时永远分不清哪些是测试数据。演示数据按模块最小量给一个管理员、三个课程、两台设备就够了。INSERT INTO sys_user (username, password, role, status) VALUES (admin, MD5(123456), 1, 1);密码用 MD5 只是为了初始化脚本方便正式项目应该换成 BCrypt。这里有一个容易踩的坑sys_user.password 字段如果建成了 VARCHAR(16)存 MD5 的 32 位字符串时数据库会报 Data too long很多人第一反应是框架连接有问题其实是字段长度不够。另外INSERT 一定要写成“字段名 VALUES”的形式不要写 INSERT INTO member VALUES (...) 这种省略字段名的语句。一旦后面表结构加了字段省略字段名的脚本就会错位排查起来非常难受。3. 用 SSM 搭出可运行的后端Spring、SpringMVC、MyBatis 各负责什么SSM 不是三个框架各写各的。Spring 管对象创建和事务SpringMVC 管 HTTP 请求路由MyBatis 管 SQL 映射。对健身房管理系统来说最舒服的划分是Controller 只接收参数和返回结果Service 只处理业务规则Mapper 只写 SQL。我看到过不少项目把会员状态的判断写在 Controller 里短时间能跑等第二个接口要用同样规则时只能复制粘贴改起来就很痛苦。3.1 分层约定与包结构别把 Service 写成摆设我习惯把代码分成 com.gym.controller、com.gym.service、com.gym.mapper、com.gym.entity、com.gym.common 五个包。entity 对应表结构service 暴露业务方法mapper 里面定义数据库操作common 放 Result、分页参数这类公用类。下面是会员服务的接口public interface MemberService { // 查询会员列表带分页和关键字 PageResultMember list(MemberQuery query); // 办理新卡内部校验手机号是否存在 boolean createMember(Member member); // 续费内部处理卡状态机 boolean renewCard(Long memberId, RenewRequest request); }list 返回 PageResult 而不是 List是因为前端表格需要总条数、当前页码和每页条数把分页信息放在返回对象里页面才好在底部渲染分页条。createMember 和 renewCard 都是业务动作不是简单的 insert/update。createMember 要检查手机号是否重复、卡类型是否合法renewCard 要判断当前卡状态能否续费这些规则放在 Service 里Controller 才能保持薄薄一层。Mapper 接口和 XML 的名称必须对应。接口方法名是 selectPageXML 里的 id 也要写 selectPagenamespace 必须写接口的全限定名public interface MemberMapper { ListMember selectPage(MemberQuery query); }如果代码没报编译错误但启动时说找不到 statement先看 Maven 有没有把 XML 一起打包到 target 目录。很多人把 XML 放在 src/main/java 下面没有配置 resources 目录导致 XML 没有进入最终产物接口运行时自然找不到映射。我还会在 MyBatis 全局配置里打开驼峰映射settings setting namemapUnderscoreToCamelCase valuetrue/ /settings这样数据库列 create_time 自动映射到实体属性 createTime不用手写 resultMap。注意个别字段如果不想被自动映射仍然可以用 resultMap 覆盖。3.2 配置文件的几个硬编码雷区SSM 的配置不难但数据源配置很容易让你卡一整天。一份稳定到能直接用的最小配置是jdbc.drivercom.mysql.cj.jdbc.Driver jdbc.urljdbc:mysql://localhost:3306/gym?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/ShanghaiuseSSLfalseallowPublicKeyRetrievaltrue jdbc.usernameroot jdbc.password123456serverTimezone 必加。MySQL 8 的驱动在连接串不带时区时通常会直接抛异常说服务器时区无法识别。allowPublicKeyRetrievaltrue 是 MySQL 8 使用 sha256_password 认证时需要的开发环境可以开生产环境建议关掉避免任何客户端都能向服务器申请公钥。useSSLfalse 是为了省掉证书校验的告警和握手开销内网部署可以这么写。连接串里的 host 写 localhost 时某些容器环境会优先解析成 IPv6 的 ::1导致连接失败遇到这种情况可以临时改成 127.0.0.1 验证。事务配置也别省。在 Spring XML 里声明事务管理器然后在续费、开卡这类写多个表的 Service 方法上加 Transactionalbean idtxManager classorg.springframework.jdbc.datasource.DataSourceTransactionManager property namedataSource refdataSource/ /bean tx:annotation-driven transaction-managertxManager/为什么必须有事务续费操作要同时更新 member.card_status 和插入 card_order假设第二步插入失败第一步却已经提交会员卡到期日变了但流水没记录月底对账时这笔钱永远对不上。加上 Transactional 后任一环节抛异常整个方法回滚到调用前状态。配置里的真实密码不要写死开发阶段可以用占位参数部署时通过环境变量或 Maven profile 替换。3.3 最小可运行闭环从登录到会员列表先串起登录接口。用 RestController 加统一返回对象前端拿 JSON 最省事RestController RequestMapping(/api/auth) public class AuthController { private final UserService userService; public AuthController(UserService userService) { this.userService userService; } PostMapping(/login) public Result login(RequestBody LoginRequest req, HttpSession session) { User user userService.login(req.getUsername(), req.getPassword()); if (user null) { return Result.error(用户名或密码错误); } session.setAttribute(loginUser, user); return Result.ok(user); } }登录成功后把 User 对象放进 session后续拦截器直接取。健身房系统这种管理后台QPS 不高session 完全够用硬换 Token 反而要额外处理过期、续签、跨域复杂度不值得。UserService.login 负责校验账号密码Controller 只把结果变成响应。Result 是统一的返回包装前端只看 code 是否为 200结构稳定后后面所有接口都按这个格式返回联调成本会很低。UserService 登录实现里真正查库的是 MyBatis。对应 XML 写select idselectByUsername resultTypeUser SELECT id, username, password, role FROM sys_user WHERE username #{username} /select这里必须写 #{username}。预编译占位符会把参数变成 PreparedStatement 的绑定变量既安全又能走数据库预编译缓存。如果写成 ${username}用户输入直接拼进 SQL遇到 or 11 这种输入登录接口就变成了查询全部用户这是最典型的注入入口。分页查询也可以用同样的方式select idselectPage resultTypeMember SELECT * FROM member WHERE name LIKE CONCAT(%, #{keyword}, %) ORDER BY id DESC LIMIT #{offset}, #{pageSize} /selectLIMIT 使用 #{offset}, #{pageSize}两个值都由 Service 层计算不要把 offset 拼进 SQL 字符串。这样写能保持 SQL 可读同时避免参数拼接带来的类型转换问题。4. 前端页面与权限控制这个系统能不能拿得出手全看这里很多后端同学把接口写完就觉得项目结束了结果一打开页面样式是乱的按钮没有权限控制登录之后还能直接访问后台菜单整个项目观感很差。健身房管理系统的前端本质是一个管理后台不需要太重的前端工程。常见做法有两种JSP JSTL 服务端渲染或者后端出 JSON、前端用 JS 渲染。对“源码 数据库”这种交付物来说我会选 JSP 方案代码量少、部署简单、评审时也好解释。4.1 页面直接读接口还是走 JSP 标签服务端渲染的列表页核心就是一个 forEach 循环c:forEach items${memberList} varm tr td${m.name}/td td${m.phone}/td td c:choose c:when test${m.cardStatus 1}正常/c:when c:when test${m.cardStatus 2}冻结/c:when c:otherwise异常/c:otherwise /c:choose /td /tr /c:forEach这种写法的好处是直观后端把一个 memberList 塞进 ModelJSP 就能渲染整张表格。缺点是状态文本在 JSP 写了一遍接口返回的 JSON 里又要写一遍两处不一致时页面显示就会和接口数据对不上。我的做法是写一个公共 JSP 标签或工具方法负责状态映射不要在页面里到处写魔法数字。如果用 JSP先确认依赖里有 JSTL 相关 jar 包。很多项目只在 pom 里加了 spring-web没有加 JSTL运行时 c:forEach 直接报错。页面顶部必须写% taglib prefixc urihttp://java.sun.com/jsp/jstl/core %如果不带 taglibc:forEach 和 c:if 都不会生效页面可能只显示空标签。这个错误很隐蔽因为 JSP 编译错误有时只在第一次访问时出现。如果做前后端分离后端只需要返回 JSON前端用 jQuery 去拼表格但项目小的时候纯 JSON 要多处理模板渲染、事件绑定、错误弹窗调试链路更长。到底选哪种不是看谁更流行而是看你能不能维护下去。4.2 拦截器实现登录态与角色过滤权限不能只靠页面藏按钮后端必须做拦截。SpringMVC 里最直接的是 HandlerInterceptorpublic class LoginInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { User loginUser (User) request.getSession().getAttribute(loginUser); if (loginUser null) { response.sendRedirect(request.getContextPath() /login); return false; } if (request.getRequestURI().startsWith(/admin/) loginUser.getRole() ! 1) { response.sendError(403, 无权限访问); return false; } return true; } }preHandle 在请求进入 Controller 之前跑。session 里没有登录用户就跳转登录页有用户但角色不满足就返回 403。这里要注意从 session 取出的 User 不要顺手修改里面的属性比如 loginUser.setRole(...)因为 session 里的对象是共享的改一次会影响后续所有请求的判断。角色值建议放到一个常量类里统一维护比如 RoleConstant.ROLE_ADMIN 1、ROLE_COACH 2不要在代码里到处写魔法数字。在 spring-mvc.xml 里配置拦截路径mvc:interceptors mvc:interceptor mvc:mapping path/admin/**/ mvc:exclude-mapping path/admin/login/ bean classcom.gym.interceptor.LoginInterceptor/ /mvc:interceptor /mvc:interceptorspath/admin/** 拦截 admin 目录下所有地址排除登录页本身。如果还要放行静态资源配合 default-servlet-handler 一起做。这里最常见的问题是拦截器只配置了路径没配资源放行结果登录页能打开但 CSS、JS 全被拦了页面光秃秃的。拦截器做的是粗粒度权限像“教练只能看自己的课表”这种数据级权限要在 Service 层再判断一次否则会越权拉取别人数据。4.3 前后端联调时最容易翻车的接口返回格式统一返回结构是联调不吵架的前提。我用的 Result 很简单public class Result { private int code; private String message; private Object data; public static Result ok(Object data) { Result r new Result(); r.code 200; r.message OK; r.data data; return r; } public static Result error(String message) { Result r new Result(); r.code 500; r.message message; return r; } }code 200 表示成功500 表示业务失败。还有一类字段级错误比如手机号已存在、课程已约满用同一个 500 返回即可前端读取 message 弹提示。真正容易翻车的是实体字段名不一致数据库列是 create_time实体属性写成 createTime 没问题但序列化给前端时字段名变成 createTime前端却用 createdAt 去拿页面就会显示空白。这类问题属于字段命名约定。我在做这个系统时会打开 MyBatis 的 mapUnderscoreToCamelCase让数据库下划线风格自动映射成实体驼峰前端再用驼峰字段一条链路上就不会再有别名差异。日期字段也要统一。为了让所有接口返回的日期格式一致我通常在配置里指定 Jackson 的日期格式和时区mvc:annotation-driven mvc:message-converters bean classorg.springframework.http.converter.json.MappingJackson2HttpMessageConverter property nameobjectMapper refjacksonObjectMapper/ /bean /mvc:message-converters /mvc:annotation-driven也可以直接用配置项 jackson.date-formatyyyy-MM-dd HH:mm:ss 和 jackson.time-zoneAsia/Shanghai。关键是让数据库、MyBatis、Jackson 三层的时区一致否则页面显示的签到时间会和实际差 8 小时。5. SSM 健身房管理系统常见排查与避坑现象、原因、解决这一章写给那些代码能启动、但总是在某些角落翻车的系统。下面五条是我在类似项目里反复见过的坑每条都按“现象、原因、解决”展开。如果你已经在调试过程中可以直接跳到对应小节不必按顺序读。5.1 数据库连不上报错总在最后一行现象项目启动正常但第一次访问登录接口就报 500控制台最后一行写着 Communications link failure有时候前面还有 Access denied for user。原因绝大多数情况不是框架的问题而是 JDBC URL、用户名密码、MySQL 驱动版本三者没对齐。错误堆栈里最重要的部分不是最上面那些异常是最后一段 Caused by那里才会写明真正原因。解决先在命令行确认数据库本身可以连接mysql -h127.0.0.1 -uroot -p能连上说明数据库服务正常。再看 MySQL 驱动版本和服务端版本是否匹配最后核对 jdbc.properties 里的 database 名是否存在。我见过一种隐蔽情况项目里多个 properties 文件都定义了 jdbc.url修改了其中一个Spring 加载的是另一个导致怎么改都连不上。排查时用全局搜索把 jdbc.url 的定义都找出来一次看清楚。5.2 中文乱码过滤器、页面编码、数据库字符集三方对齐现象数据库里存的会员名是“张伟”页面显示成乱码反过来页面输入正常存进数据库却变成问号。原因乱码基本都是字符集不一致。JSP 页面可能是 GBKJDBC URL 没带 characterEncodingutf8数据库表建成了 latin1这三方只要有两方不一致中文就会出错。只改任何一方都很难彻底解决因为脏数据可能已经写进库里了。解决分别在三个地方统一为 UTF-8。Spring 配置里加编码过滤器filter filter-nameencodingFilter/filter-name filter-classorg.springframework.web.filter.CharacterEncodingFilter/filter-class init-param param-nameencoding/param-name param-valueUTF-8/param-value /init-param init-param param-nameforceEncoding/param-name param-valuetrue/param-value /init-param /filter filter-mapping filter-nameencodingFilter/filter-name url-pattern/*/url-pattern /filter-mapping同时确认 JSP 页面声明 contentTypetext/html;charsetUTF-8JDBC URL 带 characterEncodingutf8数据库表统一 utf8mb4。这一步做完再重新写入乱码一般就消失了。历史脏数据需要用 SQL 脚本清洗而不是指望改了配置自动恢复。5.3 修改了 Mapper 却查不出数据缓存与命名空间现象明明把 SQL 从只查未删除数据改成查全部数据重启后第一次执行还是旧结果。有时候两个模块调用同一个查询返回结果却不一样。原因MyBatis 默认开启一级缓存同一个 SqlSession 内相同查询会复用结果如果之前配置了二级缓存影响范围更广。另一个常见原因是 Mapper XML 的 namespace 写错让方法名映射到了别人家的查询上。解决开发阶段把 MyBatis 缓存关掉settings setting namecacheEnabled valuefalse/ /settings再把 XML 里的 namespace 和对应接口全限定名逐一核对。改完 SQL 之后先清理 Maven target 目录再重启避免旧的 XML 还留在输出目录。如果需要手动清一级缓存可以通过 SqlSession 的 clearCache() 调用但更推荐的做法是开发期关闭缓存上线前再评估。课程表这种读多写少的数据可以开二级缓存会员预约、卡流水这种高频写的数据不建议开。5.4 日期时间差 8 小时时区问题的隐藏入口现象数据库存的是 20:00页面显示 12:00。或者接口返回的 createTime 和数据库看到的时间对不上总是差 8 小时。原因JDBC URL 没有设置 serverTimezone驱动用 JVM 默认时区解析或者 MySQL 服务端的 time_zone 是 UTC和东八区差 8 小时。解决连接串里明确写 serverTimezoneAsia/Shanghai并且在数据库执行下面的查询确认时区SELECT NOW(), global.time_zone, session.time_zone;如果结果是 SYSTEM再去检查操作系统时区。开发机最稳的方法就是连接串写死 Asia/Shanghai不要依赖服务器默认值。后端把 java.util.Date 序列化成 JSON 给前端时Jackson 也会按 JVM 时区输出所以 SpringMVC 配置里最好统一指定日期格式和时区避免第一个接口返回的日期和页面显示差 8 小时。5.5 静态资源 404SpringMVC 拦截了不该拦的路径现象登录页能打开但样式全丢控制台报 css、js 404。原因DispatcherServlet 的 url-pattern 映射成 /把所有请求都当成 Controller 路由。如果没配置静态资源处理器默认 Servlet 也不会生效css、js 就全 404 了。解决在 spring-mvc.xml 里加两行mvc:default-servlet-handler/ mvc:resources mapping/static/** location/static//这样 /static/** 下的文件会交给 Servlet 容器默认的静态资源处理器不会进入 Controller。页面里引用资源时要写绝对路径比如 /static/css/style.css不要用相对路径。还要检查项目目录结构看看静态文件是否真的在 webapp/static 下。如果项目是传统 WAR 包标准做法是把静态文件放在 src/main/webapp/static如果放在 src/main/resources 下面即使配置了映射也访问不到因为容器不会把它当作 webapp 根目录。6. 从“能跑”到“能答辩/能上线”验证方法与进阶改造一个系统做完后最大的风险不是没功能而是改着改着把核心流程改挂了。所以我最后都会用一行命令把核心接口回归一遍再做一两个不影响主流程的增强点。6.1 用 curl 回归核心接口curl -s -X POST http://localhost:8080/api/auth/login \ -H Content-Type: application/json \ -d {username:admin,password:123456}拿到登录成功响应后把这个请求的 Cookie 存下来再依次请求新增会员、查询会员列表、续费三个接口。验收标准就三条登录返回 code 为 200列表条数和数据库一致续费后 member 的 card_status 与 card_order 各多一条记录。这条链路能覆盖系统最核心的业务价值每次改动后跑一遍能挡住大部分低级回归。6.2 加一个操作日志模块如果想让项目在评审时更有说服力我给系统加一个操作日志表字段不用多字段含义operator_id操作人action操作类型target操作对象及 IDrequest_body请求参数摘要result成功/失败ip来源 IPcreated_at操作时间用 SpringMVC 拦截器或 AOP 在 Controller 层统一记录比在每个方法里手动写 Logger 要好维护。这个模块不依赖具体业务属于通用能力能体现你对系统可观测性的思考。6.3 改动前先备份数据库我自己的习惯是每次要动表结构或清测试数据之前先导出一份备份mysqldump -uroot -p gym gym_$(date %Y%m%d_%H%M%S).sql有一次为了加会员卡续费功能直接改了 card_order 表没备份就执行了删除列结果测试数据全部报废花了一整天才重新造出来。从那以后我再也不敢不备份就动表备份就是系统开发的后悔药。这个习惯比任何高级架构都值钱。希望帮到你。本文还有配套的精品资源点击获取
返回列表