清风博客 https://www.skyfinder.cc 关注IT世界,记录平凡生活 Sun, 04 Dec 2022 14:06:07 +0000 zh-CN hourly 1 https://wordpress.org/?v=6.1.1 Chrome浏览器中的XPath https://www.skyfinder.cc/2022/12/04/chrome%e6%b5%8f%e8%a7%88%e5%99%a8%e4%b8%ad%e7%9a%84xpath/ https://www.skyfinder.cc/2022/12/04/chrome%e6%b5%8f%e8%a7%88%e5%99%a8%e4%b8%ad%e7%9a%84xpath/#respond Sat, 03 Dec 2022 16:52:11 +0000 https://www.skyfinder.cc/?p=4030 背景

某一个应用自动在网页上获取一些文本内容,本来是通过document.querySelector来找指定节点。经过一段时间网页貌似升级了,一些节点的class属性的值会出现随机的变动,每次class属性的值都会不一样。最初的方式就失去了作用,根据节点内容的分析发现可以通过xpath来获取。曾经在IE浏览器上使用过XPath,并且API相当简单。在非IE浏览器上貌似没有这么好用。以下内容在Chrome浏览器进行尝试,经过测试可以完成自己的预期工作。

浏览器支持

Mozilla是根据DOM标准来实现对XPath的支持的。DOM Level 3附加标准DOM Level 3 XPath定义了用于在DOM中计算XPath表达式的接口。遗憾的是,这个标准要比微软直观的方式复杂得多。虽然有好多与XPath相关的对象,最重要的两个是:XPathEvaluatorXPathResultXPathEvaluator利用方法evaluate()计算XPath表达式。

evaluate()方法有五个参数:XPath表达式、上下文节点、命名空间解释程序和返回的结果的类型,同时在XPathResult中存放结果(通常为null)。命名空间解释程序,只有在XML代码用到了XML命名空间时才是必要的,所以通常留空,置为null。返回结果的类型,可以是以下十个常量值之一

参数解释
XPathResult.ANY_TYPE返回符合XPath表达式类型的数据
XPathResult.ANY_UNORDERED_NODE_TYPE返回匹配节点的节点集合,但顺序可能与文档中的节点的顺序不匹配
XPathResult.BOOLEAN_TYPE返回布尔值
XPathResult.FIRST_ORDERED_NODE_TYPE返回只包含一个节点的节点集合,且这个节点是在文档中第一个匹配的节点
XPathResult.NUMBER_TYPE返回数字值
XPathResult.ORDERED_NODE_ITERATOR_TYPE返回匹配节点的节点集合,顺序为节点在文档中出现的顺序。这是最常用到的结果类型
XPathResult.ORDERED_NODE_SNAPSHOT_TYPE返回节点集合快照,在文档外捕获节点,这样将来对文档的任何修改都不会影响这个节点列表。节点集合中的节点与它们出现在文档中的顺序一样
XPathResult.STRING_TYPE返回字符串值
XPathResult.UNORDERED_NODE_ITERATOR_TYPE返回匹配节点的节点集合,不过顺序可能不会按照节点在文档中出现的顺序排列
XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE返回节点集合快照,在文档外捕获节点,这样将来对文档的任何修改都不会影响这个节点列表。节点集合中的节点和文档中原来的顺序不一定一样。

JavaScript实现XPath选择节点


function xpathQuery(xpath){
  let resultXpath = document.evaluate(xpath, document, null, XPathResult.ORDERED_NODE_ITERATOR_TYPE, null);
  let result=[];
  if(resultXpath){
     let item;
     while(item=resultXpath.iterateNext())
     {
        result.push(item);
     }
  } 
  return result;
}

xpathQuery("//div[@class='container-body']/div[contains(@class,'order-item')]");
JavaScript 使用XPath



转载请注明:清风博客 » Chrome浏览器中的XPath

]]>
https://www.skyfinder.cc/2022/12/04/chrome%e6%b5%8f%e8%a7%88%e5%99%a8%e4%b8%ad%e7%9a%84xpath/feed/ 0
Javascript判断当前页面是否处于激活状态 https://www.skyfinder.cc/2022/11/21/javascript%e5%88%a4%e6%96%ad%e5%bd%93%e5%89%8d%e9%a1%b5%e9%9d%a2%e6%98%af%e5%90%a6%e5%a4%84%e4%ba%8e%e6%bf%80%e6%b4%bb%e7%8a%b6%e6%80%81/ https://www.skyfinder.cc/2022/11/21/javascript%e5%88%a4%e6%96%ad%e5%bd%93%e5%89%8d%e9%a1%b5%e9%9d%a2%e6%98%af%e5%90%a6%e5%a4%84%e4%ba%8e%e6%bf%80%e6%b4%bb%e7%8a%b6%e6%80%81/#respond Mon, 21 Nov 2022 05:44:00 +0000 https://www.skyfinder.cc/?p=4020 背景

现有一个考试项目,当在浏览器进行考试时需要判断用户是否存在切屏,如果切屏就对当前考试进行自动强制交卷。浏览器中可通过window对象的onbluronfocus判断,或者documenthidden属性判断。

获取焦点(onfocus)和失去焦点(onblur)

关于是否失焦点,浏览器对象有onfocus onblur事件可以监听。但是触发这两个事件的前提是页面之前是获取焦点的,就是说要是激活的。也就是说页面刚刚渲染完,用户在没有页面上任何操作时,页面是不会正常监听这两个事件的;或者页面在打开状态下,但是触发了onblur之后并无页面操作的情况下也不会正常监听这两个事件。直到,用户操作页面触发focus,之后离开页面才会触发blur,再次点击到当前页面时才会触发focus,如此反复都会触发相应的事件。

onblur

  • chrome浏览器下,点击console面板也会触发blur事件,前提是之前是focus的状态。
  • 页面最小化;
  • 浏览器切换tab页面;
  • 页面中的任何弹窗;
  • focus状态下切换到其他应用

onfocus

  • 用户存在页面操作(包括页面中js脚本运行。如页面加载完无js运行,用户无操作,则不会触发 );
  • 事件触发前提下,页面最大化;
  • 事件触发前提下,页面从其他tab页切换回当前页面;

onblur与onfocus示例

  
//离开          
window.onblur = function () {
         console&&console.log("切屏了onblur");
 };
 //回来
 window.onfocus = function () {
        console&&console.log("切屏了onfocus");
  };

HTML5新增API

HTML5新增的API, visibilitychange, document.hidden, document.visibilityState来实现相关内容。

visibilitychange

  • 浏览器支持 visibilitychangeHTML5API所以支持持最新浏览器 Chrome, Firefox, IE10+
  • addEventListener添加事件,当页面发生改变就会触发

老版本浏览器如果失效需要添加前缀

  • mozvisibilitychange 火狐
  • msvisibilitychange IE
  • webkitvisibilitychange 谷歌,Safari

document.hidden

hiddendocument的属性,可以判断页面是否显示的是当前的页面。visibilitychange事件就是触发页面可见的事件。当然不同的浏览器内核记得要加前缀。表示页面处于非激活状态,反之处于激活状态。

  • 页面最小化
  • 页面在后台运行
  • 切换tab栏到其他页面

当其hiddenfalse的时候就是当前网页正在被用户浏览,其值为ture就是当前网页进入后台。

document.visibilityState

  • visible 是浏览器当前激活,窗口不是最小化状态
  • hidden 不是当前激活,或者窗口最小化了
  • prerender 重新生成,对用户不可见

visibilitychange事件示例

        
var hiddenProperty = 'hidden' in document ? 'hidden' :
            'webkitHidden' in document ? 'webkitHidden' :
                'mozHidden' in document ? 'mozHidden':null;
        var visibilityChangeEvent = hiddenProperty.replace(/hidden/i, 'visibilitychange');
        var onVisibilityChange = function () {
            if (!document[hiddenProperty]) {
                console && console.log('页面激活');
            } else {
                console && console.log('页面非激活');
            }
        }
        document.addEventListener(visibilityChangeEvent, onVisibilityChange);

以上两种方式在某些情况下存在一定差异,为了实现一些需求,可能需要组合使用。



转载请注明:清风博客 » Javascript判断当前页面是否处于激活状态

]]>
https://www.skyfinder.cc/2022/11/21/javascript%e5%88%a4%e6%96%ad%e5%bd%93%e5%89%8d%e9%a1%b5%e9%9d%a2%e6%98%af%e5%90%a6%e5%a4%84%e4%ba%8e%e6%bf%80%e6%b4%bb%e7%8a%b6%e6%80%81/feed/ 0
在非HTTPS站点中使用Content Security Policy引发的问题 https://www.skyfinder.cc/2022/10/25/csp/ https://www.skyfinder.cc/2022/10/25/csp/#respond Tue, 25 Oct 2022 08:40:42 +0000 https://www.skyfinder.cc/?p=4011 背景

有一个客户需要对以前老项目部分功能进行升级,需要升级页面按照最新版本的内容进行更新,测试发现页面无法加载。F12使用开发者工具发现所有资源文件异常,所有的资源文件竟然自动将HTTP协议换成 HTTPS 协议。异常信息如下图所示:

