喜欢“数据库优先”的朋友会发现,一旦 nuget 中将 MySql.Data 与 MySql.Data.EntityFramework 升级到版本超过 8.0.19,将无法正常打开 .edmx 文件进行从数据库更新模型。这个问题从 8.0.20 开始出现,在经过等待试验后续多个更新版本后,笔者认为 Oracle 官方不会再针对 8.0.20 及以后的版本支持可视化更新实体模型。
于是面临两个选择:退回到 8.0.19 版本,或使用其它数据库连接组件(如 Pomelo)。
使用其它组件将会更改映射的属性名称,对 Include 延时加载等机制也会发生改变,对于代码量比较大的项目修改成本还是比较大的。那么用回 8.0.19 呢?
在服务器上卸载 8.0.20+ 版本的 mysql-connector-net,安装 8.0.19 版本,重启 IIS 或重启服务器。
项目中,MySql.Data 与 MySql.Data.EntityFramework 也降至 8.0.19,发布后发现,仍然提示:
未能加载文件或程序集“MySql.Data.EntityFramework, Version=8.0.19.0, Culture=neutral, PublicKeyToken=c5687fc88969c44d”或它的某一个依赖项。找到的程序集清单定义与程序集引用不匹配。 (异常来自 HRESULT:0x80131040)
几个月来,我一直认为是服务器上的高版本 mysql-connector-net 没有卸载干净,今天才发现原因是新发布的项目 bin 目录中的 MySql.Data.EntityFramework.dll 版本号仍然高于 8.0.19。手动替换成 8.0.19 后可正常访问。
既然 NuGet 已经将 MySql.Data.EntityFramework 的版本降至 8.0.19,而且 web.config 中所有的版本号也都一致,解决方案的 packages 目录下的也只有 MySql.Data.EntityFramework.8.0.19 这一个版本,甚至连项目源代码中的 bin 目录下的该文件版本也是 8.0.19,可发布后的 MySql.Data.EntityFramework.dll 版本却仍高于 8.0.19?
于是在解决方案中找到以下路径:
\obj\Release\Package\PackageTmp\bin\MySql.Data.EntityFramework.dll
重新发布时,这个文件并没有按照 NuGet 中的版本来覆盖,因此只需要在发布前删除整个 \obj\ 目录再次发布即可解决问题。
场景:
打印机只有 USB 接口,没有网线接口。
方案:
使用一台电脑连接打印机,并共享给局域网其它电脑。
将连接打印机的电脑称为服务机,局域网其它电脑称为客户机。
使用 Windows 7 作服务机比使用 Windows 10 更容易设置成功。
以下过程以使用 Windows 7 作服务机为例,Windows 10 的操作稍有不同。
服务机:
安装打印机驱动,打印测试页成功。
在“设备和打印机”界面选中该打印机,右键属性,切换到“共享”,共享这台打印机。
在“计算机管理”中打开“用户”,点击 Guest 属性,将“帐户已禁用”前的勾去掉。
打开“本地安全策略”(命令:secpol.msc),在 安全设置-本地策略-用户权限分配 中选中“拒绝从网络访问这台计算机”,将“Guest”用户删除。
如果使用打印机的人数超过10人,需要在 安全设置-本地策略-安全选项 中将“交互式登录:之前登录到缓存的次数(域控制器不可用时)”改大。
将“睡眠”关闭
客户机
以 Windows 10 为例,打开“我的电脑”或“计算机”或“此电脑”,点击菜单中的“网络”
双击打开打印机所在的计算机(或直接在地址栏输出入“\\192.168.1.*”即服务机的 IP 地址)
正常情况下,打开后显示打印机图标,右键点击“连接”后安装驱动即可正常使用。
如果提示以下错误 0x80070035
打开注册表(命令:regedit),定位到:
计算机\HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\LanmanWorkstation\Parameters
修改
AllowInsecureGuestAuth
值为 1,若没有找到,创建一个就行了,类型是 DWORD。如果“网络”中不显示该计算机图标,试试直接输入IP地址,仍然打不开的话,我还不知道怎么搞,可能是公用/私有网络的缘故,或者家庭网络/工作网络的区别,请自行百度解决。
扩展设置
设置电脑自动开机
以某 Dell 为例,开机进入 BIOS:
设置电脑自动关机
这里借助一款比较老牌又好用的关机助手
此处设置开机时自动运行关机助手,如果不生效,直接在系统启程程序中添加快捷方式。
另外,需要删除开机密码。
通过 nuget 安装 UEditorNetCore
从 UEditor 官网 下载最新的包 ueditorx_x_x_x-utf8-net.zip
解压包,并复制到项目的 wwwroot/lib 目录下,删除 net 目录。
根据 UEditorNetCore 官方的使用说明进行操作
步骤中,控制器 UEditorController 接替原 controller.ashx 的功能,用于统一处理上传逻辑
原 config.json 复制到项目根目录,用于配置上传相关设置(若更改文件名和路径在 services.AddUEditorService() 中处理)。
个人喜欢将 xxxPathFormat 值改为:upload/ueditor/{yyyy}{mm}{dd}/{time}{rand:6},方便日后迁移附件后进行批量替换。
记得配置 catcherLocalDomain 项。
上传相关的身份验证和授权控制请在 UEditorController 中处理,如:
public void Do()
{
if (用户未登录) { throw new System.Exception("请登录后再试"); }
_ue.DoAction(HttpContext);
}
如果图片仅仅上传到网站目录下,那么这样配置就结束了,如果要上传到阿里云 OSS 等第三方图床,那么继续往下看。
因 UEditorNetCore 虽然实现了上传到 FTP 和 OSS 的功能,但未提供配置相关的账号信息的途径,所以只能(1)重写 action,或(2)下载 github 的源代码,将核心项目加入到您的项目中进行修改。
重写 action 不方便后期维护,这里记录修改源码的方式。
将源码改造为可配置账号的上传到 OSS 的功能,具体步骤如下:
将 Consts.cs 从项目中排除。
打开 Handlers/UploadHandler.cs,找到 UploadConfig 类,将
FtpUpload 改为 OssUpload,
FtpAccount 改为 OssAccessKeyId,
FtpPwd 改为 OssAccessKeySecret,
FtpIp 改为 OssAccessEndpoint,
再添加一个属性 OssBucketName。
打开 Handlers/UploadHandler.cs,找到 Process() 方法,
将 UploadConfig.FtpUpload 改为 UploadConfig.OssUpload,
将 Consts.AliyunOssServer.* 改为 UploadConfig.Oss*。
打开 Handlers/CrawlerHandler.cs,找到 Fetch() 方法,
将 Config.GetValue<bool>("catcherFtpUpload") 改为 Config.GetValue<bool>("OssUpload"),
将 Consts.AliyunOssServer.* 改为 Config.GetString("Oss*")。
打开 UEditorActionCollection.cs,找到 UploadImageAction,将
FtpUpload = Config.GetValue<bool>("imageFtpUpload"), FtpAccount = Consts.ImgFtpServer.account, FtpPwd = Consts.ImgFtpServer.pwd, FtpIp = Consts.ImgFtpServer.ip,
替换为
OssUpload = Config.GetValue<bool>("OssUpload"), OssAccessKeyId = Config.GetString("OssAccessKeyId"), OssAccessKeySecret = Config.GetString("OssAccessKeySecret"), OssAccessEndpoint = Config.GetString("OssAccessEndpoint"), OssBucketName = Config.GetString("OssBucketName"),
其余 3 个 Action(UploadScrawlAction、UploadVideoAction、UploadFileAction)按同样的方式修改。
在所有创建 UploadHandler 对象时补充添加 SaveAbsolutePath 属性。
打开 config.json,添加相关配置项(注:配置文件中的 *FtpUpload 全部废弃,统一由 OssUpload 决定)
"OssUpload": true, "OssAccessEndpoint": "", "OssAccessKeyId": "", "OssAccessKeySecret": "", "OssBucketName": "",
将 xxxUrlPrefix 的值改为 OSS 对应的 CDN 网址(以 / 结尾,如://cdn.xoyozo.net/)。
其它注意点:
若使用 UEditorNetCore github 提供的源代码类库代替 nuget,且使用本地存储,那么需要将 Handlers 目录中与定义 savePath 相关的代码(查找 var savePath)替换成被注释的行
The cast to value type 'System.Int32' failed because the materialized value is null. Either the result type's generic parameter or the query must use a nullable type.
在 EF 查询数据库时发生:
var list = db.TableA.Select(a => new
{
a.Id,
bSum = a.TableB.Sum(b => b.Num),
}).ToList();
本例中 TableB 有外键关联到 TableA.Id,TableB.Num 为 int,而非 int?。
原因是 EF 以为 Sum 的结果可能为 Nullable<int>,将代码修改如下即可正常:
var list = db.TableA.Select(a => new
{
a.Id,
bSum = (int?)a.TableB.Sum(b => b.Num) ?? 0,
}).ToList();
2020年5月30日,AddTrust External CA Root 过期,虽然不影响客户端浏览器访问网站,但服务器端调用接口(file_get_contents)会出现问题,原因是证书链中根证书过期(必须使用检测工具检查服务端的证书链,而不是查看客户端浏览器上的证书信息,因为客户端浏览器上看到的的根证书是客户端系统上的根证书,是随着系统自动更新的)。
这里,Windows Server 的 IIS 与 CentOS 中的 nginx 又有所区别,咱们分开来讲:
如何更新 Windows Server 的 IIS 中的网站的根证书?
Windows Server 本身相当于一个客户端,会自动更新根证书,只不过 IIS 中的网站证书未同步更新。
打开 IIS,进入对应网站的“绑定”界面,重新绑定即可生效。简单的方法是简单修改主机名,确定,再改回原主机名,确定。
如何更新 CentOS 的 nginx 中的网站的根证书?
nginx 中使用的域名证书(pem 文件),即 conf 文件中设置的 ssl_certificate 路径对应的证书文件,是包含根证书信息的:
因此保留第一段域名证书信息,用新的根证书信息替换掉除第一段的其它内容即可。
在升级 kernel 后若无法启动系统,网站无法打开,SSH 无法连接,无法 ping 通。
使用 VNC 进入操作界面:
一种界面是可选择次新的内核版本:
应该能正常启动。
另一种界面提示:
Give root password for maintenance
输入密码后可启动。
阿里云工程师的建议:
1、当前默认是以最新内核启动的,由于新版内核文件存在异常无法正常运行,在手动选择低内核版本启动后,可以先更改下默认内核引导顺序,配置默认使用低版本内核运行,避免重启再次出现问题。 修改内核引导顺序 https://help.aliyun.com/knowledge_detail/41463.html 2、升级内核本身属于高危操作,建议操作前先做好快照备份,同时更新时可以参考下文档方案 避免Linux实例升级内核系统无法启动的方法 https://help.aliyun.com/knowledge_detail/59360.html
安装 Nuget 包:
项目根目录创建绑定配置文件:bundleconfig.json
示例:
[
{
"outputFileName": "wwwroot/css/site.min.css",
"inputFiles": [
"wwwroot/css/site.css",
"wwwroot/css/custom.css"
]
},
{
"outputFileName": "wwwroot/js/site.min.js",
"inputFiles": [
"wwwroot/js/site.js"
],
"minify": {
"enabled": true,
"renameLocals": true
},
"sourceMap": false
}
]
配置文件中所有路径都相对于项目根目录(而非静态文件根目录 wwwroot),因此配置的路径都要以“wwwroot”开头。
outputFileName 是压缩合并后的文件,inputFiles 是被压缩合并的原始文件集合。
对于 js 配置部分,minify.enabled 配置是否缩小,renameLocals 配置是否修改变量名,sourceMap 配置是否生成 map 映射文件。
引用示例:
<link rel="stylesheet" href="~/css/site.min.css" asp-append-version="true" />
asp-append-version 表示是否在引用路径添加版本参数,可实现在文件有修改时及时在客户端浏览器中生效。
* 注意:有时候“生成”不一定生效,“重新生成”肯定会生效。
更多高级用法请参考官方文档。
Orbi 型号命名规则:
套装:RBK
主体:RBR
分身:RBS、RBW 等
套装对比:
CPU | 高通 IPQ4019 四核 717MHz | 四核 710MHz | ||||
ROM | 128MB | - | - | - | - | - |
内存 RAM | 256MB DDR3 | 512MB | ||||
闪存 Flash | - | 256MB | 4GB | |||
包装内容 | 2 个(不分子母) | 1 个 RBR10 1 个 RBS10 | 1 个 RBR20 1 个 RBS20 | 1 个 RBR40 1 个 RBW30 插墙式 | 1 个 RBR40 1 个 RBS40 | 1 个 RBR50 1 个 RBS50 |
速度 | AC1300 双频段 1300Mbps 电力线 1000M 有线 | AC1200 双频段 2.4GHz 300Mbps+5GHz 866Mbps | 三频段 AC2200 本体 AC2200 分身 (866回程+866+400Mbps) | 三频段 AC3000 本体 AC3000 分身 (1733回程+866+400Mbps) | ||
本体端口 | 每个主机 3 个 WAN/LAN 自适应以太网端口 | 1 WAN & 1 LAN | 1 WAN & 1 LAN | 1 WAN & 3 LAN | 1 WAN & 3 LAN & 1 USB 2.0 | |
分身端口 | - | 2 LAN | - | 4 LAN | 4 LAN & 1 USB 2.0 | |
天线 | 4根 | 6根 | ||||
以太网回程 | 支持 | 支持 | ||||
Daisy Chain Topology 菊花链 | 支持 | |||||
Beamforming 波束成形 | 支持 | |||||
MU-MIMO | 支持 | |||||
802.11k/v 快速漫游 | 支持 | |||||
802.11r 快速漫游 | 不支持 | |||||
802.11ax(Wi-Fi 6) | 不支持 | |||||
AP 模式 | 不支持 | 支持 | ||||
关闭 WiFi 双频合一 | 原厂不支持,小米和 RBK50 可通过修改实现,分离后有线回程可能会失效 | |||||
IPv6 | 支持 | |||||
带机量 | 248 | |||||
覆盖范围 | - | 418 平方米 | 250 平方米 | 200 平方米 | 250 平方米 | 350 平方米 |
官网价 | 229.99 美元 | - | - | - | - | |
京东自营 | ||||||
京东旗舰店 | - | |||||
天猫旗舰店 | - | |||||
其它天猫店 | 969 苏宁易购 | - | RBK30:1288 | RBK40:1699 | - | |
优势 | 管理功能丰富。 配合米家APP 设置小米智能家居设备入网时,无需手动输入密码,入网更便捷。 网口盲插,不分子母。后续再扩展两个分身时可直接购买套装充当两个分身,相对于 Orbi 购买两个分身要节省成本。 | 三频。 性能稳定。 大内存 |
分体价格:
本体 RBR20:599
本体 RBR40:999
本体 RBR50:999 活动
分身 RBS20:699
分身 RBW30 插墙:999
分身 RBS40:1099
分身 RBS50:1449
分身 RBS50Y 室外:未知
价格采自2019年8月
主体/分身 匹配:
分身 RBS20 可作为 RBR20, RBR40 or RBR50 的卫星
分身 RBW30 可作为 RBR40 or RBR50 的卫星
分身 RBS40 可作为 RBR40 or RBR50 的卫星
分身 RBS50 可作为 RBR50 的卫星
分身 RBS50Y 可作为 RBR50 or RBR40 or SRR60 的卫星
RBK13 = 1个 RBR10 + 2个 RBS10
RBK20 = 1个 RBR20 + 1个 RBS20
RBK23 = 1个 RBR20 + 2个 RBS20
RBK30 = 1个 RBR40 + 1个 RBW30
RBK33 = 1个 RBR40 + 2个 RBW30
RBK40 = 1个 RBR40 + 1个 RBS40
RBK43 = 1个 RBR40 + 2个 RBS20
RBK44 = 1个 RBR40 + 3个 RBS20
RBK50 = 1个 RBR50 + 1个 RBS50
RBK53 = 1个 RBR50 + 2个 RBS50
扩展阅读:Mesh 路由器有线回程布线方案
之前已经介绍了百度编辑器在 ASP.NET 环境下的配置,本文继续补充改进其将图片等附件上传到阿里云 OSS。
首先从阿里云控制台下载相关 SDK,并阅读相关的 API 文档。链接:https://promotion.aliyun.com/ntms/act/ossdoclist.html
改造上传代码
打开文件 UploadHandler.cs,在 Process() 方法中找到:
File.WriteAllBytes(localPath, uploadFileBytes);
在其下方插入代码:
client.PutObject(bucketName, "upload/ueditor/" + savePath, localPath);
实现图标按钮上传和 HTML5 拖放上传。
PutObject() 的第 2 个参数是 OSS 中的图片路径,第 3 个参数是图片在网站目录下的磁盘路径。
打开文件 CrawlerHandler.cs,在 Fetch() 方法中找到:
File.WriteAllBytes(savePath, bytes);
在其下方插入代码:
client.PutObject(bucketName, "upload/ueditor/" + ServerUrl, savePath);
实现粘贴板图片上传。
同样注意两个参数变量。
修改路径前缀
上传成功后,编辑器会拼装路径到 HTML 代码中,这个前缀是在 config.json 文件中配置的,具体找到以 UrlPrefix
结尾的属性,如 imageUrlPrefix
,进行相应的修改即可:
"imageUrlPrefix": "//oss.xoyozo.net/upload/ueditor/", /* 图片访问路径前缀 */
路径以“//”开头能更好地兼容 https。
原标题:关于 PHP 获取 IP 地址的几种方法
PHP 获取客户端的 IP 地址有 4 种方式:
REMOTE_ADDR:浏览当前页面的用户计算机的 IP 地址
HTTP_X_FORWARDED_FOR:记录代理信息,会把每一层代理都记录
HTTP_CLIENT_IP:客户端的 IP
X-Real-IP:没有标准,由上一跳决定
REMOTE_ADDR 一般是不能伪造的,因为是通过服务器与客户端握手协议来获取的。而 HTTP_X_FORWARDED_FOR 和 HTTP_CLIENT_IP 是在 header 信息里面,所以客户端是可以进行很轻松的伪造。下面是对这三种方式详解:
https://www.test404.com/post-1448.html
https://www.cnblogs.com/mypath/articles/5239687.html
一、关于 REMOTE_ADDR
这个变量获取到的是“直接来源”的 IP 地址,所谓“直接来源”指的是直接请求该地址的客户端 IP 。这个 IP 在单服务器的情况下,很准确的是客户端 IP ,无法伪造。当然并不是所有的程序都一定是单服务器,比如在采用负载均衡的情况(比如采用 haproxy 或者 nginx 进行负载均衡),这个 IP 就是转发机器的 IP ,因为过程是客户端 -> 负载均衡 -> 服务端。是由负载均衡直接访问的服务端而不是客户端。
二、关于 HTTP_X_FORWARDED_FOR 和 HTTP_CLIENT_IP
基于“一”,在负载均衡的情况下直接使用 REMOTE_ADDR 是无法获取客户端 IP 的,这就是一个问题,必须解决。于是就衍生出了负载均衡端将客户端 IP 加入到 HEAD 中发送给服务端,让服务端可以获取到客户端的真实 IP 。当然也就产生了各位所说的伪造,毕竟 HEAD 除了协议里固定的那几个数据,其他数据都是可自定义的。
三、为何网上找到获取客户端 IP 的代码都要依次获取 HTTP_CLIENT_IP 、 HTTP_X_FORWARDED_FOR 和 REMOTE_ADDR
基于“一”和“二”以及对程序通用性的考虑,所以才这样做。 假设你在程序里直接写死了 REMOTE_ADDR ,有一天你们的程序需要做负载均衡了,那么你有得改了。当然如果你想这么做也行,看个人爱好和应用场景。也可以封装一个只有 REMOTE_ADDR 的方法,等到需要的时候改这一个方法就行了。
X_FORWARDED_FOR 与 X-Real-IP 的区别:
X_FORWARDED_FOR:记录了所有链路里的代理 IP 值,比如下面所说的,经过了 3 次代理,分别是 1.1.1.1, 2.2.2.2, 3.3.3.3,其中 1.1.1.1 是客户端 IP,2.2.2.2 是代理服务器,接下来同理。
X-Real-IP:是只会记录前一次代理的 IP 地址。
一般来说,X-Forwarded-For
是用于记录代理信息的,每经过一级代理(匿名代理除外),代理服务器都会把这次请求的来源IP
追加在X-Forwarded-For
中
来自4.4.4.4
的一个请求,header 包含这样一行
代表请求由1.1.1.1
发出,经过三层代理,第一层是2.2.2.2
,第二层是3.3.3.3
,而本次请求的来源IP4.4.4.4
是第三层代理
而X-Real-IP
,没有相关标准,上面的例子,如果配置了X-Read-IP
,可能会有两种情况
所以 ,如果只有一层代理,这两个头的值就是一样的。
那一般在后端取值(比如 node.js 通过 nginx 代理)是用哪个值呢?我看 sf 上看一般推荐是用 X-Forwarded-For,直接用 X-Real-IP 岂不是更方便点?
X-Forwarded-For 确实是一般的做法
他在正向(如 squid)反向(如 nginx)代理中都是标准用法,而正向代理中是没有 x-real-ip 相关的标准的,也就是说,如果用户访问你的 nginx 反向代理之前,还经过了一层正向代理,你即使在 nginx 中配置了 x-real-ip,取到的也只是正向代理的 IP 而不是客户端真实 IP
大部分 nginx 反向代理配置文章中都没有推荐加上 x-real-ip ,而只有 x-forwarded-for,因此更通用的做法自然是取 x-forwarded-for
多级代理很少见,只有一级代理的情况下二者是等效的
如果有多级代理,x-forwarded-for 效果是大于 x-real-ip 的,可以记录完整的代理链路
1、将以下代码保存为 Client.php
// php 脚本开始
$ch = curl_init();
$url = "http://localhost/ser.php";
$header = array('CLIENT-IP:208.165.188.175', 'X-FORWARDED-FOR:208.165.188.175');
// 声明伪造 head 请求头
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_HTTPHEADER, $header);
curl_setopt($ch, CURLOPT_RETURNTRANSFER,true);
$page_content = curl_exec($ch); curl_close($ch);
echo $page_content;
2、将以下代码保存为 ser.php
// php 脚本开始
echo getenv('HTTP_CLIENT_IP');
echo getenv('HTTP_X_FORWARDED_FOR');
echo getenv('REMOTE_ADDR');
测试结果为
208.165.188.175
208.165.188.175
127.0.0.1
上面结果可看出,http_client_ip、http_x_forwarded_for 都被伪造了,而 remote_addr 还是127.0.0.1 就是客户端 IP
总结:
如果我们没用到负载均衡(CDN)的话直接用 REMOTE_ADDR 获取 IP。
如果使用了一级 CDN 的话,CDN 会把 REMOTE_ADDR 转发成 X-Real-IP,我们服务器可以获取 X-Real-IP 来获取 IP 值。如果是多级 CDN 的话需要我们来做 nginx 代理,详细:https://www.cnblogs.com/princessd8251/articles/6268943.html
就是第一台负载服务器获取 REMOTE_ADDR 转发成 X-Real-IP,之后的去继承前一台的 X-Real-IP 就可以了,这里记住必须是去继承,不然第二台会把第一台的 REMOTE_ADDR 转发成 X-Real-IP 导致错误。
在 discuz! 中的获取 IP 的方法:
private function _get_client_ip() {
$ip = $_SERVER['REMOTE_ADDR'];
if (isset($_SERVER['HTTP_CLIENT_IP']) && preg_match('/^([0-9]{1,3}\.){3}[0-9]{1,3}$/', $_SERVER['HTTP_CLIENT_IP'])) {
$ip = $_SERVER['HTTP_CLIENT_IP'];
} elseif(isset($_SERVER['HTTP_X_FORWARDED_FOR']) AND preg_match_all('#\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}#s', $_SERVER['HTTP_X_FORWARDED_FOR'], $matches)) {
foreach ($matches[0] AS $xip) {
if (!preg_match('#^(10|172\.16|192\.168)\.#', $xip)) {
$ip = $xip;
break;
}
}
}
return $ip;
}
这里 DZ 为了符合有些用户会用代理,所以才首先使用了两个容易伪造的方法,如果有需要可以自行修改。