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

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

@overridevoid 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;}// 文件删除后重新设置logFuture<void> resetLogFile() async {_currentDate = null;getDirectoryForLogRecord();}@overridevoid 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();@overridevoid 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;@overridevoid 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 filefinal 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 methodvar 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 filevar stream = InputFileStream(zipPath!);// The archive will have the memory of the compressed archive. ArchiveFile's are decompressed on// demandvar 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 memoryfile.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 {@overrideFuture<String> getToken() {// TODO: implement getTokenreturn 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://xiahunao.cn/news/3030995.html

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

相关文章

指针系列三

文章目录 1.字符指针&#xff1a;2.数组指针&#xff1a;3.二维数组传参的本质4.函数指针变量typedef 关键字 5.函数指针数组6.转移表 1.字符指针&#xff1a; 字符指针&#xff0c;也称为字符串指针&#xff0c;是指向内存中的字符或字符串的指针。 在C语言中&#xff0c;字符…

bash: docker-compose: 未找到命令

bash: docker-compose: 未找到命令 在一台新的服务器上使用 docker-compose 命令时&#xff0c;报错说 docker-compose 命令找不到&#xff0c;在网上试了一些安装方法&#xff0c;良莠不齐&#xff0c;所以在这块整理一下&#xff0c;如何正确快速的安装 docker-compose cd…

Linux 进程间通信 System V系列: 共享内存,信号量,简单介绍消息队列

进程间通信 System V系列: 共享内存,初识信号量 一.共享内存1.引入2.原理3.系统调用接口1.shmget2.shmat和shmdt3.shmctl 4.边写代码边了解共享内存的特性1.ftok形成key,shmget创建与获取共享内存2.shm相关指令3.shmat和shmdt挂接和取消挂接4.shmctl获取共享内存信息,释放共享内…

判断字符是否唯一——力扣

面试题 01.01. 判定字符是否唯一 已解答 简单 相关标签 相关企业 提示 实现一个算法&#xff0c;确定一个字符串 s 的所有字符是否全都不同。 示例 1&#xff1a; 输入: s "leetcode" 输出: false 示例 2&#xff1a; 输入: s "abc" 输出: true…

Vue项目npm install certificate has expired报错解决方法

1.Vue项目 npm install 安装依赖突然报错&#xff1a; npm ERR! code CERT_HAS_EXPIRED npm ERR! errno CERT_HAS_EXPIRED npm ERR! request to https://registry.npm.taobao.org/zrender/download/zrender-4.3.0.tgz failed, reason: certificate has expired npm ERR! A com…

Xilinx 千兆以太网TEMAC IP核简介

Xilinx 公司提供了千兆以太网MAC控制器的可参数化LogiCORET™IP解决方案&#xff0c;通过这个IPCore可以实现FPGA与外部网络物理层芯片的互连。基于Xilinx FPGA 的以太网设计&#xff0c;大大降低了工程的设计复杂度&#xff0c;缩短了开发周期&#xff0c;加快了产品的面市速度…

金南瓜EAP库使用开发

前言 最近做了 一个半导体公司的上位机开发。厂商要求要支持EAP通讯。 先了解一下EAP是什么吧&#xff1f;百度资料 EAP&#xff08; Equipment Automation Program&#xff09;设备自动化处理&#xff0c;工厂实现设备自动化生产和管理。 1. 机台状态数据收集&#xff0c;包…

网络编程——Socket——模拟用户登录

功能一&#xff1a;模拟用户登录 功能二&#xff1a;实现客户发送登录用户信息&#xff0c;服务器端显示登录信息并响应给客户端登录成功 这里设置的用户登录信息为&#xff1a;admin&#xff0c;123456 实现&#xff1a; 1.首先&#xff0c;服务端创建并启动服务器&#x…

JINGWHALE 虚拟现实物质与空间理论 —— 全息世界

JINGWHALE 对此论文相关未知以及已知概念、定理、公式、图片等内容的感悟、分析、创新、创造等拥有作品著作权。未经 JINGWHALE 授权&#xff0c;禁止转载与商业使用。 一、虚拟现实物质与空间理论 物质是由离散的奇点JING粒子&#xff0c;依据不同的维度粒度&#xff0c;通过…

SSM【Spring SpringMVC Mybatis】——Mybatis(二)

如果对一些基础理论感兴趣可以看这一期&#x1f447; SSM【Spring SpringMVC Mybatis】——Mybatis 目录 1、Mybatis中参数传递问题 1.1 单个普通参数 1.2 多个普通参数 1.3 命名参数 1.4 POJO参数 1.5 Map参数 1.6 Collection|List|Array等参数 2、Mybatis参数传递【#与…

