网站中级

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 &quot;%r&quot; %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    栏目改名

 

posted @ 2019-02-28 11:30  ricky0001  阅读(248)  评论(0编辑  收藏  举报