Flutter如何使用mvi? bloc结合自定义http库的实现

news2024/11/17 20:15:25

文章目录

  • 前言
  • 一、先看看如何使用bloc吧
    • 1. 定义页面需要的数据
    • 2. 定义通用加载状态
    • 3. 定义事件
    • 4. 定义bloc
    • 5. 定义UI
    • 6. 使用
  • 二、lib_http
    • 1. request定义
    • 2. response定义
    • 3. 适配器接口
    • 4. 构建adapter需要的数据
    • 5. 网络异常统一封装
    • 6. 核心请求类
    • 7. 提供网络访问配置
    • 8. dio适配器
    • 9. 抽象数据类型
    • 10. HttpBaseRepository
    • 11. 使用片段
  • 总结


前言

提示:本篇并不算严谨的科普文章,仅仅只是记录使用bloc的思路
最近对kotlin的mvi使用比较娴熟,但是关于flutter架构相关的比较少,之前也有看过provider这些框架总觉得没那么好使而且还挺麻烦的,现在也有大佬研究getx的mvvm,这里我就不展开了,我的本意是想使用getx作为路由管理框架,将它的状态管理使用bloc替代,别问为什么这样考虑,getx虽然提供了很强大的状态管理,但是总有些缺点,具体的没有去深入研究真假暂不确定,可能企业级使用bloc会多一点,新版本dart提供了一些新功能,怎么说呢依旧感觉没有kotlin好使,本篇文章的目的是为了记录bloc使用的示例,可能会比较依赖multiple_result这个库,但是使用Result返回参数这个概念感觉还不错,dart提供了类似模式匹配的简化版,搭配Result还是挺不错的,本篇文章和Android mvi 三这篇文章思路是一致的,可惜dart对sealed class的支持比较薄弱,可以当作是换了关键字的抽象类,下面就是最终的简单效果,点击按钮请求网络出现加载动画,拿到数据后显示数据,效果图如下:

在这里插入图片描述


一、先看看如何使用bloc吧

1. 定义页面需要的数据

代码如下:

import '../../lib_base/index.dart';
import '../../models/banner_model.dart';

class MyFromState {
  MyFromState({required this.banner});

/// 可以不写这段代码,将这段代码放在bloc类里面写是可以的
  factory MyFromState.init() {
    return MyFromState(
      banner: InitState()
    );
  }

  final BannerState banner;

  MyFromState copyWith({
    BannerState? banner,
  }) {
    return MyFromState(
      banner: banner ?? this.banner,
    );
  }
}

/// 定义ui状态
sealed class BannerState {}
/// 用于初始化状态实例
class InitState extends BannerState {}
/// 成功返回 - 有数据
class OnSuccess extends BannerState {
  final List<BannerModel> body;
  OnSuccess(this.body);
}
/// 成功返回 - 无数据
class OnNoData extends BannerState {}
/// 加载状态
class OnLoading extends BannerState {
  final LoadingEffect loading;
  OnLoading(this.loading);
}

2. 定义通用加载状态

代码如下:

/// 通用响应事件
sealed class LoadingEffect{}

/// 开启或关闭加载动画, true表示开启加载动画,false表示请求已经结束关闭加载动画
final class Loading extends LoadingEffect {
  final bool isShow;
  Loading(this.isShow);
}

/// 用于处理401需要鉴权的情况
final class OnAuthority extends LoadingEffect {}

3. 定义事件

代码如下:

sealed class MyFromEvent {}

/// 需要发送的事件
class BannerEvent extends MyFromEvent {}

4. 定义bloc

代码如下:

import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:multiple_result/multiple_result.dart';

import '../../lib_base/index.dart';
import '../../lib_http/index.dart';
import '../repository/banner_repository.dart';
import 'event.dart';
import 'state.dart';

class MyFromBloc extends Bloc<MyFromEvent, MyFromState> {
  // 负责数据请求
  final BannerRepository _repository = BannerRepository();

  // 这里和mvi的思路一致,初始化state,并绑定对应事件
  MyFromBloc() : super(MyFromState.init()) {
    // 绑定事件,当使用add时就会执行对应事件
    on<BannerEvent>(_bannerEvent);
  }

