Java阶段二Day05

news2024/12/26 23:31:24

Java阶段二Day05

文章目录

  • Java阶段二Day05
    • 截至此版本可实现的流程图为
    • V14
      • UserController
      • ClientHandler
      • DispatcherServlet
      • HttpServletResponse
      • HttpServletRequest
    • V15
      • DispatcherServlet
      • HttpServletResponse
      • HttpServletRequest
    • V16
      • HttpServletRequest
      • HttpServletResponse
    • 反射
      • JAVA反射机制
      • 获取一个类的类对象方式
      • 反射对象

截至此版本可实现的流程图为

在这里插入图片描述

V14

独立完成登录模块

步骤:
1:创建登录相关的页面
1.1:login.html页面,表单要求两个输入框分别为用户名和密码,表单action=“/loginUser”
1.2:login_info_error页面,当用户登录信息输入有误(空着不填等)时提示该页面,页面居中显示一行字:登录信息输入有误,请重新登录
1.3:login_success.html 登录成功提示页面
1.4:login_fail.html 登录失败提示页面

2:在UserController中定义用于处理登录业务的方法login方法定义参考reg方法
3:在DispatcherServlet的判断注册业务下面添加一个else if分支,判断请求路径path的值是否为"/loginUser" 从而调用登录方法处理登录业务

UserController

package com.birdboot.controller;

import com.birdboot.entity.User;
import com.birdboot.http.HttpServletRequest;
import com.birdboot.http.HttpServletResponse;

import java.io.*;

/**
 * 处理与用户相关的业务
 */
public class UserController {
    private static File userDir;//表示存放所有用户信息的目录:users
    static {
        userDir = new File("./users");
        if(!userDir.exists()){
            userDir.mkdirs();
        }
    }

