当前位置: 首页 > news >正文

SAF文件选择、谷歌PhotoPicker图片视频选择与真实路径转换

一、构建选择文件与回调方法

//文件选择回调ActivityResultLauncher<String[]> pickFile = registerForActivityResult(new ActivityResultContracts.OpenDocument(), uri->{if (uri != null) {Log.e("cxy", "返回的uri:" + uri);Log.e("cxy","Path是:"+ FileUtil.getFilePathByUri(context,uri));}});//启动选择
pickFile.launch(new String[]{"*/*"})
//photopicker选择器回调(多选同理)ActivityResultLauncher<PickVisualMediaRequest> pickMedia =registerForActivityResult(new ActivityResultContracts.PickVisualMedia(), uri -> {if (uri != null) {Log.e("cxy", "返回的uri:" + uri);Log.e("cxy","Path是:"+ FileUtil.getFilePathByUri(context,uri));} else {}});//photopicker选择器启动选择,选择模式:ImageOnly、VideoOnly、ImageAndVideo
pickMedia.launch(new PickVisualMediaRequest.Builder().setMediaType(isImg ? ActivityResultContracts.PickVisualMedia.ImageOnly.INSTANCE : ActivityResultContracts.PickVisualMedia.VideoOnly.INSTANCE).build()));

二、uri转真实路径工具类

备注:

1、针对Download目录先判空,为空则复制文件至内部路径再返回

2、SAF选择文件时,存储设备选择返回的uri路径为:

content://com.android.externalstorage.documents/document/primary%3ADocuments%2Fenvironment.pub

通过选择侧边栏中的图片、视频、音频、文档等分类进入选择文件目录时,返回的路径为:

content://com.android.providers.media.documents/document/image%3A1000010017

content://com.android.providers.media.documents/document/video%3A1000008421

content://com.android.providers.media.documents/document/audio%3A1000000212

content://com.android.providers.media.documents/document/document%3A1000009571

这时如果没有授予存储权限,则走复制方法返回路径,Android13以上可按需申请图片、视频、音频读取权限,但是针对doc、pdf等其它类型文件无法返回真实路径,除非授予管理所有文件权限

<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />

权限:使用photopicker时,需要Android13以上按需申请以下使用的权限,否则MediaStore不返回真实路径,无权限情况下走复制方法返回路径

    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"android:maxSdkVersion="32" /><uses-permission android:name="android.permission.READ_MEDIA_IMAGES"/><uses-permission android:name="android.permission.READ_MEDIA_VIDEO"/><uses-permission android:name="android.permission.READ_MEDIA_AUDIO"/>//Activity中权限申请与回调ActivityResultLauncher<String[]> permissionLauncher = registerForActivityResult(new ActivityResultContracts.RequestMultiplePermissions(), map->{//处理权限结果{android.permission.READ_MEDIA_IMAGES=true, android.permission.READ_MEDIA_VIDEO=true}});if (!PmUtil.hasStoragePm(this)){PmUtil.requestStoragePm(permissionLauncher);}//权限申请工具类
