2024年10月

Solon 3.0 引入一个叫 HttpUtils 小插件,这是一个简单的同步 HTTP 客户端,基于 URLConnection 适配(也支持切换为 OkHttp 适配)。使得编写 HTTP 客户端代码更加直观和易于阅读。

  • 使用 URLConnection 适配时(大小为 40KB 左右)。默认
  • 使用 OkHttp 适配时(大小为 3.1MB 左右)。当引入 okhttp 包时,自动切换为 okhttp 适配。

一、请求操作

  • HEAD 请求并返回 status code
int code = HttpUtils.http("http://localhost:8080/hello").head();
  • GET 请求并返回 body string
String body = HttpUtils.http("http://localhost:8080/hello").get();
  • GET 请求并返回 body as bean
//for Bean
Book book = HttpUtils.http("http://localhost:8080/book?bookId=1")
                     .getAs(Book.class);

二、提交操作

PUT、PATCH、DELETE 数据提交,与 POST 相同。

  • POST 请求并返回 body stirng (x-www-form-urlencoded)
//x-www-form-urlencoded
String body = HttpUtils.http("http://localhost:8080/hello")
                       .data("name","world")
                       .post();
  • POST 请求并返回 body stirng (form-data)
//form-data
String body = HttpUtils.http("http://localhost:8080/hello")
                       .data("name","world")
                       .post(true); // useMultipart
                       
                       
//form-data :: upload-file
String body = HttpUtils.http("http://localhost:8080/hello")
                       .data("name", new File("/data/demo.jpg"))
                       .post(true); // useMultipart
  • POST 请求并返回 body stirng (body-raw)
//body-json
String body = HttpUtils.http("http://localhost:8080/hello")
                       .bodyOfJson("{\"name\":\"world\"}")
                       .post();
  • POST 请求并返回 body as bean (body-raw)
//for Bean
Result body = HttpUtils.http("http://localhost:8080/book")
                       .bodyOfBean(book) //会通过 serializer 指定 contentType;默认为 json serializer
                       .postAs(Result.class);
                       
                       
//for Bean generic type
Result<User> body = HttpUtils.http("http://localhost:8080/book")
                       .bodyOfBean(book)
                       .postAs(new Result<User>(){}.getClass()); //通过临时类构建泛型(或别的方式)

三、高级操作

获取完整的响应(用完要关闭)

try(HttpResponse resp = HttpUtils.http("http://localhost:8080/hello").data("name","world").exec("POST")) {
    int code = resp.code();
    String head = resp.header("Demo-Header");
    String body = resp.bodyAsString();
    Books body = resp.bodyAsBean(Books.class);
}

配置序列化器。默认为 json,比如改为 fury;或者自己定义。

FuryBytesSerializer serializer = new FuryBytesSerializer();

Result body = HttpUtils.http("http://localhost:8080/book")
                       .serializer(serializer)
                       .bodyOfBean(book)
                       .postAs(Result.class);

四、总结

HttpUtils 的几个小优点:

  • 简单的 API。主要就是简单!也很小巧。
  • 支持自动序列化(使用了 solon serializer 接口规范;已适配的序列化插件可直接用)
  • 支持泛型

强大的无头UI表格库:TanStack Table!Github Star达到了惊人的25K!

在构建现代化 Web 应用时,
表格

数据网格
是常见的 UI 组件,特别是在处理
大量数据
或需要
复杂交互
时,选择合适的表格库尤为重要。
TanStack Table
是一款功能强大的
Headless UI
表格库,支持
TypeScript/JavaScript

React

Vue

Solid

Qwik

Svelte
等多种框架。它提供了极高的灵活性和扩展性,适用于各种复杂的数据展示需求。它不仅可以处理简单的表格,还能够通过其高度可扩展的 API 满足复杂的数据网格需求。无论是
分页

排序

过滤
,还是
多维分组

虚拟滚动
,它都能灵活应对,同时保持高效的性能表现。

今天,我们将深入介绍
TanStack Table
,分析其显著特性、使用方式及适用场景,探讨为什么它是各大前端框架开发者的不二选择。
image

显著特性

  • 支持React、Vue、Solid 的一流框架绑定
  • ~15kb 或更少(使用 tree-shaking)
  • 100% TypeScript(但不是必需的)
  • 无头(100% 可定制,自带 UI)
  • 自动开箱即用,选择加入可控状态
  • 过滤器(列和全局)
  • 排序(多列、多方向)
  • 分组和聚合
  • 旋转(即将推出!)
  • 行选择
  • 行扩展
  • 列可见性/排序/固定/调整大小
  • 表分割
  • 可动画化
  • 可虚拟化
  • 服务器端/外部数据模型支持

