Skip to content

Files

Latest commit

590e50c · Jan 11, 2022

History

History
1390 lines (730 loc) · 29.2 KB

File metadata and controls

1390 lines (730 loc) · 29.2 KB

二、使用短信的自动化

简介

我们已经看到了 Twilio 如何使我们能够以直观和直接的方式实现短信通信,它甚至可以用于使用 2FA 增强身份验证。

利用我们到目前为止学到的核心原则和示例,在本章中,我们将重点讨论如何在这些知识的基础上创建一些小而有用的应用程序,我们可以使用这些应用程序来优化和自动化业务流程,例如约会提醒和自动即时销售线索提醒。

到本章结束时,您应该对如何使用短信实现这些解决方案有了广泛的了解,并对两者背后的整体业务逻辑有了很好的理解。

因为本章中的一些示例需要数据存储,所以我们将使用 Azure 表存储来保持我们的数据在微软云上的安全性和易访问性。

本章中给出的示例易于理解和实施,同时在业务环境中也很有用。我们将继续使用我们在第 1 章中开发的TwilioCore类。

使用 Azure 表存储作为我们的后端

你可能知道, Azure 是微软令人敬畏的云服务平台。它提供广泛的服务,并在多个平台上工作。

存储是其许多有用的服务之一,Azure 包括一个名为表存储的表子服务。

表存储服务使用表格格式来存储数据。每个记录代表一个实体,列代表该实体的各种属性(表中的字段)。

每个实体都有一对唯一标识它的键(一个PartitionKey和一个RowKey)。每个实体还有一个时间戳列,Table 服务使用该列来了解实体上次更新的时间(这是自动发生的,时间戳值不能被覆盖——它由服务本身在内部控制)。

关于存储和表存储服务如何工作的大量文档可以直接在 Azure 网站上找到,这是一个非常宝贵的资源,值得查看,以便更好地了解这两种服务。

然而,我们将快速探索如何使用 Azure 表存储启动和运行。

为了开始使用 Microsoft Azure 上的存储实例,您需要使用 Microsoft 帐户登录或注册 Azure。你可以通过访问 https://azure.microsoft.com 来实现。

图 25:微软 Azure 登录屏幕

注册或登录 Azure 门户后,您可以浏览 Azure 服务列表并选择存储帐户(经典)。

图 26:过滤 Azure 服务列表

当您选择存储帐户(经典)时,您将看到以下屏幕,允许您添加新的存储帐户。

图 27:存储帐户(经典)面板

要添加新的存储帐户(经典),请单击添加。这样做将显示一个屏幕,允许您添加帐户的名称并选择帐户将托管的 Azure 位置。

图 27:创建新存储帐户(经典)面板

选择名称并选择离您最近的位置后,单击创建。Azure 将立即创建存储帐户。一旦创建,它将如图 28 所示。您将需要您的访问键,以便从您的代码或任何外部工具与服务交互。通过单击“键”,可以找到这些键。

图 28:存储帐户(经典)面板

随着 Azure Storage 帐户的准备就绪,您可以使用一个名为 Azure Storage Explorer 的开源便捷工具,该工具将允许您轻松连接到您的 Storage 帐户,并创建、更新、删除和查看任何存储表和数据。

图 29:CodePlex 上的 Azure 存储资源管理器

从 CodePlex 下载并解压缩 zip 文件后,您会发现一个安装该工具的可执行文件。

虽然请注意 Azure Storage Explorer 应用程序只能在 Windows 上运行,但是安装向导很容易理解,只需点击几下鼠标即可。

安装后,磁盘上将会有以下文件。

图 30:安装后的 Azure 存储资源管理器

要运行该工具,请双击 Azure 存储资源管理器快捷方式。应用程序运行后,您需要将您的 Azure Storage 帐户连接到它。这可以通过单击添加帐户来完成。

图 31: Azure 存储资源管理器添加帐户

当您单击添加帐户时,您将被要求添加您的存储帐户名称和密钥,如图 32 所示。

