Sunday, May 23, 2021

V2EX - 技术

V2EX - 技术


安卓版 yotube 梯子已切至美利坚 无法开启画中画

Posted: 23 May 2021 04:06 AM PDT

我感觉是 ssr 的问题
让 google 检测程序识别到了本地 ip
但是不管我怎么设置都没用
不知道有没有人遇到过一样的问题
PS:画中画权限已开启 测试 ip 归属为德克萨斯

这样的微服务目录结构该如何优雅地编译

Posted: 23 May 2021 03:52 AM PDT

项目目录,app1 和 2 均引用了 library 的包

  • project
    • go.mod
    • go.sum
    • docker-compose.yml
    • app1
      • Dockerfile
    • app2
      • Dockerfile
    • library
      • tool1
      • tool2

Dockerfile 长这样

FROM golang:latest as build  COPY . /data/ WORKDIR /data/  RUN go build -o dist/main cmd/main.go  FROM scratch  WORKDIR /app  EXPOSE 80  CMD ["./main"] 

这样 docker-compose 编辑 Dockerfile 的话会报找不到 library 的引用,因为没有 copy 进来。但是好像 Dockerfile 的 copy 不能 copy 上级目录的东西。

所以现在的问题是如何能够优雅地编译微服务?

Vivo 手机某系统进程开放 55555 端口疑似用作 mCDN

Posted: 23 May 2021 03:31 AM PDT

某天晚上在家用电脑看直播时发现画面卡顿,排查发现并非是电脑 /主站网络问题,排查是否有第三方未知设备或服务占用带宽,发现母上大人所使用的 vivo 手机在当时确实存在占用了大量带宽的情况,核查发现,部分在线视频应用确实有在后台运行,但视频在全部缓冲完成之后带宽占用会暂时降低到正常范围,但带宽占用过高的问题仍未彻底解决!

进一步分析,在路由器上针对 vivo 手机的 ip 进行抓包分析,确认该端口确实存在有来自外部 ip 的访问,但对于已关闭 UPnP 功能的局域网环境来说,外部 ip 则无法访问。由于没有 Root 权限,目前仅确认了该端口是由某个系统进程所使用,且根据抓包数据来分析,通信报文使用 HTTP 协议,手动测试请求访问时会回应包含有"Test Intercept"字段数据,据此在全网范围内搜索符合以上条件的主机有将近 7 万台,基本都是国内电信的,相信这些数据只是冰山一角,背后隐藏的秘密尚待深挖…

用 Vue 写列表页/详情页 的一个常见的需求,大神们都是如何解决的?

Posted: 23 May 2021 03:19 AM PDT

比如一个管理后台,列表页展示所有文章,包括标题、作者、发布时间等。 点击文章标题进入详情页,可以对文章进行编辑,可以编辑后返回列表页,也可以直接返回列表页。

因为文章列表很长,从文章详情页返回列表页需要保持其滚动条位置,还有其他组件的状态,所以对列表页设置了 keep-alive 。

如果不做其他控制对话,修改 /删除文章后,返回列表页,刚刚编辑的文章还是以原来的名称显示,并没有更新。

网上看到一种方案,是说一旦编辑或修改,再返回列表页时,在 beforeRouteLeave 回调中,让列表页的 keep-alive 设为 fasle,但这样整个列表页都被刷新了,之前的状态并没被保留,我期望即使文章被编辑后,返回列表还能保留之前的滚动条位置以及其他组件的状态,同时列表页显示最新的文章标题及内容。

暂没有用 Vuex,大家是如何解决这个问题的?

Google Photos 也太难用了吧

Posted: 23 May 2021 03:15 AM PDT

虽然 AI 确实很强大,但也不能除了 AI 啥也不行啊。
1. 人脸数据不会更新,永远只能识别出最早识别的那几张脸,新上传的人脸根本不会自动归类识别。
2. 相册里无法删除照片,只能跑到主页里翻出照片再删掉。
3. 查看照片的时候看不到这张照片到底在哪些相册里,至少 Android 版看不到。
4. 手机上不管什么文件夹上传的照片都会混到主页里,不应该不同文件夹的照片新建一个相册么?

我想维护一个大型字典,有没有什么省内存的方法

Posted: 23 May 2021 03:06 AM PDT

