Java学习路线-47:Ajax学习XMLHttpRequest、XStream、json-lib

简介: Java学习路线-47:Ajax学习XMLHttpRequest、XStream、json-lib

课时1 1.ajax简介(异步与同步)

asynchronous javascript and xml

异步js和xml

1、异步交互和同步交互

同步 发送,等待 整个页面刷新

异步 发送,不等待 局部刷新

示例:异步刷新

<button id="btn">点击</button> 
<h2 id="text"></h2>
<script>
// 文档加载完成后马上执行
window.onload = function(){
    let btn = document.getElementById("btn");
    // 给btn注册点击事件监听
    btn.onclick = function(){
        let text = document.getElementById("text");
        text.innerHTML= "hello!";
    }
}
</script>

课时2 2.异步和同步交互图

数据格式

text、xml、json

同步:

请求 -> 
响应 <-
请求 -> 
响应 <-

异步:

请求 -> 
请求 -> 
响应 <-
响应 <-

课时3 3.ajax的应用场景和优缺点

优点:

异步交互,增强用户体验

性能:只需要响应部分内容,服务器压力减少

缺点:

ajax不能应用在所有场景

ajax增多了对服务器的请求,给服务器增加压力

课时4 4.ajax四步操作

1、获取XMLHttpRequest

// 大多数浏览器
var xmlHttp = new XMLHttpRequest();
// IE6.0
var xmlHttp = new ActiveXObject("Msxml2.XMLHTTP");
// IE<=5.5
var xmlHttp = new ActiveXObject("Microsoft.XMLHTTP");

编写一个创建XMLHttpRequest对象的函数

function createXMLHttpRequest(){
    try{
        // 大多数浏览器
        return new XMLHttpRequest();
    }catch(e){
        try{
            // IE6.0
            new ActiveXObject("Msxml2.XMLHTTP");
        }catch(e){
            try{
                // IE<=5.5
                new ActiveXObject("Microsoft.XMLHTTP");
            }catch(e){
                console.log("浏览器版本太老了!"); 
                throw e;
            }   
        }   
    }
}

2、连接服务器

xmlHttp.open("GET", "/url", true);
// 参数:请求方式,请求url,是否为异步

3、发送请求

xmlHttp.send(null);
// 参数:请求体内容,如果是GET,必须给出null,不然FireFox可能不发送

4、注册事件监听器

(1)5个状态:

0 刚创建
1 请求开始,调用open
2 请求发送完成 调用send
3 服务器开始响应
4 服务器响应结束

(2)获取响应内容

// 获取状态
var state = xmlHttp.readyState; 
// 得到服务器响应状态码 200, 404, 500
var status = xmlHttp.status;
// 得到服务器响应内容 
var content = xmlHttp.responseText; // 文本格式
var content = xmlHttp.responseXml; // xml格式,document对象

(3)注册监听事件

xmlHttp.onreadystatechange = function(){
    // 双重判断 xmlHttp状态为服务器响应结束,服务器状态响应结束
    if(xmlHttp.readyState == 4 && xmlHttp.status == 200){
        var text = xmlHttp.responseText;
    }
}

课时5 5.ajax第一例:helloworld

为了便于测试,服务端使用Python语言

服务端 hello.py

# pip install flask, flask-cors
from flask import Flask
from flask_cors import CORS
app = Flask(__name__)
CORS(app, supports_credentials=True)
@app.route("/")
def index():
    return "<h2>Hello!</h2>"
if __name__ == '__main__':
    app.run()

客户端 demo.html

<button id="btn">点击</button> 
<h2 id="text"></h2>
<script>
// 获取XMLHttpRequest对象
function createXMLHttpRequest(){
    try{
        // 大多数浏览器
        return new XMLHttpRequest();
    }catch(e){
        try{
            // IE6.0
            new ActiveXObject("Msxml2.XMLHTTP");
        }catch(e){
            try{
                // IE<=5.5
                new ActiveXObject("Microsoft.XMLHTTP");
            }catch(e){
                console.log("浏览器版本太老了!"); 
                throw e;
            }   
        }   
    }
}
// 文档加载完成后马上执行
window.onload = function(){
    let btn = document.getElementById("btn");
    // 给btn点击事件注册监听
    btn.onclick = function(){
        let xmlHttp = createXMLHttpRequest();
        xmlHttp.open("GET", "http://127.0.0.1:5000/", true);
        xmlHttp.send();
        xmlHttp.onreadystatechange = function(){
            // 双重判断 xmlHttp状态为服务器响应结束,服务器状态响应结束
            if(xmlHttp.readyState == 4 && xmlHttp.status == 200){
            var text = xmlHttp.responseText;
            let h2 = document.getElementById("text");
            h2.innerHTML= text;
        }
}
    }
}
</script>

