尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
SSM框架实战:在线投票系统开发与部署全流程解析
简介这是一套基于SSMSpringSpringMVCMyBatis框架的在线投票系统Java毕业设计源码面向计算机相关专业学生和Java入门开发者可直接作为毕业设计或课程设计的完整参考项目。系统前台包含用户注册登录、发起自定义投票题目、选项、单选/多选均可配置、投票主题收藏、在线投票及结果图表统计并设有固定IP不可重复投票校验后台支持管理员管理、注册用户审核、投票主题与内容管理、系统配置更新配套完整的在线留言及回复功能。压缩包共914个文件除Java源码、JSP页面、SQL数据库脚本外还包含CSS/JS前端资源、JAR依赖包、GIF演示录屏和图片素材整体大小约39MB说明文档便于快速启动。目前已有80人学习下载适合需要掌握SSM项目分层结构、理解投票业务逻辑并能快速二次扩展的开发者。1. 在线投票系统这个选题为什么每年都有人做但做好的人不多java毕业设计里“在线投票系统”是出现率极高的题目。它看起来就是一个增删改查但真正拿源码去跑、去答辩很多人会卡在同一个地方项目能启动可一投票就出空指针或者候选人明明被选中统计结果里还是 0。这套方案基于 SSMSpring Spring MVC MyBatis MySQL 前端静态页面适合想用 java 毕业设计源码完整走一遍开发流程的人也适合帮别人改系统的同学。判断一套投票系统好不好不看页面多花哨而看投票是否防重复、统计是否准确、说明文档能不能带你三分钟跑起来。这篇笔记就从这三个点展开。2. 把 SSM 结构拆开看投票系统的分层、表设计和请求走向SSM 这组框架组合在 java 毕业设计里出现得太频繁早几年面试也总被问。它把项目强制分成了 Controller、Service、Mapper 三层好处是一个人维护代码时知道每个类该放哪。投票系统虽然业务不大但三层一点都不能少否则后面加需求会越改越乱。2.1 三层架构与一次投票请求的完整生命周期一次投票请求的路径大概是这样的前端页面把投票数据通过 AJAX 提交到 ControllerController 只做参数接收和路由真正判断“这个主题是否还在投票期”“这个用户是否已经投过”的逻辑放在 Service 里Service 再调用 Mapper 接口Mapper 对应的 XML 里写 SQL最后把结果返回到前端。很多人省略 Service 层直接在 Controller 里写逻辑那会带来一个问题两个入口都要投票时重复判断代码要复制两次事务也难圈住。用一张表把一次投票的完整请求链路列出来步骤所在层做的事情1前端校验必填项禁用按钮发起 AJAX POST2Controller接收 candidateId 和 topicId调 Service3Service校验主题状态、校验是否重复、执行插入4Mapper执行 insert/update SQL提交事务5Controller把结果包装成 JSON 返回看起来简单但每一步都有细节Controller 不能直接依赖 JDBC 代码否则事务会失效Service 里要加 Transactional保证“插入投票记录”和“更新候选人票数”一起成功或一起回滚。这些在 java 面试题里也经常被拿来问事务的默认传播行为答案就在这个场景里。2.2 数据库设计五张表如何支撑“一次投票”的最小闭环在线投票系统最少需要五张表用户表登录表、投票主题表、候选人表、投票记录表、管理员表。有些方案会加“用户每日投票记录”之类的明细表但核心还是投票记录表因为它同时承载防重复和统计两个关键职责。下面这段建表 SQL 是常见做法我一般会把字符集直接指定成 utf8mb4避免中文乱码CREATE DATABASE IF NOT EXISTS vote_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE vote_db; CREATE TABLE vote_topic ( id INT PRIMARY KEY AUTO_INCREMENT, title VARCHAR(100) NOT NULL, start_time DATETIME NOT NULL, end_time DATETIME NOT NULL, status TINYINT DEFAULT 1 COMMENT 1进行中 0已结束 ); CREATE TABLE candidate ( id INT PRIMARY KEY AUTO_INCREMENT, topic_id INT NOT NULL, name VARCHAR(50) NOT NULL, description VARCHAR(255) ); CREATE TABLE vote_record ( id INT PRIMARY KEY AUTO_INCREMENT, topic_id INT NOT NULL, candidate_id INT NOT NULL, user_id INT DEFAULT NULL, ip VARCHAR(64) DEFAULT NULL, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uk_topic_user (topic_id, user_id), UNIQUE KEY uk_topic_ip (topic_id, ip) );这段 SQL 里的唯一索引是关键uk_topic_user 保证同一个用户在同一主题下只能存在一条投票记录uk_topic_ip 是给匿名投票用的防重复手段。注意 ip 相同会导致同一个 NAT 出口下的用户全都不能投所以更稳的方案是把 user_id 作为第一道防线ip 只做参考。候选人表通过 topic_id 关联主题表投票记录表又用 candidate_id 关联候选人表三张表组成一次投票的数据闭环。这里有几个参数容易踩topic_id 不要用 varchar用 int 且建索引vote_record 的 create_time 用 DEFAULT CURRENT_TIMESTAMP插入时不用手动传时间避免服务器与数据库时间不一致。如果你用的是 MySQL 5.7DATETIME 不要写超出默认值的复杂表达式否则建表会报错。这些细节在 mysql 安装配置教程里往往不会写但实际开发中很常见。我这里没有加外键约束毕业设计阶段用逻辑关联替代物理外键插入时少一层校验导出数据也更快。但在 Service 的插入顺序上要手动保证先有主题、再有候选人、才能产生投票记录。2.3 前端与后端的约定接口先行的协作方式拿到源码后第一件事不是看页面而是找后端 Controller 里的接口定义。常见做法是前端页面和后端约定好下面这套接口接口方法参数返回/vote/listGET无JSON 数组投票主题列表/vote/detailGETtopicIdJSON 对象候选人和截止时间/vote/submitPOSTtopicId, candidateIdJSON{code:0,msg:投票成功}/vote/resultGETtopicIdJSON按票数排序的结果前端页面里所有请求都走这套接口。约定好接口之后就算后面想换 Vue 或小程序只需要保留接口不变页面重写就行。这里给一个最小调用示例fetch(/vote/vote/list) .then(res res.json()) .then(data { if (data.code 0) { const topics data.data || []; topics.forEach(topic { console.log(topic.title); }); } });这段代码里的 code 是应用层状态码0 表示成功和 HTTP 状态码分开避免歧义。如果前后端项目部署在不同的端口这个 fetch 还会触发跨域需要在 SpringMVC 配置里加 CORS 过滤但本项目是 SSM 自带的 JSP 页面同源访问可以晚点再处理。熟悉完结构后再去改配置就不会在一堆 XML 里迷路。下一章我们直接从 zip 包跑到浏览器里。3. 从 zip 到本地跑通导入、配置与启动的全过程拿到 zip 包后不要急着双击打开先解压到一个没有中文和空格的路径比如 D:\workspace\vote-system。很多 mysql 安装在中文路径下都会产生奇奇怪怪的问题java 项目同理。3.1 解压后先搞清楚包里有什么一个标准的 java 毕业设计源码包通常包含这些东西src 目录Java 源码、webapp 或 WebContent前端和 JSP、sql 目录数据库脚本、说明文档docx 或 PDF、pom.xmlMaven 依赖声明。SSM 项目最直接的特征是 pom.xml 里躺着 spring-webmvc、mybatis、mysql-connector-java 三个依赖。如果用 tree 命令看结构大致如下vote-system/ ├── pom.xml ├── README.txt ├── sql/ │ └── vote_db.sql └── src/ ├── main/ │ ├── java/com/example/vote/ │ │ ├── controller/ VoteController.java │ │ ├── service/ VoteService.java │ │ ├── mapper/ VoteMapper.java │ │ └── entity/ VoteTopic.java │ ├── resources/ │ │ ├── jdbc.properties │ │ ├── mybatis-config.xml │ │ └── mapper/VoteMapper.xml └── webapp/ ├── WEB-INF/web.xml ├── index.jsp └── static/ ├── css/ └── js/这个结构中最容易被忽略的是 resources 下的 mapper 目录。如果 MyBatis 没有扫描到这个目录项目启动不报错但所有 SQL 会在运行时报“Invalid bound statement”。所以导入前先确认 applicationContext.xml 里的 mapperLocations 是不是写了 classpath:mapper/*.xml。3.2 数据库初始化用 MySQL 命令行把建表脚本跑干净数据库初始化我建议用命令行而不是图形工具因为命令行能直接看到编码和报错信息。先登录 MySQLmysql -u root -p登录后如果脚本里已经写了 CREATE DATABASE可以直接执行mysql -u root -p D:/workspace/vote-system/sql/vote_db.sql注意 Windows 的 cmd 执行这个命令时路径不能带中文也不能用反斜杠结尾否则会提示文件找不到。更直观的是登录后用 sourcemysql -u root -p vote_db source D:/workspace/vote-system/sql/vote_db.sql;执行完后用 SHOW TABLES 验证一下至少能看到那五张表。如果出现 ERROR 1064多半是脚本里有 MySQL 5.7 不支持的语法比如 utf8mb4_0900_ai_ci 是 MySQL 8 专属排序规则换成 utf8mb4_general_ci 就兼容了。3.3 修改 SSM 配置文件里的四项关键参数SSM 项目连接数据库的配置一般集中在 src/main/resources/jdbc.properties 里。你需要把四项参数改成自己本机的jdbc.drivercom.mysql.cj.jdbc.Driver jdbc.urljdbc:mysql://localhost:3306/vote_db?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai jdbc.usernameroot jdbc.password123456这里很容易忽略的是 serverTimezone。MySQL 8 驱动没配它会报 “The server time zone value” 运行时错误。如果你的 MySQL 是 5.7驱动也可以统一用 com.mysql.cj.jdbc.Driver向下兼容。改完 jdbc.properties 再检查 Spring 配置文件里的 mapperLocationsbean idsqlSessionFactory classorg.mybatis.spring.SqlSessionFactoryBean property namedataSource refdataSource/ property namemapperLocations valueclasspath:mapper/*.xml/ /bean如果这里配成 classpath:mapper/ 而不是 classpath:mapper/*.xml启动时也能加载但运行到对应方法时就找不到 SQL属于延迟踩坑。配置完一定要确认通配符写全。3.4 启动 Tomcat 后如何验证系统真的活了SSM 项目最常见的启动方式是直接在 IDEA 里配置一个 Tomcat 运行。点 Run 后看到 Console 输出 “Server startup in xx ms” 就说明容器起来了。如果这个源码包带了 Maven也可以直接用命令启动内置 Tomcatmvn tomcat7:run这个命令会读取 pom.xml 里的插件配置默认端口 8080。启动成功后访问 http://localhost:8080/vote/index.jsp。如果你的项目名不是 vote把 URL 里的上下文路径换成 war 包名。启动失败时先看两个地方一是端口是否被占用报 Port 8080 was already in use 就换一个二是看有没有空指针空指针十有八九是 Spring 配置里的 bean 没扫到包。把 Controller 所在包和 springmvc 配置里 context:component-scan 的 base-package 对齐问题就解决了。这里提到的 mysql 安装配置细节其实比很多人想象的重要得多。数据库连不上时先 ping 3306 端口再检查用户名密码最后才怀疑代码。4. 核心代码怎么改投票、统计和防重复提交的实现细节跑通之后如果只是看页面很难判断这个系统是“真货”还是“假货”。真正的区分点在后端三段代码。4.1 投票动作的后端 Service事务和状态判断放哪里很多毕设源码的做法是 Controller 里直接调 Mapper 插一条投票记录。这种写法能用但有三个坑没检查主题有效期、没检查重复投票、插入和更新票数不在同一个事务里。正确位置在 Service用 Transactional 圈住整个逻辑Transactional(rollbackFor Exception.class) public VoteResult submitVote(Integer topicId, Integer candidateId, Integer userId, String ip) { VoteTopic topic voteMapper.findTopicById(topicId); if (topic null || topic.getStatus() 0) { return VoteResult.error(1001, 投票主题不存在或已结束); } if (voteMapper.countByTopicAndUser(topicId, userId) 0) { return VoteResult.error(1002, 你已经投过票了); } VoteRecord record new VoteRecord(); record.setTopicId(topicId); record.setCandidateId(candidateId); record.setUserId(userId); record.setIp(ip); voteMapper.insertRecord(record); voteMapper.increaseCandidateCount(candidateId); return VoteResult.ok(); }这段代码里有两个关键参数Transactional 默认只在 RuntimeException 上回滚rollbackFor Exception.class 才能把 SQL 异常也圈进事务。candidateId 的合法性校验也建议放这里因为接口可以被绕过前端直接 POST抓包刷票在答辩时是很致命的问题后端校验是底线。一个隐蔽的坑是如果系统允许匿名投票userId 是 nullMySQL 唯一索引对 NULL 不生效同一个 IP 可以插入多条记录。所以要么把 userId 的 null 统一转成 0要么在投票记录表上建 (topic_id, ip) 的唯一索引两者至少占一个。4.2 候选人排序和结果统计用一条 SQL 而不是 Java 循环统计结果时最常见的翻车写法是 Java 遍历候选人列表再对每个候选人执行一次 count 查询。N 个候选人就是 N1 次查询数据量变大后接口响应会明显变慢。正确写法是用 SQL 的 GROUP BY 一次算完select idgetVoteResult resultTypemap SELECT c.id AS candidateId, c.name AS candidateName, COUNT(r.id) AS voteCount FROM candidate c LEFT JOIN vote_record r ON c.id r.candidate_id AND r.topic_id #{topicId} WHERE c.topic_id #{topicId} GROUP BY c.id, c.name ORDER BY voteCount DESC, c.id ASC /select这里用 LEFT JOIN 而不是 INNER JOIN能把没人选的候选人票数显示为 0而不是直接消失。ORDER BY 后面加的 c.id ASC 是给票数相同的情况一个稳定排序避免每次刷新顺序乱跳。Java 端接收时直接用 ListMapString, Object前端拿到后渲染柱状图或百分比都很方便。如果你发现统计结果里有重复计数多半是投票记录表里没有唯一索引Service 校验被并发绕过。真正的可靠防线是数据库层的唯一索引这一点在第 2 章已经埋了伏笔。回去给投票记录表加两个唯一键比在 Service 里写十行 if 都管用。另外给 vote_record 的 topic_id 建普通索引GROUP BY 的查询也能快不少。4.3 前端倒计时与表单校验控制投票节奏的两个小函数前端通常需要两个函数一个控制按钮提交后置灰防止连点一个在倒计时结束时重新开放。这两个都属于体验层但如果只做前端限制后端没做仍然会被刷票。function startCountdown(seconds) { let remain seconds; const timer setInterval(() { if (remain 0) { clearInterval(timer); document.getElementById(voteBtn).disabled false; document.getElementById(voteBtn).innerText 投票; } else { document.getElementById(voteBtn).disabled true; document.getElementById(voteBtn).innerText remain s; remain--; } }, 1000); } function submitVote(topicId, candidateId) { if (!candidateId) { alert(请先选择候选人); return; } fetch(/vote/vote/submit, { method: POST, headers: { Content-Type: application/json }, body: JSON.stringify({ topicId: topicId, candidateId: candidateId }) }).then(res res.json()).then(data { if (data.code 0) { startCountdown(30); loadResult(topicId); } else { alert(data.msg); } }); }这段 JS 里有两个细节fetch 的路径是 /vote/vote/submit第一个 vote 是项目上下文路径第二个 vote 是 Controller 的 RequestMapping两个同名概念经常让人看混。JSON.stringify 提交后后端 Controller 必须用 RequestBody 接收如果后端用的还是 RequestParam数据解析就会失败。所以前后端要提前约定用 JSON 还是 form 表单。另外倒计时的秒数最好由后端接口返回不要写死在前端。用户改本地电脑时间纯前端倒计时就会失效前端的角色只是展示真正的截止时间判断在 Service 里。5. 踩坑与排查从空指针到中文乱码的 5 条血泪经验这部分帮你排雷。笔者当年做类似项目时踩过的真实问题每一条都按“现象→原因→解决”展开。5.1 现象投票成功后列表没变化刷新才出来原因投票完成后前端通过 AJAX 重新加载结果浏览器把 GET 请求结果缓存了第二次拿到的还是旧数据。部分源码的 result 接口返回数据里没有时间戳更加剧了缓存误判。解决在 result 请求地址后面加时间戳参数或在 jQuery 请求里设置 cache:false。$.ajax({ url: /vote/vote/result?topicId topicId t Date.now(), method: GET, cache: false, success: function (data) { renderChart(data); } });这只解决展示问题真正要确认数据写进去了还是要查数据库。如果用的是 fetch可以再加一层 Cache-Control 头后端 Controller 返回响应头 no-cache。5.2 现象Tomcat 启动报 ClassNotFoundException但代码里明明有原因IDEA 里项目依赖没有打进 WEB-INF/lib编译通过但运行时容器找不到类。Maven 项目里依赖如果不小心标了 provided 作用域也会出现这种问题。解决检查项目结构里的 Artifacts把缺少的 library 添加到 WEB-INF/lib。Maven 项目优先用 IDEA 的 Maven 面板执行 clean package然后使用生成的 war 包部署这样依赖最完整。mvn clean package -DskipTests如何确认问题出在这打开 Tomcat 运行后解压出的 WEB-INF/lib 目录如果里面没有 spring-webmvc.jar那基本就是依赖没进去。这个经验在处理任何 java 后端项目时都通用。5.3 现象插入数据库的中文全是问号原因三层不一致。数据库连接串没有 characterEncoding表字符集不是 utf8mb4前端页面 meta 没指定 charset。MySQL 8 默认字符集虽然是 utf8mb4但 JDBC 连接串不指定Java 传来的 String 仍可能被转坏。解决把 jdbc.url 补上 useUnicodetruecharacterEncodingutf8同时建库语句用 DEFAULT CHARACTER SET utf8mb4。JSP 页面顶部加 % page contentTypetext/html;charsetUTF-8 %三者缺一不可。这个坑的诡异之处在于英文正常只有中文出问题。排查时先别急着改 Java 代码用 mysql 命令行直接执行一条中文 INSERT如果命令行也乱码问题不在 Java 端而是字符集配置。另外Spring 的 CharacterEncodingFilter 如果没配置POST 请求的中文也会乱记得检查 web.xml。5.4 现象同一个 IP 换个浏览器又能投了原因前端用 localStorage 或 Cookie 记录“已投票”状态这只是本机限制。换浏览器、清 Cookie、换手机都等于没投过。有些源码的 Service 根本没查投票记录表Mapper 里也没有唯一约束所以多投也能插入。解决在后端 Service 的 submitVote 里先用 userId 查重再用 ip 兜底。匿名投票至少要有 (topic_id, ip) 唯一索引插入重复时数据库直接报 DuplicateKeyExceptionService 捕获后返回友好提示。try { voteMapper.insertRecord(record); } catch (DuplicateKeyException e) { return VoteResult.error(1002, 该 IP 已经投过票); }先确认唯一索引有没有建上SHOW INDEX FROM vote_record;。校园网同一个出口 IP 下会有误伤所以更合理的做法是登录后投票索引改回 (topic_id, user_id)ip 只记录不限制。5.5 现象导出 war 包后 JS 报 404原因开发时前端路径写的是 /static/js/xxx.js运行在 IDEA 上下文路径 / 时能访问打成 war 包后部署到 Tomcat上下文路径变成 /vote绝对路径就找不到了。解决把前端所有静态资源访问路径改成带上下文路径的形式。JSP 里用 ${pageContext.request.contextPath}link href${pageContext.request.contextPath}/static/css/style.css relstylesheet script src${pageContext.request.contextPath}/static/js/vote.js/script如果前后端已经分离404 要检查 Nginx 的静态资源 alias 配置。但纯 SSM 项目通常不涉及先检查上下文路径即可。6. 答辩和二次开发给评委演示的三种验证方法与一个提分技巧6.1 给评委演示的三步验证答辩现场最怕功能演示时出意外。我一般提前做三遍验证。第一遍功能验证按“投票→看结果→换账号投票看是否被拦”的顺序走重点演示防重复。第二遍并发验证用 Apache Bench 同时发 50 个请求观察会不会插入多条记录ab -n 50 -c 10 -p post_data.txt -T application/json http://localhost:8080/vote/vote/submit第三遍数据验证执行 SELECT COUNT(*) FROM vote_record WHERE topic_id1与页面显示的总票数对比。评委问“你怎么证明没有刷票”这三步可以回答大半。6.2 一个提分技巧把“匿名投票”改成“登录后投票”匿名投票很难做出亮点加一个登录拦截器就能把题目提升到“有一定业务深度”。做法是拦截器检查 session 里有没有登录用户再在投票表 user_id 上建唯一索引。这一步代码量不大但答辩时能讲出“为什么要登录、session 存活时间怎么设、并发下唯一索引如何兜底”三个问题足够撑场。给一个拦截器最小实现public class LoginInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { Object user request.getSession().getAttribute(loginUser); if (user null) { response.sendRedirect(request.getContextPath() /login.jsp); return false; } return true; } }建议再做两件小事把 jdbc.properties 的密码改成环境变量注入把投票记录表的唯一索引单独写一个 SQL 文件。这些细节在老师眼里都是加分项。我当年做这个题目时偏偏把统计写在 Java 里循环现场数据一多页面就转圈后来改成 GROUP BY 一次查询才解决。希望帮到你。本文还有配套的精品资源点击获取
RELATED

相关推荐

t3code轻量编码实践:从零搭建极简项目与避坑指南

t3code轻量编码实践:从零搭建极简项目与避坑指南

1. 从“t3code”这个名字说起:它到底是什么,能解决什么问题第一次看到“t3code”这个标题,很多人会愣一下——这到底是个工具、一个项目代号,还是一套编码规范?我最初接触它的时候也有同样的疑问。简单来说&#xff0c…

📅 2026/10/27 23:13:54
CSS Grid布局:现代Web二维布局的核心技术

CSS Grid布局:现代Web二维布局的核心技术

1. CSS网格布局:现代界面设计的基石CSS Grid Layout(网格布局)是近年来最令人兴奋的CSS模块之一,它彻底改变了我们构建Web界面的方式。作为一名长期奋战在前端开发一线的工程师,我亲历了从表格布局到浮动布局&#xff…

📅 2026/10/27 23:13:54
基于Cloudflare Workers与MCP构建主动型Slack AI机器人实战

基于Cloudflare Workers与MCP构建主动型Slack AI机器人实战

1. 为什么“会主动干活的 AI”才是团队协作的下一站Slack 里塞一个 AI 机器人,这件事本身不新鲜。真正让我眼前一亮的是company-brain这个项目的定位——它不是那种你问一句它答一句的“问答机”,而是一个会主动在频道里干活、主动整理信息、主动把散落在…

📅 2026/10/27 23:08:53
MORE NEWS

更多资讯

📰

深度学习训练数据集加载避坑指南:从解压到DataLoader的完整流程

简介:这份深度学习训练数据集面向图像分类与目标检测方向的开发者、学生及研究者,提供可直接用于模型训练与验证的标注图像资源,帮助解决入门与实验阶段缺少真实样本的问题。压缩包共305个文件,以300张jpg图像为主体,另…

📰

剑网三团长语音合成:讯飞TTS接入与指令队列实战

简介:这是一款面向剑网三玩家、尤其是团队副本团长与指挥新手的语音合成辅助工具,基于讯飞TTS技术,将文本指令自动转换为团长指挥语音并播放,帮助不擅长口头表达的玩家也能顺畅完成副本指挥,提升团队协作效率与游戏体验…

📰

Debian系统下Ollama的完整安装与优化指南

1. Debian系统下Ollama的完整安装指南在开源大模型工具链中,Ollama以其轻量化和易用性成为开发者本地部署AI模型的首选方案。作为长期使用Debian进行AI开发的从业者,我将在本教程中详细演示如何在Debian系统上完成Ollama的完整部署,特别针对国…

📰

水稻病害识别:基于传统机器学习的轻量级落地方案

简介:本资源是一套基于Python机器学习技术实现的水稻病虫害自动识别系统源码,面向农业信息化开发者、计算机视觉初学者及智慧农业项目实践者,旨在解决田间图像采集后病虫害种类快速判别与模型部署的实际问题。压缩包共310个文件,含…

📰

超帧(Hyperframes)解决视频时间一致性问题的原理与实战

做视频处理这几年,我一度是个彻头彻尾的“逐帧主义者”——把每一帧当成独立的图片去修、去增强、去插帧。直到有一次接了个老胶片修复项目,单帧输出张张完美,一播放却像迪斯科灯球一样疯狂闪烁,客户直接发来一句“你是不是用了什…

📰

OpenShell复盘:让终端听懂人话,自动生成Shell命令

写这篇OpenShell复盘之前,我先说点实在的:如果你手里每天都在做重复性文件操作、日志分析、数据统计这类“含键盘量过高”的活儿,那么OpenShell这类工具就是给这些破事按下的暂停键。它不是一个能让你真正“躺着”写完程序的玩具,…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