博客 (109)

您好,MySQL Connector/NET  社区,

从 8.0.23  版本开始,我们的  Entity Framework Core 提供程序有了一个新名称。主要目标是保持对 Microsoft Entity Framework Core 不同版本的支持,并确保这些版本与我们的发行版保持紧密联系。同样,此新命名在包装目的方面更为具体。因此,名称的“数据”部分已删除。

之前

    MySql.Data.EntityFrameworkCore v8.0.x

现在:

    MySql.EntityFrameworkCore v8.0.x

现在,Microsoft 维护的不仅是 Entity Framework Core 的单个版本,我们还需要找到一种方法来命名程序包并维护 Entity Framework Core 和 MySQL 版本之间的相关性。这就是我们想到使用软件包元数据的时候。软件包的版本现在由两部分组成,第一部分对应于当前支持的 Microsoft Entity Framework Core 版本,然后是一个“加号”,指示我们包含 MySQL 版本的元数据。

    MySql.EntityFrameworkCore v5.0.0+MySQL8.0.23
    MySql.EntityFrameworkCore v3.1.10+MySQL8.0.23

好吧,还有另外一种未来情况需要考虑。如果 Microsoft 没有为我们的较新的 Connector/NET 版本及时发布 Entity Framework Core 的新版本怎么办?假设当前受支持的 Entity Framework Core 版本是5.0.0,所以我们的软件包看起来像这样 MySql.EntityFrameworkCore 5.0.0+MySQL8.0.23,并且在我们发布下一个版本 8.0.24 时,Microsoft Entity Framework Core 的最新版本仍然是 5.0.0在这种情况下,我们将在版本中添加第四个数字,将包命名为:MySql.EntityFrameworkCore 5.0.0.1+MySQL8.0.23

我们希望您发现此新的命名模型有用且更精确。始终欢迎您提供反馈,您的所有意见都会激励我们不断改进,以便我们为您提供高品质的产品。

最后,以下是一些可能对您有用的链接:

我们希望能与您保持联系!


D
转自 Daniel Valdez 5 年前
3,519

喜欢“数据库优先”的朋友会发现,一旦 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\ 目录再次发布即可解决问题。

xoyozo 5 年前
9,206

“DatabaseFacade”未包含“ExecuteSqlCommand”的定义,并且找不到可接受第一个“DatabaseFacade”类型参数的可访问扩展方法“ExecuteSqlCommand”(是否缺少 using 指令或程序集引用?)


参:原生 SQL 查询

思路:(以 Entity Framework Core 5.0 为例)

先借用 SELETE 的 IQueryable 语句,将 SELETE 关键字替换成 DELETE,再使用 FromSqlRaw 执行。这样可准确使用真实的数据库表名和字段名。

如:

using Microsoft.EntityFrameworkCore;
string sql = db.表.Where(c => c.Date < minDate).ToQueryString();
sql = Regex.Replace(sql, @"SELECT\s.*?\sFROM", "DELETE FROM", RegexOptions.Singleline); // SET @__minDate_0 = '2020-01-14'; DELETE FROM `表` AS `d` WHERE `d`.`Date` < @__minDate_0
sql = Regex.Replace(sql, @"\sAS\s`(.*?)`", "", RegexOptions.Singleline); // SET @__minDate_0 = '2020-01-14'; DELETE FROM `表` WHERE `d`.`Date` < @__minDate_0
sql = Regex.Replace(sql, @"`([^`]*?)`\.`", "`", RegexOptions.Singleline); // SET @__minDate_0 = '2020-01-14'; DELETE FROM `表` WHERE `Date` < @__minDate_0
db.Database.ExecuteSqlRaw(sql);

