Eclipse plugins 插件开发学习 - IScanner 和 IBuffer 封装的高级应用类

Eclipse plugins 插件开发学习 - 扩展点介绍 Eclipse 存在很多的扩展点:1. org.eclipse.ui.actionSets                  给 Eclipse 增加主菜单和工具栏 (Workbench Window Actions)             implements IWorkbenchWindowActionDelegate             2. org.eclipse.ui.popup 阅读详情
由于 IScanner 和 IBuffer 修改源码比较麻烦,功能也不是很强大,需要编写大量的代码。

所以这里谢了一个辅助类,来改善代码。

package com.humpic.plugins.helper.utils;

import java.util.ArrayList;
import java.util.List;
import org.apache.commons.lang.StringUtils;
import org.eclipse.jdt.core.*;
import org.eclipse.jdt.core.compiler.IScanner;
import org.eclipse.jdt.core.compiler.InvalidInputException;

public class ObjectScanBuffer {
    
private Offset offset = new Offset();
    
private IBuffer buffer;
    
private IScanner scanner;

    
public ObjectScanBuffer(IType objectClass) {
        
try {
            ICompilationUnit cu 
= objectClass.getCompilationUnit();
            
this.buffer = cu.getBuffer();
            
this.scanner = ToolFactory.createScanner(false, false, false, false);
            
this.scanner.setSource(this.buffer.getCharacters());
            
// 不扫描 package 和 import 部分
            ISourceRange sr = objectClass.getSourceRange();
            
this.scanner.resetTo(sr.getOffset(), sr.getOffset() + sr.getLength() - 1);
        } 
catch (JavaModelException e) {
            
throw new RuntimeException(e);
        }
    }

    
/**
     * 返回下一个 Token
     
*/
    
public int getNextToken() throws InvalidInputException {
        
return this.scanner.getNextToken();
    }

    
/**
     * 查找 Token
     * 
     * 
@param reset
     *            为 true,那么如果没有找到,则会重置 scan 位置
     
*/
    
public boolean findToken(int token, boolean reset) throws InvalidInputException {
        
int[] savepoint = this.savePoint();
        
int t = this.scanner.getNextToken();
        
while (t != token) {
            
if (t <= 0)
                
break;
            t 
= this.scanner.getNextToken();
        }
        
if (t != token && reset) {
            
this.resetPoint(savepoint);
        }
        
return t == token;
    }

    
/**
     * 查找 Token, 如果碰到 stopToken,则停止.
     * 
     * 
@param reset
     *            为 true,那么如果没有找到,则会重置 scan 位置
     
*/
    
public boolean findToken(int token, int stopToken, boolean reset) throws InvalidInputException {
        
int[] savepoint = this.savePoint();
        
int t = this.scanner.getNextToken();
        
while (t != token) {
            
if (t <= 0)
                
break;
            
if (t == stopToken)
                
break;
            t 
= this.scanner.getNextToken();
        }
        
if (t != token && reset) {
            
this.resetPoint(savepoint);
        }
        
return t == token;
    }

    
/**
     * 查找从 startToken 到 endToken 中间的所有 Tokens
     * 
     * 
@param reset
     *            为 true,那么如果没有找到,则会重置 scan 位置 (如果 startToken 找到,重置到 startToken)
     * 
@return new Object[] { new Integer(token), tokenText }
     
*/
    
public List findTokensBetween(int startToken, int endToken, boolean reset) throws InvalidInputException {
        List tokens 
= new ArrayList();
        
if (this.findToken(startToken, reset)) {
            
int[] savepoint = this.savePoint();

            
int t = this.scanner.getNextToken();
            
while (t != endToken) {
                
if (t <= 0)
                    
break;
                tokens.add(
new Object[] { new Integer(t), this.getTokenText() });
                t 
= this.scanner.getNextToken();
            }
            
if (t != endToken) {
                tokens.clear();
                
if (reset) {
                    
this.resetPoint(savepoint);
                }
            }
        }
        
return tokens;
    }

    
/**
     * 在 findTokensBetween 返回中查找 是否存在 token
     
*/
    
public boolean existToken(List tokens, int token) {
        
for (int i = 0; i < tokens.size(); i++) {
            Object[] t 
= (Object[]) tokens.get(i);
            
if (((Integer) t[0]).intValue() == token) {
                
return true;
            }
        }
        
return false;
    }

    
/**
     * 在 findTokensBetween 返回中查找 是否存在 tokenText
     
*/
    
public boolean existToken(List tokens, String tokenText) {
        
for (int i = 0; i < tokens.size(); i++) {
            Object[] t 
= (Object[]) tokens.get(i);
            
if (StringUtils.equals((String) t[1], tokenText)) {
                
return true;
            }
        }
        
return false;
    }

    
/**
     * 返回当前的 Token 文本内容
     
*/
    
public String getTokenText() {
        
return new String(this.scanner.getCurrentTokenSource());
    }

    
/**
     * 返回下一个 Token 文本内容
     
*/
    
public String getNextTokenText() throws InvalidInputException {
        
this.scanner.getNextToken();
        
return this.getTokenText();
    }

    
/**
     * 当前 Token 前面是否存在 空格,或者回车符
     
*/
    
public boolean skipSpaces() {
        
int pos = this.offset.getNewPos(this.scanner.getCurrentTokenStartPosition() - 1);
        
if (pos < 0)
            
throw new RuntimeException("该Token所在的位置已经被删除或者修改过了");
        String s 
= this.buffer.getText(pos, 1);
        
char c = (s.length() == 1 ? s.charAt(0) : '/0');
        
return c == ' ' || c == ' ' || c == ' ';
    }

    
/**
     * 用 text 替换 当前的 Token
     
*/
    
public void replaceText(String text) {
        
int pos = this.offset.getNewPos(this.scanner.getCurrentTokenStartPosition());
        
if (pos < 0)
            
throw new RuntimeException("该Token所在的位置已经被删除或者修改过了");
        
int size = this.scanner.getCurrentTokenEndPosition() - this.scanner.getCurrentTokenStartPosition() + 1;
        
this.buffer.replace(pos, size, text);
        
this.offset.addChange(pos, 0 - size);
        
this.offset.addChange(pos, text.length());
    }

    
/**
     * 在当前 token 位置添加文本
     
*/
    
public void appendText(String text) {
        
this.appendText(text, 0);
    }

    
/**
     * 在距离当前 token 位置Offset距离的地方添加文本(offset可以为负数)
     
*/
    
public void appendText(String text, int offset) {
        
int pos = this.offset.getNewPos(this.scanner.getCurrentTokenStartPosition() + offset);
        
if (pos < 0)
            
throw new RuntimeException("该Token所在的位置已经被删除或者修改过了");
        
int size = 0;
        
this.buffer.replace(pos, size, text);
        
this.offset.addChange(pos, 0 - size);
        
this.offset.addChange(pos, text.length());
    }

    
/**
     * 保存当前位置
     
*/
    
public int[] savePoint() {
        
return new int[] { this.scanner.getCurrentTokenStartPosition(), this.scanner.getCurrentTokenEndPosition() };
    }

    
/**
     * 恢复到保存的位置
     
*/
    
public void resetPoint(int[] savepoint) {
        
this.scanner.resetTo(savepoint[0], savepoint[1]);
    }

    
/**
     * 有2个相同的字符串Buffer,对一个Buffer进行Scan,然后在另外一个Buffer进行修改。
     * 这个类就是将用来Scan的原始Buffer的原始位置,映射到新的被修改过的字符串位置
     
*/
    
private static class Offset {
        
private List changes = new ArrayList();

        
// 在新的位置修改(newsize < 0 代表删除)
        protected void addChange(int newpos, int newsize) {
            
if (newsize == 0)
                
return;
            
this.changes.add(new int[] { newpos, newsize });
            System.out.println(
"changes: " + newpos + " " + newsize);
        }

        
// 从老位置计算新位置 (返回 -1 代表已经被删除)
        protected int getNewPos(int oldpos) {
            
int newpos = oldpos;
            
for (int i = 0; i < this.changes.size(); i++) {
                
int[] pos = (int[]) this.changes.get(i);
                
if (newpos >= pos[0]) {
                    
if (pos[1] < 0) {
                        
if (newpos + pos[1] < pos[0]) {
                            
return -1;// return pos[0]; ??? // 已经被删除
                        }
                    }
                    newpos 
+= pos[1];
                }
            }
            System.out.println(
"pos: " + oldpos + " => " + newpos);
            
return newpos;
        }
    }

    
public static void main(String[] args) {
        String s1 
= "0123456789012345678901234567890123456789012345678901234567890123456789";
        
// "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
        String s2 = s1;

        Offset offset 
= new Offset();
        s2 
= testReplaceText(offset, s2, 4, 5, "@@@");
        s2 
= testAppendText(offset, s2, 10, "%%%%%%");
        s2 
= testReplaceText(offset, s2, 14, 0, "$$");
        s2 
= testAppendText(offset, s2, 32, "######");
        s2 
= testReplaceText(offset, s2, 44, 3, "********");
        s2 
= testReplaceText(offset, s2, 24, 5, "@@@");
        System.out.println(s2);
        System.out.println("result = " + (s2.equals("0123@@@9%%%%%%0123$$4567890123@@@901######234567890123********78901234567890123456789")));
    }

    
private static String testReplaceText(Offset offset, String s, int start, int size, String text) {
        
int pos = offset.getNewPos(start);
        offset.addChange(pos, 
0 - size);
        offset.addChange(pos, text.length());
        
return StringUtils.left(s, pos) + text + StringUtils.right(s, s.length() - pos - size);
    }

    
private static String testAppendText(Offset offset, String s, int start, String text) {
        
int pos = offset.getNewPos(start);
        offset.addChange(pos, text.length());
        
return StringUtils.left(s, pos) + text + StringUtils.right(s, s.length() - pos);
    }
}
 

