安卓谷歌智能锁

2023-11-20

在本教程中,我们将讨论智能锁功能并在 Android 应用程序中实现它。

谷歌智能锁

Smart Lock 用于通过一劳永逸地保存凭据来自动登录您的应用程序。这意味着,如果您在一段时间后重新安装应用程序,您可以使用之前保存的凭据自动登录,前提是您没有从 chrome 密码中删除它们。

Google Smart Lock 让您只需轻轻一按即可登录。

为了将 Smart Lock 集成到您的应用程序中,您需要使用 Credentials API。凭证 API 允许用户:

  • 打开应用程序时请求凭据。
  • 保存登录表单中的凭据。
  • 在应用程序和网站之间同步凭据。
  • 如果我们想在登录/注册过程中帮助用户,则显示电子邮件提示。

要在您的应用程序中使用 Google Smart Lock,您需要添加以下依赖项:

dependencies {
    implementation 'com.google.android.gms:play-services-auth:16.0.0'
}

SmartLock 需要在您的 Android 应用程序中设置 GoogleApiClient。当只有一个凭据时,SmartLock 允许自动登录。当有多个凭据时,它们会显示在对话框中。

早些时候,我们曾经依赖 SharedPreferences 在本地自动签名和保存凭据。现在,有了 Google Smart Lock,一切都由 Google 服务器处理。

以下是 Credentials API 中存在的主要方法:

  • save(GoogleApiClient client, Credential credential)
  • request(GoogleApiClient client, CredentialRequestrequest)- 请求为应用程序保存的所有凭据。
  • getHintPickerIntent(GoogleApiClient client, HintRequest request)- 显示您拥有的登录帐户列表,以便快速填写登录表单。
  • disableAutoSignIn(GoogleApiClient client)
  • delete(GoogleApiClient client, Credential credential)

您可以通过访问查看为 Google 帐户保存的所有凭据密码.google.com. 带有智能锁的应用程序的流程是什么?您需要按以下方式构建登录屏幕代码:

  • 检查凭证。如果存在单个凭据,请自动签名或自动填写登录表单。
  • 如果有多个凭据,请在对话框中显示它们并让用户选择。
  • 如果没有保存的凭据,您可以让用户填写表单,或者通过自动填充或显示带有可用帐户登录的提示对话框来让他们更轻松。

入门

让我们开始在 Android 应用程序中实现智能锁功能。设置 GoogleApiClient

mGoogleApiClient = new GoogleApiClient.Builder(this)
                .addConnectionCallbacks(this)
                .addApi(Auth.CREDENTIALS_API)
                .enableAutoManage(this, this)
                .build();

实现 GoogleApiClient 接口并实现方法。初始化凭证客户端

CredentialsOptions options = new CredentialsOptions.Builder()
                .forceEnableSaveDialog()
                .build();


CredentialsClient  mCredentialsApiClient = Credentials.getClient(this, options);

强制启用保存对话框()Android Oreo 及更高版本需要。创建凭证请求

CredentialRequest mCredentialRequest = new CredentialRequest.Builder()
                .setPasswordLoginSupported(true)
                .setAccountTypes(IdentityProviders.GOOGLE)
                .build();

检索凭证

Auth.CredentialsApi.request(mGoogleApiClient, mCredentialRequest).setResultCallback(this);

The setResultCallBack需要我们重写该方法onResult从界面ResultCallback<CredentialRequestResult>

@Override
    public void onResult(@NonNull CredentialRequestResult credentialRequestResult) {

        Status status = credentialRequestResult.getStatus();
        if (status.isSuccess()) {
            onCredentialRetrieved(credentialRequestResult.getCredential());
        } else {
            if (status.getStatusCode() == CommonStatusCodes.RESOLUTION_REQUIRED) {
                try {
                    isResolving = true;
                    status.startResolutionForResult(this, RC_READ);
                } catch (IntentSender.SendIntentException e) {
                    Log.d(TAG, e.toString());
                }
            } else {

                showHintDialog();
            }
        }
    }

