2023年3月

这份报告是我在学校上软件工程这门课上机票预订系统的详细设计,老师评分95分。

image-20230328171528937

一、引言

1.编写目的

由前面的总体设计,得出了系统的基本架构,要实现整个系统,需要对每个模块进行详细设计,详细设计主要是利用比较具体的设计对整个系统进行分析,确定对系统每个模块的物理配置,确定整个系统的处理流程和系统的数据结构,接口设计。目的在推动软件工程的规范化,使开发人员遵循统一的详细设计进行编码,节省编码的时间,降低系统实现的风险,以利于系统的测试、维护、版本升级等。

2.背景

本项目的名称:机票预订系统。

随着人们物质需求的提高,科技全球化的发展,乘坐飞机成为多数人生活、旅行中不可缺少的一部分。而飞机的航班的数量和业务量庞大,仅仅靠传统的记账式管理是不可行的。机票预订系统应运而生,逐渐成为信息化建设的重要组成部分。机票预订系统为机场的管理员提供所有乘客的详细信息,以及飞机航班的详细情况,对飞机购票和航班信息两大功能进行合理操纵并登记。

3.定义

开发(develop ):除了单纯的开发活动外,还包括维护活动。

项目(project ):向顾客交付的最终的全部产品,包括程序及各种文档,以及开发活动所需资源经费等各种信息。

项目开发计划(project development plan):把项目与过程联系起来的计划方案。

产品生命周期(product life cycle):产品从构思到不可在使用的持续时间。

4.参考资料

张海藩:《软件工程导论》第五版 清华大学出版社 肖刚等:《实用软件文档写作》清华大学出版社 李涛、刘凯奎、王永皎:《Visual C# SQL Server 数据库开发与实例 》清华大学出版社

二、程序设计说明

1.连接

1.1程序描述

该程序是用来连接航空公司的数据库的,对于有合作关系的航空公司,连接他们的数据库,然后把数据写到文件上,没有合作的航空公司,断开连接,从文件上删除数据。这一步为后面的订票,退票提供数据支持,并且是常驻内存的。

1.2功能

连接航空公司数据库,从而获得航班的起飞,飞行路线,落地时间等数据,为用户的选择提供依据。

1.3流程逻辑

img

1.4输入项

使用系统管理员的账户名和密码登录,在界面的对应位置输入合作的航空公司的名字,点击连接按钮进行连接。

1.5输出项

显示合作的航空公司的名字,获得他们的数据库连接密码,把数据写入文件中。

2.查询

2.1程序描述

该功能用于旅客确认自己的航班、机票的相关信息。其特点为非常驻内存,用户可以在需要的时候查询,且采取并发处理请求数据。

2.2功能

a) 查询航班:可以通过输入出发地,目的地,日期和时间选定自己的航班。

img

b) 查询机票:该项功能三方都可以操作,可以通过自己的身份证号以及取票通知上的机票号查询自己的机票信息。

2.3性能

精度:时间要求精确到分,价格精确到个位

灵活性:响应点击、鼠标和键盘的操作

时间特性:是手机、电脑的配置和网络的响应速度而定

2.4流程逻辑

img

2.5 接口

服务器程序上可使用Mysql 的对数据库的备分命令,以做到对数据的保存。

在网络软件接口方面,使用一种无差错的传输协议,采用滑动窗口方式对数据进行网络传输及接收。

在输入方面,对于键盘、鼠标的输入,可用Java、jsp的标准输入/输出,对输入进行处理。

在输出方面,打印机的连接及使用,也可用Java的标准输入/输出对其进行处理。在网络传输部分,在网络硬件部分,为了实现高速传输,将使用高速ATM。

内部接口方面,各模块之间采用函数调用、参数传递、返回值的方式进行信息传递。具体参数的结构将在下面数据结构设计的内容中说明。接口传递的信息将是以数据结构封装了的数据,以参数传递或返回值的形式在各模块间传输。

2.6 存储分配

本程序用高级语言jsp进行编程,直接内存分配由jsp程序运行时分配。

本组件所依赖的变量,结构要求全部在组建内申明。

2.7尚未解决的问题

对用户ID和密码的更安全加密方式尚未解决。

3.订票

3.1程序描述

机票订票系统的主要模块,根据客户提供的要求(航班、订票数额),查询该航班的票额情况,若有余票,则为客户办理订票手续,输入座位号;则重新询问客户要求。若需要可登记排队候补。

3.2功能

img

3.2性能

因机票预订系统对系统与现实时间的相对应有比较高的要求,因此系统时间需要确保精确并频繁校准。

3.3 输入项

客户预订信息:票数、班次、位次

3.4 输出项

订单:票数、班次、位次、是否成功预订

3.5 算法

客户预订数与余票数进行比对后输出是否可以订票

3.6 流程逻辑

img

3.7 接口

img

