flutter开发实战-log日志存储zip上传,发送钉钉机器人消息

news2024/7/6 17:50:37

flutter开发实战-log日志存储zip上传,发送钉钉机器人消息

当我们需要Apk上传的时候,我们需要将日志打包并上传到七牛,上传之后通过钉钉通知我们日志下载地址。
这里我使用的是loggy来处理日志

一、引入loggy日志格式插件

在工程的pubspec.yaml中引入插件

  loggy: ^2.0.2
    

使用loggy,当引入loggy后,可以在main方法中进行初始化

import 'package:loggy/loggy.dart';

main() {
  Loggy.initLoggy();
}
    

之后在需要的地方进行输入相关日志

import 'package:loggy/loggy.dart';

class DoSomeWork {
 DoSomeWork() {
   logDebug('This is debug message');
   logInfo('This is info message');
   logWarning('This is warning message');
   logError('This is error message');
 }
}
    

二、日志存储到本地

查看loggy源码看到,日志没有存储到本地,在日志的目录下可以看到printers目录,LoggyPrinter为printer的抽象类
在这里插入图片描述

part of loggy;

/// Printer used to show logs, this can be easily swapped or replaced
abstract class LoggyPrinter {
  const LoggyPrinter();

  void onLog(LogRecord record);
}

    

当然我们需要通过继承该类来实现将日志内容写入到本地文件中,这时候我们定义了一个FilePrinter,实现onLog方法将日志写入文件中。

  • 创建日志File

我们需要指定log日志所在目录,可以使用path_provider来获取document、tmp等目录。

Future<String> createDirectory(String appTAG) async {
  final Directory directory = await getApplicationDocumentsDirectory();
  var file = Directory("${directory.path}/$appTAG");
  try {
    bool exist = await file.exists();
    if (exist == false) {
      await file.create();
    }
  } catch (e) {
    print("createDirectory error");
  }

  return file.path;
}
    

创建日志File

Future<void> getDirectoryForLogRecord() async {
    String currentDay = getCurrentDay();
    if (currentDay != _currentDate) {
      final String fileDir = await createDirectory(this.appTAG);
      file = File('$fileDir/$currentDay.log');

      _sink = file!.openWrite(
        mode: overrideExisting ? FileMode.writeOnly : FileMode.writeOnlyAppend,
        encoding: encoding,
      );

      _currentDate = currentDay;
    }
  }
    
  • 处理日志跨天的情况

每一天的日志对应一个date.log文件,如20240101.log
如果出现正好跨天的情况下,需要生成新的File来处理。这里定义了一个定时器,10分钟检测一次,如果日期不一致,则重新创建File

// 定时器
  void startTimer() {
    timerDispose();
    _timer = Timer.periodic(dateCheckDuration!, (timer) {
      getDirectoryForLogRecord();
    });
  }

  void timerDispose() {
    _timer?.cancel();
    _timer = null;
  }
    
  • IOSink写入文件

写入文件,我们需要用到IOSink,

写入的文件file调用openWrite即可获得IOSink对象。
openWrite方法如下

IOSink openWrite({FileMode mode: FileMode.write, Encoding encoding: utf8});
    

默认情况下写入是会覆盖整个文件的,但是可以通过下面的方式来更改写入模式

IOSink ioSink = logFile.openWrite(mode: FileMode.append);
    

IOSink写入文件流程如下

var logFile = File('log.txt');
var sink = logFile.openWrite();
sink.write('FILE ACCESSED ${DateTime.now()}\n');
await sink.flush();
await sink.close();
    

通过onLog方法输入的record

@override
  void onLog(LogRecord record) async {
    _sink?.writeln('${record.time} [${record.level.toString().substring(0, 1)}] ${record.loggerName}: ${record.message}');
  }
    

通过sink将文件保存到文件中。

完整的FilePrinter如下

import 'dart:async';
import 'dart:convert';
import 'dart:io';

