.NET 7+Angular 4 轻量级新零售进销存系统 - 小码编匠 - 博客园

mikel阅读(137)

来源: .NET 7+Angular 4 轻量级新零售进销存系统 – 小码编匠 – 博客园

前言

给大家推荐一个专为新零售快消行业打造了一套高效的进销存管理系统。

系统不仅具备强大的库存管理功能,还集成了高性能的轻量级 POS 解决方案,确保页面加载速度极快,提供良好的用户体验。

项目介绍

Dorisoy.POS 是一款基于 .NET 7 和 Angular 4 开发的新零售快消进销存管理系统。

系统集成了先进的库存管理和高性能的轻量级 POS 功能,拥有出色的性能和快速的页面加载速度,确保用户获得流畅的操作体验。

无论是快速销售还是库存控制,Dorisoy.POS 都能实现业务流程的自动化与优化。

项目功能

  • 客户管理:记录并维护所有客户的详细信息。
  • 产品管理:全面管理产品信息,包括品牌、类别、单位、销售价格、采购价格和税率。
  • 供应商管理:管理供应商的账单和发货地址信息。
  • 采购订单管理
  • 处理包含多种产品和税率的采购订单。
  • 生成并打印采购订单发票/收据。
  • 根据请求/报价创建多个采购订单。
  • 管理采购订单的付款。
  • 支持采购订单中的退货处理,并生成相关发票/收据。
  • 在采购订单交付日期收到自动通知/电子邮件。
  • 销售订单管理
  • 管理包含多种产品和税率的销售订单。
  • 生成并打印销售订单发票/收据。
  • 管理销售订单的付款。
  • 支持销售订单中的退货处理,并生成相关发票/收据。
  • 在销售订单发货日期收到自动通知/电子邮件。
  • 库存管理
  • 自动管理产品库存,包括平均销售额、采购价格等。
  • 支持手动调整库存。
  • 查看所有采购、销售、采购退货和销售退货的历史记录。
  • 费用管理:记录和管理所有公司的费用及其类别。
  • 查询管理
  • 跟踪所有查询及其产生的活动。
  • 设置特定日期的提醒,以发送通知或电子邮件。
  • 仪表板
  • 快速统计销售、采购、销售退货及采购退货总额。
  • 展示月度畅销产品排行榜。
  • 提供带有提醒功能的日历。
  • 显示最新查询列表。
  • 展示最近销售订单的预计发货情况。
  • 显示最近采购订单的预计收货情况。
  • 报告: 提供多种报告,包括但不限于采购订单、销售订单、支出、损益分析等。
  • 生成产品采购、销售报告及库存报告。
  • 提醒计划程序:支持设置每日、每周、每月、季度、半年、每年或特定日期的提醒。
  • 多语言支持:默认支持英语和中文,可快速添加其他语言。
  • 用户和角色管理:管理员可通过管理面板创建用户和角色,并分配权限。
  • 权限管理:授予员工特定权限,覆盖角色和用户权限。
  • 高性能:高级轻量级 POS 系统,具有闪电般的性能和快速的页面加载速度。
  • 电子邮件集成:使用文本编辑器设置预定义的电子邮件模板,管理 SMTP 设置,并一键发送邮件。
  • 完整源代码:提供完整的源代码和 SQL Server 及 MySQL 数据库的版本。

项目环境

  • 核心框架:.NET 6 SDK
  • 数据库:SQL SERVER 或 MYSQL 8+
  • Node.js(同时安装 npm 前端包管理工具)
  • 开发工具:Visual Studio 2022

项目运行

1、选择数据库

根据您的需求,在相应的源文件夹中选择 SQL 或 MySQL API。

2、打开解决方案文件

使用 Visual Studio 2022 打开 .NET 核心文件夹中的解决方案文件 POS.sln。

3、还原 NuGet 包

在解决方案资源管理器中,右键单击解决方案,选择”管理 NuGet 包”,并确保所有依赖项已安装。

4、配置数据库连接字符串

修改 POS.API 项目中的 appsettings.Development.json 文件中的数据库连接字符串。

5、设置启动项目

在解决方案资源管理器中,右键单击 POS.API 项目,然后从上下文菜单中选择”设为启动项目”。

6、运行项目

按 F5 运行项目

项目展示

1、仪表盘

2、销售订单

3、库存管理

4、客户信息

项目地址

GitHub:https://github.com/dorisoy/Dorisoy.POS

在C# 中Sql 执行超时的问题 - 郝奕 Frank Hao - 博客园

mikel阅读(121)

来源: 在C# 中Sql 执行超时的问题 – 郝奕 Frank Hao – 博客园

解决方法:

C#代码
SQLCommand selectCommand = new SQLCommand(queryText, this.openConnection());
//得到Web.config里DB_ConnectionString中Connection Timeout=90
selectCommand.CommandTimeout = cn.ConnectionTimeout;
或者
SQLCommand selectCommand = new SqlCommand(queryText, this.openConnection());
selectCommand.CommandTimeOut=0;

Web.config(.NET 2005)
<connectionStrings>
<add name=”DB_ConnectionString” connectionString=”Data Source=xxx.xxx.xxx.xxx;Initial Catalog=DB NAME;Persist Security Info=false;User ID=sa;Password=sa;Connection Timeout=90;” providerName=”System.Data.SqlClient”/>
</connectionStrings>

CommandTimeout 属性

指示执行命令期间在终止尝试和产生错误之前需等待的时间。

设置和返回值

设置或返回 Long 值,该值指示等待命令执行的秒数。默认值为 30。

说明

用 Connection 对象或 Command 对象的 CommandTimeout 属性来允许因网络拥挤或服务器负载过重产生的延迟而取消 Execute 方法调用。如果在 CommandTimeout 属性设置的时间间隔内未执行完命令,将产生错误,并且 ADO 取消该命令。如果将属性设置为零,ADO 将一直等待到命令执行完毕。请确保正在为其编写代码的提供者和数据源支持 CommandTimeout 功能。

Connection 对象的 CommandTimeout 设置对同一 Connection 中 Command 对象上的 CommandTimeout 设置没有影响,即 Command 对象的 CommandTimeout 属性不继承 Connection 对象的 CommandTimeout 值。

在 Connection 对象上,打开 Connection 后,CommandTimeout 属性将保持为读/写。

ConnectionTimeout 属性

指示在终止尝试和产生错误前建立连接期间所等待的时间。

设置和返回值

设置或返回指示等待连接打开的时间的长整型值(单位为秒)。默认值为 15。

说明

如果由于网络拥塞或服务器负载过重导致的延迟使得必须放弃连接尝试时,请使用 Connection 对象的 ConnectionTimeout 属性。如果打开连接前所经过的时间超过 ConnectionTimeout 属性上设置的时间,将产生错误,并且 ADO 将取消该尝试。如果将该属性设置为零,ADO 将无限等待直到连接打开。请确认正在对其编写代码的提供者会支持 ConnectionTimeout 功能。

连接关闭时 ConnectionTimeout 属性为读/写,而打开时其属性为只读。

还原数据库提示“ 因为数据库正在使用,所以无法获得对数据库的独占访问权_还原数据库失败 因为数据库正在使用-CSDN博客

mikel阅读(140)

来源: 还原数据库提示“ 因为数据库正在使用,所以无法获得对数据库的独占访问权_还原数据库失败 因为数据库正在使用-CSDN博客

 

问题:提示错误信息:
因为数据库正在使用,所以无法获得对数据库的独占访问权。

解决方案:
解决方案1.
如果你使用管理工具还原数据库并且在SQL Server Management Studio 2016或以上版本的话,可以在还原的时候勾选”关闭到目标数据库的现有链接”
解决方案2.
SQL SERVER 2016或以下的版本的
在需要还原的数据库上右击,在右键菜单命令上选择**“属性”- >“选项”- >“状态”- >“限制访问”- >“Single”**
单用户模式,直接还原,完成后把访值改回来
————————————————

版权声明:本文为博主原创文章,遵循 CC 4.0 BY 版权协议,转载请附上原文出处链接和本声明。

原文链接:https://blog.csdn.net/qq_36701078/article/details/111560416

Scratch的下载与安装_scratch1.0安装包-CSDN博客

mikel阅读(345)

来源: Scratch的下载与安装_scratch1.0安装包-CSDN博客

如果你在某搜索引擎用这个,就是离被骗钱不远了

 

添加图片注释,不超过 140 字(可选)

废话不多说,直接放软件安装包。重要的事情说三遍,收费的全是骗人的!收费的全是骗人的!收费的全是骗人的!

夸克网盘分享​ Scratch软件下载

包里各版本的软件都有,根据自己的需求下载一个就行:

 

添加图片注释,不超过 140 字(可选)

Scratch有三个大的版本,Scratch1.0(几乎没人用了),Scratch2.0,支持win7系统,现在用的人也比较少。最新的版本是Scratch3.0,现在更新到了3.29.1,大家直接下载安装就可以。其它版本的也建议保存,老版本的可能不好找了。

MAC版本的在这里:

 

添加图片注释,不超过 140 字(可选)

安装方式:

1,双击安装文件:

 

添加图片注释,不超过 140 字(可选)

2,选中为使用这台电脑的任何人安装(所有用户),点击安装。

 

添加图片注释,不超过 140 字(可选)

3,正在安装

 

添加图片注释,不超过 140 字(可选)

4,取消运行Scratch 3(R)的选中框,点击完成。Scratch程序就安装好了。

 