3.8 储存分配

需要储存客户订票信息,与输出订单等

3.9 限制条件

确保系统信息安全需要使用特定数据库

3.10测试计划

承载力测试:预计可同时处理一千个预订

数据准确性测试:准确率达到百分·九十九以上

3.11尚未解决的问题

系统如何应对航班延误或取消问题

4.退票

4.1程序描述

该功能用于旅客退订自己的航班、机票的相关信息。其特点为非常驻内存,用户可以在需要的时候查询,且采取并发处理请求数据

4.2功能

1667750131580

4.3输入项

机票编号,时间,客户信息

4.4输出项

是否退订成功

4.5流程逻辑

1667750573900

4.6储存分配

需要储存客户订票信息,与输出订单等

5.用户投诉

5.1程序描述

a.投诉渠道

在用户界面基于程序本身给出合理的反馈机制,在使用机票预订系统遇到不可挽回错误,如资金支付但未获得机票、航班信息错误、操作流程卡顿等,可以通过本机票预订系统内部本身设立的投诉渠道进行反馈,工作人员通过反馈对系统进行修复和优化

b.其他

同时我们也设立了建议渠道,人们对于使用系统的不舒服的地方,也可以通过该渠道进行反馈,或者在遇到资金流失等不可逆转的结果的时候,可以联系人工客服进行反馈,客服根据反馈内容进行处理,已弥补软件设计不足的问题。

5.2 功能

通过投诉系统进行反馈,分为建议和问题解决

img

5.3 流程控制

img

5.4性能

因为反馈问题程序处理优先级不高,所以性能及时要求不需要太高,但需要一定量的储存空间进行存放信息

5.5存储分配

初步划分服务器内存10%左右,当接近临界值时再进行自适应增长

6.用户充值

6.1程序描述

用户进入充值界面,点击支付按钮进入支付流程,进入支付流程后会先生成订单然后再进入支付环节,支付如果失败则订单失效重新进入充值界面,支付成功则把钱存入账户余额,结束流程。

6.2功能

用户可以通过充值功能把钱存入账户余额,以方便,快速的进行机票的预定

​ 输入 处理 输出

img

6.3流程逻辑

img

6.4限制条件

  1. 限制单个用户的最大充值数额;

  2. 限制所有用户的累积充值次数;

    3.设置充值规则的开始时间和结束时间;

这份报告你们可以参考一下,想了想,这些东西一直待在电脑本地没有发挥它的价值,决定把这些高分作业分享出来,希望能够帮助各位园友。

工厂方法模式是一种
创建型
设计模式, 提供一种统一的方式来创建对象, 调用者无需关心具体的构建细节

对象的创建过程被封装在工厂类中, 调用者只需要使用一个共同的接口来获取对象, 不需要直接使用new操作符

这样可以降低客户端和具体产品类之间的耦合度, 提高系统的可扩展性和可维护性

工厂方法模式的作用

  • 定义统一的工厂接口, 实现了对象创建和使用的分离, 让客户端不需要知道具体的产品类名, 只需要知道产品所属的工厂即可
  • 可以根据不同的需求和环境, 动态地选择具体的产品类来创建对象, 增加了系统的灵活性
  • 可以对产品进行统一的管理和配置, 方便后期维护和升级
  • 可以解耦对象的创建和使用过程, 把对象的实例化交给工厂类
  • 可以灵活应对变化的业务需求, 方便代码管理、避免代码重复
  • ......

工厂方法模式适用于什么场景

当一个类不知道或者不关心它需要创建的对象的具体细节时, 可以使用工厂方法模式

例如, 游戏在开始的时候需要创建一个角色, 但是不知道具体要创建哪种角色(如战士、法师、盗贼等, 角色的选择可能是在这个流程开始之前确定下来的), 同样也不知道创建这些对象都需要什么条件, 这种情况下就可以考虑使用工厂方法模式, 让子类工厂(例如战士工厂)来创建角色

类图

classDiagram
角色<|--战士
角色<|--法师
角色<|--盗贼
角色工厂<|--战士工厂
角色工厂<|--法师工厂
角色工厂<|--盗贼工厂
战士工厂..>战士
法师工厂..>法师
盗贼工厂..>盗贼
角色工厂..>角色

class 角色:::role{
+string 角色名称
+跑路()
}
class 角色工厂{
+创建角色(): 角色
}
class 战士{
+string 角色名称
+跑路()
}
class 法师{
+string 角色名称
+跑路()
}
class 盗贼{
+string 角色名称
+跑路()
}
class 战士工厂{
+创建角色(): 角色
}
class 法师工厂{
+创建角色(): 角色
}
class 盗贼工厂{
+创建角色(): 角色
}

代码

虽然很怪, 但还是先用中文编码吧, 看懂应该不难

定义角色