  /// 对应事件的处理
  void _bannerEvent(BannerEvent event, Emitter<MyFromState> emit) async {
    // http请求
    final response = await _repository.getBanner(
        onLoading: (isShow) =>
            // 发送加载状态
            emit(state.copyWith(banner: OnLoading(Loading(isShow)))));
     // 判断当前请求结果
    switch (response) {
      case Success():
        // 返回加载成功的数据
        emit(state.copyWith(banner: OnSuccess(response.success)));
        break;
      case Error():
        final error = response.error;
        if (error case NoData()) {
          // 返回无数据
          emit(state.copyWith(banner: OnNoData()));
        }
        if (error case RequestFailure()) {
          // 在下面使用中有对这个函数的定义,这个函数对应于具体的业务项目的业务逻辑处理
          requestFailureError(error.code, error.msg,
              () => emit(state.copyWith(banner: OnLoading(OnAuthority()))));
        }
        break;
    }
  }
}

5. 定义UI

代码如下:

import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

import '../../lib_base/index.dart';
import '../../models/index.dart';
import 'bloc.dart';
import 'event.dart';
import 'state.dart';

class MyFromPage extends StatelessWidget {
  const MyFromPage({super.key});

  
  Widget build(BuildContext context) {
    return BlocProvider(
      create: (_) => MyFromBloc(),
      child: const MyFromView(),
    );
  }
}

class MyFromView extends StatelessWidget {
  const MyFromView({super.key});

  
  Widget build(BuildContext context) {
    return Scaffold(
      body:  BlocBuilder<MyFromBloc, MyFromState>(
        builder: body,
      ),
        floatingActionButton: FloatingActionButton(
          onPressed: _requestBanner(context.read<MyFromBloc>()),
          tooltip: 'Increment',
          child: const Icon(Icons.add),
        )
    );
  }

  Widget body(BuildContext context, MyFromState state) {
    // 拿对应状态
    final banner = state.banner;
    // 如果当前状态是OnLoading则进入判断
    if (banner case OnLoading()) {
      final loading = banner.loading;
      switch(loading) {
        case Loading():
          if (loading.isShow) {
            return _buildLoadingView();
          }
          break;
        case OnAuthority():
          // 跳转登录页
          break;
      }
    }
    if (banner case OnSuccess()) {
      // 成功后显示的布局
      return _listView(banner.body);
    }

    // 这里只会在没有数据的时候触发,如果请求在页面初始化后就已经发起了,这个布局是一个无用布局
    return const Center();
  }

  /// 加载中,加载动画可自行替换
  Widget _buildLoadingView() {
    return const SizedBox(
      width: double.maxFinite,
      height: double.maxFinite,
      child: Center(
        child: SizedBox(
          height: 22,
          width: 22,
          child: CircularProgressIndicator(
            strokeWidth: 2,
            // valueColor: AlwaysStoppedAnimation<Color>(AppColors.primaryBgBlue),
          ),
        ),
      ),
    );
  }

  Widget _listView(List<BannerModel> dataArray) {
    return ListView.builder(
      itemCount: dataArray.length,
        itemBuilder: (context, index) {
          final data = dataArray[index];
          return Column(
            children: [
              Image.network(data.imagePath),
              Text(data.title, style: const TextStyle(fontSize: 30)),
              Text(data.desc, style: const TextStyle(fontSize: 20)),
            ],
          );
        }
    );
  }

  /// 发起网络请求
  void Function() _requestBanner(MyFromBloc bloc) => () {
    bloc.add(BannerEvent());
  };
}

6. 使用

都已经这么详细了就不贴代码了。

二、lib_http

使用适配器模式的http上层封装

1. request定义

代码如下:

import 'rock_net_adapter.dart';

/// http 请求方式
enum HttpMethod { get, post, put, delete, patch }

/// 请求参数配置
abstract class RockRequest {
  /// 请求路径
  final String url;

  /// 规范子类, 必须要传递的参数
  RockRequest(this.url, [this._method = HttpMethod.get]);

  /// 请求路径
  HttpMethod _method;
  HttpMethod get method => _method;

  //region 请求头参数
  /// 请求头参数
  final Map<String, String> _headers = {};

