2024年8月

《数据资产管理核心技术与应用》是清华大学出版社出版的一本图书,全书共分10章,第1章主要让读者认识数据资产,了解数据资产相关的基础概念,以及数据资产的发展情况。第2~8章主要介绍大数据时代数据资产管理所涉及的核心技术,内容包括元数据的采集与存储、数据血缘、数据质量、数据监控与告警、数据服务、数据权限与安全、数据资产管理架构等。第9~10章主要从实战的角度介绍数据资产管理技术的应用实践,包括如何对元数据进行管理以发挥出数据资产的更大潜力,以及如何对数据进行建模以挖掘出数据中更大的价值。

图书介绍:
《数据资产管理核心技术与应用》

今天主要是给大家分享一下第一章的内容:

第一章的标题为认识数据资产,总共分为了8个小节。

1.1 数据资产的基本介绍

数据资产通常是指那些可以通过分析来揭示价值、支持企业决策制定、优化企业流程、预测行业的未来趋势或产生更大的经济价值的数据集。这些数据可能是由企业自身产生也可能是从外部获取(如社交媒体、第三方数据提供商、网络爬虫等)的,而且这些数据的格式多样,可能是结构化数据、半结构化数据或者非结构化数据。

数据资产的关键特性:可用性、可访问性、完整性、可靠性和安全性

可用性:指的数据资产需要能被使用,如果无法被使用,那么数据资产就无法体现其核心价值,而数据资产的可用性则需要依赖数据质量、数据监控等很多关键要素的支撑。
可访问性:指的是数据资产需要能够被数据的使用者访问,如果无法被访问,那么数据资产也会显得没有任何的价值,因为只有能被访问,才能去挖掘出数据的更多价值。
安全性:指的是数据资产需要保障其数据的安全性,防止数据被泄露、丢失或者被黑客攻击篡改等。
可靠性:指的是数据资产一定是可靠的,不然无法用于企业的决策和判断,如果数据不可靠,那么通过数据做出来的决策肯定也不会可靠,从而会给企业带来巨大的损失。
完整性:指的数据资产中的数据一定是完整的,如果数据不完整,那么获取到的信息也不会完整,不完整的数据是无法用于数据分析、数据决策等。
数据资产的几种常见类型:

非结构化数据: 非结构化数据没有预定义的格式或组织,因此更难以处理和分析。这类数据包括文本文档、PDF文件、电子邮件、视频、图像和音频文件。虽然处理起来更复杂,但非结构化数据通常提供更丰富的信息和见解,对于机器学习和自然语言处理等领域尤其有价值。
结构化数据: 这类数据通常存在于预定义的数据模型之中,因此它们格式清晰、易于搜索和组织。结构化数据通常存储在关系数据库中,如SQL数据库,这类数据库支持复杂的查询、报告和分析。例如,客户信息、销售记录、库存数据和金融交易都可以以结构化的形式存储。通常他们表格形式存在,每一列代表一个数据字段,每一行代表一个数据记录。
半结构化数据: 半结构化数据介于结构化和非结构化数据之间,它们可能不符合严格的数据库模型,但包含标签或其他标记来分隔语义元素,并使元素的层次结构可识别。XML和JSON是半结构化数据的典型例子,它们被广泛用于网络数据交互。
实时数据: 实时数据是指需要立即处理的数据,以便快速做出响应或决策。这类数据在金融交易、网络分析、物联网(IoT)设备监控和在线广告投放中非常常见。实时数据处理通常要求具有较高的技术能力,以便快速捕捉、分析和响应数据流。
时间序列数据: 时间序列数据是按照时间顺序收集的数据信息,通常会用于分析数据的趋势、周期性和季节性变化等,比如股票价格、气象记录和监控数据等都是时间序列数据的例子。
地理空间数据: 地理空间数据含有关于地理位置的信息,这类数据在规划、物流和位置分析中非常关键。比如地图数据、卫星图像和GPS追踪数据都属于这一类型。
元数据: 元数据是描述其他数据的数据,它可以包括文件大小、存储路径、创建日期、作者信息等。元数据有助于组织、管理和检索数据,通常是数据管理、数据仓库、数据湖中不可或缺的核心组成部分。
1.2 数据资产的分类
数据资产分类的方式通常包含如下几种

根据数据敏感性分类:根据数据的敏感程度,通常可以将数据分为不同的级别,如公开数据、内部使用数据、敏感数据、隐私数据、绝密数据等。这种分类方式一般有助于企业或者组织对不同级别的数据采取不同的保护措施,以确保数据的安全性和隐私性。
根据数据来源分类:根据数据的来源,通常可以将数据分为很多不同类别,如会员数据、商品数据、业务数据、交易数据、第三方数据等。这种分类方式一般有助于企业或者组织更好地了解数据的来源和用途,从而更好地利用数据。
根据数据用途分类:根据数据的用途,通常可以将数据分为各种不同的类别,如分析数据、决策数据、销售数据、风控数据等。这种分类方式一般有助于企业或者组织更好地了解数据的应用场景和使用的目的,从而更好地发挥数据的使用价值。
根据数据格式分类:根据数据的格式和类型,通常可以将数据分为更多不同的类别,如半结构化数据、结构化数据、非结构化数据、文本数据、图像数据、音频数据等。这种分类有助于企业或者组织更好地了解数据的结构和特点,从而更好地处理和保存数据。
1.3 数据资产的价值评估

1、通过成本来评估数据资产的价值