import 'package:loggy/loggy.dart';
import 'package:path_provider/path_provider.dart';
import 'package:common_utils/common_utils.dart';


Future<String?> getDirectory(String appTAG) async {
  final Directory directory = await getApplicationDocumentsDirectory();
  var file = Directory("${directory.path}/$appTAG");
  try {
    bool exist = await file.exists();
    if (exist == true) {
      return file.path;
    }
  } catch (e) {
    print("createDirectory error");
  }

  return null;
}

Future<String> createDirectory(String appTAG) async {
  final Directory directory = await getApplicationDocumentsDirectory();
  var file = Directory("${directory.path}/$appTAG");
  try {
    bool exist = await file.exists();
    if (exist == false) {
      await file.create();
    }
  } catch (e) {
    print("createDirectory error");
  }

  return file.path;
}

// 输出的文本文件, 开启定时器处理跨天的log存储问题
class FilePrinter extends LoggyPrinter {
  final bool overrideExisting;
  final Encoding encoding;
  final String appTAG;

  // 检查日期时长,可能出现跨天的情况,比如十分钟检测一次,
  Duration? dateCheckDuration;

  IOSink? _sink;

  File? file;
  String? _currentDate;

  // 定时器
  Timer? _timer;

  FilePrinter(
    this.appTAG, {
    this.overrideExisting = false,
    this.encoding = utf8,
    this.dateCheckDuration = const Duration(minutes: 10),
  }) {
    directoryLogRecord(onCallback: () {
      // 开启定时器
      startTimer();
    });
  }

  void directoryLogRecord({required Function onCallback}) {
    getDirectoryForLogRecord().whenComplete(() {
      onCallback();
    });
  }

  Future<void> getDirectoryForLogRecord() async {
    String currentDay = getCurrentDay();
    if (currentDay != _currentDate) {
      final String fileDir = await createDirectory(this.appTAG);
      file = File('$fileDir/$currentDay.log');

      _sink = file!.openWrite(
        mode: overrideExisting ? FileMode.writeOnly : FileMode.writeOnlyAppend,
        encoding: encoding,
      );

      _currentDate = currentDay;
    }
  }

  String getCurrentDay() {
    String currentDate =
        DateUtil.formatDate(DateTime.now(), format: "yyyyMMdd");
    return currentDate;
  }

  // 文件删除后重新设置log
  Future<void> resetLogFile() async {
    _currentDate = null;
    getDirectoryForLogRecord();
  }

  @override
  void onLog(LogRecord record) async {
    _sink?.writeln('${record.time} [${record.level.toString().substring(0, 1)}] ${record.loggerName}: ${record.message}');
  }

  dispose() {
    timerDispose();
  }

  // 定时器
  void startTimer() {
    timerDispose();
    _timer = Timer.periodic(dateCheckDuration!, (timer) {
      getDirectoryForLogRecord();
    });
  }

  void timerDispose() {
    _timer?.cancel();
    _timer = null;
  }
}

// 输出到ConsolePrinter
class ConsolePrinter extends LoggyPrinter {
  const ConsolePrinter() : super();

  @override
  void onLog(LogRecord record) {
    print('${record.time} [${record.level.toString().substring(0, 1)}] ${record.loggerName}: ${record.message}');
  }
}

// 多种同时使用的printer
class MultiPrinter extends LoggyPrinter {
  MultiPrinter({
    required this.consolePrinter,
    required this.filePrinter,
  });

  final LoggyPrinter consolePrinter;
  final LoggyPrinter filePrinter;

  @override
  void onLog(LogRecord record) {
    consolePrinter.onLog(record);
    filePrinter.onLog(record);
  }
}

    

三、日志log压缩成zip

将日志log压缩成zip,打包成zip时候,我们需要用到archive插件

在工程的pubspec.yaml中引入插件

  archive: ^3.3.7
    

archive是一个Dart库,用于对各种存档和压缩格式进行编码和解码。

该archive使用示例如下

import 'dart:io';
import 'package:archive/archive_io.dart';

