【flutter滑动拼图验证码】

在这里插入图片描述
Java后台使用aj_captcha插件,提供/captcha/get(获取captcha底图和拼块图片)、/captcha/check(验证拼图偏移量)这两个接口。并且这个插件在GitHub上有源码。
1.先准备好aj_captcha的工具类:

import 'dart:convert';import 'package:steel_crypt/steel_crypt.dart';
//import 'package:encrypt/encrypt.dart';class EncryptUtil {///aes加密/// [key]AesCrypt加密key/// [content] 需要加密的内容字符串static String aesEncode({String key, String content}) {// var aesEncrypter = AesCrypt(key, 'ecb', 'pkcs7');var encodeKey = base64UrlEncode(utf8.encode(key));var aesEncrypter = AesCrypt(padding: PaddingAES.pkcs7, key: encodeKey);return aesEncrypter.ecb.encrypt(inp: content);}///aes解密/// [key]aes解密key/// [content] 需要加密的内容字符串static String aesDecode({String key, String content}) {// var aesEncrypter = AesCrypt(key, 'ecb', 'pkcs7');var encodeKey = base64UrlEncode(utf8.encode(key));var aesEncrypter = AesCrypt(key: encodeKey, padding: PaddingAES.pkcs7);// return aesEncrypter.decrypt(content);return aesEncrypter.ecb.decrypt(enc: content);}
}
import 'dart:convert';class ObjectUtils {/// isEmpty.static bool isEmpty(Object value) {if (value == null) return true;if (value is String && value.isEmpty) {return true;}return false;}//list length == 0  || list == nullstatic bool isListEmpty(Object value) {if (value == null) return true;if (value is List && value.length == 0) {return true;}return false;}static String jsonFormat(Map<dynamic, dynamic> map) {Map _map = Map<String, Object>.from(map);JsonEncoder encoder = JsonEncoder.withIndent('  ');return encoder.convert(_map);}
}
import 'dart:async';
import 'package:flutter/widgets.dart';
import 'object_utils.dart';/// Widget Util.
class WidgetUtil {bool _hasMeasured = false;double _width;double _height;/// Widget rendering listener./// Widget渲染监听./// context: Widget context./// isOnce: true,Continuous monitoring  false,Listen only once./// onCallBack: Widget Rect CallBack.void asyncPrepare(BuildContext context, bool isOnce, ValueChanged<Rect> onCallBack) {if (_hasMeasured) return;WidgetsBinding.instance.addPostFrameCallback((Duration timeStamp) {RenderBox box = context.findRenderObject();if (box != null && box.semanticBounds != null) {if (isOnce) _hasMeasured = true;double width = box.semanticBounds.width;double height = box.semanticBounds.height;if (_width != width || _height != height) {_width = width;_height = height;if (onCallBack != null) onCallBack(box.semanticBounds);}}});}/// Widget渲染监听.void asyncPrepares(bool isOnce, ValueChanged<Rect> onCallBack) {if (_hasMeasured) return;WidgetsBinding.instance.addPostFrameCallback((Duration timeStamp) {if (isOnce) _hasMeasured = true;if (onCallBack != null) onCallBack(null);});}///get Widget Bounds (width, height, left, top, right, bottom and so on).Widgets must be rendered completely.///获取widget Rectstatic Rect getWidgetBounds(BuildContext context) {RenderBox box = context.findRenderObject();return (box != null && box.semanticBounds != null)? box.semanticBounds: Rect.zero;}///Get the coordinates of the widget on the screen.Widgets must be rendered completely.///获取widget在屏幕上的坐标,widget必须渲染完成static Offset getWidgetLocalToGlobal(BuildContext context) {RenderBox box = context.findRenderObject();return box == null ? Offset.zero : box.localToGlobal(Offset.zero);}/// get image width height,load error return Rect.zero.(unit px)/// 获取图片宽高,加载错误情况返回 Rect.zero.(单位 px)/// image/// url network/// local url , packagestatic Future<Rect> getImageWH({Image image, String url, String localUrl, String package}) {if (ObjectUtils.isEmpty(image) &&ObjectUtils.isEmpty(url) &&ObjectUtils.isEmpty(localUrl)) {return Future.value(Rect.zero);}Completer<Rect> completer = Completer<Rect>();Image img = image != null? image: ((url != null && url.isNotEmpty)? Image.network(url): Image.asset(localUrl, package: package));img.image.resolve(new ImageConfiguration()).addListener(new ImageStreamListener((ImageInfo info, bool _) {completer.complete(Rect.fromLTWH(0, 0, info.image.width.toDouble(),info.image.height.toDouble()));},onError: (dynamic exception, StackTrace stackTrace) {completer.completeError(exception, stackTrace);},));return completer.future;}/// get image width height, load error throw exception.(unit px)/// 获取图片宽高,加载错误会抛出异常.(单位 px)/// image/// url network/// local url (full path/全路径,example:"assets/images/ali_connors.png",""assets/images/3.0x/ali_connors.png"" );/// packagestatic Future<Rect> getImageWHE({Image image, String url, String localUrl, String package}) {if (ObjectUtils.isEmpty(image) &&ObjectUtils.isEmpty(url) &&ObjectUtils.isEmpty(localUrl)) {return Future.error("image is null.");}Completer<Rect> completer = Completer<Rect>();Image img = image != null? image: ((url != null && url.isNotEmpty)? Image.network(url): Image.asset(localUrl, package: package));img.image.resolve(new ImageConfiguration()).addListener(new ImageStreamListener((ImageInfo info, bool _) {completer.complete(Rect.fromLTWH(0, 0, info.image.width.toDouble(),info.image.height.toDouble()));},onError: (dynamic exception, StackTrace stackTrace) {completer.completeError(exception, stackTrace);},));return completer.future;}
}