利用成本来评估数据资产的价值是一种在数据领域经常使用的方法,主要思想是通过考量 数据的获取、处理、存储以及后期维护和升级的成本来确定数据能够产生多大的价值。

2、通过收益来评估数据资产的价值

利用收益来评估数据资产的价值是指基于现有的数据资产在过去的应用和使用情况和未来应用场景来评估数据资产能产生多大的价值。

1.4 数据资产的质量

确保数据高质量是数据资产管理的核心之一,企业或者组织管理其数据是因为需要使用数据或者挖掘数据中更大的价值,为了确保数据满足使用的需要,那么就一定要做好数据质量的管理。如果数据质量过差,对于任何的企业或者组织来说都是一种高成本的消耗。低质量的数据通常会产生如下不必要的成本开支,比如:

数据经常需要不断的返工和修正
数据的质量低,导致企业或者组织的决策错误,从而造成巨大的经济损失
数据的质量低,导致数据的使用变少,从而无法让数据发挥出应用的价值
高质量数据带来的相应好处包括:

可以更好的去改善客户的使用体验
可以更好去提升生产力
降低低质量的数据造成不可控的风险
高质量的数据,可以带来更大的机会和机遇
从对客户、产品、流程和机会的洞察中获得的更大的竞争优势
1.5 数据资产的存储

从传统的IT时代到现今的互联网时代和大数据时代,随着技术的不断快速发展,数据资产的存储方式也在不断的发生着翻天覆地的变化,数据资产的存储发展历程主要分为如下几个阶段

文档存储时代:

普通数据库存储的时代:

数据仓库存储的时代:

数据湖存储的时代:

湖仓一体的时代:

1.6 数据资产的管理

数据资产管理是一个涉及数据识别、分类、存储、保护和使用的复杂过程。数据资产的管理包括需要知晓数据的来源、存储位置、质量、适用的合规要求以及如何最大限度地利用这些数据等。在做数据资产管理时,还需要注意数据的生命周期。数据的生命周期通常包括数据的创建、存储、使用、共享、归档和销毁等。在每个阶段,都需要相应的管理措施来保护数据的价值并确保其质量和合规性。

通常来说,数据资产的管理包括:

元数据管理:在前面已经提到,元数据是描述其他数据的数据,是数据资产管理的核心,如果没有元数据管理,用户在使用数据时,就不知道数据是什么、包含了什么信息,自己需要的数据在哪里等,只有做好了元数据的管理,才能让数据更容易被检索,才能让数据的使用者快速的找到自己需要的数据。
数据获取管理:通常指的是从数据源端获取数据的管理,比如当存在很多个数据源时,需要对每个采集数据的数据源进行管理。
数据处理管理:当从数据的源端获取到数据后,通常还需要对数据做一些加工和处理,比如数据格式的处理、数据的压缩处理、数据的异常值处理等。
主数据管理:是对核心业务的实体相关的关键数据进行的管理,在不同的企业或者不同的环境中,主数据可能是不同的。主数据的管理可以进一步提高数据的价值,提升数据对业务的响应速度。
数据血缘管理:是对数据之间的关联关系进行的管理,通过数据血缘管理,数据的使用者就可以知道数据是从哪里来的、做了什么处理和加工等。
数据质量管理:通过对数据质量规则的定义来衡量数据的质量的管理,数据质量的好坏直接会关系数据价值。
数据监控管理:数据监控管理是对数据链路、数据任务、数据服务、数据处理资源等环节进行监控与告警,当发现问题时,能够及时将问题告警和通知出来,以便数据的运维人员后者管理人员及时进行处理。
数据服务管理:在数据资产中,数据服务是对外提供使用和访问的一种最重要的形式,数据只有提供对外的访问,才能体现其自身的价值。数据服务的管理就是对这些对外提供数据使用的服务进行管理。
数据权限与安全管理:在数据资产中,数据权限与安全的管理是让数据的整个生命周期中不会出现数据在未经授权的情况下被滥用,从而保护数据的安全和隐私不受侵犯的管理。
通常来说,数据资产的管理方式包括如下几种

加强数据治理:通常来说,数据治理是做好数据资产管理的核心,通过不断的建立和完善数据治理的流程和规范,明确数据管理的职责和分工,对数据做好分类和标记,让数据更方便的被查找。
建立完善的数据质量体系:数据质量直接决定了数据能否发挥其应有的作用,健全完善的数据质量体系可以持续不断的提高数据的质量,让数据能够更准确的支撑企业或者组织的决策。
建立完善的数据权限和安全管理体系:数据安全是整个数据资产管理的基础,建立一套包括数据备份和恢复、数据加密和解密、数据权限控制等在内的体系,可以更好去保障数据的安全可靠。
通过数据分析挖掘数据的更多价值:数据分析是数据资产的核心应用,在数据资产管理中,需要更多的去对数据做分析,以挖掘出数据的更多潜在价值。
1.7 数据资产管理的信息化建设

数据资产管理的信息化建设通常是指通过类似大数据等信息化的技术对企业或者组织的数据资产进行管理和维护,数据资产的信息化管理可以带来如下好处

及早发现数据问题:通过数据资产管理的信息化可以强化数据的质量以及监控和告警,当数据出现问题时,能够及早的被发现。
提高数据管理的效率:通过大数据等IT技术手段,实现自动化、智能化管理数据,减少人工操作以及人为失误,降低人力成本和数据出错的风险。
让数据可以更快的产生价值:通过大数据等IT技术手段,让数据分析、数据挖掘更加迅速,能更快的为企业或者组织提供更快更准确的决策。
让数据可追溯和跟踪:通过建设数据资产管理平台,管理数据的处理过程和血缘关系等,让数据的使用者能对数据进行溯源。
数据资产管理信息化建设核心要素如下:

数据采集:通过信息化的方式实现自动、实时、准确的去各个业务系统或者软硬件设备上采集数据.
数据处理:通过Spark、Flink等大数据技术,实时的对采集到的数据做清洗和转换处理,挖掘出更多的数据价值。
数据存储:通过数据仓库或者数据湖等分布式存储的技术手段来存储不同数据种类和格式的海量数据。
数据服务:搭建统一的数据服务平台,让数据能够被业务需求轻松的访问到。
数据安全:建立信息化的安全机制,自动识别数据中可能存在的安全访问风险,对数据进行自动备份以便在数据出现丢失时能够自动的恢复。
1.8 数据资产与人工智能

随着新一轮科技技术的变革,人工智能已经成为了当前技术的热点话题之一,而数据更是赋能人工智能发展的关键。

人工智能在算法学习和模型训练时,需要大量的数据做支撑,而人工智能算法预测的结果又需要通过数据反馈来验证其准确的程度,所以可以看到数据是支撑人工智能发展的关键,提高人工智能的准确性需要大量的数据来不断的训练其模型。

数据资产和人工智能的结合将会使得

人工智能更加智能化,能解放更多的人力成本。
能更好和更快的推动很多传统企业或者组织做数字化的转型。
加速科技发展的进程,发现更多未知的规律和现象。
总之数据资产与人工智能的结合,可以为很多的企业或者组织带来更大的商机,可以让企业或者组织更好的去理解数据和探索挖掘数据中更多潜在的价值。

Java RMI(Remote Method Invocation)是一种允许Java虚拟机之间进行通信和交互的技术。它使得远程Java对象能够像本地对象一样被访问和操作,从而简化了分布式应用程序的开发。一些应用依然会使用 RMI 来实现通信和交互,今天的内容我们来聊聊 RMI 的那些事儿。

一、先来了解一下概念

RMI原理

RMI的基本思想是远程方法调用。客户端调用远程方法时,实际上是发送一个调用请求到服务器,由服务器执行该方法,并将结果返回给客户端。RMI通过存根(Stub)和骨架(Skeleton)类来实现远程调用,存根位于客户端,而骨架位于服务器端。

RMI组件

  1. 远程接口
    :必须继承自
    java.rmi.Remote
    接口,并声明抛出
    RemoteException
  2. 远程对象
    :实现了远程接口的类。
  3. RMI服务器
    :提供远程对象,并处理客户端的调用请求。
  4. RMI客户端
    :发起远程方法调用请求。
  5. 注册服务(Registry)
    :提供服务注册与获取,类似于目录服务。

数据传递

RMI使用Java序列化机制来传递数据。客户端将方法参数序列化后通过网络发送给服务器,服务器反序列化参数并执行远程方法,然后将结果序列化回传给客户端。

RMI案例

以下是一个简单的RMI案例,包括服务器和客户端的实现思路,下文V 将再用代码来解释:

服务器端

  1. 实现一个远程接口,例如
    PersonController
    ,包含一个远程方法
    queryName
  2. 创建该接口的具体实现类
    PersonControllerImpl
    ,并在其中实现远程方法。
  3. 在服务器的
    main
    方法中,实例化远程对象,创建RMI注册表,并使用
    Naming.rebind
    将远程对象绑定到指定名称。

客户端

  1. 通过
    Naming.lookup
    方法,使用RMI注册表提供的名称获取远程对象的存根。
  2. 调用存根上的方法,就像调用本地方法一样,实际上是在调用服务器上的远程方法。

RMI的局限性

  • 语言限制
    :RMI是Java特有的技术,不能直接用于非Java应用程序。
  • 安全性问题
    :RMI的序列化机制可能带来安全风险,不建议将1099端口暴露在公网上。
  • 性能和扩展性
    :RMI的性能受网络延迟和带宽影响,且在高并发情况下可能面临扩展性限制。

RMI的应用场景

RMI适用于需要Java程序之间进行远程通信的场景,如分布式银行系统、游戏服务器、股票交易系统和网上商城等。接下来一起看一个简单的案例使用吧。

二、案例使用

先来搞一个简单的Java RMI服务器端和客户端的实现案例。这个案例中,服务器端将提供一个名为
HelloWorld
的远程服务,客户端将调用这个服务并打印返回的问候语。

服务器端实现

  1. 定义远程接口

    服务器和客户端都需要这个接口。它必须继承自
    java.rmi.Remote
    接口,并且所有远程方法都要声明抛出
    RemoteException
import java.rmi.Remote;
import java.rmi.RemoteException;

public interface HelloWorld extends Remote {
    String sayHello() throws RemoteException;
}
  1. 实现远程接口

    创建一个实现了上述接口的类,并实现远程方法。
import java.rmi.server.UnicastRemoteObject;
import java.rmi.RemoteException;

public class HelloWorldImpl extends UnicastRemoteObject implements HelloWorld {
    protected HelloWorldImpl() throws RemoteException {
        super();
    }

    @Override
    public String sayHello() throws RemoteException {
        return "Hello, World!";
    }
}
  1. 设置RMI服务器

    创建一个主类来设置RMI服务器,绑定远程对象到RMI注册表。
import java.rmi.Naming;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;

