ASP.NET Web API 返回 Unauthorized 401 未授权代码:
return Unauthorized(new AuthenticationHeaderValue("getUserInfo"));
响应的头部信息是这样的:
以微信小程序中获取 header 的 www-authenticate 为例,不同操作系统中获取的 key 是不一样的:
iOS:
Android:
微信开发者工具:
所以,小程序端获取该值,暂时使用以下代码吧:
let www_authenticate;
if (!www_authenticate) {
www_authenticate = res.header['Www-Authenticate']; // iOS
}
if (!www_authenticate) {
www_authenticate = res.header['WWW-Authenticate']; // Android / 微信开发者工具
}
if (!www_authenticate) {
www_authenticate = res.header['www-authenticate']; // ASP.NET Web API 原始输出
}
if (!www_authenticate) {
www_authenticate = res.header['WWW-AUTHENTICATE']; // 其它情况
}

传统的 LIKE 模糊查询(前置百分号)无法利用索引,特别是多个关键词 OR,或在多个字段中 LIKE,更是效率低下。本文研究对文章进行分词以提高检索的准确度和查询效率。
根据自己的编程语言选择一款合适的中文分词组件,我在 ASP.NET 平台下选择了 jieba.NET。
设想的步骤:
分别对文章标题、标签、正文进行分词,保存到一张分词表上。该表把“文章 ID”和“词语”设为联合主键,用 3 个字段记录该词语分别在标题、标签、正文中出现的次数,另外还可以按需要添加文章分类 ID、文章创建时间等字段。
当用户输入关键词进行检索时,先将关键词分词,在分词表中用 in 语法查询到所有相关的记录;
使用 group by 语法对查询结果按文章 ID 分组;
关键在排序上,理想的排序是:
a. 先按搜索关键词中不同词语的出现量排序,即:若搜索关键词分词后是 3 个词语,那么全部包含这 3 个词的文章优先,只匹配其中 2 个词语的其次;
b. 再按搜索关键词在文中累计出现的次数排序(考虑权重),即:我们先假定标题和标签的分词权重为 5(意思是一个分词在标题中出现 1 次相当于在正文中出现 5 次),那么累加每个分词在标题、标签、正文的权重次数,得分高的优先;
c. 再进一步考虑文章的发布时间,即将文章的发布时间距离最早一篇文章的发布时间(或一个较早的固定日期)相隔的天数,乘以一个系数加入到权重中,这个系数按不同文章分类(场景)不同,比如新闻类的大一点,情感类的小一点)。乘以系数时一篇文章只加权一次,不要加权到每个分词。
d. 根据需求还可以加入文章热度(阅读数)的权重。
根据上述逻辑对一个有 18 万篇文章的内容管理系统进行改造,循环所有文章进行分词统计,得到一张包含 5 千万条记录的分词表(系统中部分文章只有标题、标签和外链,没有正文,否则更多)。
由于查询中包含 in、group by、count、sum、运算等,再若分类是无级限的,即文章分类 ID 也是 in 查询,然后分页,即使创建索引,效率也只能呵呵了。
简化:
不对正文进行分词;
不按权重进行排序;
那么分词表的记录数降到 250 万条,同样用 in 查询分词,先按搜索关键词中不同词语的出现量排序,再按发布时间排序,分页后获得一页的文章 ID 集合,再去文章表中 in 获取详细信息(注意保持一页中的排序)。
添加相关索引后,查询一个包含 3 个分词的关键词仅需十几毫秒。因为 in 的内容比较离散,所以索引的利用率比较高。

当微信支付的 notify_url
填写的是 https 的回调地址时,如果遇到支付成功但没有接收到回调的情况,可能是服务器开启了 SNI。
SNI,即服务器名称指示,用于在一台服务器上支持多个网站使用不同的 SSL 证书。
解决方案:
方法一、
notify_url
改为 http 的回调地址,缺点是容易被运营商 QJ,而且如果日后网站开启强制要求 https 访问就会使回调失败;方法二、【不推荐】可以为该网站单独配置一台服务,不使用 SNI,缺点是增加成本;
方法三、【推荐】该网站仍然“需要服务器名称指示”,在 IIS 中给默认网站(不是当前网站)添加一个 https 的域名绑定,不勾选“需要服务器名称指示”,证书随便选一个。缺点是时间一长容易忘记有这回事,不要轻易删除该绑定就好了,而且在网站搬迁的时候也同样要做该配置。
感谢 V2EX

