SpringBoot请求参数加密、响应参数解密

SpringBoot请求参数加密、响应参数解密

1.说明

在项目开发工程中,有的项目可能对参数安全要求比较高,在整个http数据传输的过程中都需要对请求参数、响应参数进行加密,也就是说整个请求响应的过程都是加密处理的,不在浏览器上暴露请求参数、响应参数的真实数据。

补充:也可以用于单点登录,在请求参数中添加时间戳,后台解析请求参数对时间戳进行校验,比如当前时间和请求参数中的时间戳相差多少秒、分钟才能进行放行,返回token。这样做的好处在于请求端每次加密之后的密文都是变化的,也能够避免携带相同的报文可以重复的登录。

2.准备工作

1.引入依赖, 创建SpringBoot工程

	<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></dependency><dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId></dependency><dependency><groupId>commons-io</groupId><artifactId>commons-io</artifactId><version>2.11.0</version></dependency><dependency><groupId>junit</groupId><artifactId>junit</artifactId></dependency><dependency><groupId>commons-codec</groupId><artifactId>commons-codec</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId></dependency><!--swagger--><dependency><groupId>io.springfox</groupId><artifactId>springfox-swagger2</artifactId><version>3.0.0</version></dependency><dependency><groupId>io.springfox</groupId><artifactId>springfox-swagger-ui</artifactId><version>3.0.0</version></dependency><dependency><groupId>io.swagger</groupId><artifactId>swagger-annotations</artifactId><version>1.5.22</version></dependency><dependency><groupId>com.github.xiaoymin</groupId><artifactId>swagger-bootstrap-ui</artifactId><version>1.8.7</version></dependency><dependency><groupId>org.apache.commons</groupId><artifactId>commons-lang3</artifactId></dependency></dependencies>

3.代码实现

1.定义两个注解