public abstract class 角色
{
    protected 角色(string 角色名称) => this.角色名称 = 角色名称;
    public string 角色名称 { get; set; }
    public abstract void 跑路();
}
public class 战士 : 角色
{
    public 战士() : base("战士") { }
    public override void 跑路() => Console.WriteLine($"{角色名称}开着野蛮冲锋跑路");
}
public class 法师 : 角色
{
    public 法师() : base("法师") { }
    public override void 跑路() => Console.WriteLine($"{角色名称}开着疾风术跑路");
}
public class 盗贼 : 角色
{
    public 盗贼() : base("盗贼") { }
    public override void 跑路() => Console.WriteLine($"{角色名称}开着潜行跑路");
}

然后定义对应的角色工厂

public abstract class 角色工厂
{
    public abstract 角色 创建角色();
}
public class 战士工厂 : 角色工厂
{
    public override 角色 创建角色() => new 战士();
}
public class 法师工厂 : 角色工厂
{
    public override 角色 创建角色() => new 法师();
}
public class 盗贼工厂 : 角色工厂
{
    public override 角色 创建角色() => new 盗贼();
}

如何去使用

角色工厂 工厂 = new 法师工厂();
var 玩家角色 = 工厂.创建角色();
玩家角色.跑路();

工厂 = new 盗贼工厂();
玩家角色 = 工厂.创建角色();
玩家角色.跑路();

两次
跑路
的输出为

法师开着疾风术跑路

盗贼开着潜行跑路

在这种时候可能看不出工厂模式的作用, 下面是一个简单的代码演示

new 山洞副本(new 法师工厂()).危险发生();

class 山洞副本
{
    private 角色 玩家角色;
    private readonly 角色工厂 工厂;
    public 山洞副本(角色工厂 工厂)
    {
        this.工厂 = 工厂;
        Init();
    }

    private void Init()
    {
        Console.WriteLine("开始初始化");
        玩家角色 = 工厂.创建角色();
        Console.WriteLine($"成功加载 {玩家角色.角色名称}");
    }

    public void 危险发生()
    {
        Console.WriteLine("出现大群野生篮球");
        玩家角色.跑路();
        if (DateTime.Now.DayOfWeek == DayOfWeek.Thursday)
        {
            Console.WriteLine("今天是逃不过的肯德基疯狂星期四");
            Console.WriteLine($"角色{玩家角色.角色名称} 死亡,重新初始化");
            Init();
        }
        else
        {
            Console.WriteLine("成功逃脱了!");
        }
    }
}

创建副本时传入角色工厂, 初始化副本数据的时候由工厂创建角色, 当危险发生时触发玩家角色的
跑路
方法, 如果周四就逃跑失败重新初始化角色, 副本并不需要知道创建角色的细节, 这些细节都被封装在了工厂中

在这种情况下, 即使以后有新增加的角色, 比如平民,游侠什么的, 只需要实现对应的工厂和角色类, 然后在创建副本的时候修改传入的工厂即可

只要副本的业务没有变化就无需更改副本类的代码

概述

之前在 天翼云上用 4 台机器安装了一个 1 master(及 etcd) 3 node 的 K3S 集群,并在其上使用 Helm 安装了 Rancher 2.6.3 版本。

前几天发现 Rancher 官方推荐的最新版为:
v2.6.4

所以决定先后对 Rancher 和 K3S 集群进行升级。

根据官方推荐,计划:

  1. 将 Rancher 从 v2.6.3 升级到 v2.6.4
  2. 将 K3S 集群从 v1.21.7+k3s1 升级到 v1.22.5+k3s2

本文为 Rancher 的升级记录。

相关信息

本次升级的 Rancher 的基本信息为:

  1. Rancher v2.6.3
  2. 使用 Helm 3, 在线安装
  3. 使用 cert-manager(v1.7.1) + let's encrypt 管理证书

升级步骤

一、备份运行 Rancher Server 的 Kubernetes 集群

使用
备份应用程序
来备份 Rancher。

如果在升级过程中出现问题,你将使用备份作为恢复点。

备份结果如下图:

Rancher 界面备份结果

对象存储中的备份对象

