博客 (646)

前言:

ChatGPT 给了 3 条建议:

  1. 在应用程序中正确释放数据库连接。确保在使用完数据库连接后,将其关闭并将其返回到连接池中。您可以使用 using 语句来确保连接在使用完毕后被正确释放。

  2. 调整连接池的大小。默认情况下,连接池的最大大小为 100。如果您的应用程序需要更多的连接,则可以增加连接池的大小。您可以在连接字符串中设置 Max Pool Size 属性来调整连接池的大小。

  3. 调整连接池的超时时间。默认情况下,连接池中的连接在 30 秒钟内未使用时将被关闭。如果您的应用程序需要更长的连接时间,则可以增加连接池的超时时间。您可以在连接字符串中设置 Connection Lifetime 属性来调整连接池的超时时间。

亲测有效,尤其是第 3 条,原因是 Connection Lifetime 的默认值是 0,即没有超时限制。

—— 2023.5

一般地,我们使用 EF 连接数据库前会先初始化一个数据库上下文:

dbEntities db = new dbEntities();

虽然 ASP.NET 会在查询完毕后自动关闭该连接,但是在什么情况下回收等都是不确定的,所以会导致 MySQL 中出现很多 Sleep 的连接(执行命令 SHOW FULL PROCESSLIST 可见),占用数据库的连接数,除非主动调用 Dispose():

db.Dispose();

官方建议的写法是使用 using 语法:

using (dbEntities db = new dbEntities())
{
}

using 会自动调用 Dispose()。这样对减少连接数是很有效的,但官方提示为了提高下一次连接的速度,并不会完全关闭所有连接。

C# 8 建议写法:

using dbEntities db = new dbEntities();

在实际项目中(该项目有 500+处数据库连接)测试结果,在不执行 Dispose() 时稳定为 140 个左右连接数,使用 using 或 Dispose() 后稳定变为 40 个左右。


如果不小心在 using 外部或 Dispose() 后再次对该上下文执行查询操作会出现异常:

无法完成该操作,因为 DbContext 已释放。

此 ObjectContext 实例已释放,不可再用于需要连接的操作。

所以要避免出现这种情况。这里还有一种另类的解决方法(不建议),根据上下文的特性,只要在 using 内查询一次(譬如视图中需要用到的导航属性,即外键关联的表),就可以在外部使用这个属性。


(建议)在 ASP.NET MVC 或 Web API 项目中,如果一个控制器中仅在 Action 外部定义一个 DbContext,那么,只要重写该控制器的 Dispose() 方法即可:

private dbEntities db = new dbEntities();

protected override void Dispose(bool disposing)
{
    if (disposing)
    {
        db.Dispose();
    }
    base.Dispose(disposing);
}

上下文使用 private 修饰即可。


参考:SQL Server 连接池 (ADO.NET)

xoyozo 6 年前
6,313

原标题:关于 PHP 获取 IP 地址的几种方法


PHP 获取客户端的 IP 地址有 4 种方式:

  1. REMOTE_ADDR:浏览当前页面的用户计算机的 IP 地址

  2. HTTP_X_FORWARDED_FOR:记录代理信息,会把每一层代理都记录

  3. HTTP_CLIENT_IP:客户端的 IP

  4. X-Real-IP:没有标准,由上一跳决定


REMOTE_ADDR 一般是不能伪造的,因为是通过服务器与客户端握手协议来获取的。而 HTTP_X_FORWARDED_FOR 和 HTTP_CLIENT_IP 是在 header 信息里面,所以客户端是可以进行很轻松的伪造。下面是对这三种方式详解:

https://www.v2ex.com/t/230367

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 包含这样一行

X-Forwarded-For: 1.1.1.1, 2.2.2.2, 3.3.3.3

代表请求由1.1.1.1发出,经过三层代理,第一层是2.2.2.2,第二层是3.3.3.3,而本次请求的来源IP4.4.4.4是第三层代理

X-Real-IP,没有相关标准,上面的例子,如果配置了X-Read-IP,可能会有两种情况

// 最后一跳是正向代理,可能会保留真实客户端IP
X-Real-IP: 1.1.1.1
// 最后一跳是反向代理,比如Nginx,一般会是与之直接连接的客户端IP
X-Real-IP: 3.3.3.3

所以 ,如果只有一层代理,这两个头的值就是一样的。

