IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> 前后端分离 -> 正文阅读

[Java知识库]前后端分离

接:Servlet的日常开发(基于场景)_林纾???的博客-CSDN博客

目录

一、模板技术

二、前后端分离

总结—图析:

三、Java中为什么需要这么多类

1.承载数据的对象

2.承载流程的对象

对上面代码重构,分一下包:

四、浏览器以JSON格式把数据给服务器

1)通过JSON方式发送一个请求体

2)从请求体中把数据读出

五、eg:在浏览器中看到一个页面的简单架构


? ? ? 观察上个博客中的代码可知,拼接HTML是比较麻烦的(都是字符串操作),需要手写写出所有字符串,可以有两种技术来改善:

1.模板技术(jsp、thymeleaf...)

2.前后端分离(后端只负责提供数据,以JSON格式,前端JS修改DOM树)

一、模板技术

提前把HTML的内容放在一个文件中

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>模板</title>
</head>
<body>
    <h1>你好 {{ target }}</h1>
</body>
</html>

<!--这里写在项目的目录下,不写在webapp目录下-->

//Template:模板
    //模板模式:提前写好一个文件(template.html)作为模板
public class WithTemplate {
    public static void main(String[] args) throws IOException {
        try(FileInputStream is=new FileInputStream("template.html")){
            try(Reader reader=new InputStreamReader(is,"UTF-8")){
                char[] buf=new char[1024];
                int n=reader.read(buf);
                String template=new String(buf,0,n);
                String s=template.replace("{{ target }}","世界");
                System.out.println(s);
            }
        }
    }
}

二、前后端分离

前端通过ajax技术,从后端(通过HTTP协议)读取数据(数据的格式是JSON为主)

eg:对录入成绩前后端分离

静态资源:

<!DOCTYPE html>
<html lang="zh-hans">
<head>
    <meta charset="UTF-8">
    <title>成绩列表</title>
</head>
<body>
    <table>
        <tbody>
        </tbody>
    </table>
    <!--前端要去后端去取一定需要一个js-->
    <script>
        function render(students) {
            var tbody = document.querySelector('tbody');
            for (var i in students) {
                var s = students[i];
                var tr = `<tr><td>${s.姓名}</td><td>${s.成绩}</td></tr>`;

                tbody.innerHTML = tbody.innerHTML + tr;
            }
        }

        var xhr = new XMLHttpRequest();//构建一个ajax出来
        xhr.open("GET", "/data/grade-list.json");//把这个目录下的数据给我//注意下面动态时的路径写的是/grade-list.json
         //前端只用读取json数据,读取出来后通过DOM树的修改操作把它加到tbody中
        xhr.onload = function() {
            // this.responseText 是 JSON 格式的字符串,是我们得到的响应体
            // 进行 JSON 的反序列(进行JSON解码)
            var students = JSON.parse(this.responseText);

            // 进行渲染操作 (渲染:render)(进行DOM树的修改,即渲染操作)
            render(students);
        }
        xhr.send();//发送
    </script>
</body>
</html>

/**
 * .json下的静态资源改为动态资源
 **/
@WebServlet("/grade-list.json")
public class GradeListJsonServlet extends HttpServlet {
    private final HashMap<String,Integer> gradeMap=new HashMap<>();

    //初始化
    @Override
    public void init() throws ServletException {
        gradeMap.put("小A",100);
        gradeMap.put("小B",90);
        gradeMap.put("小C",80);
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //输出:json格式
        resp.setCharacterEncoding("utf-8");
        resp.setContentType("application/json");//json格式的输出
        PrintWriter writer = resp.getWriter();//把小A...这组数据类似拼接成开心超人那些属性那样的格式
        //调用getJSON方法进行拼接操作
        String json=getJSON();

        writer.println(json);
    }

    //手动写拼接方法
    private String getJSON() {
        StringBuilder sb=new StringBuilder();
        sb.append("[");
        for (Map.Entry<String,Integer> entry: gradeMap.entrySet()) {
            String name= entry.getKey();
            int value=entry.getValue();
            sb.append("{")
                    .append("\"姓名\": ")
                    .append("\"")
                    .append(name)
                    .append("\",")
                    .append("\"成绩\": ")
                    .append(value)
                    .append("},");
        }
        sb.delete(sb.length()-1,sb.length());
        sb.append("]");
        return sb.toString();
    }

}

