Jsp环境搭建及入门

jsp的全名: JavaServer Pages
jsp的意思: 动态网页

动态、静态区分

  1. 不用和是否有“动感”混为一谈
  2. 是否随着时间、地点、用户操作的改变而改变

注:动态网页需要用到服务端脚本语言(JSP)

架构

CS架构:

全名: Client Server(客户端服务)
图示:
在这里插入图片描述
优点:

1.美观

2.响应更加迅速

不足:

1.如果软件升级,那么全部软件都需要升级

2.维护麻烦,需要维护每一台客户端软件

3.每一台客户端都需要安装客户端软件

BS架构:

全名: Browser Server(浏览器服务)

在这里插入图片描述

优点: 客户端通过 浏览器直接访问服务端,弥补CS的缺点

注:BS和CS各有优势

注:JSP是基于BS的网页开发,且使用TOMCAT服务器

Tomcat

Tomcat目录

bin: 可执行文件(startup.bat shutdown.bat)

conf: 配置文件(server.xml)

lib: tomcat依赖的jar文件

log: 日志文件(记录出错等信息)

temp: 临时文件

webapps: 可执行的项目(将我们开发的项目放入该目录)

work: 存在由jsp翻译成的java,以及编辑成的class文件

注:.jsp->.java->.class,class文件才是最终执行的文件

配置tomcat

1.配置jdk(必须配置JAVA_HOME)

2.配置catalina_home 内容是tomcat的根目录

3.通过bin目录中的startup.bat可以验证是否配置成功

注:不要直接关闭tomcat,建议使用shutdown.bat关闭

常见错误

可能与其他服务的端口号冲突

tomcat端口号默认8080(此端口较为常见,容易冲突),建议修改端口

修改端口

在conf目录下的配置文件server.xml
在这里插入图片描述
修改端口之后可以通过在浏览器中输入网址 localhost:8888来验证是否修改成功

注:如果是IE浏览器需要加上www.的前缀

访问顺序

如果直接输入localhost:8888会按照中的文件顺序来依次寻找,直到找到存在的文件,然后访问
在这里插入图片描述

常见状态码

404:资源不存在

200:一切正常

