首页
留言
导航
统计
Search
1
追番推荐!免费看动漫的网站 - 支持在线观看和磁力下载
2,520 阅读
2
推荐31个docker应用,每一个都很实用
1,314 阅读
3
PVE自动启动 虚拟机 | 容器 顺序设置及参数说明
935 阅读
4
一条命令,永久激活!Office 2024!
618 阅读
5
优选 Cloudflare 官方 / 中转 IP
491 阅读
默认分类
服务器
宝塔
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
docker部署
虚拟机
WordPress
群晖
uni-app
CentOS
Vue
Java类库
Linux命令
防火墙配置
Mysql
脚本
Nginx
微醺
累计撰写
264
篇文章
累计收到
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
计算机
网络技术
网站源码
主题模板
页面
留言
导航
统计
搜索到
264
篇与
的结果
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日
26 阅读
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日
30 阅读
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日
27 阅读
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日
26 阅读
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日
43 阅读
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日
29 阅读
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日
19 阅读
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日
15 阅读
0 评论
0 点赞
2023-09-26
SpringBoot 玩一玩代码混淆,防止反编译代码泄露
编译简单就是把代码跑一哈,然后我们的代码 .java文件 就被编译成了 .class 文件反编译就是针对编译生成的 jar/war 包 里面的 .class 文件 逆向还原回来,可以看到你的代码写的啥。比较常用的反编译工具 JD-GUI ,直接把编译好的jar丢进去,大部分都能反编译看到源码:那如果不想给别人反编译看自己写的代码呢?怎么做?混淆该篇玩的代码混淆 ,是其中一种手段。我给你看,但你反编译看到的不是真正的代码。先看一张效果示例图 :开整正文先看一下我们混淆一个项目代码,要做啥?一共就两步第一步, 在项目路径下,新增一份文件 proguard.cfg :proguard.cfg#指定Java的版本 -target 1.8 #proguard会对代码进行优化压缩,他会删除从未使用的类或者类成员变量等 -dontshrink #是否关闭字节码级别的优化,如果不开启则设置如下配置 -dontoptimize #混淆时不生成大小写混合的类名,默认是可以大小写混合 -dontusemixedcaseclassnames # 对于类成员的命名的混淆采取唯一策略 -useuniqueclassmembernames #混淆时不生成大小写混合的类名,默认是可以大小写混合 -dontusemixedcaseclassnames #混淆类名之后,对使用Class.forName('className')之类的地方进行相应替代 -adaptclassstrings #对异常、注解信息予以保留 -keepattributes Exceptions,InnerClasses,Signature,Deprecated,SourceFile,LineNumberTable,*Annotation*,EnclosingMethod # 此选项将保存接口中的所有原始名称(不混淆)--> -keepnames interface ** { *; } # 此选项将保存所有软件包中的所有原始接口文件(不进行混淆) #-keep interface * extends * { *; } #保留参数名,因为控制器,或者Mybatis等接口的参数如果混淆会导致无法接受参数,xml文件找不到参数 -keepparameternames # 保留枚举成员及方法 -keepclassmembers enum * { *; } # 不混淆所有类,保存原始定义的注释- -keepclassmembers class * { @org.springframework.context.annotation.Bean *; @org.springframework.beans.factory.annotation.Autowired *; @org.springframework.beans.factory.annotation.Value *; @org.springframework.stereotype.Service *; @org.springframework.stereotype.Component *; } #忽略warn消息 -ignorewarnings #忽略note消息 -dontnote #打印配置信息 -printconfiguration -keep public class com.example.myproguarddemo.MyproguarddemoApplication { public static void main(java.lang.String[]); }注意点:其余的看注释,可以配置哪些类不参与混淆,哪些枚举保留,哪些方法名不混淆等等。第二步,在 pom 文件上 加入 proguard 混淆插件 :build标签里面改动加入一下配置<build> <plugins> <plugin> <groupId>com.github.wvengen</groupId> <artifactId>proguard-maven-plugin</artifactId> <version>2.6.0</version> <executions> <!-- 以下配置说明执行mvn的package命令时候,会执行proguard--> <execution> <phase>package</phase> <goals> <goal>proguard</goal> </goals> </execution> </executions> <configuration> <!-- 就是输入Jar的名称,我们要知道,代码混淆其实是将一个原始的jar,生成一个混淆后的jar,那么就会有输入输出。 --> <injar>${project.build.finalName}.jar</injar> <!-- 输出jar名称,输入输出jar同名的时候就是覆盖,也是比较常用的配置。 --> <outjar>${project.build.finalName}.jar</outjar> <!-- 是否混淆 默认是true --> <obfuscate>true</obfuscate> <!-- 配置一个文件,通常叫做proguard.cfg,该文件主要是配置options选项,也就是说使用proguard.cfg那么options下的所有内容都可以移到proguard.cfg中 --> <proguardInclude>${project.basedir}/proguard.cfg</proguardInclude> <!-- 额外的jar包,通常是项目编译所需要的jar --> <libs> <lib>${java.home}/lib/rt.jar</lib> <lib>${java.home}/lib/jce.jar</lib> <lib>${java.home}/lib/jsse.jar</lib> </libs> <!-- 对输入jar进行过滤比如,如下配置就是对META-INFO文件不处理。 --> <inLibsFilter>!META-INF/**,!META-INF/versions/9/**.class</inLibsFilter> <!-- 这是输出路径配置,但是要注意这个路径必须要包括injar标签填写的jar --> <outputDirectory>${project.basedir}/target</outputDirectory> <!--这里特别重要,此处主要是配置混淆的一些细节选项,比如哪些类不需要混淆,哪些需要混淆--> <options> <!-- 可以在此处写option标签配置,不过我上面使用了proguardInclude,故而我更喜欢在proguard.cfg中配置 --> </options> </configuration> </plugin> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <executions> <execution> <goals> <goal>repackage</goal> </goals> <configuration> <mainClass>com.example.myproguarddemo.MyproguarddemoApplication</mainClass> </configuration> </execution> </executions> </plugin> </plugins> </build>注意点:然后可以看到:然后点击 package,正常执行编译打包流程就可以 :然后可以看到jar的生成:看看效果:好了,该篇就到这。
2023年09月26日
28 阅读
0 评论
0 点赞
2023-09-26
微软发布史上最强虚拟机!流畅度堪比主机(附保姆级安装教程)
虚拟化技术有以下几个关键概念虚拟化技术的优点Hyper-V 简介启动 Hyper-V创建虚拟机虚拟化技术有以下几个关键概念主机(Host):也称为宿主机或物理机,指实际物理计算机,它上面部署了虚拟化软件的hypervisor。客户机(Guest):也称为虚拟机实例,指在主机上运行的虚拟环境,每个客户机都运行独立的操作系统和应用程序。Hypervisor:虚拟机监控器,是虚拟化软件的核心组件,负责管理和分配主机资源给客户机,并提供虚拟机的隔离性和管理功能。Hypervisor可以分为两种类型:类型1 Hypervisor(裸金属Hypervisor):直接安装在物理硬件上,作为主机操作系统。它能够更好地利用硬件资源,提供更高的性能和效率,而且更稳定可靠。类型2 Hypervisor(主机操作系统上的Hypervisor):安装在主机操作系统之上,例如在Windows或Linux操作系统之上。它相对较轻量,易于安装和管理,但性能和效率可能略低。资源池化:虚拟化技术可以将主机上的物理资源进行整合和共享,形成一个资源池。资源池可以根据需要动态分配和调整资源,提高资源利用率,实现更好的性能和灵活性。快照与复制:虚拟化技术提供了创建虚拟机快照和复制的功能。通过快照,可以记录虚拟机在某个时间点的状态,并在需要时进行还原。这对于备份、恢复和测试非常有用。虚拟化技术的优点资源利用率提高:通过虚拟化,可以更好地利用和共享物理计算机的资源,提高硬件资源的利用率。灵活性和可扩展性:虚拟化技术允许在同一台物理机上创建多个虚拟环境,根据需求动态配置和调整资源,以实现更好的灵活性和可扩展性。简化管理:虚拟化技术通过虚拟机管理工具提供集中化的管理和监控功能,简化了物理服务器的管理工作。高可用性和容灾:通过虚拟机迁移和聚合技术,可以实现虚拟机的高可用性和灾难恢复,提高系统的可靠性和稳定性。无论你是软件开发人员、IT 专业人员还是技术爱好者,你们中的许多人都需要运行多个操作系统。Hyper-V 让你可以在 Windows 上以虚拟机形式运行多个操作系统。Hyper-V 简介Hyper-V 是微软推出的一款虚拟化技术,它能够在单一物理服务器上运行多个虚拟机操作系统。Hyper-V 包含在 Windows Server 操作系统中,并允许管理员创建,运行和管理虚拟化服务器和虚拟机。Hyper-V 可以帮助企业节省硬件成本,提高应用程序可用性和灵活性,同时减少维护和管理成本。它还可以作为一种工具来测试不同操作系统和应用程序配置,以及支持迁移和备份虚拟机。Hyper-V 支持 Windows 操作系统和其他一些非 Windows 操作系统,如 Linux、FreeBSD 等。系统要求Windows 10 企业版、专业版或教育版;具有二级地址转换 (SLAT) 的 64 位处理器;CPU 支持 VM 监视器模式扩展(Intel CPU 的 VT-c 技术);最少 4 GB 内存;首先,确认您的计算机支持 Hyper-V 技术。具体方法是,在运行框中输入“msinfo32”进入系统信息界面,查看“Hyper-V需求存在”项是否为“是”。启动 Hyper-V通过 PowerShell 启动 以管理员身份打开 PowerShell 控制台,运行下面的命令。Enable-WindowsOptionalFeature -Online -FeatureName Microsoft-Hyper-V -All使用 CMD 和 DISM 启用 Hyper-V 以管理员身份打开 PowerShell 或 CMD 会话。键入以下命令:DISM /Online /Enable-Feature /All /FeatureName:Microsoft-Hyper-V通过设置启动右键单击 Windows 按钮并选择“应用和功能”。选择相关设置下右侧的“程序和功能”。选择“打开或关闭 Windows 功能”。选择“Hyper-V”,然后单击“确定”。安装完成后,系统会提示你重新启动计算机。创建虚拟机从 “开始” 菜单中打开 “Hyper-V Quick Create” 。选择一个操作系统或者使用本地安装源选择你自己的操作系统。如果你想要使用自己的映像创建虚拟机,请选择 Local Installation Source。选择 Change Installation Source。选择要转变为新虚拟机的 .iso 或 .vhdx。如果映像为 Linux 映像,请取消选择“安全启动”选项。选择 “创建虚拟机” 就这么简单!“快速创建” 将完成其余的工作。现在,你可以安装操作系统了。简单配置一下,然后就重新启动,之后就可以正常进入系统了。创建虚拟机其实和Vmware没什么区别。安装Win11虚拟机创建虚拟机时,选择本地安装源,点击更改安装源,选择你提前下载的Win11的iso镜像文件,此虚拟机将运行 Windows 保持默认勾选,点击右下角更多选项,给虚拟机一个名称和网络,网络选择Default Switch即可(你也可以选择你提前创建的网络)。然后点击创建虚拟机进入后续步骤。创建完虚拟机后,别着急点连接!!!选择编辑设置。在硬件>安全配置项下,加密支持下,一定一定一定要勾选启用受信任的平台模块(加密状态和虚拟机迁移流量选项可选可不选)。如果不勾选启用受信任的平台模块,在后面安装Windows时,将会提示:这台电脑无法运行Windows 11。在硬件>内存配置项下,RAM推荐最少4096 MB,默认为2048 MB,然后应用即可。现在你可以在成功创建虚拟机界面,点击连接按钮,来连接你创建的虚拟机了。点击启动按钮,你可能会看到如下界面,Press any key to boot from CD or DVD,>>Start PXE over IPv4。如果不出意外的话,你应该就能看到下面的安装界面了。唯一需要强调的是在你想执行哪种类型的安装,选择自定义:仅安装Windows(高级)选项。一切都配置完后,你可能会进入到看不到Windows的登录页面。如果无登录页面,你需要在菜单栏的"查看",取消"增强会话",然后自动重启后,即可看到登录界面,然后登录即可开始你的win11体验之旅。目前,微软又发布新版本(根据 22621.1848这个版本发布的)!下载地址:https://developer.microsoft.com/zh-cn/windows/downloads/virtual-machines/ 有兴趣的可以下载自行体验,安装方法和上面一样。
2023年09月26日
21 阅读
0 评论
0 点赞
1
...
9
10
11
...
27