实现复杂树结构返回(不含子树), 并且结点间建立关联

💡 一句话结:
实现传感器和深度及采集的数值动态对应,将不规则的数据转变成固定列头的一行行数据。

🔑 关键信息点:

  1. 通过传感器编号和深度将传感器对应的数值与时间建立关联。
  2. 使用SpringBoot+MyBatis框架实现动态查询不同参数条件下的传感器数据。
  3. 通过动态SQL实现各级数据项的对应查询,动态生成不同条件对应的字段。
  4. 利用HashMap和Set等数据结构处理数据并建立传感器编号和深度的对应关系。
  5. 提供两个版本的代码实现,一个使用Map,一个使用Set,实现对应关系和数据处理。

对应关系分析(掺杂业务逻辑)

如图 简明来讲就是实现传感器和深度及采集的数值动态对应
因为硬件设备的限制, 在数据采集及通过MQ到库中时数据是一个传感器对应一个深度对应一个该传感器的采集值, 我们要做的是列出所有传感器, 以时间为索引, 时间和传感器对应上一个值简单来说就是把一列列不规则的数据, 转变成固定列头的一行行数据
在这里插入图片描述
数据字段返回(简单以json格式展示)
在这里插入图片描述
:这样就对应上标题了tableData有多组, 但是每一组的数值还和日期时间树的结点能对应上, 可以变向理解为tabledata中含有子树(截图比较抽象)

实现

思路: 使用springboot+mybatis框架
mybatis可以动态接收不同参数 (传感器点位,传感器深度, 传感器类型[温湿度]等) 筛选后的传感器编号,并查询匹配传感器对应的数据
返回格式的转化以及查询后格式的完善在业务层完成
效果比对: 上面为原格式没有明显对应关系,下面为转换后的
在这里插入图片描述

Dao层(mapper)

首先是先从SQL实现各级数据项对应, 并实现使用mybatis框架动态接收符合条件的各组传感器
注意使用的resultType不是直接使用的resultMap
这儿需要使用动态SQL, 先使用传感器编号查询接口, 查询出符合条件的传感器编号, 然后动态赋值到case…when中的相关字段, 动态生成不同条件对应的case…when字段进行第二次具体数值的获取查询

  <select id="" resultType="map">selectto_char(detection_time , 'yyyy/MM/dd') as 日期,to_char(detection_time , 'hh24:mi:ss') as 时间<choose><when test="sensorCodes != null and sensorCodes.size() != 0"><foreach item="sensorCode" collection="sensorCodes" separator=""><if test=" sensorCode != null and sensorCode != ''">,max(case when sensor_code = '${sensorCode}' then humidity   end) as "${sensorCode}"</if></foreach></when><otherwise>,'没有与之对应的传感器编号'</otherwise></choose>FROM structure_internal_humidity_infoWHERE 1=1<if test="startDate != null">AND date_format(detection_time, '%Y-%m-%d %H:%i:%s') <![CDATA[ >= ]]> ${startDate}</if><if test="endDate != null">AND date_format(detection_time, '%Y-%m-%d %H:%i:%s') <![CDATA[ <= ]]> ${endDate}</if><if test="observationPointId != null">AND observation_point_id = ${observationPointId}</if><if test="sensorCodes != null and sensorCodes.size() > 0">AND sensor_code IN<foreach item="sensorCode" collection="sensorCodes" open="(" separator="," close=")">'${sensorCode}'</foreach></if>GROUP BYdetection_timeORDER BYdetection_time<!--<if test=" pageSize != null and pageSize != ''">--><!--  limit #{pageSize}--><!--</if>--><!--<if test="pageIndex != null and pageIndex != ''">--><!--  offset #{pageIndex}--><!--</if>--></select>

为什么注掉了offset分页, 因为业务代码中使用了mybatis分页, 这样更灵活的解决了分页, 但是不可避免的可能会有Map类或者ArrayList类中会有方法触发意料之外的bug, 留存注释备用! [ 稳妥起见还是不注释掉使用数据库原生的分页不容易踩坑 ]

这儿牵扯到一个子接口根据参数动态查询对应传感器sensorCode 要实现的就是根据参数动态匹配相关的传感器数据, 可以理解为根据不同的位置坐标作为查询条件, 获取不同位置的一组传感器编号