资源加载HTTPS错误

项目目前部署是非HTTPS的,很奇怪为什么会自动转换为HTTPS资源。经过排查在异常页面中发现了问题,在head节点下发现以下代码


<meta http-equiv="Content-Security-Policy" content="upgrade-insecure-requests">

以上内容会将HTTP协议自动转换为HTTPS协议。

Content-Security-Policy

Content-Security-PolicyCSP)允许站点管理者控制用户代理能够为指定的页面加载哪些资源。除了少数例外情况,设置的政策主要涉及指定服务器的源和脚本结束点。这将帮助防止跨站脚本攻击(Cross-Site Script)。

CSP 的实质就是白名单制度,大大增强了网页的安全性。攻击者即使发现了漏洞,也没法注入脚本,除非还控制了一台列入了白名单的可信主机。

两种方法可以启用 CSP。一种是通过 HTTP 头信息的Content-Security-Policy的字段。

另一种是通过网页的<meta>标签。

  • 脚本:只信任当前域名
  • <object>标签:不信任任何URL,即不加载任何资源
  • 样式表:只信任cdn.example.orgthird-party.org
  • 框架(frame):必须使用HTTPS协议加载
  • 其他资源:没有限制

限制选项

资源加载限制

以下选项限制各类资源的加载。

  • script-src:外部脚本
  • style-src:样式表
  • img-src:图像
  • media-src:媒体文件(音频和视频)
  • font-src:字体文件
  • object-src:插件(比如 Flash
  • child-src:框架
  • frame-ancestors:嵌入的外部资源(比如<frame><iframe><embed><applet>
  • connect-srcHTTP 连接(通过 XHRWebSocketsEventSource等)
  • worker-srcworker脚本
  • manifest-src:manifest 文件

default-src

default-src用来设置上面各个选项的默认值。


Content-Security-Policy: default-src 'self

上面代码限制所有的外部资源,都只能从当前域名加载。

如果同时设置某个单项限制(比如font-src)和default-src,前者会覆盖后者,即字体文件会采用font-src的值,其他资源依然采用default-src的值。

URL 限制

有时,网页会跟其他 URL 发生联系,这时也可以加以限制。

  • frame-ancestors:限制嵌入框架的网页
  • base-uri:限制<base#href>
  • form-action:限制<form#action>

其他限制

其他一些安全相关的功能,也放在了 CSP 里面。

  • block-all-mixed-contentHTTPS 网页不得加载 HTTP 资源(浏览器已经默认开启)
  • upgrade-insecure-requests:自动将网页上所有加载外部资源的 HTTP 链接换成 HTTPS 协议
  • plugin-types:限制可以使用的插件格式
  • sandbox:浏览器行为的限制,比如不能有弹出窗口等。

参考

https://www.ruanyifeng.com/blog/2016/09/csp.html



转载请注明:清风博客 » 在非HTTPS站点中使用Content Security Policy引发的问题

]]>
https://www.skyfinder.cc/2022/10/25/csp/feed/ 0
使用ABP框架UI找不到文件的异常 https://www.skyfinder.cc/2022/10/05/abp-install-libs/ https://www.skyfinder.cc/2022/10/05/abp-install-libs/#respond Wed, 05 Oct 2022 14:03:03 +0000 https://www.skyfinder.cc/?p=4000 背景

自己的一个小应用使用了ABP框架最新内容创建了一个项目模板,为了快速的完成第一版就是用来ABP自带的UI框架。在测试项目模板正常与否的时候发现关于UI方面的异常信息,第一次使用ABP官方提供的UI框架,所以先在此做一下记录。

以下是异常信息:

AbpException: Could not find the bundle file ‘/libs/abp/core/abp.css’ for the bundle ‘LeptonXLite.Global’!

ABP框架UI异常图片

解决方法

安装ABP框架UI依赖包abp install-libs



转载请注明:清风博客 » 使用ABP框架UI找不到文件的异常

]]>
https://www.skyfinder.cc/2022/10/05/abp-install-libs/feed/ 0
.Net Core获取Window系统机器码 https://www.skyfinder.cc/2022/10/04/dot-net-core-machinecode/ https://www.skyfinder.cc/2022/10/04/dot-net-core-machinecode/#respond Tue, 04 Oct 2022 06:27:45 +0000 https://www.skyfinder.cc/?p=3989 背景

因为朋友想做一个软件认证相关的东西,所以考虑到关于电脑唯一标识机器码的问题。关于机器码之前并没有真正实现过,也只是了解大概的原理。今天就做一下简单记录,以便以后使用。

机器码

机器码指的是软件根据计算机的硬件信息,例如:CPU、内存、主板序列号等,按照一定的算法生成的一串无规律的字符串,并且在不同计算机上生成的机器码是不一样的,因为每台计算机的硬件信息不一样。机器码,也叫序列号认证码注册申请码等。

代码实现


using System.Management;
using System.Runtime.InteropServices;

namespace HonourWorld.Core.Common
{
    public sealed class MachineCode
    {
        private readonly static MachineCode machineCode=new MachineCode();
        public static string GetMachineCodeString()
        {
            string machineCodeString = $"PC.{machineCode.GetCpuInfo()}.{machineCode.GetHDid()}.{machineCode.GetDiskSerialNumber()}.{machineCode.GetMacAddress()}.{machineCode.GetBiosSerialNumber()}";
            return machineCodeString;
        }
        private MachineCode()
        { 
        }
        ///   <summary>
        ///   获取cpu序列号
        ///   </summary>
        ///   <returns> string </returns>
        public string GetCpuInfo()
        {
            string? cpuInfo = "";
            try
            {
                if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
                {
                    using (ManagementClass cimobject = new ManagementClass("Win32_Processor"))
                    {
                        ManagementObjectCollection moc = cimobject.GetInstances();
                        foreach (ManagementObject mo in moc)
                        {
                            if (mo == null || mo.Properties == null)
                            {
                                continue;
                            }
                            var info = mo.Properties["ProcessorId"];
                            if (info!=null)
                            {
                                cpuInfo = info.Value.ToString();
                            }
                            mo?.Dispose();
                        }
                    }
                }
            }
            catch (Exception)
            {
                throw;
            }
            return (cpuInfo??"").Trim();
        }
        ///   <summary>
        ///   获取硬盘ID
        ///   </summary>
        ///   <returns> string </returns>
        public string GetHDid()
        {
            string HDid = "";
            try
            {
                if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
                {
                    using (ManagementClass cimobject1 = new ManagementClass("Win32_DiskDrive"))
                    {
                        ManagementObjectCollection moc1 = cimobject1.GetInstances();
                        foreach (ManagementObject mo in moc1)
                        {
                            HDid = (string)mo.Properties["Model"].Value;
                            mo.Dispose();
                        }
                    }
                }
            }
            catch (Exception)
            {
                throw;
            }
            return HDid.Trim();
        }

        /// <summary>
        /// 获得硬盘序列号
        /// </summary>
        /// <returns></returns>
        public string GetDiskSerialNumber()
        {
            string serialNumber = "";
            try
            {
                if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
                {
                    using (ManagementClass bios = new ManagementClass("Win32_PhysicalMedia"))
                    {
                        ManagementObjectCollection moc1 = bios.GetInstances();
                        foreach (ManagementObject mo in moc1)
                        {
                            serialNumber = (string)mo.Properties["SerialNumber"].Value;
                            mo.Dispose();
                        }
                    }
                }
            }
            catch (Exception)
            {
                throw;
            }
            return serialNumber.Trim();
        }

        ///   <summary>
        ///   获取网卡硬件地址
        ///   </summary>
        ///   <returns> string </returns>
        public string GetMacAddress()
        {
            string mocAddress = "";
            try
            {
                if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
                {
                    using (ManagementClass mc = new ManagementClass("Win32_NetworkAdapterConfiguration"))
                    {
                        ManagementObjectCollection moc2 = mc.GetInstances();
                        foreach (ManagementObject mo in moc2)
                        {
                            if ((bool)mo["IPEnabled"]&& mo["MacAddress"]!=null)
                            {
                                mocAddress = (string)mo["MacAddress"];
                            }
                            mo.Dispose();
                        }
                    }
                }
            }
            catch (Exception)
            {
                throw;
            }
            return mocAddress.Trim();
        }

        /// <summary>
        /// 获得主板序列号
        /// </summary>
        /// <returns></returns>
        public string GetBiosSerialNumber()
        {

            string serialNumber = "";
            try
            {
                if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
                {
                    using (ManagementClass bios = new ManagementClass("Win32_BaseBoard"))
                    {
                        ManagementObjectCollection moc1 = bios.GetInstances();
                        foreach (ManagementObject mo in moc1)
                        {
                            serialNumber = (string)mo.Properties["SerialNumber"].Value;
                            mo.Dispose();
                        }
                    }
                }
            }
            catch (Exception)
            {
                throw;
            }
            return serialNumber.Trim();
        }

    }
}

