跳到主要内容

博客

代码人生:编织技术与生活的博客之旅

1. java基本数据类型和他们的封装类

八种基本数据类型,int ,double ,long ,float, short,byte,character,boolean

对应的封装类型是:Integer ,Double ,Long ,Float, Short,Byte,Character,Boolean

1.1 补充

1.1.1 Java 5 (2004) - 重大变革

  • 自动装箱/拆箱 :基本类型与包装类自动转换
// Java 5之前
Integer i = new Integer(10);
int j = i.intValue();

// Java 5之后
Integer i = 10; // 自动装箱
int j = i; // 自动拆箱
  • 值缓存 :引入包装类的缓存机制(Integer缓存-128~127)

1.1.2 Java 8 (2014) - 函数式与工具增强

  • 无符号运算 :为Integer、Long添加无符号操作方法
int unsigned = Integer.parseUnsignedInt("4294967295");
long result = Integer.toUnsignedLong(-1);
  • 函数式接口 :虽然不直接针对基本类型,但影响了它们的用法
  • Stream API :需要频繁使用包装类进行装箱操作

1.1.3 Java 9 (2017) - 废弃构造方法

  • 废弃包装类的构造方法
// Java 9之前
Integer i = new Integer(10);

// Java 9之后推荐使用
Integer i = Integer.valueOf(10); // 使用缓存
Integer i = 10; // 自动装箱

1.1.4 Java 10+ - 局部变量类型推断

  • var关键字 :但有限制
var i = 10;        // 推断为int
var integer = 10; // 推断为Integer(自动装箱)

1.1.5 Java 17/21 - 稳定与性能优化

  • 模式匹配instanceof模式匹配简化类型检查
  • 性能优化 :持续优化自动装箱和缓存机制
  • Valhalla项目预备 :为值类型做准备,可能改变基本类型的未来
总结阅读需 2 分钟

==与equals的主要区别是:

  • ==常用于比较原生类型,而equals()方法用于检查对象的相等性。
  • 如果==和equals()用于比较对象,当两个引用地址相同,==返回true。而equals()可以返回true或者false主要取决于重写实现。

最常见的一个例子,字符串的比较,不同情况==和equals()返回不同的结果。

equals()方法最重要的一点是,能够根据业务要求去重写,按照自定义规则去判断两个对象是否相等。

重写equals()方法的时候,要注意一下hashCode是否会因为对象的属性改变而改变,否则在使用散列集合储存该对象的时候会碰到坑!!理解equals()方法的存在是很重要的。

1.1.1 字符串字面量比较

String s1 = "hello";
String s2 = "hello";
String s3 = "he" + "llo";

System.out.println(s1 == s2); // true - 指向字符串常量池的同一个对象
System.out.println(s1 == s3); // true - 编译期优化,同样指向常量池
System.out.println(s1.equals(s2)); // true - 内容相同
System.out.println(s1.equals(s3)); // true - 内容相同

1.1.2 new String() 创建对象

String s1 = "hello";
String s2 = new String("hello");
String s3 = new String("hello");

System.out.println(s1 == s2); // false - s1在常量池,s2在堆内存
System.out.println(s2 == s3); // false - 两个不同的堆内存对象
System.out.println(s1.equals(s2)); // true - 内容相同
System.out.println(s2.equals(s3)); // true - 内容相同

1.1.3 运行时拼接的字符串

String s1 = "hello";
String s2 = "he";
String s3 = s2 + "llo"; // 运行时拼接

System.out.println(s1 == s3); // false - s3在堆内存中
System.out.println(s1.equals(s3)); // true - 内容相同

1.1.4 intern() 方法的影响

String s1 = "hello";
String s2 = new String("hello");
String s3 = s2.intern(); // 将s2放入常量池

System.out.println(s1 == s2); // false
System.out.println(s1 == s3); // true - intern()后指向同一个常量池对象
System.out.println(s1.equals(s2)); // true

1.1.5 更复杂的例子

String s1 = "java";
String s2 = "ja";
String s3 = "va";
String s4 = s2 + s3; // 运行时拼接
String s5 = "ja" + "va"; // 编译期优化
final String s6 = "ja";
String s7 = s6 + "va"; // 编译期优化(s6是final)

System.out.println(s1 == s4); // false
System.out.println(s1 == s5); // true
System.out.println(s1 == s7); // true
System.out.println(s1.equals(s4)); // true

1.1.6 空字符串和null的比较

String s1 = "";
String s2 = new String("");
String s3 = null;

System.out.println(s1 == s2); // false
System.out.println(s1.equals(s2)); // true
System.out.println(s1.equals(s3)); // false
// System.out.println(s3.equals(s1)); // NullPointerException!

1.2 详细说明

1.2.1 使用==比较有两种情况

比较基础数据类型(Java中基础数据类型包括八中:short,int,long,float,double,char,byte,boolen):

这种情况下,==比较的是他们的值是否相等。

引用间的比较:

在这种情况下,==比较的是他们在内存中的地址,也就是说,除非引用指向的是同一个new出来的对象,此时他们使用 ==去比较得到true,否则,得到false。

1.2.2 使用equals进行比较

equals追根溯源,是Object类中的一个方法,在该类中,equals的实现也仅仅只是比较两个对象的内存地址是否相等,但在一些子类中,如:String、Integer 等,该方法将被重写。

1.2.3 以 String类为例子说明 eqauls==的区别:

在开始这个例子之前,同学们需要知道JVM处理String的一些特性。

Java的虚拟机在内存中开辟出一块单独的区域,用来存储字符串对象,这块内存区域被称为字符串缓冲池。

当使用 String a = "abc"这样的语句进行定义一个引用的时候,首先会在字符串缓冲池中查找是否已经相同的对象,如果存在,那么就直接将这个对象的引用返回给a,如果不存在,则需要新建一个值为"abc"的对象,再将新的引用返回a。

String a = new String("abc");这样的语句明确告诉JVM想要产生一个新的String对象,并且值为"abc",于是就 在堆内存中的某一个小角落开辟了一个新的String对象 。

即使多次调用 new String("abc");

  • ==在比较引用的情况下,会去比较两个引用的内存地址是否相等。
    String str1 = "abc"; // true
String str2 = "abc";

System.out.println(str1 == str2); // true 因为在str2赋值之前,str1的赋值操作就已经在内存中创建了一个值为"abc"的对象了,然后str2将会与str1指向相同的地址。
System.out.println(str1.equals(str2)); // true

String str2 = new String("abc");
System.out.println(str1 == str2); // false
System.out.println(str1.equals(str2)); // true

以上代码将会输出

