59、SpringBoot 自定义JSON的序列化器和反序列化器

Serialization(序列化): 将java对象以一连串的字节码保存在磁盘文件中的过程,也可以说是保存java对象状态的过程。序列化可以将数据永久保存在磁盘上(通常保存在文件中)。

deserialization(反序列化): 将保存在磁盘文件中的java字节码重新转换成java对象称为反序列化。

★ 自定义JSON的序列化器和反序列化器

▲ 注册自定义序列化器和反序列化器有两种方式:- 方式1:利用Jackson的模块机制来注册自定义序列化器和反序列化器。- 方式2:利用Spring Boot提供的@JsonComponent来注册自定义序列化器和反序列化器。第一种方式是Jackson原生的注册方式,一般不推荐。推荐使用第二种方式。

★ 使用@JsonComponent注册序列化器或反序列化器

Spring Boot提供了@JsonComponent注解来注册自定义自定义序列化器和反序列化器,
该注解有两种使用方式:

-方式1: 直接使用 @JsonComponent 注解修饰JsonSerializer、JsonDeserializer或KeyDeserializer实现类,这些实现类将会由Spring Boot注册为自定义JSON序列化器和反序列化器。@JsonComponent 注解修饰 JsonSerializer,就是自定义 JSON 序列化器@JsonComponent 注解修饰 JsonDeserializer,就是自定义 JSON 反序列化器-方式2:使用 @JsonComponent 修饰包含JsonSerializer/JsonDeserializer内部实现类的外部类,这些内部实现类将会由Spring Boot注册为自定义JSON序列化器和反序列化器。这种方式相当于将序列化器和反序列化器都定义在一个外部类中,这种方式具有更好的内聚性,通常推荐用方式2。

内部类( Inner Class )就是定义在另外一个类里面的类。与之对应,包含内部类的类被称为外部类

Token定义
Token是Fastjson中定义的json字符串的同类型字段,即"{“、”["、数字、字符串等,用于分隔json字符串不同字段。
例如,{“姓名”:“张三”,“年龄”:“20”}是一个json字符串,在反序列化之前,需要先将其解析为
{ 、 姓名、 :、 张三、 ,、 年龄、 :、 20、 }这些字段的Token流,随后再根据class反序列化为响应的对象。
在进行Token解析之前,json字符串对程序而言只是一个无意义的字符串。需要将json字符串解析为一个个的Token,并以Token为单位解读json数据。

代码演示:

自定义序列化和反序列化器的代码

