首页
留言
导航
统计
Search
1
追番推荐!免费看动漫的网站 - 支持在线观看和磁力下载
3,120 阅读
2
推荐31个docker应用,每一个都很实用
1,492 阅读
3
PVE自动启动 虚拟机 | 容器 顺序设置及参数说明
1,047 阅读
4
一条命令,永久激活!Office 2024!
678 阅读
5
优选 Cloudflare 官方 / 中转 IP
538 阅读
默认分类
服务器
宝塔
VPS
Docker
OpenWRT
Nginx
群晖
前端编程
Vue
React
Angular
NodeJS
uni-app
后端编程
Java
Python
SpringBoot
SpringCloud
流程引擎
检索引擎
Linux
CentOS
Ubuntu
Debian
数据库
Redis
MySQL
Oracle
虚拟机
VMware
VirtualBox
PVE
Hyper-V
计算机
网络技术
网站源码
主题模板
登录
Search
标签搜索
Java
小程序
Redis
SpringBoot
docker
Typecho
Cloudflare
uni-app
docker部署
虚拟机
WordPress
群晖
CentOS
Vue
Java类库
Linux命令
防火墙配置
Mysql
脚本
Nginx
微醺
累计撰写
266
篇文章
累计收到
11
条评论
首页
栏目
默认分类
服务器
宝塔
VPS
Docker
OpenWRT
Nginx
群晖
前端编程
Vue
React
Angular
NodeJS
uni-app
后端编程
Java
Python
SpringBoot
SpringCloud
流程引擎
检索引擎
Linux
CentOS
Ubuntu
Debian
数据库
Redis
MySQL
Oracle
虚拟机
VMware
VirtualBox
PVE
Hyper-V
计算机
网络技术
网站源码
主题模板
页面
留言
导航
统计
搜索到
266
篇与
的结果
2023-10-18
让你免费拥有一个无限大小的存储服务器【全过程分享】
前言前段时间玩了AList,突发奇想既然RaiDrive能把Alist映射到本地,那是不是能把映射盘部署到服务器呢?这样我就有了一个无限大小的存储了,既然写了这篇文章,说明还是可以的,这篇没什么技术含量,没有什么深层次解读,只说实现过程细节步骤,查的资料也比较多,只是觉得有趣记录一下,如果涉及到谁的原创内容我没说请私聊,我给你名字和链接加上(/doge 保命)话不多说,开搂步骤大纲使用Alist管理网盘使用RaiDrive把Alist映射到本地把映射盘部署到服务器一:Alist管理网盘Alist:相当于一个聚合网盘管理器,能实现网盘的统一管理,并能达到文件在线浏览的效果AList 开源项目地址: https://github.com/alist-org/alist可以点击链接下载Alist Windows最新版:https://github.com/alist-org/alist/releases/download/v3.28.0/alist-windows-amd64.zip也可以在官网找自己喜欢的版本,但是注意新版本(V3 及更高版本)与 V2 不兼容下载完成后解压,在解压后的文件夹中打开cmd,运行 alist server 启动服务password is 后面是初始密码 start server 后面是 AList 的管理页面地址,IP(127.0.0.1/localhost) + 端口然后打开浏览器,输入链接127.0.0.1:5244 / localhost:5244输入账号(admin)密码(刚刚的初始密码)进入管理页面后可以点击右下角管理,修改初始密码,重新登录接下来添加需要管理的网盘,这里以百度网盘为例点击 管理 -> 存储 -> 添加驱动 选择百度网盘挂载路径 是根目录下的文件夹WebDav策略 推荐选择代理地址刷新令牌 为你百度网盘的 refresh_token ,官方文档里有获取方法:获取客户端ID与密匙 会自动获取,无需手动填写参数填写完毕后,直接点击添加 这就添加好了,接下来你就可以通过本机IP+端口访问网盘了如果要添加更多网盘,可以去 官方文档 查看,基本步骤都大差不差二:使用RaiDrive把alist映射到本地盘RaiDrive:将云存储和网络存储映射在本地磁盘 RaiDrive官网 :www.raidrive.com/ (点击Download下载)下载好后,打开 install 一直无脑下一步即可(注意安装位置,不建议安装到C盘)安装好后打开,点击 添加 服务类型 :选择 NAS —— WebDAV 虚拟驱动器 :前面是盘符,后面是名字,可以自行选择地址 :取消勾选,http后面填Alist的登陆地址,下面填/dav账户 :刚刚Alist登录的账号密码随后点击 连接 ,搞定此时在我的电脑里就多一个盘,打开之后就会显示Alist中管理的网盘注意:此处的磁盘大小为 7.99EB !!而 1EiB = 1,024 PiB = 1,048,576 TiB = 1,073,741,824 GiB 先别急着幻想,其实没有这么大,只不过是 没办法显示具体的大小,而随机生成的大小而已 /doge三:部署到服务器这里详细步骤我就不赘述了直接使用 Idea 新建一个web工程,配置Tomcat,默认 "Hello World" 出现之后(说明工程没有问题)在 Deployment 中添加映射路径选择刚刚添加的映射路径然后启动服务打开网页输入地址:localhost(ip):8080(Tomcat端口)/bd(Application context)至此,大功告成拓展啥?你问我这有啥用?既然都部署到服务器了,那还不是被咱玩弄于股掌之间以Android为例随便写个数据库 存放个文件路径,刚刚的web项目里 随便写个接口把路径返回,Android随便写个请求 从接口拿到数据,再随便找个播放器加载(顺便提一嘴,如果不行。。。那就是你写的太随便了/doge)注意事项还记得第一步,解压 Alist 的时候,启动了 alist server 的窗口吗,这个是在线的, 不能关闭!!! 关闭会导致Alist获取不到网盘资源,那么RaiDrive自然也就映射不到本地所以如果不想麻烦每次都手动启动,可以写个脚本实现:隐藏cmd窗口开机自启动 隐藏cmd窗口 :在 Alist解压文件夹 中新建一个 txt ,填入以下内容Set ws = CreateObject("Wscript.Shell") ws.run "D:\alist\alist.exe server",vbhide MsgBox "success"ws.run 修改为你 alist.exe 的路径保存之后将后缀改为 vbs 双击打开即可,弹出 success 说明启动成功这时你会发现,并没有cmd黑窗口弹出,打开alist管理页面,一切正常,🆗可以在任务管理器的进程中找到你启动的脚本,也可以在此处结束进程开机自启动 :将刚刚 vbs 文件,添加个快捷方式(注意!是快捷方式)打开运行( Win + R ),输入 shell:startup ,再将快捷方式拖入打开的文件夹里重启测试下,开机后等待success窗口弹出,大功告成!结语Alist加载会有延迟,对于过大的文件,有时亦会导致卡顿,对于宽带会有一定的要求,目前网上对Alist各网盘的优化和解决办法很多,大家可以根据自己的需求添加完善内网访问效果理想,若想在公网访问,无论是本地穿透,还是部署到云服务器,都需要通过服务器中转,转发之后具体能达到什么样的效果需要测试,如果有哪位大佬做出来了欢迎告知本篇没什么深层讲解,因此也没什么难度,按照步骤一步一步来即可,如果有问题随时提问,要是我哪里写错了,别客气,过来给我一巴掌,就行了(温柔点,别打肿,影响颜值就不好了/doge)。
2023年10月18日
51 阅读
0 评论
0 点赞
2023-10-16
Centos 7.9 离线安装 ORACLE 19C
本文涉及的安装包从以下百度网盘地址可获取:链接:https://pan.baidu.com/s/1XD_64B7awDjvkqcGXwWRqA提取码:42ow一、基础环境配置1、关闭系统防火墙systemctl stop firewalld systemctl disable firewalld2、关闭selinuxvim /etc/selinux/config SELINUX=disabled3、准备安装oracle安装所需依赖说明:下载安装oracle时所需依赖的rpm包,然后制作本地yum源进行安装rpm依赖包下载地址:https://pan.baidu.com/s/1zg6DUG0BTxY7H63lj78CrA 提取码:2ucbbase.zip放在/root路径下解压unzip base.zip创建离线yum源:备份原有repo文件 ,建个bakup文件夹把原路径下的文件放进去创建local.repo文件echo "[local]" > /etc/yum.repos.d/local.repo echo "name=local" >> /etc/yum.repos.d/local.repo echo "enable=1" >> /etc/yum.repos.d/local.repo echo "baseurl=file:///root/base" >> /etc/yum.repos.d/local.repo echo "gpgcheck=0" >> /etc/yum.repos.d/local.repoyum clean all4、安装oracle-database-preinstall 链接:https://pan.baidu.com/s/11TGhQ8H95umuV5PRUrfEcw 提取码:3yruyum -y localinstall oracle-database-preinstall-19c-1.0-1.el7.x86_64.rpm可能会出现如下报错:解决上面报错: 1.可以从以下网站缺少的依赖软件包:https://rpmfind.net/linux/rpm2html/search.php?query=kernel-headers&submit=Search+...&system=centos&arch=2.使用rpm命令手工安装缺少的依赖软件包rpm -i glibc-devel-2.17-317.el7.x86_64.rpm 二、安装Oracle 19c1、安装Oracle 19c 下载地址:https://www.oracle.com/database/technologies/oracle-database-software-downloads.htmlyum -y localinstall oracle-database-ee-19c-1.0-1.x86_64.rpm初始化Oracle数据库: 如需,可修改/etc/init.d/oracledb_ORCLCDB-19c,比如,CDB模式,实例ID等等。/etc/init.d/oracledb_ORCLCDB-19c configure配置环境变量: 切换用户su - oracle vim /home/oracle/.bash_profile加入以下内容:export ORACLE_HOME=/opt/oracle/product/19c/dbhome_1 export PATH=$PATH:/opt/oracle/product/19c/dbhome_1/bin export ORACLE_SID=ORCLCDB登陆oracle数据库(需要退出oracle用户重新登录):exit su - oracle sqlplus / as sysdba修改密码:alter user system identified by 123456;重新登录: ========================ORACLE19C的sqlnet.ora配置:/opt/oracle/product/19c/dbhome_1/network/adminNAMES.DIRECTORY_PATH= (TNSNAMES, ONAMES, HOSTNAME) SQLNET.AUTHENTICATION_SERVICES=(ALL) SQLNET.ALLOWED_LOGON_VERSION_SERVER=8 SQLNET.ALLOWED_LOGON_CLIENT=8 SQLNET.INBOUND_CONNECT_TIMEOUT=0 SQLNET.EXPIRE_TIME=10以上配置可解决兼容低版本客户端、客户端超时卡慢、dblink等问题,重启监听后生效lsnrctl stop lsnrctl start其中兼容低版本客户端的,在用户已经创建的情况下要再修改一次密码才能生效。表空间文件放置的文件夹需要对oracle用户授权:chown oracle:oinstall /data chown oracle:oinstall -R /data chmod 777 -R /data启动oraclelsnrctl start su - oracle sqlplus / as sysdba startup
2023年10月16日
29 阅读
0 评论
0 点赞
2023-10-05
2023最新 子比主题V6.9.2 开心版源码下载 WordPress主题 亲测可用
模板介绍Zibll子比主题专为阅读类网站开发,设计简约优雅、功能全面。UI界面模块化、多种布局、多种显示效果可选择,高度自由化,更容易搭配出自己喜欢的网站。支持付费阅读,付费下载,付费视频的支付功能和完善用户VIP会员系统加上强大的模块化编辑器工具,为站长提供有力的生产力。整体的开发理念都是围绕着阅读体验,减少花里胡哨的无用功能,把核心都集中在内容上。页面的布局、间距、功能都精心设计,只为让页面浏览更加自然,让用户更加易于阅读,让作者更加易于写作。安装教程将 zibll-V6.9.2.zip 主题压缩包上传到 \wp-content\themes\ 这个主题目录下面解压将 HttpRequest.php 上传到 \wp-content\themes\zibll\vendor\yurunsoft\yurun-http\src 目录下替换,然后点一键授权。下载{cloud title="子比主题V6.9.2 开心版" type="ct" url="https://www.123pan.com/s/shFiVv-C9WKA.html" password=""/}
2023年10月05日
31 阅读
0 评论
0 点赞
2023-10-05
二次元和穿纱雾网站错误404源码
一款简约的二次元纱雾404源码,喜欢的小伙伴可以来取哦!安装教程静态源码,直接上传到虚拟主机或服务器即可使用。源码演示下载{cloud title="二次元和穿纱雾网站错误404源码" type="ct" url="https://www.123pan.com/s/shFiVv-W9WKA.html" password=""/}
2023年10月05日
43 阅读
0 评论
0 点赞
2023-10-05
网站更换域名HTML源码
源码介绍美观大气域名更换自动跳转提示页面网址更换倒计时跳转中转页源码个性化域名跳转html单页源码安装教程静态源码,修改index.html中代码后,直接上传到虚拟主机或服务器即可使用。效果演示下载{cloud title="网站更换域名HTML源码" type="ct" url="https://www.123pan.com/s/shFiVv-s9WKA.html" password=""/}
2023年10月05日
42 阅读
0 评论
0 点赞
2023-10-05
彩虹外链网盘V5.5更新 支持批量封禁/优化加载速度
功能介绍彩虹外链网盘V5.5更新 支持批量封禁/优化加载速度彩虹外链网盘,是一款PHP网盘与外链分享程序,支持所有格式文件的上传,可以生成文件外链、图片外链、音乐视频外链,生成外链同时自动生成相应的UBB代码和HTML代码,还可支持文本、图片、音乐、视频在线预览,这不仅仅是一个网盘,更是一个图床亦或是音乐在线试听网站。新版本支持对接阿里云OSS、腾讯云COS、华为云OBS、又拍云、七牛云等云存储,同时增加了图片违规检测功能。更新记录更新记录:V5.5:1.后台支持批量封禁解封2.优化后台加载图片速度3.修复部分云存储下载中文名乱码源码演示下载{cloud title="彩虹外链网盘V5.5" type="ct" url="https://www.123pan.com/s/shFiVv-29WKA.html" password="nz2M"/}
2023年10月05日
33 阅读
0 评论
0 点赞
2023-10-05
WordPress微信壁纸小程序暗黑系列源码_支持流量主收益
源码介绍WordPress微信壁纸小程序壁纸小程序,可流量主收益,提供高清壁纸下载服务。该小程序基于Wordpress+酱茄二开插件进行开发,拥有美观漂亮的壁纸展示页面,可搜索喜欢的壁纸,下载壁纸需观看激励视频广告,看一次,可免费下载一天。此外,该小程序还提供热门壁纸榜单、壁纸收藏和分享功能。该小程序采用原生微信小程序代码开发,页面美观、操作简单。此外,该小程序是全开源版本,可自由修改和定制。要使用该小程序,需要在WordPress网站基础之上进行搭建,请使用干净的WordPress系统进行配合使用。源码演示下载{cloud title="WordPress微信壁纸小程序暗黑系列源码" type="ct" url="https://www.123pan.com/s/shFiVv-n9WKA.html" password="L0Ru"/}
2023年10月05日
51 阅读
0 评论
0 点赞
2023-10-05
不要再封装各种JAVA Util 工具类了,这个神级框架值得拥有!
今天给大家推荐一个非常好用的Java工具类库,企业级常用工具类,基本都有,能避免重复造轮子及节省大量的开发时间,非常不错,值得大家去了解使用。Hutool 谐音 “糊涂”,寓意追求 “万事都作糊涂观,无所谓失,无所谓得” 的境界。Hutool 是一个 Java 工具包,也只是一个工具包,它帮助我们简化每一行代码,减少每一个方法,让 Java 语言也可以 “甜甜的”。Hutool 最初是我项目中 “util” 包的一个整理,后来慢慢积累并加入更多非业务相关功能,并广泛学习其它开源项目精髓,经过自己整理修改,最终形成丰富的开源工具集。一、功能一个 Java 基础工具类,对文件、流、加密解密、转码、正则、线程、XML 等 JDK 方法进行封装,组成各种 Util 工具类,同时提供以下组件:tool-aop JDK 动态代理封装,提供非 IOC 下的切面支持hutool-bloomFilter 布隆过滤,提供一些 Hash 算法的布隆过滤hutool-cache 缓存hutool-core 核心,包括 Bean 操作、日期、各种 Util 等hutool-cron 定时任务模块,提供类 Crontab 表达式的定时任务hutool-crypto 加密解密模块hutool-db JDBC 封装后的数据操作,基于 ActiveRecord 思想hutool-dfa 基于 DFA 模型的多关键字查找hutool-extra 扩展模块,对第三方封装(模板引擎、邮件等)hutool-http 基于 HttpUrlConnection 的 Http 客户端封装hutool-log 自动识别日志实现的日志门面hutool-script 脚本执行封装,例如 Javascripthutool-setting 功能更强大的 Setting 配置文件和 Properties 封装hutool-system 系统参数调用封装(JVM 信息等)hutool-json JSON 实现hutool-captcha 图片验证码实现二、安装maven 项目在 pom.xml 添加以下依赖即可:<dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>4.6.3</version> </dependency>三、简单测试DateUtil 日期时间工具类,定义了一些常用的日期时间操作方法。//Date、long、Calendar之间的相互转换 //当前时间 Date date = DateUtil.date(); //Calendar转Date date = DateUtil.date(Calendar.getInstance()); //时间戳转Date date = DateUtil.date(System.currentTimeMillis()); //自动识别格式转换 String dateStr = "2017-03-01"; date = DateUtil.parse(dateStr); //自定义格式化转换 date = DateUtil.parse(dateStr, "yyyy-MM-dd"); //格式化输出日期 String format = DateUtil.format(date, "yyyy-MM-dd"); //获得年的部分 int year = DateUtil.year(date); //获得月份,从0开始计数 int month = DateUtil.month(date); //获取某天的开始、结束时间 Date beginOfDay = DateUtil.beginOfDay(date); Date endOfDay = DateUtil.endOfDay(date); //计算偏移后的日期时间 Date newDate = DateUtil.offset(date, DateField.DAY_OF_MONTH, 2); //计算日期时间之间的偏移量 long betweenDay = DateUtil.between(date, newDate, DateUnit.DAY);StrUtil 字符串工具类,定义了一些常用的字符串操作方法。//判断是否为空字符串 String str = "test"; StrUtil.isEmpty(str); StrUtil.isNotEmpty(str); //去除字符串的前后缀 StrUtil.removeSuffix("a.jpg", ".jpg"); StrUtil.removePrefix("a.jpg", "a."); //格式化字符串 String template = "这只是个占位符:{}"; String str2 = StrUtil.format(template, "我是占位符"); LOGGER.info("/strUtil format:{}", str2);NumberUtil 数字处理工具类,可用于各种类型数字的加减乘除操作及判断类型。double n1 = 1.234; double n2 = 1.234; double result; //对float、double、BigDecimal做加减乘除操作 result = NumberUtil.add(n1, n2); result = NumberUtil.sub(n1, n2); result = NumberUtil.mul(n1, n2); result = NumberUtil.div(n1, n2); //保留两位小数 BigDecimal roundNum = NumberUtil.round(n1, 2); String n3 = "1.234"; //判断是否为数字、整数、浮点数 NumberUtil.isNumber(n3); NumberUtil.isInteger(n3); NumberUtil.isDouble(n3); BeanUtil JavaBean的工具类,可用于Map与JavaBean对象的互相转换以及对象属性的拷贝。 PmsBrand brand = new PmsBrand(); brand.setId(1L); brand.setName("小米"); brand.setShowStatus(0); //Bean转Map Map<String, Object> map = BeanUtil.beanToMap(brand); LOGGER.info("beanUtil bean to map:{}", map); //Map转Bean PmsBrand mapBrand = BeanUtil.mapToBean(map, PmsBrand.class, false); LOGGER.info("beanUtil map to bean:{}", mapBrand); //Bean属性拷贝 PmsBrand copyBrand = new PmsBrand(); BeanUtil.copyProperties(brand, copyBrand); LOGGER.info("beanUtil copy properties:{}", copyBrand);MapUtil Map操作工具类,可用于创建Map对象及判断Map是否为空。//将多个键值对加入到Map中 Map<Object, Object> map = MapUtil.of(new String[][]{ {"key1", "value1"}, {"key2", "value2"}, {"key3", "value3"} }); //判断Map是否为空 MapUtil.isEmpty(map); MapUtil.isNotEmpty(map); AnnotationUtil 注解工具类,可用于获取注解与注解中指定的值。 //获取指定类、方法、字段、构造器上的注解列表 Annotation[] annotationList = AnnotationUtil.getAnnotations(HutoolController.class, false); LOGGER.info("annotationUtil annotations:{}", annotationList); //获取指定类型注解 Api api = AnnotationUtil.getAnnotation(HutoolController.class, Api.class); LOGGER.info("annotationUtil api value:{}", api.description()); //获取指定类型注解的值 Object annotationValue = AnnotationUtil.getAnnotationValue(HutoolController.class, RequestMapping.class);SecureUtil 加密解密工具类,可用于MD5加密。//MD5加密 String str = "123456"; String md5Str = SecureUtil.md5(str); LOGGER.info("secureUtil md5:{}", md5Str);CaptchaUtil 验证码工具类,可用于生成图形验证码。//生成验证码图片 LineCaptcha lineCaptcha = CaptchaUtil.createLineCaptcha(200, 100); try { request.getSession().setAttribute("CAPTCHA_KEY", lineCaptcha.getCode()); response.setContentType("image/png");//告诉浏览器输出内容为图片 response.setHeader("Pragma", "No-cache");//禁止浏览器缓存 response.setHeader("Cache-Control", "no-cache"); response.setDateHeader("Expire", 0); lineCaptcha.write(response.getOutputStream()); } catch (IOException e) { e.printStackTrace(); }Hutool中的工具类很多,可以参考官网:https://www.hutool.cn/当然,还有很多其他非常方便的方法,留着你自己去测试吧!使用Hutool工具,可以大大提高你的开发效率!
2023年10月05日
40 阅读
0 评论
0 点赞
2023-10-05
讲的太通透了,切面 AOP 优雅的实现权限校验!
1 理解AOP1.1 什么是AOPAOP(Aspect Oriented Programming) ,面向切面思想,是 Spring 的三大核心思想之一(两外两个:IOC-控制反转、DI-依赖注入)。那么 AOP 为何那么重要呢?在我们的程序中,经常存在一些系统性的需求,比如权限校验、日志记录、统计等,这些代码会散落穿插在各个业务逻辑中,非常冗余且不利于维护。例如下面这个示意图:有多少业务操作,就要写多少重复的校验和日志记录代码,这显然是无法接受的。当然,用面向对象的思想,我们可以把这些重复的代码抽离出来,写成公共方法,就是下面这样:这样,代码冗余和可维护性的问题得到了解决,但每个业务方法中依然要依次手动调用这些公共方法,也是略显繁琐。有没有更好的方式呢?有的,那就是AOP,AOP将权限校验、日志记录等非业务代码完全提取出来,与业务代码分离,并寻找节点切入业务代码中:1.2 AOP体系与概念简单地去理解,其实AOP要做三类事:在哪里切入,也就是权限校验等非业务操作在哪些业务代码中执行。在什么时候切入,是业务代码执行前还是执行后。切入后做什么事,比如做权限校验、日志记录等。因此,AOP的体系可以梳理为下图:一些概念详解:「Pointcut」:切点,决定处理如权限校验、日志记录等在何处切入业务代码中(即织入切面)。切点分为execution方式和annotation方式。前者可以用路径表达式指定哪些类织入切面,后者可以指定被哪些注解修饰的代码织入切面。「Advice」:处理,包括处理时机和处理内容。处理内容就是要做什么事,比如校验权限和记录日志。处理时机就是在什么时机执行处理内容,分为前置处理(即业务代码执行前)、后置处理(业务代码执行后)等。「Aspect」:切面,即Pointcut和Advice。「Joint point」:连接点,是程序执行的一个点。例如,一个方法的执行或者一个异常的处理。在 Spring AOP 中,一个连接点总是代表一个方法执行。「Weaving」:织入,就是通过动态代理,在目标对象方法中执行处理内容的过程。网络上有张图,我觉得非常传神,贴在这里供大家观详:2 AOP实例实践出真知,接下来我们就撸代码来实现一下AOP。完整项目已上传至:GitHub AOP demo项目,该项目是关于springboot的集成项目,AOP部分请关注【aop-demo】模块。使用 AOP,首先需要引入 AOP 的依赖。<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency>2.1 第一个实例接下来,我们先看一个极简的例子:所有的get请求被调用前在控制台输出一句"get请求的advice触发了"。具体实现如下:1.创建一个AOP切面类,只要在类上加个 @Aspect 注解即可。@Aspect 注解用来描述一个切面类,定义切面类的时候需要打上这个注解。@Component 注解将该类交给 Spring 来管理。在这个类里实现advice:package com.mu.demo.advice; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Pointcut; import org.springframework.stereotype.Component; @Aspect @Component public class LogAdvice { // 定义一个切点:所有被GetMapping注解修饰的方法会织入advice @Pointcut("@annotation(org.springframework.web.bind.annotation.GetMapping)") private void logAdvicePointcut() {} // Before表示logAdvice将在目标方法执行前执行 @Before("logAdvicePointcut()") public void logAdvice(){ // 这里只是一个示例,你可以写任何处理逻辑 System.out.println("get请求的advice触发了"); } }2.创建一个接口类,内部创建一个get请求:package com.mu.demo.controller; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import org.springframework.web.bind.annotation.*; @RestController @RequestMapping(value = "/aop") public class AopController { @GetMapping(value = "/getTest") public JSONObject aopTest() { return JSON.parseObject("{\"message\":\"SUCCESS\",\"code\":200}"); } @PostMapping(value = "/postTest") public JSONObject aopTest2(@RequestParam("id") String id) { return JSON.parseObject("{\"message\":\"SUCCESS\",\"code\":200}"); } }项目启动后,请求http://localhost:8085/aop/getTest接口:请求http://localhost:8085/aop/postTest接口,控制台无输出,证明切点确实是只针对被GetMapping修饰的方法。2.2 第二个实例下面我们将问题复杂化一些,该例的场景是:自定义一个注解PermissionsAnnotation创建一个切面类,切点设置为拦截所有标注PermissionsAnnotation的方法,截取到接口的参数,进行简单的权限校验将PermissionsAnnotation标注在测试接口类的测试接口test上具体的实现步骤:1.使用 @Target、@Retention、@Documented 自定义一个注解(关于这三个注解详情请见:元注解详解):@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface PermissionAnnotation{ }2.创建第一个AOP切面类,,只要在类上加个 @Aspect 注解即可。@Aspect 注解用来描述一个切面类,定义切面类的时候需要打上这个注解。 @Component 注解将该类交给 Spring 来管理。在这个类里实现第一步权限校验逻辑:package com.example.demo; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; @Aspect @Component @Order(1) public class PermissionFirstAdvice { // 定义一个切面,括号内写入第1步中自定义注解的路径 @Pointcut("@annotation(com.mu.demo.annotation.PermissionAnnotation)") private void permissionCheck() { } @Around("permissionCheck()") public Object permissionCheckFirst(ProceedingJoinPoint joinPoint) throws Throwable { System.out.println("===================第一个切面===================:" + System.currentTimeMillis()); //获取请求参数,详见接口类 Object[] objects = joinPoint.getArgs(); Long id = ((JSONObject) objects[0]).getLong("id"); String name = ((JSONObject) objects[0]).getString("name"); System.out.println("id1->>>>>>>>>>>>>>>>>>>>>>" + id); System.out.println("name1->>>>>>>>>>>>>>>>>>>>>>" + name); // id小于0则抛出非法id的异常 if (id < 0) { return JSON.parseObject("{\"message\":\"illegal id\",\"code\":403}"); } return joinPoint.proceed(); } }3.创建接口类,并在目标方法上标注自定义注解 PermissionsAnnotation :package com.example.demo; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import org.springframework.web.bind.annotation.*; @RestController @RequestMapping(value = "/permission") public class TestController { @RequestMapping(value = "/check", method = RequestMethod.POST) // 添加这个注解 @PermissionsAnnotation() public JSONObject getGroupList(@RequestBody JSONObject request) { return JSON.parseObject("{\"message\":\"SUCCESS\",\"code\":200}"); } }在这里,我们先进行一个测试。首先,填好请求地址和header:其次,构造正常的参数:可以拿到正常的响应结果:然后,构造一个异常参数,再次请求:响应结果显示,切面类进行了判断,并返回相应结果:有人会问,如果我一个接口想设置多个切面类进行校验怎么办?这些切面的执行顺序如何管理?很简单,一个自定义的AOP注解可以对应多个切面类,这些切面类执行顺序由@Order注解管理,该注解后的数字越小,所在切面类越先执行。下面在实例中进行演示:创建第二个AOP切面类,在这个类里实现第二步权限校验:package com.example.demo; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; @Aspect @Component @Order(0) public class PermissionSecondAdvice { @Pointcut("@annotation(com.mu.demo.annotation.PermissionAnnotation)") private void permissionCheck() { } @Around("permissionCheck()") public Object permissionCheckSecond(ProceedingJoinPoint joinPoint) throws Throwable { System.out.println("===================第二个切面===================:" + System.currentTimeMillis()); //获取请求参数,详见接口类 Object[] objects = joinPoint.getArgs(); Long id = ((JSONObject) objects[0]).getLong("id"); String name = ((JSONObject) objects[0]).getString("name"); System.out.println("id->>>>>>>>>>>>>>>>>>>>>>" + id); System.out.println("name->>>>>>>>>>>>>>>>>>>>>>" + name); // name不是管理员则抛出异常 if (!name.equals("admin")) { return JSON.parseObject("{\"message\":\"not admin\",\"code\":403}"); } return joinPoint.proceed(); } }重启项目,继续测试,构造两个参数都异常的情况:响应结果,表面第二个切面类执行顺序更靠前:3 AOP相关注解上面的案例中,用到了诸多注解,下面针对这些注解进行详解。3.1 @Pointcut@Pointcut 注解,用来定义一个切点,即上文中所关注的某件事情的入口,切入点定义了事件触发时机。@Aspect @Component public class LogAspectHandler { /** * 定义一个切面,拦截 com.mutest.controller 包和子包下的所有方法 */ @Pointcut("execution(* com.mutest.controller..*.*(..))") public void pointCut() {} }@Pointcut 注解指定一个切点,定义需要拦截的东西,这里介绍两个常用的表达式:一个是使用 execution() ,另一个是使用 annotation() 。execution表达式: 以 execution(* com.mutest.controller...(..))) 表达式为例:第一个 号的位置:表示返回值类型, 表示所有类型。包名:表示需要拦截的包名,后面的两个句点表示当前包和当前包的所有子包,在本例中指 com.mutest.controller包、子包下所有类的方法。第二个 号的位置:表示类名, 表示所有类。(..):这个星号表示方法名, 表示所有的方法,后面括弧里面表示方法的参数,两个句点表示任何参数。annotation() 表达式: annotation() 方式是针对某个注解来定义切点,比如我们对具有 @PostMapping 注解的方法做切面,可以如下定义切面:@Pointcut("@annotation(org.springframework.web.bind.annotation.PostMapping)") public void annotationPointcut() {}然后使用该切面的话,就会切入注解是 @PostMapping 的所有方法。这种方式很适合处理 @GetMapping、@PostMapping、@DeleteMapping 不同注解有各种特定处理逻辑的场景。还有就是如上面案例所示,针对自定义注解来定义切面。@Pointcut("@annotation(com.example.demo.PermissionsAnnotation)") private void permissionCheck() {}3.2 @Around@Around 注解用于修饰Around增强处理,Around增强处理非常强大,表现在:@Around可以自由选择增强动作与目标方法的执行顺序,也就是说可以在增强动作前后,甚至过程中执行目标方法。这个特性的实现在于,调用ProceedingJoinPoint参数的procedd()方法才会执行目标方法。@Around可以改变执行目标方法的参数值,也可以改变执行目标方法之后的返回值。Around增强处理有以下特点:当定义一个Around增强处理方法时,该方法的第一个形参必须是 ProceedingJoinPoint 类型(至少一个形参)。在增强处理方法体内,调用ProceedingJoinPoint的proceed方法才会执行目标方法:这就是@Around增强处理可以完全控制目标方法执行时机、如何执行的关键;如果程序没有调用ProceedingJoinPoint的proceed方法,则目标方法不会执行。调用ProceedingJoinPoint的proceed方法时,还可以传入一个Object[ ]对象,该数组中的值将被传入目标方法作为实参——这就是Around增强处理方法可以改变目标方法参数值的关键。这就是如果传入的Object[ ]数组长度与目标方法所需要的参数个数不相等,或者Object[ ]数组元素与目标方法所需参数的类型不匹配,程序就会出现异常。@Around功能虽然强大,但通常需要在线程安全的环境下使用。因此,如果使用普通的Before、AfterReturning就能解决的问题,就没有必要使用Around了。如果需要目标方法执行之前和之后共享某种状态数据,则应该考虑使用Around。尤其是需要使用增强处理阻止目标的执行,或需要改变目标方法的返回值时,则只能使用Around增强处理了。下面,在前面例子上做一些改造,来观察@Around的特点。自定义注解类不变。首先,定义接口类:package com.example.demo; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import org.springframework.web.bind.annotation.*; @RestController @RequestMapping(value = "/permission") public class TestController { @RequestMapping(value = "/check", method = RequestMethod.POST) @PermissionsAnnotation() public JSONObject getGroupList(@RequestBody JSONObject request) { return JSON.parseObject("{\"message\":\"SUCCESS\",\"code\":200,\"data\":" + request + "}"); } }唯一切面类(前面案例有两个切面类,这里只需保留一个即可):package com.example.demo; import com.alibaba.fastjson.JSONObject; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; @Aspect @Component @Order(1) public class PermissionAdvice { @Pointcut("@annotation(com.example.demo.PermissionsAnnotation)") private void permissionCheck() { } @Around("permissionCheck()") public Object permissionCheck(ProceedingJoinPoint joinPoint) throws Throwable { System.out.println("===================开始增强处理==================="); //获取请求参数,详见接口类 Object[] objects = joinPoint.getArgs(); Long id = ((JSONObject) objects[0]).getLong("id"); String name = ((JSONObject) objects[0]).getString("name"); System.out.println("id1->>>>>>>>>>>>>>>>>>>>>>" + id); System.out.println("name1->>>>>>>>>>>>>>>>>>>>>>" + name); // 修改入参 JSONObject object = new JSONObject(); object.put("id", 8); object.put("name", "lisi"); objects[0] = object; // 将修改后的参数传入 return joinPoint.proceed(objects); } }同样使用JMeter调用接口,传入参数:{"id":-5,"name":"admin"},响应结果表明:@Around截取到了接口的入参,并使接口返回了切面类中的结果。3.3 @Before@Before 注解指定的方法在切面切入目标方法之前执行,可以做一些 Log 处理,也可以做一些信息的统计,比如获取用户的请求 URL 以及用户的 IP 地址等等,这个在做个人站点的时候都能用得到,都是常用的方法。例如下面代码:@Aspect @Component @Slf4j public class LogAspectHandler { /** * 在上面定义的切面方法之前执行该方法 * @param joinPoint jointPoint */ @Pointcut("execution(* com.mutest.controller..*.*(..))") public void pointCut() {} @Before("pointCut()") public void doBefore(JoinPoint joinPoint) { log.info("====doBefore方法进入了===="); // 获取签名 Signature signature = joinPoint.getSignature(); // 获取切入的包名 String declaringTypeName = signature.getDeclaringTypeName(); // 获取即将执行的方法名 String funcName = signature.getName(); log.info("即将执行方法为: {},属于{}包", funcName, declaringTypeName); // 也可以用来记录一些信息,比如获取请求的 URL 和 IP ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); HttpServletRequest request = attributes.getRequest(); // 获取请求 URL String url = request.getRequestURL().toString(); // 获取请求 IP String ip = request.getRemoteAddr(); log.info("用户请求的url为:{},ip地址为:{}", url, ip); } }JointPoint 对象很有用,可以用它来获取一个签名,利用签名可以获取请求的包名、方法名,包括参数(通过 joinPoint.getArgs() 获取)等。3.4 @After@After 注解和 @Before 注解相对应,指定的方法在切面切入目标方法之后执行,也可以做一些完成某方法之后的 Log 处理。@Aspect @Component @Slf4j public class LogAspectHandler { /** * 定义一个切面,拦截 com.mutest.controller 包下的所有方法 */ @Pointcut("execution(* com.mutest.controller..*.*(..))") public void pointCut() {} /** * 在上面定义的切面方法之后执行该方法 * @param joinPoint jointPoint */ @After("pointCut()") public void doAfter(JoinPoint joinPoint) { log.info("==== doAfter 方法进入了===="); Signature signature = joinPoint.getSignature(); String method = signature.getName(); log.info("方法{}已经执行完", method); } }到这里,我们来写个 Controller 测试一下执行结果,新建一个 AopController 如下:@RestController @RequestMapping("/aop") public class AopController { @GetMapping("/{name}") public String testAop(@PathVariable String name) { return "Hello " + name; } }启动项目,在浏览器中输入:localhost:8080/aop/csdn,观察一下控制台的输出信息:====doBefore 方法进入了==== 即将执行方法为: testAop,属于com.itcodai.mutest.AopController包 用户请求的 url 为:http://localhost:8080/aop/name,ip地址为:0:0:0:0:0:0:0:1 ==== doAfter 方法进入了==== 方法 testAop 已经执行完从打印出来的 Log 中可以看出程序执行的逻辑与顺序,可以很直观的掌握 @Before 和 @After 两个注解的实际作用。3.5 @AfterReturning@AfterReturning 注解和 @After 有些类似,区别在于 @AfterReturning 注解可以用来捕获切入方法执行完之后的返回值,对返回值进行业务逻辑上的增强处理,例如:@Aspect @Component @Slf4j public class LogAspectHandler { /** * 在上面定义的切面方法返回后执行该方法,可以捕获返回对象或者对返回对象进行增强 * @param joinPoint joinPoint * @param result result */ @AfterReturning(pointcut = "pointCut()", returning = "result") public void doAfterReturning(JoinPoint joinPoint, Object result) { Signature signature = joinPoint.getSignature(); String classMethod = signature.getName(); log.info("方法{}执行完毕,返回参数为:{}", classMethod, result); // 实际项目中可以根据业务做具体的返回值增强 log.info("对返回参数进行业务上的增强:{}", result + "增强版"); } }需要注意的是,在 @AfterReturning 注解 中,属性 returning 的值必须要和参数保持一致,否则会检测不到。该方法中的第二个入参就是被切方法的返回值,在 doAfterReturning 方法中可以对返回值进行增强,可以根据业务需要做相应的封装。我们重启一下服务,再测试一下:方法 testAop 执行完毕,返回参数为:Hello CSDN 对返回参数进行业务上的增强:Hello CSDN 增强版3.6 @AfterThrowing当被切方法执行过程中抛出异常时,会进入 @AfterThrowing 注解的方法中执行,在该方法中可以做一些异常的处理逻辑。要注意的是 throwing 属性的值必须要和参数一致,否则会报错。该方法中的第二个入参即为抛出的异常。@Aspect @Component @Slf4j public class LogAspectHandler { /** * 在上面定义的切面方法执行抛异常时,执行该方法 * @param joinPoint jointPoint * @param ex ex */ @AfterThrowing(pointcut = "pointCut()", throwing = "ex") public void afterThrowing(JoinPoint joinPoint, Throwable ex) { Signature signature = joinPoint.getSignature(); String method = signature.getName(); // 处理异常的逻辑 log.info("执行方法{}出错,异常为:{}", method, ex); } }完整的项目已上传至Gtihub:https://github.com/ThinkMugz/aopDemo以上就是AOP的全部内容。通过几个例子就可以感受到,AOP的便捷之处。欢迎大家指出文中不足之处。( •̀ ω •́ )y
2023年10月05日
25 阅读
0 评论
0 点赞
2023-09-28
Java8 Stream 一行代码实现数据分组统计、排序、最大值、最小值、平均值、总数、合计
示例:统计用户status的最大值,最小值,求和,平均值分组统计:如果我们想看某个部门下面有哪些数据,可以如下代码求最大值,最小值对某个字段求最大,最小,求和,统计,计数求最大值,最小值还可以这样做对某个字段求和并汇总求某个字段的平均值拼接某个字段的值,可以设置前缀,后缀或者分隔符根据部门进行分组,并获取汇总人数根据部门和是否退休进行分组,并汇总人数根据部门和是否退休进行分组,并取得每组中年龄最大的人Java8 对数据处理可谓十分流畅,既不改变数据,又能对数据进行很好的处理,今天给大家演示下,用Java8的 Stream 如何对数据进行分组统计,排序,求和等这些方法属于Java8的汇总统计类:getAverage(): 它返回所有接受值的平均值。getCount(): 它计算所有元素的总数。getMax(): 它返回最大值。getMin(): 它返回最小值。getSum(): 它返回所有元素的总和。示例:统计用户status的最大值,最小值,求和,平均值看官可以根据自己的需求进行灵活变通@GetMapping("/list") public void list(){ List<InputForm> inputForms = inputFormMapper.selectList(); Map<String, IntSummaryStatistics> collect = inputForms.stream() .collect(Collectors.groupingBy(InputForm::getCreateUserName, Collectors.summarizingInt(InputForm::getStatus))); // 对名字去重 Set<String> collect1 = inputForms.stream().distinct().map(InputForm::getCreateUserName).collect(Collectors.toSet()); // 遍历名字,从map中取出对应用户的status最大值,最小值,平均值。。。 for (String s1 : collect1) { IntSummaryStatistics statistics1 = collect.get(s1); System.out.println("第一个用户的名字为====" + s1); System.out.println("**********************************************"); System.out.println("status的个数为===" + statistics1.getCount()); System.out.println("status的最小值为===" + statistics1.getMin()); System.out.println("status的求和为===" + statistics1.getSum()); System.out.println("status的平均值为===" + statistics1.getAverage()); System.out.println(); System.out.println(); } }结果如下:分组统计:@GetMapping("/list") public void list(){ List<InputForm> inputForms = inputFormMapper.selectList(); System.out.println("inputForms = " + inputForms); Map<String, Long> collect = inputForms.stream().collect(Collectors.groupingBy(InputForm::getCreateUserName, Collectors.counting())); System.out.println("collect = " + collect); }❝其中Collectors.groupingBy(InputForm::getCreateUserName, Collectors.counting())返回的是一个Map集合,InputForm::getCreateUserName代表key,Collectors.counting()代表value,我是按照创建人的姓名进行统计❞可以看到总共有九条数据,其中莫昀锦有两个,周亚丽有七个如果我们想看某个部门下面有哪些数据,可以如下代码@GetMapping("/list") public Map<String, List<InputForm>> list(){ List<InputForm> inputForms = inputFormMapper.selectList(); System.out.println("inputForms = " + inputForms); Map<String, List<InputForm>> collect = inputForms.stream() .collect(Collectors.groupingBy(InputForm::getCreateCompanyName)); return collect; }求最大值,最小值@GetMapping("/list") public Map<String, List<InputForm>> list(){ List<InputForm> inputForms = inputFormMapper.selectList(); System.out.println("inputForms = " + inputForms); Optional<InputForm> min = inputForms.stream() .min(Comparator.comparing(InputForm::getId)); System.out.println("min = " + min); return null; }可以看到此id是最小的,最大值雷同对某个字段求最大,最小,求和,统计,计数@GetMapping("/list") public void list(){ List<InputForm> inputForms = inputFormMapper.selectList(); System.out.println("inputForms = " + inputForms); IntSummaryStatistics collect = inputForms.stream() .collect(Collectors.summarizingInt(InputForm::getStatus)); double average = collect.getAverage(); int max = collect.getMax(); int min = collect.getMin(); long sum = collect.getSum(); long count = collect.getCount(); System.out.println("collect = " + collect); }求最大值,最小值还可以这样做// 求最大值 Optional<InputForm> max = inputForms.stream().max(Comparator.comparing(InputForm::getAgency)); if (max.isPresent()){ System.out.println("max = " + max); } // 求最小值 Optional<InputForm> min = inputForms.stream().min(Comparator.comparing(InputForm::getAgency)); if (min.isPresent()){ System.out.println("min = " + min); }对某个字段求和并汇总int sum = inputForms.stream().mapToInt(InputForm::getStatus).sum(); System.out.println("sum = " + sum);求某个字段的平均值// 求某个字段的平均值 Double collect2 = inputForms.stream().collect(Collectors.averagingInt(InputForm::getStatus)); System.out.println("collect2 = " + collect2); // 简化后 OptionalDouble average = inputForms.stream().mapToDouble(InputForm::getStatus).average(); if (average.isPresent()){ System.out.println("average = " + average); }拼接某个字段的值,可以设置前缀,后缀或者分隔符// 拼接某个字段的值,用逗号分隔,并设置前缀和后缀 String collect3 = inputForms.stream().map(InputForm::getCreateUserName).collect(Collectors.joining(",", "我是前缀", "我是后缀")); System.out.println("collect3 = " + collect3);根据部门进行分组,并获取汇总人数// 根据部门进行汇总,并获取汇总人数 Map<String, Long> collect4 = inputForms.stream().collect(Collectors.groupingBy(InputForm::getCreateDeptName, Collectors.counting())); System.out.println("collect4 = " + collect4);根据部门和是否退休进行分组,并汇总人数// 根据部门和是否退休进行分组,并汇总人数 Map<String, Map<Integer, Long>> collect5 = inputForms.stream().collect(Collectors.groupingBy(InputForm::getCreateDeptName, Collectors.groupingBy(InputForm::getIsDelete, Collectors.counting()))); System.out.println("collect5 = " + collect5);根据部门和是否退休进行分组,并取得每组中年龄最大的人// 根据部门和是否退休进行分组,并取得每组中年龄最大的人 Map<String, Map<Integer, InputForm>> collect6 = inputForms.stream().collect( Collectors.groupingBy(InputForm::getCreateDeptName, Collectors.groupingBy(InputForm::getIsDelete, Collectors.collectingAndThen( Collectors.maxBy( Comparator.comparing(InputForm::getAge)), Optional::get)))); System.out.println("collect6 = " + collect6);
2023年09月28日
16 阅读
0 评论
0 点赞
1
...
9
10
11
...
27