首页
归档
留言
广告合作
友链
美女主播
Search
1
博瑞GE车机升级/降级
5,146 阅读
2
Mac打印机设置黑白打印
4,517 阅读
3
修改elementUI中el-table树形结构图标
4,516 阅读
4
Mac客户端添加腾讯企业邮箱方法
4,351 阅读
5
intelliJ Idea 2022.2.X破解
4,060 阅读
Java
HarmonyOS Next
Web前端
微信开发
开发辅助
App开发
数据库
随笔日记
登录
/
注册
Search
标签搜索
Spring Boot
Java
Spring Cloud
Mac
mybatis
WordPress
Nacos
Spring Cloud Alibaba
Mybatis-Plus
jQuery
Java Script
asp.net
微信小程序
Sentinel
UniApp
MySQL
asp.net core
IntelliJ IDEA
Jpa
树莓派
Laughing
累计撰写
570
篇文章
累计收到
1,424
条评论
首页
栏目
Java
HarmonyOS Next
Web前端
微信开发
开发辅助
App开发
数据库
随笔日记
页面
归档
留言
广告合作
友链
美女主播
搜索到
570
篇与
的结果
2024-06-29
别只傻傻的盯着Navicat,这些数据库连接工具才是YYDS
在国内,提到数据库连接工具,相信程序猿们肯定会想到Navicat,我们必须得承认,Navicat的确是一个强大且易用的软件,几乎支持所有主流的数据库,比如MySQL、PostgreSQL、SQL Server、Oracle、MariaDB,甚至是 Redis 和 MongoDB 等NoSQL 数据库。并且在最新的版本,也提供了免费的Navicat Premium Lite,虽然是Navicat 的精简版,但它也支持所有主流数据库,能满足日常使用。虽然Navicat足够强大,但是作为一个程序猿,在日常使用起来,我可能不需要太强大的功能,但是我更关注的是我本身的实际场景,目前来讲,Navicat主要是因为以下两点原因,让我不得不放弃它:在目前的XC环境之下,国产数据库呈井喷式发展,像瀚高、神通、大梦、高斯等等数据库,Navicat虽然支持主流的数据库,但是国产数据库几乎没有能用的,现在一套软件,恨不得兼容10种数据库,如果每种数据库都安装一套软件,对于程序猿来说是致命的。新版本的Navicat,我看着也能够通过URI进行连接,但是具体我没尝试。不管是测试环境还是开发环境,有时候修改后台数据是不可避免的。在这个动不动拉程序猿祭天的环境下,如何快速、安全的修改后台数据,成了硬性条件,对于我来讲,我喜欢通过数据库工具,查询到需要修改的数据,然后修改后保存,但是Navicat这方面的表现确实差强人意,几乎无法修改数据。在这个百花齐放的环境,其实还是有不少优秀的可能不为人熟知的数据库软件。下面是一些在数据库管理领域同样表现出色的工具:DatagripDatagrip是我着力推荐的一款软件,也是我现在的主力开发工具。Datagrip的优点多平台支持:可以在Windows、macOS、Linux操作系统上运行,满足不同用户的系统需求。广泛数据库支持:Datagrip专为数据库开发者设计的IDE,支持多种数据库(包括MySQL、PostgreSQL、Microsoft SQL Server、SQLite等),并且可以自己创建数据源模板,通过自定义数据源,可以实现包括国产数据库在内的所有数据库连接。智能代码辅助:在编写sql语句时,提示功能非常强大,不管是表名、列名还是函数名,智能提示都极其强大。防御性编程:相信如果项目干的久了,一定遇到过更新或删除数据时,忘记写where条件的事故,在Datagrip中,如果没有写where条件,会不让你执行并给出提示。当然,如果你确实需要执行,可以写where 1=1。数据迁移方便:不管是导出数据、生成insert或update语句,亦或是生成DDL语句,都能一键搞定。查询工具强大:包含高效的查询执行、结果集处理(如排序、过滤)、数据导出和可视化功能,便于数据分析和处理。界面美观:借助丰富的插件,可以选择自己喜欢的外观样式。Datagrip的缺点当然,任何一款软件都不是完美的,Datagrip也不例外Datagrip是商业软件,个人使用,单独购买,首年800元,次年640元,第三年起480元。与其兄弟软件IntelliJ IDEA 类似,资源占用比较高,如果是8G内存的电脑,建议不要使用,如果使用的话,起码16G以上内存吧。数据库备份不够灵活。比如MySql备份整库时,Navicat基本上右键导出就行了,Datagrip得需要借助mysqldump进行导出。DBeaver CommunityDBeaver Community是一款免费且开源的多平台数据库管理工具,支持几乎所有的数据库类型。它提供了丰富的数据编辑、SQL编写和执行功能,以及ER图的生成,非常适合需要跨数据库工作的用户。当然,他也有收费版本DBeaver Pro,这个不是我们介绍的重点。DBEaver的优点免费开源:DBeaver是一款免费且开源的数据库管理工具,遵循GPL协议,适合预算有限的个人和组织。广泛兼容性:支持多种数据库管理系统(DBMS),包括但不限于MySQL、PostgreSQL、Oracle、SQLite、Microsoft SQL Server等,实现了跨数据库管理的便利。跨平台:可在Windows、macOS、Linux等多个操作系统上运行,提高了使用的灵活性。功能丰富:提供数据库连接、SQL编辑器、数据导入导出、数据备份恢复、ER图生成等多种功能,满足日常数据库管理的各种需求。高度可定制:通过插件和扩展支持,用户可以根据需要定制功能,增强其适用性。强大的SQL编辑与执行:支持SQL语法高亮、代码补全、错误提示及执行计划查看,提升开发效率。数据可视化:能够以表格、图表等形式展示数据,便于数据分析。DBEaver的缺点学习曲线:由于功能较为复杂和灵活,对于初学者来说,DBeaver的用户界面和功能可能需要一定时间去学习和适应。性能配置:在某些场景下,用户可能需要对DBeaver进行一些性能相关的配置以达到最佳使用效果,尤其是处理大量数据时。知名度:相对于Navicat和phpMyAdmin等工具,DBeaver的知名度较低,可能导致社区支持和在线资源相对较少。PL/SQL Developer专门用于Oracle数据库的开发和管理。这款软件为数据库管理员和开发人员提供了强大的工具集,用于编写、测试、调试和优化PL/SQL代码,以及管理数据库对象。在前些年Oracle风靡天下的时候,这是每一个开发人员必备的软件。自定义快捷键、Sql语句美化等等,都比较强大。缺点是只支持Windows,并且为需要配置OCI、环境变量(否则中文显示乱码)。SSMS全称SQL Server Management Studio 是由微软开发的一款强大的、免费的集成环境,专为管理和配置SQL Server基础设施而设计的软件。支持Windows系统,如果你使用SQL Server数据库可以使用,配套软件功能完备,没有其他亮点。phpMyAdmin当年LNMP或者LAMP套件,很多快捷网站运维工具都带着,比如宝塔、lnmp等,当年WordPress等基于php的博客或者CMS系统盛行的时候,这玩意用的还是比较多的。现在基本用来备份一下博客数据库。MySQL WorkBenchMySQL WorkBench 是官方出品的客户端,支持 Mac、Windows、Linux。因为是官方出品,所以功能比较全。官方出品的软件,因为只适配自己,不做过多介绍。SQLyogSQLyog是一款简洁高效的MySQL数据库管理工具,它的特点在于支持多种连接方式和可视化操作。支持Windows和Linux,更多的专注于MySql数据库,这款软件,我本身实际没有使用过,只是见过我们测试同事有使用的,不做过多介绍,有兴趣的可以下载试试。
2024年06月29日
399 阅读
0 评论
0 点赞
2024-06-23
微信小程序开发修改页面名称大小写遇到的坑
最近在使用UniApp开发小程序时,有一个页面名称大小写不是很规范,因此修改了一下命名,当然,只是修改了页面名称的大小写,没有修改其他的东西。修改完成后,在UniApp运行调试,发现没有任何问题,于是顺理成章的发行并上传,但是,此时问题来了,代码上传到微信小程序之后,在真机测试时,页面始终无法打开。这个时候,首先想到是不是在UniApp发行出现了问题,于是乎,在UniApp重新发行了一次,这次发行后,特意在微信开发者工具进行了测试,页面也是能够正常打开没有任何报错或警告信息,在调试通过后,再次上传代码,进行真机测试,发现页面仍然无法打开。联想到Git对文件名称大小写不敏感的问题,我觉得应该是在微信开发者工具上传代码时,同样对文件名称大小写不敏感出现了问题,导致虽然我修改了页面名称,但是在微信服务器,文件名称仍然还是小写的,但是我代码中,此时已经修改了打开的页面路径的大小写,因此导致找不到页面。其实解决方式也特别简单,重命名一下页面名称,当然,这个时候,不能只是修改页面名称大小写,重新发行、上传代码,然后真机测试,此时页面能够完美打开了。
2024年06月23日
643 阅读
0 评论
0 点赞
2024-06-21
UniApp中修改某个页面的uView样式
最近在使用UniApp开发微信小程序时,使用了uView UI框架,在进行列表展示时,使用了Cell单元格组件。但是在使用的时候,感觉Cell单元格的字体较大,想调整小一点。在Vue开发中,我们知道都是用depp进行样式穿透,但是在UniApp开发小程序时,如果我们添加以下代码 ::v-deep { .u-cell { padding: 10rpx 32rpx !important; font-size: 20rpx !important; line-height: 54rpx; font-family: cursive; } }可以尝试运行一下,你会发现没有任何效果。原因就在于小程序组件样式是隔离的。如果我们想解除样式隔离,可以在父组件(基本就是我们自己的页面)与methods同级的位置,添加以下代码options: { styleIsolation: 'shared', // 解除样式隔离 },对于组件的styleIsolation有三个可选值:可选值说明isolated默认值,组件内外的样式互不影响apply-shared接收外部(包括父页面和全局)的样式,但组件内的样式不影响外部。shared接收外部样式,同时组件内的样式会共享到页面
2024年06月21日
629 阅读
1 评论
0 点赞
2024-05-30
ElementUI中一个页面多个el-descriptions列不对齐问题处理
使用ElementUI框架时,如果我们希望列表形式展示多个字段。可能经常使用Descriptions描述列表,如果一个页面只有一个Descriptions时,列宽显示是没有问题的,但是如果有多个Descriptions,你会发现,各个Descriptions之间的列宽,可能显示的不一致。为了保持各列宽度一致,我们可以设置contentStyle属性。<el-descriptions border :label-style="{ width: '110px' }" :contentStyle="content_style"> </el-descriptions> 然后在data里面,设置具体的content_stylecontent_style: { // 居左 'text-align': 'left', // 设置长度 width: '400px', // 排列第二行 'word-break': 'break-all' }
2024年05月30日
834 阅读
0 评论
1 点赞
2024-05-29
ElementUI中el-input-number实时监听值变化
在前端开发过程中,我们经常会遇到将金额转大写的情况,我们要实现的效果,是只要输入就实时计算大写,而不是等全部输入完成失去焦点后。如果使用ElementUI框架的el-input-number控件的@input事件,是无法达到实时显示效果的。html代码 <el-col :span="24"> <el-form-item label="本次报价" prop="quotationPrice"> <el-input-number :precision="2" :step="100" :max="10000000" :min="0" v-model="form.quotationPrice" style="width: 100%" controls-position="right" data-unit="元" placeholder="请输入本次报价" ref="refQuotationPrice" @input.native="convertPrice2CnyMoney" @change="convertPrice2CnyMoney4Change" /> </el-form-item> </el-col> <el-col :span="24"> <el-form-item label="总金额" prop="cnyMoney"> <div class="cnyMoney">{{ cnyMoney }}</div> </el-form-item> </el-col> JavaScript代码//本次报价金额实时转大写 convertPrice2CnyMoney(event) { let value = this.$refs.refQuotationPrice.displayValue; this.cnyMoney = numToCny(value); }, //本次报价金额实时转大写 convertPrice2CnyMoney4Change(val) { this.cnyMoney = numToCny(val); }
2024年05月29日
774 阅读
0 评论
0 点赞
2024-05-12
MateBook X Pro 2020 升级Sanoma 14.4.1 EFI
先上截图我这次不是全新安装,是在BigSur的版本基础上,通过在线更新直接升级的。壹、电脑配置我这个是Matebook X Pro 2020最低配集成显卡那款,16G内存,其他信息不记得了,别的版本没有验证。贰、使用感受流畅度来讲,我个人感觉比BigSur流畅一丢丢。蓝牙:不知道是不是我之前BigSur的驱动有问题,机械师的蓝牙键盘没法用,升级最新的之后,机械师的蓝牙键盘能用了。苹果鼠标、键盘亲测能用,其他蓝牙鼠标,感觉跟之前一样不可用。叁、发现问题系统会偶发自动重启,使用两天,遇到过两回了,自动重启后没啥问题。附Sanoma EFI,天翼云盘,不限速下载https://cloud.189.cn/web/share?code=uIrmuayyiuYv(访问码:3whx)[alt type="error"]如果下载链接失效,烦请留言通知我,谢谢[/alt]
2024年05月12日
775 阅读
0 评论
0 点赞
2024-03-26
深度解析:如何在若依系统中集成阿里云OSS实现高效文件存储
零、引言随着信息化技术的快速发展,企业级应用对于海量文件存储的需求日益增长。而阿里云对象存储服务(OSS)以其高可用、高可靠、低成本的特点成为众多企业的首选解决方案。本文将以流行的开源后台管理系统——若依系统为例,详细阐述如何将其与阿里云OSS无缝集成,以实现文件资源的安全、高效存储。壹、若依系统上传文件的现状若依系统基于ElementUI的el-upload组件,对于我们的业务来讲,目前存在两个需要改进的地方(1)文件选择后会自动上传,这个在前面的文章有过介绍若依系统上传图片压缩 - 香草物语 (xiangcaowuyu.net)(2)若依系统上传文件是上传到应用服务器的,我们需要实现的是上传到阿里云OSS,同时可以将OSS内容,通过内网下载到ECS,方便备份文件,减少OSS存储费用。叁、开通并配置阿里云OSS首先,您需要在阿里云官网注册并登录账号,然后开通OSS服务。在控制台中创建一个新的Bucket,为您的项目设定专属的存储空间,并根据业务需求设置合适的访问权限和地域属性。获取Bucket的相关信息,包括Endpoint、AccessKey ID 和 AccessKey Secret,这是后续与OSS交互的重要凭证。肆、集成阿里云OSS SDK在若依系统的后端开发环境中,通过引入阿里云OSS SDK的依赖包:在根目录的pom.xml的properties配置阿里云OSS的版本 <properties> <aliyun-oss.version>3.17.4</aliyun-oss.version> </properties>在dependencyManagement配置阿里云 OSS依赖 <!--阿里云--> <dependency> <groupId>com.aliyun.oss</groupId> <artifactId>aliyun-sdk-oss</artifactId> <version>${aliyun-oss.version}</version> </dependency>接着,在项目的配置文件(若依是在admin工程的的resources文件夹中)中添加OSS相关的连接信息:Yaml# application.yml 示例 aliyun: endpoint: 'your-endpoint' endpointInternal: 'your-endpoint-internal' accessKeyId: 'your-access-key-id' accessKeySecret: 'your-access-key-secret' bucketName: 'your-bucket-name' urlPrefix: 'your-domain' urlPrefixInternal: 'https://' + 'your-endpoint-internal'解释一下上面几个配置的含义endpoint创建阿里云Bucket时提供的外网地域节点,使用这个endpoint实现文件的上传endpointInternal创建阿里云Bucket时提供的内网地域节点,为了节约费用,我们ECS跟OSS买的是同一个地域的,这样通过内网下载OSS的文件是不收取费用的,把文件通过内网备份到ECS后,我们可以在空闲的时候,将备份的文件,通过ECS下载到本地accessKeyId、accessKeySecret是您访问阿里云API的密钥,具有该账户完全的权限,这个可以在账户下的AccessKey管理查看bucketName这个是我们创建的Bucket名称伍、配置参数为了方便读取application.yml的配置参数,我们创建一个配置类并完成OSS初始化AliyunConfig.java@Configuration @ConfigurationProperties(prefix = "aliyun") @Data public class AliyunConfig { /** * 外网endpoint */ private String endpoint; /** * 内网endpoint */ private String endpointInternal; /** * key */ private String accessKeyId; /** * 密钥 */ private String accessKeySecret; /** * 空间名称 */ private String bucketName; /** * 外网Url前缀 */ private String urlPrefix; /** * 内网Url前缀 */ private String urlPrefixInternal; @Bean public OSS oSSClient() { return new OSSClient(endpoint, accessKeyId, accessKeySecret); } }陆、编写文件上传类第三步:编写文件上传逻辑在后端服务中创建一个专门处理文件上传的服务类或工具类,利用OSS SDK提供的API实现文件上传功能:AliyunFileUploadService.java@Component @Slf4j public class AliyunFileUploadService { /** * 默认大小 50M */ public static final long DEFAULT_MAX_SIZE = 50 * 1024 * 1024; @Resource private OSS ossClient; @Resource private AliyunConfig aliyunConfig; /** * 阿里云文件上传 * * @param file 上传的文件 * @param ownerDirectory 目录 */ public String upload(MultipartFile file, String ownerDirectory) throws InvalidExtensionException, IOException { //文件新路径 String originalFilename = file.getOriginalFilename(); // 校验格式、大小等 boolean isLegal = false; assertAllowed(file, MimeTypeUtils.DEFAULT_ALLOWED_EXTENSION); String filePath = getFilePath(file, ownerDirectory); // 上传到阿里云 ossClient.putObject(aliyunConfig.getBucketName(), filePath, new ByteArrayInputStream(file.getBytes())); //this.aliyunConfig.getUrlPrefix() + filePath 文件路径需要保持数据库 return aliyunConfig.getUrlPrefix() + filePath; } /** * 生成文件路径 * * @param file 文件 * @param ownerDirectory 自定义目录 * @return 生成的文件目录 */ private String getFilePath(MultipartFile file, String ownerDirectory) { String fileName; String extension = getExtension(file); if (!StringUtils.isEmpty(ownerDirectory)) { fileName = ownerDirectory + "/" + IdUtils.fastUUID() + "." + extension; } else { fileName = DateUtils.datePath() + "/" + IdUtils.fastUUID() + "." + extension; } return fileName; } /** * 查看文件列表 * * @return 对象信息 */ public List<OSSObjectSummary> list() { // 设置最大个数。 final int maxKeys = 200; // 列举文件。 ObjectListing objectListing = ossClient.listObjects(new ListObjectsRequest(aliyunConfig.getBucketName()).withMaxKeys(maxKeys)); List<OSSObjectSummary> sums = objectListing.getObjectSummaries(); return sums; } /** * 删除文件 * * @param objectName 文件名 * @return 结果 */ public boolean delete(String objectName) { //如果文件路径是OSS的,截取后删除,否则不处理,直接返回成功 if (objectName != null && objectName.contains(aliyunConfig.getUrlPrefix())) { objectName = objectName.replace(aliyunConfig.getUrlPrefix(), ""); ossClient.deleteObject(aliyunConfig.getBucketName(), objectName); return true; } return true; } /** * 下载文件下载文件 * * @param objectName 数据库存储的文件路径 */ public void exportOssFile(String objectName) throws IOException { // ossObject包含文件所在的存储空间名称、文件名称、文件元信息以及一个输入流。 if (objectName != null && objectName.contains(aliyunConfig.getUrlPrefix())) { objectName = objectName.replace(aliyunConfig.getUrlPrefix(), ""); // 创建OSSClient实例。 OSS ossClientLocal = new OSSClientBuilder().build(aliyunConfig.getEndpointInternal(), aliyunConfig.getAccessKeyId(), aliyunConfig.getAccessKeySecret()); try { File file = getAbsoluteFile(objectName); ossClientLocal.getObject(new GetObjectRequest(aliyunConfig.getBucketName(), objectName), file); }catch (Exception exception){ throw new CustomException(exception.getMessage()); }finally { if (ossClientLocal != null) { ossClientLocal.shutdown(); } } } } /** * 文件大小校验 * * @param file 上传的文件 * @throws FileSizeLimitExceededException 如果超出最大大小 */ public void assertAllowed(MultipartFile file, String[] allowedExtension) throws FileSizeLimitExceededException, InvalidExtensionException { long size = file.getSize(); if (size > DEFAULT_MAX_SIZE) { throw new FileSizeLimitExceededException(DEFAULT_MAX_SIZE / 1024 / 1024); } String fileName = file.getOriginalFilename(); String extension = getExtension(file); if (allowedExtension != null && !isAllowedExtension(extension, allowedExtension)) { if (allowedExtension == MimeTypeUtils.IMAGE_EXTENSION) { throw new InvalidExtensionException.InvalidImageExtensionException(allowedExtension, extension, fileName); } else if (allowedExtension == MimeTypeUtils.FLASH_EXTENSION) { throw new InvalidExtensionException.InvalidFlashExtensionException(allowedExtension, extension, fileName); } else if (allowedExtension == MimeTypeUtils.MEDIA_EXTENSION) { throw new InvalidExtensionException.InvalidMediaExtensionException(allowedExtension, extension, fileName); } else { throw new InvalidExtensionException(allowedExtension, extension, fileName); } } } /** * 获取文件名的后缀 * * @param file 表单文件 * @return 后缀名 */ public String getExtension(MultipartFile file) { String extension = FilenameUtils.getExtension(file.getOriginalFilename()); if (StringUtils.isEmpty(extension)) { extension = MimeTypeUtils.getExtension(Objects.requireNonNull(file.getContentType())); } return extension; } /** * 判断MIME类型是否是允许的MIME类型 * * @param extension 扩展名 * @param allowedExtension 允许的扩展名 * @return true-允许;false-不允许 */ public boolean isAllowedExtension(String extension, String[] allowedExtension) { for (String str : allowedExtension) { if (str.equalsIgnoreCase(extension)) { return true; } } return false; } /** * 生成本地文件 * * @param fileName 文件名 * @return 文件 * @throws IOException 异常 */ private File getAbsoluteFile(String fileName) throws IOException { String uploadDir = LeeFrameConfig.getProfile(); File desc = new File(uploadDir + File.separator + fileName); if (!desc.getParentFile().exists()) { desc.getParentFile().mkdirs(); } if (!desc.exists()) { desc.createNewFile(); } return desc; } }柒、Controller public AjaxResult add(List<MultipartFile> imageFileList, @RequestParam("form") String form) throws IOException, InvalidExtensionException { }Controller通过MultipartFile接收前端传递的文件,然后调用服务层完成上传。捌、前端交互在若依系统的前端部分,当用户选择文件后,前端需将文件转换为二进制数据并通过Ajax或者其他HTTP请求方式发送给后端。后端接收到请求后,调用OSS服务进行文件上传并将返回的URL反馈至前端展示或保存至数据库。具体可以参考若依系统上传图片压缩 - 香草物语 (xiangcaowuyu.net)其他、安全与优化考量为了增强安全性,可以考虑使用STS临时访问凭证进行上传操作,防止关键密钥泄露。另外,如果希望提高文件访问速度,可以为Bucket开启CDN加速,并根据实际场景调整缓存策略。总结起来,通过上述步骤,我们成功实现了若依系统与阿里云OSS的集成,使得整个系统的文件存储和管理能力得到了显著提升。这一过程不仅展示了云存储服务的优势,也展现了若依系统良好的扩展性和兼容性,为企业级应用提供了更加灵活且高效的文件管理方案。
2024年03月26日
1,683 阅读
2 评论
0 点赞
2024-03-26
阿里云OSS+CDN:一记组合拳,轻松降低您的建站成本
在数字化时代,拥有一个高效、稳定且成本优化的网站对于企业的线上业务至关重要。如果你遇到跟我类似的业务,需要上传大量图片的场景,可以参考一下我是如何降低费用的。在企业网站部署时,硬盘及带宽是相对比较费钱的地方,为了降低ECS带宽及硬盘费用,我们可以采用OSS的方案,前端将图片压缩后,存储到OSS中,但是即便如此,如果我们图片比较多的话,OSS的外网流出费用也是相对比较贵的,相对比而言,CDN的费用要相对便宜很多。为了尽可能的降低费用,我么可以采用阿里云对象存储服务(OSS)与内容分发网络(CDN)的组合运用的“降本增效”组合拳,降低建站成本,提升网站性能。本文将深入探讨如何借助阿里云OSS+CDN实现这一目标。壹、OSS:低成本、高可用的对象存储服务存储成本优化阿里云OSS作为一款大规模、安全可靠的云存储服务,其计费模式灵活,仅按实际使用量付费,无需预先投入高昂的硬件购置和运维成本。同时,OSS提供多种存储类型(如标准存储、低频访问存储、归档存储等),用户可根据数据访问频率和生命周期选择最经济的存储方案,进一步节省成本。高扩展性与可用性随着网站数据量的增长,OSS能够无缝扩展存储空间,无需担心容量瓶颈。其服务可用性高达99.999999999%(12个9),确保数据随时可访问。此外,OSS支持多版本控制、跨区域复制等功能,为数据安全与业务连续性提供有力保障。贰、CDN:全球加速,提升用户体验内容分发,缩短访问延迟CDN通过在全球范围内部署节点,将网站内容缓存至距离用户最近的服务器,大大减少了数据传输的距离和时间,显著降低访问延迟,提升用户浏览体验。尤其对于具有大量静态资源(如图片、视频、CSS、JS文件等)的网站,效果尤为明显。流量成本控制通过CDN分发,大部分用户请求直接由CDN节点响应,减轻了源站压力,降低了回源带宽费用。同时,阿里云CDN采用阶梯计费方式,随着使用量增大,单位流量成本逐渐降低,有效控制了大流量场景下的成本支出。叁、OSS+CDN:双剑合璧,降本增效简化网站架构,降低运维成本将网站静态资源托管于OSS,并配合CDN进行分发,可以简化网站架构,减少服务器负担,降低运维复杂度。用户只需专注于核心业务逻辑开发与运营,无需过多关注存储扩容、数据备份、服务器维护等繁琐工作,从而节省人力及时间成本。实时同步,保证内容一致性OSS与CDN深度集成,支持自动刷新、预热等机制,确保CDN节点上的内容与OSS中的原始数据保持实时同步,避免因数据更新不及时导致的用户体验下降。数据分析,助力精细化运营阿里云CDN提供详尽的访问日志和数据分析功能,帮助用户了解用户行为、地域分布、热门资源等信息,为网站优化、精准营销提供数据支持,进一步提升运营效率。总结来说,阿里云OSS与CDN的组合使用,不仅从存储成本、访问速度、运维复杂度等多个维度降低了建站成本,还提升了网站性能与用户体验,为企业的线上业务发展提供了强大支撑。在数字化转型的浪潮中,善用云服务工具,尤其是如阿里云OSS+CDN这样的“降本增效”组合拳,无疑是企业实现低成本、高效率建站的明智之选。肆、实战1. 创建对象存储OSS当我们使用OSS+CDN的组合方式时,我们购买资源包时,只需要购买两个(1)标准存储放,用于放我们上传的文件(2)CDN回源流量包,用于CDN回源流量。对于这两个资源包,在OSS中是相对比较便宜的,我们不需要在购买昂贵的外网流出费用资源包。我们不介绍如何使用OSS,本文假设你已经创建了一个Bucket找到【权限控制】→【读写权限】,将Bucket ACL改成私有,之所以改成私有,我是基于这三个考虑(1)我使用阿里云的CDN,不会影像CDN回源(2)防止有人通过默认的域名恶意刷流量(3)防止产生外网流出费用。找到【Bucket配置】→【域名管理】,点击绑定域名,绑定我们自己的域名。配置完域名后,我们点击阿里云CDN加速,会自动跳转到阿里云CDN配置界面2.CDN配置CDN配置中,主要是要注意回源配置在配置源站信息时,我们选择OSS域名,在域名中,会自动列出我们已经创建的Bucket,我们选择对应的Bucket还记得我们上面在配置Bucket是设置的ACL是私有的,所以在CDN的回源配置中,我们需要勾选阿里云OSS私有Bucket回源3.使用最后需要注意的一点就是,我们在使用图片是,需要通过我们绑定的二级域名,不能使用阿里云OSS默认的域名,如果你没有配置Bucket ACL为私有,那么使用阿里云默认域名访问时,会产生外网流出费用,如果配置了配置Bucket ACL为私有,那么阿里云默认的域名是不能访问的,这也是为什么我们配置Bucket ACL为私有的一个原因。
2024年03月26日
707 阅读
0 评论
0 点赞
2024-03-24
Spring Boot Controller调用某个方法报Service注入为null
最近为了部署方便,尝试将项目的依赖与配置文件分开进行打包,可以参考Spring Boot分开打包依赖及配置文件 - 香草物语 (xiangcaowuyu.net)项目部署之后,试了一下,没有报错,但是后面在用的时候,有一个接口始终报空指针,通过日志分析,是服务层没有注入导致的。接口通过@Resource注入的 @Resource private ICarQuotationPriceHistoryService carQuotationPriceHistoryService;首先,既然别的接口都不存在问题,那么可以断定出现问题不是我们分打开打包依赖导致的。其次,在Idea中直接运行时,接口也不报错,说明方法本身不存在问题(姑且这么说吧),检查了配置、包名等地方,都没有发现问题。既然问题出现在这个方法,那说明肯定是这个方法出现了问题,检查了方法的注解、参数等,也都没发现问题,就在检查方法属性的时候,突然发现问题了,这个方法没有public,其他方法都是有pubic的,方法加上public后,问题解决其实这个地方,只是粗心大意了,忘记写public了。我们都知道,当一个方法没有修饰符时,默认就是default,default通常称为默认访问模式。该模式下,只允许在同一个包中进行访问。这也就为什么我们在不拆分依赖的时候,接口能正常访问,当我们拆分依赖后,因为我们这个是一个单独的模块(依赖),这个接口就无法访问了。通过这件事,得到了两个教训:1.做事不可粗心大意,像controller的方法,记得加public修饰符。2.遇到事情不要被表象迷惑,比如这种注入是null的,我们一般首先想到的是包名、扫描配置、注解上出现问题,往往不会考虑方法修饰符出现问题了。
2024年03月24日
605 阅读
0 评论
0 点赞
2024-03-24
Spring Boot分开打包依赖及配置文件
壹、为何要分开打包依赖Spring Boot默认会将依赖全部打包到一个jar中,这样导致的问题就是我们的一个jar往往很大。加之平时我们分模块开发,往往修改很小的一个部分,就需要打包整个jar包,上传整个jar到服务器。比如我用阿里云服务器,3M的带宽,如果我不拆分开依赖,仅仅是上传jar都需要耗时接近1分钟的时间。当然这样也有一些其他问题,比如我这种多模块的项目,如果我们修改了其他模块(非启动类所在模块),那么我们需要记得将打包的jar要放到依赖对应的文件夹中。贰、为何要分开打包配置文件相对于分开打包依赖,其实配置文件才是更有必要打包的。Spring Boot配置文件默认包裹在jar包中的形式,一方面容易造成配置文件的覆盖,另一方面修改配置文件也相对比较麻烦。叁、如何拆分打包依赖及配置文件Spring Boot分开打包依赖及配置文件的方法也比较简单,我们只需要修改pom.xml文件即可。只需要注意一点就是,如果我们是多模块的项目,需要修改主工程的pom.xml文件。添加一些配置属性,方便修改 <properties> <!--依赖输出目录--> <output.dependence.file.path>../output/lib/</output.dependence.file.path> <!--manifest中lib配置路径--> <manifest.classpath.prefix>lib</manifest.classpath.prefix> <!--jar输出目录--> <output.jar.file.path>../output/</output.jar.file.path> <!--配置文件输出目录--> <output.resource.file.path>../output/config/</output.resource.file.path> </properties>我这里实现的效果是把所有的文件都放到项目顶级的output文件夹中,项目的jar放到output中,依赖放到lib文件夹中,配置文件放到config文件夹中然后我们修改打包插件 <build> <plugins> <!-- 打JAR包,不包含依赖文件;显式剔除配置文件 --> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-jar-plugin</artifactId> <configuration> <outputDirectory>${output.jar.file.path}</outputDirectory> <!-- 将配置文件排除在jar包 --> <excludes> <exclude>*.properties</exclude> <exclude>*.yml</exclude> <exclude>*.xml</exclude> <exclude>*.txt</exclude> </excludes> <archive> <!-- 生成的jar中,包含pom.xml和pom.properties这两个文件 --> <addMavenDescriptor>true</addMavenDescriptor> <!-- 生成MANIFEST.MF的设置 --> <manifest> <!--这个属性特别关键,如果没有这个属性,有时候我们引用的包maven库 下面可能会有多个包,并且只有一个是正确的, 其余的可能是带时间戳的,此时会在classpath下面把那个带时间戳的给添加上去,然后我们 在依赖打包的时候, 打的是正确的,所以两头会对不上,报错。 --> <useUniqueVersions>false</useUniqueVersions> <!-- 为依赖包添加路径, 这些路径会写在MANIFEST文件的Class-Path下 --> <addClasspath>true</addClasspath> <!-- MANIFEST.MF 中 Class-Path 各个依赖加入前缀 --> <!--这个jar所依赖的jar包添加classPath的时候的前缀,需要 下面maven-dependency-plugin插件补充--> <!--一定要找对目录,否则jar找不到依赖lib--> <classpathPrefix>${manifest.classpath.prefix}</classpathPrefix> <!--指定jar启动入口类 --> <mainClass>net.xiangcaowuyu.LeeFrameApplication</mainClass> </manifest> <manifestEntries> <!-- 假如这个项目可能要引入一些外部资源,但是你打包的时候并不想把 这些资源文件打进包里面,这个时候你必须在 这边额外指定一些这些资源文件的路径,假如你的pom文件里面配置了 <scope>system</scope>,就是你依赖是你本地的 资源,这个时候使用这个插件,classPath里面是不会添加,所以你得手动把这个依赖添加进这个地方 --> <!--MANIFEST.MF 中 Class-Path 加入自定义路径,多个路径用空格隔开 --> <!--此处resources文件夹的内容,需要maven-resources-plugin插件补充上--> <Class-Path>${output.resource.file.path}</Class-Path> </manifestEntries> </archive> </configuration> </plugin> <!-- 复制依赖的jar包到指定的文件夹里 --> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-dependency-plugin</artifactId> <executions> <execution> <id>copy-dependencies</id> <phase>package</phase> <goals> <goal>copy-dependencies</goal> </goals> <configuration> <!-- 拷贝项目依赖包到指定目录下 --> <outputDirectory>${output.dependence.file.path}</outputDirectory> <!-- 是否排除间接依赖,间接依赖也要拷贝 --> <excludeTransitive>false</excludeTransitive> <!-- 是否带上版本号 --> <stripVersion>false</stripVersion> </configuration> </execution> </executions> </plugin> <!-- 用于复制指定的文件 --> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-resources-plugin</artifactId> <executions> <!-- 复制配置文件 --> <execution> <id>copy-resources</id> <phase>package</phase> <goals> <goal>copy-resources</goal> </goals> <configuration> <resources> <resource> <directory>src/main/resources</directory> <includes> <!--将如下格式配置文件拷贝--> <exclude>*.properties</exclude> <exclude>*.yml</exclude> <exclude>*.xml</exclude> <exclude>*.txt</exclude> </includes> </resource> </resources> <!--输出路径--> <outputDirectory>${output.resource.file.path}</outputDirectory> </configuration> </execution> </executions> </plugin> </plugins> <finalName>${project.artifactId}</finalName> </build>
2024年03月24日
951 阅读
0 评论
0 点赞
2024-03-23
Typora配置又拍云图片并设置文件上传路径
之前使用Typora配置过七牛云的存储,但是对于我这种几乎没有任何收益的小站,任何支出都是多余的,之所以用又拍云,主要是加入又拍云联盟后,每月能免费获取10GB存储空间外加15GB的流量,而且与阿里云存储不同,它这个15G的流量是不限制HTTP或者HTTPS请求的。但是又拍云也有其劣势,作为小众存储服务提供商,整体来讲使用起来不是很方便,功能也比较简单,在网站上,基本就能查看一下已经上传的文件,没法直接下载上传的文件,这块对于备份来说比较麻烦,如果不会调用其SDK需要考虑借助别人开发的工具。壹、创建又拍云存储服务登录又拍云管网,找到存储服务,点击创建服务服务名称我们输入有意义的名称就可以了应用场景因为我是存储博客照片,所以选择网页照片,按需选择就行了储存类型我选择标准类型,如果存储不常适用的文件,也可以选择低频访问类型地区选择这个只能选择国内加速,国际加速好像要收费,这个我没试过授权管理员这个我们一会配置PicGo的时候会用到,没有的话创建一个,记住用户名及密码,后面会用到以上信息输入完成后,完成创建服务。服务创建后,我们可以继续完善我们的服务,配置绑定域名及HTTPS等,这个不再这里介绍了。贰、安装PicGo我这里没有命令行的方式,是因为我需要适用PicGo的插件。因为博客适用的Typecho,他默认的文件上传路径为usr/upload/年/月的格式,为了保持一致,我也希望上传到图床的文件,保持这么一个格式。PicGo安装完成后,打开界面,找到图床设置下拉到最下面,找到又拍云,点击默认的打开下面的界面图床配置名这个不用动,默认Default即可,我们其实一般也就配置一个设定Bucket这个不要被名称迷惑,其实就是我们创建服务时的服务名设定操作员这个就是我们创建服务时创建的操作员设定管理员密码这个是我们创建的操作员密码设定加速域名这个是我们服务绑定的域名设定存储路径这个是我们文件上传的路径配置完成后,我们可以上传个文件测试一下配置是否正确叁、配置PicGo插件上面我们也说了,我们希望配置文件上传路径为usr/upload/年/月的格式以便与Typecho保持一致。但是PicGo本身是不支持配置这种格式的路径的,好比我们上面配置的路径为typecho/uploads/,因此我们需要借助插件实现这种文件路径。我这里借助picgo-plugin-super-prefix实现这种效果打开PicGo,找到插件设置,搜索picgo-plugin-super-prefix并安装安装完成后,点击插件设置安装如下进行配置安装可以上传一个图片,查看是否正确按照配置的路径上传文件。肆、配置TyporaTypora的配置就比较简单了,打开Typora,找到偏好设置,打开后,切换到图像页签插入图片时选择上传图片上传服务选择PicGo.app配置完成后,可以点击【验证图片上传选项】测试一下图片是否能上传成功。
2024年03月23日
659 阅读
0 评论
1 点赞
2024-03-22
若依系统上传图片压缩
虽然标题里面有若依,实际上所有的Vue项目都能够适用的。最近基于若依系统做点东西,若依系统本身封装了一个图片上传组件ImageUpload,但是这个组件对我来说不太适用,最主要的问题就是这个组件是自动上传的,这样就会导致我们业务数据跟附件无法做到同步,比如我们新增一个单据,点了上传图片,此时图片已经上传到服务器,但是我并没有保存单据,这样就造成了部分垃圾数据。当然不是说不能处理,只是来说更加麻烦。先来说下我们目前做的功能需求吧:基于ElementUI的el-upload上传图片为了减少图片占用的空间(目前基于阿里云OSS,这个后期介绍),我们在前端上传图片之前在不影响图片展示质量的前提下,对图片进行压缩表单数据与图片文件同步上传可以单张图片上传也可以同时上传多张图片(以下我们以多张图片上传来说明)壹、提取图片压缩公共方法在ruoyi.js公共方法中,封装图片压缩方法/** 图片压缩,默认同比例压缩 * @param {Object} fileObj * 图片对象 * 回调函数有一个参数,base64的字符串数据 */ export function compress(fileObj, callback) { // console.log('压缩前文件大小', fileObj.size) try { const image = new Image() image.src = URL.createObjectURL(fileObj) image.onload = function () { const that = this // 默认按比例压缩 let w = that.width let h = that.height const scale = w / h w = fileObj.width || w h = fileObj.height || (w / scale) let quality = 0.5 // 默认图片质量为0.7 // 生成canvas const canvas = document.createElement('canvas') const ctx = canvas.getContext('2d') // 创建属性节点 const anw = document.createAttribute('width') anw.nodeValue = w const anh = document.createAttribute('height') anh.nodeValue = h canvas.setAttributeNode(anw) canvas.setAttributeNode(anh) ctx.drawImage(that, 0, 0, w, h) // 图像质量 if (fileObj.quality && fileObj.quality <= 1 && fileObj.quality > 0) { quality = fileObj.quality } // quality值越小,所绘制出的图像越模糊 const data = canvas.toDataURL('image/jpeg', quality) // 压缩完成执行回调 const newFile = convertBase64UrlToBlob(data) callback(newFile) // console.log('压缩后文件信息', newFile) } } catch (e) { console.log('压缩失败!') } } // Base64 => 二进制(Blob) function convertBase64UrlToBlob(urlData) { // 去掉url的头,并转换为byte const bytes = window.atob(urlData.split(',')[1]) // 处理异常,将ascii码小于0的转换为大于0 const ab = new ArrayBuffer(bytes.length) const ia = new Uint8Array(ab) for (let i = 0; i < bytes.length; i++) { ia[i] = bytes.charCodeAt(i) } return new Blob([ab], {type: 'image/png'}) } 在main.js中挂载公共方法,方便在后面使用Vue.prototype.compress = compress贰、页面增加上传图片功能在表单中,增加上传图片功能<el-upload action="#" list-type="picture-card" :auto-upload="false" multiple :headers="headers" ref="carPhotoListRef" :on-preview="handlePictureCardPreview" :on-remove="handleRemove" :on-change="handleChange" :file-list="imageFileList" :disabled="form.inquiryStatus !== 'inquirying'" :class="{ hide: form.inquiryStatus !== 'inquirying' }" accept="image/bmp,image/jpg,image/png,image/svg,image/psd,image/webp,image/jpeg"> <i class="el-icon-plus"></i> </el-upload> <el-dialog :visible.sync="dialogImageVisible" :append-to-body="true"> <img width="100%" :src="dialogImageUrl" alt=""/> </el-dialog>因为我们要手工上传图片,所以需要对el-upload的属性进行配置action因为我们要收工上传,所以action需要设置成#auto-upload设置成false禁止自动上传headers是基于若依的认证,传递头部信息的on-preview预览图片on-remove删除图片同步处理我们后端绑定数据on-change图片改变的钩子,因为我们要手工上传图片,因此通过这个钩子完成图片的压缩其他的一些属性基本设置图片预览,限制上传图片类型的,按需设置即可。data里面主要设置图片预览、绑定图片列表等属性data() { return { headers: { Authorization: "Bearer " + getToken() }, //预览图片窗口是否可见 dialogImageVisible: false, //预览图片Url dialogImageUrl: null, imageFileList: [], } }method方法如下//删除图片 handleRemove(file, fileList) { this.imageFileList = fileList if (this.form.carPhotoList) { let index = -1 for (let i = 0; i < this.form.carPhotoList.length; i++) { if (this.form.carPhotoList[i].photoId === file.photoId) { index = i } } if (index !== -1) { this.$delete(this.form.carPhotoList, index) } } }, //图片改变 handleChange(file, fileList) { let that = this // 调用自定义的压缩方法 compress(file.raw, function (val) { // 图片格式: blob => file let newFile = new window.File([val], file.name, {type: file.raw.type}); // 新增属性(file)并赋值 let fileObj = {} fileObj.raw = newFile fileObj.name = file.name fileObj.uid = file.uid; fileObj.url = URL.createObjectURL(file.raw); that.imageFileList.push(fileObj) }) // this.imageFileList = fileList }, //预览图片 handlePictureCardPreview(file) { this.dialogImageUrl = file.url this.dialogImageVisible = true },实现图片压缩主要是通过on-change钩子,选择图片后,通过on-change钩子调用handleChange方法,我们拿到图片文件后,调用compress方法,通过回调函数,将压缩后的图片绑定到imageFileList表单保存/** 提交按钮 */ submitForm() { if (this.imageFileList.length <= 0) { this.msgInfo('请上传图片') return } const loading = this.$loading({ lock: true,//lock的修改符--默认是false text: "保存中",//显示在加载图标下方的加载文案 spinner: "el-icon-loading",//自定义加载图标类名 background: "rgba(0, 0, 0, 0.7)",//遮罩层颜色 target: document.querySelector("#table")//loadin覆盖的dom元素节点 }) this.$refs["form"].validate(valid => { if (valid) { let formData = new FormData() this.imageFileList.forEach(file => { formData.append("imageFileList", file.raw)// 图片列表 }) formData.append("form", JSON.stringify(this.form))//表单 if (this.form.inquiryId != null) { updateInquiry(formData).then(response => { loading.close() this.msgSuccess("修改成功") this.open = false this.getList() }).catch(error => { loading.close() }) } else { addInquiry(formData).then(response => { loading.close() this.msgSuccess("新增成功") this.open = false this.getList() }).catch(error => { loading.close() }) } } else { loading.close() } }) },在表单保存方法中,我们通过FormData将图片文件与表单数据,一起保存。
2024年03月22日
560 阅读
0 评论
0 点赞
1
2
3
4
...
48