Future<void> main() async {
  // Read the Zip file from disk.
  final bytes = File('test.zip').readAsBytesSync();

  // Decode the Zip file
  final archive = ZipDecoder().decodeBytes(bytes);

  // Extract the contents of the Zip archive to disk.
  for (final file in archive) {
    final filename = file.name;
    if (file.isFile) {
      final data = file.content as List<int>;
      File('out/$filename')
        ..createSync(recursive: true)
        ..writeAsBytesSync(data);
    } else {
      Directory('out/$filename').createSync(recursive: true);
    }
  }

  // Encode the archive as a BZip2 compressed Tar file.
  final tarData = TarEncoder().encode(archive);
  final tarBz2 = BZip2Encoder().encode(tarData);

  // Write the compressed tar file to disk.
  final fp = File('test.tbz');
  fp.writeAsBytesSync(tarBz2);

  // Zip a directory to out.zip using the zipDirectory convenience method
  var encoder = ZipFileEncoder();
  await encoder.zipDirectoryAsync(Directory('out'), filename: 'out.zip');

  // Manually create a zip of a directory and individual files.
  encoder.create('out2.zip');
  await encoder.addDirectory(Directory('out'));
  await encoder.addFile(File('test.zip'));
  encoder.closeSync();
}

压缩log,我这里创建一个log_archive类

  • 首先创建zip目录
Future<String?> getZipDir() async {
  final Directory directory = await getApplicationDocumentsDirectory();
  var file = Directory("${directory.path}/zip");
  try {
    bool exist = await file.exists();
    if (exist == false) {
      await file.create();
    }
  } catch (e) {
    print("createDirectory error");
  }

  return file.path;
}

Future<void> setZipPath({String? zipName}) async {
    if (!(zipName != null && zipName.isNotEmpty)) {
      String currentTime =
      DateUtil.formatDate(DateTime.now(), format: "yyyy_MM_dd_HH_mm_ss");
      zipName = "$currentTime.zip";
    }

    if (!zipName.endsWith(".zip")) {
      zipName = "$zipName.zip";
    }

    String? zipDir = await getZipDir();
    if (zipDir != null && zipDir.isNotEmpty) {
      String zipPath = "${zipDir}/${zipName}";
      this.zipPath = zipPath;
    }
  }
    
  • 创建zip文件

创建zip文件,需要用到ZipFileEncoder,如果有同名的zip文件,则删除后重新生成新的zip文件

Future<void> createZip() async {
    if (!(zipPath != null && zipPath!.isNotEmpty)) {
      return;
    }

    bool fileExists = await checkFileExists();
    if (fileExists == true) {
      // 文件存在
      // 删除后重新创建
      File file = File(zipPath!);
      await file.delete();
    }

    // zip文件重新生成
    zipFileEncoder.open(zipPath!);
  }
    
  • 添加File文件

zipFileEncoder生成zip后,添加File问价

Future<void> addFile(File file) async {
    bool fileExists = await checkFileExists();
    if (fileExists) {
      await zipFileEncoder.addFile(file);
      await close();
    }
  }
    
  • 最后调用close

zipFileEncoder添加File后,需要结束编码并关闭

Future<void> close() async {
    zipFileEncoder.close();
  }
    

log_archive完整代码如下

import 'dart:convert';
import 'dart:io';
import 'package:archive/archive.dart';
import 'package:archive/archive_io.dart';
import 'package:common_utils/common_utils.dart';
import 'package:path_provider/path_provider.dart';

Future<String?> getZipDir() async {
  final Directory directory = await getApplicationDocumentsDirectory();
  var file = Directory("${directory.path}/zip");
  try {
    bool exist = await file.exists();
    if (exist == false) {
      await file.create();
    }
  } catch (e) {
    print("createDirectory error");
  }

  return file.path;
}

// archive
class LogArchive {
  String? zipPath;

  late ZipFileEncoder zipFileEncoder;

  LogArchive() {
    zipFileEncoder = ZipFileEncoder();
  }