  /// 提供可访问的 header对象, 注意该对象只能用于访问修改该对象无法影响到实际关联对象的修改
  Map<String, String> get headers => {}..addAll(_headers);

  //endregion

  //region 请求参数
  /// 请求参数
  final Map<String, dynamic> _params = {};

  /// 提供参数的访问
  Map<String, dynamic> get params => {}..addAll(_params);

  //endregion

  //region query参数
  // url参数
  final Map<String, String> _queryParams = {};

  /// 提供参数的访问
  Map<String, String> get queryParams => {}..addAll(_queryParams);

  //endregion

  /// 单独设置适配器
  IRockNetAdapter? _adapter;

  IRockNetAdapter? get adapter => _adapter;

  /// 添加请求头
  RockRequest addHeader(String key, String value);

  /// 添加url query参数
  RockRequest addQuery(String key, dynamic value);

  /// 添加请求参数
  RockRequest addParam(String key, dynamic value);

  /// 指定适配器
  void setAdapter(IRockNetAdapter adapter);

  /// 创建一个新对象
  RockRequest setMethod(HttpMethod method);
}

/// 具体实现
class RockRequestBuilder extends RockRequest {
  RockRequestBuilder(super.url);

  
  RockRequest addHeader(String key, String value) {
    _headers[key] = value;
    return this;
  }

  
  RockRequest addParam(String key, value) {
    _params[key] = value;
    return this;
  }

  
  RockRequest addQuery(String key, value) {
    _queryParams[key] = value;
    return this;
  }

  
  void setAdapter(IRockNetAdapter adapter) {
    _adapter = adapter;
  }

  
  RockRequest setMethod(HttpMethod method) {
    _method = method;
    return this;
  }
}

2. response定义

代码如下:


/// 用于封装请求相关参数与处理
final class RockResponse<T> {
  /// 请求状态码
  final int statusCode;
  /// 返回数据
  final T? data;
  /// 异常或消息
  final String? message;
  /// 任意数据
  dynamic extra;

  RockResponse(this.statusCode, {this.data, this.message, this.extra});

  RockResponse copyWith({
    int? statusCode,
    T? data,
    String? message,
    dynamic extra,
  }) {
    return RockResponse(
      statusCode ?? this.statusCode,
      data: data ?? this.data,
      message: message ?? this.message,
      extra: extra ?? this.extra,
    );
  }
}

3. 适配器接口

代码如下:

import 'package:multiple_result/multiple_result.dart';

import 'rock_adapter_engine.dart';
import 'rock_error.dart';
import 'rock_response.dart';

/// 适配器接口
abstract class IRockNetAdapter {
  Future<Result<RockResponse<T>, RockNetException>> send<T>(RockAdapterEngine config);
}

4. 构建adapter需要的数据

代码如下:

import 'rock_request.dart';

/// 构建adapter需要的数据
final class RockAdapterEngine {
  /// base url
  final String _baseUrl;

  /// 请求数据
  final RockRequest request;

  RockAdapterEngine(this._baseUrl, this.request);

  /// 生成url
  String url() {
    // http 和 https的切换
    final (isHttp, authority) = _authority(_baseUrl);
    final uri = isHttp
        ? Uri.https(authority, request.url,
            request.queryParams.isNotEmpty ? request.queryParams : null)
        : Uri.http(authority, request.url,
            request.queryParams.isNotEmpty ? request.queryParams : null);

    return uri.toString();
  }

  /// 获取域名
  (bool, String) _authority(String url) {
    var urlArray = url.split('//');
    if (urlArray.length <= 1) {
      return (false, '');
    }
    if (url.startsWith('https')) {
      return (true, urlArray[1]);
    } else {
      return (false, urlArray[1]);
    }
  }
}

5. 网络异常统一封装

代码如下:

/// 网络异常统一封装
sealed class RockNetException implements Exception {
  final String _message;
  final int _code;

  RockNetException(this._code, this._message);

  /// 请求状态
  int get code => _code;

  /// 获取异常信息
  String get message => _message;
}

/// 通用错误处理
class RockNetError extends RockNetException {
  RockNetError(super.code, super.message);
}

/// 需要登录异常
class RockNeedLogin extends RockNetError {
  RockNeedLogin() : super(401, '请先登录');
}