【全开源】JAVA台球助教台球教练多端系统源码支持微信小程序+微信公众号+H5+APP

功能介绍 球厅端&#xff1a;球厅认证、教练人数、教练的位置记录、助教申请、我的项目、签到记录、我的钱包、数据统计 教练端&#xff1a;我的页面&#xff0c;数据统计、订单详情、保证金、实名认证、服务管理、紧急求助、签到功能 用户端&#xff1a;精准分类、我的助教…

第八章小程序后端开发,运用Bmob后端云

比目后端云简介 一个完整的小程序系统&#xff0c;不但需要前端的展现&#xff0c;而且需要后端服务器的支撑&#xff0c;以提供数据服务。也就是说&#xff0c;开发一个真正完整的小程序应用&#xff0c;需要前后端的相互配合。小程序与远程服务器之间通过HTTPS传输协议进行数…

Spring框架学习笔记(二):Spring IOC容器配置 Bean,分别基于XML配置bean 和 基于注解配置 bean

1 Spring 配置/管理 bean 介绍 Bean 管理包括两方面 &#xff1a;创建 bean 对象&#xff1b;给 bean 注入属性 Bean 配置方式&#xff1a;基于 xml 文件配置方式&#xff1b;基于注解方式 2 基于 XML 配置 bean 2.1 通过类型来获取 bean 方法&#xff1a;给getBean传入一…

linux fdisk 银河麒麟操作系统 v10 磁盘分区和挂载 详细教程

1查看 未加载的磁盘 fdisk -l 2 开始分区 fdisk /dev/vdb #查看分区 #新建分区和保存 3 格式化和挂载 fdisk -l mkfs.xfs /dev/vdb1 #查看uuid blkid /dev/vdb1 mkdir /data vi /etc/fstab UUID209daa-fb1c-48f2-bf5e-e63f38cb8a /data xfs defaults 0 0 #加载下 mo…

Vue 中动态与静态处理 Element UI/Element Plus 组件禁用状态样式

目录 一、静态样式修改 - 使用 ::v-deep 穿透组件样式二、选择器的优先级和匹配顺序三、动态添加样式 - 使用 Vue 实例属性&#xff08;非推荐&#xff09;四、区别总结五、应用场景总结 本文主要探讨在 Vue.js 项目中&#xff0c;特别是搭配 Element UI 或 Element Plus 组件库…

NVME协议第四章-Data Structures

本章描述NVME所需的数据结构&#xff08;讨论均基于PCIE over NVME&#xff09;。 一、Submission Queue & Completion Queue Definition host提交cmd到SQ&#xff0c;填入SQ后&#xff0c;Tail指针需要1&#xff0c;如果tail指针超过队列queue size&#xff0c;那么tai…

Springboot+Vue项目-基于Java+MySQL的车辆管理系统(附源码+演示视频+LW)

大家好&#xff01;我是程序猿老A&#xff0c;感谢您阅读本文&#xff0c;欢迎一键三连哦。 &#x1f49e;当前专栏&#xff1a;Java毕业设计 精彩专栏推荐&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb; &#x1f380; Python毕业设计 &…

TVM简介

TVM FGPA,CPU, GPU 1.什么是TVM&#xff1f; 是一个支持GPU&#xff0c;CPU&#xff0c;FPGA指令生成的开源编译器框架 2.特点 基于图和算符结构来优化指令生成&#xff0c;最大化硬件执行效率。其中使用了很多方法 来改善硬件执行速度&#xff0c;包括算符融合、数据规划…

LeetCode96:不同的二叉搜索树

题目描述 给你一个整数 n &#xff0c;求恰由 n 个节点组成且节点值从 1 到 n 互不相同的 二叉搜索树 有多少种&#xff1f;返回满足题意的二叉搜索树的种数。 代码 /*dp[i]&#xff1a;表示i个节点有dp[i]个不同的二搜索叉树递推公式&#xff1a;dp[i] dp[j-1] * dp[i-j], j…

【C语言—猜数字小游戏】

一、游戏规则 电脑自动生成一个1~100范围内的随机数&#xff0c;由玩家猜测本轮生成的随机数是什么&#xff0c;系统根据玩家猜测数据的⼤⼩给出猜⼤了或猜⼩了的反馈&#xff0c;直到玩家猜对&#xff0c;游戏结束。 如何生成随机数&#xff1a;【C语言】/*如何生成随机值*/-C…