网站中级
2.1 文件下载 文件上传:将本机文件上传到服务器 文件下载:将服务器上的文件下载到本地 如果是浏览器支持的文件格式,则下载后直接显示 例如:*.html,*.jpg 如果浏览器不支持显示该文件,则提示保存到本地 例如:*.exe,*.rar 注意:都是文件下载,区别是有的能直接显示 不支持下载的目录 Web Application 目录下的文件由tomcat自动支持下载的. 但是: 1.WEB-INF 和META-INF下的文件不支持下载 2.Web Application目录之外的文件不支持下载. 小结: 介绍了什么是文件下载,哪些位置的文件不支持自动下载. 文件下载的本质:tomcat把一个文件的内容传递给客户端. 2.2 自定义下载 自定义下载:使用Servlet自己控制下载 特点:-文件可以在任意位置(Web目录之外) -可以控制权限 -可以控制下载速度(下载限速) -可以控制是否允许多点下载(多线程下载) -其他方面的下载控制 自定义下载的示例: -所有用户的在头像存储在 c:\data\photo\下 -头像的命名规则:20180001.jpg -要求在web项目中显示加载显示头像. 一般可以认为是"/"的作用等同于"\\" 最好用“/”因为java是跨平台的。“\”(在java代码里应该是\\)是windows环境下的路径分隔符, Linux和Unix下都是用“/”。而在windows下也能识别“/”。所以最好用“/” 自定义下载的实现步骤: 1.添加一个Servlet:DownloadService 2.配置映射路径 /downlad/* 3.从访问URI里取得用户的请求参数,对应到实际的本地文件 4.读取本地文件的内容,发给客户端. 404错误,如果用户访问的目标文件不存在,应返回404错误. 前端无法区别这是一个Servlet还是一个静态文件 如<img src="download/photo/20180001.jpg" /> 2.3 内容类型Content-Type 在HTTP应答的头部,需要指定内容的类型 示例: HTTP/1.1 200 Content-Type:image/jpeg //内容的类型 Content-Length:51967 //内容的长度 观察2.1的抓包数据,找到Content-Type字段. 内容类型主要分为5大类: text/* //文本 image/* //图片 audio/* //音频 video/* //视频 application/* //应用文件 @WebServlet("/download/*") public class DownloadService extends HttpServlet { File dataDir = new File("c:/data/"); protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // getServletPath() 返回的是 /download ,不含全路径信息 // String servletPath = request.getServletPath(); // 请求路径 // requestUri: 例如 /mid0201/download/photo/20180001.jpg String requestUri = request.getRequestURI(); // contextPath: 例如 /mid0201 String contextPath = request.getContextPath(); // servletPath: 例如 /download/photo/20180001.jpg String servletPath = requestUri.substring( contextPath.length()); // 提取出目标信息 targetPath: "photo/20180001.jpg" String targetPath = servletPath .substring("/download/".length()); File targetFile = new File(dataDir, targetPath); // 检查目标文件是否存在 if(!targetFile.exists() || ! targetFile.isFile()) { System.out.println("目标文件不存在!" +targetFile); response.sendError(404); return; } // 应答:设置 Content-Type 和 Content-Length 读取目标文件,发送个客户端 String contentType = "image/jpeg";//"application/octet-stream"; long contentLength = targetFile.length(); response.setContentType(contentType); response.setHeader("Content-Length", String.valueOf(contentLength)); // 应答:读取目标文件的数据, 发送给客户端 InputStream inputStream = new FileInputStream(targetFile); OutputStream outputStream = response.getOutputStream(); try { streamCopy (inputStream, outputStream); }catch(Exception e) { try{ inputStream.close();} catch(Exception e2){} } outputStream.close(); } private long streamCopy(InputStream in, OutputStream out) throws Exception { long count = 0; byte[] buf = new byte[8192]; while (true) { int n = in.read(buf); if (n < 0) break; if (n == 0) continue; out.write(buf, 0, n); count += n; } return count; } } 测试1 映射:DownloadService->/download/* 比较: image/jpeg application/octet-steam 观察浏览器反应. HTTP应答时,应设置正确的Content-Type 浏览器会结合文件后缀和Content-Type来处理 注:不要求记住,直到它的作用和5种分类即可 2.4 HTTP 404错误 404 Not Found 是一个网站开发里常见的错误 404是啥意思?为什么不能是403,405? HTTP状态码(HTTP Status Code) 分为4类: 2XX:成功,正常,已受理 3XX:重定向 4XX:请求错误 5XX:服务器错误 常见状态码: 200:ok 206:Partial Content 302:Move temporaily 304:Not Modified 403:ForBidden 404:Not Found // 检查目标文件是否存在 Servlet返回应答错误 在Servlet里,可以调用sendError()返回应答错误: // 检查目标文件是否存在 if(!targetFile.exists() || ! targetFile.isFile()) { System.out.println("目标文件不存在!" +targetFile); response.sendError(404); return; } isFile() public boolean isFile()测试此抽象路径名表示的文件是否是一个标准文.如果该文件不是一个目录, 并且满足其他与系统有关的标准,那么该文件是标准文件.由Java应用程序创建的所有非目录文件一定是标准文件. 返回:当且仅当此抽象路径名表示的文件存在且是一个标准文件时,返回true;否则返回false; 抛出:SecurityException,如果存在安全管理器,且其SecurityManager.checkRead(java.lang.String)方法拒绝对文件进行读访问. exists() public boolean exists()测试此抽象路径名表示的文件或目录是否存在. 返回:当且仅当此抽象路径名表示的文件或目录存在时,返回true;否则返回false; 抛出:SecurityException如果存在安全管理器,且其SecurityManager.checkRead(java.lang.String)方法拒绝对文件或目录进行写访问. isFile():判断是否文件,也许可能是文件或者目录 exists():判断是否存在,可能不存在 两个不一样的概念 介绍了HTTP状态码的含义 介绍了用sendError()返回404. 3.1 会话Session 在javaweb开发中,Servlet,Session,Filter是三个最重要的机制. 什么叫会话 和日常生活中的"会话"意思差不多 比如:去拜访一家公司,从敲门到离开的这一过程就是一次会话. 敲门 xxx-------->Session <-------(公司) 离开 当用户访问网站时,会话指从打开到关闭这一过程 开始:用户打开浏览器,打开网站地址 结束:用户关闭浏览器 请求和会话 从用户开始访问该网站,每一个请求都会关联到后台的一个Session对象. 再Servlet里,可以取得当前的会话对象. HttpSession session =request.getSession(); 每一个会话都有一个ID String sessionId=session.getId(); 演示:在一次会话期间,所有的请求都关联到同一个Session对象.
@WebServlet("/Example1") public class Example1 extends HttpServlet { protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //每一个request都关联到一个session对象上 HttpSession session=request.getSession(); //每一个会话都有一个ID String sessionId=session.getId(); System.out.println("访问example1-Session ID: " + sessionId); response.getWriter().append("Served at: ").append(request.getContextPath()); } } 访问example1-Session ID: 30A34D43449564344339374868C36FEC 访问example2-Session ID: 30A34D43449564344339374868C36FEC 浏览器未关闭的话是同一个会话. 浏览器关闭后再访问 访问example1-Session ID: 95CF8FA07AC01878D292836AD73FB9DE 思考: 1.如果关闭浏览器,再打开浏览器重新打开网站,后台会创建一个新的会话吗? 会 2.除了Servlet之外,访问静态文件(html,jpg)等请求不是也在会话里吗? 是的 由于静态文件是由tomcat处理,所有没法用代码直接进行演示. 如果有多个用户同时访问网站,那么tomcat会创建多个Session对象,分别表示每一个会话. 引入会话Session概论 1.会话表示从打开网站到离开的一次过程 2.重新打开浏览器,则重启一个新的会话 3.会话期间,该用户所有的请求操作都关联到一个 Session session = request.getSession() 3.2 会话与当前用户 会话的作用:一般用于保存当前用户的信息. 应用演示: -实现一个提供下载业务的网站 -要求用户必须在登录后才能下载. 演示:下载业务 -添加下载页project.html -添加DownladService.java 提供下载服务 现在,任何人都可以打开project.html进行下载... 怎样限制用户必须在登录后才能下载呢? 用户登录 -添加登录页面 login.html -添加登录接口 LoginApi.java 现在,用户输入用户名,密码即可实现登录... 下一步:在DownloadService里如何检查当前用户是否已经登录呢? 现在单独实现了登录页面和下载页面,但是用户可以直接访问登录页面进行下载. 需要在下载页面的session中对用户权限进行审查.在下载页面中检查用户是否登录. 3.3 会话与用户权限. 会话的作用:一般用于保存当前的用户信息. 当用户登录后,把用户的相关信息保存在当前会话里,例如,用户ID,用户权限... 用户登录 -LoginApi.java 当用户登录后,应把用户信息保存到当前会话里 //在当前会话里保存当前用户的信息 //在真实项目里,此信息应当从数据库里取得,这里仅供演示 User user=new User(username,true); HttpSession httpSession =this.httpReq.getSession(); httpSession.setAttribute("user", user); 用户权限检查 -DownloadService.java 在下载之前,检查当前用户的权限 // 检查当前用户是否已经登录 User user=(User)request.getSession().getAttribute("user"); if(user==null) { System.out.println("用户未登陆,请先登录"); response.sendRedirect("login.html"); //302重新定向 return; } download通过前端的超链接 <a href="down?id=20180002"> YY系统源码 </a> 通过识别down调用@WebServlet("/down")注解的方法. 关闭浏览器后再下载,需要重新登录,因为此时后台开启了一个新的会话,user变为null. 3.4 HTTP 302重新定向 重定向,Redirection,即指示客户端打开一个新的位置. response.sendRedirect("login.html"); //302重定向. 第一步:浏览器发送get请求 down?id=2018001 服务器返回:HTTP/1.1 302 Location:login.html 浏览器发送新的get请求 login.html 服务器返回:200. 4.1 实例-BBS论坛 演示一个BBS论坛系统,从中理解会话的作用 -用户登录 login.html -浏览 index.html -发帖 edit.html 代码框架 沿用网站入门篇的af-service框架 -数据库MySQL:af_example.sql -框架支持 -添加API -添加前端页面. 4.2 会话的作用. 会话的作用,主要用于保存当前用户的信息 1.在用户登录时,将用户信息保存到当前会话 2.在后续操作时,从会话中取出当前用户信息 1.UserLoginApi登录 在登录时,保存当前用户信息 httpReq.getSession().setAttribute("user",u); 2.ArticleSaveApi登录 在保存发帖时,显然,要知道当前用户是谁 user=(User)httpReq.getSession().getAttribute("user"); row.setCreator(user.id); 更多功能 1.用户注册 2.只看自己的帖子 3.删帖 -管理员删帖 -普通用户删自己的帖子 几乎所有的后台操作,都要检验当前用户的身份和权限. 5.1 权限检查 权限检查:即检查当前用户有没有执行操作的权限 例如,发帖时要检查用户是否登录,有没有发帖权限. 存在的问题,在当前演示系统中,已经有了后台检查,但是缺少前端检查. 比如,如果用户未登陆,那么应该让他根本点不了"发帖界面",而不是编辑好了点保存,才提示说尚未登陆. 添加前端检查 第一种方法:(不推荐) 在点击发帖按钮时,向后台发起请求,检查当前用户的信息. User user = (User) httpReq.getSession().getAttribute("user"); //(User)是什么意思 强制类型转换为User类 if(user == null) throw new Exception("请先登录!"); 检查权限的两种方式:前端检查和后台检查 后台检查:(必须有)保证系统的安全性,完整性 前端检查:(最好有)增强系统的易用性,避免误操作,提升用户体验 5.2 前端权限检查 存在的问题,在上一节课中,在每次按钮时,向后台发起一个请求SesionInfo.api来获取当前用户信息... 问题:当用户访问量大时,会影响网站效率... 前端权限检查:借助于浏览器缓存,在前端自行完成检查. 一、sessionStroage sessionStorage是浏览器对象,可以用JavaScript来访问它。前端页面可以把数据存储到这里。 例如, 存数据sessionStorage.setItem (“username”, “邵发”); 取数据 var name = sessionStorage.getItem(“username”); 注意: 1 当浏览器关闭时,sessionStorage的内容被清空。 2 只能存储字符串。如果想存储一个对象,需要转成字符串。 sessionStorage.setItem (“key”, JSON.stringify( obj ) ); var obj = JSON.parse( sessionStorage.getItem(“key”)); 二、添加前端检查 1 在登录返回时,保存当前用户信息到缓存 (1) 修改 UserLoginApi, 返回当前用户的信息 (2) 修改 login.html sessionStorage.setItem("user", JSON.stringify(data)); 2 在发贴时,检查缓存里的当前用户的信息 修改 index.html // 点击 '发贴' 按钮 M.doCreateNew = function() { var user =JSON.parse(sessionStorage.getItem("user")); if(user == null) { alert("请先登录鸭"); location.href = "login.html"; } else { location.href = "edit.html"; } } 1.在登录返回时,将当前用户的信息存在缓存里 sessionStorage.setItem("key",value) 2.在点发帖时,从缓存里检查当前用户的信息 value=sessionStorage.getItem("key") UserLoginApi.java 最后添加 JSONObject jdata =new JSONObject(u); jdata.remove("password"); //不把密码返回给客户端 return jdata; 小结 认识了浏览器缓存对象 sessionStorage 学会使用sessionStorage存储当前用户信息,实现前端检查,减少服务器的负担. 5.3 后端检查与网络安全 问题:只使用前端检查,不使用后端检查,可以吗 例如:在点"发帖"时,前端检查一次.. 在点"发表"时,前端检查一次.. 似乎不再需要后端检查了,是不是? 取消后台检查,所有检查都在前端进行.看起来网站功能和以前一样,没有影响.. 模拟攻击 不做后台检查的网站,很容易能攻击 演示: 新建一个Java Project,直接调用后台的接口.. 显然,攻击者不需要知道用户名和密码,直接可以随意修改网站的数据. 后端检查:必须有,保证安全性,数据完整性. 前端检查:最好有,提升易用性和用户体验. 5.4 显示细节优化. 添加方法 // 查询,并返回 JSONArray public static JSONArray executeQuery2JSON(String sql) throws Exception { JSONArray result = new JSONArray(); AfSqlConnection connection = getConnection(); try{ ResultSet rs = connection.executeQuery(sql); // 取得每一列的名称和类型 ResultSetMetaData rsmd = rs.getMetaData(); int numColumns = rsmd.getColumnCount(); // 一共几列 int[] columnTypes = new int[numColumns]; // 每列的类型 String[] columnLabels = new String[numColumns]; // 每列的标题 for(int i=0; i<numColumns; i++) { int columnIndex = i + 1; // 列序号 columnLabels[i] = rsmd.getColumnLabel(columnIndex); // 列标题 columnTypes[i] = rsmd.getColumnType(columnIndex); // 类型, 参考 java.sql.Types定义 } while(rs.next()) { // 每一行转成一个JSONObject JSONObject jrow = new JSONObject(); result.put(jrow); for(int i=0; i<numColumns; i++) { String columnValue = rs.getString( i + 1); // 每列的值 if(columnValue == null) continue; int type = columnTypes[i]; if(type == Types.TINYINT || type == Types.SMALLINT || type == Types.INTEGER || type == Types.BIGINT) { jrow.put(columnLabels[i], Long.valueOf(columnValue)); } else if(type == Types.DOUBLE || type == Types.FLOAT) { jrow.put(columnLabels[i], Double.valueOf(columnValue)); } else { jrow.put(columnLabels[i], columnValue); } } } return result; }finally{ connection.close(); } } 使输出为一个JSONArray对象 在前端页面index.html中修改方法M.shouResult // 格式化数据并显示 M.showResult = function(data) { // 创建一个 AfTemplate对象用于替换 var templ = new AfTemplate( $('.template').html()); var target = $(".main .content tbody"); target.html(""); // 清空 for(var row of data) { row.timeCreated =row.timeCreated.substr(0,16); target.append( templ.replace(row) ); } // 如果没有数据,则把下面的提示显示出来 if(data.length > 0) $(".main .content .empty").hide(); else $(".main .content .empty").show(); } 6.1 用户注册 实现用户以手机号进行注册 -数据库af_example.sql -注册页面 register.html -登录页面 login.html 手机号需要效验. 6.2 手机短信验证 在用户以手机号进行用户注册时,需要填写验证码 1.绑定手机号 2.点"发送验证码".后台系统将发送一个验证码到这个手机号,同时把验证码存在Session中. 3.用户在手机上查看短信验证码,填写到页面 4.用户点"注册"...后台对检查验证码是否正确.. 通过UserSendVerifyApi生成验证码 // 生成4位验证码 int randomCode = new Random().nextInt(5000) + 1000; String verifyCode = String.valueOf(randomCode); 通过MySmsService发送给手机 public class MySmsService { public static MySmsService i = new MySmsService(); //public static String serviceUrl = "http://127.0.0.1:8080/service/SendSms.api"; public static String serviceUrl = "http://service.afanihao.cn/SendSms.api"; // 从 http://service.afanihao.cn 上获取 API Key / API Secret private String API_KEY = "CAE742E06B27C66D7DD79B3CF3F043BB"; private String API_SECRET = "C0CDE27BC95F32E4C831A376C8E43F0254F6D55A"; private MySmsService() { } public void send(String cellphone, String code) throws Exception { for(int i=0; i<cellphone.length(); i++) { char ch = cellphone.charAt(i); if(ch<'0' || ch >'9') throw new Exception("手机号必须全是数字!"); } if(cellphone.length() != 11) throw new Exception("手机号必须是11位数字!"); if(code.length() > 6) throw new Exception("验证码最大6位!"); JSONObject jreq = new JSONObject(); jreq.put("apiKey", API_KEY.trim()); jreq.put("apiSecret", API_SECRET.trim()); jreq.put("target", cellphone); jreq.put("code", code); JSONObject jresp = REST.post(serviceUrl, jreq); int error = jresp.getInt("error"); if(error != 0) { String reason = jresp.getString("reason"); throw new Exception(reason); } } } 再通过UserRegisterApi验证 // 检查验证码 String verifyCode = jreq.getString("verifyCode").trim(); //从前台输入中获取 String code = (String)httpReq.getSession().getAttribute("verifyCode"); //从会话中的code获取 if(code == null || ! code.equals( verifyCode)) throw new Exception("验证码不匹配!"); 网站后台如何发送短信 注:参考<Java如何发送手机短信> 本章的例子是可以运行的,在发送短信的环节,需要大家按以下说明修改一下。 1 打开 http://service.afanihao.cn/ 2 注册一个账号 3 登录系统,可以看到你的API Key, API Secret 4 修改代码 MySmsService.java 内部原理: MySmsService里将手机号、验证码发给给 service.afanihao.cn , 然后由service.afanihao.cn这台服务器来发送一个短信到目标手机上。 注:如果你有一个已备案的带域名的个人网站,则可以各家云服务提供商自行申请短信功能, 然后用云平台的API来发送短信。(必须是已备案的网站才有可能申请成功)。 7.1 过滤器 Filter 网站开发的三大核心机制:Servlet,Session,Filter 过滤器的作用:对请求进行过滤处理 filter1 filter2 |--->Servlet 请求---------------------->| |--->静态文件 创建过滤器 演示:添加一个Filter 右键new Filter 最主要的是doFilter方法 过滤器的配置 Filter的配置也分注解方式和XML方式 URL规则和Servlet完全一样 //在xml中配置项 <url-pattern> 精准路径:如/home/ex.html 前缀匹配:如/download/* 后缀匹配:如*.api
public void init(FilterConfig fConfig) throws ServletException 初始化方法 public void destroy() 停止时调用. public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException 主要方法. @WebFilter("/login.html") //修改注释的拦截路径 注释,访问http://127.0.0.1:8080/demo06/login.html Filter方法被调用. 不想放行 public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { System.out.println("Filter 被调用"); HttpServletResponse httpResp =(HttpServletResponse)response; httpResp.sendError(403,"无权访问"); // chain.doFilter(request, response); } 7.2 访问过滤 假设有一些静态资源放在/download下面, 但是,其中的vip/目录下的资源要求登录后可见. // servletPath : 例如 /download/vip/yanran.jpg String servletPath = httpReq.getServletPath(); //.getServletPath() 获取路径 if(servletPath.startsWith("/download/vip/")) //.startsWith(String prefix) 检测字符串是否以指定的前缀开始。 if(servletPath.startsWith("/download/vip/") { { User user = (User) httpReq.getSession().getAttribute("user"); if(user == null) { // 第1种:可以返回重定向 302 // 控制浏览器重定向到 login.html // 注意:要计算出绝对路径 String contextPath = httpReq.getContextPath(); httpResp.sendRedirect(contextPath + "/login.html"); // 第2种:可以返回错误 4XX // httpResp.sendError(403, "请先登录"); // 第3种:可以返回200,并返回HTML内容 // httpResp.setCharacterEncoding("utf-8"); // httpResp.setContentType("text/html"); // httpResp.getWriter().write("<html><body>你还没登录呢!</body></html>"); return; } } 要点 1.使用前缀匹配/download/* 2.检查会话,确认用户身份 3.在Filter里返回应答 (通过HttpServletResponse对象来返回应答) 进一步理解过滤器写法 通过检查Session信息,让一部分请求通过,另一部分请求直接返回. 7.3 访问过滤(2) 示例: 假设有一些服务接口需要在登录后才能调用 例如:/GetSecret 是一个Servlet服务接口 要求当前用户有相应权限才能调用 MyFilter3 请求:/GetSecret <----------------->MyService3
Filter3 @WebFilter("/GetSecret") //通过注解的方式 比较 在Servlet里也可以进行权限检查啊 区别:在Filter里便于统一处理 例如,系统有30个接口,如果在Servlet里需要写30次,而在Filter里只写一次统一检查就可以了 演示了Filter对Servlet服务的过滤 注:Filter可以做很多事,不止是权限处理. 7.4 多重过滤 FilterChain,过滤链 指一个请求可以被多个过滤器依次过滤 chain.doFilter(request,response) 告诉Tomcat让后面的Filter继续处理这个请求. 示例: 请求/test/nice.html 添加3个过滤器: MyFilter4Html,MyFilter4Nice,MyFilterNiceHtml 3个过滤器匹配规则分别是*.html, /nice*, nice.html 显然,这三个过滤器都能拦截/test/nice.html 如果放行,则调用下面一行处理(继续后续处理) chain.doFilter(request, response); //chain链条,过滤链的意思. 讲解了什么是多重过滤,FilterChain,了解即可,比较简单 7.5 XML方式的配置 在web.xml里配置 Filter也有2种配置方式:注解,web.xml 为Filter添加配置参数 在web.xml里,可以配置一些参数 例如, //param参数 <init-param> <param-name>enableLog</param-name> <param-value>yes</param-value> </init-param> 然后在Filter.init()方法里可以读到这些数据... //init()初始化 一、XML方式配置 Filter 1. 新建一个 Filter ,如 MyFilter5 里面不要使用注解配置 2. 修改web.xml <filter> <filter-name>MyFilter5</filter-name> <filter-class>my.MyFilter5</filter-class> </filter> <filter-mapping> <filter-name>MyFilter5</filter-name> <url-pattern> /abc.html </url-pattern> </filter-mapping> 其中, filter-class表示类的路径,如 my.MyFilter5 Url-pattern 表示拦截的URL格式,如 /abc.html 二、添加配置参数 在web.xml 里,可以添加一些Filter的配置参数。 1 修改web.xml <filter> <filter-name>MyFilter5</filter-name> <filter-class>my.MyFilter5</filter-class> <init-param> <param-name> enableLog </param-name> <param-value> yes </param-value> </init-param> <init-param> <param-name> interval </param-name> <param-value> 30</param-value> </init-param> </filter> 这里,<init-param> 表示一个配置参数。 第一个参数的名字是 enableLog,值为 yes。第二个参数名称为interval,值为30 2 在 my.MyFilter5 中读取参数 public class MyFilter5 implements Filter { public void init(FilterConfig fConfig) throws ServletException { String enableLog = fConfig.getInitParameter("enableLog"); String interval = fConfig.getInitParameter("interval"); ... ... } ... ... } 在 init () 方法里,fConfig 就指的是web.xml 里传入的参数数据。使用fConfig.getInitParameter() 就可以取出配置参数。 实例: web.xml 添加配置 <!-- 配置 filter --> <filter> <filter-name>MyFilter5</filter-name> <filter-class>my.MyFilter5</filter-class> <init-param> <param-name> enableLog </param-name> <param-value> yes </param-value> </init-param> <init-param> <param-name> interval </param-name> <param-value> 30</param-value> </init-param> </filter> <filter-mapping> <filter-name>MyFilter5</filter-name> <url-pattern> /abc.html </url-pattern> </filter-mapping> Filter生命周期 1.当Web App被加载时,创建该Filter的实例,并调用init()方法(仅创建一个实例) 2.当有请求到来时,如果URL时匹配的,则由doFilter()进行处理 3.当Web App被卸载/关闭时,调用destroy()方法 Filter Chain里的顺序 当有多个Filter时,按出现的先后顺序依次执行 -web.xml方式:按出现的顺序排列 -注解方式:按类名的顺序排序 小结:学会了web.xml里配置Filter 在应用被加载时会创建Filter的单一实例 8.1 实例-食物与饮料 本章内容:通过一个实例来练习和理解Filter 并给出一个通用的权限检查框架 1.用户:已经添加3个用户 2.用户数据:包含食物和饮料的数量 3.增加食物:用户可以选择增加更多食物 4.增加饮料:用户可以选择增加更多饮料 8.2 首页登录检查.
写一个IndexFilter.java 重写doFilter方法 检查会话里保存的user,如果没有重定向到login.html public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest httpReq = (HttpServletRequest) request; HttpServletResponse httpResp =(HttpServletResponse) response; User user=(User)httpReq.getSession().getAttribute("user"); if(user==null) { httpResp.sendRedirect(httpReq.getContextPath()+"/login.html"); return; } chain.doFilter(request, response); } 在web.xml里配置这个Filter <!-- filter配置 --> <filter> <filter-name>IndexFilter</filter-name> <filter-class>my.IndexFilter</filter-class> </filter> <filter-mapping> <filter-name>IndexFilter</filter-name> <url-pattern>/</url-pattern> <url-pattern>/index.html</url-pattern> </filter-mapping> 实现用户如果未登录,则跳转到登录页面login.html ---------------------------------------------------------------------------------------------- 回忆af-service.xml配置的读取 使用反射技术改造框架 创建xml,new file,选择目录src下面创建af-service.xml 右键properties other UTF-8 用af-service.xml来描述对应关系: Hello.api->my.HelloApi HowOld.api->my.HowOldApi AreYouGood.api->my.AreYourGood.api af-service.xml <?xml version="1.0" encoding="UTF-8"?> <config> <service name="Hello" class="my.HelloApi" /> <service name="HowOld" class="my.HowOldApi" /> <service name="AreYouGood" class="my.AreYouGoodApi" /> </config> 然后在AfGenericService中重写init()方法. //init():在创建对象后,调用此方法进行初始化 @Override public void init() throws ServletException { //从xml配置文件中读取配置 try { loadConfig(); }catch(Exception e) { e.printStackTrace(); throw new Error("af-service.xml 格式不正确!启动终止启动!"); } } 再添加一个loadConfig()方法来实现读取 // 从 af-service.xml 中获取配置 private void loadConfig() throws Exception { InputStream stream = this.getClass().getResourceAsStream( "/af-service.xml"); SAXReader reader = new SAXReader(); Document doc = reader.read(stream); stream.close(); Element root = doc.getRootElement(); List<Element> xServiceList = root.elements("service"); for (Element e : xServiceList) { String name = e.attributeValue("name"); String clazzName = e.attributeValue("class"); configs.put(name, new ConfigItem(name, clazzName)); } protected HashMap<String, ConfigItem> configs = new HashMap<String, ConfigItem>(); af-config.xml中的配置项 // af-service.xml 中的配置项 class ConfigItem { public String name; // 服务接口名 public String clazzName; // 类名 public Class clazz; // 类的实体 public String charset = "UTF-8"; public ConfigItem(String name, String clazzName) { this.name = name; this.clazzName = clazzName; } } ------------------------------------------------------------------------------------------------ 8.3 后台登录检查 在以下接口中,要检查当前用户... UserData.api HaveFood.api HaveDrink.api 再次强调:后台检查是必须的,保存安全性和完整性 在UserLogin和UserLogout中不用进行用户检查,因为这两个接口本来就是用于用户登录. 权限检查框架 1.修改af-service.xml,添加permissions属性凡是有此属性的,表示需要后台进行权限检查 2.添加PermissionFilter -拦截*.api -读取af-service.xml -doFilter()里:进行后台权限检查 添加web.xml的配置 <!-- API后台权限检查 --> <filter> <filter-name>PermissionFilter</filter-name> <filter-class>my.PermissionFilter</filter-class> </filter> <filter-mapping> <filter-name>PermissionFilter</filter-name> <url-pattern>*.api</url-pattern> </filter-mapping> 修改af-service.xml <!-- 获取用户数据 --> <service name="UserData" class="my.userdata.UserDataApi" permissions=""/> <service name="HaveFood" class="my.userdata.HaveFoodApi" permissions=""/> <service name="HaveDrink" class="my.userdata.HaveDrinkApi" permissions=""/> 添加一个Filter类Permissions, //Permissions权限 初始化 protected HashMap<String, ConfigItem> configs = new HashMap<String, ConfigItem>(); public void init(FilterConfig fConfig) throws ServletException { // 从af-service.xml配置文件中读取配置 try { loadConfig(); } catch (Exception e) { throw new Error("af-service.xml 加载失败! " + e.getMessage()); } } 拦截所有后缀为api的请求. 8.4 用户权限细分 对用户的操作权限细分: 有的用户只有吃的权限"food" 有的用户只有喝的权限"drink" 添加user_permission表 规定每个用户的操作权限:food,drink... 在af-service.xml里,规定每个接口要检查的权限 UserData.api:已登录 HaveFood.api:已登录,具有food权限 HaveDrink.api:已登录,具有drink权限 修改af-service.xml <!-- 获取用户数据 --> <service name="UserData" class="my.userdata.UserDataApi" permissions=""/> <service name="HaveFood" class="my.userdata.HaveFoodApi" permissions="food"/> <service name="HaveDrink" class="my.userdata.HaveDrinkApi" permissions="drink"/> 添加pojo类UserPermission,对应数据库的表user_permisson 通过UserLoginapi获取用户权限存入会话 然后再在PermissionsFilter里添加方法 protected void checkPermission(List<String> permissions, HttpServletRequest request, HttpServletResponse response) throws AfRestfulException { User user = (User) request.getSession().getAttribute("user"); if (user == null) throw new AfRestfulException(-90, "尚未登录"); // 权限检查细分 if (permissions.size() > 0) { // af-service.xml:permission:表示调用此API必须有这些属性 // Session里保存当前用户有哪些权限"user.permission" String s = (String) request.getSession().getAttribute("user.permissions"); for (String p : permissions) { if (s.indexOf(p) < 0) throw new AfRestfulException(-91, "没有权限" + p); } } } 在doFilter里调用checkPermission方法 8.5 通用用户权限检查框架 在项目中如果需要进行权限检查,可以使用这个框架. 1.修改af-service.xml,添加permission属性 2.新建PermissonFilter -继承于AfPermissionFilter -拦截*.api请求 -重写checkPermission()方法,检查权限 9.1 Filter于Servlet 一般来说,Servlet用于创建服务,而Filter用于请求过滤 但是实际上,Filter可以完全替代Servlet 例如,使用Filter也可以实现API请求 演示:添加一个HelloApi... @WebFilter("/Hello.api") //这里是WebFilter而不是WebServlet public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { JSONObject jresp=new JSONObject(); jresp.put("er", 0); jresp.put("reason", "OK"); //发送请求给客户端 response.setCharacterEncoding("UTF-8"); response.setContentType("text/plain"); Writer writer=response.getWriter(); writer.write(jresp.toString(2)); writer.close(); } 可以实现和Servlet一样的效果 小结 Filter一般用于过滤,可以串成一个过滤链 Servlet一般用于实现一个服务 Filter亦可以用于实现Servlet的所有功能 9.2 设定客户端缓存 Filter可以对请求做出的三种反应: 1.通过 2.不通过,直接返回应答 3.通过,但对请求/应答施加修改 例如:修改Session,header等 Filter修改请求头部 示例:禁用客户端缓存,解决谷歌浏览器不能自动刷新的问题 添加Filter拦截*.js *.css对静态文件的请求,在应答里添加:Cache-Control:max-age=0 //max-age表示缓存的时间,0表示不缓存,10表示10s //注解参数是一种String数组,设定多个Url pattern @WebFilter({"*.js","*.css","*.html"}) public class HelloApi implements Filter { public void init(FilterConfig fConfig) throws ServletException { } public void destroy() { } public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletResponse hresp=(HttpServletResponse)response; hresp.addHeader("Cache-Control", "max-age=15"); chain.doFilter(request, response); } } 小结 Filter里可以修改request,response,session.
9.3 系统初始化 初始化加载 Filter的特性: -单例(只创建一次) -随应用启动而创建,创建时init被调用 所有,可以利用Filter的特性,把系统初始化代码放在Filter.init()里. 示例:创建一个后台线程,此线程用于定期清理某些目录下的垃圾文件 问题:此线程在哪里启动它? 在init()里启动 @WebFilter() //不写参数,不做拦截. 小结所有的全局对象,后台线程都可以在Filter.init()里进行初始化,在destory()里做关闭. 10.1 伪静态文件 静态static,内容不发生改变的 静态文件:即应用目录下真实存在的文件如html,js,css,mp4,jpg 一般来说,我们通过URL的形式能区分是不是静态文件. 例如,/service/GetList.do /images/photo.jpg 伪静态文件:看起来似乎是一个静态文件,但实际并不是一个文件 例如,当浏览器里打开 http://127.0.0.1:8080/mid1001/example.js 在客户端看起来感觉它就是一个JS文件,但是... 10.2 伪静态JPG(二维码) 后台动态生成的JPG. 使用 try { response.setContentType("image/jpeg"); OutputStream outStream = response.getOutputStream(); String website = "https://www.cnblogs.com/cqbstyx/"; new AfQRCode(150).generate( website , outStream); outStream.close(); } 来生成二维码 AfQRCode()通过使用lib ZXing来实现. 小结: 这说明,即使是图片也不一定是静态文件,使用AfQRCode,可以根据上下文的内容来生成一张二维码的图片 10.3 伪静态HTML 演示: http://127.0.0.1:8080/mid1001/query/20180001.html http://127.0.0.1:8080/mid1001/query/20180002.html 此网页不是静态文件,而是后台动态生成的. @WebServlet("/query/*") public class QueryService extends HttpServlet { protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 请求格式 /query/20180001.html // TODO: 此处最好 try..catch..一下,防止输入格式不对 String requestUri = request.getRequestURI(); int p1 = requestUri.lastIndexOf('/'); int p2 = requestUri.lastIndexOf('.'); String filename = requestUri.substring(p1 + 1, p2); // 文件名就是学号 int id = Integer.valueOf(filename); Student s = query(id); if(s == null) { response.sendError(400, "不存在的学号!"); return; } try { // 生成HTML: 模板 + 数据 => 内容 String content = generateHtml(s); response.setCharacterEncoding("UTF-8"); response.setContentType("text/html"); response.getWriter().write(content); } catch (Exception e) { e.printStackTrace(); response.sendError(500, e.getMessage()); } } // 模拟:从数据库中查询数据 , 这里仅作原理演示 private Student query(int id) { // 模拟一个数据源 List<Student> source = new ArrayList<Student>(); source.add(new Student(20180001, "邵", 98)); source.add(new Student(20180002, "王", 86)); source.add(new Student(20180003, "赵", 90)); for(Student s : source) { if(s.id == id) return s; } return null; } private String generateHtml(Student s) throws Exception { // 应用所在目录 String appDir = this.getServletContext().getRealPath("/"); //TODO: FreeMarker的规范使用请见 : Java学习指南(FreeMarker篇) // 本节课仅作演示 Configuration cfg = new Configuration(Configuration.VERSION_2_3_22); cfg.setDirectoryForTemplateLoading(new File(appDir)); cfg.setDefaultEncoding("UTF-8"); cfg.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER); cfg.setLogTemplateExceptions(false); // 准备数据 HashMap<String,Object> model = new HashMap(); model.put("result", s); model.put("contextPath", getServletContext().getContextPath()); // 模板替换 Template templ = cfg.getTemplate("test3.html"); StringWriter writer = new StringWriter(); templ.process(model, writer); return writer.toString(); } } 小结 除了Servlet,Session,Filter三大核心机制之外,两个最常用的工具: FreeMarker,HttpClient. 11.1 伪静态JS框架 伪静态JS框架AfJsxService AfJsxService:使用这个框架可以很方便的实现伪静态JS/JSON文件 此框架也封装在af-web.jar里 一、在项目中添加框架支持 1 在项目里加入 af-web.jar 或者 af.web.jsx.* 源码支持 2 添加依赖的jar: json-org, dom4j 3 修改web.xml <!-- 伪静态JS/JSON 服务框架 --> <servlet> <servlet-name>AfJsxService</servlet-name> <servlet-class>af.web.jsx.AfJsxService</servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>AfJsxService</servlet-name> <url-pattern>*.jsx</url-pattern> <url-pattern>*.jsonx</url-pattern> </servlet-mapping> 二、添加伪静态JS 4 添加 my.jsx.JsxUser public class JsxUser extends AfJsxCreator { } 5 修改 af-service.xml <jsx name="user" class="my.jsx.JsxUser" /> 6 测试 http://127.0.0.1:8080/mid1101/user.jsx http://127.0.0.1:8080/mid1101/user.jsonx 7 在HTML中引入 <script type="text/javascript" src="user.jsx" ></script> 由于后台是以 *.jsx 匹配的,所以src属性里不需要加路径。 特点: 1.可以生成js和json 2.在HTML里引入时不用考虑路径 3.文件名就是js对象名 11.2 同步加载和异步加载 同步加载:使用<script src='...'/>方式引入JS 同步加载按照顺序来加载 例如: <script type="text/javascript" src="user.jsx"></script> 异步加载:使用AJAX方式加载 -不等待服务器返回结果,立刻返回. -当服务器返回结果时,调用回调方法进行处理. $.get("user.jsonx",...) $.ajax(...) 同步加载. 何时使用jsx框架?在页面加载初始化数据时使用(同步加载) 注:与RESTful框架结合使用,当需要页面初始化时候加载的数据可以使用jsx框架 11.3 JSX框架的原理 1.AfJsxService是Servlet 2.从af-service.xml里读取配置 3.分发处理在AfJsxCreator里,可以访问request,response,queryParams... 12.1 服务器重定向 服务器重定向:在服务端将请求重定向到另一个资源 演示: 假设网站正在维护升级,则输入首页时,跳转到升级提示页面... http://127.0.0.1:8080/mid1201/index.html 通过一个UpgradeFilter来拦截访问index.html的请求. 在UpgradeFilter里的doFilter方法来实现重定向 boolean upgrading = true; // 演示:网站状态 public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest req = (HttpServletRequest) request; HttpServletResponse resp = (HttpServletResponse) response; // 网站正在升级,重定向到 /upgrade.html if (upgrading) { // 服务端重定向, 前面不需要加 ContextPath (如 /mid1201) request.getRequestDispatcher("/upgrade.html").forward(request, response); // 302 重定向 // resp.sendRedirect( req.getServletContext().getContextPath() + "/upgrade.html"); return; } chain.doFilter(request, response); } 服务器重定向: request.getRequestDispatcher("/upgrade.html").forward(request,response); RequestDispatcher:请求分发处理器 forward:推送,转发的意思. 服务器重定向 // 服务端重定向, 前面不需要加 ContextPath (如 /mid1201) request.getRequestDispatcher("/upgrade.html").forward(request, response); 302重定向 // 302 重定向, 前面要加 ContextPath String contextPath = req.getServletContext().getContextPath(); resp.sendRedirect( contextPath + "/upgrade.html"); 比较:sendRedirect() sendRedirect()是两次请求: -先返回一个302应答,然后客户端再发起一个请求. -浏览器地址栏的URL发送变化. -参数URL是任意地址(包含ContetPath) 而RequestDispatcher 服务器重定向是一次请求: -第一次Filter/Servlet并不返回应答 -请求交给RequestDispatcher重新分发处理 -浏览器地址不变 -目标参数是站内资源的地址(不包含ContextPath) 小结:介绍了服务器重定向的方法,介绍了服务器重定向和302重定向的区别 12.2 URL地址重写 URL地址重写,URL Rewrite 一般用于实现伪静态文件 实例: 有一个服务可用于得到用户头像. /mid1201/GetPhoto?id=20180001 用服务器重定技术作一个伪静态地址 /mid1201/photo/20180002.jpg 现在,同一个资源有2个访问地址,当访问地址A时重定向到地址B...称为URL Rewrite 由Filter PhotoUrlRewriter来拦截photo/*的请求, String target = "/GetPhoto?id=" + filename; 然后服务器重定向到request.getRequestDispatcher(target).forward(request, response); 再由GetPhott的Servlet来实现服务 比较 服务器重定向的效率略低,多一道处理 其实,为了提高运行效率,可以直接由GetPhoto来处理.即,让GetPhoto直接映射处理/photo/*会更快一些. 小结:介绍了使用URL地址重写的实现方法. 由于Java Web的可选实现手段很多,可以根据需求选择适合自己的实现方式. 13.1 网站日志 网站日志:即用日志文件记录网站发生了什么事情 日志分为两类: 1.系统运行日志 2.用户操作日志 注:网站时7*24小时不间断运行的! 网站访问日志 在TOMCAT的logs目录下,有tomcat自带的日志 网站访问日志,形如localhost_access_log.2018-07-18.txt 内容格式:来访问IP,时间,URI,应答状态码 访问日志的设置在server.xml里 <Host name="localhost" appBase="webapps" unpackWARs="true" autoDeploy="true"> <Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs" prefix="localhost_access_log" suffix=".txt" pattern="%h %l %u %t "%r" %s %b" /> <Context path="/demo" docBase="C:\Users\renhao.chen\Desktop\java\a.javaee\mid1202\WebRoot" reloadable="true" /> </Host> 日志清理 logs\下面的日志,需要我们自己清理 比如,自己启动一个新理线程来清理半年以前的日志. 13.2 添加日志输出 添加日志输出 log4j,logger for java 使用log4j在项目代码里添加日志输出. 注:log4j是一个简单好用的小工具,不必深入研究 使用log4j 在代码里输出日志 一、添加log4j支持 1 添加jar包 log4j-1.2.17.jar commons-logging-1.1.3.jar commons-io-2.4.jar 2 添加配置文件 在 src\ 目录下添加 log4j.properties Log4j的配置直接使用项目里的模板,稍微修改几项即可 (注意红字部分) log4j.logger.af=DEBUG,C,R log4j.logger.my=DEBUG,C,R //日志输出到控制台 log4j.appender.C=org.apache.log4j.ConsoleAppender log4j.appender.C.Threshold=DEBUG log4j.appender.C.layout=org.apache.log4j.PatternLayout log4j.appender.C.layout.ConversionPattern=%-d{HH:mm:ss} %-5p [XYZ] %c %x - %m%n //日志输出到目标地址 log4j.appender.R=org.apache.log4j.RollingFileAppender log4j.appender.R.Threshold=DEBUG log4j.appender.R.File=c:/mylogs/xyz.log log4j.appender.R.MaxFileSize=4000KB log4j.appender.R.MaxBackupIndex=1 log4j.appender.R.layout=org.apache.log4j.PatternLayout log4j.appender.R.layout.ConversionPattern=%-d{yyyy-MM-dd HH:mm:ss} %-5p %c %x - %m%n 此配置文件表示: (1) 包名 af.* 和 my.* (2) 一路输出到控制台 (3) 一路输出到文件: 位置为 c:/mylogs/xyz.log 二、在代码里输出日志 1 创建 Logger 对象 添加一个 Logger对象用于输出日志 static Logger logger = Logger.getLogger(ExampleService.class); 这个对象是轻量级的,意味着在每个类里都可以添加一个 Logger对象。 这个对象是线程安全的,意味着不用担心线程重入的问题。 2 输出日志的级别 logger.trace() - 普通级别,一般不太用到 logger.debug() - 调试输出 logger.info() - 信息输出 logger.warn() - 警告输出 logger.error() - 错误输出 logger.fatal() - 致命错误输出,一般不太用到 常用的就是 debug, info , warn 和 error 这四级输出 3 按级别过滤日志 在 log4j.properties 可以设置日志输出级别。 log4j.appender.C.Threshold=DEBUG //DEBUG以下的就不输出 例如,如果级别定为WARN,则WARN以下级别的日志就被屏蔽了。 log4j.logger.af=DEBUG,C,R log4j.logger.my=DEBUG,C,R 13.3 网站性能监视 网站长年累月不间断的运行,其性能是否稳定十分重要. 注:tomcat是一个java写的程序,其实就是要监视这个java进程的运行情况... 网站性能监视 关注以下数据. -CPU -内存 -线程数 -句柄数 使用工具:jconsole,系统的任务管理器 任务管理器 使用windows/linux自带的任务管理器 对tomcat进程进行监视 -cpu/内存:是否一直攀升? -线程数:是否一直攀升? -句柄数:是否一直攀升? 任务管理器 内存占用:检查代码的new对象是不是太频繁了 线程数:检查代码里线程的创建 句柄数:检查代码里的文件句柄,Socket句柄 例如: FileInputStream是否关闭了? Socket是否关闭了 JDBC Connection是否关闭了 JConsole:JDK自带工具 小结:介绍了如何对tomcat进程进行性能监视 注:此技能要在实际工程中慢慢累积经验 13.4 网站项目部署 在网站开发调试完毕后,需要部署到服务器主机上 服务器/域名/IP 1.服务器主机:购买云主机/或自备主机 2.IP:云主机自带公网IP/自备主机需另外购买IP 3.域名:需购买一个域名,并将域名指向IP 端口 修改server.xml里端口的配置,一般网站端口为80 示例: <Connector port="80" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443"> 然后,在浏览器输入地址的时候. http://xxx.cn/ http://xxx.cn:80/ 部署应用 将WebRoot拷贝到服务器主机上 然后进行配置 一、主应用 tomcat会自动加载webapps下的子目录,并默认规定 ROOT 目录为主应用目录 例如, Webapps\ - ROOT\ - music\ - bbs\ 表示在tomcat下部署了3个应用,ROOT主为应用 在访问时: ROOT: http://your.com/ Music: http://your.com/music/ Bbs: http://your.com/bbs/ 二、webapps的位置 webapps的位置可以自由指定,例如,指向 e:\somewhere\ 目录 <Host name="localhost" appBase="e:\somewhere\" ... ... ...> 三、映射目录 在正式部署的时候,可能不太使用映射目录 在开发的时候,经常使用 例如,我们在练习中经常使用的 <Host name="localhost" ... > .... <Context path="/mid1301" docBase="E:\JavaWeb\mid1301\WebRoot" /> </Host> 四、多域名 (本节为高级内容) 一台主机上可以部署两个网站的内容。 例如,我有2个网站域名,但只有一台主机, www.afanihao.cn service.afanihao.cn 则可以这样部署,在 service.xml 里添加2个 <Host> 配置 <Host name="www.afanihao.cn" appBase="c:/java/www_afanihao_cn" unpackWARs="true" autoDeploy="true"> </Host> <Host name="service.afanihao.cn" appBase="c:/java/service_afanihao_cn" unpackWARs="true" autoDeploy="true"> </Host> tomcat会自动适配: 当请求里的域名为 www.afanihao.cn 时,自动适配到 c:/java/www_afanihao_cn/ 目录。 当请求里的域名为 service.afanihao.cn 时,自动适配到c:/java/service_afanihao_cn 目录 一般情况下,可以把应用拷贝到webapps/ROOT下就可以了. 小结: 介绍了WEB应用的部署方法 注:本节课只介绍了几个基本点,另见centos服务器篇和网站高级篇. 14.1 开始网站项目 BBS论坛项目 网站类型有很多种,BBS,图片网站,视频网站,博客网站,微博网站,电商网站,公司门户,新闻门户,政府门户 行业内还有专门基于BS的网站系统. 本篇最后两章演示一个BBS论坛项目. 什么是完全的网站? 比如,用户如要有登录,就必须有退出,如果有更改密码,就得有找回密码.这才是完全网站 显然,在演示中并不需要实现完全功能的网站. 首先要考虑的问题 1.网站可以由一个人独立完成吗? 可以完成,但是没必要. 2.你的网站有哪些人使用?有多少人使用? (设计网站之前的首要问题.) 14.2 需求分析 需求,Requirement 就是要实现什么东西(功能+性能) 本项目的功能需求: -个人注册,登录 -按栏浏览 -发帖和回复 参考:bbs.tianya.cn 性能需求: -10万用户量,200并发访问 //设计和用户量紧密相关 -首页浏览要快,发帖要快. 为什么要有需求分析 1.防止所实现的并不是客户想要的 2.防止需求发散,分阶段实现 3.需求分析要固化为文档,避免最后漏项,不匹配 注意:实际项目的需求分析是一个重要过程 需要充分讨论认证 不是一两天能完成的. 14.3 网站设计 网站系统的通用框架(B/S) 浏览器<----->Tomcat<------->MySQL 后台架构:即网站入门和中级篇中的技术 restful接口调用 upload文件上传 jsx伪静态数据调用 permissons权限检查 注:本例重点在与练习巩固,不引入其他新技术 数据库设计 导入af_bbs.sql admin:系统管理员 user:用户 article:帖子 folder:栏目 设计阶段给出主要的表和字段 如有遗漏,开发过程中可以补充 14.4 搭建WEB项目 本章提供一个给管理员操作的界面 项目名称bbs_admin 映射路径/bbs/admin 后台服务框架 添加af-web.jar及依赖jar包 修改web.xml 添加af-service.xml 后台数据库支持 添加af-sql.jar及依赖jar包 添加c3p0-config.xml并修改 拷贝MyC3P0Factory工具类 生成POJO类. //自动映射到数据库,查看前面 前端使用jquery.js,afquery.js 运行测试,添加index.html,在浏览器里测试 14.5 界面布局 使用HBuilder实现界面布局, index.html总布局 folder.html子页面 info.html子页面 14.6 栏目列表与新增 添加栏目列表功能,列出所有栏目 SuFolderList.api (Su:Super User管理接口前缀) -后台用Myeclipse,前端使用HBuilder.. -怎么调试子页面的JS -此时还没有数据,怎么测试栏目列表 动态调试JS脚本文件 sourceURL(源映射)—> //@ sourceURL=b.js //@ sourceURL=b.js (要调试当前文件的全名) 如果还没有写添加功能,可以直接在数据库中添加数据进行调试. 新增栏目 1.调用G.openPage('folder_add.html') 2.输入栏目标题,保存. 14.7 栏目改名