6.ajax第二例:发送POST请求

多添加一个请求头

// 设置请求头
xmlHttp.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
// 发送请求体
xmlHttp.send("username=Tom&age=23");

服务端接收函数 hello.py

@app.route("/post", methods=['POST'])
def post():
    username = request.form.get("username")
    age = request.form.get("age")
    return f"<h2>username: {username}, age: {age}</h2>"

客户端修改 demo.html

xmlHttp.open("POST", "http://127.0.0.1:5000/post", true);
xmlHttp.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xmlHttp.send("username=Tom&age=23");

课时7 7.ajax第三例:用户名是否已被注册

客户端要求:

1、注册表单

2、监听用户名文本框失去焦点onblur事件

3、获取文本框内容,通过ajax异步发送给服务器

4、如果为1 显示:用户名已被注册

如果为0 什么都不显示

<meta charset="utf-8">
<style>
    #errorText {
        color: red;
    }
</style>
<form action="">
    <input type="text" name="username" id="username">
    <span id="errorText"></span>
</form>
<script>
    // 获取XMLHttpRequest对象
    function createXMLHttpRequest() {
        try {
            // 大多数浏览器
            return new XMLHttpRequest();
        } catch (e) {
            try {
                // IE6.0
                new ActiveXObject("Msxml2.XMLHTTP");
            } catch (e) {
                try {
                    // IE<=5.5
                    new ActiveXObject("Microsoft.XMLHTTP");
                } catch (e) {
                    console.log("浏览器版本太老了!");
                    throw e;
                }
            }
        }
    }
    // 文档加载完成后马上执行
    window.onload = function () {
        let username = document.getElementById("username");
        // 失去焦点注册事件监听
        username.onblur = function () {
            let xmlHttp = createXMLHttpRequest();
            xmlHttp.open("POST", "http://127.0.0.1:5000/validate", true);
            xmlHttp.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
            xmlHttp.send("username=" + username.value);
            xmlHttp.onreadystatechange = function () {
                // 双重判断 xmlHttp状态为服务器响应结束,服务器状态响应结束
                if (xmlHttp.readyState == 4 && xmlHttp.status == 200) {
                    var text = xmlHttp.responseText;
                    let errorText = document.getElementById("errorText");
                    if (text == '1') {
                        errorText.innerHTML = "用户名已被注册";
                    } else {
                        errorText.innerHTML = "";
                    }
                }
            }
        }
    }
</script>

服务端要求:

1、获取客户端传递的用户名参数

2、判断是否为demo,是返回1,否返回0

@app.route("/validate", methods=['POST'])
def validate():
    username = request.form.get("username")
    if username == "demo":
        return "1"
    else:
        return "0"

课时8 8.ajax第四例:响应内容为xml

服务端响应头

Content-Type: text/xml; charset=utf-8

客户端设置

var doc = xmlHttp.responseXML; // 得到Document对象

服务端代码

@app.route("/xml")
def xml():
    data = """
        <person>
            <name>Tom</name>
            <age>23</age>
        </person>
    """
    res = make_response(data)
    res.headers['Content-Type'] = 'text/xml; charset=utf-8'
    return res

客户端代码

