☰
javaWeb+servlet购物车项目:从零跑通核心链路
2026/10/9 0:42:47 网站建设 项目流程

简介:一套基于JavaWeb与Servlet的简易购物车系统源码,面向JavaWeb初学者与课程设计人群,演示如何通过Servlet接收商品添加请求、记录选购信息与数量,并跳转至另一个Servlet以表格形式展示购物车内容。工程基于Eclipse构建,技术栈为JavaWeb + Servlet + Tomcat + HTML + CSS,通过商品展示页面和超链接参数传递触发后台添加逻辑,覆盖了从页面展示到会话内数据记录的核心流程。压缩包共25个文件,主要包括3个Java源文件、对应3个class编译文件、1个HTML页面、2个XML配置及8张JPG图片等,完整保留Eclipse项目结构,便于直接导入运行;整包仅491KB,轻量易用。资源目前已有638人学习下载,适合用来理解购物车模块的后台交互流程,可作为课设或Servlet入门练手参考。

1. javaWeb+servlet购物车:这个项目能让你在一天内跑通servlet核心链路

做javaWeb课程设计或者刚接触servlet的人,通常会卡在“明明代码能编译,部署到Tomcat就404/500”。这里拆的shop010是一个极其简洁但完整的servlet购物车demo:HTML展示商品,点击“添加到购物车”跳转到Servlet,用Session记录选购信息和数量,再跳转到另一个Servlet用表格输出购物车明细。它没有Spring、没有Maven,正好把HttpServletRequest、HttpSession、Servlet跳转这几个最核心的机制暴露出来。适合刚学完Java SE、想弄懂servlet请求响应模型的人,也适合做课程设计参考。下面从目录结构开始,一步一步讲清楚怎么跑、怎么改、哪些地方容易翻车。

2. 项目结构与运行环境:读懂shop010再动手

2.1 目录结构:src、build、WebContent各管什么事

先花五分钟把shop010的目录结构看清楚。这个项目是经典的Eclipse Dynamic Web Project布局,项目正文里列出的文件已经足够还原它的骨架。

  • src:Java源码目录。Servlet类、过滤器、工具类都放这里,包名一般类似com.shop.servlet。IDE保存时自动编译,生成到build/classes。
  • build:Eclipse的编译输出目录,默认叫build/classes,Tomcat运行时实际加载的是这里的.class文件。所以改完Java代码后必须触发重新编译,否则Tomcat用的还是旧class。
  • WebContent:Web应用根目录。这个目录下的shop.html、img、WEB-INF会被原样发布到Tomcat的webapps/shop010下。浏览器能直接访问根目录下的静态文件,但WEB-INF不能通过URL直接访问。
  • WEB-INF:里面最关键的是web.xml(Servlet 3.0之前)和lib目录。如果是Servlet 3.0注解方式,web.xml可以不存在。Spring项目会把依赖jar放在WEB-INF/lib,但这个教学项目没有依赖,所以不需要。
  • .classpath、.project:Eclipse的项目描述文件,导入时靠它们识别项目类型。IDEA导入Eclipse项目时也会读这两个文件。

为什么Tomcat一定要找WEB-INF/classes?因为Servlet是Java类,Tomcat启动时会根据映射关系用类加载器去WEB-INF/classes目录下找对应的.class文件。源码.java文件对Tomcat是透明的,它只认字节码。所以如果只把WebContent复制到Tomcat的webapps下,WEB-INF/classes为空,服务能启动但所有Servlet路径404。这是这个项目最容易踩的部署坑,后面避坑章节会再展开。

2.2 环境与部署:JDK+Tomcat+Eclipse的推荐组合

这个项目用的是老式Servlet API,没有Maven,环境搭配有一个“安全区”:

  • JDK 8(不要用JDK 17以上,除非你愿意处理JAXB缺失等一堆编译问题)
  • Tomcat 8.5或9.0(不要用Tomcat 10,原因见避坑章节)
  • Eclipse IDE for Enterprise Java(或者IDEA,二选一)

以Eclipse为例,导入部署步骤如下:

  1. File > Import > Existing Projects into Workspace,选择shop010.zip解压后的文件夹。
  2. 如果项目没有显示为Web项目,右键项目Properties > Project Facets,勾选Dynamic Web Module,版本选3.0。
  3. 在Java Build Path > Libraries > Add Library > Server Runtime里选Tomcat 9。
  4. 右键项目Run As > Run on Server,选择刚配置的Tomcat。
  5. 启动后访问http://localhost:8080/shop010/shop.html。

