Vue3项目中引入 ElementUI
1.安装
vue3中使用如下命令通过 npm 安装 ECharts(本人项目使用的安装方式)
npm install element-plus --save
也可以使用其他的包管理起进行安装:
# Yarn yarn add element-plus # pnpm pnpm install element-plus
2.引入
element-plus分为全局引入和按需引入两种方式,一般在工程项目中,由于全局引入会导致不必要的资源加载,为提升项目性能,建议进行按需引入。以下我们对两种引入方式进行介绍。
2.1 全局引入
全局引入就是在项目入口(main.ts)文件直接引入组件以及组件全部的样式文件;代码如下所示:
// main.ts import { createApp } from 'vue' import ElementPlus from 'element-plus' //全局引入 import 'element-plus/dist/index.css' import App from './App.vue' const app = createApp(App) app.use(ElementPlus) app.mount('#app')
2.2 按需引入
在vue3中按需引入ElementUI,需要使用其他的插件辅助,需要安装unplugin-vue-components 和 unplugin-auto-import这两款插件;安装方法如下:
npm install -D unplugin-vue-components unplugin-auto-import
然后再vite或者webpack配置中添加相应的配置,如下所示:
vite
// vite.config.ts import { defineConfig } from 'vite' import AutoImport from 'unplugin-auto-import/vite' import Components from 'unplugin-vue-components/vite' import { ElementPlusResolver } from 'unplugin-vue-components/resolvers' export default defineConfig({ // ... plugins: [ // ... AutoImport({ resolvers: [ElementPlusResolver()], }), Components({ resolvers: [ElementPlusResolver()], }), ], })
SpringBoot不仅继承了Spring框架原有的优秀特性,而且还通过简化配置来进一步简化了Spring应用的整个搭建和开发过程。在Spring-Boot项目开发中,存在着本模块的代码需要访问外面模块接口,或外部url链接的需求。 调用外部接口是指在应用程序中与其他系统、服务或服务端点进行通信,以获取数据或执行某些操作。这种通信可以通过 HTTP、HTTPS、SOAP、gRPC 等协议来实现。
调用外部接口通常涉及以下几个步骤:
创建请求:根据接口文档或约定,构造请求的 URL、请求方法(如 GET、POST、PUT、DELETE 等)、请求头、请求参数等信息。
发送请求:使用合适的客户端工具(如 RestTemplate、WebClient、Feign 等)发送请求。这些工具提供了便捷的 API 来发送请求并将响应结果返回给你。
处理响应:根据接口的响应格式(如 JSON、XML 等),解析响应内容并提取需要的数据。你可以使用解析库(如 Jackson、Gson、JAXB 等)来处理响应内容。
错误处理:在调用外部接口时,可能会遇到各种错误情况,如网络连接失败、接口返回错误码等。你需要适当处理这些错误情况,例如进行重试、回退、记录日志等操作。
在调用外部接口时,还需要注意以下事项:
接口安全性:如果接口需要身份验证或授权,你需要提供相应的凭据(如 API 密钥、令牌)或配置安全设置,以确保请求能够被正确处理。
异步调用和并发性:如果你的应用程序需要高并发或异步处理,你可以考虑使用基于消息队列、异步任务或多线程等技术,在后台进行接口调用,以提高系统的性能和可伸缩性。
监控和日志记录:对于重要的接口调用,建议设置适当的监控和日志记录,以便及时发现问题并进行故障排查。
总之,调用外部接口是实现应用程序与其他系统集成的重要方式,它能够使你的应用程序获取到外部数据,实现复杂的业务逻辑,并与其他系统进行交互。选择合适的调用方式和合理处理错误情况,能够保证接口调用的可靠性和性能。
/** 1. 跨域请求工具类 */ public class HttpClientUtils { public static String doGet(String url, Map<String, String> param) { // 创建Httpclient对象 CloseableHttpClient httpclient = HttpClients.createDefault(); String resultString = ""; CloseableHttpResponse response = null; try { // 创建uri URIBuilder builder = new URIBuilder(url); if (param != null) { for (String key : param.keySet()) { builder.addParameter(key, param.get(key)); } } URI uri = builder.build(); // 创建http GET请求 HttpGet httpGet = new HttpGet(uri); // 执行请求 response = httpclient.execute(httpGet); // 判断返回状态是否为200 if (response.getStatusLine().getStatusCode() == 200) { resultString = EntityUtils.toString(response.getEntity(), "UTF-8"); } } catch (Exception e) { e.printStackTrace(); } finally { try { if (response != null) { response.close(); } httpclient.close(); } catch (IOException e) { e.printStackTrace(); } } return resultString; } public static String doGet(String url) { return doGet(url, null); } public static String doPost(String url, Map<String, String> param) { // 创建Httpclient对象 CloseableHttpClient httpClient = HttpClients.createDefault(); CloseableHttpResponse response = null; String resultString = ""; try { // 创建Http Post请求 HttpPost httpPost = new HttpPost(url); // 创建参数列表 if (param != null) { List<NameValuePair> paramList = new ArrayList<>(); for (String key : param.keySet()) { paramList.add(new BasicNameValuePair(key, param.get(key))); } // 模拟表单 UrlEncodedFormEntity entity = new UrlEncodedFormEntity(paramList); httpPost.setEntity(entity); } // 执行http请求 response = httpClient.execute(httpPost); resultString = EntityUtils.toString(response.getEntity(), "utf-8"); } catch (Exception e) { e.printStackTrace(); } finally { try { response.close(); } catch (IOException e) { e.printStackTrace(); } } return resultString; } public static String doPost(String url) { return doPost(url, null); } public static String doPostJson(String url, String json) { // 创建Httpclient对象 CloseableHttpClient httpClient = HttpClients.createDefault(); CloseableHttpResponse response = null; String resultString = ""; try { // 创建Http Post请求 HttpPost httpPost = new HttpPost(url); // 创建请求内容 StringEntity entity = new StringEntity(json, ContentType.APPLICATION_JSON); httpPost.setEntity(entity); // 执行http请求 response = httpClient.execute(httpPost); resultString = EntityUtils.toString(response.getEntity(), "utf-8"); } catch (Exception e) { e.printStackTrace(); } finally { try { response.close(); } catch (IOException e) { e.printStackTrace(); } } return resultString; } }
RestTemplate 是 Spring 框架提供的用于访问 RESTful 服务的客户端工具类。它封装了常见的 HTTP 请求操作,提供了简单易用的 API,这里主要介绍Get和Post方法的使用。
在 RestTemplate 中,getForEntity 方法有多个重载形式,可以根据需要选择合适的方法进行 GET 请求。提供了getForObject 、getForEntity两种方式。
以下是 getForEntity 方法的三种形式:
1. getForEntity(String url, Class<T> responseType, Object... uriVariables); 2. getForEntity(String url, Class<T> responseType, Map<String, ?> uriVariables); 3. getForEntity(URI url, Class<T> responseType);
以下是对每个方法的说明和示例:
getForEntity(String url, Class responseType, Object... uriVariables): 该方法提供了三个参数,其中url为请求的地址,responseType为请求响应body的包装类型,urlVariables为url中的参数绑定
此方法发送带有路径参数的 GET 请求,并以可变参数的形式传递路径参数。 路径参数将按顺序替换 URL 中的占位符 {}。
示例:
RestTemplate restTemplate = new RestTemplate(); ResponseEntity<User> response = restTemplate.getForEntity("http://example.com/api/users/{id}", User.class, 1); User user = response.getBody();
getForEntity(String url, Class responseType, Map<String, ?> uriVariables):
此方法发送带有路径参数的 GET 请求,并以键值对的形式传递路径参数。 路径参数通过键值对映射进行替换。
示例:
RestTemplate restTemplate = new RestTemplate(); Map<String, Integer> uriVariables = new HashMap<>(); uriVariables.put("id", 1); ResponseEntity<User> response = restTemplate.getForEntity("http://example.com/api/users/{id}", User.class, uriVariables); User user = response.getBody();
getForEntity(URI url, Class responseType):
此方法发送 GET 请求,并通过 URI 对象来指定请求的完整 URL。
示例:
RestTemplate restTemplate = new RestTemplate(); URI url = new URI("http://example.com/api/users/1"); ResponseEntity<User> response = restTemplate.getForEntity(url, User.class); User user = response.getBody(); //或者 RestTemplate restTemplate=new RestTemplate(); UriComponents uriComponents=UriComponentsBuilder.fromUriString("http://example.com/api/users/{id}") .build() .expand(1) .encode(); URI uri=uriComponents.toUri(); ResponseEntity<User> response = restTemplate.getForEntity(uri, User.class);
以下是 getForEntity 方法的三种形式:
1. getForObject(String url, Class<T> responseType, Object... uriVariables); 2. getForObject(String url, Class<T> responseType, Map<String, ?> uriVariables); 3. getForObject(URI url, Class<T> responseType);
这段代码是 RestTemplate 类中的 getForObject 方法的重写实现。它用于发送 GET 请求,并返回一个指定类型的对象,而不是完整的响应实体。
以下是对每个方法的说明和示例:
getForObject(String url, Class responseType, Object... uriVariables):
此方法发送带有路径参数的 GET 请求,并以可变参数的形式传递路径参数。 路径参数将按顺序替换 URL 中的占位符 {}。 最后,返回响应体中转换为指定类型的对象。
示例:
RestTemplate restTemplate = new RestTemplate(); User user = restTemplate.getForObject("http://example.com/api/users/{id}", User.class, 1);
getForObject(String url, Class responseType, Map<String, ?> uriVariables):
此方法发送带有路径参数的 GET 请求,并以键值对的形式传递路径参数。 路径参数通过键值对映射进行替换。 最后,返回响应体中转换为指定类型的对象。
示例:
RestTemplate restTemplate = new RestTemplate(); Map<String, Integer> uriVariables = new HashMap<>(); uriVariables.put("id", 1); User user = restTemplate.getForObject("http://example.com/api/users/{id}", User.class, uriVariables);
getForObject(URI url, Class responseType):
此方法发送 GET 请求,并通过 URI 对象来指定请求的完整 URL。 最后,返回响应体中转换为指定类型的对象。
示例:
RestTemplate restTemplate = new RestTemplate(); URI url = new URI("http://example.com/api/users/1"); User user = restTemplate.getForObject(url, User.class);
getForObject 和 getForEntity 是 RestTemplate 类中用于发送 GET 请求并获取响应的两个方法。它们在功能上有一些相似之处,但也有一些区别。
相同点:
都用于发送 GET 请求,并接收服务器返回的响应。 都支持传递 URL、路径参数和查询参数来构建完整的请求 URL。 都可以指定响应的期望类型,并进行自动的类型转换。
区别:
getForObject 方法返回的是服务器响应体中的内容,并将其转换为指定类型的对象。这意味着你直接获取到了响应体的内容。 getForEntity 方法返回的是一个 ResponseEntity 对象,包含了完整的响应信息,包括响应头、响应状态码和响应体。可以通过 ResponseEntity 对象来获取更多的响应信息。 选择使用哪个方法取决于你对响应的需求:
如果你只需要获得响应体的内容,并将其转换为指定类型的对象,可以使用 getForObject 方法。这样可以简化代码,仅关注响应体的内容。 如果你还需要获取更多的响应信息,例如响应头或状态码,或者需要对响应进行更细粒度的处理,那么可以使用 getForEntity 方法。它提供了更多的灵活性,可以访问完整的响应信息。
示例:
RestTemplate restTemplate = new RestTemplate(); // 使用 getForObject 方法 User user1 = restTemplate.getForObject("http://example.com/api/users/1", User.class); // 使用 getForEntity 方法 ResponseEntity<User> responseEntity = restTemplate.getForEntity("http://example.com/api/users/1", User.class); User user2 = responseEntity.getBody(); HttpStatus statusCode = responseEntity.getStatusCode(); HttpHeaders headers = responseEntity.getHeaders();
总之,getForObject 和 getForEntity 都是用于发送 GET 请求并获取响应的方法,选择使用哪个方法取决于你对响应的需求。
Post请求提供有postForEntity、postForObject和postForLocation三种方式,其中每种方式都有三种方法,下面介绍postForEntity的使用方法。
以下是 postForEntity 方法的三种形式:
1. postForEntity(String url, @Nullable Object request,Class<T> responseType, Object... uriVariables); 2. postForEntity(String url, @Nullable Object request,Class<T> responseType, Map<String, ?> uriVariables); 3. postForEntity(URI url, @Nullable Object request, Class<T> responseType);
这段代码是 RestTemplate 类中的 postForEntity 方法的重写实现。它用于发送 POST 请求,并返回一个包含完整响应信息的 ResponseEntity 对象,而不仅仅是响应体内容。
下面是对每个重写方法的详细说明和示例:
postForEntity(String url, @Nullable Object request, Class responseType, Object... uriVariables):
此方法发送带有路径参数的 POST 请求,并以可变参数的形式传递路径参数。 request 参数用于发送 POST 请求时的请求体内容。 最后,返回一个包含完整响应信息的 ResponseEntity 对象,其中包括响应头、响应状态码和响应体。
示例:
RestTemplate restTemplate = new RestTemplate(); User userRequest = new User("John", "Doe"); ResponseEntity<User> responseEntity = restTemplate.postForEntity("http://example.com/api/users", userRequest, User.class, 1); User user = responseEntity.getBody(); HttpStatus statusCode = responseEntity.getStatusCode(); HttpHeaders headers = responseEntity.getHeaders();
postForEntity(String url, @Nullable Object request, Class responseType, Map<String, ?> uriVariables):
此方法发送带有路径参数的 POST 请求,并以键值对的形式传递路径参数。 request 参数用于发送 POST 请求时的请求体内容。 最后,返回一个包含完整响应信息的 ResponseEntity 对象。
示例:
RestTemplate restTemplate = new RestTemplate(); User userRequest = new User("John", "Doe"); Map<String, Integer> uriVariables = new HashMap<>(); uriVariables.put("id", 1); ResponseEntity<User> responseEntity = restTemplate.postForEntity("http://example.com/api/users/{id}", userRequest, User.class, uriVariables); User user = responseEntity.getBody(); HttpStatus statusCode = responseEntity.getStatusCode(); HttpHeaders headers = responseEntity.getHeaders();
postForEntity(URI url, @Nullable Object request, Class responseType):
此方法发送 POST 请求,并通过 URI 对象来指定请求的完整 URL。 request 参数用于发送 POST 请求时的请求体内容。 最后,返回一个包含完整响应信息的 ResponseEntity 对象。
示例:
RestTemplate restTemplate = new RestTemplate(); User userRequest = new User("John", "Doe"); URI url = new URI("http://example.com/api/users"); ResponseEntity<User> responseEntity = restTemplate.postForEntity(url, userRequest, User.class); User user = responseEntity.getBody(); HttpStatus statusCode = responseEntity.getStatusCode(); HttpHeaders headers = responseEntity.getHeaders();
注:
request 和 uriVariables 是用于构造 POST 请求的两个不同的参数,它们在不同的作用域中起作用。
request 参数:
request 参数表示发送 POST 请求时的请求体内容。 它可以是任意类型的对象,根据实际的请求需求来决定具体的类型。通常情况下,request 参数会作为请求体的内容进行发送。 如果你的 POST 请求不需要请求体,可以将 request 参数设置为 null。
uriVariables 参数:
uriVariables 参数表示可选的路径参数,用于替换 URL 中的占位符。 它是一个可变参数,可以传递多个参数值。参数值的顺序必须与 URL 中占位符的顺序一致。 在发送 POST 请求时,将路径参数与 URL 进行替换,以获得最终的请求 URL。 综上所述,request 和 uriVariables 是两个用于构建 POST 请求不同参数,分别代表请求体的内容和路径参数。需要根据具体的需求来使用它们。如果需要发送请求体,将内容放在 request 参数中;如果需要替换 URL 中的占位符,将参数值传递给 uriVariables 参数。
以下是 postForObject 方法的三种形式:
1. postForObject(String url, @Nullable Object request, Class<T> responseType,Object... uriVariables); 2. postForObject(String url, @Nullable Object request, Class<T> responseType,Map<String, ?> uriVariables); 3. postForObject(URI url, @Nullable Object request, Class<T> responseType);
这是 RestTemplate 类中关于 POST 请求的三个 postForObject 方法的实现。下面将详细解释每个方法及其示例用法:
postForObject(String url, @Nullable Object request, Class responseType, Object... uriVariables)
通过传递 URL 字符串、请求体对象、响应类型和路径参数数组,发送一个 POST 请求,并将响应体转换为指定的类型对象。 如果不需要发送请求体,可以将 request 参数设置为 null。 此方法使用占位符来替换 URL 中的路径参数。
示例:
String url = "http://example.com/api/users/{id}"; User request = new User("John", "Doe"); Class<User> responseType = User.class; Object[] uriVariables = { 1 }; User user = restTemplate.postForObject(url, request, responseType, uriVariables);
postForObject(String url, @Nullable Object request, Class responseType, Map<String, ?> uriVariables)
通过传递 URL 字符串、请求体对象、响应类型和路径参数映射,发送一个 POST 请求,并将响应体转换为指定的类型对象。 如果不需要发送请求体,可以将 request 参数设置为 null。 此方法使用键-值对来替换 URL 中的路径参数。
示例:
String url = "http://example.com/api/users/{id}"; User request = new User("John", "Doe"); Class<User> responseType = User.class; Map<String, Object> uriVariables = new HashMap<>(); uriVariables.put("id", 1); User user = restTemplate.postForObject(url, request, responseType, uriVariables);
postForObject(URI url, @Nullable Object request, Class responseType)
通过传递完整的 URI 对象、请求体对象和响应类型,发送一个 POST 请求,并将响应体转换为指定的类型对象。 如果不需要发送请求体,可以将 request 参数设置为 null。 此方法不支持替换 URL 中的路径参数。
示例:
URI url = new URI("http://example.com/api/users"); User request = new User("John", "Doe"); Class<User> responseType = User.class; User user = restTemplate.postForObject(url, request, responseType);
以下是 postForLocation 方法的三种形式:
1. postForLocation(String url, @Nullable Object request, Object... uriVariables); 2. postForLocation(String url, @Nullable Object request, Map<String, ?> uriVariables); 3. postForLocation(URI url, @Nullable Object request);
这是 RestTemplate 类中关于 POST 请求并获取 Location 头部信息的三个 postForLocation 方法的实现。下面将详细解释每个方法及其示例用法:
postForLocation(String url, @Nullable Object request, Object... uriVariables)
通过传递 URL 字符串、请求体对象和路径参数数组,发送一个 POST 请求,并返回响应头部中的 Location 信息,作为一个 URI 对象返回。 如果不需要发送请求体,可以将 request 参数设置为 null。 此方法使用占位符来替换 URL 中的路径参数。
示例:
String url = "http://example.com/api/users"; User request = new User("John", "Doe"); Object[] uriVariables = { 1 }; URI location = restTemplate.postForLocation(url, request, uriVariables);
postForLocation(String url, @Nullable Object request, Map<String, ?> uriVariables)
通过传递 URL 字符串、请求体对象和路径参数映射,发送一个 POST 请求,并返回响应头部中的 Location 信息,作为一个 URI 对象返回。 如果不需要发送请求体,可以将 request 参数设置为 null。 此方法使用键-值对来替换 URL 中的路径参数。
示例:
String url = "http://example.com/api/users"; User request = new User("John", "Doe"); Map<String, Object> uriVariables = new HashMap<>(); uriVariables.put("id", 1); URI location = restTemplate.postForLocation(url, request, uriVariables);
postForLocation(URI url, @Nullable Object request)
通过传递完整的 URI 对象和请求体对象,发送一个 POST 请求,并返回响应头部中的 Location 信息,作为一个 URI 对象返回。 如果不需要发送请求体,可以将 request 参数设置为 null。 此方法不支持替换 URL 中的路径参数。
示例:
URI url = new URI("http://example.com/api/users"); User request = new User("John", "Doe"); URI location = restTemplate.postForLocation(url, request);
postForEntity、postForObject 和 postForLocation 都是 RestTemplate 类中用于发送 POST 请求的方法,它们之间存在一些联系和区别。
联系:
参数:这三个方法都接受相同的参数:请求的 URL、请求体对象以及可选的路径参数或路径参数映射。 HTTP 请求方法:这三个方法都使用 POST 方法发送请求。
区别:
返回值类型:
postForEntity 返回一个 ResponseEntity 对象,包含完整的响应信息,如响应状态码、头部信息和响应体。 postForObject 返回一个指定的响应体类型的对象,仅包含响应体内容。 postForLocation 仅返回响应头部中的 Location 信息作为一个 URI 对象。
使用场景:
postForEntity 和 postForObject 适用于需要处理完整响应信息的情况,可以获取响应状态码、头部信息和响应体,并根据需要进行处理。 postForLocation 适用于只关注响应头部中的 Location 信息的情况,常用于资源的创建和重定向场景。
总结:
这三个方法都用于发送 POST 请求,根据需要返回不同的信息。如果需要完整的响应信息,包括响应状态码、头部信息和响应体,可以使用 postForEntity;如果只需要响应体内容,可以使用 postForObject;如果仅关注响应头部中的 Location 信息,可以使用 postForLocation。
Feign 是一个声明式的、基于接口的 HTTP 客户端,它简化了使用 Spring Cloud 进行远程服务调用的过程。通过 Feign,可以以声明式的方式定义和调用远程服务接口,而无需手动编写实现代码。
在使用 Feign 进行消费时,需要完成以下几个步骤:
创建 Feign 客户端接口:定义一个接口,使用 @FeignClient 注解指定服务名称和服务地址。该接口中定义了需要调用的远程服务接口的方法及其相关信息,例如 HTTP 方法、URL 路径、请求参数和请求体等。
启用 Feign 客户端:在 Spring Boot 应用程序的启动类或配置类上添加 @EnableFeignClients 注解,以启用 Feign 客户端功能。
使用 Feign 客户端接口:通过注入 Feign 客户端接口对象,即可使用该接口中定义的方法进行远程服务调用。
Feign 会根据接口定义自动实现具体的调用逻辑,并处理请求和响应。
Feign 的工作原理如下:
根据 Feign 客户端接口的定义,在运行时动态生成具体的代理类。
当调用 Feign 客户端接口的方法时,代理类会负责根据方法的元数据(如 HTTP 方法、URL 路径、请求参数等)组装出一个完整的 HTTP 请求。
发送构建好的 HTTP 请求到目标服务端,进行远程调用。
目标服务端响应请求后,将响应结果返回给 Feign 客户端。
Feign 客户端根据定义的返回类型,将响应结果转换为相应的对象或数据,并返回给调用方。
Feign 还具备以下特性:
内置负载均衡:Feign 集成了 Ribbon 负载均衡器,可以在多个服务提供方之间自动进行负载均衡,提高系统的可用性和性能。
自动服务发现:Feign 可以与服务注册中心(如 Eureka)集成,自动从服务注册中心获取服务地址,避免硬编码服务地址。
可插拔的编解码器:Feign 支持多种序列化和反序列化方式,如 JSON、XML 等,可以根据需求选择适合的编解码器。
客户端日志记录:Feign 具备日志记录功能,可以方便地打印出请求和响应的详细信息,便于排查问题和监控性能。
总而言之,Feign 提供了一种简单且优雅的方式来定义和调用远程服务接口,它屏蔽了底层的 HTTP 请求细节,使得远程服务调用更加便捷和灵活。同时,通过与 Spring Cloud 的集成,Feign 还可以享受到负载均衡、服务发现等分布式系统支持。
使用 Feign 进行消费时,需要进行以下步骤:
添加 Feign 依赖:在项目的构建文件中添加 Feign 相关的依赖,例如 Maven 的 pom.xml 文件:
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency>
启用 Feign 客户端:在 Spring Boot 应用程序的启动类或配置类上添加 @EnableFeignClients 注解,以启用 Feign 客户端功能。
@SpringBootApplication @EnableFeignClients @ComponentScan(basePackages = {"com.definesys.mpaas", "com.xdap.*" ,"com.xdap.*"}) public class MobilecardApplication { public static void main(String[] args) { SpringApplication.run(MobilecardApplication.class, args); } }
创建 Feign 客户端接口:创建一个接口,使用 @FeignClient 注解指定服务名称和服务地址。定义需要调用的 HTTP 方法、URL 路径、请求参数和请求体等信息。例如:
//此处name需要设置不为空,url需要在.properties中设置,也可直接写url @FeignClient(name = "example-service",url = "${outSide.url}") public interface ExampleClient { @GetMapping("/api/users/{id}") User getUser(@PathVariable("id") Long id); @PostMapping("/api/users") User createUser(@RequestBody User user); }
调用 Feign 客户端接口:通过注入 Feign 客户端接口对象,即可使用其定义的方法进行远程服务调用。例如:
@RestController public class ExampleController { private final ExampleClient exampleClient; public ExampleController(ExampleClient exampleClient) { this.exampleClient = exampleClient; } @GetMapping("/users/{id}") public User getUser(@PathVariable Long id) { return exampleClient.getUser(id); } @PostMapping("/users") public User createUser(@RequestBody User user) { return exampleClient.createUser(user); } }
配置 Feign 客户端:可以通过配置文件来配置 Feign 客户端的行为,例如设置连接超时时间、请求重试等。在 application.properties 或 application.yml 文件中添加相关配置。
以上是使用 Feign 进行消费的基本步骤。Feign 可以根据接口定义自动生成符合服务提供方 API 规范的客户端实现,并且内部集成了负载均衡和服务发现等功能,简化了远程服务调用的过程。
在使用 Feign 进行远程服务调用时,可以通过添加 Header 来传递额外的请求信息。下面介绍两种常见的方式来添加 Header。
使用 @RequestHeader 注解:在 Feign 客户端接口的方法参数上使用 @RequestHeader 注解,指定要添加的 Header 的名称和值。例如:
@GetMapping("/api/users/{id}") User getUser(@PathVariable("id") Long id, @RequestHeader("Authorization") String token);
在调用该方法时,将会在请求头中添加一个名为 “Authorization” 的 Header,其值为传入的 token 参数的值。
使用 Interceptor 拦截器:可以自定义一个 Interceptor 实现 RequestInterceptor 接口,在 apply() 方法中添加需要的 Header。例如:
public class CustomHeaderInterceptor implements RequestInterceptor { @Override public void apply(RequestTemplate template) { template.header("Authorization", "Bearer your_token"); } }
然后,在 Feign 客户端接口上使用 @FeignClient 注解的 configuration 属性指定使用的拦截器类。例如:
@FeignClient(name = "example-service", url = "http://example.com", configuration = CustomHeaderInterceptor.class) public interface ExampleClient { // ... }
这样,在每次使用该 Feign 客户端接口进行远程调用时,都会在请求头中自动添加上述定义的 Header。
以上是两种常见的添加 Header 的方法。根据实际需求和场景,可以选择适合的方式来添加自定义的 Header 信息。
Spring Boot 调用外部接口的方式有多种,常见的有以下三种方式:RestTemplate、Feign 和 WebClient。它们之间存在一些联系和区别。
RestTemplate:
RestTemplate 是 Spring Framework 提供的传统的 HTTP 客户端工具,在 Spring Boot 中也得到了支持。 通过 RestTemplate,可以发送 HTTP 请求并接收响应,支持同步调用。 RestTemplate 具有广泛的功能,可以处理各种请求和响应内容,支持自定义编解码、拦截器等功能。 RestTemplate 使用起来相对简单,直接调用其方法即可,适用于简单的接口调用场景。
Feign:
Feign 是基于接口的声明式的 HTTP 客户端,是 Spring Cloud 提供的组件之一。 Feign 在 Spring Boot 中通过 @FeignClient 注解定义和使用。 通过 Feign,可以以声明式的方式定义远程服务接口,并且 Feign 会自动代理实现具体的调用逻辑,无需手动编写实现代码。 Feign 集成了 Ribbon 负载均衡器和 Eureka 服务注册中心,可以自动进行负载均衡和服务发现。 Feign 更加高级抽象和灵活,适用于需要更多功能和集成分布式系统环境的场景。
WebClient:
WebClient 是 Spring WebFlux 提供的非阻塞的 HTTP 客户端。 WebClient 基于 Reactor 响应式编程模型,能够在异步非阻塞的情况下处理大量并发请求。 WebClient 支持使用函数式编程风格来定义请求和处理响应,可以使用 Mono 和 Flux 处理异步结果。 WebClient 适用于高性能、高并发的场景,并且在 Spring Boot 2.x 中是推荐的方式。 这三种方式在实际使用中有一些联系和区别:
RestTemplate 是传统的 HTTP 客户端,使用较为简单,适用于简单的接口调用场景。 Feign 是基于接口的声明式客户端,集成了负载均衡和服务发现等功能,适用于分布式系统下的服务调用。 WebClient 是非阻塞的 HTTP 客户端,支持异步、响应式编程模型,适用于高并发、高性能的场景。 选择使用哪种方式需要根据具体的需求和场景来决定。在 Spring Boot 中,可以根据项目的特点和要求,灵活选择适合的方式进行外部接口调用。
此次项目涉及到技术栈包含vue3+TypeScript+vue-router+pinia+element-plus+axios+echarts等技术栈。
通信仓库地址:https://gitee.com/jch1011/vue3_communication.git
不管是vue2还是vue3,组件通信方式很重要,不管是项目还是面试都是经常用到的知识点。
比如
组件通信方式**props:**可以实现父子组件、子父组件、甚至兄弟组件通信
自定义事件:可以实现子父组件通信
全局事件总线$bus:可以实现任意组件通信
**pubsub:**发布订阅模式实现任意组件通信
vuex:集中式状态管理容器,实现任意组件通信
ref:父组件获取子组件实例VC,获取子组件的响应式数据以及方法
**slot:**插槽(默认插槽、具名插槽、作用域插槽)实现父子组件通信........
props可以实现父子组件通信,在vue3中我们可以通过defineProps获取父组件传递的数据。且在组件内部不需要引入defineProps方法可以直接使用!
父组件给子组件传递数据
<Child info="我爱祖国" :money="money"></Child>
子组件获取父组件传递数据:方式1
let props = defineProps({ info:{ type:String,//接受的数据类型 default:'默认参数',//接受默认数据 }, money:{ type:Number, default:0 }})
子组件获取父组件传递数据:方式2
let props = defineProps(["info",'money']);
子组件获取到props数据就可以在模板中使用了,但是切记props是只读的(只能读取,不能修改)
在vue框架中事件分为两种:一种是原生的DOM事件,另外一种自定义事件。
原生DOM事件可以让用户与网页进行交互,比如click、dbclick、change、mouseenter、mouseleave....
自定义事件可以实现子组件给父组件传递数据
代码如下:
<pre @click="handler"> 我是祖国的老花骨朵 </pre>
当前代码级给pre标签绑定原生DOM事件点击事件,默认会给事件回调注入event事件对象。当然点击事件想注入多个参数可以按照下图操作。但是切记注入的事件对象务必叫做$event.
<div @click="handler1(1,2,3,$event)">我要传递多个参数</div>
在vue3框架click、dbclick、change(这类原生DOM事件),不管是在标签、自定义标签上(组件标签)都是原生DOM事件。
自定义事件可以实现子组件给父组件传递数据.在项目中是比较常用的。
比如在父组件内部给子组件(Event2)绑定一个自定义事件
<Event2 @xxx="handler3"></Event2>
在Event2子组件内部触发这个自定义事件
<template> <div> <h1>我是子组件2</h1> <button @click="handler">点击我触发xxx自定义事件</button> </div> </template> <script setup lang="ts"> let $emit = defineEmits(["xxx"]); const handler = () => { $emit("xxx", "法拉利", "茅台"); }; </script> <style scoped> </style>
我们会发现在script标签内部,使用了defineEmits方法,此方法是vue3提供的方法,不需要引入直接使用。defineEmits方法执行,传递一个数组,数组元素即为将来组件需要触发的自定义事件类型,此方执行会返回一个$emit方法用于触发自定义事件。
当点击按钮的时候,事件回调内部调用$emit方法去触发自定义事件,第一个参数为触发事件类型,第二个、三个、N个参数即为传递给父组件的数据。
需要注意的是:代码如下
<Event2 @xxx="handler3" @click="handler"></Event2>
正常说组件标签书写@click应该为原生DOM事件,但是如果子组件内部通过defineEmits定义就变为自定义事件了
let $emit = defineEmits(["xxx",'click']);
全局事件总线可以实现任意组件通信,在vue2中可以根据VM与VC关系推出全局事件总线。
但是在vue3中没有Vue构造函数,也就没有Vue.prototype.以及组合式API写法没有this,
那么在Vue3想实现全局事件的总线功能就有点不现实啦,如果想在Vue3中使用全局事件总线功能
可以使用插件mitt实现。
mitt:官网地址:https://www.npmjs.com/package/mitt
v-model指令可是收集表单数据(数据双向绑定),除此之外它也可以实现父子组件数据同步。
而v-model实指利用props[modelValue]与自定义事件[update
]实现的。下方代码:相当于给组件Child传递一个props(modelValue)与绑定一个自定义事件update
实现父子组件数据同步
<Child v-model="msg"></Child>
在vue3中一个组件可以通过使用多个v-model,让父子组件多个数据同步,下方代码相当于给组件Child传递两个props分别是pageNo与pageSize,以及绑定两个自定义事件update
与update实现父子数据同步<Child v-model:pageNo="msg" v-model:pageSize="msg1"></Child>
在Vue3中可以利用useAttrs方法获取组件的属性与事件(包含:原生DOM事件或者自定义事件),次函数功能类似于Vue2框架中listeners方法。
比如:在父组件内部使用一个子组件my-button
<my-button type="success" size="small" title='标题' @click="handler"></my-button>
子组件内部可以通过useAttrs方法获取组件属性与事件.因此你也发现了,它类似于props,可以接受父组件传递过来的属性与属性值。需要注意如果defineProps接受了某一个属性,useAttrs方法返回的对象身上就没有相应属性与属性值。
<script setup lang="ts"> import {useAttrs} from 'vue'; let $attrs = useAttrs(); </script>
ref,提及到ref可能会想到它可以获取元素的DOM或者获取子组件实例的VC。既然可以在父组件内部通过ref获取子组件实例VC,那么子组件内部的方法与响应式数据父组件可以使用的。
比如:在父组件挂载完毕获取组件实例
父组件内部代码:
<template> <div> <h1>ref与$parent</h1> <Son ref="son"></Son> </div> </template> <script setup lang="ts"> import Son from "./Son.vue"; import { onMounted, ref } from "vue"; const son = ref(); onMounted(() => { console.log(son.value); }); </script>
但是需要注意,如果想让父组件获取子组件的数据或者方法需要通过defineExpose对外暴露,因为vue3中组件内部的数据对外“关闭的”,外部不能访问
<script setup lang="ts"> import { ref } from "vue"; //数据 let money = ref(1000); //方法 const handler = ()=>{ } defineExpose({ money, handler }) </script>
$parent可以获取某一个组件的父组件实例VC,因此可以使用父组件内部的数据与方法。必须子组件内部拥有一个按钮点击时候获取父组件实例,当然父组件的数据与方法需要通过defineExpose方法对外暴露
<button @click="handler($parent)">点击我获取父组件实例</button>
provide[提供]
inject[注入]
vue3提供两个方法provide与inject,可以实现隔辈组件传递参数
组件组件提供数据:
provide方法用于提供数据,此方法执需要传递两个参数,分别提供数据的key与提供数据value
<script setup lang="ts"> import {provide} from 'vue' provide('token','admin_token'); </script>
后代组件可以通过inject方法获取数据,通过key获取存储的数值
<script setup lang="ts"> import {inject} from 'vue' let token = inject('token'); </script>
pinia官网:https://pinia.web3doc.top/
pinia也是集中式管理状态容器,类似于vuex。但是核心概念没有mutation、modules,使用方式参照官网
插槽:默认插槽、具名插槽、作用域插槽可以实现父子组件通信.
默认插槽:
在子组件内部的模板中书写slot全局组件标签
<template> <div> <slot></slot> </div> </template> <script setup lang="ts"> </script> <style scoped> </style>
在父组件内部提供结构:Todo即为子组件,在父组件内部使用的时候,在双标签内部书写结构传递给子组件
注意开发项目的时候默认插槽一般只有一个
<Todo> <h1>我是默认插槽填充的结构</h1> </Todo>
具名插槽:
顾名思义,此插槽带有名字在组件内部留多个指定名字的插槽。
下面是一个子组件内部,模板中留两个插槽
<template> <div> <h1>todo</h1> <slot name="a"></slot> <slot name="b"></slot> </div> </template> <script setup lang="ts"> </script> <style scoped> </style>
父组件内部向指定的具名插槽传递结构。需要注意v-slot:可以替换为#
<template> <div> <h1>slot</h1> <Todo> <template v-slot:a> //可以用#a替换 <div>填入组件A部分的结构</div> </template> <template v-slot:b>//可以用#b替换 <div>填入组件B部分的结构</div> </template> </Todo> </div> </template> <script setup lang="ts"> import Todo from "./Todo.vue"; </script> <style scoped> </style>
作用域插槽
作用域插槽:可以理解为,子组件数据由父组件提供,但是子组件内部决定不了自身结构与外观(样式)
子组件Todo代码如下:
<template> <div> <h1>todo</h1> <ul> <!--组件内部遍历数组--> <li v-for="(item,index) in todos" :key="item.id"> <!--作用域插槽将数据回传给父组件--> <slot :$row="item" :$index="index"></slot> </li> </ul> </div> </template> <script setup lang="ts"> defineProps(['todos']);//接受父组件传递过来的数据 </script> <style scoped> </style>
父组件内部代码如下:
<template> <div> <h1>slot</h1> <Todo :todos="todos"> <template v-slot="{$row,$index}"> <!--父组件决定子组件的结构与外观--> <span :style="{color:$row.done?'green':'red'}">{{$row.title}}</span> </template> </Todo> </div> </template> <script setup lang="ts"> import Todo from "./Todo.vue"; import { ref } from "vue"; //父组件内部数据 let todos = ref([ { id: 1, title: "吃饭", done: true }, { id: 2, title: "睡觉", done: false }, { id: 3, title: "打豆豆", done: true }, ]); </script> <style scoped> </style>
今天来带大家从0开始搭建一个vue3版本的后台管理系统。一个项目要有统一的规范,需要使用eslint+stylelint+prettier来对我们的代码质量做检测和修复,需要使用husky来做commit拦截,需要使用commitlint来统一提交规范,需要使用preinstall来统一包管理工具。
下面我们就用这一套规范来初始化我们的项目,集成一个规范的模版。
本项目使用vite进行构建,vite官方中文文档参考:cn.vitejs.dev/guide/
pnpm
npm ,意味“高性能的 npm”。pnpm由npm/yarn衍生而来,解决了npm/yarn内部潜在的bug,极大的优化了性能,扩展了使用场景。被誉为“最先进的包管理工具”pnpm安装指令
npm i -g pnpm
项目初始化命令:
pnpm create vite
进入到项目根目录pnpm install安装全部依赖.安装完依赖运行程序
run dev运行完毕项目跑在http://127.0.0.1:5173/,可以访问你得项目啦
eslint中文官网:http://eslint.cn/
ESLint最初是由Nicholas C. Zakas 于2013年6月创建的开源项目。它的目标是提供一个插件化的javascript代码检测工具
首先安装eslint
pnpm i eslint -D
生成配置文件:.eslint.cjs
npx eslint --init
.eslint.cjs配置文件
module.exports = { //运行环境 "env": { "browser": true,//浏览器端 "es2021": true,//es2021 }, //规则继承 "extends": [ //全部规则默认是关闭的,这个配置项开启推荐规则,推荐规则参照文档 //比如:函数不能重名、对象不能出现重复key "eslint:recommended", //vue3语法规则 "plugin:vue/vue3-essential", //ts语法规则 "plugin:@typescript-eslint/recommended" ], //要为特定类型的文件指定处理器 "overrides": [ ], //指定解析器:解析器 //Esprima 默认解析器 //Babel-ESLint babel解析器 //@typescript-eslint/parser ts解析器 "parser": "@typescript-eslint/parser", //指定解析器选项 "parserOptions": { "ecmaVersion": "latest",//校验ECMA最新版本 "sourceType": "module"//设置为"script"(默认),或者"module"代码在ECMAScript模块中 }, //ESLint支持使用第三方插件。在使用插件之前,您必须使用npm安装它 //该eslint-plugin-前缀可以从插件名称被省略 "plugins": [ "vue", "@typescript-eslint" ], //eslint规则 "rules": { } }
# 让所有与prettier规则存在冲突的Eslint rules失效,并使用prettier进行代码检查 "eslint-config-prettier": "^8.6.0", "eslint-plugin-import": "^2.27.5", "eslint-plugin-node": "^11.1.0", # 运行更漂亮的Eslint,使prettier规则优先级更高,Eslint优先级低 "eslint-plugin-prettier": "^4.2.1", # vue.js的Eslint插件(查找vue语法错误,发现错误指令,查找违规风格指南 "eslint-plugin-vue": "^9.9.0", # 该解析器允许使用Eslint校验所有babel code "@babel/eslint-parser": "^7.19.1",
安装指令
pnpm install -D eslint-plugin-import eslint-plugin-vue eslint-plugin-node eslint-plugin-prettier eslint-config-prettier eslint-plugin-node @babel/eslint-parser
// @see https://eslint.bootcss.com/docs/rules/ module.exports = { env: { browser: true, es2021: true, node: true, jest: true, }, /* 指定如何解析语法 */ parser: 'vue-eslint-parser', /** 优先级低于 parse 的语法解析配置 */ parserOptions: { ecmaVersion: 'latest', sourceType: 'module', parser: '@typescript-eslint/parser', jsxPragma: 'React', ecmaFeatures: { jsx: true, }, }, /* 继承已有的规则 */ extends: [ 'eslint:recommended', 'plugin:vue/vue3-essential', 'plugin:@typescript-eslint/recommended', 'plugin:prettier/recommended', ], plugins: ['vue', '@typescript-eslint'], /* * "off" 或 0 ==> 关闭规则 * "warn" 或 1 ==> 打开的规则作为警告(不影响代码执行) * "error" 或 2 ==> 规则作为一个错误(代码不能执行,界面报错) */ rules: { // eslint(https://eslint.bootcss.com/docs/rules/) 'no-var': 'error', // 要求使用 let 或 const 而不是 var 'no-multiple-empty-lines': ['warn', { max: 1 }], // 不允许多个空行 'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off', 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off', 'no-unexpected-multiline': 'error', // 禁止空余的多行 'no-useless-escape': 'off', // 禁止不必要的转义字符 // typeScript (https://typescript-eslint.io/rules) '@typescript-eslint/no-unused-vars': 'error', // 禁止定义未使用的变量 '@typescript-eslint/prefer-ts-expect-error': 'error', // 禁止使用 @ts-ignore '@typescript-eslint/no-explicit-any': 'off', // 禁止使用 any 类型 '@typescript-eslint/no-non-null-assertion': 'off', '@typescript-eslint/no-namespace': 'off', // 禁止使用自定义 TypeScript 模块和命名空间。 '@typescript-eslint/semi': 'off', // eslint-plugin-vue (https://eslint.vuejs.org/rules/) 'vue/multi-word-component-names': 'off', // 要求组件名称始终为 “-” 链接的单词 'vue/script-setup-uses-vars': 'error', // 防止<script setup>使用的变量<template>被标记为未使用 'vue/no-mutating-props': 'off', // 不允许组件 prop的改变 'vue/attribute-hyphenation': 'off', // 对模板中的自定义组件强制执行属性命名样式 }, }
dist node_modules
package.json新增两个运行脚本
"scripts": { "lint": "eslint src", "fix": "eslint src --fix", }
有了eslint,为什么还要有prettier?eslint针对的是javascript,他是一个检测工具,包含js语法以及少部分格式问题,在eslint看来,语法对了就能保证代码正常运行,格式问题属于其次;
而prettier属于格式化工具,它看不惯格式不统一,所以它就把eslint没干好的事接着干,另外,prettier支持
包含js在内的多种语言。
总结起来,eslint和prettier这俩兄弟一个保证js代码质量,一个保证代码美观。
pnpm install -D eslint-plugin-prettier prettier eslint-config-prettier
{ "singleQuote": true, "semi": false, "bracketSpacing": true, "htmlWhitespaceSensitivity": "ignore", "endOfLine": "auto", "trailingComma": "all", "tabWidth": 2 }
/dist/* /html/* .local /node_modules/** **/*.svg **/*.sh /public/*
通过pnpm run lint去检测语法,如果出现不规范格式,通过pnpm run fix 修改
stylelint为css的lint工具。可格式化css代码,检查css语法错误与不合理的写法,指定css书写顺序等。
我们的项目中使用scss作为预处理器,安装以下依赖:
pnpm add sass sass-loader stylelint postcss postcss-scss postcss-html stylelint-config-prettier stylelint-config-recess-order stylelint-config-recommended-scss stylelint-config-standard stylelint-config-standard-vue stylelint-scss stylelint-order stylelint-config-standard-scss -D
.stylelintrc.cjs
配置文件官网:https://stylelint.bootcss.com/
// @see https://stylelint.bootcss.com/ module.exports = { extends: [ 'stylelint-config-standard', // 配置stylelint拓展插件 'stylelint-config-html/vue', // 配置 vue 中 template 样式格式化 'stylelint-config-standard-scss', // 配置stylelint scss插件 'stylelint-config-recommended-vue/scss', // 配置 vue 中 scss 样式格式化 'stylelint-config-recess-order', // 配置stylelint css属性书写顺序插件, 'stylelint-config-prettier', // 配置stylelint和prettier兼容 ], overrides: [ { files: ['**/*.(scss|css|vue|html)'], customSyntax: 'postcss-scss', }, { files: ['**/*.(html|vue)'], customSyntax: 'postcss-html', }, ], ignoreFiles: [ '**/*.js', '**/*.jsx', '**/*.tsx', '**/*.ts', '**/*.json', '**/*.md', '**/*.yaml', ], /** * null => 关闭该规则 * always => 必须 */ rules: { 'value-keyword-case': null, // 在 css 中使用 v-bind,不报错 'no-descending-specificity': null, // 禁止在具有较高优先级的选择器后出现被其覆盖的较低优先级的选择器 'function-url-quotes': 'always', // 要求或禁止 URL 的引号 "always(必须加上引号)"|"never(没有引号)" 'no-empty-source': null, // 关闭禁止空源码 'selector-class-pattern': null, // 关闭强制选择器类名的格式 'property-no-unknown': null, // 禁止未知的属性(true 为不允许) 'block-opening-brace-space-before': 'always', //大括号之前必须有一个空格或不能有空白符 'value-no-vendor-prefix': null, // 关闭 属性值前缀 --webkit-box 'property-no-vendor-prefix': null, // 关闭 属性前缀 -webkit-mask 'selector-pseudo-class-no-unknown': [ // 不允许未知的选择器 true, { ignorePseudoClasses: ['global', 'v-deep', 'deep'], // 忽略属性,修改element默认样式的时候能使用到 }, ], }, }
/node_modules/* /dist/* /html/* /public/*
"scripts": { "lint:style": "stylelint src/**/*.{css,scss,vue} --cache --fix" }
最后配置统一的prettier来格式化我们的js和css,html代码
"scripts": { "dev": "vite --open", "build": "vue-tsc && vite build", "preview": "vite preview", "lint": "eslint src", "fix": "eslint src --fix", "format": "prettier --write \"./**/*.{html,vue,ts,js,json,md}\"", "lint:eslint": "eslint src/**/*.{ts,vue} --cache --fix", "lint:style": "stylelint src/**/*.{css,scss,vue} --cache --fix" },
当我们运行pnpm run format
的时候,会把代码直接格式化
在上面我们已经集成好了我们代码校验工具,但是需要每次手动的去执行命令才会格式化我们的代码。如果有人没有格式化就提交了远程仓库中,那这个规范就没什么用。所以我们需要强制让开发人员按照代码规范来提交。
要做到这件事情,就需要利用husky在代码提交之前触发git hook(git在客户端的钩子),然后执行pnpm run format
来自动的格式化我们的代码。
安装husky
pnpm install -D husky
执行
npx husky-init
会在根目录下生成个一个.husky目录,在这个目录下面会有一个pre-commit文件,这个文件里面的命令在我们执行commit的时候就会执行
在.husky/pre-commit
文件添加如下命令:
#!/usr/bin/env sh . "$(dirname -- "$0")/_/husky.sh" pnpm run format
当我们对代码进行commit操作的时候,就会执行命令,对代码进行格式化,然后再提交。
对于我们的commit信息,也是有统一规范的,不能随便写,要让每个人都按照统一的标准来执行,我们可以利用commitlint来实现。
安装包
pnpm add @commitlint/config-conventional @commitlint/cli -D
添加配置文件,新建commitlint.config.cjs
(注意是cjs),然后添加下面的代码:
module.exports = { extends: ['@commitlint/config-conventional'], // 校验规则 rules: { 'type-enum': [ 2, 'always', [ 'feat', 'fix', 'docs', 'style', 'refactor', 'perf', 'test', 'chore', 'revert', 'build', ], ], 'type-case': [0], 'type-empty': [0], 'scope-empty': [0], 'scope-case': [0], 'subject-full-stop': [0, 'never'], 'subject-case': [0, 'never'], 'header-max-length': [0, 'always', 72], }, }
在package.json
中配置scripts命令
# 在scrips中添加下面的代码 { "scripts": { "commitlint": "commitlint --config commitlint.config.cjs -e -V" }, }
配置结束,现在当我们填写commit
信息的时候,前面就需要带着下面的subject
'feat',//新特性、新功能 'fix',//修改bug 'docs',//文档修改 'style',//代码格式修改, 注意不是 css 修改 'refactor',//代码重构 'perf',//优化相关,比如提升性能、体验 'test',//测试用例修改 'chore',//其他修改, 比如改变构建流程、或者增加依赖库、工具等 'revert',//回滚到上一个版本 'build',//编译相关的修改,例如发布版本、对项目构建或者依赖的改动
配置husky
npx husky add .husky/commit-msg
在生成的commit-msg文件中添加下面的命令
#!/usr/bin/env sh . "$(dirname -- "$0")/_/husky.sh" pnpm commitlint
当我们 commit 提交信息时,就不能再随意写了,必须是 git commit -m 'fix: xxx' 符合类型的才可以,需要注意的是类型的后面需要用英文的 :,并且冒号后面是需要空一格的,这个是不能省略的
团队开发项目的时候,需要统一包管理器工具,因为不同包管理器工具下载同一个依赖,可能版本不一样,
导致项目出现bug问题,因此包管理器工具需要统一管理!!!
在根目录创建scritps/preinstall.js
文件,添加下面的内容
if (!/pnpm/.test(process.env.npm_execpath || '')) { console.warn( `\u001b[33mThis repository must using pnpm as the package manager ` + ` for scripts to work properly.\u001b[39m\n`, ) process.exit(1) }
配置命令
"scripts": { "preinstall": "node ./scripts/preinstall.js" }
当我们使用npm或者yarn来安装包的时候,就会报错了。原理就是在install的时候会触发preinstall(npm提供的生命周期钩子)这个文件里面的代码。
硅谷甄选运营平台,UI组件库采用的element-plus,因此需要集成element-plus插件!!!
官网地址:https://element-plus.gitee.io/zh-CN/
pnpm install element-plus @element-plus/icons-vue
入口文件main.ts全局安装element-plus,element-plus默认支持语言英语设置为中文
import ElementPlus from 'element-plus'; import 'element-plus/dist/index.css' //@ts-ignore忽略当前文件ts类型的检测否则有红色提示(打包会失败) import zhCn from 'element-plus/dist/locale/zh-cn.mjs' app.use(ElementPlus, { locale: zhCn })
Element Plus全局组件类型声明
// tsconfig.json { "compilerOptions": { // ... "types": ["element-plus/global"] } }
配置完毕可以测试element-plus组件与图标的使用.
在开发项目的时候文件与文件关系可能很复杂,因此我们需要给src文件夹配置一个别名!!!
// vite.config.ts import {defineConfig} from 'vite' import vue from '@vitejs/plugin-vue' import path from 'path' export default defineConfig({ plugins: [vue()], resolve: { alias: { "@": path.resolve("./src") // 相对路径别名配置,使用 @ 代替 src } } })
TypeScript 编译配置
// tsconfig.json { "compilerOptions": { "baseUrl": "./", // 解析非相对模块的基地址,默认是当前目录 "paths": { //路径映射,相对于baseUrl "@/*": ["src/*"] } } }
项目开发过程中,至少会经历开发环境、测试环境和生产环境(即正式环境)三个阶段。不同阶段请求的状态(如接口地址等)不尽相同,若手动切换接口地址是相当繁琐且易出错的。于是环境变量配置的需求就应运而生,我们只需做简单的配置,把环境状态切换的工作交给代码。
开发环境(development) 顾名思义,开发使用的环境,每位开发人员在自己的dev分支上干活,开发到一定程度,同事会合并代码,进行联调。
测试环境(testing) 测试同事干活的环境啦,一般会由测试同事自己来部署,然后在此环境进行测试
生产环境(production) 生产环境是指正式提供对外服务的,一般会关掉错误报告,打开错误日志。(正式提供给客户使用的环境。)
注意:一般情况下,一个环境对应一台服务器,也有的公司开发与测试环境是一台服务器!!!
项目根目录分别添加 开发、生产和测试环境的文件!
.env.development .env.production .env.test
文件内容
# 变量必须以 VITE_ 为前缀才能暴露给外部读取 NODE_ENV = 'development' VITE_APP_TITLE = '硅谷甄选运营平台' VITE_APP_BASE_API = '/dev-api'
NODE_ENV = 'production' VITE_APP_TITLE = '硅谷甄选运营平台' VITE_APP_BASE_API = '/prod-api'
# 变量必须以 VITE_ 为前缀才能暴露给外部读取 NODE_ENV = 'test' VITE_APP_TITLE = '硅谷甄选运营平台' VITE_APP_BASE_API = '/test-api'
配置运行命令:package.json
"scripts": { "dev": "vite --open", "build:test": "vue-tsc && vite build --mode test", "build:pro": "vue-tsc && vite build --mode production", "preview": "vite preview" },
通过import.meta.env获取环境变量
在开发项目的时候经常会用到svg矢量图,而且我们使用SVG以后,页面上加载的不再是图片资源,
这对页面性能来说是个很大的提升,而且我们SVG文件比img要小的很多,放在项目中几乎不占用资源。
安装SVG依赖插件
pnpm install vite-plugin-svg-icons -D
在vite.config.ts
中配置插件
import { createSvgIconsPlugin } from 'vite-plugin-svg-icons' import path from 'path' export default () => { return { plugins: [ createSvgIconsPlugin({ // Specify the icon folder to be cached iconDirs: [path.resolve(process.cwd(), 'src/assets/icons')], // Specify symbolId format symbolId: 'icon-[dir]-[name]', }), ], } }
入口文件导入
import 'virtual:svg-icons-register'
因为项目很多模块需要使用图标,因此把它封装为全局组件!!!
在src/components目录下创建一个SvgIcon组件:代表如下
<template> <div> <svg :style="{ width: width, height: height }"> <use :xlink:href="prefix + name" :fill="color"></use> </svg> </div> </template> <script setup lang="ts"> defineProps({ //xlink:href属性值的前缀 prefix: { type: String, default: '#icon-' }, //svg矢量图的名字 name: String, //svg图标的颜色 color: { type: String, default: "" }, //svg宽度 width: { type: String, default: '16px' }, //svg高度 height: { type: String, default: '16px' } }) </script> <style scoped></style>
在src/components文件夹目录下创建一个index.ts文件:用于注册components文件夹内部全部全局组件!!!
import SvgIcon from './SvgIcon/index.vue'; import type { App, Component } from 'vue'; const components: { [name: string]: Component } = { SvgIcon }; export default { install(app: App) { Object.keys(components).forEach((key: string) => { app.component(key, components[key]); }) } }
在入口文件引入src/index.ts文件,通过app.use方法安装自定义插件
import gloablComponent from './components/index'; app.use(gloablComponent);
我们目前在组件内部已经可以使用scss样式,因为在配置styleLint工具的时候,项目当中已经安装过sass sass-loader,因此我们再组件内可以使用scss语法!!!需要加上lang="scss"
<style scoped lang="scss"></style>
接下来我们为项目添加一些全局的样式
在src/styles目录下创建一个index.scss文件,当然项目中需要用到清除默认样式,因此在index.scss引入reset.scss
@import reset.scss
在入口文件引入
import '@/styles/index.scss'
但是你会发现在src/styles/index.scss全局样式文件中没有办法使用.
在style/variable.scss创建一个variable.scss文件!
在vite.config.ts文件配置如下:
export default defineConfig((config) => { css: { preprocessorOptions: { scss: { javascriptEnabled: true, additionalData: '@import "./src/styles/variable.scss";', }, }, }, } }
@import "./src/styles/variable.less";
后面的;
不要忘记,不然会报错!
配置完毕你会发现scss提供这些全局变量可以在组件样式中使用了!!!
安装依赖:https://www.npmjs.com/package/vite-plugin-mock
pnpm install -D vite-plugin-mock mockjs
在 vite.config.js 配置文件启用插件。
import { UserConfigExport, ConfigEnv } from 'vite' import { viteMockServe } from 'vite-plugin-mock' import vue from '@vitejs/plugin-vue' export default ({ command })=> { return { plugins: [ vue(), viteMockServe({ localEnabled: command === 'serve', }), ], } }
在根目录创建mock文件夹:去创建我们需要mock数据与接口!!!
在mock文件夹内部创建一个user.ts文件
//用户信息数据 function createUserList() { return [ { userId: 1, avatar: 'https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif', username: 'admin', password: '111111', desc: '平台管理员', roles: ['平台管理员'], buttons: ['cuser.detail'], routes: ['home'], token: 'Admin Token', }, { userId: 2, avatar: 'https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif', username: 'system', password: '111111', desc: '系统管理员', roles: ['系统管理员'], buttons: ['cuser.detail', 'cuser.user'], routes: ['home'], token: 'System Token', }, ] } export default [ // 用户登录接口 { url: '/api/user/login',//请求地址 method: 'post',//请求方式 response: ({ body }) => { //获取请求体携带过来的用户名与密码 const { username, password } = body; //调用获取用户信息函数,用于判断是否有此用户 const checkUser = createUserList().find( (item) => item.username === username && item.password === password, ) //没有用户返回失败信息 if (!checkUser) { return { code: 201, data: { message: '账号或者密码不正确' } } } //如果有返回成功信息 const { token } = checkUser return { code: 200, data: { token } } }, }, // 获取用户信息 { url: '/api/user/info', method: 'get', response: (request) => { //获取请求头携带token const token = request.headers.token; //查看用户信息是否包含有次token用户 const checkUser = createUserList().find((item) => item.token === token) //没有返回失败的信息 if (!checkUser) { return { code: 201, data: { message: '获取用户信息失败' } } } //如果有返回成功信息 return { code: 200, data: {checkUser} } }, }, ]
安装axios
pnpm install axios
最后通过axios测试接口!!!
在开发项目的时候避免不了与后端进行交互,因此我们需要使用axios插件实现发送网络请求。在开发项目的时候
我们经常会把axios进行二次封装。
目的:
1:使用请求拦截器,可以在请求拦截器中处理一些业务(开始进度条、请求头携带公共参数)
2:使用响应拦截器,可以在响应拦截器中处理一些业务(进度条结束、简化服务器返回的数据、处理http网络错误)
在根目录下创建utils/request.ts
import axios from "axios"; import { ElMessage } from "element-plus"; //创建axios实例 let request = axios.create({ baseURL: import.meta.env.VITE_APP_BASE_API, timeout: 5000 }) //请求拦截器 request.interceptors.request.use(config => { return config; }); //响应拦截器 request.interceptors.response.use((response) => { return response.data; }, (error) => { //处理网络错误 let msg = ''; let status = error.response.status; switch (status) { case 401: msg = "token过期"; break; case 403: msg = '无权访问'; break; case 404: msg = "请求地址错误"; break; case 500: msg = "服务器出现问题"; break; default: msg = "无网络"; } ElMessage({ type: 'error', message: msg }) return Promise.reject(error); }); export default request;
在开发项目的时候,接口可能很多需要统一管理。在src目录下去创建api文件夹去统一管理项目的接口;
比如:下面方式
//统一管理咱们项目用户相关的接口 import request from '@/utils/request' import type { loginFormData, loginResponseData, userInfoReponseData, } from './type' //项目用户相关的请求地址 enum API { LOGIN_URL = '/admin/acl/index/login', USERINFO_URL = '/admin/acl/index/info', LOGOUT_URL = '/admin/acl/index/logout', } //登录接口 export const reqLogin = (data: loginFormData) => request.post<any, loginResponseData>(API.LOGIN_URL, data) //获取用户信息 export const reqUserInfo = () => request.get<any, userInfoReponseData>(API.USERINFO_URL) //退出登录 export const reqLogout = () => request.post<any, any>(API.LOGOUT_URL)
贾成豪老师代码仓库地址:https://gitee.com/jch1011/vue3_admin_template-bj1.git
项目在线文档:
服务器域名:http://sph-api.atguigu.cn
swagger文档:
http://139.198.104.58:8209/swagger-ui.html
http://139.198.104.58:8212/swagger-ui.html#/
echarts:国内镜像网站
#输出文件末尾行(默认10行),当文件有追加时,会输出后续添加的行,不会中断输出,除非ctrl+c中断 #-f 即 --follow=file.log tail -f file.log #输出文件末尾包含关键字的行,当文件有追加时,会输出后续添加的行,不会中断输出,除非ctrl+c中断 #-f 即 --follow=file.log tail -f file.log | grep "关键字" #输出文件的后100行中包含关键字的行(-n 100 即 --lines=100) tail -n 100 file.log | grep "关键字" #输出文件的后100行中包含关键字的行和该行的后10行 tail -n 100 file.log | grep "关键字" -A10 #输出文件的后100行中包含关键字的行和该行的前10行 tail -n 100 file.log | grep "关键字" -B10 #输出文件的后100行中包含关键字的行和该行的前后10行 tail -n 100 file.log | grep "关键字" -B10 -A10
-a 归档模式, 表示以递归方式传输文件,并保持所有属性,它等同于-r、-l、-p、-t、-g、-o、-D 选项。 -r 表示以递归模式处理子目录,它主要是针对目录来说的 -n 参数模拟命令执行的结果,并不真的执行命令 -v 表示打印一些信息,比如文件列表、文件数量等 -p 表示保持文件权限。 -o 表示保持文件属主信息。 -g 表示保持文件属组信息。 -z 加上该选项,将会在传输过程中压缩。 --delete 表示删除 目标目录中源目录中没有的文件。 --exclude 表示指定排除不需要传输的文件,等号后面跟文件名,可以是通配符模式(如 *.txt) --include 参数用来指定必须同步的文件模式,往往与 --exclude 结合使用。
rsync -a 源目录 目标目录
rsync -a 源文件/目录 user@主机号:/path/to/dir
mkdir empty #首先创建一个空目录
rsync /path/to/dir
rsync -a --exclude={需要除过的文件目录} /path/to/dir /path/to
rsync -a --include={需要的文件目录} --exclude=* /path/to/dir /path/to
scp 可以在 2个 linux 主机间复制文件;
命令基本格式:
scp [可选参数] file_source file_target
复制文件:
命令格式:
scp local_file remote_username@remote_ip
或者
scp local_file remote_username@remote_ip:remote_file
或者
scp local_file remote_ip:remote_folder
或者
scp local_file remote_ip:remote_file
第1,2个指定了用户名,命令执行后需要再输入密码,第1个仅指定了远程的目录,文件名字不变,第2个指定了文件名;
第3,4个没有指定用户名,命令执行后需要输入用户名和密码,第3个仅指定了远程的目录,文件名字不变,第4个指定了文件名;
例子:
scp /home/space/music/1.mp3 root@www.cumt.edu.cn:/home/root/others/music scp /home/space/music/1.mp3 root@www.cumt.edu.cn:/home/root/others/music/001.mp3 scp /home/space/music/1.mp3 www.cumt.edu.cn:/home/root/others/music scp /home/space/music/1.mp3 www.cumt.edu.cn:/home/root/others/music/001.mp3
复制目录:
命令格式:
scp -r local_folder remote_username@remote_ip:remote_folder
或者
scp -r local_folder remote_ip:remote_folder
第1个指定了用户名,命令执行后需要再输入密码;
第2个没有指定用户名,命令执行后需要输入用户名和密码;
例子:
scp -r /home/space/music/ root@www.cumt.edu.cn:/home/root/others/ scp -r /home/space/music/ www.cumt.edu.cn:/home/root/others/
上面 命令 将 本地 music 目录 复制 到 远程 others 目录下,即复制后有 远程 有 ../others/music/ 目录
从 远程 复制到 本地,只要将 从 本地 复制到 远程 的命令 的 后2个参数 调换顺序 即可;
例如:
scp root@www.cumt.edu.cn:/home/root/others/music /home/space/music/1.mp3 scp -r www.cumt.edu.cn:/home/root/others/ /home/space/music/
最简单的应用如下 :
scp 本地用户名 @IP 地址 : 文件名 1 远程用户名 @IP 地址 : 文件名 2
[ 本地用户名 @IP 地址 :] 可以不输入 , 可能需要输入远程用户名所对应的密码 .
可能有用的几个参数 :
-v 和大多数 linux 命令中的 -v 意思一样 , 用来显示进度 . 可以用来查看连接 , 认证 , 或是配置错误 . -C 使能压缩选项 . -P 选择端口 . 注意 -p 已经被 rcp 使用 . -4 强行使用 IPV4 地址 . -6 强行使用 IPV6 地址 .
netstat -tuln #查看具体的端口或者应用 netstat -tuln | grep 443 netstat -tulnp | grep nginx
vim /etc/iptables/rules.v4 #开放443端口 -A INPUT -p tcp --dport 443 -j ACCEPT #开放端口给指定的ip -A INPUT -s 10.180.269.234/29 -p tcp -m state --state NEW -m tcp --dport 80 -j ACCEPT -m comment --comment "open 80" #使配置生效 iptables-restore < /etc/iptables/rules.v4 #查看所有iptables规则 iptables -L -n
df -h #查看各个目录占用情况 du -ah --max-depth=1
let、const、var在js中都是用于声明变量的,在没有进行ES6的学习前,我基本只会使用到var关键字进行变量的声明,但在了解了ES6之后就涉及到了块级作用域以及let,const了。
let是用于替代var来声明变量(var是ES6之前用来声明变量的关键词)
const是用来声明常量的(var,let声明变量时,变量一旦初始化之后,还可以重新赋值,const声明常量,一旦初始化,就不能重新赋值了,否则会报错)
使用const的原因:const 就是为了那些一旦初始化就不希望重新赋值的情况设计的
注意事项:使用const声明常量,一旦声明,就必须立即初始化
特殊说明:const声明常量,允许在不重新赋值的情况下修改它的值(基本数据类型不可,只有引用数据类型可以,引用类型引用的是地址不是值)
//基本数据类型赋值为常量之后是不可修改的 const name='zhangsan'; name='nn'; //x //引用数据类型 const student={age:18}; //student={}; student.age=19;
毕竟基础数据类型去修改const会报错,那其实可以都用const进行变量的声明,当变量后面发生变化时自然会报错(前提是你还记得住,还是不太推荐这个傻瓜指南的)。
咱就是说块级作用域简单说明一下,const和let的使用区别是会涉及到这个的
常见的作用域主要分为几个类型:全局作用域、函数作用域、块状作用域、动态作用域。
对象 | 类型 |
---|---|
global/window | 全局作用域 |
function | 函数作用域(局部作用域) |
{} | 块级作用域 |
this | 动态作用域 |
JS中作用域有:全局作用域、函数作用域。没有块作用域的概念。ECMAScript 6(简称ES6)中新增了块级作用域,使用let声明的变量只能在块级作用域里访问,有“暂时性死区”的特性(也就是说声明前不可用)。
块级作用域:大部分包含{}的都可以构成块级作用域,但是函数和对象不构成块级作用域(函数属于的是函数作用域而不是块级作用域,对象的话,又不能在里面let)
作用域链:内层作用域->外层作用域->…->全局作用域
其中对于变量的查找只能是由内到外的
let、const、var的区别:①重复声明 ②变量提升 ③暂时性死区 ④块级作用域 ⑤window对象的属性和方法(全局作用域中)
var允许重复声明,let、const不允许
var会提升变量的声明到作用域的顶部,但let和const不会(说白了就是let和const没有变量提升) (无论是常量还是变量最好还是声明后再进行定义)
只要作用域内存在let、const,它们所声明的变量或常量就会自动“绑定”这个区域,不再受外部作用域的影响
【简单说明一下:这个暂时性死区说白了就是在函数作用域内部声明的变量将会与对应的函数作用域进行绑定(当然没有的话还是会向上查找),当函数内部存在重复声明或者变量提示时,就算外面的作用域已经声明了这个变量,它的运行还是认准的该函数作用域中的声明情况】
全局作用域中,var声明的变量,通过function声明的函数,会自动变为window对象的变量,属性或方法,但const和let不会
var没有块级作用域,let和const有块级作用域