为什么选择 TanStack Table?

  1. 多框架兼容,适用广泛

TanStack Table
支持多种前端框架,不管你是使用 React、Vue,还是新兴的 Solid 和 Svelte,都可以无缝集成。这种跨框架的能力使得它在不同技术栈项目中都非常适用。

  1. 轻量高效,性能优越

与其他捆绑了大量 UI 元素和功能的表格库不同,
TanStack Table
保持了核心的
轻量化
,同时提供丰富的功能模块供
按需加载
。它的虚拟滚动功能在处理大数据集时尤为出色,确保用户交互的流畅性。

  1. 无 UI 限制,自定义能力强

对于那些需要高度定制化的项目,
TanStack Table

Headless架构
提供了极大的灵活性。你可以根据项目的具体需求,自由选择和设计表格的外观,而不被库自带的 UI 所限制。

  1. 扩展性强,满足复杂需求

无论是简单的数据展示,还是复杂的数据网格应用,
TanStack Table
的插件扩展机制和高度可定制的 API 都能满足你的需求。你可以在不修改核心代码的情况下,快速实现复杂的功能需求。

适用场景

  1. 跨框架项目

TanStack Table
不局限于单一框架,它支持多种前端框架,如 React、Vue、Svelte、Solid 等,非常适合那些跨框架或需要高复用性的项目。

  1. 处理大数据集的应用

TanStack Table
轻量且支持虚拟滚动,在处理大数据集时,它能够仅渲染可视区域的数据,极大提升了性能表现。特别适合电商、管理系统等需要展示大量数据的应用场景。

  1. 需要高度定制表格样式的项目

如果项目对表格的外观有特定要求,
TanStack Table
的无 UI 设计让你可以自由定制表格样式。结合你喜欢的 UI 库或自定义组件,打造完全符合需求的表格组件。

  1. 数据网格和复杂交互的场景

TanStack Table
提供了丰富的 API 和插件机制,支持复杂的数据交互逻辑,比如
多维分组

拖拽排序
等功能,非常适合在
SaaS

管理系统
等需要处理复杂数据的场景中使用。

使用方式

安装

npm install @tanstack/vue-table

使用组件

image

比如,要实现上述复杂表格效果,需要用到的应用代码如下:

<script setup lang="ts">
import {
  FlexRender,
  getCoreRowModel,
  useVueTable,
  createColumnHelper,
} from '@tanstack/vue-table'
import { ref } from 'vue'

type Person = {
  firstName: string
  lastName: string
  age: number
  visits: number
  status: string
  progress: number
}

const defaultData: Person[] = [
  {
    firstName: 'tanner',
    lastName: 'linsley',
    age: 24,
    visits: 100,
    status: 'In Relationship',
    progress: 50,
  },
  {
    firstName: 'tandy',
    lastName: 'miller',
    age: 40,
    visits: 40,
    status: 'Single',
    progress: 80,
  },
  {
    firstName: 'joe',
    lastName: 'dirte',
    age: 45,
    visits: 20,
    status: 'Complicated',
    progress: 10,
  },
]

const columnHelper = createColumnHelper<Person>()

const columns = [
  columnHelper.group({
    header: 'Name',
    footer: props => props.column.id,
    columns: [
      columnHelper.accessor('firstName', {
        cell: info => info.getValue(),
        footer: props => props.column.id,
      }),
      columnHelper.accessor(row => row.lastName, {
        id: 'lastName',
        cell: info => info.getValue(),
        header: () => 'Last Name',
        footer: props => props.column.id,
      }),
    ],
  }),
  columnHelper.group({
    header: 'Info',
    footer: props => props.column.id,
    columns: [
      columnHelper.accessor('age', {
        header: () => 'Age',
        footer: props => props.column.id,
      }),
      columnHelper.group({
        header: 'More Info',
        columns: [
          columnHelper.accessor('visits', {
            header: () => 'Visits',
            footer: props => props.column.id,
          }),
          columnHelper.accessor('status', {
            header: 'Status',
            footer: props => props.column.id,
          }),
          columnHelper.accessor('progress', {
            header: 'Profile Progress',
            footer: props => props.column.id,
          }),
        ],
      }),
    ],
  }),
]

const data = ref(defaultData)

const rerender = () => {
  data.value = defaultData
}

