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

mikel阅读(141)

来源: 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阅读(125)

来源: 基于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阅读(142)

来源: 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阅读(103)

来源: 省钱的开源项目「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阅读(107)

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

本文分享自华为云社区《员工考勤打卡时,如何避免非本人代替打卡?》,作者: 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阅读(99)

来源: 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,以便它们能够协同工作,提供有效的访问控制。

推荐一款开源一站式SQL审核查询平台!功能强大、安全可靠! - 狂师 - 博客园

mikel阅读(146)

来源: 推荐一款开源一站式SQL审核查询平台!功能强大、安全可靠! – 狂师 – 博客园

1、前言

在当今这个数据驱动的时代,数据库作为企业核心信息资产的载体,其重要性不言而喻。随着企业业务规模的不断扩大,数据库的数量和种类也日益增多,这对数据库的管理与运维工作提出了前所未有的挑战。在这样的背景下,一款高效、易用的数据库管理工具显得尤为重要。Archery,作为一款开源的数据库管理平台,以其独特的魅力成为了众多开发者和运维工程师的得力助手,如同古代战场上的利箭,精准而有力地击中了数据库管理的痛点。

2、Archery介绍

Archery是一个基于Python Flask开发的数据库管理平台,它支持多种数据库(如MySQL、PostgreSQL、MariaDB、Redis等),提供了包括数据库查询、管理、权限控制、SQL审核、数据备份与恢复等功能在内的全方位数据库管理解决方案。其设计初衷是为了简化数据库管理流程,提高运维效率,同时保证数据的安全性和可靠性。

项目地址

https://github.com/hhyo/Archery
https://gitee.com/rtttte/Archery

3、核心功能解析

  1. 数据库管理
    Archery通过直观的Web界面,允许用户轻松管理多个数据库实例。用户可以添加、删除、修改数据库连接信息,实时查看数据库状态,以及执行诸如数据库备份、恢复、优化等高级操作。这一功能极大地降低了数据库管理的门槛,使得即便是非专业的运维人员也能快速上手。
  2. SQL查询与审核
    Archery内置了强大的SQL编辑器,支持语法高亮、自动补全等功能,极大提升了SQL编写和调试的效率。同时,它还提供了SQL审核功能,通过预设的规则库对SQL语句进行自动化审查,帮助用户及时发现并纠正潜在的风险,保障数据库操作的安全性和合规性。
  3. 权限控制
    Archery支持细粒度的权限控制策略,可以针对不同的用户或用户组设置不同的操作权限。这一功能有效防止了数据泄露和误操作的风险,保障了数据的安全性和隐私性。
  4. 数据备份与恢复
    数据备份是数据库运维中不可或缺的一环。Archery提供了自动化的数据备份和恢复功能,用户可以自定义备份策略,实现定时备份、增量备份等需求。同时,当数据发生丢失或损坏时,用户可以迅速通过备份文件恢复数据,保障业务的连续性。
  5. 可视化监控
    Archery还集成了数据库性能监控功能,通过图表和报告的形式展示数据库的运行状态、性能指标等关键信息。这使得运维人员能够实时监控数据库的健康状况,及时发现并解决潜在的问题。

4、优势与应用场景

优势

  • 开源免费:Archery作为一款开源项目,其源代码完全公开,用户可以自由下载、使用并根据实际需求进行修改和扩展。
  • 易于部署:Archery支持Docker等容器化部署方式,简化了部署流程,降低了部署门槛。
  • 功能全面:涵盖了数据库管理的各个方面,满足了从日常运维到高级管理的各种需求。
  • 安全性高:通过权限控制、SQL审核等功能保障了数据的安全性和合规性。

应用场景

  • 中小型企业:对于资源有限的中小型企业而言,Archery提供了一种低成本、高效率的数据库管理解决方案。
  • 互联网企业:面对海量数据和复杂的数据库环境,Archery的自动化管理和监控功能能够显著提升运维效率。
  • 金融、医疗等敏感行业:这些行业对数据的安全性和合规性要求极高,Archery的权限控制和SQL审核功能能够有效保障数据的安全性。

5、安装与使用

Archery提供了容器化部署和非容器化部署两种模式,部署较为简单,在此不过多介绍,可参照如下部署文档即可