  Future<void> setZipPath({String? zipName}) async {
    if (!(zipName != null && zipName.isNotEmpty)) {
      String currentTime =
      DateUtil.formatDate(DateTime.now(), format: "yyyy_MM_dd_HH_mm_ss");
      zipName = "$currentTime.zip";
    }

    if (!zipName.endsWith(".zip")) {
      zipName = "$zipName.zip";
    }

    String? zipDir = await getZipDir();
    if (zipDir != null && zipDir.isNotEmpty) {
      String zipPath = "${zipDir}/${zipName}";
      this.zipPath = zipPath;
    }
  }

  Future<void> createZip() async {
    if (!(zipPath != null && zipPath!.isNotEmpty)) {
      return;
    }

    bool fileExists = await checkFileExists();
    if (fileExists == true) {
      // 文件存在
      // 删除后重新创建
      File file = File(zipPath!);
      await file.delete();
    }

    // zip文件重新生成
    zipFileEncoder.open(zipPath!);
  }

  Future<void> addFile(File file) async {
    bool fileExists = await checkFileExists();
    if (fileExists) {
      await zipFileEncoder.addFile(file);
      await close();
    }
  }

  Future<void> addFiles(List<File> files) async {
    bool fileExists = await checkFileExists();
    if (fileExists) {
      for (File file in files) {
        await zipFileEncoder.addFile(file);
      }
      await close();
    }
  }

  Future<void> close() async {
    zipFileEncoder.close();
  }

  Future<bool> checkFileExists() async {
    if (!(zipPath != null && zipPath!.isNotEmpty)) {
      return false;
    }
    try {
      File file = File(zipPath!);
      bool exist = await file.exists();
      if (exist == true) {
        return true;
      }
    } catch (e) {
      print("checkFileExists error");
    }

    return false;
  }

  // 删除单个zip文件
  Future<void> zipDelete(String aZipPath) async {
    if (aZipPath.isEmpty) {
     return;
    }
    final File file = File(aZipPath);
    bool exist = await file.exists();
    if (exist == false) {
      print("LogArchive 文件不存在");
      return;
    }

    await file.delete();
  }

  // 清空zip目录
  Future<void> zipClean() async {
    String? zipDir = await getZipDir();
    if (zipDir != null && zipDir.isNotEmpty) {
      var dir = Directory(zipDir);
      await dir.delete(recursive: true);
    }
  }

  Future<void> readZip(String zipDir) async {
    if (!(zipPath != null && zipPath!.isNotEmpty)) {
      return;
    }

    // Read the Zip file from disk.
    final File file = File(zipPath!);
    bool exist = await file.exists();
    if (exist == false) {
      print("LogArchive 文件不存在");
      return;
    }

    try {
      // InputFileStream only uses a cache buffer memory (4k by default), not the entire file
      var stream = InputFileStream(zipPath!);
      // The archive will have the memory of the compressed archive. ArchiveFile's are decompressed on
      // demand
      var zip = ZipDecoder().decodeBuffer(stream);
      for (var file in zip.files) {
        final filename = file.name;
        if (file.isFile) {
          final data = file.content as List<int>;
          final logFile = await File('${zipDir}/out/$filename')
            ..create(recursive: true)
            ..writeAsBytesSync(data);
          String logContent = await logFile.readAsString(encoding: utf8);
          print("readZip logContent:${logContent}");
        } else {
          await Directory('${zipDir}/out/' + filename).create(recursive: true);
        }
        // file.clear() will release the file's compressed memory
        file.clear();
      }
    } catch(e) {
      print("readZip e:${e.toString()}");
    }
  }
}

    

四、上传到七牛

文件上传的到七牛,需要用到七牛的qiniu_flutter_sdk插件

在工程的pubspec.yaml中引入插件

  qiniu_flutter_sdk: ^0.5.0
    

