Day48

Day48

手写Spring-MVC之前后置处理器与异常处理

前后置处理器

概念:从服务器获取的JSON数据可能是加密后的,因此服务端获取的时候需要进行解密(前置处理器)。

而从服务器传出的JSON数据可能需要加密,因此需要在处理返回值的时候进行加密(后置处理器)。

思路:

首先搭建前后置处理器的框架

和是否处理JSON格式的数据类似,需要根据注解判断controller层中的方法是否需要对JSON格式的数据进行解密或者返回数据进行加密,因此要添加两个注解@BeforeAdviser和@AfterAdviser

@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface BeforeAdviser {
}@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AfterAdviser {
}

controller层的方法需要添加相应的注解标识

/*** 使用postman发送请求* url:http://localhost:8080/user/test12.action* json:{"username":"zs","password":"123123"}* 传递JSON参数和返回JSON*/@RequestMapping("/test12.action")@ResponseBody@AfterAdviserpublic User test12(@RequestBody @BeforeAdviser User user){System.out.println("user对象:"+ user);return user;

同时需要在参数描述类中和方法描述类中添加是否有相应注解的属性:

/*** 参数描述类
*/
@NoArgsConstructor
@AllArgsConstructor
@Data
public class ParameterDefinition {private String name;//参数名private Class<?> clazz;//参数类型private int index;//参数下标private Type[] actualTypeArguments;//参数泛型的数组private boolean requestBodyHasOrNot;//参数上是否有@RequestBody注解private boolean beforeAdviserHasOrNot;//参数上是否有@BeforeAdviser注解}/*** 方法描述类
*/
@NoArgsConstructor
@AllArgsConstructor
@Data
public class MethodDefinition {private String requestMappingPath;//子级URiprivate String name;//方法名private Method method;//方法对象private Class<?> returnClazz;//返回值类型private List<ParameterDefinition> parameterDefinitions;//参数描述类对象的集合private boolean responseBodyHasOrNot;//方法上是否有@ResponseBody注解private boolean afterAdviserHasOrNot;//方法上是否有@AfterAdviser注解
}

添加后监听器中封装部分也要进行相应修改:

//获取参数上是否有@BeforeAdviser注解
boolean beforeAdviserHasOrNot = false;
BeforeAdviser beforeAdviser = parameters[i].getAnnotation(BeforeAdviser.class);
if(beforeAdviser!=null){beforeAdviserHasOrNot = true;
}ParameterDefinition parameterDefinition = new ParameterDefinition(parameterName, parameterType, index,actualTypeArguments,requestBodyHasOrNot,beforeAdviserHasOrNot);//封装参数描述类对象
parameterList.add(parameterDefinition);
//获取方法上是否有@AfterAdviser注解
boolean afterAdviserHasOrNot = false;
AfterAdviser afterAdviser = method.getAnnotation(AfterAdviser.class);
if(afterAdviser!=null){afterAdviserHasOrNot = true;
}
MethodDefinition methodDefinition = new MethodDefinition(sonUri, methodName, method, returnType, parameterList,responseBodyHasOrNot,afterAdviserHasOrNot);//封装方法描述类对象

至此,监听器就能把信息记录下来,调度的DispatcherServlet进行工作的时候就可以获取到相应的注解信息。

前置处理器的使用是在获取参数类型的时候会判断是否有@BeforeAdviser注解,如果有则代表需要进行解密操作:

//解密
if(parameterDefinition.isBeforeAdviserHasOrNot()){//在这里进行具体的解密操作吗?
}

在处理返回值的时候会判断方法是否有@AfterAdviser注解,如果有则代表需要进行加密操作:

//加密
if(methodDefinition.isAfterAdviserHasOrNot()){//在这里进行加密操作吗?
}

进行具体的解密和加密操作:

前后置处理器的框架搭建好之后,会发现一个问题,如果在DispatcherServlet中进行具体的解密加密的话,那么在用户使用该框架的时候,就只能使用框架所规定的解密加密,这显然不具有灵活性。不同的用户解密、加密逻辑不同,所以这里的思路是在框架中只写抽象类,在DispatcherServlet中利用多态创建抽象类的继承类对象,调用继承类对象中的解密加密方法。而在web模块中用户可以自己重写一个解密加密抽象方法,这样调用的就是用户自定义的逻辑方法了。

抽象方法:

public abstract class HanderAdviserResolver {public abstract String beforeRequestBody(String reqData);public abstract String afterResponseBody(String respData);public String before(String reqData){return beforeRequestBody(reqData);}public String after(String respData){return afterResponseBody(respData);}
}

注意:这里的抽象方法是交给用户重写的,而自己的成员方法则直接调用抽象方法,通过这种方式实现在DispatcherServlet中调用用户重写方法的逻辑。

用户自定义前后置处理器:

public class BeforeAndAfterAdviser extends HanderAdviserResolver {@Overridepublic String beforeRequestBody(String reqData) {System.out.println("解密:"+reqData);return reqData;}@Overridepublic String afterResponseBody(String respData) {System.out.println("加密:"+respData);return respData;}
}

又一个问题来了,框架中怎样拿到用户自定义的类对象呢?思路和监听器拿到controller层类对象相似,通过配置文件拿到注解类,注解类通过一个注解注明用户自定义的前后置处理器路径。这样的注解叫做使能注解,它的功能就是使得DispatcherServlet能够拿到用户自定义类。然后在DispatcherServlet中重写init()方法,在方法中获取配置文件信息,进而拿到注解类,通过注解类的注解信息拿到自定义前后置处理器类对象,调用其重写的解密加密处理方法。

使能注解:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface EnableAdviser {String adviserPackage();
}

注解类:

@EnableAdviser(adviserPackage= "com.qf.shop.web.adviser.BeforeAndAfterAdviser")

DispatcherServlet中:

private HanderAdviserResolver adviserResolver;public HanderAdviserResolver getAdviserResolver(String config){try {Class<?> clazz = Class.forName(config);EnableAdviser enableAdviserAnnotation = clazz.getAnnotation(EnableAdviser.class);String adviserPackage = enableAdviserAnnotation.adviserPackage();if(adviserPackage!=null){Class<?> adviserClass = Class.forName(adviserPackage);adviserResolver = (HanderAdviserResolver) adviserClass.newInstance();}return adviserResolver;} catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {throw new RuntimeException(e);}}@Overridepublic void init() throws ServletException {ServletContext servletContext = this.getServletContext();String config = servletContext.getInitParameter("config");adviserResolver = getAdviserResolver(config);}
//加密
if(methodDefinition.isAfterAdviserHasOrNot()){jsonString = adviserResolver.after(jsonString);
}
//解密
if(parameterDefinition.isBeforeAdviserHasOrNot()){jsonStr = adviserResolver.before(jsonStr);
}

异常

概念:DispatcherServlet中对于全局的异常需要进行处理,而具体如何处理也是由业务决定的,换言之是用户进行定义而非框架中写死。但是框架中又需要调用处理异常的方法,如何处理?-和前后置处理器一样,通过多态,servlet调用的是用户继承框架中抽象类的类重写的方法。

抽象类:

public abstract class HanderGlobalException {public abstract void handlerException(Exception err, HttpServletRequest request, HttpServletResponse response);public void hander(Exception err, HttpServletRequest request, HttpServletResponse response){handlerException(err,request,response);}
}

用户继承类:

public class GlobalException extends HanderGlobalException {@Overridepublic void handlerException(Exception err, HttpServletRequest request, HttpServletResponse response) {System.out.println("处理全局异常......");try {request.getRequestDispatcher("/err.jsp").forward(request,response);} catch (ServletException | IOException e) {throw new RuntimeException(e);}}
}

那么框架如何拿到用户自己写的处理异常类和方法呢?思路和前后置处理器一样,DispactherServlet在重写的init()方法中通过配置文件拿到配置类,配置类通过框架中写的使能注解将用户自定义的异常处理类路径告诉servlet。

使能注解:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface EnableException {String exceptionPackage();
}

配置类:

/*** 当前项目的配置类
*/
@Configuration("com.qf.shop.web.controller")
@EnableAdviser(adviserPackage= "com.qf.shop.web.adviser.BeforeAndAfterAdviser")
@EnableException(exceptionPackage = "com.qf.shop.web.globalException.GlobalException")
public class AppConfig {
}

DispatcherServlet:

private HanderGlobalException globalException;public HanderGlobalException getGlobalException(String config){try {Class<?> clazz = Class.forName(config);EnableException enableExceptionAnnotation = clazz.getAnnotation(EnableException.class);String exceptionPackage = enableExceptionAnnotation.exceptionPackage();if(exceptionPackage!=null){Class<?> exceptionClass = Class.forName(exceptionPackage);globalException = (HanderGlobalException) exceptionClass.newInstance();}return globalException;} catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {throw new RuntimeException(e);}
}@Overridepublic void init() throws ServletException {ServletContext servletContext = this.getServletContext();String config = servletContext.getInitParameter("config");adviserResolver = getAdviserResolver(config);globalException = getGlobalException(config);}
try{//调用Controller层里的某个方法Object returnVal = method.invoke(t, args);if(returnVal!=null){//处理返回值handlerReturnVal(methodDefinition,returnVal,request,response,model);}
}catch (Exception e){globalException.hander(e,request,response);//处理全局异常
}

数据库模块

功能:该模块不是Spring-MVC中的一部分,是用来和数据库交互的框架。

思路:分包逐步实现JDBC。

base包:根据获得的结果集实现封装功能的接口,包含结果集处理器接口和行处理器接口

/**
* 结果集处理器的接口
*实现类:BeanHandler(获取单个对象)、BeanListHandler(获取对象集合)
*/
public interface ResultSetHandler<T> {public T handler(ResultSet resultSet)throws SQLException,IllegalAccessException,InstantiationException, InvocationTargetException;
}
/**
* 行处理器的接口
* @param <T>
*/
public interface RowProcessor<T> {public T toArray(ResultSet resultSet) throws SQLException;
}

handler包:实现接口:

/**
* 结果集处理接口的实现类
* 操作结果集并封装实体类
* @param <T>
*/
public class BeanHandler<T> implements ResultSetHandler<T> {private Class beanClass;public BeanHandler(Class beanClass) {this.beanClass = beanClass;}@Overridepublic T handler(ResultSet resultSet) throws SQLException, IllegalAccessException, InstantiationException, InvocationTargetException {ResultSetMetaData metaData = resultSet.getMetaData();int columnCount = metaData.getColumnCount();if(resultSet.next()){T t = (T) beanClass.newInstance();for (int i = 0; i < columnCount; i++) {String columnName = metaData.getColumnName(i + 1);Object columnValue = resultSet.getObject(columnName);BeanUtils.copyProperty(t,columnName,columnValue);}return t;}return null;}
}
/**
* 结果集处理接口的实现类
* 处理结果集并封装为集合
* @param <T>
*/
public class BeanListHandler<T> implements ResultSetHandler<List<T>> {private Class beanListClass;public BeanListHandler(Class beanListClass) {this.beanListClass = beanListClass;}@Overridepublic List<T> handler(ResultSet resultSet) throws SQLException, IllegalAccessException, InstantiationException, InvocationTargetException {List<T> list = new ArrayList<>();ResultSetMetaData metaData = resultSet.getMetaData();int columnCount = metaData.getColumnCount();while(resultSet.next()){T t = (T) beanListClass.newInstance();for (int i = 0; i < columnCount; i++) {String columnName = metaData.getColumnName(i + 1);T columnValue = (T) resultSet.getObject(columnName);BeanUtils.copyProperty(t,columnName,columnValue);list.add(t);}}return list;}
}
/**
* 结果集处理接口的实现类
* 操作结果集并封装数组对象
*
* @param <T>
*/
public class ArrayHandler<T> implements ResultSetHandler<T> {//行处理器private RowProcessor<T> rowProcessor;public ArrayHandler(RowProcessor<T> rowProcessor) {this.rowProcessor = rowProcessor;}@Overridepublic T handler(ResultSet resultSet) throws SQLException, IllegalAccessException, InstantiationException, InvocationTargetException {return  rowProcessor.toArray(resultSet);}
}

其中,行处理的逻辑是拿到结果集后交给自定义的行处理接口实现类方法处理,这样做的目的是统一代码的格式(见后续web项目中的使用)。

processor包(处理行):

/**
* 行处理器的实现类,将结果集中的数据获取并封装成数组
* @param <T>
*/
public class BaskRowProcessor<T> implements RowProcessor<T[]> {private Class arrayTClass;public BaskRowProcessor(Class arrayTClass) {this.arrayTClass = arrayTClass;}@Overridepublic T[] toArray(ResultSet resultSet) throws SQLException {//创建数据容器List<T> list = new ArrayList<>();//遍历结果集while(resultSet.next()){T t = (T) resultSet.getObject(1);list.add(t);}if(list.size()==0||resultSet==null){throw new RuntimeException("参数异常无法获取泛型数组");}else {//创建数组T[] ts = (T[]) Array.newInstance(arrayTClass, list.size());//添加数据for (int i = 0; i < list.size(); i++) {ts[i] = list.get(i);}return ts;}}
}

通过结果集返回封装好的对象、列表、数组的功能已实现,接下类实现操作数据库返回结果集的功能:

core包:

public class QueryRunner {private DruidDataSource dataSource;private ThreadLocal<Connection> local = new ThreadLocal<>();public QueryRunner(DruidDataSource dataSource) {this.dataSource = dataSource;}//获取连接private Connection getConnection() throws SQLException {Connection connection = local.get();if(connection==null){connection = dataSource.getConnection();local.set(connection);}return connection;}//配置参数private PreparedStatement getPreparedStatement(Connection connection,String sql,Object... args) throws SQLException {PreparedStatement statement = connection.prepareStatement(sql);for (int i = 0; i < args.length; i++) {statement.setObject(i+1,args[i]);}return statement;}//更新操作public int update(String sql,Object... args) throws SQLException {Connection connection = getConnection();PreparedStatement statement = getPreparedStatement(connection, sql, args);int i = statement.executeUpdate();return i;}//查询操作public <T> T query(ResultSetHandler<T> handler,String sql,Object... args) throws SQLException, InvocationTargetException, IllegalAccessException, InstantiationException {Connection connection = getConnection();PreparedStatement statement = getPreparedStatement(connection, sql, args);ResultSet resultSet = statement.executeQuery();T t = handler.handler(resultSet);return t;}}

至此,框架搭建完毕,接下来以用户创建的web项目举例:

数据库工具类:

public class JDBCUtils {//德鲁伊连接池引用private static DruidDataSource dataSource;static{//创建德鲁伊连接池dataSource = new DruidDataSource();//获取配置信息Properties properties = new Properties();try {properties.load(JDBCUtils.class.getClassLoader().getResourceAsStream("DBConfig.properties"));String username = properties.getProperty("username");String password = properties.getProperty("password");String url = properties.getProperty("url");String driverName = properties.getProperty("driverName");dataSource.setUrl(url);dataSource.setUsername(username);dataSource.setPassword(password);dataSource.setDriverClassName(driverName);} catch (IOException e) {throw new RuntimeException(e);}}public static QueryRunner getQueryRunner(){return new QueryRunner(dataSource);}
}

controller层中对数据库进行操作:

//---操作数据库---------------------------------------------------------------------------------------
@RequestMapping("/test13.action")
public void test13() throws SQLException {//操作更新语句JDBCUtils.getQueryRunner().update("update user set password = ? where username = ?","123456","zs");
}
@RequestMapping("/test14.action")
public void test14() throws SQLException, InvocationTargetException, IllegalAccessException, InstantiationException {//查询对象User zs = JDBCUtils.getQueryRunner().query(new BeanHandler<>(User.class), "select * from user where username = ?", "zs");System.out.println(zs);
}
@RequestMapping("/test15.action")
public void test15() throws SQLException, InvocationTargetException, IllegalAccessException, InstantiationException {//查询对象列表List<User> users = JDBCUtils.getQueryRunner().query(new BeanListHandler<>(User.class), "select * from user");for (User user : users) {System.out.println("列表中的对象:"+user);}
}
@RequestMapping("/test16.action")
public void test16() throws SQLException, InvocationTargetException, IllegalAccessException, InstantiationException {//查询所有的usernameString[] usernames = JDBCUtils.getQueryRunner().query(new ArrayHandler<>(new BaskRowProcessor<>(String.class)),"select username from user");System.out.println(usernames);
}

遇到的问题:

1.无法找到数据库连接的配置文件DBCfig.properties

解决方案:将配置文件放到src/main/resources文件夹中去,这样 资源文件才会被复制到target/classes 目录下。

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

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

相关文章

VMware虚拟机迁移:兼用性踩坑和复盘

文章目录 方法失败情况分析&#xff1a;参考文档 方法 虚拟机关机&#xff0c;整个文件夹压缩后拷贝到新机器中&#xff0c;开机启用即可 成功的情况&#xff1a; Mac (intel i5) -> Mac (intel i7)Mac (intel, MacOS - VMware Fusion) -> DELL (intel, Windows - VMw…

Zynq7000系列FPGA中的DMA控制器简介(二)

AXI互连上的DMA传输 所有DMA事务都使用AXI接口在PL中的片上存储器、DDR存储器和从外设之间传递数据。PL中的从设备通过DMAC的外部请求接口与DMAC通信&#xff0c;以控制数据流。这意味着从设备可以请求DMA交易&#xff0c;以便将数据从源地址传输到目标地址。 虽然DMAC在技术…

mysql5.7安装使用

mysql5.7安装包&#xff1a;百度网盘 提取码: 0000 一、 安装步骤 双击安装文件 选择我接受许可条款–Next 选择自定义安装&#xff0c;下一步 选择电脑对应的系统版本后(我的系统是64位)&#xff0c;点击中间的右箭头&#xff0c;选择Next 选择安装路径–Next 执行…

.NET之C#编程:懒汉模式的终结,单例模式的正确打开方式

概述 在C#编程世界中&#xff0c;单例模式是一种常见的设计模式&#xff0c;用于确保一个类只有一个实例&#xff0c;并提供一个全局访问点。然而&#xff0c;传统的懒汉模式实现方式在多线程环境下存在安全隐患。本文将深入探讨单例模式的正确实现姿势&#xff0c;带你走出懒汉…

matlab可以把图像数据转换为小波分析吗

&#x1f3c6;本文收录于《CSDN问答解答》专栏&#xff0c;主要记录项目实战过程中的Bug之前因后果及提供真实有效的解决方案&#xff0c;希望能够助你一臂之力&#xff0c;帮你早日登顶实现财富自由&#x1f680;&#xff1b;同时&#xff0c;欢迎大家关注&&收藏&…

【后端面试题】【中间件】【NoSQL】ElasticSearch 节点角色、写入数据过程、Translog和索引与分片

中间件的常考方向&#xff1a; 中间件如何做到高可用和高性能的&#xff1f; 你在实践中怎么做的高可用和高性能的&#xff1f; Elasticsearch节点角色 Elasticsearch的节点可以分为很多种角色&#xff0c;并且一个节点可以扮演多种角色&#xff0c;下面列举几种主要的&…

【软件测试】白盒测试(知识点 + 习题 + 答案)

《 软件测试基础持续更新中》 最近大家总是催更……&#xff0c;我也是百忙之中给大家详细总结了白盒测试的重点内容&#xff01; 知识点题型答案&#xff0c;让你用最短的时间&#xff0c;学到最高效的知识&#xff01; 整理不易&#xff0c;求个三连 ₍ᐢ..ᐢ₎ ♡ 目录 一、…

Spring专题一:源码编译

下载源码 因为公司使用的是Spring5.2.x所以就下载了这个版本&#xff0c;github源码地址如下&#xff1a; GitHub - spring-projects/spring-framework at v5.2.6.RELEASE&#xff1a; 如果网络不稳定可以使用下载压缩版即可&#xff0c;网络稳定的话还是建议使用git clone …

JDBC中的元数据是什么?如何获取?

JDBC中的元数据&#xff08;MetaData&#xff09;是关于数据的数据&#xff0c;它描述了数据库的结构、表的结构、列的数据类型、存储过程、支持的SQL语法和数据库产品的版本等信息。元数据在JDBC中主要通过DatabaseMetaData和ResultSetMetaData两个接口来获取。 1. DatabaseM…

代理IP用什麼協議?

代理IP的運作主要依賴兩種協議&#xff1a;HTTP代理協議和SOCKS代理協議。 HTTP代理協議 HTTP代理協議是最常見的代理協議。它主要用於HTTP請求&#xff0c;即我們常說的網頁流覽。當你通過HTTP代理伺服器訪問網站時&#xff0c;你的設備會先向代理伺服器發送HTTP請求&#x…

【redis】redis RDB

1、概述 1.1定义 RDB (Redis Database) 是 Redis 的默认持久化机制&#xff0c;它能够在指定的时间间隔内将内存中的数据集快照写入磁盘。RDB 持久化产生的文件是一个经过压缩的二进制文件&#xff0c;通过该文件可以还原生成 RDB 文件时的数据库状态。 1.2特点 一次性全量备…

RStudio学习笔记(三):其他数据结构

1、矩阵 在R语言中&#xff0c;矩阵是二维的&#xff0c;包括行和列&#xff0c;其中分为数值型、字符型、逻辑型三种&#xff0c;在每个矩阵中的矩阵元素的类型必须一致&#xff0c;可以通过matrix函数创建矩阵。 m <- matrix(1:20, nrow 4, ncol 5) # 创建一个四行…

【工具分享】SQLmap

文章目录 工具介绍安装方式环境准备安装 sqlmap 工具介绍 sqlmap 是一个非常强大的自动化 SQL 注入工具&#xff0c;主要用于渗透测试和安全审计。它能够检测和利用 SQL 注入漏洞&#xff0c;进而访问数据库服务器。 GitHub&#xff1a;https://github.com/sqlmapproject/sql…

JS:防抖与节流函数的实现与应用

一、防抖 防抖&#xff1a;一连串操作只执行一次。通过设置一个延迟时间&#xff0c;如果这段时间没有再次执行操作则运行目标函数&#xff0c;否则重新计时。 下面是一个防抖函数代码&#xff1a; let a1; const add () > {a; }; const debounce (fun: Function, dela…

Everything 一款功能强大的搜索工具

要在电脑上使用Everything搜索文件&#xff0c;您需要使用以下步骤&#xff1a; 在您的电脑上下载并安装Everything软件。您可以从官方网站https://www.voidtools.com/downloads/下载最新版本的软件。 安装完成后&#xff0c;打开Everything软件。 在搜索栏中输入您要查找的文…

Java中如何处理大数据量的排序?

Java中如何处理大数据量的排序&#xff1f; 大家好&#xff0c;我是免费搭建查券返利机器人省钱赚佣金就用微赚淘客系统3.0的小编&#xff0c;也是冬天不穿秋裤&#xff0c;天冷也要风度的程序猿&#xff01;今天&#xff0c;我们来讨论一下在Java中如何处理大数据量的排序问题…

JsonCpp:更简洁的序列化及反序列化

简介 jsoncpp 提供了一组简单易用的 API&#xff0c;使得在 C 程序中处理 JSON 数据变得更加方便和高效。 安装 linux环境下安装jsoncpp sudo apt-get update sudo apt-get install --reinstall libjsoncpp-dev建立软链接确保编译器找到头文件 #include <json/json.h>…

Java数据结构6-栈与队列

1. 栈(Stack) 1.1 概念 栈&#xff1a;一种特殊的线性表&#xff0c;其只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除操作的一端称为栈顶&#xff0c;另一端称为栈底。栈中的数据元素遵守后进先出LIFO&#xff08;Last In First Out&#xff09;的原则 压栈…

机电公司管理小程序的设计

管理员账户功能包括&#xff1a;系统首页&#xff0c;个人中心&#xff0c;用户管理&#xff0c;管理员管理&#xff0c;客户管理&#xff0c;公告管理&#xff0c;考勤管理&#xff0c;请假管理 微信端账号功能包括&#xff1a;系统首页&#xff0c;公告&#xff0c;机电零件&…

gitee配置ssh教程

生成公钥 执行命令&#xff1a; ssh-keygen -t rsa查看公钥 cat ~/.ssh/id_rsa.pub这个公钥就是要复制粘贴到Gitee中的ssh公钥。 配置Gitee SSH公钥 来到Gitee的ssh公钥中&#xff0c;配置