共有三种情况——

单个凭证 - 成功 - 多个凭证 - 解决它们并在对话框中显示所有可用的凭证

  • 无凭据 - 显示包含所有可用登录帐户的提示对话框

状态码为RESOLUTION_REQUIRED意味着有多个凭证需要解析。为此,我们调用startResolutionForResult它在 onActivityResult 方法中返回结果。我们使用布尔标志来防止发生多重解析。这将导致构建多个对话框。现在我们已经了解了 SmartLock 功能的要点,让我们通过保存和删除凭据功能来完整地实现它。

项目结构

android google smart lock project

Code

Activity_main.xml 布局的代码如下:


<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="https://schemas.android.com/apk/res/android"
    xmlns:app="https://schemas.android.com/apk/res-auto"
    xmlns:tools="https://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:importantForAutofill="noExcludeDescendants">


    <Button
        android:id="@+id/btnLogin"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginLeft="8dp"
        android:layout_marginRight="8dp"
        android:layout_marginTop="24dp"
        android:text="Login"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/inPassword" />

    <EditText
        android:id="@+id/inEmail"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginLeft="16dp"
        android:layout_marginRight="16dp"
        android:layout_marginTop="32dp"
        android:ems="10"
        android:hint="email"
        android:inputType="textEmailAddress"
        app:layout_constraintHorizontal_bias="0.503"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent" />


    <EditText
        android:id="@+id/inPassword"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginLeft="16dp"
        android:layout_marginRight="16dp"
        android:layout_marginTop="8dp"
        android:ems="10"
        android:hint="password"
        android:inputType="textPassword"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/inEmail" />


</android.support.constraint.ConstraintLayout>

android:importantForAutofill="noExcludeDescendants">用于禁用 EditText 字段上的自动填充。我们将在单独的教程中讨论自动填充 API。 MainActivity.java 类的代码如下:

package com.journaldev.androidgooglesmartlock;

import android.app.PendingIntent;
import android.content.Intent;
import android.content.IntentSender;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.Log;
import android.util.Patterns;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;

import com.google.android.gms.auth.api.Auth;
import com.google.android.gms.auth.api.credentials.Credential;
import com.google.android.gms.auth.api.credentials.CredentialPickerConfig;
import com.google.android.gms.auth.api.credentials.CredentialRequest;
import com.google.android.gms.auth.api.credentials.CredentialRequestResponse;
import com.google.android.gms.auth.api.credentials.CredentialRequestResult;
import com.google.android.gms.auth.api.credentials.Credentials;
import com.google.android.gms.auth.api.credentials.CredentialsClient;
import com.google.android.gms.auth.api.credentials.CredentialsOptions;
import com.google.android.gms.auth.api.credentials.HintRequest;
import com.google.android.gms.auth.api.credentials.IdentityProviders;
import com.google.android.gms.auth.api.signin.GoogleSignIn;
import com.google.android.gms.auth.api.signin.GoogleSignInAccount;
import com.google.android.gms.auth.api.signin.GoogleSignInClient;
import com.google.android.gms.auth.api.signin.GoogleSignInOptions;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.api.ApiException;
import com.google.android.gms.common.api.CommonStatusCodes;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.common.api.ResolvableApiException;
import com.google.android.gms.common.api.ResultCallback;
import com.google.android.gms.common.api.Status;
import com.google.android.gms.tasks.OnCompleteListener;
import com.google.android.gms.tasks.Task;

import java.util.regex.Pattern;

public class MainActivity extends AppCompatActivity implements GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener, ResultCallback<CredentialRequestResult> {

    private GoogleApiClient mGoogleApiClient;
    CredentialsClient mCredentialsApiClient;
    CredentialRequest mCredentialRequest;
    public static final String TAG = "API123";
    private static final int RC_READ = 3;
    private static final int RC_SAVE = 1;
    private static final int RC_HINT = 2;
    boolean isResolving;

