news 2026/10/1 19:17:25

JSP+Servlet蛋糕店实战系统:从零掌握JavaWeb四层架构

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
JSP+Servlet蛋糕店实战系统:从零掌握JavaWeb四层架构

简介:这是一份面向计算机相关专业在校生、教师及初学者的JavaWeb课程设计与毕业设计实战资源,基于JSP+Servlet技术栈实现完整的蛋糕店在线售卖系统,覆盖用户管理、商品展示、购物车与订单支付等核心电商功能。资源包共290个文件,包含58个JSP页面(实现前后端交互与视图渲染)、55个Java类(含Servlet控制器与业务逻辑)、20个XML配置文件(如web.xml与数据库连接配置)、14个JS与12个CSS文件(支撑Bootstrap+Layer前端框架的响应式交互),以及82张界面截图与9个运行依赖JAR包,整体18.95MB,结构清晰、模块完整。已有182人学习下载,所有代码均通过本地Tomcat环境实测运行,答辩平均分96分,附带详细README说明文档与SQL建表脚本,可直接部署学习,亦支持在现有基础上拓展会员系统、后台管理或支付对接等功能。

1. 这不是“老古董演示”,而是一套能跑通、能改、能答辩的 JSP+Servlet 蛋糕店实战系统:含完整前后端交互链路、Bootstrap 响应式 UI 和可落地的 MySQL 数据库设计

你可能刚在头歌实训里敲完out.print("Hello World"),也可能正被 IDEA 里红得发亮的javax.servlet.ServletException报错卡住——别急,这套《基于 JSP 和 Servlet 的蛋糕店售卖网站》不是网上随手搜的“空壳模板”,它是一份真实跑通在 Tomcat 8.5+、MySQL 5.7+ 环境下的毕设级工程:首页轮播图自动切换、商品按“生日蛋糕/奶油蛋糕/慕斯蛋糕”三级分类加载、购物车实时增删改数量、用户登录态跨页面保持、订单生成后跳转模拟支付页……所有功能都经过答辩现场实机演示验证,平均分 96 分。它不依赖 Spring Boot 自动装配黑匣子,而是用最原始的HttpServlet生命周期(init → service → destroy)、request.getSession()管理会话、JDBC DriverManager直连数据库——这意味着你能看清每一行请求怎么进来、数据怎么查、HTML 怎么拼、Session 怎么存。适合计科/软工/信安专业学生做课程设计、毕设原型、实训作业补全,也适合想从零理解 JavaWeb 请求-响应本质的初学者。它不教你“怎么配 Maven 依赖”,而是手把手告诉你web.xml里<servlet-mapping>的 path 写错一个斜杠会导致整个登录跳转失效。


2. 项目结构与核心模块拆解:从webapp/目录树到DAO→Service→Servlet→JSP四层流转逻辑

2.1 文件目录结构:看清WEB-INF是什么、lib里放什么、为什么css文件夹要放在webapp/下而非src/

项目解压后主目录结构如下(已剔除.idea、.git等 IDE 元数据):

cake-shop/ ├── src/ # Java 源码根目录 │ ├── dao/ # 数据访问层:UserDAO.java、ProductDAO.java、OrderDAO.java │ ├── service/ # 业务逻辑层:UserService.java、ProductService.java、CartService.java │ ├── servlet/ # 控制层:LoginServlet.java、AddToCartServlet.java、CheckoutServlet.java │ └── util/ # 工具类:DBUtil.java(封装 getConnection)、StringUtil.java(判空/截取) ├── webapp/ # Web 资源根目录(Tomcat 部署时直接映射为上下文路径) │ ├── css/ # Bootstrap 4.6 + 自定义 style.css(注意:不是 Bootstrap 5!) │ │ ├── bootstrap.css # 原始未压缩版(共 4 份同名文件?实为不同版本混入,需统一) │ │ └── style.css # 作者自写:轮播图尺寸、购物车表格边框、按钮 hover 动效 │ ├── js/ # layer.js(弹窗组件)+ custom.js(购物车数量加减绑定) │ ├── images/ # 商品图、logo、banner 图(命名规范:cake_birthday_01.jpg) │ ├── WEB-INF/ # 安全敏感目录:仅服务器可访问,浏览器无法直连 │ │ ├── web.xml # 核心配置:servlet 映射、filter、welcome-file-list │ │ └── lib/ # 必须存在:mysql-connector-java-5.1.47.jar(注意版本!) │ ├── index.jsp # 首页:包含 <jsp:include page="header.jsp"/> 复用头部 │ ├── product-detail.jsp # 商品详情页:URL 参数 ?id=123 → DAO 查询单条 │ └── checkout.jsp # 结算页:从 session.getAttribute("cart") 取购物车对象渲染 └── README.md # 关键提示:数据库建表 SQL 在 /sql/cake_shop.sql,初始账号 admin/123456

