Go 1.27 于 2026 年 8 月 19 日正式发布,其中最受关注的语言特性之一,就是 Generic Methods(泛型方法)。
在 Go 1.18 引入泛型之后,我们可以定义泛型类型和泛型函数,但一直不能直接给普通类型的方法声明额外的类型参数。
Go 1.27 正式补上了这一能力。
在 Go 1.18 之后,我们已经可以这样定义泛型类型:
type List[T any] struct {
items []T
}
也可以定义泛型函数:
func Map[T any, R any](items []T, fn func(T) R) []R {
result := make([]R, 0, len(items))
for _, item := range items {
result = append(result, fn(item))
}
return result
}
但是,在 Go 1.26 以及更早的版本中,下面这种写法是不允许的:
func (l List[T]) Map[R any](fn func(T) R) List[R] {
// ...
}
也就是说:
Go 1.18 ~ Go 1.26
泛型类型 ✅
泛型函数 ✅
泛型方法 ❌
而 Go 1.27 开始:
Go 1.27
泛型类型 ✅
泛型函数 ✅
泛型方法 ✅
Go 官方已经明确说明,Go 1.27 允许具体类型(concrete type)的 Method 声明自己的类型参数。
这个问题其实非常有意思。
Go 在 Go 1.18 引入泛型时,并不是“忘记实现泛型方法”,而是经过了设计上的权衡。
例如:
type List[T any] struct {
items []T
}
我们可以定义:
func Map[T any, R any](items []T, fn func(T) R) []R {
// ...
}
但是不能:
func (l List[T]) Map[R any](fn func(T) R) List[R] {
// Go 1.18 ~ 1.26 不允许
}
以前的 Go 泛型设计认为,泛型方法尤其是泛型接口方法会带来较复杂的实现问题。
例如假设存在:
type Empty struct{}
func (Empty) Nop[T any](x T) T {
return x
}
那么当一个 Empty 被放进接口以后,运行时到底需要检查多少种 Nop 方法?
例如:
interface {
Nop(string) string
}
或者:
interface {
Nop(int) int
}
或者:
interface {
Nop(io.Reader) io.Reader
}
泛型方法理论上可以产生无限多种实例化形式。
因此,Go 早期的泛型设计没有支持这种能力。
Go 1.27 的设计发生了变化:
允许具体类型的方法拥有自己的类型参数,但仍然不允许接口方法声明类型参数。
这是理解 Go 1.27 泛型方法最重要的一点。
Go 1.27 的泛型方法语法非常直观:
func (receiver ReceiverType) MethodName[T Constraint](arg T) T {
// ...
}
例如:
type Box struct {
value any
}
func (b Box) Get[T any]() T {
return b.value.(T)
}
调用:
box := Box{
value: 100,
}
value := box.Get[int]()
fmt.Println(value)
输出:
100
这里需要注意:
type Box struct {
value any
}
和:
func (b Box) Get[T any]() T
是两个完全独立的类型参数。
T 是 Get 方法自己的类型参数。
Go 1.27 最有价值的地方之一,就是可以同时结合:
例如:
type List[T any] struct {
items []T
}
func (l List[T]) Map[R any](fn func(T) R) List[R] {
result := make([]R, 0, len(l.items))
for _, item := range l.items {
result = append(result, fn(item))
}
return List[R]{
items: result,
}
}
这里实际上存在两套类型参数:
List[T]
中的:
T
是类型本身的类型参数。
而:
Map[R any]
中的:
R
是方法自己的类型参数。
可以理解成:
List[T]
│
└── T:List 的类型参数
Map[R]
│
└── R:Map 方法自己的类型参数
完整代码:
package main
import "fmt"
type List[T any] struct {
items []T
}
func NewList[T any](items ...T) List[T] {
return List[T]{
items: items,
}
}
func (l List[T]) Map[R any](fn func(T) R) List[R] {
result := make([]R, 0, len(l.items))
for _, item := range l.items {
result = append(result, fn(item))
}
return List[R]{
items: result,
}
}
func main() {
numbers := NewList(1, 2, 3, 4)
strings := numbers.Map(func(n int) string {
return fmt.Sprintf("number-%d", n)
})
fmt.Println(strings.items)
}
输出:
[number-1 number-2 number-3 number-4]
这里:
numbers := NewList(1, 2, 3, 4)
得到:
List[int]
然后:
numbers.Map(...)
通过:
R any
把:
int
转换成:
string
最终得到:
List[string]
在 Go 1.18 ~ 1.26 中,我们通常只能这么写:
func MapList[T any, R any](
list List[T],
fn func(T) R,
) List[R] {
// ...
}
调用:
result := MapList(
MapList(
NewList(1, 2, 3),
func(n int) int {
return n + 1
},
),
func(n int) string {
return fmt.Sprintf("%d", n)
},
)
这种写法有一个明显的问题:
链式调用不够自然。
代码是从里向外执行的。
Go 1.27 可以直接写:
result := NewList(1, 2, 3).
Map(func(n int) int {
return n + 1
}).
Map(func(n int) string {
return fmt.Sprintf("%d", n)
})
这种写法更符合我们对对象方法调用的直觉:
List[int]
↓
Map
↓
List[int]
↓
Map
↓
List[string]
Go 官方的 Generic Methods 介绍也使用了类似的 List.Map 示例,强调泛型方法能够让链式操作更加自然。
Go 1.27 同样支持泛型方法的类型推断。
例如:
type List[T any] struct {
items []T
}
func (l List[T]) Map[R any](fn func(T) R) List[R] {
result := make([]R, 0, len(l.items))
for _, item := range l.items {
result = append(result, fn(item))
}
return List[R]{
items: result,
}
}
调用:
numbers := NewList(1, 2, 3)
strings := numbers.Map(func(n int) string {
return fmt.Sprintf("%d", n)
})
我们没有写:
Map[string]
Go 编译器可以从:
func(n int) string
推断出:
T = int
R = string
因此最终返回:
List[string]
例如:
type Pair struct {
first any
second any
}
func (p Pair) Convert[A any, B any](
first func(any) A,
second func(any) B,
) (A, B) {
return first(p.first), second(p.second)
}
使用:
pair := Pair{
first: 100,
second: "hello",
}
a, b := pair.Convert(
func(v any) int {
return v.(int)
},
func(v any) string {
return v.(string)
},
)
最终:
a = 100
b = hello
因此,泛型方法并不要求只有一个类型参数。
泛型方法同样可以使用 Go 的类型约束。
例如定义一个数字类型约束:
type Number interface {
~int | ~int64 | ~float32 | ~float64
}
然后:
type Calculator struct {
value int
}
func (c Calculator) Add[T Number](value T) T {
return value + T(c.value)
}
使用:
calculator := Calculator{
value: 10,
}
a := calculator.Add(20)
b := calculator.Add(3.14)
fmt.Println(a)
fmt.Println(b)
类型推断会自动确定:
T = int
以及:
T = float64
这是实际开发中特别容易混淆的地方。
例如:
type Container[T any] struct {
value T
}
这里的:
T
属于:
Container
而:
func (c Container[T]) Convert[R any]() R {
// ...
}
这里的:
R
属于:
Convert
可以理解为:
Container[T]
│
│
▼
Convert[R]
因此:
Container[int]
调用:
Convert[string]()
并不意味着:
T = string
实际上:
T = int
R = string
这是两个不同的类型参数。
这里是 Go 1.27 泛型方法最重要的限制之一。
虽然 Go 1.27 支持:
type User struct{}
func (u User) Convert[T any](value T) T {
return value
}
但是不能定义:
type Converter interface {
Convert[T any](value T) T
}
也就是说:
具体类型的泛型方法 ✅
接口中的泛型方法 ❌
官方 Go 1.27 Release Notes 明确指出:
接口的方法不能声明类型参数,同时接口方法也不能由泛型方法实现。
因此下面代码依然是错误的:
type Converter interface {
Convert[T any](value T) T
}
这不是 Go 1.27 泛型方法的遗漏,而是当前设计中的明确限制。
假设:
type Converter interface {
Convert[T any](value T) T
}
那么一个类型:
type MyConverter struct{}
理论上需要实现:
Convert[int]
Convert[string]
Convert[float64]
Convert[User]
Convert[Order]
...
问题在于:
类型系统和接口实现检查应该如何处理无限种可能的类型实例?
这会给接口的 method set、接口类型断言以及运行时实现机制带来非常大的复杂度。
因此 Go 1.27 采取的是一种折中的方案:
泛型方法:
普通具体类型 ✅
泛型类型的方法 ✅
接口方法 ❌
这也是 Go 1.27 泛型方法和其他语言泛型方法设计之间比较重要的区别。
Go 1.27 以后,我们经常会面临一个选择:
func Map[T, R any](...)
还是:
func (list List[T]) Map[R any](...)
一般可以按照下面的思路选择。
推荐使用泛型方法:
type List[T any] struct {
items []T
}
func (l List[T]) Map[R any](fn func(T) R) List[R] {
// ...
}
调用:
list.Map(...)
这种 API 更符合面向对象的数据组织方式。
推荐使用泛型函数:
func Max[T cmp.Ordered](a, b T) T {
if a > b {
return a
}
return b
}
调用:
Max(10, 20)
这种情况下没有必要人为创建一个类型,然后把函数挂到这个类型上。
泛型方法一个非常明显的应用场景就是:
数据处理链
例如:
type Collection[T any] struct {
items []T
}
定义:
func (c Collection[T]) Map[R any](fn func(T) R) Collection[R] {
result := make([]R, 0, len(c.items))
for _, item := range c.items {
result = append(result, fn(item))
}
return Collection[R]{
items: result,
}
}
再定义:
func (c Collection[T]) Filter(fn func(T) bool) Collection[T] {
result := make([]T, 0)
for _, item := range c.items {
if fn(item) {
result = append(result, item)
}
}
return Collection[T]{
items: result,
}
}
那么可以:
numbers := NewCollection(
1, 2, 3, 4, 5, 6,
)
result := numbers.
Filter(func(n int) bool {
return n%2 == 0
}).
Map(func(n int) string {
return fmt.Sprintf("number-%d", n)
})
数据类型变化过程:
Collection[int]
│
│ Filter
▼
Collection[int]
│
│ Map
▼
Collection[string]
这就是泛型方法非常典型的应用场景。
Go 1.27 不仅支持直接调用泛型方法,也支持将泛型方法转换成函数。
例如:
type List[T any] struct {
items []T
}
func (l List[T]) Map[R any](fn func(T) R) List[R] {
// ...
}
可以使用:
f := List[int].Map[string]
此时:
f
就是一个具体的函数。
可以理解为:
func(
List[int],
func(int) string,
) List[string]
然后:
result := f(
NewList(1, 2, 3),
func(n int) string {
return fmt.Sprintf("%d", n)
},
)
Go 官方也特别提到了这一点:泛型方法可以通过 method expression 转换成对应的函数形式。
下面实现一个简单的集合类型:
package main
import "fmt"
type Collection[T any] struct {
items []T
}
func NewCollection[T any](items ...T) Collection[T] {
return Collection[T]{
items: items,
}
}
func (c Collection[T]) Map[R any](
fn func(T) R,
) Collection[R] {
result := make([]R, 0, len(c.items))
for _, item := range c.items {
result = append(result, fn(item))
}
return Collection[R]{
items: result,
}
}
func (c Collection[T]) Filter(
fn func(T) bool,
) Collection[T] {
result := make([]T, 0)
for _, item := range c.items {
if fn(item) {
result = append(result, item)
}
}
return Collection[T]{
items: result,
}
}
func (c Collection[T]) ForEach(
fn func(T),
) {
for _, item := range c.items {
fn(item)
}
}
func main() {
numbers := NewCollection(
1, 2, 3, 4, 5, 6,
)
result := numbers.
Filter(func(n int) bool {
return n%2 == 0
}).
Map(func(n int) string {
return fmt.Sprintf("number-%d", n)
})
result.ForEach(func(s string) {
fmt.Println(s)
})
}
输出:
number-2
number-4
number-6
这个例子很好地体现了 Go 1.27 泛型方法的优势:
Collection[int]
│
├── Filter
│
▼
Collection[int]
│
├── Map
│
▼
Collection[string]
│
└── ForEach
假设我们需要实现:
List[int]
↓
转换
List[string]
由于没有泛型方法,只能把转换操作放到函数中:
func Map[T any, R any](
list List[T],
fn func(T) R,
) List[R] {
// ...
}
调用:
result := Map(
list,
func(n int) string {
return strconv.Itoa(n)
},
)
可以直接写:
func (l List[T]) Map[R any](
fn func(T) R,
) List[R] {
// ...
}
调用:
result := list.Map(
func(n int) string {
return strconv.Itoa(n)
},
)
最大的区别并不是“以前做不到,现在做到了”。
因为 Go 1.18 以后,通过泛型函数其实已经能够完成类似工作。
真正的变化是:
泛型操作终于可以自然地组织到类型的方法集合中。
这使得 API 设计更加自然,也让链式调用更加清晰。
例如:
type Box[T any] struct {
value T
}
func (b Box[T]) Convert[R any]() R {
// ...
}
这里有:
T → Box 的类型参数
R → Convert 的类型参数
不要混淆。
错误:
type Converter interface {
Convert[T any](value T) T
}
Go 1.27 仍然不支持。
例如:
type Utils struct{}
func (Utils) Max[T cmp.Ordered](a, b T) T {
// ...
}
这种设计通常没有必要。
如果一个操作本身与某个具体类型没有关系:
func Max[T cmp.Ordered](a, b T) T {
// ...
}
泛型函数往往更合适。
Go 1.18 的泛型解决的是:
减少重复代码。
Go 1.27 的泛型方法进一步解决的是:
让泛型能力更自然地组织到类型 API 中。
两者可以简单理解为:
Go 1.18
泛型类型
+
泛型函数
发展到:
Go 1.27
泛型类型
+
泛型函数
+
泛型方法
这使得很多以前只能写成:
Map(list, fn)
Filter(list, fn)
Reduce(list, fn)
的 API,可以自然地变成:
list.
Filter(fn).
Map(fn).
Reduce(fn)
尤其是在:
等场景中,泛型方法都具有比较明显的价值。
Go 1.27 正式加入了 Generic Methods(泛型方法)。
最核心的语法就是:
func (receiver Receiver[T]) Method[R any](value R) R {
// ...
}
它允许:
type List[T any] struct {
items []T
}
func (l List[T]) Map[R any](fn func(T) R) List[R] {
// ...
}
从而实现:
list.Map(...)
而不是过去的:
Map(list, ...)
不过需要特别注意:
具体类型的泛型方法 ✅
泛型类型的泛型方法 ✅
泛型函数 ✅
接口中的泛型方法 ❌
泛型方法实现接口泛型方法 ❌
因此,Go 1.27 的泛型方法并不是把 Go 变成一个完全自由的“泛型方法语言”,而是在保持 Go 类型系统和接口设计原则的前提下,增加了一项非常实用的能力。
对于已经大量使用 Go 泛型的项目来说,Go 1.27 最大的变化之一,就是可以开始重新审视过去为了绕开“泛型方法不存在”而设计的 API:
Map(list, fn)
很多时候现在可以重新设计成:
list.Map(fn)
这会让代码更加符合 Go 的方法组织方式,也让链式数据处理更加自然。
Go 1.27.0 的正式发布时间为 2026 年 8 月 19 日。
官方关于 Generic Methods 的专门文章发布于 2026 年 8 月 26 日。
最近遇到一个问题,默认Electron双击exe的时候会创建一个新的实例进程,当第2个实例时首次加载会缓慢。
起初以为是主进程某个功能阻塞导致渲染缓慢,后面也排查了很久,AI 也解决不了。
后来一步一步删除代码逐步排查影响范围,发现只要使用了 localStorage/sessionStorage 就会导致缓慢,
卡顿的窗口是由于在第一个应用程序仍在运行时启动了第二个应用程序副本造成的。两个副本都使用同一个 userData 文件夹。第一个副本持有会话存储数据库的锁,因此第二个副本需要等待大约四秒钟才能获得锁,之后才会放弃并转而使用内存。您的页面在加载时也会读取会话存储,因此也会等待一段时间。这是 Chromium 在所有平台上的工作方式。Chrome 不允许在同一个用户配置文件下启动第二个副本。
在启动时调用 app.requestSingleInstanceLock(),如果返回 false 则退出。
不要利用默认方式去启动多进程实例,需要退出,通过主进程去调用创建窗口实现。
这是一款由 Go 语言开发的商店零售销售系统,给所有中小店铺企业免费使用,目前该系统正在完善,欢迎使用该系统的用户提供建议。
支持 POS 扫码枪。
该项目支持跨平台,无需安装下载即用