    Button btnLogin;
    EditText inEmail, inPassword;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        setUpGoogleApiClient();

        //needed for Android Oreo.
        CredentialsOptions options = new CredentialsOptions.Builder()
                .forceEnableSaveDialog()
                .build();


        mCredentialsApiClient = Credentials.getClient(this, options);
        createCredentialRequest();

        btnLogin = findViewById(R.id.btnLogin);
        inEmail = findViewById(R.id.inEmail);
        inPassword = findViewById(R.id.inPassword);

        btnLogin.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {

                String email = inEmail.getText().toString();
                String password = inPassword.getText().toString();
                if (TextUtils.isEmpty(email) || TextUtils.isEmpty(password) || !Patterns.EMAIL_ADDRESS.matcher(email).matches())
                    showToast("Please enter valid email and password");

                else {

                    Credential credential = new Credential.Builder(email)
                            .setPassword(password)
                            .build();

                    saveCredentials(credential);
                }

            }
        });
    }

    public void setUpGoogleApiClient() {


        mGoogleApiClient = new GoogleApiClient.Builder(this)
                .addConnectionCallbacks(this)
                .addApi(Auth.CREDENTIALS_API)
                .enableAutoManage(this, this)
                .build();
    }

    public void createCredentialRequest() {
        mCredentialRequest = new CredentialRequest.Builder()
                .setPasswordLoginSupported(true)
                .setAccountTypes(IdentityProviders.GOOGLE)
                .build();
    }

    public void requestCredentials() {
        Auth.CredentialsApi.request(mGoogleApiClient, mCredentialRequest).setResultCallback(this);
    }


    private void onCredentialRetrieved(Credential credential) {
        String accountType = credential.getAccountType();
        if (accountType == null) {
            // Sign the user in with information from the Credential.
            gotoNext();
        } else if (accountType.equals(IdentityProviders.GOOGLE)) {


            GoogleSignInOptions gso = new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
                    .requestEmail()
                    .build();

            GoogleSignInClient signInClient = GoogleSignIn.getClient(this, gso);
            Task<GoogleSignInAccount> task = signInClient.silentSignIn();

            task.addOnCompleteListener(new OnCompleteListener<GoogleSignInAccount>() {
                @Override
                public void onComplete(@NonNull Task<GoogleSignInAccount> task) {
                    if (task.isSuccessful()) {
                        // See "Handle successful credential requests"
                        populateLoginFields(task.getResult().getEmail(), null);
                    } else {
                        showToast("Unable to do a google sign in");
                    }
                }
            });
        }
    }


    public void gotoNext() {
        startActivity(new Intent(this, SecondActivity.class));
        finish();
    }


    public void showToast(String s) {
        Toast.makeText(getApplicationContext(), s, Toast.LENGTH_SHORT).show();
    }


    @Override
    public void onConnected(@Nullable Bundle bundle) {
        Log.d("API123", "onConnected");
        requestCredentials();

    }

    @Override
    public void onConnectionSuspended(int i) {

    }

    @Override
    public void onConnectionFailed(@NonNull ConnectionResult connectionResult) {

    }

    @Override
    protected void onDestroy() {
        mGoogleApiClient.disconnect();
        super.onDestroy();
    }

    @Override
    public void onResult(@NonNull CredentialRequestResult credentialRequestResult) {

        Status status = credentialRequestResult.getStatus();
        if (status.isSuccess()) {
            onCredentialRetrieved(credentialRequestResult.getCredential());
        } else {
            if (status.getStatusCode() == CommonStatusCodes.RESOLUTION_REQUIRED) {
                try {
                    isResolving = true;
                    status.startResolutionForResult(this, RC_READ);
                } catch (IntentSender.SendIntentException e) {
                    Log.d(TAG, e.toString());
                }
            } else {

                showHintDialog();
            }
        }
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);

        Log.d(TAG, "onActivityResult");
        if (requestCode == RC_READ) {
            if (resultCode == RESULT_OK) {
                Credential credential = data.getParcelableExtra(Credential.EXTRA_KEY);
                onCredentialRetrieved(credential);
            } else {
                Log.d(TAG, "Request failed");
            }
            isResolving = false;
        }

        if (requestCode == RC_HINT) {
            if (resultCode == RESULT_OK) {
                Credential credential = data.getParcelableExtra(Credential.EXTRA_KEY);
                populateLoginFields(credential.getId(), "");
            } else {
                showToast("Hint dialog closed");
            }
        }

        if (requestCode == RC_SAVE) {
            if (resultCode == RESULT_OK) {
                Log.d(TAG, "SAVE: OK");
                gotoNext();
                showToast("Credentials saved");
            }
        }


    }

    public void populateLoginFields(String email, String password) {
        if (!TextUtils.isEmpty(email))
            inEmail.setText(email);

        if (!TextUtils.isEmpty(password))
            inPassword.setText(password);
    }

    public void showHintDialog() {
        HintRequest hintRequest = new HintRequest.Builder()
                .setHintPickerConfig(new CredentialPickerConfig.Builder()
                        .setShowCancelButton(true)
                        .build())
                .setEmailAddressIdentifierSupported(true)
                .setAccountTypes(IdentityProviders.GOOGLE)
                .build();

        PendingIntent intent = mCredentialsApiClient.getHintPickerIntent(hintRequest);
        try {
            startIntentSenderForResult(intent.getIntentSender(), RC_HINT, null, 0, 0, 0);
        } catch (IntentSender.SendIntentException e) {
            Log.e(TAG, "Could not start hint picker Intent", e);
        }
    }

    public void saveCredentials(Credential credential) {


        mCredentialsApiClient.save(credential).addOnCompleteListener(new OnCompleteListener<Void>() {
            @Override
            public void onComplete(@NonNull Task<Void> task) {
                if (task.isSuccessful()) {
                    Log.d(TAG, "SAVE: OK");
                    showToast("Credentials saved");
                    return;
                }

                Exception e = task.getException();
                if (e instanceof ResolvableApiException) {
                    // Try to resolve the save request. This will prompt the user if
                    // the credential is new.
                    ResolvableApiException rae = (ResolvableApiException) e;
                    try {
                        rae.startResolutionForResult(MainActivity.this, RC_SAVE);
                    } catch (IntentSender.SendIntentException f) {
                        // Could not resolve the request
                        Log.e(TAG, "Failed to send resolution.", f);
                        showToast("Saved failed");
                    }
                } else {
                    // Request has no resolution
                    showToast("Saved failed");
                }
            }
        });

    }
}