<button id="btn">点击</button>
<h2 id="text"></h2>
<script>
    // 获取XMLHttpRequest对象
    function createXMLHttpRequest() {
        try {
            // 大多数浏览器
            return new XMLHttpRequest();
        } catch (e) {
            try {
                // IE6.0
                new ActiveXObject("Msxml2.XMLHTTP");
            } catch (e) {
                try {
                    // IE<=5.5
                    new ActiveXObject("Microsoft.XMLHTTP");
                } catch (e) {
                    console.log("浏览器版本太老了!");
                    throw e;
                }
            }
        }
    }
    // 判断是否为IE浏览器
    function isIE() {
        if (window.addEventListener) {
            return false;
        } else {
            return true;
        }
    }
    // 文档加载完成后马上执行
    window.onload = function () {
        let btn = document.getElementById("btn");
        // 注册事件监听
        btn.onclick = function () {
            let xmlHttp = createXMLHttpRequest();
            xmlHttp.open("GET", "http://127.0.0.1:5000/xml", true);
            xmlHttp.send(null);
            xmlHttp.onreadystatechange = function () {
                // 双重判断 xmlHttp状态为服务器响应结束,服务器状态响应结束
                if (xmlHttp.readyState == 4 && xmlHttp.status == 200) {
                    // 获取响应结果
                    var doc = xmlHttp.responseXML;
                    // IE 和非IE有所区别
                    let name = doc.getElementsByTagName("name")[0].textContent;
                    let age = doc.getElementsByTagName("age")[0].textContent;
                    let text = document.getElementById("text");
                    text.innerHTML = `name: ${name}, age: ${age}`;
                }
            }
        }
    }
</script>

课时9-10 ajax第五例:省市联动

<select name="province" id="">
    <option value="">请选择省份</option>
</select>
<select name="city" id="">
    <option value="">请选择城市</option>
</select>

服务端提供两个接口

province

city?province=北京

完整代码

一、前端代码

1、util.js

// 获取XMLHttpRequest对象
function createXMLHttpRequest() {
    try {
        // 大多数浏览器
        return new XMLHttpRequest();
    } catch (e) {
        try {
            // IE6.0
            new ActiveXObject("Msxml2.XMLHTTP");
        } catch (e) {
            try {
                // IE<=5.5
                new ActiveXObject("Microsoft.XMLHTTP");
            } catch (e) {
                console.log("浏览器版本太老了!");
                throw e;
            }
        }
    }
}
// 判断是否为IE浏览器
function isIE() {
    if (window.addEventListener) {
        return false;
    } else {
        return true;
    }
}

2、demo.html

<select name="province" id="province">
    <option value="">请选择省份</option>
</select>
<select name="city" id="city">
    <option value="">请选择城市</option>
</select>
<script src="./util.js"></script>
<script>
    function createOption(name) {
        // 创建option元素
        let option = document.createElement("option");
        option.value = name;
        // 创建文本节点
        let textNode = document.createTextNode(name);
        option.appendChild(textNode);
        return option;
    }
    // 文档加载完成后马上执行
    window.onload = function () {
        // 第一步:先获取省级列表
        let xmlHttp = createXMLHttpRequest();
        xmlHttp.open("GET", "http://127.0.0.1:5000/provinces", true);
        xmlHttp.send(null);
        let province = document.getElementById("province");
        xmlHttp.onreadystatechange = function () {
            // 双重判断 xmlHttp状态为服务器响应结束,服务器状态响应结束
            if (xmlHttp.readyState == 4 && xmlHttp.status == 200) {
                // 获取响应结果
                var text = xmlHttp.responseText;
                let list = text.split("|"); // 拆分数据得到数组
                for (let item of list) {
                    let option = createOption(item);
                    province.appendChild(option);
                }
            }
        }
        // 第二步:监听省级列表变动,获取城市列表
        province.onchange = function () {
            if (province.value == "") {
                return
            }
            let xmlHttp = createXMLHttpRequest();
            xmlHttp.open("POST", "http://127.0.0.1:5000/cities", true);
            xmlHttp.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
            xmlHttp.send(`province=${province.value}`);
            xmlHttp.onreadystatechange = function () {
                // 双重判断 xmlHttp状态为服务器响应结束,服务器状态响应结束
                if (xmlHttp.readyState == 4 && xmlHttp.status == 200) {
                    // 移除所有结果
                    let city = document.getElementById("city");
                    let optionList = city.getElementsByTagName("option");
                    while (optionList.length > 1) {
                        city.removeChild(optionList[1]);
                    }
                    // 获取响应结果
                    var doc = xmlHttp.responseXML;
                    let cities = doc.getElementsByTagName("city");
                    for (let item of cities) {
                        let cityName = "";
                        // 兼容IE浏览器和其他浏览器
                        if (isIE()) {
                            cityName = item.text; // IE
                        } else {
                            cityName = item.textContent; // FireFox等
                        }
                        let option = createOption(cityName);
                        city.appendChild(option);
                    }
                }
            }
        }
    }