添加图片注释,不超过 140 字(可选)

5,电脑桌面会出现Scratch图标。双击打开,就是咱们的Scratch程序了。

 

添加图片注释,不超过 140 字(可选)

大家在下载过程中遇到问题可以流言,我会在评论区回复。
————————————————

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。

原文链接:https://blog.csdn.net/liuhejianwei/article/details/140909302

C# 解析获取Url参数值 - 傅小灰 - 博客园

mikel阅读(181)

来源: C# 解析获取Url参数值 – 傅小灰 – 博客园

今天遇到一个需求,需要处理通过接口传过来的一个参数,参数内容为一个拼接好的Url地址,且该地址还会携带了一些额外的参数,包括但不限于数字,字符串,json串。样例如下:

http://www.cplemom.com/?url=https://www.cnblogs.com/cplemom/?id=15&data={"id":12,"name":"今天天气不错"}

现在的问题就是,我需要将url参数中的data值的json中的id修改为url参数中id的值,然后再拼接成完整的url去获取数据。简单来说就是将url进行如下改变,现在问题的重点就在于如何根据url获取对应的参数值了。

//处理前
https://www.cnblogs.com/cplemom/?id=15&data={"id":12,"name":"今天天气不错"}

//处理后
https://www.cnblogs.com/cplemom/?id=15&data={"id":15,"name":"今天天气不错"}

Url编码

这里插一句,在将url作为参数进行请求的时候,需要注意一些特殊字符(比如 ? "" / 等)对请求的影响,特别是发送Get请求的时候。可以在发送请求前,先对url进行编码处理。

encodeURIComponent(url) //js
HttpUtility.UrlEncode(url) //C#
url.QueryEscape(url) //Go

获取Url参数

仔细思考了下,解析url获取参数的也无非通过分割关键字符来实现。区别就是分割字符的方式是通过正则匹配或者遍历字符罢了。下面就介绍框架带的方法,然后通过总结一些规律,最后自己实现一个方法。

我知道肯定有很多朋友经常使用HttpUtility类中的UrlEncode/UrlDecode方法,其实该类还有一个ParseQueryString方法,用来解析url参数。该方法需要传入url参数部分字符串,然后返回一个NameValueCollection对象(以key/value的形式记录了所有参数)。

string url = "https://www.cnblogs.com/cplemom/?id=15&data={'id':12,'name':'今天天气不错'}";
var uri = new Uri(url);

var collection= HttpUtility.ParseQueryString(uri.Query);//默认采用UTF-8编码,当然也可以传入特定编码进行解析
//var collection= HttpUtility.ParseQueryString(uri.Query,Encoding.ASCII);

Console.WriteLine(collection["data"]);//输出结果:  {'id':12,'name':'今天天气不错'}

该方法有几个要注意的点

  1. uri.Query是?开头的,但是在转为键值对以后被自动过滤掉了,且只会过滤一个?字符
  2. 参数内容会自动使用UTF-8解码
  3. 对于传递的数组参数(?ids=1&ids=2),会通过,拼接
  4. 在参数字符中通过&划分后,只会把第一个=前的字符串作为key

基于上述一些要求就可以自己写一个解析参数的方式了。

public static Dictionary<string, string> ParseQueryString(string url)
{
	if (string.IsNullOrWhiteSpace(url))
	{
		throw new ArgumentNullException("url");
	}
	var uri = new Uri(url);
	if (string.IsNullOrWhiteSpace(uri.Query))
	{
		return new Dictionary<string, string>();
	}
                  //1.去除第一个前导?字符
	var dic = uri.Query.Substring(1)
                        //2.通过&划分各个参数
			.Split(new char[] { '&' }, StringSplitOptions.RemoveEmptyEntries)
                        //3.通过=划分参数key和value,且保证只分割第一个=字符
			.Select(param => param.Split(new char[] { '=' }, 2, StringSplitOptions.RemoveEmptyEntries))
                        //4.通过相同的参数key进行分组
			.GroupBy(part => part[0], part => part.Length > 1 ? part[1] : string.Empty)
                        //5.将相同key的value以,拼接
			.ToDictionary(group => group.Key, group => string.Join(",", group));

	return dic;
}

总结

总体来说这只是一个小小的功能点,往往框架本身就帮我们封装好了解析url获取参数的方法,但是自己去实现的时候,可能就会有这样那样的情况没有考虑到。最后如果有遇到相似需求的朋友,我建议还是用HttpUtility.ParseQueryString()吧~~~

基于OpenCV的刷脸考勤&人脸校验&用户管理系统(源码&教程) | qunmasj

mikel阅读(181)

来源: 基于OpenCV的刷脸考勤&人脸校验&用户管理系统(源码&教程) | qunmasj

由于图床过期,图片无法正常显示,有图阅览请移步以下Gitee/Github网址,文末获取【源码和部署教程】或者通过以下Gitee/Github的文末邮件获取

Gitee(推荐国内访问): https://gitee.com/qunmasj/projects

Github(推荐国外访问): https://github.com/qunshansj?tab=repositories

# 1.研究背景
随着人工智能时代的来临,深度学习赋予了以人脸图像为中心的计算机视觉任务更多的可能。人脸检测是人脸识别系统组成的关键部分之一,其目的是检测出任意给定图片中的包含的一个或多个人脸,是人脸识别、表情识别等下游任务的基础。人脸识别是通过采集包含人脸的图像或视频数据,通过对比和分析人脸特征信息从而实现身份识别的生物识别技术,是人脸识别系统的核心组件。如今人脸识别系统广泛应用于写字楼、学校、医院和车站等需要对入场人员身份信息进行确认和鉴别的场所。进而利用人脸检测算法检测出摄像头拍摄图片中包含的人脸,再使用识别算法对比当前人脸特征和数据库内的人脸特征,确认人员身份,最终输出人员相关的身份信息并生成打卡记录。在实际的运行测试中,针对戴口罩的场景也有优异的表现,满足公司打卡应用的实际需求。

2.视频演示

基于OpenCV的刷脸考勤&人脸校验&用户管理系统(源码&教程)_哔哩哔哩_bilibili

3.考勤打卡算法原理

人脸识别

基于Python语言,通过对图像预处理、提取特征向量、与人脸数据库中数据进行校验进而判别人物身份的一项技术。具体流程图如图所示。
12.png
(1)人脸图像采集与对齐:首先 Dlib检测人脸并输出人脸矩形的四个坐标点,即在图片中识别到人脸的位置。然后由OpenCV ( Open Source Computer Vision Library)从底层采集人脸图像并转换成数据集,并通过对图像的灰度化、二值化等对人脸图像进行预处理【5】。
人脸对齐即在检测到的人脸矩阵中找到人脸上的眼鼻口和脸部轮廓等标志性特征向量。
(2)计算人脸图像与待识别人脸对应的特征向量:首先利用Dlib中的梯度提升树(Gradient Boosting Deci-sion Tree,GBDT)提取人脸68个特征点【6】。再利用ResNet残差学习深度神经网络为待识别人脸创建128维特征向量T。
(3)计算人脸间的欧式距离:假设数据库中的人脸图像向量与待识别人脸向量分别为x (x1,x2,X3…,Xn)与y(yi,y2zy3.….yn),根据欧式距离公式(1)计算两向量欧式距离,为人脸校验提供数据。
(4)人脸校验:两个人脸特征向量的欧式距离越小,则两张人脸越相似,若人脸图像与待识别人像之间的欧式距离小于设定的阈值时,则判定为同一人。

算法流程图

13.png

4.刷脸&密码登录(用户&管理员)

2.png

8.png

代码实现

用户注册

plaintext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
def register(self):
    username = self.textEdit_1.toPlainText()
    password = self.textEdit_2.toPlainText()
    #print(username,password)


    # 执行的都是原生SQL语句
    sql = '''
    SELECT Name
    FROM   users
    '''

    has_name = 0
    for row in self.get(sql):
        if username == str(row[0]):
            #print(row)
            has_name = 1
            self.textEdit_1.setText('用户已经注册')
            break
    if not has_name:
        savepath = './images_db'
        if not os.path.exists(savepath):
            os.makedirs(savepath)
        # 打开摄像头
        detector = dlib.get_frontal_face_detector()
        predictor = dlib.shape_predictor("shape_predictor_68_face_landmarks.dat")
        cap = cv2.VideoCapture(0)
        for x in range(50):
            ret, image = cap.read()
            imagecopy = image.copy()
            img_gray = cv2.cvtColor(image, cv2.COLOR_RGB2GRAY)

            # 使用人脸检测器检测每一帧图像中的人脸。并返回人脸数rects
            faces = detector(img_gray, 0)

            # 待会要显示在屏幕上的字体
            font = cv2.FONT_HERSHEY_SIMPLEX

            # 如果检测到人脸
            if (len(faces) != 0):

                for i in range(len(faces)):
                    # enumerate方法同时返回数据对象的索引和数据,k为索引,d为faces中的对象
                    for k, d in enumerate(faces):
                        # 用红色矩形框出人脸
                        cv2.rectangle(imagecopy, (d.left(), d.top()), (d.right(), d.bottom()), (0, 255, 0))
                        # 计算人脸热别框边长
                        face_width = d.right() - d.left()

                        shape = predictor(image, d)
                        mouth_width = (shape.part(54).x - shape.part(48).x) / face_width  # 嘴巴咧开程度
                        mouth_higth = (shape.part(66).y - shape.part(62).y) / face_width  # 嘴巴张开程度
                        for i in range(68):
                            if i > 27:
                                cv2.circle(imagecopy, (shape.part(i).x, shape.part(i).y), 2, (0, 0, 255), -1, 2)

            self.showimg(imagecopy)
            QApplication.processEvents()
        cv2.imwrite(savepath + '/' + str(username) + '.jpg', image, [int(cv2.IMWRITE_JPEG_QUALITY), 100])
        val = (str(username),str(password))
        sql = 'insert into users (Name , Password) values (%s, %s)'
        self.add(sql, val)
        self.textEdit_1.setText('注册成功')

