日志输出-第四章-接口级(单体应用)前后端数据加解密 Filter 实现

devtools/2024/12/22 3:59:56/

在这里插入图片描述

文章目录

  • 日志输出-第四章-接口级(单体应用)前后端数据加解密 Filter 实现
    • 一、概述
    • 二、通过 Filter 的方式实现
      • 2.1、加解密工具类
      • 2.2、请求包装类
      • 2.3、响应包装类
      • 2.4、实现加解密
      • 2.5、效果展示
    • 三、总结

日志输出-第四章-接口级(单体应用)前后端数据加解密 Filter 实现

前置内容

  1. 日志输出指南
  2. 日志输出-第二章-接口级出入参的实现
  3. 日志输出-第三章-接口级出入参输出完整数据的实现

一、概述

上一章内容为如何输出完整数据,但是一般情况下还是会采用第二章的实现方式(因为输出 body 会影响性能),上一章的处理方式实际上更多是用于处理前后端数据的加解密。

本章的内容实际上并不属于 日志输出的范围 而是对上一章的内容进行了衍生(因为日志输出是需要在流量的出入口做处理,前后端数据加解密也是在流量的出入口做处理,并且都是对 body 数据处理),是 SpringBoot 项目如何处理前后端数据加解密问题(SpringCloud 版本的加解密会在后续日志写到相应版本后再更新)。

一般情况下的做法分为两种:

  1. 通过 Filter 的方式,也就是和我们上一章的日志输出一样,只是将输出日志改为对数据进行加解密就可以了。
  2. 通过 SpringRequestBodyAdviceResponseBodyAdvice 来实现。

两种的实现难度都差不多,唯一的区别在于 通过 Spring 的这种方式好像是只能实现 POST 这类请求的拦截(我只是大概试了一下)。

二、通过 Filter 的方式实现

这一部分除了输出之外,最大的区别在于 Response 的包装类,因为上一章的内容在于如何输出日志,所以我们实际上只需要在响应中写数据的时候写两份就可以做到了。

java">/*** @Param* @Return* @Description 内部类,对 ServletOutputStream 进行包装,方便日志记录* @Author lizelin* @Date 2023/11/3 16:43**/private class ResponseWrapperOutputStream extends ServletOutputStream {private ByteArrayOutputStream bos = null;private HttpServletResponse response = null;public ResponseWrapperOutputStream(ByteArrayOutputStream stream, HttpServletResponse response) {bos = stream;this.response = response;}/*** @Param b* @Return void* @Description 将写暂存一份方便日志* @Author lizelin* @Date 2023/11/3 17:31**/@Overridepublic void write(int b) throws IOException {bos.write(b);response.getOutputStream().write(b);}/*** @Param b* @Return void* @Description 将写暂存一份方便日志* @Author lizelin* @Date 2023/11/3 17:32**/@Overridepublic void write(byte[] b) throws IOException {bos.write(b, 0, b.length);response.getOutputStream().write(b, 0, b.length);}@Overridepublic boolean isReady() {return false;}@Overridepublic void setWriteListener(WriteListener writeListener) {//不需要重新}}

也就是上面代码中的 write 部分,ByteArrayOutputStream 的作用在于我们自己输出日志使用,但是向客户端响应的部分实际上还是走的 response.getOutputStream().write(b); 也就是说实际上我们上一章的内容只是在原有的响应逻辑上做了一个旁路逻辑用于输出日志。

2.1、加解密工具类

一共有两组密钥,后端响应加密、前端响应解密、后端请求解密、前端请求加密