1、容器化部署:

https://github.com/hhyo/archery/wiki/docker

2、非容器化部署:

https://github.com/hhyo/archery/wiki/manual

3、在线体验地址 : https://demo.archerydms.com/
在这里插入图片描述
账号和密码为:archer/archer

6、小结

Archery作为一款开源的数据库管理平台,以其丰富的功能、易于部署的特点和强大的性能赢得了广泛的关注和好评。它如同一把精准的利箭,穿透了数据库管理的重重迷雾,为数据库的管理与运维工作带来了前所未有的便利和效率。未来,随着技术的不断进步和应用的不断深化,Archery有望成为更多企业和个人首选的数据库管理工具。

使用C#爬取快手作者主页,并下载视频/图集(附源码及软件下载链接) - 猫叔Vincent - 博客园

mikel阅读(188)

来源: 使用C#爬取快手作者主页,并下载视频/图集(附源码及软件下载链接) – 猫叔Vincent – 博客园

最近发现一些快手的作者,作品还不错,出于学习研究的目的,决定看一下怎么爬取数据。现在网上有一些爬虫工具,不过大部分都失效了,或者不开源。于是自己就写了一个小工具。先看一下成果:
image
image
软件只需要填写作者uid以及网页版的请求Cookie,即可实现自动下载,下载目录在程序根目录下的Download文件夹。
由于快手的风控比较厉害,软件也做了应对措施。不过需要用户点击软件中的提示文字,复制粘贴到浏览器,把请求的json保存到本地文件。使用软件提供的解析本地json按钮解析下载即可。如果返回的json文件很短或者没有数据,需要在快手的任意一个页面刷新一下,也就是告诉快手风控,现在是正常浏览,没有机器人的行为。

下面说一下构建整个App的思路。

1. 快手网页端准备

  1. 打开https://live.kuaishou.com/ ,在顶部搜索你要爬取的作者昵称,进入作者主页。也可以从App端分享作者的主页链接,粘贴进来。作者主页加载完成后,地址栏的地址一定要是类似:https://live.kuaishou.com/profile/xxxxxx。 后面的xxxxxx就是作者的user id。这个记住,复制出来,后面会用到。
  2. 按F12打开浏览器的开发者工具(我之前就说过开发者工具是好东西,研究爬虫必备,一定要好好学习)。
  3. 选择开发者工具顶部的“网络”,“全部”,如图所示。在请求列表中找到user id,点击它,右面就会出来请求的标头。里面有个Cookie,需要记住,复制出来。如果没有的话,记得刷新页面。
    image
  4. 在列表里面可以看到很多请求,我们需要从中找到网页端展示作品列表的那条请求,即public开头的,或者直接在左上角搜索public,即可过滤绝大部分无关请求。这个请求的响应数据里面有作者作品的完整json响应。
    image

你可以右击它,在新标签页面打开,打开后地址栏会显示完成的浏览器请求地址。这个网址需要记住,后续会用到。那个count默认是12或者20,我们用到时候,直接拉满,9999即可。
image
image

2. Postman拦截请求,模拟请求,并生成C#请求代码

  1. 安装postman interceptor拦截器,安装地址https://chromewebstore.google.com/detail/postman-interceptor/aicmkgpgakddgnaphhhpliifpcfhicfo 不得不说,这又是一个神器,搭配开发者工具,理论上可以搞定几乎所有的爬虫需求了。
  2. 打开Postman,点击右下角的Start Proxy,
    image
    开启拦截后,重新回到网页版作者主页,刷新一下页面,等页面加载完成后,点击停止拦截。否则列表会一直增多,因为他会拦截电脑的所有网络请求。这时Postman拦截器就会拦截到一大堆请求,同理,找到public请求,或者在左上角输入public,即可过滤出来我们需要的。
    image
    点击这个请求链接
    image
    这是Postman会打开一个新的窗口,包含了请求这个链接的所有参数以及标头信息。
    image
    点击Postman最右面的代码工具即可生成我们需要的代码。你可以选择C#、python、js、curl等等。
    image

