首次提交
This commit is contained in:
7
.gitignore
vendored
Normal file
7
.gitignore
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
# https://dart.dev/guides/libraries/private-files
|
||||
# Created by `dart pub`
|
||||
.dart_tool/
|
||||
|
||||
# Avoid committing pubspec.lock for library packages; see
|
||||
# https://dart.dev/guides/libraries/private-files#pubspeclock.
|
||||
pubspec.lock
|
||||
83
example/easy_dart_module_example.dart
Normal file
83
example/easy_dart_module_example.dart
Normal file
@@ -0,0 +1,83 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:EasyDartModule/EasyDartModule.dart';
|
||||
import 'package:EasyDartModule/base/database/DataBase.dart';
|
||||
import 'package:EasyDartModule/base/discovery/Discovery.dart';
|
||||
import 'package:EasyDartModule/base/logger/Logger.dart';
|
||||
import 'package:EasyDartModule/base/storage/Storage.dart';
|
||||
import 'package:EasyDartModule/base/webserver/WebServer.dart';
|
||||
|
||||
void main() async {
|
||||
//初始化服务发现
|
||||
EasyDartModule.init(
|
||||
discoveryConfig: DiscoveryConfig(
|
||||
host: "http://10.20.1.2:8848",
|
||||
namespaceId: "d3b43bfe-f584-4b8f-a390-353abc69c856"));
|
||||
String ip = "10.20.0.80";
|
||||
int port = 9100;
|
||||
String sn = "test-server";
|
||||
//注册实例
|
||||
await EasyDartModule.discovery.registerInstance(sn, ip, port);
|
||||
|
||||
//查询日志服务配置信息
|
||||
String logger = await EasyDartModule.discovery.getConfig("logger");
|
||||
var loggerConfig = jsonDecode(logger);
|
||||
//查询数据库配置信息
|
||||
String mongodb = await EasyDartModule.discovery.getConfig("mongodb");
|
||||
var mongodbConfig = jsonDecode(mongodb);
|
||||
//查询存储配置
|
||||
String storage = await EasyDartModule.discovery.getConfig("storage");
|
||||
var storageConfig = jsonDecode(storage);
|
||||
|
||||
EasyDartModule.init(
|
||||
loggerConfig: LoggerConfig(host: loggerConfig["host"], serviceName: sn),
|
||||
dataBaseConfig: DataBaseConfig(
|
||||
host: mongodbConfig["host"],
|
||||
userName: mongodbConfig["userName"],
|
||||
password: mongodbConfig["password"],
|
||||
dataBase: mongodbConfig["dataBase"]),
|
||||
storageConfig: StorageConfig(
|
||||
host: storageConfig["host"],
|
||||
port: storageConfig["port"],
|
||||
accessKey: storageConfig["accessKey"],
|
||||
secretKey: storageConfig["secretKey"]));
|
||||
|
||||
EasyDartModule.webServer.addHandler(TestController());
|
||||
EasyDartModule.webServer.start(port);
|
||||
|
||||
//测试db
|
||||
Future.delayed(Duration(seconds: 5), () {
|
||||
var db = EasyDartModule.dataBase;
|
||||
var r = db.query("uc_sys_user");
|
||||
print(r);
|
||||
});
|
||||
|
||||
//测试minio
|
||||
String bucketName = "test-bu";
|
||||
String objectName = "/aa/bb/objectName.gif";
|
||||
// EasyDartModule.storage.createBucket(bucketName).then((v) {
|
||||
// EasyDartModule.storage
|
||||
// .uploadObject(
|
||||
// bucketName,
|
||||
// objectName,
|
||||
// File("F:\\qq\\11603720\\Image\\Group2\\\$C\\WL\\\$CWLJBX}@Z7E487L2J4MH`G.gif")
|
||||
// .readAsBytesSync())
|
||||
// .then((path) {
|
||||
// print("上传文件: $path");
|
||||
// EasyDartModule.storage.getObject(bucketName, objectName).then((data) {
|
||||
// print("下载文件");
|
||||
// File("a.gif").writeAsBytesSync(data);
|
||||
// print("下载完毕");
|
||||
// });
|
||||
// });
|
||||
// // EasyDartModule.storage.deleteObject(bucketName, "objectName");
|
||||
// });
|
||||
}
|
||||
|
||||
@RequestMapping(path: "/test")
|
||||
class TestController {
|
||||
@RequestMapping(path: "/tt", method: HttpMethod.GET)
|
||||
Response test(Request request) {
|
||||
return Response(200, body: "ok");
|
||||
}
|
||||
}
|
||||
59
lib/EasyDartModule.dart
Normal file
59
lib/EasyDartModule.dart
Normal file
@@ -0,0 +1,59 @@
|
||||
library EasyDartModule;
|
||||
|
||||
import 'package:EasyDartModule/base/database/DataBase.dart';
|
||||
import 'package:EasyDartModule/base/database/impl/MongoDb.dart';
|
||||
import 'package:EasyDartModule/base/discovery/Discovery.dart';
|
||||
import 'package:EasyDartModule/base/discovery/impl/NacosDiscovery.dart';
|
||||
import 'package:EasyDartModule/base/http/TraceDio.dart';
|
||||
import 'package:EasyDartModule/base/logger/Logger.dart';
|
||||
import 'package:EasyDartModule/base/logger/impl/LokiLogger.dart';
|
||||
import 'package:EasyDartModule/base/mqtt/mqtt.dart';
|
||||
import 'package:EasyDartModule/base/storage/Storage.dart';
|
||||
import 'package:EasyDartModule/base/storage/impl/MinIoStorage.dart';
|
||||
import 'package:EasyDartModule/base/webserver/WebServer.dart';
|
||||
import 'package:EasyDartModule/base/webserver/impl/ShelfWebServer.dart';
|
||||
|
||||
export 'package:shelf/shelf.dart';
|
||||
export 'package:mongo_dart/mongo_dart.dart';
|
||||
|
||||
class EasyDartModule {
|
||||
static Discovery get discovery => Discovery.getInstance();
|
||||
static DataBase get dataBase => DataBase.getInstance();
|
||||
static WebServer get webServer => WebServer.getInstance();
|
||||
static Logger get logger => Logger.getInstance();
|
||||
static TraceDio get dio => TraceDio.getInstance();
|
||||
static Mqtt get mqtt => Mqtt.getInstance();
|
||||
static Storage get storage => Storage.getInstance();
|
||||
static bool init(
|
||||
{DiscoveryConfig? discoveryConfig,
|
||||
DataBaseConfig? dataBaseConfig,
|
||||
LoggerConfig? loggerConfig,
|
||||
MqttConfig? mqttConfig,
|
||||
StorageConfig? storageConfig}) {
|
||||
if (discoveryConfig != null) {
|
||||
//nacos注册配置中心
|
||||
Discovery.setInstance(NacosDiscovery(discoveryConfig));
|
||||
}
|
||||
if (dataBaseConfig != null) {
|
||||
//mongo数据库
|
||||
DataBase.setInstance(MongoDb(dataBaseConfig));
|
||||
}
|
||||
if (mqttConfig != null) {
|
||||
//mqtt
|
||||
Mqtt.setInstance(Mqtt(mqttConfig));
|
||||
}
|
||||
if (storageConfig != null) {
|
||||
//s3存储
|
||||
Storage.setInstance(MinioStorage(storageConfig));
|
||||
}
|
||||
if (loggerConfig != null) {
|
||||
//初始化日志
|
||||
Logger.setInstance(LokiLogger(loggerConfig));
|
||||
//web服务器
|
||||
WebServer.setInstance(ShelfWebServer(logger));
|
||||
//Dio组件
|
||||
TraceDio.setInstance(TraceDio(logger));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
36
lib/base/database/DataBase.dart
Normal file
36
lib/base/database/DataBase.dart
Normal file
@@ -0,0 +1,36 @@
|
||||
abstract class DataBase {
|
||||
static late DataBase _dataBase;
|
||||
|
||||
static DataBase getInstance() {
|
||||
return _dataBase;
|
||||
}
|
||||
|
||||
static void setInstance(DataBase database) {
|
||||
_dataBase = database;
|
||||
}
|
||||
|
||||
// 执行查询操作
|
||||
Future<List<Map<String, dynamic>>> query(String table, {dynamic condition});
|
||||
|
||||
// 执行插入操作
|
||||
Future<void> insert(String table, Map<String, dynamic> data);
|
||||
|
||||
// 执行更新操作
|
||||
Future<void> update(
|
||||
String table, Map<String, dynamic> data, dynamic condition);
|
||||
|
||||
// 执行删除操作
|
||||
Future<void> delete(String table, dynamic condition);
|
||||
}
|
||||
|
||||
class DataBaseConfig {
|
||||
String host;
|
||||
String userName;
|
||||
String password;
|
||||
String dataBase;
|
||||
DataBaseConfig(
|
||||
{required this.host,
|
||||
required this.userName,
|
||||
required this.password,
|
||||
required this.dataBase});
|
||||
}
|
||||
51
lib/base/database/impl/MongoDb.dart
Normal file
51
lib/base/database/impl/MongoDb.dart
Normal file
@@ -0,0 +1,51 @@
|
||||
import 'package:EasyDartModule/base/database/DataBase.dart';
|
||||
import 'package:mongo_dart/mongo_dart.dart';
|
||||
|
||||
class MongoDb implements DataBase {
|
||||
final DataBaseConfig config;
|
||||
final Db db;
|
||||
MongoDb(this.config)
|
||||
: db = Db(
|
||||
"mongodb://${config.userName}:${config.password}@${config.host}/${config.dataBase}?authSource=admin") {
|
||||
Future.delayed(Duration(seconds: 1), () async {
|
||||
do {
|
||||
try {
|
||||
await db.open();
|
||||
print('Connected successfully!');
|
||||
} catch (e) {
|
||||
print('Connection error: $e');
|
||||
await Future.delayed(Duration(seconds: 1));
|
||||
}
|
||||
} while (!db.isConnected);
|
||||
});
|
||||
}
|
||||
|
||||
DbCollection getCollection(String name) {
|
||||
return db.collection(name);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> delete(String table, dynamic condition) async {
|
||||
await getCollection(table).deleteMany(condition);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> insert(String table, Map<String, dynamic> data) async {
|
||||
await getCollection(table).insert(data);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<Map<String, dynamic>>> query(String table,
|
||||
{dynamic condition}) async {
|
||||
if (condition == null) {
|
||||
return await getCollection(table).find().toList();
|
||||
}
|
||||
return await getCollection(table).find(condition).toList();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> update(
|
||||
String table, Map<String, dynamic> data, dynamic condition) async {
|
||||
await getCollection(table).update(condition, data);
|
||||
}
|
||||
}
|
||||
44
lib/base/discovery/Discovery.dart
Normal file
44
lib/base/discovery/Discovery.dart
Normal file
@@ -0,0 +1,44 @@
|
||||
abstract class Discovery {
|
||||
static late Discovery _discovery;
|
||||
|
||||
static Discovery getInstance() {
|
||||
return _discovery;
|
||||
}
|
||||
|
||||
static void setInstance(Discovery discovery) {
|
||||
_discovery = discovery;
|
||||
}
|
||||
|
||||
// 注册实例到 Nacos
|
||||
Future<bool> registerInstance(String serviceName, String ip, int port,
|
||||
{String groupName = 'DEFAULT_GROUP'});
|
||||
|
||||
// 注销实例
|
||||
Future<bool> deRegisterInstance(String serviceName, String ip, int port,
|
||||
{String groupName = 'DEFAULT_GROUP'});
|
||||
|
||||
// 获取服务实例列表
|
||||
Future<List<Map<String, dynamic>>> getInstanceList(String serviceName,
|
||||
{String groupName = 'DEFAULT_GROUP'});
|
||||
|
||||
// 获取配置
|
||||
Future<String> getConfig(String dataId, {String group = 'DEFAULT_GROUP'});
|
||||
|
||||
//获取配置历史记录
|
||||
|
||||
// 发布配置
|
||||
Future<bool> publishConfig(String dataId, String group, String content);
|
||||
|
||||
// 删除配置
|
||||
Future<bool> deleteConfig(String dataId, String group);
|
||||
}
|
||||
|
||||
class DiscoveryConfig {
|
||||
String host;
|
||||
String namespaceId;
|
||||
String groupName;
|
||||
DiscoveryConfig(
|
||||
{required this.host,
|
||||
required this.namespaceId,
|
||||
this.groupName = "DEFAULT_GROUP"});
|
||||
}
|
||||
211
lib/base/discovery/impl/NacosDiscovery.dart
Normal file
211
lib/base/discovery/impl/NacosDiscovery.dart
Normal file
@@ -0,0 +1,211 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:EasyDartModule/base/discovery/Discovery.dart';
|
||||
|
||||
class NacosDiscovery implements Discovery {
|
||||
// final String host;
|
||||
// final String namespaceId;
|
||||
final DiscoveryConfig config;
|
||||
final Dio dio;
|
||||
bool healthCheck = true;
|
||||
|
||||
NacosDiscovery(this.config) : dio = Dio(BaseOptions(baseUrl: config.host));
|
||||
|
||||
// 注册实例到 Nacos
|
||||
@override
|
||||
Future<bool> registerInstance(String serviceName, String ip, int port,
|
||||
{String groupName = 'DEFAULT_GROUP'}) async {
|
||||
try {
|
||||
final response = await dio.post(
|
||||
'/nacos/v2/ns/instance',
|
||||
queryParameters: {
|
||||
'serviceName': serviceName,
|
||||
'ip': ip,
|
||||
'port': port,
|
||||
'groupName': groupName,
|
||||
'namespaceId': config.namespaceId,
|
||||
'ephemeral': true
|
||||
},
|
||||
);
|
||||
if (response.statusCode == 200) {
|
||||
print('服务注册成功: $serviceName');
|
||||
healthCheck = true;
|
||||
//启动定时器 更新服务健康状态
|
||||
Future.doWhile(() async {
|
||||
if (healthCheck) {
|
||||
await Future.delayed(Duration(seconds: 5), () async {
|
||||
//发送心跳包
|
||||
try {
|
||||
final rr = await dio
|
||||
.put("/nacos/v1/ns/instance/beat", queryParameters: {
|
||||
'serviceName': serviceName,
|
||||
'ip': ip,
|
||||
'port': port,
|
||||
'groupName': groupName,
|
||||
'namespaceId': config.namespaceId,
|
||||
});
|
||||
print(rr);
|
||||
} catch (e) {
|
||||
print(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
return healthCheck;
|
||||
}).then((v) {
|
||||
print("心跳线程结束");
|
||||
});
|
||||
// Timer.periodic(Duration(seconds: 5), (timer) async {
|
||||
// //判断是否取消注册
|
||||
// if (!healthCheck) {
|
||||
// timer.cancel();
|
||||
// return;
|
||||
// }
|
||||
// });
|
||||
return true;
|
||||
} else {
|
||||
print('服务注册失败: ${response.statusCode} - ${response.data}');
|
||||
return false;
|
||||
}
|
||||
} catch (e) {
|
||||
print('请求失败: $e');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 注销实例
|
||||
@override
|
||||
Future<bool> deRegisterInstance(String serviceName, String ip, int port,
|
||||
{String groupName = 'DEFAULT_GROUP'}) async {
|
||||
try {
|
||||
final response = await dio.delete(
|
||||
'/nacos/v2/ns/instance',
|
||||
queryParameters: {
|
||||
'serviceName': serviceName,
|
||||
'ip': ip,
|
||||
'port': port,
|
||||
'groupName': groupName,
|
||||
'namespaceId': config.namespaceId,
|
||||
},
|
||||
);
|
||||
if (response.statusCode == 200) {
|
||||
print('服务注销成功: $serviceName');
|
||||
//标记心跳线程为退出状态
|
||||
healthCheck = false;
|
||||
return true;
|
||||
} else {
|
||||
print('服务注销失败: ${response.statusCode} - ${response.data}');
|
||||
return false;
|
||||
}
|
||||
} catch (e) {
|
||||
print('请求失败: $e');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 获取服务实例列表
|
||||
@override
|
||||
Future<List<Map<String, dynamic>>> getInstanceList(String serviceName,
|
||||
{String groupName = 'DEFAULT_GROUP'}) async {
|
||||
try {
|
||||
final response = await dio.get(
|
||||
'/nacos/v2/ns/instance/list',
|
||||
queryParameters: {
|
||||
'serviceName': serviceName,
|
||||
'groupName': groupName,
|
||||
'namespaceId': config.namespaceId,
|
||||
'healthyOnly': true,
|
||||
},
|
||||
);
|
||||
if (response.statusCode == 200) {
|
||||
final instances = response.data["data"]['hosts'];
|
||||
// print('服务实例列表: $instances');
|
||||
return List<Map<String, dynamic>>.from(instances);
|
||||
} else {
|
||||
print('获取服务实例列表失败: ${response.statusCode} - ${response.data}');
|
||||
return [];
|
||||
}
|
||||
} catch (e) {
|
||||
print('请求失败: $e');
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
// 获取配置
|
||||
@override
|
||||
Future<String> getConfig(String dataId,
|
||||
{String group = 'DEFAULT_GROUP'}) async {
|
||||
try {
|
||||
final response = await dio.get(
|
||||
'/nacos/v2/cs/config',
|
||||
queryParameters: {
|
||||
'dataId': dataId,
|
||||
'group': group,
|
||||
'namespaceId': config.namespaceId,
|
||||
},
|
||||
);
|
||||
if (response.statusCode == 200) {
|
||||
// print('获取配置成功: ${response.data}');
|
||||
return response.data["data"];
|
||||
} else {
|
||||
print('获取配置失败: ${response.statusCode} - ${response.data}');
|
||||
return '';
|
||||
}
|
||||
} catch (e) {
|
||||
print('请求失败: $e');
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
// 发布配置
|
||||
@override
|
||||
Future<bool> publishConfig(
|
||||
String dataId, String group, String content) async {
|
||||
try {
|
||||
final response = await dio.post(
|
||||
'/nacos/v2/cs/config',
|
||||
queryParameters: {
|
||||
'dataId': dataId,
|
||||
'group': group,
|
||||
'namespaceId': config.namespaceId,
|
||||
'content': content,
|
||||
},
|
||||
);
|
||||
if (response.statusCode == 200) {
|
||||
print('配置发布成功: $dataId');
|
||||
return true;
|
||||
} else {
|
||||
print('配置发布失败: ${response.statusCode} - ${response.data}');
|
||||
return false;
|
||||
}
|
||||
} catch (e) {
|
||||
print('请求失败: $e');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 删除配置
|
||||
@override
|
||||
Future<bool> deleteConfig(String dataId, String group) async {
|
||||
try {
|
||||
final response = await dio.delete(
|
||||
'/nacos/v2/cs/config',
|
||||
queryParameters: {
|
||||
'dataId': dataId,
|
||||
'group': group,
|
||||
'namespaceId': config.namespaceId,
|
||||
},
|
||||
);
|
||||
if (response.statusCode == 200) {
|
||||
print('配置删除成功: $dataId');
|
||||
return true;
|
||||
} else {
|
||||
print('配置删除失败: ${response.statusCode} - ${response.data}');
|
||||
return false;
|
||||
}
|
||||
} catch (e) {
|
||||
print('请求失败: $e');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
119
lib/base/http/TraceDio.dart
Normal file
119
lib/base/http/TraceDio.dart
Normal file
@@ -0,0 +1,119 @@
|
||||
import 'package:EasyDartModule/base/logger/Logger.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:shelf/shelf.dart' as sf;
|
||||
|
||||
class TraceDio {
|
||||
final Dio _dio;
|
||||
final Logger _logger;
|
||||
|
||||
static late TraceDio _traceDio;
|
||||
|
||||
TraceDio(Logger logger)
|
||||
: _dio = Dio(),
|
||||
_logger = logger {
|
||||
// 配置 Dio
|
||||
// 设置连接超时
|
||||
_dio.options.connectTimeout = Duration(seconds: 5);
|
||||
// 设置接收超时
|
||||
_dio.options.receiveTimeout = Duration(seconds: 5);
|
||||
//保留原始大小写
|
||||
_dio.options.preserveHeaderCase = true;
|
||||
|
||||
// 设置拦截器,自动添加 traceId 和 spanId,并记录日志
|
||||
String traceId = "none";
|
||||
String spanId = "none";
|
||||
_dio.interceptors.add(InterceptorsWrapper(
|
||||
onRequest: (options, handler) {
|
||||
// 获取请求中的 traceId(如果没有则生成)
|
||||
traceId = options.headers['X-Trace-ID'];
|
||||
spanId = options.headers['X-Span-ID'];
|
||||
|
||||
// 在请求头中添加 traceId 和 spanId
|
||||
// options.headers['X-Trace-ID'] = traceId;
|
||||
// options.headers['X-Span-ID'] = spanId;
|
||||
|
||||
// 记录请求日志
|
||||
_logger.info(
|
||||
'traceId=$traceId, spanId=$spanId Sending request: ${options.method} ${options.uri}',
|
||||
tag: "DIO");
|
||||
return handler.next(options); // 继续请求
|
||||
},
|
||||
onResponse: (response, handler) {
|
||||
// 记录响应日志
|
||||
_logger.info(
|
||||
'traceId=$traceId, spanId=$spanId Response received: ${response.statusCode} ${response.statusMessage}',
|
||||
tag: "DIO");
|
||||
return handler.next(response); // 继续处理响应
|
||||
},
|
||||
onError: (DioException e, handler) {
|
||||
// 记录错误日志
|
||||
_logger.error(
|
||||
'traceId=$traceId, spanId=$spanId Request failed: ${e.message}',
|
||||
tag: "DIO");
|
||||
return handler.next(e); // 继续处理错误
|
||||
},
|
||||
));
|
||||
}
|
||||
|
||||
static TraceDio getInstance() {
|
||||
return _traceDio;
|
||||
}
|
||||
|
||||
static void setInstance(TraceDio traceDio) {
|
||||
_traceDio = traceDio;
|
||||
}
|
||||
|
||||
Map<String, dynamic>? getHeader(
|
||||
{Map<String, dynamic>? headers, sf.Request? request}) {
|
||||
if (request != null) {
|
||||
return {
|
||||
"X-Trace-ID": request.context['request_trace_id'] as String,
|
||||
"X-Span-ID": request.context['request_span_id'] as String
|
||||
};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// 发起 GET 请求
|
||||
Future<Response> get(String url,
|
||||
{Map<String, dynamic>? queryParameters, sf.Request? request}) async {
|
||||
return await _dio.get(url,
|
||||
queryParameters: queryParameters,
|
||||
options: Options(headers: getHeader(request: request)));
|
||||
}
|
||||
|
||||
// 发起 POST 请求
|
||||
Future<Response> post(String url,
|
||||
{Object? data,
|
||||
Map<String, dynamic>? queryParameters,
|
||||
sf.Request? request}) async {
|
||||
return await _dio.post(url,
|
||||
data: data,
|
||||
queryParameters: queryParameters,
|
||||
options: Options(headers: getHeader(request: request)));
|
||||
}
|
||||
|
||||
// 发起 PUT 请求
|
||||
Future<Response> put(String url,
|
||||
{Object? data,
|
||||
Map<String, dynamic>? queryParameters,
|
||||
sf.Request? request}) async {
|
||||
return await _dio.put(url,
|
||||
data: data,
|
||||
queryParameters: queryParameters,
|
||||
options: Options(headers: getHeader(request: request)));
|
||||
}
|
||||
|
||||
// 发起 DELETE 请求
|
||||
Future<Response> delete(String url,
|
||||
{Object? data,
|
||||
Map<String, dynamic>? queryParameters,
|
||||
sf.Request? request}) async {
|
||||
return await _dio.delete(
|
||||
url,
|
||||
data: data,
|
||||
queryParameters: queryParameters,
|
||||
options: Options(headers: getHeader(request: request)),
|
||||
);
|
||||
}
|
||||
}
|
||||
21
lib/base/logger/Logger.dart
Normal file
21
lib/base/logger/Logger.dart
Normal file
@@ -0,0 +1,21 @@
|
||||
abstract class Logger {
|
||||
static late Logger _logger;
|
||||
|
||||
static Logger getInstance() {
|
||||
return _logger;
|
||||
}
|
||||
|
||||
static void setInstance(Logger logger) {
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
void info(String msg, {String tag});
|
||||
void warning(String msg, {String tag});
|
||||
void error(String msg, {String tag});
|
||||
}
|
||||
|
||||
class LoggerConfig {
|
||||
String host;
|
||||
String serviceName;
|
||||
LoggerConfig({required this.host, required this.serviceName});
|
||||
}
|
||||
72
lib/base/logger/impl/LokiLogger.dart
Normal file
72
lib/base/logger/impl/LokiLogger.dart
Normal file
@@ -0,0 +1,72 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:EasyDartModule/base/logger/Logger.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
|
||||
enum LoggerLevel {
|
||||
debug(1),
|
||||
info(2),
|
||||
warning(3),
|
||||
error(4),
|
||||
off(5),
|
||||
;
|
||||
|
||||
final int level;
|
||||
const LoggerLevel(this.level);
|
||||
}
|
||||
|
||||
class LokiLogger implements Logger {
|
||||
final LoggerConfig? _config;
|
||||
final Dio dio;
|
||||
LoggerLevel level = LoggerLevel.info;
|
||||
LokiLogger(this._config)
|
||||
: dio = Dio(BaseOptions(
|
||||
baseUrl: _config == null ? "" : _config.host,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"Content-Encoding": "gzip"
|
||||
}));
|
||||
|
||||
@override
|
||||
void info(String msg, {String? tag}) {
|
||||
log(msg, level: LoggerLevel.info, tag: tag);
|
||||
}
|
||||
|
||||
@override
|
||||
void warning(String msg, {String? tag}) {
|
||||
log(msg, level: LoggerLevel.warning, tag: tag);
|
||||
}
|
||||
|
||||
@override
|
||||
void error(String msg, {String? tag}) {
|
||||
log(msg, level: LoggerLevel.error, tag: tag);
|
||||
}
|
||||
|
||||
void log(String msg, {required LoggerLevel level, String? tag}) {
|
||||
if (level.level < this.level.level) {
|
||||
//日志等级小于设置的输出日志等级
|
||||
return;
|
||||
}
|
||||
if (_config == null) {
|
||||
print("$tag $level $msg");
|
||||
} else {
|
||||
//推送到loki服务器
|
||||
//{_config.url}
|
||||
var now = DateTime.now();
|
||||
// 转换为纳秒
|
||||
int nanoseconds = now.microsecondsSinceEpoch * 1000;
|
||||
var zip = gzip.encode(utf8.encode(jsonEncode({
|
||||
"streams": [
|
||||
{
|
||||
"stream": {"service_name": _config.serviceName},
|
||||
"values": [
|
||||
[nanoseconds.toString(), "$tag ${level.name.toUpperCase()} $msg"]
|
||||
]
|
||||
}
|
||||
]
|
||||
})));
|
||||
dio.post("/loki/api/v1/push", data: zip);
|
||||
}
|
||||
}
|
||||
}
|
||||
82
lib/base/mqtt/mqtt.dart
Normal file
82
lib/base/mqtt/mqtt.dart
Normal file
@@ -0,0 +1,82 @@
|
||||
import 'package:mqtt5_client/mqtt5_client.dart';
|
||||
import 'package:mqtt5_client/mqtt5_server_client.dart';
|
||||
|
||||
class Mqtt {
|
||||
final MqttConfig _config;
|
||||
MqttClient? _client;
|
||||
|
||||
Mqtt(this._config);
|
||||
|
||||
static late Mqtt _mqtt;
|
||||
|
||||
static Mqtt getInstance() {
|
||||
return _mqtt;
|
||||
}
|
||||
|
||||
static void setInstance(Mqtt server) {
|
||||
_mqtt = server;
|
||||
}
|
||||
|
||||
Future<bool> connect() async {
|
||||
if (_client != null) {
|
||||
return true;
|
||||
}
|
||||
_client =
|
||||
MqttServerClient.withPort(_config.host, _config.clientId, _config.port);
|
||||
_client?.autoReconnect = true;
|
||||
|
||||
await _client?.connect(_config.username, _config.password);
|
||||
_client?.updates.listen((List<MqttReceivedMessage<MqttMessage>> message) {
|
||||
final recMess = message[0].payload as MqttPublishMessage;
|
||||
final payload =
|
||||
MqttUtilities.bytesToStringAsString(recMess.payload.message!);
|
||||
_config.messgae(message[0].topic!, payload);
|
||||
});
|
||||
_config.topic?.forEach((topic) {
|
||||
subscribe(topic, _config.qos);
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void disconnect() {
|
||||
_client?.disconnect();
|
||||
_client = null;
|
||||
}
|
||||
|
||||
void subscribe(String topic, int qos) {
|
||||
_client?.subscribe(topic, MqttUtilities.getQosLevel(qos));
|
||||
}
|
||||
|
||||
void unSubscribe(String topic) {
|
||||
_client?.unsubscribeStringTopic(topic);
|
||||
}
|
||||
|
||||
void publish(String topic, String msg, {int qos = 0}) {
|
||||
var payload = MqttPayloadBuilder();
|
||||
payload.addString(msg);
|
||||
_client?.publishMessage(
|
||||
topic, MqttUtilities.getQosLevel(qos), payload.payload!);
|
||||
}
|
||||
}
|
||||
|
||||
class MqttConfig {
|
||||
final String host;
|
||||
final int port;
|
||||
final String clientId;
|
||||
String? username;
|
||||
String? password;
|
||||
List<String>? topic;
|
||||
int qos;
|
||||
Function(String topic, String message) messgae;
|
||||
|
||||
MqttConfig(
|
||||
{required this.host,
|
||||
this.port = 1883,
|
||||
required this.clientId,
|
||||
required this.messgae,
|
||||
this.topic,
|
||||
this.qos = 0,
|
||||
this.username,
|
||||
this.password});
|
||||
}
|
||||
36
lib/base/storage/Storage.dart
Normal file
36
lib/base/storage/Storage.dart
Normal file
@@ -0,0 +1,36 @@
|
||||
import 'dart:typed_data';
|
||||
|
||||
abstract class Storage {
|
||||
static late Storage _storage;
|
||||
|
||||
static Storage getInstance() {
|
||||
return _storage;
|
||||
}
|
||||
|
||||
static void setInstance(Storage server) {
|
||||
_storage = server;
|
||||
}
|
||||
|
||||
Future<void> createBucket(String name);
|
||||
Future<void> removeBucket(String name);
|
||||
|
||||
Future<String> uploadObject(
|
||||
String bucketName, String objectName, Uint8List data);
|
||||
Future<Uint8List> getObject(String bucketName, String objectName);
|
||||
Future<void> deleteObject(String bucketName, String objectName);
|
||||
}
|
||||
|
||||
class StorageConfig {
|
||||
final String host;
|
||||
final int port;
|
||||
final bool ssl;
|
||||
final String accessKey;
|
||||
final String secretKey;
|
||||
|
||||
StorageConfig(
|
||||
{required this.host,
|
||||
required this.port,
|
||||
this.ssl = false,
|
||||
required this.accessKey,
|
||||
required this.secretKey});
|
||||
}
|
||||
54
lib/base/storage/impl/MinIoStorage.dart
Normal file
54
lib/base/storage/impl/MinIoStorage.dart
Normal file
@@ -0,0 +1,54 @@
|
||||
import 'dart:async';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:EasyDartModule/base/storage/Storage.dart';
|
||||
import 'package:minio/minio.dart';
|
||||
|
||||
class MinioStorage implements Storage {
|
||||
final Minio _minio;
|
||||
final StorageConfig _config;
|
||||
MinioStorage(this._config)
|
||||
: _minio = Minio(
|
||||
endPoint: _config.host,
|
||||
port: _config.port,
|
||||
useSSL: _config.ssl,
|
||||
accessKey: _config.accessKey,
|
||||
secretKey: _config.secretKey);
|
||||
|
||||
@override
|
||||
Future<void> createBucket(String name) async {
|
||||
if (await _minio.bucketExists(name)) {
|
||||
return;
|
||||
}
|
||||
return await _minio.makeBucket(name);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> removeBucket(String name) async {
|
||||
return await _minio.removeBucket(name);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> deleteObject(String bucketName, String objectName) async {
|
||||
return await _minio.removeObject(bucketName, objectName);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Uint8List> getObject(String bucketName, String objectName) async {
|
||||
MinioByteStream stream = await _minio.getObject(bucketName, objectName);
|
||||
// 将 Stream<List<int>> 转为 List<int>
|
||||
final List<int> bytes = await stream
|
||||
.toList()
|
||||
.then((chunks) => chunks.expand((chunk) => chunk).toList());
|
||||
|
||||
// 转换为 Uint8List
|
||||
return Uint8List.fromList(bytes);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<String> uploadObject(
|
||||
String bucketName, String objectName, Uint8List data) async {
|
||||
await _minio.putObject(bucketName, objectName, Stream.fromIterable([data]));
|
||||
return "http${_config.ssl ? "s" : ""}://${_config.host}:${_config.port}/$bucketName/$objectName";
|
||||
}
|
||||
}
|
||||
32
lib/base/webserver/WebServer.dart
Normal file
32
lib/base/webserver/WebServer.dart
Normal file
@@ -0,0 +1,32 @@
|
||||
abstract class WebServer {
|
||||
static late WebServer _webServer;
|
||||
|
||||
static WebServer getInstance() {
|
||||
return _webServer;
|
||||
}
|
||||
|
||||
static void setInstance(WebServer server) {
|
||||
_webServer = server;
|
||||
}
|
||||
|
||||
void start(int port);
|
||||
void stop();
|
||||
void addHandler(handler);
|
||||
}
|
||||
|
||||
enum HttpMethod {
|
||||
GET,
|
||||
POST,
|
||||
PUT,
|
||||
DELETE,
|
||||
ALL,
|
||||
WS,
|
||||
;
|
||||
}
|
||||
|
||||
class RequestMapping {
|
||||
final HttpMethod method;
|
||||
final String path;
|
||||
|
||||
const RequestMapping({this.method = HttpMethod.ALL, required this.path});
|
||||
}
|
||||
212
lib/base/webserver/impl/ShelfWebServer.dart
Normal file
212
lib/base/webserver/impl/ShelfWebServer.dart
Normal file
@@ -0,0 +1,212 @@
|
||||
import 'dart:io';
|
||||
import 'dart:mirrors';
|
||||
|
||||
import 'package:EasyDartModule/EasyDartModule.dart';
|
||||
import 'package:EasyDartModule/base/logger/Logger.dart';
|
||||
import 'package:EasyDartModule/base/webserver/WebServer.dart';
|
||||
import 'package:shelf/shelf.dart';
|
||||
import 'package:shelf/shelf_io.dart';
|
||||
import 'package:shelf_router/shelf_router.dart';
|
||||
import 'package:shelf_web_socket/shelf_web_socket.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
|
||||
class ShelfWebServer implements WebServer {
|
||||
late Logger logger;
|
||||
HttpServer? _server;
|
||||
final Router _router = Router();
|
||||
final Uuid uuid = Uuid();
|
||||
final Map<String, WebSocketHandler> _wsCall = {};
|
||||
|
||||
ShelfWebServer(this.logger);
|
||||
final String tag = "webserver";
|
||||
|
||||
// 中间件:记录请求日志
|
||||
Middleware logRequests() {
|
||||
return (Handler innerHandler) {
|
||||
return (Request request) async {
|
||||
final requestId = request.context['request_trace_id'] as String;
|
||||
final requestSpanId = request.context['request_span_id'] as String;
|
||||
final requestParentSpanId =
|
||||
request.context['request_parent_span_id'] as String?;
|
||||
final String logPer =
|
||||
"traceId=$requestId spanId=$requestSpanId parentSpanId=$requestParentSpanId";
|
||||
logger.info('$logPer | 请求路径: ${request.requestedUri}', tag: tag);
|
||||
// final stopwatch = Stopwatch()..start();
|
||||
final stopwatch = request.context["request_stop_watch"] as Stopwatch;
|
||||
stopwatch.start();
|
||||
Response response;
|
||||
try {
|
||||
response = await innerHandler(request);
|
||||
stopwatch.stop();
|
||||
logger.info(
|
||||
'$logPer | 响应状态码: ${response.statusCode} | 响应时间: ${stopwatch.elapsedMilliseconds}ms',
|
||||
tag: tag);
|
||||
} catch (e, s) {
|
||||
if (e is HijackException) {
|
||||
//不能处理该异常直接抛出
|
||||
throw e;
|
||||
}
|
||||
stopwatch.stop();
|
||||
logger.error("$logPer | 服务器错误 | ${e.toString()} ${s.toString()}",
|
||||
tag: tag);
|
||||
response = Response(500,
|
||||
body: "Internal Server Error\r\nTraceId:$requestId");
|
||||
}
|
||||
return response;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
// 中间件:为每个请求生成唯一的请求ID
|
||||
Middleware requestIdMiddleware() {
|
||||
return (Handler innerHandler) {
|
||||
return (Request request) async {
|
||||
// 获取请求头中的 X-Request-ID,如果没有则生成一个
|
||||
final requestId = request.headers['X-Trace-ID'] ?? uuid.v4();
|
||||
String? spanId = request.headers['X-Span-ID'];
|
||||
String? parentSpanId;
|
||||
if (spanId != null) {
|
||||
parentSpanId = spanId;
|
||||
}
|
||||
spanId = uuid.v4();
|
||||
final updatedRequest = request.change(context: {
|
||||
'request_trace_id': requestId,
|
||||
"request_span_id": spanId,
|
||||
"request_parent_span_id": parentSpanId,
|
||||
"request_stop_watch": Stopwatch()
|
||||
});
|
||||
return innerHandler(updatedRequest);
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
Future<Response> _routerHandler(Request request) async {
|
||||
String path = request.url.path;
|
||||
if (_wsCall.containsKey(path)) {
|
||||
final String spanId = request.context['request_span_id'] as String;
|
||||
final String requestId = request.context['request_trace_id'] as String;
|
||||
final String? requestParentSpanId =
|
||||
request.context['request_parent_span_id'] as String?;
|
||||
return await webSocketHandler((channel) {
|
||||
_wsCall[path]!.chanelMap[spanId] = channel;
|
||||
_wsCall[path]!.open(spanId);
|
||||
// 监听 WebSocket 消息
|
||||
channel.stream.listen((message) {
|
||||
print('Received message: $message');
|
||||
// 处理消息并回应客户端
|
||||
// channel.sink.add(message);
|
||||
_wsCall[path]!.message(spanId, message);
|
||||
}, onDone: () {
|
||||
try {
|
||||
_wsCall[path]!.close(spanId);
|
||||
_wsCall[path]!.chanelMap.remove(spanId);
|
||||
} catch (e) {
|
||||
print(e);
|
||||
}
|
||||
|
||||
final stopwatch = request.context["request_stop_watch"] as Stopwatch;
|
||||
stopwatch.stop();
|
||||
logger.info(
|
||||
'traceId=$requestId spanId=$spanId parentSpanId=$requestParentSpanId | WebSocket closed | 连接时长: ${stopwatch.elapsedMilliseconds}ms',
|
||||
tag: "webserver");
|
||||
}, onError: (error) {
|
||||
logger.info(
|
||||
'traceId=$requestId spanId=$spanId parentSpanId=$requestParentSpanId | WebSocket error: $error',
|
||||
tag: "webserver");
|
||||
});
|
||||
})(request);
|
||||
} else {
|
||||
return await _router.call(request);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void start(int port) async {
|
||||
//反射获取全部路由地址
|
||||
final handler = Pipeline()
|
||||
.addMiddleware(requestIdMiddleware())
|
||||
.addMiddleware(logRequests())
|
||||
.addHandler(_routerHandler);
|
||||
_server = await serve(handler, InternetAddress.anyIPv4, port);
|
||||
print('Server listening on port ${_server?.port}');
|
||||
}
|
||||
|
||||
@override
|
||||
void stop() {
|
||||
_server?.close();
|
||||
}
|
||||
|
||||
@override
|
||||
void addHandler(handler) {
|
||||
ClassMirror cm = reflectClass(handler.runtimeType);
|
||||
var im = reflect(handler);
|
||||
String path = "";
|
||||
for (var metadata in cm.metadata) {
|
||||
// 检查元数据是否为RequestMapping类型
|
||||
if (metadata.reflectee is RequestMapping) {
|
||||
// 获取实例
|
||||
RequestMapping annotation = metadata.reflectee;
|
||||
path = annotation.path;
|
||||
if (path.endsWith("/")) {
|
||||
path = path.substring(0, path.length - 1);
|
||||
}
|
||||
if (annotation.method == HttpMethod.WS) {
|
||||
//wwebsocket处理句柄直接加入路由
|
||||
addRouter(annotation.method, path, handler);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
im.type.declarations.forEach((k, v) {
|
||||
if (v is MethodMirror) {
|
||||
for (var m in v.metadata) {
|
||||
if (m.reflectee is RequestMapping) {
|
||||
var mp = m.reflectee.path as String;
|
||||
|
||||
if (mp.startsWith("/")) {
|
||||
mp = mp.substring(1, mp.length);
|
||||
}
|
||||
|
||||
String p = "$path/$mp";
|
||||
// print("method: ${m.reflectee.method} $p");
|
||||
//把地址加入路由
|
||||
|
||||
addRouter(m.reflectee.method, p, (req, [a, b, c, d, e, f]) {
|
||||
return im.invoke(v.simpleName, [req]).reflectee;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void addRouter(HttpMethod method, String path, dynamic handler) {
|
||||
if (method == HttpMethod.WS) {
|
||||
//添加websocket连接处理函数
|
||||
if (handler is WebSocketHandler) {
|
||||
_wsCall[path] = handler;
|
||||
} else {
|
||||
print("回调函数类型错误 需要 WebSocketHandler ");
|
||||
}
|
||||
} else {
|
||||
_router.add(method.name, path, handler);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
abstract class WebSocketHandler {
|
||||
Map<String, dynamic> chanelMap = {};
|
||||
void open(String id);
|
||||
void close(String id);
|
||||
void message(String id, dynamic message);
|
||||
|
||||
void sendData(String id, dynamic data) {
|
||||
if (chanelMap.containsKey(id)) {
|
||||
chanelMap[id].sink.add(data);
|
||||
} else {
|
||||
print("id未找到");
|
||||
}
|
||||
}
|
||||
}
|
||||
24
pubspec.yaml
Normal file
24
pubspec.yaml
Normal file
@@ -0,0 +1,24 @@
|
||||
name: EasyDartModule
|
||||
description: A starting point for Dart libraries or applications.
|
||||
version: 1.0.0
|
||||
# repository: https://github.com/my_org/my_repo
|
||||
|
||||
environment:
|
||||
sdk: ^3.5.4
|
||||
|
||||
# Add regular dependencies here.
|
||||
dependencies:
|
||||
# path: ^1.8.0
|
||||
grpc: ^4.0.1
|
||||
dio: ^5.0.0
|
||||
mongo_dart: ^0.10.3
|
||||
shelf: ^1.4.2
|
||||
shelf_router: ^1.1.4
|
||||
shelf_web_socket: ^2.0.1
|
||||
uuid: ^4.5.1
|
||||
mqtt5_client: ^4.6.2
|
||||
minio: ^3.5.7
|
||||
|
||||
dev_dependencies:
|
||||
lints: ^4.0.0
|
||||
test: ^1.24.0
|
||||
Reference in New Issue
Block a user