调用方式


        private void btnIntroduction_Click(object sender, EventArgs e)
        {
            string machineCode = Honour.MachineCode.GetMachineCodeString();
            machineCode = EncryptionSha.GetSha1HashString(machineCode);
            machineCode = Regex.Replace(machineCode, @"\w{8}", "$0-", RegexOptions.IgnoreCase).TrimEnd('-');
            var result = MessageBox.Show($"机器码:{machineCode}", "提示", MessageBoxButtons.OK);
        }

机器码是使用CPU序列号硬盘ID硬盘序列号MAC地址主板序列号进行拼接的字符串。由于电脑硬件信息拼接而成的无规则字符串,所以显得杂乱。为了让其看上去比较规范就对拼接的取散列信息,散列算法有很多种,例如:MD5SHA1等。这里使用SHA1来实现需求。

机器码

示例下载

.Net Core机器码示例
以上获取机器相关信息的方式方法只支持Windows系统。



转载请注明:清风博客 » .Net Core获取Window系统机器码

]]>
https://www.skyfinder.cc/2022/10/04/dot-net-core-machinecode/feed/ 0
判断登录的QQ是否已经加入指定的QQ群之二 https://www.skyfinder.cc/2022/09/25/check-qq-in-group/ https://www.skyfinder.cc/2022/09/25/check-qq-in-group/#comments Sun, 25 Sep 2022 10:32:10 +0000 https://www.skyfinder.cc/?p=3966 背景

任何事情都有一个原由,本篇内容也不例外。如标题所示,关于判断登录的QQ是否已经加入指定的QQ群的问题。为什么是之二?因为之前已经写过一次相关内容了。既然曾经已经写过一次为什么还要写第二次?因为今天早上收到了一个邮件通知,这个是博客评论通知,有人评论就以邮件形式告知。评论内容是这样的:c#那个QQ群验证已经无法使用了能更新下吗。如下图所示:

关于C#QQ群验证的评论回复图

没有错,就如我回复的一样,示例程序的代码的确不可以用了,不过思路依然可以使用。

分析

其实没有什么好分析的,思路与上次一样。只是上次验证的地址(http://qun.qzone.qq.com)无效了,可能是下线了吧!不过QQ也有关于群里管理的网站(https://qun.qq.com/),这个网站依然可以实现这样的操作。实现比较简单,访问网址https://qun.qq.com/member.html并登录,然后通过正则表达式匹配就行了!

QQ群管理

此次还是与上次有些变化的,网页登录竟然取消了账号与密码登录,只能手机扫码或者选择已经登录的账号。也许是为了安全考虑吧,不过这也不重要了!

实现

这里使用Microsoft .netWindows Form实现示例,使用WebBrowser(其实也可以使用CefSharpWebView2等控件)控件加载网址https://qun.qq.com/member.html登录成功后,有使用正则匹配指定的群号来完成操作。

代码大致如下:


using System;
using System.Text.RegularExpressions;
using System.Windows.Forms;

namespace QQGroupVerification
{
    public partial class QQVerificationLogin : Form
    {
        public QQVerificationLogin()
        {
            InitializeComponent();
        }
        private void Form1_Load(object sender, EventArgs e)
        {
            webBrowser1.Navigate("https://xui.ptlogin2.qq.com/cgi-bin/xlogin?pt_disable_pwd=1&appid=715030901&daid=73&hide_close_icon=1&pt_no_auth=1&s_url=https%3A%2F%2Fqun.qq.com%2Fmember.html%23");
            webBrowser1.ScriptErrorsSuppressed=true;
            webBrowser1.DocumentCompleted += WebBrowser1_DocumentCompleted;
        }

        private void WebBrowser1_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e)
        {
            if (webBrowser1.ReadyState == WebBrowserReadyState.Complete&&webBrowser1.Url.ToString().StartsWith("https://qun.qq.com"))
            {
                webBrowser1.Hide();
                string webPageString = webBrowser1.Document.Body.InnerHtml;
                webPageString = Regex.Replace(webPageString, @"<script\s?type=""text/template""\s+?id=.+?>[\s\S]+?</script>", "", RegexOptions.IgnoreCase);
                var match = Regex.Match(webPageString, @"<div\sclass=.?body-content.?>[\s\S]+?<div\sclass=.?member-body.?>[\s\S]+(?=<div\s+?class=""foot\sbtn-footer"")", RegexOptions.IgnoreCase);
                if (match == null|| !match.Success)
                {
                    return;
                }
                string groupid =this.textBox1.Text;// "这里是你的QQ群ID";
                if (Regex.IsMatch(match.Value, $"data-id=\"{groupid}\""))
                {
                    MessageBox.Show("验证成功");
                    CloseChrome();
                    ProgramMain form2 = new ProgramMain();
                    form2.Show();

                }
                else
                {
                    MessageBox.Show("请加指定交流群入本群");
                    CloseChrome();
                    Application.Exit();
                }
            }
            bool isBusy = webBrowser1.IsBusy;
        }
      
        public void CloseChrome()
        {
            if (webBrowser1 != null)
            {
                webBrowser1.Dispose();
            }
        }

    }
}

此次代码与上次相比主要是多了两个正则,为什么要多这两个正则?新的QQ群管理网站用了前后端分离用了一些模板,这些模板可能会干扰接下来的操作。所以,第一个正则表达式替换掉所有JavaScript模板相关代码,替换为空字符串。第二个正则用来获取QQ群里列表区域所有文本内容,接下来才是判断返回信息中是否包含指定的QQ群。

预览

判断登录的QQ是否已经加入指定的QQ群之二-第2张图片
判断登录的QQ是否已经加入指定的QQ群之二-第3张图片

另一种方式

这一种方式是使用Windows Api查找窗体句柄,要打开指定的群,找到了窗体就认为加入了群。不过这个方法是不精准的,这个通过查找窗体的标题。知道要验证QQ群的名称,随便建一个临时群修改名称为要验证QQ群名称就可以过掉验证。

实现


using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;

namespace QQGroupVerificationByWindowsApi
{
    public class WindownApiVerification
    {
        [DllImport("user32.dll", EntryPoint = "FindWindow")]
        private static extern IntPtr FindWindow(string IpClassName, string IpWindowName);
        /// <summary>
        /// 找到句柄
        /// </summary>
        /// <param name="IpClassName">类名</param>
        /// <returns></returns>
        public static IntPtr GetHandle(string IpClassName, string IpWindowName)
        {
            return FindWindow(IpClassName, IpWindowName);

        }
        /// <summary>
        /// 判断是否打开了某指定的QQ群
        /// </summary>
        /// <param name="qqGroupName">参数是群名</param>
        /// <returns></returns>
        public static bool IsInQQGroup(string qqGroupName) 
        {
            IntPtr result = GetHandle("TXGuiFoundation", qqGroupName);
            if (result == IntPtr.Zero)
            {
                return false;
            }
            return true;
        }
    }
}

使用方式如下:


using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace QQGroupVerificationByWindowsApi
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            string qqGroup = textBox1.Text;
            if(string.IsNullOrWhiteSpace(qqGroup))
            {

                MessageBox.Show("请输入需要验证的QQ群的名称","提示",MessageBoxButtons.OK,MessageBoxIcon.Information);
                return;
            }
           var result=WindownApiVerification.IsInQQGroup(qqGroup);
            if (result)
            {
                MessageBox.Show("存在", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
                return;
            }
            MessageBox.Show("不存在", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
        }
    }
}

预览

判断登录的QQ是否已经加入指定的QQ群之二-第4张图片

示例下载

判断登录的QQ是否已经加入指定的QQ群

总结

以上就是关于判断QQ是否加入指定QQ群的简单判断,使用哪种方式就看个人想法了!其实还有其他的一些方法,例如:内存、网络等。不过就不再进行深入了解了,实现其他的方式现在也没有意义。无论哪种方式都可以被绕过,有时候不必太过执着。

警告:本章内容仅供学习交流,请勿使用非法用途。有任何非法行为均需自行承担!!!!



转载请注明:清风博客 » 判断登录的QQ是否已经加入指定的QQ群之二

]]>
https://www.skyfinder.cc/2022/09/25/check-qq-in-group/feed/ 6
.NET CORE实现SHA1 https://www.skyfinder.cc/2022/09/24/net-core-sha1/ https://www.skyfinder.cc/2022/09/24/net-core-sha1/#comments Sat, 24 Sep 2022 14:34:21 +0000 https://www.skyfinder.cc/?p=3956 背景

因业务需求,需要保存一些指定的字符串,但这些字符串可能相同。为了避免重复存储造成数据大量冗余,所以需要对这些字符串进行一些散列计算。使这一些相同的字符串产生唯一的标识,实现业务上的需求。关于字符串散列算法还是比较多的,例如:MD2MD4MD5Sha1Sha256Sha512等等,在结合字符串散列碰撞以及散列最终长度考虑,最后选中Sha1作为此次散列算法。

SHA-1

(英语:Secure Hash Algorithm 1,中文名:安全散列算法1)是一种密码散列函数,美国国家安全局设计,并由美国国家标准技术研究所(NIST)发布为联邦数据处理标准(FIPS)。SHA-1可以生成一个被称为消息摘要的160位(20字节)散列值,散列值通常的呈现形式为40个十六进制数。