史上最详细的 ethers.js web3 入门教程
你可能需要在你自己的 Project 分享自己的产品,你可能会使用截图的方式,这太糟糕了,并不好看。
使用 Beautiful window 可以制作好看的窗口,效果:

在线制作地址 https://xjh22222228.github.io/beautiful-window/ 你可能需要科学上网才能打开,但他是完全免费

此项目开源在 Github 上 https://github.com/xjh22222228/beautiful-window

分享一个作者维护多年的导航网站,叫做发现导航,从2018年维护至今,是目前全球开源且持续维护的项目,目前拥有1000+使用者。 Github项目, 2.2KStar+

三不需:无需数据库、无需服务器、无需成本
发现导航 的理念就是做一款无需依赖后端服务既简单又方便,没有繁杂的配置和数据库等配置概念, 做到开箱即用。
800+ 优质网站1、打开 https://github.com/xjh22222228/nav 点击右上角Fork到自己仓库

2、确认

3、创建 Token 后续访问后台需要, 打开 https://github.com/settings/tokens/new
输入 TOKEN,过期时间选为不过期, 权限全部勾选,TOKEN 只要不泄露是安全的。

复制并保存TOKEN,如果这一步不保存就查看不了

4、打开 https://github.com/你的用户名/nav/settings/secrets/actions/new 把链接的用户名改成你的Github
Name填写 TOKEN 大写, Secret 填写刚刚保存的 TOKEN