const table = useVueTable({
  get data() {
    return data.value
  },
  columns,
  getCoreRowModel: getCoreRowModel(),
})
</script>

<template>
  <div class="p-2">
    <table>
      <thead>
        <tr
          v-for="headerGroup in table.getHeaderGroups()"
          :key="headerGroup.id"
        >
          <th
            v-for="header in headerGroup.headers"
            :key="header.id"
            :colSpan="header.colSpan"
          >
            <FlexRender
              v-if="!header.isPlaceholder"
              :render="header.column.columnDef.header"
              :props="header.getContext()"
            />
          </th>
        </tr>
      </thead>
      <tbody>
        <tr v-for="row in table.getRowModel().rows" :key="row.id">
          <td v-for="cell in row.getVisibleCells()" :key="cell.id">
            <FlexRender
              :render="cell.column.columnDef.cell"
              :props="cell.getContext()"
            />
          </td>
        </tr>
      </tbody>
      <tfoot>
        <tr
          v-for="footerGroup in table.getFooterGroups()"
          :key="footerGroup.id"
        >
          <th
            v-for="header in footerGroup.headers"
            :key="header.id"
            :colSpan="header.colSpan"
          >
            <FlexRender
              v-if="!header.isPlaceholder"
              :render="header.column.columnDef.footer"
              :props="header.getContext()"
            />
          </th>
        </tr>
      </tfoot>
    </table>
    <div class="h-4" />
    <button @click="rerender" class="border p-2">Rerender</button>
  </div>
</template>

<style>
html {
  font-family: sans-serif;
  font-size: 14px;
}

table {
  border: 1px solid lightgray;
}

tbody {
  border-bottom: 1px solid lightgray;
}

th {
  border-bottom: 1px solid lightgray;
  border-right: 1px solid lightgray;
  padding: 2px 4px;
}

tfoot {
  color: gray;
}

tfoot th {
  font-weight: normal;
}
</style>

结语

TanStack Table
作为一款跨框架的强大表格库,凭借其高性能、轻量化和极致的定制能力,成为了现代 Web 开发中的理想选择。无论是在 React、Vue,还是其他前端框架中,它都能灵活应对复杂的数据展示和交互需求。如果你正在寻找一个可以满足从简单到复杂应用场景的表格库,
TanStack Table
无疑是你的不二选择。


该框架已经收录到我的全栈前端一站式开发平台 “前端视界” 中(浏览器搜 前端视界 第一个),感兴趣的欢迎浏览使用!

image

一、写在开头

最近有同学私信说自己去面试时,被面试官一个小问题给难住了,一个关于网络连通性的问题,面试官问这位同学:


日常中,如何测试两台主机之间的网络连通性,网络延迟,端口是否开放?并说一说原理”

这种问题在过完的面试中从没见过,毕竟太细小了,对于任何一个有工作经验的程序员来说,这都不算是问题,但你让我去说一说它们的原理,我也会一时语塞,并不是我太菜,而是面试官太无理取闹!

二、PING

2.1 PING的使用

Ping是一个非常非常常用的网络测试工具,经常用来测试网络中主机之间的连通性和网络延迟。无论是Windows还是Linux系统下都常用。

使用方式:ping ip 或者 ping 域名

我们可以在DOS窗口中键入“ping /?”打开指令帮助页面,帮我们更好的使用该命令。

在这里插入图片描述
现在,我们以百度为例,我们ping一下www.baidu.com的网址:
在这里插入图片描述
如上图就是我们本地ping百度域名的一个过程及反馈结果,可见网络连通性良好,响应时间18ms,如果时间过长,说明网络延迟较大,我们还可以通过-n 或者-i 以及-t 来测试网络承载能力。

2.2 PING的原理

ping命令检测网络连通性的原理主要是依赖于网络层ICMP(
互联网控制报文协议
)协议实现。主要形式是通过向目标主机发送ICMP请求报文和接受ICMP响应报文。如果请求的是域名,则会通过DNS域名系统解析为对应主机IP地址,再发送报文。

  • PING 命令会向目标主机发送 ICMP Echo Request。
  • 如果两个主机的连通性正常,目标主机会返回一个对应的 ICMP Echo Reply。

三、Telnet

telnet(
远程登录协议
),它是一个基于TCP协议的应用层协议,经常用于测试网络及端口占用情况。我们通过
telnet ip port
命令的方式去连接远程服务器,连接失败表示端口未占用。否则表示被占用。
在这里插入图片描述