图 32: Azure 存储资源管理器添加帐户设置

输入请求的详细信息后,您应该单击测试访问,以检查连接是否正常。如果成功,您可以单击保存。

存储这些详细信息后,下次打开 Azure 存储资源管理器应用程序时,您将能够从“添加帐户”按钮旁边的下拉列表中访问您的存储帐户。

随着我们存储帐户的连接,现在让我们探索如何使用 Twilio 实现一些短信自动化,并使用表存储作为我们的后端。

预约提醒

让客户满意可能是当今任何公司面临的最大挑战。幸运的是,正确的态度和一点自动化可以使客户满意成为一个可实现的目标。

预约提醒是一种简单有效的方法,可以在即将到来的预约之前自动联系客户。我们可以编写代码来实现这一点——我们将使用已经编写的TwilioCore类。

让我们先思考一下如何设计这个解决方案。我们需要能够创建未来的约会DateTime,我们可以将其存储在 Azure Table Storage 上。这个未来DateTime将是约会的预定日期。我们还需要一个适当的流程来监视预约时间,从表存储中检索信息,并向该预约的注册电话号码发送短信。

我们将首先探索允许我们与 Azure 表存储交互的第一个操作。为了做到这一点,让我们得到窗口。安装存储 NuGet 包,然后创建一个Appointment类。

图 33:窗口。NuGet 上的存储库

要安装 NuGet 软件包,只需单击“安装”。这将添加对 Visual Studio 项目的必要引用。

在进入代码之前,您需要使用 Azure 存储资源管理器创建存储表。让我们简单地说出约会的名字。

图 34:使用 Azure 存储资源管理器简单地创建约会表

安装了 NuGet 包,我们可以创建AppointmentDataAppointment类。