通过使用该插件上传示例

    // 创建 storage 对象
  storage = Storage();

  // 创建 Controller 对象
  putController = PutController();

  // 使用 storage 的 putFile 对象进行文件上传
  storage.putFile(File('./file.txt'), 'TOKEN', PutOptions(
      controller: putController,
  ))
    
  • 七牛token获取

我这边使用定义个log_uploader类,由于七牛上传需要token,token需要从服务端获取,所以定义个一个抽象类LogTokenFetch
可以实现一个类来实现getToken。

  // 定义获取token接口
abstract class LogTokenFetch {
  Future<String> getToken();
}

// 下面是示例, 请勿使用
class LogTokenFetchImpl implements LogTokenFetch {
  @override
  Future<String> getToken() {
    // TODO: implement getToken
    return Future.value('');
  }
}
    
  • 文件上传到七牛

我这边使用定义个log_uploader类,本质上还是套用qiniu_flutter_sdk插件的实现。

import 'dart:convert';
import 'dart:io';

import 'package:qiniu_flutter_sdk/qiniu_flutter_sdk.dart';
import 'package:crypto/crypto.dart';

import 'log_token_fetch.dart';

// logUploader
class LogUploader {

  // 创建 storage 对象
  final Storage storage = Storage();
  final PutController putController = PutController();

  LogTokenFetch? tokenFetch;

  LogUploader(this.tokenFetch) {
    init();
  }

  init() {
    // 添加状态监听
    putController.addStatusListener((StorageStatus status) {
      print('LogUploader status:$status');
    });
  }

  Future<String?> uploadFile(File zipFile, {String? customKey}) async {
    if (tokenFetch != null) {
      String? token = await tokenFetch!.getToken();
      if (token != null && token.isNotEmpty) {
        print("token:${token}");
        String key = customKey??md5.convert(utf8.encode(zipFile.path)).toString();
        PutResponse putResponse = await storage.putFile(zipFile, token, options: PutOptions(
          key: key,
          controller: putController,
        ));
        return putResponse.key;
      } else {
        return null;
      }
    } else {
      return null;
    }
  }
}
    

上传七牛过程中,可能会出现一下错误
StorageError [StorageErrorType.RESPONSE, 403]: {error: limited mimeType: this file type (application/octet-stream) is forbidden to upload}

当然需要确认token是否支持了对应的mimeType类型。

五、发送消息到钉钉机器人

经常会遇到钉钉的机器人消息,我们也可以调用其api实现发送消息。
请查考网址:https://open.dingtalk.com/document/orgapp/custom-robots-send-group-messages

这里使用的是插件dingtalk_bot_sender插件,当然dingtalk_bot_sender源码也是http请求实现了发送消息到钉钉机器人的API接口。

在工程的pubspec.yaml中引入插件

  dingtalk_bot_sender: ^1.2.0
    

发送消息,使用的是DingTalkSender,我们可以发送markdown、url、文本等等

使用示例如下

final sender = DingTalkSender(
    hookUrl: hookUrl,
    keyword: keyword,
    appsecret: appsecret,
  );
  await sender.sendText('1');

  final markdown = '''
markdown内容
  ''';
  await sender.sendMarkdown(markdown);
    

我这边发送的是日志链接地址

final sender = DingTalkSender(
    hookUrl: hookUrl,
    keyword: keyword,
    appsecret: appsecret,
  );

await sender.sendLink(
        title: "app日志", text: "下载地址:${url}", messageUrl: url);

到此,flutter开发实战-log日志存储zip上传,发送钉钉机器人消息完成。
在这里插入图片描述

六、小结

flutter开发实战-log日志存储zip上传,发送钉钉机器人消息。

学习记录,每天不停进步。

本文地址:https://brucegwo.blog.csdn.net/article/details/138672565

两款小游戏

战机长空小绳套牛
在这里插入图片描述在这里插入图片描述

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

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

相关文章

Dijkstra求最短路 I:图解 详细代码(图解)