package cn.ljh.app.custom;import cn.ljh.app.domain.Book;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import lombok.SneakyThrows;
import org.springframework.beans.BeanUtils;
import org.springframework.boot.jackson.JsonComponent;import java.io.IOException;//内部类就是定义在另外一个类里面的类。与之对应,包含内部类的类被称为外部类,这个 CustomSeDe 就是外部类
//使用 @JsonComponent 修饰包含JsonSerializer/JsonDeserializer内部实现类的外部类,
//这些内部实现类将会由Spring Boot注册为自定义JSON序列化器和反序列化器。
@JsonComponent
public class CustomSeDe
{//在该类中可定义自定义的序列化器和反序列化器//postman 用get请求时走这个方法//自定义序列化器,这个就是内部类public static class MySerializer extends JsonSerializer<Book>{@Overridepublic void serialize(Book book, JsonGenerator jsonGenerator,SerializerProvider serializerProvider) throws IOException{//在该方法中完成自定义的序列化输出,将对象输出成 json 或者是 xml 字符串jsonGenerator.writeStartObject(); //输出 {  这个花括号 , 输出对象用这个括号
//            jsonGenerator.writeStartArray();  //输出 [ 这个中括号  , 输出数值用这个括号jsonGenerator.writeNumberField("id", book.getId());//将book对象的name属性,序列化为title 属性jsonGenerator.writeStringField("title", book.getName());jsonGenerator.writeNumberField("price", book.getPrice());jsonGenerator.writeStringField("author", book.getAuthor());
//            jsonGenerator.writeEndArray();   // 输出这个 ] 中括号jsonGenerator.writeEndObject();  //输出这个 } 花括号}}//postman 用post的请求访问到这个方法//自定义反序列化器,这个就是内部类public static class MyDeserializer extends JsonDeserializer<Book>{//这个方法负责反序列化,就是将 json 或者是 xml 字符串 恢复成对象@SneakyThrows@Overridepublic Book deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException{Book book = new Book();//JsonToken采用顺序方式依次读取 JSON 或 XML 字符串中的每个 token//token:一个花括号也叫token,一个符号隔开一个内容就叫tokenJsonToken jsonToken = jsonParser.getCurrentToken();//只要这个 Json 或者 xml 字符串没有结束,都应该继续向下读取while (!jsonToken.equals(JsonToken.END_OBJECT)){//这个循环的作用,是去保证要找到代表 Field Name 的 Token//如果当前读取的Token 不是字段名if (!jsonToken.equals(JsonToken.FIELD_NAME)){jsonToken = jsonParser.nextToken();//读取下一个tokencontinue;}//只要程序进到此处,就一定是读取到 field name 这种字段token//从 JSON 或者是 XML 字符串中读取得到的字段名,也就是Book 对象的属性名String fieldName = jsonParser.getCurrentName();//读取属性名之后,下一个Token就是属性值了JsonToken valueToken = jsonParser.nextToken();//获取当前token的值String value = jsonParser.getText();//对于要逻辑处理的属性,可以在这里进行判断处理if (fieldName.equals("title")){if (!value.startsWith("《")){value = "《" + value;}if (!value.endsWith("》")){value += "》";}//完成了对Book对象的name属性的设置book.setName(value);} else if (fieldName.equals("price")){double price = Double.parseDouble(value);//把从json或xml格式的价格数据拿出来,然后打折set进去book.setPrice(price * 0.5);} else{//对于其他属性,不需要额外处理的话,直接通过反射来调用Book对象的fieldName对应的setter方法BeanUtils.getPropertyDescriptor(Book.class, fieldName).getWriteMethod().invoke(book, value);}//继续向下读取下一个 TokenjsonToken = jsonParser.nextToken();}return book;}}}

测试:

序列化输出,将对象输出成 json 或者是 xml 字符串
反序列化,就是将 json 或者是 xml 字符串 恢复成对象

前端发送 get 请求,会走序列化方法。
http://localhost:8080/books
(不知道是如何定义访问的方法是走序列化还是反序列化方法)
get 请求是获取对象,序列化方法,把对象序列化成json格式的数据返回
在这里插入图片描述

序列化输出,将对象输出成 json 或者是 xml 字符串
反序列化,就是将 json 或者是 xml 字符串 恢复成对象

用 post 请求,会走反序列化方法,
发送的请求是 json 格式的数据,所以走反序列化方法,将数据恢复成对象
在这里插入图片描述

不太懂如何怎么应用这个自定义的序列化和反序列化器。

完整代码:

Book

在这里插入图片描述

BookController

package cn.ljh.app.controller;import cn.ljh.app.domain.Book;
import cn.ljh.app.service.BookService;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;import java.util.List;/**     GET  /books/{id} - (获取数据) 获取指定id的图书*     GET  /books?参数  -(获取数据) 获取符合查询参数的图书*     GET  /books        -(获取数据) 获取所有图书*     POST /books        -(添加数据) 添加图书*     PUT  /books/{id}    -(更新数据) 更新指定ID的图书*     DELETE /books/{id}    -(删除数据) 删除指定ID的图书*     DELETE /books?参数    -(删除数据) 删除符合指定参数的图书**  Restful处理方法的返回值通常都应该使用HttpEntity或ResponseEntity。**/@RequestMapping("/books")
@RestController
public class BookController
{//有参构造器进行依赖注入private BookService bookService;public BookController(BookService bookService){this.bookService = bookService;}//根据id查看图书@GetMapping("/{id}")public ResponseEntity<Book> viewBookById(@PathVariable Integer id){Book book = bookService.getBookById(id);//参数1:响应数据体  参数2:需要添加的响应头,没有就给个null   参数3:响应码 , OK 代表 200return new ResponseEntity<>(book, null, HttpStatus.OK);}//查看所有图书@GetMapping("")public ResponseEntity<List<Book>> viewBooks(){List<Book> allBooks = bookService.getAllBooks();return new ResponseEntity<>(allBooks, null, HttpStatus.OK);}//添加图书@PostMapping("")public ResponseEntity<Book> addBook(@RequestBody Book book){Book b = bookService.addOrUpdateBook(book);//HttpStatus.CREATED 代表返回的状态码为 201return new ResponseEntity<>(b, null, HttpStatus.CREATED);}//根据id更新图书信息@PutMapping("/{id}")public ResponseEntity<Book> updateBookById(@PathVariable Integer id, @RequestBody Book book){book.setId(id);Book b = bookService.addOrUpdateBook(book);return new ResponseEntity<>(b, null, HttpStatus.OK);}//根据id删除图书@DeleteMapping("/{id}")public ResponseEntity<Book> deleteBookById(@PathVariable Integer id){Book book = bookService.deleteBookById(id);return new ResponseEntity<>(book, null, HttpStatus.OK);}}

BookService

package cn.ljh.app.service;import cn.ljh.app.domain.Book;import java.util.List;public interface BookService
{//根据id查看图书Book getBookById(Integer id);//查看所有图书List<Book> getAllBooks();//添加/修改图书Book addOrUpdateBook(Book book);//根据id删除图书Book deleteBookById(Integer id);}

BookServiceImpl

package cn.ljh.app.service.impl;import cn.ljh.app.domain.Book;
import cn.ljh.app.service.BookService;
import org.springframework.stereotype.Service;import java.util.*;
import java.util.concurrent.ConcurrentHashMap;@Service
public class BookServiceImpl implements BookService
{//创建一个线程安全的map集合存数据,假设为数据库static Map<Integer, Book> bookDB = new ConcurrentHashMap<>();static int nextId = 1;//初始化数据库的数据static{bookDB.put(nextId, new Book(nextId++, "火影忍者", 120, "岸本"));bookDB.put(nextId, new Book(nextId++, "七龙珠", 121, "鸟山明"));}//根据id查看图书@Overridepublic Book getBookById(Integer id){if (id != null){Book book = bookDB.get(id);if (book!=null){return book;}}throw new RuntimeException("根据id查看图书失败!");}//查看所有图书@Overridepublic List<Book> getAllBooks(){//获取map中的所有数据Collection<Book> mapBooks = bookDB.values();//强转List<Book> books = new ArrayList<>(mapBooks);return books;}//添加/修改图书@Overridepublic Book addOrUpdateBook(Book book){if (book.getId() != null){//修改//map的key是唯一的,所以map里面有这个key的话,直接把原来的value覆盖掉bookDB.put(book.getId(),book);return book;}else {//新增//为新增的图书设置idbook.setId(nextId);//book添加完之后,这个id才会自增bookDB.put(nextId++,book);return book;}}//根据id删除图书@Overridepublic Book deleteBookById(Integer id){Book book = bookDB.remove(id);return book;}
}

CustomSeDe

package cn.ljh.app.custom;import cn.ljh.app.domain.Book;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import lombok.SneakyThrows;
import org.springframework.beans.BeanUtils;
import org.springframework.boot.jackson.JsonComponent;import java.io.IOException;//内部类就是定义在另外一个类里面的类。与之对应,包含内部类的类被称为外部类,这个 CustomSeDe 就是外部类
//使用 @JsonComponent 修饰包含JsonSerializer/JsonDeserializer内部实现类的外部类,
//这些内部实现类将会由Spring Boot注册为自定义JSON序列化器和反序列化器。
@JsonComponent
public class CustomSeDe
{//在该类中可定义自定义的序列化器和反序列化器//postman 用get请求时走这个方法//自定义序列化器,这个就是内部类public static class MySerializer extends JsonSerializer<Book>{@Overridepublic void serialize(Book book, JsonGenerator jsonGenerator,SerializerProvider serializerProvider) throws IOException{//在该方法中完成自定义的序列化输出,将对象输出成 json 或者是 xml 字符串jsonGenerator.writeStartObject(); //输出 {  这个花括号 , 输出对象用这个括号//jsonGenerator.writeStartArray();  //输出 [ 这个中括号  , 输出数值用这个括号jsonGenerator.writeNumberField("id", book.getId());//将book对象的name属性,序列化为title 属性jsonGenerator.writeStringField("title", book.getName());jsonGenerator.writeNumberField("price", book.getPrice());jsonGenerator.writeStringField("author", book.getAuthor());//jsonGenerator.writeEndArray();   // 输出这个 ] 中括号jsonGenerator.writeEndObject();  //输出这个 } 花括号}}//postman 用post的请求访问到这个方法//自定义反序列化器,这个就是内部类public static class MyDeserializer extends JsonDeserializer<Book>{//这个方法负责反序列化,就是将 json 或者是 xml 字符串 恢复成对象@SneakyThrows@Overridepublic Book deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException{Book book = new Book();//JsonToken采用顺序方式依次读取 JSON 或 XML 字符串中的每个 token//token:一个花括号也叫token,一个符号隔开一个内容就叫tokenJsonToken jsonToken = jsonParser.getCurrentToken();//只要这个 Json 或者 xml 字符串没有结束,都应该继续向下读取while (!jsonToken.equals(JsonToken.END_OBJECT)){//这个循环的作用,是去保证要找到代表 Field Name 的 Token//如果当前读取的Token 不是字段名if (!jsonToken.equals(JsonToken.FIELD_NAME)){jsonToken = jsonParser.nextToken();//读取下一个tokencontinue;}//只要程序进到此处,就一定是读取到 field name 这种字段token//从 JSON 或者是 XML 字符串中读取得到的字段名,也就是Book 对象的属性名String fieldName = jsonParser.getCurrentName();//读取属性名之后,下一个Token就是属性值了JsonToken valueToken = jsonParser.nextToken();//获取当前token的值String value = jsonParser.getText();//对于要逻辑处理的属性,可以在这里进行判断处理if (fieldName.equals("title")){if (!value.startsWith("《")){value = "《" + value;}if (!value.endsWith("》")){value += "》";}//完成了对Book对象的name属性的设置book.setName(value);} else if (fieldName.equals("price")){double price = Double.parseDouble(value);//把从json或xml格式的价格数据拿出来,然后打折set进去book.setPrice(price * 0.5);} else{//对于其他属性,不需要额外处理的话,直接通过反射来调用Book对象的fieldName对应的setter方法BeanUtils.getPropertyDescriptor(Book.class, fieldName).getWriteMethod().invoke(book, value);}//继续向下读取下一个 TokenjsonToken = jsonParser.nextToken();}return book;}}}

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.4.5</version></parent><groupId>cn.ljh</groupId><artifactId>CustomSeDe</artifactId><version>1.0.0</version><name>CustomSeDe</name><properties><java.version>11</java.version><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!-- 添加 Jackson format XML , 用于对象与XML之间的转换 --><dependency><groupId>com.fasterxml.jackson.dataformat</groupId><artifactId>jackson-dataformat-xml</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-devtools</artifactId><scope>runtime</scope><optional>true</optional></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId><configuration><excludes><exclude><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></exclude></excludes></configuration></plugin></plugins></build></project>

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Jmx协议远程连接java服务器

注意&#xff1a;本例里&#xff0c;我用的是jdk17 通常用jdk自带的jconsole&#xff0c;或者想要功能强大点的使用visualVM 需要java服务器在启动的时候加上以下参数 -Dcom.sun.management.jmxremote 启用jxm远程连接-Djava.rmi.server.hostname10.1.3.99 指定jxm监听地址&…

python 自(1)定义变量 数据类型 判断数据类型 转换数据类型 算数运算符 复合运算符 比较运算符 逻辑运算符 赋值运算符

注释 # 注释 就是一个 # 也可以 ctrl / 也可以出来注释 命名规范 # 标识符 由字母 下划线 数字 组成 数字不能开头 # 不可以使用 关键字 # 严格区分大小写 定义变量 # 变量定义 重复利用 # 例如 cons 你好小周 print(cons) print(cons)print("你好小周") #这…

【经典小练习】JavaSE—拷贝文件夹

&#x1f38a;专栏【Java小练习】 &#x1f354;喜欢的诗句&#xff1a;天行健&#xff0c;君子以自强不息。 &#x1f386;音乐分享【如愿】 &#x1f384;欢迎并且感谢大家指出小吉的问题&#x1f970; 文章目录 &#x1f384;效果&#x1f33a;代码&#x1f6f8;讲解&#x…

每日一题 2596. 检查骑士巡视方案

难度&#xff1a;中等 很简单&#xff0c;从第 0 步开始模拟即可&#xff0c;唯一sb的就是测试用例中如果&#xff08;0&#xff0c;0&#xff09;处不为0的话就直接false&#xff0c;而不是去找0在哪 我的代码&#xff1a; class Solution:def checkValidGrid(self, grid: L…

【PowerQuery】Excel的PowerQuery的复制

在Excel中构建符合要求的PowerQuery连接之后&#xff0c;所有的PowerQuery 连接已经顺利的保存在Excel 工作簿当中&#xff0c;但是如何去查看已经保存的PowerQuery连接呢&#xff1f;图6.3 显示了查看PowerQuery连接。 Excel界面->数据页签->查询与连接 如果你的Power…

python求解一维弦振动方程

1、一维弦振动方程 2、差分公式及求解 import numpy as np import matplotlib.pyplot as plt l1 #长度 nx 101 #x方向网格节点数量 dx l/(nx-1) #空间网格步长 t6 #总时间 dt 0.01 # 时间步长 t_setnp.array([0.00,0.10,0.20,0.30,0.40,0.50,0.60]) #需要观察的时…

GO语言篇之发布开源软件包

GO语言篇之发布开源软件包 文章目录 GO语言篇之发布开源软件包新建仓库拉取到本地初始化项目编写代码提交代码发布引用软件包 我们写GO语言程序的时候难免会引用第三方的软件包&#xff0c;那么你知道别人是怎么发布自己的软件包吗&#xff0c;别急&#xff0c;这篇博客教你怎么…

如何使用 Node.js和Express搭建服务器?

如何使用NodeJs搭建服务器 1. 准备工作1.1 安装Node.js 2. 安装express2.1 初始化package.json2.2 安装express2.3 Express 应用程序生成器 欢迎使用Markdown编辑器新的改变功能快捷键合理的创建标题&#xff0c;有助于目录的生成如何改变文本的样式插入链接与图片如何插入一段…

geohash学习

geohash编解码 import ("github.com/mmcloughlin/geohash" )func Test_Demo(t *testing.T) {// 编码经纬度到 geohash 字符串lat, lon : 40.7128, -74.0060 // 纽约市的位置geoHash : geohash.Encode(lat, lon) // geohash 字符串fmt.Println(geoHash) …

Spring WebFlux详解

Spring 框架中包含的原始 Web 框架 Spring Web MVC 是专门为 Servlet API 和 Servlet 容器而设计的。后来在 5.0 版本中加入了 reactive 栈的 Web 框架 Spring WebFlux。它是完全非阻塞的&#xff0c;支持 Reactive Streams 背压&#xff0c;并在 Netty、Undertow 和 Servlet 容…

lv4 嵌入式开发-1 Linux文件IO

目录 1 文件的概念和类型 2 如何理解标准IO 3 流(FILE)的含义 3.1 流 3.2 文本流和二进制流 3.3 流的缓冲类型 4 小结 5 缓存区实验 1 文件的概念和类型 概念&#xff1a;一组相关数据的有序集合 文件类型&#xff1a; 常规文件 r 目录文件 d 字符设备文件 …

一键部署k8s集群

前置动作 关闭防火墙 systemctl disable firewalld && systemctl stop firewalld 关闭SELinux sed -i s#SELINUXenforcing#SELINUXdisabled#g /etc/selinux/config && grep SELINUXdisabled /etc/selinux/config setenforce 0 getenforce 关闭swap # 关闭…

Unity3D URP 仿蜘蛛侠风格化BloomAO

Unity3D URP 仿蜘蛛侠风格化Bloom&AO BloomBloom效果流程&#xff1a;制作控制面板VolumeComponent.CSCustom Renderer FeatherCustom Renderer PassBloom ShaderComposite Shader 完善Custom Feather风格化AO 总结 本篇文章介绍在URP中如何进行风格化后处理&#xff0c;使…

做题(2)

1.command_execution 题目提示&#xff1a; (这道题的名字就叫命令执行漏洞) 发现ping 先rce一下 127.0.0.1 | ls 发现了东西 尝试访问一下 找到了所有的文件 访问var文件夹 发现在var/www/html目录下没找到 去别的地方找找 在home文件夹下找到flag文件 访问 得到flag cyb…

网络层--IP协议

引入&#xff1a; IP协议主要解决什么问题呢&#xff1f; IP协议提供一种将数据从主机&#xff21; 发送到 主机&#xff22;的能力。&#xff08;有能力不一定能做到&#xff0c;比如小明很聪明&#xff0c;可以考100分&#xff0c;但是他也不是每次搜能考100分&#xff0…

Unity VideoPlayer 指定位置开始播放

如果 source是 videoclip&#xff08;以下两种方式都可以&#xff09;&#xff1a; _videoPlayer.Play();Debug.Log("time: " _videoPlayer.clip.length);_videoPlayer.time 10; [SerializeField] VideoPlayer videoPlayer;public void SetClipWithTime(VideoClip…

23062C++QTday5

将之前定义的栈类和队列类都实现成模板类 栈&#xff1a; #include <iostream> #define MAX 128using namespace std;template<typename T,typename C> class Stack { private:T top; //栈顶元素的下标C *data; //指向堆区空间public:Sta…

【C++】常用排序算法

0.前言 1.sort #include <iostream> using namespace std;// 常用排序算法 sort #include<vector> #include<algorithm>//利用仿函数 打印输出 class myPrint { public:void operator()(int val){cout << val << " ";} };//利用普通函…

界面控件DevExpress WPF TreeMap,轻松可视化复杂的分层结构数据!

DevExpress WPF TreeMap控件允许用户使用嵌套的矩形块可视化复杂的平面或分层结构数据。 P.S&#xff1a;DevExpress WPF拥有120个控件和库&#xff0c;将帮助您交付满足甚至超出企业需求的高性能业务应用程序。通过DevExpress WPF能创建有着强大互动功能的XAML基础应用程序&a…

接入Websocket,自动接收CSDN短消息

最近在研究Websocket功能&#xff0c;本来想接入抖音和快手的弹幕功能&#xff0c;以及短消息功能。 在了解的过程中&#xff0c;也开发了一些测试项目。 这不是&#xff0c;就把CSDN的短消息项目给弄出来了。 直接上代码&#xff1a; # !/usr/bin python3 # -*- encodingu…