java">
import cn.hutool.crypto.SecureUtil;
import cn.hutool.crypto.SmUtil;
import cn.hutool.crypto.asymmetric.KeyType;
import cn.hutool.crypto.asymmetric.RSA;
import cn.hutool.crypto.asymmetric.SM2;/*** @ClassName ParametersSecureUtil* @Author lizelin* @Description 参数加密 util* @Date 2024/5/29 14:50* @Version 1.0*/
public class ParametersSecureUtil {//后端持有私钥解密private static String REQ_REAR_PRIVATE_KEY = "MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBALLu5fiTx0RbbCMVFaiAx9LSiddTQT58ab07I6LtlIem8J2Q8C+1P6m+8w81T3kPz8WfpQM+7npdv3FhQmrRggj37Lm5+8Q7OMMCXG3va6kpRr9xGd4TjyuJBi8AGX58MnojruiQCwhxjngyJggmHZYlAB61A3OMq1Bi2ExBbkGnAgMBAAECgYAGJKGMmTY8KI9b3PtzX4h8unG1DMyuooLW1lLw4ws4ZQjZwAIfATAAWefqW8AwvdQ6SrLVm7GATfumntoy5KJ8MaF86pfTcGsuqpYZXwcjAHJ4sikKPZYUPn+BaEDcMIBBx8QkVSd0okV2m0bwou6nbVoorjkLCzdQrlXSpmeeUQJBAN8BL8QFYed+BLqyYMiZicGjuRRKGe4QUeMjpLDys0WC4HXCQjozbk1t9LL63GzC7BkMrH/BIReqIv9S7i6ff/UCQQDNaGeXNwGWj2JfsrQMmBe3HReVuwNV7bBlD2EmKT8csZ3F05t+JMR/XaBP44ApZGiCjfPfDAUPBNR0TYEXVAWrAkEAx5SLSDa8+W36E5CjN8TZ2fiKIpNzA3GNp+f1c/ux38sS0bFKjkYLOLbooeoLrjcBECYcl7WjxUcaTUHOMuHCpQJAYocOCY6tCFdGzLiffNsHpSIjSgMmmnUlA5TY+MEYMN9R2q6iC2P/jUiPuUJbG3+6UcVdkUPmuUmLzy3OGi6HeQJBAJOZmEtgHMCfqirahDbrDIbRojw7qNDSX2bbIPEmiFs8iEN+JR8ULUwDBRvmhj6a2IddfPGLOEK5xepiuco2BGs=";//前端持有公钥加密 这个实际上是给前端用的,后端不需要使用private static String REQ_FRONT_PUBLICE_KEY = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCy7uX4k8dEW2wjFRWogMfS0onXU0E+fGm9OyOi7ZSHpvCdkPAvtT+pvvMPNU95D8/Fn6UDPu56Xb9xYUJq0YII9+y5ufvEOzjDAlxt72upKUa/cRneE48riQYvABl+fDJ6I67okAsIcY54MiYIJh2WJQAetQNzjKtQYthMQW5BpwIDAQAB";//后端持有公钥加密private static String RES_REAR_PUBLICE_KEY = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC9AeIxSwTTLmtgCOMsCpytG+SdX5PjC0jOjuIbY4wd61rVNemjqJNldBrrJ6ldF+t+5GXB/O0IevAL47At5WltTcWrOGEpSJssHDaVmya5E/yyDDP+3PPlvH6KR1SdgH8fppipjWRFYU5/ke+EQLTmrNxFqvqniUlEPl/63TyuqQIDAQAB";//前端持有私钥解密private static String RES_FRONT_PRIVATE_KEY = "MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAL0B4jFLBNMua2AI4ywKnK0b5J1fk+MLSM6O4htjjB3rWtU16aOok2V0GusnqV0X637kZcH87Qh68AvjsC3laW1Nxas4YSlImywcNpWbJrkT/LIMM/7c8+W8fopHVJ2Afx+mmKmNZEVhTn+R74RAtOas3EWq+qeJSUQ+X/rdPK6pAgMBAAECgYBr+qR/5szl3TIo1kr6gUGLQFE2e0Egx/SbVVPls9R7z1bAUiGdhxRWNKOgTrNaZOz8PH3J+rZsTtfO4xBm2BaHCTmCShOVl6RG/qeNC1A1S2nmpkckvS4XfH7DVs0IILaEVnHIYLSUd8oiP/nJ+Hppn6Sj7cUGSwb2itYx1YtpUQJBAN6dRipcvKHpQkvodFfkT1m6XkwgrMuiIvTmqWYvKTXUwZ7uvyYIjB42O7DziCKoBFUualED/g69ft5fhGDepFUCQQDZWlovo0RSjfUyzuh1VcI9mQu3gLSgfUbJKgeOD+435jHFhIXiKKIdaN1L1R1MLggCbaZGyq0rqS6D9GykRpUFAkEA1DJKXbsEO7ni7gRoUhdY5AjYNey3iWvFsnfkZXjy6VMiNOMS5agkF/BOOcAJti894gxaX1tU4qwSsNmPj97p+QJAC7vW9o9n1tUXEaEd54ezrsOeYE+wcKGSurVsJv0xLQ9eTH11BNqQtem9WKSuqjgp8oec3GGAq8S8YB9H5i5xSQJALd09O7Hv0fZRn8yI09qQ2KCB0CpIHrXHjGI1I/TR72k/DlTJOOIKe6LnkecXF21xiMOq0aqhs0Ol5U2FIXkkzw==";/*** 请求*/private static RSA reqRsa = SecureUtil.rsa(REQ_REAR_PRIVATE_KEY, REQ_FRONT_PUBLICE_KEY);/*** 响应*/private static RSA resRsa = SecureUtil.rsa(RES_FRONT_PRIVATE_KEY, RES_REAR_PUBLICE_KEY);/*** @Param encryptedData* @Return java.lang.String* @Description 请求解密* @Author lizelin* @Date 2024/5/29 15:43**/public static String requestDecrypt(String encryptedData) {return  reqRsa.decryptStr(encryptedData, KeyType.PrivateKey);}/*** @Param encryptedData* @Return java.lang.String* @Description 请求内容加密,todo 测试用* @Author lizelin* @Date 2024/5/29 15:52**/public static String requestEncrypt(String encryptedData) {return  reqRsa.encryptHex(encryptedData, KeyType.PublicKey);}/*** @Param encryptedData* @Return java.lang.String* @Description 响应加密* @Author lizelin* @Date 2024/5/29 15:45**/public static String responseEncrypt(String encryptedData) {return resRsa.encryptHex(encryptedData, KeyType.PublicKey);}/*** @Param encryptedData* @Return java.lang.String* @Description 响应内容解密 todo 测试用* @Author lizelin* @Date 2024/5/29 15:52**/public static String responseDecrypt(String encryptedData) {return resRsa.decryptStr(encryptedData, KeyType.PrivateKey);}public static void main(String[] args) {
//        String str = "{\n" +
//                "    \"createBy\":\"lzl\",\n" +
//                "    \"content\":\"大厦春,你要干什么\"\n" +
//                "}";String str = "95b06517952572ccd3cb645991658bcfee0cc71a465b454fa2db6cd814c2ff72e69130c334105d4303fc6378f2c0720a7e24f1c1d19f366840dc75bfa858833df7860373070b8586b42127cd489b419ac0093da7936d984c65a4b8d2b8dc1697eb3d239b7446258d4eaabaf5341e92ab2d4cb25f8da3c571c165c35e635fa1db";System.out.println(responseDecrypt(str));}}

2.2、请求包装类

这个实际上与上一章的代码一致

java">
import cn.hutool.core.collection.CollUtil;
import org.apache.catalina.util.ParameterMap;import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Map;/*** @ClassName LogHttpServletRequestWrapper* @Author lizelin* @Description 日志 Http Servlet 请求包装器* @Date 2024/5/27 18:26* @Version 1.0*/
public class LogHttpServletRequestWrapper extends HttpServletRequestWrapper {/*** 所有参数的 Map 集合*/private Map<String, String[]> parameterMap;/*** 存储 body 数据的容器(这里存储为解析流后的JSON)*/private String body;/*** @Param* @Return java.lang.String* @Description 获取Body* @Author lizelin* @Date 2023/11/3 16:09**/public String getBody() {return this.body;}/*** @Param body* @Return void* @Description 修改 body* @Author lizelin* @Date 2023/11/3 16:09**/public void setBody(String body) {this.body = body;}public LogHttpServletRequestWrapper(HttpServletRequest request) {super(request);// 给参数集合赋值parameterMap = request.getParameterMap();// 获取Bodybody = RequestResponseUtil.getRequestBody(request);}/*** @Param parameterMap* @Return void* @Description 替换整个参数 Map* @Author lizelin* @Date 2023/11/3 14:59**/public void setParameterMap(Map<String, String[]> parameterMap) {this.parameterMap = parameterMap;}/*** @Param key* @Param value* @Return void* @Description 向参数集合中添加参数* @Author lizelin* @Date 2023/11/3 14:59**/public void putParameterMap(String key, String[] value) {if (this.parameterMap instanceof ParameterMap) {((ParameterMap<String, String[]>) this.parameterMap).setLocked(false);}this.parameterMap.put(key, value);}/*** @Param* @Return java.util.Enumeration<java.lang.String>* @Description 获取所有参数名* @Author lizelin* @Date 2023/11/3 14:59**/@Overridepublic Enumeration<String> getParameterNames() {return CollUtil.asEnumeration(parameterMap.keySet().iterator());}/*** @Param name* @Return java.lang.String* @Description 获取指定参数名的值,如果有重复的参数名,则返回第一个的值 接收一般变量 ,如 text 类型* @Author lizelin* @Date 2023/11/3 14:59**/@Overridepublic String getParameter(String name) {ArrayList<String> values = CollUtil.toList(parameterMap.get(name));if (CollUtil.isNotEmpty(values)) {return values.get(0);} else {return null;}}/*** @Param name* @Return java.lang.String[]* @Description 获取单个的某个 key 的 value* @Author lizelin* @Date 2023/11/3 14:58**/@Overridepublic String[] getParameterValues(String name) {return parameterMap.get(name);}/*** @Param* @Return java.util.Map<java.lang.String, java.lang.String [ ]>* @Description 获取值列表* @Author lizelin* @Date 2023/11/3 14:58**/@Overridepublic Map<String, String[]> getParameterMap() {return parameterMap;}/*** @Param* @Return java.lang.String* @Description 获取 queryString* @Author lizelin* @Date 2023/11/3 14:57**/@Overridepublic String getQueryString() {return super.getQueryString();}@Overridepublic BufferedReader getReader() throws IOException {return new BufferedReader(new InputStreamReader(this.getInputStream()));}/*** @Param* @Return javax.servlet.ServletInputStream* @Description 重写获取输入流,因为在输出日志的时候会读取输入流,而流只能读取一次,所以在向后传递的时候就需要做特殊处理* @Author lizelin* @Date 2023/11/3 14:55**/@Overridepublic ServletInputStream getInputStream() throws IOException {final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(body.getBytes());return new ServletInputStream() {@Overridepublic boolean isFinished() {return false;}@Overridepublic boolean isReady() {return false;}@Overridepublic void setReadListener(ReadListener listener) {//不需要重写}@Overridepublic int read() {//重写读return byteArrayInputStream.read();}};}
}

2.3、响应包装类

这里主要的修改点在于去掉了 response.getOutputStream().write(b, 0, b.length) 这些内容。也就是响应包装类其实主要用途在于读取数据,写数据的部分在 Filter 中实现

java">
import lombok.extern.slf4j.Slf4j;import javax.servlet.ServletOutputStream;
import javax.servlet.WriteListener;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;/*** @ClassName LogHttpServletResponseWrapper* @Author lizelin* @Description 日志 Http Servlet 响应 包装器* @Date 2024/5/27 18:25* @Version 1.0*/
@Slf4j
public class LogHttpServletResponseWrapper extends HttpServletResponseWrapper {private ByteArrayOutputStream byteArrayOutputStream = null;private ResponseWrapperOutputStream servletOutputStream = null;private PrintWriter printWriter = null;public LogHttpServletResponseWrapper(HttpServletResponse response) throws IOException {super(response);byteArrayOutputStream = new ByteArrayOutputStream();servletOutputStream = new ResponseWrapperOutputStream(byteArrayOutputStream);printWriter = new PrintWriter(new OutputStreamWriter(byteArrayOutputStream, this.getCharacterEncoding()));}/*** @Param* @Return javax.servlet.ServletOutputStream* @Description 获取 OutputStream* @Author lizelin* @Date 2023/11/3 16:47**/@Overridepublic ServletOutputStream getOutputStream() throws IOException {return servletOutputStream;}/*** @Param* @Return java.io.PrintWriter* @Description 获取 Writer* @Author lizelin* @Date 2023/11/3 16:46**/@Overridepublic PrintWriter getWriter() throws UnsupportedEncodingException {return printWriter;}/*** @Param* @Return void* @Description 获取 flushBuffer* @Author lizelin* @Date 2023/11/3 16:46**/@Overridepublic void flushBuffer() throws IOException {if (servletOutputStream != null) {servletOutputStream.flush();}if (printWriter != null) {printWriter.flush();}}/*** @Param* @Return void* @Description 重置流* @Author lizelin* @Date 2023/11/3 16:46**/@Overridepublic void reset() {byteArrayOutputStream.reset();}/*** @Param* @Return String* @Description 读取流中的数据* @Author lizelin* @Date 2023/11/3 16:43**/public String getBody() throws IOException {//刷新缓冲区flushBuffer();//读取流中的数据return new String(byteArrayOutputStream.toByteArray(), StandardCharsets.UTF_8);}public byte[] getBodyBytes() throws IOException {//刷新缓冲区flushBuffer();//读取流中的数据return byteArrayOutputStream.toByteArray();}/*** @Param str* @Return void* @Description 写入数据* @Author lizelin* @Date 2024/5/29 17:33**/public void setBody(String str) throws IOException {flushBuffer();byteArrayOutputStream.reset();for (byte item : str.getBytes()) {servletOutputStream.write(item);}}/*** @Param* @Return* @Description 内部类,对 ServletOutputStream 进行包装,方便日志记录* @Author lizelin* @Date 2023/11/3 16:43**/private class ResponseWrapperOutputStream extends ServletOutputStream {private ByteArrayOutputStream bos = null;public ResponseWrapperOutputStream(ByteArrayOutputStream stream) {bos = stream;}/*** @Param b* @Return void* @Description 将写暂存一份方便日志* @Author lizelin* @Date 2023/11/3 17:31**/@Overridepublic void write(int b) throws IOException {bos.write(b);}/*** @Param b* @Return void* @Description 将写暂存一份方便日志* @Author lizelin* @Date 2023/11/3 17:32**/@Overridepublic void write(byte[] b) throws IOException {bos.write(b, 0, b.length);}@Overridepublic boolean isReady() {return false;}@Overridepublic void setWriteListener(WriteListener writeListener) {//不需要重新}}
}

2.4、实现加解密

这部分的步骤实际上和日志输出的思路基本上一样

java">import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;/*** @ClassName LogHandlerInterceptor* @Author lizelin* @Description 日志请求入参过滤器* @Date 2023/11/3 12:15* @Version 1.0*/
@Slf4j
@Component
@AllArgsConstructor
public class LogRecordRequestFilter implements Filter, Ordered {@Overridepublic void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {HttpServletRequest httpServletRequest = (HttpServletRequest) request;//这里主要是由于流不可重复读取,采用包装类的方式LogHttpServletRequestWrapper requestWrapper = new LogHttpServletRequestWrapper(httpServletRequest);String requestWrapperBody = requestWrapper.getBody();//数据解密requestWrapper.setBody(ParametersSecureUtil.requestDecrypt((String)requestWrapperBody));HttpServletResponse httpServletResponse = (HttpServletResponse) response;LogHttpServletResponseWrapper responseWrapper = new LogHttpServletResponseWrapper(httpServletResponse);chain.doFilter(requestWrapper, responseWrapper);//未加密数据String responseWrapperBody = responseWrapper.getBody();//数据加密responseWrapper.setBody(ParametersSecureUtil.responseEncrypt((String)responseWrapperBody));byte[] bodyBytes = responseWrapper.getBodyBytes();response.setContentLength(bodyBytes.length);//输出加密数据ServletOutputStream outputStream = response.getOutputStream();outputStream.write(bodyBytes);outputStream.flush();}@Overridepublic int getOrder() {return Ordered.HIGHEST_PRECEDENCE;}
}

2.5、效果展示

请求数据为加密数据

image-20240529221647616

控制台数据为解密数据

image-20240529221818628

响应结果为加密数据

image-20240529221910720

对加密的响应结果进行解密,输出结果为 P2 中希望返回的数据

image-20240529222006713

三、总结

SpringBoot 的前后端加解密内容基本上就完成了,整体比较简单,基本上就是日志的思路。

只是需要注意的是示例中的内容只对请求 body 中的内容进行解密操作。

也就是我没写路径传参加解密,主要是因为如果代码比较严格的话,是不允许 POST 请求的时候带 QueryString 的,然后 GET 请求一般 url 参数也没必要加密,但是不排除奇怪的需求或者是屎山代码的情况。

判断一下请求的 Method 然后获取 QueryString 就能做了

下一章再更新 通过 SpringRequestBodyAdviceResponseBodyAdvice 的实现。


http://www.ppmy.cn/devtools/44610.html

相关文章

php正则中的i,m,s,x,e分别表示什么

正则表达式模式修饰符&#xff08;也称为标志或模式修饰符&#xff09;用于改变正则表达式的行为。这些修饰符可以附加在正则表达式的定界符之后&#xff0c;通常为正斜杠&#xff08;/&#xff09;或井号&#xff08;#&#xff09;&#xff0c;以改变搜索或替换的方式。 1、i…

debian11安装留档@VirtualBox

因为debian12无法安装tpot&#xff0c;所以又把11重新安装一遍&#xff0c;以前的安装文档&#xff1a;安装Debian 11 留档-CSDN博客 下载光盘 华为云地址&#xff1a;https://repo.huaweicloud.com/debian-cd/11.0.0/amd64/iso-cd/ 使用了debian11 教育版&#xff0c;比较有…

搜维尔科技:穿上Xsens Link动作捕捉套装,进行精准的运动捕捉

穿上Xsens Link动作捕捉套装&#xff0c;进行精准的运动捕捉 搜维尔科技&#xff1a;穿上Xsens Link动作捕捉套装&#xff0c;进行精准的运动捕捉

ABB 控制柜

1&#xff0c;主计算机&#xff1a;相当于电脑的主机&#xff0c;用于存放系统和数据&#xff0c;需要24V直流电才能工作。执行用户编写的程序&#xff0c;控制机器人进行响应的动作。主计算机有很多接口&#xff0c;比如与编程PC连接的服务网口、用于连接示教器的网口、连接轴…

C++设计模式之策略模式、迭代器模式、适配器模式、工厂模式、超级工厂模式、享元模式、代理模式

文章目录 一、介绍1.毫无价值的使用虚函数例子 二、策略模式1.策略模式2.多重策略与迭代器模式3.不要什么东西都塞一块 三、适配器模式1.跨接口的适配器2.跨接口的适配器 四、工厂模式1.工厂模式2.超级工厂模式3.RAII 自动管理内存4.工厂模式实战 五、享元模式1.享元模式2.代理…

揭秘SQL中的公用表表达式:数据查询的新宠儿

欢迎来到我的博客&#xff0c;代码的世界里&#xff0c;每一行都是一个故事 揭秘SQL中的公用表表达式&#xff1a;数据查询的新宠儿 前言公用表表述的概述非递归CTE的作用递归CTE的作用CTE性能优化 前言 你是否曾经为SQL查询的复杂性而困扰不已&#xff1f;尤其是那些读写层子…

hdfs机器下线维修

HDFS&#xff08;Hadoop Distributed File System&#xff09;是Hadoop分布式文件系统&#xff0c;它设计用来跨多个物理服务器存储大量数据。当HDFS集群中的某个机器需要下线维修时&#xff0c;需要谨慎处理以避免数据丢失或服务中断。以下是处理HDFS机器下线的步骤&#xff1…

如何让一个普通用户可以读写某个目录

循环设置这个目录以及上面每一级目录的读取和执行权限 sudo chmod -R orx /opt/software/yourdir 然后设置指定用户user1可以读写这个目录 sudo setfacl -Rm u:user1:rwx /opt/software/yourdir 读取acl sudo getfacl -R /opt/software/yourdir -R 是循环读取子目录和文件的意思…