上面改动态资源是用拼接json格式自己拼接的,比较麻烦,可以引用别人提供好的方法去做:引入一个第三方包:

<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.12.4</version>
</dependency>
/**
 * .json下的静态资源改为动态资源
 **/
@WebServlet("/grade-list.json")
public class GradeListJsonServlet extends HttpServlet {
//    private final HashMap<String,Integer> gradeMap=new HashMap<>();
    List<Map<String,Object>> gradeList=new ArrayList<>();//list里面放map
    //初始化
    @Override
    public void init() throws ServletException {
//        gradeMap.put("小A",100);
//        gradeMap.put("小B",90);
//        gradeMap.put("小C",80);
        {
            Map<String,Object> s=new HashMap<>();
            s.put("姓名","小王");
            s.put("成绩",100);
            gradeList.add(s);
        }
        {
            Map<String,Object> s=new HashMap<>();
            s.put("姓名","小李");
            s.put("成绩",90);
            gradeList.add(s);
        }
        {
            Map<String,Object> s=new HashMap<>();
            s.put("姓名","小张");
            s.put("成绩",80);
            gradeList.add(s);
        }
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //输出:json格式
        resp.setCharacterEncoding("utf-8");
        resp.setContentType("application/json");//json格式的输出
        PrintWriter writer = resp.getWriter();//把小A...这组数据类似拼接成开心超人那些属性那样的格式
        //调用getJSON方法进行拼接操作
        String json=getJSON();

        writer.println(json);
    }

    //2.调用别人写好的东西
    //数组或线性表方式存储(要按协议来,map方式存储是不遵守协议的,打印不一样)//期望打印达到的效果是上面静态.json文件中哪个效果
    private String getJSON() {
        ObjectMapper om=new ObjectMapper();
        try{
//            return om.writeValueAsString(gradeList);
            return om.writerWithDefaultPrettyPrinter().writeValueAsString(gradeList);//与上面一样,只是网页显示上带一些换行和缩进,对数据格式没影响
        }catch (JsonProcessingException e){
            throw new RuntimeException(e);
        }
    }

}

? ? ? 小结:index.html通过构建ajax GET(用户直接GET)请求动态资源/grade-list.json,动态资源根据数据修改DOM树(将index.html内容按所想要的方式呈现)

继续改造:

对象和Map其实是一回事,JSON直接支持定义好一个类往里面放东西,所以可以:

class? Grade{

? ? ? ? 姓名;

? ? ? ? 成绩;

}

List<Grade>

public class Grade {
    public String 姓名;
    public int 成绩;
}
/**
 * .json下的静态资源改为动态资源(动态获取json的一个工程)
 **/
@WebServlet("/grade-list.json")
public class GradeListJsonServlet extends HttpServlet {
//    private final HashMap<String,Integer> gradeMap=new HashMap<>();
    List<Map<String,Object>> gradeList=new ArrayList<>();//list里面放map
    List<Grade> gradeList2=new ArrayList<>();
    //初始化
    @Override
    public void init() throws ServletException {
        {
            Grade g=new Grade();
            g.姓名="胡图图";
            g.成绩=60;
            gradeList2.add(g);
        }
        {
            Grade g=new Grade();
            g.姓名="张小丽";
            g.成绩=98;
            gradeList2.add(g);
        }


//        gradeMap.put("小A",100);
//        gradeMap.put("小B",90);
//        gradeMap.put("小C",80);
        {
            Map<String,Object> s=new HashMap<>();
            s.put("姓名","小王");
            s.put("成绩",100);
            gradeList.add(s);
        }
        {
            Map<String,Object> s=new HashMap<>();
            s.put("姓名","小李");
            s.put("成绩",90);
            gradeList.add(s);
        }
        {
            Map<String,Object> s=new HashMap<>();
            s.put("姓名","小张");
            s.put("成绩",80);
            gradeList.add(s);
        }
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //输出:json格式
        resp.setCharacterEncoding("utf-8");
        resp.setContentType("application/json");//json格式的输出
        PrintWriter writer = resp.getWriter();//把小A...这组数据类似拼接成开心超人那些属性那样的格式
        //调用getJSON方法进行拼接操作
        String json=getJSON();

        writer.println(json);
    }