字典大概是下面这样的结构,我粗跑了一下,大概 100W 条数据,占用 400M 内存

{'dn0pFVBjQGwRYKCO2Ix45ibo6r9kW3Zedn0pFVBjQGwRYKCO2Ix45ibo6r9kW3Ze': 1, 'XuYRUn50f7j9zG2k8EZvoaWmeAdqTVrQXuYRUn50f7j9zG2k8EZvoaWmeAdqTVrQ': 2,  .... 't2n9wLoU53ql4hPMHi7pFaIEkCRxNuf0t2n9wLoU53ql4hPMHi7pFaIEkCRxNuf0',1000000} 

有没有方法能大幅度减少内存的占用?

Go Context 源码阅读

Posted: 23 May 2021 01:06 AM PDT

读源码一是为了学习好的代码风格,二是为了对 Go 这门语言能有深入的了解,能成长为一名合格的 Gopher

Context

Context 翻译成中文就是 '上下文' 的意思,准确的说它是 goroutine 的上下文, Go 1.7 开始引入 context,我看大代码是 Go 1.14.6 context 代码位于 go 源码的 src/context 文件中,这个包代码很少并且包含大量的注释,很方便我们阅读源代码

Context 的作用是用来传递 goroutine 之间的上下文信息,包括 取消信号, 超时信号,截止时间,请求信息(session, cookie),控制一批 goroutine 的生命周期. 在 Go 中我们往往使用 channel + select 的方式来控制协成的生命周期。但是对于复杂的场景,比如 Go 中通常一个协程会衍生出很多子协程, 分别处理不同的事情,这些携程往往具有相同生命周期,具有通用的变量,如果 goroutine 的层级较深使用 channel + select 不太方便,这个时候就可以使用 context.

Context 的底层原理实现

在 context 包中定义了 Context 这种 interface 类型

type Context interface { 	Deadline() (deadline time.Time, ok bool) 	Done() <-chan struct{} 	Err() error 	Value(key interface{}) interface{} } 

这个 interface 包含 Deadline, Done, Err, Value 四个方法 Deadline: 返回 context 是否会被取消,以及取消的时间 Done: 是在 context 被取消或者 deadline 后返回一个被关闭的 channel Err: 在 channel 关闭后,返回 context 取消原因 Value: 用来获取 key 对应的 value

同时在这个包中有一个 emptyCtx,它实现了 Context 接口

type emptyCtx int  func (*emptyCtx) Deadline() (deadline time.Time, ok bool) { 	return }  func (*emptyCtx) Done() <-chan struct{} { 	return nil }  func (*emptyCtx) Err() error { 	return nil }  func (*emptyCtx) Value(key interface{}) interface{} { 	return nil } 

emptyCtx 是 context 的一个最小实现,方法很简单,要么直接返回,要么直接返回 nil 。

在 Go 中初始化一个 context,我们经常使用 context.Background() 或者 context.TODO(), 从如下源码中可以看出这两个方法实际上返回的就是一个 emptyCtx

var ( 	background = new(emptyCtx) 	todo       = new(emptyCtx) )  func Background() Context { 	return background }  func TODO() Context { 	return todo } 

context.Background() 和 context.TODO() 看着除了方法名不一样,其他都是一样的。 在使用中我们需要区分两种 context 的使用场景,context.Background() 通常是用在 main 、测试, 或者最高层的 context (相当于根 context) 的初始化 context 的时候,而 TODO context 则是当我们不清楚使用什么 context 的时候使用

除了 context.Background() 和 context.TODO() 初始化 context 的方法,context 包还为我们提供了如下四个生成 context 的函数

咱们先看看函数签名如下:

WithCancel(parent Context) (ctx Context, cancel CancelFunc) WithDeadline(parent Context, d time.Time) (Context, CancelFunc) WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) WithValue(parent Context, key, val interface{}) Context 

WithCancel

WithCancel 用于生成一个可取消的 context

func WithCancel(parent Context) (ctx Context, cancel CancelFunc) { 	c := newCancelCtx(parent) 	propagateCancel(parent, &c) 	return &c, func() { c.cancel(true, Canceled) } } 

通常我们代码里如下使用

ctx := context.Background() cancel, ctx := context.WithCancel(ctx) 

