- 浏览: 426986 次
- 性别:
- 来自: 广州
文章分类
最新评论
-
iwwenbo:
别逗好不好
Popup.js 弹出窗口 -
bugnuke:
尼玛 跑不了。。。
Popup.js 弹出窗口 -
tangzhifei:
创建Subversion授权文件放哪目录下?前面我的都对了,到 ...
Trac系列(8):windows下Apache+SVN+Trac安装及配置(一) -
zoutuo1986:
正在安装,确实很慢,一定要选择网速快的时候安装,
eclipse 插件springide安装 -
johnsonyang:
this.jmsTemplate.send(
...
spring,weblogic配置jms
随着 Web 应用的复杂化,网站用户的操作过程也日益复杂,网站功能的多样化和交互性的提高为用户提供了多种可能的浏览路径。为了改进用户的使用体验,有时也是为了模拟用户的操作过程以帮助用户解决使用中的问题,需要能在日志中识别某个用户在整个 Session 中所经历的操作过程,本文针对基于 Apache Log4J 的 Web 应用,讨论如何利用 NDC 和 MDC 的机制,简单快捷的为 Web 应用日志增加用户跟踪的基础数据。通过本文,读者可以学习到关于 NDC 和 MDC 的工作机制,以及如何利用他们在一个 Web 应用中记录用户在一个网站上的全部行为和操作过程,并可以直接使用文中的代码和思路,提高工作效率。
随着 Web 应用的复杂化,用户在网站上的操作过程日益复杂。网站功能的多样化和交互性的提高为用户提供了多种可能的浏览路径。对于一个复杂的站点,用户在网站上操作的行为模式和操作习惯的分析,会给网站的优化提供基础的数据支持。而从技术上要为这种分析提供支持,就需要记录下每个用户在网站上的操作过程。另一方面,这种数据的记录也有助于解决用户在使用中出现的问题。我们只要知道用户遇到问题的时间和一些基本信息,就可以从日志中查出此用户遇到问题时的操作过程,从而有助于再现用户的出错场景,进而帮助用户解决问题。此外,网站用户的安全审计和分析用户特征的数据挖掘等工作也需要提供一个方法能对用户的网站操作进行跟踪和纪录。
通常 Web 应用的开发者会在开发过程中设置很多的跟踪点,在这些跟踪点向日志系统输出一些应用程序运行的信息,如果这些信息足够全面的话,开发者就可以利用他们猜测出程序如何处理的用户请求,以及可能遇到了什么问题。但不幸的是,如果一个站点在设计阶段没有把用户跟踪作为系统必须解决的一个问题提出的话,这些日志很可能就只是开发者为满足系统调试的需要而设置的一些信息。当用户访问量急剧增加的的时候,就会出现下面的问题。
在一个高访问量的 Web 应用中,经常要在同一时刻处理大量的用户请求。Web 服务器会为每一个请求分配一个线程,每一个线程都会向日志系统输入一些信息,通常日志系统都是按照时间顺序而不是用户顺序排列这些信息的,这些线程的交替运行会让所有用户的处理信息交错在一起,让人很难分辨出那些记录是同一个用户产生的。另外,高可用性的网站经常会使用负载均衡系统平衡网络流量,这样一个用户的操作记录很可能会分布在多个 Web 服务器上,如果我们没有一种方法来标示一条记录是哪个用户产生的,从这众多的日志信息中筛选出对我们有用的东西将是一项艰巨的工作。
本文试图探讨的解决方案是建立在 Log4J 的基础上的,如果你的 Web 站点已经使用了 Log4J 作为日志系统的 API 接口,根据本文所介绍的方法,就可以很容易的在每一条日志上保存用户上下文信息,为用户跟踪保存基本的访问数据。为了更清晰地介绍这种方法,我们先对 Log4J 以及 NDC/MDC 做个简单的介绍。
Log4J 是 Apache 组织提供的一个日志组件, 它设计了灵活的配置文件,利用它可以在不更改程序的情况下,通过修改配置文件来调控日志的输出。下面是 Log4J 最主要的三大基本构件:
- 记录器(Loger)
对日志信息进行分类筛选。通过指定优先级,控制程序中日志信息的输出:高于优先级的日志可以被输出,低于优先级的日志则被忽略。
- 输出源(Appenders)
指定日志信息的输出设备。Log4J 目前支持的输出设备有以下几种:
- org.apache.log4j.ConsoleAppender(控制台)
- org.apache.log4j.FileAppender(文件)
- org.apache.log4j.DailyRollingFileAppender(每天产生一个日志文件)
- org.apache.log4j.RollingFileAppender(文件大小到达指定尺寸的时候产生一个新的文件)
- org.apache.log4j.WriterAppender(将日志信息以流格式发送到任意指定的地方)
- org.apache.log4j.SocketAppender (Socket)
- org.apache.log4j.NtEventLogAppender (NT的Event Log)
- org.apache.log4j.JMSAppender (电子邮件)
程序员也可以根据自己的需要定制 Appenders,实现更复杂和更为方便实用的日志管理,比如把日志输入数据库,或者传输到统一的日志服务器,等等。
- 布局(Layouts)
指定日志输出的格式。Log4J 提供的 Layout 有以下几种:
- org.apache.log4j.HTMLLayout(以 HTML 表格形式布局)
- org.apache.log4j.PatternLayout(可以灵活地指定布局模式)
- org.apache.log4j.SimpleLayout(包含日志信息的级别和信息字符串)
- org.apache.log4j.TTCCLayout(包含日志产生的时间、线程、类别等等信息)
软件开发人员可以通过这三大构件,根据日志的类型和优先级进行记录,并且可以在程序运行时去控制日志信息输出的格式和往什么地方输出(控制台、日志文件)。
下面是一个在 Web 应用中使用 Log4J 的简单例子。
第 1 步:修改 Web 应用的 web.xml 文件
在 Web 应用的 web.xml 中指明 Log4J 的配置文件名称。
…… <servlet> <servlet-name>log4j-init</servlet-name> <servlet-class>is.dsw.common.base.log4jInit</servlet-class> <init-param> <!—下面的初始化参数指定 log4j 的配置文件为 log4j.properties --> <param-name>log4j</param-name> <param-value>/WEB-INF/log4j.properties</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet> <servlet-name>log4jServlet</servlet-name> <servlet-class>is.dsw.common.base.log4jServlet</servlet-class> <load-on-startup>0</load-on-startup> </servlet> …… |
第 2 步:对 Log4J 进行配置
我们看到,在上面的配置中指定了 Log4J 的配置文件名为 log4j.properties。在这个文件中,我们可以通过对前文所述的 Log4J 的三大控件进行配置:
#此项指定 log4j 本身不输出调试信息 log4j.debug=false # 设置记录器(logger)的输出信息的级别,并指定信息源appender,此例中,输出到JADEA and A1. log4j.rootCategory=DEBUG, OPAL, A1 …… #信息源appender为每天产生一个日志文件 log4j.appender.OPAL= org.apache.log4j.DailyRollingFileAppender #日志格式为灵活布局模式 log4j.appender.OPAL.layout=org.apache.log4j.PatternLayout #指定灵活布局模式下日志的格式 #%c 输出所属类的全名 #%d 输出日志时间其格式为 可指定格式 如 %d{HH:mm:ss}等 #%n 换行符 #%m 输出代码指定信息,如info(“message”),输出message #%p 输出日志的优先级,即 FATAL ,ERROR,INFO 等 log4j.appender.OPAL.layout.ConversionPattern=%d %p %c - %m%n |
第 3 步:在 Web 应用的 Java 代码中使用 Log4J
进行完上面两步的配置,我们就可以在 Java 程序中使用 Log4J 进行日志的输出。
首先读取 Log4J 配置信息,初始化 Logger。
public class log4jInit extends HttpServlet { public void init() { /*找到在web.xml中指定的log4j.properties文件并读取配置信息*/ String prefix = getServletContext().getRealPath("/"); String file = getInitParameter("log4j"); System.out.println("................log4j start"); if(file != null) { PropertyConfigurator.configure(prefix+file); } } } |
然后在 Java 代码中就可以任意地使用 Log4J 进行日志输出。
代码 4. 在 Web 应用的 Java 代码中使用 Log4J 进行日志输出
public class log4jServlet extends HttpServlet { public void doGet(HttpServletRequest req, HttpServletResponse res) throws IOException { Logger logger = Logger.getLogger(log4jServlet.class); /*输出日志*/ logger.info("Entering doGet@Log4jServlet."); /*更多的代码和日志输出*/ ........ ........ logger.info("Exiting doGet@Log4jServlet."); } } |
运行该 Web 应用后,当有客户机访问该 Web 应用时,就会得到应用程序输出的日志信息。下面是以三个并发用户访问该 Web 应用的情况下,从打印出的日志中挑选出的上述相关日志信息:
在上面的例子中,我们看到,利用 Log4J 提供的功能,我们很方便地输出了程序的日志,但是同时也看到,这样的日志在多用户并发访问的情况下,特别是应用程序复杂且拥有大量并发访问用户的情况下,根本无法区分哪些日志是属于哪个用户的,这样就使得进行日志分析和用户跟踪变成一件非常困难的事情。
NDC(Nested Diagnostic Context)是 Neil Harrison 在名为《Patterns for Logging Diagnostic Messages》的书中提出的嵌套诊断环境的机制。这种机制的提出,主要为了减少多线程的系统为每个客户单独记录日志的系统开销。在过去,区分两个客户的日志输出的常用方法是单独为每个客户机实例化新类别,但该方法会增加类别数量,并增加日志记录的管理开销。Neil Harrison 提出的方法就是把用户的上下文信息放到嵌套式诊断环境 (NDC) 中。
NDC 为每一个线程管理一个堆栈。开发人员可以在代码中合适的位置嵌入简单的 push 和 pop 方法,用来维护堆栈。通常 push 进堆栈的是可以唯一标示客户的上下文信息,如 SessionID 或者客户名称,IP 地址等。因为每个客户请求都会有单独的 NDC 堆栈,因此日志系统在输出的时候会根据每个线程找到对应的堆栈,并在输出日志的时候附加上堆栈内的信息。开发人员就可以很容易的在日志中区分出各个不同客户所产生的日志条目。
Log4J 从 1.2 起开始支持 NDC,org.apache.log4j.NDC 声明如下:
public class NDC { // 返回诊断堆栈的内容 public static String get(); // 从堆栈的顶端删除一个元素 public static String pop(); //在堆栈顶端加入一个元素 public static void push(String message); //察看这个堆栈最顶层的元素,但不删除它 public static String peek() // 删除这个线程的堆栈内容 public static void remove(); } |
要注意的是,org.apache.log4j.NDC 类中所有的方法都是静态的。假设 NDC 日志输出功能被打开,每一次的日志请求,Log4J 组件都会把当前线程的整个 NDC 堆栈内容输出在日志条目中。这样的过程不需要开发人员写过多的代码,程序员只需要在代码中合适的地方通过 push 和 pop 方法将正确的信息放到 NDC 的堆栈中,然后通过修改 Log4J 的配置文件,指定用户标志信息输出的位置和格式,而原来 Java 代码中输出日志的代码不需要任何修改,就能够输出带有用户标志信息的日志。
在前面的 Log4J 使用示例 部分,我们曾经讲过 Log4J 配置文件中相应的配置信息,其中 PatternLayout 的 ConversionPattern 用于程序员指定日志输出的格式。要使用 NDC 的方式输出用户标志信息,只需要在 PatternLayout 的格式定义 ConversionPattern 中使用 %x,就能在相应的位置上输出 NDC 存储的上下文信息。具体的使用方法我们将在后面的 在 Web 应用中添加用户跟踪 部分进行介绍。
MDC 和 NDC 相似,也可以减少多线程的系统为每个客户单独记录日志的系统开销。它同样是为每个线程建立一个独立的存储空间,开发人员可以根据需要把信息存入其中。不同的是 MDC 使用 Map 的机制来存储信息,信息以 key/value 对的形式存储在 Map 中。
Log4J 从 1.3 alpha 版本开始提供对 MDC 的支持,org.apache.log4j.MDC 声明如下:
public class MDC { // 清空map所有的条目。 public static void clear(); // 根据key值返回相应的对象 public static object get(String key); //返回所有的key值. public static Enumeration getKeys(); //把key值和关联的对象,插入map中 public static void put(String key, Object val), //删除key对应的对象 public static remove(String key) } |
MDC 和 NDC 的使用方法也类似,区别只是在 Log4J配置文件中,在通过 PatternLayout 的 ConversionPattern 来配置日志的格式的时候,需要使用 %x{key} 来输出相应的用户标志信息对象。
下面,我们通过具体的例子来说明如何在使用 Log4J 的 Web 应用中增加用户标志信息,达到进行用户跟踪的目的。在开发中,对于使用 NDC 还是 MDC 的机制,要看具体的应用在处理上下文信息的时候,是采用堆栈式的还是 Map 式的方便。下面我们以 NDC 为例进行说明。
通常,开发人员会在系统的很多地方设置输出点,输出日志。如果要全面的修改这些输出点使日志条目附加上所需的信息,是一件繁重的工作。我们可以利用 Servlet 的 filter 来简化这项工作。Servlets Filter 是 Servlet 2.3 规范中出现的,它能截取用户从客户端提交的请求,并在请求没有到达真正需要访问的资源前运行一个指定的类。如果我们在这个类中实现 NDC 或 MDC 的功能,就可以大量简化日志修改的工作。下面的清单显示了实现这种修改所需的三个步骤。在这里,我们使用的是 NDC,您也可以使用 MDC 实现相同的功能。
在下面的例子中,介绍如何在前面已有的 Log4J 使用示例 的 Web 应用代码的基础上,通过为 Web 应用的 Servlet 增加一个 filter 的方法,将用户标志信息在 filter 中压入/弹出 NDC 堆栈,而不需要修改任何原来的 Java 程序中的输出日志的代码,使用起来非常简便。
第 1 步:增加一个处理 NDC 堆栈信息的 filter 类
本例通过在 filter 中取得访问该 Web 应用的客户机的IP地址,用以唯一地标识客户。您也可以和实际的应用程序代码相配合,使用更加人性化的方式来唯一标识客户,如取得 Session 中存储的客户名称等。
代码 7. 在 filter 中增加将用户标志信息放入 NDC 堆栈
package is.dsw.base.filter; import javax.servlet.Filter; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpSession; import org.apache.log4j.NDC; public class Log4jNdcFilter implements Filter { public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { // 获得客户的网络地址 String address = request.getRemoteAddr(); // 把网络地址放入NDC中. 那么在在layout pattern 中通过使用 %x,就可在每条日之中增加网络地址的信息. NDC.push(address); //继续处理其他的filter链. chain.doFilter(req, res); // 从NDC的堆栈中删除网络地址. NDC.pop(); } } |
第 2 步:修改 Web 应用的 web.xml 文件
我们需要修改 Web 应用的 web.xml 文件,对 filter 进行设置。在 <web-app< 元素下面增加下列的代码行:
代码 8. 修改 web.xml 文件,增加 filter 配置
…… <filter> <filter-name>NdcFilter</filter-name> <filter-class> is.dsw.base.filter.Log4jNdcFilter</filter-class> </filter> <filter-mapping> <filter-name>NdcFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> …… |
上述 XML 中的 url-pattern 元素与这个 Web 应用中的所有 URL 匹配。如果只想应用于部分 URL,可以相应地调整模式。
第 3 步:修改 Log4J 的配置文件
需要对 Log4J.properties 文件的配置做一些改变,以便看到 NDC 上下文。在 NDC 简介部分,我们曾经说过,%x 表示会在每个日志行上打印当前 NDC 上下文。我们对 Log4J使用示例 中的 Log4J.properties配置文件 进行如下修改,在 PatternLayout 的格式定义 ConversionPattern 中增加 %x, 将 NDC 堆栈中的信息在 %x 指定的位置上进行输出。如下:
代码 9. Log4J 配置文件中修改 PatternLayout 的输出格式
log4j.appender.A1.layout.ConversionPattern=%d %p %c %x - %m%n |
完成以上修改之后,每一条记录都会把我们在 Filter 中 push 进 NDC 堆栈的内容打印出来。仍然以 Log4J 使用示例 中的三个并发用户访问为例,我们可以得到如下的日志信息,和前面不使用 NDC 的方式下 打印的日志信息相比较,可以看到在原来日志的基础上增加了客户机 IP 地址,这样可以很容易地区分不同的用户的信息,为我们进行日志分析和用户跟踪打下了很好的基础。
图 2. 应用 NDC 之后,运行 Web 应用后输出的日志
或许以上的描述已经让你了解到如何利用 NDC 和 MDC 的机制来记录某个用户的唯一标示,或者跟踪其他特定于应用的数据。一旦这些用户标示的数据记入日志,则能很容易地利用其他工具将他们抽取出来,如 grep。如果我们自定义一个数据库的 Appenders,把日志信息插入数据库的话,还能很容易的对这些数据进行统计和分析。
Log4J 提供了对 NDC 和 MDC 机制的支持,开发人员可以利用此机制为每条日志记录增加我们需要的内容。本文通过在 servlet 的 filter 中合适的位置应用很少的代码,就可以修改整个应用的日志策略。合理的善用本文所提到的机制,可以节省我们的工作量,也简化程序中日志的维护。最终,客户可以获得更好的网络应用程序,遇到问题也能从技术支持团队得到迅速有效的响应。
值得注意的是,我们并不能保证本文所提出的用户跟踪方案可以完美地解决所有的问题。如果读者希望采用本方案,请参考 Apache Log4J 的文档以了解更多的信息。
发表评论
-
Java SE 6 新特性: Java DB 和 JDBC 4.0
2009-10-11 16:29 11862006 年底,Sun 公司发布了 Java Stan ... -
Java SE 6 Web Service 之旅
2009-10-11 16:17 1851在过去的几个月里,Sun ... -
用Java动态代理实现AOP
2009-10-10 13:38 1003目前整个开发社区对AOP(Aspect Oriented Pr ... -
泛型DAO类设计模式
2009-10-10 11:56 970Generic Data Access Objects ... -
理解ThreadLocal
2009-07-27 22:29 869ThreadLocal是什么 早在JDK ... -
JSON taglib学习笔记
2008-11-04 10:11 1372JSON-taglib 是 JSP 2.0 标签库,用于 ... -
页面生成打开excel(运用poi)
2008-10-29 09:45 1203把excel作为流在页面输出 publ ... -
J2EE使用iText将数据保存为PDF文档
2008-09-18 21:36 1221在B/S结构的项目中,经 ... -
java操作Excel(Jakarta_POI)
2008-09-09 17:22 4023微软在桌面系统上的成 ... -
优化Java中的正则表达式
2008-09-09 14:14 1796如果你花费了数小时和正则表达式做斗争,只是为了让它完成它几秒内 ... -
Java对象池技术的原理及其实现
2008-09-09 09:24 1135Java对象的生命周期分析 Java对象的生命周期大致包括 ... -
jakarta commons logging 的使用方法(续一)
2008-09-08 11:36 862常用log4j配置,一般可以采用两种方式,.propertie ... -
jakarta commons logging 的使用方法(续)
2008-09-08 11:28 1923JCL(Jakarta Commons Logging)和lo ... -
在Java应用程序中访问USB设备
2008-09-08 10:48 1194Java 平台一直都以其平台无关性自豪。虽然这种无关性 ... -
正则表达式
2008-09-05 17:29 1200Java代码 检测时间 ([0-1]?[0-9]|2[ ... -
汉字验证码
2008-09-05 09:38 1535Java代码 package com.toy; ... -
java数字签名
2008-09-05 09:30 2148... -
jakarta commons logging 的使用方法
2008-09-05 09:30 1617日志(Logging)使得我们能够调试和跟踪应用程序任意时刻的 ...
相关推荐
在多线程的Web应用环境中,追踪用户操作变得尤为重要,尤其是在需要分析用户行为、解决用户问题或进行安全审计时。本文将详细介绍如何利用Log4j的Nested Diagnostic Context (NDC) 和Mapped Diagnostic Context (MDC...
在MyEclipse中开发Java Web应用是一门涉及多个技术领域的重要实践。MyEclipse作为一款强大的集成开发环境(IDE),特别适合于Java Web应用程序的构建。以下将详细讲解配置JDK、Tomcat以及使用SVN的基本步骤和相关...
在.NET框架下,C#是一...总的来说,这个案例为我们提供了一个学习和实践C#在.NET环境中开发Web应用的宝贵资源,通过深入研究B2CShop项目,我们可以了解如何将三层架构应用于实际项目,并掌握C#在Web开发中的核心应用。
### 关于Web回话跟踪与Session机制 #### Session机制概览 ...综上所述,通过合理的使用Cookie和URL重写技术,结合适当的编程手段,开发者可以有效地实现Web应用程序中的Session跟踪,从而为用户提供更佳的用户体验。
《Flask Web开发:基于Python的Web应用开发实战》是一本深入浅出的教程,旨在帮助读者掌握使用Python的Flask框架构建Web应用程序的技术。Flask是一个轻量级的Web服务器网关接口(WSGI)Web应用框架,以其灵活性、...
在Web应用中,会话是指用户打开浏览器与服务器进行交互的一系列连续操作。为了识别这些操作属于同一个用户,服务器需要一种方式来跟踪用户,这就是会话跟踪。在HTTP协议本身是无状态的,因此不能自动维持会话信息,...
1. **新建Web用户控件**:在项目中添加一个新项,选择“Web用户控件”模板。 2. **设计工具栏**:在Web用户控件中使用HTML和CSS设计工具栏,可以包含静态HTML代码以及必要的CSS样式。 3. **重用工具栏**:在需要使用...
在IT行业中,Servlet技术是Java Web开发中的核心组件,它用于扩展服务器的功能,尤其是在构建动态网站和Web应用程序方面。在这个“一个servlet的web应用”项目中,我们看到使用Servlet和JSP(JavaServer Pages)来...
在进行Web应用测试时,测试团队应采用全面的方法,包括功能测试、性能测试、安全性测试、兼容性测试和用户体验测试等多个维度,以确保应用的质量和可靠性。同时,持续集成和自动化测试工具的应用可以帮助提高测试...
当我们在Web应用上下文中谈论扩展点时,它可能指的是如何通过定义特定接口或API来增加应用的功能,使得其他开发者能够根据需要轻松地集成或扩展应用。这有助于创建灵活、可维护且可扩展的Web应用。 其次,...
* 登录用图片验证码:为了防止自动程序攻击,需要在登录页面添加图片验证码,以确保登录请求来自真实用户。 * 口令传输:为了保护用户口令安全,需要使用加密协议传输口令,例如 HTTPS。 * 保存登录功能:为了提高...
会话跟踪技术是Web应用程序中不可或缺的一部分,它主要用于在用户浏览多页时保持其状态,以便提供个性化的用户体验和服务。传统的会话跟踪技术主要包括Cookie、URL重写和隐藏表单字段。 Cookie技术是最常见的会话...
在JAVA Web开发中,实现在线用户统计是一项关键功能,它能够帮助网站或应用程序的管理员实时了解当前有多少用户正在访问和使用系统。这个“JAVA Web在线用户统计”解决方案显然是强大、好用且简单的,这意味着它可能...
在开发网络应用时,用户登录功能是至关重要的一个部分,它确保了用户的个人数据安全以及对特定资源的访问权限。本实验旨在通过实践操作来学习如何利用Session技术来实现这一功能。Session是一种服务器端存储用户状态...
查找与编辑权限相关的代码,比如在`permissions.php`或`user_group.php`,修改或添加规则,使得普通用户无法访问排版编辑功能。 4. **修改视图**:在视图文件中,找到展示编辑按钮或链接的部分,并将其隐藏或移除。...
7. **用户输入验证**:确保用户在表单中输入的数据符合预期格式和规则,防止恶意输入和数据错误,是Web应用安全的重要一环。 8. **用户控件**:自定义的可重用UI组件,可以提高代码复用性和页面设计效率。 9. **...
3. Web Service控件:PB11包含Web Service控件,可以直接在窗口中添加,调用Web Service函数就像调用本地函数一样简单。 三、创建Web Service 在PB11中创建Web Service的步骤包括: 1. 定义服务接口:使用PB11的...
使用Xpra在Web浏览器中传输远程Dwarf Fortress" 的描述进一步强调了这个项目的核心功能,即利用Xpra技术,用户可以在任何支持Web浏览器的地方,无论何处,都能通过Web界面体验Dwarf Fortress游戏,这大大增加了游戏...
在商业编程领域,Web 应用程序的版本检查与声音效果的添加是两个重要的功能,它们可以提升用户体验并确保软件的稳定性和安全性。下面将详细解释这两个知识点。 首先,我们来探讨 Web 版本检查。在开发 Web 应用程序...