主动拉取融云聊天数据项目总结

目录

一、融云聊天为啥会丢失消息和消息同步不及时

二、项目代码知识点整理:

1、wsClinet请求

2、按字节读取文件的方法

3、java文件分割符之pathSeparator、 pathSeparatorChar、 separator、 separatorChar区别:

4、FileUtil文件操作工具类

5、复制文件

6、把字符串写入文件

7、把字节数组写入文件

8、把集合里面的内容写入文件

9、往文件里面写内容

10、文件移动

11、清空和删除文件夹

12、创建文件夹

13、文件获取输入/输出流

14、读取文件

15、其他

三、正则解析

四、java集合三种类型:Set<集>、List<列表>、Map<映射>

1、 Set 

2、List 

3、Map 

五、判空函数

六、MySql避免重复插入记录方法

1、使用ignore关键字

2、使用replace

3、使用ON DUPLICATE KEY UPDATE

七、mysql增加、查看、删除唯一约束

八、Ebean相关

1、Ebean中的相关映射:

2、查询单个结果

3、查询多个结果

4、使用select()和fetch()

5、数据库保存与删除

6、Transactions

九、时间日期运算

1、Date日期转换为指定的String格式

2、关于年月日小时的前后加减(下例为一小时前)

十、项目思路:

十一、项目问题总结:


一、融云聊天为啥会丢失消息和消息同步不及时

融云聊天丢失消息和消息同步不及时可能与以下几个因素有关:

  1. 网络连接问题:消息传输需要依赖网络连接,如果网络不稳定或存在延迟,可能导致消息丢失或同步延迟。网络连接差、丢包率高等问题都可能影响消息的传输和同步。

  2. 客户端异常:如果使用的聊天客户端存在bug或者异常情况,可能导致消息的处理出现问题。例如,客户端在发送消息时崩溃或关闭,导致消息丢失;或者客户端接收消息的逻辑有问题,导致消息同步不及时。

  3. 服务器故障:聊天平台的服务器可能遭遇故障或维护,在这种情况下,消息的传输和同步可能会受到影响。服务器故障可能导致消息丢失或同步延迟。

  4. 异步消息处理:聊天平台通常采用异步消息处理机制,即消息发送后不会立即被接收方收到,而是经过一系列的处理后再进行推送。这个过程可能会引起消息同步的延迟。

为了解决消息丢失和同步延迟的问题,可以考虑以下方法:

  1. 提供稳定的网络环境:确保用户在使用聊天功能时有稳定的网络连接,避免网络抖动和延迟。

  2. 客户端异常处理:及时修复聊天客户端可能存在的bug,并确保客户端正常运行,避免崩溃或异常情况。

  3. 监控和维护服务器:对聊天平台的服务器进行监控和维护,及时发现并解决潜在的故障或问题。

  4. 优化消息传输和处理机制:优化聊天平台的消息传输和处理机制,提高消息的同步速度和可靠性。

需要注意的是,不同的聊天平台和技术实现方式可能有不同的原因导致消息丢失和同步延迟。具体的解决方法需要根据实际情况进行分析和调整。

简单的说:

目的需求:主动拉取服务器数据流程,补充因服务器挂掉未收到推送数据

除了硬件和运维方面的维护,我们也提供一个主动拉去的入口,作为我们可控的按钮

二、项目代码知识点整理:

1、wsClinet请求