.NET CORE 实现 SHA1

以下是使用.Net Core自带类库实现Sha1的简单方法。

    
public static class EncryptionSha
{
        public static byte[] GetSha1Hash(string inputString)
        {
            var sha1 = SHA1.Create();
            byte[] data = sha1.ComputeHash(Encoding.UTF8.GetBytes(inputString));
            sha1.Dispose();
            return data;
        }

        public static string GetSha1HashString(string inputString)
        {
            StringBuilder sb = new StringBuilder();
            byte[] hashByte = GetSha1Hash(inputString);
            foreach (byte b in hashByte)
            {
                sb.Append(b.ToString("X2"));
            }
            return sb.ToString();
        }
    }

使用方式


using System;

namespace TestSha1
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine(EncryptionSha.GetSha1HashString("Hello World!"));
            Console.ReadKey();
        }
    }
}
.net core使用Sha1散列调用方式

示例

SHA1散列



转载请注明:清风博客 » .NET CORE实现SHA1

]]>
https://www.skyfinder.cc/2022/09/24/net-core-sha1/feed/ 2
Visual Studio Code(VS Code)意外放大后恢复设置 https://www.skyfinder.cc/2022/09/20/visual-studio-code-zoom-level/ https://www.skyfinder.cc/2022/09/20/visual-studio-code-zoom-level/#respond Tue, 20 Sep 2022 01:00:00 +0000 https://www.skyfinder.cc/?p=3941 背景

使用VS Code的时候,自己没有注意到底操作了什么导致VS Code的字体整个放大了好多。原本以为关闭后重新打开就会恢复,结果还是没有任何效果。效果如下:

Visual Studio Code(VS Code)意外放大后恢复设置-第0张图片

经过了解,应该是不小心触碰了缩放的快捷键。

解决办法

恢复的方法也比较简单,以下是第一种方式。

  • 查看
  • 外观
  • 放大、缩小、重置缩放
Zoom Level恢复第一种方式

嗯!没有错!应该是意外碰到Ctrl+=的快捷键了。

第二种方式,如下图所示:

Zoom Level恢复第二种方式第一张图片-设置
Zoom Level恢复第二种方式第二张图片
Zoom Level恢复第二种方式第三张图片-恢复

Window:Zoom Level

调整窗口的缩放级别。原始大小是 0,每次递增(例如 1)或递减(例如 -1)表示放大或缩小 20%。也可以输入小数以便以更精细的粒度调整缩放级别。

本来是不打算记录的,想了一下最终决定记录一下。俗话说得好:”好记性,不如烂笔头”。



转载请注明:清风博客 » Visual Studio Code(VS Code)意外放大后恢复设置

]]>
https://www.skyfinder.cc/2022/09/20/visual-studio-code-zoom-level/feed/ 0
使用Nginx配置资源目录达到下载目的 https://www.skyfinder.cc/2022/09/05/nginx-downlaod/ https://www.skyfinder.cc/2022/09/05/nginx-downlaod/#respond Mon, 05 Sep 2022 02:24:24 +0000 https://www.skyfinder.cc/?p=3931 使用Nginx配置资源目录达到下载目的-第0张图片

背景

实施人员临时需要在客户服务器上配置一个可下载的目录,为了可以在内网进行交换文件。

Nginx配置

       
location /resources {
          #下载的资源目录 绝对路径 最后 "/" 结尾
          alias /app/smart/logs/;
          autoindex on;
          autoindex_format html; #以html风格将目录展示在浏览器中
          autoindex_exact_size off; #切换为 off 后,以可读的方式显示文件大小,单位为 KB、MB 或者 GB
          autoindex_localtime on; #以服务器的文件时间作为显示的时间
          client_max_body_size 4048M;
          proxy_max_temp_file_size 4048M;
          proxy_send_timeout 600; #后端服务器数据回传时间(代理发送超时)
          proxy_read_timeout 600; #连接成功后,后端服务器响应时间(代理接收超时)
          
          #符合条件,直接下载
          if ($request_filename ~* ^.*?\.(txt|doc|pdf|rar|gz|zip|docx|exe|xlsx|ppt|pptx)$){
             add_header Content-Disposition attachment;
          }
        }

访问地址规则为:http://url/resources,这样就可以看到我们需要页面。示例如下:

https://www.skyfinder.cc/resources



转载请注明:清风博客 » 使用Nginx配置资源目录达到下载目的

]]>
https://www.skyfinder.cc/2022/09/05/nginx-downlaod/feed/ 0
WordPress自动对没有alt属性的img添加alt属性 https://www.skyfinder.cc/2022/08/07/wordpress-auot-add-img-alt/ https://www.skyfinder.cc/2022/08/07/wordpress-auot-add-img-alt/#respond Sun, 07 Aug 2022 01:00:00 +0000 https://www.skyfinder.cc/?p=3918 背景

使用了微软必应搜索的站长工具,看到必应搜索工具中SEO报告提示错误蛮多。主要有两个错误提示,其中一个是关于页面描述内容长短问题,另外一个就是img标签没有alt属性的问题。页面固定img标签的alt属性比较容易改,而文章内容的img标签的alt属性就没有那么方便了。

SEO错误提示

博客使用的是wordpress,其实处理起来也没有那么麻烦。一种方法就是使用现成的插件,例如:SEO Friendly Images,另一种就是在使用模板目录下,找到functions.php函数文件添加自定义函数处理。

这里提供自定义函数,内容如下:


//Wordpress判断并自动添加图片ALT属性
function image_alt($imgalt) {
	global $post;
	$title = $post->post_title;
	$imgUrl = "/<img\s*?.+?[^>]>/si";
	$isMatch=preg_match_all($imgUrl,$imgalt,$matches,PREG_SET_ORDER);
	if($isMatch) {
		if(!empty($matches) ) {
			for ($i=0; $i < count($matches); $i++) {
				$tag = $url = $matches[$i][0];
				$tag=preg_replace('/alt="\s*"/','',$tag);
				$judge = '/alt=/';
				$isMatched=preg_match($judge,$tag,$match,PREG_OFFSET_CAPTURE);
				if($isMatched) {
				   continue;
				}
				$tag=preg_replace('/<img/','<img alt="'.$title.'-第'.$i.'张图片"',$tag);
				$imgalt =str_replace($url,$tag,$imgalt);
			}
		}
	}
	return $imgalt;
}
add_filter('the_content', 'image_alt');



转载请注明:清风博客 » WordPress自动对没有alt属性的img添加alt属性

]]>
https://www.skyfinder.cc/2022/08/07/wordpress-auot-add-img-alt/feed/ 0
MYSQL修改数据库、表、字段字符集 https://www.skyfinder.cc/2022/08/05/mysql-update-character/ https://www.skyfinder.cc/2022/08/05/mysql-update-character/#respond Fri, 05 Aug 2022 01:00:00 +0000 https://www.skyfinder.cc/?p=3906 背景

由于mysql低版本暴了漏洞,所以客户就升级了mysql版本。升级到了最新版8.0.30,升级最新版后,应用就再也查不出数据,恢复到最初的版本有可以查到数据。经过确认最初数据库的表字符集为utf8,升级后这些字符集自动修改为utf8mb3。新版本数据库移除了utf8字符集,而应用本身不支持utf8mb3,所以需要修改这些字符集。经过再次确认最新版mysql以及应用都支持utf8mb4,最终决定修改字符集为utf8mb4

修改字符集

数据库


ALTER DATABASE 数据库名称 DEFAULT CHARACTER SET 编码名称 [COLLATE ...];

例如:


ALTER DATABASE activity CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci;

ALTER TABLE 表名称  CONVERT TO CHARACTER SET 编码名称 [COLLATE ...]

例如:

ALTER TABLE users CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;

以上修改会同时变更现有数据的字符集。如果只修改表的默认字符集,而不修改现有数据的字符集可以使用以下内容


ALTER TABLE 表名称 DEFAULT CHARACTER SET 编码名称 [COLLATE...];

例如:

ALTER TABLE users DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;

字段

ALTER TABLE 表名称 CHANGE 字段名称 字段名称 CHARACTER SET 编码名称 [COLLATE ...];

例如:

ALTER TABLE users CHANGE LoginName LoginName VARCHAR(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;

批量修改数据库表字符集

第一步

使用以下脚本并执行,产生修改数据表字符集的脚本。

SELECT 
CONCAT("ALTER TABLE ",TABLE_SCHEMA,'.',TABLE_NAME," CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;") 
AS target_tables
FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_SCHEMA IN('数据库名称','数据库名称') 
AND TABLE_TYPE="BASE TABLE";

例如:

SELECT 
CONCAT("ALTER TABLE ",TABLE_SCHEMA,'.',TABLE_NAME," CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;") 
AS target_tables
FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_SCHEMA IN('phpdbeqx') 
AND TABLE_TYPE="BASE TABLE";
MYSQL修改数据库、表、字段字符集-第0张图片

导出以上查询结果,执行导出的查询结果即可。如果数据量比较大,执行的时间可能会比较久。

查看字符集

查看数据库支持的字符集

select * from information_schema.character_sets;

查看数据库支持的检验规则

select * from information_schema.collations;

查看表字符集及检验规则

select * from information_schema.tables where table_name='表名' 

查看字段编码

SHOW FULL COLUMNS FROM  表名



转载请注明:清风博客 » MYSQL修改数据库、表、字段字符集

]]>
https://www.skyfinder.cc/2022/08/05/mysql-update-character/feed/ 0
.NET CORE引用Aspose.Words的异常Could not load file or assembly https://www.skyfinder.cc/2022/08/01/net-core-could-not-load-file-or-assembly/ https://www.skyfinder.cc/2022/08/01/net-core-could-not-load-file-or-assembly/#respond Mon, 01 Aug 2022 07:22:50 +0000 https://www.skyfinder.cc/?p=3897 背景

