BITCOIN ETF TICKERSCRYPTO CITYDOES BLACKROCK OWN MORE BTC THAN BINANCE

flutter web面世已经有一段时间了,一路上也算是经过了官方不少的优化;虽说可能距离实际生产使用还是有一定距离,不过怎么说也可以玩玩。今天就谈谈flutter web某个在官方demo上都表现比较僵硬的问题:路由。

在讨论如何处理flutter web的路由之前,首先要理解app和web对路由处理的本质差异:
在app内,整个路由栈都是由app自己进行存储和控制的,当前未被使用的页面会被缓存,当回退时重新利用。优点自然是对路由有更准确的掌控,并且能保存之前页面的状态。缺点,呃,我感觉比起web模式没什么缺点。

在浏览器里,有一些重要的东西使得app模式变得不可能:浏览器的url栏和前进后退(乃至刷新)操作。这些操作随时可能销毁掉代码中自己存储的路由栈,导致app模式完全不可用。而相应地在浏览器中,路由栈则由浏览器进行存储和控制,业务代码则要负责每个url如何映射到对应的页面、整个应用(可以将fltterweb视为spa)在未初始化的时候如何处理url等。

需要注意的是,某种意义上来说,web模式的路由是app模式路由的子集,是一种牺牲了体验的妥协。换句话来说,我们完全可以在能满足app模式的框架下面,把他退化成web模式。而在flutter中,我们用Navigator2.0的api来实现这一点。

navigator2.0的简单用法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class MyApp extends StatefulWidget {
@override
_MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
final delegate = MyRouteDelegate();

@override
Widget build(BuildContext context) {
return MaterialApp.router(
title: 'Flutter Demo',
routeInformationParser: MyRouteParser(),
routerDelegate: delegate,
);
}
}

注意到我们引入了两个东西,一个叫routeInformationParser,一个叫routerDelegate,先看这个parser,MyRouteParser是这样一个东西

1
2
3
4
5
6
7
8
9
10
11
class MyRouteParser extends RouteInformationParser<String> {
@override
Future<String> parseRouteInformation(RouteInformation routeInformation) {
return SynchronousFuture(routeInformation.location!);
}

@override
RouteInformation restoreRouteInformation(String configuration) {
return RouteInformation(location: configuration);
}
}

这是一个最简单的实现,对于flutter web的场景,需要注意的是RouteInformation的location属性,在官方注释中可以看到这样一段:

1
2
3
4
5
6
7
/// The location of the application.
///
/// The string is usually in the format of multiple string identifiers with
/// slashes in between. ex: `/`, `/path`, `/path/to/the/app`.
///
/// It is equivalent to the URL in a web application.
final String? location;

注释声称,location属性在web场景下就对应url,严格来说这么说不太对,location实际上对于的是web场景下url的hash。
我们不详细阐述hash在routeProvider、delegate等等东西内部的层层传递,结论上来说,parseRouteInformation在web内负责解析url,restoreRouteInformation在web内则负责在创建新页面时改变浏览器的url。至于最简单的处理,就是直接把hash原样传来传去。

然后是delegate,delegate具体的写法请参照官网文档,这里强调几个方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@override
Future<void> setInitialRoutePath(String configuration) {
return setNewRoutePath(configuration);
}

@override
Future<void> setNewRoutePath(String configuration) {
if(isWeb) {
_stack
..clear()
..add(configuration);
return SynchronousFuture<void>(null);
}
}

setInitialRoutePath是页面刷新/新开页面时会触发的函数
setNewRoutePath则是直接修改url或者使用浏览器后退、前进时触发的函数。
对于flutter web来说(就我目前的认知),后退、前进和修改地址栏的行为是不可区分的,这或许和flutter使用hash策略有关,如果使用popstate来实现或许可以稍作区分。
因此,一旦触发setNewRoutePath,那么app内部的路由存储就会错乱,这到底是进入了新路由,还是进行了前进或者后退的操作?而解决的方法也很简单,就是直接开摆!
既然分不清那就不分了……反正,他们web就是不分的。

