实现基于netty的web框架,了解一下

使用Netty实现静态Web服务器 以学习为目的,基于Netty实现一个简单的静态web服务器。 使用到的技术及开发环境:基于netty实现静态web服务器。 能够提供基本的http服务,仅支持静态资源服务即可。支持静的态资源包括:HTML、JS、CSS、常见的图片(包括但不限于jpeg、png、svg、webp、gif等格式)、字体文件以及其他文件。 以上资源在响应到客户端时必须给与正确的MimeType(http中称MediaType),例如:html文件响应时,必须指定响应头。 其他资源文件:非web资源文件,例如压缩文件,返回响应头。 阅读详情


这篇实现基于netty的web框架,你说netty强不强,文中有不对的地方,欢迎大牛指正

先普及几个知识点

@sharable

复制代码
标注一个channel handler可以被多个channel安全地共享。
ChannelHandlerAdapter还提供了实用方法isSharable()。如果其对应的实现被标注为Sharable,那么这个方法将返回true,
表示它可以被添加到多个ChannelPipeline中。 因为一个ChannelHandler可以从属于多个ChannelPipeline,所以它也可以绑定到多个ChannelHandlerContext实例。
用于这种用法的ChannelHandler必须要使用@Sharable注解标注;否则,试图将它添加到多个ChannelPipeline时将会触发异常。
显而易见,为了安全地被用于多个并发的Channel(即连接),这样的ChannelHandler必须是线程安全的。
复制代码

AtomicInteger:这个类的存在是为了满足在高并发的情况下,原生的整形数值自增线程不安全的问题,在Java语言中,++i和i++操作并不是线程安全的,在使用的时候,不可避免的会用到synchronized关键字。而AtomicInteger则通过一种线程安全的加减操作接口。AtomicInteger为什么能够达到多而不乱,处理高并发应付自如呢?

  这是由硬件提供原子操作指令实现的,这里面用到了一种并发技术:CAS。在非激烈竞争的情况下,开销更小,速度更快

TimeUnit: 

TimeUnit是Java.util.concurrent包下面的一个类。它提供了两大功能:

1)提供了可读性更好的线程暂停操作,通常用来替换Thread.sleep(); 

2)提供了便捷方法用于把时间转换成不同单位,如把秒转换成毫秒;

TimeUnit.MINUTES.sleep(4);  // sleeping for 4 minutes

Thread.sleep(4*60*1000);

项目的目录结构

 

上代码,分享一些关键的代码,后续的giuhub上的demo的注释很详细

复制代码
//Netty 事件回调类
@Sharable
public class MessageCollector extends ChannelInboundHandlerAdapter {
    private final static Logger LOG = LoggerFactory.getLogger(MessageCollector.class);
    //业务线程池
    private ThreadPoolExecutor[] executors;
    private RequestDispatch requestDispatch;
    //业务队列最大值
    private int requestsMaxInflight=1000;

    public MessageCollector(int workerThreads,RequestDispatch dispatch){
        //给业务线程命名
        ThreadFactory factory =new ThreadFactory() {
            AtomicInteger seq=new AtomicInteger();
            @Override
            public Thread newThread(Runnable r) {
                Thread thread =new Thread(r);
                thread.setName("http-"+seq.getAndIncrement());
                return thread;
            }
        };
        this.executors=new ThreadPoolExecutor[workerThreads];
        for(int i=0;i<workerThreads;i++){
            ArrayBlockingQueue queue=new ArrayBlockingQueue<Runnable>(requestsMaxInflight);
            ////闲置时间超过30秒的线程就自动销毁
            this.executors[i]=new ThreadPoolExecutor(1,1,
                    30, TimeUnit.SECONDS, queue,factory,new CallerRunsPolicy());
        }

        this.requestDispatch=dispatch;
    }