    //2.调用别人写好的东西
    //数组或线性表方式存储(要按协议来,map方式存储是不遵守协议的,打印不一样)//期望打印达到的效果是上面静态.json文件中哪个效果
    private String getJSON() {
        ObjectMapper om=new ObjectMapper();
        try{
            //将gradeList2序列化
            return om.writerWithDefaultPrettyPrinter().writeValueAsString(gradeList2);

            return om.writeValueAsString(gradeList);
//            return om.writerWithDefaultPrettyPrinter().writeValueAsString(gradeList);//与上面一样,只是网页显示上带一些换行和缩进,对数据格式没影响
        }catch (JsonProcessingException e){
            throw new RuntimeException(e);
        }
    }
}

? ? ? 问题:一般java中不直接用中文起名,如Grade类中的姓名成绩,一般用英文起名,但是修改代码会出问题,即你在Grade修改成英文时,访问grade-list.json显示修改成功,但是调用index显示是错误显示的(因为html中写的是姓名成绩两个中文)。

解决:通过注解的方式

? ? ? 此时数据全部处理OK,可以将数据改到数据库中(利用之前数据库grades表,直接在数据库中查询),此时代码:


/**
 * GetGradeList:从数据源(目前是MySQL)中,获取对应的数据
 * 只要调用这个方法就可以从数据库把数据查出来
 */
public class GetGradeList {
    List<Grade> list=new ArrayList<>();
    public List<Grade>  getList(){
        try (Connection c= DBUtil.connection()){
            String sql="select name,grade from grades order by id";
            try(PreparedStatement ps=c.prepareStatement(sql)){
                try (ResultSet rs= ps.executeQuery()){
                    while (rs.next()){
                        Grade grade=new Grade();
                        grade.name=rs.getString("name");
                        grade.score=rs.getInt("grade");
                        list.add(grade);
                    }
                }
            }
        }catch (SQLException e){
            throw new RuntimeException(e);
        }
        return list;
    }
}
@WebServlet("/grade-list.json")
public class GradeListJsonServlet extends HttpServlet {

    //初始化GetGradeList类:(该类负责从数据库中查出数据并放在了该类的list中返回)
    private final GetGradeList getGradeList=new GetGradeList();

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //输出:json格式
        resp.setCharacterEncoding("utf-8");
        resp.setContentType("application/json");//json格式的输出
        PrintWriter writer = resp.getWriter();//把小A...这组数据类似拼接成开心超人那些属性那样的格式
        //调用getJSON方法进行拼接操作
        String json=getJSON();

        writer.println(json);
    }
    //调用别人写好的东西
    //将数据来源改为从数据库拿(与其他没有差别,只是这里数据源出现区别,变成了数据库中读到的数据)
    private String getJSON() {
        ObjectMapper om=new ObjectMapper();
        try{
            List<Grade> list=getGradeList.getList();//获取GetGradeList类的对象getGradeList调用到的数据(name,score)给list
           return  om.writerWithDefaultPrettyPrinter().writeValueAsString(list);//将数据序列化(调用别人写好的方法将数据转化为了对应的json格式)
        }catch (JsonProcessingException e){
            throw new RuntimeException(e);
        }
    }
}

总结—图析:

三、Java中为什么需要这么多类

java中一切皆对象,可分成两种:

1.承载数据的对象

? ? ? eg:宫保鸡丁、鱼香肉丝、麻婆豆腐:是加工出来的产品

? ? ? 承载数据重要的是属性,如上面Grade类,类中只有属性name? score,完全无方法,这就是承载数据的对象,以数据位主。

数据承担的是各种各样的序列化如上面代码json格式的序列化
数据为什么序列化序列化的目标是存储和搬运(酒厂中的酒才需要搬运,厂房不要)
一般承载数据的对象中的方法equals、comparable、hashCode(产品间才有比较的价值)

2.承载流程的对象

? ? ? eg:前台、服务员、厨师:流程,是职责中的一部分

? ? ? 承载流程重要的是方法,如上面代码GetGradeList和GradeListJsonServlet就是流程,GetGradeList就是从数据库中把对象查出来这个过程,就是个流程/方法。

? ? ? 总而言之,实际开发中,有偏向数据的对象,有偏向属性的对象,一般也没有纯数据或纯方法,讲求一个偏向性来理解。

一般使用流程时主要是单例的

1)MVC:Model(模型-厨师)+View(展示)+Controller(控制器)

2)Controller(前台)+Service(服务员)+DataAccessObject(数据访问对象DAO(厨师))

