Java游泳馆管理系统:Spring Boot 2.7实战项目

发布时间:2026/11/2 17:33:23

Java游泳馆管理系统:Spring Boot 2.7实战项目 简介这是一套面向高校数据库与Java课程设计初学者的游泳馆管理系统实战项目聚焦中小型场馆日常运营痛点提供会员管理、在线预约、场地调度与收费记账四大核心功能模块助力学生将ER建模、SQL设计、Java Swing/FX界面开发及事务处理等理论知识落地为可运行系统。资源包共1286个文件含28个核心Java源码文件如VIP.java、ManagerDao.java、29个编译后class文件、1132个htm页面构成完整前端界面辅以sql数据库脚本、css/js样式逻辑及gif/png界面资源整体3.81MB结构清晰、模块边界明确便于分层学习与调试。已有1537人学习下载读者可直接导入运行获取完整可执行工程、分模块源码注释、数据库表结构说明及典型业务场景如预约冲突校验、会员等级计费的实现逻辑是理解B/S或C/S架构下业务系统开发流程的优质实践样本。1. 游泳馆管理系统一个能当天部署、次日上线的 Java Web 实战项目专治「人多、场次乱、排班糊、账目不清」的中小型场馆运营痛点某游泳馆老板在微信里发来一张截图前台手写登记表密密麻麻教练排班靠微信群接龙会员续费靠Excel手动对账月底结账花了三天还漏了7个私教课时。这不是段子是真实踩坑现场。这个「游泳馆管理系统」就是为这类场景量身写的——它不堆高大上的微服务架构不用 Docker/K8s 做包装核心功能就四块会员全生命周期管理注册/充值/冻结/退卡、课程与场次智能排程含时段冲突校验、教练排班与课时统计自动关联课消与工资、收银财务流水日结报表支持现金/微信/支付宝三通道。技术栈干净利落Spring Boot 2.7 MyBatis-Plus Thymeleaf Bootstrap 5 H2开发默认 MySQL生产可切JDK 11 编译Maven 管理。整套源码包解压即跑连数据库初始化脚本都封装进src/main/resources/sql/下新手照着 README 里的三行命令就能看到登录页。它不是教学 Demo而是某高校实验室团队为本地三家社区泳馆落地迭代过 5 个版本的真实系统——你拿到手的是删掉客户定制模块、保留主干逻辑、加了完整注释和调试断点的「可交付精简版」。2. 环境准备与一键启动从 JDK 到浏览器登录页全程 12 分钟内完成2.1 开发环境最低要求与验证清单这个项目对环境极其友好但恰恰因为“太简单”新手最容易栽在 JDK 版本或 Maven 配置上。我建议你先执行这三步验证比直接mvn spring-boot:run更省时间提示不要用 JDK 17 或更高版本Spring Boot 2.7 官方仅支持到 JDK 11LTSJDK 17 会触发java.lang.NoSuchMethodError: javax.servlet.http.HttpServletRequest.getHttpServletMapping()这类玄学报错——这是 Spring Boot 2.x 和 Servlet 4.0 的兼容性黑匣子不是你代码的问题。# 1. 检查 JDK 版本必须输出 11.x java -version # 2. 检查 Maven3.6.3 即可推荐 3.8.6 mvn -v # 3. 检查本地是否已占用 8080 端口项目默认端口 netstat -ano | findstr :8080 # Windows lsof -i :8080 # macOS/Linux如果java -version输出的是17.0.1或21.0.2请立刻切换 JDK 11。IntelliJ IDEA 用户可在 Project Structure → Project → Project SDK 中下拉选择命令行用户需配置JAVA_HOME指向 JDK 11 根目录并确保PATH中java命令指向它。别信网上“加-Dspring.mvc.servlet.path/就能硬扛 JDK 17”的偏方——那是给 Spring Boot 3.x 准备的后悔药对本项目无效。2.2 数据库初始化H2 内存库开箱即用MySQL 切换只需改两行项目默认使用 H2 内存数据库好处是零安装、零配置适合快速验证业务逻辑。所有建表语句、初始数据如管理员账号admin/123456、测试会员、默认泳道信息都封装在src/main/resources/sql/schema-h2.sql和># 第一步清理旧编译产物尤其当你之前用错 JDK 编译过 mvn clean # 第二步编译并跳过测试测试用例未覆盖全部跳过不影响功能验证 mvn compile -Dmaven.test.skiptrue # 第三步启动默认激活 h2 配置 mvn spring-boot:run等待控制台输出类似Tomcat started on port(s): 8080 (http)后在浏览器打开http://localhost:8080/login。你应该看到一个蓝白配色、带泳镜图标的登录页。输入默认账号admin/123456点击登录——如果跳转到「仪表盘」页面顶部显示「欢迎回来管理员」且左侧菜单栏有「会员管理」「课程排程」「教练排班」「财务管理」四个主项恭喜你的系统已活了。注意首次登录后系统会自动跳转至/dashboard该页面调用DashboardController.index()它会查询今日预约数、未处理订单、教练出勤率三个聚合指标。如果页面空白或报 500 错大概率是数据库连接失败请回看 2.2 节检查application.yml配置。3. 核心模块拆解从会员注册到课时核销一条请求的完整链路3.1 会员管理模块注册、充值、状态机驱动的生命周期控制会员是整个系统的起点。MemberController是入口但真正决定业务边界的是MemberService中的状态机设计。它没用复杂的状态模式框架而是用枚举 switch 实现轻量级状态流转// src/main/java/com/swim/system/service/impl/MemberServiceImpl.java public boolean recharge(Long memberId, BigDecimal amount) { Member member memberMapper.selectById(memberId); if (member null || !MemberStatus.ACTIVE.equals(member.getStatus())) { throw new BusinessException(会员不存在或已冻结); } // 关键余额不能为负且单次充值上限 5000 元防误操作 if (amount.compareTo(BigDecimal.ZERO) 0 || amount.compareTo(new BigDecimal(5000)) 0) { throw new BusinessException(充值金额需大于0且不超过5000元); } BigDecimal newBalance member.getBalance().add(amount); member.setBalance(newBalance); member.setUpdatedAt(LocalDateTime.now()); return memberMapper.updateById(member) 0; }逻辑说明member.getStatus()返回MemberStatus枚举包含ACTIVE正常、FROZEN冻结、CLOSED销户三种状态recharge()方法强制校验状态冻结会员无法充值这是业务强约束金额校验用compareTo()而非避免 BigDecimal 精度陷阱updatedAt字段由代码显式更新而非依赖数据库ON UPDATE CURRENT_TIMESTAMP保证 Java 层可审计。参数说明memberId路径变量来自PathVariable(id)前端路由/member/recharge/{id}amount请求体 JSON 字段{ amount: 300.00 }经RequestBody绑定抛出BusinessException会被全局异常处理器GlobalExceptionHandler捕获返回统一格式{ code: 400, msg: 充值金额需大于0且不超过5000元, data: null }。3.2 课程排程模块时段冲突检测与泳道资源锁定「课程排程」是高频并发场景。用户选「7月10日 19:00-20:00 儿童蛙泳班」时系统必须同时校验① 该时段该泳道是否已被其他课程占用② 教练在该时段是否有空档③ 班级人数是否超限。核心逻辑在CourseScheduleService.checkConflict()// src/main/java/com/swim/system/service/impl/CourseScheduleServiceImpl.java public boolean checkConflict(LocalDateTime start, LocalDateTime end, Long poolLaneId, Long coachId) { // 1. 查该泳道在 [start, end) 区间内是否有其他课程 QueryWrapperCourseSchedule laneQw new QueryWrapper(); laneQw.eq(pool_lane_id, poolLaneId) .ge(start_time, start) .lt(end_time, end) .ne(status, ScheduleStatus.CANCELLED); // 忽略已取消的排程 if (courseScheduleMapper.selectCount(laneQw) 0) { return false; // 冲突 } // 2. 查该教练在 [start, end) 区间内是否有其他课程含待确认 QueryWrapperCourseSchedule coachQw new QueryWrapper(); coachQw.eq(coach_id, coachId) .ge(start_time, start) .lt(end_time, end) .ne(status, ScheduleStatus.CANCELLED); return courseScheduleMapper.selectCount(coachQw) 0; }逻辑说明使用ge和lt构建左闭右开区间[start, end)这是时间冲突检测的标准做法避免19:00-20:00和20:00-21:00被误判为冲突ne(status, ScheduleStatus.CANCELLED)过滤掉已取消的排程它们不占用资源两次selectCount()是原子操作但未加数据库锁——因为本系统预设并发量 50 QPS用应用层校验 乐观锁version字段足够若你接入高并发场馆需在insert时加SELECT ... FOR UPDATE。参数说明start/endLocalDateTime类型来自前端传入的字符串2024-07-10T19:00经 Spring MVC 自动转换poolLaneId泳道 ID对应pool_lane表主键coachId教练 ID来自coach表。3.3 教练排班模块课时统计与自动出勤标记教练端最关心「我今天上了几节课多少钱」。CoachAttendanceService每日凌晨 2 点触发一次定时任务扫描昨日所有statusCONFIRMED的课程生成出勤记录// src/main/java/com/swim/system/service/impl/CoachAttendanceServiceImpl.java Scheduled(cron 0 0 2 * * ?) // 每天凌晨2点执行 public void generateYesterdayAttendance() { LocalDate yesterday LocalDate.now().minusDays(1); LocalDateTime start yesterday.atStartOfDay(); LocalDateTime end yesterday.plusDays(1).atStartOfDay(); // 查询昨日所有已确认课程 QueryWrapperCourseSchedule qw new QueryWrapper(); qw.between(start_time, start, end) .eq(status, ScheduleStatus.CONFIRMED); ListCourseSchedule confirmedCourses courseScheduleMapper.selectList(qw); for (CourseSchedule course : confirmedCourses) { // 检查是否已存在出勤记录防重复执行 if (attendanceMapper.selectOne( new QueryWrapperCoachAttendance() .eq(coach_id, course.getCoachId()) .eq(schedule_id, course.getId()) ) null) { CoachAttendance attendance new CoachAttendance(); attendance.setCoachId(course.getCoachId()); attendance.setScheduleId(course.getId()); attendance.setDate(yesterday); attendance.setStatus(AttendanceStatus.PRESENT); attendance.setCreatedAt(LocalDateTime.now()); attendanceMapper.insert(attendance); } } }逻辑说明Scheduled注解启用 Spring 定时任务无需 Quartzbetween查询昨日 00:00:00 到今日 00:00:00 的课程覆盖所有跨午夜的晚班插入前selectOne检查是典型的「先查后插」防重逻辑比INSERT IGNORE更可控AttendanceStatus.PRESENT是默认状态教练可登录后手动改为ABSENT或LEAVE。参数说明yesterdayLocalDate用于生成date字段日期类型非时间戳schedule_id关联course_schedule表实现课时与出勤强绑定createdAt记录系统生成时间用于审计。4. 避坑指南那些让我重启三次 IDE 才定位到的典型问题4.1 现象登录页 CSS 加载失败页面纯白无样式原因Thymeleaf 默认开启缓存spring.thymeleaf.cachetrue开发时修改templates/layout.html后未重启浏览器仍加载旧版本导致link th:href{/css/app.css}解析为 404。解决在application-dev.yml中添加spring.thymeleaf.cachefalse并确保启动时激活devprofilemvn spring-boot:run -Dspring.profiles.activedev。4.2 现象会员充值成功但余额未更新数据库字段仍为 NULL原因Member实体类中balance字段用了BigDecimal但数据库member表对应列为DECIMAL(10,2)而 MyBatis-Plus 默认的 TypeHandler 对null值处理异常——当balance为null时updateById()会忽略该字段不执行SET balance ?。解决在Member实体类的balance字段上加TableField(updateStrategy FieldStrategy.NOT_NULL)强制非空才更新或初始化时设private BigDecimal balance BigDecimal.ZERO;。4.3 现象课程排程保存时报org.hibernate.exception.ConstraintViolationException原因course_schedule表有联合唯一索引uk_pool_coach_time泳道教练开始时间但前端传参时start_time是字符串2024-07-10 19:00后端未做格式化MyBatis 插入时因时区或精度问题导致索引失效。解决在CourseScheduleController.save()中用DateTimeFormatter.ofPattern(yyyy-MM-dd HH:mm)显式解析字符串并存为LocalDateTime同时在application.yml中加spring.jackson.date-formatyyyy-MM-dd HH:mm统一 JSON 时间格式。4.4 现象H2 控制台能连但http://localhost:8080/h2-console页面空白原因Spring Boot 2.7 默认禁用 H2 控制台的 Web 资源spring.h2.console.settings.web-allow-othersfalse且 Chrome 98 对localhost的SameSite策略更严格。解决在application-h2.yml中添加spring.h2.console.settings.web-allow-otherstrue并确保访问地址是http://localhost:8080/h2-console不能少/。4.5 现象MySQL 切换后member表中文名显示为?原因MySQL 服务端字符集非utf8mb4或 JDBC URL 缺少characterEncodingutf8mb4serverTimezoneAsia/Shanghai参数。解决检查 MySQL 配置my.cnf中collation-server utf8mb4_unicode_ci和init-connectSET NAMES utf8mb4JDBC URL 改为jdbc:mysql://localhost:3306/swimdb?useUnicodetruecharacterEncodingutf8mb4serverTimezoneAsia/Shanghai。5. 二次开发实战如何快速接入微信支付并生成对账单 PDF5.1 微信支付接入替换收银模块三步嵌入现有流程原系统收银只支持现金和模拟支付。要接入微信支付你不需要重写整个PaymentController只需聚焦三个点统一下单、回调验签、状态同步。第一步在pom.xml引入微信官方 SDK推荐com.github.wechatpay-apiv3!-- 微信支付 v3 SDK -- dependency groupIdcom.github.wechatpay-apiv3/groupId artifactIdwechatpay-apache-httpclient/artifactId version0.4.9/version /dependency第二步创建WechatPayService封装统一下单逻辑。关键在于构造HttpClient时注入平台证书// src/main/java/com/swim/system/service/WechatPayService.java public String createOrder(Long orderId, BigDecimal amount, String description) { // 1. 构建 HttpClient需提前加载平台证书 PrivateKey merchantPrivateKey PemUtil.loadPrivateKey( new FileInputStream(cert/apiclient_key.pem)); AutoUpdateCertificatesVerifier verifier new AutoUpdateCertificatesVerifier( new WechatPay2Credentials(your_mch_id, new PrivateKeySigner(your_serial_no, merchantPrivateKey)), new FileInputStream(cert/wechatpay_cert.pem)); CloseableHttpClient httpClient WechatPayHttpClientBuilder.create() .withMerchant(your_mch_id, your_serial_no, merchantPrivateKey) .withValidator(new WechatPay2Validator(verifier)) .build(); // 2. 构造下单请求体 JsonObject json new JsonObject(); json.addProperty(appid, wx1234567890abcdef); json.addProperty(mchid, your_mch_id); json.addProperty(description, description); json.addProperty(out_trade_no, SWIM orderId); json.addProperty(time_expire, LocalDateTime.now().plusMinutes(15).format(DateTimeFormatter.ISO_LOCAL_DATE_TIME)); json.add(amount, new JsonObject().addProperty(total, amount.multiply(new BigDecimal(100)).longValue()).addProperty(currency, CNY)); json.add(payer, new JsonObject().addProperty(openid, oABC1234567890abcdef)); // 3. 发起 POST 请求 HttpPost httpPost new HttpPost(https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi); httpPost.setEntity(new StringEntity(json.toString(), UTF-8)); httpPost.setHeader(Accept, application/json); try (CloseableHttpResponse response httpClient.execute(httpPost)) { String body EntityUtils.toString(response.getEntity()); JsonObject result JsonParser.parseString(body).getAsJsonObject(); return result.getAsJsonObject(prepay_id).get(prepay_id).getAsString(); // 返回 prepay_id 供前端调起支付 } }逻辑说明AutoUpdateCertificatesVerifier自动轮询更新微信平台证书避免手动更新time_expire设为 15 分钟符合微信支付超时规则amount.total必须为分单位整数所以amount.multiply(100).longValue()返回prepay_id前端调用wx.requestPayment()时传入。第三步在PaymentController.pay()中根据支付方式路由PostMapping(/pay) public Result pay(RequestBody PaymentRequest request) { if (wechat.equals(request.getPayType())) { String prepayId wechatPayService.createOrder(request.getOrderId(), request.getAmount(), 游泳馆课程支付); return Result.success(Map.of(prepayId, prepayId)); } else if (cash.equals(request.getPayType())) { // 原有现金逻辑 } return Result.fail(不支持的支付方式); }5.2 对账单 PDF 生成用 iText7 替代 JasperReports轻量又可控原系统财务报表只有 HTML 表格。要导出 PDF 对账单我弃用重量级 JasperReports改用 iText7 —— 因为它的 API 直接操作 PDF 元素调试方便且能精准控制每行高度、字体、边框。在FinancialReportService中新增方法// src/main/java/com/swim/system/service/FinancialReportService.java public byte[] generateDailyReport(LocalDate date) throws IOException { ByteArrayOutputStream baos new ByteArrayOutputStream(); PdfWriter writer new PdfWriter(baos); PdfDocument pdfDoc new PdfDocument(writer); Document document new Document(pdfDoc); // 设置中文字体需提前下载 simsun.ttc PdfFont font PdfFontFactory.createFont(fonts/simsun.ttc,0, PdfEncodings.IDENTITY_H); document.setFont(font).setFontSize(10f); // 标题 document.add(new Paragraph(游泳馆日结对账单 - date).setBold().setFontSize(14f).setTextAlignment(TextAlignment.CENTER)); // 表格列宽按内容比例分配 Table table new Table(UnitValue.createPercentArray(new float[]{1, 2, 1, 1, 1})); table.setWidth(UnitValue.createPercentValue(100)); table.addHeaderCell(new Cell().add(new Paragraph(序号).setBold())); table.addHeaderCell(new Cell().add(new Paragraph(会员姓名).setBold())); table.addHeaderCell(new Cell().add(new Paragraph(消费项目).setBold())); table.addHeaderCell(new Cell().add(new Paragraph(金额(元)).setBold())); table.addHeaderCell(new Cell().add(new Paragraph(支付方式).setBold())); // 查询当日流水简化版实际应分页 ListFinancialRecord records financialRecordMapper.selectList( new QueryWrapperFinancialRecord().eq(date, date) ); for (int i 0; i records.size(); i) { FinancialRecord r records.get(i); table.addCell(new Cell().add(new Paragraph(String.valueOf(i 1)))); table.addCell(new Cell().add(new Paragraph(r.getMemberName()))); table.addCell(new Cell().add(new Paragraph(r.getItem()))); table.addCell(new Cell().add(new Paragraph(r.getAmount().toString()))); table.addCell(new Cell().add(new Paragraph(r.getPayType()))); } document.add(table); document.close(); return baos.toByteArray(); }逻辑说明PdfFontFactory.createFont()加载宋体解决中文乱码Table构造时用PercentArray动态分配列宽比固定像素更适应不同长度文本document.add()直接追加元素无模板引擎学习成本返回byte[]Controller 中用ResponseEntitybyte[]直接响应前端可window.open(URL.createObjectURL(blob))预览。参数说明dateLocalDate来自RequestParam(date)FinancialRecord财务流水实体含memberName、item如“儿童蛙泳班 1节”、amount、payTypefonts/simsun.ttc需将字体文件放入src/main/resources/fonts/目录。从那以后我每次接入第三方支付都强制走一遍「沙箱环境全流程」从下单 → 支付 → 回调 → 状态更新 → 通知会员哪怕只是模拟也要让日志里出现完整的SUCCESS链路。因为线上环境一旦漏掉验签或异步通知钱就真进了黑洞。希望帮到你。本文还有配套的精品资源点击获取
延伸阅读