提示:webapp/是 Tomcat 的docBase,所有静态资源(CSS/JS/图片)必须放在此目录下;WEB-INF是安全边界,web.xml和lib必须在此;src/编译后自动输出到WEB-INF/classes/,无需手动复制。

2.2 四层架构流转示例:以“用户登录”为例,看请求如何穿透 DAO→Service→Servlet→JSP

我们以login.jsp提交表单触发LoginServlet为例,走一遍完整链路:

  1. JSP 层(视图):login.jsp中<form action="login" method="post">,提交到 URL/login
  2. Servlet 层(控制器):web.xml中<servlet-mapping>将/login映射到LoginServlet.class
    // src/servlet/LoginServlet.java protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String username = request.getParameter("username"); // 获取表单字段 String password = request.getParameter("password"); UserService userService = new UserService(); // 实例化 Service User user = userService.login(username, password); // 调用业务方法 if (user != null) { request.getSession().setAttribute("user", user); // 登录成功,存入 Session response.sendRedirect("index.jsp"); // 重定向到首页 } else { request.setAttribute("error", "用户名或密码错误"); // 错误信息存 request request.getRequestDispatcher("login.jsp").forward(request, response); // 转发回登录页 } }
  3. Service 层(业务):UserService.login()封装校验逻辑(如密码是否加密存储)并调用 DAO
    // src/service/UserService.java public User login(String username, String password) { // 注意:此处未加盐哈希!生产环境必须改造 String encryptedPassword = MD5Util.md5(password); // 使用项目自带 MD5 工具类 return userDAO.findByUsernameAndPassword(username, encryptedPassword); }
  4. DAO 层(数据):UserDAO.findByUsernameAndPassword()执行 SQL 查询
    // src/dao/UserDAO.java public User findByUsernameAndPassword(String username, String password) { String sql = "SELECT id, username, password, email, phone, address FROM users WHERE username=? AND password=?"; try (Connection conn = DBUtil.getConnection(); PreparedStatement ps = conn.prepareStatement(sql)) { ps.setString(1, username); ps.setString(2, password); ResultSet rs = ps.executeQuery(); if (rs.next()) { User user = new User(); user.setId(rs.getInt("id")); user.setUsername(rs.getString("username")); user.setEmail(rs.getString("email")); return user; } } catch (SQLException e) { e.printStackTrace(); } return null; }

这个流程清晰展示了传统 JavaWeb 的“贫血模型”:Servlet 不处理业务,只做参数解析和跳转;Service 做简单编排;DAO 专注 SQL;JSP 专注展示。没有 Spring 的 IOC/AOP,但每一步都可控、可断点、可调试。

2.3 Bootstrap 4.6 的实际应用方式:不是“引入 CDN”,而是本地 CSS 文件 + 自定义覆盖规则

项目中css/目录下有 4 份bootstrap.css,实测为不同子版本混入(如 4.3.1 与 4.6.0),需统一为Bootstrap 4.6.0(兼容 IE10+,且与 layer.js 无冲突)。关键使用方式如下:

  • 栅格系统真正在用:首页推荐区使用container-fluid+row+col-md-4布局三列商品,非简单堆砌 div
  • 组件非全量引入:仅用到carousel(轮播图)、nav(导航栏)、card(商品卡片)、modal(登录弹窗),未启用dropdown或tooltip
  • 自定义样式优先级设置:style.css中所有规则均加!important(如.carousel-inner img { width: 100% !important; }),确保覆盖 Bootstrap 默认行为
  • 响应式断点验证:在 Chrome DevTools 切换iPhone X设备,首页商品列表自动变为单列,navbar-toggler按钮出现