那一般在后端取值(比如 node.js 通过 nginx 代理)是用哪个值呢?我看 sf 上看一般推荐是用 X-Forwarded-For,直接用 X-Real-IP 岂不是更方便点?

X-Forwarded-For 确实是一般的做法

  1. 他在正向(如 squid)反向(如 nginx)代理中都是标准用法,而正向代理中是没有 x-real-ip 相关的标准的,也就是说,如果用户访问你的 nginx 反向代理之前,还经过了一层正向代理,你即使在 nginx 中配置了 x-real-ip,取到的也只是正向代理的 IP 而不是客户端真实 IP

  2. 大部分 nginx 反向代理配置文章中都没有推荐加上 x-real-ip ,而只有 x-forwarded-for,因此更通用的做法自然是取 x-forwarded-for

  3. 多级代理很少见,只有一级代理的情况下二者是等效的

  4. 如果有多级代理,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 为了符合有些用户会用代理,所以才首先使用了两个容易伪造的方法,如果有需要可以自行修改。

h
转自 hykeda 6 年前
8,091
变量名字段名
官方描述
return_code返回状态码

SUCCESS/FAIL

此字段是通信标识,非交易标识,交易是否成功需要查看 trade_state 来判断

在“统一下单”和“支付结果通知”中,该描述变成了:交易是否成功需要查看 result_code 来判断

result_code业务结果SUCCESS/FAIL
trade_state交易状态

SUCCESS — 支付成功

REFUND — 转入退款

NOTPAY — 未支付

CLOSED — 已关闭

REVOKED — 已撤销(付款码支付)

USERPAYING — 用户支付中(付款码支付)

PAYERROR — 支付失败(其他原因,如银行返回失败)

总的来说,

return_code 是用来判断通信状态的,个人理解在“结果通知”时必为 SUCCESS;

result_code 是用来判断业务结果的,指一次调用接口或回调的动作是否如愿执行成功。如“关闭订单”时关闭成功为 SUCCESS,因参数配置错误、找不到订单号、订单状态不允许关闭等其它关闭失败的情况为 FAIL;

trade_state 是用来判断交易状态的,“交易”是指微信支付订单。


另,在“统一下单”和“支付结果通知”中,return_code 的描述变成了:交易是否成功需要查看 result_code 来判断。不知道是官方笔误,还是真的可以用来判断交易是否成功,因为在调用“统一下单”时是未支付状态,根本没有支付成功的可能。


保险起见,我们在异步收到“结果通知”时,不要相信文档去判断 result_code,应调用“查询订单”,并判断 trade_state

xoyozo 6 年前
7,445

微信支付商户可以直接注册,也可以由公众号、小程序、开放平台等开通。

直接注册时可勾选使用的公众号、小程序、APP、PC 网站等,并填写相应的 APPID。

填写资料提交审核,等待 1~2 工作日。

审核通过后签约,选择“手动提现”或“自动提现”,建议“手动提现”,原因如下:


用户付款、退款、企业付款等流程

以乐趣到家为例,

用户 A 提交搬家需要,并支付 100 元搬家费(其中的 10 元是平台抽成,90 元是服务者 B 的收入)。

那么 A 付款成功后,100 元打入商户的“未结算”中,在结算周期后扣除手续费变成“已结算”(若手续费 0.6% 则变成 99.4 元),如果商户开通了自动提现,则直接打入对公账户。

当 A 申请退款时,如果“未结算”资金充足,那么从“未结算”资金中退款给 A,如果不足,则从“可用余额”中退款(从商户角度来说应该是“企业付款”)。

若服务正常完成,则从可用余额中付款 90 元给 B。


可用余额(简称余额)是专用于支付的资金,其资金来源可以是充值或从“已结算”资金转入(前提是未开通自动提现)。

如果开通了“企业付款”,那么相关资金也是从余额中扣除的。


所以,退款(用户角度的“退款”)直接从“未结算”中扣是最划算的,只当“未结算”中资金不足时采用余额退款(用户角度的“退款”,商户角度的“企业付款”)。

由于退款(商户角度的“退款”)的前提是有相应的订单,所以上例中的 90 元支付给 B 时不能使用商户的退款功能,只能从余额支付(企业付款),而从“已结算”转到“余额”是最划算的,如从“已结算”到“对公账户”再充值到“余额”就会涉及开票缴税。