本文讲述使用网页开发适用于宴会、活动、年会现场 LED 大屏幕显示抽奖、抢楼、游戏、视频等互动界面,通过在浏览器中打开后 F11 全屏投影到大银幕上。
设计思路:
为了保证最佳的显示效果,设计稿的尺寸按大屏幕的“宽高比”来做(本文以4米*3米的大银幕为例,设计稿以800*600像素为例)。
由于电脑的视频输出分辨率很有可能与大银幕的宽高比不同,那么我们全屏投影后会界面会有变形,但是投影设备接收到电脑视频信号后再全屏显示到大银幕时,界面又会变回正常。
按设计稿的尺寸开发网页,并使用 CSS3 的 scale 实现整体伸缩,目的是自适应窗口尺寸来全屏显示内容,无留白、无裁切。整体拉伸的要求是文字、图片、背景、间距、iframe 等所有元素以统一的比例拉伸,无错位。
网页上通过 JS 设置一些快捷键来实现页面展示内容的切换。
若投影设备功能有限或操作员水平有限等原因导致无法顺利设置输入输出分辨率时,网页可设置 x 轴和 y 轴两个方向的伸缩系数、定位偏移量等,更灵活地实现完美投影需求。
开发要点:
网页尺寸按设计稿来定(以 800x600 为例)。
使用 CSS3 的 transform 的 scale 来实现缩放,以电脑显示器分辨率 1920x1080,宽放大 1920/800 倍,高放大 1080/600 倍,即可将设计稿全屏展示到电脑显示器上。为了更灵活,我们使用窗口大小来代替显示器分辨率,这样不管怎么改变窗口大小或者全屏,设计稿都能完整地显示在窗口上。
在页面初始化和窗口尺寸变化时重新调整设置上述比例。
代码参考:

这是一篇可能解决困扰了 .NET 程序猿多年的文章!
首先,使用 Visual Studio 2019 创建一个 ASP.NET WEB 应用程序,直接选用了 MVC 模板。
直接发布项目,默认的设置如下图:
对于小型项目,按默认设置发布基本可满足正常运行,首次运行打开第一个页面基本在 5~6 秒(视服务器配置),其它页面的首次打开也基本在 1~2 秒完成,非首次瞬间打开。
一旦项目功能变得复杂,文件增多,会导致发布后首次运行打开第一个页面 30 秒以上,其它页面的首次打开 10 秒左右,非首次瞬间打开。
这是因为项目在发布时没有进行预编译,而是在用户访问网页时动态编译,一旦应用程序池回收,或项目文件改动,都会重新编译,再次经历缓慢的“第一次”,这是不能忍的。
于是咱们来研究一下“预编译”。
在发布页面勾选“在发布期间预编译”,这时发布会在“输出”窗口显示正在执行编译命令(编译时间会比较长):
该选项对发布后的文件结构和内容影响不大,因此对首次执行效率的提升也不大,重要的在后面。
在“高级预编译设置”窗口中:
我们针对预编译选项和合并选项分别做测试。
不勾选“允许更新预编译站点”,会致 bin 目录中生成许多 .compiled 文件,而所有 .cshtml 文件的内容都是:
这是预编译工具生成的标记文件,不应删除!
如果是 Web From 网站,.aspx/.ashx 等文件内容同上。
尝试打开网站页面,你会发现,除了项目启动后的第一个页面仍然需要 1~2 秒(无 EF),其余每个页面的首次都是瞬间打开的(EF 的首次慢不在本文讨论范围)。这让我对预编译有一种相见恨晚的感觉!
这里偷偷地告诉你,把 Views 目录删掉也不影响网页正常打开哦~为什么不让删,咱也不敢问,咱也不敢删。
目的达到了,有一些后遗症需要解决,比如 bin 目录内杂乱无章。
选“不合并。为每个页面和控件创建单独的程序集”,结果是 bin 多出许多 App_Web_*.dll 文件。
选“将所有输出合并到单个程序集”,填写程序集名称。这时会发现“输出”窗口执行了命令:
如果程序集名称跟已有名称冲突,会发生错误:
合并程序集时出错: ILMerge.Merge: The target assembly '******' lists itself as an external reference.
查看 bin 目录,.compiled 文件还在,但 App_Web_*.dll 合并成了一个程序集。FTP 的“不合并”也是把所有 App_Web_*.dll 上传一遍,所以不存在相对于“合并”的增量发布优势。
是不是勾了“视为库组件删除(AppCode.compiled 文件)”.compiled 文件就会不见?意外之外,情理之中,bin 没有什么变化。
继续选择“将各个文件夹输出合并到其自己的程序集”,呃,bin 中文件数不降反增。
最后选择“将所有页和控件输出合并到单个程序集”,同样程序集名称不能冲突。结局令人失望,bin 中仍然一大堆 .compiled 和 App_Web_*.dll。
整理成表格:
1 | 允许更新预编译站点 aspnet_compiler.exe -v / -p \Source -u \TempBuildDir 重复发布后 bin 目录文件数:不会增多(页面不进行预编译) |
2 | 不允许更新预编译站点,不合并 aspnet_compiler.exe -v / -p \Source \TempBuildDir 重复发布后 bin 目录文件数:.compiled 不会增多,App_Web_*.dll 增多 |
3 | 不允许更新预编译站点,不合并。为每个页面和控件创建单独的程序集 aspnet_compiler.exe -v / -p \Source -c -fixednames \TempBuildDir 重复发布后 bin 目录文件数:不会增多(编译后的文件名固定,FTP 方式的部分 App_Web_*.dll 文件可能实现增量上传) |
4 | 不允许更新预编译站点,将所有输出合并到单个程序集,不勾选“视为库组件” aspnet_compiler.exe -v / -p \Source -c \TempBuildDir aspnet_merge.exe \TempBuildDir -o 程序集名称 -copyattrs AssemblyInfo.dll -a 重复发布后 bin 目录文件数:不会增多(.compiled 固定名称,App_Web_*.dll 合并为一个) |
5 | 不允许更新预编译站点,将所有输出合并到单个程序集,勾选“视为库组件” aspnet_compiler.exe -v / -p \Source \TempBuildDir aspnet_merge.exe \TempBuildDir -o 程序集名称 -copyattrs AssemblyInfo.dll -a -r 重复发布后 bin 目录文件数:不会增多(.compiled 固定名称,App_Web_*.dll 合并为一个) |
6 | 不允许更新预编译站点,将每个文件夹输出合并到其自己的程序集,前缀 aspnet_compiler.exe -v / -p \Source -c \TempBuildDir aspnet_merge.exe \TempBuildDir -prefix 前缀 -copyattrs AssemblyInfo.dll -a 重复发布后 bin 目录文件数:.compiled 不会增多,App_Web_*.dll 增多 |
7 | 不允许更新预编译站点,将所有页和控件输出合并到单个程序集 aspnet_compiler.exe -v / -p \Source -c \TempBuildDir aspnet_merge.exe \TempBuildDir -w 程序集名称 -copyattrs AssemblyInfo.dll -a 重复发布后 bin 目录文件数:.compiled 不会增多,App_Web_*.dll 增多 |
bin 目录下,页面的程序集文件(App_Web_*.dll)增多的原因是编译后的文件名不固定,发布到会导致残留许多无用的程序集文件。
相比较,如果第 3 种能实现 FTP 稳定的增量上传的话是比较完美的(还有一个缺点是:如果页面有删除,目标 bin 内对应该页面的 .compiled 和 .dll 不会删除,这跟“允许更新预编译站点”是一个情况),那么第 4 种 或第 5 种也是不错的选择(同样有缺点:执行合并比较慢)。
测试一个有 300 个页面的项目,compiler 用时约 2 分钟,merge 用时约 5 分钟,发布用时约 4 分钟。
这里有个槽点,执行预编译和合并是佛系的,CPU 和磁盘使用率永远保持在最低水平。
所以如果是要经常修改的项目,那么建议选择“不合并”的第 3 种发布方式:
“视为库组件(删除 AppCode.compiled 文件)”:移除主代码程序集(App_Code 文件夹中的代码)的 .compiled 文件。 如果应用程序包含对主代码程序集的显式类型引用,则不要使用此选项。
补充:
不允许更新预编译站点发布后,因为前端页面没有内容,因此无法单独修改发布(单独发布一个页面后,在访问时不会生效!),只能全站发布,耗时较长;bin 目录有变动将导致使用 InProc 方式存储的 Session 丢失。
预编译的另一个优点是可以检查 .aspx / .cshtml 页面 C# 部分的错误(特别是命名空间和路径引用)。
改为预编译发布后,可以将服务器上残留的 .master / .ascx / .asax 删除,但不能删除 .aspx / .ashx /.config 等。
VS 发布到 FTP 经常会遗漏一些页面文件,不合并时会遗漏独立文件,合并后也会遗漏合并后的 .dll 的文件,合并的好处就是方便检查是否完全上传发布。
慎选“在发布前删除所有现有文件”!一旦勾选发布,世界就清静了,所有网友上传的图片附件以及网站运行产生的其它文件,消失得无影无踪。不管发布到文件系统还是发布到 FTP 都一样。当然,如果是先发布到文件系统,再通过 FileZilla 等 FTP 软件上传到服务器的,建议勾选此项。
.NET Core 应用程序部署:https://docs.microsoft.com/zh-cn/dotnet/core/deploying/index
由于上传文件时 bin 目录文件较多,理论上 bin 目录内的文件有任何改动都会重启应用池,而且 VS 是单线程上传的,导致期间网站访问缓慢,服务器 CPU 升高,我的做法是:发布到文件系统,再使用专用 FTP 工具上传,上传用时约半分钟(如果大小不同或源文件较新则覆盖文件)。还嫌慢?那就打包上传,解压覆盖。
关于本文研究对象的官方解释:高级预编译设置对话框
出现“未能加载文件或程序集“***”或它的某一个依赖项。试图加载格式不正确的程序。”的问题时,先用改用 Debug 方式发布会报详细错误,一般是 .aspx 等客户端页面有 C# 语法问题,注意提示报错的是 /obj/ 目录下的克隆文件,应更改原文件。排除错误后关闭所有页面,再使用 Release 方式发布。