/// 无权限访问异常
class RockNeedAuth extends RockNetError {
  RockNeedAuth() : super(403, '无权限访问');
}

/// 404
class NotFoundError extends RockNetError {
  NotFoundError() : super(404, '请求路径不正确');
}

/// 500
class InternalServerError extends RockNetError {
  InternalServerError() : super(500, '服务器内部错误');
}

/// 连接异常
class RockNoNetwork extends RockNetError {
  RockNoNetwork() : super(-1, '当前网络连接异常,请检查网络配置');
}

6. 核心请求类

代码如下:

import 'package:multiple_result/multiple_result.dart';
import '../../lib_utils/log_util.dart';
import 'rock_adapter_engine.dart';
import 'rock_error.dart';
import 'rock_net_adapter.dart';
import 'rock_request.dart';

/// json 类型的数据
typedef JSONData = dynamic;

/// 发起请求
final class RockNet {
  /// 发送请求, dynamic表示为json类型
  Future<Result<JSONData, RockNetException>> send<T>(
      String baseUrl,
      RockRequest request,
      IRockNetAdapter adapter,
      void Function(RockRequest)? block) async {
    // 创建request
    final adapterRequest = RockAdapterEngine(baseUrl, request);
    // 注: 使用时必须要指定adapter
    final newAdapter = request.adapter ?? adapter;
    // 使用拦截器
    block?.call(request);
    // 打印请求参数
    _printRequest(adapterRequest);

    // 开始请求
    final result = await newAdapter.send(adapterRequest);
    switch (result) {
      case Success():
        _log('http send result = ${result.success.data}');
        return Success(result.success.data);
      case Error():
        _log("${result.error}");
        return Error(result.error);
    }
  }

  /// 打印请求数据
  void _printRequest(RockAdapterEngine engine) {
    _log('url = ${engine.url()} ${engine.request.method}');
    _log('headers = ${engine.request.headers}');
    _log('params = ${engine.request.params}');
  }

  /// 打印函数
  void _log(dynamic msg) {
    LogUtil.debug(msg);
  }
}

7. 提供网络访问配置

代码如下:

import 'package:multiple_result/multiple_result.dart';

import '../core/rock_error.dart';
import '../core/rock_net.dart';
import '../core/rock_net_adapter.dart';
import '../core/rock_request.dart';

/// 提供网络访问配置
final class RockNetUtil {
  RockNetUtil._();

  static RockNetUtil get instance => _getInstance();
  static RockNetUtil? _instance;

  static RockNetUtil _getInstance() {
    _instance ??= RockNetUtil._();
    return _instance!;
  }

  /// 网络请求 url
  String? _baseUrl;

  /// 适配器, 一定要指定
  IRockNetAdapter? _adapter;

  /// 拦截器, 用于自定义配置
  void Function(RockRequest request)? _interceptor;

  /// 设置 base url
  RockNetUtil setBaseUrl(String url) {
    _baseUrl = url;
    return this;
  }

  /// 设置适配器
  RockNetUtil setAdapter(IRockNetAdapter adapter) {
    _adapter = adapter;
    return this;
  }

  /// 添加拦截器
  void setInterceptor(void Function(RockRequest request) block) {
    _interceptor = block;
  }

  /// 请求统一封装
  Future<Result<JSONData, RockNetException>> _send<T>(RockRequest request, void Function() callback) async {
    final rockNet = RockNet();
    // 回调设置不同属性
    callback();
    return await rockNet.send(_baseUrl!, request, _adapter!, _interceptor);
  }

  /// 发起get请求
  Future<Result<JSONData, RockNetException>> get<T>(RockRequest request) async {
    return _send<T>(request, () {
      request.setMethod(HttpMethod.get);
    });
  }

  /// post请求
  Future<Result<JSONData, RockNetException>> post<T>(RockRequest request) async {
    return _send<T>(request, () {
      request.setMethod(HttpMethod.post);
    });
  }

  /// put请求
  Future<Result<JSONData, RockNetException>> put<T>(RockRequest request) async {
    return _send<T>(request, () {
      request.setMethod(HttpMethod.put);
    });
  }