IDE可以换,但部署逻辑不变:关键是让IDE把build/classes里的Servlet class和WebContent一起发布到Tomcat的应用目录。Eclipse的“Run on Server”会自动完成合并,这也是为什么在Eclipse里跑得好好的,手动拷目录到webapps却404的原因。

IDEA用户导入Eclipse项目后,需要手动配置Tomcat。进入Run > Edit Configurations > + > Tomcat Server > Local,在Deployment标签页点+,选Artifact,选择shop010:war exploded,Application context填/shop010。这里的war exploded模式表示按展开目录方式部署,改Java文件后可以热更新,适合调试。

有两个部署参数很关键:Tomcat的port和应用的context path。端口默认8080,被占用时在server.xml的Connector标签里改port="8090"。Context path决定了URL前缀,Eclipse自动部署时通常是/shop010,IDEA要自己填,填错就会出现“访问shop.html 404但Eclipse能跑”的怪事。

2.3 没有web.xml也能跑?Servlet 3.0的注解映射

这个购物车项目如果在Servlet类上看到@WebServlet,就不需要web.xml来注册Servlet。这是Servlet 3.0(Java EE 6)引入的注解式映射,配置更轻,也适合课程设计。

@WebServlet(name = "AddCartServlet", urlPatterns = { "/addCart" }) public class AddCartServlet extends HttpServlet { private static final long serialVersionUID = 1L; }

如果用传统的web.xml,对应的映射是这样的:

<servlet> <servlet-name>AddCartServlet</servlet-name> <servlet-class>com.shop.servlet.AddCartServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>AddCartServlet</servlet-name> <url-pattern>/addCart</url-pattern> </servlet-mapping>

这里有几个细节必须记住:

  • url-pattern不能漏掉前面的斜杠/,写成addCart会变形为默认路径匹配。
  • servlet-class必须是编译后存在于WEB-INF/classes的全限定名,包名+类名一个都不能错。
  • 同一个Servlet不能既用@WebServlet又在web.xml注册同一个URL,否则Tomcat启动时控制台会报Ambiguous mapping,应用直接启动失败。

判断这个项目用的是哪种映射很简单:打开WebContent/WEB-INF看有没有web.xml,以及Servlet类源码上有没有@WebServlet注解。两种方式都能跑,注解方式代码就近维护,web.xml集中管理。课堂答辩时如果老师让我解释URL怎么找到Servlet,我建议你先把url-pattern和servlet-class这两行讲清楚,讲“匹配过程”会比只念代码得分高。

2.4 请求路径是怎么和Servlet对上的

顺手补一个关键概念:浏览器发起GET /shop010/addCart?goodsId=1001后,Tomcat先按/shop010找到应用,再在应用里查/addCart的映射。映射匹配按“精确匹配 > 路径匹配 > 扩展名匹配 > 默认”的优先级执行。我们这个项目全是精确匹配,所以/addCart必须与注解或web.xml里的urlPatterns完全一致,多一个末尾斜杠都返回404。

如果用注解,URL参数里的?后的内容不属于映射匹配范围,属于request.getParameter的领地。所以/addCart和/addCart?foo=bar匹配的是同一个Servlet,参数不同只是导致Servlet内取到的值不同。理解这一点后,调试404时思路就很清晰:先看URL的path部分是否匹配,再去Servlet内部Debug参数。

3. 商品展示与添加到购物车:核心链路怎么串起来

3.1 商品页:HTML里的超链接就是请求入口

shop.html是项目的门面。它不需要JSP,不需要EL表达式,纯HTML+CSS就能把商品展示做得足够干净。因为需求里只要求“点击添加到购物车超链接后跳转到Servlet”,所以每个商品卡片上放一个超链接,链接地址直接携带商品参数。

一个典型的商品卡片长这样:

<div class="goods-card"> <img src="img/goods_01.jpg" alt="商品图"> <h3>黑色签字笔</h3> <p class="price">¥5.00</p> <a href="addCart?goodsId=G001&goodsName=黑色签字笔&price=5.00">添加到购物车</a> </div>