引用了一个第三方的dll库,编译无任何异常提示,但是运行就报异常。自从有了Nuget基本没有再使用直接引用dll这种方式了。经过确认dll也设置了输出到目录,在 bin目录也的确存在此dll库。

异常信息

Could not load file or assembly ‘Aspose.Words, Version=20.6.0.0, Culture=neutral, PublicKeyToken=716fcc553a201e56’. 系统找不到指定的文件。
System.IO.FileNotFoundException: Could not load file or assembly ‘Aspose.Words, Version=20.6.0.0, Culture=neutral, PublicKeyToken=716fcc553a201e56’. 系统找不到指定的文件。
File name: ‘Aspose.Words, Version=20.6.0.0, Culture=neutral, PublicKeyToken=716fcc553a201e56’

.NET CORE引用Aspose.Words的异常Could not load file or assembly-第0张图片

解决

查看下引用dll库的项目文件,项目文件中少了dll的相关版本号。


<Reference Include="Aspose.Words">

在这条记录中添加对应的版本号后,问题得到解决!如下:


<Reference Include="Aspose.Words" Version="20.6.0.0">



转载请注明:清风博客 » .NET CORE引用Aspose.Words的异常Could not load file or assembly

]]>
https://www.skyfinder.cc/2022/08/01/net-core-could-not-load-file-or-assembly/feed/ 0
解决MYSQL连接异常:SQLSTATE[HY000] [2054] https://www.skyfinder.cc/2022/07/29/mysql-sqlstate-hy000-2054/ https://www.skyfinder.cc/2022/07/29/mysql-sqlstate-hy000-2054/#respond Fri, 29 Jul 2022 03:11:41 +0000 https://www.skyfinder.cc/?p=3881 异常信息

SQLSTATE[HY000] [2054] The server requested authentication method unknown to the client[/app/thinkphp/library/think/db/Connection.php:295]

解决MYSQL连接异常:SQLSTATE[HY000] [2054]-第0张图片

原因

MySQL8中用户的认证类型(Authentication type)默认为cacheing sha2 password导致的错误,需要修改用户权限认证方式为mysql_native_password

我这个错误就是因为数据库升级导致的。

解决办法

  • 数据库降级,退回以前版本
  • 改为mysql_native_password认证方式

数据库退回以前的版本,这里不进行操作了。下面只针对认证方式改回mysql_native_password的方式。

修改mysql配置文件my.cnf

vi /etc/my.cnf

[mysqld]节点下如下内容,如果以及存在default_authentication_plugin节点,覆盖即可。

default_authentication_plugin=mysql_native_password
解决MYSQL连接异常:SQLSTATE[HY000] [2054]-第1张图片

由于更改了认证方式,所以要更改链接MYSQL用户的密码。以下以root为例

mysql -u root -p

执行以下代码完成链接MYSQL的用户密码的修改。


alter user '用户'@'%' identified with mysql_native_password by '密码';

flush privileges;

root用户为例


alter user 'root'@'%' identified with mysql_native_password by '123456';
flush privileges;

操作完毕后,重启MYSQL服务。



转载请注明:清风博客 » 解决MYSQL连接异常:SQLSTATE[HY000] [2054]

]]>
https://www.skyfinder.cc/2022/07/29/mysql-sqlstate-hy000-2054/feed/ 0
无题 https://www.skyfinder.cc/2022/05/28/%e6%97%a0%e9%a2%98/ https://www.skyfinder.cc/2022/05/28/%e6%97%a0%e9%a2%98/#respond Sat, 28 May 2022 11:39:45 +0000 https://www.skyfinder.cc/?p=3871 明月残缺空中挂,

白光倾洒覆天涯。

遥看皆是行人影,

心有归处哪有家。



转载请注明:清风博客 » 无题

]]>
https://www.skyfinder.cc/2022/05/28/%e6%97%a0%e9%a2%98/feed/ 0
.net core在Linux系统报Gdip异常的问题 https://www.skyfinder.cc/2022/04/08/net-core-linux-system-gdip-error/ https://www.skyfinder.cc/2022/04/08/net-core-linux-system-gdip-error/#respond Fri, 08 Apr 2022 01:00:00 +0000 https://www.skyfinder.cc/?p=3844 背景

.net core 应用中使用了Excel文件导出,发现Excel导出失败,导出的操作出现了异常。看了一下是Gdip的这个异常。这个异曾经见到过,记得也很快解决了,不晓得为什么又会出现呢?仔细看了一下,详细错误还是有点差别。经过确认上一次安装的libc6-devlibgdiplus是存在的,并没有丢失或者损坏。

根据异常信息,基本可以确定是引用System.Drawing.Common类库引起的问题,经过排查发现引用的Excel组件Magicodes.IE.Excel.Abp包中有使用System.Drawing.Common。前几天还好好的,现在为何System.Drawing.Common不支持非Windows平台呢?只是确定引用的System.Drawing.Common库的版本是6.0,其他的就没有太多线索。

最后又看一遍异常信息,根据异常提示打开所提示的网址看一下:

https://aka.ms/systemdrawingnonwindows

提示中断性变更,仅在Windows上支持 System.Drawing.Common

System.Drawing.Common NuGet 包现在被归为 Windows 特定的库。 在为非 Windows 操作系统编译时,平台分析器会在编译时发出警告。

在非 Windows 操作系统上,除非设置了运行时配置开关,否则将引发 TypeInitializationException 异常,其中 PlatformNotSupportedException 作为内部异常。

旧行为

.NET 6 之前,使用 System.Drawing.Common 包不会产生任何编译时警告,也不会引发任何运行时异常。

新行为

.NET 6 开始,当为非 Windows 操作系统编译引用代码时,平台分析器会发出编译时警告。 此外,除非设置了配置选项,否则将引发以下运行时异常:

.net core在Linux系统报Gdip异常的问题-第0张图片

The type initializer for ‘Gdip’ threw an exception.
System.TypeInitializationException: The type initializer for ‘Gdip’ threw an exception.
—> System.PlatformNotSupportedException: System.Drawing.Common is not supported on non-Windows platforms. See https://aka.ms/systemdrawingnonwindows for more information.
at System.Drawing.LibraryResolver.EnsureRegistered()
at System.Drawing.SafeNativeMethods.Gdip.PlatformInitialize()
at System.Drawing.SafeNativeMethods.Gdip..cctor()
— End of inner exception stack trace —
at System.Drawing.SafeNativeMethods.Gdip.GdipGetGenericFontFamilySansSerif(IntPtr& fontfamily)
at System.Drawing.FontFamily.GetGdipGenericSansSerif()
at System.Drawing.FontFamily.get_GenericSansSerif()
at System.Drawing.Font.CreateFont(String familyName, Single emSize, FontStyle style, GraphicsUnit unit, Byte charSet, Boolean isVertical)
at System.Drawing.Font..ctor(String familyName, Single emSize, FontStyle style, GraphicsUnit unit, Byte gdiCharSet, Boolean gdiVerticalFont)
at OfficeOpenXml.ExcelRangeBase.AutoFitColumns(Double MinimumWidth, Double MaximumWidth)
at OfficeOpenXml.ExcelRangeBase.AutoFitColumns(Double MinimumWidth)
at OfficeOpenXml.ExcelRangeBase.AutoFitColumns()
at OfficeOpenXml.ExcelColumn.AutoFit()
at Magicodes.ExporterAndImporter.Excel.Utility.ExportHelper1.AddStyle() at Magicodes.ExporterAndImporter.Excel.Utility.ExportHelper1.AddHeaderAndStyles()
at Magicodes.ExporterAndImporter.Excel.ExcelExporter.ExportHeaderAsByteArray(String[] items, String sheetName)

引入的版本

此更改会影响源兼容性和二进制兼容性。

更改原因

由于 System.Drawing.Common 被设计为 Windows 技术的精简包装器,因此其跨平台实现欠佳。

libgdiplus 是本机端 System.Drawing.Common 跨平台实现的主要提供程序。 libgdiplus 实际上是对 System.Drawing.Common 所依赖的 Windows 部分的重新实现。 该实现使 libgdiplus 成为一个重要的组件。 它大约有 30,000C 代码,大部分未经测试,而且缺少很多功能。 libgdiplus 还具有许多用于图像处理和文本呈现的外部依赖项,例如 cairopango 和其他本机库。 这些依赖项使得维护和交付组件更具挑战性。 自从包含 Mono 跨平台实现以来,我们已将许多从未得到修复的问题重定向到 libgdiplus。 相比之下,我们采用的其他外部依赖项,例如 icu 或 openssl,都是高质量的库。 使 libgdiplus 的功能集和质量与 .NET 堆栈的其余部分相媲美是不可行的。