public class HelloWorldServer {
    public static void main(String[] args) {
        try {
            // 创建远程对象
            HelloWorld helloWorld = new HelloWorldImpl();
            // 获取RMI注册表的引用,并在指定端口上创建或获取注册表实例
            LocateRegistry.createRegistry(1099);
            // 将远程对象绑定到RMI注册表中,客户端可以通过这个名字访问远程对象
            Naming.bind("rmi://localhost/HelloWorld", helloWorld);
            System.out.println("HelloWorld RMI object bound");
        } catch (Exception e) {
            System.err.println("Server exception: " + e.toString());
            e.printStackTrace();
        }
    }
}

客户端实现

  1. 调用远程服务

    客户端使用RMI注册表的名字来查找远程对象,并调用其方法。
import java.rmi.Naming;
import java.rmi.RemoteException;

public class HelloWorldClient {
    public static void main(String[] args) {
        try {
            // 使用RMI注册表的名字查找远程对象
            HelloWorld helloWorld = (HelloWorld) Naming.lookup("rmi://localhost/HelloWorld");
            // 调用远程方法
            String response = helloWorld.sayHello();
            System.out.println("Response: " + response);
        } catch (Exception e) {
            System.err.println("Client exception: " + e.toString());
            e.printStackTrace();
        }
    }
}

来详细解释吧

  • 远程接口
    (
    HelloWorld
    ): 这是服务器和客户端之间通信的协议。它定义了可以被远程调用的方法。
  • 远程对象实现
    (
    HelloWorldImpl
    ): 这是远程接口的一个实现。RMI调用实际上会调用这个实现中的方法。
  • 服务器
    (
    HelloWorldServer
    ): 负责创建远程对象的实例,并将这个实例绑定到RMI注册表中。这样客户端就可以通过注册表的名字来访问这个对象。
  • 客户端
    (
    HelloWorldClient
    ): 使用RMI注册表的名字来查找服务器上的远程对象,并调用其方法。

接下来就可以编译所有类文件,运行服务器端程序,确保RMI注册表已经启动(在某些Java版本中会自动启动),再运行客户端程序,搞定。注意一下哈,由于RMI使用Java序列化机制,因此客户端和服务器的类路径必须一致或兼容。

三、RMI 在分布式银行系统中的应用

接下来V哥要介绍业务场景下的应用了,拿在分布式银行系统中来说,我们可以使用RMI来实现不同银行分行之间的通信,例如,实现账户信息的查询、转账等操作。以下是一个简化的示例,其中包括两个基本操作:查询账户余额和执行转账,按步骤一步一步来吧。

步骤1: 定义远程接口

首先,定义一个远程接口
BankService
,它将被各个分行实现以提供银行服务。

import java.rmi.Remote;
import java.rmi.RemoteException;

public interface BankService extends Remote {
    double getAccountBalance(String accountNumber) throws RemoteException;
    boolean transferFunds(String fromAccount, String toAccount, double amount) throws RemoteException;
}

步骤2: 实现远程接口

接下来,实现这个接口来创建远程对象,这个对象将提供实际的银行服务。

import java.rmi.server.UnicastRemoteObject;
import java.rmi.RemoteException;
import java.util.HashMap;
import java.util.Map;

public class BankServiceImpl extends UnicastRemoteObject implements BankService {
    private Map<String, Double> accounts = new HashMap<>();

    protected BankServiceImpl() throws RemoteException {
        super();
        // 初始化一些账户信息
        accounts.put("123456789", 5000.00);
        accounts.put("987654321", 1000.00);
    }

    @Override
    public double getAccountBalance(String accountNumber) throws RemoteException {
        return accounts.getOrDefault(accountNumber, 0.00);
    }

    @Override
    public boolean transferFunds(String fromAccount, String toAccount, double amount) throws RemoteException {
        if (accounts.containsKey(fromAccount) && accounts.get(fromAccount) >= amount) {
            accounts.put(fromAccount, accounts.get(fromAccount) - amount);
            accounts.merge(toAccount, amount, Double::sum);
            return true;
        }
        return false;
    }
}

步骤3: 设置RMI服务器

服务器端将创建
BankService
的远程对象实例,并将其绑定到RMI注册表中。

import java.rmi.Naming;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;

public class BankServer {
    public static void main(String[] args) {
        try {
            LocateRegistry.createRegistry(1099); // 创建RMI注册表
            BankService bankService = new BankServiceImpl();
            Naming.rebind("//localhost/BankService", bankService); // 绑定远程对象
            System.out.println("BankService is ready for use.");
        } catch (Exception e) {
            System.err.println("Server exception: " + e.toString());
            e.printStackTrace();
        }
    }
}

步骤4: 实现RMI客户端

客户端将使用RMI注册表的名字来查找远程对象,并调用其方法。

import java.rmi.Naming;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;

public class BankClient {
    public static void main(String[] args) {
        try {
            BankService bankService = (BankService) Naming.lookup("//localhost/BankService");
            System.out.println("Account balance: " + bankService.getAccountBalance("123456789"));
            
            // 执行转账操作
            boolean isTransferSuccess = bankService.transferFunds("123456789", "987654321", 200.00);
            if (isTransferSuccess) {
                System.out.println("Transfer successful.");
            } else {
                System.out.println("Transfer failed.");
            }
            
            // 再次查询余额
            System.out.println("New account balance: " + bankService.getAccountBalance("123456789"));
        } catch (RemoteException | NotBoundException e) {
            System.err.println("Client exception: " + e.toString());
            e.printStackTrace();
        }
    }
}