2.绘制验证弹窗

import 'dart:convert';
import 'package:test/constant.dart';
import 'package:test/generated/l10n.dart';
import 'package:test/http/DioManager.dart';
import 'package:tset/util/easy_loading_util.dart';
import 'package:test/util/encrypt_util.dart';
import 'package:test/util/object_utils.dart';
import 'package:test/util/widtet_util.dart';
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';typedef VoidSuccessCallback = dynamic Function(String c);class CaptchaPage extends StatefulWidget {final VoidSuccessCallback onSuccess; //拖放完成后验证成功回调final VoidCallback onFail; //拖放完成后验证失败回调CaptchaPage({this.onSuccess, this.onFail});_CaptchaPageState createState() => _CaptchaPageState();
}class _CaptchaPageState extends State<CaptchaPage>with TickerProviderStateMixin {/// 是否启用bool enable = true;//  String baseImageBase64 =
//      "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";String baseImageBase64 = "";String slideImageBase64 = "";String captchaToken = "";String secretKey = "";Size baseSize = Size.zero; //底部基类图片Size slideSize = Size.zero; //滑块图片var sliderColor = Colors.white; //滑块的背景色var sliderIcon = Icons.arrow_forward; //滑块的图标var movedXBorderColor = Colors.white; //滑块拖动时,左边已滑的区域边框颜色double sliderStartX = 0; //滑块未拖前的X坐标double sliderXMoved = 0;bool sliderMoveFinish = false; //滑块拖动结束bool checkResultAfterDrag = false; //拖动后的校验结果//-------------动画------------int _checkMilliseconds = 0; //滑动时间bool _showTimeLine = false; //是否显示动画部件bool _checkSuccess = false; //校验是否成功AnimationController controller;var _ratio = 3.0;var dialogWidth;GlobalKey _baseImageKey = new GlobalKey();//高度动画Animation<double> offsetAnimation;//------------动画------------//校验通过void checkSuccess(String content) {setState(() {checkResultAfterDrag = true;_checkSuccess = true;_showTimeLine = true;});_forwardAnimation();updateSliderColorIcon();//刷新验证码Future.delayed(Duration(milliseconds: 1000)).then((v) {_reverseAnimation().then((v) {setState(() {_showTimeLine = false;});//回调if (widget.onSuccess != null) {widget.onSuccess(content);// NavigatorUtil.pop(value: true);}Navigator.pop(context);});});}//校验失败void checkFail() {setState(() {_showTimeLine = true;_checkSuccess = false;checkResultAfterDrag = false;});_forwardAnimation();updateSliderColorIcon();//刷新验证码Future.delayed(Duration(milliseconds: 1000)).then((v) {_reverseAnimation().then((v) {setState(() {_showTimeLine = false;});loadCaptcha();//回调if (widget.onFail != null) {widget.onFail();}});});}//重设滑动颜色与图标void updateSliderColorIcon() {var _sliderColor = null; //滑块的背景色var _sliderIcon = null; //滑块的图标var _movedXBorderColor = null; //滑块拖动时,左边已滑的区域边框颜色//滑块的背景色if (sliderMoveFinish) {//拖动结束_sliderColor = checkResultAfterDrag ? Colors.green : Colors.red;_sliderIcon = checkResultAfterDrag ? Icons.check : Icons.close;_movedXBorderColor = checkResultAfterDrag ? Colors.green : Colors.red;} else {//拖动未开始或正在拖动中_sliderColor = sliderXMoved > 0 ? Color(0xffe63850) : Colors.white;_sliderIcon = Icons.arrow_forward;_movedXBorderColor = Color(0xff89F2D0);}sliderColor = _sliderColor;sliderIcon = _sliderIcon;movedXBorderColor = _movedXBorderColor;setState(() {});}//加载验证码void loadCaptcha() {setState(() {_showTimeLine = false;sliderMoveFinish = false;checkResultAfterDrag = false;sliderColor = Colors.white; //滑块的背景色sliderIcon = Icons.arrow_forward; //滑块的图标movedXBorderColor = Colors.white; //滑块拖动时,左边已滑的区域边框颜色});DioManager.getInstance().post(Constant.baseUrl + '/captcha/get', {"captchaType": "blockPuzzle"},(res) async {if (res['repCode'] != '0000' || res['repData'] == null) {setState(() {secretKey = "";});if (res['repCode'] == '6202') {enable = false;esLoadingError('您失败的次数太多啦,请稍后试试吧!');}return;}Map<String, dynamic> repData = res['repData'];print("--------------");print(repData.keys);print("${repData["point"]}");sliderXMoved = 0;sliderStartX = 0;captchaToken = '';checkResultAfterDrag = false;baseImageBase64 = repData["originalImageBase64"];baseImageBase64 = baseImageBase64.replaceAll('\n', '');secretKey = repData['secretKey'] ?? "";slideImageBase64 = repData["jigsawImageBase64"];slideImageBase64 = slideImageBase64.replaceAll('\n', '');captchaToken = repData["token"];print(baseImageBase64);var baseR = await WidgetUtil.getImageWH(image: Image.memory(Base64Decoder().convert(baseImageBase64)));baseSize = baseR.size;var silderR = await WidgetUtil.getImageWH(image: Image.memory(Base64Decoder().convert(slideImageBase64)));slideSize = silderR.size;enable = true;setState(() {});}, (error) {print(error);});}//校验验证码void checkCaptcha(sliderXMoved, captchaToken, {BuildContext myContext}) {setState(() {sliderMoveFinish = true;});//滑动结束,改变滑块的图标及颜色
//    updateSliderColorIcon();//pointJson参数需要aes加密//    MediaQueryData mediaQuery = MediaQuery.of(myContext);/** ScreenUtil().setHeight(20)*/print('sliderXMoved= $sliderXMoved');print('_baseImageKeyWidth ${_baseImageKey.currentContext.size.width}');print('_baseImageKeyWidthRatio= ${_baseImageKey.currentContext.size.width / baseSize.width}');//由于不同屏幕分辨率或者屏幕设置放大后拖动从最右侧拖动到同一位置的偏移量是不同的(屏幕),根据底图在屏幕上的实际宽度和从接口获取的底图的宽度(baseSize.width)的百分比来计算接口偏移量参数var pointMap = {"x": sliderXMoved / (_baseImageKey.currentContext.size.width / baseSize.width), "y": 5};//x:拖动的偏移量  y:偏移量误差范围 var pointStr = json.encode(pointMap);var cryptedStr = pointStr;/// secretKey 不为空,进行as加密if (!ObjectUtils.isEmpty(secretKey)) {// var aesEncrypter = AesCrypt(secretKey, 'ecb', 'pkcs7');cryptedStr = EncryptUtil.aesEncode(key: secretKey, content: pointStr);var dcrypt = EncryptUtil.aesDecode(key: secretKey, content: cryptedStr);// Map _map = json.decode(dcrypt);}// print("dcrypt ---- ${_map}");DioManager.getInstance().post(Constant.baseUrl + '/captcha/check', {"pointJson": cryptedStr,"captchaType": "blockPuzzle","token": captchaToken}, (res) {if (res['repCode'] != '0000' || res['repData'] == null) {checkFail();return;}Map<String, dynamic> repData = res['repData'];if (repData["result"] != null && repData["result"] == true) {// 如果不加密 将 token 和 坐标序列化 通过 --- 链接成字符串var captchaVerification = '$captchaToken---$pointStr';if (!ObjectUtils.isEmpty(secretKey)) {// 如果加密 将 token 和 坐标序列化通过 --- 链接成字符串进行加密 加密秘钥为 _clickWordCaptchaModel.secretKeycaptchaVerification = EncryptUtil.aesEncode(key: secretKey, content: captchaVerification);}checkSuccess(captchaVerification);} else {checkFail();}}, (error) {loadCaptcha();print(error);});}void initState() {super.initState();initAnimation();loadCaptcha();}void dispose() {controller.dispose();super.dispose();}// 初始化动画void initAnimation() {controller =AnimationController(duration: Duration(milliseconds: 500), vsync: this);offsetAnimation = Tween<double>(begin: 0.5, end: 0).animate(CurvedAnimation(parent: controller, curve: Curves.ease))..addListener(() {this.setState(() {});});}// 反向执行动画_reverseAnimation() async {await controller.reverse();}// 正向执行动画_forwardAnimation() async {await controller.forward();}void didUpdateWidget(CaptchaPage oldWidget) {// TODO: implement didUpdateWidgetsuper.didUpdateWidget(oldWidget);}Widget build(BuildContext context) {dialogWidth = 0.9 * MediaQuery.of(context).size.width;_ratio = MediaQuery.of(context).devicePixelRatio;return Scaffold(backgroundColor: Colors.transparent,body: MediaQuery(data: MediaQueryData(devicePixelRatio: _ratio),child: Center(child: UnconstrainedBox(child: Container(width: dialogWidth,color: Colors.white,child: Stack(children: <Widget>[Column(mainAxisAlignment: MainAxisAlignment.start,crossAxisAlignment: CrossAxisAlignment.center,children: <Widget>[//顶部,提示+关闭Container(height: 50,padding: EdgeInsets.fromLTRB(10, 0, 10, 0),decoration: BoxDecoration(border: Border(bottom:BorderSide(width: 1, color: Color(0xffe5e5e5))),),child: Row(mainAxisAlignment: MainAxisAlignment.spaceBetween,children: <Widget>[Expanded(child: Text(S.current.qingwanchenganquanyanzheng,maxLines: 1,overflow: TextOverflow.ellipsis,style: TextStyle(fontSize: 18),textScaleFactor: 1.0,)),IconButton(padding: EdgeInsets.all(3),icon: Icon(Icons.refresh),iconSize: 30,color: Colors.black54,onPressed: () {//刷新loadCaptcha();}),IconButton(padding: EdgeInsets.all(3),icon: Icon(Icons.highlight_off),iconSize: 30,color: Colors.black54,onPressed: () {//退出Navigator.pop(context);}),],),),//显示验证码Container(margin: EdgeInsets.all(10),child: Stack(children: <Widget>[//底图 310*155baseImageBase64.length > 0? Image.memory(Base64Decoder().convert(baseImageBase64),fit: BoxFit.fitWidth,key: _baseImageKey,height: 310.w,gaplessPlayback: true,): Container(width: dialogWidth - 20,height: 310.w,alignment: Alignment.center,child: CircularProgressIndicator(),),//滑块图slideImageBase64.length > 0? Container(margin: EdgeInsets.fromLTRB(sliderXMoved, 0, 0, 0),child: Image.memory(Base64Decoder().convert(slideImageBase64),height: 310.w,fit: BoxFit.fitHeight,gaplessPlayback: true,),): Container(),Positioned(bottom: 0,left: -10,right: -10,child: Offstage(offstage: !_showTimeLine,child: FractionalTranslation(translation: Offset(0, offsetAnimation.value),child: Container(margin: EdgeInsets.only(left: 10, right: 10),padding: EdgeInsets.only(left: 10),height: 40,color: _checkSuccess? Color(0x7F66BB6A): Color.fromRGBO(200, 100, 100, 0.4),alignment: Alignment.centerLeft,child: Text(_checkSuccess? "${(_checkMilliseconds / (60.0 * 12)).toStringAsFixed(2)}${S.current.yanzhengchenggong}": S.current.yanzhengshibai,style: TextStyle(color: Colors.white),),),),)),Positioned(bottom: -20,left: 0,right: 0,child: Offstage(offstage: !_showTimeLine,child: Container(margin: EdgeInsets.only(left: 10, right: 10),height: 20,color: Colors.white,),))],),),//底部,滑动区域baseSize.width > 0? Container(margin: EdgeInsets.all(10),height: slideSize.width * (dialogWidth - 20) / baseSize.width,width: baseSize.width * 2.w,child: Stack(alignment: AlignmentDirectional.centerStart,children: <Widget>[Container(height: slideSize.width * (dialogWidth - 20) / baseSize.width,decoration: BoxDecoration(border: Border.all(width: 1,color: Color(0xffe5e5e5),),color: Color(0xffe1e1e1),),),Container(alignment: Alignment.center,child: Text(S.current.xiangyouhuadong,style: TextStyle(fontSize: 16),textScaleFactor: 1.0,),),Container(width: sliderXMoved,height: 58,decoration: BoxDecoration(border: Border.all(width: sliderXMoved > 0 ? 1 : 0,color: movedXBorderColor,),color: Color(0xff89F2D0),),),GestureDetector(onPanStart: (startDetails) {if (!enable) return;_checkMilliseconds =new DateTime.now().millisecondsSinceEpoch;print("startDetails");print(startDetails.globalPosition);sliderStartX = startDetails.globalPosition.dx;print("startDetails --- sliderStartX ${sliderStartX} ");},onPanUpdate: (updateDetails) {if (!enable) return;print("updateDetails");print(updateDetails.globalPosition);double offset =updateDetails.globalPosition.dx  - sliderStartX;double _w1 = baseSize.width * 2.w - 100.w;if (offset < 0) {offset = 0;} else if ((offset > _w1)) {offset = _w1;}setState(() {sliderXMoved = offset;});//滑动过程,改变滑块左边框颜色updateSliderColorIcon();},onPanEnd: (endDetails) {if (!enable) return;checkCaptcha(sliderXMoved, captchaToken);int _nowTime =new DateTime.now().millisecondsSinceEpoch;_checkMilliseconds =_nowTime - _checkMilliseconds;//滑动结束},child: Container(width: slideSize.width * (dialogWidth - 20) / baseSize.width,height: slideSize.width * (dialogWidth - 20) / baseSize.width,margin: EdgeInsets.fromLTRB(sliderXMoved > 0 ? sliderXMoved : 1,0,0,0),decoration: BoxDecoration(border: Border(top: BorderSide(width: 1,color: Color(0xffe5e5e5),),right: BorderSide(width: 1,color: Color(0xffe5e5e5),),bottom: BorderSide(width: 1,color: Color(0xffe5e5e5),),),color: sliderColor,),child: IconButton(icon: Icon(sliderIcon),iconSize: 20,color: Colors.black54,),),)],)): Container(),],),],),),)),));}
}