用户登录

plaintext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
def login(self):
    global inname
    username = self.textEdit_1.toPlainText()
    password = self.textEdit_2.toPlainText()
    # print(username,password)

    # 执行的都是原生SQL语句
    sql = '''
            SELECT Name, Password
            FROM   users
            '''
    has_name = 0
    for row in self.get(sql):
        if username == 'administrator' and username == str(row[0]):
            has_name = 1
            if password == str(row[1]):
                print('密码正确,登陆成功')
                inname = str(row[0])
                MainWindow.close()
                ui4.show()
                try:
                    show = cv2.imread('./images_db/' + str(inname) + '.jpg')
                    ui4.showimg(show)
                except:
                    pass
                ui4.printf('密码正确,登陆成功')
                ui4.printf('欢迎您,管理员')
            else:
                self.textEdit_2.setText('密码错误')
            break

        if username == str(row[0]):
            # print(row)
            has_name = 1
            if password == str(row[1]):
                print('密码正确,登陆成功')
                inname = str(row[0])
                MainWindow.close()
                ui2.show()
                try:
                    show = cv2.imread('./images_db/' + str(inname) + '.jpg')
                    ui2.showimg(show)
                except:
                    pass
                ui2.printf('密码正确,登陆成功')
                ui2.printf('欢迎您 ' + str(inname))
            else:
                self.textEdit_2.setText('密码错误')
            break

    if not has_name:
        self.textEdit_1.setText('该用户不存在')

5.用户考勤打卡&管理员考勤查看

10.png

11.png

6.管理员用户信息管理&MySQL增删改查

9.png

MySQL的主要优势

运行速度快,MySQL体积小,命令执行的速度快。
使用成本低。MySQL是开源的,且提供免费版本,对大多数用户来说大大降低了使用成本。
使用容易。与其他大型数据库的设置和管理相比,其复杂程度较低,易于使用。
可移植性强。MySQL能够运行与多种系统平台上,如windouws,Linux,Unix等。
适用更多用户。MySQL支持最常用的数据管理功能,适用于中小型企业甚至大型网站应用。

Mysql增删改查

代码实现如下

plaintext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
def get(self,sql):
    try:
        conn = MySQLdb.connect(
            host=host,  # 主机名
            user=user,  # 用户名
            passwd=passwd,  # 密码
            db=db)  # 数据库名

        # 查询前,必须先获取游标
        cursor = conn.cursor()
        cursor.execute(sql)
        results = cursor.fetchall()
        cursor.close()  # 关闭游标连接
        conn.close()  # 关闭数据库连接
        return results
    except Exception:
        print("查询失败")

def update(self,sql,cg):
    try:
        conn = MySQLdb.connect(
            host=host,  # 主机名
            user=user,  # 用户名
            passwd=passwd,  # 密码
            db=db)  # 数据库名

        # 查询前,必须先获取游标
        cursor = conn.cursor()
        cursor.execute(sql,cg)
        conn.commit()     # 提交
        cursor.close()  # 关闭游标连接
        conn.close()  # 关闭数据库连接

    except Exception:
        print("修改失败")

def add(self,sql,val):
    try:
        conn = MySQLdb.connect(
            host=host,  # 主机名
            user=user,  # 用户名
            passwd=passwd,  # 密码
            db=db)  # 数据库名

        # 查询前,必须先获取游标
        cursor = conn.cursor()
        cursor.execute(sql, val)
        conn.commit()     # 提交
        cursor.close()  # 关闭游标连接
        conn.close()  # 关闭数据库连接
    except Exception:
        print("插入失败")

def delete(self,sql):
    try:
        conn = MySQLdb.connect(
            host=host,  # 主机名
            user=user,  # 用户名
            passwd=passwd,  # 密码
            db=db)  # 数据库名

        # 查询前,必须先获取游标
        cursor = conn.cursor()
        cursor.execute(sql)
        conn.commit()
        cursor.close()  # 关闭游标连接
        conn.close()  # 关闭数据库连接
    except Exception:
        print("删除失败")

7.人脸校验&年龄、性别、民族识别(防替考&本人检测)

5.png

参考原博客提出的思路

代码实现

plaintext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
import cv2
import numpy as np
import face_recognition
from age_detection import f_my_age
from gender_detection import f_my_gender
from race_detection import f_my_race
from emotion_detection import f_emotion_detection
from my_face_recognition import f_main



# instanciar detectores
age_detector = f_my_age.Age_Model()
gender_detector =  f_my_gender.Gender_Model()
race_detector = f_my_race.Race_Model()
emotion_detector = f_emotion_detection.predict_emotions()
rec_face = f_main.rec()
#----------------------------------------------



def get_face_info(im):
    # face detection
    boxes_face = face_recognition.face_locations(im)
    out = []
    if len(boxes_face)!=0:
        for box_face in boxes_face:
            # segmento rostro
            box_face_fc = box_face
            x0,y1,x1,y0 = box_face
            box_face = np.array([y0,x0,y1,x1])
            face_features = {
                "name":[],
                "age":[],
                "gender":[],
                "race":[],
                "emotion":[],
                "bbx_frontal_face":box_face             
            } 

            face_image = im[x0:x1,y0:y1]

            # -------------------------------------- face_recognition ---------------------------------------
            face_features["name"] = rec_face.recognize_face2(im,[box_face_fc])[0]

            # -------------------------------------- age_detection ---------------------------------------
            age = age_detector.predict_age(face_image)
            face_features["age"] = str(round(age,2))

            # -------------------------------------- gender_detection ---------------------------------------
            face_features["gender"] = gender_detector.predict_gender(face_image)

            # -------------------------------------- race_detection ---------------------------------------
            face_features["race"] = race_detector.predict_race(face_image)

            # -------------------------------------- emotion_detection ---------------------------------------
            _,emotion = emotion_detector.get_emotion(im,[box_face])
            face_features["emotion"] = emotion[0]

            # -------------------------------------- out ---------------------------------------       
            out.append(face_features)
    else:
        face_features = {
            "name":[],
            "age":[],
            "gender":[],
            "race":[],
            "emotion":[],
            "bbx_frontal_face":[]             
        }
        out.append(face_features)
    return out



def bounding_box(out,img):
    for data_face in out:
        box = data_face["bbx_frontal_face"]
        if len(box) == 0:
            continue
        else:
            x0,y0,x1,y1 = box
            img = cv2.rectangle(img,
                            (x0,y0),
                            (x1,y1),
                            (0,0,255),2);
            thickness = 2
            fontSize = 0.6
            step = 13

            try:
                cv2.putText(img, "age: " +data_face["age"], (x0, y0-7), cv2.FONT_HERSHEY_SIMPLEX, fontSize, (0,255,0), thickness)
            except:
                pass
            try:
                cv2.putText(img, "gender: " +data_face["gender"], (x0, y0-step-15*1), cv2.FONT_HERSHEY_SIMPLEX, fontSize, (0,255,0), thickness)
            except:
                pass
            try:
                cv2.putText(img, "race: " +data_face["race"], (x0, y0-step-15*2), cv2.FONT_HERSHEY_SIMPLEX, fontSize, (0,255,0), thickness)
            except:
                pass
            try:
                cv2.putText(img, "emotion: " +data_face["emotion"], (x0, y0-step-15*3), cv2.FONT_HERSHEY_SIMPLEX, fontSize, (0,255,0), thickness)
            except:
                pass
            try:
                cv2.putText(img, "name: " +data_face["name"], (x0, y0-step-15*4), cv2.FONT_HERSHEY_SIMPLEX, fontSize, (0,255,0), thickness)
            except:
                pass
    return img

8.情绪&状态&表情识别(用户状态监测)

7.png

参考原博客提出的方案

背景

面部表情识别技术主要的应用领域包括人机交互、智能控制、安全、医疗、通信等领域。笔者目前接触过的两个领域,
第一个是商场门店的顾客情绪分析。即通过摄像头捕获商场或门店的顾客画面,分析其面部表情,再进一步解读出客人的情绪信息,从而分析顾客在商场的体验满意度。
第二个是人机交互。在笔者参与的一个教育辅助机器人项目中,负责视觉部分的研发工作,其中一项功能就是通过面部表情分析来判断机器人眼前的用户的情绪和心理。

代码实现

plaintext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
#encoding:utf-8
import pandas as pd
import numpy as np
import scipy.misc as sm
import os
 
emotions = {
    '0':'anger', #生气
    '1':'disgust', #厌恶
    '2':'fear', #恐惧
    '3':'happy', #开心
    '4':'sad', #伤心
    '5':'surprised', #惊讶
    '6':'normal', #中性
}
 
#创建文件夹
def createDir(dir):
    if os.path.exists(dir) is False:
        os.makedirs(dir)
 