二、更新 Helm Chart repository

  1. 更新本地 helm 缓存。

    helm repo update
    
  2. 获取用来安装 Rancher 的存储库名称。

    关于存储库及其区别,请参见
    Helm Chart Repositories

    • Latest:推荐用于尝试最新功能

    • Stable:推荐用于生产环境 (

项目地址:
https://github.com/sherlockchou86/video_pipe_c

往期文章:
https://www.cnblogs.com/xiaozhi_5638/p/16969546.html

最近有多个更新,有兴趣的扫码加群交流。

新增实例分割相关支持

增加了基于mask-rcnn的实例分割插件和相关sample。

1 #include "VP.h"
2 
3 #include "../nodes/vp_file_src_node.h"
4 #include "../nodes/infers/vp_mask_rcnn_detector_node.h"
5 #include "../nodes/track/vp_sort_track_node.h"
6 #include "../nodes/osd/vp_osd_node_v3.h"
7 #include "../nodes/vp_screen_des_node.h"
8 
9 #include "../utils/analysis_board/vp_analysis_board.h"
10 
11 /*
12 * ## mask rcnn sample ##13 * image segmentation using mask rcnn.14 */
15 
16 #if mask_rcnn_sample
17 
18 intmain() {19 VP_SET_LOG_LEVEL(vp_utils::vp_log_level::INFO);20 VP_LOGGER_INIT();21 
22     //create nodes
23     auto file_src_0 = std::make_shared<vp_nodes::vp_file_src_node>("file_src_0", 0, "./test_video/19.mp4", 0.6);24     auto mask_rcnn_detector = std::make_shared<vp_nodes::vp_mask_rcnn_detector_node>("mask_rcnn_detector", "./models/mask_rcnn/frozen_inference_graph.pb", "./models/mask_rcnn/mask_rcnn_inception_v2_coco_2018_01_28.pbtxt", "./models/coco_80classes.txt");25     auto track_0 = std::make_shared<vp_nodes::vp_sort_track_node>("sort_track_0");26     auto osd_v3_0 = std::make_shared<vp_nodes::vp_osd_node_v3>("osd_v3_0", "../third_party/paddle_ocr/font/NotoSansCJKsc-Medium.otf");27     auto screen_des_0 = std::make_shared<vp_nodes::vp_screen_des_node>("screen_des_0", 0);28 
29     //construct pipeline
30     mask_rcnn_detector->attach_to({file_src_0});31     track_0->attach_to({mask_rcnn_detector});32     osd_v3_0->attach_to({track_0});33     screen_des_0->attach_to({osd_v3_0});34 
35     file_src_0->start();36 
37     //for debug purpose
38 vp_utils::vp_analysis_board board({file_src_0});39 board.display();40 }41 
42 
43 #endif

上面代码效果图如下:

新增语义分割相关支持

新增了基于ENet网络的语义分割插件和sample。

1 #include "VP.h"
2 
3 #include "../nodes/vp_file_src_node.h"
4 #include "../nodes/infers/vp_enet_seg_node.h"
5 #include "../nodes/osd/vp_seg_osd_node.h"
6 #include "../nodes/vp_screen_des_node.h"
7 
8 #include "../utils/analysis_board/vp_analysis_board.h"
9 
10 /*
11 * ## enet seg sample ##12 * semantic segmentation based on ENet.13 * 1 input, 2 outputs including orignal frame and mask frame.14 */
15 
16 #if enet_seg_sample
17 
18 intmain() {19 VP_SET_LOG_LEVEL(vp_utils::vp_log_level::INFO);20 VP_LOGGER_INIT();21 
22     //create nodes
23     auto file_src_0 = std::make_shared<vp_nodes::vp_file_src_node>("file_src_0", 0, "./test_video/21.mp4");24     auto enet_seg = std::make_shared<vp_nodes::vp_enet_seg_node>("enet_seg", "models/enet-cityscapes/enet-model.net");25     auto seg_osd_0 = std::make_shared<vp_nodes::vp_seg_osd_node>("seg_osd_0", "models/enet-cityscapes/enet-classes.txt", "models/enet-cityscapes/enet-colors.txt");26     auto screen_des_mask = std::make_shared<vp_nodes::vp_screen_des_node>("screen_des_mask", 0, true, vp_objects::vp_size(400, 225));27     auto screen_des_original = std::make_shared<vp_nodes::vp_screen_des_node>("screen_des_original", 0, false, vp_objects::vp_size(400, 225));28 
29     //construct pipeline
30     enet_seg->attach_to({file_src_0});31     seg_osd_0->attach_to({enet_seg});32     screen_des_mask->attach_to({seg_osd_0});33     screen_des_original->attach_to({seg_osd_0});34 
35     file_src_0->start();36 
37     //for debug purpose
38 vp_utils::vp_analysis_board board({file_src_0});39 board.display();40 }41 
42 #endif

上面代码效果图如下:

新增多级推理插件sample

多个检测、分类插件串联,不同分类器作用于不同的主目标:

1 #include "VP.h"
2 
3 #include "../nodes/vp_file_src_node.h"
4 #include "../nodes/infers/vp_yolo_detector_node.h"
5 #include "../nodes/infers/vp_classifier_node.h"
6 #include "../nodes/osd/vp_osd_node.h"
7 #include "../nodes/vp_screen_des_node.h"
8 #include "../utils/analysis_board/vp_analysis_board.h"
9 
10 /*
11 * ## multi detectors and classifiers sample ##12 * show multi infer nodes work together.13 * 1 detector and 2 classifiers applied on primary class ids(1/2/3).14 */
15 
16 #if multi_detectors_and_classifiers_sample
17 
18 intmain() {19 VP_SET_LOG_LEVEL(vp_utils::vp_log_level::INFO);20 VP_LOGGER_INIT();21 
22     //create nodes
23     auto file_src_0 = std::make_shared<vp_nodes::vp_file_src_node>("file_src_0", 0, "test_video/20.mp4", 0.6);24     /*primary detector*/
25     //labels for detector model26     //0 - person27     //1 - car28     //2 - bus29     //3 - truck30     //4 - 2wheel
31     auto primary_detector = std::make_shared<vp_nodes::vp_yolo_detector_node>("primary_detector", "models/det_cls/yolov3-tiny-2022-0721_best.weights", "models/det_cls/yolov3-tiny-2022-0721.cfg", "models/det_cls/yolov3_tiny_5classes.txt", 416, 416, 1);32     /*secondary classifier 1, applied to car(1)/bus(2)/truck(3) only*/
33     auto _1st_classifier = std::make_shared<vp_nodes::vp_classifier_node>("1st_classifier", "models/det_cls/vehicle/resnet18-batch=N-type_view_0322_nhwc.onnx", "", "models/det_cls/vehicle/vehicle_types.txt", 224, 224, 1, std::vector<int>{1, 2, 3}, 10, false, 1, cv::Scalar(), cv::Scalar(), true, true);34     /*secondary classifier 2, applied to car(1)/bus(2)/truck(3) only*/
35     auto _2nd_classifier = std::make_shared<vp_nodes::vp_classifier_node>("2nd_classifier", "models/det_cls/vehicle/resnet18-batch=N-color_view_0322_nhwc.onnx", "", "models/det_cls/vehicle/vehicle_colors.txt", 224, 224, 1, std::vector<int>{1, 2, 3}, 10, false, 1, cv::Scalar(), cv::Scalar(), true, true);36     auto osd_0 = std::make_shared<vp_nodes::vp_osd_node>("osd_0");37     auto screen_des_0 = std::make_shared<vp_nodes::vp_screen_des_node>("screen_des_o", 0);38 
39     //construct pipeline
40     primary_detector->attach_to({file_src_0});41     _1st_classifier->attach_to({primary_detector});42     _2nd_classifier->attach_to({_1st_classifier});43     osd_0->attach_to({_2nd_classifier});44     screen_des_0->attach_to({osd_0});45 
46     //start
47     file_src_0->start();48 
49     //for debug purpose
50 vp_utils::vp_analysis_board board({file_src_0});51 board.display();52 }53 
54 #endif

上面代码运行效果如下:

新增图片源输入插件

支持以图片方式输入(文件或UDP),频率可调、各个通道互相独立。

1 #include "VP.h"
2 
3 #include "../nodes/vp_image_src_node.h"
4 #include "../nodes/infers/vp_yolo_detector_node.h"
5 #include "../nodes/osd/vp_osd_node.h"
6 #include "../nodes/vp_split_node.h"
7 #include "../nodes/vp_screen_des_node.h"
8 
9 #include "../utils/analysis_board/vp_analysis_board.h"
10 
11 /*
12 * ## image_src_sample ##13 * show how vp_image_src_node works, read image from local file or receive image from remote via udp.14 */
15 
16 #if image_src_sample
17 
18 intmain() {19 VP_SET_LOG_LEVEL(vp_utils::vp_log_level::INFO);20 VP_LOGGER_INIT();21 
22     //create nodes
23     auto image_src_0 = std::make_shared<vp_nodes::vp_image_src_node>("image_file_src_0", 0, "./images/test_%d.jpg", 1, 0.4); //read 1 image EVERY 1 second from local files, such as test_0.jpg,test_1.jpg
24     /*sending command for test: `gst-launch-1.0 filesrc location=16.mp4 ! qtdemux ! avdec_h264 ! videoconvert ! videoscale ! video/x-raw,width=416,height=416 ! videorate ! video/x-raw,framerate=1/1 ! jpegenc ! rtpjpegpay ! udpsink host=ip port=6000`*/
25     auto image_src_1 = std::make_shared<vp_nodes::vp_image_src_node>("image_udp_src_1", 1, "6000", 3);                       //receive 1 image EVERY 3 seconds from remote via udp , such as 127.0.0.1:6000
26     auto yolo_detector = std::make_shared<vp_nodes::vp_yolo_detector_node>("yolo_detector", "models/det_cls/yolov3-tiny-2022-0721_best.weights", "models/det_cls/yolov3-tiny-2022-0721.cfg", "models/det_cls/yolov3_tiny_5classes.txt");27     auto osd = std::make_shared<vp_nodes::vp_osd_node>("osd");28     auto split = std::make_shared<vp_nodes::vp_split_node>("split_by_channel", true);    //split by channel index (important!)
29     auto screen_des_0 = std::make_shared<vp_nodes::vp_screen_des_node>("screen_des_0", 0);30     auto screen_des_1 = std::make_shared<vp_nodes::vp_screen_des_node>("screen_des_1", 1);31     
32     //construct pipeline
33     yolo_detector->attach_to({image_src_0, image_src_1});34     osd->attach_to({yolo_detector});35     split->attach_to({osd});36     screen_des_0->attach_to({split});37     screen_des_1->attach_to({split});38 
39     image_src_0->start();  //start read from local file
40     image_src_1->start();  //start receive from remote via udp41 
42     //for debug purpose
43 vp_utils::vp_analysis_board board({image_src_0, image_src_1});44 board.display();45 }46 
47 #endif

上面代码运行效果如下:

新增图片结果输出插件

支持以图片格式输出结果(文件或UDP),频率可调、各通道互相独立。

1 #include "VP.h"
2 
3 #include "../nodes/vp_file_src_node.h"
4 #include "../nodes/infers/vp_yunet_face_detector_node.h"
5 #include "../nodes/infers/vp_sface_feature_encoder_node.h"
6 #include "../nodes/osd/vp_face_osd_node_v2.h"
7 #include "../nodes/vp_screen_des_node.h"
8 #include "../nodes/vp_image_des_node.h"
9 
10 #include "../utils/analysis_board/vp_analysis_board.h"
11 
12 /*
13 * ## image_des_sample ##14 * show how vp_image_des_node works, save image to local file or push image to remote via udp.15 */
16 
17 #if image_des_sample
18 
19 intmain() {20 VP_SET_LOG_LEVEL(vp_utils::vp_log_level::INFO);21 VP_LOGGER_INIT();22 
23     //create nodes
24     auto file_src_0 = std::make_shared<vp_nodes::vp_file_src_node>("file_src_0", 0, "./test_video/10.mp4", 0.6);25     auto yunet_face_detector_0 = std::make_shared<vp_nodes::vp_yunet_face_detector_node>("yunet_face_detector_0", "./models/face/face_detection_yunet_2022mar.onnx");26     auto sface_face_encoder_0 = std::make_shared<vp_nodes::vp_sface_feature_encoder_node>("sface_face_encoder_0", "./models/face/face_recognition_sface_2021dec.onnx");27     auto osd_0 = std::make_shared<vp_nodes::vp_face_osd_node_v2>("osd_0");28     auto screen_des_0 = std::make_shared<vp_nodes::vp_screen_des_node>("screen_des_0", 0);29     
30     /*save to file, `%d` is placeholder for filename index*/
31     //auto image_des_0 = std::make_shared<vp_nodes::vp_image_des_node>("image_file_des_0", 0, "./images/%d.jpg", 3);
32     
33     /*push via udp,  receiving command for test: `gst-launch-1.0 udpsrc port=6000 ! application/x-rtp,encoding-name=jpeg ! rtpjpegdepay ! jpegparse ! jpegdec ! queue ! videoconvert ! autovideosink`*/
34     auto image_des_0 = std::make_shared<vp_nodes::vp_image_des_node>("image_udp_des_0", 0, "192.168.77.248:6000", 3, vp_objects::vp_size(400, 200));35 
36     //construct pipeline
37     yunet_face_detector_0->attach_to({file_src_0});38     sface_face_encoder_0->attach_to({yunet_face_detector_0});39     osd_0->attach_to({sface_face_encoder_0});40     screen_des_0->attach_to({osd_0});41     image_des_0->attach_to({osd_0});42 
43     file_src_0->start();44 
45     //for debug purpose
46 vp_utils::vp_analysis_board board({file_src_0});47 board.display();48 }49 
50 #endif

上面代码运行效果如下:

更多更新扫码加入微信群,及时获取通知。

近些年来,随着手机技术迭代更新越来越快,用户更换手机的周期也在缩短,在这样的背景下,开发者不得不面临以下问题:

同一开发者旗下常常有多个安卓应用和多形态应用(快应用和Web应用),用户更换一个新的设备(手机或平板)后,在新设备上登录各应用时每次都需要重复输入帐号和密码,导致用户在登录阶段流失率增加,同时开发者还需要承担额外的短信成本(如用户使用短信登录)。

华为HMS Core
钥匙环服务
(Keyring)提供凭据管理接口(Credentials Management API),为Android手机、平板提供用户登录凭据存储和跨应用、跨应用形态、跨设备共享的能力。

钥匙环服务提供了
Android API

Web API

快应用API
,应用程序通过调用这些API来使用钥匙环服务。无论调用哪种形式的接口,所有的用户凭据最终都会存储在HMS Core的钥匙环服务中,以便实现统一的凭据管理能力和共享能力。

一、功能特点

钥匙环服务提供登录凭据本地储存和跨形态、跨应用共享能力。钥匙环服务将用户登录凭据加密储存在本地设备,被保存的凭据通过钥匙环服务共享至同一开发者旗下的其他快应用、Web应用和安卓应用;实现跨形态、跨应用无缝登录体验。

钥匙环服务使用端到端加密同步技术实现登录凭据跨设备同步能力。用户在新老设备上打开“凭据多设备同步”功能,就可以在新设备上免密登录同一开发者旗下的各形态应用,实现跨设备无缝登录体验。

例如,同一开发者将旗下的两个安卓应用和两个快应用接入钥匙环服务后,用户只需要在手机A和手机B上打开“凭据多设备同步”功能,手机A上登录一个应用后,用手机B登录时无需再输入帐号和密码,实现跨设备、跨应用、跨形态的无缝登录体验。

二、接入优势

打造无缝登录体验

通过钥匙环服务接口获取本地存储的用户凭据,实现便捷登录。

保障数据安全可靠

用户登录凭据在设备内加密存储,在设备间通过端到端加密技术同步,云端无法解密。

降低登录流失率

简化用户登录时操作流程,降低流失率

降低运营成本

减少使用短信登录,降低运营成本

三、开发步骤

开发准备

详细准备步骤可参考
华为开发者联盟官网

集成Keyring客户端

用户登录场景

1、使用一个Activity实例初始化
CredentialClient
,可以写在Activity的onCreate方法中。

CredentialClient credentialClient = CredentialManager.getCredentialClient(this);

2、查询是否存在可用的凭据。

List<AppIdentity> trustedAppList = new ArrayList<>();
trustedAppList.add(new AndroidAppIdentity("yourAppName", "yourAppPackageName", "yourAppCodeSigningCertHash"));
trustedAppList.add(new WebAppIdentity("youWebSiteName", "www.yourdomain.com"));
trustedAppList.add(new WebAppIdentity("youWebSiteName", "login.yourdomain.com"));
SharedCredentialFilter sharedCredentialFilter = SharedCredentialFilter.acceptTrustedApps(trustedAppList);
credentialClient.findCredential(sharedCredentialFilter, new CredentialCallback<List<Credential>>() {
    @Override
    public void onSuccess(List<Credential> credentials) {
        if (credentials.isEmpty()) {
            Toast.makeText(MainActivity.this, R.string.no_available_credential, Toast.LENGTH_SHORT).show();
        } else {
            for (Credential credential : credentials) {
            }
        }
    }
    @Override
    public void onFailure(long errorCode, CharSequence description) {
        Toast.makeText(MainActivity.this, R.string.query_credential_failed, Toast.LENGTH_SHORT).show();
    }
});

3、调用
Credential.getContent
获取凭据内容,在
CredentialCallback

获取结果。

private Credential mCredential; 
//获取的凭据
mCredential.getContent(new CredentialCallback<byte[]>() {
    @Override
    public void onSuccess(byte[] bytes) {
        String hint = String.format(getResources().getString(R.string.get_password_ok),
                new String(bytes));
        Toast.makeText(MainActivity.this, hint, Toast.LENGTH_SHORT).show();
        mResult.setText(new String(bytes));
    }
 
    @Override
    public void onFailure(long l, CharSequence charSequence) {
        Toast.makeText(MainActivity.this, R.string.get_password_failed,
                Toast.LENGTH_SHORT).show();
        mResult.setText(R.string.get_password_failed);
    }
});

4、用户输入了新凭据,调用凭据存储接口。

AndroidAppIdentity app2 = new AndroidAppIdentity(sharedToAppName,
                sharedToAppPackage, sharedToAppCertHash);
List<AppIdentity> sharedAppList = new ArrayList<>();
sharedAppList.add(app2);

Credential credential = new Credential(username, CredentialType.PASSWORD, userAuth,
                password.getBytes());
credential.setDisplayName("user_niceday");
credential.setSharedWith(sharedAppList);
credential.setSyncable(true);

credentialClient.saveCredential(credential, new CredentialCallback<Void>() {
    @Override
    public void onSuccess(Void unused) {
        Toast.makeText(MainActivity.this,
                R.string.save_credential_ok,
                Toast.LENGTH_SHORT).show();
    }
 
    @Override
    public void onFailure(long errorCode, CharSequence description) {
        Toast.makeText(MainActivity.this,
                R.string.save_credential_failed + " " + errorCode + ":" + description,
                Toast.LENGTH_SHORT).show();
    }
});

用户登出场景

1、使用一个Activity实例初始化
CredentialClient
,可以写在Activity的onCreate方法中。

CredentialClient credentialClient = CredentialManager.getCredentialClient(this);

2、查询是否存在可用的凭据。

List<AppIdentity> trustedAppList = new ArrayList<>();
trustedAppList.add(new AndroidAppIdentity("yourAppName", "yourAppPackageName", "yourAppCodeSigningCertHash"));
trustedAppList.add(new WebAppIdentity("youWebSiteName", "www.yourdomain.com"));
trustedAppList.add(new WebAppIdentity("youWebSiteName", "login.yourdomain.com"));
SharedCredentialFilter sharedCredentialFilter = SharedCredentialFilter.acceptTrustedApps(trustedAppList);
credentialClient.findCredential(sharedCredentialFilter, new CredentialCallback<List<Credential>>() {
    @Override
    public void onSuccess(List<Credential> credentials) {
        if (credentials.isEmpty()) {
            Toast.makeText(MainActivity.this, R.string.no_available_credential, Toast.LENGTH_SHORT).show();
        } else {
            for (Credential credential : credentials) {
                //可以对可用凭据进行进一步处理,包括:获取凭据相关信息、获取凭据内容、删除
            }
        }
    }
 
    @Override
    public void onFailure(long errorCode, CharSequence description) {
        Toast.makeText(MainActivity.this, R.string.query_credential_failed, Toast.LENGTH_SHORT).show();
    }
});

3、调用
deleteCredential
删除凭据,在
CredentialCallback
获取结果。

credentialClient.deleteCredential(credential, new CredentialCallback<Void>() {
    @Override
    public void onSuccess(Void unused) {
        String hint = String.format(getResources().getString(R.string.delete_ok),
                credential.getUsername());
        Toast.makeText(MainActivity.this, hint, Toast.LENGTH_SHORT).show();
    }
 
    @Override
    public void onFailure(long errorCode, CharSequence description) {
        String hint = String.format(getResources().getString(R.string.delete_failed),
                description);
        Toast.makeText(MainActivity.this, hint, Toast.LENGTH_SHORT).show();
    }
});

凭据共享机制

通过API参数实现凭据共享

在调用
saveCredential
保存凭据时,您可以通过
setSharedWith
设置
Credential
对象的属性实现凭据共享,最多支持共享给128个应用。

示例如下:

AndroidAppIdentity app1 = new AndroidAppIdentity("your android app name",
                "your android app package name", "3C:99:C3:....");
QuickAppIdentity app2 = new QuickAppIdentity("your quick app name",
                "your quick app package name", "DC:99:C4:....");
List<AppIdentity> sharedAppList = new ArrayList<>(); // 共享关系列表
sharedAppList.add(app1);
sharedAppList.add(app2);
Credential credential = new Credential("username", CredentialType.PASSWORD, true,
                "password".getBytes());
credential.setSharedWith(sharedAppList); // 设置共享关系
credentialClient.saveCredential(credential, new CredentialCallback<Void>() {
    @Override
    public void onSuccess(Void unused) {
        Toast.makeText(MainActivity.this,
                R.string.save_credential_ok,
                Toast.LENGTH_SHORT).show();
    }
    @Override
    public void onFailure(long errorCode, CharSequence description) {
        Toast.makeText(MainActivity.this,
                R.string.save_credential_failed + " " + errorCode + ":" + description,
                Toast.LENGTH_SHORT).show();
    }
});

通过Digital Asset Links资源实现凭据共享

您可以在Android应用的AndroidManifest.xml中添加凭据共享关系,方法如下:

1、在AndroidManifest.xml的
节点中添加以下内容:

<application>
           <meta-data
            android:name="asset_statements"
            android:value="@string/asset_statements" />
</application>
  1. 在res\values\strings.xml中添加以下内容:
<string name="asset_statements">your digital asset links statements</string>

Digital asset links statements是一个遵循Digital Asset links规范的JSON字符串,示例如下:

[{
                   "relation": ["delegate_permission/common.get_login_creds"],
                   "target": {
                            "namespace": "web",
                            "site": "https://developer.huawei.com" // 您的网站域名
                   }
         },
         {
                   "relation": ["delegate_permission/common.get_login_creds"],
                   "target": {
                            "namespace": "android_app",
                            "package_name": "your android app package name",
                            "sha256_cert_fingerprints": [
                                     "F2:52:4D:..."
                            ]
                   }
         },
         {
                   "relation": ["delegate_permission/common.get_login_creds"],
                   "target": {
                            "namespace": "quick_app",
                            "package_name": "your quick app package name",
                            "sha256_cert_fingerprints": [
                                     "C3:68:9F:..."
                            ]
                   }
         }
]

relation属性的值固定为["delegate_permission/common.get_login_creds"],表示把凭据共享给target属性所描述的应用。

案例分享

航班管家和高铁管家集成华为钥匙环服务,为两亿用户打造无缝登录体验。

了解更多详情>>

访问
华为开发者联盟官网
获取
开发指导文档
华为移动服务开源仓库地址:
GitHub

Gitee

关注我们,第一时间了解 HMS Core 最新技术资讯~