代码清单 14:约会和约会数据类

  using System;
  using Microsoft.WindowsAzure.Storage;
  using
  Microsoft.WindowsAzure.Storage.Table;

  namespace TwilioSuccinctly
  {

  public class AppointmentData : TableEntity

  {

  public AppointmentData(string p, string d)

  {

  PartitionKey = p;

  RowKey = d;

  }

  public AppointmentData()

  {

  }

  public string Id { get; set;
  }

  public string Name { get; set;
  }

  public string PhoneNumber { get; set; }

  public string Time { get; set;
  }

  public bool
  Sent { get; set;
  }

  }

  public class Appointment : IDisposable

  {

  private const string cStrConnStr = 

  "<< Your Azure Storage Connection String >>";

  private const string cStrAppTable = "AppointmentsSuccinctly";

  protected bool
  disposed;

  public AppointmentData Data { get; set;
  }

  public Appointment(string id, string n, string pn, DateTime
  t)

  {

  string tt = t.Day.ToString().PadLeft(2, '0') +

  t.Month.ToString().PadLeft(2, '0') +

  t.Year.ToString() + 

  t.Hour.ToString().PadLeft(2, '0') + 

  t.Minute.ToString().PadLeft(2, '0') +

  t.Second.ToString().PadLeft(2, '0');

  Data = new AppointmentData(pn, tt);

  Data.Sent = false;

  Data.Id = id;

  Data.Name = n;

  Data.PhoneNumber = pn;

  Data.Time = tt; 

  }

  public Appointment(string pn)

  {

  string tt = DateTime.Now.Day.ToString().PadLeft(2, '0') +

  DateTime.Now.Month.ToString().PadLeft(2, '0') +

  DateTime.Now.Year.ToString() +

  DateTime.Now.Hour.ToString().PadLeft(2, '0') +

      DateTime.Now.Minute.ToString().PadLeft(2, '0') +

  DateTime.Now.Second.ToString().PadLeft(2, '0');

  Data = new AppointmentData(pn, tt);

  Data.PhoneNumber = pn;

  Data.Time = tt;

  }

  ~Appointment()

  {

  Dispose(false);

  }

  public virtual void Dispose(bool disposing)

  {

  if (!disposed)

  {

  if (disposing)

  {

  }

  Data = null;

  }

  disposed = true;

  }

  public void
  Dispose()

  {

  Dispose(true);

  GC.SuppressFinalize(this);

  }

  public bool
  Create()

  {

  bool res = false;

  try

  {

  var storageAccount = 

  CloudStorageAccount.Parse(cStrConnStr);

  CloudTableClient tableClient = 

  storageAccount.CreateCloudTableClient();

  CloudTable table = 

  tableClient.GetTableReference(cStrAppTable);

  TableOperation retrieveOperation = 

  TableOperation.Retrieve<AppointmentData>

  (Data.PhoneNumber, Data.PhoneNumber);

  TableResult retrievedResult = 

  table.Execute(retrieveOperation);

  AppointmentData updateEntity = 

  (AppointmentData)retrievedResult.Result;

  if (updateEntity == null)

  {

  updateEntity = new AppointmentData(Data.PhoneNumber, 

  Data.Time);

  updateEntity.Id = Data.Id;

  updateEntity.Name = Data.Name;

      updateEntity.PhoneNumber = Data.PhoneNumber;

  updateEntity.Time = Data.Time;

  }

  TableOperation ins = TableOperation.Insert(updateEntity);

  table.Execute(ins);

  res = true;

  }

  catch (Exception ex)

  {

  Console.WriteLine(ex.ToString());

  }

  return res;

  }

  public AppointmentData Get()

  {

  AppointmentData res = null;

  try

  {

  int tDay = DateTime.Now.Day;

  int tMonth = DateTime.Now.Month;

  int tYear = DateTime.Now.Year;

  int tHour = DateTime.Now.Hour;

  int tMin = DateTime.Now.Minute;

  int tSec = DateTime.Now.Second;

  DateTime tdy = new DateTime(tYear, tMonth, tDay, tHour, 

  tMin, tSec);

  var storageAccount = 

  CloudStorageAccount.Parse(cStrConnStr);

  CloudTableClient tableClient = 

  storageAccount.CreateCloudTableClient();

  CloudTable table = 

  tableClient.GetTableReference(cStrAppTable);

  TableQuery<AppointmentData> query = new 

  TableQuery<AppointmentData>().Where(

  TableQuery.GenerateFilterCondition("PartitionKey", 

  QueryComparisons.Equal, Data.PhoneNumber));

  foreach (AppointmentData en in table.ExecuteQuery(query))

  {

  int day = Convert.ToInt32(en.Time.Substring(0, 2));

  int month = Convert.ToInt32(en.Time.Substring(2, 2));

  int year = Convert.ToInt32(en.Time.Substring(4, 4));

  int hour = Convert.ToInt32(en.Time.Substring(8, 2));

  int min = Convert.ToInt32(en.Time.Substring(10, 2));

  int sec = Convert.ToInt32(en.Time.Substring(12, 2));

  DateTime exp = new DateTime(year, month, day, hour, 

  min, sec);

  if (!en.Sent && DateTime.Compare(tdy, exp) >= 0)

  {

  res = en;

  break;

  }

  }

  }

  catch (Exception ex)

  {

  Console.WriteLine(ex.ToString());

  }

  return res;

  }

  public bool
  MarkAsSent(AppointmentData ad)

  {

  bool res = false;

  try

  {

  var storageAccount = 

  CloudStorageAccount.Parse(cStrConnStr);

     CloudTableClient tableClient =     

  storageAccount.CreateCloudTableClient();

  CloudTable table = 

  tableClient.GetTableReference(cStrAppTable);

  TableOperation retrieveOperation = 

  TableOperation.Retrieve<AppointmentData>

  (ad.PhoneNumber, ad.Time);

  TableResult retrievedResult = 

  table.Execute(retrieveOperation);

  AppointmentData updateEntity = 

  (AppointmentData)retrievedResult.Result;

  if (updateEntity != null)

  {

  updateEntity.Sent = ad.Sent;

  TableOperation insertOrReplaceOperation = 

  TableOperation.InsertOrReplace(updateEntity);

  table.Execute(insertOrReplaceOperation);

  res = true;

  }

  }

  catch (Exception ex)

  {

  Console.WriteLine(ex.ToString());

  }

     return res;

  }

  public string FormatTime(string t)

  {

  int day = Convert.ToInt32(t.Substring(0, 2));

  int month = Convert.ToInt32(t.Substring(2, 2));

  int year = Convert.ToInt32(t.Substring(4, 4));

  int hour = Convert.ToInt32(t.Substring(8, 2));

  int min = Convert.ToInt32(t.Substring(10, 2));

  int sec = Convert.ToInt32(t.Substring(12, 2));

  DateTime exp = new DateTime(year, month, day, hour, min, 

  sec);

  return exp.ToString();

  }

  }
  }

让我们分析一下这段代码。我们已经定义了一个AppointmentData类,它表示要存储在 Azure 表存储中的数据。AppointmentData类继承自TableEntity,包含一个PartitionKeyRowKey属性,表存储使用这些属性来索引和查找数据记录。

我们将用户的电话号码存储在PartitionKey ,RowKey上,我们将约会到期的日期和时间保存为字符串,例如 29042016203000,其中 29 是日,04 是月,2016 是年,20 是小时(晚上 8 点),30 是分,00 是秒。

AppointmentData类还包含一个string Id属性、一个代表约会名称的string Name属性、一个包含与PartitionKey相同数据的string PhoneNumber属性、一个包含与RowKey相同数据的string Time属性以及一个代表是否为该约会发送了短信的bool Sent属性。

AppointmentData类将由Appointment类实例化,以便在 Azure 表存储中存储信息。您可以将AppointmentData类视为数据层(模型),将Appointment类视为业务逻辑层(控制器)。

Appointment类的构造函数创建一个 AppointmentData实例,用于在 Azure 上存储约会数据。

使用表存储,DateTime值应存储为string值。FormatTime方法的目的是取一个日期时间string,如 29042016203000,并将其转换为其等效的DateTime表示(2016 年 4 月 29 日晚上 8:30:00)作为string,以便在Console上显示。

Create方法负责在表存储上创建约会提醒。通过将cStrConnStr连接字符串传递给CloudStorageAccount.Parse方法来建立与 Azure 实例的连接。接下来,创建一个CloudTableClient实例,然后使用它来获取对约会简洁表的引用。

调用CloudTable实例的Execute方法时,AppointmentData类作为T调用,Data.PhoneNumber作为PartitionKeyRowKey调用。我们这样做是为了得到一个TableResult对象,这样它的Result属性就可以被转换成一个空的AppointmentData对象。该对象将用于设置相应的约会属性,并通过调用TableOperation.Insert方法插入表存储。最后,CloudTable实例的Execute方法将AppointmentData对象提交给表存储。

MarkAsSent方法的工作方式几乎与Create方法完全相同。主要区别在于MarkAsSent执行的是InsertOrReplace而不是Insert,因此在短信发送后会用值true更新AppointmentData对象的Sent属性。

反过来,Get方法的工作方式几乎与MarkAsSentCreate方法完全相同,但是它创建了一个TableQuery类的实例,然后通过将其与GenerateFilterCondition方法链接来执行Where方法。有了这个,调用CloudTable.ExecuteQuery调用,返回 IEnumerable <AppointmentData>

因为GenerateFilterCondition的过滤是基于PartitionKey的,所以所有电话号码相同的记录都会被检索到。通过循环查看IEnumerable<AppointmentData>结果,并将今天的日期tdy与约会的到期日期exp进行比较,第一个未发送短信的过期约会(Sent属性设置为false)将被检索并返回给调用者方法。

现在让我们实现几个例子,让所有这些都发挥作用。在代码清单 15 中,我们将创建一个AppointmentExample类,该类将创建一些约会并为到期的约会发送短信。

代码清单 15:指定的示例包装类

  using System;
  using System.Timers;

  namespace TwilioSuccinctly
  {

  public class AppointmentExample

  {

  private const string cStrTwilioSid = 

  "<< Your
  Twilio Account SID goes here >>";

  private const string cStrTwilioAuthTk =  

  "<< Your
  Twilio Auth Token goes here >>";

  private const string cStrTwilioSender = 

  "<< Your
  Twilio Number>>";

  private static bool started = false;

  public static Timer Fetch { get; set; }

  public static string PhoneNumber { get; set; }

  public static void InitFetch(int ms)

  {

  started = false;

  Fetch = new Timer();

  Fetch.Interval = ms;

  Fetch.Elapsed += new ElapsedEventHandler(Fetch_Elapsed);

  Fetch.Start();

  }

  public static void StopFetch()

  {

          started = false;

  Fetch.Stop();

  }

  public static void Fetch_Elapsed(object sender, 

  ElapsedEventArgs e)

  {

  if (!started)

  {

  started = true;

  Console.WriteLine(

  "Fetching Oldest
  Elapsed Appointment...");

  using (Appointment a = new Appointment(PhoneNumber))

  {

  AppointmentData ad = a.Get();

  if (ad != null)

  {

  using (TwilioCore t = new 

  TwilioCore(cStrTwilioSid, cStrTwilioAuthTk))

  {

  t.SendSms(cStrTwilioSender, PhoneNumber,

               $"Reminder
  of appointment {ad.Id}{ad.Name} on: {a.FormatTime(ad.Time)}");

  Console.WriteLine(

  $"Reminder of appointment 

  {ad.Id} - {ad.Name} on: 

  {a.FormatTime(ad.Time)}");

  ad.Sent = true;

  }

  a.MarkAsSent(ad);

  }
                  }

  started = false;

  }

  }

  public static bool Create(string id, string n, string pn, 

  DateTime t)

  {

  bool res = false;

  using (Appointment a = new Appointment(id, n, pn, t))

  {

  res = a.Create();

  if (res)

  Console.WriteLine(

  $"Appointment
  created: {id} - {n}
  on {t}");

  else

  Console.WriteLine(

                   $"Could
  not create appointment: {id} - {n}
  on {t}");

  }

  return res;

  }

  }
  }

AppointmentExample类做两件事。它通过其Create方法创建一个约会,并且它还创建一个Fetch Timer对象,该对象执行一个Fetch_Elapsed事件,该事件将检索第一个过期的约会并发送一条短信。

在代码清单 16 中,我们将通过从Main program调用它来实现这一点。

代码清单 16:主程序

  using System;

  namespace TwilioSuccinctly
  {

  public class Program

  {

  public static void CreateSeveralAppointments()

  {

  AppointmentExample.Create("app01", "BurgerKing", 

  "33484464", new
  DateTime(2016, 4, 29, 20, 30, 00));

  AppointmentExample.Create("app02", "Run", "33484464", new

  DateTime(2016, 4, 29, 21, 20, 00));

  AppointmentExample.Create("app03", "Gym", "33484464", new

  DateTime(2016, 4, 29, 22, 05, 00));

  }

  public static void FetchAppointments()

  {

  Console.WriteLine(

  "Waiting for
  Timer (Appointment Fetch)...");

  AppointmentExample.PhoneNumber = "33484464";

  AppointmentExample.InitFetch(1500);

  }

  static void
  Main(string[] args)

  {

  CreateSeveralAppointments();

  Console.ReadLine();

  FetchAppointments();

  Console.ReadLine();

  }

  }
  }

当我们执行Main程序时,我们得到图 35 中创建的约会。

图 35:创建几个约会

创建完这些约会后,我们等待一会儿,让程序寻找第一个过期的约会。

图 36:获取第一个过期约会

然后,我们可以在 Azure 上简单地检查约会表,看看数据在执行程序后是什么样子的。

图 37:Azure 表存储中的约会数据

我们可以清楚地看到 Sent 属性已经设置为 True 为【布尔清】(首次过期)预约。

自动即时销售线索提醒

对于销售人员来说,即时销售线索是达成更多交易的好方法,因为它们允许销售代表在发现感兴趣的客户后立即联系他们。将接触线索的过程自动化是让 Twilio 的力量为您服务的绝佳方式。

收集销售线索的过程始于拥有一个或多个登录页面,您可以在其中从网站访问者那里收集关于您的组织提供的产品或服务的信息。

为了简单起见,我们假设正在收集的信息存储在 Azure 表存储中。这些信息将包括客户的全名、电子邮件地址、电话号码以及他们感兴趣的网络研讨会。还将有一个FollowedUp属性,用于指示是否跟踪了一条线索。

我们将创建一个小应用程序,从 Azure 获取销售线索数据,然后使用我们的TwilioCore类发送短信。

让我们创建一个LeadsCore类,负责检索存储在 Azure 上的销售线索并发送短信。我们将使用 Azure 存储资源管理器在表存储上手动创建销售线索记录。

首先,我们使用存储资源管理器创建一个名为 LeadsSuccinctly 的表。此表将用于跟踪潜在销售线索可能感兴趣的网络研讨会。所有字段都将具有string值,除了FollowedUp字段,它将具有bool值。

表格的PartitionKey应包含销售线索感兴趣的网上研讨会的名称,RowKey应为销售线索在网站上发布的日期和时间的字符串表示。

就像我们处理约会的提醒代码一样,我们将使用一个Timer对象来检索第一个发布的线索。

让我们考虑一下从 Azure 网站上收集的线索,如图 38 所示。

图 38:Azure 表存储上的销售线索数据

让我们写代码。与前面的例子一样,我们将有一个与表存储交互的核心类,然后我们将有一个围绕它的包装类,它实际上实现了业务逻辑。

代码清单 17: LeadsCore 类

  using System;
  using Microsoft.WindowsAzure.Storage;
  using
  Microsoft.WindowsAzure.Storage.Table;

  namespace TwilioSuccinctly
  {

  public class LeadsData : TableEntity

  {

  public LeadsData(string p, string d)

  {

  PartitionKey = p;

  RowKey = d;

  }

  public LeadsData(string p)

  {

  PartitionKey = p;

  RowKey = string.Empty;

  }

  public LeadsData()

  {

  PartitionKey = string.Empty;

  RowKey = string.Empty;

  }

  public string FullName { get; set;
  }

  public string Email { get; set;
  }

  public string Webinar { get; set;
  }

  public bool
  FollowedUp { get; set;
  }

  public string PhoneNumber { get; set; }

  }

  public class LeadsCore : IDisposable

  {

  private const string cStrConnStr = 

  "<< Your
  Azure Storage Connection String >>";

  private const string cStrAppTable = "LeadsSuccinctly";

  protected bool
  disposed;

  public LeadsData Data { get; set;
  }

  public LeadsCore()

  {

  LeadsData ld = new
  LeadsData();

  ld.FullName = string.Empty;

  ld.Email = string.Empty;

  ld.Webinar = string.Empty;

  ld.PhoneNumber = string.Empty;

  }

  ~LeadsCore()

  {

    Dispose(false);

  }

  public virtual void Dispose(bool disposing)

  {

  if (!disposed)

  {

  if (disposing)

  { }

  Data = null;

  }

  disposed = true;

  }

  public void
  Dispose()

  {

  Dispose(true);

  GC.SuppressFinalize(this);

  }

  public LeadsData Get(string webinar)

  {

  LeadsData res = null;

  try

  {

  int tDay = DateTime.Now.Day;

  int tMonth = DateTime.Now.Month;

  int tYear = DateTime.Now.Year;

  int tHour = DateTime.Now.Hour;

  int tMin = DateTime.Now.Minute;

  int tSec = DateTime.Now.Second;

  DateTime tdy = new DateTime

  (tYear, tMonth, tDay,
  tHour, tMin, tSec);

  var storageAccount = 

  CloudStorageAccount.Parse(cStrConnStr);

  CloudTableClient tableClient = 

  storageAccount.CreateCloudTableClient();

  CloudTable table = 

  tableClient.GetTableReference(cStrAppTable);

  TableQuery<LeadsData> query = new 

      TableQuery<LeadsData>().Where(

  TableQuery.GenerateFilterCondition("PartitionKey", 

  QueryComparisons.Equal, webinar));

  foreach (LeadsData en in table.ExecuteQuery(query))

  {

               int day = Convert.ToInt32(

  en.RowKey.Substring(0, 2));

  int month = Convert.ToInt32(

  en.RowKey.Substring(2, 2));

  int year = Convert.ToInt32(

  en.RowKey.Substring(4, 4));

  int hour = Convert.ToInt32(

  en.RowKey.Substring(8, 2));

  int min = Convert.ToInt32(

  en.RowKey.Substring(10, 2));

            int sec = Convert.ToInt32(

  en.RowKey.Substring(12, 2));

  DateTime exp = new DateTime(

  year, month, day, hour, min, sec);

  if (!en.FollowedUp && 

  DateTime.Compare(tdy, exp) >= 0)

  {

  res = en;

  break;

  }

  }

  }

  catch (Exception ex)

  {

     Console.WriteLine(ex.ToString());

  }

  return res;

  }

  public bool
  MarkAsFollowedUp(LeadsData ld)

  {

  bool res = false;

  try

  {

  var storageAccount = 

  CloudStorageAccount.Parse(cStrConnStr);

  CloudTableClient tableClient = 

  storageAccount.CreateCloudTableClient();

  CloudTable table = 

  tableClient.GetTableReference(cStrAppTable);

  TableOperation retrieveOperation = 

  TableOperation.Retrieve<LeadsData>(

  ld.PartitionKey, ld.RowKey);

  TableResult retrievedResult = 

  table.Execute(retrieveOperation);

  LeadsData updateEntity = 

  (LeadsData)retrievedResult.Result;

  if (updateEntity != null)

  {

  updateEntity.FollowedUp = ld.FollowedUp;

  TableOperation insertOrReplaceOperation = 

  TableOperation.InsertOrReplace(updateEntity);

  table.Execute(insertOrReplaceOperation);

  res = true;

  }

  }

  catch (Exception ex)

  {

  Console.WriteLine(ex.ToString());

  }

  return res;

  }

  public string FormatTime(string t)

  {

  int day = Convert.ToInt32(t.Substring(0, 2));

  int month = Convert.ToInt32(t.Substring(2, 2));

  int year = Convert.ToInt32(t.Substring(4, 4));

  int hour = Convert.ToInt32(t.Substring(8, 2));

  int min = Convert.ToInt32(t.Substring(10, 2));

  int sec = Convert.ToInt32(t.Substring(12, 2));

  DateTime exp = new DateTime(

  year, month, day, hour, min, sec);

  return exp.ToString();

  }

  }
  }

我们已经定义了一个LeadsData类,它代表了存储在表存储中的数据模型。

这个类包含几个直接对应于 Azure 表上的字段的属性。这些字段是FullNameEmailWebinarFollowedUpPhoneNumber

LeadsCore类实现了必要的逻辑,以便检索第一条非跟进线索并将该线索设置为跟进线索。

Get方法接收一个字符串参数,该参数将是我们有兴趣过滤掉的网络研讨会的名称。该值将用于使用表的PartitionKey查询 Azure 表上的记录。

遵循Appointments类的Get方法的相同逻辑,LeadsCore类的Get方法连接到 Azure,检索LeadsData对象的 IEnumerable 列表,并检查尚未跟进的(FollowedUp设置为false),还检查销售线索的发布日期exp是否早于今天的日期tdy

MarkAsFollowedUp方法负责连接到 Azure 表,定位对应于使用Get方法检索到的线索的记录,并将其设置为后续记录。MarkAsFollowedUp方法基本上是将FollowedUp属性设置为true,这样同一条线索就不能被再次检索(跟进)。

为了将这一点付诸行动,让我们创建一个LeadsExample类来包装这个逻辑,并负责创建检索线索的Timer对象。

代码清单 18: LeadsExample 包装类

  using System;
  using System.Timers;

  namespace TwilioSuccinctly
  {

  public class LeadsExample

  {

  private const string cStrTwilioSid = 

  "<< Your
  Twilio Account SID goes here >>";

  private const string cStrTwilioAuthTk =  

  "<< Your
  Twilio Auth Token goes here >>";

  private const string cStrTwilioSender = 

  "<< Your
  Twilio Number>>";

  private static bool started = false;

  public static Timer Fetch { get; set; }

  public static string PhoneNumber { get; set; }

  public static string Webinar { get; set; }

  public static void InitFetch(int ms)

  {

  started = false;

  Fetch = new Timer();

  Fetch.Interval = ms;

  Fetch.Elapsed += new ElapsedEventHandler(Fetch_Elapsed);

  Fetch.Start();

   }

  public static void StopFetch()

  {

  started = false;

  Fetch.Stop();

  }

  public static void Fetch_Elapsed(object sender, 

  ElapsedEventArgs e)

  {

  if (!started)

  {

  started = true;

  Console.WriteLine

  ("Fetching
  Oldest Non-Followed Up Lead...");

  using (LeadsCore l = new LeadsCore())

  {

  LeadsData ld = l.Get(Webinar);

  if (ld != null)

  {

  using (TwilioCore t = new 

  TwilioCore(cStrTwilioSid, cStrTwilioAuthTk))

  {

  t.SendSms(cStrTwilioSender, PhoneNumber,

  $"Please follow
  up with Lead 

  {ld.PartitionKey}{ld.Email} - {ld.FullName} on: 

  {l.FormatTime(ld.RowKey)}");

  Console.WriteLine($"Followed up with Lead 

  {ld.PartitionKey} - {ld.Email}{ld.FullName} on: 

  {l.FormatTime(ld.RowKey)}");

  ld.FollowedUp = true;

  }

  l.MarkAsFollowedUp(ld);

  }

  }

  started = false;

  }

  }

  }
  }

LeadsExample类的InitFetch方法创建一个Timer对象(分配给Fetch属性),当Fetch_Elapsed事件被触发时,该对象将检索线索。

Fetch_Elapsed事件创建一个LeadsCore类的实例,用于执行Get方法来检索需要跟进的线索。然后,检索到的导联被标记为后续导联(FollowedUp属性设置为true),并且该过程重复进行——当Fetch_Elapsed事件被Timer对象再次触发时,获取另一个导联。

现在让我们编写Main program以便调用LeadsExample包装器类。

代码清单 19:销售线索主程序

  using System;

  namespace TwilioSuccinctly
  {

  public class Program

  {

  public static void FollowUpWithLeads()

  {

  Console.WriteLine("Waiting for Timer (Leads
  Fetch)...");

  LeadsExample.PhoneNumber = "33484464";

  LeadsExample.Webinar = "SAP";

  LeadsExample.InitFetch(1500);

  }

  static void
  Main(string[] args)

  {

  FollowUpWithLeads();

  Console.ReadLine();

  }

  }
  }

用先前发布在 Azure 表存储表上的销售线索数据执行这个program会产生如图 39 所示的结果。

图 39:获取站点上的第一条销售线索

总结

我们已经看到了自动化几个小但有用的业务流程是多么容易和直接,以及我们以前编写的TwilioCore类如何可以用来发送短信。我们还看到了如何使用 Azure 表存储作为快速可靠的后端来满足我们的数据需求。

在接下来的几章中,我们将探讨 Twilio 提供的一些语音功能,以及它们如何用于自动化有用的业务流程。