更多相关文章

2026/11/2 17:33:23

Jev框架解析:类型安全的LLM交互与API Key管理实践

1. 从一个让人摸不着头脑的名字说起第一次看到“Jev”这三个字母,我脑子里蹦出来的第一反应是某个开源库的缩写,或者是某位开发者的ID。直到我在几个技术群里反复看到有人问“jev模型官网在哪”“jev密钥怎么申请”“jev在codex里怎么用”,才…

2026/11/2 17:33:23

悬挂指针与野指针全解析:从use-after-free排查到防御性编码

1. 悬挂指针不是"野指针":先把概念拆开揉碎我见过不少初学者把这两个词混着用,结果在排查问题时方向就偏了。悬挂指针(dangling pointer)指的是那块内存曾经属于你、你确实拥有过它、后来你把它还回去了,但指…

2026/11/2 17:33:23

TreeSizeFree:DBA必备的磁盘空间分析利器

1. 磁盘空间告急:DBA运维的日常痛点凌晨三点,服务器告警短信又一次把我从睡梦中惊醒。磁盘空间不足的红色警告在监控大屏上闪烁,而距离早高峰业务流量到来只剩四个小时。这已经是本月第三次了——我熟练地SSH登录服务器,开始逐层执…

2026/11/2 18:53:59

