Tomcat
启停服务
-
打开tomcat压缩包,在bin里面,双击startup.bat,然后弹出CMD窗口,不要关闭
-
打开浏览器,输入localhost:8080,能看到tomcat的默认主页
将web应用放置到服务器
- 直接把web应用文件夹复制到webapps里面去,文件夹名就是访问路径
-
在conf/server.xml里的host标签内配一个Context标签
-
在conf/Catalina/localhost文件夹下 添加一个xml文件
-
将真实web应用文件夹打包为war包,然后将war包放置到webapps里面
- 当前项目文件夹下输入cmd回车
-
使用jar命令
-
进行压缩
jar -cvf FF.war *
FF.war要压缩的文件名 *所有文件进行压缩
- 移动.war位置,从原项目下剪切到webapps
- 启动tomcat,进行访问
- 经tomcat的后台管理里的应用管理,远程上传一个war文件(远程上传,停止卸载方便)
- 输入要配置的用户名,密码等内容
-
压缩war包
-
点击Manager App
-
输入配置内容
-
部署
-
根据war包名称访问
关于ROOT
浏览器访问页面
增加主机
- 增加Host标签
-
写入本地文件
-
新建文件夹
-
访问:
www.kiddkid.com:8080/FFFF/index.html
IDEA中创建web应用和使用tomcat
- tomcat可以在IDEA eclipse等中使用
- web应用应该包含静态资源和动态资源以及一些配置文件 , 也就是说 一个web应用 应该有良好的文件目录结构,来管理不同类型的文件
- web应用结构在eclipse 和 IDEA 中 有些许不同,使用了maven 等项目构建和管理工具之后,结构也会发生变化.
F3:浏览器上运行时的项目名,可更改
Servlet
介绍
-
servlet是服务器端java写的小程序,是被web服务器(容器)来运行管理的,用来处理客户端的请求
-
Servlet三大域: Request Session Application
WEB-INF:安全路径
关于"/"
- 在页面上 路径开头部分的 / 代表的是虚拟主机 也就是 localhost:8080
<h2><a href="/servlet0321/first">点击访问第一个Servlet程序1</a></h2>
<!-- 超链接默认是get请求 -->
- web.xml / 注解 里 书写路径时 开头的/ 代表本项目
<welcome-file-list>
<welcome-file>/index.html</welcome-file>
</welcome-file-list>
servlet配置
web.xml(tomcat)
<!-- 配置servlet组件 javaweb三大组件: servlet filter listener -->
<servlet>
<!-- web.xml 中可以配置多个servlet,这里的first仅仅是一个昵称/符号 -->
<servlet-name>first</servlet-name>
<servlet-class>com.ls.servlets.FirstServlet</servlet-class><!-- 反射 -->
<!-- 对内找这个类 -->
<!-- 可以配置servlet 跟随服务器的启动(web应用的加载)而创建和初始化 -->
<!-- 数值越小,创建和初始化时间越早,数值是一个正整数 -->
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>first</servlet-name>
<!-- 对外first对应的servlet访问路径 -->
<!-- 访问路径 可以配置多个,可以使用通配符 -->
<url-pattern>/first</url-pattern>
<url-pattern>/first.action</url-pattern>
<url-pattern>/first.do</url-pattern>
<url-pattern>*.f30</url-pattern>
<url-pattern>/a/b/c/*</url-pattern>
<!-- 当精确的和有通配符的同时满足,先以精确的为准,再以通配符为准 -->
</servlet-mapping>
- ServletConfig
<!-- servlet的配置 -->
<servlet>
<servlet-name>t3</servlet-name>
<servlet-class>com.ls.servlets.ThirdServlet</servlet-class>
<!-- servlet的初始化参数 是配置在指定的servlet内的,所以ServletConfig对象 是 每一个servlet都有的 -->
<!-- servlet 之间不共享! -->
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
<init-param>
<param-name>tea</param-name>
<param-value>jasmin tea</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>t3</servlet-name>
<url-pattern>/third</url-pattern>
</servlet-mapping>
//获取当前servlet在 web.xml 或 使用注解 做的配置
ServletConfig servletConfig = this.getServletConfig();
//获取配置中所有初始化参数名字 的迭代器(老版本)
Enumeration<String> initParameterNames = servletConfig.getInitParameterNames();
while (initParameterNames.hasMoreElements()){
String name = initParameterNames.nextElement();
System.out.println(name + "=" +servletConfig.getInitParameter(name));
}
System.out.println(servletConfig.getServletName());
- ServletContext
<!-- ServletContext对象,一个web应用 对应 一个servlet上下文对象 -->
<!-- 作用:用于servlet跟web应用服务器(容器)进行交流的.是web应用内所有servlet共享的 -->
<!-- ServletContext对象 就代表了 整个web应用 -->
<!-- ServletContext对象 也被成为 application 应用域 , 跟随服务器启动(web应用加载)而创建了,是最大的域对象 -->
<!-- 域对象:有4个,域:有一定的作用范围 -->
<context-param>
<param-name>key</param-name>
<param-value>正月里来是新春!</param-value>
</context-param>
//获取web应用对象/上下文对象
ServletContext application = this.getServletContext();
String key = application.getInitParameter("key");
System.out.println(key);
resp.getWriter().println("OK 4!");
注解
servlet注解里 urlPatterns属性里 路径开始的**/** 也是代表当前的项目
/* /:当前项目路径 */
@WebServlet(urlPatterns = {"/s4","/ssss"},loadOnStartup = 1,
initParams = {@WebInitParam(name="keyS4",value = "values4"),@WebInitParam(name="teas",value = "花茶s")})
public class FourServlet extends HttpServlet {
....
}
请求与响应
请求
-
请求转发 和 请求包含 都是 服务器上应用内部的行为.对客户端不可见.
-
获取请求的配置等
@WebServlet(urlPatterns = {"/FiveServlet"})
public class FiveServlet extends HttpServlet {
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
this.doPost(req, resp);
}
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//请求对象可以获取 客户端请求里的方式,请求的头信息,请求的参数信息,其他信息
System.out.println("本次请求的请求方式: " + req.getMethod());
//获取本次请求的所有请求头的名字 组成的 迭代
Enumeration<String> headerNames = req.getHeaderNames();
while (headerNames.hasMoreElements()) {
String header = headerNames.nextElement();
System.out.println("请求头: " + header +"\t" +req.getHeader(header));
}
//获取请求参数(查询参数) 如果get方式 url?后面的键值对
//如果是post方式,就是表单里的组件的name 和 组件的值 的 键值对
//获取指定请求参数名的 请求参数值
System.out.println("p1 = "+req.getParameter("p1"));
System.out.println("本次请求的URL: "+req.getRequestURL());//带协议
System.out.println("本次请求的URI: "+req.getRequestURI());//项目下的资源
System.out.println("本次请求的主机: "+req.getLocalAddr());
System.out.println("本次请求的 请求参数的字符串: "+req.getQueryString());//查询字符串
System.out.println("本次请求的web应用上下文路径: "+req.getContextPath());
//响应对象 打印回一句话
resp.getWriter().println("okkkkk!");
}
}
- 获取请求参数
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//设置本次请求中的字符编码形式 仅对post方式起效果
//get方式下 不设置字符编码格式 就默认为UTF-8,原因是在 tomcat 的安装包里 server.xml 配置里.
req.setCharacterEncoding("UTF-8");
//获取表单里的请求数据
//1.获取指定请求参数名字的 值
System.out.println(req.getParameter("username"));
System.out.println(req.getParameter("pass"));
System.out.println(req.getParameter("nickname"));//无此请求参数
//2.获取请求参数的值是数组形式
String[] hobbies = req.getParameterValues("hobby");
Stream.of(hobbies).forEach(System.out::println);
//3.获取本次请求中的 所有请求参数名字 作为键的map
Map<String, String[]> parameterMap = req.getParameterMap();
for (Map.Entry<String, String[]> entry : parameterMap.entrySet()) {
System.out.println("请求参数名: "+entry.getKey());
System.out.println("请求参数值: "+ Arrays.toString(entry.getValue()));
}
//响应对象里设置 响应回的数据的字符编码形式 还可以设置响应头
resp.setHeader("Content-Type","text/html;charset=UTF-8");
resp.getWriter().write("一百昏!");
}
请求转发
@WebServlet(urlPatterns = {"/EightServlet"})
public class EightServlet extends HttpServlet {
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
this.doPost(req, resp);
}
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//请求里的参数(来自客户端,来自用户)
String p1 = req.getParameter("p1");
System.out.println("8号servlet里面,获得的请求参数p1 = " + p1);
//请求转发 的 是同一个请求,也就在一个请求域内. 任何的域对象 都可以向作用域中设置属性(来自服务器内部)
req.setAttribute("attr1","属性值1");
//经过某些逻辑,需要将请求转发给9号
req.getRequestDispatcher("/NineServlet").forward(req,resp);
}
}
@WebServlet(urlPatterns = {"/NineServlet"})
public class NineServlet extends HttpServlet {
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
this.doPost(req, resp);
}
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//可以再次获取 由8号 转发过来的请求里的 请求参数(原本来自客户端)
String p1 = req.getParameter("p1");
System.out.println("9号 获取请求参数p1 = "+p1);
//获取同一个请求域中 刚才 8号 给我传递过来的属性
String attr1 = (String) req.getAttribute("attr1");
System.out.println("请求域里的属性值 attr1 = "+attr1);
resp.setContentType("text/html;charset=UTF-8");
resp.getWriter().write("咱们9号为你完成了处理!");
}
}
请求包含
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println(req.getParameter("p10"));
req.setAttribute("attr10","attr10");
//请求包含
req.getRequestDispatcher("/ElevenServlet").include(req,resp);
resp.getWriter().write("10!");
}
请求重定向
响应
- 响应对象里设置 响应回的数据的字符编码形式 还可以设置响应头
//设置本次响应回的内容类型是 页面类型 编码是UTF-8
resp.setHeader("Content-Type","text/html;charset=UTF-8");
resp.getWriter().write("<h2 style='color:red;'>GET 好的</h2>");
- 读文件(图片)
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//使用上下文(应用域)对象 获得web目录下的1.png文件的数据
ServletContext application = req.getServletContext();
//获取web目录下/imgs/1.png在tomcat软件 所在主机上的 真实路径
String realPath = application.getRealPath("/imgs/1.png");
FileInputStream fis = new FileInputStream(realPath);
//使用 响应流 输出
//设置 响应的内容类型是图片
resp.setContentType("image/png");
IOUtils.copy(fis,resp.getOutputStream());
}
JSP java serve page
介绍
<%@ page contentType="text/html;charset=UTF-8" language="java" errorPage="error.jsp" %>
<html>
<head>
<title>JSP已经过时!</title>
</head>
<body>
<p>
1.JSP java serve page 使用java语言生成HTML的技术.<br/>
2.原理: .jsp文件 在HTML的基础上 嵌入java语法的部分,交给JSP容器(web应用服务器)来处理.<br/>
3.JSP容器 会把.jsp文件 生成.java文件.
</p>
<p>
JSP可以由7部分组成.
html
jsp编译指令 第一行就是页面编译指令,通常是当前JSP页面的一些属性或导包等.
jsp动作指令
jsp注释
jsp声明 jsp文件生成的java文件 里的 成员变量 或 成员方法 声明使用的
jsp脚本
jsp表达式
</p>
<!-- html注释 看浏览器页面源码,可以看到 -->
<%-- jsp注释 在java生成html的时候,会被跳过,看不到 --%>
<%-- 声明 --%>
<%! private int a = 10; %>
<%! public int add2(int a, int b) {
return a + b;
}%>
<%-- 脚本 --%>
<%
int a = 10, b = 20,c=0;
if (a + b >= 25) {
%>
<p>a和b的总和多于25!</p>
<%
}
%>
<%-- 表达式 --%>
<%=a+b%>
<%=a/c%>
<%="hello word!"%>
</body>
</html>
错误页面
<%@ page contentType="text/html;charset=UTF-8" language="java" isErrorPage="true" %>
<html>
<head>
<title>错误页面</title>
</head>
<body>
<%-- 500是服务器内部 异常导致的 --%>
<p>服务器内部出错了,稍后重试,请联系:网络管理员:QQ:123132</p>
<p>JSP内 默认有个内置对象 excpetion 可以在错误页面内 使用出来,获取异常的信息 </p>
<p>错误信息:<%=exception.getMessage()%></p>
<img src="/servlet0321/imgs/1.png" width="400px"/>
</body>
</html>
- 单页面配置
<%@ page contentType="text/html;charset=UTF-8" language="java" errorPage="error.jsp" %>
- 全局错误页面配置(web.xml)
<error-page>
<error-code>500</error-code>
<location>/error.jsp</location>
</error-page>
<error-page>
<error-code>404</error-code>
<location>/error404.jsp</location>
</error-page>
页面包含
jsp内置对象
-
9个内置对象里 有4个是 作用域对象
-
大到小顺序: application > session > request > pageContext(页面域)
-
作用域都有:setAttribute getAttribute removeAttribute 方法
-
pageContext里 有个 方法 findAttribute(“键”) 自动从小到大 查找域中的属性
-
pageContext内置对象 可以通过getXXX 获取其他8个对象
<a href="/servlet0321/XYServlet">访问XYServlet</a>
<%
pageContext.setAttribute("name","page");
request.setAttribute("name","req");
session.setAttribute("name","session");
application.setAttribute("name","application");
%>
<%=pageContext.getAttribute("name")%><br/>
<%=request.getAttribute("name")%><br/>
<%=session.getAttribute("name")%><br/>
<%=application.getAttribute("name")%><br/>
EL&JSTL
介绍
jsp中,html和java混在一起,不利于观看和维护,不符合面对象的思想
java提供了JSTL 和 EL 替代 JSP的脚本,表达式等,使得jsp更像是页面
EL expressionLanguage
- EL expressionLanguage 是表达式语言. 格式$ {},大括号内可以完成简单的运算
- ${对象.某属性} 实际去调用 对象.get某属性方法()
- 更重要的是 获取某对象里的值,还有11个内置对象
- 当值是空的时候,EL会自动转为空串
<%--<%=request.getAttribute("key100")%>--%>
<%-- el表达式里 requestScope 表示 请求域中 所有 属性键值对 组成的集合 --%>
<%-- el表达式里 sessionScope 表示 会话域中 所有 属性键值对 组成的集合 --%>
<%-- el表达式里 applicationScope 表示 应用域中 所有 属性键值对 组成的集合 --%>
<p>
请求域里的数据: ${requestScope.reqKey}
</p>
<p>
会话域里的数据: ${sessionScope.sessionKey.name} - ${sessionScope.sessionKey.age}
</p>
<p>
应用域里的数据: ${applicationScope.appKey}
</p>
<%-- 原理是 pageContext.findAttribute(键) --%>
<p>
最好用的从小到大的 在 四个作用域中找数据: ${reqKey} - ${sessionKey} - ${appKey}
</p>
<p>
学生的年龄大于25: ${sessionScope.sessionKey.age >= 25}
</p>
<p>
域中的属性 some : ${some} <%-- 使用EL去取值的好处,当值是空的时候,EL会自动转为空串 --%>
</p>
<p>
域中的属性 some : <%=pageContext.findAttribute("some")%>
</p>
<p>
域中的属性 some : ${some==null}
</p>
JSTL jsp standard tag lib
- JSTL: jsp standard tag lib jsp标准标签库,是一种标签形式的java代码
- 主要能够完成页面里 判断 循环 遍历 设置 等功能
- 需要导入.jar
core标签库
遍历
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%-- taglib指令用来导入 标签库信息 prefix是标签的前缀 --%>
<html>
<head>
<title>Title</title>
</head>
<body>
<h2>JSTL结合EL来遍历域中的各种数据</h2>
<%-- items是要遍历的域中的集合或数组 var是集合或数组里的单个数据 --%>
<%-- varStatus遍历时 集合中单个数据的状态 --%>
<%-- step 对应 普通循环里的 i++ --%>
<%-- begin 对应 普通循环里的 i的初始值 end对应 普通循环里i的结束值 --%>
<c:forEach items="${requestScope.names}" var="nick" varStatus="state">
<p>
${nick} - 遍历时当前数据的个数:${state.count} - 遍历时当前数据的索引: ${state.index}
</p>
</c:forEach>
<c:forEach begin="1" end="5" step="1" varStatus="sta">
<p>你好吗? - ${sta.index}</p>
</c:forEach>
<hr/>
学生名字:${student.name} - 学生年龄:${requestScope.student.age}
<hr/>
<h2>遍历List</h2>
<c:forEach items="${requestScope.stus}" var="stu">
<p>学生名字:${stu.name} - 学生年龄:${stu.age}</p>
</c:forEach>
<h2>遍历Map</h2>
<c:forEach items="${requestScope.maps}" var="entry">
<p style="background-color: cornflowerblue">键:${entry.key}</p>
<p>学生名字:${entry.value.name} - 学生年龄:${entry.value.age}</p>
</c:forEach>
<h2>遍历Map2</h2>
<c:forEach items="${requestScope.datas}" var="item" varStatus="state">
<p style="background-color: gold">键:${item.key}</p>
<c:forEach items="${item.value}" var="s">
<p>学生名字:${s.name} - 学生年龄:${s.age}</p>
</c:forEach>
</c:forEach>
</body>
</html>
注: 当step==2
补充: jstl表达式判空:
判断标签
<c:if test="${requestScope.student.age<18}">
<p>${requestScope.student.name}还未成年!</p>
</c:if>
<c:if test="${requestScope.student.age>=18}">
<p>${requestScope.student.name}已经成年!</p>
</c:if>
<%-- 向某个域中设置属性值 --%>
<c:set scope="session" var="nation" value="俄罗斯"/>
<%-- 多选switch --%>
<c:choose>
<c:when test="${nation==null}">没有值</c:when>
<c:when test="${nation=='中国'}">是中国</c:when>
<c:when test="${nation=='俄罗斯'}">是俄罗斯</c:when>
<c:otherwise>是其他</c:otherwise>
</c:choose>
fmt标签库
国际化
-
配置资源文件
-
监听器中在应用域里设置大语言
-
在要修改的部分设置资源文件
-
与后端相连的语言切换按钮
-
后端程序
Maven
介绍
maven的web项目结构
MVC
Lombok
Filter
介绍
servlet listener filter 都是由容器进行管理运行的
过滤器常见功能
- 字符编码过滤
- 权限验证
- 敏感词汇替换
- … 很多
示例
过滤post请求普通请求的组件
listener
-
介绍
-
配置
- @webListener 里面没有要配置的数据,只是在事件发生时要运行的代码(对内,不对用户)
-
示例
@WebListener
public class AppListener implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent sce) {
//sce是 事件对象,可以从中获取一些发生事件时的信息(数据)
ServletContext application = sce.getServletContext();
System.out.println("application 创建了!");
//查询所有的角色信息,设置到应用域中
RoleService roleService = RoleService.getInstance();
List<Role> rolelist = roleService.rolelist();
application.setAttribute("rolelist",rolelist);
//遍历测试
rolelist.stream().forEach(System.out::println);
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
System.out.println("application 销毁了!");
}
}
注: 用的多 不常变
Cookie
介绍
示例
- 十天免登录
用户页面
<div class="form-group form-check">
<input type="checkbox" class="form-check-input" id="Check1" value="1" name="remember">
<label class="form-check-label text-muted" for="Check1">记住用户名和密码</label>
</div>
后端
//判断是否勾选了记住用户名和密码
String remember = req.getParameter("remember");
if ("1".equals(remember)) {
//设置 用户名cookie 密码cookie 到客户端
Cookie yhm = new Cookie("yhm",login.getUsername());
yhm.setMaxAge(10*24*60*60);
Cookie mm = new Cookie("mm",login.getPassword());
yhm.setMaxAge(10*24*60*60);
resp.addCookie(yhm);
resp.addCookie(mm);
}
用户页面读入 : 使用EL表达式
<div class="form-group">
<label for="username" class="text-muted">用户名:</label>
<input type="text" class="form-control" name="username" value="${cookie.yhm.value}" id="username" aria-describedby="usernameHelp">
<small id="usernameHelp" class="form-text text-muted">用户名或邮箱名</small>
</div>
<div class="form-group">
<label for="password" class="text-muted">密码:</label>
<input type="password" class="form-control" name="password" value="${cookie.mm.value}" id="password" aria-describedby="usernameHelp">
<small id="passwordHelp" class="form-text text-muted">这里可以写一些提示的小文字</small>
</div>
AJAX
介绍
AJAX : Asynchronous JavaScript and XML
应用方法
应用实例
- 大分类级联小分类
每次点击<a>,总会重新请求后端程序,重新遍历,然后返回新的数据,从而做到及时刷新
展示页面:
<div class="container-fluid">
<h2 class="mt-2 pb-2">分类信息</h2>
<div class="row">
<div class="col-2"></div>
<div class="col-4">
<label for="big">大分类</label>
<select class="custom-select custom-select-lg" id="big" >
<option value="0">请选择</option>
<c:forEach items="${requestScope.bigs}" var="big">
<option value="${big.bigid}">${big.bigname}</option>
</c:forEach>
</select>
</div>
<div class="col-4">
<label for="small">小分类</label>
<select class="custom-select custom-select-lg" id="small" >
<option value="0">请选择</option>
</select>
</div>
<div class="col-2"></div>
</div>
</div>
<script>
//大分类:
//上面经categorylist.action servlet 转发过来 bigs,在页面遍历出来
//还可以在categorylist.jsp页面 加载完毕之后 发一个ajax请求,得到 所有大分类的List<Big> 对应的json格式的字符串
//---------------------- window.onload = function(){} ---- $(function{}) --> $(document).ready()
//小分类:
//必须使用ajax 根据大分类的不同 去 请求 对应的小分类数据
$("#big").change(function () {
$("#small").empty();
//当前选择的大分类选项
let bigid = $("#big option:selected").val();
if (bigid == "0"){
let $op = $("<option value='0'>请选择小分类</option>");
$("#small").append($op);
return;
}
//发 ajax 请求
let datas = {"choose":"small","bigid":bigid};
$.ajax({
url:"${pageContext.request.contextPath}/categorylist.action",
data:datas,
method:"post",
dataType:"json",
success:function (smalls) {//接收到返回数据后要做的事
for (let i = 0; i < smalls.length; i++) {
let $opi = $("<option></option>");
$opi.val(smalls[i].smallid);
$opi.text(smalls[i].smallname);
$("#small").append($opi);
}
}
})
})
</script>
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String choose = req.getParameter("choose");
if (choose == null){//如果请求不带参数,则查询大分类
List<Big> bigs = CategoryService.getInstance().queryAllBig();
req.setAttribute("bigs",bigs);
req.getRequestDispatcher("/WEB-INF/categorylist.jsp").forward(req,resp);//转发回去,准备调用ajax
return;
}else if (choose.equals("small")){
//按照 大分类id 查询对应的小分类数据
String bigid = req.getParameter("bigid");
int bid = Integer.parseInt(bigid);
List<Small> smalls = CategoryService.getInstance().querySmallsByBigid(bigid);
//页面ajax请求 预期的 json数据
ObjectMapper om = new ObjectMapper();
String jsonStr = om.writeValueAsString(smalls);
resp.setCharacterEncoding("UTF-8");
resp.getWriter().write(jsonStr);
}
}
- 用户名是否重复查询
jsp页面内:
function chkuser() {
//当 用户名 输入框 失去焦点的时候 发送ajax异步请求 问 是否重名
let b = false;
b = chksome("#username",/[a-z0-9]{4,8}/i,"#usernameHelp");
if (!b) return b;
let username = $("#username").val();
let datas = {"username":username,"op":"findname"};
$.ajax({
url:"${pageContext.request.contextPath}/member",//传递到后端服务器
method:"post",
data:datas,
async:true,
dataType:"text",
success:function (msg) {
if (msg == "1"){//返回
$("#usernameHelp").removeClass("text-muted");
$("#usernameHelp").removeClass("text-danger");
$("#usernameHelp").addClass("text-success");
$("#usernameHelp").text("√");
b = true;
}else {
$("#usernameHelp").removeClass("text-muted");
$("#usernameHelp").removeClass("text-success");
$("#usernameHelp").addClass("text-danger");
$("#usernameHelp").text("此用户名太受欢迎了,请再换一个!");
b = false;
}
}
})
return b;
}
后端处理程序:
注: jdbc工具查询数据进行封装,当没有查到数据时会报错空结果集异常
后端功能
退出登录
在web.xml中配置会话自动失效的时间,tomcat默认配置30mins
注: 已设置post登录,可设置get退出
此处设置会话立即失效
验证码
- 验证码刷新
<script>
$("#verifyImg").click(function () {
let num = Math.random();
$(this).attr("src","${pageContext.request.contextPath}/verify?num="+num);
})
</script>
ps:
-
带参可以使图片不被浏览器缓存
-
也可使用button
-
将验证码设置到会话域中
-
判断登录是否成功
String verify = req.getParameter("verify");//用户输入
HttpSession session = req.getSession();
String verifyCode = (String) session.getAttribute("verifyCode");//随机生成的原码,被设置在session
if (verify==null||!verify.equals(verifyCode)){
req.setAttribute("msg","验证码输入错误!");
req.getRequestDispatcher("/login.jsp").forward(req, resp);
return;//不进行后续操作
}
//
-
登录成功后(进入首页前)
session.removeAttribute("verifyCode");//登录后验证码失效
十天免登录
参照cookie
角色列表
头像上传
- 上传图片回显
-
设置文件上传表单组件:
<form ... enctype="multipart/form-data"> //编码改变 ..... </form>
<div class="custom-file">
<input type="file" class="custom-file-input" id="customFile" name="headimg">
<label class="custom-file-label" for="customFile">Choose file</label>
</div>
- 设置回显函数:
<script>
$("#customFile").change(function () {
let file0 = this.files[0];
let url = URL.createObjectURL(file0);
$("#showimg").attr("src",url).removeClass("d-none");//给img标签加
})
if (${param.msg=='upok'}){
alert("上传头像成功!");
}
</script>
- 表单提交到用户处理servlet
-
更新头像文件,上传到服务器对应的文件夹下
-
login.setImageName() 设置登录用户的头像名
-
更新数据库中该用户的头像名,刷新头像
-
- 图片服务器:
conf/server.xml : 修改端口 8005 8080(都在localhost)
conf/web.xml:添加
在listener中设置:
//用户头像文件所在的服务器的路径
application.setAttribute("headpath","http://localhost:7070/IMAGESERVER/heads/");
servelt中的代码:
protected void upheadimg(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//表单有文件上传组件,则编码格式发生改变,java的req无法获取请求参数
/*String hidden = req.getParameter("hidden");
System.out.println(hidden);*/
//apache-commons-fileupload工具 解析 格式发生改变的 请求参数
//磁盘文件项工厂
DiskFileItemFactory factory = new DiskFileItemFactory();
ServletFileUpload sfu = new ServletFileUpload(factory);
sfu.setFileSizeMax(1024L*1024*20);//可解析的单个文件大小20MB
sfu.setSizeMax(1024L*1024*20*20);//本次请求所有文件大小 最多 400MB
try {
//servlet请求的解析器 可以把请求参数的数据 解析为一个个的 文件项 对象
List<FileItem> fileItems = sfu.parseRequest(req);
//FileItem对象 有可能保存了 表单里的文件上传数据 还有可能保存了普通的请求数据
for (FileItem fileItem : fileItems) {
if (fileItem.isFormField()){//判断此文件项对象 是不是 普通的请求数据(表单组件)
//获取 请求参数的名和值
String fieldName = fileItem.getFieldName();
System.out.println("普通表单组件:"+fieldName+"\t值:");
System.out.println(fileItem.getString("UTF-8"));
}else {
ServletContext application = this.getServletContext();
Object headpath = application.getAttribute("headpath");//应用域里 图片服务器保存图片的文件夹的路径
String name = fileItem.getName();
System.out.println("上传的文件名:"+name);
InputStream inputStream = fileItem.getInputStream();
/* //演示1:将用户的头像(文件)上传到 服务器上该应用内部的upload文件夹内
//使用应用域对象 来获取 upload文件 在 服务器所在机器的真实路径
ServletContext application = this.getServletContext();
String uploadRealPath = application.getRealPath("/upload");
//这里IDEA和eclipse等其他编程软件做法不一样,IDEA将web应用放在工作区里的target目录里
String upFileName = UUID.randomUUID().toString().replace("-","")+name;
File upFile = new File(uploadRealPath,upFileName);
FileOutputStream fos = new FileOutputStream(upFile);
IOUtils.copy(inputStream,fos);*/
//演示2:将文件传输到7070服务器
String upFileName = UUID.randomUUID().toString().replace("-","")+name;//防止同名文件的覆盖
//8080是7070的客户端
Client client = new Client();
//根据 资源路径 获取对应的web资源
WebResource resource = client.resource(headpath+upFileName);
//上传数据
resource.put(inputStream);
//更新数据库中 该用户的 headimg字段的值
//调用用户业务层 将upFileName文件名 修改到 该用户的 headimg字段
HttpSession session = req.getSession();
User login = (User) session.getAttribute("login");//获取该用户
UserService us = UserService.getInstance();
us.updateHeadimg(login.getUid(),upFileName);//更新数据库图片
login.setHeadimg(upFileName);//修改登录用户的字段
req.getRequestDispatcher("/userdetails.jsp?msg=upok").forward(req,resp);
}
}
} catch (FileUploadException e) {
e.printStackTrace();
}
}
tomcat/config/web.xml 含有mime类型 用来判断文件是什么类型
应用: Content-Type
下载文件
- 本地
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String action = req.getParameter("action");
if (action!=null && "download".equals(action)) {
//获取文件的真实路径
ServletContext application = req.getServletContext();
String realPath = application.getRealPath("/WEB-INF/files/生产记录.xlsx");
File file = new File(realPath);
FileInputStream fis = new FileInputStream(file);//输入流
String fileName = file.getName();
long length = file.length();
//1.要下载的文件名是非中文,不用其他设置
//2.要下载的文件名带有中文,文件名需要使用base64来转码
fileName = URLEncoder.encode(fileName,"UTF-8");
//设置本次响应的 响应头为 带有附件的
resp.setHeader("content-disposition","attachment;filename="+fileName);
//设置本次附件的大小
resp.setContentLengthLong(length);
IOUtils.copy(fis,resp.getOutputStream());//上传
}
}
-
网络
protected void down(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //获取文件真实路径 ServletContext app = req.getServletContext(); String softPath = (String) app.getAttribute("sp"); //app.setAttribute("sp","http://localhost:7070/share/soft/"); String path = req.getParameter("path"); String url = softPath+path; URL downURL = new URL(url); URLConnection urlConnection = downURL.openConnection(); InputStream is = urlConnection.getInputStream(); String contentType = urlConnection.getContentType();//MIME long length = urlConnection.getContentLengthLong();//内容大小 //设置内容头 resp.setContentType(contentType); //设置内容大小头 resp.setContentLengthLong(length); //设置内容处理方式 resp.setHeader("Content-Disposition","attachment;filename="+path); IOUtils.copy(is,resp.getOutputStream()); }
分页
bean - servlet - service - dao - servlet - jsp
- 不止用户数据需要分页显示,图片,公告…数据量多的都需要分页显示
- 此类为设计的一个 方便在页面上展示每页数据和相关页码等的 数据模型类 / javabean
- 通过物理分页 结合 mysql limit语句 n m
- n:查询起始记录的下标
- m:本次查几条(每页的条数)
- n = (查询第几页 - 1) * m
数据模型类
public class PageModel<T> {
//最主要的 每一页的 n条数据组成的集合
private List<T> data;
private int pagesize = 6;//每页几条数据 程序设定
private int total;//总记录条数,必须查询数据得到
private int lastPage;//总页数=最后一页页码 经过total pagesize计算
private int currentpage = 1;//本次查询的页码 当前页 用户决定
private int showFirst;//是否显示首页/上一页 默认=0:不显示 1:显示 经过currentpage计算
private int showLast;//是否显示尾页/下一页 默认=0:不显示 1:显示 经过currentpage计算
private int start;//页码的开始 经过currentpage计算
private int end;//页码的结束 经过currentpage计算
public List<T> getData() {
return data;
}
public void setData(List<T> data) {
this.data = data;
}
public int getPagesize() {
return pagesize;
}
public void setPagesize(int pagesize) {
this.pagesize = pagesize;
}
public int getTotal() {
return total;
}
public void setTotal(int total) {
this.total = total;
//当确定好total以后 计算sum总页数 也就是 最后一页
lastPage = total%pagesize == 0? total/pagesize : total/pagesize + 1;
showFirst = currentpage == 1 ? 0:1;
showLast = currentpage == lastPage ? 0 : 1;
start = currentpage - 3;
end = currentpage + 3;
start = start <=0 ? 1 : start;
end = end >= lastPage ? lastPage : end;
}
public int getLastPage() {
return lastPage;
}
public void setLastPage(int lastPage) {
this.lastPage = lastPage;
}
public int getCurrentpage() {
return currentpage;
}
public void setCurrentpage(int currentpage) {
this.currentpage = currentpage;
}
public int getShowFirst() {
return showFirst;
}
public void setShowFirst(int showFirst) {
this.showFirst = showFirst;
}
public int getShowLast() {
return showLast;
}
public void setShowLast(int showLast) {
this.showLast = showLast;
}
public int getStart() {
return start;
}
public void setStart(int start) {
this.start = start;
}
public int getEnd() {
return end;
}
public void setEnd(int end) {
this.end = end;
}
public PageModel() {
}
}
用户页面后端程序
@WebServlet(urlPatterns = {"/userlist.action"})
public class UserListServlet extends HttpServlet {
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
this.doPost(req, resp);
}
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//点击 用户列表 按钮时 查询第一页
PageModel<User> pageModel = new PageModel<>();//空桶
//如果查询指定页 肯定要带参数
String currentpage = req.getParameter("currentpage");
if (currentpage!=null) {
pageModel.setCurrentpage(Integer.parseInt(currentpage));
}
//业务层查询 填满pageModal
UserService userService = UserService.getInstance();
pageModel = userService.userlistByPage(pageModel);
//请求域里设置 pageModal的对象
req.setAttribute("pm",pageModel);
//转发到 userlist.jsp上
req.getRequestDispatcher("WEB-INF/userlist.jsp").forward(req,resp);
}
}
用户页面
注: 是从pageModule对象pm中取currentPage,更新page也是保存在这里面
<!-- 分页 -->
<nav aria-label="Page navigation example">
<ul class="pagination pagination-lg justify-content-center">
<c:if test="${requestScope.pm.showFirst == 1}">
<li class="page-item">
<a class="page-link" href="${pageContext.request.contextPath}/userlist.action?currentpage=1" aria-label="Previous">
<span aria-hidden="true">首页</span>
</a>
</li>
<li class="page-item">
<a class="page-link" href="${pageContext.request.contextPath}/userlist.action?currentpage=${requestScope.pm.currentpage - 1}" aria-label="Previous">
<span aria-hidden="true">«</span>
</a>
</li>
</c:if>
<!-- 每页的编号 -->
<c:forEach begin="${requestScope.pm.start}" end="${requestScope.pm.end}" step="1" var="i">
<c:if test="${requestScope.pm.currentpage == i}">
<li class="page-item active"><a class="page-link" href="${pageContext.request.contextPath}/userlist.action?currentpage=${i}">${i}</a></li>
</c:if><!-- 每个li都包含一个超链接,一旦点击就使用get传递当前第i页到后端程序 -->
<c:if test="${requestScope.pm.currentpage != i}">
<li class="page-item"><a class="page-link" href="${pageContext.request.contextPath}/userlist.action?currentpage=${i}">${i}</a></li>
</c:if>
</c:forEach>
<c:if test="${requestScope.pm.showLast == 1}">
<li class="page-item">
<a class="page-link" href="${pageContext.request.contextPath}/userlist.action?currentpage=${requestScope.pm.currentpage + 1}" aria-label="Next">
<span aria-hidden="true">»</span>
</a>
</li>
<li class="page-item">
<a class="page-link" href="${pageContext.request.contextPath}/userlist.action?currentpage=${requestScope.pm.lastPage}" aria-label="Next">
<span aria-hidden="true">尾页</span>
</a>
</li>
</c:if>
</ul>
</nav>
注册
- 思路
用户名是否重复:参照AJAX应用实例
大小分类级联
参照AJAX应用实例
国际化
参照fmt标签库-国际化
会员签到
-
准备工作
- 数据库&bean
- 连续签到concount 金币/积分gold 上次签到日期signdate 今日是否签到signflag
- concount 计算该次签到gold
- signdate 上一次和今天是否连续 如果连续,concount++
- 连续签到concount 金币/积分gold 上次签到日期signdate 今日是否签到signflag
- 数据库&bean
-
制作跳转签到页面
-
设置签到按钮触发ajax请求
-
$("#signBtn").click(function () { $.ajax({ url:"${pageContext.request.contextPath}/sign", type:'post', dataType:'json', success:function (res) { $("#p1").text("签到成功!连续签到天数: "+res.map.concount); $("#p2").text("获得积分 :"+ res.map.addGold); $("#p3").text("目前总积分 :"+ res.map.sum); $("#signA").attr("href","#").text("已签到"); } }) $("#signBtn").addClass("disabled"); })
-
-
后端
-
/sign (servelt)
-
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //签到 Member member = (Member) req.getSession().getAttribute("member"); Map<String, Object> data = getData(member);//保存更改后的数据 data.put("id",member.getId()); //调用业务层 MemberService ms = new MemberServiceImpl(); Member newMember = ms.sign(data); //更新session req.getSession().setAttribute("member",newMember); //响应 Result result = new Result(); result.setStatus("200"); result.setMsg("okk"); result.setMap(data); //将数据返回前端页面 ObjectMapper m = new ObjectMapper(); resp.getWriter().write(m.writeValueAsString(result)); } //计算并封装要更新的数据 public Map<String,Object> getData(Member member){ Map<String,Object> map = new HashMap<>();//用于向后传递数据到dao层 String signdate = member.getSigndate();//上一次签到日期 int addGold = 1;//不连续为1 传给dao层的本次获得的积分数量 boolean isContinue = true;//连续签到?有什么用?没用到 Integer concount = member.getConcount();//连续签到次数 //从未签到过 新账号!!!!!!! if (signdate==null){ map.put("concount",1); isContinue = false; map.put("isContinue",isContinue);//第一天签到 和 断了之后的第一天签到 都即是连续又是不连续 map.put("addGold",1); map.put("sum",member.getGold()+addGold);//总积分 return map; } //不是新账号 long between = DateUtil.between(new Date(), DateUtil.parseDate(signdate), DateUnit.DAY);//计算是否连续签到 if (between==1){//连续 map.put("concount",concount+1);//连续签到次数 int cc = concount+1; switch (cc){//更新金币 case 2:addGold = 2;break; case 3:addGold = 3;break; case 4:addGold = 4;break; default:addGold = 5;break; } }else {//不连续 map.put("concount",1); isContinue = false; } map.put("addGold",addGold); map.put("isContinue",isContinue); map.put("sum",member.getGold()+addGold); return map; }
-
-
service
-
@Override public Member sign(Map<String, Object> data) { //1.签到 md.sign(data); //2.重新查询该id的会员数据,用以更新会话域 Member member = md.queryById(data.get("id")); return member; }
-
-
dao
-
@Override public void sign(Map<String, Object> data) { String sql = "update member set signflag = 1,gold = gold+? , concount = ?,signdate = curdate() where id = ?"; Object[] args = {data.get("addGold"),data.get("concount"),data.get("id")}; t.update(sql,args); } @Override public Member queryById(Object id) { String sql = "select * from member where id = ?"; Object[] args = {id}; return t.queryForObject(sql,new BeanPropertyRowMapper<>(Member.class),args); }
-
-
app监听器中设置定时任务
-
@WebListener public class AppLifeListener implements ServletContextListener { Timer timer = new Timer();//定时器 @Override public void contextInitialized(ServletContextEvent sce) { .... //周期执行 将 会员签到改为0 Calendar now = Calendar.getInstance();//目前日历 now.set(Calendar.HOUR_OF_DAY,0);//调整时间 now.set(Calendar.MINUTE,0); now.set(Calendar.SECOND,1); //调试时要注意天数是否为今天 now.add(Calendar.DAY_OF_MONTH,1);//第二天 00:00:01 Date firstDate = now.getTime();//第一次执行的时间:服务器启动之后的 timer.scheduleAtFixedRate(new ClearTimerTask(),firstDate,1000L*60*60*24); } @Override public void contextDestroyed(ServletContextEvent sce) { if (timer!=null){//服务器下线时定时器取消运行 timer.cancel(); } } class ClearTimerTask extends TimerTask{//定时器要运行的定时任务 @Override public void run() { MemberDao md = new MemberDaoImpl(); md.resetSign(); } } }
-
-
定时器操作dao
-
@Override public void resetSign() { String sql = "update member set signflag = 0"; t.update(sql); }
-
-
hutool-maven-tomcat7插件
注意事项
-
多表查询
-
没有javabean(通常对应单表)对应
-
设计一个专门的javabean对应本次查询,因为查询都是为了页面展示使用
所以javabean可以起名为XXXXdto XXXXvo …
主要是为了JDBCTemplate 方便把查询的记录 封装为java对象 -
java查询的时候,使用Map去接收记录 一个map对应接收一个记录
-
在页面上遍历资源
- 注意将id更改(连接id/sta.count/sta.index)
-
正则
-
java: str.matches(regx)
-
js: regx.test(str)
-
endar.getInstance();//目前日历
now.set(Calendar.HOUR_OF_DAY,0);//调整时间
now.set(Calendar.MINUTE,0);
now.set(Calendar.SECOND,1);
//调试时要注意天数是否为今天
now.add(Calendar.DAY_OF_MONTH,1);//第二天 00:00:01
Date firstDate = now.getTime();//第一次执行的时间:服务器启动之后的
timer.scheduleAtFixedRate(new ClearTimerTask(),firstDate,1000L*60*60*24);
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
if (timer!=null){//服务器下线时定时器取消运行
timer.cancel();
}
}
class ClearTimerTask extends TimerTask{//定时器要运行的定时任务
@Override
public void run() {
MemberDao md = new MemberDaoImpl();
md.resetSign();
}
}
}
- 定时器操作dao
@Override
public void resetSign() {
String sql = "update member set signflag = 0";
t.update(sql);
}
hutool-maven-tomcat7插件
注意事项
-
多表查询
-
没有javabean(通常对应单表)对应
-
设计一个专门的javabean对应本次查询,因为查询都是为了页面展示使用
所以javabean可以起名为XXXXdto XXXXvo …
主要是为了JDBCTemplate 方便把查询的记录 封装为java对象 -
java查询的时候,使用Map去接收记录 一个map对应接收一个记录
-
在页面上遍历资源
- 注意将id更改(连接id/sta.count/sta.index)
-
正则
-
java: str.matches(regx)
-
js: regx.test(str)
-