通过对 NuGet 包的分析,我们观察到 System.Drawing.Common 主要用于跨平台的图像处理,例如 QR 代码生成器和文本呈现。 由于我们的跨平台图形支持不完整,我们还没有注意到大量的图形使用。 System.Drawing.Common 在非 Windows 环境中的使用通常得到 SkiaSharp ImageSharp 的良好支持。

System.Drawing.Common 将仅在 Windows 窗体和 GDI+ 的上下文中继续演变。

建议的操作

或者,可通过将 runtimeconfig.json 文件中的 System.Drawing.EnableUnixSupportSystem.Drawing.EnableUnixSupport设置为 true 来启用对 .NET 6 中非 Windows 平台的支持:

{
   "configProperties": {
      "System.Drawing.EnableUnixSupport": true
   }
}

添加此配置开关是为了让严重依赖此包的跨平台应用有时间迁移到更新式的库。 但是,不会修复非 Windows bug。 此外,此开关已在 .NET 7 中删除。

说白了就是在非Windows平台下.net 6中默认不支持System.Drawing.Common了,想继续使用要设置运行时配置,并且以后.NET 7中这个配置也要干掉。

问题解决

又核实了一遍,确认是引用了.Net 6.0版本下的System.Drawing.Common,应该是其他人引用了6.0版本导致的。目前应用使用的是.net 5,配置是不可能修改配置的。所以,最简单的办法就是降级,将System.Drawing.Common版本降级到5.0,问题就此解决。

.NET 5或更早版本解决

docker环境下使用EPPlus 导出Excel报Gdip异常

基于dotnet官方的aspnet5的镜像构建安装libgdiplus基础镜像



转载请注明:清风博客 » .net core在Linux系统报Gdip异常的问题

]]>
https://www.skyfinder.cc/2022/04/08/net-core-linux-system-gdip-error/feed/ 0
使用sqlserver的排名函数实现积分排名 https://www.skyfinder.cc/2022/04/06/sql-server-rank-handle/ https://www.skyfinder.cc/2022/04/06/sql-server-rank-handle/#respond Wed, 06 Apr 2022 01:00:00 +0000 https://www.skyfinder.cc/?p=3828 背景

客户需要针对用户的积分进行排名,按照积分的多少降序进行。为了更快更好的满足客户需求,就采取了SQL Server已有的排名函数RANKDENSE_RANK来实现

RANK

返回结果集的分区内每行的排名。 行的排名是相关行之前的排名数加一。

ROW_NUMBER RANK 类似。 ROW_NUMBER 按顺序对所有行进行编号(例如: 1、2、3、4、5)。 RANK 为相应关联提供相同的数值(例如: 1、2、2、4、5)。

RANK是运行查询时计算出的临时值

语法


RANK ( ) OVER ( [ partition_by_clause ] order_by_clause )
如果两个或多个行与一个排名关联,则每个关联行将得到相同的排名

DENSE_RANK

此函数返回结果集分区中每行的排名,排名值没有间断。 特定行的排名等于该特定行之前不同排名值的数量加一。

语法

DENSE_RANK ( ) OVER ( [ <partition_by_clause> ] < order_by_clause > )
如果两个或更多行在同一分区中具有相同的排名值,那么每个行将获得相同的排名

积分排名实现

数据准备

-- 创建积分表
Create table integral
(
  userUid varchar(36),
  integralScore decimal(18,2)
);
-- 创建用户表
Create table UserInfo
(
 userUid varchar(36),
 user_name varchar(100)
);
--查询
with integral AS
(
   select userUid, ISNULL(SUM(integralScore), 0) as integralScore  from integral  group by userUid
)
,
ranks AS(
select  rank() OVER(order by integralScore desc) as ranks,integral.integralScore,UserInfo.user_name from  integral Inner Join UserInfo  on integral.userUid=UserInfo.userUid
  
)
select  * from ranks;
使用sqlserver的排名函数实现积分排名-第0张图片

rank函数实现排名,排名不连续,排名会跳过序号。如上图所示。此种情况可以使用dense_rank函数来解决,这样排名需要就会连续出现。


with integral AS
(
   select userUid, ISNULL(SUM(integralScore), 0) as integralScore  from integral  group by userUid
)
,
ranks AS(
select  dense_rank() OVER(order by integralScore desc) as ranks,integral.integralScore,UserInfo.user_name from  integral Inner Join UserInfo  on integral.userUid=UserInfo.userUid
)
select  * from ranks;
使用sqlserver的排名函数实现积分排名-第1张图片

当使用dense_rank函数排名后,就完美避免了排名跳编号的问题。



转载请注明:清风博客 » 使用sqlserver的排名函数实现积分排名

]]>
https://www.skyfinder.cc/2022/04/06/sql-server-rank-handle/feed/ 0
C#使用正则表达式移除字串符前后指定的字符串 https://www.skyfinder.cc/2022/04/04/csharp-regex-trim-remove/ https://www.skyfinder.cc/2022/04/04/csharp-regex-trim-remove/#respond Mon, 04 Apr 2022 15:49:55 +0000 https://www.skyfinder.cc/?p=3819 有时候一些需求,移除字符串前后指定的字符串。其实,如果没有仅移除一次的需求,使用系统自带的方法即可完成,使用TrimTrimEndTrimStart方法也比较快捷。当有一些特殊需求的时候,这些方法就不再方便。所以,就使用正则表达式简单的实现相关需求。

字符串扩展代码实现


    public static partial class Extensions
    {
        /// <summary>
        /// 移除字符串前后指定的字符串
        /// </summary>
        /// <param name="value">字符串本身</param>
        /// <param name="trimContent">将要移除字符前后的指定内容</param>
        /// <param name="isRepeat">是否允许重复匹配</param>
        /// <returns></returns>
        public static string Trim(this string value, string trimContent = "",bool isRepeat=true)
        {
            if (string.IsNullOrWhiteSpace(value))
            {
                return "";
            }
            if (string.IsNullOrWhiteSpace(trimContent))
            {
                return value.Trim();
            }
            trimContent = Regex.Replace(trimContent, @"([.$\\^*+?\[\]()])", @"\$1");
            string limit = isRepeat ? "+" : "";
            string regexString = $"^(?:{trimContent}){limit}|(?:{trimContent}){limit}$";
            value = Regex.Replace(value, regexString, "");
            return value;
        }

        /// <summary>
        /// 移除字符串开始指定的字符串
        /// </summary>
        /// <param name="value">字符串本身</param>
        /// <param name="trimContent">将要在字符串开始移除的字符</param>
        /// <param name="isRepeat">是否允许重复匹配</param>
        /// <returns></returns>
        public static string TrimStart(this string value, string trimContent = "", bool isRepeat = true)
        {
            if (string.IsNullOrWhiteSpace(value))
            {
                return "";
            }
            if (string.IsNullOrWhiteSpace(trimContent))
            {
                return value.TrimStart();
            }
            trimContent = Regex.Replace(trimContent, @"([.$\\^*+?\[\]()])", @"\$1");
            string limit = isRepeat ? "+" : "";
            string regexString = $"^(?:{trimContent}){limit}";
            value = Regex.Replace(value, regexString, "");
            return value;
        }

        /// <summary>
        /// 移除字符串结尾指定的字符
        /// </summary>
        /// <param name="value">字符串本身</param>
        /// <param name="trimContent">将要在字符串结尾移除的字符</param>
        /// <param name="isRepeat">是否允许重复匹配</param>
        /// <returns></returns>
        public static string TrimEnd(this string value, string trimContent = "", bool isRepeat = true)
        {
            if (string.IsNullOrWhiteSpace(value))
            {
                return "";
            }
            if (string.IsNullOrWhiteSpace(trimContent))
            {
                return value.Trim();
            }
            trimContent = Regex.Replace(trimContent, @"([.$\\^*+?\[\]()])", @"\$1");
            string limit = isRepeat ? "+" : "";
            string regexString = $"(?:{trimContent}){limit}$";
            value = Regex.Replace(value, regexString, "");
            return value;
        }

        /// <summary>
        ///通过指定正则表达式替换指定内容
        /// </summary>
        /// <param name="value">字符串本身</param>
        /// <param name="pattern">正则表达式</param>
        /// <param name="replacement">替换内容</param>
        /// <returns></returns>
        public static string Replace(this string value, string pattern, string replacement = "")
        {
            if (string.IsNullOrWhiteSpace(value))
            {
                return "";
            }
            if (string.IsNullOrWhiteSpace(pattern))
            {
                throw new ArgumentException("pattern 不能为空");
            }
            value = Regex.Replace(value, pattern, replacement);
            return value;
        }
        /// <summary>
        /// 通过指定正则表达式替换指定内容
        /// </summary>
        /// <param name="value">字符串本身</param>
        /// <param name="pattern">正则表达式</param>
        /// <param name="replacement">替换内容</param>
        /// <param name="options">正则表达式选项</param>
        /// <returns></returns>
        public static string Replace(this string value, string pattern, string replacement, RegexOptions options)
        {
            if (string.IsNullOrWhiteSpace(value))
            {
                return "";
            }
            if (string.IsNullOrWhiteSpace(pattern))
            {
                throw new ArgumentException("pattern 参数不能为空");
            }
            value = Regex.Replace(value, pattern, replacement, options);
            return value;
        }

    }