如果你是刚入手的小米 WiFi 电力猫,那么按说明书一步一步配置即可。这里记录一下移机的重置方法。
本次移机的场景是电力猫从一台小米路由器更换到另一台小米路由器的配置,两台小米路由器 WIFI 的 SSD 不同(假设为 WIFI-A 和 WIFI-B)。
错误做法:打开米家,切换到 WIFI-B 所在的房间,点击右上角的添加新设备,根据介绍插上子猫,并按子猫的配置键 5 秒以上。连上“xiaomi-plc-xxx”,米家显示正在连接,但到达 100% 后显示连接超时。
可能的错误原因:
没有插上主猫
该电子猫已经在米家上添加过了(在 WIFI-A 所在的房间),就无法添加到其它房间了(仅猜测,未实测)
于是,同时插上主猫和子猫,并且路由器和主猫使用网线连接,什么都不设置,过一两分钟,两猫自动会变成蓝灯(表示连接成功)。这时,如果刚才那步没有重置成功的话,打开米家主界面上的“小米电力线wifi扩展器”(如果没有删除过,还在 A 房间),里面会显示网络名称为 WIFI-A,还可能没有开“WIFI漫游”。这样虽然能连接上网,但有两个 SSD,不能实现漫游。
这时,按子猫配置键 5 秒以上,并静等一分钟以上,变回蓝灯后,再打开米家,就会看到网络名称可能是 WIFI-B-Plus,这时"WIFI漫游”也能打开了,完美扩展了 WIFI。
综上实践得出:
WIFI 是由子猫负责的,所以跟 SSD 有关的配置由子猫负责;
一台设备(这里指电子猫套餐)只能添加到米家一次,但可以更改设备所在的房间。
正确的重置电力猫步骤是:
同时插上主猫和子猫
主猫连上路由器
子猫按配置键 5 秒以上,等待一分钟以上
打开米家,进入“小米电力线wifi扩展器”,打开 WIFI 漫游
以上仅在使用小米路由器情况下实测,非小米路由器未尝试。