  /// delete 请求
  Future<Result<JSONData, RockNetException>> delete<T>(RockRequest request) async {
    return _send<T>(request, () {
      request.setMethod(HttpMethod.delete);
    });
  }

  /// patch 请求
  Future<Result<JSONData, RockNetException>> patch<T>(RockRequest request) async {
    return _send<T>(request, () {
      request.setMethod(HttpMethod.patch);
    });
  }
}

8. dio适配器

代码如下:

import 'dart:io';

import 'package:dio/dio.dart';
import 'package:multiple_result/multiple_result.dart';

import '../../lib_utils/log_util.dart';
import '../core/rock_adapter_engine.dart';
import '../core/rock_error.dart';
import '../core/rock_net_adapter.dart';
import '../core/rock_request.dart';
import '../core/rock_response.dart';

/// dio 适配器
class DioAdapter implements IRockNetAdapter {
  
  Future<Result<RockResponse<T>, RockNetException>> send<T>(
      RockAdapterEngine config) async {
    // 提前调用
    var url = config.url();
    // dio 配置
    var options = Options(
      headers: config.request.headers,
      sendTimeout: const Duration(seconds: 60),
    );

    try {
      final response = await _sendHandle(config, url, options);
      return Success(RockResponse(response?.statusCode ?? -1,
          data: response?.data, message: response?.statusMessage));
    } on DioException catch (e) {
      var response = e.response;
      // 输出当前抛出异常的url
      LogUtil.error('url = ${response?.realUri}');

      // 每一种对应错误都需要写出来
      switch (response?.statusCode) {
        case 401:
          return Error(RockNeedLogin());
        case 403:
          return Error(RockNeedAuth());
        case 404:
          return Error(NotFoundError());
        case 500:
          return Error(InternalServerError());
        default:
          if (e.error is SocketException) {
            return Error(RockNoNetwork());
          }

          // 这里的错误一般不是http请求错误不用特殊处理, ui直接弹出提示即可
          return Error(RockNetError(-1, e.toString()));
      }
    }
  }

  /// 用于处理请求的实际实现
  Future<Response?> _sendHandle(
      RockAdapterEngine config, String url, Options options) async {
    switch (config.request.method) {
      case HttpMethod.get:
        return await Dio().get(url, options: options);
      case HttpMethod.post:
        return await Dio()
            .post(config.url(), data: config.request.params, options: options);
      case HttpMethod.put:
        return await Dio()
            .put(config.url(), data: config.request.params, options: options);
      case HttpMethod.delete:
        return await Dio().delete(config.url(),
            data: config.request.params, options: options);
      case HttpMethod.patch:
        return await Dio()
            .patch(config.url(), data: config.request.params, options: options);
    }
  }
}

9. 抽象数据类型

代码如下:

/// 抽象数据接收类
abstract class BaseResult<T> {
  /// 当前请求是否成功
  bool isSuccess();

  /// 实际要返回的数据
  T? getData();

  /// 可以是业务错误, 也可以是http状态码
  int errCode();

  /// 请求成功但返回失败
  String errMsg();
}

10. HttpBaseRepository

下面中的log打印需要替换成弹窗提示,由于当前只是示例需要各位自行引入替换

import 'package:multiple_result/multiple_result.dart';

import '../../lib_utils/log_util.dart';
import '../core/rock_error.dart';
import '../core/rock_net.dart';
import 'base_result.dart';

/// 请求失败
sealed class ResponseStateError {
  final String? _msg;
  final int _code;

  ResponseStateError(this._code, this._msg);

  /// 请求状态
  int get code => _code;

  /// 获取异常信息
  String? get msg => _msg;
}

/// 没有数据返回
class NoData extends ResponseStateError {
  NoData() : super(0, null);
}

/// 忽略改错误,该错误只用于返回
class DefError extends ResponseStateError {
  DefError() : super(0x0, null);
}

/// 请求失败
class RequestFailure extends ResponseStateError {
  RequestFailure(super.code, super.msg);
}

/// Repository 专属返回类型
typedef RepositoryResult<T> = Result<T, ResponseStateError>;