import android.Manifest;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Build;import androidx.activity.result.ActivityResultLauncher;
import androidx.core.content.ContextCompat;public class PmUtil {public static boolean hasStoragePm(Context context) {if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {return ContextCompat.checkSelfPermission(context, Manifest.permission.READ_MEDIA_IMAGES) == PackageManager.PERMISSION_GRANTED && ContextCompat.checkSelfPermission(context, Manifest.permission.READ_MEDIA_VIDEO) == PackageManager.PERMISSION_GRANTED;} else {return ContextCompat.checkSelfPermission(context, Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED;}}public static void requestStoragePm(ActivityResultLauncher<String[]> launcher) {if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {launcher.launch(new String[]{Manifest.permission.READ_MEDIA_IMAGES, Manifest.permission.READ_MEDIA_VIDEO});} else {launcher.launch(new String[]{Manifest.permission.READ_EXTERNAL_STORAGE});}}
}

import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.os.Build;
import android.os.Environment;
import android.provider.DocumentsContract;
import android.provider.MediaStore;
import android.provider.OpenableColumns;
import android.util.Log;import androidx.loader.content.CursorLoader;import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;public class FileUtil {public static String getFilePathByUri(Context context, Uri uri) {// 以 file:// 开头的if (ContentResolver.SCHEME_FILE.equals(uri.getScheme())) {return uri.getPath();}// 以/storage开头的也直接返回if (isOtherDocument(uri)) {return uri.getPath();}// 版本兼容的获取!String path;if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {path = getFilePathByUri_BELOWAPI11(context, uri);if (path != null) {Log.e("cxy", "getFilePathByUri_BELOWAPI11获取到的路径为:" + path);return path;}}if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB && Build.VERSION.SDK_INT <= Build.VERSION_CODES.JELLY_BEAN_MR2) {path = getFilePathByUri_API11to18(context, uri);if (path != null) {Log.e("cxy", "getFilePathByUri_API11to18获取到的路径为:" + path);return path;}}path = getFilePathByUri_API19(context, uri);Log.e("cxy", "getFilePathByUri_API19获取到的路径为:" + path);return path;}private static String getFilePathByUri_BELOWAPI11(Context context, Uri uri) {// 以 content:// 开头的,比如 content://media/extenral/images/media/17766if (ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())) {String path = null;String[] projection = new String[]{MediaStore.Images.Media.DATA};Cursor cursor = context.getContentResolver().query(uri, projection, null, null, null);if (cursor != null) {if (cursor.moveToFirst()) {int columnIndex = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);if (columnIndex > -1) {path = cursor.getString(columnIndex);}}cursor.close();}return path;}return null;}private static String getFilePathByUri_API11to18(Context context, Uri contentUri) {String[] projection = {MediaStore.Images.Media.DATA};String result = null;CursorLoader cursorLoader = new CursorLoader(context, contentUri, projection, null, null, null);Cursor cursor = cursorLoader.loadInBackground();if (cursor != null) {int column_index = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);cursor.moveToFirst();result = cursor.getString(column_index);cursor.close();}return result;}private static String getFilePathByUri_API19(Context context, Uri uri) {// 4.4及之后的 是以 content:// 开头的,比如 content://com.android.providers.media.documents/document/image%3A235700if (ContentResolver.SCHEME_CONTENT.equals(uri.getScheme()) && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {if (DocumentsContract.isDocumentUri(context, uri)) {if (isExternalStorageDocument(uri)) {// ExternalStorageProviderString docId = DocumentsContract.getDocumentId(uri);String[] split = docId.split(":");String type = split[0];if ("primary".equalsIgnoreCase(type)) {if (split.length > 1) {return Environment.getExternalStorageDirectory() + "/" + split[1];} else {return Environment.getExternalStorageDirectory() + "/";}// This is for checking SD Card}} else if (isDownloadsDocument(uri)) {//下载内容提供者时应当判断下载管理器是否被禁用int stateCode = context.getPackageManager().getApplicationEnabledSetting("com.android.providers.downloads");if (stateCode != 0 && stateCode != 1) {return null;}String id = DocumentsContract.getDocumentId(uri);// 如果出现这个RAW地址,我们则可以直接返回!if (id.startsWith("raw:")) {return id.replaceFirst("raw:", "");}if (id.contains(":")) {String[] tmp = id.split(":");if (tmp.length > 1) {id = tmp[1];}}Uri contentUri = Uri.parse("content://downloads/public_downloads");Log.e("cxy", "测试打印Uri: " + uri);try {contentUri = ContentUris.withAppendedId(contentUri, Long.parseLong(id));} catch (Exception e) {e.printStackTrace();}String path = getDataColumn(context, contentUri, null, null);if (path != null) return path;// 兼容某些特殊情况下的文件管理器!String fileName = getFileNameByUri(context, uri);if (fileName != null) {path = Environment.getExternalStorageDirectory().toString() + "/Download/" + fileName;File pathFile = new File(path);if (!pathFile.exists()) {path = getFilePathForCopy(context, uri);}return path;}} else if (isMediaDocument(uri)) {// MediaProviderString docId = DocumentsContract.getDocumentId(uri);String[] split = docId.split(":");String type = split[0];Uri contentUri;if ("image".equals(type)) {contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;} else if ("video".equals(type)) {contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;} else if ("audio".equals(type)) {contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;} else {contentUri = MediaStore.Files.getContentUri("external");}String selection = "_id=?";String[] selectionArgs = new String[]{split[1]};String resultPath = getDataColumn(context, contentUri, selection, selectionArgs);if (resultPath == null) {resultPath = getFilePathForCopy(context, uri);}return resultPath;}} else {//content://media/picker/0/com.android.providers.media.photopicker/media/1000008424//photopicker情况,有权限则返回真实路径,无权限则返回复制后的内部路径,但复制后的名称是IDString uriPath = uri.getPath();String id = "";if (uriPath != null) {String[] strings = uriPath.split("/");id = strings[strings.length - 1];}String selection = "_id=?";String[] selectionArgs = new String[]{id};Uri contentUri = MediaStore.Files.getContentUri("external");String resultPath = getDataColumn(context, contentUri, selection, selectionArgs);if (resultPath == null) {resultPath = getFilePathForCopy(context, uri);}return resultPath;}}return null;}private static String getFileNameByUri(Context context, Uri uri) {String relativePath = getFileRelativePathByUri_API18(context, uri);if (relativePath == null) relativePath = "";final String[] projection = {MediaStore.MediaColumns.DISPLAY_NAME};try (Cursor cursor = context.getContentResolver().query(uri, projection, null, null, null)) {if (cursor != null && cursor.moveToFirst()) {int index = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DISPLAY_NAME);String path = relativePath + cursor.getString(index);cursor.close();return path;}}return null;}private static String getFileRelativePathByUri_API18(Context context, Uri uri) {final String[] projection;if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.Q) {projection = new String[]{MediaStore.MediaColumns.RELATIVE_PATH};try (Cursor cursor = context.getContentResolver().query(uri, projection, null, null, null)) {if (cursor != null && cursor.moveToFirst()) {int index = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.RELATIVE_PATH);String path = cursor.getString(index);cursor.close();return path;}}}return null;}private static String getFilePathForCopy(Context context, Uri uri) {try {Cursor returnCursor = context.getContentResolver().query(uri, null, null, null, null);int nameIndex = returnCursor.getColumnIndex(OpenableColumns.DISPLAY_NAME);returnCursor.moveToFirst();String name = (returnCursor.getString(nameIndex));File file = new File(context.getCacheDir(), name);InputStream inputStream = context.getContentResolver().openInputStream(uri);FileOutputStream outputStream = new FileOutputStream(file);int read = 0;int maxBufferSize = 1024 * 1024;int bytesAvailable = inputStream.available();int bufferSize = Math.min(bytesAvailable, maxBufferSize);final byte[] buffers = new byte[bufferSize];while ((read = inputStream.read(buffers)) != -1) {outputStream.write(buffers, 0, read);}returnCursor.close();inputStream.close();outputStream.close();return file.getPath();} catch (Exception e) {e.printStackTrace();}return null;}private static String getDataColumn(Context context, Uri uri, String selection, String[] selectionArgs) {final String column = MediaStore.Images.Media.DATA;final String[] projection = {column};try (Cursor cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs, null)) {if (cursor != null && cursor.moveToFirst()) {final int column_index = cursor.getColumnIndexOrThrow(column);String path = cursor.getString(column_index);cursor.close();return path;}} catch (IllegalArgumentException iae) {iae.printStackTrace();}return null;}private static boolean isExternalStorageDocument(Uri uri) {return "com.android.externalstorage.documents".equals(uri.getAuthority());}private static boolean isOtherDocument(Uri uri) {// 以/storage开头的也直接返回if (uri != null && uri.getPath() != null) {String path = uri.getPath();if (path.startsWith("/storage")) {return true;}if (path.startsWith("/external_files")) {return true;}}return false;}private static boolean isDownloadsDocument(Uri uri) {return "com.android.providers.downloads.documents".equals(uri.getAuthority());}private static boolean isMediaDocument(Uri uri) {return "com.android.providers.media.documents".equals(uri.getAuthority());}//如果是复制的文件,完成后清除缓存public static void deleteCacheFile(Context context, String path) {if (path == null) {return;}File file = new File(path);if (file.exists() && context.getCacheDir().equals(file.getParentFile())) {Log.e("cxy", "删除成功?" + file.delete());}}
}

相关文章:

SAF文件选择、谷歌PhotoPicker图片视频选择与真实路径转换

一、构建选择文件与回调方法 //文件选择回调ActivityResultLauncher<String[]> pickFile registerForActivityResult(new ActivityResultContracts.OpenDocument(), uri->{if (uri ! null) {Log.e("cxy", "返回的uri:" uri);Log.e("cxy&q…...

java可变参数

前言 我们虽然能够用重载实现&#xff0c;但多个参数无法弹性匹配 代码 class mycalculator{//下面的四个calculate方法构成了重载//计算2个数的和&#xff0c;3个数的和&#xff0c;4&#xff0c;5&#xff0c;6个数的和// public void calculate(int n1){// System.out.…...

Flutter 中的 Expanded 小部件:全面指南

Flutter 中的 Expanded 小部件&#xff1a;全面指南 在 Flutter 中&#xff0c;Expanded 是一个用于控制子控件占据可用空间的布局小部件&#xff0c;通常与 Row、Column 或 Flex 等父级布局小部件一起使用。Expanded 允许你创建灵活的布局&#xff0c;其中子控件可以按照指定…...

[Kubernetes] KubeKey 部署 K8s v1.28.8

文章目录 1.K8s 部署方式2.操作系统基础配置3.安装部署 K8s4.验证 K8s 集群5.部署测试资源 1.K8s 部署方式 kubeadm: kubekey, sealos, kubespray二进制: kubeaszrancher 2.操作系统基础配置 主机名内网IP外网IPmaster192.168.66.2139.198.9.7node1192.168.66.3139.198.40.17…...

C# 与 Qt 的对比分析

C# 与 Qt 的对比分析 目录 C# 与 Qt 的对比分析 1. 语言特性 2. 开发环境 3. 框架和库 4. 用户界面设计 5. 企业级应用 6. 性能考量 在软件开发领域&#xff0c;C# 和 Qt 是两种常用的技术栈&#xff0c;它们分别在.NET平台和跨平台桌面应用开发中占据重要位置。本文将深…...

MapReduce | 二次排序

1.需求 主播数据--按照观众人数降序排序&#xff0c;如果观众人数相同&#xff0c;按照直播时长降序 # 案例数据 用户id 观众人数 直播时长 团团 300 1000 小黑 200 2000 哦吼 400 7000 卢本伟 100 6000 八戒 250 5000 悟空 100 4000 唐僧 100 3000 # 期望结果 哦吼 4…...

Java后端初始化项目(项目模板)

介绍 emmmm&#xff0c;最近看了一些网络资料&#xff0c;也是心血来潮&#xff0c;想自己手工搭建一个java后端的初始化项目模板来简化一下开发&#xff0c;也就发一个模板的具体制作流程&#xff0c;&#xff08;一步一步搭建&#xff0c;从易到难&#xff09; ok&#xff…...

electron 多窗口 vuex/pinia 数据状态同步简易方案(利用 LocalStorage)

全局 stroe 添加 mutations 状态同步方法 // 用于其他窗口同步 vuex 中的 DeviceTcpDataasyncDeviceTcpData(state: StateType, data: any) {state.deviceTcpData data},App.vue 里 onMounted(() > {console.log("App mounted");/*** vuex 多窗口 store 同步*//…...

自定义数据集图像分类实现

模型训练 要使用自己的图片分类数据集进行训练&#xff0c;这意味着数据集应该包含一个目录&#xff0c;其中每个子目录代表一个类别&#xff0c;子目录中包含该类别的所有图片。以下是一个使用Keras和TensorFlow加载自定义图片数据集进行分类训练的例子。 我们自己创建的数据集…...

【C++】手搓读写ini文件源码

【C】手搓读写ini文件源码 思路需求&#xff1a;ini.hini.cppconfig.confmian.cpp 思路 ini文件是一种系统配置文件&#xff0c;它有特定的格式组成。通常做法&#xff0c;我们读取ini文件并按照ini格式进行解析即可。在c语言中&#xff0c;提供了模板类的功能&#xff0c;所以…...

undolog

undolog回滚段 undolog执行的时间&#xff1a;在执行器操作bufferpool之前。 undolog页...

项目文档分享

Hello , 我是小恒。提前祝福妈妈母亲节快乐 。 本文写一篇初成的项目文档 &#xff08;不是README.md哈&#xff09;&#xff0c;仅供参考 项目名称 脚本存储网页 项目简介 本项目旨在创建一个网页&#xff0c;用于存储和展示各种命令&#xff0c;用户可以通过粘贴复制命令到…...

【深耕 Python】Quantum Computing 量子计算机(5)量子物理概念(二)

写在前面 往期量子计算机博客&#xff1a; 【深耕 Python】Quantum Computing 量子计算机&#xff08;1&#xff09;图像绘制基础 【深耕 Python】Quantum Computing 量子计算机&#xff08;2&#xff09;绘制电子运动平面波 【深耕 Python】Quantum Computing 量子计算机&…...

手写Spring5【笔记】

Spring5【笔记】 前言前言推荐Spring5【笔记】1介绍2手写 最后 前言 这是陈旧已久的草稿2022-12-01 23:32:59 这个是刷B站的时候&#xff0c;看到一个手写Spring的课程。 最后我自己好像运行不了&#xff0c;就没写。 现在2024-5-12 22:22:46&#xff0c;发布到[笔记]专栏中…...

2024中国(重庆)机器人展览会8月举办

2024中国(重庆)机器人展览会8月举办 邀请函 主办单位&#xff1a; 中国航空学会 重庆市南岸区人民政府 招商执行单位&#xff1a; 重庆港华展览有限公司 2024中国重庆机器人展会将汇聚机器人全产业链知名企业&#xff0c;世界科技领先的生产制造企业与来自多个国家和地区…...

Apache 开源项目文档中心 (英文 + 中文)

进度&#xff1a;持续更新中。。。 Apache Ambari 2.7.5 Apache Ambari Installation 2.7.5.0 (latest)Apache Ambari Installation 2.7.5.0 中文版 (latest) Apache DolphinScheduler Apache DolphinScheduler 1.2.0 中文版Apache DolphinScheduler 1.2.1 中文版...

蓝桥杯 算法提高 ADV-1164 和谐宿舍 python AC

贪心&#xff0c;二分 同类型题&#xff1a;蓝桥杯 算法提高 ADV-1175 打包 def judge(x):wood 0max_val 0ans_len 0for i in ll:if i > x:return Falseelif max(max_val, i) * (ans_len 1) < x:max_val max(max_val, i)ans_len 1else:wood 1max_val ians_len …...

Dragonfly 拓扑的路由算法

Dragonfly 拓扑的路由算法 1. Dragonfly 上的路由 (1)最小路由(2)非最小路由 2. 评估3. 存在问题 (1)吞吐量限制(2)较高的中间延迟 references Dragonfly 拓扑的路由算法 John Kim, William J. Dally 等人在 2008 年的 ISCA 中提出技术驱动、高度可扩展的 Dragonfly 拓扑。而…...

android基础-服务

同样使用intent来传递服务 oncreate是服务第一次启动调用&#xff0c;onStartCommand是服务每次启动的时候调用&#xff0c;也就是说服务只要启动后就不会调用oncreate方法了。可以在myservice中的任何位置调用stopself方法让服务停止下来。 服务生命周期 前台服务类似于通知会…...

mysql 事物

MySQL中的事务&#xff08;Transaction&#xff09;是一个确保数据完整性和一致性的重要概念。它将一组SQL操作捆绑在一起&#xff0c;当作一个单一的工作单元来执行。事务具备以下四个关键特性&#xff0c;即ACID特性&#xff1a; 原子性&#xff08;Atomicity&#xff09;&am…...

ES6从入门到精通:前言

ES6简介 ES6&#xff08;ECMAScript 2015&#xff09;是JavaScript语言的重大更新&#xff0c;引入了许多新特性&#xff0c;包括语法糖、新数据类型、模块化支持等&#xff0c;显著提升了开发效率和代码可维护性。 核心知识点概览 变量声明 let 和 const 取代 var&#xf…...

逻辑回归:给不确定性划界的分类大师

想象你是一名医生。面对患者的检查报告&#xff08;肿瘤大小、血液指标&#xff09;&#xff0c;你需要做出一个**决定性判断**&#xff1a;恶性还是良性&#xff1f;这种“非黑即白”的抉择&#xff0c;正是**逻辑回归&#xff08;Logistic Regression&#xff09;** 的战场&a…...

oracle与MySQL数据库之间数据同步的技术要点

Oracle与MySQL数据库之间的数据同步是一个涉及多个技术要点的复杂任务。由于Oracle和MySQL的架构差异&#xff0c;它们的数据同步要求既要保持数据的准确性和一致性&#xff0c;又要处理好性能问题。以下是一些主要的技术要点&#xff1a; 数据结构差异 数据类型差异&#xff…...

镜像里切换为普通用户

如果你登录远程虚拟机默认就是 root 用户&#xff0c;但你不希望用 root 权限运行 ns-3&#xff08;这是对的&#xff0c;ns3 工具会拒绝 root&#xff09;&#xff0c;你可以按以下方法创建一个 非 root 用户账号 并切换到它运行 ns-3。 一次性解决方案&#xff1a;创建非 roo…...

工业自动化时代的精准装配革新:迁移科技3D视觉系统如何重塑机器人定位装配

AI3D视觉的工业赋能者 迁移科技成立于2017年&#xff0c;作为行业领先的3D工业相机及视觉系统供应商&#xff0c;累计完成数亿元融资。其核心技术覆盖硬件设计、算法优化及软件集成&#xff0c;通过稳定、易用、高回报的AI3D视觉系统&#xff0c;为汽车、新能源、金属制造等行…...

高效线程安全的单例模式:Python 中的懒加载与自定义初始化参数

高效线程安全的单例模式:Python 中的懒加载与自定义初始化参数 在软件开发中,单例模式(Singleton Pattern)是一种常见的设计模式,确保一个类仅有一个实例,并提供一个全局访问点。在多线程环境下,实现单例模式时需要注意线程安全问题,以防止多个线程同时创建实例,导致…...

三分算法与DeepSeek辅助证明是单峰函数

前置 单峰函数有唯一的最大值&#xff0c;最大值左侧的数值严格单调递增&#xff0c;最大值右侧的数值严格单调递减。 单谷函数有唯一的最小值&#xff0c;最小值左侧的数值严格单调递减&#xff0c;最小值右侧的数值严格单调递增。 三分的本质 三分和二分一样都是通过不断缩…...

比较数据迁移后MySQL数据库和OceanBase数据仓库中的表

设计一个MySQL数据库和OceanBase数据仓库的表数据比较的详细程序流程,两张表是相同的结构,都有整型主键id字段,需要每次从数据库分批取得2000条数据,用于比较,比较操作的同时可以再取2000条数据,等上一次比较完成之后,开始比较,直到比较完所有的数据。比较操作需要比较…...

渗透实战PortSwigger靶场:lab13存储型DOM XSS详解

进来是需要留言的&#xff0c;先用做简单的 html 标签测试 发现面的</h1>不见了 数据包中找到了一个loadCommentsWithVulnerableEscapeHtml.js 他是把用户输入的<>进行 html 编码&#xff0c;输入的<>当成字符串处理回显到页面中&#xff0c;看来只是把用户输…...

Django RBAC项目后端实战 - 03 DRF权限控制实现

项目背景 在上一篇文章中&#xff0c;我们完成了JWT认证系统的集成。本篇文章将实现基于Redis的RBAC权限控制系统&#xff0c;为系统提供细粒度的权限控制。 开发目标 实现基于Redis的权限缓存机制开发DRF权限控制类实现权限管理API配置权限白名单 前置配置 在开始开发权限…...