</script>

二、后端代码

1、数据文件china.xml

<china>
    <province name="北京">
        <city>东城区</city>    
        <city>西城区</city>
    </province>    
    <province name="天津">
        <city>和平区</city>    
        <city>河东区</city>
    </province>  
</china>

2、数据解析文件demo.py

# pip install lxml
from lxml import etree
class China():
    path = "china.xml"
    @classmethod
    def getProvinces(cls):
        """获取省份
        """
        tree = etree.parse(cls.path)
        return tree.xpath('//province/@name')
    @classmethod
    def getCities(cls, province):
        """获取城市
        """
        tree = etree.parse(cls.path)
        result = tree.xpath(f"//province[@name='{province}']")
        if result:
            return etree.tostring(result[0], encoding="UTF-8")
        else:
            return ""
if __name__ == "__main__":
    print(China.getProvinces())
    print(China.getCities("北京"))

3、接口文件

from flask import Flask, request, make_response
from flask_cors import CORS
from demo import China
app = Flask(__name__)
CORS(app, supports_credentials=True)
@app.route("/provinces")
def provinces():
    return "|".join(China.getProvinces())
@app.route("/cities", methods=['POST'])
def cities():
    province = request.form.get("province")
    res = make_response(China.getCities(province))
    res.headers['Content-Type'] = 'text/xml; charset=utf-8'
    return res
if __name__ == '__main__':
    app.run(debug=True)

课时11 11.XStream(可把Javabean转换成XMl的小工具)

依赖

<dependency>
    <groupId>xstream</groupId>
    <artifactId>xstream</artifactId>
    <version>1.2.2</version>
</dependency>

代码实例

import com.thoughtworks.xstream.XStream;
import java.util.ArrayList;
import java.util.List;
class City {
    private String name;
    public City(String name) {
        this.name = name;
    }
}
class Province {
    private String name;
    private List<City> cities = new ArrayList<>();
    public void addCity(City city){
        cities.add(city);
    }
    public Province(String name) {
        this.name = name;
    }
}
public class TestXStream {
    public static void main(String[] args) {
        // 数据准备
        List<Province> list = new ArrayList<Province>();
        Province province = new Province("北京");
        province.addCity(new City("东城区"));
        province.addCity(new City("昌平区"));
        list.add(province);
        XStream xStream = new XStream();
        // 指定别名
        xStream.alias("china", List.class);
        xStream.alias("province", Province.class);
        xStream.alias("city", City.class);
        // 属性设置
        xStream.useAttributeFor(Province.class, "name");
        // 去除无用的标签
        xStream.addImplicitCollection(Province.class, "cities");
        String str = xStream.toXML(list);
        System.out.println(str);
    }
}

课时12 12.JSON的概述

js提供的一种数据交换格式

Json语法

属性名必须使用双引号括起来

对象:{}
属性:
    null、数值、字符串、数组[]、boolean(true/false)
var s = "1 + 2";
eval(s); 
// 3

1、示例

(1)服务端代码

from flask import Flask, jsonify
from flask_cors import CORS
app = Flask(__name__)
CORS(app, supports_credentials=True)
@app.route("/json")
def json():
    return jsonify({"name": "Tom"})
if __name__ == '__main__':
    app.run(debug=True)

(2)客户端代码

<button id="btn">点击</button>
<h2 id="text"></h2>
<script src="./util.js"></script>
<script>
    // 文档加载完成后马上执行
    window.onload = function () {
        let btn = document.getElementById("btn");
        btn.onclick = function () {
            let xmlHttp = createXMLHttpRequest();
            xmlHttp.open("GET", "http://127.0.0.1:5000/json", true);
            xmlHttp.send(null);
            xmlHttp.onreadystatechange = function () {
                // 双重判断 xmlHttp状态为服务器响应结束,服务器状态响应结束
                if (xmlHttp.readyState == 4 && xmlHttp.status == 200) {
                    // 获取响应结果
                    let text = xmlHttp.responseText;
                    let obj = JSON.parse(text);
                    document.getElementById("text").innerHTML = `name: ${obj.name}`;
                }
            }
        }
    }
</script>

json与xml比较

可读性

解码难度

流行度

课时13 13.json-lib的应用

继承关系

public final class JSONArray extends AbstractJSON 
    implements JSON, List, Comparable