在 onConnected 方法中,我们请求可用的凭据。这意味着一旦活动开始,就会检索凭据(如果有)。如果存在单个凭据,那么它将自动签名并转到下一个活动。 Activity_second.xml 布局的代码如下:

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="https://schemas.android.com/apk/res/android"
    xmlns:app="https://schemas.android.com/apk/res-auto"
    xmlns:tools="https://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".SecondActivity">

    <Button
        android:id="@+id/btnDeleteAccount"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="8dp"
        android:text="Delete account"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/btnSignOut" />

    <TextView
        android:id="@+id/textView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="You are logged in."
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <Button
        android:id="@+id/btnSignOut"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="8dp"
        android:text="SIGN OUT"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/textView" />

    <Button
        android:id="@+id/btnSignOutDisableAutoSign"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="SIGN OUT AND DISABLE AUTO SIGN IN"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/btnDeleteAccount" />
</android.support.constraint.ConstraintLayout>

在 SecondActivity 中,我们将执行三个不同的操作 - 注销、注销并禁用下次自动登录、删除凭据。 SecondActivity.java 类的代码如下:

package com.journaldev.androidgooglesmartlock;

import android.content.Intent;
import android.content.IntentSender;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;

import com.google.android.gms.auth.api.Auth;
import com.google.android.gms.auth.api.credentials.Credential;
import com.google.android.gms.auth.api.credentials.CredentialRequest;
import com.google.android.gms.auth.api.credentials.CredentialRequestResult;
import com.google.android.gms.auth.api.credentials.Credentials;
import com.google.android.gms.auth.api.credentials.CredentialsClient;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.common.api.ResultCallback;
import com.google.android.gms.common.api.Status;