def saveImageFromFer2013(file):
 
 
    #读取csv文件
    faces_data = pd.read_csv(file)
    imageCount = 0
    #遍历csv文件内容,并将图片数据按分类保存
    for index in range(len(faces_data)):
        #解析每一行csv文件内容
        emotion_data = faces_data.loc[index][0]
        image_data = faces_data.loc[index][1]
        usage_data = faces_data.loc[index][2]
        #将图片数据转换成48*48
        data_array = list(map(float, image_data.split()))
        data_array = np.asarray(data_array)
        image = data_array.reshape(48, 48)
 
        #选择分类,并创建文件名
        dirName = usage_data
        emotionName = emotions[str(emotion_data)]
 
        #图片要保存的文件夹
        imagePath = os.path.join(dirName, emotionName)
 
        # 创建“用途文件夹”和“表情”文件夹
        createDir(dirName)
        createDir(imagePath)
 
        #图片文件名
        imageName = os.path.join(imagePath, str(index) + '.jpg')
 
        sm.toimage(image).save(imageName)
        imageCount = index
    print('总共有' + str(imageCount) + '张图片')
 
 
if __name__ == '__main__':
    saveImageFromFer2013('fer2013.csv')

9.用户疲劳检测

6.png

参考原博客

(6条消息) Python基于OpenCV的工作疲劳检测系统[源码&UI界面&部署教程]_weixin__qunmasj的博客-CSDN博客

算法逻辑

定义 EYE_AR_THRESH。如果眼睛纵横比低于此阈值,我们将开始计算人闭上眼睛的帧数。
如果该人闭上眼睛的帧数超过 EYE_AR_CONSEC_FRAMES,我们将发出警报。

在实验中,我发现 0.3 的 EYE_AR_THRESH 在各种情况下都能很好地工作(尽管您可能需要为自己的应用程序自己调整它)。

我还将 EYE_AR_CONSEC_FRAMES 设置为 48 ,这意味着如果一个人连续闭眼 48 帧,我们将播放警报声。

您可以通过降低 EYE_AR_CONSEC_FRAMES 来使疲劳检测器更敏感——同样,您可以通过增加它来降低疲劳检测器的敏感度。

定义了 COUNTER,即眼睛纵横比低于 EYE_AR_THRESH 的连续帧的总数。

如果 COUNTER 超过 EYE_AR_CONSEC_FRAMES ,那么我们将更新布尔值 ALARM_ON。

plaintext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# start the video stream thread

print("[INFO] starting video stream thread...")

vs = VideoStream(src=args["webcam"]).start()

time.sleep(1.0)

# loop over frames from the video stream

while True:

    # grab the frame from the threaded video file stream, resize

    # it, and convert it to grayscale

    # channels)

    frame = vs.read()

    frame = imutils.resize(frame, width=450)

    gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)

    # detect faces in the grayscale frame

    rects = detector(gray, 0)

实例化 VideoStream。

暂停一秒钟,让相机传感器预热。

开始遍历视频流中的帧。

读取下一帧,然后我们通过将其大小调整为 450 像素的宽度并将其转换为灰度进行预处理。

应用 dlib 的人脸检测器来查找和定位图像中的人脸。

下一步是应用面部标志检测来定位面部的每个重要区域:

plaintext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# loop over the face detections

for rect in rects:

    # determine the facial landmarks for the face region, then

    # convert the facial landmark (x, y)-coordinates to a NumPy

    # array

    shape = predictor(gray, rect)

    shape = face_utils.shape_to_np(shape)

    # extract the left and right eye coordinates, then use the

    # coordinates to compute the eye aspect ratio for both eyes

    leftEye = shape[lStart:lEnd]

    rightEye = shape[rStart:rEnd]

    leftEAR = eye_aspect_ratio(leftEye)

    rightEAR = eye_aspect_ratio(rightEye)

    # average the eye aspect ratio together for both eyes

    ear = (leftEAR + rightEAR) / 2.0

循环遍历检测到的每个人脸——在我们的实现中(特别与司机睡意有关),我们假设只有一张脸——司机——但我把这个 for 循环留在这里以防万一你想应用多张脸视频的技术。

对于每个检测到的人脸,我们应用 dlib 的面部标志检测器并将结果转换为 NumPy 数组。

使用 NumPy 数组切片,我们可以分别提取左眼和右眼的 (x, y) 坐标。

给定双眼的 (x, y) 坐标,我们然后计算它们的眼睛纵横比。

代码实现

plaintext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
# check to see if the eye aspect ratio is below the blink

# threshold, and if so, increment the blink frame counter

if ear < EYE_AR_THRESH:

    COUNTER += 1

    # if the eyes were closed for a sufficient number of

    # then sound the alarm

    if COUNTER >= EYE_AR_CONSEC_FRAMES:

        # if the alarm is not on, turn it on

        if not ALARM_ON:

            ALARM_ON = True

            # check to see if an alarm file was supplied,

            # and if so, start a thread to have the alarm

            # sound played in the background

            if args["alarm"] != "":

                t = Thread(target=sound_alarm,

                    args=(args["alarm"],))

                t.deamon = True

                t.start()

        # draw an alarm on the frame

        frame=cv2ImgAddText(frame,"醒醒,别睡!",10,30,(255, 0, 0),30)

# otherwise, the eye aspect ratio is not below the blink

# threshold, so reset the counter and alarm

else:

    COUNTER = 0

    ALARM_ON = False

10.系统整合

下图源码&环境部署视频教程&自定义UI界面
1.png
参考博客《基于OpenCV的刷脸考勤&人脸校验&用户管理系统(源码&教程)》

11.参考文献

[1]刘淇缘,卢树华,兰凌强.遮挡人脸检测方法研究进展[J].计算机工程与应用.2020,(13).33-46.DOI:10.3778/j.issn.1002-8331.2003-0142.
[2]蒋纪威,何明祥,孙凯.基于改进YOLOv3的人脸实时检测方法[J].计算机应用与软件.2020,(5).200-204.DOI:10.3969/j.issn.1000-386x.2020.05.035.
[3]张文超,山世光,张洪明,等.基于局部Gabor变化直方图序列的人脸描述与识别[J].软件学报.2006,(12).2508-2517.
[4]王蕴红,朱勇,谭铁牛.基于虹膜识别的身份鉴别[J].自动化学报.2002,(1).1-10.
[5]梁路宏,艾海舟,徐光档,等.人脸检测研究综述[J].计算机学报.2002,(5).449-458.
[6]尹义龙,宁新宝,张晓梅.自动指纹识别技术的发展与应用[J].南京大学学报(自然科学版).2002,(1).29-35.
[7]孙冬梅,裘正定.生物特征识别技术综述[J].电子学报.2001,(z1).1744-1748.
[8]卢春雨,张长水,闻芳,等.基于区域特征的快速人脸检测法[J].清华大学学报(自然科学版).1999,(1).101-105.
[9]梁路宏,艾海舟,何克忠.基于多模板匹配的单人脸检测[J].中国图象图形学报.1999,(10).825-830.
[10]孙晓玲.人脸识别系统中眼睛定位方法的研究[D].2008
[11]山世光.人脸识别中若干关键问题的研究[D].2004
[12]刘伟强.基于级联卷积神经网络的人脸检测算法的研究[D].2017
[13]Li Liu,Wanli Ouyang,Xiaogang Wang,等.Deep Learning for Generic Object Detection: A Survey[J].International Journal of Computer Vision.2020,128(2).261-318.DOI:10.1007/s11263-019-01247-4.
[14]Lin, Tsung-Yi,Goyal, Priya,Girshick, Ross,等.Focal Loss for Dense Object Detection[J].IEEE Transactions on Pattern Analysis and Machine Intelligence.2020,42(2).318-327.DOI:10.1109/TPAMI.2018.2858826.
[15]Yang, Shuo,Luo, Ping,Loy, Chen Change,等.Faceness-Net: Face Detection through Deep Facial Part Responses.[J].IEEE Transactions on Pattern Analysis & Machine Intelligence.2018,40(8).1845-1859.
[16]Xiang,Zhang,Wei,Yang,Xiaolin,Tang,等.A Fast Learning Method for Accurate and Robust Lane Detection Using Two-Stage Feature Extraction with YOLO v3.[J].Sensors (Basel, Switzerland).2018,18(12).DOI:10.3390/s18124308.
[17]Kaipeng Zhang,Zhanpeng Zhang,Zhifeng Li,等.Joint Face Detection and Alignment Using Multitask Cascaded Convolutional Networks[J].IEEE signal processing letters.2016,(10).1499-1503.
[18]LeCun, Yann,Bengio, Yoshua,Hinton, Geoffrey.Deep learning[J].Nature.2015,521(May 28 TN.7553).436-444.DOI:10.1038/nature14539.
[19]Xuelong Li,Jing Pan,Yuan Yuan,等.Efficient HOG human detection[J].Signal Processing: The Official Publication of the European Association for Signal Processing.2011,91(4).
[20]Viola P,Jones MJ.Robust real-time face detection[J].International Journal of Computer Vision.2004,57(2).
[21]Giles C.L.,Lawrence S..Face recognition: a convolutional neural-network approach[J].IEEE Transactions on Neural Networks.1997,8(1).
[22]C. Lawrence Zitnick,Piotr Dollar.Edge Boxes: Locating Object Proposals from Edges[C].2014
[23]Sun, Yi,Wang, Xiaogang,Tang, Xiaoou.Deep Convolutional Network Cascade for Facial Point Detection[C].2013
[24]Zhou, Erjin,Fan, Haoqiang,Cao, Zhimin,等.Extensive Facial Landmark Localization with Coarse-to-Fine Convolutional Network Cascade[C].2013
[25]Neubeck, A.,Van Gool, L..Efficient Non-Maximum Suppression[C].2006
[26]T.F.Cootes,G.J.Edwards,C.J.Taylor.Active appearance models[C].1998
[27]Guanglu Song,Yu Liu,Ming Jiang,等.Beyond Trade-Off: Accelerate FCN-Based Face Detector with Higher Accuracy[C].
[28]Hao Wang,Yitong Wang,Zheng Zhou,等.CosFace: Large Margin Cosine Loss for Deep Face Recognition[C].
[29]Ranjan, R.,Patel, V.M.,Chellappa, R..A deep pyramid Deformable Part Model for face detection[C].
[30]Kaipeng Zhang,Zhanpeng Zhang,Zhifeng Li,等.Joint Face Detection and Alignment Using Multitask Cascaded Convolutional Networks[C].
[31]Weiyang Liu,Yandong Wen,Zhiding Yu,等.SphereFace: Deep Hypersphere Embedding for Face Recognition[C].
[32]Taigman, Y.,Ming Yang,Ranzato, M.,等.DeepFace: Closing the Gap to Human-Level Performance in Face Verification[C].
[33]Chenchen Zhu,Ran Tao,Khoa Luu,等.Seeing Small Faces from Robust Anchor’s Perspective[C].
[34]Jiangjing Lv,Xiaohu Shao,Junliang Xing,等.A Deep Regression Architecture with Two-Stage Re-initialization for High Performance Facial Landmark Detection[C].