403:权限不足(比如访问a目录,但是a目录设置为不可见

3xx:页面重定向

500:服务器内部错误(代码有误)

Tomcat启动时乱码

将conf文件下的logging.properties文件中的java.util.logging.ConsoleHandler.encoding = UTF-8
改为java.util.logging.ConsoleHandler.encoding = GBK

该代码位于47行

JSP项目

Jsp项目需要的东西

1.项目/WEB-INF/lib

2.项目/WEB-INF/classes

3.项目/WEB-INF/web.xml 注:主要需要的是里面的配置信息

注:项目中的lib和 tomcat中 lib的区别是,项目中的lib中的 Jar包仅仅适用于当前项目,tomcat中的lib使用与所有的 tomcat项目

jsp的意思: 在html文件中嵌套java代码

例如:
在这里插入图片描述

JSP如何访问

访问路径 localhost:8888/项目名/文件名

如果wel.xml文件中目录下存在文件名

例如:

<welcome-file-list>
       <welcome-file>index.jsp</welcome-file>
       <welcome-file>index1.jsp</welcome-file>
</welcome-file-list>

就可以通过localhost:8888/项目名来直接访问文件

如果index.jsp存在则访问它,否则访问index1.jsp

虚拟路径和虚拟主机

tomcat默认的项目路径是webapps,如果项目不放在webappps在可以通过虚拟路径来识别

注:在tomcat\server.xml文件中配置

相对路径和绝对路径

相对路径: /项目名 (建议使用)

绝对路径: D:\Apache\apache-tomcat-8.5.49\webapps\项目名

虚拟路径

docBase:实际路径

path:虚拟路径 (绝对路径,相对路径(相对于webapps))

在这里插入图片描述

配置虚拟路径之后,访问虚拟路径 /项目 相当于访问实际路径D:\study\项目

注:配置虚拟路径后需要重启 tomcat

还可以使用第二种方式来访问不在webapps中的项目

1.在D:\Apache\apache-tomcat-8.5.49\conf\Catalina\localhost中创建文件 项目名.xml

2.在xml文件中写入虚拟路径

注:该方式不需要重启 tomcat

注:如果使用 ROOT.xml作为文件名,那么访问的时候可以直接通过localhost:8888/文件名来访问项目(一般不这么用)

虚拟主机

可以改变访问项目的名字

例如可以将localhost/8888/文件名 ----》 www.jd.com

1.在conf.xml文件的目录下创建一个虚拟主机

        <Host name="www.jd.com"  appBase="D:\study\JspProject">
		<Context docBase="D:\study\JspProject" path="/" />
		</host>

appBase: 项目的发布路径

name: 项目的访问名

2.修改conf.xml文件中的默认主机

在这里插入图片描述

注:之前默认的是localhost

3.新增主机映射

在这里插入图片描述

文件位置: C:\Windows\System32\drivers\etc\hosts.SYS

4.将端口改成默认端口80

5.通过www.jd.com来访问我们自己写的jsp页面

JSP执行流程

在这里插入图片描述

第一次访问

服务端将jsp翻译成java,再将java编译成class文件

java和class的生成位置:

D:\Apache\apache-tomcat-8.5.49\work\Catalina\localhost\项目名\org\apache\jsp

注:这是第一次访问慢的原因

第二次访问

直接访问class文件

注:如果服务端代码修改了,将会在访问时重新翻译,编译

生成的JAVA文件实质

翻译命名规则: index.jsp->index_jsp.java

本质: Servlet文件

注:jsp和Servlet可以互相转换

使用Eclipse开发web项目

注:eclipse重置布局小技巧

在这里插入图片描述

1.用eclipse关联(集成)tomcat

Windows->Preferences->Server->Runtime Environment->add->Apache tomcat 8.5

2.创建 Dynamic Web Project

3.将创建的项目部署到tomcat

Jsp项目结构

在这里插入图片描述

其中WebContent是一个虚拟路径,我们可以通过localhost/8888/项目名/文件名直接访问WebContent**来目录下的文件

注:WEB-INF是一个权限很高的目录,我们无法通过localhost/8888/项目名/WEB-INF/文件名(浏览器)来直接访问,只能通过请求转发来访问

注:跳转请求转发重定向两种方式,只有请求转发可以访问

原因:因为有一些文件比较敏感,是基于安全原因导致WEB-INF只能通过请求转发访问

配置Tomcat运行时环境

如果创建的Jsp文件第一行报错,说明没有配置Tomcat的运行时环境,导致无法将jsp文件翻译成servlet(java)文件

1.将tomcat/lib中的servlet.jar加入项目的构建路径

2.右键项目->Build Path -> Add library ->Server Runtime

注:方法二的本质是将tomcat的lib中的api全部加入到项目中

统一字符编码

编码分类

设置jsp文件的编码

通过配置jsp文件中的PageEncoding属性,jsp->java的编码

设置浏览器读取jsp的编码

通过配置jsp文件中的Content属性

注:一般需要将上述统一设置为UTF-8(国际化编码),大小写不区分

文本编码

1.统一设置(设置Eclipse中的所有的文件,从当前时间开始)

Windows->Preferences->web->Jsp File

2.设置某一个项目的编码

右键项目->Properties->Resource

3.设置单独文件的编码

右键文件->Properties->Resource

部署Eclipse中tomcat的端口

在这里插入图片描述

选择第一个,是在将项目部署到eclipse的tomcat上是,复制一份tomcat使用,存储在server文件夹中

选择第二个,是指eclipse中的tomcat与本地的tomcat一致的,即将eclipse中的tomcat设置为托管模式

JSP路径问题

根目录

1.src(构建路径)

2.WebContent**(Idea中为web**)

例子

a. WebContent中有个index.jsp,index.jsp中有xxx,那么系统会在webContent和src中寻找abc

b. 对于xxx,系统会在src和Content中找QQ,然后再在QQ目录中找abc

/的含义

/出现在不同的文件有着不同的含义

web.xml:/(打头)代表着项目路径, http://localhost:8888/项目名/

例如:/WelcomeServlet的含义是http://localhost:8888/JspProject/WelcomeServlet*

.jsp:/(打头)代表服务器根路径 http://localhost:8888/

例如:/a/index.jsp的含义是http://localhost:8888/a/index.jsp

JSP中的页面元素

脚本Scriptlet

1. 局部变量、常见java语句

<%
	String name = "zhangsan";
	out.print("hello...." + name);
	//换行
	out.print("<br/>");
%>

2. 定义全局变量,定义方法

<%!	

		public String bookName; //全局变量`

		public void init(){
            bookName="java书";

		}

%>
  1. 输出表达式
<%="hello..." + bookName %>

注:修改jsp代码不需要重启tomcat服务器,修改web.xml、配置文件、java代码需要重新启动 tomcat服务器

注:jsp中脚本中可以直接解析HTML代码,直接在里面写就行

指令

<%@ page language="java" contentType="text/html; charset=UTF-8"  pageEncoding="UTF-8"  import = "java.util.Date"%>

Page指令

位置: 写在网页的开头

属性:

language: jsp页面使用的脚本语言

import: 导入类

pageEncoding: jsp文件的自身编码 jsp->java

contentType: 浏览器解析jsp的编码

注释

html注释

<!-- -->

注:用户可以通过浏览器查看源码,所观察到

java注释

//
/* … */

JSP注释

<%-- %–>

get和post区别

get提交方式

method=“get” 和地址栏、超链接()等请求方式都默认为get方式

在这里插入图片描述

形式为: 链接/文件?参数名1=参数值1&参数名2=参数值2&参数名3=参数值3

post提交方式

method=“post”

在这里插入图片描述

形式为: 链接/文件

注:如果没有特别标明,建议使用post方式

区别

  1. get会在地址栏显示请求信息(但是地址栏能容纳的信息有限,大概是4kb-5kb),post不会显示
  2. 文件上传操作必须使用post提交方式
  3. 推荐使用post方式,因为get方式将信息暴露出来了,不安全

重定向和请求转发

重定向

使用:

response.sendRedirect(String location);

在这里插入图片描述

说明: 客户端向服务端发送两次请求,服务端返回两次响应给客户端

请求转发

使用:

request.getRequestDispatch(String location).forward(reques,response)

在这里插入图片描述

说明: 客户端向服务端发送一次请求,服务端进行一次浏览器内部的跳转,服务端返回一次响应给客户端

区别

请求转发 重定向

地址栏是否改变 不变 改变

是否保留第一次请求时数据 保留 不保留

请求的次数 1次 2次

如何理解

请求转发: 张三(客户端)去找李四(服务端) 办事,李四(服务端) 说:“我办不了,但是我可以去帮你找 王五(服务端) 来办”;

重定向: 张三(客户端) 去找李四(服务端) 办事,李四(服务端) 说:“我办不了,你自己去找王五(服务端) 办吧”;

JSP九大内置对象

指自带的,不需要new 也可以使用的对象

注:Jsp九大内置对象中又有四种范围对象,根据编号范围从小到大

1.out

输出对象: 向客户端输出内容

用法(常用对象)

在jsp页面中可以直接调用:

out.print(String str)

在servlet页面中:

PrintWriter out = response.getWriter();

注:在servlet调用out,会作用在请求该serlvet的jsp页面中

2.request(2.范围对象)

请求对象: 存储“客户端”向服务端发送的请求信息

在这里插入图片描述

用法(常用对象)

在jsp页面:

作为内置对象可以直接调用

在servlet页面:

作为doGet()和doPost()方法的形参,可以直接调用

抓取类型

1.jsp页面的form表单

<form action="">
    姓名:<input type="text" name="name"><br/>
    <input type="submit" value="提交">
</form>>

action的值是 数据传送的地方

2.jsp页面的超链接

<a href=""?name="xxx">提交</a>

href的值是 数据传送的地方

作用域

只在同一次请求有效
在这里插入图片描述
解释

第一次请求: 通过输出姓名和密码来跳转到check.jsp页面,check.jsp可以拿到姓名和密码的信息

第二次请求: 在check.jsp页面直接通过浏览器再次访问check.jsp页面是相当于第二次请求了check.jsp页面,拿不到姓名和密码的信息

注:如果是按F5刷新的话,是相当于重新输入账号,密码从 login.jsp页面跳转到 check.jsp页面,这样是不会丢失数据的

常见方法

String getParameter(String name)  

根据请求的字段名key,返回字段值value,key通常是标签内的name属性的值,value通常是根据请求的字段名key,返回字段值value,key通常是标签内的value属性的值

String[] getParameterValues(String name )

根据请求的字段名key,返回多个字段值value,常见多选按钮checkbox

void setCharacterEncoding("编码格式UTF-8")

设置请求编码,tomcat7以前默认iso-8859-1,tomcat8之后默认utf-8

getRequestDispatch("b.jsp").forward(reques,response)

请求转发的方式跳转页面,当前页面跳转到b页面

 getServerContext()

获取项目的ServletContext对象

例子

使用request对象实现注册和展示功能

注册页面:

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>注册页面</title>
</head>
<body>
	<form action="show.jsp">
	姓名:<input type="text" name="uname" /><br/>
	密码:<input type="password" name="upwd"/><br/>
	年龄:<input type="text" name="uage" /><br/>
	爱好:<input type="checkbox" name="uhobbies" value="足球" /> 足球、
		<input type="checkbox" name="uhobbies" value="篮球" /> 篮球、
		<input type="checkbox" name="uhobbies" value="羽毛球" /> 羽毛球<br/>
		<input type="submit" value="注册" />
	</form>

</body>
</html>

显示页面:

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>显示页面</title>
</head>
<body>
	<%
	//设置POST形式的编码,仅仅对post有效
	request.setCharacterEncoding("utf-8");
	String name = request.getParameter("uname");
	String password = request.getParameter("upwd");
	int age = Integer.parseInt(request.getParameter("uage"));
	String[] hobbies=request.getParameterValues("uhobbies");
	%>
	注册成功<br/>
	姓名:<%=name+"<br/>" %>
	密码:<%=password + "<br/>" %>
	年龄:<%=age + "<br/>" %>
	爱好:
	<%
		if(hobbies!=null){
			for(String hobby:hobbies){
				out.print(hobby+"&nbsp");
			}
		}
	%>>

</body>
</html>

注:使用get方法会将信息存储在地址栏,我们可以直接通过地址栏来提交信息

在这里插入图片描述

3.response

响应对象: 响应客户端的请求
在这里插入图片描述

用法(常用对象)

在jsp页面:

作为内置对象可以直接调用

在servlet页面:

作为doGet()和doPost()方法的形参,可以直接调用

常用方法

void addCookie(Cookie cookie)

服务端向客户端增加cookie对象

void sendRedirect(String loaction) throws IOException

页面跳转的一种方式,重定向

void setContentType(String type)

设置服务端content类型

cookies

介绍: 是客户端对象,不是内置对象。由服务端产生,发送给客户端保存

产生: javax.servlet.http.Cookie

作用: 相当于本地缓存的作用,可以提高效率

缺点: 因为会将数据缓存在本地(客户端),所以这导致数据安全性较差

用法
public Cookie(String name,String value) 
常见方法
String getName();

获取name

String getValue();

获取value

void setMaxAge(int expiry)

设置最大有效期,单位为秒

服务端发送给客户端流程

1.创建Cookie

Cookie cookie = new Cookie(String name,String value)

2.利用response

response.addCookie(Cookie cookie)

3.页面跳转(转发,重定向都可以)

4.客户端获取cookie

Cookie[] cookies = request.getCookies()

注:不能获取某个Cookie,而是一次性获取所有的Cookie

注:建议Cookie中的name只保存数字或者英文,不然的话需要编码和解码处理

login.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>登录页面</title>
</head>
<body>
<!-- 因为客户端是共享Cookie的,所以这里也可以访问到Cookie-->
    <%!
        //需要使用全局变量
      String uname;
    %>

    <%
        Cookie[] cookies = request.getCookies();
        for (Cookie cookie:
             cookies) {
            if(cookie.getName().equals("name")){
                uname = cookie.getValue();
            }
        }
    %>
        <form action="check.jsp" method="post">
        姓名:<input type="text" name="uname" value="<%=uname==null?"":uname%>" /><br/>
        密码:<input type="password" name="upwd"/><br/>
            <input type="submit" value="登录">
        </form>
</body>
</html>

check.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>核查页面</title>
</head>
<body>
    <%
      String name = request.getParameter("uname");
      String pwd = request.getParameter("upwd");
      Cookie cookie = new Cookie("name",name);
      //设置最大存在时间,单位为秒
      cookie.setMaxAge(7200);
      response.addCookie(cookie);
      //利用重定向,将cookie发送到客户端
        response.sendRedirect("success.jsp");
    %>

</body>
</html>

success.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>成功页面</title>
</head>
<body>
    登录成功<br/>
    欢迎你
    <%
       String name = request.getParameter("uname");
       out.print(name);
    %>

</body>
</html>

4.Session(3.范围对象)

说明

**会话对象:**会话,即一次开始到一次结束的过程

例子:

1.浏览网站 打开-关闭 是一次对话

2.购物 浏览-付款-退出 是一次对话

用法(常用对象)

在jsp页面:

作为内置对象可以直接调用

在servlet页面:

HttpSession session = request.getSession();

运行机制(原理)

1.客户端在第一次请求服务端的时候,会生成一个Session对象(用于保存该客户的信息)

2.每个Session对象都会有一个唯一的SessionID(用于区分其他Session)

3.服务端会产生一个Cookie,并且该Cookie的name是JSESSIONID,key是SessionID

4.服务端会在响应客户端的时候,将Cookie发送给客户端

5.客户端中的Cookie就可以合服务端中的Session一一对应(实际上是通过JSEIONIDSessionID

作用: 服务端通过JSESSIONID来区别客户

注:客户端会通过JSESSIONID去和服务端中的 Session通过 SessionID进行匹配

如果匹配成功,说明此用户不是第一次访问,将服务端存储的信息显示给客户端

如果匹配失败,就是第一次访问,就行处理

理解

在这里插入图片描述
**客户端:**顾客

​ **服务端:**商场存包处

JSESSIONID: 钥匙

SessionID: 柜子

Session中的信息: 柜子中的物品

情景:

​ 顾客去商场存包处

处理:

商场存包处会通过顾客手中是否有钥匙(并且钥匙要和柜子匹配)来判断是否是新顾客

​ 如果顾客手中没有钥匙说明是新顾客,就分配一把钥匙给新顾客

​ 如果手中有钥匙说明是老顾客,就不用分配钥匙

​ 一个钥匙和一个柜子一一对应,顾客可以通过钥匙来获取柜子中的物品

Session特点

1.session存储在服务端

2.session是在同一个用户请求时共享,即每一个顾客都有一个只属于自己的柜子

3.session同一次会话共享
在这里插入图片描述
解释: session在同一次会员中,所有的页面都可以访问到session,但是切换浏览器就需要重新进行会话过程

注:Session存储在服务端,Cookie存储在客户端

方法

String getId()

获取sessionId

boolean isNew()

判断是否是 新用户(第一次访问)

void invalidate()

使session失效(场景为:退出登录,注销),实际是清空session对象里的东西,并不是清除session这个对象本身

void removeAttribute(String str)

清空某个session对象(一般使用invalidate())

void setAttribute(String str,Object obj)

利用session来保存信息,使用key-value对的形式

void getAttribute(String str)

获取session中保存的信息

Object setMaxInactiveInterval(int s)

设置最大有效 非活动时间 单位为秒

int getMaxInactiveInterval()

获取最大有效 非活动时间 单位为秒

非活动时间: 浏览器没有经过任何操作的时间

### 例子

login.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>登录页面</title>
</head>
<body>
        <form action="check.jsp" method="post">
        姓名:<input type="text" name="uname" /><br/>
        密码:<input type="password" name="upwd"/><br/>
            <input type="submit" value="登录">
        </form>
</body>
</html>

check.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>核查页面</title>
</head>
<body>
    <%
      String name = request.getParameter("uname");
      String pwd = request.getParameter("upwd");
      if("zs".equals(name)&&"abc".equals(pwd)){
          //说明成功登录
          response.sendRedirect("success.jsp");
          session.setAttribute("name",name);
      }else {
          response.sendRedirect("login.jsp");
      }
    %>

</body>
</html>

success.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>成功页面</title>
</head>
<body>
    登录成功<br/>
    欢迎你
    <%
       String name = (String) session.getAttribute("name");
       if(name!=null) {
           out.print(name);
       }else {
           request.getRequestDispatcher("login.jsp").forward(request,response);
       }
    %>

</body>
</html>

Cookie和Session的区别

Session Cookie

保存位置 服务器 客户端

安全性(相对) 安全 不安全

保存的内容 Object String

5.Application(4.范围对象)

说明

**全局对象:**获取当前项目的所有信息

用法(常用对象)

在jsp页面:

作为内置对象可以直接调用

在servlet页面:

HttpSession session = req.getSession();
ServletContext application = request.getServletContext();

方法

String getContextPath()

获取虚拟路径

String getRealPath(String name)

获取绝对路径(虚拟路径对应的)

6.Config

说明

配置对象

用的较少

7.page

说明

当前页面对象:相当于Java的This

用的较少

8.pageContext(1.范围对象)

说明

JSP页面容器对象

用的较少

9.exception

说明

异常对象

用的较少

四种范围对象

共有的方法

Object getAttribute(String name)

根据属性名获取属性值

void setAttribute(String name,Object obj)

设置属性值

void removeAttribute(String name)

根据属性名删除对象

作用范围

注:根据编号从小到大

1.pageContext

范围: 当前页面有效

2.request

范围:同一次请求有效

解释: 请求转发有效,重定向无效

3.session

范围:同一次会话有效

解释: 关闭(切换)浏览后无效

4.application

范围:整个项目(全局)有效

解释: 整个项目运行期间,切换浏览器也有效。关闭浏览器,关闭项目后无效

注:以上四个范围对象都是通过 以上的三个方法进行取值和赋值,而且尽量使用范围最小的对象,这样可以减小开销,降低性能的损耗

JDBC

全名: java DataBase Connectivity

作用

图形示例

为多种关系型数据库DBMS 提供统一的访问,用java的方式来操作数据库

在这里插入图片描述

JDBC API主要功能

在这里插入图片描述
DriverManager: 管理JDBC驱动

Connection: 连接数据库

Statement(PreparedStatement) : 发送增删改查语句

CallableStatement: 调用数据库中的 存储过程/存储函数

Result: 返回的结果集

数据库驱动对应的jar包

数据库 驱动jar包

Oracle -------- ojdbc-x.jar

MySQL-------- mysql-connection-java-x.jar

SqlServer----- sqljdbc-x.jar

注:x是版本号,使用的jar版本要和数据库版本一致

JDBC访问数据库的步骤

1.导入驱动包,加载具体的驱动类

2.与数据库建立连接

3.获取操作数据库的对象

4.处理结果集(针对查询操作)

例子

public class JdbcTest {
    public static void main(String[] args) throws ClassNotFoundException, SQLException {
        //1.加载驱动程序
        Class.forName("com.mysql.cj.jdbc.Driver");
        //2.获取数据库连接
        Connection connection = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/book?useSSL=false&useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2B8", "root", "root");
        //3.获取操作数据库的对象,三种
        Statement statement = connection.createStatement();
        String sql = "select * from category";
        ResultSet resultSet = statement.executeQuery(sql);
        //4.处理结果集
        while (resultSet.next()){
            int id = resultSet.getInt("id");
            String cname = resultSet.getString("cname");
            System.out.println("id:" + id + "   cname" +cname);
        }
        statement.close();
        connection.close();
    }
}

statement

用法:

通过连接获取Statement对象

Statement statement = connection.createStatement();

Statement接口

  • 用于执行静态SQL语句并返回它所生成结果的对象。

三种Statement类

  • Statement:
    • 由createStatement创建,用于发送简单的SQL语句(最好是不带参数的)
  • PreparedStatement:
    • 继承Statement接口,由preparedStatement创建,用于发送含有一个或多个输入参数的sql语句。PreparedStatement对象比Statement对象的效率更高,实行是参数化查询,这样可以防止SQL注入。
  • CallableStatement:
    • 继承自PreparedStatement。由方法prePareClas创建,用于调用存储过程。

常用的Statement方法

  • execute():运行语句,返回是否有结果集。
  • executeQuery():运行select语句,返回ResultSet结果集
  • executeUpdate():运行insert/update/delete操作,返回更新的行数

ResultSet接口

  • Statement执行SQL语句时返回ResultSet结果集
  • ResultSet提供的检索不同类型字段的方法,常用的有:
    • getString():获得在数据库里是varchar、char等数据类型的对象
    • getFloat():获得在数据库里是Float类型的对象
    • getDate():获得在数据库里是Date类型的数据
    • getBoolean():获得在数据库里是Boolean类型的数据

注:处理结果集ResultSet,使用的方式很像迭代器,但是并不是迭代方式

关闭的顺序:要分开关闭

  1. ResultSet
  2. Statement
  3. Connection

PreparedStatement(推荐)

用法

通过connection获取preStatement对象

Preparedstatement pstm = connection.PreparedStatement();

例子

public static int delete(int id) throws SQLException {
    Connection connection = linkDataBase();
    String sql = "delete from category where id = ?";
    PreparedStatement pstm = connection.prepareStatement(sql);
    pstm.setInt(1,id);
    int update = pstm.executeUpdate();
    pstm.close();
    connection.close();
    return update;
}

优势

1.编码更加简便(避免的了字符串的拼接)

String sql = " insert into student(stuname,stuage) values('"+name+',"age") ";
stmt.executeUpdate(sql);

使用statement的sql语句,遇到char类型需要加’ ';

String sql = " insert into student(stuname,stuage) values(?,?) ";

//预编译SQL

pstm = connection.prepareStatement(sql);

pstm.setString(1,name);

pstm.setInt(2,age);

使用preparedStatement,就可以直接用?充当占位符

2.提高性能

在重复使用sql语句的过程中。因为预处理的过程,preparedStatement只需要编译一次(预编译),而Statement需要编译多次,这样提高了性能

3.安全(可以有效的防止sql注入)

sql注入: 将客户输出的内容与开发人员的SQL语句混为一起

例如:

在实行登录操作的时候的sql语句

select count(*) from login where name = '"+name+"'and pwd='"+pwd+"'

我们可以通过name= 任意值’ or 1=1 --来完成sql注入

select count(*) from login where name = '任意值' or 1=1 --'and pwd='任意值';

注:该语句相当于

select count(*) from login;

分析:

对于上诉的sql语句,name=‘任意值’ 为false,1=1为true, 所以总结果为true,而且 – 为注释符号,后面的全部被注释了,这样就可以跳过sql的检查,直接登录成功

原因: preparedStatement会自动帮我们补齐’ ',这样可以避免SQL注入

CallableStatement

用法

CallableStatement cstm = con.prepareCall("存储过程或存储函数");

例子

//调用存储过程
public static int invokeProcedure() throws SQLException {
    Connection con = linkDataBase();
    CallableStatement cstm = con.prepareCall("{call addTwoNum(?,?,?)}");
    cstm.setInt(1,20);
    cstm.setInt(2,30);
    //设置返回值类型
    cstm.registerOutParameter(3, Types.INTEGER);
    cstm.execute();
   int result = cstm.getInt(3);

   cstm.close();
   con.close();
   return result;
}

//调用存储函数
public static int invokeFunction() throws SQLException {
    Connection con = linkDataBase();
    CallableStatement cstm = con.prepareCall("{?=call addTwoNum(?,?)}");
    cstm.setInt(2,20);
    cstm.setInt(3,30);
    //设置返回值类型
    cstm.registerOutParameter(1, Types.INTEGER);
    cstm.execute();
    int result = cstm.getInt(1);

    cstm.close();
    con.close();
    return result;
}

JSP访问数据

说明

Jsp就是在Html中嵌套Java代码,因此Java代码可以写在jsp中(<% … %>)

导包操作

注:针对idea

Java项目:

1.将jar复制到项目中,然后add as Lirbary

2.点击Project Structure->Project Settings->Modules->Dependencies->add jar

Web项目:

1.直接将jar包复制到WEB-INF/lib中,然后add as libary

缺点

在jsp页面写java代码会导致代码十分混乱,所以我们在src目录下写java代码

命名规范

xxxDao(数据访问层): 用于访问数据库

例子

check.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>核查页面</title>
</head>
<body>
    <%
      String name = request.getParameter("uname");
      String pwd = request.getParameter("upwd");
        LoginDao dao = new LoginDao();
        int result = dao.login(name, pwd);
        if(result>0){
            request.getRequestDispatcher("success.jsp").forward(request,response);
        }else if (result==0){
            out.print("账号或者密码错误");
        }else {
            out.print("系统错误!!!");
        }
    %>

</body>
</html>

LoginDao.java

package JspAndDataBase;
import java.sql.*;

public class LoginDao {
    public static Connection getConnection(){
        try {
            Class.forName("com.mysql.cj.jdbc.Driver");
            Connection connection = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/jsp?useSSL=false&useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2B8", "root", "root");
            return connection;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
    public static void close(Connection conn, PreparedStatement pstm, ResultSet rs) throws Exception{
        if(conn!=null) conn.close();
        if(pstm!=null) pstm.close();
        if (rs!=null) rs.close();
    }
    public int login(String name,String pwd) {
        int result = -1;
        PreparedStatement pstm=null;
        Connection con = null;
        ResultSet rs= null;
        int index = 0;
        try {
            String sql = "select count(*) from login where name= ? and password=?";
            con = getConnection();
            pstm = con.prepareStatement(sql);
            pstm.setString(1,name);
            pstm.setString(2,pwd);
             rs = pstm.executeQuery();
            while (rs.next()){
                index++;
            }
            result = index;
            return index;

        } catch (SQLException e) {
            e.printStackTrace();
            return result;
        }finally {
            try {
                close(con,pstm,null);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

注:如果Jsp出现错误:The import Xxxx Cannot be resolved

解决方案:

a: 可能是tomcat和jdk的版本问题,右键build Path,删除其中报错的Libary和lib,重新导入

b: 清空各种缓存,Clean Tomcat,Clean Project,或者进入Tomcat删除work的子目录

c: 删除tomcat,然后重启后重新安装,配置

d: 如果之前类没有包,那么就将该类加入包中

JavaBean

定义

实质上是一个java类

要求满足以下两点:

1.public修饰的类,具有public 无参构造

2.所有属性都是private,并且实现get,set方法(如果是boolean类型,可以用is方法代替get方法)

注:系统自动提供的无参构造是public修饰的,所以上述的LoginDao.java也是一个JavaBean

作用

1.减轻jsp的复杂度

2.提高代码复用(例如:以后登录只需要调用LoginDao.java)

分类

封装数据的javaBean

数据

package JavaBean;

public class Student {
    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

注:一般对应于数据库中的一张表

优点

之前的jsp传值
缺点:如果不使用JavaBean,用之前的传值方式,一旦需要传的值太多的话,那么需要传递很多数据,很麻烦
javaBean传值
优势:使用JavaBean将数据封装成一个类对象,那么无论需要传递多少值,都只需要传递一个类对象

封装业务逻辑的JavaBean

逻辑

之前写的LoginDao.java

注:用于操作一个封装数据的一个JavaBean

MVC设计模式

MVC概念图示
在这里插入图片描述
MVC流程图示
在这里插入图片描述

Model(模型

作用:

实现功能(例如,增加,删除)

实现:

利用JavaBean实现

View(视图

作用:

用于展示、以及与用户交互

实现:

html、js、css、jquery等前端技术实现

Controller(控制器

作用:

1.接收请求,将请求跳转到模型进行处理

2.模型处理完毕,将处理结果返回给请求处

实现:

可以用jsp实现 (不推荐)

建议使用Servlet实现

注:Jsp->Java(Servlet)->Class

Servlet

要求

Java类必须符合一定的规范

a. 必须继承 javax.servlet.http.HttpServet

b. 重写其中的doGet()或doPost()方法

doGet: 接收 并 处理 所有的get提交方式的请求

doPost: 接收 并 处理 所有的post提交方式的请求

注:一般而言,无论是访问doGet还是doPost都是用一个方法去解决

public class WelcomeServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("Welcome..........");
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        this.doPost(req, resp);
    }
}

使用

要想使用Servlet需要配置

Servlet2.5版本

要求: 配置web.xml

流程示意图:
在这里插入图片描述
解释:

1.jsp页面请求Servlet处理

2.请求被标签拦截

3.根据标签中的寻找标签中标签(二者同名

4.中的标签访问对应的Servlet类(全类名

注:标签中填入的应该是请求访问的页面在项目的相对路径,
注: /WelcomeServlet

例如:

下面的例子中,servlet.jsp所在的目录是WebContent/Servlet/servlet.jsp,所以标签内的路径为

/Servlet/WelcomeServlet

注:/WelcomeServlet的含义是http://localhost:8888/JspProject/WelcomeServlet

例子
servlet.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
            <a href="WelcomeServlet">WelcomeSerlvet</a>
</body>
</html>

WelcomeServlet.java

public class WelcomeServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("Welcome..........");
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
       this.doPost(req, resp);
    }
}

web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">

    <servlet>
        <servlet-name>WelcomeServlvet</servlet-name>
        <servlet-class>Servlet.WelcomeServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>WelcomeServlvet</servlet-name>
           <!-- 相对路径为请求的jsp页面的路径-->
        <url-pattern>/Servlet/WelcomeServlet</url-pattern>
    </servlet-mapping>
</web-app>

Servlet3.0版本

要求: 不需要配置web.xml,只需要在Servlet类的之上写注解@webServlet(“的值”)

解释: 请求地址与注解@webServlet中的值进行匹配,如果匹配成功,则说明请求的就是注解的Servlet类

WelcomeServlet2.java

//注解的内容为请求页面的相对路径
@WebServlet(value="/JspServlet/WelcomeServlet2")
public class WelcomServlet2 extends HttpServlet {
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        System.out.println(".................Welcome....");
    }

    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
            this.doGet(request,response);
    }
}

servlet.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
            <a href="WelcomeServlet2">WelcomeSerlvet</a>
</body>
</html>

注:对于注解@WebServlet("/WelcomeServlet2")@WebServlet(urlPatterns ="/WelcomeServlet2")是等价的

注:对于注解@WebServlet("/WelcomeServlet2")@WebServlet(value ="/WelcomeServlet2")也是等价的

生命周期

Servlet的生命周期为5个
在这里插入图片描述
加载: Servlet容器自动处理

初始化: 该方法会在Servlet被加载的时候自动执行 只在Servlet被第一次访问的时候执行一次(可修改),且执行一次

服务: 主要是doget()和doPost两个方法 调用几次,执行几次

销毁: Servlet被系统回收时会自动执行 只在tomcat关闭时执行,且只执行一次

卸载: Servlet容器自动处理

初始化详解

第一次访问Servlet时会执行,且只执行一次(默认,但可以修改成Tomcat启动时自动执行)

修改方式:

对于Servlet2.5:

<servlet>
    <servlet-name>WelcomeServlvet</servlet-name>
    <servlet-class>Servlet.WelcomeServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
</servlet>

加入1这段代码,可以让init()在tomcat启动时执行

对于Servlet3.0:

//注解的内容为请求页面的相对路径
@WebServlet(urlPatterns = "/JspServlet/WelcomeServlet2",loadOnStartup = 1)
public class WelcomServlet2 extends HttpServlet {
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        System.out.println(".................Welcome....");
    }

    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
            this.doGet(request,response);
    }
}

注:1的意思为,当存在多个Servlet需要Tomcat启动时自动执行,执行的顺序

Servlet API

组成: Http协议的软件包+除Http协议以外的其他软件包(即Servlet API适用于所有的通信协议)

注: 这次主要是学习javax.servlet.http,即 Http协议的软件包中类和接口,是基础的 HTTP协议

在这里插入图片描述

其中,单个是继承,三个的是实现

ServletConfig

成分: 接口

方法
ServletContext getServletContext();

获取Servlet上下文对象,返回值用于创建application对象

String getInitParameter(String name);

在当前Servlet范围内,获取名为name的参数值(初始化参数)

注:ServletContext类中同样有一个方法为String getInitParameter(String name);

例子:

WelcomeSerlvet.java

public class WelcomeServlet extends HttpServlet {
    @Override
    public void init() throws ServletException {
        System.out.println("init.......");
        //获取当前servlet的初始化参数值
        String servletValue = super.getInitParameter("servletParamter");
        System.out.println("servletValue: " +servletValue);
        //获取整个容器的初始化参数值
        ServletContext servletContext = super.getServletContext();
        String globalValue = servletContext.getInitParameter("globalParamter");
        System.out.println("globalValue: " +globalValue);
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("Welcome..........");
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
       this.doPost(req, resp);
    }
}

web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">

    <servlet>
        <servlet-name>WelcomeServlvet</servlet-name>
        <servlet-class>Servlet.WelcomeServlet</servlet-class>

        <!-- 要求放在<load-on-startup>上面-->
        <init-param>
            <!-- 当前Servlet的初始化参数-->
            <param-name>servletParamter</param-name>
            <param-value>servletvalue...</param-value>
        </init-param>

        <load-on-startup>1</load-on-startup>

    </servlet>
    <context-param>
        <!-- 整个容器的初始化参数 -->
        <param-name>globalParamter</param-name>
        <param-value>globalVale</param-value>
    </context-param>

    <servlet-mapping>
        <servlet-name>WelcomeServlvet</servlet-name>
        <!-- 相对路径为请求的jsp页面的路径-->
        <url-pattern>/JspServlet/WelcomeServlet</url-pattern>
    </servlet-mapping>
</web-app>

注:Servlet3.0版本同样是在注解中写,如下所示,并且Servlet3.0不能通过注解配置整个容器的初始化参数,只能通过web.xml

@WebServlet(initParams = {@WebInitParam(name="ServletParamter",value = "ServletValue")})

Servlet

重点是sevice方法,他是服务过程调用的函数

在这里插入图片描述

所以对象req,和对象resp的方法为JSP中 request和response的方法,其中就包括了请求转发和重定向

解释
void service(ServletRequest servletRequest, ServletResponse servletResponse)

上述的方法来着接口Servlet

public abstract void service(ServletRequest servletRequest, ServletResponse servletResponse)

抽象类GenericServlet继承该方法

    @Override
    public void service(ServletRequest req, ServletResponse res)
        throws ServletException, IOException {

        HttpServletRequest  request;
        HttpServletResponse response;

        try {
            request = (HttpServletRequest) req;
            response = (HttpServletResponse) res;
        } catch (ClassCastException e) {
            throw new ServletException(lStrings.getString("http.non_http"));
        }
        service(request, response);
    }
}

抽象类HttpServlet继承了GenericServlet,并且重写了该方法,主要是将req,和res转换成只被http协议访问的参数

然后http重载了service方法,然后将请求分类(get,post…),然后调用对应的方法进行处理,例如get请求就调用doGet()方法

空实现的优势

实际上这是设计模式中的接口适配器模式

在这里插入图片描述

如果不使用空实现,那么实现类在实现接口的时候需要实现所有的抽象方法,就算是只使用其中一个方法,也得全部实现

例如: 我只需要使用add()方法,但是我却要把上述三个方法都实现

在这里插入图片描述

如果使用空实现,因为我们空实现类已经实现(空实现)了接口的所有抽象方法,所以当我们需要哪个方法时,只需要继承空实现类,然后重写该方法即可

例如: 我需要add()方法,我只需要写一个add类来继承空实现类,然后重写其中的add()方法

MVC实例

图示

在这里插入图片描述

modul层

数据存储的JavaBean

package mvcTest.entry;

//JaveBean
public class Login {
    private String name;
    private String password;

    public Login(){
    }

    public Login(String name, String password) {
        this.name = name;
        this.password = password;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }
}

业务逻辑的JavaBean

package mvcTest.dao;

import mvcTest.entry.Login;
import java.sql.*;

public class LoginDao {
    public static Connection getConnection(){
        try {
            Class.forName("com.mysql.cj.jdbc.Driver");
            Connection connection = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/jsp?useSSL=false&useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2B8", "root", "root");
            return connection;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
    public static void close(Connection conn, PreparedStatement pstm, ResultSet rs) throws Exception{
        if(conn!=null) conn.close();
        if(pstm!=null) pstm.close();
        if (rs!=null) rs.close();
    }
    public static int login(Login login) throws Exception {
            int flag=0;
            Connection con = getConnection();
            String sql = "select count(*) from login where name=? and password = ?";
            PreparedStatement pstm = con.prepareStatement(sql);
            pstm.setString(1,login.getName());
            pstm.setString(2,login.getPassword());
            ResultSet rs = pstm.executeQuery();
            while (rs.next()){
                flag++;
            }
            close(con,pstm,rs);
            return flag;
    }
}

view层

Login.jsp 路径:web/mvcTest/Login.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>登录页面</title>
</head>
<body>
        <form action="LoginController" method="post">
             姓名:<input type="text" name="uname" /><br/>
            密码:<input type="password" name="upwd"/><br/>
            <input type="submit" value="登录">
        </form>
</body>
</html>

**success.jsp ** **路径:**web/mvcTest/xx/success.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>登录成功</title>
</head>
<body>
        登录成功!!!!!!!!!!!!!!!!!

</body>
</html>

controller层

package mvcTest.controller;

import mvcTest.entry.Login;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.sql.SQLException;

import static mvcTest.dao.LoginDao.login;

@WebServlet("/mvcTest/LoginController")
public class LoginController extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
            this.doGet(request,response);
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
         String name = request.getParameter("uname");
         String password = request.getParameter("upwd");
         Login login = new Login(name,password);
        System.out.println(login.getName());
        try {
            int result = login(login);
            if(result>0){
                //路径为请求页面的相对路径
                response.sendRedirect("xx/success.jsp");
            }else {
                response.sendRedirect("login.jsp");
            }
        } catch (SQLException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

三层架构(ThreeTiar)

与MVC设计模式设计的初衷一致,都是为了 解耦合,提高代码复用度

但是,二者对于项目的理解不同

表示层

全名: USL (User Show Layer)

常用名: 视图层

前台代码

作用: 对应于MVC中的View,用于和用户交互,界面的显示

实现技术: jsp js html css jquery等前端技术实现

位置: WebContent

后台代码

作用: 对应于MVC中的Controller,用于控制跳转,调用业务逻辑

实现技术: Servlet (SpringMVC Struts2)

位置: xxx.servlet包中

业务逻辑层

全名: BLL(Business Logic Layer)

常用名: dao层

作用:

用于组装数据访问层,带逻辑性的操作

用于接收表示层的请求,调用

位置:xxx.service包中(主流)或者xxx.managerxxx.bll

数据访问层

全名: DAL(Data Access Layer)

常用名 : service层

作用: 直接访问数据库的操作,原子性的操作(增删改查)

位置: xxx.dao包中

图示

在这里插入图片描述
流程:

​ 上层将请求发送给下层,下层处理完返回给上层

比MVC模式比较

在这里插入图片描述
三层架构中不包括实体类,但是需要实体类来进行数据的传递

三层架构的关系

上层将请求发送给下层,下层处理完返回给上层

上层依赖于下层,即上层持有下层的成员变量

例如:A类中有B类的对象,那么A类依赖于B类

class  A {
   private int id;
   private B b;
}

class B{
    private int id;
}

在这里插入图片描述

优化

建议面向接口编程:即先接口,再实现类

接口和实现类命名规范:

接口(interface): XXXX 例如:StudentService StudentDao

实现类(implements): XXXXImple StudentServiceImpl StudentDaoImpl

页面分页

MySQL实现分页

假设每页显示10条数据

第n页 开始 结束

​ 0 ------- 0------------- 9

​ 1 ------- 10----------- 19

​ n ------- n*10 -------- (n *10)+9

注:mysql计数是从0开始

sql语句: select * from table limt 0,10 获取第一页的数据

数据需求

1.总数据的数量

2.页面大小(每页数据的条数)

3.总页数(可以根据总数据数量和页面大小计数得到)

4.当前页面

5.当前页面的数据集合

流程图示

在这里插入图片描述

Logo

CSDN联合极客时间,共同打造面向开发者的精品内容学习社区,助力成长!

更多推荐