标签 用户体验 下的文章

在现代 Web 应用的交互场景中,数据删除操作是用户与系统频繁互动的功能之一。当用户轻松点击 “删除” 按钮时,背后实则是一系列环环相扣、复杂精妙的技术流程在运转。而且,多数情况下,内容并不会真正从服务器上消失,只是被标记为不可见,依然留存在服务器的数据仓库中。本文将深入剖析这一过程及其背后的多重考量。

一、前端触发:点击背后的初始响应

(一)前端事件捕获

当用户在网站界面点击 “删除” 按钮时,前端的 JavaScript 脚本迅速响应,如同训练有素的哨兵。它会利用事件监听机制,如 addEventListener 方法,精准捕捉这一点击事件。为了避免用户误操作,通常会弹出确认对话框,以友好的提示询问用户是否真的要删除该项内容。这个对话框就像是一道安全闸门,为可能的误删操作设置了一道防线。

(二)请求发送

若用户确认删除,前端会借助 AJAX(Asynchronous JavaScript and XML)或者 Fetch API 发起一个 HTTP 请求。按照 RESTful API 的设计规范,这个请求一般采用 DELETE 方法。请求中会携带要删除资源的唯一标识符,如数据库中的主键 ID 或者 UUID,它就像是一把钥匙,能让后端准确找到要处理的数据。

二、后端承接:请求的接收与精细处理

(一)路由匹配

后端服务器接收到前端发送的请求后,首先会根据请求的 URL 和 HTTP 方法进行路由匹配。这就好比在一个大型图书馆中,根据书籍的分类标识和借阅规则,快速定位到负责处理该请求的具体函数。通过精确的路由匹配,请求被导向相应的处理逻辑。

(二)请求验证

在正式处理请求之前,后端会进行严格的验证工作。这包括用户身份验证,通过检查用户的登录凭证、令牌等信息,确保请求来自合法的用户;同时还会进行数据验证,检查请求中携带的数据格式是否正确、是否符合业务规则。只有通过这一系列严格的验证,请求才会被允许继续处理。

(三)核心删除操作

这是整个删除流程的关键环节。虽然用户直观感觉数据已被删除,但实际情况存在两种不同的处理方式:

  • 逻辑删除:这是更为常见的处理方式。后端会在数据库中对数据进行标记,通常是通过设置一个名为 is_deleted 的字段。将该字段的值设置为特定标识(如 true1),表示数据已被删除。此时,数据在物理上仍然完整地保留在数据库中,只是在后续的查询操作中,会通过 SQL 语句的 WHERE 条件过滤掉这些标记为已删除的数据,从而对普通用户不可见。
  • 物理删除:直接从数据库中永久移除数据。这种方式相对较少使用,尤其是在需要考虑数据恢复的业务场景下。因为一旦执行物理删除,数据将难以恢复,可能会给业务带来不可挽回的损失。

(四)后续操作跟进

删除操作完成后,后端可能会执行一些后续操作来确保系统的一致性和可追溯性。例如,更新缓存以保证数据的实时性,避免用户看到旧的数据;同时记录审计日志,详细记录删除操作的执行者、执行时间、被删除的数据等信息,为后续的数据分析和安全审计提供依据。

三、结果反馈:后端到前端的信息传递

(一)响应状态返回

后端完成处理后,会返回一个 HTTP 状态码给前端。例如,返回 200 OK 表示操作成功;若出现错误,会返回相应的错误状态码,如 400 Bad Request 表示请求参数有误,403 Forbidden 表示用户没有权限进行该操作等。这个状态码就像是一个信号旗,向前端传达操作的基本结果。

(二)详细反馈信息

除了状态码,后端还会返回详细的消息,进一步告知用户删除操作的具体结果。例如,“删除成功” 或者 “由于 XX 原因,删除失败” 等信息,让用户清楚了解操作的最终情况。
四、前端呈现:用户体验的优化与更新

(一)界面更新

前端接收到后端的响应后,会根据响应状态对用户界面进行更新。如果删除操作成功,通常会从视图中移除已标记为删除的数据项,让用户直观看到数据已被删除。这一过程就像是舞台上的演员退场,界面变得更加简洁。

(二)用户体验提升