abstract class HttpBaseRepository {
  /// 帮助请求
  /// onLoading - 当前是否加载
  /// request - 具体请求 RockNetUtil.x
  /// onDataConversion - 将请求数据转换成对应类型
  Future<RepositoryResult<T>> baseRequest<T>({
    /// 当前是否加载
    void Function(bool isShow)? onLoading,

    /// 具体请求
    required Future<Result<JSONData, RockNetException>> Function() request,

    /// 数据转换
    required BaseResult<T?> Function(JSONData response) onDataConversion,
  }) async {
    // 开始加载
    onLoading?.call(true);
    // 开始请求并获取结果
    final result = await request();
    // 关闭加载动画
    onLoading?.call(false);

    // 请求成功
    switch (result) {
      case Success():
        final response = result.success;
        final baseModel = onDataConversion(response);
        if (baseModel.isSuccess()) {
          final data = baseModel.getData();
          return data != null ? Success(data) : Error(NoData());
        } else {
          return Error(RequestFailure(baseModel.errCode(), baseModel.errMsg()));
        }
      case Error():
        final err = result.error;
        switch (err) {
          case RockNeedLogin():
            return Error(RequestFailure(err.code, err.message));
          case RockNeedAuth():
            LogUtil.error('RockNeedAuth = ${err.code}: ${err.message}');
            break;
          case NotFoundError():
            LogUtil.error('NotFoundError = ${err.code}: ${err.message}');
            break;
          case InternalServerError():
            LogUtil.error('InternalServerError = ${err.code}: ${err.message}');
            break;
          case RockNoNetwork():
            LogUtil.error('RockNoNetwork = ${err.code}: ${err.message}');
            break;
          case RockNetError():
            LogUtil.error('RockNetError = ${err.code}: ${err.message}');
            break;
        }
        return Error(DefError());
    }
  }
}

11. 使用片段

代码如下:

/// 请求失败处理, 独立个体方便使用, 上面代码也使用到了这个,这里的封装需要根据对应项目业务来展开
/// 这里只提供一个处理的思路
void requestFailureError(int errCode, String? errMsg, void Function()? onAuth) {
  if (errCode == 401 || errCode == -1001) {
    onAuth?.call();
  }
}
final class TestRepository extends HttpBaseRepository {
  /// 获取轮播图
  Future<RepositoryResult<List<BannerModel>>> getBanner({
    // 请求结束,可用于关闭加载动画
    void Function(bool isShow)? onLoading,
  }) async {
    final request = RockRequestBuilder('/banner/json');
    final response = await baseRequest(
        onLoading: onLoading,
        request: () => RockNetUtil.instance.get(request),
        onDataConversion: (jsonData) => BaseModel.fromJson(
            jsonData, (json) => BannerModel.fromJsonArray(json)));
    return response;
	}
}

void main() {
  // 初始化网络配置
  initHttp();
  runApp(const MyApp());
}
void initHttp() {
  RockNetUtil.instance
  		// 设置base url
      .setBaseUrl('https://www.wanandroid.com')
      // .setBaseUrl("http://192.168.190.128:3000")
      // 设置适配器
      .setAdapter(DioAdapter())
      // 设置拦截器
      .setInterceptor((request) {
    request.addHeader('token', 'test');
  });
}

// 发起请求测试
void _incrementCounter() async {
    // TODO 测试 发起请求
    final response = await repository.getBanner(
        onLoading: (isShow) => LogUtil.error('开启加载状态: $isShow'));
    if (response case Success()) {
      LogUtil.error(response.success);
    }
    if (response case Error()) {
      final error = response.error;
      if (error case NoData()) {
        LogUtil.error('body 为空');
      }
      if (error case RequestFailure()) {
        requestFailureError(error.code, error.msg, () => LogUtil.error('需要登录'));
      }
    }
}

总结

// 使用到的库
# 状态管理
flutter_bloc: ^8.1.3
# 网络框架
dio: ^5.1.2
# json序列化注解
json_annotation: ^4.8.0
# 日志
logger: ^1.3.0
# result的一种实现
multiple_result: ^5.0.0

以上就是本篇的全部代码,如果感觉思路不太清晰可以先去了解mvi架构图来对照看,bloc的demo示例比较少,官方demo感觉不太能理解其意,所以将mvi思路照搬一下就解释得通了,http库中使用到了multiple_result作为数据返回核心,这样不需要使用try catch来对异常捕获,很好的解决了数据返回异常的特点,如有其他思路欢迎交流讨论。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/670302.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