(链接:https://www.playframework.com/documentation/2.6.x/JavaWS

1.1、建立http请求路径

WSRequest request = ws.url("http://example.com");

1.2、设置请求头

ws.url(url).addHeader("headerKey", "headerValue").get();

1.3、请求身份验证

ws.url(url).setAuth("user", "password", WSAuthScheme.BASIC).get();

1.4、请求参数

ws.url(url).addQueryParameter("paramKey", "paramValue");

1.5.请求格式类型和内容

ws.url(url).addHeader("Content-Type", "application/json").post(jsonString);

// OR

ws.url(url).setContentType("application/json").post(jsonString);

1.6、提交数据

(1)提交json数据

JsonNode json = Json.newObject()

                    .put("key1", "value1")

                    .put("key2", "value2");

(2)表单数据

ws.url(url).setContentType("application/x-www-form-urlencoded")

           .post("key1=value1&key2=value2");

ws.url(url).post(json);

//OR

ObjectMapper objectMapper = play.libs.Json.newDefaultMapper();

ws.url(url).post(body(json, objectMapper));

(3)XML数据

Document xml = play.libs.XML.fromString("<document></document>");

ws.url(url).post(xml);

(4)Stream数据

CompletionStage<WSResponse> wsResponse = ws.url(url).setBody(body(largeImage)).execute("PUT");

1.7、请求超时

ws.url(url).setRequestTimeout(Duration.of(1000, ChronoUnit.MILLIS)).get();

1.8、响应过程

(1)json数据响应

// implements WSBodyReadables or use WSBodyReadables.instance.json()

CompletionStage<JsonNode> jsonPromise = ws.url(url).get()

        .thenApply(r -> r.getBody(json()));

(2)XML数据响应

// implements WSBodyReadables or use WSBodyReadables.instance.xml()

CompletionStage<Document> documentPromise = ws.url(url).get()

        .thenApply(r -> r.getBody(xml()));

2、按字节读取文件的方法

2.1、读取的body为数组array:

byte[] asByteArray

2.2、读取的body为字符串String:

byte[] asString

2.3、读取的body为数据流Stream:

InputStream asInputStream

3、java文件分割符之pathSeparator、 pathSeparatorChar、 separator、 separatorChar区别:

在Java中,有四个常量可以用于在操作系统中分隔不同的文件路径,这些常量是:

  1. pathSeparator - 表示路径分隔符的字符串。在Windows系统中,路径分隔符为";",在Unix/Linux系统中,路径分隔符为":"。这个常量是一个静态变量,以String类型存储。

  2. pathSeparatorChar - 表示路径分隔符的字符。在Windows系统中,路径分隔符为";",在Unix/Linux系统中,路径分隔符为":"。这个常量是一个静态变量,以char类型存储。

  3. separator - 表示文件分隔符的字符串。在Windows系统中,文件分隔符为"",在Unix/Linux系统中,文件分隔符为"/"。这个常量是一个静态变量,以String类型存储。

  4. separatorChar - 表示文件分隔符的字符。在Windows系统中,文件分隔符为"",在Unix/Linux系统中,文件分隔符为"/"。这个常量是一个静态变量,以char类型存储。

简单来说,pathSeparator和pathSeparatorChar用于分隔不同的路径,而separator和separatorChar用于分隔文件名和路径。这些常量的使用可以帮助程序员编写可移植的代码,因为它们能够适应不同的操作系统。

4、FileUtil文件操作工具类

Java IO流学习总结七:Commons IO 2.5-FileUtils_赵彦军的博客-CSDN博客

1、复制文件夹(文件夹里面的文件内容也会复制)

(1)file1和file2平级

void copyDirectory(file1, file2);

(2)file1是file2的子文件夹

void copyDirectoryToDirectory( file1 , file2 );

(3)带有文件过滤功能

void copyDirectory(File srcDir, File destDir, FileFilter filter)

5、复制文件

(1)复制文件到另一个文件

void copyFile(final File srcFile, final File destFile)

(2)复制文件到输出流

void long copyFile(final File input, final OutputStream output)

(3)复制文件到一个指定的目录

void copyFileToDirectory( file1 , file2)

(4)把流里面的内容复制到指定文件

void copyInputStreamToFile( InputStream source, File destination)

(5)把URL 里面内容复制到文件。可以下载文件。

参数1:URL资源 ; 参数2:目标文件

void copyURLToFile(final URL source, final File destination)

(6)把URL 里面内容复制到文件。可以下载文件。

参数1:URL资源 ; 参数2:目标文件;参数3:http连接超时时间 ; 参数4:读取超时时间

void copyURLToFile(final URL source, final File destination,

                                     final int connectionTimeout, final int readTimeout)

6、把字符串写入文件

void writeStringToFile(final File file, final String data, final String encoding)

//参数1:需要写入的文件,如果文件不存在,将自动创建。  参数2:需要写入的内容

//参数3:编码格式     参数4:是否为追加模式( ture: 追加模式,把字符串追加到原内容后面)

void writeStringToFile(final File file, final String data, final Charset encoding, final boolean

            append)

7、把字节数组写入文件

//File:目标文件

//byte[]: 字节数组

//boolean append : 是否为追加模式

//final int off: 数组开始写入的位置 ; final int len :写入的长度



void writeByteArrayToFile(final File file, final byte[] data)



void writeByteArrayToFile(final File file, final byte[] data, final boolean append)



void writeByteArrayToFile(final File file, final byte[] data, final int off, final int len)



void writeByteArrayToFile(final File file, final byte[] data, final int off, final int len,

                                            final boolean append)

8、把集合里面的内容写入文件

//File file: 目标文件

//Collection<?> lines: 内容集合

//boolean append : 是否为追加模式

//String encoding : 编码方式,比如"UTF-8" 

//String lineEnding : 每一行以什么结尾

void writeLines(final File file, final Collection<?> lines)



void writeLines(final File file, final Collection<?> lines, final boolean append)



void writeLines(final File file, final String encoding, final Collection<?> lines)



void writeLines(final File file, final String encoding, final Collection<?> lines,

                                  final boolean append)



void writeLines(final File file, final String encoding, final Collection<?> lines,

                                  final String lineEnding)



void writeLines(final File file, final String encoding, final Collection<?> lines,

                                  final String lineEnding, final boolean append)



void writeLines(final File file, final Collection<?> lines, final String lineEnding)



void writeLines(final File file, final Collection<?> lines, final String lineEnding,

                                  final boolean append)

9、往文件里面写内容

/**

* 参数解释

* File file:目标文件

* CharSequence data : 要写入的内容

* Charset encoding;String encoding : 编码格式

* boolean append:是否为追加模式

*/

void write(final File file, final CharSequence data, final Charset encoding)



void write(final File file, final CharSequence data, final String encoding)



void write(final File file, final CharSequence data, final Charset encoding, final boolean append)



void write(final File file, final CharSequence data, final String encoding, final boolean append)

10、文件移动

(1)文件夹移动,文件夹在内的所有文件都将移动 

void moveDirectory(final File srcDir, final File destDir)

(2)文件夹移动到另外一个文件内部。

boolean createDestDir:如果destDir文件夹不存在,是否要创建一个

void moveDirectoryToDirectory(final File src, final File destDir, final boolean createDestDir)

(3)移动文件

void moveFile(final File srcFile, final File destFile)

(4)把文件移动到另外一个文件内部。

boolean createDestDir:如果destDir文件夹不存在,是否要创建一个

void moveFileToDirectory(final File srcFile, final File destDir, final boolean createDestDir)

(5)移动文件或者目录到指定的文件夹内。

boolean createDestDir:如果destDir文件夹不存在,是否要创建一个

void moveToDirectory(final File src, final File destDir, final boolean createDestDir)

11、清空和删除文件夹

(1)删除一个文件夹,包括文件夹和文件夹里面所有的文件

void deleteDirectory(final File directory)

(2)清空一个文件夹里面的所有的内容

void cleanDirectory(final File directory)

(3)删除一个文件,会抛出异常

如果file是文件夹,就删除文件夹及文件夹里面所有的内容。如果file是文件,就删除。

如果某个文件/文件夹由于某些原因无法被删除,会抛出异常

void forceDelete(final File file)  

(4)删除一个文件,没有任何异常抛出

如果file是文件夹,就删除文件夹及文件夹里面所有的内容。如果file是文件,就删除。

如果某个文件/文件夹由于某些原因无法被删除,不会抛出任何异常

boolean deleteQuietly(final File file) 

12、创建文件夹

(1)创建一个文件夹,如果由于某些原因导致不能创建,则抛出异常

一次可以创建单级或者多级目录

void forceMkdir(final File directory)

(2)创建文件的父级目录

void forceMkdirParent(final File file)

13、文件获取输入/输出流

(1)获取输入流

FileInputStream openInputStream(final File file)

(2)获取输出流

FileOutputStream openOutputStream(final File file)

14、读取文件

(1)把文件读取到字节数组里面

byte[] readFileToByteArray(final File file)

(2)把文件读取成字符串 ;Charset encoding:编码格式

String readFileToString(final File file, final Charset encoding)

(3)把文件读取成字符串 ;String encoding:编码格式

String readFileToString(final File file, final String encoding)

(4)把文件读取成字符串集合 ;Charset encoding:编码格式

List<String> readLines(final File file, final Charset encoding)

(5)把文件读取成字符串集合 ;String encoding:编码格式

List<String> readLines(final File file, final String encoding)

15、测试两个文件的修改时间哪个比较新/老

(1)判断file文件的修改是否比reference文件新

boolean isFileNewer(final File file, final File reference) 

(2)判断file文件的修改是否比 date日期新

boolean isFileNewer(final File file, final Date date)

(3)判断file文件的修改是否比 timeMillis 毫秒值新

boolean isFileNewer(final File file, final long timeMillis) 

(4)判断file文件的修改是否比reference文件老

boolean isFileOlder(final File file, final File reference)

(5)判断file文件的修改是否比 date日期老

boolean isFileOlder(final File file, final Date date)

(6)判断file文件的修改是否比 timeMillis 毫秒值老

boolean isFileOlder(final File file, final long timeMillis)

15、其他

(1)判断文件夹内是否包含某个文件或者文件夹

boolean directoryContains(final File directory, final File child)

(2)获取文件或者文件夹的大小

long sizeOf(final File file) 

(3)获取临时目录文件

File getTempDirectory()

(4)获取临时目录路径

String getTempDirectoryPath() 

(5)获取用户目录文件  

File getUserDirectory()

(6)获取用户目录路径  

static String getUserDirectoryPath()

(7)如果不存在,新建文件或者创建单级目录或者多级目录    

如果存在,修改文件修改时间   

void touch(final File file)

(8)比较两个文件内容是否相同

boolean contentEquals(final File file1, final File file2)

三、正则解析

(1)正则表达式介绍:正则表达式_百度百科

正则表达式(Regular Expression)是一种用来描述、匹配和搜索文本串的模式。它是由一些字符和特殊字符组成的字符串,可以用来匹配特定模式的文本。正则表达式在计算机科学领域被广泛使用,例如在搜索引擎、文本编辑器、编译器、数据库查询、数据验证等方面,都可以用正则表达式来进行文本的匹配和处理。

在正则表达式中,字符和特殊字符有不同的含义,例如字母、数字和空格等字符代表它们本身,而特殊字符则有特殊含义,例如 ^ 表示匹配字符串的开头,$ 表示匹配字符串的结尾,* 表示匹配前面的字符0次或多次,+ 表示匹配前面的字符1次或多次,? 表示匹配前面的字符0次或1次,. 表示匹配任意一个字符,[] 表示匹配括号中的任意一个字符,{} 表示匹配前面的字符出现的次数等等。

一个正则表达式是由一些字符和特殊字符组成的字符串,并被编译成一个模式。这个模式可以用来匹配一个文本串中是否符合某个规则。例如,一个电话号码的正则表达式可以匹配所有符合电话号码格式的文本串,从而实现电话号码的验证和过滤等功能。

 (2)在线正则表达式测试:在线正则表达式测试

(3)string.replace()和string.replaceAll的区别:

在replace()中, 传入的两个参数 均表示普通的字符串, 即用第二个参数表示的字符串将对象(字符串对象)中和第一个参数表示的字符 串相同的部分替换掉。

replaceAll()中,第一个参数并不是普通意义上的字符串, 而是一个正则表达式; 意思是将对象中与第一个参数所表示的正则表达式相匹 配的地方用第二个参数所表示的字符串替换.

例如:

public class ReplaceAllTest {

public static String s = "acdabcdadc";

public static void main(String[] args) {

System.out.println(s.replace("a", "A"));

System.out.println(s.replaceAll("[abcd]", "[A]"));

}

}

输出: 

AcdAbcdAdc
[A][A][A][A][A][A][A][A][A][A]

从输出结果可以看出, replaceAll()中第一个参数表示的是正则表达式, 意思是: 包含a,b,c,d的任何字符, 因此 对象s 中的所有字符都被替换成了 [A], 而第二个参数就代表普通的字符串了。

四、java集合三种类型:Set<>List<列表>Map<映射>

(参考链接 Java集合三种类型:Set<集>、List<列表>、Map<映射>_YDSimons的博客-CSDN博客

1、 Set 

集(set)是最简单的一种集合,它的对象不按特定方式排序,只是简单的把对象加入集合中,就像往口袋里放东西。对集中成员的访问和操作是通过集中对象的引用进行的,所以集中不能有重复对象。我们知道数学上的集合也是Set这个,集合里面一定是没有重复的元素的。 

2、List 

列表(List)的主要特征是其对象以线性方式存储,没有特定顺序,只有一个开头和一个结尾,当然,它与根本没有顺序的Set是不同的。它是链表嘛,一条链肯定有顺序这个顺序就不一定了。

(1)例如:此项目中MsgUid为信息的唯一标识,为了增加效率,单独把ChatMessage列表中获得的 MsgUid映射成一个新表,作为判断是否需要补漏的查询依据字典集

List<String> messageIds = newChatMessageList.stream().map(ChatMessage::getMsgUid).collect(Collectors.toList());

(2)库通过定义的类的字段去数据库寻找该字段所有数据生成新表

final List<String> rcBaseMessageList = Ebean.find(RcBaseMessage.class).select("msgUid").findSingleAttributeList();

取rcBaseMessageList表和messageIds得交集值

rcBaseMessageList.retainAll(messageIds);

messageIds去除交集值,即本项目中的新增MsgUid集

messageIds.removeAll(rcBaseMessageList);

(3)定义一个新的长度可以改变的数组,可以对元素随机访问

List<ChatMessage> newChatMessageList = new ArrayList<>();

将ChatMessage表中解析的对象通过遍历依次加入新表

arrayNode.forEach(jsonNode -> newChatMessageList.add(new ChatMessage(jsonNode)));

3、Map 

映射(Map),这个在java里不是地图的意思,其实地图也是映射哈。它里面的东西是键-值对(key-value)出现的,键值对是什么呢?举个例子,比如我们查字典,用部首查字法。目录那个字就是键,这个字的解释就是值。键和值成对出现。这样说可以理解吧。这也是很常用的数据结构哦。

五、判空函数

1、判断对象是否为空

StringUtils.isEmpty(Object str)

2、判断列表是否为空

CollectionUtils.isEmpty(newChatMessageList)

3、判断字符串数据是否为空

StringUtils.isBlank(chatData)

六、MySql避免重复插入记录方法

(ignore,Replace,ON DUPLICATE KEY UPDATE)(链接:http://blog.sae.sina.com.cn/archives/3491

1、使用ignore关键字

如果用主键用primary或者唯一索引unique区分了记录的唯一性,避免重复插入记录可以使用

INSERT IGNORE INTO `table_name` (`email`, `phone`, `user_id`) 

VALUES ('test9@163.com', '99999', '9999');

复制表,避免重复记录

INSERT IGNORE INTO `table_1` (`name`) SELECT `name` FROM `table_2`;

2、使用replace

REPLACE INTO `table_name`(`col_name`, ...) VALUES (...);

REPLACE INTO `table_name` (`col_name`, ...) SELECT ...;

REPLACE INTO `table_name` SET `col_name`='value',

算法说明:
REPLACE的运行与INSERT很相像,但是如果旧记录与新记录有相同的值,则在新记录被插入之前,旧记录被删除,即:

尝试把新行插入到表中
当因为对于主键或唯一关键字出现重复关键字错误而造成插入失败时:
从表中删除含有重复关键字值的冲突行
再次尝试把新行插入到表中
旧记录与新记录有相同的值的判断标准就是:
表有一个PRIMARY KEY或UNIQUE索引,否则,使用一个REPLACE语句没有意义。该语句会与INSERT相同,因为没有索引被用于确定是否新行复制了其它的行。

返回值:
REPLACE语句会返回一个数,来指示受影响的行的数目。该数是被删除和被插入的行数的和
受影响的行数可以容易地确定是否REPLACE只添加了一行,或者是否REPLACE也替换了其它行:检查该数是否为1(添加)或更大(替换)。

3、使用ON DUPLICATE KEY UPDATE

你也可以在INSERT INTO…..后面加上 ON DUPLICATE KEY UPDATE方法来实现。如果您指定了ON DUPLICATE KEY UPDATE,并且插入行后会导致在一个UNIQUE索引或PRIMARY KEY中出现重复值,则执行旧行UPDATE。

例如,如果列a被定义为UNIQUE,并且包含值1,则以下两个语句具有相同的效果:

INSERT INTO `table` (`a`, `b`, `c`) VALUES (1, 2, 3) 

ON DUPLICATE KEY UPDATE `c`=`c`+1; 

UPDATE `table` SET `c`=`c`+1 WHERE `a`=1;

其实就是以唯一键为标识过滤,相同则不更新表,不同则插入表数据

七、mysql增加、查看、删除唯一约束

1、增加唯一性约束

alter table tableName add unique(column_name);

2、查看唯一性约束

Show keys from tableName;

3、删除唯一性约束

drop index key_name on tableName;

八、Ebean相关

1、Ebean中的相关映射:

Ebean使用了和JPA一样的映射,所以你可以使用@Entity, @Table, @Column, @OneToMany等等对你的实体进行注释

2、查询单个结果

B_Customer customer = Ebean.find(B_Customer.class, 4);

等同于sql:

select t0.id c0, t0.uuid c1, t0.name c2, t0.version c3 from b_customer t0 where t0.id = 4

3、查询多个结果

List<B_Customer> customers = Ebean.find(B_Customer.class)

                                .where().like("name", "%a%")

                                .orderBy("name desc") 

                                .findList();

等同于sql:

select t0.id c0, t0.uuid c1, t0.name c2, t0.version c3 from b_customer t0 where t0.name like '%a%'  order by t0.name desc

4、使用select()和fetch()

List<B_Order> orderList =Ebean.find(B_Order.class).select("id").where().eq("status",B_Order.STATUS_ACTIVE).findList();

等同于sql:

select t0.id c0 from b_order t0 where t0.status = 1



List<B_Order> orderList =Ebean.find(B_Order.class).select("id")

                            .fetch("customer","name")

                            .where().eq("status",B_Order.STATUS_ACTIVE)

                            .findList();

等同于sql:

select t0.id c0, t1.id c1, t1.name c2 from b_order t0 left outer join b_customer t1 

on t1.id = t0.customer_id  where t0.status = 1

5、数据库保存与删除

(链接:Ebean-功能介绍_ebean 工作原理_EvolutionJJ的博客-CSDN博客 andEbeanServer (Ebean 2.7.3 API) - Javadoc Extreme

Order order = Ebean.find(Order.class, 12);

order.price = 2;

Ebean.save(order);    



Order order = Ebean.find(Order.class, 12);    

Ebean.delete(order);    

6、Transactions

Ebean.beginTransaction();

try {

    ...

    Ebean.save(customer);

    Ebean.save(order);

    Ebean.commitTransaction();

} finally {

    Ebean.endTransaction();

}

九、时间日期运算

1、Date日期转换为指定的String格式

Date date =new Date();

String dateString = DateFormatUtils.format(date, "yyyyMMddHH");

2、关于年月日小时的前后加减(下例为一小时前)

Calendar c = Calendar.getInstance();

c.setTime(date);

c.add(c.HOUR_OF_DAY, -1);

Date time = c.getTime();

十、项目思路:

融云对接文档:融云开发者文档

建立一个actor做定时任务(由于10点产生数据11点才能拉取,故定1h定时拉取一次,且是从当前时间起拉取前一个小时数据)

1、计算签名,配置请求头和请求体

2、将数据拉取下来,若code返回200且有消息记录,则url会得到一个消息记录zip压缩包

3、将压缩包下载后按字节写入建好的默认路径的固定zip文件中,并每次都删除该路径下的文件

4、对zip文件进行解压,并存入默认路径下

5、解压后文件都是以传入的date命名,故依据此特点去读文件并转化为字符串类型数据

6、用正则表达式将字符串数据替换成标准数组再解析成jsonNode格式

7、用rc_base_message表中的msg_uid作为唯一键,若与zip文件获取的msg_uid不相等则更新rc_trade_message列表数据,否则不更新(rc_base_message.msg_uid=rc_trade_message.base_msg_uid)

十一、项目问题总结:

1、数据全局和局部定义问题,会变化的数据不能设置成全局的,以免服务器启动后值不变

2、请求体格式问题

3、zip文件下载到通用指定路径问题,防止通用路径下存储zip包过多问题

4、读取解压后的变动的文件名文件问题,要如何读取到并存放到指定路径问题

5、非正规json格式数据解析问题

6、数据库根据唯一键去重更新数据库问题

7、Ebean.find(RcBaseMessage.class).select(“var”)的var问题不是数据库表中的变量名,是定义类中的变量名,否则会一直报找不到该var

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

进击的雷神

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值