来详细解释一下

  • 远程接口
    (
    BankService
    ): 定义了两个方法:
    getAccountBalance
    用于查询账户余额,
    transferFunds
    用于执行转账操作。
  • 远程对象实现
    (
    BankServiceImpl
    ): 实现了
    BankService
    接口。它使用一个
    HashMap
    来模拟账户和余额信息。
  • 服务器
    (
    BankServer
    ): 设置了RMI服务器,将
    BankService
    的实现绑定到RMI注册表中,供客户端访问。
  • 客户端
    (
    BankClient
    ): 查找RMI注册表中的
    BankService
    服务,并调用其方法来查询余额和执行转账。

撸完代码后,编译所有类文件,运行服务器端程序
BankServer
,再运行客户端程序
BankClient
,测试效果吧。

最后

最后V哥要提醒一下,在实际的银行系统中,当然还需要考虑安全性、事务性、持久性以及错误处理等多方面的因素,RMI的网络通信也需要在安全的网络环境下进行,以防止数据泄露或被篡改。你在应用中是怎么使用 RMI 的,欢迎关注威哥爱编程,一起交流一下哈。

企业在选择设备管理方案时,常常面对ABM和MDM的选择。ABM和MDM各有其独特的优点和限制,并且结合使用能带来更加灵活和强大的设备管理能力。本文将深入比较ABM和MDM的不同之处,并解释如何结合使用这两种工具以实现最优管理。

Apple Business Manager (ABM)

概述

Apple Business Manager是一种集中管理Apple设备、应用程序和内容的解决方案,专为企业设计。通过ABM,企业可以更高效地配置和管理其Apple设备,简化大规模部署,并保持应用和内容的统一性。

优点

  1. 集中管理


    • 定义
      :通过一个平台集中管理所有Apple设备、应用和内容。
    • 优势
      :简化了设备注册和配置流程,并提供集中应用购买和分发功能。
  2. 自动设备注册(ADE, Apple Deployment Enrollment)


    • 定义
      :允许新的Apple设备在初次开机时自动注册到ABM,并附带预定义的配置和应用。
    • 优势
      :极大简化了新设备的配置和管理,不需要IT人员手动操作。
  3. 更高的安全性和控制


    • 定义
      :提供强制执行设备安全政策的能力,并确保软件的完整性和合法性。
    • 优势
      :能锁定设备功能,从而确保设备的合规性和安全性。

局限

  1. 需要新的苹果设备


    • 定义
      :ABM的优势更多体现在新的Apple设备上,这些设备需通过苹果或授权经销商购买。
    • 局限
      :老旧设备无法自动注册,需要手动配置。
  2. 有限的设备种类


    • 定义
      :主要支持Apple设备。
    • 局限
      :对于需要管理混合设备(例如不同品牌和平台设备)的企业来说,ABM的适用性有限。

Mobile Device Management (MDM)

概述

Mobile Device Management是一套用于远程管理和配置移动设备的软件解决方案。通过MDM,企业可以管理各种类型和品牌的设备,涵盖从安全策略到应用分发的广泛功能。

优点

  1. 广泛兼容性


    • 定义
      :支持多种设备类型和品牌,包括iOS、Android、Windows等。
    • 优势
      :适用于有异构设备环境的企业,可以集中管理不同品牌和型号的设备。
  2. 灵活管理


    • 定义
      :可以根据企业需求定制化管理设备,并灵活分配应用、配置安全策略。
    • 优势
      :企业可以根据实际需要设定不同的策略和管理方式,灵活性强。
  3. 持续适用


    • 定义
      :能够管理市面上已经使用多年的设备,而不局限于新购买的设备。
    • 优势
      :企业无需更换现有设备即可实现集中管理,节省成本。

局限

  1. 较为复杂的初始配置


    • 定义
      :相比ABM,MDM的初始配置需要更多的手动操作。
    • 局限
      :需要专业技术人员进行初始设备注册和配置,复杂度较高。
  2. 需要对设备进行手动注册


    • 定义
      :旧设备需要通过手动方式加入管理系统。
    • 局限
      :增加了工作量和时间成本。

结合使用ABM和MDM

尽管ABM和MDM各有优点和局限,但两者结合使用可以充分发挥其各自的优势。结合使用ABM和MDM的方法如下:

管理新设备与现有设备

  1. 注册并配置ABM


    • 步骤

      1. 访问
        Apple Business Manager注册页面
        进行注册。
      2. 登录ABM平台,配置初始设置,包括添加管理员和配置MDM服务器。
    • 优势
      :通过ABM管理新设备使企业能够自动化配置流程,减少IT运营成本。
  2. 新设备通过ABM注册


    • 步骤

      1. 确保新购买的苹果设备通过苹果或授权经销商,以便自动加入ABM。
      2. 在购买时,提供企业ABM账户信息,使设备在出厂时即绑定到企业ABM账户。
    • 优势
      :利用自动设备注册功能,新设备开箱即用,附带预先配置的设置和应用。
  3. 现有设备接入MDM


    • 步骤

      1. 使用手动注册方法将现有设备加入MDM管理。
      2. 可以通过配置文件或手动输入方式将这些设备注册到MDM。
    • 优势
      :高效地管理已经在使用中的设备,避免设备更新的高额成本。

实现统一管理

  1. 使用支持ABM和MDM的统一平台

    • 定义
      :选择能够与ABM无缝集成的MDM解决方案,实现统一管理。
    • 优势
      :通过一个平台即可管理新旧设备,提高管理效率。