编译原理笔记13:自上而下语法分析(3)构造预测分析表、LL(1) 文法

目录 构造预测分析表不懂也能用的构造步骤FIRST、FOLLOW 和分析表的原理&#xff1f; LL(1) 文法 构造预测分析表 预测分析表的作用&#xff0c;是为推导的进行指明方向——我们用当前下推栈栈顶和读写头所指向的符号的组合&#xff08;即当前的状态&#xff09;&#xff0c;去…

网络安全学习指南:新手入门建议

&#x1f482; 个人网站:【海拥】【游戏大全】【神级源码资源网】&#x1f91f; 前端学习课程&#xff1a;&#x1f449;【28个案例趣学前端】【400个JS面试题】&#x1f485; 寻找学习交流、摸鱼划水的小伙伴&#xff0c;请点击【摸鱼学习交流群】 目录 前言网络安全基础知识学…

IPv6:连接未来的新一代互联网协议

由于互联网发展迅猛&#xff0c;IPv4地址数量已经接近枯竭。IPv6应运而生&#xff0c;成为下一代互联网协议。IPv6较IPv4来说&#xff0c;地址容量上升了数倍&#xff0c;并有更好的安全性和效率。 IPv6&#xff08;Internet Protocol version 6&#xff09;是指新一代互联网协…

【Java】如何优雅的关闭线程池

文章目录 背景一、线程中断 interrupt二、线程池的关闭 shutdown 方法2.1、第一步&#xff1a;advanceRunState(SHUTDOWN) 把线程池置为 SHUTDOWN2.2、第二步&#xff1a;interruptIdleWorkers() 把空闲的工作线程置为中断2.3、 第三步&#xff1a;onShutdown() 一个空实现&…

PG系列2:Linux下yum安装PG 15

文章目录 一. 下载PG二. 开始安装2.1 安装数据库2.2 初始化数据库2.3 设置开机启动2.4 修改密码2.5 设置允许远程连接2.6 重启数据库服务2.7 修改数据库密码 三. 验证参考: 一. 下载PG 官网地址: https://www.postgresql.org/选择Download 选择CentOS 选择对应的版本 …

iOS 单元测试之常用框架 OCMock 详解

目录 前言&#xff1a; 一、单元测试 1.1 单元测试的必要性 1.2 单元测试的目的 1.3 单元测试依赖的两个主要框架 二、OCMock 的集成与使用 2.1 OCMock 的集成方式 2.2 OCMock 的使用方法 2.3 mock使用限制 三、最后 前言&#xff1a; 在iOS开发中&#xff0c;单元测…

OpenCV——实验结果输出《图像基本操作》

1.图像融合 图像融合主要使用的函数为 cv2.addWeighted() #图像融合 # img_cat img_dog #shapes (414,500,3) (429,499,3) img_dog cv2.resize(img_dog,(500,414))#resize函数&#xff0c;不考虑图像形变问题。 print(img_dog.shape) #两张图片的权重相同&#xff0c;gamm…

java编写金字塔

一、实心金字塔 首先&#xff0c;尝试写一个逐层加1个*的金字塔&#xff1a; 可以看出&#xff0c;每一层由空格和*组成&#xff0c;且空格*的总数为底部基石的数量&#xff0c;那么前后空格数就是&#xff08;底部基石数 - 各层星星数&#xff09;*1/2&#xff0c;然后拼接字…

C# 特性(Attribute)总结

目录 特性是什么&#xff1f; 如何使用特性&#xff1f; &#xff08;1&#xff09;.Net 框架预定义特性 &#xff08;2&#xff09;自定义特性 为什么要使用特性&#xff1f; 特性的应用 特性实现枚举展示描述信息 特性是什么&#xff1f; 特性&#xff08;Attribute&…

基于spss的多元统计分析 之 聚类分析+判别分析(2/8)