文章目录 题目&#xff1a;Dijkstra求最短路思路伪代码&#xff1a;代码优化优化代码&#xff1a;Java代码 总结 题目&#xff1a;Dijkstra求最短路 给定一个 n个点 m条边的有向图&#xff0c;图中可能存在重边和自环&#xff0c;所有边权均为正值。 请你求出 1号点到 n号点的…

如何打开远程桌面连接?

远程桌面连接是一项强大的功能&#xff0c;它允许我们远程访问其他计算机&#xff0c;并在远程计算机上进行操作。这对于远程办公、技术支持和远程培训等场景非常有用。本文将介绍如何在不同操作系统中打开远程桌面连接。 Windows系统 在Windows操作系统中&#xff0c;打开远程…

实用的Chrome命令 帮你打开Chrome浏览器的隐藏功能

前言 Chrome作为主力浏览器&#xff0c;支持相当丰富的第三方扩展&#xff0c;其实浏览器本身也内置了大量实用的命令。许多实用的功能并没有直接显示在Chrome的菜单上。在这篇文章中&#xff0c;我们将介绍几个实用的chrome:// commands。 通过下面整理的 Chrome 命令&#x…

6.数据库

1.实体用矩形表示&#xff0c;属性用椭圆表示&#xff0c;联系用菱形表示 2.层次模型用数表示 3.网状模型用图结构表示 4.关系模型用二维表格结构来表示 5.概念模式基本表 外模式视图 内模式存储 6.模式/内模式映像 外模式/模式映像 7.数据的物理独立性 跟内模式关系 逻辑是视图…

C语言自定义类型枚举、枚举类型的定义、枚举的特点、以及自定义类型联合体、联合类型的定义、联合的特点、联合大小的计算、联合判断大小端 的介绍

文章目录 前言一、枚举1. 枚举类型的定义2. 枚举的特点 二、联合&#xff08;共用体&#xff09;1. 联合类型的定义2. 联合的特点3. 联合大小的计算4. 联合体判断大小端1. 不适用联合体判断大小端2. 使用联合体判断大小端 总结 前言 C语言自定义类型枚举、枚举类型的定义、枚举…

MyBatis——MyBatis入门程序

一、数据准备 二、开发步骤 1、引入依赖 <dependencies><dependency><groupId>org.mybatis</groupId><artifactId>mybatis</artifactId><version>3.5.15</version></dependency><dependency><groupId>c…

邻域注意力Transformer

邻域注意力&#xff08;NA&#xff09;&#xff0c;这是第一个高效且可扩展的视觉滑动窗口注意力机制&#xff0c;NA是一种逐像素操作&#xff0c;将自注意力&#xff08;SA&#xff09;定位到最近的相邻像素&#xff0c;因此与SA的二次复杂度相比&#xff0c;具有线性时间和空…

6 7 8 9 11 12 15 17 18 20 22cm散热风扇防护网风扇金属网罩

品牌&#xff1a;威驰 颜色分类&#xff1a;60mm/6cm金属网,80mm/8cm金属网,92mm/9.2cm金属网,110mm/11cm金属网,120mm/12cm金属网,150mm/15cm金属网,172mm/17.2cm金属网,200mm/20cm金属网,280mm/28cm金属网 1产品参数&#xff0c;防护网罩60 80 90 110 120 125 145 150 180…

详解:ic网站建设开发需要注意什么?

IC网站建设开发需注重专业内容的呈现、强大的产品检索功能、全面的技术支持、严格的合规性展示、便捷的采购工具、良好的用户账户管理、移动适应性和多语言支持&#xff0c;以及高性能与高安全性&#xff0c;以满足行业用户的专业需求&#xff0c;提升网站的实用性和吸引力。 …

linux - 搭建部署ftp服务器

ftp 服务: 实现ftp功能的一个服务,安装vsftpd软件搭建一台ftp服务器 ftp协议: 文件传输协议 (file transfer protocol),在不同的机器之间实现文件传输功能, 例如 视频文件下载,源代码文件下载 公司内部:弄一个专门的文件服务器,将公司里的文档资料和视频都存放…

