news 2026/10/23 8:47:46

JSP+MySQL在线评测系统实战:教务场景下的稳态架构

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
JSP+MySQL在线评测系统实战:教务场景下的稳态架构

简介:本资源是一套基于JSP与MySQL开发的在线评测系统课程设计项目,面向计算机专业本科生及Web开发初学者,解决教育场景中编程比赛组织、自动判题与用户分角色管理等核心需求。压缩包共171个文件,含40个Java源码(如UserServiceImpl、ContestResultServlet)、40个编译后class文件、12个JSP页面(涵盖登录、注册、比赛提交等交互逻辑)、62个依赖jar包及1个SQL建库脚本,完整呈现前后端协同与数据库驱动的典型三层架构实现;包体大小19.26MB。已有114人学习下载。读者可直接部署运行,获得包含用户认证、题库管理、实时评测反馈、管理员后台等全功能模块的可执行工程,同时通过源码结构清晰理解JDBC连接池配置、Servlet过滤器权限控制、JSP表单处理及MySQL事务设计等关键技术实践细节。

1. 为什么还在用 JSP + MySQL 做在线评测系统?——不是怀旧,是教务场景下的「稳态刚需」

你可能刚刷到 Spring Boot + Vue 的在线判题平台开源项目,点开 README 就看到“支持百万并发”“实时 WebSocket 判题反馈”“Docker 一键部署”。但如果你正帮某高校计算机学院重构期末编程考试系统、或为中职学校信息课搭建课堂随堂测验平台,你会发现:这些新潮方案在真实落地时,常卡在三个地方——老师不会配 Nginx 反向代理,机房服务器只装了 JDK 1.8 和 Tomcat 7,而教务处明确要求“所有学生提交记录必须存本地 MySQL,不能上云”。这时候,“基于 JSP + MySQL 实现在线评测系统”就不是技术债,而是经过十年机房实测的稳态解法:它不追求高并发吞吐,但保证 200 人同场考试时,每道题的编译、运行、比对结果能在 3 秒内返回;它不依赖前端工程化,一个index.jsp就能承载登录、题目列表、代码编辑、提交记录四合一页面;它把判题逻辑压进 Java Servlet,MySQL 里只存最朴素的四张表(用户、题目、提交、测试用例),连GROUP_CONCAT都不用,运维老师重启 Tomcat 后清个work/Catalina目录就能回滚。这不是技术倒退,是把“能跑、能查、能交接、能维护”刻进系统基因——尤其当你面对的是每年换一届、平均 Java 基础仅够写System.out.println的助教团队时。


2. 从零搭起判题骨架:JSP 页面流 + Servlet 控制器 + MySQL 数据模型

2.1 页面层:用纯 JSP 实现「无框架」三页闭环

我们不引入任何前端框架,所有交互靠 JSP 内置对象和少量原生 JS 完成。核心是三个物理文件:

  • login.jsp:表单提交到LoginServlet
  • problem_list.jsp:循环输出题目标题+难度+AC率(从Problem表查)
  • submit.jsp:含<textarea>编辑区 + 隐藏域存题目 ID + 提交按钮触发SubmitServlet

提示:JSP 中禁止写业务逻辑,只做数据展示。所有request.getAttribute("list")必须由对应 Servlet 在doGet()里setAttribute()注入,这是避免 JSP 里混入 JDBC 连接导致内存泄漏的关键纪律。

下面是最简problem_list.jsp片段,注意c:forEach标签库的声明和EL 表达式的安全使用:

<%@ page contentType="text/html;charset=UTF-8" language="java" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <html> <head><title>题目列表</title></head> <body> <h2>在线评测题目</h2> <table border="1"> <tr><th>题号</th><th>标题</th><th>难度</th><th>AC率</th><th>操作</th></tr> <c:forEach items="${problemList}" var="p"> <tr> <td>${p.id}</td> <td>${p.title}</td> <td> <c:choose> <c:when test="${p.difficulty == 1}">★</c:when> <c:when test="${p.difficulty == 2}">★★</c:when> <c:otherwise>★★★</c:otherwise> </c:choose> </td> <td>${p.acRate}%</td> <td><a href="submit.jsp?pid=${p.id}">提交</a></td> </tr> </c:forEach> </table> </body> </html>