public class SecondActivity extends AppCompatActivity implements View.OnClickListener, GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener, ResultCallback<CredentialRequestResult> {


    Button btnSignOut, btnSignOutDisableAuto, btnDelete;
    private GoogleApiClient mGoogleApiClient;
    CredentialsClient mCredentialsApiClient;
    CredentialRequest mCredentialRequest;
    public static final String TAG = "API123";
    private static final int RC_REQUEST = 4;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_second);

        setUpGoogleApiClient();
        mCredentialsApiClient = Credentials.getClient(this);


        btnSignOut = findViewById(R.id.btnSignOut);
        btnSignOutDisableAuto = findViewById(R.id.btnSignOutDisableAutoSign);
        btnDelete = findViewById(R.id.btnDeleteAccount);

        btnSignOut.setOnClickListener(this);
        btnSignOutDisableAuto.setOnClickListener(this);
        btnDelete.setOnClickListener(this);
    }

    @Override
    public void onClick(View view) {
        switch (view.getId()) {
            case R.id.btnSignOut:
                signOut(false);
                break;
            case R.id.btnSignOutDisableAutoSign:
                signOut(true);
                break;
            case R.id.btnDeleteAccount:
                requestCredentials();

                break;
        }
    }

    @Override
    public void onConnected(@Nullable Bundle bundle) {

    }

    @Override
    public void onConnectionSuspended(int i) {

    }

    @Override
    public void onConnectionFailed(@NonNull ConnectionResult connectionResult) {

    }

    @Override
    public void onResult(@NonNull CredentialRequestResult credentialRequestResult) {

        Status status = credentialRequestResult.getStatus();
        if (status.isSuccess()) {
            onCredentialSuccess(credentialRequestResult.getCredential());
        } else {
            if (status.hasResolution()) {
                try {
                    status.startResolutionForResult(this, RC_REQUEST);
                } catch (IntentSender.SendIntentException e) {
                    Log.d(TAG, e.toString());
                }
            } else {
                showToast("Request Failed");
            }
        }
    }

    public void setUpGoogleApiClient() {

        mGoogleApiClient = new GoogleApiClient.Builder(this)
                .addConnectionCallbacks(this)
                .addApi(Auth.CREDENTIALS_API)
                .enableAutoManage(this, this)
                .build();
    }


    private void requestCredentials() {
        mCredentialRequest = new CredentialRequest.Builder()
                .setPasswordLoginSupported(true)
                .build();

        Auth.CredentialsApi.request(mGoogleApiClient, mCredentialRequest).setResultCallback(this);
    }


    @Override
    protected void onDestroy() {
        mGoogleApiClient.disconnect();
        super.onDestroy();
    }

    private void onCredentialSuccess(Credential credential) {

        Auth.CredentialsApi.delete(mGoogleApiClient, credential).setResultCallback(new ResultCallback<Status>() {
            @Override
            public void onResult(@NonNull Status status) {
                if (status.isSuccess()) {
                    signOut(false);
                } else {
                    showToast("Account Deletion Failed");
                }
            }
        });


    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (requestCode == RC_REQUEST) {
            if (resultCode == RESULT_OK) {
                showToast("Deleted");
                Credential credential = data.getParcelableExtra(Credential.EXTRA_KEY);
                onCredentialSuccess(credential);
            } else {
                Log.d(TAG, "Request failed");
            }
        }
    }

    public void showToast(String s) {
        Toast.makeText(getApplicationContext(), s, Toast.LENGTH_SHORT).show();
    }

    private void signOut(boolean disableAutoSignIn) {

        if (disableAutoSignIn)
            Auth.CredentialsApi.disableAutoSignIn(mGoogleApiClient);

        startActivity(new Intent(this, MainActivity.class));
        finish();
    }
}