字符串扩展使用


        static void Main(string[] args)
        {
            string original = "NameNameemaNHello Name World!NameName";
            Console.WriteLine($"原字符串:{original}\r\n");
            Console.WriteLine($"移除指定字符串前后Name并重复匹配:{original.Trim("Name")}");
            Console.WriteLine(Environment.NewLine);
            Console.WriteLine($"移除指定字符串前后Name单次匹配:{original.Trim("Name", false)}");
            Console.WriteLine(Environment.NewLine);
            Console.WriteLine($"移除字符串开始Name并重复匹配:{original.TrimStart("Name")}");
            Console.WriteLine(Environment.NewLine);
            Console.WriteLine($"移除字符串开始Name不重复匹配:{original.TrimStart("Name", false)}");
            Console.WriteLine(Environment.NewLine);
            Console.WriteLine($"移除字符串结束Name并重复匹配:{original.TrimEnd("Name")}");
            Console.WriteLine(Environment.NewLine);
            Console.WriteLine($"移除字符串结束Name不重复匹配:{original.TrimEnd("Name", false)}");
            Console.Read();
        }

C#使用正则表达式移除字串符前后指定的字符串-第0张图片



转载请注明:清风博客 » C#使用正则表达式移除字串符前后指定的字符串

]]>
https://www.skyfinder.cc/2022/04/04/csharp-regex-trim-remove/feed/ 0
东航MU5735 https://www.skyfinder.cc/2022/03/27/%e4%b8%9c%e8%88%aamu5735/ https://www.skyfinder.cc/2022/03/27/%e4%b8%9c%e8%88%aamu5735/#respond Sat, 26 Mar 2022 17:21:33 +0000 https://www.skyfinder.cc/?p=3811 3月21日,东航MU5735航班坠毁在广西梧州市藤县琅南镇莫埌村一片山坳中。

东方航空公司MU5735航班上人员已全部遇难。

向东航MU5735航班失事遇难者默哀。

东航MU5735-第0张图片



转载请注明:清风博客 » 东航MU5735

]]>
https://www.skyfinder.cc/2022/03/27/%e4%b8%9c%e8%88%aamu5735/feed/ 0
枉凝眉 https://www.skyfinder.cc/2022/03/13/%e6%9e%89%e5%87%9d%e7%9c%89/ https://www.skyfinder.cc/2022/03/13/%e6%9e%89%e5%87%9d%e7%9c%89/#respond Sun, 13 Mar 2022 08:40:37 +0000 https://www.skyfinder.cc/?p=3792 枉凝眉-第0张图片

在刷抖音时,刷到一首歌曲。感觉比较好听,歌的名字《枉凝眉》,作词竟然是曹雪芹。想了想,曹雪芹应该是写《红楼梦》 的那个吧!于是就稍微了解了一下,枉凝眉是《红楼梦》十二曲之一。说起《红楼梦》,就知道它是4大名著之一,上学的时候是这样学的,也是这样记的。其实除了这些以外,剩下的只知道曹雪芹是它的作者了,对《红楼梦》了解真的不多。《红楼梦》原著没有读过,小时候只看过1987年上映的电视剧《红楼梦》。其实,不能称为看过这部电视剧,因为只是看了几集而已,因为小时候看不懂,所以就没有再看过了。那个时候的我更喜欢看一些战争片、武打片或者动画片,尽管也看的不是太懂,但是不妨碍我喜欢。

枉凝眉

曹雪芹

一个是阆苑仙葩,一个是美玉无瑕。
若说没奇缘,今生偏又遇着他。
若说有奇缘,如何心事终虚话?
一个枉子嗟呀,一个空劳牵挂。
一个是水中月,一个是镜中花。
想眼中能有多少泪珠儿,
怎禁得秋流到冬,春流到夏!

作者在这支曲子中,进一步抒写了贾宝玉与林黛玉之间的真挚的爱情。他们”一个是阆苑仙葩”,”一个是美玉无瑕”,都是心地纯洁,聪明善良的青年。这样一对有情人,能不能结合呢?作者用两个设问,怀着含蓄而又诚挚的感情,把人们带到现实世界里来。现实就是这样残酷,在封建宗法制度的统治迫害下,有情人难成眷属,只能空自嗟,枉牵挂。接着应用”水中月”、”镜中花”这两个形象化的比喻,对造成这种可望而不可及的爱情悲剧的现实提出了质问,最后用眼泪的控诉作结。整个曲子婉转深沉,发人深思。当然作者不会认识造成这个爱情悲剧的社会原因,但他写出这种悲剧,明白而响亮地发出呐喊,提出控诉,也是难能可贵的。这种现实主义的创作方法,使这部伟大的作品发出反封建的思想光芒



转载请注明:清风博客 » 枉凝眉

]]>
https://www.skyfinder.cc/2022/03/13/%e6%9e%89%e5%87%9d%e7%9c%89/feed/ 0
我做了一个梦 https://www.skyfinder.cc/2022/02/26/%e6%88%91%e5%81%9a%e4%ba%86%e4%b8%80%e4%b8%aa%e6%a2%a6/ https://www.skyfinder.cc/2022/02/26/%e6%88%91%e5%81%9a%e4%ba%86%e4%b8%80%e4%b8%aa%e6%a2%a6/#respond Sat, 26 Feb 2022 01:00:00 +0000 https://www.skyfinder.cc/?p=3732 昨夜我好像做了一个梦啊,梦儿是那么样的长!

梦到了我又回到了儿时的故乡。

那个又瘸又聋的哑巴吓得我哭出了声响。

亲爱的妈妈抱着我回到屋内,我还是哭的那么的响亮。

谁能知道这就成了我的噩梦,它就这样恐惧的伴着我成长。

昨夜我好像做了一个梦啊,梦儿是那么样的长!

梦到了我又回到了年幼的时光 。

与那可爱的伙伴开心的奔跑捉着迷藏。

童年无忧的快乐与我们一起成长,但它没有时常走进我的梦乡。

昨夜我好像做了一个梦啊,梦儿是那么样的长!

梦到我的爸爸没有经常出现在我的身旁。

只知道爸爸的出现有了玩具,还有可能的几颗糖。

那时候的我哪里会知道,爸爸他在为这个家庭奔忙。

昨夜我好像做了一个梦啊,梦儿是那么样的长。

梦到我的姐姐帮妈爸做饭搞的手脚忙。

炒几个鸡蛋却黑成那个样。

我却幸灾乐祸的四处张扬。

昨夜我好像做了一个梦啊,梦儿是那么样的长!

梦到了小学二年级的数学教师恶毒的心肠。

她那无情的言语,泯灭了我眼中光。

这般恶毒为何可以在教师队伍里久长。

真的好想爸妈为我出头,挣一个地老天荒。

昨夜我好像做了一个梦啊,梦儿是那么样的长!

梦到我那个疼我的二姨娘。

我们又去到她在的地方,又带回那么多好吃的粮。

昨夜我好像做了一个梦啊,梦儿是那么样的长!

梦到了我又回到了那个地方。

一个小姑娘她到了机井旁,无忧无虑竟如此疯狂。

吓得妈妈与姥姥发慌,却又不敢声张,害怕可能失足的小姑娘。

昨夜我好像做了一个梦啊,梦儿是那么样的长!

梦到了我又去到了那个村庄。

慈祥的姥姥给我留的水果,还有那么多的糖。

如今谁再为我留下水果,还有那些甜蜜的糖。

再想那音容唯有在梦乡,从此少了那万般疼爱,却多了那么多的心伤。

每当想起就忍不住的伤心,泪两行。

昨夜我好像做了一个梦啊,梦儿是那么样的长!

梦到了曾经的我远走他乡,开始为生计奔忙。

看到五彩缤纷的世界,觉得自己可以把控住方向。

遇到一些慈眉善目的人呐,可谁想他转眼就变成了虎豹和豺狼。

初入社会的我怎能不遍体鳞伤,受伤的我瞬间想起那至亲爹娘。

昨夜我好像做了一个梦啊,梦儿是那么样的长!

梦到了我去到了父亲奔波的在地方。

我看到了皱纹慢慢爬上父亲的脸庞,看到的那都是沧桑。

都怨我这个无用的人啊,他还奔波在异乡。

昨夜我好像做了一个梦啊,梦儿是那么样的长!

梦到了我再次回到生我养我的家乡。

看到我那妻儿都围在我身旁,看那欢乐的笑脸,希望他们给我力量。

不知为何,我还是透着一些忧伤!

昨夜我好像做了一个梦啊,梦儿是那么样的长!

梦到了我亲爱的妈妈,她来到了我的身旁。