如果您需要更详细的【源码和环境部署教程】,除了通过【系统整合】小节的链接获取之外,还可以通过邮箱以下途径获取:

1.请先在GitHub上为该项目点赞(Star),编辑一封邮件,附上点赞的截图、项目的中文描述概述(About)以及您的用途需求,发送到我们的邮箱

sharecode@yeah.net

2.我们收到邮件后会定期根据邮件的接收顺序将【完整源码和环境部署教程】发送到您的邮箱。

GitHub 6大热门实时人脸识别开源项目!哪个最适合初级开发者?_github上有哪些好的深度学习人脸表情识别图像分类项目-CSDN博客

mikel阅读(193)

来源: GitHub 6大热门实时人脸识别开源项目!哪个最适合初级开发者?_github上有哪些好的深度学习人脸表情识别图像分类项目-CSDN博客

实时人脸识别系统在计算机视觉领域仍然是一个非常热门的话题,许多公司已经开发了自己的解决方案来尝试进入不断增长的市场。 与传统的识别方法相比,实时人脸识别系统的优势在于在连续帧中使用同一个人的多个实例。

如果你希望利用实时人脸识别的优势,开源项目可能是一个很好的起点。 由于源代码已发布,你可以看到它是如何工作的,并确保它不会窃取你的数据。 在本文中,我们将帮助你挑选最佳开源人脸识别项目,并向你展示为什么选择开源软件通常是最佳选择。

为什么选择开源人脸识别项目?
开源软件有很多优点。 首先,使用开源代码,你可以确定数据的处理方式。 其次,开源项目通常质量更高。由于多个开发人员不断审查代码,因此可以非常快速地识别错误。 第三,许可费用较低,此类项目通常由内部开发或由可自由选择的 IT 服务提供商开发。

很难找到过时的开源软件,因为它通常遵循现代软件开发实践。 最后,开源被认为是代码成熟的下一个层次。 它允许开发人员在几分钟内流利地理解代码并激励他们进行工作。

最佳开源人脸识别软件
我们研究了实时开源人脸识别软件的 github 存储库,并准备了一份最佳选择列表:

1.Deepface

该库支持不同的人脸识别方法,如 FaceNet 和 InsightFace。它还提供了 REST API,但它只支持验证方法,因此你无法创建人脸集合并在其中查找人脸。尽管 Python 开发人员很容易上手,但其他人可能更难集成。截至 2021 年初的最新版本是 0.0.49。

2.CompreFace

这个解决方案是2020年7月才在github上发布的,看起来非常有前途。 CompreFace 成为我们最好的开源人脸识别项目名单,因为它是少数可以通过一个 docker-compose 命令启动的自托管 REST API 人脸识别解决方案之一。

REST API 允许你轻松地将其集成到你的系统中,而无需事先具备机器学习技能。此外,它是可扩展的,因此你可以同时识别多个视频流中的人脸。

CompreFace 有一个用于管理用户角色和面部集合的简单 UI。它提供了两种最流行的人脸识别方法之间的选择:FaceNet(LFW 准确率 99.65%)和 InsightFace(LFW 准确率 99.86%)。它仍处于活跃的开发阶段,截至 2021 年初的最新版本是 0.5 版本。

3.Face Recognition

这个解决方案的主要特点是它使用了他们的 Python API 和二进制命令行工具。 此外,他们的 github 上提供了所有主要平台的安装说明,甚至是用于快速设置的 docker 镜像。

尽管它很受欢迎,但该软件有一些缺点。上一次发布是在 2018 年,此后没有重大改进。它使用了一个相当过时的人脸识别模型,在 LFW 上的准确率只有 99.38%,并且没有 REST API。

4.InsightFace

InsightFace 是另一个开源 Python 库,它使用最新最准确的人脸识别方法之一进行人脸检测 (RetinaFace) 和人脸识别 (SubCenter-ArcFace)。 该解决方案的准确率非常高——在 LFW 数据集上为 99.86%。 唯一的缺点是它不易于使用。

5. FaceNet

FaceNet 是一个流行的开源 Python 库。 这种方法的准确率相当高——在 LFW 数据集上为 99.65%,这很好但不是最高的。 此解决方案的缺点是它没有 REST API,并且不再支持存储库(最后一次更新是在 2018 年 4 月)。

6.InsightFace-REST

这是 2019 年创建的另一个有前途的存储库,并于 2020 年 10 月开始积极开发。与 CompreFace 一样,这是一个基于 docker 的解决方案,提供方便的 REST API。最大的优势是它的开发人员将 InsightFace 的识别速度提高了三倍。

这个解决方案的缺点是它只提供人脸的嵌入,没有提供用于实际人脸识别的 API,所以你需要有自己的分类器。该存储库仍然没有许可证,因此你需要询问作者是否可以使用它。截至 2021 年初的最新版本是 v0.5.9.6。

结论
尽管当今 GitHub 上可用的最佳开源人脸识别项目在功能上各不相同,但它们都有可能让你的开发更轻松。

在选择开源人脸识别解决方案时,我们建议编制与你的业务相关的标准列表,并选择优先考虑你所做的相同事情的选项。虽然可能有些功能对你来说比其他功能更重要,但我们在此处确定的每个免费开源项目都将提供高质量的实时人脸识别体验。
————————————————

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。

原文链接:https://blog.csdn.net/TsingSee/article/details/120533615

省钱的开源项目「GitHub 热点速览」 - 削微寒 - 博客园

mikel阅读(135)

来源: 省钱的开源项目「GitHub 热点速览」 – 削微寒 – 博客园

本期,我从上周的热门开源项目中挑选了 5 个既省钱又省事,还好玩的开源项目。

首先,推荐的是省钱的电动汽车智能充电管理平台 evcc,它可以根据分时电价智能安排电动车充电时间,从而降低电费,如果你家还有太阳能充电和储能设备,evcc 更能最大限度地利用电能,让你的充电成本降到最低。说到省事,开源的 PaaS 平台 dokku,它可以让你轻松搭建一个类似 Heroku 的平台,又能帮你省下一笔可观的费用。再来看看在线 AI 证件照制作工具 HivisionIDPhotos,这款工具让你无需再花钱拍摄证件“大头照”,随时在线生成专业证件照,方便又实惠。

除此之外,还有两款特别好玩的开源项目。《暗黑破坏神》网页版 diabloweb,让你无需安装游戏就能在浏览器里重温经典;以及 SQLpage,通过 SQL 命令就能创建简单的网页,让你无需编写前端代码,同样省时省力。

  • 本文目录
    • 1. 开源热搜项目
      • 1.1 电动汽车智能充电管理平台:evcc
      • 1.2 基于 Docker 的开源 PaaS 平台:dokku
      • 1.3 一条 SQL 生成网页的工具:SQLpage
      • 1.4 在浏览器里玩《暗黑破坏神》:diabloweb
      • 1.5 轻量级的 AI 证件照制作工具:HivisionIDPhotos
    • 2. HelloGitHub 热评
      • 2.1 强大的终端日志文件查看工具:lnav
      • 2.2 游戏修改器管理工具:Game-Cheats-Manager
    • 3. 结尾

1. 开源热搜项目

1.1 电动汽车智能充电管理平台:evcc

主语言:GoStar:3.1k周增长:200

这是一个开源的 EV(电动汽车)充电器控制平台,为电动汽车车主提供灵活且易于安装的充电解决方案。它提供了可视化且适配移动端的 Web 平台,用户可以通过该平台远程启动、停止和监控车辆的充电状态。智能充电功能还可以根据电价、太阳储能和日程安排,智能安排充电时间,从而节约电费。平台支持多种充电设备和车辆型号,为家庭充电桩提供更加智能的控制。

GitHub 地址→github.com/evcc-io/evcc

1.2 基于 Docker 的开源 PaaS 平台:dokku

主语言:GoStar:28k周增长:1600