示例代码

假设你正在使用一个支持RESTful API的MDM解决方案,可以参考下面的代码示例,通过API将现有设备(手动注册的设备)加入MDM系统:

import requests
import json

# MDM server API URL(示例)
mdm_server_url = 'https://mdm.yourcompany.com/api/v1/register_device'

# 认证信息(请使用实际的Token或凭证)
headers = {
    'Authorization': 'Bearer YOUR_MDM_API_TOKEN',
    'Content-Type': 'application/json'
}

# 包含设备信息的负载
payload = {
    'device_name': 'Employee-iPhone',
    'serial_number': 'SERIAL_NUMBER_HERE',
    'os_version': 'iOS 15.0',
    'owner': {
        'user_id': 'USER_ID_HERE',
        'department': 'Sales'
    }
}

# 发送注册请求到MDM服务器
response = requests.post(mdm_server_url, headers=headers, data=json.dumps(payload))

# 检查响应结果
if response.status_code == 200:
    print('设备已成功注册到MDM服务器。')
else:
    print(f'注册设备失败:{response.status_code}, {response.text}')

结论

  • ABM适用于新的苹果设备
    :通过自动设备注册和集中管理,适合新购买的苹果设备,简化部署和配置流程。
  • MDM适用于已有的设备
    :无论设备的品牌或操作系统,可以灵活管理企业已有设备,避免设备更新成本。
  • 结合使用
    :利用ABM和MDM的结合,使企业可以灵活管理新旧设备,提高管理效率和安全性。

企业在选择设备管理方案时,需要根据具体情况和需求,选择最适合的策略,并充分利用ABM和MDM的组合,以实现最佳的设备管理效果。这样不仅可以简化新设备的配置流程,也能有效管理现有设备,降低成本,提高运营效率。

Kotlin 布尔值

在编程中,您经常需要一种只能有两个值的数据类型,例如:

  • 是 / 否
  • 开 / 关
  • 真 / 假

为此,Kotlin 有一种布尔数据类型,可以取
true

false
值。

布尔值

布尔类型可以用
Boolean
关键字声明,并且只能取
true

false
值:

示例

val isKotlinFun: Boolean = true
val isFishTasty: Boolean = false
println(isKotlinFun)   // 输出 true
println(isFishTasty)   // 输出 false 

就像您在前几章中学到的其他数据类型一样,上面的示例也可以在不指定类型的情况下编写,因为 Kotlin 足够智能,可以理解变量是布尔类型:

示例

val isKotlinFun = true
val isFishTasty = false
println(isKotlinFun)   // 输出 true
println(isFishTasty)   // 输出 false 

布尔表达式

布尔表达式返回一个布尔值:
true

false

您可以使用比较运算符,例如大于(
>
)运算符,来判断表达式(或变量)是否为真:

示例

val x = 10
val y = 9
println(x > y) // 返回 true,因为 10 大于 9

或者更简单:

示例

println(10 > 9) // 返回 true,因为 10 大于 9

在下面的示例中,我们使用等于(
==
)运算符来评估表达式:

示例

val x = 10
println(x == 10) // 返回 true,因为 x 的值等于 10

示例

println(10 == 15) // 返回 false,因为 10 不等于 15

表达式的布尔值是所有 Kotlin 比较和条件的基础。

在下一章中,您将学习更多关于条件的内容。

Kotlin 条件和
If..Else

Kotlin 支持数学中的常见逻辑条件:

  • 小于:
    a < b
  • 小于或等于:
    a <= b
  • 大于:
    a > b
  • 大于或等于:
    a >= b
  • 等于:
    a == b
  • 不等于:
    a != b

您可以使用这些条件为不同的决策执行不同的操作。

Kotlin 有以下条件语句:

  • 使用
    if
    指定在条件为
    true
    时要执行的一段代码
  • 使用
    else
    指定在条件为
    false
    时要执行的一段代码
  • 使用
    else if
    在第一个条件为
    false
    时测试新的条件
  • 使用
    when
    指定多个替代代码块来执行

注意:与
Java
不同,
if..else
可以在 Kotlin 中用作语句或表达式(为变量赋值)。请参阅页面底部的示例以更好地理解它。

Kotlin
if

使用
if
指定在条件为
true
时要执行的一段代码。

语法

if (condition) {
  // 如果条件为 true 要执行的代码块
}

注意,
if
是小写字母。大写字母(
If

IF
)会产生错误。

在下面的示例中,我们测试两个值以确定
20
是否大于
18
。如果条件为
true
,打印一些文本:

示例

if (20 > 18) {
  println("20 大于 18")
}

我们还可以测试变量:

示例

val x = 20
val y = 18
if (x > y) {
  println("x 大于 y")
}

示例解释

在上面的示例中,我们使用两个变量
x

y
来测试
x
是否大于
y
(使用
>
运算符)。由于
x

20

y

18
,并且我们知道
20
大于
18
,因此我们在屏幕上打印
"x 大于 y"

Kotlin
else

使用
else
指定在条件为
false
时要执行的一段代码。

语法

if (condition) {
  // 如果条件为 true 要执行的代码块
} else {
  // 如果条件为 false 要执行的代码块
}

示例

val time = 20
if (time < 18) {
  println("Good day.")
} else {
  println("Good evening.")
}
// 输出 "Good evening."

示例解释

在上面的示例中,
time

20
)大于
18
,因此条件为
false
,因此我们转到
else
条件并在屏幕上打印
"Good evening"
。如果
time
小于
18
,程序会打印
"Good day"