接下来,我们看一下具体的应用:

描述: 判断一个类是否已经实现 Java.io.Serializable, 如果没有实现,那么加入实现代码

    public void generate(Shell parentShell, IType objectClass) {

        System.out.println(
"AddSerializableGenerator");

        IField existingFields 
= objectClass.getField("serialVersionUID");

        
try {
            
// import
            ICompilationUnit cu = objectClass.getCompilationUnit();
            cu.createImport(
"java.io.Serializable", null, null);

            
// field
            if (!existingFields.exists()) {
                IJavaElement insertPosition 
= null;
                
if (objectClass.getMethods().length > 0)
                    insertPosition 
= objectClass.getMethods()[0];
                
if (objectClass.getFields().length > 0)
                    insertPosition 
= objectClass.getFields()[0];

                String formattedContent 
= "private static final long serialVersionUID = " + RandomStringUtils.randomNumeric(19) + "L;";
                objectClass.createField(formattedContent, insertPosition, 
true, null);
            }

            
// implements
            if (cu.isWorkingCopy()) {
                ObjectScanBuffer sb 
= new ObjectScanBuffer(objectClass);
                List tokens = sb.findTokensBetween(ITerminalSymbols.TokenNameclass, ITerminalSymbols.TokenNameLBRACE, 
false);
                
if (sb.existToken(tokens, "Serializable")) {
                    System.out.println("Serializable interface has been implements");
                    
return;
                }
                
if (sb.existToken(tokens, ITerminalSymbols.TokenNameimplements)) {
                    sb.appendText(", Serializable ");
                } 
else {
                    
if (!sb.skipSpaces())
                        sb.appendText(" ");
                    sb.appendText("implements Serializable "
);
                }
            }
        } 
catch (Exception e) {
            MessageDialog.openError(parentShell,
"Generation Failed", e.getMessage());
        }
    }