    public  void  closeGracefully(){
        //优雅一点关闭,先通知,再等待,最后强制关闭
        for (int i=0;i<executors.length;i++){
            ThreadPoolExecutor executor=executors[i];
            try {
                executor.awaitTermination(10,TimeUnit.SECONDS);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            executor.shutdownNow();
        }
    }

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        //客户端来了一个新的连接
       LOG.info("connection comes {}",ctx.channel().remoteAddress());
    }

    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        //客户端走了一个
        LOG.info("connection leaves {}",ctx.channel().remoteAddress());
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        if (msg instanceof FullHttpRequest){
            FullHttpRequest req= (FullHttpRequest) msg;
            CRC32 crc32=new CRC32();
            crc32.update(ctx.hashCode());
            int idx =(int) (crc32.getValue()%executors.length);
            //用业务线程处理消息
            this.executors[idx].execute(() ->{
                requestDispatch.dispatch(ctx,req);
            });
        }
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        //此处可能因为客户机器突发重启
        //也可能客户端连接时间超时,后面的REadTimeoutHandle抛出异常
        //也可能消息协议错误,序列化异常
        ctx.close();
    }
}
复制代码
HttpServer
复制代码
public class HttpServer {
    private final static Logger LOG= LoggerFactory.getLogger(HttpServer.class);
    private String ip;
    private int port;  //端口
    private int ioThreads;  //IO线程数,用于处理套接字读写,由Netty内部管理
    private int workerThreads;  //业务线程数,专门处理http请求,由我们本省框架管理
    private RequestDispatch requestDispatch;//请求配发器对象

    public HttpServer() {
    }

    public HttpServer(String ip, int port, int ioThreads,
                      int workerThreads, RequestDispatch requestDispatch) {
        this.ip = ip;
        this.port = port;
        this.ioThreads = ioThreads;
        this.workerThreads = workerThreads;
        this.requestDispatch = requestDispatch;
    }
    //用于服务端,使用一个ServerChannel接收客户端的连接,
    // 并创建对应的子Channel
    private ServerBootstrap bootstrap;
    //包含多个EventLoop
    private EventLoopGroup group;
    //代表一个Socket连接
    private Channel serverChannel;
    //
    private MessageCollector collector;

    public  void start(){
        bootstrap=new ServerBootstrap();
        group=new NioEventLoopGroup(ioThreads);
        bootstrap.group(group);
        collector=new MessageCollector(workerThreads,requestDispatch);
        bootstrap.channel(NioServerSocketChannel.class).childHandler(new ChannelInitializer<SocketChannel>() {
            @Override
            protected void initChannel(SocketChannel socketChannel) throws Exception {
                ChannelPipeline pipeline=socketChannel.pipeline();
                //如果客户端60秒没有任何请求,就关闭客户端连接
                pipeline.addLast(new ReadTimeoutHandler(10));
                //客户端和服务器简单的编解码器:HttpClientCodec和HttpServerCodec。
                //ChannelPipelien中有解码器和编码器(或编解码器)后就可以操作不同的HttpObject消息了;但是HTTP请求和响应可以有很多消息数据,
                // 你需要处理不同的部分,可能也需要聚合这些消息数据
                pipeline.addLast(new HttpServerCodec());
                //通过HttpObjectAggregator,Netty可以聚合HTTP消息,
                // 使用FullHttpResponse和FullHttpRequest到ChannelPipeline中的下一个ChannelHandler,这就消除了断裂消息,保证了消息的完整
                pipeline.addLast(new HttpObjectAggregator(1 << 30)); // max_size = 1g
                //允许通过处理ChunkedInput来写大的数据块
                pipeline.addLast(new ChunkedWriteHandler());
                //将业务处理器放到最后
                pipeline.addLast(collector);
            }
        });
    }

    public void stop() {
        // 先关闭服务端套件字
        serverChannel.close();
        // 再斩断消息来源,停止io线程池
        group.shutdownGracefully();
        // 最后停止业务线程
        collector.closeGracefully();
    }

}
复制代码

 

RequestDispatcherImpl 是请求派发器,用于将收到的HTTP请求对象扔给响应的RequestHandler进行处理。
复制代码
public class RequestDispatcherImpl implements RequestDispatch {
    private final static Logger LOG = LoggerFactory.getLogger(RequestDispatcherImpl.class);

    private String contextRoot;
    private Router router;
    private Map<Integer, WebExceptionHandler> exceptionHandlers = new HashMap<>();
    private WebExceptionHandler defaultExceptionHandler = new DefaultExceptionHandler();