The output of the above application in action is given below: android smartlock output We created the first account and see that everytime you open the app, it automatically signs in. Unless we disable auto-sign. Then it will ask for permission before signing in. We created another account. This time when we delete it. And the application auto-signs into the first account after deletion. That brings an end to this tutorial. You can download the project from the link below:

Android谷歌SmartLock

Github 项目链接

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

安卓谷歌智能锁 的相关文章

随机推荐

  • 如何删除本地和远程 Git 分支

    分支是日常开发过程的一部分 也是 Git 中最强大的功能之一 一个分支一旦合并 除了历史研究之外就没有任何作用了 成功合并后删除分支是常见且推荐的做法 本指南介绍如何删除本地和远程 Git 分支 删除本地 Git 分支 The git br
  • Python 枚举函数

    enumerate 是 Python 中的一个内置函数 允许您在循环迭代时拥有一个自动计数器 Python enumerate 功能 The enumerate 函数采用以下形式 enumerate iterable start 0 该函数
  • 如何在 Ubuntu 18.04 上部署 Mattermost

    Mattermost 是一个企业级即时消息平台 是一个开源自托管 Slack 替代品 它是用 Golang 和 React 编写的 可以使用 MySQL 或 PostgreSQL 作为数据库后端 Mattermost 将您的所有团队沟通集中
  • 如何在 Ubuntu 18.04 上安装 OpenCart

    OpenCart是一个免费开源的 PHP 电子商务平台 将强大的功能与灵活性和用户友好的界面相结合 OpenCart 具有用户管理 多商店 附属机构 折扣 产品评论 多语言和多个支付网关等功能 是许多在线商家的首选平台 在本教程中 我们将向
  • 如何在 CentOS 7 上停止和禁用 Firewalld

    防火墙D是一个完整的防火墙解决方案 可动态管理网络连接和接口的信任级别 它使您可以完全控制允许或禁止进出系统的流量 从 CentOS 7 开始 FirewallD 取代 iptables 成为默认的防火墙管理工具 强烈建议保持 Firewa
  • 如何使用 Mysqldump 备份和恢复 MySQL 数据库

    本教程介绍如何使用 mysqldump 实用程序从命令行备份和恢复 MySQL 或 MariaDB 数据库 mysqldump 实用程序创建的备份文件基本上是一组可用于重新创建原始数据库的 SQL 语句 mysqldump 命令还可以生成
  • 如何在 Ubuntu 20.04 上安装 Spotify

    Spotify是一种数字音乐流媒体服务 可让您即时访问数百万首歌曲 从经典老歌到最新热门歌曲 本指南展示了在 Ubuntu 20 04 上安装 Spotify 的两种方法 Spotify 可以通过 Snapcraft 商店作为 snap 包
  • 如何在 Debian 9 上设置或更改时区

    使用正确的时区对于许多与系统相关的任务和流程都很重要 例如 cron 守护进程使用系统的时区来执行 cron 作业 并且日志文件中的时间戳基于同一系统的时区 系统的时区是在安装过程中设置的 但以后可以轻松更改 本教程展示如何在 Debian
  • 如何在 Ubuntu 20.04 上安装 VMware Workstation Player

    VMwareWorkstation Player 是一款桌面虚拟化软件 允许您在一台计算机上运行多个独立的操作系统 借助 VMware Player 您可以创建并运行自己的虚拟机 并评估由许多软件供应商提供的作为虚拟设备分发的软件VMwar
  • 如何在 CentOS 8 上设置 Apache 虚拟主机

    Apache 虚拟主机允许您在一台计算机上运行多个网站 使用虚拟主机 您可以指定站点文档根 包含网站文件的目录 为每个站点创建单独的安全策略 使用不同的 SSL 证书等等 本文介绍如何在 CentOS 8 服务器上设置 Apache 虚拟主
  • 如何在 Ubuntu 18.04 上安装 VLC 媒体播放器

    VLC 是最流行的开源多媒体播放器之一 它是跨平台的 几乎可以播放所有多媒体文件以及 DVD 音频 CD 和不同的流媒体协议 本教程介绍如何在 Ubuntu 18 04 上安装 VLC 媒体播放器 相同的说明适用于 Ubuntu 16 04
  • 如何在 CentOS 8 上安装和配置 Redis

    Redis 是一个开源内存键值数据存储 它可以用作数据库 缓存和消息代理 并支持各种数据结构 例如字符串 哈希 列表 集合等 Redis 通过 Redis Sentinel 提供高可用性 并通过 Redis Cluster 跨多个 Redi
  • 如何在Linux中删除组(groupdel命令)

    在 Linux 中 组用于组织和管理用户帐户 组的主要目的是定义一组权限 例如读 写或执行允许对于可以在组内的用户之间共享的给定资源 可以使用以下命令创建一个新组groupadd命令 如果不再需要某个组并且可以从系统中删除 本文介绍了如何在
  • Python 加入列表

    Python join list 的意思是将一串字符串与指定的分隔符连接起来形成一个字符串 有时 当您必须将列表转换为字符串时 它很有用 例如 将字母列表转换为逗号分隔的字符串以保存在文件中 Python 加入列表 我们可以用蟒蛇字符串jo
  • Java Hello World 程序

    每当我们开始学习一门编程语言时 第一个程序总是打印Hello World 在上一篇文章中 我们了解到如何在 Windows 10 上安装 Java 现在我们准备编写并运行我们的第一个 Hello World Java 程序 Java Hel
  • 从 Python 调用 C 函数

    我们可以使用 Python 程序调用 C 函数ctypes module 从 Python 调用 C 函数 它涉及以下步骤 创建具有所需函数的 C 文件 c 扩展名 使用 C 编译器创建共享库文件 so 扩展名 在 Python 程序中 从
  • Java中的迭代器设计模式

    迭代器设计模式中的行为模式之一 迭代器模式用于提供遍历一组对象的标准方法 迭代器模式广泛应用于Java集合框架 Iterator 接口提供了遍历集合的方法 迭代器设计模式 According to GoF iterator design p
  • 如何使用 HAProxy 在 Ubuntu VPS 上设置 HTTP 负载平衡

    关于HAProxy HAProxy 高可用性代理 是一个开源负载均衡器 可以对任何 TCP 服务进行负载均衡 它特别适合 HTTP 负载平衡 因为它支持会话持久性和第 7 层处理 与数字海洋专用网络 HAProxy 可以配置为前端 通过专用
  • SSH 要点:使用 SSH 服务器、客户端和密钥

    介绍 SSH 是一种安全协议 用作远程连接 Linux 服务器的主要方式 它通过生成远程 shell 来提供基于文本的界面 连接后 您在本地终端中输入的所有命令都会发送到远程服务器并在那里执行 在这份备忘单式的指南中 我们将介绍一些使用 S
  • 安卓谷歌智能锁

    在本教程中 我们将讨论智能锁功能并在 Android 应用程序中实现它 谷歌智能锁 Smart Lock 用于通过一劳永逸地保存凭据来自动登录您的应用程序 这意味着 如果您在一段时间后重新安装应用程序 您可以使用之前保存的凭据自动登录 前提