该项目是一款开源的 PaaS(平台即服务)平台,利用 Docker 容器进行应用程序的部署和运行。它提供了类似 Heroku 的命令行工具,让部署和管理应用程序的变得更加方便,支持从 Git 仓库自动部署应用、同时管理多个应用以及丰富的插件系统,可作为 Heroku 的开源替代品。

GitHub 地址→github.com/dokku/dokku

1.3 一条 SQL 生成网页的工具:SQLpage

主语言:RustStar:1.4k周增长:100

该项目是用 Rust 编写的基于 SQL 的 Web 应用构建工具。用户只需编写 SQL,就能自动生成可交互的 Web 界面,无需使用其他编程语言,让数据科学家和分析师能够方便地展示 SQL 的查询结果,是一种快速、简单的数据可视化方案,支持多种主流数据库。

GitHub 地址→github.com/lovasoa/SQLpage

1.4 在浏览器里玩《暗黑破坏神》:diabloweb

主语言:JavaScriptStar:2.6k周增长:300

该项目是将经典的动作角色扮演游戏《Diablo 1》(暗黑破坏神)移植到浏览器的开源项目。它通过 WebAssembly 技术,让用户无需安装即可以在浏览器重温这款经典游戏。

GitHub 地址→github.com/d07RiV/diabloweb

1.5 轻量级的 AI 证件照制作工具:HivisionIDPhotos

主语言:PythonStar:1.9k周增长:1.5k

这是一款简单易用的 AI 证件照制作工具,能够生成标准证件照和六寸排版照。它提供了简洁的 Web 界面和 API 服务,即使在没有 GPU 的电脑上也能够运行,支持抠图、尺寸调整和自定义底色等功能。

GitHub 地址→github.com/Zeyi-Lin/HivisionIDPhotos

2. HelloGitHub 热评

在本章节中,我们将为大家介绍本周 HelloGitHub 网站上的热门开源项目。同时,期待您与我们分享使用这些开源项目的心得与体验。

2.1 强大的终端日志文件查看工具:lnav

主语言:C++

这是一款用于查看和分析日志文件的轻量级工具。它无需配置、开箱即用,可自动识别日志格式并解压文件,支持同时处理多个文件和目录、实时更新、文本高亮、正则与 SQL 过滤日志等功能,特别适合在服务器和开发环境中使用。

项目详情→hellogithub.com/repository/f771d96afed44371b9c805d3cc8954c6

2.2 游戏修改器管理工具:Game-Cheats-Manager

主语言:Python

这是一款强大的游戏修改器管理工具,支持搜索、下载、启动、导入和更新游戏修改器等功能。

项目详情→hellogithub.com/repository/3ca6e8e23401477282ba72d2d8932311

3. 结尾

以上就是本期「GitHub 热点速览」的全部内容,希望你能够在这里找到自己感兴趣的开源项目,如果你有其他好玩、有趣的 GitHub 开源项目想要分享,欢迎来 HelloGitHub 与我们交流和讨论。

员工考勤打卡时,如何避免非本人代替打卡? - 华为云开发者联盟 - 博客园

mikel阅读(136)

来源: 员工考勤打卡时,如何避免非本人代替打卡? – 华为云开发者联盟 – 博客园

本文分享自华为云社区《员工考勤打卡时,如何避免非本人代替打卡?》,作者: HuaweiCloudDeveloper 。

1、背景

使用APP进行打卡时,为避免非本人及非真人现场打卡的情况出现,想结合华为云的人脸识别能力,通过调用API,达成可检测是否本人且真人现场打卡的效果。

2、云服务介绍

华为云FRS:人脸识别服务(Face Recognition Service),能够在图像中快速检测人脸、分析人脸关键点信息、获取人脸属性、实现人脸的精确比对和检索。该服务可应用于身份验证、电子考勤、客流分析等场景。

华为云FunctionGraph:函数工作流(FunctionGraph)是一项基于事件驱动的函数托管计算服务。通过函数工作流,只需编写业务函数代码并设置运行的条件,无需配置和管理服务器等基础设施,函数以弹性、免运维、高可靠的方式运行。

华为云APIG:API网关(API Gateway)是为企业开发者及合作伙伴提供的高性能、高可用、高安全的API托管服务, 帮助企业轻松构建、管理和部署不同规模的API。简单、快速、低成本、低风险的实现内部系统集成、成熟业务能力开放及业务能力变现。

华为云OBS: 对象存储服务(Object Storage Service,OBS)是一个基于对象的海量存储服务,为客户提供海量、安全、高可靠、低成本的数据存储能力,使用时无需考虑容量限制,并且提供多种存储类型供选择,满足客户各类业务场景诉求。

华为云DNS:云解析服务(Domain Name Service)提供高可用,高扩展的权威DNS服务和DNS管理服务,把人们常用的域名或应用资源转换成用于计算机连接的IP地址,从而将最终用户路由到相应的应用资源上。此服务默认开通,免费使用。

3 、方案设计

3.1 方案简述

通过APIG调用functiongraph函数,在functiongraph上完成人脸识别-活体检测、人脸识别-人脸比对等API的调用,并将响应结果通过API返回给APP。实现APP调用一次API即可完成人脸识别的功能。

  1. 人脸识别服务的人脸比对功能,可实现检测是否其本人打卡。
  2. 人脸识别服务的活体检测功能,可实现检测是否活人打卡。
  3. 使用Functiongraph的函数,APP端只需考虑调用一个API,且只需考虑人脸识别的总体输入和返回结果。
  4. Functiongraph由APIG来调用,利用APPkey、APPsecret及HTTPS,解决了APP端调用的安全认证等问题。
  5. OBS桶用来存储人脸照片,通过约定的用户标识做文件存储路径,易于管理和使用。

3.2 方案架构图

0、初始化:前置准备工作,新建一个OBS桶做人脸库,将员工的人脸照片存放到人脸库,并把以user-id 或自定义字段作为路径,标识员工。

1、员工登录APP后,进行人脸识别时,将通过APIG来调用functionGraph,上传关键信息:摄像头捕获的照片或视频、员工的人脸库标识(user-id)。

2、functionGraph调用活体检测API,传入照片/视频(根据需求选择动作活体检测/静默活体检测,推荐静默活体检测)。

注:本方案采用静默活体检测方式,APP端上传的照片提前转换为base64格式

3、活体检测API返回响应:有confidence、picture(base64)。

4、在functionGraph中调用OBS接口,通过user-id从OBS人脸库获取库中的员工照片。

5、通过代码将从OBS获取的照片文件,转换为base64格式。

6、将两个base64格式的照片作为输入参数调用人脸比对API 。(备注:此处可根据需求,是否需要多次调用API,使用多个照片进行验证)

7、人脸比对API返回包含了similarity的响应。

8、functionGraph将similarity、confidence传回给APP/后端。(备注:也可直接在functionGraph完成判定,返回人脸识别结果)

3.3 Functiongraph实现代码

代码附件:(附件请见文章最后)

代码时序图:

4、方案部署

4.1 部署流程图

4.2 前置准备

  • 拥有已实名认证的华为云账号,开通云服务functiongraph、人脸比对、活体检测、OBS
  • 注册公网域名,完成ICP备案

4.3、创建OBS人脸库

4.3.1 创建OBS桶

参考帮助文档:https://support.huaweicloud.com/qs-obs/obs_qs_0007.html创建私有桶

4.3.2 上传对象

参考帮助文档: https://support.huaweicloud.com/qs-obs/obs_qs_0008.html,上传对象,建立OBS人脸库。

要求:文件的路径使用用户标识(如userid)命名

4.4 Functiongraph搭建

4.4.1 创建委托

登录IAM控制台(https://console.huaweicloud.com/iam/?region=cn-north-4#/iam/agencies

1)创建委托

委托名称:自定义

委托类型:云服务

云服务:函数工作流functiongraph

持续时间:永久

2)选择策略

OBS:获取对象等基本操作权限

FRS:fullaccess

APIG:fullaccess

3)设置最小授权范围,此处选择所有,实际可根据项目情况分配。

4)完成委托创建

4.4.2 上传FRS依赖包

因functiongraph公共的依赖包中,FRS-SDK不是最新的(无静默活体检测API),故我们需上传最新的FRS-SDK,作为依赖包。

1)从官网获取FRS-SDK下载路径

https://sdkcenter.developer.huaweicloud.com/?language=python

2)下载整个Python-v3的SDK

https://github.com/huaweicloud/huaweicloud-sdk-python-v3

下载后解压,进入内部,找到frs后缀的SDK

进入SDK目录,在setup所在的目录,全部选择进行压缩。压缩成功后,需要确保setup文件在压缩包的根目录下

将压缩好的文件,上传到functiongraph的依赖包管理。

依赖包名称:自定义

运行时语言:2.7

描述:自定义

上传方式:上传ZIP文件

4.4.3 创建函数

1)进入functiongraph控制台创建函数。

Functiongraph版本:functiongraph v2

函数类型:事件函数

函数名称:自定义

所属应用:默认

委托名称:选择创建的委托(如无,请点击右边的 创建委托 前往创建,创建步骤参考4.4.1)

企业项目:自行选择

自定义函数:关闭

运行时语言:Python2.7

函数执行入口:Index.handler

代码上传方式:静默代码

4.4.4 编辑函数代码

将示例代码复制进来,编辑相关默认变量的值

1)粘贴3.3节的代码至index.py中

2)根据自己项目情况,设置默认Region、endpoint、buketname的值,若4.4.6节不设置环境变量的值,将默认取此处的默认值。如下默认是北京四