上面的setNewRoutePath的实现中可以看到,在stack中粗暴地直接清空stack然后添加单个新页面。这覆盖了浏览器操作,然后我们要做的就是覆盖flutter内部的函数:

1
2
3
4
5
6
7
8
9
bool _onPopPage(Route<dynamic> route, dynamic result) {
js.context.callMethod("browserBack");
return true;
}

void push(String newRoute) {
setNewRoutePath(newRoute);
notifyListeners();
}

并且在index.html的script内添加:

1
2
3
window.browserBack = () => {
history.go(-1)
}

这样flutter内部的push和pop也和我们一起摆烂了,改造结束之后,url就不会有app和web的缝合奇怪表现了,特别地,现在也可以点击flutter内部的后退直接后退到之前的其他页面了。

NEW BITCOIN CASINOSCOINBASE USER IS NOT AUTHENTICATED198 POUNDS TO DOLLARS

最近整了自己的网站,但是看起来有点空荡荡的,那就把简书的东西搬过来吧!

稍微写了个脚本,爬了一下个人页面里的文章然后丢到hexo里

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
const axios = require("axios")
const fs = require("fs")

// 在简书的请求header中复制此处需要的cookie,这会包含用于区分你用户以及登录的信息,具体是哪个字段生效我就没管了(
const cookie = "YOUR COOKIE HERE"

const contentURL = articleID => `https://www.jianshu.com/author/notes/${articleID}/content`

const articleListURL = noteID => `https://www.jianshu.com/author/notebooks/${noteID}/notes`

const notebooksURL = "https://www.jianshu.com/author/notebooks"

const headers = {
"Accept": "application/json",
"Cookie": cookie,
}

// 增加hexo专有的文档头,记得把时间弄对
const articleTop = article => {
const padToTwo = number => number.toString().padStart(2, "0")
const date = new Date(article.content_updated_at * 1000)
return `---
title: ${article.title}
date: ${date.getFullYear()}-${padToTwo(date.getMonth() + 1)}-${padToTwo(date.getDate())} ${padToTwo(date.getHours())}:${padToTwo(date.getMinutes())}:${padToTwo(date.getSeconds())}
tags:
---
`
}

// 把图片下载到本地,然后替换掉文章里的链接
const migrateContentImage = content => {
const imageReg = /\(https\:\/\/upload\-images\.jianshu\.io\/upload_images\/(.+)\?.+\)/g

const result = Array.from(content.matchAll(imageReg))

result.forEach(r => {
downloadImage(r[0], r[1])
})

return content.replace(imageReg, (_, p) => "(/images/" + p + ")")
}

const downloadImage = async (src, fileName) => {
const path = "source/images/" + fileName

const writer = fs.createWriteStream(path)

const res = await axios.get(src.slice(1,-1), {
responseType: "stream",
})

res.data.pipe(writer)
}

