七、Java中SpringBoot组件集成接入【Minio文件服务器】
七、Java中SpringBoot组件集成接入【Minio文件服务器】
- 1.Minio介绍
- 2.搭建Minio服务
- 2.1Windows部署
- 2.2Linux部署
- 2.3docker部署
- 3.Minio可视化操作
- 4.SpringBoot接入Minio
- 1.添加maven依赖
- 2.yaml配置文件
- 3.配置类
- 4.工具类
- 5.控制类
- 5.常见问题
- 6.其他参考文章
1.Minio介绍
对象存储服务OSS(Object Storage Service)是一种海量、安全、低成本、高可靠的云存储服务,适合存放任意类型的文件。容量和处理能力弹性扩展,多种存储类型供选择,全面优化存储成本。
Minio是一个开源的对象存储服务器,可以提供Amazon S3兼容的API接口。它设计用来容易部署和扩展,支持高性能、高可用性和强大的分布式存储。
关键特点包括:
- Amazon S3兼容性:Minio兼容S3 API,使得现有的S3客户端能够无缝地与Minio集成。
- 分布式架构:Minio可以水平扩展,在多节点环境中提供高性能和高可用存储解决方案。
- 高可用性:Minio支持数据冗余备份和故障转移,确保数据的可靠性和持久性。
- 易于部署:Minio的部署和配置简单,支持Docker、Kubernetes等现代化部署方式。
- 开源和免费:基于Apache License 2.0开源许可发布,用户可以免费使用并定制。
- 强大的生态系统:Minio拥有活跃的社区和文档支持,为用户提供丰富的资源和工具。
总体来说,Minio是一个灵活、可靠且易于使用的对象存储解决方案,适用于各种规模的应用和场景,帮助用户有效管理数据并构建可扩展的存储基础设施。
Minio官方文档:https://min.io/
Minio中文文档:http://minio.org.cn/
github项目地址:https://github.com/minio/
2.搭建Minio服务
下载地址:https://min.io/download
2.1Windows部署
1.下载minio.exe
2.在cmd命令行窗口中,进行minio.exe所在的文件夹,输入minio.exe server E:\minio
命令启动服务。
(server后面的地址是你图片上传之后的存储目录,默认账户密码为minioadmin/minioadmin)
2.2Linux部署
wget https://dl.min.io/server/minio/release/linux-amd64/minio
chmod +x minio
MINIO_ROOT_USER=admin MINIO_ROOT_PASSWORD=password ./minio server /mnt/data --console-address ":9001"
2.3docker部署
docker search minio
docker pull minio/minio
docker run -d -p 9000:9000 --name=minio --restart=always -e "MINIO_ROOT_USER=admin" -e "MINIO_ROOT_PASSWORD=admin123456" -v /home/data:/data -v /home/config:/root/.minio minio/minio server /data --console-address ":9000" --address ":9090"
docker logs -f containerid
3.Minio可视化操作
部署启动后,访问:http://127.0.0.1:9000/minio,可以进入到minio的控制台,可以尝试进行创建存储桶,上传文件到桶中,删除文件等操作.
4.SpringBoot接入Minio
1.添加maven依赖
<!--整合minio--><dependency><groupId>io.minio</groupId><artifactId>minio</artifactId><version>8.3.7</version></dependency><dependency><groupId>com.squareup.okhttp3</groupId><artifactId>okhttp</artifactId><version>4.9.1</version></dependency>
2.yaml配置文件
# 文件服务器
minio:endpoint: http://ip:9000accessKey: minioadminsecretKey: minioadminbucketName: funfan
3.配置类
import io.minio.MinioClient;
import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;@Data
@Configuration
public class MinioConfig {/*** 访问地址*/@Value("${minio.endpoint}")private String endpoint;/*** accessKey类似于用户ID,用于唯一标识你的账户*/@Value("${minio.accessKey}")private String accessKey;/*** secretKey是你账户的密码*/@Value("${minio.secretKey}")private String secretKey;/*** 默认存储桶*/@Value("${minio.bucketName}")private String bucketName;@Beanpublic MinioClient minioClient() {MinioClient minioClient = MinioClient.builder().endpoint(endpoint).credentials(accessKey, secretKey).region(bucketName).build();return minioClient;}
}
4.工具类
import cn.hutool.core.codec.Base64Decoder;
import io.minio.*;
import io.minio.http.Method;
import io.minio.messages.Bucket;
import io.minio.messages.DeleteObject;
import io.minio.messages.Item;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.*;/*** MinIO工具类*/
@Slf4j
@Component
@RequiredArgsConstructor
public class MinioUtils {private final MinioClient minioClient;/****************************** Operate Bucket Start ******************************//*** 启动SpringBoot容器的时候初始化Bucket* 如果没有Bucket则创建** @param bucketName*/@SneakyThrows(Exception.class)private void createBucket(String bucketName) {if (!bucketExists(bucketName)) {minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());}}/*** 判断Bucket是否存在,true:存在,false:不存在** @param bucketName* @return*/@SneakyThrows(Exception.class)public boolean bucketExists(String bucketName) {return minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());}/*** 获得Bucket的策略** @param bucketName* @return*/@SneakyThrows(Exception.class)public String getBucketPolicy(String bucketName) {return minioClient.getBucketPolicy(GetBucketPolicyArgs.builder().bucket(bucketName).build());}/*** 获得所有Bucket列表** @return*/@SneakyThrows(Exception.class)public List<Bucket> getAllBuckets() {return minioClient.listBuckets();}/*** 根据bucketName获取其相关信息** @param bucketName* @return*/@SneakyThrows(Exception.class)public Optional<Bucket> getBucket(String bucketName) {return getAllBuckets().stream().filter(b -> b.name().equals(bucketName)).findFirst();}/*** 根据bucketName删除Bucket,true:删除成功; false:删除失败,文件或已不存在** @param bucketName* @throws Exception*/@SneakyThrows(Exception.class)public void removeBucket(String bucketName) {minioClient.removeBucket(RemoveBucketArgs.builder().bucket(bucketName).build());}/****************************** Operate Bucket End ******************************//****************************** Operate Files Start ******************************//*** 判断文件是否存在** @param bucketName* @param objectName* @return*/public boolean isObjectExist(String bucketName, String objectName) {boolean exist = true;try {minioClient.statObject(StatObjectArgs.builder().bucket(bucketName).object(objectName).build());} catch (Exception e) {log.error("[Minio工具类]>>>> 判断文件是否存在, 异常:", e);exist = false;}return exist;}/*** 判断文件夹是否存在** @param bucketName* @param objectName* @return*/public boolean isFolderExist(String bucketName, String objectName) {boolean exist = false;try {Iterable<Result<Item>> results = minioClient.listObjects(ListObjectsArgs.builder().bucket(bucketName).prefix(objectName).recursive(false).build());for (Result<Item> result : results) {Item item = result.get();if (item.isDir() && objectName.equals(item.objectName())) {exist = true;}}} catch (Exception e) {log.error("[Minio工具类]>>>> 判断文件夹是否存在,异常:", e);exist = false;}return exist;}/*** 根据文件前置查询文件** @param bucketName 存储桶* @param prefix 前缀* @param recursive 是否使用递归查询* @return MinioItem 列表*/@SneakyThrows(Exception.class)public List<Item> getAllObjectsByPrefix(String bucketName,String prefix,boolean recursive) {List<Item> list = new ArrayList<>();Iterable<Result<Item>> objectsIterator = minioClient.listObjects(ListObjectsArgs.builder().bucket(bucketName).prefix(prefix).recursive(recursive).build());if (objectsIterator != null) {for (Result<Item> o : objectsIterator) {Item item = o.get();list.add(item);}}return list;}/*** 获取文件流** @param bucketName 存储桶* @param objectName 文件名* @return 二进制流*/@SneakyThrows(Exception.class)public InputStream getObject(String bucketName, String objectName) {return minioClient.getObject(GetObjectArgs.builder().bucket(bucketName).object(objectName).build());}/*** 断点下载** @param bucketName 存储桶* @param objectName 文件名称* @param offset 起始字节的位置* @param length 要读取的长度* @return 二进制流*/@SneakyThrows(Exception.class)public InputStream getObject(String bucketName, String objectName, long offset, long length) {return minioClient.getObject(GetObjectArgs.builder().bucket(bucketName).object(objectName).offset(offset).length(length).build());}/*** 获取路径下文件列表** @param bucketName 存储桶* @param prefix 文件名称* @param recursive 是否递归查找,false:模拟文件夹结构查找* @return 二进制流*/public Iterable<Result<Item>> listObjects(String bucketName, String prefix, boolean recursive) {return minioClient.listObjects(ListObjectsArgs.builder().bucket(bucketName).prefix(prefix).recursive(recursive).build());}/*** 使用MultipartFile进行文件上传** @param bucketName 存储桶* @param file 文件名* @param objectName 对象名* @param contentType 类型* @return*/@SneakyThrows(Exception.class)public ObjectWriteResponse uploadFile(String bucketName, MultipartFile file, String objectName, String contentType) {InputStream inputStream = file.getInputStream();return minioClient.putObject(PutObjectArgs.builder().bucket(bucketName).object(objectName).contentType(contentType).stream(inputStream, inputStream.available(), -1).build());}/*** 图片上传* @param bucketName* @param imageBase64* @param imageName* @return*/public ObjectWriteResponse uploadImage(String bucketName, String imageBase64, String imageName) {if (!StringUtils.isEmpty(imageBase64)) {InputStream in = base64ToInputStream(imageBase64);return uploadFile(bucketName, imageName, in);}return null;}public static InputStream base64ToInputStream(String base64) {ByteArrayInputStream stream = null;try {byte[] bytes = Base64Decoder.decode(base64.trim());stream = new ByteArrayInputStream(bytes);} catch (Exception e) {e.printStackTrace();}return stream;}/*** 上传本地文件** @param bucketName 存储桶* @param objectName 对象名称* @param fileName 本地文件路径* @return*/@SneakyThrows(Exception.class)public ObjectWriteResponse uploadFile(String bucketName, String objectName, String fileName) {return minioClient.uploadObject(UploadObjectArgs.builder().bucket(bucketName).object(objectName).filename(fileName).build());}/*** 通过流上传文件** @param bucketName 存储桶* @param objectName 文件对象* @param inputStream 文件流* @return*/@SneakyThrows(Exception.class)public ObjectWriteResponse uploadFile(String bucketName, String objectName, InputStream inputStream) {return minioClient.putObject(PutObjectArgs.builder().bucket(bucketName).object(objectName).stream(inputStream, inputStream.available(), -1).build());}/*** 创建文件夹或目录** @param bucketName 存储桶* @param objectName 目录路径* @return*/@SneakyThrows(Exception.class)public ObjectWriteResponse createDir(String bucketName, String objectName) {return minioClient.putObject(PutObjectArgs.builder().bucket(bucketName).object(objectName).stream(new ByteArrayInputStream(new byte[]{}), 0, -1).build());}/*** 获取文件信息, 如果抛出异常则说明文件不存在** @param bucketName 存储桶* @param objectName 文件名称* @return*/@SneakyThrows(Exception.class)public String getFileStatusInfo(String bucketName, String objectName) {return minioClient.statObject(StatObjectArgs.builder().bucket(bucketName).object(objectName).build()).toString();}/*** 拷贝文件** @param bucketName 存储桶* @param objectName 文件名* @param srcBucketName 目标存储桶* @param srcObjectName 目标文件名*/@SneakyThrows(Exception.class)public ObjectWriteResponse copyFile(String bucketName, String objectName, String srcBucketName, String srcObjectName) {return minioClient.copyObject(CopyObjectArgs.builder().source(CopySource.builder().bucket(bucketName).object(objectName).build()).bucket(srcBucketName).object(srcObjectName).build());}/*** 删除文件** @param bucketName 存储桶* @param objectName 文件名称*/@SneakyThrows(Exception.class)public void removeFile(String bucketName, String objectName) {minioClient.removeObject(RemoveObjectArgs.builder().bucket(bucketName).object(objectName).build());}/*** 批量删除文件** @param bucketName 存储桶* @param keys 需要删除的文件列表* @return*/public void removeFiles(String bucketName, List<String> keys) {List<DeleteObject> objects = new LinkedList<>();keys.forEach(s -> {objects.add(new DeleteObject(s));try {removeFile(bucketName, s);} catch (Exception e) {log.error("[Minio工具类]>>>> 批量删除文件,异常:", e);}});}/*** 获取文件外链** @param bucketName 存储桶* @param objectName 文件名* @param expires 过期时间 <=7 秒 (外链有效时间(单位:秒))* @return url*/@SneakyThrows(Exception.class)public String getPresignedObjectUrl(String bucketName, String objectName, Integer expires) {GetPresignedObjectUrlArgs args = GetPresignedObjectUrlArgs.builder().expiry(expires).bucket(bucketName).object(objectName).build();return minioClient.getPresignedObjectUrl(args);}/*** 获得文件外链** @param bucketName* @param objectName* @return url*/@SneakyThrows(Exception.class)public String getPresignedObjectUrl(String bucketName, String objectName) {GetPresignedObjectUrlArgs args = GetPresignedObjectUrlArgs.builder().bucket(bucketName).object(objectName).method(Method.GET).build();return minioClient.getPresignedObjectUrl(args);}/*** 将URLDecoder编码转成UTF8** @param str* @return* @throws UnsupportedEncodingException*/public String getUtf8ByURLDecoder(String str) throws UnsupportedEncodingException {String url = str.replaceAll("%(?![0-9a-fA-F]{2})", "%25");return URLDecoder.decode(url, "UTF-8");}
}
5.控制类
import com.unitech.camera.common.model.RestResponse;
import com.unitech.camera.component.oss.MinioConfig;
import com.unitech.camera.component.oss.MinioUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletResponse;
import java.io.InputStream;/*** @Author: jiangzhiyuan* @Date: 2024/3/5 10:21* @Description:*/
@Slf4j
@RestController
@RequestMapping("/oss")
public class OSSController {@Autowiredprivate MinioUtils minioUtils;@Autowiredprivate MinioConfig minioConfig;/*** 文件上传** @param file*/@PostMapping("/upload")public Object upload(@RequestParam("file") MultipartFile file) {try {//文件名String fileName = file.getOriginalFilename();String newFileName = System.currentTimeMillis() + "." + StringUtils.substringAfterLast(fileName, ".");//类型String contentType = file.getContentType();minioUtils.uploadFile(minioConfig.getBucketName(), file, newFileName, contentType);return RestResponse.success();} catch (Exception e) {log.error("上传失败");return RestResponse.error("上传失败");}}/*** 删除** @param fileName*/@DeleteMapping("/")public Object delete(@RequestParam("fileName") String fileName) {minioUtils.removeFile(minioConfig.getBucketName(), fileName);//不需要加桶名称return RestResponse.success();}/*** 获取文件信息** @param fileName* @return*/@GetMapping("/info")public Object getFileStatusInfo(@RequestParam("fileName") String fileName) {return RestResponse.success(minioUtils.getFileStatusInfo(minioConfig.getBucketName(), fileName));}/*** 获取文件外链** @param fileName* @return*/@GetMapping("/url")public Object getPresignedObjectUrl(@RequestParam("fileName") String fileName) {return RestResponse.success(minioUtils.getPresignedObjectUrl(minioConfig.getBucketName(), fileName));}/*** 文件下载** @param fileName* @param response*/@GetMapping("/download")public Object download(@RequestParam("fileName") String fileName, HttpServletResponse response) {try {InputStream fileInputStream = minioUtils.getObject(minioConfig.getBucketName(), fileName);response.setHeader("Content-Disposition", "attachment;filename=" + fileName);response.setContentType("application/force-download");response.setCharacterEncoding("UTF-8");IOUtils.copy(fileInputStream, response.getOutputStream());return RestResponse.success();} catch (Exception e) {return RestResponse.error("下载失败");}}
}
5.常见问题
暂无
6.其他参考文章
SpringBoot集成Minio实战详解
springboot整合minio全网最详细的教程
相关文章:

七、Java中SpringBoot组件集成接入【Minio文件服务器】
七、Java中SpringBoot组件集成接入【Minio文件服务器】 1.Minio介绍2.搭建Minio服务2.1Windows部署2.2Linux部署2.3docker部署 3.Minio可视化操作4.SpringBoot接入Minio1.添加maven依赖2.yaml配置文件3.配置类4.工具类5.控制类 5.常见问题6.其他参考文章 1.Minio介绍 对象存储…...

使用Pygame做一个乒乓球游戏
项目介绍 使用Pygame做一个乒乓球游戏。左侧为电脑,右侧为玩家。 视频地址-YT 视频搬运-B站 视频教程约90分钟。 代码地址 环境:需要pygame库,可用pip安装:pip install pygame 1. 基础版本 首先进行一些初始化,初始…...

力扣---完全平方数
思路: 还是比较好想的,g[i]定义为和为 i 的完全平方数的最少数量。那么递推关系式是g[i]min(g[i-1],g[i-4],g[i-9],...)1,数组初始化是g[0]0,g[1]1。注意这里要对g[0]初始化,(举个例子)因为在遍历到g[4]时&…...

接口测试、postman、测试点提取【主】
接口测试是测试系统组件间接口的一种测试 接口测试主要用于检测外部系统与系统之间以及内部各个子系统之间的交互点 测试的重点是要检查数据的交换,传递和控制管理过程,以及系统间的相互逻辑依赖关系 文章目录 HTTP接口 & Web Service接口RESTful接口…...

C++ list详解及模拟实现
目录 本节目标 1. list的介绍及使用 1.2 list的使用 2.list的模拟实现 1.对list进行初步的实现 2.头插和任意位置的插入 3.pos节点的删除,头删,尾删 4.销毁list和析构函数 5.const迭代器 6.拷贝构造和赋值操作 3.完整代码 本节目标 1. list的…...

【tls招新web部分题解】
emowebshell (php7.4.21版本漏洞) 非预期 题目提示webshell,就直接尝试一下常见的后门命名的规则 如 shell.php这里运气比较好,可以直接shell.php就出来 要是不想这样尝试的话,也可以直接dirsearch进行目录爆破 然后在phpinfo中直接搜素c…...

力扣热门算法题 52. N 皇后 II,53. 最大子数组和,54. 螺旋矩阵
52. N 皇后 II,53. 最大子数组和,54. 螺旋矩阵,每题做详细思路梳理,配套Python&Java双语代码, 2024.03.20 可通过leetcode所有测试用例。 目录 52. N 皇后 II 解题思路 完整代码 Python Java 53. 最大子数组…...
【OpenVINO】解决OpenVINO在GPU推理中报错的方法
1. 问题描述 使用OpenVINO进行深度学习推理时,通常会借助GPU以提升计算速度。然而,有时候运行程序时候会出现如下错误: <kernel>:8153:2: error: expected identifier or (unroll_for (int i 0; i < TILE_SIZE; i) {^ <kernel…...
AES加密的中文乱码与Java默认编码
0. 背景 win11环境下 java8 idea 开发的项目接口有加密需求,暂时使用AES完成,AES工具类代码如下 public static String aesEncrypt(String content, String key) throws Exception {//指定加密算法Cipher cipher Cipher.getInstance("AES");//创建加密规则&#…...
Node.js笔记 (二)浏览器和服务器
Ajax Ajax是什么 全称:Asynchronous Javascript And Xml. 用javascript执行异步网络请求,可以说是定义了一种编程行为/习惯。 通信双方:浏览器 和 服务器 特点:异步,所以可以在异步请求服务器,在不刷新页…...
面试经典-32-判断子序列
题目 给定字符串 s 和 t ,判断 s 是否为 t 的子序列。 字符串的一个子序列是原始字符串删除一些(也可以不删除)字符而不改变剩余字符相对位置形成的新字符串。(例如,"ace"是"abcde"的一个子序列…...
windows使用知识
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 前言windows使用知识 一、cmd鼠标选中后,程序不运行的解决方案总结 前言 提示:这里可以添加本文要记录的大概内容: windows使用…...
用python如何实现智能合约?如何使用remix编写solidity智能合约并部署上链
目录 用python如何实现智能合约? 直接展示下成功界面 下面分步骤说: remix代码 python链接remix代码...
Electron窗口管理详解:使用BrowserWindow API打造个性化界面
Electron窗口管理详解:使用BrowserWindow API打造个性化界面 创建和初始化窗口窗口定制化窗口操作与事件监听多窗口管理和工作区布局结语 在当今跨平台桌面应用开发领域,Electron 凭借其 JavaScript 与 HTML5 技术栈结合原生操作系统 API 的能力…...

19---时钟电路设计
视频链接 时钟硬件电路设计01_哔哩哔哩_bilibili 时钟电路设计 晶振是数字电路的心脏,数字电路需要一个稳定的工作时钟信号,时钟电路至关重要! 1、晶振概述 晶振一般指晶体振荡器。晶体振荡器是指从一块石英晶体上按一定方位角切下薄片&…...
PSNR/SSIM/LPIPS图像质量评估三件套(含代码)
在图像质量评估上,有三个重要指标:PSNR,SSIM,LPIPS。本文提供简易脚本分别实现。 PSNR,峰值信噪比,是基于MSE的像素比较低质量评估,一般30dB以上质量就不错,到40dB以上肉眼就很难分…...

20240318uniapp怎么引用组件
在script中增加 import index from "/pages/index/index.vue" 把index直接整个作为一个组件引入 然后注册组件 在export default中增加 components: {index:index }, 注册了index组件,内容为import的index 然后就可以在template里使用 <index&…...

扩展以太网(数据链路层)
目录 一、在物理层扩展以太网 二、在数据链路层扩展以太网 三、以太网交换机的特点 四、以太网交换机的交换方式 五、以太网交换机的自学习功能 六、小结 一、在物理层扩展以太网 使用光纤扩展: • 主机使用光纤(通常是一对光纤)和…...
每日一练 | 华为认证真题练习Day202
1、在组播网络环境中,如果IGMPv2主机和IGMP V1路由器(以下简称版本2主机和版本1路由器)共同处于同一局域网当中,那他们是如何协同工作的?(多选) A. 版本1路由器把IGMPv2报告看作无效的IGMP信息…...

基于python+vue的幼儿园管理系统flask-django-php-nodejs
随着信息时代的来临,过去的传统管理方式缺点逐渐暴露,对过去的传统管理方式的缺点进行分析,采取计算机方式构建幼儿园管理系统。本文通过课题背景、课题目的及意义相关技术,提出了一种活动信息、课程信息、菜谱信息、通知公告、家…...

wordpress后台更新后 前端没变化的解决方法
使用siteground主机的wordpress网站,会出现更新了网站内容和修改了php模板文件、js文件、css文件、图片文件后,网站没有变化的情况。 不熟悉siteground主机的新手,遇到这个问题,就很抓狂,明明是哪都没操作错误&#x…...
synchronized 学习
学习源: https://www.bilibili.com/video/BV1aJ411V763?spm_id_from333.788.videopod.episodes&vd_source32e1c41a9370911ab06d12fbc36c4ebc 1.应用场景 不超卖,也要考虑性能问题(场景) 2.常见面试问题: sync出…...

大数据零基础学习day1之环境准备和大数据初步理解
学习大数据会使用到多台Linux服务器。 一、环境准备 1、VMware 基于VMware构建Linux虚拟机 是大数据从业者或者IT从业者的必备技能之一也是成本低廉的方案 所以VMware虚拟机方案是必须要学习的。 (1)设置网关 打开VMware虚拟机,点击编辑…...
【android bluetooth 框架分析 04】【bt-framework 层详解 1】【BluetoothProperties介绍】
1. BluetoothProperties介绍 libsysprop/srcs/android/sysprop/BluetoothProperties.sysprop BluetoothProperties.sysprop 是 Android AOSP 中的一种 系统属性定义文件(System Property Definition File),用于声明和管理 Bluetooth 模块相…...
Android Bitmap治理全解析:从加载优化到泄漏防控的全生命周期管理
引言 Bitmap(位图)是Android应用内存占用的“头号杀手”。一张1080P(1920x1080)的图片以ARGB_8888格式加载时,内存占用高达8MB(192010804字节)。据统计,超过60%的应用OOM崩溃与Bitm…...
AspectJ 在 Android 中的完整使用指南
一、环境配置(Gradle 7.0 适配) 1. 项目级 build.gradle // 注意:沪江插件已停更,推荐官方兼容方案 buildscript {dependencies {classpath org.aspectj:aspectjtools:1.9.9.1 // AspectJ 工具} } 2. 模块级 build.gradle plu…...

AI,如何重构理解、匹配与决策?
AI 时代,我们如何理解消费? 作者|王彬 封面|Unplash 人们通过信息理解世界。 曾几何时,PC 与移动互联网重塑了人们的购物路径:信息变得唾手可得,商品决策变得高度依赖内容。 但 AI 时代的来…...
RLHF vs RLVR:对齐学习中的两种强化方式详解
在语言模型对齐(alignment)中,强化学习(RL)是一种重要的策略。而其中两种典型形式——RLHF(Reinforcement Learning with Human Feedback) 与 RLVR(Reinforcement Learning with Ver…...

(12)-Fiddler抓包-Fiddler设置IOS手机抓包
1.简介 Fiddler不但能截获各种浏览器发出的 HTTP 请求,也可以截获各种智能手机发出的HTTP/ HTTPS 请求。 Fiddler 能捕获Android 和 Windows Phone 等设备发出的 HTTP/HTTPS 请求。同理也可以截获iOS设备发出的请求,比如 iPhone、iPad 和 MacBook 等苹…...

vue3 手动封装城市三级联动
要做的功能 示意图是这样的,因为后端给的数据结构 不足以使用ant-design组件 的联动查询组件 所以只能自己分装 组件 当然 这个数据后端给的不一样的情况下 可能组件内对应的 逻辑方式就不一样 毕竟是 三个 数组 省份 城市 区域 我直接粘贴组件代码了 <temp…...