? ? ? Controller负责承接HTTP,即对接过来的客户点什么菜,DAO,即dao对象,库管,从数据库(仓库)里面把东西拿出来,Service上面代码无,我们流程简单,只是拿出来就给前台了,若业务复杂,就需要服务员参与。

对上面代码重构,分一下包:

? ? ? GetGradeList放在dao层/包下,改名GradeDao,可以给看的人快速知道这是从数据库取东西的一个类,数据放在model层下,model包下放承载数据职责的类,util包下放DBUtil工具。

Model类型对象也被称为DataObject(DO),注意不要和DAO混起来。

四、浏览器以JSON格式把数据给服务器

? ? ? 之前代码是服务器以json格式把数据给浏览器,浏览器以表单(form)形式将数据交给服务器,但还可以以json方式发送过去,如下:

1)通过JSON方式发送一个请求体

<!DOCTYPE html>
<html lang="zh-hans">
<head>
    <meta charset="UTF-8">
    <title>只做json数据的发送,即浏览器以json形式把数据发给服务器</title>
</head>
<body>
    <button>发送 JSON 到服务器</button>
    <script>
        // 只能使用 ajax 做到

        var btn = document.querySelector('button');
        //绑定一个事件
        btn.onclick = function() {
            var students = [
                { 姓名: "小赵", 成绩: 83 },
                { 姓名: "小钱", 成绩: 84 },
                { 姓名: "小李", 成绩: 85 }
            ];
            //写js代码,key可以不加引号,所以姓名那里没加引号

            // 1. 先 JS 的数据变成 String 类型 —— JSON 序列化【准备数据】
            // 反序列化: JSON.parse(...);
            // 序列化: JSON.stringify


            //对students序列化为json格式的内容
            var s = JSON.stringify(students);
            console.log(s);//通过打印观察,s就是一串字符串(JSON格式的)
            console.log(students);//通过打印观察,students就是上面所写的那一组数据

            // 2. 发送,因为要在请求体中携带 JSON 数据,所以只能使用 POST 方法
            var xhr = new XMLHttpRequest();
            xhr.open("POST", "/get-json-from-request-body");
            xhr.setRequestHeader("Content-Type", "application/json; charset=utf-8");//设置请求头
            xhr.send(s);    // 发送。send 的参数,就是请求体(传入s就代表作为请求体发送)
        }
    </script>
</body>
</html>

分析:students和json之间是相互转化的,String(JSON下的/JSON格式)到数据(json格式解析后的内容)是在JS中调的是JSON.parse(...),叫做解序列化过程,数据到String,在JS中是JSON.stringify(...),叫做序列化过程。(数据->String在java中用ObjectMapper.writeValueAsString(...),String->数据在java中用ObjectMapper.readValue(...)

2)从请求体中把数据读出

package com.wy4.servlet;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.wy4.model.Grade;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
/**
 * @author 美女
 * @date 2022/06/28 21:58
 **/
@WebServlet("/get-json-from-request-body")
public class GetJsonFromRequestBodyServlet extends HttpServlet {
    //支持的方法是POST方法,因为是POST请求提交的

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //1.请求体放在req.getInputStream()的输入流中
        InputStream inputStream = req.getInputStream();
        //2.利用Jackson进行解析(这个需要导依赖)
        ObjectMapper om=new ObjectMapper();
        //3.3
        List<Grade> list = om.readValue(inputStream, om.getTypeFactory().constructParametricType(List.class, Grade.class));
        for (Grade grade:list) {
            System.out.println(grade);
        }
        //结果:Grade{name='小赵', score=83}
        //Grade{name='小钱', score=84}
        //Grade{name='小李', score=85}

//        //3.2. 读一行
//        List list1 = om.readValue(inputStream, List.class);//数据里主要是List.class。至于外面的list会自己帮我解析,最终就会解析出来
                                                          //写的话操作是write,在GradeListJsonServlet类里有
//        //结果:[{姓名=小赵, 成绩=83}, {姓名=小钱, 成绩=84}, {姓名=小李, 成绩=85}]
//        System.out.println(list1);


//        //3.1.多个读
//        Object o = om.readValue(inputStream, om.getTypeFactory().constructParametricType(List.class, Grade.class));
//        System.out.println(o);
        //结果:[Grade{name='小赵', score=83}, Grade{name='小钱', score=84}, Grade{name='小李', score=85}]
    }
}
public class Grade {
    //注解意思是写到json里面是用姓名or成绩作为它的key
    @JsonProperty("姓名")
    public String name;
    @JsonProperty("成绩")
    public int score;

