`
bhj626
  • 浏览: 22157 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

用struts实现一个完整的登录框实例

阅读更多
    这几天一直都犯愁:感觉自己越来越厌学了,也许是学习的方法不对吧。一个月前就给自己说,学习再踏实些,我要一边补习java基础,一边学习struts、spring、ibatis、hibernate框架,一天天过去了,偶尔的会有些小的感悟,只是感觉还是没什么大的进展。
    细细的想,觉得或许还是自己的基础不够好,还是需要把最基本的学扎实了。于是,这些天又回到了起点:强化学习jsp、servlet、javabean、jdbc,争取更深入,更扎实些,再此期间,慢慢琢磨些设计模式的东西,也许学习框架时会好点。所以以后决定还是做些实例性的东西,至少这样不会枯燥,也给学习增加点刺激感吧。
    经过两个晚上的努力,做了这个登录实例,有点慢,不过为了练手嘛,主要在于体验各个环节及设计思路。
    本想采用jsp+javabean+jdbc来做,不过太懒了,看见自己前几天用struts做的一个登录页面(并未实现,只是练习验证码图片的生成),于是顺手就接着这个做吧。
    环境配置:开发环境myeclipse、框架struts、数据库采用sqlserver2000及相应的驱动包:mssqlserver.jar、msbase.jar、msutil.jar
步骤:
    1、在myeclipse里新建工程strutslogon,并搭建好struts框架
    2、在sqlserver中新建数据库mytest,新建表login,其字段:username、password。将三个驱动包导入到WEB-INF/lib/里
    3、编辑配置文件
    4、编辑以下java文件及jsp文件

web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.4" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee   http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
  <servlet>
    <servlet-name>action</servlet-name>
    <servlet-class>org.apache.struts.action.ActionServlet</servlet-class>
    <init-param>
      <param-name>config</param-name>
      <param-value>/WEB-INF/struts-config.xml</param-value>
    </init-param>
    <init-param>
      <param-name>debug</param-name>
      <param-value>3</param-value>
    </init-param>
    <init-param>
      <param-name>detail</param-name>
      <param-value>3</param-value>
    </init-param>
    <load-on-startup>0</load-on-startup>
  </servlet>
  <servlet-mapping>
    <servlet-name>action</servlet-name>
    <url-pattern>*.do</url-pattern>
  </servlet-mapping>
  
  	<servlet>
  		<servlet-name>Image</servlet-name>
  		<servlet-class>logon.Image</servlet-class>
  	</servlet>
  	<servlet-mapping>
  		<servlet-name>Image</servlet-name>
  		<url-pattern>/image</url-pattern>
  	</servlet-mapping>
  	
  	<welcome-file-list>
  		<welcome-file>logon.jsp</welcome-file>
  	</welcome-file-list>
  	
</web-app>

struts-config.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE struts-config PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 1.1//EN" "http://jakarta.apache.org/struts/dtds/struts-config_1_1.dtd">

<struts-config>
  <data-sources />
  	<form-beans >
  		<form-bean name="logonForm" type="logon.LogonForm"></form-bean>
  	</form-beans>
  <global-exceptions />
  <global-forwards />
  	<action-mappings>
  		<action path="/logon"
  				name="logonForm"
  				type="logon.LogonAction"
  				input="/logon.jsp">
  			<forward name="success" path="/index.jsp"></forward>
  			<forward name="failure" path="/logon.jsp"></forward>
  		</action>
  	</action-mappings>
  <message-resources parameter="com.yourcompany.struts.ApplicationResources" />
</struts-config>



LogonAction.java
package logon;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.struts.action.Action;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionForward;
import org.apache.struts.action.ActionMapping;

public class LogonAction extends Action{
	private LogonBean objLogonBean = new LogonBean();
	
	public LogonBean getObjLogonBean() {
		return objLogonBean;
	}

	public void setObjLogonBean(LogonBean objLogonBean) {
		this.objLogonBean = objLogonBean;
	}

	public ActionForward execute(ActionMapping mapping,	ActionForm form, 
		HttpServletRequest request, HttpServletResponse response)
		throws IOException, ServletException {
		
		String target = "success";
		if(!objLogonBean.validate(form, request, response))	target="failure";
		return mapping.findForward(target);
	}
}

LogonBean.java
package logon;

import java.util.List;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.apache.struts.action.ActionForm;

public class LogonBean {
	
	String inputrand 	= null;
	String realrand 	= null;
	String username 	= null;
	String password 	= null;
	String sqlstr 		= null;
	String warning 		= null;	
	public boolean validate(ActionForm form,HttpServletRequest request, HttpServletResponse response){

		HttpSession session = request.getSession();
		realrand = (String)session.getAttribute("rand");
		System.out.println(realrand);
		
		if(form!=null){
			LogonForm logonForm = (LogonForm)form;
			inputrand = logonForm.getRand();
			username = logonForm.getUsername();
			password = logonForm.getPassword();
			System.out.println("input:" + inputrand);
		} else {
			inputrand = null;
		}
		
		//验证是否输入了完整的登录信息
		if(inputrand.equals("") || username.equals("") || password.equals("")){
			request.setAttribute("warning", "请输入完整的登录信息");
			return false;
		}
		
		//如果验证码为空,返回false,及警告信息
		if(!inputrand.equals(realrand)){
			request.setAttribute("warning", "对不起,您输入的验证码有误,请重新输入!");
			return false;
		}
		
		//查询用户输入的用户名及密码是否合法
		sqlstr = "select * from login where username='" +username+ "' and password='" +password+ "'";
		List list = null;
		list = objLoginDao.ResultToList(objLoginDao.executeQuery(sqlstr));

		//如果查询结果为空,返回false,及警告信息
		if(list.size()<1){
			request.setAttribute("warning", "对不起,您输入的用户名和密码有误,请重新输入!");
			return false;
		}
		
		request.setAttribute("username", username);
		return true;
	}
	
	private LogonDao objLoginDao = new LogonDao();

	public LogonDao getObjLoginDao() {
		return objLoginDao;
	}

	public void setObjLoginDao(LogonDao objLoginDao) {
		this.objLoginDao = objLoginDao;
	}
}

LogonDao.java
package logon;

import java.sql.*; 
import java.util.*; 

public class LogonDao{ 
	
	String 		sDBDriver	= "com.microsoft.jdbc.sqlserver.SQLServerDriver"; 
	String 		url			= "jdbc:sqlserver://localhost:1433;DatabaseName=mytest"; 
	String 		user		= "sa"; 
	String 		password	= "1"; 
	Statement 	stmt		= null; 
	Connection 	conn		= null; 
	ResultSet 	rs			= null; 
	List 		list 		= null;	//自定义,用于转化ResultSet到List类型
	
	public LogonDao(){ 
		try{ 
			Class.forName(sDBDriver).newInstance(); 
			System.out.println("加载数据库驱动成功"); 
		} 
		catch(Exception e) 
		{ 
			e.printStackTrace() ; 
		} 
	} 
	
	//执行查询语句 sql 并返回查询结果 
	public ResultSet executeQuery(String sql){ 
		System.out.println(sql);
		try{ 
			conn=DriverManager.getConnection(url,user,password); 
			Statement stmt=conn.createStatement(); 
			System.out.println("连接数据库成功"); 
			rs=stmt.executeQuery(sql); 
		} 
		catch(SQLException ex){ 
			System.err.println(ex.getMessage()); 
		} 
		return rs; 
	} 
	
	/**
	 * @description:该函数用于将查询结果转化为List,便于使用
	 * @param rs	待转换的ResultSet类型数据
	 * @return	转换后的List数据
	 */
	public List ResultToList(ResultSet rs){
		try {
			ResultSetMetaData rsmd = rs.getMetaData();
			int numberOfColumns = rsmd.getColumnCount();
			//
			System.out.println("numberOfColumns: " + numberOfColumns);
			Map rsMap = null;
			list = new ArrayList();
			
			while(rs.next()){
				rsMap = new HashMap(numberOfColumns);
				for(int r=1;r<numberOfColumns+1;r++){
					rsMap.put(rsmd.getColumnName(r), rs.getObject(r));
				}
				list.add(rsMap);
			}
		}
		catch(SQLException ex){
			System.err.println(ex.getMessage());
		}
		return list;
	}
	
	//执行一次更新
	public void executeUpdate(String sql){ 
		stmt=null; 
		rs=null; 
		try{ 
			conn=DriverManager.getConnection(url,user,password); 
			Statement stmt=conn.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE,ResultSet.CONCUR_UPDATABLE); 
			stmt.executeQuery(sql); 
			stmt.close(); 
			conn.close(); 
		} 
		catch(SQLException ex){ 
			System.err.println(ex.getMessage()); 
		} 
	} 
	
	//关闭 Statement 对象
	public void closeStmt() 
	{ 
		try{ 
			stmt.close(); 
		} 
		catch(SQLException e) 
		{ 
			e.printStackTrace(); 
		} 
	} 
	
	//关闭数据库连接
	public void closeConn() 
	{ 
		try{ 
			conn.close(); 
		} 
		catch(SQLException e) 
		{ 
			e.printStackTrace(); 
		} 
	} 
} 

LogonForm.java
package logon;

import org.apache.struts.action.ActionForm;

public class LogonForm extends ActionForm {
	private String username;
	private String password;
	private String rand;
	public String getPassword() {
		return password;
	}
	public void setPassword(String password) {
		this.password = password;
	}
	public String getRand() {
		return rand;
	}
	public void setRand(String rand) {
		this.rand = rand;
	}
	public String getUsername() {
		return username;
	}
	public void setUsername(String username) {
		this.username = username;
	}
}

Image.java
package logon;

import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.Random;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.sun.image.codec.jpeg.JPEGCodec;
import com.sun.image.codec.jpeg.JPEGImageEncoder;

public class Image extends HttpServlet {
 
	//public static final long serialVersionUID = 1L ;
 
    //private static final String CONTENT_TYPE = "text/html; charset=GBK";

    public Color getRandColor(int fc, int bc) { //给定范围获得随机颜色
        Random random = new Random();
        if (fc > 255) {
            fc = 255;
        }
        if (bc > 255) {
            bc = 255;
        }
        int r = fc + random.nextInt(bc - fc);
        int g = fc + random.nextInt(bc - fc);
        int b = fc + random.nextInt(bc - fc);
        return new Color(r, g, b);
    }

    //Initialize global variables
    public void init() throws ServletException {
    }

    //Process the HTTP Get request
    public void doGet(HttpServletRequest request, HttpServletResponse response) throws
            ServletException, IOException {
        //设置页面不缓存
        response.setHeader("Pragma", "No-cache");
        response.setHeader("Cache-Control", "no-cache");
        response.setDateHeader("Expires", 0);

        // 在内存中创建图象
        int width = 60, height = 20;
        BufferedImage image = new BufferedImage(width, height,
                                                BufferedImage.TYPE_INT_RGB);

        // 获取图形上下文
        Graphics g = image.getGraphics();

        //生成随机类
        Random random = new Random();

        // 设定背景色
        g.setColor(getRandColor(200, 250));
        g.fillRect(0, 0, width, height);

        //设定字体
        g.setFont(new Font("Times New Roman", Font.PLAIN, 18));

        //画边框
        //g.setColor(new Color());
        //g.drawRect(0,0,width-1,height-1);

        // 随机产生155条干扰线,使图象中的认证码不易被其它程序探测到
        g.setColor(getRandColor(160, 200));
        for (int i = 0; i < 155; i++) {
            int x = random.nextInt(width);
            int y = random.nextInt(height);
            int xl = random.nextInt(12);
            int yl = random.nextInt(12);
            g.drawLine(x, y, x + xl, y + yl);
        }

        // 取随机产生的认证码(4位数字)
        String sRand = "";
        for (int i = 0; i < 4; i++) {
            String rand = String.valueOf(random.nextInt(10));
            sRand += rand;
            // 将认证码显示到图象中
            g.setColor(new Color(20 + random.nextInt(110),
				20 + random.nextInt(110),
				20 + random.nextInt(110)));
            //调用函数出来的颜色相同,可能是因为种子太接近,所以只能直接生成
            g.drawString(rand, 13 * i + 6, 16);
        }

		// 将认证码存入SESSION
	    request.getSession().setAttribute("rand", sRand);
	
	    // 图象生效
	    g.dispose();
	
		// 输出图象到页面
		//ImageIO.write(image, "JPEG", response.getOutputStream());
		JPEGImageEncoder encode = JPEGCodec.createJPEGEncoder(response.
		getOutputStream());
		encode.encode(image);
	}

	//Clean up resources
	public void destroy() {
	}
}

logon.jsp
<%@ page language="java" pageEncoding="gbk"%>
<%@ taglib uri="/WEB-INF/struts-html.tld" prefix="html" %>

<%
	String msg = null;
	msg = (String)request.getAttribute("warning");
%>
<html>
<head>
    <title>logon.jsp</title>
</head>

<body>
	<html:form action="/logon.do" method="post">
     	<table border="0" cellspacing="0" cellpadding="0" style="border:#1A3C79 1px solid;" bgcolor="white" align="center">
	        <tr height="10"><td colspan="2"></td></tr>
	        <tr height="22"><td colspan="2" align="center">用户登录</td></tr>
	        <tr height="22">
	          	<td width="100" align="right">用户名:</td>
	          	<td width="180" align="left"><input name="username" type="text" style="width:120px;height:18px;"></td>
	        </tr>
	        <tr height="22">
	          	<td align="right">密&nbsp;&nbsp;码:</td>
	          	<td align="left"><input name="password" type="password" style="width:120px;height:18px;"></td>
	        </tr>
	    	<tr height="22">
		    	<td align="right">验证码:</td>
		    	<td align="left">
			     	<table border="0" cellspacing="0" cellpadding="0">
			        	<tr>
			          		<td><input  name="rand" type="text" style="width:70px;height:18px;" maxLength="4"></td>
			          		<td>&nbsp;
			          			<img id="randImage" src="image" onclick="loadimage();" alt="点击下一张替换图片" style="margin-top:3px;width:60px;height:20px;cursor:hand;">
			          		</td>
			        	</tr>
			     	</table>
		    	</td>
	        </tr>
	        <tr height="25"><td colspan="2" align="center" valign="bottom"><input type="submit" value="提交"></td></tr>
	        <tr height="10"><td colspan="2"></td></tr>
      	</table>
    </html:form>
</body>
<script language="javascript"> 
	function loadimage(){ 
		document.getElementById("randImage").src = "image"; 
	}
	
	//验证登录信息如果有误,则给出提示信息
	msgWarning();
	function msgWarning(){
		var msg = "<%=msg %>";
		if(msg!="null")alert(msg);
	}
</script>
</html>

index.jsp
<html>
<head>
    <title>index.jsp</title>
</head>
<body>
	<br>
	<h3 align="center">${username},welcoming your comming!</h3>
</body>
</html>
分享到:
评论
1 楼 向奕昌 2011-03-02  
<img id="randImage" src="image" onclick="loadimage();" alt="点击下一张替换图片" style="margin-top:3px;width:60px;height:20px;cursor:hand;">  


这里点击不能生成第2张

document.getElementById("randImage").src = "image";   
这个没有用到

相关推荐

    一个基于Qt Creator(qt,C++)实现中国象棋人机对战

    qt 一个基于Qt Creator(qt,C++)实现中国象棋人机对战.

    热带雨林自驾游自然奇观探索.doc

    热带雨林自驾游自然奇观探索

    冰川湖自驾游冰雪交融景象.doc

    冰川湖自驾游冰雪交融景象

    C51 单片机数码管使用 Keil项目C语言源码

    C51 单片机数码管使用 Keil项目C语言源码

    基于智能算法的无人机路径规划研究 附Matlab代码.rar

    1.版本:matlab2014/2019a/2024a 2.附赠案例数据可直接运行matlab程序。 3.代码特点:参数化编程、参数可方便更改、代码编程思路清晰、注释明细。 4.适用对象:计算机,电子信息工程、数学等专业的大学生课程设计、期末大作业和毕业设计。

    前端分析-2023071100789s12

    前端分析-2023071100789s12

    Delphi 12.3控件之Laz-制作了一些窗体和对话框样式.7z

    Laz_制作了一些窗体和对话框样式.7z

    ocaml-docs-4.05.0-6.el7.x64-86.rpm.tar.gz

    1、文件内容:ocaml-docs-4.05.0-6.el7.rpm以及相关依赖 2、文件形式:tar.gz压缩包 3、安装指令: #Step1、解压 tar -zxvf /mnt/data/output/ocaml-docs-4.05.0-6.el7.tar.gz #Step2、进入解压后的目录,执行安装 sudo rpm -ivh *.rpm 4、更多资源/技术支持:公众号禅静编程坊

    学习笔记-沁恒第六讲-米醋

    学习笔记-沁恒第六讲-米醋

    工业机器人技术讲解【36页】.pptx

    工业机器人技术讲解【36页】

    基于CentOS 7和Docker环境下安装和配置Elasticsearch数据库

    内容概要:本文档详细介绍了在 CentOS 7 上利用 Docker 容器化环境来部署和配置 Elasticsearch 数据库的过程。首先概述了 Elasticsearch 的特点及其主要应用场景如全文检索、日志和数据分析等,并强调了其分布式架构带来的高性能与可扩展性。之后针对具体的安装流程进行了讲解,涉及创建所需的工作目录,准备docker-compose.yml文件以及通过docker-compose工具自动化完成镜像下载和服务启动的一系列命令;同时对可能出现的问题提供了应对策略并附带解决了分词功能出现的问题。 适合人群:从事IT运维工作的技术人员或对NoSQL数据库感兴趣的开发者。 使用场景及目标:该教程旨在帮助读者掌握如何在一个Linux系统中使用现代化的应用交付方式搭建企业级搜索引擎解决方案,特别适用于希望深入了解Elastic Stack生态体系的个人研究与团队项目实践中。 阅读建议:建议按照文中给出的具体步骤进行实验验证,尤其是要注意调整相关参数配置适配自身环境。对于初次接触此话题的朋友来说,应该提前熟悉一下Linux操作系统的基础命令行知识和Docker的相关基础知识

    基于CNN和FNN的进化神经元模型的快速响应尖峰神经网络 附Matlab代码.rar

    1.版本:matlab2014/2019a/2024a 2.附赠案例数据可直接运行matlab程序。 3.代码特点:参数化编程、参数可方便更改、代码编程思路清晰、注释明细。 4.适用对象:计算机,电子信息工程、数学等专业的大学生课程设计、期末大作业和毕业设计。

    网络小说的类型创新、情节设计与角色塑造.doc

    网络小说的类型创新、情节设计与角色塑造

    毕业设计-基于springboot+vue开发的学生考勤管理系统【源码+sql+可运行】50311.zip

    毕业设计_基于springboot+vue开发的学生考勤管理系统【源码+sql+可运行】【50311】.zip 全部代码均可运行,亲测可用,尽我所能,为你服务; 1.代码压缩包内容 代码:springboo后端代码+vue前端页面代码 脚本:数据库SQL脚本 效果图:运行结果请看资源详情效果图 2.环境准备: - JDK1.8+ - maven3.6+ - nodejs14+ - mysql5.6+ - redis 3.技术栈 - 后台:springboot+mybatisPlus+Shiro - 前台:vue+iview+Vuex+Axios - 开发工具: idea、navicate 4.功能列表 - 系统设置:用户管理、角色管理、资源管理、系统日志 - 业务管理:班级信息、学生信息、课程信息、考勤记录、假期信息、公告信息 3.运行步骤: 步骤一:修改数据库连接信息(ip、port修改) 步骤二:找到启动类xxxApplication启动 4.若不会,可私信博主!!!

    57页-智慧办公园区智能化设计方案.pdf

    在智慧城市建设的大潮中,智慧园区作为其中的璀璨明珠,正以其独特的魅力引领着产业园区的新一轮变革。想象一下,一个集绿色、高端、智能、创新于一体的未来园区,它不仅融合了科技研发、商业居住、办公文创等多种功能,更通过深度应用信息技术,实现了从传统到智慧的华丽转身。 智慧园区通过“四化”建设——即园区运营精细化、园区体验智能化、园区服务专业化和园区设施信息化,彻底颠覆了传统园区的管理模式。在这里,基础设施的数据收集与分析让管理变得更加主动和高效,从温湿度监控到烟雾报警,从消防水箱液位监测到消防栓防盗水装置,每一处细节都彰显着智能的力量。而远程抄表、空调和变配电的智能化管控,更是在节能降耗的同时,极大地提升了园区的运维效率。更令人兴奋的是,通过智慧监控、人流统计和自动访客系统等高科技手段,园区的安全防范能力得到了质的飞跃,让每一位入驻企业和个人都能享受到“拎包入住”般的便捷与安心。 更令人瞩目的是,智慧园区还构建了集信息服务、企业服务、物业服务于一体的综合服务体系。无论是通过园区门户进行信息查询、投诉反馈,还是享受便捷的电商服务、法律咨询和融资支持,亦或是利用云ERP和云OA系统提升企业的管理水平和运营效率,智慧园区都以其全面、专业、高效的服务,为企业的发展插上了腾飞的翅膀。而这一切的背后,是大数据、云计算、人工智能等前沿技术的深度融合与应用,它们如同智慧的大脑,让园区的管理和服务变得更加聪明、更加贴心。走进智慧园区,就像踏入了一个充满无限可能的未来世界,这里不仅有科技的魅力,更有生活的温度,让人不禁对未来充满了无限的憧憬与期待。

    一种欠定盲源分离方法及其在模态识别中的应用 附Matlab代码.rar

    1.版本:matlab2014/2019a/2024a 2.附赠案例数据可直接运行matlab程序。 3.代码特点:参数化编程、参数可方便更改、代码编程思路清晰、注释明细。 4.适用对象:计算机,电子信息工程、数学等专业的大学生课程设计、期末大作业和毕业设计。

    Matlab实现基于BO贝叶斯优化Transformer结合GRU门控循环单元时间序列预测的详细项目实例(含完整的程序,GUI设计和代码详解)

    内容概要:本文介绍了使用 Matlab 实现基于 BO(贝叶斯优化)的 Transformer 结合 GRU 门控循环单元时间序列预测的具体项目案例。文章首先介绍了时间序列预测的重要性及其现有方法存在的限制,随后深入阐述了该项目的目标、挑战与特色。重点描述了项目中采用的技术手段——结合 Transformer 和 GRU 模型的优点,通过贝叶斯优化进行超参数调整。文中给出了模型的具体实现步骤、代码示例以及完整的项目流程。同时强调了数据预处理、特征提取、窗口化分割、超参数搜索等关键技术点,并讨论了系统的设计部署细节、可视化界面制作等内容。 适合人群:具有一定机器学习基础,尤其是熟悉时间序列预测与深度学习的科研工作者或从业者。 使用场景及目标:适用于金融、医疗、能源等多个行业的高精度时间序列预测。该模型可通过捕捉长时间跨度下的复杂模式,提供更为精准的趋势预判,辅助相关机构作出合理的前瞻规划。 其他说明:此项目还涵盖了从数据采集到模型发布的全流程讲解,以及GUI图形用户界面的设计实现,有助于用户友好性提升和技术应用落地。此外,文档包含了详尽的操作指南和丰富的附录资料,包括完整的程序清单、性能评价指标等,便于读者动手实践。

    漫画与青少年教育关系.doc

    漫画与青少年教育关系

    励志图书的成功案例分享、人生智慧提炼与自我提升策略.doc

    励志图书的成功案例分享、人生智慧提炼与自我提升策略

    人工智能在食品安全与检测中的应用.doc

    人工智能在食品安全与检测中的应用

Global site tag (gtag.js) - Google Analytics