3. 使用WPF写界面以及下载逻辑

  1. 新建WPF工程,为了界面好看,这次我用了开源的WPF UI,之前用过HandyControl、MicaWPF,这些都是不错的UI控件库。
    下载使用了开源的Downloader,请求使用了RestSharp,解析Json使用NewtonsoftJson,另外推荐一个免费的图标库FlatIcon。
    界面如下:
点击查看代码
  1. 后台逻辑没有使用MVVM,就是图方便。
点击查看代码
  1. 下载类,下载完文件后,将文件的日志修改为发表日志,方便排序以及数据分析。
点击查看代码
  1. 源码分享
    完整版代码已上传到Github https://github.com/hupo376787/KuaishouDownloader ,喜欢的点一下Star谢谢。

4. 下载使用

打开https://github.com/hupo376787/KuaishouDownloader/releases/tag/1.0,点击下载zip文件,解压缩后,就可以像开头那样使用了。
image
image

ThinkPHP5中find()和select()区别 - 范仁义 - 博客园

mikel阅读(108)

来源: ThinkPHP5中find()和select()区别 – 范仁义 – 博客园

读取数据是指读取数据表中的一行数据(或者关联数据),主要通过find方法完成,例如:

1
2
3
4
$User = M("User"); // 实例化User对象
// 查找status值为1name值为think的用户数据 
$data $User->where('status=1 AND name="thinkphp"')->find();
dump($data);

find方法查询数据的时候可以配合相关的连贯操作方法,其中最关键的则是where方法,如何使用where方法我们会在查询语言章节中详细描述。

如果查询出错,find方法返回false,如果查询结果为空返回NULL,查询成功则返回一个关联数组(键值是字段名或者别名)。 如果上面的查询成功的话,会输出:

1
2
3
4
array (size=3)
  'name' => string 'thinkphp' (length=8)
  'email' => string 'thinkphp@gmail.com' (length=18)
  'status'=> int 1

即使满足条件的数据不止一个,find方法也只会返回第一条记录(可以通过order方法排序后查询)。

还可以用data方法获取查询后的数据对象(查询成功后)

1
2
3
4
$User = M("User"); // 实例化User对象
// 查找status值为1name值为think的用户数据 
$User->where('status=1 AND name="thinkphp"')->find();
dump($User->data());

读取数据集

读取数据集其实就是获取数据表中的多行记录(以及关联数据),使用select方法,使用示例:

1
2
3
$User = M("User"); // 实例化User对象
// 查找status值为1的用户数据 以创建时间排序 返回10条数据
$list $User->where('status=1')->order('create_time')->limit(10)->select();

如果查询出错,select的返回值是false,如果查询结果为空,则返回NULL,否则返回二维数组。

复制代码
$about=M('document');
$abouts=$about->where('id=2')->select();
$abouts2=$about->where('id=2')->find();
var_dump($abouts);
var_dump($abouts2);
复制代码

输出结果:

复制代码
array (size=1)
  0 => 
    array (size=24)
      'id' => string '2' (length=1)
      'uid' => string '1' (length=1)
      'name' => string '' (length=0)
      'title' => string '公司简介' (length=12)
      'category_id' => string '39' (length=2)
      'group_id' => string '0' (length=1)
      'description' => string '公司简介公司简介公司简介公司简介公司简介公司简介公司简介公司简介公司简介公司简介公司简介公司简介公司简介' (length=156)
      'root' => string '0' (length=1)
      'pid' => string '0' (length=1)
array (size=24)
  'id' => string '2' (length=1)
  'uid' => string '1' (length=1)
  'name' => string '' (length=0)
  'title' => string '公司简介' (length=12)
  'category_id' => string '39' (length=2)
  'group_id' => string '0' (length=1)
  'description' => string '公司简介公司简介公司简介公司简介公司简介公司简介公司简介公司简介公司简介公司简介公司简介公司简介公司简介' (length=156)
  'root' => string '0' (length=1)
  'pid' => string '0' (length=1)
复制代码

从上面的代码可以看出,find()返回一个一维数组select()返回一个二维数组,所以在取值时有所不同,一维数组取值用data[id]data[“id”],二维数组取值用data[0][“id”],由于一开始没了解这个用法,调试一天也取不值,最后有var_dump()方法才看到两个方法的不同所在!