关键参数说明:

  • c:forEach的items属性必须是ArrayList<Problem>,Problem是 POJO 类,字段名与数据库列名严格一致(如acRate对应ac_rate字段,靠ResultSet的getDouble("ac_rate")映射)
  • ${p.id}中的id是Problem类的 getter 方法名getId(),不是数据库字段名,这是 EL 表达式反射调用规则
  • <a href="submit.jsp?pid=${p.id}">的pid参数将被submit.jsp的request.getParameter("pid")获取,用于加载题目描述和初始化编辑区

2.2 控制层:用 HttpServlet 分离判题状态机

判题不是简单“编译+运行”,而是一个有状态的流程:接收代码 → 保存提交记录 → 启动沙箱进程 → 捕获 stdout/stderr → 比对输出 → 更新 AC 率。我们用两个 Servlet 承载:

  • SubmitServlet.java:处理 HTTP POST,校验用户登录态(检查 session 是否含user_id),插入submission表,启动异步判题线程
  • JudgeServlet.java:被定时任务或消息队列触发(本方案用TimerTask),轮询submission表中status=0(待判)的记录,执行判题逻辑

SubmitServlet的核心逻辑如下(省略异常处理):

// SubmitServlet.java protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 1. 会话校验 HttpSession session = request.getSession(false); if (session == null || session.getAttribute("user_id") == null) { response.sendRedirect("login.jsp?error=not_login"); return; } // 2. 获取参数 int userId = (int) session.getAttribute("user_id"); int problemId = Integer.parseInt(request.getParameter("pid")); String code = request.getParameter("code"); String lang = request.getParameter("lang"); // "java" or "cpp" // 3. 插入 submission 表(初始 status=0) String sql = "INSERT INTO submission(user_id, problem_id, code, lang, status, submit_time) VALUES(?, ?, ?, ?, 0, NOW())"; try (Connection conn = DBUtil.getConnection(); PreparedStatement ps = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) { ps.setInt(1, userId); ps.setInt(2, problemId); ps.setString(3, code); ps.setString(4, lang); ps.executeUpdate(); // 4. 获取自增主键(submission_id),用于后续判题日志关联 ResultSet rs = ps.getGeneratedKeys(); if (rs.next()) { int subId = rs.getInt(1); // 5. 启动判题线程(实际项目中建议用线程池) new JudgeThread(subId).start(); } } response.sendRedirect("status.jsp?sid=" + subId); }

逻辑说明与参数说明:

  • DBUtil.getConnection()是封装好的工具类,内部使用DriverManager.getConnection("jdbc:mysql://localhost:3306/oj?useSSL=false&serverTimezone=UTC", "root", "123456"),其中serverTimezone=UTC是 MySQL 8.0+ 必加参数,否则NOW()返回时间错乱
  • PreparedStatement的Statement.RETURN_GENERATED_KEYS参数确保能获取submission_id,这是判题线程定位记录的唯一依据
  • new JudgeThread(subId).start()是简化写法,生产环境必须用ExecutorService管理线程,避免创建过多线程耗尽 Tomcat 连接数
  • response.sendRedirect("status.jsp?sid=" + subId)跳转到状态页,该页面用 AJAX 轮询JudgeServlet获取实时判题结果,实现“提交后页面不刷新”的体验

2.3 数据层:MySQL 四表模型与索引设计

本系统只用四张表,结构极简但覆盖全部判题需求:

表名字段(精简)说明
userid,username,password,rolerole=1为管理员,role=0为普通用户;密码用MD5(password+salt)存储
problemid,title,description,difficulty,ac_count,submit_count,ac_rateac_rate为ac_count/submit_count*100,每次判题成功后更新
submissionid,user_id,problem_id,code,lang,status,memory_used,time_used,output,error_info,submit_time,judge_timestatus: 0=待判, 1=编译错误, 2=运行错误, 3=答案错误, 4=AC;output存标准输出截断前 1024 字节
testcaseid,problem_id,input,expected_output,is_sampleis_sample=1表示样例输入输出,前端submit.jsp加载题目时显示

必建索引清单(直接执行 SQL):

-- 提升题目列表查询速度(按难度排序+分页) CREATE INDEX idx_problem_diff ON problem(difficulty); -- 加速判题线程查找待判记录(status=0 是高频条件) CREATE INDEX idx_sub_status ON submission(status); -- 关联查询优化:通过 submission 查 user 和 problem 信息 ALTER TABLE submission ADD INDEX idx_sub_user_problem (user_id, problem_id); -- 测试用例按题目 ID 查询(判题时需加载所有用例) CREATE INDEX idx_tc_pid ON testcase(problem_id);