    //处理"/regUser"这个请求
    public void reg(HttpServletRequest request, HttpServletResponse response){
        System.out.println("开始处理用户注册");
        String username = request.getParameter("username");
        String password = request.getParameter("password");
        String nickname = request.getParameter("nickname");
        String ageStr = request.getParameter("age");
        System.out.println(username+","+password+","+nickname+","+ageStr);
        //必要的验证
        if(username==null||username.isEmpty()||
                password==null||password.isEmpty()||
                nickname==null||nickname.isEmpty()||
                ageStr==null||ageStr.isEmpty()||
                !ageStr.matches("[0-9]+")
        ){
            response.sendRedirect("/reg_info_error.html");
            return;
        }

        int age = Integer.parseInt(ageStr);//将年龄转换为int值
        //2
        User user = new User(username,password,nickname,age);
        /*
            File的构造器
            File(File parent,String child)
            创建一个File对象表示child这个子项,而它是在parent这个File对象所表示的目录中
         */
        File file = new File(userDir,username+".obj");
        if(file.exists()){//注册前发现以该用户名命名的obj文件已经存在,说明是重复用户
            response.sendRedirect("/have_user.html");
            return;
        }

        try (
                FileOutputStream fos = new FileOutputStream(file);
                ObjectOutputStream oos = new ObjectOutputStream(fos);
        ){
            oos.writeObject(user);//保存用户信息完毕

            //3给用户回馈注册成功页面
            //要求浏览器重新访问下述地址对应的页面
            response.sendRedirect("/reg_success.html");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    public void login(HttpServletRequest request,HttpServletResponse response){
        System.out.println("开始处理登录!!!!");
        //1获取登录信息
        String username = request.getParameter("username");
        String password = request.getParameter("password");
        //必要验证
        if(username==null||username.isEmpty()||password==null||password.isEmpty()){
            response.sendRedirect("/login_info_error.html");
            return;
        }

        //根据该登录用户的名字去定位users下该用户的注册信息
        File file = new File(userDir,username+".obj");
        //判断该文件是否存在,不存在则说明该用户没有注册过
        if(file.exists()){
            //将该用户曾经的注册信息读取出来用于比较密码
            try (
                    FileInputStream fis = new FileInputStream(file);
                    ObjectInputStream ois = new ObjectInputStream(fis);
            ){
                User user = (User)ois.readObject();//读取注册信息
                //比较本次登录的密码是否与注册时该用户输入的密码一致
                if(user.getPassword().equals(password)){
                    //密码一致则登录成功
                    response.sendRedirect("/login_success.html");
                    return;
                }
            } catch (IOException | ClassNotFoundException e) {
                e.printStackTrace();
            }
        }

        //如果程序可以执行到这里,则说明要么是用户名没输入对,要么是密码没有输入对.都属于登录失败
        response.sendRedirect("/login_fail.html");

    }
}

ClientHandler

package com.birdboot.core;

import com.birdboot.http.EmptyRequestException;
import com.birdboot.http.HttpServletRequest;
import com.birdboot.http.HttpServletResponse;

import java.io.*;
import java.net.Socket;
import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;

/**
 * 该线程任务负责与指定的客户端进行HTTP交互
 * HTTP协议要求浏览器与服务端采取"一问一答"的模式。对此,这里的处理流程分为三步:
 * 1:解析请求
 * 2:处理请求
 * 3:发送响应
 */
public class ClientHandler implements Runnable {
    private Socket socket;

    public ClientHandler(Socket socket) {
        this.socket = socket;
    }
    public void run() {
        try {
            //1 解析请求
            HttpServletRequest request = new HttpServletRequest(socket);
            HttpServletResponse response = new HttpServletResponse(socket);

            //2 处理请求
            //V8改造:将处理请求的操作移动到DispatcherServlet的service方法中并调用
            DispatcherServlet servlet = new DispatcherServlet();
            servlet.service(request,response);

            //3 发送响应
            response.response();

        } catch (IOException e) {
            e.printStackTrace();
        } catch (EmptyRequestException e) {

        } finally {
            //HTTP协议要求浏览器与服务端交互完毕后要断开连接
            try {
                socket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

    }

}

DispatcherServlet

package com.birdboot.core;

import com.birdboot.controller.UserController;
import com.birdboot.http.HttpServletRequest;
import com.birdboot.http.HttpServletResponse;

import java.io.File;
import java.net.URISyntaxException;

/**
 * V8新增内容:
 * 该类是SpringMVC框架与Tomcat整合时的一个关键类
 * Tomcat处理业务原生的都是调用继承了HttpServlet的类来完成,此时需要进行很多配置
 * 以及使用时要作很多重复性劳动。
 * SpringMVC框架提供的该类也是继承了HttpServlet的,使用它来接收处理请求的工作。
 */
public class DispatcherServlet {
    private static File baseDir;//类加载路径
    private static File staticDir;//类加载路径下的static目录

    static{
        try {
            //定位当前项目的类加载路径
            baseDir = new File(
                    DispatcherServlet.class.getClassLoader().getResource(".").toURI()
            );
            //定位类加载路径下的static目录
            staticDir = new File(baseDir, "static");
        } catch (URISyntaxException e) {
            e.printStackTrace();
        }
    }

    /**
     * service方法实际上是当我们继承了HttpServlet后必须重写的方法
     * 该方法要求接收两个参数:请求对象与响应对象。
     * Tomcat在处理请求时就是调用某个Servlet的service方法并将请求与响应对象传入
     * 来让其完成处理工作的。
     */
    public void service(HttpServletRequest request, HttpServletResponse response){
        //获取请求的抽象路径
        //不能在使用uri判断请求了,因为uri可能含参数,内容不固定。
        String path = request.getRequestURI();
        System.out.println(path);

        //判断该请求是否为请求一个业务
        if("/regUser".equals(path)){
            UserController controller = new UserController();
            controller.reg(request, response);
        }else if("/loginUser".equals(path)){
            UserController controller = new UserController();
            controller.login(request, response);
        }else {
            File file = new File(staticDir, path);
            if (file.isFile()) {
                //由于响应对象中状态代码和描述默认值为200,OK因此正确情况下不用再设置
                response.setContentFile(file);
                //设置响应头
                response.addHeader("Server", "BirdServer");
            } else {
                response.setStatusCode(404);
                response.setStatusReason("NotFound");
                file = new File(staticDir, "404.html");
                response.setContentFile(file);
                response.addHeader("Server", "BirdServer");
            }
        }
    }

}

HttpServletResponse

package com.birdboot.http;

import javax.activation.MimetypesFileTypeMap;
import java.io.*;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

/**
 * V7新增内容:
 * 响应对象
 * 该类的每一个实例用于表示服务端给客户端发送的一个HTTP的响应
 * HTTP协议要求一个响应由三部分构成:状态行,响应头,响应正文
 */
public class HttpServletResponse {

    private static MimetypesFileTypeMap mftm = new MimetypesFileTypeMap();

    private Socket socket;

    //状态行相关信息
    private int statusCode = 200;//状态代码
    private String statusReason = "OK";//状态描述

    //响应头相关信息 key:响应头的名字  value:响应头的值
    private Map<String,String> headers = new HashMap<>();

    //响应正文相关信息
    private File contentFile;//响应正文对应的实体文件


    public HttpServletResponse(Socket socket){
        this.socket = socket;
    }

    /**
     * 该方法用于将当前响应对象内容以标准的HTTP响应格式发送给客户端
     */
    public void response() throws IOException {
        //3.1发送状态行
        sendStatusLine();
        //3.2发送响应头
        sendHeaders();
        //3.3发送响应正文
        sendContent();
    }

    //发送状态行
    private void sendStatusLine() throws IOException {
        println("HTTP/1.1"+" "+statusCode+" "+statusReason);
    }
    //发送响应头
    private void sendHeaders() throws IOException {
      
        Set<Map.Entry<String,String>> entrySet = headers.entrySet();
        for(Map.Entry<String,String> e : entrySet){
            String name = e.getKey();
            String value = e.getValue();
            println(name+": "+value);
        }

        //单独发送回车+换行,表示响应头发送完毕
        println("");
    }
    //发送响应正文
    private void sendContent() throws IOException {
        if(contentFile!=null) {
            FileInputStream fis = new FileInputStream(contentFile);
            OutputStream out = socket.getOutputStream();
            byte[] buf = new byte[1024 * 10];//10kb
            int d;//记录每次实际读取的数据量
            while ((d = fis.read(buf)) != -1) {
                out.write(buf, 0, d);
            }
        }
    }

    /**
     * V7:将ClientHandler中发送响应的工作全部移动到这里,println方法也是。
     * 向客户端发送一行字符串
     * @param line
     */
    private void println(String line) throws IOException {
        OutputStream out = socket.getOutputStream();
        byte[] data = line.getBytes(StandardCharsets.ISO_8859_1);
        out.write(data);
        out.write(13);//发送回车符
        out.write(10);//发送换行符
    }

    public int getStatusCode() {
        return statusCode;
    }

    public void setStatusCode(int statusCode) {
        this.statusCode = statusCode;
    }

    public String getStatusReason() {
        return statusReason;
    }

    public void setStatusReason(String statusReason) {
        this.statusReason = statusReason;
    }

    public File getContentFile() {
        return contentFile;
    }

    /**
     * 设置响应正文对应的实体文件,该方法中会自动根据该文件添加对应的两个响应头:
     * Content-Type和Content-Length
     * @param contentFile
     */
    public void setContentFile(File contentFile) {
        this.contentFile = contentFile;


        addHeader("Content-Type",mftm.getContentType(contentFile));
        addHeader("Content-Length",contentFile.length()+"");
    }

    /**
     * 添加一个响应头
     * @param name
     * @param value
     */
    public void addHeader(String name,String value){
        headers.put(name,value);
    }

    /**
     * 要求浏览器重定向到指定位置
     * @param location
     */
    public void sendRedirect(String location){
        //1设置状态代码302
        statusCode = 302;
        statusReason = "Moved Temporarily";
        //2添加响应头Location
        addHeader("Location",location);
    }
}

HttpServletRequest

package com.birdboot.http;

import java.io.IOException;
import java.io.InputStream;
import java.net.Socket;
import java.util.HashMap;
import java.util.Map;

/**
 * V4:新增内容
 * 请求对象
 * 该类的每一个实例用于表示浏览器发送过来的一个HTTP请求
 * HTTP协议要求请求的格式由三部分构成:请求行,消息头,消息正文
 */
public class HttpServletRequest {
    private Socket socket;
    //请求行相关信息
    private String method;//请求方式
    private String uri;//抽象路径
    private String protocol;//协议版本
    private String requestURI;//保存uri中"?"左侧的请求路径部分
    private String queryString;//保存uri中"?"右侧的参数部分
    private Map<String,String> parameters = new HashMap<>();//保存每一组参数

    //消息头相关信息 key:消息头名字  value:消息头对应的值
    private Map<String,String> headers = new HashMap<>();

    public HttpServletRequest(Socket socket) throws IOException, EmptyRequestException {
        this.socket = socket;
        //1.1解析请求行
        parseRequestLine();
        //1.2解析消息头
        parseHeaders();
        //1.3解析消息正文
        parseContent();
    }
    //解析请求行
    private void parseRequestLine() throws IOException, EmptyRequestException {
        String line = readLine();

        if(line.isEmpty()){//如果请求行是个空字符串,则说明本次为空请求
            throw new EmptyRequestException();
        }

        System.out.println("请求行:"+line);

        //将请求行按照空格("\s"在正则表达式中表示一个空白字符,包含空格)拆分为三部分
        String[] data = line.split("\\s");
        method = data[0];
        uri = data[1];
        protocol = data[2];

        parseURI();//进一步解析uri

        System.out.println("method:"+method);
        System.out.println("uri:"+uri);
        System.out.println("protocol:"+protocol);
    }
    //进一步解析uri
    private void parseURI(){
  
        String[] data = uri.split("\\?");
        requestURI = data[0];
        if(data.length>1){
            queryString = data[1];
           
            String[] paraArr = queryString.split("&");
            /*
                再遍历paraArr进一步拆分每一组参数的参数名和参数值
                每一组参数按照"="进行拆分
             */
            //para:username=fancq
            for(String para : paraArr){
                /*
                    username=fancq   用户在浏览器该输入框输入信息
                    username=        用户在浏览器该输入框没有输入信息
                 */
                String[] arr = para.split("=",2);
                parameters.put(arr[0],arr[1]);
            }
        }
        System.out.println("requestURI:"+requestURI);
        System.out.println("queryString:"+queryString);
        System.out.println("parameters:"+parameters);

    }

    //解析消息头
    private void parseHeaders() throws IOException {
        while(true) {
            String line = readLine();
            if(line.isEmpty()){//如果读取到了空行
                break;
            }
            System.out.println("消息头:" + line);
            String[] data = line.split(":\\s");
            headers.put(data[0],data[1]);
        }
        System.out.println("headers:"+headers);
    }
    //解析消息正文
    private void parseContent(){}

    /**
     * 通过socket获取的输入流读取客户端发送过来的一行字符串
     * @return
     */
    private String readLine() throws IOException {//通常被重用的代码不自己处理异常
        //对一个socket实例调用多次getInputStream()返回的始终是同一条输入流。而输出流也是如此
        InputStream in = socket.getInputStream();
        int d;
        char pre='a',cur='a';//pre表示上次读取的字符,cur表示本次读取的字符
        StringBuilder builder = new StringBuilder();//保存读取后的所有字符
        while((d = in.read())!=-1){
            cur = (char)d;//本次读取的字符
            if(pre==13 && cur==10){//是否连续读取到了回车+换行
                break;
            }
            builder.append(cur);//将本次读取的字符拼接
            pre=cur;//在进行下次读取前,将本次读取的字符保存到"上次读取的字符"中
        }
        return builder.toString().trim();
    }

    public String getMethod() {
        return method;
    }

    public String getUri() {
        return uri;
    }

    public String getProtocol() {
        return protocol;
    }

    /**
     * 根据给定的消息头的名字获取对应消息头的值
     * @param name
     * @return
     */
    public String getHeader(String name) {
        return headers.get(name);
    }

    public String getRequestURI() {
        return requestURI;
    }

    public String getQueryString() {
        return queryString;
    }

    public String getParameter(String name) {
        return parameters.get(name);
    }
}

V15

支持POST请求
当页面form表单中包含用户隐私信息或有附件上传时,应当使用POST形式提交。
POST会将表单数据包含在请求的消息正文中。
如果表单中没有附件,则正文中包含的表单数据就是一个字符串,而格式就是原GET形式提交时抽象路径中"?"右侧的内容。

实现:
1:完成HttpServletRequest中的解析消息正文的方法,当页面(reg.html或login.html)中form的提交方式改为POST时,表单数据被包含在正文里,并且请求的消息头中会出现Content-TypeContent-Length用于告知服务端正文内容。因此我们可以根据它们来解析正文。
2:将解析参数的操作从parseURI中单独提取出来定义在parseParameter()方法中重用。parseURI和解析正文方法parseContent都可以调用parseParameter()来重用拆分操作。

DispatcherServlet

package com.birdboot.core;

import com.birdboot.controller.UserController;
import com.birdboot.http.HttpServletRequest;
import com.birdboot.http.HttpServletResponse;

import java.io.File;
import java.net.URISyntaxException;

/**
 * V8新增内容:
 * 该类是SpringMVC框架与Tomcat整合时的一个关键类
 * Tomcat处理业务原生的都是调用继承了HttpServlet的类来完成,此时需要进行很多配置
 * 以及使用时要作很多重复性劳动。
 * SpringMVC框架提供的该类也是继承了HttpServlet的,使用它来接收处理请求的工作。
 */
public class DispatcherServlet {
    private static File baseDir;//类加载路径
    private static File staticDir;//类加载路径下的static目录

    static{
        try {
            //定位当前项目的类加载路径
            baseDir = new File(
                    DispatcherServlet.class.getClassLoader().getResource(".").toURI()
            );
            //定位类加载路径下的static目录
            staticDir = new File(baseDir, "static");
        } catch (URISyntaxException e) {
            e.printStackTrace();
        }
    }

    /**
     * service方法实际上是当我们继承了HttpServlet后必须重写的方法
     * 该方法要求接收两个参数:请求对象与响应对象。
     * Tomcat在处理请求时就是调用某个Servlet的service方法并将请求与响应对象传入
     * 来让其完成处理工作的。
     */
    public void service(HttpServletRequest request, HttpServletResponse response){
        //获取请求的抽象路径
        //不能在使用uri判断请求了,因为uri可能含参数,内容不固定。
        String path = request.getRequestURI();
        System.out.println(path);

        //判断该请求是否为请求一个业务
        if("/regUser".equals(path)){
            UserController controller = new UserController();
            controller.reg(request, response);
        }else if("/loginUser".equals(path)){
            UserController controller = new UserController();
            controller.login(request, response);
        }else {
            File file = new File(staticDir, path);
            if (file.isFile()) {
                //由于响应对象中状态代码和描述默认值为200,OK因此正确情况下不用再设置
                response.setContentFile(file);
                //设置响应头
                response.addHeader("Server", "BirdServer");
            } else {
                response.setStatusCode(404);
                response.setStatusReason("NotFound");
                file = new File(staticDir, "404.html");
                response.setContentFile(file);
                response.addHeader("Server", "BirdServer");
            }
        }
    }
}

HttpServletResponse

package com.birdboot.http;

import javax.activation.MimetypesFileTypeMap;
import java.io.*;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

/**
 * V7新增内容:
 * 响应对象
 * 该类的每一个实例用于表示服务端给客户端发送的一个HTTP的响应
 * HTTP协议要求一个响应由三部分构成:状态行,响应头,响应正文
 */
public class HttpServletResponse {

    private static MimetypesFileTypeMap mftm = new MimetypesFileTypeMap();

    private Socket socket;

    //状态行相关信息
    private int statusCode = 200;//状态代码
    private String statusReason = "OK";//状态描述

    //响应头相关信息 key:响应头的名字  value:响应头的值
    private Map<String,String> headers = new HashMap<>();

    //响应正文相关信息
    private File contentFile;//响应正文对应的实体文件


    public HttpServletResponse(Socket socket){
        this.socket = socket;
    }

    /**
     * 该方法用于将当前响应对象内容以标准的HTTP响应格式发送给客户端
     */
    public void response() throws IOException {
        //3.1发送状态行
        sendStatusLine();
        //3.2发送响应头
        sendHeaders();
        //3.3发送响应正文
        sendContent();
    }

    //发送状态行
    private void sendStatusLine() throws IOException {
        println("HTTP/1.1"+" "+statusCode+" "+statusReason);
    }
    //发送响应头
    private void sendHeaders() throws IOException {
        /*
            遍历headers将所有待发送的响应头发送给浏览器
            headers
            key                 value
            Content-Type        text/html
            Content-Length      42123
            Server              BirdServer
            ...                 ...
         */
        Set<Map.Entry<String,String>> entrySet = headers.entrySet();
        for(Map.Entry<String,String> e : entrySet){
            String name = e.getKey();
            String value = e.getValue();
            println(name+": "+value);
        }

        //单独发送回车+换行,表示响应头发送完毕
        println("");
    }
    //发送响应正文
    private void sendContent() throws IOException {
        if(contentFile!=null) {
            FileInputStream fis = new FileInputStream(contentFile);
            OutputStream out = socket.getOutputStream();
            byte[] buf = new byte[1024 * 10];//10kb
            int d;//记录每次实际读取的数据量
            while ((d = fis.read(buf)) != -1) {
                out.write(buf, 0, d);
            }
        }
    }

    /**
     * V7:将ClientHandler中发送响应的工作全部移动到这里,println方法也是。
     * 向客户端发送一行字符串
     * @param line
     */
    private void println(String line) throws IOException {
        OutputStream out = socket.getOutputStream();
        byte[] data = line.getBytes(StandardCharsets.ISO_8859_1);
        out.write(data);
        out.write(13);//发送回车符
        out.write(10);//发送换行符
    }

    public int getStatusCode() {
        return statusCode;
    }

    public void setStatusCode(int statusCode) {
        this.statusCode = statusCode;
    }

    public String getStatusReason() {
        return statusReason;
    }

    public void setStatusReason(String statusReason) {
        this.statusReason = statusReason;
    }

    public File getContentFile() {
        return contentFile;
    }

    /**
     * 设置响应正文对应的实体文件,该方法中会自动根据该文件添加对应的两个响应头:
     * Content-Type和Content-Length
     * @param contentFile
     */
    public void setContentFile(File contentFile) {
        this.contentFile = contentFile;


        addHeader("Content-Type",mftm.getContentType(contentFile));
        addHeader("Content-Length",contentFile.length()+"");
    }

    /**
     * 添加一个响应头
     * @param name
     * @param value
     */
    public void addHeader(String name,String value){
        headers.put(name,value);
    }

    /**
     * 要求浏览器重定向到指定位置
     * @param location
     */
    public void sendRedirect(String location){
        //1设置状态代码302
        statusCode = 302;
        statusReason = "Moved Temporarily";
        //2添加响应头Location
        addHeader("Location",location);
    }
}

HttpServletRequest

package com.birdboot.http;

import java.io.IOException;
import java.io.InputStream;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;

/**
 * V4:新增内容
 * 请求对象
 * 该类的每一个实例用于表示浏览器发送过来的一个HTTP请求
 * HTTP协议要求请求的格式由三部分构成:请求行,消息头,消息正文
 */
public class HttpServletRequest {
    private Socket socket;
    //请求行相关信息
    private String method;//请求方式
    private String uri;//抽象路径
    private String protocol;//协议版本

    private String requestURI;//保存uri中"?"左侧的请求路径部分
    private String queryString;//保存uri中"?"右侧的参数部分
    private Map<String,String> parameters = new HashMap<>();//保存每一组参数


    //消息头相关信息 key:消息头名字  value:消息头对应的值
    private Map<String,String> headers = new HashMap<>();


    public HttpServletRequest(Socket socket) throws IOException, EmptyRequestException {
        this.socket = socket;

        //1.1解析请求行
        parseRequestLine();

        //1.2解析消息头
        parseHeaders();

        //1.3解析消息正文
        parseContent();
    }
    //解析请求行
    private void parseRequestLine() throws IOException, EmptyRequestException {
        String line = readLine();

        if(line.isEmpty()){//如果请求行是个空字符串,则说明本次为空请求
            throw new EmptyRequestException();
        }

        System.out.println("请求行:"+line);

        //将请求行按照空格("\s"在正则表达式中表示一个空白字符,包含空格)拆分为三部分
        String[] data = line.split("\\s");
        method = data[0];
        uri = data[1];
        protocol = data[2];

        parseURI();//进一步解析uri

        System.out.println("method:"+method);
        System.out.println("uri:"+uri);
        System.out.println("protocol:"+protocol);
    }
    //进一步解析uri
    private void parseURI(){
        String[] data = uri.split("\\?");
        requestURI = data[0];
        if(data.length>1){
            queryString = data[1];
            parseParameters(queryString);
        }
        System.out.println("requestURI:"+requestURI);
        System.out.println("queryString:"+queryString);
        System.out.println("parameters:"+parameters);

    }

    /**
     * 解析参数
     * 参数的格式应当是:name=value&name=value&...
     * 如果是GET请求,参数来自抽象路径的"?"右侧
     * 如果是POST请求,参数来自消息正文
     * 但是格式是一致的。
     * @param line
     */
    private void parseParameters(String line){
        String[] paraArr = line.split("&");
        for(String para : paraArr){
            String[] arr = para.split("=",2);
            parameters.put(arr[0],arr[1]);
        }
    }

    //解析消息头
    private void parseHeaders() throws IOException {
        while(true) {
            String line = readLine();
            if(line.isEmpty()){//如果读取到了空行
                break;
            }
            System.out.println("消息头:" + line);
            String[] data = line.split(":\\s");
            headers.put(data[0],data[1]);
        }
        System.out.println("headers:"+headers);
    }
    //解析消息正文
    private void parseContent() throws IOException {
        //确定本次请求是否包含正文(消息头中是否含有Content-Length)
        if(headers.containsKey("Content-Length")){
            int contentLength = Integer.parseInt(getHeader("Content-Length"));
            byte[] contentData = new byte[contentLength];
            InputStream in = socket.getInputStream();
            in.read(contentData);//读取消息正文到字节数组中

            String contentType = getHeader("Content-Type");
            //分支:判断正文类型,并进行对应的解析
            if("application/x-www-form-urlencoded".equals(contentType)){
                //正文是form表单提交的数据(原get请求提交是在抽象路径"?"右侧内容)
                String line = new String(contentData, StandardCharsets.ISO_8859_1);
                System.out.println("=============正文:"+line);
                parseParameters(line);
            }
            //后续可扩展支持其他正文类型的解析
//            else if("xxx/xxx".equals(contentType)){
//
//            }

        }
    }

    /**
     * 通过socket获取的输入流读取客户端发送过来的一行字符串
     * @return
     */
    private String readLine() throws IOException {//通常被重用的代码不自己处理异常
        //对一个socket实例调用多次getInputStream()返回的始终是同一条输入流。而输出流也是如此
        InputStream in = socket.getInputStream();
        int d;
        char pre='a',cur='a';//pre表示上次读取的字符,cur表示本次读取的字符
        StringBuilder builder = new StringBuilder();//保存读取后的所有字符
        while((d = in.read())!=-1){
            cur = (char)d;//本次读取的字符
            if(pre==13 && cur==10){//是否连续读取到了回车+换行
                break;
            }
            builder.append(cur);//将本次读取的字符拼接
            pre=cur;//在进行下次读取前,将本次读取的字符保存到"上次读取的字符"中
        }
        return builder.toString().trim();
    }

    public String getMethod() {
        return method;
    }

    public String getUri() {
        return uri;
    }

    public String getProtocol() {
        return protocol;
    }

    /**
     * 根据给定的消息头的名字获取对应消息头的值
     * @param name
     * @return
     */
    public String getHeader(String name) {
        return headers.get(name);
    }

    public String getRequestURI() {
        return requestURI;
    }

    public String getQueryString() {
        return queryString;
    }

    public String getParameter(String name) {
        return parameters.get(name);
    }
}

V16

解决传递中文问题:

  • 现象:

    • 当浏览器无论是以GET形式提交还是POST形式提交表单,如果表单中含有中文信息时,所有的中文内容都会被转为:
    • “username=%E8%8C%83%E4%BC%A0%E5%A5%87&password=123456&nickname=%E4%BC%A0%E5%A5%87&age=22”,
  • 原因:

    • 以GET请求为例,表单信息会被拼接到抽象路径的"?"右侧。抽象路径是包含在浏览器发送的请求的请求行中:
    • GET /regUser?username=%E8%8C%83%E4%BC%A0%E5%A5%87&… HTTP/1.1

HTTP协议要求浏览器发送的请求的请求行和消息头必须是文本且字符集只能是ISO8859-1
实际上可传输的字符只有英文,数字,符号
因为==ISO8859-1不支持中文==,因此不能直接将中文包含在抽象路径中
GET /regUser?username=张三&… HTTP/1.1 不合法!!!

解决办法

  • 核心思想:ISO8859-1支持的字符来表达不支持的字符
  • 可以使用的字符:英文,数字,符号

例:
首先:先将中文字符"范"按照支持的字符(通常是UTF-8)转换为2进制"范"-----UTF8---->11101000 10001100 10000011
我们可以用字符"0"和字符"1"表示2进制
传递时可以将:/regUser?username=&… 不合法

换做:
/regUser?username=111010001000110010000011&… 合法

服务端接收到后,再将这一串1和0组成的内容当做2进制看待,再按照UTF-8还原就可以看到"范"

问题得到解决,但是新的问题产生了:数据太长

解决办法:将2进制用16进制表示

二进制十进制十六进制
000000
000111
001022
001133
010044
010155
011066
011177
100088
100199
101010a
101111b
110012c
110113d
111014e
111115f

原本:
/regUser?username=11101000 10001100 10000011&…
用16进制表示:
/regUser?username=E88C83&…
长度可以缩短4倍

长度问题解决了,新的问题又出现了
/regUser?username=E88C83&…
服务端接收到username后,该用户的名字是"范"还是此人就叫 E88C83
为了避免混淆,URL地址格式进行了规定
如果使用英文+数字组合表示的是16进制,则需要在每两位16进制前添加一个"%"
因此:
/regUser?username=E88C83&… 此人就叫E88C83
/regUser?username=%E8%8C%83&… 这里是16进制表示了3个字节,可转换为"范"

服务端如果想正确得到中文则需要将上述动作反推。
%E8%8C%83–>11101000 10001100 10000011—>范
该操作JAVA有现成的API:URLDecoder

HttpServletRequest

package com.birdboot.http;

import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.Socket;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;

/**
 * V4:新增内容
 * 请求对象
 * 该类的每一个实例用于表示浏览器发送过来的一个HTTP请求
 * HTTP协议要求请求的格式由三部分构成:请求行,消息头,消息正文
 */
public class HttpServletRequest {
    private Socket socket;
    //请求行相关信息
    private String method;//请求方式
    private String uri;//抽象路径
    private String protocol;//协议版本

    private String requestURI;//保存uri中"?"左侧的请求路径部分
    private String queryString;//保存uri中"?"右侧的参数部分
    private Map<String,String> parameters = new HashMap<>();//保存每一组参数

    //消息头相关信息 key:消息头名字  value:消息头对应的值
    private Map<String,String> headers = new HashMap<>();

    public HttpServletRequest(Socket socket) throws IOException, EmptyRequestException {
        this.socket = socket;
        //1.1解析请求行
        parseRequestLine();
        //1.2解析消息头
        parseHeaders();
        //1.3解析消息正文
        parseContent();
    }
    //解析请求行
    private void parseRequestLine() throws IOException, EmptyRequestException {
        String line = readLine();

        if(line.isEmpty()){//如果请求行是个空字符串,则说明本次为空请求
            throw new EmptyRequestException();
        }

        System.out.println("请求行:"+line);

        //将请求行按照空格("\s"在正则表达式中表示一个空白字符,包含空格)拆分为三部分
        String[] data = line.split("\\s");
        method = data[0];
        uri = data[1];
        protocol = data[2];

        parseURI();//进一步解析uri

        System.out.println("method:"+method);
        System.out.println("uri:"+uri);
        System.out.println("protocol:"+protocol);
    }
    //进一步解析uri
    private void parseURI(){
        String[] data = uri.split("\\?");
        requestURI = data[0];
        if(data.length>1){
            queryString = data[1];
            parseParameters(queryString);
        }
        System.out.println("requestURI:"+requestURI);
        System.out.println("queryString:"+queryString);
        System.out.println("parameters:"+parameters);

    }

    /**
     * 解析参数
     * 参数的格式应当是:name=value&name=value&...
     * 如果是GET请求,参数来自抽象路径的"?"右侧
     * 如果是POST请求,参数来自消息正文
     * 但是格式是一致的。
     * @param line
     */
    private void parseParameters(String line){
        //将line转码
        try {
            line = URLDecoder.decode(line,"UTF-8");
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        String[] paraArr = line.split("&");
        for(String para : paraArr){
            String[] arr = para.split("=",2);
            parameters.put(arr[0],arr[1]);
        }
    }

    //解析消息头
    private void parseHeaders() throws IOException {
        while(true) {
            String line = readLine();
            if(line.isEmpty()){//如果读取到了空行
                break;
            }
            System.out.println("消息头:" + line);
            String[] data = line.split(":\\s");
            headers.put(data[0],data[1]);
        }
        System.out.println("headers:"+headers);
    }
    //解析消息正文
    private void parseContent() throws IOException {
        //确定本次请求是否包含正文(消息头中是否含有Content-Length)
        if(headers.containsKey("Content-Length")){
            int contentLength = Integer.parseInt(getHeader("Content-Length"));
            byte[] contentData = new byte[contentLength];
            InputStream in = socket.getInputStream();
            in.read(contentData);//读取消息正文到字节数组中

            String contentType = getHeader("Content-Type");
            //分支:判断正文类型,并进行对应的解析
            if("application/x-www-form-urlencoded".equals(contentType)){
                //正文是form表单提交的数据(原get请求提交是在抽象路径"?"右侧内容)
                String line = new String(contentData, StandardCharsets.ISO_8859_1);
                System.out.println("=============正文:"+line);
                parseParameters(line);
            }
            //后续可扩展支持其他正文类型的解析
//            else if("xxx/xxx".equals(contentType)){
//            }

        }
    }

    /**
     * 通过socket获取的输入流读取客户端发送过来的一行字符串
     * @return
     */
    private String readLine() throws IOException {//通常被重用的代码不自己处理异常
        //对一个socket实例调用多次getInputStream()返回的始终是同一条输入流。而输出流也是如此
        InputStream in = socket.getInputStream();
        int d;
        char pre='a',cur='a';//pre表示上次读取的字符,cur表示本次读取的字符
        StringBuilder builder = new StringBuilder();//保存读取后的所有字符
        while((d = in.read())!=-1){
            cur = (char)d;//本次读取的字符
            if(pre==13 && cur==10){//是否连续读取到了回车+换行
                break;
            }
            builder.append(cur);//将本次读取的字符拼接
            pre=cur;//在进行下次读取前,将本次读取的字符保存到"上次读取的字符"中
        }
        return builder.toString().trim();
    }

    public String getMethod() {
        return method;
    }

    public String getUri() {
        return uri;
    }

    public String getProtocol() {
        return protocol;
    }

    /**
     * 根据给定的消息头的名字获取对应消息头的值
     * @param name
     * @return
     */
    public String getHeader(String name) {
        return headers.get(name);
    }

    public String getRequestURI() {
        return requestURI;
    }

    public String getQueryString() {
        return queryString;
    }

    public String getParameter(String name) {
        return parameters.get(name);
    }
}

HttpServletResponse

package com.birdboot.http;

import javax.activation.MimetypesFileTypeMap;
import java.io.*;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

/**
 * V7新增内容:
 * 响应对象
 * 该类的每一个实例用于表示服务端给客户端发送的一个HTTP的响应
 * HTTP协议要求一个响应由三部分构成:状态行,响应头,响应正文
 */
public class HttpServletResponse {

    private static MimetypesFileTypeMap mftm = new MimetypesFileTypeMap();

    private Socket socket;

    //状态行相关信息
    private int statusCode = 200;//状态代码
    private String statusReason = "OK";//状态描述

    //响应头相关信息 key:响应头的名字  value:响应头的值
    private Map<String,String> headers = new HashMap<>();

    //响应正文相关信息
    private File contentFile;//响应正文对应的实体文件


    public HttpServletResponse(Socket socket){
        this.socket = socket;
    }

    /**
     * 该方法用于将当前响应对象内容以标准的HTTP响应格式发送给客户端
     */
    public void response() throws IOException {
        //3.1发送状态行
        sendStatusLine();
        //3.2发送响应头
        sendHeaders();
        //3.3发送响应正文
        sendContent();
    }

    //发送状态行
    private void sendStatusLine() throws IOException {
        println("HTTP/1.1"+" "+statusCode+" "+statusReason);
    }
    //发送响应头
    private void sendHeaders() throws IOException {
        /*
            遍历headers将所有待发送的响应头发送给浏览器
            headers
            key                 value
            Content-Type        text/html
            Content-Length      42123
            Server              BirdServer
            ...                 ...
         */
        Set<Map.Entry<String,String>> entrySet = headers.entrySet();
        for(Map.Entry<String,String> e : entrySet){
            String name = e.getKey();
            String value = e.getValue();
            println(name+": "+value);
        }

        //单独发送回车+换行,表示响应头发送完毕
        println("");
    }
    //发送响应正文
    private void sendContent() throws IOException {
        if(contentFile!=null) {
            FileInputStream fis = new FileInputStream(contentFile);
            OutputStream out = socket.getOutputStream();
            byte[] buf = new byte[1024 * 10];//10kb
            int d;//记录每次实际读取的数据量
            while ((d = fis.read(buf)) != -1) {
                out.write(buf, 0, d);
            }
        }
    }

    /**
     * V7:将ClientHandler中发送响应的工作全部移动到这里,println方法也是。
     * 向客户端发送一行字符串
     * @param line
     */
    private void println(String line) throws IOException {
        OutputStream out = socket.getOutputStream();
        byte[] data = line.getBytes(StandardCharsets.ISO_8859_1);
        out.write(data);
        out.write(13);//发送回车符
        out.write(10);//发送换行符
    }
    public int getStatusCode() {
        return statusCode;
    }
    public void setStatusCode(int statusCode) {
        this.statusCode = statusCode;
    }
    public String getStatusReason() {
        return statusReason;
    }
    public void setStatusReason(String statusReason) {
        this.statusReason = statusReason;
    }
    public File getContentFile() {
        return contentFile;
    }

    /**
     * 设置响应正文对应的实体文件,该方法中会自动根据该文件添加对应的两个响应头:
     * Content-Type和Content-Length
     * @param contentFile
     */
    public void setContentFile(File contentFile) {
        this.contentFile = contentFile;
        addHeader("Content-Type",mftm.getContentType(contentFile));
        addHeader("Content-Length",contentFile.length()+"");
    }

    /**
     * 添加一个响应头
     * @param name
     * @param value
     */
    public void addHeader(String name,String value){
        headers.put(name,value);
    }

    /**
     * 要求浏览器重定向到指定位置
     * @param location
     */
    public void sendRedirect(String location){
        //1设置状态代码302
        statusCode = 302;
        statusReason = "Moved Temporarily";
        //2添加响应头Location
        addHeader("Location",location);
    }
}

反射

JAVA反射机制

  • 反射是java的动态机制,允许程序在"运行期间"再确定如:对象的实例化,方法的调用,属性的操作等。
  • 反射机制可以提高代码的灵活性和适应性。但是会带来较多的系统开销和较慢的运行效率
  • 因此反射机制不能过度被依赖。

反射机制使用的第一步:获取待操作的类的类对象类对象:Class类的实例

  • JVM内部每个被加载的类都有且只有一个Class的实例与之对应。
  • JVM加载一个类时会读取该类的.class文件然后将其载入到JVM内部
  • 与此同时会实例化一个Class的实例,用该实例记录被加载的类的信息(类名,方法,构造器等)

获取一个类的类对象方式

  1. 类名.class
Class cls = String.class;//获取String的类对象
Class cls = int.class;//获取int的类对象(基本类型只有这一种方式获取类对象)
  1. Class.forName(String className)

    //根据类的完全限定(包名.类名)名加载并获取该类的类对象
    Class cls = Class.forName("java.lang.String");
    
  2. ClassLoader类加载器方式

反射对象

  • Class,它的每一个实例用于表示一个类的信息
  • Package,它的每一个实例用于表示一个包的信息
  • Method,它的每一个实例用于表示一个方法
  • Constructor,它的每一个实例用于表示一个构造器
  • Filed,它的每一个实例用于表示一个属性

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/440453.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

SpringCloud整合AOP做日志管理

目录 1、前置知识2、步骤2.1、依赖2.2、自定义注解&#xff0c;用于注解式AOP2.3、定制切面类2.4、测试 1、前置知识 切面&#xff08;Aspect&#xff09;&#xff1a;官方的抽象定义为“一个关注点的模块化&#xff0c;这个关注点可能会横切多个对象”&#xff0c;在本例中&a…

超详细Redis入门教程——Redis命令(上)

前言 本文小新为大家带来 超详细Redis入门教程——Redis命令&#xff08;上&#xff09; 相关知识&#xff0c;具体内容包括Redis 基本命令&#xff0c;Key 操作命令&#xff0c;String 型 Value 操作命令&#xff0c;Hash 型 Value 操作命令&#xff0c;List 型 Value 操作命令…

快速搭建外卖配送服务:利用外卖系统源码实现

外卖配送服务已经成为了现代消费者生活的一部分&#xff0c;它不仅方便了消费者的用餐需求&#xff0c;也给商家提供了新的销售渠道&#xff0c;同时也为外卖配送员提供了更多的就业机会。为了满足这个市场的需求&#xff0c;外卖系统源码应运而生。 外卖系统源码是一个集成了…

第一章:数、式、方程与方程组

1.实数 1.内容概述 1.了解实数分类2.数轴3.相反数和倒数4.绝对值5.算数平方根相关概念及有关计算2.实数分类 3.实数的基本概念 1.数轴:规定原点、正方向和单位长度的直线叫做数轴2.相反数:绝对值相同而符号相反的两个数,互称相反数3.倒数:1除以任何数的商,我们叫做倒数,0…

超市购物系统【GUI/Swing+MySQL】(Java课设)

系统类型 Swing窗口类型Mysql数据库存储数据 使用范围 适合作为Java课设&#xff01;&#xff01;&#xff01; 部署环境 jdk1.8Mysql8.0Idea或eclipsejdbc 运行效果 本系统源码地址&#xff1a;https://download.csdn.net/download/qq_50954361/87682510 更多系统资源库…

Jenkins ssh windows 部署 java程序

版权说明&#xff1a; 本文由博主keep丶原创&#xff0c;转载请保留该段内容在文章头部。 原文地址&#xff1a; https://blog.csdn.net/qq_38688267/article/details/130203785 文章目录 前言实现步骤1. windows下载安装ssh2. windows 安装 winsw2.1 下载 winsw2.2 配置winsw2…

Linux 0.11启动过程分析(一)

Linux 0.11 系列文章 Linux 0.11启动过程分析&#xff08;一&#xff09; Linux 0.11 fork 函数&#xff08;二&#xff09; Linux0.11 缺页处理&#xff08;三&#xff09; Linux0.11 根文件系统挂载&#xff08;四&#xff09; Linux0.11 文件打开open函数&#xff08;五&…

[oeasy]python0132_变量含义_meaning_声明_declaration_赋值_assignment

变量定义 回忆上次内容 上次回顾了一下历史 python 是如何从无到有的看到 Guido 长期的坚持和努力 编程语言的基础都是变量声明 python是如何声明变量的呢&#xff1f; 变量 想要定义变量首先明确什么是变量 变量就是数值能变的量英文名称 variable 计算机在内存中分配出…

SpringBoot Starter 作用及原理

本文会以 mybatis 为例&#xff0c;通过对比 mybatis-spring 和 mybatis-spring-boot-starter 代码示例&#xff0c;了解 Starter 的作用。并对 mybatis-spring-boot-starter 进行简单剖析&#xff0c;了解 Starter 原理。 下面还有投票&#xff0c;一起参与进来吧&#x1f44d…

DataEase看中国 - 中国影星“成龙”电影票房数据分析

背景介绍 说起成龙&#xff0c;我们并不陌生&#xff0c;著名的动作明星。以武打动作片出道&#xff0c;凭借动作片《红番区》打入好莱坞&#xff0c;该片打破北美外语片票房纪录。 目前&#xff0c;由成龙、郭麒麟等主演的新片《龙马精神》正在公映&#xff0c;电影《…

【每日一练】JAVA算法求柱状图中最大的矩形面积

文章目录 前言题目分析算法实战1、创建算法方法2、创建测试用例3、查看测试结果 写在最后 前言 作为一名以JAVA语言为主的搬砖人&#xff0c;学习掌握好函数语法很重要&#xff0c;但是算法也是需要掌握的。今天我们就分享一个求柱状图中最大的矩形面积的题目&#xff0c;这个…

torch.utils.data.DataLoader中的next(iter(train_dataloader))

在做实验时&#xff0c;我们常常会使用用开源的数据集进行测试。而Pytorch中内置了许多数据集&#xff0c;这些数据集我们常常使用DataLoader类进行加载。 如下面这个我们使用DataLoader类加载torch.vision中的FashionMNIST数据集。 from torch.utils.data import DataLoader …

数据结构入门(C语言)顺序表的增删查改

目录 前言1. 顺序表的概念2. 动态顺序表2.1 顺序表的初始化与销毁2.2 顺序表的尾插容量检查2.3 顺序表的尾删2.4 顺序表的头插2.5 顺序表的头删2.6 固定位置的插入2.7 固定位置的删除2.8 查找和打印2.9 修改元素主函数部分(菜单) 结语 前言 本章会用C语言来描述数据结构中的顺…

协同运力、算力、存力,加速迈向智能世界

2023年4月20日&#xff0c;华为在HAS2023期间举办“迈向智能世界”主题论坛&#xff0c;吸引了来自全球的分析师、专家学者及媒体与会。会上&#xff0c;华为ICT战略与Marketing总裁彭松发表了“持续技术创新&#xff0c;加速迈向智能世界”的主题演讲。 华为ICT战略与Marketin…

zabbix监控linux主机

1.本实验使用centos7主机&#xff0c;IP地址为10.1.60.115&#xff0c;firewalld和selinux服务已关闭 2.下载zabbix yum源(与zabbix server用一样的版本) rpm -Uvh https://repo.zabbix.com/zabbix/5.0/rhel/7/x86_64/zabbix-release-5.0-1.el7.noarch.rpm 3.安装zabbix客户…

玛雅水上乐园|玩趣系列作品集

玛雅水上乐园曾经是一座历史悠久的玛雅金字塔&#xff0c;曾用于宗教和水上航行&#xff0c;被废弃了 3000 多年。现在&#xff0c;01a1 工作室已将其改造成一个令人兴奋的旅游景点&#xff0c;在这里你可以享受美食和饮料&#xff0c;享受日光浴&#xff0c;并结交新朋友。所以…

从零学习SDK(8)SDK的集成和部署

选择使用SDK与其他平台和服务进行集成和部署的好处有&#xff1a; 简化开发流程&#xff0c;节省时间和成本&#xff0c;无需从零开始编写复杂的代码逻辑。 保证功能的稳定性和兼容性&#xff0c;避免出现各种潜在的错误和问题。 享受SDK提供方的技术支持和更新&#xff0c;获…

L频段GaN功率放大器的设计关键点

氮化镓技术的不断进步促使设备在更高的功率、电源电压和频率下工作。 ​图1 QPD1013 晶体管的照片 如图1所示&#xff0c; QPD1013晶体管采用0.50 μm GaN-on-SiC技术。它采用具有成本效益的6.6x7.2 mm DFN(双边扁平无引脚)封装&#xff0c;与传统的金属陶瓷封装相比&#xff…

ROS学习——rotors仿真下载与运行

rotors 无人机仿真主要分为两类&#xff1a;硬件在环仿真&#xff08;HITL&#xff09;和软件在环仿真&#xff08;SITL全称Software in the loop&#xff09;。 无人机软件在环仿真是指完全用计算机来模拟出无人机飞行时的状态&#xff0c;而硬件在环仿真是指计算机连接飞控…

【ArcGIS Pro二次开发】(22):生成分级用地编码和名称

在国土空间规划中&#xff0c;用地用海分类采用三级分类体系&#xff0c;共设置24种一级类、106种二级类及39 种三级类。在某些场景中&#xff0c;需要按等级归类并汇总统计。 这个小工具的作用就是通过用地编码生成三级地类&#xff0c;作为后续统计的基础。 一、要实现的功能…