为了提升用户体验,前端会采取一系列优化措施。比如,在请求发送过程中显示加载指示器,让用户知道系统正在处理请求,避免用户因长时间等待而产生焦虑;操作完成后提供确认消息,给予用户明确的反馈;甚至还可以提供可选的撤销操作,允许用户在一定时间内反悔,恢复刚刚删除的数据,增加用户操作的灵活性和安全感。

五、数据留存:不消失的背后逻辑

(一)数据恢复保障

逻辑删除的方式为数据恢复提供了便利。在用户误操作删除数据后,可以通过简单的操作,如将 is_deleted 字段的值改回原来的状态,轻松恢复数据。这就像是给数据加了一个 “后悔药”,避免了因误删造成的信息丢失,保障了数据的安全性和完整性。

(二)数据分析与合规审计

保留数据对于企业的数据分析和审计工作具有重要意义。通过对历史数据的分析,企业可以了解用户行为、业务趋势等信息,为决策提供有力支持。同时,在一些行业中,法规要求企业保留一定期限的业务数据,以便进行合规审计。逻辑删除可以满足这一需求,确保企业在不影响正常业务操作的前提下,能够顺利通过审计。

(三)存储性能优化

逻辑删除可以减少频繁的物理删除操作,从而提高数据库的性能。物理删除操作会导致数据库产生碎片,影响数据的读写效率。而逻辑删除只是对数据进行标记,不会对数据库的物理结构造成实质性的改变,减少了数据库维护的成本和复杂度。

六、安全防线:保障删除操作的可靠性

(一)身份验证与授权管理

在实现删除功能时,确保只有经过身份验证的用户才能进行操作是至关重要的。通过用户登录系统时的身份验证机制,如用户名和密码、OAuth 认证等,确定用户的合法性。同时,根据用户的角色和权限,精确控制用户能够删除的数据范围,防止越权操作。

(二)CSRF 攻击防护

为了防止跨站请求伪造(CSRF)攻击,系统会使用 CSRF 令牌。在用户登录或页面加载时,服务器会生成一个唯一的 CSRF 令牌,并将其嵌入到页面中。当用户发起删除请求时,前端会将该令牌一并发送给后端。后端会验证该令牌的有效性,只有令牌合法的请求才会被处理,从而确保请求的来源是合法的,避免恶意网站伪造请求。

(三)确认机制强化

在用户执行删除操作之前,提供明确的确认提示是防止误删除的有效手段。除了前端弹出的确认对话框,还可以在后端进行二次确认,确保用户的删除意图是真实的。例如,要求用户输入密码或验证码等额外信息,进一步提高删除操作的安全性。

七、总结

当我们在网站上轻点删除按钮时,背后隐藏着一个复杂而精密的技术世界。尽管表面上数据似乎已被删除,但实际上它往往只是被标记为不可见,仍然静静地存留在服务器上。这种逻辑删除的方式为数据管理带来了极大的灵活性,既避免了误删除带来的困扰,又满足了数据恢复、分析和审计等多方面的需求。通过深入理解删除操作背后的机制,开发者可以更好地设计和实现安全、高效的删除功能,为用户提供更加优质的服务体验。希望本文能为你的开发实践提供有价值的参考,让你在处理数据删除问题时更加得心应手。

现状分析

在 Android 第三方 APP 拍照功能的实现现状中,我们不难发现存在不少问题,其中图像不清晰是较为突出的一个。经过深入研究发现,这主要归因于大部分 APP 采用 preview(预览)截图的方式来充当拍照结果。这种做法存在很大的局限性,preview 截图只是对相机预览画面的简单截取,并没有经过相机真正拍照时完整且精细的图像生成和处理流程。相机拍照时,会利用复杂的光学和电子元件精确地捕捉光线信息,然后经过一系列诸如自动对焦、自动曝光、色彩校正、降噪等处理环节,而 preview 截图往往缺失这些关键步骤,所以导致拍摄出的图像质量远低于预期。

所以,这种方法不仅导致图片质量差,而且用户体验也不佳。由于没有充分利用Android系统的相机功能,很多开发者在实现拍照时面临诸多困难。大概归为下面几个比较突出的问题:

  • 图片质量低:预览截图无法获取高质量的照片。
  • 用户体验差:拍照过程不够流畅,用户可能需要多次尝试才能获得满意的照片。
  • 功能限制:无法使用相机的高级功能,如闪光灯、焦距调整等。

正确的拍照方式

一、调用系统相机拍照