3)编辑完成后,点击保存

4.4.5 添加依赖包

1)在函数菜单-代码页,点击添加-依赖代码包

2)在公共依赖包,搜索obs,勾选OBS-sdk

3)在私有依赖包,勾选前面步骤上传的frs-sdk,然后确定保存

4.4.6 编辑环境变量

在函数菜单-配置页,添加环境变量:region、bucketname(OBS桶名)、endpoint

若此处不设置环境变量,则函数会使用4.4.4节代码设置的默认值。

4.4.7 调试函数

1)点击配置测试事件

2)选择apig的事件模板,添加body的内容和queryStringParameters的userid,进行保存。

3)点击测试,运行完毕可查看执行结果。

4.5 添加APIG

4.5.1 添加APIG触发器

1)在函数菜单-触发器页,点击创建触发器

触发器类型:API网关服务(APIG)

API名称:自定义

分组:选择API分组(如无点击右边 创建分组 进行创建)

发布环境:RELEASE(如无点击右边 创建发布环境 进行创建)

安全认证:测试环境可选择None(后面可编辑进行更改)

请求协议:测试环境可选择HTTP(后面可编辑进行更改)

后端超时(毫秒):5000

2)创建完成后,在触发器页面会添加一个APIG触发器,提供访问URL

4.5.2 编辑APIG

1)点击APIG触发器名称,前往APIG控制台,点击编辑

2)编辑基本信息

此处可更改安全认证,为方便调试,此处保持无认证

3)定义API请求

此处需添加API的请求参数-用户标识,用于functiongraph中,取在OBS人脸照片库中的员工照片。

4)定义后端服务

添加后端服务参数,跟前面的入参做一个映射。

因为是在functiongraph创建的APIG,故此处已自动绑定functiongraph的函数为后端服务,故基础定义保持默认即可。

5)返回结果基础定义

返回结果的响应示例,暂设置为空即可,点击完成。

4.5.3 调试API

1)API详情页,点击调试,跳转到API调试页面

2)输入相关请求参数,发起请求,进行调试。

4.5.4 发布API

编辑完成后的API,需要进行发布,公网才可访问

4.6 绑定独立域名

子域名仅供开发测试使用,每天最多访问1000次。如需开发服务,则需为API所在分组绑定独立域名。

4.6.1 添加记录集

1、登录云解析控制台(也可使用其他平台,已完成ICP备案的域名),选择域名解析》公网域名,点击需要创建记录集的域名名称。

2、添加记录集

填写以下信息

主机记录:域名前缀,如face-test

类型:选择CNAME – 将域名指向另外一个域名

别名:默认即可

线路类型:默认即可

TTL(秒):默认即可

值:填写要指向的别名(此处为APIG上的子域名)

添加成功

4.6.2 添加自定义域名

1、在API详情页-总览,点击添加增加自定义域名。

2、跳转到API所在分组的域名管理控制台,点击绑定独立域名

3、输入前面创建的记录集,点击确定。(如果是刚添加的记录集需刷新,约等5分钟)

添加完后,即可在公网通过自定义域名,访问APIG。

4.7 问题记录

1、并发测试API时,发现偶现以下错误

错误1:人脸比对传入的base64字符串无法识别

错误2:数据传输被提前终止了

问题定位:因函数中的存储到本地的文件用的是同一个路径,并发操作时出现异步的同时占用一个路径,从而导致文件有丢失或文件转码有误

解决方案:给函数中的文件路径配置上时间戳,避免并发操作时,交叉操作同一个文件。

2、APIG错误码请参考:https://support.huaweicloud.com/usermanual-apig/apig-ug-180530090.html

3、使用APIG触发functiongraph时,发现第一个api请求响应时间较长(2s多),后面的请求就较短了(约500ms)。

问题定位:超过一分钟无调用函数时,函数会销毁。再次进行函数调用时,需要重新启动实例,所以第一次调用时间会比较长。

解决方案:设置预留实例,来消除冷启动效果。预留实例是为指定函数版本单独预留的函数运行实例,不同于普通的函数实例,预留实例长期存活,可以达到消除函数冷启动的效果。

预留实例需要提交工单开通,详情请参考:https://support.huaweicloud.com/usermanual-functiongraph/functiongraph_01_0306.html

5、后期思考

本方案的人脸比对,只比对一次。若人脸库中,用户的库照片有多个,是否需要遍历对比,取总体对比的结果。如对比多次,需要考虑从OBS获取照片、人脸比对的API要多次调用,性能下降、费用提升等。

附件:index.zip2.62KB

NET Core 多身份校验与策略模式 - 无昵称老炮儿 - 博客园

mikel阅读(129)

来源: NET Core 多身份校验与策略模式 – 无昵称老炮儿 – 博客园

背景需求:

系统需要对接到XXX官方的API,但因此官方对接以及管理都十分严格。而本人部门的系统中包含诸多子系统,系统间为了稳定,程序间多数固定Token+特殊验证进行调用,且后期还要提供给其他兄弟部门系统共同调用。

原则上:每套系统都必须单独接入到官方,但官方的接入复杂,还要官方指定机构认证的证书等各种条件,此做法成本较大。

so:

为了解决对接的XXX官方API问题,我们搭建了一套中继系统,顾名思义:就是一套用于请求中转的中继系统。在系统搭建的时,Leader提出要做多套鉴权方案,必须做到 动静结合 身份鉴权。

动静结合:就是动态Token 和 静态固定Token。

动态Token:用于兄弟部门系统或对外访问到此中继系统申请的Token,供后期调用对应API。

固定Token:用于当前部门中的诸多子系统,提供一个超级Token,此Token长期有效,且不会随意更换。

入坑:

  因为刚来第一周我就接手了这个项目。项目处于申请账号阶段,即将进入开发。对接的是全英文文档(申请/对接流程/开发API….),文档复杂。当时我的感觉:OMG,这不得跑路?整个项目可谓难度之大。然后因为对内部业务也不熟悉,上手就看了微服务等相关系统代码,注:每套系统之间文档少的可怜,可以说系统无文档状态

项目移交的时候,Leader之说让我熟悉并逐渐进入开发,让我请教同事。好嘛,请教了同事。同事也是接了前任离职的文档而已,大家都不是很熟悉。于是同事让我启新的项目也是直接对接微服务形式开发,一顿操作猛如虎。

项目开发第二周,已经打出框架模型并对接了部分API。此时,Leader开会问进度,结果来一句:此项目使用独立API方式运行,部署到Docker,不接入公司的微服务架构。好嘛,几天功夫白费了,真是取其糟粕去其精华~,恢复成WebAPI。

技术实现:

  因为之前对身份认证鉴权这一块没有做太多的深入了解,Leader工期也在屁股追,就一句话:怎么快怎么来,先上后迭代。好嘛,为了项目方便,同时为了符合动静结合的身份认证鉴权 。于是,我用了 JWT+自定义身份认证 实现了需求。

方案一:多身份认证+中间件模式实现

添加服务:Services.AddAuthentication 默认使用JWT

 //多重身份认证
//默认使用JWT,如果Controller使用 AuthenticationSchemes 则采用指定的身份认证
Services.AddAuthentication(options =>
{
    options.AddScheme<CustomAuthenticationHandler>(CustomAuthenticationHandler.AuthenticationSchemeName, CustomAuthenticationHandler.AuthenticationSchemeName);
    options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
    options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
    options.RequireHttpsMetadata = false;//设置元数据地址或权限是否需要HTTPs
    options.SaveToken = true;
    options.TokenValidationParameters = new TokenValidationParameters
    {
        ValidateIssuer = true,
        ValidateAudience = true,
        ValidateLifetime = true,
        ValidateIssuerSigningKey = true,
        ValidIssuer = builder.Configuration["Jwt:Issuer"],
        ValidAudience = builder.Configuration["Jwt:Audience"],
        IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(builder.Configuration["Jwt:SecretKey"]!))
    };
    options.Events = new CustomJwtBearerEvents();
});