Kotlin
else if

使用
else if
在第一个条件为
false
时指定一个新条件。

语法

if (condition1) {
  // 如果 condition1 为 true 要执行的代码块
} else if (condition2) {
  // 如果 condition1 为 false 且 condition2 为 true 要执行的代码块
} else {
  // 如果 condition1 和 condition2 都为 false 要执行的代码块
}

示例

val time = 22
if (time < 10) {
  println("Good morning.")
} else if (time < 20) {
  println("Good day.")
} else {
  println("Good evening.")
}
// 输出 "Good evening."

示例解释

在上面的示例中,
time

22
)大于
10
,因此第一个条件为
false
。在
else if
语句中的下一个条件也为
false
,因此我们转到
else
条件,因为
condition1

condition2
都为
false
,并在屏幕上打印
"Good evening"

然而,如果
time

14
,我们的程序会打印
"Good day"

Kotlin
If..Else
表达式

在 Kotlin 中,您还可以将
if..else
语句用作表达式(为变量赋值并返回):

示例

val time = 20
val greeting = if (time < 18) {
  "Good day."
} else {
  "Good evening."
}
println(greeting)

使用
if
作为表达式时,您还必须包含
else
(必需)。

注意:如果
if
只有一个语句,可以省略大括号
{}

示例

fun main() {
  val time = 20
  val greeting = if (time < 18) "Good day." else "Good evening."
  println(greeting)
}

提示:这个示例类似于
Java
中的“三元运算符”(简写的
if...else
)。

最后

为了方便其他设备和平台的小伙伴观看往期文章:

微信公众号搜索:
Let us Coding
,关注后即可获取最新文章推送

看完如果觉得有帮助,欢迎 点赞、收藏、关注

首先感谢 江科大 的stm32入门课程 受益匪浅。推荐有兴趣的朋友去看看。

先看看我用的矩阵键盘是啥样的(很常见的一种)

接线如图(其他型号根据自己需求接上GPIO口)

代码基于
stm大善人
的代码修改而来,讲的很详细,非常感谢。

直接上代码:

头文件Key4x4.h

#ifndef __KEY4x4_H#define __KEY4x4_H

void KEY_4x4_Init(void);void KEY_Scan(void);
u16 Key_Read(
void);#endif

主体文件Key4x4.c

#include "stm32f10x.h"#include"Delay.h"u8 anxia= 0;
u8 key
= 1;
u16 line[
4] = {0x00fe , 0x00fd , 0x00fb ,0x00f7};
u16 off
= 0x00ff; //全部引脚置为1 u16 keys[16] ={49, 50, 51, 65,52, 53, 54, 66,55, 56, 57, 67,42, 48, 35, 68,
};
void KEY_4x4_Init(void){

GPIO_InitTypeDef GPIO_InitStructre;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA , ENABLE);
//使能 GPIOA 的时钟//第一组 GPIO_InitStructre.GPIO_Pin = GPIO_Pin_0|GPIO_Pin_1|GPIO_Pin_2|GPIO_Pin_3;
GPIO_InitStructre.GPIO_Mode
= GPIO_Mode_Out_PP; //推挽输出 GPIO_InitStructre.GPIO_Speed =GPIO_Speed_50MHz;
GPIO_Init(GPIOA ,
&GPIO_InitStructre);
GPIO_SetBits(GPIOA , GPIO_Pin_0
|GPIO_Pin_1|GPIO_Pin_2|GPIO_Pin_3);//第二组数据 GPIO_InitStructre.GPIO_Pin = GPIO_Pin_4|GPIO_Pin_5|GPIO_Pin_6|GPIO_Pin_7;
GPIO_InitStructre.GPIO_Mode
=GPIO_Mode_IPU;
GPIO_Init(GPIOA ,
&GPIO_InitStructre);
GPIO_SetBits(GPIOA , GPIO_Pin_4
|GPIO_Pin_5|GPIO_Pin_6|GPIO_Pin_7);

}
voidDo_Click(uint16_t gpio_pin_x , u8 num){
anxia
= 1;
key
=num;while(!GPIO_ReadInputDataBit(GPIOA , gpio_pin_x));
}
voidKEY_Click_Listener(u8 num){if((GPIO_ReadInputDataBit(GPIOA , GPIO_Pin_4)==0)||(GPIO_ReadInputDataBit(GPIOA , GPIO_Pin_5)==0)||(GPIO_ReadInputDataBit(GPIOA , GPIO_Pin_6)==0)||(GPIO_ReadInputDataBit(GPIOA , GPIO_Pin_7)==0)){
Delay_ms(
10);if(0 ==GPIO_ReadInputDataBit(GPIOA , GPIO_Pin_4)){
Do_Click(GPIO_Pin_4 , num
+0);
}
else if(0 ==GPIO_ReadInputDataBit(GPIOA , GPIO_Pin_5)){
Do_Click(GPIO_Pin_5 , num
+1);
}
else if(0 ==GPIO_ReadInputDataBit(GPIOA , GPIO_Pin_6)){
Do_Click(GPIO_Pin_6 , num
+2);
}
else if(0 ==GPIO_ReadInputDataBit(GPIOA , GPIO_Pin_7)){
Do_Click(GPIO_Pin_7 , num
+3);
}
else{
anxia
= 0;
GPIO_Write(GPIOA , off);
}
}
}
voidKEY_Scan(){//第一行 1111 1110 GPIO_Write(GPIOA , line[0]);
KEY_Click_Listener(
1);//第二行 GPIO_Write(GPIOA , line[1]);
KEY_Click_Listener(
5);//第三行 GPIO_Write(GPIOA , line[2]);
KEY_Click_Listener(
9);//第四行 GPIO_Write(GPIOA , line[3]);
KEY_Click_Listener(
13);
}