    private WebTemplateEngine templateEngine = new WebTemplateEngine() {
    };

    static class DefaultExceptionHandler implements WebExceptionHandler {

        @Override
        public void handle(ApplicationContext ctx, AbortException e) {
            if (e.getStatus().code() == 500) {
                LOG.error("Internal Server Error", e);
            }
            ctx.error(e.getContent(), e.getStatus().code());
        }

    }

    public RequestDispatcherImpl(Router router) {
        this("/", router);
    }

    public RequestDispatcherImpl(String contextRoot, Router router) {
        this.contextRoot = CurrentUtil.normalize(contextRoot);
        this.router = router;
    }

    public RequestDispatcherImpl templateRoot(String templateRoot) {
        this.templateEngine = new FreemarkerEngine(templateRoot);
        return this;
    }

    public String root() {
        return contextRoot;
    }

    public RequestDispatcherImpl exception(int code, WebExceptionHandler handler) {
        this.exceptionHandlers.put(code, handler);
        return this;
    }

    public RequestDispatcherImpl exception(WebExceptionHandler handler) {
        this.defaultExceptionHandler = handler;
        return this;
    }
    @Override
    public void dispatch(ChannelHandlerContext channelCtx, FullHttpRequest req) {
        ApplicationContext ctx = new ApplicationContext(channelCtx, contextRoot, templateEngine);
        try {
            this.handleImpl(ctx, new Request(req));
        } catch (AbortException e) {
            this.handleException(ctx, e);
        } catch (Exception e) {
            this.handleException(ctx, new AbortException(HttpResponseStatus.INTERNAL_SERVER_ERROR, e));
        } finally {
            req.release();
        }
    }

    private void handleException(ApplicationContext ctx, AbortException e) {
        WebExceptionHandler handler = this.exceptionHandlers.getOrDefault(e.getStatus().code(), defaultExceptionHandler);
        try {
            handler.handle(ctx, e);
        } catch (Exception ex) {
            this.defaultExceptionHandler.handle(ctx, new AbortException(HttpResponseStatus.INTERNAL_SERVER_ERROR, ex));
        }
    }

    private void handleImpl(ApplicationContext ctx, Request req) throws Exception {
        if (req.decoderResult().isFailure()) {
            ctx.abort(400, "http protocol decode failed");
        }
        if (req.relativeUri().contains("./") || req.relativeUri().contains(".\\")) {
            ctx.abort(400, "unsecure url not allowed");
        }
        if (!req.relativeUri().startsWith(contextRoot)) {
            throw new AbortException(HttpResponseStatus.NOT_FOUND);
        }
        req.popRootUri(contextRoot);
        router.handle(ctx, req);
    }
}


上传的项目

https://download.youkuaiyun.com/download/one_developer/10498186



基于netty框架的WebServer实现 阅读详情

相关推荐

基于netty实现的web框架

一个基于netty实现web框架,或者mvc框架,实现基于netty的web框架,你说netty强不强,文中有不对的地方,欢迎大牛指正

【Netty中间件子系列四】Netty实现Web服务器

本来打算用Netty来实现一个Severlet服务器,发现spring已经做了相应的支持,那么我们来看看究竟他们的性能有什么差异,如果我们要用netty实现一个severlet容器应该这么做 测试机器:Linux CentOS6.5 4核16G SpringBoot版本:2.2.2.RELEASE JDK版本:jdk1.8.0_151 ab压测 我们先对上面说的三个接口进行压测,为避免网络环境影响,我们直接在服务器上使用ab进行压力测试。 压测分三组,每组压测这三个接口,每个接口发...

qq_35789269的博客 2762

手把手教你做基于stm32的红外、语音、按键智能灯光控制(上)

这次项目使用的板子是stm32f103c8t6最小系统板,这个板子在tb上都能够买到,随便一个最小系统板都可以,== 注意在买最小系统板的时候需要买一个stlink下载器来下载程序。显示模块是使用的0.96英寸的OLED屏幕,注意是使用的IIC通信协议的OLED屏幕,不是使用SPI总线协议的。这次写的代码还顺便集成了检测温湿度的功能,所以要使用DHT11温湿度传感器,来采集温度。本文所设计的基于单片机的灯光控制系统主要由模式选择功能、手动模式和自动模式组成。LED灯自然就是要控制的部件了。