自定义身份认证 CustomAuthenticationHandler.cs代码

    public class CustomAuthenticationHandler : AuthenticationHandler<AuthenticationSchemeOptions>
    {
        public const string AuthenticationSchemeName = "CustomAuthenticationHandler";
        private readonly IConfiguration _configuration;
        public CustomAuthenticationHandler(
            IOptionsMonitor<AuthenticationSchemeOptions> options,
            ILoggerFactory logger,
            UrlEncoder encoder,
            ISystemClock clock,
            IConfiguration configuration)
            : base(options, logger, encoder, clock)
        {
            _configuration = configuration;
        }
        /// <summary>
        /// 固定Token认证
        /// </summary>
        /// <returns></returns>
        protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
        {
            string isAnonymous = Request.Headers["IsAnonymous"].ToString();
            if (!string.IsNullOrEmpty(isAnonymous))
            {
                bool isAuthenticated = Convert.ToBoolean(isAnonymous);
                if (isAuthenticated)
                    return AuthenticateResult.NoResult();
            }

            string authorization = Request.Headers["Authorization"].ToString();
            // "Bearer " --> Bearer后面跟一个空格
            string token = authorization.StartsWith("Bearer ") ? authorization.Remove(0, "Bearer ".Length) : authorization;
            if (string.IsNullOrEmpty(token))
                return AuthenticateResult.Fail("请求头Authorization不允许为空。");

            //通过密钥,进行加密、解密对比认证
            if (!VerifyAuthorization(token))
                return AuthenticateResult.Fail("传入的Authorization身份验证失败。");


            return AuthenticateResult.Success(GetTicket());
        }
        private AuthenticationTicket GetTicket()
        {
            // 验证成功,创建身份验证票据
            var claims = new[]
            {
                new Claim(ClaimTypes.Role, "Admin"),
                new Claim(ClaimTypes.Role, "Public"),
            };
            var identity = new ClaimsIdentity(claims, Scheme.Name);
            var principal = new ClaimsPrincipal(identity);
            var ticket = new AuthenticationTicket(principal, new AuthenticationProperties(), this.Scheme.Name);
            return ticket;
        }
        private bool VerifyAuthorization(string token)
        {
            //token: [0]随机生成64位字符串,[1]载荷数据,[2]采用Hash对[0]+[1]的签名
            var tokenArr = token.Split('.');
            if (tokenArr.Length != 3)
            {
                return false;
            }
            try
            {
                //1、先比对签名串是否一致
                string signature = tokenArr[1].Hmacsha256HashEncrypt().ToLower();
                if (!signature.Equals(tokenArr[2].ToLower()))
                {
                    return false;
                }

                //解密
                var aecStr = tokenArr[1].Base64ToString();
                var clientId = aecStr.DecryptAES();
                //2、再验证载荷数据的有效性
                var clientList = _configuration.GetSection("FixedClient").Get<List<FixedClientSet>>();
                var clientData = clientList.SingleOrDefault(it => it.ClientID.Equals(clientId));
                if (clientData == null)
                {
                    return false;
                }
            }
            catch (Exception)
            {
                throw;
            }

            return true;
        }
    }

使用中间件:UseMiddleware

app.UseAuthentication();
//中间件模式:自定义认证中间件:双重认证选其一
//如果使用 策略,需要注释掉 中间件
app.UseMiddleware<FallbackAuthenticationMiddleware>(); //使用中间件实现
app.UseAuthorization();

中间件FallbackAuthenticationMiddleware.cs代码实现

   public class FallbackAuthenticationMiddleware
  {
      private readonly RequestDelegate _next;
      private readonly IAuthenticationSchemeProvider _schemeProvider;

      public FallbackAuthenticationMiddleware(RequestDelegate next, IAuthenticationSchemeProvider schemeProvider)
      {
          _next = next;
          _schemeProvider = schemeProvider;
      }
      /// <summary>
      /// 身份认证方案
      /// 默认JWT。JWT失败,执行自定义认证
      /// </summary>
      /// <param name="context"></param>
      /// <returns></returns>
      public async Task InvokeAsync(HttpContext context)
      {
          var endpoints = context.GetEndpoint();
          if (endpoints == null || !endpoints.Metadata.OfType<IAuthorizeData>().Any() || endpoints.Metadata.OfType<IAllowAnonymous>().Any())
          {
              await _next(context);
              return;
          }

          //默认JWT。JWT失败,执行自定义认证
          var result = await Authenticate_JwtAsync(context);
          if (!result.Succeeded)
              result = await Authenticate_CustomTokenAsync(context);

          // 设置认证票据到HttpContext中 
          if (result.Succeeded)
              context.User = result.Principal;

          await _next(context);
      }
      /// <summary>
      /// JWT的认证
      /// </summary>
      /// <param name="context"></param>
      /// <returns></returns>
      private async Task<dynamic> Authenticate_JwtAsync(HttpContext context)
      {
          var verify = context.User?.Identity?.IsAuthenticated ?? false;
          string authenticationType = context.User.Identity.AuthenticationType;
          if (verify && authenticationType != null)
          {
              return new { Succeeded = verify, Principal = context.User, Message = "" };
          }

          await Task.CompletedTask;

          // 找不到JWT身份验证方案,或者无法获取处理程序。
          return new { Succeeded = false, Principal = new ClaimsPrincipal { }, Message = "JWT authentication scheme not found or handler could not be obtained." };
      }

      /// <summary>
      /// 自定义认证
      /// </summary>
      /// <param name="context"></param>
      /// <returns></returns>
      private async Task<dynamic> Authenticate_CustomTokenAsync(HttpContext context)
      {
          // 自定义认证方案的名称
          var customScheme = "CustomAuthenticationHandler";

          var fixedTokenHandler = await context.RequestServices.GetRequiredService<IAuthenticationHandlerProvider>().GetHandlerAsync(context, customScheme);
          if (fixedTokenHandler != null)
          {
              var Res = await fixedTokenHandler.AuthenticateAsync();
              return new { Res.Succeeded, Res.Principal, Res.Failure?.Message };
          }

          //找不到CustomAuthenticationHandler身份验证方案,或者无法获取处理程序。
          return new { Succeeded = false, Principal = new ClaimsPrincipal { }, Message = "CustomAuthenticationHandler authentication scheme not found or handler could not be obtained." };

      }
  }

方案二:通过[Authorize]标签的AuthenticationSchemes
因为中间件还要多维护一段中间件的代码,显得略微复杂,于是通过[Authorize(AuthenticationSchemes = “”)]方式。

     //使用特定身份认证    
    //[Authorize(AuthenticationSchemes = CustomAuthenticationHandler.AuthenticationSchemeName)]
    //任一身份认证
    [Authorize(AuthenticationSchemes = $"{CustomAuthenticationHandler.AuthenticationSchemeName},{JwtBearerDefaults.AuthenticationScheme}")]
    public class DataProcessingController : ControllerBase
    {
    }

方案二:通过[Authorize]标签的policy

如果还有其他身份认证,那不断增加AuthenticationSchemes拼接在Controller的头顶,显得不太好看,且要是多个Controller使用,也会导致维护麻烦,于是改用策略方式。

在Program.cs添加服务AddAuthorization。使用策略的好处是增加易维护性。

 //授权策略
//Controller使用 policy 则采用指定的策略配置进行身份认证
builder.Services.AddAuthorization(option =>
{
    option.AddPolicy(CustomPolicy.Policy_A, policy => policy
            .RequireAuthenticatedUser()
            .AddAuthenticationSchemes(CustomAuthenticationHandler.AuthenticationSchemeName, JwtBearerDefaults.AuthenticationScheme)
            );

    option.AddPolicy(CustomPolicy.Policy_B, policy => policy
            .RequireAuthenticatedUser()
            .AddAuthenticationSchemes(CustomAuthenticationHandler.AuthenticationSchemeName)
            );

    option.AddPolicy(CustomPolicy.Policy_C, policy => policy
            .RequireAuthenticatedUser()
            .AddAuthenticationSchemes(JwtBearerDefaults.AuthenticationScheme)
            );
});
     //使用特定策略身份认证
    [Authorize(policy:CustomPolicy.Policy_B)]
    public class DataProcessingController : ControllerBase
    {
    }
     /// <summary>
    /// 策略类
    /// </summary>
    public static class CustomPolicy
    {
        public const string Policy_A= "Policy_A";

        public const string Policy_B = "Policy_B";

        public const string Policy_C = "Policy_C";
    }

最后附上截图:

添加服务:

使用中间件:

控制器:

这样,整套中继系统就能完美的满足Leader的需求,且达到预期效果。

源码Demo:https://gitee.com/LaoPaoE/project-demo.git
最后附上:

AuthorizeAttribute 同时使用 Policy 和 AuthenticationSchemes 和 Roles 时是怎么鉴权的流程:

  1. AuthenticationSchemes鉴权:
    • AuthenticationSchemes 属性指定了用于验证用户身份的认证方案(如Cookies、Bearer Tokens等)。
    • ASP.NET Core会根据这些认证方案对用户进行身份验证。如果用户未通过身份验证(即未登录或未提供有效的认证信息),则请求会被拒绝,并可能重定向到登录页面。
  2. Roles鉴权(如果指定了Roles):
    • 如果AuthorizeAttribute中还指定了 Roles 属性,那么除了通过身份验证外,用户还必须属于这些角色之一。
    • ASP.NET Core会检查用户的角色信息,以确定用户是否属于 Roles  属性中指定的一个或多个角色。
  3. Policy鉴权(如果指定了Policy):
    • Policy 属性指定了一个或多个授权策略,这些策略定义了用户必须满足的额外条件才能访问资源。
    • ASP.NET Core会调用相应的 IAuthorizationHandler 来评估用户是否满足该策略中的所有要求。这些要求可以基于角色、声明(Claims)、资源等定义。
    • 如果用户不满足策略中的任何要求,则授权失败,并返回一个HTTP 403 Forbidden响应。

鉴权顺序和组合

  • 通常,AuthenticationSchemes的验证会首先进行,因为这是访问任何受保护资源的前提。
  • 如果AuthenticationSchemes验证通过,接下来会根据是否指定了Roles和Policy来进一步进行鉴权。
  • Roles和Policy的鉴权顺序可能因ASP.NET Core的具体版本和配置而异,但一般来说,它们会作为独立的条件进行评估。
  • 用户必须同时满足AuthenticationSchemes、Roles(如果指定)和Policy(如果指定)中的所有条件,才能成功访问受保护的资源。

注意事项

  • 在某些情况下,即使AuthenticationSchemes和Roles验证都通过,但如果Policy中的要求未得到满足,用户仍然无法访问资源。
  • 可以通过自定义 IAuthorizationRequirement 和 IAuthorizationHandler 来实现复杂的授权逻辑,以满足特定的业务需求。
  • 确保在应用程序的身份验证和授权配置中正确设置了AuthenticationSchemes、Roles和Policy,以便它们能够协同工作,提供有效的访问控制。