Telnet 协议的最大缺点之一是所有数据(包括用户名和密码)均以明文形式发送,这有潜在的安全风险。这就是为什么如今很少使用 Telnet,而是使用一种称为 SSH 的非常安全的网络传输协议的主要原因。

本想
吐槽
一下阿里云,推动阿里云做的更好,为园子的商业化中做一些阿里云相关业务做一些准备,自己明知用着不舒服,还要推广阿里云的产品真的没底气。

没想到的是,吐槽阿里云,倒没得罪阿里云,反而引起阿里云的重视,在积极解决问题,却得罪了园子的很多用户,引来一片批评,这也许就是大家对园子的发展恨铁不成钢的一种表达方式吧。

算了,本来想做的阿里云云大使业务不作为重点了,虽然阿里云给了很诱人的返佣比例,把大部分返佣直接返给用户吧,通过
https://market.cnblogs.com/
链接领券购买,阿里云官网上能买到的产品都可以享受折上6.5折(仅限新用户)。

以后尽量减少在官博上发布情绪化的小学生作文,等园子发展壮大了,有专业人员撰稿,会根本解决这个问题。

最近园子上线了华为的一个广告,在首页侧边栏与博文下方,推广的是开发者调查问卷,本想写一篇博文推一下,现在也有点不敢写了,因为这个问卷调查有个尴尬的地方。

这个调查问卷是由CSDN创建的,之前这个调查问卷都是华为找CSDN合作,根本轮不到园子,今年幸运的是华为分了一些调查指标给园子完成。因为这个原因,所以调查表单用的是CSDN的,广告上线后遇到有园友在会员群里反馈,因为看到是CSDN的调查表单,不愿填写。

还是回到众包平台吧,这个帮开发者挣钱的商业模式应该是人心所向吧,这也很苦恼。

目前已经召集了2000多位合作开发者,但单子很少,好不容易在国庆期间10月2日等来一个50w的app开发大单,但到今天还没找到符合要求的开发团队接单,本想发博文出来公开招募,但客户又不愿意。

国庆假期快结束的时候,有个报价5w的单子,要求支撑峰值50w并发,接单开发者报价8w,客户同意,但启动开发之前最终确认的时候,客户问了一下自己公司的开发人员,开发人员说只需要几k就能搞定,于是客户取消了单子。

目前还有一个在谈的很有想象空间的广告大单,如果接下来做好了,可以解决园子这个季度的收入问题,但这也要写博文在园子里推广,需要大家的支持才能完成。

继续努力吧,二十年没有完成的商业化不可能短时间完成,大家着急的心情可以理解,但因为主观上的着急而操之过急是商业化的大忌,牢牢把握属于自己的市场机会才是关键。

一:背景

1. 讲故事

大家都知道所谓的
.NET Native AOT
即通过AOT编译器直接将C#代码编译成机器码,大家也习惯用C/C++的编译过程来类比,都是静态编译本质上都差不多,这篇我们借助工具从宏观层面去看一看AOT的编译过程。

二:C/C++ 的编译过程

用gcc编译过c代码的朋友都知道,分别可以用
-E, -S, -c,-o
来显示编译的各个阶段,即:

  1. 预处理阶段:落地 define,include文件和代码。
  2. 编译阶段:将C转为汇编代码。
  3. 汇编阶段:汇编代码转为机器码。
  4. 链接阶段:链接libc库及系统库,生成可执行文件。

画一张图如下:

这个世界上虽然说隔行如隔山,但隔行不隔理,有了这些知识,接下来就是
按图索骥
的对号入座即可。

三:AOT编译过程

在.NET中AOT编译器叫做
ilc.exe
,它是用C#代码写的,并且随.NET版本更新,比如我这里的
C:\Users\Administrator\.nuget\packages\runtime.win-x64.microsoft.dotnet.ilcompiler\8.0.8\tools\ilc.exe

对应的源码是在
D:\sources\runtime\src\coreclr\tools\aot
下。

还有一点要注意的是 ilc.exe 接收的是 MSIL 代码,而不是 C# 代码,有些朋友要问了
MSIL
何处来,自然是 dotnet publish 的时候先调用
Rolysn
来准备了,画个图如下:

接下来就是正式的ilc阶段。

1. 预处理阶段在哪里

这个阶段其实就对应着AOT的
依赖图构建和优化
,当然C#这里比较复杂,包括的东西也比较多,比如:

  1. 构建依赖图
  2. Pinvoke,COM,Delegate 的IL代码二次处理
  3. ValueType 的 GetHashCode 和 Equals 生成。
  4. 对 反射的有限支持,提供了一些元数据。
  5. 摇树优化

