博客 (45)

使用 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 5 年前
7,211

* 若无特殊注明,本文中的“手机”包含平板电脑


判断网页是否在移动设备中打开


Web Form:

public static bool IsMobileDevice
{
    get
    {
        return (HttpContext.Current.Request.Browser?.IsMobileDevice ?? false)
            || (HttpContext.Current.Request.UserAgent?.Contains("Mobile") ?? false);
    }
}


ASP.NET Core:

好像还没有找到,可能是因为随着响应式布局的流行以及电脑和手机之间的界线越来越不明朗,取消了这个判断?


判断网页是否在微信浏览器中打开(宽松)


Web Form:

public bool IsInWechat
{
    get
    {
        return HttpContext.Current.Request.UserAgent?.Contains("MicroMessenger") ?? false;
    }
}


ASP.NET Core:

需注入 IHttpContextAccessor

public bool IsInWechat
{
    get
    {
        string ua = httpContextAccessor.HttpContext.Request.Headers[HeaderNames.UserAgent];
        return ua?.Contains("MicroMessenger") ?? false;
    }
}


判断网页是否在微信浏览器中打开(严谨)


使用 JS-SDK 网页授权来判断




xoyozo 6 年前
4,471

要将 ASP.NET Core 中的会话存储在 Redis 中,可以按照以下步骤进行操作:

安装必要的 NuGet 包:

首先,确保你已经安装了 Microsoft.Extensions.Caching.StackExchangeRedis 包,这是与 Redis 集成的主要包。

在 Visual Studio 中,你可以使用 NuGet 包管理器控制台执行以下命令:

Install-Package Microsoft.Extensions.Caching.StackExchangeRedis

配置 Redis 连接:

在你的 ASP.NET Core 应用程序的 Startup.cs 文件中,使用 AddStackExchangeRedisCache 方法配置 Redis 连接。在 ConfigureServices 方法中添加以下代码:

services.AddStackExchangeRedisCache(options =>
{
    options.Configuration = "your_redis_connection_string";
    options.InstanceName = "your_instance_name";
});

请将 "your_redis_connection_string" 替换为你的 Redis 连接字符串,并将 "your_instance_name" 替换为你的 Redis 实例名称。

配置会话中间件:

Startup.csConfigureServices 方法中,使用 AddSession 方法配置会话中间件,并在 Configure 方法中启用它:

services.AddSession(options =>
{
    options.IdleTimeout = TimeSpan.FromMinutes(30); // 设置会话超时时间
});

使用会话中间件:

Startup.csConfigure 方法中,在调用 app.UseRouting() 之前添加以下代码来启用会话中间件:

app.UseSession();

在控制器或视图中使用会话:

现在,你可以在控制器或视图中使用会话来存储和检索数据。例如:

// 存储数据到会话
HttpContext.Session.SetString("Key", "Value");

// 从会话中检索数据
var value = HttpContext.Session.GetString("Key");

通过这种方式,你可以将会话数据存储在 Redis 中,而不是默认的内存中。

请确保根据你的应用程序的需求进行适当的配置和安全措施,以确保 Redis 连接的安全性和可靠性。

xoyozo 6 年前
6,439

首先在项目根目录中创建目录 Areas。

在 Areas 中添加区域,以“Admin”、“Console”两个区域为例:

image.png


VS 提醒在 Startup.cs 的 Configure() 方法中添加 app.UseMvc,此与 app.UseEndpoints 冲突,我们改为在 app.UseEndpoints 中添加 MapControllerRoute(放在 default 之前):

app.UseEndpoints(endpoints =>
{
    endpoints.MapControllerRoute(
        name: "MyArea",
        pattern: "{area:exists}/{controller=Home}/{action=Index}/{id?}");

    endpoints.MapControllerRoute(
        name: "default",
        pattern: "{controller=Home}/{action=Index}/{id?}");
});


给区域的控制器添加属性,如:

[Area("Admin")]
public class HomeController : Controller
{
    public IActionResult Index()
    {
        return View();
    }
}

更多详细请参官方文档:https://docs.microsoft.com/zh-cn/aspnet/core/mvc/controllers/areas?view=aspnetcore-3.1#area-folder-structure

xoyozo 6 年前
6,881

* 本文凡使用变量 httpContextAccessor_httpContextAccessor 的地方都需要需要注入 Microsoft.AspNetCore.Http.IHttpContextAccessor