/*** @description: : 请求参数解密*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DecryptionAnnotation {
}/*** @description: 响应参数加密*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface EncryptionAnnotation {
}

2.加密解密实现核心代码

DecryptRequestBodyAdvice:请求参数解密,针对post请求

package com.llp.crypto.advice;import cn.hutool.json.JSONUtil;
import com.llp.crypto.annotation.DecryptionAnnotation;
import com.llp.crypto.utils.AESUtil;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.springframework.core.MethodParameter;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.servlet.mvc.method.annotation.RequestBodyAdvice;import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Type;/*** @description: 请求参数解密,针对post请求*/@Slf4j
@ControllerAdvice
public class DecryptRequestBodyAdvice implements RequestBodyAdvice {/*** 方法上有DecryptionAnnotation注解的,进入此拦截器** @param methodParameter 方法参数对象* @param targetType      参数的类型* @param converterType   消息转换器* @return true,进入,false,跳过*/@Overridepublic boolean supports(MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {return methodParameter.hasMethodAnnotation(DecryptionAnnotation.class);}@Overridepublic HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) throws IOException {try {return new MyHttpInputMessage(inputMessage, parameter);} catch (Exception e) {throw new RuntimeException(e);}}/*** 转换之后,执行此方法,解密,赋值** @param body          spring解析完的参数* @param inputMessage  输入参数* @param parameter     参数对象* @param targetType    参数类型* @param converterType 消息转换类型* @return 真实的参数*/@SneakyThrows@Overridepublic Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {log.info("解密后的请求报文:{}", body);return body;}/*** 如果body为空,转为空对象** @param body          spring解析完的参数* @param inputMessage  输入参数* @param parameter     参数对象* @param targetType    参数类型* @param converterType 消息转换类型* @return 真实的参数*/@Overridepublic Object handleEmptyBody(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {return body;}class MyHttpInputMessage implements HttpInputMessage {private HttpHeaders headers;private InputStream body;private MethodParameter parameter;public MyHttpInputMessage(HttpInputMessage inputMessage, MethodParameter parameter) throws Exception {this.headers = inputMessage.getHeaders();//只对post请求进行加密if (parameter.hasMethodAnnotation(PostMapping.class)) {/**请求报文示例:*  {*  "requestData":"JF7kvl9Wd/vgdmAS8JijsQ=="*  }*/String decrypt = AESUtil.decrypt(easpData(IOUtils.toString(inputMessage.getBody(), "UTF-8")));log.info("解密后的请求参数:{}", decrypt);this.body = IOUtils.toInputStream(decrypt, "UTF-8");} else {this.body = inputMessage.getBody();}}@Overridepublic InputStream getBody() throws IOException {return body;}@Overridepublic HttpHeaders getHeaders() {return headers;}}public String easpData(String requestData) {if (requestData != null && !requestData.equals("")) {String start = "requestData";if (requestData.contains(start)) {return JSONUtil.parseObj(requestData).getStr(start);} else {throw new RuntimeException("参数【requestData】缺失异常!");}}return "";}
}

GetDeleteDecryptAspect:针对get、delete请求参数进行解密

@Aspect
//值越小优先级越高
@Order(-1)
@Component
@Slf4j
public class GetDeleteDecryptAspect {/*** 对get、delete方法进行解密* @param point* @return* @throws Throwable*/@Around("@annotation(com.llp.crypto.annotation.DecryptionAnnotation) && " + "(@annotation(org.springframework.web.bind.annotation.GetMapping) || @annotation(org.springframework.web.bind.annotation.DeleteMapping))")public Object aroundMethod(ProceedingJoinPoint point) throws Throwable {MethodSignature signature = (MethodSignature) point.getSignature();Method method = signature.getMethod();// 获取到请求的参数列表Object[] args = point.getArgs();// 判断方法请求参数是否需要解密if (method.isAnnotationPresent(DecryptionAnnotation.class)) {try {this.decrypt(args, point);log.info("返回解密结果=" + args);} catch (Exception e) {e.printStackTrace();log.error("对方法method :【" + method.getName() + "】入参数据进行解密出现异常:" + e.getMessage());}}// 执行将解密的结果交给控制器进行处理,并返回处理结果return point.proceed(args);}/*** 前端对请求参数进行加密,最终将这个加密的字符串已 localhost:48080?data=xxx这样的方式进行传递* 后端后去到 data的数据进行解密最终得到解密后的数据* @param args* @param point* @throws Exception*/// 解密方法@SuppressWarnings("unchecked")public void decrypt(Object[] args, ProceedingJoinPoint point) throws Exception {ServletRequestAttributes sc = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();HttpServletRequest request = sc.getRequest();String data = request.getParameter("data");log.info("data: " + data);// 将密文解密为JSON字符串Class<?> aClass = args[0].getClass();log.info("数据类型:{}",aClass.getClass());if (StringUtils.isNotEmpty(data)) {// 将JSON字符串转换为Map集合,并替换原本的参数args[0] = JSONUtil.toBean(AESUtil.decrypt(data), args[0].getClass());}}
}

EncryptResponseBodyAdvice:响应参数解密,针对统一返回结果类的装配

/*** @description: 响应加密*/@Slf4j
@ControllerAdvice
public class EncryptResponseBodyAdvice implements ResponseBodyAdvice {@Overridepublic boolean supports(MethodParameter methodParameter, Class aClass) {return methodParameter.hasMethodAnnotation(EncryptionAnnotation.class);}@Overridepublic Object beforeBodyWrite(Object body, MethodParameter methodParameter, MediaType mediaType, Class aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) {log.info("对方法method :【" + methodParameter.getMethod().getName() + "】返回数据进行加密");// 只针对回参类型为CommonResult的对象,进行加密if (body instanceof CommonResult) {CommonResult commonResult = (CommonResult) body;Object data = commonResult.getData();if (Objects.nonNull(data)) {// 将响应结果转换为json格式String result = JSONUtil.toJsonStr(data);log.info("返回结果:{}", result);try {String encrypt = AESUtil.encrypt(result);commonResult.setData(encrypt);log.info("返回结果加密=" + commonResult);} catch (Exception e) {log.error("对方法method :【" + methodParameter.getMethod().getName() + "】返回数据进行解密出现异常:" + e.getMessage());}return commonResult;}}return body;}}

3.统一返回结果

@Data
public class CommonResult<T> {private String code;private String msg;private T data;public CommonResult() {}public CommonResult(T data) {this.data = data;}/*** 表示成功的Result,不携带返回数据** @return*/public static CommonResult success() {CommonResult result = new CommonResult();result.setCode("200");result.setMsg("success");return result;}/*** 便是成功的Result,携带返回数据* 如果需要在static方法使用泛型,需要在static后指定泛型表示 static<T>** @param data* @return*/public static <T> CommonResult<T> success(T data) {CommonResult<T> result = new CommonResult<>(data);result.setCode("200");result.setMsg("success");return result;}/*** 失败不携带数据* 将错误的code、msg作为形参,灵活传入** @param code* @param msg* @return*/public static CommonResult error(String code, String msg) {CommonResult result = new CommonResult();result.setCode(code);result.setMsg(msg);return result;}/*** 失败携带数据* 将错误的code、msg、data作为形参,灵活传入* @param code* @param msg* @param data* @param <T>* @return*/public static <T> CommonResult<T> error(String code, String msg, T data) {CommonResult<T> result = new CommonResult<>(data);result.setCode(code);result.setMsg(msg);return result;}}

4.加密工具类

public class AESUtil {// 加解密方式private static final String AES_ALGORITHM = "AES/ECB/PKCS5Padding";// 与前端统一好KEYprivate static final String KEY = "abcdsxyzhkj12345";// 获取 cipherprivate static Cipher getCipher(byte[] key, int model) throws Exception {SecretKeySpec secretKeySpec = new SecretKeySpec(KEY.getBytes(), "AES");Cipher cipher = Cipher.getInstance(AES_ALGORITHM);cipher.init(model, secretKeySpec);return cipher;}// AES加密public static String encrypt(String data) throws Exception {Cipher cipher = getCipher(KEY.getBytes(), Cipher.ENCRYPT_MODE);return Base64.getEncoder().encodeToString(cipher.doFinal(data.getBytes("UTF-8")));}// AES解密public static String decrypt(String data) throws Exception {Cipher cipher = getCipher(KEY.getBytes(), Cipher.DECRYPT_MODE);return new String(cipher.doFinal(Base64.getDecoder().decode(data.getBytes("UTF-8"))),"UTF-8");}public static byte[] decryptUrl(String url) throws Exception {Cipher cipher = getCipher(KEY.getBytes(), Cipher.DECRYPT_MODE);return cipher.doFinal(Base64.getDecoder().decode(url.replaceAll(" +", "+")));}// AES解密MySQL AES_ENCRYPT函数加密密文public static String aesDecryptMySQL(String key, String content){try {SecretKey secretKey = generateMySQLAESKey(key,"ASCII");Cipher cipher = Cipher.getInstance("AES");cipher.init(Cipher.DECRYPT_MODE, secretKey);byte[] cleartext = Hex.decodeHex(content.toCharArray());byte[] ciphertextBytes = cipher.doFinal(cleartext);return new String(ciphertextBytes, StandardCharsets.UTF_8);} catch (Exception e) {e.printStackTrace();}return null;}//加密public static String aesEncryptMySQL(String key2, String content) {try {SecretKey key = generateMySQLAESKey(key2,"ASCII");Cipher cipher = Cipher.getInstance("AES");cipher.init(Cipher.ENCRYPT_MODE, key);byte[] cleartext = content.getBytes("UTF-8");byte[] ciphertextBytes = cipher.doFinal(cleartext);return new String(Hex.encodeHex(ciphertextBytes));} catch (Exception e) {e.printStackTrace();}return null;}public static SecretKeySpec generateMySQLAESKey(final String key, final String encoding) {try {final byte[] finalKey = new byte[16];int i = 0;for(byte b : key.getBytes(encoding)) {finalKey[i++%16] ^= b;}return new SecretKeySpec(finalKey, "AES");} catch(UnsupportedEncodingException e) {throw new RuntimeException(e);}}@Testpublic void decodeTest() {try {String a = "{\"username\":\"admin\",\"deptId\":\"1250500000\",\"userId\":\"1\",\"phone\":\"15195928695\"}";String encrypt = AESUtil.encrypt(a);System.out.println("加密后的字符串: "+encrypt);System.out.println("解密后的字符串:" +AESUtil.decrypt(encrypt));String str = "5tAayXF5ZcPC9yoNvBIT0fw2Li2uoxUhGyMq4JKUvCttOFnU7iKovyB9pm/ZV+2qU8h2htdk5s6ht9kCpTGG9WZAGTdMUgIJkD/Tf6IQ3gw=";String decrypt = AESUtil.decrypt(IOUtils.toString(str.getBytes(), "UTF-8"));System.out.println(decrypt);} catch (Exception e) {e.printStackTrace();}}
}

5.请求流支持多次获取

/*** 请求流支持多次获取*/
public class InputStreamHttpServletRequestWrapper extends HttpServletRequestWrapper {/*** 用于缓存输入流*/private ByteArrayOutputStream cachedBytes;public InputStreamHttpServletRequestWrapper(HttpServletRequest request) {super(request);}@Overridepublic ServletInputStream getInputStream() throws IOException {if (cachedBytes == null) {// 首次获取流时,将流放入 缓存输入流 中cacheInputStream();}// 从 缓存输入流 中获取流并返回return new CachedServletInputStream(cachedBytes.toByteArray());}@Overridepublic BufferedReader getReader() throws IOException {return new BufferedReader(new InputStreamReader(getInputStream()));}/*** 首次获取流时,将流放入 缓存输入流 中*/private void cacheInputStream() throws IOException {// 缓存输入流以便多次读取。为了方便, 我使用 org.apache.commons IOUtilscachedBytes = new ByteArrayOutputStream();IOUtils.copy(super.getInputStream(), cachedBytes);}/*** 读取缓存的请求正文的输入流* <p>* 用于根据 缓存输入流 创建一个可返回的*/public static class CachedServletInputStream extends ServletInputStream {private final ByteArrayInputStream input;public CachedServletInputStream(byte[] buf) {// 从缓存的请求正文创建一个新的输入流input = new ByteArrayInputStream(buf);}@Overridepublic boolean isFinished() {return false;}@Overridepublic boolean isReady() {return false;}@Overridepublic void setReadListener(ReadListener listener) {}@Overridepublic int read() throws IOException {return input.read();}}}

4.测试

1.测试类

@Slf4j
@RestController
@Api(tags = "测试加密解密")
public class TestController {/*** 请求示例:* {* "requestData":"5tAayXF5ZcPC9yoNvBIT0fw2Li2uoxUhGyMq4JKUvCttOFnU7iKovyB9pm/ZV+2qU8h2htdk5s6ht9kCpTGG9WZAGTdMUgIJkD/Tf6IQ3gw="* }** @return*/@PostMapping(value = "/postEncrypt")@ApiOperation("测试post加密")@EncryptionAnnotation@DecryptionAnnotationpublic CommonResult<String> postEncrypt(@RequestBody UserReqVO userReqVO) {System.out.println("userReqVO: ============>" + userReqVO);return CommonResult.success("成功");}@GetMapping(value = "/getEncrypt")@ApiOperation("测试get加密")@DecryptionAnnotation // requestBody 自动解密public CommonResult<UserReqVO> getEncrypt(String data) {log.info("解密后的数据:{}",data);UserReqVO userReqVO = JSONUtil.toBean(data, UserReqVO.class);//UserReqVO(username=admin, deptId=1250500000, userId=1, phone=15195928695)log.info("用户信息:{}",userReqVO);return CommonResult.success(userReqVO);}
}
@ApiModel(description = "用户请求vo")
@Data
public class UserReqVO {@ApiModelProperty(value = "用户名", required = true)private String username;@ApiModelProperty(value = "部门id",required = true)private Long deptId;@ApiModelProperty(value = "用户id",required = true)private Long userId;@ApiModelProperty(value = "电话号码",required = true)private String phone;}

测试结果

image-20240108110033110

image-20240108110124687

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/606982.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

40道java集合面试题含答案(很全)

点击下载《40道java集合面试题含答案&#xff08;很全&#xff09;》 1. 什么是集合 集合就是一个放数据的容器&#xff0c;准确的说是放数据对象引用的容器集合类存放的都是对象的引用&#xff0c;而不是对象的本身集合类型主要有3种&#xff1a;set(集&#xff09;、list(列…

Unity文字转语音(使用RT-Voice PRO [2023.1.0])

参考文章Unity插件——文字转朗读语音RtVioce插件功能/用法/下载_rtvoice-CSDN博客 一、使用步骤 1.导入进Unity&#xff08;插件形式为 .unitypackage&#xff09; https://download.csdn.net/download/luckydog1120446388/88717512 2.添加所需Prefab 1&#xff09;.右键可…

后端 API 接口文档 Swagger 使用

Swagger 是什么 swagger是一款可以根据 restful 风格生成的接口开发文档&#xff0c;并且支持做测试的一款中间软件。 例如当我们在开发前后端分离项目时&#xff0c;当后端开发完一个功能想要测试时&#xff0c;若此时还没有相应的前端页面发起请求&#xff0c;可以通过 swag…

python炒股自动化(0),申请券商API接口

上次发了量化交易接口的区别&#xff0c;发现很多人根本不知道券商提供的API交易接口&#xff0c;这里补充一篇&#xff0c;关于券商接口的介绍。 现在市面上可以给个人账户接入的股票交易接口&#xff0c;用的最多的也就是QMT和Ptrade&#xff0c;以前接入量化交易需要机构或…

高压放大器设计要求是什么

高压放大器在科学研究和工程应用中扮演着至关重要的角色&#xff0c;特别是在需要处理高电压信号的实验和应用中。高压放大器设计要求的充分考虑至关重要&#xff0c;以确保其在各种环境中稳定、可靠地工作。下面将介绍设计高压放大器时需要考虑的关键要求和因素。 1.电压范围 …

赋能软件开发:生成式AI在优化编程工作流中的应用与前景

随着人工智能&#xff08;AI&#xff09;技术的快速发展&#xff0c;特别是生成式AI模型如GPT-3/4的出现&#xff0c;软件开发行业正经历一场变革&#xff0c;这些模型通过提供代码生成、自动化测试和错误检测等功能&#xff0c;极大地提高了开发效率和软件质量。 本文旨在深入…

Android readelf 工具查找函数符号

ELF&#xff08;Executable and Linkable Format&#xff09;是一种执行文件和可链接文件的格式。它是一种通用的二进制文件格式&#xff0c;用于在各种操作系统中存储可执行程序、共享库和内核模块。 Android 开发当中的 so 库本质上就是一种特殊类型的 ELF 文件&#xff0c;…

文章解读与仿真程序复现思路——电工技术学报EI\CSCD\北大核心《考虑灵活性补偿的高比例风电与多元灵活性资源博弈优化调度》

本专栏栏目提供文章与程序复现思路&#xff0c;具体已有的论文与论文源程序可翻阅本博主免费的专栏栏目《论文与完整程序》 这个标题涉及到高比例风电与多元灵活性资源在博弈优化调度中考虑灵活性补偿的问题。以下是对标题各个部分的解读&#xff1a; 高比例风电&#xff1a; …

【模拟IC学习笔记】Cascode OTA 设计

辅助定理 增益Gm*输出阻抗 输出短路求Gm 输入置0求输出阻抗 求源极负反馈的增益 随着Vin的增加&#xff0c;Id也在增加&#xff0c;Rs上压降增加&#xff0c;所以&#xff0c;Vin的一部分电压体现在Rs上&#xff0c;而不是全部作为Vgs&#xff0c;因此导致Id变得平滑。 Rs足…

【数据结构篇】数据结构中的 R 树和 B 树

数据结构中的 R 树和 B 树 ✔️关于R树&#xff08;RTree&#xff09;✔️什么是B树&#xff08;B-tree&#xff09;✔️B树和B树的区别✔️B树和B树在数据存储方面的具体差异 ✔️拓展知识仓✔️R树和B树的区别✔️ 那在内存消耗上有什么区别&#xff1f;✔️ R树有哪些优点和…

【算法与数据结构】509、LeetCode斐波那契数

文章目录 一、题目二、递归&#xff0c;动态规划解法2.1 递归解法2.2 动态规划解法 三、完整代码 所有的LeetCode题解索引&#xff0c;可以看这篇文章——【算法和数据结构】LeetCode题解。 一、题目 二、递归&#xff0c;动态规划解法 2.1 递归解法 思路分析&#xff1a;斐波…

【Leetcode】240. 搜索二维矩阵 II

一、题目 1、题目描述 编写一个高效的算法来搜索 m x n 矩阵 matrix 中的一个目标值 target 。该矩阵具有以下特性: 每行的元素从左到右升序排列。每列的元素从上到下升序排列。示例1: 输入:matrix = [[1,4,7,11,15],[2,5,8,12,19],[3,6,9,16,22],[10,13,14,17,24],[18,21…

【REST2SQL】05 GO 操作 达梦 数据库

【REST2SQL】01RDB关系型数据库REST初设计 【REST2SQL】02 GO连接Oracle数据库 【REST2SQL】03 GO读取JSON文件 【REST2SQL】04 REST2SQL第一版Oracle版实现 信创要求用国产数据库&#xff0c;刚好有项目用的达梦&#xff0c;研究一下go如何操作达梦数据库 1 准备工作 1.1 安…

ros2 基础学习 15- URDF:机器人建模方法

URDF&#xff1a;机器人建模方法 ROS是机器人操作系统&#xff0c;当然要给机器人使用啦&#xff0c;不过在使用之前&#xff0c;还得让ROS认识下我们使用的机器人&#xff0c;如何把一个机器人介绍给ROS呢&#xff1f; 为此&#xff0c;ROS专门提供了一种机器人建模方法——…

软件测试|SQL中的UNION和UNION ALL详解

简介 在SQL&#xff08;结构化查询语言&#xff09;中&#xff0c;UNION和UNION ALL是用于合并查询结果集的两个关键字。它们在数据库查询中非常常用&#xff0c;但它们之间有一些重要的区别。在本文中&#xff0c;我们将深入探讨UNION和UNION ALL的含义、用法以及它们之间的区…

Ubuntu 22.04 编译安装 Qt mysql驱动

参考自 Ubuntu20.04.3 QT5.15.2 MySQL驱动编译 Ubuntu 18.04 编译安装 Qt mysql驱动 下边这篇博客不是主要参考的, 但是似乎解决了我的难题(找不到 libmysqlclient.so) ubuntu18.04.2 LTS 系统关于Qt5.12.3 无法加载mysql驱动&#xff0c;需要重新编译MYSQL数据库驱动的问题以…

【代码随想录】刷题笔记Day45

前言 早上又赖了会床......早睡早起是奢望了现在&#xff0c;新一年不能这样&#xff01;支棱起来&#xff01; 377. 组合总和 Ⅳ - 力扣&#xff08;LeetCode&#xff09; 这一题用的就是完全背包排列数的遍历顺序&#xff1a;先背包再物品&#xff0c;从前往后求的也是有几…

mac电脑php命令如何设置默认的php版本

前提条件&#xff1a;如果mac电脑还没安装多个PHP版本&#xff0c;可以先看这篇安装一下 mac电脑运行多个php版本_mac 同时运行两个php-CSDN博客 第一部分&#xff1a;简单总结 #先解除现在默认的php版本 brew unlink php7.4#再设置的想要设置的php版本 brew link php8.1第二部…

Java20:反射

1. 概念2. 获取成员变量2.1 获取public修饰的成员变量2.2 获取已声明的属性 3.获取方法3.1 获取public修饰的&#xff0c;和继承自父类的 方法3.2 获取本类中定义的方法 4. 获取构造器4.1 获取所有public修饰的构造器4.2 获取本类中定义的构造器 5.jdk文件分析5.1bin目录&#…

CodeGPT,你的智能编码助手—CSDN出品

CodeGPT是由CSDN打造的一款生成式AI产品&#xff0c;专为开发者量身定制。 无论是在学习新技术还是在实际工作中遇到的各类计算机和开发难题&#xff0c;CodeGPT都能提供强大的支持。其涵盖的功能包括代码优化、续写、解释、提问等&#xff0c;还能生成精准的注释和创作相关内…