为依赖图构建的所有物料,可以参考
obj\Debug\net8.0\win-x64\native
文件夹下的
Example_21_2.ilc.rsp

感兴趣的朋友可以重点研究下这个库下的代码以及 DependencyAnalyzer 类,截图如下:


    /// <summary>
    /// Implement a dependency analysis framework. This works much like a Garbage Collector's mark algorithm
    /// in that it finds a set of nodes from an initial root set.
    ///
    /// However, in contrast to a typical GC in addition to simple edges from a node, there may also
    /// be conditional edges where a node has a dependency if some other specific node exists in the
    /// graph, and dynamic edges in which a node has a dependency if some other node exists in the graph,
    /// but what that other node might be is not known until it may exist in the graph.
    ///
    /// This analyzer also attempts to maintain a serialized state of why nodes are in the graph
    /// with strings describing the reason a given node was added to the graph. The degree of logging
    /// is configurable via the MarkStrategy
    ///
    /// </summary>
    public sealed class DependencyAnalyzer<MarkStrategy, DependencyContextType> : DependencyAnalyzerBase<DependencyContextType> where MarkStrategy : struct, IDependencyAnalysisMarkStrategy<DependencyContextType>
    {
        private MarkStrategy _marker = new MarkStrategy();
        private IComparer<DependencyNodeCore<DependencyContextType>> _resultSorter;
        private RandomInsertStack<DependencyNodeCore<DependencyContextType>> _markStack;
        private List<DependencyNodeCore<DependencyContextType>> _rootNodes = new List<DependencyNodeCore<DependencyContextType>>();
    }

官方注释中写的挺有意思,这玩意就像
GC Mark 算法
,看字段也是一个
深度优先算法

有些朋友可能比较好奇,这个
依赖树
最后变成了什么样子,可以在 csproj 上配置
<IlcGenerateMapFile>true</IlcGenerateMapFile>
节点,然后通过 dotnet publish 就会生成一个
Example_21_2.map.xml
文件,打开即可看到类型和方法节点。

2. 编译阶段在哪里

C 的编译阶段是用来将C代码转成汇编代码,在 ILC 中叫做
代码生成后端
,在落地方案上支持两种。

  1. RyuJIT

对,你没看错,就是你熟悉的不能再熟悉的JIT编译器,AOT也在用它,毕竟这东西太成熟了,支持各大操作系统平台,对应的高层封装在
ILCompiler.RyuJit
库中,截图如下:

  1. LLVM

这东西目前主要用来生成 WebAssembly 代码,具体参见:
https://github.com/dotnet/runtimelab/tree/feature/NativeAOT-LLVM

3. 汇编阶段在哪里

在 C 中这个阶段主要是将
.s
变成
.o
文件,即 汇编代码 到 机器码,如果往AOT上套的话,当属 ObjectWriter 类了,它要干的事情就是生成最终的 xxx.obj 文件。

4. 链接阶段在哪里

生成了 obj 之后,不管是 C 还是 C# 都
殊途同归
了,即调用 link.exe 将 VCRuntime运行时以及系统的.lib 库进行整体性合并,这个在 link.rsp 文件中可以窥之一二,截图如下:

图中有一个小注意点,此时的 obj 还没有 gc 代码,最终是在 link 阶段合并进去的。

三:如何眼见为实

为了研究这些过程,这里提供两款工具,一款叫
prefview
,一款叫
procmon

1. 如何观测编译流程

要想知道这个答案,找到一款合适的工具还是很容易知道的,在 Prefview 中有一个 Processes 视图,可以利用它观测 dotnet publish 命令执行后的进程启停情况,截图如下:

从卦中很明显看的出来是:
dotnet.exe -> dotnet.exe (Roslyn) -> ilc.exe -> link.exe
,哈哈,这流程图是不是加深了对编译过程的理解哈。

2. 如何观测 obj 的生成

观测 obj 的生成,自然就是对 ilc.exe 的文件读写进行监控,对,可以用微软的 Procmon 工具,配置如下:

配置好之后,接下来就是使用 dotnet publish 来引诱 ilc.exe 出洞,然后
倾巢覆卵
,截图如下:

接下来我们双击这一行观察
Stack
选项卡,可以很明显的看到是
ObjectWriter
所为,截图如下:

四:总结

研究这些东西还是比较麻烦的,主要是官方github上对ilc的介绍也比较有限,更多的还是需要研究源码,术业有专攻,作为一个调试师,更多精力还是耗在市场上的dump中吧。

图片名称