以上例子针对 MySQL,且未考虑字符串内容中包含符号 ` 的情况。实例操作中应严格防范 SQL 注入。

封装成方法:

using Microsoft.EntityFrameworkCore;

const RegexOptions ro = RegexOptions.Singleline;

/// <summary>
/// 组装一个 DELETE SQL 语句
/// </summary>
/// <typeparam name="T">数据库表实体类</typeparam>
/// <param name="queryableWithConditions">IQueryable,可带筛选条件</param>
/// <returns></returns>
public static string AssembleDeleteSql<T>(IQueryable<T> queryableWithConditions) where T : class
{
    // MySQL 版
    string sql = queryableWithConditions.ToQueryString(); // SET @__minDate_0 = '2020-01-14'; SELECT `d`.`Date`, `d`.`Code6` FROM `表` AS `d` WHERE `d`.`Date` < @__minDate_0
    sql = Regex.Replace(sql, @"SELECT\s.*?\sFROM", "DELETE FROM", ro); // SET @__minDate_0 = '2020-01-14'; DELETE FROM `表` AS `d` WHERE `d`.`Date` < @__minDate_0
    sql = Regex.Replace(sql, @"\sAS\s`(.*?)`", "", ro); // SET @__minDate_0 = '2020-01-14'; DELETE FROM `表` WHERE `d`.`Date` < @__minDate_0
    sql = Regex.Replace(sql, @"`([^`]*?)`\.`", "`", ro); // SET @__minDate_0 = '2020-01-14'; DELETE FROM `表` WHERE `Date` < @__minDate_0
    return sql;
}
/// <summary>
/// 组装一个 UPDATE SQL 语句
/// </summary>
/// <typeparam name="T">数据库表实体类</typeparam>
/// <param name="queryableWithConditions">IQueryable,可带筛选条件</param>
/// <param name="columnsAndValues">需要更改的字段和值</param>
/// <returns></returns>
public static string AssembleUpdateSql<T>(IQueryable<T> queryableWithConditions, Dictionary<string, object> columnsAndValues) where T : class
{
    // MySQL 版
    string sql = queryableWithConditions.ToQueryString(); // SET @__minDate_0 = '2020-01-14'; SELECT `d`.`Date`, `d`.`Code6` FROM `表` AS `d` WHERE `d`.`Date` < @__minDate_0
    sql = Regex.Replace(sql, @"SELECT\s.*?\sFROM", "UPDATE", ro); // SET @__minDate_0 = '2020-01-14'; UPDATE `表` AS `d` WHERE `d`.`Date` < @__minDate_0
    sql = Regex.Replace(sql, @"\sAS\s`(.*?)`", "", ro); // SET @__minDate_0 = '2020-01-14'; UPDATE `表` WHERE `d`.`Date` < @__minDate_0
    sql = Regex.Replace(sql, @"`([^`]*?)`\.`", "`", ro); // SET @__minDate_0 = '2020-01-14'; UPDATE `表` WHERE `Date` < @__minDate_0
    string sets = string.Join(",", columnsAndValues.Select(c => $"`{c.Key.Replace("`", @"\`")}` = '{c.Value.ToString().Replace("'", @"\'")}'"));
    sql = Regex.Replace(sql, @"`\s*WHERE", $"` SET {sets} WHERE", ro); // SET @__minDate_0 = '2020-01-14'; UPDATE `表` SET `field1` = 'value1', `field2` = 'value2' WHERE `Date` < @__minDate_0
    return sql;
}

调用示例:

using Microsoft.EntityFrameworkCore;

// 删除记录
DateTime minDate = DateTime.Today.AddYears(-1).AddMonths(-1);
string sql = AssembleDeleteSql(db.表.Where(c => c.Date < minDate));
db.Database.ExecuteSqlRaw(sql);

// 更新记录
var sets = new Dictionary<string, object>();
foreach (var p in db.Model.FindEntityType(typeof(表)).GetProperties())
{
    switch (p.Name)
    {
        case nameof(表.字段一): sets.Add(p.GetColumnBaseName(), 123); break;
        case nameof(表.字段二): sets.Add(p.GetColumnBaseName(), 456); break;
    }
}
DateTime minDate = DateTime.Today.AddYears(-1).AddMonths(-1);
string sql = AssembleUpdateSql(db.表.Where(c => c.Date < minDate), sets);
db.Database.ExecuteSqlRaw(sql);


xoyozo 5 年前
10,143

前言:本文介绍的是攻击IP的案例,如果攻击的是域名(如 CC),参此文


【阿里云】尊敬的@aliyun.com:为了保障服务器的稳定运行,您的IP:  实例名称: 已启动限流保护措施。阈值与产品规格相关,您可以登录云盾控制台调整清洗阈值,若超出调整范围请提交售后工单反馈或电话95187-1进行咨询。

【阿里云】尊敬的@aliyun.com:您的IP: 受到攻击流量已超过云盾DDoS基础防护的带宽峰值,服务器的所有访问已被屏蔽,如果300分钟后攻击停止将自动解除否则会延期解除。详情请登录【云盾控制台】-【DDoS基础防护】查看或致电95187-1进行咨询。

恭喜,你的 ECS 服务器受到 DDoS 攻击了。这个时候,工单客服和电话客服都会极力推销高防 IP 这个产品(DDoS高防(新BGP)),为了抵挡一年才会遇到几分钟的攻击,咱们还是选择更为经济的方式来解决攻击这个问题吧。

ECS 等产品都会自带流量清洗这个功能,在阈值范围内的攻击流量会免费处理掉,超过阈值就把 ECS 拉入黑洞,以防止同机房的其它服务受到影响。一般攻击才持续 5~20 分钟,关小黑屋就等 5 个小时。

既然受攻击的是 ECS 公网 IP,那么我们可以借用负载均衡(SLB)使用 ECS 的私网 IP 来请求业务。

初期可以创建一个按量付费的 SLB,熟悉费用后可再次调整。选择“私网”(文末解释为什么不选择“公网”)

image.png

服务地址绑定到 ECS,按业务需求添加监听端口:

image.png


此时,SLB 已经和 ECS 打通了,但是这个私网 SLB 并没有公网 IP,所以继续购买一个叫弹性公网 IP (EIP)的产品,用于打通公网与私网。同样初期选择按量付费。

将 SLB 与 EIP 绑定(各自的管理界面都有相关操作入口)。

最后,将域名解析到 EIP 即可,顺便将 TTL 改为最小值。

解析生效后,外部的请求通过 EIP 到 SLB 再到 ECS 的私网 IP 访问成功,但 ECS 主动的出流量仍然通过其公网 IP 流出,在黑洞期间相关的操作仍然会失败(如上传文件到外部图床),这个等后续遇到再补充解决方案。


一旦 DDoS 改为攻击 EIP,那么只要重新创建一个新的 EIP,将 SLB 绑定到该新的 EIP,并解析域名即可。


既然本例的 SLB 是私网到私网的,是不是可以省去 SLB,由 EIP 直接到 ECS?

经实践证明,EIP 绑定 ECS 会提示 ECS 已存在公网 IP,无法绑定。而且,带宽计费方式为按固定带宽的包年包月实例,不支持将公网 IP 转换为弹性公网 IP。


SLB 为什么不选择“公网”?

本方案仅为临时防止 DDoS 攻击,所以一旦再次受到攻击需要立即做出反应,而 SLB 的配置相对于 EIP 会更为复杂,所以我们选择新建 EIP 的方式来代替新建公网 SLB。费用方面,私网 SLB 没有流量费用,EIP 的流量费用与公网 SLB 相同,所以两种方案的费用是相差不大的。


最后,推荐使用阿里云 SDK 通过 API 接口来实现一键部署/更换 EIP 功能。

进阶功能:使用接口 DescribeEipAddresses 判断 EIP 的 LockReason 状态是否为 security,若是则自动切换 EIP,实现无人值守全自动防 DDoS。

xoyozo 5 年前
11,068

.menu-nav 包裹

<ul class="menu-nav">
...
</ul>


.menu-section 区段

<li class="menu-section">
    <h4 class="menu-text">区段</h4>
    <i class="menu-icon ki ki-bold-more-hor icon-md"></i>
</li>


.menu-item 菜单

<li class="menu-item" aria-haspopup="true">
    <a href="###" class="menu-link">
        <span class="svg-icon menu-icon">
            <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24px" height="24px" viewBox="0 0 24 24" version="1.1">
                <g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
                    <rect x="0" y="0" width="24" height="24" />
                    <path d="M3,16 L5,16 C5.55228475,16 6,15.5522847 6,15 C6,14.4477153 5.55228475,14 5,14 L3,14 L3,12 L5,12 C5.55228475,12 6,11.5522847 6,11 C6,10.4477153 5.55228475,10 5,10 L3,10 L3,8 L5,8 C5.55228475,8 6,7.55228475 6,7 C6,6.44771525 5.55228475,6 5,6 L3,6 L3,4 C3,3.44771525 3.44771525,3 4,3 L10,3 C10.5522847,3 11,3.44771525 11,4 L11,19 C11,19.5522847 10.5522847,20 10,20 L4,20 C3.44771525,20 3,19.5522847 3,19 L3,16 Z" fill="#000000" opacity="0.3" />
                    <path d="M16,3 L19,3 C20.1045695,3 21,3.8954305 21,5 L21,15.2485298 C21,15.7329761 20.8241635,16.200956 20.5051534,16.565539 L17.8762883,19.5699562 C17.6944473,19.7777745 17.378566,19.7988332 17.1707477,19.6169922 C17.1540423,19.602375 17.1383289,19.5866616 17.1237117,19.5699562 L14.4948466,16.565539 C14.1758365,16.200956 14,15.7329761 14,15.2485298 L14,5 C14,3.8954305 14.8954305,3 16,3 Z" fill="#000000" />
                </g>
            </svg>
        </span>
        <span class="menu-text">一级菜单(无子菜单)</span>
    </a>
</li>

其中,<svg /> 可以替换成其它图标库,如:

<li class="menu-item" aria-haspopup="true">
    <a href="###" class="menu-link">
        <span class="svg-icon menu-icon">
            <i class="fab fa-app-store-ios"></i>
        </span>
        <span class="menu-text">一级菜单(无子菜单)</span>
    </a>
</li>

并修正样式:

.aside-menu .menu-nav > .menu-item > .menu-heading .menu-icon i,
.aside-menu .menu-nav > .menu-item > .menu-link .menu-icon i { height: 23px; width: 23px; line-height: 23px; text-align: center; vertical-align: middle; font-size: 1.25rem; }


.menu-item-submenu 表示该 .menu-item 有子菜单,即内部包含 .menu-submenu。该规则在任意一级菜单均有效。

.menu-item-open 表示该 .menu-item 当前是打开状态(高亮),否则为闭合状态,需配合 <i class="menu-arrow"></i> 使用。该规则在任意一级菜单均有效。

.menu-item-active 表示该 .menu-item 对应当前页面,效果为灰色背景且文字持续高亮。

.menu-item-parent 作为一级菜单在不同设备上展示的替代,位于一级菜单下的 .menu-submenu 下的 .menu-subnav 内部。

.menu-link 表示可点击(hover 效果),用于 .menu-item 下的 <a /> 或 .menu-item-parent 下的 <span />。

aria-haspopup="true" 每个 .menu-item 都有。

data-menu-toggle="hover" 每个 .menu-item-submenu 都有。

.menu-icon 一级菜单项的左侧图标,支持 <svg /> 或 <i />。

.menu-bullet 非一级菜单项的左侧图标,与 .menu-text 同级,可选 .menu-bullet-line / .menu-bullet-dot。

.menu-text 菜单文字。

.menu-label 菜单项右侧徽章,与 .menu-text 同级。

.menu-arrow 菜单项右侧箭头(当包含子菜单时使用),与 .menu-text 同级。

.menu-toggle 放在 .menu-item-submenu 内部的 .menu-link,与 .menu-item-submenu 上的  data-menu-toggle="hover" 同时出现。


完整示例:

<ul class="menu-nav">
    <li class="menu-item" aria-haspopup="true">
        <a href="index.html" class="menu-link">
            <span class="svg-icon menu-icon">
                <!--begin::Svg Icon | path:assets/media/svg/icons/Design/Layers.svg-->
                <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24px" height="24px" viewBox="0 0 24 24" version="1.1">
                    <g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
                        <polygon points="0 0 24 0 24 24 0 24" />
                        <path d="M12.9336061,16.072447 L19.36,10.9564761 L19.5181585,10.8312381 C20.1676248,10.3169571 20.2772143,9.3735535 19.7629333,8.72408713 C19.6917232,8.63415859 19.6104327,8.55269514 19.5206557,8.48129411 L12.9336854,3.24257445 C12.3871201,2.80788259 11.6128799,2.80788259 11.0663146,3.24257445 L4.47482784,8.48488609 C3.82645598,9.00054628 3.71887192,9.94418071 4.23453211,10.5925526 C4.30500305,10.6811601 4.38527899,10.7615046 4.47382636,10.8320511 L4.63,10.9564761 L11.0659024,16.0730648 C11.6126744,16.5077525 12.3871218,16.5074963 12.9336061,16.072447 Z" fill="#000000" fill-rule="nonzero" />
                        <path d="M11.0563554,18.6706981 L5.33593024,14.122919 C4.94553994,13.8125559 4.37746707,13.8774308 4.06710397,14.2678211 C4.06471678,14.2708238 4.06234874,14.2738418 4.06,14.2768747 L4.06,14.2768747 C3.75257288,14.6738539 3.82516916,15.244888 4.22214834,15.5523151 C4.22358765,15.5534297 4.2250303,15.55454 4.22647627,15.555646 L11.0872776,20.8031356 C11.6250734,21.2144692 12.371757,21.2145375 12.909628,20.8033023 L19.7677785,15.559828 C20.1693192,15.2528257 20.2459576,14.6784381 19.9389553,14.2768974 C19.9376429,14.2751809 19.9363245,14.2734691 19.935,14.2717619 L19.935,14.2717619 C19.6266937,13.8743807 19.0546209,13.8021712 18.6572397,14.1104775 C18.654352,14.112718 18.6514778,14.1149757 18.6486172,14.1172508 L12.9235044,18.6705218 C12.377022,19.1051477 11.6029199,19.1052208 11.0563554,18.6706981 Z" fill="#000000" opacity="0.3" />
                    </g>
                </svg>
                <!--end::Svg Icon-->
            </span>
            <span class="menu-text">Dashboard</span>
        </a>
    </li>
    <li class="menu-section">
        <h4 class="menu-text">区段 A(一级菜单示例)</h4>
        <i class="menu-icon ki ki-bold-more-hor icon-md"></i>
    </li>
    <li class="menu-item" aria-haspopup="true">
        <a href="item1" class="menu-link">
            <span class="svg-icon menu-icon">
                <i class="fas fa-dice-one"></i>
            </span>
            <span class="menu-text">菜单 A</span>
        </a>
    </li>
    <li class="menu-item" aria-haspopup="true">
        <a href="item2" class="menu-link">
            <span class="svg-icon menu-icon">
                <i class="fas fa-dice-two"></i>
            </span>
            <span class="menu-text">菜单 B(带徽章)</span>
            <span class="menu-label">
                <span class="label label-rounded label-primary">6</span>
            </span>
        </a>
    </li>
    <li class="menu-item menu-item-active" aria-haspopup="true">
        <a href="item2" class="menu-link">
            <span class="svg-icon menu-icon">
                <i class="fas fa-dice-three"></i>
            </span>
            <span class="menu-text">菜单 C(高亮)</span>
        </a>
    </li>
    <li class="menu-section">
        <h4 class="menu-text">区段 B(二级菜单示例)</h4>
        <i class="menu-icon ki ki-bold-more-hor icon-md"></i>
    </li>
    <li class="menu-item menu-item-submenu" aria-haspopup="true" data-menu-toggle="hover">
        <a href="javascript:;" class="menu-link menu-toggle">
            <span class="svg-icon menu-icon">
                <i class="fas fa-dice-one"></i>
            </span>
            <span class="menu-text">一级菜单(默认关闭)</span>
            <i class="menu-arrow"></i>
        </a>
        <div class="menu-submenu">
            <i class="menu-arrow"></i>
            <ul class="menu-subnav">
                <li class="menu-item menu-item-parent" aria-haspopup="true">
                    <span class="menu-link">
                        <span class="menu-text">一级菜单(默认关闭)</span>
                    </span>
                </li>
                <li class="menu-item" aria-haspopup="true">
                    <a href="custom/apps/inbox.html" class="menu-link">
                        <i class="menu-bullet menu-bullet-line"><span></span></i>
                        <span class="menu-text">二级菜单 a</span>
                    </a>
                </li>
                <li class="menu-item" aria-haspopup="true">
                    <a href="custom/apps/inbox.html" class="menu-link">
                        <i class="menu-bullet menu-bullet-line"><span></span></i>
                        <span class="menu-text">二级菜单 b(带徽章)</span>
                        <span class="menu-label">
                            <span class="label label-danger label-inline">new</span>
                        </span>
                    </a>
                </li>
            </ul>
        </div>
    </li>
    <li class="menu-item menu-item-submenu menu-item-here menu-item-open" aria-haspopup="true" data-menu-toggle="hover">
        <a href="javascript:;" class="menu-link menu-toggle">
            <span class="svg-icon menu-icon">
                <i class="fas fa-dice-two"></i>
            </span>
            <span class="menu-text">一级菜单(高亮/默认打开)</span>
            <i class="menu-arrow"></i>
        </a>
        <div class="menu-submenu">
            <i class="menu-arrow"></i>
            <ul class="menu-subnav">
                <li class="menu-item menu-item-parent" aria-haspopup="true">
                    <span class="menu-link">
                        <span class="menu-text">一级菜单(高亮/默认打开)</span>
                    </span>
                </li>
                <li class="menu-item menu-item-active" aria-haspopup="true">
                    <a href="custom/apps/inbox.html" class="menu-link">
                        <i class="menu-bullet menu-bullet-line"><span></span></i>
                        <span class="menu-text">二级菜单 c(高亮)</span>
                    </a>
                </li>
                <li class="menu-item" aria-haspopup="true">
                    <a href="custom/apps/inbox.html" class="menu-link">
                        <i class="menu-bullet menu-bullet-line"><span></span></i>
                        <span class="menu-text">二级菜单 d(带徽章)</span>
                        <span class="menu-label">
                            <span class="label label-danger label-inline">new</span>
                        </span>
                    </a>
                </li>
            </ul>
        </div>
    </li>
    <li class="menu-section">
        <h4 class="menu-text">区段 C(三级菜单示例)</h4>
        <i class="menu-icon ki ki-bold-more-hor icon-md"></i>
    </li>
    <li class="menu-item menu-item-submenu menu-item-open menu-item-here" aria-haspopup="true" data-menu-toggle="hover">
        <a href="javascript:;" class="menu-link menu-toggle">
            <span class="svg-icon menu-icon">
                <i class="fas fa-dice-one"></i>
            </span>
            <span class="menu-text">一级菜单</span>
            <i class="menu-arrow"></i>
        </a>
        <div class="menu-submenu">
            <i class="menu-arrow"></i>
            <ul class="menu-subnav">
                <li class="menu-item menu-item-parent" aria-haspopup="true">
                    <span class="menu-link">
                        <span class="menu-text">一级菜单</span>
                    </span>
                </li>
                <li class="menu-item menu-item-submenu menu-item-open menu-item-here" aria-haspopup="true" data-menu-toggle="hover">
                    <a href="javascript:;" class="menu-link menu-toggle">
                        <i class="menu-bullet menu-bullet-line"><span></span></i>
                        <span class="menu-text">二级菜单(默认打开)</span>
                        <span class="menu-label">
                            <span class="label label-rounded label-primary">6</span>
                        </span>
                        <i class="menu-arrow"></i>
                    </a>
                    <div class="menu-submenu">
                        <i class="menu-arrow"></i>
                        <ul class="menu-subnav">
                            <li class="menu-item" aria-haspopup="true">
                                <a href="custom/apps/user/list-default.html" class="menu-link">
                                    <i class="menu-bullet menu-bullet-dot"><span></span></i>
                                    <span class="menu-text">三级菜单 1</span>
                                </a>
                            </li>
                            <li class="menu-item menu-item-active" aria-haspopup="true">
                                <a href="custom/apps/user/list-datatable.html" class="menu-link">
                                    <i class="menu-bullet menu-bullet-dot"><span></span></i>
                                    <span class="menu-text">三级菜单 2(高亮)</span>
                                </a>
                            </li>
                        </ul>
                    </div>
                </li>
                <li class="menu-item menu-item-submenu" aria-haspopup="true" data-menu-toggle="hover">
                    <a href="javascript:;" class="menu-link menu-toggle">
                        <i class="menu-bullet menu-bullet-line"><span></span></i>
                        <span class="menu-text">二级菜单(默认关闭)</span>
                        <i class="menu-arrow"></i>
                    </a>
                    <div class="menu-submenu">
                        <i class="menu-arrow"></i>
                        <ul class="menu-subnav">
                            <li class="menu-item menu-item-submenu" aria-haspopup="true" data-menu-toggle="hover">
                                <a href="javascript:;" class="menu-link menu-toggle">
                                    <i class="menu-bullet menu-bullet-dot"><span></span></i>
                                    <span class="menu-text">二级菜单(默认关闭)</span>
                                    <i class="menu-arrow"></i>
                                </a>
                                <div class="menu-submenu">
                                    <i class="menu-arrow"></i>
                                    <ul class="menu-subnav">
                                        <li class="menu-item" aria-haspopup="true">
                                            <a href="custom/apps/profile/profile-1/overview.html" class="menu-link">
                                                <i class="menu-bullet menu-bullet-line"><span></span></i>
                                                <span class="menu-text">三级菜单 3</span>
                                            </a>
                                        </li>
                                    </ul>
                                </div>
                            </li>
                        </ul>
                    </div>
                </li>
            </ul>
        </div>
    </li>
</ul>

效果:

image.png

关于手风琴功能:Metronic 菜单已实现手风琴功能,即当展开一个闭合的菜单时,非当前页面所在菜单项的房系菜单会自动闭合。

xoyozo 5 年前
7,784

通过 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 的功能,具体步骤如下:

  1. 将 Consts.cs 从项目中排除。

  2. 打开 Handlers/UploadHandler.cs,找到 UploadConfig 类,将

    FtpUpload 改为 OssUpload,

    FtpAccount 改为 OssAccessKeyId,

    FtpPwd 改为 OssAccessKeySecret,

    FtpIp 改为 OssAccessEndpoint,

    再添加一个属性 OssBucketName。

  3. 打开 Handlers/UploadHandler.cs,找到 Process() 方法, 

    将 UploadConfig.FtpUpload 改为 UploadConfig.OssUpload,

    将 Consts.AliyunOssServer.* 改为 UploadConfig.Oss*。

  4. 打开 Handlers/CrawlerHandler.cs,找到 Fetch() 方法,

    将 Config.GetValue<bool>("catcherFtpUpload") 改为 Config.GetValue<bool>("OssUpload"),

    将 Consts.AliyunOssServer.* 改为 Config.GetString("Oss*")。

  5. 打开 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 属性。

  6. 打开 config.json,添加相关配置项(注:配置文件中的 *FtpUpload 全部废弃,统一由 OssUpload 决定)

    "OssUpload": true,
    "OssAccessEndpoint": "",
    "OssAccessKeyId": "",
    "OssAccessKeySecret": "",
    "OssBucketName": "",

    将 xxxUrlPrefix 的值改为 OSS 对应的 CDN 网址(以 / 结尾,如://cdn.xoyozo.net/)。


其它注意点:

  • 若使用 UEditorNetCore github 提供的源代码类库代替 nuget,且使用本地存储,那么需要将 Handlers 目录中与定义 savePath 相关的代码(查找  var savePath)替换成被注释的行

xoyozo 5 年前
8,317

发现主力:

筹码集中(未必是主力)且获得30%仍未出货,确定是主力。

image.png

买入方法:

破位买入法、回调买入法(推荐)

注意必须结合其他功能指标:主力雷达拐头向上、AI机构活跃度重新活跃

image.png

卖出方法:

底部筹码松动时减仓,筹码峰转移到上方时平仓。

image.png

实战案例:

image.pngimage.png主力筹码案例.pngimage.png

xoyozo 6 年前
521

如果富士施乐 DocuPrint P118 w 打印机开启后电脑无法连接,但路由器上显示已连网,很有可能是 IP 已变更(增添新设备后抢占了打印机原来的 IP 地址)。

这时候需要重新安装打印机驱动:(可能有不需要重装驱动的方法)

富士施乐 DocuPrint P118 w 在安装驱动时需要路由器支持 WPS/AOSS,

但小米路由器 Pro 中找不到该功能,而且实测无线方式安装打印机驱动失败。

我的方法是,找一台 TP-LINK 路由器,

将 SSID 和 WIFI 密码改成与小米路由器相同,

关闭小米路由器,

电脑连接这个 SSID,

然后按无线的方式安装打印机驱动程序,

完成后,关闭 TP-LINK 路由器,打开小米路由器,

网络启动后,重启打印机。

(打印时可能需要在窗口中选择另一个打印机名称)

xoyozo 6 年前
8,209

本文适用于 CentOS(Linux),Window 系统请移步:https://xoyozo.net/Blog/Details/FileSystemWatcher


安装

参照官方说明:https://github.com/inotify-tools/inotify-tools/wiki

以 CentOS 为例:

安装 EPEL :

yum install -y epel-release && yum update

安装 inotify-tools:

yum install inotify-tools

在 CentOS-7 中

yum --enablerepo=epel install inotify-tools

v3.14-8.el7.×86_64 as of 4-18-2018


配置

创建 Shell 脚本文件:

#!/bin/bash
inotifywait -mrq -e modify,attrib,move,create,delete /要监视的目录 | while read dir event file;
do
  curl -d "df=${dir}${file}&ev=${event}" https://xxx.xxx.xxx/api/inotify/
done

并将该文件设置为可执行:chmod +x xxx.sh

注:上述示例将对文件的部分操作事件信息传递到远程接口。inotifywaitcurl 的用法请自行百度。


如果需要忽略部分文件路径,可以使用正则表达式进行过滤,例:

#!/bin/bash
inotifywait -mrq -e modify,attrib,move,create,delete /要监视的目录 | while read dir event file;
do
  df=${dir}${file};
  if [[ ! $df =~ ^/www/wwwroot/[0-9a-z]+.xxx.com/[0-9a-zA-Z]+/Runtime/Cache/[0-9a-zA-Z]+/[0-9a-f]{32}.php$
     && ! $df =~ ^/www/wwwroot/[0-9a-z]+.xxx.com/[0-9a-zA-Z]+/Runtime/Logs/[0-9a-zA-Z]+/[0-9]{2}_[0-9]{2}_[0-9]{2}.log$
    ]]; then
    curl -d "df=${df}&ev=${event}" https://xxx.xxx.xxx/api/inotify/
  else
    echo "Ignored: $df"
  fi
done

注意:bash 中使用 [[ string =~ regex ]] 表达式进行正则匹配,“!”取反,“&&”为且,书写时不要吝啬使用空格,否则程序可能不会按预期运行。


执行

先直接执行 sh 命令,排查一些错误。

Failed to watch /www/wwwroot; upper limit on inotify watches reached!

Please increase the amount of inotify watches allowed per user via `/proc/sys/fs/inotify/max_user_watches'.

因被监视的目录中文件数超过默认值 8192 提示失败,更改该值即可。

echo 8192000 > /proc/sys/fs/inotify/max_user_watches

设置开机自动运行,参:https://xoyozo.net/Blog/Details/linux-init-d


xoyozo 6 年前
5,268

使用 JS 根据屏幕宽度来计算页面尺寸和字体大小并不是一个好办法。

rem 单位是跟随网页根元素的 font-size 改变而自动改变的,是一个相对的长度单位,非常适合在不同手机界面上自适应屏幕大小。 


一般的手机浏览器的默认字体大小为 16px。即:

:root { font-size: 16px; } /* 在 HTML 网页中,:root 选择器相当于 html */

也就是说,如果我定义一个宽度为 1rem,那么它的实际宽度为 16px。反过来说,如果设计稿宽度为 1000px,那么相当于 1000÷16=62.5‬rem。因此,我们设置一个 62.5rem 的宽度,即可正常显示 1000px 的设计效果。


为了适应不同屏幕尺寸的手机,只需按比例换算更改 :root 的 font-size 即可。这个比例是由“窗口宽度 ÷ 设计稿宽度”得到的,姑且称它为“窗设比”。

以 iPhone X 逻辑宽度 375px 为例,设计稿宽度 1000px 时,窗设比为:0.375。:root 的 font-size 将会被重置为 16 * 0.375 = 6px。上面的 62.5rem 宽度将对应 iPhone X 宽度为 62.5 * 6 = 375px,正好是屏幕逻辑宽度。


根据这个思路,理论上,我们只需要使用 CSS 将 :root 的 font-size 设置为 1px,然后使用 JS 重置为与“窗设比”相乘的积(本例中将被重置为 0.375px)。这样,我们可以不通过换算,只需要更改单位,将设计稿中的 500px 直接写入到 CSS 为 500rem,即可实现在 iPhone X 上显示逻辑宽度为(500 * 0.375 =)187.5px,即设计稿中的 1/2 宽度对应到屏幕上 1/2 的宽度。


实际上,部分安卓手机(如华为某旗舰机)不支持 :root 的 font-size 小于 12px,其它一些浏览器也会出现小于 12px 的文字以 12px 显示。


为了避免在任何环节出现小于 12px 的 font-size 值,且不增加设计稿尺寸到 CSS 数值的换算难度,我们设计了以下思路:

  1. 约定“设样比(设计稿的 px 值与 CSS 的 rem 值的比)”为 100;

  2. 使用 JS 将 :root 的 font-size 重置为“设样比”与“窗设比(窗口宽度 ÷ 设计稿宽度)”的乘积;

  3. 计算 CSS 时的 rem 值只需从设计稿中获取 px 值再除以“设样比”100 即可。 


这样做的另一个好处是,不用要求设计师必须按多少尺寸来设计,当然按主流尺寸来建议是可行的。


完整代码(jQuery 版):

<!DOCTYPE html>
<html lang="zh-cn">
<head>
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0" />
    <title>Index</title>
    <style>
        body { margin: 0; text-align: center; }
        #app { margin: 0 auto; background-color: #EEE; display: none; }
        /* 长度计算方法:从设计稿获得长度(例如 160px),除以 px2rem 值(本例为 100),得到 1.6rem */
        .whole { background-color: #D84C40; width: 10rem; }
        .half { background-color: #3296FA; width: 5rem; }
        .half2 { background-color: #3296FA; width: 5rem; margin-left: 5rem; }
    </style>
</head>
<body>
    <div id="app">
        <div class="whole">100%</div>
        <div class="half">50%</div>
        <div class="half2">50%</div>
    </div>
    <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
    <script>
        // [函数] 重置尺寸
        function fn_resize() {
            // 设计稿宽度(px)
            var designsWidth = 1000;
            // 约定的“设计稿 px 值与 CSS rem 值的比”,为方便计算,一般无需改动
            var px2rem = 100;
            // 限制 #app 的最大宽度为设计稿宽度
            $('#app').css('max-width', designsWidth).css('min-height', $(window).height());
            // 重置 :root 的 font-size
            var appWidth = Math.min($(window).width(), designsWidth);
            $(':root').css('font-size', px2rem * appWidth / designsWidth);
            $('#app').show();
        }
        // 页面加载完成后重置尺寸
        $(fn_resize);
        // 改变窗口大小时重置尺寸
        $(window).resize(fn_resize);
    </script>
</body>
</html>

完整代码(Vue2 版):

<!DOCTYPE html>
<html lang="zh-cn">
<head>
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0" />
    <title>Index</title>
    <style>
        body { margin: 0; text-align: center; }
        #app { margin: 0 auto; background-color: #EEE; }
        /* 长度计算方法:从设计稿获得长度(例如 160px),除以 px2rem 值(本例为 100),得到 1.6rem */
        .whole { background-color: #D84C40; width: 10rem; }
        .half { background-color: #3296FA; width: 5rem; }
        .half2 { background-color: #3296FA; width: 5rem; margin-left: 5rem; }
    </style>
</head>
<body>
    <div id="app" v-bind:style="{ maxWidth: designsWidth + 'px', minHeight: appMinHeight + 'px' }" style="display: none;" v-show="appShow">
        <div class="whole">100%</div>
        <div class="half">50%</div>
        <div class="half2">50%</div>
        <div>appWidth: {{ appWidth }}</div>
    </div>
    <script src="https://cdn.jsdelivr.net/npm/vue@2"></script>
    <script>
        var app = new Vue({
            el: '#app',
            data: {
                // 设计稿宽度(px)
                designsWidth: 1000,
                // 约定的“设计稿 px 值与 CSS rem 值的比”,为方便计算,一般无需改动
                px2rem: 100,
                // #app 的 width
                appWidth: 0, // 勿改
                appMinHeight: 0, // 勿改
                appShow: false, // 勿改
            },
            mounted: function () {
                // 页面加载完成后重置尺寸
                this.fn_resize();
                const that = this;
                // 改变窗口大小时重置尺寸
                window.onresize = () => {
                    return (() => {
                        console.log('RUN window.onresize()');
                        that.fn_resize();
                    })();
                };
            },
            watch: {
                // 侦听 appWidth 更改 root 的 font-size
                appWidth: function () {
                    console.log('RUN watch: appWidth');
                    var root = document.getElementsByTagName("html")[0];
                    root.style.fontSize = (this.px2rem * this.appWidth / this.designsWidth) + 'px';
                    this.appShow = true;
                }
            },
            methods: {
                fn_resize: function () {
                    console.log('RUN methods: fn_resize()');
                    this.appWidth = Math.min(document.body.clientWidth, this.designsWidth);
                    this.appMinHeight = document.documentElement.clientHeight;
                }
            }
        });
    </script>
</body>
</html>

完整代码(Vue3 版):

<!DOCTYPE html>
<html>
<head>
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0" />
    <title>Index</title>
    <style>
        body { margin: 0; text-align: center; }
        #app > div { display: none; margin: 0 auto; background-color: #EEE; }
        /* 长度计算方法:从设计稿获得长度(例如 160px),除以 px2rem 值(本例为 100),得到 1.6rem */
        .whole { background-color: #D84C40; width: 10rem; }
        .half { background-color: #3296FA; width: 5rem; }
        .half2 { background-color: #3296FA; width: 5rem; margin-left: 5rem; }
    </style>
</head>
<body>
    <div id="app">
        <div style="display: none;" v-bind:style="{ maxWidth: designsWidth + 'px', minHeight: appMinHeight + 'px', display: appShow ? 'block' : 'none' }">
            <div class="whole">100%</div>
            <div class="half">50%</div>
            <div class="half2">50%</div>
            <div>appWidth: {{ appWidth }}</div>
        </div>
    </div>
    <script src="https://cdn.jsdelivr.net/npm/vue@3"></script>
    <script>
        const app = Vue.createApp({
            data: function () {
                return {
                    // 设计稿宽度(px)
                    designsWidth: 1000,
                    // 约定的“设计稿 px 值与 CSS rem 值的比”,为方便计算,一般无需改动
                    px2rem: 100,
                    // #app 的 width
                    appWidth: 0, // 勿改
                    appMinHeight: 0, // 勿改
                    appShow: false, // 勿改
                }
            },
            mounted: function () {
                // 页面加载完成后重置尺寸
                this.fn_resize();
                const that = this;
                // 改变窗口大小时重置尺寸
                window.onresize = () => {
                    return (() => {
                        console.log('RUN window.onresize()');
                        that.fn_resize();
                    })();
                };
            },
            watch: {
                // 侦听 appWidth 更改 root 的 font-size
                appWidth: function () {
                    console.log('RUN watch: appWidth');
                    var root = document.getElementsByTagName("html")[0];
                    root.style.fontSize = (this.px2rem * this.appWidth / this.designsWidth) + 'px';
                    this.appShow = true;
                }
            },
            methods: {
                fn_resize: function () {
                    console.log('RUN methods: fn_resize()');
                    this.appWidth = Math.min(document.body.clientWidth, this.designsWidth);
                    this.appMinHeight = document.documentElement.clientHeight;
                }
            }
        });
        const vm = app.mount('#app');
    </script>
</body>
</html>


示例中 CSS 初始 :root 的 font-size 为 16px(一个较小值,防止页面加载时瞬间出现大号文字影响用户体验),经过 fn_resize 后,:root 的 font-size 被设置为(100 * 375 / 1000 =)37.5px(iPhone X 中),那么宽度为 1000px 的设计稿中的 500px 换算到 CSS 中为 5rem,也即 37.5 * 5 = 187.5px,就是 iPhone X 的屏幕宽度的一半。

示例中 id 为 app 的 div 是用来在 PC 浏览器中限制页面内容最大宽度的(类似微信公众号发布的文章),如果网页不需要在 PC 端显示,jQuery 版代码中的 $('#app').width() 可以用 $(window).width() 来代替。

这个 div#app 一般设计有背景色,用于在宽度超过设计稿的设备上显示时区别于 body 的背景。但是当网页内容不超过一屏时,div#app 高度小于窗口,示例中与 appMinHeight 相关的代码就是为了解决这个问题。

示例中 div#app 隐藏/显示相关代码用于解决在页面加载初期由于 font-size 值变化引起的一闪而过的排版错乱。

image.png


最后补充一点,如果改变窗口大小时涉及执行耗时的操作,为避免页面卡顿,可以参考这篇文章添加函数防抖:https://xoyozo.net/Blog/Details/js-function-anti-shake

xoyozo 6 年前
9,532