轻轻的拉着我的手,抚摸着我的脸庞,那慈祥的容颜从未变过模样。

伤心的泪水就这样止不住的流淌,满眼汪洋再也看不清妈妈的脸庞。

无论如何哭泣,妈妈好像再也没有给我任何回响,好似近在身边,却是远隔阴阳。

真不该阻止你吃那喜欢的糖,真不该让你万般心伤、万般失望!

你可知我是如何的忧伤,又是如何度过漫长时光。

你可知道我再也不敢轻易述说衷肠。

伸手想把你留在我的身旁,哪知我会重重的摔下了床,我傻傻的坐在地上,原来这是大梦一场!

你可知道我想你呀,我的亲娘!!!

我果真做了一个梦啊,梦儿是这么样的长!

梦到了我的童年,梦到了我的家乡。

梦到了曾经的亲人,曾经的模样。

我不想再做这样的梦啊,我不想再这般心伤,可是我担心你再也走不进我的梦乡,更害怕忘记你的模样!

我想再做这样的梦啊,不管它如何忧伤,这样才能让我感觉到你还在我身旁!



转载请注明:清风博客 » 我做了一个梦

]]>
https://www.skyfinder.cc/2022/02/26/%e6%88%91%e5%81%9a%e4%ba%86%e4%b8%80%e4%b8%aa%e6%a2%a6/feed/ 0
移动端页面禁用缩放 https://www.skyfinder.cc/2022/01/15/user-scalable-no/ https://www.skyfinder.cc/2022/01/15/user-scalable-no/#respond Sat, 15 Jan 2022 15:59:00 +0000 https://www.skyfinder.cc/?p=3589 背景

在调整一个移动端的页面,测试过程中发现在苹果IOS系统的浏览器中缩放异常,其实页面操作上来讲是不需要当前页面缩放的。所以,就需要禁止当前页面的缩放操作。经过确认,可以使用以下代码实现禁止缩放操作。


<meta name="viewport" content="width=device-width,initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no">

Viewport

属性名取值描述
width正整数或device-width定义视口的宽度,单位为像素
height正整数或device-height定义视口的高度,单位为像素,一般不用
initial-scale[0.0-10.0]定义初始缩放值
minimum-scale[0.0-10.0]定义放大最大比例,它必须小于或等于maximum-scale设置
maximum-scale[0.0-10.0]定义缩小最小比例,它必须大于或等于minimum-scale设置
user-scalableyes / no定义是否允许用户手动缩放页面,默认值 yes

注意

  • viewport 标签只对移动端浏览器有效,对 PC 端浏览器是无效的
  • 当缩放比例为 100% 时,dip 宽度 = CSS 像素宽度 = 理想视口的宽度 = 布局视口的宽度
  • 单独设置 initial-scalewidth 都会有兼容性问题,所以设置布局视口为理想视口的最佳方法是同时设置这两个属性
  • 即使设置了 user-scalable = no,在 Android Chrome 浏览器中也可以强制启用手动缩放



转载请注明:清风博客 » 移动端页面禁用缩放

]]>
https://www.skyfinder.cc/2022/01/15/user-scalable-no/feed/ 0
基于dotnet官方的aspnet5的镜像构建安装libgdiplus基础镜像 https://www.skyfinder.cc/2021/12/18/dotnet-aspnet5-libgdiplus/ https://www.skyfinder.cc/2021/12/18/dotnet-aspnet5-libgdiplus/#respond Sat, 18 Dec 2021 01:00:00 +0000 https://www.skyfinder.cc/?p=3696 背景

.net 5应用中,使用了Excel文件处理,在Docker容器中运行就会出现关于libgdiplus的异常。虽然在Dockerfile中可以使用以下内容解决异常,但是这个速度太慢了。每次构建慢的让人怀疑人生,最重要的是还可能失败。

RUN apt-get update && apt-get install -y libgdiplus libc6-dev && ln -s /usr/lib/libgdiplus.so /usr/lib/gdiplus.dll

为了提高构建速度,所以使用官方的基础镜像再二次构建一个基础镜像,默认安装 libgdiplus ,这样每次构建速度就会提升好多好多。默认安装了libgdiplus等库,以便支持Excel导入导出

构建基础镜像的Dockerfile

Dockerfile


FROM mcr.microsoft.com/dotnet/aspnet:5.0

RUN apt-get update && apt-get install -y libgdiplus libc6-dev && ln -s /usr/lib/libgdiplus.so /usr/lib/gdiplus.dll
# 安装fontconfig库,用于Pdf导出
RUN apt-get update && apt-get install -y fontconfig
# 复制字体文件
COPY /simsun.ttc /usr/share/fonts/simsun.ttc

如果CPU架构是ARM架构的话,引用的官方基础镜像包就要做一些调整。详情参考Dockerfile.arm

Dockerfile.arm


FROM mcr.microsoft.com/dotnet/aspnet:5.0-buster-slim-arm64v8

RUN apt-get update && apt-get install -y libgdiplus libc6-dev && ln -s /usr/lib/libgdiplus.so /usr/lib/gdiplus.dll
# 安装fontconfig库,用于Pdf导出
RUN apt-get update && apt-get install -y fontconfig
# 复制字体文件
COPY /simsun.ttc /usr/share/fonts/simsun.ttc

字体文件

字体simsun.ttc

已构建镜像

基于dotnet官方的aspnet5的镜像构建安装libgdiplus基础镜像-第0张图片

如果自己不构建镜像,也可以使用已经构建好的镜像。

docker pull skyfinder/dotnet-aspnet5

ARM架构的CPU可以使用以下镜像

docker pull skyfinder/dotnet-aspnet5-buster-slim-arm64v8



转载请注明:清风博客 » 基于dotnet官方的aspnet5的镜像构建安装libgdiplus基础镜像

]]>
https://www.skyfinder.cc/2021/12/18/dotnet-aspnet5-libgdiplus/feed/ 0
.NET 5中使用GB2312编码报错的问题 https://www.skyfinder.cc/2021/12/17/net-core-register-encoding/ https://www.skyfinder.cc/2021/12/17/net-core-register-encoding/#respond Fri, 17 Dec 2021 02:53:22 +0000 https://www.skyfinder.cc/?p=3702 背景

在使用.net 5构建应用时,在处理某些编码问题使用了GB2312,应用运行后报错。经过确认知道了编码 GB2312 默认不支持。

异常信息

Not Support Encoding’GB2312′ is not a supported encoding name. For information on defining a custom encoding, see the documentation for the Encoding.RegisterProvider method. (Parameter ‘name’)
System.Exception: Not Support Encoding’GB2312′ is not a supported encoding name. For information on defining a custom encoding, see the documentation for the Encoding.RegisterProvider method. (Parameter ‘name’)

解决办法

添加引用包

.NET 5中使用GB2312编码报错的问题-第0张图片
System.Text.Encoding.CodePages

注册

在使用System.Text.Encoding.GetEncoding ("GB2312")之前,在代码中执行注册

 System.Text.Encoding.RegisterProvider(System.Text.CodePagesEncodingProvider.Instance);



转载请注明:清风博客 » .NET 5中使用GB2312编码报错的问题

]]>
https://www.skyfinder.cc/2021/12/17/net-core-register-encoding/feed/ 0
Visual Studio(VS) 2022 密钥/激活码 https://www.skyfinder.cc/2021/11/12/visual-studio-2022-key/ https://www.skyfinder.cc/2021/11/12/visual-studio-2022-key/#respond Fri, 12 Nov 2021 04:52:21 +0000 https://www.skyfinder.cc/?p=3684 Visual Studio(VS) 2022 密钥/激活码-第0张图片

简介

Visual Studio 2022简称VS2022,包含了专业版、企业版以及社区版等版本,这是由微软推出的新一代集成开发环境,软件提供了丰富的工具集,可以带来更快的开发速度,新版本还拥有更可靠的代码开发速度。

下载

Visual Studio 2022Visual Studio 2022 For Mac

密钥/激活码

专业版/Professional

Visual Studio 2022 Professional

TD244-P4NB7-YQ6XK-Y8MMM-YWV2J

企业版/ Enterprise

Visual Studio 2022 Enterprise

VHF9H-NXBBB-638P6-6JHCY-88JWH



转载请注明:清风博客 » Visual Studio(VS) 2022 密钥/激活码

]]>
https://www.skyfinder.cc/2021/11/12/visual-studio-2022-key/feed/ 0
有些梦 https://www.skyfinder.cc/2021/11/07/%e6%9c%89%e4%ba%9b%e6%a2%a6/ https://www.skyfinder.cc/2021/11/07/%e6%9c%89%e4%ba%9b%e6%a2%a6/#respond Sun, 07 Nov 2021 14:02:58 +0000 https://www.skyfinder.cc/?p=3682 有些梦,是一种回忆,亦是一种折磨!有些梦,是一种救赎,亦是一种惭悔!有时想而又想,有时不想再想!万般滋味,衷肠难诉!



转载请注明:清风博客 » 有些梦

]]>
https://www.skyfinder.cc/2021/11/07/%e6%9c%89%e4%ba%9b%e6%a2%a6/feed/ 0