游戏测试的基本要求

一.测试基本要求 1.研发开发游戏时&#xff1a;编写测试用例、提前开始测试独立模块、催促开发在规定工时完成内容 2.需求开发完成后&#xff1a;研发自测完成、我们执行测试用例、如果用例不完整&#xff08;时间够就维护用例&#xff09;、实际测试发现用例之外的问题 3.完…

DIY可视化软件环境准备

DIY官网可视化工具做好的可视化拖拽开发工具无须编程、零代码基础、所见即所得设计工具支持轻松在线可视化导出微信小程序、支付宝小程序、头条小程序、H5、WebApp、UNIAPP等源码 支持组件库,高颜值,卡片,列表,轮播图,导航栏,按钮,标签,表单,单选,复选,下拉选择,多层选择,级联选…

python中的数据可视化:二维直方图 hist2d()

【小白从小学Python、C、Java】 【计算机等考500强证书考研】 【Python-数据分析】 python中的数据可视化&#xff1a; 二维直方图 hist2d() 选择题 关于以下代码输出结果的说法中正确的是? import matplotlib.pyplot as plt import numpy as np x np.random.normal(0, 1, …

短剧奔向小程序,流量生意如何开启?

随着移动互联网的飞速发展&#xff0c;小程序作为一种轻量级、易传播的应用形态&#xff0c;逐渐在各个领域展现出其独特的商业价值。而最近爆火的短剧小视频作为一种受众广泛的娱乐形式&#xff0c;与小程序结合后&#xff0c;不仅为观众提供了更为便捷的观看体验&#xff0c;…

Windows下安装httpd

一、下载http安装包 1、下载地址 Welcome! - The Apache HTTP Server Project 2、点击“Download” 3、选择对应httpd服务&#xff0c;点击“Files for Microsoft Windows” 4、选择“Apache Lounge”&#xff0c;进入下载页面 5、点击“httpd-2.4.59-240404-win64-VS17.zip …

基于SSM框架多人命题系统

采用技术 基于SSM框架多人命题系统的设计与实现~ 开发语言&#xff1a;Java 数据库&#xff1a;MySQL 技术&#xff1a;SpringMVCMyBatis 工具&#xff1a;IDEA/Ecilpse、Navicat、Maven 页面展示效果 学生端 登录 个人中心 公告信息 试题信息 管理员 登录 个人信息…

Blazor入门-基础知识+vs2022自带例程的理解

参考&#xff1a; Blazor 教程 - 生成首个应用 https://dotnet.microsoft.com/zh-cn/learn/aspnet/blazor-tutorial/intro Blazor基础知识&#xff1a;Visual Studio 2022 中的Blazor开发入门_vs2022 blazor webassembly-CSDN博客 https://blog.csdn.net/mzl87/article/detail…

局域网手机端远程控制手机

局域网手机端远程控制手机 随着科技的进步和智能设备的普及&#xff0c;远程控制技术在日常生活与工作中的应用越来越广泛。其中&#xff0c;局域网内的手机端远程控制手机技术&#xff0c;因其便捷性和实用性&#xff0c;受到了众多用户的关注。本文将简要介绍该技术及其应用…

【Vue】Vue的核心

目录 计算属性-computed插值语法实现methods实现计算属性实现使用使用总结&#xff1a; 监视属性-watch监视的两种写法&#xff1a;深度监视备注&#xff1a; computed和watch之间的区别 绑定样式class样式绑定字符串写法数组写法对象写法 style样式绑定对象式1对象式2数组式 条…

探索GitHub上的GPTs项目:泄露和被破解的GPT提示

GPTs项目是一个在GitHub上由用户linexjlin发起的开源项目&#xff0c;专注于提供泄露的GPT&#xff08;生成式预训练转换器&#xff09;提示。这些提示用于指导和优化AI模型的输出&#xff0c;进而提升代码生成的质量和效率。项目页面提供了丰富的功能和资源&#xff0c;旨在帮…