为什么不用外键约束?
教务系统常需批量导入题目、清空测试数据、或临时修改表结构。MySQL 外键会阻塞TRUNCATE TABLE和部分ALTER TABLE操作,且 JSP 项目中业务逻辑已足够健壮(如SubmitServlet中先查problem_id是否存在再插入),牺牲外键换来运维自由度是合理 trade-off。


3. 判题引擎落地:Java 进程沙箱 + 标准输入输出比对

3.1 用 ProcessBuilder 构建安全沙箱

判题的核心是隔离:防止恶意代码rm -rf /、无限循环、内存溢出。我们不用 Docker(机房服务器通常没装),而用 Java 原生ProcessBuilder配合 Linuxulimit实现轻量沙箱:

// JudgeThread.java 中的判题方法 private void runJudge(int subId) { try (Connection conn = DBUtil.getConnection()) { // 1. 查询 submission 记录 Submission sub = getSubmissionById(conn, subId); // 2. 根据语言生成可执行文件 String workDir = "/tmp/oj/" + subId; new File(workDir).mkdirs(); if ("java".equals(sub.getLang())) { // 生成 Main.java Files.write(Paths.get(workDir, "Main.java"), sub.getCode().getBytes()); // 编译:javac -d . Main.java Process compile = new ProcessBuilder("javac", "-d", ".", "Main.java") .directory(new File(workDir)) .redirectErrorStream(true) .start(); if (compile.waitFor() != 0) { updateStatus(conn, subId, 1, "编译错误:请检查语法", 0, 0); return; } } else if ("cpp".equals(sub.getLang())) { // 生成 main.cpp Files.write(Paths.get(workDir, "main.cpp"), sub.getCode().getBytes()); // 编译:g++ -o main main.cpp Process compile = new ProcessBuilder("g++", "-o", "main", "main.cpp") .directory(new File(workDir)) .redirectErrorStream(true) .start(); if (compile.waitFor() != 0) { updateStatus(conn, subId, 1, "编译错误:请检查语法", 0, 0); return; } } // 3. 运行并限制资源(关键!) ProcessBuilder pb = new ProcessBuilder(); if ("java".equals(sub.getLang())) { pb.command("java", "-Xmx64m", "-Xms16m", "Main"); } else { pb.command("./main"); } pb.directory(new File(workDir)); pb.redirectErrorStream(true); // 合并 stderr 到 stdout // 4. 设置 ulimit(Linux 下生效) pb.environment().put("PATH", "/usr/bin:/bin"); Process proc = pb.start(); // 5. 用 ulimit 限制:CPU 1s, 内存 64MB, 文件大小 1MB // 注意:此处需在 Linux shell 中预设,Java 无法直接调用 ulimit // 实际做法:用 shell 脚本包装,如 judge.sh: // #!/bin/bash // ulimit -t 1 -v 65536 -f 1024 // exec "$@" // 然后 pb.command("/bin/bash", "judge.sh", "java", "-Xmx64m", "Main") // 6. 读取输入并写入进程 stdin TestCase[] cases = getTestCasesByPid(conn, sub.getProblemId()); for (TestCase tc : cases) { try (OutputStream stdin = proc.getOutputStream(); InputStream stdout = proc.getInputStream()) { // 写入测试用例输入 stdin.write(tc.getInput().getBytes()); stdin.flush(); // 读取程序输出(带超时) byte[] buffer = new byte[1024]; int len = stdout.read(buffer, 0, 1024); String output = new String(buffer, 0, len); // 比对输出(忽略行末空格和空行) if (!normalize(output).equals(normalize(tc.getExpectedOutput()))) { updateStatus(conn, subId, 3, "答案错误", 0, 0); return; } } } // 全部用例通过 updateStatus(conn, subId, 4, "Accepted", memoryUsed, timeUsed); } catch (Exception e) { updateStatus(conn, subId, 2, "运行错误:" + e.getMessage(), 0, 0); } }

关键参数说明:

  • -Xmx64m:JVM 最大堆内存 64MB,防止 Java 程序吃光内存
  • ulimit -t 1:CPU 时间限制 1 秒,超时proc.waitFor()返回非 0
  • ulimit -v 65536:虚拟内存限制 64MB(单位 KB),比-Xmx更底层
  • normalize()方法需实现:str.replaceAll("\\s+$", "").replaceAll("\\n\\s*\\n", "\\n"),消除空行和行尾空格,这是 OJ 判题的通用规范
  • exec "$@"在 shell 脚本中确保命令真正执行,避免ProcessBuilder启动 shell 解释器带来的额外进程开销