(async () => {
const notebooks = (await axios.get(notebooksURL, { headers })).data

const notes = (await Promise.all(notebooks.map(n => axios.get(articleListURL(n.id), { headers }))))
.map(n => n.data)
.reduce((prev, next) => [...prev, ...next], [])

const articles = (await Promise.all(notes.map(n => axios.get(contentURL(n.id), { headers }))))
.map((n, index) => ({
...notes[index],
content: n.data.content,
}))
.filter(a => a.shared)


articles.forEach(article => {
// 去掉非法文件名
const fileName = article.title.replace(/[\:\\\/\:\*\?\"\<\>\|]/, "_")
fs.writeFileSync("source/_posts/" + fileName + ".md", articleTop(article) + migrateContentImage(article.content))
})
})()

XRD CRYPTOBNB TO USDTHOW DOES COINBASE MAKE MONEY

不管是web还是app,现在很流行一种对新用户的遮罩+高亮的指引。大概就像下图里这样
引导off
引导on

第一反应都会是加个全局的modal然后上面叠个东西,但是这样一来我们就得额外给modal上边加个元素,不方便维护不好说,也不好看。另外,如果页面中有滚动要素,或者是元素位置会根据数据变化,那整起来就需要先定位需要高亮的目标元素,更麻烦了。所以还是希望用纯css解决这个问题。

于是我们先跳出这个具体需求来看z-index的原理。如果去看mdn的话我们会发现一个叫“堆叠上下文”的东西:

对于一个已经定位的盒子(即其 position 属性值不是 static,这里要注意的是 CSS 把元素看作盒子),z-index 属性指定:
盒子在当前堆叠上下文中的堆叠层级。
盒子是否创建一个本地堆叠上下文。

这里直接说结论,z-index的堆叠方式如下:
1、在同一个堆叠上下文中,z-index会决定其中元素的堆叠顺序
2、若两个元素不在同一个堆叠上下文中,则它们的z-index不会影响他们之前相互的堆叠顺序。若堆叠上下文不同,它们之间的堆叠顺序由它们第一个具有共同堆叠上下文的祖先之间的堆叠顺序决定。例如:一个位于z-index: 1内的元素,即使其z-index为99999,也会被和它父元素同级的,z-index为2的元素覆盖。
3、body的根本身会创建一个堆叠上下文。

这里注意一点:无论position是relative,absolute还是fixed,都不影响它的堆叠上下文是继承自第一个有z-index的父元素。也就是说,即使position为fixed,它的定位是相对于屏幕的,其堆叠上下文也不会被嫁接到根上,还是会随着父元素改变。这一点是比较反直觉,但也是我们可以利用的。

下面是一段实验代码,可相对直观的了解z-index作用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
<template lang="pug">
div
.bg__fixed
.bg
.box__1
.box__1-1
.box__1-2
.box__1-3
.box__1-4

.box__2
.box__2-1
</template>

<script>
export default {
}
</script>

<style lang="stylus">
.bg
height 100vh
width 100vw
position relative

.box__1
position absolute
left 100px
top 100px
height 250px
width 250px
background red
z-index 100

> div
position absolute
width 100px
height 100px

.box__1-1
left 50px
top 50px
background gray
z-index 100

.box__1-2
left 100px
top 100px
background black
z-index 105

.box__1-3
left 0px
top 100px
background blue
z-index 1

.box__1-4
left -50px
top -50px
background green
z-index -100

.box__2
position absolute
left 0
top 0
height 250px
width 250px
background purple
z-index 90

> div
position fixed
width 200px
height 200px
left 0
top 0
background pink
z-index 1000
position fixed

.bg__fixed
width 100px
position fixed
height 400px
left 180px
top 50px
background green
z-index 95
</style>

乱

直接看图很混乱,试一下代码就会很明白了。另外这里还有一个小点:当z-index为负数时,它会被流式布局的元素遮挡,但是即使为负数,它依然会覆盖在父元素的background上方

一般来说,如果我们的布局很简单,全是流式布局,那么这种情况下这个需求就很好搞:直接对需要高亮的元素进行一个:
position: relative
z-index: x(x>遮罩的z-index)
就行了
但是实际上我们的产品和ui常常会提一些需求,必须或者是最好用相对布局来做,有时候就导致了我们目标的元素被包在了新的堆叠上下文内,这样简单暴力的方法就没用了。

一个比较通用的方法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
<template lang="pug">  
.content
.content__item(
v-for="i in [1,2,3,4,5,6]"
) {{ i }}
.content__item__button high light!
.modal
</template>


<style lang="stylus">
.content
width 300px

> div
height 50px
display flex
align-items center
justify-content center

.content__item:nth-child(odd)
background red
.content__item:nth-child(even)
background green

.content__item__button
background blue
color white
position relative
z-index 101

.modal
position fixed
z-index 100
height 100vh !important
width 100vw
top 0
left 0
background: black
opacity: 0.6
</style>

思路就是,让需要被高亮的元素和遮罩层处于同一个父元素之下,这样可以保证它们处在同一个堆叠上下文,则可保证一定能使高亮元素能堆叠在遮罩层上。另一方面,当引导激活时,可以调整父元素对应的堆叠上下文,令其能覆盖其他所有元素。这样就可以保证引导页需要的效果:有且仅有目标元素在遮罩层上方。

BITCOIN PRICE FINTECHZOOMBITCOIN ARCHIVE TWITTERWHY OWNING BITCOIN IS MUCH SMARTER THAN OWNING GOLD

公司最近要开发一个qq机器人,于是产品很自然就有了个需求:希望在h5推广页面上能点击一个链接直接跳转到qq内的机器人。结果为了搞清楚这个狗比api我花了前前后后快两天的时间……

首先,pc端(windows,mac不清楚)和手机端的解决方案是不同的。pc上的解决方案最简单:
直接使用这样一个链接即可 http://wpa.qq.com/msgrd?v=3&uin=qqIDHere&site=qq&menu=yes
uin处填入qq号。这个链接不会直接允许发起私信,而会要求先加好友。不过由于需求只需要找到机器人用户,这个方案是直接可行的。

对于手机端,一开始的想法是使用二维码分享,即令用户跳转到二维码对应的链接。每个qq用户都可以生成一个自己的二维码,扫码之后就可以跳转到个人页面,那么很自然就会认为这个二维码对应的链接就能解决问题。然而事情并没有这么简单……
该方案的url形式如下:
https://qm.qq.com/cgi-bin/qm/qr?k=4z7n7RymBjt83***0DD-Mhxezj__h7zg6&noverify=0
(打了个码)
这里可以发现一个问题,二维码方案的url里面不是靠qq号来辨识的。如果需要有多个机器人,就会有繁复的配置。不过这个问题还算可以解决。这个方案被抛弃的主要原因是另一个问题:只有在qq内部打开这个链接才是百分百可靠的。这个链接在某些浏览器下无法正常跳转到qq,这里点名批评safari,我手机里装了qq他还坚持不懈地给我跳app store,把参数全给整丢了。这哪儿行啊,safari用的人也不少,否决否决。

手机上跳转app,其实最自然的想法是用urlschema,然而并没有在qq的官方文档里找到这方面的资料……只有在互联网大海中的几个琐碎角落发现了这样一个东西:
mqqwpa://im/chat?chat_type=wpa&uin=qq_id_here&version=1&src_type=web
这个url schema会在手机端打开对应qq号的私信界面,通过“qq咨询”发起会话。看到这个的时候我老开心了,结果一私信,好家伙,直接红色感叹号。提示“对方没有开通在线咨询,无法发起临时会话”。也挺河里,qq不让你随便找个人就骚扰。那么如果我想被骚扰怎么办呢?有两个步骤:
1、对该qq号进行设置,发起临时会话的许可中勾选上qq咨询
2、打开https://shang.qq.com/v3/index.html,使用需要被骚扰的qq号登录,之后点击顶部的推广工具tab。这时候他会弹出一个xxx服务免费开通的弹窗,直接给他开通了。之后这个qq号就能收到私信了。

CRYPTO ACCOUNTANT123 DEMANDSTRADING SERVICES

小程序……治疗低血压的超人

话不多说直接开喷。
首先小程序不知道为什么要搞一套长得像html和css的东西但是又要搞出不同的表现已经不是一天两天了,但是遇到各种神奇jb bug还是得骂一骂。

对于一个标准的html规范,input的placeholder的样式是用以下方式展示的:

1
2
3
input::-webkit-input-placeholder {
// some style
}

但是小程序的特立独行早已广为人知,小程序是这么整的:

1
2
3
4
5
6
7
// 首先,小程序当然是直接把标准的placeholder选择器杀了
<input placeholder-class="placeholder-class">
</input>

.placeholder-class {
// some style
}

你以为这就完了?没有。
上面的代码在ios设备上运行正常,但是对安卓设备有明显的问题:当用户对input focus的时候,安卓设备的placeholder即使没有任何输入也会直接变为输入内容的样式,非常nm刺激。
于是我们只能整一个组件来判断内容是否为空来给他加上不同的样式但是这样就要写一大堆东西我懒得放到这篇文章里反正这些代码是个人都会而且毫无意义。

今天的血压也是很高呢。

2 5 LAKH IN DOLLARSBEST CRYPTO MINING PROGRAMWSM CRYPTO PRICE

简书里看不了幻影坦克,点了也没用
大概很久很久以前有过一阵子很火的一种图片,大概就是一张图片,在预览的时候看起来是一张图,然而点进去看详情的话是另一张图。正好最近这种图不知道为啥就又火了,那就来讲讲这种图的原理。

“幻影坦克”图实际上是一张有特殊透明度分布的图片,而它实现“预览一张,详情一张”的原理实际上有点无聊:由于这张图的透明度分布,可以让图片在不同的背景色下看起来是不同的。而所谓的预览和详情不同只是利用了许多app移动端的设计:预览时的背景是白色,而全屏查看大图时,则会选择黑色作为图片背景。而在不同透明度下就显出了不同的图案。
上图在黑色和红色背景下的表现
于是幻影坦克很多问题就很好解释了:为什么幻影坦克图不生效?(只有两张图中的某一张)可能是因为:
1、某些途径下复制/保存文件会丢失alpha通道也就是透明度信息,强制把背景变成黑色,那自然是没有。
2、图片所在的平台没有使用“预览白色背景,详情黑色背景”的设计。

SEC ETHEREUM

我们工程师自然不会满足于这样流于表象的定性解释,如果我们要把两张图合在一起,那么有什么通用的解决方案呢?
好,首先我们通过查阅资料找到了透明度叠加的公式:
Alpha混合公式如下:
R(C)=(1-alpha) * R(B) + alpha * R(A)
G(C)=(1-alpha) * G(B) + alpha * G(A)
B(C)=(1-alpha) * B(B) + alpha * B(A)
于是我们取红色为例,假设我们的目标表图(预览图)的某个坐标的像素点的红色为r1,目标里图(详情图)的某个坐标的像素点为r2。而我们图片本身的红色为r,透明度为alpha,则对于黑色、白色背景而言,上式变为:
r1 = alpha * r
r2 = 255(1 - alpha) + aplha * r

很容易解得方程:
alpha = (r1 - r2) / 255 + 1
r = r1 / alpha = r1 / ((r1 - r2) / 255 + 1) (alpha≠0)

*当alpha = 0时,r有无限根

于是我们获得了一个映射,对任意两张目标图片,已知其像素分布的情况下,都可以得到幻影图对应坐标下的颜色和对比度。

于是,我们的图片方案就完成啦!可喜可贺可喜可贺……

(然而事情并没有这么简单)
需要注意到一件事情,颜色和透明度的取值是有范围的。对于色值r而言,取值范围在[0, 255],而对于透明度,取值在[0, 1]。
首先我们看alpha的取值范围:
0 <= alpha <= 1
将alpha的解带入可得:
0 <= (r1 - r2) / 255 + 1 <= 1
化简得
-255 <= r1 - r2 <= 0
注意到由于r1和r2都是色值,因此取值范围都是[0, 255]。因此显然r1 - r2 >= -255总是满足,因此alpha的取值范围约束使得目标图片的色值有一条件:
r2 >= r1

接下来看r,对r,有
0 <= r <= 255
代入r之后,化简可得:
0 <= r1 / (r1 - r2 + 255) <= 1
由刚才alpha的约束和开头alpha=0情况的讨论可知:r1 - r2 > -255,而r1 >= 0。因此 r1 >= 0,r1 - r2 + 255 > 0。于是 r1 - r2 + 255 >= 0始终成立。
对r1 / (r1 - r2 + 255) <= 1,根据上述讨论可化为:
r1 <= r1 - r2 + 255
得到r2 <= 255,亦始终满足。

于是制作幻影坦克图的完备性如下。
对任意两张图片1和2,若满足:
图1在任意一点的rgb色值均大于图2在相同点的色值,则可以以图1为表图,图2为里图做成一张幻影坦克图。

那完了呀!幻影坦克图对目标图片有要求,那岂不是很多表情包都做不了了?

CRYPTO HACK BOOKLET

我们是程序员嘛,我们最擅长的事情就是解决不了的问题让产品改需求。细心的同学可能已经发现了一件事,就是幻影图的表图看起来都比较白,里图看起来都比较黑。如果更细心一点去拿取色器看,会发现:里图里的白色部分和表图里的黑色部分都不是纯黑纯白,甚至都不算是黑色或者白色,而是在128左右的灰色。

这下破案了,解决不了问题可以改变问题嘛。我们更改一下需求,把需求的目标图都强行变成满足色值要求的图片不就行了?
于是我们给目标表图叠上一层半透明白色,给目标里图叠上一层半透明黑色,之前的r1、r2就变成了r1’、r2’:
r1’ = r1 / 2
r2’ = r2 / 2 + 128
由于r1 r2都在0到255之间,这样就可以让r1’和r2’都满足幻影坦克的成图条件,之后根据映射进行生成,就可以了。

SHIB COINGECKOHOW TO START TRADING CRYPTOBLOCKCHAIN TRANSACTION UNCONFIRMED

对于app而言,分享功能总是必要的,所以小程序自然也会有这个功能……哎,有的同学可能要问了,小程序这个nt玩意搞出来不就是为了微信自己独占流量吗?那为什么还有分享呢?哎,那就对了,因为小程序的分享只能分享给微信好友和朋友圈。不过这个不是我们今天要讨论的重点。

一般来说,app的分享通常是调用某个第三方接口,就会唤起第三方app并进行分享。偶尔地,有直接弹出分享菜单的api,那我们看看微信小程序是怎么做的。

假如你是第一次做小程序分享的话,你应该会在文档里找到两个看起来比较有用的api:
1、button组件的属性open-type=”share”
2、showShareMenu,文档的描述是“显示当前页面的转发按钮”

上面的两个,1会是你想找的东西,2则是命名欺诈。
小程序实际的分享流程如下:
0、(可选)开启胶囊内的分享功能
在页面内调用showShareMenu函数可以将当前页面右上角胶囊内的分享功能开启

1、发起分享:
由open-type=”share”的button组件发起(顺带一提虽然名字叫share但是你搜分享是搜不到的,得搜转发)
由右上角胶囊内的三个点里的选项发起
这是小程序仅有的发起分享的方式,也就是说不能通过函数发起分享

2、分享配置与回调:
在Page构造器内添加onShareAppMessage
onShareAppMessage会根据传入的参数中的type字段是button还是menu来告知是通过点击页面内按钮还是右上角胶囊触发了本次回调
函数返回值则为本次分享的标题、文字等设置

总之,小程序作为张小龙同志的优秀作品,没有那些直来直去的低级api,整个分享功能都是通过按钮、胶囊、页面回调和showShareMenu几个不同地方发起的环环相扣的api来组合完成的,不把微信文档内showShareMenu和“显示当前页面的转发按钮”文字的个中深意参透的人自然是搞不懂。

对了,还有一件事,如果是qq小程序的话,不把他默认关闭的胶囊分享打开是不会给你过审的

CRYPTO FUDCRYPTO THAT WILL MAKE YOU RICH IN 2025WHEN IS THE BITCOIN CONFERENCE IN NASHVILLE

(讲笑话)
本来第二篇是打算给小程序的分享api相关的,但是订阅消息实在是太搞笑了所以给他加急

一般来说,我们使用app的时候,无论是ios还是安卓,都可以给用户发送系统推送。但是小程序的场景下怎么办呢?小程序提供了订阅服务,允许通过小程序内部的途径给用户发消息。看起来好像很不错?我们看看文档

原文如下:“
订阅消息包括两种:

一次性订阅消息

一次性订阅消息用于解决用户使用小程序后,后续服务环节的通知问题。用户自主订阅后,开发者可不限时间地下发一条对应的服务消息;每条消息可单独订阅或退订。

长期订阅消息

一次性订阅消息可满足小程序的大部分服务场景需求,但线下公共服务领域存在一次性订阅无法满足的场景,如航班延误,需根据航班实时动态来多次发送消息提醒。为便于服务,我们提供了长期性订阅消息,用户订阅一次后,开发者可长期下发多条消息。

目前长期性订阅消息仅向政务民生、医疗、交通、金融、教育等线下公共服务开放,后期将逐步支持到其他线下公共服务业务。

看起来好长一段,我们直接总结:长期订阅是人上人小程序,你我不配用。普通用户只能用一次性订阅消息。
在看一次性订阅消息之前我们先看看长期订阅的流程:
1、用户点击触发事件,前端向用户请求订阅
2、若用户同意订阅,则后端可以向用户发送消息;不然,用户的消息无法收到。
看起来是很正常的流程对吧?对,长期订阅是很正常的流程,所以对比之下,一次性订阅就是非常不正常的流程

一次性订阅的流程是这样的:
1、用户点击触发事件,前端向用户请求订阅
2、若用户同意前端弹出的订阅申请,则“为用户的订阅次数计数器+1”
3、若计数器的数量为0,则无法收到后端的消息;若不为0,则可以收到,且每当收到信息的时候使计数器-1

啥意思呢,可以理解为微信给的一次性订阅本质上是一个长期的异步请求。它对用户的某个操作给出一个异步的回应,比如说:用户点击了一次下单,就可以给用户一次到货的回应。这是一次性订阅适合的场景。
不适合的场景是什么呢?几乎所有的其他消息推送的场景,比如用户的集体推送,社区类的点赞回复通知,可以说微信小程序一概不支持。如果您还在为这些功能如何实现而烦心,那大可不必,因为微信小程序根本就不支持。

拓展阅读之一次性订阅消息的解决方案:订阅消息,你懂与不懂,它就在那里
https://developers.weixin.qq.com/community/develop/article/doc/0008802e8381e0eeabb92c9975b013

NEURALINK CRYPTOBLUE CHIP CRYPTOKUCOIN USA

(发牢骚)
在微信(和qq)小程序中,许多元素是有自带的默认样式的,这很正常,web也是这样。但是小程序中默认样式的选取则显得极端迷惑。
先看一下web标准里图片尺寸的css行为:
1、若width和height均未指定,则按照图片原尺寸显示图片
2、若width和height仅指定了一方,则另一方根据图片原始比例进行缩放
3、若width和height均已指定,则按指定的width和height进行显示

而在小程序中,如果我们按照web中的习惯进行使用,则会发现变成了这样
1、若width和height均已指定,那自然是猴面雀也能正常渲染
2、若width和height仅指定了一方,则发现未指定的一方变得血妈长
3、若width和height均未指定,则显示为固定大小

我说停停,怎么回事。然后一看开发者工具里的样式,哦,原来是小程序给image元素指定了默认宽高,搞小聪明偷袭开发者:
没十年脑溢血想不出这个设计

我一看那好办啊,如果我希望“不指定”某个样式,我把他改成默认样式就行了。在mdn上我们可以看到其实每个css的样式都有自己的默认值,当我们什么也没指定的时候就相当于指定了这个默认值。于是我们发现width和height属性的默认值是auto。
然后我们就用web试刀,哎,试了一下,果然指定为auto和不指定的缩放效果是一样的,很顺利啊,然后我们就进小程序。啪,我设置width为auto,他直接宽度变成0,来打我脸。
当时流眼泪了
那也就,就没什么好说的了呗,微信小程序就是不支持web规范内css的“width或height为auto时自动缩放图片”

那可能有的水友就要问了:那如果我想要一个高度固定,宽度根据图片比例自适应的image元素怎么办呢?哎,微信也发现这个问题了,image元素的mode属性可以做这件事。
你以为这就结束了?没这么容易!
用了一下,非常有效,孩子很喜欢,给好……嗯?
发现一个问题:heightFix是有最低版本限制的(警觉)
后续调研发现:heightFix确实在低版本无效。另外,虽然qq小程序“兼容”微信小程序,但是两者对一些feature的支持版本不同,qq小程序也是不支持heightFix的。这啥意思呢,就是说脑溢血的小程序不知道是程序员还是产品经理觉得,只需要widthFix就够了嘛,要heightFix干嘛呢!后来大概是被人骂了才大发慈悲加上了罢。
另外还有个彩蛋:widthFix到底是什么?
好活,好活啊
我悟了,widthFix就是小程序给图片加了个和他高度/宽度对应比例的内联样式

那么问题来了,如果我要在低版本或者qq小程序得到heightFix的行为,我该咋办嘞?
那大概只能手动去做一下heightFix的事情了吧。image元素的bindload钩子内可以获取到图片的信息(只有宽高),这样我们就可以用js去处理这个问题了。话说回来,既然你的bindload里都有且只有宽高了,那我寻思其实小程序已经意识到自己设计很nt了吧?

BEST CRYPTO DEBIT CARDBSC COINBITCOIN MINING LOGIN

来扯扯js里的种子随机
想当年刚学编程学c的时候感觉c的随机函数还要指定种子一步一步跳很麻烦——现在写前端写js了,遇到了需要通过种子固定结果的随机,人的一生啊,就是不可以预料。于是就到处找了一下在js里实现用种子随机的方法。

查了一点资料,明白了这个问题的大概情况:其实基本就是js阉割了这个接口(大概)

如果看java的源码可以发现其随机实现是这样的:

1
2
3
4
5
6
7
8
9
protected int next(int bits) {
long oldseed, nextseed;
AtomicLong seed = this.seed;
do {
oldseed = seed.get();
nextseed = (oldseed * multiplier + addend) & mask;
} while (!seed.compareAndSet(oldseed, nextseed));
return (int)(nextseed >>> (48 - bits));
}

虽然我并不懂java()但是这里的代码还是基本能看懂的,把这段代码换成数学公式其实就是:
X[n+1] = (a·X[n] + c) mod m
即一个数列的递推公式。这个东西是一种 “伪随机数生成器”(pseudo random number generator,PRNG),特别地,这个形式的PRNG叫线性同余法(linear congruential generator, LCG)。
一般地,一个PRNG要求其具有周期性,并且希望其是均匀分布的。
对于LCG有一个重要的Hull-Dobell定理:
当且仅当
(1) c与m互素
(2) a - 1可以被所有m的质因数整除
(3) 如果m能被4整除,那么a - 1也能被4整除
以上三个条件同时满足时,X是一个周期为m的序列。

(我承认我没彻底搞清楚)不过在js里实现种子随机的思路大概明确了:首先是按照LCG整的如下一段代码:

1
2
3
4
5
6
let _seed = initSeed
const seededRandom = _ => {
_seed = (_seed * a+ b) % m

return rnd = _seed / m
}

然后选取符合啥啥啥定理的三个abm参数就可以了。自然,早有老哥给我们选好了参数:
a=9301
b=49297
m=233280
当然你也可以自己去找参数,不过注意除了要符合Hull-Dobell定理之外,作为随机数生产器,要让m尽量大。

好,那么接下来问题就来了:js自己的random是怎么处理的呢?mdn直接告诉了我们答案
“Math.random() 函数返回一个浮点数, 伪随机数在范围从0到小于1,也就是说,从0(包括0)往上,但是不包括1(排除1),然后您可以缩放到所需的范围。实现将初始种子选择到随机数生成算法;它不能被用户选择或重置。”
哦!源赖氏js本来就有按种子随机的功能,只是他不给你用。
js,不愧是你
此外,js中也有类似java中“真随机”的函数,“Crypto.getRandomValues() 方法让你可以获取符合密码学要求的安全的随机值。传入参数的数组被随机值填充(在加密意义上的随机)。”不过既然js主要是前端在用,那么密码学上的安全问题应该是基本用不上的(。