当第三方 APP 在 Android 系统中调用相机拍照时,本质上是通过与系统相机服务交互来实现的。应用会发送一个特定的 Intent 来唤起系统相机应用。这个 Intent 可以携带一些参数,比如指定输出的图像格式、存储位置等。系统相机应用启动后,它会初始化相机硬件,包括启动相机传感器、配置镜头参数等。当用户触发拍照操作时,相机传感器开始工作,光线通过镜头在传感器上成像。传感器将光信号转换为电信号,再经过模数转换为数字信号。这些数字信号随后在相机内部的图像处理器中经过一系列复杂算法的处理,如根据光线条件调整曝光值、通过自动对焦算法使图像清晰对焦、对色彩进行平衡处理以保证色彩的准确性。处理完成后,图像数据会根据设定的格式(如 JPEG)存储在指定位置,并通过 Intent 将拍摄结果回传给第三方应用,这个过程需要确保应用有相应的存储权限和读取权限。

那么,在Android中,调用系统相机拍照的逻辑相对简单,主要通过Intent来实现。以下是基本的实现步骤:

  1. 创建Intent:使用MediaStore.ACTION_IMAGE_CAPTURE创建一个拍照的Intent。
  2. 传递文件URI:指定照片存储的位置,确保拍摄的照片能正确保存。
  3. 启动Activity:通过startActivityForResult启动相机界面。
  4. 处理结果:在onActivityResult中处理拍照后的结果。

简单的实现步骤如下:

private static final int REQUEST_IMAGE_CAPTURE = 1;
private Uri photoURI;

private void dispatchTakePictureIntent() {
    Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
    if (takePictureIntent.resolveActivity(getPackageManager()) != null) {
        // 创建一个文件来存储照片
        File photoFile = createImageFile();
        if (photoFile != null) {
            photoURI = FileProvider.getUriForFile(this,
                    "com.example.android.fileprovider",
                    photoFile);
            takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoURI);
            startActivityForResult(takePictureIntent, REQUEST_IMAGE_CAPTURE);
        }
    }
}

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    if (requestCode == REQUEST_IMAGE_CAPTURE && resultCode == RESULT_OK) {
        // 处理拍照结果
        // photoURI指向的文件就是拍摄的照片
    }
}

二、通过Camera2软件包进行拍照

android.hardware.camera2

首先一张图来说明一下构建相机APP的正确逻辑:

2018 I/O大会

Camera2 API提供了对相机硬件的深入控制,支持更复杂的功能,如手动对焦、曝光控制等。使用Camera2 API的基本步骤如下:

  1. 获取CameraManager:使用CameraManager获取相机服务。
  2. 打开相机:通过openCamera方法打开相机。
  3. 创建CaptureSession:配置并创建用于捕获图像的会话。
  4. 拍照:使用CaptureRequest配置拍照参数并执行。

具体实现代码(上述步骤为简要步骤,具体请参考代码):

申请权限

<uses-permission android:name="android.permission.CAMERA" />

获取可用相机设备列表

CameraManager manager = (CameraManager) getSystemService(Context.CAMERA_SERVICE);
try {
    String[] cameraIds = manager.getCameraIdList();
    for (String cameraId : cameraIds) {
        CameraCharacteristics characteristics = manager.getCameraCharacteristics(cameraId);
        // 这里可以进一步检查相机特性,如是否支持特定功能等
    }
} catch (CameraAccessException e) {
    e.printStackTrace();
}

配置相机参数

private CameraDevice.StateCallback stateCallback = new CameraDevice.StateCallback() {
    @Override
    public void onOpened(CameraDevice camera) {
        // 相机打开成功,可以进行参数配置和开始预览、拍照等操作
        try {
            CameraCaptureSession.StateCallback sessionCallback = new CameraCaptureSession.StateCallback() {
                @Override
                public void onConfigured(CameraCaptureSession session) {
                    // 会话配置成功,可以设置拍照请求等
                    try {
                        CaptureRequest.Builder builder = camera.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
                        // 设置图像格式、自动对焦模式等参数
                        builder.addTarget(imageReader.getSurface());
                        CaptureRequest request = builder.build();
                        session.capture(request, null, null);
                    } catch (CameraAccessException e) {
                        e.printStackTrace();
                    }
                }

                @Override
                public void onConfigureFailed(CameraCaptureSession session) {
                    // 会话配置失败处理
                }
            };
            camera.createCaptureSession(Arrays.asList(imageReader.getSurface()), sessionCallback, null);
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void onDisconnected(CameraDevice camera) {
        // 相机断开连接处理
        camera.close();
    }

    @Override
    public void onError(CameraDevice camera, int error) {
        // 相机出现错误处理
        camera.close();
    }
};

进行拍照

CaptureRequest.Builder builder = camera.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
builder.addTarget(imageReader.getSurface());
// 设置自动对焦模式为自动
builder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_AUTO);
// 设置曝光模式为自动
builder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);
CaptureRequest request = builder.build();
session.capture(request, null, null);