要注意,这个超链接是GET请求,商品信息会暴露在地址栏。对教学项目不是问题,但如果参数里有中文,浏览器地址栏会显示URL编码后的字符,比如%E9%92%A2%E7%AC%94,这是正常现象。

初学者容易踩的路径坑:如果shop.html在应用根目录,href="addCart"是相对路径,浏览器基于当前页面URL解析成/shop010/addCart,正确。但如果shop.html在pages子目录下,href="addCart"会解析成/shop010/pages/addCart,导致404。这时候应该写绝对路径/shop010/addCart,或者先用${pageContext.request.contextPath}(JSP里)拼出上下文。项目里shop.html在根目录,所以相对路径最稳。

你可能会问:为什么不用form+button?也完全可以。但超链接的写法更直观地展示了“GET参数在URL上”这个事实,能让新手一眼看懂Servlet拿参数的过程。如果要用POST方式传递商品数据,就得改成表单,后面会在3.3提一下。

3.2 AddCartServlet:把商品写入Session购物车

Servlet收到请求后,需要完成三件事:读取参数、更新购物车、跳转到显示页面。下面这段代码是符合项目需求的最小实现:

@WebServlet("/addCart") public class AddCartServlet extends HttpServlet { private static final long serialVersionUID = 1L; protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding("UTF-8"); String goodsId = request.getParameter("goodsId"); String goodsName = request.getParameter("goodsName"); String priceStr = request.getParameter("price"); // 参数校验:goodsId空则返回提示,避免后面空指针 if (goodsId == null || goodsId.trim().isEmpty()) { response.setContentType("text/html; charset=UTF-8"); response.getWriter().print("<h3>缺少商品ID,无法添加</h3>"); return; } // 从Session取购物车,不存在则新建 HttpSession session = request.getSession(); Map<String, Integer> cart = (Map<String, Integer>) session.getAttribute("cart"); if (cart == null) { cart = new LinkedHashMap<String, Integer>(); } // 数量累加:同一个商品再次加入时数量+1 Integer oldCount = cart.get(goodsId); cart.put(goodsId, oldCount == null ? 1 : oldCount + 1); session.setAttribute("cart", cart); // 写操作完成后重定向到明细页 response.sendRedirect("showCart"); } }

逻辑说明:

  • request.getParameter()返回String,如果后续要把商品ID转成数字,用Integer.parseInt(goodsId)前必须先校验格式,否则抛NumberFormatException。
  • 购物车选LinkedHashMap<String, Integer>,key是商品ID,value是数量。LinkedHashMap记录插入顺序,显示时按用户添加顺序排列,比HashMap更符合购物车直觉。
  • oldCount == null ? 1 : oldCount + 1避免对null做+1的空指针。Integer是包装类型,自动拆箱时null会触发NPE,所以这里用三目判断。
  • response.sendRedirect("showCart")是重定向,浏览器会重新请求/showCart,地址栏变化。如果改成request.getRequestDispatcher("showCart").forward(request, response)转发,地址栏保持/addCart,用户按F5刷新时商品会被重复添加,所以写操作后跳转我一般用重定向。

这里还隐含一个设计决策:购物车放在Session里。Session是每个用户独立的,所以不同用户购物车天然隔离。如果放在ServletContext或static变量里,所有请求共享同一份数据,那就不叫购物车而叫“商品计数器”了。看懂这个决策,就能理解Servlet为什么不能把实例变量用作请求间数据。

3.3 中文乱码与参数转码:GET与POST各有一套

商品名是中文时最容易出乱码。乱码分三层:HTML页面编码、GET参数编码、POST请求体编码。

HTML页面:<meta charset="UTF-8">只是告诉浏览器按UTF-8解码,但文件本身如果是GBK保存,浏览器解码后照样是乱码。在Eclipse里检查Window > Preferences > Web > HTML Files编码设为UTF-8,IDEA里打开右下角文件编码检查一下。

GET参数:GET参数走URL,Tomcat 8以上默认按UTF-8解码,Tomcat 7及以下默认ISO-8859-1。如果项目跑在旧Tomcat上,request.setCharacterEncoding("UTF-8")对GET无效,因为它只影响POST body。

POST请求:必须在读取任何参数之前调用request.setCharacterEncoding("UTF-8"),并且放在Servlet的doPost第一行。这个设置只对本次请求body生效,不是全局开关。

如果遇到GET乱码且不想改server.xml,可以用“后悔药”式转码:

String goodsName = request.getParameter("goodsName"); if (goodsName != null) { goodsName = new String(goodsName.getBytes("ISO-8859-1"), "UTF-8"); }

思路:把Tomcat按ISO-8859-1解码得到的字符重新转回原始字节,再用UTF-8解码。只有原始字节确实是UTF-8时才能还原。

更推荐直接改Tomcat的server.xml,一次解决:

<Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" URIEncoding="UTF-8" />

改完重启Tomcat,所有GET参数按UTF-8处理。注意是重启,不是热加载。这个配置对动态请求和静态请求都生效,属于我每次搭环境必改的第一项。

4. 购物车明细展示:表格输出与数量联动的四种写法

4.1 ShowCartServlet:从Session读取并渲染表格

添加完商品后重定向到/showCart,这个Servlet负责把Session里的购物车变成HTML表格。最直接的写法是在Servlet中拼HTML字符串,用response.getWriter()输出。

@WebServlet("/showCart") public class ShowCartServlet extends HttpServlet { private static final long serialVersionUID = 1L; protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType("text/html; charset=UTF-8"); HttpSession session = request.getSession(); Map<String, Integer> cart = (Map<String, Integer>) session.getAttribute("cart"); PrintWriter out = response.getWriter(); out.println("<html><head><title>购物车</title></head><body>"); out.println("<table border='1'>"); out.println("<tr><th>商品ID</th><th>商品名称</th><th>数量</th></tr>"); if (cart != null && !cart.isEmpty()) { for (Map.Entry<String, Integer> entry : cart.entrySet()) { String id = entry.getKey(); int count = entry.getValue(); out.println("<tr><td>" + id + "</td><td>" + id + "</td><td>" + count + "</td></tr>"); } } else { out.println("<tr><td colspan='3'>购物车是空的</td></tr>"); } out.println("</table>"); out.println("<a href='shop.html'>继续购物</a>"); out.println("</body></html>"); } }

这段代码暴露了一个设计缺陷:Map<String, Integer>只记录商品ID和数量,商品名称、价格在AddCartServlet里被丢了,所以表格“商品名称”列只能显示ID。如果原项目确实这样实现,它满足“显示选购信息”的最低要求;但如果你想做出更完整的课程设计,建议按4.2升级。

4.2 用CartItem类替代Map,表格才能显示完整信息

更完整的做法是自定义CartItem类,把商品ID、名称、单价、数量封在一起。该类通常放com.shop.model包。

public class CartItem { private String goodsId; private String goodsName; private double price; private int count; public CartItem(String goodsId, String goodsName, double price, int count) { this.goodsId = goodsId; this.goodsName = goodsName; this.price = price; this.count = count; } // getter/setter省略 }

AddCartServlet里维护List<CartItem>:

List<CartItem> cart = (List<CartItem>) session.getAttribute("cart"); if (cart == null) { cart = new ArrayList<CartItem>(); } boolean found = false; for (CartItem item : cart) { if (item.getGoodsId().equals(goodsId)) { item.setCount(item.getCount() + 1); found = true; break; } } if (!found) { cart.add(new CartItem(goodsId, goodsName, Double.parseDouble(priceStr), 1)); } session.setAttribute("cart", cart);

这里有两处和Map版本不同的处理逻辑:

  • 重复商品通过遍历List判断goodsId是否已存在,而不是直接get(goodsId)。List的查找是O(n),购物车商品数量很小,性能不是问题。
  • Double.parseDouble(priceStr)可能抛NumberFormatException,所以在解析前应该先try-catch或正则校验,课程设计里至少做到非空判断。

表格里要显示价格和小计,循环内累加:

<tr> <td>1001</td> <td>钢笔</td> <td>12.5</td> <td>2</td> <td>25.0</td> </tr>

金额计算用double会有精度问题,但只是显示购物车不涉及支付结算,教学上可以接受。想专业就用BigDecimal:new BigDecimal(String.valueOf(price)).multiply(new BigDecimal(String.valueOf(count)))。

4.3 表格里怎么加“删除”和“修改数量”

如果需求只要求显示,这节可以跳过。但老师经常追问“如何删除某商品”。在ShowCartServlet表格的每行加一个删除链接:

<a href='removeCart?goodsId=1001'>删除</a>

然后增加RemoveCartServlet:

@WebServlet("/removeCart") public class RemoveCartServlet extends HttpServlet { protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { HttpSession session = request.getSession(); Map<String, Integer> cart = (Map<String, Integer>) session.getAttribute("cart"); if (cart != null) { cart.remove(request.getParameter("goodsId")); session.setAttribute("cart", cart); } response.sendRedirect("showCart"); } }

注意这里的Session引用语义:cart是从Session取出的对象引用,remove修改了这个对象本身,Session里的同一对象也变了,所以setAttribute不是必需的。但如果代码里把cart重新赋值为新Map,就必须setAttribute回去。判断标准只有一句话:对象是否被替换。

如果要“修改数量”,可以做一个简单的加减链接addCart?goodsId=1001(数量+1)和reduceCart?goodsId=1001(数量-1)。reduce逻辑就是count-1,减到0就移除。多一个Servlet也就十来行,不再展开。

4.4 购物车为空与Session过期时的兜底

用户直接访问/showCart,或者Session超时后,session.getAttribute("cart")返回null。如果代码直接调用cart.size()就会空指针。4.1的代码先判空再迭代,这是必须的。

Session默认超时时间30分钟,可在web.xml配置:

<session-config> <session-timeout>30</session-timeout> </session-config>

单位是分钟。答辩时如果老师开着浏览器一阵子再点,可能遇到Session过期。两种处置方式:把超时调大,或者在AddCartServlet首次添加时不管Session是否存在都新建购物车。我一般推荐第二种,因为购物车本来就不需要长期保存,用户重新添加是正常行为。

另外,如果用户清除了浏览器Cookie,JSESSIONID会丢失,Session也找不到,表现为购物车“丢失”。这不是代码bug,是HTTP无状态协议的天然限制。要记住这个边界,不要试图用Session做持久化。

5. 避坑与常见问题:部署这个servlet购物车时的五个典型坑

5.1 坑1:Session取出来强转ClassCastException

现象:第一次点“添加到购物车”正常,第二次再点直接500,Tomcat日志报java.lang.ClassCastException: java.lang.String cannot be cast to java.util.Map。

原因:Session里cart属性被之前某段代码(可能是测试时)存成了String或其他类型,当前强转Map<String, Integer>失败。这种问题在多人协作或反复修改代码时特别容易出现。

解决:先清浏览器JSESSIONID或重启Tomcat清空Session。代码层面加上类型判断,不要盲转:

Object cartObj = session.getAttribute("cart"); Map<String, Integer> cart; if (cartObj instanceof Map<?, ?>) { cart = (Map<String, Integer>) cartObj; } else { cart = new LinkedHashMap<String, Integer>(); }

这个写法以后每次处理Session属性都成立:先instanceof判断,再强转。从那以后我再没被ClassCastException坑过。

5.2 坑2:加了商品刷新购物车后数量翻倍

现象:点击“添加到购物车”,控制台日志只打印了一次请求,但购物车数量变成2、3。

原因:最常见的是showCart页面上的某个资源(比如图片)路径不对,导致浏览器对同一个Servlet发出了额外请求。但购物车数量增加只可能是AddCartServlet被再次调用。需要确认是不是HTML里有两个相同超链接,或者页面里有个img的src写成了addCart?goodsId=...,浏览器加载图片时也触发了Servlet。

解决:在AddCartServlet第一行加System.out.println("addCart called");,观察日志条数。如果发送多次,检查HTML标签。如果只有一次但数量仍翻倍,检查是不是sendRedirect之后浏览器自动重新请求了addCart——通常不会,除非你用了Forward导致地址栏不变。

5.3 坑3:Tomcat 10选错版本,javax变成jakarta

现象:项目导入后所有javax.servlet.*都报红,运行直接抛NoClassDefFoundError: javax/servlet/ServletException。

原因:Tomcat 10开始采用Jakarta EE 9规范,Servlet包名从javax.servlet迁移为jakarta.servlet。这个项目是老的javax写法,在Tomcat 10上连编译都过不了。

解决:最简单换Tomcat 8.5或9。判断下载的Tomcat版本:看lib目录下有没有servlet-api.jar,Tomcat 8/9有;Tomcat 10只有jakarta.servlet-api.jar。如果你非要用Tomcat 10,就得全局替换javax.servlet为jakarta.servlet,但由于Tomcat 10还有EL/JSP等配套差异,课程设计不推荐。

5.4 坑4:Eclipse里能跑,手动拷贝WebContent却404

现象:在Eclipse里点Run on Server没问题,但把WebContent整个目录复制到Tomcat的webapps/shop010,启动后访问shop.html正常,/addCart和/showCart全部404。

原因:手动复制时只复制了WebContent,build/classes里的Servlet class没有被放进去。Tomcat在WEB-INF/classes里找不到对应的.class,自然无法完成Servlet映射。

解决:不要手动拷贝,用WAR部署。在Eclipse里右键项目Export > WAR file,把生成的shop010.war放到Tomcat的webapps目录,Tomcat启动后自动解压。用命令行打包也可以:

jar -cf shop010.war -C WebContent .

前提是WebContent/WEB-INF/classes下已经有编译好的class。如果没有,先确认build/classes里的对应类存在,再手动复制过去。

5.5 坑5:重定向和转发的选择导致刷新时重复添加

现象:添加商品后,地址栏显示/addCart,按F5刷新,购物车数量又加了一次。

原因:AddCartServlet里用了request.getRequestDispatcher("showCart").forward(request, response)。Forward是服务器内部跳转,浏览器地址栏不会变,刷新时浏览器重新发送/addCart请求,于是重复执行添加逻辑。

解决:写操作后跳转一律用response.sendRedirect("showCart")。重定向返回302,浏览器地址栏变成/showCart,刷新只刷新显示页,不会重复添加。这是一种被称为“PRG模式”(Post/Redirect/Get)的通用做法,只不过这里不是Post而是Get。记住“写操作重定向,读操作转发”这个口诀,能避开很多重复提交问题。

6. 再进一步:用Cookie把购物车数量恢复到上次会话

这个项目的购物车放在Session里,Session一过期数据就没了。想让购物车更“像样”,可以改成:用户关闭浏览器再打开,购物车里的商品还在。做法是把购物车数据序列化进Cookie,再次访问时由Servlet读Cookie恢复。

先把购物车Map拼成goodsId:count,goodsId:count的字符串,并反序列化回来:

private static final String CART_COOKIE = "cart_cookie"; public static String cartToString(Map<String, Integer> cart) { StringBuilder sb = new StringBuilder(); for (Map.Entry<String, Integer> e : cart.entrySet()) { if (sb.length() > 0) sb.append(","); sb.append(e.getKey()).append(":").append(e.getValue()); } return sb.toString(); } public static Map<String, Integer> stringToCart(String value) { Map<String, Integer> cart = new LinkedHashMap<String, Integer>(); if (value == null || value.isEmpty()) return cart; for (String item : value.split(",")) { String[] pair = item.split(":"); cart.put(pair[0], Integer.parseInt(pair[1])); } return cart; }

AddCartServlet写完Session后同步写Cookie:

Cookie cookie = new Cookie(CART_COOKIE, cartToString(cart)); cookie.setMaxAge(60 * 60 * 24 * 7); response.addCookie(cookie);

ShowCartServlet读Session,如果为空就尝试从Cookie恢复:

HttpSession session = request.getSession(); Map<String, Integer> cart = (Map<String, Integer>) session.getAttribute("cart"); if (cart == null && request.getCookies() != null) { for (Cookie c : request.getCookies()) { if (CART_COOKIE.equals(c.getName())) { cart = stringToCart(c.getValue()); session.setAttribute("cart", cart); break; } } }

Cookie方案有自己的边界:Cookie总大小约4KB,只能存短ID和数量,存不了商品名和价格;用户禁用Cookie就直接失效。所以Session仍然是主路径,Cookie只适合做“上次购物车找回”。

注意修改购物车后必须同步更新Cookie,否则删了商品,Cookie里还是旧数据,下次又会“复活”。我当时就吃过这个亏,调试半天以为Cookie没写入,最后发现是删了Session的Map却没重写Cookie。从那以后我每次双写Session和Cookie,都会强制走一遍“修改→刷新→关浏览器→重开”四步验证。你下载这份shop010练手时,建议先按第2章导入把链路跑通,再试这个增强,希望帮到你。

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询