使用以下代码对字符串 s 进行判断:
var s = null;
if (s) {
console.log('true');
} else {
console.log('false');
}
测试当 s 为不同值时的结果如下:
s 的值 | if(s) 的结果 |
s = null; | false |
s = undefined; | false |
s = ''; | false |
s = ""; | false |
s = 0; | false |
s = 1; | true |
s = 2; | true |
s = -1; | true |
s = '0'; | true |
s= ' '; | true |
结论:
当 s 未定义或未赋值时,结果为 false;
当 s 为字符串类型时,长度为 0 即 false,长度大于 0 即 true;
当 s 为数字类型时,等于 0 为 false,不等于 0 为 true。
如果判断 s 的长度:
var s = null;
if (s.length > 0) {
console.log('true');
} else {
console.log('false');
}
那么会有以下结果:
s 的值 | if(s.length > 0) 的结果 |
s = null; | Uncaught TypeError |
s = undefined; | Uncaught TypeError |
s = ''; | false |
s = ""; | false |
s = 0; | false |
s = 1; | false |
s = 2; | false |
s = -1; | false |
s = '0'; | true |
s= ' '; | true |
结论:
当 s 未定义或未赋值时,异常;
当 s 为字符串类型时,长度为 0 即 false,长度大于 0 即 true;
当 s 为数字类型时,永远为 false。
这样的话,if(s.length > 0) 等同于 if(s.length)。
综上所述,判断一个字符串是否“有值且长度大于 0 ”,可以这样写:
var s = null;
if (s && s.length) {
console.log('true');
} else {
console.log('false');
}
相反,是否“无值或长度为 0”,可以这样写:
var s = null;
if (!s || !s.length) {
console.log('true');
} else {
console.log('false');
}
当然,如果要将不为 0 的数字类型也视为“有值”,只要 if(s) 或 if(!s) 即可。

这是一个数据可视化项目,基于D3.js。能够将历史数据排名转化为动态柱状图图表。
先来看看效果:
https://xoyozo.net/Demo/BarGraph
作者 Jannchie 见齐还提供了官方视频教程:
https://www.bilibili.com/video/av28087807
不过由于开源项目的不断更新,该教程的部分内容已失效,本文针对 2018-12-25 版本总结了一些常用的配置说明,仅供参考。
从 GitHub 下载源代码,在 src 目录中可以看到 4 个文件:
bargraph.html --> 运行示例页面
config.js --> 配置文件
stylesheet.css --> 样式表
visual.js --> 核心文件
作者并没有将代码封装为插件的方式,所以我们是通过修改 config.js 配置文件的方式应用到自己的项目中的。
visual.js 虽然是核心文件,但作者将部分示例中的代码也包含其中,但并不影响我们直接在自己的项目中引用。
以下是 config.js 中的主要属性:
属性 | 说明 | 参考值 |
encoding | 数据源(csv、json)等的文件编码 | GBK / UTF-8 等 |
max_number | 每个时间节点最多显示的条目数 | 10 |
showMessage | 控制是否显示顶部附加信息文字 | true / false |
auto_sort | 时间自动排序(详细含义、作用及限制见代码中注释) | true / false |
timeFormat | 时间格式,显示于图表右下角的时间 | %Y、%Y-%M-%D 等 |
reverse | 是否倒序 | true / false |
divide_by | 类型根据什么字段区分?如果是 name,则关闭类型显示 | |
divide_color_by | 颜色根据什么字段区分? 须要注意的是,如果配置成 name,则各条颜色不同(因为 name 值各异),如果配置成 type 等,那么相同 type 值的条颜色相同。 | 字段名 |
color | 指定部分或所有条的颜色,该项与 divide_color_by 设置有关。 | 以 divide_color_by = 'name' 为例,"中国"是其中的一项 name 值,那么该项将显示 #D62728 色: color: { '中国': '#D62728' } |
changeable_color | 若 true 则颜色的深浅将根据数据的增长率实时改变 | |
itemLabel | 左边文字 | |
typeLabel | 右边文字 | |
item_x | ||
interval_time | 时间点间隔时间 | 2 |
text_y | ||
text_x | ||
offset | ||
display_barInfo | 如果希望不显示,则可以设置较大的值,单位像素 | |
use_counter | ||
step | ||
format | 格式化数值 | |
left_margin | ||
right_margin | ||
top_margin | ||
bottom_margin | ||
dateLabel_x | ||
dateLabel_y | ||
allow_up | ||
enter_from_0 | ||
big_value | ||
use_semilogarithmic_coordinate | ||
long | ||
wait | 数据加载完成后开始播放前的等待时间 | 0 |
update_rate |