3.使用:

_sendPhoneCode(setBottomSheetState) {showDialog<Null>(context: context,barrierDismissible: true,builder: (BuildContext context) {return CaptchaPage(onSuccess: (value) async {Response response = await _dio.post(LoginApi.SEND_MESSAGE_URL,data: {'ic': '+$areaCode','phone': phoneController.text,'captchaVerification': value});String dataStr = json.encode(response.data);Map<String, dynamic> dataMap = json.decode(dataStr);if (dataMap != null && dataMap['code'] == 200) {if (mounted) {setBottomSheetState(() {isButtonEnable = false; //按钮状态标记});}timer = new Timer.periodic(Duration(seconds: 1), (Timer timer) {if (mounted) {setBottomSheetState(() {count--;if (count == 0) {timer.cancel(); //倒计时结束取消定时器isButtonEnable = true; //按钮可点击count = 60; //重置时间buttonText = S.current.fasongyanzhengma; //重置按钮文本} else {buttonText = '${count}S'; //更新文本内容}});}});esLoadingToast(S.current.fasongchenggong);} else {esLoadingError(S.current.fasongshibai);}},onFail: () {// esLoadingError('人机校验失败');},);},);}

滑块拼图验证码

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【Spring/Java项目】如何利用swagger-parser解析yaml中的api请求类型、注释、接口名等等(含示例代码)

手打不易&#xff0c;如果转摘&#xff0c;请注明出处&#xff01; 注明原文&#xff1a;https://zhangxiaofan.blog.csdn.net/article/details/129167371 目录 前言 官方文档 项目配置 示例代码 测试文件 解析代码 运行结果 前言 用到这个工具是因为项目需要&#xff0…

Java9集合类新增功能

前言 Java8及Java9在集合Collection类中新增了一些很好用的新方法&#xff0c;能方便程序员更便捷的处理集合数据&#xff0c;本文对其中的一些方法进行总结 一. List 1.创建 // 传统方法List<String> list1 new ArrayList<>();list1.add("item1");li…

FPGA实验一:层次法设计组合电路(加法器)

目录 一、实验目的 二、实验要求 三、实验代码 四、实验结果及分析 1、引脚锁定 2、仿真波形及分析 3、下载测试结果及分析 五、实验心得 一、实验目的 &#xff08;1&#xff09;掌握基本组合逻辑电路的 FPGA实现&#xff1b; &#xff08;2&#xff09;学习 Verilo…

烂sql导致clickhouse集群memory_tracking直线飙升触发熔断

版 本 v e r s i o n 1 9 . 1 7 . 4 . 1 1 c l i c k h o u s e 集 群 &#xff0c; 主 要 存 日 志 数 据 与 监 控 数 据 。 架 构 为 4 台 主 机 1 2 个 实 例 数 &#xff0c; 数 据 为 单 副 本 。 近 日 &#xff0c; 该 c l i c k h o u s e 集 群 有 一 台 物…

Leetcode 数据库刷题记录

https://leetcode-cn.com/problemset/database/ 题目都是leetcode 上的可以点击题目会有相应的链接 每道题后面都应相应的难度等级&#xff0c;如果没时间做的话 可以在leetcode 按出题频率刷题&#xff0c;答案仅供参考 175. 组合两个表 难度简单 SQL架构 表1: Person ---…

深入理解什么是端口(port)

每当看到有人的简历上写着熟悉 TCP/IP, HTTP 等协议时, 我就忍不住问问他们: 你给我说说, 端口是啥吧! 可惜, 很少有人能说得让人满意… 所以这次就来谈谈 端口(port) , 这个熟悉的陌生人. 在此过程中, 还会谈谈 间接层, naming service 等概念, IoC, 依赖倒置 等原则以及 TCP …

服务器配置静态IP

服务器配置静态IP 一、前期准备二、配置静态IP 将服务器配置为使用静态IP地址。这将使服务器拥有一个永久的IP地址&#xff0c;而不会在每次启动时更改。为此&#xff0c;您需要编辑网络配置文件并将服务器的IP地址添加到其中。详细步骤如下&#xff1a; 一、前期准备 请在配置…

一篇搞懂socket、websocket、http协议及其使用

socket 介绍socket之前先看小编的这篇文章报文、报文段、数据包、帧、比特、字符、字节&#xff0c;与编码 在网络传输中数据都是经过多层封装的&#xff0c;在协议簇中最低层次为传输层才可以传输数据。再往底层就是面向计算机硬件和网络的部分了。例如常使用的ping baidu.co…

查看windows上的dll内容

1、安装Visual Studio时选择c桌面开发和通用Windows平台开发 2、cmd运行在Visual Studio安装路径下的VC\Auxiliary\Build\vcvars64.bat 3、在这个窗口中运行dumpbin

【JVM内存模型】—— 每天一点小知识

&#x1f4a7; J V M 内存模型 \color{#FF1493}{JVM内存模型} JVM内存模型&#x1f4a7; &#x1f337; 仰望天空&#xff0c;妳我亦是行人.✨ &#x1f984; 个人主页——微风撞见云的博客&#x1f390; &#x1f433; 《数据结构与算法》专栏的文章图文并茂&#x…

如何修改Jupyter Notebook的默认目录和默认浏览器

一、修改默认目录 Jupyter Notebook的文件默认保存目录是C:\Users\Administrator&#xff0c;默认目录可在黑窗口中查看&#xff0c;如下图所示&#xff1a; 为了方便文档的管理&#xff0c;可将默认目录修改成自己想保存的地方。修改方法如下&#xff1a; 1、找到config文件 …

迅为RK3568开发板Buildroot 系统自启动 QT 程序

本小节将讲解如何开机自启动 QT 程序。 在设置自启动 QT 程序之前&#xff0c;首先要编译好 QT 可执行程序&#xff0c;完成以下两步。 1、 已经根据 03_【北京迅为】itop-3568 开发板快速使用编译环境 ubuntu18.04 v1.0.doc 第 10 章节进行了 QT 程序的交叉编译 2、 将交叉…