注意:不要删除bootstrap.css后直接写原生 CSS —— 项目中product-detail.jsp的“加入购物车”按钮使用btn btn-primary btn-lg类,若移除 Bootstrap,按钮将丢失圆角、阴影和 hover 效果。


3. 环境搭建与部署实操:从 JDK 8 配置到 Tomcat 8.5 部署 WAR 包的六步闭环

3.1 开发环境四件套:JDK 8u291 + Tomcat 8.5.93 + MySQL 5.7.36 + IDEA 2022.3(非必须,但最稳)

组件版本要求为什么必须是这个版本验证命令/位置
JDK8u291(非 11+)Servlet 3.1 规范要求,javax.servlet.*包在 JDK 8 中默认存在java -version输出1.8.0_291
Tomcat8.5.93(非 9/10)支持web.xml中<servlet>的load-on-startup,且与 Bootstrap 4.6 JS 兼容bin/startup.bat启动后访问http://localhost:8080显示猫页
MySQL5.7.36(非 8.0+)cake_shop.sql中CREATE TABLE语句含ENGINE=InnoDB DEFAULT CHARSET=utf8,MySQL 8 默认 utf8mb4mysql --version输出Ver 14.14 Distrib 5.7.36
IDEA2022.3(非最新)内置 Tomcat 插件对web.xml解析最稳定,避免Artifact not found错误File → Project Structure → Project SDK 选 JDK 8

提示:若用 VS Code,需手动安装Extension Pack for Java+Debugger for Java+Language Support for Java(TM),但web.xml配置识别率低于 IDEA。

3.2 数据库初始化:执行sql/cake_shop.sql建库建表,并修正字符集陷阱

项目sql/目录下cake_shop.sql文件包含建库、建表、插入测试数据三部分。必须按顺序执行:

-- 第一步:创建数据库(关键!指定字符集) CREATE DATABASE IF NOT EXISTS cake_shop CHARACTER SET utf8 COLLATE utf8_general_ci; -- 第二步:切换数据库 USE cake_shop; -- 第三步:建表(注意:users 表中 password 字段为 VARCHAR(32),对应 MD5 加密后长度) CREATE TABLE users ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(32) NOT NULL, email VARCHAR(100), phone VARCHAR(20), address VARCHAR(200), created_time DATETIME DEFAULT CURRENT_TIMESTAMP ); -- 第四步:插入管理员账号(密码明文为 '123456',MD5 后为 'e10adc3949ba59abbe56e057f20f883e') INSERT INTO users (username, password, email) VALUES ('admin', 'e10adc3949ba59abbe56e057f20f883e', 'admin@cake.com');

避坑重点:

  • 若跳过CHARACTER SET utf8,中文商品名(如“提拉米苏”)插入后显示为???
  • password字段长度必须为32,否则 MD5 加密字符串被截断,导致登录失败
  • 执行前确认 MySQL 客户端连接编码:SET NAMES utf8;,否则source cake_shop.sql会乱码