国央企创新负责人如何通过平台实现科研成果的快速识别与转化?.docx 科易网基于AI模型+行业大数据,在技术转移、成果转化、技术经纪、知识产权、产业创新、科技招商等垂直领域的多样化应用场景,推出科技创新领域的AI+工具与数智化解决方案,推动科技创新与产业创新智能化发展。 立即下载

相关推荐

PotPlayer自用皮肤

自用 PotPlayer 皮肤,仅微调了字体大小与颜色,是按个人习惯调整,未做适配性调整。

Eclipse plugins 插件开发学习 - 使用 IScanner 扫描 Java 源代码

怎样分析 Java 代码以进行修改?     JDT 提供了几个工具来帮助您分析代码。本文有意选择了最简单的 IScanner 接口进行演示,它的作用域也最有限。这个接口属于 JDT 工具箱,可以通过 JDT 的 ToolFactory 类访问它。其 createScanner 方法返回一个扫描程序,该扫描程序会简化对一串 Java 代码作标记的工作。它不处理任何特别困难的操作,只是对所返回的标记

SUB 的专栏 2249

2-shell及常用命令-续.pdf

2-shell及常用命令-续.pdf

Java 用正则表达式来实现 通配符匹配 (支持*, ?)

    private static boolean wildMatch(String pattern, String str) {        pattern = toJavaPattern(pattern);        return java.util.regex.Pattern.matches(pattern, str);    }    private static String