业务实现
分两个版本分别使用java的Map和Set作为主要数据结构进行数据处理hashMap返回, 逻辑图及各数据结构之间的转换后续出图传过来, 时间有限先码上
在这里插入图片描述

版本一

未使用Set

{//public CommonPage<Map<String, Object>> queryNewStandardlist(CommonQuery query) {List<SensorDeviceInfoPO> sensorCodes = new ArrayList<SensorDeviceInfoPO>();if (!(query.getMonitoringDataTypes().equals("STRUCTURE_INTERNAL_HUMIDITY_INFO"))){sensorCodes = sensorDeviceInfoMapper.querySensorCodeByDeviceid( query);}else {throw new BizException(BizCodeEnum.BIZ_FAILURE, "无效的类型");}List<String> sensorsList = new ArrayList<String>();List<BigDecimal> depthList = new ArrayList<BigDecimal>();//声明一个变量存储深度和传感器编号,key为传感器编号,value为传感器对应的深度Map<String,BigDecimal> sensorDepthMap = new HashMap<String,BigDecimal>();for (SensorDeviceInfoPO sensor : sensorCodes) {String sensorCode = sensor.getSensorCode();BigDecimal depth = sensor.getDepth();// 因为有传感器编号为空的情况所以添加非空判断去除编号为空的传感器从而对齐横列和纵列表格数据(后续 如果数据库更新后没有此情况可删除此段代码中的判断)if ( ! (sensorCode.isEmpty() || sensorCode.isBlank() ) && ! (depth == null || depth.compareTo(BigDecimal.ZERO) == 0) ){//用于结果集查询sensorsList.add(sensorCode);depthList.add(depth);//用于列头传感器和深度值匹配处理sensorDepthMap.put(sensorCode,depth);}}query.setSensorCodes(sensorsList);int total = 0;//TODO 另一种逻辑如果,没有分页条件则不查询总条数(需要优化)if (query.getPageIndex() != null || query.getPageSize() != null) {QueryBackUp backupQuery = new QueryBackUp(query.getPageIndex(), query.getPageSize()); // 使用Query类的拷贝构造函数创建备份对象query.setPageIndex(null);query.setPageSize(null);total = structureInternalHumidityInfoMapper.queryStandardColumn(query).size();query.setPageIndex(backupQuery.getPageIndex());query.setPageSize(backupQuery.getPageSize());}query.setStartDate(Optional.ofNullable(query.getStartDate()).orElse(Date.from(LocalDate.now().minusDays(2).atStartOfDay().toInstant(ZoneOffset.ofHours(8)))));query.setEndDate(Optional.ofNullable(query.getEndDate()).orElse(Date.from(LocalDate.now().atStartOfDay().toInstant(ZoneOffset.ofHours(8)))));query.setPageSize(Optional.ofNullable(query.getPageSize()).orElse(16));query.setPageIndex(Optional.ofNullable(query.getPageIndex()).orElse(1));
// Page<Map<String, String>> standardResult = PageHelper.startPage(query.getPageIndex(), query.getPageSize()).doSelectPage(() -> {
// Page<Map<String, String>> standardResult = PageHelper.startPage(query.getPageIndex(), query.getPageSize()).doSelectPage(// 	new PageRowBounds<Map<String, String>>() {// @Override// public List<Map<String, String>> doResult(List<Map<String, String>> list) {// 	return list != null ? list : Collections.emptyList();// }//        });// List<Map<String, String>> mapperResult =// Page<Map<String, String>> standardResult = PageHelper.startPage(query.getPageIndex(), query.getPageSize()).doSelectPage(() ->// 	structureInternalHumidityInfoMapper.queryStandardColumn(query));// structureInternalHumidityInfoMapper.queryStandardColumn(query);
//     if (mapperResult == null) {
//         mapperResult = Collections.emptyList();
//     }
// });List<Map<String, String>>  standardResult = structureInternalHumidityInfoMapper.queryStandardColumn(query);//表格内容用于返回// standardResult.getResult();List<String> columnSensorCode = new ArrayList<String>();List<BigDecimal> columnDepth = new ArrayList<>();List<List<String>> tableData = new ArrayList<List<String>>();if (!standardResult.isEmpty()) {/* 列头排序及传感器与深度动态匹配及传感器与深度动态匹配及传感器与深度动态匹配BEGIN *///列头处理, 以及处理列头因为数据结构差异y引发的列头汉字有时候不是排在第一个的问题Map<String, String> columnCodeMap = standardResult.get(0);//做到编号与深度匹配for (Entry<String, String> columnCode : columnCodeMap.entrySet()) {//传感器编号Object columnCodeValue = columnCode.getKey();//传感器深度String columnDepthValue = String.valueOf(sensorDepthMap.get(columnCode.getKey()));if ( ! (columnDepthValue.isEmpty() || columnDepthValue.isBlank() || columnDepthValue.equals("") || columnDepthValue == null|| columnCodeValue.equals("") || columnCodeValue == null) ){columnSensorCode.add(String.valueOf(columnCodeValue));//sensorDepthMap.put(columnCodeValue,columnDepthValue);}}columnSensorCode.sort(new Comparator<String>() {@Overridepublic int compare(String o1, String o2) {return o1.hashCode ()- o2.hashCode();}});// Check if the first element is not Chineseif (! (columnSensorCode.get(0).equals("日期"))) {// Reverse the listCollections.reverse(columnSensorCode);// Swap the first and second elementsString temp = columnSensorCode.get(0);columnSensorCode.set(0, columnSensorCode.get(1));columnSensorCode.set(1, temp);}/* 列头排序及传感器与深度动态匹配及传感器与深度动态匹配及传感器与深度动态匹配END */for (Map<String, String> rowDataMap : standardResult)  {//遍历结果中的每一行Map//rowDataMap  = standardResult.get(i);//声明一个List存储每一行Map中的目标值List<String> rowData = new ArrayList<String>();//循环遍历Map     根据列名动态  取得目标值使值与列名匹配for (Object columnName : columnSensorCode) {//todo finish   bigDecimal  处理Object rowDataValue = rowDataMap.get(columnName);//Object depthValue = rowDataMap.get()rowData.add(String.valueOf(rowDataValue));if (0 == tableData.size()){columnDepth.add(sensorDepthMap.get(columnName));}}//用于返回塞值tableData.add(rowData);}Map<String, Object> result = new HashMap<String, Object>();result.put("columnSensorCode", columnSensorCode);result.put("columnDepth", columnDepth);result.put("tableData", tableData);CommonPage<Map<String, Object>>  standardResultPage = new CommonPage<Map<String, Object>>();standardResultPage.setPageIndex(query.getPageIndex());standardResultPage.setPageSize(query.getPageSize());standardResultPage.setTotal(total);standardResultPage.setData(result);
//System.out.println("result = " + result);return standardResultPage;}else {throw new BizException(BizCodeEnum.BIZ_FAILURE, "无效的类型, 湿度结果为空");}}

版本二

使用了Set

{//public Map<String, Object> queryStandardTableList(CommonQuery query) {CommonPage<Map<String, Object>>  resultPage = new CommonPage<>();List<SensorDeviceInfoPO> sensorCodes = sensorDeviceInfoMapper.querySensorCodeByDeviceid(query);//去除List中的空字符串List<String>  sensorCodeList = new ArrayList<String>();Map<String,BigDecimal>  sensorCodeDepMap = new HashMap<>();for(SensorDeviceInfoPO sensorCode:sensorCodes) {String  sensorCodeStr = sensorCode.getSensorCode();BigDecimal  sensorCodeDep = sensorCode.getDepth();if (!(sensorCodeStr.isEmpty() && String.valueOf(sensorCodeDep).isEmpty() && sensorCodeDep == null && sensorCodeStr == null) ){sensorCodeList.add(sensorCodeStr);sensorCodeDepMap.put(sensorCodeStr,sensorCodeDep);}}// 使用临时变量保存原始的分页参数,避免影响查询结果int originPageIndex = Optional.ofNullable(query.getPageIndex()).orElse(1);int originPageSize = Optional.ofNullable(query.getPageSize()).orElse(18);// 设置传感器代码query.setSensorCodes(sensorCodeList);// 获取结果集总数int total = structureInternalTemperatureInfoMapper.queryStandardTableTempData(query).size();// 设置查询参数的默认值query.setPageIndex(originPageIndex);query.setPageSize(originPageSize);// 设置部分参数默认值query.setStartDate(Optional.ofNullable(query.getStartDate()).orElseGet(() -> Date.from(LocalDate.now().minusDays(2).atStartOfDay().toInstant(ZoneOffset.ofHours(8)))));query.setPageSize(Optional.ofNullable(query.getPageSize() ).orElse(18));query.setPageIndex(Optional.ofNullable(query.getPageIndex()).orElse(1));//查询结果集List<Map<String, String>> dynamicResults = structureInternalTemperatureInfoMapper.queryStandardTableTempData(query);//使用mybatis 分页插件// 	AtomicReference<List<Map<String, String>>> dynamicResultsMapper = null;// Page<Map<String, String>> dynamicResults = PageHelper.startPage(query.getPageIndex(), query.getPageSize()).doSelectPage( () ->{// 			dynamicResultsMapper.set(structureInternalTemperatureInfoMapper.queryStandardTableTempData(query));// });//动态处理映射属性中的动态列名List<String> columnHeaders = new ArrayList<>();List<BigDecimal> depths = new ArrayList<>();List<List<String>> tableData = new ArrayList<>();Set<String> uniqueSensorCodeSet = new HashSet<>();if (!dynamicResults.isEmpty()) {//动态列头// 拼接dynamicResults.get(0)和dynamicResults.get(1)的去重值uniqueSensorCodeSet.addAll(dynamicResults.get(0).keySet());uniqueSensorCodeSet.addAll(dynamicResults.get(1).keySet());/*列头处理begin*//* 根据传感器编码动态匹配目标值    动态结果集列头 */// 拼接dynamicResults.get(0)和get(1)中的不重复值作为列头, 将去重后的值作为sensorCodeRow的键Map<String, String> newSensorCodeRow = new HashMap<>();for (String uniqueKey : uniqueSensorCodeSet) {newSensorCodeRow.put(uniqueKey, uniqueKey);}for (Entry<String, String> entry : newSensorCodeRow.entrySet()) {String columnName = entry.getKey();// if (columnName.contains((CharSequence) dynamicResults.get(0)) || columnName.contains((CharSequence) dynamicResults.get(1))) {uniqueSensorCodeSet.add(columnName);// }}// 将不重复的列名添加到columnHeaders中columnHeaders.addAll(uniqueSensorCodeSet);//对列头进行排序从而对tableData进行排序// columnHeaders.sort(new Comparator<String>() {// 	@Override// 	public int compare(String o1, String o2) {// 		return o1.hashCode() - o2.hashCode();// 	}// });// 使用jdk11的排序三目运算符简洁写法但是有时会把汉字排到最后, 和上面两种排序方法哪种生效使用哪一个columnHeaders.sort(String::compareTo);// 解决上述汉字后排问题,使用上面这种写法有时候会导致汉字排到后面面,所以使用jdk8的排序方法针对汉字进行排序让日期排第一个时间排第二个if (! (columnHeaders.get(0).equals("日期"))) {// Reverse the listCollections.reverse(columnHeaders);// Swap the first and second elementsString temp = columnHeaders.get(0);columnHeaders.set(0, columnHeaders.get(1));columnHeaders.set(1, temp);}//根据排序后的传感器编号取得深度值的数据for (String senorCode : columnHeaders) {depths.add(sensorCodeDepMap.get(senorCode));}/*列头处理end*///表格内容for (Map<String, String> rowDataMap : dynamicResults) {List<String> rowData = new ArrayList<>();//按照列头具体数据动态抽取表格数据for (String columnName : columnHeaders) {String cellData = rowDataMap.get(columnName);if (cellData == null) {rowData.add(""); // 如果当前行数据中没有对应的列数据,添加空字符串} else {rowData.add(cellData);}}tableData.add(rowData);}Map<String , Object> result = new HashMap<>();result.put("columnHeaders", columnHeaders);result.put("depth",depths);result.put("tableData", tableData);resultPage.setPageIndex(query.getPageIndex());resultPage.setPageSize(query.getPageSize());resultPage.setTotal(total);resultPage.setData(result);//return structureInternalTemperatureInfoMapper.queryStandardTableTempData(sensorCodes,startDate,endDate,observationPointId);// return (CommonPage<List<StructureInternalTemperatureInfoPO>>)return resultPage;}else{throw new BizException(BizCodeEnum.BIZ_FAILURE,"暂无内部温度数据");}}

析: 这两段代码都处理了参数筛选传感器将传感器编号传给SQL , 接受SQL返回的数据并将列头 ( 动态的传感器编号组和传感器对应的深度 ) 数据: (传感器检测的数值 ) 包装成不同的树结点返回给接收方
上面提到的有没有用到Set的使用与否, 主要处理的就是列头传感器与传感器深度的对应这儿的列头与数据看成Excel表格中的表头和表头对应的数据

个人目前感觉Set和其他数据集混用的好一点, 浅显一点的理解就是可以少声明一个变量

End

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

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

相关文章

RSA算法加解密

RSA算法的加密过程如下&#xff1a; 选择两个大素数①p和q&#xff0c;计算它们的乘积np*q计算欧拉函数φ(n)(p-1)*(q-1)选择一个整数e&#xff0c;满足1<e<φ(n)&#xff0c;且e与φ(n)互质计算e关于φ(n)的模逆元d&#xff0c;即满足e*d mod φ(n) 1的整数d②公钥为(…

【设计模式深度剖析】【2】【结构型】【装饰器模式】| 以去咖啡馆买咖啡为例 | 以穿衣服出门类比

&#x1f448;️上一篇:代理模式 目 录 装饰器模式定义英文原话直译如何理解呢&#xff1f;4个角色类图1. 抽象构件&#xff08;Component&#xff09;角色2. 具体构件&#xff08;Concrete Component&#xff09;角色3. 装饰&#xff08;Decorator&#xff09;角色4. 具体装饰…

2024电工杯数学建模A题Matlab代码+结果表数据教学

2024电工杯A题保姆级分析完整思路代码数据教学 A题题目&#xff1a;园区微电网风光储协调优化配置 以下仅展示部分&#xff0c;完整版看文末的文章 %A_1_1_A % 清除工作区 clear;clc;close all;warning off; %读取参数%正常读取 % P_LOADxlsread(附件1&#xff1a;各园区典…

前端 CSS 经典:SVG 描边动画

1. 原理 使用 css 中的 stroke 属性&#xff0c;用来描述描边的样式&#xff0c;其中重要的属性 stroke-dasharray、stroke-dashoffset。理解了这两个属性的原理&#xff0c;才能理解描边动画实现的原理。 stroke-dasharray&#xff1a;将描边线变成虚线、其中实线和虚线部分…

小程序丨公告栏功能,自动弹出提醒

发布查询时&#xff0c;您是否遇到这样的困扰&#xff1a; 1、查询发布时间未到&#xff0c;学生进入查询主页后发现未发布任何查询&#xff0c;不断咨询原因。 2、有些重要事项需要进入查询主页就进行强提醒&#xff0c;确保人人可见&#xff0c;用户需要反馈“我知道了”才…

【openlayers系统学习】3.4波段数学计算(计算NDVI)

四、波段数学计算&#xff08;计算NDVI&#xff09; 我们已经看到了如何使用 ol/source/GeoTIFF​ 源代码来渲染真彩色和假彩色合成。我们通过将缩放的反射率值直接渲染到红色、绿色或蓝色显示通道中的一个来实现这一点。还可以对来自GeoTIFF&#xff08;或其他数据瓦片源&…

Day48 Javascript详解

Day48 Javascript详解 文章目录 Day48 Javascript详解一、什么是javascript二、javascript特点三、 Javascript的历史四、Javascript vs Java五、JS的基本数据类型六、JS基本数据类型的特殊点七、数组 一、什么是javascript JavaScript是一种高级的、解释型的编程语言&#xf…

cmake编译redis6.0源码总结

1配置clion使用cygwin模拟linux环境&#xff0c;先下载cygwin后配置 2导入源码&#xff0c;配置cmake文件 由于redis是基于Linux上的Makefile&#xff0c;所以Windows上需要配置CMakeLists.txt使用cmake工具编译运行。github上已经有人尝试编写CMakeLists.txt文件&#xff0c…

MCF-Microbial Cell Factories

文章目录 一、期刊简介二、征稿信息三、期刊表现四、投稿须知五、投稿咨询 一、期刊简介 Microbial Cell Factories 是一份开放的同行评审期刊&#xff0c;涵盖了与微生物细胞作为重组蛋白和天然产物的生产者或作为工业兴趣的生物转化的催化剂的开发、使用和研究相关的任何主题…

【学习笔记】Windows GDI绘图(四)矩阵Matrix详解

矩阵Matrix 基于矩阵在GDI绘图的重要性&#xff0c;所以想深入了学习矩阵的相关属性与方法。 先上个本文中所有的函数图例演示吧。 原型&#xff1a; namespace System.Drawing.Drawing2D;public sealed unsafe class Matrix : MarshalByRefObject, IDisposableMatrix类封装…

系统架构师-考试-基础题-错题集锦2

108.总线-全双工、半双工&#xff1a; 109.软件配置管理-产品配置&#xff1a; 产品配置&#xff1a;指一个产品在其生命周期各个阶段所产生的各种形式和各种版本的文档、计算机程序、部件及数据的集合。 注意&#xff1a;选项中的需求规格说明、设计说明等均可归属于文档。 …

Netty学习02----使用多线程优化Selector

背景前置 在单线程环境下&#xff0c;使用一个线程同时绑定多个事件&#xff1a;连接事件、读事件、写事件。不能充分发挥多核CPU的优势&#xff0c;考虑使用多个线程&#xff0c;每个线程专门负责处理不同的事件&#xff0c;如下图所示&#xff1a;一个线程专门负责连接&#…

【ARK Survival Evolved】方舟:生存进化一键使用服务器开服联机教程

1、进入控制面板 2、第一次购买服务器会安装游戏端&#xff0c;大约5分钟左右&#xff0c;如果长时间处于安装状态请联系客服 3、设置游戏端口 方舟生存进化的设置需要三个端口&#xff0c;它们用于游戏端口&#xff08;必须为首选端口&#xff09;&#xff0c;查询端口&#…

uniapp中使用mockjs模拟接口测试总结(swiper轮播图示例)

完整总结下在uni-app中如何使用Mock.js模拟接口测试&#xff0c;这在后台接口未就绪的情况下非常有用。同时也给出个首页swiper轮播图的mock接口使用。网上的文章都不太完整&#xff0c;这里总结下完整的使用示例&#xff0c;同时也支持h5和小程序平台&#xff0c;分享给需要的…

webpack5 splitChunks分割代码

首先明确webpack 自身的打包行为 当splitChunks为false时&#xff0c;此时不启用任何打包设置 可以看到&#xff0c;静态引入全都打到一个chunk里&#xff0c;动态引入会拆分出来一个chunk,这是纯webpack无配置的打包&#xff0c; webpack会给每个模块打上标记 ,如下 { m…

Python使用multiprocessing实现多进程

大家好&#xff0c;当我们工作中涉及到处理大量数据、并行计算或并发任务时&#xff0c;Python的multiprocessing模块是一个强大而实用的工具。通过它&#xff0c;我们可以轻松地利用多核处理器的优势&#xff0c;将任务分配给多个进程并同时执行&#xff0c;从而提高程序的性能…

基于transformers框架实践Bert系列3-单选题

本系列用于Bert模型实践实际场景&#xff0c;分别包括分类器、命名实体识别、选择题、文本摘要等等。&#xff08;关于Bert的结构和详细这里就不做讲解&#xff0c;但了解Bert的基本结构是做实践的基础&#xff0c;因此看本系列之前&#xff0c;最好了解一下transformers和Bert…

【JavaEE】加法计算器与用户登录实战演练

目录 综合练习加法计算器1. 准备工作2. 约定前后端交互接口3. 服务器代码 用户登录1. 准备工作2. 约定前后端交互接口3. 服务器代码4. 调整前端页面代码 综合练习 理解前后端交互过程接⼝传参, 数据返回, 以及⻚⾯展⽰ 加法计算器 需求: 输⼊两个整数, 点击"点击相加&q…

56. UE5 RPG 给敌人添加AI实现跟随玩家

在这一篇里&#xff0c;我们要实现一下敌人的AI&#xff0c;敌人也需要一系列的行为&#xff0c;比如朝向英雄攻击&#xff0c;移动&#xff0c;在满足条件时施放技能。这些敌人的行为可以通过使用UE的内置的AI系统去实现。 在UE里&#xff0c;只要是基于Character类创建的蓝图…

安卓绕过限制直接使用Android/data无需授权,支持安卓14(部分)

大家都知道&#xff0c;安卓每次更新都会给权限划分的更细、收的更紧。   早在安卓11的时候还可以直接通过授权Android/data来实现操作其他软件的目录&#xff0c;没有之前安卓11授权的图了&#xff0c;反正都长一个样&#xff0c;就直接贴新图了。   后面到了安卓12~13的…