* 在视图(view)中应以 ViewContext 类引出


获取当前页面网址:

using Microsoft.AspNetCore.Http.Extensions;
string url = Request.GetDisplayUrl();


获取当前访问的域名和端口:

image.png


获取来源:

Request.GetTypedHeaders().Referer


判断 Scheme 是否为 https:

Request.IsHttps


获取客户端IP和端口、服务器IP和端口:

image.png


获取浏览器用户代理(UserAgent):

httpContextAccessor.HttpContext.Request.Headers[HeaderNames.UserAgent];


获取当前请求的唯一标识:

httpContextAccessor.HttpContext.TraceIdentifier

返回结果:80000564-0002-f700-b63f-84710c7967bb

用途:可作为生成随机数的种子。


获取客户端IP地址:

httpContextAccessor.HttpContext.Connection.RemoteIpAddress


获取当前项目根目录磁盘路径:

AppContext.BaseDirectory // 以“\”结尾

注意:此路径到项目根目录,而非网站根目录,网站根目录请自行追加,默认为 wwwroot\


编码/解码:(参数和返回值均为 string?

using System.Net;
WebUtility.HtmlEncode(myString)
WebUtility.HtmlDecode(myString)
WebUtility.UrlEncode(myString)
WebUtility.UrlDecode(myString)


更多:https://xoyozo.net/Blog/Index/Core


xoyozo 6 年前
4,352

确保磁盘未满,否则会提示各种无法连接。


  1. 在“小米WiFi”App 中开启对应路由器的“SAMBA 协议”(工具箱 - 更多工具 - SAMBA)。

  2. 在“小米WiFi”App 中开启相应电脑或设备的“全盘访问”。

  3. 通过 miwifi.com 下载安装 PC 客户端。

  4. 打开“此电脑”,“映射网络驱动器”或“添加一个网络位置”,填写路由器的地址和目录,如:“\\192.168.31.1\下载”。

  5. 如果上一步无法成功添加,那么首先需要检查 samba 服务器是否正常,可以在 iPhone 上安装“Remote File Manager”来验证。

  6. 如果 iPhone 连接正常,那么在电脑上打开“本地组策略编辑器”(命令行:gpedit.msc),在“计算机配置 - 管理模板 - 网络 - Lanman 工作站”右侧,双击“启用不安全的来宾登录”,改为“已启用”。Win7 用户参考此文


注意:电脑必须运行 PC 客户端才能访问小米路由器硬盘。

xoyozo 6 年前
17,577

获取控制器(Controller)名称

控制器的 Action 中

RouteData.Values["controller"].ToString()


RouteData.Route.GetRouteData(HttpContext).Values["controller"].ToString()


视图中

ViewContext.RouteData.Values["controller"].ToString()


ViewContext.RouteData.Route.GetRouteData(Context).Values["controller"].ToString()


公共方法中

HttpContext.Current.Request.RequestContext.RouteData.Values["controller"].ToString()


过滤器中

以 ActionFilterAttribute 为例,实现一个继承类,重写相关方法,在重写的方法中获取控制器名称

/// <summary>
/// 可用于检查用户是否已经登录(在 Action 之前执行)
/// </summary>
/// <param name="filterContext"></param>
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
    base.OnActionExecuting(filterContext);
    string controller = filterContext.RouteData.Values["controller"].ToString();
}


获取 Action 名称

将以上代码中的 "controller" 替换成 "action" 即可获取 Action 名称。

获取区域(Area)名称

控制器的 Actione 中

RouteData.DataTokens["area"]?.ToString()

注意:应使用空传播运算符

获取所有 Action 参数

RouteData.Values
转自 邵雷 6 年前
4,429

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

直接注册时可勾选使用的公众号、小程序、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,488

这是一篇可能解决困扰了 .NET 程序猿多年的文章!

首先,使用 Visual Studio 2019 创建一个 ASP.NET WEB 应用程序,直接选用了 MVC 模板。

直接发布项目,默认的设置如下图:

image.png

对于小型项目,按默认设置发布基本可满足正常运行,首次运行打开第一个页面基本在 5~6 秒(视服务器配置),其它页面的首次打开也基本在 1~2 秒完成,非首次瞬间打开。