总结简单的实现方法

private void openCamera() {
    CameraManager manager = (CameraManager) getSystemService(Context.CAMERA_SERVICE);
    try {
        String cameraId = manager.getCameraIdList()[0]; // 获取后置相机ID
        manager.openCamera(cameraId, stateCallback, null);
    } catch (CameraAccessException e) {
        e.printStackTrace();
    }
}

private CameraDevice.StateCallback stateCallback = new CameraDevice.StateCallback() {
    @Override
    public void onOpened(@NonNull CameraDevice camera) {
        // 相机打开成功,开始拍照
        // 此处可以配置CaptureSession
    }

    @Override
    public void onDisconnected(@NonNull CameraDevice camera) {
        camera.close();
    }

    @Override
    public void onError(@NonNull CameraDevice camera, int error) {
        camera.close();
    }
};

通过CameraX软件包进行拍照

CameraX 是一个 Jetpack 库。同样更方便地提供了包括但不限于相机的预览,分析,视频和图片的拍摄的api。下面是使用代码:

添加依赖

def camerax_version = "1.2.0"
implementation "androidx.camera:camera-core:$camerax_version"
implementation "androidx.camera:camera-camera2:$camerax_version"
def camerax_version = "1.1.0-alpha07"
implementation "androidx.camera:camera-core:$camerax_version"
implementation "androidx.camera:camera-camera2:$camerax_version"
implementation "androidx.camera:camera-lifecycle:$camerax_version"
implementation "androidx.camera:camera-view:$camerax_version"

初始化CameraX

ProcessCameraProvider.getInstance(this).addListener(() -> {
    try {
        cameraProvider = ProcessCameraProvider.getInstance(context).get();
        bindCameraUseCases();
    } catch (ExecutionException | InterruptedException e) {
        // 异常处理
    }
}, ContextCompat.getMainExecutor(this));

配置和使用相机示例

// 创建 ImageCapture 和 Preview 等相机用例,并将它们绑定到相机设备上
private void bindCameraUseCases() {
    Preview preview = new Preview.Builder()
          .build();
    preview.setSurfaceProvider(viewFinder.getSurfaceProvider());

    ImageCapture imageCapture = new ImageCapture.Builder()
          .setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY)
          .build();

    CameraSelector cameraSelector = new CameraSelector.Builder()
          .requireLensFacing(CameraSelector.LENS_FACING_BACK)
          .build();

    try {
        cameraProvider.unbindAll();
        camera = cameraProvider.bindToLifecycle((LifecycleOwner) this, cameraSelector, preview, imageCapture);
    } catch (Exception e) {
        // 绑定失败处理
    }
}

拍照操作

imageCapture.takePicture(ContextCompat.getMainExecutor(this), new ImageCapture.OnImageCapturedCallback() {
    @Override
    public void onCaptureSuccess(@NonNull ImageProxy image) {
        // 拍照成功处理,这里可以获取图像数据并进一步处理
        super.onCaptureSuccess(image);
        image.close();
    }

    @Override
    public void onError(@NonNull ImageCaptureException exception) {
        // 拍照错误处理
        super.onError(exception);
    }
});

依然,提供一个简单的示例

private void startCamera() {
    CameraX.bindToLifecycle(this, preview, imageCapture);
}

// 配置预览
Preview preview = new Preview.Builder().build();

// 配置拍照
ImageCapture imageCapture = new ImageCapture.Builder().build();

小结

在Android应用开发中,实现高质量的拍照功能至关重要。通过合理选择相机API(如Camera2和CameraX),开发者可以提供更高质量的照片和更流畅的用户体验。还有很多内容本文没有提到,例如:HDR,多相机(Multi-Camera)调用,屏幕闪烁设置,视频流(相机帧)的优化,CameraX的Extensions API 。

目前来看,android的相机和iOS一对比,真是一坨越堆越高的大屎山!