SUB 的专栏 1万+

一个Android应用层开发如何转型深入Android Framework?

不知何时起,单纯的Android应用层开发越来越少了,主流技术演变也越来越快,从之前的rxjava到如今的协程,从之前MVC到如今的MVVM,还有Flutter、Compose等技术,可以说是日新月异。

m0_64420071的博客 1911

ESP32 WiFi自平衡小车硬件与PID控制实战

自平衡小车是典型的嵌入式实时反馈控制系统,其核心依赖姿态感知(如MPU6050)、电机驱动(如L298N)与闭环控制算法(如PID)的协同。系统通过IMU采集俯仰角,经互补滤波融合处理,再由PID控制器实时计算PWM输出,驱动直流电机产生恢复力矩;WiFi模块(ESP32内置)则提供远程遥控接口,实现AP模式下的Web指令交互。该架构兼具硬实时性(毫秒级采样与响应)与软实时性(HTTP服务),广泛应用于机器人教学、智能小车开发及边缘控制原型验证。本文围绕ESP32平台,详解传感器接口、驱动电路设计、PID抗

weixin_42509513的博客 391

Eclipse plugins 插件开发学习 - 第三方Jar包依赖

程序中需要加入的第三方Jar包,不能自己在 Build Path 中手动加入, 必须使用下面的方法1. 如果第三方 Jar 包属于 eclipse 本身自带的插件之一, 那么可以通过     Dependencies -> Required plugins-ins -> Add        2. 其他的第三方 Jar 包 (包括 Classpath)     Runtime -> Classpa

SUB 的专栏 8502

动态数据脱敏技术分析

在当前国内信息安全热潮中,数据脱敏作为数据安全的重要一环得到了业界的认可与重视。早在2012年,数据脱敏首次作为一个单独的魔力象限由Gartner发布,Gartner在2014年又提出了:按照数据使用场景,将数据脱敏分为静态数据脱敏(Static data masking-SDM )与动态数据脱敏(Dynamic data masking-DDM )。 可能有人望文生义,认为动态数据脱敏一定比静态数据脱敏高级。非也非也,静态or动态,取决于脱敏的使用场景,主要是以使用场景为由来选择合适的数据脱敏的模式。 本

美创科技的博客 4672

Eclipse plugins 插件开发学习 - 在 plugin.xml 中使用资源文件 plugin.properties

在 plugin.xml 中定义的界面菜单和工具栏文本和提示可以存放在 .properties 文件中,常用的为 plugin.properties为了使用 .properties 文件,需要在 META-INF/MANIFEST.MF 文件中定义:      Bundle-Localization: plugin这样就会自动加载 plugin.properties 文件然后在 plugin.xm

SUB 的专栏 7427

保姆级教程:用MediaPipe和BlazePose在Python里实时追踪你的健身动作(附完整代码)

本文提供了一份详细的Python教程,教你如何使用MediaPipe和BlazePose实现实时健身动作追踪。从环境配置、模型选型到实时视频流处理架构设计,再到健身动作量化分析和可视化反馈系统优化,教程涵盖了完整的实现步骤和代码示例,帮助开发者快速构建高效的姿态特征点检测系统。

weixin_42531779的博客 153

【RTOS】 Vxworks 653 第一章 —— 初识 vx653 3.x