    @Override
    public String toString() {
        return "Grade{" +
                "name='" + name + '\'' +
                ", score=" + score +
                '}';
    }
}

五、eg:在浏览器中看到一个页面的简单架构

在前后端分离的场景下:

? ? ? xxx.html是用户看到的主页面,是入口。页面由多个资源组成,一般就是css做样式资源,js做逻辑资源,除此之外,在前后端分离场景下,数据往往是后端以xxx.json形式提供好的。

eg:其他代码小练习:

<!DOCTYPE html>
<html lang="zh-hans">
<head>
    <meta charset="UTF-8">
    <title>成绩列表</title>
</head>
<body>
    <div>
        <input type="text" id="name" name="name" placeholder="姓名">
        <input type="text" id="score" name="score" placeholder="成绩">
        <!--button的type是button时,代表不是提交form表单的功能,我们通过js添加点击事件处理自行控制流程
        (即框架中浏览器->服务器的操作方法2类似,通过js手动把数据序列化,通过POST传出去)-->
        <button type="button">保存</button><!--由于button本身是提交表单,现在不需要,默认type处是submit,
        现在改成button,默认就不提交了(失去了提交表单功能),type可写可不写【此时就需要js添加点击事件处理来处理它】-->
    </div>
    <table>
        <tbody>
        </tbody>
    </table>
    <!--前端要去后端去取一定需要一个js-->
    <script>
<!--        function render(students) {-->
<!--            var tbody = document.querySelector('tbody');-->
<!--            for (var i in students) {-->
<!--                var s = students[i];-->
<!--                var tr = `<tr><td>${s.姓名}</td><td>${s.成绩}</td></tr>`;-->

<!--                tbody.innerHTML = tbody.innerHTML + tr;-->
<!--            }-->
<!--        }-->

<!--        var xhr = new XMLHttpRequest();//构建一个ajax出来-->
<!--        xhr.open("GET", "/grade-list.json");//把这个目录下的数据给我-->
<!--         //前端只用读取json数据,读取出来后通过DOM树的修改操作把它加到tbody中-->
<!--        xhr.onload = function() {-->
<!--            // this.responseText 是 JSON 格式的字符串,是我们得到的响应体-->
<!--            // 进行 JSON 的反序列(进行JSON解码)-->
<!--            var students = JSON.parse(this.responseText);-->

<!--            // 进行渲染操作 (渲染:render)(进行DOM树的修改,即渲染操作)-->
<!--            render(students);-->
<!--        }-->
<!--        xhr.send();//发送-->

       //通过添加事件处理进行的处理:
       var btn=document.querySelector('button');//找到button
       btn.onclick=function(){
            var name=document.querySelector('#name').value;
            var score=document.querySelector('#score').value;
            var data={
                姓名:name,
                成绩:score
            };
            var s=JSON.stringify(data);
            var xhr2=new XMLHttpRequest();
            xhr2.open('post','/save.json');
            xhr2.onload=function(){
                //不考虑错的情况了,所以我们手动重定向【一旦返回代表保存成功,保存成功用一个重定向(这是前端重定向,直接把location一改就会自动重定向)】
                location='/';
            }
            xhr2.send(s);
       }

    </script>
</body>
</html>
@WebServlet("/save.json")
public class SaveServlet extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        ObjectMapper om=new ObjectMapper();
        Grade grade=om.readValue(req.getInputStream(),Grade.class);
        System.out.println(grade);
        //TODO:保存到数据库中
        try(Connection c= DBUtil.connection()){
            String sql="insert into grades(name,grade) values(?,?)";
            try(PreparedStatement ps=c.prepareStatement(sql)){
                ps.setString(1,grade.name);
                ps.setInt(2,grade.score);
                ps.executeUpdate();
            }
        }catch (SQLException e){
            throw new RuntimeException(e);
        }

        resp.setCharacterEncoding("utf-8");
        resp.setContentType("application/json");
        PrintWriter writer = resp.getWriter();
        HashMap<String,String> result=new HashMap<>();
        result.put("结果","正确");
        String s=om.writeValueAsString(result);
        writer.println(s);
    }
}

下一篇:文件上传场景+会话管理(Cookie/Session)_林纾???的博客-CSDN博客

  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2022-07-04 22:41:23  更:2022-07-04 22:43:07 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/23 15:42:30-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码