实验目的&#xff1a; 1&#xff0e;掌握聚类分析及判别分析的基本原理&#xff1b; 2&#xff0e;熟悉掌握SPSS软件进行聚类分析及判别分析的基本操作&#xff1b; 3&#xff0e;利用实验指导的实例数据&#xff0c;上机熟悉聚类分析及判别分析方法。 实验前预习&#xff1a;…

js中原型和原型链的理解(透彻)

js中原型、原型链、继承的理解&#xff08;透彻&#xff09; 1、前言1.1 什么是函数对象1.2 什么是实例对象1.3 什么是原型对象1.4 构造函数、原型对象、实例对象的关系 2、原型3、原型链4、原型的相关属性及方法5、总结 1、前言 1.1 什么是函数对象 函数对象就是我们平时称呼…

centos连接XShell

先设置网络自动连接&#xff0c;为Xshell 连接centos做准备 选择应用程序->系统工具->设置 选择网络&#xff0c;如果有线没有打开&#xff0c;选择打开&#xff0c;在点击设置 记住ipv4地址&#xff0c;选择自动连接&#xff0c;然后应用 最后鼠标右键点击桌面&#xf…

RabbitMQ入门案例之Topic模式

前言&#xff1a; 本文章将介绍RabbitMQ中的Topic&#xff08;主题&#xff09;模式&#xff0c;其中还会涉及 ‘#’ 和 ‘*’ 两个通配符在RabbitMQ中的区别。 官网文档地址&#xff1a;https://rabbitmq.com/getstarted.html 什么是Topic模式 RabbitMQ的Topic模式是一种基于…

SpringBoot 如何使用 Spring Integration 处理事件

SpringBoot 如何使用 Spring Integration 处理事件 Spring Integration 是 Spring Framework 的一个扩展&#xff0c;它提供了一种基于消息传递的集成模式。使用 Spring Integration&#xff0c;我们可以将不同的应用程序、系统和服务连接起来&#xff0c;从而实现数据的传递、…

VMware中Linux虚拟机配置静态ip

一、输入ip addr查看ip地址 二、输入cd /etc/sysconfig/network-scripts进入centos网络配置文件夹 三、接着输入ls查看目录 四、 输入vi ifcfg-ens33进入网卡配置 五、 进入以后是这个界面&#xff0c;红色方框里的内容是需要手动修改的&#xff0c;下面图片里已经修改过了。 …

【C】分支和循环语句的简单介绍

语句 分支语句if语句语法结构代码演示 switch语句语法结构代码演示 循环语句while循环语法结构代码分析 for循环语法结构代码演示 do...while循环语法结构代码分析 什么是语句呢&#xff1f; 在C语言中由分号&#xff08;;&#xff09;隔开的就是一条语句。 分支语句 if语句 …

【算法设计与分析】期末考试知识总结(知识超浓缩版)

目录 简要介绍 复杂度 迭代 插入排序 二分查找 快排划分 选择排序 计数排序 基数排序 桶排序 递归 递归式的计算-四种方法 欧几里得算法 汉诺塔问题 快速排序 归并排序 堆排序 分治 二维极大点问题 一维最邻近点对 二维最邻近点对 逆序对的数目 凸包 最大字段…

RecyclerView 低耦合单选、多选模块实现

作者&#xff1a;丨小夕 前言 需求很简单也很常见&#xff0c;比如有一个数据列表RecyclerView&#xff0c;需要用户去点击选择一个或多个数据。 实现单选的时候往往简单下标记录了事&#xff0c;实现多选的时候就稍微复杂去处理集合和选中。随着项目选中需求增多&#xff0c…

k8s的部署

二进制搭建 Kubernetes v1.20 k8s集群master01&#xff1a;192.168.92.30 kube-apiserver kube-controller-manager kube-scheduler etcd k8s集群master02&#xff1a;192.168.92.21 k8s集群node01&#xff1a;192.168.92.40 kubelet kube-proxy docker k8s集群node02…

阿里云热修复打补丁包注意事件

1、每次发布app到应用市场前&#xff0c;注意保存没有加固前的apk文件和mapping.txt 2、修复好bug&#xff0c;打包app前&#xff0c;要做的事情 &#xff08;1)先把有问题的apk的mapping.txt文件复制到/app路径下 (2)修改混淆配置&#xff1a;将-printmapping mapping.txt使…