。。不是很重要,自己搜吧鲁棒分区是一种确保共享计算资源中独立运行功能实现​​时空隔离​​的机制,其目标是为集成模块中的混合临界性应用提供与联邦式部署(federated implementation)同等级别的功能隔离保障。具体表现为:​​多关键性共存​​:支持不同安全等级应用(如飞控系统A级与娱乐系统D级)在同一模块中协同运行。​​抗干扰能力​​:防止未授权或意外的时空干扰(如内存越界、时间窗口冲突),确保应用间互不影响。​​。

qq_38183694的博客 1280

Java 时区问题

在 Servlet 初始化的时候调用System.setProperty("user.timezone", "GMT+8");然后在其他 action 里面(不同线程)调用 new Date() 得到的时间还是少了8个小时,此时查看 system 环境变量,已经是 GMT+8 了, new Date() 却不生效,奇怪。但是如果在new Date() 前面再加一句 System.setProper

SUB 的专栏 6392

【GNSS数据处理】赫尔默特(helmert)方差分量估计解析及代码实现

一、背景 helmert方差分量估计是方差-协方差分量估计的一个特例,在近现代测量平差中,我们处理的数据有明显的变化,数据序列中包括粗差,数据类型不再单一等问题。当我们遇到数据类型两类及以上时,可以考虑使用赫尔默特方差分量估计,应用的前提条件是不同类别的数据需要相互独立,这样我们的权阵就是对角阵。 二、引入 我们先用王者荣耀这款游戏引入一下,王者可是撩妹利器,哈哈,就像你有两部手机(安卓和苹果),带妹的时候大概率会用苹果,因为游戏体验方面苹果要优于安卓,带妹的时候不掉帧,不卡顿,不闪退,要操作有操作,有皮肤

ANKP的博客 1万+

数据库中的树形结构 - JAVA 设计 (通用)

<!--body, table, tr, td, p { font-size: 14px; }.style2 { font-size: 18px; font-weight: bold;}.style3 {color: rgb(0, 0, 255)}--> 我们通常会在应用中碰到树形结构的内容,比如 文件夹/文件模型, 部门组织结构,目录树等等,通常在设

SUB 的专栏 6021

电力电子技术(13)——大功率可控整流电路

目录引言2.6.1 带平衡电抗器的双反星形可控整流电路电路结构的特点接平衡电抗器的原因平衡电抗器使得两组三相半波整流电路同时导电的原理分析谐波分析2.6.2 多重化整流电路概述原理目标1)移相多重联结2)多重联结电路的顺序控制3重晶闸管整流桥顺序控制带平衡电抗器的双反星形可控整流电路(是多重化整流电路的重要形式之一)的特点:适用于低电压、大电流的场合。多重化整流电路的特点:绕组的极性相反的目的:消除直流磁通势如图所示,虽然两组相电流的瞬时值不同,但是平均电流相等而绕组的极性相反,所以直流安匝(电流*匝数)互

weixin_45589295的博客 1万+

Android 13 内置xapk安装包的方法

安装xapk 内置xapk

安卓兼职framework和app工程师的博客 223

JAVA 进程异常高的 CPU 占用率

<br />问题描述<br />  系统管理员或用户注意到 JAVA 进程消耗大量的 CPU 资源,<br />  并想要了解是哪个方面消耗了大量 CPU 资源,以及导致出现这种现象的原因。 <br /> <br />故障排除<br />  请注意,并非下面所有任务都需要完成。有些问题仅通过执行几项任务就可以解决。<br /> <br />为什么发生此问题?<br />  发生此问题有许多原因:JAVA创建的线程、不良编码习惯或第三方软件。<br />  遗憾的是,证明在什么地方发生此问题有时候非常困难。<

SUB 的专栏 5108

数学建模模型总结

这是本人在两年内准备参加数学建模竞赛搜集到的模型资料,已经经过了本人的筛选和整理,希望能够对准备参加数学建模竞赛的学弟学妹们提供一些帮助!

为 浏览器手动安装 JRE Applet

如果次要运行带有Swing的Applet,总要安装一次JDK(从sun download的),有时机器里竟同时又好几个jdk,什么JBuilder的,Weblogic的,bea server,从sun download的,等等,是不是太多了呢?机器有点乱,其实除了application server 以及其他的Servlet(EJB)container,如果要单独运行jdk,会提高服务器的效率(其

SUB 的专栏 3746

老版QQ原生表情gif动态

这是老版的QQ表情,不过全是动态的的,Windows照片查看器是不会动的,可以右击用浏览器打开查看,再说一遍是老版的,只是分享给宁愿用老版也要动态的人。

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

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

JSP 获得客户端正式IP地址 (非代理服务器)

    public static String getIpAddr(HttpServletRequest request) {        String ip = request.getHeader("X-Forwarded-For");        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip))