nbbskk的博客 6559

web编程(5) Netty

Netty是目前最流行的NIO框架之一,包含基本概念、知识点详细整理,以及3个经典案例源码

sjzwangxufeng的专栏 311

LCD、LED、OLED、Mini LED、Micro LED、全阵列LED、QLED、QD-OLED、QNED、AMOLED是什么意思?

LCD(液晶显示器,Liquid Crystal Display)定义:LCD 是一种利用液晶材料控制光线通过的显示技术,需依靠背光源(如冷阴极荧光灯 CCFL 或 LED)照亮液晶层以形成图像。技术原理:液晶分子在电场作用下改变排列,控制背光通过的强度和颜色。液晶本身不发光,需外部光源。特点:结构包括背光层、液晶层、彩色滤光片和偏光片。响应时间较慢,黑色表现因背光漏光而偏灰。LED(发光二极管显示,Light Emitting Diode)定义。

中国最威武的城市!武功军威-武威,铁骨铮铮! 3432

WSOLA原理及matlab仿真

WSOLA原理 对于 OLA 算法来说,常常无法保持原信号中的周期性结构,在帧裁剪的过程中,不能保证每一个帧都能覆盖完整周期并保证其相位对齐,会产生相位跳跃失真。 为保证**基频不变**,就要使用 WSOLA 算法。 WSOLA 并不直接在 Ha 处找一帧进行直接拼接,而是在一个区间内寻找与理想帧最接近的一帧进行叠加。 ![在这里插入图片描述](https://img-blog.csdnimg.cn/8a234f4fde104fc69a9f3ddb7bcf3014.png?x-oss-process=ima

ManiacLook的博客 3218

netty web 容器_Netty网络通信框架和JettyWeb容器的关系、区别与适用情况

最近一直想做一个分布式服务的模型用来完成实验室项目的分流计算量的要求,于是上网查找资料,发现有以下几个开源框架或者方法可以采用(按LZ的个人经验分类,如有不妥,请大家指证):1、Netty、Mina和Grizzly2、Jetty、Tomcat、Apache Server和Nginx3、Thrift、Spring MVC、Spring RESTful和SOA4、RMI、Hessian和Burlap5...

weixin_39604685的博客 743

ardupilot BMI088加速度陀螺仪学习

本节主要记录ardupilot 中BMI088传感器的学习过程,欢迎批评指正。BMI088是一款高性能6轴惯性传感器,由16位数字三轴±24g加速度计和16位数字三轴±2000°/ s陀螺仪组成。 BMI088允许高精度测量方向和沿三个正交轴的运动检测。BMI088具有高振动鲁棒性和3 x 4.5 x0.95mm³的小尺寸,是用于无人机和机器人应用等恶劣环境的高性能IMU中的独特产品。 BMI088专门设计用于有效抑制由于PCB上的共振或整个系统的结构而可能发生的振动。 除了高振动稳定性外,BMI088的出

陌城烟雨 2057

web服务和netty不能一起使用?

web服务和netty不能一起使用?当然是可以一起的,本篇文章介绍在落地这一需求时所遇到的两个问题以及原因和解决方法

cgj296645438的博客 1510

Skills入门学习路线

简单来说,Skills就像是给Claude准备的”插件包"或”工具箱”。它是一个集合,里面装了特定的操作指令(Instructions)、可执行脚本(Scripts)和相关素材(Resources)。2025 年 10 月中旬,Anthropic 正式发布 Claude Skills。两个月后,Agent Skills 作为开放标准被进一步发布,意在引导一个新的 AI Agent 开发生态。OpenAI、Github、VS Code、Cursor 均已跟进。为了更好的理解,你可以把。

十二测试录 1100

STM32芯片解锁读写保护:更改Option Byte [Error已解决]

解锁读保护:STLink更改Option Byte 变Level从1变为0,点击Apply即可。(程序执行读保护,其实是将Level从0变为1) 默认情况下该保护功能是不开启的,若要开启该功能,需要改写内部FLASH选项字节(Option Bytes)中的配置。 RDP读保护级别 级别0,无保护, 级别1,使能读保护, 这种保护下,若使用调试功能(使用下载器、仿真器)或者从内部SRAM自举时都不能对内部FLASH及备份SRAM作任何访问(读写、擦除都被禁止); 当芯片处于级别1的时候,可以把选项字节

HowieXue 薛永浩的博客 2万+

基于Netty手工实现springMVC框架

一、Netty简介 Netty是由JBOSS提供的一个java开源框架。Netty提供异步的、事件驱动的网络应用程序框架和工具,用以快速开发高性能、高可靠性的网络服务器和客户端程序。 也就是说,Netty 是一个基于NIO的客户、服务器端编程框架,使用Netty 可以确保你快速和简单的开发出一个网络应用,例如实现了某种协议的客户、服务端应用。Netty相当于简化和流线化了网络应用的编程开发过程...

不安分的猿人的专栏 6930

4D毫米波雷达:高度信息与微多普勒效应

4D毫米波雷达:高度信息与微多普勒效应​ ,人工智能,计算机视觉,大模型,AI,本文深入解析4D毫米波雷达的高度信息获取与微多普勒效应。4D毫米波雷达通过MIMO技术、垂直天线布局及相位差计算等获取高度信息,相较传统雷达更精准。微多普勒效应由目标微运动引发,可用于目标识别、分类和运动监测。文中结合自动驾驶、安防监控案例,分析技术挑战与未来趋势,如信号干扰、数据处理复杂,未来将向高精度、与其他传感器融合发展,展现出在多领域的巨大应用潜力。

不踩坑,不知道坑有多深;不总结,踩过的坑白踩。 9万+

【IDEA】基于Springboot+Netty实现Web聊天室

目录一、项目创建二、编写代码三、项目运行测试四、总结五、参考 一、项目创建 在IDEA中新建Spring项目 选择JDK版本 选择Spring Web 设置项目名称与位置 创建成功 二、编写代码 导入jar包 下载地址: https://search.maven.org/artifact/com.google.code.gson/gson/2.8.9/jar DemoApplication.Java代码 package com.example.demo; impor

YangMax1的博客 3402

全局注意机制 GAM | Global Attention Mechanism: Retain Information to Enhance Channel-Spatial Interactions

全局注意机制 GAM

Coding... 2580

win10 跳过U8环境检测的IE WebControls.rar

win10 安装u8的时候 提示IE WebControls 不通过,这个工具可以欺骗u8的检查。 跳过U8环境检测的IE WebControls。提示导入失败后,重新检测通过。

java easyrest_EasyRest 一个为快速开发而设计的高性能RESTful框架

EasyRest-NAS中文文档地址:EasyRest integration with Netty, Akka and Spring.This is the high performance RESTful framework, designed for the fast development. Easy to cluster and distributed. You can focus on...

weixin_35476698的博客 470

lwip-2.0.3

lwip是瑞典计算机科学院(SICS)的Adam Dunkels 开发的一个小型开源的TCP/IP协议栈。实现的重点是在保持TCP协议主要功能的基础上减少对RAM 的占用。

Netty框架学习之(一):Netty框架简介

1. 简介 官方定义为:”Netty 是一款异步的事件驱动的网络应用程序框架,支持快速地开发可维护的高性能的面向协议的服务器 和客户端”,按照惯例贴上一张High Level的架构图: 纵观Java系的多种服务器/大数据框架,都离不开Netty做出的贡献,本文对Netty做一个简单的概述 2. 主要特性 Netty有很多重要的特性,主要特性如下: - 优雅的设计 - 统一...

不积跬步,无以至千里;不积小流,无以成江海! 15万+

Data-Structures-and-Algorithm-Analysis-in-Java.pdf

作者: Weiss, Mark A. 出版社: Addison Wesley Data Structures and Algorithm Analysis in Java is an “advanced algorithms” book that fits between traditional CS2 and Algorithms Analysis courses. In the old ACM Curriculum Guidelines, this course was known as CS7. This text is for readers who want to learn good programming and algorithm analysis skills simultaneously so that they can develop such programs with the maximum amo...

大中型超市收银系统(可直接运行)

光步前台收银系统系统功能:商品销售、商品退货、挂单、收银员权限、会员销售、单品取消、整单取消、商品查询、脱机销售、脱机数据上传,基本资料下载,开钱箱、小票打印、收款结算,支持多币种付款方式(现金、储值卡、礼券、微信等),支持多种促销。运行环境:.net 4.0登陆信息用户名:1001 密码:123456内置商品信息,商品编码可输入00001000 - 00010000,测试效果相关功能键F5 结算 F9挂单 F11商品查询 F8退货 Insert单品取消 Del整单取消 F12开钱箱F2 会员 Ctrl+Z退出系统 Home 锁屏 相关源码待公开

Netty框架详解

Netty是一款基于Java NIO的网络编程、高性能、异步事件驱动的网络应用框架。它的设计目标是提供简单易用、高性能、可扩展的网络编程框架。

qq_33807380的博客 1万+

【SpringBoot框架篇】18.使用Netty加websocket实现在线聊天功能

使用netty加websocket实现在线聊天功能

皓亮君的博客 1万+

基于Netty框架的轻量级Web框架

这里写自定义目录标题基于Netty框架开发的HttpWeb轻量框架支持的协议项目地址服务启动流程1.服务启动类:HttpServerLauncher2.服务启动流程:如何使用开发http接口示例: 基于Netty框架开发的HttpWeb轻量框架 使用该框架可以轻松的开发web应用接口,提供了一些类似于Spring框架的注解,例如@Controller, @RequestMapping,@RequestBody,@Param。 支持的协议 支持Http协议和WebSocket协议,另外您可以对该服务器扩展任何

David Liu的博客 1304

Java基于Netty的高性能Web框架TurboWeb

TurboWeb 是一个充分利用JDK 21 虚拟线程特性的现代 Java Web 框架,它在保持同步代码简单易写的同时,致力于提供极高的并发性能。其中间件驱动、插件化的设计也使得它非常灵活和可裁剪。如果你是一个 Java 开发者,希望寻找一个能轻松应对高并发场景、学习成本相对较低、又能享受最新 Java 特性带来红利的 Web 框架,那么 TurboWeb 无疑是一个值得你尝试的选择。

xuweilin的博客 847

【Netty4核心原理④】【简单实现 Tomcat 和 RPC框架功能】

本系列内容为阅读《Netty4 核心原理》一书内容总结,内容存在个人删改,仅做个人笔记使用。

猫吻鱼的博客 1527

java netty 框架_使用 Java 语言基于 Netty 自己动手实现一个极简 web 框架

上节课我们自己动手制作了一个 RPC 框架,本节课我们挑战一个稍有难度的一点的任务,手动制作一个 Web 框架。我不太愿意叫什么 MVC 框架,因为我在制作这个小项目的时候可没想过它要怎么怎么的 MVC,一切皆以易于使用为目标。首先我们看看这个 Web 框架使用起来如何简单Hello Worldimport httpkids.server.KidsRequestDispatcher;import ...

weixin_39790528的博客 235

netty web 容器_Netty 实战:如何编写一个麻小俱全的 web 框架

学习 Netty 也有一段时间了,为了更好的掌握 Netty,我手动造了个轮子,一个基于 Netty 的 web 框架:redant,中文叫红火蚁。创建这个项目的目的主要是学习使用 Netty,俗话说不要轻易的造轮子,但是通过造轮子我们可以学到很多优秀开源框架的设计思路,编写优美的代码,更好的提升自己。PS:项目地址:https://github.com/all4you/redant快速启动Red...

weixin_42524276的博客 178

深入解析Cicada框架:基于Netty的轻量级WEB框架实现

深入解析Cicada框架:基于Netty的轻量级WEB框架实现 【免费下载链接】JCSprout ????‍???? Java Core Sprout : basic, concurrent, algorithm 项目地址: https:/...

gitblog_00430的博客 492
下一篇: Eureka error "java.net.UnknownHostException:
developerxiaofeng
博客等级 码龄9年 2粉丝 5原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值