false true第一个true: **第二个true:**因为 String已经重写了 equals方法:为了方便大家阅读我贴出来,并且在注释用进行分析: public boolean equals(Object anObject) {

//如果比较的对象与自身内存地址相等的话 //就说明他两指向的是同一个对象 //所以此时equals的返回值跟==的结果是一样的。 if (this == anObject) { return true; } //当比较的对象与自身的内存地址不相等,并且 //比较的对象是String类型的时候 //将会执行这个分支 if (anObject instanceof String) { String anotherString = (String)anObject; int n = value.length; if (n == anotherString.value.length) { char v1[] = value; char v2[] = anotherString.value; int i = 0; //在这里循环遍历两个String中的char while (n-- != 0) { //只要有一个不相等,那么就会返回false if (v1[i] != v2[i]) return false; i++; } return true; } } return false; } 进行以上分析之后,就不难理解第一段代码中的实例程序输出了。

总结阅读需 5 分钟

2.1 展现信息

Spring Web应用中,获取和处理数据是控制器的任务,将数据渲染到HTML中并在浏览器中展现是视图的任务。为了支撑taco的创建页面,需要构建如下组件:

  • 用来定义taco配料属性的领域类
  • 用来获取配料信息并将其传递至视图的Spring MVC控制器类
  • 用来在用户的浏览器中渲染配料列表的视图模版

2.1.1 构建领域类

在我们的Taco Cloud中,领域对象包括Taco设计,组成这些设计的配料,顾客,以及顾客下的订单。

定义配料如下:

Spring阅读需 1 分钟

唯一不变的就是变化 - 希腊哲学家拉克利特(heraclitus)

Spring框架在不断的变化,以解决现代应用开发中的问题,微服务,反应式编程,Spring Boot等等。

1.1什么是Spring

任何实际的应用程序都是由很多组件组成的,每个组件负责整个应用功能的一部分,这些组件需要与其他的应用元素进行协调以完成自己的任务。当程序运行时,需要以某种方式创建并引入这些组件。

Spring的核心是提供了一个容器(Container),通常称为Spring应用上下文(Spring application context),会创建和管理应用组件--bean, 会在Spring应用上下文中装配在一起,从而形成一个完整的应用程序。

将bean装配在一起的行为是通过一种基于依赖注入(Dependency Injection, DI)的模式实现的。此时,组件不会再去创建他所依赖的组件并管理他们的生命周期,使用依赖注入的应用依赖于单独的实体(容器)来创建和维护所有的组件,并将其注入到需要他们的bean中。通常,这是通过构造器参数和属性访问方法来实现的。

以前Spring通过一个或多个XML文件来进行依赖的定义,现在一般通过基于注解的方式。

1.2 初始化Spring应用

这里我认为无论通过那种方式,只要达到目的即可,没必要学习太多创建项目的方式。

所以,不同于书中介绍,我们使用Intellij Idea来创建

1.2.1 初始化项目

1763297780017

创建好之后,等待依赖下载完毕

1.2.2 Spring项目目录结构

1763297836102

  • maven相关
    • graddle和maven使用的都比较多,这里为了和书里面保持一致,用maven
    • mvnw和mvnw.cmd: 是Maven包装器脚本(wrapper)
    • pom.xml:maven构建规范
  • Spring SpringBoot
    • TacoCloudApplication.java SpringBoot的主类
  • resources
    • static: 存放静态内容
    • templates: 渲染内容到浏览器的模板文件,我们选用的Thymeleaf就是一个模板的库
    • application.properties 配置属性的地方
  • 测试
    • TacoCloudApplicationTests.java 简单的测试类

探索构建规范

<packaging>, 这里将应用构建成了一个可执行的jar文件,而不是war文件,因为所有的Java云平台都能够运行可执行的JAR文件,所以Spring Initializr基于云思维,默认都是使用JAR打包方式,当然你可以修改。

<parent>,<version>表名项目以 spring-boot-starter-parent作为其父POM,这个父POM为Spring项目常用的一些库提供了依赖管理。

<dependencies>元素下面声明了四个依赖,这些大都是我们之前创建项目的时候选择的

也可以看到好几个是starter依赖(Spring Boot Starter依赖本身不包含库代码,传递性的拉取其他库),其好处:

  1. 构建文件会显著减小并且易于管理,这样不必为每个所需的库都声明依赖
  2. 能够根据所提供的功能来思考依赖,而不是根据库的名称。
  3. 不必担心版本的问题

最后 pom.xml里面还有一个Spring Boot插件:

  • 提供了一个maven goal,允许我们使用Maven来运行应用
  • 确保依赖的所有库都会包含在可执行文件中,并且能够保证它们在运行时类路径下是可用的
  • 在jar中生成一个manifest文件,将引导类声明为可执行的JAR主类

引导应用

我们通过可执行的JAR文件的形式来运行应用,所以需要有一个主类,它将会在JAR运行的时候被执行。同时还需要一个最小化的Spring配置,以引导该应用。这就是TacoCloudApplication所做的事儿。

TacoCloudApplication的 @SpringBootApplication是一个组合注解:

  • @SpringBootConfiguration
    • 将类声明为配置类,其实就是 @Configuration的特殊形式
  • @EnableAutoConfiguration:启用Spring Boot的自动配置
  • @ComponentScan:启用组件扫描

TacoCloudApplication的main方法,是JAR文件执行的时候要运行的方法,这里会调用 SpringApplication 的静态run方法,后者会真正执行引用的引导过程,创建Spring应用上下文,传递的参数:

  • 一个是配置类(传递给run的配置类不一定要和引导类相同,但这是最便利,最典型的做法)
  • 一个是命令行参数

测试应用

这个测试类会执行必要的检查,确保Spring应用上下文能够成功加载

1.3 编写Spring应用

目标:

  • 添加一个控制器类,处理主页相关的请求
  • 添加一个视图模板,用来定义主页看起来是什么样子

1.3.1 处理web请求

Spring自带了一个强大的web框架,SpringMVC。

SpringMVC的核心是控制器概念,控制器是处理请求并以某种方式进行信息相应的类。在面向浏览器的应用中,控制器会填充可选的数据模型并将请求传递给一个视图,以便于生成返回给浏览器中的HTML。

下面是一个简单的控制器,用来处理对根路径("/")的请求,并将这些请求转发到主页视图。

package com.chengxuyuancd.tacocloud.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class HomeController {
@GetMapping("/")
public String home() {
return "home";
}
}

  • @Controller其实并没有做太多的事儿,主要目的是为了让 @ComponentScan将这个类识别为一个组件。所以这里Spring会自动发现它,并创建一个 HomeController的实例作为Spring应用上下文中的bean。
  • 而实际上,@Service, @Component, @Repository都可以做到和 @Controller一样的效果
  • home()是一个简单的控制器方法,带有 @GetMapping注解,如果针对"/" 发送HTTP GET请求,则这个方法将会处理请求,返回String类型的home值。
  • home()方法返回的值将会被解析成视图的逻辑名,视图可以有多种实现方式,这里将会使用Thymeleaf模板
  • 模板的名称由逻辑视图名派生而来,加上"/templates/"前缀和".html"后缀,最终形成的模板路径将是:"/templates/home.html"

1.3.2 定义视图

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Taco CLoud</title>
</head>
<body>
<h1>Welcome to...</h1>
<img th:src="@{/images/TacoCloud.png}">
</body>
</html>

这里使用了Thymeleaf的th:src属性和 @{...}表达式,用来引用相对于上下文路径的图片作为img的参数。

Spring中静态资源的地址默认是: /src/main/resources/static

1.3.3 测试控制器

一般的测试,对HTML页面进行断言比较困难,但Spring测试提供了一些支持。

我们期望测试对根路径("/")发送一个HTTP GET请求并期望得到成功的结果,结果应该包含"Welcome to..."

package com.chengxuyuancd.tacocloud;

import com.chengxuyuancd.tacocloud.controller.HomeController;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.test.web.servlet.MockMvc;

import static org.hamcrest.Matchers.containsString;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

@WebMvcTest(HomeController.class)
class TacoCloudApplicationTests {

@Autowired
private MockMvc mockMvc;

@Test
void contextLoads() {
}

@Test
void testHomePage() throws Exception {
mockMvc.perform(get("/"))
.andExpect(status().isOk())
.andExpect(view().name("home"))
.andExpect(content().string(containsString("Welcome to...")));
}
}

这里我们没有使用 @SpringBootTest 注解,而是使用 @WebMvcTest,他会让测试在Spring MVC应用的上下文中执行,具体的是,这里会将HomeController注册到Spring MVC中,这样就可以向他发送请求了。

@WebMvcTest也会为测试Spring MVC应用提供Spring环境支持,仿造了Spring MVC的运行机制,测试类被注入了一个MockMvc,能够让测试实现mockup。

测试testHomePage()方法中,首先使用MockMvc对象对"/"根路径发送HTTP GET请求,设置的预期如下:

  • 相应应该返回HTTP 200(OK)状态码
  • 视图的逻辑名称应该是home
  • 渲染后的视图应该包含文本"Welcome to..."

1.3.4 构建和运行测试

这里我提供两种我常用的方式:

  1. 直接运行 TacoCloudApplication.java文件
  2. 使用maven运行: ./mvnw spring-boot:run

控制台会打印出来Spring的ASCII字符,以及Tomcat已经在8080端口启动的日志

1763386158951

1.3.5 Spring Boot DevTools

DevTools为开发人员提供了很多便利:

  1. 代码变更后应用会自动重启
  2. 面相浏览器的资源(如模板,JavaScript,样式表)等发生变化的时候,自动刷新浏览器
  3. 自动禁用模板缓存
  4. 如果使用H2数据库,内置了H2的控制台

1.3.6 回顾一下

我们执行了如下步骤:

  • 使用Spring Initializr创建初始的项目结构
  • 编写控制器类处理针对主页的请求
  • 定义一个视图模板来渲染主页
  • 编写一个简单的测试类来验证工作符合预期

1.4 Spring 概览

1.4.1 Spring核心框架

Spring的核心中有一个是Spring MVC

数据持久化的基础操作

最新版的Spring中,添加了反应式(reactive)风格编程的支持,包括Spring WebFlux

1.4.2 Spring Boot

1.4.3 Spring Data

关系型数据库(JPA),文档数据库(Mongo),图数据库(Neo4j)

1.4.4 Spring Security

1.4.5 Spring Integration 和 Spring Batch

1.4.6 Spring Cloud

Spring阅读需 8 分钟

1. java 基础

1.1 java 语言基础

1.1.1 基础语法与面向对象

1.1.1.1 重载与重写的区别
1.1.1.2 == 和 equals
1.1.1.3 String, StringBuilder,StringBuffer
1.1.1.4 Java 中的异常

1.1.2 集合类

1.1.2.1 java 的数据结构
1.1.2.2 java 的集合类
1.1.2.3 HashMap 的原理

1.1.3 Lambda 表达式

1.1.4 反射以及泛型

1.1.4.1 反射
1.1.4.2 泛型

1.1.5 网络编程

1.1.5.1 BIO, NIO, AIO

1.1.6 IO 流

1.1.6.1 IO 流

1.1.7 数据结构与算法

1.1.7.1 排序算法
1.1.7.2 字符串类
1.1.7.3 搜索

1.2 并行相关

1.2.1 原理

1.2.1.1 ThreadLocal 的原理

1.2.1.2 解释悲观锁与乐观锁

1.2.1.3 synchronized 原理

1.2.1.4 synchronized 锁升级

1.2.1.5 对比 synchronized 和 volatile

1.2.1.6 对比 synchronized 和 lock

1.2.2 线程池

1.2.2.1 线程池的核心参数

  • 七个参数
    • 核心线程数
    • 最大线程数
    • 存活时间
    • 存活时间单位
    • 工作队列
    • 线程工厂
    • 拒绝策略
      • AbortPolicy
      • CallerRunsPolicy
      • DiscardOldestPolicy
      • DiscardPolicy

3. JVM 虚拟机

3.1 堆内存结构

3.2 垃圾回收算法

2. spring 生态

spring

IoC 和 DI

Spring bean

Spring 中的容器是线程安全的吗
作用域
bean 生命周期
bean 的循环依赖问题

AOP

AoP 底层实现
JDK 动态代理与 CgLib 动态代理
Spring 事务
事务失效
事务的传播行为

Spring MVC

核心组件
  • DispatchServlet
  • HandlerMapping
  • HandlerAdapter
  • Handler
  • ViewResolver
请求执行流程是什么样
拦截器
Interceptor 与 Filter
如何处理异常
常用注解

spring boot

理解

starter

配置的优先级

自动配置的原理

如何自定义 starter

MyBatis 以及 MyBatis-Plus

spring cloud

Nacos

Nacos 服务注册流程
Nacos 分级存储模型
Nacos 与 Eureka 的区别

OpenFeign

OpenFeign 的服务调用流程

Ribbon,Spring LoadBalancer 负载均衡

限流

Hystrix 和 Sentinel
如何利用 Sentinel 配置限流
滑动窗口算法

Gateway 相关,路由断言,过滤器

网关作用
实现原理
路由断言类型
过滤器实现方式

3. 数据库

sql 基本语法

mysql 中 char 和 varchar 的区别

什么是事务以及事务的四大特性

并发事务会引发哪些问题,如何解决,越高越好吗

mysql 索引

Mysql 数据库索引的数据结构,B+tree 索引结构的特点

聚簇索引(聚集索引),二级索引(非聚簇索引,辅助索引)

回表查询

为什么 MySQL 索引结构是 B+tree

索引优化

索引创建的原则

索引失效的场景

最左前缀法则

SQL 新能分析以及优化

如何定位慢 SQL

  • SkyWalking

如何知道 SQL 语句的执行性能,以及索引是否生效

4. 中间件

redis

基础

redis 常见数据类型

集群

redis 主从集群与分片集群的区别

持久化

持久化策略

内存淘汰策略

redis 数据删除

redis 是单线程吗

为什么单线程快

redis 红锁

MQ

为什么要使用 MQ

MQ 应用场景

MQ 如何保障发送消息可靠

MQ 如何保障消费消息可靠

MQ 死信队列(延迟队列)

如何防止消息重复消费

如何解决消息解压问题

RabbitMQ 的消息模式

elasticsearch

正向索引

倒排索引

ES 索引文档的过程

ES 搜索步骤

ES 相对 mysql 的特点是什么

ES 集群情况

ES 采用什么数据结构

5. CICD

docker

基础

docker 与虚拟机的区别
常用命令
数据卷
默认网络模式
docker compose

K8S

常用组件

tomcat

Nginx

什么是正向代理

什么是反向代理

CDN

Nginx 负载均衡

Nginx 限流

基础知识

git

git 本地仓库,远程仓库

git 工作原理

处理冲突

分支如何管理,创建分支有什么规则

Maven

maven 用来做什么

maven 规约

maven 的生命周期

密码学

加密算法

有哪些加密算法
对称加密,非对称加密以及哈希摘要
签名算法
项目中密码如何存储
DES, AES, SM4
RSA, ECDSA, SM2

web

HTTP 协议

Session 会话跟踪的原理

分布式

理论

CAP 定理

BASE 理论

二阶段提交

SEATA 模式

柔性事务,刚性事务

分布式锁

什么是分布式锁

分布式锁的实现方案

如何选择分布式锁的方案

redisson 分布式锁如何应用

如何实现分布式锁的可重入

如何提升分布式锁的性能

总结阅读需 4 分钟

1. 并发编程的基础知识

  • 并发和并行的区别?

1.1 并发模型

1.1.1 抢占式

总控制权在操作系统手中,操作系统会轮流询问每一个任务是否需要使用 CPU,需要使用的话就让他使用,不过在一定时间之后,操作系统会剥夺当前任务的 CPU 使用前,把他排在询问队列的最后,再去询问下一个任务

  • 例如
    • windows

1.1.2 协作式

一个任务得到了 CPU 时间,除非他自己放弃使用 CPU,否则将完全霸占 CPU,所以任务之间需要写作,使用一段时间 CPU 然后放弃,其他任务也是如此,这样才能保证系统正常运行

  • 例如
    • 嵌入式系统,例如早期的游戏机系统

1.2 同步编程原语(Windows)

1.2.1 用户态和内核态

  • 用户态: CriticalSection, SRWLock, InterLocked

  • 内核态: Mutex, Event, Semaphore, AsyncIO

  • 优缺点

    • 用户态:速度快,无法命名,无法设置超时,容易卡死
    • 内核态:速度慢(用户/内核态切换),可命名,可设置超时
  • 推荐参考书

    • Windows Via C++
  • 问题:大的分布式系统,访问共享资源,如何加锁

  • 答:把锁放在数据里面,利用数据库的锁和事务的特性就解决了

1.3 关键字

  • Volatile 关键字(嵌入式系统非常重要)

    • 提醒编译器他后面所定义的变量随时都可能发生改变,因此编译后的程序每次需要存储或者读取这个变量的时候,都会直接从变量地址中读取数据,如果没有 Volatile 关键字,则编译器可能优化读取和存储,可能暂时使用寄存器中的值,如果这个变量由别的程序更新了的话,将出现不一致现象。
    • 无锁编程
  • 无锁编程

    • 利用 Compare and Swap 原子操作
    • 不推荐使用,可移植性和可读性都有问题

1.4 异步编程模型

  • 案例

    • 阿里 Dubbo 中间件系统
    • Nginx
    • Linux: epoll
    • Windows: AsyncIO
  • 多线程能解决 C10K 问题吗?

    • 有十万个并发,多线程扛不住,线程的调度资源就很大
    • 线程的本身也有很大的开销
    • 线程不是很多的时候,可以做线程池的优化

异步编程可以加快系统处理速度 IO 密集型,而不是 CPU 密集型的话,用异步可以极大提高效率

  • 如何利用单线程支持多任务?
  • 建立正确的异步世界观
  • HTTP 服务器案例

1.5 高并发系统如何优化

  • 降低所的粒度和锁的持有时间
  • 使用读写分离锁
  • 确保顺序获取锁
    • TA: x, y
    • TB: x, y
  • 重要场景要设置超时等待时间,不能无线等
  • 减少对写共享资源的访问依赖
    • 宁可做多份,分散开,也不要大量挤在一起去访问
  • 同步变异步优化 CPU 使用率,避免过多的线程切换浪费 CPU 资源
    • 线程切换的成本比较大

2. Map/Reduce

2.1 简介

  • 为什么要 Map/Reduce?

  • Map/Reduce 是什么

    • Map: 分解工作,把一个大任务分解成独立不相关,可以并行的子任务并完成处理
    • Reduce:把每个子任务的处理结果汇总成最终的结果
  • 单机版矩阵乘法的例子

    • a[i][j] = i行 * j列
    • 结合稀疏矩阵的运算
    • GPU 更快
  • 单机版 TopK 计算的例子

    • 一亿个数,找到最大的 500 个
      • 排序 nlog(n)
      • 拆成 100 份, 100 万个数找最大的 500 个
  • 作业

    • 自己去搭建一个单机版的 hadoop 集群
    • 跑单词统计
    • 实在不行,用 python 的 map reduce 函数,实现超简化版本的

2.2 Hadoop 与 Spark 的区别

  • Hadoop 实质上更多是一个分布式数据基础设施: 它将巨大的数据集分派到一个由普通计算机组成的集群中的多个节点进行存储,意味着您不需要购买和维护昂贵的服务器硬件。
  • Hadoop 还会索引和跟踪这些数据,让大数据处理和分析效率达到前所未有的高度。
  • Spark,则是那么一个专门用来对那些分布式存储的大数据进行处理的工具,它并不会进行分布式数据的存储。
    • 在内存中做流数据处理
  • Hadoop 更庞大
    • 包含分布式文件系统
    • spark 可以替换 hadoop 的某个组件,hadoop 提供的是整个分布式处理的基础服务,包括分布式存储,工作的断点继续,工作记录的保存,包括 map reduce 模块
    • 是一个大而全的东西,所以他慢

2.2 为什么 Spark 速度更快?

  • MapReduce 是分步对数据进行处理的: 从集群中读取数据,进行一次处理,将结果写到集群,从集群中读取更新后的数据,进行下一次的处理,将结果写到集群,等等…
  • Spark 会在内存中以接近“实时”的时间完成所有的数据分析:“从集群中读取数据,完成所有必须的分析处理,将结果写回集群,完成。
  • Hadoop 可以和 Spark 一起工作,也可以各自独立与其他工具组合。
  • 什么时候用 Spark,Hadoop
    • 有些是实时流处理,可以忍受丢数据,但要快速得到结果,结果不必是最优的,用 Spark
    • 每天有海量的商业数据做分析,数据在内存里面装不下,用 Hadoop,可以一块一块的处理,保存之后在处理,中间结果也可以有效保存下来

3. 分布式系统设计简介

3.1 CAP

  • 关于 CAP 问题

    • C = Consistency 一致性
    • A = Availability 可用性
    • P = Partition Tolerance 容错性
  • CPA 三个指标不可能被同时满足,最多同时满足其中两项。

  • CAP 场景分析

    • 假设我们使用一台服务器 A 对外提供存储服务,为了避免服务器宕机导致服务不可用,我们又部署了服务器 B 做为备份。每次用户往 A 写入数据时,A 同步在 B 上写入备份。用户在读取数据时可以任意从 A 或 B 读取写入的历史和最新数据。
    • 故障发生,A 和 B 之间网络中断导致无法通信,用户往 A 写入数据时,B 没有更新,针对这种情况我们策略该如何设计?
      • 牺牲可用性 A:A 停止数据写入服务,但是依然可以读取,一致性得到保证。
      • 牺牲一致性 C:A 选择不把数据写入 B 就向用户返回操作成功。可用性得到保证。
      • 弱 CPA 可以满足,比如放弃强一致性而选择最终一致性。比如淘宝大促时的各种限流开关。

3.2 传统服务架构的局限性

  • 单机性能处理有上限
  • 负载分配不均衡。
  • 开发、升级成本高
  • 部署成本高
  • 维护成本高

3.3 微服务架构简介

  • 拆分服务,通过 RPC 通讯进行协同工作
  • 负载更均衡,服务弹性更强。
  • 理论吞吐量更高
  • 开发部署效率更高
  • 缺点
    • 通讯成本高
    • 依赖导致的部署成本
    • 服务治理和故障调试

3.4 分布式系统常见优化方案

  • 动静分离
    • CDN
  • 多级访问,缓存读优化
  • 牺牲强一致性,改为最终一致性。
  • 同步变异步,通过队列传递消息。

4. 分布式存储系统设计案例

4.1 常见挑战

  • 数据的高效检索
  • 数据的高效存储
  • 数据冗余与效率的平衡
  • 灾难恢复
  • 小文件存储优化

分布式存储系统设计案例

4.2 解决方案

  • 元数据(meta data)通过数据库独立存储,通过元数据访问实际存储数据。
  • 增量存储与压缩合并
    • 增量
    • 如果一直变,增量做合并
  • 通过专门算法降低冗余存储
    • 早期亚马逊,盛大做云存储,数据都是一式三份,很浪费
    • EMC(易安信,2016 年,EMC 公司被戴尔科技(Dell Technologies) 以 670 亿美元的天价收购,现在叫 Dell EMC)搞了一个很牛逼的算法,一份数据只要拿出来三分之一做冗余即可,把一份数据打成 12 块,12 块数据存储在 12 块硬盘上,只要没有 4 块硬盘同时坏,那就没问题,因为他们有运维脚本,实时监控数据的完整性,有哪一份数据,找到的块数少于 12 块,如果找到的只有 9 块,那会把剩余的 3 块恢复出来,只要硬盘坏的速度没有运维脚本扫描的速度快,那么就是安全的。当时他们一个数据中心,差不多 12 万 块硬盘,一个数据中心,5000 台服务器,每台服务器,24 块硬盘,差不多一年坏 1%,一年坏 1000 块左右
  • 运维脚本实时监控扫描数据完整性
  • 小文件直接数据库存储,或者合并存储,在元数据中记载偏移量和大小信息。
    • 小文件容易浪费空间

4. 从数字货币谈共识机制

4.1 简介

  • 比特币的区块链本质是分布式账本
  • 分布式账本面临谁来记账的问题
  • 比特币运行的环境毫无信任可言
  • 比特币愿意支付什么代价
    • 计算成本
    • 不要求实时计算,效率可以牺牲。

4.2 共识机制的实现

  • 完成工作量证明,首先证明的拿到记账权。
  • 将交易记录打包生成新区块,同时广播告诉其它节点。同时系统对记账者发放奖励,俗称“挖矿”
  • 其它节点收到广播后对新区块就行校验,接收后继续广播,最终全网接收,交易确认。

5. 设计模式

5.1 设计模式常见面试题目

  • 如何编写一个线程安全的单例
  • 什么是 IOC,IOC 的优点与应用举例
    • 通过配置文件控制代码的行为
    • 现在是通过注解来
  • 工厂模式应用场景
    • 配置文件告诉要什么,就生产什么
  • 面向接口编程
  • 开放闭合原则的应用案例
  • 推荐教材:大话设计模式

5.2 面试经验分享

  • 精简建立,突出重点
  • 不打无准备之仗
  • 白板编程先确认题目,明确思路,先实现再优化
  • 架构问题想办法往经典套路靠拢

6. 作业

并发面试阅读需 10 分钟

1. 创建项目

npm i -g @tarojs/cli

npm info @tarojs/cli

>taro init 01-base-taro
Taro 即将创建一个新项目!
Need help? Go and open issue: https://tls.jd.com/taro-issue-helper

? 请输入项目介绍 01-base-taro
? 请选择框架 (Use arrow keys)
React
PReact
Vue3
Solid
? 是否需要使用 TypeScript (Y/n)n
? 是否需要编译为 ES5No
? 请选择 CSS 预处理器(Sass/Less/Stylus) (Use arrow keys)
Sass
Less
Stylus

? 请选择包管理工具 (Use arrow keys)
❯ yarn
pnpm
npm
cnpm
? 请选择编译工具 (Use arrow keys)
Webpack5
Vite
? 请选择模板源 (Use arrow keys)
Gitee(最快)
Github(最新)
CLI 内置默认模板
自定义
社区优质模板源
❯ 默认模板
mobx
pwa
react-NutUI(NutUI + React 模板(https://nutui.jd.com/react/))
react-native
react-native-harmony
redux
(Move up and down to reveal more choices)


✔ 初始化 git 成功
执行安装项目依赖 yarn install, 需要一会儿...
yarn install v1.22.22
info No lockfile found.
[1/4] Resolving packages...
warning @tarojs/helper > @swc/register@0.1.10: Use @swc-node/register instead

...



[4/4] Building fresh packages...
success Saved lockfile.
$ husky
Done in 145.78s.
安装项目依赖成功
创建项目 01-base-taro 成功!
请进入项目目录 01-base-taro 开始工作吧!😝
(base) jiexu:002-coder-why-taro/ (master✗) $ cd 01-base-taro                                                                                   [20:56:14]
(base) jiexu:01-base-taro/ (master✗) $ yarn [21:00:14]
yarn install v1.22.22
[1/4] 🔍 Resolving packages...
success Already up-to-date.
$ husky
Done in 0.84s.

1.1 项目运行

  • VSCODE需要安装插件:ESLint, Prettier

  • 用H5打开

yarn dev:h5
  • 小程序打开
yarn dev:weapp

1.1.1 编译运行

  • Taro编译分为 devbuild模式

    • dev 模式(增加 --watch参数) 将会监听文件修改
    • build模式(去掉 --watch参数) 将不会监听文件修改,并会对代码进行压缩打包
  • dev命令启动Taro项目的开发环境

    • pnpm run dev:h5 启动H5端
    • pnpm run dev:weapp 启动小程序端
  • build 命令可以把Taro代码编译成不同端的代码,然后再对应的开发工具中查看效果

    • H5直接在浏览器中可以查看效果
    • 微信小程序需要在《微信开发工具》打开根目录下dist查看效果
    • RN应用需要参考《React Native端开发流程》

1.1.2 项目目录结构

1.2 Taro+React开发规范

  • 为了实现多端兼容,综合考虑编译速度,运行性能等因素,Taro约定了如下开发规范
    • 页面文件遵循 React组件(JSX)规范
    • 组件标签靠近小程序规范(但遵从大驼峰,并导包),详见Taro组件规范
    • 接口能力(JS API)靠近小程序规范,但需要将前缀wx替换为Taro(需要导包),详见Taro接口规范
    • 数据绑定及事件处理同React规范,同时补充了APP及页面的生命周期
    • 为了兼容多端运行,建议使用Flex布局进行开发,推荐使用px单位(750设计稿)
    • 在React中使用Taro内置组件前,必须从 @tarojs/components进行引入
    • 文档直接查看Taro的官网文档: https://docs.taro.zone/docs

1.3 webpack编译配置(config)

  • https://docs.taro.zone/docs/config

  • https://docs.taro.zone/docs/config-detail

  • 编译配置存放于项目根目录下的config目录中,包含三个文件:

    • index.js 是通用配置
    • dev.js 是项目开发时的配置
    • prod.js 是项目生产环境时的配置
  • 常用的配置

    • projectName: 项目名称
    • date: 项目创建时间
    • designWidth: 设计稿尺寸
    • sourceRoot: 项目源码目录
    • outputRoot: 项目产出目录
    • defineConstants: 定义全局的变量(DefinePlugin)
    • alias: 配置路径别名
    • h5:webpackChain: webpack配置
    • h5:devServer: 开发者服务配置

1.3.1 定义常量

// config/index.js
defineConstants: {
VERSION: "'1.0.0'"
},
// .eslintrc
"globals": {
"definePageConfig": "readonly",
"defineAppConfig": "readonly",
"VERSION": "readonly"
}
// src/pages/index/index.jsx
console.log(VERSION)

1.3.2 alias

import path from 'path'

...

alias: {
"@": path.resolve(__dirname, '..', 'src'),
},

1.4 全局配置(app.config.js)

Taro学习笔记前端阅读需 3 分钟

本地智能助手使用手册

1. 启动

​ 其中1.1 Docker Desktop应该设置了开机自动启动,只需要检查下容器是否打开。

1.2 需要启动

1.1 启动Docker Desktop

​ 在搜索栏中搜索 Docker Desktop,直接点击打开,

image-20250430153423704

然后找到containers,找到这个postgres对应的actions,是否为正方形(表示开启状态),如果是三角形需要点击一下启动 5-7

然后就不用管了,可以把它最小化,以免干扰

1.2 启动项目后端

进入文件夹:G:/jie/08-09-Electronic-Industry-agent-2/backend/, 右键, 选择显示更多选项: 5-1

点击 “Open Git Bash here”: 5-2

输入命令:python main.py

可以看到应该会出现如下,表示程序后端启动成功: 5-3

1.3 启动项目前端

进入文件夹:G:/jie/08-09-Electronic-Industry-agent-2/, 右键, 选择显示更多选项: 5-4

点击 “Open Git Bash here”: 5-2

输入命令:npm run dev

可以看到应该会出现如下,表示程序前端启动成功: 且箭头指向的地址就是网页地址,找到浏览器打开: 5-5

找到任意的浏览器,输入: http://localhost:3000,并回车即可看到页面

5-6

2. 简单使用

我们是一个数据处理助手,所以需要先上传处理所需要的文件 ​

2.1 需要先上传文件

右上角有一个悬浮按钮,可以上传文件 5-8

在上传文件页面,目前规定了可以上传哪些文件用来解析,除此之外的文件是没法解析的 5-9

上传某个文件的时候,就是从文件夹中选择,然后点击上传并对比, 如果你的文件非常大,比如几十M或者几百M,会很慢,请理解,需要一行一行做对比,可以把网页放在那里,等过一段时间来看, 5-10

下图是对比之后,哪些需要做新增,删除和修改,然后会给出一个excel来 5-11

2.2 使用AI

目前只支持三种业务场景,我提前在页面上给出了

2.2.1 OLT低效分析

直接点击 我提供的一个按钮,可以快速分析

2.2.2 二级分光器能否开通FTTR

点击我提供的一个按钮,然后修改这个二级分光器的名称,点击发送即可拿到结果 5-12

2.2.3 查看ONU用户能否开通FTTR

同样点击我的一个按钮,修改 ONU用户名称即可 5-14

智能助手使用交付阅读需 3 分钟

0. 快速链接

1. 安装Taro

  • 目前Taro仅提供一种开发方式:安装 Taro 命令行工具(Taro CLI)进行开发

  • Taro CLI 依赖于 Node.js 环境。

  • 如果Node.js环境存在,输入 npm i -g @tarojs/cli 安装, 安装好之后,输入 taro验证:

(base) jiexu:~/ $ taro                                               [12:01:03]
👽 Taro v4.1.6

2. 初始化项目

2.1 命令行初始化

  • taro init
Taro 即将创建一个新项目!
Need help? Go and open issue: https://tls.jd.com/taro-issue-helper

? 请输入项目名称! 001-docs-larning-notes
? 请输入项目介绍 will add latter
? 请选择框架 (Use arrow keys)
❯ React
PReact
Vue3
Solid
? 是否需要使用 TypeScript ? (Y/n)
? 请选择 CSS 预处理器(Sass/Less/Stylus) 无
? 请选择包管理工具 npm
? 请选择编译工具 Webpack5
? 请选择模板源 Github(最新)
✔ 拉取远程模板仓库成功!
? 请选择模板
wxcloud(云开发模板)
wxplugin
youshu(腾讯有数统计模板(https://nervjs.github.io/taro/docs/youshu))
❯ 默认模板
mobx
pwa
react-NutUI(NutUI + React 模板(https://nutui.jd.com/react/))
(Move up and down to reveal more choices)

3. 认识项目

3.1 入口组件

  • 每一个 Taro 项目都有一个入口组件和一个入口配置,我们可以在入口组件中设置全局状态/全局生命周期,一个最小化的入口组件会是这样:
// src/app.js
import React, { Component } from 'react'
import './app.css'

class App extends Component {
render() {
// this.props.children 是将要会渲染的页面
return this.props.children
}
}

// 每一个入口组件都必须导出一个 React 组件
export default App
// src/app.js
import Vue from 'vue'
import './app.css'

const App = {
render(h) {
// this.$slots.default 是将要会渲染的页面
return h('block', this.$slots.default)
},
}

export default App

每一个入口组件(例如 app.js)总是伴随一个全局配置文件(例如 app.config.js),我们可以在全局配置文件中设置页面组件的路径、全局窗口、路由等信息,一个最简单的全局配置如下:

export default {
pages: ['pages/index/index'],
}
export default {
pages: ['pages/index/index'],
}

你可能会注意到,不管是 React 还是 Vue,两者的全局配置是一样的。

这是因为在配置文件中,Taro 并不关心框架的区别,Taro CLI 会直接在编译时在 Node.js 环境直接执行全局配置的代码,并把 export default 导出的对象序列化为一个 JSON 文件。

因此,我们必须保证配置文件是在 Node.js 环境中是可以执行的,不能使用一些在 H5 环境或小程序环境才能运行的包或者代码,否则编译将会失败。

3.2 页面组件

页面组件是每一项路由将会渲染的页面,Taro 的页面默认放在 src/pages 中,每一个 Taro 项目至少有一个页面组件

一个简单的页面组件如下:

//src/pages/index/index.jsx

import { View } from '@tarojs/components'
class Index extends Component {
state = {
msg: 'Hello World!',
}

onReady() {
console.log('onReady')
}

render() {
return <View>{this.state.msg}</View>
}
}

export default Index
// src/pages/index/index.vue

<template>
<view> {{ msg }} </view>
</template>

<script>
export default {
data() {
return {
msg: 'Hello World!',
}
},
onReady() {
console.log('onReady')
},
}
</script>

与react和vue的细微差别

  • onReady 生命周期函数。这是来源于微信小程序规范的生命周期,表示组件首次渲染完毕,准备好与视图交互。Taro 在运行时将大部分小程序规范页面生命周期注入到了页面组件中,同时 React 或 Vue 自带的生命周期也是完全可以正常使用的。

  • View 组件。这是来源于 @tarojs/components 的跨平台组件。相对于我们熟悉的 div、span 元素而言,在 Taro 中我们要全部使用这样的跨平台组件进行开发。

和入口组件一样,每一个页面组件(例如 index.vue)也会有一个页面配置(例如 index.config.js),我们可以在页面配置文件中设置页面的导航栏、背景颜色等参数,一个最简单的页面配置如下:

// src/pages/index/index.config.js
export default {
navigationBarTitleText: '首页',
}

3.3 自定义组件

  • 我们先把首页写好,首页的逻辑很简单:把论坛最新的帖子展示出来。
// src/pages/index/index.jsx

import Taro from '@tarojs/taro'
import React from 'react'
import { View } from '@tarojs/components'
import { ThreadList } from '../../components/thread_list'
import api from '../../utils/api'

import './index.css'

class Index extends React.Component {
config = {
navigationBarTitleText: '首页',
}

state = {
loading: true,
threads: [],
}

async componentDidMount() {
try {
const res = await Taro.request({
url: api.getLatestTopic(),
})
this.setState({
threads: res.data,
loading: false,
})
} catch (error) {
Taro.showToast({
title: '载入远程数据错误',
})
}
}

render() {
const { loading, threads } = this.state
return (
<View className="index">
<ThreadList threads={threads} loading={loading} />
</View>
)
}
}

export default Index
// src/pages/index/index.vue

<template>
<view class="index">
<thread-list :threads="threads" :loading="loading" />
</view>
</template>

<script>
import Vue from 'vue'
import Taro from '@tarojs/taro'
import api from '../../utils/api'
import ThreadList from '../../components/thread_list.vue'
export default {
components: {
'thread-list': ThreadList,
},
data() {
return {
loading: true,
threads: [],
}
},
async created() {
try {
const res = await Taro.request({
url: api.getLatestTopic(),
})
this.loading = false
this.threads = res.data
} catch (error) {
Taro.showToast({
title: '载入远程数据错误',
})
}
},
}
</script>
  • 在我们的首页组件里,还引用了一个 ThreadList 组件,我们现在来实现它:
// src/components/thread_list.jsx

import React from 'react'
import { View, Text } from '@tarojs/components'
import { Thread } from './thread'
import { Loading } from './loading'

import './thread.css'

class ThreadList extends React.Component {
static defaultProps = {
threads: [],
loading: true,
}

render() {
const { loading, threads } = this.props

if (loading) {
return <Loading />
}

const element = threads.map((thread, index) => {
return (
<Thread
key={thread.id}
node={thread.node}
title={thread.title}
last_modified={thread.last_modified}
replies={thread.replies}
tid={thread.id}
member={thread.member}
/>
)
})

return <View className="thread-list">{element}</View>
}
}

export { ThreadList }
src/components/thread.jsx



import Taro, { eventCenter } from '@tarojs/taro'
import React from 'react'
import { View, Text, Navigator, Image } from '@tarojs/components'

import api from '../utils/api'
import { timeagoInst, Thread_DETAIL_NAVIGATE } from '../utils'

class Thread extends React.Component {
handleNavigate = () => {
const { tid, not_navi } = this.props
if (not_navi) {
return
}
eventCenter.trigger(Thread_DETAIL_NAVIGATE, this.props)
// 跳转到帖子详情
Taro.navigateTo({
url: '/pages/thread_detail/thread_detail',
})
}

render() {
const { title, member, last_modified, replies, node, not_navi } = this.props
const time = timeagoInst.format(last_modified * 1000, 'zh')
const usernameCls = `author ${not_navi ? 'bold' : ''}`

return (
<View className="thread" onClick={this.handleNavigate}>
<View className="info">
<View>
<Image src={member.avatar_large} className="avatar" />
</View>
<View className="middle">
<View className={usernameCls}>{member.username}</View>
<View className="replies">
<Text className="mr10">{time}</Text>
<Text>评论 {replies}</Text>
</View>
</View>
<View className="node">
<Text className="tag">{node.title}</Text>
</View>
</View>
<Text className="title">{title}</Text>
</View>
)
}
}

export { Thread }
// src/components/thread_list.vue

<template>
<view className="thread-list">
<loading v-if="loading" />
<thread
v-else
v-for="t in threads"
:key="t.id"
:node="t.node"
:title="t.title"
:last_modified="t.last_modified"
:replies="t.replies"
:tid="t.id"
:member="t.member"
/>
</view>
</template>

<script>
import Vue from 'vue'
import Loading from './loading.vue'
import Thread from './thread.vue'
export default {
components: {
loading: Loading,
thread: Thread,
},
props: {
threads: {
type: Array,
default: [],
},
loading: {
type: Boolean,
default: true,
},
},
}
</script>
// src/components/thread.vue

<template>
<view class="thread" @tap="handleNavigate">
<view class="info">
<view>
<image :src="member.avatar_large | url" class="avatar" />
</view>
<view class="middle">
<view :class="usernameCls"> {{member.username}} </view>
<view class="replies">
<text class="mr10">{{time}}</text>
<text>评论 {{replies}}</text>
</view>
</view>
<view class="node">
<text class="tag">{{node.title}}</text>
</view>
</view>
<text class="title">{{title}}</text>
</view>
</template>

<script>
import Vue from 'vue'
import { eventCenter } from '@tarojs/taro'
import Taro from '@tarojs/taro'
import { timeagoInst, Thread_DETAIL_NAVIGATE } from '../utils'
import './thread.css'
export default {
props: ['title', 'member', 'last_modified', 'replies', 'node', 'not_navi', 'tid'],
computed: {
time() {
return timeagoInst.format(this.last_modified * 1000, 'zh')
},
usernameCls() {
return `author ${this.not_navi ? 'bold' : ''}`
},
},
filters: {
url(val) {
return 'https:' + val
},
},
methods: {
handleNavigate() {
const { tid, not_navi } = this.$props
if (not_navi) {
return
}
eventCenter.trigger(Thread_DETAIL_NAVIGATE, this.$props)
// 跳转到帖子详情
Taro.navigateTo({
url: '/pages/thread_detail/thread_detail',
})
},
},
}
</script>

  • 这里可以发现我们把论坛帖子渲染逻辑拆成了两个组件,并放在 src/components 文件中,因为这些组件是会在其它页面中多次用到。 拆分组件的力度是完全由开发者决定的,Taro 并没有规定组件一定要放在 components 文件夹,也没有规定页面一定要放在 pages 文件夹。

  • 另外一个值得注意的点是:我们并没有使用 div/span 这样的 HTML 组件,而是使用了 View/Text 这样的跨平台组件。

3.4 路由与 Tabbar

  • src/components/thread 组件中,我们通过
Taro.navigateTo({ url: '/pages/thread_detail/thread_detail' })

跳转到帖子详情,但这个页面仍未实现,现在我们去入口文件配置一个新的页面:

// src/app.config.js

export default {
pages: ['pages/index/index', 'pages/thread_detail/thread_detail'],
}

然后在路径 src/pages/thread_detail/thread_detail 实现帖子详情页面,路由就可以跳转,我们整个流程就跑起来了:

// src/pages/thread_detail/thread_detail

import Taro from '@tarojs/taro'
import React from 'react'
import { View, RichText, Image } from '@tarojs/components'
import { Thread } from '../../components/thread'
import { Loading } from '../../components/loading'
import api from '../../utils/api'
import { timeagoInst, GlobalState } from '../../utils'

import './index.css'

function prettyHTML (str) {
const lines = ['p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6']

lines.forEach(line => {
const regex = new RegExp(`<${line}`, 'gi')

str = str.replace(regex, `<${line} class="line"`)
})

return str.replace(/<img/gi, '<img class="img"')
}

class ThreadDetail extends React.Component {
state = {
loading: true,
replies: [],
content: '',
thread: {}
} as IState

config = {
navigationBarTitleText: '话题'
}

componentWillMount () {
this.setState({
thread: GlobalState.thread
})
}

async componentDidMount () {
try {
const id = GlobalState.thread.tid
const [{ data }, { data: [ { content_rendered } ] } ] = await Promise.all([
Taro.request({
url: api.getReplies({
'topic_id': id
})
}),
Taro.request({
url: api.getTopics({
id
})
})
])
this.setState({
loading: false,
replies: data,
content: prettyHTML(content_rendered)
})
} catch (error) {
Taro.showToast({
title: '载入远程数据错误'
})
}
}

render () {
const { loading, replies, thread, content } = this.state

const replieEl = replies.map((reply, index) => {
const time = timeagoInst.format(reply.last_modified * 1000, 'zh')
return (
<View className='reply' key={reply.id}>
<Image src={reply.member.avatar_large} className='avatar' />
<View className='main'>
<View className='author'>
{reply.member.username}
</View>
<View className='time'>
{time}
</View>
<RichText nodes={reply.content} className='content' />
<View className='floor'>
{index + 1} 楼
</View>
</View>
</View>
)
})

const contentEl = loading
? <Loading />
: (
<View>
<View className='main-content'>
<RichText nodes={content} />
</View>
<View className='replies'>
{replieEl}
</View>
</View>
)

return (
<View className='detail'>
<Thread
node={thread.node}
title={thread.title}
last_modified={thread.last_modified}
replies={thread.replies}
tid={thread.id}
member={thread.member}
not_navi={true}
/>
{contentEl}
</View>
)
}
}

export default ThreadDetail


// src/pages/thread_detail/thread_detail.vue



<template>
<view class="detail">
<thread
:node="topic.node"
:title="topic.title"
:last_modified="topic.last_modified"
:replies="topic.replies"
:tid="topic.id"
:member="topic.member"
:not_navi="true"
/>
<loading v-if="loading" />
<view v-else>
<view class="main-content">
<rich-text :nodes="content | html" />
</view>
<view class="replies">
<view v-for="(reply, index) in replies" class="reply" :key="reply.id">
<image :src="reply.member.avatar_large" class="avatar" />
<view class="main">
<view class="author"> {{reply.member.username}} </view>
<view class="time"> {{reply.last_modified | time}} </view>
<rich-text :nodes="reply.content_rendered | html" class="content" />
<view class="floor"> {{index + 1}} 楼 </view>
</view>
</view>
</view>
</view>
</view>
</template>

<script>
import Vue from 'vue'
import Taro from '@tarojs/taro'
import api from '../../utils/api'
import { timeagoInst, GlobalState, IThreadProps, prettyHTML } from '../../utils'
import Thread from '../../components/thread.vue'
import Loading from '../../components/loading.vue'
import './index.css'
export default {
components: {
loading: Loading,
thread: Thread,
},
data() {
return {
topic: GlobalState.thread,
loading: true,
replies: [],
content: '',
}
},
async created() {
try {
const id = GlobalState.thread.tid
const [
{ data },
{
data: [{ content_rendered }],
},
] = await Promise.all([
Taro.request({
url: api.getReplies({
topic_id: id,
}),
}),
Taro.request({
url: api.getTopics({
id,
}),
}),
])
this.loading = false
this.replies = data
this.content = content_rendered
} catch (error) {
Taro.showToast({
title: '载入远程数据错误',
})
}
},
filters: {
time(val) {
return timeagoInst.format(val * 1000)
},
html(val) {
return prettyHTML(val)
},
},
}
</script>

到目前为止,我们已经实现了这个应用的所有逻辑,除去「节点列表」页面(在进阶指南我们会讨论这个页面组件)之外,剩下的页面都可以通过我们已经讲解过的组件或页面快速抽象完成。按照我们的计划,这个应用会有五个页面,分别是:

  1. 首页,展示最新帖子(已完成)
  2. 节点列表
  3. 热门帖子(可通过组件复用)
  4. 节点帖子 (可通过组件复用)
  5. 帖子详情 (已完成)

其中前三个页面我们可以把它们规划在 tabBar 里,tabBar 是 Taro 内置的导航栏,可以在 app.config.js 配置,配置完成之后处于的 tabBar 位置的页面会显示一个导航栏。最终我们的 app.config.js 会是这样:

// app.config.js


export default {
pages: [
'pages/index/index',
'pages/nodes/nodes',
'pages/hot/hot',
'pages/node_detail/node_detail',
'pages/thread_detail/thread_detail',
],
tabBar: {
list: [
{
iconPath: 'resource/latest.png',
selectedIconPath: 'resource/lastest_on.png',
pagePath: 'pages/index/index',
text: '最新',
},
{
iconPath: 'resource/hotest.png',
selectedIconPath: 'resource/hotest_on.png',
pagePath: 'pages/hot/hot',
text: '热门',
},
{
iconPath: 'resource/node.png',
selectedIconPath: 'resource/node_on.png',
pagePath: 'pages/nodes/nodes',
text: '节点',
},
],
color: '#000',
selectedColor: '#56abe4',
backgroundColor: '#fff',
borderStyle: 'white',
},
window: {
backgroundTextStyle: 'light',
navigationBarBackgroundColor: '#fff',
navigationBarTitleText: 'V2EX',
navigationBarTextStyle: 'black',
},
}
Taro学习笔记前端阅读需 10 分钟

Git 常用命令

1. Set up 仓库

  • 查看remote是哪个:git remote -v
  • 更换远程仓库: git remote set-url origin xxx
  • 移除远程仓库: git remote remove origin
git阅读需 1 分钟