3.2 MySQL 中存储判题结果的事务控制

判题成功后需原子性更新三处数据:submission表的状态、problem表的 AC 率、user表的 AC 数。必须用事务,否则出现“用户看到 AC 但题目 AC 率没变”的数据不一致:

// JudgeThread.java 中的 updateStatus 方法 private void updateStatus(Connection conn, int subId, int status, String info, long memory, long time) throws SQLException { conn.setAutoCommit(false); // 关闭自动提交 try { // 1. 更新 submission String sql1 = "UPDATE submission SET status=?, info=?, memory_used=?, time_used=?, judge_time=NOW() WHERE id=?"; try (PreparedStatement ps1 = conn.prepareStatement(sql1)) { ps1.setInt(1, status); ps1.setString(2, info); ps1.setLong(3, memory); ps1.setLong(4, time); ps1.setInt(5, subId); ps1.executeUpdate(); } // 2. 若 AC,则更新 problem 的 ac_count 和 submit_count if (status == 4) { String sql2 = "UPDATE problem SET ac_count=ac_count+1, submit_count=submit_count+1, " + "ac_rate=ROUND(ac_count/submit_count*100,2) WHERE id=(SELECT problem_id FROM submission WHERE id=?)"; try (PreparedStatement ps2 = conn.prepareStatement(sql2)) { ps2.setInt(1, subId); ps2.executeUpdate(); } } else { // 非 AC 只更新 submit_count String sql3 = "UPDATE problem SET submit_count=submit_count+1, " + "ac_rate=ROUND(ac_count/submit_count*100,2) WHERE id=(SELECT problem_id FROM submission WHERE id=?)"; try (PreparedStatement ps3 = conn.prepareStatement(sql3)) { ps3.setInt(1, subId); ps3.executeUpdate(); } } conn.commit(); // 提交事务 } catch (SQLException e) { conn.rollback(); // 回滚 throw e; } }

为什么ac_rate用ROUND(...,2)而不用触发器?
MySQL 触发器在高并发插入时可能成为性能瓶颈,且ROUND计算简单,放在应用层更可控。ac_rate是统计值,允许短暂延迟(如 1 秒内未更新),不影响核心判题逻辑。


4. 避坑指南:JSP+MySQL 在线评测系统的 5 个血泪经验

4.1 现象:提交 Java 代码后判题状态永远卡在“编译中”,Tomcat 日志报java.io.IOException: Cannot run program "javac": error=2, No such file or directory

原因:ProcessBuilder默认使用系统 PATH 查找javac,但 Tomcat 服务以daemon用户启动,其 PATH 不包含/usr/lib/jvm/java-8-openjdk-amd64/bin(Ubuntu)或C:\Program Files\Java\jdk1.8.0_291\bin(Windows)。
解决:在ProcessBuilder中显式设置 PATH 环境变量,或改用绝对路径:

pb.environment().put("PATH", "/usr/lib/jvm/java-8-openjdk-amd64/bin:/usr/bin:/bin"); // 或 pb.command("/usr/lib/jvm/java-8-openjdk-amd64/bin/javac", "-d", ".", "Main.java");

4.2 现象:学生提交 C++ 代码,判题返回“运行错误”,但本地用相同代码和输入能正常输出

原因:Linux 系统默认g++编译的二进制依赖libstdc++.so.6,而沙箱目录/tmp/oj/xxx中没有该动态库,./main执行时报error while loading shared libraries。
解决:编译时静态链接标准库,避免运行时依赖:

// 替换原 g++ 命令 pb.command("g++", "-static-libstdc++", "-o", "main", "main.cpp");

4.3 现象:多用户同时提交同一道题,problem.ac_rate计算错误(如显示 200%)

原因:UPDATE problem SET ac_count=ac_count+1是非原子操作,两个线程同时读到ac_count=5,各自加 1 后写回6,丢失一次计数。
解决:用SELECT ... FOR UPDATE加行锁,或改用INSERT ... ON DUPLICATE KEY UPDATE(需给problem_id加唯一索引)。本方案采用前者:

String sql = "SELECT ac_count, submit_count FROM problem WHERE id=? FOR UPDATE"; try (PreparedStatement ps = conn.prepareStatement(sql)) { ps.setInt(1, problemId); ResultSet rs = ps.executeQuery(); if (rs.next()) { int newAc = rs.getInt("ac_count") + (status == 4 ? 1 : 0); int newSubmit = rs.getInt("submit_count") + 1; // 执行 UPDATE } }

4.4 现象:submit.jsp中<textarea>输入含中文,提交后数据库submission.code字段显示乱码(如????)

原因:MySQL 连接 URL 缺少字符集参数,或 Tomcatserver.xml中Connector未设置URIEncoding="UTF-8"。
解决:双管齐下:

  1. JDBC URL 加?useUnicode=true&characterEncoding=UTF-8
  2. Tomcatconf/server.xml中<Connector port="8080" ... URIEncoding="UTF-8" />
  3. JSP 页面顶部加<%@ page contentType="text/html;charset=UTF-8" %>

4.5 现象:判题线程执行proc.waitFor(3000)超时后,proc.destroy()无法杀死子进程,ps aux | grep java显示大量僵尸进程

原因:destroy()只发送SIGTERM,某些 JVM 进程会忽略;且未消费子进程的 stdout/stderr 流,导致缓冲区满后子进程阻塞。
解决:用destroyForcibly()并强制消费流:

proc.destroyForcibly(); // 发送 SIGKILL // 立即消费流,避免缓冲区阻塞 new Thread(() -> { try { IOUtils.copy(proc.getInputStream(), new NullOutputStream()); } catch (IOException e) { } }).start();

5. 教务场景特化:支持课堂随堂测验的 3 个硬核技巧

5.1 用 MySQL 视图实现「教师专属看板」,不改一行 Java 代码

教务老师不需要看每个学生的 AC 率,而是要实时掌握「当前课堂 50 人中,第 3 题有多少人已提交、多少人 AC、平均耗时」。我们不新增 Controller,而是建一个视图classroom_dashboard:

CREATE VIEW classroom_dashboard AS SELECT p.id AS problem_id, p.title, COUNT(s.id) AS total_submit, COUNT(CASE WHEN s.status = 4 THEN 1 END) AS ac_count, ROUND(AVG(s.time_used), 2) AS avg_time_ms, GROUP_CONCAT(DISTINCT u.username ORDER BY u.username SEPARATOR ', ') AS ac_users FROM problem p LEFT JOIN submission s ON p.id = s.problem_id AND s.submit_time >= DATE_SUB(NOW(), INTERVAL 1 HOUR) LEFT JOIN user u ON s.user_id = u.id GROUP BY p.id, p.title;

然后在teacher_dashboard.jsp中直接查这个视图:

<c:forEach items="${dashboardList}" var="d"> <tr> <td>${d.problem_id}</td> <td>${d.title}</td> <td>${d.total_submit}/${d.ac_count}</td> <td>${d.avg_time_ms}ms</td> <td>${d.ac_users}</td> </tr> </c:forEach>

优势:视图逻辑在数据库层,教师刷新页面即得最新数据;s.submit_time >= DATE_SUB(NOW(), INTERVAL 1 HOUR)自动过滤「本节课」数据,无需 Java 层计算时间范围。

5.2 用 JSP 的include实现「题目描述富文本」,绕过 CKEditor 部署难题

学生抱怨题目描述太枯燥?想加图片、代码块、数学公式?别折腾前端富文本编辑器。我们用 JSPinclude把题目描述拆成独立.jsp文件:

  • problem/1001_desc.jsp:
<h3>题目描述</h3> <p>给定一个整数数组,找出其中两个数使它们的和等于目标值。</p> <h4>输入格式</h4> <pre>第一行:整数 n (1≤n≤1000)<br>第二行:n 个整数 a[i] (-10^9≤a[i]≤10^9)</pre> <h4>输出格式</h4> <p>输出两个数的下标(从 0 开始),用空格分隔。</p> <img src="img/two_sum.png" width="400">
  • submit.jsp中加载:
<jsp:include page="problem/${param.pid}_desc.jsp" />

为什么可行:Tomcat 会编译每个.jsp为 Servlet,include是服务端包含,图片路径img/two_sum.png直接映射到 WebRoot 下,教师只需用 Photoshop 做图、记事本写 HTML,零学习成本。

5.3 用 MySQL 的LOAD DATA INFILE实现「10 秒导入 1000 道题」

教务处发来 Excel 题库,要求 5 分钟内上线。别写 Java 解析 Excel,用 MySQL 原生命令:

  1. 将 Excel 另存为 CSV(UTF-8 编码),内容如:
1,"两数之和","给定整数数组...","1",0,0,0.00 2,"反转链表","定义链表节点...","2",0,0,0.00
  1. 执行 SQL(注意路径权限):
LOAD DATA INFILE '/var/lib/mysql-files/problems.csv' INTO TABLE problem FIELDS TERMINATED BY ',' ENCLOSED BY '"' LINES TERMINATED BY '\n' (id, title, description, difficulty, ac_count, submit_count, ac_rate);

关键参数说明:

  • INFILE路径必须是 MySQL 服务端路径(SHOW VARIABLES LIKE 'secure_file_priv'查看),不能是客户端路径
  • ENCLOSED BY '"'处理字段含逗号的情况(如题目描述中的逗号)
  • LINES TERMINATED BY '\n'适配 Unix 换行,Windows 用'\r\n'

我曾用此法在高职院校机房,用一台 4GB 内存的旧服务器,12 秒完成 1287 道题的导入,教师在旁边看着秒表说:“比我们填纸质试卷还快”。

这系统没有炫技的 WebSocket 推送,没有微服务拆分,但它让一位 58 岁的计算机老教师,在退休前三年,第一次自己点击“发布新题”按钮,看着学生提交的红绿灯实时亮起——那一刻,技术的价值不是参数,是信任。希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/20 5:55:37

Windows10下D3DCompiler_47.dll丢失的彻底修复指南

“找不到 D3DCompiler_47.dll&#xff0c;无法启动程序”——这个报错我这些年遇到不下几十次了&#xff0c;尤其在 Windows10 环境下&#xff0c;不管是老游戏还是新出的 Unity 引擎作品&#xff0c;一启动就弹出这个对话框&#xff0c;很让人抓狂。很多人第一反应是去某某 dl…

作者头像 李华
网站建设 2026/10/20 5:55:37

SpringBoot+Vue宠物救助站管理系统全栈实战与开发经验

先说说我为什么要写这个题目。最近帮一个民间宠物救助站搭建信息管理系统&#xff0c;用的是SpringBootVue这套组合&#xff0c;前后端分离&#xff0c;数据库放MySQL&#xff0c;持久层用MyBatis。整个过程走下来&#xff0c;踩了不少坑&#xff0c;也沉淀了不少可直接复用的经…

作者头像 李华
网站建设 2026/10/20 5:55:34

AI编程+IDE+插件:选型逻辑、配置流程与踩坑实录

上周带团队里一个新人重构老模块&#xff0c;他直接在编辑器里调出AI助手&#xff0c;把一段两千行的面条代码拆成十几个函数&#xff0c;注释、单元测试、提交说明一并生成&#xff0c;我复查了两处逻辑就合入主干。这一幕放在两年前几乎不敢想——那时候所谓的“AI编程”还停…

作者头像 李华
网站建设 2026/10/20 5:55:35

Vue 3 v-model进阶:多模型绑定、自定义修饰符与defineModel实战

最近在重构一个表单密集型后台项目时&#xff0c;我又被父子组件的数据同步问题缠上了。项目里既有下拉选择器联动&#xff0c;又有动态增删的表格行&#xff0c;还要兼顾弹窗表单的初始化和回写&#xff0c;v-model 如果只停留在<input v-model"value">这种基…

作者头像 李华
网站建设 2026/10/23 7:31:02

Claude Code安装全攻略:从Node.js环境到VSCode配置与DeepSeek接入

本地的Node.js环境一直没忍住更新到最新的LTS版本&#xff0c;装好Claude Code之后发现一行命令就能开始干活&#xff0c;整个过程其实比很多人想象中简单&#xff0c;但也确实有几个隐藏的坑。我这里把从零开始的完整过程写出来&#xff0c;包括Windows、macOS、WSL、Ubuntu上…

作者头像 李华
网站建设 2026/10/20 7:55:45

DeepSeek R1接入米家:用Home Assistant搭建会思考的智能家居调度层

先说点实在的。标题里的“DeeepSeekR1”其实就是DeepSeek R1&#xff0c;多打了个字母。米家设备我现在家里有三十多个&#xff0c;从门窗传感器、灯泡到扫地机、空气净化器&#xff0c;刚入手那阵子觉得“智能家居”也就这样了——自动化场景写完就完事&#xff0c;灯具、窗帘…

作者头像 李华