它接受一个 context (父 context),返回一个 context 和一个可取消该 context 的方法,

WithCancel 首先调用了 newCancelCtx 私有方法,生成了一个 cancelCtx 结构体,然后在调用 propagateCancel 方法将 new context 挂载到父 context 上,我们着重看下 newCancelCtx 和 propagateCancel 方法

// newCancelCtx returns an initialized cancelCtx. func newCancelCtx(parent Context) cancelCtx { 	return cancelCtx{Context: parent} }  // 新的结构体,包含一个 Context,和 4 个私有属性 type cancelCtx struct { 	// 指向的是父 context 	// context 链类似一个链表,但是 Context 指向是父 context     Context          // 互斥锁,保证字段的安全     mu       sync.Mutex            // protects following fields      // 在 context 取消后首先关闭该 chan     done     chan struct{}         // created lazily, closed by first cancel call     // 从此 context 衍生出的子 context 挂载在这里     children map[canceler]struct{} // set to nil by the first cancel call     // cancel 的原因     err      error                 // set to non-nil by the first cancel call }  / A canceler is a context type that can be canceled directly. The // implementations are *cancelCtx and *timerCtx. type canceler interface {     cancel(removeFromParent bool, err error)     Done() <-chan struct{} } 

newCancelCtx 代码比较少,他一个父 context,返回一个 cancelCtx 类型的 context, 父 context 被赋值到了 cancelCtx 的 Context 字段 需要注意的是 newCancelCtx 返回的是一个 cancelCtx 类型,该 cancelCtx 实现了 canceler interface 的 cancel 和 Done 方法 cancel 方法用来取消这个 context 以及这个 context children map 上的子 context Done 返回的怎是一个关闭的 channel, 用来表示该 context 是否 cancel

cancel 方法源码:

func (c *cancelCtx) cancel(removeFromParent bool, err error) { 	if err == nil { 		panic("context: internal error: missing cancel error") 	} 	c.mu.Lock() 	// 该 context 已经 Done 	if c.err != nil { 		c.mu.Unlock() 		return // already canceled 	} 	c.err = err 	// 关闭 channel 	if c.done == nil { 		c.done = closedchan 	} else { 		close(c.done) 	} 	// 循环取消该 context 的子 context 	for child := range c.children { 		// NOTE: acquiring the child's lock while holding parent's lock. 		child.cancel(false, err) 	} 	// 取消子 context 后,将 children 字段置空 	c.children = nil 	c.mu.Unlock()  	// 如果指定了 removeFromParent = true 	// 则需要将该 context 从其父 context 的 children map 字段中删除 	if removeFromParent { 		removeChild(c.Context, c) 	} }  // Done 方法则返回该 context 的 done channel // context 关闭后,外部接受到 channel 的 close 信号 func (c *cancelCtx) Done() <-chan struct{} {     c.mu.Lock()     if c.done == nil {         c.done = make(chan struct{})     }     d := c.done     c.mu.Unlock()     return d } 

cancel 方法实现特别简单,通过 context 结构体中的 done chan struct{} 这个字段实现的 调用 cancel 方法,本质上就是对该 context 的 done channel 字段执行 close 操作 此外,如果入参 removeFromParent = ture, 会将此 context 从他的父 context 的 children map 上删除

下面是 propagateCancel 方法源码,propagateCancel 方法特别重要 该方法就是将 newCancelCtx 方法生成的新的 cancelCtx 挂在亲父 context 的 children map 上 在执行挂载的时候,如果父 context 已经取消,就将此 context 也取消掉

func propagateCancel(parent Context, child canceler) { 	done := parent.Done() 	// step1: 如果 parent 是不可 cancel 的 	// 此时直接返回,没有将 child context 挂到 children map 上的必要 	// 因为即便挂载上去,因为父 context 不能取消,子 context 也更无法通过父 context 来取消 	if done == nil { 		return // parent is never canceled 	}  	select { 	case <-done: 		// step2: 父 context 被取消,所以需要将此子 context 也取消 		child.cancel(false, parent.Err()) 		return 	default: 	}  	// step3: 获取当前 context 的父 context 	// parentCancelCtx 是用来获取父 cancelCtx 	if p, ok := parentCancelCtx(parent); ok { 		p.mu.Lock() 		if p.err != nil { // 父 context 是 canceled ( context 如果取消的,err 字段一定不为空) 			// step3.1: 如果父 context 是已取消的,就需要将子 context 也取消了 			child.cancel(false, p.err) 		} else { 			// step3.2: 父 context 没有取消,将此子 context 挂到父 context 的 children map 字段 			if p.children == nil { 				p.children = make(map[canceler]struct{}) 			} 			p.children[child] = struct{}{} 		} 		p.mu.Unlock() 	} else {  		// step4: 这个 else 分支是比较难以理解的地方 		// 可以理解为,在并发模型下,如果其他 goroutine 将 parent 的 context 改成了一个 cancelCtx 		// 那么没有这个分支,会出现 parent done 的时候 child 不知道 parent done 信息 		// 导致 child context 无法 cancel, child context 控制的相关的 goroutine 就无法结束,出现内存泄漏 		atomic.AddInt32(&goroutines, +1) 		go func() { 			select { 			case <-parent.Done(): // 监听父 context 的 cancel 信息 				child.cancel(false, parent.Err()) 			case <-child.Done(): // 如果 child 自身 cancel 就退出 select,避免当前这个 goroutine 内存泄漏 			} 		}() 	} } 

propagateCancel 方法是 context 中算是最复杂的一个方法了,它实现的功能是很简单的, 就是将 newCancelCtx 方法生成的新的 cancelCtx 挂在亲父 context 的 children map 上,不过过程中有很多细节处理,只有耐性阅读源码才能准确的理解这些处理上的细节

WithCancel 方法最后一句 return &c, func() { c.cancel(true, Canceled) }返回当前这个 cancelCtx 的 cancel 方法,作为该 context 外部控制该 context 取消的方法

通过 WithCancel 方法的分析,我们知道了 WithCancel 就是接受 context 参数,该参数作为 parent context 生成一个可以取消的 context, 并且会判断 parent context,如果他是一个未没有取消的 cancelCtx 类型的 context,就将当前新生成的 context 挂到 parent context 的 children map 上, 而 context 的是否取取消是通过 context 的 done 字段实现的,该字段是 chan struce{} 类型,取消一个 context 本质是将该 context 的 done 字段的的 channel 关闭

如下可见,cancel context 的 Done 方法,可以看出其返回的就是一个 close 的 channel

func (c *cancelCtx) Done() <-chan struct{} { 	c.mu.Lock() 	if c.done == nil { 		c.done = make(chan struct{}) 	} 	d := c.done 	c.mu.Unlock() 	return d } 

WithDeadline

看完 WithCancel 后,我们接着看 WithDeadline 底层实现,有了上面 WithCancel 的学习,看 WithDeadline 就比较容易了

func WithDeadline(parent Context, d time.Time) (Context, CancelFunc) { 	// 父 context 已经过期,或者父 context 的 deadline 是早于当前 context 的过期时间的 	// 就调用 WithCancel 创建一个 cancel context 	if cur, ok := parent.Deadline(); ok && cur.Before(d) { 		// The current deadline is already sooner than the new one. 		return WithCancel(parent) 	} 	// 创建一个 timerCtx,timerCtx 实现了 Context 接口 	// timerCtx 结构体包含了 (继承了) cancelCtx 	// 此外还有一个 *time.Timer 类型的 timer 字段和一个 time.Time 类型的 deadline 字段 	// timer 字段存储用来执行 deadline 的定时任务 	// deadline 是结束时间 	c := &timerCtx{ 		// 创建一个 cancelCtx 的 context 		cancelCtx: newCancelCtx(parent), 		// context 的取消时间 		deadline:  d, 	} 	 	// 这里和 WithCancel 中一样,将 context 挂载到父 context children map 上 	propagateCancel(parent, c) 	 	// 结束时间已过, 取消当前 context 	dur := time.Until(d) 	if dur <= 0 { 		// 这里的 cancel 是 timerCtx 中实现的 cancel 方法 		// 而不是从 cancelCtx 中继承的 cancel,详见下文 		c.cancel(true, DeadlineExceeded) // deadline has already passed 		return c, func() { c.cancel(false, Canceled) } 	} 	c.mu.Lock() 	defer c.mu.Unlock() 	 	// 这里是 deadline context 实现的核心 	// 创建一个定时任务,到达时间指定的结束时间(d)的时候,执行此任务,将这个 context 取消掉 	if c.err == nil { 		c.timer = time.AfterFunc(dur, func() { 			c.cancel(true, DeadlineExceeded) 		}) 	} 	return c, func() { c.cancel(true, Canceled) } }  func (c *timerCtx) cancel(removeFromParent bool, err error) {     // timerCtx 的 cancel 首先调用了 cancelCtx 的 cancel 方法,将此 context 关闭     // 并将 children map 字段上挂的子 context 取消     c.cancelCtx.cancel(false, err)          // 如果指定了 removeFromParent = true     // 将从父 context 的 children map 上将当前 context 移除     if removeFromParent {         removeChild(c.cancelCtx.Context, c)     }          c.mu.Lock()     // 终止 timer 字段上挂载的定时任务     // 因为上面已经主动 cancel 了,所以需要停止当前 context 上的取消任务了     if c.timer != nil {         c.timer.Stop()         c.timer = nil     }        c.mu.Unlock() } 

从 WithDeadline 源码中我们会发现 with deadline context 的实现很简单 底层的创建和 WithCancel 基本一致,不同点是 WithDeadline 创建出来的 context 提多了一个 deadline 和 timer 字段 deadline 字段用来记录该 context 的结束时间 timer 上面挂了一个定时任务,负责到了指定的 deadline 时执行 cancel 方法,取消当前 context

WithTimeout

看完 WithDeadline 的实现,相信大家能想到 WithTimeout 这种 context 的实现了, 将 WithTimeout 的相对时间,转为一个绝对时间就是,WithTimeout 就变成了一个 WithDeadline,context 源码包中也是这么实现的,大家可以自行阅读源码

WithValue

现在看看 WithValue 的实现, 从 WithValue 源码中,我们可以看到,WithValue 返回了一个 valueCtx,该 valueCtx 是实现了 Context 接口

func WithValue(parent Context, key, val interface{}) Context { 	// key 不能是 nil 	if key == nil { 		panic("nil key") 	} 	// key 必须是可比较的,因为在通过 key 来取 value 的时候,需要对比 key 是否相等 	// 可以看下文中的 valueCtx 的 Value 方法的实现,方法返回的时候会对 key 进行比较 	if !reflectlite.TypeOf(key).Comparable() { 		panic("key is not comparable") 	} 	// 创建一个 valueCtx 类型的 context 	return &valueCtx{parent, key, val} } 

// valueCtx 继承了 Context, 新增了 key, value 两个字段 // WithValue(parent Context, key, val interface{}) Context // 这里显而易见,key 、value 两个字段就是用来存储 WithValue 调用的时候传递进来的 key 和 value

type valueCtx struct { 	Context 	key, val interface{} } 

我们接着看 WithValue context 获取 value 的方法 这个方法实现很简单,需要注意的是获取值操作是递归获取的 先从 context 本身取值,如果取不到会沿着父链向上获取,最终会找到 emptyCtx, 此时返回值是 nil

func (c *valueCtx) Value(key interface{}) interface{} { 	if c.key == key { 		return c.val 	} 	// 递归获取 key 对应的 value 	return c.Context.Value(key) } 

context 的使用建议

以下建议来自 context 这个包里的详细注释,我做了一个简单翻译,我们在使用中应当遵循这些使用建议

  • 不应当将 context 作为结构体的字段,应该将 context 作为函数参数往下传递
  • context 作为参数使用的时候,应该作为第一个形参,如下
func DoSomething(ctx context.Context, arg Arg) error { 	// ... use ctx ... } 
  • 不能传递 nil Context,如果不知道使用什么 context 的时候使用 context.TODO 即可
  • 使用 context Values 传递值的时候,不应当将函数参数作为 value 传递,context 应当用来 传递通用性的变量,如请求相关的 accept-language,cookie,session 等
  • context 是线程安全的

总结

Go 从 1.7 引入了 context,主要用于在 goroutine 之间传递取消信号、截止时间控制、超时时间控制以及一些通用型变量传递, 我读的源码是 Go 1.14.6 版本的,Go context 包代码特别简短,有大量的注释(注释比代码多,哈哈哈),很适合学习大家可以去读一读源码

Go Slice 是如何扩容的 ?

Posted: 23 May 2021 12:54 AM PDT

以前是从别人的博客看到关于 slice 的扩容机制,有人问起来就搬别人博客上看到的内容回答,回答的时候总是不够自信,今天通过学习源码和借助 dlv 工具,算是明白了 slice 扩容的事情,这里记录下自己总结的结论,slice 源码也不难,有想深入了解的伙伴可以看 src/runtime/slice.go 中的 growslice 方法。

要是有理解错误的地方,希望能大佬指正。

我源码阅读的是 Go 1.14.6 版本

这里以 old = append(old, append...) 为例子说明

1 触发扩容的条件: 如果 old.len + append.len > old.cap 就会触发扩容

2: 扩容的时候调用的 src/runtime.growslice growslice(et *_type, old slice, cap int) slice 方法,该方法接收 slice 的元素类型(el)、old slice 以及预期容量长度 cap, 预期 cap: expectCap = old.len + append.len

3: growslice 的方法会预先获取到新的容量;再根据 slice 元素类型(size) 做内存对齐,最终计算出新的 cap 值

3.1: 预先获取新 newCap 的方式如下: 如果 expectCap > 2 * old.cap, 则 newCap = expectCap ; 否则,如果 old.cap < 1024, newCap = 2 * old.cap, 否则以 old.cap 的长度作为 newCap 初始值,以的 0.25 的步长循环增长,直到 newCap >= expectCap

3.2: 内存对齐,根据 newCap * slice 元素 size 获取内存空间,根据 golang 内置的一个内存表向上去整,拿到做内存对其后的内存空间 capmem,然后用 capmem 除以 slice 元素类型 size 得到最终的 newCap

看到网上很多人写的 slice 的扩容机制,其实只是写到了 3.1 这步 (也可能是因为和我看的 Go 版本不同),我做实验验证也发现有时候出现和文章描述不符合的结果,今天看了源码才终于明白,出现不符合的情况就是因为有 3.2 的情况出现

国产/国行 moto 手机是不是都可以解锁 bl?

Posted: 22 May 2021 11:11 PM PDT

moto 官方的解锁页面:motorola.com/unlockbootloader,但是现在的 moto 毕竟是联想旗下的企业,我就担心它也来个内外有别:在中国之外的手机都可以解锁就中国的不能解锁?我不是瞎操心,想想联想在中国生产的笔记本电脑在美国的售价竟然还比在中国的便宜,你就应该理解我担心了。

另外,moto 的手机系统怎么样?类原生吗?如果没有什么恶心的、侵犯隐私的魔改,解锁后就直接上 Magisk 和 XP 套件,不刷别的 ROM 了,选 ROM 也累。

有没有精简安装包的技术

Posted: 22 May 2021 10:16 PM PDT

我用 pygtk 写了一款 PC 软件,因为接入了 AI 的 SDK,加上本身 gtk 和 python 的库,安装包体积到了 50M,想做个下载安装器,有没有人做过安装程序,如何实现? 最好是轻量上手简单的,不要超过 200 行 nsi 脚本, 没有很多精力投在这个安装器上面

有办法在国内的某个网店买到 moto 的国际版本的手机吗?

Posted: 22 May 2021 06:45 PM PDT

如果系统是类原生的,那么就直接国际版的解锁 BL 然后面具和 XP 框架,彻底告别刷机了,能省很多事

移动 nfc-sim 公交卡分享

Posted: 22 May 2021 06:06 PM PDT

移动去年整了个超级 sim 卡的,比起以前的 nfc-sim 卡只能刷公交强大很多,还能拿来当 U 盾之类的功能,但是这些不提,最有用的其实还是公交卡功能电信和联通应该也有类似的 支持条件,首先要手机支持 nfc 然后还要支持 nfc-sim.可以在设置里面启动了,先确定下支持不支持,有些手机更新完了不支持,更新前支持,酷安上有人就遇到过的,先去确定下 undefined 大概支持的手机的话,三星除了国行系统之外的,用不了国行 Samsung pay,LG 和索尼的洋垃圾,或者国行不支持公交卡的,比方说索尼的国行的马克兔还有最近摩托的 edge s 之类的,或者小米的刷了包之后用不了自带钱包的,真的很有用,iPhone 用户的话不支持 申请的话,可以线下营业厅,具体哪家支持的话打电话先问下 我是在线申请的,中国移动的手机 app 里面就可以申请,点击我的-补换卡业务,然后在里面申请,选择超级 SIM 卡,输入你的手机号,然后还要提供身份证还有刷脸实名认证的,然后就寄给你了 undefined 收到后就在手机 app 里面用补换卡业务里面激活,记得先别插进去,在外面激活了再插进去,放在 sim 卡 1 然后在手机设置里面选择 nfc-sim 卡,手机上下个和包支付,在里面划到我的里面,有个我的 nfc 卡,然后在里面激活,我领的是小蓝卡,因为有活动冲 50 送 50,分 5 个月返还话费,真香 刷卡的话就和你小米,华为之类自带的钱包一样用,具体支持的范围,我开的小蓝卡的话支持交通联合的地方都是可以用的

安卓手机的 NFC 功能都是一样的吗?

Posted: 22 May 2021 02:40 PM PDT

我至今还没有用过 NFC,新手机必须要支持 NFC,看上的 moto edge s 的网店客服说:"支持 NFC,但是不支持刷公交和地铁和门禁",那是不是也有某些安卓机"支持 NFC 也能刷公交地铁和门禁"? NFC 支持不支持公交地铁门禁这些东西是看软件还是看硬件?

MagicaCSG

Posted: 22 May 2021 12:55 PM PDT

http://ephtracy.github.io/index.html?page=magicacsg

MagicaVoxel 作者的新项目,一个 3D 建模和渲染创作工具。

看起来可以做一些很有趣的东西:

https://twitter.com/ManXiaoMisaki/status/1396083194923950083

开发者的友链接力-Dev2Dev

Posted: 22 May 2021 10:47 AM PDT

快高考了,不想学习,做了一个友链接力Dev2Dev (Github)。欢迎大佬加入!

技术栈:Vue3, Vite, windicss

PS. Vue3 真香

好奇, 如果想深入学习 零信任边缘, 参与什么项目或者加入什么组织合适?

Posted: 22 May 2021 10:25 AM PDT

或者学习什么知识

小白提问,自己电脑挖矿以后怎么套现成现金?

Posted: 22 May 2021 07:41 AM PDT

在论坛看大家挖矿,也跟风用自己的 1660ti 开挖,算力有 23M 左右,挖了几天后有个问题,如何换成现金?

大概自己也了解一下,ETH 需要转成 USDT,USDT 再币商收购。问题现在不知道上哪找。而且好像直接把 USDT 转出到交易所也有要一笔不少的手续费。

  1. 我是在 viabtc 矿场挖矿,这个平台可以把币无损提现到 coinex 交易所
  2. coinex 交易所我只看到了充值买币,提现到数字钱包以及币币交易的功能,没有找到出售给币商

现在是不是只有将 ETH 买 USDT 币,然后提现再卖掉吗?钱不多,不到 20 美元,挖了 3 天。如果套现太麻烦的话,以后不打算挖了。

unraid 怎么用命令开机某一个虚拟机,想做一个延迟启动虚拟机的脚本 延迟会了 启动不会

Posted: 22 May 2021 07:08 AM PDT

Google 了一下 好像是用 virsh 命令,搜了一些网页好像没有启动虚拟机的选项

找到了一个脚本不知道行不行,可有大佬了解的

#!/bin/bash

VM1="$1" VM2="$2"

echo $VM1 echo $VM2

if virsh list | grep -q " ${VM1} .*running" ; then virsh shutdown "${VM1}" fi

echo "1" WAIT=0

while virsh list | grep -q " ${VM1} .*running" ; do if [ $WAIT -lt 51 ]; then WAIT=$(($WAIT + 5)) sleep 5 echo $WAIT else echo "too long" break fi done

if virsh list | grep -q " ${VM1} .*running" ; then virsh destroy "${VM1}" fi

WAIT=0

while virsh list | grep -q " ${VM1} .*running" ; do if [ $WAIT -lt 30 ]; then WAIT=$(($WAIT + 5)) sleep 5 echo $WAIT else echo "too long" break fi done

virsh start "$VM2"

No comments:

Post a Comment