Scala Array操作:从定长数组到ArrayBuffer的完整指南

Scala的Array就是Java数组的封装,但Scala给它装上了集合操作的"超能力"。索引访问、map/filter、与List互转——让数组处理从Java的for循环中解放出来。 如果你从Java转到Scala,第一次用数组时可能会有疑问:Scala的Array和Java的数…

2026/11/2 18:53:59

MonoGame 跨平台游戏开发:30 分钟写出一个能动的方块

MonoGame 跨平台游戏开发:30 分钟写出一个能动的方块 【免费下载链接】MonoGame One framework for creating powerful cross-platform games. 项目地址: https://gitcode.com/gh_mirrors/mo/MonoGame 你手上有一个很小的创意:一个方块在窗口里移…

2026/11/2 18:53:59

低压电工作业实操考试题库-2026 年版-低压电工实操考试

概要本文档主要说明低压电工实操考试内容和要求,涵盖考试科目构成、考试规则与评分标准,帮助考生系统了解考试全貌并做好备考准备。详细说明文件,请查看顶部附件。科目说明实操考试包括4个科目,分别为:科目一 安全用具使用(包含4道试题);科目二 作业现场安全隐患排除(…

2026/11/2 18:48:53

迅雷怎么加快下载速度_迅雷在线解析工具

迅雷限速怎么破解这个很简单,这个方法我还是在我朋友那里找到的。下载速度也是非常可以的。我让大家看一下。点我打开方法 这个就是我测试的速度。速度基本能跑到10M左右。宽带问题。下面开始今天的教学环节 打开上面图片中的地址,你会看到一个获取文件列…

2026/11/2 6:08:01

面向复杂光照与角度的宠物面部识别鲁棒性增强策略

在智慧养宠时代,基于视觉的宠物身份识别技术正逐步从实验室走向家庭应用。然而,与配合度高的人类不同,宠物是典型的“非合作生物识别对象”——它们不会主动配合摄像头调整姿态,更无法控制光照环境。实际应用中,我们常…

2026/11/2 6:39:12

SSM电子书店源码全解析:结构、原理、部署与排错实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/11/2 1:04:04

工业级SPI隔离设计:硬件抗扰与信号完整性实战指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/11/2 1:04:04

PLFM_RADAR:主动探测数据通道,构建低误报的数据感知系统

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/11/2 1:04:04

工业SPI隔离设计:MAX14431与STM32F746硬件抗干扰实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/11/2 6:08:01

面向复杂光照与角度的宠物面部识别鲁棒性增强策略

在智慧养宠时代,基于视觉的宠物身份识别技术正逐步从实验室走向家庭应用。然而,与配合度高的人类不同,宠物是典型的“非合作生物识别对象”——它们不会主动配合摄像头调整姿态,更无法控制光照环境。实际应用中,我们常…

2026/11/2 6:39:12

SSM电子书店源码全解析:结构、原理、部署与排错实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