一旦项目功能变得复杂,文件增多,会导致发布后首次运行打开第一个页面 30 秒以上,其它页面的首次打开 10 秒左右,非首次瞬间打开。

这是因为项目在发布时没有进行预编译,而是在用户访问网页时动态编译,一旦应用程序池回收,或项目文件改动,都会重新编译,再次经历缓慢的“第一次”,这是不能忍的。

于是咱们来研究一下“预编译”。

在发布页面勾选“在发布期间预编译”,这时发布会在“输出”窗口显示正在执行编译命令(编译时间会比较长):

image.png

该选项对发布后的文件结构和内容影响不大,因此对首次执行效率的提升也不大,重要的在后面。

在“高级预编译设置”窗口中:

image.png

我们针对预编译选项和合并选项分别做测试。

不勾选“允许更新预编译站点”,会致 bin 目录中生成许多 .compiled 文件,而所有 .cshtml 文件的内容都是:

这是预编译工具生成的标记文件,不应删除!

如果是 Web From 网站,.aspx/.ashx 等文件内容同上。

尝试打开网站页面,你会发现,除了项目启动后的第一个页面仍然需要 1~2 秒(无 EF),其余每个页面的首次都是瞬间打开的(EF 的首次慢不在本文讨论范围)。这让我对预编译有一种相见恨晚的感觉!

这里偷偷地告诉你,把 Views 目录删掉也不影响网页正常打开哦~为什么不让删,咱也不敢问,咱也不敢删。

目的达到了,有一些后遗症需要解决,比如 bin 目录内杂乱无章。

选“不合并。为每个页面和控件创建单独的程序集”,结果是 bin 多出许多 App_Web_*.dll 文件。

选“将所有输出合并到单个程序集”,填写程序集名称。这时会发现“输出”窗口执行了命令:

image.png

如果程序集名称跟已有名称冲突,会发生错误:

合并程序集时出错: 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 种发布方式:

微信图片_20190715160035.png

“视为库组件(删除 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 方式发布。

xoyozo 6 年前
12,149

确定唯一凭证:

若项目仅提供微信端访问,那么使用微信的授权(即以微信 openid / unionid 作为唯一凭证)是一种体现注重用户体验的方式。如果项目需要应用到多个平台上,那么通过微信来授权变得不那么完美(譬如未安装微信客户端或平台不支持使用微信授权等)。因手机的使用率已明显高于电脑的使用率,再加上用户帐号实名认证的要求,以手机号码为作为帐号的唯一凭证是非常合理的。

 

登录唤起条件:

当用户打开需要登录授权的页面或触发需要登录授权才能执行的操作时。

 

场景实现:

平台

实现

微信网页/小程序

授权获取 openid / unionid

同时若未绑定手机号码则强制绑定(发送验证码的方式)

如果允许在微信上登录不同的用户帐号,那么需要提供【“手机号码 + 密码”登录】方式

手机 App

提供【“手机号码 + 密码”登录】

提供【第三方帐号登录】,登录后强制绑定手机号码

参“京东”等

PC 端浏览器网页

提供【“手机号码 + 密码”登录】

登录框可切换成【扫二维码登录】,登录后强制绑定手机号码

手机浏览器网页

提供【“手机号码 + 密码”登录】

如果提供【第三方帐号登录】,那么可能需要在网页上手动输入第三方帐号的用户名密码,而非跳转到第三方 App 上(视第三方开放平台)

 

l  【“手机号码 + 密码”登录】页面显示填写手机号码和密码框的表单,提供“忘记密码”、“短信验证码登录”、“手机快速注册”等功能。如果在可长期记住登录状态的场景中(如手机 App)也可取消密码,直接使用短信验证码登录。“手机快速注册”必须通过短信验证码实现实名认证。

l  【第三方帐号登录 / 扫二维码登录】App 中提供跳转相应第三方 App(如微信)授权登录,PC 端网页中提供微信扫二维码登录。登录获得 unionid,同时判断是否已经绑定过手机号码,若未绑则强制绑定,确保数据库中的每一条用户信息都有手机号码。

 

注:同一个微信帐号在不同的公众号和 App 中的 openid 不同,将这些公众号和 App 通过微信开放平台绑定后,同一个微信帐号的 unionid 是相同的,可用于判断同一用户。


流程图:

image.png

多平台用户登录模块设计.vsdx


各平台登录设计:

xoyozo 7 年前
5,204