Flutter 应用框架构建(四)网络请求封装
「这是我参与2022首次更文挑战的第5天,活动详情查看:2022首次更文挑战」
应用开发中,网络请求几乎是必不可少的功能,本文将介绍如何通过对 dio
进行二次封装一步一步实现网络请求封装,以便于在项目中方便快捷的使用网络请求。
封装后的网络请求将具备如下功能:
- 简单易用
- 数据解析
- 异常处理
- 请求拦截
- 日志打印
- loading 显示
下面将一步一步带你实现网络请求的封装。
添加依赖
首先在项目里添加 dio
的依赖:
dependencies:
dio: ^4.0.4
请求封装
首先创建一个 RequestConfig
类,用于放置 dio
的配置参数,如下:
class RequestConfig{
static const baseUrl = "https://www.fastmock.site/mock/6d5084df89b4c7a49b28052a0f51c29a/test";
static const connectTimeout = 15000;
static const successCode = 200;
}
配置了请求的 baseUrl
、连接超时时间、请求成功的业务编码。如果还有需其他配置也可以统一配置到该类下。
创建 RequestClient
用于封装 dio
的请求,在类的构造方法中初始化 dio 配置:
RequestClient requestClient = RequestClient();
class RequestClient {
late Dio _dio;
RequestClient() {
_dio = Dio(
BaseOptions(baseUrl: RequestConfig.baseUrl, connectTimeout: RequestConfig.connectTimeout)
);
}
}
在类的上方,创建了一个全局的变量 requestClient
方便外部调用。
dio
本身提供了get
、post
、put
、delete
等一系列 http 请求方法,但是通过源码发现最终这些方法都是调用的 request
的方法实现的。所以这里直接对 dio 的 request
方法进行封装。
Future<dynamic> request(
String url, {
String method = "GET",
Map<String, dynamic>? queryParameters,
data,
Map<String, dynamic>? headers
}) async {
Options options = Options()
..method = method
..headers = headers;
Response response = await _dio.request(url,
queryParameters: queryParameters, data: data, options: options);
return response.data;
}
将常用参数进行统一封装为 request 方法然后调用 dio 的 request
方法,然后再在 request 方法里进行统一的数据处理,如数据解析等。
数据解析
返回数据解析
在移动开发中,开发者习惯将返回数据解析成实体类使用,在 Flutter应用框架搭建(三)Json数据解析 一文中讲解了在 Flutter 中如何将 json 数据解析为实体类,接下来将介绍如何结合 dio 完成数据解析的封装。
项目开发中接口返回的数据结构一般是这样的:
{
"code": 200,
"message": "success",
"data":{
"id": "12312312",
"name": "loongwind",
"age": 18
}
}
结合 Flutter应用框架搭建(三)Json数据解析 一文,创建 ApiResponse
类用于解析接口返回数据:
class ApiResponse<T> {
int? code;
String? message;
T? data;
ApiResponse();
factory ApiResponse.fromJson(Map<String, dynamic> json) => $ApiResponseFromJson<T>(json);
Map<String, dynamic> toJson() => $ApiResponseToJson(this);
@override
String toString() {
return jsonEncode(this);
}
}
关于 json 解析请详阅 Flutter应用框架搭建(三)Json数据解析
因为返回的数据中 data 的数据类型是不定的,所以改造 request
支持泛型,然后在 request 方法中统一进行数据解析,然后返回 data 数据,代码如下:
Future<T?> request<T>(
String url, {
String method = "GET",
Map<String, dynamic>? queryParameters,
data,
Map<String, dynamic>? headers
}) async {
Options options = Options()
..method = method
..headers = headers;
Response response = await _dio.request(url,
queryParameters: queryParameters, data: data, options: options);
return _handleRequestResponse<T>(response);
}
///请求响应内容处理
T? _handleResponse<T>(Response response) {
if (response.statusCode == 200) {
ApiResponse<T> apiResponse = ApiResponse<T>.fromJson(response.data);
return _handleBusinessResponse<T>(apiResponse);
} else {
return null;
}
}
///业务内容处理
T? _handleBusinessResponse<T>(ApiResponse<T> response) {
if (response.code == RequestConfig.successCode) {
return response.data;
} else {
return null;
}
}
通过 ApiResponse
解析返回数据,然后判断 ApiResponse
的业务 code 是否为成功,成功则返回 data 数据。
有时候在应用里还需要调用第三方接口,但是第三方接口返回的数据结构可能会有差异,此时就需要返回原始数据单独做处理。创建一个 RawData
类,用于解析原始数据:
class RawData{
dynamic value;
}
然后修改 RequestClient
中的 _handleResponse
:
///请求响应内容处理
T? _handleResponse<T>(Response response) {
if (response.statusCode == 200) {
if(T.toString() == (RawData).toString()){
RawData raw = RawData();
raw.value = response.data;
return raw as T;
}else {
ApiResponse<T> apiResponse = ApiResponse<T>.fromJson(response.data);
return _handleBusinessResponse<T>(apiResponse);
}
} else {
var exception = ApiException(response.statusCode, ApiException.unknownException);
throw exception;
}
}
新增判断泛型是否为 RawData
,是则直接去除 response.data
放入 RawData
中返回,即 RawData
的 value 就是接口返回的原始数据。
请求数据转换
除了返回数据的解析,实际开发过程中还会遇到对请求参数的处理,比如请求参数为 json 数据,但是代码里为了方便处理使用的实体类,request 中 data 参数可能传入的是一个实体类实例,此时就需要将 data 转换为 json 数据再进行数据请求。
_convertRequestData(data) {
if (data != null) {
data = jsonDecode(jsonEncode(data));
}
return data;
}
Future<T?> request<T>(
String url, {
String method = "GET",
Map<String, dynamic>? queryParameters,
data,
Map<String, dynamic>? headers
}) async {
///...
data = _convertRequestData(data);
Response response = await _dio.request(url,
queryParameters: queryParameters, data: data, options: options);
return _handleResponse<T>(response);
}
}
此处使用 _convertRequestData
方法,将请求 data 数据先使用 jsonEncode
转换为字符串,再使用 jsonDecode
方法将字符串转换为 Map。
异常处理
接下来看看如何进行统一的异常处理,异常一般分为两部分:Http异常、业务异常。
- Http 异常:Http 错误,如 404、503 等
- 业务异常:请求成功,但是业务异常,如:登录时用户名密码错误等
首先创建一个 ApiException
用于统一封装请求的异常信息:
class ApiException implements Exception {
static const unknownException = "未知错误";
final String? message;
final int? code;
String? stackInfo;
ApiException([this.code, this.message]);
factory ApiException.fromDioError(DioError error) {
switch (error.type) {
case DioErrorType.cancel:
return BadRequestException(-1, "请求取消");
case DioErrorType.connectTimeout:
return BadRequestException(-1, "连接超时");
case DioErrorType.sendTimeout:
return BadRequestException(-1, "请求超时");
case DioErrorType.receiveTimeout:
return BadRequestException(-1, "响应超时");
case DioErrorType.response:
try {
/// http 错误码带业务错误信息
ApiResponse apiResponse = ApiResponse.fromJson(error.response?.data);
if(apiResponse.code != null){
return ApiException(apiResponse.code, apiResponse.message);
}
int? errCode = error.response?.statusCode;
switch (errCode) {
case 400:
return BadRequestException(errCode, "请求语法错误");
case 401:
return UnauthorisedException(errCode!, "没有权限");
case 403:
return UnauthorisedException(errCode!, "服务器拒绝执行");
case 404:
return UnauthorisedException(errCode!, "无法连接服务器");
case 405:
return UnauthorisedException(errCode!, "请求方法被禁止");
case 500:
return UnauthorisedException(errCode!, "服务器内部错误");
case 502:
return UnauthorisedException(errCode!, "无效的请求");
case 503:
return UnauthorisedException(errCode!, "服务器异常");
case 505:
return UnauthorisedException(errCode!, "不支持HTTP协议请求");
default:
return ApiException(
errCode, error.response?.statusMessage ?? '未知错误');
}
} on Exception catch (e) {
return ApiException(-1, unknownException);
}
default:
return ApiException(-1, error.message);
}
}
factory ApiException.from(dynamic exception){
if(exception is DioError){
return ApiException.fromDioError(exception);
} if(exception is ApiException){
return exception;
} else {
var apiException = ApiException(-1, unknownException);
apiException.stackInfo = exception?.toString();
return apiException;
}
}
}
/// 请求错误
class BadRequestException extends ApiException {
BadRequestException([int? code, String? message]) : super(code, message);
}
/// 未认证异常
class UnauthorisedException extends ApiException {
UnauthorisedException([int code = -1, String message = ''])
: super(code, message);
}
ApiException 主要根据 DioError 信息创建 ApiException,但是仔细发现其中有一段解析返回数据让创建 ApiException 的代码,如下:
ApiResponse apiResponse = ApiResponse.fromJson(error.response?.data);
if(apiResponse.code != null){
return ApiException(apiResponse.code, apiResponse.message);
}
是因为有些时候后端业务异常时修改了返回的 http 状态码,当 http 状态码非 200 开头时 dio 会抛出 DioError
错误,但此时需要的错误信息为 response 中的错误信息,所以这里需要先解析 response 数据获取错误信息。
ApiException 类创建好后,需要在 request 方法中捕获异常,对 request 方法改造如下:
Future<T?> request<T>(
String url, {
String method = "Get",
Map<String, dynamic>? queryParameters,
data,
Map<String, dynamic>? headers,
bool Function(ApiException)? onError,
}) async {
try {
Options options = Options()
..method = method
..headers = headers;
data = _convertRequestData(data);
Response response = await _dio.request(url,
queryParameters: queryParameters, data: data, options: options);
return _handleResponse<T>(response);
} catch (e) {
var exception = ApiException.from(e);
if(onError?.call(exception) != true){
throw exception;
}
}
return null;
}
///请求响应内容处理
T? _handleResponse<T>(Response response) {
if (response.statusCode == 200) {
ApiResponse<T> apiResponse = ApiResponse<T>.fromJson(response.data);
return _handleBusinessResponse<T>(apiResponse);
} else {
var exception = ApiException(response.statusCode, ApiException.unknownException);
throw exception;
}
}
///业务内容处理
T? _handleBusinessResponse<T>(ApiResponse<T> response) {
if (response.code == RequestConfig.successCode) {
return response.data;
} else {
var exception = ApiException(response.code, response.message);
throw exception;
}
}
在 request 方法上添加了 bool Function(ApiException)? onError
参数,用于错误信息处理的回调,且返回值为 bool
。
request 方法中添加 try-catch 包裹,并在 catch 中创建 ApiException ,调用 onError,当 onError 返回为 true 时即错误信息已被调用方处理,则不抛出异常,否则抛出异常。
同时为 response 数据解析的方法也加上了抛出异常的处理。当业务异常时抛出对应的业务异常信息。
经过上述封装后,确实能对异常信息进行处理,但在实际开发中有个问题,开发中经常会在接口请求成功后做其他处理,比如数据处理或者界面刷新等,请求失败后弹出提示或者错误处理等等,如果按照上述的封装则需要判断返回数据是否为 null 不为空进行后续处理,如果一个业务存在多个请求依赖调用,则此处则会嵌套多次,代码阅读性不好。如下:
var data1 = requestClient.request(url1);
if( data1 != null ){
var data2 = requestClient.request(url2);
if(data2 != null){
var data3 = requestClient.request(url3);
///...
}
}
为了解决上述问题,并且实现统一异常处理,创建一个*的 request 方法:
Future request(Function() block, {bool Function(ApiException)? onError}) async{
try {
await block();
} catch (e) {
handleException(ApiException.from(e), onError: onError);
}
return;
}
bool handleException(ApiException exception, {bool Function(ApiException)? onError}){
if(onError?.call(exception) == true){
return true;
}
if(exception.code == 401 ){
///todo to login
return true;
}
showError(exception.message ?? ApiException.unknownException);
return false;
}
request 方法有个 block
函数参数,在 request 中进行调用,并对其包裹 try-catch ,在 catch 中进行统一异常处理,当外部未处理异常时则在 handleException
中进行统一处理,如 401 则跳转登录页,其他错误统一弹出错误提示。
此时使用如下:
void testRequest() => request(() async {
UserEntity? user = await apiService.test();
print(user?.name);
user = await apiService.test();
print(user?.name);
});
当 request 包裹的代码中其中一个请求错误则不会继续向下执行。
请求拦截
dio 支持添加拦截器自定义处理请求和返回数据,只需实现自定义拦截类继承 Interceptor
实现 onRequest
和 onResponse
即可。
比如当登录后需要给所有请求添加统一的 Header 携带 token 信息时就可以通过拦截器实现。
class TokenInterceptor extends Interceptor{
@override
void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
///token from cache
var token = Cache.getToken();
options.headers["Authorization"] = "Basic $token";
super.onRequest(options, handler);
}
@override
void onResponse(dio.Response response, ResponseInterceptorHandler handler) {
super.onResponse(response, handler);
}
}
然后在初始化 dio 时添加拦截器即可:
_dio.interceptors.add(TokenInterceptor());
日志打印
开发过程中为了方便调试经常需要打印请求返回日志,可以使用自定义拦截器实现,也可以使用第三方实现的日志打印的拦截器 pretty_dio_logger
库。
添加依赖:
pretty_dio_logger: ^1.1.1
dio 添加日期拦截器:
_dio.interceptors.add(PrettyDioLogger(requestHeader: true, requestBody: true, responseHeader: true));
PrettyDioLogger
拦截器可以设置打印哪些信息,可根据需求进行设置。
打印效果:
flutter: ╔╣ Request ║ POST
flutter: ║ https://www.fastmock.site/mock/6d5084df89b4c7a49b28052a0f51c29a/test/test
flutter: ╚══════════════════════════════════════════════════════════════════════════════════════════╝
flutter: ╔ Headers
flutter: ╟ content-type: application/json; charset=utf-8
flutter: ╟ Authorization: Basic ZHhtaF93ZWI6ZHhtaF93ZWJfc2VjcmV0
flutter: ╟ token: Bearer
flutter: ╟ contentType: application/json; charset=utf-8
flutter: ╟ responseType: ResponseType.json
flutter: ╟ followRedirects: true
flutter: ╟ connectTimeout: 15000
flutter: ╟ receiveTimeout: 0
flutter: ╚══════════════════════════════════════════════════════════════════════════════════════════╝
flutter:
flutter: ╔╣ Response ║ POST ║ Status: 200 OK
flutter: ║ https://www.fastmock.site/mock/6d5084df89b4c7a49b28052a0f51c29a/test/test
flutter: ╚══════════════════════════════════════════════════════════════════════════════════════════╝
flutter: ╔ Headers
flutter: ╟ access-control-allow-credentials: [true]
flutter: ╟ connection: [keep-alive]
flutter: ╟ x-powered-by: [Express]
flutter: ╟ set-cookie:
flutter: ║ [connect.sid=s%3AkDiyUQw5crHmB0UuY03dYX3Z2HPVO8Sf.bOVO2aDh%2FSviB70e9Xt5sMQjkiDtorwn%2B%2F
flutter: ║ bKN7y8UtY; Path=/; Expires=Sun, 06 Feb 2022 21:37:08 GMT; HttpOnly]
flutter: ╟ date: [Sun, 06 Feb 2022 09:37:08 GMT]
flutter: ╟ vary: [Accept, Origin, Accept-Encoding]
flutter: ╟ content-length: [82]
flutter: ╟ etag: [W/"52-2tuUsqqRy8jX+vcUJL+3D5AmQss"]
flutter: ╟ content-type: [application/json; charset=utf-8]
flutter: ╟ server: [nginx/1.17.8]
flutter: ╚══════════════════════════════════════════════════════════════════════════════════════════╝
flutter: ╔ Body
flutter: ║
flutter: ║ {
flutter: ║ code: 200,
flutter: ║ message: "success",
flutter: ║ data: {id: 111111, name: zhangsan, age: 18}
flutter: ║ }
flutter: ║
flutter: ╚══════════════════════════════════════════════════════════════════════════════════════════╝
loading 显示
网络请求是一个耗时操作,为了提高用户体验,一般会在请求的过程中显示 loading 提示用户正在加载数据。
前面解决异常处理使用了一个全局的 request 方法,loading 可以使用同样的思路实现,创建 loading 方法:
Future loading( Function block, {bool isShowLoading = true}) async{
if (isShowLoading) {
showLoading();
}
try {
await block();
} catch (e) {
rethrow;
} finally {
dismissLoading();
}
return;
}
void showLoading(){
EasyLoading.show(status: "加载中...");
}
void dismissLoading(){
EasyLoading.dismiss();
}
实现很简单,在 block 调用前后调用 loading 的 show 和 dismiss。同时对 block 包裹 try-catch 保证在异常时取消 loading,并且在 catch 中不做任何处理直接抛出异常。
这里 loading 使用了
flutter_easyloading
插件
对 request 方法进行改造支持 loading :
Future request(Function() block, {bool showLoading = true, bool Function(ApiException)? onError, }) async{
try {
await loading(block, isShowLoading: showLoading);
} catch (e) {
handleException(ApiException.from(e), onError: onError);
}
return;
}
对 request 中的 block 又包装了一层 loading 从而实现自动 loading 的显示隐藏。
使用示例
经过上述步骤就完成了对网络请求的封装,接下来看看怎么使用。
开发过程中常用的网络请求为 get 和 post,为了方便调用,在 RequestClient
中添加 get 和 post 方法,如下:
Future<T?> get<T>(
String url, {
Map<String, dynamic>? queryParameters,
Map<String, dynamic>? headers,
bool showLoading = true,
bool Function(ApiException)? onError,
}) {
return request(url,
queryParameters: queryParameters,
headers: headers,
onError: onError);
}
Future<T?> post<T>(
String url, {
Map<String, dynamic>? queryParameters,
data,
Map<String, dynamic>? headers,
bool showLoading = true,
bool Function(ApiException)? onError,
}) {
return request(url,
method: "POST",
queryParameters: queryParameters,
data: data,
headers: headers,
onError: onError);
}
实际也是封装后调用 request 方法。
基本使用
void login(String password) => request(() async {
LoginParams params = LoginParams();
params.username = "loongwind";
params.password = password;
UserEntity? user = await requestClient.post<UserEntity>(APIS.login, data: params);
state.user = user;
update();
});
/// View
Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// Text("${SR.hello.tr} : ${state.count}", style: TextStyle(fontSize: 50.sp),),
ElevatedButton(onPressed: () => controller.login("123456"), child: const Text("正常登录")),
ElevatedButton(onPressed: () => controller.login("654321"), child: const Text("错误登录")),
Text("登录用户:${state.user?.username ?? ""}", style: TextStyle(fontSize: 20.sp),),
],
)
自定义异常处理
void loginError(bool errorHandler) => request(() async {
LoginParams params = LoginParams();
params.username = "loongwind";
params.password = "654321";
UserEntity? user = await requestClient.post<UserEntity>(APIS.login, data: params);
state.user = user;
print("-------------${user?.username ?? "登录失败"}");
update();
}, onError: (e){
state.errorMessage = "request error : ${e.message}";
print(state.errorMessage);
update();
return errorHandler;
});
onError 无论返回 false 或者 true 都会调用 onError 方法,且 print("-------------${user?.username ?? "登录失败"}");
这句输出并没有执行,当 onError 返回 false 时依然会弹出错误的提示,是因为返回 false 时调用了默认的异常处理弹出提示,返回 true 时则不会调用默认的异常处理方法。
在 requestClient 的请求方法上添加 onError 处理是一样的效果,不同的是在 requestClient 上的 onError 为 true 时,下面的代码会正常执行:
void loginError(bool errorHandler) => request(() async {
LoginParams params = LoginParams();
params.username = "loongwind";
params.password = "654321";
UserEntity? user = await requestClient.post<UserEntity>(APIS.login, data: params, onError: (e){
state.errorMessage = "request error : ${e.message}";
print(state.errorMessage);
update();
return errorHandler;
});
state.user = user;
print("-------------${user?.username ?? "登录失败"}");
update();
});
界面效果跟上面的一样,当 onError 返回 true 时,requestClient 下面的代码会正常执行。即会打印出 -------------登录失败
, 返回 false 时则不会执行下面的代码。
loading 显示隐藏
void loginLoading(bool showLoading) => request(() async {
LoginParams params = LoginParams();
params.username = "loongwind";
params.password = "123456";
UserEntity? user = await requestClient.post<UserEntity>(APIS.login, data: params, );
state.user = user;
update();
}, showLoading: showLoading);
切换接口地址
在开发过程中会出现多个环境地址,比如开发环境、测试环境、预发布环境、生产环境等,此时为了方便切换环境一般都会在开发时增加一个环境切换的功能,此时就可以修改 baseUrl
然后重新创建 RequestClient
来实现。代码如下:
RequestConfig.baseUrl = "https://xxxxxx";
requestClient = RequestClient();
源码:flutter_app_core
Flutter 应用框架搭建系列文章:
- Flutter应用框架搭建(一)GetX集成及使用详解
- Flutter应用框架搭建(二)屏幕适配
- Flutter应用框架搭建(三) Json数据解析
- Flutter应用框架搭建(四) 网络请求封装
上一篇: linux (iv) 网络链接测试~
下一篇: VMware 三种网络连接类型的概念
推荐阅读
-
NeurIPS 2022 | 最强斗地主AI!网易互娱AI Lab提出基于完美信息蒸馏的方法-完美信息蒸馏(PTIE) 在斗地主游戏中,非完美信息的引入主要是由于三位玩家均不能看到别人的手牌,对于任意一位玩家而言,仅可知道其余两位玩家当前手牌的并集,而难于精准判断每位玩家当前手牌。完美信息蒸馏的思路是针对这种非完美问题,构建一个第三方角色,该角色可以看到三位玩家的手牌,该角色在不告知每位玩家完美信息的情况下通过信息蒸馏的方式引导玩家打出当前情况下合理的出牌。 以强化学习常用的 Actor-Critic 算法为例,PTIE 在 Actor-Critic 算法的应用中可以利用 Critic 的 Value 输出作为蒸馏手段来提升 Actor 的表现。具体而言即在训练中 Critic 的输入为完美信息(包含所有玩家的手牌信息),Actor 的输入为非完美信息(仅包含自己手牌信息),此种情况下 Critic 给予的 Value 值包含了完美信息,可以更好地帮助 Actor 学习到更好的策略。 从更新公式上来看,正常的 Actor-Critic 算法 Actor 更新的方式如下: 在 PTIE 模式下,对于每个非完美信息状态 h,我们可以在 Critic 中构建对应的完美信息状态 D(h),并用 Critic 的输出来更新 Actor 的策略梯度,从而达到完美信息蒸馏的效果。 PTIE 框架的整体结构如下图所示: 无论是训练还是执行过程中智能体都不会直接使用完美信息,在训练中通过蒸馏将完美信息用于提升策略,从而帮助智能体达到一个更高的强度。 PTIE 的另一种蒸馏方式是将完美信息奖励引入到奖励值函数的训练中,PerfectDou 提出了基于阵营设计的完美信息奖励 node reward,以引导智能体学习到斗地主游戏中的合作策略,其定义如下: 如上所示,完美信息部分 代表 t 时刻地主手牌最少几步可以出完,在斗地主游戏中可以近似理解为是距游戏获胜的距离, 代表 t 时刻地主阵营和农民阵营距游戏获胜的距离之差, 为调节系数。通过此种奖励设计,在训练时既可以一定程度地引入各玩家的手牌信息(出完的步数需要知道具体手牌才能计算),同时也鼓励农民以阵营的角度做出决策,提升农民的合作性。 特征构建: PerfectDou 针对牌类游戏的特点主要构建了两部分特征:牌局状态特征和动作特征。其中牌局状态特征主要包括当前玩家手牌牌型特征、当前玩家打出的卡牌牌型特征、玩家角色、玩家手牌数目等常用特征,动作特征主要用于刻画当前状态下玩家的所有可能出牌,包括了每种出牌动作的牌型特征、动作的卡牌数目、是否为最大动作等特征。 牌型特征为 12 * 15 的矩阵,如下图所示: 该矩阵前 4 行代表对应每种卡牌的张数,5-12 行代表该种卡牌的种类和对应位置。 网络结构和动作空间设计 针对斗地主游戏出牌组合数较多的问题,PerfectDou 基于 RLCard 的工作上对动作空间进行了简化,对占比最大的两个出牌牌型:飞机带翅膀和四带二进行了动作压缩,将整体动作空间由 27472 种缩减到 621 种。 PerfectDou 策略网络结构如下图所示: 策略网络结构同样分为两部分:状态特征部分和动作特征部分。 在状态特征部分,LSTM 网络用于提取玩家的历史行为特征,当前牌局状态特征和提取后的行为特征会再通过多层的 MLP 网络输出当前的状态信息 embedding。 在动作特征部分,每个可行动作同样会经过多层 MLP 网络进行编码,编码后的动作特征会与其对应的状态信息 embedding 经过一层 MLP 网络计算两者间的相似度,并经由 softmax 函数输出对应的动作概率。 实验结果
-
.NET高级面试指南 Topic XVIII [ 介绍外观模式(Appearance Pattern),该模式提供了一个隐藏系统复杂性的简化界面 ]。- 简化复杂系统:当系统具有复杂的子系统结构时,可以使用外观模式来简化界面。提供统一界面:当客户端需要访问多个子系统时,可以使用外观模式提供统一界面。 外观模式在现代软件开发中得到广泛应用,尤其是在复杂系统中。例如 图形用户界面库:许多图形用户界面库(如 Qt、GTK+ 等)都使用外观模式来隐藏底层的复杂性,并为开发人员提供简单的界面来创建用户界面。 操作系统接口:操作系统中的系统调用和应用程序接口通常也使用外观模式来隐藏底层硬件和系统的复杂性,为应用程序提供访问系统资源的简单接口。企业应用程序:在可能涉及多个子系统的大型企业应用程序中,外观模式可用于封装这些子系统,并为客户端提供统一的使用界面。 网络框架:许多网络框架(如 ASP.NET MVC、Spring MVC 等)也使用外观模式来隐藏底层的复杂性,并为开发人员提供简单的接口来处理 HTTP 请求和响应。 集成开发环境(IDE):集成开发环境通常包含代码编辑器、编译器、调试器等多种功能。外观模式可用于封装这些功能,并为开发人员提供开发软件的简单界面。 代码示例:
-
澎湃新闻对话腾讯丁珂:从 "治已病 "到 "治未病",企业需快速构建 "安全免疫力"--丁珂指出,对企业而言,安全不是成本而是生命线 丁珂指出,对企业而言,安全不是成本而是生命线,也是商业 "硬币 "的另一面。在数字智能化的新阶段,发展驱动安全建设已成为普遍共识,企业需要转变安全思维,从被动建设到主动防御,构建一套新的安全范式和框架,以更加积极、主动的安全观来提升数字安全免疫力,以 "治未病 "的理念取代 "治已病",前置安全,快速构建 "安全免疫力"。对 "已病",前置预判,及时应对处置安全风险,才能维护品牌价值,保障健康发展。 与此同时,安全建设还普遍存在 "不知道往哪投、怎么投 "的痛点。对此,腾讯安全提出,企业可以按照数字安全免疫模型的框架进行安全全局部署,重点在业务安全、数据安全、安全运维管理、边界安全、终端安全、应用开发安全等薄弱环节的关键领域注入 "免疫增强针"。 今年进入公众视野的AIGC还在产业化、产品化的过程中,但大量攻击者已经利用它生成攻击脚本、钓鱼邮件,甚至伪造身份进行诈骗。"人工智能本身是否安全,会不会让网络更不安全? 腾讯安全研究认为,AIGC的风险主要集中在 "无法解释 "和 "无法追踪 "的特点上,但这在技术上是能够找到应对方法的。丁珂谈到,AIGC作为生产力的巨大提升,确实会带来更复杂的攻防态势和更大的防御难度。但任何新技术都要经历这样的周期。而法律法规也会随着技术的演进而不断更新,使新技术的发展更加规范和健全。 丁珂认为,随着我国网络安全法律法规体系的不断完善,合规性将给企业推进网络安全带来很大的推动力,并很直观地展现在需求端。未来,伴随着数据要素市场的建立或企业对数据价值的挖掘,也将带动数据安全市场的快速增长。 对于腾讯安全的商业逻辑和运营,丁珂表示,不谋求建立竞争壁垒,而是期望与生态共同发展,腾讯安全希望通过能力开放,实现安全与业务相伴的生态模式。 谈到未来,丁磊表示,安全领域已经进入加速发展期,在蓝海中会持续关注很多新的业务领域,希望孵化出新的商业模式,腾讯安全团队也会持续关注并抓住机会做好产品。 以下为采访实录(在不改变原意的基础上略有删减): 冲浪新闻:当前,以人工智能、大数据等新技术为驱动的第四次工业革命正向纵深推进,给人类生产生活带来深刻变革。而互联网作为新技术的载体,面临的安全挑战不仅数量越来越多,形式也越来越复杂。从互联网安全从业者的角度,腾讯观察到近年来国内外网络安全形势发生了哪些变化?这些变化呈现出怎样的趋势?
-
Flutter 应用框架构建(四)网络请求封装