下面是如何关闭和再次开通“自动提现”。(注意:并非所有的商户都有此功能!据我所知,结算周期为 T+1 的有关闭功能,T+7 的不能关闭)

image.pngimage.png


以上系本人经验总结,仅供交流,不承担任何责任,如有错误敬请指正。QQ 940534113


xoyozo 6 年前
6,332

本文讲述使用网页开发适用于宴会、活动、年会现场 LED 大屏幕显示抽奖、抢楼、游戏、视频等互动界面,通过在浏览器中打开后 F11 全屏投影到大银幕上。


设计思路:

  1. 为了保证最佳的显示效果,设计稿的尺寸按大屏幕的“宽高比”来做(本文以4米*3米的大银幕为例,设计稿以800*600像素为例)。

  2. 由于电脑的视频输出分辨率很有可能与大银幕的宽高比不同,那么我们全屏投影后会界面会有变形,但是投影设备接收到电脑视频信号后再全屏显示到大银幕时,界面又会变回正常。

  3. 按设计稿的尺寸开发网页,并使用 CSS3 的 scale 实现整体伸缩,目的是自适应窗口尺寸来全屏显示内容,无留白、无裁切。整体拉伸的要求是文字、图片、背景、间距、iframe 等所有元素以统一的比例拉伸,无错位。

  4. 网页上通过 JS 设置一些快捷键来实现页面展示内容的切换。

  5. 若投影设备功能有限或操作员水平有限等原因导致无法顺利设置输入输出分辨率时,网页可设置 x 轴和 y 轴两个方向的伸缩系数、定位偏移量等,更灵活地实现完美投影需求。


开发要点:

  • 网页尺寸按设计稿来定(以 800x600 为例)。

  • 使用 CSS3 的 transform 的 scale 来实现缩放,以电脑显示器分辨率 1920x1080,宽放大 1920/800 倍,高放大 1080/600 倍,即可将设计稿全屏展示到电脑显示器上。为了更灵活,我们使用窗口大小来代替显示器分辨率,这样不管怎么改变窗口大小或者全屏,设计稿都能完整地显示在窗口上。

  • 在页面初始化和窗口尺寸变化时重新调整设置上述比例。


代码参考:

xoyozo 6 年前
10,713

今天发现,阿里云 CDN 的 Refer 防盗链并没有真正防止白名单外的域名调用 OSS 的图片。经过一番研究发现,这跟 https 有关。

我在阿里云开通了 OSS 来存在图片,并使用 CDN 访问,在 CDN 中开启了 https,但并不强制。并且使用“白名单”的方式来设置 Refer 防盗链。关键是开启了“允许空 Referer”。

image.png 

我在 http://b.com/ 上测试调用 httphttps 的图片都是打不开的(防盗链有效),但是在 https://b.com/ 上却能显示 http 的图片(防盗链失效):

image.png

究其原因,是 https 页面在调用 http 图片时请求头不包含 Referer 字段,导致 CDN 判断 Referer 为空,这时候如果开启了为空也允许访问的话,就会出现防盗链失效的情况了。

image.png

image.png

总结:如果 CDN 开启了 https 访问,那么不要允许空 Referer。这里白名单内的网站必须使用与 CDN 一致的 protocol(http 或 https)。建议的写法是:

<img src="//cdn.xxx.com/abc.jpg" />


xoyozo 6 年前
7,568

数据表所占用的空间(简称“表空间”)一般会大于其数据空间索引空间的和。

当数据被删除时,其所占空间并不会立即释放,而是等待新数据写入,这会导致出现许多磁盘碎片。使用 OPTIMIZE TABLE 或 ALTER TABLE 可以回收碎片,重组文件。优化表的过程类似于 Windows 碎片整理。

操作过程会导致该表上的写操作无法执行。

一般在删除了大批量数据或更改了许多可变长度字段后执行优化表

碎片率 = 100% - (数据空间 + 索引空间) / 表空间

优化后碎片率接近于 0%,数据空间索引空间也会变小,此时 表空间 接近于“数据空间 + 索引空间


MyISAM 引擎上遇到优化后导致获取行数为 0,SELECT 数据只有 1 条的情况,需要执行修复表REPAIR TABLE),使数据恢复正常。执行后结果显示:Number of rows changed from 0 to xxxxxx