复制代码
        $about=M('document');
        $abouts=$about->where('id=2')->select();
        $abouts2=$about->where('id=2')->find();
//        var_dump($abouts);
//        var_dump($abouts2);
         if($abouts){
            $article = M('document_article');
            //$info = $article->find($abouts['id']);
            $info=$article->where('id='.$abouts2['id'])->find();
        }
        $this->assign('wzjj',$info);
复制代码

 

这是DDD建模最难的部分(其实很简单) - 老肖想当外语大佬 - 博客园

mikel阅读(134)

来源: 这是DDD建模最难的部分(其实很简单) – 老肖想当外语大佬 – 博客园

本文书接上回《为了落地DDD,我是这样“PUA”大家的》 ,欢迎关注公众号(老肖想当外语大佬),加群、获取最新文章更新和DDD框架源码,视频和直播在B站。
https://mp.weixin.qq.com/s/DjC0FSWY1bgJyLPIND5evA
 

什么是最重要的事

如果你认真读过前面的文章,那么一定知道我们的核心逻辑:领域驱动是一种价值观,这个价值观是:“领域(边界)”的明确是软件设计掌控复杂度最重要的事。
那么整个软件交付过程中,架构师的职责就是持续保持“需求”、“模型”、“代码”三者的边界范围明确且一致。再回过头看整个软件交付存在的意义,就是为了满足需求,因此本质上来说,“需求”的边界就决定了一切,那么结论就不言而喻了:
最重要的事,就是需求的边界(范围)。
 

什么叫边界明确

假如说我有两个需求A和B,那么我们建模的时候就有如下几种结果,大家感受一下,通常情况下,自己给出的结果是哪个选项?哪个选项又是最理想的?
 

我相信大部分人都会认同,其中B和D是符合边界明确、需求与模型边界一致的原则的,而现实的情况大部分结果是A和C,各种各样的“join”充斥着系统的各个角落,一个典型的例子,就是用户-角色系统的设计。
用户-角色系统,通常会有这样几个关键需求:
  1. 创建用户
  2. 创建角色
  3. 为用户设置角色
  4. 查看用户有哪些角色
  5. 查看一个角色包含多少个用户
我们用最传统的设计方法来做,模型大体是这样的:
 

这样的结果是不是对应到了前面选项A和C比较类似?因为“用户聚合”与“角色聚合”连线的存在,导致需求与模型的边界不一致。
而我们要做的,就是在满足所有需求的同时,消除打破边界的连线。

如何操作

首先我们分析上图,假如我们把下面几个需求先去掉:
  1. 为用户设置角色
  2. 查看用户有哪些角色
  3. 查看一个角色包含多少个用户
那么我们会得到一个符合边界明确原则的设计:
 

然后我们再思考,下面两个需求,应该哪个聚合负责:
  1. 为用户设置角色
  2. 查看用户有哪些角色
答案很显然是“用户聚合”,那么我们可以得到下面的设计:
 

这时你会疑问,如果没有“用户聚合”和“角色聚合”的连线,怎么设置用户有哪些角色呢?
问题的关键,就在这里,通常我们总是会把“关系表”在图中用一条线来表示,那如果我说,“用户聚合”有一个集合属性,叫做“用户角色”,你会认同吗?如果我们知道用户对象有一个集合属性叫“用户角色”,那么是不是上图就很合理?
如果顺着这个思路,我们再来看需求“查看一个角色包含多少个用户”,它应该由哪个模型来解决?我想你已经知道答案了,就是“用户聚合”,最终我们得到如下设计:
 

到此,所有的需求可以满足,需求被划分为两个范围,分别对应两个模型。

为什么说它很难

如果你一直跟着我的思路,完成了上面的过程,那么你会发现,需求的边界不是客观存在的,而是我们主观的划分,这个划分的目的是为了在一个确定的范围内,能够解决这个问题。因为它是主观的,就不可衡量和判断,每个人都可以有自己的划分思路。另外它又是简单的,因为你可以像上图一样,这样划分边界,给出对应的模型解决它,就像在给自己家的袜子分配收纳盒一样简单。
所以,我常常叹息,关于领域驱动设计:
说它难,难的是做出取舍。
说它简单,是因为能明确知道取什么舍什么。