SUB 的专栏 3116

蓝牙模块HM-10手册

蓝牙模块HM-10手册,该手册对蓝牙模块HM-10进行了详细的介绍和使用方法以及一些AT指令。

SVN for Eclipse plugins 中文转英文 (新方法)

新方法:修改eclipseconfigurationconfig.ini文件,添加:osgi.nl=en_US=====================================================旧方法:在 eclipse/plugins/目录下,找到  1. org.tigris.subversion.subcl

SUB 的专栏 3073

全面超越 Velocity 的全新一代 Java 模板引擎 jetbrick-template 1.0.2 正式版发布。

jetbrick-template 是一个新一代 Java 模板引擎,具有高性能和高扩展性。 适合于动态 HTML 页面输出或者代码生成,可替代 JSP 页面或者 Velocity 等模板。 指令和 Velocity 相似,表达式和 Java 保持一致,易学易用。 支持类似与 Velocity 的多种指令 支持静态编译 支持编译缓存 支持热加载 支持类型推导 支持泛型 支持可变参数方法调用 支持方法重载 支持类似于 Groovy 的方法扩展 支持函数扩展

SUB 的专栏 3012

新一代Java模板引擎 jetbrick-template 1.1.2 发布

jetbrick-template 是一个新一代 Java 模板引擎,具有高性能和高扩展性。 适合于动态 HTML 页面输出或者代码生成,可替代 JSP 页面或者 Velocity 等模板。 指令和 Velocity 相似,表达式和 Java 保持一致,易学易用。 支持类似与 Velocity 的多种指令 支持静态编译 支持编译缓存 支持热加载 支持类型推导 支持泛型 支持可变参数方法调用 支持方法重载 支持类似于 Groovy 的方法扩展 支持函数扩展 支持自定义标签 #tag 支持宏定义 #mac

SUB 的专栏 2791

让Java程序自带JRE

用Java开发程序,发布时总要考虑的问题就是怎么在使用者的机器上装好JRE。要考虑的问题很多:使用者有没有能力独自安装JRE,使用者已有的JRE和我们需要的版本是不是一致,会不会出现版本问题,等等。使用.NET要考虑的问题就少些。现在.NET CLR似乎已经很普及了,看好多D版的WinXP都会自己安装最新的.NETCLR,而且似乎它的安装界面也比JRE友好些。彻底解决安装JRE的问题的

SUB 的专栏 2766

dom4j 的 DTD 验证,以及输出乱码问题解决方案

正常的 dom4j 读取 xml 的方式如下: SAXReader reader = new SAXReader();reader.setEntityResolver(resolver);Document doc = reader.read(resource.getInputStream());Element root = doc.getRootElement();

SUB 的专栏 2752

Apache Commons-configuration 范例

configuration.xml:xml version="1.0" encoding="UTF-8"?>configuration>    header>        result forceReloadCheck="true">result>    header>    override>        system />        properties fileName="$

SUB 的专栏 2610

SCA:一、eclipse中搭建Spring Boot4 + SCA 2025.1.0.0 + Nacos + OpenFeign 完整 Demo

本文基于Spring Boot 4与Spring Cloud Alibaba 2025.1.0,演示如何使用Nacos实现服务注册与Feign远程调用。通过创建Maven父子工程,配置Nacos注册中心,分别开发提供者(nacos-provider)和消费者(nacos-consumer)模块,利用Feign声明式接口完成跨服务调用。启动后访问/call/jangle即可验证服务间通信,完整展示了微服务架构下的服务发现与负载均衡实践。

博风_刺耳的博客(网络记事本) 271
上一篇: Eclipse plugins 插件开发学习 - 使用 IScanner 扫描 Java 源代码
下一篇: ClassLoader 学习 - MyURLClassLoader
subchen
博客等级 码龄25年 33粉丝 77原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值