xoyozo 6 年前
5,756
  • 使用会话状态服务器(StateServer)管理会话状态,可防止网站发布后会话丢失,参 ASP.NET 网站每次发布后丢失 Session 怎么办?

  • 查询数据库时,尽量使用 using 包裹 db 上下文,或者 db.Dispose(),或重写 Dispose(),可以减少数据库连接数(Sleep),参 如何减少 ASP.NET 连接 MySQL 时的 Sleep 查询(即时关闭数据库上下文)

  • 发布时使用预编译功能,参 彻底告别 .NET 网站首次访问速度慢的问题

  • 清理日志不要这样写(先读取再删除):

    db.dt_log.RemoveRange(db.dt_log.Where(c => c.time < dt).OrderBy(c => c.time).Take(count));

    SQL Server 应该:

    db.Database.ExecuteSqlCommand($"DELETE FROM {nameof(db.dt_log)} WHERE {nameof(l.id)} IN (SELECT TOP {count} {nameof(l.id)} FROM {nameof(db.dt_log)} WHERE {nameof(l.time)} < '{dt.ToString("yyyy-MM-dd HH:mm:ss")}' ORDER BY {nameof(l.time)})");

    注意时间可能需要使用 convert 函数转化,给 time 字段添加索引。

    MySQL 应该:

    db.Database.ExecuteSqlCommand($"DELETE FROM {nameof(db.dt_log)} WHERE {nameof(l.time)} < '{dt.ToString("yyyy-MM-dd HH:mm:ss")}' ORDER BY {nameof(l.time)} LIMIT {count}");

    给 time 字段添加索引。

  • 未完待续

xoyozo 6 年前
2,431

于 2024 年 8 月:

查看空间占用

  • 主菜单“云数据库 RDS” - 选择相应实例 - 自治服务 - 一键诊断 - 空间分析 - 立即分析 - 几分钟后就能看到报表(经济版 / 企业版可以开启自动分析)

  • 如果没有可清理的空间,可以直接扩容(有条件):实例 - 基本信息 - 变更配置 - 立即升配

慢查询(索引优化建议)

  • 主菜单“云数据库 RDS” - 选择相应实例 - 自治服务 - 慢SQL


2019 年 5 月前:

查看空间占用

  • 主菜单“云数据库 RDS 版” - 选择相应实例 - 二级菜单“CloudDBA” - 空间管理

  • 主菜单“混合云数据库管理 HDM” - 选择相应实例 - 二级菜单“库表空间” - 数据空间

注意“数据空间”只是数据的大小,跟磁盘占用有直接关系的是“表空间”,某些表(如日志表)记录频繁插入删除的,表空间会比较大,应执行“优化表(OPTIMIZE TABLE)”,优化过程中会影响表的写操作


慢查询(索引优化建议)

  • 主菜单“云数据库 RDS 版” - 选择相应实例 - 二级菜单“CloudDBA” - 性能优化

  • 主菜单“混合云数据库管理 HDM” - 选择相应实例 - 二级菜单“请求分析” - 慢日志


xoyozo 6 年前
7,041

无法向会话状态服务器发出会话状态请求。请确保 ASP.NET State Service (ASP.NET 状态服务)已启动,并且客户端端口与服务器端口相同。如果服务器位于远程计算机上,请检查 HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\aspnet_state\Parameters\AllowRemoteConnection 的值,确保服务器接受远程请求。如果服务器位于本地计算机上,并且上面提到的注册表值不存在或者设置为 0,则状态服务器连接字符串必须使用“localhost”或“127.0.0.1”作为服务器名称。

无法连接 StateService 的原因有很多种,我遇到的情况是:

VS 中使用 IISExpress 调试项目,系统没有安装 IIS,

ASP.NET 状态服务是开启状态,

没有对系统作任何修改,无缘无故无法连接。


修改注册表、配置防火墙无效(我没有用到远程连接),

执行 aspnet_regiis -i -enables 无效,

重装 .NET Framework 4.8 Developer Pack 无效,

差点就重装系统了。


最终的解决方法是:

在“启用或关闭 Windows 功能”中勾选安装:Internet Information Services -> 万维网服务 -> 应用程序开发功能 -> ASP.NET 4.8

重启系统。

image.png

xoyozo 6 年前
3,695