public final class JSONObject extends AbstractJSON 
    implements JSON, Map, Comparable

依赖

<dependency>
    <groupId>net.sf.json-lib</groupId>
    <artifactId>json-lib</artifactId>
    <version>2.4</version>
    <classifier>jdk15</classifier>
</dependency>

示例

设置标签

import net.sf.json.JSONObject;
class Demo {
    public static void main(String[] args)   {
        JSONObject map = new JSONObject();
        map.put("name", "Tom");
        map.put("age", 23);
        String str = map.toString();
        System.out.println(str);
        // {"name":"Tom","age":23}
    }
}

java对象转为json

Person.java

public class Person {
    private String name;
    private int age;
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
}

Demo.java

import net.sf.json.JSONArray;
import net.sf.json.JSONObject;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
class Demo {
    public static void main(String[] args) {
        // 对象转JSONObject
        Person person = new Person("Tom", 23);
        JSONObject obj = JSONObject.fromObject(person);
        System.out.println(obj.toString());
        // {"name":"Tom","age":23}
        // List转 JSONArray
        List<Person> list = new ArrayList<Person>();
        list.add(new Person("Tom", 23));
        list.add(new Person("Jack", 23));
        JSONArray array = JSONArray.fromObject(list);
        System.out.println(array.toString());
        // [{"age":23,"name":"Tom"},{"age":23,"name":"Jack"}]
        // map转JSONObject
        Map<String ,String> map = new HashMap<String ,String>();
        map.put("name", "Tom");
        map.put("sex", "male");
        System.out.println(JSONObject.fromObject(map).toString());
        // {"sex":"male","name":"Tom"}
    }
}

课时14 14.打包ajax生成小工具

参数

option{
    method
    url
    asyn
    type
    callback
    params
    data
}

xml

text

json

后端接口

from flask import Flask, request, jsonify
from flask_cors import CORS
app = Flask(__name__)
CORS(app, supports_credentials=True)
@app.route("/json", methods=["GET", "POST"])
def json():
    username = request.args.get("username")
    if request.method == "POST":
        username = request.form.get("username")
        if request.is_json:
            username = request.json.get("username")
    return jsonify({"name": username})
if __name__ == '__main__':
    app.run(debug=True)

封装的工具 ajax-util.js

// 获取XMLHttpRequest对象
function createXMLHttpRequest() {
    try {
        // 大多数浏览器
        return new XMLHttpRequest();
    } catch (e) {
        try {
            // IE6.0
            new ActiveXObject("Msxml2.XMLHTTP");
        } catch (e) {
            try {
                // IE<=5.5
                new ActiveXObject("Microsoft.XMLHTTP");
            } catch (e) {
                console.log("浏览器版本太老了!");
                throw e;
            }
        }
    }
}
// 判断是否为IE浏览器
function isIE() {
    if (window.addEventListener) {
        return false;
    } else {
        return true;
    }
}
/**
 * 将对象转为url查询参数
 * @param {*} data 
 * { "name": "Tom", "age": 23 }
 * -> name=Tom&age=23
 */
function encodeData(data) {
    if (!data) {
        return null;
    }
    let list = [];
    for (let [key, value] of Object.entries(data)) {
        list.push(`${key}=${value}`);
    }
    return list.join("&");
}
const CONTENT_TYPE = "Content-Type";
const contentTypeMap = {
    html: "text/html; charset=utf-8",
    xml: "text/xml; charset=utf-8",
    json: "application/json; charset=utf-8",
    form: "application/x-www-form-urlencoded"
}
/**
 * 
 * @param {*} option:
 *   method
 *   url
 *   asyn
 *   type
 *   callback
 *   params
 *   data 
 */
function ajax(option) {
    // 必传参数
    let url = option.url;
    let callback = option.callback;
    // 可选参数
    let method = option.method || "GET";
    let asyn = option.asyn || true;
    let params = option.params || {};
    let type = option.type || "html";
    let data = option.data || {};
    let xmlHttp = createXMLHttpRequest();
    // 处理响应数据
    xmlHttp.onreadystatechange = function () {
        // 双重判断 xmlHttp状态为服务器响应结束,服务器状态响应结束
        if (xmlHttp.readyState == 4 && xmlHttp.status == 200) {
            // 获取响应结果
            let responseData = null;
            if (xmlHttp.responseXML) {
                responseData = xmlHttp.responseXML;
            }
            else {
                responseData = xmlHttp.responseText;
                try {
                    responseData = JSON.parse(responseData);
                } catch (e) {
                }
            }
            callback(responseData);
        }
    }
    // 处理请求数据
    if (params) {
        url = url + "?" + encodeData(params);
    }
    xmlHttp.open(method, url, asyn);
    xmlHttp.setRequestHeader(CONTENT_TYPE, contentTypeMap[type]);
    let sendData = null;
    if (type == "json") {
        sendData = JSON.stringify(data);
    } else {
        sendData = encodeData(data);
    }
    xmlHttp.send(sendData);
}
// console.log(encodeData(undefined));

测试代码

<button id="get-btn">GET</button>
<h2 id="get-text"></h2>
<button id="post-btn">POST</button>
<form action="">
    <input type="text" name="username" id="username">
</form>
<h2 id="post-text"></h2>
<script src="./ajax-util.js"></script>
<script>
    // 文档加载完成后马上执行
    window.onload = function () {
        // get方法
        let getBtn = document.getElementById("get-btn");
        getBtn.onclick = function () {
            ajax({
                url: "http://127.0.0.1:5000/json",
                method: "GET",
                params: {
                    "username": "Tom"
                },
                callback: function (data) {
                    console.log(data);
                    document.getElementById("get-text").innerHTML = data.name;
                }
            })
        };
        // post方法
        let postBtn = document.getElementById("post-btn");
        postBtn.onclick = function () {
            ajax({
                url: "http://127.0.0.1:5000/json",
                method: "POST",
                type: "json",
                params: {
                    "username": "Tom"
                },
                data: {
                    "username": document.getElementById("username").value
                },
                callback: function (data) {
                    console.log(data);
                    document.getElementById("post-text").innerHTML = data.name;
                }
            })
        }
    }
</script>
相关文章
|
1天前
|
Java Nacos 开发者
Java从入门到精通:4.2.1学习新技术与框架——以Spring Boot和Spring Cloud Alibaba为例
Java从入门到精通:4.2.1学习新技术与框架——以Spring Boot和Spring Cloud Alibaba为例
|
1天前
|
Dubbo Java 应用服务中间件
Java从入门到精通:3.2.2分布式与并发编程——了解分布式系统的基本概念,学习使用Dubbo、Spring Cloud等分布式框架
Java从入门到精通:3.2.2分布式与并发编程——了解分布式系统的基本概念,学习使用Dubbo、Spring Cloud等分布式框架
|
1天前
|
SQL Java 数据库连接
Java从入门到精通:2.3.1数据库编程——学习JDBC技术,掌握Java与数据库的交互
ava从入门到精通:2.3.1数据库编程——学习JDBC技术,掌握Java与数据库的交互
|
1天前
|
设计模式 存储 前端开发
Java从入门到精通:2.2.1学习Java Web开发,了解Servlet和JSP技术,掌握MVC设计模式
Java从入门到精通:2.2.1学习Java Web开发,了解Servlet和JSP技术,掌握MVC设计模式
|
1天前
|
Java API
Java从入门到精通:2.1.5深入学习Java核心技术之文件操作
Java从入门到精通:2.1.5深入学习Java核心技术之文件操作
|
1天前
|
并行计算 算法 安全
Java从入门到精通:2.1.3深入学习Java核心技术——掌握Java多线程编程
Java从入门到精通:2.1.3深入学习Java核心技术——掌握Java多线程编程
|
5天前
|
JavaScript Java 测试技术
基于Java的驾考自主学习预约平台的设计与实现(源码+lw+部署文档+讲解等)
基于Java的驾考自主学习预约平台的设计与实现(源码+lw+部署文档+讲解等)
18 0
|
6天前
|
JavaScript Java 测试技术
基于Java的精品课程在线学习系统的设计与实现(源码+lw+部署文档+讲解等)
基于Java的精品课程在线学习系统的设计与实现(源码+lw+部署文档+讲解等)
25 1
|
6天前
|
JavaScript Java 测试技术
基于Java的中文学习系统的设计与实现(源码+lw+部署文档+讲解等)
基于Java的中文学习系统的设计与实现(源码+lw+部署文档+讲解等)
22 0
|
7天前
|
XML JSON 前端开发