确定唯一凭证:
若项目仅提供微信端访问,那么使用微信的授权(即以微信 openid / unionid 作为唯一凭证)是一种体现注重用户体验的方式。如果项目需要应用到多个平台上,那么通过微信来授权变得不那么完美(譬如未安装微信客户端或平台不支持使用微信授权等)。因手机的使用率已明显高于电脑的使用率,再加上用户帐号实名认证的要求,以手机号码为作为帐号的唯一凭证是非常合理的。
登录唤起条件:
当用户打开需要登录授权的页面或触发需要登录授权才能执行的操作时。
场景实现:
平台 | 实现 |
微信网页/小程序 | 授权获取 openid / unionid 同时若未绑定手机号码则强制绑定(发送验证码的方式) 如果允许在微信上登录不同的用户帐号,那么需要提供【“手机号码 + 密码”登录】方式 |
手机 App | 提供【“手机号码 + 密码”登录】 提供【第三方帐号登录】,登录后强制绑定手机号码 参“京东”等 |
PC 端浏览器网页 | 提供【“手机号码 + 密码”登录】 登录框可切换成【扫二维码登录】,登录后强制绑定手机号码 |
手机浏览器网页 | 提供【“手机号码 + 密码”登录】 如果提供【第三方帐号登录】,那么可能需要在网页上手动输入第三方帐号的用户名密码,而非跳转到第三方 App 上(视第三方开放平台) |
l 【“手机号码 + 密码”登录】页面显示填写手机号码和密码框的表单,提供“忘记密码”、“短信验证码登录”、“手机快速注册”等功能。如果在可长期记住登录状态的场景中(如手机 App)也可取消密码,直接使用短信验证码登录。“手机快速注册”必须通过短信验证码实现实名认证。
l 【第三方帐号登录 / 扫二维码登录】App 中提供跳转相应第三方 App(如微信)授权登录,PC 端网页中提供微信扫二维码登录。登录获得 unionid,同时判断是否已经绑定过手机号码,若未绑则强制绑定,确保数据库中的每一条用户信息都有手机号码。
注:同一个微信帐号在不同的公众号和 App 中的 openid 不同,将这些公众号和 App 通过微信开放平台绑定后,同一个微信帐号的 unionid 是相同的,可用于判断同一用户。
流程图:
各平台登录设计:

新项目上线,在 Windows XP 中使用 IE 访问网站,http 正常, https 提示“无法显示网页”,这是因为当前电脑不支持网站提供的 SSL 协议。遂简单整理了各 IE 版本在不同操作系统下 SSL/TLS 的支持情况,表格仅供参考,可能会因特殊情况而有所不同:
SSL 2.0 | SSL 3.0 | TLS 1.0 | TLS 1.1 | TLS 1.2 | TLS 1.3 | |
XP - IE6 | 默认支持 | 默认支持 | 可选支持 | 不支持 | 不支持 | 不支持 |
XP - IE8 | 可选支持 | 默认支持 | 默认支持 | 不支持 | 不支持 | 不支持 |
Vista - IE7 | 可选支持 | 默认支持 | 默认支持 | 不支持 | 不支持 | 不支持 |
Vista - IE9 | 可选支持 | 默认支持 | 默认支持 | 不支持 | 不支持 | 不支持 |
Win7 - IE8 | 可选支持 | 默认支持 | 默认支持 | 可选支持 | 可选支持 | 不支持 |
Win7 - IE11 | 可选支持 | 可选支持 | 默认支持 | 默认支持 | 默认支持 | 不支持 |
Win8 - IE10 | 可选支持 | 默认支持 | 默认支持 | 可选支持 | 可选支持 | 不支持 |
Win8.1 - IE11 | 可选支持 | 默认支持 | 默认支持 | 默认支持 | 默认支持 | 不支持 |
Win10 早期 - IE11 / Edge | 不支持 | 可选支持 | 默认支持 | 默认支持 | 默认支持 | 不支持 |
Win10 - IE11 / Edge | 不支持 | 可选支持 | 默认支持 (部分版本 可选支持) | 默认支持 (部分版本 可选支持) | 默认支持 | 可选支持 |
几个注意点:
