我如何在COVID-19锁定期间构建用于在线购物的移动应用程序

When many stores that sell nonessential items were ordered to close in March by government decrees, consumers moved their shopping online. U.S. online sales increased 49% in April over the prior year, according to Adobe Analytics.

当许多出售非必需品的商店在3月被政府法令下令关闭时,消费者将他们的购物转移到了网上。 根据Adobe Analytics的数据 ,4月份美国在线销售额比去年同期增长了49%。

The increase in sales was not particularly significant on desktop. But mobile shopping apps such as Instacart saw an increase of about 650% (wow!) in new mobile app users (in the U.S alone) between March and April.

在台式机上,销售额的增长并不是特别明显。 但是在3月到4月之间,仅Instacart这样的移动购物应用程序的新增移动应用程序用户(仅在美国) 增长了650%(哇!)

So, in order for e-Commerce stores to take full advantage of the increase in online shoppers during the Coronavirus pandemic, they should have a native mobile app.

因此,为了使电子商务商店能够在冠状病毒大流行期间充分利用在线购物者的增长,他们应该拥有本机移动应用程序。

In this tutorial, I will be developing a native mobile Android app using the Woocommerce theme. I will also explain why I went for a native mobile app (instead of a hybrid app or a progressive web app) and the Woocommerce theme. Even better, I will start with some research data to show just how important it is for an e-Commerce store to have a mobile app.

在本教程中,我将使用Woocommerce主题开发本地移动Android应用。 我还将说明为何选择本地移动应用程序(而不是混合应用程序或渐进式Web应用程序)和Woocommerce主题。 更好的是,我将从一些研究数据开始,以显示电子商务商店拥有移动应用程序的重要性。

实体商店的流量几乎消失了 (Traffic to brick-and-mortar stores has almost vanished)

Consumer surveys suggest that the shift to online shopping will continue as long as COVID-19 remains a threat. A survey of 1,200 consumers in late March 2020 found that 90% of shoppers were hesitant to shop in stores because of the Coronavirus pandemic. And 45% expected online shopping would be a necessity for them during the crisis.

消费者调查表明,只要COVID-19仍然是威胁,向在线购物的转变将继续下去。 2020年3月下旬对1200名消费者进行的一项调查发现,有90%的购物者由于冠状病毒大流行而在商店购物犹豫。 在危机期间,有45%的人预计在线购物对他们是必要的。

A separate survey in April found that 55% of online consumers said they were ordering more online than they were before the virus hit, up from 26% in March. And 22% said in April they were ordering a lot more online, as opposed to only 6% in March.

4月另一项调查发现,有55%的在线消费者表示,他们在线订购的病毒数量比3月份的26%有所增加。 22%的人表示,他们在4月份在线订购的数量更多,而3月份仅为6%。

If the pandemic continues to linger, there could be a big shift to online shopping during retailers’ two biggest seasons of the year – back-to-school and holiday shopping. 23% of retailers surveyed in March planned to shift resources as a result of the Coronavirus outbreak.

如果这种流行病持续存在,那么在零售商一年中的两个最大季节–重返校园和度假购物期间,在线购物可能会发生重大转变。 3月份接受调查的零售商中有23%计划由于冠状病毒爆发而转移资源。

Retail has been forced to make some major changes. As all non-essential businesses were urged to shut down, and foot traffic into brick-and-mortar retail stores all but vanished. Even with staggered re-openings, the importance of having e-Commerce functionality is clearer than ever.

零售业被迫做出一些重大改变。 由于敦促所有非必要的企业关闭,进入实体零售商店的人流几乎消失了 。 即使交错开放,拥有电子商务功能的重要性也比以往更加明显。

The pandemic proved the need for businesses of all types to be flexible and ready for anything, and the need for them to take the digital leap. e-Commerce will be front and center for the new normal.

这场大流行证明,各种类型的企业都必须具有灵活性并为任何事情做好准备,并且它们需要实现数字化飞跃。 电子商务将成为新常态的前沿和中心。

但是我的客户有一个响应Swift的网站! (But my client has a responsive website!)

A number of trends are converging that make website owners want a native mobile app. The main goal of every website owner is to add value to the business by reaching more users. I think it will come as no surprise that mobile phone usage is on the rise and more people interact with their mobile phones than with desktops.

许多趋势正在融合,使网站所有者希望使用本机移动应用程序。 每个网站所有者的主要目标是通过吸引更多用户来为企业增值。 我认为移动电话的使用量在上升,与台式机进行交互的人更多,这不足为奇。

In order to tap into this market of mobile phone users, we need to know which types of apps these mobile phone users interact with the most (progressive web apps, hybrid apps, responsive mobile websites, or native apps). Luckily for us the people at Go-Globe did an analysis: a whopping 85% of mobile users interact with native mobile apps the most.

为了打入这个手机用户市场,我们需要了解这些手机用户与哪些应用程序互动最多(渐进式Web应用程序,混合应用程序,响应式移动网站或本机应用程序)。 幸运的是, Go-Globe的人员进行了分析:高达85%的移动用户与本地移动应用程序进行互动的次数最多。

There are a number of other reasons why companies should have a native mobile app to meet the needs of the growing number of online shoppers amid the coronavirus pandemic. Let's explore them now a bit more.

在冠状病毒大流行期间,公司应该拥有本机移动应用程序来满足日益增长的在线购物者的需求还有很多其他原因。 现在让我们进一步探讨它们。

他们为忠实的网站访问者提供了更好的体验 (They offer a better experience to loyal website visitors)

Mobile sites are great for discovery. But loyal users – those that come back more often – want to have an app. People use apps more than they use search on mobile devices. A brand (that is, an app) on a user’s home screen is a constant reminder of a site and its content.

移动站点非常适合发现。 但是忠实的用户(回头率更高的用户)希望拥有一个应用。 人们在移动设备上使用应用程序的次数要多于搜索。 用户主屏幕上的品牌(即应用)是对网站及其内容的不断提醒。

他们直接与网站访问者联系 (They connect directly with website visitors)

A brand can be on all social channels, but only a fraction of users will ever see its message. Emails can also be sent, but with a 25% open rate, only a fraction of the audience will be reached.

品牌可以出现在所有社交渠道上,但是只有一小部分用户会看到其信息。 也可以发送电子邮件,但是打开率为25%时,只能达到一小部分受众。

A branded mobile app gives a direct line to users, ultimately retaining users and turning casual visitors into loyal users.

品牌移动应用程序为用户提供了直接的渠道,最终保留了用户,并将临时访问者转变为忠实用户。

他们利用移动设备功能 (They make use of mobile device features)

Native mobile apps have the advantage of utilizing features of a mobile phone like the camera, contact list, GPS, phone calls, accelerometer, compass, and etc. Such device features, when used within an app, can make the user experience interactive and fun.

本地移动应用程序具有利用手机功能的优势,例如相机,联系人列表,GPS,电话,加速度计,指南针等。当在应用程序中使用时,此类设备功能可以使用户体验互动且有趣。

Moreover, these features can also reduce the efforts users would have to make otherwise. For instance, users that would like to know the location of a business can use the GPS/navigation to easily find it (especially useful in food apps).

此外,这些功能还可以减少用户不得不付出的努力。 例如,想要知道公司位置的用户可以使用GPS /导航轻松找到它(在食品应用程序中尤其有用)。

These features can also significantly shorten the time users take to perform a certain task in an app, and can even boost conversions.

这些功能还可以大大缩短用户在应用程序中执行特定任务所需的时间,甚至可以提高转化率。

Mobile websites, PWAs, and hybrid apps can also use some of the device's features. Still, there are technological constraints or limits in utilizing all the features of a device which native mobile apps can use easily.

移动网站,PWA和混合应用程序也可以使用设备的某些功能。 尽管如此,利用本机移动应用程序可以轻松使用的设备的所有功能仍存在技术约束或限制。

他们使您能够离线工作 (They give you the ability to work offline)

This is probably the most fundamental difference between a mobile website and an app. Although native mobile apps might require internet connectivity to perform most of their tasks, they can still offer basic content and functionality to users in offline mode.

这可能是移动网站和应用程序之间最根本的区别。 尽管本机移动应用程序可能需要Internet连接才能执行其大部分任务,但它们仍可以在离线模式下为用户提供基本内容和功能。

Let’s take the example of an e-Commerce website – the app can provide features like tax and installment calculation, and determine a user's spending limit. These features can work even without the help of an internet connection.

让我们以电子商务网站为例-该应用程序可以提供税收和分期付款等功能,并确定用户的支出限额。 即使没有互联网连接,这些功能也可以使用。

Even though mobile websites, PWAs, and hybrid mobile apps can use caching to load web pages without an internet connection, they can only offer limited functions.

即使移动网站,PWA和混合移动应用程序可以使用缓存来加载网页而无需互联网连接,但它们只能提供有限的功能。

它们有助于增加品牌知名度 (They help increase brand presence)

Users spend a substantial amount of their time on mobile devices. It’s safe to say that many users see the apps they’ve installed on their devices almost every day.

用户在移动设备上花费了大量时间。 可以肯定地说,许多用户几乎每天都会看到自己在设备上安装的应用。

This regular encounter can be viewed as a branding opportunity for the apps. Even when users are not actively using a mobile app, they are still reminded of the brand associated with the app. The app's icon acts like a mini-advertisement for the brand.

可以将这种定期相遇视为应用的品牌商机。 即使用户没有积极使用移动应用程序,也仍然会提醒他们与该应用程序相关的品牌。 该应用程序的图标就像是该品牌的迷你广告。

The presence of an app on a user’s device helps influence their perception about a brand subconsciously. This behavior can be linked to the Signal Detection Theory, which suggests that users still process ads they’ve ignored at some level subconsciously.

应用程序在用户设备上的存在有助于下意识地影响他们对品牌的看法。 这种行为可以与信号检测理论联系起来,该理论表明用户仍然在潜意识中处理他们已经忽略了一些广告。

应用程式的运作速度可能比网站更快 (Apps can work faster than websites)

A well-designed mobile app can perform actions much quicker than a mobile website.Apps usually store their data locally on mobile devices, in contrast to websites that generally use web servers. For this reason, data retrieval happens swiftly in mobile apps.

精心设计的移动应用程序可以比移动网站更快地执行操作。与通常使用Web服务器的网站相比,应用程序通常将数据本地存储在移动设备上。 因此,数据检索在移动应用程序中Swift发生。

Apps can further save users’ time by storing their preferences and using them to take proactive actions on their behalf.

应用程序可以存储用户的首选项,并使用它们代表他们采取主动行动,从而进一步节省用户的时间。

There is also a technical justification as to why mobile apps can work faster. Mobile websites use JavaScript code to perform most of their functions. And some of the frameworks that mobile apps use can run almost five times faster than JavaScript!

关于为什么移动应用程序可以运行得更快的技术上也有道理。 移动网站使用JavaScript代码执行大多数功能。 移动应用程序使用的某些框架的运行速度几乎比JavaScript快五倍!

While all this is happening in the background, users get to complete actions more quickly on the front end of mobile apps, again contributing to a delightful user experience.

尽管所有这些操作都是在后台进行的,但用户可以在移动应用程序的前端更快地完成操作,从而再次带来令人愉悦的用户体验。

他们增加了网站的SEO潜力 (They increase SEO potential for a website)

Mobile apps can be advantageous in two ways – for in-app content and website content as synonymous words will be used in the content for products and services. Google these days rank in-app content too and you can modify your content in your application to help you with your website SEO.

移动应用程序有两种优势-对于应用程序内内容和网站内容,因为在产品和服务的内容中将使用同义词。 如今,Google如今也对应用程序内内容进行排名,您可以在应用程序中修改内容,以帮助您进行网站SEO。

尽管有COVID-19的经济影响,但到2024年,移动应用程序支出预计将翻倍 (Mobile app spending is expected to double by 2024, despite the economic impacts of COVID-19)

According to a revised 2020-2024 market forecast, worldwide consumer spending in mobile apps is projected to reach $171 billion by 2024. This is more than double the $85 billion from 2019.

根据修订后的2020-2024年市场预测到2024年 ,全球消费者在移动应用程序上的支出预计将达到1710亿美元。这是2019年850亿美元的两倍多。

This total, however, is about $3 billion (or 2%) less than the forecast the firm had released prior to the COVID-19 outbreak.

但是,这个总数比公司在COVID-19爆发前发布的预测少了约30亿美元(或2%)。

Still, it's notable that even the slowest-growing regions on both Apple's App Store and Google Play will see revenue that's over 80% higher than their 2019 levels by the year 2024. The app stores will also hit several milestones during the next five years.

尽管如此,值得注意的是,即使到苹果App Store和Google Play上增长最慢的地区,到2024年,其收入也将比其2019年水平增长80%以上。在未来五年中,这些应用商店也将达到几个里程碑。

For starters, global spending in mobile apps will surpass $100 billion for the first time in 2020, growing at approximately 20% year-over-year to hit $102 billion. This indicates that website owners do not just get more customers, but more paying customers that are willing to spend.

首先,2020年全球在移动应用程序上的支出将首次超过1000亿美元,同比增长约20%,达到1020亿美元。 这表明网站所有者不仅会获得更多的客户,而且还会获得更多愿意付费的付费客户。

他们吸引了年轻的观众 (They reach out to a younger audience)

The stats indicate that 18-24 years olds are the most active mobile app users.

统计数据表明18-24岁的年龄段是最活跃的移动应用程序用户。

Still not convinced? Let these statistics do the talking:

还是不服气? 让这些统计数据进行讨论:

  1. 77% of Americans own a smartphone.

    77%的美国人拥有智能手机。
  2. Over 230 million U.S. consumers own smartphones.

    超过2.3亿美国消费者拥有智能手机。
  3. Around 100 million U.S. consumers own tablets.

    大约有1亿美国消费者拥有平板电脑。
  4. 79% of smartphone users have made a purchase online using their mobile device in the last 6 months.

    在过去的6个月中,有79%的智能手机用户使用他们的移动设备在线购物。
  5. e-Commerce dollars now comprise 10% of all retail revenue.

    电子商务美元现在占所有零售收入的10%。

  6. 80% of shoppers used a mobile phone inside of a physical store to either look up product reviews, compare prices, or find alternative store locations.

    80%的购物者在实体店内使用手机查询产品评论,比较价格或查找其他商店位置。
  7. An estimated 10 billion mobile connected devices are currently in use.

    目前估计有100亿移动连接设备正在使用。
  8. Mobile app users spend an average 201.8 minutes per month shopping, compared to 10.9 minutes/month for website users.

    移动应用程序用户平均每月购物201.8分钟,而网站用户每月10.9分钟。
  9. 58% of millennials mentioned that they preferred purchasing through apps.

    58%的千禧一代提到他们更喜欢通过应用程序购买。

Ignoring these trends in mobile e-Commerce (referred to as m-Commerce in the industry) evolution means potentially missing out on more and more profit as these trends continue.

忽略移动电子商务(在行业中称为移动商务)发展中的这些趋势,意味着随着这些趋势的继续,可能会失去越来越多的利润。

聊够了! 让我们写一些代码 (Enough talk! Let’s write some code)

All native mobile apps are just a bundle of code written in Java, Kotlin, Objective-C, or Swift that manipulate data and resources (.png, .xml files). The manipulated data can be retrieved from the mobile device’s sensors such as the screen, camera, storage memory, GPS, speakers, accelerometer, compass, or from a server.

所有本机移动应用程序都是用Java,Kotlin,Objective-C或Swift编写的用于处理数据和资源(.png,.xml文件)的代码。 可以从移动设备的传感器(例如屏幕,照相机,存储存储器,GPS,扬声器,加速度计,指南针)或服务器中检索操纵的数据。

In this tutorial we will be using the following tools:

在本教程中,我们将使用以下工具:

  • JSoup (a Java library): JSoup is an HTML parser which can directly parse a URL, HTML text content, and provides a set of very convenient API interfaces to manipulate data.

    JSoup( J ava库): JSoup是HTML解析器,可以直接解析URL,HTML文本内容,并提供一组非常方便的API接口来处理数据。

  • Android Studio: This is the official tool for writing and compiling Android apps in Java or Kotlin and produces ready to install .apk files. All code in this tutorial will be written in Java.

    Android Studio:这是用于以Java或Kotlin编写和编译Android应用程序的官方工具,可以立即安装.apk文件。 本教程中的所有代码都将用Java编写。

  • Woocommerce Plugin: This is the most popular e-Commerce plugin for WordPress. So building a mobile app for the most popular e-Commerce plugin seems like a good idea.

    Woocommerce插件:是最流行的WordPress电子商务插件 。 因此,为最受欢迎的电子商务插件构建移动应用程序似乎是个好主意。

从服务器获取和处理数据 (Getting and Manipulating Data from the Server)

Data will be gotten from the server using a RESTful API. WooCommerce (version 2.6+) is fully integrated with the WordPress REST API. This allows data to be created, read, updated, and deleted using requests in JSON format. It uses WordPress REST API Authentication methods and standard HTTP verbs which are understood by most HTTP clients.

数据将使用RESTful API从服务器获取。 WooCommerce(版本2.6及更高版本)已与WordPress REST API完全集成。 这允许使用JSON格式的请求来创建,读取,更新和删除数据。 它使用WordPress REST API身份验证方法和大多数HTTP客户端都能理解的标准HTTP动词。

I will be using the API version 2, v2 which is available for Woocommerce version 3.0.x or later and WordPress version 4.4 or later.

我将使用API​​版本2,v2,该版本可用于Woocommerce版本3.0.x或更高版本以及WordPress版本4.4或更高版本。

The default response format is JSON. Successful requests will return a 200 OK HTTP status. Some key information about responses are:

默认响应格式为JSON。 成功的请求将返回200 OK HTTP状态。 有关响应的一些关键信息是:

  • Dates are returned in ISO8601 format: YYYY-MM-DDTHH:MM:SS

    日期以ISO8601格式返回: YYYY-MM-DDTHH:MM:SS

  • Resource IDs are returned as integers.

    资源ID以整数形式返回。
  • Any decimal monetary amount, such as prices or totals, will be returned as strings with two decimal places.

    任何小数货币金额(例如价格或总计)将作为带两位小数位的字符串返回。
  • Other amounts, such as item counts, are returned as integers.

    其他金额(例如项目计数)以整数形式返回。
  • Blank fields are generally included as null or empty string instead of being omitted.

    空白字段通常以null或空字符串形式包含在内,而不是被忽略。

Most requests made to the Woocommerce REST API have to be authenticated using pre-generated keys (consumer key and consumer secret). New keys are generated through the WordPress admin interface. Just go to WooCommerce > Settings > API > Keys/Apps.

对Woocommerce REST API的大多数请求都必须使用预生成的密钥(消费者密钥和消费者秘密)进行身份验证。 新密钥是通过WordPress管理界面生成的。 只需转到WooCommerce>设置> API>密钥/应用程序。

Click the "Add Key" button. In the next screen, add a description and select the WordPress user you would like to generate the key for. Then click the "Generate API Key" button and WooCommerce will generate REST API keys for the selected user.

点击“添加密钥”按钮。 在下一个屏幕中,添加描述,然后选择您要为其生成密钥的WordPress用户。 然后单击“生成API密钥”按钮,WooCommerce将为所选用户生成REST API密钥。

Now that keys have been generated, you should see two new keys. These two keys are your Consumer Key and Consumer Secret.

现在已经生成了密钥,您应该看到两个新密钥。 这两个密钥是您的消费者密钥和消费者秘密。

We then create a URL which will include the business website and the API endpoint from which the data in JSON format will be returned.

然后,我们创建一个URL,其中将包含商业网站和API端点,将从中返回JSON格式的数据。

Using the Jsoup library, we connect to the URL (website + API endpoint) and add the API keys (consumer key and consumer secret) with other parameters of the API endpoint. Almost all endpoints accept optional parameters which can be passed as a HTTP query string parameter:

使用Jsoup库,我们连接到URL(网站+ API端点),并添加API密钥(消费者密钥和消费者机密)以及API端点的其他参数。 几乎所有端点都接受可选参数,这些参数可以作为HTTP查询字符串参数传递:

public static final int JSOUP_CONNECTION_TIMEOUT = 100000;

String websiteUrl = "www.freecodecamp.shop"; //example website doesn't exist
//This API lets you retrieve all product categories
String apiExtension = "/wp-json/wc/v2/products/categories";
//Map to store our parameters in a key value format
HashMap<String, String> data = new HashMap<>();
data.put("page", String.valueOf(page)); // API parameter to get the current page of the collection. Default is 1.
//add API keys for authentication
data.put("consumer_key", getKey());
data.put("consumer_secret", getSecret());

//concatenate both websiteUrl and apiExtension to form the requestUrl
String requestUrl = websiteUrl + apiExtension

try {
    Connection.Response response = Jsoup.connect(requestUrl).timeout(JSOUP_CONNECTION_TIMEOUT).followRedirects(true)
        .ignoreContentType(true)
        .data(data)
        .execute();
		
    String json = response.body();
	//parse json string to get needed data.

} catch (Exception e){
    //catch both JSONException and IOException
}

The  getKey() and getSecret() methods just return the API keys:

getKey()getSecret()方法仅返回API密钥:

public static String getKey() {
        return "ck_a89d59d7441f027df0d91f01c9e2dcaxxxxxxxxx";
    }

    public static String getSecret() {
        return "cs_c3f8fe620bd5b1cb3567712eb843609xxxxxxxxx";
    }

But wait...for some websites, when I run this code it gives me the error 401 Unauthorized. This is an authentication or permission error, due to incorrect API keys...so what gives?

但是请稍候...对于某些网站,当我运行此代码时,它给我错误401 Unauthorized 。 由于API密钥不正确,这是身份验证或权限错误。

Sure, the above code only works for secured websites with the HTTPS protocol, whereas unsecured websites which use the HTTP protocol will need to encrypt the API keys before sending them.

当然,以上代码仅适用于使用HTTPS协议的安全网站,而使用HTTP协议的不安全网站则需要在发送API密钥之前对其进行加密。

What does that mean, exactly? HTTPS is HTTP with encryption, as HTTPS uses TLS (SSL) to encrypt normal HTTP requests and responses. This makes HTTPS far more secure than HTTP. A website that uses HTTP has http:// in its URL, while a website that uses HTTPS has https://.

到底是什么意思? HTTPS是具有加密功能的HTTP,因为HTTPS使用TLS(SSL)来加密普通的HTTP请求和响应。 这使HTTPS远比HTTP安全得多。 使用HTTP的网站的URL中带有http://,而使用HTTPS的网站的https://中。

You must use OAuth 1.0a "one-legged" authentication to ensure your REST API credentials cannot be intercepted by an attacker. The required parameters are oauth_consumer_key, oauth_timestamp, oauth_nonce, oauth_signature and oauth_signature_method.

您必须使用OAuth 1.0a“一条腿”身份验证,以确保攻击者无法拦截您的REST API凭据。 必需的参数是oauth_consumer_key,oauth_timestamp,oauth_nonce,oauth_signatureoauth_signature_method。

We will create a method that will get these encrypted parameters as a HashMap. The method, getAuthenticationPrams(String url, HashMap<String, String> mData) will accept a request URL (website URL + API extension) and any parameters that we might want to add to the API extension.

我们将创建一个将这些加密参数作为HashMap获取的方法。 方法getAuthenticationPrams(String url, HashMap<String, String> mData)将接受请求URL(网站URL + API扩展)以及我们可能想要添加到API扩展的任何参数。

Here we collect and normalize our parameters, which includes all oauth_* parameters except for the oauth_signature itself.

在这里,我们收集并标准化我们的参数,其中包括除oauth_signature本身以外的所有oauth_ *参数。

public static HashMap<String, String> getAuthenticationParams(String url, @Nullable HashMap<String, String> mData){
    HashMap<String, String> data = new HashMap<>();
    if(url.startsWith("http://")){
        String nonce = new TimestampService().getNonce();
        String timestamp = new TimestampService().getTimestampInSeconds();

        data.put("oauth_consumer_key", getKey());
        data.put("oauth_signature_method", "HMAC-SHA1");
        data.put("oauth_version", "1.0");
        data.put("oauth_nonce", nonce);
        data.put("oauth_timestamp", timestamp);

        if(mData != null)
            data.putAll(mData);

        String firstBaseString = "GET&" + urlEncoded(url);
        String generatedBaseString = formatQuery(data);

        ParametersList result = new ParametersList();
        result.addQuerystring(generatedBaseString);
        generatedBaseString = result.sort().asOauthBaseString();
        String secondBaseString = "&" + generatedBaseString;

        if (firstBaseString.contains("%3F")) {
            secondBaseString = "%26" + urlEncoded(generatedBaseString);
        }
        String baseString = firstBaseString + secondBaseString;
        String signature = new HmacSha1SignatureService().getSignature(baseString, getSecret(), "");
        data.put("oauth_signature", signature);

    } else{
        data.put("consumer_key", getKey());
        data.put("consumer_secret", getSecret());

        data.putAll(mData);
    }

    return data;
}

The TimestampService class generates a timestamp and nonce for the oauth_nonce and oauth_timestamp parameters.

TimestampService类为oauth_nonceoauth_timestamp参数生成时间戳和随机数。

import java.util.Random;

public class TimestampService {
    private Timer timer;

    /**
     * Default constructor.
     */
    public TimestampService() {
        timer = new Timer();
    }

    public String getNonce() {
        Long ts = getTs();
        return String.valueOf(ts + timer.getRandomInteger());
    }

    public String getTimestampInSeconds() {
        return String.valueOf(getTs());
    }

    private Long getTs() {
        return timer.getMilis() / 1000;
    }

    void setTimer(Timer timer) {
        this.timer = timer;
    }

    /**
     * Inner class that uses {@link System} for generating the timestamps.
     *
     */
    static class Timer {
        private final Random rand = new Random();
        Long getMilis() {
            return System.currentTimeMillis();
        }

        Integer getRandomInteger() {
            return rand.nextInt();
        }
    }

}

The  formatQuery(HashMap<String, String> mData) method formats the parameters into query parameters which are a set of parameters attached to the end of a url. The urlEncoded(String url) method translates the given string into an application/x-www-form-urlencoded format using a specific encoding scheme. This method uses the supplied encoding scheme to obtain the bytes for unsafe characters.

formatQuery(HashMap<String, String> mData)方法将参数格式化为查询参数,这些参数是附加在url末尾的一组参数。 urlEncoded(String url)方法使用特定的编码方案将给定的字符串转换为application / x-www-form-urlencoded格式。 此方法使用提供的编码方案来获取不安全字符的字节。

import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;

    private static String formatQuery(HashMap<String, String> mData){
        int i = 0;
        StringBuilder param = new StringBuilder();
        for(String key : mData.keySet()){
            if(i > 0){
                param.append("&");
            }
            param.append(key);
            param.append("=");
            param.append(mData.get(key));
            i++;
        }

        return param.toString();
    }

    private static String urlEncoded(String url) {
        String encodedurl = "";
        try {
            encodedurl = URLEncoder.encode(url, "UTF-8");
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }

        return encodedurl;
    }

The ParametersList class takes the formatted query and then encodes it using the OAuthEncoder class. The values that start with oauth_* need to be encoded into a string which will be used later on. The process to build the string is very specific:

ParametersList类接受格式化的查询,然后使用OAuthEncoder类对其进行编码。 需要将以oauth_ *开头的值编码为字符串,稍后再使用。 生成字符串的过程非常具体:

  1. Percent encode every key and value that will be signed.

    百分比对将要签名的每个键和值进行编码。
  2. Sort the list of parameters alphabetically by encoded key.

    按编码键按字母顺序对参数列表进行排序。
  3. For each key/value pair:

    对于每个键/值对:
  • Append the encoded key to the output string

    将编码的密钥附加到输出字符串
  • Append the = character to the output string

    =字符附加到输出字符串

  • Append the encoded value to the output string

    将编码值附加到输出字符串
  • If there are more key/value pairs remaining, append an & character to the output string.

    如果还有更多的键/值对,请在输出字符串后附加&字符。

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;


public class ParametersList {
    private static final char QUERY_STRING_SEPARATOR = '?';
    private static final String PARAM_SEPARATOR = "&";
    private static final String PAIR_SEPARATOR = "=";
    private static final String EMPTY_STRING = "";

    private final List<Parameter> params;

    public ParametersList() {
        params = new ArrayList<Parameter>();
    }

    ParametersList(List<Parameter> params) {
        this.params = new ArrayList<Parameter>(params);
    }

    public void add(String key, String value) {
        params.add(new Parameter(key, value));
    }

    public String asOauthBaseString() {
        return OAuthEncoder.encode(asFormUrlEncodedString());
    }

    public String asFormUrlEncodedString() {
        if (params.size() == 0) return EMPTY_STRING;

        StringBuilder builder = new StringBuilder();
        for(Parameter p : params) {
            builder.append('&').append(p.asUrlEncodedPair());
        }
        return builder.toString().substring(1);
    }

    public void addAll(ParametersList other) {
        params.addAll(other.params);
    }

    public void addQuerystring(String queryString) {
        if (queryString != null && queryString.length() > 0) {
            for (String param : queryString.split(PARAM_SEPARATOR)) {
                String pair[] = param.split(PAIR_SEPARATOR);
                String key = OAuthEncoder.decode(pair[0]);
                String value = pair.length > 1 ? OAuthEncoder.decode(pair[1]) : EMPTY_STRING;
                params.add(new Parameter(key, value));
            }
        }
    }

    public boolean contains(Parameter param) {
        return params.contains(param);
    }

    public int size() {
        return params.size();
    }

    public ParametersList sort() {
        ParametersList sorted = new ParametersList(params);

        Collections.sort(sorted.params);
        return sorted;
    }
}
public class Parameter implements Comparable<Parameter> {
    private final String key;
    private final String value;

    public Parameter(String key, String value) {
        this.key = key;
        this.value = value;
    }

    public String asUrlEncodedPair() {
        return OAuthEncoder.encode(key).concat("=").concat(OAuthEncoder.encode(value));
    }

    public boolean equals(Object other) {
        if(other == null) return false;
        if(other == this) return true;
        if(!(other instanceof Parameter)) return false;

        Parameter otherParam = (Parameter) other;
        return otherParam.key.equals(key) && otherParam.value.equals(value);
    }

    public int hashCode() {
        return key.hashCode() + value.hashCode();
    }

    public int compareTo(Parameter parameter) {
        int keyDiff = key.compareTo(parameter.key);

        return keyDiff != 0 ? keyDiff : value.compareTo(parameter.value);
    }
}
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Pattern;


public class OAuthEncoder {
    private static String CHARSET = "UTF-8";
    private static final Map<String, String> ENCODING_RULES;

    static {
        Map<String, String> rules = new HashMap<String, String>();
        rules.put("*", "%2A");
        rules.put("+", "%20");
        rules.put("%7E", "~");
        ENCODING_RULES = Collections.unmodifiableMap(rules);
    }

    private OAuthEncoder(){}

    public static String encode(String plain) {
        String encoded = "";
        try {
            encoded = URLEncoder.encode(plain, CHARSET);
        }
        catch (UnsupportedEncodingException uee) {
            throw new OAuthException("Charset not found while encoding string: " + CHARSET, uee);
        }
        for(Map.Entry<String, String> rule : ENCODING_RULES.entrySet()) {
            encoded = applyRule(encoded, rule.getKey(), rule.getValue());
        }
        return encoded;
    }

    private static String applyRule(String encoded, String toReplace, String replacement) {
        return encoded.replaceAll(Pattern.quote(toReplace), replacement);
    }

    public static String decode(String encoded) {
        try {
            return URLDecoder.decode(encoded, CHARSET);
        }
        catch(UnsupportedEncodingException uee) {
            throw new OAuthException("Charset not found while decoding string: " + CHARSET, uee);
        }
    }
}
public class OAuthConstants {
    private OAuthConstants(){}

    public static final String OUT_OF_BAND = "oob";
}
public class OAuthException extends RuntimeException {

    /**
     * Default constructor
     * @param message message explaining what went wrong
     * @param e original exception
     */
    public OAuthException(String message, Exception e) {
        super(message, e);
    }

    /**
     * No-exception constructor. Used when there is no original exception
     *
     * @param message message explaining what went wrong
     */
    public OAuthException(String message) {
        super(message, null);
    }

    private static final long serialVersionUID = 1L;
}

The collected values (oauth_* parameters + API extension parameters) must be joined to make a single string, from which the signature will be generated. This is called the signature base string in the OAuth specification.

必须将收集的值( oauth_ *参数+ API扩展参数)连接在一起,以形成一个字符串,从该字符串将生成签名。 在OAuth规范中,这称为签名基础字符串。

To encode the HTTP method, request URL, and parameter string into a single string:

要将HTTP方法,请求URL和参数字符串编码为单个字符串,请执行以下操作:

  1. Set the output string equal to the uppercase HTTP Method (GET in this example).

    将输出字符串设置为等于大写的HTTP方法(在此示例中为GET)。
  2. Append the & character to the output string.

    &字符附加到输出字符串。

  3. Percent encode the URL and append it to the output string.

    百分比对URL进行编码并将其附加到输出字符串。
  4. Append the & character to the output string

    &字符附加到输出字符串

  5. Percent encode the parameter strng and append it to the output string.

    百分比编码参数strng并将其附加到输出字符串。

The HmacSha1SignatureService class generates the signature using the signature base string and your consumer secret key with an & character with the HMAC-SHA1 hashing algorithm.

HmacSha1SignatureService类生成使用签名基本字符串,并与你的消费者密钥签名&性格与HMAC-SHA1散列算法。

import android.util.Base64;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;


public class HmacSha1SignatureService {
    private static final String EMPTY_STRING = "";
    private static final String CARRIAGE_RETURN = "\r\n";
    private static final String UTF8 = "UTF-8";
    private static final String HMAC_SHA1 = "HmacSHA1";
    private static final String METHOD = "HMAC-SHA1";

    public String getSignature(String baseString, String apiSecret, String tokenSecret) {
        try {
            return doSign(baseString, OAuthEncoder.encode(apiSecret) + '&' + OAuthEncoder.encode(tokenSecret));
        }
        catch (Exception e) {
            throw new OAuthSignatureException(baseString, e);
        }
    }

    private String doSign(String toSign, String keyString) throws Exception {

        SecretKeySpec key = new SecretKeySpec((keyString).getBytes(UTF8), HMAC_SHA1);
        Mac mac = Mac.getInstance(HMAC_SHA1);
        mac.init(key);
        byte[] bytes = mac.doFinal(toSign.getBytes(UTF8));
        return bytesToBase64String(bytes).replace(CARRIAGE_RETURN, EMPTY_STRING);
    }

    private String bytesToBase64String(byte[] bytes) {
        return Base64.encodeToString(bytes,Base64.NO_WRAP);
    }

    public String getSignatureMethod() {
        return METHOD;
    }
}
/**
 * Specialized exception that represents a problem in the signature
  */
public class OAuthSignatureException extends OAuthException {
    private static final long serialVersionUID = 1L;
    private static final String MSG = "Error while signing string: %s";

    /**
     * Default constructor
     *
     * @param stringToSign plain string that gets signed (HMAC-SHA, etc)
     * @param e original exception
     */
    public OAuthSignatureException(String stringToSign, Exception e) {
        super(String.format(MSG, stringToSign), e);
    }

}

That's it! Now you can connect to any e-Commerce website that uses the Woocommerce plugin without installing any other plugin. Using the WordPress REST API you can get/manipulate the data you want using its API endpoints. The endpoints I used are:

而已! 现在,您可以连接到使用Woocommerce插件的任何电子商务网站,而无需安装任何其他插件。 使用WordPress REST API,您可以使用其API端点获取/操作所需的数据。 我使用的端点是:

  1. /wp-json/wc/v2/customers - lets you create a new customer after you have verified them through the signin/login screen of the app.

    /wp-json/wc/v2/customers通过应用的登录/登录屏幕验证新客户后,您可以创建新客户。

  2. /wp-json/wc/v2/payment_gateways - lets you retrieve and view all the available payment gateways

    /wp-json/wc/v2/payment_gateways可让您检索和查看所有可用的支付网关

  3. /wp-json/wc/v2/products - helps you view all the products that have been sold on the website

    /wp-json/wc/v2/products帮助您查看网站上已售出的所有产品

  4. /wp-json/wc/v2/shipping/zones - helps you create a new shipping zone

    /wp-json/wc/v2/shipping/zones帮助您创建新的运输区域

  5. /wp-json/wc/v2/settings/general/woocommerce_currency - gets the currency used

    /wp-json/wc/v2/settings/general/woocommerce_currency获取所使用的货币

  6. /wp-json/wc/v2/orders - helps you create a new order

    /wp-json/wc/v2/orders帮助您创建新订单

  7. /wp-json/wc/v2/products/categories - lets you retrieve all product categories

    /wp-json/wc/v2/products/categories可让您检索所有产品类别

  8. /wp-json/wc/v2/coupons - helps you list all the coupons that have been created by the website administrator

    /wp-json/wc/v2/coupons帮助您列出网站管理员创建的所有优惠券

Here's my final mobile app:

这是我最后的移动应用程序:

使应用看起来更漂亮的资源 (Resources that Makes the App Look Pretty)

In finalizing your native mobile app creation, you will need a UI/UX which the user will interact with to manipulate the data gotten from the WordPress server. Luckily, the people at Wsdesign have some free and ready to use templates that you can download.

在完成本机移动应用程序的创建时,您将需要一个UI / UX,用户将与之交互以操纵从WordPress服务器获得的数据。 幸运的是, Wsdesign的人员可以免费下载一些可立即使用的模板。

I hope that you found this article useful and it was able to help you learn and build an awesome app today. If you really liked it, please do share it on all social media platforms.

希望本文对您有用,并且可以帮助您今天学习和构建出色的应用程序。 如果您真的喜欢它,请在所有社交媒体平台上共享它。

翻译自: https://www.freecodecamp.org/news/how-i-built-a-mobile-app-for-online-shopping-amid-covid-19-lock-down/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值