折腾了很多天,首先,要用证书登录,别的系统别想了,要么读不到证书,要么读出来点击没反应,无法选择。
要搞定,很简单,装个 windows xp 吧!

装了 windows xp ,登录、申请一切都没问题了。最后,等初审出来,要支付费用,发现支付不了!
怎么办,我开始是用的系统自带的IE,不行,换360浏览器,还是不行。最后:
装 Google Chrome,解决问题!

真不容易啊。不用浪费资源拿一台电脑专门来做这个系统,直接弄个 vmware,装个虚拟机就可以了。虚拟机文件还可以备份,很容易保留。

我们的项目从.net framework 转到 net core webapi,不想改太多代码,特别是身份认证,之前是直接在 QueryString 中传输 token 来认证,各个 action 上有个自定义的 CheckLogin 标签,要改成的 JWT 认证体系,工作量巨大,于是想到利用过滤器来实现自定义的身份认证过程。

先建一个空的 Attribute,不用任何功能,有这个 Attribute 的 Action 表示都需要认证

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class LoginCheckAttribute : Attribute
{
}

然后建一个 IActionFilter

public class AuthFilter : IActionFilter
{
    private const string UnauthorizedMessage = "授权失效或过期,请重新登录。";

    public void OnActionExecuting(ActionExecutingContext context)
    {
        var controllerInfo = context.ActionDescriptor as ControllerActionDescriptor;
        var needcheck = controllerInfo?.EndpointMetadata.Any(_ => _.GetType() == typeof(LoginCheckAttribute)) ?? false;
        if (needcheck)
        {
            var token = context.HttpContext.Request.Query["token"].ToString();

            ApiResult<object>? result = null;
            if (string.IsNullOrEmpty(token))
            {
                result = new() { Code = 888, Message = UnauthorizedMessage };
            }
            else
            {
                var uinfo = Factory.SessionAccess.GetUserByToken(token);//根据token得到用户信息
                if (uinfo == null)
                {
                    result = new() { Code = 888, Message = UnauthorizedMessage };
                }
                else
                {
                    if (uinfo.State != (int)AdminState.enable)
                    {
                        result = new() { Code = 888, Message = "账号无法使用" };
                    }
                    else
                        context.HttpContext.Items["user"] = uinfo;//用户信息存在会话中,供业务逻辑中调用
                }
            }

            if(result != null)
            {
                context.Result = new UnauthorizedObjectResult(result);
            }
        }
    }

    public void OnActionExecuted(ActionExecutedContext context)
    {
    }
}

然后在 program.cs 中加入过滤器:

builder.Services.AddControllers().AddMvcOptions(options => options.Filters.Insert(0, new AuthFilter()));

业务逻辑中需要用到用户信息的地方这样获取:

    public UserDetails? UserInfo
    {
        get
        {
            if (MyHttpContext.Current == null) return null;
            if(MyHttpContext.Current.Items.TryGetValue("user", out var value))
            {
                return value as UserDetails;
            }
            return null;
        }
    }

直接用 扩展

    public static string GetRemoteIPAddress(this HttpContext context, bool allowForwarded = true)
    {
        if (allowForwarded)
        {
            string? header = context.Request.Headers["CF-Connecting-IP"].FirstOrDefault() ?? context.Request.Headers["X-Forwarded-For"].FirstOrDefault();
            if (!string.IsNullOrEmpty(header) && IPAddress.TryParse(header, out var ip))
            {
                return ip.ToString();
            }
        }
        var _ip = context.Connection.RemoteIpAddress;

        string res = "";
        if (_ip != null)
        {
            if (_ip.IsIPv4MappedToIPv6)
                res = _ip.MapToIPv4().ToString();
            else
                res = _ip.ToString();
        }
        return res;
    }

使用:

string ip = HttpContext.GetRemoteIPAddress();

开发技术上,微软走下坡路的一大原因,除了开源生态不够,还有一个很重要的原因,很多常用的功能,不能简单的代码实现,这是很多微软系程序猿头疼一件事。

比如,要想做个简单的 http server,需要很多代码,最后,性能跟IIS还相差十万八千里
比如,要搭个FTP,非要搞得很复杂,跟系统用户挂钩,不能简单的用户名、密码、目录
比如,要做个最简单的md5加密,还得好几行代码,还不是常见的写法,每次得去搜索
比如,得到一个时间戳,也是一样得好几行代码,敲很多文字
比如,读取数据库到对象,还要自己去实现 DataTableToEntityList
…………

太多了,最近在使用 net core 开发 控制台应用程序时,为了读取一个 json 配置文件,被折磨得不行了。。。。
网上的资源都是比较老的,读取时,无法解决错误:

IConfiguration builder = new ConfigurationBuilder()

这个简单的语句,怎么都是提示
错误 CS0246 未能找到类型或命名空间名“ConfigurationBuilder”(是否缺少 using 指令或程序集引用?)

我加了这些 Nuget 引用:

Microsoft.Extensions.Configuration
Microsoft.Extensions.Configuration.Json
Microsoft.Extensions.Configuration.EnvironmentVariables
Microsoft.Extensions.Configuration.FileExtensions

仍然没用,还是找不到!
真无语,这种常用的功能,居然要用微软自己的东西来实现这么麻烦,于是手动写了一个,而且增加了监控功能,当文件有修改时,自动读取新的配置。

代码如下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

namespace CommProcess
{
    /// <summary>
    /// Json 配置读取类
    /// </summary>
    public class JsonConfig
    {
        private JObject JsonObj;
        private readonly string JsonFilePath;
        private readonly FileSystemWatcher JsonWatcher;

        /// <summary>
        /// Json 配置读取类
        /// </summary>
        /// <param name="JsonFilePath">Json 文件路径,绝对或相对路径</param>
        /// <param name="MonitorChange">是否监视配置文件改动</param>
        public JsonConfig(string JsonFilePath, bool MonitorChange = true)
        {
            this.JsonFilePath = JsonFilePath;
            ReadFile();

            if (MonitorChange)
            {
                if (JsonFilePath.IndexOf(':') != 1)
                {
                    JsonFilePath = Path.Combine(Environment.CurrentDirectory, JsonFilePath);
                }
                JsonWatcher = new FileSystemWatcher();
                JsonWatcher.Path = Path.GetDirectoryName(JsonFilePath);
                JsonWatcher.Filter = Path.GetFileName(JsonFilePath);
                JsonWatcher.EnableRaisingEvents = true;
                JsonWatcher.Changed += JsonWatcher_Changed;
            }
        }

        private void JsonWatcher_Changed(object sender, FileSystemEventArgs e)
        {
            Task.Run(() =>
            {
                Task.Delay(1000);
                ReadFile();
            });
        }

        private void ReadFile()
        {
            using var stream = File.Open(JsonFilePath, FileMode.Open, FileAccess.Read);
            byte[] b = new byte[stream.Length];
            stream.Read(b, 0, b.Length);
            string json = Encoding.UTF8.GetString(b);
            JsonObj = JObject.Parse(json);
        }

        /// <summary>
        /// 获取配置值
        /// </summary>
        /// <typeparam name="T">数据类型</typeparam>
        /// <param name="node">路径,比如:config.server</param>
        /// <returns></returns>
        public T? Get<T>(string node)
        {
            var jnode = JsonObj.SelectToken(node);
            if (jnode == null) return default;
            return jnode.Value<T>();
        }
    }
}

使用很简单,

var jconfig = new JsonConfig("jsconfig.json");
var name = jconfig.Get<string>("info.name");

多条件查询及 left join 的利用,几乎在所有项目都会用到。但只有数据量大、并发大时才会有人去注意他的性能。

先来看一个最有代表性的分页存储过程:

ALTER PROCEDURE [dbo].[P_Article_List]  
@page int=1,
@pagesize int=10,
@key VARCHAR(450)=''
AS
BEGIN
    SELECT COUNT(1)OVER(PARTITION BY '''') AS Total,b.NickName,b.Avator,a.*
    FROM dbo.t_article a with (NOLOCK)
    LEFT join t_user b with (NOLOCK) ON b.UserID=a.UserID
    WHERE a.IsValid=1
    AND (@key='' or a.Title like '%'+@key+'%')
    ORDER BY a.order DESC desc
    OFFSET @pagesize*(@page-1) ROWS FETCH NEXT @pagesize ROWS ONLY
END

这是一个很标准的存储过程,这样写代码也很漂亮,但这样的性能很差。通过查看执行计划,你会发现一个很奇怪的问题:

明明是 left join,为什么执行计划里是 inner join 呢,要知道后者的性能是低很多的。

这个问题是因为 where 条件导致的,所以我们要优化它,就必须用新的方式:

先把左边表的数据按条件查询出来放入临时表,再将临时表与右表 left join,不带where

当然,还有一个很重要的因素,分页,我们可以把分页也在临时表里先做了,比如一页10条,这样最后 left join时,左表只有10条数据,这个性能,就会很高

最后贴出优化后的存储过程:

ALTER PROCEDURE [dbo].[P_Article_List]  
@page int=1,
@pagesize int=10,
@key VARCHAR(450)=''
AS
BEGIN
    SELECT COUNT(1)OVER(PARTITION BY '''') AS Total,b.NickName,b.Avator,a.* FROM
    (SELECT * FROM dbo.t_article with (NOLOCK)
        WHERE IsValid=1
        AND (@key='' or Title like '%'+@key+'%')
        ORDER BY order DESC desc
        OFFSET @pagesize*(@page-1) ROWS FETCH NEXT @pagesize ROWS ONLY
    ) a
    LEFT join t_user b with (NOLOCK) ON b.UserID=a.UserID
END

搞定,最后测试,我们数据库的这个存储过程,查询时间从平均 2.3 秒优化到了 0.15 秒!