u16 Key_Read(){
return keys[key-1];
}

主要代码说明:

初始化配置 (KEY_4x4_Init):

  • 使能GPIOA模块的时钟。
  • 配置GPIOA的前四个引脚(GPIO_Pin_0至GPIO_Pin_3)为推挽输出模式,用于键盘行线的扫描。
  • 设置GPIOA的后四个引脚(GPIO_Pin_4至GPIO_Pin_7)为上拉输入模式,用于检测键盘列线的状态。
//第一组
    GPIO_InitStructre.GPIO_Pin  = GPIO_Pin_0|GPIO_Pin_1|GPIO_Pin_2|GPIO_Pin_3;
GPIO_InitStructre.GPIO_Mode
= GPIO_Mode_Out_PP; //推挽输出 GPIO_InitStructre.GPIO_Speed =GPIO_Speed_50MHz;
GPIO_Init(GPIOA ,
&GPIO_InitStructre);
GPIO_SetBits(GPIOA , GPIO_Pin_0
|GPIO_Pin_1|GPIO_Pin_2|GPIO_Pin_3);//第二组数据 GPIO_InitStructre.GPIO_Pin = GPIO_Pin_4|GPIO_Pin_5|GPIO_Pin_6|GPIO_Pin_7;
GPIO_InitStructre.GPIO_Mode
=GPIO_Mode_IPU;
GPIO_Init(GPIOA ,
&GPIO_InitStructre);
GPIO_SetBits(GPIOA , GPIO_Pin_4
|GPIO_Pin_5|GPIO_Pin_6|GPIO_Pin_7);

按键检测 (KEY_Click_Listener):

  • 检测列线状态,如果有键按下,则调用
    Do_Click
    函数记录按键信息并等待按键释放。
  • 使用延时函数
    Delay_ms
    来消除抖动。
voidKEY_Click_Listener(u8 num){if((GPIO_ReadInputDataBit(GPIOA , GPIO_Pin_4)==0)||(GPIO_ReadInputDataBit(GPIOA , GPIO_Pin_5)==0)
||(GPIO_ReadInputDataBit(GPIOA , GPIO_Pin_6)==0)||(GPIO_ReadInputDataBit(GPIOA , GPIO_Pin_7)==0)){
Delay_ms(
10);if(0 ==GPIO_ReadInputDataBit(GPIOA , GPIO_Pin_4)){
Do_Click(GPIO_Pin_4 , num
+0);
}
else if(0 ==GPIO_ReadInputDataBit(GPIOA , GPIO_Pin_5)){
Do_Click(GPIO_Pin_5 , num
+1);
}
else if(0 ==GPIO_ReadInputDataBit(GPIOA , GPIO_Pin_6)){
Do_Click(GPIO_Pin_6 , num
+2);
}
else if(0 ==GPIO_ReadInputDataBit(GPIOA , GPIO_Pin_7)){
Do_Click(GPIO_Pin_7 , num
+3);
}
else{
anxia
= 0;
GPIO_Write(GPIOA , off);
}
}
}
voidDo_Click(uint16_t gpio_pin_x , u8 num){
anxia
= 1;
key
=num;while(!GPIO_ReadInputDataBit(GPIOA , gpio_pin_x));
}

扫描过程 (KEY_Scan):

  • 循环扫描每一行,通过改变行线的状态来检测是否有键按下。
  • 调用
    KEY_Click_Listener
    函数来处理每一行的按键检测。

读取按键值 (Key_Read):

  • 返回当前按下的键对应的数值。

补充说明:

u16 line[4] = {0x00fe , 0x00fd , 0x00fb ,0x00f7};
u16 off
= 0x00ff; //全部引脚置为1 u16 keys[16] ={49, 50, 51, 65,52, 53, 54, 66,55, 56, 57, 67,42, 48, 35, 68,
};

line 定义 了4个16进制的数值分别转成二进制
0000 0000 1111 1110  0x00fe

0000 0000 1111 1101 0x00fd

0000 0000 1111 1011 0x00fb

0000 0000 1111 0111 0x00f7

低4位为行 高四位为列

设置0就是给对应行设置低电平

这样我们在scan的代码就能看出来 扫描的做法就是先给传入的行line[x] 设置低电平

所有列都是高电平当扫描到某一列为低电平时就说明这一列被点击了。

循环从第一列到第四列设置低电平直到检测到某一列也变成低电平

假设1被点击 则1这一列也是低电平

就变成第一行 第一列低电平

这样就能确定1被点击。给对应参数赋值1即可

再根据定义的keys (askII 码表对应数值 )

主要代码就是上面这些,其他代码只要复制 江科大OLED的课件源码即可

使用方式 man.c

#include "stm32f10x.h"                  //Device header
#include "Delay.h"#include"OLED.h"#include"Key4x4.h"

int main(void)
{
/*模块初始化*/OLED_Init();//OLED初始化 KEY_4x4_Init();/*OLED显示*/OLED_ShowString(1, 1, "in put:"); //1行1列显示字符A u8 num = 0;while (1)
{
KEY_Scan();
num
=Key_Read();
OLED_ShowChar(
1 ,8,num);
}
}