5、编辑根目录 nav.config.yaml 文件,只需要修改仓库地址,把ID改成Github ID

6、这个时候Github Action 会自动启动

7、完成!坐等2分钟,打开链接 https://你的用户名.github.io/nav
项目包含后台,不需要额外部署,只需要将链接后面增加 system => https://nav3.cn/system

如果你的应用服务启用了history路由模式,服务需要重定向到 index.html
netlify 默认不启用,启用需要在 public 目录新建 _redirects 文件内容:
/* /index.html 200
netlify.toml
[[redirects]]
from = "/*"
to = "/index.html"
status = 200
参考:https://docs.netlify.com/routing/redirects/rewrites-proxies/#history-pushstate-and-single-page-apps
找了下目前小程序没有跨端小程序生成二维码的库, 花了点时间整合了一下
小程序生成二维码支持微信、支付宝 等小程序使用。
具体使用移步到仓库:
可以对选中的 DOM 节点进行截图:

或者使用快捷键 shift+command+p 然后输入 screenshot 进行全屏截图。

这是浏览器控制台内置的方法,主要用来剪贴内容。 比如说我们要把 localStorage 里的数据都拷贝出来,如果一个一个key/value 考出来就很费劲。 copy 就可以很好的解决这个问题。

在控制台调试时需要对某个元素进行操作时一般会使用 document.querySelector(element) ,这种方式不仅长,还得知道元素选择器。
$0 则可以获取选中的目标元素。

切换隐藏选中元素,实际上 Chrome 添加了一个类, visibility: hidden !important。

这2个方法实际上是 document.querySelector / document.querySelectorAll 简写。

在一些场景定位BUG时想要对某个请求重新发起,如果不知道这个技巧,一般都是刷新页面。
选中需要重新发起请求栈右键:

这个功能我用得也挺多的, 主要是移动端调试直接扫一扫即可,而不需复制链接。

获取最后一次执行结果,减少了赋值操作很方便。


在大部分时候初始化变量我们会这么做:
var n int
// 或者
n := 0
上面的代码实际上是等价的,没有问题, 但是在切片就不同了,看下面例子:
var sliceN1 []int // 结果为 nil
sliceN2 := []int{} // 结果为 []
这TM是一个坑啊,官方是这么解释的:
When storage is allocated for a variable, either through a declaration or a call of new, or when a new value is created, either through a composite literal or a call of make, and no explicit initialization is provided, the variable or value is given a default value. Each element of such a variable or value is set to the zero value for its type: false for booleans, 0 for numeric types, “” for strings, and nil for pointers, functions, interfaces, slices, channels, and maps. This initialization is done recursively, so for instance each element of an array of structs will have its fields zeroed if no value is specified.
官方文档指出切片在没有初始化的情况下切换的零值是 nil , 第一种是未初始化,第二种是初始化为空,所以这2种声明都是不等价的,也就是 nil != slice。 在开发过程中需要注意。
还有一种区别就是在 json 转换时 nil 会被当成 null, 而另外一种是被当初 [] 空数组。 在WEB开发中完全是坑。
var n []string
r, _ := json.Marshal(n)
fmt.Println(string(r)) // "null"
n := []string{}
r, _ := json.Marshal(n)
fmt.Println(string(r)) // "[]"
如果我们使用第二种初始化,就会被 Goland 认为是一种冗余的写法,会让你转换为第一种 var, 这样就又出刚刚说的问题了。

有人说这是 Goland 的 Bug, Goland 团队认为 nil切片在功能上等同于零长度切片,即使它不指向任何内容。
解决办法是使用 make
var n []int = make([]int, 0) // 全局
sliceN := make([]int, 0) // 函数内
我的建议就是在声明切片时都使用 make 来初始化,防止入坑。

go1.16 出了 go:embed 指令, 了解后发现很强!!!
go:embed 是一个指令,用来嵌入静态文件到二进制文件当中。
我们在部署时会打包成一个二进制文件,如果应用包含一些配置文件比如 config.ini / index.html config.yaml 等一系列静态文件就比较麻烦了,在以往的做法就是将这些静态文件放到二进制同一个目录里头,通过文件系统进行读取, 或者是写在 .go 文件里, 就很不方便。
现在可以通过 go:embed 指令将这些静态文件打包在二进制文件里,也就是只有一个二进制文件。
在使用 go:embed 指令之前先了解一下有哪些限制。
go:embed 指令跟着单个文件,只支持 string / []byte / embed.FSgo:embed 指令跟着多个文件或文件夹,只支持 embed.FSgo:embed 指令下一行必须紧跟着嵌入变量string / []byte / embed.FS, 其他类型包括别名都不支持. / .., 当前路径使用 *2.txt 文件内容:
hello golang
package main
import (
_ "embed"
"fmt"
)
//go:embed 2.txt
var s string
func main() {
fmt.Println(s)
// hello golang
}
注意 // 注释后面不能有空格
字节切片类型:
package main
import (
_ "embed"
"fmt"
)
//go:embed 2.txt
var b []byte
func main() {
fmt.Println(b)
// [104 101 108 108 111 32 103 111 108 97 110 103]
}
package main
import (
"embed"
"fmt"
)
//go:embed 2.txt
var f embed.FS
func main() {
s, _ := f.ReadFile("2.txt")
fmt.Println(string(s))
// hello golang
}
当嵌入多个文件只能使用 embed.FS 类型,不支持其他类型。
package main
import (
"embed"
"fmt"
)
//go:embed 1.txt 2.txt
var f embed.FS
func main() {
s1, _ := f.ReadFile("1.txt")
s2, _ := f.ReadFile("2.txt")
fmt.Println(string(s1))
fmt.Println(string(s2))
}
或者指令分开多行写也可以
package main
import (
"embed"
"fmt"
)
//go:embed 1.txt
//go:embed 2.txt
var f embed.FS
func main() {
s1, _ := f.ReadFile("1.txt")
s2, _ := f.ReadFile("2.txt")
fmt.Println(string(s1))
fmt.Println(string(s2))
}
FS 目前提供了3个API:
这3个都是只读的,没有可写的接口, 也就是不存在竞态问题,可以并发读取。
embed.FS 实现了 1.16 io/fs.FS 新的接口:
package main
import (
"embed"
"fmt"
"io/fs"
)
//go:embed 1.txt
var f embed.FS
func main() {
content, _ := fs.ReadFile(f, "1.txt")
fmt.Println(string(content))
}
采用 / 斜杠来访问目录下的文件,windows 也一样。
当嵌入的是文件夹时除了 . 和 _ 开头的文件和文件夹都会被嵌入即使在程序中没有使用。
package main
import (
"embed"
"fmt"
"io/fs"
)
//go:embed test
var f embed.FS
func main() {
content, _ := fs.ReadFile(f, "test/1.txt")
fmt.Println(string(content))
// hello golang
}
快速获取你的电脑WIFI密码,并在需要时生成WiFi的QR码,以便手机轻松连接。

Mac:
curl -fsSL https://raw.sevencdn.com/xjh22222228/wifi/main/install.sh | bash
Windows:
./wifi.exe
mac
# 获取WIFI密码
$ wifi
# 打印二维码
$ wifi -q
# 生成二维码图片
$ wifi -i
# 获取 SSID
$ wifi -s
windows 一样
./wifi.exe
./wifi.exe -q

如果你用的 go版本 >= 1.13 GOMODULE 默认会启用,低于此版本的话升级吧。
如果项目需要 go module 就必须初始化一次。
go mod init 模块名
如果是开源项目放在 github 上:
# 注意不要带协议
go mod init github.com/xjh22222228/gosh
生成后会在项目下生成 go.mod 文件, 大概长这样
module github.com/xjh22222228/gosh
go 1.15
如果开了 go module 下载依赖会跟以往不太一样
go get ./...
更新项目下所有依赖最新版本
go get -u
也可以指定要更新的包
go get -u github.com/xjh22222228/gosh
删除用不到的依赖包和下载用到的依赖包。
go mod tidy -v
在项目下生成 vendor 目录,也就是依赖包。
go mod vendor
$ go mod verify
all modules verified
用Go写的一个检查Python依赖包最新版本命令行工具
全称 python-check-updates 简称 pcu。
主要原因是 Go 非常适合写工具类,因为不需要安装环境即可运行,加上更快,体积也小。
项目开源戳 https://github.com/xjh22222228/python-check-updates
这个项目只是用来玩的,直接用 pip 更好。
Find the latest version of your requirements.txt current dependency package.


Deno ships as a single executable with no dependencies. You can install it using the installers below, or download a release binary from the releases page.
Shell (Mac, Linux):
curl -fsSL https://raw.githubusercontent.com/xjh22222228/python-check-updates/main/install.sh | bash
Show any new dependencies for the project in the current directory:
$ pcu
Checking /opt/requirements_test.txt
13 / 13 [--------------------------] 100.00% 1 p/s
pytest-cov → 2.10.1
pytest-mock ==2.0.0 → 3.3.1
httpbin ==0.7.0 → 0.7.0
pytest-httpbin <1.0 → 1.0.0
MySQL-python ==1.2.5 → 1.2.5
aliyun-python-sdk-cdn ==2.3.1 → 3.5.8
XlsxWriter ==0.9.3 → 1.3.7
aliyun-python-sdk-core ==2.3.2 → 2.13.29
wheel → 0.35.1
service-identity ==16.0.0 → 18.1.0
pytest >=2.8.0,<=3.10.1 → 6.1.2
zope.interface ==4.3.2 → 5.2.0
Done in 1 s.
-f, --file Specify the file name of the check dependency
package, default 'requirements.txt'
-v, --version output the version number
-c, --check Check the latest version
-h, --help display help for command
$ pcu -f=requirements_test.txt
$ pcu -v

基于Flutter。
由于笔者不是专业的APP开发,在这方面较弱,在接入微信登录遇到不少坑,基本都是配置问题,在这里记录下过程。
IOS接入微信登录必须具备以下几点(缺一不可):
1、新建一个文件名叫 apple-app-site-association 没有任何后缀, 并写入以下配置
{
"applinks": {
"apps": [],
"details": [
{
"appID": "1UPF2ET6AP.com.example.app.scp",
"paths": [ "/help/*" ]
}
]
}
}
需要修改 appID 和 paths。

2、将文件放在域名根目录下。
打开你的域名比如 https://www.example.com/apple-app-site-association 如果文件被下载说明这步没问题。
3、使用 safari 浏览器打开 https://www.example.com/help/123 前面修改为你的域名,help 是前面在 paths 指定的, 123是乱写的随机字符串,打开后如果出现类似下面说明正常:

4、打开微信开放平台,找到IOS配置,下面必须全部都要配置

5、打开APPLE开发者中心,找到 Associated Domains 这个选项将它开启。

XCODE 打开 Runner.xcodeproj 文件。
如图配置 Domains, applinks 前缀是固定的,后面就前面配置的 Universal Links

这个干嘛用的呢,主要是添加微信的 scheme 到白名单上去,这一步少不了,如果不添加没有办法知道微信是否已安装(尽管已安装了)。
找到 info.plist 文件, 将下面这一块内容添加到 dict 节点的末位,如下:
<key>LSApplicationQueriesSchemes</key>
<array>
<string>wechat</string>
<string>weixin</string>
<string>weixinULAPI</string>
</array>
如果不知道这个文件在哪里,直接在当前工程下搜索这个文件, 一定有。

接入这微信登录费了不少心思,少一步就要找各种问题进行排除,太蛋疼,还好终于搞定了,祝后面的小老弟们一切顺利哈。

很多情况需要在 宿主 运行容器内的命令, 比如需要在宿主运行容器 nginx / redis / 等命令
有2种方法可以执行容器命令:
1、用内联重定向 EOF
docker exec -it c6261194df8e /bin/bash << EOF
touch /home/inner.txt
EOF
2、docker 参数-c 可以执行容器shell
docker exec -it c6261194df8e /bin/bash -c "touch /home/inner.txt"
用上面2种方法会发现虽然执行了,但是并没有在容器内创建文件,不信可以试试。
目前我还没知道什么问题。
既然无法在宿主内执行容器内的Shell,那是不是可以执行容器Shell脚本?
写 Shell 脚本得有编辑器,容器默认不带 vim,所以得先安装:
apt-get update && apt-get install vim
Shell 脚本, 这里是Demo,实际参照自己需要执行的Shell。
ci.sh 内容如下:
#!/bin/bash
echo "hello" >> /home/inner.txt
来试试
docker exec -it c6261194df8e /bin/bash -c "/bin/bash /opt/ci.sh"
然后进入容器会发现 /home 目录下确实有 inner.txt 文件,说明这思路是可以的。
对了,执行容器内的外建命令记得用绝对路径执行,例如:
# 必须
/bin/nginx -s reload
# 这样不行的,因为有些外建命令没有加入环境变量
nginx -s reload

在我们编写程序时是离不开日志的,在Go标准库中提供了一个 log 模块。
比较常用的方法有下面这几个:
ln 结尾的只是多了个换行符 \n, f 结尾的是格式化文本。
很多新手不知道这些方法和 fmt 包中的方法有什么区别。
实际上上面那3个都只是 fmt 包中的语法糖
抛出异常信息并终止程序。
示例代码:
package main
import (
"log"
)
func main() {
log.Panic("error")
}
底层实现是这样子的:
可以看到这其实只是一个语法糖而已
func Panic(v ...interface{}) {
s := fmt.Sprint(v...)
std.Output(2, s)
panic(s)
}
如果说能不能用全局方法 panic 呢? 不行, 因为panic 只接受一个参数:
panic("error")
所以为什么会有 log.Panic 这个方法了吧。
Fatal 和 Panic 都是用来打印信息然后终止程序,不知道何时用哪个?
Panic: 抛出异常终止程序,一般来说在调试时想获得更多错误信息那就使用 PanicFatal: 这其实只是打印信息后正常退出程序不会提供更多额外信息给你所以在程序中使用 Fatal 是没什么意义的,所以不推荐使用。
创建日志文件的好处在于可以持久化,在出现问题的时候可以快速定位。
创建日志文件需要借助 os 模块和 log 模块。
示例代码,以注释说明:
package main
import (
"log"
"os"
)
func main() {
// 打开一个文件
// 第一个参数是写入日志文件的位置
// 第二个参数是需要以什么权限进行操作,多个mode用 | 符号分隔代表多个
// 最后参数是权限的八进制表示法
file, err := os.OpenFile("./log.txt", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0)
if err != nil {
log.Panicln("文件打开错误:", err)
}
defer file.Close()
// 用 New 方法实例化
// 第一个参数是 io.Writer 接口,只要实现了该接口都支持,文件就是
// 第二个参数是 前缀信息 一般用于区别日志是属哪个分类
// 最后一个参数是 标志, 也就是系统额外附加信息 LstdFlags 包含了日志和时间
ilog := log.New(file, "Error:", log.LstdFlags)
ilog.Println("输出错误信息")
}
标志有以下(都是以 L 开头):
| 参数 | 描述 |
|---|---|
| Ldate | 本地时区日期 2020/11/11 |
| Ltime | 本地时区时间 01:23:23 |
| Lmicroseconds | 微妙 01:23:23.123123 |
| Llongfile | 打印完整文件路径和行号 |
| Lshortfile | 打印文件名和行号 |
| LUTC | 如果设置了日期或时间,请使用UTC而不是本地时区 |
| LstdFlags | Ldate和Ltime的结合,日期和时间一起打印 |
| Lmsgprefix | 将Flag信息移至消息前面 |
golang打印日志行号有时非常有用,因为默认 fmt 是不打印行号的,给调试带来不便。
有了上面的Flag信息就好实现了, 借助 Llongfile 即可。
package main
import (
"log"
"os"
)
func main() {
ilog := log.New(os.Stdout, "Info:", log.Llongfile)
ilog.Println("Hello")
// 输出:Info:/Users/main.go:10: Hello
}
目前比较流行的是这两个日志系统,可以去了解下

每次新建项目都会遇到这个提示,明明安装依赖包了, 能用,就是爆红。


解决办法是在 Goland 设置里将 Go Modules 开启即可。