3.3 IDEA 中部署 WAR 包:六步操作,拒绝 “Artifact is not configured”

  1. 导入项目:File → Open → 选择cake-shop/根目录 → 选择 “Create project from existing sources”
  2. 配置 SDK:File → Project Structure → Project → Project SDK 选 JDK 1.8
  3. 标记源码目录:Project Structure → Modules → Sources → 将src/标为Sources,webapp/标为Resources
  4. 配置 Artifacts:Project Structure → Artifacts →+→Web Application: Archive→ 名称填cake-shop.war
    • Output Layout 中,Available Elements下拖入cake-shop:war exploded
    • WEB-INF/lib/下必须包含mysql-connector-java-5.1.47.jar(项目webapp/WEB-INF/lib/中已提供)
  5. 配置 Tomcat Server:Run → Edit Configurations →+→ Tomcat Server → Local
    • Deployment →+→Artifact→ 选择cake-shop:war exploded
    • Application context 填/cake(访问路径为http://localhost:8080/cake)
  6. 启动验证:点击绿色三角形 → 浏览器打开http://localhost:8080/cake→ 显示蛋糕店首页即成功

注意:若报错java.lang.ClassNotFoundException: com.mysql.jdbc.Driver,说明mysql-connector-java-5.1.47.jar未打入 WAR 包 —— 回到第 4 步检查Output Layout中WEB-INF/lib/是否包含该 jar。


4. 避坑指南:五个血泪经验总结的高频翻车点与排查路径

4.1 现象:首页轮播图不自动切换,手动点击箭头也无反应

原因:index.jsp中 Bootstrap Carousel 的 JS 初始化代码缺失,或jquery.min.js加载顺序错误
解决:

  • 检查index.jsp底部是否包含以下三段脚本(顺序不可变):
    <script src="js/jquery.min.js"></script> <!-- 必须在 bootstrap.js 之前 --> <script src="js/bootstrap.min.js"></script> <!-- 必须在 layer.js 之前 --> <script src="js/layer.js"></script>
  • 若js/目录下jquery.min.js版本低于 3.2.1,替换为 jQuery 3.2.1 CDN 本地保存
  • 在浏览器 Console 输入$('.carousel').carousel({interval: 3000})手动启动,验证 JS 是否生效

4.2 现象:登录成功后跳转index.jsp,但右上角仍显示 “登录/注册”,未显示用户名

原因:index.jsp中判断登录态的 JSP 脚本片段未正确读取 Session
解决:

  • 打开index.jsp,定位到<% if (session.getAttribute("user") == null) { %>区块
  • 确认session.getAttribute("user")存储的是User对象(非字符串),且User类有getUsername()方法
  • 在LoginServlet.java中添加日志:System.out.println("Login success, user: " + user.getUsername());验证 Session 写入

4.3 现象:添加商品到购物车后,刷新页面购物车清空

原因:购物车对象未序列化,或Cart类未实现Serializable接口
解决:

  • 检查src/util/Cart.java是否包含implements Serializable
    public class Cart implements Serializable { // 必须有! private static final long serialVersionUID = 1L; // 防止反序列化失败 private Map<Integer, CartItem> items; // 商品 ID → 购物项 // getter/setter... }
  • 在AddToCartServlet.java中,确认session.setAttribute("cart", cart)执行时机在cart.addItem()之后

4.4 现象:MySQL 连接报错Access denied for user 'root'@'localhost'

原因:DBUtil.java中数据库连接参数硬编码为root/123456,但本地 MySQL root 密码非此值
解决:

  • 打开src/util/DBUtil.java,修改url、username、password三处:
    private static final String URL = "jdbc:mysql://localhost:3306/cake_shop?useSSL=false&serverTimezone=UTC"; private static final String USERNAME = "your_mysql_username"; // 如 root 或 cake_user private static final String PASSWORD = "your_mysql_password"; // 如 123456 或更复杂密码
  • 若 MySQL 8.0+,需下载mysql-connector-java-8.0.28.jar替换lib/下旧 jar,并修改URL中useSSL=false为allowPublicKeyRetrieval=true

4.5 现象:IDEA 启动 Tomcat 后控制台刷屏SEVERE: Error filterStart

原因:web.xml中<filter>配置了不存在的类,或CharacterEncodingFilter类路径错误
解决:

  • 打开webapp/WEB-INF/web.xml,查找<filter-class>标签
  • 项目中实际使用的是org.springframework.web.filter.CharacterEncodingFilter?不,本项目未引入 Spring!
  • 正确应为com.cake.util.EncodingFilter(项目src/util/EncodingFilter.java中定义),确认该类存在且包名一致
  • 若EncodingFilter.java不存在,则注释掉web.xml中整个<filter>块(不影响核心功能)

5. 功能扩展与二次开发:从“改文字”到“加微信支付”的三层改造路径

5.1 第一层:UI 层快速定制(10 分钟内完成)

目标:将首页 Banner 文字“欢迎光临蛋糕店”改为“生日蛋糕限时 8 折”,并更换 banner 图片。

操作步骤:

  1. 打开webapp/index.jsp,找到<div class="carousel-caption">区块
  2. 修改其中<h5>欢迎光临蛋糕店</h5>为<h5>生日蛋糕限时 8 折</h5>
  3. 将webapp/images/banner1.jpg替换为新图(尺寸建议 1200×400px,格式 JPG)
  4. 重启 Tomcat,刷新页面即生效

技巧:所有 JSP 中的静态文本(如按钮文字“立即购买”、“去结算”)均可直接编辑 HTML,无需编译 Java 类。

5.2 第二层:业务层逻辑增强(30 分钟内完成)

目标:在用户注册时,增加手机号格式校验(11 位数字,以 13/15/18 开头)。

操作步骤:

  1. 修改src/servlet/RegisterServlet.java的doPost方法:
    String phone = request.getParameter("phone"); if (!phone.matches("^1[3-9]\\d{9}$")) { request.setAttribute("error", "手机号格式不正确"); request.getRequestDispatcher("register.jsp").forward(request, response); return; // 阻止后续执行 }
  2. 在register.jsp的手机号输入框后添加提示:
    <small class="text-muted">请输入 11 位手机号,如 13812345678</small>
  3. 重新编译RegisterServlet.java(IDEA 中 Ctrl+Shift+F9),重启 Tomcat

5.3 第三层:数据层与支付对接(2 小时内完成)

目标:将“模拟支付”页(pay.jsp)对接微信沙箱环境,生成真实支付二维码。

技术选型与实施要点:

  • 不重写整套支付逻辑:复用现有Order表结构,新增字段pay_status TINYINT DEFAULT 0(0=未支付,1=已支付)
  • 接入微信 JSAPI 支付:需申请微信商户平台沙箱账号,获取APPID、MCH_ID、KEY
  • 关键改造文件:
    • src/servlet/CheckoutServlet.java:生成订单后,调用微信统一下单 API,返回code_url
    • webapp/pay.jsp:用qrcode.js将code_url渲染为二维码,并轮询CheckPayStatusServlet
    • 新增src/servlet/CheckPayStatusServlet.java:查询微信订单状态,更新Order.pay_status

核心代码片段(微信下单):

// src/servlet/CheckoutServlet.java 中新增 String url = "https://api.mch.weixin.qq.com/pay/unifiedorder"; String xml = "<xml>" + "<appid>" + APPID + "</appid>" + "<mch_id>" + MCH_ID + "</mch_id>" + "<nonce_str>" + UUID.randomUUID().toString().replace("-", "") + "</nonce_str>" + "<body>蛋糕订单-" + order.getId() + "</body>" + "<out_trade_no>" + order.getId() + "</out_trade_no>" + "<total_fee>" + (int)(order.getTotalPrice()*100) + "</total_fee>" + // 单位:分 "<spbill_create_ip>" + request.getRemoteAddr() + "</spbill_create_ip>" + "<notify_url>http://yourdomain.com/notify</notify_url>" + "<trade_type>QR</trade_type>" + "<sign>" + getWXSign(xml) + "</sign>" + "</xml>"; // 发送 POST 请求,解析返回 XML 中的 code_url 字段

注意:微信支付需备案域名、HTTPS,沙箱环境可跳过;生产环境务必使用https://回调地址,且notify_url必须能被微信服务器访问。


6. 我的调试习惯:从System.out.println到log4j2的渐进式日志治理法

刚拿到这套代码时,我第一件事不是跑起来,而是打开LoginServlet.java,在doPost方法开头加了一行:

System.out.println("[DEBUG] LoginServlet received: username=" + username + ", password=" + password);

然后启动 Tomcat,用 Postman 发送 POST 请求,盯着catalina.out文件看这行日志是否打印——这是最原始、最可靠的“请求是否到达 Servlet”验证法。但很快我发现,当UserService、UserDAO层层调用时,System.out日志散落在各处,难以归因。于是我把System.out.println全部替换成log.info(),并引入log4j2。

具体落地步骤:

  1. 添加依赖:将log4j-api-2.17.1.jar和log4j-core-2.17.1.jar放入webapp/WEB-INF/lib/
  2. 配置log4j2.xml(放在src/下,编译后自动到WEB-INF/classes/):
    <?xml version="1.0" encoding="UTF-8"?> <Configuration status="WARN"> <Appenders> <File name="FileAppender" fileName="logs/cake-shop.log"> <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/> </File> </Appenders> <Loggers> <Root level="info"> <AppenderRef ref="FileAppender"/> </Root> </Loggers> </Configuration>
  3. 在每个类顶部声明 Logger:
    // src/servlet/LoginServlet.java private static final Logger log = LogManager.getLogger(LoginServlet.class); protected void doPost(...) { log.info("Login attempt for username: {}", username); // {} 占位符,避免字符串拼接 // ... 其他逻辑 }

为什么坚持这么做?

  • System.out在多线程环境下会乱序,log4j2保证日志原子性
  • logs/cake-shop.log文件可按天滚动,避免catalina.out被撑爆
  • log.info("SQL: {}", sql)比System.out.println("SQL: " + sql)更安全(SQL 含特殊字符时不崩溃)
  • 后期加监控时,ELK 栈天然支持log4j2格式,无需额外解析

从那以后我每次接手一个新 JavaWeb 项目,都强制走一遍:
① 在web.xml中确认load-on-startup是否为 1(保证DBUtil初始化)
② 在DBUtil.getConnection()中加log.debug("DB connected to: {}", URL)
③ 在每个Servlet的doGet/doPost开头打log.info
④ 部署后立刻tail -f logs/cake-shop.log盯 30 秒,确认无ClassNotFoundException或SQLException

这套动作现在已成肌肉记忆。它不能让你写出高并发代码,但能让你在答辩前 2 小时,精准定位到是web.xml的<servlet-mapping>写错了,而不是怀疑人生。希望帮到你。

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

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

Jev:用智能if语句替代传统条件分支的代码实践

我最近在看代码里那些乱七八糟的条件判断&#xff0c;越看越觉得不对劲。业务方提了个需求&#xff0c;说要在工单系统里判断用户是不是“真的着急”&#xff0c;然后决定要不要优先处理。我盯着需求文档看了半天&#xff0c;发现这事用传统if-else根本没法写——你怎么用代码表…

作者头像 李华
网站建设 2026/10/1 19:16:42

iOS虚拟摄像头实现原理:AVFoundation Hook与CMSampleBuffer替换

1. 这不是“换摄像头”&#xff0c;而是对 iOS 视频采集链路的精准外科手术“iOS 虚拟视频替换摄像头”——这个标题乍看像魔术&#xff0c;实则是 iOS 系统级多媒体架构下一次高度可控的介入。它不依赖越狱、不修改系统分区、不注入内核模块&#xff0c;而是通过Hook AVFounda…

作者头像 李华
网站建设 2026/10/1 19:16:23

openrig 实战:统一编排 Claude Code 与 Codex 多 AI 编码代理

1. 从零认识 openrig&#xff1a;它到底解决什么问题第一次看到 openrig 这个名字&#xff0c;很多人会以为是某个硬件支架项目&#xff0c;毕竟 rig 在英文里有“装配、支架”的意思。但如果你最近在折腾 Claude Code、Codex 这类命令行 AI 编程工具&#xff0c;就会明白它其实…

作者头像 李华
网站建设 2026/10/1 19:16:12

Anaconda+Jupyter路径配置与虚拟环境内核实战指南

1. 先把这套组合的定位说清楚Anaconda 装完、Jupyter 打开、路径配好&#xff0c;这三件事单拎出来都不算难&#xff0c;但串在一起就是新手最容易翻车的地方。我自己带过不少刚入行的朋友&#xff0c;十个里面有七八个卡在"明明装好了&#xff0c;命令敲下去却说不是内部…

作者头像 李华
网站建设 2026/10/1 19:15:48

LLM调用审计系统:轻量级可回溯操作日志方案

1. 项目概述&#xff1a;Hindsight 不是“事后诸葛亮”&#xff0c;而是一套可落地的 LLM 操作审计与回溯系统 你有没有遇到过这样的场景&#xff1a;线上服务突然返回一堆 400 Bad Request 或更扎心的 401 Unauthorized: incorrect api key provided &#xff0c;日志里只…

作者头像 李华
网站建设 2026/10/1 19:15:23

iOS 上运行 x86-64 Windows 程序:Wine + FEX-Emu + DXMT 技术方案解析

1. 项目缘起&#xff1a;为什么要在 iOS 上折腾 x86-64 的 Wine“Madeira”这个项目标题&#xff0c;乍一看像是个地名&#xff0c;但在我们这圈子里&#xff0c;它指的是一套把Wine、FEX-Emu、DXMT串起来&#xff0c;让 iOS 设备能够运行 x86-64 Windows 程序的技术方案。我第…

作者头像 李华